参赛队名:0 ERROR 队伍编号:CICC1957
这是本参赛队发表的第五篇帖子
本文介绍嵌入式开发
调试 debug时 不能按 mcu上面的 reset按了之后会从flash将程序下载进去 将 ilm上面的程序清除
在调试时下载程序是从 ilm 上下载进去
volatile是一个特征修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的
MCU是各个寄存器是存储器映射,访问每一个寄存器是访问每一个寄存器的地址来访问 主要访问方法是基地址加偏移地址
嵌入式开发就是将是不断的操作寄存器的地址,然后对寄存器进行赋值 来实现一系列功能
利用所写好的库函数来进行开发
定义了GPIO的结构体 每一个结构体中的变量 uint32_t 32位
MCU 一共有 32个外接引脚 ,可以将每一个结构体中的变量理解为 对应的寄存器 每个寄存器32位对应 32个引脚 配置寄存器 来实现引脚的不同功能
typedef struct { /*!< GPIO Structure */ __IOM uint32_t INPUT_VAL; __IOM uint32_t INPUT_EN; __IOM uint32_t OUTPUT_EN; __IOM uint32_t OUTPUT_VAL; __IOM uint32_t PULLUP_EN; __IOM uint32_t DRIVE; __IOM uint32_t RISE_IE; __IOM uint32_t RISE_IP; __IOM uint32_t FALL_IE; __IOM uint32_t FALL_IP; __IOM uint32_t HIGH_IE; __IOM uint32_t HIGH_IP; __IOM uint32_t LOW_IE; __IOM uint32_t LOW_IP; __IOM uint32_t IOF_EN; __IOM uint32_t IOF_SEL; __IOM uint32_t OUTPUT_XOR; } GPIO_TypeDef;
但是注意 声明的这个结构体不是用来声明其余变量的 而是用来 进行地址 存储的
#define GPIO_BASE (HBIRD_PERIPH_BASE + 0x12000)
#define GPIO ((GPIO_TypeDef *) GPIO_BASE)
定义GPIO为 GPIO_TypeDef 类型的指针
GPIO的地址就是 (HBIRD_PERIPH_BASE + 0x12000)
各个寄存器的地址:由于定义结构体中变量的顺序是按着各个寄存器地址偏移量从小到大的顺序依次排序的,也就是每一个变量的地址就对应着对应寄存器的地址
int32_t gpio_enable_output(GPIO_TypeDef *gpio, uint32_t mask) { if (__RARELY(gpio == NULL)) { return -1; } gpio->OUTPUT_EN |= mask; gpio->INPUT_EN &= ~mask; return 0; }
实际操作中 导入到 函数中的 第一项不是 自己声明的变量 而是GPIO
mask导入的是第几位 若mask是第5位 二进制 10000
OUTPUT_EN | = mask;
mask是掩码 ,1<<15 指的是GPIO的第15位 因为起始位为0 是 引脚的对应值 1<<5 100000 PB5 0001 为pb1
OUTPUT_EN这个寄存器 有1 为1 已经打开的输出使能不变 并且新加上这个1 置1
INPUT_EN这个寄存器 与上01111 即将 第五位清零 后面四位不变 置0
第二种方法
直接对地址进行操作
#define _REG32(p, i) (*(volatile uint32_t *) ((p) + (i)))
#define GPIO_REG(offset) _REG32(GPIO_BASE, offset)
GPIO_REG(GPIO_INPUT_EN)
通过这个函数直接访问GPIO_INPUT_EN这个寄存器的地址
GPIO_INPUT_EN是定义的偏移量
x<< n
操作 将x左移n位
x>>
将x右移n位
第一代板子的缺点 第一代板子烧录MCU之后仅仅只是完成了芯片的引脚与外部引脚的连接,板子上面的外设没与连接到芯片的引脚上 需要另外拉线进行连接,只有三个LED灯是连接上的
注意:1<<15 指的是GPIO的第15位 因为起始位为0
运行GPIO之前需要选择工作模式 GPIO有两个工作模式 IOF与软件工作模式 软件工作模式就是由人工配置寄存器 通常用的方式 IOF工作模式 下寄存器的信号来自于信号线
gpio_iof_config(GPIO_TypeDef *gpio, uint32_t mask, IOF_FUNC func);
IOF_SEL_GPIO = 0,
IOF_SEL_0 = 1,
IOF_SEL_1 = 2
RISC-V中的中断包括软件中断,计时器中断,外部中断,调试中断 (主要是前三个)
计时器中断与软件中断由clint控制 clint局部中断控制器
外部中断由plic模块控制 plic平台级别中断控制器
mie寄存器用于控制计时器中断外部中断软件中断单独的使能
mie寄存器中的MEIE控制外部中断MTIE控制计时器中断 MSIE控制软件中断使能
mip寄存器用于反映中断的标识位 MEIP外部中断的标识位,MTIP计时器中断的标识位,MSIP软件中断的标识位
中断源通过clint与plic 进行仲裁处理选出中断目标与e203核进行交互 进而产生中断
(e203是单核 单线程 hart为1)
外部中断 plic
中断产生需要先打开全局中断
__enable_irq();打开全局中断是CSR中的 mstatus寄存器下(32位)的MIE(只有一位)控制 1 全局中断打开 0全局中断关闭
__enable_ext_irq(); //Enable External IRQ Interrupts 打开外部中断使能 mie寄存器的外部中断域
PLIC的每一个中断源的寄存器是存放在一起的不是分别存储,中断源是以ID来区分 ,每一个中断源有自己的ID,见书74 页,区分于51单片机的中断优先级51的中断优先级是 1与0 相同之后按地址顺序来仲裁
中断方式的选择 中断 标识位看各个外设对应的寄存器
PLIC_EnableInterrupt(source); //PLIC的每一个中断源的外部使能 寄存器?
/** * \brief Register a specific plic interrupt and register the handler * \details * This function set priority and handler for plic interrupt * \param [in] source interrupt source * \param [in] priority interrupt priority * \param [in] handler interrupt handler, if NULL, handler will not be installed * \return -1 means invalid input parameter. 0 means successful. * \remarks * - This function use to configure specific plic interrupt and register its interrupt handler and enable its interrupt. */ __enable_irq(); int32_t PLIC_Register_IRQ(uint32_t source, uint8_t priority, void *handler) { if ((source >= __PLIC_INTNUM)) { return -1; } /* set interrupt priority */ PLIC_SetPriority(source, priority); if (handler != NULL) { /* register interrupt handler entry to external handlers */ Interrupt_Register_ExtIRQ(source, (unsigned long)handler); } /* enable interrupt */ PLIC_EnableInterrupt(source); //PLIC的每一个中断源的外部使能 __enable_ext_irq(); //Enable External IRQ Interrupts 打开外部中断使能 return 0; }
外部中断函数
PLIC_Register_IRQ(uint32_t source, uint8_t priority, void *handler)
source是 中断源 输入为中断的ID 见书74 页 GPIO的中断为 第几个引脚加8 定义如此
priority 为中断优先级 PLIC 可以支持 1024个中断源所以有1024个中断优先级 但是板子只用到了53个中断源 故 中断源 的优先级53 每一个中断源的优先级都对应一个寄存器 PLIC的寄存器只支持操作尺寸SIZE为32的读写访问
外部中断是从外部输入一个信号来产生中单
如从外部产生一个脉冲 当有上升沿是就触发中断
中断的去除由软件进行去除 将中断标识位清零
gpio_clear_interrupt(GPIO, SOC_BUTTON_1_GPIO_OFS, GPIO_INT_RISE);
SOC_BUTTON_1_GPIO_OFS, 选择引脚
GPIO_INT_RISE 选择中断触发类型
GPIO_INT_RISE = 0, 上升沿触发
GPIO_INT_FALL = 1, 下降沿触发
GPIO_INT_HIGH = 2, 高电平触发
GPIO_INT_LOW = 3 低电平触发
handler是中断处理函数
IP是中断等待标识位 当IP为1时表示执行该中断 当IP为0时表示该中断尚未执行 具体看336页
对GPIO的配置
工作方式
gpio_iof_config(GPIO, SOC_LED_RED_GPIO_MASK , IOF_SEL_GPIO );
输入输出使能
gpio_enable_output(GPIO,SOC_LED_RED_GPIO_MASK );
gpio_enable_input(GPIO,SOC_BUTTON_1_GPIO_MASK);
中断触发方式
gpio_enable_interrupt(GPIO,SOC_BUTTON_1_GPIO_MASK,GPIO_INT_RISE);
当软件中断外部中断计时器中断同时发生
外部中断优先级最高
软件中断其次
计时器中断再次
软件中断与计时器中断 clint
软件中断与计时器中断相当于在核内部的中断 中断源的ID与内部的异常在一起编码
/** * \brief Register a riscv core interrupt and register the handler * \details * This function set interrupt handler for core interrupt * \param [in] irqn interrupt number * \param [in] handler interrupt handler, if NULL, handler will not be installed * \return -1 means invalid input parameter. 0 means successful. * \remarks * - This function use to configure riscv core interrupt and register its interrupt handler and enable its interrupt. */ int32_t Core_Register_IRQ(uint32_t irqn, void *handler) { if ((irqn > 10)) { return -1; } if (handler != NULL) { /* register interrupt handler entry to core handlers */ Interrupt_Register_CoreIRQ(irqn, (unsigned long)handler); } switch (irqn) { case SysTimerSW_IRQn: __enable_sw_irq(); break; case SysTimer_IRQn: __enable_timer_irq(); break; default: break; } return 0; }
打开全局中断
__enable_irq();打开全局中断
__enable_sw_irq();打开软件中断
__enable_timer_irq();打开时钟中断
在利用Core_Register_IRQ函数进行产生软件中断和计时器中断之前首先要打开全局中断(该函数中已经将软件中断与计时器中断配置完成)打开全局中断后使用 Core_Register_IRQ函数
Core_Register_IRQ(uint32_t irqn, void *handler);
第一项中断的ID分为两种 SysTimerSW_IRQn: 软件中断 SysTimer_IRQn:时钟中断
第二项为中断处理函数(相当于往这里返回了函数地址 用于执行 )
前两步仅仅只是打开中断但并没有触发中断
计时器触发中断的方式:板子中有两个计时的寄存器一个是 Mtime寄存器 一个是 mtimecmp寄存器 两者都是64位的寄存器 mtime寄存器从上电就开始计数 一直计数到mtimecmp寄存器中的数据就会触发一次中断 中断的消除用软件进行消除
SysTimer_GetLoadValue()得到现在寄存器mtime的值
SysTimer_SetCompareValue()为 mtimecmp设置初值
uint64_t now = SysTimer_GetLoadValue();
uint64_t then = now + 0.5 * SOC_TIMER_FREQ;
SysTimer_SetCompareValue(then);
当计时器计时到mtimecmp寄存器中的值之后产生中断 进行中断处理函数
消除中断__disable_core_irq(SysTimer_IRQn); (看函数名是消除内部中断 SysTimer_IRQn内部中断的ID用计时器中断)
计时器属于常开域 时钟为32.768khz
软件中断的触发
SysTimer_SetSWIRQ(); /* trigger timer sw interrupt */ 将MSIP为置一,软件中断触发执行中断处理函数
SysTimer_ClearSWIRQ(); 清除软件中断标识位 将MSIP置零