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:

  1. /** 
  2.  * Primeiro teste de comunicação com o sensor de 
  3.  * Digitais FPM10 
  4.  *  
  5.  * Daniel Quadros, out/20 
  6.  */  
  7.   
  8. // Algumas constantes  
  9. const uint16_t START = 0xEF01;  
  10. const byte CMD_READSYSPARAM = 0x0F;  
  11.   
  12. // Estrutura do cabeçalho dos pacotes  
  13. typedef struct {  
  14.   byte startHi;  
  15.   byte startLo;  
  16.   byte addr3;  
  17.   byte addr2;  
  18.   byte addr1;  
  19.   byte addr0;  
  20.   byte tipo;  
  21.   byte tamHi;  
  22.   byte tamLo;  
  23. } HEADER;  
  24. HEADER header;  
  25.   
  26. // Estrutura dos comandos  
  27. typedef struct {  
  28.   byte cmd;  
  29. } READSYSPARAM;  
  30.   
  31. // Estrutura da resposta  
  32. typedef struct {  
  33.   HEADER header;  
  34.   byte codigo;  
  35.   union {  
  36.     byte dados[256];  
  37.     struct {  
  38.       byte statusreg[2];  
  39.       byte verify[2];  
  40.       byte libsize[2];  
  41.       byte seclevel[2];  
  42.       byte addr[4];  
  43.       byte datasize[2];  
  44.       byte baud[2];  
  45.     } sysparam;  
  46.   } d;  
  47. } RESPOSTA;  
  48. RESPOSTA resp;  
  49.   
  50. void setup() {  
  51.   Serial.begin(115200);  
  52.   while (!Serial)  
  53.     ;  
  54.   Serial1.begin(9600*6);  
  55.   while (!Serial1)  
  56.     ;  
  57.   delay (500);  
  58.   Serial.println("Pronto");  
  59.   READSYSPARAM cmd;  
  60.   cmd.cmd = CMD_READSYSPARAM;  
  61.   enviaCmd ((byte *) &cmd, sizeof(cmd));  
  62.   Serial.println("Resposta:");  
  63.   if (recebeResp()) {  
  64.     Serial.println("Sucesso");  
  65.     Serial.print("Memoria para ");  
  66.     uint16_t mem = (resp.d.sysparam.libsize[0] << 8) + resp.d.sysparam.libsize[1];  
  67.     Serial.print(mem);  
  68.     Serial.println(" digitais");  
  69.   } else {  
  70.     Serial.println("Falhou");  
  71.   }  
  72. }  
  73.   
  74. void loop() {  
  75. }  
  76.   
  77. // Envia um comando para o sensor  
  78. void enviaCmd (byte *cmd, int tam) {  
  79.   // monta o cabeçalho e envia  
  80.   header.startHi = START >> 8;  
  81.   header.startLo = START & 0xFF;  
  82.   header.addr3 = 0xFF;  // endereço default  
  83.   header.addr2 = 0xFF;  
  84.   header.addr1 = 0xFF;  
  85.   header.addr0 = 0xFF;  
  86.   header.tipo = 0x01; // comando  
  87.   header.tamHi = (tam+2) >> 8;  
  88.   header.tamLo = (tam+2) & 0xFF;  
  89.   Serial1.write((byte *) &header, sizeof(header));  
  90.   
  91.   // calcula o checksum  
  92.   uint16_t chksum = header.tipo + header.tamHi + header.tamLo;  
  93.   for (int i = 0; i < tam; i++) {  
  94.     chksum += cmd[i];  
  95.   }  
  96.   
  97.   // envia o comando  
  98.   Serial1.write(cmd, tam);  
  99.     
  100.   // envia o checksum  
  101.   Serial1.write(chksum >> 8);  
  102.   Serial1.write(chksum & 0xFF);  
  103. }  
  104.   
  105. // Recebe uma resposta do sensor  
  106. bool recebeResp() {  
  107.   enum { START1, START2, ADDR, TAG, LEN1, LEN2, DADOS, CHECK1, CHECK2 } estado = START1;  
  108.   uint16_t checksum;  
  109.   byte *p = (byte *) &resp;  
  110.   int tam;  
  111.   int i;  
  112.   bool ok = false;  
  113.   while (true) {  
  114.     int c = Serial1.read();  
  115.     if (c == -1) {  
  116.       continue;  
  117.     }  
  118.     Serial.print (c, HEX);  
  119.     Serial.print (" ");  
  120.     *p++ = c;  
  121.     switch (estado) {  
  122.       case START1:  
  123.         if (c == (START >> 8)) {  
  124.           estado = START2;  
  125.         } else {  
  126.           p = (byte *) &resp; // ignora o byte lido  
  127.         }  
  128.         break;  
  129.       case START2:  
  130.         if (c == (START & 0xFF)) {  
  131.           estado = ADDR;  
  132.           i = 0;  
  133.         } else {  
  134.           estado = START1;  
  135.           p = (byte *) &resp; // ignora os bytes lidos  
  136.         }  
  137.         break;  
  138.       case ADDR:  
  139.         if (++i == 4) {  
  140.           estado = TAG;  
  141.         }  
  142.         break;  
  143.       case TAG:  
  144.         checksum = c;  
  145.         estado = LEN1;  
  146.         break;  
  147.       case LEN1:  
  148.         checksum += c;  
  149.         tam = c << 8;  
  150.         estado = LEN2;  
  151.         break;  
  152.       case LEN2:  
  153.         checksum += c;  
  154.         tam += c - 2;  // desconta o checksum  
  155.         estado = DADOS;  
  156.         i = 0;  
  157.         break;  
  158.       case DADOS:  
  159.         checksum += c;  
  160.         if (++i == tam) {  
  161.           estado = CHECK1;  
  162.         }  
  163.         break;  
  164.       case CHECK1:  
  165.         ok = c == (checksum >> 8);  
  166.         estado = CHECK2;  
  167.         break;  
  168.       case CHECK2:  
  169.         Serial.println ();  
  170.         return ok && (c == (checksum & 0xFF));  
  171.     }  
  172.   }  
  173. }  

É 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: