使用STC8A8K64S4A12单片机实现的“基于脉冲宽度调制(PWM)技术的智能温度控制器”

笔者在校期间曾经自制过一个使用半导体制冷片制冷的小冰箱,一开始采用的是继电器控制,施密特触发器模式的温度控制,但实际使用上有十分多的缺点,因此制作一个使用PWM技术的温度控制器的想法就萌生了。

先来介绍一下笔者的冰箱结构,众所周知,热量不会凭空消失,致冷片的效果只是将冰箱内的热量转移到了空气中而已,因此制冷片的热面需要有十分强大的散热器才能很好地实现致冷的效果。为了达到这个目的,我使用了一个4热管CPU散热器来实现这个功能,通过AM4扣具将其安装在热面上方,冷面则是采用了一大块铝制的散热器。

使用STC8A8K64S4A12单片机实现的“基于脉冲宽度调制(PWM)技术的智能温度控制器”_第1张图片
其实笔者并不推荐这么设计,原因很简单,热面和冷面的距离太近了,影响实际效果,最好是使用CPU一体式水冷式散热器将热量导至别处散掉。但出于预算和空间方面的考虑,笔者还是采用了热管式的散热器。

下面来说说实际使用上的缺点,因为是使用施密特模式的温度控制器,工作模式仅仅只有简单的“开”和“关”两种状态,当温度低于设定温度时,继电器断开,制冷片不再制冷,直到温度高于回差温度时继电器才会吸合,所以在制冷片不制冷的时候,这整个散热器就会通过制冷片将热量吸回冰箱内,逆向散热,实在是有些蠢,所以使用PWM技术就显得十分重要了。

关于PWM技术,笔者这里就不对其进行解释了,如果还不明白的朋友可以自行百度。

本次的温度控制器使用的是来自宏晶(STC)的1T 8051 单片机,自带12位ADC,增强型PWM,以及可编程计数器阵列(PCA/CCP/PWM)

首先作为温度控制器的基本功能就是读取冰箱内的温度,笔者这里采用的是一个B值为395025摄氏度时阻值为10K的负温度系数热敏电阻。

热敏电阻的B值在热敏电阻烧结时就已确定且不可修改,并且通过B值和当前的热敏电阻阻值以及特定温度时的阻值就能计算出当前热敏电阻的温度。通过单片机内部的ADC模块将热敏电阻上的电压转换为数字量,再根据实际电路、以下公式和已知数据即可推得当前温度。
热 敏 电 阻 阻 值 与 温 度 的 关 系 式 : R t = R ∗ E X P ( B ∗ ( 1 / T 1 − 1 / T 2 ) ) 热敏电阻阻值与温度的关系式: Rt = R *EXP(B*(1/T1-1/T2)) Rt=REXP(B(1/T11/T2))
上述公式中,T1和T2所代表的的物理量是温度,单位为K(绝对温标);Rt是热敏电阻在温度为T1时的阻值,单位为欧姆;R为热敏电阻在温度为T2时的阻值,单位为欧姆;EXP表示以自然常数e为底的指数函数,即e的多少次方。

以下为笔者所采用的具体电路方案,header2为接口,外接热敏电阻,AVref为单片机的ADC的输入参考电压,笔者在这里通过TPS76301DBVR芯片将其设置成了2.82V,网络标号ADC-1接至单片机的ADC1脚,即取热敏电阻与上的电压送去ADC转换。

使用STC8A8K64S4A12单片机实现的“基于脉冲宽度调制(PWM)技术的智能温度控制器”_第2张图片

以下为稳压芯片TPS76301DBVR的电路,该芯片的输出电压由图中的电阻R3和R4决定,计算方法请参考datasheet,此处的参数所设定的输出电压是2.82V左右。注意!输出端的电解电容等效串联电阻必须足够大,因此要串联一个1欧姆的电阻。

使用STC8A8K64S4A12单片机实现的“基于脉冲宽度调制(PWM)技术的智能温度控制器”_第3张图片

以下为笔者的ADC部分的C语言代码(示例):

