Hardware
Neste meu experimento vou fazer a comunicação entre dois Arduinos Nano, cada um com uma tecla e um LED. Quando a tecla for apertada ou solta em um Arduino, ele enviará uma mensagem para o outro acender ou apagar o LED.
Nada de muito especial na montagem. O botão é ligado entre o pino digital 7 e terra. O LED é ligado ao terra e, via um resistor de 220R, ao pino digital 6. Para selecionar endereços diferentes, um dos Arduinos terá o pino digital 8 ligado a terra e o outro o pino digital 8 desconectado.
A placa com o nRF24L01+ é ligada da seguinte forma (neste teste simples não vou usar o sinal IRQ):
- GND (1)ao GND ou ISP 6
- Vcc (2) ao 3V
- CE (3) ao pino digital 9
- CSN (4) ao pino digital 10
- SCK (5) ao pino digital 13 ou ISP 3
- MOSI (6) ao pino digital 11 ou ISP 4
- MISO (7) ao pino digital 12 ou ISP 1
Software
O software completo pode ser baixado dos arquivos do blog (TestenRF24L01.zip) ou do github.
A comunicação com o nRF24L01+ é feita via SPI, uma transação é iniciada baixando o sinal CSN e finalizada retornando-o ao nível alto. O primeiro byte enviado é um comando, dependendo do comando bytes adicionais podem ser escritos ou lidos. É importante lembrar que a comunicação SPI ocorre sempre simultaneamente nos dois sentidos: a cada byte transmitido é recebido.
Vamos começar vendo duas rotinas simples, para enviar um comando e escrever e ler em um registrador. Existe um comando para leitura e outro para escrita, o número do registrador é colocado no próprio comando:
// Envia comando ao nRF24L01+ uint8_t send_command(uint8_t cmd) { uint8_t status; digitalWrite(pinCSN, LOW); status = SPI.transfer(cmd); digitalWrite(pinCSN, HIGH); return status; } // Escreve um valor em um registrador do nRF24L01+ uint8_t write_register(uint8_t reg, uint8_t value) { uint8_t status; digitalWrite(pinCSN, LOW); status = SPI.transfer( W_REGISTER | ( REGISTER_MASK & reg ) ); SPI.transfer(value); digitalWrite(pinCSN, HIGH); return status; } // Le um registrador do nRF24L01+ uint8_t read_register(uint8_t reg) { uint8_t result; digitalWrite(pinCSN, LOW); SPI.transfer( R_REGISTER | ( REGISTER_MASK & reg ) ); result = SPI.transfer(0xff); digitalWrite(pinCSN, HIGH); #ifdef TRACE Serial.print ("Reg "); Serial.print (reg, HEX); Serial.print (" = "); Serial.println (result, HEX); #endif return result; }Os registradores de endereço, que veremos adiante, são um pouco diferentes. Após o comando de leitura ou escrita são lidos ou escritos vários bytes. A rotina abaixo será usada para escrever um endereço:
// Escreve varios valores em um registrador do nRF24L01+ uint8_t writeN_register(uint8_t reg, uint8_t *pValue, uint8_t n) { uint8_t status; digitalWrite(pinCSN, LOW); status = SPI.transfer( W_REGISTER | ( REGISTER_MASK & reg ) ); while (n--) SPI.transfer(*pValue++); digitalWrite(pinCSN, HIGH); return status; }Os endereços nos pacotes podem ter de 3 a 5 bytes, vou trabalhar com o mínimo. Para a transmissão existe um registrador, TX_ADDR, onde é deve ser escrito o endereço do destino. A recepção é mais complicada, pois existem seis "pipes" (quase) independentes, numerados de 0 a 5. A ideia é que você pode colocar endereços diferentes nestes seis pipes e receber mensagens com até seis endereços diferentes. Mais precisamente, apenas os pipes 0 e 1 possuem endereços completos (3 a 5 bytes), os pipes 2 a 5 possuem apenas o byte menos significativo do endereço, os bytes mais significativos são os do pipe 1.
Para confundir um pouco mais, o pipe 0 é automaticamente usado pelo recurso de ACK automático do nRF24L01+ . Isto significa que o seu endereço será automaticamente alterado após uma transmissão.
No meu caso vou ficar no mais simples possível, recebendo apenas no pipe 1 e deixando o pipe 0 para a recepção do ACK automático.
Com estas explicações, podemos ver a rotina de iniciação do rádio. Conforme indicado nos cometários, adotei uma configuração simples e fixa para os parâmetros.
// Iniciacao do Radio void radioInit() { uint8_t addr[3]; Serial.println ("Inciando radio..."); // Incia o SPI SPI.begin(); // Inicia os sinais de controle pinMode (pinCE, OUTPUT); digitalWrite(pinCE, LOW); pinMode (pinCSN, OUTPUT); digitalWrite(pinCSN, HIGH); // Configura o radio delay(5); // para o caso do radio acabar de ser ligado write_register(CONFIG, 0b00001100); // CRC de 16 bits write_register(SETUP_RETR, 0x5F);// ate 15 retries, timeout = 1,5ms write_register(RF_SETUP, 0x06); // 1Mbps, Potencia maxima write_register(FEATURE,0 ); // trabalhar com pacotes de tamanho fixo write_register(DYNPD,0); // trabalhar com pacotes de tamanho fixo write_register(NRF_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT)); write_register(RF_CH, 76); // usar canal 76 send_command(FLUSH_RX); // limpa a recepcao send_command(FLUSH_TX); // limpa a transmissao write_register(CONFIG, read_register(CONFIG) | _BV(PRIM_RX)); // modo Rx powerUp(); // liga o radio // Configura os enderecos addr[0] = addrTx; addr[1] = 0; addr[2] = 0; write_register(SETUP_AW, 1); // enderecos de 3 bytes writeN_register(TX_ADDR, addr, 3); // endereco de transmissao writeN_register(RX_ADDR_P0, addr, 3); // auto ACK addr[0] = addrRx; writeN_register(RX_ADDR_P1, addr,3); // endereco de recepcao write_register(EN_RXADDR,2); // recepcao habilitada no pipe 1 // Pacotes com 1 byte de dado write_register(RX_PW_P1, 1); Serial.println ("Radio iniciado."); } // Liga o radio void powerUp(void) { uint8_t cfg = read_register(CONFIG); // Se estava desligado, liga e espera iniciar if (!(cfg & _BV(PWR_UP))) { write_register(CONFIG, cfg | _BV(PWR_UP)); delay(5); } }A minha aplicação vai deixar o rádio com a recepção ligada a maior parte do tempo. Para ligar a recepção basta selecionar recepção no registrador CONFIG e levantar o sinal CE, o resto dos registradores já foram programados na iniciação. Fora isto, os indicadores no registrador de status são limpos:
// Inicia a recepcao void startRx(void) { write_register(CONFIG, read_register(CONFIG) | _BV(PRIM_RX)); write_register(NRF_STATUS, _BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT) ); digitalWrite(pinCE, HIGH); }Para verificar se ocorreu uma recepção basta testar o bit correspondente no registrador de status:
// Testa se tem recepcao uint8_t temRx(void) { return read_register(NRF_STATUS) & _BV(RX_DR); }Detectada uma recepção, os bytes de dados (payload) podem ser lidos através do comando R_RX_PAYLOAD. No meu caso será um único byte:
// Le o dado recebido e limpa flag de recebido uint8_t rxDado(void) { uint8_t dado; digitalWrite(pinCSN, LOW); SPI.transfer(R_RX_PAYLOAD); dado = SPI.transfer(0xFF); digitalWrite(pinCSN, HIGH); write_register(NRF_STATUS, _BV(RX_DR)); return dado; }Para desligar a recepção, é preciso baixar o sinal CE, aguardar uma eventual recepção em curso ser finalizada (inclusive o envio do ACK) e depois chavear para transmissão no registrador CONFIG.
// Para a recepcao void stopRx(void) { // Desliga RX digitalWrite(pinCE, LOW); delayMicroseconds(65); // Tempo de recepcao delayMicroseconds(65); // Tempo de envio de ACK send_command(FLUSH_TX); // Limpa eventual ACK write_register(CONFIG, (read_register(CONFIG) ) & ~_BV(PRIM_RX)); // Habilita recepcao no pipe 0 (para ACK) write_register(EN_RXADDR,read_register(EN_RXADDR) | 1); }Uma vez desligada a recepção, podemos disparar uma transmissão. Os dados devem ser enviados em uma única transação SPI, usando o comando W_TX_PAYLOAD. Escritos os bytes, basta levantar o sinal CE para automagicamente o pacote ser transmitido e retransmitido até receber um ACK ou estourar o limite de retransmissões. Ambas ocorrências (sucesso ou erro) são sinalizadas no registrador de status. Ao final da transmissão o sinal CE é devolvido ao nível baixo.
// Envia um byte uint8_t txDado (uint8_t dado) { uint8_t status; // Coloca na fila o byte a transmitir digitalWrite(pinCSN, LOW); SPI.transfer(W_TX_PAYLOAD); SPI.transfer(dado); digitalWrite(pinCSN, HIGH); // Dispara a transmissao digitalWrite(pinCE, HIGH); // Espera concluir while ((read_register(NRF_STATUS) & (_BV(TX_DS) | _BV(MAX_RT))) == 0) ; // Desligar o transmissor digitalWrite(pinCE, LOW); // desliga recepcao no pipe 0 write_register(EN_RXADDR,2); // Verifica o resultado status = write_register(NRF_STATUS,_BV(RX_DR) | _BV(TX_DS) | _BV(MAX_RT)); if( status & _BV(MAX_RT)) { send_command(FLUSH_TX); return 0; } else { return 1; } }Para completar o programa, temos a iniciação geral e a aplicação principal:
void setup() { // Serial para debug Serial.begin(9600); // Iniciacao dos pinos de LED, Botao e Selecao do endereco pinMode (pinLED, OUTPUT); pinMode (pinBotao, INPUT); digitalWrite (pinBotao, HIGH); pinMode (pinSelAddr, INPUT); digitalWrite (pinSelAddr, HIGH); // Seleciona os enderecos if (digitalRead(pinSelAddr) == LOW) { addrRx = 1; addrTx = 2; } else { addrRx = 2; addrTx = 1; } Serial.print("Recebendo no endereco "); Serial.println(addrRx); // Iniciacao do Radio radioInit(); startRx(); } void loop() { uint8_t botao; botao = digitalRead(pinBotao); if (botao != botaoAnt) { botaoAnt = botao; Serial.print ("Transmitindo: "); stopRx(); if (txDado (botao)) Serial.println ("Ok"); else Serial.println ("Erro"); startRx(); } else if (temRx()) { uint8_t dado = rxDado(); Serial.print("Recebido "); Serial.println(dado); if (dado == LOW) digitalWrite(pinLED, HIGH); else digitalWrite(pinLED, LOW); } }
O que sobra (e pode ser visto no programa completo) são as definições das constantes.
No próximo post vamos ver esta mesma aplicação usando a biblioteca RF24.
05/04/16: Deixado mais claro que o pino de seleção de endereço é o digital 8 do Arduino.
7 comentários:
Boa noite, Daniel,
Sou novato em transmissão de dados. Usei um par do módulo RF 433Mhz para enviar e receber dados no mesmo arduino e ficou bom, porém a conexão depende de uma antena bem grande.
Agora estou tentando usar o NRF, mas é bem complicado para quem inicia. Gostaria de montar um circuito onde o mesmo Arduino pudesse transmitir e receber.
Além disso, gostaria de transmitir para outros Arduinos ao redor, como se eu montasse uma rede, por exemplo: aciono um botão no Arduino A e no Arduino B, C, D... liga um LED... não sei até quantas conexões dessa posso conseguir.
Poderia, por favor, me sinalizar de uma forma simples como fazer e se tem como?
Desde já muito obrigado e parabéns pelo seu trabalho e explicação. Vou tentar usar um pouco do que li aqui.
Abraços. Tom
Tom,
O alcance deste módulo que estou usando não é muito grande, para ter um alcance razoável é preciso um amplificador e uma antena externa, como este módulo aqui.
A montagem do hardware é simples, o que é complicado com o nRF24L01+ é o software. O jeito mais fácil de fazer o que você quer é usando a biblioteca RF24, veja os meus posts sobre ela.
boas;
estou tentado o seu exemplos ,mas sempre que pressiono o botão de pressão diz tramiter erro tanto num como no outro .
estou usando dois nano com chip ch304 e não estou usado os isp deles .
quanto ao pin 8 ligado o terra tem de se programar o modulo com pino ligado ao terra ou so se liga depois de programado ?
se poder me dar ai uma mãozinha agradecia
Luís, você deve montar um dos Arduinos com o pino 8 ligado ao terra e o outro com o pino 8 sem conexão. Desta forma eles ficarão com endereço diferente. O pino 8 é testado na iniciação (rotina setup()).
sim eu sei isso mas faço isso e carrego o programa com o pino ligado ou na transferência de dados não importa só quando se liga os 2 e que se liga o pino 8 (irq) ao gnd ?
Luís, é o pino 8 (Digital 8) do Arduino, não do rádio. O pino 8 precisa estar na posição correta no início da execução do programa, não faz diferença na hora da carga. Se você mudar a ligação do pino 8 depois de carregar o programa, dê um reset para o pino ser lido novamente.
já funciona xd.
realmente foi falta de atenção minha , não ter realmente reparado que era o pino 8 do arduino não do radio .
a única coisa fora de anormal e que um dos radio ao transmitir diz transmitir erro mas faz a transmissão .
O outro já não, transmiti e diz transmissão ok .
Postar um comentário