Аналогово-цифровой преобразователь - это узел, который "оцифровывает" мгновенное значение напряжения. В STM32F1 содержится два 12-битных преобразователя, а это значит, напряжения на любом канале АЦП будут представлены значением от 0 до 4095. Какое напряжение на канале на самом деле, вы сможете понять, если наверняка знаете напряжение питания АЦП (VDDA). Предположим, что питание микроконтроллера 3.30V, это значит, что показание АЦП 4095 соответствует этому напряжению, 3.30V. Показание АЦП 2048 единиц, это будет 1/2 от VDDA, а конкретно 1.65V. Эти величины меняются линейно, в зависимости от напряжения на канале АЦП. При напряжении питания 3.3В "вес единицы" АЦП будет 0.806 мВ. При напряжении питания 2.8В "вес единицы" АЦП будет 0.684 мВ. Думаю, принцип вы уже поняли.
АЦП не может замерять напряжение на нескольких каналах одновременно. Между АЦП и каналами стоит мультиплексор, который может захватывать только один из десяти возможных каналов. Однако если измерять каналы быстро по очереди, то можно создать эффект параллельности измерений. Эффективно и достаточно точно значения измеряются при меньшей частоте работы АЦП и большем Sampleticks. Более-менее сносно можно делать около 18000 замеров (выборок) в секунду. Минимальное время выборки для 12-битного режима - 1 мкс (то есть до 1000000 выборок в секунду при тактовой частоте АЦП 14 МГц).
В этом уроке мы будем пользоваться АЦП STM32F103 в самом простом режиме — одиночной конверсии. Это когда за одно преобразование мы обрабатываем только один канал. В этом уроке я покажу неблокирующую работу с АЦП (без прерываний) на разных каналах.
В этой инициализации разрешаем тактирование АЦП, GPIOA, настраиваем пины АЦП как аналоговые, для лучшей точности увеличиваем количество сэмплов конверсии до 71.5. И в конце инициализации выполняем калибровку АЦП.
// Настройка АЦП
void ADC_Setup(void) {
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // Включить тактирование GPIOA
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Включить тактирование АЦП
GPIOA->CRL = 0x0000; // Все 7 каналов аналоговые
// Значение 0b110 -> 71.5 тактов на выборку. Сделаем на все каналы так.
ADC1->SMPR2 = (6 << 21) | (6 << 18) | (6 << 15) | (6 << 12) | (6 << 9) | (6 << 6) | (6 << 3) | (6 << 0);
ADC1->CR2 = ADC_CR2_ADON; // Первое включение АЦП (с переходом в standby)
wait_ticks32(100);
ADC1->CR2 |= ADC_CR2_RSTCAL;
while(ADC1->CR2 & ADC_CR2_RSTCAL) {} // Сбросить калибровку АЦП
ADC1->CR2 |= ADC_CR2_CAL;
while(ADC1->CR2 & ADC_CR2_CAL) {} // Выполнить калибровку АЦП
wait_ticks32(100);
ADC1->CR2 |= ADC_CR2_TSVREFE; // Разрешить температурный канал 16
wait_ticks32(1000);
}
// Примитивная задержка
void wait_ticks32(uint32_t ticks) {
while(ticks--) __NOP();
}
Разрешите измерение температурного канала (№16) если это необходимо. Если канал не нужен, то и последние 2 строчки кода вы можете не использовать.
Чтобы выбрать канал АЦП для следующего замера, нужно сделать
// Выбор канала АЦП
void ADC_SelectChannel(uint8_t channel) {
ADC1->SQR3 = channel;
}
В качестве примера работы с АЦП внутри main приведу очень простой: множественные неблокирующие измерения без прерывания с занесением значений в массив.
Объявим массив и указатель активного (текущего) канала
uint8_t adcCurrentChannel = 0;
uint16_t adcMeasures[8];
Перед бесконечным циклом нужно запустить АЦП (пнуть)
// Стартануть АЦП
ADC1->CR2 |= ADC_CR2_ADON;
while(1) { // Бесконечный главный цикл
А внутри бесконечного цикла
// Обработка АЦП
if (ADC1->SR & ADC_SR_EOC) { // Если завершен замер АЦП
adcMeasures[adcCurrentChannel] = ADC1->DR; // Забрать замер, сбросит флаг EOC
if (++adcCurrentChannel > 7) adcCurrentChannel = 0;
ADC_SelectChannel(adcCurrentChannel);
ADC1->CR2 |= ADC_CR2_ADON; // Начать новый замер
}
Такой алгоритм будет поочередно считывать показания с каждого канала АЦП и записывать его по соответствующему индексу массива.
Можно существенно повысить точность замеров АЦП если делать много выборок и усреднять их значения. Заодно я покажу, как получать температуру ядра микроконтроллера.
// Неблокирующая обработка АЦП, вызывать в бесконечном цикле
void ADC_Process(void) {
#define CONVERSIONS_COUNT 512 // Сколько замеров на канал будем делать
static uint16_t adcConversionsCounter = 0; // Счетчик выборок
static uint32_t adcAcc = 0; // Накопитель значений для усреднения
if (ADC1->SR & ADC_SR_EOC) { // Если завершен замер АЦП
adcAcc += ADC1->DR;
if (++adcConversionsCounter >= CONVERSIONS_COUNT) {
adcConversionsCounter = 0;
adcAcc = adcAcc / CONVERSIONS_COUNT;
switch (adcCurrentChannel) {
case 0: // Канал 0
adcMeasures[0] = adcAcc;
adcCurrentChannel = 1; // Следующий канал будет 1
break;
case 1: // Канал 1
adcMeasures[1] = adcAcc;
adcCurrentChannel = 4; // Следующий канал будет 4
break;
case 4: // Канал 4
adcMeasures[2] = adcAcc;
adcCurrentChannel = 16; // Следующий канал будет 16
break;
case 16: // Температура процессора
adcMeasures[3] = adcAcc;
coreTemp = GetCoreTemperature(adcAcc);
adcCurrentChannel = 0; // Начать сначала
break;
default:
adcCurrentChannel = 0;
}
adcAcc = 0;
}
ADC_SelectChannel(adcCurrentChannel);
ADC1->CR2 |= ADC_CR2_ADON; // Начать новый замер
}
}
// Вернуть температуру ядра в градусах Цельсия, при питании 3.30В
int16_t GetCoreTemperature(uint16_t adc) {
int32_t vSense = (vdda * (uint32_t)adc) / 4095U; // напряжение на канале измерения температуры
int16_t Temperature = (((1430U - vSense) * 1000U) / 4300U) + 25U;
return Temperature;
}
Остается только в бесконечный основной цикл поместить вызов ADC_Process() и готово
Больше инфы на канале, никаких денег там не просят.
Возникли вопросы по этому уроку? Вам сюда https://t.me/ipasoftel/38

