本文所说的裸机编程是基于“龙芯1c库”的。“龙芯1c库”类似于STM32库,“龙芯1c库”的git地址是https://gitee.com/caogos/OpenLoongsonLib1c
中断对于任何一款CPU来说都是非常重要的,对龙芯1c也是如此。另一篇博文以linux中的中断作为实例,详细分析了龙芯1c的中断。龙芯1c的中断与arm的中断有些不同,如果对龙芯1c的中断还没有概念,请移步到《【龙芯1c库】龙芯1c的中断分析》http://blog.csdn.net/caogos/article/details/69948579
除了linux中有龙芯1c的中断实现以外,RT-Thread中也有,事实上RT-Thread的中断移植是参考了linux的,而本文提到的裸机程序的中断则主要参考了RT-Thread,所以裸机程序的和RT-Thread的中断相关接口类似,用法也类似。RT-Thread的git地址是https://github.com/RT-Thread/rt-thread
常用的中断接口简介
这里只介绍几个对“龙芯1c库”的“用户”(嵌入式软件工程师,单片机工程师)可见的,还有一些具体实现的函数接口在这里就不阐述了,感兴趣的可以移步到git查看。
主要有以下几个函数会
// 初始化异常 void exception_init(void); /* * 使能指定中断 * @IRQn 中断号 */ void irq_enable(int IRQn); /* * 禁止指定中断 * @IRQn 中断号 */ void irq_disable(int IRQn); /* * 设置中断处理函数 * @IRQn 中断号 * @new_handler 新设置的中断处理函数 * @param 传递给中断处理函数的参数 * @ret 之前的中断处理函数 */ irq_handler_t irq_install(int IRQn, irq_handler_t new_handler, void *param);
其中,函数exception_init()默认已被调用,对于具体的中断来说,只需要先调用irq_install()设置中断处理函数,然后调用irq_enable()使能即可。
下面以按键中断和硬件定时器中断为例,演示如何使用这几个函数。感兴趣的可以跳到相应也没查看。
用龙芯1c库在裸机编程中实现外部中断(GPIO中断、按键中断)
http://blog.csdn.net/caogos/article/details/78166271
用龙芯1c库在裸机编程环境中实现硬件定时器中断
http://blog.csdn.net/caogos/article/details/78166348
函数exception_init()
龙芯1c(mips系列的cpu)中,对于异常和中断分得很清楚,中断常指外设的中断,可能外设已经集成在芯片内了,比如定时器等。中断是一种“特殊的异常”,所有中断共用一个异常入口。所以中断的初始化,就涉及异常的初始化。函数exception_init()就是对龙芯1c的整个异常进行初始化,包括初始化中断。要是用中断,函数exception_init()必须被调用,因此在函数bsp_init()中默认调用了exception_init()。
函数irq_enable()和irq_disable()
简介
/* * 使能指定中断 * @IRQn 中断号 */ void irq_enable(int IRQn); /* * 禁止指定中断 * @IRQn 中断号 */ void irq_disable(int IRQn);
这两个函数的入参都是中断号。所有中断的中断号都可以在INTx_SR(INT0_SR、INT1_SR、……、INT4_SR)中找到,如下图所示
来看看代码中的中断号
// 中断号 #define LS1C_ACPI_IRQ 0 #define LS1C_HPET_IRQ 1 //#define LS1C_UART0_IRQ 3 // linux中是3,v1.4版本的1c手册中是2,暂屏蔽,待确认 #define LS1C_UART1_IRQ 4 #define LS1C_UART2_IRQ 5 #define LS1C_CAN0_IRQ 6 #define LS1C_CAN1_IRQ 7 #define LS1C_SPI0_IRQ 8 #define LS1C_SPI1_IRQ 9 #define LS1C_AC97_IRQ 10 #define LS1C_MS_IRQ 11 #define LS1C_KB_IRQ 12 #define LS1C_DMA0_IRQ 13 #define LS1C_DMA1_IRQ 14 #define LS1C_DMA2_IRQ 15 #define LS1C_NAND_IRQ 16 #define LS1C_PWM0_IRQ 17 #define LS1C_PWM1_IRQ 18 #define LS1C_PWM2_IRQ 19 #define LS1C_PWM3_IRQ 20 #define LS1C_RTC_INT0_IRQ 21 #define LS1C_RTC_INT1_IRQ 22 #define LS1C_RTC_INT2_IRQ 23 #define LS1C_UART3_IRQ 29 #define LS1C_ADC_IRQ 30 #define LS1C_SDIO_IRQ 31 #define LS1C_EHCI_IRQ (32+0) #define LS1C_OHCI_IRQ (32+1) #define LS1C_OTG_IRQ (32+2) #define LS1C_MAC_IRQ (32+3) #define LS1C_CAM_IRQ (32+4) #define LS1C_UART4_IRQ (32+5) #define LS1C_UART5_IRQ (32+6) #define LS1C_UART6_IRQ (32+7) #define LS1C_UART7_IRQ (32+8) #define LS1C_UART8_IRQ (32+9) #define LS1C_UART9_IRQ (32+13) #define LS1C_UART10_IRQ (32+14) #define LS1C_UART11_IRQ (32+15) #define LS1C_I2C2_IRQ (32+17) #define LS1C_I2C1_IRQ (32+18) #define LS1C_I2C0_IRQ (32+19) #define LS1C_GPIO_IRQ 64 #define LS1C_GPIO_FIRST_IRQ 64 #define LS1C_GPIO_IRQ_COUNT 96 #define LS1C_GPIO_LAST_IRQ (LS1C_GPIO_FIRST_IRQ + LS1C_GPIO_IRQ_COUNT-1) #define LS1C_LAST_IRQ 159 // 龙芯1c的中断分为五组,每组32个 #define LS1C_NR_IRQS (32*5) // GPIO编号和中断号之间的互相转换 #define LS1C_GPIO_TO_IRQ(GPIOn) (LS1C_GPIO_FIRST_IRQ + (GPIOn)) #define LS1C_IRQ_TO_GPIO(IRQn) ((IRQn) - LS1C_GPIO_FIRST_IRQ)
注意,gpio的中断号是通过宏LS1C_GPIO_TO_IRQ()转换得到的。
函数irq_enable()和irq_disable()只是把寄存器INTx_EN中相应的位置一或清零了,寄存器INTx_EN是龙芯1c特有的,其它MIPS系列的CPU可能没有。除了寄存器INTx_EN控制着中断的使能外,还有协处理器0的状态(SR)寄存器中的一些位域(比如:IM7-IM0、IE、EXL等)。其中IE和IM7-IM0是我们关心的,IE为全局中断使能位,IM7为滴答定时器的屏蔽位,IM6到IM2共5个位分别控制龙芯1c的五组中断。 即龙芯1c的中断使能分为三级:IE全局中断使能位、IM6-IM2为分组中断使能位、龙芯1c特有的寄存器INTx_EN为每个外设中断的使能位。默认情况,我已经把IE和IM6-IM2都设置为允许中断,而由龙芯1c特有的寄存器INTx_EN来控制每个外设中断是否使能。
滴答定时器是个例外
为什么说滴答定时器是个例外呢?首先从滴答定时器的由来说起,滴答定时器是为了给操作系统提供时钟,也就是说基本上只有在初始化的时候设置一下定时时间之外,很少看见有禁止滴答定时器的。虽然是可以禁止滴答定时器,甚至作为普通定时器一样使用都是可以的,但实际上很少这样做。
前面提到普通的外设中断使能有三级,而滴答定时器只有两级。即滴答定时器只受协处理器0的SR寄存器中的IE和IM7控制,INTx_EN寄存器中没有控制滴答定时器的位。
最新的代码中默认禁止了IM7,即禁止滴答定时器中断。单独封装了函数sys_tick_enable()和sys_tick_disable()来使能和禁止IM7,即使能和禁止滴答定时器中断。
函数irq_install()
/* * 设置中断处理函数 * @IRQn 中断号 * @new_handler 新设置的中断处理函数 * @param 传递给中断处理函数的参数 * @ret 之前的中断处理函数 */ irq_handler_t irq_install(int IRQn, irq_handler_t new_handler, void *param);
函数irq_install()用于设置中断处理函数。来看看中断处理函数的类型irq_handler_t。
typedef void (*irq_handler_t)(int IRQn, void *param);
比如,设置定时器0的中断处理函数
irq_install(LS1C_PWM0_IRQ, test_timer_pwm0_irqhandler, NULL);
中断处理函数test_timer_pwm0_irqhandler()的实现为
void test_timer_pwm0_irqhandler(int IRQn, void *param) { Return ; }
源码清单
ls1c_irq.h
#ifndef __IRQ_H #define __IRQ_H // 中断号 #define LS1C_ACPI_IRQ 0 #define LS1C_HPET_IRQ 1 //#define LS1C_UART0_IRQ 3 // linux中是3,v1.4版本的1c手册中是2,暂屏蔽,待确认 #define LS1C_UART1_IRQ 4 #define LS1C_UART2_IRQ 5 #define LS1C_CAN0_IRQ 6 #define LS1C_CAN1_IRQ 7 #define LS1C_SPI0_IRQ 8 #define LS1C_SPI1_IRQ 9 #define LS1C_AC97_IRQ 10 #define LS1C_MS_IRQ 11 #define LS1C_KB_IRQ 12 #define LS1C_DMA0_IRQ 13 #define LS1C_DMA1_IRQ 14 #define LS1C_DMA2_IRQ 15 #define LS1C_NAND_IRQ 16 #define LS1C_PWM0_IRQ 17 #define LS1C_PWM1_IRQ 18 #define LS1C_PWM2_IRQ 19 #define LS1C_PWM3_IRQ 20 #define LS1C_RTC_INT0_IRQ 21 #define LS1C_RTC_INT1_IRQ 22 #define LS1C_RTC_INT2_IRQ 23 #define LS1C_UART3_IRQ 29 #define LS1C_ADC_IRQ 30 #define LS1C_SDIO_IRQ 31 #define LS1C_EHCI_IRQ (32+0) #define LS1C_OHCI_IRQ (32+1) #define LS1C_OTG_IRQ (32+2) #define LS1C_MAC_IRQ (32+3) #define LS1C_CAM_IRQ (32+4) #define LS1C_UART4_IRQ (32+5) #define LS1C_UART5_IRQ (32+6) #define LS1C_UART6_IRQ (32+7) #define LS1C_UART7_IRQ (32+8) #define LS1C_UART8_IRQ (32+9) #define LS1C_UART9_IRQ (32+13) #define LS1C_UART10_IRQ (32+14) #define LS1C_UART11_IRQ (32+15) #define LS1C_I2C2_IRQ (32+17) #define LS1C_I2C1_IRQ (32+18) #define LS1C_I2C0_IRQ (32+19) #define LS1C_GPIO_IRQ 64 #define LS1C_GPIO_FIRST_IRQ 64 #define LS1C_GPIO_IRQ_COUNT 96 #define LS1C_GPIO_LAST_IRQ (LS1C_GPIO_FIRST_IRQ + LS1C_GPIO_IRQ_COUNT-1) #define LS1C_LAST_IRQ 159 // 龙芯1c的中断分为五组,每组32个 #define LS1C_NR_IRQS (32*5) // GPIO编号和中断号之间的互相转换 #define LS1C_GPIO_TO_IRQ(GPIOn) (LS1C_GPIO_FIRST_IRQ + (GPIOn)) #define LS1C_IRQ_TO_GPIO(IRQn) ((IRQn) - LS1C_GPIO_FIRST_IRQ) // 定义中断处理函数 typedef void (*irq_handler_t)(int IRQn, void *param); typedef struct irq_desc { irq_handler_t handler; void *param; }irq_desc_t; // 初始化异常 void exception_init(void); /* * 使能指定中断 * @IRQn 中断号 */ void irq_enable(int IRQn); /* * 禁止指定中断 * @IRQn 中断号 */ void irq_disable(int IRQn); /* * 设置中断处理函数 * @IRQn 中断号 * @new_handler 新设置的中断处理函数 * @param 传递给中断处理函数的参数 * @ret 之前的中断处理函数 */ irq_handler_t irq_install(int IRQn, irq_handler_t new_handler, void *param); #endif
ls1c_irq.c
/************************************************************************* * * 中断相关的函数 * 龙芯1c中中断是一种类型的异常, * 可以理解为cpu中有几种异常,而中断是其中一种异常下面的一个子类型 * 龙芯1c的异常分为四级 * 第一级: 各种情况下异常向量的总入口 * 第二级: 各个异常的入口,其中ExcCode=0的异常为外设中断的总入口 * 第三级: 外设中断的每组的总入口(龙芯1c将所有外设分为五组) * 第四级: 每个外设中断的入口 * *************************************************************************/ #include <stdio.h> #include <string.h> #include "ls1c_public.h" #include "ls1c_mipsregs.h" #include "ls1c_clock.h" #include "ls1c_gpio.h" #include "ls1c_cache.h" #include "ls1c_sys_tick.h" #include "ls1c_irq.h" #include "ls1c_regs.h" // 每个异常有0x80字节的空间 #define EXCEPTION_HANDER_MAX_SIZE (0x80) // 正常运行时,异常的入口基地址 // 正常运行时,STATUS寄存器的BEV=0,cpu采用RAM空间的异常入口 // 0x80000000地址处不经TLB映射,但缓存 #define EXCEPTION_RAM_EBASE (0x80000000) // 异常的最大个数 #define EXCEPTION_MAX_NUM (32) // 中断的最大个数 #define IRQ_MAX_NUM (LS1C_NR_IRQS) // 中断配置寄存器 #define LS1C_INTREG_BASE (0xbfd01040) struct ls1c_intc_regs { volatile unsigned int int_isr; volatile unsigned int int_en; volatile unsigned int int_set; volatile unsigned int int_clr; /* offset 0x10*/ volatile unsigned int int_pol; volatile unsigned int int_edge; /* offset 0 */ }; struct ls1c_intc_regs volatile *ls1c_hw0_icregs = (struct ls1c_intc_regs volatile *)LS1C_INTREG_BASE; // 异常处理函数 unsigned long exception_handlers[EXCEPTION_MAX_NUM]; // 中断处理函数 irq_desc_t irq_handlers[IRQ_MAX_NUM]; // 汇编实现的函数 extern void irq_disable_all(void); extern void irq_enable_all(void); extern void general_exception(void); extern void handle_int(void); extern void handle_reserved(void); // 设置整个异常向量的处理函数 // @offset 异常向量总入口的偏移 // @src_addr 异常向量总入口处理函数的首地址 void irq_set_exception_vector_handler(unsigned long offset, void *src_addr, unsigned long size) { unsigned long dst_addr; // 异常入口 dst_addr = EXCEPTION_RAM_EBASE+offset; memcpy((void *)dst_addr, src_addr, size); // 先回写dcache,再作废icache // memcpy之后,现在异常向量总入口的指令位于dcache中,需要回写到内存, // 并作废相应icache,作废后当有中断发生时,才会从内存重新加载新代码到icache,这样新代码就生效了 dcache_writeback_invalidate_range(dst_addr, dst_addr + size); icache_invalidate_range(dst_addr, dst_addr + size); return ; } // 设置一个异常的处理函数 // @n 协处理器0的cause寄存器的[2,6]位,即ExcCode // @addr 异常处理函数的首地址 void irq_set_one_exception_handler(int n, void *addr) { unsigned long handler = (unsigned long)addr; exception_handlers[n] = handler; return ; } /* * 默认的中断处理函数 * @IRQn 中断号 * @param 参数 */ void irq_default_handler(int IRQn, void *param) { myprintf("unhandled irq %d occured!\r\n", IRQn); return ; } /* * 使能指定中断 * @IRQn 中断号 */ void irq_enable(int IRQn) { (ls1c_hw0_icregs + (IRQn >> 5))->int_en |= (1 << (IRQn & 0x1f)); return ; } /* * 禁止指定中断 * @IRQn 中断号 */ void irq_disable(int IRQn) { (ls1c_hw0_icregs + (IRQn >> 5))->int_en &= ~(1 << (IRQn & 0x1f)); return ; } /* * 设置中断处理函数 * @IRQn 中断号 * @new_handler 新设置的中断处理函数 * @param 传递给中断处理函数的参数 * @ret 之前的中断处理函数 */ irq_handler_t irq_install(int IRQn, irq_handler_t new_handler, void *param) { irq_handler_t old_handler = NULL; if((0 <= IRQn) && (IRQ_MAX_NUM > IRQn)) { old_handler = irq_handlers[IRQn].handler; irq_handlers[IRQn].handler = new_handler; irq_handlers[IRQn].param = param; } return old_handler; } // 初始化中断 void irq_init(void) { volatile struct ls1c_intc_regs *intc_regs = NULL; int i; int IRQn; // 禁止中断:设置龙芯1C里面的中断配置寄存器 for (i=0; i<5; i++) // 龙芯1c的中断分为五组 { intc_regs = ls1c_hw0_icregs+i; intc_regs->int_en = 0x0; // disable intc_regs->int_pol = -1; // must be done here intc_regs->int_edge = 0x00000000; // 电平触发 intc_regs->int_clr = 0xffffffff; // 清中断 } // 设置默认的中断处理函数 for (IRQn = 0; IRQn < IRQ_MAX_NUM; IRQn++) { irq_handlers[IRQn].handler = irq_default_handler; irq_handlers[IRQn].param = 0; } return ; } // 初始化异常 void exception_init(void) { int i; // 禁止中断:设置协处理器0 irq_disable_all(); // 初始化高速缓存 cache_init(); // 初始化中断 irq_init(); // 设置整个异常向量的处理函数 irq_set_exception_vector_handler(0x180, &general_exception, EXCEPTION_HANDER_MAX_SIZE); irq_set_exception_vector_handler(0x200, &general_exception, EXCEPTION_HANDER_MAX_SIZE); // 设置各个异常的处理函数 for (i=0; i<EXCEPTION_MAX_NUM; i++) { irq_set_one_exception_handler(i, handle_reserved); } irq_set_one_exception_handler(0, handle_int); // 先回写整个dcache,再作废整个icache dcache_writeback_invalidate_all(); icache_invalidate_all(); // 使能中断 irq_enable_all(); return ; } /* * 执行中断处理流程 * @IRQn 中断号 */ void ls1c_do_IRQ(int IRQn) { irq_handler_t irq_handler = NULL; void *param = NULL; irq_handler = irq_handlers[IRQn].handler; param = irq_handlers[IRQn].param; // 执行中断处理函数 irq_handler(IRQn, param); return ; } void ls1c_irq_dispatch(int n) { unsigned int intstatus, irq; /* Receive interrupt signal, compute the irq */ intstatus = (ls1c_hw0_icregs+n)->int_isr & (ls1c_hw0_icregs+n)->int_en; if (0 == intstatus) return ; // 执行中断处理函数 irq = ffs(intstatus) - 1; ls1c_do_IRQ((n<<5) + irq); /* ack interrupt */ (ls1c_hw0_icregs+n)->int_clr |= (1 << irq); return ; } // 中断分发函数 void plat_irq_dispatch(void) { unsigned int pending; pending = read_c0_cause() & read_c0_status() & ST0_IM; if (pending & CAUSEF_IP7) { sys_tick_handler(); } else if (pending & CAUSEF_IP2) { ls1c_irq_dispatch(0); } else if (pending & CAUSEF_IP3) { ls1c_irq_dispatch(1); } else if (pending & CAUSEF_IP4) { ls1c_irq_dispatch(2); } else if (pending & CAUSEF_IP5) { ls1c_irq_dispatch(3); } else if (pending & CAUSEF_IP6) { ls1c_irq_dispatch(4); } else { // 其它情况不处理 } return ; }
ls1c_start.S
/************************************************************************* * * 用汇编实现的启动相关函数 * *************************************************************************/ /* * 汇编文件的扩展名必须是大写的S,不能用小写的s。否则不会执行预处理, * 即把#include, #define等这些当作以'#'开头的注释,而忽略掉,最终导致编译错误 */ #include "ls1c_regdef.h" #include "ls1c_mipsregs.h" #include "ls1c_asm.h" #include "ls1c_stackframe.h" #include "ls1c_cacheops.h" .section ".text", "ax" .set noreorder /* * 禁止中断 * void irq_disable_all(void) */ .globl irq_disable_all irq_disable_all: mfc0 t0, CP0_STATUS and t0, 0xfffffffe mtc0 t0, CP0_STATUS jr ra nop /* * 使能中断 * void irq_enable_all(void) */ .globl irq_enable_all irq_enable_all: mfc0 t0, CP0_STATUS or t0, 0xFC01 mtc0 t0, CP0_STATUS jr ra nop /* * General exception vector for all other CPUs. * * Be careful when changing this, it has to be at most 128 bytes * to fit into space reserved for the exception handler. * * 整个异常向量的入口处理函数 * void general_exception(void) */ .globl general_exception general_exception: mfc0 k1, CP0_CAUSE andi k1, k1, 0x7c # 注意,这里exception_handlers不是函数,而是地址 # 即exception_handlers+k1 = exception_handlers+ExcCode*4 # exception_handlers[]中保存了各个异常处理函数的首地址,每个占4字节 # 这里看上去很巧妙,实际上个人认为很不好理解,很容易混淆 PTR_L k0, exception_handlers(k1) jr k0 .globl handle_int handle_int: SAVE_ALL jal plat_irq_dispatch nop RESTORE_ALL_AND_RET .globl handle_reserved handle_reserved: .set mips3 eret .set mips0 .set reorder