STM32 – Подключаем энкодер


Экодер
Сложнее было купить энкодер, чем написать программу и подключить его. :)
После шуршания по магазинам электронных компонентов, был найден недорогой энкодер “PEC12-4220F-S0024 энкод.инкр.+выкл” по цене 85 рублей.

Полная документация: смотреть.
Подключение
Работать энкодер будет совместно с таймером 2, схему подключения рисовать не вижу смысла, опишу словами: вывод A подключаем к линии ввода-вывода PA0 (эта линия подключена к первому каналу таймера 2), вывод В подключаем к линии ввода-вывода PA1 (эта линия подключена ко второму каналу таймера 2), и последний вывод С подключаем к земле.
Настройка линий:
#define ENCODER_A A, 0, HIGH, INPUT_PULL_UP, #define ENCODER_B A, 1, HIGH, INPUT_PULL_UP, PIN_CONFIGURATION(ENCODER_A); PIN_CONFIGURATION(ENCODER_B);
Настройка таймера 2 для работы с энкодером
Основная задача при настройке таймера “подать” сигналы от энкодера на счетный вход таймера.
Посмотрим на функциональную схему (рассмотрим один из входов):

Входной сигнал (выходной для одной из линий энкодера) проходя через входной фильтр, поступает на детектор фронтов выход которого подключен к последовательной цепочке мультиплексоров, выход последнего поступает на счетный вход таймера.
Полной схемы работы энкодера в документации я не нашел, да она особо и не нужна, всё и так предельно просто.
Рассмотрю самый просто режим работы.
Первым делом разрешаем тактирование таймера 2:
RCC->APB1ENR = RCC_APB1ENR_TIM2EN;
Настройку входного фильтра пропускаю, точнее просто не фильтруем сигнал IC1F=IC2F=0(см. биты ICF регистра CCMR1).
С детектора фронтов возьмем не инверсный, т.е. активный уровень высокий (для первого и второго входа):
TIM2->CCER = TIM_CCER_CC1P | TIM_CCER_CC2P;
Настраиваем второй мультиплексор (для первого и второго входа):
TIM2->CCMR1 = TIM_CCMR1_CC1S_0 | TIM_CCMR1_CC2S_0;
и последний:
TIM2->SMCR = TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1;
Примечание: приведенный выше режим подразумевает “счет” по каждому из фронтов для двух входных линий, можно так же вести счет либо по первой, либо по второй линии (см. биты SMS регстр SMCR1).
Осталось задать модуль счета, для этого установить регистр автоперезагрузки TIMx_ARR (напомню этот регистр определяет значение при котором счетчик обнуляется):
TIM2->ARR = 100;
Всё, можно разрешать работу счетчика:
TIM2->CR1 = TIM_CR1_CEN;
Инициализация разом:
void mcu_tim2_init(void) { RCC->APB1ENR = RCC_APB1ENR_TIM2EN; TIM2->CCMR1 = TIM_CCMR1_CC1S_0 | TIM_CCMR1_CC2S_0; TIM2->CCER = TIM_CCER_CC1P | TIM_CCER_CC2P; TIM2->SMCR = TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1; TIM2->ARR = 100; TIM2->CR1 = TIM_CR1_CEN; }
Для тестирования вывожу значения счетчика на ЖКИ дисплей:
int main(void) { PIN_CONFIGURATION(ENCODER_A); PIN_CONFIGURATION(ENCODER_B); lcd_2x16_init(); mcu_tim2_init(); while (1) { lcd_2x16_set_position(0, 0); lcd_2x16_print_hex_xx((uint8_t)(TIM2->CNT >> 8)); lcd_2x16_print_hex_xx((uint8_t)TIM2->CNT); } }
Счет происходит согласно алгоритму:

Обращаю внимание в данном режиме один шаг дает 4 тактовых сигнала, т.е. счетчик инкрементируется или декрементируется на 4.
Дальнейшие хитрости настройки за вами.
- +6
- 19 марта 2011, 19:24
- ZiB
по мне так наоборот все просто. подключили к входу счетчика -> настроили счетчик -> читаем значение счетчика. всю обработку делает за нас железка. куда проще-то
- marvin_yorke
- 20 марта 2011, 00:19
- ↑
- ↓
А что именно интересует про USB на STM32? Может смогу чем помочь? HID я с помощью стандартных библиотек на нем запустил.
Меня интересует собственно как настроить и принять/отправить даные через USB на STM32 и есть ли готовые драйверы STM32 по типу как d2xx у FTDI, чтоб в LabView данные принимать на большой скорости. Показать все это дело можно, скажем, на примере логического анализатора ну или на чем нибудь другом, мне не принципиально. А сам я хочу осцил сделать с функциональным генератором и в конечном счере характериограф получить))) АРМ и конкретно STM32 в руки не брал. Ток MCS-51 и AVR.
Готовых драйверов со стороны компа вроде нету. Потому как он не только СОМ-порт изображать может, а все что запрограммируешь. Есть готовые библиотеки от производителя. С помощью них элементарно делается HID или CDC устройство например. Тогда никаких дров под винду не надо, работает через стандартные.
Насчет прикрутить к LV — у LV вроде есть какой-то модуль для работы с USB. Вроде даже в VESA-драйверах. Но что он может и как я не знаю, потому как почти не работал с этой оболочкой.
Насчет прикрутить к LV — у LV вроде есть какой-то модуль для работы с USB. Вроде даже в VESA-драйверах. Но что он может и как я не знаю, потому как почти не работал с этой оболочкой.
Я маленько ошибся)) d2xx это не драйвер, а библиотека dll. Если у STM32 библиотека есть, то это гуд. В общем то я уже делал простенький логический анализатор на AVR, подключал его через FT232 к компу. Со стороны ПК писал прогу в LV. Общались они на скорости 500 кбит/с. Использовал как раз библиотеку d2xx.dll, точнее примеры c сайта FTDI. С госами разберусь и напишу наверно статейку. Может кому-нибудь будет интересно посмотреть что из этого вышло)) Спасибо за инфу))
Первый результат по демки для усб ziblog.ru/2011/03/25/demka-dlya-stm32f103cb/
С детектора фронтов возьмем не инверсный, т.е. активный уровень высокий (для первого и второго входа):
TIM2->CCER = TIM_CCER_CC1P | TIM_CCER_CC2P;
А вроде как наоборот инверсные берем, при установке TIM_CCER_CCхP в 1:
Bit 1 CC1P: Capture/Compare 1 output polarity
CC1 channel configured as input:
This bit selects whether IC1 or IC1 is used for trigger or capture operations.
0: non-inverted: capture is done on a rising edge of IC1. When used as external trigger, IC1 is non-inverted.
1: inverted: capture is done on a falling edge of IC1. When used as external trigger, IC1 is inverted.
Подскажите как с помощью еще одного таймера измерять период импульсом квадратурного счетчика (для определения скорости вращения)?
- alexey_1811
- 31 октября 2011, 15:56
- ↓
Можно. Если не использовать аппаратную поддержку энкодера и считать по прерываниям. Например так (код под libmaple):
Одно замечание: на каждый шаг энкодера приходит минимум 4 прерывания, которые переводят внутреннюю статическую переменную в разные состояния в зависимости от направления. Когда она выходит за границы, она сбрасывается в 0 и происходит посылка сообщения о том, что энкодер повернулся на один шаг в определенном направлении. Замечу, что в этом варианте легко сделать, например, реверс направления (может оказаться удобнее разводить плату) или реализовать нелинейную характеристику регулирования (например, шаг может зависеть от скорости вращения ручки энкодера). Ну и, понятно, повесить энкодер можно на практически любые ноги (но желательно, все-таки, что бы это были два последовательных пина одного порта).
...
#define ENCODER_TRESHOLD 4
...
void encoderHandler();
...
void setup(void)
{
...
exti_attach_interrupt(AFIO_EXTI_0, AFIO_EXTI_PA, encoderHandler, EXTI_RISING_FALLING);
exti_attach_interrupt(AFIO_EXTI_1, AFIO_EXTI_PA, encoderHandler, EXTI_RISING_FALLING);
...
void encoderHandler()
{
static uint8 tablePtr = 3;
static int8 table[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
static int currentEncoderValue = 0;
tablePtr <<= 2;
tablePtr |= GPIOA->regs->IDR & 0x03;
currentEncoderValue += table[tablePtr & 0x0F];
if (currentEncoderValue > ENCODER_TRESHOLD) {
currentEncoderValue = 0;
// send increment event
...
} else if (currentEncoderValue < -ENCODER_TRESHOLD) {
currentEncoderValue = 0;
// send decrement event
}
}
...
Одно замечание: на каждый шаг энкодера приходит минимум 4 прерывания, которые переводят внутреннюю статическую переменную в разные состояния в зависимости от направления. Когда она выходит за границы, она сбрасывается в 0 и происходит посылка сообщения о том, что энкодер повернулся на один шаг в определенном направлении. Замечу, что в этом варианте легко сделать, например, реверс направления (может оказаться удобнее разводить плату) или реализовать нелинейную характеристику регулирования (например, шаг может зависеть от скорости вращения ручки энкодера). Ну и, понятно, повесить энкодер можно на практически любые ноги (но желательно, все-таки, что бы это были два последовательных пина одного порта).
Что за макросы такие: PIN_CONFIGURATION и т.д.? Гугл даёт ссылки на статьи с их использованием, но что это такое само по себе как-то уже не.
Хотя у меня другой подход к конфигурированию GPIO, но тем не менее, стало интересно, откуда такое =)
Хотя у меня другой подход к конфигурированию GPIO, но тем не менее, стало интересно, откуда такое =)
- teplofizik
- 18 апреля 2013, 10:11
- ↓
Это мои макросы можно взять из шаблонов:
ziblog.ru/2012/09/09/linii-vvoda-vyivoda-stm32-chast-3.html
в конце статьи.
Но там кажется я уже что-то правил, всё ни как не соберусь, нужно прогнать под все семейства и выложить в одном месте.
ziblog.ru/2012/09/09/linii-vvoda-vyivoda-stm32-chast-3.html
в конце статьи.
Но там кажется я уже что-то правил, всё ни как не соберусь, нужно прогнать под все семейства и выложить в одном месте.
А, понятно =) *скачал и посмотрел пример*
Использовать я их буду вряд ли (я не люблю макросы в принципе, люблю изоляцию периферии от основного кода и предпочитаю использовать отдельные функции для настройки), но сам вариант реализации переносимости любопытен.
Но вообще немного удивил подход стм в этих контроллерах, что для энкодера надо использовать обычный GPIO-вход, а не альтернативную функцию таймера Оо В ф4 как-то всё гораздо логичнее с настройкой выводов ><
Использовать я их буду вряд ли (я не люблю макросы в принципе, люблю изоляцию периферии от основного кода и предпочитаю использовать отдельные функции для настройки), но сам вариант реализации переносимости любопытен.
Но вообще немного удивил подход стм в этих контроллерах, что для энкодера надо использовать обычный GPIO-вход, а не альтернативную функцию таймера Оо В ф4 как-то всё гораздо логичнее с настройкой выводов ><
- teplofizik
- 18 апреля 2013, 11:30
- ↑
- ↓
Тот вариант, который в топике использует как раз таймер. Чуть выше — мой вариант на прерываниях вместо таймера. Все зависит от конкретного применения. Скажем, для двигателей с датчиками удобнее таймер. Для юзер интерфейса — обработка по прерываниям.
Ненене, я про саму конфигурацию выводов. Используется для энкодера таймер, но функция выводов (оопс) — цифровой вход. А не альтернативная функция соответствующего таймера, что я ожидал.
Да вообще, если есть возможность, лучше использовать таймер, если он есть. А суррогаты — это уже от отсутствия соответстующего интерфейса =) Хотя я вот для отладки использую суррогат RTC на таймере, пока до самих часов не добрался… Интерфейс программный тот же, проге пофиг.
Да вообще, если есть возможность, лучше использовать таймер, если он есть. А суррогаты — это уже от отсутствия соответстующего интерфейса =) Хотя я вот для отладки использую суррогат RTC на таймере, пока до самих часов не добрался… Интерфейс программный тот же, проге пофиг.
- teplofizik
- 18 апреля 2013, 11:50
- ↑
- ↓
А не альтернативная функция соответствующего таймера, что я ожидал.Подозреваю это из-за того, что откуда брать импульсы настраивается в таймере, а нога остается как есть.
Да вообще, если есть возможность, лучше использовать таймер, если он есть. А суррогаты — это уже от отсутствия соответстующего интерфейса =)Я придерживался такого же мнения, пока не прикрутил енкодер для работы с интерфейсом и обплевался на неудобство такого решения. Главное неудобство в том, что таймер считает абсолютные значения, а в интерфейсе с юзером гораздо удобнее инкрементальные. Плюс таймер надо опрашивать. Плюс он теряет информацию о времени возникновения события. Все это, конечно, решаемо, но достаточно черезжопными методами и заметно усложняет код. А вот тот кусок, который я выше процитировал, позволяет делать все эти вещи легко и с минимумом кода.
Подозреваю это из-за того, что откуда брать импульсы настраивается в таймере, а нога остается как есть.Вполне возможно. Но всё же не здорово.
Инкрементальные получаются несложно же, достаточно хранить предыдущее значение и вычитать его из текущего. Если делать так регулярно, то ок. Единственное, что сейчас мне не нравится, так это то, что он за один щелчок может посчитать две единицы вместо одной (в зависимости от чего-то неизвестного).з. Вся логика энкодера у меня представлена этой функцией:
// Регулярный опрос крутилки (десяток Гц)
static void enc_onTimer(void)
{
int16_t Rel = enc_GetRelativeMove(); // по сути там { return (TIM->CNT - (TIM_last = TIM->CNT)); }
if(Rel)
{
// Есть движение
if(enc_Handler)
{
// Пользовательский обработчик (если он есть, конечно)!
enc_Handler(Rel, enc_GetPosition()); // enc_GetPosition = { return TIM->CNT; }
}
}
}
Достаточно лаконично же =)
- teplofizik
- 18 апреля 2013, 12:56
- ↑
- ↓
по сути там { return (TIM->CNT — (TIM_last = TIM->CNT));Угу. Ага. И переполнение не забыть обработать. Причем в обе стороны. Получается в итоге вовсе не так просто и красиво.
Достаточно лаконично же =)Если не считать опроса то да, сравнимо с кодом на прерываниях. Теперь добавить учет скорости поворота енкодера :)
P.S. я все эти фазы, которые есть в вашем варианте, прошел, а потом таки сделал на прерываниях.
И переполнение не забыть обработать.Неа, при использовании типа int16_t и ARR=0xFFFF, это посчитается само собой.
Теперь добавить учет скорости поворота енкодераЕсли частота опроса задана (например, как у меня 10 Гц), то скорость вычисляется просто.
Speed = (TIM->CNT — TIM_last) * Freq;
Например, если разница между двумя вызовами в 10 щелчков, а частота вызовов 10 Гц, то результат: 100 щелчков/сек.
Если меня модуль не устроит, сделаю на прерываниях, благо, коду пофиг =)
- teplofizik
- 18 апреля 2013, 13:36
- ↑
- ↓
Неа, при использовании типа int16_t и ARR=0xFFFF, это посчитается само собой.При такой настройке смысл в использовании таймера вообще утрачивается. Это значение уже напрямую нигде не используешь.
Если частота опроса задана (например, как у меня 10 Гц), то скорость вычисляется просто.Строго говоря нет. Разница за время между опросами могла измениться, скажем, от -20 до +30, а вы считаете, что она +10. К тому же гораздо интереснее использовать не скорость, а время между соседними изменениями счетчика, типа такого:
Speed = (TIM->CNT — TIM_last) * Freq;
sysTicks = (tickCounter - lastTickCounter);
delta = 1 + sysTicks/THRESHOLD;
Знак дельты, соответственно определяется тем, в какую сторону крутится енкодер.
Что значит «не используешь»? Для расчёта относительного перемещения всё удобно и подход не требует никаких ручных проверок. Абсолютное значение мне сейчас и даром не надо, хотя и считается. Если надо ограничить его заданными пределами — никто не запрещает пользоваться математикой.
Мм, частоту опроса можно и увеличить, это не проблема. А за 0.1 секунду направление столько кардинально поменять не так и просто =D А если и поменяется, интегральное значение как раз такое и будет.
Я со скоростью не заморачивался, ибо мне не надо =)
Если цель — получение достаточно точного и актуального значения скорости, то да, считать надо время между фронтами… Впрочем, никто не мешает повесить прерывания по фронтам параллельно таймеру Оо
Всё зависит от цели же.
Мм, частоту опроса можно и увеличить, это не проблема. А за 0.1 секунду направление столько кардинально поменять не так и просто =D А если и поменяется, интегральное значение как раз такое и будет.
Я со скоростью не заморачивался, ибо мне не надо =)
Если цель — получение достаточно точного и актуального значения скорости, то да, считать надо время между фронтами… Впрочем, никто не мешает повесить прерывания по фронтам параллельно таймеру Оо
Всё зависит от цели же.
- teplofizik
- 18 апреля 2013, 16:16
- ↑
- ↓
Единственное, что сейчас мне не нравится, так это то, что он за один щелчок может посчитать две единицы вместо одной (в зависимости от чего-то неизвестного).
У моего энкодера (РЕС12, буквы не помню, а на самом не напИсано) на один щелчок значение таймера меняется на 2. Т.е., видимо, устойчивые состояния энкодера либо оба ключа замкнуты, либо разомкнуты.
Ei= TIM3->CNT >> 1 ;
Ed = Ej-Ei;
Ej=Ei;
if (Ed > 0) MenuSetEvent(MENU_EVENT_CW);
if (Ed<0) MenuSetEvent(MENU_EVENT_CUW);
Это зависит от параметров работы таймера) Я тоже могу настроить, чтобы менялось за шаг и на два, и на четыре =)
- teplofizik
- 18 апреля 2013, 16:19
- ↑
- ↓
Кстати, надо попробовать. И фильтрацию тоже… не зря ж там она предусмотрена.з.
- teplofizik
- 18 апреля 2013, 16:22
- ↑
- ↓
Самому не нравиться, но в данном случае настройка оставлена для однотипности, а вот работа с линиями в режиме «дергать ножкой» мне больше нравятся макросы, так как функция увеличивает время выполнения данных операций. F4z более поздняя разработка, много чего улучшили.
У меня нет таких проектов, где вызов функции опечалил бы меня своей медлительностью, так что пофиг на десяток лишних тактов =D Всё в угоду изоляции от cmsis
Ага, после возвращения к F10x их корявость прям режет глаз.
Ага, после возвращения к F10x их корявость прям режет глаз.
- teplofizik
- 18 апреля 2013, 11:54
- ↑
- ↓
При чем здесь CMSIS? не понял...
Ну например при эмуляции SPI, сильно будет заметно. Я иногда использую его в проектах.
Ну например при эмуляции SPI, сильно будет заметно. Я иногда использую его в проектах.
При том, что если в основном коде использовать макросы, то придётся в список заголовков добавлять
#include <stm32fxxxxxxx.h>
Чего я делать не хочу =) Количество файлов, которые надо исправлять при переносе должно стремиться к минимуму Оо
Если эмулировать скоростные интерфейсы — да, функции и я не буду использовать =) Хотя, повторюсь, в стм до такого у меня ещё не доходило. Периферии обычно там дофига… Если только в младших контроллерах АВР, где с этим потяжелее…
#include <stm32fxxxxxxx.h>
Чего я делать не хочу =) Количество файлов, которые надо исправлять при переносе должно стремиться к минимуму Оо
Если эмулировать скоростные интерфейсы — да, функции и я не буду использовать =) Хотя, повторюсь, в стм до такого у меня ещё не доходило. Периферии обычно там дофига… Если только в младших контроллерах АВР, где с этим потяжелее…
- teplofizik
- 18 апреля 2013, 12:46
- ↑
- ↓
Ага, именно так. У меня есть минимальный набор файлов драйверов: gpio.h (вход, выход, линию вверх, линию вниз, что там на линии), timer.h (как раз для вызовов функций с заданной периодисностью), spi.h (отправить байт, отправить буфер), i2c.h (отправить буфер тому-то, прочитать оттуда-то), и т.д, которые реализуют периферию и протоколы так, как им хочется.
Программно ли, аппаратно ли, как хотят и с какими хотят внешними библиотеками и CMSIS.
Весь основной код работает только через них и к левым библиотекам периферии (CMSIS/SPL и т.д.) обращаться права не имеет. Хотя, конечно, есть и дополнительные уровни абстракции, например, для кнопок, для светодиодов. Чтоб обращаться к ним просто по номерам, а не (порт-вывод). Ну и чтоб там был антидребезг у кнопок, автоповтор нажатия, моргание у светодиодов и прочая неважная для основного кода ерунда.
Не, я через Keil, хотя тот же код можно и через gcc без особых проблем скомпилить. Компиляторозависимые конструкции — бе. За что и не люблю авр с их PROGMEM.
Программно ли, аппаратно ли, как хотят и с какими хотят внешними библиотеками и CMSIS.
Весь основной код работает только через них и к левым библиотекам периферии (CMSIS/SPL и т.д.) обращаться права не имеет. Хотя, конечно, есть и дополнительные уровни абстракции, например, для кнопок, для светодиодов. Чтоб обращаться к ним просто по номерам, а не (порт-вывод). Ну и чтоб там был антидребезг у кнопок, автоповтор нажатия, моргание у светодиодов и прочая неважная для основного кода ерунда.
Не, я через Keil, хотя тот же код можно и через gcc без особых проблем скомпилить. Компиляторозависимые конструкции — бе. За что и не люблю авр с их PROGMEM.
- teplofizik
- 18 апреля 2013, 13:07
- ↑
- ↓
Если я правильно понял вашу логику, полная аналогия моему решению.
Под каждый мк вы пишите свои spi.h, gpio.h, i2c.h и т.д.
Однако я использую CMSIS он как раз и задумывался для абстрагирования и стандартную либу для инициализации периферии. (исключение один только файл mcu_gpio.h, где мои макросы)
При таком подходе я спокойно с минимальными телодвижениями пишу сразу под два мк STM32L1 и STM32F4. Одно устройство в различных модификациях + куча версий плат.
Хотя конечно у каждого свой подход :)
Под каждый мк вы пишите свои spi.h, gpio.h, i2c.h и т.д.
Однако я использую CMSIS он как раз и задумывался для абстрагирования и стандартную либу для инициализации периферии. (исключение один только файл mcu_gpio.h, где мои макросы)
При таком подходе я спокойно с минимальными телодвижениями пишу сразу под два мк STM32L1 и STM32F4. Одно устройство в различных модификациях + куча версий плат.
Хотя конечно у каждого свой подход :)
Ненене, CMSIS просто описывает ядро и регистры как они есть.
SPL всего лишь даёт более человекочитаемую оболочку над этими регистрами.
Для переноса между контроллерами это не очень подходит. Привожу в пример то же GPIO от f1 и f4. Оно разное =D Если переезжать на LPC, так там стмовская библиотека вовсе бесполезна.
А так функции одни и те же (spi_Send, gpio_DigitalOutput) и всем пофиг, какой там контроллер. Драйвера переделывать, конечно, придётся, но это и неизбежно. Зато всё, требующее переделки локализовано вот так.з.
Но вообще я повспоминал, и понял, что в последний месяц я похожим образом описываю настройки периферии =) Для того же энкодера: ссылка на структуру таймера, какие выводы используются, какой номер альтернативной функции и т.д. Всё в одной структуре в начале файла. не макрос, конечно, но тоже всё вместе и рядом. И если для энкодера это одна запись, то для ШИМа их может быть вовсе штук тридцать =)
А так да, аналогично. Только что не через макросы =)
SPL всего лишь даёт более человекочитаемую оболочку над этими регистрами.
Для переноса между контроллерами это не очень подходит. Привожу в пример то же GPIO от f1 и f4. Оно разное =D Если переезжать на LPC, так там стмовская библиотека вовсе бесполезна.
А так функции одни и те же (spi_Send, gpio_DigitalOutput) и всем пофиг, какой там контроллер. Драйвера переделывать, конечно, придётся, но это и неизбежно. Зато всё, требующее переделки локализовано вот так.з.
Но вообще я повспоминал, и понял, что в последний месяц я похожим образом описываю настройки периферии =) Для того же энкодера: ссылка на структуру таймера, какие выводы используются, какой номер альтернативной функции и т.д. Всё в одной структуре в начале файла. не макрос, конечно, но тоже всё вместе и рядом. И если для энкодера это одна запись, то для ШИМа их может быть вовсе штук тридцать =)
{ TIM1, 3, true, RCC_APB2ENR_TIM1EN, &RCC->APB2ENR, 1, {PORTA, 10} }, // PA10 - TIM1_CH3
{ TIM1, 4, true, RCC_APB2ENR_TIM1EN, &RCC->APB2ENR, 1, {PORTA, 11} }, // PA11 - TIM1_CH4
{ TIM2, 1, false, RCC_APB1ENR_TIM2EN, &RCC->APB1ENR, 1, {PORTA, 15} }, // PA15 - TIM2_CH1
{ TIM2, 2, false, RCC_APB1ENR_TIM2EN, &RCC->APB1ENR, 1, {PORTB, 3} }, // PB3 - TIM2_CH2
А так да, аналогично. Только что не через макросы =)
- teplofizik
- 18 апреля 2013, 13:49
- ↑
- ↓
Да вообще, множество алгоритмов же можно представить в платформонезависимом виде.
Хотя вчера вечером я вот начал переносить код с STM32F4Discovery на STM32F105, потому пишу в этом контексте. Тут заголовочные файлы тоже отличаются =)
Хотя вчера вечером я вот начал переносить код с STM32F4Discovery на STM32F105, потому пишу в этом контексте. Тут заголовочные файлы тоже отличаются =)
- teplofizik
- 18 апреля 2013, 12:58
- ↑
- ↓
Лучше залей на местный сервер картинки, чтобы опять не отвалилось. Оно умеет прямо по http их утягивать с другого сервера.
Чтобы не отвалилось опять. Поскольку картинки важная часть статьи и бессмысленны без нее — лучше хранить их в той же корзине, что и статью.
для новых записей оно и понятно, но вот перелапачивать старые (особенно учитывая говеность редактора) это лишено смысла на мой взгляд.
Более правильно было бы реализовать данный механизм автоматом, в том числе тот же кат, на который напарываются больше половины писальщков.
Более правильно было бы реализовать данный механизм автоматом, в том числе тот же кат, на который напарываются больше половины писальщков.
обработка энкодера без таймера и прерываний, с гистерезисом поворота, антидребезгом, и генерацией событий по повороту/нажатию. для нормальной работы хватает частоты опроса 100 Гц.
хедер:
код:
хедер:
// A/B/button bits
enum { ENC_A=1, ENC_B=2, ENC_AB=3, ENC_C=4 };
// events
enum { ENC_NONE, ENC_FORW, ENC_BACK, ENC_BUTT };
// AB counter reset threshold
#ifndef ENC_AB_RES
#define ENC_AB_RES 300
#endif
// short press counter
#ifndef ENC_BUTT_SHORT
#define ENC_BUTT_SHORT 80
#endif
// long press counter
#ifndef ENC_BUTT_LONG
#define ENC_BUTT_LONG 2000
#endif
struct enc_struct {
u8 en; // disable / enable
// A/B input
u8 prev, cur; // previous/current state
u8 inv, event; // =1 direction inversion, current event
s8 acc, threshold, dec; // step accumulator, threshold, decrement
u16 cnt; // current state counter
// button input
struct {
u8 prev, cur; // previous/current state
u16 cnt; // current state counter
} butt;
};
u8 enc_update( struct enc_struct *enc, u8 next );
код:
#include "enc.h"
// forward/backward next states
const u8 enc_forw[] = { 0b01, 0b11, 0b00, 0b10 };
const u8 enc_back[] = { 0b10, 0b00, 0b11, 0b01 };
u8 enc_update( struct enc_struct *enc, u8 next ){
enc->event = ENC_NONE; // reset event
// update AB state
enc->prev = enc->cur; // update previous AB state
enc->cur = next & ENC_AB; // get current AB state
if (enc->cur != enc->prev){ // changed ?
enc->cnt = 0;
// forward direction
if (enc->cur == enc_forw[enc->prev]){
if (enc->inv) enc->acc--; else enc->acc++;
}
// backward direction
else if (enc->cur == enc_back[enc->prev]){
if (enc->inv) enc->acc++; else enc->acc--;
}
// wrong sequence - reset accumulator
else enc->acc = 0;
// forward step
if (enc->acc >= enc->threshold){
enc->acc -= enc->dec;
enc->event = ENC_FORW;
}
// backward step
else if (enc->acc <= -enc->threshold){
enc->acc += enc->dec;
enc->event = ENC_BACK;
}
} else {
// no change - increment counter
if (enc->cnt != 0xFFFF) enc->cnt++;
// reset accumulator
if (enc->cnt == ENC_AB_RES) enc->acc = 0;
}
// update button state
enc->butt.prev = enc->butt.cur;
enc->butt.cur = !(next & ENC_C);
// button state change - reset counter
if (enc->butt.cur != enc->butt.prev){
enc->butt.cnt = 0;
} else { // not changed - increment counter
if (enc->butt.cnt != 0xFFFF) enc->butt.cnt++;
// gnenerate button press event
if ((enc->event == ENC_NONE) && enc->butt.cur && ((enc->butt.cnt == ENC_BUTT_SHORT)||(enc->butt.cnt == ENC_BUTT_LONG)))
enc->event = ENC_BUTT;
}
return enc->event;
}
Комментарии (69)
RSS свернуть / развернуть