织女星开发板是OPEN-ISA社区为中国大陆地区定制的一款体积小、功耗超低和功能丰富的 RISC-V评估开发板,基于NXP半导体四核异构RV32M1主控芯片。html
4个核被分为两个子系统,大核CM4F/RI5CY和小核CM0+/ZERO-RISCY,片上集成1.25 MB Flash 、384 KB SRAM,其中1 MB的Flash被大核所使用,起始地址0x0000_0000,另外的256 KB Flash被小核所使用,起始地址0x0100_0000。利用该开发板,用户能够快速创建一个使用 RV32M1 的 RISC-V应用和演示系统。详细的介绍能够参考: 真正的RISC-V开发板——VEGA织女星开发板开箱评测 ,本篇文章介绍如何基于RISC-V RI5CY/ZERO内核来点亮板载的RGB_LED/STS_LED、读取按键输入,演示GPIO的输入输出和外部中断功能。git
在进行如下操做以前,要确保开发环境已经搭建完成,并且能正常下载调试。github
以上资料的获取、开发环境搭建和启动模式修改等教程,能够到官方中文论坛查找:www.open-isa.cn
api
或者是参考我分享的如下文章:ide
根据RV32M1参考手册GPIO章节的介绍,咱们能够得到关于GPIO相关寄存器信息:函数
各GPIO组的基地址:工具
GPIOA——4802_0000h GPIOB——4802_0040h GPIOC——4802_0080h GPIOD——4802_00C0h GPIOE——4100_F000h
这是一个32位的寄存器,每个引脚都有对应的一个PORTx_PCRn,用来配置GPIO的如下功能:ui
以PA0控制寄存器,PORTA_PCR0为例:调试
经过查看参考手册,能够了解到各Bit的功能:code
详细的配置介绍能够查看参考手册。官方库fsl_port中的
PORT_SetPinConfig(PORT_Type *base, uint32_t pin, const port_pin_config_t *config) PORT_SetPinMux(PORT_Type *base, uint32_t pin, port_mux_t mux) PORT_SetPinInterruptConfig(PORT_Type *base, uint32_t pin, port_interrupt_t config) PORT_SetPinDriveStrength(PORT_Type* base, uint32_t pin, uint8_t strength)
这些函数就是控制的这个PCR寄存器。
主要包括控制GPIO输入输出控制,读取输入,控制输出,方向控制等。
寄存器描述和地址偏移量:
RV32M1的GPIO共有6个32位的控制寄存器,从字面意思能够直接知道每一个寄存器的功能:
官方库中的fsl_gpio文件中实现的函数就是控制的这几个寄存器。
void GPIO_PinInit(GPIO_Type *base, uint32_t pin, const gpio_pin_config_t *config) void GPIO_WritePinOutput(GPIO_Type *base, uint32_t pin, uint8_t output) void GPIO_SetPinsOutput(GPIO_Type *base, uint32_t mask) void GPIO_ClearPinsOutput(GPIO_Type *base, uint32_t mask) void GPIO_TogglePinsOutput(GPIO_Type *base, uint32_t mask)
和其余的MCU同样,因为RV32M1的寄存器众多,为了方便使用,加强程序的可读性,官方开发了库函数,来实现对寄存器的控制,本质上仍是操做的寄存器。GPIO控制的库主要由fsl_gpio和fsl_port两个文件组成,其中fsl_gpio主要是对GPIO的控制,如读取输入,控制输出,清除中断标志等,而fsl_port主要实现对GPIO工做的模式进行配置,如复用功能,上拉下拉,开漏推挽,中断触发方式,DMA功能等进行设置。
下面简单介绍几个经常使用的函数:
配置GPIO的复用功能,驱动能力,推挽开漏,上下拉,滤波器,翻转速率等功能,基于PCR寄存器实现。
port_pin_config_t config; config.driveStrength = kPORT_HighDriveStrength; //驱动能力配置 config.mux = kPORT_MuxAsGpio; //通用GPIO config.openDrainEnable = kPORT_OpenDrainDisable; //推挽 config.passiveFilterEnable = kPORT_PassiveFilterDisable;//滤波器 config.pullSelect = kPORT_PullUp; //上拉 config.slewRate = kPORT_FastSlewRate; //翻转速率 PORT_SetPinConfig(PORTA, 22, &config); //配置GPIOA22
配置GPIO的复用功能,基于PCR寄存器实现。
//PA22做为普通GPIO使用 PORT_SetPinMux(PORTA, 22, kPORT_MuxAsGpio); //PA25做为UART1_RX功能 PORT_SetPinMux(PORTA, 25, kPORT_MuxAlt2);
具体复用为哪一种功能,不一样的引脚有不一样的复用功能,对应的ALTn,能够查看参考手册RV32M1 Pinout介绍。
PORT_SetPinConfig已经包含了PORT_SetPinMux的功能,能够只使用PORT_SetPinConfig来GPIO功能的配置。PORT_SetPinMux函数不推荐和PORT_SetPinsConfig函数一块儿使用:
This function is NOT recommended to use together with the PORT_SetPinsConfig, because the PORT_SetPinsConfig need to configure the pin mux anyway (Otherwise the pin mux is reset to zero : kPORT_PinDisabledOrAnalog). This function is recommended to use to reset the pin mux
控制GPIO的输入输出方式,及默认输出电平,基于PDDR、PCOR、PSOR寄存器实现。
gpio_pin_config_t io_init; //配置输出/输出模式 io_init.outputLogic = 0; //默认输出0 io_init.pinDirection = kGPIO_DigitalOutput; //数字输出 GPIO_PinInit(LED_RGB_GPIO, LED_RED_Pin, &io_init); //LED引脚配置
指定引脚输出高低电平,基于PCOR和PSOR寄存器实现。
GPIO_WritePinOutput(GPIOA, 22, 1); //PA22输出1
指定引脚输出翻转,基于PTOR寄存器实现
GPIO_TogglePinsOutput(GPIOA, 1 << 22); //PA22输出翻转
读取GPIO输入状态,基于PDIR寄存器实现
in = GPIO_ReadPinInput(GPIOA, 22); //读取PA22输入状态
GPIO操做的函数还有不少,详细的介绍和实现能够直接查看库函数源码。
从原理图中咱们能够得知,织女星开发板上共有4个用户可控制的LED,包括3个RGB LED和1个红色LED,均采用MOS来驱动,引脚输出高电平LED点亮,和GPIO的对应关系以下:
LED_RED——PTA24
LED_GREEN——PTA23
LED_BLUE——PTA22
LED_STS——PTE0
因此咱们须要配置PTA22/PTA23/PTA24为普通推挽输出方式,而后输出高低电平就能够控制LED闪烁了。
#include "led_driver.h" void LED_RGB_Init(void) { gpio_pin_config_t io_init; port_pin_config_t config; //配置输出/输出模式 io_init.outputLogic = 0; io_init.pinDirection = kGPIO_DigitalOutput; config.driveStrength = kPORT_HighDriveStrength; //驱动能力 config.lockRegister = kPORT_LockRegister; //PCR寄存器被锁定,不能再次改变 config.mux = kPORT_MuxAsGpio; //通用GPIO config.openDrainEnable = kPORT_OpenDrainDisable; //推挽输出 config.passiveFilterEnable = kPORT_PassiveFilterDisable;//滤波器 config.pullSelect = kPORT_PullUp; //上拉 config.slewRate = kPORT_FastSlewRate; //翻转速率 CLOCK_EnableClock(LED_RGB_Clk_Name); CLOCK_EnableClock(LED_STS_Clk_Name); //GPIOE时钟必须一直开启 CLOCK_EnableClock(kCLOCK_Rgpio1); //GPIOE配置须要使能这个时钟 /*如下两个函数均可以配置端口功能*/ PORT_SetPinConfig(LED_RGB_Port, LED_RED_Pin, &config); //配置功能更详细 PORT_SetPinConfig(LED_RGB_Port, LED_GREEN_Pin, &config); PORT_SetPinConfig(LED_RGB_Port, LED_BLUE_Pin, &config); PORT_SetPinConfig(LED_STS_Port, LED_STS_Pin, &config); // PORT_SetPinMux(LED_RGB_Port, LED_RED_Pin, kPORT_MuxAsGpio); //只能配置是否复用 // PORT_SetPinMux(LED_RGB_Port, LED_GREEN_Pin, kPORT_MuxAsGpio); // PORT_SetPinMux(LED_RGB_Port, LED_BLUE_Pin, kPORT_MuxAsGpio); // CLOCK_DisableClock(LED_RGB_Clk_Name); //能够在配置完成以后关闭时钟,不影响使用 GPIO_PinInit(LED_RGB_GPIO, LED_RED_Pin, &io_init); GPIO_PinInit(LED_RGB_GPIO, LED_GREEN_Pin, &io_init); GPIO_PinInit(LED_RGB_GPIO, LED_BLUE_Pin, &io_init); GPIO_PinInit(LED_STS_GPIO, LED_STS_Pin, &io_init); }
要注意的是,时钟使能要放在GPIO配置以前,不然不能访问GPIO配置寄存器,在配置完成以后能够关闭时钟,也能够一直开启。其中GPIOE很是特殊,要想使用GPIOE,必须使能Rgpio1快速时钟,其余的GPIO配置不须要,这是由于GPIOE属于快速GPIO,和其余几组GPIO不是同一个总线。
CLOCK_EnableClock(kCLOCK_Rgpio1); //GPIOE配置须要使能这个时钟
#ifndef __LED_DRIVER_H__ #define __LED_DRIVER_H__ #include "fsl_gpio.h" #include "fsl_port.h" #include "fsl_clock.h" /* LED_RGB_BLUE - A22 LED_RGB_GREEN - A23 LED_RGB_RED - A24 LED_STS - E0 */ #define LED_RED_Pin 24 #define LED_GREEN_Pin 23 #define LED_BLUE_Pin 22 #define LED_RGB_Port PORTA #define LED_RGB_GPIO GPIOA #define LED_RGB_Clk_Name kCLOCK_PortA #define LED_STS_Pin 0 #define LED_STS_Port PORTE #define LED_STS_GPIO GPIOE #define LED_STS_Clk_Name kCLOCK_PortE #define LED_STS_ON GPIO_WritePinOutput(LED_STS_GPIO, LED_STS_Pin, 1) #define LED_STS_OFF GPIO_WritePinOutput(LED_STS_GPIO, LED_STS_Pin, 0) #define LED_STS_TOGGLE GPIO_TogglePinsOutput(LED_STS_GPIO, 1 << LED_STS_Pin) #define LED_RED_ON GPIO_WritePinOutput(LED_RGB_GPIO, LED_RED_Pin, 1) #define LED_RED_OFF GPIO_WritePinOutput(LED_RGB_GPIO, LED_RED_Pin, 0) #define LED_RED_TOGGLE GPIO_TogglePinsOutput(LED_RGB_GPIO, 1 << LED_RED_Pin) #define LED_GREEN_ON GPIO_WritePinOutput(LED_RGB_GPIO, LED_GREEN_Pin, 1) #define LED_GREEN_OFF GPIO_WritePinOutput(LED_RGB_GPIO, LED_GREEN_Pin, 0) #define LED_GREEN_TOGGLE GPIO_TogglePinsOutput(LED_RGB_GPIO, 1 << LED_GREEN_Pin) #define LED_BLUE_ON GPIO_WritePinOutput(LED_RGB_GPIO, LED_BLUE_Pin, 1) #define LED_BLUE_OFF GPIO_WritePinOutput(LED_RGB_GPIO, LED_BLUE_Pin, 0) #define LED_BLUE_TOGGLE GPIO_TogglePinsOutput(LED_RGB_GPIO, 1 << LED_BLUE_Pin) void LED_RGB_Init(void); #endif
头文件中经过宏定义的方式实现了LED的亮灭和翻转控制。
按键部分硬件原理图,按下为低电平。
#include "button_driver.h" #include "delay.h" #include "led_driver.h" //按键使用普通输入GPIO方式 void Button_Init(void) { gpio_pin_config_t io_init; port_pin_config_t config; io_init.outputLogic = 0; io_init.pinDirection = kGPIO_DigitalInput; config.mux = kPORT_MuxAsGpio; //通用GPIO config.lockRegister = kPORT_LockRegister; //PCR寄存器被锁定,不能再次改变 config.pullSelect = kPORT_PullUp; //上拉 config.slewRate = kPORT_FastSlewRate; //翻转速率 config.lockRegister = kPORT_LockRegister; //PCR寄存器被锁定,不能再次改变 config.passiveFilterEnable = kPORT_PassiveFilterEnable; //滤波器 CLOCK_EnableClock(BTN_SW2_Clk_Name); CLOCK_EnableClock(BTN_SW3_Clk_Name); // CLOCK_EnableClock(BTN_SW4_Clk_Name); // CLOCK_EnableClock(BTN_SW5_Clk_Name); CLOCK_EnableClock(kCLOCK_Rgpio1); //GPIOE配置须要使能这个时钟 //如下两个函数功能同样 PORT_SetPinConfig(BTN_SW2_Port, BTN_SW2_Pin, &config); PORT_SetPinConfig(BTN_SW3_Port, BTN_SW3_Pin, &config); PORT_SetPinConfig(BTN_SW4_Port, BTN_SW4_Pin, &config); PORT_SetPinConfig(BTN_SW5_Port, BTN_SW5_Pin, &config); // PORT_SetPinMux(BTN_SW2_Port, BTN_SW2_Pin, kPORT_MuxAsGpio); //设置IO模式为通用GPIO // PORT_SetPinMux(BTN_SW3_Port, BTN_SW3_Pin, kPORT_MuxAsGpio); //设置IO模式为通用GPIO // PORT_SetPinMux(BTN_SW4_Port, BTN_SW4_Pin, kPORT_MuxAsGpio); //设置IO模式为通用GPIO // PORT_SetPinMux(BTN_SW5_Port, BTN_SW5_Pin, kPORT_MuxAsGpio); //设置IO模式为通用GPIO GPIO_PinInit(BTN_SW2_GPIO, BTN_SW2_Pin, &io_init); GPIO_PinInit(BTN_SW3_GPIO, BTN_SW3_Pin, &io_init); GPIO_PinInit(BTN_SW4_GPIO, BTN_SW4_Pin, &io_init); GPIO_PinInit(BTN_SW5_GPIO, BTN_SW5_Pin, &io_init); } //按键使用外部中断初始化函数 void ButtonInterruptInit(void) { gpio_pin_config_t io_init; port_pin_config_t config; io_init.outputLogic = 0; io_init.pinDirection = kGPIO_DigitalInput; config.mux = kPORT_MuxAsGpio; //通用GPIO config.lockRegister = kPORT_LockRegister; //PCR寄存器被锁定,不能再次改变 config.pullSelect = kPORT_PullUp; //上拉 config.slewRate = kPORT_FastSlewRate; //翻转速率 config.lockRegister = kPORT_LockRegister; //PCR寄存器被锁定,不能再次改变 config.passiveFilterEnable = kPORT_PassiveFilterEnable; //滤波器 CLOCK_EnableClock(BTN_SW2_Clk_Name); CLOCK_EnableClock(BTN_SW3_Clk_Name); // CLOCK_EnableClock(BTN_SW4_Clk_Name); // CLOCK_EnableClock(BTN_SW5_Clk_Name); CLOCK_EnableClock(kCLOCK_Rgpio1); //GPIOE配置须要使能这个时钟 //如下两个函数功能同样 PORT_SetPinConfig(BTN_SW2_Port, BTN_SW2_Pin, &config); PORT_SetPinConfig(BTN_SW3_Port, BTN_SW3_Pin, &config); PORT_SetPinConfig(BTN_SW4_Port, BTN_SW4_Pin, &config); PORT_SetPinConfig(BTN_SW5_Port, BTN_SW5_Pin, &config); //设置中断触发方式 PORT_SetPinInterruptConfig(BTN_SW2_Port, BTN_SW2_Pin, kPORT_InterruptFallingEdge); //降低沿触发中断 PORT_SetPinInterruptConfig(BTN_SW3_Port, BTN_SW3_Pin, kPORT_InterruptFallingEdge); PORT_SetPinInterruptConfig(BTN_SW4_Port, BTN_SW4_Pin, kPORT_InterruptFallingEdge); PORT_SetPinInterruptConfig(BTN_SW5_Port, BTN_SW5_Pin, kPORT_InterruptFallingEdge); #if defined(CPU_RV32M1_ri5cy) //RI5CY Core GPIOE须要使能如下两个函数, ZERO Core不用 INTMUX_Init(INTMUX0); INTMUX_EnableInterrupt(INTMUX0, 0, PORTE_IRQn); #endif EnableIRQ(BTN_SW2_IRQ); EnableIRQ(BTN_SW3_IRQ); // EnableIRQ(BTN_SW4_IRQ); // EnableIRQ(BTN_SW5_IRQ); GPIO_PinInit(BTN_SW2_GPIO, BTN_SW2_Pin, &io_init); GPIO_PinInit(BTN_SW3_GPIO, BTN_SW3_Pin, &io_init); GPIO_PinInit(BTN_SW4_GPIO, BTN_SW4_Pin, &io_init); GPIO_PinInit(BTN_SW5_GPIO, BTN_SW5_Pin, &io_init); } void PORTA_IRQHandler(void) { GPIO_ClearPinsInterruptFlags(BTN_SW2_GPIO, 1U << BTN_SW2_Pin); LED_STS_TOGGLE; LOG("sw2 is pressed \r\n"); } //GPIOE外部中断函数 void PORTE_IRQHandler(void) { uint32_t flag; flag = GPIO_GetPinsInterruptFlags(BTN_SW3_GPIO); GPIO_ClearPinsInterruptFlags(BTN_SW3_GPIO, 1U << BTN_SW3_Pin); GPIO_ClearPinsInterruptFlags(BTN_SW4_GPIO, 1U << BTN_SW4_Pin); GPIO_ClearPinsInterruptFlags(BTN_SW5_GPIO, 1U << BTN_SW5_Pin); if(flag & (1 << BTN_SW3_Pin)) //SW3产生中断 { LED_RED_TOGGLE; LOG("sw3 is pressed \r\n"); } else if(flag & (1 << BTN_SW4_Pin)) { LED_GREEN_TOGGLE; LOG("sw4 is pressed \r\n"); } else if(flag & (1 << BTN_SW5_Pin)) { LED_BLUE_TOGGLE; LOG("sw5 is pressed \r\n"); } } //轮询方式获取按键状态 uint8_t GetKey(void) { uint8_t key = 1; //按键按下为0 if(BTN_SW2_IN && BTN_SW3_IN && BTN_SW4_IN && BTN_SW5_IN) { Delay_ms(10); if(!BTN_SW2_IN) key = 2; else if(!BTN_SW3_IN) key = 3; else if(!BTN_SW4_IN) key = 4; else if(!BTN_SW5_IN) key = 5; while(!(BTN_SW2_IN && BTN_SW3_IN && BTN_SW4_IN && BTN_SW5_IN)); } return key; }
按键配置为上拉输入模式,一样若是使用GPIOE做为通用GPIO输入,还须要使能Rgpio1时钟:
CLOCK_EnableClock(kCLOCK_Rgpio1); //GPIOE配置须要使能这个时钟
若是使用GPIOE的外部中断功能,还须要使能INTMUX:
#if defined(CPU_RV32M1_ri5cy) //RI5CY Core GPIOE须要使能如下两个函数, ZERO Core不用 INTMUX_Init(INTMUX0); INTMUX_EnableInterrupt(INTMUX0, 0, PORTE_IRQn); #endif
#ifndef __BUTTON_DRIVER_H__ #define __BUTTON_DRIVER_H__ #include "fsl_gpio.h" #include "fsl_port.h" #include "fsl_intmux.h" /* * SW2 - A0 * SW3 - E12 * SW4 - E8 * SW5 - E9 * */ //按下为低电平 #define BTN_SW2_GPIO GPIOA #define BTN_SW3_GPIO GPIOE #define BTN_SW4_GPIO GPIOE #define BTN_SW5_GPIO GPIOE #define BTN_SW2_Pin 0 #define BTN_SW3_Pin 12 #define BTN_SW4_Pin 8 #define BTN_SW5_Pin 9 #define BTN_SW2_Port PORTA #define BTN_SW3_Port PORTE #define BTN_SW4_Port PORTE #define BTN_SW5_Port PORTE #define BTN_SW2_IRQ PORTA_IRQn #define BTN_SW3_IRQ PORTE_IRQn #define BTN_SW4_IRQ PORTE_IRQn #define BTN_SW5_IRQ PORTE_IRQn #define BTN_SW2_Clk_Name kCLOCK_PortA #define BTN_SW3_Clk_Name kCLOCK_PortE #define BTN_SW4_Clk_Name kCLOCK_PortE #define BTN_SW5_Clk_Name kCLOCK_PortE #define BTN_SW2_IN GPIO_ReadPinInput(BTN_SW2_GPIO, BTN_SW2_Pin) #define BTN_SW3_IN GPIO_ReadPinInput(BTN_SW3_GPIO, BTN_SW3_Pin) #define BTN_SW4_IN GPIO_ReadPinInput(BTN_SW4_GPIO, BTN_SW4_Pin) #define BTN_SW5_IN GPIO_ReadPinInput(BTN_SW5_GPIO, BTN_SW5_Pin) /* #define BTN_SW2_IN ReadGPIO(BTN_SW2_GPIO, BTN_SW2_Pin) #define BTN_SW3_IN ReadGPIO(BTN_SW3_GPIO, BTN_SW3_Pin) #define BTN_SW4_IN ReadGPIO(BTN_SW4_GPIO, BTN_SW4_Pin) #define BTN_SW5_IN ReadGPIO(BTN_SW5_GPIO, BTN_SW5_Pin) */ void Button_Init(void); uint8_t GetKey(void); void ButtonInterruptInit(void); #endif
经过GPIO读取函数来获取按键输入状态,或者是经过中断标志来判断输入状态。
使用外部中断方式读取按键输入状态。
#include "main.h" extern uint32_t SystemCoreClock; int main(void) { BOARD_BootClockRUN(); //ϵͳʱ֓Ťփ UART0_Init(); Delay_Init(); LOG("SystemCoreClock: %ld \r\n", SystemCoreClock); #if defined(CPU_RV32M1_ri5cy) LOG("RV32M1 RISC-V RI5CY Core Demo \r\n"); #elif defined(CPU_RV32M1_zero_riscy) LOG("RV32M1 RISC-V ZERO Core Demo \r\n"); #endif LED_RGB_Init(); // Button_Init(); ButtonInterruptInit(); // LPMTR2_Init(); // LPIT1_CH3_Init(); while (1) { } }
织女星开发板VEGA_Lite支持从4个核启动,因此在进行程序下载以前,要确认当前的启动模式和当前的工程是对应的。如当前工程是使用RISC-V RI5CY核来驱动GPIO,那么就须要配置芯片启动模式为RI5CY核启动。不然会不能下载。关于启动模式的修改能够参考:织女星开发板启动模式修改
RV32M1芯片的GPIOE与其余几组GPIO配置方法稍有不一样,使用时要特别注意。