STM32F4xx开发学习_USART串口通讯

news2024/11/17 10:01:30

USART串口通讯

USART简介

USART(universal synchronous asynchronous receiver transmitter),通用同步异步接收发射机,是一种全双工异步通信串行通讯方式,是STM32内部集成的硬件外设,以帧格式传输数据。搭配DMA进行多缓冲配置,可进行高速数据通信。

异步通信接线图如下
USART接线图

这里给出码元传输速率 R B R_B RB和信息传输速率 R b R_b Rb的定义

  • 码元传输速率
    亦称传码率、波特率,单位时间内传输了多少个码元
  • 信息传输速率
    亦称传信率、比特率,单位时间内传递多少比特数

在M进制中,每个码元携带 log ⁡ 2 M \log_2M log2M比特的信息量,二者满足: R b = R B log ⁡ 2 M R_b = R_B \log_2M Rb=RBlog2M,二进制中两者相等。
USART框图

USART协议

下面根据串口数据帧格式进行说明,可配置为8位或9位长,如下图
USART数据帧

参数

  • 起始位:低电平为一个数据帧的开始
  • 数据位:可配置为8位或9位,低位先行
  • 校验位:可选择进行奇偶校验,也可不进行校验,位于最后一个数据位之后,停止位之前
  • 停止位:高电平为一个数据帧的结束,长度可设置为0.5、1(默认)、1.5、2位宽

发射端

发送器根据是否设置校验位(即USART_CR1寄存器的M位)发送8位或9位的数据字,USART_CR1寄存器的发送使能位TE置1时,发送移位寄存器的数据会在TX引脚输出,相应的时钟脉冲输出到CK引脚。在数据传输过程中为保证不丢失数据TE位不应被重置

USART_发生器

实现步骤:1、通过USART_CR1寄存器中的UE位使能USART;2、通过USART_CR1寄存器中的M位设置数据位长度;3、通过USART_CR2寄存器设置停止位长度;4、通过USART_CR3寄存器中的DMAT位选择使能DMA;5、通过USART_BRR寄存器设置波特率;6、通过USART_CR1中的TE位发送空闲帧作为第一次传输;7、将要发送的数据写入USART_DR寄存器(这回自动清除TXE位);8、在写完最后一个数据到USART_DR寄存器中,等待TC=1。
简述为:TXE=1,先发送一个空闲帧,数据就送入TDR同时TXE=0;等到开始位时TDR内数据送入移位寄存器,恢复TXE=1;TXE=1,下一个数据送入TDR同时TXE=0。依次类推。

接收端

接收器根据校验位(即USART_CR1寄存器的M位)接收8位或9位的数据字,前提是保证和发送器相同的波特率,并且要求每次采样的位置正好处于每一位的正中间,同时还要对噪声有一定的判断能力。起始位侦测如下

USART起始位检测
检测到正确的起始位后,就是进行数据接收。实现步骤:1、通过USART_CR1寄存器中的UE位使能USART;2、通过USART_CR1寄存器中的M位设置数据位长度;3、通过USART_CR2寄存器设置停止位长度;4、通过USART_CR3寄存器中的DMAT位选择使能DMA;5、通过USART_BRR寄存器设置波特率;6、通过USART_CR1寄存器中的RE位使能USART接收,RX引脚检测起始位。
当完整收到一个字节数据时,RXNE位置1、如果使能了RXNEIE将会产生中断、发生错误将产生错误标志位、在配置了DMA时RXNE由DMA清除、未配置DMA时RXNE是在RDR读完后自动清除。

选择合适的过采样法

为保证接收端接收到正确的数据帧,需配置合适的过采样技术,通过USART_CR1寄存器这OVER8位进行选择。

  • 选择 8(OVER8=1)的过采样以实现更高的速度(最高 fPCLK/8),在这种情况下,接收机对时钟偏差的最大容差会降低
  • 选择过采样 16 (OVER8=0) 以增加接收器对时钟偏差的容差。在这种情况下,最大速度限制为最大 fPCLK/16

小数波特率

波特率指数据信号对载波的调制速率,计算公式如下

波特率 = f C K 8 × ( 2 − O V E R 8 ) × U S A R T D I V 波特率 = \frac{f_{CK}}{8\times (2 - OVER8)\times USARTDIV} 波特率=8×(2OVER8)×USARTDIVfCK

其中 f C K f_{CK} fCK是USART时钟即所在总线时钟频率,USARTDIV是一个存放在USART_BRR寄存器中的无符号定点数。其中DIV_Mantissa[11:0]位定义USARTDIV的整数部分, DIV_Fraction[3:0]位定义USARTDIV的小数部分,DIV_Fraction[3]位只有在OVER8位为0时有效,否则必须清零。

校验位控制

通过USART_CR1寄存器中的PCE位启用校验。分为奇校验和偶校验,如果使用了奇校验,则9位数据中会出现奇数个1;如果使用了偶校验,则9位数据中会出现偶数个1。

配置DMA

USART能够使用DMA进行连续通信。Rx 缓冲区和 Tx 缓冲区的 DMA 请求是独立生成的。

  • 发送端配置DMA
    通过USART_CR3寄存器中的DMAT位配置DMA进行发送。TXE位置位时,SRAM内数据都会通过DMA外设加载到TDR中。通过以下流程配置
    • 在DMA控制寄存器中写入TDR地址,将其作为传输目的地
    • 在DMA控制寄存器中写入SRAM地址,将其作为传输起点
    • 配置DMA其他配置

USART_DMA发送

  • 接收端配置DMA
    通过USART_CR3寄存器中的DMAR位配置DMA进行接收。RXNE位置位时,RDR内数据都会通过DMA外设加载到SRAM中。通过以下流程配置
    • 在DMA控制寄存器中写入RDR地址,将其作为传输起始地
    • 在DMA控制寄存器中写入SRAM地址,将其作为传输目的地
    • 配置DMA其他配置

USART_DMA接收

代码实现

  1. 单字节串口发送
  • 开启USART时钟和GPIO引脚时钟
  • 配置GPIO引脚
  • 配置USART
  • 使能USART
  • 功能函数编写

代码如下

/*
**********************************************************************************
*   @brief  USART1串口初始化
*			PA9是USART1_TX,PA10是USART1_RX
*			GPIO是AHB1总线,USART1是APB2总线
*   @param  none
*   @return none
*   @use	Serial_Init()
**********************************************************************************
*/
void Serial_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	//USART1对应引脚复用映射
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
	//配置PA9、PA10引脚
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//配置USART1
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;											//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		//硬件流控制,这里不选用
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;						//双工
	USART_InitStructure.USART_Parity = USART_Parity_No;									//校验位,这里不选用
	USART_InitStructure.USART_StopBits = USART_StopBits_1;								//1位长的停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;							//字长8位
	USART_Init(USART1, &USART_InitStructure);
	USART_Cmd(USART1, ENABLE);
}

/*
**********************************************************************************
*   @brief  发送单个字节数据
*   @param  一个十六进制数据
*   @return none
*   @use	Serial_SendOneByte(uint8_t Byte)
**********************************************************************************
*/
void Serial_SendOneByte(uint8_t Byte)
{
	//数据先被写入TDR,然后发到发送移位寄存器才能通过GPIO口输出
	USART_SendData(USART1, Byte);
	//此处作等待标志位之用,即发送数据寄存器空标志位,标志位会自动置0
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) != SET);
}

/*
**********************************************************************************
*   @brief  发送字符串
*   @param  一个十六进制数组
*   @return none
*   @use	Serial_SendArray(uint8_t *String)
**********************************************************************************
*/
void Serial_SendString(uint8_t *String)
{
	for(uint8_t i = 0; String[i] != '\0'; i++)
	{
		Serial_SendOneByte(String[i]);
	}
}
  1. 单字节串口接收
    有两种接收方法,查询法和中断法
  • 使用查询
    不需要配置中断,需要在主函数中不断判断RXNE标志位。
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
		{
			RxData = USART_ReceiveData(USART1);
			Serial_SendOneByte(RxData);
		}
  • 使用中断
    使用中断可使程序更加灵活
    代码如下
#include "Serial.h"
/*
**********************************************************************************
*   @brief  USART1串口初始化
*			PA9是USART1_TX,PA10是USART1_RX
*			GPIO是AHB1总线,USART1是APB2总线
*   @param  none
*   @return none
*   @use	Serial_Init()
**********************************************************************************
*/
void Serial_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	//USART1对应引脚复用映射
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
	//配置PA9、PA10引脚
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//配置USART1
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;											//波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		//硬件流控制,这里不选用
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;						//双工
	USART_InitStructure.USART_Parity = USART_Parity_No;									//校验位,这里不选用
	USART_InitStructure.USART_StopBits = USART_StopBits_1;								//1位长的停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;							//字长8位
	USART_Init(USART1, &USART_InitStructure);
	//配置USART中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);										//配置USART接收数据寄存器非空中断
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);										//选择第二组中断
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;									//配置USART1中断通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;										//使能NVIC
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;							//先占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;									//子优先级
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

/*
**********************************************************************************
*   @brief  发送单个字节数据
*   @param  一个十六进制数据
*   @return none
*   @use	Serial_SendOneByte(uint8_t Byte)
**********************************************************************************
*/
void Serial_SendOneByte(uint8_t Byte)
{
	//数据先被写入TDR,然后发到发送移位寄存器才能通过GPIO口输出
	USART_SendData(USART1, Byte);
	//此处作等待标志位之用,即发送数据寄存器空标志位,标志位会自动置0
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) != SET);
}

/*
**********************************************************************************
*   @brief  发送字符串
*   @param  一个十六进制数组
*   @return none
*   @use	Serial_SendArray(uint8_t *String)
**********************************************************************************
*/
void Serial_SendString(uint8_t *String)
{
	for(uint8_t i = 0; String[i] != '\0'; i++)
	{
		Serial_SendOneByte(String[i]);
	}
}

/*
**********************************************************************************
*   @brief  发送数字
*   @param  数字,以及数字长度
*   @return none
*   @use	Serial_SendNumber(uint32_t Number, uint8_t Length)
**********************************************************************************
*/
//求x的y次方
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;
	while (Y --)
	{
		Result *= X;
	}
	return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	//需要把Number的个位、十位、百位..以十进制拆开,然后换成字符数字对应的数据依次发出去
	for(uint8_t i = 0; i < Length; i ++)
	{
		Serial_SendOneByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

/*
**********************************************************************************
*   @brief  printf()函数重定向到串口,需要打开MicroLIB
*   @param  int ch, FILE *f
*   @return none
*   @use	printf("Num = %d\n", 666)
**********************************************************************************
*/
//重定向fputc函数到串口,通过串口发送。
//但此方法printf函数只能用于一个串口
//fputc是printf的底层
int fputc(int ch, FILE *f)
{
	Serial_SendOneByte(ch);
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	return ch;
}

uint8_t Serial_RxFlag;					//读取标志位
uint8_t Serial_RxData;					//读取数据

/*
**********************************************************************************
*   @brief  是否读取数据的标志位
*   @param 	none
*   @return 1表示正常读取,0表示读取失败
*   @use	Serial_GetRxFlag()
**********************************************************************************
*/
uint8_t Serial_GetRxFlag()
{
	if(Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

/*
**********************************************************************************
*   @brief  返回所读取的数据
*   @param 	none
*   @return 所读取的一个字节数据
*   @use	Serial_GetRxData()
**********************************************************************************
*/
uint8_t Serial_GetRxData()
{
	return Serial_RxData;
}

/*
**********************************************************************************
*   @brief 	中断函数
*   @param 	none
*   @return none
*   @use	中断事件触发会自动跳转到此函数
**********************************************************************************
*/
void USART1_IRQHandler()
{
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//如果正常读取RDR数据会自动清除标志位
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1656108.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NAT网络基本原理和认识,内网穿透的必备知识

NAT的基本介绍 NAT&#xff08;Network Address Translation&#xff09;是网络地址转换&#xff0c;它是一个IETF(Internet Engineering Task Force, Internet工程任务组)标准&#xff0c;允许一个整体机构以一个公用IP&#xff08;Internet Protocol&#xff09;地址出现在I…

二总线,替代传统485总线通讯,主站设计

二总线通信设计专栏 《二总线&#xff0c;替代传统485总线通讯&#xff0c;选型及应用-CSDN博客》《二总线&#xff0c;替代传统485总线通讯&#xff0c;低成本直流载波方案实现及原理-CSDN博客》《二总线&#xff0c;替代传统485总线通讯&#xff0c;调试避坑指南之最大的电流…

探索鸿蒙开发:鸿蒙系统如何引领嵌入式技术革新

嵌入式技术已经成为现代社会不可或缺的一部分。而在这个领域&#xff0c;华为凭借其自主研发的鸿蒙操作系统&#xff0c;正悄然引领着一场技术革新的浪潮。本文将探讨鸿蒙开发的特点、优势以及其对嵌入式技术发展的深远影响。 鸿蒙操作系统的特点 鸿蒙&#xff0c;作为华为推…

【Java】从0实现一个基于SpringBoot的个人博客系统

从0实现一个基于SpringBoot的个人博客系统 项目介绍准备工作数据准备创建项目准备前端页面编写配置文件 项目公共模块实体类公共层业务代码持久层实现博客列表实现博客列表约定前后端交互接口 实现博客详情约定前后端交互接口实现服务器代码 实现登录JWT令牌JWT令牌生成和校验实…

详解如何把文件或应用开机自启动

相信很多人都想把某些开机都要用的软件或文件打开&#xff0c;但不知道如何操作&#xff0c;或不知道可以手动设置开机自启动这一操作&#xff0c;下面为大家讲解实现思路&#xff1a; 1.首先打开winR打开运行框&#xff0c;在里面弄输入&#xff1a;shell:startup 2.回车进入文…

Win10/11共享文件夹,访问提示需要输入用户名密码

Win10/11共享文件夹&#xff0c;访问提示需要输入用户名密码 问题 已经关闭了密码保护共享&#xff0c;但是局域网其他电脑访问该文件夹&#xff0c;提示需要输入用户名和密码 解决方法 操作步骤 1.按WINR键打开运行&#xff0c;输入gpedit.msc打开本地组策略编辑器 2.按如…

Hive SQL-DML-Load加载数据

Hive SQL-DML-Load加载数据 在 Hive 中&#xff0c;可以使用 SQL DML&#xff08;Data Manipulation Language&#xff09;语句中的 LOAD 命令来加载数据到表中。LOAD 命令用于将本地文件系统或 HDFS&#xff08;Hadoop 分布式文件系统&#xff09;中的数据加载到 Hive 表中。 …

CSAPP | Floating Point

CSAPP | Floating Point b i b_i bi​ b i − 1 b_{i-1} bi−1​ … b 2 b_2 b2​ b 1 b_1 b1​ b 0 b_0 b0​ b − 1 b_{-1} b−1​ b − 2 b_{-2} b−2​ b − 3 b_{-3} b−3​ … b − j b_{-j} b−j​ S ∑ k − j i b k 2 k S\sum_{k-j}^{i}b_k\times2^k S∑k…

目标检测算法YOLOv6简介

YOLOv6由Chuyi Li等人于2022年提出&#xff0c;论文名为&#xff1a;《YOLOv6: A Single-Stage Object Detection Framework for Industrial Applications》&#xff0c;论文见&#xff1a;https://arxiv.org/pdf/2209.02976 &#xff0c;项目网页&#xff1a;https://github.c…

67万英语单词学习词典ACCESS\EXCEL数据库

这似乎是最多记录的英语单词学习词典&#xff0c;包含复数、过去分词等形式的单词。是一个针对想考级的人员辅助背单词学英语必备的数据&#xff0c;具体请自行查阅以下的相关截图。 有了数据才能想方设法做好产品&#xff0c;结合权威的记忆理论&#xff0c;充分调动用户的眼…

GD32F103RCT6/GD32F303RCT6(9)高级定时器互补PWM波输出实验

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 后续项目主要在下面该专栏中发布&#xff1a; 手把手教你嵌入式国产化_不及你的温柔的博客-CSDN博客 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转&#xff1a; 手把手教你嵌入式国产化-实战项目-无刷电机驱动&am…

Linux网络编程(四) 同时处理一个端口的UDP与TCP连接

从bind系统调用的参数来看&#xff0c;一个socket只能与一个socket地址绑定&#xff0c;即一个socket只能用来监听一个端口。因此&#xff0c;服务器如果要同时监听多个端口&#xff0c;就必须创建多个socket&#xff0c;并将它们分别绑定到各个端口上。这样一来&#xff0c;服…

Github下载的项目使用

根据该视频整理GitHub上的项目要怎么运行&#xff1f;一个视频教会你&#xff01;_哔哩哔哩_bilibili 方法一&#xff1a;从release中找。 方法二&#xff1a; 从官网中找&#xff08;位于右上角&#xff09; 方法三&#xff1a;看readme&#xff08;从readme中搜索以下词汇&a…

Milvus Cloud 的RAG 的广泛应用及其独特优势

一个典型的 RAG 框架可以分为检索器(Retriever)和生成器(Generator)两块,检索过程包括为数据(如 Documents)做切分、嵌入向量(Embedding)、并构建索引(Chunks Vectors),再通过向量检索以召回相关结果,而生成过程则是利用基于检索结果(Context)增强的 Prompt 来激…

【Qt 开发基础体系】字符串类应用和常用的数据类型

文章目录 1. Qt 字符串类应用1.1 操作字符串1.2 QString::append()函数1.3 QString::sprintf()函数1.4 QString::arg()函数 2. 查询字符串2.1 函数 QString::startsWith()2.2 函数 QString::contains()2.3 函数 QString::toInt()2.4 函数 QString::compare()2.5 将 QString 转换…

Github 50k star!吴恩达联合OpenAi共同编写<面向开发者的LLM入门教程> PDF推荐!

今天给大家推荐一本由吴恩达和OpenAI团队共同编写的关于大型语言模型&#xff08;LLM&#xff09;的权威教程<面向开发者的LLM入门教程>&#xff01;&#xff0c;在Github上已经高达50k star了&#xff0c;这含金量不用多说&#xff0c;在这里给大家强烈推荐一波&#xf…

电脑文件怎么加密?电脑涉密文件加密方法

文件加密是保护电脑涉密文件的重要方法&#xff0c;可以有效避免文件泄露风险。那么&#xff0c;电脑涉密文件该怎么加密呢&#xff1f;下面我们就来了解一下吧。 超级加密3000 在加密电脑涉密文件时&#xff0c;首先需要考虑的就是文件加密的安全性。因此&#xff0c;我们可以…

2024挂耳式耳机怎么选?5款高性价比开放式耳机推荐榜

近年来&#xff0c;开放式耳机受到了越来越多人的关注&#xff0c;特别是对于运动爱好者来说&#xff0c;在运动的过程中&#xff0c;传统的有线耳机不适合户外运动&#xff0c;不仅佩戴不稳&#xff0c;线还容易缠绕&#xff0c;而普通的蓝牙耳机长时间佩戴会感觉耳朵不适。在…

基于短时傅里叶变换域的一维信号邻域降噪方法(MATLAB)

基于傅里叶变换的信号频域表示及能量频域分布揭示了信号在频域的特征&#xff0c;但傅里叶变换是一种整体变换&#xff0c;只能了解信号的全局特性&#xff0c;不能有效检测信号频率随时间的变化情况。只有把时域和频域结合起来才能更好地反映非平稳信号的特征。时频分析的基本…

机器学习 - 梯度下降算法推导

要逐步推导多变量线性回归的梯度计算过程&#xff0c;我们首先需要明确模型和损失函数的形式&#xff0c;然后逐步求解每个参数的偏导数。这是梯度下降算法核心部分&#xff0c;因为这些偏导数将指导我们如何更新每个参数以最小化损失函数。 模型和损失函数 考虑一个多变量线…