1
0
mirror of https://github.com/WangXuan95/FpOC.git synced 2025-01-17 23:22:52 +08:00

first commit

This commit is contained in:
WangXuan95 2021-02-22 14:17:59 +08:00
commit 728ba144e2
21 changed files with 8101 additions and 0 deletions

30
FPGA/foc.qpf Normal file
View File

@ -0,0 +1,30 @@
# -------------------------------------------------------------------------- #
#
# Copyright (C) 1991-2013 Altera Corporation
# Your use of Altera Corporation's design tools, logic functions
# and other software and tools, and its AMPP partner logic
# functions, and any output files from any of the foregoing
# (including device programming or simulation files), and any
# associated documentation or information are expressly subject
# to the terms and conditions of the Altera Program License
# Subscription Agreement, Altera MegaCore Function License
# Agreement, or other applicable license agreement, including,
# without limitation, that your use is for the sole purpose of
# programming logic devices manufactured by Altera and sold by
# Altera or its authorized distributors. Please refer to the
# applicable agreement for further details.
#
# -------------------------------------------------------------------------- #
#
# Quartus II 64-Bit
# Version 13.1.0 Build 162 10/23/2013 SJ Full Version
# Date created = 17:37:36 February 05, 2021
#
# -------------------------------------------------------------------------- #
QUARTUS_VERSION = "13.1"
DATE = "17:37:36 February 05, 2021"
# Revisions
PROJECT_REVISION = "foc"

92
FPGA/foc.qsf Normal file
View File

@ -0,0 +1,92 @@
# -------------------------------------------------------------------------- #
#
# Copyright (C) 1991-2013 Altera Corporation
# Your use of Altera Corporation's design tools, logic functions
# and other software and tools, and its AMPP partner logic
# functions, and any output files from any of the foregoing
# (including device programming or simulation files), and any
# associated documentation or information are expressly subject
# to the terms and conditions of the Altera Program License
# Subscription Agreement, Altera MegaCore Function License
# Agreement, or other applicable license agreement, including,
# without limitation, that your use is for the sole purpose of
# programming logic devices manufactured by Altera and sold by
# Altera or its authorized distributors. Please refer to the
# applicable agreement for further details.
#
# -------------------------------------------------------------------------- #
#
# Quartus II 64-Bit
# Version 13.1.0 Build 162 10/23/2013 SJ Full Version
# Date created = 17:37:36 February 05, 2021
#
# -------------------------------------------------------------------------- #
#
# Notes:
#
# 1) The default values for assignments are stored in the file:
# foc_assignment_defaults.qdf
# If this file doesn't exist, see file:
# assignment_defaults.qdf
#
# 2) Altera recommends that you do not modify this file. This
# file is updated automatically by the Quartus II software
# and any changes you make may be lost or overwritten.
#
# -------------------------------------------------------------------------- #
set_location_assignment PIN_128 -to clk_50m
set_location_assignment PIN_142 -to pwm_en
set_location_assignment PIN_143 -to pwm_a
set_location_assignment PIN_137 -to pwm_b
set_location_assignment PIN_136 -to pwm_c
set_location_assignment PIN_144 -to spi_ss
set_location_assignment PIN_7 -to spi_mosi
set_location_assignment PIN_10 -to spi_miso
set_location_assignment PIN_11 -to spi_sck
set_location_assignment PIN_125 -to i2c_scl
set_location_assignment PIN_121 -to i2c_sda
set_location_assignment PIN_105 -to uart_tx
set_global_assignment -name FAMILY "Cyclone IV E"
set_global_assignment -name DEVICE EP4CE15E22I7
set_global_assignment -name TOP_LEVEL_ENTITY top
set_global_assignment -name ORIGINAL_QUARTUS_VERSION 13.1
set_global_assignment -name PROJECT_CREATION_TIME_DATE "17:37:36 FEBRUARY 05, 2021"
set_global_assignment -name LAST_QUARTUS_VERSION 13.1
set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output_files
set_global_assignment -name MIN_CORE_JUNCTION_TEMP "-40"
set_global_assignment -name MAX_CORE_JUNCTION_TEMP 100
set_global_assignment -name ERROR_CHECK_FREQUENCY_DIVISOR 1
set_global_assignment -name NOMINAL_CORE_SUPPLY_VOLTAGE 1.2V
set_global_assignment -name PARTITION_NETLIST_TYPE SOURCE -section_id Top
set_global_assignment -name PARTITION_FITTER_PRESERVATION_LEVEL PLACEMENT_AND_ROUTING -section_id Top
set_global_assignment -name PARTITION_COLOR 16764057 -section_id Top
set_global_assignment -name STRATIX_DEVICE_IO_STANDARD "2.5 V"
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/top.sv
set_global_assignment -name VERILOG_FILE ../RTL/pll.v
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/uart/uart_monitor.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/uart/uart_tx.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/uart/itoa.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/sensors/as5600_read.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/sensors/i2c_register_read.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/sensors/adc_ad7928.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/foc/foc_top.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/foc/clark_tr.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/foc/park_tr.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/foc/sincos.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/foc/pi_controller.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/foc/cartesian2polar.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/foc/svpwm.sv
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/foc/hold_detect.sv
set_instance_assignment -name PARTITION_HIERARCHY root_partition -to | -section_id Top

150
README.md Normal file
View File

