龙芯LS1C101单片机实验(3)--外部中断

时间:2022-12-26 09:00:44

LS1C101单片机实验板,8个LED灯,代表gpio32~gpio39

一.准备工作 参考<龙芯LS1C101单片机实验(2)--内核定时器中断> (https://blog.51cto.com/u_13752418/4844315)

二.实验目的 gpio针脚外部中断,例如按键,按下开关键触发中断; 实验板带有4个按键(gpio49~52),但因无实验板资料,不知这4个按键功能,测试不顺利.

如果外置独立按键的话,需接上/下拉电阻,本人不懂电路; 经网上搜索,说可以用板上自带3.3伏电压触碰gpio针脚模拟按下开关,因此为方便,就采用此方式; 注意不要用外置电压,以免烧坏实验板.

外部中断触发可分电平触发和边沿触发 电平触发又分高电平触发和低电平触发

复位默认是高电平触发,所以为方便不需设置,本实验采用高电平触发

本实验使用GPIO6、GPIO7作外部中断

三.实验过程 用杜邦线母头插到实验板3.3伏针脚,另一头杜邦线公头轻轻触碰GPIO6、GPIO7针脚并即刻松开,模拟按下开关键

灯gpio35,用于观察已进入中断 灯gpio33,用于观察已返回断点 灯gpio36,用于观察按下GPIO6键 灯gpio37,用于观察按下GPIO7键

gpio35灯亮1秒左右便熄灭,代表发生中断 gpio34灯亮,表示发生了外部中断 gpio36灯亮,表示GPIO6发生中断 gpio37灯亮,表示GPIO7发生中断 gpio33灯灭1秒左右又点亮,代表返回断点

四. 1.源代码 代码文件为irqgpio_start.S,放在/home/linlin/loongson/myls1c/下

#include <asm.h>
#define DELAY(count)    \
    li      t3, count;  \
88:                     \
    nop;                \
    subu    t3, 0x1;    \
    bnez    t3, 88b;    \
    nop
 
    .set noreorder
    .set noat

    nop

li t0,0xbfeb0000      // t0作为电源管理模块(PMU)基址
li t1,0xbfeb0080      // t1用作GPIO位访问端口(GPIOBit)起始地址,0xbfeb0000+0x80对应gpio00地址

li t2,0xFBFF0400      // 看门狗复位等待时间长为0x400秒
sw t2,0x30(t0)        // watchdog地址相对t0偏移0x30


//--v--
// #1
//--^--

li t2,0x3
sb t2,39(t1)         // 点亮gpio39,相对t1偏移39
 
li    a0,0x00408001  // 允许外部中断 100 0000 1000 0000 0000 0001=0x00408001  第15位(第0位算起,下同)
mtc0  a0,c0_status   // #2 

//a)外部中断使能(ExintEn) 偏移0x20(如无特别说明,偏移指相对PMU,下同)
li t2,0xC0           // 1100 0000=0xC0  第6、7位分别对应GPIO6、GPIO7使能中断
sw t2,0x20(t0)

//--v--
// #3
//--^-- 

//b)命令与状态(CmdSts) 偏移0x04
li t2,0x2000000     // 10 0000 0000 0000 0000 0000 0000=0x2000000  第25位
sw t2,0x04(t0)      // 外部中断使能

//--v-- while(1)    // 死循环,中断断点将发生在此里边,便于实验验证
li v0, 100
99:       
 
li t4,0x3           // 中断里使用t2值点、灭灯,为排除实验干扰,所以这里不能用t2
sb t4,33(t1)        // 点亮gpio33
   
bnez  v0,99b
nop
//--^--
 
sb t4,38(t1)        // gpio38不亮,说明while(1)正确,说明此句不执行到,同上不能用t2
 
//--^ 上面的代码要确保编译后地址不能超过中断入口地址0x380 
 
.org 0x380          // 中断入口,0xbfc00000(链接开始地址)+0x380=0xbfc00380
 
is_int:

li t2,0x3
sb t2,35(t1)       // 点亮gpio35,便于观察已进入中断
nop

//任何中断和异常都会进入中断入口,所以要区分中断源以区别处理不同中断
//--v-- 查询中断源
mfc0 t2,c0_cause   // 读c0_cause,CP0的原因寄存器
andi t2,t2,0x7c    // 111 1100=0x7c  第2~6位为exccode,与0x7c后得到exccode,值0表示中断(不是异常)
beq  t2,zero,44f   // 等于0往下跳转到到44标签位置.f表示往下,b表示往上,所以不写作44b
nop  
li   t2,0x3
sb   t2,38(t1)     // 点亮gpio38,表示发生了异常.实验过程中,此句没发生过

44:
mfc0 t2,c0_cause
andi t2,t2,0x8000  // 1000 0000 0000 0000=0x8000,与第15位,即IP7,在LS1C101即外部中断
beq  t2,zero,66f   // 等于0跳转,IP7位为0表示没发生外部中断
nop                // 延迟槽

li t2,0x3
sb t2,34(t1)       // 点亮gpio34,表示发生了外部中断
nop

//c)外部中断状态(ExintSrc) 偏移0x2c
lw   t2,0x2c(t0)
nop                // 延迟槽插入nop
andi t2,t2,0x40    // 100 0000=0x40  第6位对应GPIO6
beq  t2,zero,55f   // 等于0跳转,为0表示GPIO6没发生中断
nop
li   t2,0x3
sb   t2,36(t1)     // 点亮gpio36,表示GPIO6发生中断

55:
lw   t2,0x2c(t0)
nop
andi t2,t2,0x80    // 1000 0000=0x80  第7位对应GPIO7
beq  t2,zero,66f   // 手册说'1'为高电平/上升沿有效.那对于设置为低电平/下降沿触发是不是状态为'0'才表示发生中断?不是很明白
nop
li   t2,0x3
sb   t2,37(t1)     // 点亮gpio37,表示GPIO7发生中断

66:

nop
//--^--查询中断源

nop
DELAY(0x5fff)      // 1秒左右
nop
li t2,0x2          // 0x2为灯灭
sb t2,35(t1)       // 熄灭gpio35
sb t2,33(t1)       // 熄灭gpio33

sb t2,34(t1)
sb t2,36(t1)
sb t2,37(t1) 
 
DELAY(0x5fff)      // 让gpio33熄灭1秒左右;如果1秒后gpio33重新点亮,说明正常返回while(1)断点
nop
li t2,0x3
sb t2,32(t1)
nop

//--v-- 清中断标志,本来应该在不同查询中断源清各自中断标志,本次实验为简单无其它的中断,所以直接清外部中断
//d)命令与状态(CmdSts)的写端口(CommandW) 偏移0x3c
li  t2,0x1000000   // 01 0000 0000 0000 0000 0000 0000=0x1000000  第24位才是外部中断ExtInt状态
sw  t2,0x3c(t0)    // 写1清中断状态,电平模式应只需此清中断标志
//--^--

nop

eret
nop

2.解析 1)#1处 引脚复用有gpio、主功能、第一复用、第二复用;gpio分输入、输出;复位默认gpio功能并是输入. 外部中断极性(ExintPol)是设置高电平/上升沿还是低电平/下降沿有效,复位默认高电平/上升沿. 外部中断边沿(ExintEdge)是选择电平模式还是边沿模式,复位默认电平.

本实验是高电平触发,对引脚施加电压输入,所以无需在#1处设置gpio功能及输入、极性、边沿

2)#2处 协处理器CP0的状态寄存器c0_status需先参考MIPS体系结构资料(我参考的是<See MIPS Run Linux>协处理器0 章节)得知第15 ~ 8位是中断屏蔽IM7 ~ IM0(LS1C101手册无此内容); 然后再参考LS1C101手册中断章节得到IM7 ~ IM0(有的地方是写IP7 ~ IP0)在LS1C101的对应关系,可知IM7对应ExInt即外部中断(应该MIPS兼容不同系列CPU对应的IM7 ~ IM0具体意义可能不同,所以IM7 ~ IM0要参考具体CPU手册)

3)#3处 如要设置极性、边沿,需在#1处设置,即只能在外部中断使能(ExintEn)之前设置,不能在其后,即不能在#3处设置外部中断极性和外部中断边沿; 否则会还未按下键就立即触发中断(已测试中断使能之后#3处设置极性边沿,还没进入while死循环处就中断了)

4)参考文档 需参考两方面的内容: 一个是体系结构,通用所有同体系CPU;另一个是具体CPU了.

LS1C101是MIPS兼容,所以LS1C101手册只介绍硬件寄存器,没介绍体系结构.

有关mfc0指令、CP0的原因寄存器c0_cause详见MIPS体系结构书籍<See MIPS Run Linux>协处理器0 章节.

a) ~ d)硬件寄存器详见龙芯官方LS1C101手册文档寄存器定义章节;d)CmdSts的写端口(CommandW)寄存器在手册没独立的小节,是在b)CmdSts小节描述.

5)电源管理模块 看门狗、GPIO位访问端口(GPIOBit)、a) ~ d)等都在PMU之中,所以访问这些寄存器只需相对PMU基址偏移即可(直接用手册列出的偏移). 源码GPIOBit单独以gpio00作基址,那是为了以gpio编号方便阅读.

6)延迟槽 分支跳转及加载(lw、lb)指令都有延迟槽(即其紧接下的那条).

编译器会自动处理延迟槽,程序员不需关心,但下面情况需程序员正确看待: 汇编源码开头使用.set noreorder强制保证汇编源码顺序(程序员意图),即编译器不会为优化而改变汇编源码指令顺序,保证了硬件寄存器初始化顺序,此时编译器就不会自动插入延迟槽指令了; 因此程序员必须自己手动插入延迟槽指令,为方便阅读理解并避坑,还是用nop插入到延迟槽,总之,如果你不了解延迟槽,最为保险就是插入nop空操作指令.

本实验代码开头使用了.set noreorder,所以上面分支跳转及加载语句后面的延迟槽nop指令不能省略;反汇编结果与源码指令次序一致.

延迟槽是为了提高流水线的效率,但给程序员的感觉是一大坑,我认为好的体系结构指令集作为硬件和软件的接口应该对程序员屏蔽底层细节,按程序员的直观思维看待问题. 并不是所有CPU体系结构都有延迟槽,现代采用了动态流水线和多发射后,延迟槽已没必要.

7)测试注释掉.set noreorder反汇编结果 7.1)源码while(1)死循环处

  40:    24020064     li    v0,100
  44:    240c0003     li    t4,3
  48:    1440fffe     bnez    v0,44 <is_int-0x33c>
  4c:    a12c0021     sb    t4,33(t1)  //这条是从源码bnez上方下调到下方,可见编译器调整了次序
  50:    00000000     nop

7.2)源码查询中断源开头处

 38c:    400a6800     mfc0    t2,c0_cause
 390:    314a007c     andi    t2,t2,0x7c
 394:    11400004     beqz    t2,3a8 <is_int+0x28>
 398:    00000000     nop     //这条是编译器插入nop
 39c:    00000000     nop     //这条是源码本身
 3a0:    240a0003     li    t2,3

为什么不是如7.1)调整次序而是插入空操作? 因为beqz和其源码上条andi共同t2相关,所以不能调整andi次序; 那为何插入空操作,假如源码beqz下方不是nop而是有意义的li指令等,如果编译器不插入nop则li是延迟槽了,表明li先于beqz执行,这就与程序员意图不符

