CH585 CH587 蓝牙固件自动调整频偏

 

什么是频偏

调制特性

在调整频偏前,先了解一下 BLE 是怎么区分射频信号的 0/1 的。

BLE(低功耗蓝牙)空中数据交互核心依赖于 GFSK(高斯频移键控)调制。在 GFSK 中,区分射频信号中的 bit 0 和 bit 1,不是通过信号“有无”或简单的幅度高低来判断,而是通过信号频率的偏移方向来区分:正偏差表示 bit 1,负偏差表示 bit 0。如下图所示,是蓝牙核心手册中关于 GFSK 的截图。

在这里插入图片描述

(内容相关路径是:BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 6, Part A | 3.1 MODULATION CHARACTERISTIC 中)

从上图可知:

比特值频率偏移实际表现
0 负偏移(-Δf) 载波频率 降低
1 正偏移(+Δf) 载波频率 升高
参数BLE (LE PHY)
调制指数 0.45 ~ 0.55
最小频偏 (1 M/s) 185 kHz
最小频偏 (2 M) 370 kHz

小结:调制特性关注的是 bit 0 和 bit 1 之间的频率差,确保两者的频率差异足够大,接收端才能可靠分辨。因此,物理层并不传输绝对频率值(类似 PWM 中开启表示 bit 0、关闭表示 bit 1),而是通过相对于信道中心频率的偏移方向来表示 bit 0 或 bit 1。

下面是 GFSK 调制中 bit 0 与 bit 1 的区分逻辑:

image

 中心频率偏差

从调制特性 GFSK 可知,空中传输是通过相对于信道中心频率的偏移方向来表示 bit 0 或 bit 1 的。

既然数据是依靠相对于信道中心频率的偏移来承载的,那么一旦信道中心频率本身出现误差,数据的解调也会不准确。因此,蓝牙规范对信道中心频率同样有明确的要求。如下图:
在这里插入图片描述

(内容相关路径是:BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 6, Part A 3.3 RADIO FREQUENCY TOLERANCE 中)

小结:

中心频率偏差是指发射载波相对于信道标称中心频率的偏移。若该偏移超出规范允许范围(如 ±150 kHz),会导致信号偏离信道,落入邻道或带外,造成邻道干扰与邻道噪声混入,导致数据无法解调。

一般讨论的频偏指的就是中心频率偏差,需要调整的也是中心频率偏差。

中心频率偏差对信号的影响过程如下:

image

频偏调整方式

晶振负载电容

为什么 BLE 的频偏可以通过外部的高速晶振的负载电容调整?

BLE 的 2.4G 是通过外部高速晶振经 PLL 倍频后输出的,时钟源就是外部的高速晶振,所以调整外部的高速晶振的频偏就可以调整 2.4G 的频偏。

CH584/5 芯片内置负载电容,所以可以直接使用代码调整。
在这里插入图片描述
注:可以直接调用相关 API 调整负载电容,没有特别的限制。

BLE 频偏调整的链路如下:

image

获取频偏

CFO(Carrier Frequency Offset,载波频率偏移)用于衡量发射端与接收端之间的基准时钟同步状态,反映的是发射机射频载波频率与接收机本振频率之间的不匹配程度,获取到的是相对误差。

SDK 中关于 CFO 函数:

/**
 * @brief   read cfo
 *
 * @param   None.
 *
 * @return  the value of cfo.
 */
extern int16_t BLE_ReadCfo( void );

注:该函数的返回值 1 单位表示 0.625 kHz,

小结:
因为获取的是相对频率差,所以需要以频偏足够准确的发射端作为基准。例如,可以使用官方评估板配合最新 SDK 中的主机代码,先经频谱仪确认其实际频偏后再作为基准使用;也可以选用较新的苹果手机作为基准。

代码实现

逻辑框图

在这里插入图片描述

实现逻辑

CH584/5 负载电容枚举:

typedef enum
{
    HSECap_10p = 0, // 10pF | 10进制: 0  | 16进制: 0x00 | 2进制: 0000b
    HSECap_12p,     // 12pF | 10进制: 1  | 16进制: 0x01 | 2进制: 0001b
    HSECap_14p,     // 14pF | 10进制: 2  | 16进制: 0x02 | 2进制: 0010b
    HSECap_16p,     // 16pF | 10进制: 3  | 16进制: 0x03 | 2进制: 0011b
    HSECap_18p,     // 18pF | 10进制: 4  | 16进制: 0x04 | 2进制: 0100b
    HSECap_20p,     // 20pF | 10进制: 5  | 16进制: 0x05 | 2进制: 0101b
    HSECap_22p,     // 22pF | 10进制: 6  | 16进制: 0x06 | 2进制: 0110b
    HSECap_24p,     // 24pF | 10进制: 7  | 16进制: 0x07 | 2进制: 0111b
    HSECap_2p,      //  2pF | 10进制: 8  | 16进制: 0x08 | 2进制: 1000b
    HSECap_4p,      //  4pF | 10进制: 9  | 16进制: 0x09 | 2进制: 1001b
    HSECap_6p,      //  6pF | 10进制: 10 | 16进制: 0x0A | 2进制: 1010b
    HSECap_8p       //  8pF | 10进制: 11 | 16进制: 0x0B | 2进制: 1011b
} HSECapTypeDef;

根据上表可知 CH584/5 系列的负载电容寄存器值是非线性的,在调整、计算的过程中需要做 2 次转化

1、将目前在使用的负载电容值转化为线性的(0 pF -> 0,2 pF -> 1,4 pF -> 2,6 pF -> 3),如:

①大于 8:减去 8,2 pF 的线性值 0,实际寄存器值 0x08,所以要减去 8,8-8=0
②小于 8:加上 4,10 pF 线性值 4,实际寄存器值 0x00,所以要加上 4,4+0=4

经过上文的步骤得知线性挡位后,按需加上或减去差值,计算出调整目标值,并且确认调整目标值合法后,需要将线性值转化为实际枚举值。

代码

uint8_t get_curr_cap_load(void){
    uint8_t reg_val = R8_XT32M_TUNE; 
    uint8_t cap_load = (reg_val >> 4) & 0x0F;
    
    uint8_t cap_val = 0;
    
    if ((cap_load & 0x08) == 0) {
        // Bit 7 为 0 (0000b~0111b): 电容 = cap_load * 2 + 10 (对应 10pF ~ 24pF)
        cap_val = cap_load * 2 + 10;
    } else {
        // Bit 7 为 1 (1000b~1111b): 取低 3 位计算,电容 = (cap_load & 0x07) * 2 + 2 (对应 2pF ~ 16pF)
        cap_val = (cap_load & 0x07) * 2 + 2;
    }
    return cap_val;
}


#define FREQ_TGT_KHZ 50


void Adjust_Cap_LOAD (int32_t freq_offset_khz){

    // 在 ±50kHz范围内直接退出,不用再次校准
    if (freq_offset_khz >= -FREQ_TGT_KHZ && freq_offset_khz <= FREQ_TGT_KHZ) {
        PRINT("Offset within +/-%d kHz. No adjustment needed. Current Load Cap: %d pF\n",  FREQ_TGT_KHZ, get_curr_c_load());
        return;
    }

    PRINT("===== Start Cap Adjustment =====\n");
    PRINT("Input Frequency: %d kHz  Current Load Cap: %d pF\n", freq_offset_khz,get_curr_cap_load());

    // 读取当前负载电容配置
    uint8_t current_enum = (R8_XT32M_TUNE >> 4) & 0x0F;

    // 计算负载电容调整量,加15目的是向上取整,经测试2pf可以调整30khz的频率
    int32_t delta_steps;
    if (freq_offset_khz > 0) {
        delta_steps = -((freq_offset_khz + 15) / 30);
    } else {
        delta_steps = ((-freq_offset_khz) + 15) / 30;
    }

    // 一次性转换:当前负载电容 -> 步进值 -> 目标步进(限幅) -> 目标枚举
    //由于58的负载电容配置是非线性的所以需要转化一下才可使用
    int32_t cur_step = (current_enum >= 8) ? (current_enum - 8) : (current_enum + 4);//例:2pf->挡位0 实际 寄存器值 0x08-> 8-8=0
    int32_t tgt_step = cur_step + delta_steps;

    // 边界限幅 (0 ~ 11 档)
    if (tgt_step > 11) tgt_step = 11;
    if (tgt_step < 0)  tgt_step = 0;

    // 转换为 HSECapTypeDef 枚举 //例:2pf->挡位0 实际 寄存器值 0x08-> 0+8=8
    uint8_t target_enum = (tgt_step < 4) ? (tgt_step + 8) : (tgt_step - 4);
    
    PRINT("Current enum: %d | Delta Steps: %+d | Target Enum: %d\n", 
          current_enum, delta_steps, target_enum);

    // 5. 写入寄存器
    if (target_enum != current_enum) {
        PRINT("Change Enum: %d -> %d\n", current_enum, target_enum);
    
        HSECFG_Capacitance(target_enum);
        PRINT("After Adjustment Load Cap: %d pF\n",get_curr_cap_load());
    }

    PRINT("===== Cap Adjustment End =====\n");

}

代码调用

可以直接在广播扫描回复内,筛选特定主机 MAC 地址(频偏较小的主机)的扫描回复请求后,触发负载电容调整函数。

image

实际运行

运行LOG 如下:

注意:开启校准前,主机要持续地扫描从机,有交互 CFO 才可以获取到相对频偏。

Receive scan req from 02 02 03 e4 c2 84  ..
CFO:85 frequency 53 
===== Start Cap Adjustment =====
Input Frequency: 53 kHz  Current Load Cap: 22 pF
Current enum: 6 | Delta Steps: -2 | Target Enum: 4
Change Enum: 6 -> 4
After Adjustment Load Cap: 18 pF
===== Cap Adjustment End =====

Receive scan req from 02 02 03 e4 c2 84  ..
CFO:-16 frequency -10 
Offset within +/-50 kHz. No adjustment needed. Current Load Cap: 18 pF

Receive scan req from 02 02 03 e4 c2 84  ..
CFO:-12 frequency -7 
Offset within +/-50 kHz. No adjustment needed. Current Load Cap: 18 pF

Receive scan req from 02 02 03 e4 c2 84  ..
CFO:-14 frequency -8 
Offset within +/-50 kHz. No adjustment needed. Current Load Cap: 18 pF

独立文件

#include "frequency_calibration.h"


uint8_t get_curr_cap_load(void){
    uint8_t reg_val = R8_XT32M_TUNE; 
    uint8_t cap_load = (reg_val >> 4) & 0x0F;
    
    uint8_t cap_val = 0;
    
    if ((cap_load & 0x08) == 0) {
        // Bit 7 为 0 (0000b~0111b): 电容 = cap_load * 2 + 10 (对应 10pF ~ 24pF)
        cap_val = cap_load * 2 + 10;
    } else {
        // Bit 7 为 1 (1000b~1111b): 取低 3 位计算,电容 = (cap_load & 0x07) * 2 + 2 (对应 2pF ~ 16pF)
        cap_val = (cap_load & 0x07) * 2 + 2;
    }
    return cap_val;
}


#define FREQ_TGT_KHZ 50


void Adjust_Cap_LOAD (int32_t freq_offset){

    // 在 ±50kHz范围内直接退出,不用再次校准
    if (freq_offset >= -FREQ_TGT_KHZ && freq_offset <= FREQ_TGT_KHZ) {
        PRINT("Offset within +/-%d kHz. No adjustment needed. Current Load Cap: %d pF\n",  FREQ_TGT_KHZ, get_curr_cap_load());
        return;
    }

    PRINT("===== Start Cap Adjustment =====\n");
    PRINT("Input Frequency: %d kHz  Current Load Cap: %d pF\n", freq_offset,get_curr_cap_load());

    // 读取当前负载电容配置
    uint8_t current_enum = (R8_XT32M_TUNE >> 4) & 0x0F;

    // 计算负载电容调整量,加15目的是向上取整,经测试2pf可以调整30khz的频率
    int32_t delta_steps;
    if (freq_offset > 0) {
        delta_steps = -((freq_offset + 15) / 30);
    } else {
        delta_steps = ((-freq_offset) + 15) / 30;
    }

    // 转换:当前负载电容 -> 步进值 -> 目标步进(限幅) -> 目标枚举
    //由于58的负载电容配置是非线性的所以需要转化一下才可使用
    int32_t cur_step = (current_enum >= 8) ? (current_enum - 8) : (current_enum + 4);//例:2pf->挡位0 实际 寄存器值 0x08-> 8-8=0
    int32_t tgt_step = cur_step + delta_steps;

    // 边界限幅 (0 ~ 11 档)
    if (tgt_step > 11) tgt_step = 11;
    if (tgt_step < 0)  tgt_step = 0;

    // 转换为 HSECapTypeDef 枚举 //例:2pf->挡位0 实际 寄存器值 0x08-> 0+8=8
    uint8_t target_enum = (tgt_step < 4) ? (tgt_step + 8) : (tgt_step - 4);
    
    PRINT("Current enum: %d | Delta Steps: %+d | Target Enum: %d\n", 
          current_enum, delta_steps, target_enum);

    // 5. 写入寄存器
    if (target_enum != current_enum) {
        PRINT("Change Enum: %d -> %d\n", current_enum, target_enum);
    
        HSECFG_Capacitance(target_enum);
        PRINT("After Adjustment Load Cap: %d pF\n",get_curr_cap_load());
    }

    PRINT("===== Cap Adjustment End =====\n");

}

frequency_calibration.h

#ifndef FREQUENCY_CALIBRATION_H
#define FREQUENCY_CALIBRATION_H


#ifdef __cplusplus
extern "C" {
#endif

#include "CONFIG.h"
#include "HAL.h"

void Adjust_Cap_LOAD (int32_t frequency);


#ifdef __cplusplus
}
#endif

#endif

以上就是频偏调整的方法与思路。

posted @ 2026-09-20 15:07  debugdabiaoge  阅读(0)  评论(0)    收藏  举报