在一个嵌入式系统中,可能存在许多输入或输出的IO口,输入有霍尔传感器、红外对管等,输出有LED、电源控制开关等。如果说硬件可以一次成型,那么随便一份代码都可以完成IO的配置工作,但研发阶段的产品,硬件各种修改是难免的,每一次 IO 的修改,对于底层开发人员来说,可能都是一次挑战。因为一旦有某一个 IO 配置错误,或者原来的配置没有修改正确(比如一个 IO 在原来的硬件适配中是输入,之后的硬件需要修改成输出),那么你很难查出来这是什么问题,因为这个时候不仅硬件修改了,软件也修改了,你需要先定位到底是软件问题还是硬件问题,所以一个好用的 IO 的配置框架就显得很有必要了。
有道友会说,不如使用 CubeMx 软件进行开发吧。
1、这个软件适用于 ST 单片机,以前还能用,现在,除非你家里有矿,不然谁用的起STM32?基本上都国产化了(虽然有些单片机号称兼容,但到底还是有些差异的)。2、公司原本的代码就是使用标准库,只是因为IO 的变化,你就需要把整个库换掉吗?时间上允许吗?你确定修改后不会出现大问题?3、国产化的芯片可没有所谓的标准库和HAL库供你选择,每一家都有各自的库,如果你的产品临时换方案怎么办?4、HAL 效率问题。今天鱼鹰介绍一个简单实用的框架,可用于快速增加或修改IO配置,甚至修改底层库。假设有3个 LED 作为输出、3 个霍尔传感器作为输入:输入配置代码:调试的时候,我们可以很方便的查看每个 IO 的状态是怎样的,而不用管 0 或 1 到底代表什么意思:#defineGPIOx_DefGPIO_TypeDef*#define GPIOMode_Def GPIOMode_TypeDeftypedef struct{GPIOx_Def gpio;uint16_t msk;GPIOMode_Def pull_up_down;} bsp_input_pin_def;#define _GPIO_PIN_INPUT(id, pull, gpiox, pinx) [id].gpio = (GPIOx_Def)gpiox, [id].msk = (1 << pinx), [id].pull_up_down = (GPIOMode_Def)pull#define GPIO_PIN_INPUT(id, pull, gpiox, pinx) _GPIO_PIN_INPUT(id, pull, gpiox, pinx)#define bsp_pin_get_port(gpiox) ((uint16_t)((GPIO_TypeDef *)gpiox)->IDR)#define bsp_pin_get_value(variable,id) do{ bsp_pin_get_port(bsp_input_pin[id].gpio) & bsp_input_pin[id].msk ? variable |= (1 << id) : 0;} while(0)typedef enum{PIN_INPUT_HALL_0 = 0, // 输入 IO 定义PIN_INPUT_HALL_1,PIN_INPUT_HALL_2,PIN_INPUT_MAX}bsp_pin_input_id_def;static const bsp_input_pin_def bsp_input_pin [PIN_INPUT_MAX] ={GPIO_PIN_INPUT(PIN_INPUT_HALL_0, BSP_GPIO_PUPD_NONE, GPIOA, 0),GPIO_PIN_INPUT(PIN_INPUT_HALL_1, BSP_GPIO_PUPD_NONE, GPIOB, 8),GPIO_PIN_INPUT(PIN_INPUT_HALL_2, BSP_GPIO_PUPD_NONE, GPIOE, 9),};// 单个 IO 初始化函数void bsp_pin_init_input(GPIOx_Def gpiox, uint32_t msk, GPIOMode_TypeDef pull_up_down){uint32_t temp;assert_param((msk & 0xffff0000) == 0 && gpiox != 0);temp = ((uint32_t) gpiox - (uint32_t) GPIOA) / ( (uint32_t) GPIOB - (uint32_t) GPIOA);/* enable the led clock */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA << temp, ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = (GPIOMode_Def)pull_up_down;GPIO_InitStruct.GPIO_Pin = msk;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init((GPIO_TypeDef*)gpiox, &GPIO_InitStruct);}// 所有 IO 初始化void gpio_input_init(){bsp_input_pin_def *info;info = (bsp_input_pin_def *)&bsp_input_pin;for(int i = 0; i < sizeof(bsp_input_pin)/sizeof(bsp_input_pin[0]); i++){bsp_pin_init_input(info->gpio, info->msk, info->pull_up_down);info++;}}// 最多支持 32 个 IO 输入uint32_t bsp_input_all(void){uint32_t temp = 0;bsp_pin_get_value(temp, PIN_INPUT_HALL_0);bsp_pin_get_value(temp, PIN_INPUT_HALL_1);bsp_pin_get_value(temp, PIN_INPUT_HALL_2);return temp;}// 读取单个 IO 状态uint32_t bsp_input_level(bsp_pin_input_id_def id){return (bsp_pin_get_port(bsp_input_pin[id].gpio) & bsp_input_pin[id].msk) ? 1 : 0;}typedef enum{HW_HAL_LEVEL_ACTIVE = 0, // 可直接修改为 0 或 1,另一个枚举值自动修改为相反值HW_HAL_LEVEL_NO_ACTIVE = !HW_HAL_LEVEL_ACTIVE,}hw_input_hal_status_def;typedef struct{hw_input_hal_status_def hal_level0;uint8_t hal_level1;uint8_t hal_level2;}bsp_input_status_def;bsp_input_status_def bsp_input_status;int main(void){USRAT_Init(9600);//必须,进入调试模式后点击全速运行gpio_input_init();while(1){uint32_t temp = bsp_input_all();bsp_input_status.hal_level0 = (hw_input_hal_status_def)((temp >> PIN_INPUT_HALL_0) & 1);bsp_input_status.hal_level1 = ((temp >> PIN_INPUT_HALL_1) & 1);bsp_input_status.hal_level2 = ((temp >> PIN_INPUT_HALL_2) & 1);}}
输出配置代码:这个框架有啥好处呢?1、自动完成 GPIO 的时钟初始化工作,也就是说你只需要修改引脚即可,不必关心时钟配置,但对于特殊引脚(比如PB3),还是得另外配置才行。2、应用和底层具体 IO 分离,这样一旦修改了 IO,应用代码不需要进行任何修改。3、增加或删减 IO 变得很简单,增加 IO时,首先加入对应枚举,然后就可以添加对应的 IO 了。删除 IO时,只要屏蔽对应枚举值和引脚即可。4、参数检查功能, IO 删除时,因为屏蔽了对应的枚举,所以编译时可以帮你发现问题,而增加 IO 时,它可以帮你在运行时检查该 IO是否进行配置了,可以防止因为失误导致的问题。#define GPIOx_Def GPIO_TypeDef*#define GPIOMode_Def GPIOMode_TypeDeftypedef struct{GPIOx_Def gpio;uint32_t msk;uint32_t init_value;} bsp_output_pin_def;#define _GPIO_PIN_OUT(id, gpiox, pinx, init) [id].gpio = gpiox, [id].msk = (1 << pinx), [id].init_value = init#define GPIO_PIN_OUT(id, gpiox, pinx, init) _GPIO_PIN_OUT(id, gpiox, pinx, init)#define _bsp_pin_output_set(gpiox, pin) (gpiox)->BSRR = pin#define bsp_pin_output_set(gpiox, pin) _bsp_pin_output_set(gpiox, pin)#define _bsp_pin_output_clr(gpiox, pin) (gpiox)->BRR = pin#define bsp_pin_output_clr(gpiox, pin) _bsp_pin_output_clr(gpiox, pin)typedef enum{PIN_OUTPUT_LED_G,PIN_OUTPUT_LED_R,PIN_OUTPUT_LED_B,PIN_OUTPUT_MAX}bsp_pin_output_id_def;static const bsp_output_pin_def bsp_output_pin [PIN_OUTPUT_MAX] ={GPIO_PIN_OUT(PIN_OUTPUT_LED_G, GPIOA, 0, 0),GPIO_PIN_OUT(PIN_OUTPUT_LED_R, GPIOF, 15, 0),GPIO_PIN_OUT(PIN_OUTPUT_LED_B, GPIOD, 10, 0),};void bsp_pin_init_output(GPIOx_Def gpiox, uint32_t msk, uint32_t init){uint32_t temp;assert_param((msk & 0xffff0000) == 0 && gpiox != 0);temp = ((uint32_t) gpiox - (uint32_t) GPIOA) / ( (uint32_t) GPIOB - (uint32_t) GPIOA);/* enable the led clock */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA << temp, ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode = (GPIOMode_Def)GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Pin = msk;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;GPIO_Init((GPIO_TypeDef*)gpiox, &GPIO_InitStruct);if(init == 0){bsp_pin_output_clr(gpiox, msk);}else{bsp_pin_output_set(gpiox, msk);}}void bsp_output_init(){bsp_output_pin_def *info;info = (bsp_output_pin_def *)&bsp_output_pin;for(int i = 0; i < sizeof(bsp_output_pin)/sizeof(bsp_output_pin[0]); i++){bsp_pin_init_output(info->gpio, info->msk, info->init_value);info++;}}void bsp_output(bsp_pin_output_id_def id, uint32_t value){assert_param(id < PIN_OUTPUT_MAX);if(value == 0){bsp_pin_output_clr(bsp_output_pin[id].gpio, bsp_output_pin[id].msk);}else{bsp_pin_output_set(bsp_output_pin[id].gpio, bsp_output_pin[id].msk);}}int main(void){USRAT_Init(9600);//必须,进入调试模式后点击全速运行bsp_output_init();while(1){bsp_output(PIN_OUTPUT_LED_G, 1);bsp_output(PIN_OUTPUT_LED_B, 0);bsp_output(PIN_OUTPUT_LED_R, 1);}}
5、更改库时可以很方便,只需要修改对应的宏即可,目前可以顺利在 GD32 和 STM32 库进行快速更换。6、对于输入 IO 而言,可以方便的修改有效和无效状态,防止硬件修改有效电平。对于输出 IO 而言,可以设定初始 IO 电平状态。7、代码简单高效,尽可能的复用代码,增加一个 IO 只需要很少的空间。8、缺点就是,只对同种配置的 IO 可以这样用。
审核编辑 :李倩
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。
举报投诉
-
单片机
+关注
关注
6078文章
45570浏览量
673392 -
霍尔传感器
+关注
关注
28文章
803浏览量
66449
原文标题:简单实用IO输入输出框架
文章出处:【微信号:zhuyandz,微信公众号:FPGA之家】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
热点推荐
MTK Android 13状态栏耳机图标“失踪”?这个SystemUI配置修改帮你找回
在 Android ROM 定制或设备调试中,SystemUI(系统界面)的配置修改往往直接影响用户对设备的视觉感知 —— 比如状态栏图标显示 / 隐藏、通知样式、导航栏布局等。今天我们就以 MTK
RK3588平台串口配置修改指南:切换至串口8
在嵌入式开发中,串口作为基础调试接口至关重要。本文档针对 RK3588 平台,详细介绍如何将系统默认串口修改为串口 8(UART8),包括 U-Boot 阶段和内核阶段的配置调整。该修改适用于
硕博电子IO模块修改波特率和节点ID操作步骤
正确设置波特率与节点ID,是保证IO模块与控制器稳定通信的关键。配置不当易引发设备失联、数据错乱等故障。本文将详细讲解波特率与节点ID的规范修改步骤,轻松搞定配置难题。
linux-arm开发环境的简单配置
linux-arm开发环境简单配置
关于linux-arm开发环境简单配置是ARM学习的第一步,很多初学者会在这问题上纠结很久都不能配置好
发表于 01-13 07:56
利用EasyGo DeskSim快速完成Modbus协议通讯交互
EasyGo DeskSim是一款配置型的实时仿真软件,它允许用户将 Simulink 算法程序快速部署到 EasyGo 实时仿真机上。实时仿真机支持选配不同的 FPGA 芯片和 IO 模块,能够处理高速信号,并通过
利用EasyGo DeskSim快速实现PWM波信号采集
EasyGo DeskSim是一款配置型的实时仿真软件,它允许用户将 Simulink 算法程序快速部署到 EasyGo 实时仿真机上。实时仿真机支持选配不同的 FPGA 芯片和 IO 模块,能够处理高速信号,并通过
利用EasyGo DeskSim快速实现PWM波信号输出
EasyGo DeskSim是一款配置型的实时仿真软件,它允许用户将 Simulink 算法程序快速部署到 EasyGo 实时仿真机上。实时仿真机支持选配不同的 FPGA 芯片和 IO 模块,能够处理高速信号,并通过
GraniStudio:IO初始化以及IO资源配置例程
1.文件运行 导入工程 双击运行桌面GraniStudio.exe。 通过引导界面导入IO初始化以及IO资源配置例程,点击导入按钮。 打开IO初始化以及
利用EasyGo DeskSim快速实现信号采集
EasyGo DeskSim是一款配置型的实时仿真软件,它允许用户将 Simulink 算法程序快速部署到 EasyGo 实时仿真机上。实时仿真机支持选配不同的 FPGA 芯片和 IO 模块,能够处理高速信号,并通过
干货分享 | TSMaster IO功能使用指南—基于同星带IO设备的配置与操作步骤
IO模块是一种用于连接计算机系统或控制系统与外部设备之间的接口模块。数字IO模块用于处理二进制信号的输入和输出,它们可以接收和发送数字信号,
利用EasyGo DeskSim快速实现信号输出
EasyGo DeskSim是一款配置型的实时仿真软件,它允许用户将 Simulink 算法程序快速部署到 EasyGo 实时仿真机上。实时仿真机支持选配不同的 FPGA 芯片和 IO 模块,能够处理高速信号,并通过
Linux系统中iptables防火墙配置详解
iptables是Linux内核中用于配置防火墙规则的工具。它基于Netfilter框架,可以对通过网络接口的数据包进行过滤、修改等操作。通过设置一系列规则,iptables能够控制哪
MCU是否可以使用普通IO口和数据总线控制cy7c68013a,用异步slavefifo模式增加一个与PC通信的USB口?
cy7c68013a的固件已经配置成异步slave模式,是否有MCU用IO口控制cy7c68013a通过数据总线来实现与PC的USB口通信,这样应用的示例程序或教程?
目的就是在现有的MCU系统中
发表于 05-30 06:32
简单实用的框架,可用于快速增加或修改IO配置
评论