【每周分享】以ADC芯片为例讲解易于开发调试的驱动初始化代
我们在使用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 registerPOWER_REG = 0x01, //power registerINTERFACE_REG = 0x02, //interface registerMODE0_REG = 0x03, //mode0 registerMODE1_REG = 0x04, //mode1 registerMODE2_REG = 0x05, //mode2 registerINPMUX_REG = 0x06, //input multiplexer registerOFCAL0_REG = 0x07, //offset calibration register 0OFCAL1_REG = 0x08, //offset calibration register 1OFCAL2_REG = 0x09, //offset calibration register 2FSCAL0_REG = 0x0A, //full-scale calibration register 0FSCAL1_REG = 0x0B, //full-scale calibration register 1FSCAL2_REG = 0x0C, //full-scale calibration register 2IDACMUX_REG = 0x0D, //IDAC multiplexer registerIDACMAG_REG = 0x0E, //IDAC magnitude registerREFMUX_REG = 0x0F, //reference multiplexer registerTDACP_REG = 0x10, //TDACP control registerTDACN_REG = 0x11, //TDACN control registerGPIOCON_REG = 0x12, //GPIO Connection registerGPIODIR_REG = 0x13, //GPIO direction registerGPIODAT_REG = 0x14, //GPIO data registerADC2CFG_REG = 0x15, //ADC2 configuration registerADC2MUX_REG = 0x16, //ADC2 input multiplexer registerADC2OFC0_REG = 0x17, //ADC2 offset calibration register 0ADC2OFC1_REG = 0x18, //ADC2 offset calibration register 1ADC2FSC0_REG = 0x19, //ADC2 full-scale calibration register 0ADC2FSC1_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) //RWtypedef enum {TIMEOUT_CONFIG_DISABLE = 0, //defaultTIMEOUT_CONFIG_ENABLE = 1,}ads1262_inerface_reg_timeout_config_e;#define ADS1262_INTERFACE_REG_STATUS(x) (((x) & 0x01) << 2) //RWtypedef enum {STATUS_CONFIG_DISABLE = 0,STATUS_CONFIG_ENABLE = 1, //default}ads1262_inerface_reg_status_config_e;#define ADS1262_INTERFACE_REG_CRC(x) ((x) & 0x03) //RWtypedef enum {CRC_CONFIG_DISABLE = 0,CRC_CONFIG_CHECKSUM_MODE = 1, //defaultCRC_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) //RWtypedef enum {RESET_CONFIG_CLEAR = 1,}ads1262_power_reg_reset_config_e;#define ADS1262_POWER_REG_VBIAS(x) (((x) & 0x01) << 1) //RWtypedef enum {VBIAS_CONFIG_DISABLE = 0, //defaultVBIAS_CONFIG_ENABLE = 1,}ads1262_power_reg_vbias_config_e;#define ADS1262_POWER_REG_INTREF(x) ((x) & 0x01) //RWtypedef 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) //RWtypedef enum {REFREV_CONFIG_NORMAL_POLARITY = 0, //defaultREFREV_CONFIG_REVERSE_POLARITY = 1,}ads1262_mode0_reg_refrev_config_e;#define ADS1262_MODE0_REG_RUNMODE(x) (((x) & 0x01) << 6) //RWtypedef enum {RUNMODE_CONFIG_CONTINUOUS_CONVERSION = 0, //defaultRUNMODE_CONFIG_PULSE_CONVERSION = 1,}ads1262_mode0_reg_runmode_config_e;#define ADS1262_MODE0_REG_CHOP(x) (((x) & 0x03) << 4) //RWtypedef enum {CHOP_CONFIG_DISABLE = 0, //defaultCHOP_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) //RWtypedef enum {DELAY_CONFIG_NO_DELAY = 0, //defaultDELAY_CONFIG_8_7US = 1, //8.7usDELAY_CONFIG_17US = 2, //17usDELAY_CONFIG_35US = 3, //35usDELAY_CONFIG_69US = 4,DELAY_CONFIG_139US = 5,DELAY_CONFIG_278US = 6,DELAY_CONFIG_555US = 7,DELAY_CONFIG_1_1MS = 8, // 1.1msDELAY_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) //RWtypedef 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) //RWtypedef enum {SBADC_CONFIG_CONNECT_ADC1 = 0, //defaultSBADC_CONFIG_CONNECT_ADC2 = 1,}ads1262_mode1_reg_sbadc_config_e;#define ADS1262_MODE1_REG_SBPOL(x) (((x) & 0x07) << 3) //RWtypedef enum {SBPOL_CONFIG_PULLUP_MODE = 0, //defaultSBPOL_CONFIG_PULLDOWN_MODE = 1,}ads1262_mode1_reg_sbpol_config_e;#define ADS1262_MODE1_REG_SBMAG(x) ((x) & 0x07) //RWtypedef enum {SBMAG_CONFIG_NO = 0, //defaultSBMAG_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) //RWtypedef enum {BYPASS_CONFIG_PGA_ENABLE = 0, //defaultBYPASS_CONFIG_PGA_BYPASS = 1,}ads1262_mode2_reg_bypass_config_e;#define ADS1262_MODE2_REG_GAIN(x) (((x) & 0x07) << 4) //RWtypedef enum {GAIN_CONFIG_1V = 0, //default,1V/VGAIN_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) //RWtypedef enum {DR_CONFIG_2_5SPS = 0, //2.5 SPS, data rateDR_CONFIG_5SPS = 1,DR_CONFIG_10SPS = 2,DR_CONFIG_16_6SPS = 3,DR_CONFIG_20SPS = 4, //defaultDR_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) //RWtypedef enum {MUXP_CONFIG_AIN0 = 0, //defaultMUXP_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) //RWtypedef enum {MUXN_CONFIG_AIN0 = 0,MUXN_CONFIG_AIN1 = 1, //defaultMUXN_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 registerwrite_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 registerwrite_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 registerwrite_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 registerwrite_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 registerwrite_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 registerwrite_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芯片等,同样可以以这种方式实现一劳永逸的操作。
---------------------
作者:dffzh
链接:https://bbs.21ic.com/icview-3475456-1-1.html?_dsign=9d19c4c8
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。
