起因

BP 机(寻呼机)虽然早已退网,但它是一个非常好的无线协议实验对象:POCSAG 编码格式公开、调制简单,用一片单片机就能完整走一遍“编码—调制—发射—接收”的全链路。

需要注意的是,现在BP机工作频段已经被国家收回,所以自己使用手台、车台等大功率无线发射器在这个频段发射无线电波是违法的,所以需要进行发射信号的衰减,无线发射功率控制在0.1W之内是没有问题的。

需求

  • 单片机使用STC公司的IAP15W4K58S4,时钟频率为27MHz,上位机通过串口1下发待发内容,串口波特率为115200
  • 支持1200和512比特速率的POCSAG编码,支持正负相位
  • 可以发送数字信息和汉字信息(不支持混输)
  • 能呼响现有的 BP 机(摩托罗拉精英王、大顾问实测成功)

技术实现原理

POCSAG 码流长什么样

POCSAG 是单向寻呼协议,物理层是直接 FSK:0/1 对应两个频点(±4.5kHz 左右的频偏),速率 512/1200/2400 bps。整条码流按这个顺序排:

  1. 前置码:至少 576 bit 的 1010…交替码。接收机靠它的过零间隔建立位同步、让接收电路稳定,所以只能多不能少——代码里 for(i=0;i<18;i++) Send_Num(0xAAAAAAAA);,18 个 32bit 字正好 576 bit;
  2. 同步码字 0x7CD215D8:告诉接收机“正式的批次(batch)从这里开始”;
  3. 批次:一个 batch = 同步码 + 8 帧 × 2 码字,码字 32 bit。发长消息时每到 batch 边界(16 个码字)再插一个同步码,代码里 if(i%16==0) TxBuff[n++]=SYNC_DATA; 干的就是这件事。

寻址与帧:为什么地址码只发在自己的帧里

32 bit 码字分两类,最高位是类型标志:

  • 地址码字(最高位 0):18 bit 地址 + 2 bit 功能位;
  • 消息码字(最高位 1):20 bit 数据,紧跟在属于同一地址的地址码字后面。

POCSAG 最关键的设计:地址的低 3 位不在码字里传,而是隐含在帧号里——地址 mod 8 等于几,BP 机就在每 batch 的第几帧醒来收地址。BP 机平时省电休眠,每个 batch 只醒 1/8 的时间。这带来两个直接后果:

  • 发送端必须把地址码字放进 地址 & 7 对应的帧,其余帧填闲置码 0x7A89C197——GetAddrNumber() 里 Tx_Num=(unsigned char)(add&0x00000007)、然后 TxBuff[i*2]=calc_bch_and_parity(tem) 做的就是这个映射;
  • 2 bit 功能位相当于同一台 BP 机的 4 个“分机号”,各对应一种响声——它是地址的一部分,所以“显示乱码/响声不对”时换个功能位再试。

校验:BCH(31,21) + 偶校验

每个码字 = 21 bit 信息 + 10 bit BCH 校验 + 1 bit 偶校验。BCH(31,21) 最小码距 5,能纠 2 位随机错——寻呼信道是低信噪比的噪声环境,没有这层纠错误码率完全不可用。calc_bch_and_parity() 用多项式除法(对 0xED200000 反复异或)算出余数填进校验位,再数一遍前 31 bit 里 1 的个数、必要时把第 32 bit 置 1 凑成偶数。两道校验合起来,接收端才能放心判定“这真的是发给我的码字吗”。

从比特到无线电:IO 直接驱动对讲机

发射链路极简:单片机 IO(P36)按位节拍翻转电平,送进对讲机的调制输入端,对讲机以 FM 方式把它变成频偏发出去;BP 机解调出 NRZ 码流再解码。几个关键点:

  • 正/负相位:1/0 电平与频偏方向的对应关系在收发两端可能约定相反,所以 Send_Num() 里留了 N/P 分支(if(UartBuff[1]=='N')),“不响先换相位”就是这个开关;
  • 位节拍:512/1200 bps 的位宽由定时器 0 的溢出决定(WaitTF0() 每次等一个位周期)。初值按 27MHz 主频算出,但晶振有公差,位宽误差随码流累积——这就是代码注释“定时器初值,需要微调”的原因;
  • PTT 时序:先拉 PTT、延时 200ms 等对讲机发射链路稳定再送码流;发完延时 600ms 才释放。起振前发的码全白费,提前释放会截掉末尾码字,BP 机校验失败直接整条丢弃。

硬件与连线

对讲机我用的是宝峰UV-5R。单片机的P36接对讲机的压控振荡器输入端,P35接对讲机的PTT控制端,还要和对讲机共地(至于这三个接线端具体接在哪一个位置,网上一搜就有了)。对讲机的发射频率需要调到BP机的工作频率一样。

BP机我用摩托罗拉精英王和大顾问实验成功,如果不响可以试试切换一下相位。如果显示乱码就换一个响声/功能位。

如果就是不响,要知道哪有这么容易响?地址码、BP机工作频率、对讲机频率调到和BP机工作频率一致、对讲机不能有频偏。如果就是不响,要考虑BP机是否被改过频率,很多BP机在使用的时候都是改过频率的,或者就是对讲机有频偏,可以使用SDR(软件无线电)测试对讲机的频率。

关于如何准确获得BP机的工作频率和地址码,跳转这里:

如何获得BP机的地址码和工作频率?

开源!单片机源码和上位机源码都开源!讨厌那种一个单片机加个Si4463模块就卖你几百块的商家,虽然我知道写程序画板子也不容易,但是价格是成本的十倍着实太坑。

代码实现

单片机源码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
#include <stc15.h>
#include <intrins.h>  

#define TX                   P36    //数据输出端
#define PTT                  P35    //PTT控制端
#define HIGH                 1      //高电平
#define LOW                  0      //低电平
#define NILL_DATA            0x7A89C197  //闲置码
#define SYNC_DATA            0x7CD215D8  //同步码
#define DATA_START           12     //串口中数据开始位置
#define TEXT_OR_NUM          11     //串口中文字或数字标志位置
#define SPEED                10     //串口中速率标志位置
#define UARTBUFF_SIZE        400    //串口接收缓冲区
#define TXBUFF_SIZE          200    //TX发送缓冲区
#define LOWSPEED_TIMER_L     0xE7   //512bit/s时定时器初值,需要微调
#define LOWSPEED_TIMER_H     0x31
#define HIGHTSPEED_TIMER_L   0x1A   //1200bit/s时定时器初值,需要微调
#define HIGHTSPEED_TIMER_H   0xA8

unsigned long TxBuff[TXBUFF_SIZE] = {0};//TX发送缓冲区
unsigned char Tx_Num;//地址码发射次序
unsigned char beep;//功能位,1,2,3,4
unsigned char UartBuff[UARTBUFF_SIZE] = "#P12345674HT0001$";//串口缓冲区
unsigned int UartCount = 13;//串口接收计数
unsigned char UartTmp;
bit TM0_FLAG=0;//定时器0标志
bit NewData = 0;//串口是否有新数据收到标志

void UartInit(void);//函数声明
void Delay200ms();
unsigned long calc_bch_and_parity(unsigned long cw_e);
unsigned long calc_addr(unsigned long add,unsigned char fun);
void Timer0Init();
void WaitTF0(void);
void Send_Num(unsigned long s);
void GetAddrNumber();
void calc_NumberData();
void SendTxBuff();
void Empty_Buff();
void calc_TextData();

void UartInit(void)        //115200bps@27.000MHz
{
    SCON = 0x50;        //8位数据,可变波特率
    AUXR |= 0x40;        //定时器1时钟为Fosc,即1T
    AUXR &= 0xFE;        //串口1选择定时器1为波特率发生器
    TMOD &= 0x0F;        //设定定时器1为16位自动重装方式
    TL1 = 0xC5;        //设定定时初值
    TH1 = 0xFF;        //设定定时初值
    ET1 = 0;        //禁止定时器1中断
    TR1 = 1;        //启动定时器1
    ES = 1;    //开启接收中断
    EA = 1;    //开总中断
}

void Timer0Init()
{
    AUXR |= 0x80;        //定时器时钟1T模式
    TMOD &= 0xF0;        //设置定时器模式
    if(UartBuff[SPEED] == 'L')//为L表示512bit/s速率,下同
    {
        TL0 = LOWSPEED_TIMER_L;          //设置定时初值
        TH0 = LOWSPEED_TIMER_H;
    }
    if(UartBuff[SPEED] == 'H')
    {
        TL0 = HIGHTSPEED_TIMER_L;        //设置定时初值
        TH0 = HIGHTSPEED_TIMER_H;
    }
    ET0=1; //定时器0充许中断
    TR0=0;    
    EA=1;  //开总中断
}

void UartSendString(unsigned char* ch, unsigned char n)  //串口发送字符串函数
{
    unsigned char i;
    for(i = 0; i < n; i++)
    {
        SBUF = *ch++;
        while(TI == 0);
        TI = 0;
    }
}

void Delay200ms()        //@27.000MHz
{
    unsigned char i, j, k;

    _nop_();
    _nop_();
    i = 21;
    j = 133;
    k = 210;
    do
    {
        do
        {
            while (--k);
        } while (--j);
    } while (--i);
}

unsigned long calc_bch_and_parity(unsigned long cw_e) //BCH校验和奇偶校验函数
{ 
    unsigned char i;
    unsigned char  parity = 0; //奇偶校验计数    
    unsigned long local_cw; //临时存放数      
    local_cw=cw_e;//保存cw_e参数值
    for(i=1;i<=21; i++,cw_e<<=1)       
        if (cw_e & 0x80000000) cw_e ^= 0xED200000;  
    cw_e=cw_e&0xFFC00000;//保留前10位,BCH校验值共11位,只保留前10位有效数据      
    local_cw |= (cw_e >> 21); //BCH校验数移至第22位到31位,BCH共10位,并和原始数据相加
    cw_e=local_cw;     
    for(i=0; i<31; i++, cw_e<<=1) if(cw_e&0x80000000) parity++;        
    if(parity%2) local_cw+=1;//从1至31位判断为奇数则后面加1补充为偶数
    return local_cw;     
}  

unsigned long calc_addr(unsigned long add,unsigned char fun) //地址转换,第1参数为地址,第2参数为功能
{
    unsigned long adr;
    unsigned long tem;    
    Tx_Num=(unsigned char)(add&0x00000007);//获取地址发射的帧位次,111位第7帧,后3位地址数据隐藏不发送,接收按帧位还原
    adr=0x00;
    adr=add&0xFFFFFFF8;    //去掉地址码后3位
    adr=adr<<10;  //地址左移10位
    tem=0x00;
    tem=fun;  //功能位
    tem=tem<<11;//功能位左移11位,功能位为00 01 10 11四种状态,代表4个地址码
    adr=adr|tem; //地址码和功能位合成地址码;
    return adr;
}

void WaitTF0(void)
{
    while(!TM0_FLAG);//等待定时器溢出
    TM0_FLAG=0;//清标志位
}

void Send_Num(unsigned long s)//发送数据
{
   unsigned char n; 
   for (n=0;n<32;n++)
    {  
      if(s&0x80000000)
        {
            if(UartBuff[1] == 'N')//判断正负相位,下同
                TX = LOW;
            else
                TX= HIGH;
        }
        else
        {
            if(UartBuff[1] == 'N')
                TX = HIGH;
            else
                TX=LOW;
        }
      WaitTF0();//等待延时结束 0.833ms                    
      s<<=1;
    }   
}

void Empty_Buff()//清空缓冲区
{
    unsigned int i;
    for(i = TEXT_OR_NUM; i < UARTBUFF_SIZE; i++)
    {
        UartBuff[i] = 0x00000000;
    }
    for(i = TEXT_OR_NUM; i < TXBUFF_SIZE; i++)
    {
        TxBuff[i] = 0x00000000;
    }
}

void GetAddrNumber()
{
    unsigned char i;
    unsigned long tem;
    unsigned long addr_tmp;

    addr_tmp = (UartBuff[2] - '0')*1000000;//提取地址码
    addr_tmp += (UartBuff[3] - '0')*100000;
    addr_tmp += (UartBuff[4] - '0')*10000;
    addr_tmp += (UartBuff[5] - '0')*1000;
    addr_tmp += (UartBuff[6] - '0')*100;
    addr_tmp += (UartBuff[7] - '0')*10;
    addr_tmp += (UartBuff[8] - '0');

    beep = UartBuff[9] - '0';//提取功能位

    tem=calc_addr(addr_tmp,beep);//前面是地址码,后面是BB机内00 01 10 11  代表0,1,2,3种不同的声音

    for(i = 0; i < 8; i++)
    {
        if(i == Tx_Num)//地址码放入对应的帧中
            TxBuff[i*2] = calc_bch_and_parity(tem);//取得BCH校验后的地址码序列 
        else
        {
            TxBuff[i*2] = NILL_DATA;//其他帧填充闲置码
            TxBuff[i*2+1] = NILL_DATA;
        }
    }
}

void calc_NumberData() //计算数值数据(数字机)
{
    unsigned long Num_Negate[UARTBUFF_SIZE] = {0};
    unsigned int i, j, k, n;
    unsigned char byte_tmp[10], byte_tmp_negate[10];
    float TxCount = 0.0;

    for(i = DATA_START; i < UARTBUFF_SIZE; i++)  //表意字符替换
    {
        if(UartBuff[i] == 'A')
            UartBuff[i] = 0x0A;
        if(UartBuff[i] == 'B')
            UartBuff[i] = 0x0B;
        if(UartBuff[i] == 'C')
            UartBuff[i] = 0x0C;
        if(UartBuff[i] == 'D')
            UartBuff[i] = 0x0D;
        if(UartBuff[i] == 'E')
            UartBuff[i] = 0x0E;
        if(UartBuff[i] == 'F')
            UartBuff[i] = 0x0F;
    }

    n = DATA_START;
    for(i = DATA_START; i < UARTBUFF_SIZE; i++)      //数字的ASIIC码只用到第四位,所以将所有数字的低四位合并,高四位丢弃
    {
        if(i % 2 == 0)
            UartBuff[n] = UartBuff[i] << 4;
        else
        {
            UartBuff[n] |= UartBuff[i] & 0x0f;
            n++;
        }
    }

    k = Tx_Num * 2 + 1;                               //定位到地址码后数据所在的位置
    for(i = DATA_START; i < UARTBUFF_SIZE; i += 5)                      //按四位取出并放在高四位准备倒序
    {
        byte_tmp[0] = (UartBuff[i] & 0xf0);
        byte_tmp[1] = (UartBuff[i] & 0x0f) << 4;
        byte_tmp[2] = (UartBuff[i + 1] & 0xf0);
        byte_tmp[3] = (UartBuff[i + 1] & 0x0f) << 4;
        byte_tmp[4] = (UartBuff[i + 2] & 0xf0);
        byte_tmp[5] = (UartBuff[i + 2] & 0x0f) << 4;
        byte_tmp[6] = (UartBuff[i + 3] & 0xf0);
        byte_tmp[7] = (UartBuff[i + 3] & 0x0f) << 4;
        byte_tmp[8] = (UartBuff[i + 4] & 0xf0);
        byte_tmp[9] = (UartBuff[i + 4] & 0x0f) << 4;

        for(j = 0; j < 10; j++)                           //倒序
        {
            for(n = 0; n < 8; n++)
            {
                byte_tmp_negate[j] <<= 1;
                byte_tmp_negate[j] |= (byte_tmp[j] >> n) & 0x01;
            }
        }

        Num_Negate[k] = 0x80000000;                                        //重新组合
        Num_Negate[k] |= (unsigned long)byte_tmp_negate[0] << 27;
        Num_Negate[k] |= (unsigned long)byte_tmp_negate[1] << 23;
        Num_Negate[k] |= (unsigned long)byte_tmp_negate[2] << 19;
        Num_Negate[k] |= (unsigned long)byte_tmp_negate[3] << 15;
        Num_Negate[k] |= (unsigned long)byte_tmp_negate[4] << 11;
        Num_Negate[k + 1] = 0x80000000;
        Num_Negate[k + 1] |= (unsigned long)byte_tmp_negate[5] << 27;
        Num_Negate[k + 1] |= (unsigned long)byte_tmp_negate[6] << 23;
        Num_Negate[k + 1] |= (unsigned long)byte_tmp_negate[7] << 19;
        Num_Negate[k + 1] |= (unsigned long)byte_tmp_negate[8] << 15;
        Num_Negate[k + 1] |= (unsigned long)byte_tmp_negate[9] << 11;
        k = k + 2;

    }

    //UartCount = 25;
    n = Tx_Num * 2 + 1;                                                   //将数据合并到Tx发送缓冲区并计算校验码
    TxCount = 0.0;
    for(i = Tx_Num * 2 + 1; i < UARTBUFF_SIZE; i++)
    {
        if(TxCount >= (UartCount - DATA_START - 1))
            break;
        if(i % 16 == 0)                                                     //每8帧(16个码字)插入一个同步码
            TxBuff[n++] = SYNC_DATA;
        TxBuff[n++] = calc_bch_and_parity( Num_Negate[i]);
        TxCount += 5;                                                     //串口接收5个数字相当于POCSAG码中一个码字
        if(n >= TXBUFF_SIZE || i >= UARTBUFF_SIZE)                        //超出缓冲区退出
            break;
    }

}

void calc_TextData()  //计算汉字数据
{
    unsigned long Text_Negate[UARTBUFF_SIZE] = {0};
    unsigned int n, i, k, byte_tmp;
    float TxCount = 0.0;
    for(k = DATA_START; k < UARTBUFF_SIZE; k++)         //字节按位倒序
    {
        byte_tmp = UartBuff[k];
        UartBuff[k] = 0;
        for(n = 0; n < 8; n++)   //倒序
        {
            UartBuff[k] <<= 1;
            UartBuff[k] |= (byte_tmp >> n) & 0x01;
        }
        UartBuff[k] &= 0xFE;    //倒序后丢弃最低位
    }

    k = Tx_Num * 2 + 1;
    for(n = DATA_START; n < UARTBUFF_SIZE; n += 20)                 //移位,20个字节一循环
    {
        Text_Negate[k] = 0x80000000;
        Text_Negate[k] |= (unsigned long)UartBuff[n] << 23;
        Text_Negate[k] |= (unsigned long)UartBuff[n + 1] << 16;
        Text_Negate[k] |= (unsigned long)UartBuff[n + 2] << 9;
        Text_Negate[k] &= 0xfffff800;

        Text_Negate[k + 1] = 0x80000000;
        Text_Negate[k + 1] |= (unsigned long)UartBuff[n + 2] << 29;
        Text_Negate[k + 1] &= 0xC0000000;
        Text_Negate[k + 1] |= (unsigned long)UartBuff[n + 3] << 22;
        Text_Negate[k + 1] |= (unsigned long)UartBuff[n + 4] << 15;
        Text_Negate[k + 1] |= (unsigned long)UartBuff[n + 5] << 8;
        Text_Negate[k + 1] &= 0xfffff800;

        Text_Negate[k + 2] = 0x80000000;
        Text_Negate[k + 2] |= (unsigned long)UartBuff[n + 5] << 28;
        Text_Negate[k + 2] &= 0xE0000000;
        Text_Negate[k + 2] |= (unsigned long)UartBuff[n + 6] << 21;
        Text_Negate[k + 2] |= (unsigned long)UartBuff[n + 7] << 14;
        Text_Negate[k + 2] |= (unsigned long)UartBuff[n + 8] << 7;
        Text_Negate[k + 2] &= 0xfffff800;

        Text_Negate[k + 3] = 0x80000000;
        Text_Negate[k + 3] |= (unsigned long)UartBuff[n + 8] << 27;
        Text_Negate[k + 3] &= 0xF0000000;
        Text_Negate[k + 3] |= (unsigned long)UartBuff[n + 9] << 20;
        Text_Negate[k + 3] |= (unsigned long)UartBuff[n + 10] << 13;
        Text_Negate[k + 3] |= (unsigned long)UartBuff[n + 11] << 6;
        Text_Negate[k + 3] &= 0xfffff800;

        Text_Negate[k + 4] = 0x80000000;
        Text_Negate[k + 4] |= (unsigned long)UartBuff[n + 11] << 26;
        Text_Negate[k + 4] &= 0xF8000000;
        Text_Negate[k + 4] |= (unsigned long)UartBuff[n + 12] << 19;
        Text_Negate[k + 4] |= (unsigned long)UartBuff[n + 13] << 12;
        Text_Negate[k + 4] |= (unsigned long)UartBuff[n + 14] << 5;
        Text_Negate[k + 4] &= 0xfffff800;

        Text_Negate[k + 5] = 0x80000000;
        Text_Negate[k + 5] |= (unsigned long)UartBuff[n + 14] << 25;
        Text_Negate[k + 5] &= 0xFC000000;
        Text_Negate[k + 5] |= (unsigned long)UartBuff[n + 15] << 18;
        Text_Negate[k + 5] |= (unsigned long)UartBuff[n + 16] << 11;
        Text_Negate[k + 5] |= (unsigned long)UartBuff[n + 17] << 4;
        Text_Negate[k + 5] &= 0xfffff800;

        Text_Negate[k + 6] = 0x80000000;
        Text_Negate[k + 6] |= (unsigned long)UartBuff[n + 17] << 24;
        Text_Negate[k + 6] &= 0xFE000000;
        Text_Negate[k + 6] |= (unsigned long)UartBuff[n + 18] << 17;
        Text_Negate[k + 6] |= (unsigned long)UartBuff[n + 19] << 10;
        Text_Negate[k + 6] &= 0xfffff800;

        k += 7;
    }

    n = Tx_Num * 2 + 1;
    TxCount = 0.0;
    for(i = Tx_Num * 2 + 1; i < UARTBUFF_SIZE; i++)                       //将数据合并到Tx发送缓冲区并计算校验码
    {
        if(TxCount >= (UartCount - DATA_START - 1))
            break;
        if(i % 16 == 0)                                                     //每8帧(16个码字)插入一个同步码
            TxBuff[n++] = SYNC_DATA;
        TxBuff[n++] = calc_bch_and_parity( Text_Negate[i]);
        TxCount += 2.8571428571;                                            //串口接收2.8571428571个字节相当于POCSAG码中一个码字
        if(n >= TXBUFF_SIZE || i >= UARTBUFF_SIZE)
            break;
    }
}

void SendTxBuff()
{
    unsigned int i;

    PTT = 0;                    //触发PTT
    Delay200ms();                  //PTT延时,根据对讲机不同进行调整
    if(UartBuff[SPEED] == 'L')
    {
        TL0 = LOWSPEED_TIMER_L;        //设置定时初值
        TH0 = LOWSPEED_TIMER_H;
    }
    if(UartBuff[SPEED] == 'H')
    {
        TL0 = HIGHTSPEED_TIMER_L;        //设置定时初值
        TH0 = HIGHTSPEED_TIMER_H;
    }
    TR0 = 1; //启动定时器

    for(i = 0; i < 18; i++)//至少发送576个前导码,只能多,不能少
    {
        Send_Num(0xAAAAAAAA);
    }

    Send_Num(SYNC_DATA);//发送同步码

    for(i = 0; i < TXBUFF_SIZE; i++)//将TX发送缓冲区中的数据发出
    {
        if(TxBuff[i] == 0x00000000 || TxBuff[i] == 0xffffffff)//如果缓冲区中数据为全0或全1则表示发送完毕
            break;
        Send_Num(TxBuff[i]);
    }

    TX=LOW; 
    TR0 = 0;
    Delay200ms();                       //发送完毕PTT延时,根据对讲机不同进行调整
    Delay200ms();
    Delay200ms();
    PTT = 1;

    UartSendString("OK\r\n",4);          //串口输出调试信息
    UartSendString("UartCount=",10);
    SBUF = UartCount / 100 + '0';
    while(TI == 0);
    TI = 0;
    SBUF = (UartCount / 10) % 10 + '0';
    while(TI == 0);
    TI = 0;
    SBUF = UartCount % 10 + '0';
    while(TI == 0);
    TI = 0;
    UartSendString("\r\n",2);
}

void main()
{
    UartInit();//串口初始化
    Timer0Init();//定时器0初始化

    TX = 0;     //初始化IO引脚
    PTT= 1;

    while(1)
    {
        //NewData = 1;
        if(NewData == 1 && (UartBuff[TEXT_OR_NUM] == 'N' || UartBuff[TEXT_OR_NUM] == 'T'))
        {
            GetAddrNumber();//获取地址码及功能位
            if(UartBuff[TEXT_OR_NUM] == 'N')
                calc_NumberData();//计算数字数据
            if(UartBuff[TEXT_OR_NUM] == 'T')
                calc_TextData();//计算文字数据
            SendTxBuff();//发送数据
            Empty_Buff();//清空TX发送缓冲区和串口接收缓冲区
            NewData = 0;
        }
    }
}

void UART_RxData() interrupt 4   //串口接收中断函数
{
    if(RI == 1)
    {
        RI = 0;
        UartTmp = SBUF;
        if(UartTmp == '#')              //串口开头
            UartCount = 0;
        if(UartTmp == '$')              //串口结尾
        {
            UartTmp = 0x00;
            NewData = 1;
        }
        UartBuff[UartCount] = UartTmp;
        UartCount++;                     //串口计数
        if(UartCount >= UARTBUFF_SIZE)   //超出缓冲区归零
            UartCount = 0;
    }
}

void IntTimer0() interrupt 1 //定时器0中断函数
{
    if(UartBuff[SPEED] == 'L')
    {
        TL0 = LOWSPEED_TIMER_L;           //设置定时初值
        TH0 = LOWSPEED_TIMER_H;
    }
    if(UartBuff[SPEED] == 'H')
    {
        TL0 = HIGHTSPEED_TIMER_L;        //设置定时初值
        TH0 = HIGHTSPEED_TIMER_H;
    }
    TM0_FLAG=1; 
} 

参考链接

原作者出了个STM32的,基于STM32的寻呼发射器

项目地址:https://359303267.github.io/STM32_POCSAG_Transmit

视频教程:https://www.bilibili.com/video/BV1sZ4y1u76M?spm_id_from=444.42.0.0

原文地址:https://359303267.github.io/POCSAG/

踩坑记录与注意事项

  • 不响第一件事:换相位。正/负相位是 1/0 对频偏极性的约定,两边没对上时 BP 机完全解不出码,现象是“毫无反应”而不是乱码;
  • 乱码/响声不对:换功能位。功能位 2 bit 是地址码字的一部分,同一台机 4 个取值对应 4 种提示音;
  • 前置码只能多不能少:位同步靠 576 bit 的 1010… 建立,减短后 BP 机在“半同步”状态开始解码,误码率暴涨;
  • PTT 前后延时别省:起振前发的码白发,提前释放会截断末尾码字导致整条消息被丢弃;
  • 位定时初值按晶振微调:0.1% 的晶振公差乘上几百个 bit 就是半个位的漂移;用逻辑分析仪量实际位宽、对着调 LOWSPEED/HIGHTSPEED 初值最直接;
  • 地址低 3 位别当成地址发:它就是帧号,码字里只有高 18 位地址,这是读代码时最容易误解的地方;
  • 合法发射:见文首说明,功率衰减到 0.1W 以内再做实验。