#申请原创# #技术资源# @21小跑堂 红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出。红外线的波长在可见光范围外,所以人眼是看不到的。在生活中电视机空调等设备都可以用红外遥控器来控制,正好手里有个HX1838红外接收器和遥控器,想着也给电脑做个红外遥控。
用到的设备:HX1838红外接收器,红外遥控,引出USB的STM32F103核心板
基本原理就是用STM32F103核心板模拟USB键盘,解析HX1838收到的红外信号后转为按键命令发送给电脑
先来实现对红外信号的解析,要解析红外信号就要用到红外接收器,红外接收器是一种能够接收红外光信号并将其转换为电信号的元件。红外接收器会对接收到的红外光信号进行解析,以还原出原始的指令或数据。红外接收头通常被集成在一个元件中,内部包含光敏二极管等关键部件,以及用于信号放大、解调等功能的电路。这里用到的HX1838模块是可接收标准38KHz调制的遥控器信号,通过对进行编程,即可实现对遥控器信号的解码操作。
这套遥控套件使用的是NEC编码,关于NEC编码的介绍可以看这篇帖子 【每周分享】红外遥控中常用的NEC编码https://bbs.21ic.com/icview-3409956-1-1.html
HX1838模块接收到38KHz的红外PWM信号时输出低电平,否则输出高电平,这点和NEC编码中相反,编程时需要注意,程序通过高低电平的时间来解析红外数据。
配套遥控器的按键码
先根据NEC编码规则写出解析红外信号的代码
enum
{
IR_CODE_STEP_IDLE = 0,
IR_CODE_STEP_START,
IR_CODE_STEP_DATA,
IR_CODE_STEP_STOP,
};
enum
{
IR_CODE_FLAG_NONE = 0,
IR_CODE_FLAG_CMD,
IR_CODE_FLAG_REPEAT,
};
#define MAX_TIME_DIFF 60
uint32_t IR_time_us = 0;
uint32_t IR_H_time_us = 0;
uint32_t IR_L_time_us = 0;
uint8_t IR_code[4];
uint8_t IR_code_index = 0;
uint8_t IR_code_bit = 0;
uint8_t IR_code_step = IR_CODE_STEP_IDLE;
uint8_t IR_lev_last = 0;
uint8_t IR_code_flag = IR_CODE_FLAG_NONE;
uint8_t IRIsTimeInRange(uint32_t time,uint32_t targettime,uint32_t diff)
{
if(time < targettime && targettime - time > diff)
return 0;
if(time > targettime && time - targettime > diff)
return 0;
return 1;
}
void IRTimeCount(uint32_t us)
{
IR_time_us += us;
}
void IRDataProc(void)
{
switch(IR_code_step)
{
case IR_CODE_STEP_IDLE:
if(IRIsTimeInRange(IR_H_time_us,9000,MAX_TIME_DIFF) != 0)
{
IR_code_step = IR_CODE_STEP_START;
IR_code_flag = IR_CODE_FLAG_NONE;
}
break;
case IR_CODE_STEP_START:
if(IRIsTimeInRange(IR_L_time_us,4500,MAX_TIME_DIFF) != 0)
{
//开始指令
memset(IR_code,0,4);
IR_code_index = 0;
IR_code_bit = 0;
IR_code_step = IR_CODE_STEP_DATA;
}
else if(IRIsTimeInRange(IR_L_time_us,2250,MAX_TIME_DIFF) != 0)
{
//重复指令
IR_code_step = IR_CODE_STEP_STOP;
IR_code_flag = IR_CODE_FLAG_REPEAT;
}
else
{
IR_code_step = IR_CODE_STEP_IDLE;
IR_code_flag = IR_CODE_FLAG_NONE;
}
break;
case IR_CODE_STEP_DATA:
if(IRIsTimeInRange(IR_H_time_us,560,MAX_TIME_DIFF) != 0)
{
if(IRIsTimeInRange(IR_L_time_us,1690,MAX_TIME_DIFF) != 0)
{
IR_code[IR_code_index] |= (1<<IR_code_bit);
}
else if(IRIsTimeInRange(IR_L_time_us,560,MAX_TIME_DIFF) == 0)
{
IR_code_step = IR_CODE_STEP_IDLE;
IR_code_flag = IR_CODE_FLAG_NONE;
}
IR_code_bit += 1;
if(IR_code_bit == 8)
{
IR_code_bit = 0;
IR_code_index += 1;
if(IR_code_index == 4)
{
IR_code_step = IR_CODE_STEP_STOP;
IR_code_flag = IR_CODE_FLAG_CMD;
}
}
}
else
{
IR_code_step = IR_CODE_STEP_IDLE;
IR_code_flag = IR_CODE_FLAG_NONE;
}
break;
case IR_CODE_STEP_STOP:
if(IRIsTimeInRange(IR_H_time_us,560,MAX_TIME_DIFF) != 0)
{
//结束指令
IR_code_step = IR_CODE_STEP_IDLE;
}
else
{
IR_code_step = IR_CODE_STEP_IDLE;
IR_code_flag = IR_CODE_FLAG_NONE;
}
break;
default:
IR_code_step = IR_CODE_STEP_IDLE;
break;
}
}
void IRIOcheck(uint8_t lev)
{
if(lev != IR_lev_last)
{
IR_lev_last = lev;
if(lev == 0)
{
IR_H_time_us = IR_time_us;
if(IR_code_step == IR_CODE_STEP_IDLE || IR_code_step == IR_CODE_STEP_STOP)
{
IRDataProc();
}
}
else
{
if(IR_code_step == IR_CODE_STEP_IDLE)
{
IR_L_time_us = 0;
}
else
{
IR_L_time_us = IR_time_us;
IRDataProc();
}
}
IR_time_us = 0;
}
}
通过STM32CubeMX配置一个定时器用来对高低电平进行计时,为了避免太过频繁的进入中断,这里配置的周期是20us
在定时器中断函数里调用IRTimeCount传入参数为定时器触发的周期
void TIM4_IRQHandler(void)
{
/* USER CODE BEGIN TIM4_IRQn 0 */
if(LL_TIM_IsActiveFlag_UPDATE(TIM4) == SET)
{
LL_TIM_ClearFlag_UPDATE(TIM4);
IRTimeCount(20);
}
/* USER CODE END TIM4_IRQn 0 */
/* USER CODE BEGIN TIM4_IRQn 1 */
/* USER CODE END TIM4_IRQn 1 */
}
配置检测红外信号的引脚,我这里使用的是PB9开启了双边沿中断
触发中断后检测PB9的电平,需要注意的是要做取反处理
uint8_t ir_pin_it_flag = 0;
void EXTI9_5_IRQHandler(void)
{
/* USER CODE BEGIN EXTI9_5_IRQn 0 */
/* USER CODE END EXTI9_5_IRQn 0 */
if (LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_9) != RESET)
{
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_9);
/* USER CODE BEGIN LL_EXTI_LINE_9 */
ir_pin_it_flag = 1;
/* USER CODE END LL_EXTI_LINE_9 */
}
/* USER CODE BEGIN EXTI9_5_IRQn 1 */
/* USER CODE END EXTI9_5_IRQn 1 */
}
void IRAPP(void)
{
if(ir_pin_it_flag != 0)
{
ir_pin_it_flag = 0;
IRIOcheck(!LL_GPIO_IsInputPinSet(IR_PIN_GPIO_Port,IR_PIN_Pin));
}
}
如果不用中断就直接轮询PB9的状态
void IRAPP(void)
{
IRIOcheck(!LL_GPIO_IsInputPinSet(IR_PIN_GPIO_Port,IR_PIN_Pin));
}
在主循环中调用IRAPP(),检测到正确的红外控制命令后IR_code_flag会被置位,先通过串口打印看看效果
if(IR_code_flag == IR_CODE_FLAG_CMD)
{
printf("rev ir data: %02X%02X%02X%02X\r\n",IR_code[0],IR_code[1],IR_code[2],IR_code[3]);
IR_code_flag = IR_CODE_FLAG_NONE;
}
else if(IR_code_flag == IR_CODE_FLAG_REPEAT)
{
printf("rev ir repeat\r\n");
}
接下来实现用STM32F103模拟键盘,在STM32CubeMX中开启USBDevice
选择HID
生成的代码默认设备类型是鼠标,找个键盘的描述符替换掉usbd_hid.c中的鼠标描述符
//#define HID_MOUSE_REPORT_DESC_SIZE 74U
#define HID_MOUSE_REPORT_DESC_SIZE 63U
/*修改usbd_hid.c中的报告设备描述符*/
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{
0x05, 0x01, // USAGE_PAGE (Generic Desktop) //63
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x05, 0x08, // USAGE_PAGE (LEDs)
0x19, 0x01, // USAGE_MINIMUM (Num Lock)
0x29, 0x05, // USAGE_MAXIMUM (Kana)
0x91, 0x02, // OUTPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x65, // LOGICAL_MAXIMUM (101)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xc0, // END_COLLECTION
};
文件中USBD_HID_OtherSpeedCfgDesc这两处也改掉
看一下键盘HID消息格式
找一份键盘值映射代码
typedef enum
{
KEYBOARD_NONE = 0,
KEYBOARD_ERROR_ROLL_OVER,
KEYBOARD_POST_FAIL,
KEYBOARD_ERROR_UNDEFINED,
KEYBOARD_A,
KEYBOARD_B,
KEYBOARD_C,
KEYBOARD_D,
KEYBOARD_E,
KEYBOARD_F,
KEYBOARD_G,
KEYBOARD_H,
KEYBOARD_I,
KEYBOARD_J,
KEYBOARD_K,
KEYBOARD_L,
KEYBOARD_M,
KEYBOARD_N,
KEYBOARD_O,
KEYBOARD_P,
KEYBOARD_Q,
KEYBOARD_R,
KEYBOARD_S,
KEYBOARD_T,
KEYBOARD_U,
KEYBOARD_V,
KEYBOARD_W,
KEYBOARD_X,
KEYBOARD_Y,
KEYBOARD_Z,
KEYBOARD_1_EXCLAMATION,
KEYBOARD_2_AT,
KEYBOARD_3_NUMBER_SIGN,
KEYBOARD_4_DOLLAR,
KEYBOARD_5_PERCENT,
KEYBOARD_6_CARET,
KEYBOARD_7_AMPERSAND,
KEYBOARD_8_ASTERISK,
KEYBOARD_9_OPARENTHESIS,
KEYBOARD_0_CPARENTHESIS,
KEYBOARD_ENTER,
KEYBOARD_ESCAPE,
KEYBOARD_BACKSPACE,
KEYBOARD_TAB,
KEYBOARD_SPACEBAR,
KEYBOARD_MINUS_UNDERSCORE,
KEYBOARD_EQUAL_PLUS,
KEYBOARD_OBRACKET_AND_OBRACE,
KEYBOARD_CBRACKET_AND_CBRACE,
KEYBOARD_BACKSLASH_VERTICAL_BAR,
KEYBOARD_NONUS_NUMBER_SIGN_TILDE,
KEYBOARD_SEMICOLON_COLON,
KEYBOARD_SINGLE_AND_DOUBLE_QUOTE,
KEYBOARD_GRAVE_ACCENT_AND_TILDE,
KEYBOARD_COMMA_AND_LESS,
KEYBOARD_DOT_GREATER,
KEYBOARD_SLASH_QUESTION,
KEYBOARD_CAPS_LOCK,
KEYBOARD_F1,
KEYBOARD_F2,
KEYBOARD_F3,
KEYBOARD_F4,
KEYBOARD_F5,
KEYBOARD_F6,
KEYBOARD_F7,
KEYBOARD_F8,
KEYBOARD_F9,
KEYBOARD_F10,
KEYBOARD_F11,
KEYBOARD_F12,
KEYBOARD_PRINTSCREEN,
KEYBOARD_SCROLL_LOCK,
KEYBOARD_PAUSE,
KEYBOARD_INSERT,
KEYBOARD_HOME,
KEYBOARD_PAGEUP,
KEYBOARD_DELETE,
KEYBOARD_END1,
KEYBOARD_PAGEDOWN,
KEYBOARD_RIGHTARROW,
KEYBOARD_LEFTARROW,
KEYBOARD_DOWNARROW,
KEYBOARD_UPARROW,
KEYBOARD_KEYBOARDPAD_NUM_LOCK_AND_CLEAR,
KEYBOARD_KEYBOARDPAD_SLASH,
KEYBOARD_KEYBOARDPAD_ASTERIKS,
KEYBOARD_KEYBOARDPAD_MINUS,
KEYBOARD_KEYBOARDPAD_PLUS,
KEYBOARD_KEYBOARDPAD_ENTER,
KEYBOARD_KEYBOARDPAD_1_END,
KEYBOARD_KEYBOARDPAD_2_DOWN_ARROW,
KEYBOARD_KEYBOARDPAD_3_PAGEDN,
KEYBOARD_KEYBOARDPAD_4_LEFT_ARROW,
KEYBOARD_KEYBOARDPAD_5,
KEYBOARD_KEYBOARDPAD_6_RIGHT_ARROW,
KEYBOARD_KEYBOARDPAD_7_HOME,
KEYBOARD_KEYBOARDPAD_8_UP_ARROW,
KEYBOARD_KEYBOARDPAD_9_PAGEUP,
KEYBOARD_KEYBOARDPAD_0_INSERT,
KEYBOARD_KEYBOARDPAD_DECIMAL_SEPARATOR_DELETE,
KEYBOARD_NONUS_BACK_SLASH_VERTICAL_BAR,
KEYBOARD_APPLICATION,
KEYBOARD_POWER,
KEYBOARD_KEYBOARDPAD_EQUAL,
KEYBOARD_F13,
KEYBOARD_F14,
KEYBOARD_F15,
KEYBOARD_F16,
KEYBOARD_F17,
KEYBOARD_F18,
KEYBOARD_F19,
KEYBOARD_F20,
KEYBOARD_F21,
KEYBOARD_F22,
KEYBOARD_F23,
KEYBOARD_F24,
KEYBOARD_EXECUTE,
KEYBOARD_HELP,
KEYBOARD_MENU,
KEYBOARD_SELECT,
KEYBOARD_STOP,
KEYBOARD_AGAIN,
KEYBOARD_UNDO,
KEYBOARD_CUT,
KEYBOARD_COPY,
KEYBOARD_PASTE,
KEYBOARD_FIND,
KEYBOARD_MUTE,
KEYBOARD_VOLUME_UP,
KEYBOARD_VOLUME_DOWN,
KEYBOARD_LOCKING_CAPS_LOCK,
KEYBOARD_LOCKING_NUM_LOCK,
KEYBOARD_LOCKING_SCROLL_LOCK,
KEYBOARD_KEYBOARDPAD_COMMA,
KEYBOARD_KEYBOARDPAD_EQUAL_SIGN,
KEYBOARD_INTERNATIONAL1,
KEYBOARD_INTERNATIONAL2,
KEYBOARD_INTERNATIONAL3,
KEYBOARD_INTERNATIONAL4,
KEYBOARD_INTERNATIONAL5,
KEYBOARD_INTERNATIONAL6,
KEYBOARD_INTERNATIONAL7,
KEYBOARD_INTERNATIONAL8,
KEYBOARD_INTERNATIONAL9,
KEYBOARD_LANG1,
KEYBOARD_LANG2,
KEYBOARD_LANG3,
KEYBOARD_LANG4,
KEYBOARD_LANG5,
KEYBOARD_LANG6,
KEYBOARD_LANG7,
KEYBOARD_LANG8,
KEYBOARD_LANG9,
KEYBOARD_ALTERNATE_ERASE,
KEYBOARD_SYSREQ,
KEYBOARD_CANCEL,
KEYBOARD_CLEAR,
KEYBOARD_PRIOR,
KEYBOARD_RETURN,
KEYBOARD_SEPARATOR,
KEYBOARD_OUT,
KEYBOARD_OPER,
KEYBOARD_CLEAR_AGAIN,
KEYBOARD_CRSEL,
KEYBOARD_EXSEL,
KEYBOARD_RESERVED1,
KEYBOARD_RESERVED2,
KEYBOARD_RESERVED3,
KEYBOARD_RESERVED4,
KEYBOARD_RESERVED5,
KEYBOARD_RESERVED6,
KEYBOARD_RESERVED7,
KEYBOARD_RESERVED8,
KEYBOARD_RESERVED9,
KEYBOARD_RESERVED10,
KEYBOARD_RESERVED11,
KEYBOARD_KEYBOARDPAD_00,
KEYBOARD_KEYBOARDPAD_000,
KEYBOARD_THOUSANDS_SEPARATOR,
KEYBOARD_DECIMAL_SEPARATOR,
KEYBOARD_CURRENCY_UNIT,
KEYBOARD_CURRENCY_SUB_UNIT,
KEYBOARD_KEYBOARDPAD_OPARENTHESIS,
KEYBOARD_KEYBOARDPAD_CPARENTHESIS,
KEYBOARD_KEYBOARDPAD_OBRACE,
KEYBOARD_KEYBOARDPAD_CBRACE,
KEYBOARD_KEYBOARDPAD_TAB,
KEYBOARD_KEYBOARDPAD_BACKSPACE,
KEYBOARD_KEYBOARDPAD_A,
KEYBOARD_KEYBOARDPAD_B,
KEYBOARD_KEYBOARDPAD_C,
KEYBOARD_KEYBOARDPAD_D,
KEYBOARD_KEYBOARDPAD_E,
KEYBOARD_KEYBOARDPAD_F,
KEYBOARD_KEYBOARDPAD_XOR,
KEYBOARD_KEYBOARDPAD_CARET,
KEYBOARD_KEYBOARDPAD_PERCENT,
KEYBOARD_KEYBOARDPAD_LESS,
KEYBOARD_KEYBOARDPAD_GREATER,
KEYBOARD_KEYBOARDPAD_AMPERSAND,
KEYBOARD_KEYBOARDPAD_LOGICAL_AND,
KEYBOARD_KEYBOARDPAD_VERTICAL_BAR,
KEYBOARD_KEYBOARDPAD_LOGIACL_OR,
KEYBOARD_KEYBOARDPAD_COLON,
KEYBOARD_KEYBOARDPAD_NUMBER_SIGN,
KEYBOARD_KEYBOARDPAD_SPACE,
KEYBOARD_KEYBOARDPAD_AT,
KEYBOARD_KEYBOARDPAD_EXCLAMATION_MARK,
KEYBOARD_KEYBOARDPAD_MEMORY_STORE,
KEYBOARD_KEYBOARDPAD_MEMORY_RECALL,
KEYBOARD_KEYBOARDPAD_MEMORY_CLEAR,
KEYBOARD_KEYBOARDPAD_MEMORY_ADD,
KEYBOARD_KEYBOARDPAD_MEMORY_SUBTRACT,
KEYBOARD_KEYBOARDPAD_MEMORY_MULTIPLY,
KEYBOARD_KEYBOARDPAD_MEMORY_DIVIDE,
KEYBOARD_KEYBOARDPAD_PLUSMINUS,
KEYBOARD_KEYBOARDPAD_CLEAR,
KEYBOARD_KEYBOARDPAD_CLEAR_ENTRY,
KEYBOARD_KEYBOARDPAD_BINARY,
KEYBOARD_KEYBOARDPAD_OCTAL,
KEYBOARD_KEYBOARDPAD_DECIMAL,
KEYBOARD_KEYBOARDPAD_HEXADECIMAL,
KEYBOARD_RESERVED12,
KEYBOARD_RESERVED13,
KEYBOARD_LEFTCONTROL,
KEYBOARD_LEFTSHIFT,
KEYBOARD_LEFTALT,
KEYBOARD_LEFT_GUI,
KEYBOARD_RIGHTCONTROL,
KEYBOARD_RIGHTSHIFT,
KEYBOARD_RIGHTALT,
KEYBOARD_RIGHT_GUI,
} USBH_HID_KEYBOARD_VALUE_T;
将遥控器的数字键按照原本的数字输入,方向键控制光标方向,OK键回车,*键退格,#键del
uint8_t IRCode2Key(uint8_t ircode)
{
switch(ircode)
{
case 0x45:
return KEYBOARD_1_EXCLAMATION;
case 0x46:
return KEYBOARD_2_AT;
case 0x47:
return KEYBOARD_3_NUMBER_SIGN;
case 0x44:
return KEYBOARD_4_DOLLAR;
case 0x40:
return KEYBOARD_5_PERCENT;
case 0x43:
return KEYBOARD_6_CARET;
case 0x07:
return KEYBOARD_7_AMPERSAND;
case 0x15:
return KEYBOARD_8_ASTERISK;
case 0x9:
return KEYBOARD_9_OPARENTHESIS;
case 0x16:
return KEYBOARD_BACKSPACE;
case 0x19:
return KEYBOARD_0_CPARENTHESIS;
case 0x0D:
return KEYBOARD_DELETE;
case 0x0C:
break;
case 0x18:
return KEYBOARD_UPARROW;
case 0x5E:
break;
case 0x08:
return KEYBOARD_LEFTARROW;
case 0x1C:
return KEYBOARD_ENTER;
case 0x5A:
return KEYBOARD_RIGHTARROW;
case 0x42:
break;
case 0x52:
return KEYBOARD_DOWNARROW;
case 0x4A:
break;
default:
break;
}
return 0;
}
发送按键数据后还要发送一个空的包,表示按键抬起,不然会一直输入,在定时器哪里增加个变量来计时
uint16_t release_key_time = 0;
void IRTimeCount(uint32_t us)
{
IR_time_us += us;
if(release_key_time > us)
{
release_key_time -= us;
}
else if(release_key_time > 0)
{
release_key_time = 1;
}
}
void IRAPP(void)
{
uint8_t HID_Buffer[8] = {0};
if(ir_pin_it_flag != 0)
{
ir_pin_it_flag = 0;
IRIOcheck(!LL_GPIO_IsInputPinSet(IR_PIN_GPIO_Port,IR_PIN_Pin));
}
if(IR_code_flag == IR_CODE_FLAG_CMD)
{
//printf("rev ir data: %02X%02X%02X%02X\r\n",IR_code[0],IR_code[1],IR_code[2],IR_code[3]);
IR_code_flag = IR_CODE_FLAG_NONE;
if((IR_code[2]^IR_code[3]) == 0xFF)
{
HID_Buffer[2] = IRCode2Key(IR_code[2]);
USBD_HID_SendReport(&hUsbDeviceFS, HID_Buffer, 8);
release_key_time = 20000;
}
}
else if(IR_code_flag == IR_CODE_FLAG_REPEAT)
{
//printf("rev ir repeat\r\n");
IR_code_flag = IR_CODE_FLAG_NONE;
}
if(release_key_time == 1)
{
release_key_time = 0;
USBD_HID_SendReport(&hUsbDeviceFS, HID_Buffer, 8);
}
}
需要注意开启USB后要注释掉串口相关操作,不然会有问题,我这里会出现发送空指令变成输入e的情况,最终效果如下
就实际使用来说蓝牙或者2.4G的遥控更好用,这个项目适合对红外解码和USB通讯的学习。
|
|