本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:จุ๊บ冰语
本次有幸参与并通过了极术社区组织的【GD32F427开发板试用】活动,让我对国产兆易创新的GD32处理器有了更深刻的认识。
开发板到手后,先从邮箱中提供的链接https://aijishu.com/a/1060000…下载相关资料,当然我们也可以直接访问兆易创新的官网 资料库:https://www.gd32mcu.com/cn/do…去下载关于GD32F4更加详尽的资料。本次开发板主要使用到的资料为(对资料清单进行了稍详细的说明,希望对其他人有帮助):
表1 资料清单
编号 | 文件名 | 说明 |
---|---|---|
1 | GD32F427xx Datasheet | GD32F427系列MCU数据手册,里面可以看到每个引脚对应的复用功能及复用配置的AFx等信息 |
2 | GD32F4xx系列MCU用户手册 | 支持GD32F470/GD32F427/GD32F425,这个是一个1000页的庞大pdf文件,对每个内部资源及外设都有详细介绍,包括对应的寄存器等信息 |
3 | GD32F4xx Firmware Library | GD32F4xx标准固件库。适用于GD32F4xx系列MCU,与Cortex-M微控制器软件接口标准(CMSIS)兼容。固件库包括程序、数据结构和宏定义,覆盖所有集成外设的特征,并包括了全部相关驱动和示例程序。如果在 GD32F4xx_Demo_Suites 目录下未找到合适的例程代码,可在 GD32F4xx_Firmware_Library\Examples 中查找,总有咱能用到的 |
4 | GD32F4xx Firmware Library User Guide | GD32F4xx系列MCU固件库使用指南,大部分的接口都会在这个pdf中说明 |
5 | GD32F4xx系列硬件开发指南 | 专为基于GD32F4xx系列MCU开发者提供,对GD32F4xx系列产品硬件开发做了总体介绍。提供常用外设接口的电路及设计指导 |
6 | GD32F4xx AddOn | 1. 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 以上版本。 |
7 | GD32F4xx_Demo_Suites | GD32F4xx系列开发板套件,支持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。
参考这个电路,我在multisim上也仿真了一个电路,不过我的调流使用的电位器+MOS管实现。
图2 仿真充电电路可行性
实际MOS管选用的FSP740,实测GS电压3.21v开始导通,4.14v可通过500mA电流,MOS管只是轻微发热,如图
图3 MOS管Vgs电压实测: ch1为gs电压电流,ch2是ds电压电流
实际上我们充电时,通过INA226(单路)来采集充电电压电流信息,反馈给gd32,再控制数字电位器调节MOS管Vgs,进而调节电流。够成一个pid反馈环。
设想模块最终可对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接口虚拟串口上报数据,便于在上位机上把数据绘制成曲线。
在这里向大家推荐一个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;
}
图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();
}
采集端: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( ¤t1 );
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
在定时器中上报电压、电流,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 );
}
最终实物如下,请不要嫌弃拙劣的焊接技巧,毕竟咱就是个程序猿_