@ -0,0 +1,150 @@
![test](https://img.shields.io/badge/test-passing-green.svg)
![docs](https://img.shields.io/badge/docs-passing-green.svg)
FpOC
===========================
基于 **FPGA** 的**磁场定向控制 (FOC)**,用于驱动**永磁同步电机 (PMSM)**
# 简介
**FOC控制算法**对**传感器采样速率**和**处理器算力**提出了一定的要求,使用 **FPGA** 实现的 **FOC** 可以获得更好的**实时性**和零**延迟抖动**,并且更方便进行**多路扩展**。
本库实现了基于**角度传感器**(例如磁编码器)的**有感 FOC**(一个完整的**电流反馈环**),可以进行**扭矩控制**。借助本库,可以进一步使用 **FPGA** 、**软核 MCU** 或**外置 MCU** 实现更复杂的电机应用。
| ![diagram](https://github.com/WangXuan95/FpOC/blob/main/diagram.png) |
| :---: |
| 图1系统框图 |
该项目代码有**详细的注释**,结合其它科普资料(见[6][8][9]),可以用来快速地熟悉 **FOC**
## 特点
* **平台无关** :纯 RTL 编写,可以在 Altera 和 Xilinx 等各种 FPGA 上运行。
* 支持 **12bit 分辨率**的**角度传感器**和**相电流采样ADC**,对于>12bit的传感器需要进行低位截断。对于<12bit的传感器需要进行低位补0
* 支持 **3路PWM** + **1路EN** PWM=1 时上半桥导通PWM=0 时下半桥导通。 EN=0 时所有 MOS 关断。
* 使用 **16bit 有符号整数**进行计算,降低了资源消耗,考虑到传感器为 12bit16bit 计算是够用的。
# 运行示例
**图1** 是本库的系统框图,实现了一个简单的行为——控制电机的电流(扭矩)按顺时针,逆时针交替运行。同时,使用 UART 打印电流的**控制目标值**和**实际值**,以便观察控制的质量。
该示例的所有代码都在 [./RTL](https://github.com/WangXuan95/FpOC/blob/main/RTL) 目录下。工程在 [./FPGA](https://github.com/WangXuan95/FpOC/blob/main/FPGA) 目录下,需要用 Quartus 软件打开。
## 准备硬件
需要准备以下硬件:
* PMSM 电机
* FPGA 开发板
* AD7928 ADC 模块,用于进行相电流采样(好像没有现成卖的,需要自己画模块)
* 电机驱动板要支持3相 EN+PWM 输入信号,并且使用低侧电阻采样法+放大器对3相电流进行放大。
可以直接使用我画的 电机驱动板自带AD7928立创EDA工程[在此](https://oshwhub.com/wangxuan/arduino-foc-shield),只需要把它接一个 FPGA 开发板即可。
## 硬件连接与引脚分配
见该工程的顶层文件 [top.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/top.sv) 的注释,以下外设需要连到 FPGA 的引脚上普通IO引脚即可
* **晶振** 50MHz 时钟,连接在 clk_50m 信号上。
* **角度传感器 AS5600** , I2C 接口, 2根线: i2c_scl, i2c_sda
* **ADC AD7928** , SPI接口4根线: spi_ss, spi_sck, spi_mosi, spi_miso
* **3相PWM输出信号**通常接在Gate Driver上例如MP6540/DRV83014根线: pwm_en, pwm_a, pwm_b, pwm_c
另外还有一个 UART 发送信号 (uart_tx) 是可选的,可以把它连接在 UART 转 USB 模块上,通过 UART 来监测电流环的跟随曲线。
连接好后别忘了使用 Quartus (或者手动修改[./FPGA/foc.qsf](https://github.com/WangXuan95/FpOC/blob/main/FPGA/foc.qsf))根据实际情况**修改FPGA芯片型号****修改引脚约束**。
## 调参
[foc_top.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/foc/foc_top.sv) 中有一些参数可以调整,例如电机的**极对数**、**PID参数**等,每个参数的含义详见 [foc_top.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/foc/foc_top.sv) 。可以通过修改 [top.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/top.sv) 的 97~103 行来修改这些参数。
## 运行示例
综合并烧录到 FPGA 后,可以看到电机正反交替运行。
## 监视串口
把 uart_tx 信号通过 **UART 转 USB 模块** 例如CP2102模块 连接到电脑上,就可以用**串口助手**、**Putty**等软件来监测电流环的跟随效果。
> 注: UART 的格式是 115200,8,n,1
以下是串口打印的部分信息。其中第1~4列分别为**d轴电流的实际值****d轴电流的目标值****q轴电流的实际值****q轴电流的目标值**。可以看到,即使目标值从+200突变到-200实际值能跟着目标值走说明电流环的 PID 控制是有效的。
-5 0 206 200
-16 0 202 200
16 0 192 200
15 0 201 200
1 0 197 200
17 0 -211 -200
-6 0 -199 -200
-10 0 -210 -200
-3 0 -207 -200
0 0 -202 -200
-15 0 -211 -200
另外,你可以借用 **Arduino IDE** 的**串口绘图器**来实时显示电流跟随曲线。前往[该网站](https://www.arduino.cc/en/software)下载 **Arduino IDE**,安装后打开,在“**工具→端口**”中选择正确的COM口然后点击“**工具→串口绘图器**”,**串口绘图器**会自动接收串口并使用上述4列数据画实时曲线图。
**图2** 是我这里绘制出的电流跟随曲线。蓝色曲线是第1列数据d轴电流的实际值红色曲线是第2列数据d轴电流的目标值绿色曲线是第3列数据q轴电流的实际值土黄色曲线是第4列数据q轴电流的目标值。可以看到实际值能跟着目标值走。
| ![wave](https://github.com/WangXuan95/FpOC/blob/main/wave.png) |
| :---: |
| 图1电流跟随曲线 |
# 代码详解
下表罗列了该工程使用的所有 **(System-)Verilog** 代码文件,这些文件都在 [./RTL](https://github.com/WangXuan95/FpOC/blob/main/RTL) 目录下。,结合**图1**就能看出每个模块的作用。
| 文件名 | 功能 | 备注 |
| :-- | :-- | :-- |
| top.sv | FPGA工程的顶层模块 | |
| pll.v | 使用 50MHz 时钟生成 36.864MHz 时钟 | 只支持 Altera Cyclone IV其它型号的 FPGA 需要用相应的IP核或原语代替 |
| uart_monitor.sv | UART 发送器,用于数据监测 | 不需要的话可以删除 |
| uart_tx.sv | UART 发送控制器,被 uart_monitor.sv 调用 | 不需要的话可以删除 |
| itoa.sv | 数字转十进制字符串,被 uart_monitor.sv 调用 | 不需要的话可以删除 |
| as5600_read.sv | AS5600 磁编码器读取器 | |
| i2c_register_read.sv | I2C 读取器,被 as5600_read.sv 调用 | |
| adc_ad7928.sv | AD7928 读取器 | |
| foc_top.sv | FOC+SVPWM (即**图1**中的青色部分) | 固定算法,一般不需要改动 |
| clark_tr.sv | Clark 变换 | 固定算法,一般不需要改动 |
| park_tr.sv | Park 变换 | 固定算法,一般不需要改动 |
| sincos.sv | 正弦/余弦计算器,被 park_tr.sv 调用 | 固定算法,一般不需要改动 |
| pi_controller.sv | PID 控制器只有P和I | 固定算法,一般不需要改动 |
| cartesian2polar.sv | 直角坐标系转极坐标系 | 固定算法,一般不需要改动 |
| svpwm.sv | SVPWM 调制器 | 固定算法,一般不需要改动 |
| hold_detect.sv | 监测3个下桥臂都导通时延迟一段时间后触发 sn_adc 信号指示ADC可以开始采样 | 固定算法,一般不需要改动 |
| ![diagram](https://github.com/WangXuan95/FpOC/blob/main/diagram.png) |
| :---: |
| 图1系统框图 |
在**图1**中:
* **淡橙色**部分是FPGA外部的硬件包括Gate Driver、3相半桥(6个MOSFET)、采样电阻、放大器、ADC芯片、角度传感器等部件取决于电路使用什么方案。例如有些集成度很高的芯片例如MP6540可以把Gate Driver、3相半桥、采样电阻、放大器集成在同一个芯片里。
* **粉色**部分是FPGA中的**硬件相关逻辑**,即传感器控制器,如果角度传感器和 ADC 型号变了,这部分代码需要重写。
* **青色**部分是FPGA中的 FOC 的固定算法,属于**硬件无关逻辑**,一般不需要改变。
* **黄色**部分是**用户自定逻辑**,用户可以修改 user behavior 来实现各种电机应用。或者修改 uart_monitor 来监测其它变量。
另外,除了 [pll.v](https://github.com/WangXuan95/FpOC/blob/main/RTL/pll.v) 外,该库的所有代码都使用纯 RTL 编写可以轻易地移植到其它厂商Xilinx、Lattice等的 FPGA 上。 [pll.v](https://github.com/WangXuan95/FpOC/blob/main/RTL/pll.v) 只是用来把 50MHz 时钟变成 36.864MHz 时钟的,只适用于 Altera Cyclone IV FPGA当使用其它厂商或系列的FPGA时需要使用它们各自的 IP 核或原语例如Xilinx的clock wizard来代替。
[top.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/top.sv) 和 [foc_top.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/foc/foc_top.sv) 注有详细的注释。如果你了解 FOC 算法,可以直接读懂。如果刚入门 FOC可以结合参考资料[6][8][9]去阅读。
## 可扩展功能
* 使用自己编写的模块来代替 [as5600_read.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/sensors/as5600_read.sv),以适配其它型号的角度传感器。
* 使用自己编写的模块来代替 [adc_ad7928.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/sensors/adc_ad7928.sv),以适配其它型号的 ADC。
* 在 [top.sv](https://github.com/WangXuan95/FpOC/blob/main/RTL/top.sv) 中添加外环(速度环、位置环等)进一步实现各种电机应用。
# 参考资料
* [1] [Sensorless FOC for PMSM](https://www.microchip.com/stellent/groups/SiteComm_sg/documents/Training_Tutorials/en532365.pdf), MicroChip.
* [2] [Current sensing in BLDC motor application](https://www.st.com/resource/en/application_note/dm00666970-current-sensing-in-bldc-motor-application-stmicroelectronics.pdf), ST.
* [3] [Center-Aligned SVPWM Realization](https://www.ti.com/lit/an/sprabs6/sprabs6.pdf), TI
* [4] [MP6540, 3-phase, brushless DC motor drivers](https://www.monolithicpower.com/en/mp6540-mp6540a.html), MPS.
* [5] [AD7928, 8-Channel, 1 MSPS, 12-Bit ADC](https://www.analog.com/en/products/ad7928.html), Analog Devices.
* [6] [深入浅出讲解FOC算法与SVPWM技术](https://zhuanlan.zhihu.com/p/147659820), 稚晖 - 知乎
* [7] [如何从零开始写一套自己的FOC矢量控制程序](https://zhuanlan.zhihu.com/p/103758450?utm_source=qzone), 上官致远 - 知乎
* [8] [STM32电动机控制应用系列讲座](https://www.bilibili.com/video/BV1vT4y1j7kc)
* [9] [BLDC电机基础](https://www.bilibili.com/video/BV1TW411d7k6)

4187
RTL/foc/cartesian2polar.sv Normal file

File diff suppressed because it is too large Load Diff

61
RTL/foc/clark_tr.sv Normal file
View File

@ -0,0 +1,61 @@
`timescale 1 ns/1 ns
module clark_tr(
input wire rstn,
input wire clk,
input wire i_en,
input wire signed [15:0] i_ia, i_ib, i_ic, // range -8191 ~ 8191
output reg o_en,
output reg signed [15:0] o_ialpha, o_ibeta
);
// registers for pipeline stage 1
reg en_s1;
reg signed [15:0] ax2_s1, bmc_s1, bpc_s1;
// registers for pipeline stage 2
reg en_s2;
reg signed [15:0] ialpha_s2, i_beta1_s2, i_beta2_s2, i_beta3_s2;
// pipeline stage 1
always @ (posedge clk or negedge rstn)
if(~rstn) begin
{en_s1, ax2_s1, bmc_s1, bpc_s1} <= '0;
end else begin
en_s1 <= i_en;
ax2_s1 <= i_ia << 1;
bmc_s1 <= i_ib - i_ic;
bpc_s1 <= i_ib + i_ic;
end
// pipeline stage 2
always @ (posedge clk or negedge rstn)
if(~rstn) begin
{en_s2, ialpha_s2, i_beta1_s2, i_beta2_s2, i_beta3_s2} <= '0;
end else begin
en_s2 <= en_s1;
ialpha_s2 <= ax2_s1 - bpc_s1;
i_beta1_s2 <= bmc_s1 +
$signed({{ 1{bmc_s1[15]}}, bmc_s1[15: 1]}) +
$signed({{ 3{bmc_s1[15]}}, bmc_s1[15: 3]});
i_beta2_s2 <= $signed({{ 4{bmc_s1[15]}}, bmc_s1[15: 4]}) +
$signed({{ 5{bmc_s1[15]}}, bmc_s1[15: 5]}) +
$signed({{ 7{bmc_s1[15]}}, bmc_s1[15: 7]});
i_beta3_s2 <= $signed({{ 8{bmc_s1[15]}}, bmc_s1[15: 8]}) +
$signed({{10{bmc_s1[15]}}, bmc_s1[15:10]}) +
$signed({{11{bmc_s1[15]}}, bmc_s1[15:11]});
end
// pipeline stage output
always @ (posedge clk or negedge rstn)
if(~rstn) begin
{o_en, o_ialpha, o_ibeta} <= 1'b0;
end else begin
o_en <= en_s2;
if(en_s2) begin
o_ialpha <= ialpha_s2;
o_ibeta <= i_beta1_s2 + i_beta2_s2 + i_beta3_s2;
end
end
endmodule

272
RTL/foc/foc_top.sv Normal file
View File

@ -0,0 +1,272 @@
`timescale 1 ns/1 ns
// 模块: foc_top
// 功能FOC 算法(仅包含电流环) + SVPWM
// 参数:详见下方注释
// 输入输出:详见下方注释
module foc_top #(
// ----------------------------------------------- 模块参数 ---------------------------------------------------------------------------------------------------------------------------------------------------
parameter INIT_CYCLES = 16777216, // 决定了初始化步骤粘多少个时钟(clk)周期取值范围为1~4294967294。该值不能太短因为要留足够的时间让转子回归电角度=0。例如若时钟(clk)频率为 36.864MHzINIT_CYCLES=16777216则初始化时间为 16777216/36864000=0.45 秒
parameter logic ANGLE_INV = 1'b0, // 若角度传感器没装反A->B->C->A 的旋转方向与 φ 增大的方向相同),则该参数应设为 0。若角度传感器装反了A->B->C->A 的旋转方向与 φ 增大的方向相反),则该参数应设为 1。
parameter logic [ 7:0] POLE_PAIR = 8'd7, // 电机极对数 (简记为N)取值范围1~255根据电机型号决定。电角度ψ = 极对数N * 机械角度φ)
parameter logic [ 8:0] MAX_AMP = 9'd384, // SVPWM 的最大振幅取值范围为1~511该值越小电机能达到的最大力矩越小但考虑到使用3相下桥臂电阻采样法来采样电流该值也不能太大以保证3个下桥臂有足够的持续导通时间来供ADC进行采样。
parameter logic [ 8:0] SAMPLE_DELAY = 9'd120, // 采样延时取值范围0~511考虑到3相的驱动 MOS 管从开始导通到电流稳定需要一定的时间所以从3个下桥臂都导通到 ADC 采样时刻之间需要一定的延时。该参数决定了该延时是多少个时钟周期,当延时结束时,该模块在 sn_adc 信号上产生一个高电平脉冲,指示外部 ADC “可以采样了”
parameter logic [23:0] Kp = 24'd32768,// 电流环 PID 控制算法的 P 参数
parameter logic [23:0] Ki = 24'd2 // 电流环 PID 控制算法的 I 参数
) (
// ----------------------------------------------- 驱动时钟和复位 ---------------------------------------------------------------------------------------------------------------------------------------------
input wire rstn, // 复位信号,应该先拉低来对模块进行复位,然后一直保持高电平来让模块正常工作。
input wire clk, // 时钟信号频率可取几十MHz。控制频率 = 时钟频率 / 2048。比如若时钟频率为 36.864MHz ,那么控制频率为 36.864MHz/2048=18kHz。控制频率 = 3相电流采样的采样率 = PID算法的控制频率 = SVPWM占空比的更新频率
// ----------------------------------------------- 角度传感器输入信号 -----------------------------------------------------------------------------------------------------------------------------------------
input wire [11:0] phi, // 角度传感器输入机械角度简记为φ取值范围0~4095。0对应0°1024对应90°2048对应180°3072对应270°。
// ----------------------------------------------- 3相电流 ADC 采样时刻控制信号 和采样结果输入信号 ------------------------------------------------------------------------------------------------------------
output wire sn_adc, // 3相电流 ADC 采样时刻控制信号当需要进行一次采样时sn_adc 信号上产生一个时钟周期的高电平脉冲指示ADC应该进行采样了。
input wire en_adc, // 3相电流 ADC 采样结果有效信号sn_adc 产生高电平脉冲后外部ADC开始采样3相电流在转换结束后应在 en_adc 信号上产生一个周期的高电平脉冲同时把ADC转换结果产生在 adc_a, adc_b, adc_c 信号上
input wire [11:0] adc_a, adc_b, adc_c, // 3相电流 ADC 采样结果简记为ADCa, ADCb, ADCc取值范围0 ~ 4095
// ----------------------------------------------- 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 时,上桥臂导通
// ----------------------------------------------- d/q轴转子直角坐标系的电流监测 --------------------------------------------------------------------------------------------------------------------------
output wire en_idq, // 出现高电平脉冲时说明 id 和 iq 出现了新值,每个控制周期 en_idq 会产生一个高电平脉冲
output wire signed [15:0] id, // d 轴(直轴)的实际电流值(简记为 Id可正可负
output wire signed [15:0] iq, // q 轴(交轴)的实际电流值(简记为 Iq可正可负若正代表逆时针则负代表顺时针反之亦然
// ----------------------------------------------- d/q轴转子直角坐标系的电流控制目标 ----------------------------------------------------------------------------------------------------------------------
input wire signed [15:0] id_aim, // d 轴(直轴)的目标电流值(简记为 Idaim可正可负在不使用弱磁控制的情况下一般设为0
input wire signed [15:0] iq_aim, // q 轴(直轴)的目标电流值(简记为 Iqaim可正可负若正代表逆时针则负代表顺时针反之亦然
// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
output reg init_done // 初始化结束信号。在初始化结束前=0在初始化结束后进入FOC控制状态=1
);
reg [31:0] init_cnt;
reg [11:0] init_phi; // 初始机械角度(简记为 Φ)。即电角度=0时对应的机械角度在初始化结束时被确定用来在之后进行机械角度到电角度的转换。取值范围0~4095。0对应0°1024对应90°2048对应180°3072对应270°。
reg [11:0] psi; // 当前电角度(简记为 ψ。取值范围0~4095。0对应0°1024对应90°2048对应180°3072对应270°。
reg en_iabc; // 3相上的电流有效在产生高电平脉冲时说明 Ia, Ib, Ic 发生更新
reg signed [15:0] ia, ib, ic; // 3相上的电流。为正代表电流从半桥流入电机为负代表电流从电机流入半桥。ia 是A相的电流简记为Iaib 是B相的电流简记为Ibic 是C相的电流简记为Ic
wire en_ialphabeta; // α/β轴(定子直角坐标系)上的电流矢量有效信号,在产生高电平脉冲时,说明 Iα, Iβ 发生更新
wire signed [15:0] ialpha, ibeta; // α/β轴定子直角坐标系上的电流矢量。ialpha 是 α 轴的分量(简记为 Iαibeta 是 β 轴的分量(简记为 Iβ
wire signed [15:0] vd, vq; // d/q轴转子直角坐标系上的电压矢量是 PID 算法输出的值。Vd 是 d 轴 上的电压分量Vq 是 q 轴上的电压分量
wire [11:0] vr_rho; // 转子极坐标系上的电压矢量的幅值(简记为 Vrρ ),由 Vd 和 Vq 转换到极坐标系得来具体地讲Vrρ = √(Vd^2+Vq^2)
wire [11:0] vr_theta; // 转子极坐标系上的电压矢量的角度(简记为 Vrθ ),由 vd 和 vq 转换到极坐标系得来具体地讲Vrθ = arctan(Vq/Vd) 。取值范围0~4095。0对应0°1024对应90°2048对应180°3072对应270°。
reg [11:0] vs_rho; // 定子极坐标系上的电压矢量的幅值(简记为 Vsρ ),由 Vrρ 做旋转变换得来,由于幅值的旋转不变性,实际上 Vsρ = Vrρ。 通过 SVPWM 模块Vsρ 和 Vsθ 可以产生 3 相 PWM 信号
reg [11:0] vs_theta; // 定子极坐标系上的电压矢量的角度(简记为 Vsθ ),由 Vrθ 做旋转变换得来,由于转子极坐标系是定子极坐标系旋转 ψ 得来,所以 Vsθ = Vrθ + ψ。 通过 SVPWM 模块Vsρ 和 Vsθ 可以产生 3 相 PWM 信号。 Vsθ取值范围0~4095。0对应0°1024对应90°2048对应180°3072对应270°。
// 简介 :该 always 块负责从机械角度 φ 算出电角度 ψ
// 参数 :极对数 N 参数POLE_PAIR
// 输入 :机械角度 φ
// 初始机械角度 Φ
// 输出 :电角度 ψ
// 计算公式: ψ = N * (φ - Φ) (若 A->B->C->A 的旋转方向与 φ 增大的方向相同)
// :或 ψ = -N * (φ - Φ) (若 A->B->C->A 的旋转方向与 φ 增大的方向相反,即角度传感器装反了)
// 输出更新:只要 φ 改变,ψ 就在下一周期立即改变
generate if(ANGLE_INV) begin // 如果角度传感器装反了
always @ (posedge clk or negedge init_done)
if(~init_done)
psi <= '0;
else
psi <= {4'h0, POLE_PAIR} * (init_phi - phi); // ψ = -N * (φ - Φ)
end else begin // 如果角度传感器没装反
always @ (posedge clk or negedge init_done)
if(~init_done)
psi <= '0;
else
psi <= {4'h0, POLE_PAIR} * (phi - init_phi); // ψ = N * (φ - Φ)
end endgenerate
// 简介 :该 always 块根据基尔霍夫电流定律(KCL)在 ADC 原始值 (ADCa, ADCb, ADCc) 上减去偏移值,计算出 3 相电流值 Ia, Ib, Ic
// 输入 ADC 原始值 ADCa, ADCb, ADCc
// 输出 :相电流 Ia, Ib, Ic
// 计算公式Ia = ADCb + ADCc - 2*ADCa
// Ib = ADCa + ADCc - 2*ADCb
// Ic = ADCa + ADCb - 2*ADCc
// 输出更新ADC 每采样完成一次即en_adc每产生一次高电平脉冲后更新一次即更新频率 = 控制周期,更新后 en_iabc 产生一个时钟周期的高电平脉冲
always @ (posedge clk or negedge init_done)
if(~init_done) begin
{en_iabc, ia, ib, ic} <= '0;
end else begin
en_iabc <= en_adc;
if(en_adc) begin
ia <= $signed( {4'b0, adc_b} + {4'b0, adc_c} - {3'b0, adc_a, 1'b0} ); // Ia = ADCb + ADCc - 2*ADCa
ib <= $signed( {4'b0, adc_a} + {4'b0, adc_c} - {3'b0, adc_b, 1'b0} ); // Ib = ADCa + ADCc - 2*ADCb
ic <= $signed( {4'b0, adc_a} + {4'b0, adc_b} - {3'b0, adc_c, 1'b0} ); // Ic = ADCa + ADCb - 2*ADCc
end
end
// 简介 :该模块用于进行 clark 变换,根据 3 相电流计算 α/β 轴(定子直角坐标系)的电流矢量
// 输入 :相电流 Ia, Ib, Ic
// 输出 α/β 轴的电流矢量 Iα, Iβ
// 计算公式Iα = 2 * Ia - Ib - Ic
// Iβ = √3 * (Ib - Ic)
// 输出更新en_iabc 每产生一个高电平脉冲后的若干周期后 Iα, Iβ 更新,同时 en_ialphabeta 产生一个时钟周期的高电平脉冲,即更新频率 = 控制周期
clark_tr clark_tr_i(
.rstn ( init_done ),
.clk ( clk ),
.i_en ( en_iabc ),
.i_ia ( ia ), // input : Ia
.i_ib ( ib ), // input : Ib
.i_ic ( ic ), // input : Ic
.o_en ( en_ialphabeta ),
.o_ialpha ( ialpha ), // output: Iα
.o_ibeta ( ibeta ) // output: Iβ
);
// 简介 :该模块用于进行 park 变换,根据 α/β 轴(定子直角坐标系)的电流矢量 计算 d/q 轴(转子直角坐标系)的电流矢量
// 输入 :电角度 ψ
// α/β 轴的电流矢量 Iα, Iβ
// 输出 d/q 轴的电流矢量 Id, Iq
// 计算公式Id = Iα * cosψ + Iβ * sinψ;
// Iq = Iβ * cosψ - Iα * sinψ;
// 输出更新en_ialphabeta 每产生一个高电平脉冲后的若干周期后 Id, Iq 更新,同时 en_idq 产生一个时钟周期的高电平脉冲,即更新频率 = 控制周期
park_tr park_tr_i (
.rstn ( init_done ),
.clk ( clk ),
.psi ( psi ), // input : ψ
.i_en ( en_ialphabeta ),
.i_ialpha ( ialpha ), // input : Iα
.i_ibeta ( ibeta ), // input : Iβ
.o_en ( en_idq ),
.o_id ( id ), // output: Id
.o_iq ( iq ) // output: Iq
);
// 简介 :该模块用于进行 Id (电流矢量在d轴的分量) 的 PID 控制,根据 Id 的目标值id_aim和 Id 的实际值id算出执行变量 Vd电压矢量在d轴上的分量
// 输入 电流矢量在d轴的分量的实际值id
// 电流矢量在d轴的分量的目标值id_aim
// 输出 电压矢量在d轴上的分量vd
// 原理 PID 控制实际上没有D只有P和I
// 输出更新en_idq 每产生一个高电平脉冲后的若干周期后 Vd 更新,即更新频率 = 控制周期
pi_controller #(
.Kp ( Kp ),
.Ki ( Ki )
) pi_id_i (
.rstn ( init_done ),
.clk ( clk ),
.i_en ( en_idq ),
.i_aim ( id_aim ), // input : Idaim
.i_real ( id ), // input : Id
.o_en ( ),
.o_value ( vd ) // output: Vd
);
// 简介 :该模块用于进行 Iq (电流矢量在q轴的分量) 的 PID 控制,根据 Iq 的目标值iq_aim和 Iq 的实际值iq算出执行变量 Vq电压矢量在d轴上的分量
// 输入 电流矢量在q轴的分量的实际值iq
// 电流矢量在q轴的分量的目标值iq_aim
// 输出 电压矢量在q轴上的分量vq
// 原理 PID 控制实际上没有D只有P和I
// 输出更新en_idq 每产生一个高电平脉冲后的若干周期后 Vq 更新,即更新频率 = 控制周期
pi_controller #(
.Kp ( Kp ),
.Ki ( Ki )
) pi_iq_i (
.rstn ( init_done ),
.clk ( clk ),
.i_en ( en_idq ),
.i_aim ( iq_aim ), // input : Iqaim
.i_real ( iq ), // input : Iq
.o_en ( ),
.o_value ( vq ) // output: Vq
);
// 简介 :该模块用于把电压矢量从转子直角坐标系 (Vd, Vq) 变换到转子极坐标系 (Vrρ, Vrθ)
// 输入 :电压矢量在转子直角坐标系的 d 轴上的分量Vd
// 电压矢量在转子直角坐标系的 q 轴上的分量Vq
// 输出 电压矢量在转子极坐标系上的幅值Vrρ
// 原理 电压矢量在转子极坐标系上的角度Vrθ
// 输出更新Vd, Vq 每产生变化的若干周期后 Vrρ 和 Vrθ 更新,更新频率 = 控制周期
cartesian2polar cartesian2polar_i (
.rstn ( init_done ),
.clk ( clk ),
.i_en ( 1'b1 ),
.i_x ( vd ), // input : Vd
.i_y ( vq ), // input : Vq
.o_en ( ),
.o_rho ( vr_rho ), // output: Vrρ
.o_theta ( vr_theta ) // output: Vrθ
);
// 简介 :该 always 块用于进行初始化 和 反park变换
// 一、初始化: 进行初始机械角度标定。首先令 Vsρ 取最大Vsθ=0则转子自然会转到电角度 ψ=0 的地方。然后记录下此时的机械角度 φ 作为初始机械角度 Φ 。则之后就可以用公式 ψ = N * (φ - Φ) 计算电角度。
// 二、反park变换 初始化完成后,持续地把电压矢量从转子极坐标系 (Vrρ, Vrθ) 变换到 定子极坐标系 (Vsρ, Vsθ)
// 输入 φVrρ, Vrθ
// 输出 ΦVsρ, Vsθinit_done
always @ (posedge clk or negedge rstn)
if(~rstn) begin
{vs_rho, vs_theta} <= '0;
init_cnt <= '0;
init_phi <= '0;
init_done <= 1'b0;
end else begin
if(init_cnt<=INIT_CYCLES) begin // 若 init_cnt 计数变量 <= INIT_CYCLES ,则初始化未完成
vs_rho <= 12'd4095; // 初始化阶段令 Vsρ 取最大
vs_theta <= 12'd0; // 初始化阶段令 Vsθ = 0
init_cnt <= init_cnt + 1;
if(init_cnt==INIT_CYCLES) begin // 若 init_cnt 计数变量 == INIT_CYCLES , 说明初始化即将完成
init_phi <= phi; // 记录当前机械角度φ 作为初始机械角度 Φ
init_done <= 1'b1; // 令 init_done = 1 ,指示初始化结束
end
end else begin // 若 init_cnt 计数变量 > INIT_CYCLES ,则初始化完成
vs_rho <= vr_rho; // 反park变换。由于幅值的旋转不变性Vsρ = Vrρ
vs_theta <= vr_theta + psi; // 反park变换。由于转子极坐标系是定子极坐标系旋转 ψ 得来,所以 Vsθ = Vrθ + ψ
end
end
// 简介 该模块是7段式 SVPWM 发生器,用于生成 3 相上的 PWM 信号。
// 输入 :定子极坐标系下的电压矢量 Vsρ, Vsθ
// 输出 PWM使能信号 pwm_en
// 3相PWM信号 pwm_a, pwm_b, pwm_c
// 说明 :该模块产生的 PWM 的频率是 clk 频率 / 2048。例如 clk 为 36.864MHz ,则 PWM 的频率为 36.864MHz / 2048 = 18kHz
svpwm svpwm_i (
.rstn ( rstn ),
.clk ( clk ),
.v_amp ( MAX_AMP ),
.v_rho ( vs_rho ), // input : Vsρ
.v_theta ( vs_theta ), // input : Vsθ
.pwm_en ( pwm_en ), // output
.pwm_a ( pwm_a ), // output
.pwm_b ( pwm_b ), // output
.pwm_c ( pwm_c ) // output
);
// 简介 :该模块用于控制相电流检测 ADC 的采样时机
// 输入 3相PWM信号 pwm_a, pwm_b, pwm_c
// 输出 3相电流 ADC 采样时刻控制信号 sn_adc
// 原理 :该模块检测 pwm_a, pwm_b, pwm_c 均为低电平的时刻,并延迟 SAMPLE_DELAY 的时钟周期,在 sn_adc 信号上产生一个时钟周期的高电平。
hold_detect #(
.SAMPLE_DELAY ( SAMPLE_DELAY )
) adc_sn_ctrl_i (
.rstn ( init_done ),
.clk ( clk ),
.in ( ~pwm_a & ~pwm_b & ~pwm_c ), // input : 当 pwm_a, pwm_b, pwm_c 均为低电平时=1否则=0
.out ( sn_adc ) // output: 若输入信号=1并保持 SAMPLE_DELAY 个周期,则 sn_adc 上产生1个周期的高电平脉冲
);
endmodule

40
RTL/foc/hold_detect.sv Normal file
View File

@ -0,0 +1,40 @@
`timescale 1 ns/1 ns
module hold_detect #(
parameter [15:0] SAMPLE_DELAY = 16'd100
) (
input wire rstn,
input wire clk,
input wire in,
output reg out
);
reg latch1, latch2;
reg [15:0] cnt;
always @ (posedge clk or negedge rstn)
if(~rstn)
{latch1, latch2} <= '1;
else
{latch1, latch2} <= {in, latch1};
always @ (posedge clk or negedge rstn)
if(~rstn) begin
out <= 1'b0;
cnt <= 16'd0;
end else begin
out <= 1'b0;
if(latch1) begin
if(latch2) begin
if( cnt != 16'd0 )
cnt <= cnt - 16'd1;
out <= cnt == 16'd1;
end else begin
cnt <= SAMPLE_DELAY;
end
end else begin
cnt <= 16'd0;
end
end
endmodule

53
RTL/foc/park_tr.sv Normal file
View File

@ -0,0 +1,53 @@
`timescale 1 ns/1 ns
module park_tr(
input wire rstn,
input wire clk,
input wire [11:0] psi,
input wire i_en,
input wire signed [15:0] i_ialpha, i_ibeta,
output reg o_en,
output reg signed [15:0] o_id, o_iq
);
reg signed [15:0] sin_psi, cos_psi; // -1~+1 is mapped to -16384~+16384
reg en_s1;
reg signed [31:0] alpha_cos, alpha_sin, beta_cos, beta_sin;
wire signed[31:0] ide = alpha_cos + beta_sin;
wire signed[31:0] iqe = beta_cos - alpha_sin;
sincos sincos_i(
.rstn ( rstn ),
.clk ( clk ),
.i_en ( 1'b1 ),
.i_theta ( psi ),
.o_en ( ),
.o_sin ( sin_psi ),
.o_cos ( cos_psi )
);
always @ (posedge clk or negedge rstn)
if(~rstn) begin
{en_s1, alpha_cos, alpha_sin, beta_cos, beta_sin} <= '0;
end else begin
en_s1 <= i_en;
alpha_cos <= i_ialpha * cos_psi;
alpha_sin <= i_ialpha * sin_psi;
beta_cos <= i_ibeta * cos_psi;
beta_sin <= i_ibeta * sin_psi;
end
always @ (posedge clk or negedge rstn)
if(~rstn) begin
{o_en, o_id, o_iq} <= '0;
end else begin
o_en <= en_s1;
if(en_s1) begin
o_id <= ide[31:16];
o_iq <= iqe[31:16];
end
end
endmodule

102
RTL/foc/pi_controller.sv Normal file
View File

@ -0,0 +1,102 @@
`timescale 1 ns/1 ns
module pi_controller #(
parameter logic [23:0] Kp = 24'd32768,
parameter logic [23:0] Ki = 24'd2
) (
input wire rstn,
input wire clk,
input wire i_en,
input wire signed [15:0] i_aim,
input wire signed [15:0] i_real,
output reg o_en,
output wire signed [15:0] o_value
);
reg en1, en2, en3, en4;
reg signed [31:0] pdelta, idelta, kpdelta1, kpdelta, kidelta, kpidelta, value;
assign o_value = value[31:16];
function automatic logic signed [31:0] protect_add(input logic signed [31:0] a, input logic signed [31:0] b);
automatic logic signed [32:0] y;
y = $signed({a[31],a}) + $signed({b[31],b});
if( y > $signed(33'h7fffffff) )
return $signed(32'h7fffffff);
else if(y < -$signed(33'h7fffffff) )
return -$signed(32'h7fffffff);
else
return $signed(y[31:0]);
endfunction
function automatic logic signed [31:0] protect_mul(input logic signed [31:0] a, input logic signed [24:0] b);
automatic logic signed [56:0] y;
y = a * b;
if( y > $signed(57'h7fffffff) )
return $signed(32'h7fffffff);
else if(y < -$signed(57'h7fffffff) )
return -$signed(32'h7fffffff);
else
return $signed(y[31:0]);
endfunction
always @ (posedge clk or negedge rstn)
if(~rstn) begin
en1 <= 1'b0;
pdelta <= 0;
end else begin
en1 <= i_en;
if(i_en) begin
pdelta <= $signed({{16{i_aim[15]}},i_aim}) - $signed({{16{i_real[15]}},i_real});
end
end
always @ (posedge clk or negedge rstn)
if(~rstn) begin
en2 <= 1'b0;
kpdelta1 <= 0;
idelta <= 0;
end else begin
en2 <= en1;
if(en1) begin
kpdelta1 <= protect_mul(pdelta, $signed({1'b0,Kp}) );
idelta <= protect_add(idelta, pdelta);
end
end
always @ (posedge clk or negedge rstn)
if(~rstn) begin
en3 <= 1'b0;
kpdelta <= 0;
kidelta <= 0;
end else begin
en3 <= en2;
if(en2) begin
kpdelta <= kpdelta1;
kidelta <= protect_mul(idelta, $signed({1'b0,Ki}) );
end
end
always @ (posedge clk or negedge rstn)
if(~rstn) begin
en4 <= 1'b0;
kpidelta <= 0;
end else begin
en4 <= en3;
if(en3) begin
kpidelta <= protect_add(kpdelta, kidelta);
end
end
always @ (posedge clk or negedge rstn)
if(~rstn) begin
o_en <= 1'b0;
value <= 0;
end else begin
o_en <= en4;
if(en4) begin
value <= protect_add(value, kpidelta);
end
end
endmodule

1118
RTL/foc/sincos.sv Normal file

File diff suppressed because it is too large Load Diff

1174
RTL/foc/svpwm.sv Normal file

File diff suppressed because it is too large Load Diff

120
RTL/pll.v Normal file
View File

@ -0,0 +1,120 @@
// PLL 50MHz 时钟产生 36.864 MHz 时钟
// 该模块仅适用于 Altera Cyclone IV FPGA 对于其他厂家或系列的FPGA请使用各自相同效果的IP核/原语例如Xilinx的clock wizard代替该模块
// synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module pll (
inclk0,
c0,
locked);
input inclk0;
output c0;
output locked;
wire [4:0] sub_wire0;
wire sub_wire2;
wire [0:0] sub_wire5 = 1'h0;
wire [0:0] sub_wire1 = sub_wire0[0:0];
wire c0 = sub_wire1;
wire locked = sub_wire2;
wire sub_wire3 = inclk0;
wire [1:0] sub_wire4 = {sub_wire5, sub_wire3};
altpll altpll_component (
.inclk (sub_wire4),
.clk (sub_wire0),
.locked (sub_wire2),
.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
altpll_component.bandwidth_type = "AUTO",
altpll_component.clk0_divide_by = 99,
altpll_component.clk0_duty_cycle = 50,
altpll_component.clk0_multiply_by = 73,
altpll_component.clk0_phase_shift = "0",
altpll_component.compensate_clock = "CLK0",
altpll_component.inclk0_input_frequency = 20000,
altpll_component.intended_device_family = "Cyclone IV E",
altpll_component.lpm_hint = "CBX_MODULE_PREFIX=pll",
altpll_component.lpm_type = "altpll",
altpll_component.operation_mode = "NORMAL",
altpll_component.pll_type = "AUTO",
altpll_component.port_activeclock = "PORT_UNUSED",
altpll_component.port_areset = "PORT_UNUSED",
altpll_component.port_clkbad0 = "PORT_UNUSED",
altpll_component.port_clkbad1 = "PORT_UNUSED",
altpll_component.port_clkloss = "PORT_UNUSED",
altpll_component.port_clkswitch = "PORT_UNUSED",
altpll_component.port_configupdate = "PORT_UNUSED",
altpll_component.port_fbin = "PORT_UNUSED",
altpll_component.port_inclk0 = "PORT_USED",
altpll_component.port_inclk1 = "PORT_UNUSED",
altpll_component.port_locked = "PORT_USED",
altpll_component.port_pfdena = "PORT_UNUSED",
altpll_component.port_phasecounterselect = "PORT_UNUSED",
altpll_component.port_phasedone = "PORT_UNUSED",
altpll_component.port_phasestep = "PORT_UNUSED",
altpll_component.port_phaseupdown = "PORT_UNUSED",
altpll_component.port_pllena = "PORT_UNUSED",
altpll_component.port_scanaclr = "PORT_UNUSED",
altpll_component.port_scanclk = "PORT_UNUSED",
altpll_component.port_scanclkena = "PORT_UNUSED",
altpll_component.port_scandata = "PORT_UNUSED",
altpll_component.port_scandataout = "PORT_UNUSED",
altpll_component.port_scandone = "PORT_UNUSED",
altpll_component.port_scanread = "PORT_UNUSED",
altpll_component.port_scanwrite = "PORT_UNUSED",
altpll_component.port_clk0 = "PORT_USED",
altpll_component.port_clk1 = "PORT_UNUSED",
altpll_component.port_clk2 = "PORT_UNUSED",
altpll_component.port_clk3 = "PORT_UNUSED",
altpll_component.port_clk4 = "PORT_UNUSED",
altpll_component.port_clk5 = "PORT_UNUSED",
altpll_component.port_clkena0 = "PORT_UNUSED",
altpll_component.port_clkena1 = "PORT_UNUSED",
altpll_component.port_clkena2 = "PORT_UNUSED",
altpll_component.port_clkena3 = "PORT_UNUSED",
altpll_component.port_clkena4 = "PORT_UNUSED",
altpll_component.port_clkena5 = "PORT_UNUSED",
altpll_component.port_extclk0 = "PORT_UNUSED",
altpll_component.port_extclk1 = "PORT_UNUSED",
altpll_component.port_extclk2 = "PORT_UNUSED",
altpll_component.port_extclk3 = "PORT_UNUSED",
altpll_component.self_reset_on_loss_lock = "OFF",
altpll_component.width_clock = 5;
endmodule

135
RTL/sensors/adc_ad7928.sv Normal file
View File

@ -0,0 +1,135 @@
`timescale 1 ns/1 ns
// 模块: adc_ad7928
// 功能:通过 SPI 接口从 ADC7928 (ADC芯片) 中读出 ADC 值。
// 参数:详见下方注释,该模块可以使用参数完全自由地配置单次转换要用多少个通道以及用哪些通道
// 输入输出:详见下方注释
module adc_ad7928 #(
parameter [2:0] CH_CNT = 3'd7, // 单次 ADC 转换使用的通道数为 CH_CNT+1例如若 CH_CNT=0则只使用 CH0 。若 CH_CNT=2则使用 CH0,CH1,CH2。 若 CH_CNT=7则使用 CH0,CH1,CH2,CH3,CH4,CH5,CH6,CH7。用的通道越多ADC转换时延越长即从 sn_adc 到 en_adc 之间的时间差越长)
parameter [2:0] CH0 = 3'd0, // 指示了 CH0 对应 AD7928 的哪个通道
parameter [2:0] CH1 = 3'd1, // 指示了 CH1 对应 AD7928 的哪个通道
parameter [2:0] CH2 = 3'd2, // 指示了 CH2 对应 AD7928 的哪个通道
parameter [2:0] CH3 = 3'd3, // 指示了 CH3 对应 AD7928 的哪个通道
parameter [2:0] CH4 = 3'd4, // 指示了 CH4 对应 AD7928 的哪个通道
parameter [2:0] CH5 = 3'd5, // 指示了 CH5 对应 AD7928 的哪个通道
parameter [2:0] CH6 = 3'd6, // 指示了 CH6 对应 AD7928 的哪个通道
parameter [2:0] CH7 = 3'd7 // 指示了 CH7 对应 AD7928 的哪个通道
) (
input wire rstn,
input wire clk,
// -------------------- SPI 接口,应该接到 AD7928 芯片上 ---------------------------------------------------------------
output reg spi_ss, // SPI 接口SS
output reg spi_sck, // SPI 接口SCK
output reg spi_mosi, // SPI 接口MOSI
input wire spi_miso, // SPI 接口MISO
// -------------------- 用户逻辑接口 ------------------------------------------------------------------------------------
input wire i_sn_adc, // ADC 转换开始信号,当 i_sn_adc 上出现高电平脉冲时ADC转换开始
output reg o_en_adc, // ADC 转换完成信号当转换完成时o_en_adc 产生一个时钟周期的高电平脉冲
output wire [11:0] o_adc_value0,// 当 o_en_adc 产生一个时钟周期的高电平脉冲时CH0 的 ADC 转换结果出现在该信号上
output wire [11:0] o_adc_value1,// 当 o_en_adc 产生一个时钟周期的高电平脉冲时CH1 的 ADC 转换结果出现在该信号上
output wire [11:0] o_adc_value2,// 当 o_en_adc 产生一个时钟周期的高电平脉冲时CH2 的 ADC 转换结果出现在该信号上
output wire [11:0] o_adc_value3,// 当 o_en_adc 产生一个时钟周期的高电平脉冲时CH3 的 ADC 转换结果出现在该信号上
output wire [11:0] o_adc_value4,// 当 o_en_adc 产生一个时钟周期的高电平脉冲时CH4 的 ADC 转换结果出现在该信号上
output wire [11:0] o_adc_value5,// 当 o_en_adc 产生一个时钟周期的高电平脉冲时CH5 的 ADC 转换结果出现在该信号上
output wire [11:0] o_adc_value6,// 当 o_en_adc 产生一个时钟周期的高电平脉冲时CH6 的 ADC 转换结果出现在该信号上
output wire [11:0] o_adc_value7 // 当 o_en_adc 产生一个时钟周期的高电平脉冲时CH7 的 ADC 转换结果出现在该信号上
);
localparam WAIT_CNT = 8'd6;
wire [2:0] channels [8];
assign channels[0] = CH0;
assign channels[1] = CH1;
assign channels[2] = CH2;
assign channels[3] = CH3;
assign channels[4] = CH4;
assign channels[5] = CH5;
assign channels[6] = CH6;
assign channels[7] = CH7;
reg [ 7:0] cnt;
reg [ 2:0] idx;
reg [ 2:0] addr;
reg [11:0] wshift;
reg nfirst;
reg [11:0] data_in_latch;
reg sck_pre;
reg [11:0] ch_value [8];
assign o_adc_value0 = ch_value[0];
assign o_adc_value1 = ch_value[1];
assign o_adc_value2 = ch_value[2];
assign o_adc_value3 = ch_value[3];
assign o_adc_value4 = ch_value[4];
assign o_adc_value5 = ch_value[5];
assign o_adc_value6 = ch_value[6];
assign o_adc_value7 = ch_value[7];
always @ (posedge clk or negedge rstn)
if(~rstn)
spi_sck <= 1'b1;
else
spi_sck <= sck_pre;
always @ (posedge clk or negedge rstn)
if(~rstn) begin
cnt <= '0;
idx <= 3'd7;
addr <= 3'd0;
wshift <= '1;
{spi_ss, sck_pre, spi_mosi} <= '1;
end else begin
if(cnt==8'd0) begin
{spi_ss, sck_pre, spi_mosi} <= '1;
if(idx!='0) begin
cnt <= 8'd1;
idx <= idx - 3'd1;
end else if(i_sn_adc) begin
cnt <= 8'd1;
idx <= CH_CNT;
end
end else if(cnt==8'd1) begin
{spi_ss, sck_pre, spi_mosi} <= '1;
addr <= (idx=='0) ? CH_CNT : idx - 3'd1;
cnt <= cnt + 8'd1;
end else if(cnt==8'd2) begin
{spi_ss, sck_pre, spi_mosi} <= '1;
wshift <= {1'b1, 1'b0, 1'b0, channels[addr], 2'b11, 1'b0, 1'b0, 2'b11};
cnt <= cnt + 8'd1;
end else if(cnt<WAIT_CNT) begin
{spi_ss, sck_pre, spi_mosi} <= '1;
cnt <= cnt + 8'd1;
end else if(cnt<WAIT_CNT+8'd32) begin
spi_ss <= 1'b0;
sck_pre <= ~sck_pre;
if(sck_pre)
{spi_mosi,wshift} <= {wshift,1'b1};
cnt <= cnt + 8'd1;
end else begin
spi_ss <= 1'b0;
{sck_pre, spi_mosi} <= '1;
cnt <= 8'd0;
end
end
always @ (posedge clk or negedge rstn)
if(~rstn) begin
o_en_adc <= 1'b0;
nfirst <= 1'b0;
data_in_latch <= '0;
for(int ii=0; ii<8; ii++) ch_value[ii] <= '0;
end else begin
o_en_adc <= 1'b0;
if(cnt>=WAIT_CNT+8'd2 && cnt<WAIT_CNT+8'd32) begin
if(~spi_sck)
data_in_latch <= {data_in_latch[10:0], spi_miso};
end else if(cnt==WAIT_CNT+8'd32) begin
if(idx=='0) begin
nfirst <= 1'b1;
o_en_adc <= nfirst;
end
ch_value[idx] <= data_in_latch;
end
end
endmodule

View File

@ -0,0 +1,35 @@
`timescale 1 ns/1 ns
// 模块: as5600_read
// 功能:通过 I2C 接口从 AS5600 磁编码器中读出转子机械角度 φ
// 参数:详见下方注释
// 输入输出:详见下方注释
module as5600_read #(
parameter [15:0] CLK_DIV = 16'd10 // I2C SCL 时钟信号分频系数SCL 时钟频率 = clk频率 / (4*CLK_DIV) ,例如若 clk 为 40MHzCLK_DIV=10则 SCL 频率为 40/(4*10) = 1MHz。注AS5600 芯片要求 SCL 频率不超过 1MHz
)(
input wire rstn,
input wire clk,
output wire scl, // I2C SCL 信号
inout sda, // I2C SDA 信号
output reg o_en, // 不断读取转子机械角度 φ每读出一个新值o_en 产生一个高电平脉冲
output reg [11:0] o_phi // o_en 产生一个高电平脉冲的同时o_phi 上产生最新的转自角度取值范围0~4095。0对应0°1024对应90°2048对应180°3072对应270°。
);
wire [15:0] regout;
assign o_phi = regout[11:0];
i2c_register_read #(
.CLK_DIV ( CLK_DIV )
) i2c_register_read_i (
.rstn ( rstn ),
.clk ( clk ),
.scl ( scl ),
.sda ( sda ),
.start ( 1'b1 ),
.ready ( ),
.done ( o_en ),
.regout ( regout )
);
endmodule

View File

@ -0,0 +1,138 @@
`timescale 1 ns/1 ns
module i2c_register_read #(
parameter [15:0] CLK_DIV = 16'd16,
parameter [ 6:0] SLAVE_ADDR = 7'h36,
parameter [ 7:0] REGISTER_ADDR = 8'h0E
) (
input wire rstn,
input wire clk,
output reg scl,
inout sda,
input wire start,
output wire ready,
output reg done,
output reg [15:0] regout
);
localparam [15:0] CLK_DIV_PARSED = CLK_DIV>16'd0 ? CLK_DIV-16'd1 : 16'd0;
reg sda_e, sda_o;
assign sda = sda_e ? sda_o : 1'bz;
reg epoch;
reg [15:0] clkcnt;
reg [ 7:0] cnt;
reg [ 7:0] send_shift;
reg [15:0] recv_shift;
always @ (posedge clk or negedge rstn)
if(~rstn) begin
epoch <= 1'b0;
clkcnt <= '0;
end else begin
if(clkcnt==CLK_DIV_PARSED) begin
epoch <= 1'b1;
clkcnt <= '0;
end else begin
epoch <= 1'b0;
clkcnt <= clkcnt + 16'd1;
end
end
assign ready = (cnt=='0);
always @ (posedge clk or negedge rstn)
if(~rstn) begin
{scl, sda_e, sda_o} <= '1;
cnt <= '0;
send_shift <= '0;
recv_shift <= '0;
regout <= '0;
done <= 1'b0;
end else begin
if(ready) begin
{scl, sda_e, sda_o} <= '1;
if(start) begin
cnt <= 8'd1;
end
end else if(done) begin
done <= 1'b0;
cnt <= 8'd0;
end else if(epoch) begin
cnt <= cnt + 8'd1;
if(cnt<8'd2) begin
end else if(cnt< 8'd4) begin
sda_o <= 1'b0;
send_shift <= {SLAVE_ADDR, 1'b0};
end else if(cnt< 8'd37) begin
scl <= cnt[1];
if(cnt[1:0]==2'b01) begin
{sda_o, send_shift} <= {send_shift, 1'b1};
end
end else if(cnt< 8'd40) begin
send_shift <= REGISTER_ADDR;
scl <= cnt[1];
sda_e <= 1'b0;
end else if(cnt< 8'd73) begin
scl <= cnt[1];
if(cnt[1:0]==2'b01) begin
sda_e <= 1'b1;
{sda_o, send_shift} <= {send_shift, 1'b1};
end
end else if(cnt< 8'd77) begin
scl <= cnt[1];
sda_e <= 1'b0;
end else if(cnt==8'd77) begin
scl <= cnt[1];
sda_e <= 1'b1;
sda_o <= 1'b1;
end else if(cnt< 8'd82) begin
scl <= 1'b1;
end else if(cnt< 8'd84) begin
scl <= 1'b1;
sda_o <= 1'b0;
send_shift <= {SLAVE_ADDR, 1'b1};
end else if(cnt< 8'd117) begin
scl <= cnt[1];
if(cnt[1:0]==2'b01) begin
{sda_o, send_shift} <= {send_shift, 1'b1};
end
end else if(cnt< 8'd121) begin
scl <= cnt[1];
sda_e <= 1'b0;
end else if(cnt< 8'd153) begin
scl <= cnt[1];
sda_e <= 1'b0;
if( cnt[1:0]==2'b11 )
recv_shift <= {recv_shift[14:0], sda};
end else if(cnt< 8'd157) begin
scl <= cnt[1];
sda_e <= 1'b1;
sda_o <= 1'b0;
end else if(cnt< 8'd189) begin
scl <= cnt[1];
sda_e <= 1'b0;
if( cnt[1:0]==2'b11 )
recv_shift <= {recv_shift[14:0], sda};
end else if(cnt< 8'd193) begin
scl <= cnt[1];
sda_e <= 1'b1;
sda_o <= 1'b1;
end else if(cnt< 8'd195) begin
scl <= 1'b0;
sda_o <= 1'b0;
end else if(cnt< 8'd198) begin
scl <= 1'b1;
sda_o <= 1'b0;
end else if(cnt< 8'd204) begin
sda_o <= 1'b1;
regout <= recv_shift;
end else begin
done <= 1'b1;
end
end
end
endmodule

164
RTL/top.sv Normal file
View File

@ -0,0 +1,164 @@
`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.864MHzCLK_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.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 “可以采样了”
.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

52
RTL/uart/itoa.sv Normal file
View File

@ -0,0 +1,52 @@
`timescale 1 ns/1 ns
module itoa(
input wire rstn,
input wire clk,
input wire i_en,
input wire signed [15:0] i_val,
output reg o_en,
output reg [ 7:0] o_str [6]
);
reg [ 2:0] cnt;
reg sign;
reg zero;
reg [15:0] abs;
reg [ 3:0] rem;
always @ (posedge clk or negedge rstn)
if(~rstn) begin
cnt <= 3'd0;
{sign, abs, zero, rem} <= '0;
o_en <= 1'b0;
o_str <= '{6{'0}};
end else begin
if(cnt==3'd0) begin
if(i_en)
cnt <= 3'd1;
sign <= i_val[15];
abs <= i_val[15] ? $unsigned(-i_val) : $unsigned(i_val);
end else begin
cnt <= cnt + 3'd1;
abs <= abs / 16'd10;
rem <= abs % 16'd10;
zero <= abs==16'd0;
if(cnt>3'd1) begin
o_str[5] <= o_str[4];
o_str[4] <= o_str[3];
o_str[3] <= o_str[2];
o_str[2] <= o_str[1];
o_str[1] <= o_str[0];
if(cnt>3'd2 && zero) begin
o_str[0] <= sign ? 8'h2D : 8'h20;
sign <= 1'b0;
end else begin
o_str[0] <= {4'h3, rem};
end
end
end
o_en <= cnt == 3'd7;
end
endmodule

131
RTL/uart/uart_monitor.sv Normal file
View File

@ -0,0 +1,131 @@
`timescale 1 ns/1 ns
// 模块top
// 功能UART发送器格式为115200,8,n,1可以把 i_val0, i_val1, i_val2, i_val3 变成10进制格式放在一行里通过 UART 发送出去,
// 参数:无
// 输入输出:详见下方注释
module uart_monitor #(
parameter [15:0] CLK_DIV = 217 // UART分频倍率例如若时钟频率为 36.864MHz, CLK_DIV=320则 UART 波特率为 36.864MHz/320=115200
) (
input wire rstn,
input wire clk,
input wire i_en,
input wire signed [15:0] i_val0,
input wire signed [15:0] i_val1,
input wire signed [15:0] i_val2,
input wire signed [15:0] i_val3,
output wire o_uart_tx // UART TX 信号
);
enum logic [2:0] {IDLE, SELECT, WAIT, PARSING, SENDING} stat;
wire tx_rdy;
reg tx_en;
reg [7:0] tx_data;
reg itoa_en;
reg signed [15:0] itoa_val;
wire itoa_oen;
wire [ 7:0] itoa_str [6];
reg [ 2:0] vcnt;
reg [ 2:0] cnt;
reg [ 7:0] eov;
wire [ 7:0] s_str[8];
assign s_str[0] = itoa_str[0];
assign s_str[1] = itoa_str[1];
assign s_str[2] = itoa_str[2];
assign s_str[3] = itoa_str[3];
assign s_str[4] = itoa_str[4];
assign s_str[5] = itoa_str[5];
assign s_str[6] = 8'h20;
assign s_str[7] = eov;
always_comb begin
tx_en = 1'b0;
tx_data = '0;
if(stat==SENDING) begin
tx_en = 1'b1;
tx_data = s_str[cnt];
end
end
always @ (posedge clk or negedge rstn)
if(~rstn) begin
stat <= IDLE;
itoa_en <= 1'b0;
itoa_val <= '0;
vcnt <= '0;
cnt <= '0;
eov <= 8'h20;
end else begin
itoa_en <= 1'b0;
case(stat)
IDLE: if(i_en)
stat <= SELECT;
SELECT: begin
if (vcnt==3'd0) begin
vcnt <= vcnt + 3'd1;
stat <= WAIT;
itoa_en <= 1'b1;
itoa_val <= i_val0;
eov <= 8'h20;
end else if(vcnt==3'd1) begin
vcnt <= vcnt + 3'd1;
stat <= WAIT;
itoa_en <= 1'b1;
itoa_val <= i_val1;
eov <= 8'h20;
end else if(vcnt==3'd2) begin
vcnt <= vcnt + 3'd1;
stat <= WAIT;
itoa_en <= 1'b1;
itoa_val <= i_val2;
eov <= 8'h20;
end else if(vcnt==3'd3) begin
vcnt <= vcnt + 3'd1;
stat <= WAIT;
itoa_en <= 1'b1;
itoa_val <= i_val3;
eov <= 8'h0A;
end else begin
vcnt <= 3'd0;
stat <= IDLE;
eov <= 8'h20;
end
end
WAIT:
stat <= PARSING;
PARSING: if(itoa_oen)
stat <= SENDING;
SENDING: if(tx_rdy) begin
cnt <= cnt + 3'd1;
if(cnt==3'd7)
stat <= SELECT;
end
endcase
end
itoa itoa_i (
.rstn ( rstn ),
.clk ( clk ),
.i_en ( itoa_en ),
.i_val ( itoa_val ),
.o_en ( itoa_oen ),
.o_str ( itoa_str )
);
uart_tx #(
.CLK_DIV ( CLK_DIV )
) uart_tx_i (
.rstn ( rstn ),
.clk ( clk ),
.i_e ( tx_en ),
.i_r ( tx_rdy ),
.i_d ( tx_data ),
.tx ( o_uart_tx )
);
endmodule

47
RTL/uart/uart_tx.sv Normal file
View File

@ -0,0 +1,47 @@
`timescale 1 ns/1 ns
module uart_tx #(
parameter [15:0] CLK_DIV = 217 // 25MHz / 217 = 115207 ~= 115200
)(
input wire rstn, // active-low reset
input wire clk,
input wire i_e,
output wire i_r,
input wire [ 7:0] i_d,
output reg tx
);
reg [15:0] ccnt;
reg [ 3:0] cnt;
reg [12:1] tx_shift;
assign i_r = (cnt==4'd0);
always @ (posedge clk or negedge rstn)
if(~rstn) begin
tx <= 1'b1;
ccnt <= '0;
cnt <= '0;
tx_shift <= '1;
end else begin
if(cnt==4'd0) begin
tx <= 1'b1;
ccnt <= '0;
if(i_e) begin
cnt <= 4'd12;
tx_shift <= {2'b10, i_d[0], i_d[1], i_d[2], i_d[3], i_d[4], i_d[5], i_d[6], i_d[7], 2'b11};
end
end else begin
tx <= tx_shift[cnt];
if( ccnt + 16'd1 < CLK_DIV ) begin
ccnt <= ccnt + 16'd1;
end else begin
ccnt <= '0;
cnt <= cnt - 4'd1;
end
end
end
endmodule

BIN
diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
wave.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB