蓝牙芯片有2种ADC,一种是普通多通道10bit ADC,一种是音频mic单通道16bit LADC。
普通ADC => 无DMA,单次采样单通道,带校准带参考电压,ADC采样值可以换算成绝对电压值。
音频LADC => 支持DMA连续采样,无参考电压,一般用作交流变化量的采样使用。
普通ADC外设说明:
量程电压 => 强VDDIO的电压值,满值ADC值:0x3FFL对应芯片的VDDIO电压值。 强VDDIO设置为VDDIOM_VOL_32V,即3.2V,那能测量的范围就是0V-3.2V。 强VDDIO设置为VDDIOM_VOL_30V,即3.0V,那能测量的范围就是0V-3.0V。
参考电压 => LDOREF电压,芯片内部的固定幅值参考电压。(生产会存在一定的离散)
校准值 => 芯片出产校准值,get_vbg_trim(),校准芯片LDOREF电压的偏差值。
ADC通道说明:
芯片内部通道 => 芯片内部某些外设或结构电路,会预留有ADC通道,连接到ADC外设,用于芯片内部模块的自校准。
外部通道GPIO引脚 => 引脚是否具有ADC功能,是固定的,软件无法映射的。设计板子注意引脚功能。
SDK有专门的接口文件 adc_api.c 和adc_api.h。
adc的使用方式,SDK预留了2种方式:
注册定时采样 => u32 adc_add_sample_ch(u32 ch); 在定时器中断函数里,定时轮询所有的注册通道。注册的通道越多,轮询的时间越长。 usr_timer_add(NULL, adc_scan, 10, 0); 此语句即每10ms切换下一个通道采样。
独占式立即采样 => adc_enter_occupy_mode(); adc_occupy_run(); adc_exit_occupy_mode(); 立即采样需要打断定时轮询的操作。立即采样当前设置的通道。 一般用于采样频率高,电压变化快,采样时刻短的场景。
1. 注册定时采样
//以下初始化代码添加到void adc_init()里
u32 gpio = IO_PORTA_01;//指定IO,要和ADC通道对应上
gpio_set_pull_down(gpio, 0);//看需求是否需要开内部下拉,会有20%的误差,不同芯片理论电阻值不一样,看规格书说明
gpio_set_pull_up(gpio, 0);//看需求是否需要开内部上拉,会有20%的误差,不同芯片理论电阻值不一样,看规格书说明
gpio_set_die(gpio, 0);//模拟态
gpio_set_dieh(port, 0);//模拟态
gpio_set_direction(gpio, 1);//输入态
u32 ch = AD_CH_PA1; ///指定ADC通道,要和GPIO对应上
adc_add_sample_ch(ch);//添加检测通道
#define CH_SAMPLE_FREQ 1000 //ms
adc_set_sample_freq(ch, CH_SAMPLE_FREQ);//设置固定的采样频率,一般不设置,就是最高的轮询时间
//以下2句自己写个函数丢定时器里
//注意:这里只负责读取AD转换的结果值和根据VDDIO换算出电压值,并不是实际的AD转换过程。AD转换是adc_sample(ch)函数开启的,转换完成后会进入中断,在中断服务函数___interrupt static void adc_isr()里读取结果。
u32 adc = adc_get_value(ch); //!!!切记,不建议用adc值做判断,不同的VDDIO电压adc值是不一样!!!
u32 voltage = adc_get_voltage(ch);//一定要用带校准值换算的接口,电压值才是准确的
2. 独占式立即采样
u32 io_adc_check(u8 port, u8 ch) //耗时250us
{
log_info("%s[%d %d]", __func__, port, ch);
u32 adc_value = 0;
u32 voltage = 0;
gpio_set_die(port, 0);//模拟态
gpio_set_dieh(port, 0);//模拟态
gpio_set_direction(port, 1);//输入态
gpio_set_pull_down(port, 0);
gpio_set_pull_up(port, 0);
adc_enter_occupy_mode(ch); //!!!注意此函数有bug,不一定会进入独占模式
// 参考下面说明优化 while(adc_enter_occupy_mode(ch));
adc_value = adc_occupy_run();
adc_exit_occupy_mode();
voltage = adc_value_to_voltage(adc_get_value(AD_CH_LDOREF), adc_value);
log_info("%s[0x%08x %d]", __func__, adc_value, voltage);
return voltage;
}
u32 voltage = io_adc_check(IO_PORTA_01, AD_CH_PA1);
//公版SDK独占模式adc采样的bug优化
int adc_enter_occupy_mode(u32 ch) //此函数改成带返回值
{
if (JL_ADC->CON & BIT(4)) {
return 1;
}
adc_queue[ADC_MAX_CH].ch = ch;
cur_ch_value = adc_sample(ch);
return 0;
}
while(adc_enter_occupy_mode(ch)); //调用方式改成while死等adc中断释放adc外设,其他语句不变
3.抢占式快速采样立即采样
static volatile u8 is_break = 0;
void adc_scan(void *priv)
{
static u8 adc_sample_flag = 0;
if (adc_queue[ADC_MAX_CH].ch != -1) { //occupy mode
return;
}
if (JL_ADC->CON & BIT(4)) {
return ;
}
if(is_break){ // 添加此段代码 start
adc_sample(adc_queue[cur_ch].ch);
is_break = 0;
return;
} // 添加此段代码 end
if (adc_sample_flag) {
......
}
AT_VOLATILE_RAM_CODE//指定以下函数放到内部RAM里运行
u32 adc_fast_sample(u32 ch) //96M频率耗时1us-2us之间
{
/* printf("%s[%d 0x%x]", __func__, clk_get("lsb"), ch); */
u16 adc_value = 0;
/* local_irq_disable(); */
is_break = 1; //打断原先未采样完的通道,adc_scan需要重新设置采样
adc_queue[ADC_MAX_CH].ch = ch;
JL_ADC->CON = BIT(6);//CPND:只写,写‘1’清除中断请求位,写‘0’无效(该 bit充当 kst,对该写‘1’将启动 ADC 转换)
JL_ADC->CON |= ((ch&0xf)<<8);//IO通道
JL_ADC->CON |= ((0x0<<12)|BIT(4)|BIT(3)|(0b000));//lsb 1分频
JL_ADC->CON |= BIT(6);//启动 ADC 转换
while ((JL_ADC->CON & BIT(7)) == 0);//判断是否转换完成。PND:只读,中断请求位,当 ADC 完成一次转换后,此位会被设置为‘1’,需由软件清‘0’
adc_value = JL_ADC->RES;//读取转换结果
JL_ADC->CON = BIT(6);//清除中断请求位
adc_queue[ADC_MAX_CH].ch = -1;
/* local_irq_enable(); */
return adc_value;
}
4. 其他注意事项
公版SDK初始化的ADC采样时钟不高,采样一次大概耗时66us,理论最高可做到2us采样一次。
程序在开机的时候,必须要调用 adc_init(), 不要调整SDK此函数的调用位置 ,建议其他功能都要放到此函数之后。
注册定时采样和独占式采样和抢占式快速采样均是使用中断方式。
检测外部电压较高的电池(超过4.5V)时,硬件上可以用分压电阻加MOS管方式,软件上采用快速抢占式立即采样,在检测前打开MOS管,检测完毕关闭MOS即可,可以极大的降低系统功耗。
————————————————
版权声明:本文为优快云博主「ydgd118」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/ydgd118/article/details/128188758