This blog post continues the discussion about driving Adafruit NeoPixel RGB LED strings. Having explained the NeoPixel drive waveforms, the hardware required to interface to NeoPixels, and NeoPixel power requirements in previous instalments, it is time to look at the architecture of a design solution implemented within the PS (processor system) and PL (programmable logic) side of the Zynq SoC.
Thinking back to the requirements, we want to be able to drive a variable number of Neo Pixels, with the ability to address and update each pixel individually while the program is executing.
Expanding a little more on this, in my mind when I first thought of this demonstration I pictured a GUI running on my lap top with a button for each LED in the array I want to drive. When I click on a particular LED’s button, I want to be able to select a 24-bit color for that LED. Once I select the colors for all of the LEDs, these commands will be downloaded to the Zynq and used to update the state of the NeoPixels.
Jumping ahead a little the Neo Pixel GUI – to give some context
Obviously the PS side will handle all of the communication with the GUI program, storing the pixel values and passing these to the custom Neo Pixel driver within the PL. To ensure reliable communication, I need to define a simple data transfer protocol for use over this interface.
Naturally, this means we need to pass data between the PS and PL sides of the device, which is part of the whole idea of this learning exercise after all. The PS-PL communication solution I have in mind is a dual-port RAM, which allows the PS to write the pixel values via one side and the PL to read out values via the other port.
Each pixel is allocated one address within this RAM. Each RAM location stores the 8-bit green, red, and blue value. The first address of the BRAM will contain the number of pixels to be configured. Setting this to zero will prevent the NeoPixel array from being updated and acts as an enable.
This approach therefore provides the following context diagram of the system solution:
System Context Diagram
Rather helpfully within Vivado IP Integrator there already exists a number of IP blocks provided in the IP library that provide the functions I require. These IP blocks provide two benefits. They decrease the time it takes for me to get my solution up and running the allow me to focus my design effort in areas where I can add the best value. (Very important should this be for a commercial application).
This solution will use the following IP cores in addition to the PS and PS reset cores:
AXI Interconnect – provides flexibility of AXI connections within the PL side, for this solution only one AXI master is needed.
AXI Bram controller – This allows a for AXI interconnection to the interface to a BRAM, allowing the PS to write into BRAM attached to this controller.
Dual-Port BRAM – With one port connected to the AXI BRAM controller and the other to the custom NeoPixel driver.
The block diagram of the completed solution appears below:
At this point, I only really need to write the NeoPixel driver which we will be looking at in the next blog.
Please see the previous entries in this MicroZed series by Adam Taylor: