Avançar para o conteúdo

Interrupção externa do CH552 (8051) #5

Já vimos como usar interrupção de temporizador Timer0 no CH552. Agora veremos como implementar a Interrupção externa do CH552, usando um botão push button para controlar a piscada de um LED. Outra funcionalidade interessante que vamos implementar é a da comunicação UART com um computador.

Uma das funções mais úteis de um microcontrolador é a interrupção. Ela é basicamente uma função que desvia o programa principal imediatamente quando algum evento ocorre. Então neste momento uma função especial é executada, conforme você desejar. Isto possibilita o controle instantâneo de periféricos, como por exemplo um robô.

Imagine que o robô tenha que ficar de pé, e para isso a leitura do seu sensor de inclinação tenha que ser muito rápida. O sensor pode por exemplo ter uma saída digital que vai a nível alto toda vez que uma nova leitura de posição está disponível. Esta saída digital do sensor pode ser conectada à uma entrada digital do nosso microcontrolador CH552.

leds e ch552 com potenciometro na protoboard
CH552 em uma protoboard

Quando o pino de saída do sensor for a nível ato, Isto ativaria a entrada digital do CH552. Então desvia-se imediatamente o programa sendo executado e calcula-se a posição do robô dentro de uma função especial. Após o fim do cálculo, que tem que ser rápido, o programa (o loop principal “main()”)volta a executar normalmente. Isto até a próxima interrupção, quando tudo começa novamente.

O CH552 suporta várias fontes de interrupção, como SPI, timer, externa, UART, PWM, watchdog. Neste artigo vamos aprender a usar a interrupção externa, vinda de um pino GPIO digital. Outra função do CH552 que quero mostrar para vocês é a comunicação serial UART.

Vamos usa-la para enviar uma mensagem ao computador toda vez que uma interrupção ocorre. Também vamos implementar um contador crescente, que envia um valor inteiro (1, 2, 3, 4, …..) para o computador a cada um segundo. Criaremos um exemplo de código que usa ambas as funções: interrupção externa e comunicação UART.

Como funciona a interrupção?

Veja a tabela abaixo, retirada diretamente do datasheet do CH552. Ela indica que o chip tem apenas duas interrupções externas, INT0 e INT1. Ambas são de mais alta prioridade, isto significa que para o CH552 estas interrupções externas são muito importantes de serem tratadas.

Tabela de interrupções do CH552
Tabela de interrupções do CH552

Cada uma das duas interrupções está mapeada para apenas um pino do CH552. Conforme visto na tabela abaixo, a INT0 está no pino P3.2 e a INT1 está no pino P3.3. Aproveitando que estamos aqui, veja o mapeamento das portas de comunicação UART. O RXD1 está no pino P3.4, enquanto o TXD1 está no pino P3.2.

Veja que o pino P3.2 acumula função de interrupção INT0 e também TXD1 da UART. Por este motivo nós utilizaremos aqui neste exemplo a interrupção INT1 do pino P3.3. Isto deixa o pino P3.2 livre para ser usado com a UART1.

Como funciona a comunicação serial UART?

UART significa (do Inglês) “Universal Asynchronous Receiver/Transmitter”, ou transmissor/receptor assíncrono universal. É um protocolo de comunicação entre dois dispositivos eletrônicos que usa apenas três fios. São eles o transmissor TX, o receptor RX e a referência GND. O “assíncrono” no nome significa que não há sinal de clock compartilhado entre transmissor e receptor.

Este protocolo tem velocidades padronizadas, como 9600 ou 115200 bps (bits por segundo), que devem ser acordadas e respeitadas entre transmissor e receptor. Ações como não compartilhar o GND ou configurar velocidades diferentes (entre transmissor e receptor) fazem com que a comunicação UART não funcione.

No nosso caso e exemplo, utilizaremos a comunicação UART a uma velocidade baixa, 9600 bps, para enviar um valor inteiro incremental para o computador. Este valor é gerado a cada um segundo dentro do microcontrolador CH552. Utilizaremos um software chamado Putty para receber a informação no computador, e mostra-la na tela.

A maioria (se não todos) os computadors e notebook modernos não tem mais porta serial estilo DB9. Mas ainda assim nós conseguimos implementar uma comunicação serial com o computador. Para isto basta utilizar um conversor USB para Serial, como por exemplo o FT232 ou CH340. A função dele é converter de protocolo serial para USB, que o computador consegue ler.

Cada conversor USB para serial, ao ser conectado ao computador, recebe uma porta de comunicação específica, chamada COM. Há também um número após o texto pode variar, então por exemplo um conversor pode receber o nome COM17, ou COM3, etc. É esta porta “COMxx” ao qual o software Putty vai se conectar, usando para isto a velocidade acordada entre as partes. No nosso caso será 9600 bps.

O hardware/conexões elétricas

Para este experimento vamos usar a pequena plaquinha CH522 (daqui) ou (daqui). Usaremos também um botão estilo push button com contato normalmente aberto. Eu usei um modelo 6x6mm, porém você pode usar qualquer outro. Até mesmo dois fios se encostando já servem.

E finalmente usaremos um LED 3mm ou 5mm, qualquer cor. Para limitar sua corrente utilize um resistor com valor entre 220 Ohm e 1,5k Ohm. Eu usei um resistor de 1k Ohm, é o que eu sempre uso para minhas prototipagens.

Botão e LED para interrupção, FT232 para UART
Botão e LED para interrupção, FT232 para UART

Conforme visto acima e já comentado, o push button vai entre o pino P3.3 e o GND. Isto porque neste chip CH552 há por padrão um resistor de pull-up em cada pino com a função GPIO. Isto significa que para alterarmos seu estado, precisamos colocar seu nível lógico em 0 (GND).

O LED de indicação vai no pino P3.5, mas poderia ser qualquer outro disponível (por exemplo P1.4 ou P3.1). O conversor USB-serial que usei é o FT232, com seu TX no pino P3.4 (RXD1) e seu RX no pino P3.2 (TXD1).

Como o código funciona?

A estrutura do código (inicialização de oscilador à cristal, timer, etc) segue o mesmo padrão que já fizemos nas aulas anteriores. Deixarei esta parte para você pesquisar caso queira sabem. Começarei te mostrando a configuração para ativar o uso de interrupção.

Como eu tomei por princípio sempre ativar as interrupções “por último” no código, eu coloco-as sempre na função “timer0_init()” (EA= 1). Por este motivo eu fiz a ativação da interrupção externa INT1 também dentro da inicialização do timer0, fazendo “EX= 1”.

void timer0_init(void) {
    TMOD &= ~0x03;  // clear Timer0 mode bits
    TMOD |=  0x01;  // Timer0 mode 1: 16-bit

    // 10ms @ 24MHz: tick = 24M/12 = 2MHz = 0.5us
    // 10ms / 0.5us = 20000 ticks; 65536-20000 = 45536 = 0xB1E0
    TH0 = 0xB1;
    TL0 = 0xE0;

    ET0 = 1;   // enable Timer0 interrupt
    TR0 = 1;   // start Timer0
    EX1 = 1; // enable external interrupt 1. This does not need to be here
             // it just needs to be put before EA= 1;
    EA = 1;
}

A partir deste momento, quando o pino de GPIO P3.3 vai a nível baixo, a interrupção já está ativa. Ela chama sempre a mesma função “void INT1_ISR(void) __interrupt (INT_NO_INT1){}”.

void INT1_ISR(void) __interrupt (INT_NO_INT1) // You can do __interrupt (2) if you prefer 
{
    // only accepts/enters function if pin is LOW. This prevents
    // input bouncing.
    // External interrupts on CH552 happen on the falling edge only,
    // so this IF statement is making sure the button is still pressed when
    // the interrupt enters here.

    if (!(P3 & (1 << 3))) {   
        button_irq = 1;       
        debounce= 1; // enter debounce time (300ms)
    }
}

Tudo o que eu faço dentro da interrupção externa é verificar se o nível lógico ainda é 0 (GND). Caso seja, eu então aciono a flag indicando pressão no push button “button_irq” e inicialializo uma variável de debounce para evitar múltiplos acionamentos.

Veja que a interrupção faz pouquíssima coisa, e é assim que tem que ser. Entra rápido, saí rápido, para que o programa principla continue executando “sem sustos”. Então é no loop principal “main()” e “while(1)” que a mágica do código acontece.

if(button_irq && !debounce) { // if push button was pressed and debounce has finished
            button_irq = 0; // clear the external interrupt 1 bit            
            if(ledON == 0){ // every time we enter here the LED status changes
                ledON= 1;   // from "blinking" to "not blinking" and vice versa
            }else{
                ledON= 0;
            }            
            Serial_println("INT1 detected!");
        }
        if(ledON){
            blink_led();
        }else {
            P3 &= ~(1 << 5);   // LED OFF           
        }

Aqui eu verifico o estado das variáveis que setei lá em cima na interrupção (“button_irq” e “debounce”). Caso a condição certa se apresente, eu então entro no IF e verifico se a flag “ledON” está setada. Caso não estiver eu seto, caso estiver eu reseto. É basicamente um oscilador entre ligado e desligado.

