1
0
mirror of https://github.com/WangXuan95/FpOC.git synced 2025-01-17 23:22:52 +08:00
FpOC/RTL/fpga_top.v
2023-06-09 20:54:43 +08:00

174 lines
15 KiB
Verilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//--------------------------------------------------------------------------------------------------------
// fpga_top
// Type : synthesizable, FPGA's top
// Standard: Verilog 2001 (IEEE1364-2001)
// 功能FOC 使用示例是FPGA工程的顶层模块控制电机的切向力矩一会顺时针一会逆时针同时可以通过 UART 监测电流环控制的跟随曲线
// 参数:无
// 输入输出:详见下方注释
//--------------------------------------------------------------------------------------------------------
module fpga_top (
input wire clk_50m, // 连接 50MHz 晶振
// ------- 3相 PWM 信号,(包含使能信号) -----------------------------------------------------------------------------------------------------
output wire pwm_en, // 3相共用的使能信号当 pwm_en=0 时6个MOS管全部关断。
output wire pwm_a, // A相PWM信号。当 =0 时。下桥臂导通;当 =1 时,上桥臂导通
output wire pwm_b, // B相PWM信号。当 =0 时。下桥臂导通;当 =1 时,上桥臂导通
output wire pwm_c, // C相PWM信号。当 =0 时。下桥臂导通;当 =1 时,上桥臂导通
// ------- AD7928 (ADC 芯片) ,用于相电流检测 (SPI接口) ---------------------------------------------------------------------------------------
output wire spi_ss,
output wire spi_sck,
output wire spi_mosi,
input wire spi_miso,
// ------- AS5600 磁编码器,用于获取转子机械角度 (I2C接口) ------------------------------------------------------------------------------------
output wire i2c_scl,
inout i2c_sda,
// ------- UART: 打印转子直角坐标系下的 d 轴实际电流(id)、d 轴目标电流(id_aim)、q 轴实际电流(iq)、q 轴目标电流(iq_aim) ------------------------
output wire uart_tx
);
wire rstn; // 复位信号初始为0当PLL锁相成功后置1。
wire clk; // 时钟信号频率可取几十MHz。控制频率 = 时钟频率 / 2048。比如本例取时钟频率为 36.864MHz ,那么控制频率为 36.864MHz/2048=18kHz。控制频率 = 3相电流采样的采样率 = PID算法的控制频率 = SVPWM占空比的更新频率
wire [11:0] phi; // 从 AS5600 磁编码器读出的转子机械角度 φ 取值范围0~4095。0对应0°1024对应90°2048对应180°3072对应270°。
wire sn_adc; // 3相电流 ADC 采样时刻控制信号当需要进行一次采样时sn_adc 信号上产生一个时钟周期的高电平脉冲指示ADC应该进行采样了。
wire en_adc; // 3相电流 ADC 采样结果有效信号sn_adc 产生高电平脉冲后adc_ad7928 模块开始采样3相电流在转换结束后在 en_adc 信号上产生一个周期的高电平脉冲,同时把 ADC 转换结果产生在 adc_value_a, adc_value_b, adc_value_c 信号上。
wire [11:0] adc_value_a; // A 相电流检测 ADC 原始值
wire [11:0] adc_value_b; // B 相电流检测 ADC 原始值
wire [11:0] adc_value_c; // C 相电流检测 ADC 原始值
wire en_idq; // 出现高电平脉冲时说明 id 和 iq 出现了新值,每个控制周期 en_idq 会产生一个高电平脉冲
wire signed [15:0] id; // 转子 d 轴(直轴)的实际电流值,
wire signed [15:0] iq; // 转子 q 轴(交轴)的实际电流值,可正可负(若正代表逆时针,则负代表顺时针,反之亦然)
wire signed [15:0] id_aim; // 转子 d 轴(直轴)的目标电流值,可正可负
reg signed [15:0] iq_aim; // 转子 q 轴(交轴)的目标电流值,可正可负(若正代表逆时针,则负代表顺时针,反之亦然)
// PLL用 50MHz 时钟clk_50m产生 36.864 MHz 时钟clk
// 注:该模块仅适用于 Altera Cyclone IV FPGA 对于其他厂家或系列的FPGA请使用各自相同效果的IP核/原语例如Xilinx的clock wizard代替该模块。
wire [3:0] subwire0;
altpll u_altpll ( .inclk ( {1'b0, clk_50m} ), .clk ( {subwire0, clk} ), .locked ( rstn ), .activeclock (), .areset (1'b0), .clkbad (), .clkena ({6{1'b1}}), .clkloss (), .clkswitch (1'b0), .configupdate (1'b0), .enable0 (), .enable1 (), .extclk (), .extclkena ({4{1'b1}}), .fbin (1'b1), .fbmimicbidir (), .fbout (), .fref (), .icdrclk (), .pfdena (1'b1), .phasecounterselect ({4{1'b1}}), .phasedone (), .phasestep (1'b1), .phaseupdown (1'b1), .pllena (1'b1), .scanaclr (1'b0), .scanclk (1'b0), .scanclkena (1'b1), .scandata (1'b0), .scandataout (), .scandone (), .scanread (1'b0), .scanwrite (1'b0), .sclkout0 (), .sclkout1 (), .vcooverrange (), .vcounderrange ());
defparam u_altpll.bandwidth_type = "AUTO", u_altpll.clk0_divide_by = 99, u_altpll.clk0_duty_cycle = 50, u_altpll.clk0_multiply_by = 73, u_altpll.clk0_phase_shift = "0", u_altpll.compensate_clock = "CLK0", u_altpll.inclk0_input_frequency = 20000, u_altpll.intended_device_family = "Cyclone IV E", u_altpll.lpm_hint = "CBX_MODULE_PREFIX=pll", u_altpll.lpm_type = "altpll", u_altpll.operation_mode = "NORMAL", u_altpll.pll_type = "AUTO", u_altpll.port_activeclock = "PORT_UNUSED", u_altpll.port_areset = "PORT_UNUSED", u_altpll.port_clkbad0 = "PORT_UNUSED", u_altpll.port_clkbad1 = "PORT_UNUSED", u_altpll.port_clkloss = "PORT_UNUSED", u_altpll.port_clkswitch = "PORT_UNUSED", u_altpll.port_configupdate = "PORT_UNUSED", u_altpll.port_fbin = "PORT_UNUSED", u_altpll.port_inclk0 = "PORT_USED", u_altpll.port_inclk1 = "PORT_UNUSED", u_altpll.port_locked = "PORT_USED", u_altpll.port_pfdena = "PORT_UNUSED", u_altpll.port_phasecounterselect = "PORT_UNUSED", u_altpll.port_phasedone = "PORT_UNUSED", u_altpll.port_phasestep = "PORT_UNUSED", u_altpll.port_phaseupdown = "PORT_UNUSED", u_altpll.port_pllena = "PORT_UNUSED", u_altpll.port_scanaclr = "PORT_UNUSED", u_altpll.port_scanclk = "PORT_UNUSED", u_altpll.port_scanclkena = "PORT_UNUSED", u_altpll.port_scandata = "PORT_UNUSED", u_altpll.port_scandataout = "PORT_UNUSED", u_altpll.port_scandone = "PORT_UNUSED", u_altpll.port_scanread = "PORT_UNUSED", u_altpll.port_scanwrite = "PORT_UNUSED", u_altpll.port_clk0 = "PORT_USED", u_altpll.port_clk1 = "PORT_UNUSED", u_altpll.port_clk2 = "PORT_UNUSED", u_altpll.port_clk3 = "PORT_UNUSED", u_altpll.port_clk4 = "PORT_UNUSED", u_altpll.port_clk5 = "PORT_UNUSED", u_altpll.port_clkena0 = "PORT_UNUSED", u_altpll.port_clkena1 = "PORT_UNUSED", u_altpll.port_clkena2 = "PORT_UNUSED", u_altpll.port_clkena3 = "PORT_UNUSED", u_altpll.port_clkena4 = "PORT_UNUSED", u_altpll.port_clkena5 = "PORT_UNUSED", u_altpll.port_extclk0 = "PORT_UNUSED", u_altpll.port_extclk1 = "PORT_UNUSED", u_altpll.port_extclk2 = "PORT_UNUSED", u_altpll.port_extclk3 = "PORT_UNUSED", u_altpll.self_reset_on_loss_lock = "OFF", u_altpll.width_clock = 5;
//assign rstn=1'b1; assign clk=clk_50m;
// 简易 I2C 读取控制器,实现 AS5600 磁编码器读取,读出当前转子机械角度 φ
wire [3:0] i2c_trash; // 丢弃的高4位
i2c_register_read #(
.CLK_DIV ( 16'd10 ), // i2c_scl 时钟信号分频系数scl频率 = clk频率 / (4*CLK_DIV) ,例如在本例中 clk 为 36.864MHzCLK_DIV=10则 SCL 频率为 36864/(4*10) = 922kHz 。注AS5600 芯片要求 SCL 频率不超过 1MHz
.SLAVE_ADDR ( 7'h36 ), // AS5600's I2C slave address
.REGISTER_ADDR( 8'h0E ) // the register address to read
) u_as5600_read (
.rstn ( rstn ),
.clk ( clk ),
.scl ( i2c_scl ), // I2C 接口: SCL
.sda ( i2c_sda ), // I2C 接口: SDA
.start ( 1'b1 ), // 持续进行 I2C 读操作
.ready ( ),
.done ( ),
.regout ( {i2c_trash, phi} )
);
// AD7928 ADC 读取器用于读取3相电流采样值读出的是未经任何处理的ADC原始值
adc_ad7928 #(
.CH_CNT ( 3'd2 ), // 该参数取2指示我们只想要 CH0, CH1, CH2 这三个通道的 ADC 值
.CH0 ( 3'd1 ), // 指示 CH0 对应 AD7928 的 通道1。硬件上 A 相电流连接到 AD7928 的 通道1
.CH1 ( 3'd2 ), // 指示 CH1 对应 AD7928 的 通道2。硬件上 B 相电流连接到 AD7928 的 通道2
.CH2 ( 3'd3 ) // 指示 CH2 对应 AD7928 的 通道3。硬件上 C 相电流连接到 AD7928 的 通道3
) u_adc_ad7928 (
.rstn ( rstn ),
.clk ( clk ),
.spi_ss ( spi_ss ), // SPI 接口SS
.spi_sck ( spi_sck ), // SPI 接口SCK
.spi_mosi ( spi_mosi ), // SPI 接口MOSI
.spi_miso ( spi_miso ), // SPI 接口MISO
.i_sn_adc ( sn_adc ), // input : 当 sn_adc 出现高电平脉冲时该模块开始进行一次3路的ADC 转换
.o_en_adc ( en_adc ), // output: 当转换结束后en_adc 产生一个周期的高电平脉冲
.o_adc_value0 ( adc_value_a ), // 当 en_adc 产生一个周期的高电平脉冲的同时adc_value_a 上出现 A 相电流的 ADC 原始值
.o_adc_value1 ( adc_value_b ), // 当 en_adc 产生一个周期的高电平脉冲的同时adc_value_b 上出现 B 相电流的 ADC 原始值
.o_adc_value2 ( adc_value_c ), // 当 en_adc 产生一个周期的高电平脉冲的同时adc_value_c 上出现 C 相电流的 ADC 原始值
.o_adc_value3 ( ), // 忽略其余 5 路 ADC 转换结果
.o_adc_value4 ( ), // 忽略其余 5 路 ADC 转换结果
.o_adc_value5 ( ), // 忽略其余 5 路 ADC 转换结果
.o_adc_value6 ( ), // 忽略其余 5 路 ADC 转换结果
.o_adc_value7 ( ) // 忽略其余 5 路 ADC 转换结果
);
// FOC + SVPWM 模块 (使用方法和原理详见 foc_top.sv
foc_top #(
.INIT_CYCLES ( 16777216 ), // 本例中,时钟(clk)频率为 36.864MHzINIT_CYCLES=16777216则初始化时间为 16777216/36864000=0.45 秒
.ANGLE_INV ( 1'b0 ), // 本例中角度传感器没装反A->B->C->A 的旋转方向与 φ 增大的方向相同),则该参数应设为 0
.POLE_PAIR ( 8'd7 ), // 本例使用的电机的极对数为 7
.MAX_AMP ( 9'd384 ), // 384 / 512 = 0.75。说明 SVPWM 的最大振幅 占 最大振幅极限的 75%
.SAMPLE_DELAY ( 9'd120 ) // 采样延时取值范围0~511考虑到3相的驱动 MOS 管从开始导通到电流稳定需要一定的时间所以从3个下桥臂都导通到 ADC 采样时刻之间需要一定的延时。该参数决定了该延时是多少个时钟周期,当延时结束时,该模块在 sn_adc 信号上产生一个高电平脉冲,指示外部 ADC “可以采样了”
) u_foc_top (
.rstn ( rstn ),
.clk ( clk ),
.Kp ( 31'd300000 ), // 电流环 PID 控制算法的 P 参数
.Ki ( 31'd30000 ), // 电流环 PID 控制算法的 I 参数
.phi ( phi ), // input : 角度传感器输入机械角度简记为φ取值范围0~4095。0对应0°1024对应90°2048对应180°3072对应270°。
.sn_adc ( sn_adc ), // output: 3相电流 ADC 采样时刻控制信号当需要进行一次采样时sn_adc 信号上产生一个时钟周期的高电平脉冲指示ADC应该进行采样了。
.en_adc ( en_adc ), // input : 3相电流 ADC 采样结果有效信号sn_adc 产生高电平脉冲后外部ADC开始采样3相电流在转换结束后应在 en_adc 信号上产生一个周期的高电平脉冲同时把ADC转换结果产生在 adc_a, adc_b, adc_c 信号上
.adc_a ( adc_value_a ), // input : A 相 ADC 采样结果
.adc_b ( adc_value_b ), // input : B 相 ADC 采样结果
.adc_c ( adc_value_c ), // input : C 相 ADC 采样结果
.pwm_en ( pwm_en ),
.pwm_a ( pwm_a ),
.pwm_b ( pwm_b ),
.pwm_c ( pwm_c ),
.en_idq ( en_idq ), // output: 出现高电平脉冲时说明 id 和 iq 出现了新值,每个控制周期 en_idq 会产生一个高电平脉冲
.id ( id ), // output: d 轴(直轴)的实际电流值,可正可负
.iq ( iq ), // output: q 轴(交轴)的实际电流值,可正可负(若正代表逆时针,则负代表顺时针,反之亦然)
.id_aim ( id_aim ), // input : d 轴直轴的目标电流值可正可负在不使用弱磁控制的情况下一般设为0
.iq_aim ( iq_aim ), // input : q 轴(直轴)的目标电流值,可正可负(若正代表逆时针,则负代表顺时针,反之亦然)
.init_done ( ) // output: 初始化结束信号。在初始化结束前=0在初始化结束后进入FOC控制状态=1
);
reg [23:0] cnt;
always @ (posedge clk or negedge rstn) // 该 always 维护一个 24bit 的 自增计数器
if(~rstn)
cnt <= 24'd0;
else
cnt <= cnt + 24'd1;
assign id_aim = $signed(16'd0); // 令 id_aim 恒等于 0
always @ (posedge clk or negedge rstn) // 该 always 块令 iq_aim 交替地取 +200 和 -200 ,即电机的切向力矩一会顺时针一会逆时针
if(~rstn) begin
iq_aim <= $signed(16'd0);
end else begin
if(cnt[23])
iq_aim <= $signed(16'd200); // 令 id_aim = +200
else
iq_aim <= -$signed(16'd200); // 令 id_aim = -200
end
// UART 发送器(监视器)格式为115200,8,n,1
uart_monitor #(
.CLK_DIV ( 16'd320 ) // UART分频倍率在本例中取320。因为时钟频率为 36.864MHz, 36.864MHz/320=115200
) u_uart_monitor (
.rstn ( rstn ),
.clk ( clk ),
.i_en ( en_idq ), // input: 当 en_idq 上出现高电平脉冲时,启动 UART 发送
.i_val0 ( id ), // input: 以十进制的形式发送变量 id
.i_val1 ( id_aim ), // input: 以十进制的形式发送变量 id_aim
.i_val2 ( iq ), // input: 以十进制的形式发送变量 iq
.i_val3 ( iq_aim ), // input: 以十进制的形式发送变量 iq_aim
.o_uart_tx ( uart_tx ) // output: UART 发送信号
);
endmodule