立创·天空星开发板-GD32F407VE-GPIO

本文以 立创·天空星开发板-GD32F407VET6-青春版 作为学习的板子,记录学习笔记。

基础概念

  • GPIO,全称为“通用输入/输出”(General Purpose Input/Output),是计算机系统中用于与外部世界进行数字通信的一种接口标准。
  • 它允许硬件和软件通过电信号来交换数据,控制外部设备或接收外部事件。
  • GPIO通常用于连接各种外设,如按钮、LED灯、传感器、马达、继电器等,以便与计算机系统进行交互。GD32 支持的 GPIO 模式有如下八种:
模式 性质
浮空输入 数字输入
上拉输入 数字输入
下拉输入 数字输入
模拟输入 模拟输入
开漏输出 数字输出
推挽输出 数字输出
复用开漏输出 数字输出
复用推挽输出 数字输出

三极管

总是记混 NPN 和 PNP 这两种型号的三极管,如下图所示:

三极管原理图对比
特性描述:

  1. 电流关系: I E = I B + I C I_E = I_B + I_C IE=IB+IC
  2. 导通条件: NPN型的基极比发射极电压高0.7v,PNP型的基极比发射极电压低0.7v
  3. 设计原理图: 无论是 NPN 还是 NPN 型的三极管,耗电元器件都需要接在集电极

助记小技巧:

  1. 电路图中,箭头永远指向的 N 极,根据箭头可快速确认是 NPN 还是 PNP
  2. 电路图中,箭头对应的极比箭尾对应的极的电压要低

思维导图:

三极管思维导图

MOS管

MOS管有 NMOS 和 PMOS 两种类型。MOS管包含了三个极:

  • 栅极(G),对应英文单词:Gate
  • 漏极(D),对应英文单词:Drain
  • 源极(S),对应英文单词:Source

MOS管的作用就是开关,通过栅极控制漏极和源极的导通。主要关注两个点:

  1. 控制:负责MOS管导通和截止,高电平导通还是低电平导通。
  2. 流向:是从漏极流向源极,还是从源极流向漏极。

MOS管和三级管主要区别:三极管导通有电流,而MOS管导通没有电流(有点像继电器)

一张图搞懂 MOS 管,如下所示:
一张图搞懂 MOS 管

如果 Input 为电平,PMOS 断开,NMOS 导通,如果 GPIO_PIN_X 有上拉电阻,则电流可以顺利从 NMOS 的漏极(D)流向源极(S)。
如果 Input 为电平,PMOS 导通,NMOS 断开,如果 GPIO_PIN_X 有下拉电阻,则电流可以顺利从 PMOS 的源极(S)流向漏极(D)。

特点描述:

  • PMOS 的栅极(G)低通高断,导通时,电流方向是源极(S)流向漏极(D)
  • PMOS 可以类比 PNP 类型的三极管
  • NMOS 的栅极(G)高通低断,导通时,电流方向是漏极(D)流向源极(S)
  • NMOS 可以类比 NPN 类型的三极管

思维导图:

MOS管思维导图

GPIO输出模式

  1. 推挽输出
  • 【推】寄存器控制输出高电平时,过非门后变低电平,PMOS导通,外部引脚为高,电流流出

输出高电平

  • 【挽】寄存器控制输出低电平时,过非门后变高电平,NMOS导通,外部引脚为低,电流流入
    输出低电平
  1. 开漏输出
  • 寄存器控制输出低电平时,过非门后变高电平,NMOS导通,外部引脚为低,电流流入

输出低电平

  • 寄存器控制输出高电平时,因为PMOS未接入,所以外部引脚断开。

无法输出高电平

  1. 高阻态
  • 因为 PMOS和 NMOS 均未接入,无论寄存器输出高或者低,外部引脚始终断开。

高阻态

  1. 复用开漏输出 和 复用推挽输出
  • 不经过寄存器来输出高低电平,也就是下图中的 Alternate function output 部分

在这里插入图片描述

输出线与

  1. 推挽线与

推挽过程中,如果一方输出高,一方输出低,则会烧芯片。因此,推挽是不可以线与的。

推挽线与
2. 开漏线与

开漏过程中,无论双方输出高低电平,芯片都不会收到影响。I2C就是线与的一个实例。
开漏线与

GPIO输入模式

  1. 浮空输入
  • 就是将模拟信号、上拉、下拉全部断开,只接收外部电路的输入信号。如下图红色线条所示:
    浮空输入
  1. 上拉输入
  • 过上拉电阻后,经由斯密特触发器,写入寄存器。如下图红色线条所示:
  • 过斯密特后,也可做复用输入,不写入寄存器
    上拉输入
  1. 下拉输入
  • 过下拉电阻后,经由斯密特触发器,写入寄存器。如下图红色线条所示:
  • 过斯密特后,可做复用输入,不写入寄存器
    下拉输入
  1. 模拟输入
  • 不经过斯密特触发器,直接读入。如下图红色线条所示:

