一、工程简介
1、 本工程实现的功能:用FPGA通过两个按键控制步进电机的启动和停止,硬件包括:黑金开发板、步进电机和Vince步进电机驱动器和电脑。
2、FPGA通过控制电机驱动器来控制步进电机的旋转,通过控制脉冲个数来控制电机角位移量,控制发送脉冲频率来控制电机的速度和加速度。
3、FPGA与Vince步进电机驱动器之间的通讯协议采用RS232电平标准的串口通信(UART),FPGA发送给驱动器的命令是32位数据或64位的,因此串口模块还实现了32转8位数据的功能。
4.我的电机用在下图中:
二、顶层设计
motor顶层包括3个子模块:2个按键模块(key_ctrl)和一个电机模块(motor_ctrl)。顶层如图一所示。
这块模块要特别注意:motor模块复位端口rst_n的接线,如果不接,那么key_ctrl模块的rst_n必须接高电平,否则电机无法启动,原因是直接接motor模块的外部rst_n,电平不稳定,会导致这个key_ctrl模块一直处于复位状态,可用SignalTap II抓取一下rst_n信号就一目了然了。
module motor (
input wire clk,
input wire rst_n,
input wire key1,
input wire key2,
output wire TXD
);
wire key_flag1;
wire key_flag2; // key_flag2 = 0 in general
//================ key1 control motor stop =============================
key_ctrl key_ctrl_inst1 (
.clk (clk ),
.rst_n (1'b1 ),
.key (key1 ),
.key_flag (key_flag1 )
);
//=========================================================================
//================ key2 control motor start/reset =========================
key_ctrl key_ctrl_inst2 (
.clk (clk ),
.rst_n (1'b1 ), // if rst_n connected to port rst_n,rst_n must be allocated pins.
.key (key2 ),
.key_flag (key_flag2 )
);
//========================================================================
motor_ctrl motor_ctrl_inst(
.clk (clk ),
.rst_n (~ key_flag2),
.motor_end (key_flag1 ),
.TXD (TXD )
);
endmodule
三、架构设计
1、key_ctrl模块
该模块分为两个部分:按键消抖和边缘检测。其中中间信号key_wave的脉宽由按键按下的时间长短来决定,key_ctrl模块顶层如下:
module key_ctrl (
input wire clk,
input wire rst_n,
input wire key,
output wire key_flag
);
wire key_wave;
key_filter key_filter_inst(
.clk (clk ),
.rst_n (rst_n ),
.key (key ),
.key_wave (key_wave )
);
edge_check edge_check_inst(
.clk (clk ),
.rst_n (rst_n ),
.key (key_wave ),
.flag_pos (),
.flag_neg (key_flag )
);
endmodule
(1)按键消抖模块(key_filter)
按键按下的标准:按下时间超过5ms。按键的状态可以分为4个:松开(KEY_OFF)、闭合抖动(ON_SHAKE)、闭合(KEY_ON)、松开抖动(OFF_SHAKE)。key_filter模块代码如下:
module key_filter (
input wire clk,
input wire key,
input wire rst_n,
output reg key_wave
);
parameter T_5ms = 250_000;
localparam KEY_OFF = 4'b0001;
localparam ON_SHAKE = 4'b0010;
localparam KEY_ON = 4'b0100;
localparam OFF_SHAKE = 4'b1000;
reg [17:0] cnt_5ms;
reg [3:0] c_state;
reg [3:0] n_state;
reg [1:0] buf_key;
wire skey;
always @ (posedge clk) buf_key <= {buf_key[0],key};
assign skey = buf_key[1];
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 0)
c_state <= KEY_OFF;
else
c_state <= n_state;
end
always @ (*) begin
case (c_state)
KEY_OFF : if (skey)
n_state = KEY_OFF;
else
n_state = ON_SHAKE;
ON_SHAKE : if (skey)
n_state = KEY_OFF;
else
if (cnt_5ms < T_5ms - 1)
n_state = ON_SHAKE;
else
n_state = KEY_ON;
KEY_ON : if (skey)
n_state = OFF_SHAKE;
else
n_state = KEY_ON;
OFF_SHAKE : if (!skey)
n_state = KEY_ON;
else
if (cnt_5ms < T_5ms - 1)
n_state = OFF_SHAKE;
else
n_state = KEY_OFF;
default : n_state = KEY_OFF;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 0)
cnt_5ms <= 0;
else
case (c_state)
KEY_OFF : cnt_5ms <= 0;
ON_SHAKE : if (skey)
cnt_5ms <= 0;
else
if (cnt_5ms < T_5ms - 1)
cnt_5ms <= cnt_5ms + 1'b1;
else
cnt_5ms <= 0;
KEY_ON : cnt_5ms <= 0;
OFF_SHAKE : if (!skey)
cnt_5ms <= 0;
else
if (cnt_5ms < T_5ms - 1)
cnt_5ms <= cnt_5ms + 1'b1;
else
cnt_5ms <= 0;
endcase
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 0)
key_wave <= 1;
else
if (c_state == KEY_ON || c_state == OFF_SHAKE)
key_wave <= 0;
else
key_wave <= 1;
end
endmodule
(2)边沿检测模块(edge_check)
该模块可以检测一个外部信号的上升沿和下降沿。edge_check模块代码如下:
module edge_check (
input wire clk,
input wire rst_n,
input wire key,
output wire flag_pos,
output wire flag_neg
);
reg syn_reg;
reg check_reg;
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 0)
syn_reg <= 1;
else
syn_reg <= key;
end
always @ (posedge clk, negedge rst_n) begin
if (rst_n == 0)
check_reg <= 1;
else
check_reg <= syn_reg;
end
assign flag_neg = check_reg & (~syn_reg);
assign flag_pos = (~check_reg) & syn_reg;
endmodule
2、motor_ctrl模块
模块输入:clk(时钟)、rst_n(电机启动)、motor_end(电机停止)
模块输出:TXD(串口发送)
实现的功能:
上电复位,FPGA给Vince步进电机驱动器发送如下指令的16进制码值:握手指令dev\n(32’h6465760a)、电机使能ena\n(32’h656e610a)、旋转指令mov\n(32’h6d6f760a)和停止指令off(32’h6f66660a)。电机接收到上述指令,开始按照预设的参数(速度、加速度等)旋转,直到按键key1按下,motor_end信号被拉高,停止旋转。
tx_demo模块是32位数据转8位数据模块,因为电机的指令至少是32位的,所以将其拆分成4次通用的8位UART串口发送。
uart_tx模块是8位数据的串口发送模块,发送波特率为115200bps,每帧数据包括10位:起始位、停止位各一位,中间8位数据位。各模块的代码如下:
(1)motor_ctrl模块代码:
//=========== control motor rotate, rs232 communicaton protocols ================
module motor_ctrl(
input wire clk,
input wire rst_n,
input wire motor_end,
output wire TXD
);
reg [31:0] order; //232
reg [2:0] state; //232
reg rst_n_t; //232
wire txd_done; //232
tx_demo tx_demo_inst(
.clk (clk ),
.rst_n (rst_n_t ),
.order (order ),
.TXD (TXD ),
.txd_done (txd_done )
);
always @ ( posedge clk or negedge rst_n )
if(!rst_n)
begin
rst_n_t <= 0;
order <= 32'h0;
state <= 3'd0;
end
else
begin
case(state)
0:
begin
order <= 32'h6465760a; //dev= 646576(HEX) ,发送指令,指令是小写
if(txd_done) //换行"\n"的ASCII的十六进制表示是0a
begin
state <= 2'd1;
rst_n_t <= 0;
end
else
begin
state <= state;
rst_n_t <= 1;
end
end
1:
begin
order <= 32'h656e610a; //ena= 656E61(HEX)
if(txd_done)
begin
state <= 2'd2;
rst_n_t <= 0;
end
else
begin
state <= state;
rst_n_t <= 1;
end
end
2:
begin
order <= 32'h6d6f760a; //mov= 6d6f76(HEX)
if(txd_done && motor_end)
begin
state <= 2'd3;
rst_n_t <= 0;
end
else
begin
state <= state;
rst_n_t <= 1;
end
end
3:
begin
order <= 32'h6f66660a; //off= 6f6666(HEX)
if(txd_done)
begin
state <= 3'd4;
rst_n_t <= 0;
end
else
begin
state <= state;
rst_n_t <= 1;
end
end
// 3:
// begin
// order <= 32'h63666720; //cfg = 3666720(HEX) 注意空格
// if(txd_done)
// begin
// state <= 3'd4;
// rst_n_t <= 0;
// end
// else
// begin
// state <= state;
// rst_n_t <= 1;
// end
// end
//
// 4:
// begin
// order <= 32'h7370643d; //spd= = 7370643d(HEX)
// if(txd_done)
// begin
// state <= 3'd5;
// rst_n_t <= 0;
// end
// else
// begin
// state <= state;
// rst_n_t <= 1;
// end
// end
//
// 5:
// begin
// order <= 32'h3231300a; //210= 323130(HEX)
// if(txd_done)
// begin
// state <= 3'd6;
// rst_n_t <= 0;
// end
// else
// begin
// state <= state;
// rst_n_t <= 1;
// end
// end
default: rst_n_t <= 0;
endcase
end
endmodule
(2)tx_demo模块代码:
module tx_demo(
input wire clk,
input wire rst_n,
input wire [31:0] order,
output wire TXD,
output reg txd_done
);
reg icall;
reg [2:0] state;
reg [7:0] idata;
uart_tx uart_tx_inst(
.clk (clk ),
.rst_n (rst_n ),
.icall (icall ),
.idata (idata ),
.TXD (TXD ),
.odone (odone )
);
always @ (posedge clk, negedge rst_n)
begin
if (!rst_n)
begin
icall <= 0;
txd_done <= 0;
idata <= 0;
state <= 0;
end
else
case (state)
3'd0 : begin
if (odone)
begin
icall <= 1'b0;
state <= 3'd1;
end
else
begin
icall <= 1'b1;
idata <= order[31:24];
state <= state;
end
end
3'd1 : begin
if (odone)
begin
icall <= 1'b0;
state <= 3'd2;
end
else
begin
icall <= 1'b1;
idata <= order[23:16];
state <= state;
end
end
3'd2 : begin
if (odone)
begin
icall <= 1'b0;
state <= 3'd3;
end
else
begin
icall <= 1'b1;
idata <= order[15:8];
state <= state;
end
end
3'd3 : begin
if (odone)
begin
icall <= 1'b0;
state <= 3'd4;
end
else
begin
icall <= 1'b1;
idata <= order[7:0];
state <= state;
end
end
3'd4 : begin
txd_done <= 1'b1;
state <= state;
end
endcase
end
endmodule
(3)uart_tx模块代码:
module uart_tx(
input wire clk,
input wire rst_n,
input wire icall,
input wire [7:0] idata,
output reg TXD,
output reg odone
);
parameter BAUDRATE = 13'd434; // 50_000_000/115200 = 434;
// 50_000_000/9600 = 5208;
reg [12:0] cnt;
reg [3:0] state;
always @ (posedge clk, negedge rst_n)
begin
if (!rst_n)
begin
odone <= 0;
cnt <= 0;
TXD <= 1'b1;
state <= 0;
end
else if (icall)
case (state)
4'd0 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= 1'b0; // UART start bit
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd1;
end
end
4'd1 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= idata[0];
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd2;
end
end
4'd2 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= idata[1];
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd3;
end
end
4'd3 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= idata[2];
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd4;
end
end
4'd4 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= idata[3];
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd5;
end
end
4'd5 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= idata[4];
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd6;
end
end
4'd6 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= idata[5];
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd7;
end
end
4'd7 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= idata[6];
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd8;
end
end
4'd8 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= idata[7];
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd9;
end
end
4'd9 : begin
if (cnt < BAUDRATE - 1)
begin
cnt <= cnt + 1'b1;
TXD <= 1'b1; // UART stop bit
state <= state;
end
else
begin
cnt <= 0;
state <= 4'd10;
end
end
4'd10 : begin
odone <= 1'b1;
state <= 4'd11;
end
4'd11 : begin
odone <= 1'b0;
state <= 4'd0;
end
default : ;
endcase
end
endmodule
四、个人总结
本人花了相当长的时间在和这个Vince步进电机驱动器打交道,遇到的问题主要有以下几个:
(1)
刚开始想用Vince驱动器产品配套的软件设定电机运转参数,但是驱动器一直无法和PC机通讯成功,奇怪的是用相同的电路用FPGA确可以控制电机旋转,最后找出原因是电路中某处连线的焊锡不足,导致用PC机来驱动电机旋转时电压驱动能力不够,所以导致通讯不成功,用电烙铁加强焊点就可以了。
(2)
关于motor顶层模块复位rst_n的接线问题,把我折腾的快怀疑人生了,好在最后用SignalTap II 找出了原因,详情可看前文。
学FPGA得慢慢来啊,你今天的日积月累,会带来明天别人的望尘莫及。