Então finalmente eu verifico se “ledON” está em nível alto, caso no qual eu finalmente faço o LED piscar com “blink_led()”. Caso “ledON” esteja em 0 (nível baixo) eu desligo o LED com “P3 &= ~(1 << 5)”.

Como vai funcionar a comunciação serial UART?

Dentro do loop principal eu fico verificando se a variável “serialTime” é maior que 100. Esta variável é um contador, então ela vai ser maior que 100 após 1000 ms (mil milisegundos). isto porque a minha interrupção de Timer0, que incrementar “serialTime” ocorre a cada 10 ms. Então a cada efetivos 1000 ms eu adiciono “1” ao contador “counter” que vai ser impresso na tela do computador.

if(serialTime > 100){ // 100 x 10ms= 1 second
            serialTime= 0;
            counter++;
            //Serial_print("Counter = ");
            Serial_println_uint(counter);
        }

A função acima é tudo que precisamos, porém tem bastante código escondido por de trás da função “Serial_println()”. Tal código é adicionado ao meu código principal via um “#include” no topo da página:

#include "serial_print\serial.h" // Serial printing

Os arquivos completos “serial.h” (e seu companheiro “serial.c”) podem (e devem) ser baixados do meu Github, aqui. Não vou colocar o arquivo intero colado aqui, portanto faça o download no link e coloque ambos em uma pasta chamada “serial_print” ao lado do seu código “.c” principal. Sem estes dois arquivos, que a inteligência artificar me ajudou a criar, a comunicação serial UART não funciona.

Gravando o microcontrolador

O código completo do exemplo está no meu Github e também abaixo. Monte o circuito conforme diagrama esquemático na seção correspondente acima. Salve o código completo acima como “external-interrupt-led.c”.

#include "inc\ch554.h"
#include <stdint.h>
#include "serial_print\serial.h" // Serial printing

volatile unsigned int tick_10ms = 0;
volatile unsigned int debounceTimer = 0;
volatile __bit button_irq = 0;
volatile __bit debounce = 0;
volatile __bit ledON = 0;
unsigned int serialTime= 0;
unsigned int counter= 0;

void timer0_ISR(void) __interrupt(1) __using(1);
void blink_led(void);
void clock_init(void);

void clock_init(void) {
    SAFE_MOD = 0x55;
    SAFE_MOD = 0xAA;
    // Set Fsys = 24 MHz (Fpll/4, bits[2:0] = 110 = 0x06)
    CLOCK_CFG = (CLOCK_CFG & ~MASK_SYS_CK_SEL) | 0x06;
    
    SAFE_MOD = 0x00;
}
void INT1_ISR(void) __interrupt (INT_NO_INT1) // You can do __interrupt (2) if you prefer 
{
    // only accepts/enters function if pin is LOW. This prevents
    // input bouncing.
    // External interrupts on CH552 happen on the falling edge only,
    // so this IF statement is making sure the button is still pressed when
    // the interrupt enters here.

    if (!(P3 & (1 << 3))) {   
        button_irq = 1;       
        debounce= 1; // enter debounce time (300ms)
    }
}
void timer0_ISR(void) __interrupt(INT_NO_TMR0) { // You can do __interrupt (1) if you prefer
    TF0 = 0;  // clear overflow flag (important for robustness)
    TH0 = 0xB1;
    TL0 = 0xE0;
    tick_10ms++; // this is the 10ms tick for LED blinking
    serialTime++; // this is the timer for Serial_println transmissions
    if(debounce){ // if external interrupt happened, activate debounce timer
        debounceTimer++;
        if(debounceTimer >= 60){ // after 600ms of not detecting the push-button
            debounce= 0; // turn debounce delay OFF
            debounceTimer= 0; // and clear timer/counter for next time
        }
    }
}

void timer0_init(void) {
    TMOD &= ~0x03;  // clear Timer0 mode bits
    TMOD |=  0x01;  // Timer0 mode 1: 16-bit

    // 10ms @ 24MHz: tick = 24M/12 = 2MHz = 0.5us
    // 10ms / 0.5us = 20000 ticks; 65536-20000 = 45536 = 0xB1E0
    TH0 = 0xB1;
    TL0 = 0xE0;

    ET0 = 1;   // enable Timer0 interrupt
    TR0 = 1;   // start Timer0
    EX1 = 1; // enable external interrupt 1. This does not need to be here
             // it just needs to be put before EA= 1;
    EA = 1;
}

void blink_led(void) {
    if(tick_10ms % 50 < 25){
        P3 |= (1 << 5);  // LED ON
    } else {
        P3 &= ~(1 << 5); // LED OFF
    }
}

void main(void) {
    clock_init();
    timer0_init();

    // Disable watchdog (important on CH55x)
    SAFE_MOD = 0x55;
    SAFE_MOD = 0xAA;
    GLOBAL_CFG &= ~bWDOG_EN;   // turn off watchdog
    SAFE_MOD = 0x00;

    // Configure P3.5 (LED) as push-pull output
    P3_MOD_OC &= ~(1 << 5);   // not open-drain
    P3_DIR_PU |=  (1 << 5);   // output, with pull-up

    // Configure P3.3 as input pull-up for interrupt
    P3_MOD_OC |=  (1 << 3);   // open-drain
    P3_DIR_PU |=  (1 << 3);   // enable pull-up (yes, |=, not &=)
    P3 |= (1 << 3);           // pull-up

    Serial_begin();

    while (1) {
        
        if(button_irq && !debounce) { // if push button was pressed and debounce has finished
            button_irq = 0; // clear the external interrupt 1 bit            
            if(ledON == 0){ // every time we enter here the LED status changes
                ledON= 1;   // from "blinking" to "not blinking" and vice versa
            }else{
                ledON= 0;
            }            
            Serial_println("INT1 detected!");
        }
        if(ledON){
            blink_led();
        }else {
            P3 &= ~(1 << 5);   // LED OFF           
        }
        if(serialTime > 100){ // 100 x 10ms= 1 second
            serialTime= 0;
            counter++;
            //Serial_print("Counter = ");
            Serial_println_uint(counter);
        }
        
    }
}

Conforme já comentado anteriormente, tenha uma pasta na qual salvar todos os códigos de exemplo. No meu caso é:

C:\Users\Clovisf\Documents\ch552

Abra o programa de linha de comando do Windows, digitando “cmd” ao pressionar o botão do menu do Windows. Então navegue até a sua pasta onde está o arquivo “external-interrupt-led.c”:

cd Documents\ch552

Agora desconecte a placa CH552 do computador, removendo o cabo USB. Então digite o comando abaixo, para usarmos um script que criei para compilação e gravação. O Script “build_flash.py” está neste link para você baixar. Ele precisa estar na mesma pasta que o seu arquivos “external-interrupt-led.c”. Aperte ENTER.

python build_flash.py external-interrupt-led.c

Após poucos segundos, aparecerá uma mensagem assim:

>> Put the CH552 into BOOT mode (hold BOOT, plug USB, release).
>> Press ENTER when ready...

Neste momento você deve apertar e segurar o botão BOOT da placa do CH552. Conecte o cabo USB entre a placa e o computador, ainda com o botão BOOT apertado. Só então solte o botão BOOT. Aperte ENTER. A gravação leva uma fração de segundos, a mensagem abaixo deve aparecer:

Write & verify complete. All seems OK.

✔  Build + Flash completed successfully!

Testes e detalhes finais

Neste momento o código que fizemos já deve estar rodando no CH552. Vamos então testar a interrupção externa do CH552?. Para isto, pressione brevemente o botão push button, o LED conectado ao pino P3.5 deve começar a piscar a 2Hz. Isto significa dois acendimentos e dois apagamentos por segundo.

Ao pressionar o push button novamente, o LED deve parar de piscar e apagar. Ele não pode ficar aceso sólido, se ficar tem algo errado no código. O mesmo para a piscada, ela deve parar ao pressionar do botão.

Para testar a comunicação serial, conecte o conversor USB-serial ao computador. Então abra o “gerenciador de dispositivos” (ou “device manager”) e verifique o nome da porta COM sob “Portas COM e LPT”. No meu caso aqui foi a “COM10”. Abra o sofware Putty e configure conforme a imagem abaixo.

Configuração do software Putty
Configuração do software Putty
  1. Insira o nome da sua porta COM (ex: COM10), vista lá no gerenciador de dispositivos
  2. Velocidade deixe em 9600
  3. Marque o radio-box “Serial”
  4. Clique em “Open”.

Uma tela preta será aberta, mostrando um número crescente por segundo. Também será mostrado um texto (“INT1 detected!”) toda vez que você pressionar o botão push button.

Comunicação serial com o CH552
Comunicação serial com o CH552

A interrupção monitorada no push button do pino P3.3 é basicamente instantânea, então você não vai conseguir ver nada especial. Apenas terá a certeza que a mesma funcionou, visto que o LED começa a piscar e depois para, conforme você pressiona o botão.

Fiz um vídeo sobre o funcionamento do código, veja logo no início do artigo. Caso você tenha dúvidas ou perguntas, pode escrever nos comentários abaixo, ou até mesmo lá no Youtube do FritzenLab_br. Use a ferramenta de pesquisa (lupa) para encontrar os demais artigos desta série, digitando “CH552”. Ou mesmo entre no Google e digite “CH552 FritzenLab”, vai achar todo o conteúdo.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *