With the transition from Spartan 6 to Spartan 7 in full swing, I decided that my SP701 needed to be added to my network for remote control access, specifically using the lwIP stack since I've previously demonstrated how to set up that bare metal C application in Vitis.
I'm starting this project using the Vivado hardware design from 2021.2 I did in this project and I'm starting with the embedded C lwIP application that I created in Vitis here. My goal for this project is to expand the echo server application running on the SP701 to include actions to specific commands received from the Python echo client script running on the host PC on top of it echoing back the string it receives.
There are 8 LEDs on the SP701 that are perfect for a quick add functionality-wise so I'll be adding the capability to turn off/on each of the 8 LEDs individually or altogether to the echo server application in Vitis then writing a new Python script echo client to test out the remote control of the LEDs.
GPIO Control FunctionsIn the hardware design for the SP701 in Vivado, the 8 LEDs are controlled by their own AXI GPIO block in the block design. It is configured such that it has one channel with an 8-bit bus of all output signals.
Make note of its name in the block design, axi_gpio_1, so we can find it in xparameters.h in Vitis.
Switching over to Vitis, create the source files for the GPIO functions to control the LEDs. I recommend create separate source files to write the GPIO functions in and simply calling them from the lwIP server source files so that everything is more readable and organized.
Right-click on src folder in the lwIP application in the Explorer window then select New > File. Create the source file for the GPIO control functions, I named mine led_8bits.c. Repeat the process to create the header file (again, led_8bits.h in my case).
Overall, there are going to be four functions for the LEDs:
- Initializing the AXI GPIO interface and configuring it to match the hardware configuration in Vivado (one channel with an 8-bit bus of all output signals).
- Set a specific LED output signal logic level high or logic level low.
- Set all LED output signals logic level low.
- Set all LED output signals logic level high.
In the header file, led_8bits.h, I specified the bit masks for the LEDs as well as the device ID from xparameters.h, which is found in the following location:
sp701_platform > export > sp701_platform > sw > sp701_platform > standalone_domain > bspinclude > include > xparameters.h
Now you could just use the device ID name from xparameters.h directly, I've gotten into the habit of renaming/defining it in header files like this so it matching the naming conventions in the rest of my function(s) simply for the sake of making my code easier to read for myself at any point in the near/distant future.
#define led_8bits_ctrl_DeviceID XPAR_AXI_GPIO_1_DEVICE_ID
#define LED0 0x00000001
#define LED1 0x00000002
#define LED2 0x00000004
#define LED3 0x00000008
#define LED4 0x00000010
#define LED5 0x00000020
#define LED6 0x00000040
#define LED7 0x00000080
#define ALL_LEDS 0x000000ff
The LEDs all need a bit mask assigned to them based on their physical location on the bus, note that there is also the bit mask to call out all of the LEDs at once. See graphic below to understand how it maps out:
Note: the reason that the hex value is 32 bits when there are only 8 LEDs is because the XGpio function input parameter is u32.
I've attached my specific LED AXI GPIO source code below.
Command/Data Receive Function in lwIP ServerIn the lwIP server application, the entry point for adding custom commands is where the incoming data from the socket is being read in and subsequently echoed. Which is in the echo.c file in the recv_callback() function.
Here, all's I did was simply specify some strings as recognizable commands and every incoming character string to the echo server is compared using strcmp() before being echoed back to the client. If/when the incoming character string matches any of the specified command strings, the appropriate LED GPIO function is called then the string is echoed back to the client as normal.
For the echo client running on my PC as a Python script, I separated out the code for creating the packet, sending it, then reading back what the server echos into it's own function since that part doesn't change regardless of what the message packet being sent is:
def echo_packet(lwIP_socket, message):
message_byte = message.encode()
lwIP_socket.sendall(message_byte)
bytes_buffer_size = 32
bytes_received = 0
bytes_total = len(message)
while bytes_received < bytes_total:
message_recvd = lwIP_socket.recv(bytes_buffer_size)
bytes_received += len(message_recvd)
print(message_recvd)
Then I scripted the main script to toggle each of the LEDs for half a second in sequential order after issuing the command to turn off all of the LEDs so they are in a known state.
try:
message = 'All LEDs off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED0 on'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED1 on'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED2 on'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED3 on'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED4 on'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED5 on'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED6 on'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED7 on'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED0 off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED1 off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED2 off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED3 off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED4 off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED5 off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED6 off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
message = 'LED7 off'
echo_packet(lwIP_socket, message)
time.sleep(0.5)
finally:
print('Closing the socket from the host PC client side...')
lwIP_socket.close()
As you can see, it's pretty straightforward to take this example and expand upon it to add any other remote control functions to your SP701. I do think the bare metal lwIP application is the simpler route as compared to running a full TCP/IP stack on an embedded Linux image, but it does have it's limitations in terms of speed and bandwidth. For simple character string commands like this though, it's more than sufficient!
Note: make sure the host PC running the Python echo client is on the same network as the SP701 running the echo server.
Comments