lwip-2.1.3自带的httpd网页服务器使用教程(一)从SD卡读取网页文件并显示

news2024/12/24 9:00:29

概述

本教程使用的单片机是STM32F103ZE,有线网口芯片为ENC28J60。
本教程里面的网页由于需要兼容Windows XP系统的IE8浏览器,所以采用HTML 4.01编写,不使用任何前端框架。笔者使用的网页设计软件是Adobe Dreamweaver CS3。
开发板PCB文件是公开的,大家可以拿去打印出来,焊好器件后,就可以直接跑本教程提供的所有示例程序。


lwip-2.1.3在STM32F103ZE+ENC28J60有线网卡上无操作系统移植(使用STM32 HAL库)_lwip stm32f103_巨大八爪鱼的博客-CSDN博客一、概述以太网芯片简介ENC28J60是一款10Mbps速率的以太网MAC+PHY芯片,和单片机的通信接口为SPI,SPI最高时钟频率为20MHz。ENC28J60支持半双工和全双工模式,但是不支持自动协商。在支持自动协商的网络环境中,ENC28J60默认的工作模式是半双工模式。另外,STM32本身有一个ETH外设,这个外设采用的接口是MII或RMII,不是SPI,所以不能连接ENC28J60芯片,这次我们用不到这个ETH外设。STM32本身的ETH外设相当于MAC,通常要外接一个PHY芯片(_lwip stm32f103https://blog.csdn.net/ZLK1214/article/details/121419417

makefsdata程序的使用

默认情况下,lwip httpd服务器使用的网页文件来源于lwip-2.1.3/apps/http/fsdata.c。
fsdata.c是由lwip自带的makefsdata程序将lwip-2.1.3/apps/http/fs里面的所有文件打包后得到的。
lwip只提供了makefsdata程序的C语言源代码,如果想要在Windows系统上运行,需要用Visual Studio编译代码,生成exe文件,放到http目录下(也就是有fs文件夹的目录下),双击即可使用。编译的方法请参考下面这篇文章。
(本文例程里面已经放好了编译好的makefsdata程序,可直接使用,不用再自己重新编译)
在Visual Studio 2012下编译lwip-2.1.3 httpd的makefsdata_makefsdata.exe_巨大八爪鱼的博客-CSDN博客步骤一 建立空白工程新建一个工程,项目名称起名为makefsdata。因为mkfsdata是一个控制台程序,所以我们要选择Win32 Console Application。这里很重要,必须要勾选“Empty Project”选项,建立空项目:建好的解决方案目录是C:\Users\Octopus\Desktop\makefsdata,工程目录是C:\Users\Octopus\Desktop\makefsdata\makefsdata。步骤二 解压lwip-2.1.3的部分头文件和_makefsdata.exehttps://blog.csdn.net/ZLK1214/article/details/122490482双击运行makefsdata程序后,程序自动将fs文件夹下的所有内容打包成一个fsdata.c文件。每次修改完fs文件夹里面的网页文件和图片后,只需要重新运行makefsdata程序,就能打包生成新的fsdata.c文件。

STM32的Keil工程里面只需要添加fs.c和httpd.c这两个C文件,不能把fsdata.c添加到Keil工程里面去。这是因为fs.c里面用#include指令包含了fsdata.c文件。

#define HTTPD_FSDATA_FILE "fsdata.c" // httpd_opts.h
#include HTTPD_FSDATA_FILE // fs.c

fsdata.c也可以改成其他自己喜欢的名字,把扩展名改成.h都行,改名后要把新的文件名定义到lwipopts.h中(注意不是改fs.c或者httpd_opts.h)。新命名的文件同样不能添加到Keil工程中。

#define HTTPD_FSDATA_FILE "fsdata_custom.h"

fsdata.c里面以C语言数组的形式保存了fs文件夹下的所有文件,包括html文件和图片文件,以及其他类型的文件。zip、rar压缩包,还有exe文件都可以放进去,用户用浏览器访问这些非网页文件的时候,浏览器不会直接显示这些文件的内容,而是启动下载工具,把文件下载下来,让用户选择一个电脑上的文件夹保存。
当用户通过浏览器访问一个不存在的网页时,lwip会自动重定向到404.html错误页面。
Keil工程编译并烧写到STM32单片机后,最终这些网页内容是保存到STM32单片机的Flash中的。STM32单片机的Flash容量是有限的,只有几百KB~几MB,能存放的网页文件非常有限。如果网页个数非常多,非常大,Flash放不下的话,就得想其他的办法。

动态加载SD卡上的网页文件(一次性加载)

(本节例程名称:webpages_from_sdcard)
既然STM32本身的Flash放不下网页文件,那我们可以外接一个SPI Flash(如W25Q128)或者一张SD卡来保存网页文件。如果用SD卡的话,能保存的网页文件就非常多了,保存一个几百兆的压缩包供用户下载都不是问题。
存储器可以使用FAT、FAT32或者exFAT文件系统,以文件的形式存储网页文件。STM32通过FatFS读取存储器中存储的文件。
笔者是把所有的网页文件放到SD卡的www目录下,如下图所示。

要想让lwip的httpd服务器从SPI Flash或SD卡上动态加载网页文件并显示的话,需要在lwipopts.h中定义下面两个宏。

// 配置HTTPD
#define LWIP_HTTPD_CUSTOM_FILES 1
#define LWIP_HTTPD_DYNAMIC_HEADERS 1

然后我们需要自己实现下面两个接口函数。

int fs_open_custom(struct fs_file *file, const char *name);
void fs_close_custom(struct fs_file *file);

fs_open_custom函数的功能是读取指定的网页文件的内容。函数返回1表示文件读取成功,返回0表示文件读取失败。
fs_close_custom函数的功能是释放fs_open_custom函数里面分配的内存块,关闭已打开的文件。

我们新建一个webpages_test.c文件,在里面实现这两个函数。

#include <ff.h>
#include <lwip/apps/fs.h>
#include <lwip/mem.h>
#include <string.h>

int fs_open_custom(struct fs_file *file, const char *name)
{
  char path[260];
  int i, size;
  int ret = 0;
  void *mem;
  FIL *fp;
  FRESULT fr;
  UINT br;
  
  // name为要访问的网页URL, 是相对路径, 将其转换成绝对路径
  snprintf(path, sizeof(path), "C:\\www%s", name);
  for (i = 0; path[i] != '\0'; i++)
  {
    if (path[i] == '/')
      path[i] = '\\';
  }
  if (path[i - 1] == '\\' && sizeof(path) - i >= 10 + 1)
    strcat(path, "index.html");
  
  fp = mem_malloc(sizeof(FIL));
  if (fp != NULL)
  {
    fr = f_open(fp, path, FA_READ); // 打开文件
    if (fr == FR_OK)
    {
      size = f_size(fp); // 获取文件大小
      mem = mem_malloc(size);
      if (mem != NULL)
      {
        f_read(fp, mem, size, &br); // 一次性读完整个文件
        
        file->data = mem; // 要显示的网页内容
        file->pextension = mem; // http连接关闭时要在fs_close_custom()函数中释放的内存块
        file->len = size; // 要显示的网页内容的长度
        file->index = size; // 要显示的网页内容的长度
        printf("%s(%p): %s\n", __func__, file->pextension, path);
        ret = 1;
      }
      f_close(fp); // 关闭文件
    }
    mem_free(fp);
  }
  // 函数返回1表示网页文件存在, 返回0表示网页文件不存在
  // 网页文件不存在时httpd会去加载lwip-2.1.3/apps/http/makefsdata里面的默认网页
  return ret;
}

void fs_close_custom(struct fs_file *file)
{
  printf("%s(%p)\n", __func__, file->pextension);
  if (file->pextension != NULL)
  {
    mem_free(file->pextension);
    file->pextension = NULL;
  }
}

fs_open_custom函数的name参数的值跟浏览器里面访问的网址有关。如果浏览器访问的网址是http://stm32f103ze/entertainment.html,那么name="/entertainment.html"。
在上面的程序中,将name与“C:\www”字符串拼接(C语言中字符串里面的反斜杠要双写),再把所有的正斜杠替换成反斜杠,就得到了“C:\www\entertainment.html”这个路径,打开的是SD卡www文件夹下的entertainment.html文件。如果浏览器访问的是一个文件夹,则自动在后面追加index.html,实际访问的是这个文件夹下的index.html文件。
把文件路径保存到path字符数组中,用FatFs的f_open函数以只读的方式打开网页文件,再用f_size函数获取网页文件的大小(size)。
用lwip的mem_malloc函数分配size大小的内存,接着用f_read函数把整个文件的内容一次性读取到新分配的这块内存里面。
把保存有网页内容的内存块指针mem赋给file->data,网页的大小赋给file->len和file->index这两个成员,lwip的httpd服务器就能显示SD卡上的网页了。
mem内存块会在fs_open_custom函数执行完毕后由lwip使用,所以mem内存块不能在fs_open_custom函数里面释放,那在哪里释放呢?fs_close_custom函数就是用来完成这个任务的。
我们把mem内存块的地址赋给file->pextension成员,lwip不会使用file->pextension这个成员变量,变量的值可以随便定义。
lwip发送完全部网页内容后就会调用fs_close_custom函数,在这个函数中用file->pextension访问刚才的mem内存块,用mem_free函数回收内存。

 

 

 

如果访问了sd卡上不存在的网页,则fs_open_custom函数返回0,lwip会去makefsdata的fs文件夹里面寻找,如果还是没找到,则会显示makefsdata的404.html错误页面。

这种方法是把整个网页文件一次性读取到单片机的SRAM内存中,再由lwip httpd通过网络发送给浏览器显示。
要是网页文件很大,SRAM一次性放不下,那就只有换一种分多次读取的方法了。

动态加载SD卡上的网页文件(分多次读取)

如果想要多次读取同一个网页文件,可以在lwipopts.h中定义LWIP_HTTPD_DYNAMIC_FILE_READ=1,然后实现int fs_read_custom(struct fs_file *file, char *buffer, int count)函数。

// 配置HTTPD
#define LWIP_HTTPD_CUSTOM_FILES 1
#define LWIP_HTTPD_DYNAMIC_FILE_READ 1
#define LWIP_HTTPD_DYNAMIC_HEADERS 1
#include <ff.h>
#include <lwip/apps/fs.h>
#include <lwip/mem.h>
#include <string.h>

struct file_content
{
  FIL fil;
  char buffer[2500];
  int pos;
};

int fs_open_custom(struct fs_file *file, const char *name)
{
  char path[260];
  int i, ret = 0;
  struct file_content *content;
  FRESULT fr;
  
  // name为要访问的网页URL, 是相对路径, 将其转换成绝对路径
  snprintf(path, sizeof(path), "C:\\public%s", name);
  for (i = 0; path[i] != '\0'; i++)
  {
    if (path[i] == '/')
      path[i] = '\\';
  }
  if (path[i - 1] == '\\' && sizeof(path) - i >= 10 + 1)
    strcat(path, "index.html");
    
  content = mem_malloc(sizeof(struct file_content));
  if (content != NULL)
  {
    content->pos = 0;
    fr = f_open(&content->fil, path, FA_READ); // 打开文件
    if (fr == FR_OK)
    {
      // 网页文件存在
      file->len = sizeof(content->buffer); // 指定fs_read_custom()函数的count参数的最大值(不能设置为0)
      file->pextension = content; // 保存分配的内存指针, http连接关闭时才释放
      printf("%s(%p): %s\n", __func__, content, path);
      ret = 1;
    }
    else
    {
      // 网页文件不存在
      mem_free(content);
      content = NULL;
    }
  }
  // 函数返回1表示网页文件存在, 返回0表示网页文件不存在
  // 网页文件不存在时httpd会去加载lwip-2.1.3/apps/http/makefsdata里面的默认网页
  return ret;
}

void fs_close_custom(struct fs_file *file)
{
  struct file_content *content = file->pextension;
  
  printf("%s(%p)\n", __func__, content);
  if (content != NULL)
  {
    f_close(&content->fil); // 关闭文件
    mem_free(content); // 释放内存
    file->pextension = NULL;
  }
}

int fs_read_custom(struct fs_file *file, char *buffer, int count)
{
  struct file_content *content = file->pextension;
  UINT br;
  
  // count为要读取的字节数, br为实际读取到的字节数
  f_read(&content->fil, buffer, count, &br);
  if (br == 0)
  {
    // 文件读取完毕
    printf("%s(%p): end of file\n", __func__, content);
    return FS_READ_EOF;
  }
  printf("%s(%p): %d~%d\n", __func__, content, content->pos, content->pos + br - 1);
  content->pos += br;
  return br;
}

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

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

相关文章

推荐Selenium 自动化测试实战

你将获得 深入 Selenium 源码、原理、封装、技巧&#xff1b; unittest、pytest、DDT、POM 迭代测试方法&#xff1b; 大型项目分布式测试解决方案&#xff1b; Jenkins 持续集成和交付。 演示地址&#xff1a;www.runruncode.com/portal/article/index/id/19451/cid/85.html 课…

【无线通信专题】NFC基本原理

NFC定义 NFC(Near Field Communication)近场通信。 NFC早期应用 NFC最开始的应用主要用于金融领域,POS机(reader)通过非接触的方式与银行卡(带NFC接口的卡片)进行交互得到银行卡信息并完成支付。因为NFC的通信距离比较近,所以安全性较高。 后来随着手机支付的流行。…

使用STM32实现 蓝牙插座

硬件介绍 蓝牙模块HC-01&#xff0c;其实之前就用过&#xff0c;使用起来非常简单 继电器模块&#xff0c; (VCC 3.3V)当左侧IN输入低电平时&#xff0c;右侧的ON 和 COM会导通&#xff0c;左上的绿灯会亮&#xff0c;此处充当插座的角色 项目需求 通过蓝牙的串口发送open打开…

JMeter 中 3 种参数值的传递

目录 前言&#xff1a; (一) 从 CSV 文件读取要批量输入的变量 (二) 利用 Cookie 进行值的传递 (三) 利用正则匹配提取上一个接口的返回数据作为下个请求的输入 前言&#xff1a; 在JMeter中&#xff0c;参数值的传递是非常重要的&#xff0c;因为它允许你在测试过程中动态…

Spring 如何解决 Bean 的循环依赖(循环引用)

Component public class A {Autowiredprivate B b;}Component public class B {Autowiredprivate A a;}上面的情况就是 循环依赖 Bean的创建初始化过程如下 如果不采取措施&#xff0c;那么循环依赖就会进入死循环 但 Spring 已经帮我们解决了大部分循环依赖问题 具体是如何解…

RabbitMQ的使用详解

一、什么是MQ 1、什么是MQ MQ&#xff08;message queue&#xff09;&#xff0c;本质是个队列&#xff0c;FIFO先入先出。只不过队列中放的是message&#xff0c;是一种跨进程的通信机制&#xff0c;用于上下游传递消息。在互联网架构中&#xff0c;MQ是一种非常常见的上下游…

EMC案例-接地环路对传导骚扰测试的影响

EMC测试案例分析——接地环路对传导骚扰测试的影响 本文主要就接地环路对传导骚扰测试的影响进行简要举例分析&#xff0c;为我们以后的测试方法提供参考。 Part 1 现象描述 某电子设备在进行传导骚扰测试时&#xff0c;在3MHz左右的频率点出现了超限的情况&#xff0c;其测…

ELK-日志服务【es-安装使用】

目录 【1】安装-配置elasticsearch&#xff08;01、02、03相同&#xff09; 端口 【2】安装-配置-启动-Kibana 【3】浏览器访问测试&#xff08;10.0.0.21:5601&#xff09; 【4】使用kibana创建、更新、删除es索引、文档 【5】组es集群&#xff08;投票选举机制&#xf…

用户体验在APP开发中的关键性作用

在 APP开发过程中&#xff0c;如何设计才能让用户感到满意&#xff0c;是非常重要的一点&#xff0c; APP开发公司需要不断地学习新的 APP设计知识&#xff0c;因为只有这样才能设计出令人印象深刻的 APP。对于用户来说&#xff0c;产品的用户体验在很大程度上决定了产品的竞争…

引入头文件#include <iostream>的时候发生了什么?

<iostream> namespace std {extern istream cin;extern ostream cout;extern ostream cerr;extern ostream clog;extern wistream wcin;extern wostream wcout;extern wostream wcerr;extern wostream wclog;};cin是什么&#xff1f; cin extern istream cin; The objec…

elasticsearch集群部署搭建(一)

elasticsearch集群部署搭建&#xff08;一&#xff09; 部署信息JDK安装下载es安装包部署安装创建用户&#xff08;三台机器都执行&#xff09;解压安装包&#xff08;选择一台机器执行&#xff09;修改配置文件&#xff08;三台机器都执行&#xff09; 拷贝分发注册系统服务服…

微信小程序监听页面跳转API

// 放在app.js 里面的onshow生命周期里面wx.onAppRoute((res) > {console.log(路由跳转,res})})

基于B/S架构SaaS服务的实验室信息系统(LIS)

实验室信息系统LIS源码 实验室信息系统&#xff08;Laboratory Information System&#xff09;&#xff0c;简称LIS&#xff0c;是一个全面基于网络化应用&#xff0c;能够帮助用户按照规范内容和规范流程进行多角色、多层次检验信息及资源管理的系统。通过条码管理系统从HIS…

Java并发编程第一弹

1、线程的创建 创建线程的方式有两种&#xff0c; 第一种是通过继承 Thread 类&#xff0c;重写run 方法&#xff1b;第二种是通过实现 Runnable 接口 通过源码发现&#xff0c;创建线程只有一种方式那就是构造 Thread 类&#xff0c;而实现线程的执行单元则有两种方式&…

将node服务打包成可执行文件-PKG

背景 有时我们需要写一些node的服务或者是工具&#xff0c;但这些工具&服务可以运行的前提条件是当前环境需要安装好node&#xff0c;有时候我们把这些工具&服务发送给别人&#xff0c;在别人的电脑中未必有安装好的node版本&#xff0c;即便有也可能不是期望的指定的…

CMU 15-445 -- Join Algorithms - 09

CMU 15-445 -- Join Algorithms - 09 引言Join AlgorithmsJoin Operator OutputI/O Cost AnalysisNested Loop JoinSimple Nested Loop JoinBlock Nested Loop JoinIndex Nested Loop Join小结 Sort-Merge Join小结&#xff1a; Hash JoinBasic Hash Join AlgorithmGrace Hash …

如何获取铁粉

忽然发现我的铁粉从100变成了540&#xff0c;分享下我的经验&#xff0c;我觉得可能是我的机器人经常互动的问题&#xff0c;结合自己的看法和平台大佬的想法一些进行了梳理&#xff1a; 在当今社交媒体时代&#xff0c;吸引和保留铁粉&#xff08;忠实粉丝&#xff09;对于个…

Robocom2021 初赛

收录一下Robocom初赛的屌题&#xff0c;调了我一个多小时&#xff0c;是我菜了 题目详情 - 7-3 打怪升级 (pintia.cn) 题意&#xff1a; Code&#xff1a; #include<bits/stdc.h> using namespace std;int n, m, a, b, c, d, q, p; int f[1005][1005];const int N 2…

Vector - CANoe - 测试报告设置

file:///C:/Program%20Files/Vector%20CANoe%2015/Help01/CANoeCANalyzerHTML5/CANoeCANalyzer.htm#Topics/CANoeCANalyzer/Windows/TestConfigurations/TCConfigTC.htm 前面有过介绍&#xff0c;我们常用的测试报告还是以XML/HTML格式来生成测试报告&#xff0c;而对于XML/HTM…

【洛谷】P1342 请柬(正反建图+dijkstra)

1&#xff1a;思考&#xff1a; 从1到所用顶点简单&#xff08;单源最短路径。&#xff09;&#xff0c;重点在怎么解决所用点到1&#xff08;单终点最短路径&#xff09; 答案&#xff1a;反向建图使&#xff08;单终点最短路径→单源最短路径。&#xff09; 复杂度&#xf…