По работе ADC можно взять информацию с сайта http://mycontroller.ru/old_site/category/mikrokontrolleryi-stm32/adc/default.htm
По работе DMA http://mycontroller.ru/old_site/category/mikrokontrolleryi-stm32/dma/default.html
Программа будет использовать ADC и способ опроса каналов с использование регулярных групп. В статье http://stormm.ru/2019/01/22/5-%D0%B0%D0%BF%D0%BF%D0%B0%D1%80%D0%B0%D1%82%D0%BD%D1%8B%D0%B9-%D0%B0%D1%86%D0%BF-%D0%B8-%D0%B4%D0%B2%D0%B0-%D0%BA%D0%B0%D0%BD%D0%B0%D0%BB%D0%B0-0-%D0%B8-1-a0-%D0%B8-a1-porta-1-%D0%B8-porta/ использовался способ опроса с использованием инжектированных групп, недостаток такого способа в том что есть возможность подключать только 4 канала. При использовании опроса каналов регулярной группы сложность в том что данные со всех каналов пишутся в один регистр ADC1->DR, перезатирая друг друга. Необходимо успевать их считать. Для считывания в нужный момент времени из регистра и пересылку данных автоматически в буфер используется DMA. В начале программы создаём буфер, куда DMA будет пересылать данные.
volatile uint16_t ADCBuffer[] = {0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA };
чтобы просмотреть значения переменных в буфере или других объявленных переменных нужно зайти в папке проекта в папку Listings, там есть файл с расширением .map и там можно найти адрес интересующей переменной.
ADCBuffer 0x20000000 Data 32 main.o(.data) ADCVoltageSum 0x20000020 Data 4 main.o(.data) ADCCurrentSum 0x20000024 Data 4 main.o(.data) ADCVoltage 0x20000028 Data 4 main.o(.data) ADCCurrent 0x2000002c Data 4 main.o(.data)
Например ADCBuffer находится по адресу 0x20000000
Затем забиваем адрес в поле memory в отладчике keil

и на протяжении пошагового выполнения можно отслеживать значения в памяти.
Для DMA1 по табл.1 запросы от ADC приходят на 1 канал. Поэтому настраиваем первый канал и его и включаем.

