RP2040 C SDK GPIO和中断功能使用
📑RP2040 中断功能简介
Each core is equipped with a standard ARM Nested Vectored Interrupt Controller (NVIC) which has 32 interrupt inputs.
Each NVIC has the same interrupts routed to it, with the exception of the GPIO interrupts: there is one GPIO interrupt per bank, per core. These are completely independent, so e.g. core 0 can be interrupted by GPIO 0 in bank 0, and core 1 by GPIO 1 in the same bank.RP2040
由于是双核,每个核心都配备了一个标准的ARM嵌套的向量中断控制器(NVIC),它有32个中断输入。每个NVIC都有相同的中断路由到它,除了GPIO中断:每个库,每个核心都有一个GPIO中断。这些都是完全独立的,例如,核心0可以被bank0中的GPIO 0中断,而核心1可以被同一银行中的GPIO 1中断。
On RP2040, only the lower 26 IRQ signals are connected on the NVIC, and IRQs 26 to 31 are tied to zero (never firing).
The core can still be forced to enter the relevant interrupt handler by writing bits 26 to 31 in the NVIC ISPR register.
- 中断号:
📗GPIO功能
Pads
Each GPIO is connected to the off-chip world via a “pad”. Pads are the electrical interface between the chip’s internal
logic and external circuitry. They translate signal voltage levels, support higher currents and offer some protection
against electrostatic discharge (ESD) events. Pad electrical behaviour can be adjusted to meet the requirements of the
external circuitry. The following adjustments are available:
• Output drive strength can be set to 2mA, 4mA, 8mA or 12mA
• Output slew rate can be set to slow or fast
• Input hysteresis (schmitt trigger mode) can be enabled
• A pull-up or pull-down can be enabled, to set the output signal level when the output driver is disabled
• The input buffer can be disabled, to reduce current consumption when the pad is unused, unconnected or
connected to an analogue signal.
- 📜 GPIO引脚功能配置,枚举类型:
enum gpio_function {GPIO_FUNC_XIP = 0,GPIO_FUNC_SPI = 1,GPIO_FUNC_UART = 2,GPIO_FUNC_I2C = 3,GPIO_FUNC_PWM = 4,GPIO_FUNC_SIO = 5,GPIO_FUNC_PIO0 = 6,GPIO_FUNC_PIO1 = 7,GPIO_FUNC_GPCK = 8,GPIO_FUNC_USB = 9,GPIO_FUNC_NULL = 0x1f,
};
作为外部中断使用,gpio引脚功能配置
GPIO_FUNC_SIO
。
- 🌿gpio功能配置
void gpio_set_function( uint gpio,enum gpio_function fn )
- 🌿gpio输入输出方式,可以配置为输入模式和输出模式。
static inline void gpio_set_dir(uint gpio, bool out)
- 🌿gpio状态,可以配置为上拉、下拉,上下拉都使能。
void gpio_set_pulls(uint gpio, bool up, bool down);
- 🌿gpio输出模式下,可以配置速度:慢和快。电平变化的斜率(压摆率)
void gpio_set_slew_rate ( uint gpio,enum gpio_slew_rate slew )
enum gpio_slew_rate {GPIO_SLEW_RATE_SLOW = 0, ///< Slew rate limiting enabledGPIO_SLEW_RATE_FAST = 1 ///< Slew rate limiting disabled
};
- 🌿gpio对外驱动能力,可配置驱动电流大小:
void gpio_set_drive_strength(uint gpio, enum gpio_drive_strength drive)
/*! \brief Drive strength levels for GPIO outputs* \ingroup hardware_gpio** Drive strength levels for GPIO outputs.* \sa gpio_set_drive_strength*/
enum gpio_drive_strength {GPIO_DRIVE_STRENGTH_2MA = 0, ///< 2 mA nominal drive strengthGPIO_DRIVE_STRENGTH_4MA = 1, ///< 4 mA nominal drive strengthGPIO_DRIVE_STRENGTH_8MA = 2, ///< 8 mA nominal drive strengthGPIO_DRIVE_STRENGTH_12MA = 3 ///< 12 mA nominal drive strength
};
blink点灯程序
#include "pico/stdlib.h"int main() {
#ifndef PICO_DEFAULT_LED_PIN
#warning blink example requires a board with a regular LED
#elseconst uint LED_PIN = PICO_DEFAULT_LED_PIN;gpio_init(LED_PIN);//gpio_set_dir(LED_PIN, GPIO_OUT);while (true) {gpio_put(LED_PIN, 1);sleep_ms(250);gpio_put(LED_PIN, 0);sleep_ms(250);}
#endif
}
- 🌿gpio初始化:
void gpio_init(uint gpio)
void gpio_init(uint gpio) {gpio_set_dir(gpio, GPIO_IN);//输入模式gpio_put(gpio, 0);//设置为低电平gpio_set_function(gpio, GPIO_FUNC_SIO);//SIO模式
}
- 多个gpio初始化操作:
void gpio_init_mask(uint gpio_mask)
- gpio位操作函数:
static inline void gpio_put(uint gpio, bool value);
- 多个gpio位操作
static inline void gpio_put_masked(uint32_t mask, uint32_t value)
gpio_set_mask(1ul << gpio);//置位
sio_hw->gpio_set = 1ul << gpio;//原子操作
gpio_clr_mask(1ul << gpio);//清零
sio_hw->gpio_clr = 1ul << gpio;//原子操作
gpio_xor_mask(1ul << BUILTIN_LED); // Toggle the LED
sio_hw->gpio_togl = 1ul << gpio;//状态翻转
从上面的函数可以看出,SDK给出了不同封装层的gpio操作方式。
📘GPIO 中断
GPIO中断函数介绍
void gpio_set_irq_enabled_with_callback(uint gpio, uint32_t events, bool enabled, gpio_irq_callback_t callback)
- 第一个形参,引脚号
- 第二个形参事件可以是设定为下面的一种或多种信号作为触发事件:
enum gpio_irq_level {GPIO_IRQ_LEVEL_LOW = 0x1u,GPIO_IRQ_LEVEL_HIGH = 0x2u,GPIO_IRQ_EDGE_FALL = 0x4u,GPIO_IRQ_EDGE_RISE = 0x8u,
};
- 形参三,是irq使能:
irq_set_enabled(IO_IRQ_BANK0, true);
- 形参四,是要执行的回调函数,✨需要注意:该回调函数可以是默认带2个形参的
typedef void (*gpio_irq_callback_t)(uint gpio, uint32_t event_mask);
这2个形参是gpio中断发生时,传递过来的返回值
,记录了gpio中断发生的引脚和触发响应事件,如果传递过来的形参,用不到的话,自己写也可以不带任何形参作为回调函数填进去。形参不可以是void
类型,如果是void
类型那么就相当于带了一个形参,无法通过编译语法✨
👉该gpio中断配置函数的好处就是,在发生gpio中断事件后,不需要手动再去清除中断事件标志位。类似的函数还有 gpio_set_irq_callback
;另外该gpio中断配置函数的好处就是,会自动使能gpio中断配置函数 irq_set_enabled(IO_IRQ_BANK0, true);
(具体看函数方法实现)
static inline void gpio_add_raw_irq_handler(uint gpio, irq_handler_t handler)
:gpio中断回调,在发生gpio中断事件后,需要手动清除标志事件。
- 形参一,配置gpio引脚
- 形参二,回调函数,
typedef void (*irq_handler_t)(void);
✨需要注意形参是void
类型,形参可以是void
类型或者无形参。
✨另外需要注意的是,使用该函数,在产生gpio中断事件时,需要手动清gpio中断事件标志位,void gpio_acknowledge_irq(uint gpio, uint32_t events)
;在配置时,还需要手动使能gpio中断,才能响应gpio中断。手动使能gpio中断: irq_set_enabled(IO_IRQ_BANK0, true);
- 📝SDK给出的例程:
/*** Copyright (c) 2020 Raspberry Pi (Trading) Ltd.** SPDX-License-Identifier: BSD-3-Clause*/#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"static char event_str[128];void gpio_event_string(char *buf, uint32_t events);void gpio_callback(uint gpio, uint32_t events) {//带2个形参// Put the GPIO event(s) that just happened into event_str// so we can print itgpio_event_string(event_str, events);printf("GPIO %d %s\n", gpio, event_str);
}int main() {stdio_init_all();printf("Hello GPIO IRQ\n");gpio_set_irq_enabled_with_callback(2, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_callback);// Wait foreverwhile (1);
}static const char *gpio_irq_str[] = {"LEVEL_LOW", // 0x1"LEVEL_HIGH", // 0x2"EDGE_FALL", // 0x4"EDGE_RISE" // 0x8
};void gpio_event_string(char *buf, uint32_t events) {for (uint i = 0; i < 4; i++) {uint mask = (1 << i);if (events & mask) {// Copy this event string into the user stringconst char *event_str = gpio_irq_str[i];while (*event_str != '\0') {*buf++ = *event_str++;}events &= ~mask;// If more events add ", "if (events) {*buf++ = ',';*buf++ = ' ';}}}*buf++ = '\0';
}
🛠需要手动清除gpio中断标志位配置使用方法
gpio_init(EXT_INT_PIN);// gpio_set_dir(EXT_INT_PIN, GPIO_IN);// sio_hw->gpio_oe_set = mask;gpio_set_input_enabled(EXT_INT_PIN, true);// 单独中断gpio_set_irq_enabled(EXT_INT_PIN, GPIO_IRQ_EDGE_RISE, true);gpio_add_raw_irq_handler(EXT_INT_PIN, my_irq_handler); irq_set_enabled(IO_IRQ_BANK0, true); // 使能中断控制器......void my_irq_handler()
{if (gpio_get_irq_event_mask(EXT_INT_PIN) & GPIO_IRQ_EDGE_RISE|GPIO_IRQ_EDGE_FALL){gpio_acknowledge_irq(EXT_INT_PIN, GPIO_IRQ_EDGE_RISE|GPIO_IRQ_EDGE_FALL); //clear irq flag// gpio_xor_mask(1<<LED_PIN); // Toggle the LEDprintf("GPIO %d\n", EXT_INT_PIN);}}
📒GPIO中断清标志位自动配置使用
gpio_set_irq_enabled_with_callback(EXT_INT_PIN, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &my_irq_handler); // 复用中断gpio_set_irq_enabled_with_callback(EXT_INT_PIN2, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &my_irq_handler);//gpio_set_irq_callback(&my_irq_handler);
......void my_irq_handler()
{if (gpio_get_irq_event_mask(EXT_INT_PIN) & GPIO_IRQ_EDGE_RISE|GPIO_IRQ_EDGE_FALL){// gpio_xor_mask(1<<LED_PIN); // Toggle the LEDprintf("GPIO %d\n", EXT_INT_PIN);// gpio_clear_irq(EXT_INT_PIN);}if (gpio_get_irq_event_mask(EXT_INT_PIN2) & GPIO_IRQ_EDGE_RISE|GPIO_IRQ_EDGE_FALL){// gpio_xor_mask(1<<LED_PIN); // Toggle the LEDprintf("GPIO %d \n", EXT_INT_PIN2);}
}
- 👉一般情况下推荐使用
gpio_set_irq_enabled_with_callback
来配置需要的gpio中断,比较省事。在SDK给出的多种API接口函数,需要熟悉各功能函数的使用差异以及注意事项。
📗GPIO 唤醒功能使用
在芯片进入睡眠模式(DORMANT State
)下,gpio可以用作唤醒。
/*CMSIS-DAP烧录命令:openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg -c "adapter speed 5000"-c "program RP2040_Deep_Sleep_Wake.elf verify reset exit"jlink命令: openocd -f interface/jlink.cfg -f target/rp2040.cfg -c "adapter speed 2000" -c "program RP2040_Deep_Sleep_Wake.elf verify reset exit"*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "hardware/gpio.h"
#include "hardware/xosc.h"
#include "pico/multicore.h"
#include "pico/stdio.h"
#include "pico/time.h"#define LED_PIN 25
#define EXT_INT_PIN 5static void measure_freqs(void);void disable_pll() {clock_configure(clk_sys,CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF,0,12 * MHZ,12 * MHZ);
}void enable_pll() {clock_configure(clk_sys,CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLKSRC_CLK_SYS_AUX,CLOCKS_CLK_SYS_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,125 * MHZ,125 * MHZ);
}int main() {// stdio_init_all();gpio_init(PICO_DEFAULT_LED_PIN);gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);gpio_put(PICO_DEFAULT_LED_PIN, true);gpio_init(EXT_INT_PIN);gpio_set_dir(EXT_INT_PIN, GPIO_IN);// sio_hw->gpio_oe_set = mask;gpio_set_pulls(EXT_INT_PIN, false, true); // 下拉gpio_set_dormant_irq_enabled(EXT_INT_PIN, IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS, true);disable_pll();xosc_dormant(); // WARNING: This stops the xosc until woken up by an irq
// gpio_acknowledge_irq(EXT_INT_PIN, IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS);enable_pll();stdio_init_all();while (true) {measure_freqs();gpio_xor_mask(1ul << PICO_DEFAULT_LED_PIN); // Toggle the LEDsleep_ms(1000);// for (uint32_t i=0; i<3; i++) {// gpio_put(PICO_DEFAULT_LED_PIN, false);// sleep_ms(100);// gpio_put(PICO_DEFAULT_LED_PIN, true);// sleep_ms(100);// }}
}static void measure_freqs(void)
{uint f_pll_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_SYS_CLKSRC_PRIMARY);uint f_pll_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_PLL_USB_CLKSRC_PRIMARY);uint f_rosc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC);uint f_clk_sys = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_SYS);uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI);uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB);uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC);uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC);printf("pll_sys = %dkHz\n", f_pll_sys);printf("pll_usb = %dkHz\n", f_pll_usb);printf("rosc = %dkHz\n", f_rosc);printf("clk_sys = %dkHz\n", f_clk_sys);printf("clk_peri = %dkHz\n", f_clk_peri);printf("clk_usb = %dkHz\n", f_clk_usb);printf("clk_adc = %dkHz\n", f_clk_adc);printf("clk_rtc = %dkHz\n", f_clk_rtc);// Can't measure clk_ref / xosc as it is the ref
}