STM32单片机C语言模块化编程实战:按键控制LED灯并串口打印详解与示例

一、开发环境

硬件:正点原子探索者 V3 STM32F407 开发板

单片机:STM32F407ZGT6

Keil版本:5.32

STM32CubeMX版本:6.9.2

STM32Cube MCU Packges版本:STM32F4 V1.27.1

虽然这里演示的是STM32F407,但是STM32F103还是STM32H系列等,但是可直接将LED、按键、串口文件复制使用,仅供需改头文件的引脚。之前介绍了很多关于点灯的方法,比如轮询、定时器中断、PWM、按键点灯等方式,这些文章使用的编程方法都不是模块化的编写方式,往往会导致代码可读性差、重用性差、扩展性差以及测试和维护困难等问题。为了避免这些问题,我们实际工作中通常会采用模块化的编写方法,这样可以确保代码结构清晰、功能明确,提高代码的可读性和可维护性,同时降低功能之间的耦合度,增强代码的重用性和扩展性。模块化的编写方式还有助于实现代码的并行开发,提高开发效率,使得整个项目更加易于管理和维护。

基于之前的按键点灯的程序和printf重定向输出进行修改,我将为您详细阐述如何使用STM32F407的HAL库,并结合STM32CubeMX配置工具,通过模块化分层方法用按键分别控制两个LED灯并通过串口打印按键与灯的状态,即用引脚PE2和PE3按键分别控制PF9和PF10引脚LED,通过USART1打印信息。这一简洁而高效的流程将助您迅速掌握LED、按键、串口模块化编写方法。

1.LED灯
用drv_led.h和drv_led.c作为一个独立的模块,并提供三个LED驱动程序的接口

int LedDrvInit(BoardLed led);//初始化指定的LED

int LedDrvWrite(BoardLed led, LedStatus status);//设置指定LED的状态

int LedDrvRead(BoardLed led);//读取指定LED的当前状态

2.按键

用drv_key.h和drv_key.c作为一个独立的模块,并提供两个KEY驱动程序的接口

int KeyDrvInit(BoardKey key);//用于初始化指定的按键。  
int KeyDrvRead(BoardKey key);//用于读取指定按键的状态。  

3.串口USART1

int UartDrvInit(BoardUart uart);// 定义宏,将DbgUart映射到具体的USART(通用同步异步收发器)硬件接口,这里映射到USART1 
// 声明UartDrvWrite函数,该函数用于向指定的UART接口写入数据,参数pbuf指向要写入的数据,length表示数据长度
int UartDrvWrite(BoardUart uart, unsigned char *pbuf, unsigned short length);
// 声明UartDrvRead函数,该函数用于从指定的UART接口读取数据,参数pbuf用于存储读取到的数据,length表示读取的数据长度
int UartDrvRead(BoardUart uart, unsigned char *pbuf, unsigned short length);

 二、配置STM32CubeMX

  1. 启动STM32CubeMX,新建STM32CubeMX项目​​
  2. 选择MCU:在软件中选择你的STM32型号-STM32F407ZGT6。​​
  3. 选择时钟源:

  4. 配置时钟:
  5. 使能Debug功能:Serial Wire
  6. HAL库时基选择:SysTick
  7. 配置LED引脚:当前硬件的LED灯的引脚是PF9和PF10:在Pinout & Configuration标签页中,找到LED连接的GPIO端口,并设置为输出模式,通常选择Push-Pull,GPIO output level选低电平。
  8. 配置KEY引脚:当前硬件的KEY的引脚是PE2和PE3:在Pinout & Configuration标签页中,找到KEY连接的GPIO端口,并设置为输入模式,通常选择Pull-up。
  9. 配置USART1串口:
  10. 配置工程参数:在Project标签页中,配置项目名称和位置,选择工具链MDK-ARM。​​
  11. 生成代码:在Code Generator标签页中,配置工程外设文件与HAL库,勾选头文件.c和.h文件分开,然后点击Project > Generate Code生成代码。 

三、代码实现与部署

  1.  新建文件:LED灯的驱动drv_led.h和drv_led.c :​ drv_led.h

    #ifndef __DRV_LED_H
    #define __DRV_LED_Htypedef enum{LED1 = 1,LED2
    }BoardLed;typedef enum{led_on = 0,led_off = 1
    }LedStatus;#define LED1_PIN      GPIO_PIN_9
    #define LED1_PORT     GPIOF
    #define LED2_PIN      GPIO_PIN_10
    #define LED2_PORT     GPIOFint LedDrvInit(BoardLed led);
    int LedDrvWrite(BoardLed led, LedStatus status);
    int LedDrvRead(BoardLed led);#endif /* __DRV_LED_H */
    

    drv_led.c

    #include "drv_led.h"
    #include "stm32f4xx_hal.h"int LedDrvInit(BoardLed led)
    {switch(led){case LED1:{break;}case LED2:{break;}default:break;}return 0;
    }int LedDrvWrite(BoardLed led, LedStatus status)
    {switch(led){case LED1:{HAL_GPIO_WritePin(LED1_PORT, LED1_PIN, (GPIO_PinState)status);break;}case LED2:{HAL_GPIO_WritePin(LED2_PORT, LED2_PIN, (GPIO_PinState)status);break;}default:break;}return 0;
    }int LedDrvRead(BoardLed led)
    {LedStatus status = led_on;switch(led){case LED1:{status = (LedStatus)HAL_GPIO_ReadPin(LED1_PORT, LED1_PIN);break;}case LED2:{status = (LedStatus)HAL_GPIO_ReadPin(LED2_PORT, LED2_PIN);break;}default:break;}return status;
    }
    
  2. 添加路径:将drv_led.c添加到所属组, drv_led.h添加到头文件的路径中。
  3. 添加按键代码:drv_key.h和drv_key.c,方法与LED的一样。drv_key.h
    // #ifndef __DRV_KEY_H 是预处理指令,用于防止头文件的内容在一个编译单元中被多次包含。  
    // 如果__DRV_KEY_H还没有被定义,则继续处理此头文件的内容;如果已经定义了,则忽略。  
    #ifndef __DRV_KEY_H
    #define __DRV_KEY_H// 定义一个名为BoardKey的枚举类型,用于表示不同的按键。
    typedef enum{K1 = 1,// K1键,其值为1  K2,    // K2键,其值为2(因为K1为1,所以K2自动为2)  K3,K4
    }BoardKey;// 定义一个名为KeyStatus的枚举类型,用于表示按键的状态。  
    typedef enum{  isPressed = 0,  // 按键被按下,其值为0  isReleased = 1  // 按键被释放,其值为1  
    }KeyStatus; // 定义了一系列的宏,用于表示按键对应的GPIO引脚和端口。  
    // 例如,K1_PIN代表K1键连接的GPIO引脚,而K1_PORT代表该引脚所在的GPIO端口。  
    #define K1_PIN          GPIO_PIN_0
    #define K1_PORT         GPIOA
    #define K2_PIN          GPIO_PIN_2
    #define K2_PORT         GPIOE
    #define K3_PIN          GPIO_PIN_3
    #define K3_PORT         GPIOE
    #define K4_PIN          GPIO_PIN_4
    #define K4_PORT         GPIOEint KeyDrvInit(BoardKey key);//用于初始化指定的按键。  
    int KeyDrvRead(BoardKey key);//用于读取指定按键的状态。  #endif /* __DRV_KEY_H */
    
    drv_key.c
    #include "drv_key.h"
    #include "stm32f4xx_hal.h"int KeyDrvInit(BoardKey key)
    {switch(key){case K1:{break;}case K2:{break;}case K3:{break;}case K4:{break;}default:break;}return 0;
    }int KeyDrvRead(BoardKey key)
    {KeyStatus status = isReleased;switch(key){case K1:{status = (KeyStatus)HAL_GPIO_ReadPin(K1_PORT, K1_PIN);break;}case K2:{status = (KeyStatus)HAL_GPIO_ReadPin(K2_PORT, K2_PIN);break;}case K3:{status = (KeyStatus)HAL_GPIO_ReadPin(K3_PORT, K3_PIN);break;}case K4:{status = (KeyStatus)HAL_GPIO_ReadPin(K4_PORT, K4_PIN);break;}default:break;}return status;
    }
    
  4. 添加串口代码:drv_uart.h和drv_uart.c,方法与LED的一样。                                   drv_uart.h
    // 防止头文件被重复包含,这是一种常见的预处理指令用法,用来确保头文件在一个编译单元中只被包含一次  
    #ifndef __DRV_UART_H
    #define __DRV_UART_H// 定义一个枚举类型BoardUart,用来区分不同功能的UART(通用异步收发器)  
    typedef enum{DbgUart = 1,// 定义一个枚举类型BoardUart,用来区分不同功能的UART(通用异步收发器)  WiFiBTUart // WiFi蓝牙UART,后面会介绍WiFi蓝牙  
    }BoardUart;// 定义宏,将DbgUart映射到具体的USART(通用同步异步收发器)硬件接口,这里映射到USART1  
    #define DBGUART     USART1
    #define WiFiUART    USART3int UartDrvInit(BoardUart uart);// 定义宏,将DbgUart映射到具体的USART(通用同步异步收发器)硬件接口,这里映射到USART1 
    // 声明UartDrvWrite函数,该函数用于向指定的UART接口写入数据,参数pbuf指向要写入的数据,length表示数据长度
    int UartDrvWrite(BoardUart uart, unsigned char *pbuf, unsigned short length);
    // 声明UartDrvRead函数,该函数用于从指定的UART接口读取数据,参数pbuf用于存储读取到的数据,length表示读取的数据长度
    int UartDrvRead(BoardUart uart, unsigned char *pbuf, unsigned short length);// 结束头文件防止重复包含的检查  
    #endif /* __DRV_UART_H */
    
    drv_uart.c
    #include "drv_uart.h"
    #include "usart.h"
    #include "stm32f4xx_hal.h"int UartDrvInit(BoardUart uart)
    {switch(uart){case DbgUart:{break;}case WiFiBTUart:{break;}default:break;}return 0;
    }int UartDrvWrite(BoardUart uart, unsigned char *pbuf, unsigned short length)
    {int ret = -1;switch(uart){case DbgUart:{HAL_StatusTypeDef status = HAL_UART_Transmit(&huart1, pbuf, length, length*5);if(HAL_OK == status)    ret = 0;break;}case WiFiBTUart:{break;}default:break;}return ret;
    }int UartDrvRead(BoardUart uart, unsigned char *pbuf, unsigned short length)
    {int ret = -1;switch(uart){case DbgUart:{HAL_StatusTypeDef status = HAL_UART_Receive(&huart1, pbuf, length, length*5);if(HAL_OK == status)    ret = 0;break;}case WiFiBTUart:{break;}default:break;}return ret;
    }
  5. 添加打印重定向代码:printf.h和printf.c,方法与LED的一样。                                               printf.h 
    #ifndef __PRINTF_H
    #define __PRINTF_H#ifndef USE_PRINTF
    #define USE_PRINTF  (1)
    #endif /* USE_PRINTF */#if USE_PRINTF#include <stdio.h>#define xprintf(...)    printf(__VA_ARGS__)
    #else #define xprintf(...)
    #endif /* USE_PRINTF */#endif /* __PRINTF_H */
    
     printf.c
    #include "drv_uart.h"
    #include <stdio.h>struct __FILE{int handle;
    };FILE __stdout;int fputc(int ch, FILE *f)
    {(void)f;int ret = UartDrvWrite(DbgUart, (unsigned char*)&ch, 1);if(0 == ret)return ch;return 0;
    }
    
  6. 在main.c添加代码:添加头文件
    #include "drv_led.h"
    #include "drv_key.h"
    #include "drv_uart.h"
    #include "printf.h"
    #include <string.h>
    
       /* USER CODE BEGIN 2 */LedStatus d1_s = led_off; //灯状态LedStatus d2_s = led_off;LedDrvInit(LED1);LedDrvInit(LED2);KeyDrvInit(K2);KeyDrvInit(K3);UartDrvInit(DbgUart);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */if(KeyDrvRead(K2) == isPressed)/* 检测按键的状态 */  {HAL_Delay(100);/* 消抖处理 */ if(KeyDrvRead(K2) == isPressed){d1_s =!d1_s; /* 切换LED1状态 */  LedDrvWrite(LED1, d1_s); /* 更新LED1的显示状态 */  UartDrvWrite(DbgUart,(unsigned char *)"KEY1 is Pressed,LED1 is On\r\n", strlen("KEY1 is Pressed,LED1 is On\r\n"));	}}if(KeyDrvRead(K3) == isPressed)		/* 检测按键的状态 */  {HAL_Delay(100);/* 消抖处理 */ if(KeyDrvRead(K3) == isPressed){d2_s =!d2_s;/* 切换LED1状态 */ LedDrvWrite(LED2, d2_s);/* 检测按键的状态 */  UartDrvWrite(DbgUart,(unsigned char *)"KEY2 is Pressed,LED2 is On\r\n", strlen("KEY2 is Pressed,LED2 is On\r\n"));}}	}/* USER CODE END 3 */
  7. 编译代码:Keil编译生成的代码。
  8. 烧录程序:将编译好的程序用ST-LINK烧录到STM32微控制器中。

四、运行结果

观察结果:一旦程序烧录完成并运行,你应该能看到按不同的按键会点亮不同的LED灯,串口打印按键和灯的状态。如果一切正常,恭喜你,你现在已经是一个掌握模块化的编写“点灯大师”了!​​

五、总结

模块化的编写方式对之前的代码封装了一层,提供了与LED、按键、串口硬件交互的接口,使得软件开发者可以在不直接操作硬件的情况下控制LED灯、按键、串口,可以直接用到STM32F103、STM32H系列等中,如果引脚不一样,只需修改引脚即可。通过上面的代码,希望你更多的采用模块化的编写方式,确保代码结构清晰、功能明确,提高可读性和可维护性,降低功能耦合,增强重用和扩展性,也促进并行开发(比如A员工做LED灯、B员工做按键、C员工做串口),提升效率,便于项目管理和维护。

六、注意事项

1.确保你的开发环境和工具链已经正确安装和配置。

2.在STM32CubeMX中配置GPIO时,注意选择正确的引脚和模式。

3.在编写代码时,确保使用正确的GPIO端口和引脚宏定义。

4.LED没有按预期点亮,按一下复位键,检查代码、连接和电源是否正确。

6.串口没有打印,检查代码、连接、电源、波特率是否正确,串口是否打开。

七、预告

下一节将LED、按键、串口封装成一个GPIO类,直接3归1,敬请关注!

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

数控6面钻的优缺点

在木工、家具制造和建筑行业中&#xff0c;数控6面钻已成为一种革命性的工具。这种先进的机器以其高效、精准和多功能性受到了广大制造商的青睐。然而&#xff0c;就像任何技术产品一样&#xff0c;数控6面钻也有其优缺点。在本文中&#xff0c;我们将深入探讨数控6面钻的优缺点…

20240423给飞凌的OK3588-C开发板适配OV13855【绿屏】查找问题

20240423给飞凌的OK3588-C开发板适配OV13855【绿屏】查找问题 2024/4/23 19:43 修改2个部分&#xff1a; 1、DTS中CAM1由ISP0处理修改为ISP1处理。【感觉修改为ISP1之后就不出错了&#xff0c;难道ISP0有问题&#xff1f;】 2、ov13855.c修改为 荣品的RK3588开发板提供的SDK An…

等级保护详解:企业为何需要等级保护及等保测评的重要性

在信息化高速发展的今天&#xff0c;网络安全问题日益凸显&#xff0c;各类网络安全事件频发&#xff0c;给企业和个人带来了极大的损失。为了加强网络安全管理&#xff0c;提高网络安全防护能力&#xff0c;我国推出了网络安全等级保护制度&#xff0c;简称“等保”。那么&…

Fisher判别:理解数据分类的经典方法

在机器学习和统计分类的领域中&#xff0c;Fisher判别&#xff08;也称为Fisher线性判别分析&#xff09;是一种非常重要的方法&#xff0c;旨在从数据中提取重要特征&#xff0c;以实现对样本的分类。即Fisher判别分析&#xff08;Fisher Discriminant Analysis, FDA&#xff…

【数据结构】stack queue —— 栈和队列

前言 这阵子一直在学数据结构&#xff0c;知识点消化地有点慢导致博客一直没写&#xff0c;现在总算是有时间歇下来补补前面落下的博客了。从现在起恢复周更&#xff0c;努努力一周两篇也不是梦……闲话少说&#xff0c;今天就让我们一起来认识栈和队列 1. 栈的介绍和使用 栈…

模块三:二分——69.x的平方根

文章目录 题目描述算法原理解法一&#xff1a;暴力查找解法二&#xff1a;二分查找 代码实现暴力查找CJava 题目描述 题目链接&#xff1a;69.x的平方根 算法原理 解法一&#xff1a;暴力查找 依次枚举 [0, x] 之间的所有数 i &#xff08;这⾥没有必要研究是否枚举到 x /…

基于Linux系统命令行安装KingbaseES数据库

人大金仓通用性数据库&#xff08;Kingbase&#xff09;下载网址&#xff1a;人大金仓-成为世界卓越的数据库产品与服务提供商 选择“软件版本-数据库”&#xff0c;筛选条件Linux、完整版。找到需要的版本&#xff0c;点击下载。我下载的是KingbaseES_V008R006C008B0014_Lin6…

初入单元测试

单元测试&#xff1a;针对最小的功能单元(方法)&#xff0c;编写测试代码对其进行正确性测试 Junit可以用来对方法进行测试&#xff0c;虽然是有第三方公司开发&#xff0c;但是很多开发工具已经集成了&#xff0c;如IDEA。 Junit 优点&#xff1a;可以灵活的编写测试代码&am…