При настройке DMA нужно указать адреса откуда и куда будет пересылать данные контроллер DMA
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;//откуда DMA1_Channel1->CMAR = (uint32_t)ADCBuffer;//куда
Кол-во данных, в данном случае это значение равно кол-ву ячеек в регулярной группе
DMA1_Channel1->CNDTR = 16;
Направление из периферии в память
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
Адрес периферии при этом не инкрементировать увеличивать, а памяти куда инкрементировать.
DMA1_Channel1->CCR &= ~DMA_CCR1_PINC; DMA1_Channel1->CCR |= DMA_CCR1_MINC;
Размер данных при пересылке устанавливаем 16 бит так, как ADC 12 разрядный
DMA1_Channel1->CCR |= DMA_CCR4_MSIZE_0; DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;
В конце включаем DMA
DMA1_Channel1->CCR |= DMA_CCR1_EN;
По окончанию заполнения буфера контроллером DMA нужно обработать эти данные. То есть нужно включить прерывание по событию окончанию пересылки данных контроллером DMA.
void interrupts_enable(void)
{
DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
NVIC_EnableIRQ (DMA1_Channel1_IRQn); //включение прерывания в ядре
}
И после возникновения прерывания обработать данный в обработчике прерывания
void DMA1_Channel1_IRQHandler(void)
{
DMA1_Channel1->CCR &= ~ DMA_CCR1_EN;
if (DMA1->ISR & DMA_ISR_TCIF1) //Если буфер заполнен
{
DMA1->IFCR |= DMA_IFCR_CGIF1; //
ADCVoltageSum = ADCBuffer[0]+ADCBuffer[2]+ADCBuffer[4]+ADCBuffer[6]+ADCBuffer[8]+ADCBuffer[10]+ADCBuffer[12]+ADCBuffer[14];
ADCCurrentSum = ADCBuffer[1]+ADCBuffer[3]+ADCBuffer[5]+ADCBuffer[7]+ADCBuffer[9]+ADCBuffer[11]+ADCBuffer[13]+ADCBuffer[15];
ADCVoltage = ADCVoltageSum/8;
ADCCurrent = ADCCurrentSum/8;
DMA1_Channel1->CCR |= DMA_CCR1_EN;
}
}
перед началом обработки данных в буфере, DMA нужно отключить чтобы он никак не влиял на буфер.
полный код программы
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_adc.h"
#include "stm32f10x_dma.h"
volatile uint16_t ADCBuffer[] = {0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA };
int ADCVoltageSum;
int ADCCurrentSum;
int ADCVoltage;
int ADCCurrent;
void delay(uint32_t ms)
{
volatile uint32_t nCount;
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq (&RCC_Clocks);
nCount=(RCC_Clocks.HCLK_Frequency/10000)*ms;
for (; nCount!=0; nCount--);
}
//обработчик прерывания ADC1 и ADC2
void DMA1_Channel1_IRQHandler(void)
{
DMA1_Channel1->CCR &= ~ DMA_CCR1_EN;
if (DMA1->ISR & DMA_ISR_TCIF1) //Если буфер заполнен
{
DMA1->IFCR |= DMA_IFCR_CGIF1; //
ADCVoltageSum = ADCBuffer[0]+ADCBuffer[2]+ADCBuffer[4]+ADCBuffer[6]+ADCBuffer[8]+ADCBuffer[10]+ADCBuffer[12]+ADCBuffer[14];
ADCCurrentSum = ADCBuffer[1]+ADCBuffer[3]+ADCBuffer[5]+ADCBuffer[7]+ADCBuffer[9]+ADCBuffer[11]+ADCBuffer[13]+ADCBuffer[15];
ADCVoltage = ADCVoltageSum/8;
ADCCurrent = ADCCurrentSum/8;
DMA1_Channel1->CCR |= DMA_CCR1_EN;
}
}
//включение прерывания по окончанию DMA
void interrupts_enable(void)
{
DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
NVIC_EnableIRQ (DMA1_Channel1_IRQn); //включение прерывания в ядре
}
//Функция включения тактирования на нужной периферии, установка значений делителей частоты
void RCC_ENABLE(void)
{
//подача тактирования на PORTA, настройка преддедлитля ADC, подача тактирования на ADC
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // Разрешить тактирование порта PORTA
RCC->CFGR |=RCC_CFGR_ADCPRE_0|RCC_CFGR_ADCPRE_1 ; //входной делитель
//обнуляем биты чтобы установить предделитель 2(наименьшее значение)
//clock for ADC (max 14MHz --> 8/2=4MHz)
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; //подаем тактирование АЦП
RCC->AHBENR |= RCC_AHBENR_DMA1EN; //подаем тактирование на DMA
}
void GPIO_Configuration(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //включить тактирование порта
//Вывод GPIOA.9
GPIOA->CRH &= ~GPIO_CRH_MODE8; //очистить разряды MODE
GPIOA->CRH &= ~GPIO_CRH_CNF8; //очистить разряды CNF
GPIOA->CRH |= GPIO_CRH_CNF8_1; //дискретный вход, подтяжка к плюсу
GPIOA->BSRR = GPIO_BSRR_BS8; //включить подтягивающий резистор
//Вывод GPIOA.8
GPIOA->CRH &= ~GPIO_CRH_MODE9; //очистить разряды MODE
GPIOA->CRH &= ~GPIO_CRH_CNF9; //очистить разряды CNF
GPIOA->CRH |= GPIO_CRH_CNF9_1; //дискретный вход, подтяжка к плюсу
GPIOA->BSRR = GPIO_BSRR_BS9; //включить подтягивающий резистор
//Вывод GPIOA.3
GPIOA->CRL &= ~GPIO_CRL_MODE3; //Очистить биты MODE
GPIOA->CRL &= ~GPIO_CRL_CNF3; //Очистить биты CNF
//Вывод GPIOA.5
GPIOA->CRL &= ~GPIO_CRL_MODE5; //Очистить биты MODE
GPIOA->CRL &= ~GPIO_CRL_CNF5; //Очистить биты CNF
}
void ADC_DMA_init(void)
{
//Настройка DMA
DMA1_Channel1->CNDTR = 16;
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
DMA1_Channel1->CMAR = (uint32_t)ADCBuffer;
DMA1_Channel1->CCR |= DMA_CCR4_MSIZE_0;
DMA1_Channel1->CCR |= DMA_CCR1_MINC;
DMA1_Channel1->CCR |= DMA_CCR1_CIRC;
DMA1_Channel1->CPAR = (uint32_t)&ADC1->DR;
DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;
DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;
DMA1_Channel1->CCR |= DMA_CCR1_EN;
ADC1->CR2 |= ADC_CR2_ADON; //включить АЦП
//настройка АЦП происходит в режиме power down mode
ADC1->CR1 = 0; //т.к. не используем AWD и прерывания в данном примере то обнуляем CR1
ADC1->CR1 |= ADC_CR1_SCAN; //Включение прерывания, и сканирование группы
ADC1->CR2 |= ADC_CR2_CONT|ADC_CR2_DMA; //режим непрерывного преобразования
ADC1->SMPR2 |= ADC_SMPR2_SMP3_1|ADC_SMPR2_SMP5_1; //установка t выборки для 3 и 5 канала 13,5 цикла
//Калибровка
//В даташите
//Before starting a calibration, the ADC must have been in power-on state (ADON bit = ‘1’) for
//at least two ADC clock cycles. enable ADC
// до начала калибровки нужно enable ADC
// в даташите в разделе 11.3.1 ADC on-off control сказано что первое включение ADON bit = ‘1’
// выводит из режима power down mode, а повторное запускает преобразование через tSTAB
ADC1->CR2 |= ADC_CR2_RSTCAL; // reset calibration
while (ADC1->CR2 & ADC_CR2_RSTCAL) {}
//start self-calibration
ADC1->CR2 |= ADC_CR2_CAL;
while(ADC1->CR2 & ADC_CR2_CAL) {}
ADC1->SQR1 |= ADC_SQR1_L_3|ADC_SQR1_L_2|ADC_SQR1_L_1|ADC_SQR1_L_0; //т.к. в регулярной группе 4 выборки то биты L[3:0] = 3 регистра SQR1
ADC1->SQR3 &= ~ ADC_SQR3_SQ1; // в начале обнуляем биты
ADC1->SQR3 |= ADC_SQR3_SQ1_0
|ADC_SQR3_SQ1_1
|ADC_SQR3_SQ2_0
|ADC_SQR3_SQ2_2
|ADC_SQR3_SQ3_0
|ADC_SQR3_SQ3_1
|ADC_SQR3_SQ4_0
|ADC_SQR3_SQ4_2
|ADC_SQR3_SQ5_0
|ADC_SQR3_SQ5_1
|ADC_SQR3_SQ6_0
|ADC_SQR3_SQ6_2;
ADC1->SQR2 |= ADC_SQR2_SQ7_0
|ADC_SQR2_SQ7_1
|ADC_SQR2_SQ8_0
|ADC_SQR2_SQ8_2
|ADC_SQR2_SQ9_0
|ADC_SQR2_SQ9_1
|ADC_SQR2_SQ10_0
|ADC_SQR2_SQ10_2
|ADC_SQR2_SQ11_0
|ADC_SQR2_SQ11_1
|ADC_SQR2_SQ12_0
|ADC_SQR2_SQ12_2;
ADC1->SQR1|= ADC_SQR1_SQ13_0
|ADC_SQR1_SQ13_1
|ADC_SQR1_SQ14_0
|ADC_SQR1_SQ14_2
|ADC_SQR1_SQ15_0
|ADC_SQR1_SQ15_1
|ADC_SQR1_SQ16_0
|ADC_SQR1_SQ16_2;
//затем устанавлием 0 бит в 1, чтобы выбрать 1 канал в SQ2,SQ4, SQ1,SQ3 оставвляем равным 0 т.к. 0 канал
//Start conversion
ADC1->CR2 |= ADC_CR2_ADON;
}
int main(void)
{
RCC_ENABLE();
GPIO_Configuration();
interrupts_enable();
ADC_DMA_init();
lcd_init();
while (1)
{
lcd_set_xy(0,0);
lcd_out("V=");
lcd_set_xy(2,0);
BCD_4IntLcd(ADCBuffer[0]);
lcd_set_xy(7,0);
lcd_out("Vsr=");
lcd_set_xy(11,0);
BCD_4IntLcd(ADCVoltage);
lcd_set_xy(0,1);
lcd_out("I=");
lcd_set_xy(2,1);
BCD_4IntLcd(ADCBuffer[1]);
lcd_set_xy(7,1);
lcd_out("Isr");
lcd_set_xy(11,1);
BCD_4IntLcd(ADCCurrent);
delay(100);
}
}