segunda-feira, 29 de julho de 2013

YABLO: Yet Another Bootloader (English Version)

My vacations has gone after about 1¹/2 months working hard on my bootloader. Finally, I got it done! Well, I decided to write this article entirely in English. Just to match with the concept of documentation style I'm producing in code.google.com... I hope this won't be an issue for you!

In my latest articles I describe some models to work with memory relocation... so here's the result of that blah blah blah!

Yablo

There's tons of bootloaders abroad. But seriously, I was tired to make LED's blinking all the time, and definitely I took this challenge. Would you dare?

Yablo is an acronym for Yet Another BootLOader... 

Differently from many other bootloaders:
- It's not short (I was fighting with compiler to have it less than 1024 bytes). It has about 0x39A bytes! :)
- It's not written in Assembly (but a mix of C with assembly). 
- It's written in C (not entirely, but a mix of Assembly with C :) )
- It MUST accept data coming from HyperTerminal or any other TTY consoles - yeah... copy and paste the .HEX content!
- It is written entirely in a FREE C Compiler  - so by license (GPL), Yablo inherits GPL, so is free!

By now, it runs in PIC18F252... I know it's easy to port to any other PIC18F252 family. :) but the code was tested in PIC18F252. Keep your eyes in the project site, soon or later we'll get other PIC's there too!

Differently of many source codes I've seen, Yablo was written to be easy to understand the internals of a bootloader. IF you read and understood my 3 articles "SDCC: Relocação de Código - PIC", soon or later a project like this was bound to appear. Knowledge is taught/learned only if you want to transform it.

Inside the source code, there's an implementation that I love and the fundamentals of it is the implementation of a Finite Automata, you know? Just to remember, FA is the kind of processing based on inputs and states... the core of compilers, interpreters, pattern recognition engines, etc... Definitely is an elegant solution (maybe not the most notable code saver, but it is simple, data saver and fast).

From theory to practice: Yablo Memory Structure

I won't spend time justifying the "why?". Yablo was designed firstly to fit in only 512bytes (1 page), but the code started growing beyond this limit... naturally :) Then the goal was set to not pass 1024bytes (2pages).

The boot block occupies the memory from 0x000 to 0x3FF. The interrupt vectors are relocated as follows:
0x000 - jump to bootstrap() function, the bootloader function, will branch to 0x400 when it finishes!
0x008 - BRA 0x408
0x018 - BRA 0x418
0x01a..0x3FF bootloader functions
0x400 - call to user main() function 
0x408 - user ISR - High vector (relocated)
0x418 - user ISR - Low vector (relocated)
0x4??... 0x7FFF - USER PROGRAM SPACE!

The rest is kept the same:
0x30xxyy - config bits
0xF0xxyy - EEPROM data

To allow, the user program to fit in this memory model, a specifik linker script (LKR) must be used. So in this LKR, this region will be protected. And to the SDCC, the new location of interrupt vectors must be informed using the flags --ivt-loc.  Pretty simple! :)

See Yablo's wiki at GoogleCode: https://code.google.com/p/yablo/wiki/Yablo

Fuses... Do I need fuses?

Yes and no. Is very important to understand, when your microcontroller carries an embedded code like that, some fuses were programmed. So NO, is not a good idea to change that. For example, if your bootloader is for a crystal based clock source, there's no reason to change it to RC Osc... Avoid such things. Also, in fuses we have the pages locked or not... don't mess the things otherwise your program won't work. And maybe the bootloader can't boot! 

Yablo and PIColino

One is born to another. Of course, generic hardwares deserve generic firmwares. And in general terms, its fantastic! You can adapt them easily to your needs.

PIColino (now) is a PIC18F252I/P hardware with RS232. Arduino "quasi" compatible shield connectors. The firmware is now targetted to this processor. I've heard, it runs smoothly in PIC18F2520 (the upgrade of 252). I'm waiting my 2550's to arrive in order to see how does it fit in.


sexta-feira, 5 de julho de 2013

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

Ainda na saga de construir um bootloader para o Picolino (http://mmc-zaap.blogspot.com.br/2013/06/picolino-development-board-para-pic_26.html). Hoje vou demonstrar os "internals" de uma técnica um tanto avançada para invocação de rotinas baseados em outros espaços de endereços.

Imagine o leitor o seguinte: Temos um programa, chamado bootloader. E além dele, um outro que é o programa do usuário. Lembro-lhe de que os ciclos de desenvolvimentos são extremamente diferentes e independentes. Portanto, são programas independentes. E como o bootloader sabe como chamar o código do usuário?

Ponteiros para funções

Até o presente momento não "chutei o balde". Estou tentando o máximo possivel ficar somente circulando entre o C e o que ele puder me prover! (com uma linha de código em assembler mato esse capitulo - aí fica sem graça!)

Quando eu fiz escola técnica (lá em 1990) sempre ouvi dizer que em C o poder da linguagem está em ponteiros. E mantenho esse princípio. Feliz daquele que souber trabalhar com essa estrutura de dados!

Um ponteiro de funções cria uma referência "callable" (você não vai encontrar descrito assim nos livros). Essa referência é um endereço de memória, o qual eu invoco como se fosse uma função... a sintaxe é assim

void (*user_init)()=0x200;

No caso acima, eu declarei um ponteiro para uma função que não recebe parâmetro algum (void) e também não retorna nada (void). Tendo o seu endereço de inicio a posição 0x200.

Para chamar é a coisa mais simples:

(*user_init)();

Pronto! O programa irá delegar controle para o que estiver em 0x200. 

O que o compilador gera? Bom, para isso teremos que analisar o .lst novamente. 

Primeiramente, ele irá declarar um segmento para manter o endereço declarado na inicialização:
                                                idata
                                           _boot        db      0x00, 0x02, 0x00

Mas no corpo da função MAIN, veja o que ele faz:
                                           _main:
                                           ;    .line   6; blptf.c      (*boot)();
000000   cff2     movff   0xff2, 0xfe5          MOVFF   INTCON, POSTDEC1
000002   ffe5
000004   9ef2     bcf     0xf2, 0x7, 0          BCF     INTCON, 7
000006   0005     push                          PUSH
000008   0e26     movlw   0x26                  MOVLW   LOW(_00107_DS_)
00000a   6efd     movwf   0xfd, 0               MOVWF   TOSL
00000c   0e00     movlw   0                     MOVLW   HIGH(_00107_DS_)
00000e   6efe     movwf   0xfe, 0               MOVWF   TOSH
000010   0e00     movlw   0                     MOVLW   UPPER(_00107_DS_)
000012   6eff     movwf   0xff, 0               MOVWF   TOSU
000014   bee4     btfsc   0xe4, 0x7, 0          BTFSC   PREINC1, 7
000016   8ef2     bsf     0xf2, 0x7, 0          BSF     INTCON, 7
000018   c082     movff   0x82, 0xffb           MOVFF   (_boot + 2), PCLATU
00001a   fffb
00001c   c081     movff   0x81, 0xffa           MOVFF   (_boot + 1), PCLATH
00001e   fffa
000020   0100     movlb   0                     BANKSEL _boot
000022   5180     movf    0x80, 0, 0x1          MOVF    _boot, W, B
000024   6ef9     movwf   0xf9, 0               MOVWF   PCL
                                           _00107_DS_:
                                           _00105_DS_:
000026   0012     return  0                     RETURN


Marquei a parte do código que nos interessa. Ele lança mão dos registros PCLATU, PCLATH e PCL. Que fazem a transferência de controle (branch) de forma calculada! :)

Matando a técnica

Para um bootloader, isso é muito grande. A solução em C puro é fantástica. Mas não é limpa em termos de código assembler. Como eu disse em uma linha de assembly consigo matar isso:

Em vez de chamar (*user_init)(), basta no lugar colocar a seguinte instrução:

__asm BRA 0x200 __endasm;

E está feito! O código chega a emagrecer 30 bytes!

--//-- 

Estamos chegando lá. Nesse interim, trabalhei em 3 frentes:
1) criação do bootloader pro Picolino - EM C+ASM (um mínimo de ASM), ocupando uns 400bytes +/-
2) criação do ambiente de desenvolvimento para gerar códigos suporados pelo bootloader do Picolino (será abordado em breve)
3) criação do programa que lê o .HEX do usuário e grava no Picolino usando o bootloader.

--//--

Esse malabarismo todo tem objetivos:
1) mostrar pro leitor o quão é complexo desenvolver uma solução que pode vir a facilitar.
2) ensinar ao leitor, técnicas avançadas de programação.
3) documentar de forma didática a minha saga no desenvolvimento de sistemas micrcontrolados.

Próximos posts vou colocar + circuitos! É para relaxar!

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!

segunda-feira, 1 de julho de 2013

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

Vamos voltar ao nosso assunto de desenvolver um bootloader em C para PIC18F. (Apesar de ser um assunto batido, eu prefiro subir o Everest a ver fotos da paisagens de quem já foi lá! Entendeu a analogia?)

O SDCC pobremente suporta relocação de código. Ele traz os switches --ivt-loc e --code-loc (endereço das tabelas de vetores de interrupções e código) mas eles são ineficientes, incapazes, moribundos!

Recentemente descobri uma diretiva de compilação (#pragma) que pareceu ser promissor e tem feito o que eu preciso. Mas funciona de forma pobre, pois não consigo relocar o start point default do programa (main) para o ponto que eu preciso... então vamos para os artifícios.

Entendendo a Relocação

Quando o sistema possui um pré-inicializador, que pode ser um bootloader. O programa do usuário não precisa assumir o endereço absoluto. E para que não acabe apagando o bootloader, o seu código normalmente é adicionado no final daquele setor.

Os compiladores normalmente irão gerar seus códigos executáveis almejando o endereçamento padrão e farão a sua linkedição com as bibliotecas de inicialização necessários (crt, c0t, c0rt...).

No caso da relocação, o compilador terá que gerar nova origem (ORG) e todos os branches, jumps, goto's, skip tests, memory access deverão observar esse novo endereço e calcular os seus "saltos" baseado nessa nova origem.

Na compilação, um dos arquivos gerados é o .LST. Ele possui a listagem em assembly, com a referencia de endereço de cada linha de código.

Vamos pegar o nosso programinha de blink!
#define __18F252
#include "pic18fregs.h"
#pragma config OSC=HS, OSCS=OFF, PWRT=OFF, BOR=OFF, WDT=OFF, CCP2MUX=OFF, LVP=OFF, CP0=OFF, CP1=OFF, CP2=OFF, CP3=OFF
int main()
{
TRISB=0xFD;
while(1) {
PORTB=(PORTB^0x02);
}
}

Veja o código da função "main()" sem relocação - in natura.

                                           ; ; Starting pCode block
                                           S_blink__main code
                                           _main:
                                           ; .line 20; blink.c TRISB=0xFD;
00000a   0efd     movlw   0xfd             MOVLW 0xfd
00000c   6e93     movwf   0x93, 0           MOVWF _TRISB
                                           _00106_DS_:
                                           ; .line 22; blink.c PORTB=(PORTB^0x02);
00000e   7281     btg     0x81, 0x1, 0     BTG _PORTB, 1
000010   d7fe     bra     0xe               BRA _00106_DS_
000012   0012     return  0                 RETURN

No código acima, eu removi os preâmbulos de inicialização CRT/C0T do sdcc (sdcc_gsinit...). Veja que a função main, começa no endereço 0x0a; o loop e o branch (BRA) apontam para 0x0e;
O label __0106_DS_ marca o inicio e fim do loop "while (1)..."

Vou relocar o código para o endereço 0x800

                                           ; ; Starting pCode block
                                           S_blink___main code 0X000800
                                           __main:
                                           ; .line 18; blink.c int _main()
000800   cfd9     movff   0xfd9, 0xfe5     MOVFF FSR2L, POSTDEC1
000802   ffe5
000804   cfe1     movff   0xfe1, 0xfd9     MOVFF FSR1L, FSR2L
000806   ffd9
                                           ; .line 20; blink.c TRISB=0xFD;
000808   0efd     movlw   0xfd             MOVLW 0xfd
00080a   6e93     movwf   0x93, 0           MOVWF _TRISB
                                           _00106_DS_:
                                           ; .line 22; blink.c PORTB=(PORTB^0x02);
00080c   7281     btg     0x81, 0x1, 0     BTG _PORTB, 1
00080e   d7fe     bra     0x80c             BRA _00106_DS_
000810   cfe4     movff   0xfe4, 0xfd9     MOVFF PREINC1, FSR2L
000812   ffd9
000814   0012     return  0                 RETURN

Eu marquei em amarelo, as referências de endereços. Veja que o compilador teve que relocar o código. Isto é, atribuir novos endereços e corrigir os saltos para a nova faixa.

Problemas à vista

Estou usando o SDCC 3.3. Portanto o que eu disser aqui, é somente associado ao SDCC 3.3 (não testei em outras versões).

Como eu disse, o suporte de relocação de código para PIC no SDCC é pobre. Com muita pesquisa você vai cair nas seguintes dicas:
1) --code-loc
2) --ivt-loc
3) usar #pragma code
4) alterar o .lkr do gputils

Cada uma delas tem a sua particularidade.

--code-loc

--code-loc não faz o que diz fazer. Ele relocaria o código para o endereço desejado. Mas isso não ocorre. NUNCA! :( (you boor!)
(sorry, SDCC team, --code-loc is would never be in PIC)


--ivt-loc

--ivt-loc faz o que tem de fazer, mas é somente para tabelas de vetores. Se você tiver alguma função tipo __interrupt. Ele irá relocar o código para os endereços de vetores a partir do ponto informado.

Adicione o trecho de código abaixo ao nosso blink.c

void isr_high()  __critical __interrupt 1 {
PORTA=!PORTA;
}

compile-o
sdcc --use-non-free -p18f252 -mpic16 blink.c

Por fim abra o blink.lst e inspecione o seu conteúdo...
                                           S_blink__isr_high code 0X000008
                                           _isr_high:
                                           ; .line 15; blink.c PORTA=!PORTA;
000008   5080     movf    0x80, 0, 0       MOVF _PORTA, W
00000a   80d8     bsf     0xd8, 0, 0       BSF STATUS, 0
00000c   66e8     tstfsz  0xe8, 0           TSTFSZ WREG
00000e   90d8     bcf     0xd8, 0, 0       BCF STATUS, 0
000010   6a80     clrf    0x80, 0           CLRF _PORTA
000012   3680     rlcf    0x80, 0x1, 0     RLCF _PORTA, F

Agora compile com essa linha de comando:

sdcc --use-non-free -p18f252 -mpic16 --ivt-loc=0x200 blink.c 

O código será relocado:

_isr_high:
                                           ; .line 15; blink.c PORTA=!PORTA;
000208   5080     movf    0x80, 0, 0       MOVF _PORTA, W
00020a   80d8     bsf     0xd8, 0, 0       BSF STATUS, 0
00020c   66e8     tstfsz  0xe8, 0           TSTFSZ WREG
00020e   90d8     bcf     0xd8, 0, 0       BCF STATUS, 0
000210   6a80     clrf    0x80, 0           CLRF _PORTA
000212   3680     rlcf    0x80, 0x1, 0     RLCF _PORTA, F

I FOUND A BUG! SDCC não está gerando RETFIE (return from Interrupt) no final das ISR's quando se usa __naked!  Bom, nada que um __asm RETFIE __endasm; não resolva. Mas fica o alerta!
o --ivt-loc não reloca o main... então falta somente essa parte.

--//--
Nos próximos artigos vamos explorar o #pragma e o .lkr! Tem coisa boa vindo aí!