电子DIY小家 发表于 2024-5-11 14:28:28

PWM硬件移相测试卡教程(五)串口空闲中断@8H2K12U系列

PWM硬件移相测试卡教程(五)串口空闲中断 @8H2K12U系列
PWM硬件移相测试卡教程(五)串口空闲中断 @8H2K32U系列
序言
    当我们在做串口接收的程序的时候,往往需要当接收到一串数据的时候需要及时的响应。但是响应的第一步就是我们得先判断这一包数据是否已经接收完成,必须要接收完成了才能进行下一步动作;
    案例一:常见的AT指令集,带固定帧尾的数据。假设我们用单片机模拟一个ESP的模块,是不是当串口发来一个"AT\r\n"的时候需要回应一个"OK\r\n";这个可以串口接收函数里判断结尾是不是"\r\n",监测到这个才是结尾。
    案例二:串口接收定长输出,每一包的数据都是固定的字节,接受到这个长度的数据就是这一包结束了。
    案例三:每一帧的数据里包含数据长度,比如每一包数据的第一个字节是长度,长度多长后面就跟几个字节的数据。
    上面的几种常见案例虽然都能实现数据的完整接收,但是在实际使用的时候数据包里有没有长度,有没有固定帧尾都不是我们做从机的时候能决定的,那我们怎么样才能用最好的办法来判断一个数据包也没有接收完成呢!答案是有的————串口超时中断。

一、串口超时中断

   
    首先有个概念我们需要先清楚,串口发送的时候数据是这样一个字节一个字节连续发送的。当某一包数据发完之后,电平长时间没有变化的,我们就可以默认为这一包数据结束了,这个我们就把他叫做超时中断。用这个办法我们就可以轻松的识别数据包有没有结束了。很明显这个超时中断其实是接收不定长数据最好用的办法,没有之一。当然这个超时的时间坑定是需要我们自己去定义的。
    当然,其实在没有这个功能之前,我们其实也能通过定时器模拟超时中断,例如代码包的如下代码




    可以看到上面的代码,每接收到一个数据,RX1_TimeOut这个变量就是赋值为5,在主函数里每隔1ms就会减一,如果串口接收中断一直没有进去,那么这个变量就会在5ms左右减为0,就表示当前的一包数据结束。虽然这个办法也能用,但是他需要浪费我们一个宝贵的定时器。其次他的定时精度也不高,所以这时候如果如果有硬件的超时功能绝对是上上选。(需要注意的是,截止至发贴之前,STC8H系列目前只有如下的两个型号支持串口超时中断,后续有支持的型号请以最新版手册为主)



二、寄存器介绍


这个寄存器其实在我们接收的时候再打开即可,ENTO不用说坑定需要使能,ENTOI是中断,可以及时响应。SCALE的话这里为了时钟相对准,我们选择系统时钟即可。这里需要注意如果选择1us时钟的话务必要设置IAP_TPS寄存器!




这里三个寄存器共同组成了一个计时器,最终的计时时间 = 1/系统时钟(单位hz)*计时器数值,假设计时器数值为44000(0XABE0),那么UR1TOTL = 0XE0;UR1TOTH = 0XAB;UR1TOTE = 0X00;需要注意的是这里一定要从低位开始写,且这三个寄存器一定都要写,哪怕这个寄存器的数值是0也必须要写一次。需要注意的是这个计时器的数值不能为0(也就是三个寄存器不能都写入0)




这个寄存器就很简单了,只有一个位,触发的时候这个位会置1,我们手动清0即可。需要注意的是这个和串口发送和接收中断共用同一个中断向量号!串口1的中断号是4,那么串口1的超时中断号也是4.


三、程序编写
1.串口初始化
void Uart1_Init(void)                                    //115200bps@22.1184MHz
{                           
      SCON = 0x50;                                          //8位数据,可变波特率
      AUXR |= 0x40;                                          //定时器时钟1T模式
      AUXR &= 0xFE;                                          //串口1选择定时器1为波特率发生器
      TMOD &= 0x0F;                                          //设置定时器模式
      TL1 = 0xD0;                                                    //设置定时初始值
      TH1 = 0xFF;                                                    //设置定时初始值
      ET1 = 0;                                                    //禁止定时器中断
      TR1 = 1;                                                    //定时器1开始计时
      ES = 1;                                                            //使能串口1中断
   
//    IAP_TPS = MAIN_Fosc / 1000000;                //超时时钟选择1us时钟时需要设置
    UR1TOCR = 0x00;                                 //关闭超时中断
    UR1TOTL = 0xE0;                                 //0xABE0 = 44000,超时中断时间为 1/22118400*44000 us
    UR1TOTH = 0xAB;                                 //注意必须先写L,在写H寄存器,最后写E寄存器,
    UR1TOTE = 0x00;                                 //不管用没用到三个寄存器都得写入一次数值
   
    B_TX1_Busy = 0;                                 //清空忙碌标志位   
    RX1_Cnt = 0;                                    //计数清0
}可以看到前几行的代码和以往的串口代码没有任何区别,只是多了四行,首先UR1TOCR寄存器我们说了在接受到一个数据的时候在开启超时中断即可。然后再这里我们例子也直接用上面的44000作为计数值即可,这里我们使用的主频是22.1184M,所以这里的超时时间为1/22118400*44000 ≈ 1989 us,这里切记注意寄存器必须从低八位写到高八位,三个寄存器的数值都要写入。

2.串口超时中断
void Uart1_Isr(void) interrupt 4
{
      if (TI)                                                            //检测串口1发送中断
      {
                TI = 0;                                                    //清除串口1发送中断请求位
      B_TX1_Busy = 0;
      }
      if (RI)                                                            //检测串口1接收中断
      {
                RI = 0;                                                    //清除串口1接收中断请求位
      RX1_Buffer = SBUF;               //接收变量
      if(++RX1_Cnt >= UART1_BUF_LENGTH)   
            RX1_Cnt = 0;
      UR1TOCR = 0xe0;                           //开启超时中断,使用系统时钟
      }
   
    if(UR1TOSR & 0x01)                              //串口超时中断
    {
      B_RX1_OK = 1;                               //接收完成标志位
      UR1TOSR = 0x00;                           //清除超时标志位
      UR1TOCR = 0x00;                           //关闭超时中断
    }   
}
除了以前的RI和TI标志位之外,这里用了UR1TOSR寄存器里的TOIF位,也就是最低位,这一位置位之后手动清除这个寄存器,并关闭接收超时中断配置寄存器即可。这里还加了个标志位表示接受完成,然后我们就可以在主程序里使用了。

      if( B_RX1_OK )                              //如果接收完成
      {
            B_RX1_OK = 0;
            Uart1_Send(RX1_Buffer,RX1_Cnt);         //发送接收到的数据
            RX1_Cnt =0;                           //清空接收计数
      }在主循环里编写如上函数,当接受到数据,就通过串口发出去。


测试的效果如上,不管发什么。单片机都会完整的发回来。




发送时的波形如上。黄色是电脑发给单片机的波形,蓝色是单片机返回的波形。发送和接收的时间差为1.99ms,和我们的推断基本符合。这样我们就可以轻松的根据自己的项目需要填写实际得串口超时时间了,用它就可以轻松的实现MODBUS RTU的3.5个字符周期的判断了哈哈。
末尾附上代码,有需要的可以自行测试!



最后有个插曲
//-----------------官方的头文件暂时没包含,手动添加(后续头文件包含了可删除这部分)-----------------
#define   UR1TOCR               (*(unsigned char volatile xdata *)0xfd70)      
#define   UR1TOSR               (*(unsigned char volatile xdata *)0xfd71)
#define   UR1TOTH               (*(unsigned char volatile xdata *)0xfd72)
#define   UR1TOTL               (*(unsigned char volatile xdata *)0xfd73)
#define    UR1TOTE               (*(unsigned char volatile xdata *)0xfd88)由于截止到目前发帖为止。官方的ISP软件里的头文件没有包含超时中断的寄存器,需要手动添加进自己的代码里!


电子DIY小家 发表于 2024-5-11 14:33:30

下一章考虑用STC8H2K32U做一个modbus rtu的,毕竟他还有硬件crc16,各位看官想要主站还是从站,下面留言。


国学芯用 发表于 2024-5-11 14:53:33

优秀的冲老师{:5_332:}

Ingram 发表于 2024-5-11 16:36:09

{:4_174:}

soma 发表于 2024-5-28 16:36:18

收藏学习一下
页: [1]
查看完整版本: PWM硬件移相测试卡教程(五)串口空闲中断@8H2K12U系列