RP2040 C SDK GPIO和IRQ 唤醒功能使用
RP2040 C SDK GPIO和中断功能使用
SIO介绍
- 手册27页:
The Single-cycle IO block (SIO) contains several peripherals that require low-latency, deterministic access from the processors. It is accessed via each processor’s IOPORT: this is an auxiliary bus port on the Cortex-M0+ which can perform rapid 32-bit reads and writes. The SIO has a dedicated bus interface for each processor’s IOPORT, as shown in Figure 7. Processors access their IOPORT with normal load and store instructions, directed to the special IOPORT address segment, 0xd0000000…0xdfffffff. The SIO appears as memory-mapped hardware within the IOPORT space.
单周期IO块(SIO)包含几个外设,它们需要从处理器中进行低延迟的、确定性的访问。它可以通过每个处理器的IOPORT进行访问:这是Cortex-M0+上的一个辅助总线端口,它可以执行快速的32位读取和写操作。SIO为每个处理器的IOPORT都有一个专用的总线接口,如图7所示。处理器使用正常负载和存储指令访问他们的IOPORT,指向特殊的IOPORT地址段0xd0000000…0xdfffffff。SIO显示为IOPORT空间中的内存映射硬件。 - NOTE:The SIO is not connected to the main system bus due to its tight timing requirements. It can only be accessed by the processors, or by the debugger via the processor debug ports.由于其严格的定时要求,SIO没有连接到主系统总线。它只能由处理器或调试器通过处理器调试端口进行访问。
上图.单周期IO块包含记忆映射硬件,处理器必须能够快速访问。FIFOs和自旋锁支持两个核之间的消息传递和同步。共享的GPIO寄存器提供快速和并发的安全的直接访问。一些核心-本地算术硬件可以用来加速处理器上的常见任务。
- 所有的IOPORT读取和写入(因此所有的SIO访问)都在一个周期内发生,这与主AHB-Lite系统总线不同,其中Cortex-M0+需要两个周期来加载或存储,并且由于来自其他系统总线主服务器的争用,可能需要等待更长的时间。这对于像GPIO这样的接口至关重要,它们有严格的时间要求。
- SIO寄存器被映射到0xd0000000…0xd000017c范围内的单词对齐地址。IOPORT空间的其余部分被保留以供将来使用。
- 下面的节将详细描述SIO外围设备。
GPIO控制
处理器可以访问GPIO寄存器,以快速和直接地控制具有GPIO功能的引脚。有两组完全相同的寄存器:
- • GPIO_x for direct control of IO bank 0 (user GPIOs 0 to 29, starting at the LSB)用于直接控制IO组0(用户GPIO0到29,从LSB开始)
- • GPIO_HI_x for direct control of the QSPI IO bank (in the order SCLK, SSn, SD0, SD1, SD2, SD3, starting at the LSB)GPIO_HI_x,用于直接控制QSPI IO库(按SCLK、SSn、SD0、SD1、SD2、SD3,从LSB开始
- NOTE:To drive a pin with the SIO’s GPIO registers, the GPIO multiplexer for this pin must first be configured to select the SIO GPIO function.要使用SIO的GPIO寄存器驱动引脚,必须首先将此引脚的GPIO复用器配置为选择SIO GPIO功能。
- 这些GPIO寄存器在两个核之间共享,并且两个核可以同时访问它们。每个BANK有三个寄存器:
• Output registers,GPIO_OUT
andGPIO_HI_OUT
, are used to set the output level of the GPIO (1/0 for high/low)
• Output enable registers,GPIO_OE
andGPIO_HI_OE
, are used to enable the output driver. 0 for high-impedance, 1
for drive high/low based onGPIO_OUT
andGPIO_HI_OUT.
• Input registers,GPIO_IN
andGPIO_HI_IN
, allow the processor to sample the current state of the GPIOs
📑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 enabled
GPIO_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 strength
GPIO_DRIVE_STRENGTH_4MA = 1, ///< 4 mA nominal drive strength
GPIO_DRIVE_STRENGTH_8MA = 2, ///< 8 mA nominal drive strength
GPIO_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
#else
const 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 it
gpio_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 forever
while (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 string
const 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 LED
printf("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 LED
printf("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 LED
printf("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 5
static 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 LED
sleep_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
}