UART

介绍UART

最早的串行通讯设备可以追溯到电报机,它使用长度可变的脉冲信号进行数据传输。要说早期的芯片级UART,不得不提一下DEC,该公司的PDP系列计算机用上了第一个UART。当时的UART的线路占据了整个电路板,体积巨大!可以联想一下早期计算机的样子,如下图。


如今PC机上的串口早已被USB取代,对RS-232(也称标准串口)有需求的用户通常使用USB转串口线,这里常见的有CH340串口驱动程序。在UART通信中,两个UART直接通信。

发送端的UART将来自控制设备(如CPU)的并行数据转换为串行数据,以串行方式将其发送到接收端的UART,然后由接收端的UART将串行数据转换为并行数据以用于接收设备的正常处理。这里只需要两条线RX/TX即可在两个UART之间传输数据,如下图所示。


UART传输的数据被封装成数据包。每个数据包包含1个起始位,5~9个数据位(取决于UART的具体设置),一个可选的奇偶校验位以及1个或2个停止位,如下图所示。


起始位

UART数据传输线通常在不传输数据时保持在高电平。为了开始数据传输,发送端UART在一个时钟周期内将传输线从高电平拉低到低电平。当接收端UART检测到高电压到低电压转换时,它开始以波特率的频率读取数据位中的每一位数据。

数据

数据位包含正在传输的实际数据。如果使用奇偶校验位,则可以是5位,最多8位。如果不使用奇偶校验位,则数据帧的长度可以为9位。在大多数情况下,数据首先以低有效位发送。

校验位

在串口通信中一种简单的检错方式。有四种检错方式:
偶校验
奇校验
高校验
低校验

对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。

停止位

发送端UART将数据传输线从低电压驱动到高电压至少持续两位数据的时间宽度来表示整个数据包的传输已经结束。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容错性越好,但是数据传输率同时也越慢。

波特率

波特率是串口数据的传输速度,即Bit/s,常见的波特率比如:9600,19200,38400,57600,115200等。假设目前UART的配置为,1个起始位,8个数据位,0个校验位,1个停止位,那么9600的波特率,可以计算出每一位数据的时间宽度为:


那么传输一个字节(也就是10 bit 数据)需要的时间为 1.04 毫秒。

UART传输过程

①发送端UART从数据总线转换并行数据。

②发送端UART将起始位,奇偶校验位和停止位添加到数据包中,示意图如下。


③整个数据包从发送端UART串行发送到接收端UART,接收端UART按照预先配置好的波特率对数据线进行采样,示意图如下。


④接收端UART解析接收的数据,丢弃数据包中的起始位,奇偶校验位和停止位。

⑤接收UART将串行数据转换回并行数据,并将其传输到接收端的数据总线。

本文转自: STM32嵌入式开发(微信号:c-stm32),作者:acket,转载此文目的在于传递更多信息,版权归原作者所有。

围观 63

本文将介绍在MM32F013x上实现UART单线半双工的功能应用。

UART单线半双工简介

在使用数字舵机通讯时所用到的通信方式为UART通信,但舵机只有三根接线,除去VCC和GND,只有一条通信线,也就是说要实现双向通信,只能使用单线半双工模式。在单线半双工模式下,TX 和 RX 引脚在芯片内部互连。

01 配置流程

单线半双工模式是通过设置UART_SCR寄存器的HDSEL位,在这个模式里UART_SCR 寄存器的SCEN位必须保持清零状态。

在单线半双工模式下,TX和RX引脚在芯片内部互联,使用控制位”HALF DUPLEX SEL”(UART_SCR 中的 HDSEL 位) 选择半双工和全双工通信。

注意
当选择单线半双工模式时RX 不再被使用,当有数据需要发送的时候IO才会被UART驱动,没有数据传输时TX总是被释放,所以使用单线半双工需要外部加上拉。

除此之外通讯上和正常的UART模式类似。由于是单线半双工同一时刻总线上只能有一个节点发送,所以需要软件协议层去管理线上冲突防止多个设备同时发送,当 TXEN 位被设置时,只要数据一写到数据寄存器上,发送就继续。

02 UART_SCR寄存器描述

配置UART_SCR 的HDSEL为1

UART_SCR 寄存器的SCEN位清零


初始化UART1

从官网上下载MM32F013x例程,里面有UART普通模式的配置,在这个基础上我们直接调用UART_HalfDuplexCmd(UART1,ENABLE);函数接口将串口配置成单线半双工模式,然后IO口初始化只需要配置PA9 TX即可,如下:

void UART1_NVIC_Init(u32 baudrate)
{
    UART_InitTypeDef UART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    //UART1 NVIC
    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    //Baud rate
    UART_StructInit(&UART_InitStructure);
    UART_InitStructure.BaudRate = baudrate;
    //The word length is in 8-bit data format.
    UART_InitStructure.WordLength = UART_WordLength_8b;
    UART_InitStructure.StopBits = UART_StopBits_1;
    //No even check bit.
    UART_InitStructure.Parity = UART_Parity_No;
    //No hardware data flow control.
    UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
    UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;
    UART_Init(UART1, &UART_InitStructure);        

    UART_HalfDuplexCmd(UART1,ENABLE);  //Half Duplex Enable 
    UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);    

    UART_Cmd(UART1, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);

    //UART1_TX   GPIOA.9
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

功能验证测试

UART单线半双工功能测试我们现在拿两个MM32F0133的板子一个做主机一个做从机进行单线收发测试,主机先发送一包数据给从节点,当从节点收到这包数据后再把这包数据发回给主机,然后主机和从机两个板子PA9短接到一起,外部在加一个4.7K上拉电阻。

主机函数处理:

uint8_t txbuff[10]= {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA};
s32 main(void)
{
    CONSOLE_Init(115200);//UART2 printf打印
    UART1_NVIC_Init(115200);
    printf("UART Half Duplex TX Test \r\n");
    UART1_Send_Group(txbuff,sizeof(txbuff));
    printf("TX Data: "); 
    for(index=0;index<10;index++)
    {
        printf(" %x ",txbuff[index]);
    }    
    printf("\r\n");        
    while(1) 
    {            
       if(gUartRxSta == 1) //收到一包数据
       {
           gUartRxSta = 0;        
           printf("RX Data: ");             
           for(index=0;index<10;index++)
           {
               printf(" %x ",Rx_buff[index]);
           }                    
               printf("\r\n");                               
               memset(Rx_buff,0x00,10);                    
        }
    }
    //return 0;
}

主机UART的中断服务函数里面,将接从机发送的数据存放在Rx_buff里面,当收到一包数据后通过printf打印到串口,和原始发送的数据进行对比。

void UART1_IRQHandler(void)
{
   u8 recvbyte;
   // Send packet
   if (UART_GetITStatus(UART1, UART_IT_TXIEN) != RESET) 
   {
      UART_ClearITPendingBit(UART1, UART_IT_TXIEN);
   }
   // Recv packet
   if (UART_GetITStatus(UART1, UART_ISR_RX) != RESET)
   {
      UART_ClearITPendingBit(UART1, UART_ISR_RX);
      recvbyte = UART_ReceiveData(UART1);
      Rx_buff[rx_cnt] = recvbyte;
      rx_cnt++;
      if(rx_cnt == 10)
      {
         gUartRxSta = 1;                 
         rx_cnt = 0;
      }
   }
}

从机函数处理:

s32 main(void)
{
   CONSOLE_Init(115200);//UART2 printf打印
   UART1_NVIC_Init(115200);
   printf("UART Half Duplex RX Test\r\n");    
   while(1)
   {            
      if(gUartRxSta == 1)//收到一包数据
      {
         gUartRxSta = 0;
         UART1_Send_Group(Rx_buff,10);
         memset(Rx_buff,0x00,10);
      }  
   }
   //return 0;
}

从机UART的中断服务函数里面,将接主机发送的数据存放在Rx_buff里面,当收到一包数据后通过单线半双工这个串口发送回去。

void UART1_IRQHandler(void)
{
    u8 recvbyte;
    // Send packet
    if (UART_GetITStatus(UART1, UART_IT_TXIEN) != RESET) 
    {
        UART_ClearITPendingBit(UART1, UART_IT_TXIEN);
    }
    // Recv packet
    if (UART_GetITStatus(UART1, UART_ISR_RX) != RESET)
    {
        UART_ClearITPendingBit(UART1, UART_ISR_RX);
        recvbyte = UART_ReceiveData(UART1);
        Rx_buff[rx_cnt] = recvbyte;
        rx_cnt++;
        if(rx_cnt == 10)
        {
            gUartRxSta = 1;
            rx_cnt = 0;
        }
    }
}

观察测试结果:

然后我们通过主机UART2 的printf打印可以看到主机TX Data 和从机返回的RX Data数据是一样的。


再看看下图逻辑分析仪抓取的逻辑波形,可以也可以看到主机发送的波形和从机返回的波形数据是一样的。


转自:灵动微电子

围观 613

本文是针对在MM32F013x上实现UART极性取反的功能应用。

在嵌入式领域,通常默认串口的电平是高电平为逻辑1,低电平为逻辑0,而在MM32的UART特性中是可以将高电平设置为逻辑0,低电平设置为逻辑1的,UART极性取反虽然不常用,但还是在特殊情况下是需要这个功能,比如硬件设计已经导致必须使用极性取反,否则电路就要改板或者增加反相电路。例如下图的UART隔离电路就需要UART发送极性取反功能。

图1 UART隔离电路

01、UART极性取反简介

UART极性取反下的电平与正常模式下的电平是相反的,正常情况下,UART空闲时电平是高,起始位是空闲状态下变成低电平,bit为1时电平也高。在UART极性取反状态下,空闲电平是低电平,起始位是高,bit为1时电平是低。

在数据接收发送寄存器中,数据也是可以反转的,原来的0变为1,原来的1变为0,这和电平极性反转是类似。需要特别注意的是,在极性反转的时候,起始位和结束位也都反转了,所有的信号电平都反转;而在数据寄存器中只反转了数据位,其中也包含了校验位,没有反转信号的起始位和结束位的极性。

图2 UART极性取反波形

上图是用逻辑分析仪抓取的UART极性取反的逻辑波形。红色字体:“IDLE”部分是空闲状态,“START”是起始位,后面“0~7”是数据的bit0~bit7,“STOP” 是停止位,“IDLE”是空闲(注意,逻辑分析仪设置反向,不然只能抓到波形,无法解析出数据)。

图3 UART极性取反控制位

UART->GCR寄存器描述

设置寄存器 TX_TOG位来使能UART发送极性取反功能。

如果UART接收极性也需要取反,则设置RX_TOG位来使能UART接收极性取反功能。

除了设置上述2个位外,其他部分的设置跟普通模式一模一样。

02、初始化UART1

从官网上下载MM32F013x例程,里面有UART普通模式的配置,主要是增加了UART->GCR的TX_TOG和RX_TOG位设置,如下:

void uart_nvic_init(u32 bound)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    UART_InitTypeDef UART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

    //UART1 NVIC
    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    //Baud rate
    UART_StructInit(&UART_InitStructure);
    UART_InitStructure.BaudRate = bound;
    //The word length is in 8-bit data format.
    UART_InitStructure.WordLength = UART_WordLength_8b;
    UART_InitStructure.StopBits = UART_StopBits_1;
    //No even check bit.
    UART_InitStructure.Parity = UART_Parity_No;
    //No hardware data flow control.
    UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
    UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;

    UART_Init(UART1, &UART_InitStructure);

    UART_ITConfig(UART1,UART_IT_RXIEN,ENABLE);

    UART1->GCR |= UART_GCR_TXTOG; //发送取反位
    UART1->GCR |= UART_GCR_RXTOG; //接收取反位

    UART_Cmd(UART1, ENABLE);

    //UART1_TX   GPIOA.9
    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //UART1_RX    GPIOA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

03、功能测试

UART极性取反测试可以自发自收,需要将PA9和PA10短接,需要注意的是收发都需要配置成极性取反功能。在main函数所在.c文件里面,定义一个u8型全局变量UART_SendValue,UART_SendValue每隔500ms自加1,然后通过UART发送出去,依次循环。

u8 UART_SendValue = 0;
s32 main(void)
{
    DELAY_Init();
    LED_Init();
    uart_nvic_init(9600);
    while(1) {        
                 UartSendByte(++UART_SendValue);
                 DELAY_Ms(500);
             }
} 

在UART的中断服务函数里面,将接收到的数据存放在printBuf,这样可以在仿真界面下的watch窗口观看printBuf的值是否每隔500ms增加一次并且和UART_SendValue的值一致。

void UART1_IRQHandler(void)
{
    if (UART_GetITStatus(UART1, UART_ISR_RX) != RESET) 
    {
         UART_ClearITPendingBit(UART1, UART_ISR_RX);
        printBuf = UART_ReceiveData(UART1);
    }
}

下图仿真界面下可以看到printBuf和UART_SendValue的值是一致的。

图4 UART仿真watch窗口数据对比

下图逻辑分析仪抓取的逻辑波形,可以看到电平和分析到的数据都是一致的。

图5 UART极性0x09取反波形

转自:灵动微电子

围观 423

在上一次的灵动微课堂中和大家分享过MM32F013x-UART 9bit通信实例,本次微课堂在此实例的基础上实现UART多处理器通信。MM32F013x系列MCU支持UART多处理器通信,其工作原理是主从机设备采用复用漏极开路,主从机外部接上拉电阻,在空闲时使从机处于静默模式,主机要控制从机执行任务时主机发送指令唤醒从机并发送数据控制从机执行相应任务。

1、UART静默模式

MM32F013x系列MCU UART静默模式的特点

  • 任何接收状态位都不会被设置
  • 所有的接收中断都被禁止
  • UART_CCR寄存器中的RWU位被置1。RWU可以被硬件自动控制或在某个条件下由软件写入。

根据UART_CCR寄存器中的WAKE位状态,UART多处理器通信有二种方法进入或退出静默模式分别是:

  • WAKE 位被设置0:进行空闲总线检测。
  • WAKE 位被设置1:进行地址标记检测。

空闲总线检测

空闲帧唤醒可以同时唤醒所有从机,在从机处于静默模式时主机发送空闲帧(即所有位均为1的数据)实现多个从机同步被唤醒。

地址标记检测

地址标记唤醒可以唤醒单个从机,从机进入静默模式时,主机向从机寻址发送地址帧,从机自动比对地址,地址配对正确则该从机被唤醒,否则继续进入静默模式。这样只有被主机寻址的从机才被唤醒激活并接收数据,从而减少未被寻址的从机参与带来的多余的UART服务开销。

2、配置流程

与UART多处理器通信相关的主要寄存器有UART通用控制寄存器、UART接收地址寄存器UART_RXADDR和UART接收掩码寄存器UART_RXMASK其描述如下寄存器表所示:

图1

如上图1所示为UART通用控制寄存器UART_CCR,在MM32F013x UM手册的第489和第490页有关于该寄存器位的详细描述。本实例UART多处理器通信要用到的相关于UART通用控制寄存器UART_CCR位的说明如下:

Bit13

WAKE(rw,reset:0x00)唤醒方法,该位决定UART多机通信从机唤醒的方法。

1:地址标记唤醒。
库函数设置:
UART_WakeUpConfig(UART1,UART_WakeUp_AddressMark);

0:空闲总线唤醒。
库函数设置:
UART_WakeUpConfig(UART1, UART_WakeUp_IdleLine);

Bit12

RWU(rw, reset:0x00)接收唤醒,该位用来决定是否把UART置于静默模式。该位可以由软件设置或清除。当唤醒序列到来时,硬件也会自动将其清零。

1:接收器处于静默模式。
库函数设置:
UART_ReceiverWakeUpCmd(UART1, ENABLE);

0:接收器处于正常工作模式。
库函数设置:
UART_ReceiverWakeUpCmd(UART1, DISABLE);

注:在设置地址标记唤醒时,如果接收 buffer 非空则不能软件修改。

Bit11

B8EN(rw, reset:0x00)UART同步帧发送第9bit使能控制位。该位使能后校验使能PEN不起作用。

1:使能同步帧第9bit发送。
库函数设置:
UART_Enable9bit(UART1, ENABLE);

0:禁止同步帧第9bit发送。
库函数设置:
UART_Enable9bit(UART1, DISABLE);

Bit10

B8TOG(rw,reset:0x00)UART同步帧发送第9bit自动翻转控制位。

1:使能第9bit自动翻转。
库函数设置:
UART_Set9bitAutomaticToggle(UART1, ENABLE);

0:禁止第9bit自动翻转。
库函数设置:

UART_Set9bitAutomaticToggle(UART1, DISABLE);
注:在 B8TXD 和 B8POL 的值相同时,在配置完寄存器后传输的第二个数据开始翻转,第一个数据默认为地址位。

Bit8

B8TXD(rw,reset:0x00)UART同步帧发送数据第9bit。

1:发送同步帧第9bit为高电平。
库函数设置:
UART_Set9bitLevel(UART1, ENABLE);

0:发送同步帧第9bit为低电平。
库函数设置:
UART_Set9bitLevel(UART1, DISABLE)

图2

如上图2所示为UART接收地址寄存器UART_RXADDR,在MM32F013x UM手册的第491页有关于该寄存器位的详细描述。本实例UART多处理器通信要用到的相关于UART_RXADDR接收地址寄存器位的说明如下:

Bit31:8:

Reserved,始终读为0x00

Bit7:0:

RXADDR(rw,reset:0x00)UART 同步帧数据本机匹配地址。

库函数设置:
UART_SetRXAddress(UART1, SLAVEADDR);

其中地址参数SLAVEADDR可以设置为宏定义形式。

如果 RXMASK =0xFF时接收到的同步帧数据与本机匹配地址相同时,产生RXB8_INTF。地址 0是广播地址,收到后都会响应。

图3

如上图3所示为UART接收掩码寄存器UART_RXMASK,在MM32F013x UM手册的第492页有关于该寄存器位的详细描述。本实例UART多处理器通信要用到的相关于UART_RXMSK接收掩码寄存器位的说明如下:

Bit31:8:

Reserved,始终读为0x00

Bit7:0:

RXMASK(rw,reset:0xFF)UART数据位全为“0”时,接收到任何数据都产生同步帧中断请求。如果数据位为“1”,RDR和RXADDR的相应位匹配时,产生同步帧中断请求。

库函数设置:

UART_SetRXMASK(UART1,SLAVEADDR); 其中地址参数SLAVEADDR可以设置为宏定义形式。

根据上文与UART 9bit多处理器通信相关的寄存器位的描述,本实例从机唤醒模式使用标记从机地址方式,在MM32F013x-UART 9bit通信实例的基础上增加从机UART 9bit多处理器通信相关的寄存器位的初始化,这里以库函数方式给出,增加的4行代码如下所示:

//Wake up method
UART_WakeUpConfig(UART1, UART_WakeUp_AddressMark);
//Synchronous frame match address
UART_SetRXAddress(UART1, SLAVEADDR);
//Synchronous frame match address mask
UART_SetRXMASK(UART1,SLAVEADDR);
//Receive wake up method
UART_ReceiverWakeUpCmd(UART1, ENABLE);

3、程序配置

01、初始化主机MM32F013x UART1 9bit通信

本实例使用MM32F0133C7P核心板作为UART多处理器通信,主机初始化代码如下所示:

bsp_UART1_Master_irq_9Bit_Init(u32 baudrate)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    UART_InitTypeDef UART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    UART_StructInit(&UART_InitStructure);
    UART_InitStructure.BaudRate = baudrate;
    UART_InitStructure.WordLength = UART_WordLength_8b;
    UART_InitStructure.StopBits = UART_StopBits_1;
    UART_InitStructure.Parity = UART_Parity_No;
    UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
    UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;

    UART_Enable9bit(UART1, ENABLE);
    UART_Set9bitLevel(UART1, DISABLE);
    UART_Set9bitAutomaticToggle(UART1, ENABLE);
    UART_Init(UART1, &UART_InitStructure);

    UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    UART_Cmd(UART1, ENABLE);
}

02、初始化从机MM32F013x UART1 9bit通信

MM32F0133C7P UART1从机初始化代码如下所示:

注意:多从机通信,初始化从机串口时需要修改从机地址宏为0x01、0x02等。

#define SLAVEADDR  (0x01)
void bsp_UART1_Slave_irq_9Bit_Init(u32 baudrate)
{
   GPIO_InitTypeDef GPIO_InitStructure;
   UART_InitTypeDef UART_InitStructure;
   NVIC_InitTypeDef NVIC_InitStructure;

   RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

   GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
   GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

   GPIO_StructInit(&GPIO_InitStructure);
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
   GPIO_Init(GPIOA, &GPIO_InitStructure);

   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
   GPIO_Init(GPIOA, &GPIO_InitStructure);

   UART_StructInit(&UART_InitStructure);
   UART_InitStructure.BaudRate = baudrate;
   UART_InitStructure.WordLength = UART_WordLength_8b;
   UART_InitStructure.StopBits = UART_StopBits_1;
   UART_InitStructure.Parity = UART_Parity_No;
   UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
   UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;

   UART_WakeUpConfig(UART1, UART_WakeUp_AddressMark);
   UART_WakeUpConfig(UART1, UART_WakeUp_IdleLine);
   UART_SetRXAddress(UART1, SLAVEADDR);
   UART_SetRXMASK(UART1,0x02);
   UART_ReceiverWakeUpCmd(UART1, ENABLE);
   UART_Enable9bit(UART1, ENABLE);
   UART_Set9bitLevel(UART1, DISABLE);
   UART_Set9bitAutomaticToggle(UART1, ENABLE);
   UART_Init(UART1, &UART_InitStructure);
   UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);

   NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
   NVIC_InitStructure.NVIC_IRQChannelPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure); 

   UART_Cmd(UART1, ENABLE);
}

03、编写MM32F013x UART1主机中断服务函数

MM32F0133C7P UART1主机中断服务函数,代码如下所示:

#define RX_MASTER_LEN   (3)
u8 g_Rx_Master_Buf[RX_MASTER_LEN] = {0x00};
u8 g_Rx_Master_Cnt = 0;
void UART1_IRQHandler(void)
{
     u8 res;
     if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET) 
     {
          UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
          res = UART_ReceiveData(UART1);
          g_Rx_Master_Buf[g_Rx_Master_Cnt] = res;
          if(g_Rx_Master_Cnt < RX_MASTER_LEN-1)
          {
             g_Rx_Master_Cnt++;
          }
          else
          {
             g_Rx_Master_Cnt = 0;
          }
     }
}

04、编写MM32F013x UART1从机中断服务函数

MM32F0133C7P UART1从机中断服务函数,代码如下所示:

#define RX_SLAVE_LEN    (3)
u8 g_Rx_Slave_Buf[RX_SLAVE_LEN] = {0x00};
u8 g_Rx_Slave_Cnt = 0;
void UART1_IRQHandler(void)
{
     u8 res;
     if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET) 
     {
         UART_ClearITPendingBit(UART1, UART_IT_RXIEN);
         res = UART_ReceiveData(UART1);
         g_Rx_Slave_Buf[g_Rx_Slave_Cnt] = res;
         if(g_Rx_Slave_Cnt < RX_SLAVE_LEN-1)
         {
             g_Rx_Slave_Cnt++;
         }
         else
         {
             g_Rx_Slave_Cnt = 0;
         }  
     }
}

05、编写MM32F013x UART1主从机

通用发送数据函数

MM32F0133C7P UART1主从机通用发送函数,代码如下所示:

u8 g_Tx_Master_Buf[2] = {0xAA,0x55};
void bsp_UART_Send_Byte_Data(UART_TypeDef* uart,u8 data)
{
    UART_SendData(uart, data);
    while(!UART_GetFlagStatus(uart, UART_FLAG_TXEPT));
}
void bsp_UART_Send_Bytes_Data(UART_TypeDef* uart, u8* buf, u16 len)
{
    while(len--)
    {
        bsp_UART_Send_Byte_Data(uart,*buf++);
    }
}

06、编写MM32F013x UART1主机发送从机地址函数

MM32F0133C7P UART1主机发送从机地址函数,代码如下所示:

u8 g_Tx_Master_Buf[2] = {0xAA,0x55};
#define SLAVEADDR1       (0x01)
#define SLAVEADDR2       (0x02)
void bsp_UART_Send_SlaveAddr(UART_TypeDef* uart,u8 data)
{
    UART_SendData(uart, data);
    while(!UART_GetFlagStatus(uart, UART_FLAG_TXEPT));
}

07、编写MM32F013x UART1主机按键

发送从机地址和数据函数

宏定义按键GPIO端口和管脚,本实例只用到MM32F013x核心板的PA0作为KEY1对应核心板。

#define KEY1_GPIO_Port  GPIOA
#define KEY1_Pin         GPIO_Pin_0
#define KEY2_GPIO_Port  GPIOB
#define KEY2_Pin         GPIO_Pin_2
#define KEY3_GPIO_Port  GPIOB
#define KEY3_Pin        GPIO_Pin_10
#define KEY4_GPIO_Port  GPIOB
#define KEY4_Pin        GPIO_Pin_11
#define KEY1         GPIO_ReadInputDataBit(KEY1_GPIO_Port,KEY1_Pin)  //read key1
#define KEY2         GPIO_ReadInputDataBit(KEY2_GPIO_Port,KEY2_Pin)  //read key2
#define KEY3         GPIO_ReadInputDataBit(KEY3_GPIO_Port,KEY3_Pin)  //read key3
#define KEY4         GPIO_ReadInputDataBit(KEY4_GPIO_Port,KEY4_Pin)  //read key4
#define KEY1_DOWN_VALUE     1   //KEY1
#define KEY2_DOWN_VALUE     0   //KEY2
#define KEY3_DOWN_VALUE     0   //KEY3
#define KEY4_DOWN_VALUE     0   //KEY4
#define KEY1_PRES           1   //KEY1
#define KEY2_PRES           2   //KEY2
#define KEY3_PRES           3   //KEY3
#define KEY4_PRES           4   //KEY4
//Init Key GPIO
void bsp_Key_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA | RCC_AHBPeriph_GPIOB ,ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin  = KEY1_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
    GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY2_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY2_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY3_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY3_GPIO_Port, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin  = KEY4_Pin;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(KEY4_GPIO_Port, &GPIO_InitStructure);    
}

u8 bsp_Key_Scan(u8 mode)
{
    static u8 key_up = 1;
    if(mode)
    {
        key_up = 1;
    }
    if(key_up && ((KEY1 == KEY1_DOWN_VALUE) || (KEY2 == KEY2_DOWN_VALUE) || \
                  (KEY3 == KEY3_DOWN_VALUE) || (KEY4 == KEY4_DOWN_VALUE))) 
    {
        DELAY_Ms(10);

        key_up = 0;

        if(KEY1 == KEY1_DOWN_VALUE)
        {
            return KEY1_PRES;
        }
        else if(KEY2 == KEY2_DOWN_VALUE)
        {
            return KEY2_PRES;
        }
        else if(KEY3 == KEY3_DOWN_VALUE)
        {
            return KEY3_PRES;
        }
        else if(KEY4 == KEY4_DOWN_VALUE)
        {
            return KEY4_PRES;
        }
    }
    else if((KEY1 != KEY1_DOWN_VALUE) && (KEY3 != KEY3_DOWN_VALUE) && \
            (KEY4 != KEY4_DOWN_VALUE) && (KEY2 != KEY2_DOWN_VALUE))
    {
        key_up = 1;
    }

    return 0;
}

u8 Key_Nnum = 0;
void bsp_Process_Key_Task(void)
{
    static u8 Key_Value = 0;
    Key_Value = bsp_Key_Scan(0);
    switch(Key_Value)
    {
        case KEY1_PRES:           
           if(Key_Nnum == 0)
           {
                Key_Nnum = 1;
                bsp_UART_Send_SlaveAddr(UART1, SLAVEADDR1); //Send SlaveAddr1
                bsp_UART_Send_Bytes_Data(UART1, g_Tx_Master_Buf, sizeof(g_Tx_Master_Buf)); //Send data           
            }
            else if(Key_Nnum == 1)
            {
                Key_Nnum = 0;                
                bsp_UART_Send_SlaveAddr(UART1, SLAVEADDR2); //Send SlaveAddr2
                bsp_UART_Send_Bytes_Data(UART1, g_Tx_Master_Buf, sizeof(g_Tx_Master_Buf)); //Send data 
            }          
            break;
        case KEY2_PRES:
            break;
        case KEY3_PRES:
            break;
        case KEY4_PRES:
            break;
        default :
            break;        
    }
}

08、编写MM32F013x UART1主机

接收从机返回的数据函数

处理MM32F0133C7P UART1主机接收数据函数,代码如下所示:

 void bsp_UART_Master_Rec_Task(void)
{
  if(((g_Rx_Master_Buf[0] == SLAVEADDR1 ) || (g_Rx_Master_Buf[0] == SLAVEADDR2)) && (g_Rx_Master_Buf[1] == 0xAA) && (g_Rx_Master_Buf[2] == 0x55))
   {
      LED1_TOGGLE();   
      g_Rx_Master_Cnt = 0;
       memset(g_Rx_Master_Buf,0,sizeof(g_Rx_Master_Buf));
    }
}

09、编写MM32F013x UART1从机

接收主机发送的数据函数

处理MM32F0133C7P UART1主机接收数据函数,代码如下所示:

