S.BUS C++ and serial USB to TTL code development

Ask software engineering and SDK questions for developers working on Mac OS X, Windows or Linux.
  • Author
  • Message
Offline

myvirtualexam

  • Posts: 3
  • Joined: Mon Mar 18, 2019 10:13 pm
  • Real Name: Martijn Stam

S.BUS C++ and serial USB to TTL code development

PostWed Mar 20, 2019 4:28 pm

For a video conferencing project I need to get remote control over S.BUS working for the Blackmagic Pocket Cinema Camera 4K with latest firmware 4.7.1.

I wrote code in C++ and I'm using an FTDI FT232 USB to TTL serial converter. All seems to work fine except that the camera doesn't respond to anything I send. It's important I get it working on the x86 architecture and not Arduino because it's not powerful enough.

Who likes to elaborate to get it working please?

serial.cpp:
Code: Select all
/*
Camera S.BUS range 44-212, center 128
*/
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <linux/serial_core.h>
#include <iostream>
#include "sbus.h"
#define BAUD_RATE B38400
#define DEVICE "/dev/ttyUSB0"

int sbus_fd, n;
static uint8_t packet[25];
static uint8_t sbusData[25];
static struct termios orig_termios;

void SBUS::create_packet(uint16_t* channels)
{
   packet[0] = _sbusHeader;
   if (channels) {
      packet[1] = (uint8_t) ((channels[0] & 0x07FF));
      packet[2] = (uint8_t) ((channels[0] & 0x07FF)>>8 | (channels[1] & 0x07FF)<<3);
      packet[3] = (uint8_t) ((channels[1] & 0x07FF)>>5 | (channels[2] & 0x07FF)<<6);
      packet[4] = (uint8_t) ((channels[2] & 0x07FF)>>2);
      packet[5] = (uint8_t) ((channels[2] & 0x07FF)>>10 | (channels[3] & 0x07FF)<<1);
      packet[6] = (uint8_t) ((channels[3] & 0x07FF)>>7 | (channels[4] & 0x07FF)<<4);
      packet[7] = (uint8_t) ((channels[4] & 0x07FF)>>4 | (channels[5] & 0x07FF)<<7);
      packet[8] = (uint8_t) ((channels[5] & 0x07FF)>>1);
      packet[9] = (uint8_t) ((channels[5] & 0x07FF)>>9 | (channels[6] & 0x07FF)<<2);
      packet[10] = (uint8_t) ((channels[6] & 0x07FF)>>6 | (channels[7] & 0x07FF)<<5);
      packet[11] = (uint8_t) ((channels[7] & 0x07FF)>>3);
      packet[12] = (uint8_t) ((channels[8] & 0x07FF));
      packet[13] = (uint8_t) ((channels[8] & 0x07FF)>>8 | (channels[9] & 0x07FF)<<3);
      packet[14] = (uint8_t) ((channels[9] & 0x07FF)>>5 | (channels[10] & 0x07FF)<<6);
      packet[15] = (uint8_t) ((channels[10] & 0x07FF)>>2);
      packet[16] = (uint8_t) ((channels[10] & 0x07FF)>>10 | (channels[11] & 0x07FF)<<1);
      packet[17] = (uint8_t) ((channels[11] & 0x07FF)>>7 | (channels[12] & 0x07FF)<<4);
      packet[18] = (uint8_t) ((channels[12] & 0x07FF)>>4 | (channels[13] & 0x07FF)<<7);
      packet[19] = (uint8_t) ((channels[13] & 0x07FF)>>1);
      packet[20] = (uint8_t) ((channels[13] & 0x07FF)>>9 | (channels[14] & 0x07FF)<<2);
      packet[21] = (uint8_t) ((channels[14] & 0x07FF)>>6 | (channels[15] & 0x07FF)<<5);
      packet[22] = (uint8_t) ((channels[15] & 0x07FF)>>3);
   }
   packet[23] = 0x00;
   packet[24] = _sbusFooter;

   memcpy(sbusData,packet,25);
  //memcpy(servos,loc_servos,18);

  /*for(int i = 0; i < 25; i++) {
    printf("%x ", packet[i]);
  }

  printf("\n");*/

  //printf("p 5: %x\n", packet[23]);
}

void SBUS::serial_config()
{
  struct termios options;

  sbus_fd = open(DEVICE, O_RDWR | O_NONBLOCK);

  if (tcgetattr(sbus_fd, &options) != 0)
    printf("\n  Error! in getting attributes \n");

  tcflush(sbus_fd, TCIFLUSH);
  bzero(&options, sizeof(options));

   options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
   options.c_cflag |= CLOCAL;
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS8;
  options.c_cflag |= PARENB;
  options.c_cflag &= ~PARODD;
  options.c_cflag |= CSTOPB;

  options.c_cc[VTIME] = 0;
  options.c_cc[VMIN] = 0;

  cfsetispeed(&options, B115200);
  cfsetospeed(&options, B115200);

  tcflush(sbus_fd, TCIFLUSH);

  if ((tcsetattr(sbus_fd, TCSANOW, &options)) != 0)
    printf("\n  ERROR ! in Setting attributes\n");

  int baud = 100000;
  struct serial_struct serials;

  if ((ioctl(sbus_fd, TIOCGSERIAL, &serials)) < 0)
    printf("\n  ERROR ! in Setting serial attributes\n");

  serials.flags = ASYNC_SPD_CUST;
  serials.custom_divisor = serials.baud_base / baud;

  if ((ioctl(sbus_fd, TIOCSSERIAL, &serials)) < 0)
    printf("\n  ERROR ! in Setting custom baudrate\n");

  ioctl(sbus_fd, TIOCGSERIAL, &serials);

  tcflush(sbus_fd, TCIFLUSH);
}

void SBUS::serial_write()
{
  n = write(sbus_fd, sbusData, 25);
  //printf("%d\n", n);
  if (n != 25)
    printf("Write failed, written %i bytes\n", n);
}

void SBUS::serial_close()
{
  close(sbus_fd);
}

int main() {
  SBUS s;
  uint16_t channels[16] = {0};

  s.serial_config();
  for (uint8_t i = 0; i < 16; i++) {
      channels[i] = 128;
   }
  channels[5] = 200;
  channels[6] = 200;
  channels[7] = 200;
  channels[8] = 200;

  for ( uint16_t x = 0; x < 500000; x++) {
   //while(true) {
    /*if (x == 100) {
      for (uint8_t i = 0; i < 16; i++) {
          channels[i] = 45;
       }
    }
    if (x == 250) {
      for (uint8_t i = 0; i < 16; i++) {
          channels[i] = 128;
       }
    }
    if (x == 400) {
      for (uint8_t i = 0; i < 16; i++) {
          channels[i] = 45;
       }
    }*/
    s.create_packet(channels);
    s.serial_write();

    usleep(14000);
  }

  s.serial_close();
  return 0;
}


sbus.h:
Code: Select all
#ifndef SBUS_h
#define SBUS_h

class SBUS{
   public:
      void create_packet(uint16_t* channels);
      void serial_config();
      void serial_write();
      void serial_close();
  private:
      const uint32_t _sbusBaud = 100000;
      static const uint8_t _numChannels = 16;
      const uint8_t _sbusHeader = 0x0F;
      const uint8_t _sbusFooter = 0x00;
};

#endif


bmmcpc.jpg
My setup
bmmcpc.jpg (547.02 KiB) Viewed 2158 times
Offline

myvirtualexam

  • Posts: 3
  • Joined: Mon Mar 18, 2019 10:13 pm
  • Real Name: Martijn Stam

Re: S.BUS C++ and serial USB to TTL code development

PostWed Apr 03, 2019 10:18 pm

Got it working. The culprit was that after looking closer at a Futaba connector that white is signal and red is ground so I had the polarity wrong all the time.

I wrote C++ code specifically for this camera. Attached all the files including the sbus binary for the x86_64 platform.

My findings are the following:

Minimum frame rate delay is 5ms, any faster and the camera blacks out.
Specified values as documented 44 min, 128 center and 212 max are values used internally. The camera chops the last 3 bits of the 11 bits values. Default Futaba range is 172 min, 1024 center and 1811 max which I used.

With my code you can set any channel to 4 different formats:

single position switch: in order to trigger rec start/stop, or auto focus
toggle up/down 1 or -1: to e.g. de- and increase gain
range in steps: to set frame rate, zoom level, and aperture
digital bit 1/0: for e.g. channel 18 reset which resets shutter speed, gain and white balance

Last channel values are saved upon exit and loaded upon start.

I used a FT232 USB to TTL converter with the TX inverted. FTDI's FT_Prog allows you to invert signals.
You must at least be sure that the UART you are using supports a custom divider. Most onboard UARTs don't. The closest baud rate will be 115200 which won't work.
You can use a 2N7000 and a 2k2 to 3k3 resistor to invert the signal. See this schematic. Replace the 10k resistor for either 2k2 or 3k3. 10k doesn't work.

https://quadmeup.com/simplest-hardware- ... ontroller/

compile with g++ -std=c++11 -Wall -o sbus ./sbus.cpp
You need INIReader as well.

sbus.h
Code: Select all
#ifndef SBUS_h
#define SBUS_h

uint16_t _channels[18] = {0};

int sbus_fd, fd, _delay, _cycles, _sbus_min, _sbus_center, _sbus_max;
uint8_t packet[25] = {0}, n;
std::string _ret, _cmd, _channel1, _channel2, _channel3, _channel4, _channel5, _channel6, _channel7, _channel8, _channel9, _channel10, _channel17, _channel18;
const char *_device;

std::ifstream ifs;

inline std::string trim(std::string& str)
{
   str.erase(0, str.find_first_not_of(' '));       //prefixing spaces
   str.erase(str.find_last_not_of(' ')+1);         //surfixing spaces
   return str;
}

class SBUS{
   public:
      int _begin();
      void _write(uint16_t* channels);
      void _end(int signum);
      void _channel_def(std::string _channel, int& _val);
      void _frmt(std::string _cmd);
      bool _cycle = false;
      int __cycles = 0;

      uint16_t _map(uint16_t x, uint16_t in_min, uint16_t in_max, uint16_t out_min, uint16_t out_max) {
         return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
      }
  private:
      const uint32_t _sbusBaud = 100000;
      const uint8_t _sbusHeader = 0x0f;
      const uint8_t _sbusFooter = 0x00;
      std::string _tmp, _types[4] = {"single","toggle","range","bit"};
      double _steps, _offset;
      int _chan=100, _val=0, _range=0, _type=0;

};

#endif

#ifndef CURRENT_TIME_H
#define CURRENT_TIME_H

#include <chrono>
#include <cstdint>

namespace TimeHelpers
{
    namespace
    {
        inline std::chrono::high_resolution_clock Clock()
        {
            static std::chrono::high_resolution_clock clock;
            return clock;
        }

        template<typename Out, typename In>
        Out TimeAs()
        {
            return std::chrono::duration_cast<In>
                (Clock().now().time_since_epoch()).count();
        }
    }

    template<typename Out>
    inline Out TimeFromEpochInMilliSeconds()
    {
        return TimeAs<Out, std::chrono::milliseconds>();
    }

    template<typename Out>
    inline Out TimeFromEpochInMicroSeconds()
    {
        return TimeAs<Out, std::chrono::microseconds>();
    }

    template<typename Out>
    inline Out TimeFromEpochInNanoSeconds()
    {
        return TimeAs<Out, std::chrono::nanoseconds>();
    }
}

#endif

sbus.cpp
Code: Select all
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <linux/serial_core.h>
#include <string>
#include <fstream>
#include <cmath>
#include <regex>
#include <csignal>
#include "../inih/INIReader.h"
#include "sbus.h"

#define CMDFILE "/dev/shm/sbus"
#define SAVEFILE "/opt/ctrl/sbus.save"

int SBUS::_begin()
{
  struct termios options;

  sbus_fd = open(_device, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);
  tcgetattr(sbus_fd, &options);
  cfsetispeed(&options, B0);
  cfsetospeed(&options, B38400);
  options.c_cflag |= (CS8 | CLOCAL | PARENB | CSTOPB | INPCK);
  options.c_lflag &= ~(CREAD | PARODD | CSIZE | CRTSCTS | ICANON | ECHO | ECHOE | ISIG | HUPCL | IXON);
  tcsetattr(sbus_fd, TCSANOW, &options);
  tcflush(sbus_fd, TCIOFLUSH);

  struct serial_struct serials;

  if ((ioctl(sbus_fd, TIOCGSERIAL, &serials)) < 0) {
    printf("\n  ERROR ! in getting attributes\n");
    return -1;
  }

   serials.flags = (serials.flags & ~ASYNC_SPD_MASK) | ASYNC_SPD_CUST;
   serials.custom_divisor = (serials.baud_base + (_sbusBaud / 2)) / _sbusBaud;
   uint32_t closestSpeed = serials.baud_base / serials.custom_divisor;

   if (closestSpeed < _sbusBaud * 98 / 100 || closestSpeed > _sbusBaud * 102 / 100) {
       printf("Cannot set serial port speed to %d. Closest possible is %d\n", _sbusBaud, closestSpeed);
      return -1;
   }

  if ((ioctl(sbus_fd, TIOCSSERIAL, &serials)) < 0) {
    printf("\n  ERROR ! in setting custom divider\n");
    return -1;
  }

  tcflush(sbus_fd, TCIOFLUSH);

  memcpy(&fd, &sbus_fd, sizeof(sbus_fd));

  return 0;
}

void SBUS::_write(uint16_t* channels)
{

   packet[0] = _sbusHeader;

  // clear received channel data
  for (uint8_t i=1; i<24; i++) {
      packet[i] = 0;
  }

  // reset counters
  uint8_t ch = 0;
  uint8_t bit_in_servo = 0;
  uint8_t byte_in_sbus = 1;
  uint8_t bit_in_sbus = 0;

  // store servo data
  for (uint8_t i=0; i<176; i++) {
      if (channels[ch] & (1<<bit_in_servo)) {
          packet[byte_in_sbus] |= (1<<bit_in_sbus);
      }
      bit_in_sbus++;
      bit_in_servo++;

      if (bit_in_sbus == 8) {
          bit_in_sbus =0;
          byte_in_sbus++;
      }
      if (bit_in_servo == 11) {
          bit_in_servo =0;
          ch++;
      }
  }

  // DigiChannel 1
  if (channels[16] > 0) {
      packet[23] |= (1<<0);
  }
  // DigiChannel 2
  if (channels[17] > 0) {
      packet[23] |= (1<<1);
  }

  packet[24] = _sbusFooter;

  write(fd, packet, 25);
}

void _end(int signum) {
  close(fd);
  ifs.close();
  std::ofstream ofs;
  ofs.open(SAVEFILE, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
  ofs.write((char*) &_channels, sizeof(_channels));
  ofs.close();
  exit(signum);
}

void SBUS::_channel_def(std::string _channel, int& _val) {
  std::regex base_regex("\\[([0-9]+)\\]");
  std::smatch base_match;
  try {
    std::sregex_iterator next(_channel.begin(), _channel.end(), base_regex);
    std::sregex_iterator end;
    if(next != end) {
      base_match = *next;
      _type = stoi(base_match[1]);
      next++;
    }
    if(next != end) {
      base_match = *next;
      _range = stoi(base_match[1]);
      if(_val>_range) _val = _range;
    }

    if(strcmp(_types[_type].c_str(), "single")==0) {
      if(_cycle == true && __cycles >= _cycles) {
        _val = _sbus_center;
        _cycle = false;
        __cycles = 0;
        std::ofstream ofs;
        ofs.open(CMDFILE, std::ofstream::out);
        ofs << "";
        ofs.close();
      }
      else {
        _val = _sbus_max;
        _cycle = true;
        __cycles++;
      }
    }
    if(strcmp(_types[_type].c_str(), "toggle")==0) {
      if(_cycle == true && __cycles >= _cycles) {
        _val = _sbus_center;
        _cycle = false;
        __cycles = 0;
        std::ofstream ofs;
        ofs.open(CMDFILE, std::ofstream::out);
        ofs << "";
        ofs.close();
      }
      else {
        if(_val==1) {
          _val = _sbus_max;
        }
        else if(_val==-1) {
          _val = _sbus_min;
        }
        _cycle = true;
        __cycles++;
      }
    }
    if(strcmp(_types[_type].c_str(), "range")==0) {
      _steps = (_sbus_max-_sbus_min)/_range;//163.9
      _offset = _steps/2;//81.95
      if(_val < 1) _val = 1;
      _val = _sbus_min + _offset + ((_val-1)*_steps);
    }
    if(strcmp(_types[_type].c_str(), "bit")==0) {
      if(_cycle == true && __cycles >= _cycles) {
        _val = 0;
        _cycle = false;
        __cycles = 0;
        std::ofstream ofs;
        ofs.open(CMDFILE, std::ofstream::out);
        ofs << "";
        ofs.close();
      }
      else {
        _val = 1;
        _cycle = true;
        __cycles++;
      }
    }
    _val = round(_val);
  } catch (std::regex_error& e) {
    printf("Compilation error\n");
  }
}

void SBUS::_frmt(std::string _cmd) {
  _tmp = _cmd.substr(0, _cmd.find(' '));
  if(strcmp(_tmp.c_str(), "")!=0) _chan = stoi(_tmp);
  _tmp = _cmd.substr(_cmd.find(' ')+1, 3);
  _tmp = trim(_tmp);
  if(strcmp(_tmp.c_str(), "")!=0) {
    _val = stoi(_tmp);
  }

  switch(_chan){
    case 1:
      _channel_def(_channel1, _val);
      break;
    case 2:
      _channel_def(_channel2, _val);
      break;
    case 3:
      _channel_def(_channel3, _val);
      break;
    case 4:
      _channel_def(_channel4, _val);
      break;
    case 5:
      _channel_def(_channel5, _val);
      break;
    case 6:
      _channel_def(_channel6, _val);
      break;
    case 7:
      _channel_def(_channel7, _val);
      break;
    case 8:
      _channel_def(_channel8, _val);
      break;
    case 9:
      _channel_def(_channel9, _val);
      break;
    case 10:
      _channel_def(_channel10, _val);
      break;
    case 17:
      _channel_def(_channel17, _val);
      break;
    case 18:
      _channel_def(_channel18, _val);
      break;
  }
  _channels[_chan-1] = _val;
}

int main() {

  signal(SIGTERM, _end);

  INIReader reader("/opt/ctrl/mve.ini");

  if (reader.ParseError() != 0) {
      printf("Failed to load 'mve.ini'\n");
      return -1;
  }

  _device = reader.Get("sbus", "device", "/dev/ttyUSB0").c_str();
  _delay = reader.GetInteger("sbus", "delay", 7);
  _cycles = reader.GetInteger("sbus", "cycles", 2);
  _sbus_min = reader.GetInteger("sbus", "sbus_min", 712);
  _sbus_center = reader.GetInteger("sbus", "sbus_center", 1024);
  _sbus_max = reader.GetInteger("sbus", "sbus_max", 1811);

  _channel1 = reader.Get("sbus", "channel1", "");
  _channel2 = reader.Get("sbus", "channel2", "");
  _channel3 = reader.Get("sbus", "channel3", "");
  _channel4 = reader.Get("sbus", "channel4", "");
  _channel5 = reader.Get("sbus", "channel5", "");
  _channel6 = reader.Get("sbus", "channel6", "");
  _channel7 = reader.Get("sbus", "channel7", "");
  _channel8 = reader.Get("sbus", "channel8", "");
  _channel9 = reader.Get("sbus", "channel9", "");
  _channel10 = reader.Get("sbus", "channel10", "");
  _channel17 = reader.Get("sbus", "channel17", "");
  _channel18 = reader.Get("sbus", "channel18", "");

  SBUS s;

  s._begin();

  ifs.open(SAVEFILE, std::ifstream::binary);
  if (ifs.is_open())
  {
    ifs.read((char*) &_channels, sizeof(_channels));
    ifs.close();
  }
  else {
    for (uint8_t i = 0; i < 16; i++) {
        _channels[i] = _sbus_center;
     }

    _channels[16] = 0;
    _channels[17] = 0;
  }

  // Create file to read from if it doesn't exist
  if(access(CMDFILE, F_OK) != 0) {
    std::ofstream ofs;
    ofs.open(CMDFILE, std::ofstream::out);
    ofs.close();
  }

  ifs.open(CMDFILE, std::ifstream::in);

  while (1) {

    std::getline(ifs, _ret);
    _ret = trim(_ret);
    ifs.clear();
    ifs.seekg(0, std::ios::beg);

    if(strcmp(_cmd.c_str(),_ret.c_str())!=0) {
      _cmd = _ret;

      if(strcmp(_cmd.c_str(),"")!=0) s._frmt(_cmd);
        else continue;
    }

    s._write(_channels);

    if(s._cycle==true) {
      s._frmt(_cmd);
    }

      usleep(_delay * 1000);
   }

  return 0;
}

sbus.ini
Code: Select all
[sbus]
version = 1.0
device = /dev/ttyUSB0      ; Serial port to read/write from/to
delay = 7         ; Set delay between frames in milliseconds
cycles = 2         ; Number of frames for each command
sbus_min = 172         ; Lowest sbus value
sbus_center = 1024      ; Center value
sbus_max = 1811         ; Max value

; Channel definitions
; [0]=single position switch
; [1]=toggle up/down where n=1 or -1
; [2]=range where [9] the number of steps
; [3]=bit 1/0 for e.g. channel 18 reset

; Examples:
; [0] 'echo 4 > /dev/shm/sbus' Trigger Auto Focus
; [1] 'echo "6 1" > /dev/shm/sbus' Increase Gain, 'echo "6 -1" > /dev/shm/sbus' Decrease Gain
; [2] 'echo "10 n" > /dev/shm/sbus' Set Frame Rate where n=1-9
; [3] 'echo 18 > /dev/shm/sbus' Set the Reset bit

channel1 = [0]         ; On/Off trigger for REC start/stop
channel2 = [2][9]      ; Iris
channel3 = [2][9]      ; Focus
channel4 = [0]         ; Auto Focus
channel5 = [2][9]      ; Zoom
channel6 = [1]         ; Gain
channel7 = [1]         ; Shutter Speed
channel8 = [1]         ; White Balance
channel9 = [1]         ; Audio Levels
channel10 = [2][9]      ; Frame Rate
channel18 = [3]         ; Reset shutter speed, gain and white balance
Attachments
S.BUS.zip
(145.83 KiB) Downloaded 238 times

Return to Software Developers

Who is online

Users browsing this forum: Nicholas Gill and 12 guests