【原创教程】自己做的直流无刷控制,经验分享,以及dspbuilder高级模块问题
[复制链接]
本帖最后由 golaced 于 2014-7-25 09:28 编辑
本人做的时间较短,如果内容有什么问题还请大家亲拍
http://v.youku.com/v_show/id_XNzQ0Mzc1ODQ0.html 贴视频手机录制效果不好
这是我平时抽空完成的直流无刷电机控制系统(实际只是完成了FPGA编程-0-),花了半个月的时间完成再次和大家分享下过程:
上图是开发板和驱动板还要无刷电机,电机参数如下
开发板是淘宝上大西瓜的l; 驱动板和电机是之前硕研DSP28335一套买的,后面由于课题选了FPGA没时间学DSP所以放下
现在没事又捡起来玩玩。。
上图为无刷直流控制系统
无刷电机相比有刷电机的最大区别就是用
电子 开关替代了内部的电刷。电刷所带来的是易损坏,难维修,还有内部碳粉沉积多之后给电机带来的堵转等一系列问题。曾经拆过很多损坏的有刷电机,80%以上的损坏原因是内部电刷断了。这也是我萌生这次这个方案的主要原因之一。任何事物都是有利有弊的。外围
电路 的简单和方便实用带来的是软件处理的复杂度。这就是电子开关带来的软件设计。同比无传感器换相也是增加了软件复杂度。
霍尔信号的电子传感器是依靠电磁感应原理利用霍尔
元件 的集成电路完成电子定子或者转子的测位的。
通过霍尔换相信号按照下表输出PWM就可以实现全速电机旋转
(注意图中的横栏是电角度,不是霍尔信号)
可调输出的verilog 代码:
反转
case(hall)
3'b101:
pwmreg <={1'b0,pwm_net,4'b0010}; //010010 1
3'b100:
pwmreg <={ 4'b0001,pwm_net,1'b0};//000110 2
3'b110:
pwmreg <= {3'b100,pwm_net,2'b00};//100100 2
3'b010:
pwmreg <= {pwm_net,5'b00001}; //100001 1
3'b011:
pwmreg <= {5'b00100,pwm_net}; //001001 2
3'b001:
pwmreg <= {2'b01,pwm_net,3'b000};//011000 2
default:
pwmreg <= 6'b000000;
endcase
end
正转
begin
//zheng
case(hall)
3'b010:
pwmreg <= {pwm_net,5'b00001}; //100001 1
3'b011:
pwmreg <= {5'b00100,pwm_net}; //001001 2
3'b001:
pwmreg <= {2'b01,pwm_net,3'b000};//011000 2
3'b101:
pwmreg <={1'b0,pwm_net,4'b0010}; //010010 1
3'b100:
pwmreg <={ 4'b0001,pwm_net,1'b0};//000110 2
3'b110 :
pwmreg <= {3'b100,pwm_net,2'b00};//100100 2
(hall【2:0】对应UVW pwm【5:0】对应 开关管143652_三个桥臂上下两mos管)
调制方式on-PWM的调制方式
控制直流电机调速的方法就是控制电压,电压越高则速度越高因此通过PWM的方法来控制输出的电压:
PWM_net脉宽越宽则输出电压就越高速度则越高,因此通过控制PWM_net就可以来实现开环调速,PWM产生方法通过比较器与计数值比较小于则为高表示管子开通(注意有些是负逻辑),目前采用的载波频率是10K。
verilog代码:(时钟50Mhz)
parameter divid_time=1;
parameter ts_test=2500;
reg [15:0]cnt;
reg clk_d;
always@(posedge clk ,posedge rst)
begin
if (rst)
begin
cnt<=0;
clk_d<=0;
end
else if (cnt==divid_time)
begin
cnt<=0;clk_d<=~clk_d;
end
else
cnt<=cnt+1;
end
reg [15:0]compose_test;
reg [31:0]compare;
always@(posedge clk)
compare<=(compose_test*ts_test)/4096;/////////////////////////占空比为0~1使用整形表示则需要放大
reg pwm_net;
always@(posedge clk,posedge rst)
begin
if(rst)
pwm_net<=0;
else
if (cnt_tri
pwm_net<=1;
else
pwm_net<=0;
end
霍尔测速:
霍尔测速原理是针对带霍尔的直流无刷电机,当然你也可以使用无位置传感器过零点的方法
霍尔测速原理就是测某一相霍尔信号方波的频率,即检测某一上升沿和下一个上升间的计数值,在通过如下公式换算就可以得出转速:
N=60*f测频时钟/(计数器值*磁极数)
霍尔测速的准确与否直接影响着后面速度闭环的控制性能,因此我采用两次测量求平均的方法,如上图对1,2两个霍尔信号上升使能各自计数器,在霍尔1的第二个上升沿求霍尔1的速度,在霍尔2的第二个上升沿求速度2并且在得出速度2后再求平均速度;而在反转时通过之前霍尔换相图可知1,2 的先后顺序会颠倒因此在求出1速度后再求平均速度。
代码如下:
module cap_hall
(
input clk, rst,//50mhz
input [2:0] cap_hall,
input clk30,//30Mhz
output [15:0] speed ,
input dir,
output reg error
);
parameter f_system=50*1000000;
parameter z=8;
parameter f_mult_60=(50*f_system)/(z);
reg cap_hall_a_r,cap_hall_b_r,cap_hall_c_r;
always@(posedge clk ,posedge rst)
begin
if (rst)
begin
cap_hall_a_r<=0;//0
cap_hall_b_r<=0;//1
cap_hall_c_r<=0;//2
end
else
begin
cap_hall_a_r<=cap_hall[0];
cap_hall_b_r<=cap_hall[1];
cap_hall_c_r<=cap_hall[2];
end
end
wire rise_flag_a,down_fig_a,rise_flag_b,down_fig_b,rise_flag_c,down_fig_c;
assign rise_flag_a=cap_hall[0]&&(~cap_hall_a_r);
assign fall_flag_a=~cap_hall[0]&&(cap_hall_a_r);
assign rise_flag_b=cap_hall[1]&&(~cap_hall_b_r);
assign fall_flag_b=~cap_hall[1]&&(cap_hall_b_r);
assign rise_flag_c=cap_hall[2]&&(~cap_hall_c_r);
assign fall_flag_c=~cap_hall[2]&&(cap_hall_c_r);
reg [31:0] cnt1,cnt2 ,cnt3;
reg [31:0] cnt_r1,cnt_r2 ,cnt_r3;
reg is_cnt;
reg [7:0] i;
parameter s0=8'b0000000,s1=8'b0000001,s2=8'b0000010,s3=8'b0000100,s4=8'b0001000;
reg [7:0] j;
parameter j0=8'b0000000,j1=8'b0000001,j2=8'b0000010,j3=8'b0000100,j4=8'b0001000;
reg en_cnt;
reg [1:0]state;
always@(posedge clk,posedge rst)
begin
if (rst)
begin
i<=s0;
cnt1<=0;
cnt_r1<=0;
str1<=0;en_cnt<=0;state[0]<=0;
end
else
begin
case(i)
s0:begin
if(rise_flag_b)//1
begin
i<=s1;en_cnt<=1;
end
else
begin
i<=s0;cnt1<=0;
end
end
s1:begin
if(rise_flag_b)
begin
str1<=1;i<=s3; cnt_r1=cnt1;state[0]<=1;
end
else
str1<=0;
cnt1<=cnt1+1;
end
s2:
i<=s3;
s3: begin
str1<=0;cnt1<=0;i<=s1; state[0]<=0;
end
default:begin str1<=0;cnt1<=0;i<=s1;end
endcase
end
end
always@(posedge clk,posedge rst)
begin
if (rst)
begin
j<=j0;
cnt2<=0;
cnt_r2<=0;
str2<=0;state[1]<=0;
end
else
begin
case(j)
j0:begin
if(rise_flag_a)//0
begin
j<=j1;
end
else
begin
j<=j0;cnt2<=0;
end
end
j1:begin
if(rise_flag_a)
begin
str2<=1;j<=j3; cnt_r2=cnt2;state[1]<=1;
end
else
str2<=0;
cnt2<=cnt2+1;
end
j2:
j<=j3;
j3:begin
str2<=0;cnt2<=0;j<=j1; state[1]<=0;
end
default:begin str2<=0;cnt2<=0;j<=j1;end
endcase
end
end
wire str/* synthesis syn_keep = 1 */;
reg str1,str2,strd;
reg str1r,str2r/* synthesis syn_keep = 1 */;
reg [31:0]cntr2r,cntd;
always@(posedge clk30,posedge rst)
begin
case(dir)
0:cntr2r<=cnt_r1;
1:cntr2r<=cnt_r2;
endcase
cntd<=(cnt_r2+cnt_r1)/2;
end
always@(*)
case(dir)
0:
if(cntr2r==cnt_r2 )
strd<=0;
else
strd<=1;
1:
if(cntr2r==cnt_r1 )
strd<=0;
else
strd<=1;
endcase
assign str=strd;
wire rdy;
wire [15:0]net1 ;
reg [15:0]net1r ;
divider divid1(
.clock(clk30),
.reset(rst),
.word1(f_mult_60),
.word2(cntd),
.Start(str),
.quotient(net1),
.remainder(),
.Ready(rdy),
.Error()
);
reg [15:0]o1,o2,o3;
reg rdyr,rise_r;
always@(posedge clk,posedge rst)
begin
if (rst)
error<=0;
else
begin
net1r<=net1>>2;
if(rdyr==0 && rdy==1)
o3<=net1r;
if (o3>9999)
spo<=9999;
else if(o3>3100)
error<=1;
else begin
spo<=(o3);error<=0;end
end
end
reg [15:0]spo;
reg [15:0]spor;
reg [20:0]c2=0;
reg clk2,clk2r;
always@(posedge clk30)
begin
if (c2==100000)
begin clk2<=~clk2;c2<=0;end
else if(en_cnt)
c2<=c2+1;
end
always@(posedge clk)
begin
clk2r<=clk2;
if(clk2r==0 &&clk2==1)
spor<=spo;
end
assign speed=(zero1||zero)?0:spor;
reg zero,zero1;
always@(posedge clk)
begin
if(cnt2==50000000)
zero<=1;
else if (rise_flag_b)
zero<=0;
end
always@(posedge clk)
begin
if(cnt1==50000000)
zero1<=1;
else if (rise_flag_a)
zero1<=0;
end
endmodule
代码中调用了一个快速除法器,最后输出进程的采样频率可以粗略当做速度PID的调节带宽,值得注意的是当上电是后或者停车时时计数器也是会计数的但是由于它不会遇到第二个上升沿这就造成在停车后速度输出不会为0,造成电机开机启动预订位无法进行或者停车后再启动需要用手拧,因此程序中还对最慢转速如1转/s进行判断输出zero信号以复位速度输出这样在停机后速度输出为0,顶层控制器中对速度判断。
无刷直流电机控制系统开环控制动态性能不好,因此一般都使用PID来进行控制,常用速度和电流双闭环,这里由于电机较小因此先实现速度闭环
这里给大家提下仿真的重要性,之前和课题组做大功率的交直交系统时在后期调试很多问题通过仿真就可以初步确定原因,有效地减少了实验和修改代码的工作量,这里首先通过matlab自带的无刷电机例子对其修改成采用霍尔换相发PWM的方式,而PID则采用DSPbuilder来实现
阶跃响应:
图中调节还是不理想,只是随便设定了下PID参数由于电机模型和实物不一样因此不再细调,主要是观察PWM输出和实验DSPBuilder工具完成数字PID
PWM输出:
(注:这里使用的是全PWM方式没使用之前的on-PWM)
PID使用增量式
通过DSPbuilder描述这个公式,网上有很多人安装DSPbuilder都失败了,我也试了好多次最后使用matlab2010b 32位 +quartus11.0 32位+dspbuilder11.0成功并且能转换vhdl代码(注意每个都要破解,并且先装matlab,quartus再装dsp 这样在安装它时才会在过程中出现叫你选择已安装matlab版本 这个十分关键)。
以上框图实际上就是描述了公式而已,这里使用了整型因此系数扩到了1024倍 计算完后又缩小1024倍,有兴趣的可以试试定点小数,图中只有蓝色部分才会被转换为VHDL因此其他的可以继续使用DSPbuilder完成也可以直接用verilog描述。
通过signal complier 转换成vhdl代码在quartus中生成原理图,在加上测速和PWM部分既可以实现速度闭环控制,最终系统原理图如下:
图中1是霍尔测速;2是PWM模块 ;3是启动定位开环闭环控制模块;4是死区和故障封脉冲;5是闭环PID;6是串口调试以及数码管显示
使用EP2C5114 FPGA 资源如下:
可以看到还有足够余量给以后的电流闭环等功能,由于之前PID在DSPbuilder中没有设为“也可以使用逻辑资源”因此主要用DSP资源来实现PID,
程序也没有进行时序约束和过多的优化 ,运行时很正常
串口调试模块是我决定这个系统里面最满意的模块,一个控制系统的设计调试十分重要,设计串口模块的初衷是想在闭环速度给定的情况下能实时设定速度,PID参数不需要重新quartus编译(目前编译要2min多十分蛋疼)后面又加入了模式选择,开机停机等功能,以后还可以扩展更多功能,使得在这样一个小系统在结构上十分完善。
串口调试由于没时间用VS编界面,因此使用了串口调试工具模拟了以下人机交互,最后的效果是输入命令后FPGA不但会执行还会返回当前可执行的操作(感觉还有点智能化- -)
串口接收很简单,网上程序很多关键是如何让你输入的数据被判断,如输入str表示开机;有经验的同学肯定就知道使用FIFO就可以完成这样的功能。
我也正是这样做的串行部分的原理图如下:
简单来说就是存入数据到FIFO中,再读出来逐个判断,这有点像序列检测,因此使用一个简单的状态机当FIFO不空时就读并判断数据是否逐一符合命令代码,当然这种方式也有点问题,不过在这样一个小系统中命令数不多使用这种方法也可以接受。
判断str,stp,set等字符串verilog代码:
reg [3:0]i;
reg [3:0]j;
reg[3:0]mode;
parameter i0 = 0, i1 = 1, i2 = 2, i3 = 3 ,i4 = 4, i5 = 5, i6 = 6 ,i7 = 7,i8 = 8,i9 = 9,i10 = 10,i11 = 11,
i12 = 12,i13=13,i14=14;
parameter j0 = 0, j1 = 1, j2 = 2, j3 = 3 ,j4 = 4, j5 = 5, j6 = 6 ,j7 = 7;
always@(posedge clk)
begin
if (rst==1 || rst_cmd==1)
begin
i<=i0;is_set<=0;state_cmd<=4'b0000 ;save<=0;
end
else
begin
case(i)
i0: if(fifo_data==115)//s
i<=i1;
else if(fifo_data==109)//m
i<=i10;
else
begin i<=i0;is_set<=0;state_cmd<=4'b0000 ;save<=0; end
i1: case(fifo_data)
101:
i<=i2;
116:
i<=i5;
97://sav_a
i<=i8;
default:
i<=i0;
endcase
i2: if(fifo_data==116)
i<=i4;
else
i<=i0;
i4:
// if(exit_set==1)//set
// begin i<=i0;end
// else
begin i<=i4;is_set<=1;state_cmd<=4'b0001 ;end
i5: case(fifo_data)
114:
i<=i6;
112:
i<=i7;
default:
i<=i0;
endcase
i6: //str
begin enable<=1;update<=1;
i<=i6;
case(mode)
2:state_cmd<=4'b0011;
3:state_cmd<=4'b1010;
default:state_cmd<=4'b0010 ;
endcase
end
i7: //stp
begin enable<=0;state_cmd<=4'b0100 ;
i<=i7;end
i8: if(fifo_data==118)
i<=i9;
else
i<=i0;
i9://save
begin save<=1;state_cmd<=4'b0101 ; i<=i9; end
i10:if(fifo_data==111)//o
i<=i11;
else
i<=i0;
i11:if(fifo_data==100)//d
i<=i12;
else
i<=i0;
i12:begin state_cmd<=4'b0110;
if(fifo_data>=48 && fifo_data<=57 )//mode
begin mode<=fifo_data;
if (mode==2)
i<=i13;//key pid
else if(mode==3)
i<=i14;//key pid
end
else
i<=i12 ;
end
i13:state_cmd<=4'b0011;
i14:state_cmd<=4'b1010;
default:begin i<=i0;state_cmd<=4'b0000 ;end
endcase
end
end
这里输入的数据都使用十进制表示但是在串口程序中输入的是字符怎么办?你可以用下面的软件来完成转换
这样实际上在小系统中用这种方法还是十分方便的,至于输入一个参数如何储存下来就留给大家思考了。。-w-
之前还说过输入命令后FPGA还会发生当前可执行的命令到串口助手中就如视频中所示一样,这又是如何?
已经上手FPGA的通信肯定知道就是判断不同状态然后发送需要的数就行了呗,那如果我输入set,FPGA需要返回 “设置参数ki kp。。。”一长串字符
怎么才能方便有效的实现呢?这里我也不再卖关子
代码如下:
module uart_tx(
clk, //ê±Öó
TXD, //uart·¢Ëíòy½Å
TI, //·¢ËíÖD¶Ï
rst, //¸′Î
state_cmd ,
state_set,
clk232
);
input rst,clk232;
input [3:0]state_cmd;
input[2:0]state_set;
input clk; //êäèëê±Öó
reg WR; //D′DÅoÅ
output TXD,TI; //′®DDêy¾Y,·¢ËíÖD¶Ï
reg [15:0] cnt; //¼ÆêyÆ÷
wire clk_equ; //·ÖÆμê±Öó
parameter time_bps = 5208; //¸ù¾Y¾ßìåμÄê±Öóà′é趨·ÖÆμÏμêy
//ÕaàïêÇ48Mê±Öó£¬2¨ìØÂêÑ¡ÔñêÇ9600£¬ËùòÔ·ÖÆμÏμêyÎa48000000/9600= 5000£»
/********************************************************************************
** Ä£¿éÃû3Æ£o
** 1|ÄüÃèêö£o2¨ìØÂê·¢éú½ø3ì
********************************************************************************/
reg cnt_sig;
always@(posedge clk)
begin
if(rst)
cnt <= 16'd0;
else
if(cnt==time_bps)
cnt <= 16'd0;
else if(tx_en_sig)
cnt<=cnt+1'b1;
else
cnt<=0;
end
assign clk_equ = (cnt == (2604))?1:0;
/********************************************************************************
** Ä£¿éÃû3Æ£o
** 1|ÄüÃèêö£o¶áêy¾Yμ½»o′æ½ø3ì
********************************************************************************/
reg [2:0]c1;
reg [3:0]state_cmdr;
reg [2:0]state_setr;
reg [9:0] addr;
reg [7:0] length;
reg [7:0] bit_cnt;
reg [2 :0]bit_cnt2;
always@(posedge clk,posedge rst)
begin
if(rst)
begin
c1<=1;tx_en_sig<=0;length<=0;addr<=0;bit_cnt<=0;bit_cnt2<=0;
end
else
begin
state_cmdr<=state_cmd;
state_setr<=state_set;
case(c1)
0:if((state_cmdr!=state_cmd)||(state_setr!=state_set))begin
c1<=1;end
else
begin c1<=0;end
1: begin
//0000:idle
// 0001:set 000:idle
// 001:kp
// 010:ki
// 100:spd
// 0010:str
// 0100:stp
// 0101:save
// 0110:mode
case(state_cmd)
4'b0000:begin addr<=0;length<=71;end
4'b0010:begin addr<=72;length<=19;end
4'b0100:begin addr<=72;length<=19;end
4'b0001:
case(state_set)
3'b000:begin addr<=93;length<=21;end
3'b001:begin addr<=116;length<=12;end
3'b010:begin addr<=130;length<=12;end
3'b100:begin addr<=144;length<=12;end
endcase
4'b0110:begin addr<=158;length<=53;end
default:begin addr<=0;length<=0;end
endcase
tx_en_sig<=1;c1<=2;
end
2:
if (isdone)
begin addr<=addr+1;bit_cnt<=bit_cnt+1; end
else if(bit_cnt==length)
begin bit_cnt<=0;c1<=3;addr<=213;end
else
c1<=2;
3://£¨0DÎa»Ø3죬0AÎa»»DD£©
if (isdone)
begin addr<=addr+1;bit_cnt2<=bit_cnt2+1; end
else if(bit_cnt2==4)
begin c1<=0;tx_en_sig<=0;bit_cnt2<=0;end
else
c1<=3;
default:begin c1<=0;tx_en_sig<=0;length<=0;addr<=0;bit_cnt<=0;bit_cnt2<=0;end
endcase
end
end
rom_tx rom1(
.address(addr),
.clock(clk),
.q(datain));
wire [7:0]datain;
/********************************************************************************
** Ä£¿éÃû3Æ£o
** 1|ÄüÃèêö£oÖ÷3ìDò½ø3ì
********************************************************************************/
reg [3:0]i;
reg rtx;
reg isdone,tx_en_sig;
always@(posedge clk)
begin
if(rst)
begin
i <= 1'b0;
rtx <= 1'b1;
isdone <= 0;
end
else if(tx_en_sig) //·¢Ëíìõ¼tÅD¶Ï£¬±£Ö¤·¢Ëíêy¾YμÄíêÕûDÔ
case(i)
0:
if(clk_equ)begin i<=i+1;rtx<=0; end
1,2,3,4,5,6,7,8:
if(clk_equ)begin i<=i+1;rtx<=datain[i-1];end
9:
if(clk_equ)begin i<=i+1;rtx<=1;end
10:
if(clk_equ)begin i<=i+1;rtx<=1;end
11:
if(clk_equ)begin i<=i+1;isdone<=1;end
12:begin i<=0;isdone<=0;end
endcase
end
assign TXD = rtx; //TXDá¬Døêä3ö
endmodule
眼尖的同学肯定看到中间我调用了个rom,对,这里就是不停读rom并将发送设定length个数,简单吧,当然这样也有问题就是修改起来比较麻烦但是通过之前的软件和quartus mif生成也可以方便的修改:
只要将转换后的数复制到rom表中就可以 ,当然你也要设好每一段字符的空间和起始地址 这样说之前说修改较麻烦的原因
AD与DA,开发板中还带有8位 10位 AD DA芯片可以通过 AD完成电流环反馈采样 DA完成对FPGA中任意数据的输出 通过示波器或者记录仪能更好的对系统性能进行评估和故障问题的定位,这里不再介绍
最后是楼主求助环节:
速度闭环整体调试完成后楼主开始着手电流环 ,拟采用之前自己研究过的神经网络PID,这里使用单神经元PID
当然还是使用dspbuilder 完成,在查阅文献后有人用其中advanced模块中 modleprim 来实现 ,楼主已经搭出了模型,仿真也通过但是在输出vhdl报错
阶跃响应
权值修改
vhdl生成报错:
楼主在网上查了很多,但是都没有这个的相关网页,不知道有没有哪位大神曾经用过
我还试过只有输入 ,hdl输入 ,模块输出的 也不行,这样要如何才能调用啊
vhdl生成还是报错: