Schematic & PCB #2

I finally finished the schematic and PCB and will send them to DirtyPCB today.

It is in HAT format ready to be used with the new (when it’s released) Raspberry PI A3.

It supports either a RF4463F30 or a RFM22B (on the back side) connected to SPI2, a MPU9250 IMU and MS5611 baro connected to SPI1 and/or another set of MPU9250 & MS5611 connected to I2C.

On the same I2C there is an ADS1115 for current/voltage sensing and 2 extra ADC pins broken out on a separate controller.

PIGPIO is connected to 6 PWM pins – 4 for motors and 2 for a gimbal.

In the end I also had sufficient space to include 3 NeoPixels (or WS2812b) RGB LEDs to show various status info. They are all driven by only one GPIO using a serial-like protocol and I think I can make this work with the PIGPIO wave functions.

 

In a future revision I might add a buzzer for alerting comm errors – like link lost – and expose 2 more PWM outputs.

 

First I have to check if the PCB will be manufactured ok.

 

Some screenshots:

 Update July 14th: 

I made some tweaks to the SCH and PCB to include a diode for the leds, according to Adafruit.

In the previous version I was powering the LEDs from 3.3V which is below their minimum voltage. Datasheet here.

To fix this I routed the led VCC to 5V but through a diode.
The WS2812b leds expect the high input voltage to be within 0.7 VCC. With a 5V VCC this results in a 3.5V high level. The raspberry pi provides a reliable 3.3V on the GPIO and there was the risk of the leds failing to understand the PI. There are 2 solutions – a level converter to increase the high level of the PI to 5v or lowering the VCC of the leds a bit. I choose the second ’cause it’s way simpler. A diode drops the voltage from 5V to ~4.3V which in turn drops the expected high level to a convenient ~3V.

Two other changes that I did are:

  • Fixed some signal traces that were running parallel on the top and bottom sides – like the I2C and some PWM outputs. Not sure how important it is but just to avoid any nasty coupling
  • Broken out the I2C to a pin header to eventually connect a PX4Flow.

The board is in production now and I really hope it gets here by Friday.

 

In the meantime I’m investigating a PPM input solution in case I want to use a standard RC system with this board.

 

Schematic & PCB

The Silk FC schematic is finished and the PCB is 70% done.

Here are some screenshots:

 

There is a bit of wiring acrobatics with the PWM connector as I couldn’t find an eagle 6×3 header so I improvised one from 3 2×3 headers… Ugly but it works.

As soon as they are done I will put them on github.

The board is 2 layers only and has the specs from my last post.

I intend to add a footprint for a RFM22B as well as the RF4463F30 so I can choose which to use, in case one of them has issues.

The board should work in both single-imu-baro more and also in dual-imu-baro.

I got 2 MPU9250 and MS5611 from my previous Drotek 10 DOF boards with a bit of hot air.

 

I’m really curious how it will end up as this is my first SMD board and especially my first QFN package. The MPU is really really tiny – like 2 square millimeters.

 

 

Custom FC

In the past weeks I made progress on the software side. I have the node editor working again and the basic RC also.

To avoid getting bored I switched again to HW and started designing the FC PCB. It will include:

  • 2 x MPU9250 and MS5611 for redundancy and better noise filtering. One of the 9250 will use SPI and the other I2C, to have bus redundancy
  • 1 RF4463F30 transceiver.
  • 1 ADS1115 for ADC conversion
  • Several status LEDs

 

PWM will be handled by PIGPIO which works very nicely. Better than a PCA9685 that only allows 1 frequency for all the outputs.

It will have a HAT layout, without the EEPROM (still not very clear what good is it).

I already started designing it in Eagle (the free version) and will do the PCB using DirtyPCB.

 

 

 

RF Modules

I found and bought the RF modules I needed for the GS-brain comms: the RF4463F30

They have a 1W PA, the receiver has -122dBm sensitivity at low data rates and around -90-96dBm at 1Mbps max rate.

They are pretty small and light – under 2-3 grams I think.

IMG_0265

I managed to find a lot of documentation from SI and the RF chip works pretty differently than the RFM22B. It’s not programmed using registers but with SPI commands. You basically create API calls in a uint8_t buffer and send it through SPI.

The FIFO is only 64 bytes but it does have a CRC and a custom 4 byte header to pack various things – like a request id.

So far it seems very nice, I will try to make it work during the weekend.

 

The plan I have is to use the wifi only for video data – tx only, and the RF4463 for bidirectional commands (GS->Brain and back) and video packet confirmations.

This should allow me to use better the limited wifi bandwidth by using uni-directional comms – so the back packets don’t keep the channel busy – and also have a rock solid link for control.

I did some tests with the RFM22B at 0dBm and it can easily go through 4-5 walls and 20 meters  inside my house. With 20 or even 30dBm I should be able to have a solid 10Km link line of sight.

 

Definition Language

aaand it’s done!

The definition language for specifying node configuration is finished – at least in principle.

It ended up being a stand-alone library called def_lang (uninspired, I know)

Basically I can now define things like this:

namespace silk
{
  alias mass_t = ufloat;

  struct UAV_Config_Base
  {
    string name : [ui_name = "Name"];
    mass_t mass : [ui_name = "Mass (Kg)"];
    ufloat moment_of_inertia : [ui_name = "Moment Of Inertia"];
  };

  struct Multirotor_Config : public UAV_Config_Base
  {
    struct Motor
    {
      math::vec3f position;
      direction_t thrust_vector = {0.f, 0.f, 1.f};
      bool clockwise = false;
    };

    ufloat radius = 1.f; //m
    ufloat height = 1.f; //m
    ufloat motor_z_torque = 0.f; //Nm
    ufloat motor_thrust = 1.f; //N
    ufloat motor_acceleration = 10.f; //N/s
    ufloat motor_deceleration = 10.f; //N/s
    vector<Motor> motors;
  };
}

And use this definition in 2 possible ways:

    • Dynamically, through a type system:
ts::ast::Builder builder;

auto parse_result = builder.parse_file("definition_file.def");
if (parse_result != ts::success)
{
  std::cerr << parse_result.error().what();
  return;
}

//walk and display the AST
std::cout << builder.get_ast_root_node().to_string(0, true) << std::endl;

ts::Type_System ts;
ts.populate_builtin_types(); //this adds standard built-in types like float and double

auto compile_result = builder.compile(ts); //populate the typesystem from the AST
if (compile_result != ts::success)
{
  std::cerr << compile_result.error().what();
  return;
}

//now the type system is fully populated and all the types is it are instantiable
std::shared_ptr<ts::IStruct_Type> base_type = ts.find_specialized_symbol_by_path<ts::IStruct_Type>("silk::UAV_Config_Base");
std::shared_ptr<ts::IStruct_Type> type = ts.find_specialized_symbol_by_path<ts::IStruct_Type>("silk::Multirotor_Config");

assert(base_type->is_base_of(*type)); //inheritance is respected

std::shared_ptr<ts::IStruct_Value> multirotor_config_value = type->create_specialized_value(); //create a value of type Multirotor_Config
auto construct_result = multirotor_config_value->construct(); //construct it. This struct is default constructible. An alternative is to initialize it using an initializer list

//let's get (select) some members
//the full dot sintax is supported when selecting so things like motors[0].position works
std::shared_ptr<ts::IString_Value> name_value = multirotor_config_value->select_specialized<ts::IString_Value>("name");

//all setters might fail so I need to check the return value
auto result = name_value->set_value("silkopter");
TS_ASSERT(result == ts::success);

//values are serializable as well to an intermediary tree
auto serialize_result = multirotor_config_value->serialize();
TS_ASSERT(serialize_result == ts::success);

//the intermediary tree can be serialized to json or binary
std::string json = ts::serialization::to_json(serialize_result.payload(), true);

//a more complicated example:
//get the motors vector
std::shared_ptr<ts::IVector_Value> motors_vector_value = multirotor_config_value->select_specialized<ts::IVector_Value>("motors");

//create a new motor
std::shared_ptr<ts::IStruct_Value> motor_value = ts.create_value<ts::IStruct_Type>("silk::Multirotor_Config::Motor");

//change it's position
motor_value->select_specialized<ts::IVec3f_Value>("position")->set_value(ts::vec3f(0, 1, 0));

//add it to the vector
auto result = motors_vector_value->insert_value(0, motor_value);
assert(result == ts::success);

Basically  it’s a full type system implementation similar to the C++ one with type safety, templated types, aliases, values, casting, inheritance etc etc. But on top of the standard C++ features it also allows custom attributes on members and types (like ui_name for example), it supports reflection so everything can be enumerated and it’s serializable. This dynamic one will be used in the GS to generate UI for all the nodes. Like this the GS can work with new nodes without me having to do custom UI for each node I intend to add (or everytime I change the node config to add/remove a parameter, or change a type etc)

 

  • Statically through generated C++ code

From the definition above I can also generate C++ code for all the structs and work with them through getters and setters. Each type in the type system can be mapped to a native C++ type. So in this example, the above structure would be usable like this from generated code:

assert(std::is_base_of<silk::UAV_Config_Base, silk::Multirotor_Config>::value); //inheritance is respected

//create a value of type Multirotor_Config. This also default constructs it
silk::Multirotor_Config multirotor_config_value;

//all members have setters and getters generated for them
std::string name_value = multirotor_config_value.get_name();
multirotor_config.set_value("silkopter");

//values are serializable as well to an intermediary tree
auto serialize_result = silk::serialize(multirotor_config_value); //serialization methods are generated as well
TS_ASSERT(serialize_result == ts::success);

//the intermediary tree can be serialized to json or binary
std::string json = ts::serialization::to_json(serialize_result.payload(), true);

//a more complicated example:
//get the motors vector
std::vector<silk::Multirotor_Config::Motor> motors_vector_value = multirotor_config_value.get_motors();

//create a new motor
silk::Multirotor_Config::Motor motor_value;

//change it's position
motor_value.set_position(ts::vec3f(0, 1, 0));

//add it to the vector
motors_vector_value.insert(motors_vector_value.begin(), motor_value);

//set it back in the config
multirotor_config_value.set_motors(motors_vector_value);

The generated code is simpler to work with partly because it’s a lower level of abstraction (I use the build-in C++ typesystem and work in value space) but also because it’s familiar.

The interested thing is that the generated code keeps inside as a string the definition file contents that it was generated from. This is useful to implement the following flow:

  1. I write the definition file for the bran and all its nodes
  2. I generate the c++ code for the brain. Now all the nodes can use C++ structs for the parameters and config structs. Simple and type-safe as it’s impossible to access non-existing fields or use them with the wrong type. It’s standard, simple C++ structs
  3. When the brain connects to the GS, it sends the def file it was generated from
  4. The GS instantiates a dynamic Typesystem and parses the def file from the brain, so now it will understand and be able to (de)serialize the data it will receive from the brain – like node configs, etc

If I want to add a new node, I add in the def, regenerate the code and use the resulting config struct in the new node. The GS will automatically know how to handle the new node as it will receive the def for it next time it connects to the brain

 

Easy peasy

A new UAV design

Since my quad decided to run away from me I have to build a new one. I spent more than one year designing the previous one and it went through 4 major iterations which ended up with a light, strong and easy to build quad. But not a perfect one though.

There are 2 issues that bother me with my quad and all quads in general:

  1. They are hard to carry around. Their propellers are sticking out, their arms are large and they are sensitive.
  2. The propellers are exposed when flying which is dangerous for everyone (including for the the propellers themselves). I could build prop guards but they are heavy and make the quad even bigger

Lately I’ve been thinking how to improve on this for the new quad. The first issue can be improved by making a smaller or a foldable one quad. I tried in the past to make a foldable tricopter out of balsa (before I had a 3d printer) but it didn’t work out that well. It was either too flimsy or too heavy. A smaller quad is attractive but it reduces flight time significantly since not all components can be scaled linearly (such as the rpi).

While looking for alternative designs I came across this – a single ducted prop UAV. It’s an interesting idea to have all axes controlled with the servo elerons but I think it’s a bit too radical for me. I need something more practical – and I think I got it:

A ducted coaxial propeller UAV, with control surfaces for pitch/roll. Yaw is done using differential thrust. I’ll call it CUAV – Coaxial UAV

So, some specs:

  • 2x 10×4.5 inches CF propeller
  • 2x 2209 980kv motors. They max out at ~900 grams of thrust for a total of 1800.
  • 2x 20 amp ESC. Not decided which one, not important for now
  • 2x digital 9g servos with metal gears for the control surfaces
  • 6mm 3k CF tubes for the structure with ABS 3D printed parts
  • Raspberry PI 3 brain, wifi video link, RFM22B telemetry and control link

I made a schematic as well:

IMG_0074.JPG

The motors are arranged with the props in the center, pointing towards one another to protect them. The ‘chassis’ is made from the motor mounts – 2 identical pieces (one for each motor) with 4 CF arms, connected on the outside.

The weight should be around 640 grams, with a diameter of ~30cm and a height of ~14cm.

The advantage of this design is that it’s pretty easy to carry – the sensitive parts are protected by the duct, and the props should not hit anything even in the case of a crash. Also I think it will be more efficient that the last quad due to having only 2 motors and way bigger props (10 vs 7 inches)

The challenges will be:

  1. To design the chassis to be stiff and light.
  2. To arrange the components – especially the RPI – so that it doesn’t block the airflow. I think the battery and GPS will be on top and the RPI + camera on one side of the duct.
  3. To write a new motor mixer that controls the servos depending on the desired torque. The trick here is that the torque depends on both the angle of the control surface but also the motor thrust (how much air flows on top of it)

Since I first have to make a new GS, this will have to wait a while. Hopefully by the time I start putting it together the RPI Foundation will have released a new camera module and a RPI 3 Model A…

 

 

 

Node Definition

As I mentioned in my previous post I’m designing a domain specific language to define the node properties. So far it looks like this:

import "UAV_Config.def"

namespace silk
{

struct Multirotor_Config : public UAV_Config
{
    struct Motor
    {
        vec3f position = {0, 0, 0};
        bool clockwise = false;
    };

    string name;
    float mass = 1.f : [ ui_name = "Mass (Kg)", min = 0.f ];
    float height = 0.5f : [ ui_name = "Height (m)", min = 0.f ];
    float radius = 0.5f : [ ui_name = "Radius (m)", min = 0.f ];
    float motor_thrust = 1.f : [ ui_name = "Motor Thrust (N)", min = 0.f ];
    float motor_z_thrust = 1.f : [ ui_name = "Motor Z Torque (Nm)", min = 0.f ];
    float motor_acceleration = 10.f : [ ui_name = "Motor Acceleration (N/s)", min = 0.f ];
    float motor_deceleration = 10.f : [ ui_name = "Motor Deceleration (N/s)", min = 0.f ];
    vector<Motor> motors : [ ui_name = "Motors" ];
}

}

It looks a lot like C++ but allows some extra attributes on types and members, like ui_name to have a nicer name displayed in the editor, a min/max value for numeric types, the number of decimals for floating points etc.

The grammar is done with flex/bison and it will actually be a full typesystem that I will use to generate serialization/deserialization code offline and to create UIs online in the GS.

The language will support:

  • namespaces, needed to be able to generate the code in the correct namespace in C++ and to separate the types properly
  • basic types, like ints, floats but also uint8_t and friends
  • structs and classes together with inheritance.
  • templated types like vectors.
  • math types like vec2/3/4, quat and matrices
  • initializer lists for vec3/4 etc
  • custom attributes
  • imports

The compiler will take a file and output a typesystem that contains all the types defined in the file. These types will have reflection so they can be traversed. From this typesystem I can generate the C++ code for the brain – the serialization/deserialization to binary and json – and I can generate UI widgets in the GS as well.

I’m still struggling with the grammar definition but over this weekend I will get it done.

I have to admit – doing a domain specific language and a typesystem is actually very satisfying and challenging. I wish C++ would help a bit more with common things like reflection and such. I even considered moving everything to rust or go but then I’ll have another problem – QT and other library bindings…

 

Node properties

One of the nice things about silkopter is that is very easy to add new types of nodes to the system.

A node can be anything from a sensor to a low pass filter to a PWM generator like PIGPIO. All nodes have the following properties:

  • Zero or more input streams. Each stream has a type and a rate
  • Zero or more output streams. Same as the inputs, they have a type and rate
  • An Init_Params struct that decides the initialization params of the node. Once the node is created this cannot be changed. The usual params that go in this struct is the rate (process frequency), number of channels for a PWM sink etc.
  • A Config struct that holds changable configuration. This can be changed at any time and contains PID settings for example

The Init_Params and the Config structs are custom, per node. They have to be serializable to json for loading/saving, serializable to binary for comms (the json serialization can be used here) and they need to be editable in an UI for the Ground Station

Silkopter defines the Init_Params and Comms in a json file – one per node – and then generates the actual C++ structs and serialization code from this json description. The UI for editing is dynamic, based on the serialized json of the structs.

So for example the load flow is this:

  • the settings.json file is loaded and converted to a json structure using the rapidjson library
  • the list of nodes is iterated and nodes are created from it
  • each node has its init method called with the init_params json passed to it
  • the init method deserializes the init_params json using generated code into a Init_Params C++ struct

The save flow is the inverse of the one above.

Sending the node Init_Params and the Config to the GS and editing it:

  • The get_init_params (or get_config) is called for a node. This serializes the internal Init_Params or Config to json using the generated code
  • The json stringified using rapidjson and sent to the GS
  • The GS parses the string using rapidjson back into a json data structure
  • A QT item model is used over this json to populate a tree view with the data
  • When data is changed by QT as a response to user interaction (typing a new value for example) the json changes. As a result of this its serialized back to string, sent to the Brain which then calls set_config with the json.

So in order to add a new node, 3 steps have to be done – all of them in the brain. The GS is unchanged:

  1. Create the node C++ class with all the processing needed
  2. Create a json description of the Init_Params and Config and generate the code for them using autojsoncxx library
[
    {
        "definition" : true,
        "namespace" : "sz::ADC_Ammeter",
        "name": "Init_Params",
        "members":
        [
            ["uint32_t", "rate", {"required": true, "json_key" : "Rate (Hz)", "default" : 0}]
        ]
    },
    {
        "definition" : true,
        "namespace" : "sz::ADC_Ammeter",
        "name": "Config",
        "members":
        [
            ["float", "scale", {"required": false, "json_key" : "Scale", "default" : 1}],
            ["float", "bias", {"required": false, "json_key" : "Bias", "default" : 0}]
        ]
    }
]

3. Register the new node in the UAV node factory:

m_node_factory.add<ADC_Ammeter>("ADC Ammeter", *this);

 

The big advantage of having the serialization code generated is that it’s impossible to make mistakes.

The nice thing about having the UI generated is that the GS doesn’t have to change every time a new node is added or a new property is added/removed from an existing node config or init params.

 

So in the new GS I intend to keep this but I really want to get rid of the json definition file and replace it with a domain specific language. Main reason is that the json is not as expressive as I want and doesn’t support all the attributes I need – like ranges for the values.

I already started working on a flex/bison grammar for this and a library that will handle the reflection.

More details and a working example soon.

New GS

In the past week I started working on the new Ground Station. The problems I’m trying to fix are:

  1. I want a dedicated GS, not a laptop. For this purpose I’m trying a raspberry pi with a 7 inch display (the official RPI touchscreen display), together with a custom case, 2 RC sticks and various buttons. The reason is that a laptop is unwieldy, not bright enough in direct sunlight, tends to get busy with other tasks in the worst moments, doesn’t have a touchscreen (at least my current laptop doesn’t)
  2. I want a nicer interface that focuses on the main purpose of the GS – flying. The current one focuses more on editing the nodes of the quad.

So I started a new project, the GS2 using QT’s QML and a touch driven UI. So far so good, here are some screenshots:

 

