串口通信

什么是串口通讯?

串行通讯是指仅用一根接收线和一根发送线就能将数据以位进行传输的一种通讯方式。尽管串行通讯的比按字节传输的并行通信慢,但是串口可以在仅仅使用两根线的情况下就能实现数据的传输。

典型的串口通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,所以端口能够在一根线上发送数据同时在另一根线上接收数据。串口通信最重要的参数是波特率、数据位、停止位和奇偶的校验。对于两个需要进行串口通信的端口,这些参数必须匹配,这也是能够实现串口通讯的前提。如下是串行通讯示数据传输意图。

1.png

串口通讯的通讯协议

最初数据是模拟信号输出简单过程量,后来仪表接口出现了RS232接口,这种接口可以实现点对点的通信方式,但这种方式不能实现联网功能,这就促生了RS485。

我们知道串口通信的数据传输都是0和1,在单总线、I2C、UART中都是通过一根线的高低电平来判断逻辑1或者逻辑0,但这种信号线的GND再与其他设备形成共地模式的通信,这种共地模式传输容易产生干扰,并且抗干扰性能也比较弱。所以差分通信、支持多机通信、抗干扰强的RS485就被广泛的使用了。

RS485通信最大特点就是传输速度可以达到10Mb/s以上,传输距离可以达到3000米左右。大家需要注意的是虽然485最大速度和最大传输距离都很大,但是传输的速度是会随距离的增加而变慢的,所以两者是不可以兼得的。

串口通讯的物理层

串口通讯的物理层有很多标准,例如上面提到的,我们主要讲解RS-232标准,RS-232标准主要规定了信号的用途、通讯接口以及信号的电平标准。

2.png

在上面的通讯方式中,两个通讯设备的"DB9接口"之间通过串口信号线建立起连接,串口信号线中使用"RS-232标准"传输数据信号。由于RS-232电平标准的信号不能直接被控制器直接识别,所以这些信号会经过一个"电平转换芯片"转换成控制器能识别的"TTL校准"的电平信号,才能实现通讯。

下图为DB9标准串口通讯接口:

3.png

DB9引脚说明:

4.png

上表中的是计算机端的DB9公头标准接法,由于两个通讯设备之间的收发信号(RXD与TXD)应交叉相连,所以调制调解器端的DB9母头的收发信号接法一般与公头的相反,两个设备之间连接时,只要使用"直通型"的串口线连接起来即可。

5.png

串口线中的RTS、CTS、DSR、DTR及DCD信号,使用逻辑 1表示信号有效,逻辑0表示信号无效。例如,当计算机端控制DTR信号线表示为逻辑1时,它是为了告知远端的调制调解器,本机已准备好接收数据,0则表示还没准备就绪。

波特率

波特率是指数据信号对载波的调制速率,它用单位时间内载波调制状态改变的次数来表示。

6.png

比如波特率为9600bps;代表的就是每秒中传输9600bit,也就是相当于每一秒中划分成了9600等份。

因此,那么每1bit的时间就是1/9600秒=104.1666...us。约0.1ms。既然是9600等份,即每1bit紧接着下一个比特,不存在额外的间隔。两台设备要想实现串口通讯,这收发端设置的波特率必须相同,否则是没办法实现通讯的:动图演示常用通信协议原理。

收发波特率一致可以实现通讯:

7.gif

收发波特率不一致,导致RX端不能正常接收:

8.gif

串口通讯的数据结构

9.png

起始位:起始位必须是持续一个比特时间的逻辑0电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步。

数据位:数据位紧跟在起始位之后,是通信中的真正有效信息。数据位的位数可以由通信双方共同约定。传输数据时先传送字符的低位,后传送字符的高位。

奇偶校验位:奇偶校验位仅占一位,用于进行奇校验或偶校验,奇偶检验位不是必须有的。如果是奇校验,需要保证传输的数据总共有奇数个逻辑高位;如果是偶校验,需要保证传输的数据总共有偶数个逻辑高位。

停止位:停止位可以是是1位、1.5位或2位,可以由软件设定。它一定是逻辑1电平,标志着传输一个字符的结束。

空闲位:空闲位是指从一个字符的停止位结束到下一个字符的起始位开始,表示线路处于空闲状态,必须由高电平来填充。

单双工通讯

单工: 数据传输只支持数据在一个方向上传输;

半双工: 允许数据在两个方向上传输,但某一时刻只允许数据在一个方向上传输,实际上是一种切换方向的单工通信,不需要独立的接收端和发送端,两者可合并为一个端口;

全双工: 允许数据同时在两个方向上传输,因此全双工通信是两个单工方式的结合,需要独立的接收端和发送端。

10.png

STM32中的串口通讯

STM32串口通信接口有两种,分别是:UART(通用异步收发器)、USART(通用同步异步收发器),对于大容量STM32F10x系列芯片,分别由3个USART和两个UART。

11.png

TXD:数据发送引脚;

RXD:数据输入引脚

对于两芯片的间的连接,两个芯片GND共地,同时TXD和RXD交叉连接,这样两个芯片间可进行TTL电平通信。

但如果对于芯片和PC机相连,除了共地条件外,不能使用如上的直接交叉连接,虽然两者都有TXD和RXD引脚,但通常PC机使用的是RS232接口(9针),通常是TXC和RXD经过电平转换得到,故如果要使芯片与PC机的RS232接口直接通信,需要将芯片的输入输出端口也电平转换为RS232类型,再交叉连接。

二者的电平标准不同:

  • 单片机的点评标准(TTL电平):+5V表示1,0V表示0;

  • RS232电平标准:+15/+13V表示0,-15/-13表示1。

12.png

因此单片机与PC机进行串口通信应该遵循:在单片机串口与上位机给出的RS232口之间,通过电平转换电路实现TTL电平与RS232电平间的转换。如果使用USB转串口也可以实现串口通讯,USB转串口电路图如下所示。

13.png

STM32中串口通讯已经给大家建好了相应的库函数,大家在使用和配置串口的时候直接进行调用库函数和配置就行了

请大家参照一下代码:

1、初始化结构体代码

typedef struct 
{ 
    uint32_t USART_BaudRate; // 波特率 
    uint16_t USART_WordLength; // 字长 
    uint16_t USART_StopBits; // 停止位 
    uint16_t USART_Parity; // 校验位 
    uint16_t USART_Mode; // USART 模式 
    uint16_t USART_HardwareFlowControl; // 硬件流控制 
} USART_InitTypeDef;

2、NVIC配置中断优先级

NVIC_Configuration(void)
{  
    NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 嵌套向量中断控制器组选择 */  
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  /* 配置USART为中断源 */  
  NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;  
  /* 抢断优先级*/  
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 
  /* 子优先级 */  
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  
  /* 使能中断 */  
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
  /* 初始化配置NVIC */  
  NVIC_Init(&NVIC_InitStructure);
}

3、USART配置函数

void DEBUG_USART_Config(void)
{ 
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    /* 第一步:初始化GPIO */
    // 打开串口GPIO的时钟 
    DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
    // 将USART Tx的GPIO配置为推挽复用模式 
    GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
    GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
    
    // 将USART Rx的GPIO配置为浮空输入模式 
    GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
    GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure); 
    
    /* 第二步:配置串口的初始化结构体 */
    // 打开串口外设的时钟 
    DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
    // 配置串口的工作参数
    // 配置波特率 
    USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
    // 配置 针数据字长 
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    // 配置停止位 
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    // 配置校验位 
    USART_InitStructure.USART_Parity = USART_Parity_No ;
    // 配置硬件流控制 
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    // 配置工作模式,收发一起 
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    // 完成串口的初始化配置
     USART_Init(DEBUG_USARTx, &USART_InitStructure);
    
    /*--------------------------------------------------------*/
    // 串口中断优先级配置 
    NVIC_Configuration();
    
    // 使能串口接收中断 
    USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
    /*--------------------------------------------------------*/
    
    /* 第三步:使能串口 */
    // 使能串口 
    USART_Cmd(DEBUG_USARTx, ENABLE); 
}

来源:STM32嵌入式开发

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 85

串口通讯 (Serial Communication) 是一种设备间非常常用的串行通讯方式,因为它简单便捷,大部分电子设备都支持该通讯方式。串口在CKS32上应用最多的莫过于“打印”程序信息,一般在硬件设计时都会预留一个串口连接电脑,用于在调试程序时可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、指出程序运行出错位置等等。

CKS32F4xx系列产品串口介绍

CKS32F4xx系列最多可提供6路串口,其中四个USART和两个UART。USART和UART在引脚上的区别是:UART只有RX和TX引脚,而USART除了这两个引脚之外,还有流控引脚RTS和CTS,以及时钟引脚SCLK。CKS32F4xx系列产品的USART1和USART6时钟来源于APB2总线时钟,其最大频率为84MHz,因此这两个串口的通信速度最高可达10.5Mbit/s。而其它四个的时钟来源于APB1总线时钟,其最大频率为42MHz,因此这四个串口的通信速度最高可达5.25Mbit/s。因为USART有SCLK引脚,因此CKS32F4xx系列产品的USART具有同步通信功能,而UART只有异步通信功能。同时USART还支持ISO7816的智能卡接口。但是当USART和UART都用在异步通信的时候,两者是没有什么区别的。CKS32F4xx系列的6个串口都支持DMA传输。

CKS32F4xx系列产品的串口在发送数据时,当发送使能位TE置1之后,发送器开始会先发送一个空闲帧(一个数据帧长度的高电平),然后就可以往USART_DR寄存器写入要发送的数据。在写入最后一个数据后,需要等USART状态寄存器(USART_SR)的TC位为1,表示数据传输完成,如果USART_CR1寄存器的TCIE位置1,将产生中断。串口发送的一个字符帧由三个部分组成:起始位+数据帧+停止位。起始位是一个位周期的低电平;数据帧就是我们要发送的8位或9位数据,数据是从最低位开始传输的;停止位是一定时间周期的高电平。停止位时间长短是可以通过USART控制寄存器2(USART_CR2)的STOP[1:0]位控制,可选0.5个、1个、1.5个和2个停止位。默认使用1个停止位。2个停止位适用于正常USART模式、单线模式和调制解调器模式。0.5个和1.5个停止位用于智能卡模式。

CKS32F4xx系列产品的串口在接收数据时,需要先将USART_CR1寄存器的RE 位置1,使能USART接收,使得接收器在RX线开始搜索起始位。在确定到起始位后就根据RX线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到RDR内,并把USART_SR寄存器的RXNE位置1,同时如果 USART_CR2寄存器的RXNEIE置1的话可以产生中断。

CKS32F4xx系列产品控制器的USART支持奇偶校验。当使用校验位时,串口传输的长度将是8位的数据帧加上1位的校验位总共9位,奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收数据时如果出现奇偶校验位验证失败,则可以产生奇偶校验中断。使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧 +校验位+停止位。

USART有多个中断请求事件,具体如下表所示:在串口的中断服务函数里,通过对这些中断事件标志的检测,就可以判断出是何种事件发生,然后再做出相应的处理。

1.jpg

CKS32F4xx系列产品串口的配置

接下来我们讲解如何利用CKS32F4xx系列固件库来完成对串口的配置使用。首先标准库函数定义了一个串口初始化结构体USART_InitTypeDef,结构体成员用于设置串口的工作参数,并由外设初始化配置函数USART_Init()调用,从而完成对串口相应寄存器的配置,进一步达到完成对串口配置的目的。

typedef struct 
{ 
    uint32_t USART_BaudRate;   // 波特率 
    uint16_t USART_WordLength; // 字长 
    uint16_t USART_StopBits;    // 停止位 
    uint16_t USART_Parity;      // 校验位 
    uint16_t USART_Mode;      // USART 模式 
    uint16_t USART_HardwareFlowControl; // 硬件流控制 
} USART_InitTypeDef;

结构体中各个成员变量的介绍及初始化时可被赋的值如下:

1) USART_BaudRate:波特率设置。一般设置为 2400、9600、19200、115200。标准库函数会根据设定值计算得到USARTDIV值,并设置USART_BRR寄存器值。

2) USART_WordLength:数据帧字长,可选8位或9位。它设定USART_CR1 寄存器的M位的值。如果没有使能奇偶校验控制,一般使用8位数据帧长;如果使能了奇偶校验则一般设置为9位数据帧长。

#define USART_WordLength_8b                  ((uint16_t)0x0000)
#define USART_WordLength_9b                  ((uint16_t)0x1000)

3) USART_StopBits: 停止位设置,可选0.5个、1个、1.5个和 2个停止位,它设定USART_CR2寄存器的STOP[1:0]位的值,一般我们选择1个停止位。

#define USART_StopBits_1                     ((uint16_t)0x0000)
#define USART_StopBits_0_5                   ((uint16_t)0x1000)
#define USART_StopBits_2                     ((uint16_t)0x2000)
#define USART_StopBits_1_5                   ((uint16_t)0x3000)

4) USART_Parity: 奇偶校验控制选择,可选USART_Parity_No(无校验)、USART_Parity_Even(偶校验)以及USART_Parity_Odd(奇校验),它设定 USART_CR1寄存器的PCE位和PS位的值。

#define USART_Parity_No                      ((uint16_t)0x0000)
#define USART_Parity_Even                    ((uint16_t)0x0400)
#define USART_Parity_Odd                     ((uint16_t)0x0600)

5) USART_Mode: USART模式选择,有USART_Mode_Rx和USART_Mode_Tx,允许使用逻辑或运算选择两个,它设定USART_CR1寄存器的RE位和TE位。

#define USART_Mode_Rx                        ((uint16_t)0x0004)
#define USART_Mode_Tx                        ((uint16_t)0x0008)

