自己设计和制作的stc白光,附上原理图,洞洞板走线图和源代码。
只实现了简单的温控,短按一下编码器快速设定温度为300℃,双击一下编码器快速设定为10℃(相当于短暂休眠
代码注释还是很详细的,适合初学者学习。
单片机源程序如下:
- /**
- 布满星星的天空 CZS 编写
- */
- #include <STC15.h>
- #include <intrins.h>
- #include <math.h>
- sbit t12 = P3 ^ 7; //T12控制
- sbit encoderb = P1 ^ 0; //编码器的b脚
- sbit encodera = P1 ^ 1; //编码器的a脚
- sbit encoderd = P1 ^ 2; //编码器的按键d脚
- sbit DIO = P3 ^ 3; // TM1650 数码管驱动的sda引脚
- sbit CLK = P3 ^ 2; // TM1650 数码管驱动的scl引脚
- sbit DO = P5 ^ 5; //DS18B20数据脚
- unsigned long VREF = 2390; // 用万用表测量基准电压的真实值,单位mv
- bit lastb = 0;
- bit lasta = 0;
- unsigned short push_last_time = 0; //记录按下编码器按钮的时间,短按和长按
- unsigned long t12_voltage; // 计算t12热电偶电压
- unsigned long system_voltage; // 计算单片机供电电压
- unsigned long input_voltage; // 计算整个板子的输入电压(12~24V)
- // PID控制算法
- #define KP 1.2f // 比例系数
- #define KI 0.2f // 积分系数
- #define KD 0.1f // 微分系数
- #define MAX_UK 400.0f // 系统允许输出的最大控制量,这里表现为加热数,400个0.5ms加热周期,最长连续加热时间为200ms
- int ek = 0, ek_1 = 0, ek_2 = 0; // 记录连续三次的偏差值(设定值-实际测量值)
- float uk_1 = 0.0f, uk = 0.0f; // 记录当前计算的PID调整值和上次计算的PID调整值
- long integralSum = 0; // 位置式PID算法的累计积分项
- // 定义一个数码管段码表,0~F
- unsigned char CODE[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0X7C, 0X39, 0X5E, 0X79, 0X71};
- unsigned int t12SetTemperature = 10; // 记录当前设定的温度
- unsigned int t12ActualTemperature = 0; // 保存T12当前的实际温度
- bit isChangeTemperature = 0; // 标记是否更改过设定温度
- unsigned int setTempShowTime = 0; // 记录显示设置温度的时长
- unsigned int need_heat_time = 0; // 需要加热的时长
- unsigned int heat_time_count = 0; //当前已经加热的时长
- unsigned int actualTempShowTime = 250; //设定当前温度的显示时长,避免采集温度过快造成数码管乱跳
- /*延时函数,使用STC-ISP自动生成的,比较准确*/
- void Delay_ms(unsigned int k) //@12.000MHz
- {
- unsigned char i, j;
- for (; k > 0; k--)
- {
- i = 12;
- j = 169;
- do
- {
- while (--j)
- ;
- } while (--i);
- }
- }
- void Delay_us(unsigned int i)
- {
- for (; i > 0; i--)
- {
- _nop_();
- _nop_();
- _nop_();
- _nop_();
- }
- }
- // 初始化各个IO口
- void initIO()
- {
- // 配置各个端口的输入模式,M1M0:00普通,01推挽,10高阻输入,11开漏
- /*
- 以下列出需要配置的端口,其他端口保持默认即可
- t12 = P3^7; //T12控制 推挽输出模式
- ADC3:系统输入电压检测 P1^3 输入模式
- ADC4:T12热电偶电压检测 P1^4 输入模式
- ADC5:2.5V参考电压输入 P1^5 输入模式
- */
- //P1M0 |= 0x00; //0000 0000
- P1M1 |= 0x38; //0011 1000
- P3M0 = 0x80; //1000 0000
- P3M1 = 0x00; //0000 0000
- }
- /*初始化ADC*/
- void initADC(void)
- {
- /*
- 开启相应ADC口的模拟输入功能(相应位置1)
- ADC3:系统输入电压检测 P1^3
- ADC4:T12热电偶电压检测 P1^4
- ADC5:2.5V参考电压输入 P1^5
- */
- P1ASF = 0x38; //0011 1000
- ADC_RES = 0; // 清楚结果寄存器
- ADC_RESL = 0;
- /*
- ADC控制寄存器
- ADC_POWER | SPEED1 | SPEED0 | ADC_FLAG | ADC_START | CHS2 | CHS1 | CHS0
- */
- // 这里初始化的时候,可以先打开电源和设置转换速度
- ADC_CONTR = 0x80; // 1000 0000
- Delay_ms(5); // 上电之后延时等待一段时间
- }
- // 关闭ADC电源,在进入空闲(休眠)模式的时候启用,降低功耗
- void closeADC(void)
- {
- /*
- ADC控制寄存器
- ADC_POWER | SPEED1 | SPEED0 | ADC_FLAG | ADC_START | CHS2 | CHS1 | CHS0
- */
- ADC_CONTR = 0x00;
- }
- // 直接插入排序
- void insertionSort(unsigned int A[], unsigned int n)
- {
- unsigned int i, j;
- for (i = 1; i < n; i++)
- {
- for (j = i; j > 0; j--)
- {
- if (A[j] < A[j - 1])
- {
- // 不使用第三变量交换两个数,使用异或运算速度快
- A[j - 1] ^= A[j];
- A[j] ^= A[j - 1];
- A[j - 1] ^= A[j];
- }
- }
- }
- }
- #define ADC_FLAG 0x10 // ADC转换完成标志
- #define ADC_START 0x08 // ADC开始置位
- // 获取某个ADC通道的转换值
- // 为了提高结果的准确性,每次测量,测5次,并且去掉一个最高值,一个最低值,最后取中间3个的均值返回
- unsigned int getADCResult(unsigned int channel)
- {
- unsigned int res[5], i, result = 0;
- for (i = 0; i < 5; i++)
- {
- /*
- ADC控制寄存器
- ADC_POWER | SPEED1 | SPEED0 | ADC_FLAG | ADC_START | CHS2 | CHS1 | CHS0
- */
- ADC_CONTR = (0x80 | channel | ADC_START); // 选择需要读取的通道,并开启转换
- Delay_us(1);
- while (!(ADC_CONTR & ADC_FLAG))
- ; //等待ADC转换完成
- res[i] = ((ADC_RES << 2) | (ADC_RESL & 0x03)); // 计算转换出来的原始结果
- ADC_RES = 0x00;
- ADC_RESL = 0x00; // 清零结果寄存器
- }
- // 对结果进行排序
- insertionSort(res, 5);
- // 去掉一个最高值,去掉一个最低值,剩余三个求平均值
- result = (res[1] + res[2] + res[3]) / 3;
- return result;
- }
- /*以下是计算各种电压的函数,返回结果的单位都是mv*/
- // 计算公式(mv) output_voltage = (VREF万用表测的TL431基准电压值(mv) * 待测通道的ADC值 / TL431基准通道ADC值)
- // 计算获取T12的电压
- void getT12Voltage(void)
- {
- t12_voltage = (VREF * getADCResult(4) / getADCResult(5)); //计算t12电压,单位mV
- }
- // 计算获取单片机的电源电压
- void getSystemVoltage(void)
- {
- system_voltage = (VREF * 1024 / getADCResult(5)); //计算单片机的电源电压,单位mV;
- }
- // 计算获取输入电源电压
- void getInputVoltage(void)
- {
- input_voltage = (VREF * getADCResult(3) / getADCResult(5) * 11); // 计算下分压电阻点的电压,并乘分压比,获得实际的输入电压
- }
- /* 计算T12热电偶温度 */
- void getT12Temperature(void)
- {
- float x = 0.0f;
- t12 = 0; //测温的时候,关闭电烙铁
- Delay_us(10); // 等待一段时间,等电容放完电后再测量温度比较准确
- getT12Voltage(); // 更新T12热电偶电压
- //T12实际温度 = (ADC电压(mV)-失调电压(mV))/运放增益*热电偶分度值(℃/mV)+室温(℃)
- //t12Temp = 1.0f*getT12Voltage() / 231.0f * 54.0f + 23.0f;
- // 插值函数计算T12温度,上面的算得不够准确,自己测量了t12温度与热电偶电动势的关系,用matlab拟合出来的公式
- x = 1.0f * t12_voltage / 231.0f;
- x = -0.9f * x * x + 48.0f * x + 22.0f;
- t12ActualTemperature = (unsigned int)x;
- }
- /*
- // 增量式PID控制算法,该算法用在温度控制效果不佳,调参调了比较久,不是很理想
- // 输入设定温度和当前温度
- // 返回当前应该加热的时长
- void incrementalPID(unsigned int setTemperature, unsigned int actualTemperature)
- {
- float delta_uk = 0.0f; // 用于计算PID增量值
- uk_1 = uk; // 记录上次计算的PID调整值
- ek_2 = ek_1; // 记录上上次计算的偏差值
- ek_1 = ek; // 记录上次计算的偏差值
- ek = setTemperature - actualTemperature; // 计算当前偏差值
- if (ek < 0)
- {
- // 如果实际温度比设定温度还要高,那么不执行加热
- need_heat_time = 0;
- return;
- }
- if (abs(ek) > 100)
- {
- // 如果温差大于100℃,则执行系统的动态加速,不管比例项为正还是为负,都取正数
- delta_uk = KP * abs(ek - ek_1) + KI * ek + KD * (ek - 2 * ek_1 + ek_2); // 计算PID增量值
- }
- else
- {
- // 当快要接近目标温度的时候,执行正常的调节
- delta_uk = KP * (ek - ek_1) + KI * ek + KD * (ek - 2 * ek_1 + ek_2); // 计算PID增量值
- }
- uk = uk_1 + delta_uk; // 计算当前应该输出的PWM值
- // 判断是否超出了系统控制量的边界范围,如果超出,则赋值为边界
- if (uk < 1e-9)
- {
- uk = 0.0f;
- }
- if (uk > MAX_UK)
- {
- uk = MAX_UK;
- }
- need_heat_time = (unsigned int)uk;
- }
- */
- // 位置式PID控制算法,这个控制算法运行起来比较理想
- void positionalPID(unsigned int setTemperature, unsigned int actualTemperature)
- {
- ek_1 = ek; // 记录上次计算的偏差值
- ek = setTemperature - actualTemperature; // 计算当前偏差值
- if (ek < 0)
- {
- // 如果实际温度比设定温度还要高,那么不执行加热
- need_heat_time = 0;
- return;
- }
- // 当偏差较大时,取消积分作用
- if (abs(ek) > 100)
- {
- integralSum = 0;
- }
- else
- {
- // 否则,根据情况进行累计积分
- if (integralSum > 100) //积分超过上限时,只累计负的积分量
- {
- if (ek < 0)
- {
- integralSum += ek;
- }
- }
- else if (integralSum < -10) //积分超过下限时,只累计正的积分量
- {
- if (ek > 0)
- {
- integralSum += ek;
- }
- }
- else
- {
- integralSum += ek;
- }
- }
- uk = KP * ek + KI * integralSum + KD * (ek - ek_1); // 计算当前应该输出的控制量值
- // 判断是否超出了系统控制量的边界范围,如果超出,则赋值为边界
- if (uk < 1e-9)
- {
- uk = 0.0f;
- }
- if (uk > MAX_UK)
- {
- uk = MAX_UK;
- }
- need_heat_time = (unsigned int)uk; // 更新当前需要加热的时间数
- }
- /********************************以下是TM1650数码管显示相关的函数****************************************************/
- void Start1650(void)
- { //开始信号
- CLK = 1;
- DIO = 1;
- Delay_us(5);
- DIO = 0;
- Delay_us(5);
- DIO = 0;
- }
- void Ask1650(void)
- { //ACK信号
- unsigned char timeout = 1;
- CLK = 1;
- Delay_us(5);
- CLK = 0;
- while ((DIO) && (timeout <= 100))
- {
- timeout++;
- }
- Delay_us(5);
- CLK = 0;
- }
- void Stop1650(void)
- { //停止信号
- CLK = 1;
- DIO = 0;
- Delay_us(5);
- DIO = 1;
- Delay_us(5);
- }
- void WrByte1650(unsigned char oneByte)
- { //写一个字节,高位在前,低位在后
- unsigned char i;
- CLK = 0;
- Delay_us(1);
- for (i = 0; i < 8; i++)
- {
- oneByte = oneByte << 1;
- DIO = CY;
- CLK = 0;
- Delay_us(5);
- CLK = 1;
- Delay_us(5);
- CLK = 0;
- }
- }
- /*
- unsigned char Scan_Key(void)
- { // 按键扫描
- unsigned char i;
- unsigned char rekey;
- Start1650();
- WrByte1650(0x49); //读按键命令
- Ask1650();
- //DIO = 1;
- for (i = 0; i < 8; i++)
- {
- CLK = 1;
- rekey = rekey << 1;
- if (DIO)
- {
- rekey++;
- }
- Delay_us(5);
- CLK = 0;
- }
- Ask1650();
- Stop1650();
- return (rekey);
- }
- */
- void Set1650(unsigned char add, unsigned char dat)
- { //数码管显示
- //写显存必须从高地址开始写
- Start1650();
- WrByte1650(add); //第一个显存地址
- Ask1650();
- WrByte1650(dat);
- Ask1650();
- Stop1650();
- }
- // 初始化1650,传入亮度参数,范围0~7
- void init1650(unsigned char light)
- {
- Set1650(0x48, ((light << 4) | 0x01));
- }
- // 数码管显示函数
- void display(signed int num)
- {
- // 计算这个数字对应的千百十个位的数字
- unsigned int tmpnum, qian, bai, shi, ge;
- if (num < 0)
- num = -num;
- tmpnum = num;
- qian = tmpnum / 1000;
- tmpnum %= 1000;
- bai = tmpnum / 100;
- tmpnum %= 100;
- shi = tmpnum / 10;
- ge = tmpnum % 10;
- Set1650(0x68, CODE[qian]);
- Set1650(0x6A, CODE[bai]);
- Set1650(0x6C, CODE[shi]);
- Set1650(0x6E, CODE[ge]);
- }
- /************************************************************************************************
- 函数名称:Encoder
- 函数功能:编码器旋转的扫描及处理
- 入口参数:无
- 出口参数:char型 0-无旋转 'R'-正转(向右转) 'L'-反转(向左转)
- ************************************************************************************************/
- unsigned char Encoder(void)
- {
- static bit Enc0 = 0;
- static unsigned char EncOld, EncX = 0;
- unsigned char EncNow;
- encodera = 1; //PINA置高电平
- encoderb = 1; //PINB置高电平
- if (Enc0 == 0)
- {
- EncOld = (encodera ? 0x02 : 0x00) + (encoderb ? 0x01 : 0x00);
- Enc0 = 1; //记住初次调用时编码器的状态
- }
- EncNow = (encodera ? 0x02 : 0x00) + (encoderb ? 0x01 : 0x00); //根据两个IO当前状态组合成16进制的0x00|0x01|0x02|0x03
- if (EncNow == EncOld)
- return (0); //如果新数据和原来的数据一样(没有转动)就直接返回0
- if (EncOld == 0x00 && EncNow == 0x02 || EncOld == 0x03 && EncNow == 0x01)
- EncX = EncNow; //00-10|11-01
- if (EncOld == 0x00 && EncX == 0x02 && EncNow == 0x03 || EncOld == 0x03 && EncX == 0x01 && EncNow == 0x00) //00-10-11|11-01-00右转
- {
- EncOld = EncNow, EncX = 0;
- return ('R'); //两定位一脉冲
- }
- if (EncOld == 0x00 && EncNow == 0x01 || EncOld == 0x03 && EncNow == 0x02)
- EncX = EncNow; //00-01|11-10
- if (EncOld == 0x00 && EncX == 0x01 && EncNow == 0x03 || EncOld == 0x03 && EncX == 0x02 && EncNow == 0x00) //00-01-11|11-10-00左转
- {
- EncOld = EncNow;
- EncX = 0;
- return ('L'); //两定位一脉冲
- }
- return (0); //没有正确解码返回0
- }
- // 编码器函数,用于设置t12的设定温度
- void bianmaqi()
- {
- unsigned char enc;
- enc = Encoder(); //扫描编码器,取得返回值
- if (enc == 'R')
- {
- t12SetTemperature += 5;
- if (t12SetTemperature > 500)
- t12SetTemperature = 500;
- isChangeTemperature = 1;
- }
- else if (enc == 'L')
- {
- t12SetTemperature -= 5;
- if (t12SetTemperature < 10)
- t12SetTemperature = 10;
- isChangeTemperature = 1;
- }
- while (!encoderd)
- {
- push_last_time++;
- Delay_ms(100);
- }
- if (push_last_time > 0 && push_last_time < 8)
- {
- push_last_time = 0;
- Delay_ms(80); // 等待延时一段时间,看看有没有第二次按下
- while (!encoderd)
- {
- push_last_time++;
- Delay_ms(100);
- }
- if (push_last_time > 0 && push_last_time < 8)
- {
- // 双击了编码器按钮,关闭电烙铁输出
- t12SetTemperature = 10;
- t12 = 0;
- isChangeTemperature = 1;
- }
- else
- {
- // 短按一次编码器按钮,设定为300℃输出
- t12SetTemperature = 300;
- isChangeTemperature = 1;
- }
- }
- else if (push_last_time >= 8)
- {
- // 长按编码器按钮,设定为250℃输出
- t12SetTemperature = 250;
- isChangeTemperature = 1;
- }
- if (isChangeTemperature)
- {
- // 如果有改变设置温度,那么就要显示新的设置温度
- setTempShowTime = 600;
- }
- push_last_time = 0;
- }
- // 该定时器初始化函数部分使用STC-ISP下载软件生成,加入开启中断的寄存器赋值
- void Timer0Init(void) //500微秒 0.5ms @12.000MHz
- {
- AUXR |= 0x80; //定时器时钟1T模式
- TMOD &= 0xF0; //设置定时器模式
- TL0 = 0x90; //设置定时初值
- TH0 = 0xE8; //设置定时初值
- TF0 = 0; //清除TF0标志
- ET0 = 1; //开启定时器0中断
- TR0 = 1; //定时器0开始计时
- EA = 1; // CPU总中断允许控制位
- }
- //定时0中断函数,每隔0.5ms执行一次
- void timer0(void) interrupt 1
- {
- bianmaqi(); // 调用编码器函数,获取编码器当前状态(左旋,右旋,短按,长按)
- if (heat_time_count < need_heat_time)
- {
- // 如果当前还没完成需要加热的时长,就控制t12加热
- t12 = 1;
- heat_time_count++;
- }
- else
- {
- // 已经完成相应的加热时长,调用PID函数获取下一个需要的加热数
- t12 = 0;
- getT12Temperature(); // 获取当前T12的温度
- positionalPID(t12SetTemperature, t12ActualTemperature); // 更新当前需要加热的时间计数
- heat_time_count = 0; //重新开始统计加热时长
- }
- if (setTempShowTime > 0)
- {
- // 还没显示够显示设置温度的时长,需要继续显示,这里用7开头表示显示设定温度
- display(t12SetTemperature + 7000);
- setTempShowTime--;
- // 已经显示了设置温度了,那么要把这个更改过设置温度的标志开关关闭
- isChangeTemperature = 0;
- }
- else
- {
- if (actualTempShowTime < 2)
- {
- display(t12ActualTemperature); // 显示当前的t12实际温度
- actualTempShowTime = 250;
- }
- actualTempShowTime--;
- }
- }
- void main()
- {
- initIO(); // 初始化IO口
- t12 = 0;
- init1650(1); // 初始化TM1650显示
- initADC(); // 初始化ADC
- getSystemVoltage(); //检测单片机电源电压
- getInputVoltage(); //检测输入电压
- // 显示输入电压,这里的7123和7456仅仅只是用于标识显示什么内容,毕竟没有OLED屏幕这么高大上可以显示很多内容
- ……………………
- …………限于本文篇幅 余下代码请从51黑下载附件…………
复制代码
压缩包内容
全部资料51hei下载地址:
51hei_T12白光.zip
(352.73 KB, 下载次数: 662)
|