基于ESP32的AIS转WiFi转换器:实现NMEA 0183数据无线传输
2026/5/26 0:26:11 网站建设 项目流程

1. 项目概述:从VHF-AIS接收器到iPad的无线桥梁

作为一名经常在海上折腾电子设备的航海爱好者,我最近遇到了一个挺实际的需求:我的主力导航设备是iPad上的iSailor应用,它功能强大、界面友好,但有个“硬伤”——它需要通过WiFi信号来接收AIS(自动识别系统)数据。而我船上的标准设备是一台VHF-AIS接收机,它老老实实地通过NMEA 0183串口线输出数据,和iPad的WiFi世界完全是两个次元。总不能为了这个每次开船都拖根长长的数据线,还得搞个串口转换器吧?这太不“航海”了。于是,动手打造一个“AIS转WiFi转换器”的想法就自然诞生了。这个项目的核心目标非常明确:构建一个硬件接口,它能从VHF-AIS接收器的NMEA 0183串口读取数据,然后将这些关键的船舶动态信息(位置、航向、航速等)通过WiFi网络广播出去,让我的iSailor App能够无缝接入。更进一步,我还希望它能同时转发GPS数据(通常来自另一台设备),并为未来添加一个本地数据显示屏预留空间。这本质上是一个典型的嵌入式物联网(IoT)项目,涉及硬件接口、数据协议转换和网络服务搭建,非常适合喜欢动手解决实际问题的技术爱好者。

2. 核心需求与方案设计解析

2.1 需求拆解与技术选型

首先,我们需要把用户模糊的描述转化为清晰的技术规格。根据原始描述,我们可以提炼出以下几个核心需求点:

  1. 数据输入:至少两个独立的NMEA 0183串行数据输入通道。

    • 通道A (AIS):波特率38400 bps。这是AIS数据的标准速率,数据量相对较大,因为包含了周围多艘船舶的信息。
    • 通道B (GPS):波特率4800 bps。这是传统GPS设备的常见速率,数据相对简单,主要是本船的位置、时间和速度。
  2. 数据处理与协议转换:设备需要同时监听两个串口,读取原始的NMEA 0183语句(例如,AIS数据主要是!AIVDM/!AIVDO语句,GPS数据是$GPGGA$GPRMC等)。它不需要理解语句的具体内容,但需要可靠地接收它们,并准备进行转发。

  3. 网络输出:建立一个WiFi接入点(AP)或者连接到一个现有WiFi网络(STA模式),并创建一个TCP服务器或UDP广播服务。iSailor这类应用通常支持通过TCP连接(指定IP和端口)来接收NMEA数据流。因此,转换器需要将两个串口汇聚来的NMEA数据流,通过一个网络端口(例如TCP 10110端口是常见选择)发送出去。

  4. 未来扩展:预留一个字符型LCD(4行x40列)的显示接口,以及两个按钮的输入接口,用于未来实现本地数据查看与界面切换功能。

基于以上需求,硬件方案的选择就呼之欲出了。使用高性能的微控制器(MCU)是必然,因为它需要管理多个外设(串口、WiFi、GPIO)。ESP32系列芯片成为了几乎是最优解。原因如下:首先,它集成了双核处理器和WiFi/蓝牙功能,原生解决了网络连接问题;其次,它通常具备多个硬件UART(串口),正好满足我们双通道输入的需求;最后,其社区生态极其丰富,有完善的Arduino核心和库支持,开发效率极高。相比于使用树莓派等单板计算机,ESP32在功耗、体积、成本和实时性方面对这类专用设备更具优势。

2.2 系统架构设计

