[APM32F4] APM32F427的GPIO驱动(IO分层封装)

[复制链接]
242|1
口天土立口 发表于 2025-11-15 10:00 | 显示全部楼层 |阅读模式
本帖最后由 口天土立口 于 2025-11-15 10:02 编辑

#申请原创# #技术资源#

1. 外设介绍
APM32F427拥有最多114IO引脚,这些引脚都能映射到外部中断向量,同时可以配置为:输入模式、输出模式、复用模式和模拟模式。


2. 硬件
APM32F427ZG TINY


3. 驱动介绍
GPIO使用的状态一般为2种:有效状态、无效状态,在电路中表现为高电平或者低电平;而不同的硬件电路设计或者IC输入或输出,有效状态可能为高电平也可能为低电平。因此,为了更好的让应用层理解并以统一的思想使用底层代码,GPIO的封装可以以有效状态或有效电平的方式执行。
如下代码中,使用结构体数组统一管理所有的输出和输入GPIO信息,并记录对应GPIO的有效电平。此方式,不仅能清晰明了的知道用到的所有GPIO情况,也能一目了然的知道对应GPIO的有效电平,同时在全新产品开发的前期阶段,往往第一版硬件回板后,调试完成,第二版原理图一般都存在调整IO的需求,这种方式能很好的适应,只需要修改结构体数组的内容即可,不影响后面函数接口的代码实现和应用层的代码编写。
  1. typedef struct {
  2.     GPIO_T          *port;          /* PIN组 */
  3.     uint16_t        pin;            /* 引脚 */
  4.     GPIO_MODE_T     mode;           /* 模式 */
  5.     uint32_t        AHB1Periph;     /* 时钟使能 */
  6.     uint8_t         valid_level;    /* 有效电平 */
  7. } gpio_info_t;

  8. /* 输出引脚信息 */
  9. static gpio_info_t output_gpio_info[OUTPUT_NUM] = {
  10.     {GPIOF,     GPIO_PIN_0,    GPIO_MODE_OUT,   RCM_AHB1_PERIPH_GPIOF,   BIT_RESET  },
  11.     {GPIOF,     GPIO_PIN_1,    GPIO_MODE_OUT,   RCM_AHB1_PERIPH_GPIOF,   BIT_RESET  },
  12.     {GPIOF,     GPIO_PIN_2,    GPIO_MODE_OUT,   RCM_AHB1_PERIPH_GPIOF,   BIT_SET    },
  13.     {GPIOF,     GPIO_PIN_3,    GPIO_MODE_OUT,   RCM_AHB1_PERIPH_GPIOF,   BIT_SET    }
  14. };

  15. /* 输入引脚信息 */
  16. static gpio_info_t input_gpio_info[INPUT_NUM] = {
  17.     {GPIOD,     GPIO_PIN_7,    GPIO_MODE_IN,   RCM_AHB1_PERIPH_GPIOD,   BIT_SET     },
  18.     {GPIOD,     GPIO_PIN_8,    GPIO_MODE_IN,   RCM_AHB1_PERIPH_GPIOD,   BIT_SET     },
  19.     {GPIOD,     GPIO_PIN_9,    GPIO_MODE_IN,   RCM_AHB1_PERIPH_GPIOD,   BIT_RESET   },
  20.     {GPIOD,     GPIO_PIN_10,   GPIO_MODE_IN,   RCM_AHB1_PERIPH_GPIOD,   BIT_RESET   }
  21. };

头文件定义各个IO的枚举,是为了能做到见名知义,因本次代码使用的是开发板,所以直接枚举命名为对应的GPIO名称,而实际的产品开发中,更加建议直接按照IO的引脚功能进行命名,或者按照电路板的接口丝印进行命名。例如,PA0在产品中,被用于开门检测,同时在电路板中的接口丝印为CN1,此时可以将枚举命名为INPUT_DOOR_DETECT或者INPUT_CN1。以电路板接口丝印进行命名,更多的是把同一块电路板当作是不同产品的通用电路板进行使用,因为不同的产品对同一个接口的用法不同,但接口名称都是一致的。
  1. /* 电平状态 */
  2. enum level_status_e {
  3.     INVALID_LEVEL,          /* 无效电平 */
  4.     VALID_LEVEL             /* 有效电平 */
  5. };

  6. enum output_pin_e {
  7.     OUTPUT_PF0,
  8.     OUTPUT_PF1,
  9.     OUTPUT_PF3,
  10.     OUTPUT_PF4,
  11.    
  12.     OUTPUT_NUM
  13. };

  14. enum input_pin_e {
  15.     INPUT_PD7,
  16.     INPUT_PD8,
  17.     INPUT_PD9,
  18.     INPUT_PD10,
  19.    
  20.     INPUT_NUM
  21. };

使用上面的结构体数组后,GPIO的初始化就能代码很精简,降低冗余度。
  1. /*
  2. * @brief       引脚通用初始化
  3. *
  4. * @param       gpio_info: GPIO信息
  5. *              gpio_num: GPIO数量
  6. *
  7. * @retval      None
  8. *
  9. */
  10. static void bsp_gpio_init_common(gpio_info_t *gpio_info, uint8_t gpio_num)
  11. {
  12.         GPIO_Config_T gpioConfig;
  13.     uint8_t i = 0;
  14.    
  15.     for (i = 0; i < gpio_num; i++) {
  16.         RCM_EnableAHB1PeriphClock(gpio_info[i].AHB1Periph);
  17.         GPIO_ConfigStructInit(&gpioConfig);
  18.         gpioConfig.pin     = gpio_info[i].pin;
  19.         gpioConfig.mode    = gpio_info[i].mode;
  20.         gpioConfig.otype   = GPIO_OTYPE_PP;
  21.         gpioConfig.speed   = GPIO_SPEED_50MHz;
  22.         gpioConfig.pupd    = GPIO_PUPD_NOPULL;
  23.         GPIO_Config(gpio_info[i].port, &gpioConfig);
  24.     }
  25. }

  26. /*
  27. * @brief       引脚初始化
  28. *
  29. * @param       None
  30. *
  31. * @retval      None
  32. *
  33. */
  34. void bsp_gpio_init(void)
  35. {
  36.     bsp_gpio_init_common(output_gpio_info, sizeof(output_gpio_info) / sizeof(output_gpio_info[0]));
  37.     bsp_gpio_init_common(input_gpio_info, sizeof(input_gpio_info) / sizeof(input_gpio_info[0]));
  38. }

有了上面的框架后,应用层在调用底层的控制GPIO接口函数时,都将变为传入对应的引脚,然后控制输出有效电平或者无效电平,而至于此引脚在电路中的有效电平是高电平还是低电平,应用层并不需要关心,底层接口函数根据前面的GPIO信息表执行转换即可。
  1. /*
  2. * @brief       引脚输出
  3. *
  4. * @param       pin: 输出引脚号
  5. *              level: 输出电平
  6. *
  7. * @retval      None
  8. *
  9. */
  10. void bsp_gpio_output(enum output_pin_e pin, enum level_status_e level)
  11. {
  12.     uint8_t output = BIT_RESET;
  13.    
  14.     if (pin < OUTPUT_NUM) {
  15.         if (level == VALID_LEVEL) {
  16.             output = output_gpio_info[pin].valid_level;
  17.         } else {
  18.             output = (output_gpio_info[pin].valid_level == BIT_RESET) ? BIT_SET : BIT_RESET;
  19.         }
  20.         GPIO_WriteBitValue(output_gpio_info[pin].port, output_gpio_info[pin].pin, output);
  21.     }
  22. }

GPIO的翻转控制,无需关心电平有效或者无效,只管翻转输出即可。
  1. /*
  2. * @brief       引脚翻转
  3. *
  4. * @param       pin: 翻转引脚号
  5. *
  6. * @retval      None
  7. *
  8. */
  9. void bsp_gpio_toggle(enum output_pin_e pin)
  10. {   
  11.     if (pin < OUTPUT_NUM) {
  12.         (GPIO_ReadOutputBit(output_gpio_info[pin].port, output_gpio_info[pin].pin) == BIT_SET) ? \
  13.                 GPIO_ResetBit(output_gpio_info[pin].port, output_gpio_info[pin].pin) : \
  14.                 GPIO_SetBit(output_gpio_info[pin].port, output_gpio_info[pin].pin);
  15.     }
  16. }

GPIO的输入检测,检测到实际输入电平,再与GPIO的信息表有效电平进行比对,转换为有效电平或者无效电平反馈给应用层即可。
  1. /*
  2. * @brief       引脚输入
  3. *
  4. * @param       pin: 输入引脚号
  5. *
  6. * @retval      输入电平
  7. *
  8. */
  9. enum level_status_e bsp_gpio_input(enum input_pin_e pin)
  10. {   
  11.     enum level_status_e level = INVALID_LEVEL;
  12.    
  13.     if (pin < INPUT_NUM) {
  14.         level = (GPIO_ReadInputBit(input_gpio_info[pin].port, input_gpio_info[pin].pin) == input_gpio_info[pin].valid_level) ? \
  15.                 VALID_LEVEL : \
  16.                 INVALID_LEVEL;
  17.     }
  18.    
  19.     return level;
  20. }

这种GPIO封装的编程思想,让应用层能更好的与底层进行隔离,不受底层的代码更改影响,同时也无需知道电路板的硬件细节,只管以见名知义的方式使用底层代码即可。

4. 测试
在输出测试中,PF0PF1的有效电平为低电平,PF2PF3的有效电平为高电平。让4个引脚同时输出有效电平或者无效电平,因为PF0PF1的有效电平一致,PF2PF3的有效电平也一致,因此PF0PF1同时为低电平时,PF2PF3同时为高电平,正好相反。
  1.     /* 输出引脚 */
  2.     output_level = (output_level == INVALID_LEVEL) ? VALID_LEVEL : INVALID_LEVEL;
  3.     for (out_pin = OUTPUT_PF0; out_pin < OUTPUT_NUM; out_pin++) {
  4. //        bsp_gpio_output(out_pin, output_pins[out_pin]);
  5. //        bsp_gpio_toggle(out_pin);
  6.         bsp_gpio_output(out_pin, output_level);
  7.     }
846426917de2a98b45.png

在输入测试中,PD7PD8的有效电平为高电平,PD9PD10的有效电平为低电平。让4个引脚同时输入低电平或者高电平,因为PD7PD8的有效电平一致,PD9PD10的有效电平也一致,因此PD7PD8同时为有效电平时,PD9PD10同时为无效电平,正好相反。
  1.     /* 输入引脚 */
  2.     for (in_pin = INPUT_PD7; in_pin < INPUT_NUM; in_pin++) {
  3.         input_pins[in_pin] = bsp_gpio_input(in_pin);
  4.     }
151296917de42cccd2.png 926876917de48f2e22.png

5. 详细代码
IO_CTRL.zip (6.57 MB, 下载次数: 0)





ShadowDance 发表于 2025-11-16 17:46 | 显示全部楼层
楼主好厉害啊!
这个F427不是才发布不久吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

66

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部
0