{"id":30135,"date":"2023-09-15T08:45:39","date_gmt":"2023-09-15T15:45:39","guid":{"rendered":"https:\/\/digilent.com\/blog\/?p=30135"},"modified":"2024-01-05T10:48:51","modified_gmt":"2024-01-05T18:48:51","slug":"getting-out-of-the-echo-chamber-transferring-data-over-ethernet-using-zynq","status":"publish","type":"post","link":"https:\/\/digilent.com\/blog\/getting-out-of-the-echo-chamber-transferring-data-over-ethernet-using-zynq\/","title":{"rendered":"Getting Out of the Echo Chamber &#8211; Transferring Data Over Ethernet Using Zynq"},"content":{"rendered":"<p><span data-contrast=\"auto\" xml:lang=\"EN-US\" lang=\"EN-US\" class=\"TextRun SCXW115355705 BCX8\"><span class=\"NormalTextRun SCXW115355705 BCX8\">We recently revamped a tutorial on the Digilent <\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">R<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">eference site which details how to set up an LWIP echo server for Zynq-7000 boards<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">:<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> <\/span><\/span><a class=\"Hyperlink SCXW115355705 BCX8\" href=\"https:\/\/digilent.com\/reference\/programmable-logic\/guides\/zynq-servers\" target=\"_blank\" rel=\"noreferrer noopener\"><span data-contrast=\"none\" xml:lang=\"EN-US\" lang=\"EN-US\" class=\"TextRun Underlined SCXW115355705 BCX8\"><span class=\"NormalTextRun SCXW115355705 BCX8\" data-ccp-charstyle=\"Hyperlink\">Getting Started with Zynq Servers.<\/span><\/span><\/a><span data-contrast=\"auto\" xml:lang=\"EN-US\" lang=\"EN-US\" class=\"TextRun SCXW115355705 BCX8\"><span class=\"NormalTextRun SCXW115355705 BCX8\">\u00a0This post is something of an appendix to that tutorial, <\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">showcasing<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> some simple changes that one can make to the server to programmatically send data back to a <\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">client<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">.<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> The <\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">tutorial<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> includes basic manipulation of echoed data \u2013 this post takes it a step farther, <\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">generating data in the server software<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> application<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">, which could be trivially extended to arbitrary data, for example, data<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> captured from a sensor or other device hooked up to the FPGA<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">.<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> <\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">This post<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> assumes that you have already run through the tutorial, however, Zynq presets make it so that you can get started <\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">pretty much right<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\"> away using any hardware platform (XSA) you already have sitting around \u2013 do check the tutorial for some bug fixes <\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">required by some boards<\/span><span class=\"NormalTextRun SCXW115355705 BCX8\">.<\/span><\/span><span class=\"EOP SCXW115355705 BCX8\" data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<h2><b><span data-contrast=\"auto\">Sending a block of data to the host<\/span><\/b><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/h2>\n<p><span data-contrast=\"auto\">First, we\u2019re going to make some changes to the LWIP echo server so that it can send back custom data \u2013 not just echoing what was sent to it. In this case, the server will accept a connection from a client, receive a packet from it, which consists only of an integer number of bytes to send back, and then return that much data. Importantly, a single TCP packet can only transmit so many bytes. We\u2019re going to implement both sides of the connection such that \u201cone transfer\u201d can be spread across multiple tcp_writes and recvs.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">All of the important code in the LWIP echo server example can be found in echo.c.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">The LWIP TCP API uses a simple callback model to take action when various Ethernet events occur. A tcp_accept event occurs when a tcp connection is first established between client and server. In the base LWIP server example, this callback just registers another callback, tcp_recv, so that whenever any data is received by the server from the client, it can take further action. We\u2019re going to modify it to also register a tcp_sent callback, which will be used to take further action when a packet is successfully sent from the server to the client, allowing us to send more data than can fit in any single packet.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<p><em><strong>Note: The API also includes a tcp_err callback which can be used to take action when a fatal error occurs on the connection, however,<span class=\"NormalTextRun SCXW9499264 BCX8\">\u00a0that <\/span><span class=\"NormalTextRun SCXW9499264 BCX8\">won\u2019t<\/span><span class=\"NormalTextRun SCXW9499264 BCX8\"> be used here.<\/span><\/strong><\/em><\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) \r\n{ \r\n\u202f \u202f static int connection = 1; \r\n\r\n\u202f \u202f \/* set the receive callback for this connection *\/ \r\n\u202f \u202f tcp_recv(newpcb, recv_callback); \r\n\r\n\u202f \u202f \/* set the sent callback for this connection *\/ \r\n\u202f \u202f tcp_sent(newpcb, sent_callback); \/\/ &lt;- add this \r\n\r\n\u202f \u202f \/* just use an integer number indicating the connection id as the \r\n\u202f \u202f \u202f \u202fcallback argument *\/ \r\n\u202f \u202f tcp_arg(newpcb, (void*)(UINTPTR)connection); \r\n\r\n\u202f \u202f \/* increment for subsequent accepted connections *\/ \r\n\u202f \u202f connection++; \r\n\r\n\u202f \u202f return ERR_OK; \r\n} <\/code><\/pre>\n<\/div>\n<p><span data-contrast=\"auto\" xml:lang=\"EN-US\" lang=\"EN-US\" class=\"TextRun SCXW123416940 BCX8\"><span class=\"NormalTextRun SCXW123416940 BCX8\">Next, <\/span><span class=\"NormalTextRun SCXW123416940 BCX8\">we\u2019re<\/span><span class=\"NormalTextRun SCXW123416940 BCX8\"> going to add some global variables to pass data between the callback functions. <\/span><span class=\"NormalTextRun SpellingErrorV2Themed SCXW123416940 BCX8\">Test_buf<\/span><span class=\"NormalTextRun SCXW123416940 BCX8\"> holds the data to be sent to the client. <\/span><span class=\"NormalTextRun SpellingErrorV2Themed SCXW123416940 BCX8\">Bytes_to_send<\/span><span class=\"NormalTextRun SCXW123416940 BCX8\"> <\/span><span class=\"NormalTextRun SCXW123416940 BCX8\">represents<\/span><span class=\"NormalTextRun SCXW123416940 BCX8\"> the number of bytes <\/span><span class=\"NormalTextRun SCXW123416940 BCX8\">remaining<\/span><span class=\"NormalTextRun SCXW123416940 BCX8\"> in the buffer which have yet to be sent. <\/span><span class=\"NormalTextRun SpellingErrorV2Themed SCXW123416940 BCX8\">Write_head<\/span><span class=\"NormalTextRun SCXW123416940 BCX8\"> will track the first address within the buffer where there is a byte which still needs to be sent.<\/span><span class=\"NormalTextRun SCXW123416940 BCX8\"> These should be added near the top of the file, so that they can be accessed from within the callback functions.<\/span><\/span><span class=\"EOP SCXW123416940 BCX8\" data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>\/\/ global variables used to pass data between callbacks, tcp_arg\r\n\/\/ could potentially be used instead \r\nu16_t bytes_to_send;\r\nu8_t test_buf[65536];\r\nu8_t *write_head;<\/code><\/pre>\n<\/div>\n<p>We&#8217;ll also add a helper function that will be used to add data to the LWIP library&#8217;s send buffer and keep track of our own buffer by updating the global variables. Note that tcp_sndbuf returns the available space left in the transmit buffer.<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>err_t push_data(struct tcp_pcb *tpcb)\r\n{\r\n    \/\/ add up to remaining bytes_to_send to the transmit buffer, or\r\n    \/\/ fill the remaining space in the transmit buffer\r\n    u16_t packet_size = bytes_to_send;\r\n    u16_t max_bytes = tcp_sndbuf(tpcb);\r\n    err_t status;\r\n\r\n    \/\/ if there's nothing left to send, exit early\r\n    if (bytes_to_send == 0) {\r\n        return ERR_OK;\r\n    }\r\n\r\n    \/\/ if adding all bytes to the buffer would make it overflow,\r\n    \/\/ only fill the available space\r\n    if (packet_size &gt; max_bytes) {\r\n        packet_size = max_bytes;\r\n    }\r\n\r\n    \/\/ write to the LWIP library's buffer\r\n    status = tcp_write(tpcb, (void*)write_head, packet_size, 1);\r\n\r\n    xil_printf(\"push_data: Asked to add %d bytes to the send buffer, adding %d bytes\\r\\n\",\r\n               bytes_to_send, packet_size);\r\n\r\n    \/\/ keep track of how many bytes have been pushed to the buffer\r\n    if (packet_size &gt; bytes_to_send) {\r\n        bytes_to_send = 0;\r\n    } else {\r\n        bytes_to_send -= packet_size;\r\n    }\r\n\r\n    \/\/ move our transmit buffer head forward\r\n    write_head += packet_size;\r\n\r\n    return status;\r\n}<\/code><\/pre>\n<\/div>\n<p><span data-contrast=\"auto\" xml:lang=\"EN-US\" lang=\"EN-US\" class=\"TextRun SCXW258429850 BCX8\"><span class=\"NormalTextRun SCXW258429850 BCX8\">Next, <\/span><span class=\"NormalTextRun SCXW258429850 BCX8\">we\u2019ll<\/span><span class=\"NormalTextRun SCXW258429850 BCX8\"> <\/span><span class=\"NormalTextRun SCXW258429850 BCX8\">modify<\/span><span class=\"NormalTextRun SCXW258429850 BCX8\"> the receive callback so that it fills a buffer with a specified amount of verifiable <\/span><span class=\"NormalTextRun ContextualSpellingAndGrammarErrorV2Themed SCXW258429850 BCX8\">data, and<\/span><span class=\"NormalTextRun SCXW258429850 BCX8\"> starts sending it back to the host.<\/span><\/span><span class=\"EOP SCXW258429850 BCX8\" data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p,\r\n                    err_t err) \r\n{ \r\n\u202f \u202f \/* do not read the packet if we are not in ESTABLISHED state *\/\r\n\u202f \u202f if (!p) {\r\n\u202f \u202f \u202f \u202f tcp_close(tpcb);\r\n\u202f \u202f \u202f \u202f tcp_recv(tpcb, NULL);\r\n\u202f \u202f \u202f \u202f return ERR_OK;\r\n\u202f \u202f }\r\n\r\n    xil_printf(\"entered recv_callback\\r\\n\");\r\n\u202f \u202f \/* indicate that the packet has been received *\/\r\n\u202f \u202f tcp_recved(tpcb, p-&gt;len);\r\n\r\n    \/\/ convert the first two bytes of the received packet's payload\r\n    \/\/ to a 16-bit unsigned integer\r\n\u202f \u202f bytes_to_send = *((u16_t*)(p-&gt;payload)); \r\n\r\n    xil_printf(\"Client asked for %d bytes\\r\\n\", bytes_to_send);\r\n\u202f \u202f \/\/ load the buffer \r\n\u202f \u202f for (int i = 0; i &lt; bytes_to_send; i++) {\r\n \u202f \u202f \u202f \u202ftest_buf[i]= (u8_t)(i &amp; 0xFF);\r\n \u202f \u202f}\r\n    write_head = test_buf;\r\n \u202f \u202fif (bytes_to_send &gt; 0) {\r\n        err = push_data(tpcb)\r\n\u202f \u202f } \/\/ else, nothing to send \r\n\r\n\u202f \u202f \/* free the received pbuf *\/ \r\n\u202f \u202f pbuf_free(p); \r\n\r\n\u202f \u202f return err; \r\n} <\/code><\/pre>\n<\/div>\n<p><span data-contrast=\"auto\" xml:lang=\"EN-US\" lang=\"EN-US\" class=\"TextRun SCXW165076330 BCX8\"><span class=\"NormalTextRun SCXW165076330 BCX8\">Lastly, <\/span><span class=\"NormalTextRun SCXW165076330 BCX8\">we\u2019re<\/span><span class=\"NormalTextRun SCXW165076330 BCX8\"> going to add the new <\/span><span class=\"NormalTextRun SpellingErrorV2Themed SCXW165076330 BCX8\">sent_callback<\/span><span class=\"NormalTextRun SCXW165076330 BCX8\"> function, which will continue sending data from the buffer until it empties out.<\/span><\/span><span class=\"EOP SCXW165076330 BCX8\" data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-c\" data-lang=\"C\"><code>err_t sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len) {\r\n\u202f \u202f \/\/ log to USBUART for debugging purposes\r\n    xil_printf(\"entered sent_callback\\r\\n\");\r\n\u202f \u202f xil_printf(\"    bytes_to_send = %d\\r\\n\", bytes_to_send);\r\n \u202f  xil_printf(\"    len = %d\\r\\n\", len);\r\n\u202f \u202f xil_printf(\"    free space = %d\\r\\n\", tcp_sndbuf(tpcb));\r\n\r\n\u202f \u202f \/\/ if all bytes have been sent, we're done \r\n\u202f \u202f if (bytes_to_send &lt;= 0)\r\n \u202f \u202f    return ERR_OK;\r\n\r\n    return push_data(tpcb);\r\n} <\/code><\/pre>\n<\/div>\n<p><span data-contrast=\"auto\">Note that this code is not robust and some situations could cause problems, like if another block of data was requested while the server was in the middle of sending a previous one. That said, it could easily be modified further so that the server can accept some arbitrary command, take action based on what\u2019s in that command, and return requested data or some other response to the client.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<p>&nbsp;<\/p>\n<h2><b><span data-contrast=\"auto\">Python socket example<\/span><\/b><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/h2>\n<p><span data-contrast=\"auto\">Next, we\u2019re going to set up a Python client that can connect to the server, request some data, and check it to see that everything made it through as expected.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<p><span data-contrast=\"auto\">The <\/span><i><span data-contrast=\"auto\">socket <\/span><\/i><span data-contrast=\"auto\">module implements an easy-to-use API for TCP connections and is included in a basic Python installation by default. The following chunk of code will connect to the Zynq server after it is running on the board, request some bytes, and then check that they match the expected. Make sure to run the Zynq application before the script.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism line-numbers lang-python\" data-lang=\"Python\"><code>import socket\r\nclient = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r\n\r\n# connect to the server; enter your server's IP and port here, as\r\n#  printed by the board's serial interface\r\nclient.connect((\"10.0.0.128\", 7))\r\n# specify the number of bytes we're requesting from the server\r\n# change this as desired, several values up to 50k have been tested\r\n# for much more, more than two bytes would need to be used to\r\n#  specify length\r\nnum_bytes = 500\r\n\r\n# arbitrary packet size, max number of bytes we'll receive at once\r\npacket_size = 256\r\n\r\n# send two bytes representing num_bytes to request that many bytes\r\n#  in response\r\n# note: little endian is important, requirement for Zynq-7000 to\r\n#       easily translate the sent number to an int without reordering\r\nprint(f\"requesting {num_bytes.to_bytes(2, 'little')} bytes\")\r\nclient.send(num_bytes.to_bytes(2, 'little'))\r\n\r\n# loop while calling recv to receive data from the client until the\r\n# expected number of bytes has been successfully transferred\r\nreceived = 0\r\nerrors = 0\r\nwhile received &lt; num_bytes:\r\n\u202f \u202f data = client.recv(packet_size)\r\n\u202f \u202f for d in range(len(data)):\r\n        expected_value = (received + d) &amp; 0xff\r\n\u202f \u202f \u202f \u202f if data[d]!= expected_value: # validate data\r\n\u202f \u202f \u202f \u202f \u202f \u202f print(f\"Error, data[{d}] ({data[d]}) != {expected_value}\")\r\n\u202f \u202f \u202f \u202f \u202f \u202f errors += 1\r\n\u202f \u202f received += len(data)\r\n    print(f\"Received {received} bytes total, {len(data)} in this recv\")\r\nif errors == 0:\r\n\u202f \u202f print(\"All data received matched the expected values!\")\r\nelse:\r\n    print(f\"{errors} errors\")<\/code><\/pre>\n<\/div>\n<h4><b><span data-contrast=\"auto\">Results<\/span><\/b><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/h4>\n<p><span data-contrast=\"auto\">Let\u2019s see what all of this looks like in action. In this video, an Eclypse has already been connected to a host computer via USB and connected to the router via Ethernet. We\u2019ll run the modified echo server application in Vitis, check the IP address the board picks up in an already-connected instance of Tera Term, then run the python script to transfer and check some data:<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<div style=\"width: 735px;\" class=\"wp-video\"><video class=\"wp-video-shortcode\" id=\"video-30135-1\" width=\"735\" height=\"390\" preload=\"metadata\" controls=\"controls\"><source type=\"video\/mp4\" src=\"https:\/\/digilent.com\/blog\/wp-content\/uploads\/2023\/09\/run-echo-server.mp4?_=1\" \/><a href=\"https:\/\/digilent.com\/blog\/wp-content\/uploads\/2023\/09\/run-echo-server.mp4\">https:\/\/digilent.com\/blog\/wp-content\/uploads\/2023\/09\/run-echo-server.mp4<\/a><\/video><\/div>\n<p>&nbsp;<\/p>\n<p><strong><em>Note: This code was tested using Vitis 2023.1.\u00a0\u00a0<\/em><\/strong><\/p>\n<p><span data-contrast=\"auto\">In this post, we&#8217;ve explored how to extend the capabilities of an LWIP echo server for Zynq-7000 boards, going beyond the standard echoing of received data. By programmatically sending custom data back to the client, we&#8217;ve demonstrated the potential for more advanced data manipulation within the server software application. While we&#8217;ve showcased a basic implementation in this article, it&#8217;s important to note that the possibilities are vast. You can easily adapt this approach to handle data from various sources, such as sensors or other FPGA-connected devices. The power of Zynq presets enables you to dive into this process swiftly, utilizing your existing hardware platform. As you embark on your data transmission journey, remember that this new capability opens doors to innovative applications and solutions. Whether you&#8217;re refining your existing systems or jumping into entirely new projects, the flexibility of Zynq-based Ethernet communication paves the way for exciting developments in the world of embedded systems and networking.<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<div class='watch-action'><div class='watch-position align-left'><div class='action-like'><a class='lbg-style6 like-30135 jlk' data-task='like' data-post_id='30135' data-nonce='d8c4d58d14' rel='nofollow'><img src='https:\/\/digilent.com\/blog\/wp-content\/plugins\/wti-like-post-pro\/images\/pixel.gif' title='Like' \/><span class='lc-30135 lc'>0<\/span><\/a><\/div><div class='action-unlike'><a class='unlbg-style6 unlike-30135 jlk' data-task='unlike' data-post_id='30135' data-nonce='d8c4d58d14' rel='nofollow'><img src='https:\/\/digilent.com\/blog\/wp-content\/plugins\/wti-like-post-pro\/images\/pixel.gif' title='Unlike' \/><span class='unlc-30135 unlc'>0<\/span><\/a><\/div><\/div> <div class='status-30135 status align-left'>Be the 1st to vote.<\/div><\/div><div class='wti-clear'><\/div>","protected":false},"excerpt":{"rendered":"<p>We recently revamped a tutorial on the Digilent Reference site which details how to set up an LWIP echo server for Zynq-7000 boards: Getting Started with Zynq Servers.\u00a0This post is &hellip; <\/p>\n","protected":false},"author":52,"featured_media":30174,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4361,4323,1561],"tags":[4538,4536,4540,4535,4539,4317,4348,4537],"ppma_author":[4462],"class_list":["post-30135","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-data-acquisition","category-software","category-applications","tag-data","tag-echo","tag-ethernet","tag-lwip","tag-transferring-data","tag-vitis","tag-zynq","tag-zynq-7000"],"jetpack_featured_media_url":"https:\/\/digilent.com\/blog\/wp-content\/uploads\/2023\/09\/2023-September-NewsletterImages-EchoChamber-5804191.png","jetpack_sharing_enabled":true,"authors":[{"term_id":4462,"user_id":52,"is_guest":0,"slug":"abrown","display_name":"Arthur Brown","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/60e7f8e1b3a55e2e20ee541df1f393c2acbcee9fd05fd3e38d07e25a2e6fd237?s=96&d=mm&r=g","1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":"","9":"","10":""}],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/posts\/30135","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/users\/52"}],"replies":[{"embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/comments?post=30135"}],"version-history":[{"count":15,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/posts\/30135\/revisions"}],"predecessor-version":[{"id":30413,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/posts\/30135\/revisions\/30413"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/media\/30174"}],"wp:attachment":[{"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/media?parent=30135"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/categories?post=30135"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/tags?post=30135"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/digilent.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=30135"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}