|
|
View previous topic :: View next topic |
Author |
Message |
canadidan
Joined: 13 Feb 2019 Posts: 24
|
[PIC24HJ256GP210] IVT Spacing / Redirection |
Posted: Tue Aug 13, 2019 3:03 pm |
|
|
Hello all, I'm hoping someone here can clarify this behaviour for me. It seems to be something compiler-specific that I can't find other reports of.
Note, this is inherited code, so when I call out the Problem below, someone discovered the workaround but it wasn't me.
Scenario
* PIC-C V4.106
* Bootloader application
* IVT redirection
Code: |
Program Memory Table
+---------------------+
| 0x0000 - RESET |
+---------------------+
| 0x0004 - IVT |
+---------------------+
| 0x0104 - AIVT |
+---------------------+
| 0x0504 - RDR |
+---------------------+
| 0x0800 - APP |
+---------------------+
| 0x26800 - BOOT |
+---------------------+
|
It's pretty simple:
Application IVT Table - Generated at 0x0504
Code: |
#BUILD (RESET=0x00800)
#BUILD (interrupt=0x0504)
|
Bootloader IVT Table - Redirection
Code: |
#define RDR 0x0504
#ROM 0x00004={RDR+00, 0x0000, RDR+4, 0x0000, ....}
|
When the interrupt is triggered, the actual IVT will point to the RDR table redirected value, and the RDR table is generated by the linker(?) to point to the correct location for the ISR.
Problem
Notice how the IVT redirection table increments by +4, and is double-spaced by padding with 0x0000. In the generated HEX file, indeed the generated RDR table has incremented by +4.
In the datasheet, the IVT is consecutive and increments by +2.
To illustrate this, see this example:
Example - U2RX
U2RX - UART 2 Receiver (IVT: 0x0050)
PIC-C Generated Code
Redirection IVT Table (Note: instruction 0x50 is byte 0xA0)
:1000A0009C050000A0050000A4050000A8050000B4
Generated IVT Table (Note: instruction 0x059C is byte 0x0B38)
:100B3000FFFFFF00FFFFFF00C613040000000000DE
MPLAB Generated Code
If IVT starts at 0x0504, UART 2 should be at 0x0550
Generated IVT Table (Note: instruction 0x0550 is byte 0x0AA0)
:100AA000C80800001A0900001A0900001A0900000D
Question
* Why does PIC-C increment the IVT by 4, and not 2?
* How does this work given the IVT is defined by datasheet/hardcoded?
* Does this mean the IVT is called early, and NOP's its way to the correct ISR by luck?
* Is my compiler just too old? |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19549
|
|
Posted: Wed Aug 14, 2019 1:52 am |
|
|
The reason for 4, is that the data sheet is talking _words_, not _bytes_.
They have a habit of switching nomenclature without warning...
The 'instruction space', always aligns on even 'byte' addresses and
goes up by two addresses in ROM, for each instruction. Each IVT entry
occupies four bytes, but two words.
Pull the Microchip reference:
"PIC24F Flash Program Memory".
Then look at figure 2-4 in this.
Then realise that the IVT, is a 24bit address, occupying 2 words each, but
as a byte address, this means it has to move by four addresses. |
|
|
canadidan
Joined: 13 Feb 2019 Posts: 24
|
|
Posted: Wed Aug 14, 2019 7:27 am |
|
|
I think I found the answer! I have the whole byte address (hex file) and word address (flash memory) concepts clear in my head. What I didn't understand is why the PIC-C redirected IVT was different than what I got MPLAB X. Now I understand:
* Using #BUILD (interrupt=0x0504) causes PIC-C to generate a table of GOTOs
* GOTOs are two instructions wide (+4 words)
* The actual IVT points to these GOTOs
* MPLAB (XC16) is only generating a normal IVT with addresses, not GOTO
* Addresses are only one instruction wide (+2 words)
This is how I figured it out
My project has two interrupts: Timer2, and U2RX.
PIC-C Code
Code: |
// IVT at 0x004
:1000380034050000380500003C05000040050000BC // Addr: 0x44 bytes, 0x22 words
:1000980094050000980500009C050000A0050000DC // Addr: 0xA0 bytes, 0x50 words
// Table at 0x504
:080A8000E01104000000000079 // Addr: 0xA80 bytes, 0x540 words
:080B3800F214040000000000AB // Addr: 0xB38 bytes, 0x59C words
|
The IVT points to GOTOs in the GOTO redirect table.
MPLAB X Code
Code: |
// Table at 0x504
:100A38001A0900001A0900001A090000FA08000043 // Addr: 0xA44 bytes, 0x522 words
:100A98001A0900001A090000C80800001A09000015 // Addr: 0xAA0 bytes, 0x550 words
|
This table is straight from the linker, and is not GOTOs. It is the actual IVT but placed later in memory. This won't work
Thanks for helping me dig deeper Ttelmah! |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1357
|
|
Posted: Wed Aug 14, 2019 11:24 am |
|
|
One note: In place of your manual ROM relocations of the ISRs, you can instead use a somewhat undocumented feature for PIC24s:
In your bootloader, instead of the #build() call to move your interrupts you can do:
Code: |
#INT_DEFAULT FAST
void isr(void)
{
jump_to_isr(0x0504);
}
|
And it will auto map the IVT for you. Then if you need an interrupt for the bootloader you can do something like:
Code: |
#INT_TIMER1 ALT
void timer1_isr(){}
|
After that, you just need to handle switching between alternate and normal interrupts using enable_interrupts(INTR_ALTERNATE) and enabled_interrupts(INTR_NORMAL). EDIT: Some current versions appear to have a bug where this doesn't work, so you may have to manually set the alternate bit and clear it instead.
In your application code, you still want the #build() call to map your ISRs though. The above is just for the bootloader
also note, for the application (not the bootloader), if you use #build(reset=XXX) it will also use a goto, so you are better off mapping your reset to right before your IVT: #build(reset=0x0500, interrupt=0x0504) and it should be at the start of a FLASH page.
CCS will handle placing updating that reset goto for you
Example bootloader for a PIC24:
Code: |
#include <24fj256ga406.h>
/* SET FUSES HERE, THE APPLICATION CANNOT SET THEM */
// User defined bootloader stuff
#define APP_START 0x0800
#define APP_IVT 0x0804
// Remaps IVT
#INT_DEFAULT FAST
void isr(void)
{
jump_to_isr(APP_IVT);
}
// Reserve application space so bootloader doesn't use it
#org APP_START, (getenv("PROGRAM_MEMORY")-1) {}
// Bootloader ISRS
#INT_TIMER1 ALT
void timer1_isr(){}
// compiler bug workaround
#BIT ALTIVT = getenv("BIT:ALTIVT")
// Bootloader main
void main(void)
{
// Setup interrupts for bootloader
ALTIVT = TRUE;
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
/* DO BOOTLOADER STUFF */
// Reset interrupts for application
disable_interrupts(GLOBAL);
disable_interrupts(INT_TIMER1);
ALTIVT = FALSE;
// Start application
goto_address(APP_START);
while(TRUE);
}
|
sample PIC24 application:
Code: |
#include <24fj256ga406.h>
#FUSES NONE
#define APP_START 0x0800
#define APP_IVT 0x0804
#build (reset = APP_START, interrupt = APP_IVT)
// reserve bootloader space so application doesn't use it
#org 0, (APP_START-1){}
// need another #org here for the last page in memory since the
// fuses reside here and you don't want to write to this page. I didn't
// take the time to work out the calculation...exercise for the reader
#INT_TIMER1
void timer1_isr(){}
void main(){
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
/* DO APPLICATION STUFF */
while(TRUE);
}
|
|
|
|
canadidan
Joined: 13 Feb 2019 Posts: 24
|
|
Posted: Wed Aug 14, 2019 1:00 pm |
|
|
Great example, thank you! Aside from the jump_to_isr() approach, the code I'm working with is similar, and uses ALTIVT for the bootloader and IVT for application.
However, I can clean up the organization a bit to map things at the start of a page.
Bootloader
Code: |
#include <ALT_24HJ256GP210.h>
#BUILD (ALT_INTERRUPT)
#DEFINE FIRMWARE_RESET_ADDRESS 0x400
// Fuses //
#org default
#org 0x26800, 0x2A7FF auto=0 default
void main(){
// stuff
}
|
Application
Code: |
#include <ALT_24HJ256GP210.h>
#BUILD (RESET=0x00400)
#BUILD (interrupt=0x0404)
#ORG 0x00000,0x003FF {}
void main(){
// stuff
}
|
|
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1357
|
|
Posted: Wed Aug 14, 2019 1:45 pm |
|
|
No problem at all
canadidan wrote: |
Application
Code: |
#include <ALT_24HJ256GP210.h>
#BUILD (RESET=0x00400)
#BUILD (interrupt=0x0404)
#ORG 0x00000,0x003FF {}
void main(){
// stuff
}
|
|
Don't forget you want
In the application header so it doesn't generate any code for the FUSES. Additionally, if you have a #use delay() statement, it should only have a "clock=" statement and not "internal=", etc. so that it doesn't generate any fuses.
EX:
Code: | #use delay(clock=8MHz) |
You also want a blank #org for the the last page in memory so that the application doesn't try to erase that page (it has the FUSES in it). A lot of bootloaders will throw that code out and error, but you don't want to rely on that for a couple of reasons:
1. Some bootloaders only ignore that page and don't generate an error or warning, which means you will potentially be referencing code that isn't there
2. Some bootloaders don't protect that page at all, so you would erase the fuses and the next time you cycle power things will go crazy. |
|
|
canadidan
Joined: 13 Feb 2019 Posts: 24
|
|
Posted: Wed Aug 14, 2019 2:57 pm |
|
|
My ignorance will show here, but aren't fuses located outside of the "usable memory" range?
0x00000 - RESET (GOTO 0x26800)
0x00004 - IVT
0x00104 - AIVT
0x00400 - APP GOTO
0x00404 - APP IVT
0x26800 - BOOTLOADER
0x2ABFF - END OF MEMORY
My understanding is that fuses are located in the 0xF00000 range, way outside (or is this a variable location the compiler sets?)
I suppose this is implementation specific, but my application "upgrade" code won't write or erase outside of 0x400 - 0x267FF (to protect bootloader and page 0). |
|
|
jeremiah
Joined: 20 Jul 2010 Posts: 1357
|
|
Posted: Wed Aug 14, 2019 6:42 pm |
|
|
canadidan wrote: | My ignorance will show here, but aren't fuses located outside of the "usable memory" range?
0x00000 - RESET (GOTO 0x26800)
0x00004 - IVT
0x00104 - AIVT
0x00400 - APP GOTO
0x00404 - APP IVT
0x26800 - BOOTLOADER
0x2ABFF - END OF MEMORY
My understanding is that fuses are located in the 0xF00000 range, way outside (or is this a variable location the compiler sets?)
I suppose this is implementation specific, but my application "upgrade" code won't write or erase outside of 0x400 - 0x267FF (to protect bootloader and page 0). |
IIRC, the fuses have two locations. You don't write your code to 0xF00000 or whatever your chips address is. You write them to the end of writable memory and the micro reads them at bootup and loads the other address with them (I think).
Either way, look at the bottom of your LST file and you'll see they are written to the bottom of writeable memory. As a matter of fact if you copy the address of
Code: |
getenv("PROGRAM_MEMORY")
|
and print it, that will be the location of the FUSES. And that location is on the last writeable page of memory, so you don't want to erase that by writing application code to the beginning of that page (which would require an erase to the page first) |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|