Relocando com #pragma code
Lendo o confuso manual do SDCC encontrei suporte a uma diretiva de compilação chamada #pragma code.
A sintaxe é simples:
#pragma code nome_da_função endereço
Exemplo:
#pragma code boot 0x800
...
void boot() {
...
}
Essa diretiva instrui o compilador a colocar a função "boot()" no endereço 0x800. Vamos ao nosso programa blink novamente para ver como funciona.
#include "pic18fregs.h"
#include "delay.h"
#pragma code init 0x800
#pragma config OSC=HS, PWRT=OFF, BOR=OFF, WDT=OFF, CCP2MUX=OFF, LVP=OFF, CP0=OFF, CP1=OFF, CP2=OFF, CP3=OFF
void init() {
 PORTC=0x00;
}
int main(){
 TRISB=0x7F;
 while(1) {
   PORTB=(PORTB^0x80);
 delay10ktcy(500);
 }
}
Ao compilarmos este programa teremos no assembly (veja .lst)  o seguinte trecho:
Para compilar: sdcc --use-non-free -p18f252 -mpic16 blink.c
                                           S_blink__init code 0X000800
                                           _init:
                                           ; .line 8; blink.c void init() {
000800   cfd9     movff   0xfd9, 0xfe5      MOVFF FSR2L, POSTDEC1
000802   ffe5
000804   cfe1     movff   0xfe1, 0xfd9      MOVFF FSR1L, FSR2L
000806   ffd9
                                           ; .line 9; blink.c PORTC=0x00;
000808   6a82     clrf    0x82, 0           CLRF _PORTC
00080a   cfe4     movff   0xfe4, 0xfd9      MOVFF PREINC1, FSR2L
00080c   ffd9
00080e   0012     return  0                 RETURN 
Fantástico não?!! :)  
Não! :) isso funciona para qualquer função, menos main(). Coisas internas do SDCC. Então usar o #pragma code não funciona direito assim nu e cru.
__interrupt 0
Lembra que eu falei em SDCC: Interrupções no PIC ? Falei dos vetores 1 e 2... mas não contei sobre o vetor 0.
Uma função declarada como __interrupt 0, irá tomar espaço na posição 0x000 da memória de código. É o vetor de RESET! Isso é bem escondido e para usar esse recurso temos que tomar mais instruções de compilação. Vamos alterar o nosso programa blink...
#include "pic18fregs.h"
#include "delay.h"
#pragma config OSC=HS, PWRT=OFF, BOR=OFF, WDT=OFF, CCP2MUX=OFF, LVP=OFF, CP0=OFF, CP1=OFF, CP2=OFF, CP3=OFF
int main();
void isr_reset() __interrupt 0 {
 main();
}
int main(){
 TRISB=0x7F;
 while(1) {
   PORTB=(PORTB^0x80);
 delay10ktcy(500);
 }
}
Vamos por partes... a primeira declaração (em azul) é uma formalidade. Estou declarando o protótipo da função main() que será chamada pelo isr_reset(). Como a função é construida depois da sua chamada, a gente simplesmente diz para o compilador que existe uma função main em algum lugar... o compilador cuida do resto.
A segunda declaração é onde vamos criar um instrução GOTO pulando para main, na posição 0x000 do microcontrolador. Que é justamente o entry point depois do RESET/STARTUP do microcontrolador.
Para compilar precisaremos de 1 diretiva nova de compilação: --no-crt.
Essa diretiva instrui o linker para NÃO usar a runtime padrão do SDCC. Com isso o controle de startup do programa no microcontrolador fica inteiramente a cargo do PROGRAMADOR!!!
Compilando o programa: sdcc --use-non-free --no-crt -p18f252 -mpic16 blink.c
Olha que interessante no .lst:
1) Entry point do programa:
                                           S_blink_ivec_0x0_isr_reset code 0X000000
                                           ivec_0x0_isr_reset:
