Понадобилось мне реализовать дистанционное управление несколькими двигателями постоянного тока.
В магазинах доступны готовые комплекты радиоуправления для разных ездящих-летающих игрушек и «не игрушек», и появилось желание использовать именно такое управление.
Сигналы на выходе приёмника такого комплекта — это импульсы для управления сервомашинками,
и задача сводится к тому, чтобы измерить длительность имульса 0,8..2,3 мс в каждом из шести каналов, затратив как можно меньше ресурсов контроллера.
Дальше описано как реализовано измерение длительности импульсов с шести каналов, используя особенности периферии STM32 микроконтроллеров.
Железо, на котором проводилась отладка — аппаратура радиоуправления HobbyKing HK-T6A и контроллер STM32F100C8T.
На выходе приёмника имеем такие сигналы:
Очевидное решение — запустить 6 таймеров и каждый канал измерять отдельным таймером.
По переднему фронту импульса сбрасываем таймер, начинаем отсчёт. По заднему фронту смотрим сколько таймер насчитал с момента сброса, это и будет длительность импульса.
Для сокращения количества задействованных таймеров попробуем завести несколько каналов на один таймер, но так, чтобы было как можно меньше программной обработки.
Будем использовать режим таймера для работы с Hall sensor.
Бесколлекторные двигатели иногда в качестве датчика положения ротора имеют три датчика холла, установленных со смещением. Картинка ниже даёт наглядное изображение как это происходит.
Для обработки датчика холла бесколлекторного электродвигателя некоторые таймеры в STM32 могут быть настроены таким образом, что:
— по переднему фронту на любом из трёх входов CC1, CC2, CC3 таймер сбрасывается,
— по заднему фронту, на любом из этих входов, в регистре capture compare CCMR1 защёлкивается значение счётчика таймера — по факту длительность импульса.
Таким образом, в одном регистре последовательно имеем значения длительности импульсов по трём каналам.
Программно можно как-то разделить эти значения, но проще оставить только два канала ch2 и ch3, значения от них будут защёлкиваться в CCR2 и CCR3 регистрах, а сбрасываться таймер будет по переднему фронту на любом из этих каналов.
Пин, на который назначен вход ch1, настроим на выход, чтобы не ловить помехи на неиспользуемом входе.
На рисунке ниже: красная стрелочка показывает путь сигнала сброса таймера, зелёные стрелочки — путь сигнала защёлкивания значения таймера.
В итоге, для чтения 6 каналов будем использовать 3 таймера, и без дополнительных программных ухищрений, в регистрах CCR таймеров иметь требуемые значения.
Небольшой сюрприз.
Когда это всё уже было реализовано, но я в очередной раз забыл распиновку выхода приёмника, то при поиске в интернете наткнулся на интересную картинку.
Взято отсюда
Поизучав интернет, сделал для себя открытие.
Оказалось, что вход, используемый для питания и привязки приёмника к передатчику, на выходе имеет сигнал, объединяющий все 6 выходов.
Только измерять надо не длительность импульсов, а время между импульсами.
Называется он PPM и широко используется для подключения аппаратуры радиоуправления к компьютеру в качестве джойстика для симулятора (через адаптер, разумеется).
Есть разные версии адаптеров, и даже на основе недорого USBasp программатора.
При наличии PPM выхода задача упростилась: измерить время между шестью последовательными импульсами, началом к измерению будет достаточно длительная пауза.
По переднему фронту импульса сохраняем значение таймера и сразу сбрасываем таймер.
Когда импульсы не поступают, происходит прерывание по переполнению таймера, в этот момент готовимся к приёму импульсов, настраиваем DMA так, чтобы сложить 7 значений в массив.
Первый импульс синхронизирует процесс измерения, значение таймера в этот момент нас не интересует, последующие 6 значений будут хранить время между импульсами.
При каждом последующем импульсе значение таймера защёлкивается, таймер сбрасывается, защёлкнутое значение по DMA сохраняется в массив.
Выводим все результаты измерения первым и вторым способом в UART, видим, что работает, на отклонения рукояток реагирует.
При отклонении рукояток значения меняются в диапазоне 3200..5700 единиц
Только вот значения измеренные первым и вторым способом отличаются, но логический анализатор то же самое показывает, так что это особенности аппаратуры.
Грабли, на которые удалось наступить в процессе разработки.
Поведение программы выглядит так — при включении питания программа работает, но при работе с отладчиком вылетает в HardFault_Handler. Программатор подключён по SWD линии (SWDIO, SWIO, GND).
Оказалось:
— при ресете программатором не очищается регистр статуса таймера TIM1->SR, там остаются флаги запроса на прерывание,
— при инициализации таймера разрешается прерывание от TIM1,
— прерывание для TIM1 и TIM15 общее, и когда обращаемся к структуре htim15, то она ещё не определена, поэтому улетаем в HardFault_Handler.
{
/* USER CODE BEGIN TIM1_BRK_TIM15_IRQn 0 */
/* USER CODE END TIM1_BRK_TIM15_IRQn 0 */
HAL_TIM_IRQHandler(&htim1);
HAL_TIM_IRQHandler(&htim15);
/* USER CODE BEGIN TIM1_BRK_TIM15_IRQn 1 */
/* USER CODE END TIM1_BRK_TIM15_IRQn 1 */
}
Весь этот код сгенерирован Cub-ом автоматически, поэтому пришлось перед всей инициализацией добавить строчку htim15.Instance = TIM15.
Приложения:
1. Проект для Cube
2. Данные 6 каналов + PPM сохранённые с USB логического анализатора Logic-U
Автор: r_o_m_k_a