2022.7.1更新一下。
第6步读数据,提到的外部时钟好像读不出数据的问题解决了。之前配置外部时钟(CFR数据位D([8,7])),datasheet里面提到说需要14sclk的转化时间(conversion time)。这一点一直没有深入理解。
最近重新又研究了一遍datasheet,看到了这么一幅图。他包括了一开始4个sclk的指令,中间12or24sclk的采样时间(sampling time),和后面14sclk的转化时间(conversion time)。
我程序里面用的12sclk的采样时间,所以发送的时候,发送了半字(HalfWord),但注意到,这里的转化时间,也是需要时钟的。而程序中,我发送完半字后,不再发内容的话,时钟就没有了(这个可以用示波器测到)。因此,需要在发送半字内容后,再发送半字,保证有时钟产生(我这里就是发送了通道选择指令后,再发上16bits的0)。
for(int i = 0; i < NumOfFIFO; i++)
{
/* 开始通讯:CS低电平*/
ADC_SPI_CS_LOW();
ADC_SPI_SendHalfWord(WriteBuf[i]);
ADC_SPI_SendHalfWord(0x0000);
//SysTick_Delay_Us(10);
/* 结束通讯:CS高电平*/
ADC_SPI_CS_HIGH();
}
}
这样,就可以用外部时钟进行转化了。
——————————————————————————————————————————
2022.6.27第一次更新
最近的课题有用到stm32f103利用SPI通信去读ADC的数据,用的ADC是tlv2548。stm32的学习是用的野火的开发板,也是刚入门的水平,调这个spi程序搞了好久,而且好像也没搜到这个adc的编程资料,备受煎熬。现在终于是能读出数据了,发个帖子记录分享一下,欢迎用同样芯片的同学跟我讨论,共同进步。
0.先贴一下原理图。需要注意一下:FS好像是跟DSP用的,这里没用就直接接了电源,CSTART好像可以自己设置采样时间,是高级用法,这里没有用,虽然没有直接接电源,但用stm32将这个引脚设为了高电平,也跟接电源一样了。
1.tlv2548的SPI通信,需要配置成固定的模式,即CPOL=0, CPHA=0。关于速度的话,他说不超过20MHz,我没有仔细研究别的行不行,我这里用的32分频。
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
2.tlv2548我这里配置的是一次发送半字,16位。一次发送8位一个字节也可以。
SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
3.读写的函数套用的野火的模板,他这里一个函数同时实现了发送和读取。
/**
* @brief 使用SPI发送两个字节的数据
* @param byte:要发送的数据
* @retval 返回接收到的数据
*/
u16 ADC_SPI_SendHalfWord(u16 HalfWord)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待发送缓冲区为空,TXE事件 */
while (SPI_I2S_GetFlagStatus(ADC_SPIx , SPI_I2S_FLAG_TXE) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(2);
}
/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
SPI_I2S_SendData(ADC_SPIx , HalfWord);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待接收缓冲区非空,RXNE事件 */
while (SPI_I2S_GetFlagStatus(ADC_SPIx , SPI_I2S_FLAG_RXNE) == RESET)
{
if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(3);
}
/* 读取数据寄存器,获取接收缓冲区数据 */
return SPI_I2S_ReceiveData(ADC_SPIx );
}
4.tlv2548说是要在使用的时候发送0xA000初始化确定型号.
void ADC_init()
{
uint16_t CFR_Writed = 0;
CFR_Writed = 0xA000;
WriteCFRdata(CFR_Writed);
}
5.tlv2548好像是只有1个16位寄存器CFR(Configuration Register)(所以我上面SPI的数据位也设成16位,方便操作),CFR的写入和读取操作最简单,所以我是从这步操作开始入门的。但需要注意的是CFR的读取只有在5,6位数据为00时才能使用。
这里写入了0xA102(第一位A是写入的意思),试一试能不能读出来 ,先拉低CS,再发送0xA102,再拉高CS。
void CFR_Write()
{
ADC_SPI_CS_LOW();
ADC_SPI_SendHalfWord(0xA102);
ADC_SPI_CS_HIGH();
}
贴一张CFR写入的时序图
发送0x9000读取CFR(第一位9是读取的意思,后面三位随意,这里就设为0),拉低CS,发送0x9000并读取数据,CS拉高
u16 CFR_Read()
{
u16 Temp = 0;
ADC_SPI_CS_LOW();
Temp = ADC_SPI_SendHalfWord(0x9000);
ADC_SPI_CS_HIGH();
return Temp;
}
贴一张CFR读取的时序图
6.读一下数据
这里读数据用的是mode11,因为时序图最简单hhh
首先进行上图第1步模式配置,这里是内部参考,4V,12sclk采样,内部时钟转换(这里的内部时钟转换需要注意,我开始是设的另外3中外部时钟的模式,但好像读不出来数据,不知道为什么。然后偶然间改成了内部时钟转换,才能读出来数据了),重复扫描模式,启用中断,8个FIFO
void ADC_Mode10_Config(uint16_t NumOfFIFO)
{
uint16_t CFR_Writed;
CFR_Writed = REF_INTER|INT_REF_4V|SHORT_SAM_12_SCLK|CON_CLK_OSC|RER_SWE_MOD|SWE_SEQ_S0|INT|FIFO_FULL;
WriteCFRdata(CFR_Writed);
}
然后第2步,设置通道0x0000~0x7000。这里设了个10us的延时,因为看到说内部时钟转换的话有3.86us转换时间
void SelectChannel(uint16_t NumOfFIFO)
{
uint16_t WriteBuf[8]={0, 0, 0, 0, 0, 0, 0, 0};
WriteBuf[0] = CHANNEL0; // 0x0000
WriteBuf[1] = CHANNEL1; // 0x1000
WriteBuf[2] = CHANNEL2; // 0x2000
WriteBuf[3] = CHANNEL3; // 0x3000
WriteBuf[4] = CHANNEL4; // 0x4000
WriteBuf[5] = CHANNEL5; // 0x5000
WriteBuf[6] = CHANNEL6; // 0x6000
WriteBuf[7] = CHANNEL7; // 0x7000
for(int i = 0; i < NumOfFIFO; i++)
{
ADC_SPI_CS_LOW();
ADC_SPI_SendHalfWord(WriteBuf[i]);
SysTick_Delay_Us(10);
ADC_SPI_CS_HIGH();
}
}
然后第3步,发送0xE000读数据
void ReadFIFOdata(uint16_t *DataBuffer, uint16_t NumOfFIFO)
{
uint16_t WriteBuf[1]={0};
WriteBuf[0] = R_FIFO;//0xE000
for(int i = 0; i < NumOfFIFO; i++)
{
ADC_SPI_CS_LOW();
DataBuffer[i] = ADC_SPI_SendHalfWord(WriteBuf[0]);
ADC_SPI_CS_HIGH();
}
}
7. 配置中断,INT是从高变到低,所以配置了下降沿中断
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
和相应的中断服务函数,设个flag判断有没有进中断
void KEY1_IRQHandler(void)
{
//确保是否产生了EXTI Line中断
if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET)
{
flag = 1;
//清除中断标志位
EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);
}
}
8.主函数main中的一些处理。首先是CFR写入和读取
ADC_init();
CFR_Write();
CFR = CFR_Read();
printf("\r\n CFR is 0x%X\r\n", CFR);
然后是读ADC数据,先进行CFR配置(对应步骤1),while循环,选择通道(对应步骤2)。这时候按说已经产生了中断,flag=1。判断是否进中断,进中断了读FIFO数据(对应步骤3),FIFO是前12位有效,所以做一下处理。再将flag置0,然后再重新选择通道,(对应步骤2的循环),每过1s打印一次数据。
ADC_Mode10_Config(NumOfFIFO);
while(1)
{
SelectChannel(NumOfFIFO);
if(flag == 1)
{
ReadFIFOdata(DataBuffer, NumOfFIFO);
for(int i = 0; i < NumOfFIFO; i++)
{
adc[i] = (DataBuffer[i] >> 4) * 3.3 / 4095;
printf("ADC = %04f V\n", adc[i]);
adc[i] = 0.0;
}
flag = 0;
}
SysTick_Delay_Ms(1000);
}
9. 目前大概理解了这些,有什么问题大家可以讨论,共同进步。