sexta-feira, 13 de novembro de 2015

Tornando o programa mais portável com linker script

Até agora, nosso programa tinha particularidades para o processador da placa STM32F0Discovery. As principais delas dizem respeito a mapeamento e tamanho das memórias flash e ram. A forma mais prática de se fazer isso é separando as particularidades de cada processador em um arquivo do linker: o linker script. Vamos começar com algo simples:


  1. /* Definindo as areas de memoria
  2. */
  3. MEMORY
  4. {
  5. FLASH : ORIGIN = 0x08000000, LENGTH = 64K
  6. RAM : ORIGIN = 0x20000000, LENGTH = 8K
  7. }
  8. /* Definindo o endereco do inicio
  9. * da pilha de sistema
  10. */
  11. _stack_init = ORIGIN(RAM)+LENGTH(RAM);
  12. /* Definindo as sessoes
  13. * por enquanto temos apenas uma
  14. * sessao de interesse: a de código
  15. */
  16. SECTIONS
  17. {
  18. .text : { *(.text) } > FLASH
  19. .data : { *(.data) } > RAM
  20. }

O linker script é um arquivo texto onde definimos as particularidades que desejamos para o processo do linker. Primeiramente faremos um arquivo chamado stm32f0discovery.ld que, como o nome diz, será particular para o processador STM32F05xx da placa STM32F0Discovery. Começamos pelo comando MEMORYEsse comando dá ao linker as características dos tipos de memória que o nosso processador tem. No nosso caso, o processador STM32F05xx possui 64k KBytes de memória flash iniciando no endereço 0x08000000 e 8 KBytes de memória ram iniciando no endereço 0x20000000. Pronto: já definimos o tipo, tamanho e endereço de nossa memória física. Com isso, na linha 14, criamos um símbolo que possui, automaticamente, o endereço da pilha de sistema.

O próximo comando faz o mapeamento entre as sessões internas (definidas no programas) com as sessões externas nas áreas e endereços físicos da(s) memória(s) do processador. Temos, como padrão, as seguintes sessões:

  • .text : contém as instruções do programa a serem executadas;
  • .data : contém as áreas de memórias pré-inicializadas com valores;
  • .bss : contém as áreas de memórias não inicializadas com valores.

Por enquanto, nos preocuparemos com a sessão .text. A sessão .data está presente por ser obrigatória no processo do linker mas nós a utilizaremos efetivamente mais adiante. No nosso arquivo, começamos esse mapeamento através do comando SECTIONS. De forma simples, mapeamos a sessão .text externa para a memória flash e a sessão externa .data para a memória ram. Como o processo do linker pode unir muitos programas em um único módulo objeto (aliás essa é sua função principal), determinamos que todas as sessões .text internas (dos programas) serão alocadas na memória flash e todas as sessões .data internas para a memória ram. Isso se dá pelo caractere coringa '*' presente no comando. Em suma:

.text : { *(.text) } > FLASH

.text : a sessão .text do módulo objeto de saída
... } engloba
*(.text) todas as sessões .text dos arquivos objetos dos programas individuais
> FLASH  e residirá na memória flash

Raciocínio semelhante é usado para a sessão .data.

Completando o processo, faremos as alterações necessárias no nosso programa e no makefile.


  1. // Segundo programa para ARM Cortex-M0 configurado para linker script
  2. .thumb // Define código como THUMB
  3. .globl _start // Necessario para o linker
  4. .globl _reset_handler // visto externamente
  5. .text // Inicio da área de código
  6. _start:
  7. // Inicio do Vector Table
  8. .word _stack_init // Inicio da pilha
  9. .word _reset_handler // Endereco do Reset Handler
  10. // define o label como função para que o linker
  11. // resolva como código THUMB
  12. .weak _reset_handler // pode ser redefinido em outro modulo
  13. .type _reset_handler, function
  14. // tabela de bytes para somar 1
  15. tbl:
  16. .byte 3,7,9,0
  17. .align
  18. _reset_handler: // inicio do tratamento do reset
  19. ldr r0,=tbl // carrega r0 com o endereco da tabela
  20. mov r1,#1 // move o valor 1 para r1
  21. segue:
  22. ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  23. cmp r2,#0 // se r2 for zero,
  24. beq _stop // encerra
  25. add r3, r2, r1 // soma r1 e r2 guardando em r3
  26. add r0,#1 // avanca na tabela
  27. b segue // continua
  28. _stop:
  29. b . // loop infinito

Primeiramente, rebatizamos nosso função de tratamento de reset como _reset_handler (linha 25) e o definimos como global (linha 5) e, com a diretiva .weak (linha 18), é possível que outro módulo externo ao nosso redefina essa função. O inicio da pilha do sistema (_stack_init) agora é uma referência resolvida externamente no linker script e não mais inicializada dentro do programa.

Quanto ao makefile, retiramos os passos de geração do arquivo binário que não usaremos por agora. Além disso, retiramos o parâmetro -Ttext=0x00000000 do passo do linker substituindo-o pelo parâmetro informando nosso arquivo de linker script: -T stm32f0discovery.ld.


  1. rst_0.elf: rst_0.o
  2. @echo&echo&echo&echo&echo&echo ///////////// Executando o linker
  3. @echo
  4. arm-none-eabi-ld -o rst_0.elf rst_0.o -T stm32f0discovery.ld
  5. @echo&echo&echo&echo&echo&echo ///////////// Mostrando os simbolos apos o linker
  6. @echo
  7. arm-none-eabi-nm rst_0.elf
  8. @echo&echo&echo&echo&echo&echo ///////////// Dump das sessoes do programa
  9. @echo
  10. arm-none-eabi-objdump -h rst_0.elf
  11. rst_0.o: rst_0.s
  12. @echo&echo&echo&echo&echo&echo ///////////// Montando o programa assembly
  13. @echo
  14. arm-none-eabi-as -mcpu=cortex-m0 -g -o rst_0.o rst_0.s
  15. clean:
  16. @echo&echo&echo&echo&echo&echo ///////////// Eliminando os arquivos de saida
  17. @echo
  18. rm *.o
  19. rm *.elf

Pronto. Nosso processo de geração do módulo objeto pode ser executado:


  1. T:\arm_03>make
  2. ///////////// Montando o programa assembly
  3. arm-none-eabi-as -mcpu=cortex-m0 -g -o rst_0.o rst_0.s
  4. ///////////// Executando o linker
  5. arm-none-eabi-ld -o rst_0.elf rst_0.o -T stm32f0discovery.ld
  6. ///////////// Mostrando os simbolos apos o linker
  7. arm-none-eabi-nm rst_0.elf
  8. 0800000c W _reset_handler
  9. 20002000 A _stack_init
  10. 08000000 T _start
  11. 0800001c t _stop
  12. 08000010 t segue
  13. 08000008 t tbl
  14. ///////////// Dump das sessoes do programa
  15. arm-none-eabi-objdump -h rst_0.elf
  16. rst_0.elf: file format elf32-littlearm
  17. Sections:
  18. Idx Name Size VMA LMA File off Algn
  19. 0 .text 00000024 08000000 08000000 00008000 2**2
  20. CONTENTS, ALLOC, LOAD, READONLY, CODE
  21. 1 .ARM.attributes 00000021 00000000 00000000 00008024 2**0
  22. CONTENTS, READONLY
  23. 2 .debug_line 00000042 00000000 00000000 00008045 2**0
  24. CONTENTS, READONLY, DEBUGGING
  25. 3 .debug_info 0000003a 00000000 00000000 00008087 2**0
  26. CONTENTS, READONLY, DEBUGGING
  27. 4 .debug_abbrev 00000014 00000000 00000000 000080c1 2**0
  28. CONTENTS, READONLY, DEBUGGING
  29. 5 .debug_aranges 00000020 00000000 00000000 000080d8 2**3
  30. CONTENTS, READONLY, DEBUGGING
  31. T:\arm_03>


Vemos duas informações importantes: na linha 27 o símbolo _stack_init foi gerado pelo linker e com conteúdo correto: 0x20002000. Nas linhas 44 e 45 temos as informações por ora relevantes da nossa sessão .text do nosso módulo objeto: ela possui um tamanho de 0x24 bytes, e possui os endereços de memória VMA e LMA iguais a 0x08000000 que é o nosso endereço da memória flash. Por enquanto é o suficiente. No próximo post quando falarmos da utilização da memória ram, descreveremos as diferenças e usos dos endereços VMA e LMA.

Agora é só depurarmos da mesma forma como feito no post anterior. Boa sorte.


REFERÊNCIAS
The GNU linker - http://www.eecs.umich.edu/courses/eecs373/readings/Linker.pdf
Using AS - http://www.eecs.umich.edu/courses/eecs373/readings/Assembler.pdf

quarta-feira, 11 de novembro de 2015

Um pouco mais de instruções Assembly e depurando nosso programa com OpenOCD

