Прямой доступ к памяти (DMA)

DMA — это способ обмена данными между подсистемами микроконтроллера, без участия центрального процессора. Такой способ позволяет снять избыточную нагрузку с ЦП и увеличить скорость передачи данных.

Напишем программу, которая будет анализировать сигнал на контакте PA0, и в зависимости от полученной величины зажигать или гасить штатный светодиод на контакте PC13. АЦП будет осуществлять преобразования каждые 20мс в фоновом режиме, а результат автоматически передавать в особую переменную посредством DMA.

Настройка АЦП

Нажимаем правую кнопку мыши на PA0 и выбираем пункт ADC1_IN0. Выбор этого пункта означает, что мы подключаем данный контакт к каналу №0 аппаратного АЦП №1. Теперь к PA0 можно подключить нужный нам прибор с аналоговым выходом, с которого мы в дальнейшем будем снимать данные.

STM32 АЦП и DMA. Настройка АЦП

Далее настроим событие, по которому АЦП будет начинать преобразование. Пусть это будет происходить каждый раз, когда на первом канале таймера №1 возникает событие Capture Compare.

Заходим на вкладку Parameter Settings, и в поле External Trigger Conversion Source выбираем пункт Timer 1 Capture Compare 1 event.

STM32 АЦП и DMA. Настройка события запуска АЦП

Выбираем пункт ADC1 в левом меню. Открываем вкладку настроек DMA Settings. Жмём кнопку ADD, добавляя тем самым новую инструкцию для передачи данных из АЦП в режиме DMA.

В появившихся полях выбираем режим (Mode) Circular. Размер данных (Data Width) менять не нужно. АЦП у нас 12-разрядное, а значит Half Word (16-разрядов) нам вполне хватит.

STM32 АЦП и DMA. Настройка АЦП

С АЦП разделались, переходим к таймеру.

Таймер

Настроим генерацию событий Capture Compare на первом канале таймера №1.

В левом меню жмём на TIM1. В поле Channel1 выбираем Output Compare No Output. Это означает, что таймер будет генерировать нужные нам события, ни на какие физические контакты этот сигнал выдавать не будет.

В поле Clock Source выбираем Internal Clock.

По умолчанию, на в среде CubeIDE тактирование микроконтроллера stm32f103c8t6 осуществляется на частоте 8 МГц, без внешнего резонатора. Так и оставим, высокие скорости нам не требуются.

Пусть таймер будет генерировать событие каждые 20мс (50 Гц). Выставляем предделитель на 800-1, а период счетчика на 200-1. Это и даст нам нужную частоту.

STM32 АЦП и DMA. Настройка события capture compare в таймере

Наконец, там же в параметрах таймера, находим блок Output Compare No Output и выбираем режим (Mode) Toggle on match.

STM32 АЦП и DMA. Настройка события capture compare в таймере

Индикатор

Настроим штатный индикатор, который на отладочных платах обычно привязан к контакту PC13.

Жмём на контакт PC13 и выбираем режим GPIO_Output. Теперь этот контакт будет работать на выход, и мы сможем выставлять на нем логический уровень. Затем в левом меню открываем раздел GPIO, прописываем этому контакту псевдоним LED в поле User Label.

STM32 настройка светодиода PC13

Сохраняем проект и переходим к исходному коду.

Программа

После генерации шаблона, приступаем к написанию своего кода, строго в выделенных для этого разделах.

Будем зажигать светодиод при достижении значения 2100 на потенциометре, и выключать его при снижении до 2000. Разница в 100 единиц сделана для создания гистерезиса — чтобы исключить мерцание светодиода в пограничном состоянии потенциометра.

Чтобы запустить преобразования АЦП с передачей результата в память при помощи DMA, в блоке кода USER CODE 2 пишем нужные вызовы:

/* USER CODE BEGIN 2 */

HAL_ADCEx_Calibration_Start(&hadc1); // калибровка АЦП
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&pot_adc, 1); // запуск DMA
HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_1); // запуск таймера для опроса АЦП

DMA будет записывать показания потенциометра в переменную pot_adc, которую, неплохо бы, тоже объявить в соответствующем блоке кода. Там же объявим переменную flag_adc, отметив её признаком volatile. Она пригодится нам далее.

/* USER CODE BEGIN PV */
uint16_t pot_adc;
volatile uint8_t flag_adc = 0;

Далее, нам потребуется переопределить функцию-обработчик прерывания HAL_ADC_ConvCpltCallback. Эта функция будет вызываться каждый раз, когда АЦП порождает новые данные.

/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
    if(hadc->Instance == ADC1){
        flag_adc = 1;
    }
}
/* USER CODE END 4 */

Как мы уже говорили в уроке про прерывания на stm32, в теле обработчика недопустимы сложные вычисления. Мы лишь изменим значение переменной flag_adc на 1.

Далее, отредактируем суперцикл в соответствующем блоке. При использовании переменной pot_adc в каких либо вычислениях, необходимо на время выключить режим DMA. Иначе может возникнуть ситуация, когда переменная изменится прямо в процессе вычисления.

/* USER CODE BEGIN WHILE */

while (1){
  if( flag_adc ){
    flag_adc = 0;
    HAL_ADC_Stop_DMA(&hadc1);
    if( pot_adc > 2100 ){
      HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
    } else
      if( pot_adc < 2000 ){
        HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
    }
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&pot_adc, 1);
}
/* USER CODE END WHILE */

Готово. Загружаем программу на контроллер. Подключаем потенциометр к контакту PA0. Крутим ручку и смотрим за светодиодом.


Изменено:

STM32: АЦП: 3 комментария

  1. нет ли в этом уроке ошибки? функции, которые в нем используются, ведь не были в начале обьявлены?
    а при компиляции дает ошибку:
    <<undefined reference to `HAL_ADCEx_Calibration_Start'

    • а перед этим еще и предупреждение было:
      <<implicit declaration of function 'HAL_ADCEx_Calibration_Start'; did you mean 'HAL_ADCEx_calibration_Start'? [-Wimplicit-function-declaration]

      • Эти функции должны были быть объявлены в коде, сгенерированном средой. То есть в редакторе CubeMX включаем ADC, сохраняем проект, и после этого генерируется весь нужный для ADC код.
        Надо знать последовательность ваших действий, чтобы понять что пошло не так.

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Комментарий
Имя
E-mail

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.