terça-feira, outubro 27, 2020

Sensor Biométrico de Impressão Digital - Parte 2: Comunicando com um módulo FPM10A

Entre os vários sensores de digital disponíveis, acabei comprando um baseado no módulo FPM10A, cujo datasheet você pode ver aqui. Neste post vamos ver como é a comunicação com ele e fazer um primeiro teste.


A ligação do módulo a outros dispositivos é feito por comunicação serial assíncrona. Segundo o datasheet, a alimentação do módulo pode ser 3,6 e 6V e ele pode ser ligado diretamente a microcontroladores operando a 3,3 ou 5V. Entretanto, o meu módulo vem com uma indicação de 3,3V na placa... 


A velocidade padrão da comunicação é 57600, podendo ser programada para múltiplos de 9600bps. O formato dos dados é 8N1 (8 bits de dados, sem paridade, um stop bit). Após ser ligado o módulo precisa de 0,5 segundo para iniciação e só começa a tratar a comunicação após isso.

Os dados trafegam em pacotes com o seguinte formato:

O módulo suporta 23 comandos diferentes:

Os detalhes sobre cada comando, e suas respostas, estão no datasheet. Para um primeiro teste vamos usar o comando ReadSysPara (o formato da resposta está errado no datasheet):

O fato da comunicação ser serial assíncrona nos trás uma pequena dificuldade ao conectar ao Arduino Uno: o ATmega328 possui somente uma serial, que é usada para comunicar com o PC via USB. Isto significa que, usando o Arduino Uno, quando formos falar com o módulo vamos precisar desconectar do PC, o que dificulta a depuração. Neste meu primeiro teste eu quero ver no PC os dados que estão sendo trocados entre o Arduino e o PC. Uma opção seria usar a biblioteca SoftwareSerial, mas a considero bastante precária. Uma solução melhor é usar uma placa com mais de uma serial. Eu resolvi usar o Seeeduino XIAO, mas poderia ser um Arduino DUE  ou um Arduino Mega (se você não tiver medo de ligar um sinal de 5V no sensor).

O código ficou assim:

/**
 * Primeiro teste de comunicação com o sensor de
 * Digitais FPM10
 * 
 * Daniel Quadros, out/20
 */

// Algumas constantes
const uint16_t START = 0xEF01;
const byte CMD_READSYSPARAM = 0x0F;

// Estrutura do cabeçalho dos pacotes
typedef struct {
  byte startHi;
  byte startLo;
  byte addr3;
  byte addr2;
  byte addr1;
  byte addr0;
  byte tipo;
  byte tamHi;
  byte tamLo;
} HEADER;
HEADER header;

// Estrutura dos comandos
typedef struct {
  byte cmd;
} READSYSPARAM;

// Estrutura da resposta
typedef struct {
  HEADER header;
  byte codigo;
  union {
    byte dados[256];
    struct {
      byte statusreg[2];
      byte verify[2];
      byte libsize[2];
      byte seclevel[2];
      byte addr[4];
      byte datasize[2];
      byte baud[2];
    } sysparam;
  } d;
} RESPOSTA;
RESPOSTA resp;

void setup() {
  Serial.begin(115200);
  while (!Serial)
    ;
  Serial1.begin(9600*6);
  while (!Serial1)
    ;
  delay (500);
  Serial.println("Pronto");
  READSYSPARAM cmd;
  cmd.cmd = CMD_READSYSPARAM;
  enviaCmd ((byte *) &cmd, sizeof(cmd));
  Serial.println("Resposta:");
  if (recebeResp()) {
    Serial.println("Sucesso");
    Serial.print("Memoria para ");
    uint16_t mem = (resp.d.sysparam.libsize[0] << 8) + resp.d.sysparam.libsize[1];
    Serial.print(mem);
    Serial.println(" digitais");
  } else {
    Serial.println("Falhou");
  }
}

void loop() {
}

// Envia um comando para o sensor
void enviaCmd (byte *cmd, int tam) {
  // monta o cabeçalho e envia
  header.startHi = START >> 8;
  header.startLo = START & 0xFF;
  header.addr3 = 0xFF;  // endereço default
  header.addr2 = 0xFF;
  header.addr1 = 0xFF;
  header.addr0 = 0xFF;
  header.tipo = 0x01; // comando
  header.tamHi = (tam+2) >> 8;
  header.tamLo = (tam+2) & 0xFF;
  Serial1.write((byte *) &header, sizeof(header));

  // calcula o checksum
  uint16_t chksum = header.tipo + header.tamHi + header.tamLo;
  for (int i = 0; i < tam; i++) {
    chksum += cmd[i];
  }

  // envia o comando
  Serial1.write(cmd, tam);
  
  // envia o checksum
  Serial1.write(chksum >> 8);
  Serial1.write(chksum & 0xFF);
}

// Recebe uma resposta do sensor
bool recebeResp() {
  enum { START1, START2, ADDR, TAG, LEN1, LEN2, DADOS, CHECK1, CHECK2 } estado = START1;
  uint16_t checksum;
  byte *p = (byte *) &resp;
  int tam;
  int i;
  bool ok = false;
  while (true) {
    int c = Serial1.read();
    if (c == -1) {
      continue;
    }
    Serial.print (c, HEX);
    Serial.print (" ");
    *p++ = c;
    switch (estado) {
      case START1:
        if (c == (START >> 8)) {
          estado = START2;
        } else {
          p = (byte *) &resp; // ignora o byte lido
        }
        break;
      case START2:
        if (c == (START & 0xFF)) {
          estado = ADDR;
          i = 0;
        } else {
          estado = START1;
          p = (byte *) &resp; // ignora os bytes lidos
        }
        break;
      case ADDR:
        if (++i == 4) {
          estado = TAG;
        }
        break;
      case TAG:
        checksum = c;
        estado = LEN1;
        break;
      case LEN1:
        checksum += c;
        tam = c << 8;
        estado = LEN2;
        break;
      case LEN2:
        checksum += c;
        tam += c - 2;  // desconta o checksum
        estado = DADOS;
        i = 0;
        break;
      case DADOS:
        checksum += c;
        if (++i == tam) {
          estado = CHECK1;
        }
        break;
      case CHECK1:
        ok = c == (checksum >> 8);
        estado = CHECK2;
        break;
      case CHECK2:
        Serial.println ();
        return ok && (c == (checksum & 0xFF));
    }
  }
}

É algo mais ou menos típico do que costumo escrever para protocolos e deve servir como base para a implementação de mais comandos.

No próximo post vamos começar a ver os comandos relativos a digitais.

01/11/20: Corrigido código


Nenhum comentário: