51单片机
1、51单片机初始知识
在51单片机里,int为16位。
给单片机写程序的意义就是让输入/输出的高低电平可以动起来。(不写代码的高电平就一直是高电平了,除非拿开关等期间让它改变。)
51有自己的编译器,有些语法和C语言并不相通。
51单片机有256位寻址。即256Byte空间可用。但高128Byte不建议使用,一般用来存放运行时数据栈。低128Byte可供我们用来存储代码和操作代码中的数据。
另外有一些可供操作的寄存器,它们的起始地址为0x80,和高128Byte寻址重合了,所以使用起来语法有些怪异。高128Byte可以用指针访问,但不建议访问它们。寄存器的0x80开始的我们可以如下方法使用:
sfr P0 = 0x80; // 第一次使用这个P0赋值等同于指针指定地址,只不过这样是指定的寄存器,是告诉编译器,我们以后再使用这个P0时,就是往这个寄存器的值作修改。一个寄存器8bit
sbit g_led = P0^0; // 声明寄存器第0位的地址,以后再使用g_led即是操作第0位
sbit g_led1 = P0^1; // 声明寄存器第1位的地址,以后再使用g_led即是操作第1位,不可 直接 P1=0x81这样声明,不是整8
2、关于头文件
// 摘取一丢丢代码,这是说明咱以后要操作的哪个空间,以后再使用这些P就是给里面赋值了
sfr P0 = 0x80; // 这个有8个bit空间
sbit P00 = P0^0; // 这个有1个bit空间
sbit P01 = P0^1;
sbit P02 = P0^2;
sbit P03 = P0^3;
sbit P04 = P0^4;
sbit P05 = P0^5;
sbit P06 = P0^6;
sbit P07 = P0^7;
注意:这个sfr 和 sbit 第一次是准备好地址,第二次是操作,有点怪
使用char右移时遇到了一个问题,这个有符号的char右移时前面补1,有时间搞清以下
51编译器不能赋值二进制
3、51控制数码管
这个模块由两个共阴极的数码管、一个138译码器、一个74HC245驱动器、八个100Ω电阻构成。
- 八个100Ω电阻不作解释;
- XD74HC245驱动器的作用是增强电流。单片机会输出一个信号,只不过是弱电流,大概也就只能点亮个LED灯,所以我们需要一个驱动器来整合电流。
- 74HC138N译码器,即138译码器(也有其他种类的38译码器)。138译码器的输出为Y0-Y7的0为表示的位,即当输入的A2、A1、A0为0、0、0时,Y0-Y7的输出为01111111。 001为10111111。
- 共阴极数码管。用了两个,每个数码管有四个能表示的数,每个表示的数(能表示0-9的那一个)所用到的二极管共阴极(共负极,尾巴相连)。每个能表示的数用到八个二极管(七个表示数,一个表示小数点)。
我们的这个51的CPU关于寄存器的引脚分别有:P00-P07、P10-P17、P20-P27、P30-P37、P40-P43。
我们将51的P15、P14、P13引脚连接上138译码器的A2、A1、A0,这样可以通过代码控制138译码器的输出,138译码器输出的Y0-Y7这八位通过DIG1-DIG8这八个网络名和数码管连接起来,以达到通过P15、P14、P13引脚控制两个数码管八个数字的哪个数字的使用状态注意:我们同时只能操作同一个数字,因为我们3位同时操作只能让输出的8位的一个为0,如果想让两个及以上数字同时显示,需要不断短时间间隔刷新两个数,以达到看起来同时显示的目的。当我们把延时调低时,数码管会更亮,否则更暗,不加延时会出现不可预料的错误,但我们写的代码中一般都有消耗的时间,可代替一些延时。
我们将51的P00-P07和245驱动器的输入引脚连接起来,输出的八个引脚分别控制每一个数字的八个二极管,一般从头顶那个二极管开始表示A,也即是输出的第一个引脚,然后依次顺时针引脚递加,最后一个引脚是小数点。
通过51的引脚操控译码器可选择操作哪个数字,这个叫做片选;通过51的引脚控制驱动器可选择操作显示出为那个数 。
下面是代码。作循环操作时,一定注意 无符号数 不要减到-1,不然它就成最大值了。
todo 无符号数右移时,前面可能补的是1,有点不确定。
Com通用代码如下(可用Util命名,这里不作.h和.c的区分):
/**
* 下面的代码为 数码管上的表示 和 真实数字 的对应
*/
char digital_code[10] = {
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};
/**
* 显存,即八个数字要显示的数
*/
static unsigned char s_digital_buffer[8];
/**
* @brief 一段延时的代码,偏差也许很大,因为我把代码简化了
*
* @param n 想要延时的毫秒数
*/
void Delay1ms(unsigned int n){
unsigned int c = 400;
while(c--);
}
Int驱动代码如下(针对此例可用DigitalTube命名,这里不作.h和.c的区分)
/**
* @brief 调用此函数给显存赋值,即给数码管的八位数字放上数
*
* @param num 16bit无符号数,这里这个数是我们要显示的数,放到显存里,如1234,我们在这个函数里给它作取余操作
*/
void DigitalTube_DisplayNum(unsigned int num){
unsigned char i;
// 给显存赋值前先清空显存
for(i=0; i0){
s_ditgital_buffer[i--] = digital_codes[num % 10]; // 将真实数字对应的映射放到显存中。
num /= 10;
}
// 单独处理num传过来0的情况
if(num==0){
s_digital_buffer[7] = digital_codes[0];
}
}
/**
* @brief 让数码管的某一位显示特定的组合
*
* @param dig 传来的片选,取值范围[0-7],即直观地把操作第几个数字传进来。从0开始。
* @param dat 传来的段选信号
* 我们在代码中要操作的 片选为P1, 段选为P0
*/
void DigitalTube_DisplaySingle(unsigned char dig, unsigned char dat){
// 关掉段选信号,否则当下面的片选赋值时,会直接显示出旧值来
P0 = 0;
// 初始化片选,我们只需要P1的3-5位,即P15、P14、P13。所以我们要让这三位置零(方便后续的操作,这个不操作的话确实是控制第0个数字,但我们要后面操作一下),其他的原来是啥还是啥,我们可以和0b11000111作与操作,但51编译器不支持二进制赋值,所以我们可以用十六进制0xc7
P1 &= 0xc7;
// 让传过来的dig转化为二进制,放到P1中的P15、P14、P13位置。
dig
/**
* @brief 将显存的内容显示到数码管
*/
void DigitalTube_Refresh()
{
u8 i;
for (i = 0; i
Main函数略,上面的代码提供好了写显存和从显存中读取的功能,想使用什么功能可以自己实现。但需要注意一下,如果想实现一个比如从100到1的递减显示操作,往显存写之后打印,然后写下一个数再打印…这样会出现只有个位数能完整显示的问题,这是因为我们执行完第一个数如100时,片选停留在选择了最后一个位,段选选择了0,下面的代码消耗时间,消耗的这段时间中,数码管一直在显示片选和段选组合出的结果。我们可以让100多循环显示一段时间,刷了后面位再刷前面的位,这样一小段时间后再执行下面的99。
4、51代码的命名规则
-
变量命名规则
- 见名知意
- 小写,下划线分隔
- 全局变量命名时要加 g_ 前缀
- 静态变量命名时要加 s_ 前缀
- 结构体变量命名时要加 st_ 前缀 (定义结构体类型时要加 _Struct 后缀,大写开头)
- 指针变量命名时要加 p_ 前缀
- 结构体指针命名时要加 p_st_ 前缀
- 内部变量要加static关键字
-
常量/枚举 命名规则 为 全大写,单词间用下划线隔开
-
自定义数据类型 数据类型 为 首字母大写,单词间用下划线隔开
-
函数命名规则
- 见名知意
- 普通函数命名 分层_ 模块_功能,如 Int_DigitalTube_XxxXxx 、 Com_Util_XxxXxx
- 返回布尔型的函数应如下面的名:IsButtonPressed() 、 HasDataArrived()
- get/set函数大驼峰命名。如 GetSpeed()
- 内部函数要加static关键字
-
文件命名规则
- 文件较大时(不大时当然也可以),可以将文件区分开为头文件.h和源文件.c
- 命名为 分层_模块 , 如 Com_Util.h
5、51代码的分层规则
- 工具函数和常规宏定义 Com
- 驱动层 (与 51CPU–芯片 交互的代码) Dri
- 接口层 (控制外设的代码) Int
- 中间层 (提供更高服务的代码,如操作系统、文件系统、通信协议等) Mid
- 应用层 (应用程序的逻辑代码,一般只与上面的中间层或接口层交互,尽量不访问驱动层)