Introdução à programação do ARM Cortex-M0+ em Assembly

Este post faz parte da série ARM Cortex-M0+. Leia também os outros posts da série:

Um processador simples pode executar operações aritméticas, lógicas, teste e comparação, controle de fluxo e movimentação de dados. Tais operações e os recursos visíveis ao programador são especificados pela ISA.

 

No artigo de introdução foi apresentada a estrutura básica do processador e os recursos visíveis ao programador. Utilizando a ISA do ARM Cortex-M0+, este artigo apresenta o padrão ARM assembly para criar programas em assembly. Para introdução, serão especificadas as operações que caracterizam a arquitetura: load-store. No próximo artigo da série, os programas serão estruturados considerando as características de um microcontrolador.

 

 

Conhecendo a ferramenta de montagem

 

O desenvolvimento de um programa depende da ferramenta utilizada. Para os códigos mostrados nesse texto será utilizado o padrão ARM assembly. Cabe ressaltar que atualmente os códigos em assembly são escritos seguindo o padrão UAL (do inglês, Unified Assembler Language) [2]. A seguir são apresentadas algumas convenções do montador.

 

 

Definindo o código que será executado: Lista de Instruções

 

Cada linha de código – que possui uma instrução – segue o seguinte formato:

 

label    mnemônico    operando1,   operando2, .    ; Comentários

 

  • label: é utilizado para referenciar o endereço que a instrução está. É opcional, e pode ser utilizado como referência em outras instruções (instruções de desvio). Também pode ser utilizado como referência para o endereço de um dado. É importante ressaltar que o label deve iniciar na primeira coluna da linha (sem espaço).
  • mnemônico: é utilizado para indicar a instrução. Dependendo da instrução é necessário indicar os operandos.
  • Para operações de escrita na memória, o primeiro operando indica a origem, isto é, o registrador que contém o dado que será gravado na memória.
  • Para operações de leitura da memória, o primeiro operando indica o destino, isto é, o registrador que que receberá a informação que está na memória.
  • Instruções no formato ARM usam o primeiro operando como destino da operação.
  • Por fim, os comentários do programa são precedidos do símbolo ‘;’.

 

Alguns exemplos são mostrados a seguir. No padrão UAL o sufixo S deve ser utilizado nas instruções que alteram o conteúdo do registrador APSR [2]. Cabe, nesse ponto, apresentar o conteúdo do registrador APSR, em que N, Z, C e V representam, respectivamente: Valor negativo; Valor zero; Carry ou Borrow; Overflow.

 

Flags condicionais da ULA.
Figura 1: Flags condicionais da ULA.

 

