Introduction: Embedded Universal Interface Board - USB/Bluetooth/WIFI Control

About: I am a professional embedded and electronic engineer with a degree in cybernetics and a masters in embedded as well as over 15 years in the industry. I have a keen interest in electronics, gaming, green energy…

I often find I create libraries for new embedded modules from scratch based on the device datasheet. In generating the library I find I get stuck in a cycle of code, compile, program and test when ensuring things work and are bug free. Often the compile and program times can be much longer then the time it takes to edit the code and so a way to cut out these steps when developing would be very handy.

I also often find I want to interface an embedded module with a PC. If the module doesn't specifically have a USB connection which is often the case then you generally have to buy an overpriced USB converter that will do a single job such as just SPI or just I2C.

It is for these reasons I decided to create the universal interface board. Its designed to allow for easy PC based communications with embedded modules.

The embedded interface features of the board I settled on include.

  • Digital I/O
  • I2C
  • SPI
  • UART
  • PWM
  • Servo Motor
  • ADC input
  • DAC output

All of which can be used completely independently.

The interface board can be controlled via a USB connection to the PC, but also has optional WIFI or Bluetooth module connections to allow the board to be used remotely or in an IoT type scenario.

By using standard 2.54mm pitch SIL headers it is possible to directly connect female dupont cables between the board and the embedded module allowing for rapid, reliable and solder free connections.

I also thought about adding things like CAN, LIN, H-bridge etc but these can maybe come later with a v2 revision.

Step 1: Designing the PCB

When designing the PCB I like to try and keep things as simple as possible. When you're going to be building boards by hand it's important to only add components when they do a specific purpose and use as many internal features of the microcontroller as possible.

Looking at my preferred electronics supplier I found a chip I was comfortable with that had the features I was looking for and was a reasonable cost. The chip I landed on was the PIC18F24K50.

With the available 23 I/O pins this allowed me these features

  • Digtal I/O
  • I2C
  • SPI
  • UART
  • PWM x 2
  • Servo Motor x 6
  • ADC input x 3
  • DAC output x 1
  • I/O driven from 5V or 3V3
  • Status LED

One drawback of the IC I chose is it only has one UART peripheral and so using the Bluetooth or Wifi control method will stop you being able to use the UART connection.

Shown in the images above are the finished schematic and PCB.

Step 2: Designing the Protocol

The first step in designing the protocol is deciding what specifically you will need the board to be able to do. Break things up adds a better level of control whereas combing things together simplifies the interface and reduces comms traffic between the board and the PC. It's a balancing game and hard to perfect.

For each function of the board you should indicate any parameters and returns. For example a function to read an ADC input might have a parameter to specify which input to sample and a return value containing the result.

In my design here is the list of functions I wanted to include:

  • Digital I/O
    • SetPin (PinNumber, State)
    • State = GetPin (PinNumber)
  • SPI
    • Initialise (SPI Mode)
    • DataIn = Transfer (DataOut)
    • ControlChipSelect (Channel, State)
    • SetPrescaler (Rate)
  • I2C
    • Initialise ()
    • Start ()
    • Restart ()
    • Stop ()
    • SlaveAck = Send (DataOut)
    • DataIn = Receive (Last)
  • UART
    • Initialise()
    • TX Byte (DataOut)
    • BytesAvailable = RX Count ()
    • DataIn = RX Byte ()
    • SetBaud (Baud)

  • PWM
    • Enable (Channel)
    • Disable (Channel)
    • SetFrequency (Channel, Frequency)
    • GetMaxDuty (Duty)
    • SetDuty (Duty)
  • Servo
    • Enable (Channel)
    • Disable (Channel)
    • SetPosition (Channel, Position)
  • ADC
    • ADCsample = Sample (Channel)
  • DAC
    • Enable
    • Disable
    • SetOutput (Voltage)
  • WIFI
    • SetSSID (SSID)
    • Set Password (Password)
    • Status = CheckConnectionStatus ()
    • IP = GetIPAddress ()

Parameters are shown in the brackets and returns are shown before the equals symbol.

Before I start coding I assign each function an command code starting from 128 (binary 0b10000000) and working upwards. I document the protocol fully to ensure that once my head is in the code I have a nice document to refer back to. The full protocol document for this project is attached and includes incoming command codes and bit widths.

Step 3: Designing the Firmware

Once the protocol is established it's then a case of implementing the functionality on the hardware.

I adopt a simple state machine type approach when developing slave systems to try and maximise the potential command and data throughput while keeping the firmware simple to understand and debug. A more advanced system such as Modbus could be used instead if you need better interaction with other connected devices but this adds overhead which will slow things down.

The state machine consists of three states:

1) Waiting for commands

2) Receiving parameters

3) Reply

The three states interact as follows:

1) We go through the incoming bytes in the buffer until we have a byte that has the most significant bit set. Once we receive such a byte we check it against a list of known commands. If we find a match then we assign the number of parameter bytes and return bytes to match the protocol. If there are no parameter bytes then we can perform the command here and either skip to state 3 or restart state 1. If there are parameter bytes then we move to state 2.

2) We go through the incoming bytes saving them until we have stored all the parameters. Once we have all the parameters we perform the command. If there are return bytes then we move to stage 3. If there are no return bytes to send then we return to stage 1.

3) We go through the incoming bytes and for each byte we overwrite the echo byte with a valid return byte. Once we have sent all the return bytes we return to stage 1.

I used Flowcode to design the firmware as it nicely demonstrates visually what I am doing. The same thing could be done equally well in Arduino or other embedded programming languages.

The first step is to establish communications with the PC. To do this the micro needs to be configured to run at the right speed and we have to add code to drive the USB and UART peripherals. In Flowcode this is as easy as dragging into the project a USB Serial component and a UART component from the Comms component menu.

We add an RX interrupt and buffer to catch incoming commands on the UART and we regularly poll the USB. We can then at our leisure process the buffer.

The Flowcode project and generated C code are attached.

Step 4: Interfacing Via Flowcode

The Flowcode simulation is very powerful and allows us to create a component to talk to the board. In creating the component we can now simply drag the component into our project and instantly have the board functions available. As an added bonus any existing component that has a SPI, I2C or UART peripheral can be used in the simulation and the comms data can be piped to the Interface Board via an Injector component. The attached images show a simple program to print a message to the display. The comms data that is sent via the Interface Board to the actual display hardware and the component setup with I2C Display, I2C Injector and Interface Board components.

The new SCADA mode for Flowcode 8.1 is an absolute added bonus in that we can then take a program that does something in the Flowcode simulator and export it so that it will run stand alone on any PC without any licensing issues. This could be great for projects like test rigs or sensor clusters.

I use this SCADA mode to create the WIFI configuration tool that can be used to configure the SSID and password as well as collect the IP address of the module. This allows me to set everything up using the USB connection and then transfer over to a WIFI network connection once things are running.

Some example projects are attached.

Step 5: Other Interfacing Methods

As well as Flowcode you can pretty much use your programming language of choice to communicate with the interface board. We used Flowcode as it had a library of parts already included which we could get up and running immediately but this also applies to many other languages.

Here is a list of languages and methods to communicate with the Interface board.

Python - Using a serial library to stream data to a COM port or IP address

Matlab - Using File commands to stream data to a COM port or IP address

C++ / C# / VB - Using either a pre-written DLL, directly accessing the COM port or Windows TCP/IP API

Labview - Using either a pre-written DLL, the VISA Serial component or TCP/IP component

If anyone would like to see the above languages implemented then please let me know.

Step 6: Finished Product

The finished product will likely be a prominent feature in my embedded tool kit for years to come. Already it has helped me to develop components for various Grove displays and sensors. I can now get the code completely nailed before resorting to any compilation or programming shenanigans.

I've even handed out some boards for colleagues so they can improve their work flow too and these have been very well received.

Thanks for reading my Instructable I hope you found it useful and hopefully it will inspire you to create your own tools to speed up your productivity.

PCB Contest

Participated in the
PCB Contest