从零开始做单相逆变电源(软件)


前言

题目:单相逆变电源
在这里插入图片描述

提示:全桥电路,单片机控制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 配置

配置设备
使用EP4CE6E22C8

按以下设置配置无用管脚,不然会输出奇怪的电平

常规管脚配置
常规管脚配置
可以导入别人的管脚配置文件(.qsf)
或者自己一个个指定

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配置
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值