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

Продолжаем разговор, сегодня очень кратенько о подключении энкодера к микроконтроллеру.
IMG_5311

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

Подключение
Работать энкодер будет совместно с таймером 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 для работы с энкодером
Основная задача при настройке таймера “подать” сигналы от энкодера на счетный вход таймера.
Посмотрим на функциональную схему (рассмотрим один из входов):
image
Входной сигнал (выходной для одной из линий энкодера) проходя через входной фильтр, поступает на детектор фронтов выход которого подключен к последовательной цепочке мультиплексоров, выход последнего поступает на счетный вход таймера.
Полной схемы работы энкодера в документации я не нашел, да она особо и не нужна, всё и так предельно просто.
Рассмотрю самый просто режим работы.
Первым делом разрешаем тактирование таймера 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);
	}
}


Счет происходит согласно алгоритму:
image
Обращаю внимание в данном режиме один шаг дает 4 тактовых сигнала, т.е. счетчик инкрементируется или декрементируется на 4.
Дальнейшие хитрости настройки за вами.
  • +6
  • 19 марта 2011, 19:24
  • ZiB

Комментарии (69)

RSS свернуть / развернуть
фига се подумал я се
зачем такие сложности?
0
по мне так наоборот все просто. подключили к входу счетчика -> настроили счетчик -> читаем значение счетчика. всю обработку делает за нас железка. куда проще-то
0
Спасибо за статью.
Для меня любая информация по STM32 представляет интерес. Собираю по крупицам. Поэтому сложно или не сложно в данном примере организована работа с энкодером это для меня дело десятое. Еще раз автору респект и уважуха за любую статью за STM32.
0
Статья хорошая. ZiB, когда думаешь работу с USB на STM32 писать?
0
спасибо. у меня есть мк STM32F103CBT6, они вроде бы с усб, но вот только плат под них нет. если есть мысли что можно на неё поставить, можно будет попробовать. хотя сам усб не прижился в моих конструкциях.
0
А что именно интересует про USB на STM32? Может смогу чем помочь? HID я с помощью стандартных библиотек на нем запустил.
0
Мне то же кажется что ни чего сложного там нет, просто не на чем проверить. Сейчас закончил программатор для плис разводить, думаю на этих выходных прикину макетку для STM32F103.
0
Меня интересует собственно как настроить и принять/отправить даные через USB на STM32 и есть ли готовые драйверы STM32 по типу как d2xx у FTDI, чтоб в LabView данные принимать на большой скорости. Показать все это дело можно, скажем, на примере логического анализатора ну или на чем нибудь другом, мне не принципиально. А сам я хочу осцил сделать с функциональным генератором и в конечном счере характериограф получить))) АРМ и конкретно STM32 в руки не брал. Ток MCS-51 и AVR.
0
Готовых драйверов со стороны компа вроде нету. Потому как он не только СОМ-порт изображать может, а все что запрограммируешь. Есть готовые библиотеки от производителя. С помощью них элементарно делается HID или CDC устройство например. Тогда никаких дров под винду не надо, работает через стандартные.
Насчет прикрутить к LV — у LV вроде есть какой-то модуль для работы с USB. Вроде даже в VESA-драйверах. Но что он может и как я не знаю, потому как почти не работал с этой оболочкой.
0
Я маленько ошибся)) d2xx это не драйвер, а библиотека dll. Если у STM32 библиотека есть, то это гуд. В общем то я уже делал простенький логический анализатор на AVR, подключал его через FT232 к компу. Со стороны ПК писал прогу в LV. Общались они на скорости 500 кбит/с. Использовал как раз библиотеку d2xx.dll, точнее примеры c сайта FTDI. С госами разберусь и напишу наверно статейку. Может кому-нибудь будет интересно посмотреть что из этого вышло)) Спасибо за инфу))
0
Для компа я DLL не видел. Библиотека есть для организации интерфейса на стороне контроллера.
0
При работе с AT91SAM7S на стороне компа я использовал библиотеку libusb (www.libusb.org), это открытый драйвер под винду и линукс.
0
Первый результат по демки для усб ziblog.ru/2011/03/25/demka-dlya-stm32f103cb/
+1
  • avatar
  • ZiB
  • 25 марта 2011, 16:02
Насколько я понимаю Вы использовали таймер в режиме счета при внешнем тактировании.
А почему не использовали режим работы «энкодер»?
0
Не верно поняли.
В статье рассмотрен режим — Encoder interface mode (стр. 322, RM0031 Rev 6).
0
Наверно RM0041 имели в виду? RM0031 это для STM8
0
Я бы еще большими буквами добавил, что режим энкодера не на всех каналах таймеров работает, а только на 1 и 2. Небольшие, но грабли. Можно нечаянно наступить :)
0
  • avatar
  • dee
  • 29 сентября 2011, 13:51
С детектора фронтов возьмем не инверсный, т.е. активный уровень высокий (для первого и второго входа):
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.
0
Кстати, для того чтоб изменить направления инкрементирования крутилки надо чтобы значение битов CC1P и CC2P были разное. Сначала просто каментил всю строку и думал че это у меня ниче не меняется)
0
Подскажите как с помощью еще одного таймера измерять период импульсом квадратурного счетчика (для определения скорости вращения)?
0
А можно ли сделать так, чтобы дойдя до максимума не перескакивало на 0 и наоборот?
0
Можно. Если не использовать аппаратную поддержку энкодера и считать по прерываниям. Например так (код под libmaple):

...
#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 и происходит посылка сообщения о том, что энкодер повернулся на один шаг в определенном направлении. Замечу, что в этом варианте легко сделать, например, реверс направления (может оказаться удобнее разводить плату) или реализовать нелинейную характеристику регулирования (например, шаг может зависеть от скорости вращения ручки энкодера). Ну и, понятно, повесить энкодер можно на практически любые ноги (но желательно, все-таки, что бы это были два последовательных пина одного порта).
0
Что за макросы такие: PIN_CONFIGURATION и т.д.? Гугл даёт ссылки на статьи с их использованием, но что это такое само по себе как-то уже не.

Хотя у меня другой подход к конфигурированию GPIO, но тем не менее, стало интересно, откуда такое =)
0
Это мои макросы можно взять из шаблонов:
ziblog.ru/2012/09/09/linii-vvoda-vyivoda-stm32-chast-3.html
в конце статьи.
Но там кажется я уже что-то правил, всё ни как не соберусь, нужно прогнать под все семейства и выложить в одном месте.
0
А, понятно =) *скачал и посмотрел пример*

Использовать я их буду вряд ли (я не люблю макросы в принципе, люблю изоляцию периферии от основного кода и предпочитаю использовать отдельные функции для настройки), но сам вариант реализации переносимости любопытен.

Но вообще немного удивил подход стм в этих контроллерах, что для энкодера надо использовать обычный GPIO-вход, а не альтернативную функцию таймера Оо В ф4 как-то всё гораздо логичнее с настройкой выводов ><
0
Тот вариант, который в топике использует как раз таймер. Чуть выше — мой вариант на прерываниях вместо таймера. Все зависит от конкретного применения. Скажем, для двигателей с датчиками удобнее таймер. Для юзер интерфейса — обработка по прерываниям.
0
Ненене, я про саму конфигурацию выводов. Используется для энкодера таймер, но функция выводов (оопс) — цифровой вход. А не альтернативная функция соответствующего таймера, что я ожидал.

Да вообще, если есть возможность, лучше использовать таймер, если он есть. А суррогаты — это уже от отсутствия соответстующего интерфейса =) Хотя я вот для отладки использую суррогат RTC на таймере, пока до самих часов не добрался… Интерфейс программный тот же, проге пофиг.
0
А не альтернативная функция соответствующего таймера, что я ожидал.
Подозреваю это из-за того, что откуда брать импульсы настраивается в таймере, а нога остается как есть.
Да вообще, если есть возможность, лучше использовать таймер, если он есть. А суррогаты — это уже от отсутствия соответстующего интерфейса =)
Я придерживался такого же мнения, пока не прикрутил енкодер для работы с интерфейсом и обплевался на неудобство такого решения. Главное неудобство в том, что таймер считает абсолютные значения, а в интерфейсе с юзером гораздо удобнее инкрементальные. Плюс таймер надо опрашивать. Плюс он теряет информацию о времени возникновения события. Все это, конечно, решаемо, но достаточно черезжопными методами и заметно усложняет код. А вот тот кусок, который я выше процитировал, позволяет делать все эти вещи легко и с минимумом кода.
0
Подозреваю это из-за того, что откуда брать импульсы настраивается в таймере, а нога остается как есть.
Вполне возможно. Но всё же не здорово.

Инкрементальные получаются несложно же, достаточно хранить предыдущее значение и вычитать его из текущего. Если делать так регулярно, то ок. Единственное, что сейчас мне не нравится, так это то, что он за один щелчок может посчитать две единицы вместо одной (в зависимости от чего-то неизвестного).з. Вся логика энкодера у меня представлена этой функцией:
// Регулярный опрос крутилки (десяток Гц)
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; }
        }
    }
}

Достаточно лаконично же =)
0
по сути там { return (TIM->CNT — (TIM_last = TIM->CNT));
Угу. Ага. И переполнение не забыть обработать. Причем в обе стороны. Получается в итоге вовсе не так просто и красиво.
Достаточно лаконично же =)
Если не считать опроса то да, сравнимо с кодом на прерываниях. Теперь добавить учет скорости поворота енкодера :)

P.S. я все эти фазы, которые есть в вашем варианте, прошел, а потом таки сделал на прерываниях.
0
И переполнение не забыть обработать.
Неа, при использовании типа int16_t и ARR=0xFFFF, это посчитается само собой.

Теперь добавить учет скорости поворота енкодера
Если частота опроса задана (например, как у меня 10 Гц), то скорость вычисляется просто.
Speed = (TIM->CNT — TIM_last) * Freq;

Например, если разница между двумя вызовами в 10 щелчков, а частота вызовов 10 Гц, то результат: 100 щелчков/сек.

Если меня модуль не устроит, сделаю на прерываниях, благо, коду пофиг =)
0
Неа, при использовании типа int16_t и ARR=0xFFFF, это посчитается само собой.
При такой настройке смысл в использовании таймера вообще утрачивается. Это значение уже напрямую нигде не используешь.
Если частота опроса задана (например, как у меня 10 Гц), то скорость вычисляется просто.

Speed = (TIM->CNT — TIM_last) * Freq;
Строго говоря нет. Разница за время между опросами могла измениться, скажем, от -20 до +30, а вы считаете, что она +10. К тому же гораздо интереснее использовать не скорость, а время между соседними изменениями счетчика, типа такого:


    sysTicks = (tickCounter - lastTickCounter);
    delta = 1 + sysTicks/THRESHOLD;

Знак дельты, соответственно определяется тем, в какую сторону крутится енкодер.
0
Что значит «не используешь»? Для расчёта относительного перемещения всё удобно и подход не требует никаких ручных проверок. Абсолютное значение мне сейчас и даром не надо, хотя и считается. Если надо ограничить его заданными пределами — никто не запрещает пользоваться математикой.

Мм, частоту опроса можно и увеличить, это не проблема. А за 0.1 секунду направление столько кардинально поменять не так и просто =D А если и поменяется, интегральное значение как раз такое и будет.
Я со скоростью не заморачивался, ибо мне не надо =)

Если цель — получение достаточно точного и актуального значения скорости, то да, считать надо время между фронтами… Впрочем, никто не мешает повесить прерывания по фронтам параллельно таймеру Оо

Всё зависит от цели же.
0
Всё зависит от цели же.
Вот и я о том. Для такой задачи использование таймера мне показалось неудобным (не только со стороны софта, жесткая привязка к конкретным ногам контроллера — тоже).
0
Поправочка надо что-то типа такого:
delta = 1 + THRESHOLD/sysTicks;
0
Единственное, что сейчас мне не нравится, так это то, что он за один щелчок может посчитать две единицы вместо одной (в зависимости от чего-то неизвестного).

У моего энкодера (РЕС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);

0
Это зависит от параметров работы таймера) Я тоже могу настроить, чтобы менялось за шаг и на два, и на четыре =)
0
Кстати, надо попробовать. И фильтрацию тоже… не зря ж там она предусмотрена.з.
0
Самому не нравиться, но в данном случае настройка оставлена для однотипности, а вот работа с линиями в режиме «дергать ножкой» мне больше нравятся макросы, так как функция увеличивает время выполнения данных операций. F4z более поздняя разработка, много чего улучшили.
0
У меня нет таких проектов, где вызов функции опечалил бы меня своей медлительностью, так что пофиг на десяток лишних тактов =D Всё в угоду изоляции от cmsis

Ага, после возвращения к F10x их корявость прям режет глаз.
0
При чем здесь CMSIS? не понял...
Ну например при эмуляции SPI, сильно будет заметно. Я иногда использую его в проектах.
0
При том, что если в основном коде использовать макросы, то придётся в список заголовков добавлять
#include <stm32fxxxxxxx.h>
Чего я делать не хочу =) Количество файлов, которые надо исправлять при переносе должно стремиться к минимуму Оо

Если эмулировать скоростные интерфейсы — да, функции и я не буду использовать =) Хотя, повторюсь, в стм до такого у меня ещё не доходило. Периферии обычно там дофига… Если только в младших контроллерах АВР, где с этим потяжелее…
0
Получается вы сами описываете периферию ?
Или вы пишите под IAR, там у них вроде свои костыли?
0
Ага, именно так. У меня есть минимальный набор файлов драйверов: gpio.h (вход, выход, линию вверх, линию вниз, что там на линии), timer.h (как раз для вызовов функций с заданной периодисностью), spi.h (отправить байт, отправить буфер), i2c.h (отправить буфер тому-то, прочитать оттуда-то), и т.д, которые реализуют периферию и протоколы так, как им хочется.
Программно ли, аппаратно ли, как хотят и с какими хотят внешними библиотеками и CMSIS.

Весь основной код работает только через них и к левым библиотекам периферии (CMSIS/SPL и т.д.) обращаться права не имеет. Хотя, конечно, есть и дополнительные уровни абстракции, например, для кнопок, для светодиодов. Чтоб обращаться к ним просто по номерам, а не (порт-вывод). Ну и чтоб там был антидребезг у кнопок, автоповтор нажатия, моргание у светодиодов и прочая неважная для основного кода ерунда.

Не, я через Keil, хотя тот же код можно и через gcc без особых проблем скомпилить. Компиляторозависимые конструкции — бе. За что и не люблю авр с их PROGMEM.
0
Если я правильно понял вашу логику, полная аналогия моему решению.
Под каждый мк вы пишите свои spi.h, gpio.h, i2c.h и т.д.
Однако я использую CMSIS он как раз и задумывался для абстрагирования и стандартную либу для инициализации периферии. (исключение один только файл mcu_gpio.h, где мои макросы)
При таком подходе я спокойно с минимальными телодвижениями пишу сразу под два мк STM32L1 и STM32F4. Одно устройство в различных модификациях + куча версий плат.
Хотя конечно у каждого свой подход :)
0
Ненене, CMSIS просто описывает ядро и регистры как они есть.
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 

А так да, аналогично. Только что не через макросы =)
0
Забыл спросить при каком переносе? между семействами или вообще между различными мк?
0
Да вообще, множество алгоритмов же можно представить в платформонезависимом виде.

Хотя вчера вечером я вот начал переносить код с STM32F4Discovery на STM32F105, потому пишу в этом контексте. Тут заголовочные файлы тоже отличаются =)
0
Выше писали, что энкодер работает только на TIM1 и TIM2, проверил на TIM2, 3, 4 ни каких проблем, на TIM5 думаю тоже. Механический энкодер даёт много ложных срабатываний из-за дребезга, проскакивают даже в режиме IC1F(IC2F) = 1111.
0
  • avatar
  • esv
  • 22 апреля 2013, 17:08
внимательнее: речь шла не о 1 и 2 таймерах, а о 1 и 2 каналах таймера. В даташите есть диаграмма устройства таймера, там см. TI1FP1 и TI2FP2
0
На F105 заработал нормально на таймере TIM4.
Вообще напрягает, что уважаемый автор не удосужился указать, какой именно STM32 он использует. Сиди гадай на кофейной гуще, о чём вообще речь.
0
Это сейчас их расплодилось великое множество, четыре года назад была по сути только плата STM32VL-Discoery (STM32F100).
Эта запись одна из небольшого цикла статей для данной платы.
Но в целом согласен с замечанием.
0
Гм, картинки отвалились.
0
  • avatar
  • Vga
  • 13 апреля 2014, 20:39
Спасибо, исправил.
0
За картинки спасибо. Гораздо понятнее стало :).
0
не за что :)
0
тут по сути мой копи-паст с моего же блога, можете там ещё глянуть ziblog.ru/category/mikrokontrolleryi/stm32
0
Посмотрим :)
0
Лучше залей на местный сервер картинки, чтобы опять не отвалилось. Оно умеет прямо по http их утягивать с другого сервера.
0
за чем мне эта «пустая» трата времени?
0
Чтобы не отвалилось опять. Поскольку картинки важная часть статьи и бессмысленны без нее — лучше хранить их в той же корзине, что и статью.
0
для новых записей оно и понятно, но вот перелапачивать старые (особенно учитывая говеность редактора) это лишено смысла на мой взгляд.
Более правильно было бы реализовать данный механизм автоматом, в том числе тот же кат, на который напарываются больше половины писальщков.
0
Перелопачивал же сейчас, чтобы ссылки восстановить. Можно было их сразу и перезагрузить.
0
просто линк сменить как раз таки очень просто. тупая замена хоста (old.ziblog.ru -> ziblog.ru).
а загрузить на текущий сервер вот так просто я не знаю как, знаешь поделись секретом :)
0
Не знаю. Но четыре картинки и вручную загрузить минуту-две займет.
0
ну вот и я не знаю, а текущий метод слишком долго…
0
мне проще удалить все записи, быстрее будет
0
ах ты ж, не удаляет :(
0
обработка энкодера без таймера и прерываний, с гистерезисом поворота, антидребезгом, и генерацией событий по повороту/нажатию. для нормальной работы хватает частоты опроса 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;
}
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.