3. 编码器信号建模:脉冲发生器、正交信号生成、Matlab Function实现

编码器信号建模,说白了就是给电机控制算法造一个“虚拟的传感器”。你想想看,在Simulink里跑仿真,总不能真接一个物理编码器吧?所以我们要自己生成A、B两路正交脉冲,还有Z相零位信号。

我个人习惯把编码器模型分成三层:脉冲发生器负责产生基础频率,正交信号生成负责把单路脉冲变成两路相差90°的方波,Matlab Function则用来处理一些逻辑判断和计数。这样分层,调试起来特别方便。

核心思路:编码器信号建模的本质,是用数字逻辑模拟机械旋转。你给一个速度指令,模型就输出对应频率的A、B脉冲,同时记录位置和方向。

3.1 脉冲发生器:从速度到频率

脉冲发生器是整个模型的“心脏”。它的输入是电机转速(rpm),输出是固定脉宽的方波信号。我在项目中遇到过一个问题:直接用Simulink的Pulse Generator模块,参数调起来很麻烦,尤其是转速变化时,频率要实时更新。

我的做法是用一个积分器+比较器结构:

% 伪代码逻辑
每步长累加:position = position + speed * dt
如果 position >= 1,输出一个脉冲,position = position - 1

这样,转速越高,累加越快,脉冲频率就越高。具体参数如下:

参数 说明 典型值
脉冲数/转 编码器线数 1024
最大转速 仿真范围上限 3000 rpm
脉冲宽度 占空比50% 半个周期

小技巧:我习惯把脉冲数/转设成4的倍数,这样后续做四倍频计数时,每个状态都能均匀分布。1024、2048、4096都是好数字。

3.2 正交信号生成:A相和B相

正交信号,说白了就是两路频率相同、相位差90°的方波。A相领先B相90°表示正转,B相领先A相90°表示反转。

生成方法其实很简单:把脉冲发生器的输出分成两路,一路直接输出(A相),另一路延迟90°相位(B相)。延迟怎么实现?用计数器+状态机。

我常用的状态机有4个状态:

  1. 状态00:A=0, B=0
  2. 状态01:A=0, B=1
  3. 状态11:A=1, B=1
  4. 状态10:A=1, B=0

正转时,状态按 00→01→11→10→00 循环。反转时,状态按 00→10→11→01→00 循环。每个状态持续的时间相等,正好是脉冲周期的1/4。

注意:状态切换时,A和B不能同时跳变。我曾经在调试时发现,如果状态机写得不好,A和B会在同一时刻翻转,导致计数器误判方向。一定要保证每次只改变一路信号。

3.3 Matlab Function实现:灵活的逻辑处理

Matlab Function是我最喜欢用的模块。它比Stateflow轻量,比纯Simulink模块灵活。编码器模型里,我主要用它做三件事:

  • 方向判断:根据A、B的边沿顺序,判断正反转
  • 位置计数:累加或累减脉冲数
  • Z相生成:每转一圈输出一个零位脉冲

下面是我常用的Matlab Function代码框架:

function [A, B, Z, count] = encoder_model(speed, reset)
% 编码器信号生成
% 输入:speed(转速,rpm),reset(复位信号)
% 输出:A, B(正交信号),Z(零位信号),count(位置计数)

persistent state phase_count position
if isempty(state)
    state = 0;      % 当前状态 0-3
    phase_count = 0; % 相位计数器
    position = 0;   % 绝对位置
end

% 参数
pulses_per_rev = 1024;  % 编码器线数
dt = 1e-6;              % 仿真步长

% 速度转频率
freq = speed / 60 * pulses_per_rev;
delta = freq * dt;

% 相位累加
phase_count = phase_count + delta;
if phase_count >= 1
    phase_count = phase_count - 1;
    % 状态切换
    if speed >= 0
        state = mod(state + 1, 4);  % 正转
        position = position + 1;
    else
        state = mod(state - 1, 4);  % 反转
        position = position - 1;
    end
end

% 状态解码
switch state
    case 0
        A = 0; B = 0;
    case 1
        A = 0; B = 1;
    case 2
        A = 1; B = 1;
    case 3
        A = 1; B = 0;
end

% Z相:每转一圈输出一个脉冲
Z = 0;
if mod(position, pulses_per_rev) == 0 && position ~= 0
    Z = 1;
end

% 复位
if reset
    state = 0;
    phase_count = 0;
    position = 0;
end

count = position;
end

经验之谈:Matlab Function里用persistent变量,比用全局变量或Data Store Memory更安全。每个模块实例都有自己的persistent空间,不会互相干扰。我在一个项目里同时跑了4个编码器模型,就是用这个办法隔离的。

3.4 整体架构与信号流

把上面三部分拼起来,就构成了完整的编码器信号模型。我画了一张架构图,你看一眼就明白了:

编码器信号模型架构 转速输入 脉冲发生器 正交信号生成 Matlab Function A/B/Z + 位置 位置反馈 说明: 转速输入 → 脉冲发生器产生基础频率 → 正交信号生成产生A/B两路方波 → Matlab Function处理计数和Z相输出

从图里你能看到,信号流是单向的,但Matlab Function会反馈位置信息给正交信号生成模块。这个反馈用来处理Z相——每转一圈,位置计数归零一次,同时输出一个Z脉冲。

3.5 仿真验证与调试

模型建好了,怎么验证对不对?我一般做三步检查:

  1. 静态检查:给一个固定转速,看A、B波形是不是标准的正交方波。用Scope看,A和B的上升沿应该错开1/4周期。
  2. 动态检查:给一个斜坡转速信号,看脉冲频率是不是跟着线性变化。转速从0升到3000 rpm,脉冲频率应该从0升到51.2 kHz(1024线×3000/60)。
  3. 方向检查:给一个正转再反转的指令,看A、B的相位关系是不是反过来。正转时A领先B,反转时B领先A。

踩坑记录:我曾经在Matlab Function里忘了处理浮点误差。phase_count累加久了,会累积到1.0000001,导致if判断失效。后来我加了一个容差判断:if phase_count >= 0.999999,问题就解决了。浮点运算,你永远不能相信等号。

3.6 参数化与复用

为了让模型能复用,我把编码器线数、最大转速、步长都做成了参数。在Matlab Function里用coder.Constant声明,或者在模型里用Simulink.Parameter定义。

我个人习惯把参数放在一个结构体里:

% 在MATLAB工作区定义
encoder_params.pulses_per_rev = 1024;
encoder_params.max_speed = 3000;  % rpm
encoder_params.dt = 1e-6;         % 仿真步长

然后在Matlab Function里用coder.load加载。这样换一个编码器,只需要改参数结构体,模型本身不用动。

复用技巧:我把这个编码器模型做成了Library模块,拖到新项目里直接改参数就能用。三年来,从直流无刷电机到永磁同步电机,从100线到4096线,这个模型一直没大改过。好的模型设计,就是能让你“一次建模,到处复用”。