前言
ADC(Analog to Digital Converter)顧名思義是將類比訊號轉換成數位訊號的元件,現今多數 MCU 都會內建 ADC,而這也是相當基本且常用的功能。
之前的文章已經介紹過 3 中不同的 ADC 使用環境,這次要再介紹以 Timer 定期觸發 ADC 進行轉換的寫法,且一樣會啓用 ADC 的轉換完成(EOC)中斷。
正文
首先一樣以 Nucleo-F446RE 做示範。
首先建立一個 PIO 的專案,選擇 Framework 爲「libopencm3」,並在 src/
資料夾中新增並開啓 main.c
與 main.h
。
完整程式
1/**
2 * @file main.c
3 * @brief ADC external trigger by timer example for
4 * STM32 Nucleo-F446RE.
5 */
6
7#include "main.h"
8
9int main(void)
10{
11 rcc_setup();
12 usart_setup();
13 adc_setup();
14 timer_setup();
15
16 while (1)
17 { }
18 return 0;
19}
20
21static void rcc_setup(void)
22{
23 rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
24
25 rcc_periph_clock_enable(RCC_USART_TX_GPIO);
26 rcc_periph_clock_enable(RCC_USART2);
27 rcc_periph_clock_enable(RCC_ADC_GPIO);
28 rcc_periph_clock_enable(RCC_ADC1);
29 rcc_periph_clock_enable(RCC_TIM3);
30 rcc_periph_reset_pulse(RST_TIM3);
31}
32
33static void adc_setup(void)
34{
35/* Set to input analog. */
36 gpio_mode_setup(GPIO_ADC_PORT,
37 GPIO_MODE_ANALOG,
38 GPIO_PUPD_NONE,
39 GPIO_ADC_IN0_PIN);
40
41 uint32_t adc = ADC1;
42
43 /* Setup ADC. */
44 adc_power_off(adc);
45
46 adc_disable_scan_mode(adc);
47 adc_set_single_conversion_mode(adc);
48 adc_set_right_aligned(adc);
49 adc_set_sample_time_on_all_channels(adc, ADC_SIMPLE_TIME);
50
51 adc_enable_external_trigger_regular(adc,
52 ADC_CR2_EXTSEL_TIM3_TRGO,
53 ADC_CR2_EXTEN_RISING_EDGE);
54
55 /* Setup interrupt. */
56 adc_enable_eoc_interrupt(adc);
57 nvic_enable_irq(ADC_IRQ);
58
59 uint8_t channels[16];
60 channels[0] = 0;
61 adc_set_regular_sequence(adc, 1, channels);
62
63 adc_power_on(adc);
64 delay(800000); /* Wait a bit. */
65}
66
67static void timer_setup(void)
68{
69 uint32_t timer = TIM3;
70
71 timer_set_mode(timer,
72 TIM_CR1_CKD_CK_INT,
73 TIM_CR1_CMS_EDGE,
74 TIM_CR1_DIR_UP);
75 timer_set_prescaler(timer, TIMER_PRESCALER); /* PSC. */
76 timer_set_period(timer, TIMER_PERIOD); /* ARR. */
77
78 /* The update event is selected as trigger output (TRGO). */
79 timer_set_master_mode(timer, TIM_CR2_MMS_UPDATE);
80
81 timer_enable_counter(timer);
82}
83
84static void usart_setup(void)
85{
86 /* Set USART-Tx pin to alternate function. */
87 gpio_mode_setup(GPIO_USART_TX_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_USART_TX_PIN);
88 gpio_set_af(GPIO_USART_TX_PORT, GPIO_USART_AF, GPIO_USART_TX_PIN);
89
90 /* Config USART params. */
91 usart_set_baudrate(USART2, USART_BAUDRATE);
92 usart_set_databits(USART2, 8);
93 usart_set_stopbits(USART2, USART_STOPBITS_1);
94 usart_set_parity(USART2, USART_PARITY_NONE);
95 usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
96 usart_set_mode(USART2, USART_MODE_TX);
97
98 usart_enable(USART2);
99}
100
101static void delay(uint32_t value)
102{
103 for (uint32_t i = 0; i < value; i++)
104 {
105 __asm__("nop"); /* Do nothing. */
106 }
107}
108
109/* For printf(). */
110int _write(int file, char *ptr, int len)
111{
112 int i;
113
114 if (file == 1)
115 {
116 for (i = 0; i < len; i++)
117 {
118 usart_send_blocking(USART2, ptr[i]);
119 }
120 return i;
121 }
122
123 errno = EIO;
124 return -1;
125}
126
127/**
128 * @brief ADC Interrupt service routine.
129 */
130void adc_isr(void)
131{
132 /* Clear regular end of conversion flag. */
133 ADC_SR(ADC1) &= ~ADC_SR_EOC;
134
135 uint16_t value = adc_read_regular(ADC1);
136 printf("%4d\r\n", value);
137}
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/stm32/timer.h>
15#include <libopencm3/cm3/nvic.h>
16
17#define USART_BAUDRATE (9600)
18
19#define GOAL_FREQUENCY (1) /* in Hz. */
20#define TIMER_CLOCK (rcc_apb1_frequency * 2)
21#define COUNTER_CLOCK (10000)
22
23/* PSC. */
24#define TIMER_PRESCALER (TIMER_CLOCK / COUNTER_CLOCK - 1)
25
26/* ARR. */
27#define TIMER_PERIOD (((TIMER_CLOCK) / ((TIMER_PRESCALER + 1) * GOAL_FREQUENCY)) - 1)
28
29#define ADC_SIMPLE_TIME (ADC_SMPR_SMP_56CYC)
30#define RCC_ADC_GPIO (RCC_GPIOA)
31#define GPIO_ADC_PORT (GPIOA)
32#define GPIO_ADC_IN0_PIN (GPIO0) /* Arduino-A0. */
33#define ADC_IRQ (NVIC_ADC_IRQ)
34
35#define RCC_USART_TX_GPIO (RCC_GPIOA)
36#define GPIO_USART_TX_PORT (GPIOA)
37#define GPIO_USART_TX_PIN (GPIO2) /* ST-Link (Arduino-D1). */
38#define GPIO_USART_AF (GPIO_AF7) /* Ref: Table-11 in DS10693. */
39
40static void rcc_setup(void);
41static void usart_setup(void);
42static void adc_setup(void);
43static void timer_setup(void);
44static void delay(uint32_t value);
45
46#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/stm32/timer.h>
9#include <libopencm3/cm3/nvic.h>
除了基本的 rcc.h
和 gpio.h
及必要的 adc.h
外,因爲我要使用 USART 和 printf()
,所以還會需要 usart.h
、stdio.h
與 errno.h
。
另外就是因爲要使用中斷及 Timer,所以 nvic.h
和 timer.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 uint32_t adc = ADC1;
10
11 /* Setup ADC. */
12 adc_power_off(adc);
13
14 adc_disable_scan_mode(adc);
15 adc_set_single_conversion_mode(adc);
16 adc_set_right_aligned(adc);
17 adc_set_sample_time_on_all_channels(adc, ADC_SIMPLE_TIME);
18
19 adc_enable_external_trigger_regular(adc,
20 ADC_CR2_EXTSEL_TIM3_TRGO,
21 ADC_CR2_EXTEN_RISING_EDGE);
22
23 /* Setup interrupt. */
24 adc_enable_eoc_interrupt(adc);
25 nvic_enable_irq(ADC_IRQ);
26
27 uint8_t channels[16];
28 channels[0] = 0;
29 adc_set_regular_sequence(adc, 1, channels);
30
31 adc_power_on(adc);
32 delay(800000); /* Wait a bit. */
33}
要使用 ADC 功能,首先要知道 ADC 的通道在哪些 GPIO 上,並將其設定爲類比輸入。
接下來就是要設定 ADC。
adc_disable_scan_mode()
禁能多通道掃描模式,因爲本範例只需要讀取一個通道而已。adc_set_single_conversion_mode()
設定成單一轉換模式,不連續轉換。adc_set_right_aligned()
讓資料的對齊方式爲靠右對齊。adc_set_sample_time_on_all_channels()
設定所有通道的取樣時間,這裡使用 56 個 Cycle。adc_enable_external_trigger_regular()
啓用 ADC 的外部觸發,並指定觸發源爲 Timer3 的 TRGO(Tregger output)。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}
這是 ADC 的 ISQ。
首先先清除 ADC 的轉換完成位元(EOC),再使用 adc_read_regular()
讀取 ADC 轉換完成的數值。
Timer 設定
1static void timer_setup(void)
2{
3 uint32_t timer = TIM3;
4
5 timer_set_mode(timer,
6 TIM_CR1_CKD_CK_INT,
7 TIM_CR1_CMS_EDGE,
8 TIM_CR1_DIR_UP);
9 timer_set_prescaler(timer, TIMER_PRESCALER); /* PSC. */
10 timer_set_period(timer, TIMER_PERIOD); /* ARR. */
11
12 /* The update event is selected as trigger output (TRGO). */
13 timer_set_master_mode(timer, TIM_CR2_MMS_UPDATE);
14
15 timer_enable_counter(timer);
16}
先設定好 Timer 的頻率(PSC 與 ARR)。
使用 timer_set_master_mode()
設定 Timer 在每次的 Update 事件都會產生 TRGO 訊號,以觸發 ADC。
Timer 的頻率設定請看之前的文章。
多環境程式(F446RE + F103RB)
由於 STM32F1 的部分函式不同,所以 F103RB 沒辦法直接使用上面的 F446RE 的程式。
由於本例的差異比較大,爲了不佔版面這裡就不列出的,完整的程式請看 GitHub repo。
小結
若需要定期進行 ADC 轉換的話,使用 Timer 進行觸發是一個不錯的做法。本次範例使用 Timer 3 的 TRGO 訊號定期觸發 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