分析动态数码管控制的原理
可以看出来,P0寄存器的每一个bit分别与输入a~dp联系起来,当对应的引脚为高电平时,对应的LED则点亮。
但是我们也可以发现每个8位数码管都是由输入a~dp进行控制的,那岂不是我们输入一个值,每个LED都呈现相同的内容了?但其实除了P0寄存器之外,P2的第2-4位作为了动态数码管的使能位,例如当这三位为1,1,1时,此时编号为0的寄存器处于使能状态,它可以输出发光的内容,而其他寄存器处于禁用状态。
由此可见,我们在同一个时刻最多只能有1个八位数码管可以发光,其他的7个都是熄灭的,那如何使我们能看到它们同时发光呢?其实很简单,只要使能位切换得够快,我们肉眼就会发现变化的几个数码管都是发光的了。
编码实现效果
数码管计数器
首先我们需要先将0~F这16个字符使用数码管表示出来,例如0这个字符,输入a,b,c,d,e,f应该为1(即高电平),则我们输入给P0的值应该是0b0011 1111,即0x3f,这样再设置P2[2..4]=0b111(设置使能位,使第0个数码管可以工作),这样我们就可以在第0个数码管看到字符0了,其他以此类推,就可以得到这16个字符对应的二进制编码。
为了方便使用,我们使用一个数组将这16个字符的编码存储起来:
u8 code smgduan[16]={
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71
};
然后我们可以实现计数器,即使一个数码管从0到F不断地变化,核心代码如下:
int main() {
u8 i;
P0 = smgduan[2];
while (1)
{
for(i=0;i<16;i++) {
P0 = smgduan[i];
defaultDeley();
}
}
}
因为P2[2..4]默认为0b111,即默认是第一个数码管是可以显示的,所以我们会看到第一个数码管由0变化到F。
运行效果:
数码管流水灯
之前已经实现了LED流水灯,这次希望可以实现数码管的流水灯。要实现它,我们需要完成数码管索引到使能位的映射,即当我指定一个编号的数码管使能时,它就能工作。
我们先看一下映射关系:
这里0号数码管使能时,P2[2..4]=0b111;当1号数码管使能时,P2[2..4]=0b110...
可以发现这里有个明显的对应关系,设数码管编号为index,则只需令P2[2..4]=0b111-index
,即可使对应的数码管工作了,但是如何使结果赋值到P2[2..4]这三位中呢?
此时我们可以使用“与置零,或置一”的方法进行部分赋值,例如下面的一个例子:
有变量toSet == 0b1010 0011
此时我们希望使它的低4位变成1010
,我们可以
-
toSet = toSet & 0b1111 0000
,此时toSet
的值为0b1010 0000
,即我们将它的低4位设置为了0 -
toSet = toSet | 0b0000 1010
,此时toSet
的值为0b1010 1010
,完成了要求
即我们对某几位赋值时可以先用与运算符清零,再使用或运算符赋值完成操作,而这里需要修改P[2...4]三位,我们先使用0x1110 0011(即~(0x7<<2)
)使用与运算符进行清零,再将(0b111-index)<<2
使用或运算符进行赋值即可,代码如下:
void enableIndexLED(u8 index) {
P2 = P2 & ~(0x07 << 2);
P2 = P2 | (0x7 - index)<<2;
}
如此一来,我们的流水灯就可以轻松实现,核心代码如下(这里流水灯的数字为‘2’):
int main() {
u8 i = 0;
P0 = smgduan[2];
while (1)
{
for(i = 0;i<8;i++) {
enableIndexLED(i);
defaultDeley();
}
}
}
运行效果:
显示0~65535的数字
首先我们将各个数码管需要显示的数字使用一个数组进行存储:u8 array[8];
,这个数组的元素的值被初始化为notDisplay:
#define notDisplay 255
int main() {
u8 array[8];
u8 n= 8;
for(i=0;i<n;i++) {
array[i] = notDisplay;
}
}
然后我们编写一个函数,接收一个变量num,然后将它分解后填入到array数组中的位置中:
void updateArray(u16 num, u8* array, u8 n) {
u8 i = n-1;
if (num == 0) {
array[i] = 0;
return;
}
while (num)
{
array[i] = num %10;
num /=10;
i--;
}
}
例如数字65535会使数组变为:
array=[notDisplay,notDisplay,notDisplay,6,5,5,3,5]
然后我们将数组的每个非notDisplay的元素映射到数码管即可。
显示数字函数:
// 第index个数码管显示num数字
void displayOneNum(u8 index, u8 num) {
enableIndexLED(index);
if (num == notDisplay) {
P0 = 0x00;
}else {
P0 = smgduan[num];
}
}
void display(u16 num, u8* array, u8 n) {
u8 i;
updateArray(num, array, n);
for(i=0;i<n;i++) {
displayOneNum(i, array[i]);
deley(100);
P0=0x00;//消隐
}
}
主函数:
int main() {
u8 i = 0;
u8 array[8];
u8 n = 8;
u16 cnt = 1;
for(i=0;i<n;i++) {
array[i] = notDisplay;
}
while (1) {
display(12345,array,n);
}
}
这样我们就可以在数码管中看到“65535”的数字字符串了,运行结果:
计数器升级版
这次的计数器是可以从0计数到65535的
displayForAWhile()
函数如下所示:
void displayForAWhile(u16 num, u8* array, u8 n) {
u8 times = 125;
while (times--)
{
display(num,array,n);
}
}
当输入一个数字时,这个函数中会重复显示这个数字125次,因为1s大概是deley(100000)消耗的时间,这里渲染一次一个数字的时间大概是deley(100*8)=deley(800),然后渲染125次的时间约为deley(100000),即这个函数会显示num约一秒钟,所以我们可以用来做个简单的计数器,主函数代码如下:
int main() {
u8 i = 0;
u8 array[8];
u8 n = 8;
u16 cnt = 1;
while (1)
{
for(i=0;i<n;i++) {
array[i] = notDisplay;
}
for(cnt = 0; cnt <= 65535; cnt++) {
displayForAWhile(cnt, array, n);
}
}
}
这样它就会从0一直变化到65535了。
运行结果(这是加快后的运行结果):
模拟时钟
tips: 在设计的过程中其实使用到了面向对象的思想,那些Time_xxx的函数其实在观念上是Time类的成员函数或静态函数,这样设计可以更加有条理。
首先先设计存储时间的结构体:
typedef struct {
u8 hour, minute, second;
} Time;
时间递增函数,每调用一次秒数加一,然后再处理进位:
void Time_increace(Time *time) {
time->second++;
if (time->second==60)
{
time->second = 0;
time->minute++;
if (time->minute==60)
{
time->minute = 0;
time->hour++;
if (time->hour == 24)
{
time->hour = 0;
}
}
}
}
数字分解函数:
void Time_split(u8 num, u8* first, u8* second) {
*second = num%10;
num/=10;
*first = num%10;
}
即功能是输入一个数字,这里是时或秒或分,把它分解成两个数并存储到first和second指针指向的空间。
数据展示函数:
static void Time_displayData(u8 num, u8 startIndex) {
u8 first, second;
Time_split(num, &first, &second);
displayOneNum(startIndex, first);
deley(100);
P0=0x00;//消隐
displayOneNum(startIndex+1,second);
deley(100);
P0=0x00;//消隐
}
输入一个数字,为时或秒或分,先将这个数字分解成两个数字,然后分别展示到第startIndex个数码管和第startIndex+1个数码管上。
中间分隔符展示函数:
void Time_displaySplitor() {
displayOneChar(2,0x40);
deley(100);
P0=0x00;//消隐
displayOneChar(5,0x40);
deley(100);
P0=0x00;//消隐
}
总的时间展示函数:
void Time_show(Time* time) {
Time_displayData(time->hour, 0);
Time_displayData(time->minute, 3);
Time_displayData(time->second, 6);
Time_displaySplitor();
}
展示一个时间一秒钟的函数:(即展示一个时间100次,大概1s的时间)
Time_showForASecond(Time* time) {
u8 times = 100;
while (times--) {
Time_show(time);
}
}
主函数:
int main() {
Time time;
time.hour = time.minute = time.second = 0;
while (1)
{
Time_showForASecond(&time);
Time_increace(&time);
}
}
这样我们就实现了计时的功能。
运行效果(这里使用time.hour = 23; time.minute = 59; time.second = 50;
进行测试。):