本帖最后由 dffzh 于 2025-8-1 13:22 编辑
#申请原创#
@21小跑堂
我们在使用ADC芯片时,往往都需要编写基于MCU和C语言的ADC芯片驱动代码,通过驱动代码以SPI总线等方式对ADC芯片的寄存器进行相关读写操作,并且读取ADC的采样数据。大部分ADC芯片的总线接口方式都是SPI,如下图所示:
在驱动代码中,ADC芯片的初始化代码是非常重要的,即对ADC芯片的寄存器进行相关配置的代码,那究竟通过C语言以什么样的方式编写初始化代码会比较好呢?会便于后面调试修改呢?会增加代码可读性呢?接下来我就以TI的ADC芯片ADS1262为例向大家展示一下我这边的初始化代码及相关宏定义及枚举操作等。不过在这之前,我们先来看下下面的初始化代码:
即将寄存器值reg_data写入相应的ADC寄存器中,如果后面需要通过修改寄存器值进行芯片调试,估计还得去打开芯片手册看下每个寄存器的位定义,然后计算一下十六进制值,最后再修改reg_data的值去更新寄存器。所以,这种代码操作方式的效率是不是比较低?而且还容易出错,即使你把魔法数字改成宏定义方式也无济于事。 接下来看看我实现的这种方式。 我们先来看看ADC芯片的寄存器,主要讲解如下6个寄存器的操作,包括如下:
要想在代码调试时快速且精准的进行寄存器值修改,那就是要在代码中明确现在需要修改的是哪个寄存器的哪个位配置,以及可以配置哪些值,因此代码的相关名称怎么命名和C语言的相关技术如何使用就显得尤为重要了。因此,我们就需要通过枚举,宏定义和位运算符等操作进行初始化代码的编写。 我们先用枚举方式定义好寄存器地址(写寄存器值即向寄存器地址写入数据),代码如下: /*ADC register address define*/
typedef enum
{
ID_REG = 0x00, //decvice identification register
POWER_REG = 0x01, //power register
INTERFACE_REG = 0x02, //interface register
MODE0_REG = 0x03, //mode0 register
MODE1_REG = 0x04, //mode1 register
MODE2_REG = 0x05, //mode2 register
INPMUX_REG = 0x06, //input multiplexer register
OFCAL0_REG = 0x07, //offset calibration register 0
OFCAL1_REG = 0x08, //offset calibration register 1
OFCAL2_REG = 0x09, //offset calibration register 2
FSCAL0_REG = 0x0A, //full-scale calibration register 0
FSCAL1_REG = 0x0B, //full-scale calibration register 1
FSCAL2_REG = 0x0C, //full-scale calibration register 2
IDACMUX_REG = 0x0D, //IDAC multiplexer register
IDACMAG_REG = 0x0E, //IDAC magnitude register
REFMUX_REG = 0x0F, //reference multiplexer register
TDACP_REG = 0x10, //TDACP control register
TDACN_REG = 0x11, //TDACN control register
GPIOCON_REG = 0x12, //GPIO Connection register
GPIODIR_REG = 0x13, //GPIO direction register
GPIODAT_REG = 0x14, //GPIO data register
ADC2CFG_REG = 0x15, //ADC2 configuration register
ADC2MUX_REG = 0x16, //ADC2 input multiplexer register
ADC2OFC0_REG = 0x17, //ADC2 offset calibration register 0
ADC2OFC1_REG = 0x18, //ADC2 offset calibration register 1
ADC2FSC0_REG = 0x19, //ADC2 full-scale calibration register 0
ADC2FSC1_REG = 0x1A, //ADC2 full-scale calibration register 1
}ads1262_reg_addr_e;
寄存器的名称命名方式可以采用“寄存器名称_REG”的方式,并在每个寄存器后面加上注释,备注一下寄存器的含义。 然后用枚举和宏定义方式定义好每个寄存器,比如以下定义INTERFACE寄存器的代码: /*interface register*/
#define ADS1262_INTERFACE_REG_TIMEOUT(x) (((x) & 0x01) << 3) //RW
typedef enum {
TIMEOUT_CONFIG_DISABLE = 0, //default
TIMEOUT_CONFIG_ENABLE = 1,
}ads1262_inerface_reg_timeout_config_e;
#define ADS1262_INTERFACE_REG_STATUS(x) (((x) & 0x01) << 2) //RW
typedef enum {
STATUS_CONFIG_DISABLE = 0,
STATUS_CONFIG_ENABLE = 1, //default
}ads1262_inerface_reg_status_config_e;
#define ADS1262_INTERFACE_REG_CRC(x) ((x) & 0x03) //RW
typedef enum {
CRC_CONFIG_DISABLE = 0,
CRC_CONFIG_CHECKSUM_MODE = 1, //default
CRC_CONFIG_CRC_MODE = 2,
}ads1262_inerface_reg_crc_config_e;
寄存器位的名称命名方式可以采用“ADC芯片名称_寄存器名称_REG_位名称”或者“寄存器名称_REG_位名称”,寄存器位值的名称命名方式可以采用“位名称_CONFIG_位值”,位值枚举的名称命名方式可以采用小写的“ADC芯片名称_寄存器名称_reg_位名称_config_e”或者“寄存器名称_reg_位名称_config_e”。 而宏定义里面的x的值即对应位值枚举里面的值,直接修改即可,比如: ADS1262_INTERFACE_REG_TIMEOUT(TIMEOUT_CONFIG_DISABLE)或者 ADS1262_INTERFACE_REG_TIMEOUT(TIMEOUT_CONFIG_ENABLE)。 其他5个寄存器的定义代码如下所示: /*power register*/
#define ADS1262_POWER_REG_RESET(x) (((x) & 0x01) << 4) //RW
typedef enum {
RESET_CONFIG_CLEAR = 1,
}ads1262_power_reg_reset_config_e;
#define ADS1262_POWER_REG_VBIAS(x) (((x) & 0x01) << 1) //RW
typedef enum {
VBIAS_CONFIG_DISABLE = 0, //default
VBIAS_CONFIG_ENABLE = 1,
}ads1262_power_reg_vbias_config_e;
#define ADS1262_POWER_REG_INTREF(x) ((x) & 0x01) //RW
typedef enum {
INTREF_CONFIG_DISABLE = 0,
INTREF_CONFIG_ENABLE = 1, //default
}ads1262_power_reg_intref_config_e;
/*mode0 register*/
#define ADS1262_MODE0_REG_REFREV(x) (((x) & 0x01) << 7) //RW
typedef enum {
REFREV_CONFIG_NORMAL_POLARITY = 0, //default
REFREV_CONFIG_REVERSE_POLARITY = 1,
}ads1262_mode0_reg_refrev_config_e;
#define ADS1262_MODE0_REG_RUNMODE(x) (((x) & 0x01) << 6) //RW
typedef enum {
RUNMODE_CONFIG_CONTINUOUS_CONVERSION = 0, //default
RUNMODE_CONFIG_PULSE_CONVERSION = 1,
}ads1262_mode0_reg_runmode_config_e;
#define ADS1262_MODE0_REG_CHOP(x) (((x) & 0x03) << 4) //RW
typedef enum {
CHOP_CONFIG_DISABLE = 0, //default
CHOP_CONFIG_INPUT_CHOP_ENABLE = 1,
CHOP_CONFIG_IDAC_ROTATION_ENABLE = 2,
CHOP_CONFIG_ALL_ENABLE = 3,
}ads1262_mode0_reg_chop_config_e;
#define ADS1262_MODE0_REG_DELAY(x) ((x) & 0x0F) //RW
typedef enum {
DELAY_CONFIG_NO_DELAY = 0, //default
DELAY_CONFIG_8_7US = 1, //8.7us
DELAY_CONFIG_17US = 2, //17us
DELAY_CONFIG_35US = 3, //35us
DELAY_CONFIG_69US = 4,
DELAY_CONFIG_139US = 5,
DELAY_CONFIG_278US = 6,
DELAY_CONFIG_555US = 7,
DELAY_CONFIG_1_1MS = 8, // 1.1ms
DELAY_CONFIG_2_2MS = 9,
DELAY_CONFIG_4_4MS = 10,
DELAY_CONFIG_8_8MS = 11,
}ads1262_mode0_reg_delay_config_e;
/*mode1 register*/
#define ADS1262_MODE1_REG_FILTER(x) (((x) & 0x07) << 5) //RW
typedef enum {
FILTER_CONFIG_SINC1_MODE = 0,
FILTER_CONFIG_SINC2_MODE = 1,
FILTER_CONFIG_SINC3_MODE = 2,
FILTER_CONFIG_SINC4_MODE = 3,
FILTER_CONFIG_FIR_MODE = 4, //default
}ads1262_mode1_reg_filter_config_e;
#define ADS1262_MODE1_REG_SBADC(x) (((x) & 0x07) << 4) //RW
typedef enum {
SBADC_CONFIG_CONNECT_ADC1 = 0, //default
SBADC_CONFIG_CONNECT_ADC2 = 1,
}ads1262_mode1_reg_sbadc_config_e;
#define ADS1262_MODE1_REG_SBPOL(x) (((x) & 0x07) << 3) //RW
typedef enum {
SBPOL_CONFIG_PULLUP_MODE = 0, //default
SBPOL_CONFIG_PULLDOWN_MODE = 1,
}ads1262_mode1_reg_sbpol_config_e;
#define ADS1262_MODE1_REG_SBMAG(x) ((x) & 0x07) //RW
typedef enum {
SBMAG_CONFIG_NO = 0, //default
SBMAG_CONFIG_0_5UA = 1,
SBMAG_CONFIG_2UA = 2,
SBMAG_CONFIG_10UA = 3,
SBMAG_CONFIG_50UA = 4,
SBMAG_CONFIG_200UA = 5,
SBMAG_CONFIG_10M = 6, // 10MΩresistor
}ads1262_mode1_reg_sbmag_config_e;
/*mode2 register*/
#define ADS1262_MODE2_REG_BYPASS(x) (((x) & 0x07) << 7) //RW
typedef enum {
BYPASS_CONFIG_PGA_ENABLE = 0, //default
BYPASS_CONFIG_PGA_BYPASS = 1,
}ads1262_mode2_reg_bypass_config_e;
#define ADS1262_MODE2_REG_GAIN(x) (((x) & 0x07) << 4) //RW
typedef enum {
GAIN_CONFIG_1V = 0, //default,1V/V
GAIN_CONFIG_2V = 1,
GAIN_CONFIG_4V = 2,
GAIN_CONFIG_8V = 3,
GAIN_CONFIG_16V = 4,
GAIN_CONFIG_32V = 5,
}ads1262_mode2_reg_gain_config_e;
#define ADS1262_MODE2_REG_DR(x) ((x) & 0x0F) //RW
typedef enum {
DR_CONFIG_2_5SPS = 0, //2.5 SPS, data rate
DR_CONFIG_5SPS = 1,
DR_CONFIG_10SPS = 2,
DR_CONFIG_16_6SPS = 3,
DR_CONFIG_20SPS = 4, //default
DR_CONFIG_50SPS = 5,
DR_CONFIG_60SPS = 6,
DR_CONFIG_100SPS = 7,
DR_CONFIG_400SPS = 8,
DR_CONFIG_1200SPS = 9,
DR_CONFIG_2400SPS = 10,
DR_CONFIG_4800SPS = 11,
DR_CONFIG_7200SPS = 12,
DR_CONFIG_14400SPS = 13,
DR_CONFIG_19200SPS = 14,
DR_CONFIG_38400SPS = 15,
}ads1262_mode2_reg_dr_config_e;
/*input multiplexer register*/
#define ADS1262_INPMUX_REG_MUXP(x) (((x) & 0x0F) << 4) //RW
typedef enum {
MUXP_CONFIG_AIN0 = 0, //default
MUXP_CONFIG_AIN1 = 1,
MUXP_CONFIG_AIN2 = 2,
MUXP_CONFIG_AIN3 = 3,
MUXP_CONFIG_AIN4 = 4,
MUXP_CONFIG_AIN5 = 5,
MUXP_CONFIG_AIN6 = 6,
MUXP_CONFIG_AIN7 = 7,
MUXP_CONFIG_AIN8 = 8,
MUXP_CONFIG_AIN9 = 9,
MUXP_CONFIG_AINCOM = 10,
MUXP_CONFIG_TSMP = 11,
MUXP_CONFIG_APSMP = 12,
MUXP_CONFIG_DPSMP = 13,
MUXP_CONFIG_TTSP = 14,
MUXP_CONFIG_FLOAT = 15,
}ads1262_inpmux_reg_muxp_config_e;
#define ADS1262_INPMUX_REG_MUXN(x) ((x) & 0x0F) //RW
typedef enum {
MUXN_CONFIG_AIN0 = 0,
MUXN_CONFIG_AIN1 = 1, //default
MUXN_CONFIG_AIN2 = 2,
MUXN_CONFIG_AIN3 = 3,
MUXN_CONFIG_AIN4 = 4,
MUXN_CONFIG_AIN5 = 5,
MUXN_CONFIG_AIN6 = 6,
MUXN_CONFIG_AIN7 = 7,
MUXN_CONFIG_AIN8 = 8,
MUXN_CONFIG_AIN9 = 9,
MUXN_CONFIG_AINCOM = 10,
MUXN_CONFIG_TSMN = 11,
MUXN_CONFIG_APSMN = 12,
MUXN_CONFIG_DPSMN = 13,
MUXN_CONFIG_TTSN = 14,
MUXN_CONFIG_FLOAT = 15,
}ads1262_inpmux_reg_muxn_config_e;
以上的代码都写在ADS1262.h文件里面,即ADC芯片驱动代码的头文件可以用“ADC芯片名称.h”或者“drv_ADC芯片名称.h”来命名。 接下来就是编写初始化代码接口了,对应写在ADS1262.c里面,同样地,ADC芯片驱动代码的源文件可以用“ADC芯片名称.c”或者“drv_ADC芯片名称.c”来命名。初始化函数接口可以用“ADC芯片名称_init”来命名。 以下即为ads1262_init函数接口,实现对ADC芯片的初始化配置操作: void ads1262_init(void)
{
uint8_t write_byte = 0x00;
ads1262_cs_set(ADS1262_CS_HIGH);
//config power register
write_byte = ADS1262_POWER_REG_RESET(RESET_CONFIG_CLEAR) \
| ADS1262_POWER_REG_VBIAS(VBIAS_CONFIG_ENABLE) \
| ADS1262_POWER_REG_INTREF(INTREF_CONFIG_ENABLE);
ads1262_write_register(POWER_REG, write_byte);
//config interface register
write_byte = ADS1262_INTERFACE_REG_TIMEOUT(TIMEOUT_CONFIG_DISABLE) \
| ADS1262_INTERFACE_REG_STATUS(STATUS_CONFIG_ENABLE) \
| ADS1262_INTERFACE_REG_CRC(CRC_CONFIG_DISABLE);
ads1262_write_register(INTERFACE_REG, write_byte);
//config mode0 register
write_byte = ADS1262_MODE0_REG_REFREV(REFREV_CONFIG_NORMAL_POLARITY) \
| ADS1262_MODE0_REG_RUNMODE(RUNMODE_CONFIG_CONTINUOUS_CONVERSION) \
| ADS1262_MODE0_REG_CHOP(CHOP_CONFIG_DISABLE) \
| ADS1262_MODE0_REG_DELAY(DELAY_CONFIG_NO_DELAY);
ads1262_write_register(MODE0_REG, write_byte);
//config mode1 register
write_byte = ADS1262_MODE1_REG_FILTER(FILTER_CONFIG_FIR_MODE) \
| ADS1262_MODE1_REG_SBADC(SBADC_CONFIG_CONNECT_ADC1) \
| ADS1262_MODE1_REG_SBPOL(SBPOL_CONFIG_PULLUP_MODE) \
| ADS1262_MODE1_REG_SBMAG(SBMAG_CONFIG_0_5UA);
ads1262_write_register(MODE1_REG, write_byte);
//config mode2 register
write_byte = ADS1262_MODE2_REG_BYPASS(BYPASS_CONFIG_PGA_ENABLE) \
| ADS1262_MODE2_REG_GAIN(GAIN_CONFIG_1V)\
| ADS1262_MODE2_REG_DR(DR_CONFIG_7200SPS);
ads1262_write_register(MODE2_REG, write_byte);
//config input multiplexer register
write_byte = ADS1262_INPMUX_REG_MUXP(MUXP_CONFIG_AIN0) \
| ADS1262_INPMUX_REG_MUXN(MUXN_CONFIG_AIN1);
ads1262_write_register(INPMUX_REG, write_byte);
ads1262_cs_set(ADS1262_CS_HIGH);
}
通过以上的基于位或运算符的代码操作,后面如果需要修改某个寄存器的某个位值,直接用枚举值复制粘贴进行替换即可,而且可以保证准确无误。 虽然一开始对照寄存器进行代码编写时会比较繁琐费时,但这是事半功倍的操作,代码的可读性和可维护性是比较好的。 以上是个人见解,有更好的方式也欢迎分享,比如使用表驱动法等也是非常好的,方法很多,归根结底,目的是增加可读性和可维护性。 另外,对于其他的外置芯片驱动初始化代码,包括DAC芯片和EEPROM芯片等,同样可以以这种方式实现一劳永逸的操作。 |
@21小跑堂 管理员,你好,求原创审核哦!