Joystick Shield met 315 MHz afstandsbediening

Het Joystick Shield uit een vorige knutselaerij wilde ik ook proberen met een ander type zender en ontvanger. In diverse webshops kwam ik een erg goedkoop setje printjes hiervoor tegen. Zó goedkoop dat ik er nieuwsgierig van werd. Voor zo'n 50 cent per printje werden ze per dozijn verkocht voor minder dan €6,- inclusief verzending vanuit China.

De soldeerkwaliteit was inderdaad niet best. Geen hoog gespannen verwachtingen dus. Maar toen ik er wat mee doorspeelde bleek het toch geen miskoop en deed ik zelfs een grappige ontdekking!

Video werd geblokkeerd
Instellingen

Een 315MHz zender voor 50 cent

Op diverse plekken is de FS100A transmitter module voor heel weinig geld te koop. Ik vond deze ook onder de naam "XY-FST", en in verschillende varianten. Het gebruikte kristal lijkt het enige verschil te zijn. De variant met een 433MHz kristal lijkt zelfs het meest gangbaar. Het is vooral toeval dat ik uiteindelijk een setje van 6 van deze moduultjes aanschafte dat op 315MHz werkt.

De zender is voor die prijs ook niet echt een wonder der techniek. Het zeer kleine printje blijkt ook nog vrij slordig te zijn gesoldeerd. Sowieso moet je er zelf een antenne aan solderen. Ook vallen een paar ongebruikte gaatjes op, waar onderdelen lijken te missen! Mijn vertrouwen nam af toen ik dit min of meer bevestigd zag op diverse fora. Een miskoop leek in de lucht te hangen.

FS1000A 315MHz transmitter
FS1000A 315MHz moduleJoystick Shield transmitter 315MHzJoystick Shield transmitter 315MHz

Geen atoomtechnologie...

Het gevonden schakelschema van deze zender is erg simpel, maar de schijnbaar missende spoel lijkt hierin wel degelijk essentieel. Het zendertje bestaat uit een eenvoudige, rond een kristal opgebouwde, oscillator die in het ritme van de te verzenden data wordt in en uitgeschakeld. Dit wordt 'Amplitude Shift Keying' (ASK) genoemd. Geen foutcorrectie of kanaal-instellingen mogelijk, dus meerdere van dit soort printjes op één locatie gebruiken wordt denk ik lastig. Maar ja, wat wil je ook voor zo'n prijs?

Uit voorzorg maar meteen zelf de missende spoel (2,5 wikkelingen) gewikkeld en gemonteerd en alle solderingen nagelopen. Volgende stap was de antenne. Allereerst moest hiervoor een dicht vertind en te klein gaatje voorzichtig worden opgeboord. Voor de zenders van 433MHz (met golflengte λ van 69 cm) is de lengte van een mogelijke antennedraad van 1/4λ=17,3 cm. Maar aangezien mijn kristal duidelijk '315' vermeldde, heb ik voor zowel de zender als de ontvanger een antenne gemaakt voor 315MHz. Een simpele draad van een kwart van de golflengte (1/4λ=23,8 cm) volstaat, maar om hem korter te maken koos ik voor een spoelwikkeling midden in. Het leek me zinvol de boel nog wat te verstevigen met een krimpkousje en de soldering nog wat te ontlasten door de antenne met een drupje lijmpistool-lijm aan het printje te plakken.

De pennen voor de stroomvoorziening heb ik voorzien van aparte draadjes met draadstekkertjes. De data-input van de module kan zó in uitgang 9 van het joystick shield gestoken worden. Dit is in dit geval op de edge-connector van het mogelijke Nokia display, of de nRF24L01 transceiver module maar die waren in dit geval toch niet in gebruik. Mochten de SPI poorten op de joystick shield wél gebruikt zijn dan blijven alleen A2 en A3 over als mogelijke stuur-uitgang.

// Arnoud, last day of 2020 and further :-)
// Uses RadioHead lib for XY-FST/XY-MK-5V combi: http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.113.zip
//
#include "RH_ASK.h"
#include "SPI.h"

#define DEBUG

// XY-FST defines...
#define XYFST_PIN  9

#define button_A  2 // Button Blue - A
#define button_B  3 // Button Yellow - B
#define button_C  4 // Button Blue - C 
#define button_D  5 // Button Yellow - D 
#define button_E  7 // SMD button E on pcb
#define button_F  6 // SMD button F on pcb
#define button_joystick 8 // Button in joystick
#define x_axis A0
#define y_axis A1
int buttons[]={ button_A, button_B, button_C, button_D, button_E, button_F, button_joystick };
int joystick[9]; // Array holding state of buttons and joystick X- and Y-reading

// RH_ASK driver(speed, receive-pin, transmit-pin, push-to-talk:not needed)
RH_ASK driver(2000, NULL, XYFST_PIN, NULL); // ESP8266 or ESP32: do not use pin 11

void setup(){
#ifdef DEBUG
  Serial.begin(115200);
#endif 

  for (int i=0; i <7 ; i++) {
    pinMode(buttons[i], INPUT_PULLUP);
    digitalWrite(buttons[i], HIGH);  
  }

  // Setup XY-FST...
  if (!driver.init())
    Serial.println("Transmitter XY-FST init failed");
  else
    Serial.println("Transmitter XY-FST success");
}

void loop() {
  // Read digital buttons...
  for (int i=0; i <7 ; i++)
    joystick[i] = digitalRead(buttons[i]);

  // Read joystick values...
  joystick[7] = analogRead(x_axis);
  joystick[8] = analogRead(y_axis);

  // Write out values array...
  driver.send((uint8_t *)joystick, sizeof(joystick));
  driver.waitPacketSent();
  delay(200);

#ifdef DEBUG
  // Log...
  for (int i=0; i <9 ; i++) {
    Serial.print(joystick[i]);
    if (i<8)
      Serial.print(", ");
  }
  Serial.print("\n");
#endif
}

De ontvanger module

De bijgeleverde ontvanger lijkt precies dezelfde te zijn voor zowel de 315MHz, als de 433MHz variant van de zender. De instelling zal wel gemaakt zijn met de spoel-trimmer op het printje. Verder niet aangezeten, alleen een antenne aangesloten. Ik heb de ontvanger hier op ingang D10 gezet, maar deze kan op elke vrije digitale input worden aangesloten.

Hieronder het aansluitschema op een motor shield. Ik heb de testopstelling op het prototype boardje hierop van de vorige knutselaerij met de LED's ter test van de afstandsbediening maar even hergebruikt. Direct aansluiten op een Arduino is vanzelfsprekend ook mogelijk.

De data-communicatie tussen de modules is erg rudimentair. Er worden simpelweg 8-bits bytes geschreven op het joystick shield die de 315MHz draaggolf moduleren. Deze worden opgepikt door de ontvanger en 'vertaald' naar dezelfde 8-bits bytes als digitale niveaus op ingang D10 van de ontvanger-Arduino. Aangezien ik integer-waarden tussen 0 en 1023 voor gelezen joystick posities wilde overzenden, heb ik in de Sketches voor de zender en ontvanger gewoon telkens twee bytes samengenomen tot één 16 bit-word. Voor de indruk-status van de knoppen (0 of 1) is dat nogal een overkill, maar het vereenvoudigde het principe. Wie het dataverkeer wil optimaliseren zou gewoon alle knoppen samen kunnen nemen in één byte bij het verzenden.

XY-MK-5V receiver
Motorshield with 315MHz receiver
// Arnoud, last 12-01-2021
// Uses RadioHead lib for XY-FST/XY-MK-5V combi: http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.113.zip
//
// Connect motors (Adafruit Motor Shield):
//    Left Motor: MotorL (M1)
//    Right Motor: MotorR (M2)

#include "RH_ASK.h"
#include "SPI.h"
#include "Adafruit_MotorShield.h"

#define DEBUG // Outcomment for stealth/live operation...

// XY-MK-5V defines...
#define XYMK5V_PIN  10

// Various defines...
#define LED_A 8  // Button A
#define LED_B 7  // Button B
#define LED_C 6  // Button C
#define LED_D 5  // Button D
#define LED_E 3  // Button E
#define LED_F 4  // Button F
#define LED_K 2  // Joystick button
#define SEND_BUTTON A1 // Switch to test talking back to joystick shield...
#define MaxSpeed 255   // Max motor speed
#define safeRange 50   // Safe range to ignore round midpoint of joystick readings...
#define Baudrate 115200

int leds[] = { LED_A, LED_B, LED_C, LED_D, LED_E, LED_F, LED_K };
int sendbutton;
int joystick[9]; // Communications array holding state of buttons and joystick X- and Y-reading 
uint16_t value;  // Momentary joystick value...
int dirMotorL = FORWARD; 
int dirMotorR = FORWARD;
int motorSpeedL = 0;
int motorSpeedR = 0;
int speedDiff = 0;
int i; // Counter...

// RH_ASK driver(speed, receive-pin, transmit-pin, push-to-talk:not needed)
RH_ASK driver(2000, XYMK5V_PIN, NULL, NULL); // ESP8266 or ESP32: do not use pin 11
Adafruit_MotorShield MShield = Adafruit_MotorShield(0x60);
Adafruit_DCMotor *MotorL = MShield.getMotor(1); // Left motor
Adafruit_DCMotor *MotorR = MShield.getMotor(2); // Right motor

bool buggyBusy = false;

void buggycontrol() { // Control the Buggy
  buggyBusy = true; 
  
  // Show feedback with LEDs and log to serial during debug...
  for (int i=0; i <9 ; i++) {
    if (i<7) {
     if (joystick[i]==1)
       digitalWrite(leds[i], LOW); 
     else
       digitalWrite(leds[i], HIGH); 
    }
#ifdef DEBUG
    Serial.print(joystick[i]);
    if (i<8)
      Serial.print(", ");
#endif
  }
#ifdef DEBUG
  Serial.print("\n");
#endif

  // Determine (signed/directional) speed based on Y-axis joystick value reading, 
  // ignore range around midpoint (theoretically 512) to prevent unwanted jitter...
  motorSpeedL = motorSpeedR = 0;
  if (joystick[8] < 512-safeRange) {
    motorSpeedL = map(joystick[8], 0, 512, -MaxSpeed, 0);
    motorSpeedR = motorSpeedL;
  } else if (joystick[8] > 512+safeRange) {
      motorSpeedL = map(joystick[8], 512, 1023, 0, MaxSpeed);
      motorSpeedR = motorSpeedL;
  }

  // Determine relative (signed/directional) curve-/turn-speed difference based on X-axis reading,
  // ignore range around midpoint (theoretically 512) to prevent unwanted jitter...
  speedDiff = 0;
  if (joystick[7] < 512-safeRange) {
    speedDiff = map(joystick[7], 0, 512, -255, 0);
  } else if (joystick[7] > 512+safeRange) {
      speedDiff = map(joystick[7], 512, 1023, 0, 255);
  }
  motorSpeedL = (motorSpeedL+speedDiff);
  motorSpeedR = (motorSpeedR-speedDiff);

  // Determine direction per motor...
  dirMotorL = FORWARD;
  dirMotorR = FORWARD;
  if (motorSpeedL<0) dirMotorL = BACKWARD; // Reverse...
  if (motorSpeedR<0) dirMotorR = BACKWARD; // Reverse...
  
  // Crop and absolutize motorspeeds...
  motorSpeedL = abs(motorSpeedL);
  motorSpeedR = abs(motorSpeedR);
  if (motorSpeedL>MaxSpeed) motorSpeedL = MaxSpeed;
  if (motorSpeedR>MaxSpeed) motorSpeedR = MaxSpeed;

#ifdef DEBUG
  Serial.print("\nmotorSpeedL = ");
  Serial.print(motorSpeedL);
  Serial.print(", motorSpeedR = ");
  Serial.print(motorSpeedR);
  Serial.print(", speedDiff = ");
  Serial.print(speedDiff);
  Serial.print(", dirMotorL=");
  Serial.print(dirMotorL);
  Serial.print(", dirMotorR=");
  Serial.print(dirMotorR);
  Serial.print("\n");
#endif

  MotorL->run(dirMotorL);
  MotorR->run(dirMotorR);
  MotorL->setSpeed(motorSpeedL);
  MotorR->setSpeed(motorSpeedR);

  buggyBusy = false;
}

