stm32f407 lan8720 实现简单的http服务器(智能家居)


硬件:正点原子stm32f407开发板(带网络功能)

lan芯片:lan8720

系统:rt-thread


先说说http服务的功能,

1.能控制正点原子探索者STM32F407板载的两个LED灯

2.支持获取灯的状态。

3.实时更新温度和系统时间,更新频率为1S,这里的温度和系统时间均为虚拟值,只做测试用。

注意:这里网页和http服务器之间采用的是CGI的交互方式,不会刷新整个页面。

网页截图如下:

 

前端页面代码如下:index.html

 
<!-- saved from url=(0023)http://192.168.100.105/ -->
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 
<script type="text/javascript">
var xmlhttp;

function loadXMLDoc(url,cfunc)
{
  if (window.XMLHttpRequest)
  {
     xmlhttp=new XMLHttpRequest();
  }
  else
  {
     xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }
  xmlhttp.onreadystatechange=cfunc;
  xmlhttp.open("GET",url,true);
  xmlhttp.send();
}
function led1_open()
{
  loadXMLDoc("/led1.cgi?led1_1&t="+ Math.random(),function()
  {
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.getElementById("led1").src=xmlhttp.responseText;
    }
  });
}
 
function led1_close()
{
  loadXMLDoc("/led1.cgi?led1_0&t="+ Math.random(),function()
  {
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.getElementById("led1").src=xmlhttp.responseText;
    }
  });
}
 
function led2_open()
{
  loadXMLDoc("/led2.cgi?led2_1&t="+ Math.random(),function()
  {
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.getElementById("led2").src=xmlhttp.responseText;
    }
  });
}
 
function led2_close()
{
  loadXMLDoc("/led2.cgi?led2_0&t="+ Math.random(),function()
  {
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.getElementById("led2").src=xmlhttp.responseText;
    }
  });
}


function led1_status()
{
  loadXMLDoc("/led_status.cgi?led1_status&t="+ Math.random(),function()
  {
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.getElementById("led1").src=xmlhttp.responseText;
    }
  });
}

function led2_status()
{
  loadXMLDoc("/led_status.cgi?led2_status&t="+ Math.random(),function()
  {
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
        document.getElementById("led2").src=xmlhttp.responseText;
    }
  });
}

function update(){
   loadXMLDoc("/upload.cgi?t="+ Math.random(),function()
  {
    if (xmlhttp.readyState==4 && xmlhttp.status==200)
    {
	  text = xmlhttp.responseText;
	  text = text.split(";");//分割函数,遇到;就分割
	  document.getElementById("temperature").innerHTML=text[0]+"℃";
	  document.getElementById("time").innerHTML=text[1];

    }
  });

}

function init()
{
 setInterval(update,1000);//1000ms更新一次
}

</script>
 
 
<title>HTTP_SERVER</title>
<style type="text/css">
body,td,th {
	font-size: 18px;
}
a:link {
	text-decoration: none;
}
a:visited {
	text-decoration: none;
}
a:hover {
	text-decoration: none;
}
a:active {
	text-decoration: none;
}
</style>
 
</head>
 
<body onload="init()">

<br>

<div align="center">
LED1状态
<img id="led1" src="./image/light_off.jpg" class="rset" width="48" height="48" alt="layer">   <input type="button" value="开灯" onclick="led1_open()">   <input type="button" value="关灯" onclick="led1_close()"> <input type="button" value="获取" onclick="led1_status()"><br>
 
LED2状态
<img id="led2" src="./image/light_off.jpg" class="rset" width="48" height="48" alt="layer">   <input type="button" value="开灯" onclick="led2_open()">   <input type="button" value="关灯" onclick="led2_close()"> <input type="button" value="获取" onclick="led2_status()">

<br><br>
<br><br>
<table border="1" cellpadding="10"> 
<tr>
  <td width="200">温度值 </td>
  <td width="200"><span id="temperature">33.3℃</span></td>
