STM32 LibOpenCM3:ADC Timer 觸發

系列:簡單入門 LibOpenCM3 STM32 嵌入式系統開發 Posted on 2022-10-06

前言

ADC(Analog to Digital Converter)顧名思義是將類比訊號轉換成數位訊號的元件,現今多數 MCU 都會內建 ADC,而這也是相當基本且常用的功能。

之前的文章已經介紹過 3 中不同的 ADC 使用環境,這次要再介紹以 Timer 定期觸發 ADC 進行轉換的寫法,且一樣會啓用 ADC 的轉換完成(EOC)中斷。

正文

首先一樣以 Nucleo-F446RE 做示範。

首先建立一個 PIO 的專案,選擇 Framework 爲「libopencm3」,並在 src/ 資料夾中新增並開啓 main.cmain.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.hgpio.h 及必要的 adc.h 外,因爲我要使用 USART 和 printf(),所以還會需要 usart.hstdio.herrno.h

另外就是因爲要使用中斷及 Timer,所以 nvic.htimer.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 本身的轉換完成中斷,是一種比較有效率的寫法。

參考資料

本文的程式也有放在 GitHub 上。
本文同步發表於 iT 邦幫忙-2022 iThome 鐵人賽



留言可能不會立即顯示。若過了幾天仍未出現,請 Email 聯繫:)

comments powered by Disqus