51微控制器mcp4728驅動程式原始碼

veis發表於2024-10-16

概述

MCP4728有4個12位dac,無論使用者需要什麼電壓設定。它可以將dac的設定儲存到內部EEPROM中。一旦儲存到內部非易失性記憶體中,當DAC啟動時,將預設載入設定。MCP4728還允許使用者在兩種參考電壓源之間進行選擇。輸入電壓可以用來給V上電 (CC)引腳或內部的2.048V參考電壓。如果使用內部參考電壓,使用者可以選擇1倍或2倍增益作為輸出,電壓範圍為0V - 2.048或0V - 4.096V,根據應用需要。

呼叫依賴層次

原始碼

標頭檔案

#ifndef __DRV_MCP4728_H__
#define __DRV_MCP4728_H__

#include "typedef.h"

#define MAX_DAC_OUTPUT  (4095)

typedef enum 
{  
    VREF_VDD = 0x0, /* 5V */
    VREF_Internal  /* 2.048V */
} eVref;

// DAC參考電壓通道偏移值
#define REFOFFSET_CHANNEL_A 0x03
#define REFOFFSET_CHANNEL_B 0x02
#define REFOFFSET_CHANNEL_C 0x01
#define REFOFFSET_CHANNEL_D 0x00

/*
The write command is defined by using
three write command type bits (C2, C1, C0) and two
write function bits (W1, W0). The register selection bits
(DAC1, DAC0) are used to select the DAC channel.

DAC1 DAC0 Channels
0 0 Ch. A - Ch. D
0 1 Ch. B - Ch. D
1 0 Ch. C - Ch. D
1 1 Ch. D

bit 7  6  5  4  3
    C2 C1 C0 W1 W0
*/
#define DAC_CHANNEL_A		0x00
#define DAC_CHANNEL_B		0x02
#define DAC_CHANNEL_C		0x04
#define DAC_CHANNEL_D		0x06

/*
PD1, PD0 Power-Down selection bits:
00 = Normal Mode
01 = VOUT is loaded with 1 kΩ resistor to ground. Most of the channel circuits are powered off.
10 = VOUT is loaded with 100 kΩ resistor to ground. Most of the channel circuits are powered
off.
11 = VOUT is loaded with 500 kΩ resistor to ground. Most of the channel circuits are powered
off.
Note: See Table 4-7 and Figure 4-1 for more details.
*/
#define NORMAL_MODE             0x00
#define PULLDOWN_1K_POWEROFF    0x01
#define PULLDOWN_100K_POWEROFF  0x02
#define PULLDOWN_500K_POWEROFF  0x03

/*
GX Gain selection bit:
0 = x1 (gain of 1)
1 = x2 (gain of 2)
Note: Applicable only when internal VREF is selected. If VREF = VDD, the device uses a gain of 1
regardless of the gain selection bit setting.
 */
#define GAIN_X1     0x00
#define GAIN_X2     0X01


/*
UDAC DAC latch bit. Upload the selected DAC input register to its output register (VOUT):
0 = Upload. Output (VOUT) is updated.
1 = Do not upload.
Note: UDAC bit affects the selected channel only.
*/
#define DAC_UPLOAD      0x00
#define DAC_NOTUPLADE   0x01

typedef enum
{
    MCP_OK = 0x0,
    MCP_BUSY,
    MCP_READY,
    MCP_ERROR
} MCP_Status;

/**
 * @brief 讀取EERPROM中儲存的裝置地址
 * 
 * @return u8 返回裝置地址
 */
u8 MCP4728_ReadSlavAddress(void);

/**
 * @brief 軟體更新資料到暫存器
 * 
 * @return u8 MCP_ERROR表示操作失敗,MCP_OK表示成功
 */
u8 MCP4728_SoftUpdate(void);

/**
 * @brief 喚醒晶片
 * 
 * @return u8 MCP_ERROR表示操作失敗,MCP_OK表示成功
 */
u8 MCP4728_Wakeup(void);

/**
 * @brief 配置晶片
 * 
 * @return u8 MCP_ERROR表示操作失敗,MCP_OK表示成功
 */
u8 MCP4728_Config(void);

/**
 * @brief 復位晶片
 * 
 * @return u8 MCP_ERROR表示操作失敗,MCP_OK表示成功
 */
u8 MCP4728_Reset(void);

/**
 * @brief 設定從機地址
 * 
 * @param address 新從機地址
 * @return u8 返回操作碼
 */
