Перейти к содержимому

#9. ADC DMA

По работе 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);
           
    }
}

Добавить комментарий