2021-02-22 14:17:59 +08:00
2023-06-09 20:54:43 +08:00
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2022-04-05 10:34:50 +08:00
// fpga_top
2022-04-01 05:22:10 +08:00
// Type : synthesizable , FPGA's top
2023-06-09 20:54:43 +08:00
// Standard : Verilog 2001 ( IEEE1364 - 2001 )
2021-02-22 14:17:59 +08:00
// 功能 : FOC 使用示例 , 是FPGA工程的顶层模块 , 控制电机的切向力矩一会顺时针一会逆时针 , 同时可以通过 UART 监测电流环控制的跟随曲线
// 参数 : 无
// 输入输出 : 详见下方注释
2023-06-09 20:54:43 +08:00
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2022-04-01 05:22:10 +08:00
2022-04-05 10:34:50 +08:00
module fpga_top (
2022-04-04 18:42:13 +08:00
input wire clk_50m , // 连接 50MHz 晶振
2021-02-22 14:17:59 +08:00
// - - - - - - - 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
) ;
2023-06-09 20:54:43 +08:00
2021-02-22 14:17:59 +08:00
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 轴 ( 交轴 ) 的目标电流值 , 可正可负 ( 若正代表逆时针 , 则负代表顺时针 , 反之亦然 )
2022-04-04 18:42:13 +08:00
// PLL , 用 50MHz 时钟 ( clk_50m ) 产生 36 . 864 MHz 时钟 ( clk )
2021-02-22 14:17:59 +08:00
// 注 : 该模块仅适用于 Altera Cyclone IV FPGA , 对于其他厂家或系列的FPGA , 请使用各自相同效果的IP核 / 原语 ( 例如Xilinx的clock wizard ) 代替该模块 。
2022-04-01 05:22:10 +08:00
wire [ 3 : 0 ] subwire0 ;
2023-06-09 20:54:43 +08:00
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 ;
2021-02-22 14:17:59 +08:00
2022-04-04 18:42:13 +08:00
// 简易 I2C 读取控制器 , 实现 AS5600 磁编码器读取 , 读出当前转子机械角度 φ
wire [ 3 : 0 ] i2c_trash ; // 丢弃的高4位
i2c_register_read # (
. CLK_DIV ( 16'd10 ) , // i2c_scl 时钟信号分频系数 , scl频率 = clk频率 / ( 4 * CLK_DIV ) , 例如在本例中 clk 为 36 . 864MHz , CLK_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
2023-06-09 20:54:43 +08:00
) u_as5600_read (
2021-02-22 14:17:59 +08:00
. rstn ( rstn ) ,
. clk ( clk ) ,
. scl ( i2c_scl ) , // I2C 接口 : SCL
. sda ( i2c_sda ) , // I2C 接口 : SDA
2022-04-04 18:42:13 +08:00
. start ( 1'b1 ) , // 持续进行 I2C 读操作
. ready ( ) ,
. done ( ) ,
. regout ( { i2c_trash , phi } )
2021-02-22 14:17:59 +08:00
) ;
// 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 )
2023-06-09 20:54:43 +08:00
) u_adc_ad7928 (
2021-02-22 14:17:59 +08:00
. 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 . 864MHz , INIT_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 %
2023-06-09 20:54:43 +08:00
. SAMPLE_DELAY ( 9'd120 ) // 采样延时 , 取值范围0 ~ 511 , 考虑到3相的驱动 MOS 管从开始导通到电流稳定需要一定的时间 , 所以从3个下桥臂都导通 , 到 ADC 采样时刻之间需要一定的延时 。 该参数决定了该延时是多少个时钟周期 , 当延时结束时 , 该模块在 sn_adc 信号上产生一个高电平脉冲 , 指示外部 ADC “ 可以采样了 ”
) u_foc_top (
2021-02-22 14:17:59 +08:00
. rstn ( rstn ) ,
. clk ( clk ) ,
2023-06-09 20:54:43 +08:00
. Kp ( 31'd300000 ) , // 电流环 PID 控制算法的 P 参数
. Ki ( 31'd30000 ) , // 电流环 PID 控制算法的 I 参数
2021-02-22 14:17:59 +08:00
. 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
2023-06-09 20:54:43 +08:00
) u_uart_monitor (
2021-02-22 14:17:59 +08:00
. 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 发送信号
) ;
2023-06-09 20:54:43 +08:00
2021-02-22 14:17:59 +08:00
endmodule