草庐IT

ZYNQ - 以太网远程更新SD卡应用程序

Vuko-wxh 2023-08-04 原文

写在前面

对于ZYNQ系列的板卡固化,可以通过JTAG接口,使用SDK固化到FLASH中,或者可将SD卡取出将SD卡中保存的固化工程进行修改,但在很多情况下,离线更新会很不方便,本文借鉴网上常见的远程更新QSPI FLASH的相关示例,对表贴式SD卡的应用程序进行了在线更新的操作适配,便于ZYNQ设备进行远程更新保存在表贴式SD卡中的固化程序。

传统SD卡与表贴SD卡区别

对于传统SD卡,直接将SD卡取出,使用读卡器进行脱机更新很方便,但是由于SD卡插拔时容易损坏,对于一些需要SD卡设备,但需要高可靠性的应用场景,使用传统的SD卡托很容易造成卡托和TF卡的脱落,很难保持SD卡长时间的稳定读取。

相比传统的SD卡,使用表贴式的SD卡,将会增加系统的可靠性和稳定性,这里硬件方案选择雷龙公司的NAND Flash(贴片式TF卡)CSNP4GCR01-AMW,产品说明如下:

相比传统的SD卡,表贴式SD卡除了保留了SD卡大容量容易读写操作的特点外,在PCB板上的占用面积也相比传统表贴卡托的面积要小。对传统的SD卡的电路设计可实现快速替代。

程序简述说明

程序大体框架借鉴了正点原子的远程更新的例程架构,只对更新QSPI的部分进行改写替换,替换成对SD卡的固化程序进行更新的相关代码。本文使用的板卡为PYNQ-Z2,这里只是为了验证表贴SD卡的功能,使用转接板对传统的SD卡进行了替代。相关样片和转接板样品可在雷龙公司官网进行申请试用。

大致实现功能为:用 LWIP 协议栈的 tcp 协议实现远程更新 表贴SD卡的功能,当输入“ update”命令时更新 SD卡并反馈信息,当输入“ clear”命令时之前传输的数据无效。

硬件平台搭建

新建工程,创建 block design。添加ZYNQ7 IP,对zynq进行初始化配置,对应板卡配置勾选SD,UART以及ENET资源,

如使用相同型号的板卡,可设置该部分为相同配置。

勾选DDR,并设置为PYNQZ2板卡的DDR的信息,

取消勾选多余资源,点击OK,完成硬件设计。如下图:

然后我们进行generate output product 然后生成HDL封装。这里没有进行使用PL资源,也不需要进行综合布局,在导出硬件时也不用包含bit流文件。

SDK软件部分

打开SDK后,新建application project,这里为了方便lwip设置,可选用使用lwip的相关模板,这里选择lwip tcp回环测试模板,保存新建工程。

选中新建好的工程,选择右击选中设置板载支持包,除了勾选lwip的板级支持包外,还需勾选sd卡需要的文件模式支持包。

点击standalone下的xilffs,可以对文件系统进行配置,这里可以使能长文件名有效,改变勾选为true。

保留模板例程的中的platform配置文件,删除其余文件。

修改main.c文件

修改main.c文件为如下:

#include <stdio.h>
#include "xparameters.h"
#include "netif/xadapter.h"
#include "platform.h"
#include "platform_config.h"
#include "lwipopts.h"
#include "xil_printf.h"
#include "sleep.h"
#include "lwip/priv/tcp_priv.h"
#include "lwip/init.h"
#include "lwip/inet.h"

#if LWIP_IPV6==1
#include "lwip/ip6_addr.h"
#include "lwip/ip6.h"
#else

#if LWIP_DHCP==1
#include "lwip/dhcp.h"
extern volatile int dhcp_timoutcntr;
#endif
#define DEFAULT_IP_ADDRESS  "192.168.1.10"
#define DEFAULT_IP_MASK     "255.255.255.0"
#define DEFAULT_GW_ADDRESS  "192.168.1.1"
#endif /* LWIP_IPV6 */

extern volatile int TcpFastTmrFlag;
extern volatile int TcpSlowTmrFlag;

void platform_enable_interrupts(void);
void start_application(void);
void print_app_header(void);
int transfer_data();

struct netif server_netif;

#if LWIP_IPV6==1
static void print_ipv6(char *msg, ip_addr_t *ip)
{
    print(msg);
    xil_printf(" %s\n\r", inet6_ntoa(*ip));
}
#else
static void print_ip(char *msg, ip_addr_t *ip)
{
    print(msg);
    xil_printf("%d.%d.%d.%d\r\n", ip4_addr1(ip), ip4_addr2(ip),
            ip4_addr3(ip), ip4_addr4(ip));
}

static void print_ip_settings(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
    print_ip("Board IP:       ", ip);
    print_ip("Netmask :       ", mask);
    print_ip("Gateway :       ", gw);
}

static void assign_default_ip(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
    int err;

    xil_printf("Configuring default IP %s \r\n", DEFAULT_IP_ADDRESS);

    err = inet_aton(DEFAULT_IP_ADDRESS, ip);
    if (!err)
        xil_printf("Invalid default IP address: %d\r\n", err);

    err = inet_aton(DEFAULT_IP_MASK, mask);
    if (!err)
        xil_printf("Invalid default IP MASK: %d\r\n", err);

    err = inet_aton(DEFAULT_GW_ADDRESS, gw);
    if (!err)
        xil_printf("Invalid default gateway address: %d\r\n", err);
}
#endif /* LWIP_IPV6 */

int main(void)
{
    struct netif *netif;

    //设置开发板的MAC地址
    unsigned char mac_ethernet_address[] = {
        0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };

    netif = &server_netif;

    init_platform();

    print_app_header();

    //初始化lwIP
    lwip_init();

    //将网络接口添加到netif,并将其设置为默认值
    if (!xemac_add(netif, NULL, NULL, NULL, mac_ethernet_address,
                PLATFORM_EMAC_BASEADDR)) {
        xil_printf("Error adding N/W interface\r\n");
        return -1;
    }

#if LWIP_IPV6==1
    netif->ip6_autoconfig_enabled = 1;
    netif_create_ip6_linklocal_address(netif, 1);
    netif_ip6_addr_set_state(netif, 0, IP6_ADDR_VALID);
    print_ipv6("\n\rlink local IPv6 address is:", &netif->ip6_addr[0]);
#endif /* LWIP_IPV6 */

    netif_set_default(netif);

    //使能中断
    platform_enable_interrupts();

    //指定网络是否已启动
    netif_set_up(netif);

#if (LWIP_IPV6==0)
#if (LWIP_DHCP==1)
    //创建新的DHCP客户端
    dhcp_start(netif);
    dhcp_timoutcntr = 2;
    while (((netif->ip_addr.addr) == 0) && (dhcp_timoutcntr > 0))
        xemacif_input(netif);

    if (dhcp_timoutcntr <= 0) {
        if ((netif->ip_addr.addr) == 0) {
            xil_printf("ERROR: DHCP request timed out\r\n");
            assign_default_ip(&(netif->ip_addr),
                    &(netif->netmask), &(netif->gw));
        }
    }

#else
    assign_default_ip(&(netif->ip_addr), &(netif->netmask), &(netif->gw));
#endif
    print_ip_settings(&(netif->ip_addr), &(netif->netmask), &(netif->gw));
#endif /* LWIP_IPV6 */

    //启动应用程序
    start_application();

    while (1) {
        if (TcpFastTmrFlag) {
            tcp_fasttmr();
            TcpFastTmrFlag = 0;
        }
        if (TcpSlowTmrFlag) {
            tcp_slowtmr();
            TcpSlowTmrFlag = 0;
        }
        xemacif_input(netif);
        transfer_data();
    }

    cleanup_platform();

    return 0;
}

添加remote_update.h文件

#ifndef REMOTE_UPDATE_H_
#define REMOTE_UPDATE_H_

#include "xparameters.h"
#include "xtime_l.h"
#include "xstatus.h"
#include <stdio.h>

//服务器端口
#define SER_PORT            5678
//接收的最大文件大小16MB
#define MAX_FLASH_LEN       16*1024*1024

void sent_msg(const char *msg);

#endif

添加remote_update.c文件

#include "remote_update.h"
#include "xparameters.h"
#include "ff.h"
#include "string.h"
#include <stdio.h>
#include "lwip/err.h"
#include "lwip/tcp.h"
#include "xil_printf.h"

