最简单的调制方案是
幅度调制(在数字领域中,这通常被称为幅移键控)。取一个固定频率(比如说10KHz)作为你的“载波波形”,使用二进制数据中的位来开关它。如果你的数据速率是每秒10位,那么你将以该速率切换10KHz信号的开和关。解调可以是(可选的)10KHz滤波器,接着与门限值进行比较。这是一个相当简单的实现方案。通常情况下,信号频率越高,可用带宽越大,你就能更快地开关该信号。
一个非常酷/有趣的应用是将其编码/解码为摩尔斯电码,看看你能走多快。
FSK,在两个频率之间切换,更有效地利用了带宽,更具抗噪性,但会使解调器更加复杂,因为你需要区分这两个频率。
先进的调制方案,例如
相移键控,在给定带宽和信噪比的情况下能够获得最高的比特率,但实现起来更加复杂。模拟电话调制解调器需要处理特定的带宽(例如至少3Khz)和噪声限制。如果您需要在带宽和噪声限制下获得最高可能的比特率,则可以选择这种方式。
对于先进调制方案的实际代码示例,我建议查阅DSP供应商(例如
TI和
Analog Devices)的应用说明,因为这些是DSP的常见应用。
使用TMS320C50实现PI/4移位D-QPSK基带调制解调器
QPSK调制揭秘
TMS320C50 DSP上的V.34发射机和接收机实现
另一种非常简单但不太有效的方法是使用DTMF。这些是由电话键盘生成的音调,每个符号都是两个频率的组合。如果你谷歌一下,你会找到很多源代码。根据您的应用/要求,这可能是一个简单的解决方案。
让我们深入了解一些简单的方案实现细节,比如我之前提到的莫尔斯电码。我们可以使用“点”表示0,“划”表示1。莫尔斯电码的一个优点是它还解决了帧同步问题,因为您可以在每个空格后重新同步采样。为了简单起见,让我们将“载波波形”的频率选为11KHz,并假设您的波形输出为44Khz、16位、单声道。我们还将使用方波,这将产生谐波,但我们并不真正关心。如果11KHz超出了您的麦克风频率响应范围,那么只需将所有频率除以2即可。我们将选择一些任意水平10000,因此我们的“开启”波形看起来像这样:
{10000, 10000, 0, 0, 10000, 10000, 0, 0, 10000, 0, 0, ...} // 4 samples = 11Khz period
我们的“关”波形只是全零。我将编码这部分留给读者作为练习。
因此,我们有类似以下内容的东西:
const int dot_samples = 400;
const int space_samples = 400;
const int dash_samples = 800;
void encode( uint8_t* source, int length, int16_t* target )
{
for(int i=0; i<length; i++)
{
for(int j=0; j<8; j++)
{
if((source[i]>>j) & 1)
{
generate_on(&target, dash_samples);
}
else
{
generate_on(&target, dot_samples);
}
generate_off(&target, space_samples);
}
}
}
解码器稍微复杂一些,但是这里有一个概述:
- 可选地对11KHz左右的采样信号进行带通滤波。这将提高在嘈杂环境中的性能。FIR滤波器非常简单,有一些在线设计程序可以为您生成滤波器。
- 对信号进行阈值处理。每个值大于最大振幅的1/2的值为1,每个值小于最大振幅的1/2的值为0。这假设您已经对整个信号进行了采样。如果这是实时的,您可以选择一个固定的阈值或者进行某种自动增益控制,在一段时间内跟踪最大信号水平。
- 扫描点或划线的开始。您可能希望在点周期内至少看到一定数量的1来考虑样本为点。然后继续扫描以查看这是否为划线。不要期望完美的信号-您会在1的中间看到一些0和在0的中间看到一些1。如果噪声很小,将"开启"周期与"关闭"周期区分开来应该相当容易。
- 然后反转上述过程。如果您看到划线,则将1位推入缓冲区,如果是点,则将零推入缓冲区。