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

文章目录

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

c 复制代码
#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文件:

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文件:

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文件,拖到最后。

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

c 复制代码
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就是接收即可。

c 复制代码
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

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

3.1 USART模块初始化

3.1.1 RCC开启时钟

c 复制代码
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

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

3.1.2 GPIO初始化

c 复制代码
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

c 复制代码
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

c 复制代码
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

c 复制代码
USART_Cmd(USART1, ENABLE);

3.2 USART串口收发模块

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

c 复制代码
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(串口数据接收中断函数)

c 复制代码
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);
	}
}
相关推荐
BigShark8886 小时前
2025蓝桥杯(单片机)备赛--扩展外设之I2C的重要应用--PCF8591(八)
单片机·职场和发展·蓝桥杯
ID2024101322068 小时前
单电源运放
单片机·嵌入式硬件
linux_carlos11 小时前
#lwIP 的 Raw API 使用指南
stm32·单片机·mcu·物联网·rtdbs
Graceful_scenery11 小时前
STM32F103系统时钟配置
stm32·单片机·嵌入式硬件
姓刘的哦11 小时前
MCU中的定时器
单片机·嵌入式硬件
xcx00312 小时前
应用于各种小家电的快充协议芯片
单片机·嵌入式硬件·物联网
what&&why13 小时前
stm32与ht7038的项目
stm32·单片机·嵌入式硬件
BigShark88814 小时前
2025蓝桥杯(单片机)备赛--扩展外设之NE555的使用及定时器1的详细讲解(十)
单片机·职场和发展·蓝桥杯
lucy1530275107915 小时前
【青牛科技】芯麦 GC2003:白色家电与安防领域中 ULN2003 的理想替代者
人工智能·科技·单片机·物联网·机器学习·安防·白色家电
Jack1530276827915 小时前
【青牛科技】D7312带 ALC 双通道前置放大器电路
人工智能·科技·单片机·嵌入式硬件·智能路由器·收录机