The resolution is 800×480, a bit small TBH but enough for now. The display size is 7″.

So far the experience in QML is great, it takes very little time to build a nice, functional UI. It does require to build proxies for anything that has to be exposed from the C++ side to qml but that’s a good thing as it separates the UI from the logic.

The main things that I still have to figure out are:

  1. How to create a property sheet in QML. This is used to show the init params and configuration for nodes. This UI is generated in the current GS so there is no need to write custom UI for every node but just a description of the parameters in a json file.
  2. How to write the node editor in QML. Screen real-estate is limited and the node structure can get complex. One possible solution is to allow node groups. Basically take the IMU and low pass filters ang build a meta-node – a Filtered IMU – and treat that as a new node in the UI. Or group all the Navio sensors together in a new Navio Node and avoid all the current clutter. This is an interesting feature that deserves a few weekends.

 

The physical part of the GS – the case, sticks, buttons are still to be designed but I’m sure I can come up with something in a few weekends. The main parts will be:

  • A raspberry pi to drive the display and run the actual GS
  • An AVR to read the sticks, battery voltage and buttons. Could be replaced with another multichannel ADC and the RPI GPIO
  • A few analog sticks. I’m thinking to salvage the ones in my TH9x remote or buy a few of these or these
  • A RFM22B and 2 TL-WN722 wifi cards for the comms. This will be in a detachable module in the GS, connected by a usb cable so I can mount it higher or on top of my car.
  • LEDs, piezo speaker etc

I already have the rpi + display and the first version of the GS working, check out the video here:


All in all – a few months of work. I hope I don’t get demotivated.

 

 

Comms, revisited

After the latest incident and the loss of my quad it became clear that I need reliability. So far I focused on physical reliability – if the quad is sturdy enough to take a light crash, if it’s well balanced, etc. But I kind of ignored the other 2 aspects – software and comms. Well – not ignored, more like I hoped for the best.

So the next issues to fix are:

  1. Solid comms for the commands (RC) and telemetry. The RFM22B is the perfect candidate for this:
    1. It’s lightweight and small.
    2. Very sensitive receiver (-104dbm @125Kbps)
    3. Powerful TX amp @20dbm
    4. 64byte FIFO so I can drive it directly from the raspberry pi
    5. SPI interface, which the RPI has enough
    6. Good penetration due to the low frequency (433Mhz)

With this setup I should have 5-10Km range, way more than the WIFI video like allows.
The issue will be the comms protocol as the device is half-duplex and the bandwidth is not that much. I made a quick calculation and it should take around 5ms to send an entire 64byte message. With a simple protocol where the GS is the master and the quad the slave, I can allocate 5ms slots to each in turn. This will give me a symmetric 7Kbyte bandwidth, -protocol losses lets say a 5Kbyte bandwidth per second. Should be enough for control and telemetry data.

2. Solid software, especially the GS. I intend to get a RPI3 and display and build a dedicated GS to replace my laptop. For now I will keep my PS4 controller but in the future I will aim to build an entire RC system with integrated touchscreen around the RPI.

I want to run the RPI without an X server and for this I will need a QT version that supports this. Don’t know if it’s possible though.
If this fails, I’ll stop using QT and do all the rendering and UI using something else but this will be quite painful… I’m used to QT and despite all it’s quirks it’s very powerful when doing quick UI work.

 

What I do know that works in the current setup is:

  1. The RPI2 is powerful enough to handle a quad with WIFI video streaming. It was using ~16-20% CPU at any moment with around 5 threads so no issues here
  2. The case I designed is very sturdy. It’s a combination of 10mm carbon fiber tubes for the arms and ABS plastic for everything else.
  3. Flight time with the current weight of 650grams is around 20-25 min with a 3000mAh multistar LiHV battery. Enough time to enjoy the landscape
  4. The motors + ESC pair are a nice match. RcTimer 1806 1450Kv and RTFQ 4-in-1 12A ESC seem to handle the load nicely. A bit underpowered due to the props – 7×2.4inches but I plan to move to 8×4.5 for the next quad.

 

So back to the drawing board for a while.