Processing at the edge is a rapidly increasing application for embedded systems, often these edge nodes are connected using a RF link e.g. Zigbee, WiFi etc. However, there are applications where RF is not suitable for example when you want electrical noise kept to a minimum or when adding RF communication adds significantly to the cost of the system.
In these cases simple communications can be implemented between a host and one or more slaves using Infra Red Communication.
Depending upon the architecture of solution we choose we can achieve very fast data rates to transfer data between FPGAs.
In this project we are going to look at how we can communicate between two Arty S7 boards using IR communications. This project will enable communication using a normal serial / UART communication to make receiving and transmitting data easy.
Of course such an approach also enables the system to be expanded to communicate with other non FPGA embedded systems which have a UART Interface.
Overall ArchitectureThe approach I am going to take is to use a IR LED emitting in the 940-950 nm wavelength range. The signal output over this IR LED will be modulated to enable better transmission performance, e.g. less interference from other IR sources.
This modulated IR signal will be received by a IR receiver which decodes the modulated signal and outputs a simple Logic level signal.
The IR decoder selected is a TSOP58438 which operates at 38KHz, this means we need to be able to modulate the IR signal at 38KHz.
Using this approach means we can work up to a range of 40 Metres.
To get started with the design we first need to create a modulator IP block which will be able to modulate the incoming signal for transmission. The modulation scheme used by the IR receiver is quite simple, it requires the carrier to be present when a logic 0 is transmitted and a the carrier to not be present when the a logic 1 is transmitted.
This is a modulation scheme is called on off keying.
To implement this scheme I am created a FPGA module which would create the 38KHz carrier wave.
This carrier wave is then output or not dependent upon the input logic signal.
To make the modulator useful with other IR receivers which might work with different frequency carrier waves. The module has been designed to be flexible without the need to change the source code thanks to the generics. This is also the same for the clock frequency of the module.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.math_real.all;
entity ir_mod is
generic(
clk_freq : natural := 100000000; -- oscillator frequency
baud_rate: natural := 38000 -- baud rate
);
Port (
clk : in std_logic;
ip_sig : in std_logic;
op_mod : out std_logic);
end ir_mod;
architecture Behavioral of ir_mod is
function vector_size(clk_freq,baud_rate : real) return integer is
variable div : real;
variable res : real;
begin
div := (clk_freq/baud_rate);
res := CEIL(LOG(div)/LOG(2.0));
return integer(res - 1.0);
end;
constant bit_period : integer :=(clk_freq/baud_rate)-1 ;
signal baud_counter : unsigned(vector_size(real(clk_freq),real(baud_rate)) downto 0) := (others => '0'); --timer for outgoing signals
signal baud : std_logic :='0';
begin
process(clk)
begin
if rising_edge(clk) then
if (baud_counter = bit_period/2) then
baud <= not(baud);
baud_counter <= baud_counter + 1;
elsif (baud_counter = bit_period) then
baud_counter <= (others =>'0');
baud <= not(baud);
else
baud_counter <= baud_counter + 1;
end if;
end if;
end process;
op_mod <= baud when ip_sig = '0' else '0';
end Behavioral;
MicroBlaze Set UpThe main application deployed in the FPGA will be the same for both the RX and the TX. The only thing which changes is the application software, which will be running on the MicroBlaze on the FPGA.
Running a MicroBlaze means we can add sensor and motor interfaces with ease to the AXI interconnect.
To get started with the MicroBlaze design we need to create a new project in Vivado. For this project I am going to use Vivado 2020.1
While I will cover the basics here for more detailed information on project creation in Vivado 2020.1 see my blogs here P1 & P2
In the new Vivado project, create a new block diagram, to which we will be adding the DDR3 SRAM controller.
We can add in the DDR3 controller by dragging the DDR3 SRAM element from the Board tab onto the block diagram.
Once this has been added, we can start adding in the MicroBlaze element of the design as well. Click on the + symbol and in the resulting dialog enter MicroBlaze.
This will open the MicroBlaze configuration dialog, leave all the options the same except on page four enable the peripheral AXI instruction and data interfaces.
Once the MicroBlaze is instantiated on the diagram we can run the block automation option to implement the resulting elements of the system.
In the block automation you can if you desire change the sizes of the local memory. Otherwise leave all the settings as desired and click on OK
This will run through and implement the necessary supporting functions for the MicroBlaze System.
All that remains then is to run the connection automation and the peripheral components will be added in.
The system is close to being ready but as it stands we have no way of communicating with the system
Add in a AXI Uart lite, this will be used to communicate with the external world.
Once this has been added in we can re run the connection automation to connect in the Uart to the AXI network.
This gives us the ability to communicate over the USB UART.
We also need to add I a clocking wizard to provide the reference clocks to the DDR3 Controller.
Set the clocks for 166.6666 MHz and 200 MHz.
Connect these to the DDR Controller deleting the currently added ports.
Finally we also need to add in the IR Modulator and a second AXi Uart Lite. The input and output from the IR modulator are routed to the Shield header pins 0 and 1
With these added in, we are then able to create a HDL wrapper and implement the design and generate a bitstream.
Once the bitstream has completed its build we can export the hardware definition to Vitis.
As we have a bitstream created, we want to include it with the exported archive.
Leave the name as default, I also export the hardware to the root directory of the Vivado project.
Once the export is completed we need to open Vitis and create a hardware platform. We can use the same platform for both TX and RX applications.
Using the new platform creation wizard, will walk you through the platform creation.
Select create hardware form XSA specification, this is the one we just exported from Vivado
Point the dialog box to the XSA export and select the standalone and MicroBlaze for the OS and Processor.
This will open Vitis with a view of the newly created platform project, click on the hammer in the menu bar to build the platform files. This includes the BSP and boot elements.
We can then create a new application project to test the IR modular, TX and RX interfaces.
The new application wizard will guide you through the new application project creation process.
Select the previously created domain.
When a template is offered select the Hello World operation
The modify the software application as shown below, this design outputs and receives over the same UART the hardware design being looping back.
The software outputs a count over the IR Modulator the receives the decoded signal back from the IR Receiver. The received count is then output to the terminal.
This means we can test the IR Modulator, IR TX and RX using only one board to begin.
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xstatus.h"
#include "xuartlite.h"
#define UARTLITE_DEVICE_ID XPAR_UARTLITE_1_DEVICE_ID
#define TEST_BUFFER_SIZE 16
XUartLite UartLite;
u8 SendBuffer[TEST_BUFFER_SIZE]; /* Buffer for Transmitting Data */
u8 RecvBuffer[TEST_BUFFER_SIZE]; /* Buffer for Receiving Data */
int main()
{
int Status;
unsigned int SentCount;
unsigned int ReceivedCount = 0;
int Index;
init_platform();
print("Hello World\n\r");
print("Successfully ran Hello World application");
Status = XUartLite_Initialize(&UartLite, UARTLITE_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
for (Index = 0; Index < TEST_BUFFER_SIZE; Index++) {
SendBuffer[Index] = Index;
RecvBuffer[Index] = 0;
}
while(1){
u8 op,ip;
for(int i=0;i<TEST_BUFFER_SIZE;i++){
op = SendBuffer[i];
SentCount = XUartLite_Send(&UartLite, &op , 1);
XUartLite_Recv(&UartLite, &ip, 1);
xil_printf("%x\n\r",ip);
usleep(1000000);
}
}
cleanup_platform();
return 0;
}
To download the application to the Arty S7 we need to create a debug configuration.
We will be creating a System Project Debug
This will create a new configuration dialog which can be used to download the FPGA configuration and then the MicroBlaze application.
The main element of the front end design is the IR transmitter, we need to create a simple transistor drive. This is because the IO pins of the FPGA cannot source the current required by the IR Diode.
The transistor switch is simple to design, we want 100mA flowing through the LED. We will connect the LED between the transistor collector and ground. Switching on or off the transistor will allow voltage to flow across the LED or not.
The collector voltage will be supplied from 3v3 supply on the Arty S7 board, while the emitter will be driven from the FPGA pin modulator output.
Selecting the right Collector is simple, assuming the switch saturates.
3.3 / 0.1 = 33 Ohms
The Base resistor can be determine by Ib = Ic/HFe where HFE is 50 for this application.
As such 0.1 / 50 = 0.002 A
The base voltage is also driven from 3v3 as such the required resistor is 1650 Ohms.
The closets resistor I had available was 1000 Ohms this means we can supply 165 mA to the IR LED, still acceptable.
To make sure the circuit functions as intended I simulated it within LTSPice
You can download LT spice here
Simulating the switch should show the waveform at the collector switching as the transistor saturates.
With the front end design complete and the FPGA design complete we can test the system.
The first thing to do is observe the modulated output, with no signal is being transmitted.
What we want to check here is the carrier wave is at the correct frequency, we can verify this using a Oscilloscope.
Once we are satisfied the carrier frequency is correct we can send over data. Here is where we need to be careful, the transmitter needs at least 10 38KHz pulses for a minimum burst. As such if we want to send data over the link we need to take this into account, that is each bit period must be atleast 26 microseconds duration.
This means the maximum data rate we can use is 3800 Baud more than sufficient for simple communications.
Observing this with an oscilloscope will shows the decoded UART input (blue) and the modulated output signal bottom which the IR Transmitter relieves (red).
The count as decoded on the oscilloscope should align with the number displayed on the output screen.
In this manner we can send and receive data between the two boards while they have no electrical connection.
Creating the RX and TX applicationWith the IR front end and modulator proved to be working it is time to create two applications one for the IR TX and another for the IR RX.
We need to create a new application system for the RX application, which targets the same hardware platform.
TX Software
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xstatus.h"
#include "xuartlite.h"
#define UARTLITE_DEVICE_ID XPAR_UARTLITE_1_DEVICE_ID
#define TEST_BUFFER_SIZE 16
XUartLite UartLite;
u8 SendBuffer[TEST_BUFFER_SIZE]; /* Buffer for Transmitting Data */
u8 RecvBuffer[TEST_BUFFER_SIZE]; /* Buffer for Receiving Data */
int main()
{
int Status;
unsigned int SentCount;
unsigned int ReceivedCount = 0;
int Index;
init_platform();
print("Hello World\n\r");
print("Successfully ran Hello World application");
Status = XUartLite_Initialize(&UartLite, UARTLITE_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
for (Index = 0; Index < TEST_BUFFER_SIZE; Index++) {
SendBuffer[Index] = Index;
RecvBuffer[Index] = 0;
}
while(1){
u8 op,ip;
for(int i=0;i<TEST_BUFFER_SIZE;i++){
op = SendBuffer[i];
SentCount = XUartLite_Send(&UartLite, &op , 1);
//XUartLite_Recv(&UartLite, &ip, 1);
//xil_printf("%x\n\r",ip);
usleep(1000000);
}
}
cleanup_platform();
return 0;
}
RX Software
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xstatus.h"
#include "xuartlite.h"
#define UARTLITE_DEVICE_ID XPAR_UARTLITE_1_DEVICE_ID
#define TEST_BUFFER_SIZE 16
XUartLite UartLite;
u8 SendBuffer[TEST_BUFFER_SIZE]; /* Buffer for Transmitting Data */
u8 RecvBuffer[TEST_BUFFER_SIZE]; /* Buffer for Receiving Data */
int main()
{
int Status;
unsigned int SentCount;
unsigned int ReceivedCount = 0;
int Index;
init_platform();
print("Adiuvo Engineering\n\r");
print("RX System\n\r");
Status = XUartLite_Initialize(&UartLite, UARTLITE_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
while(1){
u8 op,ip;
//for(int i=0;i<TEST_BUFFER_SIZE;i++){
//op = SendBuffer[i];
//SentCount = XUartLite_Send(&UartLite, &op , 1);
XUartLite_Recv(&UartLite, &ip, 1);
xil_printf("%x\n\r",ip);
usleep(1000000);
//}
}
All that remains now is to include the ELF files in with the bit streams (we need one RX and One TX bitstream)
We can do this in Vivado, using the associate ELF file, in turn merge the TX and RX ELF with the bitstream.
Once we have two bit streams we can open the hardware manager and program the QSPI flash with the bit files.
To add a QSPI memory right click add configuration memory and then select the correct memory type.
Once both boards are flashed we can test the application
In this example I have connected the IR TX board to be powered from a USB battery bank. while the RX is connected to the PC, to be able to display the output.
We can communicate in this manner up to 40 Metres not bad for a simple approach.
See previous projectshere.
Additional Information on Xilinx FPGA / SoC Development can be found weekly onMicroZed Chronicles.
Comments