Кто любит RISC в жизни, заходим, не стесняемся.
Ответить

STM32 библиотека работы с ИК пультом (NEC)

Пн мар 07, 2016 21:26:17

Здравствуйте, читатели форума:)
13 страниц форума и ни одного вопроса по управлению с пульта ДУ=)
Уже много времени хотелось разобраться и найти библиотеку декодирования сигналов NEC пультов.
Прочитал теорию, вроде бы всё хорошо и понятно.
С помощью программы RCExplorer (кстати, она сейчас бесплатная. Связался с её автором и он через некоторое время сделал её бесплатной) и давным-давно собранного WinLirc убедился что мой пульт использует NEC протокол. Даже не расширенный.
НО, найденная библиотека почему-то не работает корректно. Зато, как ни странно, декодирует сигналы протокола SANYO (похож на NEC).
Прикладываю "осциллограммы" кнопок пультов. В коде реализуется прием 32 байт. У sanyo их явно больше, но принимаются же!!! а у NEC идет какое-то недополучение (байт 10 не успевают проскочить)
SANYO.PNG
(13.89 KiB) Скачиваний: 1137
NEC.PNG
(12.51 KiB) Скачиваний: 845

Может посмотрите в чём дело? Прикладываю проект в CooCox. Или поделитесь библиотекой для декодирования NEC протокола?

Описание проекта:
Используется таймер TIM2, ИК-фотоприемник подключен по типовой схеме, сигнальный вывод к ножке GPIOA0 (TIM2_CH1).
К отладочной плате подключен 1602 дисплей по четырёхбитной шине.



Добавлю. Принятый код с SANYO на картинке RCExplorer
SANYO1.png
(20.57 KiB) Скачиваний: 553

Как мы видим, теряются 10 байт. Что делать - не знаю)
Вложения
RemoteC.rar
CooCox IDE 1.7.5
(265.2 KiB) Скачиваний: 336

Re: STM32 библиотека работы с ИК пультом (NEC)

Вт мар 08, 2016 01:29:42

Очень странная библиотека, в своё время у меня не завелась, пришлось стряпать своё.
Вся обработка в прерывании по месту события, в последнем правильно принятом бите - вызов задачи декодирования из спячки. Тупой перебор до совпадения.
При зажатой кнопке и верности принимаемых таймингов - декодируется повтор. В случае посторонних помех - декодирование срывается но не искажается, то-есть ложных срабатываний - ноль. Чего собственно мне и требовалось.
Для иной частоты мк -проще менять TIM2->PSC = 720; в пересчёте процентах от 72мгц.
Ось своя собственная,http://forum.ixbt.com/topic.cgi?id=48:11735. Хотя здесь она применяется всего в одном месте sTask_wake(&table_simvol_bits) , можно заменить на свой метод. Но не рекомендую использование без оси - тупой метод перебора жрёт много времени мк.
Код:
/*
    /// таймер TIM2 72мгц - IR , вход CH1 + CH4
    TIM2->PSC = 720;
    TIM2->CCMR1|=TIM_CCMR1_CC1S_0 | TIM_CCMR1_IC2F_3; //TI1FP1+фильтр
    TIM2->CCMR1 |= (TIM_CCMR1_OC2M_2|TIM_CCMR1_OC2M_1);
    TIM2->CCMR2|=TIM_CCMR2_CC4S_1 | TIM_CCMR2_IC4F_3; //TI1FP2+фильтр
    TIM2->CCR2 = 15800 ;   // детектор тишины
    TIM2->SMCR = (5<<4) | 4; //TI1FP1+Reset Mode
    TIM2->CCER|= TIM_CCER_CC1E | TIM_CCER_CC4P | TIM_CCER_CC4E | TIM_CCER_CC2E;
    TIM2->DIER = TIM_DIER_CC1IE | TIM_DIER_CC2IE; // прерывание от захвата и тишины
    TIM2->CR1 = TIM_CR1_CEN;
*/

volatile uint32_t data_IR;
volatile char *time2_data;
_bitFlag table_simvol_bits;
_bitFlag time2_data_IR_RE;

#define IR_ZERO_L       56  // время передачи нуля

void TIM2_IRQHandler (void)
{
    uint16_t tmp_H;
    uint16_t tmp_L;
    static uint8_t nomer_bit = 32;
    static uint8_t status_bit = 0;
    static uint32_t time2_data_IR;
    if (TIM2->SR & TIM_SR_CC2IF)
        { TIM2->SR = 0; nomer_bit = 32;status_bit = 0; return;}
        else
        {
            tmp_L = TIM2-> CCR4; tmp_H = (TIM2->CCR1) - tmp_L;
            if (status_bit ==0 )
            { if ((tmp_H < ((uint16_t)IR_ZERO_L * 1.25)) && (tmp_L > ((uint16_t)IR_ZERO_L * 7.75))
                        && (tmp_L < ((uint16_t)IR_ZERO_L * 8.25)))
                        { nomer_bit = 32; time2_data_IR =0;status_bit++;}else; return; }else;
            if (status_bit == 1 )
            { if (tmp_H < ((uint16_t)IR_ZERO_L * 1.25))
                { if (tmp_L < ((uint16_t)IR_ZERO_L * 1.25)) nomer_bit--;
                        else
                            { if (tmp_L < ((uint16_t)IR_ZERO_L * 3.25))
                                { nomer_bit--; time2_data_IR |= (uint32_t) 1 << nomer_bit;} else;
                            }
                } else status_bit = 0;
            if (nomer_bit == 0)
                    {data_IR = time2_data_IR; sTask_wake(&table_simvol_bits); status_bit++; return;} else;
            }else;
            if (status_bit == 2 ) // H903-L4000; (H56-L224; H900-L9627;) // (H900-L4000; H56-L228) lg
            {   if ((tmp_H > ((uint16_t)IR_ZERO_L * 15.75)) && (tmp_L > ((uint16_t)IR_ZERO_L * 69))
                    && (tmp_L < ((uint16_t)IR_ZERO_L * 73))) status_bit++;
                    else status_bit = 0;  return;
            }else;
            if (status_bit == 3 )
            {   if ((tmp_H < ((uint16_t)IR_ZERO_L * 1.25)) && (tmp_L > ((uint16_t)IR_ZERO_L * 3.75))
                    && (tmp_L < ((uint16_t)IR_ZERO_L * 4.25))) status_bit++;
                    else status_bit = 0;  return;
            }else;
            if (status_bit == 4 )
            {   if ((tmp_H > ((uint16_t)IR_ZERO_L * 15.75)) && (tmp_L > ((uint16_t)IR_ZERO_L * 166))
                    && (tmp_L < ((uint16_t)IR_ZERO_L * 176)))
                    {status_bit = 3; time2_data_IR_RE = 1;}
                    else status_bit = 0;  return;
            }else;

        }

}

void table_simvol (void)
{
    for(;;)
 {
    if (data_IR == 0)  {time2_data = "..........";  } else;
  if (( data_IR >> 16) == 0x00FF) // пульт от плеера
  {
    data_IR = data_IR & 0xFFFF;
    if (data_IR == 0x38c7)  {time2_data = "POWER";  } else;
    if (data_IR == 0xd22d)  {time2_data = "LCD_ON/OF"; } else;
    if (data_IR == 0xda25)  {time2_data = "MODE";  } else;
    if (data_IR == 0x20df)  {time2_data = "3D";  } else;
    if (data_IR == 0x5aa5)  {time2_data = "PROG";  } else;
    if (data_IR == 0x50af)  {time2_data = "MITE";  } else;
    if (data_IR == 0x00ff)  {time2_data = "REPEAT"; } else;
    if (data_IR == 0x08f7)  {time2_data = "A-B";  } else;
    if (data_IR == 0xa25d)  {time2_data = "1";  } else;
    if (data_IR == 0xe817)  {time2_data = "2";  } else;
    if (data_IR == 0x48b7)  {time2_data = "3";  } else;
    if (data_IR == 0xb847)  {time2_data = "TITLE";  } else;
    if (data_IR == 0x28d7)  {time2_data = "4";  } else;
    if (data_IR == 0xe01f)  {time2_data = "5";  } else;
    if (data_IR == 0xb04f)  {time2_data = "6";  } else;
    if (data_IR == 0x1ae5)  {time2_data = "ANGLE";  } else;
    if (data_IR == 0xd827)  {time2_data = "7";  } else;
    if (data_IR == 0x926d)  {time2_data = "8";  } else;
    if (data_IR == 0x22dd)  {time2_data = "9";  } else;
    if (data_IR == 0x3ac5)  {time2_data = "SUBTITLE";  } else;
    if (data_IR == 0x9867)  {time2_data = "+10";  } else;
    if (data_IR == 0x7887)  {time2_data = "0";  } else;
    if (data_IR == 0x7a85)  {time2_data = "MENU";  } else;
    if (data_IR == 0xc837)  {time2_data = "OSD";  } else;
    if (data_IR == 0xf00f)  {time2_data = "ZOOM";  } else;
    if (data_IR == 0x728d)  {time2_data = "CH+";  } else;
    if (data_IR == 0x629d)  {time2_data = "PBC";  } else;
    if (data_IR == 0x9a65)  {time2_data = "SLOW";  } else;
    if (data_IR == 0x30cf)  {time2_data = "<<-";  } else;
    if (data_IR == 0x609f)  {time2_data = "ENTER";  } else;
    if (data_IR == 0xa05f)  {time2_data = "->>";  } else;
    if (data_IR == 0xc23d)  {time2_data = "RETURN";  } else;
    if (data_IR == 0xf20d)  {time2_data = "AUDIO";  } else;
    if (data_IR == 0xb24d)  {time2_data = "CH-";  } else;
    if (data_IR == 0x32cd)  {time2_data = "GOTO";  } else;
    if (data_IR == 0xe21d)  {time2_data = "SETUP";  } else;
    if (data_IR == 0x10ef)  {time2_data = "VOL+";  } else;
    if (data_IR == 0x42bd)  {time2_data = "<<";  } else;
    if (data_IR == 0x02fd)  {time2_data = ">>";  } else;
    if (data_IR == 0xc03f)  {time2_data = "STOP";  } else;
    if (data_IR == 0x0af5)  {time2_data = "VOL-";  } else;
    if (data_IR == 0x807f)  {time2_data = "|<<";  } else;
    if (data_IR == 0x40bf)  {time2_data = ">>|";  } else;
    if (data_IR == 0x8877)  {time2_data = "PLEY";  } else;
  }else;


  if (( data_IR >> 16) == 0x20DF) // пульт от телека LG
  {
    data_IR = data_IR & 0xFFFF;
    if (data_IR == 0x10ef)  {time2_data = "POWER";  } else;
    if (data_IR == 0x0ff0)  {time2_data = "TV/RADIO";  } else;
    if (data_IR == 0x9c63)  {time2_data = "SUBTITLE";  } else;
    if (data_IR == 0x06f9)  {time2_data = "AD(PIP/*)";  } else;
    if (data_IR == 0xd926)  {time2_data = "TV/PC";  } else;
    if (data_IR == 0xd02f)  {time2_data = "INPUT";  } else;
    if (data_IR == 0x8877)  {time2_data = "1";  } else;
    if (data_IR == 0x48b7)  {time2_data = "2";  } else;
    if (data_IR == 0xc837)  {time2_data = "3";  } else;
    if (data_IR == 0x28d7)  {time2_data = "4";  } else;
    if (data_IR == 0xa857)  {time2_data = "5";  } else;
    if (data_IR == 0x6897)  {time2_data = "6";  } else;
    if (data_IR == 0xe817)  {time2_data = "7";  } else;
    if (data_IR == 0x18e7)  {time2_data = "8";  } else;
    if (data_IR == 0x9867)  {time2_data = "9";  } else;
    if (data_IR == 0xca35)  {time2_data = "LIST";  } else;
    if (data_IR == 0x08f7)  {time2_data = "0";  } else;
    if (data_IR == 0x58a7)  {time2_data = "Q.VIEW";  } else;
    if (data_IR == 0x40bf)  {time2_data = "VOL+";  } else;
    if (data_IR == 0x7887)  {time2_data = "FAV";  } else;
    if (data_IR == 0x00ff)  {time2_data = "CH+";  } else;
    if (data_IR == 0xd52a)  {time2_data = "GUIDE";  } else;
    if (data_IR == 0xc03f)  {time2_data = "VOL-";  } else;
    if (data_IR == 0x906f)  {time2_data = "MITE";  } else;
    if (data_IR == 0x807f)  {time2_data = "CH-";  } else;
    if (data_IR == 0x4fb)  {time2_data = "TEXT";  } else;
    if (data_IR == 0x55aa)  {time2_data = "INFO";  } else;
    if (data_IR == 0x847b)  {time2_data = "T.OPT";  } else;
    if (data_IR == 0xc23d)  {time2_data = "SETTINGS";  } else;
    if (data_IR == 0x02fd)  {time2_data = "TOP";  } else;
    if (data_IR == 0xa25d)  {time2_data = "Q.MENU";  } else;
    if (data_IR == 0xe01f)  {time2_data = "LEFT";  } else;
    if (data_IR == 0x22dd)  {time2_data = "OK";  } else;
    if (data_IR == 0x609f)  {time2_data = "RIGHT";  } else;
    if (data_IR == 0x14eb)  {time2_data = "BACK";  } else;
    if (data_IR == 0x827d)  {time2_data = "BOTTOM";  } else;
    if (data_IR == 0xda25)  {time2_data = "EXIT";  } else;
    if (data_IR == 0x7e81)  {time2_data = "smplink";  } else;
    if (data_IR == 0xbd42)  {time2_data = "RES/*";  } else;
    if (data_IR == 0x8d72)  {time2_data = "STOP";  } else;
    if (data_IR == 0xf10e)  {time2_data = "<<";  } else;
    if (data_IR == 0x0df2)  {time2_data = "PLEY";  } else;
    if (data_IR == 0x5da2)  {time2_data = "PAUSE";  } else;
    if (data_IR == 0x718e)  {time2_data = ">>";  } else;
    if (data_IR == 0x4eb1)  {time2_data = "RED";  } else;
    if (data_IR == 0x8e71)  {time2_data = "GREEN";  } else;
    if (data_IR == 0xc639)  {time2_data = "YELLOW";  } else;
    if (data_IR == 0x8679)  {time2_data = "BLUE";  } else;
  }else;


    sTask_wait (&table_simvol_bits);
 }

Re: STM32 библиотека работы с ИК пультом (NEC)

Вт мар 08, 2016 11:12:41

трындец какой-то :(

Re: STM32 библиотека работы с ИК пультом (NEC)

Вт мар 08, 2016 13:35:17

У меня работает, а у вас?

Re: STM32 библиотека работы с ИК пультом (NEC)

Вт мар 08, 2016 13:46:03

AVI-crak писал(а):У меня работает, а у вас?

А у меня не работает :cry:
Правда, я про свой найденный код. С ОС сталкиваться ой как не хочется - это ж дебри. Если я с портами и таймерами еще не совсем освоился, куда уж ОС....
Ну вот почему он может пропускать 10 байт-то?

Код:
NEC remote power button
NC   0xF4 Инверсия комманды
C    0x0B Комманда
NA   0xF7 инверсия адреса
A    0x08 адрес
     received     bites           | LOST Bites   |
  F   4   |  0    B   |   F    X  |  X    X
1111 0100 | 0000 1011 | 1111 01LL | LLLL LLLL


Заполнение идет с конца, т.е. потерянные байты L находятся после стартовой посылки и паузы.
Вложения
NEC1.png
(14.82 KiB) Скачиваний: 368

Re: STM32 библиотека работы с ИК пультом (NEC)

Вт мар 08, 2016 14:50:53

Ican писал(а):
AVI-crak писал(а):У меня работает, а у вас?

А у меня не работает :cry:
Заполнение идет с конца, т.е. потерянные байты L находятся после стартовой посылки и паузы.

Ну собственно это и есть проблема, ик передача начинается со старших битов посылки. И первыми есно прилетают 16 бит кода фирмы - уникальный идентификатор принадлежности пульта к железяке, а уже после летит код команды кнопки. Игнорировать идентификатор не желательно, с его помощью можно различать разные пульты.

У меня void TIM2_IRQHandler (void) - это банальное прерывание из cmsis, и не имеет зависимости от ос. На выхлопе volatile uint32_t data_IR; - готовая посылка пульта.
Защита от сбоев, левых пультов, засветок и так далее. При любом отклонении от стандарта - процесс декодирования прерывается. Флагами выставляется состояние декодирования: table_simvol_bits - новые данные, time2_data_IR_RE - повтор команды (зажатая кнопка).

Весь список кнопок - это просто вариант, без ос нужно просто опрашивать флаг table_simvol_bits в бесконечном цикле, и проверять на совместимость чего там прилетело.
Хотя бесконечные циклы - огромное зло, и прежде всего в плане энергопотребления.

Алгоритм? - Дык его итак видно, в коде сплошные IF - куда уж проще.
Тут главное понять, что прилетевший на ик датчик тон 38кгц - событие в прошедшем времени, его нельзя отменить каким-либо образом. Это не радиоприёмник, где можно селекцией отстроится от помехи, тут всё топорно и прямолинейно. Есть тон - ик датчик роняет выход в ноль.
Собственно по этому принципу работает счётчик таймера, в момент прерывания по спаду - мы уже имеем на компараторах 1и4 - два значения, общее время и время единицы. Это тоже событие в прошедшем времени, остаётся просто проверить - насколько оно подходит под наши условия. Компаратор таймера CH2 для того - чтобы поймать событие: "кнопка больше не нажата". Это тоже в прошедшем времени, потому как общее время при не нажимании кнопок получается огромным, есть шанс пропустить стартовую посылку. Число в CH2 - это 1,5 максимального времени в посылке, а именно - задержка перед повтором символа "кнопка зажата". Если срабатывает компаратор CH2 - то процесс декодирования устанавливается в начальное время, и так до момента пока не прилетит первая корректная посылка.

Несколько свободных ик библиотек, в том числе и аурдино - все они измеряют время единицы и нуля в реальном времени, собственно мартышкин труд. Особо упорные измеряют время программными задержками. Причём эти библиотеки живут ещё со времён восмибитных пиков, практически без изменений.

Re: STM32 библиотека работы с ИК пультом (NEC)

Чт мар 10, 2016 21:39:57

Разобрался. Нашел место, где грабли лежат.
было
pos = -10; // change (ТЕРЯЛОСЬ 10 первых байт!!!!)
стало
pos = 0; // change принимается все на ура. Неоптимально, зато принимается.

Всем спасибо, расходимся)

Re: STM32 библиотека работы с ИК пультом (NEC)

Чт апр 07, 2016 13:49:45

Внесу свои 5 копеек. Мне тоже понадобилось подключить пульт ДУ. Я все сделал через захват таймера. У меня плата stm32 Mini, там был свободный вход PA8. Это канал захвата №1 таймера 1. Я видел ваши и другие библиотеки и мягко говоря в шоке. Декодирование пульта это довольно простая задача. У меня больше времени ушло на настройку таймера и прерывания.
Выкладываю здесь свой вариант декодирования (только основные моменты). На выходе имеем переменную NecEnd которая равна нулю или 32-битному коду принятому от пульта. После чтения переменной ее необходимо снова сбросить в 0.

Спойлерuint32_t NecEnd, NecB; // Эти переменные нужно объявить в начале
uint16_t NecData, NecDataOld, NecD;
uint8_t NecA;

// Пример обработки кода пульта
// Хотя бы 10 раз в секунду выполнять этот код
switch (NecEnd) {
case 0: // Ничего не принято
break;
case 2653519744: // 0x9E297F80
// Обработка клавиши "1"
NecEnd=0; // Обнуляем переменную NecEnd
break;
case 2653536064: // 0x9E29BF40
// Обработка клавиши "2"
NecEnd=0;
break;
case 2653503424: // 0x9E293FC0
// Обработка клавиши "3"
NecEnd=0;
break;
default:
NecEnd=0;
}

// ======================
// Настройка таймера
void TIM1_Configuration(void)
{
// создаём переменную (структуру) для определения режима работы таймера
TIM_TimeBaseInitTypeDef Timert1_Base;
TIM_ICInitTypeDef Timert1_ICI;

// TIM1 clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

// Базовые настройки таймера
TIM_TimeBaseStructInit(&Timert1_Base);
Timert1_Base.TIM_CounterMode = TIM_CounterMode_Up; // Выбираем режим работы счетчика
Timert1_Base.TIM_Prescaler = 720 - 1; // пред делитель
Timert1_Base.TIM_Period = 0xFFFF; // Коэффициент деления таймера
Timert1_Base.TIM_ClockDivision = TIM_CKD_DIV1; // Определяем частоту для фильтров (tDTS)
Timert1_Base.TIM_RepetitionCounter = 0; // Этот параметр только для Т1 и Т8
TIM_TimeBaseInit(TIM1, &Timert1_Base); // Записываем настройки в регистры

// Настройки каналов таймера на захват
Timert1_ICI.TIM_Channel = TIM_Channel_1; // Выбираем канал (1-4)
Timert1_ICI.TIM_ICPolarity = TIM_ICPolarity_Falling; // Определяем полярность входа
Timert1_ICI.TIM_ICSelection = TIM_ICSelection_DirectTI; // источник: напрямую со входа
Timert1_ICI.TIM_ICPrescaler = TIM_ICPSC_DIV1; // Значение предделителя канала (отключен)
Timert1_ICI.TIM_ICFilter = 15; // Значение фильтра от 0x0 до 0xF
TIM_ICInit(TIM1, &Timert1_ICI); // Записываем настройки в регистры

// На всякий случай сбрасываем флаги
TIM_ClearFlag(TIM1, TIM_IT_CC1);

// Разрешаем таймеру генерировать прерывание по захвату 1ый канал
TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);

// Разрешаем ядру принимать прерывания
NVIC_EnableIRQ(TIM1_CC_IRQn);

// ЗАПУСК таймера TIM1
TIM_Cmd(TIM1, ENABLE);
}

// ======================
// Настройка прерывания
void TIM1_CC_IRQHandler(void)
{
// Смотрим что прерывание от таймера именно по событию захвата 1-ого канала
if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET)
{
NecDataOld = NecData;
NecData = TIM_GetCapture1(TIM1); // Читаем значение из регистра захвата
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1); // Очищаем флаг этого прерывания
NecD=NecData-NecDataOld;

switch (NecA) {
case 0:
if (1115<NecD && 1135>NecD){ // повтор
// NecEnd=1; // Раскомментировать строку если нужен повтор
}
else
{
if (1340<NecD && 1360>NecD) NecA=1; // стартовый импульс
}
break;
case 1:
if (100<NecD && 235>NecD){
NecA=2;
}
else
{
NecA=0;
return; // ошибка выходим
}
default:
if (100<NecD && 235>NecD){
NecA++;
NecB = (NecB<<1)+NecD/175; // принимаем 32 бита данных
if (NecA==34){
NecEnd=NecB; // данные приняты успешно
NecA=0;
}
}
else
{
NecA=0;
return; // ошибка выходим
}
}
}
}

Re: STM32 библиотека работы с ИК пультом (NEC)

Чт апр 07, 2016 19:55:58

Andry_67
Нет защиты от помех.
Поиграйся двумя разными пультами одновременно, должно быть либо честное декодирование - либо сброс посылки.

Re: STM32 библиотека работы с ИК пультом (NEC)

Пт апр 08, 2016 13:36:05

Защита от помех есть. В строке
if (100<NecD && 235>NecD){
проверяется длительность между двумя импульсами и если длительность выходит за пределы то декодирование прекращается и ожидается новый стартовый импульс.
Поиграйся двумя разными пультами одновременно

А не надо одновременно жать на разные пульты, в этом случае ни у кого ничего работать не будет.

Re: STM32 библиотека работы с ИК пультом (NEC)

Сб май 14, 2022 21:44:41

Допрограмировался, в поисках нахожу своё старое.
В гитхаб теперь только через телефон, bitbucket.org просто стёр всё мои каракули, ещё парочка репозитариев успела пять раз в прыжке переобуться, а один уникальный склад кода - умудрился буквально сгореть, вместе с датацентром.
А тут даже пылью не покрылось.
ik_nec.с
Спойлер
Код:
/// IK protocol NEC
/// TIMx->PSC = clock frequency of the timer / 50526

#include <stdint.h>
#include "ik_nec.h"
#include "stm32f030x6.h"

#define IK_N 0xFFU /* unnamed china */
//#define IK_N 0xDFU /* LG */
#define DECODER ((uint16_t)((~IK_N)<<8)|IK_N)

volatile uint8_t ik_out;

const uint16_t range_z[] ={
    628,1167,35,66,35,66,634,1177,0,0,
    76,278,76,278,2890,12759,798,1483,2890,12759,949,1764};

void TIM3_IRQHandler (void) //220
{
    uint32_t tmp;
    static uint8_t status = 0;
    static uint8_t poz;
    static uint32_t stor;
    if ((TIM3->SR & (TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF))
         != (TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF)) goto uno;
    tmp = TIM3->CCR2;
    if (tmp < range_z[status << 1]) goto uno;
    if (tmp > range_z[(status << 1) + 1]) goto uno;
    switch (status)
    {
        case 0: status = 1; poz = 32; stor = 0; tmp = 10; break;
        case 1: tmp = stor << 1;
                if (TIM3->CCR1 > 170) tmp |= 1;
                stor = tmp; poz--; tmp = 12;
            if (poz == 0)
                {
                 /// watch raw IR code
                 /// stor = (~IK_N<<24)|(IK_N<<16)|(~ik_out<<8)|(ik_out)
                    if ((DECODER == (stor >> 16)) && ((((stor >> 8) ^ stor) & 0xFF) == 0xFF))
                    {
                        stor &= 0xFF; ik_out = stor; tmp = 14; status = 2;
                    }else goto uno;
                }; break;
        case 2: status = 3; tmp = 16; break;
        case 3: status = 2; tmp = 18; ik_out = stor; break;
        default: uno: status = 0; tmp = 20; break;
    };
    TIM3->SR = 0;
    TIM3->CCR3 = range_z[tmp++];
    TIM3->CCR4 = range_z[tmp];
};

void ik_tim_init(void)
{
    RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER |= _VAL2FLD(GPIO_MODER_MODER6, 2); //Alternate function mode
    GPIOA->AFR[0] |= _VAL2FLD(GPIO_AFRL_AFSEL6, 1);
    TIM3->PSC = 950; /// clock frequency of the timer / 50526 (509us = 51L)
    TIM3->CCMR1 = _VAL2FLD(TIM_CCMR1_CC1S, 1)|
                  _VAL2FLD(TIM_CCMR1_CC2S, 2);
    TIM3->CCER = _VAL2FLD(TIM_CCER_CC1P, 1)|
                 _VAL2FLD(TIM_CCER_CC2P, 0)|
                 _VAL2FLD(TIM_CCER_CC1E, 1)|
                 _VAL2FLD(TIM_CCER_CC2E, 1);
    TIM3->SMCR = _VAL2FLD(TIM_SMCR_TS, 5)| // Filtered Timer Input 1 (TI1FP1)
                 _VAL2FLD(TIM_SMCR_SMS, 4); // Reset Mode (TRGI)
    TIM3->CCMR2 = _VAL2FLD(TIM_CCMR2_OC3M,6)|
                 _VAL2FLD(TIM_CCMR2_OC4M,6);
    TIM3->CCR3 = 949; // 1357 start P
    TIM3->CCR4 = 1764;
    TIM3->DIER = TIM_DIER_CC1IE;
    TIM3->CR1 |= TIM_CR1_CEN;
    NVIC_EnableIRQ(TIM3_IRQn);
}

ik_nec.h
Спойлер
Код:
/// #include "ik_nec.h"


#ifdef _ik_nec_
 extern "C" {
#endif /* _ik_nec_ */

extern volatile uint8_t ik_out;

void ik_tim_init(void);


#ifdef _ik_nec_
}
#endif /* _ik_nec_ */
#define _ik_nec_


Написано для мелкого stm32f030f, требует таймера с поддержкой PWM Input и одним свободным контактом CH1. По протоколу NEC - необходимо выполнить реверс битов в каждом байте, с сохранением порядка байтов. Но мне лень.

Re: STM32 библиотека работы с ИК пультом (NEC)

Ср дек 14, 2022 18:22:21

AVI-crak, спасибо тебе добрый человек за рабочий код для cmsis!
Оно заработало.
Дефайны только дёрнул, не знаю можно так?
Спойлер
Код:
#define _VAL2FLD(field, value)    (((uint32_t)(value) << field ## _Pos) & field ## _Msk)
//
#define GPIO_MODER_MODER6_Pos           (12U)
#define GPIO_MODER_MODER6_Msk           (0x3U << GPIO_MODER_MODER6_Pos)        /*!< 0x00003000 */
#define GPIO_AFRL_AFSEL6_Pos            (24U)
#define GPIO_AFRL_AFSEL6_Msk            (0xFU << GPIO_AFRL_AFSEL6_Pos)         /*!< 0x0F000000 */
//
#define TIM_SMCR_TS_Pos           (4U)                                         
#define TIM_SMCR_TS_Msk           (0x7UL << TIM_SMCR_TS_Pos)                    /*!< 0x00000070 */
#define TIM_CCMR1_CC1S_Pos        (0U)                                         
#define TIM_CCMR1_CC1S_Msk        (0x3UL << TIM_CCMR1_CC1S_Pos)                 /*!< 0x00000003 */
#define TIM_CCMR1_CC2S_Pos        (8U)                                         
#define TIM_CCMR1_CC2S_Msk        (0x3UL << TIM_CCMR1_CC2S_Pos)                 /*!< 0x00000300 */
#define TIM_CCER_CC1P_Pos         (1U)                                         
#define TIM_CCER_CC1P_Msk         (0x1UL << TIM_CCER_CC1P_Pos)                  /*!< 0x00000002 */
#define TIM_CCER_CC2P_Pos         (5U)                                         
#define TIM_CCER_CC2P_Msk         (0x1UL << TIM_CCER_CC2P_Pos)                  /*!< 0x00000020 */
#define TIM_CCER_CC1E_Pos         (0U)                                         
#define TIM_CCER_CC1E_Msk         (0x1UL << TIM_CCER_CC1E_Pos)                  /*!< 0x00000001 */
#define TIM_CCER_CC2E_Pos         (4U)                                         
#define TIM_CCER_CC2E_Msk         (0x1UL << TIM_CCER_CC2E_Pos)                  /*!< 0x00000010 */
#define TIM_SMCR_SMS_Pos          (0U)                                         
#define TIM_SMCR_SMS_Msk          (0x7UL << TIM_SMCR_SMS_Pos)                   /*!< 0x00000007 */
#define TIM_CCMR2_OC3M_Pos        (4U)                                         
#define TIM_CCMR2_OC3M_Msk        (0x7UL << TIM_CCMR2_OC3M_Pos)                 /*!< 0x00000070 */
#define TIM_CCMR2_OC4M_Pos        (12U)                                       
#define TIM_CCMR2_OC4M_Msk        (0x7UL << TIM_CCMR2_OC4M_Pos)                 /*!< 0x00007000 */

В моей cmsis их не нашлось, надо бы обновить, только не разобрался откуда правильно взять.
И TIM3->PSC = 570; поставить для 48MHz пришлось.
У тебя 950 стоит, с таким значением попадаю только при 80MHz (RCC_CFGR_PLLMUL10) .
STM32F030K6T6.
А, вот ещё, сырой код (stor) без декодирования не получилось принять , поковыряю ещё...

Re: STM32 библиотека работы с ИК пультом (NEC)

Ср дек 14, 2022 20:53:16

На правах рекламы, можно сказать, тоже предложу вариант решения (подсмотрено на easyelectronics)
Спойлер
Код:
/**
 * @brief Class for IR receiver
 *
 * @tparam _Timer GP timer instance
 * @tparam _Pin Input pin
 * @tparam _Decoder Decoder
 */
template <typename _Timer, typename _Pin, typename _Decoder>
class IrReceiver
{
    using InputCaptureFalling = typename _Timer::InputCapture<0>;
    using InputCaptureRising = typename _Timer::InputCapture<1>;
    using TimeoutOCChannel = typename _Timer::OutputCompare<2>;

public:
    /**
     * @brief Init receiver
     *
     * @par Returns
     *  Nothing
     */
    static void Init()
    {
        _Timer::Enable();
        _Timer::SetPrescaler(_Timer::GetClockFreq() / 1000000 * 2 - 1); // 1us period
        _Timer::SetPeriod(0xffff);

        _Timer::SlaveMode::SelectTrigger(_Timer::SlaveMode::Trigger::FilteredTimerInput1);
        _Timer::SlaveMode::EnableSlaveMode(_Timer::SlaveMode::Mode::ResetMode);

        InputCaptureFalling::SetCapturePolarity(InputCaptureFalling::CapturePolarity::FallingEdge);
        InputCaptureFalling::SetCaptureMode(InputCaptureFalling::CaptureMode::Direct);
        InputCaptureFalling::EnableInterrupt();
        InputCaptureFalling::Enable();

        InputCaptureRising::SetCapturePolarity(InputCaptureRising::CapturePolarity::RisingEdge);
        InputCaptureRising::SetCaptureMode(InputCaptureRising::CaptureMode::Indirect);
        InputCaptureRising::EnableInterrupt();
        InputCaptureRising::Enable();

        TimeoutOCChannel::SetPulse(15'000);

        _Pin::Port::Enable();
        _Pin::template SetConfiguration<_Pin::Configuration::In>();
        _Pin::template SetPullMode<_Pin::PullMode::PullUp>();

        _Timer::Start();
    }

    /**
     * @brief Timer IRQ handler (call this method in TIMx_IRQHandler)
     *
     * @par Returns
     *  Nothing
     */
    static void IRQHandler()
    {
        static bool idleState = true;

        if(InputCaptureFalling::IsInterrupt()) {
            InputCaptureFalling::ClearInterruptFlag();

            uint16_t width = InputCaptureFalling::GetValue();
            uint16_t pulse = InputCaptureRising::GetValue();

            if(idleState) {
                idleState = false;
                TimeoutOCChannel::EnableInterrupt();
            }
            else {
                if(IsSimilar<_Decoder::StartWidth>(width) && IsSimilar<_Decoder::StartPulse>(pulse)) {
                    _Decoder::Start();
                }
                else if(IsSimilar<_Decoder::Width0>(width) && IsSimilar<_Decoder::Pulse0>(pulse)) {
                    _Decoder::Add0();
                }
                else if(IsSimilar<_Decoder::Width1>(width) && IsSimilar<_Decoder::Pulse1>(pulse)) {
                    _Decoder::Add1();
                }
                else {
                    idleState = true;
                }
            }
        }

        if (TimeoutOCChannel::IsInterrupt()) {
            TimeoutOCChannel::DisableInterrupt();
            TimeoutOCChannel::ClearInterruptFlag();

            if(!idleState) {
                idleState = true;
                _Decoder::Handle();
            }
        }
    }
private:
    /**
     * @brief Compare received value with decoder constant with given accuracy
     *
     * @tparam _TargetValue Constant
     * @param value Received value
     *
     * @return true If value ~ _TargetValue
     * @return false If value != _TargetValue
     */
    template<uint16_t _TargetValue>
    inline static bool IsSimilar(uint16_t value)
    {
        return ((_TargetValue * (100 - _Decoder::EpsilonInPercent) / 100) < value) && (value < (_TargetValue * (100 + _Decoder::EpsilonInPercent) / 100));
    }
};

/**
 * @brief NEC decoder
 */
class NecDecoder
{
public:
    static const uint16_t StartWidth = 13500;
    static const uint16_t StartPulse = 9000;

    static const uint16_t Width0 = 1125;
    static const uint16_t Pulse0 = 562;

    static const uint16_t Width1 = 2250;
    static const uint16_t Pulse1 = 562;

    static const uint16_t EpsilonInPercent = 20;

    /// Command
    /// @todo Add commands
    enum Command : uint16_t
    {
        NoCommand = 0
    };

    using Callback = std::add_pointer_t<void(Command command)>;

    /**
     * @brief Set the callback for command receive
     *
     * @param callback Callback
     *
     * @par Returns
     *  Nothing
     */
    static void SetCallback(Callback callback)
    {
        _callback = callback;
    }

    /**
     * @brief Start new frame
     *
     * @par Returns
     *  Nothing
     */
    static void Start()
    {
        _frame = 0;
    }

    /**
     * @brief Add bit 0 to frame
     *
     * @par Returns
     *  Nothing
     */
    static void Add0()
    {
        _frame >>= 1;
    }

    /**
     * @brief Add bit 1 to frame
     *
     * @par Returns
     *  Nothing
     */
    static void Add1()
    {
        _frame = (_frame >> 1) | 0x80000000;
    }

    /**
     * @brief End of frame (pause detect) handler
     *
     * @par Returns
     *  Nothing
     */
    static void Handle()
    {
        if ((_frame & 0xff000000) != ( (~(_frame) & 0x00ff0000) << 8)) {
            return;
        }

        if ((_frame & 0xff00) != (((~_frame) & 0x00ff) << 8)) {
            return;
        }

        uint16_t command = ((_frame & 0xff000000) >> 16) | ((_frame & 0xff00) >> 8);
       
        if(_callback)
            _callback(static_cast<Command>(command));
    }

private:
    static uint32_t _frame;
    static Callback _callback;
};

uint32_t NecDecoder::_frame;
NecDecoder::Callback NecDecoder::_callback;


Использовать максимально просто, вот пример:
Спойлер
Код:
#include <clock.h>
#include <iopins.h>
#include <timer.h>
#include <drivers/ir.h>

using namespace Zhele;
using namespace Zhele::Drivers;
using namespace Zhele::IO;
using namespace Zhele::Timers;
using namespace Zhele::Clock;

void ConfigureClock();
void ConfigureOutputPwm();
void ConfigureInputCapture();

using Receiver = IrReceiver<Timer4, Pb6, NecDecoder>;

int main()
{   
    ConfigureClock();

    Receiver::Init();
    NecDecoder::SetCallback([](NecDecoder::Command command) {
        // Do smth
    });

    for (;;)
    {
    }
}

void ConfigureClock()
{
    PllClock::SelectClockSource(PllClock::ClockSource::External);
    PllClock::SetMultiplier(9);
    Apb1Clock::SetPrescaler(Apb1Clock::Div2);
    SysClock::SelectClockSource(SysClock::Pll);
}

extern "C"
{
    void TIM4_IRQHandler()
    {
        Receiver::IRQHandler();
    }
}

Re: STM32 библиотека работы с ИК пультом (NEC)

Чт дек 15, 2022 01:02:21

Дефайны только дёрнул, не знаю можно так?

Раньше-бы посоветовал дёрнуть из кубика от st, но сейчас всё свежее приходится искать на гитхабе.
https://github.com/STMicroelectronics/c ... er/Include

Re: STM32 библиотека работы с ИК пультом (NEC)

Сб янв 21, 2023 22:04:22

А можно ли перекинуть на другой таймер?
Попробовал в лоб изменить на 14 или 16 ... не вышло.
Инит:
Код:
del

Обработчик:
Код:
del
Последний раз редактировалось zenon Вс янв 22, 2023 09:49:04, всего редактировалось 1 раз.

Re: STM32 библиотека работы с ИК пультом (NEC)

Сб янв 21, 2023 22:20:53

Если контроллер F100RB, то у таймера 16 всего 1 канал (у 15 их два, если что), так что эта реализация неприменима.

Re: STM32 библиотека работы с ИК пультом (NEC)

Сб янв 21, 2023 22:31:07

Хм. У меня F030K6T6, о 32-х ногах, те, если я правильно понял на PA2 можно повесить?
фиг там, нет у меня 15-го

Re: STM32 библиотека работы с ИК пультом (NEC)

Вс янв 22, 2023 01:22:41

А можно ли перекинуть на другой таймер?

При работе с st чипами необходимо иметь STM32Cube, скачивать любым доступным способом. Это приложение необходимо для наглядного выбора периферии, и ротации ног. Что-бы не гадать на чайной гуще, и не курить тонны документации в поиске доступного варианта решения.
Таймер должен уметь работать в режиме "PWM input", и иметь контакт на ногу чипа.

Re: STM32 библиотека работы с ИК пультом (NEC)

Вс янв 22, 2023 04:00:44

Таймер должен уметь работать в режиме "PWM input", и иметь контакт на ногу чипа.

У меня все ограничения в либах прописаны, по старинке обхожуcь без кубов :)
Спойлер
Код:
template<TimCh Channel>
class Remote
{
   using Tim = Channel::Timer;
   static constexpr uint32_t chNum = Channel::number();
   static_assert(chNum <= 3);
   static_assert(Tim::hasFeatures(TimFeature::SlaveCtrl), "TimFeature::SlaveCtrl is Required!");
   static_assert(chNum == 1 || Tim::hasFeatures(TimFeature::Xor), "TimFeature::Xor is Required!");
   .........
};

Remote<Tim3Ch2<PA7>> remote;

Скомпилируется только если подставить подходящий таймер, канал и пин.

Re: STM32 библиотека работы с ИК пультом (NEC)

Вс янв 22, 2023 09:58:40

AVI-crak, да ну его этот куб, у меня от него зуд... лучше покопашусь по-своему, задач каких-то у меня нет, так потихоньку то с одним, то с другим разобраться...
Ноги поменять глянув в даташит вообще труда не составляет.
В общем на TIM1 перекинул, теперь TIM3 освободился для энкодера.
TIM14 вроде умеет PWM Input, но на него не перекинуть.
Спойлер
Код:
void ik_tim_init(void) { 
    RCC->APB2ENR  |= RCC_APB2ENR_TIM1EN; // 1-й таймер
    //  TIM1_CH1 PA8 AF2
    RCC->AHBENR   |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER  |= _VAL2FLD(GPIO_MODER_MODER8, 2); // Ногу в альтернативную функцию
    GPIOA->AFR[1] |= _VAL2FLD(GPIO_AFRH_AFSEL8, 2);  //
    TIM1->PSC = 480;//340;//950;//400; //950; /// clock frequency of the timer / 50526 (509us = 51L)  // TIM prescaler register

    TIM1->CCMR1 = _VAL2FLD(TIM_CCMR1_CC1S, 1)|
                  _VAL2FLD(TIM_CCMR1_CC2S, 2);
    TIM1->CCER  = _VAL2FLD(TIM_CCER_CC1P, 1)|
                  _VAL2FLD(TIM_CCER_CC2P, 0)|
                  _VAL2FLD(TIM_CCER_CC1E, 1)|
                  _VAL2FLD(TIM_CCER_CC2E, 1);
    TIM1->SMCR  = _VAL2FLD(TIM_SMCR_TS, 5)| // Filtered Timer Input 1 (TI1FP1)
                  _VAL2FLD(TIM_SMCR_SMS, 4); // Reset Mode (TRGI)
    TIM1->CCMR2 = _VAL2FLD(TIM_CCMR2_OC3M,6)|
                  _VAL2FLD(TIM_CCMR2_OC4M,6);
    TIM1->CCR3 = 949;  //949; // 1357 start P
    TIM1->CCR4 = 1764;
    TIM1->DIER = TIM_DIER_CC1IE;
    TIM1->CR1 |= TIM_CR1_CEN;
    NVIC_EnableIRQ(TIM1_CC_IRQn);
}

Код:
void TIM1_CC_IRQHandler(void) { //
  uint32_t tmp;
  static uint8_t status = 0;
  static uint8_t poz;
  static uint32_t stor;
  if ((TIM1->SR & (TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF | TIM_SR_CC4IF))
       != (TIM_SR_CC1IF | TIM_SR_CC2IF | TIM_SR_CC3IF)) goto uno;
  tmp = TIM1->CCR2;
  if (tmp < range_z[status << 1]) goto uno;
  if (tmp > range_z[(status << 1) + 1]) goto uno;
  switch (status) {
    case 0: status = 1; poz = 32; stor = 0; tmp = 10; break;
    case 1: tmp = stor << 1;
            if (TIM1->CCR1 > 170) tmp |= 1;
            stor = tmp; poz--; tmp = 12;
            if (poz == 0) {
              /// watch raw IR code
              /// stor = (~IK_N<<24)|(IK_N<<16)|(~ik_out<<8)|(ik_out)
              //if ((DECODER == (stor >> 16)) && ((((stor >> 8) ^ stor) & 0xFF) == 0xFF)) {
                stor &= 0xFF; ik_out = stor; tmp = 14; status = 2;
              //} else goto uno;
            }; break;
    case 2: status = 3; tmp = 16; break;
    case 3: status = 2; tmp = 18; ik_out = stor; break;
    default: uno: status = 0; tmp = 20; break;
    };
    TIM1->SR = 0;
    TIM1->CCR3 = range_z[tmp++];
    TIM1->CCR4 = range_z[tmp];
}


Reflector писал(а):Скомпилируется только если подставить подходящий таймер, канал и пин.

Для TIM14 и TIM16 у меня компилируется норм, даже не ругается не на что, но не заводится... наверное всё-так потому что одноканальные, как сказали выше, только не понял зачем два канала?
Ответить