
简介这是一份围绕AIS船舶自动识别系统驱动、解码与解析的教程资源包适合海事信息化开发者、通信专业学生以及需要对接船位数据的软件工程师。资源从AIS的VHF无线传输协议入手覆盖ITU-R M.1371标准下的报文结构、二进制解码、错误检测与经纬度坐标转换等关键环节帮助读者理解从射频信号到人可读船位信息的完整链路。压缩包体积仅5KB共包含3个文件主要为2个txt说明/数据文件和1个cpp示例解码源程序txt文件可用于梳理协议字段或存放样例报文cpp文件则演示了解析函数的关键实现。已有1029人学习下载。通过这份紧凑示例读者可以快速掌握AIS解码程序的基本框架了解驱动层获取原始数据后如何提取MMSI、船名、航速等字段并在实际项目中参照该思路完成数据清洗、格式转换与简单报警逻辑。 如果你最近在折腾船舶通信、岸基监控或者AIS相关的硬件八成会遇到一整套看似简单但实际有点绕的链路接收机拿到VHF信号通过串口送到电脑然后我们要把里面那堆像乱码一样的字符还原成船名、经纬度、航向。这套链路里驱动、解码、解析三步每一步都有不少新手容易卡住的细节。这篇文章我就把这整条流程掰开揉碎讲一遍从USB转串口驱动一直到NMEA报文解析尽量把我踩过的坑也都交代清楚。1. 先搞清楚AIS这条链路到底是什么1.1 从一个盒子到一串字符AIS数据接收的完整路径AISAutomatic Identification System船舶自动识别系统工作在VHF频段两个专用信道分别是161.975MHzAIS 1和162.025MHzAIS 2。船上的AIS设备周期性地在TDMA时隙里广播本船的MMSI、船名、位置、航速、航向等信息。对岸基或者做船舶监控的人来说最常见的工作就是拿一台AIS接收机用一根VHF天线收信号然后通过RS-232或者USB口把数据送进电脑。接收机输出的一般是NMEA 0183格式的语句其中以!AIVDM或!AIVDO开头的就是AIS消息。你拿串口助手一看会发现这些语句中间部分是一长串看起来毫无规律的字符类似1:oK这种。很多人第一次看到会以为这是加密了其实不是这串字符只是AIS物理层二进制数据的一种“6-bit ASCII”编码结果本质上就是BASE64那套思路。后面要讲的解码就是把这段字符重新还原成二进制比特流。1.2 接收机输出的是什么东西AIS(A)和AIS(B)的差异先花点时间说清楚AIS(A)和AIS(B)因为这个直接决定你后面解析哪些消息类型。A类设备是SOLAS公约强制要求安装的功率大12.5W报文发射频繁动态信息位置、航速在航行状态下每2到10秒就发一次消息类型主要是1、2、3。B类设备是自愿安装的功率小2W报文频率低很多动态消息通常30秒到3分钟发一次走的是18、19、24类型。另外B类设备分CS模式和SO模式消息类型和发射时机也不一样。所以你在接收端随便抓数据时会明显感觉到A类船的更新频率比B类船高很多。做岸基显示时如果你发现某条船“消失”很久不一定是设备坏了很可能那只是一条B类船本身报告间隔就长。这个背景知识对后面解析消息类型很有用——你知道该去解析哪一种ID的消息体而不是拿到什么都按类型1来解。2. 驱动安装数据进电脑前的最后一道关卡2.1 CP2102、CH340、FT232这些芯片驱动怎么选AIS接收机或者开发板给到电脑的接口最常见的是USB转串口。而USB转串口芯片型号又五花八门最常见的有CP2102Silicon Labs、CH340沁恒、FT232FTDI、CH341、FT231X这几种。驱动的选择和安装跟芯片型号是强绑定的不能混着用。我在Windows下安装时的经验是这样CP2102装完驱动后设备管理器里会出现“Silicon Labs CP210x USB to UART Bridge”这个设备COM口号自动分配。CH340则是出现“USB-SERIAL CH340”需要去WCH官网下CH341SER驱动。FT232要用FTDI的VCP驱动。比较坑的一点是部分CP2102模块用的是国产兼容芯片Windows自动更新装上的驱动可能不稳定解决方法是去Silicon Labs官网下最新的CP210x Universal Windows Driver然后手动更新驱动路径。CH340在Win10/Win11上一般插上就能识别但如果你在一些精简版系统上装不上记得先禁用驱动签名强制再装旧版驱动。对于FT231X它和FT232的驱动是同一套FTDI VCP驱动。装完以后如果设备管理器里出现感叹号大概率是驱动没签上名或者芯片本身是兼容片换个驱动版本通常能解决。2.2 Linux下别踩udev权限的坑如果是在Linux下做开发情况会简单一些但权限问题经常会卡你半天。CP2102对应内核模块cp210xCH340对应ch341FT232对应ftdi_sio这些模块在主流内核里基本都是自带的。插上USB后用lsusb能看到设备用dmesg | tail能看到类似usb 1-1.2: cp210x converter now attached to ttyUSB0的日志这时设备节点是/dev/ttyUSB0。但是普通用户默认没有权限打开这个串口。你需要把用户加入dialout组sudo usermod -a -G dialout $USER注销重新登录才能生效。如果你用的是某些嵌入式板子可能还需要手动建udev规则用udevadm info -a -n /dev/ttyUSB0查设备属性然后写一个/etc/udev/rules.d/99-ais.rules把设备名固定成/dev/ais方便后面脚本引用。这一步不是必须的但项目做大了以后多个设备插拔顺序变化很容易导致ttyUSB0和ttyUSB1互换固定设备名能省掉很多调试麻烦。这里还要提醒一句AIS接收机的串口波特率不一定是9600。我自己遇到过不少模块默认是38400还有一些老设备是4800。如果驱动装好了串口助手打开却一片空白先别急着怀疑硬件把波特率挨个扫一遍再说。这个排查顺序很重要。3. 解码把“6-bit压在ASCII字符里”还原成二进制3.1 为什么AIS消息体看起来像乱码AIS二进制数据在NMEA句子里传输时被编码成了一种类似BASE64的形式官方叫法叫“6-bit ASCII”或者“bit-oriented encoding”。它和Base64的核心思路完全一致用可打印ASCII字符来承载二进制数据让数据可以在串口这种文本通道里安全传输。AIS的具体做法是把二进制比特流每6位切一组得到一个0到63的数值然后加上48即字符0的ASCII码变成ASCII字符。值0到47直接映射到ASCII字符的0到_值48到63则映射到\到w范围所以AIS消息体里的有效字符主要集中在0到w之间。这就是为什么你看到的就是一串可打印字符而不是乱码。那为什么还要费劲解释“0x60以上要减8”这种细节因为在解码节目里从ASCII码还原成6位二进制值的时候对于那些落在0x60到0x77范围的字符ASCII码96到119减48得到的是48到71而实际应该对应0到15的偏差不对准确说这些字符对应的6位值应该是48到63所以减完48之后还要再减8。很多人第一次写解码脚本时没处理这个细节导致解出来的比特流错位后面所有字段全错。3.2 手写一个6-bit解码函数解码逻辑用Python写非常简单。先看字符如果是!开头说明是NMEA的AIVDM语句取第6个逗号后面的消息体字段。对每个字符做如下处理def ais_6bit_to_bits(s): bits [] for ch in s: c ord(ch) if 0x30 c 0x5f: v c - 48 elif 0x60 c 0x77: v c - 48 - 8 else: continue for i in range(5, -1, -1): bits.append((v i) 1) return bits这段代码把每个字符还原成6个比特按顺序拼成一个大的比特列表。注意AIS消息可能跨多个NMEA句子传输比如消息类型5经常拆成两句此时需要先把句子按序号拼接再统一解码。如果直接对单句解码解出来的只有一半数据字段必然对不上。解码得到的是原始二进制消息后面的解析才真正开始。很多人在这个环节容易犯的错是用普通的ASCII解码直接把消息体打印出来看结果当然是一堆乱七八糟的字符。这里要记住一个原则AIS消息体是二进制数据经过编码后的文本必须解码成比特流后按字段位宽切分而不是直接转字符串。4. 解析把比特流翻译成船名、经纬度和航速4.1 先学会读NMEA句子的结构完整的AIVDM句子长这样!AIVDM,1,1,,A,15M67FC000G?ufbEFepT3n00Sa,0*4C按逗号拆开字段含义是字段位置含义0句子标签!AIVDMDM代表来自其他船舶的AIS数据1句子总数这个消息拆成几句2当前句序号3顺序消息ID多句消息用于拼接范围0-94接收信道A或B5AIS消息体6-bit ASCII编码的二进制数据6填充位数为了使总位数凑成6的倍数而补的0个数7XOR校验和读NMEA句子时先看句子总数是不是大于1。如果是必须等同一个顺序消息ID下的所有句子都到齐了再拼接消息体。很多人解析类型5消息失败就是因为没处理多句拼接。其中填充位是最后一句话里的拼接后的消息体最后几位可能含无效补零解码时要注意截断。4.2 消息类型1和5的解析实例解析的核心步骤是把消息体6-bit解码后的比特流按表格里的位宽逐一取出字段。下面以消息类型1Class A位置报告为例def parse_msg1(bits): offset 0 def take(n): nonlocal offset val 0 for i in range(n): val (val 1) | bits[offset i] offset n return val msg_type take(6) # 消息类型固定为1/2/3 repeat take(2) # 重复指示器 mmsi take(30) # MMSI nav_status take(4) # 导航状态 rot take(8) # 转向率 sog take(10) # 对地航速单位0.1节 accuracy take(1) # 位置精度 lon take(28) # 经度1/10000分 lat take(27) # 纬度1/10000分 cog take(12) # 对地航向0.1度 heading take(9) # 真航向1度 timestamp take(6) # UTC秒 return { mmsi: mmsi, nav_status: nav_status, sog: sog / 10.0, lon: lon / 600000.0, lat: lat / 600000.0, cog: cog / 10.0, heading: heading, timestamp: timestamp, }有两个最关键的转换点必须注意。第一经度和纬度是二进制补码如果取出来的28位或27位数的最高位是1说明是西经或南纬需要减掉2^28或2^27换成负数。第二经度和纬度的原始单位是1/10000角分换算成度需要除以600000因为1度等于60角分1角分等于10000个单位600000正好是60乘以10000。消息类型5是静态和航次数据它包含MMSI、IMO号、呼号、船名、船舶类型、船舶尺寸、吃水、目的地。解析思路完全一样就是位宽不同。比较特殊的是船名、呼号这些文本字段用的还是6-bit ASCII编码需要再经过一张表映射回可读字符。6-bit ASCII表里值1对应A2对应B26对应Z值0是值32是空格小写字母在值48到63区间。解码文本字段时逐字符从比特流中取6位查表拼成字符串。这里最容易踩坑的是直接用chr输出会得到一堆错乱符号必须按AIS自己的字符表映射。我建议把消息类型判断放在解析函数入口不同类型走不同分支别写一个大函数硬解所有消息。AIS的消息类型有20多种但日常用得到的就是类型1/2/3、5、18、19、24、27其他地方简单过滤跳过就好。5. 收不到数据、解出乱码、经纬度异常问题排查实录5.1 串口有数据但看不懂先从数据形态判断故障我把实际项目中遇到过的典型问题整理成了一张速查表现象可能原因解决办法串口助手上什么字符都没有波特率不对遍历4800/9600/38400字符全是无意义的乱码波特率不对或串口线接触不良重新接线降低波特率测试有字符但都以$开头没有!AIVDM接的是GPS设备而不是AIS确认设备工作状态!AIVDM有但消息体长度明显不对天线信号太弱或接收机射频前端故障检查天线接口、加装前置滤波器解析出来经纬度都是0位置报告类型不对或未处理二进制补码打印消息类型逐位检查取位偏移船名出现一堆符号没用6-bit ASCII表映射用映射表而不是chr直接输出多句消息只解出半段未做多句拼接用顺序消息ID缓存句子我自己调试时最常用的方法是先把原始NMEA句子直接落盘存成文本日志然后再用离线脚本慢慢解析。这样做的好处是当场发现问题时不用急着追硬件先把数据留档后面写解析脚本的时候可以直接拿真实数据做回归测试。别一上来就搞实时流解析出问题很难定位。5.2 Linux下串口权限和工具链的细节Linux下调试AIS很多人会习惯性用minicom或者screen。screen /dev/ttyUSB0 38400打开串口后按CtrlA再按K退出这个组合键对新手不友好不小心就把会话挂了。我推荐用socat或者Python的pyserial做调试前者可以快速把串口数据转发到TCP端口后者适合直接写脚本解析。如果你在Linux下插上CP2102dmesg显示设备已识别但Python打开串口时报权限错误记得先确认用户是否在dialout组里。这个坑我出现过好几次每次换新机器都会忘。还有一点有些USB转串口的板子硬件流控的CTS/RTS引脚悬空AIS接收机输出到串口时如果你不小心在软件里开启了硬件流控数据可能完全进不来。所以我一般建议在打开串口时显式关掉流控只留最基础的读写import serial ser serial.Serial( port/dev/ttyUSB0, baudrate38400, timeout1, rtsctsFalse, dsrdtrFalse, xonxoffFalse )pyserial的参数细节直接影响收数稳定性实测下来关掉所有流控后在各种芯片平台上都稳。如果你用的是CANoe这类总线分析工具做协议解析思路也是一样的输入报文、按位宽拆字段、查映射表只是AIS的物理层换成了VHF无线信道而已。最后再分享一点自己的体会这套东西搞下来最让我印象深刻的不是消息类型1那个比特翻转的补码问题而是很多问题其实出在“太急着跳过细节”。比如6-bit ASCII的编码规则看起来就是加48减48的事但实际处理0x60以上字符时多了个减8经纬度单位换算看起来是除以600000但要先确认取了28位还是27位消息类型5看起来难其实最核心的只是船名字段的字符映射表。每一步单独拎出来都不复杂但串在一起任何一个环节漏了都会让最后结果完全不可信。我个人现在做AIS数据项目习惯先把一套“标准测试报文”存下来。报文的来源、船名、经纬度都是事先知道答案的每次解析代码改动后先用这套测试报文跑一遍确认输出不变才继续加新功能。这个习惯帮我省了无数次“改了一行结果全崩了”的返工时间建议你也试试。本文还有配套的精品资源点击获取