Telemetry Bridge

Bridging the Telemetry from and APM Quadcopter Controller to a Spektrum Telemetry Module

I came across this post on the RCGroups forum describing how to interface into the Spektrum Telemetry Module and send your own telemetry to a Spektrum radio.   I was building my own Quadcopter centered around the APM flight controller, which is based on an Arduino  Mega 2560.  One of the options on this controller is to put a radio transmitter on the Serial 1 port, and a radio receiver on a PC, and the controller can continuously transmit telemetry information like GPS coordinates, Battery Current and Voltage, Altitude, etc., to software running on the PC.  Since the Spektrum Telemetry is and I2C master, and the APM is sending the telemetry out a serial USART, I thought it would be a fairly straight forward task to use an ATMega 328p to read the telemetry stream from the APM, reformat it and send it on to the Spektrum Telemetry module.

While I will also have a fondness for the Arduino, because it got me started work with microcontrollers, I’ve moved away from using the Arduino in my projects because even a small Arduino is big (compared to the atmega chip itself), expensive and the software is somewhat limited.  In this case, the AVR had to respond to several I2C slave addresses (but not others), and the Arduino Wire interface is not well suited for that, but coding directly to the I2C interface made this possible.

The circuit board I made for this project consists of an ATMega 328p running at 16MHz and 5 V (because you need to run at 5 Volts to be in spec for 16MHz), a voltage level shifter to interface the 5 V I2C interface on the AVR to the 3.3 V Spektrum telemetry module, and connectors for the ISP interface, and a Bluetooth module I use to configure the Quadcopter with my phone.  I used parts I had in my inventory, so some of the components, like the 1 Amp 3.3 V regulator and the 1 Amp diodes are not what I would us if I was ordering parts just for this project.

sch

The circuit board is also designed for my home made PCB process (very large VIA pads), and would not be ideal for a professional PCB fabricator.

brd

The raw Eagle files are located on my GitHub site along with the source code.

Board

The serial stream that comes out of the APM is a protocol called Mavlink, and I spent a good bit of time on their site trying to figure out how to make this work.  First, you have to download the Mavlink include files here, and generate them with a Python script.  After running into some problems, I found that the structure  __mavlink_request_data_stream_t in common/mavlink_msg_request_stream.h was incorrect (the order of the fields was wrong).  It should look like:


typedef struct __mavlink_request_data_stream_t
{
uint8_t target_system; ///< The target requested to send the message stream.
uint8_t target_component; ///< The target requested to send the message stream.
uint8_t req_stream_id; ///< The ID of the requested data stream
uint16_t req_message_rate; ///< The requested interval between two messages of this type
uint8_t start_stop; ///< 1 to start sending, 0 to stop sending.
} mavlink_request_data_stream_t;

The other important Mavlink setting it know about when using a uC is at the top of main.c, before the include of mavlink.h:


// Save memory, MAVLINK by default allocates 4 256 byte buffers
#define MAVLINK_COMM_NUM_BUFFERS 1


#include <stdio.h>
#include <stdlib.h>

If you don’t set the number of buffers to 1 before you include mavlink.h, you will waste 768 bytes of SRAM, which the AVR does not have to spare.

A most invaluable tool for debugging both the Serial connections and the I2C I’ve found is a logic analyzer.  I use the Saleae Analyzer, which I am very happy with.  It would have been very difficult to do this without being able to see exactly what was going on the wires.

Quad hooked up to a logic analyzer

Quad hooked up to a logic analyzer

On the Telemetry side of the process, when the unit is powered on, the Telemetry module requests data from the I2C slaves from address 0x00 to 0x7D twice (0,0,1,1,2,2,etc.).  The bridge has to respond to the slave addresses it is going to fulfill (the RCGroups post above provide the list of slave addresses and functions) during this initialization, or that slave address will not be polled.  The AVR cannot answer to addresses 0x00 and 0x01, because they are for internal use of some sort.  The module Spektrum.c has a list of addresses the AVR will respond to, and, since during initialization the addresses are polled in numeric order, it sets the slave address of the AVR to the lowest address, and once that slave address has been called twice, the code resets the slave address to the next value, and so on util it has hit every address in the list.  The AVR responds to the telemetry unit with the 16 byte response packet with 0 values.

uint8_t addresses[]={0x03,0x0A,0x11,0x12,0x16,0x17,0x00};

ISR(TWI_vect)
{
    switch (TW_STATUS)
    {

        case TW_ST_SLA_ACK:
        if(initializing)
        {
            addrCount++;
            if(addrCount==2)
            {
                addrCount=0;
                if(addresses[++addrIdx]==0)
                {
                    TWAR=addresses[0]<<1;
                    initializing=0;
                }
                else
                    TWAR=addresses[addrIdx]<<1;
            }
    }
    else
        TWAMR=0xFF;

After the initialization phase, the telemetry unit will only poll the slaves that responded during initialization, so the address mask, TWMAR, is set to 0xFF, which means the AVR will respond to any slave address.  When any request for data is sent to the AVR after initialization, it looks at the address that is being requested, and sends the correct buffer back to the master.  Another important thing to note is the telemetry unit does not poll the slave addresses until the radio it is bound to is up and ready to receive data, so if the radio is off, the initialization will occur, but no polling of the slave will occur until the raid is turned on.

One final thing to note is that the AVR does not have division natively built into the instruction set, so it is an expensive operation. Since the AVR needs to be available to handled the asynchronous requests from 2 outside feeds, it has to get the calculations done as quickly as possible .  To get around this, a technique called fixed point arithmetic can be used.  This article has an excellent description of the technique.  For example, the following is a division by 10,000,000.

uint16_t divideBy1E7(uint32_t dividend)
{
	return (uint16_t)((((int64_t)dividend * 0xD6E0LU) >> 16) >> 23);
	//return (uint16_t)(dividend / 1E7);
}

In the source code there is a #define constant called TIMEIT which will include code to time the number of clock cycles the AVR takes to compute the GPS buffers (the most mathematically complex part of the code), and display that value in the Power Box 2nd battery capacity (which is normally 0).  I tested the code with the straight-forward division and with fixed point, and compiled with full optimizations (-o3), the fixed point version of the setGPSBuf call took about 46,500 clock cycles while the division averaged 56,500 clock cycles.  I’m not sure a 17.5% improvement was worth the effort of figuring out the fixed-point parameters, but it was interesting.

As I said above, all the code and eagle files are located on GitHub.

The Sensor PCB

This Garduino takes input from various sensors and logs it to an SD card on a Logger Shield.  The moisture sensors and light sensors both generate a frequency of pulses as their output, and there is only 1 timer input on the Arduino Uno that can measure this (pin 5), so I use a 74LS151 Multiplexer to put these signals on pin 5 and use Pins 7 (D0),8 (D1), and 9 (D2) to addresses the various inputs.   The LM335 temperature sensor is an analog device which I can simply plug into the analog pins of the arduino and do an analogRead, but the SD shield uses A4 and A5, which leaves me with 4 free analog inputs.  Although I am only supporting 4 temperature sensors now, I decided to use an 4051 analog multiplexer to put all the temperature signals onto A0, leaving A1-A3 free for future purposes.  Pins 7-9 are also used to set the address on the 4051 as well.  Because the electrolysis that occurs on the probes of the moisture sensors when sending a direct current through the damp soil, the moisture readings will change over time, so it is best to test the soil resistance (the de facto measure of soil moisture) with an alternating signal.  The 556 and 555 supply this alternating signal to the 3 moisture probes.  This is the schematic of the complete circuit board.

Garduino Schematic

The left hand ribbon cable connector leads to another ribbon cable connector soldered onto Logger Shield.  Pins 1 & 2 connect to the  Arduino +5V and GND, pin 3 connects to the timer pin 5 on the Arduino, pins 4, 5 and 6 connect to Arduino pins 7, 8, and 9 for the multiplexer addressing, and pin 7 on the connector leads to the A0 pin on the Arduino pin to read the analog values of the temperature sensors.  The 20 pin ribbon cable on the right is to interface to the sensors, which will be discussed in a later blog entries.

I decided to use the EAGLE software to build my first PCB, which was produced by BatchPCB.  It took about 3 weeks to get the board after ordering it, which was apparently fairly fast, but BatchPCB trades time for cost, and the total cost of the board was about $30; far less than you would pay for other services.

This is a picture of the completed board attached to the Arduino Logger Shield.

PCB And Arudino with the Logger Shield

Somehow 2 connections which where on the Schematic did not make it on the Layout and onto the PCB, but luckily, it was fixable.  I still have a lot to learn about EAGLE.

Fixed mistakes in the PCB design