void main(){
	int V_ADC=0;//储存12位的ADC结果
	float Temperature=0//储存实时温度
	
	P1M0=0x00; //ADC1脚为高阻输入模式
	P1M1=0x02;//ADC1脚为高阻输入模式
	
    ADCCFG = 0x20; //设置结果右对齐,ADC速度最快
    ADC_CONTR = 0x81; //使能 ADC 模块
    ADC_CONTR |= 0x40; //启动 AD 转换一次
    
    _nop_();//等待ADC转换完成
    _nop_();//等待ADC转换完成
    _nop_();//等待ADC转换完成
    
    V_ADC = ADC_RESL; //low 8bit(从寄存器中读取ADC结果)
    V_ADC |= (ADC_RES<<8); //high 8bit
    
    Temperature=(1/((log((((2.82/4095)*V_ADC)/((2.82-(2.82/4095)*V_ADC)/10000))/10000))/(3950)+(1)/(298.15)))-273.15;//ADC结果对温度转换(摄氏度)
    
    while(1);
}

这样我们即可获得实时的外界温度,至于代码中的特殊功能寄存器究竟有何用处,是如何工作的,请参考STC的官方pdf中有关ADC的部分。

有了温度,我们就要来看单片机的PWM模块了。

STC单片机内自带15位增强型PWM模块,大致工作原理如下:单片机内部有一个专用的15位PWM计数器,在工作时会不断根据时钟信号计数,直到达到设定值(0~32767可自由设置),产生溢出并归零,继续重新计数,如此往复。在PWM工作时,还会有四个用于比较的8位寄存器T1L、T1H、T2L、T2H,两两组成两个16位的T1和T2,当PWM计数器中的值等于T1中写入的值时,PWM口即输出低电平,当PWM计数器中的值等于T2的值时,即输出高电平。注意!!!:当T1等于T2时,仅T1生效。

并且需要强调的是,T1和T2以及PWM计数器的最大值等等的修改和读取并不能直接操作,而是需要将P_SW2寄存器(0xba)中的最高位置1才能正常操作,详细请参阅头文件中的注释。

STC8A8K64S4A12有8个增强型PWM输出通道,且每路的T1、T2是独立的,需要单独设置,笔者使用的通道为PWM0,脚位与P20复用

以下为笔者的PWM部分的C语言代码(示例):

#define PWM_CYCLE 0x800//设置PWM周期,即15位PWM计数器的重装值。

void main(){
	int PWM_DUTY;//用于修改T1或T2的中间变量,如果直接对T1进行运算操作可能会导致输出中断的问题
	float DUTY_Ratio;//用于计算占空比
	
	PWM_DUTY=0x0000;//PWM0T1计时器填充变量
	
	P_SW2 |= 0x80;//使能xdata功能寄存器读写

	PWMCKS = 0x00; // PWM 时钟为系统时钟
    PWMC = PWM_CYCLE-1; //设置 PWM 周期为,此处-1的原因是当T1或者T2等于PWM计数器中的值时会马上输出对应的值,所以想要占空比为0或者1的话,T1或者T2必须必PWM计数器的最大值大1
    PWM0T1= PWM_DUTY;//跳变到低电平所在的时刻
    PWM0T2=0x0000 ;//跳变到高电平所在的时刻
    PWM0CR= 0xc0; //使能 PWM0 输出,且初始电平为低
    
	P_SW2 = 0x00; 
	
	PWMCR = 0x80; //启动 PWM 模块
	
	Duty_Ratio=((1.0*PWM_DUTY)/(PWM_CYCLE))*100;//占空比为

	while(1){//将PWM占空比步进至1
		if (PWM_DUTY<PWM_CYCLE){
        	PWM_DUTY++;
        	_push_(P_SW2);//暂时储存P_SW2(入栈)
        	P_SW2 |= 0x80;//使能xdata功能寄存器读写
        	PWM0T1=PWM_DUTY;//PWM0输出低电平所在的时刻为
        	_pop_(P_SW2);//恢复P_SW2(出栈)
        	Duty_Ratio=((1.0*PWM_DUTY)/(PWM_CYCLE))*100;//刷新占空比
        }
	}
}

最后再强调,对T1和T2操作需要将P_SW2最高位置1!!!

说完PWM和ADC,我们来讲PCA,为什么要用这玩意?因为你需要隔一段时间就启动一次AD转换,并且比较当前温度和目标温度,再根据当前工作模式(制冷/加热)对PWM占空比进行修改。

在STC8A8K54S4A12单片机中,可编程计数器阵列有多种工作模式,分别可作为【软件定时器】、【外部脉冲捕获】、【高速脉冲输出】、【简单的PWM输出】,在这里我们使用的是【软件定时器】功能。

可编程计数器阵列在【软件定时器】模式下的工作方式和增强型PWM有些类似,有一个专用的16位计数器,会根据时钟信号的输入进行计数,从0到65535,溢出后归零,且最大值只能为65535,不可自行设定。然后也是有另外两个8位寄存器CCAPL、CCAPH组成一个16位的CCAP,当PCA计数器中的值等于CCAP中设定的值时,即触发中断,进入中断服务程序。那么我们只要在中断服务程序中对CCAP进行再装填之后再添加需要的代码即可实现定时进行当前温度的读取,当前温度与目标温度的比较,以及输出占空比的调节。

以下为笔者的PCA部分的C语言代码(示例):

#define Timmer_CYCLE (24000000L / 12 / 40)//0.025sec/Times (Fosc=24MHz)

static unsigned int value; //PCA 装填中介变量
static float  Temperature;//实时温度
int V_ADC=0;//储存12位的ADC结果

void PCA_Isr() interrupt 7{//PCA中断,优先级最高,每0.025秒执行一次(Fosc=24MHz)
    CCF0 = 0;//去中断标志
    CR = 0;//关闭PCA
    CCAP0L = value;//再装填
    CCAP0H = value >> 8;
    value += Timmer_CYCLE;

    V_ADC = ADC_RESL; //low 8bit,读取上次的ADC结果
    V_ADC |= (ADC_RES<<8); //high 8bit

    ADC_CONTR |= 0x40; //启动 AD 转换

    Temperature=(1/((log((((2.82/4095)*V_ADC)/((2.82-(2.82/4095)*V_ADC)/10000))/10000))/(3950)+(1)/(298.15)))-273.15;//ADC结果对温度转换
    
	//占空比修改代码略,请参考上一部分
	
    CR = 1;//开启PCA
}

void main(){
	CCON = 0x00;//复位
    CMOD = 0x00; //PCA 时钟为系统时钟/12
    CL = 0x00;//Low 8 bit of CCAP
    CH = 0x00;//High 8 bit of CCAP
    CCAPM0 = 0x49; //PCA 模块 0 为 16 位定时器模式
    value = Timmer_CYCLE;
    CCAP0L = value;//CCAP初次装填
    CCAP0H = value >> 8;
    value += Timmer_CYCLE;
	IP=0x80;//PPCA set to 1 (PCA中断优先级设置。PCCAH与PCCA两个bit有4种不同的组合,分别代表4种不同的中断优先级,00最低,11最高)
    IPH=0x80;//PPCAH set to 1
    
	EA = 1;//开启总中断
	CR = 1; //启动 PCA 计时器
	
    while(1);
}

讲完PWM、PCA、ADC,其实就差不多了,当然为了追求完美还是要加上屏幕显示和温度、模式调节、掉电记忆等功能,这些东西比较富有个性,我就大概讲一下,不详细讲解决方案了。
笔者使用的是一个128*64的OLED屏幕,使用IIC总线驱动,驱动芯片是SSD1306,在调试的过程中遇到了非常之蛋疼的问题——无法使用硬件IIC总线驱动。一旦使用硬件IIC驱动,就会导致莫名其妙的死机、花屏等问题,弄得我很无语。所以后来我用了模拟IIC总线的方法进行驱动,完美运行,没有任何问题,就是主频得降到6MHz左右。
目标温度调节和工作模式调节功能需要在有屏幕的基础上才能实现,题主使用的是外部中断0,通过按键激活外部中断函数进行相应的调整。

其实如果仅仅是用于半导体制冷片的温度控制器的话可以通过硬件和软件配合实现自动更换电流流向从而达成既能加热又能制冷的功能。

最后放两张我用洞洞板做的样品

哦对了,这屏幕有的时候抽风,有的字就是乱码,有的时候又是好的……

使用STC8A8K64S4A12单片机实现的“基于脉冲宽度调制(PWM)技术的智能温度控制器”_第4张图片
使用STC8A8K64S4A12单片机实现的“基于脉冲宽度调制(PWM)技术的智能温度控制器”_第5张图片

你可能感兴趣的:(硬件,C语言,单片机)