FrSky RC transmitter + SSC-32u is this possible?

I am building a quadruped for Mech Warfare events and competitions. I’m trying to figure out if I can hook up a radio control transmitter to the ssc-32u. I would like to avoid using Bluetooth if possible and I am very comfortable using my FRSky transmitter which I fight combat robots, fly drones and airplanes with. I was hoping to avoid using any extra control boards like Arduino because I do not plan to use any sensors or anything like that just Servo control and I want to keep the weight as low as possible. Thank you ahead of time for any help.

I’m trying to figure out if I can hook up a radio control transmitter to the ssc-32u.

Nope - the SSC-32U cannot accept RC PWM input (only serial commands). Similarly, the question would be if your RC receiver has (for example) 8 channels, how does that get mapped to the 32 channels of the SSC-32U? Not intuitive to know what each servo would do.

1 Like

I see that you can send ppm from a rc receiver to the BotBoarduino and you can also connect the BotBoarduino to the SSC-32U would this work?


1 Like

The receiver in the first image is the Lynxmotion PS2 receiver which is connected to the Lynxmotion BotBoarduino (programmed in Arduino). The second is an RC receiver also connected to the BotBoarduino (as opposed to the SSC-32U). Indeed as you might suspect, an Arduino microcontroller is very versatile and can be connected to wireless devices, but the SSC-32U is a dedicated RC receiver.

Most Lynxmotion SES V1 legged robots use the following setup:

PS2 Transmitter → PS2 Receiver → BotBoarduino → SSC-32U → RC servos.

2 Likes

Ok i ditched attempts at using the rc transmitter and going to use the method you recommend.

Now i cant quite figure out what and how to download software to the BotBoarduino to get it to work.

Take a look at the BotBoarduino manual. It’s important that you understand the jumper, power and PS2 connections before powering the board.

The RC approach would be significantly easier to set up than the programming + PS2 method, but doesn’t provide inverse kinematics.

You need to download the Arduino IDE:

As well as the PS2 Library (and install it):

https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries/

The library it was based on:

However the pinout is defined differently in this library than the one above.

1 Like

i get this error, do i copy and paste the entire library ?

 C:\Users\Anthony\AppData\Local\Temp\.arduinoIDE-unsaved2024211-14528-1cbu70e.cqn2f\sketch_mar11a\sketch_mar11a.ino:1:10: fatal error: PS2X_lib.h: No such file or directory
 #include "PS2X_lib.h"
          ^~~~~~~~~~~~
compilation terminated.
exit status 1

Compilation error: PS2X_lib.h: No such file or directory



here is what i used
#include "PS2X_lib.h"
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <avr/io.h>
#if ARDUINO > 22
#include "Arduino.h"
#else
#include "WProgram.h"
#include "pins_arduino.h"
#endif



static byte enter_config[]={0x01,0x43,0x00,0x01,0x00};
static byte set_mode[]={0x01,0x44,0x00,0x01,0x03,0x00,0x00,0x00,0x00};
static byte set_bytes_large[]={0x01,0x4F,0x00,0xFF,0xFF,0x03,0x00,0x00,0x00};
static byte exit_config[]={0x01,0x43,0x00,0x00,0x5A,0x5A,0x5A,0x5A,0x5A};
static byte enable_rumble[]={0x01,0x4D,0x00,0x00,0x01};
static byte type_read[]={0x01,0x45,0x00,0x5A,0x5A,0x5A,0x5A,0x5A,0x5A};

boolean PS2X::NewButtonState() {
   return ((last_buttons ^ buttons) > 0);

}

boolean PS2X::NewButtonState(unsigned int button) {
  return (((last_buttons ^ buttons) & button) > 0);
}

boolean PS2X::ButtonPressed(unsigned int button) {
  return(NewButtonState(button) & Button(button));
}

boolean PS2X::ButtonReleased(unsigned int button) {
  return((NewButtonState(button)) & ((~last_buttons & button) > 0));
}
  
boolean PS2X::Button(uint16_t button) {
   return ((~buttons & button) > 0);
}

unsigned int PS2X::ButtonDataByte() {
   return (~buttons);
}

byte PS2X::Analog(byte button) {
  return PS2data[button];
}
unsigned char PS2X::_gamepad_shiftinout (char byte) {
  

   unsigned char tmp = 0;
   for(i=0;i<8;i++) {

	  if(CHK(byte,i)) CMD_SET();
	  else  CMD_CLR();
	  CLK_CLR();

	  delayMicroseconds(CTRL_CLK);

	  if(DAT_CHK()) SET(tmp,i);
	  CLK_SET();
#if CTRL_CLK_HIGH
	  delayMicroseconds(CTRL_CLK_HIGH);
#endif	  
   }
   CMD_SET();
   delayMicroseconds(CTRL_BYTE_DELAY);
   return tmp;
}

void PS2X::read_gamepad() {
    read_gamepad(false, 0x00);
}


boolean PS2X::read_gamepad(boolean motor1, byte motor2) {
  double temp = millis() - last_read;
  
  if (temp > 1500) //waited to long
    reconfig_gamepad();
    
  if(temp < read_delay)  //waited too short
    delay(read_delay - temp);
    
    

  if(motor2 != 0x00)
    motor2 = map(motor2,0,255,0x40,0xFF); //noting below 40 will make it spin
  
  char dword[9] = {0x01,0x42,0,motor1,motor2,0,0,0,0};
  byte dword2[12] = {0,0,0,0,0,0,0,0,0,0,0,0};

  // Try a few times to get valid data...
  for (byte RetryCnt = 0; RetryCnt < 5; RetryCnt++) {
  	  CMD_SET();
      CLK_SET();
      ATT_CLR(); // low enable joystick
	  
	  delayMicroseconds(CTRL_BYTE_DELAY);
	  //Send the command to send button and joystick data;

	  for (int i = 0; i<9; i++) {
		  PS2data[i] = _gamepad_shiftinout(dword[i]);
	  }


	  if(PS2data[1] == 0x79) {  //if controller is in full data return mode, get the rest of data
		   for (int i = 0; i<12; i++) {
				PS2data[i+9] = _gamepad_shiftinout(dword2[i]);
		   }
	  }
		
  	  ATT_SET(); // HI disable joystick
	  // Check to see if we received valid data or not.  We should be in analog mode for our data
	  // to be valie
	  if ((PS2data[1] & 0xf0) == 0x70)
 		break;
	
	// If we got to here, we are not in analog mode, try to recover...
	reconfig_gamepad();	// try to get back into Analog mode.
	delay(read_delay);
  }
  
  // If we get here and still not in analog mode, try increasing the read_delay...
  if ((PS2data[1] & 0xf0) != 0x70) {
	if (read_delay < 10)
		read_delay++;	// see if this helps out...
  }	

	
	#ifdef PS2X_COM_DEBUG
    Serial.println("OUT:IN");
		for(int i=0; i<9; i++){
			Serial.print(dword[i], HEX);
			Serial.print(":");
			Serial.print(PS2data[i], HEX);
			Serial.print(" ");
		}
		for (int i = 0; i<12; i++) {
			Serial.print(dword2[i], HEX);
			Serial.print(":");
			Serial.print(PS2data[i+9], HEX);
			Serial.print(" ");
		}
	Serial.println("");	
	#endif
	
  last_buttons = buttons; //store the previous buttons states

#if defined(__AVR__)
   buttons = *(uint16_t*)(PS2data+3);   //store as one value for multiple functions
#else
   buttons =  (uint16_t)(PS2data[4] << 8) + PS2data[3];   //store as one value for multiple functions
#endif
   last_read = millis();
   return ((PS2data[1] & 0xf0) == 0x70);
}

byte PS2X::config_gamepad(uint8_t clk, uint8_t cmd, uint8_t att, uint8_t dat) {
	return config_gamepad(clk, cmd, att, dat, false, false);
}


byte PS2X::config_gamepad(uint8_t clk, uint8_t cmd, uint8_t att, uint8_t dat, bool pressures, bool rumble) {

   byte temp[sizeof(type_read)];
  
#ifdef __AVR__
 _clk_mask = digitalPinToBitMask(clk);
 _clk_oreg = portOutputRegister(digitalPinToPort(clk));
 _cmd_mask = digitalPinToBitMask(cmd);
 _cmd_oreg = portOutputRegister(digitalPinToPort(cmd));
 _att_mask = digitalPinToBitMask(att);
 _att_oreg = portOutputRegister(digitalPinToPort(att));
 _dat_mask = digitalPinToBitMask(dat);
 _dat_ireg = portInputRegister(digitalPinToPort(dat));
#else

	uint32_t            lport;                   // Port number for this pin
	_clk_mask = digitalPinToBitMask(clk); 
	lport = digitalPinToPort(clk);
	_clk_lport_set = portOutputRegister(lport) + 2;
	_clk_lport_clr = portOutputRegister(lport) + 1;

	_cmd_mask = digitalPinToBitMask(cmd); 
	lport = digitalPinToPort(cmd);
	_cmd_lport_set = portOutputRegister(lport) + 2;
	_cmd_lport_clr = portOutputRegister(lport) + 1;

	_att_mask = digitalPinToBitMask(att); 
	lport = digitalPinToPort(att);
	_att_lport_set = portOutputRegister(lport) + 2;
	_att_lport_clr = portOutputRegister(lport) + 1;
  
	_dat_mask = digitalPinToBitMask(dat); 
	_dat_lport = portInputRegister(digitalPinToPort(dat));

#endif  

  pinMode(clk, OUTPUT); //configure ports
  pinMode(att, OUTPUT);
  pinMode(cmd, OUTPUT);
  pinMode(dat, INPUT);

#if defined(__AVR__)
  digitalWrite(dat, HIGH); //enable pull-up 
#endif
    
   CMD_SET(); // SET(*_cmd_oreg,_cmd_mask);
   CLK_SET();
   
   //new error checking. First, read gamepad a few times to see if it's talking
   read_gamepad();
   read_gamepad();
   
   //see if it talked
   if(PS2data[1] != 0x41 && PS2data[1] != 0x73 && PS2data[1] != 0x79){ //see if mode came back. If still anything but 41, 73 or 79, then it's not talking
      #ifdef PS2X_DEBUG
		Serial.println("Controller mode not matched or no controller found");
		Serial.print("Expected 0x41 or 0x73, got ");
		Serial.println(PS2data[1], HEX);
	  #endif
	 
	 return 1; //return error code 1
	}
  
  //try setting mode, increasing delays if need be. 
  read_delay = 1;
  
  for(int y = 0; y <= 10; y++)
  {
   sendCommandString(enter_config, sizeof(enter_config)); //start config run
   
   //read type
   	delayMicroseconds(CTRL_BYTE_DELAY);

	CMD_SET();
    CLK_SET();
    ATT_CLR(); // low enable joystick
	
    delayMicroseconds(CTRL_BYTE_DELAY);

    for (int i = 0; i<9; i++) {
	  temp[i] = _gamepad_shiftinout(type_read[i]);
    }

	ATT_SET(); // HI disable joystick
	
	controller_type = temp[3];
   
   sendCommandString(set_mode, sizeof(set_mode));
   if(rumble){ sendCommandString(enable_rumble, sizeof(enable_rumble)); en_Rumble = true; }
   if(pressures){ sendCommandString(set_bytes_large, sizeof(set_bytes_large)); en_Pressures = true; }
   sendCommandString(exit_config, sizeof(exit_config));
   
   read_gamepad();
   
   if(pressures){
	if(PS2data[1] == 0x79)
		break;
	if(PS2data[1] == 0x73)
		return 3;
   }
   
    if(PS2data[1] == 0x73)
      break;
      
    if(y == 10){
		#ifdef PS2X_DEBUG
		Serial.println("Controller not accepting commands");
		Serial.print("mode stil set at");
		Serial.println(PS2data[1], HEX);
		#endif
      return 2; //exit function with error
	  }
    
    read_delay += 1; //add 1ms to read_delay
  }
   
 return 0; //no error if here
}