基础SQL DQL语句

基础查询 select * from 表名; 查询所有字段 create table emp(id int comment 编号,workno varchar(10) comment 工号,name varchar(10) comment 姓名,gender char(1) comment 性别,age tinyint unsigned comment 年龄,idcard char(18) comment 身份证号,worka…

yolov8使用pycharm用代码训练连续运行问题 RuntimeError

背景&#xff1a;PyCharm下使用代码运行yolov8 问题描述 连续运行两次导致进程冲突 RuntimeError 原因&#xff1a;在当前进程完成引导阶段之前&#xff0c;试图启动新的子进程。 RuntimeError: An attempt has been made to start a new process before the current proces…

【Nginx】centos和Ubuntu操作系统下载Nginx配置文件并启动Nginx服务详解

目录 &#x1f337; 安装Nginx环境 &#x1f340; centos操作系统 &#x1f340; ubuntu操作系统 &#x1f337; 安装Nginx环境 以下是在linux系统中安装Nginx的步骤&#xff1a; 查看服务器属于哪个操作系统 cat /etc/os-release安装 yum&#xff1a; 如果你确定你的系统…

【经验分享】Ubuntu22.04安装微信(linux官方版)

【经验分享】Ubuntu22.04安装微信linux官方版 前言安装方法效果展示总结 前言 最近腾讯推出了linux官方版微信wechat&#xff0c;但是仅支持国产系统麒麟和统信UOS&#xff0c;这对使用Ubuntu的小伙伴可不太友好&#xff0c;打算装个试试&#xff0c;在网上搜了下终于找到快捷…

验证 python解释器是否安装成功

一. 简介 前一篇文章学习了下载并安装 python解释器&#xff0c;文章如下&#xff1a; windows系统下python解释器安装-CSDN博客 本文验证 python解释器是否安装成功。 二. 验证 python解释器是否安装成功 1. 首先&#xff0c;打开 Windows系统的 "cmd" 界面。…

SD-WAN制造业网络优化方案

制造业在数字化浪潮的推动下&#xff0c;进行转型的需求越来越强烈。网络作为制造业数字化转型的关键基础设施&#xff0c;其稳定性、安全性和灵活性直接影响着企业的运营效率和市场竞争力。而SD-WAN可以为制造业提供有效的解决方案&#xff0c;让制造业顺利高效地进行数字化转…

内插和抽取

抽取&#xff1a; 频域表达式的关系&#xff1a; 1、角频率扩大M倍 2、移动2pi、22pi…&#xff08;n-1&#xff09; 2pi 3、相加 4、幅度变为1/M 内插&#xff1a; 加入低通滤波&#xff0c;减小混叠&#xff0c;但是由于截短&#xff0c;也会造成误差&#xff0c;但是…

投资网站汇总

1、 中信证券(600030)历年财务指标——亿牛网https://eniu.com/gu/sh600030/cwzb 2、 3、 4、

DFS与回溯专题:路径总和问题

DFS与回溯专题&#xff1a;路径总和问题 一、路径总和 题目链接&#xff1a; 112.路径总和 题目描述 代码思路 对二叉树进行dfs搜索&#xff0c;递归计算每条路径的节点值之和&#xff0c;当某个节点的左右子节点都为空时&#xff0c;说明已经搜索完成某一条路径&#xff0…

Nacos原理简单介绍

注册中心原理 官网&#xff1a;Nacos 注册中心的设计原理 | Nacos nacos注册中心采用了 &#xff1a;pull &#xff08;客户端的轮询&#xff09;和push &#xff08;服务端主动push&#xff09;策略 客户端启动时会将当前服务的信息包含ip、端口号、服务名、集群名等信息封装…

子比主题7.7开心版全版本,一款优雅的WordPress正版主题推荐

可玩性很高的一款主题&#xff0c;也是本站同款主题&#xff0c;直接免费下载&#xff01;子比主题开心版全版本 不恢复授权&#xff0c;不删除文章&#xff01;不恢复授权&#xff0c;不删除文章&#xff01;不恢复授权&#xff0c;不删除文章&#xff01; 主题已经更新7.7非…

[Flutter3] Json转dart模型举例

记录一下 Android studio plugin -> FlutterJsonBeanFactory 处理json转dart 模型 案例 json字符串, 一个 response的data返回数据 {"code":1,"msg":"\u64cd\u4f5c\u6210\u529f","data":{"list":{"id":"8…