使用单片机的ADC来解码正余弦编码器

代码如下,使用方法:在单片机定时器中断函数中调用Decode_SinCos() 中断频率取决于编码器的精度以及运动速度,我测试的编码器精度为20um/周期,速度尚未测量(以后补充),定时器间隔为50us,时间间隔太大容易丢正余弦周期,间隔太小频繁进中断增加CPU负荷,运行Decode_SinCos()这个函数大约需要3us(cpu频率550MHz,32位单片机)

用手移动音圈电机从顶部快速移动到底部,正余弦周期数始终是从0到281没有丢失或增加,相位没有实时采集而丢失不会影响最终位置的计算。

 

// 用于保存解码后的结果
float sin_voltage = 0.0f;   // 正弦电压值,表示从ADC读取到的正弦信号的电压
float cos_voltage = 0.0f;   // 余弦电压值,表示从ADC读取到的余弦信号的电压
float amplitude = 0.0f;     // 幅值,表示正弦和余弦信号的强度
float phase = 0.0f;         // 相位,表示正弦和余弦信号的相位角

// ADC 参数配置
const float v_ref = 3.3f;              // ADC 参考电压,ADC采样的最大电压(通常是3.3V)
const float adc_resolution = 65536.0f; // ADC 分辨率(16 位),表示 ADC 采样值的范围

// 差分信号中心电压和幅值的计算
const float v_center = (1.08f + 2.16f) / 2.0f;   // 差分信号的中心电压 (1.62V),即正余弦信号的零点
const float v_amplitude = (2.16f - 1.08f) / 2.0f; // 差分信号的幅值 (0.54V),信号的幅度(从波谷到波峰的一半)

// 位置和相位计数
int32_t cycle_count = 0;    // 周期计数器,用于跟踪正弦余弦信号的周期数
float last_phase = 0.0f;    // 上次的相位值,用于检测周期跳变
const float cycle_length = 20.0f; // 周期对应长度 (单位: μm),表示一个周期内的位置变化
float absolute_position = 0.0f;   // 绝对位置 (单位: μm),表示电机的当前位置

/**
 * @brief 更新周期计数器,根据当前相位检测周期跳变。
 * 
 * @param current_phase 当前相位值(弧度制),表示正弦余弦信号的相位
 * 
 * @details 该函数用于检测正弦余弦信号的周期跳变,主要用于计算周期计数 `cycle_count`。当信号从正相位跳到负相位,或从负相位跳到正相位时,周期计数会增加或减少。
 */
void Update_Cycle_Count(float current_phase) {
    // 检查相位变化是否跨越了一个周期(即从正半周跳到负半周,或从负半周跳到正半周)
    if (last_phase > M_PI_2 && current_phase < -M_PI_2) {
        cycle_count++;  // 从正相位跳到负相位,周期增加
    } else if (last_phase < -M_PI_2 && current_phase > M_PI_2) {
        cycle_count--;  // 从负相位跳到正相位,周期减少
    }
    last_phase = current_phase; // 更新上次的相位值,为下一次相位变化检测做准备
}

/**
 * @brief 从ADC缓冲区解码正弦和余弦信号,并计算幅值和相位。
 * 
 * @details 该函数从ADC缓冲区中读取正弦和余弦信号的原始采样值,将其转换为电压,并进一步归一化处理。然后根据归一化后的值计算幅值和相位。最后,更新周期计数器并计算当前位置。
 */
void Decode_SinCos(void) {
    // 从 ADC 缓冲区提取正弦和余弦的原始采样值
    // adc_buffer[1] 存储正弦信号的差分输入数据
    // adc_buffer[2] 存储余弦信号的差分输入数据
    int32_t raw_sin = adc_buffer[1]; // 通道 4 数据(正弦差分输入)
    int32_t raw_cos = adc_buffer[2]; // 通道 5 数据(余弦差分输入)

    // 原始值转换为电压
    // 将 ADC 原始采样值转换为电压值,基于 ADC 分辨率和参考电压
    sin_voltage = ((float)raw_sin / adc_resolution) * v_ref;
    cos_voltage = ((float)raw_cos / adc_resolution) * v_ref;

    // 对信号进行归一化处理
    // 将电压值转换为相对中心电压和幅值的标准化值
    float sin_normalized = (sin_voltage - v_center) / v_amplitude;
    float cos_normalized = (cos_voltage - v_center) / v_amplitude;

    // 计算幅值(信号的强度)
    // 使用正弦和余弦信号的平方和计算幅值
    amplitude = sqrtf(sin_normalized * sin_normalized + cos_normalized * cos_normalized);

    // 计算相位(单位:弧度)
    // 使用反正切函数计算正余弦信号的相位值
    phase = atan2f(sin_normalized, cos_normalized);
    phase = fmodf(phase, 2.0f * M_PI);  // 将相位值限制在 0 到 2π 之间
    if (phase < 0) {
        phase += 2.0f * M_PI; // 如果相位为负,转换为正值
    }

    // 更新周期计数器
    // 将当前相位值传递给 Update_Cycle_Count 函数,用于检测周期跳变并更新周期计数
    Update_Cycle_Count(phase);

    // 计算周期内的位置 (单位: μm)
    // 根据当前的相位计算周期内的位置,范围从 0 到 cycle_length
    float position_in_cycle = (phase / (2.0f * M_PI)) * cycle_length;
    if (position_in_cycle < 0) {
        position_in_cycle += cycle_length; // 如果计算得到的周期内位置为负数,转换为正值
    }

    // 计算绝对位置 (单位: μm)
    // 通过周期计数器和当前位置计算电机的绝对位置,单位为微米(μm)
    absolute_position = cycle_count * cycle_length + position_in_cycle;
}
View Code

 

posted @ 2025-01-15 17:01  阿坦  阅读(260)  评论(0)    收藏  举报