51单片机学习记录
一、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 置0
SCON |= (1 << 6); // 将SM1 置1
SCON |= (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存放的值自动装入TL1
TL1 = 243; //设置波特率4800
TH1 = 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 1
DQ = 0; //为1,按照协议拉高电平
_nop_();
_nop_(); //延时2us
DQ = 1; //拉高电平
delay10us(5);
}
else
{
// write 0
DQ = 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;
}