000000   ef3f     goto    0x7e              GOTO _isr_reset
000002   f000
Criou no endereço 0x000, a chamada para isr_reset()! Bingo!
Mais para baixo, temos o corpo da função isr_reset(), que chama main() (omiti os preâmbulos para salvamento e restauro de contexto que podem ser omitidos se usarmos __naked - lembre-se do bug do RETFIE)
                                           _isr_reset:
                                           ; .line 8; blink.c void isr_reset() __interrupt 0 {
00007e   cfd8     movff   0xfd8, 0xfe5      MOVFF STATUS, POSTDEC1
000080   ffe5
000082   cfe0     movff   0xfe0, 0xfe5      MOVFF BSR, POSTDEC1
...
0000a6   ffd9
                                           ; .line 9; blink.c main();
0000a8   ec6a     call    0xd4, 0           CALL _main
0000aa   f000
0000ac   cfe4     movff   0xfe4, 0xfd9      MOVFF PREINC1, FSR2L
...
0000ce   cfe4     movff   0xfe4, 0xfd8      MOVFF PREINC1, STATUS
0000d0   ffd8
0000d2   0010     retfie  0                 RETFIE 
BINGO!!! :) agora sim. Tudo parece estar indo bem para o bootloader! Mas descobri mais um bug do SDCC:
BUG: Mesmo informando --no-crt o SDCC insiste em criar as rotinas de inicialização do sdcc. Veja no .lst as entradas:
                                           __sdcc_gsinit_startup:
                                           ; I code from now on!
000004   ef04     goto    0x8               goto __sdcc_program_startup
000006   f000
                                           ; ; Starting pCode block
                                           __sdcc_program_startup:
000008   ec6a     call    0xd4, 0           CALL _main
00000a   f000
Vamos lá! :) Estamos chegando perto... Nesse contexto agora sim podemos relocar main() (mas não com esse nome, pois o SDCC não deixa relocar main com #pragma code - Mais um bug?)
Como acertamos o nosso entry point de programa, usando vector 0, o main() pode se chamar qualquer coisa, com isso consegue-se relocar o código para qualquer endereço de memória.
Novo blink.c:
#include "pic18fregs.h"
#include "delay.h"
#pragma config OSC=HS, PWRT=OFF, BOR=OFF, WDT=OFF, CCP2MUX=OFF, LVP=OFF, CP0=OFF, CP1=OFF, CP2=OFF, CP3=OFF
#pragma code bootstrap 0x200
int bootstrap();
void isr_reset() __naked __interrupt 0 {
 bootstrap();
}
int bootstrap(){
 TRISB=0x7F;
 while(1) {
   PORTB=(PORTB^0x80);
 delay10ktcy(500);
 }
} 
Simplesmente renomeei "main" para "bootstrap". Adicionei o #pragma code, mandando o bootstrap para o endereço 0x200... veja agora como ficou o .lst:
Compile com: sdcc --use-non-free --no-crt -p18f252 -mpic16  blink.c
_isr_reset:
; .line 9; blink.c bootstrap();
000000 ec00 call 0x200, 0 CALL _bootstrap
000002 f001
...
                                           S_blink__bootstrap code 0X000200
                                           _bootstrap:
                                           ; .line 12; blink.c int bootstrap(){
000200   cfd9     movff   0xfd9, 0xfe5      MOVFF FSR2L, POSTDEC1
000202   ffe5
000204   cfe1     movff   0xfe1, 0xfd9      MOVFF FSR1L, FSR2L
000206   ffd9
...
Em azul, o nosso vetor de reset, chamando bootstrap(), em verde, o nosso bootstrap() relocado para o endereço 0x200 conforme eu havia instruído! :)
Eheheheh!  E detalhe. Se você está seguindo o que estou dizendo, observe que no .lst NÃO EXISTE aquele código lixo de inicialização do SDCC (sdcc_gsinit_startup...) portanto o corpo é mais enxuto. (essa tranqueira toda gerou um HEX de 32bytes somente).
Será que esse código funciona? Testei no MPLAB SIM e funcionou perfeitamente! 
O caminho até o bootloader
Falta desacoplar o código e criar 2 modelos distintos:
1) o bootloader - que é o firmware que rodará no espaço 0x000 a 0x1FF. Conforme manual.
2) o programa em si, que é o que precisa rodar a partir do endereço 0x200.
Até agora vimos técnicas para relocar o código para outros endereços e como criar o próprio "entry point" usando vector 0. Mas tudo isso está junto.
Há 2 tópicos que preciso abordar ainda antes de iniciar o código do bootloader:
1) Delegando controle via Ponteiros para funções!
2) Relocação de código via .LKR (Linker script)
Até a próxima!
Nenhum comentário:
Postar um comentário