目录
0功能简介
1、MM32L0130芯片简介
2、开发板硬件模块介绍
2.1、ADC—数模转换器
2.2、SLCD-单色无源液晶显示器
3、固件代码解读
3.1 ADC配置
3.2 SLCD配置
4、开发板测评视频
5、总结
0、功能简介
通过ADC采集模拟电压量用SLCD显示。
1、MM32L0130芯片简介
MM32L0130 微控制器搭载 Arm® Cortex®-M0+内核,最高工作频率可达 48MHz。 内置64KB高速存储器, 并集成了丰富的I/O 端口和外设模块。本产品包含 1 个 12 位的 ADC、1 个比较器、 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.1、ADC—数模转换器
从用户手册与数据手册可知,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.2、SLCD-单色无源液晶显示器
SLCD 驱动器是用于单色无源液晶显示器(SLCD)的数字驱动器,具有多达 8 个公共端和多达 63个分段端, SLCD引脚最多为 64个,因此最多可驱动 240(60x4)或 448(56x8)个段码。
其技术特征如下所示:
显示帧率灵活控制;
支持静态、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 时序控制和波形发生器
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。搭建的步骤如下:
资料下载;
EVB-LO136的SDK https://mindsdk.mindmotion.com.cn/sdk/sdk-create/
单击即可下载;
注: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涵盖了芯片所有功能;可针对用户手册一项项的硬件进行测试验证。特别有意思的是其IRM与SLCD是与灵动微M0其他芯片不同的地方;
SLCD液晶显示驱动器与低功耗特性结合用在手持类设备中,比如手持照度计等;