Урок 5: ADC и DAC в CH32
АЦП (аналого-цифровой преобразователь) и ЦАП (цифро-аналоговый преобразователь) позволяют микроконтроллеру работать с аналоговыми сигналами: считывать напряжение с датчиков и генерировать аналоговые выходные сигналы.
ADC в CH32V307
CH32V307 имеет два 12-битных АЦП с множеством каналов.
- Разрешение — 12 бит (0-4095)
- Каналы — до 16 внешних + внутренние
- Скорость — до 1 MSPS (миллион выборок в секунду)
- Опорное напряжение — обычно 3.3В
- Режимы — одиночное преобразование, непрерывное, сканирование
Базовая настройка ADC
Пример простого однократного преобразования.
#include "ch32v30x.h"
void ADC1_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// Включение тактирования
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC clock = 72/6 = 12 МГц
// Настройка PA0 как аналоговый вход (ADC1_CH0)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Настройка ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// Настройка канала 0, время выборки
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1,
ADC_SampleTime_239Cycles5);
// Включение ADC
ADC_Cmd(ADC1, ENABLE);
// Калибровка
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
uint16_t ADC1_Read(void) {
// Запуск преобразования
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// Ожидание завершения
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
// Чтение результата
return ADC_GetConversionValue(ADC1);
}
GPIO_Mode_AIN— режим аналогового входаADC_SampleTime_239Cycles5— время выборки (влияет на точность)- Калибровка обязательна после включения ADC
- Результат: 0-4095 соответствует 0-3.3В
Преобразование в напряжение
Как преобразовать значение АЦП в реальное напряжение.
float ADC_To_Voltage(uint16_t adc_value) {
// Vref = 3.3В, разрешение = 12 бит (4096)
return (float)adc_value * 3.3f / 4096.0f;
}
int main(void) {
uint16_t adc_raw;
float voltage;
SystemInit();
ADC1_Init();
while(1) {
adc_raw = ADC1_Read();
voltage = ADC_To_Voltage(adc_raw);
printf("ADC: %d, Voltage: %.2f V\r\n", adc_raw, voltage);
Delay_Ms(500);
}
}
- Формула: V = (ADC_value / 4096) × Vref
- При Vref = 3.3В: каждая единица АЦП ≈ 0.8 мВ
Многоканальное сканирование
Опрос нескольких аналоговых каналов.
#include "ch32v30x.h"
#define ADC_CHANNELS 3
uint16_t adc_values[ADC_CHANNELS];
void ADC_MultiChannel_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// PA0, PA1, PA2 - ADC каналы 0, 1, 2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // Включаем сканирование
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = ADC_CHANNELS;
ADC_Init(ADC1, &ADC_InitStructure);
// Настройка последовательности каналов
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1,
ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2,
ADC_SampleTime_239Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3,
ADC_SampleTime_239Cycles5);
ADC_Cmd(ADC1, ENABLE);
// Калибровка
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
void ADC_ReadMultiChannel(void) {
for(uint8_t i = 0; i < ADC_CHANNELS; i++) {
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
adc_values[i] = ADC_GetConversionValue(ADC1);
}
}
ADC_ScanConvMode = ENABLE— режим сканирования каналовADC_RegularChannelConfig()— определяет порядок каналов- Можно использовать DMA для автоматического сохранения результатов
DAC в CH32V307
CH32V307 имеет 2-канальный 12-битный ЦАП.
- Разрешение — 12 бит (0-4095)
- Выходное напряжение — 0 до Vref (обычно 3.3В)
- Каналы — DAC_Channel_1 (PA4), DAC_Channel_2 (PA5)
Настройка и использование DAC
Генерация аналогового напряжения.
#include "ch32v30x.h"
void DAC_Init_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
// PA4 - DAC_OUT1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Настройка DAC
DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
DAC_Cmd(DAC_Channel_1, ENABLE);
}
void DAC_SetVoltage(float voltage) {
uint16_t dac_value;
// Ограничение напряжения 0-3.3В
if(voltage < 0.0f) voltage = 0.0f;
if(voltage > 3.3f) voltage = 3.3f;
// Преобразование в значение DAC
dac_value = (uint16_t)(voltage * 4095.0f / 3.3f);
DAC_SetChannel1Data(DAC_Align_12b_R, dac_value);
}
int main(void) {
float voltage = 0.0f;
SystemInit();
DAC_Init_Config();
while(1) {
DAC_SetVoltage(voltage);
voltage += 0.1f;
if(voltage > 3.3f) voltage = 0.0f;
Delay_Ms(100);
}
}
DAC_SetChannel1Data()— установка выходного значения- Формула: DAC_value = (V / Vref) × 4095
- Выходное напряжение меняется от 0 до 3.3В
Практический пример: Термометр
Чтение температуры с датчика и отображение.
#include "ch32v30x.h"
// Датчик LM35: 10 мВ/°C
float Read_Temperature(void) {
uint16_t adc_value;
float voltage, temperature;
adc_value = ADC1_Read();
voltage = ADC_To_Voltage(adc_value);
// LM35: 10 мВ на градус Цельсия
temperature = voltage / 0.01f;
return temperature;
}
int main(void) {
float temp;
SystemInit();
ADC1_Init();
USART_Init(); // Для вывода данных
while(1) {
temp = Read_Temperature();
printf("Температура: %.1f°C\r\n", temp);
Delay_Ms(1000);
}
}
- LM35 выдает 10 мВ на каждый градус Цельсия
- При 25°C напряжение = 250 мВ = 0.25В
- Простое и точное измерение температуры