u8 MCP4728_SetSlavAddress(u8 address);

/**
 * @brief 設定參考電壓
 * 
 * @param channel_offset 通道序號的偏移 
 * @param ref_type 設定的參考電壓型別
 * @return * u8 返回操作碼
 */
u8 MCP4728_SetRefVoltage(u8 channel_offset, eVref ref_type);


/**
 * @brief 透過DAC值設定相關通道的輸出電壓
 * 
 * @param channel DAC通道
 * @param dac_val DAC輸出值
 * @param vref DAC參考電壓
 * @param iswrite_eeprom 是否寫入EEPROM,0寫入,1不寫入
 * @return u8 返回操作碼
 */
u8 MCP4728_SetVoltage(u8 channel, u16 dac_val, eVref vref, u8 iswrite_eeprom);

/**
 * @brief 設定輸出DAC電壓
 * 
 * @param channel DAC通道
 * @param vol 電壓值
 * @param iswrite_eeprom 是否寫入EEPROM,0不寫入,1寫入
 * @return u8 返回操作碼
 */
u8 MCP4728_SetAnalogVol(u8 channel, float vol, u8 iswrite_eeprom);

#endif /* __DRV_MCP4728_H__ */

原始檔

#include "drv_mcp4728.h"
#include "i2c.h"
#include "config.h"
//#include "stdio.h"

// DAC最大解析度
#define MAX_DAC_VAL     4095
// DAC外部參考電壓
#define DAC_VDD         5.0f
// DAC內部參考電壓
#define DAC_VInternal   2.048f

// 從裝置地址(7bit,不含W/R bit),出廠預設地址碼[A0:A2]=000b,可以透過訪問EEPROM配置
#define MCP4728_SLAVE_ADDRESS   0x60
#define WRITE_MODE  			0x00
#define READ_MODE   			0X01

// 引數更新到EEPROM
#define EEPROM_UPDATE           0x01
#define EEPROM_NOTUPDATE        0x00

// 硬體直接接地,不控制,保留宏定義
#define LDAC_PIN
#define LDAC_OUT(x)        //do{LDAC_PIN=x;}while(0)
#define RDY_PIN             P17

// 就緒狀態
#define MCPRDYPIN_READY     1
// busy狀態
#define MCPRDYPIN_BUSY      0
// 獲取busy/RDY引腳狀態
#define READ_READY          (bit)RDY_PIN


// 命令字
#define MCP_CMD_RESET       0x06 // 復位
#define MCP_CMD_WAKEUP      0x09 // 喚醒
#define MCP_CMD_SOFTUPDATE  0x08 // 軟體更新I2C匯流排值到暫存器
#define MCP_CMD_READADDRESS 0x0c // 讀取儲存在EERPROM的裝置地址
#define MCP_CMD_SETADDRESS  0x60 // 向EEPROM寫入裝置地址
#define MCP_CMD_SETREFVOL   0x80 // 設定參考電壓
#define MCP_CMD_MULTIWRITE  0x40 // 多通道設定輸出電壓,不寫入EEPROM
#define MCP_CMD_SINGLEWRITE 0x58 // 單通道設定輸出電壓,寫入EEPROM

/**
 * @brief 寫一個位元組資料
 * 
 * @param dat 資料
 * @return s8 -1表示操作失敗,0表示操作成功
 */
static s8 MCP4728_WriteByte(u8 dat)
{
    I2C_Start();
    I2C_WriteByte((MCP4728_SLAVE_ADDRESS << 1) | WRITE_MODE);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        I2C_Stop();
        return -1;
    }
    I2C_WriteByte(dat);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        I2C_Stop();
        return -1;
    }
    I2C_Stop();
    return 0;
}

/**
 * @brief 配置晶片
 * 
 * @return u8 MCP_ERROR表示操作失敗,MCP_OK表示成功
 */
u8 MCP4728_Config(void)
{
    MCP4728_SetVoltage(DAC_CHANNEL_A, 0, VREF_VDD, TRUE);
    MCP4728_SetVoltage(DAC_CHANNEL_B, 0, VREF_VDD, TRUE);
    MCP4728_SetVoltage(DAC_CHANNEL_C, 0, VREF_VDD, TRUE);
    MCP4728_SetVoltage(DAC_CHANNEL_D, 0, VREF_VDD, TRUE);
    
    return MCP4728_Reset();
}


/**
 * @brief 復位晶片
 * 
 * @return u8 MCP_ERROR表示操作失敗,MCP_OK表示成功
 */
u8 MCP4728_Reset(void)
{
    s8 ret;
    if(READ_READY == MCPRDYPIN_BUSY)
        return MCP_ERROR;
	
    ret = MCP4728_WriteByte(MCP_CMD_RESET);
    if(ret == 0)
        return MCP_OK;
    
    return MCP_ERROR;
}

/**
 * @brief 喚醒晶片
 * 
 * @return u8 MCP_ERROR表示操作失敗,MCP_OK表示成功
 */
u8 MCP4728_Wakeup(void)
{
    s8 ret;
    if(READ_READY == MCPRDYPIN_BUSY)
        return MCP_ERROR;

    ret = MCP4728_WriteByte(MCP_CMD_WAKEUP);
    if(ret == 0)
        return MCP_OK;
    
    return MCP_ERROR;
}

/**
 * @brief 軟體更新資料到暫存器
 * 
 * @return u8 MCP_ERROR表示操作失敗,MCP_OK表示成功
 */
u8 MCP4728_SoftUpdate(void)
{
    s8 ret;
    if(READ_READY == MCPRDYPIN_BUSY)
        return MCP_ERROR;

    ret = MCP4728_WriteByte(MCP_CMD_SOFTUPDATE);
    if(ret == 0)
        return MCP_OK;
    
    return MCP_ERROR;
}

/**
 * @brief 讀取EERPROM中儲存的裝置地址
 * 
 * @return u8 返回裝置地址
 */
u8 MCP4728_ReadSlavAddress(void)
{
    u8 address = 0;
    if(READ_READY == MCPRDYPIN_BUSY)
        return MCP_ERROR;

    I2C_Start();
    I2C_WriteByte((MCP4728_SLAVE_ADDRESS << 1) | WRITE_MODE);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        I2C_Stop();
        return MCP_ERROR;
    }
    I2C_WriteByte(MCP_CMD_READADDRESS);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        I2C_Stop();
        return MCP_ERROR;
    }
    I2C_Start();
    I2C_WriteByte(0xc1);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        I2C_Stop();
        return MCP_ERROR;
    }
    address = I2C_ReadByte();
    //I2C_Ack(); /* 【bug】資料手冊有誤,此處不需要主機傳送ACK,如果傳送則會導致SDA一直被拉低,從而地址獲取錯誤 */
    I2C_Stop();
//    printf("address:%bu\n", (unsigned char)address);
    return address;
}


/**
 * @brief 設定從機地址
 * 
 * @param address 新從機地址
 * @return u8 返回操作碼
 */
u8 MCP4728_SetSlavAddress(u8 address)
{
    u8 tmp = (0x0e & MCP4728_SLAVE_ADDRESS) << 1; // 取得當前地址的A2-A0三個bit

    if(READ_READY == MCPRDYPIN_BUSY)
        return MCP_ERROR;

    I2C_Start();
    I2C_WriteByte((MCP4728_SLAVE_ADDRESS << 1) | WRITE_MODE);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        goto ERROR_RETURN;
    }

    I2C_WriteByte(0X61 | tmp);
    LDAC_OUT(0);

    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        goto ERROR_RETURN;
    }
    tmp = (address & 0X0e) << 1; // 取得新地址的A2-A0三個bit
    
    I2C_WriteByte(MCP_CMD_SETADDRESS | tmp | WRITE_MODE);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        goto ERROR_RETURN;
    }

    I2C_WriteByte(MCP_CMD_SETADDRESS | tmp | READ_MODE);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        goto ERROR_RETURN;
    }
    I2C_Stop();
    LDAC_OUT(1);

    return MCP_OK;

ERROR_RETURN:
    I2C_Stop();
    LDAC_OUT(1);
    return MCP_ERROR;
}

/**
 * @brief 設定參考電壓
 * 
 * @param channel_offset 通道序號的偏移 
 * @param ref_type 設定的參考電壓型別
 * @return u8 返回操作碼
 */
u8 MCP4728_SetRefVoltage(u8 channel_offset, eVref ref_type)
{
    u8 dat;
    dat = MCP_CMD_SETREFVOL | (ref_type << channel_offset);
    if(MCP4728_WriteByte(dat) == -1)
        return MCP_ERROR;
    
    return MCP_OK;
}

/**
 * @brief 透過DAC值設定相關通道的輸出電壓
 * 
 * @param channel DAC通道
 * @param dac_val DAC輸出值
 * @param vref DAC參考電壓
 * @param iswrite_eeprom 是否寫入EEPROM,0寫入,1不寫入
 * @return u8 返回操作碼
 */
u8 MCP4728_SetVoltage(u8 channel, u16 dac_val, eVref vref, u8 iswrite_eeprom)
{
    u8 send_data;
    u16 temp_vol = dac_val;

	if(channel > DAC_CHANNEL_D) return MCP_ERROR;
	
    temp_vol = (temp_vol > MAX_DAC_VAL) ? MAX_DAC_VAL : temp_vol;
    
    if(READ_READY == MCPRDYPIN_BUSY)
        return MCP_ERROR;

    I2C_Start();
    I2C_WriteByte((MCP4728_SLAVE_ADDRESS << 1) | WRITE_MODE);
    if(I2C_WaitAck())  // 器件無應答,結束通訊釋放I2C
    {
        I2C_Stop();
        return MCP_ERROR;
    }

    send_data = (iswrite_eeprom == 1) ? (MCP_CMD_SINGLEWRITE | channel) : (MCP_CMD_MULTIWRITE | channel);
    I2C_WriteByte(send_data);
    if(I2C_WaitAck())
    {
        I2C_Stop();
        return MCP_ERROR;
    }

    // 傳送[D11:D5]
    send_data = (vref << 7) | (dac_val >> 8) | (NORMAL_MODE << 5) | (GAIN_X1 << 4);
    I2C_WriteByte(send_data);
    if(I2C_WaitAck())
    {
        I2C_Stop();
        return MCP_ERROR;
    }

    // 傳送[D7:D0]
    send_data = dac_val & 0xFF;
    I2C_WriteByte(send_data);
    if(I2C_WaitAck())
    {
        I2C_Stop();
        return MCP_ERROR;
    }

    return MCP_OK;

}

/**
 * @brief 設定輸出DAC電壓
 * 
 * @param channel DAC通道
 * @param vol 電壓值
 * @param iswrite_eeprom 是否寫入EEPROM,0不寫入,1寫入
 * @return u8 返回操作碼
 */
u8 MCP4728_SetAnalogVol(u8 channel, float vol, u8 iswrite_eeprom)
{
    u16 dac_val;
    dac_val = (u16)(vol * MAX_DAC_VAL / ((vol > DAC_VInternal) ? DAC_VDD : DAC_VInternal));

    return MCP4728_SetVoltage(channel, dac_val, ((vol > DAC_VInternal) ? VREF_VDD : VREF_Internal), iswrite_eeprom);
}

I2C驅動原始碼

#ifndef __I2C_H__
#define __I2C_H__

#include "typedef.h"

/**
 * @brief 開啟I2C匯流排,預設處於空閒
 * 
 */
void I2C_Open(void);

/**
 * @brief 關閉I2C匯流排,預設處於空閒
 * 
 */
void I2C_Close(void);

/**
 * @brief 產生一個START訊號
 * 
 */
void I2C_Start(void);

/**
 * @brief 產生一個STOP訊號
 * 
 */
void I2C_Stop(void);

/**
 * @brief 產生一個ACK訊號
 * 
 */
void I2C_Ack(void);

/**
 * @brief 產生一個NACK訊號
 * 
 */
void I2C_NAck(void);

/**
 * @brief 寫一個位元組資料
 * 
 * @param dat 待傳輸的資料
 */
void I2C_WriteByte(u8 dat);

/**
 * @brief 讀取一個位元組資料
 * 
 * @return u8 返回讀取到的資料
 */
u8 I2C_ReadByte(void);

/**
 * @brief 等待應答訊號
 * 
 * @return u8 返回0表示正確應答,1表示無器件響應
 */
u8 I2C_WaitAck(void);

#endif /* __I2C_H__ */
#include "i2c.h"
#include "delay.h"
#include "app_cfg.h"

#if OLD_BOARD_PINMAP
#define SDA_PIN P16
#define SCL_PIN P17
#else /* 新板子埠定義 */
#define SDA_PIN P15
#define SCL_PIN P16
#endif 

#define HIGH    1
#define LOW     0

// I2C使用standard mode
// 停止訊號建立時間,單位us
#define STOP_CONDITION_SETUPTIME    (4)
// 起始訊號保持時間,單位us
#define START_CONDITION_HOLDTIME    (4)
// 起始訊號建立時間,單位us
#define START_CONDITION_SETUPTIME   (5)
// 時鐘高電平時間,單位us
#define CLOCK_HIGH_TIME             (4)
// 時鐘低電平時間,單位us
#define CLOCK_LOW_TIME              (5)
// 通用延時,單位us
#define I2C_DELAY_US                (4)

