STM32 LibOpenCM3:ADC 單通道 Regular

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

前言

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

上一篇已經介紹過 STM32 的 ADC 基本功能,這篇文章要示範如何使用 STM32 上的 ADC Regular 通道,並使用 UART 傳到電腦上觀看。

正文

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

首先建立一個 PIO 的專案,選擇 Framework 爲「libopencm3」,並在 src/ 資料夾中新增並開啓 main.cmain.h

完整程式

  1/**
  2 * @file   main.c
  3 * @brief  Single regular 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    uint16_t adc_value = get_adc_value(0);
 17    printf("%4d\r\n", adc_value);
 18    delay(2000000);
 19  }
 20
 21  return 0;
 22}
 23
 24static uint16_t get_adc_value(int channel)
 25{
 26  /* Setup channel. */
 27  uint8_t adc_channel[16];
 28  adc_channel[0] = channel;
 29  adc_set_regular_sequence(ADC1, 1, adc_channel);
 30
 31  /* Software start conversion. */
 32  adc_start_conversion_regular(ADC1);
 33
 34  /* Wait for ADC end of conversion. */
 35  while (!adc_eoc(ADC1))
 36  { }
 37
 38  return adc_read_regular(ADC1); /* Read ADC value. */
 39}
 40
 41static void adc_setup(void)
 42{
 43/* Set to input analog. */
 44  gpio_mode_setup(GPIO_ADC_PORT,
 45                  GPIO_MODE_ANALOG,
 46                  GPIO_PUPD_NONE,
 47                  GPIO_ADC_A0_PIN);
 48
 49  /* Setup ADC. */
 50  adc_power_off(ADC1);
 51
 52  adc_disable_scan_mode(ADC1);
 53  adc_disable_external_trigger_regular(ADC1);
 54  adc_set_single_conversion_mode(ADC1);
 55  adc_set_right_aligned(ADC1);
 56  adc_set_sample_time_on_all_channels(ADC1, ADC_SIMPLE_TIME);
 57
 58  adc_power_on(ADC1);
 59  delay(800000); /* Wait a bit. */
 60}
 61
 62static void rcc_setup(void)
 63{
 64  rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_84MHZ]);
 65
 66  rcc_periph_clock_enable(RCC_USART_TX_GPIO);
 67  rcc_periph_clock_enable(RCC_USART2);
 68  rcc_periph_clock_enable(RCC_ADC_GPIO);
 69  rcc_periph_clock_enable(RCC_ADC1);
 70}
 71
 72static void usart_setup(void)
 73{
 74  /* Set USART-Tx pin to alternate function. */
 75  gpio_mode_setup(GPIO_USART_TX_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_USART_TX_PIN);
 76  gpio_set_af(GPIO_USART_TX_PORT, GPIO_USART_AF, GPIO_USART_TX_PIN);
 77
 78  /* Config USART params. */
 79  usart_set_baudrate(USART2, USART_BAUDRATE);
 80  usart_set_databits(USART2, 8);
 81  usart_set_stopbits(USART2, USART_STOPBITS_1);
 82  usart_set_parity(USART2, USART_PARITY_NONE);
 83  usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);
 84  usart_set_mode(USART2, USART_MODE_TX);
 85
 86  usart_enable(USART2);
 87}
 88
 89static void delay(uint32_t value)
 90{
 91  for (uint32_t i = 0; i < value; i++)
 92  {
 93    __asm__("nop"); /* Do nothing. */
 94  }
 95}
 96
 97/* For printf(). */
 98int _write(int file, char *ptr, int len)
 99{
100  int i;
101
102  if (file == 1)
103  {
104    for (i = 0; i < len; i++)
105    {
106      usart_send_blocking(USART2, ptr[i]);
107    }
108    return i;
109  }
110
111  errno = EIO;
112  return -1;
113}
 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_A0_PIN (GPIO0) /* A0. */
21
22#define RCC_USART_TX_GPIO (RCC_GPIOA)
23#define GPIO_USART_TX_PORT (GPIOA)
24#define GPIO_USART_TX_PIN (GPIO2) /* ST-Link (D1). */
25#define GPIO_USART_AF (GPIO_AF7)  /* Ref: Table-11 in DS10693. */
26
27static void rcc_setup(void);
28static void usart_setup(void);
29static void adc_setup(void);
30
31static uint16_t get_adc_value(int channel);
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.hgpio.h 及 必要的 adc.h 外,因爲我要使用 USART 和 printf(),所以還會需要 usart.hstdio.herrno.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_A0_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  adc_power_on(ADC1);
19  delay(800000); /* Wait a bit. */
20}

要使用 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 的值

 1static uint16_t get_adc_value(int channel)
 2{
 3  /* Setup channel. */
 4  uint8_t adc_channel[16];
 5  adc_channel[0] = channel;
 6  adc_set_regular_sequence(ADC1, 1, adc_channel);
 7
 8  /* Software start conversion. */
 9  adc_start_conversion_regular(ADC1);
10
11  /* Wait for ADC end of conversion. */
12  while (!adc_eoc(ADC1))
13  { }
14
15  return adc_read_regular(ADC1); /* Read ADC value. */
16}

每個 ADC 都有多個通道,各個通道都有對應的 GPIO,在讀取時要指定要從哪一個通道讀取類比訊號。

使用 adc_set_regular_sequence() 設定要讀取的 Regular 通道序列。這裡一次就只讀取一個通道。Regular 最多可以設定 16 個通道,但在本例中只需要 1 個。如果要讀取的通道是固定的話,這個序列可以只設定一次就好。

adc_start_conversion_regular() 軟體觸發轉發,並以 adc_eoc() 來觀察 ADC 是否結束轉換了(End of conversion)。

ADC 轉換完成後就可以使用 adc_read_regular() 取得讀取的 Regular 資料。

由於此 ADC 是 12-bit 解析度,因此讀值範圍是 0~4095(0x0000 ~ 0x0FFF)。

設定 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    uint16_t adc_value = get_adc_value(0);
10    printf("%4d\r\n", adc_value);
11    delay(2000000);
12  }
13
14  return 0;
15}

在迴圈中每次讀取 ADC 通道的值並 Print 出去。

多環境程式(F446RE + F103RB)

由於 STM32F1 的部分函式不同,所以 F103RB 沒辦法直接使用上面的 F446RE 的程式。

以下列出主要的差異部分。完整的程式請看 GitHub repo

要注意的是除了以往的 RCC 與 GPIO 的設定不同外,ADC 也有部分不同,要特別注意。

 1static uint16_t get_adc_value(int channel)
 2{
 3  /* Setup channel. */
 4  uint8_t adc_channel[16];
 5  adc_channel[0] = channel;
 6  adc_set_regular_sequence(ADC1, 1, adc_channel);
 7
 8  /* Software start conversion. */
 9#if defined(STM32F1)
10  adc_start_conversion_direct(ADC1);
11#else
12  adc_start_conversion_regular(ADC1);
13#endif
14
15  /* Wait for ADC end of conversion. */
16  while (!adc_eoc(ADC1))
17  { }
18
19  return adc_read_regular(ADC1); /* Read ADC value. */
20}
 1static void adc_setup(void)
 2{
 3  /* 省略部分程式. */
 4
 5  adc_power_on(ADC1);
 6  delay(800000); /* Wait a bit. */
 7
 8#if defined(STM32F1)
 9  /* Self-calibration. */
10  adc_reset_calibration(ADC1);
11  adc_calibrate(ADC1);
12#endif
13}

小結

這次介紹了最基本的 ADC 用法,也就是讀取單一 Regular 通道。

雖然 ADC 本身的設定與模式都比以往的其它功能複雜,但實際使用時我想這些程式並不會太難看懂。

參考資料

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



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

comments powered by Disqus