adding blinker05, interrupt example

This commit is contained in:
David Welch
2012-06-10 12:45:58 -04:00
parent f3a4d8b399
commit e063695a38
6 changed files with 369 additions and 2 deletions

View File

@@ -21,8 +21,6 @@ extern void dummy ( unsigned int );
#define GPSET0 0x2020001C #define GPSET0 0x2020001C
#define GPCLR0 0x20200028 #define GPCLR0 0x20200028
#define TIMEOUT 4000000
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
int notmain ( void ) int notmain ( void )
{ {

70
blinker05/Makefile Normal file
View File

@@ -0,0 +1,70 @@
ARMGNU ?= arm-none-eabi
AOPS = --warn --fatal-warnings
COPS = -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding
gcc : blinker05.hex blinker05.bin
all : gcc clang
clean :
rm -f *.o
rm -f *.bin
rm -f *.hex
rm -f *.elf
rm -f *.list
rm -f *.img
rm -f *.bc
rm -f *.clang.opt.s
vectors.o : vectors.s
$(ARMGNU)-as vectors.s -o vectors.o
blinker05.o : blinker05.c
$(ARMGNU)-gcc $(COPS) -c blinker05.c -o blinker05.o
blinker05.elf : memmap vectors.o blinker05.o
$(ARMGNU)-ld vectors.o blinker05.o -T memmap -o blinker05.elf
$(ARMGNU)-objdump -D blinker05.elf > blinker05.list
blinker05.bin : blinker05.elf
$(ARMGNU)-objcopy blinker05.elf -O binary blinker05.bin
blinker05.hex : blinker05.elf
$(ARMGNU)-objcopy blinker05.elf -O ihex blinker05.hex
LOPS = -Wall -m32 -emit-llvm
LLCOPS = -march=arm -mcpu=arm1176jzf-s
LLCOPS0 = -march=arm
LLCOPS1 = -march=arm -mcpu=arm1176jzf-s
COPS = -Wall -O2 -nostdlib -nostartfiles -ffreestanding
OOPS = -std-compile-opts
clang : blinker05.clang.hex blinker05.clang.bin
blinker05.clang.bc : blinker05.c
clang $(LOPS) -c blinker05.c -o blinker05.clang.bc
blinker05.clang.opt.elf : memmap vectors.o blinker05.clang.bc
opt $(OOPS) blinker05.clang.bc -o blinker05.clang.opt.bc
llc $(LLCOPS) blinker05.clang.opt.bc -o blinker05.clang.opt.s
$(ARMGNU)-as blinker05.clang.opt.s -o blinker05.clang.opt.o
$(ARMGNU)-ld -o blinker05.clang.opt.elf -T memmap vectors.o blinker05.clang.opt.o
$(ARMGNU)-objdump -D blinker05.clang.opt.elf > blinker05.clang.opt.list
blinker05.clang.hex : blinker05.clang.opt.elf
$(ARMGNU)-objcopy blinker05.clang.opt.elf blinker05.clang.hex -O ihex
blinker05.clang.bin : blinker05.clang.opt.elf
$(ARMGNU)-objcopy blinker05.clang.opt.elf blinker05.clang.bin -O binary

107
blinker05/README Normal file
View File

@@ -0,0 +1,107 @@
See the top level README for information on where to find the
schematic and programmers reference manual for the ARM processor
on the raspberry pi. Also find information on how to load and run
these programs.
This is derived from blinker04.
I dont understand why but way too many blinker examples use interrupts.
In short only use interrupts if you have to, usually you dont have to.
If you feel you have to this example shows you a little about enabling
and handling interrupts on a raspberry pi.
Interrupts are an advanced programming topic, no matter how simple
this example makes it appear, there is a long list of things that can
and will go wrong. I definitely didnt get this working on the first
or second or third try. It took many. Fortunately on this system
we have a few layers we can play with before actually interrupting
the processor. This is both a blessing and a curse, some systems
have a number of layers, not all easy to find in the docs if in the
docs and all layers have to have the right enables, etc to route the
interrupt from the peripheral to processor. blinker03 showed a timer
with a raw interrupt status register, basically the interrupt signal
near its source. The interrupt enable bit is the first layer you
have to enable the signal through. This example kills three birds with
one stone. Instead of three examples I enable one layer at a time
and show a few blinks with each layer before completely connecting
the interrupt to the processor.
The first problem we have with the raspberry pi is where the binary
is loaded. I have had the most success by not mucking with settings
which means that our program is loaded at address 0x8000. To service
an interrupt we need to have our exception table at address 0x0000.
If you have been reading your ARM documents as you should by this point
you will know that for an interrupt on this processor it executes
the instruction at address 0x0001C. Also the interrupt mode has
a different r13 or stack pointer than supervisor mode. So if we plan
to use the stack at all in the interrupt handler we need to prepare
that stack pointer before any interrupts come along. Before we get
to that looking at vectors.s. We know that 0x8000 is our entry point
where _start will be. We know that is not the exception table but I
have made one there anyway, why? Using the ldr pc,label instruction
I am letting the assembler do the work of creating the machine code
for a relative load. Offset 0x0000 loads into the program counter
(same as doing a branch if you are not switching modes) the address at
offset 0x0020 which is filled in (by the assembler) the address for
the reset label. This repeats one for one for 8 handlers 0x0004 gets
address in 0x0024, etc. I happen to know and you can look up the
fact that ldr pc,label when used this way uses relative addressing
8000: e59ff018 ldr pc, [pc, #24] ; 8020 <reset_handler>
...
00008020 <reset_handler>:
8020: 00008040 andeq r8, r0, r0, asr #32
The machine instruction 0xe59ff018 when placed at address 0x1000 will
read from address 0x1000+0x20, basically pc+0x20 (the disassembly is
misleading). So if I were to copy the instruction at 0x8000 to 0x0000
and the address at 0x8020 to 0x0020, then it still works, a reset exception
if we could create one would read 0x8040 in this case and put that in
the pc, causing a jump to 0x8040. Same is true for all 8 instructions and
all 8 addresses. So the first thing the code does after reset is
copy those 16 words from 0x8000 to 0x0000, we didnt have to figure out
addresses for functions and didnt have to generate machine code this
approach let the assembler do the work.
The next thing the reset code does is set up the stack pointer, not
much different than the other example programs except in this case
we need to setup both the supervisor mode, which we normally run
these examples in, and interrupt mode. The modes determine which
registers are used, the stack pointer being one that has a different
register for each mode. The trick is to use the mrs instruction to
change some of the bits in the program status register (psr) the bits
in particular we care about are the mode bits. Change the mode to
interrupt, set the interrupt stack pointer, change the mode back to
supervisor and change the stack pointer, then continue by calling the
entry C function, notmain().
Just like blinker04, blinker05 configures the gpio pin for driving the
led and sets up the timer. Different from blinker05, interrupts are
enabled in the control register (bit 5). Using the assumption that this
program was run just after reset, either as kernel.img on the sd card
or using one of my bootloaders that doesnt mess with enabling interrupts,
etc. The interrupt will leave the timer but get stopped at the next layer.
Since it is leaving the timer we will be able to see it in the masked
interrupt status register. So the first 5 blinks, once state change
per second, are based on polling the MIS register.
The next layer is between the timer and the ARM. Each chip vendor and
model can vary wildly as to how many layers and how to use them. In
this case we have a relatively simple interrupt enable . By setting
a bit in the irq enable register we are allowing the arm timer interrupt
to pass through to the ARM core. Because our early cpsr registers
manipulation (to set up the interrupt stack) forced the interrupt
enable for the ARM core to a one, ARM core interrupts are disabled.
The next 5 blinks at 2 seconds per state change poll the interrupt
status register at this layer.
The last step is to enable the interrupts to the arm core by changing
the I bit in the cpsr to a zero. Because we had prepared the exception
table when the interrupt comes the code at address 0x1C is executed
which eventually hits the c_irq_handler function, remember this is
an interrupt handler, you are soemwhat running parallel. Dont take
very long and dont mess with resources in use by the foreground
application. typically you want to be in and out fast. At some
point in the isr you need to clear the source of the isr.

102
blinker05/blinker05.c Normal file
View File

@@ -0,0 +1,102 @@
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
extern void PUT32 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
extern void dummy ( unsigned int );
extern void enable_irq ( void );
extern void enable_fiq ( void );
#define ARM_TIMER_LOD 0x2000B400
#define ARM_TIMER_VAL 0x2000B404
#define ARM_TIMER_CTL 0x2000B408
#define ARM_TIMER_CLI 0x2000B40C
#define ARM_TIMER_RIS 0x2000B410
#define ARM_TIMER_MIS 0x2000B414
#define ARM_TIMER_RLD 0x2000B418
#define ARM_TIMER_DIV 0x2000B41C
#define ARM_TIMER_CNT 0x2000B420
#define SYSTIMERCLO 0x20003004
#define GPFSEL1 0x20200004
#define GPSET0 0x2020001C
#define GPCLR0 0x20200028
#define IRQ_BASIC 0x2000B200
#define IRQ_PEND1 0x2000B204
#define IRQ_PEND2 0x2000B208
#define IRQ_FIQ_CONTROL 0x2000B210
#define IRQ_ENABLE_BASIC 0x2000B218
#define IRQ_DISABLE_BASIC 0x2000B224
volatile unsigned int icount;
//-------------------------------------------------------------------------
void c_irq_handler ( void )
{
icount++;
if(icount&1)
{
PUT32(GPCLR0,1<<16);
}
else
{
PUT32(GPSET0,1<<16);
}
PUT32(ARM_TIMER_CLI,0);
}
//-------------------------------------------------------------------------
int notmain ( void )
{
unsigned int ra;
PUT32(IRQ_DISABLE_BASIC,1);
ra=GET32(GPFSEL1);
ra&=~(7<<18);
ra|=1<<18;
PUT32(GPFSEL1,ra);
PUT32(GPSET0,1<<16);
PUT32(ARM_TIMER_CTL,0x003E0000);
PUT32(ARM_TIMER_LOD,1000000-1);
PUT32(ARM_TIMER_RLD,1000000-1);
PUT32(ARM_TIMER_DIV,0x000000F9);
PUT32(ARM_TIMER_CLI,0);
PUT32(ARM_TIMER_CTL,0x003E00A2);
for(ra=0;ra<5;ra++)
{
PUT32(GPCLR0,1<<16);
while(1) if(GET32(ARM_TIMER_MIS)) break;
PUT32(ARM_TIMER_CLI,0);
PUT32(GPSET0,1<<16);
while(1) if(GET32(ARM_TIMER_MIS)) break;
PUT32(ARM_TIMER_CLI,0);
}
PUT32(ARM_TIMER_LOD,2000000-1);
PUT32(ARM_TIMER_RLD,2000000-1);
PUT32(ARM_TIMER_CLI,0);
PUT32(IRQ_ENABLE_BASIC,1);
for(ra=0;ra<5;ra++)
{
PUT32(GPCLR0,1<<16);
while(1) if(GET32(IRQ_BASIC)&1) break;
PUT32(ARM_TIMER_CLI,0);
PUT32(GPSET0,1<<16);
while(1) if(GET32(IRQ_BASIC)&1) break;
PUT32(ARM_TIMER_CLI,0);
}
PUT32(ARM_TIMER_LOD,4000000-1);
PUT32(ARM_TIMER_RLD,4000000-1);
icount=0;
enable_irq();
while(1) continue;
return(0);
}
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------

12
blinker05/memmap Normal file
View File

@@ -0,0 +1,12 @@
MEMORY
{
ram : ORIGIN = 0x8000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ram
.bss : { *(.bss*) } > ram
}

78
blinker05/vectors.s Normal file
View File

@@ -0,0 +1,78 @@
.globl _start
_start:
ldr pc,reset_handler
ldr pc,undefined_handler
ldr pc,swi_handler
ldr pc,prefetch_handler
ldr pc,data_handler
ldr pc,unused_handler
ldr pc,irq_handler
ldr pc,fiq_handler
reset_handler: .word reset
undefined_handler: .word hang
swi_handler: .word hang
prefetch_handler: .word hang
data_handler: .word hang
unused_handler: .word hang
irq_handler: .word irq
fiq_handler: .word hang
reset:
mov r0,#0x8000
mov r1,#0x0000
ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}
;@ (PSR_IRQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
mov r0,#0xD2
msr cpsr_c,r0
mov sp,#0x8000
;@ (PSR_FIQ_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
mov r0,#0xD1
msr cpsr_c,r0
mov sp,#0x4000
;@ (PSR_SVC_MODE|PSR_FIQ_DIS|PSR_IRQ_DIS)
mov r0,#0xD3
msr cpsr_c,r0
mov sp,#0x8000000
;@ SVC MODE, IRQ ENABLED, FIQ DIS
;@mov r0,#0x53
;@msr cpsr_c, r0
bl notmain
hang: b hang
.globl PUT32
PUT32:
str r1,[r0]
bx lr
.globl GET32
GET32:
ldr r0,[r0]
bx lr
.globl dummy
dummy:
bx lr
.globl enable_irq
enable_irq:
mrs r0,cpsr
bic r0,r0,#0x80
msr cpsr_c,r0
bx lr
irq:
push {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
bl c_irq_handler
pop {r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,lr}
subs pc,lr,#4