前言
ADC(Analog to Digital Converter)顧名思義是將類比訊號轉換成數位訊號的元件,現今多數 MCU 都會內建 ADC,而這也是相當基本且常用的功能。
在之前的篇章中已經分別介紹 Regular 及 Injected 的單與多通道,之前對於等待 ADC 轉換完成的程式寫法都是單純的 Blocking(阻塞),這次要介紹中斷的寫法,使用中斷對於大量的 ADC 轉換作業會比起 Blocking 更有效率。
正文
首先一樣以 Nucleo-F446RE 做示範。
首先建立一個 PIO 的專案,選擇 Framework 爲「libopencm3」,並在 src/
資料夾中新增並開啓 main.c
與 main.h
。
完整程式
1/**
2 * @file main.c
3 * @brief ADC interrupt example for STM32 Nucleo-F446RE.
4 */
5
6#include "main.h"
7
8int main(void)
9{
10 rcc_setup();
11 adc_setup();
12 usart_setup();
13
14 /* Software start the first conversion. */
15 adc_start_conversion_regular(ADC1);
16
17 while (1)
18 { }
19 return 0;
20}
21
22static void rcc_setup(void)
23{
24 rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
25
26 rcc_periph_clock_enable(RCC_USART_TX_GPIO);
27 rcc_periph_clock_enable(RCC_USART2);
28 rcc_periph_clock_enable(RCC_ADC_GPIO);
29 rcc_periph_clock_enable(RCC_ADC1);
30}
31
32static void adc_setup(void)
33{
34/* Set to input analog. */
35 gpio_mode_setup(GPIO_ADC_PORT,
36 GPIO_MODE_ANALOG,
37 GPIO_PUPD_NONE,
38 GPIO_ADC_IN0_PIN);
39
40 /* Setup ADC. */
41 adc_power_off(ADC1);
42
43 adc_disable_scan_mode(ADC1);
44 adc_disable_external_trigger_regular(ADC1);
45 adc_set_single_conversion_mode(ADC1);
46 adc_set_right_aligned(ADC1);
47 adc_set_sample_time_on_all_channels(ADC1, ADC_SIMPLE_TIME);
48
49 /* Setup interrupt. */
50 adc_enable_eoc_interrupt(ADC1);
51 nvic_enable_irq(NVIC_ADC_IRQ);
52
53 uint8_t channels[16];
54 channels[0] = 0;
55 adc_set_regular_sequence(ADC1, 1, channels);
56
57 adc_power_on(ADC1);
58 delay(800000); /* Wait a bit. */
59}
60
61static void usart_setup(void)
62{
63 /* Set USART-Tx pin to alternate function. */
64 gpio_mode_setup(GPIO_USART_TX_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_USART_TX_PIN);
65 gpio_set_af(GPIO_USART_TX_PORT, GPIO_USART_AF, GPIO_USART_TX_PIN);
66
67 /* Config USART params. */
68 usart_set_baudrate(USART2, USART_BAUDRATE);
69 usart_set_databits(USART2, 8);
70 usart_set_stopbits(USART2, USART_STOPBITS_1);
71 usart_set_parity(USART2, USART_PARITY_NONE);
72 usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
73 usart_set_mode(USART2, USART_MODE_TX);
74
75 usart_enable(USART2);
76}
77
78static void delay(uint32_t value)
79{
80 for (uint32_t i = 0; i < value; i++)
81 {
82 __asm__("nop"); /* Do nothing. */
83 }
84}
85
86/* For printf(). */
87int _write(int file, char *ptr, int len)
88{
89 int i;
90
91 if (file == 1)
92 {
93 for (i = 0; i < len; i++)
94 {
95 usart_send_blocking(USART2, ptr[i]);
96 }
97 return i;
98 }
99
100 errno = EIO;
101 return -1;
102}
103
104/**
105 * @brief ADC Interrupt service routine.
106 */
107void adc_isr(void)
108{
109 /* Clear regular end of conversion flag. */
110 ADC_SR(ADC1) &= ~ADC_SR_EOC;
111
112 uint16_t value = adc_read_regular(ADC1);
113 printf("%4d\r\n", value);
114 delay(5000000);
115
116 /* Sart a new conversion. */
117 adc_start_conversion_regular(ADC1);
118}
1/**
2 * @file main.h
3 */
4
5#ifndef MAIN_H
6#define MAIN_H
7
8#include <stdio.h> /* For printf(). */
9#include <errno.h> /* For printf(). */
10#include <libopencm3/stm32/rcc.h>
11#include <libopencm3/stm32/gpio.h>
12#include <libopencm3/stm32/adc.h>
13#include <libopencm3/stm32/usart.h>
14#include <libopencm3/cm3/nvic.h>
15
16#define USART_BAUDRATE (9600)
17
18#define ADC_SIMPLE_TIME (ADC_SMPR_SMP_56CYC)
19#define RCC_ADC_GPIO (RCC_GPIOA)
20#define GPIO_ADC_PORT (GPIOA)
21#define GPIO_ADC_IN0_PIN (GPIO0) /* Arduino-A0. */
22
23#define RCC_USART_TX_GPIO (RCC_GPIOA)
24#define GPIO_USART_TX_PORT (GPIOA)
25#define GPIO_USART_TX_PIN (GPIO2) /* ST-Link (Arduino-D1). */
26#define GPIO_USART_AF (GPIO_AF7) /* Ref: Table-11 in DS10693. */
27
28static void rcc_setup(void);
29static void usart_setup(void);
30static void adc_setup(void);
31static void delay(uint32_t value);
32
33#endif /* MAIN_H. */
分段說明
Include
1// main.h
2#include <stdio.h> /* For printf(). */
3#include <errno.h> /* For printf(). */
4#include <libopencm3/stm32/rcc.h>
5#include <libopencm3/stm32/gpio.h>
6#include <libopencm3/stm32/adc.h>
7#include <libopencm3/stm32/usart.h>
8#include <libopencm3/cm3/nvic.h>
除了基本的 rcc.h
和 gpio.h
及必要的 adc.h
外,因爲我要使用 USART 和 printf()
,所以還會需要 usart.h
、stdio.h
與 errno.h
。
另外就是因爲要使用中斷功能,所以 nvic.h
也是必要的。
USART 和
printf()
的詳細用法請看之前的文章。
設定 ADC
1static void adc_setup(void)
2{
3/* Set to input analog. */
4 gpio_mode_setup(GPIO_ADC_PORT,
5 GPIO_MODE_ANALOG,
6 GPIO_PUPD_NONE,
7 GPIO_ADC_IN0_PIN);
8
9 /* Setup ADC. */
10 adc_power_off(ADC1);
11
12 adc_disable_scan_mode(ADC1);
13 adc_disable_external_trigger_regular(ADC1);
14 adc_set_single_conversion_mode(ADC1);
15 adc_set_right_aligned(ADC1);
16 adc_set_sample_time_on_all_channels(ADC1, ADC_SIMPLE_TIME);
17
18 /* Setup interrupt. */
19 adc_enable_eoc_interrupt(ADC1);
20 nvic_enable_irq(NVIC_ADC_IRQ);
21
22 uint8_t channels[16];
23 channels[0] = 0;
24 adc_set_regular_sequence(ADC1, 1, channels);
25
26 adc_power_on(ADC1);
27 delay(800000); /* Wait a bit. */
28}
要使用 ADC 功能,首先要知道 ADC 的通道在哪些 GPIO 上,並將其設定爲類比輸入。
接下來就是要設定 ADC。
adc_disable_scan_mode()
禁能多通道掃描模式,因爲本範例只需要讀取一個通道而已。adc_disable_external_trigger_regular()
禁能外部觸發,我們將使用軟體觸發。adc_set_single_conversion_mode()
設定成單一轉換模式,不連續轉換。adc_set_right_aligned()
讓資料的對齊方式爲靠右對齊。adc_set_sample_time_on_all_channels()
設定所有通道的取樣時間,這裡使用 56 個 Cycle。adc_enable_eoc_interrupt()
啓用 ADC 的轉換完成(EOC)中斷。nvic_enable_irq()
啓用 NVIC 的 ADC IRQ。adc_set_regular_sequence()
設定 Regular 的通道序列。這裡只有 Ch0。
ADC ISQ
1/**
2 * @brief ADC Interrupt service routine.
3 */
4void adc_isr(void)
5{
6 /* Clear regular end of conversion flag. */
7 ADC_SR(ADC1) &= ~ADC_SR_EOC;
8
9 uint16_t value = adc_read_regular(ADC1);
10 printf("%4d\r\n", value);
11 delay(5000000);
12
13 /* Sart a new conversion. */
14 adc_start_conversion_regular(ADC1);
15}
這是 ADC 的 ISQ。
首先先清除 ADC 的轉換完成位元(EOC)。
使用 adc_read_regular()
讀取 ADC 轉換完成的數值。
都完成後再使用 adc_start_conversion_regular()
開始另一次新的轉換。
主程式
1int main(void)
2{
3 rcc_setup();
4 adc_setup();
5 usart_setup();
6
7 /* Software start the first conversion. */
8 adc_start_conversion_regular(ADC1);
9
10 while (1)
11 { }
12 return 0;
13}
adc_start_conversion_regular()
會觸發 ADC 進行 Regular 組轉換,由於我們要使用中斷,所以不需要用 adc_eoc()
觀察 Injected 組是否轉換完成。一旦 ADC 轉換完成就會到 ADC 的 ISR。
多環境程式(F446RE + F103RB)
由於 STM32F1 的部分函式不同,所以 F103RB 沒辦法直接使用上面的 F446RE 的程式。
由於本例的差異比較大,爲了不佔版面這裡就不列出的,完整的程式請看 GitHub repo。
小結
但需要大量進行 ADC 轉換時,如何還是透過 Blocking 的方式讀取 EOC 或 JEOC 位元來等待 ADC 完成轉換的話會非常沒效率,因此本篇簡單介紹了 ADC 的中斷寫法,基本上和以往寫過的中斷一樣,應該不會太難。
參考資料
- libopencm3/libopencm3-examples
- platformio/platform-ststm32
- STM32F446RE datasheet (DS10693)
- STM32F446xx reference manual (RM0390)
- STM32F103RB datasheet (DS5319)
- STM32 Nucleo-64 board user manual (UM1724)
本文的程式也有放在 GitHub 上。
本文同步發表於 iT 邦幫忙-2022 iThome 鐵人賽。
留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)
comments powered by Disqus