1
0
mirror of https://github.com/WangXuan95/FpOC.git synced 2025-01-17 23:22:52 +08:00
This commit is contained in:
WangXuan95 2022-04-01 05:22:10 +08:00
parent 567774744d
commit 7c5ef655f0
30 changed files with 501 additions and 287 deletions

View File

@ -63,7 +63,7 @@ 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 LAST_QUARTUS_VERSION "18.1.0 Standard Edition"
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
@ -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 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

232
README.md
View File

@ -1,38 +1,50 @@
![test](https://img.shields.io/badge/test-passing-green.svg)
![docs](https://img.shields.io/badge/docs-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)
FpOC
===========================
基于 **FPGA** 的**磁场定向控制 (FOC)**,用于驱动**永磁同步电机 (PMSM)**
基于 **FPGA** 的**磁场定向控制 (FOC)**,用于驱动**永磁同步电机 (PMSM)** 或**无刷直流电机 (BLDC)**
# 简介
**FOC控制算法**对传感器采样速率和处理器算力提出了一定的要求,使用 **FPGA** 实现的 **FOC** 可以获得更好的**实时性**,并且更方便进行**多路扩展**和**多路反馈协同**。
**FOC控制算法**对**传感器采样速率**和**处理器算力**提出了一定的要求,使用 **FPGA** 实现的 **FOC** 可以获得更好的**实时性**和零**延迟抖动**,并且更方便进行**多路扩展**
本库实现了基于**角度传感器**(也就是磁编码器)的**有感 FOC**,即一个完整的**电流环**,可以进行**扭矩控制**。借助本库,你可以进一步使用 **纯FPGA****MCU+FPGA** 的方式实现更复杂的电机应用
本库实现了基于**角度传感器**(例如磁编码器)的**有感 FOC**,即一个完整的**电流反馈环**,可以进行**扭矩控制**。借助本库,你可以进一步使用 **FPGA** 、**软核 MCU** 或**外置 MCU** 实现更复杂的电机应用。
| ![diagram](./diagram.png) |
| ![diagram](./figures/diagram.png) |
| :---: |
| 图1系统框图 |
| **图1**:系统框图 |
该项目代码有**详细的注释**,结合参考资料 [6~9],可以带你快速地熟悉 **FOC** 。另外,一些用户在读代码时向本人反馈了一些疑问,我将它们整理在了 [FAQ](#FAQ) 里。
本库代码有详细的注释,如果你熟悉 Verilog 但不熟悉 FOC ,可以通过阅读代码来快速地学习 FOC (建议先阅读 FOC 原理 [6~9])。一些用户在读代码时向本人反馈了一些疑问,我将它们整理在了 [FAQ](#FAQ) 里。
## 技术特点
### 技术特点
* **平台无关** :纯 RTL 编写,可以在 Altera 和 Xilinx 等各种 FPGA 上运行。
* 支持 **12bit 分辨率**的**角度传感器**和**相电流采样ADC**,对于>12bit的传感器需要进行低位截断。对于<12bit的传感器需要进行低位补0
* 支持 **3路PWM** + **1路EN** PWM=1 时上桥臂导通PWM=0 时下桥臂导通。 EN=0 时所有 MOS 关断。
* 使用 **16bit 有符号整数**进行计算,降低了资源消耗,考虑到传感器为 12bit16bit 计算是够用的。
* 支持 **3路PWM** + **1路EN** PWM=1 时上桥臂 MOS 导通PWM=0 时下桥臂 MOS 导通。 EN=0 时所有的 6 个 MOS 关断。
* 支持 **12bit 分辨率**的**角度传感器**和**相电流采样ADC**。对于>12bit的传感器需要进行低位截断。对于<12bit的传感器需要进行低位填充
* 内部使用 **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** ,用来接:
* **I2C (2*GPIO)** 用来连接 AS5600 磁编码器。
@ -41,19 +53,35 @@ FpOC
* **PWM_EN (1*GPIO)** 用来输出 1 路 EN (使能) 到电机驱动板EN=低电平代表所有桥臂关断)。
* **UART (1*GPIO)** ,单向(仅发送)的UART连接计算机的串口用于监测电流环的跟随曲线**可不接**。
* **PMSM****BLDC 电机**
* **获取转子角度的磁编码器**:本库支持的型号是 AS5600 ,需要安装在电机上。
* **电机驱动板**要支持3相 PWM 输入信号,并且要内置**低侧电阻采样法+放大器**对 3 相电流进行放大。
* **相电流采样的ADC**:本库支持的型号是 AD7928 ,用于采样电机驱动板放大后的 3 相电流
* 网上似乎找不到现成卖的 AD7928 模块,需要自己画 PCB。
* **电机驱动板**要支持3相 PWM 输入信号PWM=0时下桥臂导通PWM=1时上桥臂导通并且要内置**低侧电阻采样+放大器**对 3 相电流进行放大。
* **获取转子角度的磁编码器**:本库直接支持的型号是 AS5600 ,需要安装在电机上。
* **相电流采样的ADC**:本库直接支持的型号是 AD7928 ,用于采样电机驱动板放大后的 3 相电流
* 网上似乎找不到现成卖的 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** 晶振上。
* 若不是 **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 接口。
* **spi_ss, spi_sck, spi_mosi, spi_miso** 连接 AD7928 (ADC芯片) 的 SPI 接口。
* **pwm_a, pwm_b, pwm_c** 连接电机驱动板的 3 相 PWM 信号。
@ -62,19 +90,33 @@ FpOC
* 如果电机驱动板有 3 路 EN 输入,每个对应 1 相,则应该进行 1 对 3 连接。
* **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_AMPSVPWM 的最大振幅取值范围为1~511该值越小电机能达到的最大力矩越小但考虑到使用3相下桥臂电阻采样法来采样电流该值也不能太大以保证3个下桥臂有足够的持续导通时间来供ADC进行采样。在本例中使用默认值 9'd384 即可。
- SAMPLE_DELAY采样延时取值范围0~511考虑到3相的驱动 MOS 管从开始导通到电流稳定需要一定的时间所以从3个下桥臂都导通到 ADC 采样时刻之间需要一定的延时。该参数决定了该延时是多少个时钟周期,当延时结束时,该模块在 sn_adc 信号上产生一个高电平脉冲,指示外部 ADC “可以采样了”。在本例中,使用默认值 9'd120 即可。
- KpPID 的 P 参数
- KiPID 的 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
@ -94,55 +136,110 @@ FpOC
-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工程的顶层模块 | |
| 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 调用 | 不需要的话可以删除 |
| uart_monitor.sv | UART 发送器,用于数据监测 | 不需要的话可以移除 |
| 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可以开始采样 | 固定算法,一般不需要改动 |
| foc_top.sv | FOC+SVPWM (即**图1**中的蓝色部分的顶层) | 固定功能,一般不需要改动 |
| clark_tr.sv | Clark 变换 | 固定功能,一般不需要改动 |
| park_tr.sv | Park 变换 | 固定功能,一般不需要改动 |
| sincos.sv | 正弦/余弦计算器,被 park_tr.sv 调用 | 固定功能,一般不需要改动 |
| pi_controller.sv | PI 控制器PID没有D | 固定功能,一般不需要改动 |
| cartesian2polar.sv | 直角坐标系转极坐标系 | 固定功能,一般不需要改动 |
| svpwm.sv | SVPWM 调制器 | 固定功能,一般不需要改动 |
| hold_detect.sv | 监测3个下桥臂都导通时延迟一段时间后触发 sn_adc 信号指示ADC可以开始采样 | 固定功能,一般不需要改动 |
| ![diagram](./diagram.png) |
| ![diagram](./figures/diagram.png) |
| :---: |
| 图1系统框图 |
| **图1**:系统框图 |
在**图1**中
**图1**展示了这些模块的层次,我在设计模块层次时充分考虑了封装的合理性和代码重用
* **淡橙色**部分是FPGA外部的硬件包括
* Gate Driver、3相半桥(6个MOSFET)、3相采样电阻+放大器 。(这些通常集成在**电机驱动板**里)。
* 3相采样 ADC。
* 角度传感器。
* **粉色**部分是FPGA中的**硬件相关逻辑**,即传感器控制器,如果角度传感器和 ADC 型号变了,这部分代码需要重写。
* **青色**部分是FPGA中的 FOC 的固定算法,属于**硬件无关逻辑**,一般不需要改变。
* **黄色**部分是**用户自定逻辑**,用户可以修改 user behavior 来实现各种电机应用。或者修改 uart_monitor 来监测其它变量。
* **粉色**部分是FPGA内的即传感器控制器是**硬件相关逻辑**,如果角度传感器和 ADC 型号变了,这部分代码需要重写。
* **蓝色**部分是FPGA内的 FOC 的固定算法,是**硬件无关逻辑**,一般不需要修改,是本库的核心代码!
* **黄色**部分是FPGA内的**用户自定逻辑**,用户可以修改 user behavior 来实现各种电机应用。或者修改 uart_monitor 来监测其它变量。
* **淡橙色**部分是FPGA外部的硬件电路也就是电机、电机驱动板、角度传感器这些东西。
另外,除了 [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
@ -201,13 +298,14 @@ AD7928 只有一个T/H所以采样窗口内要做 3 次采样,这个过程
### 关于时钟周期和控制周期
* **问** **时钟频率为什么选择36.864MHz有什么特殊含义嘛我如果直接用FPGA的50MHz不经过PLL变成36.864MHz)会出现什么问题呢?**
* **答** 其实选 36.864MHz 只是因为可以让控制周期为 36864/2048 = 18kHz凑个整。
foc_top.sv 和 as5600_read.sv 对时钟频率是没有要求的,多高都可以(唯一的限制是频率太高时序不收敛)
但 adc_ad7928.sv 限制了频率不能大于 40.96MHz因为 ADC7928 的 SPI 时钟不能超过 20.48MHz,而 adc_ad7928.sv 内部用驱动时钟2分频作为SPI时钟。
综上,时钟频率是可调的,但要小于 40.96MHz。
* **答** 本库的 SVPWM 的频率被设计为 clk 频率/2048 ,所以选 36.864MHz 只是因为可以让控制周期为 36864/2048 = 18kHz凑个整。
foc_top.sv 和 as5600_read.sv 对时钟频率是没有要求的,多高都可以(唯一的限制是频率太高时可能会导致 FPGA 时序不收敛)
但 adc_ad7928.sv 限制了频率不能大于 40MHz因为 ADC7928 的 SPI 时钟不能超过 20.48MHz,而 adc_ad7928.sv 内部用驱动时钟2分频作为SPI时钟。
综上,时钟 clk 的频率是可调的,但要小于 40MHz。
* **问** **代码里 控制频率= 时钟频率 / 2048为什么 我觉得控制频率和时钟频率应该是没有必然的联系时钟频率是FPGA的时钟频率而控制频率是pwm的频率一般是几十khz你是根据控制频率来确定的时钟频率嘛**
* **答** 理想地来讲FPGA时钟频率和控制周期确实没有关系但要考虑实际PWM不也是FPGA的时钟驱动的既然如此必然有个分频系数控制频率 = 时钟频率 / 分频系数别的驱动器可能具有可调的分频系数但FpOC里为了简单分频系数=2048。这意味着 FpOC 如果要调节控制频率就只能调时钟频率目前受限于上述ADC7928的频率限制最高的控制频率是 40.96MHz/2048=20kHz。
* **答** 从原理上讲FPGA时钟频率和控制周期确实没有关系但要考虑实际因为PWM是由FPGA的时钟驱动的所以必然有个分频系数控制频率 = 时钟频率 / 分频系数别人实现的FOC可能具有可调的分频系数但FpOC里为了简单分频系数=2048。这意味着 FpOC 如果要调节控制频率,就只能调时钟频率。
# 参考资料

View File

@ -1,4 +1,8 @@
`timescale 1 ns/1 ns
// 模块: cartesian2polar
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能: 把直角坐标系 (x,y) 转换为极坐标系
module cartesian2polar #(
parameter ATTENUAION = 0

View File

@ -1,4 +1,8 @@
`timescale 1 ns/1 ns
// clark_tr
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能: clark 变换
module clark_tr(
input wire rstn,

View File

@ -1,12 +1,14 @@
`timescale 1 ns/1 ns
// 模块: foc_top
// Type : synthesizable, IP's top
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能FOC 算法(仅包含电流环) + SVPWM
// 参数:详见下方注释
// 输入输出:详见下方注释
module foc_top #(
// ----------------------------------------------- 模块参数 ---------------------------------------------------------------------------------------------------------------------------------------------------
parameter INIT_CYCLES = 16777216, // 决定了初始化步骤多少个时钟(clk)周期取值范围为1~4294967294。该值不能太短因为要留足够的时间让转子回归电角度=0。例如若时钟(clk)频率为 36.864MHzINIT_CYCLES=16777216则初始化时间为 16777216/36864000=0.45 秒
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进行采样。

View File

@ -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 #(
parameter [15:0] SAMPLE_DELAY = 16'd100

View File

@ -1,4 +1,8 @@
`timescale 1 ns/1 ns
// 模块: park_tr
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能: park 变换器
module park_tr(
input wire rstn,

View File

@ -1,4 +1,8 @@
`timescale 1 ns/1 ns
// 模块: pi_controller
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能: PI 控制器
module pi_controller #(
parameter logic [23:0] Kp = 24'd32768,

View File

@ -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(
input wire rstn,

View File

@ -1,11 +1,13 @@
`timescale 1 ns/1 ns
// 模块svpwm
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能7 段式 SVPWM 生成器(调制器)
// 输入:定子极坐标系下的电压矢量 Vsρ, Vsθ
// 输出PWM使能信号 pwm_en
// 3相PWM信号 pwm_a, pwm_b, pwm_c
// 说明:该模块产生的 PWM 的频率是 clk 频率 / 2048。例如 clk 为 36.864MHz ,则 PWM 的频率为 36.864MHz / 2048 = 18kHz
module svpwm (
input wire rstn,
input wire clk,

120
RTL/pll.v
View File

@ -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

View File

@ -1,9 +1,11 @@
`timescale 1 ns/1 ns
// 模块: adc_ad7928
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能:通过 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 的哪个通道

View File

@ -1,9 +1,11 @@
`timescale 1 ns/1 ns
// 模块: as5600_read
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能:通过 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
)(

View File

@ -1,4 +1,8 @@
`timescale 1 ns/1 ns
// 模块: i2c_register_read
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能 : I2C 读控制器
module i2c_register_read #(
parameter [15:0] CLK_DIV = 16'd16,

View File

@ -1,9 +1,11 @@
`timescale 1 ns/1 ns
// 模块top
// Type : synthesizable, FPGA's top
// Standard: SystemVerilog 2005 (IEEE1800-2005)
// 功能FOC 使用示例是FPGA工程的顶层模块控制电机的切向力矩一会顺时针一会逆时针同时可以通过 UART 监测电流环控制的跟随曲线
// 参数:无
// 输入输出:详见下方注释
module top(
input wire clk_50m, // 50MHz 时钟
// ------- 3相 PWM 信号,(包含使能信号) -----------------------------------------------------------------------------------------------------
@ -44,11 +46,9 @@ 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
);
wire [3:0] subwire0;
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 ());
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;

View File

@ -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

View File

@ -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 发送出去,
// 参数:无
// 输入输出:详见下方注释
module uart_monitor #(
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_val2,
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;
wire tx_rdy;
@ -25,8 +27,8 @@ reg [7:0] tx_data;
reg itoa_en;
reg signed [15:0] itoa_val;
wire itoa_oen;
wire [ 7:0] itoa_str [6];
reg itoa_oen;
reg [ 7:0] itoa_str [6];
reg [ 2:0] vcnt;
@ -108,24 +110,79 @@ always @ (posedge clk or negedge rstn)
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 )
);
reg [ 2:0] itoa_cnt;
reg itoa_sign;
reg itoa_zero;
reg [15:0] itoa_abs;
reg [ 3:0] itoa_rem;
always @ (posedge clk or negedge rstn)
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

View File

@ -1,4 +1,7 @@
`timescale 1 ns/1 ns
// 模块: uart_tx
// Type : synthesizable
// Standard: SystemVerilog 2005 (IEEE1800-2005)
module uart_tx #(
parameter [15:0] CLK_DIV = 217 // 25MHz / 217 = 115207 ~= 115200

105
SIM/tb_clark_park_tr.sv Normal file
View 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

View 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
View 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

View 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
figures/diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
figures/sch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
figures/tb_svpwm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
figures/tb_svpwm_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
gerber_pcb_foc_shield.zip Normal file

Binary file not shown.