本帖最后由 夜色星空 于 2017-2-1 21:23 编辑
单片机要与计算机等设备进行交互
那么就会产生通讯
既然有交流,那么就一定有语法和时序的要求
对与单片机的串行口来说
你如果想通过这个接口来与单片机进行通讯的话
那么就必须要遵循它的规则
首先,先来看看硬件
以黑金V2.5与计算机通讯为例
因为我们通常使用的是USB口,它与串口的协议是不一样的
所以我们要将USB转为串口
此时就用到了CH340芯片与其驱动程序
这两样东西可以将USB口虚拟为一个串口
用以与单片机进行通讯(有兴趣可自行百度,在此不再赘述)
(至于为什么TX接RX,一个是发送,一个是接收,发送给接收,这样接才能进行通讯,一般用下载器时会遇到这种问题)
如图
串口在MCU上的位置
(PS:本帖主要说一下串口简单应用与常见问题,至于底层的收发时序,发展演化,各种转换等问题在本帖中不予探讨,有兴趣可自行百度)
然后再来看MCU中的串口寄存器
(STC12C5A60S2的芯片有两个串口,然而我们现阶段只使用一个,所以有些寄存器并不会提及)
首先,串行口和定时器一样有4种模式
如图
串口发送一帧,只有8个有效字节,而方式23的九位数据,有一位是用来进行校验的
也就是说,一帧数据由
起始位,数据位,(校验位),停止位
构成
然后就是同步与UART(异步)的区别
简单讲,同步就是两者步调一致
帧与帧之间的间隔时间是确定的
而异步收发,帧与帧之间的间隔是不确定的
当使用UART传送时,你可以发一帧后等待很长时间再去发第二帧
那么这就有一个问题
MCU不能一直在这里等待数据传送,这样将会大大浪费CPU资源
所以,我们将其变成中断形式,当有数据发送或接收时
CPU就跳转到那里进行执行
这样大大提高了CPU的利用率
既然有中断,那么就有中断标志去通知CPU
既然有数据传输,就应该有暂时存储数据的地方
既然串口只有两根线而要传输8+1+1(+1)位数据,那么一定有传输的先后顺序
既然要想让对方接收,就应该让对方知道什么时候的数据是我想要传输的
综上
先说第一个问题
此寄存器中的TI与RI分别为发送中断与接收中断
如图,一般在这里会有的问题就是没有软件清零
它不像定时器一样可以进行机器清零,它只能进行软件清零,也就是说写程序时需要在代码中进行清零
再说第二个问题
过来的数据存储在哪
如图
也就是说,会在CPU还没来得及处理前先将数据保存在SBUF寄存器中
最后就是传输的时序
这就引进了一个波特率(bps)的概念
举个例子
计算机每秒发送一位数据
单片机每秒接收一位数据
这样,计算机发送的数据单片机都能够很好地收到
假如计算机每两秒发送一个数据,而单片机每一秒接收一位数据
那么当单片机接收第二个数据时,其实还是计算机的第一个数据
这样就发生了错误
为避免此类情况发生,我们引进了波特率的概念
假设波特率为9600
则相当于规定,每秒发送(或接收)9600位
也就是说计算机1/9600秒发送(接收)一位
单片机1/9600接收(发送)一位
这样就可以保证接收到的数据是正确的
然而如何去用程序实现上述功能?
我们只需要更改UART相关寄存器的值就可以了
首先选择串口模式,它规定了一帧数据有多少位,波特率如何计算
一般我们使用方式1
也就是8位数据,
波特率为定时器1的溢出率
如图,计算公式SYSclk为系统频率
在此说一下89系列与12系列单片机的区别
如图
现在大多数教材用的都是89系列单片机,固定为12T,而12系列芯片可进行1T
也就是说定时器的速度可以与系统晶振频率相同
串口模式0的速度也可为2分频
所以使用黑金V2.5时要比书上的程序多出几行代码,来设定定时器、串口的频率
(当时我刚接触黑金V2.5时用的是89的程序,然后就死活接收不到正确的数据,最后查阅手册才发现要配置12系列特有的寄存器)
(然而我们一般计算波特率直接使用STC-ISP下载软件的波特率计算器,也就避免了我当时的尴尬,并且每一行的代码功能都写的非常详细)
如
[mw_shl_code=c,true]void UartInit(void) //9600bps@32MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF7; //设定定时初值
TH1 = 0xF7; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
[/mw_shl_code]
只需简单的复制粘贴,并在主函数中调用此函数,就可以搞定波特率的设置
下附一个有详细解释的程序(若有问题,请回复,共同探讨) [mw_shl_code=c,true]#include <STC12C5A60S2.H>
unsigned char buf[6]=":I get";//写一个字符串留用
unsigned char receive=0,flag=0;//声明一个字符接收变量,声明一个标志
void UartInit(void) //9600bps@32MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF7; //设定定时初值
TH1 = 0xF7; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void main()
{
int loop;//声明一个变量,用以进行循环
UartInit();//设置波特率
EA=1;//开总中断
ES=1;//开串口中断
while(1)
{
if(flag==1)//当标志为1时,说明有串口中断发生
{
flag=0;
ES=0;//关闭串口中断,以防当数据发送时,有其他串口数据扰乱发送
SBUF=receive;//发送字符
while(!TI);//当发送完成时,TI=1,此时循环结束,开始进行下一步执行
TI=0;ES=1;//清除中断位,并打开串口中断
for(loop=0;loop<6;loop++)//循环将字符串发送
{
ES=0;
SBUF=buf[loop];//将字符串中的单个字符给发送区
while(!TI);
TI=0;ES=1;
}
}
}
}
void ser() interrupt 4//串口中断程序,中断标志为4
{
RI=0;//将接收标志清零
receive=SBUF;//将接收缓冲区的值赋给变量
flag=1;//将标志置1
}[/mw_shl_code]
注意:1.接收缓冲与发送缓冲都为SBUF,但其寄存器地址不一样,若为“=SBUF”则是接收缓冲,反之则为发送缓冲
2.当数据发送或接收到停止位时,对应TI/RI才会被置1,所以可以用while(!TI)检测此帧数据是否发送完毕,以防止当SBUF中数据还未发送完毕时新的数据将原来的数据覆盖。
3.串口中断时无论RI还是TI被置1,均会产生中断,所以在主函数发送时要将ES关闭,防止进入中断,扰乱串口数据 (PS:当给SBUF赋值时,就相当于发送数据)
4.因单片机构造问题,SBUF只有一字节,也就是8位空间,所以当你使用上述程序发送字符串时只会接收到最后一位。 (有办法解决,前提是要对串口时序有大致了解,感兴趣可以百度或和我探讨)
|