Vamos introduzir algumas novas instruções Assembly ao nosso primeiro programa. Na sequência veremos como depurar nossos programas ARM Cortex M com a solução OpenOCD. Comecemos incluindo um novo parâmetro '-g' na montagem do programa no nosso makefile:

  1. rst_0.bin: rst_0.elf
  2. @echo ///////////////////
  3. @echo Extraindo o arquivo binado sem os simbolos
  4. @echo ///////////////////
  5. @echo
  6. arm-none-eabi-objcopy -O binary rst_0.elf rst_0.bin
  7. rst_0.elf: rst_0.o
  8. @echo ///////////////////
  9. @echo Executando o linker
  10. @echo ///////////////////
  11. @echo
  12. arm-none-eabi-ld -Ttext=0x08000000 -o rst_0.elf rst_0.o
  13. @echo ///////////////////
  14. @echo Mostrando os simbolos apos o linker
  15. @echo ///////////////////
  16. @echo
  17. arm-none-eabi-nm rst_0.elf
  18. @echo ///////////////////
  19. @echo Dump das sessoes do programa
  20. @echo ///////////////////
  21. @echo
  22. arm-none-eabi-objdump -s rst_0.elf
  23. rst_0.o: rst_0.s
  24. @echo ///////////////////
  25. @echo Montando o programa assembly
  26. @echo ///////////////////
  27. @echo
  28. arm-none-eabi-as -mcpu=cortex-m0 -g -a -o rst_0.o rst_0.s
  29. clean:
  30. @echo ///////////////////
  31. @echo Eliminando os arquivos de saida
  32. @echo ///////////////////
  33. @echo
  34. rm *.bin
  35. rm *.elf
  36. rm *.o


Esse parâmetro diz ao montador para incluir no arquivo de saída, informações necessárias para executar com clareza a depuração. Além disso, dizemos ao montador qual o processador que estamos usando através do parâmetro '-mcpu=cortex-m0'. Isso garante que o montador cheque o conjunto de instruções específico para esse processador e evite exceções de hardware na execução. Mais uma execução de ordem prática para o momento da depuração é acertar o ponto de carga para o endereço físico real da memória flash do microcontrolador. Isso é feito alterando o parâmetro -T do linker de text=0x00000000 para text=0x08000000. Além disso, eliminamos o passo de geração do flash.bin e acrescentamos um dump das sessões dos nosso programa.

Façamos algumas modificações no nosso programa:

  1. // Avancando no primeiro programa para ARM Cortex-M0
  2. .thumb // Define código como THUMB
  3. .globl _start // Necessario para o linker
  4. .equ STACK_INIT,0x020002000
  5. .text // Inicio da área de código
  6. _start:
  7. // Inicio do Vector Table
  8. .word STACK_INIT // Inicio da pilha
  9. .word reset_handler // Endereco do Reset Handler
  10. // define o label como função para que o linker
  11. // resolva como código THUMB
  12. .type reset_handler, function
  13. // tabela de bytes para somar 1
  14. tbl:
  15. .byte 3,7,9,0
  16. .align
  17. reset_handler: // inicio do tratamento do reset
  18. ldr r0,=tbl // carrega r0 com o endereco da tabela
  19. mov r1,#1 // move o valor 1 para r1
  20. segue:
  21. ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  22. cmp r2,#0 // se r2 for zero,
  23. beq _stop // encerra
  24. add r3,r2,r1 // soma r1 e r2 guardando em r3
  25. add r0,#1 // avanca na tabela
  26. b segue // continua
  27. _stop:
  28. b . // loop infinito

Primeiramente, na linha 3, substituímos a diretiva .code 16 pela diretiva .thumb que produz o mesmo efeito e dá maior clareza ao entendimento do código.
Antes de prosseguirmos, vamos ver um novo registrador interno da arquitetura ARM Cortex M: o registrador APSR (Application Program Status Register) que pode ser visto na figura abaixo:



Nesse registrador estão presentes 4 bits de estado que refletem o resultado da última instrução executada: N=negativo, Z=zero, C=carry e V=overflow. Após a execução de algumas instruções, caso o valor resultante seja menor que zero, o bit Z do registrador APSR é ligado; caso o valor seja zero, é a vez do bit Z ser ligado, os bits C e V são usados no resultados de instruções aritméticas e serão vistos mais adiante. Esses bits são usados diretamente nas instruções de desvios condicionais. è possível executar algumas instruções forçando o acionamento dos bits do registrador APSR através da inclusão do sufixo 'S' em seu mnemônico. Por exemplo, a instrução MOV usada em nosso programa:

MOV R1,#0 // movimenta o valor imediato zero para o registrador R1

e

MOVS R1,#0 // movimenta o valor imediato zero para o registrador R1 e liga o bit Z do APSR.

Outros dois registradores igualmente importantes são o Interrupt Program Status Register (IPSR) e o Execution Program Status Register (EPSR). Ambos serão vistos mais adiante mas, por enquanto, podemos dizer que o IPSR contem o valor da interrupção a que o processador está submetido as quais as funções de tratamento tem seus endereços no nosso Vector Table e o EPSR diz, em seu bit T ligado se o processador está executando instruções do conjunto Thumb que será o nosso caso. Os três registradores são, na verdade, vistos de forma combinada em um único registrador de nome xPSR mostrado abaixo:



Vamos alterar o nosso programa para executar algumas instruções de desvio condicional. Na linha 23 temos a definição de uma tabela de bytes através da diretiva .byte. Assim como a diretiva .word, essa diretiva aloca espaço de memória de 1 byte cada e inicializa com o(s) valor(es) especificado(s) no código. Esse código pode ter o valor de 0 a 255 (ou 0x0 a 0xff). No nosso caso, a tabela endereçada pelo label tbl: possui os valores 3, 7, 9 e zero. O intuito é fazer um laço de instruções que some o valor de dois registradores sendo um desses valores os valores presentes nessa tabela. Na linha 24 temos uma nova diretiva: .align. Essa diretiva faz o alinhamento do código à frente para o valor de bytes especificados na diretiva. Caso nenhum valor for especificado, será alinhada em 32 bits ou 4 bytes. Isso se torna necessário uma vez que a diretiva anterior (.byte) pode desalinhar os endereços e a instrução seguinte não ocupe endereço múltiplo de 32 bits.

Nossa nova lógica, na linha 28, carrega no registrador R0 o endereço de nossa tabela de bytes. A linha 29 inicializa o registrador R1 em 1. A próxima instrução, na linha 30, possui o label 'segue:' e será o ponto de retomada de nosso laço de instruções. Aqui, o registrador R2 é carregado com o byte endereçado por R0. Na primeira passagem R2 deve receber o primeiro byte de nossa tabela, ou seja, o valor 3. Na sequência, o fim do laço é testado: na linha 32 o valor presente em R2 é comparado com zero. Se for igual (linha 33), o programa desvia para o loop infinito no label _stop. Não sendo o fim da tabela, os valores contidos em R1 e R2 são somados e o resultado depositado no registrador R3 (linha 34). O ponteiro da tabela presente em R0 é avançado em um byte na linha 35 e o programa volta ao laço na linha 36 através de um desvio incondicional para a linha 31.

Esse programa é simples ainda mas já mostra avanços no controle de lógica. Algumas diretivas novas foram mostradas assim como endereçamento indireto e controle de fluxo.

Vamos então executá-lo só que, desta vez, depurando linha a linha via o depurador interno do processador. Para tal usaremos dois utilitários: OpenOCD e GDB.


OpenOCD (open on-chip-debbuger) é um utilitário de código aberto desenvolvido para Linux mas que possui binários para MS-Windows disponíveis aqui ou aqui. Ele se comunica com o processador presente no microcontrolador via comunicação serial dependente da implementação. No caso da placa STM32F0Discovery, a interface presente é a ST-LINK V2 já descrita em post anterior. Esta fala de um lado com o processador ARM Cortex M0 via comunicação bidirecional a dois fios (SWD) e, do outro lado, via comunicação USB como o computador host (MS-Windows ou Linux). Na outra ponta dessa comunicação, está exatamente o utilitário OpenOCD que possui um driver para falar de um lado com a interface ST-Link V2 e, da outra ponta, abre 2 ports TCP: 4444 para interação com o próprio OpenOCD e, pelo port 3333 para interação com um depurador que, no nosso caso, usaremos a versão GNU: arm-none-eabi-gdb.

O utilitário OpenOCD deve ser baixado, descomprimido em um diretório conhecido, como por exemplo o raiz. O caminho do diretório bin deve ser adicionado à variável de ambiente PATH. Após concluído, abrir uma janela de linha de comando e executar o comando da figura abaixo:

  1. T:\>openocd -v
  2. GNU ARM Eclipse 64-bits Open On-Chip Debugger 0.9.0-00073-gdd34716-dirty (2015-05-19-09:55)
  3. Licensed under GNU GPL v2
  4. For bug reports, read
  5. http://openocd.org/doc/doxygen/bugs.html
  6. T:\>

A resposta pode ser diferente dependendo da versão baixada, a origem do binário e o tipo de código: 32 ou 64 bits.

