Aula 6 Assembly Saltos e Subrotinas 6.1 Objectivos......................... 1 6.2 Introdução........................ 1 6.3 Ciclos e outras estruturas de controlo........ 2 6.3.1 Ciclos.......................... 2 6.3.2 Estruturas if then else................. 4 6.3.3 Escolha múltipla.................... 5 6.4 Subrotinas......................... 5 6.5 Escolha automática de instruções........... 6 6.6 Utilização de segmentos recolocáveis......... 7 6.7 Problemas......................... 8 6.1 Objectivos Consolidação de conhecimentos sobre a linguagem assembly da família 51. Familiarização com as instruções de salto e chamada de subrotinas. Utilização da pilha (stack) e respectivo apontador (stack pointer). 6.2 Introdução Conclui-se com este guião uma breve introdução à linguagem assembly da família 51. Apresentam-se aqui diversos problemas que obrigam à utilização de instruções de salto e à organização do código em subrotinas. Estude com cuidado os exemplos apresentados e as considerações que os acompanham e depois tente resolver em casa, antes da aula, todos os problemas propostos. Poderá tirar as dúvidas que lhe surgirem na aula teórico-prática. 1
2 AULA 6. ASSEMBLY SALTOS E SUBROTINAS 6.3 Ciclos e outras estruturas de controlo A linguagem assembly não dispõe de nenhum mecanismo que leve a programar de modo estruturado. Assim, a estrutura de um programa é mais uma das responsabilidades que o programador deve assumir. Felizmente é fácil implementar algumas estruturas de controlo existentes em linguagens de alto nível recorrendo a instruções de salto. 6.3.1 Ciclos A maneira mais fácil de controlar a execução de determinado grupo de instruções um número constante de vezes é utilizando a instrução DJNZ (decrement and jump if not zero) que admite como operando um registo ou um endereço de memória de dados interna: LOC OBJ LINE SOURCE 1 ;============================================== 2 ; Inicializaç~ao de um vector em memória 3 ; for(i=0;i<16;i++) { 4 ; temp[i]=0 5 ; } 6 ; jpsousa@fe.up.pt 7 ;============================================== 8 0010 9 SIZE equ 16 ; Tamanho do vector 10 ---- 11 dseg at 40h 0040 12 temp: ds SIZE 13 ---- 14 cseg at 0 0000 7840 15 mov r0,#temp ; Endereço inicial 0002 E4 16 clr a 0003 7F10 17 mov r7,#size ; Tamanho 0005 F6 18 next: mov @r0,a ; Inicializa posiç~ao 0006 08 19 inc r0 0007 DFFC 20 djnz r7,next 21 22 end Repare-se, a propósito deste exemplo, na utilização da constante numérica SIZE definida na linha 9 e utilizada nas linhas 12 e 17. É também importante perceber o modo como se pode aceder de forma indirecta a uma variável: o apontador é inicializado (linha 15) e mais tarde (linha 18) utilizado para aceder às sucessivas posições que a variável ocupa. Um ciclo pode também ser do tipo enquanto ou do tipo repetir. O primeiro caracteriza-se por ter o teste de controlo logo no início o que faz com que o conjunto de instruções nele incluído possa nunca ser executado; o segundo caracteriza-se por ter o teste de controlo no fim pelo que o conjunto de instruções nele incluído é executado pelo menos uma vez: LOC OBJ LINE SOURCE 1 ;======================================= 2 ; Ciclo do tipo enquanto...
6.3. CICLOS E OUTRAS ESTRUTURAS DE CONTROLO 3 3 ; unsigned char x; 4 ; unsigned int y; 5 ; 6 ; while (x!=0) { 7 ; y=2*y; 8 ; x--; 9 ; } 10 ; jpsousa@fe.up.pt 11 ;======================================= 12 ---- 13 dseg at 40h 0040 14 y: ds 2 0042 15 x: ds 1 16 ---- 17 cseg at 0 0000 E542 18 tst: mov a,x ; Testa em A 0002 600F 19 jz done ; Acaba se A=0 20 21 ; Multiplica por 2 um número de 16 bits 0004 C3 22 clr c 0005 E541 23 mov a,y+1 ; Processa LSB 0007 33 24 rlc a 0008 F541 25 mov y+1,a 000A E540 26 mov a,y ; Processa MSB 000C 33 27 rlc a 000D F540 28 mov y,a 29 000F 1542 30 dec x 0011 80ED 31 sjmp tst 32 0013 33 done: 34 35 36 37 ;======================================= 38 ; Ciclo do tipo repetir... 39 ; unsigned char x; 40 ; unsigned int y; 41 ; 42 ; do { 43 ; y=2*y; 44 ; x--; 45 ; } while (x!=0); 46 ; jpsousa@fe.up.pt 47 ;======================================= 48 ---- 49 cseg at 0100h 50 0100 E541 51 next: mov a,y+1 ; Processa LSB 0102 25E0 52 add a,acc 0104 F541 53 mov y+1,a 0106 E540 54 mov a,y ; Processa MSB 0108 33 55 rlc a 0109 F540 56 mov y,a 57 010B D542F2 58 djnz x,next 59 60 end Repare-se que o teste que determina o fim do ciclo (linha 19) é específico do acumulador pelo que não chega decrementar a variável de controlo (linha 30) mas é necessário copiá-la para o acumulador (linha 18). Já no segundo caso (linha 58) o teste é efectuado directamente sobre a variável de controlo.
4 AULA 6. ASSEMBLY SALTOS E SUBROTINAS 6.3.2 Estruturas if then else As instruções de salto permitem uma implementação confortável deste tipo de estruturas de controlo nos programas escritos em assembly: LOC OBJ LINE SOURCE 1 ;======================================= 2 ; Teste de valores 3 ; #define MAXCNT 15000 4 ; unsigned int cont,val; 5 ; unsigned char state; 6 ; if(val==0) { 7 ; state++; 8 ; cont=maxcnt; 9 ; } else { 10 ; if(state==12) 11 ; state--; 12 ; } jpsousa@fe.up.pt 13 ;======================================= 14 3A98 15 MAXCNT equ 15000 16 ---- 17 dseg at 50h 0050 18 cont: ds 2 0052 19 val: ds 2 0054 20 state: ds 1 21 ---- 22 cseg at 0h 0000 E552 23 mov a,val 0002 4553 24 orl a,val+1 0004 700A 25 jnz nzero 0006 0554 26 inc state 0008 755098 27 mov cont,#low(maxcnt) ; LSB 000B 75513A 28 mov cont+1,#high(maxcnt) ; MSB 000E 8007 29 sjmp done 0010 740C 30 nzero: mov a,#12 0012 B55402 31 cjne a,state,done 0015 1554 32 dec state 0017 33 done: 34 Algumas considerações sobre o exemplo apresentado: 1. Uma vez que a variável val (linha 18) ocupa 2 bytes o teste efectuado (linhas 22, 23 e 24) deve incluir ambos. 2. Nas linhas 26 e 27 foram utilizados dois operadores suportados pelo assemblador (low e high) que calculam, respectivamente, a metade menos significativa e mais significativa de um valor de 16 bits. 3. Repare-se finalmente nos códigos das instruções de salto utilizadas neste exemplo (linhas 24, 28 e 30) o último byte de cada instrução representa a distância entre o endereço da próxima instrução e o endereço destino do salto; respectivamente 10, 7 e 2 bytes. Nem todas as instruções de salto são relativas, algumas indicam de modo absoluto o endereço destino.
6.4. SUBROTINAS 5 6.3.3 Escolha múltipla A instrução de comparação CJNE permite também implementar estruturas de escolha múltipla semelhantes às que existem em linguagens de alto nível: LOC OBJ LINE SOURCE 1 ;======================================= 2 ; Escolha múltipla 3 ; unsigned char op,pin; 4 ; switch (op) { 5 ; case 0: pin=100;break; 6 ; case 9: pin=200;break; 7 ; default: pin=0; 8 ; } 9 ; jpsousa@fe.up.pt 10 ;======================================= 11 ---- 12 dseg at 50h 0050 13 op: ds 1 0051 14 pin: ds 1 15 ---- 16 cseg at 100h 0100 E550 17 mov a,op 0102 B40004 18 cjne a,#0,not0 ; A = 0? 0105 7464 19 mov a,#100 0107 8008 20 sjmp Done 0109 B40904 21 Not0: cjne a,#9,not9 ; A = 9? 010C 74C8 22 mov a,#200 010E 8001 23 sjmp Done 0110 E4 24 Not9: clr a ; default... 0111 F551 25 Done: mov pin,a 26 27 end Algumas considerações sobre o exemplo apresentado: 1. A comparação (linhas 18 e 21) não estraga o acumulador pelo que basta carregá-lo uma vez (linha 17). 2. Novamente se nota nos códigos da instrução de comparação e salto (linhas 18 e 21) a distância entre o endereço da próxima instrução e o endereço destino do salto, 4 bytes nos dois casos deste exemplo. 3. A instrução MOV (linha 25) completa a execução. É sempre executada qualquer que seja a escolha feita. 6.4 Subrotinas As instruções de chamada de subrotinas 1 contribuem decisivamente para a estrutura dos programas ao permitirem dividir uma tarefa complexa em tarefas mais simples, cada uma executada por uma rotina que em devido tempo será chamada. 1 Os termos rotina e subrotina serão usados indistintamente para especificar um conjunto de instruções que executam uma tarefa específica no contexto da resolução de um problema mais abrangente.
6 AULA 6. ASSEMBLY SALTOS E SUBROTINAS LOC OBJ LINE SOURCE 1 ;======================================= 2 ; Atraso configurável 3 ; 4 ; delay(n) { 5 ; for(i=n;i>0;i--); 6 ; } 7 ; delay(50); 8 ; delay(100); 9 ; jpsousa@fe.up.pt 10 ;======================================= 11 ---- 12 cseg at 0 0000 7F32 13 mov r7,#50 0002 120680 14 lcall delay 0005 7F64 15 mov r7,#100 0007 D180 16 acall delay 0009 80FE 17 stop: sjmp stop 18 ---- 19 cseg at 0680h 0680 00 20 delay: nop ; Par^ametro de entrada em r7 0681 00 21 nop 0682 DFFC 22 djnz r7,delay ; Repete R7 vezes 0684 22 23 ret 24 25 end Algumas considerações sobre o exemplo apresentado: 1. A codificação manual da instrução LCALL (linha 14) é bastante mais simples e directa do que a da instrução ACALL (linha 16). Naquela aparece de forma evidente o endereço destino enquanto nesta parte do endereço é incorporada no código da instrução [1, páginas 17 e 36]. 2. A instrução de salto é necessária para encravar o programa, caso contrário depois de executada a instrução da linha 16 o programa executaria novamente a subrotina e corromperia a stack ao executar um RET sem ter executado primeiro o ACALL ou LCALL correspondente. 6.5 Escolha automática de instruções A escolha de uma entre as três instruções de salto incondicional que esta família suporta (ljmp, ajmp e sjmp) faz-se em função do tamanho do salto que se pretende dar. A maior parte dos assembladores actualmente existentes permite fazer essa escolha de uma forma automática: basta indicar como instrução a mnemónica genérica jmp que o próprio assemblador escolherá a instrução mais indicada das três. O mesmo se passa relativamente à chamada de subrotinas: basta utilizar a mnemónica genérica call que o assemblador escolherá a instrução lcall ou acall dependendo da distância a que estiver a rotina que se pretende chamar.
6.6. UTILIZAÇÃO DE SEGMENTOS RECOLOCÁVEIS 7 6.6 Utilização de segmentos recolocáveis A utilização de segmentos absolutos, já abordada anteriormente [3], tem a grande desvantagem obrigar a um grande cuidado na sua utilização para garantir a inexistência de sobreposições entre segmentos do mesmo tipo. Para evitar esse trabalho e facilitar a reutilização de código é recomendável trabalhar sempre com segmentos recolocáveis. Ao contrário de um segmento absoluto, um segmento recolocável é aquele do qual não se sabe, a priori, o endereço inicial pois ele é definido pelo linker, isto é, depois da compilação. A figura 6.1 ilustra as fases necessárias à criação de um ficheiro executável. Os segmentos recolocáveis definidos no código fonte ainda não estão definidos nos ficheiros objecto, estando por isso incompleta a codificação das instruções. O processo de codificação é completado pelo linker antes da geração do ficheiro executável. Assemblador / Compilador Assemblador / Compilador Linker Ficheiro executável Ficheiros fonte Assemblador / Compilador Ficheiros objecto Figura 6.1: Processo de geração de um ficheiro executável A definição de um segmento recolocável faz-se com o comando segment e a sua activação com o comando rseg. O exemplo ilustra a sintaxe: rotinas segment code mensagens segment code vars segment data vars2 segment xdata ; Segmento recolocável em MP ; Segmento recolocável em MP ; Segmento recolocável em MDI ; Segmento recolocável em MDE rseg vars ; Activa o segmento de dados total: DS 2 ; Reserva dois bytes em MDI contador: DS 1 ; Reserva um byte em MDI rseg vars2 ; Activa o segmento de dados dados: DS 400 ; Reserva 400 bytes em MDE ; === Programa principal =============================================== cseg at 0000h ; Segmento absoluto em MP. ; Início do programa sempre no endereço 0.. ; --- Rotinas de E/S --------------------------------------------------- rseg rotinas ; Activa o segmento rotinas... ; === Mensagens do sistema ============================================= rseg mensagens ; Activa o segmento mensagens pin_msg: DB "Introduza o PIN: " ; Preenche 7 bytes algures em MP
8 AULA 6. ASSEMBLY SALTOS E SUBROTINAS No exemplo apresentado, a única conclusão que se pode tirar quanto a endereços é que o programa principal começa no endereço 0000h da memória de programas pois é o único segmento absoluto existente. Os endereços dos segmentos recolocáveis são definidos mais tarde, automaticamente, durante o processo de geração do código máquina. 6.7 Problemas Apresentam-se de seguida alguns problemas que requerem a utilização de instruções de salto e/ou de chamada de subrotinas. 1. Escreva um programa que calcule t = 19 i=0 y[i] supondo que: (a) os elementos do vector são todos inferiores a 13, (b) os elementos do vector são todos inferiores a 256. Declare as variáveis num segmento de dados absoluto com início em 30h. 2. Escreva um programa que implemente na variável Cont definida num segmento de dados recolocável um contador cíclico de 24 bits. Considere dois casos: (a) contagem crescente: 0, 1, 2,..., 2 24 1, 0, 1,..., (b) contagem decrescente. 3. Suponha um vector v com 50 elementos de um byte cada representados em complemento para 2. Escreva um programa que conte quantos elementos negativos, nulos e positivos existem no vector. As contagens devem ser guardadas nas variáveis nn, nz e np respectivamente. Declare todas as variáveis num segmento recolocável em MDI. Repita considerando agora que v está declarado num segmento recolocável em MDE. 4. Traduza para código máquina as soluções do problema anterior. 5. Escreva um programa que calcule a soma de controlo simples (soma efectuada sem considerar eventuais transportes) de uma zona de 256 bytes de memória com início apontado pelo registo R0. No fim: R0 fica a apontar para um endereço 256 posições acima do seu valor inicial e o resultado da soma deve ficar no acumulador. 6. Escreva uma rotina que calcule o valor mínimo de uma zona de memória de dados externa com início apontado pelo registo DPTR e comprimento indicado no registo R7. No fim, o mínimo deve ficar em R7. 7. Suponha já existente a rotina Upcase que converte para maiúscula a letra minúscula cujo código ASCII está no acumulador, devolvendo o resultado da conversão também no acumulador.
6.7. PROBLEMAS 9 (a) Escreva um programa que, recorrendo a essa rotina, converta para maiúsculas um texto em MDI com início apontado por R0 e cujo fim é indicado pelo código 0. (b) Escreva a rotina Upcase. 8. Suponha já existente a rotina CountBits que conta o número de bits a um do acumulador, devolvendo o resultado também no acumulador. (a) Escreva um programa que, recorrendo a essa rotina, conte o número de bits a um de uma zona de memória da dados interna com início apontado pelo registo R0 e comprimento indicado no registo R7. O resultado deve ficar em R7. (b) Escreva a rotina CountBits.
10 AULA 6. ASSEMBLY SALTOS E SUBROTINAS
Referências [1] Philips semiconductors; 80C51 family programmer s guide and instruction set; Setembro de 1997. [2] Ferreira, José Manuel; Resumo das instruções do 80C51 ; FEUP, Setembro de 2000. [3] Sousa, João Paulo; Assembly: Primeiros passos; FEUP, Setembro de 2003. [4] Atmel Wireless and Microcontrollers; 89C51 data sheet; Setembro de 1999. [5] Ferreira, José Manuel; Introdução ao Projecto com Sistemas Digitais e Microcontroladores; FEUP Edições, 1998, ISBM 972-752-032-4. 11