織女星開發板是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配置方法稍有不一樣,使用時要特別注意。