Vamos então preparar o nosso código para execução e depuração:

  1. U:\arm_01>make
  2. ///////////////////
  3. Montando o programa assembly
  4. ///////////////////
  5. arm-none-eabi-as -mcpu=cortex-m0 -g -a -o rst_0.o rst_0.s
  6. ARM GAS rst_0.s page 1
  7. 1 // Avancando no primeiro programa para ARM Cortex-M0
  8. 2
  9. 3 .thumb // Define c├│digo como THUMB
  10. 4 .globl _start // Necessario para o linker
  11. 5
  12. 6 .equ STACK_INIT,0x020002000
  13. 7
  14. 8 .text // Inicio da área de código
  15. 9 _start:
  16. 10
  17. 11 // Inicio do Vector Table
  18. 12
  19. 13 0000 00200020 .word STACK_INIT // Inicio da pilha
  20. 14 0004 00000000 .word reset_handler // Endereco do Reset Handler
  21. 15
  22. 16 // define o label como função para que o linker
  23. 17 // resolva como c├│digo THUMB
  24. 18
  25. 19 .type reset_handler, function
  26. 20
  27. 21 // tabela de bytes para somar 1
  28. 22 tbl:
  29. 23 0008 03070900 .byte 3,7,9,0
  30. 24 .align
  31. 25
  32. 26 reset_handler: // inicio do tratamento do reset
  33. 27
  34. 28 000c 0448 ldr r0,=tbl // carrega r0 com o endereco da tabela
  35. 29 000e 0121 mov r1,#1 // move o valor 1 para r1
  36. 30 segue:
  37. 31 0010 0278 ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  38. 32 0012 002A cmp r2,#0 // se r2 for zero,
  39. 33 0014 02D0 beq _stop // encerra
  40. 34 0016 5318 add r3,r2,r1 // soma r1 e r2 guardando em r3
  41. 35 0018 0130 add r0,#1 // avanca na tabela
  42. 36 001a F9E7 b segue // continua
  43. 37 _stop:
  44. 38 001c FEE70000 b . // loop infinito
  45. 38 08000000
  46. ♀ARM GAS rst_0.s page 2
  47. DEFINED SYMBOLS
  48. rst_0.s:9 .text:00000000 _start
  49. rst_0.s:6 *ABS*:20002000 STACK_INIT
  50. rst_0.s:26 .text:0000000c reset_handler
  51. rst_0.s:22 .text:00000008 tbl
  52. rst_0.s:24 .text:0000000c $t
  53. rst_0.s:30 .text:00000010 segue
  54. rst_0.s:37 .text:0000001c _stop
  55. rst_0.s:38 .text:0000001e $d
  56. .debug_aranges:0000000c $d
  57. NO UNDEFINED SYMBOLS
  58. ///////////////////
  59. Executando o linker
  60. ///////////////////
  61. arm-none-eabi-ld -Ttext=0x08000000 -o rst_0.elf rst_0.o
  62. ///////////////////
  63. Mostrando os simbolos apos o linker
  64. ///////////////////
  65. arm-none-eabi-nm rst_0.elf
  66. 08008024 T __bss_end__
  67. 08008024 T __bss_start
  68. 08008024 T __bss_start__
  69. 08008024 T __data_start
  70. 08008024 T __end__
  71. 08008024 T _bss_end__
  72. 08008024 T _edata
  73. 08008024 T _end
  74. 00080000 N _stack
  75. 08000000 T _start
  76. 0800001c t _stop
  77. 0800000c t reset_handler
  78. 08000010 t segue
  79. 20002000 a STACK_INIT
  80. 08000008 t tbl
  81. ///////////////////
  82. Dump das sessoes do programa
  83. ///////////////////
  84. arm-none-eabi-objdump -s rst_0.elf
  85. rst_0.elf: file format elf32-littlearm
  86. Contents of section .text:
  87. 8000000 00200020 0d000008 03070900 04480121 . . .........H.!
  88. 8000010 0278002a 02d05318 0130f9e7 fee70000 .x.*..S..0......
  89. 8000020 08000008 ....
  90. Contents of section .debug_aranges:
  91. 0000 1c000000 02000000 00000400 00000000 ................
  92. 0010 00000008 24000000 00000000 00000000 ....$...........
  93. Contents of section .debug_info:
  94. 0000 36000000 02000000 00000401 00000000 6...............
  95. 0010 00000008 24000008 7273745f 302e7300 ....$...rst_0.s.
  96. 0020 553a5c61 726d5f30 3100474e 55204153 U:\arm_01.GNU AS
  97. 0030 20322e32 342e3000 0180 2.24.0...
  98. Contents of section .debug_abbrev:
  99. 0000 01110010 06110112 0103081b 08250813 .............%..
  100. 0010 05000000 ....
  101. Contents of section .debug_line:
  102. 0000 3e000000 02001e00 00000201 fb0e0d00 >...............
  103. 0010 01010101 00000001 00000100 7273745f ............rst_
  104. 0020 302e7300 00000000 0005020c 00000803 0.s.............
  105. 0030 1b012122 21212121 21220376 2e020200 ..!"!!!!!".v....
  106. 0040 0101 ..
  107. Contents of section .ARM.attributes:
  108. 0000 41200000 00616561 62690001 16000000 A ...aeabi......
  109. 0010 05436f72 7465782d 4d300006 0c074d09 .Cortex-M0....M.
  110. 0020 01 .
  111. ///////////////////
  112. Extraindo o arquivo binado sem os simbolos
  113. ///////////////////
  114. arm-none-eabi-objcopy -O binary rst_0.elf rst_0.bin
  115. U:\arm_01>

Uma vez pronto o programa, vamos depurá-lo. Primeiramente, iniciemos o OpenOCD:

  1. T:\arm_01>openocd -f board/stm32f0discovery.cfg
  2. GNU ARM Eclipse 64-bits Open On-Chip Debugger 0.9.0-00073-gdd34716-dirty (2015-05-19-09:55)
  3. Licensed under GNU GPL v2
  4. For bug reports, read
  5. http://openocd.org/doc/doxygen/bugs.html
  6. Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
  7. adapter speed: 1000 kHz
  8. adapter_nsrst_delay: 100
  9. none separate
  10. srst_only separate srst_nogate srst_open_drain connect_deassert_srst
  11. Info : Unable to match requested speed 1000 kHz, using 950 kHz
  12. Info : Unable to match requested speed 1000 kHz, using 950 kHz
  13. Info : clock speed 950 kHz
  14. Info : STLINK v2 JTAG v24 API v2 SWIM v0 VID 0x0483 PID 0x3748
  15. Info : using stlink api v2
  16. Info : Target voltage: 2.900648
  17. Info : stm32f0x.cpu: hardware has 4 breakpoints, 2 watchpoints

Ao iniciar o utilitário OpenOCD, devemos informar qual dispositivo estamos usando. Há uma série de dispositivos pré-configurados no diretório scripts/board do diretório de instalação do OpenOCD.
No caso, informamos a placa STM32F0Discovery a partir de seu arquivo de configuração presente na instalação do OpenOCD. O utilitário reconheceu a interface ST-Link V2 e está pronto para receber conexões TCP. Faremos isso a seguir executando o comando a seguir em uma nova janela de linha de comando:


  1. T:\arm_01>arm-none-eabi-gdb rst_0.elf
  2. GNU gdb (GNU Tools for ARM Embedded Processors) 7.8.0.20150604-cvs
  3. Copyright (C) 2014 Free Software Foundation, Inc.
  4. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
  5. This is free software: you are free to change and redistribute it.
  6. There is NO WARRANTY, to the extent permitted by law. Type "show copying"
  7. and "show warranty" for details.
  8. This GDB was configured as "--host=i686-w64-mingw32 --target=arm-none-eabi".
  9. Type "show configuration" for configuration details.
  10. For bug reporting instructions, please see:
  11. <http://www.gnu.org/software/gdb/bugs/>.
  12. Find the GDB manual and other documentation resources online at:
  13. <http://www.gnu.org/software/gdb/documentation/>.
  14. For help, type "help".
  15. Type "apropos word" to search for commands related to "word"...
  16. Reading symbols from rst_0.elf...done.
  17. (gdb)

Novamente vemos que o depurador tem como objeto de depuração código ARM embarcado (arm-none-eabi). O depurador foi iniciado já informando o arquivo ELF gerado. Esse arquivo, como dito anteriormente, possui o código objeto executável e os símbolos úteis para depuração. Na linha 16, o depurador carrega os símbolos e está pronto para receber comandos ao mostrar o prompt (gdb) na linha 17.

A seguir efetuaremos alguns comandos para se comunicar com o OpenOCD e, por consequência, com o processador da placa STM32F0Discovery; carregar o código objeto em sua memória flash e executá-lo passo a passo observando o conteúdo de seus registradores.


  1. (gdb) target remote localhost:3333
  2. Remote debugging using localhost:3333
  3. 0x00000000 in ?? ()
  4. (gdb) info reg
  5. r0 0x0 0
  6. r1 0x0 0
  7. r2 0x0 0
  8. r3 0x0 0
  9. r4 0x0 0
  10. r5 0x0 0
  11. r6 0x0 0
  12. r7 0x0 0
  13. r8 0x0 0
  14. r9 0x0 0
  15. r10 0x0 0
  16. r11 0x0 0
  17. r12 0x0 0
  18. sp 0x0 0x0
  19. lr 0x0 0
  20. pc 0x0 0x0
  21. xPSR 0x0 0
  22. (gdb)

Na linha 1 efetuamos a conexão TCP no próprio computador e port 3333. A resposta mostra o conteúdo da próxima instrução do processador presente no registrador PC. Para se certificar, usamos o comando info reg para ver o conteúdo dos registradores do processador. Estamos conectados ao processador. Agora vamos carregar na sua memória flash o código objeto presente no arquivo rst_0.elf usado na chamada do utilitário arm-none-eabi-gdb. Para tal, usaremos a sequência abaixo:


  1. (gdb) monitor reset halt
  2. target state: halted
  3. target halted due to debug-request, current mode: Thread
  4. xPSR: 0xc1000000 pc: 0xfffffffe msp: 0xfffffffc
  5. (gdb) monitor flash erase_sector 0 0 last
  6. erased sectors 0 through 63 on flash bank 0 in 0.029801s
  7. (gdb) load
  8. Loading section .text, size 0x24 lma 0x8000000
  9. Start address 0x8000000, load size 36
  10. Transfer rate: 220 bytes/sec, 36 bytes/write.
  11. (gdb) info reg
  12. r0 0x0 0
  13. r1 0x0 0
  14. r2 0x0 0
  15. r3 0x0 0
  16. r4 0x0 0
  17. r5 0x0 0
  18. r6 0x0 0
  19. r7 0x0 0
  20. r8 0x0 0
  21. r9 0x0 0
  22. r10 0x0 0
  23. r11 0x0 0
  24. r12 0x0 0
  25. sp 0x0 0x0
  26. lr 0x0 0
  27. pc 0x8000000 0x8000000 <_start>
  28. xPSR 0x1000000 16777216
  29. (gdb)

Na linha 1 enviamos um comando de reset ao processador com a consequente entrada no estado suspenso (halt). Isso evita que o processador comece a executar nosso reset_handler. Esse comando foi submetido através do comando monitor. O comando monitor ou mon, na verdade, executa um comando do OpenOCD e trás de volta o seu resultado. Na linha 5, pedimos ao OpenOCD, executar uma limpeza da memória flash. Finalmente, na linha 7, o utilitário arm-none-eabi-gdb efetua a carga na memória flash do programa objeto presente no arquivo rst_0.elf. Podemos ver, na linha 8, que o código, indicado pela sessão .text, foi carregado a partir do endereço 0x080000000. Se não houvéssemos alterado esse endereço no passo do linker no nosso makefile, a carga aqui não seria possível pois o endereço físico real da memória flash para esse microcontrolador é essa. Podemos ver ainda o registrador PC com o o endereço de nossa sessão .text que era de se esperar. Temos também o registrador xPSR que está com o bit T ligado indicando código thumb.

Através do comando list, vemos o código fonte armazenado nos símbolos do arquivo ELF carregado:

  1. (gdb) list 1,99999
  2. 1 // Avancando no primeiro programa para ARM Cortex-M0
  3. 2
  4. 3 .thumb // Define código como THUMB
  5. 4 .globl _start // Necessario para o linker
  6. 5
  7. 6 .equ STACK_INIT,0x020002000
  8. 7
  9. 8 .text // Inicio da área de código
  10. 9 _start:
  11. 10
  12. 11 // Inicio do Vector Table
  13. 12
  14. 13 .word STACK_INIT // Inicio da pilha
  15. 14 .word reset_handler // Endereco do Reset Handler
  16. 15
  17. 16 // define o label como função para que o linker
  18. 17 // resolva como código THUMB
  19. 18
  20. 19 .type reset_handler, function
  21. 20
  22. 21 // tabela de bytes para somar 1
  23. 22 tbl:
  24. 23 .byte 3,7,9,0
  25. 24 .align
  26. ---Type <return> to continue, or q <return> to quit---
  27. 25
  28. 26 reset_handler: // inicio do tratamento do reset
  29. 27
  30. 28 ldr r0,=tbl // carrega r0 com o endereco da tabela
  31. 29 mov r1,#1 // move o valor 1 para r1
  32. 30 segue:
  33. 31 ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  34. 32 cmp r2,#0 // se r2 for zero,
  35. 33 beq _stop // encerra
  36. 34 add r3,r2,r1 // soma r1 e r2 guardando em r3
  37. 35 add r0,#1 // avanca na tabela
  38. 36 b segue // continua
  39. 37 _stop:
  40. 38 b . // loop infinito
  41. (gdb)

Antes de continuarmos, vamos ver alguns comandos de depuração úteis para nós. à medida que vamos avançando no conhecimento e complexidade do desenvolvimento para microcontroladores com processadores ARM Cortex M, novos comandos de depuração vão sendo mostrados e usados. Relembrando que o comando monitor ou mon executa, na verdade, comandos OpenOCD. Para encerrar a depuração, usamos o comando quit.

Para executarmos uma instrução de máquina, usamos o comando stepi. Para executarmos instruções até a próxima linha de código fonte, usamos simplesmente step. Essa é a diferença dos dois comandos. Se uma linha de código fonte a ser executada representa uma função (uma chamada com desvio e retorno) e não desejamos depurar essa função, usamos o comando next. Caso desejemos monitorar algum valor de registrador ou área de memória de memória a cada passo da depuração, usamos o comando display. Esse comando guarda uma lista de objetos que desejamos que sejam mostrados a cada iteração de depuração. Vejam o exemplo abaixo:

  1. (gdb) info display
  2. There are no auto-display expressions now.
  3. (gdb) display /x $r0
  4. 1: /x $r0 = 0x800000b
  5. (gdb) display /x $r2
  6. 2: /x $r2 = 0x0
  7. (gdb) display /x $r3
  8. 3: /x $r3 = 0xa
  9. (gdb) display /x $xPSR
  10. 4: /x $xPSR = 0x61000000
  11. (gdb) info display
  12. Auto-display expressions now in effect:
  13. Num Enb Expression
  14. 4: y /x $xPSR
  15. 3: y /x $r3
  16. 2: y /x $r2
  17. 1: y /x $r0
  18. (gdb)

Na linha 1, o comando info display mostra o que há definido como auto-display. No caso não há nada. Nas linhas 3, 5, 7 e 9, definimos para o gdb mostrar, no formato hexadecimal (/x) o conteúdo dos registradores R0, R2, R3 e do registrador xPRS. Finalmente, na linha 11, o comando info display executado novamente mostra os 4 comandos desejados. Se desejarmos retirar algum comando do auto-display usamos o comando undisplay. Pronto, agora podemos começar a depurar nosso programa. Para tal, ainda vamos ver mais um comando: flushregs. Muitas vezes, os valores dos registradores guardados pelo gdb ficam desatualizados em relação aos valores reais do processador, principalmente após comandos monitor. Para forçar o gdb atualizá-los, usamos o comando flushregs. Se não digitarmos um novo comando e dermos enter no prompt do gdb, ele executa o último comando recebido.


  1. (gdb) mon reset halt
  2. target state: halted
  3. target halted due to debug-request, current mode: Thread
  4. xPSR: 0xc1000000 pc: 0x0800000c msp: 0x20002000
  5. (gdb) flushregs
  6. Register cache flushed.
  7. (gdb) frame
  8. #0 reset_handler () at rst_0.s:28
  9. 28 ldr r0,=tbl // carrega r0 com o endereco da tabela
  10. (gdb) si
  11. 29 mov r1,#1 // move o valor 1 para r1
  12. 4: /x $xPSR = 0xc1000000
  13. 3: /x $r3 = 0xffffffff
  14. 2: /x $r2 = 0xffffffff
  15. 1: /x $r0 = 0x8000008
  16. (gdb)
  17. segue () at rst_0.s:31
  18. 31 ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  19. 4: /x $xPSR = 0x1000000
  20. 3: /x $r3 = 0xffffffff
  21. 2: /x $r2 = 0xffffffff
  22. 1: /x $r0 = 0x8000008
  23. (gdb)
  24. 32 cmp r2,#0 // se r2 for zero,
  25. 4: /x $xPSR = 0x1000000
  26. 3: /x $r3 = 0xffffffff
  27. 2: /x $r2 = 0x3
  28. 1: /x $r0 = 0x8000008
  29. (gdb)
  30. 33 beq _stop // encerra
  31. 4: /x $xPSR = 0x21000000
  32. 3: /x $r3 = 0xffffffff
  33. 2: /x $r2 = 0x3
  34. 1: /x $r0 = 0x8000008
  35. (gdb)
  36. 34 add r3,r2,r1 // soma r1 e r2 guardando em r3
  37. 4: /x $xPSR = 0x21000000
  38. 3: /x $r3 = 0xffffffff
  39. 2: /x $r2 = 0x3
  40. 1: /x $r0 = 0x8000008
  41. (gdb)
  42. 35 add r0,#1 // avanca na tabela
  43. 4: /x $xPSR = 0x1000000
  44. 3: /x $r3 = 0x4
  45. 2: /x $r2 = 0x3
  46. 1: /x $r0 = 0x8000008
  47. (gdb)
  48. 36 b segue // continua
  49. 4: /x $xPSR = 0x1000000
  50. 3: /x $r3 = 0x4
  51. 2: /x $r2 = 0x3
  52. 1: /x $r0 = 0x8000009
  53. (gdb)
  54. 31 ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  55. 4: /x $xPSR = 0x1000000
  56. 3: /x $r3 = 0x4
  57. 2: /x $r2 = 0x3
  58. 1: /x $r0 = 0x8000009
  59. (gdb)
  60. 32 cmp r2,#0 // se r2 for zero,
  61. 4: /x $xPSR = 0x1000000
  62. 3: /x $r3 = 0x4
  63. 2: /x $r2 = 0x7
  64. 1: /x $r0 = 0x8000009
  65. (gdb)
  66. 33 beq _stop // encerra
  67. 4: /x $xPSR = 0x21000000
  68. 3: /x $r3 = 0x4
  69. 2: /x $r2 = 0x7
  70. 1: /x $r0 = 0x8000009
  71. (gdb)
  72. 34 add r3,r2,r1 // soma r1 e r2 guardando em r3
  73. 4: /x $xPSR = 0x21000000
  74. 3: /x $r3 = 0x4
  75. 2: /x $r2 = 0x7
  76. 1: /x $r0 = 0x8000009
  77. (gdb)
  78. 35 add r0,#1 // avanca na tabela
  79. 4: /x $xPSR = 0x1000000
  80. 3: /x $r3 = 0x8
  81. 2: /x $r2 = 0x7
  82. 1: /x $r0 = 0x8000009
  83. (gdb)
  84. 36 b segue // continua
  85. 4: /x $xPSR = 0x1000000
  86. 3: /x $r3 = 0x8
  87. 2: /x $r2 = 0x7
  88. 1: /x $r0 = 0x800000a
  89. (gdb)
  90. 31 ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  91. 4: /x $xPSR = 0x1000000
  92. 3: /x $r3 = 0x8
  93. 2: /x $r2 = 0x7
  94. 1: /x $r0 = 0x800000a
  95. (gdb)
  96. 32 cmp r2,#0 // se r2 for zero,
  97. 4: /x $xPSR = 0x1000000
  98. 3: /x $r3 = 0x8
  99. 2: /x $r2 = 0x9
  100. 1: /x $r0 = 0x800000a
  101. (gdb)
  102. 33 beq _stop // encerra
  103. 4: /x $xPSR = 0x21000000
  104. 3: /x $r3 = 0x8
  105. 2: /x $r2 = 0x9
  106. 1: /x $r0 = 0x800000a
  107. (gdb)
  108. 34 add r3,r2,r1 // soma r1 e r2 guardando em r3
  109. 4: /x $xPSR = 0x21000000
  110. 3: /x $r3 = 0x8
  111. 2: /x $r2 = 0x9
  112. 1: /x $r0 = 0x800000a
  113. (gdb)
  114. 35 add r0,#1 // avanca na tabela
  115. 4: /x $xPSR = 0x1000000
  116. 3: /x $r3 = 0xa
  117. 2: /x $r2 = 0x9
  118. 1: /x $r0 = 0x800000a
  119. (gdb)
  120. 36 b segue // continua
  121. 4: /x $xPSR = 0x1000000
  122. 3: /x $r3 = 0xa
  123. 2: /x $r2 = 0x9
  124. 1: /x $r0 = 0x800000b
  125. (gdb)
  126. 31 ldrb r2,[r0] // carrega em r2 o byte enderecado por r0
  127. 4: /x $xPSR = 0x1000000
  128. 3: /x $r3 = 0xa
  129. 2: /x $r2 = 0x9
  130. 1: /x $r0 = 0x800000b
  131. (gdb)
  132. 32 cmp r2,#0 // se r2 for zero,
  133. 4: /x $xPSR = 0x1000000
  134. 3: /x $r3 = 0xa
  135. 2: /x $r2 = 0x0
  136. 1: /x $r0 = 0x800000b
  137. (gdb)
  138. 33 beq _stop // encerra
  139. 4: /x $xPSR = 0x61000000
  140. 3: /x $r3 = 0xa
  141. 2: /x $r2 = 0x0
  142. 1: /x $r0 = 0x800000b
  143. (gdb)
  144. _stop () at rst_0.s:38
  145. 38 b . // loop infinito
  146. 4: /x $xPSR = 0x61000000
  147. 3: /x $r3 = 0xa
  148. 2: /x $r2 = 0x0
  149. 1: /x $r0 = 0x800000b
  150. (gdb)
  151. 38 b . // loop infinito
  152. 4: /x $xPSR = 0x61000000
  153. 3: /x $r3 = 0xa
  154. 2: /x $r2 = 0x0
  155. 1: /x $r0 = 0x800000b
  156. (gdb) quit
  157. A debugging session is active.
  158. Inferior 1 [Remote target] will be detached.
  159. Quit anyway? (y or n) y
  160. Detaching from program: T:\arm_01\rst_0.elf, Remote target
  161. Ending remote debugging.
  162. T:\arm_01>

Podemos ver claramente, passo a passo, o programa sendo executado. Inicialmente, executamos o reset halt na linha 1 o que carrega o registrador PC com o endereço da primeira instrução: reset_haldler. Para confirmar isso, temos mais um comando na linha 7: frame que pede ao gdb mostrar qual instrução está apontada pelo registrador PC.  Antes, na linha 5, forçamos o gdb a atualizar os valores dos registradores através do comando flushregs. Na sequência, executamos uma serie de comandos stepi (que poderia ser na sua forma abreviada: si). A cada comando executado, o gdb embute um comando frame o qual mostra a próxima instrução a ser executada uma vez que, após executar uma instrução, o processador atualiza o registrador PC. Como desejávamos, a cada iteração, o gdb mostra os conteúdos dos registradores R0, R2, R3 e xPSR. O controle do fluxo é executado pelas instruções CMP e BEQ. A linha 23 executa o comando mostrado na linha 18. Podemos ver o conteúdo do registrador R2 sendo modificado após essa execução, conforme comparação entre as linhas 21 e 27. Como R2 está com o valor 0x03, a comparação sendo executada na linha 29 não força o desvio na próxima instrução (execução na linha 35). O programa segue até a execução da linha 143 que força o desvio para o loop infinito devido ao resultado da comparação executada na linha 137 ter ligado o bit Z do registrador xPSR (linha 146).

Nos próximos posts veremos mais profundamente a programação do processador ARM Cortex M além de novos recursos de depuração.

REFERÊNCIAS
Thumb instruction summary - http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0210c/CACBCAAE.html
Thumb instruction set -  http://bear.ces.cwru.edu/eecs_382/ARM7-TDMI-manual-pt3.pdf
ARM and Thumb-2 Instruction Set - Quick Reference Guide - https://www.lri.fr/~de/ARM.pdf
OpenOCD User’s Guide - http://openocd.org/doc/html/index.html#Top
Debugging with gdb - https://sourceware.org/gdb/onlinedocs/gdb/index.html#Top

quinta-feira, 5 de novembro de 2015

Facilitando as coisas com o utilitário MAKE

No último post codificamos e executamos nosso primeiro programa para o microcontrolador STM32F051R8T6. Uma série de comandos na ordem certa são necessários para perfazer essa tarefa. Vamos aproveitar que as coisas ainda estão simples e introduzir um utilitário que facilita muito o trabalho de quem desenvolve programas: o utilitário MAKE. Em um post anterior, nós o baixamos e instalamos através de uma versão de utilitários POSIX: o MINGW. O utilitário MAKE busca dependências e comandos em um arquivo a parte que, por padrão, chama-se makefile. Vamos começar de uma forma simples e aos poucos automatizando mais o processo.

O utilitário MAKE trabalha com dependências e procedimentos. No nosso primeiro programa, o arquivo final era o flash.bin que dependia do rst_0.bin que, por sua vez, dependia do rst_0.elf e assim por diante até chegarmos na primeira dependência que era o programa assembly rst_0.s propriamente dito. Se usarmos o símbolo ':' para marcar a dependência, teremos:

  1. flash.bin: rst_0.bin
  2. rst_0.bin: rst_0.elf
  3. rst_0.elf: rst_0.o
  4. rst_0.o: rst_0.s

Do lado esquerdo dos dois-pontos temos o produto e do lado direito o produto necessário para gerá-lo. Dessa forma, temos uma cadeia de dependências:

flash.bin: rst_0.bin: rst_0.elf: rst_0.o: rst_0.s

Essa é a primeira construção do makefile.

Dissemos que o utilitário MAKE trabalha com dependências e procedimentos. Para satisfazer as dependências temos os procedimentos. Como incluir um procedimento no arquivo makefile?
Simples: na linha seguinte à dependência, iniciando com o caractere TAB (0x09), codificamos o(s) comando(s) que satisfaz(em) essa dependência. Veja o exemplo abaixo:

  1. rst_0.o: rst_0.s
  2. arm-none-eabi-as -a -o rst_0.o rst_0.s

Para satisfazer a dependência da linha 1, o utilitário MAKE executa o comando que esta na linha 2. Simples assim. O arquivo makefile completo para o nosso promeiro programa está abaixo:

  1. flash.bin: rst_0.bin
  2. dd if=/dev/zero of=flash.bin bs=4096 count=16
  3. dd if=rst_0.bin of=flash.bin bs=4096 conv=notrunc
  4. rst_0.bin: rst_0.elf
  5. arm-none-eabi-objcopy -O binary rst_0.elf rst_0.bin
  6. rst_0.elf: rst_0.o
  7. arm-none-eabi-ld -Ttext=0x00000000 -o rst_0.elf rst_0.o
  8. arm-none-eabi-nm rst_0.elf
  9. rst_0.o: rst_0.s
  10. arm-none-eabi-as -a -o rst_0.o rst_0.s
  11. clean:
  12. rm *.bin
  13. rm *.elf
  14. rm *.o

Todas as dependências estão satisfeitas por comandos. Notem uma dependência nova na linha 13. Na verdade, podemos executar o utilitário MAKE dizendo a ele qual dependência queremos satisfazer.  A linha 13 permite uma opção para limpar todos os arquivos de saída dos procedimentos executados. Vejamos sua execução:

  1. U:\arm_00>make clean
  2. rm *.bin
  3. rm *.elf
  4. rm *.o
  5. U:\arm_00>
A dependência clean executa o comando rm (remove) para excluir os arquivos desejados.

Finalmente, eis a execução de nosso makefile para gerar nosso primeiro programa:


  1. U:\arm_00>make
  2. arm-none-eabi-as -a -o rst_0.o rst_0.s
  3. ARM GAS rst_0.s page 1
  4. 1 // Primeiro programa para ARM Cortex-M0
  5. 2
  6. 3 .code 16 // Define c├│digo como THUMB
  7. 4 .globl _start // Necessario para o linker
  8. 5
  9. 6 .equ STACK_INIT,0x020002000
  10. 7
  11. 8 .text // Inicio da área de código
  12. 9 _start:
  13. 10
  14. 11 // Inicio do Vector Table
  15. 12
  16. 13 0000 00200020 .word STACK_INIT // Inicio da pilha
  17. 14 0004 00000000 .word reset_handler // Endereco do Reset Handler
  18. 15
  19. 16 // define o label como função para que o linker
  20. 17 // resolva como c├│digo THUMB
  21. 18
  22. 19 .type reset_handler, function
  23. 20
  24. 21 reset_handler: // inicio do tratamento do reset
  25. 22
  26. 23 0008 0320 mov r0, #3 // carrega r0 com o valor 3
  27. 24 000a 0421 mov r1, #4 // carrega r1 com o valor 4
  28. 25 000c 0A18 add r2, r1, r0 // soma r0 e r1 guardando em r2
  29. 26
  30. 27 000e FEE7 _stop: b . // loop infinito
  31. ♀ARM GAS rst_0.s page 2
  32. DEFINED SYMBOLS
  33. rst_0.s:9 .text:00000000 _start
  34. rst_0.s:6 *ABS*:20002000 STACK_INIT
  35. rst_0.s:21 .text:00000008 reset_handler
  36. .text:00000000 $d
  37. rst_0.s:23 .text:00000008 $t
  38. rst_0.s:27 .text:0000000e _stop
  39. NO UNDEFINED SYMBOLS
  40. arm-none-eabi-ld -Ttext=0x00000000 -o rst_0.elf rst_0.o
  41. arm-none-eabi-nm rst_0.elf
  42. 00008010 T __bss_end__
  43. 00008010 T __bss_start
  44. 00008010 T __bss_start__
  45. 00008010 T __data_start
  46. 00008010 T __end__
  47. 00008010 T _bss_end__
  48. 00008010 T _edata
  49. 00008010 T _end
  50. 00080000 T _stack
  51. 00000000 T _start
  52. 0000000e t _stop
  53. 00000008 t reset_handler
  54. 20002000 a STACK_INIT
  55. arm-none-eabi-objcopy -O binary rst_0.elf rst_0.bin
  56. dd if=/dev/zero of=flash.bin bs=4096 count=16
  57. 16+0 records in
  58. 16+0 records out
  59. 65536 bytes (66 kB) copied, 0 seconds, Infinity B/s
  60. dd if=rst_0.bin of=flash.bin bs=4096 conv=notrunc
  61. 0+1 records in
  62. 0+1 records out
  63. 16 bytes (16 B) copied, 0 seconds, Infinity B/s
  64. U:\arm_00>

Por último, para deixarmos mais fácil a leitura das saídas das sequências de comandos, usaremos o comando @echo para sinalizar o que está sendo executado:

  1. flash.bin: rst_0.bin
  2. @echo //////////////////////////////////////////////////
  3. @echo Gerando a imagem final para carga na memoria flash
  4. @echo //////////////////////////////////////////////////
  5. dd if=/dev/zero of=flash.bin bs=4096 count=16
  6. dd if=rst_0.bin of=flash.bin bs=4096 conv=notrunc
  7. rst_0.bin: rst_0.elf
  8. @echo //////////////////////////////////////////////////
  9. @echo Extraindo o arquivo binado sem os simbolos
  10. @echo //////////////////////////////////////////////////
  11. arm-none-eabi-objcopy -O binary rst_0.elf rst_0.bin
  12. rst_0.elf: rst_0.o
  13. @echo //////////////////////////////////////////////////
  14. @echo Executando o linker
  15. @echo //////////////////////////////////////////////////
  16. arm-none-eabi-ld -Ttext=0x00000000 -o rst_0.elf rst_0.o
  17. @echo //////////////////////////////////////////////////
  18. @echo Mostrando os simbolos apos o linker
  19. @echo //////////////////////////////////////////////////
  20. arm-none-eabi-nm rst_0.elf
  21. rst_0.o: rst_0.s
  22. @echo //////////////////////////////////////////////////
  23. @echo Montando o programa assembly
  24. @echo //////////////////////////////////////////////////
  25. arm-none-eabi-as -a -o rst_0.o rst_0.s
  26. clean:
  27. @echo //////////////////////////////////////////////////
  28. @echo Eliminando os arquivos de saida
  29. @echo //////////////////////////////////////////////////
  30. rm *.bin
  31. rm *.elf
  32. rm *.o

A execução ficaria assim:

  1. U:\arm_00>make
  2. //////////////////////////////////////////////////
  3. Montando o programa assembly
  4. //////////////////////////////////////////////////
  5. arm-none-eabi-as -a -o rst_0.o rst_0.s
  6. ARM GAS rst_0.s page 1
  7. 1 // Primeiro programa para ARM Cortex-M0
  8. 2
  9. 3 .code 16 // Define c├│digo como THUMB
  10. 4 .globl _start // Necessario para o linker
  11. 5
  12. 6 .equ STACK_INIT,0x020002000
  13. 7
  14. 8 .text // Inicio da área de código
  15. 9 _start:
  16. 10
  17. 11 // Inicio do Vector Table
  18. 12
  19. 13 0000 00200020 .word STACK_INIT // Inicio da pilha
  20. 14 0004 00000000 .word reset_handler // Endereco do Reset Handler
  21. 15
  22. 16 // define o label como função para que o linker
  23. 17 // resolva como c├│digo THUMB
  24. 18
  25. 19 .type reset_handler, function
  26. 20
  27. 21 reset_handler: // inicio do tratamento do reset
  28. 22
  29. 23 0008 0320 mov r0, #3 // carrega r0 com o valor 3
  30. 24 000a 0421 mov r1, #4 // carrega r1 com o valor 4
  31. 25 000c 0A18 add r2, r1, r0 // soma r0 e r1 guardando em r2
  32. 26
  33. 27 000e FEE7 _stop: b . // loop infinito
  34. ♀ARM GAS rst_0.s page 2
  35. DEFINED SYMBOLS
  36. rst_0.s:9 .text:00000000 _start
  37. rst_0.s:6 *ABS*:20002000 STACK_INIT
  38. rst_0.s:21 .text:00000008 reset_handler
  39. .text:00000000 $d
  40. rst_0.s:23 .text:00000008 $t
  41. rst_0.s:27 .text:0000000e _stop
  42. NO UNDEFINED SYMBOLS
  43. //////////////////////////////////////////////////
  44. Executando o linker
  45. //////////////////////////////////////////////////
  46. arm-none-eabi-ld -Ttext=0x00000000 -o rst_0.elf rst_0.o
  47. //////////////////////////////////////////////////
  48. Mostrando os simbolos apos o linker
  49. //////////////////////////////////////////////////
  50. arm-none-eabi-nm rst_0.elf
  51. 00008010 T __bss_end__
  52. 00008010 T __bss_start
  53. 00008010 T __bss_start__
  54. 00008010 T __data_start
  55. 00008010 T __end__
  56. 00008010 T _bss_end__
  57. 00008010 T _edata
  58. 00008010 T _end
  59. 00080000 T _stack
  60. 00000000 T _start
  61. 0000000e t _stop
  62. 00000008 t reset_handler
  63. 20002000 a STACK_INIT
  64. //////////////////////////////////////////////////
  65. Extraindo o arquivo binado sem os simbolos
  66. //////////////////////////////////////////////////
  67. arm-none-eabi-objcopy -O binary rst_0.elf rst_0.bin
  68. //////////////////////////////////////////////////
  69. Gerando a imagem final para carga na memoria flash
  70. //////////////////////////////////////////////////
  71. dd if=/dev/zero of=flash.bin bs=4096 count=16
  72. 16+0 records in
  73. 16+0 records out
  74. 65536 bytes (66 kB) copied, 0 seconds, Infinity B/s
  75. dd if=rst_0.bin of=flash.bin bs=4096 conv=notrunc
  76. 0+1 records in
  77. 0+1 records out
  78. 16 bytes (16 B) copied, 0 seconds, Infinity B/s
  79. U:\arm_00>

