USART之串口发送+接收应用案例

news2024/11/14 20:48:11

文章目录

  • 前言
  • 一、电路接线图
  • 二、应用案例代码
  • 三、应用案例分析
    • 3.1 USART模块初始化
      • 3.1.1 RCC开启时钟
      • 3.1.2 GPIO初始化
      • 3.1.3 配置USART
      • 3.1.4 开启中断、配置NVIC
      • 3.1.5 开启USART
    • 3.2 USART串口收发模块
      • 3.2.1 Serial_SendByte(发送一个字节数据)
      • 3.2.2 USART1_IRQHandler(串口数据接收中断函数)


前言

提示:本文主要用作在学习江科大自化协STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者

本案例实现了一个stm32之USART串口发送与接收的功能。本文主要目的是想借着这个例子学习一下USART的配置以及使用,更多功能完善的串口代码放在文章最后,各位可自行根据需求获取。


一、电路接线图

本案例使用的USART为USART1,经查引脚定义表可知,USART1_TX对应PA9,USART1_RX对应PA10,所以USART1_TX(PA9)要接到USB转串口模块的RXD引脚,USART1_RX(PA10)要接到USB转串口模块的TXD引脚。
在这里插入图片描述

二、应用案例代码

Serial.h文件:

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);

#endif

Serial.c文件:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData;
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
	uint16_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char *String)
{
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++)
	{
		Serial_SendByte(String[i]);
	}
}

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)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void)
{
	if (Serial_RxFlag == 1)
	{
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

uint8_t Serial_GetRxData(void)
{
	return Serial_RxData;
}

void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

主程序main.c文件:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main(void)
{
	OLED_Init();
	OLED_ShowString(1, 1, "RxData:");
	
	Serial_Init();
	
	while (1)
	{
		if (Serial_GetRxFlag() == 1)
		{
			RxData = Serial_GetRxData();
			Serial_SendByte(RxData);
			OLED_ShowHexNum(1, 8, RxData, 2);
		}
	}
}

更多功能完善的串口工程如下:

1. stm32之USART串口收发HEX数据包
2. stm32之USART串口收发文本数据包

三、应用案例分析

在这里插入图片描述

  • 第一步,RCC开启时钟。把需要用到的USART和GPIO的时钟都打开。
  • 第二步,GPIO初始化。把TX配置成复用输出,RX配置成输入。
  • 第三步,配置USART。直接使用一个结构体就可以把参数都配置好了。
  • 第四步,开启中断,配置NVIC。
  • 第五步,开启USART。

初始化完成之后,发送数据调用USART_SendData()函数,接收数据在中断函数里调用USART_ReceiveData()函数就ok了。如果要获取发送和接收的状态,那就调用获取标志位的函数,这就是USART外设的使用思路。

老规矩,先来看一下USART的相关操作函数把,找到stm32f10x_usart.h文件,拖到最后。

其实很多库函数都是老套路就不细说了,比如这三个

void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);

我们主要看一下下面这两个重要的函数,USART_SendData发送数据,USART_ReceiveData接收数据。USART_SendData就是写DR寄存器,USART_ReceiveData就是读DR寄存器。DR寄存器内部有4个寄存器控制发送与接收,至于内部实现这里就不再分析了,我们只需要知道写DR就是发送,读DR就是接收即可。

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

ok,那我们开始进入正题!

3.1 USART模块初始化

3.1.1 RCC开启时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

都是常规的套路了,没什么好讲的了,需要注意的是,USART1是APB2的外设,这个不要搞错了。

3.1.2 GPIO初始化

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

这里讲一下引脚的模式。TX引脚是USART外设控制的输出脚,所以要选复用推挽输出。RX引脚是USART外设数据输入脚,所以要选择输入模式。输入模式并不分什么普通输入、复用输入,一根线只能有一个输出,但可以有多个输入,所以输入脚外设和GPIO都可以同时用。一般RX配置是浮空输入或者上拉输入,因为串口波形空闲状态是高电平,所以不使用下拉输入,我们在这里选择GPIO_Mode_IPU上拉输入模式。

3.1.3 配置USART

USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStructure);

参数解析如下:

  • USART_BaudRate :波特率。在这里我们可以直接写一个波特率的数值就行,比如9600
  • USART_HardwareFlowControl :硬件流控制。这里我们不使用流控USART_HardwareFlowControl_None
  • USART_Mode :串口模式。这里我们选择发送和接收模式USART_Mode_Tx | USART_Mode_Rx
  • USART_Parity :校验位。这里我们选择无校验USART_Parity_No
  • USART_StopBits :停止位。这里我们选择1位停止位USART_StopBits_1
  • USART_WordLength :字长,数据位。因为我们不需要校验,所以字长也就是数据位选择8位即可USART_WordLength_8b

