Настройка ШИМ и энкодера

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

Алгоритм работы нашего сервомотора будет таким:

  1. Таймер №2 вызывает событие на 2м-м канале. АЦП считывает сигнал с потенциометра, преобразует его в число от 0 до 4095, затем DMA-контроллер копирует это число в заданную ячейку памяти.
  2. Происходит вызов прерывания HAL_ADC_ConvCpltCallback. В этот момент выставляем флаг, что событие свершилось и мы можем работать.
  3. В суперцикле проверяем, что флаг выставлен и вызываем процедуру управления сервомотором.
  4. Получаем текущее значение счетчика энкодера и значение на потенциометре. Формируем управляющий сигнал.
  5. Корректируем ШИМ сигнал мотора, согласно величине управляющего сигнала.

Полный исходный код программы есть на github: исходный код сервомотора

Потенциометр, АЦА и DMA

Чтобы запустить преобразования АЦП с передачей результата в память при помощи 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(&htim2, TIM_CHANNEL_2); // запуск таймера для опроса АЦП

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

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

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

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

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

Далее, отредактируем суперцикл в соответствующем блоке.

/* USER CODE BEGIN WHILE */

while (1)
{
    if(flag){
        flag = 0;
	// вызываем процедуру управления
	motorToPos();
    }
/* USER CODE END WHILE */

Смысл такой: как только мы наткнулись на ненулевой flag, обнуляем его и вызываем процедуру управления. Именно в этой процедуре скрывается ПИД-регулятор, который поможет нам правильно вращать мотор.

ШИМ

Ранее мы настроили генерацию ШИМ с помощью таймера №3. Чтобы управлять скоростью вращения мотора, нам нужно будет менять настройки этого таймера, но уже не через конфигуратор, а по ходу программы. Для этого напишем отдельную функцию setPWM.

/* USER CODE BEGIN 4 */
void setPWM(uint16_t value){
    TIM_OC_InitTypeDef sConfigOC;

    HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1);

    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = value;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;

    HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}

Функция принимает только один аргумент — коэффициент заполнения ШИМ, который у нас от 0 до 1000.

Энкодер

Ещё один таймер обрабатывает сигналы с энкодера. Запустим его, вызвав соответствующую функцию в разделе USER CODE 2:

  HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL); // запуск энкодера
  /* USER CODE END 2 */

Осталось только запрограммировать ПИД-регулятор, чем мы и займёмся в следующей главе.


Изменено: