【STM32&RT-Thread零基础入门】 3. PIN设备(GPIO)的使用

硬件:STM32F103ZET6、ST-LINK、usb转串口工具、4个LED灯、1个蜂鸣器、4个1k电阻、2个按键、面包板、杜邦线

文章目录


前言

在嵌入式系统中,GPIO是最常用的一种设备,在RT-Thread操作系统中,把GPIO命名为PIN设备。


一、PIN设备介绍

RT-Thread通过PIN设备对芯片的GPIO引脚进行管理,应用程序可以通过其提供的一组PIN设备管理接口来操作GPIO,PIN设备管理接口如表所示

接口 描述
rt_pin_get() 获取引脚编号
rt_pin_mode() 设置引脚模式
rt_pin_write() 设置引脚电平
rt_pin_read() 读取引脚电平
rt_pin_attach_irq() 绑定引脚中断回调函数
rt_pin_irq_enable() 使能引脚中断
rt_pin_detach_irq() 脱离引脚中断回调函数

1. 引脚编号获取

引脚变化和芯片的引脚号不是同一概念。RT-Thread的PIN设备驱动程序把芯片的不同引脚赋予不同的编号,操作PIN设备时,需要使用引脚编号来指定对哪个引脚进行操作。

可通过三种方法获得引脚编号:

(1)API法

利用rt_pin_get() 函数来获取引脚编号,例如获取PD9的引脚编号,则可以使用
pin_number = rt_pin_get("PD.9")

(2)宏定义法

对于STM32芯片,可以使用GET_PIN(PORTx,PIN)获取引脚编号,如硬件的PD9用于驱动LED_R_PIN,则可以把宏LED_R_PIN定义为相应的引脚编号,如下:
#define LED_R_PIN GET_PIN(D, 9)

(3)查看驱动文件

在drivers/drv_gpio.c中,pin\[\]数组定义了硬件平台的GPIO引脚编号,通过查看该数组,得到引脚PD9的编号为57。

具体数组代码如下:

c 复制代码
static const struct pin_index pins[] = 
{
#if defined(GPIOA)
   __STM32_PIN(0 ,  A, 0 ),
   __STM32_PIN(1 ,  A, 1 ),
   __STM32_PIN(2 ,  A, 2 ),
   __STM32_PIN(3 ,  A, 3 ),
   __STM32_PIN(4 ,  A, 4 ),
   __STM32_PIN(5 ,  A, 5 ),
   __STM32_PIN(6 ,  A, 6 ),
   __STM32_PIN(7 ,  A, 7 ),
   __STM32_PIN(8 ,  A, 8 ),
   __STM32_PIN(9 ,  A, 9 ),
   __STM32_PIN(10,  A, 10),
   __STM32_PIN(11,  A, 11),
   __STM32_PIN(12,  A, 12),
   __STM32_PIN(13,  A, 13),
   __STM32_PIN(14,  A, 14),
   __STM32_PIN(15,  A, 15),
#if defined(GPIOB)
   __STM32_PIN(16,  B, 0),
   __STM32_PIN(17,  B, 1),
   __STM32_PIN(18,  B, 2),
   __STM32_PIN(19,  B, 3),
   __STM32_PIN(20,  B, 4),
   __STM32_PIN(21,  B, 5),
   __STM32_PIN(22,  B, 6),
   __STM32_PIN(23,  B, 7),
   __STM32_PIN(24,  B, 8),
   __STM32_PIN(25,  B, 9),
   __STM32_PIN(26,  B, 10),
   __STM32_PIN(27,  B, 11),
   __STM32_PIN(28,  B, 12),
   __STM32_PIN(29,  B, 13),
   __STM32_PIN(30,  B, 14),
   __STM32_PIN(31,  B, 15),
#if defined(GPIOC)
   __STM32_PIN(32,  C, 0),
   __STM32_PIN(33,  C, 1),
   __STM32_PIN(34,  C, 2),
   __STM32_PIN(35,  C, 3),
   __STM32_PIN(36,  C, 4),
   __STM32_PIN(37,  C, 5),
   __STM32_PIN(38,  C, 6),
   __STM32_PIN(39,  C, 7),
   __STM32_PIN(40,  C, 8),
   __STM32_PIN(41,  C, 9),
   __STM32_PIN(42,  C, 10),
   __STM32_PIN(43,  C, 11),
   __STM32_PIN(44,  C, 12),
   __STM32_PIN(45,  C, 13),
   __STM32_PIN(46,  C, 14),
   __STM32_PIN(47,  C, 15),
#if defined(GPIOD)
   __STM32_PIN(48,  D, 0),
   __STM32_PIN(49,  D, 1),
   __STM32_PIN(50,  D, 2),
   __STM32_PIN(51,  D, 3),
   __STM32_PIN(52,  D, 4),
   __STM32_PIN(53,  D, 5),
   __STM32_PIN(54,  D, 6),
   __STM32_PIN(55,  D, 7),
   __STM32_PIN(56,  D, 8),
   __STM32_PIN(57,  D, 9),
   __STM32_PIN(58,  D, 10),
   __STM32_PIN(59,  D, 11),
   __STM32_PIN(60,  D, 12),
   __STM32_PIN(61,  D, 13),
   __STM32_PIN(62,  D, 14),
   __STM32_PIN(63,  D, 15),

2. 设置引脚的输入/输出模式

宏定义 描述
PIN_MODE_OUTPUT 推挽输出
PIN_MODE_OUTPUT_OD 开漏输出,硬件需要外接上拉电阻
PIN_MODE_INPUT 输入
PIN_MODE_INPUT_PULLUP 上拉输入,引脚悬空时为高电平
PIN_MODE_INPUT_PULLDOWN 下拉输入,引脚悬空时为低电平
引脚可通过以下函数设置输入/输出模式:

void rt_pin_mode(rt_base_t pin, rt_base_t mode)

pin:引脚编号

mode:输入/输出模式所对应的宏

c 复制代码
#define LED_R_PIN GET_PIN(D, 9)
rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT);

3. 设置引脚的电平值

设置函数如下:
void rt_pin_write(rt_base_t pin, rt_base_t value)

pin:引脚编号

value:高电平为PIN_HIGH;低电平为PIN_LOW

4. 读取引脚的电平值

设置函数如下:
int rt_pin_read(rt_base_t pin)

pin:引脚编号

返回值:高电平为PIN_HIGH;低电平为PIN_LOW

5. 绑定引脚中断回调函数

引脚作为中断输入时,需要设置引脚的中断触发方式,引脚的中断触发模式有5种,RT-Thread中分别有5个宏与之对应,具体如下:

宏定义 描述
PIN_IQR_MODE_RISING 上升沿触发
PIN_IQR_MODE_FALLING 下降沿触发
PIN_IQR_MODE_RISING_FALLING 双边沿触发
PIN_IQR_MODE_HIGH_LEVEL 高电平触发
PIN_IQR_MODE_LOW_LEVEL 低电平触发

当引脚产生中断时,通过中断回调函数响应中断。绑定中断回调函数到引脚后,引脚有中断发生,就会执行对应的中断回调函数。

具体函数如下:
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args)

pin:引脚编号

mode:中断触发模式

hdr:中断回调函数指针,用户需要自定义这个函数

args:中断回调函数的参数,不需要设置时为RT_NULL

返回:绑定成功为RT_EOK;失败产生错误码

6. 脱离引脚中断回调函数

如不希望响应中断,或者想更换中断响应函数,则可以使用如下脱离引脚中断回调函数:
rt_err_t rt_pin_detach_irq(rt_int32_t pin)

pin:引脚编号

返回:脱离成功为RT_EOK;失败产生错误码

7. 使能中断

rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled)

pin:引脚编号

enable:PIN_IRQ_ENABLE(开启),PIN_IRQ_DISABLE(关闭)

返回:使能成功为RT_EOK;失败产生错误码

二、任务1:LED灯双闪控制

1. 任务描述

控制LED1、LED2轮流闪烁。通过本任务,学习PIN设备输出功能的设置方法。

2. 代码编写

在项目的main.c文件中,编写如下代码:

c 复制代码
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"
 
/* 定义左右转向灯的控制引脚 */
#define LED_L_PIN GET_PIN(D, 8)
#define LED_R_PIN GET_PIN(D, 9)

int main(void)
{
    /* 把引脚设置为推拉输出模式 */
    rt_pin_mode(LED_L_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT);

    while (1){
        rt_pin_write(LED_L_PIN, PIN_HIGH);//灭左灯
        rt_pin_write(LED_R_PIN, PIN_LOW);//亮右灯
        rt_thread_mdelay(500);//延迟500毫秒
        rt_pin_write(LED_L_PIN, PIN_LOW);//亮左灯
        rt_pin_write(LED_R_PIN, PIN_HIGH);//灭右灯
        rt_thread_mdelay(500);
    }

    return RT_EOK;
}

三、任务2:蜂鸣器控制(查询法)

1. 任务描述

通过按键控制蜂鸣器的开关。当按键按下时,蜂鸣器响起;当按键松开时,蜂鸣器关闭。通过本任务,学习PIN设备输入功能的使用。

2. 代码编写

c 复制代码
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"

/* 定义左右转向灯的控制引脚 */
#define BEEP_PIN GET_PIN(A, 5)
#define KEY1_PIN GET_PIN(A, 0)

int main(void)
{
    /* 把蜂鸣器引脚设置为推拉模式 */
    rt_pin_mode(BEEP_PIN, PIN_MODE_OUTPUT);
    /* 把按键引脚设置为上拉输入模式 */
    rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP);

    while (1)
    {
        if(PIN_LOW==rt_pin_read(KEY1_PIN)){//按键按下
            rt_thread_mdelay(20);//延时去抖
            if(PIN_LOW==rt_pin_read(KEY1_PIN))
                rt_pin_write(BEEP_PIN, PIN_LOW);//蜂鸣器响
        }
        else
            rt_pin_write(BEEP_PIN, PIN_HIGH);//否则,蜂鸣器不响

        rt_thread_mdelay(300);//每0.3秒进行一次按键扫描
    }

    return RT_EOK;
}

3. 测试

按下按键,蜂鸣器发声,松开按键,停止发声,但是经过多次尝试发现,按键检测成功率较低,所以需要利用中断进行改进。

四、任务3:蜂鸣器控制(中断回调法)

1. 任务描述

通过按键控制蜂鸣器的开关。当按键按下时,蜂鸣器响起;当按键松开时,蜂鸣器关闭。通过本任务,掌握PIN设备中断回调函数的使用方法。

2. 代码编写

c 复制代码
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"

#define BEEP_PIN GET_PIN(A, 5)  //定义蜂鸣器的控制引脚
#define KEY1_PIN GET_PIN(A, 0)  //定义按键的控制引脚

/* 定义中断回调函数实现 */
void beep_on(void *args)
{
    /* 判断按键是否按下 */
    if(PIN_LOW==rt_pin_read(KEY1_PIN))
    {
        /* 按键按下,驱动蜂鸣器响  */
        rt_pin_write(BEEP_PIN, PIN_LOW);
        /* 等待按键台起  */
        while(PIN_LOW==rt_pin_read(KEY1_PIN));
        /* 关闭蜂鸣器  */
        rt_pin_write(BEEP_PIN, PIN_HIGH);
    }
}
int main(void)
{
    rt_pin_mode(BEEP_PIN, PIN_MODE_OUTPUT); //把蜂鸣器引脚设置为推拉模式
    rt_pin_write(BEEP_PIN, PIN_HIGH); //初始化蜂鸣器默认状态为不响

    /* 把按键引脚设置为上拉输入模式 */
    rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP);
    /* 绑定中断,下降沿触发模式,回调函数名为beep_on */
    rt_pin_attach_irq(KEY1_PIN, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
    rt_pin_irq_enable(KEY1_PIN, PIN_IRQ_ENABLE); //使能中断

    return RT_EOK;
}

3. 测试

按下按键,蜂鸣器发声,松开按键,停止发声,经过多次尝试发现,按键检测成功率较高,所以中断法比查询法更具优势。

五、任务4:同时实现LED闪烁和按键控制喇叭

1. 任务描述

本任务功能为同时实现LED灯双闪功能和按键控制蜂鸣器的功能,要求两个功能不能相互影响,按键检测灵敏度要高,即每次发生按键按下的事件,程序都能成功检测该事件并开启蜂鸣器。

2. 代码编写

c 复制代码
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

/* 定义左右转向灯的控制引脚 */
#define LED_L_PIN GET_PIN(D, 8)
#define LED_R_PIN GET_PIN(D, 9)
/* 定义蜂鸣器的控制引脚 */
#define BEEP_PIN GET_PIN(A, 5)
/* 定义按键的控制引脚 */
#define KEY1_PIN GET_PIN(A, 0)

/* 定义中断回调函数 */
void beep_on(void *args)
{
    /* 判断按键是否按下 */
    if(PIN_LOW==rt_pin_read(KEY1_PIN))
    {
        rt_pin_write(BEEP_PIN, PIN_LOW); //按按键下,驱动蜂鸣器响 
        while(PIN_LOW==rt_pin_read(KEY1_PIN)); //等待按键台起
        rt_pin_write(BEEP_PIN, PIN_HIGH); //关闭蜂鸣器
    }
}

int main(void)
{
    /* 把LED灯引脚设置为输出模式 */
    rt_pin_mode(LED_L_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(LED_R_PIN, PIN_MODE_OUTPUT);

    /* 把蜂鸣器引脚设置为推拉模式 */
    rt_pin_mode(BEEP_PIN, PIN_MODE_OUTPUT);
    /* 初始化蜂鸣器默认状态为不响 */
    rt_pin_write(BEEP_PIN, PIN_HIGH);

    /* 把按键引脚设置为上拉输入模式 */
    rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP);
    /* 绑定中断,下降沿触发模式,回调函数名为beep_on */
    rt_pin_attach_irq(KEY1_PIN, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
    rt_pin_irq_enable(KEY1_PIN, PIN_IRQ_ENABLE); // 使能中断 

    while(1){
        rt_pin_write(LED_L_PIN, PIN_HIGH);//亮左灯
        rt_pin_write(LED_R_PIN, PIN_LOW);//灭右灯
        rt_thread_mdelay(500);//延迟500毫秒
        rt_pin_write(LED_L_PIN, PIN_LOW);//灭左灯
        rt_pin_write(LED_R_PIN, PIN_HIGH);//亮右灯
        rt_thread_mdelay(500);
    }

    return RT_EOK;
}

3. 测试

测试过程如下:

(1)系统启动后,观察左右转向灯是否轮流闪烁;

(2)当按下按键时,喇叭是否发出响声;

(3)当松开按键时,喇叭是否停止发出响声;

(4)一直按住按键不松开,观察灯的闪烁情况。

测试结果:

(1)系统启动后,左右转向灯轮流闪烁;

(2)当按下按键时,喇叭发出响声;

(3)当松开按键时,喇叭停止发出响声;

(4)一直按住按键不松开,喇叭发出响声,灯停止闪烁。


总结

从任务4测试结果中,我们可以发现,按键功能影响了闪灯的功能,说明两个功能还是没有很好地解耦,依然存在相互影响的情况。

出现这种情况,主要是由于中断回调函数中存在需要长时间等待的代码,当按键一直按住不松开的时候,中断回调函数由于一直停留在等待按键松开的地方而无法退出中断处理。而中断的优先级又高于main()线程的优先级,从而导致main()线程无法得到执行。

通常,我们不应该在中断回调函数中进行长时间的处理,中断回调函数应该只做一些必要的快速处理操作,而把长时间的处理操作放到线程中实现。

关于线程和优先级的概念,我们在下一节讲述。

相关推荐
蓝斯49729 分钟前
从.到实现一个Windows上的虚拟hid键盘设备
windows·单片机·计算机外设
熙芯XiChip1 小时前
硬实时与软实时RTOS核心区别
嵌入式硬件
伟大的前程1 小时前
unsigned int 定义的变量 在 电脑的 64 位系统中 和 32 位 cortex-m0 和 8 位的 51 单片机
单片机·嵌入式硬件
小渔村的拉线工2 小时前
1.HPM6E80 解析芯片整体工作原理和存储架构
单片机·嵌入式硬件·mcu·架构·hpm6e80
熙芯XiChip3 小时前
CAN与CANopen的本质区别
单片机
lilihewo3 小时前
Altium Designer 封装库的创建(分享pcb 3D封装库)
嵌入式硬件·51单片机
神罚天下ET4 小时前
典型arm位单片机启动流程(从上电到main.c)
c语言·arm开发·单片机
碧海银沙音频科技研究院5 小时前
蓝牙耳机使用WSDF5361锂保芯片进入船运模式实现方法
嵌入式硬件·算法
Zevalin爱灰灰5 小时前
模拟信号链电路——基于TL431的电压基准电路
单片机·嵌入式硬件
csdn杰哥6 小时前
瑞萨单片机开发教程[二十七] DTC 外部中断触发传输实验
单片机·嵌入式硬件·mongodb·瑞萨·ra6m5