3.1.4 开启中断、配置NVIC

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);

3.1.5 开启USART

USART_Cmd(USART1, ENABLE);

3.2 USART串口收发模块

3.2.1 Serial_SendByte(发送一个字节数据)

void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

调用USART_SendData函数发送一个字节数据到TDR数据寄存器,写完之后我们还需要等待一下,等到TDR的数据转移到了移位寄存器。这样才能保证每次调用Serial_SendByte函数是在上一次数据转移后的状态,要不然如果数据还在TDR进行等待,我们再写入数据,就会产生数据覆盖。所以在发送之后,我们还需要等待一下标志位,在这里调用USART_GetFlagStatus函数获取发送数据寄存器空标志位USART_FLAG_TXE意为Transmit data register empty flag。

最后,我们是否需要将标志位手动清除一下呢?经查手册可知,不需要我们手动清除

在这里插入图片描述

3.2.2 USART1_IRQHandler(串口数据接收中断函数)

void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		Serial_RxData = USART_ReceiveData(USART1);
		Serial_RxFlag = 1;
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

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

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

相关文章

JVM对象创建和内存分配机制深度解析

一、对象创建方式 1、new关键字 这是最常见的创建对象的方式。通过调用类的构造方法&#xff08;constructor&#xff09;来创建对象。如&#xff1a;MyClass obj new MyClass()。这种方式会触发类的加载、链接、初始化过程&#xff08;如果类还未被加载过的话&#xff09;&…

递归搜索与回溯专题篇一

目录 组合 目标和 组合总和 字母大小全排列 组合 题目 思路 解决这道题利用DFS&#xff0c;决策树是怎样的&#xff1f;以n4&#xff0c;k3为例&#xff1a; 因为每个数只用到一次&#xff0c;因此需要剪枝&#xff0c;将出现重复数字的枝剪掉&#xff0c;因为组合中元素的…

Vue中的this.$emit()方法详解【父子组件传值常用】

​在Vue中&#xff0c;this.$emit()方法用于触发自定义事件。它是Vue实例的一个方法&#xff0c;可以在组件内部使用。 使用this.$emit()方法&#xff0c;你可以向父组件发送自定义事件&#xff0c;并传递数据给父组件。父组件可以通过监听这个自定义事件来执行相应的逻辑。 …

【PyQt6 应用程序】QTDesigner生成ui文件转成py源码并执行

要使用Qt Designer设计的UI界面生成Python代码并执行需要遵循几个步骤。确保已经安装了PyQt6和Qt Designer。Qt Designer是一个强大的工具,允许通过拖放组件来设计GUI界面,而不需要手写所有的代码。安装PyQt6时 Qt Designer通常会一起被安装。 文章目录 使用Qt Designer设计U…

米联客FDMA3.2源码分析以及控制BRAM、DDR3读写验证

文章目录 一、FDMA简介二、读写操作时序2.1 写时序2.2 读时序 三、FDMA源码分析四、源码仿真验证4.1 FDMA控制代码4.2 系统框图4.3 仿真结果4.3.1 写通道4.3.2 读通道 五、使用FDMA控制BRAM读写测试5.1 系统框图5.2 读写数据控制模块5.3 仿真结果5.4 下板验证 六、使用FDMA控制…

快讯 | 美军500天AI计划启动,“破解AI“与“反AI“策略亮相

在数字化浪潮的推动下&#xff0c;人工智能&#xff08;AI&#xff09;正成为塑造未来的关键力量。硅纪元视角栏目紧跟AI科技的最新发展&#xff0c;捕捉行业动态&#xff1b;提供深入的新闻解读&#xff0c;助您洞悉技术背后的逻辑&#xff1b;汇聚行业专家的见解&#xff0c;…

VBA之正则表达式(46)-- 解析业务逻辑公式

实例需求&#xff1a;某业务系统的逻辑公式如下所示&#xff08;单行文本&#xff09;&#xff0c;保存在活动工作表的A1单元格中。 "DSO_90Day"->"FA_NoFunc"->"FCCS_No Intercompany"->"FCCS_Data Input"->"FCCS_…