u8 start_update_flag = 0;
u8 rxbuffer[MAX_FLASH_LEN];
u32 total_bytes = 0;
#define FILE_NAME "BOOT.bin"
struct tcp_pcb *c_pcb;
FATFS fs;
void print_app_header()
{
    xil_printf("-----SD remote update demo------\n");
}
//挂载sd卡
void sd_mount(){

	FRESULT status;
	BYTE work[FF_MAX_SS];
	//挂载sd卡,注册文件系统对象
	status=f_mount(&fs,"",1);
	if(status != FR_OK){
		printf("%d\n",status);
		printf("It isn't FAT format\n");
		f_mkfs("",FM_FAT32,0,work,sizeof work);
		f_mount(&fs,"",1);
	}
}
//写数据
void sd_write_data(u8 wr_dat[], u32 wr_len){
	FIL fil;
	UINT bw;
	//创建或者打开文件
	f_open(&fil,FILE_NAME,FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
	//移动读写指针
	f_lseek(&fil, 0);
	//写数据
	f_write(&fil,wr_dat,wr_len,&bw);
	//关闭文件
	f_close(&fil);
}

//将接收到的BOOT.bin文件写入到SD中
int transfer_data()
{
    char msg[60];
    if (start_update_flag) {
        xil_printf("\r\nStart SD Update!\r\n");
        xil_printf("file size of BOOT.bin is %lu Bytes\r\n", total_bytes);
        sprintf(msg, "file size of BOOT.bin is %lu Bytes\r\n",total_bytes);
        sent_msg(msg);
        sd_write_data(rxbuffer,total_bytes);
        xil_printf("SD Update finish!\n");
        total_bytes = 0;
    }

    start_update_flag = 0;

    return 0;
}

//向客户端回送信息
void sent_msg(const char *msg)
{
    err_t err;
    tcp_nagle_disable(c_pcb);
    if (tcp_sndbuf(c_pcb) > strlen(msg)) {
        err = tcp_write(c_pcb, msg, strlen(msg), TCP_WRITE_FLAG_COPY);
        if (err != ERR_OK)
            xil_printf("tcp_server: Error on tcp_write: %d\r\n", err);
        err = tcp_output(c_pcb);
        if (err != ERR_OK)
            xil_printf("tcp_server: Error on tcp_output: %d\r\n", err);
    } else
        xil_printf("no space in tcp_sndbuf\r\n");
}

//接收回调函数
static err_t recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    struct pbuf *q;

    if (!p) {
        tcp_close(tpcb);
        tcp_recv(tpcb, NULL);
        xil_printf("tcp connection closed\r\n");
        return ERR_OK;
    }
    q = p;

    if (q->tot_len == 6 && !(memcmp("update", p->payload, 6))) {
        start_update_flag = 1;
        sent_msg("\r\nStart SD Update\r\n");
    } else if (q->tot_len == 5 && !(memcmp("clear", p->payload, 5))) {
        start_update_flag = 0;
        total_bytes = 0;
        sent_msg("Clear received data\r\n");
        xil_printf("Clear received data\r\n");
    } else {
        while (q->tot_len != q->len) {
            memcpy(&rxbuffer[total_bytes], q->payload, q->len);
            total_bytes += q->len;
            q = q->next;
        }
        memcpy(&rxbuffer[total_bytes], q->payload, q->len);
        total_bytes += q->len;
    }

    tcp_recved(tpcb, p->tot_len);
    pbuf_free(p);

    return ERR_OK;
}

err_t accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err)
{
    xil_printf("tcp_server: Connection Accepted\r\n");
    c_pcb = newpcb;             //保存连接的客户端PCB
    //设置接收回调
    tcp_recv(c_pcb, recv_callback);
    tcp_arg(c_pcb, NULL);

    return ERR_OK;
}

int start_application()
{
    struct tcp_pcb *pcb;
    err_t err;
    //挂载SD卡
    sd_mount();
    xil_printf("Successfully init SD\r\n");
    print_app_header();
    //创建TCP PCB
    pcb = tcp_new_ip_type(IPADDR_TYPE_ANY);
    if (!pcb) {
        xil_printf("Error creating PCB. Out of Memory\n\r");
        return -1;
    }

    //绑定端口号
    err = tcp_bind(pcb, IP_ANY_TYPE, SER_PORT);
    if (err != ERR_OK) {
        xil_printf("Unable to bind to port %d: err = %d\n\r", SER_PORT, err);
        return -2;
    }

    //此处不需要回调函数的任何参数
    tcp_arg(pcb, NULL);

    //侦听连接
    pcb = tcp_listen(pcb);
    if (!pcb) {
        xil_printf("Out of memory while tcp_listen\n\r");
        return -3;
    }

    //指定用于传入连接的回调
    tcp_accept(pcb, accept_callback);
    xil_printf("TCP server started @ port %d\n\r", SER_PORT);

    return 0;
}

完成代码编写后,进行烧写验证。

下载验证

打开网络调试助手,选择协议类型为TCP客户端,选择远程主机的IP地址和端口,选择需要加载的应用程序的bin文件,勾选加载文件数据源,点击发送。

发送完成后在发送框选择输入“update”更新SD卡的应用程序。

在串口终端中查看调试信息,表示SD卡程序更新完成。

使用读卡器查看贴片SD卡转接卡是否正常存储到SD卡中,读取文件可知已经正常写入。

将板卡启动模式调整至SD卡模式,上电重启板卡程序,观察到板卡程序成功启动。

有关ZYNQ - 以太网远程更新SD卡应用程序的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  3. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  4. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  5. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  6. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  7. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  8. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  9. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  10. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

随机推荐