IR transmit example (#180)
* DWT-based microsecond delay * simple ir app (work only with NEC protocol and predefined address - command) * remove space from file name, add delay_us_init_DWT header * float-based delay us * init tim2 by CubeMX * fix simple pwm functions * simple pwm timer based ir nec protocol * ir gui test app Co-authored-by: aanper <mail@s3f.ru>
This commit is contained in:
		@@ -25,6 +25,7 @@ void u8g2_qrcode(void* p);
 | 
			
		||||
void fatfs_list(void* p);
 | 
			
		||||
void gui_task(void* p);
 | 
			
		||||
void backlight_control(void* p);
 | 
			
		||||
void irda(void* p);
 | 
			
		||||
void app_loader(void* p);
 | 
			
		||||
void cc1101_workaround(void* p);
 | 
			
		||||
void nfc_task(void* p);
 | 
			
		||||
@@ -52,6 +53,10 @@ const FlipperStartupApp FLIPPER_STARTUP[] = {
 | 
			
		||||
    {.app = cc1101_workaround, .name = "cc1101 workaround", .libs = {1, FURI_LIB{"gui_task"}}},
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef APP_IRDA
 | 
			
		||||
    {.app = irda, .name = "irda", .libs = {1, FURI_LIB{"gui_task"}}},
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifdef APP_NFC
 | 
			
		||||
    {.app = nfc_task, .name = "nfc_task", .libs = {1, FURI_LIB{"menu_task"}}},
 | 
			
		||||
#endif
 | 
			
		||||
@@ -96,4 +101,7 @@ const FlipperStartupApp FLIPPER_APPS[] = {
 | 
			
		||||
#ifdef BUILD_CC1101
 | 
			
		||||
    {.app = cc1101_workaround, .name = "cc1101 workaround", .libs = {1, FURI_LIB{"gui_task"}}},
 | 
			
		||||
#endif
 | 
			
		||||
#ifdef BUILD_IRDA
 | 
			
		||||
    {.app = irda, .name = "irda", .libs = {1, FURI_LIB{"gui_task"}}},
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
@@ -10,6 +10,7 @@ APP_RELEASE ?= 0
 | 
			
		||||
ifeq ($(APP_RELEASE), 1)
 | 
			
		||||
APP_MENU = 1
 | 
			
		||||
APP_NFC  = 1
 | 
			
		||||
BUILD_IRDA  = 1
 | 
			
		||||
BUILD_EXAMPLE_BLINK = 1
 | 
			
		||||
BUILD_EXAMPLE_UART_WRITE = 1
 | 
			
		||||
BUILD_EXAMPLE_INPUT_DUMP = 1
 | 
			
		||||
@@ -142,6 +143,19 @@ APP_INPUT = 1
 | 
			
		||||
APP_GUI = 1
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
APP_IRDA?= 0
 | 
			
		||||
ifeq ($(APP_IRDA), 1)
 | 
			
		||||
CFLAGS		+= -DAPP_IRDA
 | 
			
		||||
BUILD_IRDA = 1
 | 
			
		||||
endif
 | 
			
		||||
BUILD_IRDA ?= 0
 | 
			
		||||
ifeq ($(BUILD_IRDA), 1)
 | 
			
		||||
CFLAGS		+= -DBUILD_IRDA
 | 
			
		||||
C_SOURCES	+= $(wildcard $(APP_DIR)/irda/*.c)
 | 
			
		||||
APP_INPUT = 1
 | 
			
		||||
APP_GUI = 1
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
# device drivers
 | 
			
		||||
 | 
			
		||||
APP_GUI	?= 0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										238
									
								
								applications/irda/irda.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								applications/irda/irda.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,238 @@
 | 
			
		||||
#include "flipper.h"
 | 
			
		||||
#include "flipper_v2.h"
 | 
			
		||||
#include "irda_nec.h"
 | 
			
		||||
#include "irda_protocols.h"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    EventTypeTick,
 | 
			
		||||
    EventTypeKey,
 | 
			
		||||
} EventType;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    union {
 | 
			
		||||
        InputEvent input;
 | 
			
		||||
    } value;
 | 
			
		||||
    EventType type;
 | 
			
		||||
} Event;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint8_t mode_id;
 | 
			
		||||
    uint16_t carrier_freq;
 | 
			
		||||
    uint8_t carrier_duty_cycle_id;
 | 
			
		||||
    uint8_t nec_packet_id;
 | 
			
		||||
} State;
 | 
			
		||||
 | 
			
		||||
typedef void (*ModeInput)(Event*, State*);
 | 
			
		||||
typedef void (*ModeRender)(CanvasApi*, State*);
 | 
			
		||||
 | 
			
		||||
void input_carrier(Event* event, State* state);
 | 
			
		||||
void render_carrier(CanvasApi* canvas, State* state);
 | 
			
		||||
void input_nec(Event* event, State* state);
 | 
			
		||||
void render_nec(CanvasApi* canvas, State* state);
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    ModeRender render;
 | 
			
		||||
    ModeInput input;
 | 
			
		||||
} Mode;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint8_t addr;
 | 
			
		||||
    uint8_t data;
 | 
			
		||||
} NecPacket;
 | 
			
		||||
 | 
			
		||||
const Mode modes[] = {
 | 
			
		||||
    {.render = render_carrier, .input = input_carrier},
 | 
			
		||||
    {.render = render_nec, .input = input_nec},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const NecPacket packets[] = {
 | 
			
		||||
    {.addr = 0xF7, .data = 0x59},
 | 
			
		||||
    {.addr = 0xFF, .data = 0x01},
 | 
			
		||||
    {.addr = 0xFF, .data = 0x10},
 | 
			
		||||
    {.addr = 0xFF, .data = 0x15},
 | 
			
		||||
    {.addr = 0xFF, .data = 0x25},
 | 
			
		||||
    {.addr = 0xFF, .data = 0xF0},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const float duty_cycles[] = {0.1, 0.25, 0.333, 0.5, 1.0};
 | 
			
		||||
 | 
			
		||||
void render_carrier(CanvasApi* canvas, State* state) {
 | 
			
		||||
    canvas->set_font(canvas, FontSecondary);
 | 
			
		||||
    canvas->draw_str(canvas, 2, 25, "carrier mode >");
 | 
			
		||||
    canvas->draw_str(canvas, 2, 37, "? /\\ freq | \\/ duty cycle");
 | 
			
		||||
    {
 | 
			
		||||
        char buf[24];
 | 
			
		||||
        sprintf(buf, "frequency: %d Hz", state->carrier_freq);
 | 
			
		||||
        canvas->draw_str(canvas, 2, 50, buf);
 | 
			
		||||
        sprintf(
 | 
			
		||||
            buf,
 | 
			
		||||
            "duty cycle: %d/1000",
 | 
			
		||||
            (uint32_t)(duty_cycles[state->carrier_duty_cycle_id] * 1000));
 | 
			
		||||
        canvas->draw_str(canvas, 2, 62, buf);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void render_nec(CanvasApi* canvas, State* state) {
 | 
			
		||||
    canvas->set_font(canvas, FontSecondary);
 | 
			
		||||
    canvas->draw_str(canvas, 2, 25, "< nec protocol mode");
 | 
			
		||||
    canvas->draw_str(canvas, 2, 37, "? /\\ \\/ packet");
 | 
			
		||||
    {
 | 
			
		||||
        char buf[24];
 | 
			
		||||
        sprintf(
 | 
			
		||||
            buf,
 | 
			
		||||
            "packet: %02X %02X",
 | 
			
		||||
            packets[state->nec_packet_id].addr,
 | 
			
		||||
            packets[state->nec_packet_id].data);
 | 
			
		||||
        canvas->draw_str(canvas, 2, 50, buf);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void input_carrier(Event* event, State* state) {
 | 
			
		||||
    if(event->value.input.input == InputOk) {
 | 
			
		||||
        if(event->value.input.state) {
 | 
			
		||||
            pwm_set(
 | 
			
		||||
                duty_cycles[state->carrier_duty_cycle_id],
 | 
			
		||||
                state->carrier_freq,
 | 
			
		||||
                &htim2,
 | 
			
		||||
                TIM_CHANNEL_4);
 | 
			
		||||
        } else {
 | 
			
		||||
            pwm_stop(&htim2, TIM_CHANNEL_4);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(event->value.input.state && event->value.input.input == InputUp) {
 | 
			
		||||
        if(state->carrier_freq < 45000) {
 | 
			
		||||
            state->carrier_freq += 1000;
 | 
			
		||||
        } else {
 | 
			
		||||
            state->carrier_freq = 33000;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(event->value.input.state && event->value.input.input == InputDown) {
 | 
			
		||||
        uint8_t duty_cycles_count = sizeof(duty_cycles) / sizeof(duty_cycles[0]);
 | 
			
		||||
        if(state->carrier_duty_cycle_id < (duty_cycles_count - 1)) {
 | 
			
		||||
            state->carrier_duty_cycle_id++;
 | 
			
		||||
        } else {
 | 
			
		||||
            state->carrier_duty_cycle_id = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void input_nec(Event* event, State* state) {
 | 
			
		||||
    if(event->value.input.input == InputOk) {
 | 
			
		||||
        if(event->value.input.state) {
 | 
			
		||||
            vTaskSuspendAll();
 | 
			
		||||
            ir_nec_send(packets[state->nec_packet_id].addr, packets[state->nec_packet_id].data);
 | 
			
		||||
            xTaskResumeAll();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(event->value.input.state && event->value.input.input == InputUp) {
 | 
			
		||||
        uint8_t packets_count = sizeof(packets) / sizeof(packets[0]);
 | 
			
		||||
        if(state->nec_packet_id < (packets_count - 1)) {
 | 
			
		||||
            state->nec_packet_id++;
 | 
			
		||||
        } else {
 | 
			
		||||
            state->nec_packet_id = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(event->value.input.state && event->value.input.input == InputDown) {
 | 
			
		||||
        uint8_t packets_count = sizeof(packets) / sizeof(packets[0]);
 | 
			
		||||
        if(state->nec_packet_id > 0) {
 | 
			
		||||
            state->nec_packet_id--;
 | 
			
		||||
        } else {
 | 
			
		||||
            state->nec_packet_id = packets_count - 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void render_callback(CanvasApi* canvas, void* ctx) {
 | 
			
		||||
    State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25);
 | 
			
		||||
 | 
			
		||||
    canvas->clear(canvas);
 | 
			
		||||
    canvas->set_color(canvas, ColorBlack);
 | 
			
		||||
    canvas->set_font(canvas, FontPrimary);
 | 
			
		||||
    canvas->draw_str(canvas, 2, 12, "irda test");
 | 
			
		||||
 | 
			
		||||
    modes[state->mode_id].render(canvas, state);
 | 
			
		||||
 | 
			
		||||
    release_mutex((ValueMutex*)ctx, state);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void input_callback(InputEvent* input_event, void* ctx) {
 | 
			
		||||
    osMessageQueueId_t event_queue = (QueueHandle_t)ctx;
 | 
			
		||||
 | 
			
		||||
    Event event;
 | 
			
		||||
    event.type = EventTypeKey;
 | 
			
		||||
    event.value.input = *input_event;
 | 
			
		||||
    osMessageQueuePut(event_queue, &event, 0, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void irda(void* p) {
 | 
			
		||||
    osMessageQueueId_t event_queue = osMessageQueueNew(1, sizeof(Event), NULL);
 | 
			
		||||
 | 
			
		||||
    State _state;
 | 
			
		||||
    uint8_t mode_count = sizeof(modes) / sizeof(modes[0]);
 | 
			
		||||
    uint8_t duty_cycles_count = sizeof(duty_cycles) / sizeof(duty_cycles[0]);
 | 
			
		||||
 | 
			
		||||
    _state.carrier_duty_cycle_id = duty_cycles_count - 2;
 | 
			
		||||
    _state.carrier_freq = 36000;
 | 
			
		||||
    _state.mode_id = 0;
 | 
			
		||||
    _state.nec_packet_id = 0;
 | 
			
		||||
 | 
			
		||||
    ValueMutex state_mutex;
 | 
			
		||||
    if(!init_mutex(&state_mutex, &_state, sizeof(State))) {
 | 
			
		||||
        printf("cannot create mutex\n");
 | 
			
		||||
        furiac_exit(NULL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Widget* widget = widget_alloc();
 | 
			
		||||
 | 
			
		||||
    widget_draw_callback_set(widget, render_callback, &state_mutex);
 | 
			
		||||
    widget_input_callback_set(widget, input_callback, event_queue);
 | 
			
		||||
 | 
			
		||||
    // Open GUI and register widget
 | 
			
		||||
    GuiApi* gui = (GuiApi*)furi_open("gui");
 | 
			
		||||
    if(gui == NULL) {
 | 
			
		||||
        printf("gui is not available\n");
 | 
			
		||||
        furiac_exit(NULL);
 | 
			
		||||
    }
 | 
			
		||||
    gui->add_widget(gui, widget, WidgetLayerFullscreen);
 | 
			
		||||
 | 
			
		||||
    Event event;
 | 
			
		||||
    while(1) {
 | 
			
		||||
        osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever);
 | 
			
		||||
        State* state = (State*)acquire_mutex_block(&state_mutex);
 | 
			
		||||
 | 
			
		||||
        if(event_status == osOK) {
 | 
			
		||||
            if(event.type == EventTypeKey) {
 | 
			
		||||
                // press events
 | 
			
		||||
                if(event.value.input.state && event.value.input.input == InputBack) {
 | 
			
		||||
                    printf("[irda] bye!\n");
 | 
			
		||||
                    // TODO remove all widgets create by app
 | 
			
		||||
                    widget_enabled_set(widget, false);
 | 
			
		||||
                    furiac_exit(NULL);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if(event.value.input.state && event.value.input.input == InputLeft) {
 | 
			
		||||
                    if(state->mode_id > 0) {
 | 
			
		||||
                        state->mode_id--;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if(event.value.input.state && event.value.input.input == InputRight) {
 | 
			
		||||
                    if(state->mode_id < (mode_count - 1)) {
 | 
			
		||||
                        state->mode_id++;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                modes[state->mode_id].input(&event, state);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // event timeout
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        release_mutex(&state_mutex, state);
 | 
			
		||||
        widget_update(widget);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								applications/irda/irda_nec.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								applications/irda/irda_nec.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
#include "flipper.h"
 | 
			
		||||
#include "irda_nec.h"
 | 
			
		||||
#include "irda_protocols.h"
 | 
			
		||||
 | 
			
		||||
void ir_nec_preambula(void) {
 | 
			
		||||
    // 9ms carrier + 4.5ms pause
 | 
			
		||||
    pwm_set(NEC_DUTY_CYCLE, NEC_CARRIER_FREQUENCY, &htim2, TIM_CHANNEL_4);
 | 
			
		||||
    delay_us(9000);
 | 
			
		||||
    pwm_stop(&htim2, TIM_CHANNEL_4);
 | 
			
		||||
    delay_us(4500);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ir_nec_send_bit(bool bit) {
 | 
			
		||||
    // 0 is 562.5us carrier + 1687.5us pause
 | 
			
		||||
    // 1 is 562.5us carrier + 562.5us pause
 | 
			
		||||
    pwm_set(NEC_DUTY_CYCLE, NEC_CARRIER_FREQUENCY, &htim2, TIM_CHANNEL_4);
 | 
			
		||||
    delay_us(562.5);
 | 
			
		||||
    pwm_stop(&htim2, TIM_CHANNEL_4);
 | 
			
		||||
    if(bit) {
 | 
			
		||||
        delay_us(562.5);
 | 
			
		||||
    } else {
 | 
			
		||||
        delay_us(1687.5);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ir_nec_send_byte(uint8_t data) {
 | 
			
		||||
    for(uint8_t i = 0; i < 8; i++) {
 | 
			
		||||
        ir_nec_send_bit((data & (1 << (i))) != 0);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ir_nec_send(uint8_t addr, uint8_t data) {
 | 
			
		||||
    // nec protocol is:
 | 
			
		||||
    // preambula + addr + inverse addr + command + inverse command + bit pulse
 | 
			
		||||
    //
 | 
			
		||||
    // oddly enough, my analyzer (https://github.com/ukw100/IRMP) displays the reverse command
 | 
			
		||||
    // and I don’t know if this is my fault or a feature of the analyzer
 | 
			
		||||
    // TODO: check the dictionary and check with a known remote
 | 
			
		||||
    uint8_t nec_packet[4] = {addr, ~(uint8_t)addr, ~(uint8_t)data, data};
 | 
			
		||||
    ir_nec_preambula();
 | 
			
		||||
    ir_nec_send_byte(nec_packet[0]);
 | 
			
		||||
    ir_nec_send_byte(nec_packet[1]);
 | 
			
		||||
    ir_nec_send_byte(nec_packet[2]);
 | 
			
		||||
    ir_nec_send_byte(nec_packet[3]);
 | 
			
		||||
    ir_nec_send_bit(1);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								applications/irda/irda_nec.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								applications/irda/irda_nec.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "flipper.h"
 | 
			
		||||
 | 
			
		||||
void ir_nec_send(uint8_t addr, uint8_t data);
 | 
			
		||||
							
								
								
									
										16
									
								
								applications/irda/irda_protocols.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								applications/irda/irda_protocols.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
// our tx pin is TIM2_CH4
 | 
			
		||||
extern TIM_HandleTypeDef htim2;
 | 
			
		||||
 | 
			
		||||
#define RC5_CARRIER_FREQUENCY 36000
 | 
			
		||||
#define RC5_DUTY_CYCLE 0.33
 | 
			
		||||
 | 
			
		||||
#define RC6_CARRIER_FREQUENCY 36000
 | 
			
		||||
#define RC6_DUTY_CYCLE 0.33
 | 
			
		||||
 | 
			
		||||
#define NEC_CARRIER_FREQUENCY 38000
 | 
			
		||||
#define NEC_DUTY_CYCLE 0.33
 | 
			
		||||
 | 
			
		||||
#define SIRC_CARRIER_FREQUENCY 40000
 | 
			
		||||
#define SIRC_DUTY_CYCLE 0.5
 | 
			
		||||
		Reference in New Issue
	
	Block a user