做了个PSK31 Beacon
2017-04-12 by Stavrospsk31 是业余无线电爱好者常用的通讯方式. psk31 的原理就不介绍了, 见链接: 哈罗 CQ 火腿社区 - HAM 软件、HAM 网站 - PSK31 簡介 BV3FG - Powered by phpwind
psk31 的调制如何具体实现呢? HAM 们常用的方式是用 digipan 等软件把要发送的消息调制到几百 Hz 的音频信号上, 再利用发射机的 SSB 模式把这个音频信号发射出去, 这里 SSB 实际上起了上变频的作用.
也可以把整个调制过程都放在 MCU 里进行, 直接从 MCU 的 DAC 输出调制好的音频信号.
ka7oei 则提供了一个偏硬件的直接调制方案, 见下图:
左下的振荡器振荡在所需频率的 4 倍上, 由计数器 74HC4017 接成四分频, 从 0 和 2 两脚输出一对相位相反的载波信号. MCU 的 PB2 通过 74HC00 选择相位. PA3 输出的 PWM 信号经低通滤波后, 再经 U2, Q4 调节 Q3 的漏极电压, 实现在输出 0 时将输出信号幅度逐渐降到零的瞬间切换相位. 这个方案很简洁, 我决定就拿这个图稍微改动一下. MCU 么, 用最简单的 ATTiny13 就好了.
首先得在 PC 上软件模拟一下. 程序很简单, 找个 psk31 的码表, 一般是类似这样:
const uint16_t VARICODE_TABLE[] = {
0x8000, /* ASCII = ' ' 1 */
0xFF80, /* ASCII = '!' 111111111 */
0xAF80, /* ASCII = '"' 101011111 */
0xFA80, /* ASCII = '#' 111110101 */
...
0xA000, /* ASCII = 't' 101 */
0xDC00, /* ASCII = 'u' 110111 */
0xF600, /* ASCII = 'v' 1111011 */
0xD600, /* ASCII = 'w' 1101011 */
0xDF00, /* ASCII = 'x' 11011111 */
0xBA00, /* ASCII = 'y' 1011101 */
0xEA80, /* ASCII = 'z' 111010101 */
};
存储格式是从高位到低位, 后面补零. 注意这个码表是从空格开始, 到小写 z 就结束了, 也就是 ASCII 码从 32 到 122 这 91 个字符, 这样能省下不少宝贵的 flash 空间. 毕竟 ATTiny13 的 flash 空间只有可怜的 1024 字节, 如果把完整的码表放进来, 一下子 512 字节就没有了. 发送一个字符的函数可以写成这样:
void psk31_tx(char c)
{
uint16_t varc;
for(varc = VARICODE_TABLE[c - ' ']; varc; varc <<= 1) {
if(varc & 0x8000)
printf("1");
else
printf("0");
}
printf("00");
}
然后随便找个字符串, 比如 Hello, world.
char msg[] = "Hello, world.";
char *p = msg;
while(*p) {
psk31_tx(*p);
p++;
}
编译运行, 就能看到效果了.
在 MCU 上实现时要稍微复杂一点. 发送 1 好办, 保持满幅输出不变, 挨过 32ms 时间就完事. 发送 0 就复杂一些, 需要按余弦函数把输出幅度逐渐减小为零, 切换相位, 再把幅度逐渐增加到最大. 把 printf("1") 和 printf("0") 分别换成这两个操作就行. 但是这样就破坏了可移植性. 解决办法就是函数指针, 把 psk31_tx 函数写成这样:
void psk31_tx(char c, void (*tx0)(void), void (*tx1)(void))
{
unsigned short varc;
for(varc = varicode_table[(c - ' ')]; varc; varc <<= 1) {
if(varc & 0x8000)
(*tx1)();
else
(*tx0)();
}
(*tx0)();
(*tx0)();
}
把发送 1 和 0 的操作写成两个回调函数传给 psk31_tx(), 这样就完美了.
之后设计电路, 如图:
振荡这里用一只 74AHC1G00 加上 14.318M 的晶振就行了, 通过调节 KV1471 的偏压来微调频率. 四分频用了更常用的 74HC74, 两级二分频, 从第二级的 Q 和 / Q 脚输出一对相位相反的载波信号. 切换相位用一只模拟开关 74LVC1G3157 来实现. 其余部分基本保持 ka7oei 的设计不变.
成品照片:
末级电源电压这里只用到 5V, 实测连续发送 1 时在 50 欧假负载上可以得到 40mW / +16dBm 的功率. 如果把末级供电电压提到 12V, 同时把 MMBT2222 和 2N7002 换成封装大一点的管子, 估计能做到 1W 上下, 再大就得加后级了.
ATTiny13 的一大缺点是不能接晶振, 于是把它的 RC 时钟设到 4.8M, 通过 OSCCAL 寄存器微调到 4.096M 左右, 然后 8 分频作为定时器 0 的时钟源, 定时器 0 输出 PWM 的同时也提供了 4.096M / 8 / 256 = 2kHz 的时基信号. 余弦波形表用了 64 个点, 从而得到 2k / 64 = 31.25Hz. 输出 1 时只要什么都不做, 等定时器 0 中断 64 次就行了.
ATTiny13 的另一大缺点... 就是 RAM 只有可怜的 64 字节. 实测发现即使发送 "Hello, world." 也一样爆栈, 只好不用回调机制, 同时把 "Hello, world." 也放到 flash, 才总算是发送成功.
用 IC-R71E 接收, Digipan 和 MultiPSK 解调的效果图如下:
Digipan
MultiPSK
感觉 Multipsk 的解调效果要好一些, 可能是 Digipan 那边有些参数没设置好.
在这个 Beacon 的基础上可以玩出很多花样来, 看你怎么发挥了.