STM32ZET6-USART使用

一、原理说明

STM32自带通讯接口

 通讯目的

通信方式:

全双工:通信时可以双方同时通信。

半双工:通信时同一时间只能一个设备发送数据,其他设备接收。

单工:只能一个设备发送到另一个设备,例如USART只有TX线。

时钟类型:

异步:约定传输速率,例如波特率相同,约定起始信号(和结束信号、帧头帧尾等进行约束和校验)进行传输。

优点是节省了硬件资源,缺点是在传输过程中停止会传输失败,不能中途暂停,而且难以用软件模拟,只能使用硬件传输。

同步:有一根单独的时钟线,通过时钟信号控制传输时的速率。

优点是在传输过程中可以中断(停止时钟信号即可停止),停止后可以继续传输,并且可以使用软件模拟时钟信号和通信,无硬件资源时可以牺牲引脚来使用。

传输类型:

单端:通过信号和GND的电平差来区分高低电平,需要把通信双方的GND接到一起,否则容易受到干扰。

差分:通过两根线的差分电平来判断高低电平通信,抗干扰信号更强,传输距离更远。

(USB尽量共地)

设备拓扑类型:

点对点:设备1-1通信,可能需要寻址,以确定

点对多:设备1-n通信

多对多:设备n-n通信

串口协议:

CH340可以将串口转换为USB协议,可以直接插在电脑上。

陀螺仪传感器可以测量角速度加速度等姿态参数,可以接串口和I2C。 

蓝牙串口模块,可以和蓝牙设备互联,实现手机遥控单片机的功能。

串口接线图:

电平标准:

串口协议的软件定义:

RB8和TB8是奇偶校验位,可选择是否使用。一般需要校验位,则选择9bit数据,不需要校验位则选择8bit。

波特率:每秒传输码元的个数,可能每个码元包含信息量不止1bit,单位为码元/s,或者为bund。

比特率:每秒传输bit的速率。单位bit/s,或者bps。在二进制调制的情况下一个码元就是一个bit。

奇偶校验:

奇校验表示,包括校验位和数据位,发送的1为奇数个。例如发送0x0F,则发送 111100001

串口波形实例:

起始位为低电平,默认线上高电平,结束位为高电平(方便区分两个连续数据包)。

 STM32的USART外设:

UART和USART类似,但是只支持时钟输出,不支持时钟输入。

同步模式是支持CLK时钟输出、硬件流控制表示传输时可通过从设备反馈 来控制主设备的发送 防止从设备处理慢导致数据丢失。DMA是串口支持DMA数据转运。STM32的串口协议也可支持智能卡、IrDA(红外通信)、LIN(局域网)等设备。

STM32-USART内部原理:

 发送数据寄存器TDR和接收数据寄存器RDR使用同一块存储区域。nRTS高速主机当前是否可以接收数据。nCTS用于接收从机nRTS信号的引脚,判断从机当前是否可以接收。n代表低电平有效。串口使用SCLK可以兼容SPI。若不知道对方波特率,可以通过SCLK计算得到并传出输出。

唤醒单元用来实现串口挂载多设备,可以给串口分配地址。总线上对应发送的地址设备会进行通信。

TXE发送寄存器空。

RXNE接收寄存器非空。

USART波形及配置: 

 一般配置9bit字长,都会使用校验位。若不是用校验位,则第九位为载荷bit。

选择8bit字长最好无校验位,若有校验位,那么有效载荷7bit。每次发送不够1byte。

 停止位高电平,一般用1bit。

USART结构图:

  USART起始位侦测原理:

 波特率分频(分频后还有16倍分频):

 例如USART1波特率9600,那么9600 = 72000000/(16*DIV);DIV = 468.785。放入寄存器为111010100.11

USB转串口模块的内部电路图:

不同数据模式:

二、实例

1、串口发送,OLED接收

接线图:

 引脚定义:

重构printf函数:

printf输出汉字需要增加--no-multibyte-chars配置,防止中文乱码

--no-multibyte-chars

 

接收时需要用相同的编码方式 

 

若utf-8兼容性不好,可更换GB2312编码方式。串口接收汉字,需要收发的两方编码方式相同。

main.c 

#include "stdio.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t SendDataA[3] = {0x01,0x02,0x03};
char *Char = "Hello Word!!!";
int32_t zNum = 23213;
int main(void){OLED_Init();Serial_Init();OLED_ShowString(1,1,"AD0:0000");OLED_ShowString(2,1,"AD1:0000");while(1){Delay_ms(1000);//Serial_SendByte(0x41);//发送十六进制0x41//Serial_SendByte('A');//发送字符A,同0x41.逻辑为发送A-底层0x41发送-串口底层接收0x41-可现实字符或十六进制数//Serial_SendByte(SendDataA[0]);//发送1byte数据//Serial_SendByteArray(&SendDataA[0],3);//发送指定长度字节数组//Serial_SendString(Char);发送字符串//Serial_SendSignedNum(zNum,4);//printf打印串口方法1//printf("Num = %d\r\n" , 666);// \r\n为字符换行,printf通过重定向打印串口//printf打印串口方法2//不使用重定向格式化字符串来串口打印,所有的串口都有可以用//char String[100];//sprintf(String,"Num = %d\r\n" , 666);//使用sprintf进行格式化,然后使用串口进行输出//Serial_SendString(String);//打印Num = 666\r\n//printf打印串口方法3//Serial_printf("Num = %d\r\n" , 666);//发送汉字字符串-接收需要和发送时相同的编码方式Serial_printf("你好!");}return 0;
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length);
void Serial_SendString(char *Char);
void Serial_SendSignedNum(int32_t Num,uint16_t length);
void Serial_SendNum(int32_t Num,uint16_t length);
void Serial_printf(char *format,...);
#endif

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
/*** @brief 初始化USART1,通过USART1进行收发* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入.GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;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_Rx | USART_Mode_Tx;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_Init(USART1,&USART_InitStructure);USART_Cmd(USART1,ENABLE);
}
/*** @brief 串口发送1byte* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendByte(uint8_t Byte){USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}/*** @brief 发送字节数组* @param  数组指针*     @arg * @param  长度*     @arg * @retval None*/
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length){for(int i = 0 ; i<length ;  i++){Serial_SendByte(ByteArray[i]);}
}/*** @brief 发送一个字符串* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendString(char *String){int i = 0;for(i=0;String[i]!='\0';i++){Serial_SendByte(String[i]);}
}/*** @brief 以字符形式发送有符号数字* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendSignedNum(int32_t Num,uint16_t length){if(Num>=0){Serial_SendByte(0x2B);//加号}else{Serial_SendByte(0x2D);//减号Num = -Num;}//Num = abs(Num); abs处理int数据所以不适用for(int i=1;i<=length;i++){Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');}
}/*** @brief 以字符形式发送无符号数字* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendNum(int32_t Num,uint16_t length){for(int i=1;i<=length;i++){Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');}
}/*** @brief 重定向fputc函数,到串口1即Serial_SendByte函数,使用micor提供的精简库*     @arg fputc是printf函数的底层,printf打印时就是调用fputc单个打印,通过重定向可以在串口打印* @param  *     @arg * @param  *     @arg * @retval None*/
int fputc(int ch,FILE *f)
{Serial_SendByte(ch);return ch;
}/*** @brief 重构v sprintf,完成printf打印串口*     @arg sprintf只能接收直接写的参数,vsprintf可以接收封装格式参数* @param  format:接收格式化字符串*     @arg * @param  ...:可变参数列表,用来接收剩余参数*     @arg * @retval None*/
void Serial_printf(char *format,...){char String[100];va_list arg;//定义一个参数列表变量va_start(arg,format);//从format位置开始接收参数列表,放在arg中vsprintf(String,format,arg);//将打印参数arg格式化为format格式字符串,最好放入打印字符串变量Stringva_end(arg);//释放参数表Serial_SendString(String);//发送格式化后的字符串
}

2、串口发送+接收,串口收到数据后将数据返回

main.c

