一.芯片介绍
CS1238是一款高精度、低功耗 模数转换芯片,两路差分输入通道,内置温度传感器和高精度振荡器。MCU可以通过2线的SPI 接口SCLK、DRDY与CS1238进行通信,对其进行配置,例如通道选择、PGA选择、输出速率选择等。下面是CS1238的一些特点。
二.硬件设计
CS1238的管脚定义如下
结合框架图,可以看出CS1238的使用方法比较简单,除去电源口就只有4个输入脚,CS1238和MCU的通信只要2个IO口模拟SPI。
做了一个小模块验证,我这里使用了一个电位器来模拟差分信号的输入。
三,程序设计
CS1238的程序设计比较简单,按照逻辑可以分为4步。
1。初始化IO口。
2。初始化CS1238,也就是配置CS1238的寄存器。
3。读取CS1238的24位ADC数据。
4。进行换算。
第一步就是初始化STM32的两个普通IO口就可以了,不需要配置SPI。
/**IO脚位初始化 */
void cs1238_IO_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_4|GPIO_Pin_5);
}
然后重点来了,我们读数据和写数据的时候,要切换IO口的模式,所以还需要写一个切换输入输出。
/** 设置数据工作引脚模式为 输入 */
void set_dout_mode_in(void)
{
GPIOA->CRL&=0XFFF0FFFF;GPIOA->CRL|=(u32)8<<16;
}
/**设置数据工作引脚模式为 输出 */
void set_dout_mode_out(void)
{
GPIOA->CRL&=0XFFF0FFFF;GPIOA->CRL|=(u32)3<<16;
}
CS1238的程序重点在于他的读写时序,首先看下时序图。
CS1238一次完整的周期有46个时钟信号。每一个时钟的都有特定的作用。首先写一个时钟周期。
/** cs1238 时序时钟*/
void cs1238_clock(void)
{
IO_CLK_AD_H();
Delay_us(5);
IO_CLK_AD_L();
Delay_us(5);
}
这里要注意到高电平的时间不可以超过100us,不然就会进入到睡眠模式。
第二步就是初始化CS1238的配置寄存器。
然后就是初始化CS1238,按照上面的功能图,48个时钟可以为下面几个步骤。
1,1-24时钟是读数据(写配置的时候跳过)。
2,25-29时钟根据功能配置IO口方向,我们这里是写,所以IO口模式是输出。
3,30到36的时候,我们发送一个操作指令给CS1238,如果我们是要写配置寄存器,
那么就发送0X65,如果是读配置寄存器,那么就发送0X56。
4,37时钟的时候,如果接下来是写配置寄存器,IO口模式是输出,如果是读配置寄存器,
IO口模式就是输入。
5,38到45如果是写配置寄存器,那么就把你要写入的值发送给CS1238,如果是读配置寄存器,
那就是收数据。
6,最后就是把数据脚拉高。
CS1238的配置寄存器只有一个,8位1字节,功能为设置ADC的放大倍数和输出数据频率等。
/**设置CS1238寄存器*/
void set_cs1238_config(uint8_t ad_reg)
{
uint16_t bit_cout = 0;
uint8_t reg_temp = 0x00;
//DOUT由高变低之后开始读取数据
set_dout_mode_out();
IO_DATA_AD_H();
set_dout_mode_in();
IO_CLK_AD_L(); //时钟拉低
while (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)) //等待芯片准备好
{
Delay_ms(1); //定时查询的方法需要缩短查询间隔 例如:1ms~5ms
bit_cout++;
if (bit_cout > 300)
{
bit_cout = 0;
set_dout_mode_out();
IO_CLK_AD_H(); // CLK 1
IO_DATA_AD_H(); // OUT 1
return; // 超时退出
}
}
/* clk1-clk29 写期间不需要操作*/
for (bit_cout = 0; bit_cout < 29; bit_cout++)
{
cs1238_clock(); //给一周期时钟
}
/* clk30 - clk36 发送写寄存器命令字 */
set_dout_mode_out();
reg_temp = 0xCA; //命令长度为 7bits (写0x65)左移1位
for (bit_cout = 0; bit_cout < 7; bit_cout++)
{
if (reg_temp & 0x80) //MSB
{
IO_DATA_AD_H();
}
else
{
IO_DATA_AD_L();
}
reg_temp = reg_temp << 1;
cs1238_clock();
}
/* clk37写寄存器模式还是输出 */
cs1238_clock();
/* clk38 ~ clk45 写入寄存器值 */
reg_temp = ad_reg;//要配件的寄存器数值
for (bit_cout = 0; bit_cout < 8; bit_cout++)
{
if (reg_temp & 0x80) //MSB
{
IO_DATA_AD_H();
}
else
{
IO_DATA_AD_L();
}
reg_temp = reg_temp << 1;
cs1238_clock();
}
/* 8: clk46 */
set_dout_mode_in();
IO_DATA_AD_H();
cs1238_clock();
return ;
}
读配置寄存器的代码如下。
/**读CS1238寄存器*/
int32_t read_cs1238_config(void)
{
uint16_t bit_cout = 0;
uint8_t reg_temp = 0x00;
//DOUT由高变低之后开始读取数据
set_dout_mode_out();
IO_DATA_AD_H();
set_dout_mode_in();
IO_CLK_AD_L(); //时钟拉低
while (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)) //等待芯片准备好
{
Delay_ms(1); //定时查询的方法需要缩短查询间隔 例如:1ms~5ms
bit_cout++;
if (bit_cout > 300)
{
bit_cout = 0;
set_dout_mode_out();
IO_CLK_AD_H(); // CLK 1
IO_DATA_AD_H(); // OUT 1
return 0; // 超时退出
}
}
/* clk1-clk29 读寄存器期间不需要操作*/
for (bit_cout = 0; bit_cout < 29; bit_cout++)
{
cs1238_clock(); //给一周期时钟
}
/* clk30 - clk36 发送读寄存器命令字 */
set_dout_mode_out();
reg_temp = 0xAC; //命令长度为 7bits (写0x56)左移1位
for (bit_cout = 0; bit_cout < 7; bit_cout++)
{
if (reg_temp & 0x80) //MSB
{
IO_DATA_AD_H();
}
else
{
IO_DATA_AD_L();
}
reg_temp = reg_temp << 1;
cs1238_clock();
}
/* clk37,读寄存器模式改输入 */
cs1238_clock();
set_dout_mode_in();
for (bit_cout = 0; bit_cout < 8; bit_cout++)
{
cs1238_clock();
reg_temp <<= 1; //左移1位准备接受数据 初始默认0
if (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)) //有数值则累加
reg_temp++;
Delay_us(5);
}
/* 8: clk46 */
cs1238_clock();
set_dout_mode_in();
IO_DATA_AD_H();
return reg_temp ;
}
第3步就是读ADC数据,这个最简单,只需要在1到24时钟周期内读数据就行。
/**读取CS1238的AD数*/
int32_t read_cs1238_ad_data(void)
{
uint16_t bit_cout = 0;
int32_t data_temp = 0;
//DOUT由高变低之后开始读取数据
set_dout_mode_out();
IO_DATA_AD_H();
set_dout_mode_in();
IO_CLK_AD_L(); //时钟拉低
while (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)) //等待芯片准备好
{
Delay_ms(1); //定时查询的方法需要缩短查询间隔 例如:1ms~5ms
bit_cout++;
if (bit_cout > 300)
{
bit_cout = 0;
set_dout_mode_out();
IO_CLK_AD_H(); // CLK 1
IO_DATA_AD_H(); // OUT 1
return 0; // 超时退出
}
}
/* clk1 ~ clk24 ADC数据*/
data_temp = 0;
for (bit_cout = 0; bit_cout < 24; bit_cout++)
{
IO_CLK_AD_H();
Delay_us(5);
data_temp <<= 1; //左移1位准备接受数据 初始默认0
if (GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)) //有数值则累加
data_temp++;
IO_CLK_AD_L();
Delay_us(5);
}
/* clk25 ~ clk46 */
for (bit_cout = 0; bit_cout < 22; bit_cout++)
{
cs1238_clock(); //给一周期时钟
}
set_dout_mode_in();
IO_DATA_AD_H();
if (data_temp & 0x00800000) // 判断是负数 最高位24位是符号位
{
return -(((~data_temp) & 0x007FFFFF) + 1); // 补码变源码
}
return data_temp; // 正数的补码就是源码
}
最后就是数据的换算。按照下面的图可以算出这样子的公式。
AINP-AINN=(读出来的24位数据/8388607)*(0.5REF/Gain放大倍数)。
这里的83888607就是7FFFFF的10十进制,REF是CS1238参考电源的数值。
最后是视频演示,资料和完整的代码工程直接在B站的视频简介下载就行,码字不易,求一个3连。
【开源,CS1238 ADC芯片功能演示,求求给一个赞-哔哩哔哩】 https://b23.tv/YFGIk9b