这是我在创新的考核任务,来回顾复习一下整个过程并总结一些错误
设计制作一个程控加热器,能根据预定的温度--时间曲线进行加热,其示意图如下图所示。
基本要求
(1)能显示设定温度和实际工作温度;
(2)可用键盘设定控制温度,温控误差要求小于±2℃;
(3)温度低于30℃时,风扇停止工作,加热器开始加热;当温度高于70℃,应切断加热器,并接通风扇开始散热。
(4)到达预定温度、上下限温度时能声光报警;
(5)自制直流电源
发挥部分
(1)能显示加热功率和风扇转速;
(2)加热功率程控可调;
(3)具有程控加热功能,能按预定的加热曲线进行加热;
(4)温控误差要求小于±1℃
(5)其他。
设计思路
显示:OLED显示屏
温度设定:按键
数据获取:ds18b20,霍尔元器件
数据处理:PID算法
数据输出:PWM1,PWM2
加热:水泥电阻
温控主要流程:用ds18b20获得水泥电阻的实时温度,再对实际温度与设定温度进行比较并进行数据处理,来对风扇和水泥电阻输出PWM实现控制
DS18B20时序
/*******************************************************************************
* 函 数 名 : Ds18b20Init
* 函数功能 : 初始化
* 输 入 : 无
* 输 出 : 初始化成功返回1,失败返回0
*******************************************************************************/
u8 Ds18b20Init()
{
u8 i;
GPIO_DeInit(GPIOA);
GPIO_WriteLow(GPIOA, GPIO_PIN_1);
Delay_us(600);
GPIO_WriteHigh(GPIOA, GPIO_PIN_1);
Delay_us(10); //然后拉高总线,如果DS18B20做出反应会将在15us~60us后总线拉低
GPIO_Init(GPIOA, GPIO_PIN_1, GPIO_MODE_IN_PU_NO_IT);
while(GPIO_ReadInputPin(GPIOA, GPIO_PIN_1)) //等待DS18B20拉低总线
{
Delay_ms(1);
i++;
if(i>5)//等待>5MS
{
return 0;//初始化失败
}
}
return 1;//初始化成功
}
/*******************************************************************************
* 函 数 名 : Ds18b20WriteByte
* 函数功能 : 向18B20写入一个字节
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Ds18b20WriteByte(u8 dat)
{
u16 j;
GPIO_Init(GPIOA, GPIO_PIN_1, GPIO_MODE_OUT_PP_HIGH_FAST);
for(j=0; j<8; j++)
{
GPIO_WriteLow(GPIOA, GPIO_PIN_1);
Delay_us(1);
if((dat&0x01))
{
GPIO_WriteHigh(GPIOA, GPIO_PIN_1);
}
else
{
GPIO_WriteLow(GPIOA, GPIO_PIN_1);
}
dat >>= 1;
Delay_us(60);
GPIO_WriteHigh(GPIOA, GPIO_PIN_1);
Delay_us(1);
}
}
/*******************************************************************************
* 函 数 名 : Ds18b20ReadByte
* 函数功能 : 读取一个字节
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
u8 Ds18b20ReadByte()
{
u8 byte, bi;
u16 j;
for(j=8; j>0; j--)
{
GPIO_Init(GPIOA, GPIO_PIN_1, GPIO_MODE_OUT_PP_HIGH_FAST);
//先将总线拉低1us
GPIO_WriteLow(GPIOA, GPIO_PIN_1);
Delay_us(1);
GPIO_WriteHigh(GPIOA, GPIO_PIN_1);//然后释放总线
Delay_us(6);
//延时6us等待数据稳定
GPIO_Init(GPIOA, GPIO_PIN_1, GPIO_MODE_IN_PU_NO_IT);
if(GPIO_ReadInputPin(GPIOA, GPIO_PIN_1))
{
bi = 0x01;
} //读取数据,从最低位开始读取
else bi = 0x00;
/*将byte左移一位,然后与上右移7位后的bi,注意移动之后移掉那位补0。*/
byte = (byte >> 1) | (bi << 7);
Delay_us(60);
}
return byte;
}
/*******************************************************************************
* 函 数 名 : Ds18b20ChangTemp
* 函数功能 : 让18b20开始转换温度
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Ds18b20ChangTemp()
{
Ds18b20Init();
Delay_ms(1);
Ds18b20WriteByte(0xcc); //跳过ROM操作命令
Ds18b20WriteByte(0x44); //温度转换命令
}
/*******************************************************************************
* 函 数 名 : Ds18b20ReadTempCom
* 函数功能 : 发送读取温度命令
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Ds18b20ReadTempCom()
{
Ds18b20Init();
Delay_ms(1);
Ds18b20WriteByte(0xcc); //跳过ROM操作命令
Ds18b20WriteByte(0xbe); //发送读取温度命令
}
/*******************************************************************************
* 函 数 名 : Ds18b20ReadTemp
* 函数功能 : 读取温度
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int Ds18b20ReadTemp()
{
int temp = 0;
u8 tmh, tml;
Ds18b20ChangTemp(); //先写入转换命令
Ds18b20ReadTempCom(); //然后等待转换完后发送读取温度命令
tml = Ds18b20ReadByte(); //读取温度值共16位,先读低字节
tmh = Ds18b20ReadByte(); //再读高字节
temp = tmh;
temp <<= 8;
temp |= tml;
return temp;
}
这个代码一步一步照着时序码的,还是花了一些时间弄这个...最后读出的数据很快很准确,还是挺高兴的~
PID
粗略理解了PID之后便写了以下代码
emm当时还修改了I的计算方式,以为自己的更好
因为I是以过去的误差来决定未来的功率大小,而经典算法中的I是不断累加的,我就认为不断累加之后巨大的值对温控稳定状态会造成很大的误差,于是我将一直累加改成一段时间累加,最后第一次运行的结果是+-0.2的波动
后来我在改进代码的时候重新用累加写了一下I,发现效果更好...这与我的判断不符,猛然惊醒。对于有风扇的温控,当我在温度接近稳定时,只需要维持稳定即可,所以到这时的I很大可以迅速抵消风吹造成的温度下降。
那对于没风扇的加热器呢?没风扇最大的问题就是温度过冲,所以加热曲线实现的时间必须变长。
一些问题:
由于stm8K103不能用sprintf函数造成了一些困扰,于是我自己写了一个int转字符串函数
判断无符号数<0是我犯过很蠢的错误之一~~
后来帮别人看代码的时候找不出来问题(时序正确,其它小错误也改了),后来发现是时钟初始化的问题...