在项目中需要使用高精度的ADC采集电压信号且没有高速需求,而STM32F103RCT6自带的ADC位数较低,故选择了一款24bit的低速ADC芯片AD7789,根据它的引脚与时序特性,复用了STM32的SPI模块来驱动。整个过程踩了一些坑,在此记录分享一下,表述适合初学者食用。
stm32cubemx的使用不做赘述,下面以cube为基础从头说起:
1.复用spi1模块:
PA4引脚可以直接配置为SPI1_NSS,且生成的代码在“MX_SPI1_Init”中会有“hspi1.Init.NSS = SPI_NSS_SOFT”;但实际调试中不能用HAL_GPIO_WritePin()直接拉低,想来或许有对应的库函数,但博主懒得找了还是选择配置为GPIO_Output,并修改标签为SPI1_CS,以至于在配置界面左侧出现一堆黄色感叹号,但没影响。
2.设置spi1的参数
此处配置为全双工模式、软件NSS控制(下面那个默认Disable就好、上一步已配置SPI1_CS)。AD7788/7789符合标准的四线制SPI通信,输入DIN脚,数据准备和数据输出在DOUT/#RDY(引脚标识上划线打不出来用#在前面表示取反,很好理解在数据准备好了的时候RDY从逻辑角度看为真就是1,取反就是拉低为0,所以它手册中的协议描述是在读到该引脚拉低后再在该引脚读数据);
再后面,数据的接收我们可以选择用中断处理,但既然是低速要求,轮询也没有问题就先用起来,有兴趣的朋友可以用中断写一下接收部分,中断需要注意ready和data的case区分;轮询情况下,配置就不再管NVIC中的中断配置,直接进入参数设置。轮询的方式是在运行时只需要调用一个HAL_SPI_Receive()函数,它会自动给芯片的SCLK引脚加上时钟信号来激活读取数据的操作,包括发送函数HAL_SPI_Transmit(),也是在调用时才会加上时钟信号来发起写入操作。
2.1分频
由于是低速ADC,博主直接把分频拉到最大256,其实大可以根据时序特性将波特率优化到一个合适的区间,但为了能先顺利工作起来,就取稳用最低频;
2.2时钟
时序图以及引脚说明可以看出操作AD7788/7789的时钟极性与时钟相位为1和1;
3.生成代码并通信
3.1配置
首先通过向通信寄存器CR写入数据来配置。
想要读取数据需要先写入一个读操作、想要设置不同读取模式需要先配置ADC的模式寄存器MR。所以说上电后一切对AD7788/7789的操作都是从向CR写入数据开始,芯片也是处在等待写入的状态之中,而一块这种ADC芯片没有也没必要有非易失性存储单元来保存之前的配置,所以每次上电重新配置(注意芯片的上电和断电、片选的启用和关闭是对应不同的操作)。下面简单描述一下CR中每一位干什么事:
①第一步写入0x10去往模式寄存器。要给模式寄存器MR写数据需要在CR中选择下一步给MR写入数据,对比一下RS1和RS0,知道这里的CR5、CR4是0和1:
CR3设置为0表示写入,CR2设置为0表示下一步不是连续读取,CR1和CR0设置为0表示差分基准源输入(其他通道有兴趣的朋友可自行了解扩展ADC的应用方案,差分基准输入为基本用途),那么对CR写入的8位二进制数据为:0b 0001 0000,16进制表示就是0x10;
②第二步写入0x06配置为连续转换模式。如图:
简单看出,只需要设置MR7、MR6、MR2即可,博主设置了0b 0000 0110 即0x06,表示持续转换模式下单极性输入(AD7788/7789有三种模式:单次转换、连续转换、连续读取[1];两种输入:单极性、双极性[2])。
uint8_t ADC_MODE_CONF[1] = {0x10}; uint8_t ADC_UNBI_MODE[1] = {0x06};
void AD7789_CONF(void){ AD7789_CS_L; HAL_SPI_Transmit(&hspi1,ADC_MODE_CONF,1,0xFFFF); delay_ms(1); HAL_SPI_Transmit(&hspi1,ADC_UNBI_MODE,1,0xFFFF); delay_ms(1); AD7789_CS_H; delay_ms(1); }
3.2读取
在单次转换的时序图中可以看出,前面0x10、0x82的配置结束后,需要等待#RDY拉低电平、然后向CR写入0x38紧跟着再读取data,博主这里配置了连续转换模式方式还是一样的。
uint8_t ADC_CON_COV[1] = {0x38};
float AD7789_GET(void){ AD7789_CS_L; uint8_t ADC_READ[3] = {0}; int ADC; float tem; while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_6) != GPIO_PIN_RESET) { }//等待ready delay_ms(1); HAL_SPI_Transmit(&hspi1,ADC_CON_COV,1,0xFFFF);//写入0x38 delay_ms(1); HAL_SPI_Receive(&hspi1,ADC_READ,3,0xFFFF);//接收data,24位数据 ADC = ADC_READ[0] << 16 | ADC_READ[1] << 8 | ADC_READ[2];//MSB优先,位数调整 tem = 5.00*ADC/16777215;//5.00为差分基准源输入 //ADC &= ~(0x1 << 23); //tem = 5.00*ADC/8388607;//双极性模式 AD7789_CS_H; delay_ms(1); return tem; }
源码量不多,AD7789_GET返回浮点数即为电压值。
4.注
[1]三种模式:单次转换、连续转换、连续读取
ready拉低后表示数据转换ok,待读取,读取完了之后恢复高电平;单次模式下这只能转换一次然后数据锁存,读取的数据不像连续转换那样读取变化的数据,只记录第一次读取的数据之后便处于掉电状态等待下次reset信号(32个高电平输入)或者真正掉、上电重启;
连续读取模式感兴趣的朋友可以尝试,在调试之初博主出了点问题没有落实下去,虽然芯片设计的默认模式是连续转换不是连续读取,但实际上从手册的描述来看是更方便的,省略了向芯片写入数据的过程,随用随读。
调试过程如果出现采集过程确实是连续转换的但结果不对,大概率是片子还处于默认的双极性模式,需要断电重启更新单极性配置 。
[2]单极性、双极性数据:单极性为24bit下,24个0到24个1,表示0到+Vref的电压采集输入范围,双极性为-Vref到+Vref,24bit下第一位为符号位,0x000000到 0x800000 为-Vref到0V电压采集输入,0x800000 到 0xffffff 为0到+Vref电压采集输入;牺牲了一位精度换取带负值的双倍采集范围。