STC15F2K60S2串口通信
.
.
一、先斩后奏,上代码!!!!
先强调:通常我们做单片机与单片机通信、单片机与PC通信、PC与PC串口通信,基本选择方式1的串口通信方式,下面的讲解中我会详细的介绍串口方式1
下面这段代码实现在PC端发送一个数据,15接受后发送给PC端 “I get xx”
#include <stc15f2k60s2.h>
#define uchar unsigned char
#define uint unsigned int
uchar Flag,a,i;
uchar code table[]="I get ";
//下面这个初始化有问题,不能正常初始化,先待定,过几天我查查问题,看看哪里出错了。
/*
void init()
{
TMOD=0x20;//T1定时器的方式2工作:8位自动重装定时器
TH1=0xfd;
TL1=0xfd;//溢出后,TH1中的值将被自动重装到TL1中
TR1=1;//定时器1开始运行
REN=1;//允许串口接受数据
SM0=0;
SM1=1;//串口方式1,10位异步收发(8位数据),波特率可变(由定时器1的溢出率控制)
EA=1;//开启全局中断允许
ES=1;//开启串口中断允许
}
*/
//下面这段初始化是由stc-isp上自带的波特率计算器实现的。到时候直接在上面选择设置,然后就自动生成出想要的代码了,下面会有详细的介绍
void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F;//01111111波特率不倍速,PCON电源管理寄存器
SCON = 0x50;//8位数据,可变波特率,SCON,串口控制寄存器
AUXR |= 0x40;//定时器1时钟为Fosc,即1T
AUXR &= 0xFE;//串口1选择定时器1为波特率发生器
TMOD &= 0x0F;//清除定时器1模式位
TMOD |= 0x20;//设定定时器1为8位自动重装方式
TL1 = 0xDC;//设定初值
TH1 = 0xDC;//设定定时器重装初值
ET1 = 0;//禁止定时器1中断
TR1 = 1;//启动定时器1
//下面这两句是stc-isp没有生成的,需要我们自己加上
EA=1;//开启全局中断允许
ES=1;//开启串口中断允许
}
void main()
{
UartInit();
while(1)
{
if(Flag==1)//Flag是我们定义的进入中断程序的标志(这里判断我们是否进入了中断程序)
{
ES=0;//发送数据前,先将禁止串口中断
for(i=0;i<6;i++)//(发送10个数据出去)
{
SBUF=table[i];//将table[]中的数据一个一个给SBUF寄存器,并一个一个发送出去
while(!TI);//等待数据发送完毕
TI=0;//将发送数据标志位清零(因为我们成功发送数据,系统自动会把TI置1,标志数据发送成功,从而向CPU申请中断,我们需要手动吧TI清零)
}
SBUF=a;//将Data中的数据给SBUF,并发送
while(!TI);//等待数据发送成功
TI=0;//手动将发送数据标志位清零
ES=1;//允许串口中断(当所有数据发送完成之后,再打开接受数据的串口中断,因为当我们没有发送完数据,而定时器T1溢出时候,这个时候我们如果打开串口中断,那么我们就会在发送数据的过程中进入到接收数据的中断程序里面,SBUF寄存器里面的数据就会混乱,导致产生错误的结果,所以我们需要发送数据时候关闭串口中断,接收数据的时候打开串口中断)
Flag=0;//进入中断程序标志位清零
}
}
}
void ser() interrupt 4
{
RI=0;//接受数据标志位清零
a=SBUF;//将接受到的数据存入Data中
Flag=1;//进入中断标志位置1
}
大家把这段程序看完之后,是不是还是一脸懵,没关系,我们会一步一步来分析,进行详细的讲解。
PS:这个程序有BUG,从PC端发送过去的数据单片机能接收到,但是就用文本方式打印不到PC端的串口助手里面。不是没有接受到数据,实际上是接收到数据了,可以将打印的方式设置成为HEX,这样就能看到被打印的数据,不过都是16进制的数据。目前这个BUG我也不知道该怎么去修复,还请路过的大佬帮忙看下
HEX接收方式
文本接收方式
二、详细的讲解
1、串口通信前,先需要了解的知识
(1)、波特率
定义:在信息传输通道中,携带数据信息的信号单元叫码元,每秒钟通过信道传输的码元数称为码元传输速率,简称波特率。波特率是传输通道频宽的指标。
详细的讲解,请看我的另一篇文章:波特率的辨析
(2)、波特率的计算
在串行通信中,收发双方对发送或接收数据的速率要有约定。通过编程,可对单片机串行口设定为4种工作方式,方式0、方式2的波特率是固定的;方式1、方式3的是可变的,有定时器T1的溢出率(即1/T1每秒溢出的次数)
在这里我们只介绍方式1的波特率计算,因为对于初学者来说,方式1是我们最常用的也是需要掌握的
方式1的波特率:=((2^(SMOD))/32) * (T1的溢出率)
SMOD是PCON寄存器的最高位
T1的溢出率:计算出T1每溢出1次所需要的时间T,则1/T为T1的溢出率。
.
.
2、初始化中涉及到的一些寄存器的使用
(1)、PCON(不可位寻址)
SMOD(SERIAL MODE):与串口通信有关的位。SMOD=0,串口方式1、2、3时波特率正常;SMOD=1,串口方式1、2、3时,波特率加倍。
PD(POWER DOWN)——掉电模式设定位;PD=0,单片机正常工作;PD=1,单片机进入掉电模式,可由外部中断低电平触发、下降沿触发、硬件复位模式唤醒。进入掉电后:1.外部晶振停振; 2.CPU、定时器、串口全部停止工作。3.外部中断继续工作
IDL(IDLE)——空闲模式设定位。IDL=0,单片机正常工作;IDL=1,单片机进入空闲模式:除CPU不工作,其他都正常工作。可由任意一个中断或者硬件复位唤醒。
其他几个不过多介绍,以后学到再介绍。
.
.
(2)、SCON(可位寻址)
SM0/SM1
SM0 | SM1 | 方式 | 功能说明 |
---|---|---|---|
0 | 0 | 0 | 同步移位寄存器方式(通常用于扩展I/O口) |
0 | 1 | 1 | 10位异步收发(8位数据),波特率可变(由定时器1的溢出率控制) |
1 | 0 | 2 | 11位异步收发(9位数据),波特率固定 |
1 | 1 | 3 | 11位异步收发(9位数据),波特率可变(由定时器1的溢出率控制) |
REN:允许串行接受位。REN=1,允许串口接受数据;REN=0,禁止串口接受数据。
.
.
(3)、AUXR(不可位寻址)
.
.
(4)、TMOD(不可位寻址)
.
.
(5)、IE&IP(可位寻址)
ET1、ES、EA等上述代码所需要调动的寄存器的位都在这里
.
.
.
3、数据的存储和发送
一个转运站——SBUF
程序代码中
SBUF=table[i];
一旦SBUF中被写入了数据,在被写入之后,数据就会自动通过串口发送出去
Data=SBUF;
在PC端接受的数据也被存储到SBUF中,需要我们读出来,上述就是一个读取的操作
.
.
.
4、怎么能够简单的得到串口的波特率设置
就像我程序中第第一个void init()函数一样,我是照着相关书籍和数据手册的指导一步一步去配置寄存器,而且还自己计算了一下波特率位9600时候,我需要将T1的溢出率调节到多少,给TH1和TL1装入的值是多少。太麻烦了!!!,而且最重要的是,不知道漏了哪一步,没有调对,程序不能运行,对于一个为了调寄存器到凌晨1点的人来所,内心真的很F**K。
后来突然想起,STC-ISP上有个波特率计算器,本想着只是计算下波特率,T1中要装入的初值是多少,不过没想到ISP把大部分的工作都做好了,简直太人性化的一个软件了 ~~感动的都哭了
好了废话不多说,上软件!
这个软件大家应该都不陌生,不过里面的有些功能大家还没有接触过。下面就带大家了解下如何快速的计算波特率并且生成初始化的寄存器配置代码:
(1)、
打开我们的界面后看到这些
(2)、
注意红色框框里面的,需要我们去调节的参数
(3)、
这是调节好后的参数,根据我们这次讲的,我们用这种方式调节,当然,我们要根据自己的需求,去调节不同的方式都可。
然后就生成代码ctrl c +ctrl v 到我们的程序中就可了
PS:由于某些版本的KEIL汉字的字符是由两个位组成的,而我们上述生成的代码中的备注只是一个位的汉字,所以当我们复制上述代码到我们程序中的时候,备注部分的汉字会变成乱码。这个时候,不要惊讶,不要慌张,轻轻的说一声艹,然后重新在KEIL上一个一个的敲上去就OK了。别说我没有提醒你哦。
.
.
.
5、串口通信的实例化程序——通过PC端控制开发板上的LED灯
这是19年吉林大学无线电爱好者协会林师哥设计的开发板,上面有8个可控的LED灯,我们通过下面的那一段程序,实现PC端对开发板上的LED灯的控制
下面这个是LED灯的引脚定义,有少许错误,大家直接顺着编号定义下来就OK
附上最终的代码:
大家可以自己试试效果,然后在代码中找下,为什么会这样。
#include <stc15f2k60s2.h>
#define uchar unsigned char
#define uint unsigned int
#define led1 P00
#define led2 P01
#define led3 P02
#define led4 P03
#define led5 P04
#define led6 P05
#define led7 P06
#define led8 P07
uchar Flag,a,i;
uchar code table[]="I get ";
void led();
//下面这个初始化有问题,不能正常初始化,先待定,过几天我查查问题,看看哪里出错了。
/*
void init()
{
TMOD=0x20;//T1定时器的方式2工作:8位自动重装定时器
TH1=0xfd;
TL1=0xfd;//溢出后,TH1中的值将被自动重装到TL1中
TR1=1;//定时器1开始运行
REN=1;//允许串口接受数据
SM0=0;
SM1=1;//串口方式1,10位异步收发(8位数据),波特率可变(由定时器1的溢出率控制)
EA=1;//开启全局中断允许
ES=1;//开启串口中断允许
}
*/
//下面这段初始化是由stc-isp上自带的波特率计算器实现的。到时候直接在上面选择设置,然后就自动生成出想要的代码了,下面会有详细的介绍
void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F;//01111111波特率不倍速,PCON电源管理寄存器
SCON = 0x50;//8位数据,可变波特率,SCON,串口控制寄存器
AUXR |= 0x40;//定时器1时钟为Fosc,即1T
AUXR &= 0xFE;//串口1选择定时器1为波特率发生器
TMOD &= 0x0F;//清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xDC;//设定初值
TH1 = 0xDC;//设定定时器重装初值
ET1 = 0;//禁止定时器1中断
TR1 = 1;//启动定时器1
//下面这两句是stc-isp没有生成的,需要我们自己加上
EA=1;//开启全局中断允许
ES=1;//开启串口中断允许
}
void main()
{
UartInit();
while(1)
{
if(Flag==1)//Flag是我们定义的进入中断程序的标志(这里判断我们是否进入了中断程序)
{
ES=0;//发送数据前,先将禁止串口中断
for(i=0;i<6;i++)//(发送10个数据出去)
{
SBUF=table[i];//将table[]中的数据一个一个给SBUF寄存器,并一个一个发送出去
while(!TI);//等待数据发送完毕
TI=0;//将发送数据标志位清零(因为我们成功发送数据,系统自动会把TI置1,标志数据发送成功,从而向CPU申请中断,我们需要手动吧TI清零)
}
SBUF=a;//将Data中的数据给SBUF,并发送
while(!TI);//等待数据发送成功
TI=0;//手动将发送数据标志位清零
ES=1;//允许串口中断(当所有数据发送完成之后,再打开接受数据的串口中断,因为当我们没有发送完数据,而定时器T1溢出时候,这个时候我们如果打开串口中断,那么我们就会在发送数据的过程中进入到接收数据的中断程序里面,SBUF寄存器里面的数据就会混乱,导致产生错误的结果,所以我们需要发送数据时候关闭串口中断,接收数据的时候打开串口中断)
Flag=0;//进入中断程序标志位清零
led();
}
}
}
void ser() interrupt 4
{
RI=0;//接受数据标志位清零
a=SBUF;//将接受到的数据存入Data中
Flag=1;//进入中断标志位置1
}
void led()
{
switch(a)
{
case 1:led1=!led1;break;
case 2:led2=!led2;break;
case 3:led3=!led3;break;
case 4:led4=!led4;break;
case 5:led5=!led5;break;
case 6:led6=!led6;break;
case 7:led7=!led7;break;
case 8:led8=!led8;break;
}
}
.
.
.
这个串口通信教程到这里就结束了,还有很多不足,请大家在评论区指教。同时,如果有哪些地方不太懂,也可以在评论区留言,我会及时的解答。