这里我们主要说的是波特率和定时器2的应用.
一般来说,我们串口通讯用到的都是异步串行通讯,工作的方式为方式1.
方式1即为发送一个完整的信号为10个bit.起始信号为低电平,终止信号为高电平,串口通讯的两根线在平常时候都是处于高电平状态,当一旦有数据要进行转发的时候,电平拉低,通讯芯片马上对信号进行监听.这样子就能正常收发数据了.
一般来说,我们都是采用定时器1的模式2(自动重装模式)来作为波特率发生器的,同理,定时器1的中断也就被我们遗弃了,因为为了波特率产生的时候不会受到干扰(如果定时器1有中断函数,那么处理中断函数会关闭定时器1中断,这时候波特率发生器就处于关闭状态了).根据STC给我们的文档,定时器1所具有的功能是比定时器2更强大的,所以,我们更倾向于把定时器1作为一个正常的中断定时器使用,而通过定时器是用说明也可以了解,定时器2的三种
用途:
1.捕获模式,简单点说就是检测外部引脚跳变时间,在整个负跳变时期记录下所在数值,然后申请中断,我们人工读取数值,得到波形宽度.
2.自动重装模式.跟定时器01的方式2一样,不过这时候他的定时器范围可以达到2^16,并且这里是用的是硬件重载,所以不存在延迟效果,如果我们要使用在对精度有严格要求,并且苦恼于定时器01只能重装到2^8=256,那这个定时器确实就是很强大的选择.
3.波特率发生器,这里跟定时器1的波特率发生器是一样的,同理,他使用的也是自动重装模式,不过这里是使用的是16位的自动重装,这可能也是他其中的优点之一吧,波特率变动范围很广.
关于波特率:波特率就是用来定义串口通讯时候每秒传送的数据量,用bps表示,像我们定义的波特率为600,即每秒发送600个二进制位,而我们每个字符占用十个二进制位,所以我们一秒一共可以发送6个二进制位.
关于波特率选定:一般来说,我们是选用低一点的波特率来进行通讯,因为高的波特率如果在12MHZ的晶振工作模式下,会产生很大的误差,如果时间长了,会导致数据没有办法对上时钟而导致通讯乱码.并且高的波特率因为传送的数据量大,在线的距离拉长的时候,容易产生很严重的干扰,所以还是优先选择低波特率进行通讯.
关于波特率和定时器2:定时器2作为波特率发生器的时候,是通过状态位的设定,来夺取定时器1的波特率发生器的作用,所以这时候我们可以通过设定,让定时器只是作为我们程序单纯中断的用途,然后定时器2作为专门的波特率发生器,这时候我们不用设定定时器2的中断,而且当初始化之后,就不用去管定时器2的中断了,我们要注意的只是串口中断,即通讯电平拉低那一瞬间所产生的中断请求.同理,串口中断跟定时器2中断是完全不同的,这里我们不需要使用定时器2中断,定时器2溢出的时候,会执行两种功能,一就是产生定时器2中断,另外一个是会向波特率发生器发送溢出信号,这时候发生器接收到这个信号的时候,自动进行时钟校对,产生一个bps的时钟信号,所以波特率发生器是这么来的,定时器2说简单点就是用来决定时钟信号的波形宽度的.溢出率越高,那么同一个时间段里面产生的时钟信号也就越密集,发送数据同步了时钟信号,同理也就能够发送更多的数据.
通过上图可得,在600bps的情况下,T2的自动重装值为FD8F.
通过上图的时序图,我们也可以清晰的得到,在上位机和下位机,下位机的时钟频率是由程序规定的,而上位机的时钟频率是由人工设定的,我们只要知道,两个时钟频率是要完全一致的,不然收发数据没有办法正常进行.
(发送)当send信号拉低的时候,txd也拉低表示要进行数据通讯,这时候发送的数据在每个时钟信号高电平的时候进行变换,在时钟信号电平的时候稳定,这样子发送出去的数据就会严格有固定的电平波长和高电平波长,当上位机进行接收到时候,当他检测到接收端的电平拉低的时候也对时钟信号进行同步,把时钟信号的高电平同步到数据接收端第一次电平拉低时刻,然后在时钟信号的低电平期间进行数据采集,比如第第二个时钟信号低电平表示的是这时候数据端的电平状态表示的是数据的第三个bit正在等待捕获,所以,如果时钟必须严格一致就是这个原因.当整个过程完成的时候,TI置位,表示接收完成,所以我们可以一直监听TI的情况,同理,置位情况下要记得给他人工清零.
(接收)接收会有点不同,虽然他也是在时钟同步的情况下进行,但是他是在shift为高电平期间捕获讯号,(上位机可能也是这种情况,暂时没有找到更明确的资料),每个bit的变换时间很短,稳定时间很长,一般来说他是在时钟信号低电平中间进行信号捕获的.当整个过程完成的时候,RI置位,表示接收完成,所以我们可以一直监听RI的情况,同理,置位情况下要记得给他人工清零.
下面我们通过一小段程序来验证下上面的理论:
1 #include <reg52.h>
2
3 unsigned char flag,dat;
4
5 void Init();
6 void SendData();
7 void main()
8 {
9 //初始化
10 //死循环
11 Init();
12 while (1)
13 {
14 if (flag==1)
15 {
16 SendData();
17 }
18 }
19 }
20
21 void Init()
22 {
23 //全部设定完再开启中断
24
25 //定时器2设定
26 RCAP2L=0x8F;//载入初值
27 RCAP2H=0xFD;
28 T2CON=0x34;//定时器2设定
29 //串口设定SCON
30 SCON=0x50;//SM01=01;REN=1
31 //开启定时器2,串口中断,es,串口中断,et2,定时器2
32 RI=0x00;
33 TI=0x00;
34 IE=0x90;
35 }
36
37 void Uart_int() interrupt 4//外部触发
38 {
39 //关闭中断
40 //定时器是用来产生波特率的,所以不用关闭
41 if (RI=1)//这时候接收到数据
42 {
43 RI=0;
44 dat=SBUF;
45 flag=1;
46 }
47 }
48
49 void SendData()
50 {
51 ES=0;//关闭串口中断
52 SBUF=dat;
53 while (!TI);
54 TI=0;
55 ES=1;//开启串口中断
56 flag=0;
57 }
关于初始化:在初始化中,我们要做的三件事情就是配置定时器的中断,第二开启或关闭定时器,第三,进行初始值设定
可以看如下图:配置如下,开启总中断EA,开启ES串口中断,0x90
串口中断模式为SM01=01,REn=1,允许串行接收,没开启这个是没有办法进行上位机发送过来的数据进行接收的.
同理,一开始我们要对RI和TI进行初始化,因为我一开始没初始化好像老出错,还是清零下比较安全.
定时器2设定,RCAP为定时器的16位自动重载值.T2CON=0x34配置如下:
TF2:溢出清零
EXF2:外部中断清零
RCLK,TCLK:夺取定时器1的波特率发生器权利.
EXEN2:外部中断清零
TR2:置位定时器启动器
CT2:选择内部时钟发生器
CP/RL2:随意.我们清零
void Init()
{
//全部设定完再开启中断
//定时器2设定
RCAP2L=0x8F;//载入初值
RCAP2H=0xFD;
T2CON=0x34;//定时器2设定
//串口设定SCON
SCON=0x50;//SM01=01;REN=1
//开启定时器2,串口中断,es,串口中断,et2,定时器2
RI=0x00;
TI=0x00;
IE=0x90;
}
这时候我们应该设定中断4,即上位机发送过来的中断处理:
1 void Uart_int() interrupt 4//外部触发
2 {
3 //定时器是用来产生波特率的,所以不用关闭
4 if (RI=1)//这时候接收到数据
5 {
6 RI=0;
7 dat=SBUF;
8 flag=1;
9 }
10 }
处理程序:
1 void SendData()
2 {
3 ES=0;//关闭串口中断
4 SBUF=dat;
5 while (!TI);
6 TI=0;
7 ES=1;//开启串口中断
8 flag=0;
9 }
处理程序比较简单,就不过多赘述了.
最后通过串口通讯助手看下我们发送的数据时候能够接收并且返回.
发送流程图如下:
小结:个人感觉,我是写程序写到一半才发现我的定时器1已经是用了,所以没办法,换取其他方法,定时器2确实很强大,主要是它是比较独立的定时器,在程序移植的时候非常方便.因为只要修改总中断就可以了,方便许多.所以,如果有串口通讯需要的,定时器2是不二选择.
PS:下期将为大家带来这个的两个下位机通讯的一些深入研究.以这个为基础.