update
@ -63,7 +63,7 @@ set_global_assignment -name DEVICE EP4CE15E22I7
|
|||||||
set_global_assignment -name TOP_LEVEL_ENTITY top
|
set_global_assignment -name TOP_LEVEL_ENTITY top
|
||||||
set_global_assignment -name ORIGINAL_QUARTUS_VERSION 13.1
|
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 PROJECT_CREATION_TIME_DATE "17:37:36 FEBRUARY 05, 2021"
|
||||||
set_global_assignment -name LAST_QUARTUS_VERSION 13.1
|
set_global_assignment -name LAST_QUARTUS_VERSION "18.1.0 Standard Edition"
|
||||||
set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output_files
|
set_global_assignment -name PROJECT_OUTPUT_DIRECTORY output_files
|
||||||
set_global_assignment -name MIN_CORE_JUNCTION_TEMP "-40"
|
set_global_assignment -name MIN_CORE_JUNCTION_TEMP "-40"
|
||||||
set_global_assignment -name MAX_CORE_JUNCTION_TEMP 100
|
set_global_assignment -name MAX_CORE_JUNCTION_TEMP 100
|
||||||
@ -74,10 +74,7 @@ set_global_assignment -name PARTITION_FITTER_PRESERVATION_LEVEL PLACEMENT_AND_RO
|
|||||||
set_global_assignment -name PARTITION_COLOR 16764057 -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 STRATIX_DEVICE_IO_STANDARD "2.5 V"
|
||||||
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/top.sv
|
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_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/as5600_read.sv
|
||||||
set_global_assignment -name SYSTEMVERILOG_FILE ../RTL/sensors/i2c_register_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/sensors/adc_ad7928.sv
|
||||||
|
232
README.md
@ -1,38 +1,50 @@
|
|||||||
![test](https://img.shields.io/badge/test-passing-green.svg)
|
![语言](https://img.shields.io/badge/语言-systemverilog_(IEEE1800_2005)-CAD09D.svg) ![仿真](https://img.shields.io/badge/仿真-iverilog-green.svg) ![部署](https://img.shields.io/badge/部署-quartus-blue.svg) ![部署](https://img.shields.io/badge/部署-vivado-FF1010.svg)
|
||||||
![docs](https://img.shields.io/badge/docs-passing-green.svg)
|
|
||||||
|
|
||||||
FpOC
|
FpOC
|
||||||
===========================
|
===========================
|
||||||
基于 **FPGA** 的**磁场定向控制 (FOC)**,用于驱动**永磁同步电机 (PMSM)**
|
基于 **FPGA** 的**磁场定向控制 (FOC)**,用于驱动**永磁同步电机 (PMSM)** 或**无刷直流电机 (BLDC)**
|
||||||
|
|
||||||
# 简介
|
**FOC控制算法**对传感器采样速率和处理器算力提出了一定的要求,使用 **FPGA** 实现的 **FOC** 可以获得更好的**实时性**,并且更方便进行**多路扩展**和**多路反馈协同**。
|
||||||
|
|
||||||
**FOC控制算法**对**传感器采样速率**和**处理器算力**提出了一定的要求,使用 **FPGA** 实现的 **FOC** 可以获得更好的**实时性**和零**延迟抖动**,并且更方便进行**多路扩展**。
|
本库实现了基于**角度传感器**(也就是磁编码器)的**有感 FOC**,即一个完整的**电流环**,可以进行**扭矩控制**。借助本库,你可以进一步使用 **纯FPGA** 或 **MCU+FPGA** 的方式实现更复杂的电机应用。
|
||||||
|
|
||||||
本库实现了基于**角度传感器**(例如磁编码器)的**有感 FOC**,即一个完整的**电流反馈环**,可以进行**扭矩控制**。借助本库,你可以进一步使用 **FPGA** 、**软核 MCU** 或**外置 MCU** 实现更复杂的电机应用。
|
| ![diagram](./figures/diagram.png) |
|
||||||
|
|
||||||
| ![diagram](./diagram.png) |
|
|
||||||
| :---: |
|
| :---: |
|
||||||
| 图1:系统框图 |
|
| **图1**:系统框图 |
|
||||||
|
|
||||||
该项目代码有**详细的注释**,结合参考资料 [6~9],可以带你快速地熟悉 **FOC** 。另外,一些用户在读代码时向本人反馈了一些疑问,我将它们整理在了 [FAQ](#FAQ) 里。
|
本库代码有详细的注释,如果你熟悉 Verilog 但不熟悉 FOC ,可以通过阅读代码来快速地学习 FOC (建议先阅读 FOC 原理 [6~9])。一些用户在读代码时向本人反馈了一些疑问,我将它们整理在了 [FAQ](#FAQ) 里。
|
||||||
|
|
||||||
## 技术特点
|
### 技术特点
|
||||||
|
|
||||||
* **平台无关** :纯 RTL 编写,可以在 Altera 和 Xilinx 等各种 FPGA 上运行。
|
* **平台无关** :纯 RTL 编写,可以在 Altera 和 Xilinx 等各种 FPGA 上运行。
|
||||||
* 支持 **12bit 分辨率**的**角度传感器**和**相电流采样ADC**,对于>12bit的传感器,需要进行低位截断。对于<12bit的传感器,需要进行低位补0。
|
* 支持 **3路PWM** + **1路EN** :PWM=1 时上桥臂 MOS 导通,PWM=0 时下桥臂 MOS 导通。 EN=0 时所有的 6 个 MOS 关断。
|
||||||
* 支持 **3路PWM** + **1路EN** :PWM=1 时上桥臂导通,PWM=0 时下桥臂导通。 EN=0 时所有 MOS 关断。
|
* 支持 **12bit 分辨率**的**角度传感器**和**相电流采样ADC**。对于>12bit的传感器,需要进行低位截断。对于<12bit的传感器,需要进行低位填充。
|
||||||
* 使用 **16bit 有符号整数**进行计算,降低了资源消耗,考虑到传感器为 12bit,16bit 计算是够用的。
|
* 内部使用 **16bit 有符号整数**进行计算,考虑到传感器为 12bit,所以 16bit 计算是够用的。
|
||||||
|
|
||||||
# 运行示例
|
### 目录
|
||||||
|
|
||||||
**图1** 是本库的系统框图,实现了一个简单的行为——控制电机的电流(扭矩)按顺时针,逆时针交替运行。同时,使用 UART 打印电流的**控制目标值**和**实际值**,以便观察控制的质量。
|
- [示例程序:让电机转起来](#示例程序:让电机转起来)
|
||||||
|
- [准备硬件](#准备硬件)
|
||||||
|
- [硬件连接与引脚分配](#硬件连接与引脚分配)
|
||||||
|
- [调参](#调参)
|
||||||
|
- [用串口监视电流环](#用串口监视电流环)
|
||||||
|
- [设计代码详解](#设计代码详解)
|
||||||
|
- [RTL仿真](#RTL仿真)
|
||||||
|
- [clark_tr和park_tr子模块的仿真](clark_tr和park_tr的仿真)
|
||||||
|
- [cartesian2polar和svpwm子模块的仿真](#cartesian2polar和svpwm的仿真)
|
||||||
|
- [FAQ](#FAQ)
|
||||||
|
|
||||||
该示例的所有代码都在 [./RTL](./RTL) 目录下。工程在 [./FPGA](./FPGA) 目录下,需要用 Quartus 软件打开。
|
|
||||||
|
|
||||||
|
# 示例程序:让电机转起来
|
||||||
|
|
||||||
|
**图1** 是示例程序的系统框图,它调用了我实现的 FOC 电流环模块 (foc_top.sv) 来实现一个简单的行为——控制电机的电流(扭矩)按顺时针、逆时针、顺时针、逆时针地交替运行。同时,使用 UART 打印电流的**控制目标值**和**实际值**,以便观察 FOC 电流环控制的质量。
|
||||||
|
|
||||||
|
该示例的所有代码都在 RTL 目录内。工程在 FPGA 目录内(这是一个 Quartus 工程,但由于本库是可移植性设计,也可以很容易地部署到 VIvado 上,只需要把不可移植的 PLL 核替换掉)。
|
||||||
|
|
||||||
## 准备硬件
|
## 准备硬件
|
||||||
|
|
||||||
需要准备以下硬件:
|
运行本库的电机驱动需要的硬件包括:
|
||||||
|
|
||||||
* **FPGA 开发板** :需有至少 **10** 个 **GPIO** ,用来接:
|
* **FPGA 开发板** :需有至少 **10** 个 **GPIO** ,用来接:
|
||||||
* **I2C (2*GPIO)** , 用来连接 AS5600 磁编码器。
|
* **I2C (2*GPIO)** , 用来连接 AS5600 磁编码器。
|
||||||
@ -41,19 +53,35 @@ FpOC
|
|||||||
* **PWM_EN (1*GPIO)** , 用来输出 1 路 EN (使能) 到电机驱动板(EN=低电平代表所有桥臂关断)。
|
* **PWM_EN (1*GPIO)** , 用来输出 1 路 EN (使能) 到电机驱动板(EN=低电平代表所有桥臂关断)。
|
||||||
* **UART (1*GPIO)** ,单向(仅发送)的UART,连接计算机的串口,用于监测电流环的跟随曲线,**可不接**。
|
* **UART (1*GPIO)** ,单向(仅发送)的UART,连接计算机的串口,用于监测电流环的跟随曲线,**可不接**。
|
||||||
* **PMSM** 或 **BLDC 电机**。
|
* **PMSM** 或 **BLDC 电机**。
|
||||||
* **获取转子角度的磁编码器**:本库支持的型号是 AS5600 ,需要安装在电机上。
|
* **电机驱动板**:要支持3相 PWM 输入信号(PWM=0时下桥臂导通,PWM=1时上桥臂导通),并且要内置**低侧电阻采样+放大器**对 3 相电流进行放大。
|
||||||
* **电机驱动板**:要支持3相 PWM 输入信号,并且要内置**低侧电阻采样法+放大器**对 3 相电流进行放大。
|
* **获取转子角度的磁编码器**:本库直接支持的型号是 AS5600 ,需要安装在电机上。
|
||||||
* **相电流采样的ADC**:本库支持的型号是 AD7928 ,用于采样电机驱动板放大后的 3 相电流
|
* **相电流采样的ADC**:本库直接支持的型号是 AD7928 ,用于采样电机驱动板放大后的 3 相电流
|
||||||
* 网上似乎找不到现成卖的 AD7928 模块,需要自己画 PCB。
|
* 网上似乎找不到现成卖的 AD7928 模块,所以如果你想DIY,就需要自己画 PCB。
|
||||||
|
|
||||||
|
如果你硬件动手能力很强,可以自己准备以上硬件。
|
||||||
|
|
||||||
|
不过!建议直接使用我画的一个自带 **AD7928** 的 **电机驱动板**,它的原理图如**图2**,我也提供了制造文件 [gerber_pcb_foc_shield.zip](./gerber_pcb_foc_shield.zip) ,你需要拿制造文件去打样PCB,然后按照**图2**来焊接。
|
||||||
|
|
||||||
|
> 注意,这个板子包括了**相电流采样的ADC**和**电机驱动板**的功能,但不包括 AS5600 磁编码器,磁编码器是需要安装在电机上的,需要你额外准备。
|
||||||
|
|
||||||
|
| ![wave](./figures/sch.png) |
|
||||||
|
| :---------------------------------------------------------: |
|
||||||
|
| **图2**:电机驱动板原理图,其中 AD7928 ADC 用来采样三相电流 |
|
||||||
|
|
||||||
|
这个板子在立创EDA开源,见 [oshwhub.com/wangxuan/arduino-foc-shield](https://oshwhub.com/wangxuan/arduino-foc-shield)
|
||||||
|
|
||||||
|
这个板子之所以被设计成 Arduino 扩展板的样子,是因为很多 FPGA 开发板也具有 Arduino 扩展板接口(比如 DE10-Nano 开发板),可以直接插上去。如果你的 FPGA 开发板没有 Arduino 扩展板接口也没关系,直接用杜邦线连接即可(建议用短的杜邦线)。
|
||||||
|
|
||||||
|
这里使用的电机驱动器是 **MP6540** 芯片, 我只试过驱动小功率云台电机,没试过大功率电机。
|
||||||
|
|
||||||
|
|
||||||
建议使用我画的一个自带 **AD7928 ADC** 的 **电机驱动板**,原理图和PCB开源[在此](https://oshwhub.com/wangxuan/arduino-foc-shield),它一个板子就能实现以上**相电流采样的ADC**和**电机驱动板**的功能。(使用的电机驱动器是 **MP6540** 芯片, 我只试过驱动小功率云台电机,没试过大功率电机)
|
|
||||||
|
|
||||||
## 硬件连接与引脚分配
|
## 硬件连接与引脚分配
|
||||||
|
|
||||||
该工程的顶层文件是 [top.sv](./RTL/top.sv) ,它的 IO 连接方法如下:
|
FPGA 工程的顶层文件是 [RTL/top.sv](./RTL/top.sv) ,它的 IO 连接方法如下:
|
||||||
|
|
||||||
* **clk_50m** : 连接在 FPGA 开发板自带的 **50MHz** 晶振上。
|
* **clk_50m** : 连接在 FPGA 开发板自带的 **50MHz** 晶振上。
|
||||||
* 若不是 **50MHz** 也没关系,读 [top.sv](./RTL/top.sv) 会发现,它用 PLL 把 **50MHz** 转换成 **36.864MHz** 去用,所以你只需要修改 PLL 的配置,把输入频率改成板上晶振的实际频率,输出频率仍保持 **36.864MHz** 即可。
|
* 若你的开发板的晶振不是 **50MHz** 也没关系,读 top.sv 就会发现,它用 PLL (altpll 原语) 把 **50MHz** 转换成 **36.864MHz** 去用,所以你只需要修改 PLL 的配置,把输入频率改成板上晶振的实际频率,输出频率仍保持 **36.864MHz** 即可。
|
||||||
* **i2c_scl, i2c_sda** : 连接 AS5600 (磁编码器) 的 I2C 接口。
|
* **i2c_scl, i2c_sda** : 连接 AS5600 (磁编码器) 的 I2C 接口。
|
||||||
* **spi_ss, spi_sck, spi_mosi, spi_miso** : 连接 AD7928 (ADC芯片) 的 SPI 接口。
|
* **spi_ss, spi_sck, spi_mosi, spi_miso** : 连接 AD7928 (ADC芯片) 的 SPI 接口。
|
||||||
* **pwm_a, pwm_b, pwm_c** : 连接电机驱动板的 3 相 PWM 信号。
|
* **pwm_a, pwm_b, pwm_c** : 连接电机驱动板的 3 相 PWM 信号。
|
||||||
@ -62,19 +90,33 @@ FpOC
|
|||||||
* 如果电机驱动板有 3 路 EN 输入,每个对应 1 相,则应该进行 1 对 3 连接。
|
* 如果电机驱动板有 3 路 EN 输入,每个对应 1 相,则应该进行 1 对 3 连接。
|
||||||
* **uart_tx** : 连接 UART 转 USB 模块,插入计算机的 USB 口,用于监测电流环的跟随曲线,**可不接**。
|
* **uart_tx** : 连接 UART 转 USB 模块,插入计算机的 USB 口,用于监测电流环的跟随曲线,**可不接**。
|
||||||
|
|
||||||
连接好后别忘了根据实际情况使用 Quartus (或者手动修改[./FPGA/foc.qsf](./FPGA/foc.qsf))**修改FPGA芯片型号**,**修改引脚约束**。
|
连接好后别忘了根据实际情况在 Quartus 中选择正确的 FPGA 芯片型号、进行引脚约束。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 调参
|
## 调参
|
||||||
|
|
||||||
[foc_top.sv](./RTL/foc/foc_top.sv) 中有一些参数可以调整,例如电机的**极对数**、**PID参数**等,每个参数的含义详见 [foc_top.sv](./RTL/foc/foc_top.sv) 。可以通过修改 [top.sv](./RTL/top.sv) 的 97~103 行来修改这些参数。
|
要让电机正常工作,可能需要调整 [foc_top.sv](./RTL/foc/foc_top.sv) 中的参数(也就是 Verilog parameter),包括:
|
||||||
|
|
||||||
## 运行示例
|
- INIT_CYCLES: 决定了初始化步骤占多少个时钟(clk)周期,取值范围为1~4294967294。该值不能太短,因为要留足够的时间让转子回归电角度=0。在clk频率为 36.864MHz 的情况下,我们可以取 INIT_CYCLES=16777216,则初始化时间为 16777216/36864000=0.45 秒。
|
||||||
|
- ANGLE_INV:
|
||||||
|
- 若角度传感器没装反(A相→B相→C相→A相 的旋转方向与角度传感器的读值的增大方向相同),则该参数应取 0
|
||||||
|
- 若角度传感器装反了(A相→B相→C相→A相 的旋转方向与 角度传感器的读值的增大方向相反),则该参数应取 1
|
||||||
|
- POLE_PAIR:电机极对数,取值范围1~255,根据电机型号决定(注意:电角度ψ = 极对数N * 机械角度φ)
|
||||||
|
- MAX_AMP:SVPWM 的最大振幅,取值范围为1~511,该值越小,电机能达到的最大力矩越小;但考虑到使用3相下桥臂电阻采样法来采样电流,该值也不能太大,以保证3个下桥臂有足够的持续导通时间来供ADC进行采样。在本例中,使用默认值 9'd384 即可。
|
||||||
|
- SAMPLE_DELAY:采样延时,取值范围0~511,考虑到3相的驱动 MOS 管从开始导通到电流稳定需要一定的时间,所以从3个下桥臂都导通,到 ADC 采样时刻之间需要一定的延时。该参数决定了该延时是多少个时钟周期,当延时结束时,该模块在 sn_adc 信号上产生一个高电平脉冲,指示外部 ADC “可以采样了”。在本例中,使用默认值 9'd120 即可。
|
||||||
|
- Kp:PID 的 P 参数
|
||||||
|
- Ki:PID 的 I 参数
|
||||||
|
|
||||||
综合并烧录到 FPGA 后,可以看到电机正反交替运行。
|
你需要根据你所选的电机型号等实际情况,通过修改 [top.sv](./RTL/top.sv) 的 97~103 行来调整这些参数。
|
||||||
|
|
||||||
## 监视串口
|
调好参后,综合并烧录到 FPGA 后,应该能看到电机正反交替运行。
|
||||||
|
|
||||||
把 uart_tx 信号通过 **UART 转 USB 模块** (例如CP2102模块) 连接到电脑上,就可以用**串口助手**、**Putty**等软件来监测电流环的跟随效果。
|
|
||||||
|
|
||||||
|
## 用串口监视电流环
|
||||||
|
|
||||||
|
把 uart_tx 信号通过 **UART 转 USB 模块** (例如 CP2102、CH340 模块) 连接到电脑上,就可以用**串口助手**、**Putty**等软件来监测电流环的跟随效果。
|
||||||
|
|
||||||
> 注: UART 的格式是 115200,8,n,1
|
> 注: UART 的格式是 115200,8,n,1
|
||||||
|
|
||||||
@ -94,55 +136,110 @@ FpOC
|
|||||||
-15 0 -211 -200
|
-15 0 -211 -200
|
||||||
|
|
||||||
|
|
||||||
另外,你可以借用 **Arduino IDE** 的**串口绘图器**来实时显示电流跟随曲线。前往[该网站](https://www.arduino.cc/en/software)下载 **Arduino IDE**,安装后打开,在“**工具→端口**”中选择正确的COM口,然后点击“**工具→串口绘图器**”,**串口绘图器**会自动接收串口并使用上述4列数据画实时曲线图。
|
另外,你可以借用 **Arduino IDE** 的**串口绘图器**来实时显示电流跟随曲线。前往[Arduino官网](https://www.arduino.cc/en/software)下载 **Arduino IDE**,安装后打开,在“**工具→端口**”中选择正确的COM口,然后点击“**工具→串口绘图器**”,**串口绘图器**会自动接收串口并使用上述4列数据画实时曲线图。
|
||||||
|
|
||||||
**图2** 是我这里绘制出的电流跟随曲线。蓝色曲线是第1列数据(d轴电流的实际值);红色曲线是第2列数据(d轴电流的目标值);绿色曲线是第3列数据(q轴电流的实际值);土黄色曲线是第4列数据(q轴电流的目标值)。可以看到实际值能跟着目标值走。
|
**图3** 是我这里绘制出的电流跟随曲线。蓝色曲线是第1列数据(d轴电流的实际值);红色曲线是第2列数据(d轴电流的目标值);绿色曲线是第3列数据(q轴电流的实际值);土黄色曲线是第4列数据(q轴电流的目标值)。可以看到实际值能跟着目标值走。
|
||||||
|
|
||||||
| ![wave](./wave.png) |
|
| ![wave](./figures/wave.png) |
|
||||||
| :---: |
|
| :---: |
|
||||||
| 图2:电流跟随曲线 |
|
| **图3**:电流跟随曲线 |
|
||||||
|
|
||||||
# 代码详解
|
|
||||||
|
|
||||||
下表罗列了该工程使用的所有 **(System-)Verilog** 代码文件,这些文件都在 [./RTL](./RTL) 目录下。,结合**图1**就能看出每个模块的作用。
|
|
||||||
|
# 设计代码详解
|
||||||
|
|
||||||
|
下表罗列了该工程使用的所有 **SystemVerilog** 代码文件,这些文件都在 RTL 目录下。结合**图1**就能看出每个模块的作用。
|
||||||
|
|
||||||
| 文件名 | 功能 | 备注 |
|
| 文件名 | 功能 | 备注 |
|
||||||
| :-- | :-- | :-- |
|
| :-- | :-- | :-- |
|
||||||
| top.sv | FPGA工程的顶层模块 | |
|
| top.sv | FPGA工程的顶层模块 | |
|
||||||
| pll.v | 使用 50MHz 时钟生成 36.864MHz 时钟 | 只支持 Altera Cyclone IV,其它型号的 FPGA 需要用相应的IP核或原语代替 |
|
| uart_monitor.sv | UART 发送器,用于数据监测 | 不需要的话可以移除 |
|
||||||
| uart_monitor.sv | UART 发送器,用于数据监测 | 不需要的话可以删除 |
|
|
||||||
| uart_tx.sv | UART 发送控制器,被 uart_monitor.sv 调用 | 不需要的话可以删除 |
|
|
||||||
| itoa.sv | 数字转十进制字符串,被 uart_monitor.sv 调用 | 不需要的话可以删除 |
|
|
||||||
| as5600_read.sv | AS5600 磁编码器读取器 | |
|
| as5600_read.sv | AS5600 磁编码器读取器 | |
|
||||||
| i2c_register_read.sv | I2C 读取器,被 as5600_read.sv 调用 | |
|
| i2c_register_read.sv | I2C 读取器,被 as5600_read.sv 调用 | |
|
||||||
| adc_ad7928.sv | AD7928 读取器 | |
|
| adc_ad7928.sv | AD7928 读取器 | |
|
||||||
| foc_top.sv | FOC+SVPWM (即**图1**中的青色部分) | 固定算法,一般不需要改动 |
|
| foc_top.sv | FOC+SVPWM (即**图1**中的蓝色部分的顶层) | 固定功能,一般不需要改动 |
|
||||||
| clark_tr.sv | Clark 变换 | 固定算法,一般不需要改动 |
|
| clark_tr.sv | Clark 变换 | 固定功能,一般不需要改动 |
|
||||||
| park_tr.sv | Park 变换 | 固定算法,一般不需要改动 |
|
| park_tr.sv | Park 变换 | 固定功能,一般不需要改动 |
|
||||||
| sincos.sv | 正弦/余弦计算器,被 park_tr.sv 调用 | 固定算法,一般不需要改动 |
|
| sincos.sv | 正弦/余弦计算器,被 park_tr.sv 调用 | 固定功能,一般不需要改动 |
|
||||||
| pi_controller.sv | PID 控制器(只有P和I) | 固定算法,一般不需要改动 |
|
| pi_controller.sv | PI 控制器(PID没有D) | 固定功能,一般不需要改动 |
|
||||||
| cartesian2polar.sv | 直角坐标系转极坐标系 | 固定算法,一般不需要改动 |
|
| cartesian2polar.sv | 直角坐标系转极坐标系 | 固定功能,一般不需要改动 |
|
||||||
| svpwm.sv | SVPWM 调制器 | 固定算法,一般不需要改动 |
|
| svpwm.sv | SVPWM 调制器 | 固定功能,一般不需要改动 |
|
||||||
| hold_detect.sv | 监测3个下桥臂都导通时,延迟一段时间后触发 sn_adc 信号,指示ADC可以开始采样 | 固定算法,一般不需要改动 |
|
| hold_detect.sv | 监测3个下桥臂都导通时,延迟一段时间后触发 sn_adc 信号,指示ADC可以开始采样 | 固定功能,一般不需要改动 |
|
||||||
|
|
||||||
|
|
||||||
| ![diagram](./diagram.png) |
|
| ![diagram](./figures/diagram.png) |
|
||||||
| :---: |
|
| :---: |
|
||||||
| 图1:系统框图 |
|
| **图1**:系统框图 |
|
||||||
|
|
||||||
在**图1**中:
|
**图1**展示了这些模块的层次,我在设计模块层次时充分考虑了封装的合理性和代码重用 :
|
||||||
|
|
||||||
* **淡橙色**部分是FPGA外部的硬件,包括:
|
* **粉色**部分是FPGA内的即传感器控制器,是**硬件相关逻辑**,如果角度传感器和 ADC 型号变了,这部分代码需要重写。
|
||||||
* Gate Driver、3相半桥(6个MOSFET)、3相采样电阻+放大器 。(这些通常集成在**电机驱动板**里)。
|
* **蓝色**部分是FPGA内的 FOC 的固定算法,是**硬件无关逻辑**,一般不需要修改,是本库的核心代码!
|
||||||
* 3相采样 ADC。
|
* **黄色**部分是FPGA内的**用户自定逻辑**,用户可以修改 user behavior 来实现各种电机应用。或者修改 uart_monitor 来监测其它变量。
|
||||||
* 角度传感器。
|
* **淡橙色**部分是FPGA外部的硬件电路,也就是电机、电机驱动板、角度传感器这些东西。
|
||||||
* **粉色**部分是FPGA中的**硬件相关逻辑**,即传感器控制器,如果角度传感器和 ADC 型号变了,这部分代码需要重写。
|
|
||||||
* **青色**部分是FPGA中的 FOC 的固定算法,属于**硬件无关逻辑**,一般不需要改变。
|
|
||||||
* **黄色**部分是**用户自定逻辑**,用户可以修改 user behavior 来实现各种电机应用。或者修改 uart_monitor 来监测其它变量。
|
|
||||||
|
|
||||||
另外,除了 [pll.v](./RTL/pll.v) 外,该库的所有代码都使用纯 RTL 编写,可以轻易地移植到其它厂商(Xilinx、Lattice等)的 FPGA 上。 [pll.v](./RTL/pll.v) 只是用来把 50MHz 时钟变成 36.864MHz 时钟的,只适用于 Altera Cyclone IV FPGA,当使用其它厂商或系列的FPGA时,需要使用它们各自的 IP 核或原语(例如Xilinx的clock wizard)来代替。
|
另外,除了 top.sv 中调用的 altpll 原语外,该库的所有代码都使用纯 RTL 编写,可以轻易地移植到其它厂商(Xilinx、Lattice等)的 FPGA 上。altpll 原语只是用来把 50MHz 时钟变成 36.864MHz 时钟的,只适用于 Altera Cyclone IV FPGA,当使用其它厂商或系列的FPGA时,需要使用它们各自的 IP 核或原语(例如Xilinx的clock wizard)来代替。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# RTL仿真
|
||||||
|
|
||||||
|
因为我并没有电机的 Verilog 模型,没法对整个 FOC 算法进行仿真,所以只对 FOC 中的部分模块进行了仿真。
|
||||||
|
|
||||||
|
仿真相关的文件都在 SIM 文件夹里,其中包括文件:
|
||||||
|
|
||||||
|
| 文件名 | 功能 |
|
||||||
|
| --------------------------------- | ------------------------------------------------------------ |
|
||||||
|
| tb_clark_park_tr.sv | 对 clark_tr.sv (clark变换) 和 park_tr.sv (park变换)的仿真程序 |
|
||||||
|
| tb_clark_park_tr_run_iverilog.bat | 用 iverilog 运行 tb_clark_park_tr.sv 的命令脚本 |
|
||||||
|
| tb_svpwm.sv | 对 cartesian2polar.sv 和 svpwm.sv 和 park_tr.sv 的仿真程序 |
|
||||||
|
| tb_svpwm_run_iverilog.bat | 用 iverilog 运行 tb_svpwm.sv 的命令脚本 |
|
||||||
|
|
||||||
|
使用 iverilog 仿真前,需要安装 iverilog ,见:[iverilog_usage](https://github.com/WangXuan95/WangXuan95/blob/main/iverilog_usage/iverilog_usage.md)
|
||||||
|
|
||||||
|
## clark_tr和park_tr的仿真
|
||||||
|
|
||||||
|
我们先来运行 clark 变换和 park 变换的仿真。
|
||||||
|
|
||||||
|
双击 tb_clark_park_tr_run_iverilog.bat 可以直接运行仿真,运行完后会生成波形文件 dump.vcd 。请用 gtkwave 打开 dump.vcd ,导入**图4**中的这些信号,可以看到如**图4**的波形(需要你把这些信号改成模拟信号显示的形式,才能看得到**图4**这种效果:第一步,对于 Verilog 代码中声明为 signed 的信号(有符号数),需要右键该信号→Data Format→Signed Decimal。第二步,右键该信号→Data Format→Analog→Step ,即可把它变成如图的模拟信号的样子)。
|
||||||
|
|
||||||
|
对该**图4**波形的解读:
|
||||||
|
|
||||||
|
- theta 是一个不断递增的角度(0→2π→0→2π→...)
|
||||||
|
- ia, ib, ic 是用 theta 生成的正弦波,相位各自相差 (2/3)*π (也就是说 ia, ib, ic)构成了三相正弦波。
|
||||||
|
- 使用 clark 变换把 ia, ib, ic 变换成 ialpha, ibeta ,得到一对正交的正弦波(相位相差 π/2 )。
|
||||||
|
- 使用 park 变换把 ialpha, ibeta 变换到定子坐标系,得到定值 id 和 iq (因为实际的计算误差,所以得到的是近似的定值,而不是严格的定值)。
|
||||||
|
|
||||||
|
| ![](./figures/tb_clark_park_tr.png) |
|
||||||
|
| :------------------------------------------: |
|
||||||
|
| **图4**:对 clark_tr 与 park_tr 仿真的波形。 |
|
||||||
|
|
||||||
|
## cartesian2polar和svpwm的仿真
|
||||||
|
|
||||||
|
现在来运行 cartesian2polar (直角坐标转极坐标系)和 svpwm 的仿真。
|
||||||
|
|
||||||
|
双击 tb_svpwm_run_iverilog.bat 可以直接运行仿真,运行完后会生成波形文件 dump.vcd 。请用 gtkwave 打开 dump.vcd ,可以看到如**图5**的波形。
|
||||||
|
|
||||||
|
注意: pwma_duty、pwmb_duty、pwmc_duty 这三个信号不在顶层,你要在 svpwm 这个模块内才能找到这三个信号。
|
||||||
|
|
||||||
|
对**图5**波形的解读:
|
||||||
|
|
||||||
|
- theta 是一个不断递增的角度(0→2π→0→2π→...)
|
||||||
|
- x 和 y 是用 theta 生成的正交的正弦波(或者说,y是正弦波,x是余弦波)
|
||||||
|
- 把 (x,y) 视作直角坐标值,然后 cartesian2polar 把它转换成极坐标系 (ρ, φ) ,也即 (rho, phi)
|
||||||
|
- 把 (rho, phi) 输给 svpwm ,产生了 pwma_duty、pwmb_duty、pwmc_duty 这三个马鞍波。如果你熟悉七段式 SVPWM 的原理,就应该知道为什么是马鞍波,这里不做赘述。
|
||||||
|
- pwma_duty、pwmb_duty、pwmc_duty 分别决定了 pwm_a, pwm_b, pwm_c 的占空比(duty这个单词就是占空比的意思)。
|
||||||
|
|
||||||
|
| ![](./figures/tb_svpwm.png) |
|
||||||
|
| :-----------------------------------------------: |
|
||||||
|
| **图5**:对 cartesian2polar 与 svpwm 仿真的波形。 |
|
||||||
|
|
||||||
|
放大波形,可以看到确实是 duty 值越大,对应的 pwm 信号的占空比就越大,如**图6**。
|
||||||
|
|
||||||
|
| ![](./figures/tb_svpwm_2.png) |
|
||||||
|
| :---------------------------: |
|
||||||
|
| **图6**:图5的放大。 |
|
||||||
|
|
||||||
[top.sv](./RTL/top.sv) 和 [foc_top.sv](./RTL/foc/foc_top.sv) 注有详细的注释。如果你了解 FOC 算法,可以直接读懂。如果刚入门 FOC,可以结合参考资料[6~9]去阅读。
|
|
||||||
|
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
@ -201,13 +298,14 @@ AD7928 只有一个T/H,所以采样窗口内要做 3 次采样,这个过程
|
|||||||
### 关于时钟周期和控制周期
|
### 关于时钟周期和控制周期
|
||||||
|
|
||||||
* **问**: **时钟频率为什么选择36.864MHz?有什么特殊含义嘛?我如果直接用FPGA的50MHz(不经过PLL变成36.864MHz)会出现什么问题呢?**
|
* **问**: **时钟频率为什么选择36.864MHz?有什么特殊含义嘛?我如果直接用FPGA的50MHz(不经过PLL变成36.864MHz)会出现什么问题呢?**
|
||||||
* **答**: 其实选 36.864MHz 只是因为可以让控制周期为 36864/2048 = 18kHz,凑个整。
|
* **答**: 本库的 SVPWM 的频率被设计为 clk 频率/2048 ,所以选 36.864MHz 只是因为可以让控制周期为 36864/2048 = 18kHz,凑个整。
|
||||||
foc_top.sv 和 as5600_read.sv 对时钟频率是没有要求的,多高都可以(唯一的限制是频率太高时序不收敛)
|
foc_top.sv 和 as5600_read.sv 对时钟频率是没有要求的,多高都可以(唯一的限制是频率太高时可能会导致 FPGA 时序不收敛)
|
||||||
但 adc_ad7928.sv 限制了频率不能大于 40.96MHz,因为 ADC7928 的 SPI 时钟不能超过 20.48MHz,而 adc_ad7928.sv 内部用驱动时钟2分频作为SPI时钟。
|
但 adc_ad7928.sv 限制了频率不能大于 40MHz,因为 ADC7928 的 SPI 时钟不能超过 20.48MHz,而 adc_ad7928.sv 内部用驱动时钟2分频作为SPI时钟。
|
||||||
综上,时钟频率是可调的,但要小于 40.96MHz。
|
综上,时钟 clk 的频率是可调的,但要小于 40MHz。
|
||||||
|
|
||||||
* **问**: **代码里 控制频率= 时钟频率 / 2048,为什么? 我觉得控制频率和时钟频率应该是没有必然的联系,时钟频率是FPGA的时钟频率,而控制频率是pwm的频率,一般是几十khz,你是根据控制频率来确定的时钟频率嘛?**
|
* **问**: **代码里 控制频率= 时钟频率 / 2048,为什么? 我觉得控制频率和时钟频率应该是没有必然的联系,时钟频率是FPGA的时钟频率,而控制频率是pwm的频率,一般是几十khz,你是根据控制频率来确定的时钟频率嘛?**
|
||||||
* **答**: 理想地来讲,FPGA时钟频率和控制周期确实没有关系,但要考虑实际,PWM不也是FPGA的时钟驱动的?既然如此,必然有个分频系数(控制频率 = 时钟频率 / 分频系数),别的驱动器可能具有可调的分频系数,但FpOC里为了简单,分频系数=2048。这意味着 FpOC 如果要调节控制频率,就只能调时钟频率,目前受限于上述ADC7928的频率限制,最高的控制频率是 40.96MHz/2048=20kHz。
|
* **答**: 从原理上讲,FPGA时钟频率和控制周期确实没有关系,但要考虑实际,因为PWM是由FPGA的时钟驱动的,所以必然有个分频系数(控制频率 = 时钟频率 / 分频系数),别人实现的FOC可能具有可调的分频系数,但FpOC里为了简单,分频系数=2048。这意味着 FpOC 如果要调节控制频率,就只能调时钟频率。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 参考资料
|
# 参考资料
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
// 模块: cartesian2polar
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// 功能: 把直角坐标系 (x,y) 转换为极坐标系
|
||||||
|
|
||||||
module cartesian2polar #(
|
module cartesian2polar #(
|
||||||
parameter ATTENUAION = 0
|
parameter ATTENUAION = 0
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
// clark_tr
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// 功能: clark 变换
|
||||||
|
|
||||||
module clark_tr(
|
module clark_tr(
|
||||||
input wire rstn,
|
input wire rstn,
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
|
||||||
// 模块: foc_top
|
// 模块: foc_top
|
||||||
|
// Type : synthesizable, IP's top
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
// 功能:FOC 算法(仅包含电流环) + SVPWM
|
// 功能:FOC 算法(仅包含电流环) + SVPWM
|
||||||
// 参数:详见下方注释
|
// 参数:详见下方注释
|
||||||
// 输入输出:详见下方注释
|
// 输入输出:详见下方注释
|
||||||
|
|
||||||
module foc_top #(
|
module foc_top #(
|
||||||
// ----------------------------------------------- 模块参数 ---------------------------------------------------------------------------------------------------------------------------------------------------
|
// ----------------------------------------------- 模块参数 ---------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
parameter INIT_CYCLES = 16777216, // 决定了初始化步骤粘多少个时钟(clk)周期,取值范围为1~4294967294。该值不能太短,因为要留足够的时间让转子回归电角度=0。例如若时钟(clk)频率为 36.864MHz,INIT_CYCLES=16777216,则初始化时间为 16777216/36864000=0.45 秒
|
parameter INIT_CYCLES = 16777216, // 决定了初始化步骤占多少个时钟(clk)周期,取值范围为1~4294967294。该值不能太短,因为要留足够的时间让转子回归电角度=0。例如若时钟(clk)频率为 36.864MHz,INIT_CYCLES=16777216,则初始化时间为 16777216/36864000=0.45 秒
|
||||||
parameter logic ANGLE_INV = 1'b0, // 若角度传感器没装反(A->B->C->A 的旋转方向与 φ 增大的方向相同),则该参数应设为 0。若角度传感器装反了(A->B->C->A 的旋转方向与 φ 增大的方向相反),则该参数应设为 1。
|
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 [ 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] MAX_AMP = 9'd384, // SVPWM 的最大振幅,取值范围为1~511,该值越小,电机能达到的最大力矩越小;但考虑到使用3相下桥臂电阻采样法来采样电流,该值也不能太大,以保证3个下桥臂有足够的持续导通时间来供ADC进行采样。
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
// 模块: hold_detect
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// 功能: 检测 in 从高电平变为低电平并保持 SAMPLE_DELAY 个时钟周期,在 sn_adc 信号上产生一个时钟周期的高电平。
|
||||||
|
|
||||||
module hold_detect #(
|
module hold_detect #(
|
||||||
parameter [15:0] SAMPLE_DELAY = 16'd100
|
parameter [15:0] SAMPLE_DELAY = 16'd100
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
// 模块: park_tr
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// 功能: park 变换器
|
||||||
|
|
||||||
module park_tr(
|
module park_tr(
|
||||||
input wire rstn,
|
input wire rstn,
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
// 模块: pi_controller
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// 功能: PI 控制器
|
||||||
|
|
||||||
module pi_controller #(
|
module pi_controller #(
|
||||||
parameter logic [23:0] Kp = 24'd32768,
|
parameter logic [23:0] Kp = 24'd32768,
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
// 模块:sincos
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// 功能: 计算 sin 和 cos
|
||||||
|
// 在 i_theta 上给出角度(0~π被映射为0~4095),
|
||||||
|
// 在 o_sin 和 o_cos 上产生 sinθ 和 cosθ (-1~+1 被映射为 -16384~+16384)
|
||||||
|
|
||||||
module sincos(
|
module sincos(
|
||||||
input wire rstn,
|
input wire rstn,
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
|
||||||
// 模块:svpwm
|
// 模块:svpwm
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
// 功能:7 段式 SVPWM 生成器(调制器)
|
// 功能:7 段式 SVPWM 生成器(调制器)
|
||||||
// 输入:定子极坐标系下的电压矢量 Vsρ, Vsθ
|
// 输入:定子极坐标系下的电压矢量 Vsρ, Vsθ
|
||||||
// 输出:PWM使能信号 pwm_en
|
// 输出:PWM使能信号 pwm_en
|
||||||
// 3相PWM信号 pwm_a, pwm_b, pwm_c
|
// 3相PWM信号 pwm_a, pwm_b, pwm_c
|
||||||
// 说明:该模块产生的 PWM 的频率是 clk 频率 / 2048。例如 clk 为 36.864MHz ,则 PWM 的频率为 36.864MHz / 2048 = 18kHz
|
// 说明:该模块产生的 PWM 的频率是 clk 频率 / 2048。例如 clk 为 36.864MHz ,则 PWM 的频率为 36.864MHz / 2048 = 18kHz
|
||||||
|
|
||||||
module svpwm (
|
module svpwm (
|
||||||
input wire rstn,
|
input wire rstn,
|
||||||
input wire clk,
|
input wire clk,
|
||||||
|
120
RTL/pll.v
@ -1,120 +0,0 @@
|
|||||||
// 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
|
|
@ -1,9 +1,11 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
|
||||||
// 模块: adc_ad7928
|
// 模块: adc_ad7928
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
// 功能:通过 SPI 接口从 ADC7928 (ADC芯片) 中读出 ADC 值。
|
// 功能:通过 SPI 接口从 ADC7928 (ADC芯片) 中读出 ADC 值。
|
||||||
// 参数:详见下方注释,该模块可以使用参数完全自由地配置单次转换要用多少个通道以及用哪些通道
|
// 参数:详见下方注释,该模块可以使用参数完全自由地配置单次转换要用多少个通道以及用哪些通道
|
||||||
// 输入输出:详见下方注释
|
// 输入输出:详见下方注释
|
||||||
|
|
||||||
module adc_ad7928 #(
|
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] 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] CH0 = 3'd0, // 指示了 CH0 对应 AD7928 的哪个通道
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
|
||||||
// 模块: as5600_read
|
// 模块: as5600_read
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
// 功能:通过 I2C 接口从 AS5600 磁编码器中读出转子机械角度 φ
|
// 功能:通过 I2C 接口从 AS5600 磁编码器中读出转子机械角度 φ
|
||||||
// 参数:详见下方注释
|
// 参数:详见下方注释
|
||||||
// 输入输出:详见下方注释
|
// 输入输出:详见下方注释
|
||||||
|
|
||||||
module as5600_read #(
|
module as5600_read #(
|
||||||
parameter [15:0] CLK_DIV = 16'd10 // I2C SCL 时钟信号分频系数,SCL 时钟频率 = clk频率 / (4*CLK_DIV) ,例如若 clk 为 40MHz,CLK_DIV=10,则 SCL 频率为 40/(4*10) = 1MHz。注,AS5600 芯片要求 SCL 频率不超过 1MHz
|
parameter [15:0] CLK_DIV = 16'd10 // I2C SCL 时钟信号分频系数,SCL 时钟频率 = clk频率 / (4*CLK_DIV) ,例如若 clk 为 40MHz,CLK_DIV=10,则 SCL 频率为 40/(4*10) = 1MHz。注,AS5600 芯片要求 SCL 频率不超过 1MHz
|
||||||
)(
|
)(
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
// 模块: i2c_register_read
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// 功能 : I2C 读控制器
|
||||||
|
|
||||||
module i2c_register_read #(
|
module i2c_register_read #(
|
||||||
parameter [15:0] CLK_DIV = 16'd16,
|
parameter [15:0] CLK_DIV = 16'd16,
|
||||||
|
12
RTL/top.sv
@ -1,9 +1,11 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
|
||||||
// 模块:top
|
// 模块:top
|
||||||
|
// Type : synthesizable, FPGA's top
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
// 功能:FOC 使用示例,是FPGA工程的顶层模块,控制电机的切向力矩一会顺时针一会逆时针,同时可以通过 UART 监测电流环控制的跟随曲线
|
// 功能:FOC 使用示例,是FPGA工程的顶层模块,控制电机的切向力矩一会顺时针一会逆时针,同时可以通过 UART 监测电流环控制的跟随曲线
|
||||||
// 参数:无
|
// 参数:无
|
||||||
// 输入输出:详见下方注释
|
// 输入输出:详见下方注释
|
||||||
|
|
||||||
module top(
|
module top(
|
||||||
input wire clk_50m, // 50MHz 时钟
|
input wire clk_50m, // 50MHz 时钟
|
||||||
// ------- 3相 PWM 信号,(包含使能信号) -----------------------------------------------------------------------------------------------------
|
// ------- 3相 PWM 信号,(包含使能信号) -----------------------------------------------------------------------------------------------------
|
||||||
@ -44,11 +46,9 @@ reg signed [15:0] iq_aim; // 转子 q 轴(交轴)的目标电流值,
|
|||||||
|
|
||||||
// PLL,用 50MHz 时钟产生 36.864 MHz 时钟
|
// PLL,用 50MHz 时钟产生 36.864 MHz 时钟
|
||||||
// 注:该模块仅适用于 Altera Cyclone IV FPGA ,对于其他厂家或系列的FPGA,请使用各自相同效果的IP核/原语(例如Xilinx的clock wizard)代替该模块。
|
// 注:该模块仅适用于 Altera Cyclone IV FPGA ,对于其他厂家或系列的FPGA,请使用各自相同效果的IP核/原语(例如Xilinx的clock wizard)代替该模块。
|
||||||
pll pll_i (
|
wire [3:0] subwire0;
|
||||||
.inclk0 ( clk_50m ), // input : clk_50m
|
altpll altpll_i ( .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 ());
|
||||||
.c0 ( clk ), // output: clk
|
defparam altpll_i.bandwidth_type = "AUTO", altpll_i.clk0_divide_by = 99, altpll_i.clk0_duty_cycle = 50, altpll_i.clk0_multiply_by = 73, altpll_i.clk0_phase_shift = "0", altpll_i.compensate_clock = "CLK0", altpll_i.inclk0_input_frequency = 20000, altpll_i.intended_device_family = "Cyclone IV E", altpll_i.lpm_hint = "CBX_MODULE_PREFIX=pll", altpll_i.lpm_type = "altpll", altpll_i.operation_mode = "NORMAL", altpll_i.pll_type = "AUTO", altpll_i.port_activeclock = "PORT_UNUSED", altpll_i.port_areset = "PORT_UNUSED", altpll_i.port_clkbad0 = "PORT_UNUSED", altpll_i.port_clkbad1 = "PORT_UNUSED", altpll_i.port_clkloss = "PORT_UNUSED", altpll_i.port_clkswitch = "PORT_UNUSED", altpll_i.port_configupdate = "PORT_UNUSED", altpll_i.port_fbin = "PORT_UNUSED", altpll_i.port_inclk0 = "PORT_USED", altpll_i.port_inclk1 = "PORT_UNUSED", altpll_i.port_locked = "PORT_USED", altpll_i.port_pfdena = "PORT_UNUSED", altpll_i.port_phasecounterselect = "PORT_UNUSED", altpll_i.port_phasedone = "PORT_UNUSED", altpll_i.port_phasestep = "PORT_UNUSED", altpll_i.port_phaseupdown = "PORT_UNUSED", altpll_i.port_pllena = "PORT_UNUSED", altpll_i.port_scanaclr = "PORT_UNUSED", altpll_i.port_scanclk = "PORT_UNUSED", altpll_i.port_scanclkena = "PORT_UNUSED", altpll_i.port_scandata = "PORT_UNUSED", altpll_i.port_scandataout = "PORT_UNUSED", altpll_i.port_scandone = "PORT_UNUSED", altpll_i.port_scanread = "PORT_UNUSED", altpll_i.port_scanwrite = "PORT_UNUSED", altpll_i.port_clk0 = "PORT_USED", altpll_i.port_clk1 = "PORT_UNUSED", altpll_i.port_clk2 = "PORT_UNUSED", altpll_i.port_clk3 = "PORT_UNUSED", altpll_i.port_clk4 = "PORT_UNUSED", altpll_i.port_clk5 = "PORT_UNUSED", altpll_i.port_clkena0 = "PORT_UNUSED", altpll_i.port_clkena1 = "PORT_UNUSED", altpll_i.port_clkena2 = "PORT_UNUSED", altpll_i.port_clkena3 = "PORT_UNUSED", altpll_i.port_clkena4 = "PORT_UNUSED", altpll_i.port_clkena5 = "PORT_UNUSED", altpll_i.port_extclk0 = "PORT_UNUSED", altpll_i.port_extclk1 = "PORT_UNUSED", altpll_i.port_extclk2 = "PORT_UNUSED", altpll_i.port_extclk3 = "PORT_UNUSED", altpll_i.self_reset_on_loss_lock = "OFF", altpll_i.width_clock = 5;
|
||||||
.locked ( rstn ) // output: rstn
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
`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
|
|
@ -1,9 +1,9 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
|
||||||
// 模块:top
|
// 模块:uart_monitor
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
// 功能:UART发送器,格式为:115200,8,n,1,可以把 i_val0, i_val1, i_val2, i_val3 变成10进制格式,放在一行里,通过 UART 发送出去,
|
// 功能:UART发送器,格式为:115200,8,n,1,可以把 i_val0, i_val1, i_val2, i_val3 变成10进制格式,放在一行里,通过 UART 发送出去,
|
||||||
// 参数:无
|
|
||||||
// 输入输出:详见下方注释
|
|
||||||
module uart_monitor #(
|
module uart_monitor #(
|
||||||
parameter [15:0] CLK_DIV = 217 // UART分频倍率,例如若时钟频率为 36.864MHz, CLK_DIV=320,则 UART 波特率为 36.864MHz/320=115200
|
parameter [15:0] CLK_DIV = 217 // UART分频倍率,例如若时钟频率为 36.864MHz, CLK_DIV=320,则 UART 波特率为 36.864MHz/320=115200
|
||||||
) (
|
) (
|
||||||
@ -14,9 +14,11 @@ module uart_monitor #(
|
|||||||
input wire signed [15:0] i_val1,
|
input wire signed [15:0] i_val1,
|
||||||
input wire signed [15:0] i_val2,
|
input wire signed [15:0] i_val2,
|
||||||
input wire signed [15:0] i_val3,
|
input wire signed [15:0] i_val3,
|
||||||
output wire o_uart_tx // UART TX 信号
|
output reg o_uart_tx // UART TX 信号
|
||||||
);
|
);
|
||||||
|
|
||||||
|
initial o_uart_tx = 1'b1;
|
||||||
|
|
||||||
enum logic [2:0] {IDLE, SELECT, WAIT, PARSING, SENDING} stat;
|
enum logic [2:0] {IDLE, SELECT, WAIT, PARSING, SENDING} stat;
|
||||||
|
|
||||||
wire tx_rdy;
|
wire tx_rdy;
|
||||||
@ -25,8 +27,8 @@ reg [7:0] tx_data;
|
|||||||
|
|
||||||
reg itoa_en;
|
reg itoa_en;
|
||||||
reg signed [15:0] itoa_val;
|
reg signed [15:0] itoa_val;
|
||||||
wire itoa_oen;
|
reg itoa_oen;
|
||||||
wire [ 7:0] itoa_str [6];
|
reg [ 7:0] itoa_str [6];
|
||||||
|
|
||||||
reg [ 2:0] vcnt;
|
reg [ 2:0] vcnt;
|
||||||
|
|
||||||
@ -108,24 +110,79 @@ always @ (posedge clk or negedge rstn)
|
|||||||
endcase
|
endcase
|
||||||
end
|
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 #(
|
reg [ 2:0] itoa_cnt;
|
||||||
.CLK_DIV ( CLK_DIV )
|
reg itoa_sign;
|
||||||
) uart_tx_i (
|
reg itoa_zero;
|
||||||
.rstn ( rstn ),
|
reg [15:0] itoa_abs;
|
||||||
.clk ( clk ),
|
reg [ 3:0] itoa_rem;
|
||||||
.i_e ( tx_en ),
|
|
||||||
.i_r ( tx_rdy ),
|
|
||||||
.i_d ( tx_data ),
|
always @ (posedge clk or negedge rstn)
|
||||||
.tx ( o_uart_tx )
|
if(~rstn) begin
|
||||||
);
|
itoa_cnt <= 3'd0;
|
||||||
|
{itoa_sign, itoa_abs, itoa_zero, itoa_rem} <= '0;
|
||||||
|
itoa_oen <= 1'b0;
|
||||||
|
itoa_str <= '{6{'0}};
|
||||||
|
end else begin
|
||||||
|
if(itoa_cnt==3'd0) begin
|
||||||
|
if(itoa_en)
|
||||||
|
itoa_cnt <= 3'd1;
|
||||||
|
itoa_sign <= itoa_val[15];
|
||||||
|
itoa_abs <= itoa_val[15] ? $unsigned(-itoa_val) : $unsigned(itoa_val);
|
||||||
|
end else begin
|
||||||
|
itoa_cnt <= itoa_cnt + 3'd1;
|
||||||
|
itoa_abs <= itoa_abs / 16'd10;
|
||||||
|
itoa_rem <= (4)'(itoa_abs % 16'd10);
|
||||||
|
itoa_zero <= itoa_abs==16'd0;
|
||||||
|
if(itoa_cnt>3'd1) begin
|
||||||
|
itoa_str[5] <= itoa_str[4];
|
||||||
|
itoa_str[4] <= itoa_str[3];
|
||||||
|
itoa_str[3] <= itoa_str[2];
|
||||||
|
itoa_str[2] <= itoa_str[1];
|
||||||
|
itoa_str[1] <= itoa_str[0];
|
||||||
|
if(itoa_cnt>3'd2 && itoa_zero) begin
|
||||||
|
itoa_str[0] <= itoa_sign ? 8'h2D : 8'h20;
|
||||||
|
itoa_sign <= 1'b0;
|
||||||
|
end else begin
|
||||||
|
itoa_str[0] <= {4'h3, itoa_rem};
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
itoa_oen <= itoa_cnt == 3'd7;
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
reg [15:0] ccnt;
|
||||||
|
reg [ 3:0] tx_cnt;
|
||||||
|
reg [12:1] tx_shift;
|
||||||
|
|
||||||
|
assign tx_rdy = (tx_cnt==4'd0);
|
||||||
|
|
||||||
|
always @ (posedge clk or negedge rstn)
|
||||||
|
if(~rstn) begin
|
||||||
|
o_uart_tx <= 1'b1;
|
||||||
|
ccnt <= '0;
|
||||||
|
tx_cnt <= '0;
|
||||||
|
tx_shift <= '1;
|
||||||
|
end else begin
|
||||||
|
if(tx_cnt==4'd0) begin
|
||||||
|
o_uart_tx <= 1'b1;
|
||||||
|
ccnt <= '0;
|
||||||
|
if(tx_en) begin
|
||||||
|
tx_cnt <= 4'd12;
|
||||||
|
tx_shift <= {2'b10, tx_data[0], tx_data[1], tx_data[2], tx_data[3], tx_data[4], tx_data[5], tx_data[6], tx_data[7], 2'b11};
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
o_uart_tx <= tx_shift[tx_cnt];
|
||||||
|
if( ccnt + 16'd1 < CLK_DIV ) begin
|
||||||
|
ccnt <= ccnt + 16'd1;
|
||||||
|
end else begin
|
||||||
|
ccnt <= '0;
|
||||||
|
tx_cnt <= tx_cnt - 4'd1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
`timescale 1 ns/1 ns
|
|
||||||
|
// 模块: uart_tx
|
||||||
|
// Type : synthesizable
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
|
||||||
module uart_tx #(
|
module uart_tx #(
|
||||||
parameter [15:0] CLK_DIV = 217 // 25MHz / 217 = 115207 ~= 115200
|
parameter [15:0] CLK_DIV = 217 // 25MHz / 217 = 115207 ~= 115200
|
||||||
|
105
SIM/tb_clark_park_tr.sv
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------------
|
||||||
|
// Module : tb_clark_park_tr
|
||||||
|
// Type : simulation, top
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// Function: testbench for sincos.sv , clark_tr.sv , park_tr.sv
|
||||||
|
//--------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
`timescale 1ps/1ps
|
||||||
|
|
||||||
|
module tb_clark_park_tr();
|
||||||
|
|
||||||
|
|
||||||
|
initial $dumpvars(1, tb_clark_park_tr);
|
||||||
|
|
||||||
|
|
||||||
|
reg rstn = 1'b0;
|
||||||
|
reg clk = 1'b1;
|
||||||
|
always #(13563) clk = ~clk; // 36.864MHz
|
||||||
|
initial begin repeat(4) @(posedge clk); rstn<=1'b1; end
|
||||||
|
|
||||||
|
reg en_theta = '0;
|
||||||
|
reg [11:0] theta = '0; // 当前电角度(简记为 ψ)。取值范围0~4095。0对应0°;1024对应90°;2048对应180°;3072对应270°。
|
||||||
|
|
||||||
|
wire en_iabc;
|
||||||
|
wire signed [15:0] ia, ib, ic;
|
||||||
|
|
||||||
|
wire en_ialphabeta;
|
||||||
|
wire signed [15:0] ialpha, ibeta;
|
||||||
|
|
||||||
|
wire en_idq;
|
||||||
|
wire signed [15:0] id;
|
||||||
|
wire signed [15:0] iq;
|
||||||
|
|
||||||
|
// 这里只是刚好借助了 sincos 模块来生成正弦波给 clark_tr ,只是为了仿真。在 FOC 设计中 sincos 模块并不是用来给 clark_tr 提供输入数据的,而是被 park_tr 调用。
|
||||||
|
sincos sincos_i1 (
|
||||||
|
.rstn ( rstn ),
|
||||||
|
.clk ( clk ),
|
||||||
|
.i_en ( en_theta ),
|
||||||
|
.i_theta ( theta + (12)'(2*4096/3) ), // input : θ + (2/3)*π
|
||||||
|
.o_en ( en_iabc ),
|
||||||
|
.o_sin ( ia ), // output: Ia, 振幅为±16384,初相位为 (4/3)*π 的正弦波
|
||||||
|
.o_cos ( )
|
||||||
|
);
|
||||||
|
|
||||||
|
sincos sincos_i2 (
|
||||||
|
.rstn ( rstn ),
|
||||||
|
.clk ( clk ),
|
||||||
|
.i_en ( en_theta ),
|
||||||
|
.i_theta ( theta + (12)'( 4096/3) ), // input : θ + (1/3)*π
|
||||||
|
.o_en ( ),
|
||||||
|
.o_sin ( ib ), // output: Ib, 振幅为±16384,初相位为 (2/3)*π 的正弦波
|
||||||
|
.o_cos ( )
|
||||||
|
);
|
||||||
|
|
||||||
|
sincos sincos_i3 (
|
||||||
|
.rstn ( rstn ),
|
||||||
|
.clk ( clk ),
|
||||||
|
.i_en ( en_theta ),
|
||||||
|
.i_theta ( theta ), // input : θ
|
||||||
|
.o_en ( ),
|
||||||
|
.o_sin ( ic ), // output: Ic, 振幅为±16384,初相位为 0 的正弦波
|
||||||
|
.o_cos ( )
|
||||||
|
);
|
||||||
|
|
||||||
|
// clark 变换
|
||||||
|
clark_tr clark_tr_i(
|
||||||
|
.rstn ( rstn ),
|
||||||
|
.clk ( clk ),
|
||||||
|
.i_en ( en_iabc ),
|
||||||
|
.i_ia ( ia / 16'sd2 ), // input : 振幅为±8192,初相位为 (4/3)*π 的正弦波
|
||||||
|
.i_ib ( ib / 16'sd2 ), // input : 振幅为±8192,初相位为 (2/3)*π 的正弦波
|
||||||
|
.i_ic ( ic / 16'sd2 ), // input : 振幅为±8192,初相位为 0 的正弦波
|
||||||
|
.o_en ( en_ialphabeta ),
|
||||||
|
.o_ialpha ( ialpha ), // output: Iα ,应该为初相位为 (4/3)*π 的正弦波
|
||||||
|
.o_ibeta ( ibeta ) // output: Iβ ,相位应该比 Iα 滞后 (1/2)*π ,也就是与 Iα 正交
|
||||||
|
);
|
||||||
|
|
||||||
|
// park 变换
|
||||||
|
park_tr park_tr_i (
|
||||||
|
.rstn ( rstn ),
|
||||||
|
.clk ( clk ),
|
||||||
|
.psi ( theta + 12'd512 ), // input : θ + (1/4)*π
|
||||||
|
.i_en ( en_ialphabeta ),
|
||||||
|
.i_ialpha ( ialpha ), // input : Iα
|
||||||
|
.i_ibeta ( ibeta ), // input : Iβ
|
||||||
|
.o_en ( en_idq ),
|
||||||
|
.o_id ( id ), // output: Id ,应该变为一个定值,因为 park 变换把转子又变回定子了
|
||||||
|
.o_iq ( iq ) // output: Iq ,应该变为一个定值,因为 park 变换把转子又变回定子了
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
while(~rstn) @ (posedge clk);
|
||||||
|
for(int i=0; i<1000; i++) @ (posedge clk) begin
|
||||||
|
en_theta <= 1'b1;
|
||||||
|
theta <= theta + 12'd10;
|
||||||
|
@ (posedge clk);
|
||||||
|
en_theta <= 1'b0;
|
||||||
|
repeat (9) @ (posedge clk);
|
||||||
|
end
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
5
SIM/tb_clark_park_tr_run_iverilog.bat
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
del sim.out dump.vcd
|
||||||
|
iverilog -g2005-sv -o sim.out tb_clark_park_tr.sv ../RTL/foc/sincos.sv ../RTL/foc/clark_tr.sv ../RTL/foc/park_tr.sv
|
||||||
|
vvp -n sim.out
|
||||||
|
del sim.out
|
||||||
|
pause
|
78
SIM/tb_svpwm.sv
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------------
|
||||||
|
// Module : tb_swpwm
|
||||||
|
// Type : simulation, top
|
||||||
|
// Standard: SystemVerilog 2005 (IEEE1800-2005)
|
||||||
|
// Function: testbench for swpwm.sv
|
||||||
|
//--------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
`timescale 1ps/1ps
|
||||||
|
|
||||||
|
module tb_swpwm();
|
||||||
|
|
||||||
|
|
||||||
|
initial $dumpvars(1, tb_swpwm);
|
||||||
|
initial $dumpvars(1, svpwm_i);
|
||||||
|
|
||||||
|
|
||||||
|
reg rstn = 1'b0;
|
||||||
|
reg clk = 1'b1;
|
||||||
|
always #(13563) clk = ~clk; // 36.864MHz
|
||||||
|
initial begin repeat(4) @(posedge clk); rstn<=1'b1; end
|
||||||
|
|
||||||
|
|
||||||
|
reg [11:0] theta = '0;
|
||||||
|
|
||||||
|
wire signed [15:0] x, y;
|
||||||
|
|
||||||
|
wire [11:0] rho;
|
||||||
|
wire [11:0] phi;
|
||||||
|
|
||||||
|
wire pwm_en, pwm_a, pwm_b, pwm_c;
|
||||||
|
|
||||||
|
// 这里只是刚好借助了 sincos 模块来生成正弦波给 cartesian2polar ,只是为了仿真。在 FOC 设计中 sincos 模块并不是用来给 cartesian2polar 提供输入数据的,而是被 park_tr 调用。
|
||||||
|
sincos sincos_i (
|
||||||
|
.rstn ( rstn ),
|
||||||
|
.clk ( clk ),
|
||||||
|
.i_en ( 1'b1 ),
|
||||||
|
.i_theta ( theta ), // input : θ, 一个递增的角度值
|
||||||
|
.o_en ( ),
|
||||||
|
.o_sin ( y ), // output : y, 振幅为 ±16384 的正弦波
|
||||||
|
.o_cos ( x ) // output : x, 振幅为 ±16384 的余弦波
|
||||||
|
);
|
||||||
|
|
||||||
|
cartesian2polar cartesian2polar_i (
|
||||||
|
.rstn ( rstn ),
|
||||||
|
.clk ( clk ),
|
||||||
|
.i_en ( 1'b1 ),
|
||||||
|
.i_x ( x / 16'sd5 ), // input : 振幅为 ±3277 的余弦波
|
||||||
|
.i_y ( y / 16'sd5 ), // input : 振幅为 ±3277 的正弦波
|
||||||
|
.o_en ( ),
|
||||||
|
.o_rho ( rho ), // output: ρ, 应该是一直等于或近似 3277
|
||||||
|
.o_theta ( phi ) // output: φ, 应该是一个接近 θ 的角度值
|
||||||
|
);
|
||||||
|
|
||||||
|
svpwm svpwm_i (
|
||||||
|
.rstn ( rstn ),
|
||||||
|
.clk ( clk ),
|
||||||
|
.v_amp ( 9'd384 ),
|
||||||
|
.v_rho ( rho ), // input : ρ
|
||||||
|
.v_theta ( phi ), // input : φ
|
||||||
|
.pwm_en ( pwm_en ), // output
|
||||||
|
.pwm_a ( pwm_a ), // output
|
||||||
|
.pwm_b ( pwm_b ), // output
|
||||||
|
.pwm_c ( pwm_c ) // output
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
while(~rstn) @ (posedge clk);
|
||||||
|
for(int i=0; i<200; i++) begin
|
||||||
|
theta <= 25 * i; // 让 θ 递增
|
||||||
|
repeat(2048) @ (posedge clk);
|
||||||
|
$display("%d/200", i);
|
||||||
|
end
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
5
SIM/tb_svpwm_run_iverilog.bat
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
del sim.out dump.vcd
|
||||||
|
iverilog -g2005-sv -o sim.out tb_svpwm.sv ../RTL/foc/sincos.sv ../RTL/foc/cartesian2polar.sv ../RTL/foc/svpwm.sv
|
||||||
|
vvp -n sim.out
|
||||||
|
del sim.out
|
||||||
|
pause
|
BIN
diagram.png
Before Width: | Height: | Size: 32 KiB |
BIN
figures/diagram.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
figures/sch.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
figures/tb_clark_park_tr.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
figures/tb_svpwm.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
figures/tb_svpwm_2.png
Normal file
After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |