Fernsteuerung mit nRF24L01 (2,4 GHz) - AZ-Delivery

Nachdem ich in den vorausgegangenen Beiträgen die Fernsteuerung mit dem 433MHz Transceiver HC-12, der leider nicht mehr im Sortiment ist, realisiert hatte, möchte ich diesen durch den nRF24L01-Transceiver ersetzen. Auch dieses Modul kann programmgesteuert als Sender und Empfänger verwendet werden. Aber es wird grundsätzlich anders angesteuert, nämlich über das Serial Peripheral Interface, kurz SPI.

Benötigte Hardware

Anzahl Bauteil
1 Mikrocontroller Board mit ATmega328P, ATmega16U2, kompatibel mit Arduino UNO R3
oder Nano V3.0 mit Atmega328 CH340! 100% Arduino kompatibel mit Nano V3
oder Nano V3.0 mit FT232RL Chip und ATmega328! 100% Arduino Nano V3 kompatibel
2 NRF24L01 mit 2,4 GHz Wireless Module für Arduino, ESP8266, Raspberry Pi
ggf. Adapter für NRF24L01 (siehe Anmerkung im Text)
1 PS2 Joystick Shield Game Pad Keypad V2.0 für Arduino
oder KY-023 Joystick Modul für Arduino UNO R3 u.a. MCUs
Breadboard, Jumper Kabel, Kleinmaterial
ggf. Gehäuse aus dem 3D-Drucker


Hier zunächst ein Blick auf den nRF24L01 mit seinem optionalen Adapter.


Auch wenn ich selbst den Adapter für den nRF24L01-Transceiver nicht verwende, stelle ich ihn hier vor, denn erstens kann man sehr schön die Pin-Belegungen erkennen.

Und zweitens muss man folgenden Hinweis geben: Der Transceiver verträgt nur 3,3V Spannung. Für höhere Spannungen ist eben dieser Adapter zu empfehlen, bei dem man links im Bild den Spannungsregler erkennt.


Aus den Bezeichnungen der Pins erkennt man schnell, dass er nicht an einer Seriellen Schnittstelle (UART), sondern am Serial Peripheral Interface (SPI) angeschlossen wird.

Hier die typische Pinbelegung:

SCE Chip Enable (RX TX Mode Select) Digital D9
CSN Chip Select (Active Low) Digital D10
SCK Serial Clock Digital D13
MOSI Master Out Slave In Digital D11
MISO Master In Slave Out Digital D12
VCC 3,3V !!!
GND GND
IRQ not connected


Diese Gedanken braucht man sich nicht zu machen, wenn man den nRF24L01 in das Joystick-Shield steckt. Merken müssen wir uns allerdings SCE=9 und CSN=10, denn diese Pins können und werden wir ggf. selbst definieren, während SCK, MOSI und MISO fest vergeben sind.

Für unseren Sketch benötigen wir eine neue Programm-Bibliothek, die über den Bibliotheksverwalter hinzugefügt wird. Dort geben wir als Suchbegiff RF24 ein.


Wenn Sie ein wenig herunter scrollen, finden Sie die Bibliothek von TMRh20, die ich verwende.

Wie (fast) immer werden mit der Bibliothek auch Beispiel-Programme installiert. Also unter Datei / Beispiele herunter scrollen bis RF24. Ein Sketch mit dem Namen GettingStarted drängt sich geradezu für den Erstbenutzer auf.

 

Wie schön, dass am Anfang etwas zur Verwendung steht:


/**
 * A simple example of sending data from 1 nRF24L01 transceiver to another.
 *
 * This example was written to be used on 2 devices acting as "nodes".
 * Use the Serial Monitor to change each node's behavior.
 */

Also: Wir brauchen zwei MCUs jeweils mit dem Transceiver. Auf beide MCUs wird der gleiche Sketch hochgeladen; welcher Micro Controller Sender und welcher Empfänger ist, wird jeweils im Serial Monitor festgelegt.

Bitte denken Sie daran, dass Sie an einem PC für zwei unterschiedliche virtuelle COM Ports die Arduino IDE zweimal starten (instanziieren) müssen.

Und wir müssen in Zeile 18 eine kleine Änderung vornehmen. Dort werden die Pins für CE und CSN festgelegt. Vorgegeben ist:

RF24 radio(7, 8);  // using pin 7 for the CE pin, and pin 8 for the CSN pin

Wie oben bereits angedeutet, ändern wir die Pinnummern in CE/SCE = 9 und CSN=10, also

RF24 radio(9, 10);  // using pin 9 for the CE pin, and pin 10 for the CSN pin

Kompilieren, Hochladen und Ausprobieren.

Das Schöne an diesem Programm ist, dass man eine Fehlermeldung bekommt, wenn der Transceiver nicht reagiert, z.B. weil er falsch angeschlossen oder die o.g. Änderung im Sketch nicht vorgenommen wurde.


Bei anderen Programmen, wo man diese Rückmeldung nicht bekommt, dauert die Fehlersuche häufig länger.

Wenn alles richtig ist, wird gefragt, um welches Gerät (Node) es sich handelt. Also im Serial Monitor der einen IDE 0 und in der anderen IDE 1 eingeben.


Welcher Node sendet, wird durch Eingabe des Buchstaben T oder t (für Transmit) in der obersten Zeile des Serial Monitor entschieden.


Ich rate Ihnen, diese Versuche nachzumachen, um Handlungssicherheit im Umgang mit den Transceivern zu erlangen. Wenn etwas bei meinen Versuchen nicht hingehauen hat, habe ich kurzerhand diesen Sketch hochgeladen, um zu sehen, ob ich die Meldung „radio hardware not responding“ bekomme. Dann war der Fehler schnell gefunden.

Jetzt gehen wir an die Schaltung und Programmierung unserer Fernsteuerungen und des modifizierten Robot Cars, zunächst mit zwei Nanos.

Die Pinbelegung ist aufgrund der SPI-Schnittstelle zu einem großen Teil vorgegeben. Bei Verwendung des Nanos liegt SCK bei Pin D13, MISO bei Pin 12 und MOSI bei Pin 11. Die weiteren Pins sind wählbar. Ich entscheide mich hier für CE an Pin D2 und CSN an Pin D3, um keine PWM Pins zu belegen, die in dem zweiten Sketch für die Motoren verwendet werden. Für den Joystick benutze ich hier die analogen Eingänge A6 und A7 sowie D4 für den Button.

Bild und Sketch für die Fernsteuerung:

 

/*
Joystick als Motor Controller, Stillstand = 505
je 5 Stufen vor/zurück, je 5 Stufen rechts/links
*
* Library: TMRh20/RF24, https://github.com/tmrh20/RF24/
*/
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(2, 3); // CE, CSN
const byte address[6] = "CodeR";

int x = 5; // x-Achse = links/rechts
int y = 5; // y-Achse = vor/zurück
int code = 505;
int joybutton = 4; // Joystick button
float faktor = 1.0; // für Anpassung bei unterschiedlicher Spannung oder ADC

void setup() {
  pinMode(joybutton,INPUT_PULLUP);
  Serial.begin(115200);
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
}

void sendcode() {
  radio.write(&code, sizeof(code));
  delay(100); // little delay for next button press
}

void loop() {
  float A6 = faktor * analogRead (6);
  float A7 = faktor * analogRead (7);
  bool button = digitalRead(joybutton);

  Serial.print("x-Achse: ");
  Serial.print(A6);
  Serial.print("y-Achse: ");
  Serial.print(A7);
  Serial.print(" Button pressed ");
  Serial.print(button);

  x = int(A6/100);
  y = int(A7/100);
  code = 100*y + x;
  Serial.print(" Code = ");
  Serial.println(code);
  sendcode();
}
 

Und hier Bild und Sketch für das Robot Car mit zwei Motoren:

 

 

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(2, 3); // CE, CSN
const byte address[6] = "CodeR";

// define variables for speed control
int x = 0;
int y = 0;
int left = 0;
int right = 0;
int code = 505;
int speedL = 0;
float factor = 0.66; // correction of speedLevel = maxValue/255
// define motor pins
int m11 = 5; //left motor(s) pwr1
int m12 = 6; //left motor(s) pwr2
int m1e = 7; //left motor(s) enable

int m21 = 9; //right motor(s) pwr1
int m22 = 10; //right motor(s) pwr2
int m2e = 8; //right motor(s) enable


void setup() {
  Serial.begin(9600); // set up Serial Monitor at 9600 bps
  Serial.println("Motor test!");

  // initialize radio nRF24L01
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);
  radio.startListening();

  // initialize digital pins as output for motors.
  pinMode(m11, OUTPUT);
  pinMode(m12, OUTPUT);
  pinMode(m1e, OUTPUT);
  digitalWrite(m1e, LOW);
  pinMode(m21, OUTPUT);
  pinMode(m22, OUTPUT);
  pinMode(m2e, OUTPUT);
  digitalWrite(m2e, LOW);

} // end of setup

void loop() {

  if (radio.available()) {
    code = 0;
    radio.read(&code, sizeof(code));
    Serial.println(code);
    motor();
  }
  delay(20); //little delay for better serial communication

} // end of loop

void motor(){
  int speedLevel[11]={-255,-210,-165,-120,-75,0,75,120,165,210,255};
  y = int(code /100);
  x = code - 100*y;
  speedL = speedLevel[y];
  Serial.print("code = ");
  Serial.print(code);
  Serial.print(" y = ");
  Serial.print(y);
  Serial.print(" x = ");
  Serial.print(x);
  Serial.print(" speedL = ");
  Serial.println(speedL);

  //correction of speedLevel for cornering
  if (x==0){
    right = speedL+60;
    left = speedL-60;
  }
  else if (x==1){
    right = speedL+48;
    left = speedL-48;
  }
  else if (x==2){
    right = speedL+36;
    left = speedL-36;
  }
  else if (x==3) {
    right = speedL+24;
    left = speedL-24;
  }
  else if (x==4) {
    right = speedL+12;
    left = speedL-12;
  }
  else if (x==6) {
    right = speedL -12;
    left = speedL+12;
  }
  else if (x==7) {
    right = speedL-24;
    left = speedL+24;
  }
  else if (x==8) {
    right = speedL-36;
    left = speedL+36;
  }
  else if (x==9) {
    right = speedL-48;
    left = speedL+48;
  }
  else if (x==10) {
    right = speedL-60;
    left = speedL+60;
  }
  else {
    right = speedL;
    left = speedL;
  }

  //speedLevel for "left" and "right"
  Serial.print("left = ");
  Serial.print(left);
  Serial.print(" right = ");
  Serial.println(right);

  // stop
  if (left < 63 & left > -63) {
    digitalWrite(m1e, LOW);
  }
  if (right < 63 & right > -63) {
    digitalWrite(m2e, LOW);
  }
  // forward
  if (left>=63) {
    if (left>255) left=255;
    int corrl=int(left * factor);
    Serial.print("corrl = ");
    Serial.println(corrl);
    analogWrite(m11,corrl);
    analogWrite(m12, 0);
    digitalWrite(m1e, HIGH);
  }
  if (right>=63) {
    if (right>255) right=255;
    int corrr=int(abs(right) * factor);
    Serial.print("corrr = ");
    Serial.println(corrr);
    analogWrite(m21, corrr);
    analogWrite(m22, 0);
    digitalWrite(m2e, HIGH);
  }
  // backward
  if (left<= -63) {
    if (left<-255) left=-255;
    int corrl=int(abs(left) * factor);
    Serial.print("corrl = ");
    Serial.println(corrl);
    analogWrite(m11,corrl);
    analogWrite(m11, 0);
    analogWrite(m12, int(abs(left) * factor));
    digitalWrite(m1e, HIGH);
  }
  if (right<= -63) {
    if (right<-255) right=-255;
    int corrr=int(abs(right) * factor);
    Serial.print("corrr = ");
    Serial.println(corrr);
    analogWrite(m21, 0);
    analogWrite(m22, corrr);
    digitalWrite(m2e, HIGH);
  }
  Serial.print("Motorsteuerung okay");
} // end of motor
 

Wie auf dem Bild mit den fliegenden Leitungen zu sehen, ist der nRF23L01 - und übrigens auch der Adapter – nicht für das Aufstecken auf das Breadboard geeignet.

Deshalb hier noch einmal ein Bild vom dem Joystick-Shield, bei dem ein Steckplatz für den nRF24L01 vorgesehen ist.


Zur Erinnerung: Hier war die Pinbelegung CE=Pin 9, CSN=Pin10, x-Achse=A0, y-Achse=A1 und JoyStick-Button=D8. Mit diesen Änderungen funktioniert der Sketch Motor Controller.

Insgesamt ist der nRF24L01 Transceiver eine preiswerte und gute Lösung für die Funkfernsteuerung unserer kleinen Robot Cars.

In den nächsten Blog-Beiträgen werden wir die Steuerung mit Sensoren erweitern, um einen Fahrtregler zu bekommen und schließlich auch autonom fahren zu können.

Für arduino

3 comments

Wolfgang Menzel

Wolfgang Menzel

Hallo,
bezüglich NRF24L01 habe ich ein riesen Problem, das mich schon einige Stunden gekostet hat.
Ich verwende einen ESP32 DevKit mit I2C-Display und NRF24L01 als Empfänger zur Anzeigen von Daten. Allerdings ist das Display etwas klein. Ich habe noch ein 4 inch RPI TFT LCD mit Touch. Dieses funktionier am ESP einwandfrei. Allerdings finde ich keine Möglichkeit, das 4 inch Display und den NRF-Empfanger gemeinsam zu betreiben.
Ich verwende folgende includes:
#include
#include
#include

Pinbelegung für:
RF:
SCK:18or MISO:19br MOSI:23gn CE:4sw CNS:2ge +5V:rt GND:bl
TFT:
SCK:18gn MISO:19wß MOSI:23sw CS:15or DC:5ge RST:ENvi TOUCH:26gr +5V:rt GND:bl

(die Buchstaben hinter den Pins sind die Kabelfarben, hier keine relevanz)

Eigentlich sollte es doch so sein, daß der SPI-Bus mehrfach verwendet werden kann, nur CS muss sich unterscheiden. Wo kann also das Problem liegen?

Kann mit jemand helfen?

Mit freundlichen Grüßen
Wolfgang

Walter

Walter

Mit Arduino UNO konnte ich den nRF24L01 nicht zum laufen bringen, zig Internet Beiträge zum trotz.

Da entgegen klappte es mit Arduino NANO und sogar mit ESP32 Dev Kit auf Anhieb.

Rudi Brand

Rudi Brand

Hi, klingt gut! Wäre das eine Möglichkeit, einen Dashbutton-Ersatz zu bauen? Senden direkt auf Tastendruck in einem Batteriebetriebenen Gehäuse und der Empfänger am Netzteil sendet dann per MQTT ins private Netz? Aktuell habe ich einige Buttons mit ESP8266 gebaut, die im Deepsleep sind und nach dem Drücken 3-5s brauchen, bis sie im WLAN sind und mit die Batteriespannung per MQTT liefern..

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery