I am trying to find a way to send movement commands to a CC3D (on a quad-copter) from an Arduino.
If it would be easier to use a raspberry pi, any tips related to that would be equally welcome, but it seems from my research as though an Arduino would be the more achievable option.

The commands I need to send are: Pitch, Yaw, Throttle and Roll.
Flight-mode and gimball move commands would also be nice but are not essential.

Here's what I've tried already and the results.

First I tried to send the CC3D a PWM input from a RPi, using the RPi GPIO python module.
I was rather unsure about the difference between Duty Cycle and Frequency, but I tried every combination of the following ranges for both:
0 - 100, 0 - 255 and 1000 to 2000.
The CC3D did not register any input in any of these scenarios.

I then tried to send PWM signals to the CC3D from an Arduino nano using the analogWrite() function.
With this method the CC3D acknowledged that a PWM signal was being received on the specific channel, but in could not seem to interpret that input as a meaningful value.

Then I stumbled upon this page:
I tried to follow it's instructions on a device running Debian, but ran into an issue setting up the build environment.
After some tracing, I determined the source of the error to be:
"Error: dependency is not satisfiable: qtdeclarative-abi-5-7-0"
I tried to manually install this dependency with no success.
Note: This was my first attempt to install librepilot on this Debian device.
To get the readings of PWM input from the CC3D that I mentioned above, I used librepilot running off a windows Vista device.
I then tried to install librepilot normally on the Debian device and got the same error.

I have not attempted to set up a build environment on the Windows Vista device, but if you think that that is worth a shot then I am happy to give it a go.

I believe that I only need the build environment to get the header files generated by UAVObjectGenerator. Is there somewhere that I can download these from without generating them myself?
I found a copy of LibrePilotSerial.h at https://github.com/MarcProe/LibrePilot.arduino .

These are just the things that I have tried, if you have some totally different idea that will help me to control a CC3D from an Arduino or Raspberry pi, then please let me know.



  • ***
  • 170
    • Frickeln und mehr
Arduino is capable to generate PWM signals, use them as input to CC3D.  I will look at home if I could find the scetch.


Gesendet von iPhone mit Tapatalk Pro
« Last Edit: March 18, 2020, 07:29:55 am by utoedter »

Thanks for your reply, I have tried this approach, but was unsure as to how to configure it at the librepilot end: I could not get past the stage in the input setup wizard where it requested that I move the joysticks and compare them to the image: the image showed no movement, below in the code that I used:
The transmittion works fine, I checked that with the serial console.
I would very much like to see the sketch that you used.
Drone End:
Code: [Select]
#include <SoftwareSerial.h>

// Arduino nano PWM pins.
// 3, 5, 6, 9, 10, 11

const byte ThrottlePin = 3; // Rise
const byte PitchPin = 5; // Throttle
const byte RollPin = 6; // Turn
const byte YawPin = 9;

// const byte ModePin = 12;

const byte Gim1 = 10;
const byte Gim2 = 11;

// const byte FctPin = 2;

const byte HC12RxdPin = 4;  // "RXD" Pin on HC12, Connect HC12 "RXD" pin to Arduino Digital Pin 4
const byte HC12TxdPin = 7;  // "TXD" Pin on HC12, Connect HC12 "TXD" pin to Arduino Digital Pin 7
const byte HC12SetPin = 8;  // "SET" Pin on HC12, Connect HC12 "Set" pin to Arduino Digital Pin 8

unsigned long timer = millis(); // Init Delay Timer

char SerialByteIn;  // Temporary variable for recieved bytes over Serial
char HC12ByteIn;  // Temporary variable for recieved bytes over HC-12

String HC12ReadBuffer = "";  // Read/Write Buffer for HC12
String SerialReadBuffer = "";  // Read/Write Buffer for Seria
boolean EndFlag = false;  // Init Flag to indiacte End of Rx String
unsigned long SerialEndTime = 0;
unsigned long HC12EndTime = 0;

String Rx = "";

boolean commandMode = false;  // Flag for mode to send AT commands to HC-12

// Software Serial ports Rx and Tx are opposite the HC12 Rx and Tx
SoftwareSerial HC12(HC12TxdPin, HC12RxdPin); // Create Software Serial Port for HC12

void setup() {  // Runs Once

  HC12ReadBuffer.reserve(64);  // Reserve 64 bytes for Serial message input
  SerialReadBuffer.reserve(64);  // Reserve 64 bytes for HC12 message input

  pinMode(HC12SetPin, OUTPUT);  // Output High for Transparent / Low for Command
  digitalWrite(HC12SetPin, HIGH);  // Enter Transparent mode at start of program
  delay(80);  // 80 ms delay before operation per datasheet
  Serial.begin(9600);  // Open serial port to RPi
  HC12.begin(9600);  // Open software serial port to HC12

  pinMode(ThrottlePin, OUTPUT);
  pinMode(PitchPin, OUTPUT);
  pinMode(RollPin, OUTPUT);
  pinMode(YawPin, OUTPUT);

  pinMode(Gim1, OUTPUT);
  pinMode(Gim2, OUTPUT);

  HC12.println("Nano Started");

void DualPrint(String msg) {

void ExecuteATcmd(String ATcmd) {
  digitalWrite(HC12SetPin, LOW);            // Enter command mode
  delay(100);                               // Allow chip time to enter command mode
  Serial.print(ATcmd);           // Echo command to serial
  HC12.print(ATcmd);             // Send command to local HC12
  delay(500);                               // Wait 0.5s for a response
  digitalWrite(HC12SetPin, HIGH);           // Exit command / enter transparent mode
  delay(100);                               // Delay before proceeding

String getValue(String data, char separator, int index)
    int found = 0;
    int strIndex[] = { 0, -1 };
    int maxIndex = data.length() - 1;

    for (int i = 0; i <= maxIndex && found <= index; i++) {
        if (data.charAt(i) == separator || i == maxIndex) {
            strIndex[0] = strIndex[1] + 1;
            strIndex[1] = (i == maxIndex) ? i+1 : i;
    return found > index ? data.substring(strIndex[0], strIndex[1]) : "";

void loop() {
  unsigned long Now = millis();
  while (HC12.available()) {  // While Arduino's HC12 soft serial rx buffer has data
    HC12ByteIn = HC12.read();  // Store each character from rx buffer in byteIn
    HC12ReadBuffer += char(HC12ByteIn);  // Write each character of byteIn to HC12ReadBuffer
    if (HC12ByteIn == '\n') {  // At the end of the line
      EndFlag = true;
      HC12EndTime = Now;
  while (Serial.available()) {  // If Arduino's computer rx buffer has data
    SerialByteIn = Serial.read();  // Store each character in byteIn
    SerialReadBuffer += char(SerialByteIn);  // Write each character of byteIn to SerialReadBuffer
    if (SerialByteIn == '\n') {  // Check to see if at the end of the line
      EndFlag = true;
      SerialEndTime = Now;

  if(EndFlag) {  // Check to see if End flag is true
    if(HC12EndTime > SerialEndTime) { // HC12 Input
      Rx = HC12ReadBuffer;

      if (Rx.startsWith("AT")) {      // Check to see if a command is received from remote
        HC12.println("Remote Command Executed");  // Acknowledge execution

      HC12ReadBuffer = "";  // Empty buffer
    else if(HC12EndTime < SerialEndTime) { // Serial Input
      Rx = SerialReadBuffer;

      if (Rx.startsWith("AT")) {    // Has a command been sent from local computer
        HC12.print(Rx);             // Send local command to remote HC12 before changing settings


      SerialReadBuffer = "";  // Clear SerialReadBuffer
    else { // Dual Input
      DualPrint("Warning: Command Missed");

    if(Rx.startsWith("Mv")) { // Movement Command
      String AnalogMvVal = getValue(Rx, ':', 1);
      int MvOut = AnalogMvVal.toInt();
      if(Rx.startsWith("MvTh")) { // Pitch
        analogWrite(PitchPin, MvOut);
      else if(Rx.startsWith("MvRo")) { // Roll
        analogWrite(RollPin, MvOut);
      else if(Rx.startsWith("MvYa")) { // Yaw
        analogWrite(YawPin, MvOut);
      else if(Rx.startsWith("MvPi")) { // Throttle
        analogWrite(ThrottlePin, MvOut);
      /*else if(Rx.startsWith("MvGi")) { // Gimball
        static int GimballStabRange = 15;
        static int gimMove = 250;
        int gimRt = map(MvOut, 0, 288, -128, 128);
        if(gimRt < GimballStabRange) {
          analogWrite(Gim2, abs(gimRt)); //rotates motor
          digitalWrite(Gim1, LOW);    // set the Pin motorPin1 LOW
          digitalWrite(Gim2, LOW);    // set the Pin motorPin2 LOW
        else if(MvOut > (255 / 2) + GimballStabRange) {
          analogWrite(Gim1, gimRt); //rotates motor
          digitalWrite(Gim2, LOW);    // set the Pin motorPin2 LOW
          delay(gimMove); //waits
          digitalWrite(Gim1, LOW);    // set the Pin motorPin1 LOW
      else {
        DualPrint("Warning: Invalid movement command");

    else if(Rx.startsWith("MD")) {
      static byte flightMode = 0;
      if(Rx.startsWith("MD1")) {
        digitalWrite(ModePin, HIGH);
      } else {
        digitalWrite(ModePin, LOW);

    Rx = "";
    EndFlag = false;


  • ***
  • 170
    • Frickeln und mehr
The input is pwm, so you have to use the cable with 10(not sure) wires.

Gesendet von iPhone mit Tapatalk Pro

The input is pwm, so you have to use the cable with 10(not sure) wires.
That is the wire that I have been using, how do I set the range of values for librepilot to recognise?


  • ***
  • 170
    • Frickeln und mehr
Usually it from zero to 3000 1500 is neutral.

Gesendet von iPhone mit Tapatalk Pro

A typical PWM pulse like comes from an RC receiver is between 1000us and 2000us (1ms and 2ms) and the pulse typically comes every 20ms or so (50 times a second).

Duty cycle and frequency are not usually the concepts that you usually think about with this.  If you did, the frequency would be about 50/sec (50 Hz) and the duty cycle would vary between 1ms/20ms=.05=5% and 2ms/20ms=.10=10%

The easiest way to set up a CC3D for you to connect as you have described is to run the setup wizard and pretend you are connecting a PWM RC receiver.  Get your Arduino/RPi code where you think it should work with 5 channels that you can control somehow, connect the 5 PWM signals to the CC3D PWM inputs (any of the PWM inputs will work and it will auto-detect, but I like to make sure I use the first 5 inputs in an order that is logical to me), and run the LP GCS setup wizard with CC3D connected to USB and Arduino/RPi.

After running the setup wizard, if you look at the GCS Configuration->Input page you will see that each input channel has somewhere around min=1000us, max=2000us, and neutral=1500us.  When you run the transmitter wizard (at the end of setup wizard or it can be run from the Input page) you will be setting these min / neutral / max settings for each channel to more closely match your PWM signal source.

As I recall, one of the issues with doing Arduino/RPi PWM is that there are two ways of doing it and the easy way is not very accurate and it jumps around:
- "bit banging" has the user program read a timer and try to start and stop the PWM pulse at the right time.  This can not be done perfectly accurately because of for instance interrupts happening at the wrong time, and taking the CPU away when the CPU should be stopping the PWM signal.  This makes the PWM signal "jitter".
- using a low level / system level driver to do it accurately.  You should look for someone else who has written a driver for this rather than writing your own.  :)

You really need at least 5 PWM channels because you need a flight mode switch plus the 4 normal motion channels.  Some versions of the GCS will look like they freeze up when doing setup transmitter stick directions / centering / etc if you don't have a Flight Mode Switch configured as that 5th channel.

Thanks TheOtherCliff, exactly the info I was looking for.
Problem solved!