#include "stdio.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"int main(void){OLED_Init();Serial_Init();OLED_ShowString(1,1,"Rx:00");while(1){Serial1Rx();OLED_ShowHexNum(1,4,Serial1_RxData,2);//显示1byte接收的十六进制数}return 0;
}

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
uint8_t Serial1_RxData;
uint8_t Serial1_Flag;
/*** @brief 初始化USART1,通过USART1进行收发* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入.GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;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_Rx | USART_Mode_Tx;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_Init(USART1,&USART_InitStructure);USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC配置-中断优先级和中断对应通道使能 stm32f10x.hNVIC_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);
}
/*** @brief 串口发送1byte* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendByte(uint8_t Byte){USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}/*** @brief USART1中断函数,接收的数据返回* @param  *     @arg * @param  *     @arg * @retval None	*		@arg 中断函数名在startup_stm32f10x_md.s中*/
void USART1_IRQHandler(void){if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){Serial1_RxData = USART_ReceiveData(USART1);Serial1_Flag = 1;USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}/*** @brief 配合USART1_IRQHandler串口1中断,中断后处理行为* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial1Rx(void){if(Serial1_Flag){Serial_SendByte(Serial1_RxData);Serial1_Flag = 0;}
}/*** @brief 发送字节数组* @param  数组指针*     @arg * @param  长度*     @arg * @retval None*/
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length){for(int i = 0 ; i<length ;  i++){Serial_SendByte(ByteArray[i]);}
}/*** @brief 发送一个字符串* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendString(char *String){int i = 0;for(i=0;String[i]!='\0';i++){Serial_SendByte(String[i]);}
}/*** @brief 以字符形式发送有符号数字* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendSignedNum(int32_t Num,uint16_t length){if(Num>=0){Serial_SendByte(0x2B);//加号}else{Serial_SendByte(0x2D);//减号Num = -Num;}//Num = abs(Num); abs处理int数据所以不适用for(int i=1;i<=length;i++){Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');}
}/*** @brief 以字符形式发送无符号数字* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendNum(int32_t Num,uint16_t length){for(int i=1;i<=length;i++){Serial_SendByte((Num/(uint32_t)pow(10,length-i)%10)+'0');}
}/*** @brief 重定向fputc函数,到串口1即Serial_SendByte函数,使用micor提供的精简库*     @arg fputc是printf函数的底层,printf打印时就是调用fputc单个打印,通过重定向可以在串口打印* @param  *     @arg * @param  *     @arg * @retval None*/
int fputc(int ch,FILE *f)
{Serial_SendByte(ch);return ch;
}/*** @brief 重构v sprintf,完成printf打印串口*     @arg sprintf只能接收直接写的参数,vsprintf可以接收封装格式参数* @param  format:接收格式化字符串*     @arg * @param  ...:可变参数列表,用来接收剩余参数*     @arg * @retval None*/
void Serial_printf(char *format,...){char String[100];va_list arg;//定义一个参数列表变量va_start(arg,format);//从format位置开始接收参数列表,放在arg中vsprintf(String,format,arg);//将打印参数arg格式化为format格式字符串,最好放入打印字符串变量Stringva_end(arg);//释放参数表Serial_SendString(String);//发送格式化后的字符串
}/*** @brief 串口1接收,查询方式,接收到的数据返回* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_Get(void){if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == SET){uint8_t Get_Data = USART_ReceiveData(USART1);Serial_SendByte(Get_Data);};
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
extern uint8_t Serial1_Flag;
extern uint8_t Serial1_RxData;
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length);
void Serial_SendString(char *Char);
void Serial_SendSignedNum(int32_t Num,uint16_t length);
void Serial_SendNum(int32_t Num,uint16_t length);
void Serial_printf(char *format,...);
void Serial_Get(void);
void Serial1Rx(void);
#endif

3、数据包收发,收发Hex数据包

程序功能和逻辑:

1、按钮按下,对应STM32发送Hex数据包,OLED显示发送Hex数据。每次按按钮数据包递增。

2、串口发送Hex数据包,对应OLED显示接收的Hex数据(当接收到正确的数据包会进行处理,把接受的数据包也发送,接受的错误的数据包不会发送,但OLED也会显示。)。

Hex数据包格式

可变长数据包可能导致:数据和包尾重合,导致误判。

对于这种情况, 可对数据进行限幅,然后包头包尾在限幅之外。或者尽量使用定长数据包,然后尽量增加包头包尾的数量,并尽量让数据不能呈现包头包尾的状态。

对于Hex数据包最好选择定长带包头包尾,可以避免接收错误。

如果载荷不会和包头包尾重复,那么选择可变包长。

Hex通信协议为:

发送:

按下按键STM32发送0xFF 0x01 0x02 0x03 0x04 0xFE,以0xFF为包头,0xFE为包尾,中间4byte为数据,每次发送数据字节递增。

接收:

通过串口助手发送0xFF 0xXX 0xXX 0xXX 0xXX 0xFE,共6byte,其中以0xFF为包头,0xFE为包尾,中间4byte为数据。STM32接收后通过OLED显示数据字节。

Hex数据包接收:

main.c

#include "stdio.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Button.h"
int main(void){OLED_Init();Serial_Init();Button1_Init();OLED_ShowString(1,1,"Tx:");OLED_ShowString(3,1,"Rx:");while(1){Serial1Rx();GetButton_Send();OLED_ShowHexNum(4,1,Serial1_RxPacket[0],2);OLED_ShowHexNum(4,4,Serial1_RxPacket[1],2);OLED_ShowHexNum(4,7,Serial1_RxPacket[2],2);OLED_ShowHexNum(4,10,Serial1_RxPacket[3],2);OLED_ShowHexNum(2,1,Serial1_TxPacket[0],2);OLED_ShowHexNum(2,4,Serial1_TxPacket[1],2);OLED_ShowHexNum(2,7,Serial1_TxPacket[2],2);OLED_ShowHexNum(2,10,Serial1_TxPacket[3],2);}return 0;
}

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
uint8_t Serial1_RxData;
uint8_t Serial1_RxFlag;//接收数据包标志位
uint8_t Serial1_RxPacket[4];//接收数据包
uint8_t Serial1_TxPacket[4] = {0x01,0x02,0x03,0x04};//发送数据包
/*** @brief 初始化USART1,通过USART1进行收发* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入.GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;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_Rx | USART_Mode_Tx;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_Init(USART1,&USART_InitStructure);USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC配置-中断优先级和中断对应通道使能 stm32f10x.hNVIC_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);
}
/*** @brief 串口发送1byte* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendByte(uint8_t Byte){USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}/*** @brief 发送字节数组* @param  数组指针*     @arg * @param  长度*     @arg * @retval None*/
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length){for(int i = 0 ; i<length ;  i++){Serial_SendByte(ByteArray[i]);}
}/*** @brief 根据数据包发送数据数组* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial1Tx_HexPacket(void){Serial_SendByte(0xFF);//发送包头Serial_SendByteArray(Serial1_TxPacket,4);Serial_SendByte(0xFE);//发送包尾
}/*** @brief 配合USART1_IRQHandler串口1接收中断,对接收到的数据进行处理* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial1Rx(void){if(Serial1_RxFlag){for(int i=0;i<4;i++){Serial1_TxPacket[i] = Serial1_RxPacket[i];}Serial1Tx_HexPacket();Serial1_RxFlag = 0;}
}/*** @brief 配合USART1_IRQHandler串口1接收中断,接收到的Hex数据包进行判断,* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial1Rx_HexPacket(void){static uint8_t RxState = 0;//接收状态机static uint8_t RxDataFlag = 0;//接收数据下标/*0/1 :包头1 :数据2 :包尾*/Serial1_RxData = USART_ReceiveData(USART1);//接收数据if(RxState == 0){//等待接收包头if(Serial1_RxData == 0xFF){//如果获取包头RxState = 1;RxDataFlag = 0;//在每次接收数据前清0,更稳定}}else if(RxState == 1){//等待数据Serial1_RxPacket[RxDataFlag] = Serial1_RxData;RxDataFlag++;if(RxDataFlag>=4){RxState = 2;}}else if(RxState == 2){//等待包尾if(Serial1_RxData == 0xFE){//等待包尾RxState = 0;Serial1_RxFlag = 1;//标志接收到了正确的数据包标志位}else{RxState = 0;//如果接收的不是包尾,则丢弃数据包}}}/*** @brief USART1中断函数,接收的数据返回* @param  *     @arg * @param  *     @arg * @retval None	*		@arg 中断函数名在startup_stm32f10x_md.s中*/
void USART1_IRQHandler(void){if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){Serial1Rx_HexPacket();//开始接收数据USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
extern uint8_t Serial1_RxData;
extern uint8_t Serial1_RxFlag;
extern uint8_t Serial1_RxPacket[];
extern uint8_t Serial1_TxPacket[];
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendByteArray(uint8_t *ByteArray,uint16_t length);
void Serial1Rx(void);
void Serial1Rx_HexPacket(void);
void Serial1Tx_HexPacket(void);
#endif

Button.h

#ifndef __BUTTON_H
#define __BUTTON_H
#include "stm32f10x.h"                  // Device headervoid Button1_Init(void);
void GetButton_Send(void);
#endif

Button.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Serial.h"
/*** @brief 初始化引脚B1地开信号接收Button,A1推挽输出控制LED* @param  *     @arg * @param  *     @arg * @retval None*/
void Button1_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef  GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);GPIO_SetBits(GPIOA,GPIO_Pin_1);//高电平
}/*** @brief * @param  *     @arg * @param  *     @arg * @retval None*/
void GetButton_Send(void){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET){Delay_ms(20);while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET);Delay_ms(20);Serial1_TxPacket[0]++;Serial1_TxPacket[1]++;Serial1_TxPacket[2]++;Serial1_TxPacket[3]++;Serial1Tx_HexPacket();}
}

4、数据包收发,收发文本数据包

程序功能逻辑:

1、按钮按下,灯开关,对应STM32发送开关灯文本数据包,OLED显示发送的文本数据

2、串口发送开关灯Hex数据包,对应开关灯,对应OLED显示接收的Hex数据,STM32发送开关灯文本数据包包反馈

文本数据包格式:

使用文本数据包,中间数据基本上都是字母,不会和包头包尾重复。可使用不定长数据包。

通常用换行做包尾,这样在打印时就是一行一行显示,相对Hex数据包解析效率低。 

文本数据包通信协议为:

发送:

按下按键STM32交替发送@ LED_ON\r\n和@LED_OFF\r\n,对应LED开关,以@为包头,\r\n为包尾(换行),LED_ON和LED_OFF为数据。并在OLED显示

接收:

通过串口助手发送@ LED_ON\r\n和@LED_OFF\r\n,对应LED开关,其中以@为包头,\r\n为包尾(换行),LED_ON和LED_OFF为数据。并通过OLED显示数据字节。当STM32接收到错误指令时报故。

文本数据包接收:

main.c

#include "stdio.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Button.h"
int main(void){OLED_Init();Serial_Init();Button1_Init();OLED_ShowString(1,1,"Tx:");OLED_ShowString(2,1,"@LED_OFF");OLED_ShowString(3,1,"Rx:");OLED_ShowString(4,1,"@LED_OFF");while(1){GetButton_LED();Serial1TextRx();}return 0;
}

代码讲解

Serial.c

#include "stm32f10x.h"                  // Device header
#include "math.h"
#include "stdio.h"
#include "stdarg.h"
#include "OLED.h"
#include "Button.h"
#include "String.h"
uint8_t Serial1_RxData;//接收数据字节
uint8_t Serial1_RxFlag;//接收完整数据包标志位
char Serial1_RxTextPacket[100];//接收文本数据包
/*** @brief 初始化USART1,通过USART1进行收发* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入.GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//上拉输入GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;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_Rx | USART_Mode_Tx;USART_InitStructure.USART_Parity = USART_Parity_No;USART_InitStructure.USART_WordLength = USART_WordLength_8b;USART_InitStructure.USART_StopBits = USART_StopBits_1;USART_Init(USART1,&USART_InitStructure);USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC配置-中断优先级和中断对应通道使能 stm32f10x.hNVIC_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);
}
/*** @brief 串口发送1byte* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendByte(uint8_t Byte){USART_SendData(USART1,Byte);while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);//发送缓冲不为空则等待
}/*** @brief 发送一个字符串* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial_SendString(char *String){int i = 0;for(i=0;String[i]!='\0';i++){Serial_SendByte(String[i]);}
}/*** @brief 根据数据包发送文本数据数组* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial1Tx_TextPacket(void){Serial_SendByte('@');//发送包头if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)==RESET){Serial_SendString("LED_ON");}else{Serial_SendString("LED_OFF");}Serial_SendByte('\r');//发送包尾Serial_SendByte('\n');//发送包尾}/*** @brief 配合USART1_IRQHandler串口1接收中断,对接收到的文本数据进行处理* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial1TextRx(void){if(Serial1_RxFlag){OLED_ShowString(4,1,"                ");OLED_ShowString(4,1,Serial1_RxTextPacket);if(strcmp(Serial1_RxTextPacket,"LED_OFF") == 0){LED_OFF(GPIOA,GPIO_Pin_1);}else if(strcmp(Serial1_RxTextPacket,"LED_ON") == 0){LED_ON(GPIOA,GPIO_Pin_1);}else{OLED_ShowString(2,1,"Rx_Error");Serial_SendString("Rx_Error\r\n");}Serial1_RxFlag = 0;}
}/*** @brief 配合USART1_IRQHandler串口1接收中断,接收到的Text数据包进行判断,* @param  *     @arg * @param  *     @arg * @retval None*/
void Serial1Rx_TextPacket(void){static uint8_t RxState = 0;//接收状态机static uint8_t RxDataFlag = 0;//接收数据下标/*0/1 :包头1 :数据2 :包尾*/Serial1_RxData = USART_ReceiveData(USART1);//接收数据if(RxState == 0){//等待接收包头if(Serial1_RxData == '@'){//如果获取包头RxState = 1;RxDataFlag = 0;//在每次接收数据前清0,更稳定}}else if(RxState == 1){//等待数据if(Serial1_RxData != '\r'){Serial1_RxTextPacket[RxDataFlag] = Serial1_RxData;RxDataFlag++;}else{RxState = 2;}		}else if(RxState == 2){//等待包尾if(Serial1_RxData == '\n'){//等待包尾RxState = 0;Serial1_RxTextPacket[RxDataFlag] = '\0';Serial1_RxFlag = 1;//标志接收到了正确的数据包标志位}else{RxState = 0;//如果接收的不是包尾,则丢弃数据包}}
}/*** @brief USART1中断函数,接收的数据返回* @param  *     @arg * @param  *     @arg * @retval None	*		@arg 中断函数名在startup_stm32f10x_md.s中*/
void USART1_IRQHandler(void){if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET){//Serial1Rx_HexPacket();//开始接收数据Serial1Rx_TextPacket();USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}

代码讲解:

 Serial1TextRx未放在中断内,减少消耗软件资源(减少中断时间占用)

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H
#include "stm32f10x.h"                  // Device header
#include "stdio.h"
extern uint8_t Serial1_RxData;
extern uint8_t Serial1_RxFlag;
extern char Serial1_RxTextPacket[];
void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendString(char *Char)void Serial1TextRx(void);
void Serial1Tx_TextPacket(void);
void Serial1Rx_TextPacket(void);
#endif

Button.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Serial.h"
#include "OLED.h"
/*** @brief 初始化引脚B1地开信号接收Button,A1推挽输出控制LED* @param  *     @arg * @param  *     @arg * @retval None*/
void Button1_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef  GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);GPIO_SetBits(GPIOA,GPIO_Pin_1);//高电平
}
/*** @brief LED关闭,OLED显示@LED_OFF,并且串口发送@LED_OFF\r\n* @param  *     @arg * @param  *     @arg * @retval None*/
uint8_t LED_OFF(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin){GPIO_WriteBit(GPIOx,GPIO_Pin,Bit_SET);//关闭LEDOLED_ShowString(2,1,"@LED_OFF        ");Serial1Tx_TextPacket();//串口发送return 1;
}/*** @brief LED打开,OLED显示@LED_ON,并且串口发送@LED_ON* @param  *     @arg * @param  *     @arg * @retval None*/
uint8_t LED_ON(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin){GPIO_WriteBit(GPIOx,GPIO_Pin,Bit_RESET);//打开LEDOLED_ShowString(2,1,"@LED_ON         ");Serial1Tx_TextPacket();//串口发送return 0;
}/*** @brief 获取B1的Button是否按下,按下则控制LED反转* @param  *     @arg * @param  *     @arg * @retval None*/
void GetButton_LED(void){if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET){Delay_ms(20);while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) == RESET);Delay_ms(20);if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == RESET){LED_OFF(GPIOA,GPIO_Pin_1);}else{LED_ON(GPIOA,GPIO_Pin_1);}}
}

 代码讲解:

 

Button.h

#ifndef __BUTTON_H
#define __BUTTON_H
#include "stm32f10x.h"                  // Device headervoid Button1_Init(void);
uint8_t LED_OFF(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
uint8_t LED_ON(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
void GetButton_LED(void);
#endif

程序逻辑:

按下按钮:GetButton_LED -> GetButton_LED -> LED_ON/LED_OFF -> GPIO_WriteBit/OLED_ShowString/Serial1Tx_TextPacket

发送控制指令:USART1_IRQHandler->Serial1Rx_TextPacket->Serial1TextRx->LED_ON/LED_OFF -> GPIO_WriteBit/OLED_ShowString/Serial1Tx_TextPacket

反思

本节程序模块之间的耦合性太高,应该针对不同的模块进行不同的功能封装,然后统一调用即可。不应该在一个模块程序中调用另一个模块程序。

例如:串口模块中尽量只写串口函数,若需要串口相关返回和处理,增加标志位返回函数或者全局变量即可。LED控制和按钮采集,应该只写LED控制和按钮采集。在主函数中,应该进行采集控制和通信函数的调用。每个模块库应该保持整洁和低耦性,方便理解和使用。

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

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

相关文章

动态库实现lua网络请求GET, POST, 下载文件

DLL需要使用的网络封装 WinHttp异步实现GET, POST, 多线程下载文件_webclient post下载文件-CSDN博客文章浏览阅读726次。基于WinHttp封装, 实现异步多线程文件下载, GET请求, POST请求_webclient post下载文件https://blog.csdn.net/Flame_Cyclone/article/details/142644088…

牛客周赛65(C++实现)

比赛链接&#xff1a;牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ 文章目录 1.超市1.1 题目描述1.2 思路1.3 代码 2. 雨幕2.1 题目描述2.2 思路2.3 代码 3.闺蜜3.1 题目描述3.2 思路3.3 代码 4. 医生4.1 题目描述4.2 思路4.3 代码 1.超市 1.1 题目描述 …

机器人技术革新:人工智能的强力驱动

内容概要 在当今世界&#xff0c;机器人技术与人工智能的结合正如星星与大海&#xff0c;彼此辉映。随着科技的不断进步&#xff0c;人工智能不仅仅是为机器人赋予了“聪明的大脑”&#xff0c;更是推动了整个行业的快速发展。回顾机器人技术的发展历程&#xff0c;我们会发现…

使用PostgreSQL进行高效数据管理

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用PostgreSQL进行高效数据管理 PostgreSQL简介 安装PostgreSQL 在Ubuntu上安装PostgreSQL 在CentOS上安装PostgreSQL 在macOS上…

为开源 AI 模型引入激励机制?解读加密 AI 协议 Sentient 的大模型代币化解决方案

撰文&#xff1a;Shlok Khemani 编译&#xff1a;Glendon&#xff0c;Techub News 古时候&#xff0c;中国人深信「阴阳」的概念——宇宙的每一个方面都蕴含着内在的二元性&#xff0c;这两种相反的力量不断地相互联系&#xff0c;形成一个统一的整体。就好比女性代表「阴」&a…

Hi3516/Hi3519DV500移植YOLOV5、YOLOV6、YOLOV7、YOLOV8开发环境搭建--YOLOV5工程编译移植到开发板测试--(5)

专栏链接如下&#xff1a; Hi3516/Hi3519DV500移植YOLOV5、YOLOV6、YOLOV7、YOLOV8开发环境搭建--安装Ubuntu18.04--&#xff08;1&#xff09; Hi3516/Hi3519DV500移植YOLOV5、YOLOV6、YOLOV7、YOLOV8开发环境搭建--安装开发环境AMCT、依赖包等--&#xff08;2&#xff09;…

【STM32】INA3221三通道电压电流采集模块,HAL库

一、简单介绍 芯片的datasheet地址&#xff1a; INA3221 三通道、高侧测量、分流和总线电压监视器&#xff0c;具有兼容 I2C 和 SMBUS 的接口 datasheet (Rev. B) 笔者所使用的INA3221是淘宝买的模块 原理图 模块的三个通道的电压都是一样&#xff0c;都是POWER。这个芯片采用…

HTML 标签属性——id、class、style 等全局属性详解

文章目录 1. id属性2. class属性3. style属性4. title属性5. lang属性6. dir属性7. accesskey属性8. tabindex属性小结HTML全局属性是一组可以应用于几乎所有HTML元素的特殊属性。这些属性提供了额外的功能和信息,使得网页开发者能够更好地控制元素的行为、样式和可访问性。 …

Python数据分析案例62——基于MAGU-LSTM的时间序列预测(记忆增强门控单元)

案例背景 时间序列lstm系列预测在学术界发论文都被做烂了&#xff0c;现在有一个新的MAGU-LSTM层的代码&#xff0c;并且效果还可以&#xff0c;非常少见我觉得还比较创新&#xff0c;然后我就分享一下它的代码演示一下&#xff0c;并且结合模态分解等方法做一次全面的深度学习…

C++泛型编程

一、什么是泛型编程 泛型编程 是一种编程范式&#xff0c;它通过编写可以处理多种数据类型的代码来实现代码的灵活复用。泛型编程主要通过模板来实现。 比如我们日常使用的容器类型vector就应用了模板来实现其通用性&#xff0c;我们在使用时可以通过传入型别创建对应的动态数…

ServletContext,Cookie,HttpSession的使用

ServletContext对象 ServletContext对象官方也称servlet上下文。服务器会为每一个Web应用创建一个ServletContext对象&#xff0c;这个对象全局唯一&#xff0c;而且Web应用中所有的Servlet都共享这个对象。 ServletContext对象的作用 相对路径转绝对路径 servletContext.g…

如何封装一个可取消的 HTTP 请求?

前言 你可能会好奇什么样的场景会需要取消 HTTP 请求呢&#xff1f; 确实在实际的项目开发中&#xff0c;可能会很少有这样的需求&#xff0c;但是不代表没有&#xff0c;比如&#xff1a; 假如要实现上述这个公告栏&#xff0c;每点击一个 tab 按钮就会切换展示容器容器中…

前端笔试新问题总结

记录总结下最近遇到的前端笔试新问题 目录 一、操作数组方法 1.Array.isArray(arr) 2.Object.prototype.toString.call(arr) "[object Array]" 3.arr instanceof Array 1&#xff09;跨帧问题 2&#xff09;修改Array.prototype 3&#xff09;模拟数组的对象…

玩转Hugging Face/魔搭社区/魔乐社区”教程

2.1 HF 平台 2.1.1 注册Hugging Face 平台 &#xff08;需要魔法上网&#xff09; Hugging Face 最初专注于开发聊天机器人服务。尽管他们的聊天机器人项目并未取得预期的成功&#xff0c;但他们在GitHub上开源的Transformers库却意外地在机器学习领域引起了巨大轰动。如今&…

Chrome与夸克谁更节省系统资源

在当今数字化时代&#xff0c;浏览器已经成为我们日常生活中不可或缺的一部分。无论是工作、学习还是娱乐&#xff0c;我们都依赖于浏览器来访问互联网。然而&#xff0c;不同的浏览器在性能和资源消耗方面存在差异。本文将探讨Chrome和夸克两款浏览器在系统资源消耗方面的表现…

【OJ题解】C++实现反转字符串中的每个单词

&#x1f4b5;个人主页: 起名字真南 &#x1f4b5;个人专栏:【数据结构初阶】 【C语言】 【C】 【OJ题解】 题目要求&#xff1a;给定一个字符串 s &#xff0c;你需要反转字符串中每个单词的字符顺序&#xff0c;同时仍保留空格和单词的初始顺序。 题目链接: 反转字符串中的所…

Vue 学习随笔系列十三 -- ElementUI 表格合并单元格

ElementUI 表格合并单元格 文章目录 ElementUI 表格合并单元格[TOC](文章目录)一、表头合并二、单元格合并1、示例代码2、示例效果 一、表头合并 参考&#xff1a; https://www.jianshu.com/p/2befeb356a31 二、单元格合并 1、示例代码 <template><div><el-…

吴恩达深度学习笔记:卷积神经网络(Foundations of Convolutional Neural Networks)4.3-4.4

目录 第四门课 卷积神经网络&#xff08;Convolutional Neural Networks&#xff09;第四周 特殊应用&#xff1a;人脸识别和神经风格转换&#xff08;Special applications: Face recognition &Neural style transfer&#xff09;4.3 Siamese 网络&#xff08;Siamese net…

vue data变量之间相互赋值或进行数据联动

摘要&#xff1a; 使用vue时开发会用到data中是数据是相互驱动&#xff0c;经常会想到watch,computed&#xff0c;总结一下&#xff01; 直接赋值&#xff1a; 在 data 函数中定义的变量可以直接在方法中进行赋值。 export default {data() {return {a: 1,b: 2};},methods: {u…

安装Blender并使用

前言 该系列记录了如何用Blenderpro来构建自己的场景数据集&#xff0c;从环境搭建到后期构建数据集的整个流程 本文章是第一部分&#xff0c;BlenderPrc2的安装以及环境配置 部分参考https://blog.csdn.net/weixin_49521551/article/details/121573334 官方文档https://dlr…