【GD32F427开发板试用】INA226完成电流电压采集

news2024/11/17 14:51:13

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:จุ๊บ冰语

前言

本次有幸参与并通过了极术社区组织的【GD32F427开发板试用】活动,让我对国产兆易创新的GD32处理器有了更深刻的认识。
开发板到手后,先从邮箱中提供的链接https://aijishu.com/a/1060000…下载相关资料,当然我们也可以直接访问兆易创新的官网 资料库:https://www.gd32mcu.com/cn/do…去下载关于GD32F4更加详尽的资料。本次开发板主要使用到的资料为(对资料清单进行了稍详细的说明,希望对其他人有帮助):
表1 资料清单

编号文件名说明
1GD32F427xx DatasheetGD32F427系列MCU数据手册,里面可以看到每个引脚对应的复用功能及复用配置的AFx等信息
2GD32F4xx系列MCU用户手册支持GD32F470/GD32F427/GD32F425,这个是一个1000页的庞大pdf文件,对每个内部资源及外设都有详细介绍,包括对应的寄存器等信息
3GD32F4xx Firmware LibraryGD32F4xx标准固件库。适用于GD32F4xx系列MCU,与Cortex-M微控制器软件接口标准(CMSIS)兼容。固件库包括程序、数据结构和宏定义,覆盖所有集成外设的特征,并包括了全部相关驱动和示例程序。如果在 GD32F4xx_Demo_Suites 目录下未找到合适的例程代码,可在 GD32F4xx_Firmware_Library\Examples 中查找,总有咱能用到的
4GD32F4xx Firmware Library User GuideGD32F4xx系列MCU固件库使用指南,大部分的接口都会在这个pdf中说明
5GD32F4xx系列硬件开发指南专为基于GD32F4xx系列MCU开发者提供,对GD32F4xx系列产品硬件开发做了总体介绍。提供常用外设接口的电路及设计指导
6GD32F4xx AddOn1. GigaDevice.GD32F4xx_Addon.3.0.0.exe Keil4 环境补丁,支持Keil v4.7x; 2. GigaDevice.GD32F4xx_DFP.3.0.0.pack Keil5 在线支持包, 支持 Keil v5.27 及以上版本;我安装的MDK5.34,使用的就是这个pack文件; 3. IAR_GD32F4xx_ADDON.3.0.0.exe IAR 环境补丁,支持 IAR v7.4 以上版本。
7GD32F4xx_Demo_SuitesGD32F4xx系列开发板套件,支持GD32F427V-START、GD32F470V-START、GD32F427R-START、GD32F470i-EVAL和GD32F470Z-EVAL。

其中,咱们到手的开发板原理图就在
GD32F4xx_Demo_Suites_V2.6.1\GD32427R_START_Demo_Suites\Docs\Schematic
路径下,对应的例程在 GD32427R_START_Demo_Suites\Projects 中。 |


设计思路

之前从公众号看到一篇关于1.5V碱性电池充电的文章,再加上家里小孩玩玩具超级费电,这次刚好收到GD32F427开发板,加上之前买的INA226单路电流电压检测模块,就想着做一个数控电池充电工装,当然也可很简单的改为电源数控表头。
首先,放出资料链接:https://mp.weixin.qq.com/s/l4…基础电路集锦,其中第4个电路就是1.5V碱性电池充电电路;

图1 1.5v电池充电电路及分析

这是用LM358制作的一个双路1.5V电池充电器,使用TL431生成2.5V基准,50Ω和100Ω构成分压电路,使LM358的同向端电压为 2.5/150*100=1.666V,当反向端检测的电压达到1.666V时,关断三极管,停止充电。R3100Ω作为限流电阻,保证最大电流不超过60mA。

其实这里还有电池内阻,特别是使用越久电压越低的电池,内阻越大,所以电流不会超过50mA。


硬件设计

1、电路仿真

参考这个电路,我在multisim上也仿真了一个电路,不过我的调流使用的电位器+MOS管实现。

图2 仿真充电电路可行性

2、MOS管实测

实际MOS管选用的FSP740,实测GS电压3.21v开始导通,4.14v可通过500mA电流,MOS管只是轻微发热,如图

图3 MOS管Vgs电压实测: ch1为gs电压电流,ch2是ds电压电流

实际上我们充电时,通过INA226(单路)来采集充电电压电流信息,反馈给gd32,再控制数字电位器调节MOS管Vgs,进而调节电流。够成一个pid反馈环。

3、电路参数分析

设想模块最终可对1.2v镍氢电池,1.5v碱性电池,保证控制mos管的控制电压始终处于大部分都可覆盖到,且能保证控制电压可以关断mos管实现停止充电。实际电路中,图2的R3,R4都使用的微型电位器503,即50k。