<数据集>非洲动物识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1504张 标注数量(xml文件个数)&#xff1a;1504 标注数量(txt文件个数)&#xff1a;1504 标注类别数&#xff1a;4 标注类别名称&#xff1a;[buffalo, elephant, rhino, zebra] 序号类别名称图片数框数1buffalo3…

Java生成一个5位的随机验证码(大小写字母和数字)

生成验证码 内容&#xff1a;可以是小写字母&#xff0c;也可以是大写字母&#xff0c;还可以是数字 规则&#xff1a;长度为5 内容中四位字母&#xff0c;一位数字 其中数字只有一位&#xff0c;但是可以出现在任意位置。 package test;impo…

arm-Pwn环境搭建+简单题目

前言 起因是看到一篇IOT CVE的分析文章。 正好也在学pwn&#xff0c;arm架构的也是IOT这些固件最常用的&#xff0c;所以先安一个arm-pwn的环境。 环境搭建/调试 1. 安装 gdb-multiarch sudo apt-get install gdb-multiarch2. 安装qemu ctf的arm_pwn只需要安装qemu-user就…

结构体内存的对齐

结构体的对齐规则 第一个成员在结构体变量偏移量为0的地址处。 其他成员变量要对齐到某个数字&#xff08;对齐数&#xff09;的整数倍的地址处 1&#xff09; 对齐数 min( 编译器默认的一个对齐数, 该成员大小&#xff09;。 2&#xff09;默认的对齐数&#xff0c;可以通过宏…

kafka的12个重要概念

kafka的12个重要概念 1、服务器broker1.1、Broker 的主要功能1.2、Kafka Broker 的架构1.3、配置和管理1.4、高可用性和负载均衡1.5、总结 2、主题topic2.1、主要特点 3、事件Event4、生产者producer4.1、主要功能4.2、Producer 的配置选项4.3、Producer 的工作流程4.4、总结 5…

(javaweb)maven高级

目录 ​编辑 1.分模块设计与开发 2.继承与聚合--继承关系实现 3.继承与聚合--版本锁定 4.继承与聚合--聚合版本 5.私服 资源的上传与下载 1.分模块设计与开发 分模块&#xff1a;拆分成多个模块进行开发 不分模块&#xff1a;业务代码堆积成一个 不利于项目管理和维护并…

考研数学|零基础9月开始100天备考攻略

马上就要9月了&#xff0c;很多同学相比快要结束强化了&#xff0c;零基础的同学&#xff0c;进度可能会慢一些&#xff0c;但是别担心&#xff0c;考研数学的学习&#xff0c;进度不是最要紧的&#xff0c;学习效果才是&#xff01;千万不要比进度&#xff0c;也不要赶进度&am…

Linux中的PCI配置空间

在计算机系统中&#xff0c;PCI&#xff08;Peripheral Component Interconnect&#xff09;总线是一种用于连接硬件设备的标准接口。PCI总线提供了一个通用的、高性能的数据传输通道&#xff0c;广泛应用于PC系统和服务器中。在Linux操作系统中&#xff0c;PCI设备的配置空间是…

Modern C++——不准确“类型声明”引发的非必要性能损耗

大纲 案例代码地址 C是一种强类型语言。我们在编码时就需要明确指出每个变量的类型&#xff0c;进而让编译器可以正确的编译。看似C编译器比其他弱类型语言的编译器要死板&#xff0c;实则它也做了很多“隐藏”的操作。它会在尝试针对一些非预期类型进行相应转换&#xff0c;以…

JS脚本实现RPA模拟人工操作网页获取数据

一、首先我们可以根据查询条件去预置一个Excel&#xff0c;比如我们以公司名称为例。 二、然后我们用JS读取Excel内容&#xff0c;进行页面打开与条件记录 <!DOCTYPE html> <html> <div style"text-align: center;margin-top: 300px;"><input …

一款人性化的终端用户界面工具

A collection of human friendly terminal user interface. 截图 历史文件预览 注意: find file 依赖 fzf. file browser依赖 ranger / lf / … 安装 git clone https://github.com/StubbornVegeta/StartUp ~/.config/ cd ~/.config/StartUp ./install.sh用法 . $HOME/.…

【binder】【android12】【2.servicemanager启动——全源码分析】

系列文章目录 可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501 目录 …

浅谈【数据结构】树与二叉树二

目录 1、二叉排序树 1.1二叉树排序树插入 1.1.1两种插入方法 1.1.2循环法 1.1.3递归法 1.2二叉树的打印 1.3二叉树的结点删除 1.4销毁二叉树 1.5层次打印 谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注 没错&#xff0c;说的就是你&#xff0c;不用再怀疑&…