51单片机(STC89C52RC版本)学习笔记(更新中...)

发布于:2024-12-06 ⋅ 阅读:(23) ⋅ 点赞:(0)

参考资料

51单片机入门教程-2020版 程序全程纯手打 从零开始入门
江协科技资料下载
51单片机(STC89C52RC)系统性学习笔记

1. 准备工作

1.1 win10配置51单片机开发环境

  1. 51单片机
正在重新握手 ... 成功			[0.578"]
当前的波特率: 115200
正在擦除目标区域 ... 完成 !		[0.328"]
正在下载用户代码 ... 完成 !		[0.141"]
正在设置硬件选项 ... 完成 !		[0.031"]

更新后的硬件选项为:
  . 当前的时钟频率: 11.088MHz
  . 系统频率为12T(单倍速)模式
  . 振荡器的放大增益不降低
  . 当看门狗启动后,任何复位都可停止看门狗
  . MCU内部的扩展RAM可用
  . ALE脚的功能选择仍然为ALE功能脚
  . P1.0和P1.1与下次下载无关
  . 下次下载用户程序时,不擦除用户EEPROM区

  单片机型号: STC89C52RC/LE52RC
  固件版本号: 6.6.4C
  1. 编程工具 Keil5 C51 新建项目时选择AT89C51RC2

  2. 将程序载入到51单片机的工具 STC-ISP
    在这里插入图片描述

  3. 51单片机接入电脑,并配置驱动CH340_CH341
    在这里插入图片描述

1.1 Ubuntu配置51单片机开发环境

  1. 编辑器 VS Code 安装教程
  2. 安装VS Code插件 C/C++ Extension Pack
  3. 安装编译工具 sudo apt-get install sdcc
  4. 程序编译
    sdcc 1_LED_1.c
    将编译生成的文件输出到out目录,需要先通过sudo mkdir out创建out目录
    sdcc 1_LED_1.c -o out/

问题1:mcs51/8051.h依赖于mcs51/lint.h

#include <mcs51/lint.h>
#include <mcs51/8051.h>

问题2:提示找不到头文件mcs51/8051.h

在这里插入图片描述
8051.h是安装sdcc后产生的,请先确保sdcc已经安装,sudo find / -name 8051.h搜索一下头文件8051.h所在的目录
在这里插入图片描述
这里目录是/usr/share/sdcc/include,将该路径配置到includepath,先点击Quick Fix
在这里插入图片描述
再点击Edit “includePath” setting
在这里插入图片描述

添加/usr/share/sdcc/includeInclude path
在这里插入图片描述

2. 认识51单片机

2.1 STC89C52RC单片机

  • 所属系列:51单片机系列
  • 公司:STC公司
  • 位数:8位
  • RAM:512字节
  • ROM:4K(Flash)
  • 工作频率:11.088MHz(本开发板使用)

2.2 管脚图

在这里插入图片描述

2.3 原理图

在这里插入图片描述
高清PDF可从这里下载江协科技资料下载

2.4 按键抖动

对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。
在这里插入图片描述
因此,需要进行防抖处理,如下:

// 这是一个检测独立按键K1是否按下,并控制LED模块的简化代码
if(P3_1==0)			// 如果K1按键按下,P3_1的值会变为0
{
	Delay(20);		// 延时20毫秒消抖
	while(P3_1==0);	// 死循环空转,直到K1按键松开,P3_1变为1,跳出循环
	Delay(20);		// 延时20毫秒消抖
	
	P2_0=~P2_0;		// LED1的亮灭情况
}

2.5 头文件说明

#include <mcs51/lint.h>
#include <mcs51/8051.h>
#include <REGX52.H>

2.6 模块化函数

2.6.1 延时函数

#ifndef __DELAY_H__
#define __DELAY_H__

void Delayms(unsigned int k);

#endif
#include <INTRINS.H>

// 延时k ms
void Delayms(unsigned int k)		//@11.0592MHz
{
	unsigned char i, j;
	
	while (k --) {
		_nop_();
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
}

2.6.2 矩阵键盘

#ifndef __MATRIXKEYBOARD_H__
#define __MATRIXKEYBOARD_H__

unsigned int matrixKeyboard();

#endif
#include <at89c51RC2.h>
#include "Delay.h"
// 反回键码(1 ~ 16),行优先遍历
// 按列扫描矩阵键盘
// 如果按行的话,P1_5和蜂鸣器冲突,会导致其发声
unsigned int matrixKeyboard() {
	unsigned int keyNum = 0;
	P1 = 0xFF;
	P1_3 = 0; // 第一列
	if (P1_7 == 0) {Delayms(20); while (P1_7 == 0); Delayms(20); keyNum = 1;}
	if (P1_6 == 0) {Delayms(20); while (P1_6 == 0); Delayms(20); keyNum = 5;}
	if (P1_5 == 0) {Delayms(20); while (P1_5 == 0); Delayms(20); keyNum = 9;}
	if (P1_4 == 0) {Delayms(20); while (P1_4 == 0); Delayms(20); keyNum = 13;}
	
	P1 = 0xFF;
	P1_2 = 0; // 第二列
	if (P1_7 == 0) {Delayms(20); while (P1_7 == 0); Delayms(20); keyNum = 2;}
	if (P1_6 == 0) {Delayms(20); while (P1_6 == 0); Delayms(20); keyNum = 6;}
	if (P1_5 == 0) {Delayms(20); while (P1_5 == 0); Delayms(20); keyNum = 10;}
	if (P1_4 == 0) {Delayms(20); while (P1_4 == 0); Delayms(20); keyNum = 14;}
	
	P1 = 0xFF;
	P1_1 = 0; // 第三列
	if (P1_7 == 0) {Delayms(20); while (P1_7 == 0); Delayms(20); keyNum = 3;}
	if (P1_6 == 0) {Delayms(20); while (P1_6 == 0); Delayms(20); keyNum = 7;}
	if (P1_5 == 0) {Delayms(20); while (P1_5 == 0); Delayms(20); keyNum = 11;}
	if (P1_4 == 0) {Delayms(20); while (P1_4 == 0); Delayms(20); keyNum = 15;}
	
	P1 = 0xFF;
	P1_0 = 0; // 第四列
	if (P1_7 == 0) {Delayms(20); while (P1_7 == 0); Delayms(20); keyNum = 4;}
	if (P1_6 == 0) {Delayms(20); while (P1_6 == 0); Delayms(20); keyNum = 8;}
	if (P1_5 == 0) {Delayms(20); while (P1_5 == 0); Delayms(20); keyNum = 12;}
	if (P1_4 == 0) {Delayms(20); while (P1_4 == 0); Delayms(20); keyNum = 16;}
	
	
	return keyNum;
}

3. LED模块

3.1 原理图

在这里插入图片描述
LED等的左侧接入VCC正极,若要使灯亮,则右侧需要接入负极,也就是对应的寄存器位需要赋值为0。

实验1:点亮LED灯D1

#include <AT89C51RC2.h>
/**
 * 点亮LED灯D1
*/
void main() {
	P2 = 0xFF; // 将所有灯熄灭
    P2_0 = 0; // D1灯亮起
    while (1); // 程序始终保持运行
}

实验2:LED灯D8闪烁

#include <AT89C51RC2.h>
#include <INTRINS.H>

/**
 * LED灯D8闪烁
*/
void main() {
    P2 = 0xFF; 
    while (1)
    {
        P2_7 = 0; // D8灯亮
        Delayms(1000); // 延迟1000ms = 1s
        P2_7 = 1; // D8灯灭
        Delayms(1000); // 延迟1000ms = 1s
    } 
}

实验3:LED流水灯

#include <AT89C51RC2.h>
#include "Delay.h"

/**
 * LED灯D1 ~ D8按顺序点亮,每次只有一个灯亮
*/
void main() {
    // 1111 1110
    // 1111 1101
    // 1111 1011
    // ...
    // 1111 1110
    // =》 其实就是实现循环左移
    P2 = 0xFE;
    while (1)
    {
        P2 <<= 1;
        if (P2 != 0xFE) P2 |= 0x01;
        Delayms(1000); // 延迟1000ms = 1s
    } 
}

4. 独立按键模块

4.1 原理图

在这里插入图片描述

实验1:独立按键控制LED灯D1

#include <AT89C51RC2.h>
#include "Delay.h"

/**
 * 独立按键K1控制LED灯D1
 * 按下按键并松开后,变换灯D1的状态
*/
void main() {
    while (1)
    {
        // 注意控制K1的是P3_1,控制K2的是P3_0
        // 当值为0是代表按键按下
        if (P3_1 == 0) 
        {
            Delay(20); // 消除按键抖动
            while (P3_1 == 0); // 一直保持按下的状态则卡在这
            Delay(20); // 消除按键抖动
            P2_0 = ~P2_0; // 状态取反
        }
    } 
}

5. 动态数码管模块

5.1 原理图

在这里插入图片描述在这里插入图片描述
LED1 ~ LED8的亮灭(位选)由P24, P23, P22三位决定,P24位高位,当这三个位表示数据n时,Yn对应的灯亮,Y0对应LED1
P0_0 ~ P0_6分别控制a ~ g晶体管,P0_7控制dp晶体管,为1代表选中。(P0_7是最高位,P0_0是最低位)

实验1:数码管LED3显示2

#include <AT89C51RC2.h>

/**
 * 数码管LED3显示2
*/
void main() {
    // 选中数码管LED3,对应Y2  P2_4 P2_3 P2_2 = 0 1 0
    P2_4 = 0;
    P2_3 = 1;
    P2_2 = 0;
    
    // a b c d e f g dp
    // 0 1 2 3 4 5 6 7
    // a b c d e f g dp
    // 1 1 0 1 1 0 1 0 => 0101 1011 = 0x5B
    // 数字2,需要点亮 a b g e d
    P0 = 0x5B;
    while (1);
}

实验2:数码管LED8 ~ LED1分别显示0 ~ 8

// 对应数字 0 ~ 9 的段码
unsigned char NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
// 在LED-loc 显示num
void Nixie(unsigned char loc, unsigned char num) { 
    P2_4 = 0; P2_3 = 0; P2_2 = 0; // 1110 0011 = 0xE3 P2 &= 0xE3
    if ((loc & 0x01) == 1) P2_2 = 1;
    if ((loc & 0x02) == 2) P2_3 = 1;
    if ((loc & 0x04) == 4) P2_4 = 1;
    
    P0 = NixieTable[num];
	
	// 消影,数字清零,防止上一个位置的数字显示到下一个位置
	Delayms(1);
	P0 = 0x00;
}

/**
 * 数码管LED8 ~ LED1分别显示0 ~ 8
**/
void main() {
	unsigned char i;
    while (1) {
		for (i = 0; i <= 7; i ++) {
			Nixie(7 - i, i);
		}
	}
}

6. LCD1602接口

6.1 原理图

在这里插入图片描述

7. 矩阵按键模块

7.1 原理图

在这里插入图片描述

实验1:在LCD1602显示按下的键码(1 ~ 16)

#include <at89c51RC2.h>
#include "Delay.h"
// 反回键码(1 ~ 16),行优先遍历
// 没有按下任何键则返回0
// 按列扫描矩阵键盘
// 如果按行的话,P1_5和蜂鸣器冲突,会导致其发声
unsigned int matrixKeyboard() {
	unsigned int keyNum = 0;
	P1 = 0xFF;
	P1_3 = 0; // 第一列
	if (P1_7 == 0) {Delayms(20); while (P1_7 == 0); Delayms(20); keyNum = 1;}
	if (P1_6 == 0) {Delayms(20); while (P1_6 == 0); Delayms(20); keyNum = 5;}
	if (P1_5 == 0) {Delayms(20); while (P1_5 == 0); Delayms(20); keyNum = 9;}
	if (P1_4 == 0) {Delayms(20); while (P1_4 == 0); Delayms(20); keyNum = 13;}
	
	P1 = 0xFF;
	P1_2 = 0; // 第二列
	if (P1_7 == 0) {Delayms(20); while (P1_7 == 0); Delayms(20); keyNum = 2;}
	if (P1_6 == 0) {Delayms(20); while (P1_6 == 0); Delayms(20); keyNum = 6;}
	if (P1_5 == 0) {Delayms(20); while (P1_5 == 0); Delayms(20); keyNum = 10;}
	if (P1_4 == 0) {Delayms(20); while (P1_4 == 0); Delayms(20); keyNum = 14;}
	
	P1 = 0xFF;
	P1_1 = 0; // 第三列
	if (P1_7 == 0) {Delayms(20); while (P1_7 == 0); Delayms(20); keyNum = 3;}
	if (P1_6 == 0) {Delayms(20); while (P1_6 == 0); Delayms(20); keyNum = 7;}
	if (P1_5 == 0) {Delayms(20); while (P1_5 == 0); Delayms(20); keyNum = 11;}
	if (P1_4 == 0) {Delayms(20); while (P1_4 == 0); Delayms(20); keyNum = 15;}
	
	P1 = 0xFF;
	P1_0 = 0; // 第四列
	if (P1_7 == 0) {Delayms(20); while (P1_7 == 0); Delayms(20); keyNum = 4;}
	if (P1_6 == 0) {Delayms(20); while (P1_6 == 0); Delayms(20); keyNum = 8;}
	if (P1_5 == 0) {Delayms(20); while (P1_5 == 0); Delayms(20); keyNum = 12;}
	if (P1_4 == 0) {Delayms(20); while (P1_4 == 0); Delayms(20); keyNum = 16;}
	
	
	return keyNum;
}

// 在LCD1602显示按下的键码(1 ~ 16)
void main() {
	unsigned int num = 0, newNum = 0;
	LCD_Init();
	while (1) {
		newNum = matrixKeyboard();
		if (newNum != 0) {
			num = newNum;
		}
		LCD_ShowNum(1, 1, num, 2); // 第一行第一列显示,占两个位置
	}
}

实验2:4位密码锁

// 10为0,11为确认,12为清空
void main() {
	// num,保存当前输入的密码
	// keyNum,记录矩阵键盘按下的键码
	// count,记录当前已经输入多少个数字
	unsigned int num = 0, keyNum = 0, count = 0;
	// LCD1602初始化
	LCD_Init();
	// 在第一行的前9个输入固定显示输入提示符
	LCD_ShowString(1, 1, "PASSWORD:");
	while (1) {
		// 第10个字符显示当前输入的密码
		LCD_ShowNum(1, 10, num, 4);
		// 获取当前矩阵键盘的键码(没有按下任何键则返回0)
		keyNum = matrixKeyboard();
		if (keyNum >= 1 && keyNum <= 9 && count < 4) {
			num = num * 10 + keyNum;
			count ++;
		} else if (keyNum == 11 && count == 4) {
			if (num == 1234) {
				LCD_ShowString(2, 1, "OK");
			} else {
				LCD_ShowString(2, 1, "ERR");
			}
		} else if (keyNum == 12) {
			num = 0;
			count = 0;
			LCD_ShowString(2, 1, "   ");
		}
	}
}

8. 定时器与中断

定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成
定时器作用︰
(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)替代长时间的Delay,提高CPU的运行效率和处理速度(…)

8.1 原理图

在这里插入图片描述

TL0和TH0发生溢出时(达到65535),TF0标志位被置位1,从而引发中断。

  1. 生成定时器初始化代码
    在这里插入图片描述
void Timer0Init(void)		//1毫秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	
	// 这两个开关配置需要自己加一下
	ET0 = 1; // 打开定时器0
	EA = 1; // 打开总开关
}

实验1:实现一个电子时钟

void Timer0Init()		//1毫秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值 
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	
	ET0 = 1; // 打开定时器0
	EA = 1; // 打开总开关
}

unsigned int count = 0;
// 设置时钟为 23:59:45 方便调试
unsigned int second = 45;
unsigned int minute = 59;
unsigned int hour = 23;

void main() {
//	// TMOD不可位寻址, M0给1 => 0x01
//	// TMOD = 0x01; // 配置定时器0
//	// 防止对定时器1的配置产生影响,使用与或赋值法
//	TMOD &= 0xF0; // 清空定时器0的配置
//	TMOD |= 0x01; // 配置定时器0
//	
//	// TCON可位寻址,TF0 = 0, TR0 = 1
//	TF0 = 0;
//	TR0 = 1;
//	
//	// 64635 到 65535 相差1000,也就是1ms会发生中断
//	TH0 = 64535 / 256; // 取高8位
//	TL0 = 54535 % 256; // 取低8位
//	
//	// 定时器开关
//	ET0 = 1; // 打开定时器0
//	EA = 1; // 打开总开关
	PT0 = 0;
	
	Timer0Init();
	LCD_Init();
	LCD_ShowChar(1, 3, ':');
	LCD_ShowChar(1, 6, ':');
	while (1) {
		if (second == 60) {
			second = 0;
			minute ++;
		}
		if (minute == 60) {
			minute = 0;
			hour ++;
		}
		if (hour == 24) {
			hour = 0;
		}
		LCD_ShowNum(1, 1, hour, 2);
		LCD_ShowNum(1, 4, minute, 2);
		LCD_ShowNum(1, 7, second, 2);
	}
}

// 发生时钟中断时,会触发该函数,中断号为1
void Timer0_Routube() interrupt 1 {
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	count ++;
	if (count == 1000) {
		second ++;
		count = 0;
		// 中断程序应该尽量简短
//		if (second == 60) {
//			second = 0;
//			minute ++;
//			if (minute == 60) {
//				minute = 0;
//				hour ++;
//				if (hour == 24) {
//					hour = 0;
//				}
//			}
//		}
	}
}

9. 串口通信

9.1 原理图

在这里插入图片描述

  1. 使用波特率计算器生成定时器1的配置,用于设置接收和发送速度
    在这里插入图片描述

    // 配置定时器1用于串口的同步通讯,控制波特率
    void UartInit(void)		//4800bps@11.0592MHz
    {
    	PCON &= 0x7F;		//波特率不倍速 
    	// 方式1(SM0 = 0; SM1 = 1) 允许接收(REN = 1)
    	SCON = 0x50;		//8位数据,可变波特率
    	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
    	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
    	TMOD &= 0x0F;		//清除定时器1模式位
    	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
    	TL1 = 0xFA;		//设定定时初值
    	TH1 = 0xFA;		//设定定时器重装值
    	ET1 = 0;		//禁止定时器1中断
    	TR1 = 1;		//启动定时器1
    	
    	// 当发送缓存区满和接收缓冲区满都会触发串口中断,中断号为4
    	EA = 1; // 中断允许控制位
    	ES = 1; // 允许串口中断
    }
    
  2. 使用串口助手测试串口通信功能
    在这里插入图片描述
    注意串口需要与左半部分的串口号保持一致,波特率需要与定时器1的配置保持一致。

实验1:每隔一秒发送一个数据,并且将接收字节保存到P2,控制LED灯

#include <AT89C51RC2.h>
#include <INTRINS.H>
#include "Delay.h"

// 配置定时器1用于串口的同步通讯,控制波特率
void UartInit(void)		//4800bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速 
	// 方式1(SM0 = 0; SM1 = 1) 允许接收(REN = 1)
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFA;		//设定定时初值
	TH1 = 0xFA;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	
	// 当发送缓存区满和接收缓冲区满都会触发串口中断,中断号为4
	EA = 1; // 中断允许控制位
	ES = 1; // 允许串口中断
}
// 串口发送一个字节的数据
void sendByte(unsigned char buffer) {
	SBUF = buffer; // 发送数据
	while (TI == 0); // 等待数据发送完成
	TI = 0; // 复位
}
void main() {
	unsigned char buffer = 0x00;
	// 初始化串口
	UartInit();

	while (1) {
		sendByte(buffer);
		buffer ++;
		Delayms(1000);
	}
}

// 串口中断程序,中断号为4
// 发送缓存区满和接收缓冲区满都会触发该中断
// 接收中断触发时,RI = 1;发送中断触发时,TI = 1
// 处理完中断后,需要将手动将RI或者TI复位为0,否则会重复触发串口中断
// 用接收的数据控制P2寄存器,也就是LED灯
void UARTRoutine() interrupt 4 {
	unsigned char buffer = SBUF;
	if (RI == 1) { // 串口接收中断
		P2 = SBUF;
		RI = 0;
	}
//	if (TI == 1) // 串口发送中断
}


网站公告

今日签到

点亮在社区的每一天
去签到