mm32l0136测试1.jpg
目录
0功能简介
1MM32L0130芯片简介
2、开发板硬件模块介绍
2.1ADC—数模转换器
2.2SLCD-单色无源液晶显示器
3、固件代码解读
3.1 ADC配置
3.2 SLCD配置
4、开发板测评视频
5、总结

0、功能简介
通过ADC采集模拟电压量用SLCD显示。

1MM32L0130芯片简介
MM32L0130 微控制器搭载 Arm® Cortex®-M0+内核,最高工作频率可达 48MHz。 内置64KB高速存储器, 并集成了丰富的I/O 端口和外设模块。本产品包含 1 12 位的 ADC1 个比较器、 2 16 位通用定时器、 2 16 位基本定时器、 1 个低功耗定时器和 1 RTC计数器, 还包含标准的通信接口: 2 UART 接口、 1 个低功耗 UART接口、 2 SPI 接口、 2 I2S 接口和 1 I2C 接口。 此外,本产品还内置了段码式液晶驱动模块(SLCD)和红外信号调制模块(IRM)。本产品系列工作电压为 1.8V 5.5V,工作温度范围(环境温度) 为 -40°C +85°C。内置多种省电工作模式保证低功耗应用的要求。这些丰富的外设配置,使得本产品微控制器适合于多种应用场合:
空调遥控器
温控器
耳、额温枪
便携医疗设备
气、水、热表
小家电
使用一款芯片前下载其规格书,链接如下:
依据系统架构可获知ADC使用APB2时钟源,SLCD使用APB1时钟源,最大可到48MHz;
2、开发板硬件模块介绍
拿到开发板后,阅读了如下链接的开发板内容
从中可知模拟功能实物对照如下:
SLCD(GDC0689TP-11)引脚与MCU连接对应关系
2.1ADC—数模转换器
从用户手册与数据手册可知,ADC通道共有15个外部通道+1个内部通道;
ADC 是 12 位的逐次逼近型(SAR)模拟数字转换器,可以将模拟信号转换成数字信号。
从系统框图中可获知如下信息:
1,选用APB2总线2,15+1个通道
3,数据位数12Bit
4,可触发中断
5,参考源Vref,外部采用VDD,内部1.2V
通过用户手册可知,通道转换分为普通,任意,注入。相比其他M0类型更新颖的通道选择,也增加了诸多细节;
如:在配置任意通道转换后,如果使能了自动注入通道转换,任意通道转换结束后,自动进入注入通道的转换。如果任意通道是连续扫描模式,需要清除 ADC_ADCR.ADST才能停止 A/D转换。
如:注入通道选择需要采用开启弥补方式得到合理数据;
2.2SLCD-单色无源液晶显示器
SLCD 驱动器是用于单色无源液晶显示器(SLCD)的数字驱动器,具有多达 8 个公共端和多达 63个分段端, SLCD引脚最多为 64个,因此最多可驱动 24060x4)或 44856x8)个段码。
其技术特征如下所示:
显示帧率灵活控制;
支持静态、1/2、1/3、1/4、1/6 和 1/8 占空比;
支持 1/2、1/3 和 1/4 偏置电压设置;
为了存储显示数据,内置了 16*32bit 显示数据寄存器 ;
通过软件来调整 SLCD 输出电压,来调节对比度;
外围电路简单,不需要模拟器件支持;
1)内嵌电容升压器来得到比电源电压更高而且不受其影响的 SLCD 驱动电压。升压器产生的 SLCD 驱动电压范围可调,可以匹配支持 3V 或者 5V 的 LCD 屏幕
2)SLCD 驱动电源可以通过软件来选择内部电源或外部电源。
3)可以选择使用内嵌电容分压器对 SLCD 驱动电压进行分压,得到驱动电压的中间值(VLCDrail1,VLCDrail2,VLCDrail3,VLCDrail4) 。
两种调整显示对比度的方法 ;
1)当采用内部升压器来提供 VLCD 电源时,可以通过软件调节 VLCD 输出电压
2)其它情况下可以在每帧显示之间插入死区时间 ;
支持以下低功耗模式:低功耗运行模式,睡眠模式,低功耗睡眠模式,停止模式,深度停止模式,待机模式;在不需要显示的时候,可以完全关闭 SLCD 驱动以达到降低功耗的目的 ;
支持相位反转模式,降低功耗和 EMI ;
每一帧显示开始的时候,通过中断信号与软件同步,更新显示数据 ;
闪烁功能
1)可以从所有段码中任意选择 1 到 8 个段码闪烁显示,也可以闪烁显示全部段码 。
2)在静态、1/2、1/3、1/4 占空比模式下可以闪烁显示任意段码。
3)软件选择闪烁频率,支持闪烁频率 0.5Hz, 1Hz, 2Hz 或 4Hz 。
灵活的引脚复用功能,可以配置任意 LCD 驱动引脚成为 COM 或者 SEG 功能,SLCD 的驱动引脚在没有被配置成 SLCD 功能的时候,可以作为 GPIO 引脚来使用;
SLCD 驱动电平(VLCDrail1,VLCDrail2,VLCDrail3,VLCDrail4)的去耦合功能
支持低功耗驱动波形;
支持DMA传输;
支持中断(帧结束中断(End of frame)、闪烁周期中断(Blink cycle));
SLCD 驱动模块包括以下几个基本的子模块,如下图所示。
l 显示数据寄存器
l SLCD输出驱动引脚
l 时钟产生单元,包括时钟预分频器、帧时钟分频器、电荷泵时钟分频器和闪烁时钟分频器
l 闪烁控制器
l 内置升压电荷泵和偏置电压生成单元
l 时序控制和波形发生器
为了存储显示数据,SLCD 驱动模块内置了 16 32 比特显示数据寄存器。显示数据寄存器中的比特位与 LCD显示屏上的段码一一对应,如果要点亮LCD 显示屏上的某个段码,则需要把显示数据寄存器中的相对应的比特写为‘1’; 反之如果要熄灭某个段码, 则需要把对应的比特写为‘0’
再介绍单色液晶显示器(GDC0689TP-11)的原理。从规格书或图纸可知每个引脚功能定义;
比如要点亮一个字段码数字为”8”,需要分别在Pin28与Pin12(COM1)、Pin13(COM2)、Pin14(COM3)、Pin15(COM4)导通,以及Pin29与Pin12(COM1)、Pin13(COM2)、Pin14(COM3)、Pin15(COM4);
比如点亮一个“mV”图标,Pin22与Pin15(COM4)接通,也即Pin22有高电平,Pin15为低电平;

3、固件代码解读
本次评测的EVB-L0136开发板上板载的MCU为MM32L0136C7P,本次测试使用的开发环境为MDK-ARM Keil 5.34。搭建的步骤如下:
资料下载;
单击即可下载;
注:Jli nk无法识别MM32L0130,在设备支持包内有驱动(MM32_PACKAGE_Segger.exe),需要自行安装。

3.1 ADC配置
void ADC1_Config(void)
{
    GPIO_InitTypeDefGPIO_InitStructure;
    ADC_InitTypeDefADC_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA,ENABLE);           //开启GPIOA的外设时钟
//配置gpio引脚
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5;        //PA1,PA4,PA5为模拟输入
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);                 //初始配置gpioA
//配置ADC1
    ADC_StructInit(&ADC_InitStructure);                       //初始化并设定指针结构体
    ADC_DeInit(ADC1);                                     //复位ADC1初始化
   
    RCC_APB2PeriphClockCmd(RCC_APB2ENR_ADC1,ENABLE);      //开启ADC1的外设时钟
   
    ADC_InitStructure.ADC_ContinuousConvMode= DISABLE;       //关闭连续转换模式
    ADC_InitStructure.ADC_Resolution= ADC_Resolution_12b;    //数据分辨率,12bit
    ADC_InitStructure.ADC_DataAlign= ADC_DataAlign_Right;    //数据右对齐
    ADC_InitStructure.ADC_Mode= ADC_Mode_Imm;                //ADC_Mode_Imm单次扫描,ADC_Mode_Scan周期扫描,ADC_Mode_Continue,连续扫描
    ADC_InitStructure.ADC_PRESCARE=ADC_PCLK2_PRESCARE_16;    //ADC_clock 的预分频系数,48M/16=3M,sample clock
    ADC_InitStructure.ADC_ExternalTrigConv= ADC1_ExternalTrigConv_T3_CC3;    //外部触摸源有12种,对于不需要外部出发,可由软件启动函数开启
    ADC_Init(ADC1,&ADC_InitStructure);
//设置采样周期  
ADC_AnychanChannelConfig(ADC1,ADC_Channel_1,0,(u32)ADC_Samctl_7_5);//Fsample=3M/(12bit+0.5+7.5)63.2khz
   ADC_AnychanChannelConfig(ADC1,ADC_Channel_4,0,(u32)ADC_Samctl_7_5);//Fsample=3M/(12bit+0.5+7.5)63.2khz
   ADC_AnychanChannelConfig(ADC1,ADC_Channel_5,0,(u32)ADC_Samctl_7_5);//Fsample=3M/(12bit+0.5+7.5)63.2khz
    ADC_Cmd(ADC1,ENABLE);                                 //开启ADC1
}
u16 Get_ADC1(u8 ch)
{
    //选择ADC采样通道
    ADC_ANY_Cmd(ADC1,DISABLE);
    ADC_ANY_NUM_Config(ADC1,3);
    ADC_ANY_CH_Config(ADC1,0,ch);
    ADC_ANY_Cmd(ADC1,ENABLE);
   
    ADC_SoftwareStartConvCmd(ADC1,ENABLE);
    while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_ENDOFCONVSEQUENCE));//等待转换结束ADC_FLAG_ENDOFCONVSEQUENCE
    ADC_ClearFlag(ADC1,ADC_IT_EOC);
    returnADC_GetConversionValue(ADC1);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief  This function isgetting the average of ADC
/// @note   None.
/// @param  times.
/// @retval average.
////////////////////////////////////////////////////////////////////////////////
u16 GetAdcAverage(u8 times,u8 chan)
{
    u32 temp_val = 0;
    u8 t;
    u8 delay;
    for(t = 0; t < times;t++)
    {
        temp_val +=Get_ADC1(chan);
        for(delay = 0; delay < 100; delay++);
    }
    return temp_val / times;
}
3.2 SLCD配置
//sample
voidLCD_Clear(void)
{
    SLCD->DR0  = 0;
    SLCD->DR1  = 0;
    SLCD->DR2  = 0;
    SLCD->DR3  = 0;
    SLCD->DR4  = 0;
    SLCD->DR5  = 0;
    SLCD->DR6  = 0;
    SLCD->DR7  = 0;
    SLCD->DR8  = 0;
    SLCD->DR9  = 0;
    SLCD->DR10 = 0;
    SLCD->DR11 = 0;
    SLCD->DR12 = 0;
    SLCD->DR13 = 0;
    SLCD->DR14 = 0;
    SLCD->DR15 = 0;
}
voidLCD_WriteBit(u8 COMn, u32 Offset, u32 SEGn, u8 State)
{
    u8 Index = COMn * 2 + Offset;

    if(State) SLCD->RAM[Index] |=  SEGn;
    else     SLCD->RAM[Index] &= ~SEGn;
}
u8LCD_SearchCode(char ch)
{
    u8 i;
    for(i = 0; i < 38; i++) {
        if(ch == LCD_CODE_Table.ch) {
            return LCD_CODE_Table.Data;
        }
    }
    return 0xFF;
}
voidLCD_SearchName(char* str, u8* COMn, u8* SEGn)
{
    u8 i, j;
    for(i = 0; i < LCD_COM_NUMBER; i++) {
        for(j = 0; j < LCD_SEG_NUMBER; j++){
            if(strcmp(str,LCD_NAME_Table[j]) == 0) {
                *COMn = i;
                *SEGn = j;
                return;
            }
        }
    }

    *COMn = 0xFF;
    *SEGn = 0xFF;
}
voidLCD_DisplayNumber1(u8 Index, char ch, u8 Point)
{
    u8 i;
    char TAB[6][8][4] =  {
        {"5A ", "5B ","5C ", "5D ", "5E ", "5F ", "5G", "DP5"},
        {"6A ", "6B ","6C ", "6D ", "6E ", "6F ", "6G", "DP6"},
        {"7A ", "7B ","7C ", "7D ", "7E ", "7F ", "7G", "DP7"},
        {"8A ", "8B ","8C ", "8D ", "8E ", "8F ", "8G", "DP8"},
        {"9A ", "9B ","9C ", "9D ", "9E ", "9F ", "9G", "DP9"},
        {"10A", "10B","10C", "10D", "10E", "10F","10G", "   "},
    };

    u8 COMn = 0xFF, SEGn = 0xFF;

    u8 Code = LCD_SearchCode(ch);

    if(Code != 0xFF) {
        for(i = 0; i < 7; i++) {
            LCD_SearchName(TAB[Index],&COMn, &SEGn);
            if((COMn != 0xFF) && (SEGn!= 0xFF)) {
                LCD_WriteBit(COMn,LCD_SEG_Table[SEGn][0], LCD_SEG_Table[SEGn][1], (Code >> i) & 0x01);
            }
        }
        LCD_SearchName(TAB[Index][7],&COMn, &SEGn);
        if((COMn != 0xFF) && (SEGn !=0xFF)) {
            LCD_WriteBit(COMn, LCD_SEG_Table[SEGn][0],LCD_SEG_Table[SEGn][1], Point);
        }
    }
}
voidLCD_DisplayNumber2(u8 Index, char ch, u8 Point)
{
    u8 i;
    char TAB[4][8][4] = {
        {"1A ", "1B ","1C ", "1D ", "1E ", "1F ", "1G", "DP1"},
        {"2A ", "2B ","2C ", "2D ", "2E ", "2F ", "2G", "DP2"},
        {"3A ", "3B ","3C ", "3D ", "3E ", "3F ", "3G", "DP3"},
        {"4A ", "4B ","4C ", "4D ", "4E ", "4F ", "4G", "   "},
    };
    u8 COMn = 0xFF, SEGn = 0xFF;
    u8 Code = LCD_SearchCode(ch);
    if(Code != 0xFF) {
        for(i = 0; i < 7; i++) {
           LCD_SearchName(TAB[Index],&COMn, &SEGn);
            if((COMn != 0xFF) && (SEGn!= 0xFF)) {
                LCD_WriteBit(COMn, LCD_SEG_Table[SEGn][0],LCD_SEG_Table[SEGn][1], (Code >> i) & 0x01);
            }
        }
        LCD_SearchName(TAB[Index][7],&COMn, &SEGn);
        if((COMn != 0xFF) && (SEGn !=0xFF)) {
            LCD_WriteBit(COMn, LCD_SEG_Table[SEGn][0],LCD_SEG_Table[SEGn][1], Point);
        }
    }
}
voidLCD_DisplayUnit(u8 Index, u8 State)
{
    char TAB[10][4] = {"S1 ","S2 ", "S3 ", "S4 ", "S9 ", "T1", "W1 ", "C1 ", "C2 ", "C3 "};
    u8 COMn = 0xFF, SEGn = 0xFF;
    LCD_SearchName(TAB[Index], &COMn,&SEGn);
    if((COMn != 0xFF) && (SEGn !=0xFF)) {
        LCD_WriteBit(COMn,LCD_SEG_Table[SEGn][0], LCD_SEG_Table[SEGn][1], State);
    }
}
#defineUSING_LSI_AS_SLCD_CLKSOURCE//USING_HSIDIV1SLCDDIV8_AS_SLCD_CLKSOURCE//USING_HSIDIV8SLCDDIV1_AS_SLCD_CLKSOURCE//USING_LSE_AS_SLCD_CLKSOURCE//
voidslcd_init(void)
{
    u32 slcd_clk_source_freq;
    SLCD_InitTypeDef slcd_struct;

    SLCD_Prescaler_TypeDef pre_value =SLCD_Prescaler_1;
    SLCD_Divider_TypeDef   div_value = SLCD_Divider_16;

    RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWR,ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1ENR_BKP,ENABLE);                           //Enabel BKP clock
    if(RCC_GetFlagStatus(RCC_FLAG_LSERDY) ==RESET) {
        RCC_BackupResetCmd(ENABLE);
        RCC_BackupResetCmd(DISABLE);
        PWR_BackupAccessCmd(ENABLE);
        RCC_LSEConfig(RCC_LSE_OFF);
    }
    else {
        PWR_BackupAccessCmd(ENABLE);                                            //allow BKP domainaccess
    }

    RCC_SLCD_ClockCmd(SLCD, ENABLE);                                           //LCD EN
    SLCD_DeInit();
#ifdefined(USING_HSIDIV1SLCDDIV8_AS_SLCD_CLKSOURCE)
    RCC_HSICLKConfig(RCC_HSI_ONDiv1);
   RCC_SLCDCLKConfig(RCC_SLCDCLKSource_HSI_Div8SLCD_Div1);                     //HSI DIV1 = 8M SLCD Div8as LCD clock source
#elifdefined(USING_HSIDIV8SLCDDIV1_AS_SLCD_CLKSOURCE)
    RCC_HSICLKConfig(RCC_HSI_ONDiv8);
    RCC_SLCDCLKConfig(RCC_SLCDCLKSource_HSI_Div1SLCD_Div8);                     //HSI DIV8 = 1M SLCD Div1as LCD clock source
#elifdefined(USING_LSE_AS_SLCD_CLKSOURCE)
    RCC_LSEConfig(RCC_LSE_ON);                                                 // Set LSE ON with crystal
    while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) ==RESET);
    while(1) {
       RCC_SLCDCLKConfig(RCC_SLCDCLKSource_LSE);                               //LSE as LCDclock source
        if(RCC_GetSlcdClockConfig() ==RCC_SLCDCLKSource_LSE) {
            break;
        }
    }
#elifdefined(USING_LSI_AS_SLCD_CLKSOURCE)
    RCC_LSICLKConfig(RCC_LSICLKSource_40KHz);
    RCC_LSICmd(ENABLE);
   RCC_SLCDCLKConfig(RCC_SLCDCLKSource_LSI);                                   //LSI as LCDclock source
#else
#error"not define "
#endif

    //clock enabe for periphral
    RCC_GPIO_ClockCmd(GPIOA, ENABLE);
    RCC_GPIO_ClockCmd(GPIOB, ENABLE);
    RCC_GPIO_ClockCmd(GPIOC, ENABLE);
    RCC_GPIO_ClockCmd(GPIOD, ENABLE);

    slcd_clk_source_freq =RCC_GetSlcdClockFreq();
    if(slcd_clk_source_freq == 0) {
        //must check the clock source is on ornot
        return;
    }
    else if(slcd_clk_source_freq <=(LSI_VALUE / 4)) {
        pre_value = SLCD_Prescaler_1;
        div_value = SLCD_Divider_16;
    }
    else if(slcd_clk_source_freq <=LSI_VALUE) {
        pre_value = SLCD_Prescaler_4;
        div_value = SLCD_Divider_16;
    }
    else if(slcd_clk_source_freq >=(HSI_VALUE) / 8) {
        pre_value = SLCD_Prescaler_32;
        div_value = SLCD_Divider_31;
    }

    SLCD_StructInit(&slcd_struct);
    slcd_struct.SLCD_Prescaler = pre_value;
    slcd_struct.SLCD_Divider = div_value;
    slcd_struct.SLCD_Duty = SLCD_Duty_1_4;
    slcd_struct.SLCD_Bias = SLCD_Bias_1_3;
    slcd_struct.SLCD_VoltageSource = SLCD_VoltSrcCapCharggDownVdd;
    SLCD_Init(&slcd_struct);
    SLCD_IO_Config(&(SEGorCOM[0]));                                            //must call after SLCD_init
   SLCD_COM_IndexInit(&(SCLD_COM_Index[0]));

    SLCD_ChargePumpClockDivConfig(SLCD_ChargePumpClock_Div1024);

    SLCD_LowPowerDriveCmd(DISABLE);
    SLCD_DeadTimeConfig(SLCD_DeadTime_0);

    LCD_Clear();

    SLCD_BlinkConfig(SLCD_BlinkMode_Off,SLCD_BlinkFrequency_Div512);
    SLCD_BLINK_IndexInit(SCLD_BLINK_Index);

    // Polling mode
    SLCD_Cmd(ENABLE);
}
voidSLCD_DisplayData(u16 uintValue)
{
        LCD_DisplayNumber1(2, '0' + ((uintValue/ 1000) % 10), 0);
        LCD_DisplayNumber1(3, '0' + ((uintValue/ 100 ) % 10), 0);
        LCD_DisplayNumber1(4, '0' + ((uintValue/ 10  ) % 10), 0);
        LCD_DisplayNumber1(5, '0' + ((uintValue/ 1   ) % 10), 0);
}
4、开发板测评视频


5、测评总结
感谢面包板论坛给予的本次试用;EVB-L0136涵盖了芯片所有功能;可针对用户手册一项项的硬件进行测试验证。特别有意思的是其IRMSLCD是与灵动微M0其他芯片不同的地方;
SLCD液晶显示驱动器与低功耗特性结合用在手持类设备中,比如手持照度计等;