灵动微电子 L0136 小米电视红外遥控器实现
概述
本文基于灵动微电子的EVB-L0136开发板实现小米电视的红外遥控器功能。
主要用到的功能模块:
- TIM3实现PWM,作为红外载波,频率38K,占空比50%;
- TIM16用于定时,作为红外数据有载波和无载波的时间;
- IRM红外调制器用于控制红外的输出和停止;
- 4个按键,分别对应4个小米电视遥控器功能按键;
- 在按键按下时,对应附近的LED会亮,按键释放后会灭
红外载波
用TIM3实现PWM,作为红外调制器的载波信号。
此处设置StepFreqHz为1000000,Period为26,那么得到的PWM频率即为1000000 ÷ 26 = 38461 Hz。
/* init tim to provide carrier signal to irm. */
void BOARD_InitTIM(void)
{
/* Setup the counter counting step. */
TIM_Init_Type tim_init;
tim_init.ClockFreqHz = CLOCK_SYS_FREQ;
tim_init.StepFreqHz = BOARD_PWM1_TIM_STEP_FREQ;
tim_init.Period = BOARD_PWM1_TIM_UPDATE_PERIOD - 1u; /* the counter would return to the base on next step. */
tim_init.EnablePreloadPeriod = false; /* no need preload, load period value immediately. */
tim_init.PeriodMode = TIM_PeriodMode_Continuous;
tim_init.CountMode = TIM_CountMode_Increasing;
TIM_Init(BOARD_PWM1_TIM_PORT, &tim_init);
/* Setup the PWM output channel. */
TIM_OutputCompareConf_Type tim_out_conf;
tim_out_conf.ChannelValue = 0u;
tim_out_conf.EnableFastOutput = false;
tim_out_conf.EnablePreLoadChannelValue = false; /* disable preload, load channel value immediately. */
tim_out_conf.RefOutMode = TIM_OutputCompareRefOut_FallingEdgeOnMatch;
tim_out_conf.ClearRefOutOnExtTrigger = false;
tim_out_conf.PinPolarity = TIM_PinPolarity_Rising;
TIM_EnableOutputCompare(BOARD_PWM1_TIM_PORT, TIM_CHN_1, &tim_out_conf);
/* Start the output compare, only available for the TIM peripheral with this feature. */
TIM_EnableOutputCompareSwitch(BOARD_PWM1_TIM_PORT, true);
/* Start the counter. */
TIM_PutChannelValue(BOARD_PWM1_TIM_PORT, TIM_CHN_1, BOARD_PWM1_TIM_UPDATE_PERIOD / 2u); /* 50% duty cycle. */
TIM_Start(BOARD_PWM1_TIM_PORT);
}
红外定时器
用TIM16实现定时,此处设置StepFreqHz为1000000,即计数精度为1us,Period值从参数传入,用于设置us级定时。
/* Setup the timer. */
void app_tim_init(uint32_t period)
{
/* Set the counter counting step. */
TIM_Init_Type tim_init;
tim_init.ClockFreqHz = BOARD_TIM_FREQ;
tim_init.StepFreqHz = APP_TIM_UPDATE_PERIOD;
tim_init.Period = period - 1u;
tim_init.EnablePreloadPeriod = false;
tim_init.PeriodMode = TIM_PeriodMode_Continuous;
tim_init.CountMode = TIM_CountMode_Increasing;
TIM_Init(BOARD_TIM_PORT, &tim_init);
/* Enable interrupt. */
NVIC_EnableIRQ(BOARD_TIM_IRQn);
TIM_EnableInterrupts(BOARD_TIM_PORT, TIM_INT_UPDATE_PERIOD, true);
/* Start the counter. */
TIM_Start(BOARD_TIM_PORT);
}
红外调制器
红外调制器初始化,此处采用ASK调制方式,并选择载波信号为TIM3的OC1通道。
/* Init IRM. */
void app_irm_init_ask(void)
{
IRM_Init_Type irm_init_ask;
irm_init_ask.Modulation = IRM_Modulation_ASK; /* Let IRM work in ASK modulation mode. */
irm_init_ask.CarrierIn0 = BOARD_IRM_CARRIER_IN; /* Carrier signal select. */
irm_init_ask.SignalIn = BOARD_IRM_SIGNAL_IN; /* Signal source select. */
irm_init_ask.EnableOutInvert = false; /* Not invert output. */
IRM_Init(BOARD_IRM_PORT, &irm_init_ask); /* Init IRM. */
IRM_Enable(BOARD_IRM_PORT, true); /* Enable IRM. */
}
根据原理图,我们知道IR发射脚是PA9,所以要把PA9映射到红外调制器上。
/* PA9 - IRM_OUT. */
gpio_init.Pins = GPIO_PIN_9;
gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
GPIO_PinAFConf(GPIOA, gpio_init.Pins, GPIO_AF_7);
按键、LED
原理图的LED标号是LD0 ~ LD3,开发板上丝印是LD1 ~ LD4,对应的GPIO分别是PB9、PB10、PB11、PC0。4个按键K1 ~ K4,对应的GPIO分别是PB2、PD5、PA8、PB5。
4个LED设置为推挽输出,输出低电平点亮LED,否则熄灭LED。
按键K1按下应该为高电平,所以设置PB2为下拉输出;其它3个按键按下应该为低电平,所以设置对应GPIO为上拉输出。
初始化代码如下:
/* PB2 - K1. */
gpio_init.Pins = GPIO_PIN_2;
gpio_init.PinMode = GPIO_PinMode_In_PullDown;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_15);
/* PD5 - K2. */
gpio_init.Pins = GPIO_PIN_5;
gpio_init.PinMode = GPIO_PinMode_In_PullUp;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &gpio_init);
GPIO_PinAFConf(GPIOD, gpio_init.Pins, GPIO_AF_15);
/* PA8 - K3. */
gpio_init.Pins = GPIO_PIN_8;
gpio_init.PinMode = GPIO_PinMode_In_PullUp;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init);
GPIO_PinAFConf(GPIOA, gpio_init.Pins, GPIO_AF_15);
/* PB5 - K4. */
gpio_init.Pins = GPIO_PIN_5;
gpio_init.PinMode = GPIO_PinMode_In_PullUp;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_15);
/* PB9 - LD1. */
gpio_init.Pins = GPIO_PIN_9;
gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_15);
/* PB10 - LD2. */
gpio_init.Pins = GPIO_PIN_10;
gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_15);
/* PB11 - LD3. */
gpio_init.Pins = GPIO_PIN_11;
gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &gpio_init);
GPIO_PinAFConf(GPIOB, gpio_init.Pins, GPIO_AF_15);
/* PC0 - LD4. */
gpio_init.Pins = GPIO_PIN_0;
gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
gpio_init.Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &gpio_init);
GPIO_PinAFConf(GPIOC, gpio_init.Pins, GPIO_AF_15);
小米红外
用小米手机发射小米电视遥控器红外码(以下简称红外码),用红外解码仪解析,发现红外码由引导码、键码、停止位组成,其中键码包含0、1、2、3四种不同数据,对一针红外码不同数据的时间进行定义。
#define MI_IR_FRAME_TIME 30000 //us
#define MI_IR_LEAD_HIGH_TIME 1000
#define MI_IR_LEAD_LOW_TIME 580
#define MI_IR_DATA_HIGH_TIME 580
#define MI_IR_0_LOW_TIME 580
#define MI_IR_1_LOW_TIME 880
#define MI_IR_2_LOW_TIME 1180
#define MI_IR_3_LOW_TIME 1480
#define MI_IR_STOP_HIGH_TIME 580
#define MI_IR_FRAME_BIT_NUM 24
总共有11个按键,定义一个结构体数据用于保存所有按键键值
enum
{
MI_IR_RIGHT,
MI_IR_OK,
MI_IR_DOWN,
MI_IR_UP,
MI_IR_BACK,
MI_IR_LEFT,
MI_IR_POWER,
MI_IR_MENU,
MI_IR_VOL_DEC,
MI_IR_HOME,
MI_IR_VOL_INC,
MI_IR_MAX,
};
typedef struct
{
uint32_t data_code;
uint8_t parity;
} mi_ir_t;
static mi_ir_t mi_ir[11] =
{
{0x20120030, 0x02}, /*右 */\
{0x20120031, 0x03}, /*确定 */\
{0x20120012, 0x20}, /*下 */\
{0x20120011, 0x23}, /*上 */\
{0x20120013, 0x21}, /*返回 */\
{0x20120023, 0x11}, /*左 */\
{0x03303030, 0x33}, /*电源 */\
{0x20120010, 0x22}, /*菜单 */\
{0x20120033, 0x01}, /*音量-*/\
{0x20120020, 0x12}, /*主页 */\
{0x20120032, 0x00}, /*音量+*/\
};
定义一个结构体数组用于临时保存红外码的时间数据
typedef struct
{
uint8_t level;
uint32_t time;
} ir_t;
static ir_t ir_data[MI_IR_FRAME_BIT_NUM+1] =
{
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{1, 0},
{0, 0},
{0xff, 0x00},
};
mi_ir_set_data函数是在需要发射红外前设置红外码的时间数据,即设置ir_data数组的元素值。
static void mi_ir_set_data(uint8_t mi_ir_index)
{
uint32_t time_count = 0;
if (mi_ir_index < MI_IR_MAX)
{
ir_data[0].time = MI_IR_LEAD_HIGH_TIME;
ir_data[1].time = MI_IR_LEAD_LOW_TIME;
for (int i = 0; i < 8; i++)
{
uint8_t temp = (mi_ir[mi_ir_index].data_code >> ((7-i)*4)) & 0xf;
ir_data[2*(i+1)].time = MI_IR_DATA_HIGH_TIME;
switch (temp)
{
case 0:
ir_data[2*(i+1) + 1].time = MI_IR_0_LOW_TIME;
break;
case 1:
ir_data[2*(i+1) + 1].time = MI_IR_1_LOW_TIME;
break;
case 2:
ir_data[2*(i+1) + 1].time = MI_IR_2_LOW_TIME;
break;
case 3:
ir_data[2*(i+1) + 1].time = MI_IR_3_LOW_TIME;
break;
default:
break;
}
}
for (int i = 0; i < 2; i++)
{
uint8_t temp = (mi_ir[mi_ir_index].parity >> ((1-i)*4)) & 0xf;
ir_data[2*(i+9)].time = MI_IR_DATA_HIGH_TIME;
switch (temp)
{
case 0:
ir_data[2*(i+9) + 1].time = MI_IR_0_LOW_TIME;
break;
case 1:
ir_data[2*(i+9) + 1].time = MI_IR_1_LOW_TIME;
break;
case 2:
ir_data[2*(i+9) + 1].time = MI_IR_2_LOW_TIME;
break;
case 3:
ir_data[2*(i+9) + 1].time = MI_IR_3_LOW_TIME;
break;
default:
break;
}
}
ir_data[22].time = MI_IR_STOP_HIGH_TIME;
for (int i = 0; i < (MI_IR_FRAME_BIT_NUM-1); i++)
{
printf("ir_data[%d].time = %d\n", i, ir_data.time);
time_count += ir_data.time;
}
ir_data[23].time = MI_IR_FRAME_TIME - time_count;
printf("ir_data[23].time = %d\n", ir_data[23].time);
}
}