首页 > 编程语言 >51单片机mcp4728驱动程序源码

51单片机mcp4728驱动程序源码

时间:2024-10-16 14:24:11浏览次数:7  
标签:u8 DAC void define 单片机 源码 I2C OUT 驱动程序

概述

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;
}

标签:u8,DAC,void,define,单片机,源码,I2C,OUT,驱动程序
From: https://www.cnblogs.com/veis/p/18469850

相关文章