沁恒MCU串口使用指南

转载注明出处。

沁恒MCU串口使用指南:

适用于WCH的32位MCU和CH559/558单片机
只描述TTL电平的TX+RX形式的常规串口,流控,RS232、RS485不在文章涉及范围之内。
大部分8位机按照标准的51单片机串口操作,建议自行搜索。


分为两部分,串口接收和串口发送:

一、串口发送:

首先摘抄CH573手册描述(请先仔细阅读一下,说不定就看明白了)
在这里插入图片描述

  发送的时候我们肯定希望发送速率越快越好,我们就要充分利用“发送空中断”。因为CPU速度肯定比串口发送速度快,所以总是会存在CPU等待串口发送完成这一情况。

  打个比方,有一辆可以坐8个乘客的小巴士,从公司带人去机场,怎么样最快呢,肯定是每次都坐满发车最快~。那怎么知道司机从机场回来,可以安排下一辆车的人了呢?两种方式:
  1、司机给负责人打电话,喊人下来
  2、负责人蹲公司门口看司机回来了没有,当然不一定要持续盯着,可以每隔几秒抬头看一下

两种通知方式,相比下肯定是第一种省力一点的,所以对应到串口上:
  1、FIFO功能毫无疑问要打开(换成8座小巴士)
  2、每次坐满发车(CPU连续填充8个字节)
  3、通知方式可选中断(打电话通知)或者查询(蹲门口盯着),这个取决于对于串口连续发送的效率要求,中断方式效率会高,但是会增加系统复杂程度(中断优先级、嵌套等问题),查询效率没那么高,但是系统会简洁一点,同时查一下中断标志的开销不大(抬头看一眼,不怎么影响低头刷手机哈哈哈)
  4、通知车回来之后,再安排8个人一起上车,如此循环

代码上上述功能的具体实现(伪代码):
1、

R8_UART1_FCR |= (1<<1);

在这里插入图片描述
2、

void UART1_SendString( UINT8 *buf )
{
    UINT8 i;
    for(i=0;i<8;i++){
        R8_UART1_THR = *buf++;	
    }       
}

3+4、
查询

void UART1_SendString( PUINT8 buf )
{
    UINT8 i;
    for(i=0;i<8;i++){
        R8_UART1_THR = *buf++;
    }       
}

void main(){

R8_UART1_IER |= (1<<1);         //使能串口空中断
while(1){
      {
          //cpu干别的事
      }
      if(R8_UART1_IIR & (1<<1))   //THR寄存器空
          UART1_SendString( buf );
}
}

中断

void main(){
    R8_UART1_IER |= (1<<1);         //使能串口空中断
    PFIC_EnableIRQ( UART1_IRQn );   //打开串口的中断功能
    while( 1 );
}

__attribute__((interrupt("WCH-Interrupt-fast")))
__attribute__((section(".highcode")))
void UART1_IRQHandler( void )
{
  if(R8_UART1_IIR & (1<<1))   //THR寄存器空
        UART1_SendString( buf );
}


二、串口接收

串口的接收整体会麻烦很多,因为是被动的。先摘抄手册描述:(同样先仔细阅读一遍)
在这里插入图片描述
串口接收首先是个被动的过程,所以我们尽量的用中断来处理。先看一下和接收相关的中断类型:
在这里插入图片描述
  ①接收数据可用:接受的字节数达到FIFO的触发点
  ②接收数据超时:超过4个字节时间未收到下一数据

  这两个中断足以应付绝大部分的场景了。因为串口有8字节的FIFO,进来的数据可以稍微缓冲一下,不至于一有数据就要处理,这会变得挺麻烦的,这点相比标准51单片机来说进步很多。

①接收数据可用中断:

  因为数据有可能连续不断的一直发送过来,我们要及时的读取,但是又不能太及时,不然可能影响CPU处理别的东西。
  打个比方,有个消毒房间,会从天花板上喷消毒水对工作人员进行消毒,这个消毒房间最多只能站8个人,每隔一分钟都会有人要进入消毒房间,单人单次消毒不到1分钟就可以完成,但是每多一个人多喷洒一小会的消毒水,防止出现死角没有喷洒到药水。所以我们有两种模式进行消毒:
  ①每来一个人就进去消毒,下一个人来的时候前一个人已经消毒结束了
  ②等多几个人,然后几个人一起进去消毒,下一个人来的时候几个人一起消毒结束了

  两种方式显然都是可以的,但是会有以下的问题,第一种模式会浪费掉更多的药水,每个人都是100%的药水喷洒量,第二种模式平均到每个人的药水用量会节省不少。但是模式二如果进入人数太多,可能消毒所需要的时间会超过1分钟,导致下一个来的人被堵在门口了。所以第二种模式需要根据规则限制人数,防止后来的人堵在门口。

  对应到串口接收上,串口每接收到一个数据就去处理当然可以,但是会导致CPU开销变大。启用“接收数据可用”中断能够节约CPU开销,但是可能存在CPU还没有读取FIFO中数据,下一个数据就到来的情况。所以可能当FIFO还没有满的时候,CPU就可以去将数据取出来,防止出现阻塞(对于串口来说实际上就是丢数据了),这时候产生“接收数据可用”中断的产生条件“FIFO触发点”就有价值了:
在这里插入图片描述
结合串口波特率、CPU频率配置合适的触发点,设置成4字节会比较保险且高效一点。
伪代码如下:

UINT8 flag=0;
UINT8 buf[4];
void main( ){
    R8_UART1_FCR = (R8_UART1_FCR & ~(3<<6))) | (2<<6); //设置4字节FIFO触发点
    R8_UART1_IER |= 1;  //使能接收数据可用中断
    while(1){
        if(flag){
          flag = 0;
          printf("1:%02x\n",buf[0]);
          printf("2:%02x\n",buf[1]);
          printf("3:%02x\n",buf[2]);
          printf("4:%02x\n",buf[3]);
        }

    }
}
__attribute__((interrupt("WCH-Interrupt-fast")))
__attribute__((section(".highcode")))
void UART1_IRQHandler( void )
{
    UINT8 i;
    if(R8_UART1_IIR & (1<<2)){  //中断源为数据接受可用
        for(i=0;i<4;i++){
            buf[i] = R8_UART1_RBR;    //直接按照触发点长度读取若干次RBR寄存器
        }
        flag = 1;
    }

}

②接收数据超时中断:

  简单说就是串口空闲了会产生中断,主观上可以认为是一帧结束,配合“数据接收可用”中断。通常数据都是有格式,会有固定长度。假设我们FIFO触发点设置4,数据一帧是14字节,一分钟来一帧数据。我们也有两种方式去接收数据:

  1、只判断“数据接收可用“中断,那么这一帧数据会触发3次中断,成功接收到12字节数据,最后剩下的2字节数据会保存在FIFO中,因为2字节不到触发点,所以CPU并不知道要去取数据。直到1分钟之后下一帧数据到来,新一帧的前2字节和前一帧的2字节凑成4字节,新一帧剩余的12字节正好能凑4的整数倍。这样数据倒是不会丢,但是出现了时效性的问题。

  2、开启“接收数据超时“中断,处理前面的那种情况,第一帧到来之后,先连续触发3次FIFO触发点,CPU取走12字节数据,剩余2字节在FIFO中储存,串口空闲4字节时间(当前波特率连续发送4字节所需要的时间),产生了超时中断,因为知道一帧数据就是14字节,当产生超时中断时,FIFO中必然有2字节数据,此时直接读取两次RBR寄存器就可以将一整帧接收完成,而不用傻傻等一分钟那么久了。伪代码如下:

UINT8 flag1=0;
UINT8 flag2=0;
UINT8 buf[4];
void main( ){
    R8_UART1_FCR = (R8_UART1_FCR & ~(3<<6))) | (2<<6); //设置4字节FIFO触发点
    R8_UART1_IER |= 1;  //使能接收数据可用中断
    while(1){
        if(flag1){
          flag1 = 0;
          printf("1:%02x\n",buf[0]);
          printf("2:%02x\n",buf[1]);
          printf("3:%02x\n",buf[2]);
          printf("4:%02x\n",buf[3]);
        }
        if(flag2){
          flag2 = 0;
          printf("1:%02x\n",buf[0]);
          printf("2:%02x\n",buf[1]);
        }
    }
}
__attribute__((interrupt("WCH-Interrupt-fast")))
__attribute__((section(".highcode")))
void UART1_IRQHandler( void )
{
    UINT8 i;
    switch(R8_UART1_IIR & (3<<2)){  
      case 0x04;    //中断源为数据接受可用
        for(i=0;i<4;i++){
            buf[i] = R8_UART1_RBR;    //直接按照触发点长度读取若干次RBR寄存器
        }
        flag1 = 1;
        break;
      case 0x0c:    //中断源为超时
        for(i=0;i<2;i++){
            buf[i] = R8_UART1_RBR;    //直接按照触发点长度读取若干次RBR寄存器
        }
        flag2 = 1;
        break;
    }
    if(R8_UART1_IIR & (3<<2))
}


总结:

  至此一个常规的16C550类串口就能够正常工作起来了,关于FIFO触发点的配置,超时中断的利用,还是要结合串口数据一帧的规律来调整。都看到这里了,其实手册讲的也很挺直白了、、、

`

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值