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