事先声明:本文提到的所有代码,均不可用作商业用途,仅供学习参考!另外请不要私信或者评论问我能不能分享整个代码文件,ps:这是不行的,因为AD7190用在了公司某光谱分析仪项目的核心功能里。有问题欢迎评论交流,有时间会回复。
-
AD7190早年主要用在电子秤、血压计等比较细分领域的场景里,有非常久远的历史,目前同家族系列已有新品替代,某些国产PIN TOPIN方案也已经十分成熟,如果批量商用建议用ADI同系列的新品或者国产平替。由于AD7190系列的采样精度和内置的各种信号处理功能比较强大,尤其是对微弱差分信号的处理十分有效,因此光学电子这个大类里,存在微弱ADC变化以及需要对光谱相对零点吸收量的检测方案,用AD7190再合适不过了,下文使用的基础驱动框架来自于ADI官网给出的例程与国内硬石嵌入式开发团队贡献的程序组合而成,外加一部分专门针对雅特力单片机重新编写的驱动一起组成,因此再次强调,不要用作商业用途,仅供学习参考!非常感谢硬石开发团队,以及本站另外一个博主@上山的路 提供的宝贵技术资源和解决问题的思路!
实测:AT32F403a、407 以及AT32F421等等几乎雅特力主流单片机方案都可以直接移植使用。但是要注意使用AT32标准库与AT32_Work_Bench库移植时,在SPI配置上会略有不同,一定要注意区分,这是个大坑,下文有提到!
使用AT32标准库与AT32_Work_Bench库移植时主要区别在于SPI配置过程中关于 gpio_pin_mux_config();函数的使用,这个函数非常坑。一、使用AT32标准库开发SPI时,配置如下:
void spi_user_init(void)
{
gpio_init_type gpio_initstructure;
spi_init_type spi_init_struct;
gpio_default_para_init(&gpio_initstructure);//初始化GPIO设置相关参数为默认值
spi_default_para_init(&spi_init_struct);//初始化所有SPI设置参数为默认值
/**SPI1 GPIO Configuration
PB12 ------> SPI2_CS
PB13 ------> SPI2_SCK
PB14 ------> SPI2_MISO
PB15 ------> SPI2_MOSI
*/
crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);//开启GPIO B组的时钟线
/* software cs, GPIO_12 as a general io to control cs */
gpio_initstructure.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;//推挽输出
gpio_initstructure.gpio_pull = GPIO_PULL_UP;//上拉输出
gpio_initstructure.gpio_mode = GPIO_MODE_OUTPUT;//输出模式
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;//强驱动电流
gpio_initstructure.gpio_pins = GPIO_PINS_12;//PB12
gpio_init(GPIOB, &gpio_initstructure);
gpio_bits_reset(GPIOB,GPIO_PINS_12);//GPIO_PINS_12 must be reseted ,if not,will can't use SPI to AD7190
/* sck,GPIO_13*/
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;//强驱动电流
gpio_initstructure.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;//推挽输出
gpio_initstructure.gpio_mode = GPIO_MODE_MUX;//复用GPIO模式,使用这种模式是因为硬件SPI是一个复用功能。
gpio_initstructure.gpio_pins = GPIO_PINS_13;//PB13
gpio_initstructure.gpio_pull = GPIO_PULL_NONE;//无上下拉
gpio_init(GPIOB, &gpio_initstructure);
/* miso */
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;//强驱动电流
gpio_initstructure.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;//推挽输出
gpio_initstructure.gpio_mode = GPIO_MODE_MUX;//复用GPIO模式,使用这种模式是因为硬件SPI是一个复用功能。
gpio_initstructure.gpio_pins = GPIO_PINS_14;//PB14
gpio_initstructure.gpio_pull = GPIO_PULL_NONE;//无上下拉
gpio_init(GPIOB, &gpio_initstructure);
/* mosi */
gpio_initstructure.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;//强驱动电流
gpio_initstructure.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;//推挽输出
gpio_initstructure.gpio_mode = GPIO_MODE_MUX;//复用GPIO模式,使用这种模式是因为硬件SPI是一个复用功能。
gpio_initstructure.gpio_pins = GPIO_PINS_15;//PB15
gpio_initstructure.gpio_pull = GPIO_PULL_NONE;//无上下拉
gpio_init(GPIOB, &gpio_initstructure);
/* spi2 */
spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX;//SPI传输模式
spi_init_struct.master_slave_mode = SPI_MODE_MASTER;//主机
spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_8; //8分频
spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;
spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;
spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_LOW;
spi_init_struct.clock_phase = SPI_CLOCK_PHASE_1EDGE;
spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
spi_init(SPI2, &spi_init_struct);
spi_enable(SPI2, TRUE);
}
-
这里使用的是硬件SPI1,CS使用软件操作,GPIO定义如上,如果后期对CS enable or
dis_enable没有需求,我的建议是初始化就拉低,这能有效避免后续因为CS控制时序不对、控制逻辑造成的天坑,也不用担心CS长期拉低会造成CLK和数据总线异常的问题,因为手册里对时序的要求如下:
只要CS不拉高,完全可以一个周期接着一个周期持续读取数据。至于其它几个CLK、MOSI、MISO引脚,我的建议是参考雅特力官方的demo,全部配置为GPIO_MODE_MUX复用模式,当然你也可以给MOSI写成GPIO_MODE_OUTPUT,MISO写成GPIO_MODE_INPUT,但是这样做会在下文提到的SPI收发逻辑以及AD7190_WaitRdyGoLow函数改写的时候触发一些因为输入、输出电平状态不同造成反极性BUG,STM的HAL库不会,因为定向一下会发现STM的SPI在HAL库中有非常完整的自动收发逻辑,逻辑闭环且完美,多数用户都是直接调用,但雅特力没有,雅特力的SPI收发底层逻辑非常原始,这也本文遇到的第一个大坑,后文会讲。二、使用AT32_Work_Bench库开发SPI时,配置如下:
void wk_spi1_init(void)
{
/* add user code begin spi1_init 0 */
/* add user code end spi1_init 0 */
gpio_init_type gpio_init_struct;
spi_init_type spi_init_struct;
gpio_default_para_init(&gpio_init_struct);
spi_default_para_init(&spi_init_struct);
/* add user code begin spi1_init 1 */
/**SPI1 GPIO Configuration
PA4 ------> SPI1_cs
PA5 ------> SPI2_sck
PA6 ------> SPI2_miso
PA7 ------> SPI2_mosi
*/
/* add user code end spi1_init 1 */
/* configure the SCK pin */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_pins = GPIO_PINS_5;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE5, GPIO_MUX_0);
/* configure the MISO pin */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_pins = GPIO_PINS_6;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE6, GPIO_MUX_0);
/* configure the MOSI pin */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_pins = GPIO_PINS_7;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE7, GPIO_MUX_0);
/* configure param */
spi_init_struct.transmission_mode = SPI_TRANSMIT_FULL_DUPLEX;
spi_init_struct.master_slave_mode = SPI_MODE_MASTER;
spi_init_struct.frame_bit_num = SPI_FRAME_8BIT;
spi_init_struct.first_bit_transmission = SPI_FIRST_BIT_MSB;
spi_init_struct.mclk_freq_division = SPI_MCLK_DIV_8;
spi_init_struct.clock_polarity = SPI_CLOCK_POLARITY_LOW;
spi_init_struct.clock_phase = SPI_CLOCK_PHASE_1EDGE;
spi_init_struct.cs_mode_selection = SPI_CS_SOFTWARE_MODE;
spi_init(SPI1, &spi_init_struct);
spi_enable(SPI1, TRUE);
/* add user code begin spi1_init 2 */
/* add user code end spi1_init 2 */
}
void wk_gpio_config(void)
{
/* add user code begin gpio_config 0 */
/* add user code end gpio_config 0 */
gpio_init_type gpio_init_struct;
gpio_default_para_init(&gpio_init_struct);
/* add user code begin gpio_config 1 */
/* add user code end gpio_config 1 */
/* gpio output config */
gpio_bits_reset(GPIOF, GPIO_PINS_0 | GPIO_PINS_1);
gpio_bits_reset(GPIOA, GPIO_PINS_0 | GPIO_PINS_1 | AD7190_CS_PIN | GPIO_PINS_8 | GPIO_PINS_11 | GPIO_PINS_12);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_0 | GPIO_PINS_1;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOF, &gpio_init_struct);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_0 | GPIO_PINS_1;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_OPEN_DRAIN;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = AD7190_CS_PIN;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(AD7190_CS_GPIO_PORT, &gpio_init_struct);
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_MODERATE;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT;
gpio_init_struct.gpio_pins = GPIO_PINS_8 | GPIO_PINS_11 | GPIO_PINS_12;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
/* add user code begin gpio_config 2 */
gpio_bits_reset(GPIOA, GPIO_PINS_8);
gpio_bits_reset(AD7190_CS_GPIO_PORT,AD7190_CS_PIN);
gpio_bits_set(GPIOB, GPIO_PINS_1);
/* add user code end gpio_config 2 */
}
这里需要留心,因为AT32_Work_Bench图形配置工具配置结束后会默认完成GPIO_INT与SPI_INT的两个部分代码块,除非使用硬件CS,不然CS配置需要去GPIO_INT函数里进行,同样的与上文一致初始化就给CS拉低。
此外,AT32_Work_Bench配置SPI的时候,遇到了本文第二个天坑:“gpio_pin_mux_config()”函数,这个函数是AT32_Work_Bench独有的关于GPIO_MODE_MUX复用模式的补充函数,在.h注释中提到了他的用途:configure the pin’s muxing function. 用来配置复用功能的,但是参数里有gpio_mux,用来选择复用引脚,这是个天坑,一定要对应着函数要求写完整参数并且完全匹配GPIO_PINS_SOURCEx,一定不要单独使用gpio_pin_mux_config()函数而不指定具体GPIO_PINS_SOURCEx,且GPIO_PINS_SOURCEx之前的configure代码块务必要明确gpio_pins,否则,以AT32F421K8U7为例,它会把用来调试SWD协议引脚复用功能给占用,结果就是没办法使用包括ST-LINK、DAP-LINK这类使用该协议接口的调试下载器,这个天坑让我一度以为自己的代码把单片机玩坏了…突然就无法debug…
除上文SPI配置过程中略有不同之外,下文内容完全一致,也就是说,改写了驱动以后,代码可以自由移植在两个不同的firmwatr函数功能库的单片机中自由自在的跑起来~
接下来是本文的重点:关于AD7190所有驱动的重构
- 雅特力SPI收发逻辑的重构:
uint8_t spi_byte_write(uint8_t data)
{
uint8_t brxbuff;
spi_i2s_data_transmit(SPI1, data);
while(spi_i2s_flag_get(SPI1, SPI_I2S_RDBF_FLAG) == RESET);
brxbuff = spi_i2s_data_receive(SPI1);
while(spi_i2s_flag_get(SPI1, SPI_I2S_BF_FLAG) != RESET);
return brxbuff;
}
uint8_t spi_byte_read(void)
{
return (spi_byte_write(AD7190_DUMMY_BYTE));
}
充分理解STM的HAL库中关于SPI收发逻辑的原理之后,自己写一个新的收、发驱动是整个AD7190通信环节能否正常运行的关键,STM的代码块有兴趣的可以自己去.h看看,我不贴了,核心原理就是用spi_i2s_flag_get();函数实现收发寄存器空闲状态的判断,如果没有判断直接spi_i2s_data_transmit();实测会在收、发过程中出现很多0xFF的空包。后续收发都可以使用这个逻辑替代原有的STM相关功能。
/***************************************************************************//**
* @brief 向AD7190写入寄存器
*
* @param registerAddress - Address of the register.
* @param registerValue - Data value to write.
* @param bytesNumber - Number of bytes to be written.
*
* @return none.
* 这里用雅特力的函数替换移植前来自于STM32的HAL_SPI_Transmit
* 区别是HAL_SPI_Transmit(&hspi_weight,registerWord, 7,0xFFFFFF)需要在参数中明确
* 使用了多少个字节、写入了什么内容,而spi_i2s_data_transmit(SPI2, registerWord[i])
* 可以通过检查标志位自动判断写入到最后一个字节时结束写入。
*******************************************************************************/
void AD7190_SetRegisterValue(unsigned char registerAddress,unsigned int registerValue,unsigned char bytesNumber)
{
unsigned char writeCommand[5] = {0, 0, 0, 0, 0};
unsigned char* dataPointer = (unsigned char*)®isterValue;
unsigned char bytesNr = bytesNumber;
int i=0;
writeCommand[0] = AD7190_COMM_WRITE | AD7190_COMM_ADDR(registerAddress);
while(bytesNr > 0)
{
writeCommand[bytesNr] = *dataPointer;
dataPointer ++;
bytesNr --;
}
for(i=0;i<bytesNumber+1;i++)
{
spi_byte_write(writeCommand[i]);
}
}
/***************************************************************************//**
* @brief 向AD7190读取寄存器
*
* @param registerAddress - Address of the register.
* @param registerValue - Data value to write.
* @param bytesNumber - Number of bytes to be written.
*
* @return none.
* 这里用雅特力的函数替换移植前来自于STM32的HAL_SPI_Transmit
* 区别是HAL_SPI_Transmit(&hspi_weight,registerWord, 7,0xFFFFFF)需要在参数中明确
* 使用了多少个字节、写入了什么内容,而spi_i2s_data_transmit(SPI2, registerWord[i])
* 可以通过检查标志位自动判断写入到最后一个字节时结束写入。
*******************************************************************************/
unsigned int AD7190_GetRegisterValue(unsigned char registerAddress,unsigned char bytesNumber)
{
unsigned char registerWord[4] = {0, 0, 0, 0};
unsigned char address = 0;
unsigned int buffer = 0x0;
unsigned char i = 0;
address = AD7190_COMM_READ | AD7190_COMM_ADDR(registerAddress);
delay_ms(1);
spi_byte_write(address); //设置通信寄存器,决定下一步操作的寄存器类型
delay_ms(2);
for(i=0; i< bytesNumber; i++)
{
registerWord[i] = spi_byte_read();
buffer = (buffer << 8) + registerWord[i];//读取到数据以后转包
}
return buffer;
}
/***************************************************************************//**
* @brief 重新复位AD7190的工作状态
*
* @param registerAddress - Address of the register.
* @param registerValue - Data value to write.
* @param bytesNumber - Number of bytes to be written.
*
* @return none.
* 这里用雅特力的函数替换移植前来自于STM32的HAL_SPI_Transmit
* 区别是HAL_SPI_Transmit(&hspi_weight,registerWord, 7,0xFFFFFF)需要在参数中明确
* 使用了多少个字节、写入了什么内容,而spi_i2s_data_transmit(SPI2, registerWord[i])
* 可以通过检查标志位自动判断写入到最后一个字节时结束写入。
*******************************************************************************/
void AD7190_Reset(void)
{
uint16_t tmp_ad7190_reset_index;
unsigned char registerWord[8];
registerWord[0] = 0xFF;
registerWord[1] = 0xFF;
registerWord[2] = 0xFF;
registerWord[3] = 0xFF;
registerWord[4] = 0xFF;
registerWord[5] = 0xFF;
registerWord[6] = 0xFF;
registerWord[7] = 0xFF;
for(tmp_ad7190_reset_index=0; tmp_ad7190_reset_index<8; tmp_ad7190_reset_index++)
{
spi_byte_write(registerWord[tmp_ad7190_reset_index]);
}
for(tmp_ad7190_reset_index=0; tmp_ad7190_reset_index<7; tmp_ad7190_reset_index++)
{
spi_byte_write(registerWord[tmp_ad7190_reset_index]);
}
delay_ms(500);
}
这里重写了复位过程中的一些细节,建议执行两次写入复位(反正整个操作最多在初始化阶段执行一次,阻塞等待也不是不行),阻塞等待的时间要适当拉长,这是因为雅特力的单片机性能太顶了,没错太顶了,主频如果不改,延时太短的话执行速度太快,很容易出现复位没结束,写入寄存器操作就尾随其后的情况,建议适当拉长。
/***************************************************************************//**
* @brief 检查是否连接AD7190并初始化.
*
* @return status - Indicates if the part is present or not.
*******************************************************************************/
unsigned char AD7190_Init(void)
{
unsigned char status = 1;
uint32_t regVal = 0;
wk_spi1_init();//重新初始化spi引脚
AD7190_Reset();//执行复位动作
regVal = AD7190_GetRegisterValue(AD7190_REG_ID, 1);
if( (regVal & AD7190_ID_MASK) != ID_AD7190)
{
status = 0;
return status ;
}
//--------------------------------------------------------------------------
ad7190_unipolar_multichannel_conf();//多路单端输入AD采集配置
ad7190_all_command_GetRegisterValue();//用于读取配置完成后的多个寄存器的数值,借此判断AD7190的故障、配置错误等
return status ;
//--------------------------------------------------------------------------
}
/***************************************************************************//**
* @brief 设置设备是否处于睡眠模式
*
* @param pwrMode - Selects idle mode or power-down mode.
* Example: 0 - power-down
* 1 - idle
*
* @return none.
*******************************************************************************/
void AD7190_SetPower(unsigned char pwrMode)
{
unsigned int oldPwrMode = 0x0;
unsigned int newPwrMode = 0x0;
oldPwrMode = AD7190_GetRegisterValue(AD7190_REG_MODE, 3);
oldPwrMode &= ~(AD7190_MODE_SEL(0x7));
newPwrMode = oldPwrMode | AD7190_MODE_SEL((pwrMode * (AD7190_MODE_IDLE)) | (!pwrMode * (AD7190_MODE_PWRDN)));
AD7190_SetRegisterValue(AD7190_REG_MODE, newPwrMode, 3);
}
下面需要重构一个非常重要的底层逻辑函数:AD7190_WaitRdyGoLow()
/***************************************************************************//**
* @brief Waits for RDY pin to go low.
*
* @return none.
*******************************************************************************/
void AD7190_WaitRdyGoLow(void)
{
unsigned int timeOutCnt = 0xFFFFF;
while(gpio_input_data_bit_read(GPIOA, GPIO_PINS_6) == RESET && timeOutCnt--)
{
;
}
}
/***************************************************************************//**
* @brief Waits for RDY pin to go low.
*
* @return none.
*******************************************************************************/
void AD7190_WaitRdyGoLow_TemperatureRead(void)
{
unsigned int timeOutCnt = 0xFFFFF;
while(gpio_input_data_bit_read(GPIOA, GPIO_PINS_6) == SET && timeOutCnt--)
{
;
}
}
这里实测发现,读取AD7190片内温度、采样电压的过程中除了2者极性配置相反以外,还需要根据通信时序在等待收发结束的机制上做调整,同样的timeOutCnt 一定要有,防止没有看门狗的情况下直接卡死。
/***************************************************************************//**
* @brief setting continuous read data enable or disable 是否需要连续读取数据
*
* @param cread - continuous read data
* Example: 0 - Disable
* 1 - enable
*
* @return none.
* 这里用雅特力的函数替换移植前来自于STM32的HAL_SPI_Transmit
* 区别是HAL_SPI_Transmit(&hspi_weight,registerWord, 7,0xFFFFFF)需要在参数中明确
* 使用了多少个字节、写入了什么内容,而spi_i2s_data_transmit(SPI2, registerWord[i])
* 可以通过检查标志位自动判断写入到最后一个字节时结束写入。
*******************************************************************************/
void AD7190_Continuous_ReadData(unsigned char cread)
{
unsigned char registerWord=0;
if(cread==1)
{
AD7190_WaitRdyGoLow();
registerWord=0x5C;
registerWord=registerWord;
}
else
{
AD7190_WaitRdyGoLow();
registerWord=0x5e;
registerWord = registerWord;
}
}
/***************************************************************************//**
* @brief Selects the channel to be enabled. 选择要启动的不同通道。
*
* @param channel - Selects a channel.
*
* @return none.
*******************************************************************************/
void AD7190_ChannelSelect(unsigned short channel)
{
unsigned int oldRegValue = 0x0;
unsigned int newRegValue = 0x0;
delay_ms(1);
oldRegValue = AD7190_GetRegisterValue(AD7190_REG_CONF, 3);//参考值:0x00800117
delay_ms(1);
oldRegValue &= ~(AD7190_CONF_CHAN(0xFF));
newRegValue = oldRegValue | AD7190_CONF_CHAN(1 << channel); //参考值:0x00801017
AD7190_SetRegisterValue(AD7190_REG_CONF, newRegValue, 3);//设置通道指令,理论上读3字节,写3字节。
}
void AD7190_MultiChannelSelect(unsigned short channel)
{
unsigned int oldRegValue = 0x0;
unsigned int newRegValue = 0x0;
delay_ms(2);
oldRegValue = AD7190_GetRegisterValue(AD7190_REG_CONF, 3);
delay_ms(2);
oldRegValue &= ~(AD7190_CONF_CHAN(0xFF));
newRegValue = oldRegValue | AD7190_CONF_CHAN(channel);
AD7190_SetRegisterValue(AD7190_REG_CONF, newRegValue, 3);
}
/***************************************************************************//**
* @brief Performs the given calibration to the specified channel.
对指定通道执行校准。
*
* @param mode - Calibration type.
* @param channel - Channel to be calibrated.
*
* @return none.
*******************************************************************************/
void AD7190_Calibrate(unsigned char mode, unsigned char channel)
{
unsigned int oldRegValue = 0x0;
unsigned int newRegValue = 0x0;
AD7190_ChannelSelect(channel);
delay_ms(2);
oldRegValue = AD7190_GetRegisterValue(AD7190_REG_MODE, 3);//参考值:0x00080060
delay_ms(2);
oldRegValue &= ~AD7190_MODE_SEL(0x7);
delay_ms(2);
newRegValue = oldRegValue | AD7190_MODE_SEL(mode);//参考值:0x00C80060
delay_ms(2);
AD7190_SetRegisterValue(AD7190_REG_MODE, newRegValue, 3);
AD7190_WaitRdyGoLow();
}
/***************************************************************************//**
* @brief Setting chop enable or disable 设置斩波启用或者禁用
*
* @param chop - chop setting
* Example: 0 - Disable
* 1 - enable
*
* @return none.
*******************************************************************************/
void AD7190_ChopSetting(unsigned char chop)
{
unsigned int oldRegValue = 0x0;
unsigned int newRegValue = 0x0;
delay_ms(100);
oldRegValue = AD7190_GetRegisterValue(AD7190_REG_CONF, 3);//参考值:0x00000117
delay_ms(100);
if(chop==1)
{
newRegValue = oldRegValue | AD7190_CONF_CHOP; //参考值:0x00800117
}
else
{
newRegValue = oldRegValue & (~AD7190_CONF_CHOP);
}
AD7190_SetRegisterValue(AD7190_REG_CONF, newRegValue, 3); //参考值:0x00800117
}
/***************************************************************************//**
* @brief Returns the result of a single conversion.返回单个转换的结果。
*
* @return regData - Result of a single analog-to-digital conversion.
*******************************************************************************/
unsigned int AD7190_SingleConversion(void)
{
unsigned int command = 0x0;
unsigned int regData = 0x0;
command = AD7190_MODE_SEL(AD7190_MODE_SINGLE) | AD7190_MODE_CLKSRC(AD7190_CLK_INT) | AD7190_MODE_RATE(0x060);
AD7190_SetRegisterValue(AD7190_REG_MODE, command, 3);
AD7190_WaitRdyGoLow();
regData = AD7190_GetRegisterValue(AD7190_REG_DATA, 3);
return regData;
}
/***************************************************************************//**
* @brief Returns the result of a single conversion.返回单个转换的结果。
*
* @return regData - Result of a single analog-to-digital conversion.
*******************************************************************************/
unsigned int AD7190_SingleConversion_TemperatureRead(void)
{
unsigned int command = 0x0;
unsigned int regData = 0x0;
command = AD7190_MODE_SEL(AD7190_MODE_SINGLE) | AD7190_MODE_CLKSRC(AD7190_CLK_INT) | AD7190_MODE_RATE(0x060);
AD7190_SetRegisterValue(AD7190_REG_MODE, command, 3);
AD7190_WaitRdyGoLow_TemperatureRead();
regData = AD7190_GetRegisterValue(AD7190_REG_DATA, 3);
return regData;
}
这里我把AD7190_SingleConversion也针对两种状态做了不同的调整,能够解决读取寄存器和采样电压时出现的数据总线卡死的bug。
/***************************************************************************//**
* 多路单端输入AD采集配置
*******************************************************************************/
void ad7190_unipolar_multichannel_conf(void)
{
// unsigned int command = 0x0;
AD7190_ChopSetting(1); // chop enable 使能斩波
/* Calibrates channel AIN1(+) - AINCOM(-). */
AD7190_Calibrate(AD7190_MODE_CAL_SYS_ZERO, AD7190_CH_AIN1P_AINCOM);//使用内部零点校准
delay_ms(100);
/* Calibrates channel AIN2(+) - AINCOM(-). */
AD7190_Calibrate(AD7190_MODE_CAL_SYS_ZERO, AD7190_CH_AIN2P_AINCOM);//使用内部零点校准
delay_ms(100);
/* Calibrates channel AIN3(+) - AINCOM(-). */
AD7190_Calibrate(AD7190_MODE_CAL_SYS_ZERO, AD7190_CH_AIN3P_AINCOM);//使用内部零点校准
delay_ms(100);
/* Calibrates channel AIN4(+) - AINCOM(-). */
AD7190_Calibrate(AD7190_MODE_CAL_SYS_ZERO, AD7190_CH_AIN4P_AINCOM);//使用内部零点校准
delay_ms(100);
/* Selects unipolar operation and ADC's input range to +-Vref/1. */
AD7190_RangeSetup(1, AD7190_CONF_GAIN_1);
delay_ms(100);
/* Calibrates channel AIN1(+) - AINCOM(-). */
AD7190_Calibrate(AD7190_MODE_CAL_SYS_FULL, AD7190_CH_AIN1P_AINCOM);//使用内部零点校准
delay_ms(100);
/* Calibrates channel AIN2(+) - AINCOM(-). */
AD7190_Calibrate(AD7190_MODE_CAL_SYS_FULL, AD7190_CH_AIN2P_AINCOM);//使用内部零点校准
delay_ms(100);
/* Calibrates channel AIN3(+) - AINCOM(-). */
AD7190_Calibrate(AD7190_MODE_CAL_SYS_FULL, AD7190_CH_AIN3P_AINCOM);//使用内部零点校准
delay_ms(100);
/* Calibrates channel AIN4(+) - AINCOM(-). */
AD7190_Calibrate(AD7190_MODE_CAL_SYS_FULL, AD7190_CH_AIN4P_AINCOM);//使用内部零点校准
delay_ms(100);
// // 使能:AD7190_CH_AIN1P_AINCOM
// // AD7190_CH_AIN2P_AINCOM
// // AD7190_CH_AIN3P_AINCOM
// // AD7190_CH_AIN4P_AINCOM
// AD7190_MultiChannelSelect(0xF0);
//
// /* Performs a conversion. */
// command = AD7190_MODE_SEL(AD7190_MODE_CONT) | AD7190_MODE_DAT_STA| \
// AD7190_MODE_CLKSRC(AD7190_CLK_EXT_MCLK1_2) | AD7190_MODE_RATE(512);
// delay_ms(2);
//
// AD7190_SetRegisterValue(AD7190_REG_MODE, command, 3);
//
// AD7190_Continuous_ReadData(1);
}
使能部分开始设置寄存器等功能我选择暂时不用。
/***************************************************************************//**
* AD7190数据读取前的各种参数获取及暂存
*******************************************************************************/
void ad7190_all_command_GetRegisterValue (void)
{
uint32_t AD7190_offset=0;
uint32_t AD7190_overset=0;
uint32_t AD7190_confValue=0;
uint32_t AD7190_modeValue=0;
uint32_t AD7190_gpo=0;
AD7190_gpo = AD7190_GetRegisterValue(AD7190_REG_GPOCON, 1);
AD7190_gpo = AD7190_gpo;
AD7190_modeValue = AD7190_GetRegisterValue(AD7190_REG_MODE, 3);
AD7190_modeValue = AD7190_modeValue;
AD7190_confValue = AD7190_GetRegisterValue(AD7190_REG_CONF, 3);
AD7190_confValue = AD7190_confValue;
AD7190_offset = AD7190_GetRegisterValue(AD7190_REG_OFFSET, 3);
AD7190_offset = AD7190_offset;
AD7190_overset = AD7190_GetRegisterValue(AD7190_REG_FULLSCALE, 3);
AD7190_overset = AD7190_overset;
}
这里需要参考手册以及.h文件中提到的寄存器部分,重点是校验核对初始化配置完成后的寄存器状态是否正常,如果不正常那肯定有问题,最后一个AD7190_REG_FULLSCALE满量程寄存器也很重要,手册中提到这个寄存器是在上电时用工厂校准的满量程校准系数配置;因此,各器件具有不同的默认系数。如果用户启动内部或系统满量程校准,或者写入满量程寄存器,该默认值将被自动覆盖。
最后读取AIN1\2\3\4几个通道的数据时,建议时写成不同的功能函数,方便调整不同类型的寄存器配置:
float AD7190_AIN2_Read(void)
{
float dataReg = 0x0;
AD7190_RangeSetup(1, AD7190_CONF_GAIN_1);
AD7190_ChannelSelect(AD7190_CH_AIN2P_AINCOM);
dataReg = AD7190_SingleConversion();
dataReg *= 4.096;
dataReg /= 0xFFFFFF;
return dataReg;
}
最后,还需解决一个因为GPIO_MODE_MUX状态机制导致数据标准位异常状态的问题,可以通过使用前重新初始化MISO引脚状态的方法解决:
void AD7190_SPI_RESET(void)
{
gpio_init_type gpio_init_struct;
gpio_default_para_init(&gpio_init_struct);
/* configure the MISO pin */
gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
gpio_init_struct.gpio_pins = GPIO_PINS_6;
gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
gpio_init(GPIOA, &gpio_init_struct);
gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE6, GPIO_MUX_0);
}
最终的任务处理函数如下:
/***************************************************************************//**
* 初始化、检查完成后开始执行采样模式
*******************************************************************************/
void AD7190_Init_scan(void)
{
if(AD7190_Init()==0)
{
//printf("获取不到 AD7190 !\n");
while(1)
{
delay_ms(1000);//重新检查,直到AD7190的ID成功出现。
if(AD7190_Init())
break;
}
}
}
void AD7190_Callback(void)
{
uint8_t ad7190_data_number;
AD7190_SPI_RESET();//初始化SPI总线状态
AD7190_TemperatureRead();
for(ad7190_data_number = 0;ad7190_data_number<5;ad7190_data_number++)
{
ad7190_data[ad7190_data_number] = AD7190_AIN2_Read();//读取数据
}
}
AD7190_Init_scan有且只能执行一次,必须放在初始化里进行,而且不建议按我的写法while阻塞反复等待,建议用定时器优化这部分写法,防止它意外触发看门狗,另外AD7190_Callback函数如果写在多个任务的while中,最好不要出现阻塞写法的程序,实测时间间隔太长、太短都会导致数据线异常,最后就是AD7190的采样速率的问题,它实际上需要你均衡调整数据质量与采样速率之间的关系,配置的速率太快数据偏移和零点漂移都会很大,速率太慢数据不会漂移但是能绘制的波形谱幅度就会相当有限,对光学光谱这种动态范围非常敏感的东西非常不友好,后期需要多花时间研究算法和硬件配置之间的关系。
最后,由于AD7190采样精度和分辨率实在是强的吓人,一点点微弱的噪声都会被完整的采集回来,一定要要求电路在设计阶段就提供良好的EMC和解耦、降噪、降高频特性,做好PCB线路优化等等问题,整个系统才能稳定工作。