ZYNQ - 以太网远程更新贴片SD卡应用程序【SD NAND应用】

news2024/11/15 1:58:59

写在前面

对于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卡模式,上电重启板卡程序,观察到板卡程序成功启动。

————————————————

  1. 【本文转载自CSDN,作者:Vuko-wxh】

  亲爱的卡友们,欢迎光临雷龙官网,如果看完文章之后还是有疑惑或不懂的地方,请联系我们,自己去理解或猜答案是件很累的事,请把最麻烦的事情交给我们来处理,术业有专攻,闻道有先后,深圳市雷龙发展专注存储行业13年,专业提供小容量存储解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/642985.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解析Java异常机制:捕获编程中的错误,保障代码稳定性

工作中&#xff0c;程序遇到的情况不可能完美。比如&#xff1a;程序要打开某个文件&#xff0c;这个文件可能不存在或者文件格式不对&#xff1b;程序在运行着&#xff0c;但是内存或硬盘可能满了等等。 软件程序在运行过程中&#xff0c;非常可能遇到刚刚提到的这些问题&…

java OpenCSV自定义列标题和列位置

背景:最近使用csv进行数据导出&#xff0c;提高导出性能 问题&#xff1a;CsvBindByName和CsvBindByPosition不能同时用&#xff0c;要么是没标题要么是不是指定的排序规则 实现思路&#xff1a; 自定义排序规则&#xff0c;HeaderNameBaseMappingStrategy的writeOrder 属性可…

C++基于jrtp实现rtp发送与接收代码实现(附源码)

C++常用功能源码系列 文章目录 C++常用功能源码系列前言一、jrtp是什么二、rtp sender源码三、rtp receive源码总结前言 本文是C/C++常用功能代码封装专栏的导航贴。部分来源于实战项目中的部分功能提炼,希望能够达到你在自己的项目中拿来就用的效果,这样更好的服务于工作实…

Mininet拓扑构建与命令使用

实验目的&#xff1a; 1、通过命令模拟创建SDN网络。 2、深入了解Mininet的命令使用。 3、学会使用网络构建启动参数、内部交互命令及外部运行参数。 实验环境&#xff1a; 设备名称软件环境硬件环境主机Mininet_2.2.0_desktop_cv1.1CPU&#xff1a;1核 内存&#xff1a;2G 磁…

【MySQL数据库】MySQL数据库管理

MySQL数据库管理 一、数据库简述1.1简介1.2数据库基本概念1.3数据库的发展史1.3主流的数据库介绍1.4数据库的类型1.4.1关系型数据库特点1.4.2非关系型数据库介绍 1.5SQL语言分类 二、数据库基础应用2.1查看数据库2.2创建新的库、表2.3删除库、表2.4管理表中的数据记录2.4.1插入…

Java新技术和趋势:如何应对Java生态的变化和发展趋势

章节一&#xff1a;引言 Java是一门广泛使用的编程语言&#xff0c;具有强大的生态系统和持续的发展。随着时间的推移&#xff0c;Java生态系统不断演进和改变&#xff0c;出现了许多新技术和趋势。在本文中&#xff0c;我们将探讨一些最新的Java技术和趋势&#xff0c;并分享…

路径规划算法:基于适应度相关优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于适应度相关优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于适应度相关优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能…

路径规划算法:基于社会群体优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于社会群体优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于社会群体优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

【Linux】Nginx+Tomcat负载均衡、动静分离、多实例部署

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 NginxTomcat负载均衡、动静分离、多实例部署 一、Tomcat 多实例部署1.安装好 jdk 及 tomcat2.配置 tomcat 环境变量3.修改 tomcat2 中的 server.xml 文件4.修改各 tomcat 文件…

RocketMQ源码解析之消息存储机制

RocketMQ是一款开源的分布式消息中间件,在大数据、云计算等领域具有广泛的应用。要想深入了解RocketMQ的源码实现,首先需要了解它的消息存储机制。 RocketMQ采用的是文件存储的方式,即RocketMQ将消息以文件的形式存储在磁盘上。具体来说,RocketMQ将消息存储在commitlog文件…

精通postman教程(三)postman各模块详解

作为一名测试工程师&#xff0c;那么Postman绝对是大伙必备的工具之一。 在这个系列教程中&#xff0c;我将为大伙详细讲解如何使用Postman进行API测试。 今天我为大伙介绍Postman的各个模块&#xff0c;让你们快速上手这款工具。 一、顶部及左侧工作栏 1、My Workspace - …

java基础(多线程)-常用方法具体使用

一、Thread.start()方法 start()方法&#xff1a;启动子线程 new Thread();当前线程的状态为NEW 调用start()方法之后当前线程的状态变为RUNNABLE 二、Thread.sleep()静态方法 1. 调用sleep会让当前线程从Running进入Timed Waiting 状态 2.其他线程可以使用interrupt方法…

ES6相关概念

什么是ES6&#xff1f; ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 为什么使用 ES6 ? 每一次标准的诞生都意味着语言的完善&#xff0c;功能的加强。JavaScript语言本身也有一些令人不满意的地方。 变量提升特性增加了程序运行…

一文讲清超算,高性能计算,并行计算,分布式计算的区别

摘要: 超算、高性能计算、并行计算和分布式计算是计算领域中的重要概念&#xff0c;但它们之间的区别常常令人困惑。本文将为你解析它们的概念与特点&#xff0c;让你在这个领域里轻松游刃有余。 ... ... 超算、高性能计算、并行计算和分布式计算是计算领域中的重要概念&#x…

LVS + Keepalived群集

文章目录 LVS Keepalived1 Keepalived及其工作原理2 一个健康的集群的特点3 Keepalived常见问题脑裂3.1 原因3.2 预防3.3 配置 4 部署keepalived4.1 部署192.168.146.204.2 部署192.168.146.30 5 LVSKeepalived高可用群集5.1 部署192.168.146.50(nginx)5.2 部署192.168.146.60…

在家就能赚钱的方法,我不允许你还不知道

近年来&#xff0c;随着互联网的迅猛发展&#xff0c;具有敏锐嗅觉的人们只需利用互联网&#xff0c;就能愉快地获取收入。一般而言&#xff0c;在线赚钱所需的投资较少&#xff0c;有时只需一台可上网的电脑或手机即可满足&#xff0c;因此有时被称为“零成本或低成本网络赚钱…

Vue学习 之 第一个Vue应用

第一个 Vue 应用 安装WebStorm 在这之前&#xff0c;已下载安装好了WebStorm。 打开WebStorm&#xff0c;点击”“就可以创建一个新的应用。 项目文件目录地址&#xff1a; /Users/morris/WebstormProjects 或者在 file -> new priject 也可以新建一个项目 新建一个 ht…

中小商户如何实现门店数字化运营与管理,有没有好用的工具推荐?

中小商户开展门店数字化运营与管理&#xff0c;能够实现在线支付、退货、换货等快速操作&#xff0c;针对性的精准营销和服务&#xff0c;增加客户忠诚度。 门店数字化运营与管理可以帮助商家建立全面、实时的业务数据管理系统&#xff0c;提高工作效率和客户满意度&#xff0c…

线程池的主要处理流程及常用方法

线程池的主要处理流程及常用方法 更多优秀文章&#xff0c;请扫码关注个人微信公众号或搜索“程序猿小杨”添加。 一、主要处理流程&#xff1a; 当调用线程池execute() 方法添加一个任务时&#xff0c; threadPoolExecutor.execute&#xff0c;具体代码如下&#xff1a; p…

013 - STM32学习笔记 - DMA_存储器到外设

011 - STM32学习笔记 - DMA_存储器到外设 DMA存储器到外设 上节学习了DMA的存储器到存储器&#xff0c;这节了解一下存储器到外设&#xff0c;以存储器到USART1为例&#xff0c;DMA的配置函数M2P_DMA_Config流程跟上节的基本一致&#xff0c;区别在于需要选择DMA的通道和数据…