前言
ADC(Analog to Digital Converter)顧名思義是將類比訊號轉換成數位訊號的元件,現今多數 MCU 都會內建 ADC,而這也是相當基本且常用的功能。
上一篇已經介紹過最基本的 ADC 單一 Regular 通道用法,這篇文章要繼續示範如何使用 ADC Injected 多通道讀取,並使用 UART 傳到電腦上觀看。
正文
首先一樣以 Nucleo-F446RE 做示範。
首先建立一個 PIO 的專案,選擇 Framework 爲「libopencm3」,並在 src/
資料夾中新增並開啓 main.c
與 main.h
。
完整程式
1/**
2 * @file main.c
3 * @brief Multi injected channel ADC 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 while (1)
15 {
16 /* Software start ADC injected conversion. */
17 adc_start_conversion_injected(ADC1);
18
19 /* Wait for ADC injected end of conversion. */
20 while (!adc_eoc_injected(ADC1))
21 { }
22
23 /* Clear ADC injected end of conversion flag. */
24 ADC_SR(ADC1) &= ~ADC_SR_JEOC;
25
26 /* Read ADC injected. */
27 uint16_t value1 = adc_read_injected(ADC1, 1);
28 uint16_t value2 = adc_read_injected(ADC1, 2);
29 uint16_t value3 = adc_read_injected(ADC1, 3);
30
31 printf("%4d, %4d, %4d\r\n", value1, value2, value3);
32 delay(5000000);
33 }
34
35 return 0;
36}
37
38static void rcc_setup(void)
39{
40 rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
41
42 rcc_periph_clock_enable(RCC_USART_TX_GPIO);
43 rcc_periph_clock_enable(RCC_USART2);
44 rcc_periph_clock_enable(RCC_ADC_GPIO);
45 rcc_periph_clock_enable(RCC_ADC1);
46}
47
48static void adc_setup(void)
49{
50/* Set to input analog. */
51 gpio_mode_setup(GPIO_ADC_PORT,
52 GPIO_MODE_ANALOG,
53 GPIO_PUPD_NONE,
54 GPIO_ADC_IN0_PIN | GPIO_ADC_IN1_PIN | GPIO_ADC_IN4_PIN);
55
56 /* Setup ADC. */
57 adc_power_off(ADC1);
58
59 adc_enable_scan_mode(ADC1);
60 adc_set_single_conversion_mode(ADC1);
61 adc_disable_discontinuous_mode_regular(ADC1);
62 adc_disable_discontinuous_mode_injected(ADC1);
63
64 /* We want to start the injected conversion in sofrware. */
65 adc_enable_external_trigger_injected(ADC1,
66 ADC_CR2_JSWSTART,
67 ADC_CR2_JEXTEN_DISABLED);
68
69 adc_set_right_aligned(ADC1);
70 adc_set_sample_time_on_all_channels(ADC1, ADC_SIMPLE_TIME);
71
72 uint8_t channels[4];
73 channels[0] = 0;
74 channels[1] = 1;
75 channels[2] = 4;
76 adc_set_injected_sequence(ADC1, 3, channels);
77
78 adc_power_on(ADC1);
79 delay(800000); /* Wait a bit. */
80}
81
82static void usart_setup(void)
83{
84 /* Set USART-Tx pin to alternate function. */
85 gpio_mode_setup(GPIO_USART_TX_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_USART_TX_PIN);
86 gpio_set_af(GPIO_USART_TX_PORT, GPIO_USART_AF, GPIO_USART_TX_PIN);
87
88 /* Config USART params. */
89 usart_set_baudrate(USART2, USART_BAUDRATE);
90 usart_set_databits(USART2, 8);
91 usart_set_stopbits(USART2, USART_STOPBITS_1);
92 usart_set_parity(USART2, USART_PARITY_NONE);
93 usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
94 usart_set_mode(USART2, USART_MODE_TX);
95
96 usart_enable(USART2);
97}
98
99static void delay(uint32_t value)
100{
101 for (uint32_t i = 0; i < value; i++)
102 {
103 __asm__("nop"); /* Do nothing. */
104 }
105}
106
107/* For printf(). */
108int _write(int file, char *ptr, int len)
109{
110 int i;
111
112 if (file == 1)
113 {
114 for (i = 0; i < len; i++)
115 {
116 usart_send_blocking(USART2, ptr[i]);
117 }
118 return i;
119 }
120
121 errno = EIO;
122 return -1;
123}
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
15#define USART_BAUDRATE (9600)
16
17#define ADC_SIMPLE_TIME (ADC_SMPR_SMP_56CYC)
18#define RCC_ADC_GPIO (RCC_GPIOA)
19#define GPIO_ADC_PORT (GPIOA)
20#define GPIO_ADC_IN0_PIN (GPIO0) /* Arduino-A0. */
21#define GPIO_ADC_IN1_PIN (GPIO1) /* Arduino-A1. */
22#define GPIO_ADC_IN4_PIN (GPIO4) /* Arduino-A2. */
23
24#define RCC_USART_TX_GPIO (RCC_GPIOA)
25#define GPIO_USART_TX_PORT (GPIOA)
26#define GPIO_USART_TX_PIN (GPIO2) /* ST-Link (Arduino-D1). */
27#define GPIO_USART_AF (GPIO_AF7) /* Ref: Table-11 in DS10693. */
28
29static void rcc_setup(void);
30static void usart_setup(void);
31static void adc_setup(void);
32static void delay(uint32_t value);
33
34#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>
除了基本的 rcc.h
和 gpio.h
及必要的 adc.h
外,因爲我要使用 USART 和 printf()
,所以還會需要 usart.h
、stdio.h
與 errno.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 | GPIO_ADC_IN1_PIN | GPIO_ADC_IN4_PIN);
8
9 /* Setup ADC. */
10 adc_power_off(ADC1);
11
12 adc_enable_scan_mode(ADC1);
13 adc_set_single_conversion_mode(ADC1);
14 adc_disable_discontinuous_mode_regular(ADC1);
15 adc_disable_discontinuous_mode_injected(ADC1);
16
17 /* We want to start the injected conversion in sofrware. */
18 adc_enable_external_trigger_injected(ADC1,
19 ADC_CR2_JSWSTART,
20 ADC_CR2_JEXTEN_DISABLED);
21
22 adc_set_right_aligned(ADC1);
23 adc_set_sample_time_on_all_channels(ADC1, ADC_SIMPLE_TIME);
24
25 uint8_t channels[4];
26 channels[0] = 0;
27 channels[1] = 1;
28 channels[2] = 4;
29 adc_set_injected_sequence(ADC1, 3, channels);
30
31 adc_power_on(ADC1);
32 delay(800000); /* Wait a bit. */
33}
要使用 ADC 功能,首先要知道 ADC 的通道在哪些 GPIO 上,並將其設定爲類比輸入。
接下來就是要設定 ADC。
adc_enable_scan_mode()
由於本例要讀取 3 個通道,所以要致能掃描模式。adc_set_single_conversion_mode()
設定成單一轉換模式,不連續轉換。adc_disable_discontinuous_mode_regular()
與adc_disable_discontinuous_mode_injected()
禁能 Regular 與 Injected 的不連續模式。adc_enable_external_trigger_injected()
設定以使用 Injected 的軟體觸發。adc_set_right_aligned()
讓資料的對齊方式爲靠右對齊。adc_set_sample_time_on_all_channels()
設定所有通道的取樣時間,這裡使用 56 個 Cycle。adc_set_injected_sequence()
設定 Injected 通道組的序列。本例要讀取的是 Ch0、Ch1 與 Ch4 這 3 個通道。
設定 RCC
1static void rcc_setup(void)
2{
3 rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
4
5 rcc_periph_clock_enable(RCC_USART_TX_GPIO);
6 rcc_periph_clock_enable(RCC_USART2);
7 rcc_periph_clock_enable(RCC_ADC_GPIO);
8 rcc_periph_clock_enable(RCC_ADC1);
9}
除了 GPIO 外,還要記得致能各功能本身的時鐘。
主程式
1int main(void)
2{
3 rcc_setup();
4 adc_setup();
5 usart_setup();
6
7 while (1)
8 {
9 /* Software start ADC injected conversion. */
10 adc_start_conversion_injected(ADC1);
11
12 /* Wait for ADC injected end of conversion. */
13 while (!adc_eoc_injected(ADC1))
14 { }
15
16 /* Clear ADC injected end of conversion flag. */
17 ADC_SR(ADC1) &= ~ADC_SR_JEOC;
18
19 /* Read ADC injected. */
20 uint16_t value1 = adc_read_injected(ADC1, 1);
21 uint16_t value2 = adc_read_injected(ADC1, 2);
22 uint16_t value3 = adc_read_injected(ADC1, 3);
23
24 printf("%4d, %4d, %4d\r\n", value1, value2, value3);
25 delay(5000000);
26 }
27
28 return 0;
29}
adc_start_conversion_injected()
會觸發 ADC 進行 Injected 組轉換,並以 adc_eoc_injected()
觀察 Injected 組是否轉換完成。
確認 ADC 轉換完成後使用 adc_read_injected()
來讀取各個轉換完的資料。雖然 Injected 組最多只能設定 4 個,但是它的 4 個通道的資料暫存器是各自獨立的(ADC_JDRx),這裡的第二個參數就是選擇要讀取 1~4 哪一個 Injected 資料暫存器。要注意這裡的第二個引數是 1~4 而非 0~3。
多環境程式(F446RE + F103RB)
由於 STM32F1 的部分函式不同,所以 F103RB 沒辦法直接使用上面的 F446RE 的程式。
不過這次的程式我還沒完成 F103RB 的部分,目前不會動作,未來有時間會再看是哪邊有問題。我還是把完整的程式的連接放上來:GitHub repo。
小結
這次延續了上一篇的內容,介紹多通道的 Injected 用法。
一般來說,由於 Regular 組只有一個 16 位元的資料暫存器,若要使用掃描模式讀出序列中的多個通道的話,就必須要設定 DMA。但 Injected 組的 4 個資料暫存器是各自獨立的,因此如過要讀取的 ADC 通道在 4 個內的話,可以考慮使用 Injected 組,這樣就不用設定 DMA 了。
參考資料
- 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