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外设的结构和工作原理

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

1.3 串口通信数据帧

在这里插入图片描述
  上图展示了9位和8位发送/接收一个字节的时序。对于9位数据帧模式,可以配置为8位有效载荷+1位校验位,也可以配置为9位有效载荷,前者更加常用;对于8位的数据帧可以配置为7位有效载荷+1位校验位,也可以配置为8位有效载荷,后者更常用。可以通过配置选择最后一个数据位是否输出时钟。空闲帧和断开帧是和局域网协议相关的函数,这里仅作了解即可。
在这里插入图片描述
  上图展示了不同停止位的时序波形,

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

在这里插入图片描述
  STM32会以波特率的16倍频对输入信号进行采样。它会在第一次侦测到0后,开启起始位侦测,如果在第3、5、7次采样中至少有2个0,就认为收到了起始位,否则认为收到的是噪声。如果检测到了两个0和一个1,认为收到了起始位,但是会置NE(Noise Error)标志位为1来提醒用户可能存在噪声。如果通过了起始位侦测,外设会开始采样起始位,在第8、9、10次采样,并且要求3位中至少有两个0,之后的位也都在第8、9、10次进行采样,保证采样尽量在一位数据的正中间。如果三次检测不是全部相同,则NE会置1来提醒用户可能存在噪声。
在这里插入图片描述

1.5 波特率发生器

在这里插入图片描述

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

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

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

2.1 HEX数据包

在这里插入图片描述
  HEX数据包适合发送最原始的数据,例如一些使用串口通信的陀螺仪、温湿度传感器等。
  如果载荷数据可能存在与包头包尾重复的情况,可以采用以下的方法解决:

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

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

2.2 文本数据包

在这里插入图片描述

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

2.3 固定包长HEX数据包接收

  如果采用一个一个字节的接收方法,每接收一个数据需要进入中断,接收结束之后需要退出中断, 但是对于数据包来说,很显然包头包尾和有效载荷数据之间存在前后的关联性。对于包头、数据和包尾这三种状态,我们需要有不同的处理逻辑,在程序中就需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行数据的合理转移。这种程序设计思维,就称为“状态机”。
在这里插入图片描述

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

在这里插入图片描述
  由于文本传输有两个包尾,所以在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 <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, ...);

#endif

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

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 <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 Setial_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_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 <stdio.h>

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 <stdio.h>
#include <stdarg.h>

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 <stdio.h>

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 <stdio.h>
#include <stdarg.h>

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 <string.h>

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入门教程,欢迎大家一起交流学习。


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【2023 年第三届长三角高校数学建模竞赛】B 题 长三角新能源汽车发展与双碳关系研究 18页论文、数据和代码

【2023 年第三届长三角高校数学建模竞赛】B 题 长三角新能源汽车发展与双碳关系研究 18页论文、数据和代码 1 题目 《节能与新能源汽车技术路线图 2.0》提出至 2035 年&#xff0c;新能源汽车市场占比超过 50%&#xff0c;燃料电池汽车保有量达到 100 万辆&#xff0c;节能汽车…

商品价格怎么监测

电商已经深入人们的生活&#xff0c;现在无论何种类型的产品&#xff0c;总能在电商平台找到相应的产品链接&#xff0c;品牌为了扩大销售&#xff0c;也会以电商平台作为重要的销售战场&#xff0c;所以电商平台的商品价格也是品牌重点关注的&#xff0c;电商平台的低价会引起…

kafka消息队列最常用的两种模式,以及应用场景

目录 一、发布-订阅模式 二、点对点模式 三、应用场景 一、发布-订阅模式 发布-订阅模式是最常见的消息传递模式&#xff0c;其中消息发布者将消息发送到一个或多个主题&#xff08;Topic&#xff09;&#xff0c;而订阅者可以选择订阅一个或多个主题来接收消息。每个订阅者…

React(2)

题外话&#xff1a;vscode有个插件可以很方便的快速写代码 输入rcc回车 1.组件嵌套 import React, { Component } from reactclass Navbar extends Component{render(){return <div>Navbar</div>} }const Swiper()>{return <div>Swiper</div> }cons…

适合小公司的自动化部署脚本

背景&#xff08;偷懒&#xff09; 在小小的公司里面&#xff0c;挖呀挖呀挖。快挖不动了&#xff0c;一件事重复个5次&#xff0c;还在人肉手工&#xff0c;身体和心理就开始不舒服了&#xff0c;并且违背了个人的座右铭&#xff1a;“偷懒”是人类进步的第一推动力。 每次想…

资深测试总结,自动化测试-JSON+YAML+CSV+Excel数据驱动(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 数据驱动 在自动…

pytorch深度学习逻辑回归 logistic regression

# logistic regression 二分类 # 导入pytorch 和 torchvision import numpy as np import torch import torchvision from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import matplotlib.pyplot as …

android editText获取不到数据

问题分析&#xff1a;在onActivityCreated一开始就创建了findViewById&#xff0c;这时获取的是默认值&#xff0c;需要在点击按钮时重新加载才能获取到输入数据。 需要在点击按钮时重新加载数据&#xff1a;

ORACLE TO POSTGRESQL 来自2天上海的印象

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

.NET网络编程——TCP通信

一、网络编程的基本概念 : 1. 网络 就是将不同区域的电脑连接到一起&#xff0c;组成局域网、城域网或广域网。把分部在不同地理区域的计算机于专门的外部设备用通信线路 互联成一个规模大、功能强的网络系统&#xff0c;从而使众多的计算机可以方便地互相传递信息&#xff0c…

netty组件详解-上

netty服务端示例: private void doStart() throws InterruptedException {System.out.println("netty服务已启动");// 线程组EventLoopGroup group new NioEventLoopGroup();try {// 创建服务器端引导类ServerBootstrap server new ServerBootstrap();// 初始化服…

了解交换机接口的链路类型(access、trunk、hybrid)

上一个章节中讲到了vlan的作用及使用&#xff0c;这篇了解一下交换机接口的链路类型和什么情况下使用 vlan在数据包中是如何体现的&#xff0c;在上一篇的时候提到测试了一下&#xff0c;从PC1去访问PC4的时候&#xff0c;只从E0/0/2发送给了E0/0/3这是&#xff0c;因为两个接…

【SpringⅡ】简单高效地存储读取对象

目录 &#x1f9e5;1 配置扫描路径 &#x1f9e4;2 类注解实现 Bean 对象的存储 &#x1fa71;2.1 五大类注解的使用 &#x1f381;2.2 五大类注解之间的关系 &#x1f38f;2.3 Java 项目的标准分层 &#x1f383;3 方法注解实现 Bean 对象的存储 &#x1f388;3.1 Bean…

【论文阅读】一些多轮对话文章的体会 ACL 2023

前言 本文是对昨天看到的ACL 2023三篇多轮对话文章的分享这三个工作都是根据一些额外属性控制输出的工作&#xff0c;且评估的方面比较相似&#xff0c;可以借鉴 方法 这几篇文章都不是做general任务的&#xff0c;倾向于通过一些额外信息&#xff0c;来做specific任务 【1】…

【ceph】存储池pg个数如何设置

存储池pg个数如何设置 参考官方文档说明&#xff1a;https://old.ceph.com/pgcalc/参数说明TargePGs per OSD&#xff1a;每个OSD的pg数OSD#存储池包含osd个数%Data存储池写入数据占总OSD容量百分比Size存储池冗余数

vue2watch监听遇到的问题

1 vue 父组件里引入子组件 显示与隐藏是v-if控制时 父传入子的参数通过watch 监听请求接口时 watch 时而监听不到 请求接口的参数就不对 如图 父组件这么引入子组件v-show 和v-if 是有区别的 2 子组件通过watch 监听后 清空页面要展示的列表数据 重新从第一页加载数据&#x…

微服务sleuth+zipkin——链路追踪

一、链路追踪&#x1f349; 1.什么是链路追踪&#xff1f;&#x1f95d; 在大型系统的微服务化构建中&#xff0c;一个系统被拆分成了许多模块。这些模块负责不同的功能&#xff0c;组合成系统&#xff0c;最终可以提供丰富的功能。在这种架构中&#xff0c;一次请求往往需要…

简单了解UML类图

前言 大话设计中&#xff0c;多次使用UML类图来表示&#xff0c;并也给了基本的介绍&#xff0c;这里从书中选出UML图和代码做成笔记&#xff0c;以方便查找。 1、类 注意前面的符号&#xff1a; &#xff1a;public -&#xff1a;private #&#xff1a;protected 抽象类&…

在阿里云linux上安装MySql数据库

我们先远程连接服务器 然后输入 sudo yum update重新运行一下 然后 sudo yum install mysql-server安装 mysql 服务 其中有两次 y n 选择 都选y就好了 然后 运行 sudo service mysqld start启动MySql 然后 我们查看一下MySql sudo service mysqld status

Debian 12上如何关闭nobody共享文件夹,一个能让INSCODE AI 创作助手不知所措的小问题

这个问题之前在Debian 10和11上都没有遇到过&#xff0c;换上Debian 12后Samba的设置就出现了状况&#xff0c;装上Samba后什么都没有设置就在局域网可以看到&#xff1a; 根据之前的经验在/etc/samba/smb.conf里查了很久也没有看出所以然来&#xff0c;后来又问了INSCODE AI…