先假设充电电池电压1.2v(实际电压约为0.9~1.4v)和1.5v(电池电压约为1.3~1.7v)档,即需要在0.9~1.7v范围内实现控制mos管,电位器(4路10k并联成2.5k)上分压范围为0.9+4.14=5.04v到1.7+3.21=4.91v,实际1.2和1.5v电池充电电流都<100mA,电压取4.2v到5v即可,此时R3电阻分压为4.2v,通过电流为(0.8/2500=0.00032A),R3=4/0.00032=12500欧,R4电阻为12-5=7/0.00032=21875欧,
即: R3: 12.5K, R4: 21.875K

软件设计

好了,硬件分析好了,开始写代码,主要四个部分:
1,INA226驱动,I2C接口;
2,AD8403驱动,spi接口;
3,反馈PID控制,调节充电电流;
4,输出系统。目前是通过usb虚拟串口上报的。

软件基于例程中 cdc_acm (路径为:GD32F4xx_Firmware_Library\Examples\USB\USB_Device\cdc_acm )编写,可以通过USB接口虚拟串口上报数据,便于在上位机上把数据绘制成曲线。

1、INA226驱动代码

在这里向大家推荐一个app,『半导小芯』,可以很方便的查看芯片资料,同类替换,国产化型号等。INA226是具有警报功能的 36V、16 位、超精密 12C 输出电流/电压/功率监控器,IN+/IN-可以通过接不同阻值的采样电阻来采样不同档位的电流,VBUS直接接IN-测量电压为36v,如果加上分压电路可扩展采样电压范围,所以是一个非常适合数控表头的芯片。

图4 INA226模块

我买的模块,将A0/A1都焊接到VCC,此时模块地址为:

地址为 100 0101,即0x45,代码中按这个地址访问I2C即可。

图5  INA226典型电路

和GD32的连接引脚为PB7/PB8,PB7为I2C0_SDA,PB8为I2C0_SCL,并通过2K电阻上拉到3.3V。

软件代码参考https://www.cnblogs.com/zjx12…
.h文件

#define     I2C0_IN226_ADDRESS7         0x45  //A0;A1 -> VCC


#define     CFG_REG             0x00        //
#define     SV_REG              0x01        //分流电压, 此处分流电阻为 0.1欧
#define     BV_REG              0x02        //总线电压
#define     PWR_REG             0x03        //电源功率
#define     CUR_REG             0x04        //电流
#define     CAL_REG             0x05        //校准,设定满量程范围以及电流和功率测数的 
//#define     ONFF_REG          0x06        //屏蔽 使能 警报配置和转换准备就绪
//#define     AL_REG            0x07        //包含与所选警报功能相比较的限定值
#define     INA226_GET_ADDR     0XFF        //包含唯一的芯片标识号

#define     CFG_RESET          (3<<14)

//===使能过压
#define SHUNT_OVER_VOLTAGE_ENABLE                       (1<<15)
//===不使能过压
#define SHUNT_OVER_VOLTAGE_DISABLE                     ~(1<<15)
    //===使能欠压
#define SHUNT_UNDER_VOLTAGE_ENABLE                      (1<<14)
//===不使能欠压
#define SHUNT_UNDER_VOLTAGE_DISABLE                    ~(1<<14)

//===使能过压
#define BUS_OVER_VOLTAGE_ENABLE                         (1<<13)
//===不使能过压
#define BUS_OVER_VOLTAGE_DISABLE                       ~(1<<13)
//===使能欠压
#define BUS_UNDER_VOLTAGE_ENABLE                        (1<<12)
//===不使能欠压
#define BUS_UNDER_VOLTAGE_DISABLE                      ~(1<<12)

//===使能功率阈值
#define POWER_OVER_LIMIT_ENABLE                         (1<<11)
//===不使能功率阈值
#define POWER_OVER_LIMIT_DISABLE                        (1<<11)

//===使能转换准备好标志
#define CONVERSION_READY_ENABLE                         (1<<10)
//===不使能转换准备好标志
#define CONVERSION_READY_DISABLE                        (1<<10)

//===报警端口低有效
#define    ALERT_POLARITY_ACTIVE_LOW                    ~(1<<1)
//===报警端口搞有效
#define    ALERT_POLARITY_ACTIVE_HIGH                    (1<<1)

//===报警端口锁存使能
#define     ALERT_LATCH_ENABLE                            (1)
//===报警端口锁存不使能
#define     ALERT_LATCH_DISABLE                           0xFFFE

//---------------------------------------  Configuration Register (00h) (Read/Write)
//===配置平均转换的次数
#define    AVG_MODE_MASK                        ~(7<<9)
#define    AVG_MODE_1                            (0<<9)
#define    AVG_MODE_4                            (1<<9)
#define    AVG_MODE_16                           (2<<9)
#define    AVG_MODE_64                           (3<<9)
#define    AVG_MODE_128                          (4<<9)
#define    AVG_MODE_256                          (5<<9)
#define    AVG_MODE_512                          (6<<9)
#define    AVG_MODE_1024                         (7<<9)

//===配置总线电压转换的时间
#define     BUS_VOLTAGE_CONVERSIOM_TIME_MASK             ~(7<<6)
#define     BUS_VOLTAGE_CONVERSIOM_TIME_140_US            (0<<6)
#define     BUS_VOLTAGE_CONVERSIOM_TIME_204_US            (1<<6)
#define     BUS_VOLTAGE_CONVERSIOM_TIME_332_US            (2<<6)
#define     BUS_VOLTAGE_CONVERSIOM_TIME_588_US            (3<<6)
#define     BUS_VOLTAGE_CONVERSIOM_TIME_1100_US           (4<<6)
#define     BUS_VOLTAGE_CONVERSIOM_TIME_2116_US           (5<<6)
#define     BUS_VOLTAGE_CONVERSIOM_TIME_4156_US           (6<<6)
#define     BUS_VOLTAGE_CONVERSIOM_TIME_8244_US           (7<<6)

//===配置采样电压转换的时间
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_MASK            ~(7<<3)
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_140_US           (0<<3)
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_204_US           (1<<3)
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_332_US           (2<<3)
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_588_US           (3<<3)
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_1100_US          (4<<3)
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_2116_US          (5<<3)
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_4156_US          (6<<3)
#define     SHUNT_VOLTAGE_CONVERSIOM_TIME_8244_US          (7<<3)

//===配置操作模式
#define     OPERATING_MODE_MASK                           ~(7<<0)
#define     OPERATING_MODE_POWER_DOWN_1                    (0<<0)
#define     OPERATING_MODE_SHUNT_VOLTAGE_TRIG              (1<<0)
#define     OPERATING_MODE_BUS_VOLTAGE_TRIG                (2<<0)
#define     OPERATING_MODE_SHUNT_BUS_VOLTAGE_TRIG          (3<<0)
#define     OPERATING_MODE_POWER_DOWN_2                    (4<<0)
#define     OPERATING_MODE_SHUNT_VOLTAGE_CONT              (5<<0)
#define     OPERATING_MODE_BUS_VOLTAGE_CONT                (6<<0)
#define     OPERATING_MODE_SHUNT_BUS_VOLTAGE_CONT          (7<<0)
//----------------------------------------------------------------------------



//===总线电压量程每BIT对应的电压值,单位是毫伏
#define INA226_RANG_BUS_VOLTAGE_MV_BIT                  1.25f
//===采样电阻上电压量程每BIT对应的电压值,单位是微伏
#define INA226_RANG_SHUNT_VOLTAGE_UV_BIT                2.5f
//===采样电阻的大小,单位是毫欧
#define INA226_SAMPLE_RES_MR                            1
//===INA226的电流最大采集量程,这个和校准寄存器有关
#define INA226_RANG_CURRENT_MA_MAX                      15000
//===INA226的电流量程每BIT对应电流值,单位是微安安
#define INA226_RANG_CURRENT_UA_BIT_X1                   (UINT16_T)( INA226_RANG_CURRENT_MA_MAX*1000/(1<<15))
//===校准寄存器的值
#define INA226_CALIB_REG_DEFAULT_X1                     (UINT16_T)( 5120*1000/(INA226_RANG_CURRENT_UA_BIT_X1*INA226_SAMPLE_RES_MR) )
//===
#define INA226_RANG_CURRENT_UA_BIT_X2                   (UINT16_T)( INA226_RANG_CURRENT_UA_BIT_X1*2 )
//===
#define INA226_CALIB_REG_DEFAULT_X2                     (UINT16_T)( INA226_CALIB_REG_DEFAULT_X1*2 )

//===结构体定义
typedef struct _INA226_HandlerType                      INA226_HandlerType;
//===指针结构体定义
typedef struct _INA226_HandlerType                    * pINA226_HandlerType;


//IO操作
#define     I2C_SCK                        GPIO_PIN_8 //PC13
#define     I2C_SDA                        GPIO_PIN_7 //PC2 //RES

#define IIC_SCL_H       gpio_bit_set(  GPIOB, I2C_SCK) //SCL H
#define IIC_SCL_L       gpio_bit_reset(GPIOB, I2C_SCK) //SCL H

#define IIC_SDA_H       gpio_bit_set(  GPIOB, I2C_SDA) //SDA_H
#define IIC_SDA_L       gpio_bit_reset(GPIOB, I2C_SDA) //SDA_L

#define READ_SDA        gpio_input_bit_get(GPIOB, I2C_SDA)  //输入SDA
#define SDA_READ        gpio_mode_set( GPIOB, GPIO_MODE_INPUT,  GPIO_PUPD_NONE, I2C_SDA );
#define SDA_WRITE       gpio_mode_set( GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, I2C_SDA );




void        INA226_Init(void);

void        INA226_SendData(uint8_t addr,uint8_t reg,uint16_t data);
uint16_t     INA226_ReadData(uint8_t addr);

void        INA226_SetRegPointer(uint8_t addr,uint8_t reg);
long        INA226_GetShunt_Current(uint8_t addr);
uint16_t    INA226_Get_ID(uint8_t addr);
uint16_t    INA226_GET_CAL_REG(uint8_t addr);
uint32_t    INA226_GetBusVoltage(uint8_t addr);
uint32_t    INA226_GetShuntVoltage(uint8_t addr);
uint16_t    INA226_Get_Power(uint8_t addr);
void        Get_Shunt_voltage(float *Voltage);
void        Get_Shunt_Current(float *Current);

.c文件

void INA226_IIC_Init(void)
{
    rcu_periph_clock_enable(RCU_GPIOB);
    
    /* set SPI0_CS as GPIO*/
    gpio_mode_set(          GPIOB, GPIO_MODE_OUTPUT,    GPIO_PUPD_PULLUP,   I2C_SCK | I2C_SDA );
    gpio_output_options_set(GPIOB, GPIO_OTYPE_PP,       GPIO_OSPEED_50MHZ,  I2C_SCK | I2C_SDA );
        
    IIC_SDA_H;
    IIC_SCL_H;  
    
    delay_nms(5);
}
  
void INA226_Init(void)
{
    INA226_IIC_Init();
    delay_nms(10);
//    INA226_SendData(INA226_ADDR1, CFG_REG, 0xC000);

//    HAL_Delay(10);
    //uint16_t cfg_reg_value = 0x4000|(3<<9)|(4<<6)|(4<<3)|7;
//    INA226_SendData(INA226_ADDR1, CFG_REG, 0x484f);
//    INA226_SendData(INA226_ADDR1, CAL_REG, 0x0012);
    INA226_SendData(INA226_ADDR1, CFG_REG, 
         AVG_MODE_64 | BUS_VOLTAGE_CONVERSIOM_TIME_2116_US | OPERATING_MODE_SHUNT_BUS_VOLTAGE_CONT );
    INA226_SendData(INA226_ADDR1, CAL_REG, 0x800);//A00
}

void INA226_SendData(uint8_t addr, uint8_t reg, uint16_t data)
{
    uint8_t temp = 0;
    INA226_IIC_Start();
    INA226_IIC_Send_Byte(addr);
    INA226_IIC_Wait_Ack();
 
    INA226_IIC_Send_Byte(reg);
    INA226_IIC_Wait_Ack();
    
    temp = (uint8_t)(data>>8);
    INA226_IIC_Send_Byte(temp);
    INA226_IIC_Wait_Ack();
 
    temp = (uint8_t)(data&0x00FF);
    INA226_IIC_Send_Byte(temp);
    INA226_IIC_Wait_Ack();
    
    INA226_IIC_Stop();
}


void INA226_SetRegPointer(uint8_t addr,uint8_t reg)
{
    INA226_IIC_Start();
 
    INA226_IIC_Send_Byte(addr);
    INA226_IIC_Wait_Ack();
 
    INA226_IIC_Send_Byte(reg);
    INA226_IIC_Wait_Ack();
 
    INA226_IIC_Stop();
}


uint16_t INA226_ReadData(uint8_t addr)
{
    uint16_t temp=0;
    INA226_IIC_Start();
 
    INA226_IIC_Send_Byte(addr+1);
    INA226_IIC_Wait_Ack();
    
    temp = INA226_IIC_Read_Byte(1);
    temp<<=8;    
    temp |= INA226_IIC_Read_Byte(0);
    
    INA226_IIC_Stop();
    return temp;
}





//-------------------------------------------------------------
//获取电流
long INA226_GetShunt_Current(uint8_t addr)
{
    long temp=0;    
    INA226_SetRegPointer(addr, CUR_REG);
    temp = INA226_ReadData(addr);
    if(temp&0x8000)    
    {
        temp = ~(temp - 1);    
        temp = (uint16_t)temp - 2 * (uint16_t)temp;
    }
    return temp;
}
 
//获取 id
uint16_t  INA226_Get_ID(uint8_t addr)
{
    uint32_t temp=0;
    INA226_SetRegPointer(addr, INA226_GET_ADDR);
    temp = INA226_ReadData(addr);
    return (uint16_t)temp;
}
 
//获取、设置 校准值  Calibration Register (05h) (Read/Write
//
uint16_t INA226_GET_CAL_REG(uint8_t addr)
{    
    uint32_t temp=0;
    INA226_SetRegPointer(addr, CAL_REG);
    temp = INA226_ReadData(addr);
    return (uint16_t)temp;
}
 
//1.25mV/bit
//Full-scale range = 40.96 V (decimal = 7FFF); LSB = 1.25 mV.
uint32_t INA226_GetBusVoltage(uint8_t addr)
{
    uint32_t temp=0;
    INA226_SetRegPointer(addr, BV_REG);
    temp = INA226_ReadData(addr);
    return (uint32_t)temp;
}
 
 
 
//2.5uV/bit,感觉这个值是测不准的,所以下面改成2.2了
uint32_t INA226_GetShuntVoltage(uint8_t addr)
{
    uint32_t temp=0;
    INA226_SetRegPointer(addr, SV_REG);
    temp = INA226_ReadData(addr);
    if(temp&0x8000)    //负数判断
        temp = ~(temp - 1);    
    return (uint32_t)temp;    
}
 
 
uint16_t INA226_Get_Power(uint8_t addr)
{
    int16_t temp=0;
    INA226_SetRegPointer(addr, PWR_REG);
    temp = INA226_ReadData(addr);
    return (uint16_t)temp;
}
 
 
void GetVoltage(float *Voltage)//mV
{
    Voltage[0] = INA226_GetBusVoltage(INA226_ADDR1)*INA226_RANG_BUS_VOLTAGE_MV_BIT;
}
 
 
void Get_Shunt_voltage(float *Voltage)//uV
{
    Voltage[0] = (INA226_GetShuntVoltage(INA226_ADDR1)*INA226_RANG_SHUNT_VOLTAGE_UV_BIT);//这里原来乘的系数是2.5
}
 
 
void Get_Shunt_Current(float *Current)//mA
{
    Current[0] = (INA226_GetShunt_Current(INA226_ADDR1)* 2.5f);
}
 
 
void GetPower()//W
{
    GetVoltage(&INA226_data.voltageVal);                //mV
    Get_Shunt_voltage(&INA226_data.Shunt_voltage);      //uV
    Get_Shunt_Current(&INA226_data.Shunt_Current);      //mA
    
    INA226_data.powerVal=INA226_data.voltageVal*0.001f * INA226_data.Shunt_Current*0.001f;
}

2、AD8403 4路10K,256级数字电位器

图6 AD8403ARU引脚图

和GD32相连的引脚为:CS:PB12;CLK:PB13;SDI:PB15;SDO:PB14;SHDN上拉;RS上拉或接GD32的RESET引脚。

/***********************************************************************************
函 数 名: Write_AD8403
功    能:
参    数:channal - AD8403通道
          send_data - 写入值
返    回: 无
**********************************************************************************/
void Write_AD8403( uint8 channal, uint8 send_data)    
{   
  SPI_cs_low(); 
  
    BSP_SPI_Write(channal);
    BSP_SPI_Write(send_data);
 
    SPI_cs_high();
}

3、反馈调节

采集端:INA226,构成电流、电压采样输入;
控制端:4路数字电位器驱动MOS管;
以其中一路来说,控制环路如下:

图7 反馈环路示意图

这里电压电流不会跳变,所以采样频率不用太快,保证电流电压不会过调即可。我是用的一个100ms的定时器处理,数据上报也是在此处理的。
PID三个系数,一般简单系统使用PI即可,P即比例系数,控制每次变化量的大小,I是积分系数,修正稳态误差。此处P不能太大,太大容易引起振荡,在此就是电流忽大忽小,我们能容忍电流缓升,但是不敢过流(过流会引起电池过热,甚至爆炸),基于这些分析,暂时设置参数为: P= 1;I=0.1; (此处后期还要慢慢优化,找最合适的系数)
代码为:

.h文件

#define   MAX_AD8403    255

//模式枚舉
enum PID_MODE
{
    PID_POSITION = 0,
    PID_DELTA
};

typedef struct
{
    //PID運算模式
    uint8_t mode;
    //PID 三個基本參數
    float Kp;
    float Ki;
    float Kd;
    float max_out; //PID最大輸出
    float max_iout; //PID最大積分輸出
    float set; //PID目標值
    float fdb; //PID當前值
    float out; //三項疊加輸出
    float Pout; //比例項輸出
    float Iout; //積分項輸出
    float Dout; //微分項輸出
    //微分項最近三個值 0最新 1上一次 2上上次
    float Dbuf[3];
    //誤差項最近三個值 0最新 1上一次 2上上次
    float error[3];
} pid_type_def;


extern  float         g_setTemp;
extern  pid_type_def  stPID;
extern  float         kPID[3] ;
extern  int           g_pwm_val;


void  PID_init(pid_type_def *pid, uint8_t mode, const float PID[3], float max_out, float max_iout);
float PID_calc(pid_type_def *pid, float ref, float set);
void  PID_clear(pid_type_def *pid);
float LimitMax(float input, float max);


.c文件
void Timer_config(void)
{
    timer_parameter_struct timer_initpara;

    rcu_periph_clock_enable(RCU_TIMER1);
    
    
    rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
    timer_deinit(TIMER1);

    //当 prescaler = 9999; period = 16799;  CPU频率168MHz,每次定时器中断1s(10000*16800 = 168,000,000)
    //当 prescaler = 99; period = 1679;  CPU频率168MHz,每次定时器中断1ms(1000*1680 = 168,000)
    /* TIMER1 configuration */
    timer_initpara.prescaler         = 999;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 167999;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER1, &timer_initpara);


    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER1);
    /* clear channel 0 interrupt bit */
    timer_interrupt_flag_clear(TIMER1, TIMER_INT_CH3);
    /* channel 0 interrupt enable */
    timer_interrupt_enable(TIMER1,TIMER_INT_CH3);
    
    /* auto-reload preload enable */
    timer_enable(TIMER1);
    
    nvic_irq_enable(TIMER1_IRQn, 1, 1);
}

void TIMER1_IRQHandler(void)
{
    float   current1= 0; 
     float   pidRes1 = 0;
     static  int  Res_val=0;
  
    //每个定时器中断 10ms
    if(SET == timer_interrupt_flag_get( TIMER1,TIMER_INT_CH3)){
        /* clear channel 0 interrupt bit */
        timer_interrupt_flag_clear( TIMER1,TIMER_INT_CH3);
            
    Get_Shunt_Current( &current1 );      
    pidRes1 = PID_calc(  &stPID, temperature, g_setTemp );
    
    Res_val = pidRes1 ;
    
    if( Res_val> MAX_AD8403)  Res_val= MAX_AD8403;
    if( Res_val< 0 )  Res_val= 0;
    
Write_AD8403( Ch1, Res_val );

    Usb_printf("curr:%0.1f,Res:%d\n", temperature, Res_val );
  }
}

//參數    功能
//    *pid    傳入要初始化的PID結構體指針
//    mode    PID運行的模式,增量式還是位置式PID,此處我們定義一個枚舉變量用於設置模式
//    PID[3]    傳入一個數組,用於作為三個基本參數P、I、D的初始值
//    max_out        PID總輸出的限幅,防止整體輸出過大,傳入一個正數,限制範圍為[-max_out,+max_out]
//    max_iout    積分項輸出的限幅,因為系統剛啟動時與目標誤差較大,累計誤差計算輸出會很大,影響系統穩定性,
//                所以對累計誤差進行限幅,傳入一個正數,限制範圍為[-max_iout,+max_iout]                 
void PID_init(pid_type_def *pid, uint8_t mode, const float PID[3], float max_out, float max_iout)
{
    if (pid == NULL || PID == NULL){
        return;
    }
    pid->mode = mode;
    pid->Kp = PID[0];
    pid->Ki = PID[1];
    pid->Kd = PID[2];
    pid->max_out = max_out;
    pid->max_iout = max_iout;
    pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f;
    pid->error[0] = pid->error[1] = pid->error[2] = pid->Pout = pid->Iout = pid->Dout = pid->out = 0.0f;
}

float LimitMax( float input, float  max) 
{
  if (input > max) { 
    return max; 
  } else if (input < -max){ 
    return -max; 
  }else{
    return input; 
  } 
}


float PID_calc(pid_type_def *pid, float ref, float set)
{
    //判斷傳入的PID指針不為空
    if (pid == NULL)    {
        return 0.0f;
    }
    //存放過去兩次計算的誤差值
    pid->error[2] = pid->error[1];
    pid->error[1] = pid->error[0];
    //設定目標值和當前值到結構體成員
    pid->set = set;
    pid->fdb = ref;
    //計算最新的誤差值
    pid->error[0] = set - ref;
    
    //判斷PID設置的模式
    if (pid->mode == PID_POSITION)
    {
        //位置式PID
        //比例項計算輸出
        pid->Pout = pid->Kp * pid->error[0];
        //積分項計算輸出
        pid->Iout += pid->Ki * pid->error[0];
        //存放過去兩次計算的微分誤差值
        pid->Dbuf[2] = pid->Dbuf[1];
        pid->Dbuf[1] = pid->Dbuf[0];
        //當前誤差的微分用本次誤差减去上一次誤差來計算
        pid->Dbuf[0] = (pid->error[0] - pid->error[1]);
        //微分項輸出
        pid->Dout = pid->Kd * pid->Dbuf[0];
        
        //對積分項進行限幅        
        if(pid->Iout > 0)
          pid->Iout = LimitMax(pid->Iout, pid->max_iout);        
        if(pid->Iout > 0)
          pid->Iout = LimitMax(pid->Iout, pid->max_iout*-1);
        
        //疊加三個輸出到總輸出
        pid->out        =       pid->Pout + pid->Iout + pid->Dout;
        
        //對總輸出進行限幅
        if(pid->out > 0)
          pid->out = LimitMax(pid->out, pid->max_out);
        
        if(pid->out < 0)
          pid->out = LimitMax(pid->out, pid->max_out*-1);
    }    
    else if (pid->mode == PID_DELTA)   {
        //增量式PID
        //以本次誤差與上次誤差的差值作為比例項的輸入帶入計算
        pid->Pout = pid->Kp * (pid->error[0] - pid->error[1]);
        //以本次誤差作為積分項帶入計算
        pid->Iout = pid->Ki * pid->error[0];
        //迭代微分項的數組
        pid->Dbuf[2] = pid->Dbuf[1];
        pid->Dbuf[1] = pid->Dbuf[0];
        //以本次誤差與上次誤差的差值减去上次誤差與上上次誤差的差值作為微分項的輸入帶入計算
        pid->Dbuf[0] = (pid->error[0] - 2.0f * pid->error[1] + pid->error[2]);
        pid->Dout = pid->Kd * pid->Dbuf[0];
        
        //疊加三個項的輸出作為總輸出
        pid->out        +=      pid->Pout + pid->Iout + pid->Dout;
        
        //對總輸出做一個先限幅
        if(pid->out > 0)
          pid->out = LimitMax(pid->out, pid->max_out);
        
        if(pid->out < 0)
          pid->out = LimitMax(pid->out, pid->max_out*-1);
    }
    return pid->out;
}

void PID_clear(pid_type_def *pid)
{
    if (pid == NULL){
        return;
    }
    //當前誤差清零
    pid->error[0] = pid->error[1] = pid->error[2] = 0.0f;
    //微分項清零
    pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f;
    //輸出清零
    pid->out = pid->Pout = pid->Iout = pid->Dout = 0.0f;
    //目標值和當前值清零
    pid->fdb = pid->set = 0.0f;
}



main函数中完成PID初始化

pid_type_def  stPID;
float  kPID[3] = {
  1,
  0.1,
  0.0
};

  PID_init( &stPID, PID_DELTA, kPID, MAX_AD8403, MAX_AD8403);//PID_DELTA

4、数据上报

在定时器中上报电压、电流,1S上报一次。 放上 usb_printf 函数的代码。

//usb虚拟串口,printf 函数
//确保一次发送数据不超USB_USART_REC_LEN字节
void usb_printf(char* fmt,...)  
{      
    uint16_t i,j;
    
    va_list ap;
    va_start(ap,fmt);
    vsprintf((char*)USART_PRINTF_Buffer, fmt, ap);
    va_end(ap);
    i=strlen((const char*)USART_PRINTF_Buffer);//此次发送数据的长度
    
    //发送数据
    usbd_ep_send( &cdc_acm, CDC_DATA_IN_EP, USART_PRINTF_Buffer, i );
}

实物展示

最终实物如下,请不要嫌弃拙劣的焊接技巧,毕竟咱就是个程序猿_

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/186179.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Scala系列之:函数式编程

Scala系列之&#xff1a;函数式编程一、面向对象编程和函数式编程二、函数基本语法三、函数和方法的区别四、函数定义五、函数参数六、函数至简原则七、匿名函数一、面向对象编程和函数式编程 面向对象编程&#xff1a; 解决问题&#xff0c;分解对象&#xff0c;行为&#x…

【最新消息】苹果放出新大招??!!

各位开发者新年快乐&#xff0c;许久没有更新了&#xff0c;近期我收到反馈意思遇到苹果回复的新政策&#xff0c;不知道各位开发者有没有碰到过&#xff0c;我也会在下文提出我的猜测&#xff0c;要是有开发者也遇到了同样的问题&#xff0c;欢迎一起交流哦。 疑似新政策&…

【寒假每日一题】洛谷 P1088 [NOIP2004 普及组] 火星人

题目链接&#xff1a;P1088 [NOIP2004 普及组] 火星人 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述 人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言&#xff0c;但是我们的科学家发明了一种用数字交流的方法。这种交流方法是…

vue-query 初探

vue-query&#xff0c;类似于vuex/pinia&#xff0c;以缓存为目的&#xff0c;但侧重的是对网络请求的缓存。 这是我预想的使用场景&#xff1a;假设在各个页面都需要发起相同的请求&#xff0c;去获取数据&#xff0c;而这种数据在一定时间内不会发生变化&#xff0c;那么这种…

【JavaWeb】带你走进Maven

文章目录1 什么是Maven?2 Maven 常用命令3 Maven 生命周期4 Maven 坐标详解5 IDEA 导入 Maven 项目1 什么是Maven? 如今我们构建一个项目需要用到很多第三方的类库&#xff0c;如写一个使用Spring的Web项目就需要引入大量的jar包。一个项目Jar包的数量之多往往让我们瞠目结舌…

线程execute()与submit()区别

线程池中有两个提交任务的方法 向线程池提交任务的两种方式大致如下&#xff1a; 方式一&#xff1a;调用execute()方法 方式二&#xff1a;调用submit()方法 一、区别 以上的submit()和execute()两类方法的区别在哪里呢&#xff1f;大致有以下三点&#xff1a; 1.二者所接收…

引入“ 自动化测试 ”都需要满足哪些条件?

&#x1f4cc; 博客主页&#xff1a; 自动化软件测试 &#x1f4cc; 专注于软件测试领域相关技术实践和思考&#xff0c;持续分享自动化软件测试开发干货知识&#xff01; &#x1f4cc; 如果你也想学习软件测试&#xff0c;文末卡片有我的交流群&#xff0c;加入我们&#xff…

由浅入深,聊聊 LeakCanary 的那些事

引言 关于内存泄漏&#xff0c;Android 开发的小伙伴应该都再熟悉不过了&#xff0c;比如最常见的静态类间接持有了某个 Activity 对象&#xff0c;又比如某个组件库的订阅在页面销毁时没有及时清理等等&#xff0c;这些情况下多数时都会造成内存泄漏&#xff0c;从而对我们Ap…

linux内核-内存管理

linux内核内存管理 注意&#xff01;内核空间和用户空间都是处于虚拟空间中 Linux的虚拟地址空间范围为0&#xff5e;4G&#xff0c;Linux内核将这4G字节的空间分为两部分 内核空间&#xff1a; 最高的1G字节&#xff08;从虚拟地址0xC0000000到0xFFFFFFFF&#xff09;&…

RTSP,RTP,RTCP协议

一 RTSP 1 简介 实时流传输协议&#xff0c;是一个应用层协议&#xff08;TCP/IP网络体系中&#xff09;&#xff0c;它是一个多媒体播放控制协议&#xff0c;主要用来使用户在播放流媒体时可以像操作本地的影碟机一样进行控制&#xff0c;即可以对流媒体进行暂停/继续、后退和…

SAP FICO 关于资产的详细解析

SAP资产模块概述 一、概述 资产&#xff08;AA&#xff09;模块是资产会计模块的简称&#xff0c;是财务会计&#xff08;FI&#xff09;模块的一个子模块&#xff0c;主要处理与各类长期资产相关业务的模块。不单指固定资产&#xff0c;也不泛指资产负债表中的资产&#xff0c…

Week4

1.试题 历届真题 时间显示【第十二届】【省赛】【B组】 思路 不难发现,应该从小时往秒处理,这样可以用O(1)的时间复杂度求出,不过有比较麻烦的进位处理。 先看里面可以拼成几个小时,然后得到的小时%24,然后把总时间减去小时的时间,再看有多少分钟,分钟%60,都是此时判断分…

vue多环境配置之 .env配置文件

Vue之.env环境配置文件 .env文件是运行项目时的环境配置文件。但是在实际开发过程中&#xff0c;有本地环境、测试环境、预生产、生产环境等等&#xff0c;不同环境对应的配置会不一样。因此&#xff0c;需要通过不同的.env文件实现差异化配置。 * 文章目录Vue之.env环境配置文…

【JAVA核心知识】46:什么是零拷贝Zero-copy

零拷贝相较于传统的IO流程拥有更高的数据发送效率&#xff0c;无论是RocketMq,Kafka还是Netty等都用到了零拷贝技术&#xff0c;那究竟什么是零拷贝呢&#xff0c;零拷贝又是通过什么方式提升数据发送效率呢&#xff1f; 首先我们要明白&#xff0c;一次数据发送过程就是将磁盘…

Java基础--方法

前言&#xff1a;介绍 Java 中方法的基本语法、分类、执行并分析参数传值。 关键字&#xff1a;方法、形参、实参、返回值、实例方法、静态方法、参数传值 程序引例–为什么要「方法」 public class IntroduceOfMethod {// 入口主方法。public static void main(String[] args…

docker 高级篇

一、DockerFile 1.1、概述 dockerfile 是用来构建docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 为什么要有dockerfile呢 在基础篇我们讲过&#xff0c;比如我们下载个 ubuntu镜像里面不包含 vim、ifconfig等组件&#xff0c;这个时候 新增…

DES加密算法

DES算法原理 对称密码算法中的分组加密算法&#xff08;对应于流密码&#xff09; 密钥64位&#xff0c;56位参与运算8位校验位&#xff08;校验位为&#xff1a;8、16、24、32、40、48、56、64&#xff09; 加密原理 1. IP置换 将明文数据转化为二进制数&#xff0c;并将它…

Lnix文件权限的修改

首先我们要清楚Linux文件的权限信息 在Linux中输入ls -l 或者 ll查看文件和目录的详细信息 文件详情实例中&#xff0c;a目录的第一个属性用“d”标识这个a是一个目录。 anaconda-ks.cfg第一个属性用“-”标识他是一个文件。 在Linux文件详情的后面属性需要分为三组查看 rwx&am…

三种调用机制: 同步调用、异步调用、回调(同步/异步)

c并发编程-01-并发与并行_发如雪-ty的博客-CSDN博客 c并发编程02-什么是I/O_发如雪-ty的博客-CSDN博客 c并发编程03-I/O多路复用_发如雪-ty的博客-CSDN博客 c并发编程04-同步与异步_发如雪-ty的博客-CSDN博客_c同步和异步 c并发编程05-什么是回调函数_发如雪-ty的博客-CSDN…

Web前端:使用ReactJS构建的应用类型

使用ReactJS&#xff0c;你可以构建各种各样的应用程序&#xff0c;包括单页应用程序、渐进式web应用程序、移动应用程序、仪表板、电子商务平台、企业web应用程序以及社交媒体和消息应用程序。1.单页应用程序(spa)单页应用程序(SPA)基本上是一个网页&#xff0c;它通过使用从w…