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


前言

提示:本文主要用作在学习江科大自化协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);
	}
}
协议说明: CCU向各个终端所发的数据格式有3种:查询,令牌,广播 查询:CCU向各个终端一对一发送一串数据,CCU在发送完后,终端在一定时间内拥有发言权。 令牌:CCU发出令牌命令后,各个终端收到自己的令牌帧后,拥有总线的发言权, 必须在一定时间内发出帧头,否则,CCU取消该终端的发言权。发言完或者没有发言, 把令牌在规定的时间内传给下一个终端 广播:CCU发出广播帧后,各个终端必须按照广播帧工作,不要回复CCU,也没有总线发言权 格式: 查询:7E, 命令,目标网络,地址,数据长度,数据,校验,7E 令牌:7E,命令,当前虚拟地址,令牌,校验,7E 广播; 7E, FF, FF, FF,数据长度,数据,校验,7E 数据格式说明: 1,7E为帧头,帧尾标志。如果在数据里面遇到有7E,将数据7E拆分为7F,80,如 果数据里面有7F,将7F拆分为7F,81.在接收时,将上面数据合成相应的数。 2,目标网络:为各个终端所在的网络。T/R0 为00,T/R 为01,T/R2 为02,T/R3 为03,T/R4为04,FF为全局广播。 3,地址:为各个受控设备物理地址。如果全局广播就为FF。 4,命令:00为CCU查询各个终端。01为各个终端回复CCU查询。02为令牌命令。 全局广播为FF。 5,数据长度,数据的长度。 6,数据,即要发送的数据。 7, 校验:两个7E之间除了校验的所有数据相加,0X55减去这个数得到的是校验值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值