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狀態,則可以點擊獲取。有點像智能家居的感覺了

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章