目录
一、项目要求
二、方案设计
三、原理图
四、硬件电路介绍
五、实物照片
六、程序
七、资料清单
资料下载地址:基于STM32的数控精密恒流源设计
(1)恒流源输入电压10~28V。
(2)输出电流可在50mA~3000mA范围内任意设定。
(3)按键数控步进10mA。
(4)数控恒流精度±10mA。
本设计首先是在硬件上实现恒流,方法是先通过采样电阻采集电压值,由于采样电阻较小,需要先通过运放的比例放大电路放大采样的电压,然后和基准电压进行加法比例积分电路后控制改变场效应管的内阻,从而改变电路的电流,实现闭环的PI调节,达到硬件恒流。在实现硬件恒流的基础上,将采样电阻采集的电压经过比例放大后,输出给AD转换器,将模拟电压转换为数字信号给控制器,控制器将采集到的电压值通过计算转化为电流值和预设的电流值进行做差,作为偏差变量,将偏差变量乘以KP,再将连续两次的偏差变量做差乘以KD,然后将这两个结果相加得到的值传给DA转换器作为输出,输出的这个电压值就是硬件恒流模块的基准电压,这就是软件PD恒流调节。
系统工作原理框图如图所示。系统由多个模块组成,控制器模块、按键模块、液晶显示模块、硬件恒流模块。控制器模块主要的功能是实现数据采集分析、处理及对其他模块的控制。按键模块的功能是设定预设电流值的人机接口。液晶显示模块的功能是显示工作状态和预设电流值的人机接口。硬件恒流模块包括比例放大、加法比例积分、场效应管和采样电阻。
主控板原理图
主控板PCB图
电源板原理图
电源板PCB图
本设计是大功率恒流源,所以选用常用的功率场效应管IRF540。又因为本设计的恒流源是使场效应管工作在线性放大区,场效应管上的压降很大,发热很严重,容易导致场效应管损坏。为了保护场效应管,本设计将三个IRF540并联设计,这样便能将电流分担到三个部分,从而降低单个场效应管的发热。由于每个场效应管的物理特性稍有差异,其线性工作状态略有差异,需要在每个场效应管前串联一个平衡电阻。如果串联的电阻的未使用得当,会使得场效应管的电流不平衡,损坏它。因此,将在每个场效应管前串联一个阻值较小的电阻,目的是尽可能让电流均匀流入每个场效应管,起到平衡并联的场效应管的作用。经过测试选用0.5欧姆的金属膜电阻较为稳定,由于该大功率恒流源要过大电流,所以该电阻要选用功率较大的电阻,本设计选用2W的金属膜电阻,因为本设计最大输出电流3A,理论平均每个电阻通过的最大电流为1A,选用2W的电阻,完全符合要求。如图所示3.9,是场效应管并联电路,Uin是电源箱输出串联负载后连接的接口,Uout是接采样电阻后接电源箱地的接口,Control是控制场效应管工作在线性放大区的控制口。该电路中的R15的作用是当场效应管关断时,寄生电容需要放电,就是通过该电阻进行放电的。
2、比例放大电路设计
本设计中比例放大电路的运放选用OP07,封装选用的是SOP-8。OP07是一款单运放,需要±12V为其供电。在本设计中比例放大电路如图所示。在整体电路中将采样电阻采集到的电压经过图中IN接口输入,然后就可以将该电压进行放大。图3.10中 R22是个平衡电阻,其目的是减少输入的静态电流,抑制零点漂移。该电阻的阻值的关系式如式3.2所示。
3、加法电路设计
本设计将电路中采集回来的电压值通过UinB输入,与控制器DA输出的电压值进行加法计算。UinB在前级进过反相,所以是负压,DA输入是个正压,这两个电压值相加后会得到个电压偏差值,这里称它位误差,该误差作为硬件比例积分的一个重要变量,硬件的比例积分就是通过不断的修正这个误差实现恒流的。
4、比例积分电路设计
误差通过比例积分运算输出给后级,经过放大控制场效应管工作在线性放大区。比例调节的好处在于调节时间短,稳定做好,可以在最快的时间达到平衡点,但是比例调节存在残差,如果在比例调节的基础上加入积分调节,可以很好的修正比例调节不能消除的静态误差,这样比例调节和积分调节一起作用,便能达到很精准的控制。
随着预设电流的增加,电流的偏差值在1100mA~1500mA时的偏差值为零。随着电流的增加,电流就出现了10mA的偏差,这个偏差估计是由于MOS管发热过大造成的温漂。
设定电流1300mA,输入电压28V,每次步进10mA测得步进电流数据
main.c
#include "stm32f10x.h"
#include "stm32f10x_conf.h"
#include "OLED.h"
#include "delay.h"
#include
/#include "codetab.h"
//OLED_CLS();
unsigned int TimingDelay ;
void Delayms(__IO uint32_t nTime);
void GPIOkeyInit(void);
void Choosevalue(void);
void Adc_Init(void);
u16 Get_Adc(u8 ch);
void PWMInit();
void PWM(int CCR2_Val);
void CurrentPD(float error,float KP,float KD);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
int current;
int CCR2_Val=0;
u16 ADValue;
float ActualC,lastA;
float piancha;
float current_last, current_now;
int main(void)
{
unsigned char str[10];
unsigned char sign=0;
SystemInit();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_GPIOB |RCC_APB2Periph_GPIOC |RCC_APB2Periph_GPIOD |RCC_APB2Periph_GPIOE, ENABLE);
OLED_Init();
GPIOkeyInit();
Adc_Init();
PWMInit();
SysTick_Config(72000);
while(1)
{
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_14)==0)
{
while(!GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_14));
while(1)
{
OLED_8x16Str(0,2,"Work");
ADValue=Get_Adc(1);
ActualC=1.321*ADValue;
sprintf(str,"AC=%d mA",ActualC);
OLED_8x16Str(0,4,str);
piancha=current-ActualC;
switch(current/100)
{
case 0: CurrentPD(piancha,0.160,0);break;
case 1: CurrentPD(piancha,0.160,0);break;
case 2: CurrentPD(piancha,0.160,0);break;
case 3: CurrentPD(piancha,0.160,0);break;
case 4: CurrentPD(piancha,0.160,0);break;
case 5: CurrentPD(piancha,0.160,0);break;
case 6: CurrentPD(piancha,0.160,0);break;
case 7: CurrentPD(piancha,0.158,0);break;
case 8: CurrentPD(piancha,0.157,0);break;
case 9: CurrentPD(piancha,0.157,0);break;
case 10: CurrentPD(piancha,0.156,0);break; //1000mA
case 11: CurrentPD(piancha,0.156,0);break;
case 12: CurrentPD(piancha,0.156,0);break;
case 13: CurrentPD(piancha,0.155,0);break;
case 14: CurrentPD(piancha,0.155,0);break;
case 15: CurrentPD(piancha,0.155,0);break;
case 16: CurrentPD(piancha,0.155,0);break;
case 17: CurrentPD(piancha,0.154,0);break;
case 18: CurrentPD(piancha,0.154,0);break;
case 19: CurrentPD(piancha,0.154,0);break;
case 20: CurrentPD(piancha,0.155,0);break; //2000mA
case 21: CurrentPD(piancha,0.155,0);break;
case 22: CurrentPD(piancha,0.155,0);break;
case 23: CurrentPD(piancha,0.154,0);break;
case 24: CurrentPD(piancha,0.154,0);break;
case 25: CurrentPD(piancha,0.154,0);break;
case 26: CurrentPD(piancha,0.154,0);break;
case 27: CurrentPD(piancha,0.154,0);break;
case 28: CurrentPD(piancha,0.154,0);break;
case 29: CurrentPD(piancha,0.154,0);break;
case 30: CurrentPD(piancha,0.154,0);break;
}
sign=0;
}
}else
{
if(sign==0)
{
OLED_CLS();
sign=1;
}
PWM(0);
Choosevalue();
sprintf(str,"Current=%d mA",current);
OLED_8x16Str(0,0,str);
}
}
}
/****************************************************************************
* 名 称:void Delayms(__IO uint32_t nTime)
* 功 能:定时延时程序 1ms为单位
* 入口参数:无
* 出口参数:无
* 说 明:
* 调用方法:无
****************************************************************************/
void Delayms(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
void GPIOkeyInit()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;//速度50MHz
GPIO_Init(GPIOA ,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_14;
GPIO_Init(GPIOE ,&GPIO_InitStructure);
// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
// GPIO_Init(GPIOB ,&GPIO_InitStructure);
GPIO_SetBits(GPIOC, GPIO_Pin_13);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void Choosevalue()
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)
{
current++;
if(current==4000)
{
current=0;
}
}
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0)
{
while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7));
// GPIO_ResetBits(GPIOC, GPIO_Pin_13);
current++;
if(current==4000)
{
current=0;
}
}
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_7)==0)
{
current--;
if(current<=0)
{
current=0;
}
}
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_8)==0)
{
while(!GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_8));
current--;
// GPIO_SetBits(GPIOC, GPIO_Pin_13);
if(current<=0)
{
current=0;
}
}
}
void Adc_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE); //使能ADC1通道时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div8); //设置ADC分频因子8 72M/8=9,ADC最大时间不能超过14M
//PA1作为模拟通道输入引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入
GPIO_Init(GPIOA,&GPIO_InitStructure);
ADC_DeInit(ADC1); //复位ADC1,将外设ADC1的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; //ADC独立模式
ADC_InitStructure.ADC_ScanConvMode=DISABLE; //单通道模式
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; //单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel=1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1,&ADC_InitStructure); //根据指定的参数初始化外设ADCx
ADC_Cmd(ADC1,ENABLE); //使能指定的ADC1
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
}
u16 Get_Adc(u8 ch) //ch = ADC_channel_1
{
//设置指定ADC的规则组通道,设置它们的转化顺序和采样时间
ADC_RegularChannelConfig(ADC1,ch,1,ADC_SampleTime_239Cycles5); //通道1 规则采样顺序值为1,采样时间为239.5周期
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //使能指定的ADC1的软件转换功能
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)); //等待转换结束
return ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转化结果
}
void PWMInit()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*-------------------------------------------------------------------
TIM3CLK=72MHz 预分频系数Prescaler=2 经过分频 定时器时钟为24MHz
根据公式 通道输出占空比=TIM3_CCR2/(TIM_Period+1),可以得到TIM_Pulse的计数值
捕获/比较寄存器2 TIM3_CCR2= CCR2_Val
-------------------------------------------------------------------*/
/* Time base configuration */
/* TIM2 clock enable */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period =999; //1KHz
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//TIM2预分频设置:1MHZ,APB1分频系数2,输入到TIM3时钟为36MHzx2 = 72MHz
TIM_PrescalerConfig(TIM2,71, TIM_PSCReloadMode_Immediate);
/* Channel 2 and 3 Configuration in PWM mode */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCIdleState_Reset;
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
//使能TIM2定时计数器
TIM_Cmd(TIM2, ENABLE);
//使能TIM2 PWM输出模式
TIM_CtrlPWMOutputs(TIM2, ENABLE);
}
void PWM(int CCR)
{
Delayms(10);
TIM_OCInitStructure.TIM_Pulse = CCR;
TIM_OC3Init(TIM2, &TIM_OCInitStructure);
TIM_Cmd(TIM2, ENABLE);
TIM_CtrlPWMOutputs(TIM2, ENABLE);
}
void CurrentPD(float error,float KP,float KD)
{
current_last=current_now;
current_now =error;
CCR2_Val=KP*error+KD*(current_now-current_last);
if(CCR2_Val>850)
{
CCR2_Val=850;
}
if(CCR2_Val<=1)
{
CCR2_Val=0;
}
PWM(CCR2_Val);
}