这次拿到EVB-L0136/MM32L0136C7P的板子差不多有1个月了,板子做得很精细,用心良苦。
20230114_043003.jpg
1、MM32L0136介绍
使用高性能的 Arm? Cortex-M0+ 的 32 位微控制器,最高工作频率可达 48MHz,内置高速存储器,丰富的增强型 I/O 端口和多种外设。
- 64KB Flash,8KB SRAM
- 内置段码式液晶驱动 SLCD,可驱动 8x36 或 4x40 个段码,支持 COM 和 SEG 引脚重映射,占空比、偏压、帧率和对比度等灵活可调,内置电荷泵可实现在电压下降时依然保持液晶屏清晰
- 3 个 UART 接口(包含1个LPUART接口)、2 个 SPI 接口、2 个 I2S 接口、1 个 I2C 接口
- 1 个红外信号调制模块 IRM,支持 ASK、PSK 或 FSK 调制
- 5 通道 DMA
- 5 个 16 位定时器
- 硬件日历 RTC
- 1 个 12 位 SAR ADC,配置 15 个外部通道,转换速率可达 1MSPS
- 1 个低功耗比较器
- 工作电压为 1.8V - 5.5V
- 支持的温度范围为 -40℃ - 85 ℃
- 多种省电工作模式支持低功耗应用的需求
- 待机(Standby)模式下功耗可低至 300nA
- 关机(Shutdown)模式下功耗可低至 100nA
- 提供 LQFP64 和 LQFP48 封装

适合于多种应用场合:
- 空调遥控器
- 温控器
- 耳、额温枪
- 便携医疗设备
- 气、水、热表
- 小家电

2、安装环境的搭建
评估板说明&用户指南:https://www.mindmotion.com.cn/support/development_tools/evaluation_boards/evboard/mm32l0136c7p/
库函数与例程文件:SDK 软件请从 https://mindsdk.mindmotion.com.cn 下载
MM32L0136支持MDK、IAR、GCC方式编译,需要从网页选择对应的环境下载例程和编译软件扩展,一路顺利安装没有问题。
本人开发环境搭建如下:
MDK-ARM 5.35.0.2 + ulink2 v2.03
直接下载MM32_KEIL_Pack.zip的扩展包,选择MM32L01136CP的Device
142131uypfrxptcxrypt9z.jpg

142131uhe7vgvbt705ak6c.jpg
md01.jpg
md04.jpg
md03.jpg
md02.jpg

3、红外控制介绍
对于控制方式直接参考和借鉴了qinyunti的【灵动微电子 L0136 温控器/遥控器应用】万能遥控器实现,先表示感谢!
遥控器应用
开发板EVB-L0136的红外收发接到了串口UART1,设计的初衷是使用串口进行红外收发的,对于自定义协议来说是没问题的,通过TX串口发送即可。
TX发送为低时载波被拉低,TX发送为高时载波就输出。接收因为是接收二极管已经从载波中滤出信号,直接串口接收即可。
142131z33r3b3aak3lmtlr.jpg
但是格力空调遥控器的协议这样实际是不行的,使用的是不同占空比的脉宽代表0和1。
image.png
所以按qinyunti的方式,选择PA7产生38KHz的PWM载波信号,然后PA7连接到PA9。

这里要说明下,红外发送管使用的是IR26-61C/L510,内部已经带有滤波,直接就是输出解码后的数字信号。
检查到载波信号就输出0,没有信号则输出1。
image.png


4、格力协议介绍
对于格力协议的介绍有好多,不过多的进行描述,其结构如下:
image.png

需要说明是,协议中的数据都是逆序,低位字节在前,高位在后。
其校验码的计算网上说明的大都不对,其计算如下:
校验码 = (模式) + (温度 - 16) + 定时2 + 10 + 左右扫风 - 开关 * 8


5、程序编码
PWM初始化部分:
void BOARD_InitPWM(void)
{
    RCC_EnableAPB2Periphs( RCC_APB2_PERIPH_TIM17, 1 );
    RCC_ResetAPB2Periphs( RCC_APB2_PERIPH_TIM17 );

    /* Setup the counter counting step. */
    TIM_Init_Type tim_init;
    tim_init.ClockFreqHz = 48000000;
    tim_init.StepFreqHz = 1000000;
    tim_init.Period = 26 - 1; /* 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( (TIM_Type *)TIM17, &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( (TIM_Type *)TIM17, TIM_CHN_1, &tim_out_conf );
    TIM_PutChannelValue( (TIM_Type *)TIM17, TIM_CHN_1, 13 );

    /* Start the output compare, only available for the TIM peripheral with this feature. */
    TIM_EnableOutputCompareSwitch( (TIM_Type *)TIM17, true );

    /* Start the counter. */
    //TIM_Start( (TIM_Type *)TIM17 );
}

IO配置
void BOARD_InitPins(void)
{
    GPIO_Init_Type gpio_init;

    /* PA3 - UART2_RX. */
    gpio_init.Pins  = GPIO_PIN_3;
    gpio_init.PinMode  = GPIO_PinMode_In_Floating;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &gpio_init);
    GPIO_PinAFConf(GPIOA, gpio_init.Pins, GPIO_AF_1);

    /* PA2 - UART2_TX. */
    gpio_init.Pins  = GPIO_PIN_2;
    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_1);

    /* KEY 0 - 3 */
    gpio_init.Pins  = BOARD_KEY0_GPIO_PIN;
    gpio_init.PinMode  = GPIO_PinMode_In_PullDown;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_KEY0_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_KEY0_GPIO_PORT, gpio_init.Pins, GPIO_AF_15);

    gpio_init.Pins  = BOARD_KEY1_GPIO_PIN;
    gpio_init.PinMode  = GPIO_PinMode_In_PullUp;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_KEY1_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_KEY1_GPIO_PORT, gpio_init.Pins, GPIO_AF_15);

    gpio_init.Pins  = BOARD_KEY2_GPIO_PIN;
    gpio_init.PinMode  = GPIO_PinMode_In_PullUp;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_KEY2_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_KEY2_GPIO_PORT, gpio_init.Pins, GPIO_AF_15);

    gpio_init.Pins  = BOARD_KEY3_GPIO_PIN;
    gpio_init.PinMode  = GPIO_PinMode_In_PullUp;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_KEY3_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_KEY3_GPIO_PORT, gpio_init.Pins, GPIO_AF_15);

    /* LED 0 - 3 */
    gpio_init.Pins  = BOARD_LED0_GPIO_PIN;
    gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_LED0_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_LED0_GPIO_PORT, gpio_init.Pins, GPIO_AF_15);

    gpio_init.Pins  = BOARD_LED1_GPIO_PIN;
    gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_LED1_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_LED1_GPIO_PORT, gpio_init.Pins, GPIO_AF_15);

    gpio_init.Pins  = BOARD_LED2_GPIO_PIN;
    gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_LED2_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_LED2_GPIO_PORT, gpio_init.Pins, GPIO_AF_15);

    gpio_init.Pins  = BOARD_LED3_GPIO_PIN;
    gpio_init.PinMode  = GPIO_PinMode_Out_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_LED3_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_LED3_GPIO_PORT, gpio_init.Pins, GPIO_AF_15);

    /* PWM */
    gpio_init.Pins = BOARD_PWM_GPIO_PIN;
    gpio_init.PinMode = GPIO_PinMode_AF_PushPull;
    gpio_init.Speed = GPIO_Speed_50MHz;
    GPIO_Init(BOARD_PWM_GPIO_PORT, &gpio_init);
    GPIO_PinAFConf(BOARD_PWM_GPIO_PORT, gpio_init.Pins, GPIO_AF_5);   

}

其它按gpio例程配置没有修改

主程序:

/*
* Definitions.
*/
/* PWM start/stop */
#define ir_outh() TIM_Stop( (TIM_Type *)TIM17 )
#define ir_outl() TIM_Start( (TIM_Type *)TIM17 )


/*
* Declerations.
*/
/* 定义信号的脉冲宽度 (us) */
#define MAX_IR_INDEX    5
const uint32_t IR_D_HOLDTIME[MAX_IR_INDEX][2] =
    { {600, 600}, {600, 1600}, {9000, 4500}, {600, 20000}, { 600, 40000 } };

#define IR_D0_INDEX     0
#define IR_D1_INDEX     1
#define IR_S_INDEX      2
#define IR_C_INDEX      3
#define IR_R_INDEX      4

/*
* 发送数据数组定义
*/
static uint32_t s_ir_index = 0;
static uint8_t  s_ir_order = 0;
static uint32_t s_ir_num = 0;
static uint32_t s_ir_done = 1;

#define MAX_IO_STATE    256
static uint8_t s_iostate[MAX_IO_STATE];



/*
* TIM16定义,计算时间使用 1us
*/
void time_init( uint32_t period )
{
    TIM_Init_Type timinit;

    TIM_Stop( (TIM_Type *)TIM16 );
    RCC_EnableAPB2Periphs( RCC_APB2_PERIPH_TIM16, 1 );
    RCC_ResetAPB2Periphs( RCC_APB2_PERIPH_TIM16 );

    timinit.ClockFreqHz = 48000000;
    timinit.StepFreqHz = 1000000;
    timinit.Period = period;
    timinit.EnablePreloadPeriod = false;
    timinit.PeriodMode = TIM_PeriodMode_Continuous;         //TIM_PeriodMode_OneTimeRun;
    timinit.CountMode = TIM_CountMode_Increasing;

    TIM_Init( (TIM_Type *)TIM16, &timinit );
    TIM_DoSwTrigger( (TIM_Type *)TIM16, 1u << 0 );
    TIM_ClearInterruptStatus( (TIM_Type *)TIM16, TIM_GetInterruptStatus((TIM_Type *)TIM16) );
    TIM_EnableInterrupts( (TIM_Type *)TIM16, TIM_INT_UPDATE_PERIOD, 1 );
    NVIC_EnableIRQ( TIM16_IRQn );
    TIM_Start( (TIM_Type *)TIM16 );
}


/*
* 载波的发送
* 这里要注意TIM_PutChannelValue( (TIM_Type *)TIM17, TIM_CHN_1, 0 )的配置
* 在PWM停止后保证载波的输出为低电平
*/
void ir_handle( void )
{
    GPIO_Init_Type gpio_init;

    if( s_ir_index < s_ir_num )
    {
        if( ( s_ir_order <= 1 ) && ( s_iostate[s_ir_index] < MAX_IR_INDEX ) )
        {            
            if( s_ir_order == 0 )
            {
                TIM_PutChannelValue( (TIM_Type *)TIM17, TIM_CHN_1, 13 );
                ir_outl();
            }
            else
            {
                TIM_PutChannelValue( (TIM_Type *)TIM17, TIM_CHN_1, 0 );
                ir_outh();
            }
            time_init( IR_D_HOLDTIME[s_iostate[s_ir_index]][s_ir_order] );
        }
        else
        {
            TIM_Stop( (TIM_Type *)TIM16 );
            s_ir_done = 1;
            s_ir_num = 0;
            s_ir_index = 0;
            s_ir_order = 0;
        }
        if( s_ir_order < 1 )
        {
            s_ir_order++;
        }
        else
        {
            s_ir_index++;
            s_ir_order = 0;
        }
    }
    else
    {
        TIM_Stop( (TIM_Type *)TIM16 );
        s_ir_done = 1;
        s_ir_num = 0;
        s_ir_index = 0;
        s_ir_order = 0;
    }
}

/*
* 生成协议
*/
/*
* key          开关 0 ~ 1
* mode         模式 0 ~ 4
* fanspeed     风速 0 ~ 3
* temperature  温度 16 ~ 30
*/
uint8_t build_gree_checksum( int mode, int key, int fanspeed, int temperture )
{
    uint32_t i;
    i = mode + ( temperture - 16) + 10 - key * 8;
    return (uint8_t)( i & 0xF );
}

uint32_t build_gree_data( int mode, int key, int fanspeed, int temperture )
{
    uint8_t chk;
    int i;

    i = 0;

    // start
    s_iostate[i++] = IR_S_INDEX;

    // 35bits
    // mode
    s_iostate[i++] = (mode >> 0) & 0x1;
    s_iostate[i++] = (mode >> 1) & 0x1;
    s_iostate[i++] = (mode >> 2) & 0x1;
    // key
    s_iostate[i++] = (key >> 0) & 0x1;
    // fanspeed
    s_iostate[i++] = (fanspeed >> 0) & 0x1;
    s_iostate[i++] = (fanspeed >> 1) & 0x1;
    // scan / sleep
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;

    // temperture
    s_iostate[i++] = ((temperture - 16) >> 0) & 0x1;
    s_iostate[i++] = ((temperture - 16) >> 1) & 0x1;
    s_iostate[i++] = ((temperture - 16) >> 2) & 0x1;
    s_iostate[i++] = ((temperture - 16) >> 3) & 0x1;
    // time
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;

    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    // super / lamp / health / dry
    s_iostate[i++] = 0;
    s_iostate[i++] = 1;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;

    // reserve
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    // reserve
    s_iostate[i++] = 1;
    s_iostate[i++] = 0;
    s_iostate[i++] = 1;
    s_iostate[i++] = 0;

    // reserve
    s_iostate[i++] = 0;
    s_iostate[i++] = 1;
    s_iostate[i++] = 0;

    // contiune
    s_iostate[i++] = IR_C_INDEX;


.....


    // reserve
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    s_iostate[i++] = 0;
    // check
    chk = build_gree_checksum( mode, key, fanspeed, temperture );
    s_iostate[i++] = ( chk >> 0 ) & 0x1;
    s_iostate[i++] = ( chk >> 1 ) & 0x1;
    s_iostate[i++] = ( chk >> 2 ) & 0x1;
    s_iostate[i++] = ( chk >> 3 ) & 0x1;

    // contiune
    s_iostate[i++] = IR_C_INDEX;

    return i;
}


