细说工程师如何编写有使用价值的单片机程序(以GPIO为例)

news2024/9/27 8:15:25

目录

一、何谓有使用价值的单片机程序

二、 硬件板及设计目的

三、建立工程

1.配置GPIO

2.配置时钟源和Debug     

3.配置系统时钟

四、代码编写与修改

1.创建应用程序文件夹

2.编写应用文件keyed.h和 keyled. c

3.修改main.c


一、何谓有使用价值的单片机程序

        前面我写了很多MCU程序开发相关的文章,这些文章都是以学习知识为目的的简单应用程序开发,不是以产品开发为目的进行的软件开发。这些简单的软件都集中写在main.c和main.h里,其特征是文件没有目录层次,源代码(函数)直接写在main.c里,main.c程序结构复杂,易读性差,可移植性差。

  • 有使用价值单片机程序首要特征是main.c程序结构扁平、简单,可读性好。
  • 有使用价值单片机程序建立了与应用相关的文件目录层次,在项目浏览器里,会建立与产品应用相关的文件夹,可以不止一个,文件夹里包括与产品应用有关的.c,.h文件。
  • 在与产品应用有关的.h文件定义了大量的与应用相关的宏;在与产品应用有关的.c文件定义了大量的与产品应用相关的宏函数、方法;
  • main.c程序中调用自定义的宏函数(方法),所以代码很短,可读性好。
  • 有使用价值单片机程序可移植性好。配置参数(MCU管脚资源)大量标识资源的别名,不直接使用管脚编程。这样编写的程序可移植性好,当更换硬件MCU,对目标MCU的管脚标识相同的名称,源程序几乎可以不修改就使用(新旧平台管脚的高低电平不同时需要简单修改)。

二、 硬件板及设计目的

        本文使用的硬件板是ST的开发板NUCLEO-G474RE,板上MCU型号为STM32G474RET6。并按照资源提示设计制造了扩展IO板,有需要此扩展板的留言联系我。

        设计一个示例Demo6_1KeyLED,其功能和操作流程如下。        

  • 按下KeyLeft键时,使LED2的输出翻转。
  • 按下KeyRight键时,使LED1的输出翻转。
  • 按下KeyUp键时,使LED1和LED2的输出都翻转。
  •  按下KeyDown键时,蜂鸣器输出翻转。

        根据按键、LED和蜂鸣器的电路,整理出MCU连接的GPIO引脚的输入/输出配置:

用户标签

引脚名称

引脚功能

GPIO模式

默认电平

上拉或下拉

LED1

PB11

GPIO_Output

推挽输出

High

上拉

LED2

PB12

GPIO_Output

推挽输出

High

上拉

KeyRight

K1

PA0

GPIO_Input

输入

上拉

KeyDown

K2

PA1

GPIO_Input

输入

上拉

KeyLeft

K3

PA6

GPIO_Input

输入

上拉

KeyUp

K5

PA7

GPIO Input

输入

上拉

Buzzer

PA4

GPIO_Output

推挽输出

LOW

上拉

三、建立工程

1.配置GPIO

        配置PB11、PB12,GPIO OUTPUT,默认High Level,PP,PullUp,High Speed,标识为LED1,LED2;

        配置PA4,GPIO OUTPUT,默认Low Level,PP,PullUp,High Speed,标识为Buzzer;

        配置PA0,Input mode,PP,标识为:KeyRight;

        配置PA1,Input mode,PP,标识为:KeyDown;

        配置PA6,Input mode,PP,标识为:KeyLeft;

        配置PA7,Input mode,PP,标识为:KeyUp;

2.配置时钟源和Debug     

        打开System Core中的RCC,高速时钟(HSE)选择Crystal/ eramic Resonator,使用片外时钟晶体作为HSE的时钟源。在SYS中将Debug设置Serial Wire。

3.配置系统时钟

        将系统时钟(SYSCLK)频率配置为170 MHz。

四、代码编写与修改

1.创建应用程序文件夹

        在项目根目录下,我们创建一个文件夹 KEYLED,然后创建文件 keyled.h和 keyled. c,保

存到这个文件夹下。创建方法:

        把鼠标光标放置在项目名称上,右键→属性(Properties),点击→C/C++ General→Paths and Symbols→选择Includes菜单,点击ADD,添加新的文件目录KEY_LED→一定要点击应用按钮。

        再选择Source Location菜单 → 点击ADD,在提示窗口中,选择刚刚新添加的文件夹KEY_LED → 最后,一定要点击应用按钮。

2.编写应用文件keyled.h和 keyled. c

/* keyled.h */
 #include "main.h"	//在main.h中定义了Keys、LEDs和Buzzer引脚的宏
//表示4个按键的枚举类型
 typedef enum {
 	KEY_NONE = 0,	//没有按键被按下
	KEY_LEFT, 		//KeyLeft键
	KEY_RIGHT, 		//KeyRight键
	KEY_UP,			//KeyUp键
	KEY_DOWN, 		//KeyDown键
 }KEYS;

#define KEY_WAIT_ALWAYS 0 	//作为函数ScanKeys()的一种参数,表示一直等待按键输入
//轮询方式扫描按键,timeout=KEY_WAIT_ALWAYS时一直扫描,否则等待时间timeout,单位ms
KEYS ScanPressedKey(uint32_t timeout);

#ifdef LED1_Pin	 	//LED1的控制
	#define LED1_Toggle()	HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin)					//输出翻转
	#define LED1_ON()		HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET) 	//输出0,亮
	#define LED1_OFF() 		HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET)		//输出1,灭
#endif

#ifdef LED2_Pin 	//LED2的控制
	#define LED2_Toggle()	HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin)					//输出翻转
	#define LED2_ON()		HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET)	//输出0,亮
	#define LED2_OFF()		HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET)		//输出1,灭
#endif

#ifdef Buzzer_Pin	//蜂鸣器的控制
	#define Buzzer_Toggle()	HAL_GPIO_TogglePin(Buzzer_GPIO_Port,Buzzer_Pin)				//输出翻转
	#define Buzzer_ON()		HAL_GPIO_WritePin(Buzzer_GPIO_Port,Buzzer_Pin,GPIO_PIN RESET)//输出0,蜂鸣器不响
	#define Buzzer_OFF()	HAL_GPIO_WritePin(Buzzer_GPIO_Port,Buzzer_Pin,GPIO _PIN_SET) //输出1,蜂鸣器响
#endif

        这个文件包含了头文件main.h,因为要用到main.h中GPIO引脚标签的宏。

        LED和蜂鸣器的操作定义为宏函数,例如,LED1_Toggle()使LED1输出翻转,LED1_ON(),点亮LED1,LED1_OFF()熄灭LED1。只有定义了GPIO引脚的宏之后,才会编译这些宏函数, 所以一个项目里如果用不到蜂鸣器,CubeMX中不定义蜂鸣器的GPIO引脚即可。

        文件还定义了表示按键的枚举类型KEYS,函数ScanPressedKey(uint32_t timeout)用于检测按 建输入,参数timeout是等待时间,如果timeout为KEY_WAIT_ALWAYS,就表示无限等待时 间。函数的返回值是按下的按键的枚举值。

/* keyled.c */
#include	"keyled.h"

//轮询方式扫描4个按键,返回按键值
//timeout单位ms,若timeout=0表示一直扫描,直到有键按下
KEYS ScanPressedKey(uint32_t timeout)
{
	KEYS  key=KEY_NONE;
	uint32_t  tickstart = HAL_GetTick();	//当前计数值
	const uint32_t  btnDelay=10;			//按键按下阶段的抖动,延时再采样时间
	GPIO_PinState keyState;

	while(1)
	{
#ifdef	KeyLeft_Pin		 //如果定义了KeyLeft,就可以检测KeyLeft
		keyState=HAL_GPIO_ReadPin(KeyLeft_GPIO_Port, KeyLeft_Pin); //PA6=KeyLeft,低输入有效,LED1
		if (keyState==GPIO_PIN_RESET)
		{
			HAL_Delay(btnDelay);  //前抖动期
			keyState=HAL_GPIO_ReadPin(KeyLeft_GPIO_Port, KeyLeft_Pin); //再采样
			if (keyState ==GPIO_PIN_RESET)
				return	KEY_LEFT;
		}
#endif

#ifdef	KeyRight_Pin 	//如果定义了KeyRight,就可以检测KeyRight
		keyState=HAL_GPIO_ReadPin(KeyRight_GPIO_Port, KeyRight_Pin); //PA0=KeyRight,低输入有效,LED0
		if (keyState==GPIO_PIN_RESET)
		{
			HAL_Delay(btnDelay); //前抖动期
			keyState=HAL_GPIO_ReadPin(KeyRight_GPIO_Port, KeyRight_Pin);//再采样
			if (keyState ==GPIO_PIN_RESET)
				return	KEY_RIGHT;
		}
#endif

#ifdef	KeyDown_Pin		//如果定义了KeyDown,就可以检测KeyDown
		keyState=HAL_GPIO_ReadPin(KeyDown_GPIO_Port, KeyDown_Pin); //PA1=KeyDown,输入低电平时蜂鸣
		if (keyState==GPIO_PIN_RESET)
		{
			HAL_Delay(btnDelay); //前抖动期
			keyState=HAL_GPIO_ReadPin(KeyDown_GPIO_Port, KeyDown_Pin);//再采样
			if (keyState ==GPIO_PIN_RESET)
				return	KEY_DOWN;
		}
#endif

#ifdef	KeyUp_Pin		//如果定义了KeyUp,就可以检测KeyUp
		keyState=HAL_GPIO_ReadPin(KeyUp_GPIO_Port, KeyUp_Pin); //PA7=KeyUp,输出低电平时双闪
		if (keyState== GPIO_PIN_RESET)
		{
			HAL_Delay(btnDelay); //10ms 抖动期
			keyState=HAL_GPIO_ReadPin(KeyUp_GPIO_Port, KeyUp_Pin);//再采样
			if (keyState == GPIO_PIN_RESET)
				return	KEY_UP;
		}
#endif

		if (timeout != KEY_WAIT_ALWAYS)  //没有按键按下时,会计算超时,timeout时退出
		{
			if ((HAL_GetTick() - tickstart) > timeout)
				break;
		}
	}

	return	key;
}

        函数ScanPressedKey()用轮询方式检测按键输入,也就是用函数HAL_GPIO_ReadPin()不断地读取4个按键引脚的输入,如果某个按键引脚的输入信号有效,就表示检测到按键输入了, 将这个按键的枚举值作为函数返回值。代码使用了条件编译,只有一个按键的引脚宏被定义后,才编译相应代码段。

        函数参数timeout定义了一个超时,单位是ms。在程序中,首先用函数HAL_GetTick()获取系统嘀嗒信号当前计数值,赋值给tickstart,系统嘀嗒信号计数值增大1,就表示过了1ms, 所以用HAL_GetTick()减去tickstart可以得到程序运行的时间。如果timeout设置为0,就表示一直等待,直到有按键按下。

3.修改main.c

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "keyled.h"
/* USER CODE END Includes */
 /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  KEYS curKey = ScanPressedKey(KEY_WAIT_ALWAYS);	//�?测按键输入,�?直等�?
	  switch(curKey)
	  {
	  case KEY_LEFT: 	//keyLeft
		  LED1_Toggle();
		  break;
	  case KEY_RIGHT:	//KeyRight
		  LED2_Toggle();
		  break;
	  case KEY_UP:		//KeyUp
		  LED1_Toggle();
		  LED2_Toggle();
		  break;
	  case KEY_DOWN:	//KeyDown
		  Buzzer_Toggle();
	  default:
		  break;
	  }
	  HAL_Delay(200);	//按键弹起阶段的消抖动延时
  }
  /* USER CODE END 3 */
}

        main()函数中添加的用户代码比较简单,就是在while()循环里用函数ScanPressedKey()一直 等待按键输入,检测到某个有效按键后,函数返回按键值,程序根据按键值做出相应的处理。 在while()循环的最后执行HAL_Delay(200)延时200ms,这是为了消除按键弹起阶段的抖动影响。这个延时时间可以调整,要既能消除按键抖动,又不至于使程序响应迟钝。

        构建项目无误后,我们将其下载到开发板并加以测试,连续运行时分别按4个按键,就会看到开发板上的LED和蜂鸣器的变化符合设计预期。

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

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

相关文章

Python打开JSON/CSV文件的正确方式(针对UnicodeDecodeError)

前言 我们在使用python的过程中,经常需要它完成一些数据处理的工作,其中尤以json/csv文件为常见。今天,博主针对UnicodeDecodeError异常进行试验,因为这个是新手最容易犯错的地方。 Q:如何应对 UnicodeDecodeError 读…

VMware 设置开机自启动虚拟机

1.虚拟机vmx文件的存储路径 2.快捷键图标,右键–>属性–>快捷方式 3.目标命令里填充 -x “vmx路径” 4.点击:应用–>确定 5.“win键R” 输入shell:startup 6.将启动快捷键复制粘贴到里面

8.6 字符串中等 481 Magical String 522 Longest Uncommon Subsequence II

481 Magical String 像之前那个base ,首先对magic string做出分析,才能够分析这道题的规律 读题: //magic string仅包含1 2 并遵循 //串联字符串中 ‘1’ 和 ‘2’ 的连续出现次数可以生成该字符串。concatenating 字符串中的 1 2 contiguous…

java学习--线程基础

概念 程序 进程 线程 单线程与多线程 并发与并行 线程基本使用 基成Tread类 关系图 /* author:我与java相爱相杀---c语言梦开始的地方 今天又是努力学习的一天!!!! */public class Main {public static void main(String[] args…

【Android Git】Android项目版本由SVN变成Git

前言 在Android开发环境中,vcs.xml 文件通常与版本控制系统(VCS,Version Control System)有关,这个文件保存了与版本控制系统相关的配置信息。 以下是 vcs.xml 文件的一些关键点: 版本控制配置&#xff…

Cryptomator-保护你云端上的隐私

网盘为我们提供了随时随地的获取数据的便利性,同时也大大减轻了你我手机电脑空间不足的压力。但是一旦我们选择使用网盘,也意味着把你的私密数据交出去了。 对于公共资料来说,无非就是提供网盘服务的公司知道了你做了保存这些资料的行为而已…

提升用户体验的秘诀:Xinstall带你玩转Web拉起App!

在移动互联网时代,App已成为我们日常生活中不可或缺的一部分。然而,随着App数量的激增,如何让用户更便捷地触达和使用App,成为了开发者和运营者面临的一大挑战。今天,我们就来揭秘一个能够一键实现Web拉起App的神器——…

类和对象(下)C++

1.初始化列表 1.为什么有初始化列表,它的作用? ->初始化列表,是构造函数初始化的另一种形式。 ->在语法上面理解,初始化列表可以认定为是每个成员变量定义初始化的地方. ->引用成员变量,const成员变量&am…

100个免费可商用图库,一次收藏,众生受益

正版图片太贵 免费图片又有风险 免费可商用图片才是设计师心头所好 (当然,土豪除外) 以下100个免费可商用图库 一次收藏,众生受益! skr~skr~skr~ 1、Unsplash https://unsplash.com/ 建…

6个免费的无损音乐下载网站,建议收藏!

分享6个免费的无损音乐下载网站,都是免费的音乐资源,国内外各种风格的音乐都能找到! MyFreeMP3 tools.liumingye.cn/music/ 一个免费的mp3音乐下载网站,里面有丰富的音乐资源,支持在线听歌,也可以下载歌…

找出电脑中的视频文件并把地址输出在记事本文件中,同理通过bat脚本找出需要的其他后缀文件,比如word文件excel文件md文件等

下午的时候,突然很着急,要找到一个之前下载的一个视频文件,我记得是mp4格式的视频文件,但是具体叫什么名字不记得了,更不记得在哪个目录下,所以想了一个办法,通过bat脚本命令,找到所…

Hack The Box-Resource【更新中】

总体思路 信息收集&端口利用 nmap -sSVC itrc.ssg.htb目标开放了两个ssh端口和一个80端口,先查看80端口 网站是一个SSG IT资源中心,主要用于解决网站问题、管理 SSH 访问、清除病毒和解决各种安全问题的权威一站式商店。 后台挂着目录扫描&#x…

threejs加载fbx带tga贴图报错

描述:threejs加载带tga贴图的fbx时,提示 FBXLoader: TGA loader not found, creating placeholder texture for 11\Pylons_A.TGA 方案一: 加载fbx之前,在LoadingManager中添加TGALoader。此方案有两个前提 1、FBXLoader和TGALoa…

JDK-Java IO流概述

JDK-Java IO流概述 概述 一直以来Java三件套(集合、io、多线程)都是最热门的Java基础技术点,我们要深入掌握好这三件套才能在日常开发中得心应手,之前有编写集合相关的文章,这里出一篇文章来梳理一下io相关的知识点。 …

电商客服的贴心快捷回复助手

作为一位电商客服,你是否曾在回复顾客的过程中感到困扰?是否因为经验不足而踩过雷,比如被平台提示用了违禁词,或是不清楚平台的响应率和满意度等问题?如果是这样,那么今天我要向大家介绍一款神奇的软件&…

小白学算法之移除元素(双指针法!)

力扣27:移除元素 题目内容: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素…

使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击-游戏存储(结束!)

文章目录 分数储存写在最后 使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击(一) 使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击-激光组件(二) 使用Godot4组件制作竖版太空射击游戏_2D卷轴飞机射击-飞船动画&#xff08…

制造知识普及(二)--企业内部物料编码(IPN)与制造商物料编码(MPN)

在日常的物料管理业务逻辑中,一物一码是物料管理的基本的业务规则,不管物料从产品开发还是仓库管理,甚至成本核算,都要遵循这个原则,才能保证产品数据的准确性,才具备唯一追溯的可行性。大部分企业都是这种…

OFD 发票解析

文章目录 参考文章1 了解ofd文件结构1.1 如何打开ofd 文件1.2 ofd文件结构1.3 提取信息思路 2. 提取发票信息实现2.1 目录结构2.2 实体类2.3 发票解析类2.4 controller2.5 service 参考文章 ofd发票解析 什么是ofd格式 ofd 格式是一种用于存储金融数据的开放格式,它…