基于STM32的WEB服务器设计

发布时间:2023-05-31  

一、环境介绍

MCU:STM32F103ZET6

网卡:ENC28J60


协议栈: UIP

开发软件:Keil5


二、功能介绍

STM32控制ENC28J60+UIP协议栈创建TCP服务器(WEB服务器),支持浏览器访问完成数据传输。 浏览器可以实时显示温度、时间、可以控制STM32开发板上的LED灯、蜂鸣器。

基于STM32的WEB服务器设计


poYBAGDYdXCAWkKMAAAAK8RNs4s030.png基于STM32的WEB服务器设计


poYBAGDYdXCAWkKMAAAAK8RNs4s030.png基于STM32的WEB服务器设计


poYBAGDYdXCAWkKMAAAAK8RNs4s030.png


基于STM32的WEB服务器设计


poYBAGDYdXCAWkKMAAAAK8RNs4s030.png


基于STM32的WEB服务器设计poYBAGDYdXCAWkKMAAAAK8RNs4s030.png


基于STM32的WEB服务器设计poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

三、ENC28J60芯片介绍

ENC28J60 是带有行业标准串行外设接口(Serial Peripheral Interface,SPI)的独立以太网 控制器。它可作为任何配备有 SPI 的控制器的以太网接口。ENC28J60 符合 IEEE 802.3 的全部规范,采用了一系列包过滤机制以对传入数据包进行限制。 它还提供了一个内部 DMA 模块, 以实现快速数据吞吐和硬件支持的 IP 校验和计算。 与主控制器的通信通过两个中断引脚和 SPI 实现,数据传输速率高达 10 Mb/s。两个专用的引脚用于连接 LED,进行网络活动状态指示。ENC28J60 总共只有 28 脚,提供 QFN/TF。

ENC28J60 的主要特点如下:

兼容 IEEE802.3 协议的以太网控制器

集成 MAC 和 10 BASE-T 物理层

支持全双工和半双工模式

数据冲突时可编程自动重发

SPI 接口速度可达 10Mbps

8K 数据接收和发送双端口 RAM

提供快速数据移动的内部 DMA 控制器

可配置的接收和发送缓冲区大小

两个可编程 LED 输出

带7个中断源的两个中断引脚

TTL 电平输入

提供多种封装:SOIC/SSOP/SPDIP/QFN 等。


ENC28J60 由七个主要功能模块组成:

1) SPI 接口,充当主控制器和 ENC28J60 之间通信通道。

2) 控制寄存器,用于控制和监视 ENC28J60。

3) 双端口 RAM 缓冲器,用于接收和发送数据包。

4) 判优器,当 DMA、发送和接收模块发出请求时对 RAM 缓冲器的访问进行控制。

5) 总线接口,对通过 SPI 接收的数据和命令进行解析。

6) MAC(Medium Access Control)模块,实现符合 IEEE 802.3 标准的 MAC 逻辑。

7) PHY(物理层)模块,对双绞线上的模拟数据进行编码和译码。

ENC28J60 还包括其他支持模块,诸如振荡器、片内稳压器、电平变换器(提供可以接受 5V 电压的 I/O 引脚)和系统控制逻辑。

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

四、UIP 简介

uIP 由瑞典计算机科学学院(网络嵌入式系统小组)的Adam Dunkels 开发。其源代码由C 语 言编写,并完全公开,uIP 的最新版本是 1.0 版本。 uIP 协议栈去掉了完整的 TCP/IP 中不常用的功能,简化了通讯流程,但保留了网络通信 必须使用的协议,设计重点放在了 IP/TCP/ICMP/UDP/ARP 这些网络层和传输层协议上,保证 了其代码的通用性和结构的稳定性。

由于 uIP 协议栈专门为嵌入式系统而设计,因此还具有如下优越功能:

1) 代码非常少,其协议栈代码不到 6K,很方便阅读和移植。

2) 占用的内存数非常少,RAM 占用仅几百字节。

3) 其硬件处理层、协议栈层和应用层共用一个全局缓存区,不存在数据的拷贝,且发送 和接收都是依靠这个缓存区,极大的节省空间和时间。

4) 支持多个主动连接和被动连接并发。

5) 其源代码中提供一套实例程序:web 服务器,web 客户端,电子邮件发送程序(SMTP 客 户端),Telnet 服务器, DNS 主机名解析程序等。通用性强,移植起来基本不用修改就可以通过。

6) 对数据的处理采用轮循机制,不需要操作系统的支持。 由于 uIP 对资源的需求少和移植容易,大部分的 8 位微控制器都使用过uIP 协议栈, 而且很多的著名的嵌入式产品和项目(如卫星,Cisco 路由器,无线传感器网络)中都在使用 uIP 协议栈。 uIP 相当于一个代码库,通过一系列的函数实现与底层硬件和高层应用程序的通讯,对于 整个系统来说它内部的协议组是透明的,从而增加了协议的通用性。

uIP 提供的接口函数有:

1.初始化 uIP 协议栈:uip_init()

2.处理输入包:uip_input()

3.处理周期计时事件:uip_periodic()

4.开始监听端口:uip_listen()

5.连接到远程主机:uip_connect()

6.接收到连接请求:uip_connected()

7.主动关闭连接:uip_close()

8.连接被关闭:uip_closed()

9.发出去的数据被应答:uip_acked()

10.在当前连接发送数据:uip_send()

11.在当前连接上收到新的数据:uip_newdata()

12.告诉对方要停止连接:uip_stop()

13.连接被意外终止:uip_aborted()

基于STM32的WEB服务器设计

五、核心代码

5.1 main.c

#include "stm32f10x.h"

#include "led.h"

#include "delay.h"

#include "key.h"

#include "usart.h"

#include 

#include 

#include "enc28j60.h"

#include "time.h"


#include "uip.h"

#include "uip_arp.h"

#include "tapdev.h"

#include "timer.h"

#include "uip-conf.h"

#include "httpd.h"

#include "ds18b20.h"

#include "rtc.h"


void uip_EventPoll(void); //事件处理函数

#define UIP_BUF ((struct uip_eth_hdr *)&uip_buf[0])


/*

当Uip接收到Uip接收到底层传递的数据,将接收到的数据通过调用http_appcall(),传递给Webserver处理,

再通过handle_connection()先后调用handle_input()函数和handle_output()函数

handle_input()主要作用是分析http数据流:得到请求的路径、解析出请求的文件名称。

然后调用函数handle_output进行查找对应文件,进行发送到浏览器,完成交互。

注意:浏览器最好使用谷歌浏览器,否则会导致访问失败!

*/


int main()

{

    u8 key;

    u32 tcnt=0;

    uip_ipaddr_t ipaddr; //保存IP地址信息


    BeepInit();   //蜂鸣器初始化

    LedInit();      //LED灯初始化

    UsartInit(USART1,72,115200);

    KeyInit();      //按键初始化

    TimerInit(TIM6,72,10000);  //定时器初始化,

DS18B20_Init();

RTC_Init();

  SET_RTC_TIME(2019,5,24,10,58,20);

    printf("串口工作正常!rn");


    while(tapdev_init()) //初始化ENC28J60错误

    {

        printf("ENC28J60 Init Error!rn");

        Delay72M_Ms(500);

    }

    printf("ENC28J60 初始化成功!rn");


    uip_init();                   //uIP初始化

    uip_ipaddr(ipaddr, 192,168,1,89);   //填充开发板IP地址

    uip_sethostaddr(ipaddr);         //设置开发板IP地址


    uip_ipaddr(ipaddr, 192,168,1,1);   //填充开发板网关地址

    uip_setdraddr(ipaddr);             //设置开发板网关IP地址(其实就是你路由器的IP地址)


    uip_ipaddr(ipaddr, 255,255,255,0);  //填充开发板网络掩码

    uip_setnetmask(ipaddr);             //填充开发板网络掩码

    httpd_init();                       //创建WEB服务器,设置监听端口


    while(1)

    {

        uip_EventPoll(); //轮询方式处理处理网络数据

    }

}


/*

函数功能:uip事件处理函数,需要将该函数插入用户主循环,循环调用

*/

void uip_EventPoll(void)

{

    u8 i;

    static struct timer  arp_timer; //定义定时器

    static u8 timer_ok=0;


    if(timer_ok==0)//仅初始化一次

    {

        timer_ok = 1;

        timer_set(&arp_timer,CLOCK_SECOND*10);     //创建1个10秒的定时器

    }


    uip_len=tapdev_read(); //从网络设备读取一个IP包,得到数据长度.uip_len在uip.c中定义

    if(uip_len>0)     //有数据

    {

        //处理IP数据包(只有校验通过的IP包才会被接收)

        if(UIP_BUF->type == htons(UIP_ETHTYPE_IP))//是否是IP包?

        {

            uip_arp_ipin(); //去除以太网头结构,更新ARP表

            uip_input();    //IP包处理

            //当上面的函数执行后,如果需要发送数据,则全局变量 uip_len > 0

            //需要发送的数据在uip_buf, 长度是uip_len  (这是2个全局变量)

            if(uip_len>0)//需要回应数据

            {

                uip_arp_out();//加以太网头结构,在主动连接时可能要构造ARP请求

                tapdev_send();//发送数据到以太网

            }

        } else if (UIP_BUF->type==htons(UIP_ETHTYPE_ARP))//处理arp报文,是否是ARP请求包?

        {

            uip_arp_arpin();

            //当上面的函数执行后,如果需要发送数据,则全局变量uip_len>0

            //需要发送的数据在uip_buf, 长度是uip_len(这是2个全局变量)

            if(uip_len>0)tapdev_send();//需要发送数据,则通过tapdev_send发送

        }

    }

    //轮流处理每个TCP连接, UIP_CONNS缺省是40个

    for(i=0; i0

        //需要发送的数据在uip_buf, 长度是uip_len (这是2个全局变量)

        if(uip_len>0)

        {

            uip_arp_out();//加以太网头结构,在主动连接时可能要构造ARP请求

            tapdev_send();//发送数据到以太网

        }

    }


    //每隔10秒调用1次ARP定时器函数 用于定期ARP处理,ARP表10秒更新一次,旧的条目会被抛弃

    if(timer_expired(&arp_timer))

    {

        timer_reset(&arp_timer);

        uip_arp_timer();

    }

}


;>

poYBAGDYdXCAWkKMAAAAK8RNs4s030.png

5.2 enc28j60.c

#include "delay.h"

#include 

#include "enc28j60.h"


/*

以下是ENC28J60驱动移植接口:

MISO--->PA6----主机输入

MOSI--->PA7----主机输出

SCLK--->PA5----时钟信号

CS----->PA4----片选

RESET-->PG15---复位

*/

#define ENC28J60_CS PAout(4) //ENC28J60片选信号

#define ENC28J60_RST PGout(15) //ENC28J60复位信号

#define ENC28J60_MOSI PAout(7)  //输出

#define ENC28J60_MISO PAin(6)   //输入

#define ENC28J60_SCLK PAout(5)  //时钟线


static u8 ENC28J60BANK;

static u32 NextPacketPtr;



/*

函数功能:底层SPI接口收发一个字节

说    明:模拟SPI时序,ENC28J60时钟线空闲电平为低电平,在第一个下降沿采集数据

*/

u8 ENC28J60_SPI_ReadWriteOneByte(u8 tx_data)

{

u16 cnt=0;  

while((SPI1->SR&1<<1)==0) //等待发送区空--等待发送缓冲为空

{

cnt++;

if(cnt>=65530)return 0;   //超时退出  u16=2个字节

}

SPI1->DR=tx_data;         //发送一个byte 

cnt=0;

while((SPI1->SR&1<<0)==0) //等待接收完一个byte   

{

cnt++;

if(cnt>=65530)return 0;    //超时退出

}       

return SPI1->DR;          //返回收到的数据

}



/*

函数功能:复位ENC28J60,包括SPI初始化/IO初始化等

MISO--->PA6----主机输入

MOSI--->PA7----主机输出

SCLK--->PA5----时钟信号

CS----->PA4----片选

RESET-->PG15---复位

*/

void ENC28J60_Reset(void)

{

/*开启时钟*/

RCC->APB2ENR|=1<<12;   //开启SPI1时钟

RCC->APB2ENR|=1<<2;    //PA

GPIOA->CRL&=0X0000FFFF; //清除寄存器

GPIOA->CRL|=0XB8B30000;

GPIOA->ODR|=0XF<<4; //    上拉--输出高电平

GPIOA->ODR&=~(1<<5);

RCC->APB2ENR|=1<<8; //2 3 4 5 6 7 8

GPIOG->CRH&=0x0FFFFFFF;

GPIOG->CRH|=0x30000000;


/*SPI2基本配置*/

SPI1->CR1=0X0; //清空寄存器

SPI1->CR1|=0<<15; //选择“双线双向”模式

SPI1->CR1|=0<<11; //使用8位数据帧格式进行发送/接收;

SPI1->CR1|=0<<10; //全双工(发送和接收);

SPI1->CR1|=1<<9;  //启用软件从设备管理

SPI1->CR1|=1<<8;  //NSS

SPI1->CR1|=0<<7;  //帧格式,先发送高位

SPI1->CR1|=0x1<<3;//当总线频率为36MHZ时,SPI速度为18MHZ,高速。

SPI1->CR1|=1<<2;  //配置为主设备

SPI1->CR1|=1<<1;  //空闲状态时, SCK保持高电平。

SPI1->CR1|=1<<0;  //数据采样从第二个时钟边沿开始。

SPI1->CR1|=1<<6;  //开启SPI设备。

//针对ENC28J60的特点(SCK空闲为低电平)修改SPI的设置

  SPI1->CR1&=~(1<<6); //SPI设备失能

SPI1->CR1&=~(1<<1); //空闲模式下SCK为0 CPOL=0

SPI1->CR1&=~(1<<0); //数据采样从第1个时间边沿开始,CPHA=0  

SPI1->CR1|=1<<6;   //SPI设备使能

ENC28J60_RST=0; //复位ENC28J60

DelayMs(10);  

ENC28J60_RST=1; //复位结束     

DelayMs(10);   

}



/*

函数功能:读取ENC28J60寄存器(带操作码) 

  数:op:操作码

addr:寄存器地址/参数

返 回 值:读到的数据

*/

u8 ENC28J60_Read_Op(u8 op,u8 addr)

{

u8 dat=0;  

ENC28J60_CS=0;  

dat=op|(addr&ADDR_MASK);

ENC28J60_SPI_ReadWriteOneByte(dat);

dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);

//如果是读取MAC/MII寄存器,则第二次读到的数据才是正确的,见手册29页

  if(addr&0x80)dat=ENC28J60_SPI_ReadWriteOneByte(0xFF);

ENC28J60_CS=1;

return dat;

}




/*

函数功能:读取ENC28J60寄存器(带操作码) 

参    数:

op:操作码

addr:寄存器地址

data:参数

*/

void ENC28J60_Write_Op(u8 op,u8 addr,u8 data)

{

u8 dat = 0;     

ENC28J60_CS=0;    

dat=op|(addr&ADDR_MASK);

ENC28J60_SPI_ReadWriteOneByte(dat);   

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

相关文章

    输入插件产生视频数据并将视频数据复制到内存中,它有多个输出插件将这些视频数据经过处理,其中最重要的输出插件是网站服务器插件,它将视频数据传送到用户浏览器中,MJPG-streamer的工......
    输入插件产生视频数据并将视频数据复制到内存中,它有多个输出插件将这些视频数据经过处理,其中最重要的输出插件是网站服务器插件,它将视频数据传送到用户浏览器中,MJPG-streamer的工......
    备强大的媒体播放、数据同步、云存储等功能,为企业用户提供了全面的存储解决方案。此外,T系列还可轻松搭建档案存储服务器、邮件服务器网站服务器、FTP服务器等、MySQL数据库、CRM、Node.js、Java 虚拟......
    倍以上,流量暴涨引发网站服务一度中断,临时云上扩容也无法及时满足巨大的流量。了解到Serverless的优势后,南瓜电影果断将系统直接部署到阿里云 Serverless 应用引擎SAE上,不用......
    不仅支持网络管理、权限管理、存储管理和资源监控等全方位的专业存储管理功能,还提供了上百个免费的应用程序,满足用户多样化的需求。通过TOS 6系统,用户可以轻松搭建文件服务器、邮件服务器网站服务器等多种服务......
    华为申请注册“华为全车智能”商标;华为技术有限公司对“华为全车智能”商标的国际分类分别为09类-科学仪器(申请/注册号:73271744)、42-网站服务(申请/注册号:73270068),当前......
    或是AI训练领域,对于“超微”这个名字,可能更容易想到今天同样如日中天的AMD。但事实上是全球前三大服务器大厂之一,其产品线十分广泛,涵盖了从服务器、存储系统、网络交换器到软件等多个领域。这些产品广泛应用于网站服务器......
    德国或将耗资数十亿换掉华为 5G 基站天线; 据业内信息,德国或将耗资数十亿换掉 5G 基站大约 8 万根,在欧盟和美国敦促下,依赖使用中国基站服务的德国不得不采取妥协政策,德国......
    该加氢站核心设备与整体解决方案的供应商,北京海德利森科技有限公司(以下简称“海德利森”)凭借其深厚的技术实力和丰富的项目经验,为该项目提供了全方位的加氢站设备和整站服务,助力......

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>