/*
* 按键发送控制
*/
int main(void)
{
    int key0_sta, key1_sta, key2_sta, key3_sta;
    int num;

    BOARD_Init();

    printf("\r\ngpio_basic example.\r\n");

    GPIO_WriteBit( BOARD_LED0_GPIO_PORT, BOARD_LED0_GPIO_PIN, 1u );
    GPIO_WriteBit( BOARD_LED1_GPIO_PORT, BOARD_LED1_GPIO_PIN, 1u );
    GPIO_WriteBit( BOARD_LED2_GPIO_PORT, BOARD_LED2_GPIO_PIN, 1u );
    GPIO_WriteBit( BOARD_LED3_GPIO_PORT, BOARD_LED3_GPIO_PIN, 1u );

    key0_sta = key1_sta = key2_sta = key3_sta = 0;

    while(1)
    {
        if( GPIO_ReadInDataBit( BOARD_KEY0_GPIO_PORT, BOARD_KEY0_GPIO_PIN ) == 1 )
        {
            /* key is pressed. */
            GPIO_WriteBit( BOARD_LED0_GPIO_PORT, BOARD_LED0_GPIO_PIN, 0u );
            key0_sta = 1;
        }
        else if( key0_sta == 1 )
        {
            /* send ir, mode auto */
            num = build_gree_data( 0, 1, 0, 25 );
            ir_start( num );
            while( ir_isdone() == 0 ) mdelay( 200 );
            GPIO_WriteBit( BOARD_LED0_GPIO_PORT, BOARD_LED0_GPIO_PIN, 1u );
            key0_sta = 0;
        }

        if( GPIO_ReadInDataBit( BOARD_KEY1_GPIO_PORT, BOARD_KEY1_GPIO_PIN ) == 0 )
        {
            /* key is pressed. */
            GPIO_WriteBit( BOARD_LED1_GPIO_PORT, BOARD_LED1_GPIO_PIN, 0u );
            key1_sta = 1;
        }
        else if( key1_sta == 1 )
        {
            /* send ir, mode cool */
            num = build_gree_data( 1, 1, 0, 20 );
            ir_start( num );
            while( ir_isdone() == 0 ) mdelay( 200 );
            GPIO_WriteBit( BOARD_LED1_GPIO_PORT, BOARD_LED1_GPIO_PIN, 1u );
            key1_sta = 0;
        }

        if( GPIO_ReadInDataBit( BOARD_KEY2_GPIO_PORT, BOARD_KEY2_GPIO_PIN ) == 0 )
        {
            /* key is pressed. */
            GPIO_WriteBit( BOARD_LED2_GPIO_PORT, BOARD_LED2_GPIO_PIN, 0u );
            key2_sta = 1;
        }
        else if( key2_sta == 1 )
        {
            /* send ir, mode heater */
            num = build_gree_data( 4, 1, 0, 24 );
            ir_start( num );
            while( ir_isdone() == 0 ) mdelay( 200 );
            GPIO_WriteBit( BOARD_LED2_GPIO_PORT, BOARD_LED2_GPIO_PIN, 1u );
            key2_sta = 0;
        }

        if( GPIO_ReadInDataBit( BOARD_KEY3_GPIO_PORT, BOARD_KEY3_GPIO_PIN ) == 0 )
        {
            /* key is pressed. */
            //GPIO_WriteBit( BOARD_LED3_GPIO_PORT, BOARD_LED3_GPIO_PIN, 0u );
            key3_sta = 1;
        }
        else if( key3_sta == 1 )
        {
            /* send ir */
            num = build_gree_data( 0, 0, 0, 25 );
            ir_start( num );
            while( ir_isdone() == 0 ) mdelay( 200 );
            //GPIO_WriteBit( BOARD_LED3_GPIO_PORT, BOARD_LED3_GPIO_PIN, 1u );
            key3_sta = 0;
        }
    }
}

发送波形:
DS1Z_QuickPrint4.png

接收波形:
DS1Z_QuickPrint7.png


6、总结
从目前开发环境的使用看,MM32L0136芯片的各类资源及SDK已经比较完善和成熟,库文件结构也比较清晰,常用寄存器和参数的调用都进行了封装。