文章目录
- 新手学单片坤之定时器与串口
- 一、利用中断发出1Khz的方波信号,驱动蜂鸣器鸣叫。
- 二、LED数码管秒表的制作。用2位数码管显示计时时间,最小计时单位为“百毫秒”,计时范围0.1~9.9s。当第1次按一下计时功能键时,秒表开始计时并显示;第2次按一下计时功能键时,停止计时,将计时的时间值送到数码管显示;如果计时到9.9s,将重新开始从0计时;第3次按一下计时功能键,秒表清0。再次按一下计时功能键,则重复上述计时过程。
- 三、使用定时器实现一个LCD显示时钟。
- 四、甲、乙两单片机进行方式3(或方式2)串行通信。甲机把控制8个流水灯点亮的数据发送给乙机并点亮其P1口的8个LED
- 五、将单片机串口与笔记本电脑串口模块相连,单片机每隔2秒发送“Hello C51”,笔记本电脑用串口助手软件接收。 如果串口助手发送字符“0" 给单片机,则单片机停止发送; 如果单片机收到“1”,则继续每隔2秒发送“Hello C51”。
- 六、总结
新手学单片坤之定时器与串口
一、利用中断发出1Khz的方波信号,驱动蜂鸣器鸣叫。
1、蜂鸣器介绍
蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号
蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音
蜂鸣器有正负极,顶部印有+号的为正极,若蜂鸣器引脚没剪,则长的为正极
本次任务使用的是无源蜂鸣器,故需要提供脉冲。
(1)脉冲
脉冲的单位
两个脉冲相继出现的间隔时间,就是脉冲周期,它是频率的倒数;而将在单位时间(1秒)内所产生的脉冲个数称为频率。
频率的单位有:Hz(赫)、kHz(千赫)、MHz(兆赫)、GHz(吉赫)。
其中1GHz=1000MHz,1MHz=1000kHz,1kHz=1000Hz。
假如脉冲频率为50HZ(一秒种发出50个脉冲,每个脉冲占用的时间就是脉冲周期),则脉冲周期 = 1 秒 / 脉冲频率 = 1 / 50 = 0.02 秒(S)
所以,对于本个实验脉冲周期为1ms,所以我们可以对定时器设定0.5ms的中断,进入中断后对蜂鸣器取反,就能得到一个1ms的脉冲周期。
2、代码实现
#include <REGX52.H>
#include "Timer0.h"
sbit Buzzer=P2^5; //单片机连接蜂鸣器的引脚是p2^5,这里就相当于给他重新取一个名字。可以查看开发板原理,查看你的开发板蜂鸣器连接的哪一个口,
void main()
{
Timer0Init(); //定时器初始化函数
while(1)
{
}
}
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
TL0 = 0x0C; //设置定时初值
TH0 = 0xFE;
Buzzer=~Buzzer;//对引脚取反,实现振荡脉冲
}
定时器初始化函数见上一篇博客
3、仿真图
由图可见,可以看见1ms的脉冲周期。
实物效果就是蜂鸣器发出持续的响。
二、LED数码管秒表的制作。用2位数码管显示计时时间,最小计时单位为“百毫秒”,计时范围0.1~9.9s。当第1次按一下计时功能键时,秒表开始计时并显示;第2次按一下计时功能键时,停止计时,将计时的时间值送到数码管显示;如果计时到9.9s,将重新开始从0计时;第3次按一下计时功能键,秒表清0。再次按一下计时功能键,则重复上述计时过程。
1、代码实现
#include<reg51.h> //头文件
unsigned char code discode1[]={0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};
//数码管显示0~9的段码表, 带小数点
unsigned char code discode2[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
//数码管显示0~9的段码表,不带小数点
unsigned char timer=0; //timer记录中断次数
unsigned char second; //second储存秒
unsigned char key=0; //key记录按键次数
main() //主函数
{
TMOD=0x01; //定时器T0方式1定时
ET0=1; //允许定时器T0中断
EA=1; //总中断允许
second=0; //设初始值
P0=discode1[second/10]; //显示秒位0
P2=discode2[second%10]; //显示0.1s位0
while(1) //循环
{
if((P3&0x80)==0x00) //当按键被按下时
{
key++; //按键次数加1
switch(key) //根据按键次数分三种情况
{
case 1: //第一次按下为启动秒表计时
TH0=0xee; //向TH0写入初值的高8位
TL0=0x00; //向TL0写入初值的低8位,定时5ms
TR0=1; //启动定时器T0
break;
case 2: //按下两次暂定秒表
TR0=0; //关闭定时器T0
break;
case 3: //按下3次秒表清0
key=0; //按键次数清
second=0; //秒表清0
P0=discode1[second/10]; P2=discode2[second%10]; //显示秒位0 //显示0.1s位0
break;
}
while((P3&0x80)==0x00); //如果按键时间过长在此循环
}
}
}
void int_T0() interrupt 1 using 0 //定时器T0中断函数
{
TR0=0; //停止计时,执行以下操作(会带来计时误差)
TH0=0xee; //向TH0写入初值的高8位
TL0=0x00; //向TL0写入初值的低8位,定时5ms
timer++; //记录中断次数
if (timer==20) //中断20次,共计时20*5ms=100ms=0.1s
{
timer=0; //中断次数清0
second++; //加0.1s
P0=discode1[second/10];P2=discode2[second%10]; //根据计时,即时显示秒位 //根据计时,即时显示0.1s位
}
if(second==99) //当计时到9.9s时
{
TR0=0; //停止计时
second=0; //秒数清0
key=2; //按键数置2,当再次按下按键时, //key++,即key=3,秒表清0复原
}
else //计时不到9.9s时
{
TR0=1; //启动定时器继续计时
}
}
2、仿真效果
三、使用定时器实现一个LCD显示时钟。
1、LCD简单函数
2、代码实现
//主函数模块
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char Sec,Min,Hour;
void main()
{
LCD_Init();
Timer0Init();
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,1," : :");//LCD显示字符串函数
while(1)
{
LCD_ShowNum(2,1,Hour,2); //LCD显示数字函数
LCD_ShowNum(2,4,Min,2);
LCD_ShowNum(2,7,Sec,2);
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC;
T0Count++; //当T0count满1000时,定时器正好为1s
if(T0Count>=1000)
{
T0Count=0; //重新从0开始
Sec++; //秒加1
if(Sec>=60)
{
Sec=0; //当秒数大于60,清零
Min++; //分钟数清零
if(Min>=60)
{
Min=0; //分钟数大于60时,清零
Hour++; //小时加加
if(Hour>=24)
{
Hour=0;
}
}
}
}
}
LCD1602函数实现模块
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
Delay函数以及定时器函数可以查看之前博客。
3、效果展示
四、甲、乙两单片机进行方式3(或方式2)串行通信。甲机把控制8个流水灯点亮的数据发送给乙机并点亮其P1口的8个LED
1、串行口通信
2、简单串口模式图
3、串行口相关寄存器
(1)SCON寄存器
(2)PCON寄存器
更加详细的可以自行查阅手册,中文版可能翻译有错,推荐看英文版
4、初始化串口(简便方法)
1、打开stc小程序
其中蓝色方框里的删去,波特率一般选择4800,9600等
当然也可以根据自己需求设置
5、波特率
波特率(bandrate),指的是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位。比如每秒钟可以传输9600个二进制(传输一个二进制位需要的时间是1/9600秒,也就是104us),波特率就是9600。
6、方式三
由于方式三多了奇偶校验位,所以我们要对奇偶校验位进行配置
**奇偶校验位:**当这个位的数时与前面1的个数的和是奇数还是偶数。奇数就是奇校验,偶数就是偶校验。
要配置奇偶校验位,我们就需要用到PSW寄存器中的P
PSW
从图中可发现P是奇偶标志位,注意区分奇偶校验位与奇偶标志位的区别。
7、代码实现
//发送机
#include <reg51.h>
sbit Ps=PSW^0; //P位为PSW寄存器的第0位,即奇偶校验位
unsigned char Tab[8]= {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f}; //控制流水灯显示数据数组,为全局变量
void Send(unsigned char dat);
void delay(void);
void main(void) //主函数
{
unsigned char i;
TMOD=0x20; //设置定时器T1为方式2
SCON=0xc0; //设置串口为方式3
PCON=0x00; //SMOD=0
TH1=0xfd; //给T1赋初值,波特率设置为9600
TL1=0xfd;
TR1=1; //启动定时器T1
while(1)
{
for(i=0;i<8;i++)
{
Send(Tab[i]);
delay( );
}
}
}//大约200ms发送一次数据
void Send(unsigned char dat) // 发送1字节数据的函数
{
TB8=P;// 将偶校验位作为第9位数据发送
SBUF=dat;while(TI==0);//检测TI,TI=0,未发送完// 空操作 // 1字节发送完,TI清0
TI=0;
}
void delay(void)
{unsigned char m,n;
for(m=0;m<250;m++)for(n=0;n<250;n++);
}
//接收机
#include <reg51.h>
sbit Ps=PSW^0;// P位为PSW 寄存器的第0位,即奇偶校验位
unsigned char Receive(void);
void main(void)//主函数
{
TMOD=0x20;//设置定时器T1为方式2
SCON=0xd0;//设置串口为方式3,允许接收REN=1
PCON=0x00;// SMOD=0
TH1=0xfd;//给定时器T1赋初值,波特率为9600
TL1=0xfd;
TR1=1;//接通定时器T1
REN=1;//允许接收
while(1)
{
P1=Receive( )//将接收到的数据送P1口显示
;}
}
unsigned char Receive(void)//接收1字节数据的函数
{
unsigned char dat;
while(RI==0);//检测RI,RI=0,未接收完,则循环等待
;
RI=0;//已接收一帧数据,将RI清0
ACC=SBUF;//将接收缓冲器的数据存于ACC
if(RB8==P)//只有偶校验成功才能往下执行,接收数据
{
dat=ACC;//将接收缓冲器的数据存于dat
return dat;//将接收的数据返回
}
}
TB8=P;// 将偶校验位作为第9位数据发送,这里为什么说是偶检验位?而不是奇校验位?
你可以这样理解
1、PSW寄存器中的P位就是监测寄存器里数据1的个数(不算上P本身),如果1的个数为奇数,p=1,为偶数,p=0。
2、将P给TB8,如果P=1,再加上这TB8本身,1的个数就为偶数了。如果P=0,加上TB8本身,1的个数还是偶数。所以这里就确定为偶校了。
8、仿真图
9、实物效果
实物由于没有两个单片机,就换一种表达形式
#include <REGX52.H>
#include "UART.h"
void main()
{
UART_Init();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2=SBUF; //将串口数据发送给P2
RI=0;
}
}
void UART_Init()//4800bps@12.000MHz
{
SCON=0x50; //串行控制寄存器配置,将SM1置一与REN置一
PCON|=0x80;
//TMOD=0x01; //0000 0001,特殊功能寄存器里,通过赋值选择定时器工作模式
//使用8位自动重装定时器,就是模式3,选用定时器1
TMOD&=0x0F;//把TMOD高四位清零,低四位保持不变
TMOD|=0x20;//
TL1 = 0xFA; //设定定时初值
TH1 = 0xFA; //设定定时器重装值
ET1=0; //因为不需要中断,就禁止中断
TR1=1; //启动定时器1
EA=1;
ES=1;//启动串口中断
}//触发串口中断函数
效果如下
f0对应的二进制就为11110000,同理ff对应的二进制就为11111111.
五、将单片机串口与笔记本电脑串口模块相连,单片机每隔2秒发送“Hello C51”,笔记本电脑用串口助手软件接收。 如果串口助手发送字符“0" 给单片机,则单片机停止发送; 如果单片机收到“1”,则继续每隔2秒发送“Hello C51”。
1、代码实现
#include <REGX52.H>
#include "UART.h"
#include "Delay.h"
unsigned char array[20]="Hello C51 ";//定义一个字符串
unsigned char a=1;//定义一个全局变量,a用来判断是否继续发送字符的依据
void main()
{
UART_Init();//串口初始化
while(1)
{
if(a==1)//当a==1时,就调用字符串以及延时函数
{ UART_SendString(array);
Delay(2000);
}
if(a==0)//当a==0时,就进入死循环
{
while(1)
if(a==1)break;//判断a是否等于1,等于1就跳出当前的死循环,去执行外循环
}
}
}
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2_0=SBUF; //将串口数据发送给P2_0端游,正好来看向单片机发送数据是否成功
if(P2_0==0)//如果P2_0=0,对应单片机上第一个led会点亮
{
a=0; //再将a赋值,去主函数里面进行判断是否要进行发送字符串函数
}if(P2_0==1)
{
a=1;
}
RI=0;
}
}
2、效果
可以对照着单片机上的第一个led灯来确定是否发送成功
六、总结
较浅的理解到了UART通信,学会了串口的配置等