#define SDA_OUT(x)  do{SDA_PIN=x;}while(0)
#define SCL_OUT(x)  do{SCL_PIN=x;}while(0)
#define SDA_IN      (bit)SDA_PIN

/**
 * @brief 開啟I2C匯流排,預設處於空閒
 * 
 */
void I2C_Open(void)
{
    SCL_OUT(HIGH);
    SDA_OUT(HIGH);
}

/**
 * @brief 關閉I2C匯流排
 * 
 */
void I2C_Close(void)
{
    SCL_OUT(HIGH);
    SDA_OUT(HIGH);
}

/**
 * @brief 產生一個START訊號
 * 
 */
void I2C_Start(void)
{
    SCL_OUT(HIGH);
    SDA_OUT(HIGH);
    delay_us(START_CONDITION_SETUPTIME);
    SDA_OUT(LOW);
    delay_us(START_CONDITION_HOLDTIME);
    SCL_OUT(LOW);
}

/**
 * @brief 產生一個STOP訊號
 * 
 */
void I2C_Stop(void)
{
    SCL_OUT(LOW);
    SDA_OUT(LOW);
    delay_us(I2C_DELAY_US);
    SCL_OUT(HIGH);
    delay_us(STOP_CONDITION_SETUPTIME);
    SDA_OUT(HIGH);
}

/**
 * @brief 產生一個ACK訊號
 * 
 */
void I2C_Ack(void)
{
    SDA_OUT(LOW);
    delay_us(I2C_DELAY_US);       
    SCL_OUT(HIGH);
    delay_us(I2C_DELAY_US);
    SCL_OUT(LOW);
}

/**
 * @brief 產生一個NACK訊號
 * 
 */
void I2C_NAck(void)
{
    SDA_OUT(HIGH);
    delay_us(I2C_DELAY_US);       
    SCL_OUT(HIGH);
    delay_us(I2C_DELAY_US);
    SCL_OUT(LOW);
}

/**
 * @brief 寫一個位元組資料
 * 
 * @param dat 待傳輸的資料
 */
void I2C_WriteByte(u8 dat)
{
    u8 mask;  // 用於探測位元組內某一位值的掩碼變數

    for (mask = 0x80; mask != 0; mask >>= 1) // 從高位到低位依次進行
    {
        if ((mask & dat) == 0)  // 該位的值輸出到SDA上
            SDA_OUT(LOW);
        else
            SDA_OUT(HIGH);
        delay_us(CLOCK_LOW_TIME);
        SCL_OUT(HIGH);          // 拉高SCL
        delay_us(CLOCK_HIGH_TIME);             
        SCL_OUT(LOW);          // 再拉低SCL,完成一個位週期
    }
}

/**
 * @brief 讀取一個位元組資料
 * 
 * @return u8 返回讀取到的資料
 */
u8 I2C_ReadByte(void)
{
    u8 dat = 0;
    u8 mask;

    SDA_OUT(HIGH);  // 首先確保主機釋放SDA
    for (mask = 0x80; mask != 0; mask >>= 1) // 從高位到低位依次進行
    {
        delay_us(CLOCK_LOW_TIME);
        SCL_OUT(HIGH);      // 拉高SCL
        if(!SDA_IN)  // 讀取SDA的值
            dat &= ~mask; // 為0時,dat中對應位清零
        else                 
            dat |= mask;  // 為1時,dat中對應位置1
        delay_us(CLOCK_HIGH_TIME);          
        SCL_OUT(LOW);      // 再拉低SCL,以使從機傳送出下一位
    }
	
    return dat;
}

/**
 * @brief 等待應答訊號
 * 
 * @return u8 返回0表示正確應答,1表示無器件響應
 */
u8 I2C_WaitAck(void)
{
	u8 ACK = 1;

	SDA_OUT(HIGH);	/* CPU釋放SDA匯流排 */
	delay_us(I2C_DELAY_US);
	SCL_OUT(HIGH);	/* CPU驅動SCL = 1, 此時器件會返回ACK應答 */
	delay_us(I2C_DELAY_US);
	if (SDA_IN)	/* CPU讀取SDA口線狀態 */
	{
		ACK = 1;
	}
	else
	{
		ACK = 0;
	}
	SCL_OUT(LOW);
	delay_us(I2C_DELAY_US);

	return ACK;
}

相關文章