我把这个模块从拆包装到调通、再到画板子、写驱动的完整过程捋了一遍。文章会先讲选型思路,再讲AT指令快速跑通,然后是硬件电路设计要点和驱动代码坑点,最后聊实际拉距和稳定性优化。文章主体是纯技术干货,章节名按实际内容命名,结构独立,不会跟其他文章的模板重名。
1. E103-W02到底是颗什么样的料:模块定位与选型思路
先别急着看驱动代码,我拿到这个模块后做的第一件事是翻手册、理清它的定位。E103-W02本质上是一颗基于ESP8266方案做的Wi-Fi串口透传模块,亿佰特在出厂前把固件刷成了“串口转Wi-Fi”的形式。它和市面上常见的ESP-01、ESP-12F这些裸模块最大的区别在于:它不需要你操心AT指令之外的任何初始化流程,上电就是透传模式或配置模式二选一,串口发什么,远端TCP/UDP服务器就收什么,反过来也一样。
这种定位就决定了它的应用场景:主控端不需要跑TCP/IP协议栈,也不需要懂socket编程,只要有一颗带UART的单片机,甚至是USB转串口接电脑,就能把一个原本“有线”的设备变成“无线”的设备。我当时的项目是要把一个温湿度采集器改成Wi-Fi上报,原来用的是RS485走线,现场布线麻烦得很,换成这个模块之后,主控板那边几乎没改代码,只把UART数据从485芯片改接到模块的串口上,剩下的Wi-Fi连接全部交给模块自己处理。这个“低侵入性”是它最大的价值。
选型的时候我也对比过其他方案,比如直接用ESP32做模组、自己跑Socket程序,或者用更贵的Wi-Fi模组加MCU方案。但如果你只是想快速打通“MCU到云端/局域网”的通道,不想在协议栈上投入太多研发时间,E103-W02这种串口透传方案是最省事的。它把AT指令集做成了用户接口,网络状态变化、TCP重连这些都会主动通过URC消息推送给串口,主控端只需要做“收到什么字符串、该回什么指令”的状态机就行。
它的使用边界我也说一下:它不是一个高吞吐的Wi-Fi模组,串口波特率最高也就跑到460800(常见的稳定配置是115200),所以传大文件、传视频这类场景它不是最优选。它适合的正好是“小数据、低频率、长连接”的物联网上报场景,比如传感器数据、设备状态、控制指令。想清楚这个边界,后面做硬件设计和写驱动的时候就不会走偏。
2. 5分钟上手的核心:AT指令链路与三种常用工作模式
真正上手的第一步,不是去写代码,而是先把模块通过USB转TTL接到电脑上,用串口助手把AT指令链路调通。这个环节是整个项目里最不能跳过的部分,因为后面所有驱动代码、硬件设计,都要围绕“模块到底工作在哪种模式下”来展开。E103-W02支持的工作模式比较多,但实际项目里常用的就三种:Station模式(STA)、SoftAP模式、以及透传模式。
- Station模式:模块作为客户端去连接路由器,然后主动向TCP/UDP服务器发起连接。这是物联网设备最常用的模式。
- SoftAP模式:模块自己开一个热点,手机或电脑连上这个热点后,通过固定IP端口收发数据。适合现场调试、无路由器场景。
- 透传模式:模块在Station或SoftAP基础上,上电后自动连接预设的服务器并进入数据透传,此时串口收到的任何字节都会原样发到网络端。
三种模式里,透传模式最常用,但前提是先用AT指令把参数配置好。我第一次上手时踩过一个坑:以为模块默认就是透传模式,接上串口直接发数据,结果收到的全是乱码和回声。后来才搞明白,新模块默认是AT指令模式,要手动进入透传,而且透传状态下想退出得发“+++”——注意,这三个加号前后不能有回车换行,否则会被当作普通数据发给服务器。
具体配置流程我整理成了一套最稳的步骤,照着做基本不会再迷路:
- 模块上电前先确认串口助手波特率是115200、8N1,供电用3.3V,电流至少500mA(ESP8266方案Wi-Fi射频发射瞬间电流不小,电脑USB口直接供容易掉电复位)。
- 打开串口助手,发送
AT,模块返回OK,确认AT指令链路正常。 - 发送
AT+CWMODE=1切换到Station模式,有返回OK就行。 - 发送
AT+CWJAP="你的Wi-Fi名","密码"连路由器,返回WIFI CONNECTED后再等WIFI GOT IP出现,这时候IP已经拿到了。 - 以连TCP server为例,发送
AT+CIPSTART="TCP","xxx.xxx.xxx.xxx",8080,返回CONNECT OK说明链路建立成功。 - 发送
AT+CIPMODE=1开启透传模式,然后再发送AT+CIPSEND,模块返回>后,串口发出去的所有内容就直接到服务端了。 - 不想透传了,就发
+++退出,回AT模式。
这七步做完,模块的透传通道就算跑通了。我第一次从零到跑通大概用了不到10分钟,其中大半时间花在翻手册确认AT+CIPMODE和AT+CIPSEND的组合顺序上。这里的关键逻辑是:必须先在非透传状态下把TCP/UDP连接建好,才能切透传模式。如果连接还没建立就开透传,数据会进缓冲区但发不出去,表现就是串口发了数据服务器收不到。
如果你是准备把模块收到自己的嵌入式项目里,建议做一个“AT参数配置工具”,把这些指令做成预置按钮,每次拿到新模块先批量配置一次,然后才焊到板子上。我习惯把Wi-Fi名、密码、服务器IP、端口、波特率都写成固定的初始化序列,上电后判断模块是否已经配置过,没配置过就进AT模式跑一遍,配置过就直接切透传。这样可以做到“同一套代码,换现场不换固件”,只要用串口工具预配置一次就行。
3. 硬件电路设计的几个关键点:开源电路到底怎么抄、怎么改
标题里写了“开源电路”,我一开始也挺看重这个。亿佰特官方给出的参考电路相当精简,核心就是模块的供电和串口电平转换。但拿到原理图和PCB之后我发现,照着抄能跑,但要跑得稳,有几个细节必须自己补课。
先看供电。E103-W02的VCC是3.3V,但ESP8266方案在Wi-Fi射频发射的瞬间电流峰值能到300mA以上,而且是毫秒级的脉冲。如果供电芯片输出电流不够、或者输出电容偏小,电压就会被拉低,模块就会重启。我在设计里用的是AMS1117-3.3的LDO,输入5V,输出端并了220uF电解电容加一个100nF的陶瓷电容,实测拉距时没有再出现过复位问题。有人觉得1117最大输出1A够用了,但其实它的动态响应并不算快,加上Wi-Fi这种脉冲负载,大电容比高电流标称更重要。
然后是串口电平。模块的TX、RX是3.3V TTL电平,如果你的主控是5V的STM32F103那种传统板子,直接连会烧IO——虽然模块内部有部分ESD保护,但长期高压灌入迟早出问题。正确做法是加电平转换电路:TX方向用三极管反相器,RX方向用电阻分压或者用专用电平转换芯片。我用过最简单的方案是两颗2N7002 MOSFET加两个上拉电阻做双向电平转换,成本几毛钱,速度在115200下毫无压力。如果你用的是新出的STM32F4、GD32、或者ESP32这类3.3V主控,那就可以直连,不用加转换。
天线布局是我这次最想提醒的一个点。E103-W02板载的是PCB天线,它要求模块下方和天线周围要净空,不能铺铜,也不能走线。我第一版布局时为了省面积,把模块贴着板边放,天线正下方还是一大片VCC铺铜。结果实测两米外数据就开始丢包,检查半天才想到是天线被地平面罩住了。后来改成模块天线部分完全悬空伸出板边,下面不铺任何铜,同样环境下能拉到30米以上。这个改动原理很简单:PCB天线的辐射方向图和有效高度受周围金属影响极大,铺铜等于给它加了个屏蔽罩,信号全被吸收了。所以抄开源电路时,模块的封装和摆放位置建议直接沿用原厂参考,别为了美观或紧凑乱动。
最后是三个容易被忽略的引脚:RST、GPIO0、CH_PD(有些版本叫EN)。CH_PD必须上拉到3.3V,否则模块不工作;GPIO0在正常运行时也要上拉,它关系到启动模式,低电平会让模块进入下载模式,你那颗模块就会开机不跑固件。我会在这三个脚上都留上拉电阻位,同时各引一个0欧电阻跳线到排针,这样既能保证正常启动,后续如果要做OTA远程升级,也能直接从排针拉低GPIO0进入下载模式,而不用拆模块下来改跳线。
电源输入端的滤波我也加了点东西:5V输入先过一颗磁珠再进LDO,LDO输出端除了上面说的大电容,还加了一颗TVS管做浪涌保护。做工业现场的朋友不要省这个,Wi-Fi模块装在配电柜里,旁边继电器一吸合,电源线上经常有几十伏的毛刺,TVS能把这些尖峰吃掉,否则模块会不定时重启,排查起来非常痛苦。
4. 串口驱动代码里最容易被忽略的坑:从轮询到中断的设计差异
模块本身只是硬件,真正让它纳入整个设备体系的,是主控端的串口驱动。我在STM32和Linux上都写过E103-W02的驱动,设计思路上有一点点差异,但共通的坑是同一个:模块的串口发送有一个“软缓冲”机制,不能按普通串口外设的思维方式去处理数据。
先说STM32 HAL库环境下的实现。很多人的第一版代码是这么写的:主循环里轮询HAL_UART_Receive,收到一个字节就存一个字节,然后等收到一帧完整数据再去处理。这在短数据、低频率下跑没问题,但E103-W02有个特性:它从Wi-Fi端收到的数据是“凑包”的,也就是说网络侧发过来的数据可能被合并在一个TCP段里一起从串口吐出来,也可能被拆成好几个段分开吐。你按“一帧一帧”去解析,协议边界完全对不上。
我的做法是开一个环形缓冲区(Ring Buffer),通过串口空闲中断(IDLE Line Interrupt)来判断“这一批数据已经到齐了”,然后置一个信号量,主循环里拿信号量后统一提取并解析。UART接收用DMA,DMA接收到空闲中断标志,这样整个传输过程不占CPU。关键代码思路是:
// 环形缓冲区定义 #define RING_BUFFER_SIZE 1024 uint8_t rx_buf[RING_BUFFER_SIZE]; volatile uint16_t rx_head = 0, rx_tail = 0; // 启用UART IDLE中断 + DMA接收 HAL_UART_Receive_DMA(&huart1, rx_buf, RING_BUFFER_SIZE); __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); void HAL_UART_IdleCallback(UART_HandleTypeDef *huart) { if (huart == &huart1) { uint16_t dma_pos = RING_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); if (dma_pos > rx_tail) { data_len = dma_pos - rx_tail; } else { data_len = RING_BUFFER_SIZE - rx_tail + dma_pos; } // 简单处理:把新到达的数据指针放到一个待解析队列 process_wifi_data(&rx_buf[rx_tail], data_len); rx_tail = dma_pos; } }这个方案本质上是“环形缓冲 + DMA + 空闲中断”三件套,大多数串口场景都能覆盖。唯一要注意的是缓冲区大小设计:我默认开1024字节,如果你的应用会传比较大的包,要按最大包长的两倍以上来设。太小会丢包,太大浪费RAM——对于STM32F103这种只有20KB RAM的料,这还真得算计一下。
再说Linux环境。我在树莓派和Ubuntu上直接通过/dev/ttyUSB0用纯C写过一个桥接程序,思路是开两个线程:一个读串口、一个读socket,互发数据。这里最大的坑是串口的“行规程”(line discipline)设置:默认情况下Linux的tty驱动会把收到的\n字符当作行结束符做缓冲,还会把\r\n做转换。你如果不做原始模式设置,Wi-Fi端收上来的二进制数据会被破坏,比如0x0A会变成0x0D 0x0A,这种问题排查起来真的很隐蔽。
正确做法是打开串口后马上设置原始模式:
int fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY); struct termios options; tcgetattr(fd, &options); cfmakeraw(&options); cfsetispeed(&options, B115200); cfsetospeed(&options, B115200); options.c_cc[VTIME] = 10; // 最多等1秒 options.c_cc[VMIN] = 0; // 有数据就读,没数据就返回 tcsetattr(fd, TCSANOW, &options);cfmakeraw这个函数把ICANON、ECHO、ISIG全部关掉,串口就变成了纯粹的字节管道,数据进去是什么出来就是什么。这个函数Windows的串口API里没有对应物,但Windows下创建串口后用SetCommState也要注意fBinary必须为TRUE、fOutxCtsFlow和fRtsControl要根据实际硬件流控设置。不做这一步,Windows下有时会出现串口收到一半数据就卡住的情况,其实就是流控线状态不对。
Linux下还有一个很隐晦的点:打开串口文件时如果用了O_NDELAY,并且串口设备还没就绪,open本身就会卡住或者返回错误。我实际踩过:USB转串口刚插上就执行程序,open失败,报No such device or address,其实等设备节点稳定出现后就好了。现在的系统用udev规则自动创建节点,理论上不用等,但在某些精简内核配置下,还是建议加一个重试机制,循环试5次,每隔100ms一次。
5. 从AT指令到云平台:一套可用性较高的驱动代码是怎么组织出来的
讲完串口侧,再看整体驱动的组织结构。我不喜欢把AT指令裸写在业务代码里,那样后期维护就是噩梦。E103-W02的驱动我一般分三层:第一层是串口硬件抽象,第二层是AT指令封装,第三层是模块状态机。下面重点说第二层和第三层,因为第一层上面已经讲完了。
AT指令封装层要做的事情很简单:把“发AT指令”“等OK/ERROR响应”这种通用操作做成基础函数,再在基础函数之上拼出具体的业务指令。核心问题是“怎么知道指令有没有执行成功”,这取决于模块返回的响应串。我的做法是发完指令后阻塞等待一个“期望响应”,带超时,比如500ms内没等到就算失败。这个等待函数很多人用HAL_Delay死等,其实不好用,最好用一个“接收解析回调”配合标志位。
伪代码思路:
typedef enum { AT_STATUS_IDLE, AT_STATUS_WAIT_OK, AT_STATUS_WAIT_CONNECT, AT_STATUS_WAIT_DATA } at_status_t; // 接收线程/中断里做状态匹配 void on_uart_line(char *line) { switch (at_status) { case AT_STATUS_WAIT_OK: if (strstr(line, "OK")) { at_status = AT_STATUS_IDLE; at_result = AT_RESULT_OK; } else if (strstr(line, "ERROR")) { at_status = AT_STATUS_IDLE; at_result = AT_RESULT_ERROR; } break; case AT_STATUS_WAIT_CONNECT: if (strstr(line, "CONNECT OK")) { // 连接成功,进入透传前的准备状态 at_status = AT_STATUS_IDLE; } break; } }这一层设计好了,第三层状态机就简单得多:它的作用是把模块的整个生命周期分成几个状态,比如POWER_ON、WAIT_CONFIG、CONNECTING、TRANSPARENT、RECONNECT。应用层调用时,不管现在模块处于什么状态,只需调用统一的接口wifi_send(data, len),状态机内部自己处理“当前是否在透传、是否需要重连”的问题。
状态机里最难控制的是断线重连。Wi-Fi环境没有绝对稳定,路由器重启、AP信号波动、TCP连接被服务端回收,都会导致模块从透传模式掉出来,URC消息会推送WIFI DISCONNECT、CLOSED之类。如果不做状态机,只靠业务层反复发送数据,你会看到现象是:模块已经断线了,但串口还在发数据,数据全部进了模块的内部缓冲区,等缓冲满了直接丢。所以驱动必须在检测到断线事件后,主动退出透传模式、重新做TCP连接,然后再次进入透传。这个过程要带重试次数限制,比如连续5次连接不上,就进入RECONNECT状态,间隔10秒再试,避免反复快速连接把路由器搞崩。
我把整个驱动做成了一个独立的.c/.h文件,对外只暴露三个接口:
int wifi_module_init(void); // 初始化串口和状态机 int wifi_module_send(const uint8_t *data, int len); // 发送数据 int wifi_module_recv(uint8_t *data, int max_len); // 接收数据业务代码完全不用知道AT指令怎么发、TCP怎么连,只需要在初始化时传入Wi-Fi配置,之后把要上报的数据丢给wifi_module_send就行。这套结构对以后换其他Wi-Fi模块也友好——只要把AT指令封装层替换掉,上层状态机和业务代码不用动。做过几个项目之后你会发现,这种“硬件抽象+协议封装”的思路才是驱动代码里最有价值的沉淀,比把AT指令散落得到处都是的写法要省心得多。
6. 实测拉距与常见的异常现象排查
代码写完了,硬件贴好了,总要拿实测说话。我在室内办公室环境做了几轮测试,参数是:发射功率默认20dBm,串口波特率115200,TCP连接一个局域网内的电脑服务端,每隔500ms发一包26字节的数据,连续跑2小时。结果分成三档:
- 同一房间,距离5米以内,无遮挡:零丢包,RTT基本稳定在1毫秒以内。
- 隔一堵砖墙,距离10米:偶发一两个TCP重传,应用层几乎感知不到丢包。
- 隔两堵承重墙,距离20米:开始出现明显丢包和时延抖动,麦克风级数据流已经可感知卡顿,但传感器上报这种低频数据还能接受。
说明这颗模块的常规可靠距离在有遮挡时确实有限。如果你需要更远距离,可以外接天线版本的型号会好很多。板载PCB天线版本更适配“小空间、近距离、弱遮挡”的场景,这是硬件选型时就该权衡好的。
测试过程中我也遇到两个极其典型的异常现象,拿出来说说。
第一个是模块不定时重启。现象是跑得好好的,突然串口打印出乱码,然后所有连接断开,约3秒钟后自动恢复。我最初怀疑是固件问题,后来抓模块的VCC波形才发现,电源电压在Wi-Fi射频发射瞬间跌到了2.8V左右,触发掉电复位。解决方案就是前面说的:加大输出电容、检查供电芯片散热。这类问题的核心排查思路是“先看供电再看代码”,不要一上来就查驱动和固件。
第二个是串口发数据服务器收不到,但服务器发数据模块能收到。这个坑最隐蔽。我排查了好几天,最后定位到是透传模式下,串口发送数据时如果发送间隔太短,模块内部的波特率自适应或者数据打包逻辑会吞掉连续字节。ESP8266方案在这个方面表现不算好:它在透传时会把串口数据按时间片打包成TCP包,如果两个相邻串口字节间隔超过一定阈值,就会被拆到两个TCP包里。有些服务端程序按“包”处理数据,就会认为这是两条消息,导致业务层解析错乱。解决办法有两条:一是把串口侧一次发送的数据保证在一个时间片内发完,不要在主循环里一个字节一个字节地挤牙膏;二是在服务端改成按帧解析,别按TCP包解析。如果只能是按包处理,那就要在业务数据前加帧头帧尾,服务端做缓存再解帧。
还有一个现象在长时间TCP连接上特别常见:服务端主动断开连接后,模块并不会立刻告诉串口侧,而是等到下一次串口发数据时才通过URC消息推送CLOSED。如果业务层连续发数据的速度很快,驱动可能还没处理到那条URC,就已经把数据又发给模块了,这时模块表现是“接收缓冲区已满”导致数据丢弃。所以我强烈建议在wifi_module_send接口里加一个“模块当前是否还在透传状态”的检查,数据发出去之前先看状态机是不是在TRANSMITTING,不是的话就先走一遍重连流程再发,宁可多等几百毫秒,也不要白白丢数据。
7. 再往后走一步:模块固件升级与低功耗设计的取舍
最后聊两个进阶话题,一个是固件升级,一个是功耗。
E103-W02支持通过串口进行固件升级,方式是GPIO0拉低后上电,进入下载模式,然后使用亿佰特自家的上位机工具通过串口烧录。我在实际项目里是把GPIO0引到一颗三极管的集电极,用MCU的一个GPIO控制。这样整个升级流程就能做成“由MCU通过网络端下发升级指令 → MCU拉低GPIO0 → 给模块断电重启 → 模块进入下载模式 → 通过另一路串口通道烧写固件”。这个功能在批量部署后尤其重要,因为很多产品一旦装到现场,人不可能去拆壳刷机,只能靠远程升级。如果你现在的设计里还没留这个引脚,我建议至少在设计阶段预留一个测试点,别到时候想升级发现硬件连不上。
低功耗是物联网项目的永恒话题。模块工作时电流在70mA~300mA之间波动,这不是一颗适合电池直供电的设备。如果你要做电池供电,建议不要把模块一直挂在电源上,而是用一颗MOS管做电源开关,只在需要上报时给模块上电。比如一个温湿度监测终端,每10分钟上报一次,每次上电到完成上报大约需要3秒(包括冷启动、连接Wi-Fi、连服务器、发数据),其余时间模块完全断电,平均功耗可以压到1mA以下。这里唯一的坑是:模块冷启动建立Wi-Fi连接需要的时间并不短,尤其是弱信号环境下可能长达10秒以上。所以“上报周期”和“实际功耗”之间的换算一定要做实验测,不能只看手册标称。
根据我自己的测试数据,一个典型的冷启动流程是:上电到AT指令就绪约400ms,扫描并连接Wi-Fi约1~2秒,DHCP拿到IP约500ms,TCP建立连接约100ms,总时长基本在2~3秒。如果你用电池供电,这个时长带来的功耗占比会很大,必须和上报频率一起评估。还有一种做法是模块加Deep-sleep模式,需要外部GPIO触发唤醒,但E103-W02这个模块因为做的是透传定位,睡眠和唤醒的控制逻辑不算友好,不如直接断电来得干脆。
说到底,这颗模块的价值就是把“Wi-Fi接入”这个看似复杂的工程问题,抽象成了一个标准的串口设备问题。你的主控只需要会收发串口数据,就能把一个设备变成物联网节点。但这种便利是有代价的——你必须把它的工作模式、断线逻辑、供电特性研究透,才能发挥出它应有的稳定性。希望这篇文章能帮你少走我走过的弯路。