6) USART_HardwareFlowControl: 硬件流控制选择,只有在硬件流控制模式才有效,可选使能RTS、使能CTS、同时使能RTS和CTS、不使能硬件流。

#define USART_HardwareFlowControl_None       ((uint16_t)0x0000)
#define USART_HardwareFlowControl_RTS        ((uint16_t)0x0100)
#define USART_HardwareFlowControl_CTS        ((uint16_t)0x0200)
#define USART_HardwareFlowControl_RTS_CTS    ((uint16_t)0x0300)

当使用同步模式时需要配置SCLK引脚输出脉冲的属性,标准库使用一个时钟初始化结构体USART_ClockInitTypeDef来设置,不使用时不需要设置。

typedef struct 
{ 
    uint16_t USART_Clock; // 时钟使能控制 
    uint16_t USART_CPOL; // 时钟极性 
    uint16_t USART_CPHA; // 时钟相位 
    uint16_t USART_LastBit; // 最尾位时钟脉冲 
} USART_ClockInitTypeDef;

结构体中各个成员变量的介绍及初始化时可被赋的值如下:

1) USART_Clock: 同步模式下SCLK引脚上时钟输出使能控制,可选禁止时钟输出(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模式发送,一般都需要开启时钟。它设定USART_CR2寄存器的CLKEN位的值。

#define USART_Clock_Disable                  ((uint16_t)0x0000)
#define USART_Clock_Enable                   ((uint16_t)0x0800)

2) USART_CPOL: 同步模式下SCLK引脚上输出时钟极性设置,可设置在空闲时SCLK引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定USART_CR2寄存器的CPOL位的值。

#define USART_CPOL_Low                       ((uint16_t)0x0000)
#define USART_CPOL_High                      ((uint16_t)0x0400)

3) USART_CPHA: 同步模式下SCLK引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定USART_CR2寄存器的CPHA位的值。USART_CPHA与USART_CPOL配合使用可以获得多种模式时钟关系。

#define USART_CPHA_1Edge                     ((uint16_t)0x0000)
#define USART_CPHA_2Edge                     ((uint16_t)0x0200)

4) USART_LastBit: 选择在发送最后一个数据位的时候时钟脉冲是否在 SCLK引脚输出,可以是不输出脉冲(USART_LastBit_Disable)、输出脉冲 (USART_LastBit_Enable)。它设定USART_CR2寄存器的LBCL位的值。

#define USART_LastBit_Disable                ((uint16_t)0x0000)
#define USART_LastBit_Enable                 ((uint16_t)0x0100)

要完成串口正常的收发数据,还需要标准库中的这些函数配合使用。

(1) void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)函数:

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
{  
    assert_param(IS_USART_ALL_PERIPH(USARTx));  
    assert_param(IS_USART_DATA(Data));   
    USARTx->DR = (Data & (uint16_t)0x01FF);
}

该函数的功能是向串口寄存器USART_DR写入一个数据,有两个入口参数,第一个是选择是哪个串口,其可选择的值为USART1、USART2、USART3、USART6、UART4、UART5。第二个参数是待发送的数据,其值只要满足如下条件即可:

#define IS_USART_DATA(DATA) ((DATA) <= 0x1FF)

(2) uint16_t USART_ReceiveData(USART_TypeDef* USARTx)函数:

uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
{  
    assert_param(IS_USART_ALL_PERIPH(USARTx));  
    return (uint16_t)(USARTx->DR & (uint16_t)0x01FF);
}

该函数的功能是从USART_DR寄存器读取串口接收到的数据,只有一个入口参数,即选择是哪个串口。

(3) void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)函数:

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
{  
    assert_param(IS_USART_ALL_PERIPH(USARTx));  
    assert_param(IS_FUNCTIONAL_STATE(NewState));  
    if (NewState != DISABLE)  
    {    
        USARTx->CR1 |= USART_CR1_UE;  
    }  
    else  
    {  
        USARTx->CR1 &= (uint16_t)~((uint16_t)USART_CR1_UE);  
    }
}

要完成串口正常的收发数据,还需要标准库中的这些函数配合使用。

该函数的功能是使能串口。有两个入口参数,第一个是选择是哪个串口,其可选择的值为USART1、USART2、USART3、USART6、UART4、UART5。第二个参数是使能或者不使能,其值为DISABLE或者ENABLE。

(4) void USART3_IRQHandler(void) 串口中断服务程序函数:

当发生中断的时候,程序就会执行中断服务函数。然后我们在中断服务函数中编写我们相应的逻辑代码即可。

(5) FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG)函数:

该函数的功能是读取串口的状态,第一个入口参数和上面的一样。这里重点讲解第二个入口参数,它是标示我们要查看串口的哪种状态,可选的值及其代表的意义如表格所示:

2.jpg

(6) ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)函数:

当我们使能了某个中断的时候,当该中断发生了,就会设置状态寄存器中的某个标志位。经常我们在中断处理函数中,要判断该中断是哪种中断,这时候就会使用该函数。第二个入口参数可选的值及其代表的意义如表格所示:

3.jpg

串口接发通信实验

接下来我们根据上面讲解的串口通信的知识,实际编写一个软件程序实现串口的接发通信。代码实现的现象是在开发板一上电时会通过print函数发送一串字符串“start”给电脑,然后开发板进入中断接收等待状态。如果电脑有发送数据过来,开发板就会产生中断, 我们在中断服务函数里接收数据,并将接收到数据标志位置1,在主函数里对标志位经过判断之后再把数据返回发送给电脑。

1.编程要点

1) 使能RX和TX引脚GPIO时钟和USART3时钟;

2) 初始化GPIO,并将GPIO复用到USART3上;

3) 配置USART3参数;

4) 配置中断控制器并使能USART3接收中断;

5) 使能USART3;

6) 在USART3接收中断服务函数里接收数据并将接收到数据的标志位置1。

2.代码分析

代码清单1:USART3初始化配置

其初始化串口的过程和我们前面讲解的编程要点中的过程是一致的。因为我们使用到了串口的中断接收,因此需要开启串口3的NVIC中断并对其进行配置。

void uart_init(u32 bound)
{    
    GPIO_InitTypeDef GPIO_InitStructure;  
    USART_InitTypeDef USART_InitStructure;  
    NVIC_InitTypeDef NVIC_InitStructure;
    //使能RX和TX引脚GPIO时钟  
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    //使能USART3时钟  
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);  
    //初始化GPIO,并将GPIO复用到USART3上  
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource10,GPIO_AF_USART3);   
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource11,GPIO_AF_USART3);   
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;   
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;   
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;   
    GPIO_Init(GPIOB,&GPIO_InitStructure);    
    //配置USART3参数  
    USART_InitStructure.USART_BaudRate = bound;    //波特率设置  
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;   
    USART_InitStructure.USART_StopBits = USART_StopBits_1;   
    USART_InitStructure.USART_Parity = USART_Parity_No;   
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;   
    USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx;      
    USART_Init(USART3, &USART_InitStructure); 
    //使能USART3    
    USART_Cmd(USART3, ENABLE); 
    //配置中断控制器并使能USART3接收中断;  
    #if EN_USART3_RX    
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);   
    NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;   
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;    
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;      
    NVIC_Init(&NVIC_InitStructure);  
    #endif  
}

代码清单2:USART3中断服务函数

当USART3有接收到数据时就会执行USART3_IRQHandler函数。然使用if 语句来判断是否是真的产生USART3数据接收这个中断事件,如果是真的就使用 USART数据读取函数USART_ReceiveData读取数据到指定存储区Res,并将自己定义的一个标志位Rxflag置1。

void USART3_IRQHandler(void)                {  if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)   {    Res =USART_ReceiveData(USART3);//(USART1->DR);      Rxflag=1;           } }

代码清单3:字符函数

//发送一个字符函数
static void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch )
{  
    USART_SendData(pUSARTx,ch);  
    while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);  
}
//发送指定长度字符的函数
void Usart_SendStr_length( USART_TypeDef * pUSARTx, uint8_t *str,uint32_t strlen )
{  
    unsigned int k=0;    
    do     
    {        
        Usart_SendByte( pUSARTx, *(str + k) );        
        k++;    
    } while(k < strlen);
}

Usart_SendByte函数用来在指定USART发送一个ASCLL码值字符,它有两个形参,第一个为USART,第二个为待发送的字符。它是通过调用库函数 USART_SendData来实现的,并且增加了等待发送完成功能。

Usart_SendString函数用来发送一个字符串,它实际是调用 Usart_SendByte函数发送每个字符,直到遇到空字符才停止发送。最后使用循环检测发送完成的事件标志来实现保证数据发送完成后才退出函数。

代码清单4:printf函数支持

#if 1
#pragma import(__use_no_semihosting)                             
struct __FILE 
{   
    int handle; 
}; 
FILE __stdout;         
void _sys_exit(int x) 
{   
    x = x; 
} 
int fputc(int ch, FILE *f)
{     
    while((USART3->SR&0X40)==0);//循环发送,直到发送完毕     
    USART3->DR = (u8) ch;        
    return ch;
}
#endif

这段代码是引入printf函数支持所必须的,加入这段代码加入之后便可以通过printf函数向串口发送我们需要的内容,方便开发过程中查看代码执行情况以及一些变量值。如果我们使用不同的串口,对这段代码的修改一般也只是用来改变 printf 函数针对的串口号,比如将上述代码中的USART3改成USART1即可。

代码清单5:主函数

u8 Res;u8 Rxflag;
u8 USART_RX_BUF[USART_REC_LEN];  
u8 usRxCount=0; int main(void)
{   
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  
    delay_init(168);       
    uart_init(115200);     
    printf("start\r\n");  
    while(1)  
    {    
        if(Rxflag)    
        {      
            if (usRxCount < sizeof(USART_RX_BUF))      
            {        
                USART_RX_BUF[usRxCount++] = Res;      
            }      
            else      
            {        
                usRxCount = 0;      
            }      
            /* 遇到换行字符,就把数据发送到串口助手*/    
            if (Res == 0x0A)  /* 换行字符 */    
            {         
                Usart_SendStr_length(USART3,USART_RX_BUF, usRxCount );        
                usRxCount = 0;      
            }      Rxflag=0;    
        }  
    }
}

首先我们调用NVIC_PriorityGroupConfig函数完成对NVIC的初始化,然后调用uart_init函数完成对串口的初始化,这里将串口波特率设置成可115200(位/秒)。接着利用printf函数发送一次“start”到串口调试助手。然后对Rxflag的值进行判断,当接收到了数据,即Rxflag的值为1时,对接收的数据长度进行判断,USART_REC_LEN是我们定义的接收最大字节数,这个值可以根据自己的需要进行修改。当接收的数据在最大字节数范围之内时,把接收到的数据赋值到数组USART_RX_BUF里,同时当接收到的数据为0x0A,即换行字符时,利用Usart_SendStr_length函数将接收到的数据发送出去。因此在利用串口调试助手向MCU发送数据时,要勾选“加回车换行符”。

在本程序中我们设置串口进入中断的方式为数据寄存器非空即进一次中断,因此每个字节的接收都会进一次中断,这会导致CPU的效率大大降低,因此在下一节我们将会讲解利用DMA的方式对串口的数据进行发送和接收。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 23

来源:公众号【鱼鹰谈单片机】

作者:鱼鹰Osprey

ID :emOsprey

工作过程中,总是会遇到各种各样的通信问题,除了掌握软件知识,必要的硬件技能也必不可少,比如万用表、示波器、逻辑分析仪等,如此才能做到精准定位,早点打卡下班~~

鱼鹰根据个人多年的嵌入式开发经验,在此斗胆总结一番,希望可以给一些新人提供排查方向。

在此,以串口通信为例,介绍排查步骤或方向:

“红色代表可能出错的位置"
红色代表可能出错的位置

1、示波器看波形

通信,不管是单向通信还是双向通信,必然存在两个器件,所以我们需要重点关注这两个,而两者之间必然存在物理连接--导线(无线除外),遇到通信问题,应该首先保证导线连接正常、电压正常,如果这一点都没有确定,直接跳过该步骤,很大可能做无用功,查来查去,最终可能查了一个寂寞。

串口双向通信,一般会设计成主从方式,即一个主器件通过双方约定好的协议主动向从机发起数据传输,并且从机永远是被动应答。这样保证在多从机通信的情况下,不会出现数据错乱的情况(如果多个从机同时发送数据,可能造成交通拥堵,毕竟一个方向只有一条道)。

这种情况下,可以让主机定时发送固定数据帧(比如版本查询,这样可以减少变量),通过查看示波器来确定从机是否有返回数据。

如此,我们可以确定两个问题:

1、观察主机发送引脚波形是否正常(串口平时一般为高电平,发数据时才会变化)、同时需要确定电压是否正常、波特率可看可不看、具体传输数据也是,因为该阶段只是从大的方向进行排查;

2、从机是否有回应。

这里又分为两种情况:

1)从机没有回应:

此时我们需要在接下来的排查步骤中确定几个问题:

<1>从机发送功能是否正常?

<2>主机发送的数据从机是否已经正常接收?

<3>主机发送的数据协议是否正确。

2)从机有回应。

这种情况下,问题就比较简单,重点排查上层协议即可(可以保证从机的收、发没有问题)。

这里特别注意的是,波形测量位置一定是在最终点,而不是中间某个探测点或者模拟开关之类的器件。

比如,主机发送引脚,测量位置应该在从机芯片的接收引脚,而测量主机的接收则在主机芯片的接收引脚,才不会导致结果误判。

2、根据波形情况,确定主从器件发送、接收功能

如果说步骤 1 发现主机没有正常传输波形产生,就要根据情况再确定一些问题。

1、如果发现波形失真、变形、电压不正常等情况,请呼叫硬件工程师一起排查。

2、主机芯片发送引脚可以看到波形,但从机接收引脚看不到,请使用万用表确定是否虚焊接。相反方向也排查一遍。

3、主机或从机不能正常发送或接收。

如果你使用了 DMA 发送,又使用了《终极串口接收方式,极致效率》笔记介绍的方式接收,那么你可以从以下方向进行排查(其他更简单的方式类似):

1)发送、接收引脚时钟是否开启、输入输出模式是否配置正常,发送引脚一般复用输出、接收一般上拉输入(如果只是引脚配置错误,发送 DMA的 计数器会变化,但是没有实际波形输出);

2)串口外设寄存器配置是否正常。如果全是 0 ,说时钟没有打开,或者未调用初始化函数,其次查看对应的 DMA 请求位(发送、接收,如果这个没有开启,DMA 计数器不会变化)、串口使能位等;波特率可以等发出波形再看,毕竟你现在连数据都没有。

3)查看 DMA 配置是否正常,外设、内存地址寄存器、计数器、使能位、对应通道的传输标志位(F4 如果传输标志不清除,无法启动下一次传输),总之把对应结构体的成员看一遍就对了,可能一个小配置错了,就导致整个传输失败。

4)如果是特殊引脚,看是否需要关闭默认功能,开启普通 GPIO 功能

5)如果是复映射功能,查看对应的映射寄存器是否配置正确,配置时,可能需要开启对应的时钟。

以上排查方法对发送、接收都适合。排查后,可以通过短接 Tx、Rx 引脚的方式确定发送、接收是否异常。如果是单方向的,可以配合串口模块测试。

终极杀招:

如果上述排查都没有发现问题,还是失败,要么换板子,要么换一个可以用的简单例程修改后测试,此时可以对比例程调试模式下的寄存器配置(寄存器配置截图后对比)。

另外, 排查时需要特别注意的是,不能只看代码,不看实际寄存器的值。因为有的时候,代码也可能有各种各样的问题(比如时钟如果没有打开,那么即使有对应的配置代码,你一个寄存器值也写不进去),只有看最终寄存器的值才最安心。这里也多次强调了时钟的重要性,不过一般这种问题很容易排查。

以上排查方向,配合鱼鹰的调试系列文章,消化好!

3、协议问题

上述排查,应该基本解决了双方通信问题的,即自发自收应该没有问题。但不代表双方就能够正常通信了。

这里可能存在几个问题:

1、双方波特率设置不匹配(这个问题比较容易查,直接看波形或寄存器(寄存器可能还没那么靠谱,外设一致可以)即可)

2、上层协议不匹配,比如 CRC 校验有问题、帧头定义有误等等……

3、对方解析函数有问题。

这个时候,配合逻辑分析仪分析是最方便的,到底是从机问题还是主机问题一目了然,当然你直接在Tx、Rx上并上两个串口模块的 Rx 去接收串口数据也是可以的~~

以上排查步骤不一定按顺序排查,但一般查看波形都是优先选择。

4、其他通信

在此继续介绍一下 SPI 通信可能出现的问题:

1、SPI 发送数据后,没有延时即立刻开始接收(此时从机可能没有反应过来,也就没有数据输出),SPI 的波形可能失真(通过示波器可以观察出来),也可能波形挺好,但接收就是有问题;

2、 CS 控制时机不对,或者也可能需要延时一段时间。

3、SPI 波特率太高,导致器件无法识别。测试时最好使用最低波特率,但也不排除太低波特率反而不行的情况,所以多试试几种。

总之,测试代码应该要考虑最极端的情况(比如延时久一点,波特率慢一点),调通之后再优化这些参数,提高通信效率。

另外,如果有参考例程,可以直接对比例程输出的波形进行修改,直到你的代码输出波形(注意,这里直接对比波形)和例程保持一致,如果还是有问题,那么换从机器件再试。当然,电源这里也一定要先确定没有问题。

一般来说,只要 CS、CLK、MOSI 控制时序正确,器件的 MISO 引脚一定会有数据输出(即使你没有接主机的 MISO),如果你能看到 MISO 输出波形,但主机还是没有正确接收,那么就查主机的 MISO 引脚配置和延时情况了。

I2C 通信:

1、和 SPI 类似,查看时序问题

2、注意地址问题,有可能说明书的地址和实际的地址之间换算可能需要移位。

3、注意从机锁死问题,传输前发几个停止信号过去试试

来源:鱼鹰谈单片机
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 122

刚开始学单片机的你,是不是会因用程序把LED点亮而感到高兴,会因用程序把数码管点亮而感到高兴。这是好事,这也是想继续学习下去的动力。

但是到了与数据相关的实验时,却感觉很难有所进步。有时候,把驱动写好了,下载到单片机后,一点反应都没有,可是又不知道问题出在哪里,数据通信又不像LED那样可以用万用表测出到底有没有电。

这是学习单片机和STM32的一道坎。又或者说,这是一条河,阻拦着你的去路的河,有一条河你会怎么办?过去的方法很多,但是笔者觉得较快的方法就是借助原有工具渡过去。过去之后你会发现河的那边是一个不一样的世界。

那这个原有的工具是什么呢?那就是"串口通信"。

串口通信介绍

串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式......这种太过理论了,看似懂了,但又不懂。还是用我笔者自己的话来说吧。

串口通信就是可以把程序在单片机或者STM32芯片中运行的结果发送到电脑的一种通信方式。如何使用串口通讯,你需要知道的几个重要的知识点:

  • 1.波特率。(数据传输的速度,两个设备在通信时的速度要设置成一样,否则会出现乱码)。

  • 2.硬件连接。串口通信是一种异步通信,一般都是TX->RX,RX->TX。

  • 3.停止位。

  • 4.奇偶校验。

  • 5.硬件数据流。

3、4、5,这几项一般不需要改变,所以在这就不用多说了。

串口作用

串口能用来做什么事情?

学过C语言的人都知道,程序开发需要不断调试不断验证。很多的语言编程软件都有很完整的程序调试功能。使用起来很方便。而我们的开发虽然也是用C语言,但我们的程序最终运行的环境不是在我们的开平台而是在真正的硬件系统中运行。这时想要查看程序的运行过程或者结果就不像编程软件那么方便了。

想要解决这个问题,我们这时就可以借助串口通信来把我们需要知道的结果又或者是程序运行的关键步骤发送到电脑上,我们就知道程序在硬件系统中运行是否出现问题。51单片机可以用串口来调试,STM32可以用串口来调试。高级一点的LINUX开发板也是使用串口来调试。

学会串口通信能做什么

当你学会串口通信时,你可以开始玩WIFI模块、GSM模块、蓝牙模块、GPS模块、以及各种使用串口通信的传感器等等。有能力你还可以编写上位机软件通过串口通信来控制设备。

