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

#7. Аппаратный АЦП и два канала 0 и 1—>A0 и A1 —-> PORTA.1 и PORTA.2 для отображения значений АЦП на LCD 1609 (HD44780)

Используем порты A0 и A1

Функция включения тактирования

void RCC_ENABLE(void)
{
		//подача тактирования на PORTA, настройка преддеделителя ADC, подача тактирования на ADC
		RCC->APB1ENR |= 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;   //подаем тактирование АЦП
}

Функция включений тактирования на требуемой периферии, в данном случае это АЦП и PORTA.

Также нужно установить ADC Prescaler в max 8, т. к. ADCCLK должно быть не более 14MHz, а SYSCLK устанавливается при инициализации проекта в 72 MHz.

Настройка частоты работы контроллера можно осуществить закомментировав и раскомментировав нужную строку в файле проекта system_stm32f10x.c, к контроллеру stm32f103c8t6 относятся строки после #else.

#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
 #define SYSCLK_FREQ_24MHz  24000000
#else
/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz  24000000 */ 
/* #define SYSCLK_FREQ_36MHz  36000000 */
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
#define SYSCLK_FREQ_72MHz  72000000
#endif

Функция настройки портов PORTA.0 и PORTA.1 настраиваю как входа с помощью генератора кода, на схеме выше указано что к данным выводам подключаются 0 и 1 каналы АЦП1.

//Конфигурирование PORTA.1 - аналоговый вход, будет использоваться для АЦП
void GPIO_Configuration(void)
{
        GPIOA->CRL   &= ~GPIO_CRL_MODE0;       //Очистить биты MODE
		GPIOA->CRL   &= ~GPIO_CRL_CNF0;        //Очистить биты CNF
		GPIOA->CRL   &= ~GPIO_CRL_MODE1;       //Очистить биты MODE
		GPIOA->CRL   &= ~GPIO_CRL_CNF1;        //Очистить биты CNF
}

Фун-ция настройки АЦП 0 и 1 канала.

Есть две разновидности опроса каналов: регулярные группы, инжектированные группы. В первом результаты преобразования 0 и 1 канала контроллер поочередно с заданным временем выборки помещает в один регистр ADC1->DR, а предыдущее значение затирается. Использование прерывания по событию EOC(end of conversion) не помогает так как это событие возникает когда заканчиваются преобразования в регулярной группе. Тут нужно использовать DMA и массив.(с этим пока сложности)

Поэтому в программе используется способ опроса канала инжектированных групп. Каждый канал в инжектированной имеет свой регистр куда контролер помещает результаты преобразований   ADC1->JDR1; ADC1->JDR2;. Всего может быть 4 канала в примере используется 2  ADC1->JSQR |= ADC_JSQR_JL_0 ; настраивается это установкой битов JL если 2канала то в 1. Также нажно прописать какие каналы мы используем в инжектированной группе для 0 ничего не пишим просто обнулемя регистр ADC1->JSQR  &= ~  ADC_JSQR_JSQ1|ADC_JSQR_JSQ2|ADC_JSQR_JSQ3|ADC_JSQR_JSQ4;, а для 1 в  ADC1->JSQR |= ADC_JSQR_JSQ3_0, устанавливаем биты  JSQ3 в 1 чтобы писать  в ADC1->JDR2.

Также перед запуском АЦП нужно его калибрануть:

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->CR2    |=  ADC_CR2_ADON;          //включить АЦП

Повторная установка бита  ADC_CR2_ADON запустит преобразование без калибровки поэтому тут нужно внимательно

Также нужно включить режим непрерывной работы АЦП

ADC1->CR2    |=  ADC_CR2_CONT; 

Режим работы с группой каналов.

ADC1->CR2  |= ADC_CR1_SCAN; 

Разрешить прерывание по событию окончания преобразования в инжектированной группе JEOC

 ADC1->CR1 |= ADC_CR1_JEOCIE

потом

NVIC_EnableIRQ (ADC1_2_IRQn);            //включение прерывания в ядре

 ЭТИ инструкции

ADC1->CR2 |= ADC_CR2_JEXTSEL; //Преобразование инжектированной группы
ADC1->CR2 |= ADC_CR2_JEXTTRIG; //Разрешаем внешний запуск инжектированной группы

нужны чтобы запустить преобразование в инжектированной группе  с помощью этой инструкции

 ADC1->CR2 |= ADC_CR2_JSWSTART;

установка t выборки для 1 и 2 канала 13,5 цикла 

ADC1->SMPR2 = ADC_SMPR2_SMP1_1|ADC_SMPR2_SMP1_0|ADC_SMPR2_SMP0_1|ADC_SMPR2_SMP0_0; 

//настройка работы АЦП

void ADC_init(void)
{
       		 ADC1->CR2    |=  ADC_CR2_ADON;          //включить АЦП
		//настройка АЦП происходит в режиме power down mode
		ADC1->CR1     =  0;          	        //т.к. не используем AWD и прерывания в данном примере то обнуляем CR1
		ADC1->CR1 |= ADC_CR1_JAUTO;             //Разрешить преобразование инжектированной группы
       		 ADC1->CR1 |= ADC_CR1_JEOCIE|ADC_CR1_SCAN;              //Включение прерывания, и сканирование группы
		
        		ADC1->CR2    |=  ADC_CR2_CONT;          //режим непрерывного преобразования 
       		 ADC1->CR2 |= ADC_CR2_JEXTSEL; //Преобразование инжектированной группы
     		 ADC1->CR2 |= ADC_CR2_JEXTTRIG; //Разрешаем внешний запуск инжектированной группы
     		 ADC1->SMPR2 = ADC_SMPR2_SMP1_1|ADC_SMPR2_SMP1_0|ADC_SMPR2_SMP0_1|ADC_SMPR2_SMP0_0; 			//установка t выборки для 1 и 2 канала 13,5 цикла        
        		NVIC_EnableIRQ (ADC1_2_IRQn);            //включение прерывания в ядре 
		
		//Калибровка
		//В даташите
		//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->JSQR  &= ~  ADC_JSQR_JSQ1|ADC_JSQR_JSQ2|ADC_JSQR_JSQ3|ADC_JSQR_JSQ4;
       		 ADC1->JSQR |= ADC_JSQR_JSQ3_0|ADC_JSQR_JL_0 ;                 //JSQ3 -----> JD2
       		 //Start conversion
       		 ADC1->CR2 |= ADC_CR2_JSWSTART;
}

После возникновения события JEOC окончания преобразование в инжектированной группе программа в main прекращается и перескакивает в обработчик прерывания в ф-цию void ADC1_2_IRQHandler(void)

//обработчик прерывания ADC1 и ADC2

void ADC1_2_IRQHandler(void)
{
	if(ADC1->SR & ADC_SR_JEOC) //Проверка что прерывание произошло по событию EOC у ADC1
    {
      ADC1->SR&= ~ADC_SR_JEOC;
      izm1 =ADC1->JDR1;
      izm2 =ADC1->JDR2;
     if(i < 10)
            {
            BuffADC1[i] =ADC1->JDR1;
            BuffADC2[i] =ADC1->JDR2;
            i++;
            }
      else
            {
            for (n = 0; n < 9; n++)
                res1 += BuffADC1[n];
            res1 = res1 / 10;
            for (k = 0; k < 9; k++)
                res2 += BuffADC2[k]; 
            res2 = res2 / 10;        
            i = 0;
            }
  
	}
}

В ф-ции обработки прервания мы проверям что его вызвало событие  JEOC, потом сбрасываем флаг  JEOC инструкцией ADC1->SR&= ~ADC_SR_JEOC; записываем в переменные значения преобразований на 0 и 1 канале.

Эта функция после 10 прерываний высчитвает среднее значение в массиве.

if(i < 10)
            {
            BuffADC1[i] =ADC1->JDR1;
            BuffADC2[i] =ADC1->JDR2;
            i++;
            }
      else
            {
            for (n = 0; n < 9; n++)
                res1 += BuffADC1[n];
            res1 = res1 / 10;
            for (k = 0; k < 9; k++)
                res2 += BuffADC2[k]; 
            res2 = res2 / 10;        
            i = 0;
            }

Для отображения измеренный значений с ацп была использована библиотека с сайта http://cxem.net/mc/mc398.php а это

lcd_init();//ф-ция инициализации
lcd_out("ch1");//вывод строки
lcd_set_xy(4,0);//установка курсора
BCD_4IntLcd(izm1);//вывод переменных 

Код программы:

#include "stm32f10x.h"                  // Device header
#include "LCD/HD44780.h"
#include "LCD/bcd.h"

unsigned int izm1;
unsigned int izm2;
unsigned int BuffADC1[10];
unsigned int BuffADC2[10];      //переменная среднеквадратичная 0 канала
unsigned int i;
unsigned int n;
unsigned int k;
unsigned int m;
unsigned int res1;              //переменная среднеквадратичная 0 канала
unsigned int res2;              //переменная среднеквадратичная 1 канала

// функция задержки - типа более точная, чем тупой подсчет тактов, на самом деле такое же ражно, но более удобно
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 ADC1_2_IRQHandler(void)
{
	if(ADC1->SR & ADC_SR_JEOC) //Проверка что прерывание произошло по событию EOC у ADC1
    {
      ADC1->SR&= ~ADC_SR_JEOC;
      izm1 =ADC1->JDR1;
      izm2 =ADC1->JDR2;
     if(i < 10)
            {
            BuffADC1[i] =ADC1->JDR1;
            BuffADC2[i] =ADC1->JDR2;
            i++;
            }
      else
            {
            for (n = 0; n < 9; n++)
                res1 += BuffADC1[n];
            res1 = res1 / 10;
            for (k = 0; k < 9; k++)
                res2 += BuffADC2[k]; 
            res2 = res2 / 10;        
            i = 0;
            }
  
	}
}

//Функция включения тактирования на нужной периферии, установка значений делителей частоты

void RCC_ENABLE(void)
{
		//подача тактирования на PORTA, настройка преддедлитля ADC, подача тактирования на ADC
		RCC->APB1ENR |= 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;   //подаем тактирование АЦП
}

//Конфигурирование PORTA.1 - аналоговый вход, будет использоваться для АЦП
void GPIO_Configuration(void)
{
        GPIOA->CRL   &= ~GPIO_CRL_MODE0;       //Очистить биты MODE
		GPIOA->CRL   &= ~GPIO_CRL_CNF0;        //Очистить биты CNF
		GPIOA->CRL   &= ~GPIO_CRL_MODE1;       //Очистить биты MODE
		GPIOA->CRL   &= ~GPIO_CRL_CNF1;        //Очистить биты CNF
}
//настройка работы АЦП
void ADC_init(void)
{
        ADC1->CR2    |=  ADC_CR2_ADON;          //включить АЦП
		//настройка АЦП происходит в режиме power down mode
		ADC1->CR1     =  0;          	        //т.к. не используем AWD и прерывания в данном примере то обнуляем CR1
		ADC1->CR1 |= ADC_CR1_JAUTO;             //Разрешить преобразование инжектированной группы
        ADC1->CR1 |= ADC_CR1_JEOCIE|ADC_CR1_SCAN;              //Включение прерывания, и сканирование группы
		
        ADC1->CR2    |=  ADC_CR2_CONT;          //режим непрерывного преобразования 
        ADC1->CR2 |= ADC_CR2_JEXTSEL; //Преобразование инжектированной группы
        ADC1->CR2 |= ADC_CR2_JEXTTRIG; //Разрешаем внешний запуск инжектированной группы
        ADC1->SMPR2 = ADC_SMPR2_SMP1_1|ADC_SMPR2_SMP1_0|ADC_SMPR2_SMP0_1|ADC_SMPR2_SMP0_0; //установка t выборки для 1 и 2 канала 13,5 цикла        
        NVIC_EnableIRQ (ADC1_2_IRQn);            //включение прерывания в ядре 
		
		//Калибровка
		//В даташите
		//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->JSQR  &= ~  ADC_JSQR_JSQ1|ADC_JSQR_JSQ2|ADC_JSQR_JSQ3|ADC_JSQR_JSQ4;
        ADC1->JSQR |= ADC_JSQR_JSQ3_0|ADC_JSQR_JL_0 ;//JSQ3 -----> JD2
        //Start conversion
        ADC1->CR2 |= ADC_CR2_JSWSTART;
}

int main (void) {
    RCC_ENABLE ();
	GPIO_Configuration ();
	ADC_init();
    lcd_init();
    
        while (1)
        {
            lcd_out("ch1");
            for (m = 0; m < 30; m++)
            {
            lcd_set_xy(4,0);
            BCD_4IntLcd(izm1); // выводим переменную
            lcd_set_xy(4,1);
            BCD_4IntLcd(res1); // выводим переменную
            delay(100);
            }
       lcd_clear();
            lcd_out("ch2");
            for (m = 0; m < 30; m++)
            {
            lcd_set_xy(4,0);
            BCD_4IntLcd(izm2); // выводим переменную
            lcd_set_xy(4,1);
            BCD_4IntLcd(res2); // выводим переменную
            delay(100);
            }
        lcd_clear();
        }
}
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x