Teensy 3.1, Arduino and SWI

Status
Not open for further replies.

TelephoneBill

Well-known member
I want to read the Teensy 3.1 Oscillator Register at address 4006_5000h to see which load capacitors are in use with the External Crystal. My Arduino ASM code fails (and causes a crash when I try read this memory address) - presumably because I'm in User Mode and don't have the priviledge to access this register. So to gain Supervisor priviledge, I need to switch modes using an SWI call and perform the register read from within the SWI handler. Looking at the Interrupt Vector Table, there only seems to be one SOFTWARE INTERRUPT allocation at IRQ94. This is puzzling because reading the ARM technical literature, they talk about multiple software interrupts. If there is only one vector in the table, how can you call several software interrupts? Or is intended that there is only one handler, and you should pass a parameter (for example in r0) to indicate which "software interrupt function" the handler must invoke?

Another puzzling aspect is the "AttachInterrupt" function in Arduino seems to cater for hardware interrupts, so how could I define my own handler for IRQ94 and get this handler address inserted into the vector table at memory address 0000_01B8h?
 
Interesting. I have read the thread... and a lot more. The fog is beginning to lift...

Yes, the ARM system exceptions are definitely how this should be done. Specifically, SVC (SuperVisor Call) at vector 11 should be used. So I then had a look at your Teensyduino "attachInterruptVector" - a one-liner which sets an array "_VectorsRam[irq + 16] = function;" (where function is an address of the handler routine).

Then the penny dropped. The vector table no longer resides in FLASH at address 0000_0000h, but I guess that Teensyduino relocates this into RAM - even before a user sketch code runs. It has to be so? Or else a user could not dynamically change it :) !

Looking at your "attachInterruptVector" this won't work for the first 16 vectors (unless you pass a negative irq number). But I should be able to use "_VectorRam[11] = function" (or an ASM equivalent). Being RAM I should have the priviledge necessary to address it in user mode, before I make the SVC call. We shall see.

Consequent from the fog dispersal, I read that SVC is exactly the same opcode as the former SWI instruction. It appears to have been "rebranded" in Cortex-M3 resultant on some other changes. I dropped a "SVC #0" instruction into my ASM just to see if it would assemble under gcc - and lo and behold - it did.

The puzzle on "multiple software interrupts" is also clear now. Its the info in the opcode information field - the "#0" bit. Your SVC handler has to go back and read the actual opcode instruction info field (in FLASH) to know which of the "multiple software interrupts" was actually intended. And this gets a bit hairy because you have to deduct either "4" or "2" from the LR register inside the handler to know where the opcode instruction can be found - which of these two values depends on whether your code is running under ARM (32 bit) Instructions of Thumb2 (16 bit) instructions.

For anyone reading this reply, its the Teensy mcu response to interpretting the SVC opcode that does the mode switch from "user mode" to "supervisor mode". What appears to happen is that the CPSR register gets "saved" temporarily into a new one called SPSR (ready for the return journey), and the CPSR is magically altered to show the new mode as well as having the INTERRUPT DISABLE FLAG set. This all happens before the SVC handler routine commences. Once under way, you then have to write a SWITCH function (after you have calculated which of the multiple software interrupts was intended) to perform the specific task requested. Finally, when exiting your SVC handler you must use "MOVS pc, lr" (or equivalent) as the last instruction - this restores the original CPSR from that saved one in SPSR before passing you back to the instruction that follows "SVC #0" (which switches you back into user mode and re-allows whatever interrupts were permitted).

Reading ARM literature, the terms "interrupt" and "exception" get mixed together - "interrupt" appears to be a response to an external event (such as a pin change) and "exception" is a response to an internal software event (such as a divide by zero). They work in almost the same way - perhaps the "mode" switch is the difference. This might explain why IRQ94 exists - the literature calls this a "software interrupt". Being at the end of the vector table, I guess that it won't mode switch, which means that I could not have used this as I intended in the original post. Glad I spotted this now!

If I ever get to read the original Oscillator Register at 4006_5000h, then I will post an example back on this forum thread, so that others can see how to do it.

All this reminds me of those bygone lazy summer days of the DOS "Int 21h" call - jeez was that really 30 years ago...
 
This is proving more complex than I imagined :)... If Teensyduino runs everything in system mode, then does this mean that my own ASM routines called as a function from the normal sketch loop() also runs in system mode? I think so, please correct me if you think not.

I think so, because I had the notion to read the CONTROL register whilst in the ASM routine using "mrs r0, CONTROL" and then stored this value as a global variable to print out using the serial monitor with Serial.print. (There is no CPSR register in Cortex-M4 apparently - there is a new bunch or registers, one of which is CONTROL). The answer I always got was CONTROL = 0h. Now bit[0] of that register is called nPRIV and a value of "0" = priviledged, whilst "1" = unpriviledged. This is why I think so...

But if true, then why does the original ASM read to memory location 4006_5000 crash the program? It certainly did so, because changing the specified memory location from "ldr r2, =0x40065000, mov r0, [r2]" to become "ldr r2, =0x4004B014, mov r0, [r2]" made the difference between a crash (LED Blink stops) and correct running (LED blink is continuous).

Very mysterious...
 
On reflection, my last post is in serious error... I now do not think a user's program can be in anything but THREAD MODE. After all, a reset takes place when you plug Teensy 3.1 into the USB socket, or after a download, and this would normally default to THREAD MODE.

But something strange is going on. I found by trial and error that the Vector Table is relocated to 1FFF_8200h, so I then calculated that the vector for an SVCall exception should be placed at 1FFF_822Ch (Vector 11 = 4 times 11 = 2Ch). So I wrote an "SVCHandler" routine in ASM which did nothing but "MOVS pc, lr" (the return from exception instruction). I placed the address of the "SVCHandler" at 1FFF_822Ch and I re-read this location just to make sure that the vector had been changed correctly (I think the address turned out to be 480h). I then made an "SVC #0" call (invoked by sending a character over the serial monitor) to try invoke the exception. It crashed (LED Blink stops). If I commented out "SVC #0" then no crash (LED Blinks forevermore). So for some strange reason, the "SVC #0" call is nor responding to the vector at 1FFF_822Ch.

The Exception Processing is described in detail in "ARMv7 Architectural Ref Manual (section B1)" and I'm still trawling my way through this looking for a reason why my code doesn't behave as desired.
 
then does this mean that my own ASM routines called as a function from the normal sketch loop() also runs in system mode?

Yes, system mode.

I placed the address of the "SVCHandler" at 1FFF_822Ch and I re-read this location just to make sure that the vector had been changed correctly (I think the address turned out to be 480h). I then made an "SVC #0" call (invoked by sending a character over the serial monitor) to try invoke the exception. It crashed (LED Blink stops).

If you used 0x00000480 for the address, you almost certainly caused a fault condition.

Cortex-M4 only support Thumb state. Vector addresses must have their LSB set. To jump to location 0x480, you'd actually put 0x481 into the vector table. LSB = 0 means ARM state, which isn't supported by Cortex-M chips.

There are many of these minor but critically important details required for assembly language programming on Cortex-M4. Most people never encounter or even know about this stuff, since it's all normally handled by the compiler. But when you take low-level control with direct assembly language, with raw addresses (not automatically filled in by the linker), you have to do all these little things correctly.

I highly recommend reading Joseph Yiu's book. It covers most of this stuff in a much more approachable manner than reading ARM's architecture reference. It'll also make using that reference easier when you do need to look up some small detail.

http://www.amazon.com/Definitive-Guide-Cortex®-M3-Cortex®-M4-Processors/dp/0124080820/

You might also consider putting some code into the 3 fault handlers (or the only one if using Teensy-LC). If you're fiddling with low level stuff, you're going to trigger those faults. Reducing the time to figure out which fault you hit, and especially some code to give you a stack dump (see mk20dx128.c for a sample for Teensy-LC) can really help you quickly figure out what went wrong.
 
I want to read the Teensy 3.1 Oscillator Register at address 4006_5000h to see which load capacitors are in use with the External Crystal.

If your only purpose is to know the content of this register, see mk20dx128.c:
Code:
    #if defined(KINETISK)
    // enable capacitors for crystal
    OSC0_CR = OSC_SC8P | OSC_SC2P;
    #elif defined(KINETISL)
    // enable capacitors for crystal
    OSC0_CR = OSC_SC8P | OSC_SC2P | OSC_ERCLKEN;
    #endif
Where
Code:
// Chapter 25: Oscillator (OSC)
#define OSC0_CR			(*(volatile uint8_t  *)0x40065000) // OSC Control Register
#define OSC_SC16P			((uint8_t)0x01)			// Oscillator 16 pF Capacitor Load Configure
#define OSC_SC8P			((uint8_t)0x02)			// Oscillator 8 pF Capacitor Load Configure
#define OSC_SC4P			((uint8_t)0x04)			// Oscillator 4 pF Capacitor Load Configure
#define OSC_SC2P			((uint8_t)0x08)			// Oscillator 2 pF Capacitor Load Configure
#define OSC_EREFSTEN			((uint8_t)0x20)			// External Reference Stop Enable, Controls whether or not the external reference clock (OSCERCLK) remains enabled when MCU enters Stop mode.
#define OSC_ERCLKEN			((uint8_t)0x80)			// External Reference Enable, Enables external reference clock (OSCERCLK).
(kinetis.h)

Reading this register is easy (and indeed, it returns 0xa (== OSC_SC8P | OSC_SC2P) on T3.1):

Serial.printf("0x%x\r\n", OSC0_CR);


Edit: You can look at the ASM-Output of the compiler to see how to read this register in asm.
For software -interrupts, please take look at this thread: https://forum.pjrc.com/threads/27194-Allocation-of-software-triggered-interrupts
In post #6 is an example how to use them.
 
Last edited:
Latest news (good progress - not quite there yet)...

1) There are only two modes of program operation. It can only be either "Thread Mode" or "Handler Mode". "Handler Mode" is what I think was previously stated in earlier quotes as "System Mode".
2) All ASM produced for the ARMv7-M cores use THUMB instruction set (16bit/32bit mix) not ARM instruction set (which is all 32bit). Very important for the next item.
3) If you want to call an EXCEPTION from ASM (and probably from any other code too) in Teensyduino, you must add "1" to the address of the handler vector in the vector table. For example, if the handler is located at 490h then you must add a "1" = 491h and put this value into the correct slot in the vector table. This "1" will be extracted when the EXCEPTION is processed by the core and will be stuffed in the EPSR register T-bit(Bit[24]). This ensures interworking of THUMB with ARM. If you don't then the first instruction of the EXCEPTION Handler will cause an invalid state (see ARMv7 TRM Sect 3.9).
4) The "MOVS pc,lr" instruction mentioned previously is the WRONG WAY to exit from an EXCEPTION Handler routine with either Cortex-M3 or Cortex-M4 cores. You need to put a special value into the "pc" register which starts with FFFFFFxxh. The last part of the value (xx) determines in which "mode" the program will be in once it exits from the handler (Thread Mode or Handler Mode - you can change permanently to Handler Mode). It will always be in Handler Mode for the duration of the EXCEPTION routine, and hence will have "priviledged" access. You put this value into the pc by executing one of several instructions (one of which is bx with any register) and the core intercepts the "pc" value and decides what mode the program will be in on handler exit (see ARMv7 ARM B1.5.8).
5) It was stated in the literature that the SVCall EXCEPTION cannot be disabled. However, there is a suggestion (from others) that it may not have the correct priority if the values in PRIMASK or BASEPRI are not appropriate, though I have yet to explore this issue.

The GOOD NEWS is that I currently have an "SVC #0" call partly working. I wrote a Serial.print wrapper function in the sketch file that could be called from ASM to print stuff to the serial monitor from ASM. I got this working (after a struggle) and once it did work, then I could use this idea to test if the SVC instruction had actually started to enter my ASM handler routine. To my delight, it proved that it did by printing a known value out to the serial monitor, proving that at least the vector table was working properly.

The BAD NEWS is that my exit from the handler did not work as expected, so the LED stopped blinking (and a crash happened). I obviously need to do more work on the exit strategy. But at least this proves that I can change mode from "Thread" to "Handler", and that the vector table works fine, and the priority must be OK too (or else it would never have executed the first instructions in my handler. I hope my next post will be a fully working example :) !! I will read that 4006_5000h register one day...
 
Thanks Frank B and Paul - my post latest collided with your replies. The good thing about have a problem like this is what you learn from it. I do hope that others will learn ASM because it really teaches you what the hardware can actually do. I'm a big fan of Dr Robert Paz.
 
Given this code:

Code:
void setup() {
}

__attribute__((noinline))
uint8_t getOSC0(void){
   return OSC0_CR;
}   

void loop() {
  Serial.printf("0x%x\r\n", getOSC0() );
  delay(500);
}

You'll get this as output for getOSC0(void):
Code:
00000468 <_Z7[B]getOSC0[/B]v>:
     468:	4b01      	ldr	r3, [pc, #4]	; (470 <_Z7getOSC0v+0x8>)
     46a:	7818      	ldrb	r0, [r3, #0]
     46c:	4770      	bx	lr
     46e:	bf00      	nop
     470:	40065000 	.word	0x40065000

The whole .S is much larger, but the generated code for a single function is easy to find (in most cases)
 
Last edited:
Thankyou Frank. Your tips are appreciated. I'm very new to Arduino IDE and Teensy, but love its simplicity. The power of Teensy 3.1 is breathtaking. I come originally from the Intel world. My first MCU was National Semiconductors SC/MP (circa 1976?) and on into Intel 8080A... in those days you wrote your own RTOS until Intel brought out RMX80 and later RMX86. Well before some young whippersnapper called Gates got big ideas :) !!

After much perspiration getting SVC Handler to finally work yesterday (exit strategy sorted), I also learnt how to call Sketch C/C++ routines from ASM, and vice versa. Attached is a zip of my efforts for those who may like to learn more. My SVCall and handler is just one of the 16 vectors from 0 to 15. It is not held up as any example of good/elegant style :), but at least it does work.

But it turns out that priviledge was not the problem in the first place. When I put my ASM code to read the 4006_5000h register in the SVCHandler, it still crashes. My code is simply:

ldr r2, =0x40065000 //sets up the indirect pointer
ldr r0, [r2] //read into r0 what r2 is pointing at

This simple example crashes on this register, but did read (for example) memory at "=0x4004B014". That's why I thought it might be a priviledge issue. Your disassemble uses "ldrb" to make the register read - and then the penny dropped (again). The OSC0_CR is only a byte wide register and I was trying to read four bytes. As soon as I used "ldrb" instead, then it did not crash and then I too got a value of "A". Issue solved.

But at least I can now call routines from either world (ASM/C/C++). And I also learnt that "0xFFFFFFF9" is the only EXC_RETURN value that seems to work in SVCHandler (the others in the ARMv7M ARM doc (table B1.8) caused a crash (???). The documentation says value "0xFFFFFFF9" puts the MCU into THREAD MODE but using the MAIN STACK rather than the PROCESS STACK. This chimes well with Paul's comment that Teensyduino code leaves user programs running in "System Mode" (as he calls it). I guess "System Mode" really means THREAD MODE with PRIVILEDGE, rather than HANDLER MODE (and why 0xFFFFFFF9 works). I'm still not sure why "0xFFFFFFF1" as exit EXC_RETURN didn't work 0 as this also uses the MAIN STACK.

I also learnt that there is nothing special about the Arduino IDE Serial Monitor too. Once your code is downloaded, users can use "HyperTerminal" etc outside of the Arduino IDE to talk to Teensy 3.1.

One thing that did give me grief was not knowing how the bootloader (HalfKay) sits in the memory map and what action it has if any on the reset process. I note that Teensy 3.1 has a second little MCU (where I think HalfKay sits). When you have done a download of your sketch, is it the bootloader that does the transcribe of the vector table from FLASH to SRAM ?, or is it some other "preamble" code that gets bolted in front of a user sketch's "void Setup()" code as part of the hex download. It would be good to have a simple "User Guide" written to explain how precisely your code gets to actually run, so that a user knows what has been added in (I guess there is considerale "preamble" otherwise the USB port would not work). I'm willing to write it if I get help to answer queries.

I have yet to learn how to dissasemble a sketch. A tick box option in the IDE's preferences menu to create a "List File" (plain text) would be nice (dumped into the project folder). Yes, you can use the gcc in command mode, but that circumvents the simplicity of the IDE.
 

Attachments

  • SVCTest001.zip
    2.5 KB · Views: 99
When you have done a download of your sketch, is it the bootloader that does the transcribe of the vector table from FLASH to SRAM ?,

No, most definitely not. The bootloader tries to give you exactly the same situation as if you had somehow programmed your code into the flash memory with magic and then applied power to the chip. Well, it doesn't power cycle the MK20 chip, but it does use the physical RESET pin, which is the closest thing.

or is it some other "preamble" code that gets bolted in front of a user sketch's "void Setup()" code as part of the hex download.

Yes, there is. It's in mk20dx128.c. The code which copies the vector table is at line 523:

https://github.com/PaulStoffregen/cores/blob/master/teensy3/mk20dx128.c#L523


It would be good to have a simple "User Guide" written to explain how precisely your code gets to actually run,

The primary user level guides are written for people using Arduino, where these very low-level details are all handled automatically.
 
But at least I can now call routines from either world (ASM/C/C++). And I also learnt that "0xFFFFFFF9" is the only EXC_RETURN value that seems to work in SVCHandler (the others in the ARMv7M ARM doc (table B1.8) caused a crash (???). The documentation says value "0xFFFFFFF9" puts the MCU into THREAD MODE but using the MAIN STACK rather than the PROCESS STACK. This chimes well with Paul's comment that Teensyduino code leaves user programs running in "System Mode" (as he calls it). I guess "System Mode" really means THREAD MODE with PRIVILEDGE, rather than HANDLER MODE (and why 0xFFFFFFF9 works). I'm still not sure why "0xFFFFFFF1" as exit EXC_RETURN didn't work 0 as this also uses the MAIN STACK.

Teensyduino defaults to use the MSP not PSP stack pointer. You have to set the CONTROL bit to use the PSP but exceptions (ISR's) will automatically use the MSP even if in you are using THREAD_MODE (0xFFFFFFFD) for your functions/tasks. To me it sounds like you are trying to write a rtos or such, if so take a look at the ChibiOS source code for how they do their context switch everything your talking about has been done and its in that source code.
 
Status
Not open for further replies.
Back
Top