硬件:正點原子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
嵌入式代碼:
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狀態,則可以點擊獲取。有點像智能家居的感覺了