Giới thiệu chung
Các tín hiệu chúng ta thường gặp trong tự nhiên chẳng hạn như điện áp, ánh sáng, âm thanh, nhiệt độ… đều tồn tại dưới dạng tương tự, có nghĩa là tín hiệu liên tục và mức độ chia nhỏ vô hạn. Ví dụ như trong khoảng điện áp từ 0 -> 5V có vô số khoảng giá trị điện áp, ánh sáng sẽ tồn tại từ mờ cho tới sáng tỏ, âm thanh từ nhỏ cho đến lớn dưới dạng liên tục. Ngược lại trong vi điều khiển chỉ có khái niệm số (digital), cấu trúc từ nhân cho đến bộ nhớ hoạt động dựa trên các transistor chỉ gồm mức 0-1 nên nếu muốn giao tiếp với chip thì tín hiệu phải được số hóa trước khi đưa vào chip. Quá trình số hóa có thể thực hiện bằng nhiều cách và nhiều công đoạn nhưng mục đích cuối cùng là để vi điều khiển hiểu được tín hiệu tương tự đó.
ADC (analog-to-digital converter) bộ chuyển đổi tín hiệu tương tự – số là thuật ngữ nói đến sự chuyển đổi một tín hiệu tương tự thành tín hiệu số để dùng trong các hệ thống số (digital systems) hay vi điều khiển. Trong STM32F407VG có 3 bộ ADC chuyển đổi tín hiệu điện áp thành tín hiệu số với độ phân giải 12-bit. Mỗi ADC có khả năng tiếp nhận tín hiệu từ 16 kênh ngoài.

Để hiểu quá trình số hóa trong STM32 diễn ra như thế nào ta xem ví dụ sau. Giả sử ta cần đo điện áp tối thiểu là 0V và tối đa là 3.3V, trong STM32 sẽ chia 0->3.3V thành 4096 khoảng giá trị (từ 0 -> 4095, do 212=4096), giá trị đo được từ chân IO tương ứng với 0V sẽ là 0, tương ứng với 1.65V là 2047 và tương ứng 3.3V sẽ là 4095. Đây chỉ là một ví dụ cơ bản, trên thực tế ADC là một ngoại vi thiết yếu của dòng STM32 nên gồm rất nhiều chức năng, người học cần có kiến thức nền tảng để có thể đọc hiểu các chức năng này.
Ví dụ
Ví dụ 1
Ví dụ dưới đây cho phép đo hiệu điện thế ở chân PA1 thông qua kênh 1 của ADC1.
#include <stm32f4xx_rcc.h>
#include <stm32f4xx_gpio.h>
#include <stm32f4xx_adc.h>
void leds_init() {
GPIO_InitTypeDef gpio;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_StructInit(&gpio);
gpio.GPIO_Mode = GPIO_Mode_AN;
gpio.GPIO_Pin = GPIO_Pin_1;
GPIO_Init(GPIOA, &gpio);
}
void adc_init() {
ADC_InitTypeDef ADC_InitStructure;
ADC_CommonInitTypeDef adc_init;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_DeInit();
ADC_StructInit(&ADC_InitStructure);
adc_init.ADC_Mode = ADC_Mode_Independent;
adc_init.ADC_Prescaler = ADC_Prescaler_Div2;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConvEdge_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_CommonInit(&adc_init);
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
}
u16 readADC1(u8 channel)
{
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_3Cycles);
ADC_SoftwareStartConv(ADC1);
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}
void Delay(unsigned int Val)
{
for (; Val != 0; Val--);
}
int main(void)
{
leds_init();
adc_init();
do
{
unsigned int bin_code = readADC1(ADC_Channel_1);
double voltage = bin_code * 2.96 / 0xfff;
Delay(500000);
} while (1);
}
Để xem giá trị hiệu điện thế ở chân PA1 ta chạy chương trình ở chế độ debug, mở cửa sổ View\Variables, đặt breakpoint ở dòng Delay(500000); của hàm main() rồi chạy, khi đó ta có thể xem giá trị của biến voltage ở cửa sổ Variables.
Khi chân PA1 không nối vào đâu, giá trị của biến voltage nhận được gần với 0V. Nếu chúng ta nối chân PA1 với chân 3V, giá trị của biến voltage nhận được sẽ là 2,96V.
Cảm biến nhiệt độ
Trong thành phần của vi điều khiển STM32D407VG có một cảm biến nhiệt độ nội, nối vào kênh ADC_IN16. Cảm biến nhiệt độ là thiết bị dùng để đo nhiệt độ môi trường, nó hoạt động dựa trên sự thay đổi điện áp của cảm biến khi nhiệt độ tăng hoặc giảm. Các cảm biến nhiệt độ có để tạo ra một điện áp thay đổi tuyến tính với nhiệt độ. Phạm vi thay đổi hiệu điện thế là từ 1,8V đến 3,6V.
Trên thực tế cảm biến nhiệt độ nội được dùng để theo dõi sự thay đổi nhiệt độ chứ không dùng để đo lường. Nếu muốn đo nhiệt độ với độ chính xác cao cần dùng cảm biến ngoài.
Cảm biến nhiệt độ nội của STM32F4 Discovery
Trên các thiết bị STM23F40x và STM32F41x, cảm biến nhiệt độ nội kết nối với kênh ADC1_IN16, được sử dụng để chuyển đổi điện áp đầu ra cảm biến đến một giá trị số. Trên các thiết bị STM23F42x và STM32F43x, cảm biến nhiệt độ trong nội bộ kết nối với các kênh đầu vào tương tự, ADC1_IN18, như VBAT: ADC1_IN18 được sử dụng để chuyển đổi điện áp đầu ra cảm biến hoặc VBAT vào một giá trị số. Chỉ có một chuyển đổi, cảm biến nhiệt độ hoặc VBAT, phải được lựa chọn tại một thời điểm. Khi cảm biến nhiệt độ và việc chuyển đổi VBAT được thiết lập cùng một lúc, chỉ việc chuyển đổi VBAT là thực hiện.


Ví dụ 2
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_tim.h"
#include "stm32f4xx_adc.h"
void Delay(__IO uint32_t nCount)
{
while(nCount--)
{
}
}
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_InitStruct;
ADC_InitTypeDef ADC_InitStruct;
ADC_CommonInitTypeDef ADC_CommonInitStruct;
int main(void)
{
unsigned int _btn_count = 0;
float TemperatureValue = 0;
/*Call SystemInit to correct the clock configuration*/
/* Since we are using the STM32F4 Discovery board that has a 8MHz crystal
* we need to adjust the following:
*
* 1) In stm32f4xx.h file, line 92
* Set the #define HSE_VALUE to the following
* #define HSE_VALUE ((uint32_t)8000000)
* 2) In system_stm32f4xx.h file, line 149
* Set the #define PLL_M to the following
* #define PLL_M 8
*/
SystemInit();
/**********************************************************************************
*
* This enables the peripheral clock to the GPIOD module. This is stated in
* the beginning of the stm32f4xx.gpio.c source file.
*
**********************************************************************************/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
/**********************************************************************************
*
* This block of code defines the properties of the GPIO port.
* The different options of each item can be found in the stm32f4xx_gpio.h header file
* Every GPIO configuration should have the following initialized
*
* GPIO_InitStruct.GPIO_Pin
* GPIO_InitStruct.GPIO_Mode
* GPIO_InitStruct.GPIO_Speed
* GPIO_InitStruct.GPIO_OType
* GPIO_InitStruct.GPIO_PuPd
* GPIO_Init(GPIOx, &GPIO_InitStruct); (x represents port - A, B, C, D, E, F, G, H, I)
*
**********************************************************************************/
//GPIO_InitStruct.GPIO_Pin configures the pins that will be used.
//In this case we will use the LED's off of the discovery board which are on
//PortD pins 12, 13, 14 and 15
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15 | GPIO_Pin_14 | GPIO_Pin_13 | GPIO_Pin_12;
//PIO_InitStruct.GPIO_Mode configures the pin mode the options are as follows
// GPIO_Mode_IN (Input Mode)
// GPIO_Mode_OUT (Output Mode)
// GPIO_Mode_AF (Alternate Function)
// GPIO_Mode_AN (Analog Mode)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
//GPIO_InitStruct.GPIO_Speed configures the clock speed, options are as follows
// GPIO_Speed_2MHz
// GPIO_Speed_25MHz
// GPIO_Speed_50MHz
// GPIO_Speed_100MHz
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStruct.GPIO_OType configures the pin type, options are as follows
// GPIO_OType_PP (Push/Pull)
// GPIO_OType_OD (Open Drain)
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
//Configures pullup / pulldown resistors on pin, options are as follows
// GPIO_PuPd_NOPULL (Disables internal pullup and pulldown resistors)
// GPIO_PuPd_UP (Enables internal pullup resistors)
// GPIO_PuPd_DOWN (Enables internal pulldown resistors)
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
//This finally passes all the values to the GPIO_Init function
//which takes care of setting the corresponding bits.
GPIO_Init(GPIOD, &GPIO_InitStruct);
/**********************************************************************************
*
* This enables the peripheral clock to the GPIOA module. This is stated in
* the beginning of the stm32f4xx.gpio.c source file.
*
**********************************************************************************/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
/**********************************************************************************
*
* This block of code defines the properties of the GPIOA port.
* We are defining Pin 0 as a digital input with a pulldown resistor
* to detect a high level. Pin 0 is connected to the 3.3V source
*
**********************************************************************************/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_Init(GPIOA, &GPIO_InitStruct);
/**********************************************************************************
*
* This enables the peripheral clock to the TIM2 (Timer 2) module.
* The TIM2 module clock is at 84MHz. The prescaler will be set
* to 42000-1 which will configure the clock for 84MHz/42kHz = 2kHz
* Then we will use the period to define as 2000-1. This will trigger
* an event every 1 second (or it should).
*
* Since we are using the STM32F4 Discovery board that has a 8MHz crystal
* we need to adjust the following:
*
* 1) Use the clock configuration tool to setup the system_stm32f4xx.c file
*
* 2) In stm32f4xx.h file
* a) Set the #define HSE_VALUE to the following
* #define HSE_VALUE ((uint32_t)8000000)
*
* 3) In startup_stm32f4xx.c file
* a) Uncomment the line
* extern void SystemInit(void);
*
* b) Add the following line under Default_Reset_Handler()
* just before the line of code that jumps to main();
* SystemInit();
*
* **********************************************************************************/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InitStruct.TIM_Prescaler = 42000 - 1; // This will configure the clock to 2 kHz
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up; // Count-up timer mode
TIM_InitStruct.TIM_Period = 2000 - 1; // 2 kHz down to 1 Hz = 1 second
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // Divide clock by 1
TIM_InitStruct.TIM_RepetitionCounter = 0; // Set to 0, not used
TIM_TimeBaseInit(TIM2, &TIM_InitStruct);
/* TIM2 enable counter */
TIM_Cmd(TIM2, ENABLE);
/**********************************************************************************
*
* This enables the A/D converter for sampling the on board
* temperature sensor.
*
* You must first enable the CommonInitStructure
* then enable the specific InitStructure for AD1, AD2 or AD3
*
* Review reference manual RM0090 for register details.
*
* 1) Deinitialize the ADC
* 2) Set the ADC1 peripheral clock
* 2) Set the Common Structure of ADC
* a) Mode = Configures the ADC to operate in independent or multi mode.
* b) Prescaler = Select the frequency of the clock to the ADC.
* The clock is common for all the ADCs.
* ADCCLK = PCLK2/Prescaler
* c) DMA = Configures the Direct memory access mode for multi ADC mode.
* d) Two Sampling Delay = Configures the Delay between 2 sampling phases.
* # * Time(ADCCLK) where # is between 5 and 20
* 3) Configure The ADC Initialization Settings
* 4) Configure the channels for for ADC1, internal temperature sensor is on channel 16
* 5) Enable the internal temperature sensor
* 6) Enable the A/D conversion for ADC1
*
* **********************************************************************************/
ADC_DeInit();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent;
ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div8;
ADC_CommonInitStruct.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStruct);
ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStruct.ADC_ScanConvMode = DISABLE;
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_NbrOfConversion = 1;
ADC_Init(ADC1, &ADC_InitStruct);
// ADC1 Configuration, ADC_Channel_TempSensor is actual channel 16
ADC_RegularChannelConfig(ADC1, ADC_Channel_TempSensor, 1,
ADC_SampleTime_144Cycles);
// Enable internal temperature sensor
ADC_TempSensorVrefintCmd(ENABLE);
// Enable ADC conversion
ADC_Cmd(ADC1, ENABLE);
/**********************************************************************************
*
* This block of code blinks all four LED's on initial startup
*
* **********************************************************************************/
GPIO_SetBits(GPIOD, GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
Delay(0xFFFFF);
GPIO_ResetBits(GPIOD, GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
while(1)
{
//Reads the status of the push-button on the Discovery board
//If button is pressed blue LED is toggled
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1)
{
_btn_count++;
if (_btn_count > 0xFFFF)
{
GPIO_ToggleBits(GPIOD, GPIO_Pin_15);
_btn_count = 0;
}
}
//Monitors the TIM2 flag status. If the TIM2 period is reached
//The TIM_FLAG_Update = 1. If this is true, we clear the flag
//and toggle the green LED.
if (TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) != RESET)
{
TIM_ClearFlag(TIM2, TIM_IT_Update);
GPIO_ToggleBits(GPIOD, GPIO_Pin_12);
}
// ADC Conversion to read temperature sensor
// Temperature (in °C) = ((Vsense – V25) / Avg_Slope) + 25
// Vense = Voltage Reading From Temperature Sensor
// V25 = Voltage at 25°C, for STM32F407 = 0.76V
// Avg_Slope = 2.5mV/°C
// This data can be found in the STM32F407VF Data Sheet
ADC_SoftwareStartConv(ADC1); //Start the conversion
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)
; //Processing the conversion
TemperatureValue = ADC_GetConversionValue(ADC1); //Return the converted data
TemperatureValue *= 3300;
TemperatureValue /= 0xfff; //Reading in mV
TemperatureValue /= 1000.0; //Reading in Volts
TemperatureValue -= 0.760; // Subtract the reference voltage at 25°C
TemperatureValue /= .0025; // Divide by slope 2.5mV
TemperatureValue += 25.0; // Add the 25°C
} //End of while(1) loop
} //End of main loop
Để đọc nhiệt độ cần chạy ở chế độ Debug, mở cửa sổ View\Variable, thêm biến TemperatureValue để xem giá trị (xem hình dưới).

Ngoài ra để hỗ trợ quá trình chuyển đổi của ADC một cách liên tục chúng tá cần tìm hiểu về khái niệm DMA.
DMA (Direct memory access) là một kỹ thuật chuyển dữ liệu từ bộ nhớ đến ngoại vi hoặc từ ngoại vi đến bộ nhớ mà không yêu cầu đến sự thực thi của CPU, có nghĩa là CPU sẽ hoàn toàn độc lập với ngoại vi được hỗ trợ DMA mà vẫn đảm bảo ngoại vi thực hiện công việc được giao, tùy vào từng loại ngoại vi mà DMA có sự hỗ trợ khác nhau.