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


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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简单认识MySQL数据库日志和数据的备份恢复

文章目录 Mysql 备份与还原一、数据备份的重要性二、数据库备份类型1 、物理备份2 、逻辑备份 三、常见的备份方法1、 物理冷备2、 专用备份工具 mysqldump 或 mysqlhotcopy3、 启用二进制日志进行增量备份3.4 第三方工具备份 四、MySQL完全备份1、简介2、优点&#xff1a;3、缺…

SAP ABAP 报表程序实现下载文件及上传 Excel 并解析

步骤1&#xff1a; 事务代码 SMW0 选择二进制数据选项点击上方按钮。 点击新建按钮输入名称和描述&#xff0c;上传模版文件。 案例传入 EXCEL 如下&#xff1a; 创建好资源库对象结果如下。 步骤2&#xff1a;报表效果展示 点击按钮选择上传的文件。 解析 Excel 文件结果…

visual studio配置调用c++ dll opencv为例

1&#xff0c;配置VC目录&#xff0c;包含目录和库目录。 2&#xff0c;链接器->输入->包含目录 3&#xff0c;生成目录下包含对应的dll文件 4&#xff0c;需注意对应的Debug&#xff0c;Release及X86&#xff0c;X64选项

创建型模式 - 建造者模式

概述 将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于&#xff1a;某个对象的构建过程复杂的情况。 由于实现了构建和装配的解耦。…

Java基础---常用类大全以及各数据结构的方法大全

目录 前言 一、Math类 二.Scanner类 三、String类、StringBuilder和StringBuffer类 &#x1f496;String类 &#x1f496;StringBuilder和StringBuffer 四.Arrays类 五.Random类 六.时间类 七.ArrayList顺序表 八、LinkedList与链表 九.Stack栈和Queue队列 十.Pri…

SpringCloud Alibaba——Ribbon的属性配置和类配置优先级

目录 一、Ribbon的属性配置和类配置哪个优先级高二、Ribbon的属性配置和类配置优先级源码解读 一、Ribbon的属性配置和类配置哪个优先级高 类配置优先级高 二、Ribbon的属性配置和类配置优先级源码解读 通过RibbonClientConfiguration类中的ribbonRule方法可知&#xff0c;优…

guava限流器RateLimiter使用简介(Springboot实现)

在大型分布式系统中&#xff0c;限流是一种重要的防护机制&#xff0c;可以帮助我们控制流量并减轻系统的负担。Google的Guava库提供了一种方便的限流器实现&#xff0c;可以帮助我们轻松地实现限流功能。本文将介绍Guava中限流器的基本概念和使用方法。 一、什么是限流器&…

实现小程序商城首页【源码公开】

效果图 页面源码 <view class"index-container"><view class"header"><!--搜索框【仅样式&#xff0c;不做处理】 start--><van-search bindtap"clickSearch" disabled shape"round" background"#9c7bf0&q…

GPT与人类:人工智能是否能够真正复制人类语言?

人类语言是一种复杂的系统&#xff0c;它不仅包含着无数单词和语法规则&#xff0c;更重要的是具有丰富的含义和上下文。这些语言特征涉及到常识、文化、情感和经验等方面&#xff0c;是人类在长期进化和文明发展中所积累起来的丰富知识和经验的体现。然而&#xff0c;人工智能…

Java对日志文件进行加密

最近碰到了一个新的需求&#xff0c;生产环境中Java程序部署的服务器会定期清理数据&#xff0c;需要将保存在程序所在服务器上的日志文件挂载到网盘上&#xff0c;但又不想让用户看到日志文件中的信息&#xff0c;因此需要对日志文件中的内容进行加密。 这里&#xff0c;并不是…

2023牛客暑期多校训练营1(D/H/J/K)

目录 D.Chocolate H.Matches J.Roulette K.Subdivision D.Chocolate 思路&#xff1a;当n1且m1时候先手必输&#xff0c;然后1*k&#xff08;k>2&#xff09;的情况下后手必输&#xff0c;因为先手可以选到只剩下一个格子。而在其它情况里先手第一步可以先选(1,1)的格子…

ARM 架构是什么?

ARM&#xff08;Advanced RISC Machines&#xff09;架构是一种处理器架构&#xff0c;它是一种精简指令集计算机&#xff08;RISC&#xff09;架构。ARM架构最初由ARM Holdings&#xff08;现在是SoftBank Group的子公司&#xff09;开发&#xff0c;并在1980年代末和1990年代…