</tr>
<tr>
  <td width="200">系统时间 </td>
  <td width="200"><span id="time">2011-11-11 11:11:11</span></td>
</tr>
</table>

</div>
<br>
 
<hr>
 
</body></html>

其中包含两张LED的图片,把它们放到image文件夹内,light_off.jpg/light_on.jpg

led_on.jpg

嵌入式代码:

1.将index.html和image文件拷贝到正点原子网络示例代码中fs文件夹内。然后点击makefsdata.exe,生成网页数据文件fsdata.c

2.打开fsdata.c,在最后面,修改代码,注意每次生成fsdata.c都要修改一次

extern struct fsdata_file file_response_ssi[];//为fs.c中定义的一个数组

const struct fsdata_file file__image_light_off_jpg[] = { {
file_response_ssi,//指向file_response_ssi,生成的文件中,这里是指向NULL的,这里是多出了一个
data__image_light_off_jpg,
data__image_light_off_jpg + 24,
sizeof(data__image_light_off_jpg) - 24,
1,
}};

const struct fsdata_file file__image_light_on_jpg[] = { {
file__image_light_off_jpg,
data__image_light_on_jpg,
data__image_light_on_jpg + 20,
sizeof(data__image_light_on_jpg) - 20,
1,
}};

const struct fsdata_file file__index_html[] = { {
file__image_light_on_jpg,
data__index_html,
data__index_html + 12,
sizeof(data__index_html) - 12,
1,
}};

#define FS_ROOT file__index_html
#define FS_NUMFILES 3 + 1 //注意这里是要+1的

3.在fs.c源文件和头文件中增加代码

fs.c第59行添加代码

#define RESPONSE_BUF_SIZE 512   //http响应缓存大小
unsigned char data_response_ssi[RESPONSE_BUF_SIZE+14] =
{
	/* /response.ssi */
	0x2F, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73, 
	0x65, 0x2E, 0x73, 0x73, 0x69, 0x00, 
};
struct fsdata_file file_response_ssi[] = //注意这个变量,需要添加到fsdata.c中,最后一个文件的连接处
{
	{
		NULL,
		data_response_ssi,
		data_response_ssi + 14,
		sizeof(data_response_ssi) - 14
	}
};

fs.h最后面增加代码:

extern unsigned char data_response_ssi[];
#define data_response_buf (data_response_ssi+14)

4.用keil打开工程,新建一个http_server目录,然后在目录下添加三个文件:[注意:是不用添加fsdata.c的]

文件简要说明:

fs.c:操作文件相关(打开、关闭,读),实际上就是通过数组名找到具体内容,返回数组的内容。这些数组都是定义在fsdata.c中的。

httpd.c:http服务器相关代码,主要是创建一个TCP服务器,等待客户端的连接,然后根据客户端的请求,返回对应的数据。

httpd_cgi_ssi.c:前端和后台的数据接口操作,主要在里面实现了LED的开关和返回温度以及系统时间的功能。这里面的函数,大多数都是回调函数,它们都是在http服务器初始化的时候,已经注册好的。

关于这几个文件,大家可以在正点原子的例子里面找到。这里的话,我们主要是针对我们自己的需求,来操作前端和后台的数据接口,即我们只需要写httpd_cgi_ssi.c里的内容就好了。

改写后的httpd_cgi_ssi.c的内容如下:

#include "lwip/debug.h"
#include "httpd.h"
#include "lwip/tcp.h"
#include "fs.h"
#include "bsp_led.h"


#include <string.h>
#include <stdlib.h>

/* CGI handler for Upload control */ 
const char *upload_cgi_handler( int iIndex, int iNumParams, char *pcParam[], char *pcValue[] );
/* CGI handler for LED control */ 
const char * leds_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);


static const tCGI URL_TABLES[] = 
{
    {"/led1.cgi", leds_cgi_handler},
    {"/led2.cgi",leds_cgi_handler},
    {"/led_status.cgi",leds_cgi_handler},
    {"/upload.cgi",upload_cgi_handler}
};

