จุ๊บ冰语 · 2022年12月27日 · 陕西

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

前言

本次有幸参与并通过了极术社区组织的【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碱性电池充电电路;
2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器2851.png

                    图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管实现。
20221228155422.jpg

                    图2 仿真充电电路可行性

2、MOS管实测

实际MOS管选用的FSP740,实测GS电压3.21v开始导通,4.14v可通过500mA电流,MOS管只是轻微发热,如图
2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器3196.png2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器3198.png

        图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,如果加上分压电路可扩展采样电压范围,所以是一个非常适合数控表头的芯片。
2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器4395.png2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器4396.png

                    图4 INA226模块         

我买的模块,将A0/A1都焊接到VCC,此时模块地址为:
2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器4439.png
地址为 100 0101,即0x45,代码中按这个地址访问I2C即可。
2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器4606.png

                        图5  INA226典型电路

和GD32的连接引脚为PB7/PB8,PB7为I2C0_SDA,PB8为I2C0_SCL,并通过2K电阻上拉到3.3V。
2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器4685.png
软件代码参考 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级数字电位器

2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器17106.png

            图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管;
以其中一路来说,控制环路如下:
2_【GD32F427开发板试用】基于GD32F427的数控表头数控电池充电器17672.png

                    图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 );
}

实物展示

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

微信图片_20221227172155.jpg微信图片_20221227172222.jpg

推荐阅读
关注数
10693
内容数
187
中国高性能通用微控制器领域的领跑者兆易创新GD系列芯片技术专栏。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息