STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)

本篇文章包含的内容

  • 一、STM32的USART外设
    • 1.1 STM32的USAER外设简介
    • 1.2 USART外设的结构和工作原理
    • 1.3 串口通信数据帧
    • 1.4 起始位侦测和USART的噪声判断机制
    • 1.5 波特率发生器
  • 二、串口发送和接收数据包
    • 2.1 HEX数据包
    • 2.2 文本数据包
    • 2.3 固定包长HEX数据包接收
    • 2.4 可变包长文本数据包接收
  • 三、代码实现
    • 3.1 常用库函数
    • 3.2 使用串口发送数据
    • 3.3 使用串口接收数据
    • 3.4 串口收发HEX数据包
    • 3.5 串口发送文本数据包实现简单的人机交互

​  本次课程采用单片机型号为STM32F103C8T6。
​  课程链接:江协科技 STM32入门教程


  往期笔记链接:
  STM32学习笔记(一)丨建立工程丨GPIO 通用输入输出
  STM32学习笔记(二)丨STM32程序调试丨OLED的使用
  STM32学习笔记(三)丨中断系统丨EXTI外部中断
  STM32学习笔记(四)丨TIM定时器及其应用(定时中断、内外时钟源选择)
  STM32学习笔记(五)丨TIM定时器及其应用(输出比较丨PWM驱动呼吸灯、舵机、直流电机)
  STM32学习笔记(六)丨TIM定时器及其应用(输入捕获丨测量PWM波形的频率和占空比)
  STM32学习笔记(七)丨TIM定时器及其应用(编码器接口丨用定时器实现编码器测速)
  STM32学习笔记(八)丨ADC模数转换器(ADC单、双通道转换)
  STM32学习笔记(九)丨DMA直接存储器存取(DMA数据转运、DMA+AD多通道转换)
  STM32学习笔记(十)丨I2C通信(使用I2C实现MPU6050和STM32之间通信)
  STM32学习笔记(十一)丨SPI通信(W25Q64芯片简介,使用SPI读写W25Q64存储器芯片)
  STM32学习笔记(十二)丨RTC实时时钟


一、STM32的USART外设

1.1 STM32的USAER外设简介

  USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器。USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。

  • 自带波特率发生器,最高达4.5Mbits/s

  • 可配置数据位长度(8/9)(包含校验位的长度)、停止位长度(0.5/1/1.5/2)

  • 可选校验位(无校验/奇校验/偶校验)

  • 支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN

    • 同步模式:STM32的同步模式只有时钟输出,没有时钟输入,所以并不是完全的同步通信模式。
    • 硬件流控制:由一根专用的状态线来接收数据流控制信号,接收的一方会将准备好信号通过状态线反馈给发送方,发送方只在接收方准备好后才开始发送数据,可以解决由于接收方处理速度慢而导致的数据丢失的问题。
    • DMA:串口支持DMA转运数据。
    • 智能卡、IrDA、LIN:这些通信协议和串口非常相似,在串口的基础上稍作配置即可实现这些协议的通信。
  • STM32F103C8T6 USART资源: USART1(APB2)、 USART2、 USART3(APB1)

1.2 USART外设的结构和工作原理

STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第1张图片
  在USART外设中,发送数据寄存器(TDR,只写)和接收数据寄存器(RDR,只读)公用同一个地址。发送数据寄存器(TDR)将数据转运到发送移位寄存器后,会置TxE标志位为1,我们检测这个标志位就可以写入下一个数据了,这样可以保证数据的连续不间断的传输,提高了串口传输数据的工作效率;同理,当接收移位寄存器接收到一个完整的数据后,就会把一个字节的数据整体转移到接收数据寄存器(RDR)中,同时置RxNE标志位为1,读取并判断这个标志位就可以及时读走数据。
  关于硬件流控制,nRTS(Request To Send)是请求发送引脚,它是一个输出引脚,用于告诉对方当前我是否能够接收;nCTS(Clear To Send)是清除发送引脚(清除发送数据寄存器,意思即将数据发送出来),是一个输入硬件,用来接收对方的nRTS信号。
  STM32的时钟输出功能主要有以下用途:可以用来兼容别的协议(例如SPI),还可以实现自适应波特率的传输,如果接收方无法确定发送过来的数据是什么波特率,就可以计算这个时钟的周期,来确定波特率,不过这就需要额外的代码来完成功能。
  图中的唤醒单元可以实现串口多设备。在开始通信前,主机给每个从机设定一个地址,当通信地址和从机地址相同时,唤醒单元开始工作,没有收到地址时就保持沉默,这样就可以实现一条总线上挂载多设备的通信。
STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第2张图片

1.3 串口通信数据帧

STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第3张图片
  上图展示了9位和8位发送/接收一个字节的时序。对于9位数据帧模式,可以配置为8位有效载荷+1位校验位,也可以配置为9位有效载荷,前者更加常用;对于8位的数据帧可以配置为7位有效载荷+1位校验位,也可以配置为8位有效载荷,后者更常用。可以通过配置选择最后一个数据位是否输出时钟。空闲帧和断开帧是和局域网协议相关的函数,这里仅作了解即可。
STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第4张图片
  上图展示了不同停止位的时序波形,

1.4 起始位侦测和USART的噪声判断机制

STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第5张图片
  STM32会以波特率的16倍频对输入信号进行采样。它会在第一次侦测到0后,开启起始位侦测,如果在第3、5、7次采样中至少有2个0,就认为收到了起始位,否则认为收到的是噪声。如果检测到了两个0和一个1,认为收到了起始位,但是会置NE(Noise Error)标志位为1来提醒用户可能存在噪声。如果通过了起始位侦测,外设会开始采样起始位,在第8、9、10次采样,并且要求3位中至少有两个0,之后的位也都在第8、9、10次进行采样,保证采样尽量在一位数据的正中间。如果三次检测不是全部相同,则NE会置1来提醒用户可能存在噪声。
STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第6张图片

1.5 波特率发生器

STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第7张图片

  • 发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
  • 计算公式:波特率 = f_PCLK2/1 / (16 * DIV),可以通过这个公式计算对应波特率的DIV。用库函数配置可以省去这个过程。

二、串口发送和接收数据包

  在实际应用中,我们常常需要连续发送或接收数据,有时需要对连续的数据进行分割和打包,我们才可以正确处理数据;使用数据包发送和接收数据还可以实现简单的人机交互设计。
  打包和分割数据的方法可以自行设计,串口的数据包采用添加包头和包尾的方式实现。

2.1 HEX数据包

STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第8张图片
  HEX数据包适合发送最原始的数据,例如一些使用串口通信的陀螺仪、温湿度传感器等。
  如果载荷数据可能存在与包头包尾重复的情况,可以采用以下的方法解决:

  1. 规定有效载荷数据的范围
  2. 增加包头包尾的数量,尽量使其产生载荷数据中不会出现的格式
  3. 尽量采用固定包长发送数据包,在接收数据时,我们不关心有效数据是否和包头包尾重复,我们只关心应该是包头包尾的位置是否是包头包尾。

  在实际使用时,如果载荷数据不会和包头包尾重复,可以二者留其一,例如只添加包头或者只添加包尾。

2.2 文本数据包

STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第9张图片

  在字符模式下,由于有大量的字符可以使用,就可以很好地避免数据和包头包尾重复的问题。使用文本数据包直观易理解,非常灵活,可以很方便的实现一些人机交互的需求,例如蓝牙模块常用的AT指令,CNC和3D打印机常用的G代码等;但是解析效率低。所以需要根据实际场景来选择数据包格式。

2.3 固定包长HEX数据包接收

  如果采用一个一个字节的接收方法,每接收一个数据需要进入中断,接收结束之后需要退出中断, 但是对于数据包来说,很显然包头包尾和有效载荷数据之间存在前后的关联性。对于包头、数据和包尾这三种状态,我们需要有不同的处理逻辑,在程序中就需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行数据的合理转移。这种程序设计思维,就称为“状态机”。
STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第10张图片

2.4 可变包长文本数据包接收

STM32学习笔记(十三)丨USART通用同步/异步收发器(串口外设的基本使用丨串口发送数据、串口发送+接收数据)_第11张图片
  由于文本传输有两个包尾,所以在S1状态下,接收数据和等待包尾需要同时进行,在S2状态需要等待第二个包尾。

三、代码实现

3.1 常用库函数

// 缺省结构体配置
void USART_DeInit(USART_TypeDef* USARTx);

// 初始化函数
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);

// 结构体初始化
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);

// 同步时钟输出配置
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);

// 开启USART
void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);

// 中断配置,接收时使用
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);

// DMA通道配置
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);

// 发送数据
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);

// 接收数据
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

// 中断标志位相关
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

3.2 使用串口发送数据

  • Setial.h
#ifndef __Serial_H_
#define __Serial_H_

#include 

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, ...);

#endif

  • Serial.c
#include "stm32f10x.h"                  // Device header
#include 
#include 

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_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	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_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_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)
{
	uint16_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');
	}
}


// printf函数重定向,在使用前需要在魔术棒中点击MicroLIB
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

// 封装sprintf()函数
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);
}

  • main.c
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Serial.h"

int main()
{
	OLED_Init();
	
	Serial_Init();
	Serial_SendByte('A');
	
	uint8_t MyArray[] = {0x42, 0x43, 0x44, 0x45};
	
	Serial_SendArray(MyArray, 4);
	
	Serial_SendString("\r\nHello world!\r\n");
	
	Serial_SendNumber(12345, 5);
	
	printf("\r\nNUM = %d\r\n", 666);

	char String[100];
	// sprintf()函数可以把格式化字符之后的内容转移到对应的字符串中,解决了多个串口无法都重定向printf的问题
	sprintf(String, "Num = %d\r\n", 333);		
	Serial_SendString(String);
	
	Serial_Printf("\r\n你好,世界");	// UTF-8,需要在编译器中添加--no-multibyte-chars指令
	
	while(1)
	{

	}
}

3.3 使用串口接收数据

  当接受的数据比较简单时,在主循环中可以采用这样的方法来查询标志位以接收数据:

while(1)
{
	if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
	{
		RxData = USART_ReceiveData(USART1);
		OLED_ShowHexNum(1, 1, RxData, 2);
	}
}

  下面展示利用中断接收数据的方法:

  • Serial.h
#ifndef __Serial_H_
#define __Serial_H_

#include 

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 Setial_GetRxData(void);

#endif

  • Serial.c
#include "stm32f10x.h"                  // Device header
#include 
#include 

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_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 	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)
{
	uint16_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');
	}
}


// printf函数重定向,在使用前需要在魔术棒中点击MicroLIB
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

// 封装sprintf()函数
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 Setial_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 "OLED.h"
#include "Serial.h"

uint8_t RxData;

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

3.4 串口收发HEX数据包

  • Serial.h
#ifndef __Serial_H_
#define __Serial_H_

#include 

extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];

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, ...);

void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);

#endif

  • Serial.c
#include "stm32f10x.h"                  // Device header
#include 
#include 

uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
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_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 	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)
{
	uint16_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');
	}
}


// printf函数重定向,在使用前需要在魔术棒中点击MicroLIB
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

// 封装sprintf()函数
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);
}

void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);
	Serial_SendArray(Serial_TxPacket, 4);
	Serial_SendByte(0xFE);
}

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

// 中断服务函数
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;
	static uint8_t pRxState = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		uint8_t RxData = USART_ReceiveData(USART1);
		
		if (RxState == 0)	// 等待包头
		{
			if (RxData == 0xFF)
			{
				RxState = 1;
				pRxState = 0;
			}
		}
		else if (RxState == 1)	// 获取数据
		{
			Serial_RxPacket[pRxState] = RxData;
			pRxState ++;
			if (pRxState >= 4)
			{
				RxState = 2;
			}
		}
		else if (RxState == 2)	// 等待包尾
		{
			if (RxData == 0xFE)
			{
				RxState = 0;
				Serial_RxFlag = 1;
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		// 读取DR,RXNE会自动清0
	}
}

  • main.cKey.c/.h文件略)
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Serial.h"
#include "Key.h"

uint8_t RxData, KeyNum;

int main()
{
	OLED_Init();
	Key_Init();
	Serial_Init();
	
	OLED_ShowString(1, 1, "TxData:");
	OLED_ShowString(3, 1, "RxData:");
	
	Serial_TxPacket[0] = 0x01;
	Serial_TxPacket[1] = 0x02;
	Serial_TxPacket[2] = 0x03;
	Serial_TxPacket[3] = 0x04;
	
	Serial_SendPacket();
	
	while(1)
	{
		KeyNum = Key_GetNum();
		if (KeyNum == 1)
		{
			Serial_TxPacket[0] ++;
			Serial_TxPacket[1] ++;
			Serial_TxPacket[2] ++;
			Serial_TxPacket[3] ++;
			Serial_SendPacket();
		}
		
		OLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2);
		OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2);
		OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2);
		OLED_ShowHexNum(2, 10, Serial_TxPacket[3], 2);
		if (Serial_GetRxFlag() == 1)
		{
			OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2);
			OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2);
			OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2);
			OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2);
		}
	}
}

3.5 串口发送文本数据包实现简单的人机交互

  • Serial.h
#ifndef __Serial_H_
#define __Serial_H_

#include 

extern char Serial_RxPacket[];
extern uint8_t Serial_RxFlag;

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);

#endif

  • Serial.c
#include "stm32f10x.h"                  // Device header
#include 
#include 

char Serial_RxPacket[100];
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_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 	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)
{
	uint16_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');
	}
}


// printf函数重定向,在使用前需要在魔术棒中点击MicroLIB
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);
	return ch;
}

// 封装sprintf()函数
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);
}

// 中断服务函数
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;
	static uint8_t pRxState = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		uint8_t RxData = USART_ReceiveData(USART1);
		
		if (RxState == 0)	// 等待包头
		{
			if (RxData == '@' && Serial_RxFlag == 0)	// 只有上一个数据处理完才会接收,方式指令被覆盖的问题
			{
				RxState = 1;
				pRxState = 0;
			}
		}
		else if (RxState == 1)	// 获取数据并等待第二个包尾
		{
			if (RxData == '\r')
			{
				RxState = 2;
			}
			else 
			{
				Serial_RxPacket[pRxState] = RxData;
				pRxState ++;
			}
		}
		else if (RxState == 2)	// 等待包尾
		{
			if (RxData == '\n')
			{
				RxState = 0;
				Serial_RxPacket[pRxState] = '\0';	// 手动添加字符串结束标志位
				Serial_RxFlag = 1;
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		// 读取DR,RXNE会自动清0
	}
}

  • main.cLED.c/.h略,执行电灯操作即可)
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include 

uint8_t RxData, KeyNum;

int main()
{
	OLED_Init();
	Serial_Init();
	
	LED_Init();
	
	OLED_ShowString(1, 1, "TxData:");
	OLED_ShowString(3, 1, "RxData:");
	
	while (1)
	{
		if (Serial_RxFlag == 1)
		{
			OLED_ShowString(4, 1, "                ");
			OLED_ShowString(4, 1, Serial_RxPacket);
			
			if (strcmp(Serial_RxPacket, "LED_ON") == 0)
			{
				LED1_ON();
				Serial_SendString("LED_ON_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_ON_OK");
			}
			else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)
			{
				LED1_OFF();
				Serial_SendString("LED_OFF_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_OFF_OK");
			}
			else 
			{
				Serial_SendString("ERROR_COMMAND\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "ERROR_COMMAND");
			}
			
			Serial_RxFlag = 0;
		}
	}
}


​  课程链接:江协科技 STM32入门教程,欢迎大家一起交流学习。


  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记~

你可能感兴趣的:(STM32,学习笔记,stm32,单片机,嵌入式硬件)