
简介面向STM32与物联网开发者该7z压缩包提供一个完整可运行的SIM800CSTM32接入OneNet云平台的温湿度与GPS数据上传工程解决GPRS模组与单片机通信、数据上云等常见问题适合具备一定单片机基础、想学习AT指令和MQTT/HTTP接入的读者。包内含87个文件以c和h源码为主包含uvproj工程配置、o/d/crf等编译中间文件、hex烧录文件及htm参考页面整体约1.08MB目录按DHT11、SIM800C、GPS、OLED等功能模块划分层次清晰。已有3578人学习下载。借助该工程读者可对照源码理解SIM800C的TCP连接建立、OneNet数据点上传流程掌握STM32多串口解析GPS与传感器数据的方法并能基于MDK工程直接编译调试。同时附带相关文档与编译产物便于逆向阅读和排错是物联网实战项目不错的起步范例。1. 用 SIM800C 和 STM32 把温湿度、GPS 一起送上 OneNet一次讲透整条链路很多远程监测项目在选型时会卡在同一个问题上设备摆在田里、冷链车里、工地上那里没有 WiFi甚至没有固定电源数据怎么上云这个标题给出的组合很典型——STM32 负责采集和业务逻辑SIM800C 通过 GPRS 拨号上网温湿度和 GPS 数据打包成 HTTP 请求发到 OneNet 平台。OneNet 是国内物联网平台里接入成本较低的一个核心玩法是“创建设备、拿 APIKey、上报数据流”不需要自己买服务器。这套方案经常出现在毕业设计、农业大棚监测、车辆定位原型里而且改一改就能迁移到 NB-IoT 或 4G 模块上。下面按“平台侧准备 → 模块连网 → HTTP 组包 → STM32 采集 → 联调排错”的顺序把整条链路讲清楚。2. OneNet 平台侧接入产品、设备与生成 APIKey 的关系写 STM32 代码之前先花半小时把 OneNet 平台侧的东西准备好。否则代码写到一半连 token 怎么放、路径长什么样都不知道后面所有调试都会卡在 401 这类权限错误上。2.1 先把 OneNet 的三层概念理清产品、设备、APIKeyOneNet 的模型不复杂四个词就够了产品、设备、数据流、数据点。一个产品对应一类设备比如“冷链监测终端”产品下面挂多个设备每个设备有唯一的设备 ID设备下可以定义若干数据流比如 temp、hum、lat、lng某个时刻往数据流里写入一个值就产生一个数据点。上报时用的鉴权信息是 APIKey它相当于这把锁的钥匙。需要注意 APIKey 分产品级和设备级上传数据时用设备级 APIKey 即可权限上更收敛。开发中最容易搞混的是“设备 ID”和“APIKey”这两个值。设备 ID 是一个数字字符串出现在请求 URL 里APIKey 是一个较长的令牌放在请求头里。我把它们写在同一个结构体里代码中统一引用避免到处复制粘贴导致不一致。typedef struct { char device_id[16]; // 例如 12345678 char api_key[48]; // 例如 AbCdeFgHijkLmN... } onenet_config_t;这里的 device_id 和 api_key 都是字符串在 HTTP 请求里逐字节使用。设备 ID 可以直接从 OneNet 设备列表里看到APIKey 在设备详情页或产品的密钥管理里生成生成后建议立即复制保存页面刷新后不会再次明文显示。2.2 创建产品并生成 APIKey多协议接入里的 HTTP 选项OneNet 控制台的界面改过几次版本但核心入口基本一致登录后创建产品产品名称随意选择“多协议接入”协议选 HTTP。不同版本的按钮文字可能有差异比如新版控制台挪到了“设备接入”或“物模型”里但你要找的本质是三个值产品 ID、设备 ID、APIKey。创建完产品后在产品下添加一个设备设备名称比如dev_001鉴权信息可以自己填也可以自动生成。然后进入设备详情找到 APIKey 相关选项复制设备级 APIKey。老控制台里叫做“APIKey”新版有些位置叫“密钥”或“鉴权信息”原则是哪个值能作为api-key请求头使用哪个就是要找的 APIKey。这一步千万别拿产品的“产品ID”当设备ID用路径不对会返回 404。2.3 用 curl 在电脑上先验证一次数据点上报硬件还没接之前先用电脑验证 OneNet 接口通不通。OneNet 的数据点上报接口路径一般是/devices/{device_id}/datapoints方式为 POST JSON。先在终端里执行下面这条命令把设备 ID 和 APIKey 换成自己的。curl -X POST \ http://api.heclouds.com/devices/12345678/datapoints \ -H api-key: AbCdeFgHijkLmN... \ -H Content-Type: application/json \ -d {datastreams:[{id:temp,datapoints:[{value:25.6}]}]}请求里datastreams是数组可以一次上报多个数据流每个数据流下的datapoints也是数组支持批量上报多个时间点的数据。这条命令执行后如果返回内容里有errno:0说明数据已经写入 OneNet。errno非 0 时后面的error字段会给出原因比如 APIKey 不对、格式错误等。字段类型说明id字符串数据流名称OneNet 会自动创建value数值数据的当前值浮点或整数均可datapoints数组支持单个或多个数据点datastreams数组一次上传多个数据流curl 验证通过后OneNet 的设备数据流里应该能看到 temp 的最新值。此时平台侧已经准备好接下来把注意力放到 SIM800C 上。3. SIM800C 上电、注册网络与建立 TCP 连接的 AT 指令时序SIM800C 是一个 2G/GPRS 模块通过串口和 STM32 通信。它的操作模式是“发 AT 指令看返回结果”。很多人在这一步折戟不是因为指令难而是因为电源和时序没处理好。串口助手里能看到 AT 指令的回显但指令不执行、返回 ERROR、或者根本没反应大半是硬件问题。3.1 SIM800C 与 STM32 的接线电平、电源、PWRKEY 三个坑SIM800C 的串口高电平大约 2.8VSTM32 的串口是 3.3V 逻辑。市面上的 SIM800C 开发板大多自带电平转换电路可以直接和 STM32 的 USART 交叉连接模块 TXD 接 STM32 的 RX模块 RXD 接 STM32 的 TX。如果自己画板不要直接把 2.8V 和 3.3V 互怼建议加转换芯片或电阻分压。供电是最容易出问题的地方。SIM800C 在 GSM 发射瞬间电流峰值能到 2A 左右STM32 开发板上的 3.3V LDO 完全带不动。常见做法是给模块单独接一个 4V/2A 以上的电源或者用 LM2596 这类降压模块从 5V 或 12V 降压到 4V。用面包板连接时还要注意杜邦线压降线太长太细会导致模块在发射瞬间电压跌落直接表现为“能发 AT 但一拨号就重启”。PWRKEY 引脚决定了模块怎么开机。SIM800C 上电后默认不开机需要把 PWRKEY 拉低大约 1.2 秒再松开。之后等 2 到 3 秒再发 AT 指令。如果模块上的 NETLIGHT 指示灯在慢闪说明正在搜网如果快速闪烁约 1Hz说明已经注册上网络。3.2 上电后的 AT 指令序列同步、查信号、注册网络模块开机后第一步是“同步波特率”。如果 STM32 和 SIM800C 的串口波特率都是 9600 或 115200直接发AT返回OK就说明通信正常。有时模块刚开机没准备好可以发几次AT或者先发一个不带回车的空字节。AT ATE0 ATCSQ ATCREG? ATCSTTcmnet ATCIICRATE0是关闭回显关闭后串口输出干净很多程序判断返回结果时不容易被回显干扰。ATCSQ返回信号质量格式如CSQ: 15,0第一项范围 0 到 31数值越大信号越好10 以上基本可用99 表示无信号。ATCREG?返回CREG: 0,1第二个字段为 1 表示已注册到本地网络5 表示漫游0 表示未注册。信号和注册都正常后再配置 APN 并激活 GPRS。指令期望返回含义失败时查什么ATOK串口通信正常波特率、接线、PWRKEY 开机ATCSQCSQ: 12,0信号质量SIM 卡是否插好、天线是否接上ATCREG?CREG: 0,1网络注册状态SIM 卡是否欠费、模块是否在服务区ATCSTTcmnetOK设置 APN物联网卡 APN 可能与默认不同ATCIICROK激活 GPRS 场景卡未开通 GPRS 数据业务ATCSTTcmnet是设置 APN。大多数手机卡和物联网卡的上网 APN 是cmnet但部分专用卡有独立的 APN 名称以运营商提供的参数为准。ATCIICR是激活 GPRS 场景类似“拨号上网”。执行成功后可以用ATCIFSR查询模块获得的本地 IP。如果ATCIICR返回 ERROR先确认ATCSQ和ATCREG?都正常再检查 APN 是否正确。3.3 建立 TCP 连接ATCIICR 附着 GPRS 后连 OneNet 域名GPRS 附着成功后下一步是建立 TCP 连接。OneNet 老版 HTTP 接口的域名是api.heclouds.comHTTP 端口是 80。SIM800C 支持直接通过域名建连但需要先开启 DNS 解析功能指令是ATCDNSGIP1。之后再执行建连指令ATCDNSGIP1 ATCIPSTARTTCP,api.heclouds.com,80正常情况下模块会输出CONNECT OK。如果返回ERROR或CLOSED大概率是 GPRS 没有附着成功或者 DNS 解析失败。此时回头执行ATCIFSR拿到 IP 才能说明网络层面已通。建连成功后SIM800C 就处于一个透明的 TCP 通道里可以向服务器发送原始数据。4. 组 HTTP POST 包上传 OneNetContent-Length 是最容易翻车的点TCP 建好之后很多人会直接往模块里丢 JSON 字符串然后发现 OneNet 没有任何反应。原因很简单服务器要的是合法 HTTP 报文不是裸 JSON。HTTP 报文的四个基本要素请求行、请求头、空行、请求体缺一不可。而请求头里的 Content-Length 必须和请求体字节数完全一致这是整个 DIY 里最容易翻车的地方。4.1 为什么不用 ATHTTPTCP 裸连跑 HTTP 更可控SIM800C 固件里其实自带一套 HTTP 扩展指令比如ATHTTPINIT、ATHTTPPARA、ATHTTPDATA。这套指令能省掉手动组 HTTP 报文的工作但实际用起来并不省心一次 HTTP 请求要拆成五六条指令ATHTTPDATA对长度参数也要求精确出错后排查链路长。相比之下我一般用 TCP 裸连接自己组报文先把报文在 STM32 里拼好通过ATCIPSEND一次发出。这样做的好处是逻辑透明服务器返回的原始 HTTP 响应也能直接看到而且换用 NB-IoT 或 4G 模块时组包代码完全不用改。需要注意 OneNet 老版 HTTP 接口支持http://明文传输不需要 TLS。SIM800C 的固件虽然有些版本支持 SSL但握手过程占用资源大、容易失败调试阶段别给自己找麻烦。等整条链路跑通真有加密需求再考虑换带硬件加密的 4G 模块。4.2 组装 HTTP POST 请求先算 Content-Length 再整包发送组包顺序有讲究先把请求体 body 拼好再用strlen算出 body 长度最后把这个长度填进请求头。反过来先写头再算长度也可以但很容易出现“头里写死了长度body 改了一个字节忘记同步”的隐蔽 bug。char body[256]; int body_len; body_len sprintf(body, {\datastreams\:[ {\id\:\temp\,\datapoints\:[{\value\:%.1f}]}, {\id\:\hum\,\datapoints\:[{\value\:%.1f}]}, {\id\:\lat\,\datapoints\:[{\value\:%.6f}]}, {\id\:\lng\,\datapoints\:[{\value\:%.6f}]} ]}, temp, hum, lat, lng); char header[128]; int header_len; header_len sprintf(header, POST /devices/%s/datapoints HTTP/1.1\r\n Host: api.heclouds.com\r\n api-key: %s\r\n Content-Type: application/json\r\n Content-Length: %d\r\n \r\n, device_id, api_key, body_len);这里有几个细节。第一Content-Length的单位是字节不是字符数ASCII 数值和字符串在内存里一一对应sprintf返回的正好是字节数。第二HTTP 行的结束符必须是\r\n也就是回车加换行两个字节写成\n在某些服务器上会直接报 400。第三请求头和 body 之间有一个单独的空行也就是最后一个\r\n这个空行不能省。最后一行的%d对应的是body_len如果用sizeof去算就会把后面没用的缓冲区空间也数进去Content-Length 偏长服务器一直等不完请求体会超时。4.3 ATCIPSEND 发送流程与 0x1A 结束符报文拼好后的发送流程是先发ATCIPSEND模块返回后把 header 和 body 连续发出最后发一个十六进制的0x1A作为结束标志。这个0x1A是 SIM800C 的 TCP 透传结束符类似键盘上的 CtrlZ它的作用是告诉模块“这包数据到此为止可以封装发送了”。USART_SendString(ATCIPSEND\r\n); // 等待串口收到 字符 delay_ms(50); USART_SendString(header); USART_SendString(body); USART_SendByte(0x1A); // 十六进制 0x1A不是字符串 1A初学最容易在这里踩坑的是把0x1A当成了字符串1A来发。字符串1A是两个字节字符1和字符A对应的十六进制是0x31 0x41。而模块要的是单个字节0x1A。用串口助手手动调试时要在 HEX 发送模式下输入1A而不是在文本模式下输入1A再点发送。发送完这个字节后模块正常会返回SEND OK表示数据已经从模块发出。有时服务器响应很快模块还没来得及打印SEND OK直接输出CLOSED这也是正常情况说明服务器收到数据后主动关闭了连接。CLOSED不一定代表失败要看后续服务器响应的内容。4.4 用响应报文判断上传结果SIM800C 在 TCP 模式下会把服务器返回的原始数据直接推到串口OneNet 的响应通常长这样HTTP/1.1 200 OK Content-Type: application/json Content-Length: 37 {errno:0,error:succ}只要看到HTTP/1.1 200 OK和errno为 0数据就写入成功。errno非 0 时重点看error字段的英文提示比如invalid api-key就是 APIKey 错误device not found是设备 ID 写错。收到响应后用ATCIPCLOSE关闭当前 TCP 连接为下一次上报做准备。这里还要提一个判断时序模块返回的数据是一次性输出的一整段而不是先输出 HTTP 状态行再等一会儿输出 body。如果你的 STM32 串口接收代码只读前几个字节就结束可能只能拿到HTTP/1.1 200而拿不到errno。调试阶段建议把完整响应缓存下来再按\r\n\r\n切分头部和 body。5. STM32 端采集DHT11 温湿度与 GPS NMEA 数据的解析数据能发到 OneNet 之后剩下的问题就是“采集端怎么把温湿度和 GPS 坐标变成干净的数值”。这个标题涉及两个典型外设DHT11 是单总线协议GPS 模块是串口 NMEA 协议。两者的坑都不在硬件上而在时序和单位换算上。5.1 DHT11 温湿度读取单总线时序与校验和DHT11 只有一根数据线通过时序区分 0 和 1。读取一次的流程是主机把总线拉低至少 18ms再释放并延时 20 到 40us此时 DHT11 会回一个 80us 的低电平响应之后拉高 80us然后开始输出 40 位数据。每位数据都是先拉低 50us再拉高高电平时间约 26 到 28us 表示 0约 70us 表示 1。uint8_t dht11_buf[5]; if (DHT11_Start() 0) { for (i 0; i 5; i) { dht11_buf[i] DHT11_ReadByte(); } // 校验前四个字节之和的低8位应等于第五个字节 if ((uint8_t)(dht11_buf[0] dht11_buf[1] dht11_buf[2] dht11_buf[3]) dht11_buf[4]) { hum dht11_buf[0]; // 湿度整数部分 temp dht11_buf[2]; // 温度整数部分 } }DHT11 的温湿度数据格式是湿度整数、湿度小数、温度整数、温度小数、校验和DHT11 的小数位通常为 0DHT22 才有实际小数。校验和是前四个字节相加取低 8 位校验不通过的数据直接丢弃不要上传到云平台。DHT11 对时序中断极其敏感。读取过程中如果发生串口中断或定时器中断很容易导致电平跳变检测超时。在裸机环境下读取 DHT11 时建议关闭全局中断在 FreeRTOS 环境下建议用临界区保护整个读取过程或者把 DHT11 读取放在优先级较高的任务里但仍要处理中断延迟问题。另一个常见错误是 GPIO 方向切换。DHT11 要求主机先输出模式拉低总线再切换输入模式读取响应如果初始化时把引脚配置成开漏输出并接了上拉电阻也可以省去切换但大多数代码仍然采用“输出/输入切换”的写法逻辑更直观。5.2 GPS NMEA 解析GGA 语句里的经纬度不是十进制GPS 模块的输出按照 NMEA 0183 协议常用语句有 GGA、RMC、GSV 等。其中 GGA 语句包含定位状态、经纬度、卫星数、海拔等关键信息适合做入网数据源。一条典型的 GGA 语句长这样$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47按逗号切分后第 2 个字段是纬度4807.038第 3 个字段是方向N第 4 个是经度01131.000第 5 个是方向E第 6 个字段1表示定位状态0表示未定位。解析时直接从缓冲区里找$GPGGA然后逐段拆逗号。char *p strstr(gps_buf, $GPGGA); if (p) { double lat_raw, lng_raw; char lat_dir, lng_dir; int fix, sat; sscanf(p, $GPGGA,%*s,%lf,%c,%lf,%c,%d,%d, lat_raw, lat_dir, lng_raw, lng_dir, fix, sat); if (fix 0 sat 4) { double lat (int)(lat_raw / 100) (lat_raw - (int)(lat_raw / 100) * 100) / 60.0; double lng (int)(lng_raw / 100) (lng_raw - (int)(lng_raw / 100) * 100) / 60.0; if (lat_dir S) lat -lat; if (lng_dir W) lng -lng; } }sscanf的%*s表示跳过时间字段%lf读取浮点经纬度%c读取方向。这里最关键的坑是单位换算NMEA 里的4807.038是“度分”格式表示 48 度 07.038 分不是十进制的 48.07038 度。换算公式是度取整数部分分除以 60 后加到度上。如果直接把4807.038当十进制数上传地图上的位置会偏出几十公里。另外南纬和西经要取负值否则坐标会落在错误半球。GPS 模块串口通常是 9600 波特率和 STM32 的某一个 USART 对接。串口接收建议做成环形缓冲区在中断里逐字节存入主循环里按行查找 GGA 语句。如果直接在主循环里等一整行数据模块 1 秒输出多条语句很容易漏数据。也可以先看\n或\r\n切分行再从行里找$GPGGA逻辑更稳。5.3 GPS 漂移处理先用距离阈值做一次过滤GPS 模块的原始定位数据存在两类误差一类是环境误差比如城市高楼反射导致的多径效应另一类是接收机本身的噪声表现为静止时坐标也会缓慢漂移。针对单点定位的应用没必要上卡尔曼滤波一个简单的“距离阈值”方案够用。基本思路是每收到一个新定位点先和上一个有效点计算距离如果距离超过预设阈值认为这次跳变大概率是漂移丢弃如果距离在阈值以内则更新当前位置。对于静止或慢速移动的场合阈值可以设 20 到 50 米对于车速较高的场景阈值要放宽否则正常移动会被误杀。两点距离的近似计算可以用等距圆柱公式在几十公里范围内精度足够double dx (lng2 - lng1) * cos((lat1 lat2) / 2 * PI / 180) * 111320.0; double dy (lat2 - lat1) * 111320.0; double dist sqrt(dx * dx dy * dy);111320是地球一米对应的纬度度数换算值经度方向需要乘以纬度的余弦值修正。这段代码在 C 里建议用double计算避免浮点精度不足。还有一个容易被忽略的点如果设备长时间静止同一坐标会重复上报OneNet 折线图看起来是一条直线这是正常现象。可以在业务层做一个“数值变化小于阈值不上报”的判断省流量也减少平台存储压力。6. 从 AT 指令到 OneNet 折线图一条链路的分段验证与排错整条链路的调试顺序应该是“先模块、再平台、后代码”很多人一上来就让 STM32 自动跑全流程出问题后无从下手。更靠谱的做法是先用 PC 串口助手把 SIM800C 调通再让 STM32 接管。6.1 用串口助手把 SIM800C 单独调通再合代码把 SIM800C 的串口接到 PC 的 USB 转 TTL 模块上用串口助手操作逐步确认上电后发 AT 能回 OKATCSQ 信号正常ATCIICR 能附着 GPRSATCIPSTART 能连上 OneNet 域名。然后手动把第 4 章的 HTTP 报文内容复制到串口助手里以 HEX 模式发送结束符0x1A观察 OneNet 返回的响应。如果这一步能返回errno:0说明 SIM800C 和 OneNet 之间的链路完全没问题剩下的事情都集中在 STM32 代码上。6.2 OneNet 数据流折线图与上报频率选择数据上传成功后登录 OneNet 控制台进入设备详情打开“数据流管理”或“数据展示”选择 temp 数据流就能看到按时间顺序的折线图。曲线不出现时先看平台的数据时间戳如果设备上报的数值正常但曲线断断续续往往是因为 SIM800C 每次上报后都断开 TCP数据虽然到达但存在秒级延迟。上报频率方面温湿度这类缓变量建议 30 秒到 60 秒一次GPS 轨迹应用可以 5 到 10 秒一次。OneNet 免费版对请求频率有速率限制短时间内高频请求可能返回限流错误使用前留意平台说明。6.3 三个部署细节SIM 卡 APN、GPS 翻转补丁、2G 覆盖实际部署时还有几个容易被忽略的细节。第一物联网卡的 APN 不一定都是cmnet一些专网卡使用独立 APN拿到卡后先确认运营商提供的参数否则 ATCSTT 执行成功但 ATCIICR 却一直失败。第二如果你用的是库房里的老款 GPS 模块在 2019 年之后出现“一直搜不到星”的现象先查 GPS 周数翻转问题。早期部分 GPS 模块固件未处理周数溢出需要刷新版固件或更换模块网上的解决方案常被称为 GPS 翻转补丁。第三SIM800C 依赖 2G 网络部分地区的 2G 覆盖已经减弱甚至退网这类方案在产线部署前一定要到目标区域实测信号如果覆盖不达标把 SIM800C 换成 NB-IoT 或 4G 模块本文里的 HTTP 组包方式和 OneNet 数据流定义可以原样迁移改动集中在 AT 指令集上。本文还有配套的精品资源点击获取