整个系统的软件架构可以设计如下:

  1. 硬件抽象层:初始化两个硬件UART(UART1和UART2),分别设置为38400和4800波特率。初始化WiFi模块,并将其配置为接入点模式(例如,SSID为“Boat_AIS_Converter”,密码自定),这样iPad可以直接连接它。初始化用于LCD和按钮的GPIO引脚。

  2. 数据接收与缓冲:为每个UART设置中断服务程序(ISR)或使用非阻塞式读取循环。当串口收到数据时,立即将其存入对应的环形缓冲区(Ring Buffer)。使用缓冲区是为了避免数据丢失,特别是在WiFi传输偶尔有延迟时。

  3. 网络服务层:在主循环中,创建一个TCP服务器,监听特定端口(如10110)。当有客户端(iSailor App)连接时,将其连接句柄保存起来。

  4. 数据汇聚与转发引擎:这是核心逻辑。在主循环中,持续检查两个串口的环形缓冲区。如果有数据,就将其读出。这里有一个关键决策点:如何混合两个速率不同的数据流?简单的做法是“来者不拒,先到先发”——从缓冲区读取数据后,直接写入当前活跃的TCP客户端连接。但需要注意,NMEA语句是以换行符(\r\n)结尾的完整句子,必须保证发送时语句的完整性,不能将一个句子拆开到两个网络数据包里。更稳健的做法是,在缓冲区中按行提取完整的NMEA语句,然后再发送。

  5. 扩展功能线程/任务:如果使用ESP32的Arduino框架,可以利用FreeRTOS创建独立的任务。例如,一个任务专门处理网络连接和数据转发,另一个任务负责扫描按钮状态并更新LCD显示屏。这样能保证网络响应的实时性不被显示刷新等操作阻塞。

3. 硬件搭建与核心电路详解

3.1 元器件清单与选型考量

  • 主控芯片:ESP32开发板(如ESP32 DevKit C V4、NodeMCU-32S)。建议选择引脚引出较多的型号,方便连接LCD。
  • 电平转换模块(关键!):NMEA 0183标准有多种电平,常见的是RS-422差分信号(用于长距离)或简单的0-5V单端信号。大多数消费级VHF-AIS接收器输出的是单端信号。ESP32的GPIO引脚是3.3V电平,且不耐5V电压。因此,如果AIS/GPS设备输出5V信号,必须使用电平转换器。推荐使用双向逻辑电平转换模块(如TXB0104),或者用分压电阻电路(例如,1kΩ和2kΩ电阻串联分压,将5V降至约3.3V)。如果设备输出是RS-422,则需要MAX485之类的芯片进行转换。
  • LCD显示屏:4x40字符型LCD,通常基于HD44780控制器或兼容芯片。这种屏幕使用并行接口,需要较多GPIO(至少7个:RS, EN, D4, D5, D6, D7,加上背光控制)。也可以选择I2C接口的版本,只需要2根线(SCL, SDA),大大节省引脚,是更推荐的选择。
  • 按钮与电阻:两个轻触开关,以及两个10kΩ的上拉电阻。
  • 电源:ESP32开发板通常通过Micro-USB供电(5V)。在船上,你需要一个稳定的5V电源,可以从船电系统通过DC-DC降压模块获得。务必考虑电源的防浪涌和隔离,船电环境比较恶劣。

注意:电平转换是硬件成功的第一步,也是最多人栽跟头的地方。直接连接5V信号到ESP32引脚极有可能瞬间烧毁芯片。在接通任何外部设备前,务必用万用表测量其输出信号电压范围。

3.2 电路连接示意图(文字描述)

