51单片机学习记录

embedded/2025/3/18 10:51:37/

一、STC89C51RC/RD+系列单片机结构

        STC89C51RC/RD+系列单片机的内部结构框图如下图所示。STC89C51RC/RD+单片机中包含中央处理器(CPU)、程序存储器(Flash)、数据存储器(SRAM)、定时/计数器、UART串口、I/O接口、EEPROM、看门狗等模块。STC89C51RC/RD+系列单片机几乎包含了数据采集和控制中所需的所有单元模块,可称得上一个片上系统。  

单片机最小应用系统:

二、单片机程序实践

1.点亮LED灯

包含51单片机的头文件:#include ”reg52.h“

查询51单片机硬件原理图,可知LED灯由P2的8个引脚位控制。引脚输出电平为0,对应LED灯点亮,引脚输出电平为1,对应LED灯熄灭。从而控制P2引脚的输出电平就可以控制各个LED的亮灭。

#include "reg52.h"			
#include <intrins.h>	void Delay1ms(unsigned int time)		//@12.000MHz
{unsigned char i, j;while(time--){i = 2;j = 239;do{while (--j);} while (--i);}
}void led_on(unsigned char led)
{P2 = ~led;
}void main()
{unsigned int i;while(1){	    unsigned char led = 0x07;for(i=0;i<6;i++)	 //将led左移{led_on(led << i);Delay1ms(1000);	}led = led << 5;for(i=0;i<6;i++)	//将led右移{led_on(led >> i);Delay1ms(1000); }}	
}	
2、获取按键的状态

根据原理图所示,K1~K4对应P1引脚的高4位,K5对应P3引脚的第6位即P35

检测引脚电平,如果是低电平,则说明该引脚对应的KEY被按下

按键初始化:将5个按键的引脚电平设置为高电平,表示没有被按下

读取按键状态:读取相应引脚的状态,判断按键是否被按下

按键状态读取中,消抖很重要,以下是按键消抖的原因:

         当用户按下或释放按键时,由于机械弹性的作用,会产生短暂的不稳定接触,称为抖动。这种抖动可能导致单片机或处理器错误地多次读取按键动作,从而产生错误的信号。因此,消除这种抖动是确保按键准确读取的关键。

#include <reg51.h>
#include "key.h"
#include "delay.h"void key_init(void)
{P1 |= (0xF << 4);P3 |= (1 << 5);
}unsigned char key_status(void)
{unsigned char num = 0;if(0 == (P1 & (1 << 4))){delayms(10);if(0 == (P1 & (1 << 4))){while(!(P1 & (1 << 4)));delayms(10);if(P1 & (1 << 4))num = 1;}		}	else if(0 == (P1 & (1 << 5))){delayms(10);if(0 == (P1 & (1 << 5))){while(!(P1 & (1 << 5)));delayms(10);if(P1 & (1 << 5))num = 2;}}else if(0 == (P1 & (1 << 6))){delayms(10);if(0 == (P1 & (1 << 6))){while(!(P1 & (1 << 6)));delayms(10);if(P1 & (1 << 6))num = 3;}}						else if(0 == (P1 & (1 << 7))){delayms(10);if(0 == (P1 & (1 << 7))){while(!(P1 & (1 << 7)));delayms(10);if(P1 & (1 << 7))num = 4;}}						else if(0 == (P3 & (1 << 5))){delayms(10);if(0 == (P3 & (1 << 5))){while(!(P3 & (1 << 5)));delayms(10);if(P3 & (1 << 5))num = 5;}}return num;
}
3、数码管

每个数码管由abcdefg以及dp  8个段控制显示的内容,只需要控制8个段的电平输出,就可以改变显示内容。所使用的单片机数码管是共阴极数码管,因此高电平使对应段亮。

根据原理图可知,P1引脚控制数码管的段选,P1的低4位控制4个数码管的位选(即控制哪个数码管亮,哪个数码管灭)

#include <reg51.h>
#include "nixietube.h"
#include "delay.h"void seg_select(unsigned char seg)
{unsigned char array[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f,0x40,0};if(seg <= 11){P0 = array[seg];}
}void pos_select(unsigned char pos)
{if(pos <= 4){P1 = (1 << (pos -1));}
}void display_num(int num)
{int flag = 0;if(num >= 0){if(0 == num / 1000){display(11,4);}else{display(num / 1000, 4);flag = 1;}num = num % 1000;if(0 == num / 100 && flag == 0){display(11,3);}else{display(num / 100, 3);flag = 1;}num = num % 100;if(0 == num / 10 && flag == 0){display(11,2);}else{display(num / 10, 2);flag = 1;}num = num % 10;display(num,1);}else{num = -num;num = num % 1000;if(0 == num / 100){display(11,3);}else{display(num / 100, 3);if(0 == flag){display(10,4);}flag = 1;}num = num % 100;if(0 == num / 10 && flag == 0){display(10,2);}else{display(num / 10, 2);if(0 == flag){display(10,3);		}		flag = 1;}num = num % 10;display(num,1);if(num != 0 && flag == 0){display(10,2);}}}void display(unsigned char seg,unsigned char pos)
{pos_select(pos);seg_select(seg);delayms(5);seg_select(10);
}
4、UART串口通信

SCON:设置SM0 = 0、SM1 = 1,使用8位UART模式,波特率可变;设置REN = 1,允许串行接收

PCON(波特率选择):设置SMOD = 1,使串行口1、2、3的波特率加倍

设置串口中断:将ES串口中断置1打开,EA总中断置1打开

设置定时器:

将TMOD.7  TMOD.6  定时器1的M0,M1 置0,表示打开定时器1,将定时器1/计数器1用作定时器

设置M1、M0 为 1 0,表示8位自动装载定时器,当溢出时将TH1存放的值自动装入TL1

设置定时器/计数器中断控制:设置TR1为1,TMOD.7为0,TR1要为1才能开始计数

void uart_init(void)
{//SM0 = 0, SM1 = 1 时为方式1:8位UART,波特率可变SCON &= ~(1 << 7);  // 将SM0 置0SCON |= (1 << 6);  // 将SM1 置1SCON |= (1 << 4); //  将REN 置1,表示允许串行接收//波特率选择PCON |= (1 << 7); //将SMOD 设置为1,方式1、2、3的波特率加倍//设置串口中断IE |= (1 << 7) | (1 << 4); 	 //将ES串口中断置1打开,EA总中断置1打开//设置定时器TMOD &= ~(0xf << 4); //将TMOD.7  TMOD.6  定时器1的M0,M1 置0,表示打开定时器1,将定时器1/计数器1用作定时器,TMOD |= (0x02 << 4); //设置M1、M0 为 1 0,表示8位自动装载定时器,当溢出时将TH1存放的值自动装入TL1TL1 = 243;		   //设置波特率4800TH1 = 243;TCON |= (1 << 6); //设置TR1为1,TMOD.7为0,TR1要为1才能开始计数
}void uart_send(const unsigned char * dat, int len)
{int i = 0;for(i = 0; i < len; i++)	 {SBUF = dat[i];			 //将要发送的数据存入串口的缓存区中while(!(SCON & (1 << 1)));			//TI位,发送中断请求标志位,为1时表示数据发送完毕TI = 0;							   //将TI位手动置0}
}unsigned char dat[24] = {0};			   //定义存放数据的数组
unsigned char pos = 0;void uart_handler(void)  interrupt 4 
{if(RI)								   //判断接收中断请求标志位,为1时表示有数据发送过来,接收数据{									   //将串口缓存区中收到的数据放入dat中dat[pos++] = SBUF;RI = 0;							   //手动将RI置0,等待下一个数据的发送if(pos == 24)					   //防止对数组dat的越界访问{pos = 23;}}
}unsigned char uart_recv(unsigned char * buf)
{unsigned char ret = 0;unsigned char i = 0;for(i = 0; i < pos; i++)    {buf[i] = dat[i];		//将接收的数据放入buf中}ret = i;for(i = 0; i < pos; i++){dat[i] = 0;			   //清空dat}pos = 0;				   return ret;
}

5、DS18B20温度传感器的配套使用

模块简介:

        DS1820数字温度计提供9位温度读数,指示器件的温度。信息经过单线接口送入DS1820或从DS1820送出,因此从中央处理器到DS1820仅需连接一条线(和地)。读、写和完成温度变换所需的电源可以由数据线本身提供,而不需要外部电源。因为每一个DS1820有唯一的系列号(silicon1serialnumber),因此多个DS1820可以存在于同一条单线总线上。这允许在许多不同的地方放置温度灵敏器件。此特性的应用范围包括HVAC环境控制,建筑物、设备或机械内的温度检测,以及过程监视和控制中的温度检测。

特性简介:

        

引脚排列及说明:

 

 读写时序图说明:

主机读‘1’的时序以及读‘0’的时序:

DS18B20初始化:根据模块时序图,配合对应时间的延时,向模块发出初始化信号

向DS18B20写数据:按照时序图,进行延时和电平的转换,向模块写1或者写0

从DS18B20读取数据:按照时序图,进行延时和电平转换,判断模块发送的数据是1还是0

#include <reg51.h>
#include <intrins.h>
#include "delay.h"
#include "DS18B20.h"sbit DQ = P3^7;unsigned char DS18B20_init()
{char ack;DQ = 1;				   //拉高P3.7引脚delay10us(100);DQ = 0;				   //按照协议,拉低电平,给出初始化的信号delay10us(72);DQ = 1;				   //拉高电平delay10us(18);ack = DQ;			   //将DS18B20返回的信号接收delay10us(32);return ack;			   //返回收回的信号,可由此判断初始化成功与否
}void DS18B20_write(unsigned char dat)
{unsigned char i = 0;for(i = 0; i < 8; i++)			   //1bit 1bit的向传感器发送数据{if(dat & (1 << i))			   //判断发送位的数据为1还是为0{//write 1DQ = 0;					   //为1,按照协议拉高电平_nop_();_nop_();				   //延时2usDQ = 1;					   //拉高电平delay10us(5);}else{// write 0DQ = 0;						//为0,先拉低电平delay10us(5);				//延时DQ = 1;						//拉高电平_nop_();_nop_();}}
}unsigned char DS18B20_read(void)
{unsigned char dat = 0;unsigned char i = 0;for(i = 0; i < 8; i++)			//按位接收传感器返回的数据{DQ = 0;						//按照协议,拉低电平_nop_();_nop_();					//延时两秒DQ = 1;						//重新拉高电平if(DQ == 1)				{dat = dat | (1 << i);	  //收到的为1,将接受的数据放置到相应的位上}delay10us(4);}return dat;					   //返回收到的数据
}short get_temp()
{unsigned char th = 0;unsigned char tl = 0;unsigned char ack = 0;short temp = 0;ack = DS18B20_init();				   //初始化传感器if(1 == ack){return 0xfff;}DS18B20_write(0xcc);				  //初始化成功,向传感器发送信号DS18B20_write(0x44);delayms(1000);ack = DS18B20_init();				  //初始化传感器if(1 == ack){return 0xfff;}DS18B20_write(0xcc);				  //成功则发送信号DS18B20_write(0xbe);tl = DS18B20_read();				  //读取传感器返回的数据,先放入低8位th = DS18B20_read();				  //读取传感器返回的数据,放入高8位temp = (th << 8) | tl;				  //将高9位和低8位合成一个short数据return temp;						  //返回接收到的温度数据
}

5、自定义协议

协议格式:帧头、数据长度、数据、校验和、帧尾

#include <reg51.h>
#include "pack.h"unsigned char check_sum(unsigned char * dat,unsigned char len)
{unsigned char sum = 0;int i = 0;for(i = 0; i < len; i++){sum += dat[i];		  //计算数据位的总和,作为校验位的数据}return sum;
}unsigned char package(unsigned char * dat, unsigned char len, unsigned char * outdat)
{int i = 0;int j =   0;outdat[i++] = FRAME_HEAD;		  //放入帧头outdat[i++] = len;				  //放入数据长度for(j = 0; j < len; j++){outdat[i++] = dat[j];			   //放入数据}outdat[i++] = check_sum(dat,len);		   //计算校验和并放入帧数据中outdat[i++] = FRAME_TAIL;				   //放入帧尾return i;								  //返回一帧数据的长度
}unsigned char data_transfor(data_type * datatype,unsigned char * dat)
{int i = 0;if(datatype->t_flag == 1){dat[i++] = 0x01;dat[i++] = (datatype->temp) >> 8;dat[i++] = datatype->temp;}if(datatype->h_flag == 1){dat[i++] = 0x02;dat[i++] = (datatype->humi) >> 8;dat[i++] = datatype->humi;}	if(datatype->l_flag == 1){dat[i++] = 0x03;dat[i++] = (datatype->light) >> 8;dat[i++] = datatype->light;}return i;	
}unsigned char package1(unsigned char * dat,unsigned char len,unsigned char * outdat)
{int i = 0;int j = 0;outdat[i++] = FRAME_HEAD;outdat[i++] = len;for(j = 0; j < len; j++){outdat[i++] = dat[j];}outdat[i++] = check_sum(dat,len);		   //计算校验和并放入帧数据中outdat[i++] = FRAME_TAIL;return i;
}

http://www.ppmy.cn/embedded/173573.html

相关文章

二叉树算法题实战:从遍历到子树判断

目录 一、引言 二、判断两棵二叉树是否相同 思路 代码实现 注意点 三、二叉树的中序遍历 思路 代码实现 注意点 四、判断一棵树是否为另一棵树的子树 思路 代码实现 注意点 ​编辑 五、补充 一、引言 作者主页&#xff1a;共享家9527-CSDN博客 作者代码仓库&am…

语音识别-FunASR-docker部署-【超简洁步骤】

FunASR介绍 FunASR是一个开源的语音识别工具包&#xff0c;它旨在为开发者提供一个灵活且易于使用的平台&#xff0c;用于开发和部署自动语音识别&#xff08;ASR&#xff09;系统。FunASR支持多种语言&#xff0c;并提供了丰富的API接口&#xff0c;使得集成和定制化变得更加简…

2025年【广东省安全员C证第四批(专职安全生产管理人员)】考试及广东省安全员C证第四批(专职安全生产管理人员)模拟试题

安全生产是各行各业不可忽视的重要环节&#xff0c;特别是在广东省这样的经济大省&#xff0c;安全生产的重要性更是不言而喻。为了确保安全生产管理人员具备足够的专业知识和实际操作能力&#xff0c;广东省定期举办安全员C证考试。本文将详细介绍2025年广东省安全员C证第四批…

在Spring Boot项目中接入DeepSeek深度求索,感觉笨笨的呢

文章目录 引言1. 什么是DeepSeek&#xff1f;2. 准备工作2.1 注册DeepSeek账号 3.实战演示3.1 application增加DS配置3.2 编写service3.3 编写controller3.4 编写前端界面chat.html3.5 测试 总结 引言 在当今快速发展的数据驱动时代&#xff0c;企业越来越重视数据的价值。为了…

K8S快速部署

前置虚拟机环境正式部署BUG解决 前置虚拟机环境 每个虚拟机配置一次就好 #关闭防火墙 systemctl stop firewalld systemctl disable firewalld #关闭 selinux sed -i s/enforcing/disabled/ /etc/selinux/config # 永久 setenforce 0 # 临时 #关闭 swap swapoff -a # 临时 vi…

08-单链表-单链表基本操作2

题目 来源 18. 链表的基本操作 思路 与上一份的最大区别就是要先判断一下要处理的k是否是合法的&#xff0c;也就是要先将指针能够指向k&#xff1b; 上一份的idx是一个全局的指针&#xff0c;由于链表天生就是物理位置不用连续&#xff0c;所以idx可以在任意位置&#xff…

使用OpenCV和MediaPipe库——抽烟检测(姿态监控)

目录 抽烟检测的运用 1. 安全监控 (1) 公共场所禁烟监管 (2) 工业安全 2. 智能城市与执法 (1) 城市违章吸烟检测 (2) 无人值守管理 3. 健康管理与医疗 (1) 吸烟习惯分析 (2) 远程监护 4. AI 监控与商业分析 (1) 保险行业 (2) 商场营销 5. 技术实现 (1) 计算机视…

微服务即时通信系统---(八)用户管理子服务

本章节,主要对项目中用户管理子服务模块进行分析、开发与测试。 功能设计 用户管理子服务,主要用于管理用户的数据,以及关于用户信息的各项操作,因此,在本模块中,用户管理子服务需要提供以下的功能性接口 用户注册用户输入 用户名(昵称) + 用户密码 进行注册。用户登录…