sábado, 30 de julho de 2016

Arduino + Bluetooth + Android (parte 3)

Android!

Já viu na internet um controle de drone via bluetooth, feito por iPhone? Esse vídeo ficou famoso pois o autor controlava um quadcóptero. Eu lembro dele pois quando ele foi divulgado os nossos aparelhos Android careciam de alguns hardwares interessantes, por exemplo, acelerômetro! (Os que tinham eram realmente caros).

Ok... fim da sessão naftalina. Certifique-se de que o SEU SMARTPHONE, tenha acelerômetro (se nao tiver, Ok, desenvolvi uma interface de dedo) :)

AppInventor

O MIT (isso mesmo Massachussets Institute of Technology) em parceria com a Google, desenvolveu um ambiente chamado AppInventor. Ele permite de forma gráfica e de layout simplificado desenvolver aplicativos para Android.

Quem já conhece Scratch, não terá problemas para  desenvolver nele. 

Já desenvolvo para Android há anos, mas uso o SDK. Isso toma mais tempo e mais linhas de código. No desafio que eu recebi, eu só tinha 2 dias para fazer a solução completa! Essa parte ficou no 2o dia.

Quer saber mais sobre o AppInventor? http://appinventor.mit.edu/explore/index-2.html

O design do aplicativo

Desenhar o aplicativo no AppInventor não é um bicho de 7 cabeças. Mas o que quero mostrar aqui, é como posso tirar proveito do mesmo design discutido nos artigos anteriores sem alterar uma virgula sequer.


Na tela acima temos o design do mesmo, os botões de direção, a seleção com conexão/desconexão do bluetooth, e no quadro abaixo does botões de ativação e comando de direção, a visualização dos registros do acelerômetro;

Conforme o balanço do acelerômetro, haverá o despacho do respectivo comando para os motores.

Abaixo do design da tela, no campo Non-visible componentes, tem os componentes responsáveis por conexão por bluetooth, leitura do acelerômetro, e um componente específico para mostrar os alertas do sistema.


O programa

Bom, não é minha intenção ensinar ninguém a programar, mas vou mostrar aqui uma parte interessante do programa que eu fiz. 


Esse são os trechos do programa em que comunico com o arduino via Bluetooth. Olha que estou enviando os mesmos comandos que especifiquei e foi testado.

Na parte da esquerda o comado do acelerômetro. Tem uma esperteza ali; dependendo do intensidade da inclinação (maior que 2, ou menor que -2) o respectivo comando é enviado pro bluetooth.

Pra quem nunca programou usando AppInventor, a programação é assim mesmo. Caixinha com caixinha, igual Lego... legal!

Eis aqui o link do resultado final https://www.youtube.com/watch?v=VqTXDF1rouc

Este programa está disponivel na App Store: https://play.google.com/store/apps/details?id=appinventor.ai_mauricio_marcosta.BTMotor

sexta-feira, 29 de julho de 2016

Arduino + Bluetooth + Android (parte 2)

Fez a parte 1? Arduino + Bluetooth + Android (parte 1)

Se não fez, estudou ou visitou, faça-o. Meus artigos seqüenciais são extremamente dependentes um do outro.

--//--

Para estudar essa parte 2, precisamos lançar mão daqueles módulos Bluetooth disponibilizados nos sites tipo MercadoLivre, AliExpress, DX, entre outros (se bobear, tem alguém ou alguma casa de eletrônica que os vende)... arrume um! Rápido!

Esses módulos são especializações dos chips de suporte a bluetooth que disponibilizam uma comunicação serial (nível TTL). E se for olhar pelo stack de protocolos bluetooth, ele só implementa o profile SPP (Serial).

Bom isso é mais do que suficiente!

O aspecto dele é simples, e assim:
Ele já tem um conversor de nível embutido se ele vier montado na plaquinha azul, Caso contrário ele é 3,3V e tem que tomar muito cuidado ao conectar ao arduino. Procure os que já tem a plaquinha azul.

Veja que ele já tem as marcas de VCC, GND, TX, RX... isso facilita bastante para conectar ao arduino. O esquema básico é plugar o RXD do Bluetooth, to TXD do arduino, o TXD do bluetooth no RXD do arduino e o VCC/GND nos seus respectivos portos.

Um ledzinho na plaquinha irá ficar piscando. Isso significa que ninguém está conectado a ele. Quando fizer o pareamento e a conexão, o led ficará aceso, estático.

Para conectá-lo ao arduino obedeça ao seguinte esquema.
Veja que não fiz nenhuma alteração ao circuito do post anterior. Só adicionei o circuito do bluetooth! E nem precisa alterar o programa da etapa anterior!!!

ATENÇÃO: Em algumas placas, por exemplo o meu HC06, o setup RX/TX do Bluetooth parece invertido. Portanto siga o esquema:
ArduinoVCCVCCBluetooth
GNDGND
TXRXD
RXTXD



Ligue o Arduino. O led do modulo bluetooth deverá permanecer piscando.

Conectando-se ao Bluetooth!

Não é nenhum bicho de sete cabeças mas vai exigir alguma disciplina. Não vou descrever como se faz mas certifique que o seu computador que tenha o bluetooth nativo (se o seu note ou computador não tem, você terá que gastar uns $$$ para comprar um adaptador bluetooth)

Enquanto o modulo bluetooth pisca, ele fica sinalizando para os sistemas ao redor: "Ei, estou aqui e me chamo X"; Esse X pode ser LINVOR se for um módulo HC-05, ou HC-06 (para modulos desse naipe);

Antes de mais nada você precisa registrar esse módulo como um "pareado" no sistema.  A senha para o pareamento é "0000" ou "1234".

Habilite o bluetooth, e espere ele "descobrir" algum dispositivo com o nome acima. Conecte-se a ele, informe a senha!

Pareou? Tudo tranquilo? O Windows vai criar uma porta serial VIRTUAL. Deve ser alguma COM3, COM4, COM5, COM8, etc...

Usando o programa terminal (PuTTY ou Termite) procure por essa nova COM e conecte usando os parametros: 9600, 8bits, N (sem paridade) 1 stop bit (ou simplificando 9600,8N1);

Assim que conectar-se, o led irá ficar acesão. Voce está ali, pronto para mandar os comandos.

Mande lá os comandos ESDXZ[espaço]M... divirta-se. Você  já está 100% wireless.


sábado, 23 de julho de 2016

Arduino + Bluetooth + Android (parte 1)

Foi se o tempo em que fazer piscar o led no Arduino já bastasse para entrar em êxtase. Peguei essa fase nos primeiros Arduinos. A dispensa daqueles programadores e fuse bits complicados já era coisa do passado.

Hoje temos à nossa disposição mais do que uma plataforma de desenvolvimento de hardware, mas uma possibilidade de inclusão dos dispositivos ao mundo IoT (Internet Of Things)

--//--

Por estes dias, recebi um desafio de um professor, amigo meu. Ensinar para uma turma de robótica os mecanismos de comunicação Bluetooth.

Mas não era um pequeno tutorial. Abrangeria demonstração com o Arduino e um controle básico usando o Android!

Challenge Accepted!

--//--

Quem já acompanhou em tópicos anteriores, montei há uns 2 ou 3 anos atrás um pequeno carro motorizado (kit da Tamyia) com um driver L293  e um outro usando o poderoso L298. O projeto do 298 está por aqui. Mas para não confundir ninguém resolvi simplificar o circuito simulando parte dos comandos usando LEDs.

Então vamos lá, o circuito base é o representado ao lado no breadboard. (Na demonstração com o arduino usei um shield, mas o esquema de conexão é o MESMO!)

O programa

Antes de qualquer coisa, precisamos definir os requisitos básicos do firmware. Vamos pensar como se estivéssemos controlando um motor. Linha para ativar o motor, linha para definir a direção conforme quadro abaixo:

LedItem"0""1"
VermelhoAtivaçãoInativoAtivo
VerdeRotaçãohorárioanti-horário

No circuito apresentado, então temos 2 conjuntos de led Verde+Vermelho. Cada um significa um motor!

E como comandarei esses "motores"? Bem, vamos aproveitar a linha serial do Arduino e utilizar um programa terminal (Termite/PuTTY) para testar o programa. Agora definirei como fazer a interação.

A minha sugestão é usar as letras das teclas para dar ações. Quem jogou usando o teclado do computador, sabe o significado das teclas E,S,D,X. 

E, seria como mandar o motor pra frente, X para trás. S e D, fazem as vistas de esquerda e direita. Poderia usar as setas? poderia, mas o teclado do computador envia códigos complexos quando são as setas. Então, prefiro ficar no simples...