Carregar um registrador com um valor constante (#constante) de 8 bits.

  • MOVS R0,#10 ; R0 = 10
  • MOVS R0,#0x0A ; R0 = 10

Movimentação de dados entre registradores

  • MOV R0,R1

Adição entre um registrador e uma constante de 3 bits

  • ADDS R0,R0,#1 ; R0 = R0 + 1
  • ADDS R1,R0,#1 ; R1 = R0 + 1

Adição entre um registrador e uma constante de 8 bits

  • ADDS R0,#10 ; R0 = R0 + 10

Adição entre registradores

  • ADD R0,R1 ; R0 = R0 + R1

Comparação entre registradores

  • CMP R0,R1 ;Z flag =1 se R0==R1

Desvio condicional

  • BEQ label ; salta para o endereço indicado se Z flag = 1

Desvio incondicional

  • B label ; salta para o endereço indicado

 

 

Instruindo o montador: Diretivas

 

Além das instruções, algumas diretivas podem ser utilizadas no programa. Para definir um valor constante no programa (#define) e referenciá-lo pelo nome, deve ser utilizada a diretiva EQU [2]. Um exemplo de aplicação é mostrado a seguir:

  • MAGIC_NUMBER EQU 10
  • MOVS R0, #MAGIC_NUMBER ; R0 = 10

 

Também é possível definir variáveis e valores constantes na memória. Para tal, as diretivas DCn são utilizadas [2]:

  • DCB: Reserva espaço para valores de um byte (-2^7 à 2^8-1). Também é utilizado para sequência de caracteres (string);
  • DCW: Reserva espaço para valores de dois bytes (-2^15 à 2^16-1);
  • DCD: Reserva espaço para uma palavra de quatro bytes (-2^31 à 2^32-1);
  • DCQ: Reserva espaço para uma palavra de oito bytes (-2^63 à 2^64-1);
  • DCFS: Ponto flutuante de precisão simples (4 bytes);
  • DCFD: Ponto flutuante de precisão dupla (8 bytes);
  • DCI: Utilizado para declarar uma instrução utilizando linguagem de máquina.

 

Exemplo: Loop com 10 iterações.

DLY EQU 10  ;#define EQU 10
	
	
Main_Loop
	MOVS R0,#DLY  ;R0 = 10
Dly_Loop
	SUBS R0,R0,#1 ;R0 = R0 - 1
	BNE Dly_Loop  ;Se R0!=0 então a flag Z (PSR) será 0. Logo a instrução não toma o desvio
	B Main_Loop   ;Salto incondicional para Main_Loop	
DLY EQU 10
	
Main_Loop
	MOVS R0,#DLY ;R0 = 10
	MOVS R1,#0   ;R1 = 0
Dly_Loop
	ADDS R1,R1,#1 ;R1 = R1 + 1
	CMP R1,R0     ;Compara se R1==R0, altera flag Z em APSR se for igual
	BNE Dly_Loop  ;Salta para se Z == 0
	B Main_Loop   ;Salto incondicional para Main_Loop		

 

 

Modos de Endereçamento

 

Como apresentado, a arquitetura do processador é caracterizada por sua operação load-store [1]. Isso significa que somente instruções load e store acessam a memória [3]. Assim sendo, tais instruções possibilitam várias formas de determinar o operando de origem e destino das operações. Além disso, é importante conhecer os tipos de dados que essas instruções operam [1]:

  • Byte: 8 bits;
  • Halfword: 16 bits;
  • Word: 32 bits;

 

É importante ressaltar que todo endereço acessado deve estar alinhado [1]. Isto é, o acesso de meia-palavra deve ser alinhado por 16 bits e os acessos de palavra precisam ser alinhados por 32 bits [3]. Qualquer violação gerará uma exceção do tipo Hardware Fault [1].

 

Para os modos de endereçamento apresentados a seguir:

  • Leitura: Rd significa registrador de destino; Rm/Rn, registrador de origem; #imm, constante de N bits.
  • Escrita: Rd significa registrador de origem; #imm, constante de N bits.

 

Imediato

 

A instrução MOV permite carregar uma constante de 8 bits em um registrador. Esse método é chamado de imediado porque a constante é definida na própria instrução.

 

Descrição

Sintaxe

Valor imediato de 8 bits

MOVS Rd, #<imm>

 

Por Registradores

 

Movimentações internas entre registradores são feitos pela instrução MOV.

 

Descrição

Sintaxe

Entre registradores da parte baixa (low registers)

MOVS Rd, Rm

Qualquer registrador

MOV Rd, Rm

Altera valor de PC (PC = Rm)MOV PC, Rm

 

Endereçamento por deslocamento

 

O endereço acessado na memória é dado por uma combinação de registradores/valores constantes.

 

Registrador base com deslocamento constante

 

Um valor imediato de 5 bits é somado (offset) com o valor de um registrador (Rn).

 

Descrição

Sintaxe

Word, offset imediato

Rd = MEMÓRIA[Rn + (offset << 2)]

LDR Rd, [Rn, #<imm>]

Halfword, offset imediato

Rd = MEMÓRIA[Rn + (offset << 1)]

LDRH Rd, [Rn, #<imm>]

Byte, offset imediato

Rd = MEMÓRIA[Rn + offset]

LDRB Rd, [Rn, #<imm>]

Word, offset imediato

MEMÓRIA[Rn + offset] = Rd

STR Rd, [Rn, #<imm>]

Halfword, offset imediato

MEMÓRIA[Rn + offset] = Rd

STRH Rd, [Rn, #<imm>]

Byte, offset imediato

MEMÓRIA[Rn + offset] = Rd

STRB Rd, [Rn, #<imm>]

 

Registrador base com deslocamento por registrador

 

Todos os registradores envolvidos são da região Low Registers.

Descrição

Sintaxe

Word, deslocamento por registrador

Rd = MEMÓRIA[Rn + Rm]

LDR Rd, [Rn, Rm]

Halfword, deslocamento por registrador

Rd = MEMÓRIA[Rn + Rm]

LDRH Rd, [Rn, Rm]

Byte, deslocamento por registrador

Rd = MEMÓRIA[Rn + Rm]

LDRB Rd, [Rn, Rm]

Word, deslocamento por registrador

MEMÓRIA[Rn + Rm] = Rd

STR Rd, [Rn, Rm]

Halfword, deslocamento por registrador

MEMÓRIA[Rn + Rm] = Rd

STRH Rd, [Rn, Rm]

Byte, deslocamento por registrador

MEMÓRIA[Rn + Rm] = Rd

STRB Rd, [Rn, Rm]

 

Registrador base com deslocamento (sinalizado) por registrador

 

O valor de leitura é ajustado considerando o sinal.

 

Descrição

Sintaxe

halfword sinalizado, offset por registrador

Rd = MEMÓRIA[sinalizado(Rn + Rm)]

LDRSH Rd, [Rn, Rm]

byte sinalizado, offset por registrador

Rd = sinalizado(MEMÓRIA[Rn + Rm])

LDRSB Rd, [Rn, Rm]

 

Relativo

 

Nessas operações, o valor imediato é uma constante de 8 bits. Cabe ressaltar que essas instruções têm desvio máximo que pode ser alcançado [1].

 

Descrição

Sintaxe

relativo ao PC

Rd = MEMÓRIA[(PC+4) + (offset << 2)]

O deslocamento do PC considera o pipeline de 2 estágios

LDR Rd, <label>

relativo ao SP

Rd = MEMÓRIA[SP + (offset << 2)]

LDR Rd, [SP, #<imm>]

relativo ao SP

MEMÓRIA[SP + (offset << 2)] = Rd

STR Rd, [SP, #<imm>]

 

Múltiplos registradores

 

O argumento loreglist representa a lista de registradores (Low Registers) que serão manipulados. Já Rn, indica o endereço base da operação que ocorre da seguinte maneira: o registrador com menor número acessa o menor endereço; o registrador com maior número acessa o maior endereço. A ordem dos registradores não faz diferença.

 

Descrição

Sintaxe

Múltiplo, o endereço final da operação é gravado em Rn

<loreglist> = MEMÓRIA[Rn]

LDM Rn!, {<loreglist>}

Múltiplo, o endereço indicado por Rn não é alterado

<loreglist> = MEMÓRIA[Rn]

LDM Rn, {<loreglist>}

Múltiplo

MEMÓRIA[Rn] = <loreglist>

STM Rn!, {<loreglist>}

 

Exemplos de aplicação

 

A seguir são demonstrados dois casos em que um vetor de inteiros é preenchido com os valores armazenados em outro vetor. No primeiro exemplo, a cópia é realizada item por item. No segundo, a cópia é realizada de 4 em 4.

 

	AREA    ASM_CODE, CODE, READONLY
	THUMB
Main_Loop
	MOVS R0,#0		;Contador.
	LDR R1,=src    ;R1 = [PC+offset até src]. O valor de SRC está gravado na memória de programa.
	LDR R2,=dst    ;R2 = [PC+offset até dst]. O valor de DST está gravado na memória de programa.	
Copy_Loop
	LSLS R3,R0,#2   ;R3 = (R0 << 2). Multiplica o valor do contador por 4 (alinhamento de palavra).
	LDR R4,[R1,R3]  ;R4 = MEMÓRIA[R1 + R3] -> Endereço base de SRC + offset -> R4 = SRC[Contador].
	STR R4,[R2,R3]  ;MEMÓRIA[R2 + R3] = R4. -> Endereço base de DST + offset -> DST[Contador] = R4.
	ADDS R0,R0,#1   ;R0 = R0 + 1. Contador++.
	CMP R0,#4       ;R0 == 4 ? (V) Zflag = 1 : (F) Zflag=0.
	BNE Copy_Loop   ;Zflag == 1? (V) Vai para Main_Loop : (F) Vai volta para Copy_Loop.
	B Main_Loop		;Salta para Main_Loop	

;Constante armazenada na memória de programa
src DCD 10,20,30,40
	
	AREA    ASM_DATA, DATA, READWRITE
;Vetor de destino (memória ram)
dst DCD 0,0,0,0

	END

 

Observações:

  • THUMB indica que as instruções são Thumb e o código é UAL.
  • AREA indica ao assembler que a sequência de comandos, código ou dados, pertence à região especificada.

 

	AREA    ASM_CODE, CODE, READONLY
	THUMB
Main_Loop
	MOVS R0,#0		;Contador.
	LDR R1,=src    ;R1 = [PC+offset até src]. O valor de SRC está gravado na memória de programa.
	LDR R2,=dst    ;R2 = [PC+offset até dst]. O valor de DST está gravado na memória de programa.	
Copy_Loop
	LSLS R3,R0,#2   ;R3 = (R0 << 2). Multiplica o valor do contador por 4 (alinhamento de palavra).
	LDM R1!,{R4,R5,R6,R7}  ;Carrega os registradores R4~R7 com os valores de SRC[Cont+0]~SRC[Cont+3]. No final R1 = R1 + 4*4;
	STM R2!,{R4,R5,R6,R7}  ;Grava em SRC[Cont+0]~SRC[Cont+3] os registradores R4~R7. No final R2 = R2 + 4*4;
	ADDS R0,R0,#4   ;R0 = R0 + 4. Contador+=4.
	CMP R0,#8       ;R0 == 8 ? (V) Zflag = 1 : (F) Zflag=0.
	BNE Copy_Loop   ;Zflag == 1? (V) Vai para Main_Loop : (F) Vai volta para Copy_Loop.
	B Main_Loop		;Salta para Main_Loop	

;Constante armazenada na memória de programa
src DCD 10,20,30,40,50,60,70,80
	
	AREA    ASM_DATA, DATA, READWRITE
;Vetor de destino (memória ram)
dst DCD 0,0,0,0,0,0,0,0

 

 

Sabia mais

 

O conjunto de instruções é bastante flexível e também apresenta variações entre arquiteturas. Para conhecer as instruções e suas restrições consulte o conjunto de instruções no manual do processador e também no manual do compilador.

 

ARM Cortex M0+

Configuração e inicialização de microcontroladores: Um estudo utilizando ARM Cortex-M0+

TickAttack: um gerenciador de tarefas simples para um ARM Cortex-M0

 

 

Referências

 

[1] Cortex-M0+Technical Reference Manual.

[2] Arm® Compiler armasm User Guide.

[3] STALLINGS, W. Arquitetura e organização de computadores. Pearson Prentice-Hall, 10ª ed., São Paulo. 2017.

Outros artigos da série

<< Introdução ao processador ARM Cortex-M0+
Este post faz da série ARM Cortex-M0+. Leia também os outros posts da série:
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Fernando Deluno Garcia
Fascinado por computação, especialmente na interface entre hardware e software, me engajei na área de sistemas embarcados. Atuo com desenvolvimento de sistemas embarcados e sou docente da Faculdade de Engenharia de Sorocaba.Para mais informações: https://about.me/fdelunogarcia

5
Deixe um comentário

avatar
 
3 Comment threads
2 Thread replies
4 Followers
 
Most reacted comment
Hottest comment thread
4 Comment authors
Fernando Deluno GarciaEder AndradeLAURIANO ELMIRO DUARTEAlessandro Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
Eder Andrade
Membro

Muito bom Fernando!

LAURIANO ELMIRO DUARTE
Visitante
laurianoelmiroduarte

show show, saberia onde eu posso comprar um ARM Cortex-M0+ ? ou tipo uma placa pra programar que tenha um deles ?
abraços

Eder Andrade
Membro

Lauriano, você pode comprar kits STM32Fx Discovery que tenha o Cortex M0+ ou M0 na Mouser, Digikey ou em sites com o Aliexpress.

Alessandro
Visitante
Alessandro

Ótimo artigo, espero pelos próximos