Pode parecer muito esforço para algo simples mas, quando uma aplicação tem muitos fontes, o utilitário MAKE facilitará o trabalho uma vez que ele sabe quais dependências precisam ser satisfeitas apenas executando procedimentos que estão desatualizados pela data dos arquivos.

quarta-feira, 4 de novembro de 2015

Primeiro programa para a placa STM32F0Discovery

No último post, vimos a arquitetura do processador ARM Cortex, da qual destacamos o Vector Table no endereço 0x00000000. A primeira entrada aponta para o início da pilha e, a partir da segunda, temos os endereços das funções de exceção. A primeira delas é a exceção de RESET do processador. Ela é a que usaremos neste primeiro programa. Eis o código:

  1. // Primeiro programa para ARM Cortex-M0
  2. .code 16 // Define código como THUMB
  3. .globl _start // Necessario para o linker
  4. .equ STACK_INIT,0x020002000
  5. .text // Inicio da área de código
  6. _start:
  7. // Inicio do Vector Table
  8. .word STACK_INIT // Inicio da pilha
  9. .word reset_handler // Endereco do Reset Handler
  10. // define o label como função para que o linker
  11. // resolva como código THUMB
  12. .type reset_handler, function
  13. reset_handler: // inicio do tratamento do reset
  14. mov r0, #3 // carrega r0 com o valor 3
  15. mov r1, #4 // carrega r1 com o valor 4
  16. add r2, r1, r0 // soma r0 e r1 guardando em r2
  17. _stop: b . // loop infinito

Vamos aos detalhes.

Como visto em um post anterior, usaremos o conjunto de ferramentas ou toolchain da GNU portanto sua sintaxe deve ser respeitada. Há muita documentação na internet do GNU Assembly e algumas delas estão nas referências. Primeiramente, um código fonte assembly tem vários tipos de instruções:

  • Instruções de máquina do dispositivo final sendo, no nosso caso: processador ARM Cortex M0;
  • Instruções para o próprio montador (assembler);
  • Instruções para o linker;
  • Comentários.
Os comentários são iniciados por @ ou por // onde o montador ignora o resto da linha. Pode ser também por um bloco delimitado por /* e */ podendo ser de mais de uma linha inclusive.
As instruções de máquina são específicas, no nosso caso, para o processador ARM Cortex M0, se resumindo a 56 instruções as quais podem ser vistas na figura abaixo:



O formato geral da sintaxe desse montador é a seguinte:


Onde label: é um rótulo para facilitar a identificação do endereço da instrução; mnemonic é ó códico mneumonico representando a instrução conforme as tabelas acima; operad1,operad2,... são os argumentos da instrução e dependente em número e formato da instrução propriamente dita.

Finalmente, temos as instruções para o montador e para o linker, ou diretivas. Elas começam com ponto e serão vistas e explicadas ao longo do seu uso.

Voltemos então ao programa.

Na linha 3, temos a primeira diretiva: .code 16  a qual diz ao montador para usar o conjunto de instruções de 16 bits Thumb.
Na linha 4, a diretiva .globl _start diz ao linker para considerar o símbolo _start como global. Isso é necessário pois o linker obriga a existência de um símbolo _start que será definido adiante.
Na linha 6, a diretiva .equ STACK_INIT,0x020002000 defini para o montador o símbolo STACK_INIT atribuindo a ele o valor 0x020002000 que, no nosso caso, é o endereço inicial da pilha.
Na linha 8, a diretiva .text diz ao linker que, nesse endereço, inicia-se o código executável do programa o qual reside na memória flash. Em contrapartida a essa diretiva, há a diretiva .data que diz ao linker qual é o inicio da área de memória volátil para uso do programa.
Na linha 9, finalmente, o símbolo _start é definido no formato de 'label:' e refere-se ao inicio do código propriamente dito.

Uma vez definido o tipo de instrução e o endereço inicial do código, podemos começar a definir o Vector Table. Até aqui, nenhuma posição de memória foi consumida.

Na linha 13, a diretiva .word ocupa uma palavra de 32 bits e define seu conteúdo como, no caso, o valor definido no símbolo STACK_INIT. A primeira posição do Vector Table foi preenchido com o endereço inicial da pilha.
Na linha 14, a nova diretiva .word define o endereço da função de tratamento da exceção de RESET, no caso, o símbolo reset_handler que será definido adiante. O uso de símbolos permite a flexibilidade de se postergar a codificação de funções ou mesmo relocá-las de posição sem dano ao funcionamento do código.
Na linha 19, temos a diretiva .type reset_handler, function necessária para dizer ao ao linker que o endereço referenciado pelo símbolo reset_handler é uma função, ou melhor, um código Thumb. Efetivamente, essa diretiva liga o bit menos significativo do endereço referenciado por esse símbolo sempre que ele for utilizado.
A partir da linha 21, está definida a função de tratamento da exceção de RESET a partir da definição do símbolo reset_handler como label. Essa função é simples contendo apenas 4 instruções:

  • Linha 23, carrega o valor 0x03 no registrador 0;
  • Linha 24, carrega o valor 0x04 no registrador 1;
  • Linha 25, soma os valores contidos nos registradores 0 e 1 guardando o resultado no registrador 2;
  • Linha 27, entra em loop infinito desviando para o endereço da própria instrução (representado como argumento ponto).
Para evitar uma mensagem de warning no montador, o programa deve encerrar com uma linha em branco.

Uma pausa antes de começar a gerar os códigos.

O processador ARM Cortex possui registradores os quais- são áreas de 32 bits dentro do processador e são usadas para controle da aplicação, do estado do processador e para a aplicação. São eles:


Os registradores que usamos: R0, R1 e R2, são de uso geral para aplicações assim como os registradores R3 a R12. O registrador R13 contem o ponteiro pra última posição da pilha utilizada. No nosso caso, ao iniciar o processador, esse registrador terá o valor definido por STACK_INIT, ou 0x20002000. O registrador R14 possui o endereço de retorno quando o processador desvia para executar funções. Ao iniciar, possui o valor 0xffffffff. O registrador R15 ou program counter, possui o endereço da próxima instrução a ser executada. Ao iniciar, é carregado com o endereço apontado pelo conteúdo da função de tratamento de RESET do Vector Table. No nosso caso, ao iniciar, possui o endereço definido por reset_handler. Os demais registradores serão vistos em posts futuros.

Vamos executar o nosso código.

Primeiramente, salvemo o programa como rst_0.s. A extensão .s representa um programa assembly.

Para compilá-lo, executemos o comando abaixo em uma janela de linha de comando do MS-Windows:

arm-none-eabi-as -a -o rst_0.o rst_0.s

O comando arm-none-eabi-as é o assembler do nosso ferramental instalado. Como parâmetros, estamos passando -a que indica ao assembler para gerar a listagem do que ele está montando; -o rst_0.o para gerar como saída o arquivo rst_0.o e, rst_0.s indica o arquivo fonte: no nosso caso, o nosso programa. A compilação gera uma saída como a da figura abaixo:



Como pedimos para gerar listagem da montagem, algumas informações estão presentes na figura: o símbolo STACK_INIT está definido como valor o absoluto 20002000. O símbolo _start está definido para o endereço de inicio ou zero. O símbolo reset_handler está definido com o valor 00000008 que é endereço da função de tratamento de RESET que codificamos.
Podemos ver na listagem de montagem que o Vector Table está preenchido para a sua primeira entrada que é o ponteiro inicial da pilha e este está no formato little-endian (que é o default do montador). Já o endereço da função de tratamento de RESET não está preenchida e o será quando executarmos o linker com o comando abaixo:

arm-none-eabi-ld -Ttext=0x00000000 -o rst_0.elf rst_0.o

O linker é o comando arm-none-eabi-ld e recebe como parâmetros -Ttext que resolve a diretiva .text do nosso programa para o endereço 0x00000000. O parâmetro seguinte: -o rst_0.elf indica ao linker para gerar um arquivo de saída no formato elf chamado rst_0.elf. Finalmente o último parâmetro é o arquivo de entrada para o linker que é o arquivo gerado na nossa compilação: rst_0.o.

O formato elf (Executable and Linking ou Extensible Linking Format) é um formato que abriga o programa executável além dos símbolos usados em seu código fonte úteis para o processo de depuração.

Se tudo correr sem problemas, o comando acima não gera relatório.

Para vermos vermos os símbolos resolvidos pelo linker, usamos o comando:

arm-none-eabi-nm rst_0.elf

A saída é a seguinte, onde podemos ver os símbolos de nosso programas:


Para executarmos o programa no microcontrolador apenas nos interessa o código binário gerado, não os símbolos. Para tanto precisamos extrair do arquivo rst_o.elf o código binário. Fararemos isso com o comando abaixo:

arm-none-eabi-objcopy -O binary rst_0.elf rst_0.bin

O comando arm-none-eabi-objcopy é um utilitário de cópia de objetos e estamos solicitando que o formato de saída seja binário e gere, a partir do arquivo rst_0.elf, o arquivo rst_0.bin. Esse comando também não gera geratório de saída. Utilizando um editor hexadecimal para observar o arquivo de saída termos:


Podemos ver, no endereço zero, o endereço inicial da pilha (0x20002000) no formato little-endian (0x00200020). Já na no endereço 0x04, temos o endereço do reset_handler já com o bit menos significativo ligado (0x00000009) no formato little-endian também (0x09000000) indicando que é um código Thumb. A partir do endereço 0x08 temos a sequência 0x032004210A18FEE7 que é o código objeto da função reset_handler que codificamos.

Vamos agora gerar uma imagem completa da memória flash do microcontrolador presente na placa STM32F0Discovery que tem o tamanho de 16 KBytes. Para tal, usaremos dois comandos. O primeiro gera uma imagem zerada de 16 Kbytes e o segundo copia nosso arquivo binário para a posição zero dessa imagem.

dd if=/dev/zero of=flash.bin bs=4096 count=16

O comando acima copia o arquivo default de zeros binários /dev/zero para o arquivo de saída flash.bin com 16 blocos de 4096 bytes cada. Teremos um arquivo de imagem da memória flash com 64 KBytes zerados.

dd if=rst_0.bin of=flash.bin bs=4096 conv=notrunc

Finalmente, copiamos o conteúdo do arquivo binário rst_0.bin para a imagem flash com o comando acima. Será copiado um bloco de 4096 bytes e o arquivo de saída não será truncado. O resultado está abaixo:


Pronto, já temos um arquivo de imagem de memória flash para carregar no microcontrolador. No software STM32 ST-Link Utility conectemos com o microcontrolador clicando no botão .

Na sequência, escolher a função: file -> open file  e escolher nosso arquivo flash.bin.


Pronto, a imagem foi carregada no utilitário:


Para carregar na memória flash do microcontrolador o arquivo aberto, usar o botão de programação: .


O endereço de carga vem preenchido com o endereço da flash que, no caso, é 0x08000000. Já vimos anteriormente que o endereço da memória flash desse microcontrolador é mapeada nesse endereço e também no endereço 0x00000000. Podemos manter preenchido ou mudá-lo. Para efetivar a carga, clicar no botão start.

Podemos ver nosso programa mostrado na tela. Como o utilitário converte o formato little-endian para big-endian para facilitar a nossa compreensão dos endereços, o código da função reset_handler a partir da posição 0x08000008 parece estar com os bytes invertidos. Basta mudarmos o display para 8 bits e tudo fica como realmente está na memória.


Vamos executar o código. Entremos na opção Target -> MCU Core. Teremos algo como a imagem abaixo:

Os registradores estão, aparentemente, com valores aleatórios e o processador esta em estado Halted (parado). Como o inicio da execução se dá com o RESET, cliquemos no botão System Reset.



O estado do processador muda para Running (executando) e não é possível ver o valor de qualquer registrador, Para tal, precisamos parar o processador clicando no botão Halt.


O processador volta ao estado Halted e podemos ver seus registradores. Como programado, o registrador R0 possui o valor 3, o R1 o valor 4 e o registrador R3 a soma dos dois, ou seja, 3+4=7. Notamos também que o registrador R13 aponta para o inicio da pilha conforme programamos. Notamos ainda que, o registrador R15 ou PC (program counter) possui o valor 0x0e que é o endereço da próxima execução ou o símbolo _stop do nosso programa (o loop infinito). Os demais registradores R3 a R12 e R14 possuem o valor inicial 0xffffffff.

Com isso concluímos a codificação e execução do nosso primeiro programa para o microcontrolador STM32F051R8T6 que equipa a placa STM32F0Discovery. Apesar de simples e de não ter uma utilidade prática, ele serviu para assimilar muitos conceitos.

No próximo post simplificaremos o processo de geração de objetos e imagem através do comando MAKE.

REFERÊNCIAS
http://tigcc.ticalc.org/doc/gnuasm.html
http://www.coranac.com/tonc/text/asm.htm
"Cortex-M0 Devices Generic User Guide", ARM
"The Definitive Guide to the ARM Cortex-M0",Joseph Yiu
https://pt.wikipedia.org/wiki/ELF

terça-feira, 3 de novembro de 2015

Antes do primeiro programa para a placa STM32F0Discovery

Antes de começarmos a programar o microcontrolador existente na placa STM32F0Dicovery, vamos entender alguns pontos da arquitetura de seu processador. Este implementa a arquitetura ARMv6-M que, por sua vez, implementa o conjunto de instruções ARMv6-M Thumb® . Com o intuito de poder ter seu processador disponível tanto para tecnologias ASIC quanto em dispositivos FPGA, a ARM emprestou o conjunto de instruções Thumb já existente arquitetura ARMv6 desenvolvendo uma nova arquitetura baseada no tratamento de exceções e de depuração da arquitetura ARMv7-M: a ARMv6-M, a qual equipa os processadores Cortex-M0 para ASIC e Cortex-M1 para FPGA.


O conjunto de instruções Thumb é um subconjunto de 16 bits do conjunto total de instruções de 32 bits da ARM. Cada instrução Thumb, além de trabalhar com o mesmo conjunto de registradores, possui uma correspondente de 32 bits que produz o mesmo efeito. Sendo de 16 bits, permite adensamento de código economizando espaço de memória flash e economia de energia, desejável em sistemas embarcados como, no caso, microcontroladores.

Sigamos então.

Estamos acostumados na programação de microcontroladores que, ao iniciar, a CPU executa a partir da posição zero de memória. No caso dos processadores ARM Cortex, o endereço zero de memória (mapeado na memória flash e remapeado no endereço 0x080000000 no processador STM32F051R8T6 presente na placa STM32F0Discovery) aponta para o chamado Vector Table que pode ser visto na figura abaixo:


Essa tabela começa, na posição 0x00000000, com o endereço inicial da pilha ou stack. Para esse processador, a pilha é descendente e o ponteiro para a pilha aponta para o último valor empilhado. Quando o processador é energizado, não há nada empilhado ainda, portanto a posição zero do Vector Table deve apontar para a última posição da memória RAM + 1. Novamente, no caso do processador STM32F051R8T6, que equipa a placa STM32F0Discovery, o endereço inicial da pilha deve ser 0x20002000. Quando for necessário guardar um valor na pilha, o processador subtrai 4 do ponteiro de pilha e armazena 4 bytes (ou 32 bits) na posição calculada. Na primeira vez que isso ocorrer, o processador obtém 0x20001ffc como novo ponteiro de pilha e armazena nas posições 0x20001ffc a 0x20001fff os 4 bytes.

Os processadores ARM Cortex podem organizar o acesso aos endereços na memória de duas formas: byte-invariant big-endian ou little-endian. Para o primeiro, os endereços são armazenados em registradores em ordem direta, do byte mais significativo ao menos significativo como na figura abaixo:

Byte-invariant big-endian
Já para o segundo, a ordem é inversa: do byte menos significativo para o mais significativo.
Little-endian
O microcontrolador STM32F051R8T6 gerencia todo o acesso em memória como little-endian. Se dermos uma olhada na memória do processador mostrada no post anterior, teremos:



Onde o endereço 0x20002000 armazenado na posição de memória 0x00000000 (ou 0x08000000 remapeada) como valor inicial do ponteiro de pilha é, na verdade, armazenado na sequência 0x00, 0x20, 0x00, 0x20.

Seguindo com o Vector Table, a partir da primeira entrada (ponteiro inicial da pilha), ele possui uma entrada para o endereço inicial de execução de cada uma das funções de exceção do microcontrolador. Por se tratar de um processador de 32 bits, cada entrada tem essa dimensão. Para indicar que o código presente nas funções de exceção são de 16 bits (código Thumb) o bit menos significativo de cada vetor deve ser um.

A primeira função de exceção, ou a de número um, é a função de RESET a qual é acionada sempre que o microcontrolador é reiniciado. Na prática, este é o endereço da primeira instrução de usuário a ser executada quando o microcontrolador inicia. Novamente no exemplo acima, temos o endereço 0x08000008 como endereço da função de tratamento do RESET. Como o bit menos significativo está ligado (0x08000009) essa função possui código Thumb.

No próximo post, faremos nosso primeiro programa para a placa STM32F0Discovery.

REFERÊNCIAS
"Cortex-M0 Devices Generic User Guide", ARM
"The Definitive Guide to the ARM Cortex-M0",Joseph Yiu
"The Thumb instruction set"- http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0210c/CACBCAAE.html
"PM0215 - STM32F0xxx Cortex-M0 programming manual" - STMicroelectronics