As outras teclas são: M e espaço: ativa/desativa o motor. E por fim "Z", zera o seu estado.

Simples?

--//--
Não. Abstraia... abstraia. Não o fiz com motores porque não tive o controlador em mãos, Ok. Mas o principio é o mesmo. Lembre-se que temos 4 leds, 2 verdes, 2 vermelhos. Cada um fazendo como se fosse um estado do motor.

Para girar um carro de 2 motores, podemos usar um artificio de girar motores em contraposição.Isto é, se girarmos um motor para um sentido e outro no sentido contrário, o conjunto todo irá girar para.

Nesse esquema teremos a seguinte indicação dos leds

Fácil! :) Lembre-se, os vermelhos estão acionados, (M ou espaço, alternam seu estado); led verde escuro, desativado; verde claro, ligado.

Vamos pro nosso primeiro algoritmo! Implemente o circuito acima e transfira o programa abaixo para o Arduino (Edite usando ArduinoIDE, compile e transfira).


// Este sketch adiciona porta serial e o controle de motores usando um terminal 
//    E       - posiciona o motor à frente   m12=m22=0;
//  S         - posiciona o motor à esquerda m12=1; m22=0
//      D     - posiciona o motor à direita  m12=0; m22=1
//    X       - posiciona o motor à ré       m12=m22=1;
// [espaço]M  - Ativa/desativa o motor       m11=m21=1/0
//  Z         - inicializa                   mXY=0;


// Definição dos motores 
// mXY onde:
// X=motor (1 ou 2) 
// Y=direção (0 FW; 1 BW)
// usando os pinos 8,9,10 e 11 do arduino
int m11=8;
int m12=9;

int m21=11;
int m22=10;

// armazena o estado do motor
int motor=0; // off

void testaLed() {
  //testa os leds...
  digitalWrite(m11,HIGH);
  digitalWrite(m12,LOW);
  digitalWrite(m21,HIGH);
  digitalWrite(m22,LOW);
  delay(500);
  digitalWrite(m11,LOW);
  digitalWrite(m12,HIGH);
  digitalWrite(m21,LOW);
  digitalWrite(m22,HIGH);
  delay(500);
}


void setup() {
  //inicia os pinos de controle do motor
  pinMode(m11,OUTPUT);
  pinMode(m12,OUTPUT);
  pinMode(m21,OUTPUT);
  pinMode(m22,OUTPUT);

  Serial.begin(9600);

  // comente a linha abaixo se realmente estiver
  // usando um motor.
  testaLed();

  // motor desligado e registros zerados
  motor=0;
  digitalWrite(m11,LOW);
  digitalWrite(m12,LOW);
  digitalWrite(m21,LOW);
  digitalWrite(m22,LOW);
}


void loop() {
  // le instrução da porta serial
  while(Serial.available()>0) {
    char c=Serial.read();
    switch(c) {
      case 'Z': // zera o motor (desliga)
      case 'z':
        motor=0;
        digitalWrite(m11,LOW);
        digitalWrite(m12,LOW);
        digitalWrite(m21,LOW);
        digitalWrite(m22,LOW);
        break;
      case 'M': // alterna o estado do motor on/off
      case ' ': // espaço:
        motor=!motor; // inverte o estado do motor
        digitalWrite(m11,motor?HIGH:LOW);
        digitalWrite(m21,motor?HIGH:LOW);
        break;
      case 'E': // pra frente
      case 'e':
        digitalWrite(m12,LOW);
        digitalWrite(m22,LOW);
        break;
      case 'X': // pra tras
      case 'x':
        digitalWrite(m12,HIGH);
        digitalWrite(m22,HIGH);
        break;
      case 'S': // gira à esquerda
      case 's':
        digitalWrite(m12,HIGH);
        digitalWrite(m22,LOW);
        break;
      case 'D': // gira à direita
      case 'd':
        digitalWrite(m12,LOW);
        digitalWrite(m22,HIGH);
        break;
    }
  }
}


Transferiu para o Arduino? Então conecte-se a ele, usando um programa de terminal ou até mesmo o Monitor de Porta Serial (no Arduino IDE), mande os comandos E,S,X,D,M,Z, espaço para compreender o seu funcionamento!

Na parte 2 iremos fazer o mesmo, só que via Bluetooth. Se ainda nao tem um modulo, arrume um, para nao perder o trem da "estória".

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í!