
Функция включения тактирования
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();
}
}