目录
一、创建产品和数据点
二、给通讯模块烧写机智云固件
三、编写单片机程序与ESP8266-01模块通讯
四、用官方App调试
五、完整工程文件下载
毕业设计是实现一个可以远程访问控制的装置,手头有个STC8(STC8A8K64S4A12)的开发板和ESP8266-01模块。了解各大云平台之后,发现大多云平台都是MQTT协议直接接入,或者提供SDK包。
一开始直接入手连接阿里云平台,但可惜并不容易。原因有三点:
①首先是STC8毕竟是51单片机,用不了阿里云提供的SDK包,Keil C51只支持ANSI C;
②用不了SDK包还可以手撸MQTT,可惜MQTT库没发现适合51单片机的,自己写的话太费力;
③可以采用支持MQTT协议AT指令的WiFi模块,很可惜ESP8266没有MQTT的AT指令集,看官网貌似是ESP32支持MQTT协议AT指令。
于是,打算先连接机智云平台练练手。之后打算购买阿里云推荐的支持MQTT协议AT指令的EMW3080模块连接阿里云。
机智云平台的设备接入方式比较特殊,采用的是先给通讯模块烧机智云的固件,然后单片机通过机智云设置的通讯协议与通讯模块通讯,通讯模块自行与机智云服务器通讯。
首先第一步,在机智云平台创建一个产品,产品分类随便选,填一个喜欢的产品名称。通讯方式采用WiFi方式(ESP8266-01模块),数据传输方式指的是单片机与ESP8266模块通讯时,上传是每次上传所有数据点(定长),还是每次可上传任意部分数据点(变长),根据个人测试,选择定长方案即可,变长方案做成后云端显示不正确(目前不清楚什么原因);功耗类型选择正常。
下一步,在产品中添加数据点,我做的是控制两个LED+实时读取DS18B20温度传感器温度数据+在超出单片机设置的温度上下限后,接收高温or低温报警信息。
需要注意的的是,机智云中数值型数据用的是无符号整数来表示,比如我设置的DS18B20温度是 -55 ~ 125,步长 0.125 ,那么温度是 -55 时,数据值应当是 0 ,温度是 125 时,数据值为 (125 - (-55)) / 0.125 = 1440,这一定程度的减轻了单片机的计算量。一般读取传感器数据时,单片机内部用无符号整数存储及运算,也就可以直接传递给通讯模块而不用sprintf函数加工为float型。
按照官方文件说明操作就可以了。
ESP8266-01 :8M Flash不能烧错,官方文档:http://docs.gizwits.com/zh-cn/deviceDev/ESP8266%E4%B8%B2%E5%8F%A3%E7%83%A7%E5%86%99%E8%AF%B4%E6%98%8E.html
烧好固件后把模块先放一边,开始编写单片机程序与模块进行通讯。
除去采集传感器数据、控制LED的程序,主要待完成的是机智云通讯协议,才能与烧有机智云固件的WiFI模组通讯。
下载机智云的通讯协议文档,(根据当前产品数据点设置,机智云自动调整了上传数据点指令和云端下达控制指令的数据格式部分)。
阅读通讯协议文档后发现协议较为简单,通讯模组上电后就会不断发送 0x01 命令,向单片机请求设备信息,我们只需要在接收后按照指定格式回复各ProductKey、ProductSecret(在产品的基本信息处复制)等信息就可以。之后通讯模组会定时发送心跳包请求,单片机应当及时回复心跳包;单片机主动上传数据时发送0x05命令,收到通讯模组回应0x06命令表示模组已收到;
每个命令的格式参见协议文档开头(重要!)
#我们要实现的是不断监视串口发来的数据,收到一个完整的命令后,判断命令号是回复命令还是请求命令。
下面示例(可跳过,直接在文末链接直接下载完整Keil5工程文件)
首先,串口1负责打印日志,串口4负责与模组通信(波特率必须是9600,机智云固件设定),定时器0负责产生1ms定时,供给系统时间。
串口4将接收到的数据放入缓冲区内,缓冲区环形存储,用两个下标作为指针维护,如果单片机来不及处理导致缓冲区写满,则亮绿色LED表明串口缓冲区已满,不再写入。
void UART4_ISR(void) interrupt 18
{
if (S4CON & S4RI)
{
S4CON &= ~S4RI;
LED_GREEN = 1;
if ((uart4_idx2 + 1) % uart4_buffer_size == uart4_idx1) //串口缓冲区已满,放弃当前数据
{ //不能打印日志,可能导致堆栈错误
LED_GREEN = 0; //亮绿灯表示串口缓冲区已满负荷
return;
}
uart4_buffer[uart4_idx2] = S4BUF;
if (uart4_idx2 + 1 == uart4_buffer_size)
uart4_idx2 = 0;
else
++uart4_idx2;
}
else
S4CON &= ~S4TI, uart4_busy = false;
}
主程序需要循环做两件事,一个是从串口缓冲区读取命令,另一个是处理待完成的任务(如重发请求命令,等待发送新的请求命令,完成定时采集传感器任务等)
void GizwitsMainLoop(void) //Gizwits主循环函数
{
GizwitsReceive();
GizwitsAct();
}
从串口缓冲区读取命令需要存储已读取的部分命令,每次循环到读取命令函数时,从串口缓冲区读取数据接到先前已读取的部分命令后。每读取到一条完整命令时,进行校验和确认和命令号orFlag验证,验证失败的命令立即发送0x12命令表示命令非法。命令验证成功时判断命令号是请求型命令还是应答型命令,调用对应函数处理。
static void GizwitsReceive(void) //从WiFi串口缓存区读取数据包
{
pdata ushort Cidx1 = uart4_idx1, Cidx2 = uart4_idx2;
//锁定串口缓存区当前可读取数据
while (Cidx1 != Cidx2)
{
++GizwitsReceiveCount; //收到的数据包长度+1
if (GizwitsReceiveCount >= 4 &&
GizwitsReceiveLast == 0xFF && uart4_buffer[Cidx1] == 0x55) //非固定包头阶段过滤0x55数据
goto WhileEnd;
if (GizwitsReceiveCount >= 3)
GizwitsReceiveLast = uart4_buffer[Cidx1]; //更新历史数据
switch (GizwitsReceiveState)
{
case 0: //固定包头接收阶段
if (uart4_buffer[Cidx1] == 0xFF)
{
if (GizwitsReceiveCount == 2)
++GizwitsReceiveState;
}
else //发现异常包头数据,重启Giawits串口接收
{
#if LOGRANK_UART1 >= 1
printf("ERR:GizwitsReceive can't find 0xFF\r\n");
#endif
GizwitsReceiveInit();
}
break;
case 1:
GizwitsReceiveLen <<= 8;
GizwitsReceiveLen |= uart4_buffer[Cidx1]; //读取数据包Len值
if (GizwitsReceiveCount == 4)
{
++GizwitsReceiveState;
if (GizwitsReceiveLen - 5 > GizwitsReceiveBufferSize) //负载长度将导致数据包负载缓存区溢出
{
#if LOGRANK_UART1 >= 1
printf("ERR:GizwitsReceive Len, but it will cause ReceiveBuffer overflow\r\n");
#endif
GizwitsReceiveInit(); //重启Giawits串口接收
}
}
break;
case 2:
GizwitsReceiveCmd = uart4_buffer[Cidx1]; //读取数据包Cmd值
++GizwitsReceiveState;
break;
case 3:
GizwitsReceiveSn = uart4_buffer[Cidx1]; ////读取数据包Sn值
++GizwitsReceiveState;
break;
case 4:
GizwitsReceiveFlags <<= 8;
GizwitsReceiveFlags |= uart4_buffer[Cidx1]; //读取数据包Flags值
if (GizwitsReceiveCount == 8)
if (GizwitsReceiveLen - 5 > 0) //判断是否含有负载
++GizwitsReceiveState;
else
GizwitsReceiveState += 2;
break;
case 5:
GizwitsReceiveBuffer[GizwitsReceiveIdx++] = uart4_buffer[Cidx1]; //读取数据包
if (GizwitsReceiveCount - 3 == GizwitsReceiveLen) //数据包剩余长度为1,进入最后读取阶段
++GizwitsReceiveState;
break;
case 6:
GizwitsReceiveSum = uart4_buffer[Cidx1]; ////读取数据包Sum值
#if LOGRANK_UART1 >= 1
if (GizwitsReceiveLen + 4 != GizwitsReceiveCount) //发现数据包读取长度不足,未知原因
printf("ERR:GizwitsReceive receive one packet, but len invalid\r\n");
else
#endif
if (GizwitsReceiveSum == GizwitsSum(0)) //校验和正确,接收到一个数据包
{
ushort ReCmdFlags = GizwitsAnalyseCmd(GizwitsReceiveCmd, GizwitsReceiveFlags); //分析数据包Cmd及检查Flags
uchar ReCmd = ReCmdFlags & 0xFF, ReFlags = (ReCmdFlags >> 8) & 0xFF;
#if LOGRANK_UART1 >= 2
//日志汇报MCU接收到格式及校验和正确的数据包
ushort i;
printf("LOG#:GizwitsReceive:%04x %02bx %02bx %04x", GizwitsReceiveLen,
GizwitsReceiveCmd, GizwitsReceiveSn, GizwitsReceiveFlags); //Len,Cmd,Sn,Flags
for (i = 0; i != GizwitsReceiveIdx; ++i)
printf(" %02bx", GizwitsReceiveBuffer[i]); //负载
printf(" %02bx\r\n", GizwitsReceiveSum); //Sum
#endif
if (ReCmd == 0x00)
{
if (ReCmdFlags == ReCmd_FlagsERR) //数据包Flags异常
{
#if LOGRANK_UART1 >= 1
printf("ERR:Packet Flags error,Ans->0x12_0x03\r\n");
#endif
GizwitsST.GizwitsIllegalCode = 0x03;
GizwitsHandleWiFiAsk(0x12);
}
else if (ReCmdFlags == ReCmd_CmdERR) //数据包Cmd无法识别
{
#if LOGRANK_UART1 >= 1
printf("ERR:Packet Cmd invalid or error,Ans->0x12_0x02\r\n");
#endif
GizwitsST.GizwitsIllegalCode = 0x02;
GizwitsHandleWiFiAsk(0x12);
}
else if (ReCmdFlags == ReCmd_MSGERR) //WiFi回应对应包异常,需要重新发送
{
#if LOGRANK_UART1 >= 2
printf("LOG#:Ask one packet is illegal,MCU will sent again\r\n");
#endif
if (GizwitsSendOld() != 0)
{
#if LOGRANK_UART1 >= 1
printf("ERR: GizwitsSendOld Fail,Sn is not found,can't sent again\r\n");
#endif
}
}
else
{
#if LOGRANK_UART1 >= 1
printf("ERR:Packet unknown error,Ans->0x12_0x03\r\n");
#endif
GizwitsST.GizwitsIllegalCode = 0x03;
GizwitsHandleWiFiAsk(0x12);
}
}
else
{
if (ReFlags == 0x00) //数据包是 Ask
GizwitsHandleWiFiAsk(ReCmd);
else //数据包是 Ans
GizwitsHandleWiFiAns(ReCmd);
}
}
else //校验和错误
{
#if LOGRANK_UART1 >= 1
printf("ERR: Packet Sum check fail,Ans->0x12_0x01\r\n");
#endif
GizwitsST.GizwitsIllegalCode = 0x01;
GizwitsHandleWiFiAsk(0x12);
}
GizwitsReceiveInit(); //重启Giawits串口接收
break;
}
WhileEnd:
++Cidx1; //读取锁定区域下1字节,释放1字节串口缓冲区空间
if (Cidx1 == uart4_buffer_size)
Cidx1 = 0, uart4_idx1 = 0;
uart4_idx1 = Cidx1;
}
}
处理模组的请求命令时,需要立即生成并发送回复命令,部分任务需要执行相关请求(如请求控制数据点、请求当前数据点信息、请求延时600ms重启单片机等)
void GizwitsHandleWiFiAsk(uchar Cmd) //参数Cmd: MCU待应答命令
{
#if LOGRANK_UART1 >= 2
printf("LOG#:Handle WiFi Ask[%02bx]\r\n", Cmd); //日志记录MCU处理WiFi Ask,Cmd为MCU待发送Ans
#endif
GizwitsSendIdx = 0;
GizwitsSendBuffer[GizwitsSendIdx++] = Cmd;
GizwitsSendBuffer[GizwitsSendIdx++] = GizwitsReceiveSn;
switch (Cmd)
{
case 0x02: //WiFi模组请求设备信息
{
pdata ushort i;
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00; //Flags
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00;
for (i = 0; i != 8; ++i)
GizwitsSendBuffer[GizwitsSendIdx++] = SerialProtocolVer[i];
for (i = 0; i != 8; ++i)
GizwitsSendBuffer[GizwitsSendIdx++] = BusinessProtocolVer[i];
for (i = 0; i != 8; ++i)
GizwitsSendBuffer[GizwitsSendIdx++] = HardwareVer[i];
for (i = 0; i != 8; ++i)
GizwitsSendBuffer[GizwitsSendIdx++] = SoftwareVer[i];
for (i = 0; i != 32; ++i)
GizwitsSendBuffer[GizwitsSendIdx++] = ProductKey[i];
GizwitsSendBuffer[GizwitsSendIdx++] = (BindingStateSec >> 8) & 0xFF;
GizwitsSendBuffer[GizwitsSendIdx++] = (BindingStateSec)&0xFF;
for (i = 0; i != 8; ++i)
GizwitsSendBuffer[GizwitsSendIdx++] = DeviceProperties[i];
for (i = 0; i != 32; ++i)
GizwitsSendBuffer[GizwitsSendIdx++] = ProductSecret[i];
GizwitsSendBuffer[GizwitsSendIdx++] = (DataLen >> 8) & 0xFF;
GizwitsSendBuffer[GizwitsSendIdx++] = (DataLen)&0xFF;
for (i = 0; i != DataLen; ++i)
GizwitsSendBuffer[GizwitsSendIdx++] = Data[i];
GizwitsST.WiFiConect=true;//发送Ans=0x02完毕,可以开始Ask
}
break;
case 0x04: //WiFi模组控制设备&读取设备的当前状态
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00; //Flags
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00;
{
uchar Action = GizwitsReceiveBuffer[0];
if (Action == 0x01) //WiFi模组控制设备
{
GizwitsSetFifiControl(); //从GizwitsReceiveBuffer中读取控制数据,立即执行
GizwitsST.NeedReport = true; //需要上报设备状态
}
else //WiFi模组读取设备的当前状态
{
GizwitsSendBuffer[GizwitsSendIdx++] = 0x03;
GizwitsGetDevState(); //向GizwitsReceiveBuffer中写入设备状态信息
GizwitsST.NeedReport = false; //相当于执行了上报数据
}
}
break;
case 0x08: //心跳
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00; //Flags
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00;
break;
case 0x0E: //WiFi推送模组工作状态
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00; //Flags
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00;
GizwitsWiFiAsk0x0E(); //调用子程序处理WiFi模组工作状态信息
break;
case 0x10: //WiFi模组请求重启MCU
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00; //Flags
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00;
GizwitsST.NeedRstMCU = true;
break;
case 0x12: //应答WiFi模组数据包非法
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00; //Flags
GizwitsSendBuffer[GizwitsSendIdx++] = 0x00;
GizwitsSendBuffer[GizwitsSendIdx++] = GizwitsST.GizwitsIllegalCode;
break;
default:
#if LOGRANK_UART1 >= 1
printf("ERR:GizwitsHandleWiFiAsk can't Ans this AnsCmd[%02bx]\r\n", Cmd);
#endif
GizwitsSendIdx = 0; //清空发送缓冲区
return;
}
GizwitsSend(1); //发送 Ans 数据包
}
处理模组的回复命令,首先需要验证当前流水Sn号和等待回复的请求命令号与回复命令是否相对应。
验证失败,转为发送0x12命令表明非法命令;验证成功后消除等待回复状态,部分回复需要处理相关信息。
void GizwitsHandleWiFiAns(uchar Cmd) //参数Cmd: MCU已请求命令
{
if (Cmd != GizwitsSendOldBuffer[GizwitsSendOldQSize][0] || GizwitsReceiveSn != GizwitsSendOldBuffer[GizwitsSendOldQSize][1])
{ //发现未知的错误,当前Ans与MCU等待应答的Ask,Sn或Cmd不符
#if LOGRANK_UART1 >= 1
printf("ERR:GizwitsHandleWiFiAns found AskCmd[%02bx|%02bx]or Sn[%02bx|%02bx]illegal,Ans->0x12_0x02\r\n",
Cmd, GizwitsSendOldBuffer[GizwitsSendOldQSize][0], GizwitsReceiveSn, GizwitsSendOldBuffer[GizwitsSendOldQSize][1]);
#endif
GizwitsST.GizwitsIllegalCode = 0x02;
GizwitsHandleWiFiAsk(0x12); //转为GizwitsHandleWiFiAsk应答数据包非法
return;
}
#if LOGRANK_UART1 >= 2
printf("LOG#:Handle WiFi Ans[%02bx]\r\n", Cmd); //日志记录MCU处理WiFi Ans,Cmd为MCU已发送Ask
#endif
GizwitsST.NeedAns = false; //取消等待WiFi应答标志,成功执行对应AskCmd
switch (Cmd) //处理WiFi Ans信息
{
case 0x05: //MCU向WiFi模组主动上报当前状态
case 0x09: //MCU通知WiFi模组进入配置模式
case 0x0B: //重置WiFi模组
case 0x15: //通知WiFi模组进入可绑定模式
case 0x29: //MCU重启通讯模组
case 0x13: //MCU请求WiFi模组进入产测模式
break; //没有信息需要处理
case 0x17: //获取网络时间
#ifdef GizwitsUseWiFiRealTime
GizwitsWiFiAns0x17();
#else
#if LOGRANK_UART1 >= 1
printf("ERR:Unable GizwitsUseWiFiRealTime Def,ignore Ans datas\r\n");
#endif
#endif
break;
case 0x21: //获取通讯模组的信息
#ifdef GizwitsUseWiFiProperty
GizwitsWiFiAns0x21();
#else
#if LOGRANK_UART1 >= 1
printf("ERR:Unable GizwitsUseWiFiProperty Def,ignore Ans datas\r\n");
#endif
#endif
break;
default: //未知命令,严重错误,可能发生了意外的SendBuffer+ReceiveBuffer错误
#if LOGRANK_UART1 >= 1
printf("ERR:MCU AskCmd for WiFi Ans is unknown,serious ERR!\r\n");
#endif
break;
}
}
回到主循环的另一部分,执行任务函数。主要分两部分,一部分是在定时任务未执行状态下根据系统时钟定时挂起任务等待执行;另一部分是处理所有挂起任务,如果处理成功则取消任务挂起状态。
目前主要有两个任务,一个是需要在各参数变化时挂起上传参数任务;另一个是定时转换DS18B20温度并读取。
void GizwitsAct(void) //处理当前任务
{
//Gizwits系统定时挂起任务
#if GizwitsNeedReportMs != 0
if (GizwitsST.NeedReport == false && GizwitsST.GizwitsSysMs - GizwitsST.NeedReport_Ms >= GizwitsNeedReportMs)
GizwitsST.NeedReport = true; //需要上报设备状态
#endif
//Gizwits系统待执行任务
if (GizwitsST.NeedReport == true && GizwitsHandleMCUAsk(0x05) == 0) //上报完成,更新上报时间
GizwitsST.NeedReport = false, GizwitsST.NeedReport_Ms = GizwitsST.GizwitsSysMs;
if (GizwitsST.NeedAns == true) //等待Ans中
GizwitsReAsk(1);
if (GizwitsST.NeedRstMCU == true && (GizwitsST.GizwitsSysMs - GizwitsST.NeedRstMCU_Ms >= 600))
MCURST(); //600ms延时后重启MCU
//Sensor定时挂起任务
#if DS18B20NeedReadMs != 0
if (GizwitsST.NeedReadDS18B20 == false && GizwitsST.GizwitsSysMs - GizwitsST.NeedReadDS18B20_Ms >= DS18B20NeedReadMs && DS18B20ConvertTemperature() == EXIT_SUCCESS) //需要读取DS18B20温度值,且温度转换指令成功
GizwitsST.NeedReadDS18B20 = true, GizwitsST.NeedReadDS18B20_Ms = GizwitsST.GizwitsSysMs; //等待读取温度值
#endif
//Sensor待执行任务
if (GizwitsST.NeedReadDS18B20 == true && GizwitsST.GizwitsSysMs - GizwitsST.NeedReadDS18B20_Ms >= DS18B20ConvertTMaxTime[DS18B20ST.ResolutionMode] && DS18B20GetTemperature() == EXIT_SUCCESS) //成功执行读取温度值
GizwitsST.NeedReadDS18B20 = false, GizwitsST.NeedReadDS18B20_Ms = GizwitsST.GizwitsSysMs;
}
主要的函数就是这些,其他程序部分多是辅助部分和传感器采集部分。详见文末的Github链接获取完整Keil5工程。
应用商店或官网下载官方调试App,模组默认为AirLink模式,右上角选择一键配置后输入当前手机连接的WiFi热点,匹配后模组就会自动连接,掉电重启后不需要重新设置。
可以通过单片机发送0x09命令更改模组的配网方式。
完整Keil5工程文件包已上传Github:https://github.com/MapleBelous/STC8-Gizwits
END