SN_ADC.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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. {
  148. std_delay_init(); // 必须的
  149. uint32_t channeN = channel;
  150. std_gpio_init_t gpio_config = {0};
  151. // 初始化IO
  152. if ((channel & 0x0000001E) != 0)
  153. std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOA);
  154. if ((channel & 0x000000E1) != 0)
  155. std_rcc_gpio_clk_enable(RCC_PERIPH_CLK_GPIOB);
  156. gpio_config.mode = GPIO_MODE_ANALOG;
  157. for (int i = 0; i < 8; i++)
  158. {
  159. if ((channeN & 0x01) == 1)
  160. {
  161. if ((i == 0) || (i == 5) || (i == 6) || (i == 7))
  162. {
  163. gpio_config.pin = ADC_CH_list[i];
  164. std_gpio_init(GPIOB, &gpio_config);
  165. }
  166. else
  167. {
  168. gpio_config.pin = ADC_CH_list[i];
  169. std_gpio_init(GPIOA, &gpio_config);
  170. }
  171. ++ADC_CH_NUM;
  172. }
  173. channeN >>= 1;
  174. }
  175. // ADC设置
  176. /* 使能ADC时钟 */
  177. std_rcc_apb2_clk_enable(RCC_PERIPH_CLK_ADC);
  178. /* ADC_CK时钟为PCLK的3分频 */
  179. std_adc_clock_config(ADC_CK_DIV3);
  180. /* 软件触发ADC */
  181. std_adc_trig_sw();
  182. #if ADC_IRQ_Handle != 1
  183. /*循环转换模式 */
  184. std_adc_conversion_mode_config(ADC_CONTINUOUS_CONVER_MODE);
  185. #else
  186. std_adc_conversion_mode_config(ADC_SINGLE_CONVER_MODE);
  187. #endif
  188. /* 采样时间配置,119个周期*/
  189. std_adc_sampt_time_config(ADC_SAMPTIME_119CYCLES);
  190. /* 扫描方向配置:正向扫描 */
  191. std_adc_scan_direction_config(ADC_SCAN_DIR_FORWARD);
  192. #ifdef ADC_BGR_Rectify
  193. /* ADC通道 + VBGR */
  194. std_adc_fix_sequence_channel_enable(channel | ADC_CHANNEL_VBGR);
  195. std_adc_internal_channel_vbgr_enable(); // 使能VBGR
  196. std_delayus(ADC_VBGR_CHANNEL_DELAY); // 等待VBGR稳定
  197. #else
  198. /* ADC通道 */
  199. std_adc_fix_sequence_channel_enable(channel);
  200. #endif
  201. /* 配置wait模式,避免数据未及时读取,转换溢出 */
  202. std_adc_wait_mode_enable();
  203. /* ADC选择正常工作模式 */
  204. std_adc_mode_config(ADC_MODE_NORMAL);
  205. /* 使能ADC */
  206. std_adc_enable();
  207. /* 等待ADC使能状态稳定 */
  208. std_delayus(ADC_EN_DELAY); /*这个时间不能排除,因为这个和MCU的ADC类型有关,
  209. 逼近型ADC必须要有个上电稳定时间。
  210. 由于使用了std_delayus();就必须要这ADC初始化前,
  211. 调用std_delay_init();
  212. */
  213. /* 使能校准 */
  214. std_adc_calibration_enable();
  215. /* 等待校准完成 */
  216. while (std_adc_get_flag(ADC_FLAG_EOCAL) == 0U)
  217. ;
  218. /* 清除ADC转换状态,确保之前状态不影响转换 */
  219. std_adc_clear_flag(ADC_FLAG_ALL);
  220. ADC_CH |= channel;
  221. #if ADC_IRQ_Handle != 0
  222. /* 通道转换完成中断使能 */
  223. std_adc_interrupt_enable(ADC_INTERRUPT_EOC); // 开启adc单通道转换完成标志
  224. std_adc_interrupt_enable(ADC_INTERRUPT_EOS); // 开启adc序列转换完成标志
  225. /* ADC的NVIC中断初始化 */
  226. NVIC_SetPriority(ADC_COMP_IRQn, ADC_NVIC_PRIO_x);
  227. NVIC_EnableIRQ(ADC_COMP_IRQn);
  228. #endif
  229. // bsp_adc_software_calibrate(); //官方调整函数,精度不够,不使用
  230. return channel;
  231. }
  232. /*
  233. 函数名称:SN_ADC_Get()
  234. 功能: 获取ADC通道值 (默认是正向扫描) 或实际电压值
  235. @参数: ADC通道转换的顺序号
  236. 例如:我同时使用了adc 通道3 通道6 通道7
  237. 那么我读通道3 uint16_t val0 = SN_ADC_Get(0);
  238. 读通道6 uint16_t val1 = SN_ADC_Get(1);
  239. 读通道7 uint16_t val2 = SN_ADC_Get(2);
  240. 读VBGR SN_ADC_Get(LEN+1); //数组长度LEN
  241. @返回值: 对应通道的ADC值
  242. */
  243. uint16_t SN_ADC_Get(uint32_t list_next)
  244. {
  245. return ADC_VAL_list[list_next];
  246. }
  247. /*
  248. 函数名称:SN_ADC_Get_float() 使用前必须保证 #define ADC_BGR_Rectify
  249. 功能: 获取ADC通道值 (默认是正向扫描) 实际电压值
  250. @参数: ADC通道转换的顺序号
  251. 例如:我同时使用了adc 通道3 通道6 通道7
  252. 那么我读通道3 float val0 = SN_ADC_Get(0);
  253. 读通道6 float val1 = SN_ADC_Get(1);
  254. 读通道7 float val2 = SN_ADC_Get(2);
  255. @返回值: 对应通道的ADC值
  256. */
  257. float SN_ADC_Get_float(uint32_t list_next)
  258. {
  259. // ADC静态参数修正 E0偏移误差 EG = E0-满量程误差
  260. return (float)((ADC_VAL_list[list_next] - SN_ADC_E0)) * ((float)(VBGR_Val / (float)(4096 - SN_ADC_E0 - SN_ADC_EN)));
  261. }
  262. /*
  263. 函数名称:SN_ADC_start()
  264. 功能: 启动ADC转换 (默认是正向扫描)
  265. 注意:SN_ADC.H 的宏定状态 ADC_IRQ_Handle
  266. #define ADC_IRQ_Handle 为 0 :不开启,直接占用cpu时间等待
  267. #define ADC_IRQ_Handle 为 1 :开启中断读取ADC值,单周期
  268. #define ADC_IRQ_Handle 为 2 :开启中断读取ADC值,周期性更新ADC数据
  269. @返回值: 无
  270. */
  271. void SN_ADC_start()
  272. {
  273. /* 启动转换 */
  274. std_adc_start_conversion();
  275. #if ADC_IRQ_Handle == 0
  276. uint8_t i = 0;
  277. while (std_adc_get_flag(ADC_FLAG_EOS) == 0U)
  278. {
  279. /* 等待ADC通道转换完成 */
  280. while (std_adc_get_flag(ADC_FLAG_EOC) == 0U)
  281. {
  282. }
  283. /* 获取ADC的转换数据 */
  284. ADC_VAL_list[i++] = std_adc_get_conversion_value();
  285. /* 清除EOC标志 */
  286. std_adc_clear_flag(ADC_FLAG_EOC);
  287. }
  288. /* 清除EOS标志 */
  289. std_adc_clear_flag(ADC_FLAG_EOS);
  290. /* 关闭ADC模块 */
  291. std_adc_stop_conversion();
  292. VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[i - 1]); // 更新VBGR数值,并转换成电压值
  293. #endif
  294. }
  295. /*
  296. 函数名称:ADC_COMP_IRQHandler
  297. 功能:adc的中断处理函数
  298. */
  299. void ADC_COMP_IRQHandler(void)
  300. {
  301. static uint8_t adc_i = 0;
  302. // 单通道处理
  303. if (std_adc_get_flag(ADC_FLAG_EOC))
  304. {
  305. /* 清除EOC中断标志 */
  306. std_adc_clear_flag(ADC_FLAG_EOC);
  307. /* 获取外部通道采样值 */
  308. ADC_VAL_list[adc_i++] = std_adc_get_conversion_value();
  309. }
  310. // 序列完成标志
  311. if (std_adc_get_flag(ADC_ISR_EOS))
  312. {
  313. /* 清除EOC中断标志 */
  314. std_adc_clear_flag(ADC_ISR_EOS);
  315. VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[adc_i - 1]); // 更新VBGR数值,并转换成电压值
  316. adc_i = 0;
  317. }
  318. #if ADC_IRQ_Handle == 1
  319. if (ADC_CH_NUM == adc_i)
  320. {
  321. adc_i = 0;
  322. std_adc_stop_conversion();
  323. VBGR_Val = std_adc_calc_vref_voltage(ADC_VAL_list[ADC_CH_NUM - 1]);
  324. }
  325. #endif
  326. }
  327. /*
  328. 函数名称:SN_ADC_MCU_VDD()
  329. 功能: 获得mcu的供电状况
  330. 参数: 无
  331. 返回: float VDD值
  332. //必须在宏定义中开启了BGR才有这个功能
  333. */
  334. float SN_ADC_MCU_VDD(void)
  335. {
  336. #ifdef ADC_BGR_Rectify
  337. return (float)VBGR_Val;
  338. #else
  339. return 0;
  340. #endif
  341. }
  342. /*
  343. 函数名称:SN_ADC_Deinit()
  344. 返回:无
  345. */
  346. void SN_ADC_Deinit(void)
  347. {
  348. // 关闭ADC中断
  349. std_adc_interrupt_disable(ADC_INTERRUPT_EOC | ADC_INTERRUPT_EOS);
  350. // 清除adc中断标志
  351. std_adc_clear_flag(ADC_FLAG_ALL);
  352. // 关闭通道
  353. std_adc_fix_sequence_channel_disable(ADC_CHANNEL_ALL);
  354. // 关闭VBGR通道
  355. std_adc_internal_channel_vbgr_disable();
  356. // 关闭等待模式
  357. std_adc_wait_mode_disable();
  358. // 禁用ADC
  359. std_adc_disable();
  360. // 复位ADC
  361. std_adc_deinit();
  362. // 关闭对于的ADC外设时钟
  363. std_rcc_apb2_clk_disable(RCC_PERIPH_CLK_ADC);
  364. }
  365. /**
  366. * @brief 提升ADC校准系数的精度
  367. * @retval 无
  368. */
  369. void bsp_adc_software_calibrate(void)
  370. {
  371. int32_t get_calfact = 0;
  372. /* 使能校准 */
  373. std_adc_calibration_enable();
  374. /* 等待校准完成 */
  375. while (std_adc_get_flag(ADC_FLAG_EOCAL) == 0U)
  376. ;
  377. /* 清除ADC转换状态,确保之前状态不影响转换 */
  378. std_adc_clear_flag(ADC_FLAG_ALL);
  379. get_calfact = std_adc_get_calibration_factor();
  380. /* 判断校准系数符号位 */
  381. if (get_calfact & ADC_CALFACT_SYMBOL)
  382. {
  383. /* 校准系数是负值,转换成32位有符号负数,方便计算 */
  384. get_calfact = get_calfact | 0xFFFFFFE0;
  385. }
  386. /* 校准系数减去ADC补偿值获取新的校准系数 */
  387. get_calfact = get_calfact - ADC_COMPENSATION_VALUE;
  388. /* 判断校准系数是否超限 */
  389. if (get_calfact > CALFACT_MAX)
  390. {
  391. get_calfact = CALFACT_MAX;
  392. }
  393. else if (get_calfact < CALFACT_MIN)
  394. {
  395. get_calfact = CALFACT_MIN;
  396. }
  397. std_adc_calibration_factor_config(get_calfact);
  398. }