AdSense

Freitag, 26. Juli 2013

Atmega/Arduino: Funkmodul nRF24L01+ Grundlagen

(English version) Heute gibt es wieder einen Gastbeitrag von mir, dieses mal zum Funkmodul nRF24L01+ (oder nRF24L01P) von Nordic Semiconductor. Das Modul ist für wenige Euro bei Ebay zu haben. Es handelt sich um einen "Ultra low power 2.4GHz RF Transceiver" - es kann also sowohl Senden als auch Empfangen und arbeitet im 2.4GHz-Band. Dabei arbeitet es laut Hersteller besonders sparsam, es zieht einen Maximalstrom von gerade einmal 15mA. Dabei erreicht es Übertragungsraten von bis 2 MBit/s. Angesprochen wird das Modul über den SPI-Bus.

Nun aber genug der Theorie, alle weiteren Details sind dem Datenblatt zu entnehmen. Dieses wird später noch wichtig werden.

Für das nRF24L01P gibt es fertige Libraries für Arduino und den Atmega. Wie ich in meinem letzten Artikel geschrieben habe, benutze ich wegen der einfacheren Bedienung gerne das Arduino-Paket. Daher werde ich im folgenden auch nur erklären, wie man die Module mit einem Arduino-Board bzw. einem "nackten" Atmega8 mit Arduino-Libraries zum Laufen bekommt.

Als einfaches Beispiel zum Einstieg wähle ich das Ping-Programm, dass die Arduino-Lib mitbringt. Der Arduino Uno sendet seine aktuelle "Systemzeit" an den Atmega, dieser sendet sie sofort wieder zurück. Aus der empfangenen Zeit und der aktuellen Zeit kann der Arduino dann die Laufzeit des Signals, also den Pin bestimmen.

Aufbau

Zuerst geht es an die Verkabelung. Die Funkmodule haben ingesamt 8 Anschlüsse: Vcc und GND für die Spannungsversorgung (WICHTIG: die Module verlangen eine Versorgung mit ~3V), MISO, MOSI und SCK für die SPI-Kommunikation, CSN (kündigt dem Modul einen Befehl an) sowie CE und IRQ. IRQ ist der Interrupt-Pin des Moduls und kann bei 3 verschiedenen Events active low werden: Daten wurden empfangen, Daten wurden gesendet, Senden fehlgeschlagen. Diese Interrupts lassen sich jedoch frei ein- und ausschalten.

Die genaue Pinbelegung hängt von der äußeren Beschaltung des Moduls ab, lässt sich jedoch mit ein bisschen googlen schnell heraus finden. Die meisten Module haben folgende Pinbelegung:
IRQ     8  7  MISO
MOSI  6  5  SCK
CSN    4  3  CE
VCC    2  1  GND

Diese Pins müssen nun entsprechend an das Arduino-Board bzw. den Atmega8 angeschlossen werden:
MISO an Pin 12 des Arduino (Pin 18 beim Atmega, Pin 50 beim Arduino Mega)
MOSI an Pin 11 (Pin 17, 51)
SCK an Pin 13 (Pin 19, 52)
IRQ an Pin 2 oder 3 (Pin 4 oder 5, Interrupts gibt es sehr viele beim Arduino Mega, daher gebe ich hier keinen speziellen Pin an)
Folgende Pins sind im Prinzip frei wählbar, die Standardbelegung der Lib ist allerdings
CSN an Pin 7 (Pin 13, 7)
CE an Pin 8 (Pin 14, 8)

Wie bereits erwähnt sind zwingend 3V zur Spannungsversorgung nötig! Die Signal-Pins vertragen jedoch 5V.

Jetzt sollte soweit alles verkabelt sein und wir können uns der Software widmen.

Software Arduino

Für das Beispiel habe ich wie schon gesagt zum Großteil des mitgelieferte Beispiel der Lib übernommen.

Die Library ist HIER erhältlich.

Folgender Code wird auf das Arduino-Board gespielt:

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

void setup(){
  Serial.begin(9600);
  
  //Falls CE- bzw. CSN-Pin anders belegt
  
  //Mirf.cePin = 7;
  //Mirf.csnPin = 8;
  
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  
  //Empfänder-Adresse einstellen   
  Mirf.setRADDR((byte *)"clie1");
  
  //Payload-Länge einstellen   
  Mirf.payload = sizeof(unsigned long);
  
  //auf 250kbit/s umstellen (0x26) (2Mbit => 0x0f)
  Mirf.configRegister(RF_SETUP, 0x26);
 
  //ggfs. Kanal ändern
  //Mirf.channel = 10;

  //Konfiguration in Modul übernehmen
  Mirf.config();
  
  Serial.println("Beginning ... "); 
}

void loop(){
  unsigned long time = millis();
  
  Mirf.setTADDR((byte *)"serv1");
  
  Mirf.send((byte *)&time);
  
  while(Mirf.isSending()){
  }
  Serial.println("Finished sending");
  delay(10);
  while(!Mirf.dataReady()){
    if ( ( millis() - time ) > 1000 ) {
      Serial.println("Timeout on response from server!");
      return;
    }
  }
  
  Mirf.getData((byte *) &time);
  
  Serial.print("Ping: ");
  Serial.println((millis() - time));
  
  delay(1000);
}  
 
In der setup()-Funktion wird zunächst das Funkmodul mit dem init()-Befehl initialisiert. Dann wird die Adresse eingestellt, unter der das Modul angesprochen werden soll. Die Payload-Länge gibt an, wie viele Daten-Bytes bei jeder Übertragung gesendet werden sollen. Bis zu 32 Bytes sind hier möglich, jedoch sollte man nie mehr wählen als nötig. Um die Reichweite der Module zu erhöhen, habe ich mich dazu entschieden, die Übertragungsrate von 2 Mbit/s auf 250 kbit/s zu ändern. Hierzu ist ein Zugriff auf die Register des Moduls nötig. Dank der Lib geht dies jedoch problemlos mit einer Zeile Code. Der Befehl configRegister(RF_SETUP, 0x26) schreibt den Werte 0x26 in das Register mit dem Namen RF_SETUP und stellt somit die Übertragungsrate ein. Die Namen der Register und ihre genaue Funktion ist dem Datenblatt ab Seite 57 zu entnehmen.
Zum Abschluss wird die Konfiguration mit dem config()-Befehl in das Modul übernommen.

Das Hauptprogramm ist eigentlich recht simpel und selbsterklärend. Mit dem Befehl setTADDR wird eingestellt, an welche Adresse gesendet werden soll. Mit dem send()-Befehl werden dann die gewünschten Daten (hier die Systemzeit) gesendet. Die folgende while-Schleife sorgt dafür, dass der Arduino solange pausiert, bis die Übertragung abgeschlossen ist. Mit der nächsten while-Schleife und der Bedingungen !Mirf.dataReady wartet der µC so lange, bis das Funkmodul Daten (also eine Antwort) empfangen hat.  Der Befehl getData() liest dann die empfangenen Daten aus dem Pufferspeicher des Moduls.

Software Atmega8

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

void setup(){

  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  
  //Empfänder-Adresse einstellen     
  Mirf.setRADDR((byte *)"serv1");
  
  //Payload-Länge einstellen   
  Mirf.payload = sizeof(unsigned long);
  
  //auf 250kbit/s umstellen (0x26) (2Mbit => 0x0f)
  Mirf.configRegister(RF_SETUP, 0x26);
  
  //ggfs. Kanal ändern
  //Mirf.channel = 10;

  //Konfiguration in Modul übernehmen
  Mirf.config();
  
}

void loop(){

  //Pufferspeicher für die Daten
  byte data[Mirf.payload];
   
  if(!Mirf.isSending() && Mirf.dataReady()){
         
    Mirf.getData(data);
         
    Mirf.setTADDR((byte *)"clie1");
   
    Mirf.send(data);
    
  }
}

Die setup()-Funktion des Atmega-Programms ist im Prinzip identisch mit der des Arduino-Programms, ich werde den Code daher nicht näher erläutern.
Das Hauptprogramm ist auch ziemlich simpel. Das Programm läuft die ganze Zeit "im Kreis" und wartet, bis das Funkmodul Daten zur Vefügung stellt (Mirf.dataReady()). Diese werden dann zwischengespeichert, es wird die Adresse des Empfängers gesetzt und die Daten werden sofort wieder zurück gesendet. Um Überschneidungen beim Senden zu verhindern, ist zusätzlich noch die !Mirf.isSending()-Bedigungen in der if-Abfrage eingefügt worden.

Der Code auf der Empfänger-Seite (also auf dem Atmega) ist so natürlich nicht besonders effizient, da der Controller nichts anderes macht als zu warten, ob den nun endlich Daten empfangen wurden. Viel praktischer und schöner lässt sich das mit den erwähnten Interrupts lösen. Wie genau das funktioniert, werde ich in meinem nächsten Beitrag erklären.

4 Kommentare:

  1. die SPI.h Datei ist nicht in dem Paket mit drin, welche wurde für den ATMega8 genutzt?

    AntwortenLöschen
  2. Der komplette Code wurde mit der Arduino IDE erzeugt. Die Datei "SPI.h" gehört zur Arduino IDE und ist da schon integriert.

    AntwortenLöschen
  3. Hallo, toller Bericht! Zu dem Funkmodul selber, wie sieht es mit der Reichweite im Freien aus?
    Ich müsste im Freien im Abstand von ca. 50m regelmäßig Schalterzustände übertragen. Ist das
    Modul dafür geeignet?

    AntwortenLöschen
    Antworten
    1. 50m sind ganz schön weit, aber Freien (ohne Hindernisse dazwischen) müsste das gerade noch machbar sein. Die Sendeleistung der Module ist in drei Stufen einstellbar, hier müsste natürlich die höchste Leistung gewählt werden. Außerdem lässt sich die Reichweite durch Reduzierung der Datenrate und der Payload-Länge erhöhen.

      Löschen