www.open-tager.ru http://open-tager.ru/forum/ |
|
Умный датчик. Smart sensor. http://open-tager.ru/forum/viewtopic.php?f=5&t=4949 |
Страница 3 из 17 |
Автор: | Alexies [ 26 дек 2016, 00:08 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Итак, по итогам первого дня работы: 1. Вот репозиторий: https://github.com/DAlexis/lasertag-smart-sensor . 2. Там есть проект STM32Cube, настроеный практически полностью. Тулчейн - gcc, cmake. Проект компилируется и прошивается. Перегенерация проекта в Кубе ничего не портит. 3. В проект добавлен newlib.c - так что нет проблем с принтфами и динамической памятью. Пока-что для отладки printf работает через UART1. Последовательный порт у контроллера всего один, а Cortex-M0 не умеют выводить текст через SWD 4. Написан ИК-приемник. Пакеты успешно принимаются, работа протестирована, вроде всё надежно. |
Автор: | Alexies [ 26 дек 2016, 00:27 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
LTagKirov писал(а): Alexies писал(а): И, разумеется, "тупые" датчики с одной зоной поражения всё равно будут поддерживаться в Caustic. Почему с одной, Каустик вроде позволяет подключить несколько групп проводных датчиков на несколько разных входов ?Да, все верно. Но вряд ли целесообразно поддерживать оба варианта "многозонности". Зоны поражения в текущем виде требуют место на плате под ключи и разъёмы. Это одна из причин разработки смарт-сенсоров. Мне представляется логичным оставить одну тупую зону + много умных. LTagKirov писал(а): ЗЫ. Схемы Каустика окончательной в варианте картинки доступной с планшетника так и не выложили. Да, все время забываю! Теперь выложил, ссылки на pdf-ки тут: http://ltcaustic.org/for-developers/schematics.html |
Автор: | Pingvin [ 26 дек 2016, 11:22 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Тоже реализовал приём пакета, но по своему - аппаратно таймером, как и предлагал. Использовал Таймер3 и ножку PA6. На Кокосе проект, библиотека SPL. Код: #include "stm32f0xx.h"
#include "stm32f0xx_crc.h" #include "stm32f0xx_dma.h" #include "stm32f0xx_exti.h" #include "stm32f0xx_flash.h" #include "stm32f0xx_gpio.h" #include "stm32f0xx_syscfg.h" #include "stm32f0xx_i2c.h" #include "stm32f0xx_pwr.h" #include "stm32f0xx_rcc.h" #include "stm32f0xx_rtc.h" #include "stm32f0xx_spi.h" #include "stm32f0xx_tim.h" #include "stm32f0xx_usart.h" #ifndef bool #define bool unsigned char #define true 1 #define false 0 #endif GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef timer3; GPIO_InitTypeDef port; TIM_ICInitTypeDef TIM_ICStructure; void init_pwm_TIM3(void){ RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); GPIO_StructInit(&port); port.GPIO_Mode = GPIO_Mode_AF;//GPIO_Mode_IN; port.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA, &port); // GPIO_PinAFConfig(GPIOA,GPIO_Pin_6,GPIO_AF_1); GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_1); TIM_TimeBaseStructInit(&timer3); timer3.TIM_Prescaler = /*72*/(SystemCoreClock/1000000)-1;//считаем микросекунды TIM_TimeBaseInit(TIM3, &timer3); TIM_ICStructure.TIM_Channel = TIM_Channel_1;//первый канал TIM_ICStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; // по заднему фронту TIM_ICStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // прямо с ножки TIM_ICStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // без делителя TIM_ICStructure.TIM_ICFilter = 0; // без фильтра // эта функция и включает режим PWM input - автоматически настраивает комплементарный канал // правда в стандартной библиотеке работает на 1 и 2 канале, на 3 и 4 - не умеет TIM_PWMIConfig(TIM3, &TIM_ICStructure); /* Выбираем источник для триггера: вход 1 */ TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); /* По событию от триггера счётчик будет сбрасываться. */ TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); /* Включаем события от триггера */ TIM_SelectMasterSlaveMode(TIM3, TIM_MasterSlaveMode_Enable); // это третий канал, для таймаута. Таймаут 15мс, поскольку максимальный бит (старт) 13.5мс TIM_OCInitTypeDef TIM_OCStructure; TIM_OCStructure.TIM_OCMode = TIM_OCMode_Timing; TIM_OCStructure.TIM_OutputState = TIM_OutputState_Disable; TIM_OCStructure.TIM_OutputNState = TIM_OutputNState_Disable; TIM_OCStructure.TIM_Pulse = 15000; TIM_OC3Init(TIM3, &TIM_OCStructure); /* Разрешаем таймеру генерировать прерывание по захвату */ TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); // и по таймауту третьего канала TIM_ITConfig(TIM3, TIM_IT_CC3, DISABLE /*ENABLE*/); TIM_ClearFlag(TIM3, TIM_FLAG_CC1); TIM_ClearFlag(TIM3, TIM_FLAG_CC3); /* Включаем таймер */ TIM_Cmd(TIM3, ENABLE); //устанавливаем приоритет, долен быть больше 11 для корректной работы FreeRTOS NVIC_SetPriority(TIM3_IRQn, 12); // разрешаем прерывания NVIC_EnableIRQ(TIM3_IRQn); } //Определим перечисляемый тип для событий ИК-приемника enum Rx_Event { NOT_EVENT, //нет событий RX_COMPLETE, //принят пакет RX_MESSAGE_COMPLETE,//принято сообщение RX_ERROR //ошибка приема пакета }; typedef enum Rx_Event trx_event; //Определим перечисляемый тип для событий ИК-приемника enum damage_zone{ zone_4, zone_3, zone_2, zone_1 }; typedef enum damage_zone TDamageZone; #define ZONE_RX_BUFFER_SIZE 40 //Размер буффера приемника зон поражения volatile uint8_t zone3_rx_buffer[ZONE_RX_BUFFER_SIZE]; //Буффер ИК-приемника void set_zone_buffer_bit (TDamageZone zone, uint8_t index, bool value){ uint8_t byte_index; uint8_t bit_index; byte_index = index/8; //Определяем, в каком байте нахадится нужный бит bit_index = index - (byte_index*8);//Определяем номер бита в байте switch(zone) { case zone_1: { // if(value) zone1_rx_buffer[byte_index]|= (1<<(7-bit_index)); // else zone1_rx_buffer[byte_index] &= ~(1<<(7-bit_index)); } break; case zone_2: { // if(value) zone2_rx_buffer[byte_index]|= (1<<(7-bit_index)); // else zone2_rx_buffer[byte_index] &= ~(1<<(7-bit_index)); } break; case zone_3: { if(value) zone3_rx_buffer[byte_index]|= (1<<(7-bit_index)); else zone3_rx_buffer[byte_index] &= ~(1<<(7-bit_index)); } break; case zone_4: { // if(value) zone4_rx_buffer[byte_index]|= (1<<(7-bit_index)); // else zone4_rx_buffer[byte_index] &= ~(1<<(7-bit_index)); } break; } } enum recStatus{ REC_Idle, REC_Recording, REC_Captured }; typedef enum recStatus TRecStatus; volatile TRecStatus tim3_rec_state=REC_Idle; volatile uint16_t tim3_ir_rx_count=0; #define IR_RAW_BUFFER_SIZE 256 //размер буфера ИК-приемника #define IR_START_BIT_DURATION 2400 // Длительность Старт-Бита (в микросекундах) #define IR_ONE_BIT_DURATION 1200 // Длительность Бита, соотретствующего единичке (в микросекундах) #define IR_ZERO_BIT_DURATION 600 // Длительность Бита, соотретствующего нулю (в микросекундах) #define IR_SPACE_DURATION 600 // Длительность Бита, соотретствующего интервалу между битами (в микросекундах) #define IR_TOL 20 //Допустимая погрешность при приеме ИК пакета (в процентах volatile trx_event Zone3RxEvent;//события 3-ей зоны // этот дефайн просто сравнивает значения с заданной точностью в процентах #define checkVal(var,val,tol) (var>(val*(100-tol)/100) && var<(val*(100+tol)/100)) void TIM3_IRQHandler(void)//3-я зона { uint16_t cnt1, cnt2; if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); TIM_ClearITPendingBit(TIM3, TIM_IT_Update); cnt1 = TIM_GetCapture1(TIM3); cnt2 = TIM_GetCapture2(TIM3); if (tim3_rec_state == REC_Recording) { if (tim3_ir_rx_count < IR_RAW_BUFFER_SIZE) { if(tim3_ir_rx_count==0)//первым должен идти заголовок { if(checkVal(cnt2,IR_START_BIT_DURATION,IR_TOL))//проверяем заголовок на ошибку { tim3_ir_rx_count++; } else //заголовок "битый" { // запрещаем прерывания по переполнению третьего канала TIM_ITConfig(TIM3, TIM_IT_CC3, /*ENABLE*/DISABLE); tim3_rec_state = REC_Idle; tim3_ir_rx_count=0; } } else //заголовок уже получен, идет прием данных { // TIM3_IR_RX_RAW_BUFFER[tim3_ir_rx_count-1].Period=cnt1; // TIM3_IR_RX_RAW_BUFFER[tim3_ir_rx_count-1].Pulse=cnt2; if(checkVal(cnt2,IR_ONE_BIT_DURATION,IR_TOL)&&checkVal(cnt1,IR_ONE_BIT_DURATION+IR_SPACE_DURATION, IR_TOL)) { set_zone_buffer_bit(zone_3,tim3_ir_rx_count-1,true);//это единица tim3_ir_rx_count++; } else if (checkVal(cnt2, IR_ZERO_BIT_DURATION,IR_TOL)&&checkVal(cnt1,IR_ZERO_BIT_DURATION+IR_ZERO_BIT_DURATION,IR_TOL)) { set_zone_buffer_bit(zone_3,tim3_ir_rx_count-1,false);//это ноль tim3_ir_rx_count++; } else {//ошибка TIM_ITConfig(TIM3, TIM_IT_CC3, /*ENABLE*/DISABLE); tim3_rec_state = REC_Idle; tim3_ir_rx_count=0; } } } else {//слишком длинный пакет - ошибка // запрещаем прерывания по переполнению третьего канала TIM_ITConfig(TIM3, TIM_IT_CC3, /*ENABLE*/DISABLE); tim3_rec_state = REC_Idle; tim3_ir_rx_count=0; } } if (tim3_rec_state == REC_Idle) { //Пришел первый фронт, начинаем запись //разрешаем прерывания по 3 каналу, предварительно очистив флаг TIM_ClearITPendingBit(TIM3, TIM_IT_CC3); TIM_ITConfig(TIM3, TIM_IT_CC3, ENABLE); tim3_rec_state = REC_Recording; // captCount = 0; } }//[if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) ] if (TIM_GetITStatus(TIM3, TIM_IT_CC3) != RESET) {//если таймаут по приёму TIM_ClearITPendingBit(TIM3, TIM_IT_CC3); if (tim3_rec_state == REC_Recording) { if((tim3_ir_rx_count==14)||(tim3_ir_rx_count==24)) { cnt2 = TIM_GetCapture2(TIM3); if(checkVal(cnt2,IR_ONE_BIT_DURATION,IR_TOL)) { set_zone_buffer_bit(zone_3,tim3_ir_rx_count-1,true);//это единица // tim3_ir_rx_count++; } else if (checkVal(cnt2, IR_ZERO_BIT_DURATION,IR_TOL)) { set_zone_buffer_bit(zone_3,tim3_ir_rx_count-1,false);//это ноль // tim3_ir_rx_count++; } else {//ошибка TIM_ITConfig(TIM3, TIM_IT_CC3, /*ENABLE*/DISABLE); tim3_rec_state = REC_Idle; tim3_ir_rx_count=0; Zone3RxEvent=RX_ERROR; // xSemaphoreGiveFromISR( xZone3Semaphore, &xHigherPriorityTaskWoken); } tim3_rec_state = REC_Captured; if(tim3_ir_rx_count==14)Zone3RxEvent=RX_COMPLETE; else Zone3RxEvent=RX_MESSAGE_COMPLETE; // xSemaphoreGiveFromISR( xZone3Semaphore, &xHigherPriorityTaskWoken ); } else {//ошибка tim3_rec_state = REC_Idle; Zone3RxEvent=RX_ERROR; // xSemaphoreGiveFromISR( xZone3Semaphore, &xHigherPriorityTaskWoken ); } // запрещаем прерывания по переполнению третьего канала TIM_ITConfig(TIM3, TIM_IT_CC3, /*ENABLE*/DISABLE); // tim5_rec_state = REC_Idle; tim3_ir_rx_count=0; //xSemaphoreGiveFromISR( xZone4Semaphore, &xHigherPriorityTaskWoken ); } } } int main(void) { init_pwm_TIM3(); while(1) { } } |
Автор: | Alexies [ 26 дек 2016, 15:54 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Pingvin писал(а): Тоже реализовал приём пакета, но по своему - аппаратно таймером, как и предлагал. Использовал Таймер3 и ножку PA6. На Кокосе проект, библиотека SPL. Понятно, что это прототип, но тем не менее позволю себе несколько замечаний по поводу кода. Вот некоторые вещи, которые бросились в глаза: Читать тяжело, с отступами что-то не так. Чтобы мигом всё исправить, можете использовать утилиту для автоматического форматирования кода: Код: astyle --style=bsd <filename> Или инструменты вашей IDE. Не выделено никакого API для работы с приемником. Какие функции нужно вызывать, чтобы получить данные и т.п. Зачем там разные зоны поражения? Судя по коду, работает только номер 3. Для одного датчика несколько зон - это перебор... Почему пакеты, для которых не выполняется условие Код: if((tim3_ir_rx_count==14)||(tim3_ir_rx_count==24)) считаются испорченными? Умный датчик не должен ничего знать о протоколе, в том числе и о возможной длине сообщений. Ну и по поводу SPL много раз уже высказывался... Вот мой вариант гораздо короче (примерно в 2 раза). И тоже использует аппаратный захват. Вот заголовочный файл: https://github.com/DAlexis/lasertag-sma ... receiver.h Вот сам код: https://github.com/DAlexis/lasertag-sma ... receiver.c |
Автор: | Pingvin [ 26 дек 2016, 16:46 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Да просто хотел запустить прием пакета, скопипастил код настройки и прерывания. Была тут пара "подводных камней" (особенности нулевой серии) - разобрался. Функции API - не успел вставить, но они есть, конечно. Но они для получения значений урона, ИД, команд и прочего из "сырых" данных пакета или сообщения. То есть для анализа содержимого. Если не нужно анализировать пакет, то и не надо ничего дополнительного - всё уже есть в приведённом коде. Данные складываются в буфер, по окончании приема генерируется событие. Хотел показать как работает аппаратный прием. Зона конечно одна останется. Если у Вас аппаратный захват, зачем это тогда? Код: void ir_task_tick() { uint16_t ticks_count = htim14.Instance->CNT; uint16_t ticks_diff = ticks_count - last_edge_ticks; if (receiver_state == RS_WAITING_HEADER) { last_edge_ticks = ticks_count; } else if (ticks_to_us(ticks_diff) > IR_MSG_END_DELAY) { memcpy(&user_buffer, &temporary_buffer, sizeof(temporary_buffer)); data_ready = 1; reset_receiver(); } } У меня сразу и период и длительность импулься в регистрах таймера лежат, не надо ничего вычислять. А в случае таймоута по приему генерит прерывание третий канал таймера. Весь прием - в одном обработчике прерывания. "Наружу" в главный цикл выдается событие и буфер с полученными данными. На самом деле - это простенькая штучка, тут вариантов можно разных напридумывать, ничего страшного, лишь бы протокол обмена был единый. Я просто удовлетворяю своё любопытство. За наводку на утилиту - спасибо! |
Автор: | Alexies [ 26 дек 2016, 17:47 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Pingvin писал(а): Если у Вас аппаратный захват, зачем это тогда? Код: void ir_task_tick() Функция void ir_task_tick() проверяет таймаут из главного потока программы, чтобы не нагружать лишней работой прерывание, и чтобы не было проблем в случае, если один из фронтов будет пропущен по каким-то причинам, например пришло два передних фронта подряд без заднего, и соответствующее прерывание не сработало. Pingvin писал(а): У меня сразу и период и длительность импулься в регистрах таймера лежат, не надо ничего вычислять. А в случае таймоута по приему генерит прерывание третий канал таймера. Весь прием - в одном обработчике прерывания. "Наружу" в главный цикл выдается событие и буфер с полученными данными. Я не уверен, что ваш код работает во всех ситуациях корректно. Вы его тестировали с реальным сигналом? Не ясно например, что происходит при переполнении таймера. У вас нигде в коде не задается его период (в поле timer3.TIM_Period лежит мусор к моменту вызова TIM_TimeBaseInit(TIM3, &timer3) ), а также не обрабатывается прерывание по его переполнению. Хотя я не разобрал полностью логику вашего обработчика, слишком много всего вместе... |
Автор: | Pingvin [ 26 дек 2016, 17:52 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Конечно проверял! http://www.youtube.com/watch?v=tiMkHheKCco Переполнения таймера быть не может - сработает третий канал - сгенерирует прерывание, если будет отсутствие сигнала более 15 mS. Как могут прийти два фронта без спада - я вообще не понял! Логика простая! Приходит фронт - счетчик обнуляется. Первый канал считает до спада. Спад пришел - в первом канале длительность импульса. Второй канал ловит фронт, фронт пришел - во втором канале имеем полный период, и тут же по фронту сброс счётчика и все по новой. А в IC1 и IC2 имеем длительность импулься и период! Если же более 15 mS фронт не пришел - таймаут по приёму (третий канал произведёт захват и выставит флаг прерывания TIM_IT_CC3)- обрабатываем. Код: // это третий канал, для таймаута. Таймаут 15мс, поскольку максимальный бит (старт) 13.5мс TIM_OCInitTypeDef TIM_OCStructure; TIM_OCStructure.TIM_OCMode = TIM_OCMode_Timing; TIM_OCStructure.TIM_OutputState = TIM_OutputState_Disable; TIM_OCStructure.TIM_OutputNState = TIM_OutputNState_Disable; TIM_OCStructure.TIM_Pulse = 15000; TIM_OC3Init(TIM3, &TIM_OCStructure); /* Разрешаем таймеру генерировать прерывание по захвату */ TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); // и по таймауту третьего канала TIM_ITConfig(TIM3, TIM_IT_CC3, DISABLE /*ENABLE*/); Никакого переполнения не будет. Цитата: (в поле timer3.TIM_Period лежит мусор к моменту вызова TIM_TimeBaseInit(TIM3, &timer3) ), Это не мусор!!! Код: TIM_OCStructure.TIM_Pulse = 15000; Это 15000 uS = 15 mS Таймаут! Счетчик таймера тикает каждую микросекунду! Код: timer3.TIM_Prescaler = (SystemCoreClock/1000000)-1;//считаем микросекунды
|
Автор: | Pingvin [ 26 дек 2016, 18:10 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Как у Вас происходит захват? Я так и не понял... |
Автор: | LTagKirov [ 26 дек 2016, 18:52 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Pingvin писал(а): Как происходит захват? Тоже интересно стало, глянул код одним глазком 1. "Заряжается" таймер для счётчика измерения длительности, подробности надо уточнить, практически это самое интересное 2. Функция обратного вызова срабатывает после импульса на ножке или по таймеру?, интересно на любую ножку можно повесить такую функцию ? И на каждую ножку будет нужен свой обработчик ? Есть ли ограничение на количество ножек, ну кроме корпуса МК? 3. Импульс разбирается на соответствие части пакета с помощью автомата. 4. По результату разбора пакета - автомат обнуляется. 5. Полученный пакет хранится в буфере и забирается по мере необходимости. |
Автор: | Alexies [ 27 дек 2016, 13:26 ] |
Заголовок сообщения: | Re: Умный датчик. Smart sensor. |
Pingvin писал(а): Переполнения таймера быть не может - сработает третий канал - сгенерирует прерывание, если будет отсутствие сигнала более 15 mS. ... Приходит фронт - счетчик обнуляется. Первый канал считает до спада. Спад пришел - в первом канале длительность импульса. Второй канал ловит фронт, фронт пришел - во втором канале имеем полный период, и тут же по фронту сброс счётчика и все по новой. А в IC1 и IC2 имеем длительность импулься и период! Если же более 15 mS фронт не пришел - таймаут по приёму (третий канал произведёт захват и выставит флаг прерывания TIM_IT_CC3)- обрабатываем. Никакого переполнения не будет. Цитата: (в поле timer3.TIM_Period лежит мусор к моменту вызова TIM_TimeBaseInit(TIM3, &timer3) ), Это не мусор!!! Код: TIM_OCStructure.TIM_Pulse = 15000; Это 15000 uS = 15 mS Таймаут! Счетчик таймера тикает каждую микросекунду! Код: timer3.TIM_Prescaler = (SystemCoreClock/1000000)-1;//считаем микросекунды Вы не точно прочитали мое сообщение, я там другой фрагмент кода "цитировал". Таймер - это счетчик, который начинает считать в вашем случае по внешнему событию (триггеру) до некоторого значения, затем автоматически сбрасывается, и считает заново с нуля. Каналы таймера отвечают за события, происходящие по достижении таймером определенных значений, или реакцию на внешние события. Но каналы не имеют своего счетчика, счетчик один на всех, и на предел счета код инициализации каналов не влияет никак. Предел счета определен при инициализации самого таймера, в случае 16-бит таймера он не превышает 65535. Инициализация периода таймера у вас происходит именно в том коде, про который я говорил: Код: TIM_TimeBaseInitTypeDef timer3; ... TIM_TimeBaseStructInit(&timer3); timer3.TIM_Prescaler = /*72*/(SystemCoreClock/1000000)-1;//считаем микросекунды TIM_TimeBaseInit(TIM3, &timer3); "Мусором" называется то, что лежит в неинициализированных переменных. Вы явно не инициализируете поле timer3.TIM_Period ничем. Но я ошибся, мусора там нет, я незаметил вызов TIM_TimeBaseStructInit(&timer3), в качестве периода по-умолчанию внутри неё задается 0xFFFF - максимальное значение (посмотрите её код). Это плохая практика, полагаться на значения по-умолчанию, но именно это спасает ваш код. Иначе таймер бы переполнялся через непредсказуемое время. А поля структуры TIM_OCStructure, о которой вы пишите, не имеют никакого отношения к периоду таймера. Pingvin писал(а): Как могут прийти два фронта без спада - я вообще не понял! Когда быстро приходят два фронта, обработчик прерывания вызывается на первый фронт, в это время приходит второй, но обработчик ещё работает, поэтому ничего не происходит. Вне зависимости от того, где вы очищаете флаг прерывания, такая штука может произойти. Я не уверен, что с вашим кодом будут проблемы в данной ситуации, но из него не очевидно, что проблем не будет. |
Страница 3 из 17 | Часовой пояс: UTC + 3 часа [ Летнее время ] |
Powered by phpBB® Forum Software © phpBB Group http://www.phpbb.com/ |