基本原理
增量式光电编码器主要由 发光二极管、码盘以及码盘背面的光传感器 三个部分组成,多用于测量旋转物体的角位移、角速度及转动方向。
码盘
码盘安装在旋转轴上,上面均匀地排列着透光和不透光的扇形区域。当码盘转动时,不透光的部分能够挡住光线,而透光区则允许光线透过,那么码盘背面的光传感器就会周期性地收到光信号,从而输出一列方波
码盘转动一周时,光传感器输出的脉冲个数是固定的,那么通过 检测一定时间内收到的脉冲个数 ,就可以知道在这段时间内码盘转动了多少圈,进而换算为速度。例如,一个码盘转动一周时会输出 100 个脉冲,在 0.1s 内我们收到了 500 个脉冲,这意味着 0.1s 内码盘转动了 5 周,即码盘的转速为
ω = 5 0.1 = 50 r / s \omega=\frac{5}{0.1}=50r/s
ω = 0 . 1 5 = 5 0 r / s
ABZ 相
如果编码器只输出一列方波(记为 A 相),则无论是正转还是反转,都会产生同样的方波,无法判断旋转方向。上面我们说过,码盘上均匀地刻着透光和不透光的扇形区域,我们在扇形区域内侧再均匀地刻上一圈透光的扇形区域,不同的是,外圈和内圈的透光区域是交错的
通过观察上图中的码盘我们可以得到如下结论
当外圈处于透光区域时,内圈对应的一半为不透光区域,一半为透光区域
当外圈处于不透光区域时,内圈对应的一半为透光区域,一半为不透光区域
我们在内外扇形区域各安装上一套发光二极管、码盘以及光传感器,那么当码盘转动时,编码器就会 输出两列相位差为 90° 方波 (习惯称之为 A、B 相 )
码盘沿不同方向旋转时 A、B 相输出如下,正转和反转输出波形具有不同特征
通过判断 B 处于上升沿时 A 的电平状态,我们就可以知道码盘旋转的方向了
当码盘正转时,在 B 的上升沿,方波 A 恒为高电平
当码盘反转时,在 B 的上升沿,方波 A 恒为低电平
注意
正/反转是相对而言的,重点在于区分不同旋转方向时的波形特征
通过观察 A 上升沿时 B 的电平亦能判旋转方向,参考下文代码实现部分
通过 A/B 相能够计算旋转的速度和方向,但无法判断当前的绝对旋转位置,因为无论从哪个起止位置开始旋转,输出的波形完全相同。若系统运行过程中因电气干扰、高速丢步等原因丢失了若干脉冲,所产生的位置计数误差将持续累积,无法自行修正。Z 相则用于提供绝对参考原点 ,增量式编码器仅在固定的机械位置输出脉冲,系统可每隔一段时间利用 Z 脉冲复位或修正计数值,避免误差持续累积
实际应用中各厂家编码器输出波形可能有所不同,但其 ABZ 相核心逻辑是一样的
速度计算
假如编码器码盘旋转一周 A/B 相输出的脉冲数目为 N,在时间 T 内统计到的有效脉冲数目为 S(正转脉冲数 + 1,反转脉冲数 - 1),小车轮子的直径为 D,那么小车的速度换算公式如下
v = π S D N T v=\frac{\pi SD}{NT}
v = N T π S D
笔者使用的编码器码盘旋转一周输出的脉冲个数为 90,小车轮子的直径为 75mm,假如 1s 内统计得到的有效脉冲数目为 500,代入上式计算小车此时的速度为
v = π ∗ 500 ∗ 0.075 90 ∗ 1 ≈ 1.31 m / s v=\frac{\pi *500*0.075}{90*1} \approx 1.31m/s
v = 9 0 ∗ 1 π ∗ 5 0 0 ∗ 0 . 0 7 5 ≈ 1 . 3 1 m / s
代码实现
显然,速度计算的关键在于 统计一定时间内的脉冲数目 。本文基于 STM32 和 FPGA 开发驱动,读者了解增量编码器原理后,移植到其它平台应该难度不大
STM32
STM32 在工业和学术界的应用非常广泛,这里提供两种思路
利用中断检测 B 的上升沿,触发中断时判断 A 的电平,来决定计数值加减
将定时器设置为编码器模式,直接读取计数值和方向
基于中断的实现
打开 CubeMX,设置相关管脚,我这里使用的是 PC2 和 PC3 来接 A、B 相
为方便观察调试,这里启用 USART 串口
打开定时器 2,定时器响应时通过 USART 串口把计数值打印出来
设置定时 200ms
导入 Cube 工程,定义相关变量
1 2 3 4 int encoder_count = 0 ; char msg[64 ]; float speed = 0 ; int flag = 0 ;
重写外部中断回调函数
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 void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_2) { if (flag == 1 ) { encoder_count = 0 ; } else { if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_3) == 0 ) { encoder_count++; } else { encoder_count--; } } } }
定时器 2 响应后,通过 USART 串口向上位机发送数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { if (flag == 0 ) { speed = (float )encoder_count * 3.14 * 0.14 / (90 * 0.2 )*100 /34 ; sprintf (msg, "speed:%dcm/s\r\n" ,(int )speed); HAL_UART_Transmit_IT(&huart1, (uint8_t *)msg, sizeof (msg)); flag = 1 ; } else { flag = 0 ; } } }
在主函数中打开定时器 2
1 HAL_TIM_Base_Start_IT(&htim2);
测试效果如图
基于定时器编码器模式的实现
设置 TIM4 的 Combined Channels 为 Encoder Mode(编码器模式)
配置编码模式
Prescaler:分频系数
Counter Mode:计数模式,设置为 UP 时码盘正转计数值增加,反转计数值减小
Counter Period:编码器计数最大值,一般设置为 65535 以防止溢出
Encoder Mode:计数模式,编码器计数有三种模式可选
TI1
只在上升沿计数 ,例如在一定时间内 A/B 产生了 100 个脉冲,那么编码器计数值为 200(A、B 产生脉冲数相等)。由于分频系数 Prescaler 的存在,实际调用函数得到的计数值为
200 P r e s c a l e r + 1 \frac{200}{Prescaler+1}
P r e s c a l e r + 1 2 0 0
TI2
只在下降沿计数 ,计数值与 TI1 相等
TI1 and TI2
在上升沿、下降沿都计时 ,计数值为 TI1/TI2 的两倍,显然该模式具有更高的分辨率
导入 CubeMx 工程,定义相关变量
1 2 3 uint8_t encoder_count; uint8_t msg[64 ]; uint8_t encoder_direction;
打开定时器 4
1 HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
读取相关值并通过 OLED 显示出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 while (1 ){ encoder_count = __HAL_TIM_GET_COUNTER(&htim4); encoder_direction = __HAL_TIM_IS_TIM_COUNTING_DOWN(&htim4); sprintf ((char *)msg, "fre:%4d dir:%d" , encoder_count*2 , encoder_direction); OLED_Clear(); OLED_ShowString(0 ,0 ,msg); __HAL_TIM_SET_COUNTER(&htim4,0 ); HAL_Delay(500 ); }
这里用函数发生器模拟 AB 两列 50Hz、相位相差 90° 的方波,编码器计数时采用 T1 and T2 模式,分频系数为 3,所以理论上 1s 内采集到的脉冲数目为
n = 50 ∗ 4 3 + 1 = 50 n=\frac{50*4}{3+1}=50
n = 3 + 1 5 0 ∗ 4 = 5 0
代码中每 500ms 读取一次编码器计数值,那么乘以 2 才是方波的真实频率
1 sprintf ((char *)msg, "fre:%4d dir:%d" , encoder_count*2 , encoder_direction);
FPGA
嵌入式平台要检测高频 ABZ 脉冲还是比较困难的,相比之下 FPGA 的优势就比较明显,驱动设计思路与前文一致
计数逻辑
本文所设计的编码器模块支持多路 ABZ 信号输入
首先同步 ABZ 信号至当前时钟域,同步寄存器级数由参数 SYNC_STAGES 控制,建议 ≥ 4
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 30 31 32 33 wire [CHANNEL_NUM-1 :0 ] A_sync, B_sync, Z_sync;(* ASYNC_REG = "TRUE" *)reg [ CHANNEL_NUM-1 :0 ] A_sync_r [SYNC_STAGES-1 :0 ]; (* ASYNC_REG = "TRUE" *)reg [ CHANNEL_NUM-1 :0 ] B_sync_r [SYNC_STAGES-1 :0 ]; (* ASYNC_REG = "TRUE" *)reg [ CHANNEL_NUM-1 :0 ] Z_sync_r [SYNC_STAGES-1 :0 ]; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin A_sync_r[0 ] <= 'b0 ; B_sync_r[0 ] <= 'b0 ; Z_sync_r[0 ] <= 'b0 ; end else begin A_sync_r[0 ] <= A; B_sync_r[0 ] <= B; Z_sync_r[0 ] <= Z; end end generate for (genvar stage = 1 ; stage < SYNC_STAGES; stage = stage + 1 ) begin always @(posedge clk or negedge rst_n) begin if (!rst_n) begin A_sync_r[stage] <= 'b0 ; B_sync_r[stage] <= 'b0 ; Z_sync_r[stage] <= 'b0 ; end else begin A_sync_r[stage] <= A_sync_r[stage-1 ]; B_sync_r[stage] <= B_sync_r[stage-1 ]; Z_sync_r[stage] <= Z_sync_r[stage-1 ]; end end end endgenerate
然后检测 ABZ 信号边沿,用于支持不同的计数模式,本设计支持 3 种计数模式(未使用 Z 相)
x1:在 A 相上升沿计数
x2:在 A 相上升沿、下降沿计数,相同旋转下计数值为 x1 模式的 2 倍
x4:在 A 相、B 相上升沿、下降沿计数,相同旋转下计数值为 x1 模式的 4 倍
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 30 31 32 33 assign A_sync = A_sync_r[SYNC_STAGES-1 ];assign B_sync = B_sync_r[SYNC_STAGES-1 ];assign Z_sync = Z_sync_r[SYNC_STAGES-1 ];wire [CHANNEL_NUM-1 :0 ] A_sync_rising_edge, A_sync_falling_edge;wire [CHANNEL_NUM-1 :0 ] B_sync_rising_edge, B_sync_falling_edge;wire [CHANNEL_NUM-1 :0 ] Z_sync_rising_edge, Z_sync_falling_edge;reg [CHANNEL_NUM-1 :0 ] A_sync_dly, B_sync_dly, Z_sync_dly;always @(posedge clk or negedge rst_n) begin if (!rst_n) begin A_sync_dly <= 'b0 ; B_sync_dly <= 'b0 ; Z_sync_dly <= 'b0 ; end else begin A_sync_dly <= A_sync; B_sync_dly <= B_sync; Z_sync_dly <= Z_sync; end end generate for (genvar ch = 0 ; ch < CHANNEL_NUM; ch = ch + 1 ) begin assign A_sync_rising_edge[ch] = !A_sync_dly[ch] && A_sync[ch]; assign A_sync_falling_edge[ch] = A_sync_dly[ch] && !A_sync[ch]; assign B_sync_rising_edge[ch] = !B_sync_dly[ch] && B_sync[ch]; assign B_sync_falling_edge[ch] = B_sync_dly[ch] && !B_sync[ch]; assign Z_sync_rising_edge[ch] = !Z_sync_dly[ch] && Z_sync[ch]; assign Z_sync_falling_edge[ch] = Z_sync_dly[ch] && !Z_sync[ch]; end endgenerate
根据输入端口 mode 配置的工作模式计数,mode 与计数模式对应关系如下
mode[1:0]
计数模式
2’b00
x1
2’b01
x2
2’b10
x3
2’b11
None
不同计数模式的计数器加减规则如下
计数模式
A
B
计数值
x1
上升沿
0
+1
上升沿
1
-1
x2
上升沿
0
+1
上升沿
1
-1
下降沿
0
-1
下降沿
1
+1
x4
上升沿
0
+1
上升沿
1
-1
下降沿
0
-1
下降沿
1
+1
0
上升沿
-1
1
上升沿
+1
0
下降沿
+1
1
下降沿
-1
上述计数规则对应代码如下
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 reg signed [COUNT_WIDTH-1 :0 ] cnt_r[CHANNEL_NUM-1 :0 ];generate for (genvar ch = 0 ; ch < CHANNEL_NUM; ch = ch + 1 ) begin always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_r[ch] <= 'b0 ; end else if (clear_rising_edge) begin cnt_r[ch] <= 'b0 ; end else begin case (mode_sync) COUNT_MODE_X1: case ({ A_sync_rising_edge[ch], B_sync[ch] }) 'b10 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b11 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; default : cnt_r[ch] <= cnt_r[ch]; endcase COUNT_MODE_X2: case ({ A_sync_rising_edge[ch], A_sync_falling_edge[ch], B_sync[ch] }) 'b100 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b101 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b010 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b011 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; default : cnt_r[ch] <= cnt_r[ch]; endcase COUNT_MODE_X4: case ({ A_sync_rising_edge[ch], A_sync_falling_edge[ch], B_sync_rising_edge[ch], B_sync_falling_edge[ch], A_sync[ch], B_sync[ch] }) 'b100010 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b100011 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b010000 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b010001 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b001001 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b001011 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b000100 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b000110 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; default : cnt_r[ch] <= cnt_r[ch]; endcase default : cnt_r[ch] <= cnt_r[ch]; endcase end end end endgenerate
由于外部模块读取输出计数值过程中该模块仍在持续计数,可能导致读取的不同通道计数值不是在同一时间采集的,因此增加 snapshot 信号缓存各通道计数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 reg snapshot_sync_dly;reg signed [COUNT_WIDTH-1 :0 ] cnt_snapshot_r[CHANNEL_NUM-1 :0 ];wire snapshot_rising_edge = !snapshot_sync_dly && snapshot_sync;always @(posedge clk or negedge rst_n) begin if (!rst_n) begin snapshot_sync_dly <= 'b0 ; end else begin snapshot_sync_dly <= snapshot_sync; end end generate for (genvar ch = 0 ; ch < CHANNEL_NUM; ch = ch + 1 ) begin always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_snapshot_r[ch] <= 'b0 ; end else if (snapshot_rising_edge) begin cnt_snapshot_r[ch] <= cnt_r[ch]; end end end endgenerate
多个 ABZ 输入通道(0~CHANNEL_NUM-1)计数值可通过 MUX 输出,以减少输出端口数量
1 2 3 4 5 6 7 always @(posedge clk or negedge rst_n) begin if (!rst_n) begin count <= 'b0 ; end else begin count <= cnt_snapshot_r[channel_id_sync]; end end
完整代码
相关参数和端口定义如下
参数
备注
SYNC_STAGES
将 ABZ 和控制信号同步至 clk 时钟域的寄存器级数
CHANNEL_NUM
输入 A/B/Z 通道数量
COUNT_WIDTH
计数器宽度(有符号数)
CHANNEL_ID_WIDTH
通道选择输入端口位宽 各通道计数值通过 MUX 输出
端口
方向
位宽
同步/异步
备注
clk
in
1
*
输入时钟 x4 模式下输入时钟频率应高于 ABZ 脉冲频率的 8 倍
rst_n
in
1
async
复位(低电平/下降沿有效)
clear
in
1
async
清零(检测到上升沿时清空各通道计数值)
snapshot
in
1
async
快照(检测到上升沿时将当前计数值同步至缓存)
mode
in
2
async
计数模式(00: x1, 01: x2, 10: x4)
channel_id
in
CHANNEL_ID_WIDTH
async
输出通道 ID 计数值输出 MUX 选择信号
A
in
CHANNEL_NUM
async
A 相
B
in
CHANNEL_NUM
async
B 相
Z
in
CHANNEL_NUM
async
Z 相
count
out
COUNT_WIDTH
sync
计数值输出
笔者使用 AXI-GPIO 控制和读取该编码器模块,其 AXI 时钟与编码器采样时钟不同,因此增加了一些同步操作
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 module abz_encoder #( parameter SYNC_STAGES = 4 , parameter CHANNEL_NUM = 4 , parameter COUNT_WIDTH = 64 , parameter CHANNEL_ID_WIDTH = 4 ) ( input clk, input rst_n, input clear, input snapshot, input [ 1 :0 ] mode, input [CHANNEL_ID_WIDTH-1 :0 ] channel_id, input [ CHANNEL_NUM-1 :0 ] A, input [ CHANNEL_NUM-1 :0 ] B, input [ CHANNEL_NUM-1 :0 ] Z, output reg signed [ COUNT_WIDTH-1 :0 ] count ); localparam COUNT_MODE_X1 = 2'b00 , COUNT_MODE_X2 = 2'b01 , COUNT_MODE_X4 = 2'b10 ; wire [CHANNEL_NUM-1 :0 ] A_sync, B_sync, Z_sync; wire [1 :0 ] mode_sync; wire [CHANNEL_ID_WIDTH-1 :0 ] channel_id_sync; wire clear_sync, snapshot_sync; (* ASYNC_REG = "TRUE" *)reg clear_sync_r [SYNC_STAGES-1 :0 ]; (* ASYNC_REG = "TRUE" *)reg [ 1 :0 ] mode_sync_r [SYNC_STAGES-1 :0 ]; (* ASYNC_REG = "TRUE" *)reg snapshot_sync_r [SYNC_STAGES-1 :0 ]; (* ASYNC_REG = "TRUE" *)reg [CHANNEL_ID_WIDTH-1 :0 ] channel_id_sync_r[SYNC_STAGES-1 :0 ]; (* ASYNC_REG = "TRUE" *)reg [ CHANNEL_NUM-1 :0 ] A_sync_r [SYNC_STAGES-1 :0 ]; (* ASYNC_REG = "TRUE" *)reg [ CHANNEL_NUM-1 :0 ] B_sync_r [SYNC_STAGES-1 :0 ]; (* ASYNC_REG = "TRUE" *)reg [ CHANNEL_NUM-1 :0 ] Z_sync_r [SYNC_STAGES-1 :0 ]; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin A_sync_r[0 ] <= 'b0 ; B_sync_r[0 ] <= 'b0 ; Z_sync_r[0 ] <= 'b0 ; clear_sync_r[0 ] <= 'b0 ; mode_sync_r[0 ] <= 'b0 ; snapshot_sync_r[0 ] <= 'b0 ; channel_id_sync_r[0 ] <= 'b0 ; end else begin A_sync_r[0 ] <= A; B_sync_r[0 ] <= B; Z_sync_r[0 ] <= Z; clear_sync_r[0 ] <= clear; mode_sync_r[0 ] <= mode; snapshot_sync_r[0 ] <= snapshot; channel_id_sync_r[0 ] <= channel_id; end end generate for (genvar stage = 1 ; stage < SYNC_STAGES; stage = stage + 1 ) begin always @(posedge clk or negedge rst_n) begin if (!rst_n) begin A_sync_r[stage] <= 'b0 ; B_sync_r[stage] <= 'b0 ; Z_sync_r[stage] <= 'b0 ; clear_sync_r[stage] <= 'b0 ; mode_sync_r[stage] <= 'b0 ; snapshot_sync_r[stage] <= 'b0 ; channel_id_sync_r[stage] <= 'b0 ; end else begin A_sync_r[stage] <= A_sync_r[stage-1 ]; B_sync_r[stage] <= B_sync_r[stage-1 ]; Z_sync_r[stage] <= Z_sync_r[stage-1 ]; clear_sync_r[stage] <= clear_sync_r[stage-1 ]; mode_sync_r[stage] <= mode_sync_r[stage-1 ]; snapshot_sync_r[stage] <= snapshot_sync_r[stage-1 ]; channel_id_sync_r[stage] <= channel_id_sync_r[stage-1 ]; end end end endgenerate assign A_sync = A_sync_r[SYNC_STAGES-1 ]; assign B_sync = B_sync_r[SYNC_STAGES-1 ]; assign Z_sync = Z_sync_r[SYNC_STAGES-1 ]; assign clear_sync = clear_sync_r[SYNC_STAGES-1 ]; assign mode_sync = mode_sync_r[SYNC_STAGES-1 ]; assign snapshot_sync = snapshot_sync_r[SYNC_STAGES-1 ]; assign channel_id_sync = channel_id_sync_r[SYNC_STAGES-1 ]; wire [CHANNEL_NUM-1 :0 ] A_sync_rising_edge, A_sync_falling_edge; wire [CHANNEL_NUM-1 :0 ] B_sync_rising_edge, B_sync_falling_edge; wire [CHANNEL_NUM-1 :0 ] Z_sync_rising_edge, Z_sync_falling_edge; reg [CHANNEL_NUM-1 :0 ] A_sync_dly, B_sync_dly, Z_sync_dly; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin A_sync_dly <= 'b0 ; B_sync_dly <= 'b0 ; Z_sync_dly <= 'b0 ; end else begin A_sync_dly <= A_sync; B_sync_dly <= B_sync; Z_sync_dly <= Z_sync; end end generate for (genvar ch = 0 ; ch < CHANNEL_NUM; ch = ch + 1 ) begin assign A_sync_rising_edge[ch] = !A_sync_dly[ch] && A_sync[ch]; assign A_sync_falling_edge[ch] = A_sync_dly[ch] && !A_sync[ch]; assign B_sync_rising_edge[ch] = !B_sync_dly[ch] && B_sync[ch]; assign B_sync_falling_edge[ch] = B_sync_dly[ch] && !B_sync[ch]; assign Z_sync_rising_edge[ch] = !Z_sync_dly[ch] && Z_sync[ch]; assign Z_sync_falling_edge[ch] = Z_sync_dly[ch] && !Z_sync[ch]; end endgenerate reg clear_sync_dly; wire clear_rising_edge = !clear_sync_dly && clear_sync; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin clear_sync_dly <= 'b0 ; end else begin clear_sync_dly <= clear_sync; end end reg signed [COUNT_WIDTH-1 :0 ] cnt_r[CHANNEL_NUM-1 :0 ]; reg signed [COUNT_WIDTH-1 :0 ] cnt_abs_r[CHANNEL_NUM-1 :0 ]; generate for (genvar ch = 0 ; ch < CHANNEL_NUM; ch = ch + 1 ) begin always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_r[ch] <= 'b0 ; end else if (clear_rising_edge) begin cnt_r[ch] <= 'b0 ; end else begin case (mode_sync) COUNT_MODE_X1: case ({ A_sync_rising_edge[ch], B_sync[ch] }) 'b10 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b11 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; default : cnt_r[ch] <= cnt_r[ch]; endcase COUNT_MODE_X2: case ({ A_sync_rising_edge[ch], A_sync_falling_edge[ch], B_sync[ch] }) 'b100 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b101 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b010 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b011 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; default : cnt_r[ch] <= cnt_r[ch]; endcase COUNT_MODE_X4: case ({ A_sync_rising_edge[ch], A_sync_falling_edge[ch], B_sync_rising_edge[ch], B_sync_falling_edge[ch], A_sync[ch], B_sync[ch] }) 'b100010 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b100011 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b010000 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b010001 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b001001 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; 'b001011 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b000100 : cnt_r[ch] <= cnt_r[ch] + 1'b1 ; 'b000110 : cnt_r[ch] <= cnt_r[ch] - 1'b1 ; default : cnt_r[ch] <= cnt_r[ch]; endcase default : cnt_r[ch] <= cnt_r[ch]; endcase end end end endgenerate generate for (genvar ch = 0 ; ch < CHANNEL_NUM; ch = ch + 1 ) begin always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_abs_r[ch] <= 'b0 ; end else begin cnt_abs_r[ch] <= cnt_r[ch] > 0 ? cnt_r[ch] : -cnt_r[ch]; end end end endgenerate reg snapshot_sync_dly; reg signed [COUNT_WIDTH-1 :0 ] cnt_snapshot_r[CHANNEL_NUM-1 :0 ]; wire snapshot_rising_edge = !snapshot_sync_dly && snapshot_sync; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin snapshot_sync_dly <= 'b0 ; end else begin snapshot_sync_dly <= snapshot_sync; end end generate for (genvar ch = 0 ; ch < CHANNEL_NUM; ch = ch + 1 ) begin always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_snapshot_r[ch] <= 'b0 ; end else if (snapshot_rising_edge) begin cnt_snapshot_r[ch] <= cnt_r[ch]; end end end endgenerate always @(posedge clk or negedge rst_n) begin if (!rst_n) begin count <= 'b0 ; end else begin count <= cnt_snapshot_r[channel_id_sync]; end end endmodule
实际使用时最好添加时序约束,假设 ABZ 脉冲最大频率为 25MHz,abz_encoder 输入时钟信号 clk_200M 频率为 200MHz,rst_n/mode/clear/snapshot 等控制信号工作在时钟域 FCLK_CLK1 下,则时序约束如下
1 2 3 4 5 6 create_clock -period 40.000 -name clk_enc_a [get_ports {A[*]}] create_clock -period 40.000 -name clk_enc_b [get_ports {B[*]}] set_clock_groups -asynchronous -group [get_clocks {clk_enc_a clk_enc_b}] \ -group [get_clocks -of_objects [get_nets bd_system_i/clk_wiz_0/clk_200M]] \ -group [get_clocks -of_objects [get_nets bd_system_i/processing_system7_0/FCLK_CLK1]]
实际使用时应根据具体设计修改
使用流程
上电后首先通过 rst_n 复位模块
设置计数模式
在 snapshot 端口施加上升沿信号,将当前计数值暂存至输出寄存器
通过控制 channel_id 读取不同通道计数值
后续读取时再次向 snapshot 端口施加上升沿信号即可
仿真
输入 2 个通道的 ABZ 信号,测试 3 种计数模式、snapshot、clear 等功能,相关代码及结果如下
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 `timescale 1ns / 1ps module abz_encoder_tb; localparam SYNC_STAGES = 5 ; localparam CHANNEL_NUM = 2 ; localparam COUNT_WIDTH = 32 ; localparam CHANNEL_ID_WIDTH = $clog2 (CHANNEL_NUM); localparam CLK_PERIOD = 2 ; localparam CLK_PERIOD_HALF = CLK_PERIOD / 2 ; localparam CLK_SIGNAL_PERIOD = CLK_PERIOD * 4 ; localparam CLK_SIGNAL_PERIOD_HALF = CLK_SIGNAL_PERIOD / 2 ; reg clk; reg rst_n; reg clear; reg snapshot; reg [ 1 :0 ] mode; reg [CHANNEL_ID_WIDTH-1 :0 ] channel_id; reg [ CHANNEL_NUM-1 :0 ] A; reg [ CHANNEL_NUM-1 :0 ] B; reg [ CHANNEL_NUM-1 :0 ] Z; wire [ COUNT_WIDTH-1 :0 ] count; abz_encoder #( .SYNC_STAGES (SYNC_STAGES), .CHANNEL_NUM (CHANNEL_NUM), .COUNT_WIDTH (COUNT_WIDTH), .CHANNEL_ID_WIDTH (CHANNEL_ID_WIDTH) ) abz_encoder_inst ( .clk (clk), .rst_n (rst_n), .clear (clear), .snapshot (snapshot), .mode (mode), .channel_id (channel_id), .A (A), .B (B), .Z (Z), .count (count) ); initial begin clk <= 1 ; forever #CLK_PERIOD_HALF clk <= ~clk; end initial begin A[0 ] = 0 ; forever begin #CLK_SIGNAL_PERIOD_HALF A[0 ] <= ~A[0 ]; end end initial begin B[0 ] <= 1 ; #(CLK_SIGNAL_PERIOD / 4) B[0 ] <= 0 ; forever begin #CLK_SIGNAL_PERIOD_HALF B[0 ] <= ~B[0 ]; end end initial begin A[1 ] <= 1 ; #(CLK_SIGNAL_PERIOD / 4) A[1 ] <= 0 ; forever begin #CLK_SIGNAL_PERIOD_HALF A[1 ] <= ~A[1 ]; end end initial begin B[1 ] = 0 ; forever begin #CLK_SIGNAL_PERIOD_HALF B[1 ] <= ~B[1 ]; end end initial begin Z[0 ] <= 0 ; Z[1 ] <= 0 ; rst_n <= 0 ; mode <= 'b00 ; clear <= 'b0 ; channel_id <= 'b0 ; snapshot <= 0 ; #10 rst_n <= 1 ; #1000 snapshot <= 1 ; #10 snapshot <= 0 ; #1000 snapshot <= 1 ; #10 snapshot <= 0 ; #100 channel_id <= 1 ; #100 channel_id <= 2 ; #100 channel_id <= 3 ; #100 clear <= 1 ; rst_n <= 0 ; #10 mode <= 2'b01 ; #10 rst_n <= 1 ; #1000 snapshot <= 1 ; #10 snapshot <= 0 ; rst_n <= 0 ; #10 mode <= 2'b10 ; #10 rst_n <= 1 ; #1000 snapshot <= 1 ; #10 snapshot <= 0 ; channel_id <= 0 ; #100 snapshot <= 1 ; #10 snapshot <= 0 ; Z[0 ] <= 1 ; #100 Z[0 ] <= 0 ; #100 $stop ; end endmodule
通道 0 中 A 相上升沿时 B 相为低电平(正转),通道 1 中 A 相上升沿时 B 相为高电平(反转),计数模式为 x1 时两个通道计数值分别 +1/-1
检测到 snapshot 上升沿时将计数值 0x000000fb、0xffffff05 缓存至输出寄存器,输出 MUX 选择信号 channel_id 为 0,因此输出通道 0 计数值 0x000000fb
输出 MUX 选择信号变为 1 时,输出通道 1 计数值 0xffffff05
mode 为 2’b01 时计数模式为 x1,在 A 相的上升沿/下降沿均计数
mode 为 2’b10 时计数模式为 x4,在 A 相、B 相的上升沿/下降沿均计数