mirror of
https://github.com/WangXuan95/FpOC.git
synced 2025-02-05 08:08:22 +08:00
165 lines
12 KiB
Systemverilog
165 lines
12 KiB
Systemverilog
|
`timescale 1 ns/1 ns
|
|||
|
|
|||
|
// 模块:top
|
|||
|
// 功能:FOC 使用示例,是FPGA工程的顶层模块,控制电机的切向力矩一会顺时针一会逆时针,同时可以通过 UART 监测电流环控制的跟随曲线
|
|||
|
// 参数:无
|
|||
|
// 输入输出:详见下方注释
|
|||
|
module 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 时钟产生 36.864 MHz 时钟
|
|||
|
// 注:该模块仅适用于 Altera Cyclone IV FPGA ,对于其他厂家或系列的FPGA,请使用各自相同效果的IP核/原语(例如Xilinx的clock wizard)代替该模块。
|
|||
|
pll pll_i (
|
|||
|
.inclk0 ( clk_50m ), // input : clk_50m
|
|||
|
.c0 ( clk ), // output: clk
|
|||
|
.locked ( rstn ) // output: rstn
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// AS5600 磁编码器读取器,内含简易 I2C 控制器,通过 I2C 接口读取当前转子机械角度 φ
|
|||
|
as5600_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
|
|||
|
) as5600_i (
|
|||
|
.rstn ( rstn ),
|
|||
|
.clk ( clk ),
|
|||
|
.scl ( i2c_scl ), // I2C 接口: SCL
|
|||
|
.sda ( i2c_sda ), // I2C 接口: SDA
|
|||
|
.o_en ( ), // output: 每成功读取一次 φ,o_en就产生一个高电平脉冲,这里我们用不到该信号
|
|||
|
.o_phi ( phi ) // output: 转子机械角度 φ
|
|||
|
);
|
|||
|
|
|||
|
|
|||
|
|
|||
|
// 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)
|
|||
|
) adc_ad7928_i (
|
|||
|
.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%
|
|||
|
.SAMPLE_DELAY ( 9'd120 ), // 采样延时,取值范围0~511,考虑到3相的驱动 MOS 管从开始导通到电流稳定需要一定的时间,所以从3个下桥臂都导通,到 ADC 采样时刻之间需要一定的延时。该参数决定了该延时是多少个时钟周期,当延时结束时,该模块在 sn_adc 信号上产生一个高电平脉冲,指示外部 ADC “可以采样了”
|
|||
|
.Kp ( 24'd32768 ), // 电流环 PID 控制算法的 P 参数
|
|||
|
.Ki ( 24'd2 ) // 电流环 PID 控制算法的 I 参数
|
|||
|
) foc_top_i (
|
|||
|
.rstn ( rstn ),
|
|||
|
.clk ( clk ),
|
|||
|
.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
|
|||
|
) uart_monitor_i (
|
|||
|
.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
|