IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32F10学习----UART 学习 通信协议制定 使用实例 以及使用总结 -> 正文阅读

[嵌入式]STM32F10学习----UART 学习 通信协议制定 使用实例 以及使用总结

学习某一个东西,我们首先要了解这个东西的定义是什么,用来干什么的,怎么用,用的过程中有什么注意事项,这些都OK了,那么我们就算是基本掌握他了。

0 前言

? USART–通用同步/异步串行接收/发送器,通用同步/异步串行接收/发送器USART是一个全双工通用同步/异步串行收发模块。

? UART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。在嵌入式设计中,UART用于主机与辅助设备通信,如汽车音响与外接AP之间的通信,与PC机通信包括与监控调试器和其它器件,如EEPROM通信。

? 简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。

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

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

1.USART

1.1概述

在STM32的参考手册中,串口被描述成通用同步异步收发器(USART),它提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。USART利用分数波特率发生器提供宽范围的波特率选择。它支持同步单向通信和半双工单线通信,也支持LIN(局部互联网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范,以及调制解调器(CTS/RTS)操作。它还允许多处理器通信。还可以使用DMA方式,实现高速数据通信。

发送和接收共用的可编程波特率,最高达4.5Mbits/s

1.2 USART的框图

在这里插入图片描述

1.2.1 串口的硬件接线

RX: 接受数据串行输入。通过过采样技术来区别数据和噪音,从而恢复数据。

TX: 发送数据输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活,并且不发送数据时,TX引脚处处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。

SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。具体智能卡模式请查看《STM32F10X数据手册》。

irDA_OUT:irDA模式下的数据输出。

irDA_IN:irD模式下的数据输入 。具体irDA模式请查看《STM32F10X数据手册》。

nRTS:请求以发送(Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当 USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时, nRTS 将被设置为高电平停止接收。该引脚只适用于硬件流控制。

nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。具体硬件流模式请查看《STM32F10X数据手册》。

SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。

想要与串口实现双向通信硬件上面至少需要3根线:**接受数据输入(RX)和发送数据输出(TX)以及GND。**如下图所示。

在这里插入图片描述

1.2.2串口的数据发送和接收

1、一个完整的串口字符包括:

1位起始位

8位数据位

1位可选的奇偶检验位

0.5-2位的停止位

从框架图可知,串口的数据发送依靠数据发送寄存器,数据接收依靠数据接收寄存器。当数据发送的时候,数据发送寄存器就会将数据移位到移位寄存器中进行发送;当数据接收的时候,先接收到接收移位寄存器中,在放入数据接收寄存器中。所以有**串口的发送和接受都应该是空闲开始的。**在发送和接收的过程中都可以设置相应的位来产生相应的中断。

数据发送时,USART_CR1寄存器的发送使能位TE会被置1,启动数据发送,发送移位寄存器的数据会在TX引脚输出,低位在前,高位在后。起始位是一个位周期的低电平,位周期就是每一位占用的时间。

数据接收时,USART_CR1寄存器的RE位会被置1,使能USART接收,接收器在RX线开始搜索起始位。在确定起始位后,就根据RX线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器的数据移到接收数据寄存器中,并把USART_SR寄存器的RXNE位置。如果USART_CR2寄存器的RXNEIE置1可以产生中断。

一般我们串口中断的方式来传输数据,这样可以保证数据传输的实时性。下面是几个带标志的中断源:

中断事件事件标志使能控制位
发送寄存器为空TXETXIEI
CTS标志CTSCTSIE
发送完成TCTCIE
准备好读取接收数据RXNERXNEIE
检测到上溢错误OREOREIE
检测到空闲线路IDLEIDLE
奇偶检验PEPEIE
断路标志LBDLB
多缓冲区通信中的噪声标志、
上溢错误和帧错误
NF、ORE、FEEIE

2、串口通讯协议设计

(1)制定串口通信协议(推荐)

一般串口完整数据帧的定义:帧头(2字节,例如AA、BB) + 数据长度(2字节) +命令(1字节)+ 数据 + CRC16校验(2字节) + 帧尾(可选的)(2字节)

判断接收到帧头后,开启定时器,在定时器计数时间到还未接收到完整的数据帧,就把这包数据丢弃

完整接收到数据帧后(字节长度 ,判断校验,判断帧尾部),就可以执行对应的命令

(2)空闲中断

? 通过空闲中断标志来识别一帧的结束, 无需制定通信协议,但是可能会出现帧粘包、帧数据丢失、帧数据延迟等问题

(3)根据接收到的字符之间的间隔进行判断

? 其实我不是很理解这个地方为什么会被带起来这种方案,这是设计通信不太靠谱的方案。。。 各种风险无法保证帧的完整 ,虽然可以做,当做扩展一下,但是不要带偏了。

2.使用示例

2.1 USART配置

使用官方提供的固件库配置usart

.c文件内容

#include "usart.h"
#include "stdio.h"
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
	USART1->DR = (u8) ch;      
	return ch;
}

/**
 * @brief  串口1和串口2初始化
 * @param  NONE
 * @retval none
 */
void GPIO_Configuration(void)
{

    GPIO_InitTypeDef GPIO_InitStructure;
  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA| RCC_APB2Periph_AFIO, ENABLE);
 
    //
    /*PA2 PA3 USART2*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; ;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /* Configure USART2  Tx (PA.02) as alternate function */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    
    /*USART1*/
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /* Configure USART1  Tx (PA.09)*/
    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);

}
/**
 * @brief  串口1和串口2初始化
 * @param  NONE
 * @retval none
 */
void USART_Configuration(void)
{
    USART_InitTypeDef USART_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

    USART_DeInit(USART1);
    /* Configure USART1 */
    USART_InitStructure.USART_BaudRate = 9600;
    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;
    /* Configure USART1 */
    USART_Init(USART1, &USART_InitStructure);

    USART_DeInit(USART2);
    /* Configure USART2 */
    USART_InitStructure.USART_BaudRate = 9600;
    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;
    /* Configure USART2 */
    USART_Init(USART2, &USART_InitStructure);

    USART_ITConfig(USART1,  USART_IT_TC, ENABLE);//开启串口发送中断
    USART_ITConfig(USART1,  USART_IT_RXNE, ENABLE);//开启串口接受中断
    USART_Cmd(USART1, ENABLE);
    USART_ITConfig(USART2,  USART_IT_TC, ENABLE);//开启串口发送中断
    USART_ITConfig(USART2,  USART_IT_RXNE, ENABLE);//开启串口接受中断
    USART_Cmd(USART2, ENABLE);
    
}

/**
 * @brief  串口1和串口2中断初始化
 * @param  NONE
 * @retval none
 */
void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

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

    USART_ITConfig(USART1,  USART_IT_TXE, ENABLE);//开启串口发送中断
    USART_ITConfig(USART1,  USART_IT_RXNE, ENABLE);//开启串口接受中断
    /
    //Usart2 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;		//子优先级3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

    USART_ITConfig(USART2,  USART_IT_TXE, ENABLE);//开启串口发送中断
    USART_ITConfig(USART2,  USART_IT_RXNE, ENABLE);//开启串口接受中断
    
}

void board_init(void)
{
    GPIO_Configuration()
    USART_Configuration();
    NVIC_Configuration();
}

void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //判断是否真的发生了中断
    {
         USART_ClearITPendingBit(USART1, USART_IT_RXNE);
         printf("串口1接收测试\n");
         //在这里可以做入队操作 
         //入队一定要保准数据完整的进入队列不能被中断打断  所以此处入队操作必须关闭中断
         //然后在主函数或者其他地方去处理
         //入队完成后不要忘记打开中断
        
    }
    else if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)
    {
         USART_ClearITPendingBit(USART1, USART_IT_TC);
         printf("串口1发送测试\n");
         
    }
}
void USART2_IRQHandler(void)
{
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //判断是否真的发生了中断
    {
         printf("串口2接收测试\n");
         USART_ClearITPendingBit(USART2, USART_IT_RXNE);
    }
    else if(USART_GetITStatus(USART2, USART_IT_TC) != RESET)
    {
         printf("串口2发送测试\n");
         USART_ClearITPendingBit(USART2, USART_IT_TC);
    }
    
    
}

.h文件内容

#ifndef __USART_H
#define __USART_H


void board_init(void);
#endif

mian.c文件内容

void main( void )
{
	while(1)
	{
		if()//判断队列
		{
		   //出队操作
		   //解析协议
		   //执行串口命令
		}
	}
}

3.使用总结

1、在程序设计中,可能会设计到串口接收完,但是无法及时处理的问题,这个时候我们可以在中断中将接收的消息入队,任务空闲再来处理串口数据

2、使用空闲中断来识别一帧,如果数据因为线路上面的延迟而造成数据推迟到达,这样会造成一包数据不完整,但是这种操作可以使用在实时性不高的操作中,比如在串口工具接收发送显示时候

3、在设计串口通信协议的时候,要考虑可能存在粘包、丢包、丢字节、接收到帧头但数据却延时接收等的问题

4、对于大多数UART来说,内部发送缓冲器”空”会产生中断或置对应的标志位,但此时数据不一定真的发送完成,因为数据有可能还在输出移位寄存器中。如果仅靠发送缓冲的状态来判断一包数据是否发送完成,从而决定是否关闭内部UART的发送使能和接口的发送使能,则这个数据包的最后一个字节将不会发送到总线上,以致对方会少接收一一个字节。

5、波特率设置太高会有误码的情况,

在这里插入图片描述

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-08 10:53:52  更:2021-09-08 10:55:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:39:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码