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

文章目录

  • 参考资料
  • [1. 准备工作](#1. 准备工作)
  • [2. 认识51单片机](#2. 认识51单片机)
    • [2.1 STC89C52单片机](#2.1 STC89C52单片机)
    • [2.2 管脚图](#2.2 管脚图)
    • [2.3 原理图](#2.3 原理图)
    • [2.4 按键抖动](#2.4 按键抖动)
    • [2.5 头文件说明](#2.5 头文件说明)
    • [2.6 模块化函数](#2.6 模块化函数)
      • [2.6.1 延时函数](#2.6.1 延时函数)
      • [2.6.2 矩阵键盘](#2.6.2 矩阵键盘)
  • [3. LED模块](#3. LED模块)
  • [4. 独立按键模块](#4. 独立按键模块)
  • [5. 动态数码管模块](#5. 动态数码管模块)
    • [5.1 原理图](#5.1 原理图)
    • 实验1:数码管LED3显示2
    • [实验2:数码管LED8 ~ LED1分别显示0 ~ 8](#实验2:数码管LED8 ~ LED1分别显示0 ~ 8)
  • [6. LCD1602接口](#6. LCD1602接口)
    • [6.1 原理图](#6.1 原理图)
  • [7. 矩阵按键模块](#7. 矩阵按键模块)
    • [7.1 原理图](#7.1 原理图)
    • [实验1:在LCD1602显示按下的键码(1 ~ 16)](#实验1:在LCD1602显示按下的键码(1 ~ 16))
    • 实验2:4位密码锁
  • [8. 定时器与中断](#8. 定时器与中断)
  • [9. 串口通信](#9. 串口通信)
    • [9.1 原理图](#9.1 原理图)

参考资料

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

1. 准备工作

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

  1. 51单片机
bash 复制代码
正在重新握手 ... 成功			[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

c 复制代码
#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 STC89C52单片机

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

2.2 管脚图

2.3 原理图

高清PDF可从这里下载江协科技资料下载

2.4 按键抖动

对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。

因此,需要进行防抖处理,如下:

c 复制代码
// 这是一个检测独立按键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 头文件说明

c 复制代码
#include <mcs51/lint.h>
#include <mcs51/8051.h>
#include <REGX52.H>

2.6 模块化函数

2.6.1 延时函数

c 复制代码
#ifndef __DELAY_H__
#define __DELAY_H__

void Delayms(unsigned char k);

#endif
c 复制代码
#include <INTRINS.H>

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

2.6.2 矩阵键盘

c 复制代码
#ifndef __MATRIXKEYBOARD_H__
#define __MATRIXKEYBOARD_H__

unsigned int matrixKeyboard();

#endif
c 复制代码
#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

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

实验2:LED灯D8闪烁

c 复制代码
#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流水灯

c 复制代码
#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

c 复制代码
#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

c 复制代码
#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

c 复制代码
// 对应数字 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)

c 复制代码
#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位密码锁

c 复制代码
// 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. 生成定时器初始化代码
c 复制代码
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:实现一个电子时钟

c 复制代码
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 小时前
C语言之输入输出
c语言·c++·算法
余额不足121383 小时前
C语言基础六:循环结构及面试上机题
c语言·开发语言
嗯? 嗯。3 小时前
工作bug,keil5编译器,理解int 类型函数返回值问题,详解!!!
c语言·return·keil5编译器·整数返回类型函数
legendary_1637 小时前
LDR6500:音频双C支持,数字与模拟的完美结合
c语言·开发语言·网络·计算机外设·电脑·音视频
一行玩python7 小时前
Xerces-C,一个成熟的 C++ XML 解析库!
xml·c语言·开发语言·c++
CHENWENFEIc7 小时前
基础排序算法详解:冒泡排序、选择排序与插入排序
c语言·数据结构·算法·排序算法·学习方法·冒泡排序
yangpipi-8 小时前
数据结构(C语言版)-4.树与二叉树
c语言·开发语言·数据结构
qystca9 小时前
洛谷 P8824 [传智杯 #3 初赛] 终端 C语言
c语言·开发语言
橘颂TA9 小时前
C语言:编译与链接
c语言·开发语言
炸鸡配泡面9 小时前
12.10 C语言作业3
c语言·c++·算法