1,ADS127L11的简介:
1.1ADS127L11内部的结构
上面是它的结构图,由此可见是要将调试器、滤波器、spi三者搭起来,内部就可以运行。
1.2引脚
我们这里主要涉及到的引脚1,spi1通信的四个引脚,(四线模式)。2、基本电源支持。3、参考电压。4、同步引脚(start)控制。5、模拟电压输入。6、复位引脚。
1.3内部寄存器的选择
ADS127L11的寄存器有16个,如下所示:
其中我们重点示意的是status寄存器、control寄存器、config1-4寄存器。图中的第一列是他们的位置,第三列是默认值,例如status的寄存器位置h,默认值是X110XXX。
1.3.1STATUS寄存器
状态寄存器:计算机中用于存储和表示处理器状态 的寄存器,包括各种标志位,如进位标志、零标志等。
状态寄存器是显示内芯片当前状态的,与此同时,转换出的数据如果开启表头,也就是在数据前面会多一个字节数据,这个数据就是status显示的数据。这个功能可以在config4寄存器的最后一位开启。
总的来说,这个寄存器是一个用户观察的,而不是写入的,我们可以通过该寄存器快速定位芯片的状态或错误。
1.3.2 config1寄存器
config1寄存器中选择的时电压的输出基准范围,与前面电源的选择有关。我这里选择的参考电压是4.096v,那么这个输入范围就是1x,与原来默认的0x00一致。
1.3.3config2寄存器
config2寄存器中重要的是第4位、第3位,这里是选择start引脚的模式。
我们可以看到它的解释,也就是选择adc转换数据的模式,参考同步的那一板块。
ads127的转换模式有三种:启停、一次、同步转换,这三个转换手册中有具体的解释,一般我们需要adc连续的转换,那么选择的时启停与同步转换,可以先试用启停模式进行测试,在启用同步转换。
1.3.4config3寄存器
该寄存器是专门选择滤波器模式与采样率,这些位配置数字滤波器。数字滤 波器有五种模式:宽带、sinc4、 sinc4 + sinc1、sinc3和sinc3 + sinc1,每种模式都有一个OSR 值范围。数据速率和带宽信息如 表7-1 ~表7-5所示。这些依照自己需求写入数值即可,至于前面的延时时间,暂且为默认值。
1.3.5config4寄存器
该寄存器是ads127l11必须配置的寄存器。
第七位:选择sclk的时钟,内部还是外部,我这里选择的是内部时钟,注意该时钟是控制内部寄存器的,不是spi的clk。
第三位:选择分辨率,我选择的输出adc数据为24位。
第二位:crc使能位,暂时选择的时关闭,以便于做测试。这个位开启,每一帧传递字节加一,crc作为最后一个字节传递。
第0位:status表头使能,暂时选择关闭,以便于做测试。
这个寄存器就决定了你每一帧传递数据的字节,根据我选择的,我应该每一帧传递三个字节。
2、硬件电路
按照上表的,以及电源那一章节,我选择的是AVDD1 AVDD2接5v,AVSS接GND,对应就是快速模式。
iovdd接5v,这里接5V,那么响应的时序要满足这个范围内的表格
快速模式下,REFP接4.096v,REFN接GND,也就是内部时钟是25.6mhz,这个也即是上述的SCLK周期,即芯片内部的时钟。ads127后面提供了能够座位参考电压的硬件,这里可以选择REF6041I系列。
CAPD CAPA按照数据手册的接法,通过一个电容接地即可。
CLK与REST引脚不用,因为我使用的时内部时钟和软件复位,那么这两个引脚接地即可。
要测试的电压通过AINN AINP接入,测试的时候可以用信号发生器测试。
start引脚这里我设的是一个正选波,数据手册上说明,这里可以通入一个高电平,或者是一个正选波,那么在在高电平时候开始转换,低电平时候停止ADC转换,具体参考同步章节。
3、spi通讯
这里选择的主单片机芯片是stm32h7,spi的通信方式也十分简单,就是将上述ADS127L11的spi引脚口与主芯片的相连接,如果硬件层大,可以通过数字隔离器在连接。
值得注意的是,ADS127L11的spi模式只适应spi模式一。
接口工作在外设模式(被动),其中SCLK由主机驱动。接口支持SPI mode 1 (CPOL) = 0和CPHA = 1)。在SPI模式1中,SCLK处于低空闲状态,数据在SCLK上升沿上更新并 继续读取 SCLK下降边。接口支持全双工工作,即输入数据和输出数据可以同时传输。在stmcbuemx里面设置的时候应该对应cpha为2edge。
4、代码编写
代码可以直接参考官网上的驱动代码,只需要将底层的换为hal库函数。
底层的spi与gpio是stm32cbue自动设置的。
4.1spi.h
使用的是stm32h743的spi1,其中时钟频率是120mhz经过32分频,也就是3.75mhz作为ADS127L11的时钟clk。
void MX_SPI1_Init(void) { /* USER CODE BEGIN SPI1_Init 0 */ /* USER CODE END SPI1_Init 0 */ /* USER CODE BEGIN SPI1_Init 1 */ /* USER CODE END SPI1_Init 1 */ hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 0x0; hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; hspi1.Init.NSSPolarity = SPI_NSS_POLARITY_LOW; hspi1.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA; hspi1.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; hspi1.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN; hspi1.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE; hspi1.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE; hspi1.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE; hspi1.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE; hspi1.Init.IOSwap = SPI_IO_SWAP_DISABLE; if (HAL_SPI_Init(&hspi1) != HAL_OK) { Error_Handler(); } /* USER CODE BEGIN SPI1_Init 2 */ /* USER CODE END SPI1_Init 2 */ }
4.2.初始化
void bsp_InitADS127L11(void) { MX_GPIO_Init();//端口初始化 MX_SPI1_Init();//spi初始化 adcStartup();//开始扫描初始化 }
端口初始化与spi初始化读用stm32cbue自动生成的,其中spi的初始化我已经摆在4.1中。
void adcStartup(void) { /* (OPTIONAL) Provide additional delay time for power supply settling */ HAL_Delay(50); /* (OPTIONAL) Populate CRC lookup table */ initCRC(); /* )设置nRESET引脚高电平用于ADC操作 */ ADS127L11_RESET_set_high(); /* (必需)设置启动引脚高开始转换 */ //如果这里的9834部分接上去了,那么这里就会变成有方波跳变。 /*ADS127L11_START_set_high();*/ /* (可选)切换nRESET引脚以确保默认的寄存器设置 拉低又拉高*/ toggleRESET(); /* 使用设备默认设置初始化内部'registerMap'数组 */ restoreRegisterDefaults(); /* 寄存器的配置*/ ads127l11_writeSingleRegister(CONFIG4_REG_ADDRESS, CONFIG4_REG_DEFAULT );//默认 }
下面关于寄存器的配置要具体情况而论,都是依照写函数的功能完成的。官方里面是以
writeSingleRegister函数为单独写入寄存器函数,我这里换了个名字为ads127l11_writeSingleRegister()
4.3写寄存器函数
/********************************************************************************************************** * 函 数 名: writeSingleRegister * 功能说明: 写单个寄存器 * 形 参: address 寄存器地址 * data 数值 * 返 回 值: 无 **********************************************************************************************************/ void ads127l11_writeSingleRegister(uint8_t address, uint8_t data) { /* 检查寄存器地址在范围内 */ //assert(address < NUM_REGISTERS); // 检查是否复位 if (address == CONTROL_REG_ADDRESS && data == CONTROL_REG_RESET_COMMAND) ads127l11_restoreRegisterDefaults(); // 如果写入CONFIG4寄存器以固定操作模式 // if (CONFIG4_REG_ADDRESS == address) // { // data = enforce_selected_device_modes(data); // } // 创建TX和RX字节数组 uint8_t dataTx[SPI_BUFFER_SIZE] = { 0 }; uint8_t dataRx[SPI_BUFFER_SIZE] = { 0 }; uint8_t opcode[2] = {(OPCODE_WREG | address), data};//命令加地址,数据 uint8_t numberOfBytes = buildSPIarray(opcode, dataTx); /* 拉低片选 */ HAL_GPIO_WritePin(MCU_ADS127L11_CS_GPIO_Port, MCU_ADS127L11_CS_Pin, GPIO_PIN_RESET); HAL_Delay(1); // 发送 HAL_SPI_TransmitReceive(&hspi1,dataTx, dataRx, numberOfBytes,10000); /* 拉高片选 */ HAL_GPIO_WritePin(MCU_ADS127L11_CS_GPIO_Port, MCU_ADS127L11_CS_Pin, GPIO_PIN_SET); // 更新寄存器数据 mcu_ads127L11.init_reg[address] = data; }
注意片选的拉高与拉低。其中构造函数buildSPIarray()这个函数
/********************************************************************************************************** * 函 数 名: buildSPIarray * 功能说明: 根据提供的操作码数构建SPI TX数据数组 * 形 参: opcodeArray 指向在SPI命令中使用的8位操作码数组的指针 * byteArray 指向要发送到设备的SPI字节数组的指针 * 返 回 值: numberOfBytes 添加到 byteArray[] 的字节数。 **********************************************************************************************************/ uint8_t buildSPIarray(const uint8_t opcodeArray[], uint8_t byteArray[]) { const uint8_t numberOpcodes = 2; //8位(命令+地址), and 8位数据位或者任意位 const uint8_t frontPadBytes = (STATUS_ENABLED ? 1 : 0) + (RES_24_BIT_ENABLED ? 1 : 0); //确定操作码/数据字节将放置在数组中的位置 const uint8_t numberOfBytes = numberOpcodes + frontPadBytes + (SPI_CRC_ENABLED ? 1 : 0);//确定时钟的总字节数 //将命令字节/数据放置在数组中的正确位置 byteArray[frontPadBytes] = opcodeArray[0];//第2位byteArray[2]=命令,例如42 4c,也就是说这前面的前两个是空着的 byteArray[frontPadBytes + 1] = opcodeArray[1];//第三位byteArray[3]=第二个命令 ,读是不在意,写是要写的数据 //byteArray[frontPadBytes + 2] = getCRC(opcodeArray, 2, CRC_INITIAL_SEED);//第四位是crc码 #ifdef SPI_CRC_MODE byteArray[frontPadBytes + 2] = getCRC(opcodeArray, 2, CRC_INITIAL_SEED); #endif return numberOfBytes; }
如果不启用表头,crc校验,选择24字节(1.3.5中)那么在片选为低的时候,只会传递三个字节数据。
上面是标准写函数的时序图,我们要传递的是三个字节分别是:0x00(dont care)、80+地址位,要写入的数据。例如写入 0x00 87 20 就是在地址位07的寄存器中写入20。通过上面的写函数,可以直接使用
ads127l11_writeSingleRegister(CONFIG4_REG_ADDRESS, CONFIG4_REG_DEFAULT);这样进行调用。
4.4读寄存器函数
我们可以通过读寄存器函数来看,是否正确写入了我们想要的数值。
/********************************************************************************************************** * 函 数 名: readSingleRegister * 功能说明: 读寄存器 * 形 参: address 寄存器地址 * 返 回 值: registerMap[address] 寄存器值 **********************************************************************************************************/ uint8_t ads127l11_readSingleRegister(uint8_t address) { /* 检查寄存器地址在范围内 */ //assert(address < NUM_REGISTERS); // 可以将数据数组移到函数之外以节省内存 uint8_t dataTx[SPI_BUFFER_SIZE] = { 0 }; uint8_t dataRx[SPI_BUFFER_SIZE] = { 0 }; // 构建操作码和 SPI TX 数组 const uint8_t arbitrary = 0x00; uint8_t opcode[2] = {(OPCODE_RREG | address), arbitrary}; uint8_t numberOfBytes = buildSPIarray(opcode, dataTx); /* 拉低片选 */ HAL_GPIO_WritePin(MCU_ADS127L11_CS_GPIO_Port, MCU_ADS127L11_CS_Pin, GPIO_PIN_RESET); // 发送 HAL_SPI_TransmitReceive(&hspi1,dataTx, dataRx, numberOfBytes,10000);//这里都是发送00,那么就是 /* 拉高片选 */ HAL_GPIO_WritePin(MCU_ADS127L11_CS_GPIO_Port, MCU_ADS127L11_CS_Pin, GPIO_PIN_SET); // 发送NULL命令取回寄存器数据 uint8_t nullopcode[2] = {(OPCODE_RREG | address), OPCODE_NULL}; mcu_ads127L11.init_reg[address] = ads127l11_sendCommand(nullopcode);//这才是第二祯 return mcu_ads127L11.init_reg[address]; }
这个代码发送了两祯数据,第二帧是通过函数
ads127l11_sendCommand发送的,该函数的具体代码参考官网。也可以使用前面一样的HAL_SPI_TransmitReceive底层函数来发送,不过这里就需要再添加一个拉低片选和拉高片选作为第二帧开始的标志。该代码形成的时序图应该完全与数据手册中对应。最后读出来的数据,会在下一帧的第一个字节中发送过来(没有表头情况。)
4.5读转换数据
这个读转换数据就是ads127的功能结果,也就是将电压转变为24为数据的结果,注意与读寄存器的数据不同。
/********************************************************************************************************** * 函 数 名: readData * 功能说明: 读adc数据 * 形 参: dataStruct adc通道 * 返 回 值: signedExtendedData **********************************************************************************************************/ int32_t ads127l11_readData(adc_channel_data_t *dataStruct) { //创建TX RX数组 uint8_t dataTx[SPI_BUFFER_SIZE] = { 0 }; uint8_t dataRx[SPI_BUFFER_SIZE] = { 0 }; uint8_t opcode[2] = {OPCODE_NULL, OPCODE_NULL}; uint8_t numberOfBytes = buildSPIarray(opcode, dataTx); /* 拉低片选 */ HAL_GPIO_WritePin(MCU_ADS127L11_CS_GPIO_Port, MCU_ADS127L11_CS_Pin, GPIO_PIN_RESET); HAL_Delay(1); // 发送 HAL_SPI_Transmit(&hspi1, dataTx, numberOfBytes, 1000); HAL_SPI_Receive(&hspi1, dataRx, numberOfBytes, 1000); /* 拉高片选 */ HAL_GPIO_WritePin(MCU_ADS127L11_CS_GPIO_Port, MCU_ADS127L11_CS_Pin, GPIO_PIN_SET); // 解析 ADC 响应... uint8_t byteIndex = STATUS_ENABLED ? 1 : 0;//表头开启 int32_t signedExtendedData = signExtend(&dataRx[byteIndex]);//拼接函数 if (dataStruct) //检查如果dataStruct是空指针 { if (STATUS_ENABLED)//如果有状态字节 { dataStruct->status = dataRx[0]; } if (SPI_CRC_ENABLED)//如果有CRC字节 { byteIndex = byteIndex + (RES_24_BIT_ENABLED ? 3 : 2);//这里是2+2 dataStruct->crc = dataRx[byteIndex]; } dataStruct->adc_data = signedExtendedData; } //mcu_ads127L11.ch_data = *dataStruct; return signedExtendedData; }
官网这里传递的形参是一个结构体,如果觉得麻烦也可以变为一个变量,我这里保留了,那么调用的时候就先要定义一个结构体变量。
我通过逻辑分析仪抓取的,当在主循环里面只调用这个函数,那么sdi是发送三个字节的0x00,下一帧就会返回这个24位的数据。其中
signExtend这个函数就是将三个八位拼接成一个24位数据,这个函数在adc与dac中用法比较常见,官网也有。
4.5 主函数的调用
定义变量
adc_channel_data_t ads127l11_data= {0, 0x00, 0x00}; uint32_t data[20]={0}; uint32_t i;
在循环里面调用,就可以读到数据了。
for(i=0; i<20; i++) { data[i] = ads127l11_readData(&ads127l11_data);//在这里显示读取的数据 }
注意主函数这里要调用前面说到的,gpio初始化,spi初始化,ads127的初始化,ads127l11_adcStartup();函数等包括你自己写的。
5、数据处理
5.1 数据的转换
我们想要得到电压的数字量,而不是一个24位的结果,简悦再写一个函数进行转换。下图给出了转换的公式,我测试当中只用到了第三个公式。
5.2 数据的显示
如果有串口通信的方式,可以串口打印输出。我用的方式是在clion中打断点,进行内监视的方式查看数据,这样比较方便,无论是寄存器的数值,还是转换的数据,都可以在clion中给他一个变量,来进行显示。
6、crc校验
在stm32cbue中存在spi校验,我们称为硬件校验,而芯片手册中的通过开启config4的第二位来开启crc校验,是软件校验。通过接入逻辑分析仪,发现硬件校验的crc是两个字节,软件校验只有一个字节。推荐使用软件校验,以便于符合数据手册中传递字节。这一部分还在测试之中。
7、测试
这里的测试,主要是通过波形发生器给芯片接入交流电压,然后获取采样点。根据采样定理,采样频率应该大于被采样的频率两倍以上,这样才能保证一个周期的采样点。
对config3的数据速率表格
因为是交流,高速模式,我们选择的数据速率就在这一个表格之中,最小是3.125ksps,最大为400ksps,经过测试发现,速率小不能满足我想要的采样点,所以就选择了OSR=32,数据速度为400ksps。
按照我输入的测试波形(频率为2000hz) ,那么应该一个周期我能输出200个数据,但是实际情况远远小于200个数据,大约一个周期70个采样点,也能满足我的需求。
这是波形,下面的是start信号频率为500hz,start是控制采样开始停止的重要信号,应该按照具体实际来设定频率。