oldbeginner 发表于 2013-11-23 09:11:25

开源PLC学习笔记05续(再从51开始 UART模块测试)——2013_11_23

本帖最后由 oldbeginner 于 2013-11-23 13:17 编辑

本节笔记是为了将uart模块化,同时进行第3次理解。
在第3次理解中,也搜了很多资料,发现开源PLC上的uart函数基本上是我找到的资料当中最难的。另外本论坛上另外一个类似帖子《分享一个自己写的串口程序,中断+循环队列接收连续字符串》http://www.amobbs.com/thread-5521671-1-1.html,按照我的理解该函数在字符串接收上应该是不完备的,同时使用结构体变量不太适合51单片机。

因为难度大,所以分解成字符串接收和字符串发送(即循环队列发送)两部分。

开源PLC在字符串接收上,同时使用了串口中断和定时器0中断,为何要使用定时器0中断?可以看这篇文章(感觉也比较难),这篇文章解释了为什么要作很多的工作。http://blog.csdn.net/gszhy/article/details/8594433

为了让理解门槛降低,首先先看一个简单的实例,http://home.eeworld.com.cn/my/space-uid-139222-blogid-28926.html


[

b]基于proteus的51单片机仿真实例六十二、串口发送和接收字符串实例

为了实现单片机通过终端接收字符的功能,我们需要在电路中再加入一路虚拟终端,新加入的这一路虚拟终端连接到单片机的串行接收端口RXD上,在仿真运行后,在该终端输入字符,然后将字符发送到单片机的串行接收端,

我将要在上面的例子上进行更改,把开源PLC中接收字符串的函数和变量移植到该程序上,通过串口助手输入字符串,然后单片机把接收到的字符串在发送给串口助手。

理解接收字符串的突破口是变量:UartDataReadyFlay,为了简洁,缩写为UDRFlag。

和UDRFlag配合的还有UartReceiveCount,UartWaitForCounter,它们的关系如下图。







把开源PLC中的字符串接收函数移植到上面的例子中,


代码如下,如何理解下面的代码可以参考上面提到过的文章http://blog.csdn.net/gszhy/article/details/8594433。


// 实例50:利用虚拟中断实现串口数据的发送和接收
//
#include <reg51.h>          //包含头文件

volatile unsigned char UartReceiveCounter=0;    //接收计数器
volatile unsigned char UartRxTimerStartFlag=0;    //接收超时计数器启动标志
volatile unsigned char UartWaitForCounter=0;    //接收超时计数器
volatile unsigned char UDRFlag=0;                //接收完成标志

#define InLEN 30
unsigned char Buffer={0};
void FX1NProcessing(void);

//毫秒级延时函数
void delay(unsigned int x)
{
unsigned char i;
while(x--)
{
   for(i = 0;i < 120;i++);
}
}
//字符发送函数
void putchar(unsigned char data1)
{
SBUF = data1;               //将待发送的字符送入发送缓冲器
while(TI == 0);            //等待发送完成
TI = 0;                     //发送中断标志请0
}
//字符串发送函数
void putstring(unsigned char *dat)
{
while(*dat != '\0')         //判断字符串是否发送完毕
{
   putchar(*dat);      //发送单个字符
dat++;               //字符地址加1,指向先下一个字符
delay(5);
}
}
//主函数
void main(void)
{
   /*****************************************/
SCON = 0x50;         //串口方式1 ,允许接收
TMOD = 0x20;         //T1工作于方式2
PCON = 0x00;         //波特率不倍增
TL1 = 0xfd;            //波特率设置
TH1 = 0xfd;            //
EA = 1;                  //开总中断
ES = 1;               //开串口接收中断
//TI = 0;
TR1 = 1;             //定时器开启
/********************************************/
//***定时器Timer0初始化***
    TMOD&=0xF0;            //将TMOD的低4位定时器0控制部分清零
    TMOD|=0x01;            //设置定时器0为方式1
    TL0=0x47;            //设置定时器0初值低8位
    TH0=0xFF;            //设置定时器0初值高8位
    TR0=1;               //启动定时器0
    ET0=1;               //Timer0中断允许
//**********************

/*****************************************/
delay(200);
putstring("Receiving from 8051...\r\n");         //串口向终端发送字符串,结尾处回车换行
putstring("----------------------\r\n");
delay(50);
while(1)
{
   FX1NProcessing();
   
}
}

//
void Uart(void) interrupt 4 using 2
{
    if(RI)
    {
      RI = 0;
      
      Buffer=SBUF&0x7f;
      UartRxTimerStartFlag=1;    // 启动超时计数器
          UartWaitForCounter=0;      // 清超时计数器    // 10ms
   
      if (UartReceiveCounter>=InLEN)
      {
            UDRFlag=1;
            REN=0;
      }
    }

    if(TI) TI=0;
}


void Timer0(void) interrupt 1
{                        //定时100微秒
    TL0=0x47;            //重新给TL0赋初值
    TH0=0xFF;            //重新给TH0赋初值

    //***此处用户自行添加定时器T0中断处理程序***

    //******************************************
      if(UartRxTimerStartFlag)            // 接收超时
    { if(UartWaitForCounter>=10)
      { UartWaitForCounter=0;
          UartRxTimerStartFlag=0;
          UDRFlag=1;
          REN=0;                        // 禁止UART接收器
      }
      else UartWaitForCounter++;
    }
}

void FX1NProcessing(void)
{
   if(UDRFlag)
      {
            UDRFlag=0;
            putstring(Buffer);
            UartReceiveCounter=0;
            
            REN=1;
      }
}




oldbeginner 发表于 2013-11-23 10:32:02

本帖最后由 oldbeginner 于 2013-11-23 10:33 编辑

在测试了接收字符串后,再测试发送字符串。本来发送字符串是比接收简单的,但是因为这里使用了循环队列,所以也变得有些复杂了。
首先再复习一下概念,

采用顺序存储结构的队列称为顺序队列。
在顺序队列中,需要设置两个指针:
          front 指示队头元素的位置(代替程序中的outlast);
          rear 指示队尾元素的位置(代替程序中的putlast)。
初始化建空队列时,令front=rear=0。
每当插入新的队尾元素时,rear增1,每当删除队头元素时,front增1。
因此,在非空队列中,front始终指向队头元素,rear始终指向队尾元素的下一个位置

            和栈类似,队列中亦有上溢和下溢现象。此外,顺序队列中还存在“假上溢”现象。因为在入队和出队的操作中,头尾指针只增加不减小,致使被删除元素的空间永远无法重新利用。因此,尽管队列中实际的元素个数远远小于向量空间的规模,但也可能由于尾指针巳超出向量空间的上界而不能做入队操作。该现象称为假上溢。

为充分利用向量空间。克服上述假上溢现象的方法是将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量,存储在其中的队列称为循环队列(Circular Queue)。在循环队列中进行出队、入队操作时,头尾指针仍要加1,朝前移动。只不过当头尾指针指向向量上界(QueueSize-1)时,其加1操作的结果是指向向量的下界0。




            显然,因为循环队列元素的空间可以被利用,除非向量空间真的被队列元素全部占用,否则不会上溢。因此,除一些简单的应用外,真正实用的顺序队列是循环队列。
         如图所示:由于入队时尾指针向前追赶头指针,出队时头指针向前追赶尾指针,故队空和队满时头尾指针均相等。因此,我们无法通过front=rear来判断队列“空”还是“满”。
    解决此问题的方法至少有三种:
1。另设一个布尔变量(标志位)以匹别队列的空和满;
2。少用一个元素的空间,约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空);
3。是使用一个计数器记录队列中元素的总数(实际上是队列长度)。






oldbeginner 发表于 2013-11-24 08:04:18

oldbeginner 发表于 2013-11-23 10:32
在测试了接收字符串后,再测试发送字符串。本来发送字符串是比接收简单的,但是因为这里使用了循环队列,所 ...

循环队列写得把自己都绕晕了,对字符串发送代码有2个不理解:
1、为什么发送字符串使用循环队列;
2、为什么在在UartSendChar对SBUF赋值,在中断函数里也对SBUF赋值。

看了《数据结构》后,我觉得开源PLC字符串发送写得有问题,不过简单测试了一下,居然是能够工作的。

借鉴http://home.eeworld.com.cn/my/space-uid-139222-blogid-28925.html的例子。






虽然源代码能够工作,我还是利用循环队列定义中的入队出对修改了一下(参考黄国瑜版《数据结构》c语言),肯定有问题,入队和出对都放在同一个函数里。
#define OutLEN 30
volatile unsigned char UartSendBuffer;            //发送缓冲
volatile unsigned char front=0;            //最后由中断传输出去的字节位置,总是指向空的
volatile unsigned char rear=0;            //最后放入发送缓冲区的字节位置 volatile unsigned char UartSendBufferemptyFlag=1;      //缓冲区数据发完标志   发完=1
volatile unsigned char UartSendBufferHaveDataFlag=0;    //发送缓冲区非空标志   有=1

把指针更改为数组序号,原因是为了 +1求余就可以循环。

void UartSendchar(unsigned char ucdata)
{
    rear=(rear+1)%OutLEN;   
    while(front==rear)//队列已满
    {
      front=(front+1)% OutLEN;
      SBUF=UartSendBuffer;   
    }
   
    UartSendBuffer=ucdata;                        // 放字节进入缓冲区
   
    UartSendBufferHaveDataFlag=1;   
                                    // 缓冲区开始为空,置为有,启动发送
    if (UartSendBufferHaveDataFlag)
    {
      front=(front+1)% OutLEN;
      SBUF=UartSendBuffer;                                                 // 未发送完继续发送
                                 // 最后传出去的字节位置加1
          if (front==rear)
            UartSendBufferHaveDataFlag=0;            // 数据发送完置发送缓冲区空标志
    }
}

void Uart(void) interrupt 4 using 2
{
    if(TI)
    {
      TI=0;
    }
    if(RI)
    {
      RI = 0;
    }
}
这个一样可以测试通过,感觉发送字符就不需要缓冲。为何多此一举?暂时先理解到这里,因为这个模块已经可以工作了。

oldbeginner 发表于 2013-11-24 09:09:26

oldbeginner 发表于 2013-11-24 08:04
循环队列写得把自己都绕晕了,对字符串发送代码有2个不理解:
1、为什么发送字符串使用循环队列;
2、为 ...

将接收字符串和发送字符串合并,并修正了一个bug,就是
                Buffer=SBUF&0x7f;
                UartRxTimerStartFlag=1;        // 启动超时计数器
                UartWaitForCounter=0;                // 清超时计数器        // 10ms
                Buffer='\0';

这样发送字符串时就可以确定尾巴了。

ruzi 发表于 2017-4-20 13:03:03

学习了!谢谢1

ruzi 发表于 2017-4-20 13:03:54

学习了!谢谢!
页: [1]
查看完整版本: 开源PLC学习笔记05续(再从51开始 UART模块测试)——2013_11_23