terça-feira, 2 de julho de 2013

SDCC: Relocação de Código - PIC (Parte 2)

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

                                           S_blink__isr_reset code 0X000000
                                           _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