123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- #include "SN_ADC.h"
- /*
- 文件名:SN_ADC.c/.h
- 作者: SN_FAE_黄泽洪
- 免责声明:无版权,可随意传播和篡改,该代码仅供开发参考,如需使用请自行验证
- 本人不担负商业使用上带来的风险。
- */
- /*
- 不带滤波,用户自行实现滤波
-
- SN_ADC 模块使用方法是:
-
-
-
-
- 1.先初始化对应通道道 SN_ADC_IN_init(ADC_CHANNEL_0|ADC_CHANNEL_1|ADC_CHANNEL_6);
- 2.启动adc转换 SN_ADC_start();
- 3.获取对应通道的ADC值 uint16_t val0 = SN_ADC_Get(0);
- uint16_t val1 = SN_ADC_Get(1);
- uint16_t val6 = SN_ADC_Get(2);
- 方法一:使用CPU等待adc转换完成,适用于CPU工作量不多的情况
- SN_ADC.h文件:
-
- #define ADC_IRQ_Handle 0
- main.c文件:
- uint16_t ADC_VAL = 0;
- float MCU_VDD = 0;
- int main(void)
- {
- SN_SYSCLK_set(SYSCLK_48MHZ);
- std_delay_init();
- SN_ADC_IN_init(ADC_CHANNEL_4_PA7|ADC_CHANNEL_5_PB3|ADC_CHANNEL_7_PB0);
- while(1){
-
- std_delayms(500);
- SN_ADC_start(); //此时编译的SN_ADC_star()函数是阻塞cpu时间的,是前台代码
- ADC_VAL = SN_ADC_Get(0);
- MCU_VDD = SN_ADC_MCU_VDD(); //获取MCU的当前vdd电压
- }
- }
- 方法二:使用ADC中断处理,减少CPU空跑时间
- SN_ADC.h文件:
-
-
- #define ADC_IRQ_Handle 1 //开启中断读取ADC值,单周期
- main.c文件:
- uint16_t ADC_VAL = 0;
- float MCU_VDD = 0;
- int main(void)
- {
- SN_SYSCLK_set(SYSCLK_48MHZ);
- std_delay_init();
- SN_ADC_IN_init(ADC_CHANNEL_4_PA7|ADC_CHANNEL_5_PB3|ADC_CHANNEL_7_PB0);
-
- SN_ADC_start(); //此时编译的SN_ADC_star()函数是中断处理的,是后台代码
-
- while(1){
-
- std_delayms(500);
- // SN_ADC_start(); //多次调用也可以
- ADC_VAL = SN_ADC_Get(0);
- MCU_VDD = SN_ADC_MCU_VDD(); //获取MCU的当前vdd电压
- }
- }
- 方法三:使用ADC中断处理,减少CPU空跑时间
- SN_ADC.h文件:
-
- #define ADC_IRQ_Handle 2 //开启中断读取ADC值,周期性更新ADC数据
- main.c文件:
- uint16_t ADC_VAL = 0;
- float MCU_VDD = 0;
- int main(void)
- {
- SN_SYSCLK_set(SYSCLK_48MHZ);
- std_delay_init();
- SN_ADC_IN_init(ADC_CHANNEL_4_PA7|ADC_CHANNEL_5_PB3|ADC_CHANNEL_7_PB0);
-
- SN_ADC_start(); //此时编译的SN_ADC_star()函数是中断处理的,是后台代码 ,处于循环更新状态的ADC,启动一次即可。
-
- while(1){
-
- std_delayms(500);
- ADC_VAL = SN_ADC_Get(0);
- MCU_VDD = SN_ADC_MCU_VDD(); //获取MCU的当前vdd电压
- }
- }
-
- 注意: ADC单通道转换的时间 = ADC采样时钟数(与ADC工作频率有关) + ADC固定的转换时间
- ADC多通道转换时间 = ADC单通道转换的时间 * 通道数
-
- 注意: ADC速度的时间选择:
- ADC的转换时间不是越快越好,这个和硬件也有关系,MCU的ADC模块内部也有一个小电容用于信号采样。
- 如果信号源的阻抗有些大(理论上信号的输出阻抗是无限小的)导致MCU的ADC电容正常充电时间慢(类似物理中的连通器),
- 而我们的采样时间快(ADC采样时钟数*ADC工作频率),那样我们采到的ADC值跟实际值相比就偏小了。
-
- 注意: ADC 12位分辨率最高采样率 1Msps ,比如ADC工作在48MHZ是无法工作的
-
- 单次单通道最高转换时间时钟(48MHZ下):std_adc_clock_config(ADC_CK_DIV2); std_adc_sampt_time_config(ADC_SAMPTIME_3CYCLES);
- 这个时间是 48mhz/2*3 + 13*48mhz/2 (固定转换时间 13 个 ADC_CK 时钟周期)
- 5通道序列最高转换时间时钟 (48MHZ下):std_adc_clock_config(ADC_CK_DIV3); std_adc_sampt_time_config(ADC_SAMPTIME_3CYCLES);
- 这个时间大致是 { 48mhz/2*3 + 13*48mhz/2 (固定转换时间 13 个 ADC_CK 时钟周期)} * 5
-
- 用户要自己在保证采样精度的前提下,实验调整时间,不要盲目追求ADC高速度。
-
- 注意: 不要使用PB6 作为ADC通道作为输入(ADC_CHANNEL_6),分压采样的上拉电阻可能导致SWD口无法使用。
-
- 注意: ADC 时钟频率与工作电压之间的关系如下,
- 2.2 V < VDDA ≤5.5V 时,300KHz≤fADC_CK ≤ 16MHz
- 1.8 V ≤ VDDA ≤ 2.2V 时,300KHz≤fADC_CK ≤ 8MHz
-
- 注意: 使用 ADC 时,需保持 RCH 时钟使能。
- */
- /* IO引脚和参数对应表格:
- ADC_CHANNEL_0 对应IO PB1
- ADC_CHANNEL_1 对应IO PA3
- ADC_CHANNEL_2 对应IO PA4
- ADC_CHANNEL_3 对应IO PA6
- ADC_CHANNEL_4 对应IO PA7
- ADC_CHANNEL_5 对应IO PB3
- ADC_CHANNEL_6 对应IO PB6
- ADC_CHANNEL_7 对应IO PB0
-
- 别名:
- ADC_CHANNEL_0_PB1 对应 ADC_CHANNEL_0
- ADC_CHANNEL_1_PA3 对应 ADC_CHANNEL_1
- ADC_CHANNEL_2_PA4 对应 ADC_CHANNEL_2
- ADC_CHANNEL_3_PA6 对应 ADC_CHANNEL_3
- ADC_CHANNEL_4_PA7 对应 ADC_CHANNEL_4
- ADC_CHANNEL_5_PB3 对应 ADC_CHANNEL_5
- ADC_CHANNEL_6_PB6 对应 ADC_CHANNEL_6
- ADC_CHANNEL_7_PB0 对应 ADC_CHANNEL_7
- */
- //该表是用来初始化通道对应的GPIO
- const static uint16_t ADC_CH_list[8] = {
-
- GPIO_PIN_1,
- GPIO_PIN_3,
- GPIO_PIN_4,
- GPIO_PIN_6,
- GPIO_PIN_7,
- GPIO_PIN_3,
- GPIO_PIN_6,
- GPIO_PIN_0,
-
- };
- //ADC通道值缓存
- __IO uint16_t ADC_VAL_list[9] = {0x00} ;
- //ADC通道注册列表
- uint8_t ADC_CH = 0;
- //VBGR值变量(每次ADC转换都通道一起获取,时刻刷新)
- __IO uint32_t VBGR_Val = 0;
- //ADC通道数量
- #ifdef ADC_BGR_Rectify
- __IO uint8_t ADC_CH_NUM = 1;
- #else
- __IO uint8_t ADC_CH_NUM = 0;
- #endif
- /*
- 函数名称:SN_ADC_IN_init()
- 功能: 设置ADC通道 (默认是正向扫描)
- @参数:channel (ADC通道):
- @ADC_CHANNEL_0 或 ADC_CHANNEL_0_PB1
- @ADC_CHANNEL_1 或 ADC_CHANNEL_1_PA3
- @ADC_CHANNEL_2 或 ADC_CHANNEL_2_PA4
- @ADC_CHANNEL_3 或 ADC_CHANNEL_3_PA6
- @ADC_CHANNEL_4 或 ADC_CHANNEL_4_PA7
- @ADC_CHANNEL_5 或 ADC_CHANNEL_5_PB3
- @ADC_CHANNEL_6 或 ADC_CHANNEL_6_PB6
- @ADC_CHANNEL_7 或 ADC_CHANNEL_7_PB0
- @ADC_CHANNEL_x|ADC_CHANNEL_y|ADC_CHANNEL_z (同时设置多通道)
- @返回值: 返回通道号集
- */
- uint8_t SN_ADC_IN_init(uint32_t channel){
-
- std_delay_init(); //必须的
-
- uint32_t channeN = channel;
-
- std_gpio_init_t gpio_config = {0};
-
- //初始化IO
- if((channel & 0x0000001E) != 0)
- std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOA);
- if((channel & 0x000000E1) != 0)
- std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOB);
- gpio_config.mode = GPIO_MODE_ANALOG;
-
- for(int i = 0 ; i < 8 ; i++){
- if( (channeN & 0x01) == 1){
- if( (i == 0) || (i == 5) || (i == 6) || (i == 7 ) ){
- gpio_config.pin = ADC_CH_list[i];
- std_gpio_init(GPIOB, &gpio_config);
- }else{
- gpio_config.pin = ADC_CH_list[i];
- std_gpio_init(GPIOA, &gpio_config);
- }
- ++ADC_CH_NUM;
- }
- channeN >>= 1 ;
- }
-
- //ADC设置
- /* 使能ADC时钟 */
- std_rcc_apb2_clk_enable(RCC_PERIPH_CLK_ADC);
-
- /* ADC_CK时钟为PCLK的3分频 */
- std_adc_clock_config(ADC_CK_DIV3);
-
- /* 软件触发ADC */
- std_adc_trig_sw();
-
- #if ADC_IRQ_Handle != 1
- /*循环转换模式 */
- std_adc_conversion_mode_config(ADC_CONTINUOUS_CONVER_MODE);
- #else
- std_adc_conversion_mode_config(ADC_SINGLE_CONVER_MODE);
-
- #endif
-
- /* 采样时间配置,119个周期*/
- std_adc_sampt_time_config(ADC_SAMPTIME_119CYCLES);
-
- /* 扫描方向配置:正向扫描 */
- std_adc_scan_direction_config(ADC_SCAN_DIR_FORWARD);
-
-
- #ifdef ADC_BGR_Rectify
- /* ADC通道 + VBGR */
- std_adc_fix_sequence_channel_enable(channel|ADC_CHANNEL_VBGR);
- std_adc_internal_channel_vbgr_enable(); //使能VBGR
- std_delayus(ADC_VBGR_CHANNEL_DELAY); //等待VBGR稳定
-
- #else
- /* ADC通道 */
- std_adc_fix_sequence_channel_enable(channel);
- #endif
- /* 配置wait模式,避免数据未及时读取,转换溢出 */
- std_adc_wait_mode_enable();
-
- /* ADC选择正常工作模式 */
- std_adc_mode_config(ADC_MODE_NORMAL);
- /* 使能ADC */
- std_adc_enable();
-
- /* 等待ADC使能状态稳定 */
- std_delayus(ADC_EN_DELAY); /*这个时间不能排除,因为这个和MCU的ADC类型有关,
- 逼近型ADC必须要有个上电稳定时间。
- 由于使用了std_delayus();就必须要这ADC初始化前,
- 调用std_delay_init();
- */
- /* 使能校准 */
- std_adc_calibration_enable();
-
- /* 等待校准完成 */
- while(std_adc_get_flag(ADC_FLAG_EOCAL) == 0U);
- /* 清除ADC转换状态,确保之前状态不影响转换 */
- std_adc_clear_flag(ADC_FLAG_ALL);
-
- ADC_CH |= channel;
-
- #if ADC_IRQ_Handle != 0
-
- /* 通道转换完成中断使能 */
- std_adc_interrupt_enable(ADC_INTERRUPT_EOC); //开启adc单通道转换完成标志
- std_adc_interrupt_enable(ADC_INTERRUPT_EOS); //开启adc序列转换完成标志
-
- /* ADC的NVIC中断初始化 */
- NVIC_SetPriority(ADC_COMP_IRQn, ADC_NVIC_PRIO_x);
- NVIC_EnableIRQ(ADC_COMP_IRQn);
-
- #endif
- // bsp_adc_software_calibrate(); //官方调整函数,精度不够,不使用
- return channel;
- }
- /*
- 函数名称:SN_ADC_Get()
- 功能: 获取ADC通道值 (默认是正向扫描) 或实际电压值
- @参数: ADC通道转换的顺序号
- 例如:我同时使用了adc 通道3 通道6 通道7
- 那么我读通道3 uint16_t val0 = SN_ADC_Get(0);
- 读通道6 uint16_t val1 = SN_ADC_Get(1);
- 读通道7 uint16_t val2 = SN_ADC_Get(2);
- 读VBGR SN_ADC_Get(LEN+1); //数组长度LEN
- @返回值: 对应通道的ADC值
- */
- uint16_t SN_ADC_Get(uint32_t list_next ){
-
- return ADC_VAL_list[list_next];
- }
- /*
- 函数名称:SN_ADC_Get_float() 使用前必须保证 #define ADC_BGR_Rectify
- 功能: 获取ADC通道值 (默认是正向扫描) 实际电压值
- @参数: ADC通道转换的顺序号
- 例如:我同时使用了adc 通道3 通道6 通道7
- 那么我读通道3 float val0 = SN_ADC_Get(0);
- 读通道6 float val1 = SN_ADC_Get(1);
- 读通道7 float val2 = SN_ADC_Get(2);
-
- @返回值: 对应通道的ADC值
- */
- float SN_ADC_Get_float(uint32_t list_next){
- //ADC静态参数修正 E0偏移误差 EG = E0-满量程误差
- return (float)(( ADC_VAL_list[list_next] - SN_ADC_E0 )) * ((float)(VBGR_Val/(float)(4096 - SN_ADC_E0 - SN_ADC_EN)));
-
- }
- /*
- 函数名称:SN_ADC_start()
- 功能: 启动ADC转换 (默认是正向扫描)
- 注意:SN_ADC.H 的宏定状态 ADC_IRQ_Handle
- #define ADC_IRQ_Handle 为 0 :不开启,直接占用cpu时间等待
- #define ADC_IRQ_Handle 为 1 :开启中断读取ADC值,单周期
- #define ADC_IRQ_Handle 为 2 :开启中断读取ADC值,周期性更新ADC数据
- @返回值: 无
- */
- void SN_ADC_start(){
-
-
- /* 启动转换 */
- std_adc_start_conversion();
-
- #if ADC_IRQ_Handle == 0
- uint8_t i = 0;
- while(std_adc_get_flag(ADC_FLAG_EOS) == 0U){
- /* 等待ADC通道转换完成 */
- while(std_adc_get_flag(ADC_FLAG_EOC) == 0U){}
- /* 获取ADC的转换数据 */
- ADC_VAL_list[i++] = std_adc_get_conversion_value();
- /* 清除EOC标志 */
- std_adc_clear_flag(ADC_FLAG_EOC);
- }
- /* 清除EOS标志 */
- std_adc_clear_flag(ADC_FLAG_EOS);
- /* 关闭ADC模块 */
- std_adc_stop_conversion();
-
- VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[i-1]); //更新VBGR数值,并转换成电压值
-
-
- #endif
-
- }
- /*
- 函数名称:ADC_COMP_IRQHandler
- 功能:adc的中断处理函数
- */
- void ADC_COMP_IRQHandler(void){
- static uint8_t adc_i = 0;
- //单通道处理
- if(std_adc_get_flag(ADC_FLAG_EOC))
- {
- /* 清除EOC中断标志 */
- std_adc_clear_flag(ADC_FLAG_EOC);
- /* 获取外部通道采样值 */
- ADC_VAL_list[adc_i++] = std_adc_get_conversion_value();
- }
- //序列完成标志
- if(std_adc_get_flag(ADC_ISR_EOS ))
- {
- /* 清除EOC中断标志 */
- std_adc_clear_flag(ADC_ISR_EOS);
- VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[adc_i-1]); //更新VBGR数值,并转换成电压值
- adc_i = 0;
- }
- #if ADC_IRQ_Handle == 1
- if(ADC_CH_NUM == adc_i){
- adc_i = 0;
- std_adc_stop_conversion();
- VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[ADC_CH_NUM-1]);
- }
- #endif
-
- }
- /*
- 函数名称:SN_ADC_MCU_VDD()
- 功能: 获得mcu的供电状况
- 参数: 无
- 返回: float VDD值
- //必须在宏定义中开启了BGR才有这个功能
- */
- float SN_ADC_MCU_VDD(void){
-
- #ifdef ADC_BGR_Rectify
-
- return (float)VBGR_Val;
- #else
- return 0;
-
- #endif
-
- }
- /*
- 函数名称:SN_ADC_Deinit()
- 返回:无
- */
- void SN_ADC_Deinit(void){
- //关闭ADC中断
- std_adc_interrupt_disable(ADC_INTERRUPT_EOC|ADC_INTERRUPT_EOS);
- //清除adc中断标志
- std_adc_clear_flag(ADC_FLAG_ALL);
- //关闭通道
- std_adc_fix_sequence_channel_disable(ADC_CHANNEL_ALL);
- //关闭VBGR通道
- std_adc_internal_channel_vbgr_disable();
- //关闭等待模式
- std_adc_wait_mode_disable();
- //禁用ADC
- std_adc_disable();
- //复位ADC
- std_adc_deinit();
- //关闭对于的ADC外设时钟
- std_rcc_apb2_clk_disable(RCC_PERIPH_CLK_ADC);
-
- }
- /**
- * @brief 提升ADC校准系数的精度
- * @retval 无
- */
- void bsp_adc_software_calibrate(void)
- {
- int32_t get_calfact = 0;
-
- /* 使能校准 */
- std_adc_calibration_enable();
-
- /* 等待校准完成 */
- while(std_adc_get_flag(ADC_FLAG_EOCAL) == 0U);
-
- /* 清除ADC转换状态,确保之前状态不影响转换 */
- std_adc_clear_flag(ADC_FLAG_ALL);
-
- get_calfact = std_adc_get_calibration_factor();
-
- /* 判断校准系数符号位 */
- if(get_calfact & ADC_CALFACT_SYMBOL)
- {
- /* 校准系数是负值,转换成32位有符号负数,方便计算 */
- get_calfact = get_calfact | 0xFFFFFFE0;
- }
-
- /* 校准系数减去ADC补偿值获取新的校准系数 */
- get_calfact = get_calfact - ADC_COMPENSATION_VALUE;
-
- /* 判断校准系数是否超限 */
- if(get_calfact > CALFACT_MAX)
- {
- get_calfact = CALFACT_MAX;
- }
- else if(get_calfact < CALFACT_MIN)
- {
- get_calfact = CALFACT_MIN;
- }
- std_adc_calibration_factor_config(get_calfact);
- }
|