串口通信需要什么

如果你只有STM32核心板,那么你还需要一个串口转USB模块和一个串口数据接收软件还有几根杜邦线就可以了。如果你的是比较完整的一款开发板的话,一般都已经带有串口转USB模块,这样使用起来就更简单了。聪明的你是不是发现还缺少了点什么,对。没错因为还缺少了最重要的程序。想要使用串口通信当然还需要写串口通信的程序。下面就教你如何去用,而不是写。

串口实验

在做一个实验时,最好把这个实验分割成几个关键的步骤,这样做的好处就是可以清晰的知道自己需要做什么,以及做完了哪些。还有哪些还没做。下面笔者把串口实验分成几个关键的步骤:

1)串口通信使用到的GPIO引脚配置

STM32F103系列的芯片一般都有三个串口以上,用来调试使用的串口一般都是使用USART1。其他的串口配置都是一样的。

下面这段就是串口配置的程序:

GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能USART1,GPIOA时钟

//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 

串口使用的的GPIO口是PA9和PA10,所以只需配置这两个IO口的输入输出模式就可以了。

2)串口主要参数设置(直接看程序)

USART_InitTypeDef USART_InitStructure;
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;
//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
//数据格式,8位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;
//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
//收发模式
USART_Init(USART1, &USART_InitStructure); 
//初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//开启串口接受中断

串口参数配置无法就是配置串口的波特率、数据格式、停止位、奇偶校验、硬件流、收发模式。除了波特率需要改变其他的参数都不需要管。直接复制拿来用。

3)串口中断配置

串口如果使用中断接收,那么就需要配置串口的中断参数,配置项无法就是配置那个的中断源和中断的优先级。

NVIC_InitTypeDef NVIC_InitStructure;
//Usart1 NVIC 配置

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;//子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能
NVIC_Init(&NVIC_InitStructure);//根据指定的参数初始化VIC寄存器

4)串口使能

就是你需要什么时候开始使用串口功能,就是一句原有的函数。

USART_Cmd(USART1, ENABLE); //使能串口1 

5)编写串口中断处理函数

使用库函数开发,所有的中断函数都是已经存在的,只是中断函数里面没有处理任何事情而已。中断函数如下:

void USART1_IRQHandler(void)
{
//这里是编写中断处理的内容,但是一般会先判断相关的标准才算完整
}

完整的串口中断函数:

void USART1_IRQHandler(void)
{
int Res=0; //定义一个变量用来接收串口数据集
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
  {
  Res =USART_ReceiveData(USART1);//读取接收到的数据
  USART_SendData(USART1,Res ); //把接收到的数据通过串口1发送出去
  }
}

串口数据的接收和发送的函数都是库函数提供的,想用时只需找到它直接拿来用就可以了。

以上就完成了一个最简单的串口实验。把程序编译烧写到STM32然后用串口转USB模块连接到电脑,使用串口数据接收软件SSCOM或其他的数据接收软件设置好波特率,打开串口,正常的话,那发送什么到STM32那就会收到什么。这样就算完成了。

这次就说到这了,如果有开发板的配套例程可以先使用,用多了,慢慢就理解了。这篇文章主要想说明串口的重要性。用串口来调试真的方便很多。

来源:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 97

首先总结一下串口232,422,485

串口232:可双向传输,全双工,最大速率20Kbps,负逻辑电平,-15V~-3V逻辑“1”, 3V~ 15V逻辑“0”。

串口422:可双向传输,4线全双工,2线单工。

串口485:可双向传输,4线全双工,2线单工,最大速率10Mb/s,差分信号,发送端:2V~ 6V逻辑“1”,-2V~-6V逻辑“0”,接收端:200mV逻辑“1”,-200mV逻辑“0”。

对于串口的实现有以两个方案:

方案一,和原子的《例说STM32》一样,首先接收,然后处理,没有消息验证处理,这样就会出现消息覆盖,消息出错后死机,无法明确区分命令,无法及时应答握手信号。

方案二,借鉴uC/OSII的消息队列,进入中断服务函数之后,关闭中断,接收数据,如果没有数据接收,等待一段时间(时间和波特率有关)后开中断,出中断,然后在对接收到的数据进行处理,下面看代码:

消息队列及其初始化函数:

“STM32的串口应用总结”

这样就把数据一次性全部存储下来了,剩下的就是对消息缓冲器message_buff[]中的消息进行处理了,这样就解决了消息覆盖,消息出错无法纠正的问题,至于消息怎么处理就是依据不同的需求不同的处理,另外注意,握手信号好用定时器中断。

STM32的IO口基本操作

“STM32的串口应用总结”

1.初始化结构体

先来看下GPIO_InitTypeDef这个结构体,源代码如下

“STM32的串口应用总结”

结构体中包含了GPIO_Pin,GPIO_Speed和GPIO_Mode信息,GPIO_Pin在stm32f10x_gpio.h中有宏定义

“STM32的串口应用总结”

刚好对应16个端口。

GPIO_Speed是一个枚举类型的结构体,在stm32f10x_gpio.h中有宏定义:

“STM32的串口应用总结”

当STM32的GPIO端口设置为输出模式时,有三种速度可以选择:2MHz、10MHz和50MHz,这个速度是指I/O口驱动电路的速度,是用来选择不同的输出驱动模块,达到最佳的噪声控制和降低功耗的目的。

高频的驱动电路,噪声也高,当你不需要高的输出频率时,请选用低频驱动电路,这样非常有利于提高系统的EMI性能。

当然如果你要输出较高频率的信号,但却选用了较低频率的驱动模块,你很可能会得到失真的输出信号。

关键是,GPIO的引脚速度跟应用匹配。

比如对于串口,假如最大波特率只需115.2k,那么用2M的GPIO的引脚速度就够了,既省电也噪声小。

对于I2C接口,假如使用400k波特率,若想把余量留大些,那么用2M的GPIO的引脚速度或许不够,这时可以选用10M的GPIO引脚速度。

对于SPI接口,假如使用18M或9M波特率,用10M的GPIO的引脚速度显然不够了,需要选用50M的GPIO的引脚速度。

GPIO_Mode也是一个枚举类型的结构体:

“STM32的串口应用总结”

2.设置系统时钟

在使用端口前必须要开启外设时钟,在开启外设时钟前我们首先要配置好系统时钟,系统时钟的设置主要在库函数SystemInit()中完成,在启动文件startup_stm32f10x_md.s中有如下一段代码:

“STM32的串口应用总结”

在调用main函数之前首先要调用SystemInit()函数,这个函数定义在system_stm32f10x.c中,函数原型为

“STM32的串口应用总结”

该函数的主要功能是将配置时钟相关的寄存器都复位为默认值,并调用SetSysClock(void)函数,

“STM32的串口应用总结”

对于SYSCLK_FREQ的宏定义在system_stm32f10x.c文件开头已经给出,默认的系统时钟为72M,当然前提条件是外接8M晶振。

“STM32的串口应用总结”

3.开启外设时钟

开启或者关闭外设时钟主要由以下函数设置

“STM32的串口应用总结”

不同的外设调用不同的函数,如果使用了io的引脚复用功能除了开启io功能时钟还需要开启复用功能时钟,例如GPIOC的Pin4还可以作为ADC1的输入引脚,当它作为ADC1来使用时,除了开启GPIOC的时钟外,还要开启ADC1的时钟

“STM32的串口应用总结”

其他操作函数参看库函数使用手册。

来源:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 124


下面先讲串口通信的一些基本概念,术语。如果对串口通信比较熟悉的,就当复习,如果哪里讲的不到位,欢迎及时指出。

这里并不对串口的编程作讲解,主要是从应用的角度去讲一讲。因为更多的时候,都是产品做好了,比如触摸屏需要和控制器,PLC通信。理想的情况下,一般只要一上电,不需要太多的操作和配置,就可以通信上。

文章后半部分罗列了一些相关问题,在解答前还需要先了解一下什么是串口通信,232,485,422等。

什么是串口通信

常见的串口通信一般是指异步串行通信。

这里就要说一下同步和异步的区别了。

先讲一下串行通信的概念。那么,与串行通信相对的是什么呢?

与串行通信相对的是并行通信。数据传输一般都是以字节传输的,一个字节8个位。拿一个并行通信举例来说,也就是会有8根线,每一根线代表一个位。一次传输就可以传一个字节,而串口通信,就是传数据只有一根线传输,一次只能传一个位,要传一个字节就需要传8次。就像小虎队那首歌一样,把你的心,我的心,串一串,再烤一烤。。串口通信就是把数据串在一根线上传输,所以就叫串口吧。


与异步通信相对的就是同步通信了。同步通信一般是指有一个时钟信号进行数据信号同步。同步通信对接收方来说就相对简单一些。因为有时钟信号在,每一个高低电平变化一下,就去取一下数据就行了。通信速率可以由发送方或者说是主站设备进行控制。通信速度也相对比串口通信快很多。但是为什么很多设备,屏和plc,控制器不采用这种方式,都使用串口呢。

那么,在很多设备上,不方便接太多线,比如接8根数据线,也不方便接同步时钟信号(这个后面再说),于是一种异步串行通信就诞生了。

相对来说,异步串口通信,就只需要一根线就可以发送数据了。在对速率要求不高的情况,使用一根线发送数据是带来大大的方便和实用价值的。

那么问题来了,怎么样才能保证一根线就能发送正常的数据呢。也就说发送方发送的数据,接收方是怎么知道是什么数据呢。

为了能正常发送数据和接收正确的数据,那异步串口通信就需要满足以下几个条件:


也就是双方必要约定一种暗号。

也许当时发送这个通信的小组是这样讨论的。

经理:我要用一根线就能传输数据,你来给我定个标准。

研发:好。

经理:只有一根线,我怎么知道数据什么时候开始呢。

研发:就一根线,默认是高电平,那就有一个起始位吧。当检测到有低电平的时候,就是开始有一个字节的数据发送了,起始位之后,先是字节的最低位,传送一个字节。

经理:可是,就一根线,过来的数据会不会有干扰,容易出错呀。

研发:行呀,那就在字节数据后再加一个校验位。可以作奇校验,偶校验,1校验,0校验,无校验。

经理:嗯 ,很不错。有起始位就应该有停止位,那我们就再加个停止位在后面吧。

研发:。。。。。。。。。

经理:传输一串数据,对方要怎么知道数据的拆分呢,怎么按时间或频率去解读数据位,校验位呢。

研发:这样吧,双方约定一个波特率吧,定义一个每个位占用多长的时间,这样双方按这个波特率就可以处理了。

经理:这个比特率呀。。。

研发:老板,是波特率。

经理:我知道,是比特率嘛。

研发:这个波特率呢,是指1S钟可以传输多个位,也就知道一个位占用多长时间。这样就解决传输的问题了。

经理:那万一传输过程,数据快太,判断失误停不下来怎么办。

研发:那就把停止位可以调节为1个停止位或者2个停止位。这样就可以停下来了。

经理:嗯,听着不错。就这样办吧。

于是,串口通信就这样出来了。

在串口的通信参数上,就有了波特率,数据位,停止位,校验位这几个参数来确保串口通信的正确性和稳定上。当然,这只是某个方面保证串口通信的正确性和稳定性,不代表设备间通信的正确性和稳定性。

串口通信主要为分232,485,422 通信三种方式。

这三种有什么区别呢。

232:


232 通信主要是由RX,TX,GND三根线组成。

RX与TX,TX接RX,GND接GND。这样还是比较好理解吧。因为发送和接收分别是由不同的线处理的,也就是能同时发送数据和接收数据,这就是所谓的全双工。

在这里扩展一下,串口通信还有一个功能叫做全功能串口通信,也叫标准串口。因为在两个设备间进行数据传输,有些设备处理速度比较快,有些数据比较慢。为了保证数据能正常传输,在RX,TX的基础上,还增加了几个控制引脚,本来好端端就R,T,G,三根线,凑着就凑齐了9个引脚,召唤出了DB9这个东西。


这要怪就怪当时使用电脑的时候,还没有互联网这个概念,但是又想在两台电脑间进行通信。所以才有这样一个东西。

在后来的设备,很多控制器,人机界面,PLC等使用串口通信中,基本上就不使用标准串口,而是就直接使用RX,TX,GND三根线来通信了。

但是这里为什么要提到这个呢。因为只是很多设备这样用,也就是还存在少数设备还保留了标准串口的功能。这就是为什么会遇到明明电脑通信是好的,换成触摸屏通信就不行了。因为很多触摸屏只使用了RX,TX,GND通信,遇到一些还保留标准串口功能的就比较讨厌了。

485:

485是为了解决232通信距离的问题。原理什么之类的就不多讲了。反正232通信距离就是不长。485主要是以一种差分信号进行传输,只需要两根线,+,-两根线,或者也叫A,B两根线。A,B两根线的差分电平信号就是作为数据信号传输。

那么问题来了,那是不是就没有RX和TX的概念了。是的,发送和接收就不能分开了。发送和接收都是靠这两根的来传输,也就是每次只能作发送或者只能作接收,这就是半双工的概念了,这在效率上就比232弱很多了。就像对讲机一样,经常是某个人讲完之后,都要说一个over,确保当前说完了,等待对方回复。


485就是这样牺牲了232全双工的效率来达到自己传输距离远的代价。那有没有即保留了232的全双工,又可以像485这样提高传输距离呢,于是,422出来了。

422:

422呢,有些标注为485-4。而485就标注为485-2。有什么区别呢。就是为了好记呢。485-2就是2根线。485-4就是4根线。


