SN_ADC.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. #include "SN_ADC.h"
  2. /*
  3. 文件名:SN_ADC.c/.h
  4. 作者: SN_FAE_黄泽洪
  5. 免责声明:无版权,可随意传播和篡改,该代码仅供开发参考,如需使用请自行验证
  6. 本人不担负商业使用上带来的风险。
  7. */
  8. /*
  9. 不带滤波,用户自行实现滤波
  10. SN_ADC 模块使用方法是:
  11. 1.先初始化对应通道道 SN_ADC_IN_init(ADC_CHANNEL_0|ADC_CHANNEL_1|ADC_CHANNEL_6);
  12. 2.启动adc转换 SN_ADC_start();
  13. 3.获取对应通道的ADC值 uint16_t val0 = SN_ADC_Get(0);
  14. uint16_t val1 = SN_ADC_Get(1);
  15. uint16_t val6 = SN_ADC_Get(2);
  16. 方法一:使用CPU等待adc转换完成,适用于CPU工作量不多的情况
  17. SN_ADC.h文件:
  18. #define ADC_IRQ_Handle 0
  19. main.c文件:
  20. uint16_t ADC_VAL = 0;
  21. float MCU_VDD = 0;
  22. int main(void)
  23. {
  24. SN_SYSCLK_set(SYSCLK_48MHZ);
  25. std_delay_init();
  26. SN_ADC_IN_init(ADC_CHANNEL_4_PA7|ADC_CHANNEL_5_PB3|ADC_CHANNEL_7_PB0);
  27. while(1){
  28. std_delayms(500);
  29. SN_ADC_start(); //此时编译的SN_ADC_star()函数是阻塞cpu时间的,是前台代码
  30. ADC_VAL = SN_ADC_Get(0);
  31. MCU_VDD = SN_ADC_MCU_VDD(); //获取MCU的当前vdd电压
  32. }
  33. }
  34. 方法二:使用ADC中断处理,减少CPU空跑时间
  35. SN_ADC.h文件:
  36. #define ADC_IRQ_Handle 1 //开启中断读取ADC值,单周期
  37. main.c文件:
  38. uint16_t ADC_VAL = 0;
  39. float MCU_VDD = 0;
  40. int main(void)
  41. {
  42. SN_SYSCLK_set(SYSCLK_48MHZ);
  43. std_delay_init();
  44. SN_ADC_IN_init(ADC_CHANNEL_4_PA7|ADC_CHANNEL_5_PB3|ADC_CHANNEL_7_PB0);
  45. SN_ADC_start(); //此时编译的SN_ADC_star()函数是中断处理的,是后台代码
  46. while(1){
  47. std_delayms(500);
  48. // SN_ADC_start(); //多次调用也可以
  49. ADC_VAL = SN_ADC_Get(0);
  50. MCU_VDD = SN_ADC_MCU_VDD(); //获取MCU的当前vdd电压
  51. }
  52. }
  53. 方法三:使用ADC中断处理,减少CPU空跑时间
  54. SN_ADC.h文件:
  55. #define ADC_IRQ_Handle 2 //开启中断读取ADC值,周期性更新ADC数据
  56. main.c文件:
  57. uint16_t ADC_VAL = 0;
  58. float MCU_VDD = 0;
  59. int main(void)
  60. {
  61. SN_SYSCLK_set(SYSCLK_48MHZ);
  62. std_delay_init();
  63. SN_ADC_IN_init(ADC_CHANNEL_4_PA7|ADC_CHANNEL_5_PB3|ADC_CHANNEL_7_PB0);
  64. SN_ADC_start(); //此时编译的SN_ADC_star()函数是中断处理的,是后台代码 ,处于循环更新状态的ADC,启动一次即可。
  65. while(1){
  66. std_delayms(500);
  67. ADC_VAL = SN_ADC_Get(0);
  68. MCU_VDD = SN_ADC_MCU_VDD(); //获取MCU的当前vdd电压
  69. }
  70. }
  71. 注意: ADC单通道转换的时间 = ADC采样时钟数(与ADC工作频率有关) + ADC固定的转换时间
  72. ADC多通道转换时间 = ADC单通道转换的时间 * 通道数
  73. 注意: ADC速度的时间选择:
  74. ADC的转换时间不是越快越好,这个和硬件也有关系,MCU的ADC模块内部也有一个小电容用于信号采样。
  75. 如果信号源的阻抗有些大(理论上信号的输出阻抗是无限小的)导致MCU的ADC电容正常充电时间慢(类似物理中的连通器),
  76. 而我们的采样时间快(ADC采样时钟数*ADC工作频率),那样我们采到的ADC值跟实际值相比就偏小了。
  77. 注意: ADC 12位分辨率最高采样率 1Msps ,比如ADC工作在48MHZ是无法工作的
  78. 单次单通道最高转换时间时钟(48MHZ下):std_adc_clock_config(ADC_CK_DIV2); std_adc_sampt_time_config(ADC_SAMPTIME_3CYCLES);
  79. 这个时间是 48mhz/2*3 + 13*48mhz/2 (固定转换时间 13 个 ADC_CK 时钟周期)
  80. 5通道序列最高转换时间时钟 (48MHZ下):std_adc_clock_config(ADC_CK_DIV3); std_adc_sampt_time_config(ADC_SAMPTIME_3CYCLES);
  81. 这个时间大致是 { 48mhz/2*3 + 13*48mhz/2 (固定转换时间 13 个 ADC_CK 时钟周期)} * 5
  82. 用户要自己在保证采样精度的前提下,实验调整时间,不要盲目追求ADC高速度。
  83. 注意: 不要使用PB6 作为ADC通道作为输入(ADC_CHANNEL_6),分压采样的上拉电阻可能导致SWD口无法使用。
  84. 注意: ADC 时钟频率与工作电压之间的关系如下,
  85. 2.2 V < VDDA ≤5.5V 时,300KHz≤fADC_CK ≤ 16MHz
  86. 1.8 V ≤ VDDA ≤ 2.2V 时,300KHz≤fADC_CK ≤ 8MHz
  87. 注意: 使用 ADC 时,需保持 RCH 时钟使能。
  88. */
  89. /* IO引脚和参数对应表格:
  90. ADC_CHANNEL_0 对应IO PB1
  91. ADC_CHANNEL_1 对应IO PA3
  92. ADC_CHANNEL_2 对应IO PA4
  93. ADC_CHANNEL_3 对应IO PA6
  94. ADC_CHANNEL_4 对应IO PA7
  95. ADC_CHANNEL_5 对应IO PB3
  96. ADC_CHANNEL_6 对应IO PB6
  97. ADC_CHANNEL_7 对应IO PB0
  98. 别名:
  99. ADC_CHANNEL_0_PB1 对应 ADC_CHANNEL_0
  100. ADC_CHANNEL_1_PA3 对应 ADC_CHANNEL_1
  101. ADC_CHANNEL_2_PA4 对应 ADC_CHANNEL_2
  102. ADC_CHANNEL_3_PA6 对应 ADC_CHANNEL_3
  103. ADC_CHANNEL_4_PA7 对应 ADC_CHANNEL_4
  104. ADC_CHANNEL_5_PB3 对应 ADC_CHANNEL_5
  105. ADC_CHANNEL_6_PB6 对应 ADC_CHANNEL_6
  106. ADC_CHANNEL_7_PB0 对应 ADC_CHANNEL_7
  107. */
  108. //该表是用来初始化通道对应的GPIO
  109. const static uint16_t ADC_CH_list[8] = {
  110. GPIO_PIN_1,
  111. GPIO_PIN_3,
  112. GPIO_PIN_4,
  113. GPIO_PIN_6,
  114. GPIO_PIN_7,
  115. GPIO_PIN_3,
  116. GPIO_PIN_6,
  117. GPIO_PIN_0,
  118. };
  119. //ADC通道值缓存
  120. __IO uint16_t ADC_VAL_list[9] = {0x00} ;
  121. //ADC通道注册列表
  122. uint8_t ADC_CH = 0;
  123. //VBGR值变量(每次ADC转换都通道一起获取,时刻刷新)
  124. __IO uint32_t VBGR_Val = 0;
  125. //ADC通道数量
  126. #ifdef ADC_BGR_Rectify
  127. __IO uint8_t ADC_CH_NUM = 1;
  128. #else
  129. __IO uint8_t ADC_CH_NUM = 0;
  130. #endif
  131. /*
  132. 函数名称:SN_ADC_IN_init()
  133. 功能: 设置ADC通道 (默认是正向扫描)
  134. @参数:channel (ADC通道):
  135. @ADC_CHANNEL_0 或 ADC_CHANNEL_0_PB1
  136. @ADC_CHANNEL_1 或 ADC_CHANNEL_1_PA3
  137. @ADC_CHANNEL_2 或 ADC_CHANNEL_2_PA4
  138. @ADC_CHANNEL_3 或 ADC_CHANNEL_3_PA6
  139. @ADC_CHANNEL_4 或 ADC_CHANNEL_4_PA7
  140. @ADC_CHANNEL_5 或 ADC_CHANNEL_5_PB3
  141. @ADC_CHANNEL_6 或 ADC_CHANNEL_6_PB6
  142. @ADC_CHANNEL_7 或 ADC_CHANNEL_7_PB0
  143. @ADC_CHANNEL_x|ADC_CHANNEL_y|ADC_CHANNEL_z (同时设置多通道)
  144. @返回值: 返回通道号集
  145. */
  146. uint8_t SN_ADC_IN_init(uint32_t channel){
  147. std_delay_init(); //必须的
  148. uint32_t channeN = channel;
  149. std_gpio_init_t gpio_config = {0};
  150. //初始化IO
  151. if((channel & 0x0000001E) != 0)
  152. std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOA);
  153. if((channel & 0x000000E1) != 0)
  154. std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOB);
  155. gpio_config.mode = GPIO_MODE_ANALOG;
  156. for(int i = 0 ; i < 8 ; i++){
  157. if( (channeN & 0x01) == 1){
  158. if( (i == 0) || (i == 5) || (i == 6) || (i == 7 ) ){
  159. gpio_config.pin = ADC_CH_list[i];
  160. std_gpio_init(GPIOB, &gpio_config);
  161. }else{
  162. gpio_config.pin = ADC_CH_list[i];
  163. std_gpio_init(GPIOA, &gpio_config);
  164. }
  165. ++ADC_CH_NUM;
  166. }
  167. channeN >>= 1 ;
  168. }
  169. //ADC设置
  170. /* 使能ADC时钟 */
  171. std_rcc_apb2_clk_enable(RCC_PERIPH_CLK_ADC);
  172. /* ADC_CK时钟为PCLK的3分频 */
  173. std_adc_clock_config(ADC_CK_DIV3);
  174. /* 软件触发ADC */
  175. std_adc_trig_sw();
  176. #if ADC_IRQ_Handle != 1
  177. /*循环转换模式 */
  178. std_adc_conversion_mode_config(ADC_CONTINUOUS_CONVER_MODE);
  179. #else
  180. std_adc_conversion_mode_config(ADC_SINGLE_CONVER_MODE);
  181. #endif
  182. /* 采样时间配置,119个周期*/
  183. std_adc_sampt_time_config(ADC_SAMPTIME_119CYCLES);
  184. /* 扫描方向配置:正向扫描 */
  185. std_adc_scan_direction_config(ADC_SCAN_DIR_FORWARD);
  186. #ifdef ADC_BGR_Rectify
  187. /* ADC通道 + VBGR */
  188. std_adc_fix_sequence_channel_enable(channel|ADC_CHANNEL_VBGR);
  189. std_adc_internal_channel_vbgr_enable(); //使能VBGR
  190. std_delayus(ADC_VBGR_CHANNEL_DELAY); //等待VBGR稳定
  191. #else
  192. /* ADC通道 */
  193. std_adc_fix_sequence_channel_enable(channel);
  194. #endif
  195. /* 配置wait模式,避免数据未及时读取,转换溢出 */
  196. std_adc_wait_mode_enable();
  197. /* ADC选择正常工作模式 */
  198. std_adc_mode_config(ADC_MODE_NORMAL);
  199. /* 使能ADC */
  200. std_adc_enable();
  201. /* 等待ADC使能状态稳定 */
  202. std_delayus(ADC_EN_DELAY); /*这个时间不能排除,因为这个和MCU的ADC类型有关,
  203. 逼近型ADC必须要有个上电稳定时间。
  204. 由于使用了std_delayus();就必须要这ADC初始化前,
  205. 调用std_delay_init();
  206. */
  207. /* 使能校准 */
  208. std_adc_calibration_enable();
  209. /* 等待校准完成 */
  210. while(std_adc_get_flag(ADC_FLAG_EOCAL) == 0U);
  211. /* 清除ADC转换状态,确保之前状态不影响转换 */
  212. std_adc_clear_flag(ADC_FLAG_ALL);
  213. ADC_CH |= channel;
  214. #if ADC_IRQ_Handle != 0
  215. /* 通道转换完成中断使能 */
  216. std_adc_interrupt_enable(ADC_INTERRUPT_EOC); //开启adc单通道转换完成标志
  217. std_adc_interrupt_enable(ADC_INTERRUPT_EOS); //开启adc序列转换完成标志
  218. /* ADC的NVIC中断初始化 */
  219. NVIC_SetPriority(ADC_COMP_IRQn, ADC_NVIC_PRIO_x);
  220. NVIC_EnableIRQ(ADC_COMP_IRQn);
  221. #endif
  222. // bsp_adc_software_calibrate(); //官方调整函数,精度不够,不使用
  223. return channel;
  224. }
  225. /*
  226. 函数名称:SN_ADC_Get()
  227. 功能: 获取ADC通道值 (默认是正向扫描) 或实际电压值
  228. @参数: ADC通道转换的顺序号
  229. 例如:我同时使用了adc 通道3 通道6 通道7
  230. 那么我读通道3 uint16_t val0 = SN_ADC_Get(0);
  231. 读通道6 uint16_t val1 = SN_ADC_Get(1);
  232. 读通道7 uint16_t val2 = SN_ADC_Get(2);
  233. 读VBGR SN_ADC_Get(LEN+1); //数组长度LEN
  234. @返回值: 对应通道的ADC值
  235. */
  236. uint16_t SN_ADC_Get(uint32_t list_next ){
  237. return ADC_VAL_list[list_next];
  238. }
  239. /*
  240. 函数名称:SN_ADC_Get_float() 使用前必须保证 #define ADC_BGR_Rectify
  241. 功能: 获取ADC通道值 (默认是正向扫描) 实际电压值
  242. @参数: ADC通道转换的顺序号
  243. 例如:我同时使用了adc 通道3 通道6 通道7
  244. 那么我读通道3 float val0 = SN_ADC_Get(0);
  245. 读通道6 float val1 = SN_ADC_Get(1);
  246. 读通道7 float val2 = SN_ADC_Get(2);
  247. @返回值: 对应通道的ADC值
  248. */
  249. float SN_ADC_Get_float(uint32_t list_next){
  250. //ADC静态参数修正 E0偏移误差 EG = E0-满量程误差
  251. return (float)(( ADC_VAL_list[list_next] - SN_ADC_E0 )) * ((float)(VBGR_Val/(float)(4096 - SN_ADC_E0 - SN_ADC_EN)));
  252. }
  253. /*
  254. 函数名称:SN_ADC_start()
  255. 功能: 启动ADC转换 (默认是正向扫描)
  256. 注意:SN_ADC.H 的宏定状态 ADC_IRQ_Handle
  257. #define ADC_IRQ_Handle 为 0 :不开启,直接占用cpu时间等待
  258. #define ADC_IRQ_Handle 为 1 :开启中断读取ADC值,单周期
  259. #define ADC_IRQ_Handle 为 2 :开启中断读取ADC值,周期性更新ADC数据
  260. @返回值: 无
  261. */
  262. void SN_ADC_start(){
  263. /* 启动转换 */
  264. std_adc_start_conversion();
  265. #if ADC_IRQ_Handle == 0
  266. uint8_t i = 0;
  267. while(std_adc_get_flag(ADC_FLAG_EOS) == 0U){
  268. /* 等待ADC通道转换完成 */
  269. while(std_adc_get_flag(ADC_FLAG_EOC) == 0U){}
  270. /* 获取ADC的转换数据 */
  271. ADC_VAL_list[i++] = std_adc_get_conversion_value();
  272. /* 清除EOC标志 */
  273. std_adc_clear_flag(ADC_FLAG_EOC);
  274. }
  275. /* 清除EOS标志 */
  276. std_adc_clear_flag(ADC_FLAG_EOS);
  277. /* 关闭ADC模块 */
  278. std_adc_stop_conversion();
  279. VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[i-1]); //更新VBGR数值,并转换成电压值
  280. #endif
  281. }
  282. /*
  283. 函数名称:ADC_COMP_IRQHandler
  284. 功能:adc的中断处理函数
  285. */
  286. void ADC_COMP_IRQHandler(void){
  287. static uint8_t adc_i = 0;
  288. //单通道处理
  289. if(std_adc_get_flag(ADC_FLAG_EOC))
  290. {
  291. /* 清除EOC中断标志 */
  292. std_adc_clear_flag(ADC_FLAG_EOC);
  293. /* 获取外部通道采样值 */
  294. ADC_VAL_list[adc_i++] = std_adc_get_conversion_value();
  295. }
  296. //序列完成标志
  297. if(std_adc_get_flag(ADC_ISR_EOS ))
  298. {
  299. /* 清除EOC中断标志 */
  300. std_adc_clear_flag(ADC_ISR_EOS);
  301. VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[adc_i-1]); //更新VBGR数值,并转换成电压值
  302. adc_i = 0;
  303. }
  304. #if ADC_IRQ_Handle == 1
  305. if(ADC_CH_NUM == adc_i){
  306. adc_i = 0;
  307. std_adc_stop_conversion();
  308. VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[ADC_CH_NUM-1]);
  309. }
  310. #endif
  311. }
  312. /*
  313. 函数名称:SN_ADC_MCU_VDD()
  314. 功能: 获得mcu的供电状况
  315. 参数: 无
  316. 返回: float VDD值
  317. //必须在宏定义中开启了BGR才有这个功能
  318. */
  319. float SN_ADC_MCU_VDD(void){
  320. #ifdef ADC_BGR_Rectify
  321. return (float)VBGR_Val;
  322. #else
  323. return 0;
  324. #endif
  325. }
  326. /*
  327. 函数名称:SN_ADC_Deinit()
  328. 返回:无
  329. */
  330. void SN_ADC_Deinit(void){
  331. //关闭ADC中断
  332. std_adc_interrupt_disable(ADC_INTERRUPT_EOC|ADC_INTERRUPT_EOS);
  333. //清除adc中断标志
  334. std_adc_clear_flag(ADC_FLAG_ALL);
  335. //关闭通道
  336. std_adc_fix_sequence_channel_disable(ADC_CHANNEL_ALL);
  337. //关闭VBGR通道
  338. std_adc_internal_channel_vbgr_disable();
  339. //关闭等待模式
  340. std_adc_wait_mode_disable();
  341. //禁用ADC
  342. std_adc_disable();
  343. //复位ADC
  344. std_adc_deinit();
  345. //关闭对于的ADC外设时钟
  346. std_rcc_apb2_clk_disable(RCC_PERIPH_CLK_ADC);
  347. }
  348. /**
  349. * @brief 提升ADC校准系数的精度
  350. * @retval 无
  351. */
  352. void bsp_adc_software_calibrate(void)
  353. {
  354. int32_t get_calfact = 0;
  355. /* 使能校准 */
  356. std_adc_calibration_enable();
  357. /* 等待校准完成 */
  358. while(std_adc_get_flag(ADC_FLAG_EOCAL) == 0U);
  359. /* 清除ADC转换状态,确保之前状态不影响转换 */
  360. std_adc_clear_flag(ADC_FLAG_ALL);
  361. get_calfact = std_adc_get_calibration_factor();
  362. /* 判断校准系数符号位 */
  363. if(get_calfact & ADC_CALFACT_SYMBOL)
  364. {
  365. /* 校准系数是负值,转换成32位有符号负数,方便计算 */
  366. get_calfact = get_calfact | 0xFFFFFFE0;
  367. }
  368. /* 校准系数减去ADC补偿值获取新的校准系数 */
  369. get_calfact = get_calfact - ADC_COMPENSATION_VALUE;
  370. /* 判断校准系数是否超限 */
  371. if(get_calfact > CALFACT_MAX)
  372. {
  373. get_calfact = CALFACT_MAX;
  374. }
  375. else if(get_calfact < CALFACT_MIN)
  376. {
  377. get_calfact = CALFACT_MIN;
  378. }
  379. std_adc_calibration_factor_config(get_calfact);
  380. }