quarta-feira, abril 27, 2016

Módulo de Rádio FM com CI RDA5807M - Parte 3

Agora que já conseguimos colocar o módulo funcionar, vamos começar a explorar um recurso interessante: o RDS (Radio Data System).


O objetivo do RDS é permitir às estações de rádio transmitir pequenas quantidades de dados. Os usos típicos são o envio da identificação da estação, informações sobre o que está sendo transmitido (como nome e interprete da música) e informações sobre o transito.

Onde estes dados são colocados no sinal? Originalmente a transmissão FM enviava somente um canal de som, com frequências de 30Hz a 15KHz. O sistema foi expandido para envio de dois canais (som estéreo) colocando informação adicional em torno dos 38KHz. Um sinal contínuo de 19KHz indica a presença desta informação adicional. Os dados do RDS são enviados em torno dos 57KHz (3*19KHz). A figura abaixo (by Arthur Murray - http://en.wikipedia.org/wiki/File:RDS_vs_DirectBand_FM-spectrum2.png) mostra as várias informações que podem ser transmitidas por uma estação FM.


Os dados são transmitidos a uma taxa ligeiramente inferior a 1200bps (57000/48 bps para ser preciso). A transmissão é feita por grupos de 104 bits, divididos em quatro blocos de 26 bits. Estes 26 bits correspondem a 16 bits de informação mais 10 bits para detecção e correção de erros.

O primeiro bloco (bloco A) contém sempre o "código de identificação do programa" (PI code) que, nos EUA,  informa o prefixo da emissora. O segundo bloco (bloco B) informa o tipo de grupo, que determina o que está codificado nos blocos C e D. Daí em diante a coisa fica ainda mais complicada, a norma americana pode ser vista aqui.

O RDA5807M possui toda a lógica para detectar, decodificar e conferir um grupo, disponibilizando os dados dos quatro blocos em quatro registradores de 16 bits. Uma limitação é que ele não efetua as correções, nem disponibiliza os bits necessários para a correção. Estranhamente, são indicados os erros encontrados apenas nos blocos A e B. Portanto não temos como saber se os dados nos blocos C e D estão corretos.

Uma outra dificuldade é saber quando um novo grupo está disponível. Existe um bit no registrador de status para isto, mas não encontrei uma forma de desligá-lo após ler os registradores. Este artigo sugere desligar e ligar a função de RDS, mas fiquei com a impressão que a recepção de um novo grupo demora bastante quando isto é feito. Outra forma, usada pela biblioteca que vou usar em testes futuros, é simplesmente ver se mudou em relação à leitura anterior.

Como primeiro teste, vamos experimentar detectar a recepção de grupos através da comparação e mostrar os blocos em hexadecimal. O código abaixo usa a mesma biblioteca do post anterior.
  1. #include <Wire.h>  
  2. #include <RDA5807M.h>  
  3.   
  4. // Estação para teste, escolha uma estação  
  5. // que você sabe que envia RDS  
  6. const word ESTACAO = 9690;  // 96,9MHz  
  7.   
  8. RDA5807M radio;  
  9. word bloco[4] = { 0, 0, 0, 0 };  
  10. byte regblc[4] =  
  11. {  
  12.   RDA5807M_REG_RDSA, RDA5807M_REG_RDSB,  
  13.   RDA5807M_REG_RDSC, RDA5807M_REG_RDSD  
  14. };  
  15.   
  16. void setup()  
  17. {  
  18.   // Inicia a serial  
  19.   Serial.begin(9600);  
  20.   
  21.   // Inicia o rádio  
  22.   radio.begin(RDA5807M_BAND_WEST);  
  23.   radio.mute();  
  24.   sintoniza (ESTACAO);  
  25.   radio.unMute(true);  
  26.   
  27.   // Tempinho para começar a receber RDS  
  28.   delay (500);  
  29.   
  30.   // Informa o status  
  31.   word frequency = radio.getFrequency();  
  32.   Serial.print(F("Sintonizado em "));  
  33.   Serial.print(frequency / 100);  
  34.   Serial.print(".");  
  35.   Serial.print(frequency % 100);  
  36.   Serial.println(F("MHz FM"));  
  37.   Serial.print(F("RSSI = "));  
  38.   Serial.print(radio.getRSSI());  
  39.   Serial.println("dBuV");  
  40.   word status = radio.getRegister(RDA5807M_REG_STATUS);  
  41.   if(status & RDA5807M_STATUS_RDSR)  
  42.     Serial.println(F("* RDS Group Ready"));  
  43.   if(status & RDA5807M_STATUS_STC)  
  44.     Serial.println(F("* Seek/Tune Complete"));  
  45.   if(status & RDA5807M_STATUS_RDSS)  
  46.     Serial.println(F("* RDS Decoder Synchronized"));  
  47.   if(status & RDA5807M_STATUS_ST)  
  48.     Serial.println(F("* Stereo Reception"));  
  49. }  
  50.   
  51. void loop()  
  52. {  
  53.   if (atlBlocos())  
  54.   {  
  55.     for (int i = 0; i < 4; i++)  
  56.     {  
  57.       printReg(bloco[i]);  
  58.     }  
  59.     Serial.println();  
  60.   }  
  61.   delay (10);  
  62. }  
  63.   
  64. // Atualiza os blocos  
  65. // Retorna true se houve mudança  
  66. byte atlBlocos()  
  67. {  
  68.   byte mudou = false;  
  69.   word status = radio.getRegister(RDA5807M_REG_STATUS);  
  70.   word erros = radio.getRegister(RDA5807M_REG_RSSI) &  
  71.                 (RDA5807M_BLERA_MASK | RDA5807M_BLERB_MASK);  
  72.     
  73.   if((status & RDA5807M_STATUS_RDSR) && (erros == 0))  
  74.   {  
  75.     for (int i = 0; i < 4; i++)  
  76.     {  
  77.       word aux = radio.getRegister(regblc[i]);  
  78.       if (aux != bloco[i])  
  79.       {  
  80.         bloco[i] = aux;  
  81.         mudou = true;  
  82.       }  
  83.     }  
  84.   }  
  85.   return mudou;  
  86. }  
  87.   
  88. // Imprime um tegistrador em hexa  
  89. const char hexa[] = "0123456789ABCDEF";  
  90. void printReg (word val)  
  91. {  
  92.     char aux[6];  
  93.     aux[0] = hexa[(val >> 12) & 0x0F];  
  94.     aux[1] = hexa[(val >> 8) & 0x0F];  
  95.     aux[2] = hexa[(val >> 4) & 0x0F];  
  96.     aux[3] = hexa[val & 0x0F];  
  97.     aux[4] = ' ';  
  98.     aux[5] = 0;  
  99.     Serial.print(aux);  
  100. }  
  101.   
  102. // Sintoniza em uma frequência  
  103. // (esta rotina está faltando na biblioteca)  
  104. #define RDA5807M_TUNE_BIT word(0x0010)  
  105. void sintoniza (word freq)  
  106. {  
  107.   // verifica a programação de banda e espaçamento  
  108.   word band = radio.getRegister(RDA5807M_REG_TUNING) &   
  109.                 (RDA5807M_BAND_MASK | RDA5807M_SPACE_MASK);  
  110.   const byte space = band & RDA5807M_SPACE_MASK;  
  111.   band = (band & RDA5807M_BAND_MASK) >> 2;  
  112.     
  113.   // calcula o valor do canal  
  114.   freq -= pgm_read_word(&RDA5807M_BandLowerLimits[band]);  
  115.   word channel = (freq*10)/pgm_read_byte(&RDA5807M_ChannelSpacings[space]);  
  116.     
  117.   // efetua a sintonia  
  118.   radio.updateRegister(RDA5807M_REG_TUNING, RDA5807M_TUNE_BIT, 0);  
  119.   radio.updateRegister(RDA5807M_REG_TUNING,   
  120.       RDA5807M_CHAN_MASK | RDA5807M_TUNE_BIT,   
  121.       (channel << RDA5807M_CHAN_SHIFT) | RDA5807M_TUNE_BIT);  
  122.     
  123.   // aguarda sintonizar  
  124.   for (int i = 0; i < 10; i++)  
  125.   {  
  126.     word status = radio.getRegister(RDA5807M_REG_STATUS);  
  127.     if(status & RDA5807M_STATUS_STC)  
  128.        break;  
  129.     delay (100);  
  130.   }  
  131. }  
Vamos agora alterar a rotina loop() para decodificar a identificação da emissora enviada através de grupos do tipo A0 ou B0 (código adaptado do artigo mencionado acima). Nestes grupos o bloco D possui dois caracteres e o bloco B informa a posição destes caracteres em um buffer de 8 posições.
  1. #define RDS_GROUP     0xF800  
  2. #define RDS_GROUP_A0  0x0000  
  3. #define RDS_GROUP_B0  0x0800  
  4.   
  5. char nome[9] = "????????";  
  6.   
  7. void loop()  
  8. {  
  9.   if (atlBlocos())  
  10.   {  
  11.     if (((bloco[1] & RDS_GROUP) == RDS_GROUP_A0) ||  
  12.         ((bloco[1] & RDS_GROUP) == RDS_GROUP_B0))  
  13.     {  
  14.       byte offset = (bloco[1] & 0x03) << 1;  
  15.       char c1 = (char)(bloco[3] >> 8);  
  16.       char c2 = (char)(bloco[3] & 0xFF);  
  17.       nome[offset] = c1;  
  18.       nome[offset+1] = c2;  
  19.       Serial.println (nome);  
  20.     }  
  21.   }  
  22.   delay (10);  
  23. }  
Executando este código com a emissora que estou usando para testes, deu para perceber dois problemas:
  • Ocorrem com frequência erros no bloco D. Uma solução é só aceitar o bloco se for recebido igual mais de uma vez consecutiva.
  • Apesar da norma recomendar não mudar o texto em intervalo inferior a 1 minuto, esta emissora (e outras que eu testei) muda o texto a cada poucos segundos para enviar o seu slogan.
A versão abaixo inclui alguns filtros adicionais para tentar fornecer textos mais limpos.
  1. char nome_a[9] = "????????";  
  2. char nome_b[9] = "????????";  
  3.   
  4. void loop()  
  5. {  
  6.   if (atlBlocos())  
  7.   {  
  8.     if (((bloco[1] & RDS_GROUP) == RDS_GROUP_A0) ||  
  9.         ((bloco[1] & RDS_GROUP) == RDS_GROUP_B0))  
  10.     {  
  11.       if (atlNome())  
  12.       {  
  13.         Serial.println (nome);  
  14.       }  
  15.     }  
  16.   }  
  17. }  
  18.   
  19. // Atualiza o nome  
  20. // Retorna true se tem um novo nome  
  21. byte atlNome()  
  22. {  
  23.   // extrai a posição e os caracteres  
  24.   byte offset = (bloco[1] & 0x03) << 1;  
  25.   char c1 = (char)(bloco[3] >> 8);  
  26.   char c2 = (char)(bloco[3] & 0xFF);  
  27.   byte igual;  
  28.     
  29.   // despreza caracteres absurdos  
  30.   if ((c1 < 0x20) || (c1 > 0x7E))  
  31.     return false;  
  32.   if ((c2 < 0x20) || (c2 > 0x7E))  
  33.     return false;  
  34.   
  35.   // só atualizar nome_a depois de receber  
  36.   // duas vezes igual  
  37.   if (nome_b[offset] == c1)  
  38.   {  
  39.     nome_a[offset] = c1;  
  40.     nome_b[offset] = '?';  
  41.   }  
  42.   else  
  43.   {  
  44.     nome_b[offset] = c1;  
  45.   }  
  46.   if (nome_b[offset+1] == c2)  
  47.   {  
  48.     nome_a[offset+1] = c2;  
  49.     nome_b[offset+1] = '?';  
  50.   }  
  51.   else  
  52.   {  
  53.     nome_b[offset+1] = c2;  
  54.   }  
  55.   
  56.   // verifica se recebeu um nome completo  
  57.   for (int i = 0; i < 8; i++)  
  58.   {  
  59.     if (nome_a[i] == '?')  
  60.       return false;  
  61.   }  
  62.     
  63.   // atualizar o nome e preparar para receber o  
  64.   // próximo  
  65.   igual = true;  
  66.   for (int i = 0; i < 8; i++)  
  67.   {  
  68.     if (nome[i] != nome_a[i])  
  69.       igual = false;  
  70.     nome[i] = nome_a[i];  
  71.     nome_a[i] = nome_b[i] = '?';  
  72.   }  
  73.     
  74.   return !igual;  
  75. }  
No próximo post vamos experimentar uma biblioteca mais sofisticada, que decodifica vários tipos de grupos. De qualquer forma, está claro que o suporte a RDS no RDA5807M está longe de ser perfeito.

Nenhum comentário: