SBUS_AXI_UART_Driver - jofranco/multi-rotor-on-FPGA GitHub Wiki
Overview
Modern drone systems rely on high throughput and low latency for precision control. The old methods of interfacing with RC controllers (PWM) does not satisfy this requirement when the number of channels needing to be controlled starts to grow. To correct this issue, the introduction of a serial interface allows many channels (in our case 18 in total) to be transmitted and received on a single signal wire (as opposed to one wire per channel with PWM). This not only drastically reduces the number of physical connections to the hardware, but also increases throughput due to the nature of the interface. For our system we chose to use the FrSky open source RC system. There are many receivers available, we had a X8R on hand so we decided to use that for this project. Smaller equally capable receivers that are compatible with the FrSky system are also available and will also work with this driver (so long as SBUS is utilized). The FrSky X8R receiver utilizes a single wire serial interface leveraging the SBUS signal protocol. While the SBUS protocol does leverage some custom implementation, this interface largely mimics standard UART protocol.
Interface
This IP core utilizes a single Digital GPIO interface pin on the PYNQ-Z1 board making hardware integration simple and quick. The IP core receives a single volatile int vector containing the UART byte data read from the Xilinx UART16550 IP core receive buffer and outputs a uint8_t vector containing the complete SBUS message from the X8R receiver. Because this is a serial interface and we must use a UART16550 to interface with the hardware, some configuration is required from the driver. The SBUS protocol mimics traditional UART with the byte framing and utilizes a 25 byte message protocol with a start and stop byte for synchronization. The data rate of SBUS is non-standard from most UARTs in that it utilizes a 100k BAUD rate (100k bits per second, 10us bit width) and sends a full message (25 bytes) every 9ms. This translates to an overall data rate of 22.222k bits/s.
To get started utilizing the SBUS protocol take a look at About AXI UART16550 document referenced under Interface Protocols. We will be using the register definitions in this document to configure the UART16550 for our SBUS interface and reading the data from the UART16550 receive buffer FIFO. The details of the required registers we are interfacing with are listed below:
- LINE_CNTRL_REG (0x100C) - Primary control register, enables BAUD rate configuration and data exchange format configuration
- DIV_LATCH_LSB (0x1000) - MSB of Divisor latch value, sets the divide rate for BAUD rate generation
- DIV_LATCH_MSB (0x1004) - LSB of Divisor latch value, sets the divide rate for BAUD rate generation
- FIFO_CNTRL_REG (0x1008) - FIFO control register, enables TX/RX FIFO and allows FIFO resets
- SCRATCH_REG (0x101C) - General purpose R/W register, used in our design as a "Who am I" for configuration validation
- LINE_STATUS_REG (0x1014) - RX/TX general status, provides FIFO data ready and FIFO overflow status
- RX_BUF_REG (0x1000) - RX FIFO register, holds received data bytes from X8R receiver
The registers listed above are necessary to configure the Xilinx UART16550 core for our planned operation. UART serial interfaces follow a semi-standard protocol and we are only accountable for configuring the appropriate BAUD rate, data exchange format, and any features we would like to enable like FIFOs. For our interface we will be decoding what is known as SBUS, which is very similar to the Futaba RC protocol. SBUS utilizes a standard UART format where the transmitter, which in our case is the X8R receiver for our system, "floats low" to conserve power. This is known as a logic high return to zero configuration. A screen capture of a single SBUS packet is shown below for reference.
The UART16550 expects that we will be giving it a logic high return to one signal however, this is typical of all UART systems where the signal floats high during dead periods. To overcome this difference, we must then use an inverter to match the expected inputs. The FPGA makes quick work of this having plenty of gates to spare, so we use a Vector Logic core and set the type to "not" with a bit width of one. This then gives us the expected UART signaling into our UART16550 core and we can then begin capturing data.
The AXI_UART_Driver IP core has two master AXI interfaces (m_axi), one that communicates directly with the UART16550 and one that writes data out to the RC_Receiver core that will decode the SBUS data. The "uart_bus" port that communicates with the UART16550 is of type "volatile int" with register size 4k (0x1000) telling the hardware these values will be modified externally and we are allowing ourselves a buffer for the input. The data that is captured is then sent out over the second port "SBUS_data" which is of type "uint8_t" and again is size 4k. We are parsing the data bits in this driver so we only need 8 bits to represent each byte of data here.
Function
For the SBUS protocol, each individual packet contains 25 bytes of data with the first being a START_BYTE = 0X0F and the last being a STOP_BYTE = 0x00. This allows us to easily frame each packet and synchronize our driver for capturing the data. Each packet contains 200 bits at a BAUD rate of 100k bits/s, this equates to a packet length of 3 ms for all 25 bytes of data. The X8R receiver can be configured to send more or less channels with a few speed options, for our setup we are sending all digital channels (18 total) over serial and outputting 8 analog channels on the receiver pins at a fast rate of 9 ms. This means a packet of data will arrive every 9 ms with a 6 ms dead period between each message. A screen capture of this is shown below for reference.
We can utilize this architecture to synchronize the UART driver IP core and ensure that we do not accidently start capturing data on a 0x0F that may appear in the middle of a packet. While this typically shouldn't occur with a UART, it does in our case because of the nature of the RC channels and how they are encoded so we must deal with it. The SBUS packet contains 25 bytes of data as mentioned previously, and we are interested in parsing that data from the packet so that we may send that to the RC_Receiver core. To do this the configuration of the UART16550 is key. For our implementation, the UART interface is configured as follows:
- BAUD rate: 100k
- Data bits: 8
- Stop bits: 2
- Parity: Even
Each individual byte then contains 12 bits based on the above configuration, and will look like the following:
start bit | bit 0 | bit 1 | bit 2 | bit 3 | bit 4 | bit 5 | bit 6 | bit 7 | parity bit | stop bit | stop bit
The UART16550 will capture the 8 data bits and we can then read each byte out of the receive buffer/FIFO. Once we have all 25 bytes we push them forward and prepare for capturing the next packet, they just keep coming like we want!
Additional Notes
All raw input channels from the X8R range from 172 to 1811 in decimal value. These ranges represent both positive and negative stick values and will be scaled at a later stage to be more usable. We clip the raw input data to be contained between 200 and 1800 to make things easier, and this gives us a nice mid stick value of ~1000 in decimal for processing.