422就是把232的RX分成两根线,RX+,RX-,把TX分成TX+,TX-。这样就可以同时发送和同时接收了,还可以像485这样,有较远的传输距离。可是这样一种很有优势的通信方式,为什么用的不多呢。我个人的答案和理解就是:线太多了。特别是像我这样懒得接线的人,超过3根线就头晕的。搞个通信还需要接这么多线,什么TX,RX,正啊负啊。交换来交换去。

因为在很多设备通信中,基本上是属于一问一答式的,因此,232的全双工通信优势其实也并没有发挥出来。就像现在打电话,虽然两个人可以同时说话,但是两个人同时说话,叽叽歪歪的,谁知道说什么呀。特别是一个主站与多个从站通信的时候,485的接线就就方便多了,反正大家就两根线,把+都接一块,把-都接一块。如果是422作一主多从,接线上还要理半天呢,而且通信异常了也不好解决。

好了,串口通信基本就普及到这里吧。下面就对刚上提到的问题进行讲一讲。

1、电脑使用USB转串口可以和设备通信上,换成屏与设备就通信不上了?

1)有可能电脑USB转串口接到设备上,使用的是标准串口功能,也就是除了RX,TX,GDN外,还使用了其它引脚。比如像欧姆龙PLC,三菱PLC,在实际与屏的通信中,就需要接某些引脚短接的情况。

2)电脑与控制器或PLC通信时,是扫描波特率参数,自适应的,屏通信可能参数没有设备好。在三菱,基恩士等PLC,就存在变化波特率进行通信交互的过程。

3)也有可能是接线方式不对。因为有些DB9,还需要公头,母头。如果不注意的话,也会存在把TX接到TX上,把RX接到RX上,这样需要注意的地方。

4) 在这里补充一下,有时候可能会使用一些串口助手发送测试数据与控制器通信,有些串口助手的奇偶校验是不起作用,这个要提醒一下。

2、 这A家的屏可以和设备通信,换成B家的屏就通信不上了?

1) 首先确认一下接线是否正确了,RX和TX是否兼容。
2) 地线是否没有接。
3) 除了RX,TX,GND,是否还有其它引脚需要短接的。
4) 通信协议是否一致或不完善,波特率是否一样。

3、 以前不接地线可以通信,换个设备为什么需要接地线了?

这个问题和上一个有类似的。因为有些设备使用了隔离电源。以前不接地可以通信,有可能是地线已经在另外一个环路已经共地了,实际地线已经接了,所以才可以通信。可能换了个带隔离电源的,两个设备的地是隔离的,就需要在串口上把地线接起来。这个我是自身经历过的,有个客户老说他的设备通信不上,后来拍个照我给我,他地线没有接,他说以前不接地线可以通信的。于是我就给他科普了一下。

4 、一个设备是232,另一个设备是422,没有转换设备,怎么办?(232与422互转的简单方法)

这个情况我遇到过,客户的设备是422通信的,但是我手上并没有422设备,只有232通信可以测试。因此就需要把422转成232进行通信。

刚才也讲了422和232的接线,因为这两个都是全双工的,接收和发送都是分到的,而422只是以一种差分信号进行传输。

把422的Rx+与232的TX接,422的RX-与232的GND接。

把422的TX+与232的RX接,422的TX-与232的GDN接。

这样,422设备要发送数据的,就可以发送到了232的RX上。232的TX发数据后,由于TX和GND也形成了差分信号给422,422就可以接收到数据了。

5、 用232通信没问题,用485通信没问题,使用232转485之后就通信不稳定?

232和485从通信原理上,最大一个差别是全双工和半双工的区别。可是应用层发送数据和接收数据才不管底下是全双工还是半双工。

但是485就得管了。因为既然是半双工,就得严格保证通路上只能有发送或只能有接收的数据,一旦同时有发送和接收,数据就会冲突了。所以解决的办法就是主站设备,也就是主动命令的一方就需要严格控制好发送数据命令的节奏了。

当然有些232转485的设备做的比较好了,可以优化这个,但是主站还是要控制,比较把通信速率调节慢一些(不是调节波特率)。

6、 485单独接每个设备都通信正常,多个从站接一块通信就不稳定?

这个是属于485通信的不稳定因素了。

7 、要想实现两个屏或两个主站通过485访问modbus设备,有什么好的办法?

在485通信中,基本上是一主多从。但是遇到一些客户实际使用中,有客户想用两个屏来访问一个modbus设备的。目前暂时还没有好的办法。

8、 针对串口通信的弱点,在使用上应该要注意哪些地方?

说来串口通信的弱点,那就说来话长了。不过还是长话短说吧。

1)信号干扰的问题

建议使用带屏蔽线,接线要严格,比如要接地。有些485通信上,还考虑接上终端电阻来匹配。如果是232,尽量不要让线太长。通信协议上尽量避免长报文的数据通信。

2)波特率匹配的问题

因为有些设备的计算的波特率是存在误差的,特别是一些控制器,由于使用的晶振不一样。因此在一些波特率比如9600波特率就存在误差。存在误差带来的影响是什么呢。因为接收方是通过时间来计算一个位的。那么如果一个报文过长,就会存在误差积累的问题,算着算着就偏了。所以,这也是串口通信不稳定的一些地方,在使用上应注意避免发送太长数据的包。

3)在一些可能会存在干扰的情况,在有的选的情况,可以考虑使用奇校验或者偶校验。因为虽说出现错误的可能性不大,但既然存在干扰,如果加了校验,至少可以把错误的报文过滤掉。总好比没有校验然后通信数据错了不知道。或者尽量使用一些带校验的协议,防止数据出错。

4)串口通信本来就比较慢,请降低对数据响应的要求。

因为串口通信本身就比以太网慢。而且,串口通信并不是能像CPU那样多线程处理。因为就一个口一个线数据出去,即便你应用到程序再怎么用多线程处理数据,但是最底下也只有一个口出去,一次也只能传一个位,一个字节过去。因为有客户在使用9600的波特率通信,但是又希望多少的数据可以在多少毫秒内得到响应。

但是串口通信还是要事实求是,所以正确认识串口通信对应用,对开发,对沟通都有着很大的帮助的。

为什么不用同步通信呢?

刚才提到,同步通信需要依赖于时钟信号。这就存在一个问题,这个时钟信号是谁来发起呢。在同步通信中,往往需要一个主设备发起时钟信号读从模块的数据。在实际中,有屏读PLC,有屏读屏的数据。而单纯地从异步串口通信来说,是没有主从之说,双方都是平等的角色,都可以互发信息,互收信息。而同步通信一般是应用于CPU读一些模块,由CPU发起时钟信号,比如读SD卡模块,就可以通过SPI方式,还有一些传感器模块。

本文是转载文章,直接来源:技成培训,转载此文目的在于传递更多信息,版权归原作者所有。

围观 136

页面

订阅 RSS - 串口通信