#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); }