STM32使用USART发送数据包指令点亮板载LED灯

2024-03-29 11:20

本文主要是介绍STM32使用USART发送数据包指令点亮板载LED灯,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

电路连接:        

        连接显示屏模块,显示屏的SCL在B10,SDA在B11。

程序目的:

        发送@LED_ON指令打开板载LED灯,发送@LED_OFF关闭板载LED灯,与上一个博客不同,这个实际上是实现串口收发文本数据包。

开始编程:

Serial.c

初始化GPIO与中断

  • 初始化A9引脚,设置为复用推挽输出,也就是让内部硬件控制引脚
  • 初始化A10引脚,设置为浮空输入或上拉输入,这里使用上拉输入,具有较好的抗干扰能力
  • 不使用硬件流控制,也就是不使用RTS,CTS等
  • 串口模式为TX|RX(Transform)|(Receive)表示发送和接收
  • 无校验位,可选择奇校验,偶校验等
  • 1位停止位,可选择0.5 1 1.5 2这几个
  • 8字长,不需要校验选8位,需要选9位
  • 开启RXNE(RX No Empty)到NVIC的输出,也就是开启中断
  • 配置中断
void Serial_Init() {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_Pin = GPIO_Pin_9;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_10;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;//硬件流控制(不使用,CTS,CTS&RTS)USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式 可以使用(或)|符号实现Tx和Rx同时设置USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,无需校验USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长USART_Init(USART1, &USART_InitStructure);//串口接收部分可以采用查询或者中断的方式,如果采用中断就需要在这里配置NVIC//开启中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE到NVIC的输出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);//开启USART
}

中断函数:

状态机如图:

        这里的中断函数与HEX数据包不同,当收到@字符时转为第一个状态,接收数据,由于这个数据不是固定包长,那么收到\r就进入状态2,再收到\n表示接收完成,进入状态0。这里如果包尾不是两个字符的话,只需要设置两个状态即可。

        由于是字符串,因此在状态2转移到状态0时,需要加上字符串的自带的'\0',这样才能定义字符串跟接受到的字符串比较。

        还需要建立两个全局变量,char Serial_RxPacket[100];uint8_t Serial_RxFlag;一个是存放接受的数据,一个是存放接收数据标志位。

        在中断函数中,定义两个静态变量,类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用,这两个静态变量:static uint8_t RxState = 0;static uint8_t pRxPacket = 0;一个用于定位状态,一个用于定位接收到的数据。

中断函数代码:

char Serial_RxPacket[100];uint8_t Serial_RxFlag;
void USART1_IRQHandler() {static uint8_t RxState = 0;//类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用static uint8_t pRxPacket = 0;if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {//如果读取DR就自动清除标志位,如果没有就需要手动清除uint8_t RxData = USART_ReceiveData(USART1);if(RxState == 0){//若在这里将RxState置为1,那么下面就会立马执行,因此要加上else,也可用switch case语句if(RxData == '@') {RxState = 1;pRxPacket = 0;}}else if(RxState == 1) {if(RxData == '\r'){RxState = 2;}else {Serial_RxPacket[pRxPacket] = RxData;pRxPacket ++;}}else if(RxState ==  2){if(RxData == '\n') {RxState = 0;Serial_RxFlag = 1;Serial_RxPacket[pRxPacket] = '\0';//不加不能使用OLED_ShowString}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}

Serial.c整体代码

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>
char Serial_RxPacket[100];uint8_t Serial_RxFlag;void Serial_Init() {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_Pin = GPIO_Pin_9;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_10;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;//硬件流控制(不使用,CTS,CTS&RTS)USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式 可以使用(或)|符号实现Tx和Rx同时设置USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,无需校验USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长USART_Init(USART1, &USART_InitStructure);//串口接收部分可以采用查询或者中断的方式,如果采用中断就需要在这里配置NVIC//开启中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE到NVIC的输出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);//开启USART
}
void Serial_SendByte(uint8_t Byte) {USART_SendData(USART1, Byte);//发送数据while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) {//等待发送寄存器空,//TXE就是发送寄存器空的标志位,不需要手动清零,下一次发送数据时候会自动清零}
}
void Serial_SendArray(uint8_t *Array, uint16_t Length){uint16_t i;for(int i = 0; i < Length; i++) {Serial_SendByte(Array[i]);}}
void Serial_SendString(char *Str) {//字符串自带结束标志位uint8_t i;for(int i = 0; Str[i] != '\0'; i++) {Serial_SendByte(Str[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(int i = 0; i < Length; i++){Serial_SendByte((Number / Serial_Pow(10, Length - i - 1)) % 10 + '0');}}
int fputc(int ch, FILE* f){Serial_SendByte(ch);//重定向到串口,使得Printf打印到串口return ch;}
//使用sprintf让其他的串口也能使用,sprintf可以把格式化字符输出到一个字符串里
void Serial_Printf(char* format,...){//三个点用来接收后面可变参数列表char String[100];va_list arg;va_start(arg, format);//从format位置开始接收参数表,放在arg里面vsprintf(String, format, arg);va_end(arg);Serial_SendString(String);
}
uint8_t Serial_GetRxFlag() {if(Serial_RxFlag == 1){Serial_RxFlag = 0;return 1;}return 0;
}
void Serial_SendPacket(){}
void USART1_IRQHandler() {static uint8_t RxState = 0;//类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用static uint8_t pRxPacket = 0;if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {//如果读取DR就自动清除标志位,如果没有就需要手动清除uint8_t RxData = USART_ReceiveData(USART1);if(RxState == 0){//若在这里将RxState置为1,那么下面就会立马执行,因此要加上else,也可用switch case语句if(RxData == '@') {RxState = 1;pRxPacket = 0;}}else if(RxState == 1) {if(RxData == '\r'){RxState = 2;}else {Serial_RxPacket[pRxPacket] = RxData;pRxPacket ++;}}else if(RxState ==  2){if(RxData == '\n') {RxState = 0;Serial_RxFlag = 1;Serial_RxPacket[pRxPacket] = '\0';//不加不能使用OLED_ShowString}}USART_ClearITPendingBit(USART1, USART_IT_RXNE);}
}

Serial.h

源代码:

#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
extern char Serial_RxPacket[];void Serial_Init();
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();#endif

GpioControl.c:

        编写GPIO控制函数,封装GPIO引脚的初始化和控制功能。

#include "stm32f10x.h"                  // Device header
void GpioInit(GPIO_TypeDef *GPIOx, uint16_t Pin, GPIOMode_TypeDef GpioMode){uint32_t RCC_APB2Periph_GPIOx;if(GPIOx == GPIOA) {RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOA;}else if(GPIOx == GPIOB) {RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOB;}else if(GPIOx == GPIOC) {RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOC;}RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);//ctrl + Alt + 空格:可以出现代码提示GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GpioMode;//推挽输出GPIO_InitStructure.GPIO_Pin = Pin;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOx, &GPIO_InitStructure);GPIO_ResetBits(GPIOx, Pin);
}
void GpioTurn(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN) {//反转当前引脚状态if(GPIO_ReadOutputDataBit(GPIOx,GPIO_PIN) == 0){GPIO_SetBits(GPIOx,GPIO_PIN);}else{GPIO_ResetBits(GPIOx, GPIO_PIN);}
}
void GpioControl(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN, uint8_t sign) {//控制引脚if(sign == ENABLE){GPIO_SetBits(GPIOx, GPIO_PIN);}if(sign == DISABLE){GPIO_ResetBits(GPIOx, GPIO_PIN);}
}

GpioControl.h:

#ifndef __GPIOCONTROL_H
#define __GPIOCONTROL_Hvoid GpioInit(GPIO_TypeDef *GPIOx, uint16_t Pin, GPIOMode_TypeDef GpioMode);
void GpioTurn(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN);
void GpioControl(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN, uint8_t sign);#endif 

main.c

        在main函数中,主要逻辑就是判断标志位来得到是否有数据接收,若有则跟指令进行对比,如果是打开灯指令,那么就置C13引脚为低电平并发送LED_ON_OK指令,点亮LED灯。若是关灯指令,那么就置引脚为高电平,关闭LED灯并发送LED_OFF_OK,若都不是,那么就输出ERROR_CMD指令表示指令错误。

主要代码如下:

#include "stm32f10x.h"                  // Device header
#include "DELAY.h"
#include "OLED.h"
#include "Serial.h"
#include "GpioControl.h"
#include <string.h>
uint8_t RxData;
uint8_t KeyNum;int main() {GpioInit(GPIOC, GPIO_Pin_13, GPIO_Mode_Out_PP);GPIO_SetBits(GPIOC,GPIO_Pin_13);OLED_Init();Serial_Init();OLED_ShowString(1, 1, "TxData:");OLED_ShowString(3, 1, "RxData:");while(1){if(Serial_GetRxFlag() == 1) {OLED_ShowString(4,1, "                ");//清除第四行OLED_ShowString(4,1, Serial_RxPacket);if(strcmp(Serial_RxPacket,  "LED_ON") == 0) {GpioControl(GPIOC, GPIO_Pin_13, DISABLE);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) {GpioControl(GPIOC, GPIO_Pin_13, ENABLE);Serial_SendString("LED_OFF_OK\r\n");OLED_ShowString(2,1,"LED_OFF_OK");}else {Serial_SendString("ERROR_CMD\r\n");OLED_ShowString(2,1,"                ");OLED_ShowString(2,1,"ERROR_CMD");}}}
}

程序现象:

 

程序及软件下载:

程序打包代码:程序包下载

串口助手下载:串口助手下载

这篇关于STM32使用USART发送数据包指令点亮板载LED灯的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/858464

相关文章

Python使用Pandas对比两列数据取最大值的五种方法

《Python使用Pandas对比两列数据取最大值的五种方法》本文主要介绍使用Pandas对比两列数据取最大值的五种方法,包括使用max方法、apply方法结合lambda函数、函数、clip方法、w... 目录引言一、使用max方法二、使用apply方法结合lambda函数三、使用np.maximum函数

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本