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.mzph.cn/news/3574.shtml

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

相关文章

使用Pandas计算两个系统客户名称的相似度

引言&#xff1a; 在日常业务处理中&#xff0c;我们经常会面临将不同系统中的数据进行匹配和比对的情况。特别是在涉及到客户管理的领域&#xff0c;我们需要确保两个系统中的客户记录是准确、一致和无重复的。 本文将介绍如何使用Python的Pandas库来处理这个问题。我们将以…

openpnp - 相机图像亮度太高的解决方法

文章目录 openpnp - 相机图像亮度太高的解决方法概述笔记设置相机的合适参数白平衡补充 - 用恒压横流降压模块调节底部相机环形灯的初步实验END openpnp - 相机图像亮度太高的解决方法 概述 看到同学在群里讨论问题, 说相机补光灯亮度太高了, 导致openpnp图像惨白惨白的, 根本…

园区电能监测系统

园区电能监测系统是一种能够对园区内电能使用情况进行实时监测和管理的系统&#xff0c;可以帮助企业更好地控制能源消耗&#xff0c;提高能源利用效率&#xff0c;从而降低能源成本&#xff0c;对于推进节能减排和可持续发展具有重要意义。 园区电能监测系统通常由多个子系统组…

css学习知识总结

一、css与html连接&#xff1a; 可以将css语句放在html内部&#xff0c;一般放在<head>之下&#xff0c;定义在<style>中&#xff0c;格式一般是一个“.”然后加上一个“名称”再加上一个“{}”&#xff0c;再在“{}”内部定义具体的语句。 二、调整元素 2.1 字体…

整数拆分(力扣)动态规划 JAVA

给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 10 输出: 36 解释: 10 3 3 4…

Python应用实例(二)数据可视化(二)

数据可视化&#xff08;二&#xff09; 1.随机漫步1.1 创建RandomWalk类1.2 选择方向1.3 绘制随机漫步图1.4 模拟多次随机漫步1.5 设置随机漫步图的样式 1.随机漫步 使用Python来生成随机漫步数据&#xff0c;再使用Matplotlib以引人瞩目的方式将这些数据呈现出来。随机漫步是…

使用 YOLOv8 和 Streamlit 构建实时对象检测和跟踪应用程序:第 1 部分-介绍和设置

示例:图像上的对象检测 介绍 实时视频中的目标检测和跟踪是计算机视觉的一个重要领域,在监控、汽车和机器人等各个领域都有广泛的应用。 由于需要能够识别和跟踪对象、确定其位置并对它们进行实时分类的自动化系统,对视频帧中的实时对象检测和跟踪的需求日益增加。 在这…

GitUI汉化

1.下载汉化文件 下载地址 备用下载地址 https://files.cnblogs.com/files/chenghu/git-gui-zh-master.zip https://files.cnblogs.com/files/chenghu/git-gui-zh-master.zip 2.找到git安装路径 C:\Program Files\Git\mingw64\share\git-gui\lib 3.解压出1下载的文件 复制粘…

VM(CentOS7安装和Linux连接工具以及换源)

目录 一、Linux意义 二、安装VMWare 三、centos7安装 1、正式安装CentOS7&#xff1a; 2、安装不了的解决方案 2.1常见问题——虚拟机开机就黑屏的完美解决办法 3、查看、设置IP地址 ① 查看ip地址&#xff1a;ip addr 或者 ifconfig&#xff0c; 注意与windows环境的区别…

宋浩线性代数笔记(一)行列式的计算

本帖更新b站宋浩老师的线代网课笔记&#xff0c;内容较为细致详细&#xff0c;参考书用的是科学出版社的第三版&#xff0c;之后会附加同济出版社第六版的教材内容。 &#xff08;字不好看大家将就看吧QAQ&#xff09;

当DevOps遇到AI,黑马迎来3.0时代丨IDCF

随着GhatGPT的爆火&#xff0c;人工智能和研发效能&#xff0c;无疑成为了2023的两个最重要的关键词。大规模语言模型LLM和相关应用的快速发展正在对研发团队的工作方式产生深远影响&#xff0c;这几乎象征着新的生产力革命的到来。 那么&#xff0c;作为一名工程师&#xff0…

java密码强度校验

一、代码 Testpublic void test(){//包含数字、大小写字母&#xff0c;长度10-20位 String regular "^(?.*\\d)(?.*[a-z])(?.*[A-Z]).{10,20}$";String example1 "1234567891";System.out.println(example1.matches(regular)); //falseString exa…

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…

数据库信息速递 MONGODB 6.0 的新特性,更多的查询函数,加密查询,与时序数据集合 (译)...

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

Spring实现文件上传,文件上传

第一步&#xff1a;创建jsp文件 创建form表单 提交文件是post 文件上传的表单 服务端能不能获得数据&#xff0c;能 实现单文件上传的步骤&#xff1a; 导入相应的坐标&#xff1a;在pom.xml文件中进行导入 再导入这份&#xff1a; 第二步&#xff0c;在spring-MVC的上传中去配…

2023云曦期末复现

目录 WEB sign SSTI serialize WEB sign 有10000个 进行bp爆破 能发现 410 和 414长度 还有 420 410 414存在16进制的字符 拼凑出来为 \x66\x6c\x61\x67\x7b\x61\x63\x63\x39\x39\x66\x39\x30\x34\x66\x30\x65\x61\x66\x61\x34\x31\x63\x30\x36\x34\x33\x36\x38\x31\x3…

less 笔记

1 margin-left 之后有 margin: 0 导致margin-left 无效 --> 不能重复定义 .btn-group {margin-left: calc(100% - 350px);display: inline-block;margin: 0; // 重复定义 导致上面 没有效果padding: 0; } 2 一定要F12检查元素 看各个div的宽度是否太长 导致靠左靠右 计算不…

数据库锁机制

锁机制 1. 概述2. 并发事务的不同场景2.1 读-读情况2.2 写-写情况2.3 读-写或写-读情况2.3.1 方案一&#xff1a;读事务使用MVCC&#xff08;多版本并发控制&#xff09;&#xff0c;写事务加锁2.3.2 方案二&#xff1a;读、写事务均加锁 3. 锁分类3.1 从数据操作类型&#xff…

MySQL学习笔记

文章目录 1、登录2、查看数据库3、连接数据库4、查看表5、退出6、创建/删除数据库7、创建表8、数据类型9、查看表中的内容10、向表中插入数据11、约束主键&#xff1a;主键&#xff08;PRIMARY KEY&#xff09;作为数据表中一行数据的唯一标识符&#xff0c;在一张表中通过主键…

e2e测试框架之Cypress

谈起web自动化测试&#xff0c;大家首先想到的是Selenium&#xff01;随着近几年前端技术的发展&#xff0c;出现了不少前端测试框架&#xff0c;这些测试框架大多并不依赖于Selenium&#xff0c;这一点跟后端测试框架有很大不同&#xff0c;如Robot Framework做Web自动化测试本…