文章描述了使用STM32F407通过SPI通信实现了ADS1256的轮询功能。解决了使用过程中,各个通道之间数据读取串码的问题。SPI通信可以使用硬件SPI通信,也可以使用I/O模拟SPI通信。区别在于硬件SPI通信的过程中不会被中断打断,具有较高的稳定性,但是代码移植性不高。在实现软件SPI通信成功后,硬件SPI也是非常简单的。
阅读数据手册
数据手册这里只列举关键部分的内容。
读懂一些必须的Timing
t1:表示的是SPI的SCLK最小时钟周期,τCLKIN 为 ADS1256 的 输入时钟周期,按照该芯片推荐的7.68MHz 的时钟可知 τCLKIN = 130ns,故 t1 为 520ns。
t 2H 和 t 2L:由 t1 可知二者必须都大于200ns,切相加要大于520ns。
t6:在发送读取命令(RDATA,RDATAC和RREG)后,需要延时6.51us,可以取10us。
t11:上一个命令的最后时钟周期的下降沿到下一个命令的第一个时钟周期的上升沿之间的时间。
t17:这是指在没有更新通道的情况下,每次数据更新的时间,即16 × 0.13us = 2.08us,同是可以推出30000SPS采样率的情况下DRDY为低电平的窗口时长为 1/30000 - 2.08 = 31us。这是一个非常重要的信息,也是解决问题的关键。
t18:根据描叙,t18 是从信号在模拟输入端被改变后,传输到数字滤波器后需要的时间,之后数据才是可读的,这时DRDY引脚被拉低。
表格13,表示的是在不同的采样率下数据建立的时间(t18),图18表示的是在SYNC命令或者硬件SYNC生效后,数据索取时序图。
轮询的原理
轮询各通道的时序图如上。结合上一节读懂的Timing,可以设计如下软件设计思路:
1 等待DRDY拉低,按照上述写寄存器命令设置下一个要读取的通道。
2 发送SYNC命令,同步A/D转换。在下一个命令到来前需要延时 t11 = 24 × 0.3us = 3.12us,这里取5us。
3 发送WAKEUP命令,使SYNC命令生效。
SYNC命令在WAKEUP命令的第一个时钟下降沿生效。生效后DRDY立即变为高电平。(在WAKEUP后面延时视情况而定,只要WAKEUP命令 + 延时 + RDATA命令 + t 6 + 接受数据的总时间 不超过 t 18即可。)
4 发送RDATA命令,延时 t 6 后接受上个通道的数据。(这里解释下,每次设置好某一通道后,需要经过t18后,该通道的数据转换才完成)
数据读取错误或者各通道出现相同的数据的问题
本次实验的软件思路为:用定时器每隔一秒触发轮询四个通道(无论是差分还是单个),同时将DRDY设置为外部中断,当两个中断的中断标志位均为 “1” 时,则设置一个通道并读取上一个通道的数据。
解决办法分为两步:1 在保证SPI通信稳定且准确的情况下,尽量提高通信速率。2 在定时器中断标志位为“1”后,必须要在新的DRDY中断标志位置 “1” 后,立即执行轮询代码(原因后面会讲)。
这是主函数WHILE部分代码:
while(1)
{
u16 show_y = 210;
//轮询部分
if(TIME3_Overflow_Count_By_1_1s == 1) //定时器中断标志位为1
{
DRDY_GET_LOW = 0; //在轮询前,先将DRDY中断标志位置“0”
for(i = 0; i < 4096; i++)
ADS1256_Cycling_Visit();
TIME3_Overflow_Count_By_1_1s = 0;
}
//轮询部分
ADS1256_Data_Convert(g_tADS1256.AdcCh1);
for(i = 0; i < 1024; i++)
{
average += convert[i]/1024;
}
temp = average;
average = 0;
if(temp < 0)
{
temp = -temp;
LCD_ShowChar(120,show_y,'-',16,0);
LCD_ShowxNum(130,show_y,(temp/1000000),1,16,0);
LCD_ShowxNum(150,show_y,((temp%1000000)/100000),1,16,0);
LCD_ShowxNum(160,show_y,(((temp%1000000)%100000))/10000,1,16,0);
LCD_ShowxNum(170,show_y,((((temp%1000000)%100000))%10000)/1000,1,16,0);
}
else
{
LCD_ShowChar(120,show_y,' ',16,0);
LCD_ShowxNum(130,show_y,(temp/1000000),1,16,0);
LCD_ShowxNum(150,show_y,((temp%1000000)/100000),1,16,0);
LCD_ShowxNum(160,show_y,(((temp%1000000)%100000))/10000,1,16,0);
LCD_ShowxNum(170,show_y,((((temp%1000000)%100000))%10000)/1000,1,16,0);
}
}
轮询函数:先设置通道,然后读取上个通道的数据。
void ADS1256_Cycling_Visit(void)
{
ADS1256_Change_CH();
ADS1256_Read_Previous_Data();
}
设置通道函数:
void ADS1256_Change_CH(void)
{
while(DRDY_GET_LOW == 0){;} //等待新的DRDY中断标志位
DRDY_GET_LOW = 0;
if(g_tADS1256.ScanMode == 0)
ADS1256_SetChannal(g_tADS1256.Channel); //切换模拟通道 /
//delay_us(5);
else
ADS1256_SetDiffChannal(g_tADS1256.Channel); /* 切换差分模拟通道 */
ADS1256_WriteCmd(CMD_SYNC);
delay_us(4);
ADS1256_WriteCmd(CMD_WAKEUP);
delay_us(25);
}
读取上一个通道数据函数:
static void ADS1256_Read_Previous_Data(void)
{
static u16 count;
if (g_tADS1256.Channel == 0)
{
g_tADS1256.AdcCh4[count] = ADS1256_Read_DOUT_Data(); /* 注意保存的是上一个通道的数据 */
}
else if(g_tADS1256.Channel == 1)
{
g_tADS1256.AdcCh1[count] = ADS1256_Read_DOUT_Data(); /* 注意保存的是上一个通道的数据 */
}
else if(g_tADS1256.Channel == 2)
{
g_tADS1256.AdcCh2[count] = ADS1256_Read_DOUT_Data();
}
else if(g_tADS1256.Channel == 3)
{
g_tADS1256.AdcCh3[count] = ADS1256_Read_DOUT_Data();
}
if (++g_tADS1256.Channel >= 4)
{
g_tADS1256.Channel = 0;
count++;
if(count >= 1024)
count = 0;
}
}
现在来解释其中的原因:
1 在保证SPI通信稳定且准确的情况下,尽量提高通信速率。
图中第一条时序线为DRDY,第二条为DOUT,第三条为DIN,第四条为SCLK。
在不同的采样率下,都必须让A(设置通道的时间) + B(SYNC命令 + t11)落在DRDY为低电平的时间窗口内,否则匹配不了ADS1256的SPI协议。如图:
在刚刚设置好通道后,DRDY就进入下一个低电平窗口期了,这就直接导致后面传输错误。同时也是因为通信频率太慢的原因,上次读取数据还没结束,下一次的通道设置又开始了。
2在定时器中断标志位为“1”后,必须要在新的DRDY中断标志位置 “1” 后,立即执行轮询代码
在判断定时器溢出后,但此时DRDY已经拉低了一段时间了,导致设置通道导函数部分依然超过了DRDY为低电平的窗口期,WAKEUP命令还没来得及激活SYNC命令,使得DRDY拉高,也就是没进入轮循模式。故期间DRDY又有拉低的情形。想想看,如果设置函数从DRDY低电平窗口的其它时间点开始,会有更明显的错误。故在轮询前,先将DRDY中断标志位置“0”。