由于不能使用图表,我将详细描述连接关系:

  1. AIS数据线连接:假设你的AIS接收器NMEA输出线是两根:AIS_TX+(信号) 和GND

    • AIS_TX+接入电平转换模块的5V侧(高压侧)。
    • 将电平转换模块3.3V侧(低压侧)的输出,连接到ESP32的某个RX引脚,例如GPIO16(RX2)。
    • 将AIS接收器的GND与ESP32的GND连接。
  2. GPS数据线连接:与AIS连接类似。GPS设备的TX信号线通过电平转换后,连接到ESP32的另一个RX引脚,例如GPIO4(可以配置为UART的RX)。

  3. LCD连接(以I2C为例)

    • LCD I2C模块的SCL-> ESP32的GPIO22(默认I2C SCL)。
    • LCD I2C模块的SDA-> ESP32的GPIO21(默认I2C SDA)。
    • VCC-> ESP32的3.3V5V(视模块支持电压而定)。
    • GND->GND
  4. 按钮连接

    • 按钮1一端接GND,另一端接GPIO32,并在GPIO323.3V之间连接一个10kΩ上拉电阻。
    • 按钮2同理,连接至GPIO33
  5. 电源:确保所有设备的GND最终都连接到一起。为ESP32提供稳定的5V USB供电。

4. 软件实现与核心代码剖析

4.1 开发环境与库准备

我们使用Arduino IDE进行开发,因为它对ESP32支持友好,库管理方便。

  1. 安装ESP32开发板支持:在Arduino IDE的“开发板管理器”中添加ESP32的板支持网址并安装。
  2. 安装必要库
    • WiFi:ESP32 Arduino核心自带。
    • LCD:如果使用I2C LCD,安装LiquidCrystal_I2C库。
    • 串口缓冲区管理:可以使用CircularBuffer库,或者自己实现简单的字符数组缓冲区。

4.2 核心代码结构解析

以下是程序骨架的关键部分,省略了细节初始化和错误处理以突出重点。

#include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <Wire.h> #include <LiquidCrystal_I2C.h> // 硬件引脚定义 #define AIS_RX_PIN 16 // UART2 RX #define GPS_RX_PIN 4 // 可配置为软串口或硬件UART1的RX #define BUTTON1_PIN 32 #define BUTTON2_PIN 33 // 网络设置 const char* ssid = "Boat_AIS_Converter"; const char* password = "your_password"; WiFiServer tcpServer(10110); // 创建TCP服务器,端口10110 WiFiClient client; // 用于保存连接的客户端 // LCD对象 (假设I2C地址为0x27) LiquidCrystal_I2C lcd(0x27, 40, 4); // 串口缓冲区 HardwareSerial SerialAIS(2); // 使用UART2 HardwareSerial SerialGPS(1); // 使用UART1 String aisBuffer = ""; String gpsBuffer = ""; void setup() { Serial.begin(115200); // 用于调试 // 1. 初始化串口 SerialAIS.begin(38400, SERIAL_8N1, AIS_RX_PIN, -1); // 只接收,不发送 SerialGPS.begin(4800, SERIAL_8N1, GPS_RX_PIN, -1); // 2. 初始化WiFi为AP模式 WiFi.softAP(ssid, password); Serial.print("AP IP address: "); Serial.println(WiFi.softAPIP()); // 3. 启动TCP服务器 tcpServer.begin(); Serial.println("TCP Server started on port 10110"); // 4. 初始化LCD lcd.init(); lcd.backlight(); lcd.setCursor(0,0); lcd.print("AIS-WiFi Conv Ready"); // 5. 初始化按钮引脚为上拉输入模式 pinMode(BUTTON1_PIN, INPUT_PULLUP); pinMode(BUTTON2_PIN, INPUT_PULLUP); } void loop() { // 检查是否有新的网络客户端连接 if (!client || !client.connected()) { client = tcpServer.available(); if (client) { Serial.println("New client connected!"); lcd.setCursor(0,1); lcd.print("Client Connected "); } } // 处理AIS串口数据 while (SerialAIS.available()) { char c = SerialAIS.read(); if (c == '\n') { // NMEA语句以换行符结束 if (client && client.connected()) { client.print(aisBuffer); // 发送完整语句 client.print("\r\n"); // 补上终止符 } // 可选:更新LCD显示AIS相关摘要信息 updateLCDWithAIS(aisBuffer); aisBuffer = ""; // 清空缓冲区 } else if (c != '\r') { // 忽略回车符 aisBuffer += c; } } // 处理GPS串口数据 (逻辑与AIS相同) while (SerialGPS.available()) { char c = SerialGPS.read(); if (c == '\n') { if (client && client.connected()) { client.print(gpsBuffer); client.print("\r\n"); } updateLCDWithGPS(gpsBuffer); gpsBuffer = ""; } else if (c != '\r') { gpsBuffer += c; } } // 检查按钮状态,用于切换LCD显示模式 static int displayMode = 0; if (digitalRead(BUTTON1_PIN) == LOW) { // 按钮按下为低电平 delay(50); // 简单消抖 if (digitalRead(BUTTON1_PIN) == LOW) { displayMode = (displayMode + 1) % 3; // 假设有3种显示模式 changeDisplayMode(displayMode); while(digitalRead(BUTTON1_PIN) == LOW); // 等待释放 } } // 类似处理BUTTON2_PIN... } // 辅助函数:从AIS NMEA语句中解析并更新LCD(简化版) void updateLCDWithAIS(String &data) { if (data.startsWith("!AIVDM")) { // 这里应放置复杂的AIVDM报文解析代码 // 简化为显示最近一条消息的时间或MMSI(船舶识别码)后几位 lcd.setCursor(0, 2); lcd.print("AIS: "); lcd.print(data.substring(data.length()-10, data.length()-5)); // 示例显示 } } void updateLCDWithGPS(String &data) { if (data.startsWith("$GPRMC")) { // 解析GPRMC语句,获取时间、经纬度、速度 // 简化为显示时间 lcd.setCursor(0, 3); lcd.print("GPS: "); lcd.print(data.substring(7, 13)); // 显示UTC时间 } } void changeDisplayMode(int mode) { lcd.clear(); switch(mode) { case 0: lcd.print("Mode0: AIS Msg"); break; case 1: lcd.print("Mode1: GPS Info"); break; case 2: lcd.print("Mode2: Sys Stat"); break; } }

这段代码提供了一个最基础的框架。它创建了一个WiFi热点,将两个串口接收到的完整NMEA语句转发给连接的TCP客户端,并初步实现了LCD显示和按钮切换功能。

5. 调试、优化与功能增强

5.1 上电调试与数据验证

  1. 分步调试:不要一次性连接所有设备。首先,只给ESP32上电,通过串口监视器查看它能否正确启动AP,并打印出IP地址。
  2. 网络测试:用手机或电脑连接“Boat_AIS_Converter”这个WiFi,尝试用网络调试工具(如TCP Client功能)连接ESP32的IP地址和10110端口。此时因为串口没数据,工具应该只是保持连接。
  3. 串口数据注入:暂时不接AIS/GPS设备。可以用一个USB转TTL模块(设置为3.3V!)模拟发送NMEA数据到ESP32的GPIO16。发送一条$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A\r\n。观察网络调试工具是否收到了完全相同的数据,同时观察LCD显示是否更新。
  4. 连接真实设备:在确认模拟数据能正确转发后,断开模拟器,连接真实的AIS接收器和GPS。观察数据流是否稳定。特别注意电平转换模块是否发热,ESP32是否工作正常。

5.2 性能优化与稳定性提升

基础版本能工作,但在真实船舶环境中可能面临挑战,需要进行优化:

  1. 双缓冲区与流控:当前使用String对象在中断环境(Serial.available()循环)中拼接数据,在高速数据流下可能导致内存碎片或响应不及时。更专业的做法是使用环形缓冲区(Ring Buffer)。为每个串口设置一个字符环形缓冲区,串口中断只负责将数据填入缓冲区。主循环loop()中定期检查并处理缓冲区中的数据,按行提取和发送。这能有效应对数据突发。

  2. WiFi稳定性与重连机制:当前代码是AP模式。如果希望设备连接船上的现有WiFi路由器(STA模式),必须增加WiFi断开重连的逻辑。可以使用WiFi.setAutoReconnect(true)WiFi.onEvent来监听WiFi事件,实现稳健的重连。

  3. 多客户端与数据广播:当前代码只支持一个TCP客户端。iSailor连接后,其他设备(如另一部手机)就无法获取数据了。可以修改为维护一个客户端链表,实现多客户端支持。或者,更简单高效的方式是使用UDP广播。将NMEA数据以UDP报文形式发送到广播地址(如255.255.255.255:10110),局域网内所有设备都能接收。这避免了连接管理的麻烦,但需要确认iSailor是否支持UDP模式。

  4. NMEA语句过滤与校验:可以增加对NMEA语句的校验和验证,丢弃无效数据。还可以根据需求过滤语句,例如只转发!AIVDM$GPRMC,忽略其他不必要的数据,减轻网络负担。

  5. LCD显示信息优化:当前的updateLCDWithAIS函数只是简单截取字符串。真正的价值在于解析AIVDM报文。你可以集成一个轻量级的AIS解码库(如libais的C++移植简化版),在LCD上显示附近最近一艘船的距离、方位和CPA(最近会遇点)/TCPA(最近会遇时间)等关键安全信息。

5.3 常见问题与排查实录

在实际制作和测试中,我遇到了以下几个典型问题及解决方法:

  1. 问题:ESP32接收到的数据全是乱码。

    • 排查:首先检查波特率是否设置正确(38400和4800)。最常见的是电平问题。用万用表测量AIS接收器输出引脚在空闲和发送时的电压。如果是5V,必须加电平转换。如果是RS-422差分信号,则需要MAX485芯片。
    • 解决:确认使用正确的电平转换电路,并确保所有设备的GND共地。
  2. 问题:iSailor App连接上WiFi后,显示“正在接收数据”但看不到任何船舶。

    • 排查:使用网络调试工具(如“TCP Client & Server”工具)连接转换器的10110端口,查看是否有原始NMEA数据流出来。如果没有,回到上一步检查串口。如果有数据,检查数据是否包含有效的!AIVDM语句。可能是AIS接收器天线问题或所在位置无AIS信号。
    • 解决:确保网络调试工具能收到!AIVDM开头的句子。在iSailor的设置中,确认输入源是“TCP”或“WiFi”,并正确输入了ESP32的IP地址和端口10110。
  3. 问题:数据转发有延迟,或者偶尔丢包。

    • 排查:可能是主循环处理太慢,或者String操作导致内存分配延迟。同时,检查WiFi信号强度。
    • 解决:实施环形缓冲区优化。将LCD刷新和按钮扫描等非实时任务放入低优先级循环或使用FreeRTOS任务管理。确保ESP32放置在信号良好的位置。
  4. 问题:同时连接AIS和GPS时,系统有时会重启。

    • 排查:很可能是电源问题。ESP32在射频(WiFi)工作时峰值电流可能达到500mA,如果电源(尤其是USB线)供电不足,会导致电压跌落而重启。
    • 解决:使用高质量的USB线和5V/2A以上的电源适配器。在船上应用时,最好使用专业的5V稳压模块为ESP32供电,并增加大容量滤波电容(如1000uF)以应对发动机启动等引起的电压波动。

这个项目从构思到实现,是一个典型的硬件集成、嵌入式编程和网络应用相结合的过程。它完美地解决了特定场景下的设备互联问题。完成后的转换器体积小巧,可以封装在防水盒内,固定在驾驶台附近。通过这个自制设备,我不仅让iPad上的导航软件发挥了全部潜力,更重要的是,在整个过程中对串口通信、网络协议和嵌入式系统有了更深入的理解。这种“创造工具来解决自己问题”的成就感,是单纯购买成品设备无法比拟的。如果你也面临类似的设备数据孤岛问题,不妨以这个项目为参考,动手搭建属于你自己的数据桥梁。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询