[FL-572][FL-577] Irda receive feature (#282)
* fix "state not acquired error" * add InterruptTypeComparatorTrigger to interrupt mgr, use interrupt mgr in irda app * separate init irda timer * capture events buffer by app * irda common decoder * irda nec decoder realization * finished work with decoder * fix app path * fix widget remove on exit * nec receive, store and send * init some packets
This commit is contained in:
		
							parent
							
								
									c70ed2f349
								
							
						
					
					
						commit
						d65e9b04ce
					
				| @ -204,6 +204,7 @@ BUILD_IRDA ?= 0 | |||||||
| ifeq ($(BUILD_IRDA), 1) | ifeq ($(BUILD_IRDA), 1) | ||||||
| CFLAGS		+= -DBUILD_IRDA | CFLAGS		+= -DBUILD_IRDA | ||||||
| C_SOURCES	+= $(wildcard $(APP_DIR)/irda/*.c) | C_SOURCES	+= $(wildcard $(APP_DIR)/irda/*.c) | ||||||
|  | C_SOURCES	+= $(wildcard $(APP_DIR)/irda/*/*.c) | ||||||
| APP_INPUT = 1 | APP_INPUT = 1 | ||||||
| APP_GUI = 1 | APP_GUI = 1 | ||||||
| endif | endif | ||||||
|  | |||||||
							
								
								
									
										146
									
								
								applications/irda/irda-decoder/irda-decoder-nec.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								applications/irda/irda-decoder/irda-decoder-nec.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,146 @@ | |||||||
|  | #include "irda-decoder-nec.h" | ||||||
|  | #include "string.h" | ||||||
|  | 
 | ||||||
|  | const uint32_t PREAMBULA_HIGH_MIN = 9000 - 900; | ||||||
|  | const uint32_t PREAMBULA_HIGH_MAX = 9000 + 900; | ||||||
|  | 
 | ||||||
|  | const uint32_t PREAMBULA_LOW_MIN = 4500 - 450; | ||||||
|  | const uint32_t PREAMBULA_LOW_MAX = 4500 + 450; | ||||||
|  | 
 | ||||||
|  | const uint32_t PREAMBULA_RETRY_LOW_MIN = 2500 - 350; | ||||||
|  | const uint32_t PREAMBULA_RETRY_LOW_MAX = 2500 + 250; | ||||||
|  | 
 | ||||||
|  | const uint32_t BIT_HIGH_MIN = 560 - 100; | ||||||
|  | const uint32_t BIT_HIGH_MAX = 560 + 100; | ||||||
|  | 
 | ||||||
|  | const uint32_t BIT_LOW_ONE_MIN = 1690 - 200; | ||||||
|  | const uint32_t BIT_LOW_ONE_MAX = 1690 + 200; | ||||||
|  | 
 | ||||||
|  | const uint32_t BIT_LOW_ZERO_MIN = 560 - 100; | ||||||
|  | const uint32_t BIT_LOW_ZERO_MAX = 560 + 100; | ||||||
|  | 
 | ||||||
|  | #define SET_STATE(_state) \ | ||||||
|  |     { decoder->state = _state; } | ||||||
|  | 
 | ||||||
|  | #define TIME_FIT(_prefix) ((time > _prefix##_MIN) && (time < _prefix##_MAX)) | ||||||
|  | 
 | ||||||
|  | #ifndef MIN | ||||||
|  | #define MIN(a, b) ((a) < (b) ? (a) : (b)) | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | bool save_decoder_nec_data(IrDANecDecoder* decoder, IrDADecoderOutputData* out) { | ||||||
|  |     bool result = false; | ||||||
|  | 
 | ||||||
|  |     if((decoder->data.simple.cmd + decoder->data.simple.cmd_inverse) == 0xFF) { | ||||||
|  |         if(out->data_length < sizeof(IrDANecDataType)) { | ||||||
|  |             out->flags |= IRDA_TOO_SHORT_BUFFER; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         memcpy(out->data, &decoder->data.data, MIN(sizeof(IrDANecDataType), out->data_length)); | ||||||
|  |         result = true; | ||||||
|  |     } else { | ||||||
|  |         reset_decoder_nec(decoder); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool process_decoder_nec( | ||||||
|  |     IrDANecDecoder* decoder, | ||||||
|  |     bool polarity, | ||||||
|  |     uint32_t time, | ||||||
|  |     IrDADecoderOutputData* out) { | ||||||
|  |     bool error = true; | ||||||
|  |     bool result = false; | ||||||
|  | 
 | ||||||
|  |     switch(decoder->state) { | ||||||
|  |     case(WAIT_PREAMBULA_HIGH): | ||||||
|  |         if(polarity) { | ||||||
|  |             if(TIME_FIT(PREAMBULA_HIGH)) { | ||||||
|  |                 SET_STATE(WAIT_PREAMBULA_LOW); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         // any values before preambula start is correct
 | ||||||
|  |         error = false; | ||||||
|  |         break; | ||||||
|  |     case(WAIT_PREAMBULA_LOW): | ||||||
|  |         if(!polarity) { | ||||||
|  |             if(TIME_FIT(PREAMBULA_LOW)) { | ||||||
|  |                 // new data, reset storage
 | ||||||
|  |                 reset_decoder_nec(decoder); | ||||||
|  |                 SET_STATE(WAIT_BIT_HIGH); | ||||||
|  |                 error = false; | ||||||
|  |             } else if(TIME_FIT(PREAMBULA_RETRY_LOW)) { | ||||||
|  |                 // wait for data repeat command
 | ||||||
|  |                 SET_STATE(WAIT_RETRY_HIGH); | ||||||
|  |                 error = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case(WAIT_RETRY_HIGH): | ||||||
|  |         if(polarity) { | ||||||
|  |             if(TIME_FIT(BIT_HIGH)) { | ||||||
|  |                 SET_STATE(WAIT_PREAMBULA_HIGH); | ||||||
|  | 
 | ||||||
|  |                 // repeat event
 | ||||||
|  |                 result = save_decoder_nec_data(decoder, out); | ||||||
|  |                 out->flags |= IRDA_REPEAT; | ||||||
|  |                 error = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case(WAIT_BIT_HIGH): | ||||||
|  |         if(polarity) { | ||||||
|  |             if(TIME_FIT(BIT_HIGH)) { | ||||||
|  |                 SET_STATE(WAIT_BIT_LOW); | ||||||
|  |                 error = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case(WAIT_BIT_STOP_HIGH): | ||||||
|  |         if(polarity) { | ||||||
|  |             if(TIME_FIT(BIT_HIGH)) { | ||||||
|  |                 SET_STATE(WAIT_PREAMBULA_HIGH); | ||||||
|  | 
 | ||||||
|  |                 // message end event
 | ||||||
|  |                 result = save_decoder_nec_data(decoder, out); | ||||||
|  |                 error = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     case(WAIT_BIT_LOW): | ||||||
|  |         if(!polarity) { | ||||||
|  |             int8_t bit = -1; | ||||||
|  |             if(TIME_FIT(BIT_LOW_ZERO)) { | ||||||
|  |                 SET_STATE(WAIT_BIT_HIGH); | ||||||
|  |                 bit = 0; | ||||||
|  |                 error = false; | ||||||
|  |             } else if(TIME_FIT(BIT_LOW_ONE)) { | ||||||
|  |                 SET_STATE(WAIT_BIT_HIGH); | ||||||
|  |                 bit = 1; | ||||||
|  |                 error = false; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(bit != -1) { | ||||||
|  |                 decoder->data.data |= (bit << decoder->current_data_index); | ||||||
|  |                 decoder->current_data_index++; | ||||||
|  | 
 | ||||||
|  |                 if(decoder->current_data_index > 31) { | ||||||
|  |                     decoder->current_data_index = 0; | ||||||
|  |                     SET_STATE(WAIT_BIT_STOP_HIGH); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if(error) reset_decoder_nec(decoder); | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void reset_decoder_nec(IrDANecDecoder* decoder) { | ||||||
|  |     decoder->state = WAIT_PREAMBULA_HIGH; | ||||||
|  |     decoder->data.data = 0; | ||||||
|  |     decoder->current_data_index = 0; | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								applications/irda/irda-decoder/irda-decoder-nec.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								applications/irda/irda-decoder/irda-decoder-nec.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <stdint.h> | ||||||
|  | #include <stdbool.h> | ||||||
|  | #include "irda-decoder-types.h" | ||||||
|  | 
 | ||||||
|  | typedef enum { | ||||||
|  |     WAIT_PREAMBULA_HIGH, | ||||||
|  |     WAIT_PREAMBULA_LOW, | ||||||
|  |     WAIT_RETRY_HIGH, | ||||||
|  |     WAIT_BIT_HIGH, | ||||||
|  |     WAIT_BIT_LOW, | ||||||
|  |     WAIT_BIT_STOP_HIGH, | ||||||
|  | } IrDANecDecoderState; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t addr2; | ||||||
|  |     uint8_t addr1; | ||||||
|  |     uint8_t cmd_inverse; | ||||||
|  |     uint8_t cmd; | ||||||
|  | } IrDANecData; | ||||||
|  | 
 | ||||||
|  | typedef uint32_t IrDANecDataType; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     union { | ||||||
|  |         IrDANecData simple; | ||||||
|  |         IrDANecDataType data; | ||||||
|  |     } data; | ||||||
|  |     uint8_t current_data_index; | ||||||
|  |     IrDANecDecoderState state; | ||||||
|  | } IrDANecDecoder; | ||||||
|  | 
 | ||||||
|  | bool process_decoder_nec( | ||||||
|  |     IrDANecDecoder* decoder, | ||||||
|  |     bool polarity, | ||||||
|  |     uint32_t time, | ||||||
|  |     IrDADecoderOutputData* out); | ||||||
|  | 
 | ||||||
|  | void reset_decoder_nec(IrDANecDecoder* decoder); | ||||||
							
								
								
									
										12
									
								
								applications/irda/irda-decoder/irda-decoder-types.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								applications/irda/irda-decoder/irda-decoder-types.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | #pragma once | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | typedef enum { IRDA_UNKNOWN, IRDA_NEC, IRDA_SAMSUNG } IrDAProtocolType; | ||||||
|  | typedef enum { IRDA_REPEAT = (1 << 0), IRDA_TOO_SHORT_BUFFER = (1 << 1) } IrDAProtocolFlags; | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     IrDAProtocolType protocol; | ||||||
|  |     uint8_t flags; | ||||||
|  |     uint8_t* data; /** < ponter to output data, filled by app */ | ||||||
|  |     uint32_t data_length; /** < output data length, filled by app */ | ||||||
|  | } IrDADecoderOutputData; | ||||||
							
								
								
									
										41
									
								
								applications/irda/irda-decoder/irda-decoder.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								applications/irda/irda-decoder/irda-decoder.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,41 @@ | |||||||
|  | #include "irda-decoder.h" | ||||||
|  | 
 | ||||||
|  | IrDADecoder* alloc_decoder(void) { | ||||||
|  |     IrDADecoder* decoder = malloc(sizeof(IrDADecoder)); | ||||||
|  | 
 | ||||||
|  |     // init decoders
 | ||||||
|  |     reset_decoder_nec(&decoder->nec); | ||||||
|  | 
 | ||||||
|  |     return decoder; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void free_decoder(IrDADecoder* decoder) { | ||||||
|  |     free(decoder); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool process_decoder( | ||||||
|  |     IrDADecoder* decoder, | ||||||
|  |     bool start_polarity, | ||||||
|  |     uint32_t* timings, | ||||||
|  |     uint32_t timings_length, | ||||||
|  |     IrDADecoderOutputData* out) { | ||||||
|  |     bool result = false; | ||||||
|  | 
 | ||||||
|  |     // zero result
 | ||||||
|  |     memset(out->data, 0, out->data_length); | ||||||
|  |     out->protocol = IRDA_UNKNOWN; | ||||||
|  |     out->flags = 0; | ||||||
|  | 
 | ||||||
|  |     // process data
 | ||||||
|  |     for(uint32_t timings_index = 0; timings_index < timings_length; timings_index++) { | ||||||
|  |         if(process_decoder_nec(&decoder->nec, start_polarity, timings[timings_index], out)) { | ||||||
|  |             out->protocol = IRDA_NEC; | ||||||
|  |             result = true; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         start_polarity = !start_polarity; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return result; | ||||||
|  | } | ||||||
							
								
								
									
										18
									
								
								applications/irda/irda-decoder/irda-decoder.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								applications/irda/irda-decoder/irda-decoder.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "flipper.h" | ||||||
|  | #include "flipper_v2.h" | ||||||
|  | #include "irda-decoder-nec.h" | ||||||
|  | #include "irda-decoder-types.h" | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  |     IrDANecDecoder nec; | ||||||
|  | } IrDADecoder; | ||||||
|  | 
 | ||||||
|  | IrDADecoder* alloc_decoder(void); | ||||||
|  | void free_decoder(IrDADecoder* decoder); | ||||||
|  | bool process_decoder( | ||||||
|  |     IrDADecoder* decoder, | ||||||
|  |     bool start_polarity, | ||||||
|  |     uint32_t* timings, | ||||||
|  |     uint32_t timings_length, | ||||||
|  |     IrDADecoderOutputData* out); | ||||||
| @ -3,6 +3,7 @@ | |||||||
| #include "irda_nec.h" | #include "irda_nec.h" | ||||||
| #include "irda_samsung.h" | #include "irda_samsung.h" | ||||||
| #include "irda_protocols.h" | #include "irda_protocols.h" | ||||||
|  | #include "irda-decoder/irda-decoder.h" | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     EventTypeTick, |     EventTypeTick, | ||||||
| @ -10,20 +11,34 @@ typedef enum { | |||||||
|     EventTypeRX, |     EventTypeRX, | ||||||
| } EventType; | } EventType; | ||||||
| 
 | 
 | ||||||
|  | typedef struct { | ||||||
|  |     bool edge; | ||||||
|  |     uint32_t lasted; | ||||||
|  | } RXValue; | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     union { |     union { | ||||||
|         InputEvent input; |         InputEvent input; | ||||||
|         bool rx_edge; |         RXValue rx; | ||||||
|     } value; |     } value; | ||||||
|     EventType type; |     EventType type; | ||||||
| } AppEvent; | } AppEvent; | ||||||
| 
 | 
 | ||||||
|  | typedef struct { | ||||||
|  |     IrDAProtocolType protocol; | ||||||
|  |     uint32_t address; | ||||||
|  |     uint32_t command; | ||||||
|  | } IrDAPacket; | ||||||
|  | 
 | ||||||
|  | #define IRDA_PACKET_COUNT 8 | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t mode_id; |     uint8_t mode_id; | ||||||
|     uint16_t carrier_freq; |     uint16_t carrier_freq; | ||||||
|     uint8_t carrier_duty_cycle_id; |     uint8_t carrier_duty_cycle_id; | ||||||
|     uint8_t nec_packet_id; | 
 | ||||||
|     uint8_t samsung_packet_id; |     uint8_t packet_id; | ||||||
|  |     IrDAPacket packets[IRDA_PACKET_COUNT]; | ||||||
| } State; | } State; | ||||||
| 
 | 
 | ||||||
| typedef void (*ModeInput)(AppEvent*, State*); | typedef void (*ModeInput)(AppEvent*, State*); | ||||||
| @ -31,47 +46,17 @@ typedef void (*ModeRender)(Canvas*, State*); | |||||||
| 
 | 
 | ||||||
| void input_carrier(AppEvent* event, State* state); | void input_carrier(AppEvent* event, State* state); | ||||||
| void render_carrier(Canvas* canvas, State* state); | void render_carrier(Canvas* canvas, State* state); | ||||||
| void input_nec(AppEvent* event, State* state); | void input_packet(AppEvent* event, State* state); | ||||||
| void render_nec(Canvas* canvas, State* state); | void render_packet(Canvas* canvas, State* state); | ||||||
| void render_carrier(Canvas* canvas, State* state); |  | ||||||
| void input_samsung(AppEvent* event, State* state); |  | ||||||
| void render_samsung(Canvas* canvas, State* state); |  | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     ModeRender render; |     ModeRender render; | ||||||
|     ModeInput input; |     ModeInput input; | ||||||
| } Mode; | } Mode; | ||||||
| 
 | 
 | ||||||
| typedef struct { |  | ||||||
|     uint8_t addr; |  | ||||||
|     uint8_t data; |  | ||||||
| } NecPacket; |  | ||||||
| 
 |  | ||||||
| typedef struct { |  | ||||||
|     uint16_t addr; |  | ||||||
|     uint16_t data; |  | ||||||
| } SamsungPacket; |  | ||||||
| 
 |  | ||||||
| const Mode modes[] = { | const Mode modes[] = { | ||||||
|     {.render = render_carrier, .input = input_carrier}, |     {.render = render_carrier, .input = input_carrier}, | ||||||
|     {.render = render_nec, .input = input_nec}, |     {.render = render_packet, .input = input_packet}, | ||||||
|     {.render = render_samsung, .input = input_samsung}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const NecPacket nec_packets[] = { |  | ||||||
|     {.addr = 0xFF, .data = 0x11}, |  | ||||||
|     {.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 SamsungPacket samsung_packets[] = { |  | ||||||
|     {.addr = 0xE0E, .data = 0xF30C}, |  | ||||||
|     {.addr = 0xE0E, .data = 0xF40D}, |  | ||||||
|     {.addr = 0xE0E, .data = 0xF50E}, |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const float duty_cycles[] = {0.1, 0.25, 0.333, 0.5, 1.0}; | const float duty_cycles[] = {0.1, 0.25, 0.333, 0.5, 1.0}; | ||||||
| @ -90,36 +75,6 @@ void render_carrier(Canvas* canvas, State* state) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void render_nec(Canvas* canvas, State* state) { |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     canvas_draw_str(canvas, 2, 25, "< nec mode >"); |  | ||||||
|     canvas_draw_str(canvas, 2, 37, "? /\\ \\/ packet"); |  | ||||||
|     { |  | ||||||
|         char buf[24]; |  | ||||||
|         sprintf( |  | ||||||
|             buf, |  | ||||||
|             "packet: %02X %02X", |  | ||||||
|             nec_packets[state->nec_packet_id].addr, |  | ||||||
|             nec_packets[state->nec_packet_id].data); |  | ||||||
|         canvas_draw_str(canvas, 2, 50, buf); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void render_samsung(Canvas* canvas, State* state) { |  | ||||||
|     canvas_set_font(canvas, FontSecondary); |  | ||||||
|     canvas_draw_str(canvas, 2, 25, "< samsung32 mode"); |  | ||||||
|     canvas_draw_str(canvas, 2, 37, "? /\\ \\/ packet"); |  | ||||||
|     { |  | ||||||
|         char buf[24]; |  | ||||||
|         sprintf( |  | ||||||
|             buf, |  | ||||||
|             "packet: %02X %02X", |  | ||||||
|             samsung_packets[state->samsung_packet_id].addr, |  | ||||||
|             samsung_packets[state->samsung_packet_id].data); |  | ||||||
|         canvas_draw_str(canvas, 2, 50, buf); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void input_carrier(AppEvent* event, State* state) { | void input_carrier(AppEvent* event, State* state) { | ||||||
|     if(event->value.input.input == InputOk) { |     if(event->value.input.input == InputOk) { | ||||||
|         if(event->value.input.state) { |         if(event->value.input.state) { | ||||||
| @ -147,68 +102,77 @@ void input_carrier(AppEvent* event, State* state) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void input_nec(AppEvent* event, State* state) { | void render_packet(Canvas* canvas, State* state) { | ||||||
|     uint8_t packets_count = sizeof(nec_packets) / sizeof(nec_packets[0]); |     canvas_set_font(canvas, FontSecondary); | ||||||
|  |     canvas_draw_str(canvas, 2, 25, "< packet mode"); | ||||||
|  |     canvas_draw_str(canvas, 2, 37, "? /\\ \\/ packet"); | ||||||
|  |     { | ||||||
|  |         const char* protocol; | ||||||
| 
 | 
 | ||||||
|  |         switch(state->packets[state->packet_id].protocol) { | ||||||
|  |         case IRDA_NEC: | ||||||
|  |             protocol = "NEC"; | ||||||
|  |             break; | ||||||
|  |         case IRDA_SAMSUNG: | ||||||
|  |             protocol = "SAMS"; | ||||||
|  |             break; | ||||||
|  |         case IRDA_UNKNOWN: | ||||||
|  |         default: | ||||||
|  |             protocol = "UNK"; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         char buf[24]; | ||||||
|  |         sprintf( | ||||||
|  |             buf, | ||||||
|  |             "P[%d]: %s 0x%X 0x%X", | ||||||
|  |             state->packet_id, | ||||||
|  |             protocol, | ||||||
|  |             state->packets[state->packet_id].address, | ||||||
|  |             state->packets[state->packet_id].command); | ||||||
|  |         canvas_draw_str(canvas, 2, 50, buf); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void input_packet(AppEvent* event, State* state) { | ||||||
|     if(event->value.input.input == InputOk) { |     if(event->value.input.input == InputOk) { | ||||||
|         if(event->value.input.state) { |         if(event->value.input.state) { | ||||||
|             vTaskSuspendAll(); |             vTaskSuspendAll(); | ||||||
|  |             switch(state->packets[state->packet_id].protocol) { | ||||||
|  |             case IRDA_NEC: | ||||||
|                 ir_nec_send( |                 ir_nec_send( | ||||||
|                 nec_packets[state->nec_packet_id].addr, nec_packets[state->nec_packet_id].data); |                     state->packets[state->packet_id].address, | ||||||
|             xTaskResumeAll(); |                     state->packets[state->packet_id].command); | ||||||
|         } |                 break; | ||||||
|     } |             case IRDA_SAMSUNG: | ||||||
| 
 |  | ||||||
|     if(event->value.input.state && event->value.input.input == InputUp) { |  | ||||||
|         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) { |  | ||||||
|         if(state->nec_packet_id > 0) { |  | ||||||
|             state->nec_packet_id--; |  | ||||||
|         } else { |  | ||||||
|             state->nec_packet_id = packets_count - 1; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void input_samsung(AppEvent* event, State* state) { |  | ||||||
|     uint8_t packets_count = sizeof(samsung_packets) / sizeof(samsung_packets[0]); |  | ||||||
| 
 |  | ||||||
|     if(event->value.input.input == InputOk) { |  | ||||||
|         if(event->value.input.state) { |  | ||||||
|             vTaskSuspendAll(); |  | ||||||
|                 ir_samsung_send( |                 ir_samsung_send( | ||||||
|                 samsung_packets[state->samsung_packet_id].addr, |                     state->packets[state->packet_id].address, | ||||||
|                 samsung_packets[state->samsung_packet_id].data); |                     state->packets[state->packet_id].command); | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|             xTaskResumeAll(); |             xTaskResumeAll(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(event->value.input.state && event->value.input.input == InputUp) { |     if(event->value.input.state && event->value.input.input == InputDown) { | ||||||
|         if(state->samsung_packet_id < (packets_count - 1)) { |         if(state->packet_id < (IRDA_PACKET_COUNT - 1)) { | ||||||
|             state->samsung_packet_id++; |             state->packet_id++; | ||||||
|         } else { |         }; | ||||||
|             state->samsung_packet_id = 0; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(event->value.input.state && event->value.input.input == InputDown) { |     if(event->value.input.state && event->value.input.input == InputUp) { | ||||||
|         if(state->samsung_packet_id > 0) { |         if(state->packet_id > 0) { | ||||||
|             state->samsung_packet_id--; |             state->packet_id--; | ||||||
|         } else { |         }; | ||||||
|             state->samsung_packet_id = packets_count - 1; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void render_callback(Canvas* canvas, void* ctx) { | static void render_callback(Canvas* canvas, void* ctx) { | ||||||
|     State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25); |     State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25); | ||||||
| 
 | 
 | ||||||
|  |     if(state != NULL) { | ||||||
|         canvas_clear(canvas); |         canvas_clear(canvas); | ||||||
|         canvas_set_color(canvas, ColorBlack); |         canvas_set_color(canvas, ColorBlack); | ||||||
|         canvas_set_font(canvas, FontPrimary); |         canvas_set_font(canvas, FontPrimary); | ||||||
| @ -218,6 +182,7 @@ static void render_callback(Canvas* canvas, void* ctx) { | |||||||
| 
 | 
 | ||||||
|         release_mutex((ValueMutex*)ctx, state); |         release_mutex((ValueMutex*)ctx, state); | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| static void input_callback(InputEvent* input_event, void* ctx) { | static void input_callback(InputEvent* input_event, void* ctx) { | ||||||
|     osMessageQueueId_t event_queue = (QueueHandle_t)ctx; |     osMessageQueueId_t event_queue = (QueueHandle_t)ctx; | ||||||
| @ -228,11 +193,49 @@ static void input_callback(InputEvent* input_event, void* ctx) { | |||||||
|     osMessageQueuePut(event_queue, &event, 0, 0); |     osMessageQueuePut(event_queue, &event, 0, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| osMessageQueueId_t irda_event_queue; | void irda_timer_capture_callback(void* htim, void* comp_ctx) { | ||||||
|  |     TIM_HandleTypeDef* _htim = (TIM_HandleTypeDef*)htim; | ||||||
|  |     osMessageQueueId_t event_queue = (osMessageQueueId_t)comp_ctx; | ||||||
|  | 
 | ||||||
|  |     if(_htim->Instance == TIM2) { | ||||||
|  |         AppEvent event; | ||||||
|  |         event.type = EventTypeRX; | ||||||
|  |         uint32_t channel; | ||||||
|  | 
 | ||||||
|  |         if(_htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { | ||||||
|  |             // falling event
 | ||||||
|  |             event.value.rx.edge = false; | ||||||
|  |             channel = TIM_CHANNEL_1; | ||||||
|  |         } else if(_htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { | ||||||
|  |             // rising event
 | ||||||
|  |             event.value.rx.edge = true; | ||||||
|  |             channel = TIM_CHANNEL_2; | ||||||
|  |         } else { | ||||||
|  |             // not our event
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         event.value.rx.lasted = HAL_TIM_ReadCapturedValue(_htim, channel); | ||||||
|  |         __HAL_TIM_SET_COUNTER(_htim, 0); | ||||||
|  | 
 | ||||||
|  |         osMessageQueuePut(event_queue, &event, 0, 0); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void init_packet( | ||||||
|  |     State* state, | ||||||
|  |     uint8_t index, | ||||||
|  |     IrDAProtocolType protocol, | ||||||
|  |     uint32_t address, | ||||||
|  |     uint32_t command) { | ||||||
|  |     if(index >= IRDA_PACKET_COUNT) return; | ||||||
|  |     state->packets[index].protocol = protocol; | ||||||
|  |     state->packets[index].address = address; | ||||||
|  |     state->packets[index].command = command; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| void irda(void* p) { | void irda(void* p) { | ||||||
|     osMessageQueueId_t event_queue = osMessageQueueNew(32, sizeof(AppEvent), NULL); |     osMessageQueueId_t event_queue = osMessageQueueNew(32, sizeof(AppEvent), NULL); | ||||||
|     irda_event_queue = event_queue; |  | ||||||
| 
 | 
 | ||||||
|     State _state; |     State _state; | ||||||
|     uint8_t mode_count = sizeof(modes) / sizeof(modes[0]); |     uint8_t mode_count = sizeof(modes) / sizeof(modes[0]); | ||||||
| @ -241,8 +244,20 @@ void irda(void* p) { | |||||||
|     _state.carrier_duty_cycle_id = duty_cycles_count - 2; |     _state.carrier_duty_cycle_id = duty_cycles_count - 2; | ||||||
|     _state.carrier_freq = 36000; |     _state.carrier_freq = 36000; | ||||||
|     _state.mode_id = 0; |     _state.mode_id = 0; | ||||||
|     _state.nec_packet_id = 0; |     _state.packet_id = 0; | ||||||
|     _state.samsung_packet_id = 0; | 
 | ||||||
|  |     for(uint8_t i = 0; i < IRDA_PACKET_COUNT; i++) { | ||||||
|  |         init_packet(&_state, i, IRDA_UNKNOWN, 0, 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     init_packet(&_state, 0, IRDA_NEC, 0xFF00, 0x11); | ||||||
|  |     init_packet(&_state, 1, IRDA_NEC, 0xF708, 0x59); | ||||||
|  |     init_packet(&_state, 2, IRDA_NEC, 0xFF00, 0x10); | ||||||
|  |     init_packet(&_state, 3, IRDA_NEC, 0xFF00, 0x15); | ||||||
|  |     init_packet(&_state, 4, IRDA_NEC, 0xFF00, 0x25); | ||||||
|  |     init_packet(&_state, 5, IRDA_SAMSUNG, 0xE0E, 0xF30C); | ||||||
|  |     init_packet(&_state, 6, IRDA_SAMSUNG, 0xE0E, 0xF40D); | ||||||
|  |     init_packet(&_state, 7, IRDA_SAMSUNG, 0xE0E, 0xF50E); | ||||||
| 
 | 
 | ||||||
|     ValueMutex state_mutex; |     ValueMutex state_mutex; | ||||||
|     if(!init_mutex(&state_mutex, &_state, sizeof(State))) { |     if(!init_mutex(&state_mutex, &_state, sizeof(State))) { | ||||||
| @ -265,26 +280,38 @@ void irda(void* p) { | |||||||
| 
 | 
 | ||||||
|     // Red LED
 |     // Red LED
 | ||||||
|     // TODO open record
 |     // TODO open record
 | ||||||
|     const GpioPin* led_record = &led_gpio[0]; |     const GpioPin* red_led_record = &led_gpio[0]; | ||||||
|  |     const GpioPin* green_led_record = &led_gpio[1]; | ||||||
| 
 | 
 | ||||||
|     // configure pin
 |     // configure pin
 | ||||||
|     gpio_init(led_record, GpioModeOutputOpenDrain); |     gpio_init(red_led_record, GpioModeOutputOpenDrain); | ||||||
|  |     gpio_init(green_led_record, GpioModeOutputOpenDrain); | ||||||
| 
 | 
 | ||||||
|     // setup irda rx timer
 |     // setup irda rx timer
 | ||||||
|     tim_irda_rx_init(); |     tim_irda_rx_init(); | ||||||
| 
 | 
 | ||||||
|  |     // add timer capture interrupt
 | ||||||
|  |     api_interrupt_add(irda_timer_capture_callback, InterruptTypeTimerCapture, event_queue); | ||||||
|  | 
 | ||||||
|  |     IrDADecoder* decoder = alloc_decoder(); | ||||||
|  | 
 | ||||||
|     AppEvent event; |     AppEvent event; | ||||||
|     while(1) { |     while(1) { | ||||||
|         osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, osWaitForever); |         osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 500); | ||||||
|         State* state = (State*)acquire_mutex_block(&state_mutex); |         State* state = (State*)acquire_mutex_block(&state_mutex); | ||||||
| 
 | 
 | ||||||
|         if(event_status == osOK) { |         if(event_status == osOK) { | ||||||
|             if(event.type == EventTypeKey) { |             if(event.type == EventTypeKey) { | ||||||
|                 // press events
 |                 // press events
 | ||||||
|                 if(event.value.input.state && event.value.input.input == InputBack) { |                 if(event.value.input.state && event.value.input.input == InputBack) { | ||||||
|                     printf("[irda] bye!\n"); |                     // remove all widgets create by app
 | ||||||
|                     // TODO remove all widgets create by app
 |  | ||||||
|                     widget_enabled_set(widget, false); |                     widget_enabled_set(widget, false); | ||||||
|  |                     gui_remove_widget(gui, widget); | ||||||
|  | 
 | ||||||
|  |                     // free decoder
 | ||||||
|  |                     free_decoder(decoder); | ||||||
|  | 
 | ||||||
|  |                     // exit
 | ||||||
|                     furiac_exit(NULL); |                     furiac_exit(NULL); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| @ -302,7 +329,44 @@ void irda(void* p) { | |||||||
| 
 | 
 | ||||||
|                 modes[state->mode_id].input(&event, state); |                 modes[state->mode_id].input(&event, state); | ||||||
|             } else if(event.type == EventTypeRX) { |             } else if(event.type == EventTypeRX) { | ||||||
|                 gpio_write(led_record, event.value.rx_edge); |                 IrDADecoderOutputData out; | ||||||
|  |                 const uint8_t out_data_length = 4; | ||||||
|  |                 uint8_t out_data[out_data_length]; | ||||||
|  | 
 | ||||||
|  |                 out.data_length = out_data_length; | ||||||
|  |                 out.data = out_data; | ||||||
|  | 
 | ||||||
|  |                 gpio_write(red_led_record, event.value.rx.edge); | ||||||
|  | 
 | ||||||
|  |                 bool decoded = | ||||||
|  |                     process_decoder(decoder, event.value.rx.edge, &event.value.rx.lasted, 1, &out); | ||||||
|  | 
 | ||||||
|  |                 if(decoded) { | ||||||
|  |                     // save only if we in packet mode
 | ||||||
|  |                     if(state->mode_id == 1) { | ||||||
|  |                         if(out.protocol == IRDA_NEC) { | ||||||
|  |                             printf("P=NEC "); | ||||||
|  |                             printf("A=0x%02X%02X ", out_data[1], out_data[0]); | ||||||
|  |                             printf("C=0x%02X ", out_data[2]); | ||||||
|  |                             if(out.flags & IRDA_REPEAT) { | ||||||
|  |                                 printf("R"); | ||||||
|  |                             } | ||||||
|  |                             printf("\r\n"); | ||||||
|  | 
 | ||||||
|  |                             state->packets[state->packet_id].protocol = IRDA_NEC; | ||||||
|  |                             state->packets[state->packet_id].address = out_data[1] << 8 | | ||||||
|  |                                                                        out_data[0]; | ||||||
|  |                             state->packets[state->packet_id].command = out_data[2]; | ||||||
|  |                         } else { | ||||||
|  |                             printf("Unknown protocol\r\n"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     // blink anyway
 | ||||||
|  |                     gpio_write(green_led_record, false); | ||||||
|  |                     delay(10); | ||||||
|  |                     gpio_write(green_led_record, true); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         } else { |         } else { | ||||||
| @ -313,22 +377,3 @@ void irda(void* p) { | |||||||
|         widget_update(widget); |         widget_update(widget); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { |  | ||||||
|     if(htim->Instance == TIM2) { |  | ||||||
|         if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { |  | ||||||
|             // falling event
 |  | ||||||
|             AppEvent event; |  | ||||||
|             event.type = EventTypeRX; |  | ||||||
|             event.value.rx_edge = false; |  | ||||||
|             osMessageQueuePut(irda_event_queue, &event, 0, 0); |  | ||||||
|         } else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) { |  | ||||||
|             // rising event
 |  | ||||||
|             //uint32_t period_in_us = HAL_TIM_ReadCapturedValue();
 |  | ||||||
|             AppEvent event; |  | ||||||
|             event.type = EventTypeRX; |  | ||||||
|             event.value.rx_edge = true; |  | ||||||
|             osMessageQueuePut(irda_event_queue, &event, 0, 0); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -29,14 +29,14 @@ void ir_nec_send_byte(uint8_t data) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void ir_nec_send(uint8_t addr, uint8_t data) { | void ir_nec_send(uint16_t addr, uint8_t data) { | ||||||
|     // nec protocol is:
 |     // nec protocol is:
 | ||||||
|     // preambula + addr + inverse addr + command + inverse command + bit pulse
 |     // preambula + addr + inverse addr + command + inverse command + bit pulse
 | ||||||
|     //
 |     //
 | ||||||
|     // oddly enough, my analyzer (https://github.com/ukw100/IRMP) displays the reverse command
 |     // 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
 |     // 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
 |     // TODO: check the dictionary and check with a known remote
 | ||||||
|     uint8_t nec_packet[4] = {addr, ~(uint8_t)addr, ~(uint8_t)data, data}; |     uint8_t nec_packet[4] = {~(uint8_t)addr, ~(uint8_t)(addr >> 8), ~(uint8_t)data, data}; | ||||||
|     ir_nec_preambula(); |     ir_nec_preambula(); | ||||||
|     ir_nec_send_byte(nec_packet[0]); |     ir_nec_send_byte(nec_packet[0]); | ||||||
|     ir_nec_send_byte(nec_packet[1]); |     ir_nec_send_byte(nec_packet[1]); | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| #pragma once | #pragma once | ||||||
| #include "flipper.h" | #include "flipper.h" | ||||||
| 
 | 
 | ||||||
| void ir_nec_send(uint8_t addr, uint8_t data); | void ir_nec_send(uint16_t addr, uint8_t data); | ||||||
| @ -4,7 +4,8 @@ | |||||||
| typedef void (*InterruptCallback)(void*, void*); | typedef void (*InterruptCallback)(void*, void*); | ||||||
| 
 | 
 | ||||||
| typedef enum { | typedef enum { | ||||||
|     InterruptTypeComparatorTrigger = 0, |     InterruptTypeComparatorTrigger, | ||||||
|  |     InterruptTypeTimerCapture, | ||||||
| } InterruptType; | } InterruptType; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|  | |||||||
| @ -1,7 +1,46 @@ | |||||||
| #include "cmsis_os.h" | #include "cmsis_os.h" | ||||||
| #include "api-hal-tim.h" | #include "api-hal-tim.h" | ||||||
| 
 | 
 | ||||||
|  | /* setup TIM2 CH1 and CH2 to capture rising and falling events */ | ||||||
| void tim_irda_rx_init(void) { | void tim_irda_rx_init(void) { | ||||||
|  |     TIM_ClockConfigTypeDef sClockSourceConfig = {0}; | ||||||
|  |     TIM_MasterConfigTypeDef sMasterConfig = {0}; | ||||||
|  |     TIM_IC_InitTypeDef sConfigIC = {0}; | ||||||
|  | 
 | ||||||
|  |     htim2.Instance = TIM2; | ||||||
|  |     htim2.Init.Prescaler = 64 - 1; | ||||||
|  |     htim2.Init.CounterMode = TIM_COUNTERMODE_UP; | ||||||
|  |     htim2.Init.Period = 4294967295; | ||||||
|  |     htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; | ||||||
|  |     htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; | ||||||
|  |     if(HAL_TIM_Base_Init(&htim2) != HAL_OK) { | ||||||
|  |         Error_Handler(); | ||||||
|  |     } | ||||||
|  |     sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; | ||||||
|  |     if(HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) { | ||||||
|  |         Error_Handler(); | ||||||
|  |     } | ||||||
|  |     if(HAL_TIM_IC_Init(&htim2) != HAL_OK) { | ||||||
|  |         Error_Handler(); | ||||||
|  |     } | ||||||
|  |     sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; | ||||||
|  |     sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; | ||||||
|  |     if(HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) { | ||||||
|  |         Error_Handler(); | ||||||
|  |     } | ||||||
|  |     sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; | ||||||
|  |     sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; | ||||||
|  |     sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; | ||||||
|  |     sConfigIC.ICFilter = 0; | ||||||
|  |     if(HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK) { | ||||||
|  |         Error_Handler(); | ||||||
|  |     } | ||||||
|  |     sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; | ||||||
|  |     sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; | ||||||
|  |     if(HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2) != HAL_OK) { | ||||||
|  |         Error_Handler(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     HAL_NVIC_SetPriority(TIM2_IRQn, 5, 0); |     HAL_NVIC_SetPriority(TIM2_IRQn, 5, 0); | ||||||
|     HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); |     HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); | ||||||
|     HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); |     HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2); | ||||||
|  | |||||||
| @ -4,3 +4,7 @@ | |||||||
| void HAL_COMP_TriggerCallback(COMP_HandleTypeDef* hcomp) { | void HAL_COMP_TriggerCallback(COMP_HandleTypeDef* hcomp) { | ||||||
|     api_interrupt_call(InterruptTypeComparatorTrigger, hcomp); |     api_interrupt_call(InterruptTypeComparatorTrigger, hcomp); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef* htim) { | ||||||
|  |     api_interrupt_call(InterruptTypeTimerCapture, htim); | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DrZlo13
						DrZlo13