const rt_uint8_t CGI_URL_NUM  = (sizeof(URL_TABLES) / sizeof(tCGI));

/**
  * @brief  点亮LED
  * @param  1:点亮LED1
  * @param  2:点亮LED2
  * @retval None
  */
static void led_set(uint8_t led)
{
    if (led == 1) {
        led_status_set(1,1);
    } else if (led == 2) {
        led_status_set(2,1);
    }
}

/**
  * @brief  熄灭LED
  * @param  1:熄灭LED1.
  * @param  2:熄灭LED2
  * @retval None
  */
static void led_clr(uint8_t led)
{
    if (led == 1) {
        led_status_set(1,0);
    } else if (led == 2) {
        led_status_set(2,0);
    }
}

//temp:存放温度字符串的首地址.如"22.2";
static void temperature_get(rt_uint8_t *temp)
{ 
    static float value = 22.2;//测试用
    value += 0.1;
    if(value > 50)
        value = 22.2;
    sprintf((char *)temp,"%.1f",value);
}

//time:存放时间字符串,形如:"2020-05-05 12:33:00"
static void systime_get(rt_uint8_t *time)
{
    static rt_uint8_t value = 0;//测试用
    if(value ++ >= 60)
        value = 0;
    sprintf((char *)time,"2020-05-05 12:33:%02d",value);
}
/**
  * @brief  LED 操作相关接口函数
  */
const char *leds_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[])
{
    uint8_t i=0;

  /* We have only one SSI handler iIndex = 0 */
    /* All leds off */
    /* Check cgi parameter : example GET /ledctrl.cgi?led=1&led=2 */
    for (i=0; i<iNumParams; i++) 
    {
      /* check parameter "led" */
        if (strcmp(pcParam[i] , "led1_0") == 0) 
        { 
            led_clr(1);  
            rt_memset(data_response_buf,0,strlen((const char *)data_response_buf));
            strcat((char *)(data_response_buf),"/image/light_off.jpg");
        }      
        else if(strcmp(pcParam[i] , "led1_1") == 0) 
        {
            led_set(1);
            rt_memset(data_response_buf,0,strlen((const char *)data_response_buf));
            strcat((char *)(data_response_buf),"/image/light_on.jpg");
        }
        
        else if(strcmp(pcParam[i] , "led2_0") == 0) 
        {
            led_clr(2); 
            rt_memset(data_response_buf,0,strlen((const char *)data_response_buf));
            strcat((char *)(data_response_buf),"/image/light_off.jpg");
        }
        else if(strcmp(pcParam[i] , "led2_1") == 0) 
        {
            led_set(2);
            rt_memset(data_response_buf,0,strlen((const char *)data_response_buf));
            strcat((char *)(data_response_buf),"/image/light_on.jpg");
        }
        //判断LED的状态
        else if(strcmp(pcParam[i], "led1_status") == 0)
        {
            rt_memset(data_response_buf,0,strlen((const char *)data_response_buf));
            if(led_pin_read(1) == 0)//0代表亮
                strcat((char *)(data_response_buf),"/image/light_on.jpg");
            else
                strcat((char *)(data_response_buf),"/image/light_off.jpg");
        }
        else if(strcmp(pcParam[i], "led2_status") == 0)
        {
            rt_memset(data_response_buf,0,strlen((const char *)data_response_buf));
            if(led_pin_read(2) == 0)//0代表亮
                strcat((char *)(data_response_buf),"/image/light_on.jpg");
            else
                strcat((char *)(data_response_buf),"/image/light_off.jpg");
        }
        
    }
    
    return "/response.ssi";
}

/**
  * @brief  温度和系统时间上传相关接口函数
*/
const char *upload_cgi_handler( int iIndex, int iNumParams, char *pcParam[], char *pcValue[] )
{
    rt_uint8_t buf[20];
    rt_memset(data_response_buf,0,strlen((const char *)data_response_buf)); //清除缓冲区的内容

    temperature_get(data_response_buf);
    strcat((char *)(data_response_buf),";");
    systime_get(buf);
    strcat((char *)(data_response_buf),(const char *)buf);

    return "/response.ssi";
}

