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个状态:
- 状态00:A=0, B=0
- 状态01:A=0, B=1
- 状态11:A=1, B=1
- 状态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会反馈位置信息给正交信号生成模块。这个反馈用来处理Z相——每转一圈,位置计数归零一次,同时输出一个Z脉冲。
3.5 仿真验证与调试
模型建好了,怎么验证对不对?我一般做三步检查:
- 静态检查:给一个固定转速,看A、B波形是不是标准的正交方波。用Scope看,A和B的上升沿应该错开1/4周期。
- 动态检查:给一个斜坡转速信号,看脉冲频率是不是跟着线性变化。转速从0升到3000 rpm,脉冲频率应该从0升到51.2 kHz(1024线×3000/60)。
- 方向检查:给一个正转再反转的指令,看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线,这个模型一直没大改过。好的模型设计,就是能让你“一次建模,到处复用”。