Baixe o app para aproveitar ainda mais
Prévia do material em texto
Programação do Microcontrolador i8051 em Assembly Departamento de Engenharia Electrotécnica do Instituto Superior de Engenharia de Coimbra Material de Apoio para a disciplina de Microprocessadores Realizado por Fernanda Madureira Coutinho 1 Índice 1 - INTRODUÇÃO 3 2 - INTRODUÇÃO AO ASSEMBLY E COMPILADORES 4 2.1 – Introdução 4 2.2 - Programação em assembly versus programação em alto nível 4 2.3 - Assembler e compiladores 5 3 - REGISTOS 8051 6 3.1 – Introdução 6 3.2 - Registos Genéricos 6 3.3 - Registos de Função Específica (SFR) 6 4 - DESCRIÇÃO DOS MODOS DE ENDEREÇAMENTO 8 4.1 – Introdução 8 4.2 - Endereçamento com valor imediato 8 4.3 - Endereçamento directo 8 4.4 - Endereçamento indirecto 8 4.5 - Endereçamento indirecto indexado 9 5 - OPERAÇÕES BÁSICAS EM ASSEMBLY 10 5.1 - MOV – MOVe (instrução de atribuição) 10 5.2 - Instruções aritméticas 10 5.3 – Instruções para teste da flag de carry 12 5.4 - Operações sobre bits 12 5.5 - Operações no espaço de dados externo 12 5.6 - Operações no espaço de código 13 6 - ESTRUTURAS DE CONTROLO DE FLUXO DE EXECUÇÃO 15 6.1 – Introdução 15 6.2 - Instruções de salto incondicional 15 6.3 - Instruções de salto condicional aplicadas a bit 16 2 6.4 - Instruções de salto condicional aplicadas a registos 17 6.5 - Transferência de dados entre espaços de endereçamento de dados 20 6.5.1 - Memória interna/memória interna 20 6.5.2 - Memória externa/interna 20 6.5.3 - Memória interna/externa 20 7 - SUBROTINAS 22 7.1 – Introdução 22 7.2 - Chamada e retorno de subrotinas (A/LCALL, RET) 22 7.3 - Pilha 22 8 - TEMPORIZADORES 25 8.1 - Descrição do temporizador e seus SFRs e modos de funcionamento 25 8.2 - Programação dos SFRs do temporizador 25 8.3 - Modos de operação 27 8.4 - Exemplos 28 9 - CANAL SÉRIE 37 9.1 – Introdução 37 9.2 - Registos de Função Específica 37 9.3 - Modos de funcionamento 38 9.4 - Exemplos 40 10 - PROCESSAMENTO DE INTERRUPÇÕES 43 10.1 - Introdução 43 10.2 - Tabela de Vectores de Interrupção 44 10.3 - Registos de Função Específica 45 10.4 - Interrupção externas 47 10.5 - Pooling vs interrupções 48 10.6 – Exemplos 50 11 - SISTEMAS MULTIPROCESSADOR BASEADOS EM 8051´S 53 11.1 – Introdução 53 11.2 - Arquitectura Mestre/Escravo 53 3 Capítulo 1 Introdução A família MCS-51 é uma família de microcontroladores desenvolvida originalmente pela Intel em 1980. Devido ao sucesso que estes µCs tiveram no mercado, actualmente existem várias empresas que desenvolvem e comercializam microcontroladores desta família. O 8051 foi o primeiro microcontrolador (µC) da família MCS-51 a ser produzido e comercializado, sendo por isso o µC de referência desta família. As principais características do µC 8051 são as seguintes: 8 Espaço de Dados Interno (00h-FFh) RAM (00-7Fh = 128 bytes) (00h - 1Fh) 4 Bancos de Registos = 4x (R0,R1,R2,R3,R4,R5,R6,R7). (20h - 2Fh) 210 bits endereçáveis individualmente. (30h – 7Fh)128 Bytes de RAM interna. Registos de Função Específica (80h-FFh) 8 Espaço de Código Interno (00h - 1000h) ROM (00-1000h = 4 Kbytes) 8 Espaço de Dados Externo (0000h-FFFFh) 64K de espaço de dados externo 8 Espaço de Código Externo (0000h-FFFFh) 64K de espaço de código externo 8 Buses: Bus de dados de 8 bits Bus de endereço de 16 bits 8 Interface série 8 2 Temporizadores de 16 bits cada 8 4 Portos bidireccionais Os outros microcontroladores da família MCS-51 apresentam algumas variações, tais como o tamanho de ROM e RAM interna e a existência de temporizadores adicionais, EPROM interna ou periféricos adicionais. 4 Capítulo 2 Introdução ao Assembly e Compiladores 2.1 – Introdução Todos os µC/µP funcionam através da execução de programas que consistem num conjunto de instruções atómicas. O conjunto de todas estas instruções que um dado µC/µP é capaz de executar é a linguagem máquina desse µC/µP. Estas instruções são codificadas e guardadas como números inteiros. A linguagem assembly consiste num conjunto de mnemónicas (palavras chave) que são associadas a cada uma das instruções de linguagem máquina, para simplificar a programação dos µCs e tornar os programas legíveis a um leitor humano. Uma característica normal de uma “família” de µC é a utilização de uma linguagem assembly comum (ou, pelo menos, a utilização de um subconjunto de instruções assembly comum)1. Por exemplo, todos os µCs da família MCS-51 são normalmente capazes de executar um programa que funcione no µC 8051 (apesar do inverso poder não ser verdade, porque estes podem ter instruções adicionais que não existem no 8051). 2.2 - Programação em assembly versus programação em alto nível A linguagem assembly é normalmente designada como sendo uma liguagem de baixo nível, porque cada instrução de assembly corresponde exactamente a uma instrução executada pelo µC. Por oposição, as linguagens de alto nível (como o C, Pascal, Fortran, PLM-51 e BASIC entre outras) são compostas por instruções mais complexas, as quais poderão necessitar de múltiplas instruções de código máquina para serem executadas. A decisão de programar em baixo nível ou em alto nível é tomada levando em conta o compromisso existente entre as características de cada uma das linguagens e a aplicação alvo a que se destina: 1 Na verdade, a compatibilidade verifica-se quase sempre ao nível do código máquina, ou seja, à codificação utilizada para armazenar cada instrução. 5 8 Programação em baixo nível – Permite gerar código mais compacto2 e mais rápido3. Podem ser manipuladas todas as as características particulares do µC. O tamanho do ficheiro de código fonte é muito superior, porque é necessário discriminar exactamente todas as instruções que o µC vai executar. O programa é menos legível, porque a função de cada linha de código só se torna perceptível se analisarmos o contexto em que se insere. O desenvolvimento do programa é mais complexo, demorando mais tempo a desenvolver e estando mais sujeito a erros. 8 Programação em alto nível – Gera código menos compacto e mais lento. Pode não estar disponível o acesso a determinadas características particulares do µC, porque só podem ser utilizadas as instruções existentes na linguagem, as quais podem não estar preparadas para determinadas tarefas particulares. O tamanho do ficheiro de código fonte é menor. O programa é mais legível porque cada instrução tem normalmente um significado facilmente perceptível a um leitor humano. O desenvolvimento do programa é mais simples, sendo o desenvolvimento mais rápido e estando menos sujeito a erros. 2.3 - Assembler e compiladores O assembler é um compilador da linguagem assembly. Um compilador é uma ferramenta de software que efectua a tradução do código desenvolvido pelo projectista (linguagem de alto ou baixo nível) para código máquina. Normalmente, a designação “compilador” usa-se quando se está a falar de linguagens de alto nível. Para baixo nível, em vez de se falar de compilador de assembly, fala-se simplesmente de assembler. No caso de um assembler, a compilação consiste na tradução directa das mnemónicas para as instruções de código máquina correspondentes. Para cada par “microprocessador – linguagem” existe um compilador específico. Por exemplo: se quisermos programar o 8051 na linguagem C teremos de usar um compilador de C para o 8051 (ex: c51 da Keil Software); se quisermos programar o 8051 em assembly teremos de usar um assembler para o 8051 (ex: ses51 da Scientific Educational SystemsLtd.). 2 Um programa é mais compacto (mais pequeno) do que outro quando o número total de instruções máquina que o compõem é menor. 3 Um programa é mais rápido do que outro quando demora menos tempo a executar. Note que um programa maior pode ser mais rápido do que outro mais pequeno, e vice-versa. Note ainda que a velocidade de execução não é directamente proporcional ao número de instruções de código máquina executadas, porque instruções diferentes podem ter tempos de execução diferentes. 6 Capítulo 3 Registos 8051 3.1 – Introdução Um registo é uma unidade de memória na qual o µC pode guardar valores numéricos inteiros. O tamanho de um registo é dado pelo número de bits que o compõem. Por exemplo, um registo de 8 bits é capaz de guardar 256 valores distintos (0-255), enquanto que um registo de 16 bits é capaz de armazenar 65536 valores distintos (0-65535). No µC 8051, todos os registos são mapeados no espaço de dados interno. 3.2 - Registos Genéricos O µC 8051 tem 32 registos de utilização genérica, mapeados nas primeiras 32 posições do espaço de dados interno (00-1Fh). Estes registos são subdivididos em 4 bancos de 8 registos cada (R0,…,R7). Devemos confugurar qual o banco de registos que está a ser utilizado, especificação essa que é válida até haver uma nova especificação. A configuração de qual o banco de registos que está a ser utilizado num dado instante é feita no registo PSW. Por omissão, o 8051 considera que se está a usar o banco 0. 3.3 - Registos de Função Específica (SFR) O µC8051 tem 18 registos de 8 bits e 2 registos de 16 bits designados por registos de função específica (special function registers - SFRs). Alguns destes registos são endereçáveis bit a bit. Em seguida vamos descrever alguns desses registos, nomeadamente os registos dos portos (P0, P1, P2 e P3), o acumulador (A), o contador de programa (PC) e o DPTR (data pointer). 8 Portos Px, com x={0,1,2,3} Px.7 Px.6 Px.5 Px.4 Px.3 Px.2 Px.1 Px.0 7 Cada um dos portos está associado a um conjunto de 8 pinos do microcontrolador. Um programa pode receber ou enviar dados para o exterior através da leitura ou escrita, respectivamente, em cada um dos registos de portos (P0, P1, P2, P3 e P4). Cada um destes registos é endereçável bit a bit. Cada bit corresponde ao mapeamento do respectivo pino do respectivo porto. Por exemplo, se quisermos ler o estado do 4º pino do porto 1, bastará aceder e ler o bit P1.3. Todos os portos permitem o Input/Output de dados (interface com periféricos externos). No entanto, todos os portos, excepto o porto 1, podem ser eventualmente utilizados para outras funções pré-definidas. No caso de ser utilizada memória externa, o porto P0 serve para multiplexar o bus de dados com o bus de endereços (8 bits menos significativos). Quando a necessidade de memória externa é superior a 256 bytes, o porto P2 assegura o bus de endereços para os 8 bits mais significativos. Os pinos do porto 3 englobam um leque vasto de funções desde os pinos para comunicação série, pinos de controlo para leitura e escrita externa, entre outros. 8 Registo A (acumulador) O registo A é um registo não endereçável bit a bit, sendo utilizado por muitas instruções (aritméticas, lógicas ou de transferência de dados) para guardar o resultado das suas operações. 8 PC (Program Counter) O registo PC é um registo de 16 bits que indica o endereço em memória da instrução que vai ser executada. O seu valor vai sendo automaticamente actualizado (pelo processador) à medida que as instruções vão sendo executadas. Não deve nunca ser alterado pelo programador. 8 DPTR (Data Pointer) O registo DPTR é um registo de 16 bits que deve ser usado unicamente no acesso ao espaço externo de dados ou de código. É no DPTR que deve estar o endereço externo (16 bits) ao qual se pretende aceder. 8 Capítulo 4 Descrição dos Modos de Endereçamento 4.1 – Introdução Os modos de endereçamento são as várias formas através das quais podem ser especificados os argumentos das instruções de código máquina. Segue-se uma descrição sucinta dos modos de endereçamento disponíveis no µC 8051. 4.2 - Endereçamento com valor imediato Neste caso, os dados, precedidos por um ‘#’, são disponibilizados imediatamente para a execução da instrução. Estes dados não podem exceder o tamanho de 8 bits. Exemplo ADD A,#55h ;Soma ao conteúdo do acumulador o valor 55 hex. MOV R0,#20 ;Carrega o registo R0 com o valor 20 decimal. 4.3 - Endereçamento directo Este tipo de endereçamento é utilizado para aceder a variáveis da memória interna ou a registos de hardware quando o seu endereço é conhecido e constante. Exemplo MOV P0,A ;Envia pelo porto P0 o conteúdo do acumulador MOV A,34h ;Carrega no registo A o conteúdo da célula de ;memória de endereço 34h 4.4 - Endereçamento indirecto Utilizado para aceder a dados localizados no endereço indicado por R0 ou R1 (endereço de 1 byte). São utilizados os símbolos ‘@R0’ ou ‘@R1’ para indicar que está a ser usado endereçamento indirecto com R0 ou R1, respectivamente. Exemplo: 9 MOV R0,#23H MOV A,@R0 ;Coloca em A o valor armazenado no endereço 23h 4.5 - Endereçamento indirecto indexado Utilizado para aceder ao espaço de endereçamento externo quando o endereço é de 2 bytes. O endereço da posição do espaço externo à qual se pretende aceder é carregado previamente no registo de 16 bits, DPTR. Os dados são então acedidos utilizando o símbolo ‘@DPTR’. Exemplo MOV DPTR,#1234h MOVX A,@DPTR ;guarda no acumulador o conteúdo do endereço ;1234h pertencente ao espaço de dados externo 10 Capítulo 5 Operações Básicas em Assembly Vão ser descritas algumas instruções de assembly usadas na manipulação de registos, nomeadamente operações de transferência de dados e aritméticas. A descrição destas e das restantes instruções assembly do 8051 podem ser consultadas no Anexo C o qual faz parte do material de apoio da disciplina. 5.1 - MOV – MOVe (instrução de atribuição) Esta instrução permite a transferência de dados entre posições de memória e/ou registos. O primeiro parâmetro é o destino e o segundo a origem dos dados. Exemplo No endereço de RAM interna 20h está o valor 15h. O valor do endereço 15h é 10h. Então, as instruções: MOV R2,#20h ;R2Å20h MOV A,@R2 ;AÅ15h MOV R3,A ;R3Å15h MOV B,@R3 ;BÅ10h deixam o acumulador com 15h e o registo B com 10h. Não é permitida a escrita entre dois registos, excepto se um deles for o acumulador. Exemplo MOV R1,R2 deverá ser por exemplo MOV A,R2 MOV R1,A A instrução MOV possui ainda as variantes MOVX e MOVC, explicadas mais à frente. 5.2 - Instruções aritméticas Vão ser descritas algumas instruções aritméticas, nomeadamente instruções de adição, subtracção, incremento, decremento, divisão e multiplicação. Todas elas actuam sobre o 11 acumulador e deixam o resultado nesse mesmo registo. As instruções aritméticas podem originar overflows. Quando ocorre um overflow, a flag de carry C é posta a 1. Para verificar se ocorreu ou não overflow tem de se analisar qual o estado da flag de carry após a operação aritmética, usando para o efeito as instruções de teste da flag de carry (ver mais à frente). 8 ADD – ADDition (adição) Exemplo MOV A,#03 ADD A,#55 ;adiciona ao conteúdo do acumulador o valor 55, ;ficando o acumulador com o valor 58. 8 INC – INCrement (incremento de uma unidade) Exemplo MOV A,#03 INC A ;Incrementa o conteúdo do acumulador de uma ;unidade,ficando o acumulador com o valor 4. 8 DEC – DECrement (decremento de uma unidade) Exemplo MOV A,#03 DEC A ;decrementa de uma unidade o valor armazenado no ;acumulador, ficando o acumulador com o valor 2. 8 DIV - DIVide (divide o valor de A por B, colocando o quociente em A e o resto em B) Exemplo MOV A,#31 ;AÅ31 MOV B,#2 ;BÅ2 DIV AB ;AÅ15, BÅ1 8 MUL - MULtiply (multiplica o valor de A por B, guarda o byte menos significativo do resultado em A e o byte mais significativo do resultado em B. Exemplo MOV A,#80 ;AÅ80(50H) MOV B,#160 ;BÅ160(0A0H) MUL AB ; Resultado de 80*160=12800 (3200H) ;AÅ00h, BÅ32h 12 5.3 – Instruções para teste da flag de carry 8 JC – Jump if Carry (salta se a flag de carry está a 1) 8 JNC – Jump if Not Carry (salta se a flag de carry está a 0) Estas são as duas únicas instruções que existem para teste da flag de carry. O argumento desta instrução é a label de salto, isto é, a label para onde o programa salta no caso de se verificar a respectiva condição. JC label_1 ; salta para label_1 se Carry=1 JNC label_0 ; salta para label_0 se Carry=0 5.4 - Operações sobre bits O valor dos bits endereçáveis individualmente pode ser manipulado directamente. Para esse efeito existem duas instruções complementares: SETB (SET Bit - coloca um bit a 1) e CLR (CLeaR bit - coloca um bit a 0). Existe ainda a instrução a CPL (ComPLement bit) que complementa o estado do bit. Exemplo SETB P1.0 ;Coloca o primeiro pino do porto P1 a 1 (nível de ;tensão Vcc) CLR P1.0 ;Coloca o primeiro pino do porto P1 a 0 (nível de ;tensão GND) SETB 07H ;Coloca a 1 o bit de RAM interna de endereço 7h CPL P1.0 ;O estado de P1.0 estava a 0 passando agora a 1 5.5 - Operações no espaço de dados externo Vai exemplificar-se como é feita a escrita e leitura no espaço de dados externo. Todos os acessos ao espaço externo tem necessariamente de ser feitos através da indicação do respectivo endereço. Se esse endereço fôr de 8 bits bastará um registo de 8 bits para guardar o endereço, mas, se o endereço fôr de 2 bytes, então já se torna necessário usar um registo de 2 bytes; esse registo é o DPTR. O registo DPTR é um registo de 16 bits usado para guardar o endereço do espaço de dados externo assim como também ao espaço de código (seja ele externo ou interno) ao qual se pretende aceder. 13 Todos os acessos ao espaço de dados externo, sejam de leitura ou de escrita, têm de ser feitos usando a instrução MOVX. A sintaxe da instrução MOVX é a seguinte: MOVX @registo,A ou MOVX A,@registo consoante se trate de uma operação de escrita ou de leitura, respectivamente, onde registo poderá representar: 8 R1…R7, se o endereço fôr menor ou igual a 8 bits 8 DPTR, se o endereço fôr superior a 8 bits Note que todos os valores que são lidos ou escritos na memória externa são sempre guardados no acumulador, porque não é possível utilizar outro registo para esse efeito. Exemplo Escrever no espaço externo de dados no endereço 23h (8 bits) o valor 3 decimal. MOV A,#3 ou MOV A,#3 MOV R0,#23h MOV DPTR,#23h MOVX @R0,A MOVX @DPTR,A Exemplo Lêr o valor do endereço 2033h (16 bits), do espaço externo de dados, para o acumulador. MOV DPTR,#2033h MOVX A,@DPTR 5.6 - Operações no espaço de código Vai ser exemplificado a forma como pode ser feita a leitura em memória de código, seja ela externa ou interna. Todos os acessos ao espaço de código têm de ser feitos usando a instrução MOVC. A sintaxe da instrução MOVC é a seguinte: MOVC A,A+@DPTR Exemplo Lêr o valor do endereço 2033h, do espaço de código. 14 MOV DPTR,#2033h MOV A,#0 MOVC A,A+@DPTR 15 Capítulo 6 Estruturas de Controlo de Fluxo de Execução 6.1 – Introdução As instruções de um programa são normalmente executadas sequencialmente. No entanto, é por vezes desejável alterar o percurso normal de execução destas instruções, especificando uma outra ordem de execução. As instruções de salto controlam o fluxo de execução de um programa, isto é, a ordem pela qual as instruções são executadas. Existem saltos condicionais e saltos incondicionais. Os saltos condicionais, contrariamente ao que acontece com os saltos incondiconais, são aqueles cuja execução está condicionada pelo valor lógico de um registo ou bit. As instruções de salto re-direcionam o fluxo do programa para uma instrução identificada por uma etiqueta (label). Essa label coloca-se antes da instrução para a qual se pretende fazer o salto. 6.2 - Instruções de salto incondicional Esta secção descreve três instruções de salto incondicional: AJMP, LJMP e SJMP. O comportamento destas instruções é idêntico, estando a diferença na distância e/ou localização possível da instrução para a qual se pretende saltar e tamanho em bytes da instrução de salto (em código máquina). A instrução de salto mais compacta é o SJMP, seguida do AJMP e por fim o LJMP. Por essa razão, a ordem de escolha preferencial deve ser essa. Por vezes, por uma questão de comodidade, utiliza-se sempre o LJMP durante a fase de desenvolvimento do programa, sendo este substituído quando o desenvolvimento está concluído pelas instruções mais eficazes se tal for possível. xJMP label da instrução destino com x={A,L,S} 8 SJMP – Short JuMP (Salta para instrução localizada a menos de 127 bytes de distância) Exemplo Ciclo: SETB P1.0 CLR P1.0 16 SJMP Ciclo ;Salta para instrução identificada pela label ;“Ciclo”. 8 LJMP – Long JuMP (Salta para instrução localizada em qualquer endereço de memória) Exemplo Ciclo: SETB P1.0 CLR P1.0 LJMP Ciclo2 ;Salta para instrução identificada pela ;label “Ciclo2”. … Ciclo2: SETB P1.1 CLR P1.1 LJMP Ciclo ;Salta para instrução identificada pela ;label “Ciclo”. 8 AJMP – Absolute JuMP (Salta para instrução dentro do mesmo bloco de 2 kbytes 4,5) Exemplo Inicio: MOV A,#00 ;instrução no endereço 0800h (2kbytes=2048 ;bytes) … AJMP Inicio … Bloco2: MOV A,#42 ;instrução no endereço 1000h (4kbytes). Não ;seria possível saltar para esta instrução ;com o AJMP (em vez de saltar para a label ;Inicio) porque já está dentro de outro bloco ;de 2 kbytes 6.3 - Instruções de salto condicional aplicadas a bit Vão ser descritas três instruções de salto condicional aplicadas a bit: JB, JNB e JBC. Na grande maioria dos casos, os bits que interessa manipular são os bits pertencentes aos SFR´s. 4 Estes blocos de 2 kbytes são contabilizados a partir do inicio do espaço de endereçamento e não a partir da instrução de salto (como é o caso no SJMP). Ou seja, são os blocos com endereços 0000h-07FFh; 0800h-0FFFh; … 5 Para se ser mais preciso, a instrução para a qual se salta tem de estar no bloco de 2 kbytes que inclui a instrução seguinte ao AJMP (o qual não é necessariamente o mesmo bloco no qual está a instrução de salto). 17 instrução bit de teste, label de salto, com instrução={JB,JNB,JBC} 8 JB - Jump if Bit (salta se bit a 1) Exemplo SETB P1.0 label_x: JB P1.0,label_x ;Se P1.0=1, salta para a label ;‘label_x’, ou seja, o programa ;fica retido nesta instrução ;enquanto P1.0 fôr 1 8 JNB - Jump if Not Bit (salta se o bit está a 0) Exemplo CLR TF0 ;Apaga flag label_y: JNB TF0,label_y ;Salta enquanto a flag é zero, ou ;seja, o programa fica retido ;nesta instrução enquanto TF0 fôr ;igual a 0 8 JBC - Jump if Bit and Clear (salta se bit a 1 e apaga-o em seguida) Repare-se que esta instrução tem uma diferençasignificativa relativamente às anteriores: testa o bit, e se fôr 1 apaga-o para 0, se fôr 0 não faz nada. Exemplo label_z: JBC P1.0,label_z ;Se P1.0=1, salta para a label ;‘label_z’ e coloca P1.0=0. Se ;P1.0=0 não faz o salto e continua ;para a instrução seguinte 6.4 - Instruções de salto condicional aplicadas a registos Vão ser descritas as principais instruções de salto condicional aplicadas a registos: JZ, JNZ, DJNZ e CJNE. As instruções JZ, JNZ e CJNE actuam exclusivamente sobre o registo acumulador, contrariamente ao que acontece com a instrução DJNZ. 8 JZ – Jump if Zero (salta se o conteúdo do acumulador fôr zero) 18 Esta instrução testa de forma implícita o registo acumulador. Se o valor deste fôr zero, então a instrução de salto é executada, caso contrário não é. Exemplo MOVX A,@DPTR JZ Turn_On 8 JNZ – Jump if Not Zero (salta se o conteúdo do acumulador fôr diferente de zero) Esta instrução testa de forma implícita o registo acumulador. Se o valor deste não fôr zero, então a instrução de salto é executada, caso contrário não é. Exemplo MOV A,#13h Decrementa:DEC A JNZ Decrementa ;A instrução de salto será ;executada 19 vezes até que o ;acumulador esteja a zero 8 DJNZ – Decrement and Jump if Not Zero Esta instrução pode ser aplicada a qualquer registo de 8 bits. A instrução primeiro decrementa e só depois testa o conteúdo do registo. A instrução de salto é efectuada se o conteúdo do registo ainda não fôr zero. Esta instrução é normamente utilizada para o controlo de ciclos. Exemplo Suponhamos que se pretende copiar o valor 20h para as células de RAM interna compreendidas entre os endereços 40h e 50h. MOV A,#20h ;Valor a copiar MOV R1,#40h ;Endereço inicial MOV R0,#10h ;Nº de vezes que o ciclo de cópia vai ;ser executado Copia: MOV @R1,A INC R1 ; Passa para o endereço seguinte DJNZ R0,Copia ; Se R0 ainda não é zero, isto é, se ;ainda não se atingiu o enedereço 50h, ;o ciclo de cópia continua. 19 8 CJNE – Compare and Jump if Not Equal Esta instrução compara o conteúdo do registo acumulador com um valor. Se forem iguais a instrução de salto é executada, caso contrário não é. Esta é a única instrução que existe para fazer a comparação entre dois valores. Esta instrução mexe na flag de carry da forma que se exmplifica a seguir. CJNE A,#valor,label ; se A>valor, a flag C->0 ; se A<valor, a flag C->1 ; se A=valor, não salta para label ; se A≠valor, salta para label Exemplo Vamos ler o valor de um periférico mapeado no endereço externo de dados 3450h. Se o valor lido for 0FFh, então o programa deve escrever para esse periférico o valor 00h. MOV DPTR,#3450h MOV A,@DPTR ; AÅvalor enviado pelo periférico CJNE A,#0FFh,Escrita … Escrita: MOV A,#00h MOV @DPTR,A Exemplo Vamos comparar o valor do acumulador com o valor 45h. Se o valor do acumulador for superior deve ligar o pino P1.0, se for inferior deve ligar o pino P1.1. Se forem iguais deve ligar o pino P1.2. CJNE A,#45h,Diferentes SETB P1.2 LJMP Fim Diferentes:JNC Superior ; se A>45h (C=0) JC Inferior ; se A<45h (C=1) Superior: SETB P1.0 LJMP Fim Inferior: SETB P1.1 Fim: LJMP Fim 20 6.5 - Transferência de dados entre espaços de endereçamento de dados 6.5.1 - Memória interna/memória interna Exemplo Como transmitir um bloco de dados com 20 bytes de tamanho do endereço 30h para o endereço 60h Inicio: MOV R0,#30H MOV R1,#60H MOV R3,#20 CopiaByte: MOV A,@R0 ; este ciclo é repetido 20 vezes MOV @R1,A INC R0 INC R1 DJNZ R3, CopiaByte 6.5.2 - Memória externa/interna Exemplo Como transmitir um bloco de dados com 20 bytes de tamanho do endereço externo 3F0Ah para o endereço interno 60h. Inicio: MOV R0,#60H MOV DPTR, #3F0AH CopiaByte: MOVX A,@DPTR MOV @R0,A INC DPTR INC R0 CJNE R0,#75H,CopiaByte ;60h+14h = 74H 6.5.3 - Memória interna/externa Exemplo Como transmitir um bloco de dados com 20 bytes de tamanho do endereço interno 60h para o endereço externo 3F0Ah. 21 Inicio: MOV R0,#60H MOV DPTR, #3F0AH CopiaByte: MOV A,@R0 MOVX @DPTR,A INC DPTR INC R0 CJNE R0, #75 ,CopiaByte ;60h+14h = 74H 22 Capítulo 7 Subrotinas 7.1 – Introdução Subrotinas são secções de código que, devido ao facto de serem necessárias em várias zonas distintas do programa, ou porque desempenham uma função específica e bem definida, são separadas do programa principal. As subrotinas são chamadas através de instruções específicas e quando terminam o seu processamento, retornam a execução ao ponto onde foram chamadas. A utilização correcta de subrotinas reduz o tamanho do código e torna a estrutura do programa mais clara e de compreensão mais fácil. 7.2 - Chamada e retorno de subrotinas (A/LCALL, RET) 8 A/LCALL nome da rotina – Instrução para chamar uma subrotina. As diferenças entre as duas formas da instrução são semelhantes às diferenças entre as instruções de salto AJMP e LJMP, aplicando-se as mesmas restrições e recomendações. Estas instruções funcionam essencialmente como um salto, mas apresentam a uma diferença. A subrotina necessita de saber o local onde deve continuar a execução uma vez que termine. Este local, designado por endereço de retorno, corresponde usualmente à instrução seguinte ao A/LCALL, pelo que esta instrução armazena o endereço dessa instrução numa estrutura de dados chamada Pilha. 8 RET – Instrução para terminar a subrotina e continuar a execução a partir do ponto onde a subrotina foi chamada. 7.3 - Pilha A pilha é uma zona de dados que representa uma fila na qual são colocados e retirados dados segundo uma política LIFO: Last In First Out. Isto significa que quando é retirado (lido) um valor da pilha, este é sempre o valor que lá foi colocado mais recentemente. Uma vez lido um valor, ele é normalmente removido da pilha, pelo que o próximo valor a ser removido será o segundo mais recente, e assim sucessivamente. Uma boa analogia para pilha 23 é, efectivamente, uma pilha de objectos como tijolos: tijolos novos são colocados no topo da pilha, e serão estes também os primeiros a serem removidos. A pilha é uma estrutura de dados especialmente útil para efectuar a gestão dos endereços de retorno das subrotinas, pelo que muitos µCs e µPs têm mecanismos de hardware especialmente dedicados à sua implementação. No caso dos µCs 8051, existe um registo, o SP (Stack Pointer) dedicado a guardar o endereço do topo da pilha. Sempre que é colocado um valor na pilha, este valor é incrementado, e sempre que é retirado um valor da pilha, este valor é decrementado, para apontar para o novo topo. As instruções PUSH e POP podem ser utilizadas pelo programador para colocar e retirar explicitamente valores da pilha: 8 PUSH endereço – Guarda o valor que está no endereço interno na pilha. O SP é incrementado. 8 POP endereço – Lê e remove valor da pilha. Guarda esse valor no endereço indicado e decrementa o SP. A pilha é utilizada automaticamente pelas funções A/LCALL e RET para armazenar o endereço de retorno da função. A instrução de chamada à subrotina coloca o endereço da instrução seguinte na pilha antes de saltar para a subrotina. A instrução RET continua a execução no endereço que está guardado no topo da pilha. Note-se que este sistema permite efectuar não só várias chamadas à mesma função em zonas de código distintas como também permite a utilização de funções recursivas. Uma outra utilização típica das pilhas é para efectuar a salvaguarda de valores deregistos de uma função. Como uma função pode, teoricamente, ser chamada de qualquer sítio, esta deve sempre garantir que nenhum registo sai alterado quando termina (a não ser, claro, quando este é usado para devolver o resultado da função). Se não fosse esse o caso, poderia haver conflitos entre os registos usados dentro e fora da subrotina. Imaginando que uma função altera o conteúdo de R1 e R2, por exemplo, então teríamos o seguinte código para preservar esses valores entre a entrada e saída: SubRotina: PUSH R1 24 PUSH R2 … ;código da função (utiliza R1 1e R2) POP R2 POP R1 RET Neste caso o valor dos registos R1 e R2 é guardado na pilha antes de estes serem alterados, e são restaurados aos valores originais antes de se sair da subrotina. Note que a ordem dos POP’s necessita de ser inversa relativamente à ordem dos PUSH’s (Porquê?). Por vezes é necessário inicializar a pilha convenientemente durante a fase de arranque do sistema. Isto é feito através da escolha de um valor inicial adequado para o SP. Quando se escolhe este valor, deve ter-se em atenção que no 8051 a pilha cresce para cima, isto é, desde os endereços menores para os maiores (há processadores nos quais se verifica o contrário). O espaço de memória alocado para a pilha deve ser suficiente para armazenar todos os valores que lá possam ser colocados, se tal valor for possível de determinar à partida. 25 Capítulo 8 Temporizadores 8.1 - Descrição do temporizador e seus SFRs e modos de funcionamento Um temporizador é um circuito que pode ser usado para contabilizar a passagem de tempo ou contabilizar o número de ocorrências de acontecimentos. O temporizador contabiliza o nº de transições descendentes do sinal presente à sua entrada. Esse sinal pode ser proveniente de duas fontes possíveis: relógio interno ou do exterior do chip através do pino Tx{x=0,1}. Se o sinal de entrada do temporizador for proveniente do sinal de relógio interno (1/12 freq. relógio externo), diz-se que está a funcionar como temporizador, se o sinal for proveniente de uma fonte externa (um sensor, por exemplo) diz-se que funciona como contador de acontecimentos externos. Note-se que quando um temporizador está a funcionar como “temporizador”, o nº de transições contabilizadas corresponde ao nº de períodos de relógio ocorridos até então (por cada período de relógio há uma transição descendente), pelo que se multiplicarmos o nº de transições pelo período do relógio interno, ficamos com a informação do tempo... O µC 8051 tem dois temporizadores internos de 16 bits cada e 4 modos de funcionamento possíveis. Quer isto dizer que cada um é capaz de contabilizar, no máximo, 65536 (216) transições descendentes do sinal presenta à sua entrada. Quando essa contagem “ultrapassa” a capacidade dos registos usados na contagem, dá-se o “overflow” e o temporizador assinala isso activando um bit dedicado para esse efeito, normalmente denominado por “flag de overflow”. Os temporizadores são independentes entre si e portanto cada um tem a sua própria flag de overflow. 8.2 - Programação dos SFRs do temporizador Para usar um temporizador é necessário, primeiro de tudo, programá-lo. Essa programação passa pela inicialização adequada dos seus registos de controlo, isto é, dos SFRs do temporizador. Em seguida são descritos os SFRs dos temporizadores do µC8051: TMOD, TCON, TH1, TL1, TH0 e TL0. 26 8 TCON (Timer CONtrol) Este registo é endereçável bit a bit e está mapeado no endereço 88h do espaço de dados interno. 8Fh 8Eh 8Dh 8Ch 8Bh 8Ah 89h 88h TF1 TR1 TF0 TR0 - - - - Timer 1 Timer 0 Os bits assinalados com ‘-‘ são bits não relevantes para a operação dos temporizadores. TF1 → flag de overflow do temporizador 1 TR1 → flag de arranque/paragem do temporizador 1. Quando é 0 o temporizador 1 não contabiliza a passagem de tempo ou os eventos, isto é, a contagem não se faz, está parada. TF0 → flag de overflow do temporizador 0 TR0 → flag de arranque/paragem do temporizador 0. Idêntica a TR1, mas para o temporizador 0. 8 TMOD (Timer MODe) Este registo é endereçável bit a bit e está mapeado no endereço 89h do espaço de dados interno. Este registo serve para definir o modo de funcionamento dos temporizadores. 90h 8Fh 8Eh 8Dh 8Ch 8Bh 8Ah 89h Gate C/T M1 M0 Gate C/T M1 M0 Timer 1 Timer 0 A definição do modo de funcionamento para os temporizadores 1 e 0 faz-se de igual forma, a diferença está na localização dos respectivos bits de controlo no registo TMOD. Gate → Quando TRx, com x={0,1}=1 e Gate=1, o respectivo temporizador funciona apenas quando e enquanto o pino INTx estiver a 1. Este tipo de controlo 27 chama-se de controlo por Hardware. Quando Gate=0, o respectivo temporizador funciona apenas quando e enquanto TRx estiver a 1. Este tipo de controlo chama-se de controlo por Software. C/T → Se C/T=0, o temporizador fica em modo “temporizador”, isto é, contador de impulsos temporais. Cada impulso corresponde ao período do relógio interno do microcontrolador. Se C/T=1, o temporizador fica em modo “contador”, isto é, contador de acontecimentos externos. Os impulsos relativos aos acontecimentos externos estão à entrada do pino Tx M1 e M0→ Estes dois bits definem o modo de funcionamento (ver adiante) M1 M0 Modo 0 0 Modo 0 0 1 Modo 1 1 0 Modo 2 1 1 Modo 3 8 THx (Timer High), TLx (Timer Low), x={0,1} TL0 e TH0 → Estes dois registos são utilizados pelo temporizador 0 para armazenar o tempo/acontecimentos decorridos. TL0 é o byte menos significativo e TH0 é o byte mais significativo desse valor. TL1 e TH1→ Análogos a TL0 e TH0, mas respeitantes ao temporizador 1. 8.3 - Modos de operação Tal como já foi referido, cada temporizador pode funcionar em 4 modos de operação possíveis, conforme o valor das flags M1 e M0: 8 Modo 0: Temporizador de 13 bits (M1=0, M0=0) Este modo de funcionamento existe para manter compatibilidade com o predecessor do 8051, o uC 8048. Neste modo de funcionamento, são utilizados os registos THx e os 5 bits menos significativos de TLx para efectuar a contagem. Quando esta contagem efectua a transição FF1Fh (11111111-xxx11111)→0h (00000000-xxx00000), a flag TFx é activada. 28 Os três bits mais significativos de TLx são ignorados. Este modo de operação é raramente utilizado em projectos actuais, que não necessitam de funcionar no 8048. 8 Modo 1: Temporizador de 16 bits (M1=0, M0=1) Neste modo de operação, semelhante ao modo de operação anterior, são utilizados todos os 16 bits disponíveis em TLx e THx para efectuar a contagem. A flag TFx é activada na transição FFFFh→0000h, o que possibilita a contagem máxima de 65536 (216) transições. 8 Modo 2:Temporizador de 8 bits, com recarregamento automático (M1=1, M0=0) A contagem é feita apenas no registo TLx. O registo THx contém um valor que é utilizado para recarregar o TLx quando ocorre o overflow. Por exemplo, se o valor do registo THx for FCh, o registo TLx vai contar a sequência FCh, FDh, FEh, FFh, FCh, FDh, .... O overflow ocorre, isto é, a flag de overflow (TFx) é activada, quando a contagem actual é FFh e se tenta incrementar mais uma contagem, “rebentando” assim com a capacidade do registo de 8 bits. Automaticamente o registo de contagem TLx é reinicializado com o valor presente em THx que é o valor de recarregamento. 8 Modo 3: (M1=1, M0=1) Neste modo, o timer 0 é decomposto em dois timers independentes: um em TL0 com a flag de overflow TF0, e outro em TH0 com a flag de overflow TF1. Portanto, neste modo, o timer 1vê-se privado da sua flag TF1. Pode no entanto ser usado desde que esta flag não seja necessária. 8.4 - Exemplos Técnica de programação - Pooling O pooling consiste em amostrar consecutivamente, por S/W, um dado valor que é alterado por uma fonte externa (p.ex um pino ou porto do processador ou uma flag do temporizador) até ser encontrado o valor pretendido. Nos exemplos seguintes é utilizado pooling para detectar a passagem dos intervalos de tempo indicados. Programação de temporizador para 200 µs: este programa envia um valor (FFh) pelo porto P2 em cada intervalo de 200 µs. 29 • Período de clock interno? Supondo que: freq. clock externo = 12Mhz. ⇓ freq.clock interno=freq.clock externo/12=1Mhz ⇒período interno=1µs • Nº de contagens? Nº de contagens= intervalo de contagem/ período clock interno = 200x10-6(s)/ 1x10-6(s) = 200 contagens • Tamanho do Temporizador? Temporizador de 8 bits ⇒ permite até 256 contagens Como 200<256, podemos usar um temporizador de 8 bits, o timer 0, por exemplo. • Programação do registo TMOD = xxxx0010b (2d) Gate C/T M1 M0 Gate C/T M1 M0 - - - - 0 0 1 0 Gate=0 → controlo de ON↔ OFF feito por SW C/T=0 → vai funcionar com temporizador, i.é, contador de tempo Modo de funcionamento → M1=1, M0=0 Temporizador de 8 bits ⇒ Modo 2 ⇓ Valor de Recarregamento? TH0 =256-200=56 • Programação do registo TCON=xx01xxxxb (16d) TF1 TR1 TF0 TR0 - - - - - - 0 1 TF0=0 → limpar a flag de overflow TR0=1 → para dar início à contagem • Código … Inicio: MOV TH0,#56; MOV TL0,#56; MOV TMOD,#2; 30 CLR TF0 SETB TR0 ;Este registo deve sempre ser o último ;a ser inicializado pois é neste ;registo (através do bit TR0 que se ;inicia a contagem Espera: JNB TF0,Espera ;O programa fica retido nesta instrução ;(200µs) até que TF0→1, isto é, espera ;pela ocorrência de overflow. CLR TF0 ;Apaga flag de overflow para poder ser ;possível detectar o próximo overflow. Escreve: MOV P2,#0FFh LJMP Espera Programação de temporizador para activação em cada 20 ms: este programa envia um valor (FFh) pelo porto P2 em cada intervalo de 20 ms. • Período de clock interno? Supondo que: freq. clock externo = 15Mhz. ⇓ freq. clock interno= freq. clock externo/12 = 1,25Mhz ⇒ período interno = 0,8µs • Nº de contagens? Nº de contagens= intervalo de contagem/ período clock interno = 20x10-3(s)/ 0,8x10-6(s) = 25000 contagens • Tamanho do Temporizador? Temporizador de 16 bits ⇒ permite até 65536 contagens Como 25000<65536, vamos usar um temporizador de 16 bits, o timer 1, por exemplo. • Programação do registo TMOD = 0001xxxxb (10h) Gate C/T M1 M0 Gate C/T M1 M0 0 0 0 1 - - - - Gate=0 → controlo de ON↔ OFF feito por SW 31 C/T=0 → vai funcionar com temporizador, i.é, contador de tempo Modo de funcionamento → M1=0, M0=1 Temporizador de 16 bits ⇒ Modo 1 ⇓ Valor de Recarregamento=65536-25000=40536d=9E58h ⇓ TH1=9Eh e TL1=58h • Programação do registo TCON=01xxxxxxb (40h) TF1 TR1 TF0 TR0 - - - - 0 1 - - - - - - TF1=0 → limpar a flag de overflow TR1=1 → para dar início à contagem • Código … Inicio: MOV TH1,#9Eh MOV TL1,#58h MOV TMOD,#10h MOV TCON,#40h Espera: JNB TF1,Espera ;O programa fica retido nesta instrução ;(20ms) até que TF1→1, isto é, espera ;pela ocorrência de overflow. MOV TH1,#9Eh ;O re-carregamento não é automático… MOV TL1,#58h CLR TF1 ;Apaga flag de overflow para poder ser ;possível detectar o próximo overflow. Escreve: MOV P2,#0FFh LJMP Espera Programação de temporizador para activação em cada 200 ms: este programa lê um valor do porto P2 escreve-o na célula de memória interna 20h, em intervalos de 200 ms. • Período de clock interno? Supondo que: freq. clock externo = 15Mhz. 32 ⇓ freq. clock interno= freq. clock externo/12 = 1,25Mhz ⇒ período interno = 0,8µs • Nº de contagens? Nº de contagens= intervalo de contagem/ período clock interno = 200x10-3(s)/ 0,8x10-6(s) = 250000 contagens • Tamanho do Temporizador? Temporizador de 16 bits ⇒ permite até 65536 contagens Mas 250000>65536 !!, como resolver a situação? Vamos obrigar um temporizador (por exemplo o temporizador 1) a fazer várias contagens sucessivas até totalizar o intervalo desejado. Optando, por exemplo, por 5 ciclos de contagem, onde cada contagem corresponderá a 250000/5=50000 contagens. • Programação do registo TMOD = 0001xxxxb (10h) Gate C/T M1 M0 Gate C/T M1 M0 0 0 0 1 - - - - Gate=0 → controlo de ON↔ OFF feito por SW C/T=0 → vai funcionar com temporizador, i.é, contador de tempo Modo de funcionamento → M1=0, M0=1 Temporizador de 16 bits ⇒ Modo 1 ⇓ Valor de Recarregamento=65536-50000=15536d=3CB0h ⇓ TH1=3Ch e TL1=B0h • Programação do registo TCON=01xxxxxxb (40h) TF1 TR1 TF0 TR0 - - - - 0 1 - - - - - - TF1=0 → limpar a flag de overflow TR1=1 → para dar início à contagem • Código 33 … Inicio: MOV TH1,#3Ch MOV TL1,#B0h MOV TMOD,#10h MOV TCON,#40h MOV R0,#5 Espera: JNB TF1,Espera ;O programa fica retido nesta instrução ;(50ms) até que TF1→1, isto é, espera ; pela ocorrência de overflow. MOV TH1,#3Ch ;O re-carregamento não é automático… MOV TL1,#B0h CLR TF1 DJNZ R0,Espera ;Obriga à execução de 5 ciclos de ;contagem sucessivos Escreve: MOV A,P2 MOV 20h,A MOV R0,#5 LJMP Espera Programação do temporizador para contar 100 impulsos externos: este programa espera que ocorram 100 impulsos no pino T0 e envia o valor FFh pelo porto P2 quando tal acontece. • Período de clock interno? Não interessa, dado que o timer não vai funcionar como temporizador mas sim como contador de acontecimentos externos. • Nº de contagens? Nºcontagens é eexcatamente igual ao nº de acontecimentos externos que se pretende contabilizar, neste caso = 100 impulsos. • Tamanho do Temporizador? Temporizador de 8 bits ⇒ permite até 256 contagens • Programação do registo TMOD = xxxx0010b (2d) Gate C/T M1 M0 Gate C/T M1 M0 - - - - 0 1 1 0 Gate=0 → controlo de ON↔ OFF feito por S/W C/T=1 → vai funcionar como contador de impulsos externos 34 Modo de funcionamento → Modo 2 (M1=1,M0=0) Temporizador de 8 bits ⇒ Modo 2 ⇓ Valor de Recarregamento? TH0 =256-100=156d • Programação do registo TCON=xx01xxxxb (16d) TF1 TR1 TF0 TR0 - - - - - - 0 1 TF0=0 → limpar a flag de overflow TR0=1 → para dar início à contagem • Código … Inicio: MOV TH0,#156; MOV TL0,#156; MOV TMOD,#6; MOV TCON,#16 ;Este registo deve sempre ser o último ;a ser inicializado pois é neste ;registo (através do bit TR0 que se ;inicia a contagem Espera: JNB TF0,Espera ;O programa fica retido nesta instrução ;(100 impulsos) até que TF0→1, isto é, ;espera pela ocorrência de overflow. CLR TF0 Escreve: MOV P2,#0FFh LJMP Espera Programação do temporizador para determinar a duração de um impulso aplicado ao pino P3.3 (INT1). Deve ser determinada a duração de um impulso aplicado ao pino P3.3 sabendo que a sua duração máxima é de 20 ms. A duração deve ser enviada pelos portos P1 e P2 quando o impulso termina (P1=>mais significativo, P2 =>menos significativo). • Período de clock interno? Supondo que: freq. clock externo = 15Mhz 35 ⇓ freq. clock interno= freq. clock externo/12 = 1,25Mhz ⇒ período interno = 0,8µs • Nº de contagens? Nº de contagens máximo=duração do impulso / período clock interno = 20x10-3(s)/ 0,8x10-6(s) = 25000 contagens • Tamanho do Temporizador? Temporizador de 16 bits ⇒ permite até65536 contagens Como 25000<65536, vamos usar um temporizador de 16 bits, o timer 1, por exemplo. • Programação do registo TMOD = 1001xxxxb (90h) Gate C/T M1 M0 Gate C/T M1 M0 1 0 0 1 - - - - Gate=1 → controlo de ON↔ OFF feito por H/W C/T=0 → vai funcionar com temporizador, i.é, contador de tempo Modo de funcionamento → M1=0, M0=1 Temporizador de 16 bits ⇒ Modo 1 ⇓ Valor de Recarregamento=65536-25000=40536d=9E58h ⇓ TH1=9Eh e TL1=58h • Programação do registo TCON=xxxxxxxxb TF1 TR1 TF0 TR0 - - - - - - - - - - - - Não interessa… • Programação adicional É necessário inibir a interrupção externa 1 (ver secção sobre interrupções), porque ela também é activada pelo pino P3.3. Para esse efeito, devemos garantir que o bit EX1 do SFR IE é colocado a 0. 36 • Código … Inicio: MOV TH1,#9Eh MOV TL1,#58h MOV TMOD,#90h CLR EX1 EsperaImp: JNB P3.3,EsperaImp ;Esperamos que o pino P3.3 seja ;colocado externamente a 1. A ;partir deste momento, o ;temporizador começa a contar EsperaFim: JB P3.3,EsperaFim ;Esperamos que o pino P3.3 seja ;colocado externamente a 0. ;Quando isso acontece, o ;temporizador deixa de estar ;activado, e podemos saber ;durante quantos impulsos do ;temporizador o pino esteve ;activado analisando o valor de ;TL1 e TH1 Escreve: MOV P2,TL1 MOV P1,TH1 MOV TH1,#9Eh ;O re-carregamento não é automático… MOV TL1,#58h LJMP EsperaImp 37 Capítulo 9 Canal Série 9.1 – Introdução O 8051 tem uma UART (Universal Asynchronous Receiver Transmitter) integrada para comunicação série. Permite a comunicação em Full Duplex pois tem dois buffers independentes: um para a recepção e outro para a transmissão de dados. Esses dois registos têm a mesma designação SBUF, no entanto trata-se de dois registos físicos distintos: quando é feita uma escrita em SBUF é utilizado o buffer de envio, quando é feita uma leitura o valor é lido do buffer de recepção. A taxa de transmissão pode ser variável (ver “Modos de funcionamento”). 8 Transmissão: Quando se pretende enviar um dado pelo canal série, escreve-se esse valor (máximo 8 bits) para o registo SBUF (buffer de envio). De cada vez, os dados a enviar/receber podem ter no máximo 8 bits, que é o tamanho do registo SBUF. Os LSB são recebidos/transmitidos primeiro. Quando a UART tiver enviado todo o conteúdo do SBUF pelo pino TXD, e após ter enviado o stop bit, assere a flag TI (Transmission Interrupt), isto é, põe a flag TI a 1 (envio concluído). 8 Recepção: Quando se pretende receber um dado que chega pelo canal série, é necessário, antes de mais, certificarmo-nos de que de facto existe informação válida para ler do SBUF. O que acontece é que quando a UART do 8051 recebe um byte de dados pelo canal série, escreve-o no SBUF (buffer de recepção) e, após receber o stop bit, assere a flag RI (Reception Interrupt), isto é, põe a lag RI a 1 (recepção concluída). 9.2 - Registos de Função Específica 8 SCON (Serial port CONtrol register) 9Fh 9Eh 9Dh 9Ch 9Bh 9Ah 99h 98h SM0 SM1 SM2 REN TB8 RB8 TI RI 38 Os bits SM0 e SM1 definem o modo de funcionamento do canal série. A definição dos modo de funcionamento está exposta na tabela seguinte: SM0 SM1 Modo 0 0 0 0 1 1 1 0 2 1 1 3 O bit SM2 é normalmente utilizado em sistemas de comunicação multiprocessador. Para comunições normais, este bit pode ser colocado a 0. O bit REN (Reception ENable) deve estar a 1 para que a UART possa receber valores do exterior. Quando REN=0, a UART ignora todas as transmissões de dados vindas do exterior. As flags TB8 e RB8 são utilizadas conjuntamente com os modos de operação 2 e 3. Em sistemas multiprocessador com a arquitectura master-slave, estes bits são fundamentais para identificar se a informação que está a circular no sistema de comunicação é um dado ou um endereço de um escravo.Quando não estamos perante um sistema multiprocessador, o 9º bit é normalmente utilizado para implementar um esquema de validação da paridade dos dados transmitidos/recebidos. Nesse caso, o nono bit indica se os 8 bits do caracter têm um número par ou impar de ‘1’. O emissor dos dados preenche este bit conforme os dados que vai enviar, e o receptor verifica se o bit de paridade que recebe está de acordo com os dados que recebeu. Se não estiver, isso significa que existiu um erro na transmissão. As flags TI e RI são utilizadas pela UART, tal como já se referiu, para assinalar a transmissão/recepção com sucesso de um caracter (8 bits), respectivamente. Adicionalmente, uma flag do registo PCON também é utilizada para controlo da UART. A flag SMOD do registo PCON serve para seleccionar, em cada modo, qual o divisor de frequência usado. 9.3 - Modos de funcionamento 8 Modo 0 – Registo de deslocamento de 8 bits 39 Neste modo de transmissão, a comunicação é do tipo half-duplex. O pino RXD é utilizado para recepção e envio de caracteres de 8 bits, enquanto que o pino TXD transmite o sinal de relógio ou de sincronismo. A entrada/saída de bits por RXD está sincronizada com o relógio emitido em TXD, que é fixo em 1/12 da frequência externa do µC. Para transmitir um dado neste modo deve fazer-se o seguinte: MOV SBUF,#valor CLR TI ;condição necessária e suficiente para ser enviado o sinal por TXD, ;aquando da transmissão. JNB TI,$ ;quando a flag TI vai a 1, o sinal de relógio deixa de ser transmitdo. Para receber um dado neste modo deve fazer-se o seguinte: CLR RI ;estas duas condições são necessárias e suficientes para ser enviado o SETB REN ;sinal de relógio por TXD. JNB RI,$ ;quando a flag RI vai a 1, o sinal de relógio deixa de ser transmitdo. MOV xpto,SBUF ; xpto representa um endereço de RAM interna ou um registo. Na recepção, compete ao circuito que está ligado ao µC detectar o sinal de relógio e enviar os dados de forma sincronizada. 8 Modo 1 – UART de 8 bits com taxa de transmissão variável Neste modo de transmissão, caracteres de 8 bits são recebidos (por RXD)/ transmitidos (por TXD) precedidos por um start bit (de valor 0) e seguidos de um stop bit (usualmente 1). Ou seja são transmitidos 10 bits por cada caracter de 8 bits. O bit menos significativo do caracter é transmitido primeiro. Start Bit LSB Bit 0 Bit 1 Bit 2 Bit 3 Bit 4 Bit 5 Bit 6 MSB Bit 7 stop bit 1 * * * * * * * * 0 Se a flag SM2 =1, então o stop bit deve ser 1. Se a flag for 0, então o stop bit pode ter qualquer valor. Se o stop bit tiver um valor incorrecto, então o caracter recebido é ignorado. A taxa de transmissão é calculada através da taxa de overflow do timer 1, a qual é dividida por 32 ou 16, conforme PCON.SMOD seja igual a 0 ou 1, respectivamente. A transmissão de caracteres é feita pelo escrita do valor a transmitir em SBUF. Quando a transmissão está completa, o bit TI é colocado a 1. 40 A linha RXD é colocada a 1 externamente quando não estão a ser transmitidos dados. Desta forma, a recepção de caracteres é iniciada (se REN=1) quando existe uma transição 1- >0 no pino RXD. O contador de 4 bits é sincronizado para começar a contar neste instante, sendo o valor TXD amostrado no meio da contagem de 16 impulsos. Quando o caracter é recebido, os seus 8 bits são guardados em SBUF, o valor do stop bit é armazenado na flag RB8 e a flag RI é asserida. 8 Modo 2 – UART de 9 bits com taxa de transmissão fixa Neste modo de funcionamento, são transmitidos/recebidos caracteres de 9 bits. Como o registo SBUF tem apenas 8 bits, sãoutilizadas duas flags (TB8 e RB8) para armazenar o valor do nono bit aquando da transmissão ou recepção, respectivamente. São utilizados start/stop bits, portanto o número total de bits transmitidos por caracter é 11. A taxa de transmissão neste modo é 1/64 ou 1/32 da frequência externa do µC, conforme o valor configurado no bit SMOD. 8 Modo 3 - UART de 9 bits com taxa de transmissão variável Este modo de funcionamento é idêntico ao modo 2, na medida em que estes dois modos têm um 9ºbit, sendo a a fórmula de cálculo da taxa de transmissão idêntica à do modo 1. 9.4 - Exemplos Programe o canal série para enviar dados com uma taxa de transmissão de 1 Mbps, utilizando um µC 8051 a 12 MHz. Impeça a recepção de dados, envie o caracter 55h e espere pelo término do envio. É necessário saber se pode ser utilizado um modo com taxa de transmissão fixa ou se é necessário utilizar um modo com taxa de transmissão variável. Se for utilizado um modo com taxa variável (modo 1 ou modo 3) é preciso também programar o temporizador para que este forneça a taxa de transmissão adequada. Portanto, quando é possível, deve optar-se por um modo com taxa de transmissão fixa. A taxa de transmissão no modo 0 é 1/12 da frequência do µC. Como a frequência de operação do µC é 12 MHz, a taxa de transmissão irá ser 12Mhz/12=1MHz (1Mbps), que é a taxa de operação pretendida. Um 41 problema do modo 0 é que é apenas half-duplex, mas isso não é relevante neste caso. … ;Inicialização de SMOD CLR SM0 CLR SM1 ;(SM=0;SM1=0) = modo 0 CLR SM2 ;Não é relevante para este problema,pode ficar a 0 CLR REN ;REN=0) impede a recepção. CLR TI CLR RI ;Coloca as flags de recepção e transmissão a 0. ;Importante! ;Alternativamente, em vez de inicializar cada bit separadamente, as ;instruções anteriores podiam ser substituídas pela instrução: MOV SCON, #00000000b MOV SBUF,#55h Espera:JNB TI,Espera ;Espera que a UART coloque a flag TI a 1, o ;que indica fim de transmissão. Programe o canal série para enviar dados com uma taxa de transmissão de 187,5Kbps, utilizando um µC8051 a 12MHz. Espere pelo caracter AAh e envie como resposta o caracter 55h. É necessário determinar o modo de funcionamento: O modo 0 transmite a 1/12 da frequência externa do µC, sendo a taxa de transmissão de 1Mbps para um relógio de 12 MHz. O modo 2 transmite a 1/32 ou 1/64 da frequência externa do µC, sendo a taxa de transmissão resultante de 375000bps ou 187500bps, respectivamente. Sendo assim, é possível utilizar o modo 2 com um factor de divisão de 1/64. MOV SCON,#10010000b ;Modo 2, REN=1 MOV PCON,#80h ;SMOD=1 EsperaR: JNB RI,EsperaR ;Espera por recepção de caracter MOV A,SBUF CJNE A,#AAh,EsperaR ;Se caracter recebido ≠ Aah, espera ;pelo próximo, senão envia resposta CLR TI 42 MOV SBUF,#55 ;Transmite resposta e espera por EsperaT: JNB TI,EsperaT ;fim de transmissão SJMP EsperaR Determine o modo de operação do canal série para enviar dados à tx.transmissão de 1250bps, utilizando um µC8051 a 12MHz. Para um relógio de 12 MHz, os modos de transmissão fixa oferecem uma taxa de transmissão de 1 Mbps, 375Kbps ou 187.5Kbps, conforme determinado nos exemplos anteriores. Sendo assim, é necessário utilizar um modo com taxa de transmissão variável. A taxa de overflow do timer é dividida conforme o modo e SMOD por 16 (modo 1) , 32 (modo 1 ou 3) ou 64 (modo 3). Sendo assim, é necessário programar o timer 1 para que a taxa de overflow seja 16, 32 ou 64 vezes maior do que a taxa que queremos. Deve escolher-se o valor que mais simplifica a configuração do timer 1 (que deve necessariamente ser configurado no modo 2 quando é usado para este efeito, i.é, para a geração da baudrate). Para programar o timer para uma taxa de overflow de 16x1250=20000 overflows/s, é necessário obter um período de 1/20000 segundos entre overflow. Se o timer for alimentado com o relógio interno de 1Mhz, então existem 1000000/20000=50 transições de relógio entre cada overflow. Desta forma, pode ser utilizado o timer 1 em modo 2 (timer de 8 bits com recarregamento automático) com um valor de recarregamento de 256-50=206. 43 Capítulo 10 Processamento de Interrupções 10.1 - Introdução Uma interrupção é uma suspensão temporária da execução do programa, para execução de uma rotina, designada normalmente por rotina de serviço à interrupção (RSI). Contrariamente ao pooling no qual o uC precisa testar por S/W (JNB flag,$) a flag de interrupção e aguardar que esta seja asserida, com interrupções esse teste é feito de forma “transparente”. Com efeito, no final da execução de cada instrução máquina, o uC vai testar, por H/W, todas as flags (permitidas no registo IE) de interrupção. Se alguma estiver asserida, a respectiva RSI será executada de imediato. No 8051, uma interrupção pode ser proveniente de 5 fontes distintas: 8 Interrupção do Timer 0 (qd ocorre overflow da contagem do timer 0 ⇔ TF0→1) 8 Interrupção do Timer 1 (qd ocorre overflow da contagem do timer 1 ⇔ TF1→1) 8 Interrupção do Canal Série (quando RI→1 ou TI→1) 8 Interrupção Externa 0 (quando IE0→1) 8 Interrupção Externa 1 (quando IE1→1) Para permitir o atendimento correcto quando várias interrupções são activadas simultaneamente, a cada interrupção é atribuído um nível de prioridade (alta →1 ou baixa →0). No caso de duas interrupções com prioridade distinta ocorrerem simultaneamente, a interrupção de maior prioridade é atendida primeiro. No caso de ocorrerem 2 ou mais interrupções simultaneamente e com a mesma prioridade, então o uC vê-se obrigado a recorrer à sequência natural para ordenar o atendimento. Existe um espaço de 8 bytes reservado por cada uma destas interrupções, em endereços predefinidos na memória de código para a localização das RSI´s. Este conjunto de endereços predefinidos chama-se vectores de interrupção, pois é para um destes “vectores” que o uC é direccionado quando ocorre uma interrupção. Como este espaço é normalmente demasiado pequeno para conter a RSI completa, geralmente coloca-se aí uma instrução de salto para uma outra zona da memória que contém a RSI completa. 44 O salto para uma dada RSI é efectuado quando a flag associada a essa interrupção (flag de interrupção) é colocada a 1. Estas flags são apagadas pelo uControlador quando é efectuado o salto para a RSI respectiva, excepto no caso da interrupção do canal série e das interrupções externas quando são configuradas por nível. Para a interrupção do canal série, existe apenas um único vector de interrupção e existem duas flags possíveis que podem desencadear a interrupção. Uma delas indica o fim de transmissão de um dado (TI) enquanto a outra indica o fim de recepção (RI). O microcontrolador não apaga automaticamente as duas flags para que a RSI possa determinar se a interrupção foi provocada pela recepção ou transmissão de um caracter. 10.2 - Tabela de Vectores de Interrupção Flag de Interrupção Designação da Interrupção Endereço do Vector de Interrupção - Interrupção do RESET 0000h IE0 Interrupção Externa 0 0003h TF0 Interrupção do Timer 0 000Bh IE1 Interrupção Externa 1 0013h TF1 Interrupção do Timer 1 001Bh TI ou RI Interrupção do Canal Série 0023h A interrupção de RESET é gerada quando se prime o botão de Reset. Tem prioridade máxima sobre qualquer outro processamento em curso. Não existe nenhuma RSI associada a esta interrupção. Quando ocorre a interrupção, o PC é automaticamente carregado com o endereço 0, no qual deverá estar, necessariamente, localizada a primeira instrução do programa ou um salto para o início do programa. Veja-seo exemplo seguinte: Exemplo ORG 00h ou ORG 00h LJMP inicio inicio: .... ... inicio: ... No final da execução de cada instrução máquina e antes de iniciar a execução da próxima, o µC vai analisar o estado de todas as flags de interrupção (permitidas no registo IE). Se tiver 45 ocorrido um pedido de interrupção, isto é, se estiver asserida uma flag de interrupção, então acontece o seguinte: 1º- o conteúdo do registo PC é guardado em RAM interna (pilha). 2º- o registo PC é carregado com o endereço do vector de interrupção relativo à interrupção que está a ser processada/atendida. Quando acaba a execução da RSI (quando o microcntrolador executa a instrução RETI), o PC é novamente carregado com o endereço da instrução que estava para ser executada quando surgiu a interrupção. 10.3 - Registos de Função Específica 8 IE (Interrupt Enable) Este registo (endereçável bit a bit) serve para indicar ao microcontrolador quais as interrupções que ele deve considerar e quais as que deve ignorar. AFh AEh ADh ACh ABh AAh A9h A8h EA - - ES ET1 EX1 ET0 EX0 8 EA (Enable All) → funciona como uma máscara: se estiver a zero inibe todas as interrupções; se estiver a um permite as interrupções cujos bits respectivos estiverem a 1 no registo IE. 8 ES (Enable Serial) → se estiver a zero inibe a interrupção do canal série; se estiver a um permite a interrupção. 8 ET1 (Enable Timer 1) → se estiver a zero inibe a interrupção do timer 1; se estiver a um permite a interrupção. 8 EX1 (Enable External 1) → se estiver a zero inibe a interrupção externa 1; se estiver a um permite a interrupção. 8 ET0 (Enable Timer 0) → se estiver a zero inibe a interrupção do timer 0; se estiver a um permite a interrupção. 8 EX0 (Enable External 0) → se estiver a zero inibe a interrupção externa 0; se estiver a um permite a interrupção. 8 IP (Interrupt Priority) 46 Este registo (endereçável bit a bit) serve para indicar a prioridade de cada uma das interrupções autorizadas na configuração do registo IE. BFh BEh BDh BCh BBh BAh B9h B8h - - - PS PT1 PX1 PT0 PX0 8 PS (Priority Serial) → se estiver a zero dá prioridade baixa à interrupção do canal série; se estiver a um dá prioridade alta. 8 PT1 (Priority Timer 1) → se estiver a zero dá prioridade baixa à interrupção do timer 1; se estiver a um dá prioridade alta. 8 PX1 (Priority External 1) → se estiver a zero dá prioridade baixa à interrupção externa 1; se estiver a um dá prioridade alta. 8 PT0 (Priority Timer 0) → se estiver a zero dá prioridade baixa à interrupção do timer 0; se estiver a um dá prioridade alta. 8 PX0 (Priority External 0) → se estiver a zero dá prioridade baixa à interrupção externa 0; se estiver a um dá prioridade alta. No caso de duas ou mais interrupções serem lançadas simultaneamente e tiverem todas a mesma prioridade, então a ordem de atendimento respeitará a sequência natural: Externa 0 → Timer 0 → Externa 1 → Timer 1 → Canal Série Exemplo Se os registos IE e IP forem inicializados da forma que se segue, indicar a ordem pela qual as interrupções são atendidas pelo processador, no caso de todas ocorrerem simultaneamente: MOV IE,#10010111b MOV IP,#01001101b Analisando o registo IE vê-se que: - interrupções permitidas são: canal série, externa1, timer0 e externa0. No registo IP, e considerando apenas as interrupções validadas no IE temos: - com prioridade alta: externa1 e externa0. - com prioridade baixa: canal série e timer 0. 47 Recorrendo à sequência natural, e analisando primeiro o grupo de interrupções de prioridade alta e em seguida o grupo das que têm prioridade baixa, obtém-se a seguinte ordem de atendimento: 1º - Externa0; 2ª - Externa1; 3ª - Timer0; 4ª - Canal Série. 10.4 - Interrupção externas Existem duas interrupções causadas por fontes externas. Essas fontes externas estão ligadas ao microcontrolador através dos pinos P3.2(INT0) e P3.3(INT1), para a interrupção externa 0 e interrupção externa 1, respectivamente. As interrupções externas podem ser configuradas para serem activadas por nível (nível 0) ou por transição (1→0) do sinal alimentado a esses pinos. O controlo das interrupções externas é feito através do registo de função específica TCON. 8 TCON (Timer CONtrol) – 88h 8Fh 8Eh 8Dh 8Ch 8Bh 8Ah 89h 88h - - - - IE1 IT1 IE0 IT0 IEx, com x={0,1} → Flags de interrupção; quando é detectada uma interrupção externa, a respectiva flag é colocada a 1. ITx, com x={0,1} → Indica se a interrupção é activada por nível ou por transição (descendente). Exemplo: Fazer um programa que liga uma lâmpada(P1.0) quando um botão ligado no pino INT0 é premido (gera uma transição descendente). ORG 00h LJMP Inicio ORG 03h LJMP RsiExt0 Inicio: MOV IE,#10000001b SETB IT0 ; por transição LJMP $ RsiExt0: SETB P1.0 RETI 48 10.5 - Pooling vs interrupções As interrupções permitem fazer sistemas que processam de forma muito eficaz as entradas/saídas de dados. Considere-se, por exemplo, a operação do canal série: quando é recebido um caracter, a flag RI á colocada a 1. Se a interrupção do canal série não estiver permitida no registo IE, o microcontrolador necessita de executar um ciclo que testa repetidamente o valor desta flag para detectar a recepção de um caracter, ficando “retido” nessa instrução (JNB RI,$) até a flag ser asserida pela UART. Este tipo de acesso a entrada/saída é designado por pooling (do inglês pool – sondagem). Se existirem outras fontes de dados que precisem de ser amostradas (por exemplo, o overflow temporizador ou um valor recebido externamente através de um porto), então é necessário testar também o valor das respectivas flags ou portos dentro desse mesmo ciclo. O funcionamento básico do pooling é demonstrado no seguinte programa em pseudo-código. Ciclo: Se RI=1, chama rotina RecepçãoSérie( ) Se TI=1, chama rotina TransmissãoSérie( ) Se TF0=1, chama rotina OverflowTimer0() …. Salta para Ciclo Existem vários inconvenientes na utilização de pooling. Por exemplo, não existe nenhuma forma simples de indicar que o processamento do overflow do timer é mais importante do que o processamento da transmissão série, por exemplo. Se o overflow acontecer imediatamente após o teste da flag TF0, todas as outras rotinas podem ser executadas antes do processamento do overflow ocorrer. Isto conduz-nos ao segundo problema: o tempo de resposta. Quanto maior o número de fontes de dados que precisam de ser amostrados, maior o tempo que demora cada volta do ciclo. Isso significa que o tempo decorrido entre o assinalar da flag e o tratamento dos dados respectivos pode ser demasiado grande. Por fim, note-se que nem tudo o que o µC faz é ler ou escrever dados em periféricos. Normalmente, é necessário efectuar processamentos adicionais, tais como o cálculo de valores para controlo, estatísticas, correcção e filtragem de erros, ou qualquer outro tipo de operação que não envolva directamente a leitura ou escrita de dados. Isto é um problema quando se utiliza looping, porque o processador está sempre a “queimar” tempo no ciclo à espera de dados de entrada. A solução mais simples é colocar esses cálculos dentro do próprio ciclo: Ciclo: Se RI=1, chama rotina RecepçãoSérie( ) 49 Se TI=1, chama rotina TransmissãoSérie( ) Se TF0=1, chama rotina OverflowTimer0() … Efectua calculos necessários Salta para Ciclo Mas esta abordagem tem vários problemas óbvios: o tempo de resposta aos periféricos torna-se ainda maior e o tempo disponível para efectuar os cálculos é muito pequeno (sob pena de o tempode resposta aos periféricos ser incomportavelmente longo). A utilização de interrupções resolve estas questões de forma simples e eficaz. Em vez de testar continuamente a existência de dados para enviar/receber dos periféricos, utilizam-se RSIs que são executadas apenas quando existe realmente algo para tratar. No tempo restante, o µC está livre e pode dedicar-se a efectuar quaisquer cálculos que necessite. Ciclo: Efectua calculos necessários Salta para Ciclo RSI do canal série: Se RI=1, chama rotina RecepçãoSérie( ) Senão chama rotina TransmissãoSérie( ) RSI do timer 0; Chama rotina OverflowTimer0() Note que nesta nova versão da rotina com interrupções: 8 As rotinas de tratamento de dados são chamadas imediatamente quando os dados estão disponíveis, ou seja, o atraso da resposta é mínimo. 8 É possível atribuir uma prioridade elevada à RSI do timer 0, o que nos permite indicar que o processamento do overflow é mais urgente do que o processamento do canal série. 8 Quando não está a ser feito tratamento de entrada/saída, o µC dedica-se exclusivamente a efectuar os cálculos que necessita, sem se preocupar em amostrar o estado das flags. Isto significa que sobra mais tempo ao µC para fazer os cálculos e que pode demorar tanto tempo quanto necessita (porque se for necessário, esses calculos serão interrompidos temporariamente para que seja efectuado o tratamento de entrada/saída) 50 Resumindo, para sistemas práticos, que necessitem de efectuar um tratamento intensivo e rápido de Entrada/Saída (E/S) e/ou que necessitem de efectuar processamento de cálculos demorados, a única opção plausível é a utilização de RSI para efectuar o processamento de E/S. 10.6 – Exemplos A instrução ORG é uma directiva do compilador SES51 (usado nas aulas laboratoriais), que indica ao compilador qual o endereço inicial de memória de código a partir do qual deve ser armazenado o código. Programação de temporizador – interrupções Suponhamos que se pretende enviar uma onda quadrada, pelo pino P1.0, com período 400µs e duty cycle de 50%. Use o timer 0, supondo que o clock externo é de 12Mhz. ORG 00h LJMP Inicio ;Primeira instrução que vai ser executada ORG 0Bh LJMP Rsi_Timer0 Inicio: MOV TL0,#56 ; há 200 contagens para cada metada da onda MOV TH0,#56 MOV TMOD,#06h ;Programação como temporizador e no modo 2 MOV IP,#02h ;Prioridade alta MOV IE,#82h ;Permissão de interrupção pelo timer 0 CLR TF0 SETB P1.0 ;Inicializa estado da onda quadrada SETB TR0 ;Arranca temporização Repete:SJMP Repete ;Execução em ciclo infinito, sendo apenas ;interrompida quando ocorre a interrupção ;desencadeada pelo overflow ; ; do timer 0. Rsi_Timer0:CPL P1.0 RETI Programação de canal série usando interrupções 51 Programe o canal série para enviar dados com uma taxa de transmissão de 4200 bps, utilizando um µC 8051 a 12 MHz. Quando o µC receber o valor 1Bh pelo porto série, deverá escrever esse valor para o porto P1. ORG 00h LJMP Inicio ORG 23h LJMP Rsi_Cserie Inicio: MOV SCON,#01010000 ;Modo1, REN=1 MOV PCON,#00h ;divisor de 64 MOV TMOD,#20h; MOV TH1,#252 MOV TL1,#252 CLR TF1 SETB TR1 Repete: LJMP Repete Rsi_CSerie:CLR RI MOV A,SBUF CJNE A,#1Bh,Fim MOV P1,A Fim: RETI Programação de um contador para contar 100 impulsos externos usando interrupções Este programa espera que ocorram 100 impulsos externos e envia o valor FFh pelo porto P2 quando tal acontece. Trata-se de uma contagem de acontecimentos externos pelo que será de supôr que a fonte desses impulsos externos estará ligada ao pino Tx {com x=1 ou 0} do porto P3. Vamos usar, por exemplo, o timer 0. • Período de clock interno? Não interessa, pois o timer vai funcionar como contador e não como temporizador. • Nº de contagens? Precisamos de contar 100 impulsos. • Tamanho do Temporizador? 52 Temporizador de 8 bits ⇒ permite até 256 contagens (<100). • Programação do registo TMOD = xxxx0010b (2d) Gate C/T M1 M0 Gate C/T M1 M0 - - - - 0 1 1 0 Gate=0 → controlo de ON↔ OFF feito por SW C/T=1 → vai funcionar como contador de impulsos externos Modo de funcionamento → M1=1, M0=0 Temporizador de 8 bits ⇒ Modo 2 ⇓ Valor de Recarregamento? TH0 =256-100=156 • Programação do registo TCON=xx01xxxxb (16d) TF1 TR1 TF0 TR0 - - - - - - 0 1 TF0=0 → limpar a flag de overflow TR0=1 → para dar início à contagem • Código ORG 00h LJMP Inicio ORG 0Bh LJMP Rsi_Timer0 Inicio: MOV TH0,#156 MOV TL0,#156 MOV TMOD,#6 MOV TCON,#16 Espera: LJMP Espera Rsi_Timer0:MOV P2,#0FFh ;A Rsi_Timer0 é chamada quando o timer 0 RETI ;tiver atingido o overflow (o que ;acontece a cada 100 contagens). O ;apagamento da flag TF0 e o ;recarregamento do registo TL0 são feitos ;automaticamente. 53 Capítulo 11 Sistemas Multiprocessador baseados em 8051´s 11.1 – Introdução Um sistema distribuído é composto por vários sistemas distintos interligados, sendo cada um dos sistemas componentes baseado num microprocessador/microcontrolador. A comunicação entre os vários sistemas pode ser efectuada de formas distintas. No caso de sistemas baseados no µC 8051, é comum a utilização do porto série para esse efeito. O canal série dos µC 8051 está preparado para ser utilizado numa arquitectura distribuída chamada mestre/escravo. 11.2 - Arquitectura Mestre/Escravo Na arquitectura Mestre/Escravo, como o próprio nome sugere, existe um sistema (Mestre) que “manda” nos restantes (Escravos). Todos os sistemas estão interligados da forma que se vÊ na figura. É o processador Mestre que “disciplina” toda a comunicação. Uma mensagem enviada pelo Mestre é recebida por todos. TXD RXD Mestre Escravo 0 Escravo 1 Escravo n TXD RXD TXD RXD TXD RXD ... Naturalmente, é necessário impedir que haja problemas devido à utilização simultânea do canal série por mais do que um sistema, ao mesmo tempo. Na arquitectura Mestre-Escravo, cada um dos sistemas Escravo só pode comunicar quando recebe uma ordem para o fazer do sistema Mestre. Cada sistema Escravo está identificado com um endereço único. Este endereço tem 8 bits, é constante e é determinado de forma fixa pelo projectista do sistema. O Mestre indica a um dos sistemas Escravo que pretende comunicar com ele enviando o endereço desse sistema 54 pelo canal série. Todos os sistemas escravo estão à escuta da informação enviada pelo canal série do mestre. Quando um destes sistemas detecta que o seu endereço foi transmitido pelo sistema mestre, então sabe que: 8 Todos os dados enviados até à próxima transmissão de um endereço são-lhe destinados. 8 Nenhum outro sistema Escravo irá escrever no canal enquanto não for enviado outro endereço pelo sistema mestre. Isto é, o sistema escravo sabe que pode ler/escrever com exclusividade no canal série até que seja transmitido outro endereço. Para que este esquema de comunicação funcione, é necessário que exista alguma forma de distinguir entre dados e endereços. Tal como já se referiu, o canal série do µC 8051 suporta a arquitectura Mestre/Escravo. Para possiblitar a distinção entre endereços e dados, é disponibilizado um 9º bit adicional nos modos de transmissão 2 e 3. Este bit pode ser ligado ou desligado pelo sistema Mestre para indicar se o byte que vai enviar corresponde a um endereço de um Escravo ou se é um byte de dados (se o TB8 é colocado a 0 então é uma transmissão de dados, se for colocado a 1 é o endereço de um sistema Escravo). O bit TB8 é configurado (1 ou 0) pelo Mestre e é
Compartilhar