注意:g_Rx_Slave_Buf[1]为主机发送给从机的地址由从机接收判断,多从机通信需要修改从机地址宏:#define SLAVEADDR (0x02)等。

void bsp_UART_Slave_Rec_Task(void)
{
  if((g_Rx_Slave_Buf[0] == SLAVEADDR) && (g_Rx_Slave_Buf[1] == 0xAA) && (g_Rx_Slave_Buf[2] == 0x55))
  {
     LED1_TOGGLE();   
     bsp_UART_Send_Bytes_Data(UART1, g_Rx_Slave_Buf, sizeof(g_Rx_Slave_Buf));
     g_Rx_Slave_Cnt = 0;
     memset(g_Rx_Slave_Buf,0,sizeof(g_Rx_Slave_Buf));
  }
}

10、MM32F013x UART1 9bit多处理器通信功能演示

MM32F0133C7P多处理器通信功能演示:

主机main函数中调用DELAY_Init、LED_Init、bsp_Key_GPIO_Init和主机串口初始化函数bsp_UART1_Master_irq_9Bit_Init,在while(1)后台调用按键发送从机地址和发送数据函数bsp_Process_Key_Task以及处理接收从机返回数据函数bsp_UART_Master_Rec_Task代码如下所示:

s32 main(void)
{
    //SysTick init
    DELAY_Init();
    //LED Init
    LED_Init();
    //Key GPIO Init
    bsp_Key_GPIO_Init();
    //UART1 9Bit irt Init
    bsp_UART1_Master_irq_9Bit_Init(115200);
    while(1) 
    {
        //Key processing multi-processor communication
        bsp_Process_Key_Task();
        //Processing master receiving tasks
        bsp_UART_Master_Rec_Task();
    }
}

从机main函数中调用LED_Init和从机串口初始化函数bsp_UART1_Slave_irq_9Bit_Init,在while(1)后台调用处理从机接收任务函数bsp_UART_Slave_Rec_Task代码如下所示:

s32 main(void)
{
    //LED Init
    LED_Init();
    //UART1 9bit init
    bsp_UART1_Slave_irq_9Bit_Init(115200);
    while(1) 
    {
        //Processing slave receiving tasks
        bsp_UART_Slave_Rec_Task(); 
    }
}

分别编译以上主机和从机工程代码,然后烧录软件到MM32F0133C7P核心板上,烧录多从机时需修改从机工程代码的从机地址宏SLAVEADDR,编译后再烧录。本实例从机烧录0x01和x02为例作为演示,即1台主机和2台从机作为演示。

4、接线方式

MM32F0133C7P多处理器通信UART主机和从机接线方法:

各从机TX线与连接,RX线与连接,从机线与连接的TX接到主机RX端,从机线与连接的RX接到主机的TX端,主从机分别外接5.1~10K值范围之一的上拉电阻,本实例接5.1K上拉电阻。

MM32F0133C7P UART多处理器通信演示1台主机和2台从机分别发送和接收数据:

主机第1次按下MM32F0133C7P核心板上的S1按键,主机发送1字节从机地址0x01,接着发送2字节数据0xAA和0x55,从机1(地址0x01)在静默模式下自动比对主机寻址从机的地址0x01比对成功从机1被唤醒继续接收0xAA和0x55数据,收到完整的3字节数据时从机LED1状态翻转LED1亮并原样返回收到的数据给主机,此时从机2仍处于静默模式,主机完全收到从机1返回的3字节数据即0x01,0XAA,0x55时主机LED1状态翻转LED亮,实物现象如下图4所示。

图4(上从机1、中主机、下从机2)

主机第2次按下MM32F0133C7P核心板上的S1按键,主机发送1字节从机地址0x02,接着发送2字节数据0xAA和0x55,从机2(地址0x02)在静默模式下自动比对主机寻址从机的地址0x02比对成功从机2被唤醒继续接收0xAA和0x55数据,收到完整的3字节数据时从机LED1状态翻转LED1亮并原样返回收到的数据给主机,此时从机1仍处于静默模式其LED1维持上次点亮状态,主机完全收到从机2返回的3字节数据时即0x02,0XAA,0x55时主机LED1状态翻转LED灭,实物现象如下图5所示:

图5(上从机1、中主机、下从机2)

5、波形抓取

逻辑分析仪抓取到的MM32F0133C7多处理器通信波形图如下图6和图7所示:

图6上波形红色框注为主机发送:0x01、0xAA、0x55寻址从机1,从机(地址0x01)从静默模式唤醒并收到主机发送的数据0xAA和0x55时原样返回给主机如图6下波形蓝色框注:0x01、0xAA、0x55,此时从机2(地址0x02)并未作响应仍处于静默模式。

图6

图7上红色框注为主机发送:0x02、0xAA、0x55寻址从机2,从机(地址0x02)从静默模式唤醒并收到主机发送的数据0xAA和0x55时原样返回给主机如图7下波形蓝色框注:0x02、0xAA、0x55,此时从机1(地址0x01)并未作响应仍处于静默模式。

图7

转自:灵动MM32MCU

围观 162

在之前的微课堂中和大家分享过灵动MM32系列MCU的UART通信实例,在此实例的基础上我们增加UART 9bit通信功能。UART 9bit通信的作用是第9bit用于标识是地址或数据,第9bit 为1标识是从机地址,为0标识是数据,此外UART通信的第9bit也可作为数据的同步帧位使用。

在双机通讯中,UART的8bit通信的第九位一般是奇偶校验位,而多机通讯中,第九位用于标识地址或数据,常用1表示后面的是从机地址,0表示后面的是数据。我们通常希望只有被寻址的接收者才被激活,来接收随后的数据,这样就可以减少由未被寻址的接收器的参与带来的多余的UART服务开销。未被寻址的设备可启用其静默功能置于静默模式。在静默模式里,任何接收状态位都不会被设置,所有接收中断被禁止。

以MM32F013x系列MCU的UART通信为例,通过一个示例Demo介绍UART 9bit通信的同步帧方式。

一、与UART 9bit通信相关的寄存器

图1

如上图1所示为UART通用控制寄存器UART_CCR,在MM32F013x UM手册的第489和第490页有关于该寄存器位的详细描述。本实例用到的UART通用控制寄存器UART_CCR位说明如下:

Bit11

B8EN(rw, reset:0x00)UART同步帧发送第9bit使能控制位。该位使能后校验使能PEN不起作用。

1:使能同步帧第9bit发送。

库函数设置:

UART_Enable9bit(UART1, ENABLE)

0:禁止同步帧第9bit发送。

库函数设置:

UART_Enable9bit(UART1, DISABLE)

Bit10

B8TOG(rw,reset:0x00)UART同步帧发送第9bit自动翻转控制位。

1:使能第9bit自动翻转。

库函数设置:

UART_Set9bitAutomaticToggle(UART1, ENABLE)

0:禁止第9bit自动翻转。

库函数设置:

UART_Set9bitAutomaticToggle(UART1, DISABLE)

注:在 B8TXD 和 B8POL 的值相同时,在配置完寄存器后传输的第二个数据开始翻转,第一个数据默认为地址位。

Bit8

B8TXD(rw,reset:0x00)UART同步帧发送数据第9bit。

1:发送同步帧第9bit为高电平。

库函数设置:

UART_Set9bitLevel(UART1, ENABLE)

0:发送同步帧第9bit为低电平。

库函数设置:

UART_Set9bitLevel(UART1, DISABLE)

二、程序配置

初始化MM32F013x UART1 9bit通信

从官网下载MM32F013x例程,以MM32F0133C7P的UART1通信为例,增加与UART 9bit通信相关的寄存器位的初始化,这里以库函数方式给出,增加的3行代码如下所示:

//Synchronous frame enable bit UART_CCR Bit11:B8EN
UART_Enable9bit(UART1, ENABLE);
//Synchronous frame transmit UART_CCR Bit8: B8TXD
UART_Set9bitLevel(UART1, DISABLE);
//Synchronous frame auto toggle UART_CCR Bit10:B8TOG
UART_Set9bitAutomaticToggle(UART1, ENABLE);

MM32F0133C7P UART1 9bit通信,初始化代码如下所示:

void bsp_UART1_9Bit_Init(u32 baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure;
UART_InitTypeDef UART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_1);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_1);

GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
UART_StructInit(&UART_InitStructure);

UART_InitStructure.BaudRate = baudrate;
UART_InitStructure.WordLength = UART_WordLength_8b;
UART_InitStructure.StopBits = UART_StopBits_1;

UART_InitStructure.Parity = UART_Parity_No;
UART_InitStructure.HWFlowControl = UART_HWFlowControl_None;
UART_InitStructure.Mode = UART_Mode_Rx | UART_Mode_Tx;
UART_Init(UART1, &UART_InitStructure);

UART_ITConfig(UART1, UART_IT_RXIEN, ENABLE);
UART_Enable9bit(UART1, ENABLE);
UART_Set9bitLevel(UART1, DISABLE);
UART_Set9bitAutomaticToggle(UART1, ENABLE);

NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

UART_Cmd(UART1, ENABLE);
}

1 ● 编写MM32F013x UART1中断函数

MM32F013x UART1中断服务函数,同时将收到的数据发送出去,代码如下所示:

void UART1_IRQHandler(void)
{
u8 res;

if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET)
{
//Receiving interrupts (data received must end at 0x0D 0x0a)
UART_ClearITPendingBit(UART1, UART_IT_RXIEN);

//read receive data.
res = UART_ReceiveData(UART1);

bsp_UART1_Send_Byte(res);
}
}

2 ● 编写MM32F013x UART1发送函数

使用之前工程的MM32F0133C7P UART1发送函数,代码如下所示:

void bsp_UART1_Send_Byte(u8 dat)
{
UART_SendData(UART1, dat);

while(!UART_GetFlagStatus(UART1, UART_FLAG_TXEPT));
}

MM32F013x UART1 9bit通信功能演示

在main函数中调用SysTick和UART1 9bit通信初始化函数,代码如下所示:

s32 main(void)
{
//SysTick init
DELAY_Init();
//UART1 9bit init
bsp_UART1_9Bit_Init(115200);

while(1)
{
bsp_UART1_Send_Byte(0x55);

DELAY_Ms(500);
}
}

编译工程代码,然后烧录软件到MM32F0133C7P核心板上,用逻辑分析仪抓取UART1 9bit通信发送数据和接收数据的波形:

演示发送数据:
以MM32F0133C7P发送0x55为例,使用逻辑分析仪抓取UART1 9bit通信发送数据的波形如下图所示。

图2

演示接收数据:
以上位机串口助手发送0xAA为例,使用逻辑分析仪抓取UART1 9bit通信收到的数据的波形,观察箭头所指第bit9位,如下图3所示:

图3

转自:灵动MM32MCU

围观 226

嵌入式工程师在开发产品中经常会用到MCU的片上UART和其它模块进行通信,为了在某些非正常的恶劣环境下能正常使用串口通信,可能需要对UART通信波特率进行自适应校准,在我们之前的微课堂中讲解过关于MM32通用MCU的UART串口通信方面的基础知识,这里我们增加基于灵动微电子MM32F013x 系列UART硬件自适应波特率的使用。

1、原理

UART硬件波特率自适应检测首个通信字节的位宽(1bit、2bit、4bit、8bit),检测前一个边沿和后一个边沿之间的位长,即检测前一个边沿为下降沿,后一个边沿为上升沿或前一个边沿为下降沿,后一个边沿为下降沿,可通过软件灵活配置。

本实例以串口工具作为上位机,MM32F013x的UART1作为下位机,MCU端初始化为非标准波特率9200,使能空闲中断及其他状态标志位,上位机切换不同的波特率,由于上位机和MCU端的波特率不同,可能出现通信失败的情况,启动UART硬件波特率自适应功能,即检测上位机发的首个字节位宽来识别上位机的波特率,MCU端通过硬件波特率自适应切换到对应的波特率,与上位机维持后续正常的通信。

如下图所示,以首字节0XF8为例,首字节位宽为4bit的原理说明:


2、程序配置

2.1 初始化MM32F013x UART1串口

从官网下载MM32F013x例程,这里我们在MM32F0133C7P的样例程序中添加注释并对代码修改。

#include "bsp_UART.h"
#include "led.h"
/*******************************************************************************
* 函数名称:void bsp_UART1_Init(u32 baudrate)
* 函数功能:初始化UART1 PA9/PA10分别作为UART1的TX/RX
* 输入参数:无
* 返回数值:无
******************************************************************************/
void bsp_UART1_Init(u32 baudrate)
{
    //GPIO初始化结构体
    GPIO_InitTypeDef GPIO_InitStructure;
    //UART初始化结构体
    UART_InitTypeDef UART_InitStructure;    
    //NVIC初始化结构体
    NVIC_InitTypeDef NVIC_InitStructure;

    //使能UART1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_UART1, ENABLE);        
    //使能GPIOA时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);  

    //开启GPIOA PA9复用于UART1_TX功能 
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_1);    
    //开启GPIOA PA10复用于UART1_RX功能 
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_1);   

    //UART1_TX   GPIOA.9
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;     
    //配置GPIOA.9 速度为高速50MHz
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    
    //配置GPIOA.9为复用推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;        
    //根据GPIO结构体初始化UART1_TX GPIOA.9
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //UART1_RX GPIOA.10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;    
    //配置UART1_RX GPIOA.10为上拉输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;    
    //根据GPIO结构体初始化UART1_RX GPIOA.10
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //串口波特率
    UART_InitStructure.UART_BaudRate = baudrate;    
    //字长为8位数据格式
    UART_InitStructure.UART_WordLength = UART_WordLength_8b;    
    //一位停止位
    UART_InitStructure.UART_StopBits = UART_StopBits_1;    
    //无奇偶校验位
    UART_InitStructure.UART_Parity = UART_Parity_No;    
    //无硬件数据流控
    UART_InitStructure.UART_HardwareFlowControl = UART_HardwareFlowControl_None;    
    //允许收发模式
    UART_InitStructure.UART_Mode = UART_Mode_Rx | UART_Mode_Tx;
    //根据UART结构体初始化串口UART1
    UART_Init(UART1, &UART_InitStructure);

    //硬件自动波特率检测第1个字节位的位宽前一个边沿为下降沿,后一个边沿为上升沿
    //___              _______
    //   |_ _ _ _|1 x x x x|        = Binary:xxxx 1000  Fall to Rise -> 1 start bit 
    //AutoBaudRate Mode Fall to Rise 4bit width,the first byte is 0xF8 use test

    UART_AutoBaudRateSet(UART1, ABRMODE_FALLING_TO_RISINGEDGE4BIT, ENABLE);

    //接收数据中断、接收帧错误中断、自动波特率结束中断、自动波特率错误中断、空闲中断
    UART_ITConfig(UART1, UART_IT_RXIEN | UART_ICR_RXFERRCLR | UART_ICR_ABRENDCLR |\ UART_ICR_ABRERRCLR | UART_ICR_RXIDLE | UART_IT_ERR, ENABLE);
    //使能UART1
    UART_Cmd(UART1, ENABLE);

    //UART1 NVIC中断优先级设置
    NVIC_InitStructure.NVIC_IRQChannel = UART1_IRQn;
    //UART通道优先级0
    NVIC_InitStructure.NVIC_IRQChannelPriority = 0;          
    //IRQ通道使能
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        
    //根据指定的参数初始化NVIC寄存器
    NVIC_Init(&NVIC_InitStructure);    
}

2.2 编写MM32F013x UART1串口中断服务函数

继续在bsp_UART.c文件中编写MM32F013x UART1串口中断服务函数如下所示。

/*******************************************************************************
* 函数名称:void UART1_IRQHandler(void)
* 函数功能:串口1 UART1中断服务程序
* 输入参数:无
* 返回数值:无
*******************************************************************************/
//自动波特率帧错误标志
u8 Auto_BaudRate_FraErr_Flag = 0;
void UART1_IRQHandler(void)                    
{
    u8 Res;

    //UART1接收中断
    if(UART_GetITStatus(UART1, UART_IT_RXIEN) != RESET)  
    {
        //清UART1接收中断标志
        UART_ClearITPendingBit(UART1,UART_IT_RXIEN);            
        //读取UART1接收到的数据
        Res = UART_ReceiveData(UART1);        
        //UART1接收数据缓存起来,最大接收UART1_REC_LEN个字节 
        UART1_Rx_Buf[UART1_Rx_Cnt] = Res;        
        //UART1作接收缓存溢出判断,最大接收UART1_REC_LEN个字节
        if(UART1_Rx_Cnt < UART1_REC_LEN-1)
        {
            //还有数据要接收,接收计数变量自加
            UART1_Rx_Cnt++;
        }
        else
        {
            UART1_Rx_Cnt = 0;
        }
    }

    //帧错误中断标志位
    if(UART_GetITStatus(UART1, UART_IER_RXFERR) != RESET)
    {
        //自动波特率帧错误标志置1
        Auto_BaudRate_FraErr_Flag = 1;
        //清帧错误中断标志位
        UART_ClearITPendingBit(UART1,UART_IER_RXFERR);
    }

    //接收数据帧错误中断
    if(UART_GetITStatus(UART1, UART_ICR_RXFERRCLR) != RESET)
    {     
        UART_ClearITPendingBit(UART1,UART_ICR_RXFERRCLR);
    }

    //空闲中断硬件波特率自校准
    if(UART_GetITStatus(UART1, UART_ICR_RXIDLE) != RESET)
    {
        UART_ClearITPendingBit(UART1,UART_ICR_RXIDLE);

        //自动波特率帧错误标志
        if(Auto_BaudRate_FraErr_Flag == 1)
        {
            Auto_BaudRate_FraErr_Flag = 0;

    //----------------Check MM32F013x UART_AutoBaudRateHard----------
    //___              _______
    //   |_ _ _ _|1 x x x x|  = Binary:xxxx 1000  Fall to Rise -> 1 start bit 
    //AutoBaudRate Mode Fall to Rise 4bit width,the first byte is 0xF8 use test

        UART_AutoBaudRateSet(UART1, ABRMODE_FALLING_TO_RISINGEDGE4BIT, ENABLE);    
        }
    }

    //自动波特率错误中断清除位
    if(UART_GetITStatus(UART1, UART_ICR_ABRERRCLR) != RESET)
    {      
        UART_ClearITPendingBit(UART1,UART_ICR_ABRERRCLR);  
    }

    //自动波特率结束中断清除位
    if(UART_GetITStatus(UART1, UART_ICR_ABRENDCLR) != RESET)
    {
        UART_ClearITPendingBit(UART1,UART_ICR_ABRENDCLR);
    }
}

2.3 MM32F013x UART1串口接收函数

在bsp_UART.h文件中宏定义UART1波特率、接收字节长度,变量声明以及UART1接收数据函数和发送数据函数声明。

 void UART1_Recv_Task(void)
{   
   //收到的数据原样返回到串口上位机
   UART_SendBytes(UART1,UART1_Rx_Buf, UART1_Rx_Cnt);
}

2.4 MM32F013x UART串口发送函数

在bsp_UART.c文件中编写MM32F013x UART1发送数据函数,发送单字节数据和发送多字节数据函数分别如下所示:

/*******************************************************************************
* 函数名称:void UART_SendByte(UART_TypeDef* UARTx,u8 dat)
* 函数功能:UART发送单字节数据
* 输入参数:UARTx:UART1/UART2;dat:待发送的数据
* 返回数值:无
*******************************************************************************/
void UART_SendByte(UART_TypeDef* UARTx,u8 dat)
{
    UART_SendData(UARTx, dat);

    while(!UART_GetFlagStatus(UARTx, UART_FLAG_TXEPT));
}

/*******************************************************************************
* 函数名称:void UART_SendBytes(UART_TypeDef* UARTx,u8* buf, u16 len)
* 函数功能:UART发送多字节数据
* 输入参数:UARTx:UART1/UART2;buf:待发送的数据;len:待发送数据的长度
* 返回数值:无
*******************************************************************************/
void UART_SendBytes(UART_TypeDef* UARTx,u8* buf, u16 len)
{
    while(len--)
    {
        UART_SendByte(UARTx,*buf++);       
    }
}

3、MM32F013x UART硬件 自适应波特率的功能演示

在main.c文件的main函数里初始化bsp_UART1_Init(9200)串口初始化函数,在while(1)大循环里调用测试UART1硬件自动波特率收发数据函数:UART1_Recv_Task();这里以检测UART1通信首字节为4bit宽为例,模式为前一个边沿为下降沿,后一个边沿为上升沿。

MCU端设置非标准波特率9200,我们通过上位机以不同的波特率发送F8进行硬件波特率自适应,自适应完成后UART1就切换到对应的波特率,MCU端收到数据后直接返回给上位机。


本文转自:灵动MM32MCU

围观 127

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称作UART) 是一种串行异步收发协议,应用十分广泛。UART工作原理是将数据的二进制位一位一位的进行传输。在UART通讯协议中信号线上的状态位高电平代表’1’低电平代表’0’。当然两个设备使用UART串口通讯时,必须先约定好传输速率和一些数据位。

硬件连接

硬件连接比较简单,仅需要3条线,注意连接时两个设备UART电平,如电平范围不一致请做电平转换后再连接,如下图所示:

  • TX:发送数据端,要接对面设备的RX
  • RX:接收数据端,要接对面设备的TX
  • GND:保证两设备共地,有统一的参考平面


软件通信协议

UART作为异步串口通信协议的一种,工作原理是将数据的字节一位接一位地传输。协议如下:


空闲位:
UART协议规定,当总线处于空闲状态时信号线的状态为‘1’即高电平

起始位:
开始进行数据传输时发送方要先发出一个低电平’0’来表示传输字符的开始。因为空闲位一直是高电平所以开始第一次通讯时先发送一个明显区别于空闲状态的信号即为低电平。

数据位:
起始位之后就是要传输的数据,数据可以是5,6,7,8,9位,构成一个字符,一般都是8位。先发送最低位最后发送最高位。

奇偶校验位:
数据位传送完成后,要进行奇偶校验,校验位其实是调整个数,串口校验分几种方式:
1.无校验(no parity)
2.奇校验(odd parity):如果数据位中’1’的数目是偶数,则校验位为’1’,如果’1’的数目是奇数,校验位为’0’。
3.偶校验(even parity):如果数据为中’1’的数目是偶数,则校验位为’0’,如果为奇数,校验位为’1’。
4.mark parity:校验位始终为1
5.space parity:校验位始终为0

停止位:
数据结束标志,可以是1位,1.5位,2位的高电平。

波特率:
数据传输速率使用波特率来表示,单位bps(bits per second),常见的波特率9600bps,115200bps等等,其他标准的波特率是1200,2400,4800,19200,38400,57600。举个例子,如果串口波特率设置为9600bps,那么传输一个比特需要的时间是1/9600≈104.2us。


以9600 8N1(9600波特率,8个数据位,没有校验位,1位停止位)为例,这是目前最常用的串口配置,现在我们传输’O’'K’两个ASCII值,'O’的ASCII为79,对应的二进制数据为01001111 ,'K’对应的二进制数据为01001011 ,传输的格式数据如下图所示:


串口波特率为9600,1bit传输时间大约为104us,传送一个数据实际是10个比特(开始位,8个数据位,停止位),一个bytes传输速率实际为9600*8/10=7680bps。

版权声明:本文为CSDN博主「sternlycore」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/sternlycore/article/details/85244515

围观 381

通讯问题,和交通问题一样,也有高速、低速、拥堵、中断等等各种情况。如果把串口通讯比做交通,UART比作车站,那么一帧的数据就好比汽车。汽车跑在路上,要遵守交通规则。如果是市内,一般限速30、40,而高速公路则可以到120。而汽车走什么路,限速多少,就要看协议怎么规定了。常见的串口协议有RS-232、RS-422、RS-485等,他们之间有何细微差别?下面我们就一起来探讨一下。

一、UART是什么

UART是通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART,是一种异步收发传输器,是设备间进行异步通信的关键模块。UART负责处理数据总线和串行口之间的串/并、并/串转换,并规定了帧格式;通信双方只要采用相同的帧格式和波特率,就能在未共享时钟信号的情况下,仅用两根信号线(Rx 和Tx)就可以完成通信过程,因此也称为异步串行通信。

若加入一个合适的电平转换器,如SP3232E、SP3485,UART 还能用于RS-232、RS-485 通信,或与计算机的端口连接。UART 应用非常广泛,手机、工业控制、PC 等应用中都要用到UART。

UART使用的是 异步,串行通信。

串行通信是指利用一条传输线将资料一位位地顺序传送。特点是通信线路简单,利用简单的线缆就可实现通信,降低成本,适用于远距离通信,但传输速度慢的应用场合。

异步通信以一个字符为传输单位,通信中两个字符间的时间间隔多少是不固定的,然而在同一个字符中的两个相邻位间的时间间隔是固定的。

数据传送速率用波特率来表示,即每秒钟传送的二进制位数。例如数据传送速率为120字符/秒,而每一个字符为10位(1个起始位,7个数据位,1个校验位,1个结束位),则其传送的波特率为10×120=1200字符/秒=1200波特。

数据通信格式如下图:

其中各位的意义如下:
起始位:先发出一个逻辑”0”信号,表示传输字符的开始。
数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。小端传输
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。
空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。

注:异步通信是按字符传输的,接收设备在收到起始信号之后只要在一个字符的传输时间内能和发送设备保持同步就能正确接收。下一个字符起始位的到来又使同步重新校准(依靠检测起始位来实现发送与接收方的时钟自同步的)

二、RS-232标准

RS-232是美国电子工业协会EIA(Electronic Industry Association)制定的一种串行物理接口标准。RS是英文“推荐标准”的缩写,232为标识号。RS-232是对电气特性以及物理特性的规定,只作用于数据的传输通路上,它并不内含对数据的处理方式。需要说明一下,很多人经常把RS-232、RS-422、RS-485 误称为通讯协议,这是很不应该的,其实它们仅是关于UART通讯的一个机械和电气接口标准(顶多是网络协议中的物理层面)。

该标准规定采用一个25 个脚的DB-25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。后来IBM的PC 机将RS-232 简化成了DB-9 连接器,从而成为今天的事实标准。而工业控制的RS-232 口一般只使用RXD(2)、TXD(3)、GND(5) 三条线。

早期由于PC都带有RS-232接口,所以我们需要使用UART时,都选择RS-232。但是现在个人电脑,不光是笔记本,包括台式机都不再带有RS-232的接口,大家看到电脑主板上面没有DB9的接口。所以现在开发板都选择TTL的UART,或者直接UART转USB做在开发板上。

嵌入式里面说的串口,一般是指UART口, 但是我们经常搞不清楚它和COM口的区别, 以及RS232, TTL等关系, 实际上UART,COM指的物理接口形式(硬件), 而TTL、RS-232是指的电平标准(电信号).

UART有4个pin(VCC, GND, RX, TX), 用的TTL电平, 低电平为0(0V),高电平为1(3.3V或以上)。

三、RS-485/ RS-422标准

RS-232接口可以实现点对点的通信方式,但这种方式不能实现联网功能。于是,为了解决这个问题,一个新的标准RS-485产生了。RS-485的数据信号采用差分传输方式,也称作平衡传输,它使用一对双绞线,将其中一线定义为A,另一线定义为B。

通常情况下,发送驱动器A、B之间的正电平在+2~+6V,是一个逻辑状态,负电平在-2~6V,是另一个逻辑状态。另有一个信号地C,在RS-485中还有一“使能”端,而在RS-422中这是可用可不用的。

RS-422 的电气性能与RS-485完全一样。主要的区别在于:RS-422 有4 根信号线:两根发送、两根接收。由于RS-422 的收与发是分开的所以可以同时收和发(全双工),也正因为全双工要求收发要有单独的信道,所以RS-422适用于两个站之间通信,星型网、环网,不可用于总线网;RS-485 只有2 根信号线,所以只能工作在半双工模式,常用于总线网。

1. RS-485的电气特性:逻辑“1”以两线间的电压差为+(2~6)V表示;逻辑“0”以两线间的电压差为-(2~6)V表示。接口信号电平比RS-232-C降低了,就不易损坏接口电路的芯片,且该电平与TTL电平兼容,可方便与TTL 电路连接。

2. RS-485的数据最高传输速率为10Mbps 。

3. RS-485接口是采用平衡驱动器和差分接收器的组合,抗共模干扰能力增强,即抗噪声干扰性好。

4. RS-485最大的通信距离约为1219M,最大传输速率为10Mb/S,传输速率与传输距离成反比,在100Kb/S的传输速率下,才可以达到最大的通信距离,如果需传输更长的距离,需要加485中继器。RS-485总线一般最大支持32个节点,如果使用特制的485芯片,可以达到128个或者256个节点,最大的可以支持到400个节点。

1 RS-423 非平衡串行通信接口

结构、信号电平、传输距离、传输速率、接口芯片

2 RS-422 平衡型串行通信接口

结构、信号电平、接口芯片,MC3486、MC3487、SN75154,SN75155

传输速率、传输距离

3 RS-485 串行通信总线

结构、信号电平、接口芯片 MAX485

传输速率、传输距离,应用实例

由于RS-232 接口标准出现较早,难免有不足之处,主要有以下四点:

(1) 接口的信号电平值较高,易损坏接口电路芯片,又因为232电平与TTL电平不兼容故需使用电平转换电路方能与TTL电路连接;

(2) 传输速率较低,在异步传输时,波特率为20Kbps。现在由于采用了新的UART芯片,波特率达到115.2Kbps(1.832M/16);

(3) 接口使用一根信号线和一根信号返回线而构成共地的传输形式,这种共地传输容易产生共模干扰,所以抗噪声干扰性弱;

(4) 传输距离有限,最大传输距离标准值为50 米,实际上也只能用在15 米左右;

(5) RS-232 只容许一对一的通信,没有考虑构成串行总线。(这点很重要,在很多控制场景,是一控多,如果主设备都需要跟从设备点对点通信,那现场布线成蜘蛛网了)

非平衡型串行通信接口RS-423,RS-449


平衡型串行通信接口RS-422

RS-422(EIA RS-422-A Standard)是Apple的Macintosh计算机的串口连接标准。RS-422使用差分信号,RS-232使用非平衡参考地的信号。差分传输使用两根线发送和接收信号,对比RS-232,它能更好的抗噪声和有更远的传输距离。在工业环境中更好的抗噪性和更远的传输距离是一个很大的优点。




四、RS-232与RS-485对比

1、抗干扰性:RS485 接口是采用平衡驱动器和差分接收器的组合,抗噪声干扰性好。RS232 接口使用一根信号线和一根信号返回线而构成共地的传输形式,这种共地传输容易产生共模干扰。

2、传输距离:RS485 接口的最大传输距离标准值为 1200 米(9600bps 时),实际上可达 3000 米。RS232 传输距离有限,最大传输距离标准值为 50 米,实际上也只能用在 15 米左右。

3、通信能力:RS-485 接口在总线上是允许连接多达128个收发器,用户可以利用单一的 RS-485 接口方便地建立起设备网络。RS-232只允许一对一通信。

4、传输速率:RS-232传输速率较低,在异步传输时,波特率为 20Kbps。RS-485 的数据最高传输速率为 10Mbps 。

5、信号线:RS485 接口组成的半双工网络,一般只需二根信号线。RS-232 口一般只使用 RXD、TXD、GND 三条线 。

6、电气电平值:RS-485的逻辑"1"以两线间的电压差为+(2-6) V 表示;逻辑"0"以两线间的电压差为-(2-6)V 表 示 。在 RS-232-C 中任何一条信号线的电压均为负逻辑关系。即:逻辑"1",-5- -15V;逻辑"0 " +5- +15V 。

五、RS-422与RS-485对比

RS-485的电气性能与RS-422完全一样。主要的区别在于:

1、RS-422 有4 根信号线:两根发送(Y、Z)、两根接收(A、B)。由于RS-422 的收与发是分开的所以可以同时收和发(全双工)。

2、RS-485 只有两根数据线:发送和接收都是A 和B。由于RS-485 的收与发是共用两根线,所以不能同时收和发(半双工)。

RS-485标准采用平衡式发送,差分式接收的数据收发器来驱动总线,具体规格要求:

接收器的输入电阻RIN≥12kΩ

驱动器能输出±7V的共模电压

输入端的电容≤50pF

在节点数为32个,配置了120Ω的终端电阻的情况下,驱动器至少还能输出电压1.5V(终端电阻的大小与所用双绞线的参数有关)

接收器的输入灵敏度为200mV(即(V+)-(V-)≥0.2V,表示信号“0”;(V+)-(V-)≤-0.2V,表示信号“1”)

因为RS-485的远距离、多节点(32个)以及传输线成本低的特性,使得EIA RS-485成为工业应用中数据传输的首选标准。

(1) RS-485 的电气特性:发送端:逻辑“0”以两线间的电压差+(2 ~6)V 表示;逻辑“1”以两线间的电压差-(2 ~6)V 表示。接收端:A 比B 高200mV 以上即认为是逻辑“0”,A 比B 低200mV 以上即认为是逻辑“1”;

(2) RS-485 的数据最高传输速率为10Mbps。但是由于RS-485 常常要与PC 机的RS-232 口通信,所以实际上一般最高115.2Kbps。又由于太高的速率会使RS-485 传输距离减小,所以往往为9600bps 左右或以下;

(3) RS-485 接口是采用平衡驱动器和差分接收器的组合,抗噪声干扰性好;

(4) RS-485 接口的最大传输距离标准为1200 米(9600bps 时),实际上可达3000米,RS-485 接口在总线上是容许连接多达128 个收发器、即RS-485 具有多机通信功能,这样用户可以利用单一的RS-485 接口方便的建立起网络。因为RS-485 接口组成的半双工网络,一般只需二根信号线,所以RS-485 接口均采用双绞线传输。RS-485 的国际标准并没有规定RS-485 的接口连接器标准、所以采用接线端子或者DB-9、DB-25 等连接器都可以。

在使用RS-485 接口时,对于特定的传输线径,从发生器到负载其数据信号传输所容许的最大电缆长度是数据信号速率的函数,这个长度数据主要是受信号失真及噪声等影响所限制。最大电缆长度与信号速率的关系曲线是使用24AWG 铜芯双绞电话电缆(线径为0.51mm),线间旁路电容为52.5PF/M,终端负载电阻为100 欧时所得出的。(引自GB11014-89 附录A)。当数据信号速率降低到90Kbit/S 以下时,假定最大容许的信号损失为6dBV 时,则电缆长度被限制在1200m。实际上,在实用时是完全可以取得比它大的电缆长度。当使用不同线径的电缆,则取得的最大电缆长度是不相同的。例如:当数据信号速率为600Kbit/S 时,采用24AWG 电缆,最大电缆长度是200m,若采用19AWG电缆(线径为0.91mm)则电缆长度将可以大于200m;若采用28AWG 电缆(线径为0.32mm),则电缆长度只能小于200m。

RS-485的远距离通信建议采用屏蔽电缆,并且将屏蔽层作为地线。

六、影响RS-485总线通讯速度和通信可靠性的三个因素

1、在通信电缆中的信号反射

在通信过程中,有两种信号因素导致信号反射:阻抗不连续和阻抗不匹配。

阻抗不连续,信号在传输线末端突然遇到电缆阻抗很小甚至没有,信号在这个地方就会引起反射,如图所示。这种信号反射的原理,与光从一种媒质进入另一种媒质要引起反射是相似的。消除这种反射的方法,就必须在电缆的末端跨接一个与电缆的特性阻抗同样大小的终端电阻,使电缆的阻抗连续。由于信号在电缆上的传输是双向的,因此,在通讯电缆的另一端可跨接一个同样大小的终端电阻。

从理论上分析,在传输电缆的末端只要跨接了与电缆特性阻抗相匹配的终端电阻,就再也不会出现信号反射现象。但是,在实现应用中,由于传输电缆的特性阻抗与通讯波特率等应用环境有关,特性阻抗不可能与终端电阻完全相等,因此或多或少的信号反射还会存在。

引起信号反射的另一个原因是数据收发器与传输电缆之间的阻抗不匹配。这种原因引起的反射,主要表现在通讯线路处在空闲方式时,整个网络数据混乱。

信号反射对数据传输的影响,归根结底是因为反射信号触发了接收器输入端的比较器,使接收器收到了错误的信号,导致CRC校验错误或整个数据帧错误。

在信号分析,衡量反射信号强度的参数是RAF(Refection AttenuationFactor反射衰减因子)。它的计算公式如式(1)。

RAF=20lg(Vref/Vinc) (1)

式中:Vref—反射信号的电压大小;Vinc—在电缆与收发器或终端电阻连接点的入射信号的电压大小。

具体的测量方法如图3所示。例如,由实验测得2.5MHz的入射信号正弦波的峰-峰值为+5V,反射信号的峰-峰值为+0.297V,则该通讯电缆在2.5MHz的通讯速率时,它的反射衰减因子为:

RAF=20lg(0.297/2.5)=-24.52dB

要减弱反射信号对通讯线路的影响,通常采用噪声抑制和加偏置电阻的方法。在实际应用中,对于比较小的反射信号,为简单方便,经常采用加偏置电阻的方法。在通讯线路中,如何通过加偏置电阻提高通讯可靠性的原理。

2、在通讯电缆中的信号衰减

第二个影响信号传输的因素是信号在电缆的传输过程中衰减。一条传输电缆可以把它看出由分布电容、分布电感和电阻联合组成的等效电路,如图所示。

电缆的分布电容C主要是由双绞线的两条平行导线产生。导线的电阻在这里对信号的影响很小,可以忽略不计。信号的损失主要是由于电缆的分布电容和分布电感组成的LC低通滤波器。PROFIBUS用的LAN标准型二芯电缆(西门子为DP总线选用的标准电缆),在不同波特率时的衰减系数如表1所示。

电缆的衰减系数

3、在通讯电缆中的纯阻负载

影响通讯性能的第三个因素是纯阻性负载(也叫直流负载)的大小。这里指的纯阻性负载主要由终端电阻、偏置电阻和RS-485收发器三者构成。

在叙述EIA RS-485规范时曾提到过RS-485驱动器在带了32个节点,配置了150Ω终端电阻的情况下,至少能输出1.5V的差分电压。一个接收器的输入电阻为12kΩ,整个网络的等效电路如图5所示。按这样计算,RS-485驱动器的负载能力为:

RL=32个输入电阻并联2个终端电阻=((12000/32)×(150/2))/(12000/32)+(150/2))≈51.7Ω

现在比较常用的RS-485驱动器有MAX485、DS3695、MAX1488/1489以及和利时公司使用的SN75176A/D等,其中有的RS-485驱动器负载能力可以达到20Ω。在不考虑其它诸多因素的情况下,按照驱动能力和负载的关系计算,一个驱动器可带节点的最大数量将远远大于32个。

在通讯波特率比较高的时候,在线路上偏置电阻是很有必要的。偏置电阻的连接方法如图6。它的作用是在线路进入空闲状态后,把总线上没有数据时(空闲方式)的电平拉离0电平,如图7。这样一来,即使线路中出现了比较小的反射信号或干扰,挂接在总线上的数据接收器也不会由于这些信号的到来而产生误动作。

通过下面后例子了,可以计算出偏置电阻的大小:

终端电阻Rt1=Rr2=120Ω;

假设反射信号最大的峰-峰值Vref≤0.3Vp-p,则负半周的电压Vref≤0.15V;终端的电阻上由反射信号引起的反射电流Iref≤0.15/(120||120)=2.5mA。一般RS-485收发器(包括SN75176)的滞后电压值(hysteresis value)为50mV,即:

(Ibias-Iref)×(Rt1||Rt2)≥50mV

于是可以计算出偏置电阻产生的偏置电流Ibias≥3.33mA

+5V=Ibias(R上拉+R下拉+(Rt1||Rt2)) (2)

通过式2可以计算出R上拉=R下拉=720Ω

在实际应用中,RS-485总线加偏置电阻有两种方法:

(1)把偏置电阻平衡分配给总线上的每一个收发器。这种方法给挂接在RS-485总线上的每一个收发器加了偏置电阻,给每一个收发器都加了一个偏置电压。

(2)在一段总线上只用一对偏置电阻。这种方法对总线上存在大的反射信号或干扰信号比较有效。值得注意的是偏置电阻的加入,增加了总线的负载。

七、RS-485总线的负载能力和通讯电缆长度之间的关系

在设计RS-485总线组成的网络配置(总线长度和带负载个数)时,应该考虑到三个参数:纯阻性负载、信号衰减和噪声容限。纯阻性负载、信号衰减这两个参数,在前面已经讨论过,现在要讨论的是噪声容限(Noise Margin)。RS-485总线接收器的噪声容限至少应该大于200mV。前面的论述者是在假设噪声容限为0的情况下进行的。在实际应用中,为了提高总线的抗干扰能力,总希望系统的噪声容限比EIA RS-485标准中规定的好一些。从下面的公式能看出总线带负载的多少和通讯电缆长度之间的关系:

Vend=0.8(Vdriver-Vloss-Vnoise-Vbias)(3)

其中:Vend为总线末端的信号电压,在标准测定时规定为0.2V;Vdriver为驱动器的输出电压(与负载数有关。负载数在5~35个之间,Vdriver=2.4V;当负载数小于5,Vdriver=2.5V;当负载数大于35,Vdriver≤2.3V);Vloss为信号在总线中的传输过程中的损耗(与通讯电缆的规格和长度有关),由表1提供的标准电缆的衰减系数,根据公式衰减系数b=20lg(Vout/Vin)可以计算出Vloss=Vin-Vout=0.6V(注:通讯波特率为9.6kbps,电缆长度1km,如果特率增加,Vloss会相应增大);Vnoise为噪声容限,在标准测定时规定为0.1V;Vbias是由偏置电阻提供的偏置电压(典型值为0.4V)。

式(3)中乘以0.8是为了使通信电缆不进入满载状态。从式(3)可以看出,Vdriver的大小和总线上带负载数的多少成反比,Vloss的大小和总线长度成反比,其他几个参数只和用的驱动器类型有关。因此,在选定了驱动器的RS-495总线上,在通信波特率一定的情况下,带负载数的多少,与信号能传输的最大距离是直接相关的。具体关系是:在总线允许的范围内,带负载数越多,信号能传输的距离就越小;带负载数据少,信号能传输的距离就发越远。

八、分布电容对RS-485总线传输性能的影响

电缆的分布电容主是由双绞线的两条平行导线产生。另外,导线和地之间也存在分布电容,虽然很小,但在分析时也不能忽视。分布电容对总线传输性能的影响,主要是因为总线上传输的是基波信号,信号的表达方式只有“1”和“0”。在特殊的字节中,例如0x01,信号“0”使得分布电容有足够的充电时间,而信号“1”到来时,由于分布电容中的电荷,来不及放电,(Vin+)—(Vin-)-还大于200mV,结果使接爱误认为是“0”,而最终导致CRC校验错误,整个数据帧传输错误。具体过程如图所示。

由于总线上分布影响,导致数据传输错误,从而使整个网络性能降低。解决这个问题有两种方法:

(1)降低数据传输的波特率;

(2)使用分布电容小的电缆,提高传输线的质量。

仅仅用一对双绞线将各个接口的A、B端连接起来,而不对RS-485通信链路的信号接地,在某些情况下也可以工作,但给系统埋下了隐患。RS-485接口采用差分方式传输信号并不需要对于某个参照点来检测信号系统,只需检测两线之间的电位差就可以了。但应该注意的是收发器只有在共模电压不超出一定范围(-7V至+12V)的条件下才能正常工作。当共模电压超出此范围,就会影响通信的可靠直至损坏接口。如图1所示,当发送器A向接收器B发送数据时,发送器A的输出共模电压为VOS,由于两个系统具有各自独立的接地系统存在着地电位差VGPD,那么接收器输入端的共模电压就会达到VCM=VOS+VGPD。RS-485标准规定VOS≤3V,但VGPD可能会有很大幅度(十几伏甚至数十伏),并可能伴有强干扰信号致使接收器共模输入VCM超出正常围,在信号线上产生干扰电流影响正常通信,或损坏设备。

总结:

串口是一种非常通用的设备接口,是仪器仪表设备常用的通信接口,常用于远程采集设备数据或者实现远程控制。串口的开发也比较简单,是很多工程师最喜欢的接口之一。

来源:硬件十万个为什么

围观 189

还记得当年的打印机,鼠标和调制解调器吗?他们都有巨大笨重的连接器和粗电缆,并且必须拧到你的电脑上。这些设备正是使用UART协议与计算机进行通信。虽然USB几乎完全取代了旧的电缆和连接器,但UART绝对没有过时。您会发现目前许多项目中使用UART的GPS模块、蓝牙模块和RFID读卡器模块等连接到Raspberry Pi,Arduino或其他微控制器上。

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART。它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。

它不是像SPI和I2C这样的通信协议,而是微控制器中独立的物理电路或独立的IC。

UART最好的一点是它只使用两根线就可以在设备之间传输数据,UART背后的原理很容易理解。

UART通信简介

在UART通信中,两个UART直接相互通信。发送UART将来自CPU等控制设备的并行数据转换为串行形式,并将其串行发送到接收UART,接收UART然后将串行数据转换回接收设备的并行数据。在两个UART之间传输数据只需要两根线。数据从发送UART的Tx引脚流向接收UART的Rx引脚:

UART以异步方式发送数据,这意味着没有时钟信号将发送UART的位输出与接收UART的位采样同步。发送UART不是时钟信号,而是将开始和停止位添加到正在传输的数据包中。这些位定义数据包的开始和结束,因此接收UART知道何时开始读取位。

当接收UART检测到起始位时,它开始以称为波特率的特定频率读取输入位。波特率是数据传输速度的度量,以每秒位数(bps)表示。两个UART必须以大致相同的波特率运行。发送和接收UART之间的波特率只能相差10%左右。

两个UART还必须配置为发送和接收相同的数据包结构。

UART如何工作

UART传输数据依靠的是UART总线,数据总线用于通过CPU,存储器或微控制器等其他设备将数据发送到UART。数据以并行形式从数据总线传输到发送UART。在发送UART从数据总线获得并行数据之后,它将添加起始位,奇偶校验位和停止位,从而创建数据包。接下来,数据包在Tx引脚上逐位串行输出。UART接收端则在其Rx引脚上逐位读取数据包。然后,接收UART将数据转换回并行形式,并删除起始位,奇偶校验位和停止位。最后,接收UART将数据包并行传输到接收端的数据总线:

UART传输的数据被组织成数据包。每个数据包包含1个起始位,5到9个数据位(取决于UART),可选的奇偶校验位以及1或2个停止位:

开始位
当UART数据传输线不传输数据时,它通常保持在高电压电平。为了开始数据传输,发送UART将传输线从高电平拉至低电平一个时钟周期。当接收UART检测到高电压到低电压转换时,它开始以波特率的频率读取数据帧中的位。

数据框
数据框包含要传输的实际数据。如果使用奇偶校验位,则它可以是5位到8位长。如果不使用奇偶校验位,则数据帧可以是9位长。在大多数情况下,数据首先以最低有效位发送。

奇偶校验位
奇偶校验描述数字的均匀性或奇数。奇偶校验位是接收UART在传输过程中判断是否有任何数据发生变化的一种方法。电磁辐射、不匹配的波特率或长距离传输时,数据都有可能发生变化。接收UART读取数据帧后,它会计算值为1的位数,并检查总数是偶数还是奇数。如果奇偶校验位为0(偶校验),则数据帧中的1位应总计为偶数。如果奇偶校验位是1(奇校验),则数据帧中的1位应总计为奇数。当奇偶校验位与数据匹配时,UART知道传输没有错误。但如果奇偶校验位为0,然而1位应总计为奇数;或者奇偶校验位是1,并且1位应总计是偶数,则数据帧中的位已经改变。

停止位
为了通知传输数据包的结束,UART发送端会将数据传输线从低电压驱动至高电压至少两位持续时间。

UART传输步骤

1.发送UART从数据总线并行接收数据:

2.发送UART将起始位,奇偶校验位和停止位添加到数据帧:

3.整个数据包从发送UART串行发送到接收UART。接收UART以预先配置的波特率对数据线进行采样:

4.接收UART丢弃数据帧中的起始位,奇偶校验位和停止位:

5.接收UART将串行数据转换回并行并将其传输到接收端的数据总线:

UART的优点和缺点

没有任何一种通信协议是完美的,以下是一些优点和缺点,可帮助您确定它们是否符合您项目的需求:

优点
只使用两根电线
不需要时钟信号
有一个奇偶校验位
只要双方设置后,就可以改变数据包的结构
有完整的文档并且具有广泛的使用

缺点
数据帧的大小限制为最多9位
不支持多个从属或多个主系统
每个UART的波特率必须在10%之内

来源:EDN电子技术设计

围观 248

页面

订阅 RSS - UART