前言
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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
#include "main.h"
int main(void) { rcc_setup(); usart_setup(); adc_setup(); timer_setup();
while (1) { } return 0; }
static void rcc_setup(void) { rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
rcc_periph_clock_enable(RCC_USART_TX_GPIO); rcc_periph_clock_enable(RCC_USART2); rcc_periph_clock_enable(RCC_ADC_GPIO); rcc_periph_clock_enable(RCC_ADC1); rcc_periph_clock_enable(RCC_TIM3); rcc_periph_reset_pulse(RST_TIM3); }
static void adc_setup(void) {
gpio_mode_setup(GPIO_ADC_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_ADC_IN0_PIN);
uint32_t adc = ADC1;
adc_power_off(adc);
adc_disable_scan_mode(adc); adc_set_single_conversion_mode(adc); adc_set_right_aligned(adc); adc_set_sample_time_on_all_channels(adc, ADC_SIMPLE_TIME);
adc_enable_external_trigger_regular(adc, ADC_CR2_EXTSEL_TIM3_TRGO, ADC_CR2_EXTEN_RISING_EDGE);
adc_enable_eoc_interrupt(adc); nvic_enable_irq(ADC_IRQ);
uint8_t channels[16]; channels[0] = 0; adc_set_regular_sequence(adc, 1, channels);
adc_power_on(adc); delay(800000); }
static void timer_setup(void) { uint32_t timer = TIM3;
timer_set_mode(timer, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); timer_set_prescaler(timer, TIMER_PRESCALER); timer_set_period(timer, TIMER_PERIOD);
timer_set_master_mode(timer, TIM_CR2_MMS_UPDATE);
timer_enable_counter(timer); }
static void usart_setup(void) { gpio_mode_setup(GPIO_USART_TX_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_USART_TX_PIN); gpio_set_af(GPIO_USART_TX_PORT, GPIO_USART_AF, GPIO_USART_TX_PIN);
usart_set_baudrate(USART2, USART_BAUDRATE); usart_set_databits(USART2, 8); usart_set_stopbits(USART2, USART_STOPBITS_1); usart_set_parity(USART2, USART_PARITY_NONE); usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE); usart_set_mode(USART2, USART_MODE_TX);
usart_enable(USART2); }
static void delay(uint32_t value) { for (uint32_t i = 0; i < value; i++) { __asm__("nop"); } }
int _write(int file, char *ptr, int len) { int i;
if (file == 1) { for (i = 0; i < len; i++) { usart_send_blocking(USART2, ptr[i]); } return i; }
errno = EIO; return -1; }
void adc_isr(void) { ADC_SR(ADC1) &= ~ADC_SR_EOC;
uint16_t value = adc_read_regular(ADC1); printf("%4d\r\n", value); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
#ifndef MAIN_H #define MAIN_H
#include <stdio.h> #include <errno.h> #include <libopencm3/stm32/rcc.h> #include <libopencm3/stm32/gpio.h> #include <libopencm3/stm32/adc.h> #include <libopencm3/stm32/usart.h> #include <libopencm3/stm32/timer.h> #include <libopencm3/cm3/nvic.h>
#define USART_BAUDRATE (9600)
#define GOAL_FREQUENCY (1) #define TIMER_CLOCK (rcc_apb1_frequency * 2) #define COUNTER_CLOCK (10000)
#define TIMER_PRESCALER (TIMER_CLOCK / COUNTER_CLOCK - 1)
#define TIMER_PERIOD (((TIMER_CLOCK) / ((TIMER_PRESCALER + 1) * GOAL_FREQUENCY)) - 1)
#define ADC_SIMPLE_TIME (ADC_SMPR_SMP_56CYC) #define RCC_ADC_GPIO (RCC_GPIOA) #define GPIO_ADC_PORT (GPIOA) #define GPIO_ADC_IN0_PIN (GPIO0) #define ADC_IRQ (NVIC_ADC_IRQ)
#define RCC_USART_TX_GPIO (RCC_GPIOA) #define GPIO_USART_TX_PORT (GPIOA) #define GPIO_USART_TX_PIN (GPIO2) #define GPIO_USART_AF (GPIO_AF7)
static void rcc_setup(void); static void usart_setup(void); static void adc_setup(void); static void timer_setup(void); static void delay(uint32_t value);
#endif
|
分段說明
Include
1 2 3 4 5 6 7 8 9
| #include <stdio.h> #include <errno.h> #include <libopencm3/stm32/rcc.h> #include <libopencm3/stm32/gpio.h> #include <libopencm3/stm32/adc.h> #include <libopencm3/stm32/usart.h> #include <libopencm3/stm32/timer.h> #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| static void adc_setup(void) {
gpio_mode_setup(GPIO_ADC_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO_ADC_IN0_PIN);
uint32_t adc = ADC1;
adc_power_off(adc);
adc_disable_scan_mode(adc); adc_set_single_conversion_mode(adc); adc_set_right_aligned(adc); adc_set_sample_time_on_all_channels(adc, ADC_SIMPLE_TIME);
adc_enable_external_trigger_regular(adc, ADC_CR2_EXTSEL_TIM3_TRGO, ADC_CR2_EXTEN_RISING_EDGE);
adc_enable_eoc_interrupt(adc); nvic_enable_irq(ADC_IRQ);
uint8_t channels[16]; channels[0] = 0; adc_set_regular_sequence(adc, 1, channels);
adc_power_on(adc); delay(800000); }
|
要使用 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 3 4 5 6 7 8 9 10 11
|
void adc_isr(void) { ADC_SR(ADC1) &= ~ADC_SR_EOC;
uint16_t value = adc_read_regular(ADC1); printf("%4d\r\n", value); }
|
這是 ADC 的 ISQ。
首先先清除 ADC 的轉換完成位元(EOC),再使用 adc_read_regular()
讀取 ADC 轉換完成的數值。
Timer 設定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| static void timer_setup(void) { uint32_t timer = TIM3;
timer_set_mode(timer, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP); timer_set_prescaler(timer, TIMER_PRESCALER); timer_set_period(timer, TIMER_PERIOD);
timer_set_master_mode(timer, TIM_CR2_MMS_UPDATE);
timer_enable_counter(timer); }
|
先設定好 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 本身的轉換完成中斷,是一種比較有效率的寫法。
參考資料
本文的程式也有放在 GitHub 上。
本文同步發表於 iT 邦幫忙-2022 iThome 鐵人賽。