模拟输入

GPIO点灯

我用 GPIO 封装了一个可以动态点亮多个 LED 的拓展驱动,针对 PD 端口, 可以动态增加 LED 灯的引脚。只需要修改 LED_PINS 数组的元素即可。

  • ExtendedLED.h
#ifndef __EXTENDED_LED_H__
#define __EXTENDED_LED_H__

#include "gd32f4xx.h"
#include "systick.h"


#define LED_RCU  RCU_GPIOD
#define LED_PORT GPIOD
#ifndef BIT
#define BIT(x) ((uint16_t)((uint16_t)0x01U<<(x)))
#endif

// 声明需要针对的引脚, PDx(x=8...15)
static uint32_t LED_PINS[] = {
	BIT(8),  BIT(9),  BIT(10), BIT(11),
	BIT(12), BIT(13), BIT(14), BIT(15)
};


/*!
    \brief    初始化 LED
    \param[in]  none
    \param[out] none
    \retval     none
*/
void extended_led_init();

/*!
    \brief    熄灭所有 LED 灯
    \param[in]  none
    \param[out] none
    \retval     none
*/
void extended_led_turn_off_all();

/*!
    \brief    点亮所有 LED 灯
    \param[in]  none
    \param[out] none
    \retval     none
*/
void extended_led_turn_on_all();

/*!
    \brief    熄灭 LED 灯
    \param[in]  index[int]: LED 灯在 LED_PINS 数组中的索引, -1 针对所有引脚
    \param[out] none
    \retval     none
*/
void extended_led_turn_off(int index);

/*!
    \brief    点亮 LED 灯
    \param[in]  index[int]: LED 灯在 LED_PINS 数组中的索引, -1 针对所有引脚
    \param[out] none
    \retval     none
*/
void extended_led_turn_on(int index);

/*!
    \brief    运行多个共阳 LED 灯
	示例: extended_led_run(0,  1, 1); 效果为:从第一个灯开始依次亮起, 切灯过程不熄灭其他灯[流水灯]
	示例: extended_led_run(0,  1, 0); 效果为:从第一个灯开始依次亮起, 切灯过程会熄灭其他灯[跑马灯]
	示例: extended_led_run(0,  2, 1); 效果为:按 0.2.4.6 顺序依次亮起, 切灯过程不熄灭其他灯[流水灯]
	示例: extended_led_run(1,  2, 0); 效果为:按 1.3.5.7 顺序依次亮起, 切灯过程会熄灭其他灯[跑马灯]
	示例: extended_led_run(6, -2, 1); 效果为:按 6.4.2.0 顺序依次亮起, 切灯过程不熄灭其他灯[流水灯]
	示例: extended_led_run(7, -2, 0); 效果为:按 7.5.3.1 顺序依次亮起, 切灯过程会熄灭其他灯[跑马灯]
    \param[in]  status  [uint16_t]:     表示灯的状态, 可选值有 (亮:0) 或 (灭:1)
    \param[in]  start   [uint32_t]:     表示从哪个灯开始亮, 可选值有 0...
    \param[in]  step    [int16_t] :     表示切灯的时候跨越步长, 可选值有 0...
    \param[in]  flowing [uint16_t]:     表示切灯的时候是否采用流水灯
    \param[out] none
    \retval     none
*/
void extended_led_run(uint16_t status, uint32_t start, int16_t step, uint16_t flowing);

#endif
  • ExtendedLED.c
#include "ExtendedLED.h"

// 获取 LED 灯的总数
static uint16_t pins_length(void) {
	return sizeof(LED_PINS) / sizeof(LED_PINS[0]);
}

// 获取所有引脚
static uint16_t pins_all(void) {
	int length = 0, i = 0;
	length = pins_length();
	uint16_t pin = (uint16_t)0x00U;
	
	for(i = 0; i < length; i++) {
		pin |= LED_PINS[i];
	}
	return pin;
}

/*!
    \brief    初始化 LED
    \param[in]  none
    \param[out] none
    \retval     none
*/
void extended_led_init() {
    rcu_periph_clock_enable(LED_RCU);
    gpio_mode_set(LED_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, pins_all());
    gpio_output_options_set(LED_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_2MHZ, pins_all());
	gpio_bit_set(GPIOD, pins_all());
}

/*!
    \brief    熄灭所有 LED 灯
    \param[in]  none
    \param[out] none
    \retval     none
*/
void extended_led_turn_off_all() { extended_led_turn_off(-1); }

/*!
    \brief    点亮所有 LED 灯
    \param[in]  none
    \param[out] none
    \retval     none
*/
void extended_led_turn_on_all() { extended_led_turn_on(-1); }

/*!
    \brief    熄灭 LED 灯
    \param[in]  index[int]: LED 灯在 LED_PINS 数组中的索引, -1 针对所有引脚
    \param[out] none
    \retval     none
*/
void extended_led_turn_off(int index) {
	int length = pins_length();
	if (index >= length) {
		return;
	}
	if (index < 0) {
		gpio_bit_set(GPIOD, pins_all());   // 负数针对所有
		return;
	}
	gpio_bit_set(GPIOD, LED_PINS[index]);
}

/*!
    \brief    点亮 LED 灯
    \param[in]  index[int]: LED 灯在 LED_PINS 数组中的索引, -1 针对所有引脚
    \param[out] none
    \retval     none
*/
void extended_led_turn_on(int index) {
	int length = pins_length();
	if (index >= length) {
		return;
	}
	if (index < 0) {
		gpio_bit_reset(GPIOD, pins_all());   // 负数针对所有
		return;
	}
	gpio_bit_reset(GPIOD, LED_PINS[index]);

}

/*!
    \brief    运行多个共阳 LED 灯
	示例: extended_led_run(0,  1, 1); 效果为:从第一个灯开始依次亮起, 切灯过程不熄灭其他灯[流水灯]
	示例: extended_led_run(0,  1, 0); 效果为:从第一个灯开始依次亮起, 切灯过程会熄灭其他灯[跑马灯]
	示例: extended_led_run(0,  2, 1); 效果为:按 0.2.4.6 顺序依次亮起, 切灯过程不熄灭其他灯[流水灯]
	示例: extended_led_run(1,  2, 0); 效果为:按 1.3.5.7 顺序依次亮起, 切灯过程会熄灭其他灯[跑马灯]
	示例: extended_led_run(6, -2, 1); 效果为:按 6.4.2.0 顺序依次亮起, 切灯过程不熄灭其他灯[流水灯]
	示例: extended_led_run(7, -2, 0); 效果为:按 7.5.3.1 顺序依次亮起, 切灯过程会熄灭其他灯[跑马灯]
    \param[in]  status  [uint16_t]:     表示灯的状态, 可选值有 (亮:0) 或 (灭:1)
    \param[in]  start   [uint32_t]:     表示从哪个灯开始亮, 可选值有 0...
    \param[in]  step    [int16_t] :     表示切灯的时候跨越步长, 可选值有 0...
    \param[in]  flowing [uint16_t]:     表示切灯的时候是否采用流水灯
    \param[out] none
    \retval     none
*/
void extended_led_run(uint16_t status, uint32_t start, int16_t step, uint16_t flowing) {
	int length = pins_length();
	int i = 0;
	if(start < 0 || start >= length) {
		start = 0;
	}
	if(start + step  >= length) {
		start = length - 1 - (step - 1);
	}
	
	for(i = start; i < length && i > -1; i += step) {
		if(status) {
			// 熄灭指定索引的灯
			extended_led_turn_off(i);
		} else {
			// 点亮指定索引的灯
			if(!flowing) {
				// 如果不是流水灯, 每次切灯之前都需要关掉所有灯
				extended_led_turn_off(-1);
				delay_1ms(200);
			}
			extended_led_turn_on(i);
		}
		delay_1ms(200);
	}
}

  • main.c
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>

#include "ExtendedLED.h"

int main(void) {
    systick_config();
    
	extended_led_init();
	extended_led_run(0, 0,  1, 1);
	extended_led_run(1, 7, -1, 1);
	extended_led_run(0, 0,  1, 0);
	extended_led_turn_off_all();
	
    while(1) { }
}

相关推荐

  1. gd32F470配置CAN通信

    2024-06-09 10:12:03       37 阅读
  2. gd32F470配置RTC时钟

    2024-06-09 10:12:03       32 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-09 10:12:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-09 10:12:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-09 10:12:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-09 10:12:03       20 阅读

热门阅读

  1. TS的高级类型

    2024-06-09 10:12:03       8 阅读
  2. kafka是什么?

    2024-06-09 10:12:03       10 阅读
  3. Docker概念速通

    2024-06-09 10:12:03       7 阅读
  4. RuoyiAdmin项目搭建及Docker 部署备忘

    2024-06-09 10:12:03       13 阅读
  5. 学习分享-注册中心Naocs的优雅上下线

    2024-06-09 10:12:03       9 阅读
  6. Redisson 源码分析 —— 调试环境搭建

    2024-06-09 10:12:03       9 阅读