📄 main.c
字号:
//-----------------------------------------------------------------
// 名称: 基于PIC18+RTL8019与Microchip协议栈的HTTP服务器应用
//-----------------------------------------------------------------
// 说明: 本例运行时,在IE浏览器中输入基于PIC18+RTL8019与Microchip协议栈
// 开发的HTTP服务器应用系统地址,形如 http://xxx.xxx.xxx.xxx时,
// 可通过打开的WEB页与单片机HTTP应用系统服务程序交互,实现对系统的
// 远程控制. 本例C程序编译器为: Microchip C18 v3.02及以上版本.
//
//-----------------------------------------------------------------
#define VERSION "v3.75" //TCP/IP协议栈版本
#define BAUD_RATE (19200) //波特率bps
//以下是必须包括的头文件
#include <string.h>
#include "..\Include\Compiler.h"
#include "..\Include\StackTsk.h"
#include "..\Include\Tick.h"
#include "..\Include\MAC.h"
#include "..\Include\Helpers.h"
#include "..\Include\Delay.h"
#include "..\Include\UART.h"
#include "..\Include\MPFS.h"
#include "..\Include\LCDBlocking.h"
#include "..\Include\GenericTCPClient.h"
#include "..\Include\HTTP.h"
#include "..\Include\XEEPROM.h"
#include "..\Include\DHCP.h"
#include "..\Include\DNS.h"
#include "..\Include\Announce.h"
#include "..\Include\NBNS.h"
//-----------------------------------------------------------------
#define USART_USE_BRGH_LOW
#if defined(USART_USE_BRGH_LOW)
#define SPBRG_VAL (((INSTR_FREQ/BAUD_RATE)/16) - 1)
#else
#define SPBRG_VAL (((INSTR_FREQ/BAUD_RATE)/4) - 1)
#endif
#if (SPBRG_VAL > 255) && !defined(__C30__)
#error "Calculated SPBRG value is out of range for currnet CLOCK_FREQ."
#endif
//-----------------------------------------------------------------
//对于以下应用程序配置内容,主程序必须定义并用适当的值初始化
//配置包括:分别为IP地址,介质访问层地址MAC,子网掩码MASK,网关GATE等
//结构中所有符号常量均定义于StackTsk.h文件
APP_CONFIG AppConfig =
{
//4字节的默认IP地址
{ MY_DEFAULT_IP_ADDR_BYTE1, MY_DEFAULT_IP_ADDR_BYTE2,
MY_DEFAULT_IP_ADDR_BYTE3, MY_DEFAULT_IP_ADDR_BYTE4
},
//6字节的MAC地址
{ MY_DEFAULT_MAC_BYTE1, MY_DEFAULT_MAC_BYTE2, MY_DEFAULT_MAC_BYTE3,
MY_DEFAULT_MAC_BYTE4, MY_DEFAULT_MAC_BYTE5, MY_DEFAULT_MAC_BYTE6
},
//4字节的默认子网掩网
{ MY_DEFAULT_MASK_BYTE1, MY_DEFAULT_MASK_BYTE2,
MY_DEFAULT_MASK_BYTE3, MY_DEFAULT_MASK_BYTE4
},
//4字节的网关地址
{ MY_DEFAULT_GATE_BYTE1, MY_DEFAULT_GATE_BYTE2,
MY_DEFAULT_GATE_BYTE3, MY_DEFAULT_GATE_BYTE4
},
//4字节的DNS地址
{ MY_DEFAULT_DNS_BYTE1, MY_DEFAULT_DNS_BYTE2,
MY_DEFAULT_DNS_BYTE3, MY_DEFAULT_DNS_BYTE4
},
//配置标志位
{0B11111111}//使能DHCP
};
//-----------------------------------------------------------------
BYTE myDHCPBindCount = 0;
#if defined(STACK_USE_DHCP)
extern BYTE DHCPBindCount;
#else
#define DHCPBindCount (0xFF)
#endif
//-----------------------------------------------------------------
//CGI命令码,本例中它们是Javascript通过GetServerFile请求的形如'0?0=LED1'
//的"URL"或称"文件名"中的"?"号前面的数字,每个数字代表一种操作命令
//本例定义的操作命令有以下三类
#define CGI_CMD_DIGOUT (0) //命令码0,其后带参数分别控制LED开关/继电器开关
#define CGI_CMD_LCDOUT (1) //命令码1,其后参数为待显示在LCD上的字符串
//CGI的命令码CGI_CMD_DIGOUT(0)下设了4个操作码(0~3)
//分别用于控制两只LED及继电器(电机启/停)
#define CMD_LED1 (0)
#define CMD_LED2 (1)
#define CMD_RELAY_ON (2)
#define CMD_RELAY_OFF (3)
//本例CGI文件中使用的动态控制码字节,动态控制码字节必须为两位,不足两位时用0填充
#define VAR_LED0 (0x00) //LED0控制
#define VAR_LED1 (0x01) //LED1控制
#define VAR_RELAY (0x10) //继电器控制
#define VAR_ANAIN_AN0 (0x02) //模拟通道0访问
#define VAR_STACK_VERSION (0x16) //协议栈版本
#define VAR_STACK_DATE (0x17) //协议栈日期
//-----------------------------------------------------------------
// 设置熔丝配置位
//-----------------------------------------------------------------
#pragma config OSC = HS, WDT = OFF, LVP = OFF
//函数声明
static void InitAppConfig(void);
static void InitializeBoard(void);
static void ProcessIO(void);
static void DisplayIPValue(IP_ADDR *IPVal);
static void SetConfig(void);
static void FormatNetBIOSName(BYTE Name[16]);
BOOL StringToIPAddress(char *str, IP_ADDR *buffer);
#if defined(MPFS_USE_EEPROM)
static void SaveAppConfig(void);
#else
#define SaveAppConfig()
#endif
//-----------------------------------------------------------------
// 中断函数定义
//-----------------------------------------------------------------
#pragma interruptlow HighISR
void HighISR(void)
{
#ifdef __18CXX
TickUpdate();
#endif
#if defined(STACK_USE_SLIP)
MACISR();
#endif
}
#if defined(__18CXX) && !defined(HI_TECH_C)
#pragma code highVector = 0x08
void HighVector (void)
{
_asm goto HighISR _endasm
}
#pragma code
#endif
ROM char NewIP[] = "New IP Address: ";
ROM char CRLF[] = "\r\n";
//-----------------------------------------------------------------
// 主程序入口
//-----------------------------------------------------------------
void main(void)
{
static TICK t = 0;
InitializeBoard(); //主控制板端口及相关硬件初始化
LCDInit(); DelayMs(100); //初始化LCD并延时100ms
//LCDText定义在LCDBlocking.c中(33个字符空间,实际使用2*16=32个,
//最后一个为结束标志),初始时通过MCC18的预定义宏VERSION在该字符串中
//存入协议栈版本及16个空格(16个空格清空LCD第二行)
strcpypgm2ram(LCDText, "TCPStack " VERSION " "
" ");//本行字符串内有16个空格
LCDUpdate(); //刷新LCD显示(两行)
TickInit(); //"嘀嗒"时钟初始
MPFSInit(); //MPFS文件系统初始化
//加载默认的NetBIOS主机名(PICWEBSERVER)
memcpypgm2ram(AppConfig.NetBIOSName,(ROM void*)MY_DEFAULT_HOST_NAME, 16);
//规范化主机名(转大写,长度不足时用空格填充)
FormatNetBIOSName(AppConfig.NetBIOSName);
//初始化协议栈及并对配置数据的有效性进行判断和处理
InitAppConfig();
//如果启动时,"配置"开关合上则初始化主控板的配置程序
if(BUTTON0_IO == 0) SetConfig();
StackInit(); //协议栈初始化
HTTPInit(); //HTTP服务器初始化
//使能DHCP及IP获取
#if defined(STACK_USE_DHCP) || defined(STACK_USE_IP_GLEANING)
if(!AppConfig.Flags.bIsDHCPEnabled)
{
myDHCPBindCount = 1; //确保IP地址更新显示
#if defined(STACK_USE_DHCP)
DHCPDisable();
#endif
}
#endif
//完成上述初始化操作以后,进入无限循环,处理协议栈任务
//如果应用需要执行自己的任务可在while循环的末尾完成
//while循环使用使用的是"协作式多任务"机制,每项任务完成后返回,以便其他
//任务能够完成他们的作业,如果某项任务需要很长的时间去完成作业时,应将
//其分解为多个更小的任务,以便其他任务能够分配到CPU时间.
while(1)
{ //系统LED每秒闪烁一次
if ( TickGetDiff(TickGet(), t) >= TICK_SECOND/2 )
{
t = TickGet(); LED_PTR_IO ^= 1;
}
//执行常规的协议栈任务,包括检查输入的包,包类型
//并调用相当的入口程序进行处理
StackTask();
HTTPServer(); //HTTP服务处理程序
#if defined(STACK_USE_ANNOUNCE)
DiscoveryTask();
#endif
#if defined(STACK_USE_NBNS)
NBNSTask();
#endif
ProcessIO(); //处理IO操作(本例中为A/D转换操作)
//显示自上次复位以来更新的IP配置
if ( DHCPBindCount != myDHCPBindCount )
{ myDHCPBindCount = DHCPBindCount;
putrsUART(NewIP);
DisplayIPValue(&AppConfig.MyIPAddr);
putrsUART(CRLF);
#if defined(STACK_USE_ANNOUNCE)
AnnounceIP();
#endif
}
}
}
//-----------------------------------------------------------------
// 显示IP地址
//-----------------------------------------------------------------
static void DisplayIPValue(IP_ADDR *IPVal)
{
BYTE i,j,IPDigit[5], LCDPos = 16;
//显示形如xxx.xxx.xxx.xxx的IP地址串
for (i = 0; i < 4; i++)
{
itoa(IPVal->v[i], IPDigit); putsUART(IPDigit);
for(j = 0; j < strlen(IPDigit); j++)
{
LCDText[LCDPos++] = IPDigit[j];
}
if (i == 3) break; //IP最后一个地址字节后不加点"."
LCDText[LCDPos++] = '.';
while(BusyUART()); WriteUART('.');
}
//LCDText字符串长不足32字节时用空格填充
while(LCDPos < 32) LCDText[LCDPos++] = ' ';
LCDUpdate();
while(BusyUART());
}
static char AN0String[8];
//-----------------------------------------------------------------
// IO事件处理
//-----------------------------------------------------------------
static void ProcessIO(void)
{ //启动A/D转换模块
ADCON0bits.GO = 1;
//等待A/D转换完成
while(ADCON0bits.GO);
//将10位的A/D转换值转换为字符串
itoa(*((WORD*)(&ADRESL)), AN0String);
}
//-----------------------------------------------------------------
// HTTP命令处理函数 void HTTPExecCmd(BYTE** argv, BYTE argc)
// argv为参数列表, argc为参数个数,该函数为回调函数,当远程结点通过WEB
// 页面执行交互操作时,HTTPSERVER将根据该参数信息调用HTTPExecCmd函数.
// argc的格式:
// 以HTTP参数:ControlPage.htm?P1=ON&P2=OFF为例,有:
// argv[0] => ControlPage.htm
// argv[1] => P1
// argv[2] => ON
// argv[3] => P2
// argv[4] => OFF
// http.c的HTTP URL解析函数HTTPParse,以空格、/、\、?、=、&等字符为
// 参数串的分隔符
//-----------------------------------------------------------------
void HTTPExecCmd(BYTE** argv, BYTE argc)
{
BYTE command, var;
//对于本命名,下面的语句将从形如GetServerFile('0?0=LED1','')的
//调用中取得'?'前面的字符并转换为数字,因为Javascript函数GetServerFile
//将'0?0=LED1'作为文件名,通过XMLHttp对象向HTTP服务器请求'0?0=LED1'文件.
command = argv[0][0] - '0';
//根据数字命令(动作代码),执行相应的操作
switch(command)
{
//CGI命令处理:数字输出(本例分于用于控制LED1/LED2)
case CGI_CMD_DIGOUT: //ACTION=0
//CGI命令处理:处理由浏览器提交到液晶屏显示输出的字符串
case CGI_CMD_LCDOUT: //ACTION=1
}
}
//-----------------------------------------------------------------
// 处理并返回客户端HTTP请求:WORD HTTPGetVar(BYTE var, WORD ref, BYTE* val)
// 参数为:var为控制变量,ref为当前回调引用,val返回值缓冲
//-----------------------------------------------------------------
WORD HTTPGetVar(BYTE var, WORD ref, BYTE* val)
{
//用于缓冲待返回到浏览器的字符串
static BYTE VarString[35];
//根据不同的CGI请求变量返回不同的值
switch(var)
{
//以下前2个case的返回值只有一个字符(或字节)时,
//返回值写入*val后函数直接返回HTTP_END_OF_VAR
//下面的case返回指示LED1,LED2的开关状态(Y/N),控制代码分别为:%00,%01
case VAR_LED0: *val = LED1_IO ? 'N':'Y'; break;
case VAR_LED1: *val = LED2_IO ? 'N':'Y'; break;
//下在的4个case返回的都是多字符(或多字节),case语句首先将返回值写入varString
//通过多次回调HTTPGetVar逐一返回所有字符,在返回最后一个字节(或字节)后,
//本函数才返回HTTP_END_OF_VAR
//下面的case根据返回电机运行状态(运行/停止 Running.../STOP!)
case VAR_RELAY:
//首先确定当前是否是第一次调用(HTTP_START_OF_VAR),首次调用时取得完整的
//返回字符并存入VarString,但返回到客户端的IE浏览器时是逐个完成的.
if(ref == HTTP_START_OF_VAR)
{ //电机正在运转时将返回红色的"Running...",否则返回黑色的"STOP!"
}
//将待返回的字符串VarString中的第ref个字符写入val所指向的缓冲位置
*val = VarString[(BYTE)ref];
//如果待返回字符串VarString中的当前字符或下一字符为空则返回HTTP_END_OF_VAR
//用来通知HTTP 服务器传输已完成,否则ref递增,HTTP服务器将再次调用该函数
//处理待返回字符串中的下一字符(直到返回所有的字符)
if(VarString[(BYTE)ref] == '\0' || VarString[(BYTE)(ref+1)] == '\0')
return HTTP_END_OF_VAR; else return ++ref;
//返回模/数转换通道AN0的当前转换值
case VAR_ANAIN_AN0: //控制代码:%02
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -