- typedef struct {
- GPIO_T *port; /* PIN组 */
- uint16_t pin; /* 引脚 */
- GPIO_MODE_T mode; /* 模式 */
- uint32_t AHB1Periph; /* 时钟使能 */
- uint8_t valid_level; /* 有效电平 */
- } gpio_info_t;
- /* 输出引脚信息 */
- static gpio_info_t output_gpio_info[OUTPUT_NUM] = {
- {GPIOF, GPIO_PIN_0, GPIO_MODE_OUT, RCM_AHB1_PERIPH_GPIOF, BIT_RESET },
- {GPIOF, GPIO_PIN_1, GPIO_MODE_OUT, RCM_AHB1_PERIPH_GPIOF, BIT_RESET },
- {GPIOF, GPIO_PIN_2, GPIO_MODE_OUT, RCM_AHB1_PERIPH_GPIOF, BIT_SET },
- {GPIOF, GPIO_PIN_3, GPIO_MODE_OUT, RCM_AHB1_PERIPH_GPIOF, BIT_SET }
- };
- /* 输入引脚信息 */
- static gpio_info_t input_gpio_info[INPUT_NUM] = {
- {GPIOD, GPIO_PIN_7, GPIO_MODE_IN, RCM_AHB1_PERIPH_GPIOD, BIT_SET },
- {GPIOD, GPIO_PIN_8, GPIO_MODE_IN, RCM_AHB1_PERIPH_GPIOD, BIT_SET },
- {GPIOD, GPIO_PIN_9, GPIO_MODE_IN, RCM_AHB1_PERIPH_GPIOD, BIT_RESET },
- {GPIOD, GPIO_PIN_10, GPIO_MODE_IN, RCM_AHB1_PERIPH_GPIOD, BIT_RESET }
- };
头文件定义各个IO的枚举,是为了能做到见名知义,因本次代码使用的是开发板,所以直接枚举命名为对应的GPIO名称,而实际的产品开发中,更加建议直接按照IO的引脚功能进行命名,或者按照电路板的接口丝印进行命名。例如,PA0在产品中,被用于开门检测,同时在电路板中的接口丝印为CN1,此时可以将枚举命名为INPUT_DOOR_DETECT或者INPUT_CN1。以电路板接口丝印进行命名,更多的是把同一块电路板当作是不同产品的通用电路板进行使用,因为不同的产品对同一个接口的用法不同,但接口名称都是一致的。
- /* 电平状态 */
- enum level_status_e {
- INVALID_LEVEL, /* 无效电平 */
- VALID_LEVEL /* 有效电平 */
- };
- enum output_pin_e {
- OUTPUT_PF0,
- OUTPUT_PF1,
- OUTPUT_PF3,
- OUTPUT_PF4,
-
- OUTPUT_NUM
- };
- enum input_pin_e {
- INPUT_PD7,
- INPUT_PD8,
- INPUT_PD9,
- INPUT_PD10,
-
- INPUT_NUM
- };
使用上面的结构体数组后,GPIO的初始化就能代码很精简,降低冗余度。
- /*
- * @brief 引脚通用初始化
- *
- * @param gpio_info: GPIO信息
- * gpio_num: GPIO数量
- *
- * @retval None
- *
- */
- static void bsp_gpio_init_common(gpio_info_t *gpio_info, uint8_t gpio_num)
- {
- GPIO_Config_T gpioConfig;
- uint8_t i = 0;
-
- for (i = 0; i < gpio_num; i++) {
- RCM_EnableAHB1PeriphClock(gpio_info[i].AHB1Periph);
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = gpio_info[i].pin;
- gpioConfig.mode = gpio_info[i].mode;
- gpioConfig.otype = GPIO_OTYPE_PP;
- gpioConfig.speed = GPIO_SPEED_50MHz;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(gpio_info[i].port, &gpioConfig);
- }
- }
- /*
- * @brief 引脚初始化
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_gpio_init(void)
- {
- bsp_gpio_init_common(output_gpio_info, sizeof(output_gpio_info) / sizeof(output_gpio_info[0]));
- bsp_gpio_init_common(input_gpio_info, sizeof(input_gpio_info) / sizeof(input_gpio_info[0]));
- }
有了上面的框架后,应用层在调用底层的控制GPIO接口函数时,都将变为传入对应的引脚,然后控制输出有效电平或者无效电平,而至于此引脚在电路中的有效电平是高电平还是低电平,应用层并不需要关心,底层接口函数根据前面的GPIO信息表执行转换即可。
- /*
- * @brief 引脚输出
- *
- * @param pin: 输出引脚号
- * level: 输出电平
- *
- * @retval None
- *
- */
- void bsp_gpio_output(enum output_pin_e pin, enum level_status_e level)
- {
- uint8_t output = BIT_RESET;
-
- if (pin < OUTPUT_NUM) {
- if (level == VALID_LEVEL) {
- output = output_gpio_info[pin].valid_level;
- } else {
- output = (output_gpio_info[pin].valid_level == BIT_RESET) ? BIT_SET : BIT_RESET;
- }
- GPIO_WriteBitValue(output_gpio_info[pin].port, output_gpio_info[pin].pin, output);
- }
- }
GPIO的翻转控制,无需关心电平有效或者无效,只管翻转输出即可。
- /*
- * @brief 引脚翻转
- *
- * @param pin: 翻转引脚号
- *
- * @retval None
- *
- */
- void bsp_gpio_toggle(enum output_pin_e pin)
- {
- if (pin < OUTPUT_NUM) {
- (GPIO_ReadOutputBit(output_gpio_info[pin].port, output_gpio_info[pin].pin) == BIT_SET) ? \
- GPIO_ResetBit(output_gpio_info[pin].port, output_gpio_info[pin].pin) : \
- GPIO_SetBit(output_gpio_info[pin].port, output_gpio_info[pin].pin);
- }
- }
GPIO的输入检测,检测到实际输入电平,再与GPIO的信息表有效电平进行比对,转换为有效电平或者无效电平反馈给应用层即可。
- /*
- * @brief 引脚输入
- *
- * @param pin: 输入引脚号
- *
- * @retval 输入电平
- *
- */
- enum level_status_e bsp_gpio_input(enum input_pin_e pin)
- {
- enum level_status_e level = INVALID_LEVEL;
-
- if (pin < INPUT_NUM) {
- level = (GPIO_ReadInputBit(input_gpio_info[pin].port, input_gpio_info[pin].pin) == input_gpio_info[pin].valid_level) ? \
- VALID_LEVEL : \
- INVALID_LEVEL;
- }
-
- return level;
- }
这种GPIO封装的编程思想,让应用层能更好的与底层进行隔离,不受底层的代码更改影响,同时也无需知道电路板的硬件细节,只管以见名知义的方式使用底层代码即可。
4. 测试
在输出测试中,PF0和PF1的有效电平为低电平,PF2和PF3的有效电平为高电平。让4个引脚同时输出有效电平或者无效电平,因为PF0和PF1的有效电平一致,PF2和PF3的有效电平也一致,因此PF0和PF1同时为低电平时,PF2和PF3同时为高电平,正好相反。
- /* 输出引脚 */
- output_level = (output_level == INVALID_LEVEL) ? VALID_LEVEL : INVALID_LEVEL;
- for (out_pin = OUTPUT_PF0; out_pin < OUTPUT_NUM; out_pin++) {
- // bsp_gpio_output(out_pin, output_pins[out_pin]);
- // bsp_gpio_toggle(out_pin);
- bsp_gpio_output(out_pin, output_level);
- }
在输入测试中,PD7和PD8的有效电平为高电平,PD9和PD10的有效电平为低电平。让4个引脚同时输入低电平或者高电平,因为PD7和PD8的有效电平一致,PD9和PD10的有效电平也一致,因此PD7和PD8同时为有效电平时,PD9和PD10同时为无效电平,正好相反。
- /* 输入引脚 */
- for (in_pin = INPUT_PD7; in_pin < INPUT_NUM; in_pin++) {
- input_pins[in_pin] = bsp_gpio_input(in_pin);
- }
5. 详细代码