使用STM32H743去读取AD7771的8通道32bit数据本文章采用的是SPIDMA的方法。首先先了解AD7771先附上AD7771的EVAL板评估板的图片根据评估版我们可以先梳理一下我们所需要的引脚。因为我们采用的是SPI通信因此最少我们需要连接的引脚包括SDI输入SDO输出CS片选SCLK时钟MOSIMISO随便一个引脚无冲突SCK配置好这四引脚我们便可以实现SPI通信。这里附上的SPI选择和配置因为标准库目前不再更新所以我们此处选择HAL库进行开发使用Cube MX进行一些基础的配置。先看选择的SPI的四个引脚本文选择的分别是PA4-PA7SPI1SPI1的参数配置如下图所示此处注意两点第一点是评估版/AD7771支持MODE0或者MODE3因此我们在配置spi的时候需要将其配置为MODE0或者MODE3这里我选择的是MODE0。第二点是AD7771的数据读取的顺序是按照MSB First来的因此我们这里SPI的配置上也选择的是MSB First。SPI的中断配置此处可以开启SPI1的DMA通道PA5 - PA7引脚的具体参数如下图所示PA4引脚的配置如下图所示配置好这些引脚之后我们就要考虑我们的时钟如何配置了本项目的时钟树配置如下大家可以自行参考注意此时我们的SPI配置的内核时钟是10MHZ但实际上经过Prescaler 预分频器分频后的时钟才是SPI的最终输出的时钟。按照以上的配置我们的Cube MX的SPI配置就完成了。进一步说只要我们生成代码进行正确的连接我们就能和我们的AD7771进行SPI数据交换了。接下来要了解AD7771的寄存器读写的数据格式。为什么要了解AD7771的寄存器读写数据格式。因为只有了解了AD7771的正确数据读写格式才能通过SPI的发送或者接收函数去成功的与AD7771进行通信。AD7771的数据读写规范如下bit1514-87-0读写位写寄存器操作为0读为17位地址位8位数据位读的时候该八位一般没用默认给0即可AD7771寄存器读取的格式如下字节数第一字节头帧第二字节数据帧0x20寄存器的值作者推荐的地址封装方式如下//读的时候地址数据结构 uint8_t tx[2], rx[2]; tx[0] 0x80 | (addr 0x7F);//add是要读取的寄存器的地址值十六进制0X--的形式 tx[1] 0x00; //写的时候的地址封装方式 uint8_t tx[2]; //构造写数据格式首位为0其他七位为地址位后八位为要写入的数据 tx[0] (addr 0x7F);//add是要写入的寄存器的地址值十六进制0X--的形式 tx[1] data;//data为要写入的数据也是16进制有以上信息的支持我们目前便可以实现对AD7771寄存器的写入和读出操作了。为了方便开发这里笔者把自己的寄存器读写操作展示供各位读者参考注意这里记得外部调用一下spi1的句柄//SPI句柄 extern SPI_HandleTypeDef hspi1; //读取AD7771寄存器的值 uint8_t AD7771_ReadRegister(uint8_t addr) { uint8_t tx[2], rx[2]; //构造读取数据格式AD7771要求先发高有效位 tx[0] 0x80 | (addr 0x7F); //tx[0] 0x80 | ((addr 0x3F) 1); tx[1] 0x00; //打印发送的内容以判断是否发送正确 printf(tx[0]0X%02X,tx[1]0X%02X\r\n,tx[0],tx[1]); HAL_GPIO_WritePin(AD7771_CS_GPIO_Port, AD7771_CS_Pin, GPIO_PIN_RESET); // 发送2字节同时接收2字节 HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, HAL_MAX_DELAY); HAL_GPIO_WritePin(AD7771_CS_GPIO_Port, AD7771_CS_Pin, GPIO_PIN_SET); // rx[0] 是Header (应为0x20)rx[1] 是数据此处打印出来判断接收是否正确 printf(rx[0]0X%02X,rx[1]0X%02X\r\n,rx[0],rx[1]); return rx[1]; } //写值到AD7771寄存器的函数 void AD7771_WriteRegister(uint8_t addr, uint8_t data) { uint8_t tx[2]; //构造写数据格式首位为0其他七位为地址为后八位为要写入的数据 tx[0] (addr 0x7F); //tx[0] 0x80 | ((addr 0x3F) 1); tx[1] data; HAL_GPIO_WritePin(AD7771_CS_GPIO_Port, AD7771_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, tx, 2, HAL_MAX_DELAY);//此处因为只需要写入因此使用Transmit函数即可 HAL_GPIO_WritePin(AD7771_CS_GPIO_Port, AD7771_CS_Pin, GPIO_PIN_SET); }此处可以进行一个测试我们可以采用读取寄存器函数去读取地址0x11寄存器的值。按照手册说明每次上电或者复位寄存器的值都会变为默认值。0x11寄存器的默认值是0x24。我们可以先对其读取默认值看spi通信是否正确接下来可以采取写入再读取的方法去测试写入是否可以正常使用。以上是我的测试结果根据结果来看写入和读出都是没有问题的因此可以进行下一步操作。通过对AD7771数据手册的研究以及对EVAL-AD777xFMCZ评估版的探索可知我们配置完spi后还需要配置的引脚如下引脚RESETSTARTDRDYALERT功能AD7771复位同步脉冲输入数据就绪错误警告使用引脚任意GPIO任意GPIO任意GPIO任意GPIO这四个引脚的作用如表所示。其中依据AD7771的特性当我们断电再上电的时候会自动复位因此RESET引脚可以暂时不用管。START引脚的作用是进行一个同步脉冲的输入当我们对AD7771的各个寄存器配置好之后我们需要通过START引脚发送一个时间大于10ns的负脉冲才能让配置生效。DRDY引脚的作用是当AD7771采集好数据后他会发送一个下降沿的电平信号检测到该电平信号我们片可以对SDO的引脚进行一个数据的读取。ALERT引脚的主要作用是AD7771 的一个错误报警输出当芯片内部检测到异常情况时这个引脚会输出一个高电平信号相当于一个“硬件中断”用来通知主控芯片出了问题。一般来说我们只是去读取AD7771所获取的8通道32位数据不需要太精确的错误判断因此该引脚可以悬空。总的来看我们只需要选择任意的GPIO引脚去连接START和DRDY即可。因此本文所选择的引脚是PB0和PB1引脚的具体配置如下图其中START引脚我们需要给一个负脉冲因此具体代码实现如下PB0//该函数作用发送一个负脉冲给PB1,使初始化配置生效 void AD7771_SendStartPulse(void) { //设置START为高电平(空闲状态) HAL_GPIO_WritePin(AD7771_START_GPIO_Port, AD7771_START_Pin, GPIO_PIN_SET); HAL_Delay(1); //拉低PB1产生下降沿 HAL_GPIO_WritePin(AD7771_START_GPIO_Port, AD7771_START_Pin, GPIO_PIN_RESET); //保持低电平大于10ns HAL_Delay(1); //拉高PB1产生上升沿完成负脉冲 HAL_GPIO_WritePin(AD7771_START_GPIO_Port, AD7771_START_Pin, GPIO_PIN_SET); //等待脉冲生效 HAL_Delay(1); }DRDY引脚我们需要检测其有一个下降沿因此具体代码实现如下PB1//判断DRDY引脚是否产生了下降沿 uint8_t AD7771_IsDRDY(void) { return (HAL_GPIO_ReadPin(AD7771_DRDY_GPIO_Port, AD7771_DRDY_Pin) GPIO_PIN_RESET); } //等待DRDY引脚产生一个下降沿 void AD7771_WaitForDRDY(void) { // 等待DRDY变为低电平 while(HAL_GPIO_ReadPin(AD7771_DRDY_GPIO_Port, AD7771_DRDY_Pin) GPIO_PIN_SET); }接下来我们还有最后一步准备工作就是对AD7771进行配置。此处我们主要的配置方式根据文档可知是往相应的寄存器中写入对应的值即可。对于我们想要进行简单的数据采集接收测试我们需要的配有包括如下//对AD7771进行寄存器配置 // 配置GENERAL_CONFIG_1(): 高分辨率模式 使能内部参考 // Bit61: 高分辨率模式 // Bit51: 使能VCM缓冲 (默认) // Bit41: 使能内部参考输出 // Bit30: SAR ADC关闭 (默认) // Bit21: 使能内部振荡器 (默认) AD7771_WriteRegister(AD7771_REG_GENERAL_CONFIG_1, 0x74); // 3.2 配置GENERAL_CONFIG_2 // Bit60: Sinc3滤波器 (默认) // Bit50: 不使能SAR模式 // Bit4-301: SDO驱动强度-强 // Bit2-100: DOUT驱动强度-默认 // Bit00: SPI_SYNC (软件同步) AD7771_WriteRegister(AD7771_REG_GENERAL_CONFIG_2, 0x08); //配置GENERAL_CONFIG_3 //Bit7-600: CONVST去抖动 (默认) //Bit41: 使能SPI从模式读取Σ-Δ数据 //Bit00: 时钟检测使能 (默认) AD7771_WriteRegister(AD7771_REG_GENERAL_CONFIG_3, 0X90); //使能所有通道 (CH_DISABLE寄存器默认0x00所有通道使能) AD7771_WriteRegister(AD7771_REG_CH_DISABLE, 0x00); printf(所有通道已使能\r\n); //配置采样率 (示例: 设置ODR为8kSPS高分辨率模式) //ODR MCLK / (4 × Decimation) //假设MCLK8.192MHz要得到8kSPSDecimation 8192k / (4×8k) 256 //SRC_N_MSB 和 SRC_N_LSB 存储抽取率 AD7771_WriteRegister(AD7771_REG_SRC_N_MSB, 0x01); // 256 0x0100 AD7771_WriteRegister(AD7771_REG_SRC_N_LSB, 0x00); AD7771_WriteRegister(AD7771_REG_SRC_IF_MSB, 0x00); // 小数部分为0 AD7771_WriteRegister(AD7771_REG_SRC_IF_LSB, 0x00); printf(配置采样率: 8kSPS\r\n);如此这般便可以完成对我们的AD7771的基础配置。这样看来我们的准备工作已经完成现在我们就差最后一步就是去编写一个接收函数去接收AD7771所传过来的8通道32位的数据。这里提供两种接收的思路。思路一DMA接收具体代码如下//DMA接收数据 static uint8_t dma_rx_buffer[32] {0}; volatile uint8_t ad7771_dma_rx_complete 0; void AD7771_ReadAllChannels_DMA(AD7771_ChannelData_t *data) { //等待DRDY下降沿信号代表数据准备完毕 AD7771_WaitForDRDY(); //重置DMA完成标志 ad7771_dma_rx_complete 0; //清空缓冲区 memset(dma_rx_buffer, 0, 32); //片选使能 HAL_GPIO_WritePin(AD7771_CS_GPIO_Port, AD7771_CS_Pin, GPIO_PIN_RESET); //启动DMA读SDO的数据 HAL_SPI_Receive_DMA(hspi1, dma_rx_buffer, 32); //等待DMA传输完成超时保护 uint32_t timeout 1000; // 1ms超时 while(!ad7771_dma_rx_complete timeout--) { HAL_Delay(1); } //拉高片选 HAL_GPIO_WritePin(AD7771_CS_GPIO_Port, AD7771_CS_Pin, GPIO_PIN_SET); //检查是否超时 if(timeout 0) { printf(DMA传输超时\r\n); return; } //解析数据 for (int ch 0; ch 8; ch) { int offset ch * 4; uint32_t raw ((uint32_t)dma_rx_buffer[offset] 24) | ((uint32_t)dma_rx_buffer[offset1] 16) | ((uint32_t)dma_rx_buffer[offset2] 8) | ((uint32_t)dma_rx_buffer[offset3]); data[ch].header (raw 24) 0xFF; data[ch].adc_value raw 0x00FFFFFF; data[ch].channel_id (data[ch].header 4) 0x07; data[ch].alert (data[ch].header 7) 0x01; data[ch].error_flags data[ch].header 0x0F; printf(CH%d | Header: 0x%02X, Value: 0x%06X, ID: %d\n, ch, data[ch].header, data[ch].adc_value, data[ch].channel_id); } }回调函数如下//回调函数 void HAL_SPI_RxTxCpltCallback(SPI_HandleTypeDef *hspi) { if(hspi-Instance SPI1) { ad7771_dma_rx_complete 1; } } void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if(hspi-Instance SPI1) { ad7771_dma_rx_complete 0; printf(SPI DMA错误\r\n); } }这样一来我们就能实现对AD7771的数据读取。方案二采用CPU阻塞的方式接收数据//SPI-CPU阻塞方式接收数据 void AD7771_ReadAllChannels_Fixed(AD7771_ChannelData_t *data) { uint8_t rx[32] {0}; HAL_GPIO_WritePin(AD7771_CS_GPIO_Port, AD7771_CS_Pin, GPIO_PIN_RESET); HAL_SPI_Receive(hspi1, rx, 32, HAL_MAX_DELAY); HAL_GPIO_WritePin(AD7771_CS_GPIO_Port, AD7771_CS_Pin, GPIO_PIN_SET); for (int ch 0; ch 8; ch) { int offset ch * 4; uint32_t raw ((uint32_t)rx[offset] 24) | ((uint32_t)rx[offset1] 16) | ((uint32_t)rx[offset2] 8) | ((uint32_t)rx[offset3]); data[ch].header (raw 24) 0xFF; data[ch].adc_value raw 0x00FFFFFF; data[ch].channel_id (data[ch].header 4) 0x07; data[ch].alert (data[ch].header 7) 0x01; data[ch].error_flags data[ch].header 0x0F; printf(CH%d | Header: 0x%02X, Value: 0x%06X, ID: %d\n, ch, data[ch].header, data[ch].adc_value, data[ch].channel_id); } }如此一来我们所需要的代码都齐全了接下来主函数我么只要先调用初始化然后在调用数据读取函数即可。以下附带一张程序运行的截图。最后我们再来解析一下AD7771发来的数据的解析。因为我们的AD7771每次是采集8通道32位数据去发送这样一看就是8*32/832字节。每个通道都是4字节头字节为头帧其中第6-4位为通道值第7位为警告值这个值目前我们可以不考虑只要考虑接收到的通道值即可。后三字节为接收到的数据。