一直知道EFM32的LCD Controller支持简单的动画效果,而不需要MCU去参与。但是一直没有时间来给这个功能做一个总结。抽空总结一下,因此在TG STK的基础上,详细的说明一下,免得自己时间久了忘记。最终的效果如下:
注:Gecko显示不全,是因为PE13被我断开了了。用来测试EM4 GPIO唤醒用了。
LCD Controller控制器支持动画状态机,可以在低功耗状态下,自动更新LCD的显示界面,而无需MCU干预。但是LCD Controller并不能将所有的LCD Segment都动画,目前仅支持LCD_COM0上的Segmeng0~Segment7.以TG STK为例,它就是将LCD左上角的那个圈圈连接到了LCD_COM0的Segmeng0~Segmeng7.如下图所示:
图1: 圈圈为A0~A7,可能不太清晰
图2:对应到真值表,说明A0~A7是连接到LCD屏的COM7上的
图3:而实际的连线却是,MCU的COM0(PE4)连接着LCD屏的COM7,反着接的
动画效果的实现是通过两个8bit的寄存器即LCD_AREGA,LCD_AREGB的左移或右移来实现的,因此总共可以有16种状态。左移和右移,是通过寄存器LCD_BACTRL中的AREGASC和AREGBSC来分别控制的。另外,Frame Conter也必须使能。总共有三种方式,不移动,左移与右移。另外,可以控制LCD_AREGA,LCD_AREGB的的逻辑关系,来最终获得显示的值。逻辑关系包括逻辑与,逻辑或。
在本例子当中,LCD_AREGA初始值为0x01,采用右移的方式,LCD_AREGB初始值为0x00, 不移动,两者的关系为逻辑或。
因此,到现在为止,我们应该清楚了,实现动画效果是有一定的硬件上的限制,并且也知道了其实动画效果是通过两个8bit寄存器的逻辑关系来获得的。剩下的就是这几个寄存器是受谁控制的,如何去配置的问题了。
目前清楚的是,这个动画的功能肯定受LCD Controller控制,但是如何操作呢?
1.首先,初始化LCD Controller. 如果不想自己写,则可以segmenglcd.c中的SegmentLCD_Init(false)函数。
void SegmentLCD_Init(bool useBoost)
{
const LCD_Init_TypeDef lcdInit = { true,
lcdMuxOctaplex,
lcdBiasOneFourth,
lcdWaveLowPower,
lcdVLCDSelVDD,
lcdConConfVLCD };
/* Ensure LE modules are accessible */
CMU_ClockEnable(cmuClock_CORELE, true);
/* Enable LFRCO as LFACLK in CMU (will also enable oscillator if not enabled) */
CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_LFRCO);
/* LCD Controller Prescaler (divide LFACLK / 64) */
CMU_ClockDivSet(cmuClock_LCDpre, cmuClkDiv_64);
/* LFACLK_LCDpre = 512 Hz */
CMU_LCDClkFDIVSet(cmuClkDiv_1);
/* Set FDIV=0, means 512/1 = 512 Hz */
/* With octaplex mode, 512/16 => 32 Hz Frame Rate */
/* Enable clock to LCD module */
CMU_ClockEnable(cmuClock_LCD, true);
/* Disable interrupts */
LCD_IntDisable(0xFFFFFFFF);
/* Initialize and enable LCD controller */
LCD_Init(&lcdInit);
/* Enable all display segments */
LCD_SegmentRangeEnable(lcdSegmentAll, true);
/* Enable boost if necessary */
if (useBoost)
{
LCD_VBoostSet(lcdVBoostLevel3);
LCD_VLCDSelect(lcdVLCDSelVExtBoost);
CMU->LCDCTRL |= CMU_LCDCTRL_VBOOSTEN;
}
/* Turn all segments off */
SegmentLCD_AllOff();
LCD_SyncBusyDelay(0xFFFFFFFF);
}
2.除了基本的初始化,还需要初始化Frame Counter。 Frame Conter是何东东,他是用来同步LCD的帧起始进行帧计数,其实这就是一个帧计数器,计到设定的值,就产生更新的事件,我们这里就是更新LCD的动画显示。
既然是一个帧计数器,那么就需要他的时钟源,以及动画更新的时间间隔是如何计算的。
首先,动画的时间CLKevent间隔计算如下:
通过29.2这条公式,我们知道,事件产生的频率是受到CLKfc控制的,是(1+FCTOP)分之CLKfc,此处的fc = Frame Counter
而CLKfc 的时钟来源于LCD Controller的Frame时钟
可以通过设置FCPRESC来配置CLKfc与CLKframe的关系。
而CLKframe则等于LFACLKLCD的时钟频率有关。如果是配制成8COM的话,CLKframe = LFACLKLCD/16
而LFACLKLCD的时钟源,又是取自哪里呢?参看如下的公式,取自LFACLKLCDpre
而LFACLKlcdpre的时钟则来源于:哦哦,来自于LFACLK.
终于理清了这里的时钟关系。难怪说,芯片里面最复杂的是时钟系统。我靠,仅仅是个LCD自动动画的功能,涉及到的时钟源就有足足6个之多。下次在计算时间的时候,我会从下我上看的。。呵呵。
最后,我们来上一段小代码吧.
#include <stdint.h>
#include <stdbool.h>
#include "efm32.h"
#include "em_chip.h"
#include "em_emu.h"
#include "em_lcd.h"
#include "segmentlcd.h"
/* LCD Animation Configuration */
LCD_AnimInit_TypeDef animInit =
{
.enable = true, /* Enable Animation at end of initialization */
.AReg = 0x01, /* Initial Animation Register A Value */
.AShift = lcdAnimShiftRight, /* Shift operation of Animation Register A */
.BReg = 0x00, /* Initial Animation Register B Value */
.BShift = lcdAnimShiftNone, /* Shift operation of Animation Register B */
.animLogic = lcdAnimLogicOr, /* A and B Logical Operation to use for mixing and outputting resulting segments */
};
/* LCD Frame Control Initialization */
LCD_FrameCountInit_TypeDef fcInit =
{
.enable = true, /* Enable at end */
.top = 4, /* Frame Counter top value */
.prescale = lcdFCPrescDiv1, /* Frame Counter clock prescaler */
};
void startLCDAnimation(void)
{
/* Show animation */
LCD_AnimInit(&animInit);
/* Configure LCD to give a frame counter interrupt every 8th frame. */
LCD_FrameCountInit(&fcInit);
LCD_SyncBusyDelay(LCD_SYNCBUSY_BACTRL);
}
int main(void)
{
/* Chip errata */
CHIP_Init();
/* Enable LCD without voltage boost */
SegmentLCD_Init(false);
SegmentLCD_Write(" Gecko ");
startLCDAnimation();
while(1)
{
EMU_EnterEM2(false);
}
}
而实现这样的动画功能,付出的功耗,仅仅为2.25uA左右。如果应用在智能仪表领域,用动画的功能,就可以不停的向用户提示,我还在工作,还在工作。呵呵。