void setup() {
#ifdef DEBUG
  Serial.begin(Baudrate); // Start serial monitor...
#endif

  // Setup LEDs...
  pinMode(LED_A, OUTPUT);
  pinMode(LED_B, OUTPUT);
  pinMode(LED_C, OUTPUT);
  pinMode(LED_D, OUTPUT);
  pinMode(LED_E, OUTPUT);
  pinMode(LED_F, OUTPUT);
  pinMode(LED_K, OUTPUT);
  pinMode(SEND_BUTTON, INPUT_PULLUP);

  MShield.begin(); // Motor Shield initialize...
  Wire.setClock(400000); // Set I²C-Frequenz at 400 kHz
    
  // Initialize motors...
  MotorL->setSpeed(motorSpeedL);
  MotorR->setSpeed(motorSpeedR);

  // Blink that we're alive: all LEDs on...
  digitalWrite(LED_A, HIGH);
  digitalWrite(LED_B, HIGH);
  digitalWrite(LED_C, HIGH);
  digitalWrite(LED_D, HIGH);
  digitalWrite(LED_E, HIGH);
  digitalWrite(LED_F, HIGH);
  digitalWrite(LED_K, HIGH);

  if (!driver.init())
    Serial.println("XY-MK-5V receiver init failed");
  else
    Serial.println("XY-MK-5V receiver init success");
  delay(2000); // Signal power-up...

  // Blink: all LEDs off...
  digitalWrite(LED_A, LOW);
  digitalWrite(LED_B, LOW);
  digitalWrite(LED_C, LOW);
  digitalWrite(LED_D, LOW);
  digitalWrite(LED_E, LOW);
  digitalWrite(LED_F, LOW);  
  digitalWrite(LED_K, LOW);
}
 
void loop() {
  uint8_t buf[RH_ASK_MAX_MESSAGE_LEN];
  uint8_t buflen = sizeof(buf);

  if (driver.recv(buf, &buflen)) { // Non-blocking
    if (buflen==18) { // Only if 9 words where received...
      for (i=0; i<9; i++) // Consolidate received values in joystick array...
        joystick[i] = buf[(2*i)+1] << 8 | buf[(2*i)];
    }
  }

  if (!buggyBusy)
    buggycontrol(); // Control the Buggy...
        
#ifdef DEBUG
  // Log...
  for (int i=0; i <9 ; i++) {
    Serial.print(joystick[i]);
    if (i<8)
      Serial.print(", ");
  }
  Serial.print("\n");
#endif
}

Er is geen ontbrekende spoel!

Het voorbereide setje werkte eigenlijk direct zonder problemen. Dit maakte natuurlijk nieuwsgierig naar de overige vijf printjes. Een verrassing was dat deze het ook gewoon deden! Zónder de bewuste, vermeend missende, spoel! Dit was na alle ervaringen, aannames en beschuldigingen van collega-kopers op het internet toch wel een verrassing!

De oplossing blijkt eenvoudig. De spoel is er gewoon en de oscillator-transistor wordt gewoon 'gevoed'. Het is tegenwoordig een smd spoel (of draadbrug door middel van 0Ω weerstand) op de onderkant van de print. De open gaatjes zijn dus (in mijn geval) compleet onschuldig. In het ergste geval heb ik nu dus een printje in gebruik met twee spoelen parallel, maar erg lijkt dat niet omdat deze spoel zo te zien alleen ter HF-ontkoppeling van de voedingsspanning is.

De les is dus weer: je niet druk maken en éérst gewoon even testen. Alle zender-moduultjes deden het, dat is dan misschien weer het voordeel van zo'n enorm eenvoudige opbouw. Maar een antenne er aan zetten lijkt wel zinvol. De geteste modules zonder antennedraad hadden een belabberd bereik. Met een antennedraad werkt het prima, en ik kan me voorstellen dat het zendvermogen en de daarmee  haalbare reikwijdte op 12V helemaal uitstekend zullen zijn.

De bestuurbare Buggy

Dit was de proof-of-concept van dit type zendertje. Daarom kwam de test-Buggy met LEDjes weer goed van pas. De besturing gaat, zoals uit het filmpje wel duidelijk wordt, op zich prima. Al lijkt de responssnelheid wat achter te blijven bij de 2.4GHz nRF24L01 uit de vorige test. En daarmee was bovendien een volledige full-duplex verbinding mogelijk.

Voor het real-time besturen van rijdende of vliegende modellen zou ik dit 315MHz zendertje misschien toch niet zo snel gebruiken, maar wie een zeer goedkope oplossing zoekt voor het draadloos doorsturen van data (bv. van een weerstation of andere sensor- of detectors-informatie) kan het zeker een bruikbaar dingetje zijn. Let wel: eenvoud troef, de tweede op dezelfde locatie zal dan een 433MHz modelletje moeten zijn om interferentie te voorkomen. Waarom ze dan toch per half dozijn worden verkocht wordt daarom ook niet helemaal duidelijk. Misschien wil iemand een 315Mhz setje ruilen voor een 433MHz setje? 😄

Buggy with 315MHz receiver