/**
 * Initialize SSI handlers
 */
void httpd_ssi_init(void)
{  
   /* configure SSI handlers (ADC page SSI) */
   //http_set_ssi_handler(ADC_Handler, (char const **)TAGS, 1);
}

/**
 * Initialize CGI handlers
 */
void httpd_cgi_init(void)
{ 
    http_set_cgi_handlers(URL_TABLES, CGI_URL_NUM);//注册回调函数
}


bsp_led.c和bsp_led.h内容如下

#include "bsp_led.h"


void bsp_led_hw_init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;

    RCC_AHB1PeriphClockCmd(LED_CLK,ENABLE);

    GPIO_InitStructure.GPIO_Pin = LED1_PIN | LED2_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

    GPIO_Init(LED_PORT,&GPIO_InitStructure);
}
void led_status_set(rt_uint8_t led_pin,rt_uint8_t status)
{
    if(status == LED_ON)
    {
        if(led_pin == 1)
            GPIO_ResetBits(LED_PORT,LED1_PIN);
        else
            GPIO_ResetBits(LED_PORT,LED2_PIN);
    }
    else
    {
        if(led_pin == 1)
            GPIO_SetBits(LED_PORT,LED1_PIN);
        else
            GPIO_SetBits(LED_PORT,LED2_PIN);
    }
}

rt_uint8_t led_pin_read(rt_uint8_t led_pin)
{
    if(led_pin == 1)
    {
        if(GPIO_ReadInputDataBit(LED_PORT,LED1_PIN))
            return 1;
        else
            return 0;
    }
    else if(led_pin == 2)
    {
        if(GPIO_ReadInputDataBit(LED_PORT,LED2_PIN))
            return 1;
        else 
            return 0;
    }
}
#ifndef __BSP_LED_H_
#define __BSP_LED_H_

#include <rtthread.h>
#include <stm32f4xx.h>

//对应正点原子探索者的两个LED灯
#define LED_CLK   RCC_AHB1Periph_GPIOF
#define LED_PORT  GPIOF
#define LED1_PIN  GPIO_Pin_9
#define LED2_PIN  GPIO_Pin_10

#define LED_ON  1
#define LED_OFF 0

void bsp_led_hw_init(void);
void led_status_set(rt_uint8_t led_num,rt_uint8_t status);
rt_uint8_t led_pin_read(rt_uint8_t led_pin);



#endif

main.c文件内容如下:

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2006-08-31     Bernard      first implementation
 * 2011-06-05     Bernard      modify for STM32F107 version
 */

#include <rthw.h>
#include <rtthread.h>

/**
 * @addtogroup STM32
 */

/*@{*/


#include "bsp_led.h"

#ifdef RT_USING_LWIP
#include <lwip/sys.h>
#include <lwip/api.h>
#include <netif/ethernetif.h>
#include "stm32f4xx_eth.h"
#endif


int main(void)
{
    /* user app entry */
    rt_err_t  result;
    bsp_led_hw_init();
#ifdef RT_USING_LWIP
    {
        extern void lwip_sys_init(void);
        lwip_sys_init();
        rt_hw_stm32_eth_init();
    }
#endif

    extern void httpd_init(void);
    httpd_init();//启动http服务器
    return 0;
}

/*@}*/

烧录程序测试:

然后我们通过串口输入ifconfig来查询一下开发板的IP:

得到开发板IP后,我们将IP拷贝到浏览器打开,注意,这里是不需要输入端口号的,因为板子的默认端口号就是80.

然后就得到显示界面了。温度值和时间都是1S变化一次,LED的状态每次进入都默认为关,如果想知道当前的LED状态,则可以点击获取。有点像智能家居的感觉了

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值