51单片机

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、关于<STC89C5xRC.H>头文件
// 摘取一丢丢代码,这是说明咱以后要操作的哪个空间,以后再使用这些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; i<8; i++) s_digital_buffer[i]=0;
    // todo
    // 给显存赋值
    i = 7;
    while(num>0){
        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
    • 返回布尔型的函数应如下面的名:Int_Xxx_IsButtonPressed() 、 Int_Xxx_HasDataArrived()
    • get/set函数大驼峰命名。如 GetSpeed()
    • 内部函数要加static关键字
  • 文件命名规则

    • 文件较大时(不大时当然也可以),可以将文件区分开为头文件.h和源文件.c
    • 命名为 分层_模块 , 如 Com_Util.h
5、51代码的分层规则
  • 工具函数和常规宏定义 Com
  • 驱动层 (与 51CPU--芯片 交互的代码) Dri
  • 接口层 (控制外设的代码) Int
  • 中间层 (提供更高服务的代码,如操作系统、文件系统、通信协议等) Mid
  • 应用层 (应用程序的逻辑代码,一般只与上面的中间层或接口层交互,尽量不访问驱动层)
    我们区分.h和.c时,如果是想让外部文件访问的,就放到.h里,只想在本文件操作,留给外部一个方法访问的,就加static并放到.c中。不想让其他文件访问的变量就加static并只在.c中声明定义,不想让其他文件访问的函数就不在.h中声明,因为我们使用某个文件时只引用它的头文件。
6、独立按键

​ 独立按键的一端接51的引脚,另一端接地。

​ 51单片机中,所有引脚的初始化都是高电平,即1。51的内部电路决定了51引脚的1 和 外部的0 短接后输出为0。 当我们把51的某引脚改为0时,这个引脚不能随外部的高低电平所改变,所以我们称51引脚为高电平1时可接收外部的信号。

51单片机的这个特点被称为 弱上拉,强下拉。  即 51的1 和 外部的 0 短接,51引脚的1即变为0。

一个不确定的知识:下拉电阻隔掉的是瞬时的强电流。  还有一个就是:当我们电路中的电流很小,电压也会降下来。

​ 关于按键的抖动。当我们按下或松开按键时,两端快要接通时,会有一个通过空气相连上的击穿,这个状态会一会儿电路通一会儿不通,所以电压一会儿强一点,一会儿弱一点。我们可以延时一小小会儿,把中间的不确定去掉。代码如下:

/**
 * @brief 按键按下并抬起返回值为1
 */
bit Int_Keyboard_IsSW1Pressed(){
        // 过掉按下的情况
        // 如果没有按下,返回0
        if(SW1) return 0;
        // 延迟一小会儿,消抖
        Delay1ms(10);
        // 再判断一次,如果还是1,则确定了是没有按下
        if(SW1) return 0;

        // 按下的时候,上面两个代码都不执行
        // 既然按下了,下面就等着它什么时候抬起来,我们要实现按下并抬起才算一次按下
        while(1){
            // 抬起时,SW1变成1
            if(SW1) Delay1ms(10);
            if(SW1) return 1;
        }
}
7、矩阵按键

​ 矩阵按键不再是一个线路接引脚,另一线路接地了,而是两个线路都接上单片机的两个不同引脚。

​ 当我们使用时,需要把一个引脚先置0,也就是充当地,然后对另一个引脚作消抖和判断按下和抬起。
就是说假如我们把P20定义为连接所有第一排开关的一个端的引脚,则我们可以把它接地,即 P2为0xfe。然后根据另一个引脚就像独立开关那样控制就可以,比如第一排第一个开关的另一个引脚是P24,则当我们按下这个开关时,P24引脚接收到0,也就是通过 P2 为 0xfb 时,这个开关被按下,然后消抖之类的。

​ 代码略。

补、一些漏掉或者杂的知识

​ bit是可存1bit的数据类型;sbit是寄存器中的可存1bit的类型;sfr是存储寄存器的8bit的类型。

posted on 2023-12-21 08:52  持枢丶  阅读(205)  评论(0编辑  收藏  举报