一、数码管
1.数码管概况
2.设计
(1)硬件设计
我们可以通过阴极控制显示的位置,通过阳极控制显示的内容。两个数码管共有8个阴极引脚和16和阳极引脚,如果所有引脚都直接接入MCU,会造成MCU引脚的极大浪费。
为了节省MCU的引脚,我们可以将两个数码管的阳极接在一起。
如此我们就有了8个阴极,8个阳极16个引脚。我们只需通过8个GPIO引脚就能控制这8位数码管的显示内容了。
既然是八位,我们很快想到了38译码器,通过三位二进制数字的输入控制八种结果。
由于51单片高电平的驱动能力很微弱,不足以点亮数码管,因此可以使用74HC245N作为驱动芯片,该芯片的用法如下。
有了74HC245之后,51单片引脚的输出就只用作信号,而驱动数码管的电流则由74HC245的电源提供。
(2)软件设计
为了实现当前需求,需要考虑两点,分别是显示位置和显示内容。确定显示位置称为数码管的位选 ,确定显示内容称为数码管的段选。
1》位选
P12 P13 P14控制显示位置,连接数码管阴极,如果为0低电平就能导通显示。故控制位选。
P13接到了A0,P14接到了A1,P15接到了A2。
例:如果p15-p13输入为000,那么与y0相连的1号数码显示。
如果001,第二位显示
010,第三位显示
011,第四位显示
100,第五位显示
······
2》段选
根据原理图可知,数码的段选通过单片机的P00~P07这7个引脚控制,具体的对应关系如下。
阳极连接。如果不点亮,给0即可。想显示,给1即可。
以上显示,代表着数字0,即给ABCDEF一个高电平1,G给0不亮,DP给0代表小数点不亮。
所以段选,P07(高位)--P01:0 0 1 1 1 1 1 1
那么可以给寄存器P0一个值0x3F.
通过p00-p07显示数码管的内容。通过让ABCDEFG、DP的亮灭,显示数字。
如此可以得到映射关系表:
3》代码实现
要求:静态显示,在任意位置,显示任意数字。
位选决定位置。根据位选的数位,决定P13~P15的取值。
段选,0~9的数字显示通过P00-P07控制。数字0要让ABCDEF亮起,如此编码就为:00111111
十六进制0x3F。数字2即为0x5B.
如果让数字在第一个位置亮起,P15~P13:0 0 0
段选好说,将数字编码2赋值于P0,2对应十六进制0X5B,即可通过控制数码管显示对应的数字。
位选需要动动脑筋,switch能够做到,但没有效率。我们用position形参,传递的是第几位。假设P1是定义的八位变量,P1=10 101 010,我们要在第4位显示数字,那么P15-P13:011 position:0000 0011,现在要做的就是将position的末三位赋值到P1的中三位,并不影响P1其他数位上的数字。
所以考虑怎样给到P1的中三位。
P1:10 101 010
position:0000 0 011
为了更好的观察规律,先将position左移三位。
P1:10 101 010
position:00 011 000
将P1的中三位置0,在与position做或运算。那么先让P1中三位置零,就与上11000111,其他几位都是1,1与任何数相与都是原来的数。
那么调整下方法:让position左移三位,让P1&11000111,在P1与position作或运算。
position<<=3;
P1&=11000111;//P1&=0xC7
p1|=position;
代码表示为:
cpp
#include <STC89C5xRC.H>
#define SMG_EN P36 // 数码管总开关
#define LED_EN P34 // LED灯总开关
typedef unsigned char u8; // 给无符号字符类型取别名,代表无符号8位
static u8 s_digit_codes[10] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};//编码数组
//定义函数
void DigitalTube_DisplaySingle(u8 position, u8 num_code)//一个参数位选:P13/P14/P15,一个参数段选
{
P1 &= 0xC7;//按位与运算
position <<= 3;
P1 |= position;
P0 = num_code;//数字编码赋予P0
}
void main()
{
// 打开数码管总开关
SMG_EN = 0;
// 关闭LED灯总开关
LED_EN = 0;
DigitalTube_DisplaySingle(0, s_digit_codes[2]);//让数字显示在位选0第一个位置,显示内容为2
while (1) {
};
}
烧录以上代码到51核心板内,数码管第一个位置显示2就完成了。
4》变量声明
在C89标准中,所有声明的变量必须在作用域的最前边声明。C99就没有这个约束了。注意一下即可!
二、数码管动态显示
1.要求
我们先搞整数的显示。
2.设计
8位数码管同一时刻只能显示一个位置,通过高速的循环显示可以做到以上效果。
(1)思路
(2)代码实现
我们首先要定义一个显存数组。
cpp
#include <STC89C5xRC.H>
#define SMG_EN P36 // 数码管总开关
#define LED_EN P34 // LED灯总开关
typedef unsigned char u8; // 给无符号字符类型取别名,代表无符号8位
typedef unsigned long u32;
static u8 s_digit_codes[10] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};//编码数组
//定义显存数组
u8 buffer[8];
//定义函数
void DigitalTube_DisplaySingle(u8 position, u8 num_code)//一个参数位选:P13/P14/P15,一个参数段选
{
P0=0x00;
P1 &= 0xC7;//按位与运算
position <<= 3;
P1 |= position;
P0 = num_code;//数字编码赋予P0
}
//设置显存数组,待展示数字
void DigitalTube_DisplayNum(u32 num)
{
u8 i;
for(i=0;i<8;i++)
{
buffer[i]=0x00;
}
if (num==0)
{
buffer[7]=s_digit_codes[0];
return;
}
i=7;
while (num>0)
{
buffer[i]=s_digit_codes[num%10];
num/=10;
i--;
}
}
//动态扫描
void DigitalTube_Refresh()
{
u8 i;
for ( i = 0; i < 8; i++)
{
DigitalTube_DisplaySingle(i,buffer[i]);
}
}
void main()
{
// 打开数码管总开关
SMG_EN = 0;
// 关闭LED灯总开关
LED_EN = 0;
DigitalTube_DisplayNum(12345);
while (1) {
DigitalTube_Refresh();
};
}
上图的效果就实现了。
三、模块化编程
随着我们的代码越来越复杂,我们的main.c越来越长,阅读性也越来越差。如果将来开始做项目,我们可能要同时操作好几个模块,这种情况下我们无法再把代码写到同一个文件,而是要分模块管理代码。具体实现方法,就是将源码按照不同功能和模块,拆成若干部分源码,再用头文件相互引用。
1.命名规范
变量命名
函数命名
文件命名
2.代码分层规范
(1)目录
(2)驱动层
所有与芯片直接交互的自身硬件代码,例如GPIO开关、硬件UART/ADC的驱动、计时器等。
目录:Dri/
前缀:Dri_
(3)接口层
位于驱动层之上,通过标准接口驱动的外部硬件代码,没有外部硬件设备,可以不用这一层。
目录:Int/
前缀:Int_
(4)中间层
提供更高级的服务,如操作系统、文件系统、通信协议栈等。
简单项目用不到这一层。
目录:Mid/
前缀:Mid_
(5)应用层
包含应用程序的主要逻辑。实现与上层交互,不直接访问这一层。
目录:App/
前缀:App_
3.创建项目模板
为方便后续项目的创建,可以创建一个基础模板,后边项目全部基于模板创建。
(1)创建一个新的项目,取名为模板。
(2)配置项目
(3)项目规范结构
(4)编写通用代码
Com_Util.c
#include "Com_Util.h"
#include <INTRINS.H>
void Com_Util_Delay1ms(u16 count)
{
u8 i, j;
while (count > 0) {
count--;
_nop_();
i = 2;
j = 199;
do {
while (--j);
} while (--i);
}
}
Com_Util.h
#ifndef __COM_UTIL_H__
#define __COM_UTIL_H__
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
void Com_Util_Delay1ms(u16 count);
#endif // !1
(5)导出模板
下次使用本地模板点开ept文件即可。