7.3)源码标签55:处

 3e8:    8d0a002c     lw    t2,44(t0)
 3ec:    00000000     nop   //这条是源码本身
 3f0:    314a0080     andi    t2,t2,0x80

可见与源码指令次序一致,说明lw指令不需调整次序也不需插入nop

7.4)小结 对于一般的汇编程序,通常不需加.set noreorder,这时程序员不要考虑延迟槽问题,就当作无延迟槽这个概念,编译器会自动处理延迟槽,如果程序员多次一举考虑延迟槽问题反而经编译器出现意图不符问题; 对于为保证硬件寄存器初始化顺序而加.set noreorder,为保险用nop插入延迟槽; 对于lw指令,我一知半解,或许分支指令和加载指令的延迟槽的确不同

8)现场保护 完善的中断还需进行现场(通用寄存器等)保存和恢复,本文就不讨论了

9 ) 汇编源码li的立即数大于16位的话,会编译为两条机器指令,加载低16位和高16位;因为指令长度固定32位,不可能立即数占完了32位,所以源码的li指令不是真的机器指令 如 li t1,0xbfeb0080 编译为:

   8:    3c09bfeb     lui    t1,0xbfeb
   c:    35290080     ori    t1,t1,0x80

可见分解的两条指令仍是使用原来的通用寄存器t1

10 ) .set noat禁止自动编译生成at寄存器(留给汇编用的临时寄存器) 汇编源码中断异常处理通常有如下形式:

.set noat       //禁用at
中断入口处理
.set at         //允许at,即.set at源码行以下又开始允许at
...

本实验代码为方便在源码开头.set noat

3.编译

linlin@debian:~/loongson/myls1c$ /home/linlin/loongson/opt/gcc-4.4.7-gnu/bin/mipsel-linux-gcc -O2 -fno-builtin -mips32  -fno-pic -mno-abicalls -g -I include -I .  -c irqgpio_start.S -nostdinc -nostdlib

4.链接

linlin@debian:~/loongson/myls1c$ /home/linlin/loongson/opt/gcc-4.4.7-gnu/bin/mipsel-linux-ld -g -T  bin.lds  -o irqgpio.elf  irqgpio_start.o   -Ttext 0xbfc00000

5.转换为二进制格式

linlin@debian:~/loongson/myls1c$ /home/linlin/loongson/opt/gcc-4.4.7-gnu/bin/mipsel-linux-objcopy -O binary irqgpio.elf irqgpio.bin

linlin@debian:~/loongson/myls1c$ ls -l irqgpio.bin
-rwxr-xr-x 1 linlin linlin 4112 2022年 8月26日 irqgpio.bin

文件irqgpio.bin大小4112字节,实验板闪存是8M,需将二进制文件大小补足到8M才能烧录. 计算填充大小 8388608-4112=8384496

linlin@debian:~/loongson/myls1c$ dd if=/dev/zero bs=1 count=8384496 >> irqgpio.bin
记录了8384496+0 的读入
记录了8384496+0 的写出
8384496字节(8.4 MB,8.0 MiB)已复制,29.4269 s,285 kB/s

6.烧写 参考<龙芯LS1C101单片机实验(2)--内核定时器中断>

( 附:单点登录管理图形界面前端 fgsso-ver0.2.2.zip 源代码 下载地址 https://url81.ctfile.com/f/41276635-741947405-ff0e01?p=3679 提取码: 3679 ) ( 附:单点登录 Kerberos+LDAP 一键安装脚本 onekeysso-ver0.2.2.zip 源代码 下载地址 https://url81.ctfile.com/f/41276635-741947406-89bf61?p=3679 提取码: 3679 ) ( 附:简易分布式容器平台 cocont-ver0.0.3.zip 源代码 下载地址 https://url81.ctfile.com/f/41276635-741947404-8eab4f?p=3679 提取码: 3679 )