初學FreeRTOS
(1)使用ZC706開發板測試PS端網口(Echo,lwIP協議棧);
(2)配合操作PL端LED(直接驅動和使用消息隊列兩種方式);
(4)QSPI固化(Dual Quad SPI Parallel 8 bit模式)。
ZC706中,MAC 控制器與 PHY 通過 RGMII(Reduced Gigabit Media Independent Interface)接口進行連接,實現千兆網。
一、工程概述
1. 開發板配置
使用Xilinx ZYNQ開發板ZC706,默認配置ARM后即可使用PS端網口、串口和QSPI,放置AXI GPIO的IP核驅動PL端的4個LED。
ARM端配置如下圖所示,以5處的ARM-A9為核心,使用1處的UART1打印調試信息,使用2處的網口0進行以太網通信,使用3處的AXI GP(General Port)Master通用主設備接口連接PL端的AXI GPIO,最后使用4處的QSPI固化程序,燒錄Boot文件。
2. SDK程序
上述工程綜合、布局布線并生成bit流后,導出硬件。
新建應用工程Application Project,選擇 OS Platform 平臺為 freertos10_xilinx (Vivado及SDK版本2018.2,低版本的可能是freertos9_xilinx),選擇Next,選中“ FreeRTOS lwIP Echo Server ”。
新建完成后,即可進行最基礎的網絡通信了。這里注意,默認設置的是DCHP動態主機配置協議,需要開發板和電腦都連接到一個路由器上。如果直接使用網線連接開發板和電腦,則啟用 IPv4 協議,默認配置的IP地址為192.168.1.10,子網掩碼255.255.255.0,網關196.128.1.1,如果想要更改默認配置,可以在main.c文件的main_thread()主線程中修改,如下所示:
xil_printf("ERROR: DHCP request timed out\\r\\n");
xil_printf("Configuring default IP of 192.168.1.10\\r\\n");
IP4_ADDR(&(server_netif.ip_addr), 192, 168, 1, 10);
IP4_ADDR(&(server_netif.netmask), 255, 255, 255, 0);
IP4_ADDR(&(server_netif.gw), 192, 168, 1, 1);
LWIP 是一個小型開源的 TCP/IP 協議棧,支持IPv4、IPv6、TCP、UDP、DHCP等。
?IGMP 協議,用于網絡組管理,可以實現多播數據的接收
?Internet 協議(IP),包括 IPv4 和 IPv6,支持 IP 分片與重裝,包括通過多個網絡接口的數據包轉發
?用于網絡維護和調試的 Internet 控制消息協議(ICMP)
?用戶數據報協議(UDP)
?傳輸控制協議(TCP)擁塞控制,往返時間(RTT)估計,快速恢復和重傳
?DNS,域名解析
?SNMP,簡單網絡管理協議
?動態主機配置協議(DHCP)
?以太網地址解析協議(ARP)
?AUTOIP,IP 地址自動配置
?PPP,點對點協議,支持
3. 網絡設置
使用網線直接連接ZC706開發板和計算機網口,配置計算機IP地址為192.168.1.11,子網掩碼255.255.255.0,網關192.168.1.1,其中IP地址的最后一處可以更改為其他值,但是不能和開發板的相同。
4. 開啟監聽測試
使用SecureCRT軟件監聽,除此之外,使用其他網口助手也可以。
二、工程測試
1. 測試Echo官方例程
先打開串口,波特率115200,下載官方例程到ZC706開發板,連接SecureCRT_CN,初始化工程中串口打印信息如下:配置DCHP動態主機協議超時,自動轉為IPv4,將板子的IP地址配置為192.168.1.10,子網掩碼255.255.255.0,網關192.168.1.1,使用端口7。
在SecureCRT_CN界面輸入字符或字符串,回車,通過網口向開發板發送數據,開發板會返回同樣的數據,測試正確。
2. 分析源碼
2.1 main函數
打開main.c文件,找到main()函數。在main函數中創建了一個線程,傳入的參數依次為 線程名(調試用)、函數指針、函數需要的參數、需要的堆棧大小、優先級 。
按照如下配置,調用了main_thread函數,不需要傳參(用0或NULL),堆棧大小由#define定義為1024,優先級為2。
int main()
{
sys_thread_new("main_thrd", (void(*)(void*))main_thread, 0,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
vTaskStartScheduler();
while(1);
return 0;
}
2.2 main_thread函數
此函數中實現的功能如下:
(1)初始化lwip協議棧;lwip_init();
(2)調用network_thread()創建線程;
(3)調用echo_application_thread()創建線程;
每500ms檢測一次DHCP是否成功,若成功則創建echo應用線程,如果10秒還沒有成功,則啟用IPv4,配置IP地址、子網掩碼和網關后,創建echo應用程序;創建成功后退出while,配置完成;
while (1) {
vTaskDelay(DHCP_FINE_TIMER_MSECS / portTICK_RATE_MS);
if (server_netif.ip_addr.addr) {
xil_printf("DHCP request success\\r\\n");
print_ip_settings(&(server_netif.ip_addr), &(server_netif.netmask), &(server_netif.gw));
print_echo_app_header();
xil_printf("\\r\\n");
sys_thread_new("echod", echo_application_thread, 0,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
break;
}
mscnt += DHCP_FINE_TIMER_MSECS;
if (mscnt >= 10000) {
xil_printf("ERROR: DHCP request timed out\\r\\n");
xil_printf("Configuring default IP of 192.168.1.10\\r\\n");
IP4_ADDR(&(server_netif.ip_addr), 192, 168, 1, 10);
IP4_ADDR(&(server_netif.netmask), 255, 255, 255, 0);
IP4_ADDR(&(server_netif.gw), 192, 168, 1, 1);
print_ip_settings(&(server_netif.ip_addr), &(server_netif.netmask), &(server_netif.gw));
/* print all application headers */
xil_printf("\\r\\n");
xil_printf("%20s %6s %s\\r\\n", "Server", "Port", "Connect With..");
xil_printf("%20s %6s %s\\r\\n", "--------------------", "------", "--------------------");
print_echo_app_header();
xil_printf("\\r\\n");
sys_thread_new("echod", echo_application_thread, 0,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
break;
}
}
2.3 echo_application_thread函數
位置:echo.c文件。
(1)創建socket,綁定端口,監聽;
(2)調用process_echo_request函數創建線程;
此函數需要傳入參數。
while (1) {
if ((new_sd = lwip_accept(sock, (struct sockaddr *)&remote, (socklen_t *)&size)) > 0) {
sys_thread_new("echos",
process_echo_request,
(void*)new_sd,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
}
}
2.4 process_echo_request函數
位置:echo.c文件, 用戶需要注意的最重要的函數,發送和接收的移植全部在這個函數 。
(1)接收數據,最大數據長度2048,char 類型,存儲在recv_buff 數組中,若接收出錯,打印錯誤信息并退出while;
(2)若接收到的數據的前4個字符為quit,則退出while;
(3)將接收到的數據發送出去;
void process_echo_request(void *p)
{
int sd = (int)p;
int RECV_BUF_SIZE = 2048;
char recv_buf[RECV_BUF_SIZE];
int n, nwrote;
while (1) {
/* read a max of RECV_BUF_SIZE bytes from socket */
if ((n = read(sd, recv_buf, RECV_BUF_SIZE)) < 0) {
xil_printf("%s: error reading from socket %d, closing socket\\r\\n", __FUNCTION__, sd);
break;
}
/* break if the recved message = "quit" */
if (!strncmp(recv_buf, "quit", 4))
break;
/* break if client closed connection */
if (n <= 0)
break;
/* handle request */
if ((nwrote = write(sd, recv_buf, n)) < 0) {
xil_printf("%s: ERROR responding to client echo request. received = %d, written = %d\\r\\n",
__FUNCTION__, n, nwrote);
xil_printf("Closing socket %d\\r\\n", sd);
break;
}
}
/* close connection */
close(sd);
vTaskDelete(NULL);
}
3. 測試網口發送數據
由2.4可知,在process_echo_request函數中更改發送即可。新增一個字符數組:
char tx_buf[16]={'H','e','l','l','o',',','W','o','r','l','d','\\r','\\n'};
在發送完接收到的數據后,新增一個發送函數,即可發送tx_buf數組,長度為16:
write(sd, tx_buf, 16);
4. 測試網口接收數據并控制LED
在向開發板發送數據時,規定一組特殊數據,如“led0”、“led5”、“led8”等,前3個字符“led”用于指示這部分數據是用于控制LED的,第4個字符表示點亮組合,四個LED使用二進制編碼數據為0~15, 注意,這里發送的是ASCII字符,在控制LED時需處理成數字(減 ’0’) 。
接收到數據后,仿照函數中對quit字符串的處理方式,新增一個處理,將接收到的字符串與字符串“led”比較,如果收到的字符串的前3個字符是“led”,則使用第4個字符控制LED的亮滅。
strncmp 函數, 字符串比較函數,字符串大小的比較以ASCII 碼表上的順序來決定。函數聲明為 int strncmp ( const char * str1, const char * str2, size_t n ) ,把 str1 和 str2 進行比較,最多比較前 n 個字節,若str1與str2的前n個字符相同,則返回0;若s1大于s2,則返回大于0的值;若s1 小于s2,則返回小于0的值。
if (!strncmp(recv_buf, "led", 3)) {
XGpio_DiscreteWrite(&Gpio_Led, 1, recv_buf[3]-'0');
xil_printf("Led Value = %d\\r\\n", recv_buf[3]-'0');
}
5. 測試LED任務及消息隊列
**5.1 **包含頭文件,聲明隊列
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
QueueHandle_t xQueue = NULL;
**5.2 **在main函數中創建消息隊列
傳入兩個參數,分別為隊列長度和隊列中每個元素的長度,xQueueCreate(1,1) 表示隊列長度為1,隊列中的每個元素時一個char類型數據,xQueueCreate(2,15) 表示隊列長度為2,每個元素都是一個char[15]類型的字符數組。
xQueue = xQueueCreate(1,1);
/* Check the queue was created. 檢查隊列是否創建成功*/
configASSERT( xQueue );
**5.3 **在main函數中創建LED任務,接收隊列消息
xTaskCreate( prvPlLedTask,
( const char * ) "PL Led",
configMINIMAL_STACK_SIZE,
NULL,
tskIDLE_PRIORITY + 1,
NULL);
其中,調用的prvPlLedTask定義如下,每次從隊列中讀取一個char類型的數據,若隊列為空則等待,若隊列不為空則讀出后控制LED,注意這里的rece_led_value一定要加取地址符號&,表示傳入指針,否則出錯。
static void prvPlLedTask( void *pvParameters )
{
const TickType_t x1second = pdMS_TO_TICKS( DELAY_1_SECOND );
char rece_led_value;
for( ;; )
{
xil_printf( "PL LED task\\r\\n" );
xQueueReceive( xQueue, /* The queue being read. */
&rece_led_value, /* Data is read into this address. */
portMAX_DELAY ); /* 延時 */
xil_printf( "PL LED task\\r\\n" );
xil_printf( "rece_led_value = %d\\r\\n", rece_led_value-'0' );
XGpio_DiscreteWrite(&Gpio_Led, 1, rece_led_value-'0');
/* Delay for 1 second. */
vTaskDelay( x1second );
}
}
**5.4 **在process_echo_request中添加發送隊列消息
若滿足條件,則將對LED的控制信息寫入隊列,注意要加取地址符號&。
if (!strncmp(recv_buf, "led", 3)) {
xQueueSend( xQueue,
&recv_buf[3],
0UL );
}
三、程序固化
1. 新建FSBL工程
2. 生成Boot鏡像文件
生成工程后,右鍵“Create Boot Image”,依次添加FSBL工程的elf(默認已添加)、工程的bit文件(默認已添加)、需固化的程序elf(Add找到路徑添加),“Create Image”。
3. 燒錄QSPI Flash
選擇Image和FSBL的路徑,對Flash, 一定選擇“qspi_dual_parallel” , 若選擇“qspi_single”也能下載成功,但是無法加載,ZC706板載指示燈亮紅燈 。
4. 配置啟動模式
-
led燈
+關注
關注
22文章
1592瀏覽量
107847 -
ARM處理器
+關注
關注
6文章
360瀏覽量
41667 -
PHY
+關注
關注
2文章
301瀏覽量
51700 -
GPIO
+關注
關注
16文章
1196瀏覽量
51924 -
MAC控制器
+關注
關注
0文章
6瀏覽量
2614
發布評論請先 登錄
相關推薦
評論