51单片机1、51单片机初始知识

在51单片机里,int为16位。

给单片机写程序的意义就是让输入/输出的高低电平可以动起来。(不写代码的高电平就一直是高电平了,除非拿开关等期间让它改变。)

51有自己的编译器,有些语法和C语言并不相通。

51单片机有256位寻址。即256Byte空间可用。但高128Byte不建议使用,一般用来存放运行时数据栈。低128Byte可供我们用来存储代码和操作代码中的数据。

另外有一些可供操作的寄存器,它们的起始地址为0x80,和高128Byte寻址重合了,所以使用起来语法有些怪异。高128Byte可以用指针访问,但不建议访问它们。寄存器的0x80开始的我们可以如下方法使用:

sfr P0 = 0x80;// 第一次使用这个P0赋值等同于指针指定地址,只不过这样是指定的寄存器,是告诉编译器,我们以后再使用这个P0时,就是往这个寄存器的值作修改。一个寄存器8bitsbit 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 <<= 3;// 先和P15-P13对齐    P1 |= dig;// 原P15-P13为000,其他位为未知。或操作后,P15-P13变成dig,其他位是和0作的或操作,所以保持不变。    // 段选,传的是数字对应好的那个数,比如要显示0,它的对应为0x3f。这个对应在别的地方给它对应上,这里不作操作。    P0 = dat;}
/** * @brief 将显存的内容显示到数码管 */void DigitalTube_Refresh(){    u8 i;    for (i = 0; i < 8; i++)    {        DigitalTube_DisplaySingle(i, s_digital_buffer[i]);        Delay1ms(1);    }}

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
  • 应用层 (应用程序的逻辑代码,一般只与上面的中间层或接口层交互,尽量不访问驱动层)