void PS2X::sendCommandString(byte string[], byte len) {
  

  #ifdef PS2X_COM_DEBUG
  byte temp[len];
  ATT_CLR(); // low enable joystick
  delayMicroseconds(CTRL_BYTE_DELAY);
	
  for (int y=0; y < len; y++)
    temp[y] = _gamepad_shiftinout(string[y]);
    
  ATT_SET(); //high disable joystick  
   delay(read_delay);                  //wait a few
  
  Serial.println("OUT:IN Configure");
  for(int i=0; i<len; i++){
			Serial.print(string[i], HEX);
			Serial.print(":");
			Serial.print(temp[i], HEX);
			Serial.print(" ");
		}
   Serial.println("");
  
  #else
  ATT_CLR(); // low enable joystick
  for (int y=0; y < len; y++)
    _gamepad_shiftinout(string[y]);
    
   ATT_SET(); //high disable joystick  
   delay(read_delay);                  //wait a few
   #endif
}

 

byte PS2X::readType() {
/*
	byte temp[sizeof(type_read)];
	
	sendCommandString(enter_config, sizeof(enter_config));
	
	delayMicroseconds(CTRL_BYTE_DELAY);

	CMD_SET();
    CLK_SET();
    ATT_CLR(); // low enable joystick
	
    delayMicroseconds(CTRL_BYTE_DELAY);

    for (int i = 0; i<9; i++) {
	  temp[i] = _gamepad_shiftinout(type_read[i]);
    }
	
	sendCommandString(exit_config, sizeof(exit_config));
	 
	if(temp[3] == 0x03)
		return 1;
	else if(temp[3] == 0x01)
		return 2;
	
	return 0;
	*/
	
	if(controller_type == 0x03)
		return 1;
	else if(controller_type == 0x01)
		return 2;
	
	return 0;
	
}

void PS2X::enableRumble() {
  
     sendCommandString(enter_config, sizeof(enter_config));
     sendCommandString(enable_rumble, sizeof(enable_rumble));
     sendCommandString(exit_config, sizeof(exit_config));
     en_Rumble = true;
  
}

bool PS2X::enablePressures() {
  
     sendCommandString(enter_config, sizeof(enter_config));
     sendCommandString(set_bytes_large, sizeof(set_bytes_large));
     sendCommandString(exit_config, sizeof(exit_config));
	 
	 read_gamepad();
	 read_gamepad();
	 
	  if(PS2data[1] != 0x79)
		return false;
		
     en_Pressures = true;
	 return true;
}

void PS2X::reconfig_gamepad(){
  
   sendCommandString(enter_config, sizeof(enter_config));
   sendCommandString(set_mode, sizeof(set_mode));
   if (en_Rumble)
      sendCommandString(enable_rumble, sizeof(enable_rumble));
   if (en_Pressures)
      sendCommandString(set_bytes_large, sizeof(set_bytes_large));
   sendCommandString(exit_config, sizeof(exit_config));
   
}


#ifdef __AVR__
inline void  PS2X::CLK_SET(void) {
	
   register uint8_t old_sreg = SREG;
   cli();
   *_clk_oreg |= _clk_mask;
   SREG = old_sreg;
}

inline void  PS2X::CLK_CLR(void) {
   register uint8_t old_sreg = SREG;
   cli();
   *_clk_oreg &= ~_clk_mask;
   SREG = old_sreg;
}

inline void  PS2X::CMD_SET(void) {
   register uint8_t old_sreg = SREG;
   cli();
   *_cmd_oreg |= _cmd_mask; // SET(*_cmd_oreg,_cmd_mask);
   SREG = old_sreg;
}

inline void  PS2X::CMD_CLR(void) {
   register uint8_t old_sreg = SREG;
   cli();
   *_cmd_oreg &= ~_cmd_mask; // SET(*_cmd_oreg,_cmd_mask);
   SREG = old_sreg;
}

inline void  PS2X::ATT_SET(void) {
   register uint8_t old_sreg = SREG;
   cli();
  *_att_oreg |= _att_mask ; 	
   SREG = old_sreg;
}

inline void PS2X::ATT_CLR(void) {
   register uint8_t old_sreg = SREG;
   cli();
  *_att_oreg &= ~_att_mask; 
   SREG = old_sreg;
}

inline bool PS2X::DAT_CHK(void) {
	return (*_dat_ireg & _dat_mask)? true : false;
}

#else
// On pic32, use the set/clr registers to make them atomic...inline void  PS2X::CLK_SET(void) {
	*_clk_lport_set |= _clk_mask;
}

inline void  PS2X::CLK_CLR(void) {
	*_clk_lport_clr |= _clk_mask;
}

inline void  PS2X::CMD_SET(void) {
	*_cmd_lport_set |= _cmd_mask;
}

inline void  PS2X::CMD_CLR(void) {
	*_cmd_lport_clr |= _cmd_mask;
}

inline void  PS2X::ATT_SET(void) {
	*_att_lport_set |= _att_mask;
}

inline void PS2X::ATT_CLR(void) {
	*_att_lport_clr |= _att_mask;
}

inline bool PS2X::DAT_CHK(void) {
	return (*_dat_lport & _dat_mask)? true : false;

}

#endif

You don’t copy / paste a library, you need to install it.
https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries/

The example includes the #include, so unless you’re creating your own code, don’t need to write it.

Hope this helps.

lol oh gosh i knew i was missing something and i watched a bunch of how to videos. ok so it did seem to load it on the Botboarduino thank you so very much for your help.

No worries. Arduino is a LOT of fun. Feel free to take some time to go over the basic examples (use the BotBoarduino on its own, disconnected from the arm) and learn some cool programming skills. The BotBoarduino has some onboard features like the LEDs, buttons and buzzer to play with too.

1 Like

You could if you wanted to:
Frsky Transmitter 》Rc receiver > Arduino > PCA9685 servo driver.

My current set up for an 8 legged robot is:
Frsky Transmitter (Taranis 9xd) 》Rc receiver (R-XSR) > Arduino Nano (serial) > Arduino Mega Pro (I2C) > PCA9685 servo driver x2

I use the HCPCA9685 library on the mega to drive the PCA9685 boards.

Iv chosen this setup to disband the PS2 as i wanted to use my Taranis for my project.

Iv used two servo driver boards as i need 24 servos.

2 Likes

I did run a few examples like blink and messed around with them for a bit before i got the PS2x loaded.

I triple checked my wiring from the ssc-32 to botboarduino and the ps2 breakout board. When my PS2 controller is on the receiver lights state it is connected but not getting the PS2 to show up on the SSC-32 servo sequencer

{…} not getting the PS2 to show up on the SSC-32 servo sequencer

No, it would not show up in the sequencer. The Lynxmotion PS2 remote control is meant to connect to the BotBoarduino, which is not compatible with the sequencer software. You would need a wired PS2 remote with USB connection, or a PS2 to receiver adapter. The Botboarduino would not be used.

1 Like

Then why would they advertise that you can use them all together? I guess i am a bit confused, so the sequencer is only made for wired operation.

If not the sequencer then how else would you give it the commands for a 4 legged walker to walk with remote operation?

Thank you for your help with all this!
So i need to locate a walking program for my 4 legged project and load it to the BotBoarduino as well as the ps2x

Ok here is what i purchased

Lynxmotion SQ3U Symmetric Quadruped Walking Robot

The only link they had was to the the ssc- 32 sequencer so i thought thats how i needed to control it.

But not finding any info about what code i need to use for it

“Controlling the Robot
The combo kit comes with the SSC-32 servo controller and the BotBoarduino microcontroller. By offloading the servo pulse generation and sequence movement timing to the SSC-32, the main board has plenty of power to do some really cool things. The basic PS2 code allows you to control the robot using a wireless (or wired) PS2 remote control.”

There are two ways to control the SQ3:

  1. With an SSC-32U and wireless device like a Bluetooth Bee. This can make use of FlowBotics and the SQ3 application. Step 7c in the guide.
    https://www.robotshop.com/products/flowbotics-studio-download

  2. Using the SSC-32U, the BotBoarduino and the PS2. Step 7b in the guide. The sample code to upload to the BotBoarduino (step 15) can be found here: GitHub - Lynxmotion/Quadrupeds: Quadrupeds

1 Like

With flowbotics would my quad be fully wireless?

1 Like

Correct. It would use your computer as the “brain” which frequently sends it position commands and takes care of all walking (and inverse kinematics calculations). You’d only use the SSC-32U plus a Bluetooth Bee module installed on it.
Ex: https://www.robotshop.com/products/bluetooth-bee
(We have not tried that specific model with the SSC-32U, but can’t see any reason why it would not work).

1 Like