FPGA project for encoding/decoding multi channel RC servo data

Do you have a question? Post it now! No Registration Necessary

Translate This Thread From English to

Threaded View

I want to start learning FPGA by experimenting with them.  (I do have
experience in digital electronics, microcontrollers and programming. But I
do not have FPGA experince.) What I want to use FPGA for is as follows;

PPM/PCM Decoding:
I want to use FPGA to decode PPM and/or PCM signals from a 8-16 channels RC
(radio control) transmitters. This is equivalent to measuring pulse widths
of 8 pulses in a repeating pattern as shown in the following liks
    * http://www.mh.ttu.ee/risto/rc/electronics/pctorc.htm
    * http://adamone.rchomepage.com/guide1.htm#ppm

I want to be able to measure servo data in 10 bits resolution and send
channel data via an RS232 port similar to one shown in the following links
    * http://www.customelectronics.co.uk/fmspic.htm
    * http://www.webx.dk/rc/sim-serial/sim-serial.htm
    * http://www.veetail.com/FMSInt1.shtml

Driving multi-channel RC servo motors :
I also want to use the FPGA to read in  multi-channel servo motors from a
serial RS232 port and control multi channel (8-16  RC servo motors) similar
to one shown in the following links
    * http://www.seattlerobotics.org/encoder/200106/16csscnt.htm
    * http://www.seetron.com/ssc.htm
    * http://home.planet.nl/~j.havinga/servo/servo.htm

Can you advise me
    * A "low cost" FPGA development board
    * Free or "low cost" software development tools
    * Example FPGA code for above mentioned projects (PPM/PCM decoding and
driving multi-channel RC servo motors)

Thank you.

FPGA Newbee

Re: FPGA project for encoding/decoding multi channel RC servo data

Quoted text here. Click to load it
Quoted text here. Click to load it
Avnet has fairly low cost boards, less than $200 a couple of years ago.
  If you need something even lower cost try a web search.

Xilinx gives away their "Web pack" tools which are quite good.  I'll bet
that Altera does also.  You could also check the Xilinx and Altera web
sites for boards, but the last time I checked (two-three years ago)
Xilinx just pointed you to various 3rd-party vendors.

PPM decoding is easy, PCM decoding will take extensive reverse
engineering, and varies from one manufacturer to the next.  I would
suggest you stick to PPM.

I can't point you to code, but PPM code is _so_ easy that if you can't
figure it out you should probably stay away from FPGA work entirely.  I
suggest you start by encoding a servo pulse, go from there to measuring
a pulse width, then tackle sensing the frame sync in a PPM train.  You
may find that getting the RS-232 to work is the hardest part, but once
you have it, you'll just need to put pieces together.


Tim Wescott
Wescott Design Services
We've slightly trimmed the long signature. Click to see the full one.
Re: FPGA project for encoding/decoding multi channel RC servo data

Quoted text here. Click to load it

I did that. with an Altera FPGA. We wanted
14 bit resolution on the pulse.
The hardware is a bit outdated and too expensive.
An AVR does the stuffing and the communication.
You basically need one counter for the repetition,
20ms, and a set of counters for the pulsewidth.
Try the schematic approach.

Ing.Buero R.Tschaggelar - http://www.ibrtses.com
& commercial newsgroups - http://www.talkto.net

Re: FPGA project for encoding/decoding multi channel RC servo data

Quoted text here. Click to load it

PCM data formats are manufacturer specific and not published anywhere that I
have seen. PPM is easily done as it is relatively standard. I have done it with
a simple piece of code in a microcontroller using a single timer capture input.
I have also done it with RTL for much the same reason that you are interested.

Quoted text here. Click to load it

This is most easily done with a single timer channel on a microcontroller
driving an external 8 bit serial in parallel out shift register and a bit of

Quoted text here. Click to load it

The reason I mentioned using the microcontroller is mostly as a disclaimer, so
nobody would point and laugh at the huge use of resources for so simple of a
problem.  That said, here is a copy of the verilog I wrote a few years ago for
measuring the pulse widths of up to 8 channels, to 12 bit resolution. It was
designed to be a microcontroller peripheral for an 8 bit micro. I have similar
code for the output function if you are interested, email me (after de-munging
the address). This is not in the shiniest verilog style, but here it is:

  *  Inport
  *  This is an RC system interface. Input data is a composite
  *  pulse train.
  *          8.0 mHz input clock rate.
  *          Negative active reset.
  *  The values of the pulse widths may be read as unsigned 12 bit numbers
  *  8 bits at a time via the CPU bus interface.
  *  Written by Bob Harbour 1/97
