Урок 20
HAL. ADC. Injected Channel. Interrupt
Сегодня мы продолжим работать с инжекторным каналом АЦП.
Только сегодня мы попробуем поработать с прерываниями такого канала, тем самым мы немного автоматизируем процесс, не будем использовать задержки, что придаст нашему проекту большую профессиональность.
Проект мы также создадим из предыдущего — из ADC_INJECTED, и назовём его ADC_INJECTED_INT, всего лишь прицепив суффикс _INT.
Запустим проект в Cube MX.
Первым делом мы, конечно, включим эти самые прерывания
Перейдём в данном диалоге в закладку «Parameter Settings» и параметр «End Of Confersion Selectiom» переключим в следующее состояние
Применим настройки, сгенерируем проект, запустим его в Keil, настроим программатор на авторезет, добавим файл LCD.c в проект, соберём проект и начнем писать код.
Скопируем из бесконечного цикла строку
HAL_ADCEx_InjectedStart(&hadc1);
и вставим её до бесконечного цикла, слегка исправив, добавив префикс _IT
LCD_Clear();
HAL_ADCEx_InjectedStart_IT(&hadc1);
/* USER CODE END 2 */
В бесконечном цикле всё закомментируем, кроме задержки.
Добавим также обработчик прерывания для инжектированного канала АЦП. Он почти ничем не отличается от обработчика прерываний регулярного
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc1)
{
}
Обработчик от регулярного канала пока оставим на будущее.
В функции-обработчике прерываний мы будем забирать результат, так как самое время это здесь именно и делать. Данный обработчик вызывается по окончанию преобразований напряжений.
Только преобразовывать в тип переменной с плавающей точкой здесь не будем для экономии времени на обработку. Будем использовать целочисленный массив. Он у нас уже был, но на прошлом занятии мы его удалили. Теперь придется его добавить заново
/* Private variables ———————————————————*/
volatile uint16_t ADC_Data[4];
Теперь можно будем добавить код в обработчик прерывания, используя данный массив
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef* hadc1)
{
ADC_Data[0]=HAL_ADCEx_InjectedGetValue(hadc1,ADC_INJECTED_RANK_1);
ADC_Data[1]=HAL_ADCEx_InjectedGetValue(hadc1,ADC_INJECTED_RANK_2);
ADC_Data[2]=HAL_ADCEx_InjectedGetValue(hadc1,ADC_INJECTED_RANK_3);
ADC_Data[3]=HAL_ADCEx_InjectedGetValue(hadc1,ADC_INJECTED_RANK_4);
}
И в конце нам нужно заново запустить преобразование. Можно вызов функции скопировать из нашего кода из функции main(), только амперсанд нам не потребуется, так как у нас уже указатель
ADC_Data[3]=HAL_ADCEx_InjectedGetValue(hadc1,ADC_INJECTED_RANK_4);
cnt1++;
HAL_ADCEx_InjectedStart_IT(hadc1);
}
Добавим переменную i в функцию main()
char str1[9];
uint8_t i;
/* USER CODE END 1 */
Теперь добавим в бесконечный цикл конечный цикл, в котором мы уложим все значения, считанны в обработчике прерывания, на место — в массив переменных с плавающей точкой
while (1)
{
for(i=0;i<4;i++) u[i]=((float)ADC_Data[i])*3/4096;
Раскомментируем код, который у нас закоментирован в бесконечном цикле и исправим его под создавшиеся новые условия, отображая постепенно считанные и обработанные данные на символьном дисплее
for(i=0;i<4;i++) u[i]=((float)ADC_Data[i])*3/4096;
sprintf(str,»%.2fv»,u[0]);//преобразуем результат в строку
sprintf(str1,»%.2fv»,u[1]);//преобразуем результат в строку
strcat(str,str1);
sprintf(str1,»%.2fv»,u[2]);//преобразуем результат в строку
strcat(str,str1);
sprintf(str1,»%.2fv»,u[3]);//преобразуем результат в строку
strcat(str,str1);
LCD_SetPos(0,0);//покажем результат на ЖКИ-дисплее
LCD_String(str);
HAL_Delay(200);
/* USER CODE END WHILE */
Соберем наш код и проверим его на практике
Мы видим, что всё отлично работает. Убедиться в этом можно, покрутив резисторы в делителях напряжений.
Также мы можем ещё добавить счётчики, как мы делали в позапрошлом уроке, и посчитать, с какой частотой у нас работает наша шина АЦП. Даже, в принципе, можно найти тот проект и скопировать код оттуда, так просто получится быстрее
volatile uint16_t ADC_Data[4];
volatile uint32_t cnt1,cnt2;
/* USER CODE END PV */
Также добавим в файл stm32f4xx_it.c
/* USER CODE BEGIN 0 */
extern volatile uint32_t cnt2;
/* USER CODE END 0 */
………………….
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
cnt2++;
/* USER CODE END SysTick_IRQn 0 */
На всякий случае инициализируем счётчики в функции main()
/* USER CODE BEGIN 2 */
cnt1=0;cnt2=0;
Считать будем в обработчике прерываний от АЦП
ADC_Data[3]=HAL_ADCEx_InjectedGetValue(hadc1,ADC_INJECTED_RANK_4);
cnt1++;
HAL_ADCEx_InjectedStart_IT(hadc1);
}
И останется нам отобразить это где-то на дисплее. Но раз уж мы заняли 1ю строчку, то отобразим всё это в другом месте
LCD_String(str);
sprintf(str,»%10u»,cnt1);//преобразуем результат в строку
LCD_SetPos(6,1);//покажем результат на ЖКИ-дисплее
LCD_String(str);
sprintf(str,»%10u»,cnt2);//преобразуем результат в строку
LCD_SetPos(6,2);//покажем результат на ЖКИ-дисплее
LCD_String(str);
HAL_Delay(200);
/* USER CODE END WHILE */
Соберём проект, прошьём контроллер и посмотрим результат. Дожидаемся, когда на дисплее в нижней строке будет применрно будет 10000
И сделав нехитрый подсчёт мы видим, что АЦП у нас работает где-то со скоростью 220 кГц.
Это очень неплохо, что одновременно 4 канала успевают преобразовывать данные. поступающие на входы с такой частотой.
Попробуем также порегулировать резисторы, чтобы убедиться, что у нас ничего не висит.
Предыдущий урок Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь STM32F4-DISCOVERY
Смотреть ВИДЕОУРОК
Добрый день, в моем эксперименте по данному уроку при точно таких же вводных (PCKL 84МГц, ADCCLK=PCKL/2, Ts=3 cycles) мой АЦП (ADC1) считает с частотой примерно 98 кГц. Единственное отличие я измеряю не подстроечные резисторы, а внутренний датчик температуры у мк на 18 канале, который подвожу также к 4 рангам инжектированной группы. У меня плата NUCLEO-F446RE. Почему у Вас 220кГц, а у меня 98кГц?
И при Ts=3 cycles АЦП измеряет не корректно входные сигналы, нужно ставить Sampling Time хотя бы 28 cycles, но тогда и частота АЦП понижается до 80кГц.