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://www.hqwc.cn/news/643327.html

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

相关文章

ClickHouse用UDF解析XML字符串和XML文件

一.如果是读取xml文件的时候&#xff0c;文件入库需要使用文件读取UDF 创建了1个测试文件 wsdFileRead()&#xff1a; 直接读取文件内容 SELECT wsdFileRead(/home/temp/wsd_test.xml)Query id: 09b6e5fe-7169-43f7-b001-90e2eeabb8da┌─wsdFileRead(/home/temp/wsd_test.xm…

关于c++中的操作符:new和delete

目录 1.什么是new和delete 2.new和delete的用法 2.1 new和delete普通用法 2.2 new和delete的升级用法 1.什么是new和delete C语言中有两个函数用于动态开辟、释放内存----malloc和freec中又引入了两个操作符----new和delete来用于开辟、释放内存 说到这应该对这两个操作符有…

西电超算使用方法-简易版

一、引言 西电超算不错&#xff0c;我很喜欢。本文仅供自己学习使用。 二、环境搭建 搭建环境需要有一些依赖库&#xff0c;但是其实西电超算说明手册并没有写的非常清楚。因此&#xff0c;这次实战演示一下&#xff0c;写一个运行sh文件脚本并提交作业。 1、选择GPU还是CP…

JAVA:Kettle 强大的开源ETL工具

请关注微信公众号&#xff1a;拾荒的小海螺 1、简述 Kettle&#xff08;Pentaho Data Integration&#xff09;&#xff1a;强大的开源ETL工具Kettle&#xff0c;又称作Pentaho Data Integration&#xff0c;是一款流行的开源ETL&#xff08;Extract, Transform, Load&#x…

mysql之执行流程图

今天有刷了一篇小林coding的MYSQL图解,写的是真好,自己根据自己的理解画了一张,整理下来真的感觉收获不少,嗯,坚持输出! 小林coding传送门: 执行一条 select 语句&#xff0c;期间发生了什么&#xff1f; | 小林coding (xiaolincoding.com)

Hdu1350 Taxi Cab Scheme 【最小路径覆盖】

Taxi Cab Scheme 题意 有一张边长不超过 200 200 200 的网格图&#xff0c;有若干个乘客&#xff0c; 乘客 i i i 的需求是&#xff1a; h h : m m , ( a , b ) , ( c , d ) hh:mm, (a,b) , (c, d) hh:mm,(a,b),(c,d)&#xff0c;意为他需要在 h h 时 m m 分 hh时mm分 hh时…

windows系统下python解释器安装

一. 简介 本文简单学习一下python开发学习中&#xff0c;所使用到的 python解释器的下载安装。后面再学习下载安装python的 IDE开发工具&#xff0c;这里要安装的python的 IDE开发工具为 PyCharm。 二. Windows系统下python解释器与IDE开发工具下载安装 1. python解释器下载…

Atlas Vector Search:借助语义搜索和 AI 针对任何类型的数据构建智能应用

Atlas Vector Search已正式上线&#xff01; Vector Search&#xff08;向量搜索&#xff09;现在支持生产工作负载&#xff0c;开发者可以继续构建由语义搜索和生成式人工智能驱动的智能应用&#xff0c;同时通过 Search Node&#xff08;搜索节点&#xff09;优化资源消耗并…

参数传递 的案例

文章目录 12 1 输出一个int类型的数组&#xff0c;要求为&#xff1a; [11,22,33,44,55] package com.zhang.parameter; //有关方法的案例 public class MethodTest3 {public static void main(String[] args) {//输出一个int类型的数组&#xff0c;要求为&#xff1a; [11,…

dremio支持设置

Dremio 支持提供可用于诊断目的的设置。这些设置通过 Dremio UI&#xff1a;设置>支持启用&#xff08;或禁用&#xff09; 使用 Client Tools 可以配置当用户查看数据集中的数据时&#xff0c;Dremio 项目的工具栏上显示哪些客户端应用程序按钮。用户可以通过单击相应的工具…

mybatis快速入门进阶篇-执行CRUD操作-typeAliases别名-接口绑定

目录结构 所需jar包 https://download.csdn.net/download/weixin_44201223/89160447?spm1003.2166.3001.6637.1 1.创建数据表&#xff08;book&#xff09; # 创建book表 create table book(id int auto_increment primary key,name varchar(255) ,price double ,num int )…

【python】python新闻文本数据统计和聚类 (源码+文本)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…