`timescale 1ns/1ns

module Inport (PulseIn, Data, Busy, Addr, nCS, nWE, nRst, Clk);

input PulseIn;            // Pulse train in
inout [7:0] Data;        // CPU data bus
output Busy;            // Indicates Pulse is in Sync area
input [3:0] Addr;        // CPU address lines
input nCS;            // CPU chip select
input nWE;            // CPU write enable
input nRst;            // reset input
input Clk;            // clock input

wire PulseIn;
wire [7:0] Data;
wire [3:0] Addr;
wire nCS, nWE, nRst, Clk;

// Instantiate CPU interface.
//    nCS  Addr   nWE      register
//     H   XXXX     X         none
//     X   XXXX     L         none
//     L   0000     H         Chan1 Latch read Hi Byte
//     L   0001     H         Chan1 Latch read Lo Byte
//     L   0010     H         Chan2 Latch read Hi Byte
//     L   0011     H         Chan2 Latch read Lo Byte
//     L   0100     H         Chan3 Latch read Hi Byte
//     L   0101     H         Chan3 Latch read Lo Byte
//     L   0110     H         Chan4 Latch read Hi Byte
//     L   0111     H         Chan4 Latch read Lo Byte
//     L   1000     H         Chan5 Latch read Hi Byte
//     L   1001     H         Chan5 Latch read Lo Byte
//     L   1010     H         Chan6 Latch read Hi Byte
//     L   1011     H         Chan6 Latch read Lo Byte
//     L   1100     H         Chan7 Latch read Hi Byte
//     L   1101     H         Chan7 Latch read Lo Byte
//     L   1110     H         Chan8 Latch read Hi Byte
//     L   1111     H         Chan8 Latch read Lo Byte

reg [11:0] Latch1, Latch2, Latch3, Latch4, Latch5, Latch6, Latch7, Latch8;
wire ReadEn;
reg [7:0] readback;

assign ReadEn = ~nCS & nWE;

always @(Latch1 or Latch2 or Latch3 or Latch4 or Latch5 or Latch6 or Latch7
          or Latch8 or Addr)
   case (Addr)
     4'b0000:  readback = ;
     4'b0001:  readback = Latch1[7:0];
     4'b0010:  readback = ;
     4'b0011:  readback = Latch2[7:0];
     4'b0100:  readback = ;
     4'b0101:  readback = Latch3[7:0];
     4'b0110:  readback = ;
     4'b0111:  readback = Latch4[7:0];
     4'b1000:  readback = ;
     4'b1001:  readback = Latch5[7:0];
     4'b1010:  readback = ;
     4'b1011:  readback = Latch6[7:0];
     4'b1100:  readback = ;
     4'b1101:  readback = Latch7[7:0];
     4'b1110:  readback = ;
     4'b1111:  readback = Latch8[7:0];

assign Data = ReadEn ? readback : 8'bz;

// Generate a 500Khz clock period wide pulse
reg [2:0] ClockDiv;
reg DelClockDiv2;
wire [2:0] NxtClockDiv;
wire Clk1Mhz;

assign NxtClockDiv = ClockDiv +1;

always @(posedge Clk or negedge nRst)
if (!nRst)
     ClockDiv <= 3'h0;
     DelClockDiv2 <= 1'b0;
     ClockDiv <= NxtClockDiv;
     DelClockDiv2 <= ClockDiv[2];

assign Clk1Mhz = DelClockDiv2 & ~ClockDiv[2];

// Generate a synchronous version of the signal input.
// Instantiate postitive and negative edge triggered one shots.

reg In, InDel, PulseIn_d1;
wire PosEdge, NegEdge;

always @(posedge Clk or negedge nRst)
if (!nRst)
     PulseIn_d1 <= 1'b0;
     In <= 1'b0;
     InDel <= 1'b0;
     PulseIn_d1 <= PulseIn
     In <= PulseIn_d1;
     InDel <= In;

assign PosEdge = In & ~InDel;
assign NegEdge = ~In & InDel;

// Instantiate a counter to measure the pulse widths.

reg [11:0] SignalCtr;
wire [11:0] SignalCtrNxt;
wire SignalCtrZero, SignalCtrOverFlow;

assign SignalCtrZero = ~|SignalCtr;
assign SignalCtrOverFlow = &SignalCtr;

assign SignalCtrNxt =  PosEdge? 12'h0 : Clk1Mhz ? (SignalCtr+1) : SignalCtr;

always @(posedge Clk or negedge nRst)
   if (!nRst)
     SignalCtr <= 12'h0;
     SignalCtr <= SignalCtrNxt;

// Instantiate the latches to hold the Signal counter value at
// the end of a pulse.

wire [11:0] NxtLatch1, NxtLatch2, NxtLatch3, NxtLatch4;
wire [11:0] NxtLatch5, NxtLatch6, NxtLatch7, NxtLatch8;
wire Store1, Store2, Store3, Store4, Store5, Store6, Store7, Store8;

assign NxtLatch1 = Store1 ? SignalCtr : Latch1;
assign NxtLatch2 = Store2 ? SignalCtr : Latch2;
assign NxtLatch3 = Store3 ? SignalCtr : Latch3;
assign NxtLatch4 = Store4 ? SignalCtr : Latch4;
assign NxtLatch5 = Store5 ? SignalCtr : Latch5;
assign NxtLatch6 = Store6 ? SignalCtr : Latch6;
assign NxtLatch7 = Store7 ? SignalCtr : Latch7;
assign NxtLatch8 = Store8 ? SignalCtr : Latch8;

always @(posedge Clk or negedge nRst)
if (!nRst)
     Latch1 <= 11'h0;
     Latch2 <= 11'h0;
     Latch3 <= 11'h0;
     Latch4 <= 11'h0;
     Latch5 <= 11'h0;
     Latch6 <= 11'h0;
     Latch7 <= 11'h0;
     Latch8 <= 11'h0;
     Latch1 <= NxtLatch1;
     Latch2 <= NxtLatch2;
     Latch3 <= NxtLatch3;
     Latch4 <= NxtLatch4;
     Latch5 <= NxtLatch5;
     Latch6 <= NxtLatch6;
     Latch7 <= NxtLatch7;
     Latch8 <= NxtLatch8;

// Instantiate a 4 bit counter to select which latch stores the signal counter
// value. A 4 bit counter is used for two reasons. First, it allows pulse
// trains with more than 8 pulses to have valid readings for the first 8.
// Second, it simplifies handling the sync pulse.

reg [3:0] Channel;
wire [3:0] NxtChannel;

assign NxtChannel = PosEdge ? (Channel+1) : SignalCtrOverFlow ? 4'h0 : Channel;

always @(posedge Clk or negedge nRst)
if (!nRst)
   Channel = 4'h0;
   Channel = NxtChannel;

// Instantiate the latch store signals
assign Store1 = PosEdge & ~Channel[3] & ~Channel[2] & ~Channel[1] &  Channel[0];
assign Store2 = PosEdge & ~Channel[3] & ~Channel[2] &  Channel[1] & ~Channel[0];
assign Store3 = PosEdge & ~Channel[3] & ~Channel[2] &  Channel[1] &  Channel[0];
assign Store4 = PosEdge & ~Channel[3] &  Channel[2] & ~Channel[1] & ~Channel[0];
assign Store5 = PosEdge & ~Channel[3] &  Channel[2] & ~Channel[1] &  Channel[0];
assign Store6 = PosEdge & ~Channel[3] &  Channel[2] &  Channel[1] & ~Channel[0];
assign Store7 = PosEdge & ~Channel[3] &  Channel[2] &  Channel[1] &  Channel[0];
assign Store8 = PosEdge &  Channel[3] & ~Channel[2] & ~Channel[1] & ~Channel[0];

// Sense the Signal Counter Overflow and clear a Busy flag. Set it on the next
// PosEdge. This circuit is Set Dominant.
reg Busy;
wire NxtBusy;

assign NxtBusy = PosEdge ? 1'b1 : SignalCtrOverFlow ? 1'b0 : Busy;

always @(posedge Clk or negedge nRst)
if (!nRst)
   Busy <= 1'b1;
   Busy <= NxtBusy;


Site Timeline