Урок 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В
  • Простое и точное измерение температуры
🏠 На главную Следующий урок