本帖最后由 ekerV5 于 2023-1-5 17:57 编辑

灵动微电子 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);
}

红外调制器
forum.jpg
红外调制器初始化,此处采用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);
forum.jpg
forum.jpg

按键、LED
原理图的LED标号是LD0 ~ LD3,开发板上丝印是LD1 ~ LD4,对应的GPIO分别是PB9、PB10、PB11、PC0。4个按键K1 ~ K4,对应的GPIO分别是PB2、PD5、PA8、PB5。
forum.jpg
forum.jpg
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四种不同数据,对一针红外码不同数据的时间进行定义。
forum.jpg
#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);
    }
}