mirror of
https://github.com/WangXuan95/FpOC.git
synced 2025-01-17 23:22:52 +08:00
first commit
This commit is contained in:
commit
728ba144e2
30
FPGA/foc.qpf
Normal file
30
FPGA/foc.qpf
Normal 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
92
FPGA/foc.qsf
Normal 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
150
README.md
Normal 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 有符号整数**进行计算,降低了资源消耗,考虑到传感器为 12bit,16bit 计算是够用的。
|
||||
|
||||
# 运行示例
|
||||
|
||||
**图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/DRV8301),4根线: 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
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
61
RTL/foc/clark_tr.sv
Normal 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
272
RTL/foc/foc_top.sv
Normal 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.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 [ 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相的电流(简记为Ia),ib 是B相的电流(简记为Ib),ic 是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
40
RTL/foc/hold_detect.sv
Normal 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
53
RTL/foc/park_tr.sv
Normal 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
102
RTL/foc/pi_controller.sv
Normal 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
1118
RTL/foc/sincos.sv
Normal file
File diff suppressed because it is too large
Load Diff
1174
RTL/foc/svpwm.sv
Normal file
1174
RTL/foc/svpwm.sv
Normal file
File diff suppressed because it is too large
Load Diff
120
RTL/pll.v
Normal file
120
RTL/pll.v
Normal 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
135
RTL/sensors/adc_ad7928.sv
Normal 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
|
35
RTL/sensors/as5600_read.sv
Normal file
35
RTL/sensors/as5600_read.sv
Normal 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 为 40MHz,CLK_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
|
138
RTL/sensors/i2c_register_read.sv
Normal file
138
RTL/sensors/i2c_register_read.sv
Normal 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
164
RTL/top.sv
Normal 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.864MHz,CLK_DIV=10,则 SCL 频率为 36864/(4*10) = 922kHz 。注,AS5600 芯片要求 SCL 频率不超过 1MHz
|
||||
) as5600_i (
|
||||
.rstn ( rstn ),
|
||||
.clk ( clk ),
|
||||
.scl ( i2c_scl ), // I2C 接口: SCL
|
||||
.sda ( i2c_sda ), // I2C 接口: SDA
|
||||
.o_en ( ), // output: 每成功读取一次 φ,o_en就产生一个高电平脉冲,这里我们用不到该信号
|
||||
.o_phi ( phi ) // output: 转子机械角度 φ
|
||||
);
|
||||
|
||||
|
||||
|
||||
// AD7928 ADC 读取器,用于读取3相电流采样值(读出的是未经任何处理的ADC原始值)
|
||||
adc_ad7928 #(
|
||||
.CH_CNT ( 3'd2 ), // 该参数取2,指示我们只想要 CH0, CH1, CH2 这三个通道的 ADC 值
|
||||
.CH0 ( 3'd1 ), // 指示 CH0 对应 AD7928 的 通道1。(硬件上 A 相电流连接到 AD7928 的 通道1)
|
||||
.CH1 ( 3'd2 ), // 指示 CH1 对应 AD7928 的 通道2。(硬件上 B 相电流连接到 AD7928 的 通道2)
|
||||
.CH2 ( 3'd3 ) // 指示 CH2 对应 AD7928 的 通道3。(硬件上 C 相电流连接到 AD7928 的 通道3)
|
||||
) adc_ad7928_i (
|
||||
.rstn ( rstn ),
|
||||
.clk ( clk ),
|
||||
.spi_ss ( spi_ss ), // SPI 接口:SS
|
||||
.spi_sck ( spi_sck ), // SPI 接口:SCK
|
||||
.spi_mosi ( spi_mosi ), // SPI 接口:MOSI
|
||||
.spi_miso ( spi_miso ), // SPI 接口:MISO
|
||||
.i_sn_adc ( sn_adc ), // input : 当 sn_adc 出现高电平脉冲时,该模块开始进行一次(3路的)ADC 转换
|
||||
.o_en_adc ( en_adc ), // output: 当转换结束后,en_adc 产生一个周期的高电平脉冲
|
||||
.o_adc_value0 ( adc_value_a ), // 当 en_adc 产生一个周期的高电平脉冲的同时,adc_value_a 上出现 A 相电流的 ADC 原始值
|
||||
.o_adc_value1 ( adc_value_b ), // 当 en_adc 产生一个周期的高电平脉冲的同时,adc_value_b 上出现 B 相电流的 ADC 原始值
|
||||
.o_adc_value2 ( adc_value_c ), // 当 en_adc 产生一个周期的高电平脉冲的同时,adc_value_c 上出现 C 相电流的 ADC 原始值
|
||||
.o_adc_value3 ( ), // 忽略其余 5 路 ADC 转换结果
|
||||
.o_adc_value4 ( ), // 忽略其余 5 路 ADC 转换结果
|
||||
.o_adc_value5 ( ), // 忽略其余 5 路 ADC 转换结果
|
||||
.o_adc_value6 ( ), // 忽略其余 5 路 ADC 转换结果
|
||||
.o_adc_value7 ( ) // 忽略其余 5 路 ADC 转换结果
|
||||
);
|
||||
|
||||
|
||||
|
||||
// FOC + SVPWM 模块 (使用方法和原理详见 foc_top.sv)
|
||||
foc_top #(
|
||||
.INIT_CYCLES ( 16777216 ), // 本例中,时钟(clk)频率为 36.864MHz,INIT_CYCLES=16777216,则初始化时间为 16777216/36864000=0.45 秒
|
||||
.ANGLE_INV ( 1'b0 ), // 本例中,角度传感器没装反(A->B->C->A 的旋转方向与 φ 增大的方向相同),则该参数应设为 0
|
||||
.POLE_PAIR ( 8'd7 ), // 本例使用的电机的极对数为 7
|
||||
.MAX_AMP ( 9'd384 ), // 384 / 512 = 0.75。说明 SVPWM 的最大振幅 占 最大振幅极限的 75%
|
||||
.SAMPLE_DELAY ( 9'd120 ), // 采样延时,取值范围0~511,考虑到3相的驱动 MOS 管从开始导通到电流稳定需要一定的时间,所以从3个下桥臂都导通,到 ADC 采样时刻之间需要一定的延时。该参数决定了该延时是多少个时钟周期,当延时结束时,该模块在 sn_adc 信号上产生一个高电平脉冲,指示外部 ADC “可以采样了”
|
||||
.Kp ( 24'd32768 ), // 电流环 PID 控制算法的 P 参数
|
||||
.Ki ( 24'd2 ) // 电流环 PID 控制算法的 I 参数
|
||||
) foc_top_i (
|
||||
.rstn ( rstn ),
|
||||
.clk ( clk ),
|
||||
.phi ( phi ), // input : 角度传感器输入(机械角度,简记为φ),取值范围0~4095。0对应0°;1024对应90°;2048对应180°;3072对应270°。
|
||||
.sn_adc ( sn_adc ), // output: 3相电流 ADC 采样时刻控制信号,当需要进行一次采样时,sn_adc 信号上产生一个时钟周期的高电平脉冲,指示ADC应该进行采样了。
|
||||
.en_adc ( en_adc ), // input : 3相电流 ADC 采样结果有效信号,sn_adc 产生高电平脉冲后,外部ADC开始采样3相电流,在转换结束后,应在 en_adc 信号上产生一个周期的高电平脉冲,同时把ADC转换结果产生在 adc_a, adc_b, adc_c 信号上
|
||||
.adc_a ( adc_value_a ), // input : A 相 ADC 采样结果
|
||||
.adc_b ( adc_value_b ), // input : B 相 ADC 采样结果
|
||||
.adc_c ( adc_value_c ), // input : C 相 ADC 采样结果
|
||||
.pwm_en ( pwm_en ),
|
||||
.pwm_a ( pwm_a ),
|
||||
.pwm_b ( pwm_b ),
|
||||
.pwm_c ( pwm_c ),
|
||||
.en_idq ( en_idq ), // output: 出现高电平脉冲时说明 id 和 iq 出现了新值,每个控制周期 en_idq 会产生一个高电平脉冲
|
||||
.id ( id ), // output: d 轴(直轴)的实际电流值,可正可负
|
||||
.iq ( iq ), // output: q 轴(交轴)的实际电流值,可正可负(若正代表逆时针,则负代表顺时针,反之亦然)
|
||||
.id_aim ( id_aim ), // input : d 轴(直轴)的目标电流值,可正可负,在不使用弱磁控制的情况下一般设为0
|
||||
.iq_aim ( iq_aim ), // input : q 轴(直轴)的目标电流值,可正可负(若正代表逆时针,则负代表顺时针,反之亦然)
|
||||
.init_done ( ) // output: 初始化结束信号。在初始化结束前=0,在初始化结束后(进入FOC控制状态)=1
|
||||
);
|
||||
|
||||
|
||||
|
||||
reg [23:0] cnt;
|
||||
always @ (posedge clk or negedge rstn) // 该 always 维护一个 24bit 的 自增计数器
|
||||
if(~rstn)
|
||||
cnt <= 24'd0;
|
||||
else
|
||||
cnt <= cnt + 24'd1;
|
||||
|
||||
|
||||
assign id_aim = $signed(16'd0); // 令 id_aim 恒等于 0
|
||||
|
||||
always @ (posedge clk or negedge rstn) // 该 always 块令 iq_aim 交替地取 +200 和 -200 ,即电机的切向力矩一会顺时针一会逆时针
|
||||
if(~rstn) begin
|
||||
iq_aim <= $signed(16'd0);
|
||||
end else begin
|
||||
if(cnt[23])
|
||||
iq_aim <= $signed(16'd200); // 令 id_aim = +200
|
||||
else
|
||||
iq_aim <= -$signed(16'd200); // 令 id_aim = -200
|
||||
end
|
||||
|
||||
|
||||
|
||||
// UART 发送器(监视器),格式为:115200,8,n,1
|
||||
uart_monitor #(
|
||||
.CLK_DIV ( 16'd320 ) // UART分频倍率,在本例中取320。因为时钟频率为 36.864MHz, 36.864MHz/320=115200
|
||||
) uart_monitor_i (
|
||||
.rstn ( rstn ),
|
||||
.clk ( clk ),
|
||||
.i_en ( en_idq ), // input: 当 en_idq 上出现高电平脉冲时,启动 UART 发送
|
||||
.i_val0 ( id ), // input: 以十进制的形式发送变量 id
|
||||
.i_val1 ( id_aim ), // input: 以十进制的形式发送变量 id_aim
|
||||
.i_val2 ( iq ), // input: 以十进制的形式发送变量 iq
|
||||
.i_val3 ( iq_aim ), // input: 以十进制的形式发送变量 iq_aim
|
||||
.o_uart_tx ( uart_tx ) // output: UART 发送信号
|
||||
);
|
||||
|
||||
endmodule
|
52
RTL/uart/itoa.sv
Normal file
52
RTL/uart/itoa.sv
Normal 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
131
RTL/uart/uart_monitor.sv
Normal 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
47
RTL/uart/uart_tx.sv
Normal 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
BIN
diagram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
Loading…
x
Reference in New Issue
Block a user