
简介POCSAG是无线电寻呼机广泛采用的数字编码标准用于将文本消息以低带宽方式广播到多个终端。压缩包提供了围绕POCSAG解码与验证的整套工程文件适合业余无线电爱好者、嵌入式开发者和通信协议学习者研究。包内共29个文件总体积约60KB包括C源代码.c、头文件.h、十六进制固件镜像.hex、工程配置.prj/.mak、编译列表.lst/.lis及调试过程文件.dbg/.cof/.bak等既可直接阅读协议实现也能配合烧录实验板观察真实解码结果。POCSAG网上专门资料本就稀缺而5位/7位字符映射、同步识别和差错控制是理解该协议的主要难点通过源码和工程文件的相互对照可快速掌握从码元解调到字符还原的完整链路。目前已有196人浏览学习对想入门寻呼机协议、低功耗数传或复古硬件通信的读者是一份值得参考的实例。1. 一份藏在 rar 里的 POCSAG 解码器能教你处理位级信号POCSAG 是一种比 LoRa 老得多的数字寻呼编码但今天医院、工地和应急系统里1200bps 的寻呼信号仍然在电波里跑。压缩包里那堆 pos.c、pos.s、uart1.c 不是桌面脚本而是 AVR 单片机工程这意味着你得在比特级别处理同步码、BCH 校验和位序而不是像用 base64 解码工具那样直接查表。不少人搜 POCSAG 解码最后找到的多是 GNU Radio 模块或论文公式这种带 .prj、.cof、.hex 的老式 AVR Studio 工程反而少见。它适合三类人玩 AVR/STM32 的嵌入式工程师、做低成本寻呼接收验证的无线电爱好者以及想从海明码一路追到真实通信协议的学生。2. POCSAG 帧结构前导码、同步码字与 5/7 位字符映射2.1 前导码和 512/1200/2400 bps 的选择POCSAG 发射前先送至少 576 位交替的 101010… 前导码接收机用它的跳变沿恢复位时钟。没有这个前导码后续的同步码字根本对不准。前导码之后才是第一批数据也就是说解码器要主动跟踪“曾经看到过 101010 序列现在准备进入同步字搜索”的状态而不是一上电就满世界找数据。速率直接决定定时器初值。三种速率使用的帧结构完全一致只是位周期不同接收逻辑里唯一要改的是每 bit 对应的定时器计数。速率 (bps)位周期 (us)32 位码字时长 (ms)典型用途5121953.12562.5长距离寻呼1200833.33326.667城市寻呼主力2400416.66713.333高速数据在 AVR 中通常用定时器溢出中断控制采样下面的宏按 7.3728MHz 晶振和 1200bps 计算#define BIT_TICKS_1200 6144 /* 7.3728MHz / 1200 */ #define SAMPLE_TICKS (BIT_TICKS_1200 / 2)BIT_TICKS_1200是一位周期内的主时钟计数SAMPLE_TICKS是位中间采样点。如果换成 8MHz 晶振这两个值都要重新算只改宏不改预分频是常见翻车原因。2.2 同步码字与批次边界前导码结束后是一个固定同步码字 0x7CD215D8先发最高位。同步码字后面是连续的数据码字按 32 位一组切分。每 33 个码字构成一个批次1 个同步码字和 16 个帧每个帧两个码字。帧号在 0 到 15 之间循环接收端必须维护这个计数器因为它参与地址的低 3 位。码字字段位长内容bit3110地址/空闲1消息地址码字 bit30..1318地址高 18 位地址码字 bit12..112功能位决定消息格式消息码字 bit30..1120消息数据bit10..110BCH(31,21) 校验bit01偶校验地址实际有 21 位低 3 位由帧号补充所以同一个 18 位地址高段出现在不同帧时会变成不同寻呼机。解析地址时若只取码字内的高 18 位而忽略帧号你会看到同一批呼叫被拆到 8 个不同地址上。你以前玩 51 单片机红外遥控解码仿真图时信号的 0/1 是靠脉宽比例判断的POCSAG 不一样它每一位都是等宽 NRZ只要边沿抖动超过半个位周期就丢码。这决定了接收端尽量用输入捕获而不是普通 GPIO 轮询。2.3 5 位数字模式和 7 位文本模式的取舍消息码字里到底装 5 位字符还是 7 位字符由功能位和上层寻呼协议共同决定。数字寻呼为了在低速下塞更多字符常把 0-9、空格、括号等映射成 5 位码一个 20 位消息字段正好放 4 个字符。字母数字寻呼则用 7 位编码需要把多个消息码字的数据区像流水一样拼起来再按 7 位滑窗切字符。5 位码字符5 位码字符0-90-914-10空格15)11U16(12[17/13]18备用解码时最忌讳从消息码字边界直接按字节取字符。5 位模式虽然每个码字刚好 4 个字符但跨码字拼接发生时7 位模式需要维护一个 bit 游标否则第二段消息会整体错位。我一般在代码里先把消息码字的数据位压进一个bitbuf[]最后统一按 5 或 7 位截取绝不中途转换。3. 从位流到消息同步捕获、码字解析与 Python 验证3.1 同步捕获滑窗找 0x7CD215D8真正的解码不依赖“前导码之后一定跟着同步字”这一假设而是持续用一个 32 位移位寄存器接收位。每收到一位寄存器左移一次并把新位放在最低位然后与 0x7CD215D8 比较。匹配成功时当前批次边界就对上了之后每 32 位切一个码字。uint32_t shifter 0; for (;;) { int b read_bit_from_radio(); shifter ((shifter 1) | (b 1)) 0xFFFFFFFF; if (shifter 0x7CD215D8) { /* 进入码字同步状态 */ break; } }read_bit_from_radio()由定时器采样实现返回值必须是严格的 0 或 1。把shifter左移、新位放 LSB对应 POCSAG 最高位先行的约定如果反过来右移同步字永远匹配不上。同步逻辑建议用状态机管理状态进入条件要做的事搜索前导码连续 16 位呈 1010 交替确认速率准备采样搜索同步字32 位值等于 0x7CD215D8清零帧号接收码字同步后每 32 位一组交给 BCH 校验和消息解析3.2 码字拆分和消息提取的 Python 原型拿到一段 POCSAG 基带位流后先用 Python 验证逻辑远比在单片机上下断点快。下面函数接收已经做过位同步的 0/1 列表从同步码字之后开始解析地址和消息。def load_32(bits, i): v 0 for j in range(i, i 32): v (v 1) | bits[j] return v def parse_stream(bits, sync_end): addr None func 0 msg_bits [] i sync_end frame 0 while i 32 len(bits): codeword load_32(bits, i) i 32 if codeword 0x80000000: # 消息码字取20位数据按发送顺序放入msg_bits data (codeword 11) 0xFFFFF for b in range(19, -1, -1): msg_bits.append((data b) 1) else: if addr is not None and len(msg_bits) 7: text for k in range(0, len(msg_bits) - 6, 7): v 0 for bit in msg_bits[k:k 7]: v (v 1) | bit text chr(v) print(faddr{addr} func{func} text{text!r}) addr_high (codeword 13) 0x3FFFF func (codeword 11) 0x3 addr addr_high | (frame 0x7) msg_bits [] frame (frame 1) % 16load_32按最高位优先装配 32 位整数。消息码字里的(codeword 11) 0xFFFFF恰好把低 11 位校验和偶校验扔掉留下 20 位消息数据。地址码字里的功能位是(codeword 11) 0x3地址高 18 位要用(codeword 13) 0x3FFFF。这段代码没有做 BCH 纠错因此信号稍差时会出现打印乱码。3.3 为什么 5 位模式先拼比特流再查表如果是数字寻呼消息码字的 20 位数据同样要先顺序压进一个位数组再每 5 位查一次字符表。因为 5 位模式每个消息码字正好 4 个字符看起来可以从码字边界直接切但地址与消息之间可能夹杂空闲码字只要有一个空闲码字没被正确处理后续位游标就乱了。统一拼位数组后空闲码字只是不产生消息数据不会让解析错位。def decode_numeric(msg_bits, table): out [] for i in range(0, len(msg_bits) - 4, 5): v 0 for bit in msg_bits[i:i 5]: v (v 1) | bit out.append(table[v] if v len(table) else ?) return .join(out)不同寻呼台对空闲码字的处置略有差异有的会在消息结束后连发两三个空闲码字有的只发一个。解码器不能一看到地址码字就重置消息缓冲要同时判断后续码字是否为空闲这些边界条件在 Python 原型中跑通后再进 C 能省很多时间。4. 在 AVR 工程里落地文件清单、串口输出与接收链路4.1 谁才是主程序从 pos.c 到 pos.mak压缩包里的文件覆盖了源码、汇编和编译产物。pos.c大概率是 POCSAG 解码主逻辑pos.s很可能是汇编级位流处理uart1.c负责串口发送pos.prj是 AVR Studio 工程pos.mak是 makefilepos.hex是最终烧录文件pos.lst是汇编清单。iom128v.h和iot2313v.h分别对应 ATmega128 和 AT90S2313说明这套代码在两种芯片上移植过。文件类型阅读顺序pos.cC1. 先找同步字常量pos.s汇编2. 看外部中断入口uart1.cC3. 串口输出格式pos.prj工程文件4. 确认芯片型号pos.hex烧录文件5. 最后与编译产物比对拿到这类老工程不要急着烧录。先打开pos.lst看开头的处理器型号与中断向量表布局。AVR Studio 3/4 时代工程经常把 AT90S2313 的头文件和 ATmega128 的工程混在一起直接烧pos.hex基本都会因中断向量错位而乱码。4.2 串口输出与调试信息的最小实现解码结果最终要从串口吐出来。下面是一段兼容 ATmega128 的发送函数寄存器名要随编译器版本调整。void uart_putc(unsigned char c) { while (!(UCSR0A (1 UDRE0))); UDR0 c; } void uart_puts(const char *s) { while (*s) uart_putc(*s); }UCSR0A的UDRE0位表示发送缓冲空不等待直接写UDR0前一个字符会被覆盖。如果编译时报UCSR0A未定义把它改成旧的UCSRA即可。这段代码只负责透传真正的消息格式化应当在内存里完成不要在中断里调用uart_puts否则长消息会让解码中断超时。调试时我会在每条消息前输出ADR... FUN...消息内容统一按十六进制再跟一份 ASCII。这样即使文本解码有误也能从 HEX 里看出位序问题。4.3 接收模块接线与位采样参数POCSAG 接收模块一般输出解调后的 NRZ 数据脚。数据脚接到 AVR 的外部中断引脚中断里记录跳变沿然后用定时器在每位的中间点采样。这个方法和你之前看 pt2272 芯片解码原理视频里的脉宽判决不同pt2272 靠高电平时长区分数据POCSAG 则必须在固定的位周期中心采样提前或落后都会误码。参数值1200bps位周期833.333 us晶振7.3728 MHz每 bit 计数6144中间采样点416 us 附近真正开始调板子前先用信号发生器或另一块板子把已知位流输入数据脚。若串口完全无输出优先看数据脚空闲电平是否合适如果输出乱码但能看到地址再把示波器探头放在数据脚上检查边沿是否抖动超过 20%。这是接收链路最常见的两个问题和 POCSAG 本身无关。4.4 动手前先做的三件事第一备份原始 hex防止刷坏后找不到原版。第二用文本编辑器打开pos.prj确认芯片型号和时钟频率直接改.mak里的MCU_TARGET也可以。第三不要动定时器初值。位采样相位一旦偏移即使同步码能抓到后续 1200 位里也会慢慢错开。想调速率就先看uart1_init里的分频数再回到pos.c的计算宏。5. BCH 纠错与解码器验证从 Hamming 到 POCSAG 的最后一步5.1 10 位校验的 BCH 生成多项式本科做海明校验编码解码实验时海明码的监督位很少POCSAG 把校验位扩到 10 位构成 BCH(31,21) 码能纠正 1 位错误。每个码字前 21 位是信息区后 10 位是校验区最后 1 位是偶校验。接收端把前 31 位对生成多项式做模 2 除法余数非零即出现错误。下面给出 C 实现。#define BCH_POLY 0x5B9 /* POCSAG 源码里常见的生成多项式 */ uint16_t bch_remainder(uint32_t code) { code 0x7FFFFFFF; for (int i 30; i 10; i--) { if (code (1u i)) { code ^ (BCH_POLY (i - 10)); } } return code 0x3FF; }code 0x7FFFFFFF把偶校验位隔开循环从最高位往下逐位做模 2 长除法。返回值是 10 位校验余数对接收码字调用余数全 0 表示通过 BCH 校验否则与本地校正子表比较可以定位出错位。在 AVR 上逐位循环开销较高pos.c里如果已经有查表逻辑尽量沿用它的表。5.2 用人为错误验证解码器验证解码器是否真的在纠错不能只看误码率。我一般把一段已知消息的位流抓下来先跑一遍确认地址和文本都对然后把某个消息码字的第 15 位取反再跑。如果输出不变说明纠错路径生效如果输出错字多半是纠错发生在消息解析之后或者校验位位置算错。由此也能看出同步码字是个例外POCSAG 规定同步字固定不做 BCH 纠错。若你的解码器把同步字也送去查表弱信号下会出现大量伪同步。5.3 从 hex 反推一段固件是否包含 POCSAG 逻辑拿到只有 hex 没有源码的固件想确认它是不是在解 POCSAG最直接的方法是反汇编后搜索同步码字常量。用avr-objdump -D -m avr pos.hex转出汇编然后在输出里找0x7CD215D8的加载指令。能找到这个常量说明固件大概率实现同步字匹配还能找到 BCH 查表地址的话基本就能确认核心逻辑完整。这个搜索方法对pos.hex和000.hex都适用。本文还有配套的精品资源点击获取