一、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;
}