一. 项目介绍
本项目使用TI的F280049C launchpad和配套的输入输出拓展板设计了一个智能温度调节展示系统。该项目使用温度传感器检测温度变化,彩色LCD显示当前温度和设定温度。LED根据温度高低展示不同颜色。再使用旋转编码器和按键用于设置温度阈值,摇杆调节LED显示的模式和控制参数。
本项目实现的功能如下:
- 通过NST112温度传感器采集加热电阻的温度
- 通过彩色LCD显示当前温度和目标温度
- 通过按键、旋转编码器调节目标温度
- 通过操控MOS管通断时间控制电阻加热功率来实现温控功能
- LED根据温度高低展示不同颜色。
二. 设计思路
根据上文的项目介绍,本项目的主要设计思路如下:
- 按键/编码器识别模块:使用定时器中断定时触发ADC读取拓展板上的按键输出模拟量,获取旋转编码器以及按键信息,从而对目标温度进行设定。
- 加热模块:使用PWM波输出控制mos管的导通时间占比,从而控制实时加热功率;再使用PID算法对PWM波的占空比进行控制,使得温度尽快尽量稳定的维持在目标温度。
- 温度采集模块:单片机使用I2C读取NST112温度传感器的数据,获得实时温度;
- LCD显示模块:单片机使用SPI协议对TFT-LCD屏幕进行控制,用于显示目标温度,实时温度以及实时加热功率。
- RGB-LED显示模块:通过控制三路PWM波的占空比来控制RGB-LED显示的颜色
三. 硬件框图
1.开发板介绍:LAUNCHXL-F280049C 是一款适用于 TI C2000™ 实时控制器系列 F28004x 器件的低成本开发板,其中包括一个 F280049CPZS MCU。该 MCU 非常适合用于低成本应用中的高级实时控制系统。用户可通过板载附件和接口获得大量此类外设,可通过板载附件和 BoosterPack 连接器使用这些外设。
2.温度传感器NST112
NST112是一款低功耗高精度数字温度传感,具有可兼容的I2C和SMBus的接口,此外还具有12bit的模数转换,提供高达0.0625℃解析度,可以正常工作在-40℃到125℃的温度范围内
3.编码器/按键电路
采取的是电阻网络分压的方案,将旋转编码器的AB相及中央按键以及其他的普通按键均接入了电阻网络,再引出一个A_out引脚让单片机使用ADC进行识别
4.加热电路:通过开关MOS管实现加热电阻的电流通断,从而实现电阻加热功率的控制。
5.LCD显示模块:一块彩色TFT-LCD屏幕,驱动芯片为ST3375S
四. 代码实现
1.温度传感器读取:
本项目根据NST112的手册编写了驱动代码,但实际上只用到了读取温度寄存器的函数来读取温度,并未对NST112的其他寄存器进行设定。下面是读取温度寄存器的代码:
float Read_NST112_Temp(void)
{
unsigned char decimal = I2C_Read_Register(NST_ADDRESS,TEM_REG);
float Temp=0;
if(decimal<=0x7FF0)//如果decimal小于150说明为正
Temp = (float)(decimal>>4)/16;
else if(decimal>0xCE00)//如果decimal大于0xCE00说明为负数的补码
Temp = -(float)(((~decimal)>>4)+1)/16;//小于0就先求补码再×0.0625
return Temp;
}
2.按键/编码器识别模块
由于输入输出扩展板的按键和编码器的电路设计采用的是电阻网络分压的方案,因此需要通过使用ADC读取电阻网络输出的模拟电压的数值,来判断出是按键的按下;对于编码器识别,由于电路中将旋转编码器的AB相及中央按键均接入了该电阻网络,单次的读取并不能体现是否旋转,需要使用一段时间内ADC采样的数据进行判断,根据不同时刻读到的ADC采样值结果进行判断正/反转
下图是ccs自带的graph工具绘制ADC采样数据得到的波形。第一张图为顺时针旋转编码器,第二张图为逆时针旋转编码器,第三张图是编码器中央按键按下,第四张图是普通按键按下
由上图可以看出在进行不同的操作时,ADC采集到的波形和各个电压台阶的数据也不同,根据这一特点就能进行编码器和按键操作的识别,使得顺时针旋转编码器增加目标温度,逆时针旋转编码器降低目标温度,按键按下改变正在更改的目标温度位数下面是具体实现的代码
void CheckState()
{
uint8_t counter=0;
float change=0;
if((myADC1Results[i]>2500)&&(myADC1Results[i]<2820)&&(NORMAL==state))
{
counter=count(i,2500,2820);
if(counter>5)
{
state = ENCODER_PRESS;
}
}
else if((myADC1Results[i]<1050)&&(NORMAL==state))
{
counter=count(i,0,1050);
if(counter>5)
{
state = KEY_PRESS;
if(digit<PERCENTILE) //小于百位就加一
digit++;
else digit=TENS;
}
}
else if((myADC1Results[i]>3100)&&(myADC1Results[i]<3200)&&(NORMAL==state))
{
counter=count(i,3100,3200);
if(counter>5)
{
state = SPIN_CW; //顺时钟
}
}
else if((myADC1Results[i]>2975)&&(myADC1Results[i]<3050)&&(NORMAL==state))
{
counter=count(i,2975,3050);
if(counter>5)
{
state = SPIN_CCW; //逆时针
}
}
else if((myADC1Results[i]>2850)&&(myADC1Results[i]<2950)&&((SPIN_CW==state)||(SPIN_CCW==state)))
{
counter=count(i,2850,2950);
if(counter>5)
{
switch(digit)
{
case TENS: change =10;
break;
case ONES: change =1;
break;
case DECILE: change =0.1;
break;
case PERCENTILE: change =0.01;
break;
}
if(state == SPIN_CW) //如果之前状态为顺时针
{
if(target_t>90) //防止设定温度超过100
target_t=target_t+change-100;
else target_t+=change;
}
else if(state == SPIN_CCW) //如果之前状态为逆时针
{
if(target_t<10)//防止设定温度低于0
target_t=target_t+100-change;
else target_t-=change;
}
state =SPIN_FINAL;
}
}
else if((myADC1Results[i]>3300)&&(myADC1Results[i]<4000)&&(NORMAL!=state))
{
counter=count(i,3300,4000);
if(counter>4)
{
state = NORMAL; //顺时钟
}
}
}
// 功能:统计从下标i开始往前,数组中有多少个数位于min和max之内
// 如果连续CONSECUTIVE_LIMIT个数都在范围内或者都不在范围内,则停止计数。
int count(int start_index, int min, int max) {
int count = 0,j=0;
int consecutive = 0;
int within_range = 0;
for (j = start_index; j >= 0; --j) {
if (myADC1Results[j] > min && myADC1Results[j] < max) {//如果该数在范围内
if (!within_range) {//如果上一个数不位于范围内
within_range = 1;
consecutive = 0;//改为连续位于范围内的数的个数
}
consecutive++; //此时代表连续位于范围内的数的个数
} else {
if (within_range) {//如果上一个数位于范围内
within_range = 0;
consecutive = 0;//改为连续不位于范围内的数的个数
}
consecutive++; //此时代表连续不位于范围内的数的个数
}
if (consecutive == CONSECUTIVE_LIMIT) {//当连续位于/不位于范围内的数的个数到达极限时返回
break;
}
if (within_range) {//位于范围则++
count++;
}
}
return count;
}
3.加热模块:根据对加热电路的分析可以发现:当PWM波形为高电平时,驱动MOS管开启,此时电阻流过电流开始发热,当PWM波形为低电平时,驱动MOS管关闭,此时电阻没有电流不发热。这样,通过控制PWM波形的占空比,就可以控制电阻的平均功率,进而控制发热温度。而对于PWM波的输出采用的方法是使用F280049C上自带的ePWM模块进行输出
对于温度的控制,我使用了位置式PID算法,以当前温度和目标温度为输入参数来计算出此时应该输出的PWM波形占空比,并且随着实际温度自动进行调整,最终使其达到设定的温度。下面是具体的代码
//pid位置式
void PID_init()
{
pid_T.SetT= 0.0; // 设定的预期
pid_T.ActualT= 0.0; // adc实际
pid_T.err= 0.0; // 当前次实际与理想的偏差
pid_T.err_last=0.0; // 上一次的偏差
pid_T.Pwr= 0.0; // 控制值
pid_T.integral= 0.0; // 积分值
pid_T.Kp= 4; // 比例系数
pid_T.Ki= 6; // 积分系数
pid_T.Kd= 3.5; // 微分系数
pid_T.Times=3;
pid_T.integral_limit=25;
pid_T.result_limit=100;
}
float PID_realize( float v, float v_r)
{
pid_T.SetT = v;
pid_T.ActualT = v_r; // 实际传入 = ADC_Value * 3.3f/ 4096
pid_T.err = pid_T.SetT - pid_T.ActualT; //计算偏差
pid_T.integral = (pid_T.integral*(pid_T.Times-1) + pid_T.err)/pid_T.Times; //积分求和
LIMIT(pid_T.integral,-pid_T.integral_limit,pid_T.integral_limit);
pid_T.result = pid_T.Kp * pid_T.err + pid_T.Ki * pid_T.integral + pid_T.Kd * ( pid_T.err - pid_T.err_last);//位置式公式
pid_T.err_last = pid_T.err; //留住上一次误差
pid_T.Pwr=0.5*v_r-5+pid_T.result;
LIMIT(pid_T.Pwr,0,pid_T.result_limit);
return pid_T.Pwr;
}
4.RGB-LED显示模块
本模块使用了两个ePWM模块产生三路PWM波来控制RGB LED灯。我将把PWM波占空比分成256个周期,即可用于控制LED灯输出256种亮度,使用三种这样的信号控制RGB灯即可得到256*256*256种颜色混合的效果。下面的代码是使用RGB888格式的值进行相应的LED颜色设置的代码。
/**
* @brief 设置RGB LED的颜色
* @param rgb:要设置LED显示的颜色值格式RGB888
* @retval 无
*/
void SetRGBColor(uint32_t rgb)
{
//根据颜色值修改定时器的比较寄存器值
EPWM_setCounterCompareValue(LED_RG_BASE, EPWM_COUNTER_COMPARE_A, (uint8_t)((rgb>>16)&0x0000FF)); //R
EPWM_setCounterCompareValue(LED_RG_BASE, EPWM_COUNTER_COMPARE_B, (uint8_t)((rgb>>8)&0x0000FF)); //G
EPWM_setCounterCompareValue(LED_B_BASE, EPWM_COUNTER_COMPARE_A, (uint8_t)(rgb&0x0000FF));
}
之后根据温度传感器采集到的数据就能控制RGB LED显示不同的颜色,当温度变化时会改变LED的颜色:
- 当温度小于30度,LED为蓝色
- 当温度大于30度小于40度,LED为青色
- 当温度大于40度小于50度,LED为绿色
- 当温度大于50度小于60度,LED为黄色
- 当温度大于60度小于70度,LED为紫色
- 当温度大于70度小于80度,LED为橙色
- 当温度大于80度,LED为红色
if(current_t<30)
SetRGBColor(COLOR_BLUE);
else if(current_t>=30&¤t_t<40)
SetRGBColor(COLOR_CYAN);
else if(current_t>=40&¤t_t<50)
SetRGBColor(COLOR_GREEN);
else if(current_t>=50&¤t_t<60)
SetRGBColor(COLOR_YELLOW);
else if(current_t>=60&¤t_t<70)
SetRGBColor(COLOR_PURPLE);
else if(current_t>=70&¤t_t<80)
SetRGBColor(COLOR_ORANGE);
else if(current_t>=80)
SetRGBColor(COLOR_RED);
5.LCD显示模块
使用GPIO模拟SPI协议对LCD屏幕进行控制,由于代码过多在此不进行展示
五. 遇到的主要难题及解决方法
1、未找到支持C2000系列的ST7735芯片例程与驱动;
解决:移植基于STM32的驱动程序;
2、NST112没有找到任何例程;
解决:根据数据手册编写驱动程序对NST112的寄存器进行读写;
3、C2000硬件I2C和SPI过于复杂,多次尝试读写失败;
解决:使用GPIO模拟I2C和SPI对读取LCD和NST112进行控制;
4、春节在家做的本项目,没有示波器、万用表等基本的仪器进行测试;
解决:使用了CCS自带的graph调试工具ADC采样数据绘制成波形,再利用ADC的采样率估计时间,从而解决了波形的测量问题;
六. 未来的计划
- 完善I2C程序和SPI程序,尝试硬件I2C和硬件SPI,实现温度传感器的参数配置;
- 尝试改进NST112的驱动程序,使用NST112手册中所展示的13位拓展模式等模式
- 尝试改进PID,测试是否有更好的温控效果;
- 利用此开发板尝试开发本平台上的其他项目,更好地掌握C2000;