Настройка ШИМ и энкодера
После генерации шаблона, приступаем к написанию своего кода, строго в выделенных для этого разделах.
Алгоритм работы нашего сервомотора будет таким:
- Таймер №2 вызывает событие на 2м-м канале. АЦП считывает сигнал с потенциометра, преобразует его в число от 0 до 4095, затем DMA-контроллер копирует это число в заданную ячейку памяти.
- Происходит вызов прерывания HAL_ADC_ConvCpltCallback. В этот момент выставляем флаг, что событие свершилось и мы можем работать.
- В суперцикле проверяем, что флаг выставлен и вызываем процедуру управления сервомотором.
- Получаем текущее значение счетчика энкодера и значение на потенциометре. Формируем управляющий сигнал.
- Корректируем ШИМ сигнал мотора, согласно величине управляющего сигнала.
Полный исходный код программы есть на 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 */
Осталось только запрограммировать ПИД-регулятор, чем мы и займёмся в следующей главе.