quinta-feira, agosto 15, 2013

Leitor RFID YET-125K: Construindo uma Fechadura Eletrônica - Parte 4

Fechando este série de posts, vamos ver o software do microcontrolador que implementa a fechadura eletrônica. O software foi desenvolvido usando o avr-gcc toolchain e gravado na placa com o bootloadHID.


Vamos começar pela iniciação do hardware: entradas e saídas digitais, USART (para receber os dados do leitor de RFID) e o timer:
// LEDs
#define LED_TOVIVO      LED8
#define LED_LEU         LED7
#define LED_TRANCA      LED6
#define LED_CADASTRA    LED2
#define LED_APAGA       LED3

// Servo
#define SERVO_PORT      PORTB
#define SERVO_DDR       DDRB
#define SERVO           _BV(PB7)

// Teclas
#define TEC_CADASTRA    TEC1
#define TEC_APAGA       TEC4

    // Inicia as direçoes dos pinos de conexão dos LEDs
    LED_DDR |= 0xFF;
    
    // LEDs Apagados
    LED_PORT = 0x00;

    // Inicia conexão ao servo
    SERVO_DDR |= SERVO;
    SERVO_PORT &= ~SERVO;
    
    // Liga pullup das teclas
    TEC_DDR &= ~(TEC1|TEC2|TEC3|TEC4);
    TEC_PORT |= TEC1|TEC2|TEC3|TEC4;

    // Programa a USART para 9600 8N1
    UCSRA = _BV(U2X);              // para maior resolução do baud rate
    UCSRB = _BV(RXEN) | _BV(RXCIE);     // liga recepção, com interrupção
    UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0) ;  // 8bit, 1 stop, sem paridade
    UBRRL = (CLK_CPU / (8 * 9600UL)) - 1;           // 9600 bps

    // Preparar o timer 1 operar no modo CTC com 16MHz / 8
    // gerando interrupcao contando até 100 (0,05 ms)
    OCR1A = 100;
    TCCR1B = _BV(WGM12) | _BV(CS11);
    TIMSK |= (1<<OCIE1A);
Em seguida vamos ler a lista de tags da EEProm. Definimos dois vetores para isto, um na Ram e o outro na EEProm. A indicação EEMEM faz com que o linker coloque eepTags na sessão eeprom.
// Lista de tags conhecidos
#define NMAX_TAGS   8
static uint32_t tags[NMAX_TAGS];

// Tags salvos na EEPROM
uint32_t eepTags[NMAX_TAGS] EEMEM;


    int i;

    // Carrega os tags conhecidos da EEPROM
    for (i = 0; i < NMAX_TAGS; i++)
        tags[i] = eeprom_read_dword (&eepTags[i]);
A interrupção da USART é usada para receber o código do tag, usando uma pequena máquina de estados. O código do tag lido é colocado em tagLido.
// Último tag detectado
volatile uint32_t tagLido;

// Controle do LED de leitura
volatile uint8_t cntLedLeu = 0;

// Interrupção de recepção da USART
ISR(USART_RXC_vect)
{
    static uint8_t estado = 0;
    static uint32_t idTag;
    uint8_t c;

    c = UDR;
    if (bit_is_clear(UCSRA, FE))
    {
        // recebeu caracter sem erro
        if (estado == 0)
        {
            // Aguardando o STX
            if (c == 0x02)
            {
                estado = 1;
                idTag = 0;
            }
        }
        else if (estado == 9)
        {
            // Aguardando o ETX
            if (c == 0x03)
            {
                // Recebeu ID com sucesso
                tagLido = idTag;
                cntLedLeu = 100;
                LED_PORT |= LED_LEU;
            }
            estado = 0;
        }
        else
        {
            // recebeu um dígito, vamos tratar como hexadecimal
            if ((c >= '0') && (c <= '9'))
                idTag = (idTag << 4) | (c & 0xF);
            else if ((c >= 'A') && (c <= 'F'))
                idTag = (idTag << 4) | (c - 'A' + 10);
            else if ((c >= 'a') && (c <= 'f'))
                idTag = (idTag << 4) | (c - 'a' + 10);
            else
                estado = 0xFF;
            estado++;
        }
    }
    else
        estado = 0;
}
O nosso loop principal também é uma máquina de estados:
// Modo de Operação
typedef enum { MODO_NORMAL, MODO_CADASTRA, MODO_APAGA1, MODO_APAGA2 } MODOOPER;
static MODOOPER modo = MODO_NORMAL;

    for (;;)
    {
        if (modo == MODO_NORMAL)
        {
            if (tagLido != 0)
            {
                // Procura nos conhecidos
                for (i = 0; (i < NMAX_TAGS) && (tags[i] != 0xFFFFFFFF); i++)
                {
                    if (tags[i] == tagLido)
                    {
                        // achou, move a tranca
                        if (tranca == ABERTA)
                        {
                            Fecha();
                            tranca = FECHADA;
                        }
                        else
                        {
                            Abre();
                            tranca = ABERTA;
                        }
                        tagLido = 0;
                        break;
                    }
                }
            }
        }
        else if (modo == MODO_CADASTRA)
        {
            if (tagLido != 0)
            {
                // Procura nos conhecidos
                for (i = 0; (i < NMAX_TAGS) && (tags[i] != 0xFFFFFFFF); i++)
                {
                    if (tags[i] == tagLido)
                    {
                        // achou, ignora
                        break;
                    }
                }
                if ((i < NMAX_TAGS) && (tags[i] == 0xFFFFFFFF))
                {
                    // Não encontrou e tem espaço, coloca na lista
                    tags[i] = tagLido;
                    eeprom_update_dword (&eepTags[i], tagLido);
                }
                tagLido = 0;
            }
        }        
        else if (modo == MODO_APAGA2)
        {
            // Apaga a lista de Tags
            for (i = 0; i < NMAX_TAGS; i++)
            {
                tags[i] = 0xFFFFFFFF;
                eeprom_update_dword (&eepTags[i], 0xFFFFFFFF);
            }
            modo = MODO_NORMAL;
        }
    }
A abertura e fechamento da fechadura é feito enviando pulsos ao servo. A largura destes pulsos é que define a posição do eixo do servo. Para garantir o posicionamento correto são enviados vários pulsos iguais.
// Estado da Fechadura
typedef enum { ABERTA, FECHADA } FECHADURA;
static FECHADURA tranca = FECHADA;

// Controle do servo
volatile uint8_t nCiclos = 0;
volatile uint16_t posServo = 0;

// Abre a fechadura
static void Abre(void)
{
    posServo = 10;  // 0,5 ms
    nCiclos = 50;
    LED_PORT &= ~LED_TRANCA;
}

// Fecha a fechadura
static void Fecha(void)
{
    posServo = 30;  // 1,5 ms
    nCiclos = 50;
    LED_PORT |= LED_TRANCA;
}
A interrupção do timer é usada para testar as teclas e gerar todas as temporizações, inclusive os pulsos de controle do servo:
// Interrupção do Timer1
// Ocorre a cada 0,05 ms
ISR(TIMER1_COMPA_vect) 
{
    static uint16_t cntCiclo = 0;       // número de pulsos para o servo
    static uint8_t cntServo = 0;        // largura do pulso
    static uint8_t cnt10ms = 200;       // 200*0,05ms = 10ms
    
    // Tempos a seguir usam a base de 10ms
    static uint16_t cntCadastra = 500;  // 5 seg
    static uint16_t cntApaga = 1000;    // 10 seg
    static uint8_t cntLed = 20;         // 0,2 seg
    static uint8_t cntToVivo = 50;      // 0,5 seg
    
    if (nCiclos != 0)
    {
        if (cntCiclo == 0)
        {
            // Fim do ciclo
            if (--nCiclos != 0)
            {
                // Novo ciclo
                SERVO_PORT |= SERVO;
                cntServo = posServo;
                cntCiclo = 400; // 20 ms
            }
        }
        if (nCiclos != 0)
        {
            if ((cntServo != 0) && (--cntServo == 0))
                SERVO_PORT &= ~SERVO;
            cntCiclo--;
        }
    }
    
    if (--cnt10ms == 0)
    {
        cnt10ms = 200;
        if (cntLedLeu != 0)
        {
            if (--cntLedLeu == 0)
                LED_PORT &= ~LED_LEU;
        }
        if (--cntToVivo == 0)
        {
            LED_PORT ^= LED_TOVIVO;
            cntToVivo = 50;
        }
        if (modo == MODO_NORMAL)
        {
            if ((TEC_PIN & TEC_CADASTRA) == 0)
            {
                if (--cntCadastra == 0)
                {
                    LED_PORT |= LED_CADASTRA;
                    modo = MODO_CADASTRA;
                }
            }
            else
                cntCadastra = 500;
            if ((TEC_PIN & TEC_APAGA) == 0)
            {
                if (--cntApaga == 0)
                {
                    modo = MODO_APAGA1;
                    cntApaga = 1000;
                    cntLed = 20;
                }
            }
            else
                cntApaga = 1000;
        }
        else if (modo == MODO_CADASTRA)
        {
            if ((TEC_PIN & TEC_CADASTRA) == 0)
            {
                if (--cntCadastra == 0)
                {
                    LED_PORT &= ~LED_CADASTRA;
                    modo = MODO_NORMAL;
                }
            }
            else
                cntCadastra = 500;
        }
        else if (modo == MODO_APAGA1)
        {
            if (--cntLed == 0)
            {
                LED_PORT ^= LED_APAGA;
                cntLed = 20;
            }
            if ((TEC_PIN & TEC_APAGA) == 0)
            {
                if (--cntApaga == 0)
                {
                    modo = MODO_APAGA2;
                    cntApaga = 1000;
                    LED_PORT &= ~LED_APAGA;
                }
            }
            else
            {
                modo = MODO_NORMAL;
                cntApaga = 1000;
                LED_PORT &= ~LED_APAGA;
            }
        }
    }
}
Como de costume, o projeto completo está nos arquivos do blog (link lá no alto à direita), neste caso no arquivo bau.zip.

Nenhum comentário: