OOK

OOK及其应用

1. OOK

1.1 什么是 OOK

“OOK” 全称为 On-Off Keying(开关键控),是一种最简单、最基础的数字调制技术,核心原理是通过 载波信号的 “存在”(On)与 “消失”(Off) 来对应传输二进制数字中的 “1” 和 “0”,广泛应用于低速率、低成本的无线通信场景。

1.2 OOK 的核心原理

数字通信的本质是将二进制数据(0 和 1)转换为可在信道(如空气、电缆)中传输的电 / 电磁波信号。

OOK 的调制逻辑极为直接:

  • 当传输二进制 “1” 时:发送端输出 → 有载波信号(比如一个固定频率的正弦波);
  • 当传输二进制 “0” 时:发送端停止 → 输出载波(信号幅度为 0,即 “无信号”)。

1.3 OOK 的优缺点

优缺点 说明
优点 1. 实现成本极低:发送端只需控制载波的通断,接收端只需检测信号 “有无”,电路简单(无需复杂的相位 / 幅度调节模块);
2. 功耗低:传输 “0” 时无载波输出,可减少设备能耗,适合电池供电的低功耗设备(如遥控器);
3. 技术门槛低:原理直观,易于设计和调试。
缺点 1. 抗干扰能力差:信道中的噪声(如电磁干扰)可能误将 “无信号”(0)判为 “有信号”(1),或反之,速率越高误码率越高;
2. 频谱效率低:相同带宽下,OOK 传输速率远低于 PSK(相移键控)、QAM(正交振幅调制)等复杂调制技术;
3. 依赖强信号:信号衰减后易丢失,适合短距离通信。

1.4 OOK 的典型应用场景

由于其 “低成本、低功耗” 的特点,OOK 主要用于 短距离、低速率、对可靠性要求不极致的场景。
常见例子包括:

  1. 红外遥控器:如电视、空调、机顶盒的遥控器,通过红外光的 “亮 / 灭” 传输控制指令。
  2. 无线射频(RF)低速率设备:如汽车遥控钥匙(解锁 / 锁车指令)、无线门铃(触发信号)、低功耗传感器(如温湿度传感器的简单数据上报);
  3. 早期通信系统:如电报系统(摩尔斯电码),本质是通过电线中电流的 “通 / 断” 实现远距离信息传输,是 OOK 的早期雏形。

1.5 OOK 与其他调制技术的对比

为了更清晰理解 OOK 的定位,可将其与常见的数字调制技术对比:

调制技术 核心原理 抗干扰能力 频谱效率 适用场景
OOK 载波 “有 / 无” 对应 1/0 最差 最低 短距离、低速率、低成本设备(遥控器、钥匙)
ASK 不同幅度载波对应不同符号 较差 较低 中短距离数据传输(如部分 RFID)
FSK 不同频率载波对应不同符号 较好 中等 无线数据传输(如蓝牙低功耗、对讲机)
PSK 不同相位载波对应不同符号 最好 较高 高速通信(如 WiFi、4G/5G、卫星通信)

2. 软件模拟OOK

2.1 原理

通过把发送串口(串口也可以换成其他)的Tx引脚发送的串口数据帧用OOK调制,再用接收串口的Rx引脚在经过解调之后接收。
高电平输出正弦波,低电平输出中间电平。

2.2 OOK波形输出

使用DAC+DMA把串口的Tx引脚发送的数据帧进行数模转换,使开发板通过PA4引脚输出带有调制的载波信号。
由于是定时器触发,因此可以通过调节定时器的周期和分频系数来调节正弦波的周期。比如,调节每个bit所输出的正弦波个数。

关键步骤:

  1. 发送:
    • 定义正弦波数组(不用函数计算),用DMA直接把数组值传输至DAC,更高效。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      const uint16_t SineWave128[128] = {
      2047, 2147, 2248, 2347, 2446, 2545, 2641, 2737, 2831, 2922, 3012, 3100, 3185, 3267, 3346, 3422,
      3495, 3564, 3630, 3692, 3749, 3803, 3853, 3898, 3939, 3975, 4006, 4033, 4055, 4072, 4085, 4092, 4095,

      4092, 4085, 4072, 4055, 4033, 4006, 3975, 3939, 3898, 3853, 3803, 3749, 3692, 3630, 3564, 3495, 3422,
      3346, 3267, 3185, 3100, 3012, 2922, 2831, 2737, 2641, 2545, 2446, 2347, 2248, 2147, 2047,

      1947, 1846, 1747, 1648, 1549, 1453, 1357, 1263, 1172, 1082, 994, 909, 827, 748, 672, 599, 530, 464,
      402, 345, 291, 241, 196, 155, 119, 88, 61, 39, 22, 9, 2, 0,

      2, 9, 22, 39, 61, 88, 119, 155, 196, 241, 291, 345, 402, 464, 530, 599, 672,
      748, 827, 909, 994, 1082, 1172, 1263, 1357, 1453, 1549, 1648, 1747, 1846, 1947
      };
    • 把串口的Tx引脚连接至一个GPIO中断引脚,通过检测该引脚的上升沿和下降沿进行判断此时的电平高低,并赋值给标志位。

1
2
3
4
5
6
7
8
// 引脚中断回调
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_9)
{
tx_level_flag = (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_9)) ? 1 : 0;
}
}
  • 使用DMA半全缓冲模式(把一个缓冲区逻辑上分为前半区和后半区,当DMA把前半区写满时,触发半传输完成中断,此时CPU可以在DMA继续写后半区时,先处理前半区的数据。传输完成中断同理)。
    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
    // DMA半传输中断
    void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef *hdac)
    {
    if (tx_level_flag)
    {
    // 高电平,把前半部分数组赋值为正弦波数组
    memcpy(&OOK_Buffer[0], &SineWave128[0], BUFFER_SIZE / 2 * sizeof(uint16_t));
    }
    else
    {
    // 低电平,把前半部分数组赋值为平电平
    memset(&OOK_Buffer[0], 2048, BUFFER_SIZE / 2 * sizeof(uint16_t));
    }
    }

    // DMA全传输中断
    void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac)
    {
    if (tx_level_flag)
    {
    // 高电平,把后半部分数组赋值为正弦波数组
    memcpy(&OOK_Buffer[BUFFER_SIZE / 2], &SineWave128[BUFFER_SIZE / 2], BUFFER_SIZE / 2 * sizeof(uint16_t));
    }
    else
    {
    // 低电平,把后半部分数组赋值为平电平
    memset(&OOK_Buffer[BUFFER_SIZE / 2], 2048, BUFFER_SIZE / 2 * sizeof(uint16_t));
    }
    }

img

  1. 接收
    • 计算采样点信息,用于匹配发送的时序。以下例子的芯片时钟为144MHz
      • 计算每一位所占用的时间 $T_{bit} =\frac 1 {BaudRate}$,如波特率9600,1bit时间 ≈ 104.167us。
      • 计算每个周期的时间 $T_{period} =\frac {T_{bit}} {n}$,n为每个bit的正弦波数量。比如6个,则每个正弦波周期 ≈ 17.36us。
      • 计算每个采样点的时间间隔 $T_{sample} =\frac {T_{period}} {m}$,m为每个周期 ADC 的采样点个数。比如8个,则每个采样点时间间隔 ≈ 2.17us。
      • ADC(12bit)总转换时间 $T_{conv} = {采样时间} + {12.5 个周期}$。比如使用13.5个周期为采样时间,则$T_{sample-total} = ({13.5} + {12.5}) \times {55.56ns} ≈ {1.44us}$, (ADC时钟为18MHz,则一个周期 ≈ 55.56ns。
      • 条件:ADC 总采样时间 ≤ 采样间隔 → 1.44us < 2.17us。
    • 一个正弦波周期时间采样六个点后,触发DMA全传输中断进入回调函数处理。
    • 将PA6引脚接入串口的RX引脚,即可读取数据。
      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
      #define p2p_threshold 100
      /*
      * 判断一个正弦波周期的六个采样点的峰峰值是否大于阈值
      * 如果大于阈值则为正弦波,返回1,代表高电平,反之返回0,代表低电平
      */
      static uint8_t decide_bit_from_block(const uint16_t *buf, uint32_t n)
      {
      uint16_t maxv = 0;
      uint16_t minv = 4095;
      for (uint32_t i = 0; i < n; i++)
      {
      if (buf[i] > maxv)
      maxv = buf[i];
      if (buf[i] < minv)
      minv = buf[i];
      }
      uint16_t p2p = maxv - minv;
      return (p2p > p2p_threshold) ? 1 : 0;
      }
      // DMA传输6个采样点完成
      void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
      {
      if (hadc->Instance == ADC1)
      {
      uint8_t b = decide_bit_from_block(adc_buf, SINGLE_PERIOD_POINTS);
      HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, b ? GPIO_PIN_SET : GPIO_PIN_RESET);
      }
      }
      转换后的波形:
      img