本篇文章包含的内容
- 一、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数据包适合发送最原始的数据,例如一些使用串口通信的陀螺仪、温湿度传感器等。
如果载荷数据可能存在与包头包尾重复的情况,可以采用以下的方法解决:
- 规定有效载荷数据的范围
- 增加包头包尾的数量,尽量使其产生载荷数据中不会出现的格式
- 尽量采用固定包长发送数据包,在接收数据时,我们不关心有效数据是否和包头包尾重复,我们只关心应该是包头包尾的位置是否是包头包尾。
在实际使用时,如果载荷数据不会和包头包尾重复,可以二者留其一,例如只添加包头或者只添加包尾。
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.c
(Key.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.c
(LED.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入门教程,欢迎大家一起交流学习。
原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、机器学习方面的学习笔记~