登录  | 立即注册

游客您好!登录后享受更多精彩

扫一扫,访问微社区

QQ登录

只需一步,快速开始

开启左侧

[寒假笔记] 第四章:简单地串口应用

[复制链接]
发表于 2017-2-4 16:06:36 | 显示全部楼层 |阅读模式
学习笔记
学习科目: C51
学习安排: 详见:http://bbs.hpuedd.com/forum.php?mod=viewthread&tid=837&fromuid=385
开始时间: 2017-01-31
结束时间: 2017-01-31
本帖最后由 夜色星空 于 2017-2-1 21:23 编辑
单片机要与计算机等设备进行交互

那么就会产生通讯

既然有交流,那么就一定有语法和时序的要求

对与单片机的串行口来说

你如果想通过这个接口来与单片机进行通讯的话

那么就必须要遵循它的规则

首先,先来看看硬件

以黑金V2.5与计算机通讯为例

因为我们通常使用的是USB口,它与串口的协议是不一样的

所以我们要将USB转为串口

此时就用到了CH340芯片与其驱动程序

这两样东西可以将USB口虚拟为一个串口

用以与单片机进行通讯(有兴趣可自行百度,在此不再赘述)

(至于为什么TX接RX,一个是发送,一个是接收,发送给接收,这样接才能进行通讯,一般用下载器时会遇到这种问题)

1-1.PNG

如图

串口在MCU上的位置

(PS:本帖主要说一下串口简单应用与常见问题,至于底层的收发时序,发展演化,各种转换等问题在本帖中不予探讨,有兴趣可自行百度)

然后再来看MCU中的串口寄存器

1-1.PNG


(STC12C5A60S2的芯片有两个串口,然而我们现阶段只使用一个,所以有些寄存器并不会提及)


首先,串行口和定时器一样有4种模式


如图


1-2.PNG


串口发送一帧,只有8个有效字节,而方式23的九位数据,有一位是用来进行校验的


也就是说,一帧数据由


起始位,数据位,(校验位),停止位


构成


然后就是同步与UART(异步)的区别


简单讲,同步就是两者步调一致


帧与帧之间的间隔时间是确定的


而异步收发,帧与帧之间的间隔是不确定的


当使用UART传送时,你可以发一帧后等待很长时间再去发第二帧


那么这就有一个问题


MCU不能一直在这里等待数据传送,这样将会大大浪费CPU资源


所以,我们将其变成中断形式,当有数据发送或接收时


CPU就跳转到那里进行执行


这样大大提高了CPU的利用率


既然有中断,那么就有中断标志去通知CPU


既然有数据传输,就应该有暂时存储数据的地方

既然串口只有两根线而要传输8+1+1(+1)位数据,那么一定有传输的先后顺序

既然要想让对方接收,就应该让对方知道什么时候的数据是我想要传输的

综上

先说第一个问题

1-3.PNG

此寄存器中的TI与RI分别为发送中断与接收中断

1-4.PNG

如图,一般在这里会有的问题就是没有软件清零

它不像定时器一样可以进行机器清零,它只能进行软件清零,也就是说写程序时需要在代码中进行清零

再说第二个问题

过来的数据存储在哪

如图

1-5.PNG

也就是说,会在CPU还没来得及处理前先将数据保存在SBUF寄存器中

最后就是传输的时序

这就引进了一个波特率(bps)的概念

举个例子

计算机每秒发送一位数据

单片机每秒接收一位数据

这样,计算机发送的数据单片机都能够很好地收到

假如计算机每两秒发送一个数据,而单片机每一秒接收一位数据

那么当单片机接收第二个数据时,其实还是计算机的第一个数据

这样就发生了错误

为避免此类情况发生,我们引进了波特率的概念

假设波特率为9600

则相当于规定,每秒发送(或接收)9600位

也就是说计算机1/9600秒发送(接收)一位

单片机1/9600接收(发送)一位

这样就可以保证接收到的数据是正确的

然而如何去用程序实现上述功能?

我们只需要更改UART相关寄存器的值就可以了

首先选择串口模式,它规定了一帧数据有多少位,波特率如何计算

一般我们使用方式1

也就是8位数据,

波特率为定时器1的溢出率

1-6.PNG

如图,计算公式SYSclk为系统频率

在此说一下89系列与12系列单片机的区别

如图
1-7.PNG

现在大多数教材用的都是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位空间,所以当你使用上述程序发送字符串时只会接收到最后一位。
(有办法解决,前提是要对串口时序有大致了解,感兴趣可以百度或和我探讨)





该会员没有填写今日想说内容.
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表