文章目录
前言
题目:单相逆变电源
提示:全桥电路,单片机控制FPGA输出SPWM波,PID控制SPWM波正弦调制波调制度来控制电压大小。
一、环境及项目情况
使用硬件:
- Tiva TM4C123GH6PM 作为烧录器
- FPGA板:Cyclone IV E + Tiva TM4C123H6PZ
使用软件:
- Code Composer Studio
- Quartus Prime 17
1.1 CCS配置
大致同MSP430的配置,额外步骤如下:
注意Tiva TM4C为ARM架构,性能强劲
1.2 Quartus 配置
按以下设置配置无用管脚,不然会输出奇怪的电平
1.3 系统总体框图
二、软件设计
2.1 单片机,FPGA通信示意图
2.2 MSP430主程序 main.c
#include "common.h"
#include "bus_fpga.h"
#include "LCD12864_rom_enable.h"
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "inc/tm4c123gh6pm.h"
#include "inc/hw_memmap.h"
#include "driverlib/debug.h"
#include "driverlib/fpu.h"
#include "driverlib/gpio.h"
#include "driverlib/sysctl.h"
#include "lib/blue.h"
#define MAXLEN 20 //多次读取AD取平均,数组大小
/**
* main.c
*/
double get_DC_voltage(unsigned char base, unsigned char L16_addr);
double get_AC_voltage(unsigned char base, unsigned char H16_addr);
double PID(double V); //调制度ma
void updateKey(void);
void responseKey(void);
void globalParamConstraints(void);
void update_AC_VI_Measured(void);
void initADint(void);
void Timer0IntHandler(void);
char keydat; //从FPGA按键读值
int key; //key = updateKey(keydat),具体的操作数
double V0 = 0, I0 = 0;
double Kp = 66, Ki = 27, Kd = 5;
double TarV = 10.1; //目标电压
int TarFreq = 50; //目标频率
double ma = 9880; // 调制度,精度万分之一
int SysFreq = 12500;
int VoI = 0;
int isPID = 1;
double ratioV = 1, ratioI = 1;
double biasV = 0, biasI = 0;
double MeasVsum[MAXLEN] = { 0 }, MeasIsum[MAXLEN] = { 0 };
double MeasV, MeasI;
int MeasCnt = 0, MeasIcnt = 0;
double deltV[3] = { 0 };
// 固化后一定要重启FPGA
// 初始化AD中断
void initADint(void)
{
SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER0);
//使能TIMER0
//TimerConfigure(TIMER0_BASE, TIMER_CFG_ONE_SHOT);//单次计数模式
TimerConfigure(TIMER0_BASE, TIMER_CFG_A_PERIODIC); //周期性计数模式
// TimerConfigure(TIMER0_BASE, TIMER_CFG_PERIODIC);
TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet() / 20 / 3); //计数频率10HZ
IntEnable(INT_TIMER0A); //NVIC
//使能TIMER0A
TimerIntEnable(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
//TIMEOUT标志位触发中断
IntMasterEnable();
//为定时器指定中断处理函数
TimerIntRegister(TIMER0_BASE, TIMER_A, Timer0IntHandler);
//master interrupt enable API for all interrupts
TimerEnable(TIMER0_BASE, TIMER_A);
//TIMER0A开始计数,当计数值等于TimerLoadSet,触发中断
}
// (AD)TimerB中断服务函数
void Timer0IntHandler(void)
{
TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT); //清除标志位
update_AC_VI_Measured();
}
int main(void)
{
SysCtlClockSet(
SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN); //Main OSC = using pll = 40MHz = 2.5 * 16MHz
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); //解锁F、D、C口
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOC);
//解锁
HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY; //该地址赋值GPIO_LOCK_KEY,解锁,p684
HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= GPIO_PIN_0; //允许写入,GPIOAFSEL, GPIOPUR, GPIOPDR, or GPIODEN
HWREG(GPIO_PORTD_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY; //Lock and commit
HWREG(GPIO_PORTD_BASE + GPIO_O_CR) |= GPIO_PIN_7; //F, D
lcd12864_init(); // 显示屏初始化
DispClear(); // 清屏
BUS_Init(); // 初始化单片机<=>FPGA
initADint();
float V0f = V0, I0f = I0;
initialize_uart();
blue1(&V0f); //double -> float 绑定蓝牙传输数值
blue2(&I0f);
IOWR(CS6, 0, 0); //不过流
while (1)
{
//读取键盘
keydat = IORD(CS0, 0);
//更新AD采样结果
V0 = MeasV;
I0 = MeasI; //Next: A5
//Update Ratio
if (TarFreq > 50)
{
V0 = V0 * (14.40184197 + biasV); // - 0.658;
I0 = I0 * (1.5358854 + biasI); // - 0.0425;
}
else
{
V0 = V0 * (14.09751 + biasV);
I0 = I0 * (1.19746 + biasI);
// V0 = V0 * (14.13+biasV - 0.1324);
// I0 = I0 * (1.363856+biasI - 0.0437);
}
V0f = V0;
I0f = I0;
//PID
if (isPID)
{
double deltM = PID(V0);
ma += deltM;
}
SysCtlDelay(SysCtlClockGet() * 0.1 / 3); //delay 0.2 s
//键盘事件响应
sentdata(); //蓝牙发送数据
updateKey();
responseKey();
//全局参数限制
globalParamConstraints();
//过流保护
if (I0 >= 1.5) //过流*A
{
IOWR(CS6, 0, 1); //触发过流
SysCtlDelay(SysCtlClockGet() * 2 / 3); //延迟两秒后取消过流保护
IOWR(CS6, 0, 0); //触发过流
}
else
{
}
//更新主要参数
IOWR(CS1, 0, TarFreq);
IOWR(CS1, 1, SysFreq);
IOWR(CS1, 2, (int) ma);
//屏幕显示
DispString5x8(1, 1 * 8, "Freq:", 1);
DispNumber5x8(1, 6 * 8, TarFreq, 3, 1); //Page 行,column列
DispString5x8(1, 10 * 8, "PID:", 1);
DispNumber5x8(1, 14 * 8, isPID, 1, 1); //Page 行,column列
DispString5x8(2, 1 * 8, "TarV:", 1);
DispNumber5x8(2, 6 * 8, TarV, 2, 1); //Page 行,column列
DispString5x8(3, 1 * 8, "V,Io:", 1);
DispFloat5x8(3, 6 * 8, V0, 1, 2, 4); //Page 行,column列
DispFloat5x8(4, 6 * 8, I0, 1, 2, 4); //Page 行,column列
DispString5x8(5, 1 * 8, "SysF:", 1);
DispNumber5x8(5, 6 * 8, SysFreq, 5, 1); //Page 行,column列
DispString5x8(6, 1 * 8, "MA:", 1);
DispNumber5x8(6, 6 * 8, (int) ma, 4, 1);
//
// DispString5x8(7, 1 * 8, "V:", 1);
DispFloat5x8(7, 12 * 8, biasV > 0 ? biasV : -biasV, 1, 2, 2);
// DispString5x8(8, 1 * 8, "I:", 1);
DispFloat5x8(8, 12 * 8, biasI > 0 ? biasI : -biasI, 1, 2, 2);
DispString5x8(7, 1 * 8, "P:", 1);
DispFloat5x8(7, 6 * 8, V0 * I0, 1, 2, 2); //Page 行,column列
// DispString5x8(6, 1 * 8, Kp > 0 ? "Kp:" : "-Kp:", 1);
// DispFloat5x8(6, 6 * 8, Kp > 0 ? Kp : -Kp, 1, 2, 4);
// DispString5x8(7, 1 * 8, Ki > 0 ? "Ki:" : "-Ki:", 1);
// DispFloat5x8(7, 6 * 8, Ki > 0 ? Ki : -Ki, 1, 2, 4);
DispString5x8(8, 1 * 8, Kd > 0 ? "Kd:" : "-Kd:", 1);
DispFloat5x8(8, 6 * 8, Kd > 0 ? Kd : -Kd, 1, 2, 4);
}
return 0;
}
void globalParamConstraints(void)
{
ma = ma > 9940 ? 9940 : ma;
ma = ma < 9400 ? 9400 : ma;
TarFreq = TarFreq > 110 ? 110 : TarFreq;
TarFreq = TarFreq < 15 ? 15 : TarFreq;
}
//将获得的采样值转换为double类型
double get_DC_voltage(unsigned char base, unsigned char L16_addr)
{
double DC_voltage;
DC_voltage = IORD(base, L16_addr);
DC_voltage = (DC_voltage - 0x8000) * 10.24 / 0x7FFF;
return DC_voltage;
}
double get_AC_voltage(unsigned char base, unsigned char H16_addr)
{
double AC_voltage, cnt;
unsigned int AC_voltage_H16, AC_voltage_M16, AC_voltage_L16;
AC_voltage_H16 = IORD(base, H16_addr);
AC_voltage_M16 = IORD(base, H16_addr + 1);
AC_voltage_L16 = IORD(base, H16_addr + 2);
cnt = IORD(base, 6); // Locked at 6
AC_voltage = AC_voltage_H16;
AC_voltage = AC_voltage * 65536;
AC_voltage = AC_voltage + AC_voltage_M16;
AC_voltage = AC_voltage * 65536;
AC_voltage = AC_voltage + AC_voltage_L16;
AC_voltage = AC_voltage / cnt;
AC_voltage = sqrt(AC_voltage);
AC_voltage = (AC_voltage) * 10.24 / 0x7FFF;
// DispString5x8(6, 1 * 8, "cnt:", 1);
// DispNumber5x8(6, 6 * 8, cnt, 5, 1);
//
// DispString5x8(7, 1 * 8, "H,M,L:", 1);
// DispNumber5x8(7, 6 * 8, AC_voltage_H16, 5, 1);
// DispNumber5x8(7, 12 * 8, AC_voltage_M16, 5, 1);
// DispNumber5x8(8, 12 * 8, AC_voltage_L16, 5, 1);
if (AC_voltage > 5)
AC_voltage = 0.1;
return AC_voltage;
}
void update_AC_VI_Measured(void)
{
double V, I, Vsum = 0, Isum = 0;
I = get_AC_voltage(CS5, 7); //A6
V = get_AC_voltage(CS4, 0); //A0
MeasVsum[MeasCnt] = V;
MeasIsum[MeasCnt] = I;
MeasCnt++;
MeasCnt = MeasCnt >= MAXLEN ? 0 : MeasCnt;
int i;
for (i = 0; i < MAXLEN; i++)
{
Vsum += MeasVsum[i];
Isum += MeasIsum[i];
}
MeasV = Vsum / (double) MAXLEN;
MeasI = Isum / (double) MAXLEN;
// MeasV = V;
// MeasI = I;
}
//上一时刻电压,此时电压,目标电压
double PID(double V)
{
double deltM; //调制度增量
deltV[2] = TarV - V;
deltM = Kp * (deltV[2] - deltV[1]) + Ki * deltV[2]
+ Kd * (deltV[2] - deltV[1] * 2 + deltV[0]);
deltV[0] = deltV[1];
deltV[1] = deltV[2];
return deltM;
}
void responseKey(void)
{
//1,4: 步进电压/频率
if (key == 1)
{
TarFreq = TarFreq + 1;
}
if (key == 4)
{
TarFreq = TarFreq - 1;
}
//2,5调制度
if (key == 2) // 2022.06.27, max at 9800
{
if (ma >= 9900)
ma += 1;
else
ma += 10;
}
if (key == 5)
{
if (ma >= 9900)
ma -= 1;
else
ma -= 10;
}
//3,6 系统频率
if (key == 3)
{
SysFreq = SysFreq + 100;
}
if (key == 6)
{
SysFreq = SysFreq - 100;
}
//*:14, #:15, 0:0, A:10, B:11, C:12, D:13
//7,* Kp
if (key == 7)
{
// Kp += 1;
biasV += 0.01;
}
if (key == 14)
{
// Kp -= 1;
biasV -= 0.01;
}
//8,0 Ki
if (key == 8)
{
// Ki += 1;
biasI += 0.01;
}
if (key == 0)
{
// Ki -= 1;
biasI -= 0.01;
}
//9,15 Kd
if (key == 9)
{
Kd += 1;
}
if (key == 15)
{
Kd -= 1;
}
//Enable PID
if (key == 10)
{
isPID = isPID > 0 ? 0 : 1;
}
}
void updateKey(void)
{
switch (keydat)
{
case 0x11:
key = 1;
break;
case 0x21:
key = 2;
break;
case 0x41:
key = 3;
break;
case 0x81:
key = 10;
break;
case 0x12:
key = 4;
break;
case 0x22:
key = 5;
break;
case 0x42:
key = 6;
break;
case 0x82:
key = 11;
break;
case 0x14:
key = 7;
break;
case 0x24:
key = 8;
break;
case 0x44:
key = 9;
break;
case 0x84:
key = 12;
break;
case 0x18:
key = 14;
break;
case 0x28:
key = 0;
break;
case 0x48:
key = 15;
break;
case 0x88:
key = 13;
break;
default:
key = -1;
break;
}
updateViaBtKey(&key);
}
2.3 Verilog 顶层文件
module top_module
(
input clk_50M, rst_n,
input RD, WR,
inout [15:0] DATA,
input [ 7:0] ADDR,
inout [27:0] GPIO_A,//FPGA A区管脚
inout [11:0] GPIO_B,//FPGA B区管脚
inout [ 7:0] GPIO_KEY,//FPGA KEY区管脚
output INT//FPGA 中断
);
wire cs0, cs1, cs2, cs3, cs4, cs5, cs6, cs7;
// KEY 4'd0:FREQ/1:SysF/2:MA NUL NUL AD1 AD2 OVER NUL
wire [15:0] rddat0, rddat1, rddat2, rddat3, rddat4, rddat5, rddat6, rddat7;
wire [15:0] wrdat;
wire [ 3:0] addr;
BUS BUS_inst
(
.clk (clk_50M), // input clk_sig
.ADDR (ADDR), // input [7:0] ADDR_sig
.RD (RD), // input RD_sig
.WR (WR), // input WR_sig
.DATA (DATA), // inout [15:0] DATA_sig
.cs0 (cs0), // output cs0_sig
.cs1 (cs1), // output cs1_sig
.cs2 (cs2), // output cs2_sig
.cs3 (cs3), // output cs3_sig
.cs4 (cs4), // output cs4_sig
.cs5 (cs5), // output cs5_sig
.cs6 (cs6), // output cs6_sig
.cs7 (cs7), // output cs7_sig
.addr (addr), // output [3:0] addr_sig
.rddat0 (rddat0), // input [15:0] rddat0_sig
.rddat1 (rddat1), // input [15:0] rddat1_sig
.rddat2 (rddat2), // input [15:0] rddat2_sig
.rddat3 (rddat3), // input [15:0] rddat3_sig
.rddat4 (rddat4), // input [15:0] rddat4_sig
.rddat5 (rddat5), // input [15:0] rddat5_sig
.rddat6 (rddat6), // input [15:0] rddat6_sig
.rddat7 (rddat7), // input [15:0] rddat7_sig
.wrdat (wrdat) // output [15:0] wrdat_sig
);
KEY KEY_inst
(
.clk (clk_50M),
.rddat (rddat0),
.cs (cs0),
.KEY_H (GPIO_KEY[7:4]),
.KEY_V (GPIO_KEY[3:0])
);
wire clk_50Hz;
assign GPIO_A[17] = clk_50Hz;
clkdiv u_clkdiv
(
.clk (clk_50M),
.rst_n (rst_n),
.div (32'd1_000_000),
.clkout (clk_50Hz)
);
ads8688 u_ads8688
(
.clk_50M (clk_50M),
.rst_n (rst_n),
.RD (RD), // 读信号
.addr (addr), // 低数据地址?
.datacs (cs4),
.datacs2 (cs5),
.clk_50Hz (clk_50Hz),
.SDI (GPIO_A[15]), // ADS8688 串行数字输DSW入
.CS (GPIO_A[19]), // ADS8688 片选信号输入
.SCLK (GPIO_A[23]), // ADS8688 串行时钟输入
.SDO (GPIO_A[27]), // ADS8688 串行数字输出
.outRMS (rddat4), //rddat4
.outRMS2 (rddat5)
);
//CS1: 4'd0:FREQ/1:SysF/2:MA
reg [15:0] sin_freq;
reg [15:0] ma;
reg [15:0] sys_freq;
always@(posedge clk_50M, negedge rst_n) //用 else if + case可以修正时序问题(无法wirte)
begin
if(!rst_n)
begin
sin_freq<=16'd50;
ma<=16'd96;
sys_freq<=16'd20000;
end
else if(WR && cs1)
begin
case(addr)
4'd0:sin_freq <= wrdat;
4'd1:sys_freq <= wrdat;
4'd2:ma<=wrdat;
endcase
end
end
wire pwm1, pwm2;
spwm spwm_inst
(
.clk(clk_50M) , // input clk_sig
.rst_n(rst_n) , // input rst_n_sig
.fre(sin_freq) , // input [15:0] freq
.sys_fre(sys_freq),// input [15:0] system freq
.ma(ma), // input [15:0] ma
.pwm1(pwm1), // output [15:0] pwm1, H-3
.pwm2(pwm2) //L-7
);
//Reversed
//CS6 控制过流保护继电器
reg is_overflow;
always@(posedge clk_50M, negedge rst_n)
begin
if(!rst_n)
is_overflow<=0;
else if(WR && cs6)
is_overflow <= (wrdat==16'd1)?1:0;
end
assign GPIO_A[2]=(is_overflow==1)?1:0;
assign GPIO_A[7]=(is_overflow==1)?0:pwm1;
assign GPIO_A[3]=(is_overflow==1)?0:pwm2;
endmodule
2.4 注意点:
- 使用ADS8688采样,FPGA驱动,注意采样时序
2.5 其他代码
见压缩包
三、固化
烧录后要重新给FPGA上电!
四、总结
- 掌握CCS、Quartus配置