使用MAXQ2000微控制器进行基于温度的风扇控制

描述

本应用笔记介绍如何使用MAXQ2000微控制器控制直流风扇速度,并通过热敏电阻或i按钮®监测温度。

介绍

MAXQ2000具有众多的特性,可以创建多种有用的应用,例如通过脉宽调制(PWM)控制风扇的速度。MAXQ2000的众多特性包括带PWM和串行外设接口(SPI)的定时器。™)和1-Wire功能。本应用笔记描述了如何使用MAXQ2000驱动风扇,并通过PWM实时改变风扇速度。该过程需要使用Maxim的另一种产品,即MAX1407多通道数据采集系统(DAS)。利用SPI,MAXQ2000可以与MAX1407通信(包含16位模数转换器[ADC]和数模转换器[DAC])。作为使用热敏电阻的替代方法,MAXQ2000的1-Wire总线主控器可与温度i按钮(DS1920)配合使用。

概述

该程序在MAXQ2000评估板(修订版B)上运行,借助温度i按钮(DS1920)或外部热敏电阻。MAXQ2000评估板(修订版B)包括一个LCD显示屏、两个按钮、一个MAX1407 ADC、两个UART、三个定时器、1-Wire和许多其他特性。程序使用LCD显示屏、按钮和MAX1407或1-Wire读取温度。此外,还需要直流风扇和适用的驱动电路、热敏电阻和电源。MAXQ2000评估板将驱动外部电路控制风扇速度。LCD显示屏显示当前温度,并定期从摄氏度到华氏度变化。按钮允许用户更改风扇的低速和全速温度阈值。

检测温度的默认方法是使用i按钮,但如果正确的i按钮不可用或通信出现问题,程序通过MAXQ2000的SPI使用热敏电阻来获取温度读数。i按钮(DS1920)是一款温度检测1-Wire器件。该程序将RL1005-5744-103-SA热敏电阻与MAX1407配合使用。

读取温度后,将显示被发送到LCD,并根据温度读数调整PWM占空比。

程序中有两个阈值,最低温度和最高温度。如果温度低于最低温度阈值,风扇关闭;如果高于最大温度阈值,则风扇设置为其最大速度。如果温度介于最小阈值和最大阈值之间,则速度与两个温度阈值之间的分数距离成正比。

两个阈值可通过标记为SW4和SW5的两个按钮进行配置。SW4 切换阈值以更改 - 基本或最大值。为更改选择的阈值显示在数值数据和 C/F 字符之间。下划线表示将更改基本阈值,而高划线表示将更改最大阈值。SW5 将当前选择的阈值增加 1。每更改一个阈值,就会重新计算风扇速度。

该应用需要使用MAXQ2000上三个定时器中的两个。定时器1专用于通过热敏电阻定期检查温度,而定时器0使用P6.5的PWM输出来控制风扇。定时器1不能用于PWM,因为它具有与MAX1407通信期间使用的硬件资源。

硬件设置

转换器

图1.将热敏电阻硬件连接到MAXQ2000评估板。

首先,使用RL1005-5744-103-SA热敏电阻和10kΩ电阻构建热敏电阻设置。这是通过首先将J7引脚7和8连接到热敏电阻的一侧来完成的。在热敏电阻的另一侧连接J1引脚7的导线。接下来,将J10的引脚1和地之间的7kΩ电阻连接(J71的引脚72或2接地)。如果使用i按钮选项,则应将i按钮放置在i按钮夹DS9094FS(或类似器件)中,该夹子应焊接到电路板的1-Wire部分。接下来,通过打开 SW6.2 和 SW6.5 启用按钮。液晶显示屏连接到J3。为了通过热敏电阻读取温度,打开SW3的全部,以便与MAX1407通信。MAX1407的ADC部分将来自热敏电阻的模拟信号转换为可用于计算温度的数字值。使用 i 按钮需要 JU7 和 JU8 上的跳线以及 i按钮夹。

转换器

图2.用于将 PWM 输出连接到风扇的可能硬件设置。

解释软件定义和包含

 

#define PWMFREQ 1000         // Change this for desired PWM frequency
#define CPUFREQ 13500000     // Change this to match current clock 
                             //    frequency
#define MIN_TICKS 0          // Minimum number of timer ticks 
                             //    for fan to overcome static friction
#define POLLING_INTERVAL 500 // Number of milliseconds between 
                             //    temperature checks

以下是编译时要检查的一些基本定义:

#define LCD0_PATTERN_C    0x039
#define LCD0_PATTERN_F    0x071

#define LCD_PATTERN_0     0x03F
#define LCD_PATTERN_1     0x006
#define LCD_PATTERN_2     0x05B
#define LCD_PATTERN_3     0x04F
#define LCD_PATTERN_4     0x066
#define LCD_PATTERN_5     0x06D
#define LCD_PATTERN_6     0x07D
#define LCD_PATTERN_7     0x007
#define LCD_PATTERN_8     0x07F
#define LCD_PATTERN_9     0x067

int PATTERNS[] = { LCD_PATTERN_0, LCD_PATTERN_1, LCD_PATTERN_2, LCD_PATTERN_3,
                   LCD_PATTERN_4, LCD_PATTERN_5,LCD_PATTERN_6, LCD_PATTERN_7,
                   LCD_PATTERN_8, LCD_PATTERN_9 };

 

使用上述定义,数字的LCD显示非常容易。LCD 图案是预定义的,然后添加到名为 PATTERNS 的数组中以供检索。显示某个数字就像:

 

LCD2 = PATTERNS[desired_digit];

定时器 1 初始化

 

 T2V1 = 0xFFFF - (CPUFREQ/128/1000*POLLING_INTERVAL);  // Set reload value.
  T2R1 = T2V1;                   // Set current timer value
  T2C1 = 0x00000;                // Set compare value
  T2CFG1_bit.T2DIV = 7           // Set Divide mode to Divide by 128
  T2CNA1_bit.TR2 = 1;            // Start Timer
  T2CNA1_bit.T2POL0 = 1;         // Set polarity high
  T2CNA1_bit.ET2 = 1;            // Enable timer interrupts
  T2CNB1_bit.T2OE1 = 1;          // Enable timer output
  IMR_bit.IM4 = 1;               // Enable interrupts from module 4

定时器1负责定期检查温度。初始值 (T2V1) 的设置取决于温度轮询之间的时间长度(以毫秒 (POLLING_INTERVAL) 为单位)。在两行不同的行上完成计算的原因是防止值变得太大而寄存器无法处理。T2R1 指定计时器在达到 65,535 时返回的值。T2C1 设置为低于重新加载值 (T2R0) 以确保它永远不会生成中断。T2CFG1 是配置许多计时器选项的寄存器。计时器的时钟分频设置为将系统时钟除以 128,这意味着系统时钟的每 128 个周期相当于计时器的 1 个周期。系统时钟也可以除以,然后将 2 个计时器周期的时间量(以 1 的幂为单位)相乘。T2CNA1 是定时器 0 本身的寄存器。这将启动定时器运行并启用定时器中断,而 T2CNB1 使能定时器的输出。最后,启用模块 4 中的中断。

定时器 0 初始化

 

  T2V0 = 0xFFFF - (CPUFREQ / PWMFREQ);    // Set current timer value
  T2R0 = T2V0;                            // Set reload value
  T2C0 = T2R0+1;                          // Set compare value to reload value +1

  T2CNA0_bit.T2OE0 = 0;	                   // Turns PWM output off
  T2CNA0_bit.T2POL0 = 0;                  // Changes polarity of PWM so it starts "off"
  T2CNA0_bit.T2OE0 = 1;	                   // Turns PWM output on
  T2CFG0 = 0x00;                          // Set timer divide at 1

下一个要初始化的项目是定时器0,它控制PWM,并通过它控制风扇。计时器 0 是一个 16 位计时器,与计时器 1 完全相同。当定时器达到比较值时(即,当 T2V0 == T2C0 时),端口引脚状态反转。当定时器重新加载时,端口引脚也会反转(图3)。T2V0 设置计时器的初始值,而 T2R2 设置重新加载值,两者都设置为 0xFFFF - (CPU 频率/PWM 频率)。此计算用于更轻松地将代码移植到具有不同 CPU 时钟速度的系统或更改所需的 PWM 频率。

转换器

图3.端口引脚图。

子选项 T2EO0 可确保 PWM 的输出关闭,以便改变极性 (T2POL0)。当极性开关设置为0时,PWM的启动状态为关闭;如上图所示。T2CFG0 确保计时器不会划分系统时钟,并告诉计时器将 T2V0 与 T2C0 进行比较。

getADCReading负责将模拟信号转换为数字值。下面列出了从热敏电阻读取温度的步骤。

 

 sendSPI(RD_ADC);                // Read the 1407's ADC register.
  spiData = sendSPI(0x0FF);
  spiData |= 0x01;                // Set the start conversion bit.
  sendSPI(WR_ADC);                // Write the new value back into
  sendSPI(spiData);               //   the 1407 register.
  
  do
  {
    sendSPI(RD_STAT);             // Read the 1407's Status register.
    spiData = sendSPI(0x0FF);
  }
  while((spiData & 0x02) == 0x00);  // Bit 1 indicates AtoD conversion complete.
  
  sendSPI(RD_DATA);               // Send the command to read the 1407's Data register.
  SPICF = 0x04;                   // Set up SPI to 16-bit mode.
  spiData = sendSPI(0x0FFFF);     // Read the Data register.
  SPICF = 0x00;                   // Put SPI  back into 8-bit mode.
  
  return spiData;

液晶屏初始化

 

 //LCRA_bit.FRM = 7;   // Set up frame frequency.
  //LCRA_bit.LCCS = 1;  // Set clock source to HF/128.
  //LCRA_bit.DUTY = 0;  // Set up static duty cycle.
  //LCRA_bit.LRA = 0;   // Set R-adj to 0.
  //LCRA_bit.LRIGC = 1; // Select external LCD drive power.
  LCRA = 0x03E0;        // Do all configuration changes at once

  LCFG_bit.PCF = 0x0F;  // Set up all segments as outputs.
  LCFG_bit.OPM = 1;     // Set to normal operation mode.
  LCFG_bit.DPE = 1;     // Enable display.
  LCD1 = 0x08;

为了显示当前温度,您必须打开LCD显示屏。前五行被注释掉了代码,因为它们已被压缩成一个语句。LCRA是LCD调整寄存器,它控制LCD显示设置。更改 FRM 设置帧频率;有了这个,您可以降低频率并使用更少的功率。帧频率是用于在 LCD 上显示的电源频率。LCCS 将 LCD 时钟分频更改为 128。Duty 将显示设置为静态,这意味着显示输出永远不会更改。LCFG 是 LCD 配置寄存器。由于LCD只需要显示,因此将PCF更改为0x0F会将LCD中的所有段设置为输出。将 OPM 和 DPE 设置为 1 将分别打开 LCD 并启用显示器。LCD1 = 0x08显示下划线,表示按钮将增加最小阈值。通过下面的两行简单操作,LCD显示屏现在处于活动状态并准备好显示。

 

LCRA = 0x03E0;
LCFG = 0xF3;

所有需要做的就是将值(如PATTERNS[数字]中的项目)加载到LCD寄存器中,这是非常快速和容易的。

按钮初始化

 

 EIE1 = 0x84;                          // enable interrupts 15 and 10
  EIES1 = 0x84;                         // set up edge transitions
  IMR_bit.IM1 = 1;                      // turn on interrupts for module 1 

现在是时候初始化按钮了。与按钮相关的中断是 10 和 15,对应于 EIE4(外部中断使能 80)上的 1 和 1(十六进制)。EIE1 启用这些外部中断,而 EIES1(外部中断边沿选择 1)将中断触发器设置为下降沿(按下按钮)。如果将其清除为零,则释放按钮是触发中断的操作。IMR 是中断掩码寄存器,它跟踪允许中断的模块,IM1 启用模块 1 的中断。

按钮中断

按钮允许用户更改base_temp和max_temp阈值。SW4(中断 10)更改将要更改的阈值,而 SW5(中断 15)将阈值增加 1。如果max_temp达到 149,则自身降低到 base_temp+1。如果base_temp比max_temp低 50,则base_temp变为 <>。

 

#pragma vector = 1
__interrupt void pushButtonInterrupt()
{
  if (EIF1 & 0x04)  //interrupt 10     // Modify which threshold to changed
  {
    if(last_state == 0)                // Modify max_temp instead of base_temp
    {
      LCD1 = 0x01;                     // Change display to underscore
      last_state = 1;
    }
    else                               // Modify base_temp instead of max_temp
    {
      LCD1 = 0x08;
      last_state = 0;                  //Change display to overscore
    }
  }

“#pragma vector = 1”是一个编译器指令,指示此函数处理来自模块 1 的任何中断,模块 <> 是外部中断的来源。

pushButtonInterrupt 函数首先检查触发了哪个中断。中断 10 0x04中断,中断 15 0x80。如果要更改阈值,则会更改切换并更新显示。如果是阈值增量器,则阈值增加 1。如果阈值已达到其上限,则绕到下限。

 

 if (EIF1 & 0x80)  //interrupt 15     // Increase threshold for base or max temps
  {
    if(last_state == 0)                                
    {                                  // Increase base_temp
      if(base_temp < (max_temp -1))                   
      {
        ++base_temp;
        if(base_temp > 99)              // If over 99, print 1 for 100's place
          LCD4 = 0x40;
        else
          LCD4 = 0;                    // Else print nothing
        LCD3 = getLCDDigit( (base_temp /10) %10);  // Print 10's digit
        LCD2 = getLCDDigit(base_temp %10);         // Print 1's digit
      }
      else if (base_temp == max_temp -1)         // If base temp is going to equal max temp
      {                                // set base temp to 50 instead of increasing
        base_temp = 50;
        LCD3 = getLCDDigit(5);         // Print 50 to display
        LCD2 = getLCDDigit(0);
      }
    }

如果正在修改阈值,它将检查last_state的值。当last_state为零时,base_temp被修改;否则max_temp将被修改。如果base_temp正好小于 max_temp 50,则程序将base_temp包装为任意值 <>。

 

  else if (last_state == 1)          // Increase max_temp
    {
      if(max_temp < 149)
      {
        ++max_temp;
        if(max_temp > 99)
          LCD4 = 0x40;                 //show 100s digit
        else
          LCD4 = 0;
        LCD2 = getLCDDigit(max_temp %10);  // Show 1s digit
        LCD3 = getLCDDigit( (max_temp / 10) %10);    // Show 10s digit
      }
      else
      {
        if (max_temp == 149)           // If max_temp is at limit then cycle 
          max_temp = base_temp+1;      // to be 1 degree over base temp
        if(max_temp > 99)
          LCD4 = 0x40;                 // Clear 100s digit
        else 
          LCD4 = 0;
        LCD2 = getLCDDigit(max_temp %10);  // Show 1s digit
        LCD3 = getLCDDigit( (max_temp / 10) %10); // Show 10s digit
      }
    }
    xplier = ( (CPUFREQ/PWMFREQ) / (max_temp - base_temp));  // Re-calculate multiplier
  }
  EIF1 = 0;                            // Clear External Interrupt Flag

按钮中断的这一部分更改max_temp阈值。它增加max_temp直到等于 149,然后它环绕起来变得比 base_temp 大 1。如果base_temp是65,那么max_temp将是66。

新增加/换行的值会短暂显示在LCD上,以便用户知道它已更改。下次计时器重新加载时,LCD 会变回温度,并使用温度更新显示。

更改阈值后,将重新计算 xplier。xplier 是确定 T2C1 值的乘数。在按钮中断完成之前,它会清除 EIF1(外部中断标志 1),以便为下一次中断做好准备。

定时器 1 中断

 

#pragma vector = 4 
__interrupt void timer1Interrupt()
{
  T2CNA1_bit.TR2 = 0;                  // Stop the timer.
  
  if(++count == 20)                    // Change the units occasionally.
  {
    count = 0;
    celsius = ~celsius;
  }
  readTemp();                          //Get and display the current temperature.
  T2CNB1_bit.TF2 = 0;                  //Clear the overflow flag.
  T2CNB1_bit.TCC2 = 0;                 //Clear the overflow flag.
  
  // output an 'alive' blip with the first decimal point, LCD0.7
  if (count & 1)
    LCD0 = LCD0 | 0x80;
  else
    LCD0 = LCD0 & 0x7F;
  T2CNA1_bit.TR2 = 1;                     // Start the timer.
}

注意:如果还使用了定时器 2,则此函数将针对定时器 2 的中断运行,因为定时器 2 也位于模块 4 中。

将 TR2 设置为 0 将关闭计时器,然后检查计数是否等于 20。计数用于定期将显示从华氏度更改为摄氏度。readTemp() 调用负责读取温度的函数。readTemp 完成后,将清除指示可能的计时器溢出(TF2 和 TCC2)的标志。if 语句检查计数基本上会在每次检查温度时创建一个“刻度”。此“勾号”是启用或禁用单位字符左侧的小数点。这对于了解检查的频率和速度以及温度保持不变时运行非常有用。最后,计时器再次启动。

功能

readTemp() 函数负责读取和显示温度,以及驱动 PWM。

 

 if(readiButtonTemp(&temp)==FALSE)   // Check to see if the device can be found
  {
    // If there is no iButton device found
    adc = getADCReading();            // Read the thermistor value.
    temp = convertToTemp(adc);        // Convert this value to degrees Celsius. 
  }
  
  showTemp(temp);                   // Display temperature
  drivePWM(temp);                   // Update fan speed

它做的第一件事是尝试从1-Wire或i按钮器件获取温度。如果没有有效的1-Wire温度器件或通信中发生错误,则使用热敏电阻读取温度。读取温度后,通过调用函数 showTemp,将 LCD 显示屏更新为最新读数。之后,驱动PWM相应地改变风扇的速度。

 

float convertToTemp(unsigned int adc)
{
  double temp = -0.00135477 * (double)adc + 69.17;
  return (float)temp;
}

 

convertToTemp将MAX1407的读数转换为摄氏度。请注意,该公式给出了接近室温的近似值,并且不补偿热敏电阻随温度的非线性变化。

 

void showTemp(int temp)
{
  LCFG_bit.DPE = 0;     // disable display.
  // Clear the display of everything but the temperature units (F or C).
  LCD0 = ( (celsius == 0) ? LCD0_PATTERN_F : LCD0_PATTERN_C);

  // clear the digits 
  LCD1 = LCD2 = LCD3 = LCD4 = 0;
  if(last_state == 0)   // Display correct threshold being edited
    LCD1 = 0x08;
  else 
    LCD1 = 0x01;

showTemp 函数做的第一件事是将单位系统写入显示器,然后通过将 LCD 寄存器设置为 0 来清除显示器中的所有数字。然后,它显示表示要通过按钮编辑的阈值的过划线或下划线。

 

 if(celsius == 0)
  {
    temp = CtoF(temp);              // Convert to Fahrenheit
  }

  if (temp > 199)                   // If temperature is 200+
  {
      temp = temp % 200;
  }

  if(temp > 99)                     // If the temperature is 100+
  {
    show100s();                     // Show a '1' on the LCD.
    temp -= 100;                    // Adjust the temperature variable.
  }

  LCD3 = getLCDDigit(temp / 10);    // show the 10's place on the LCD
  LCD2 = getLCDDigit(temp % 10);    // show the 1's place on the LCD
  LCFG_bit.DPE = 1;                 // Enable display.

如果当前单位系统为华氏度,则温度通过函数 CtoF 转换为华氏度。如果 temp 超过 100,则显示 1 表示 100 的数字,并在显示屏上显示其他数字。

drivePWM是通过改变PWM比较值来设置风扇功率的功能。

 

 T2CNA0_bit.TR2 = 0;              // Turn timer off
  T2CNA0_bit.T2OE0 = 0;            // Turns PWM output off
  T2CNA0_bit.T2POL0 = 0;           // Changes polarity of PWM so that it starts out off
  T2CNA0_bit.T2OE0 = 1;            // Turns PWM output on
  T2V0 = T2R0;                     // Manually reload timer value

此序列关闭风扇,然后手动重新加载计时器值。下面,代码设置风扇的实际速度。如果温度低于base_temp阈值,则风扇设置为可能的最低设置,否则它会检查温度是否高于最大阈值,在这种情况下,风扇设置为最高设置。最后,如果温度在阈值之间,则根据度数计算风扇的速度。然后计时器重新打开。

转换器

图4.顶部输出正常,而底部迹线是可能的,如果计时器未停止并手动重新加载。

在更新比较值和重新加载值时禁用计时器以避免无意中反转输出非常重要。图 4 显示了两种类型的输出。顶部输出是正常的,而底部的图形是可能的,如果计时器没有停止并手动重新加载。发生的情况是,新的比较值高于当前定时器值,当定时器最终达到新的比较值时,端口引脚状态反转,定时器继续。但是,由于状态变化是不需要的,因此它具有反转定时器输出极性的效果。

 

 if(temp <= base_temp)           // Below this temperature the fan is off
  {
      // Force port pin P6.5 low
      PO6 &= 0xDF;
  }
  else
  {
    if( temp >= max_temp )        // Beyond this temperature the fan is full speed
    {
      // Force port pin P6.5 high
      PO6 |= 0x20;
    }
    else                         // Set the fan speed according to temperature
    {
      T2C0 = 65535 - ((temp - base_temp) * xplier);
      if( (65535 - T2C0) < MIN_TICKS)
        T2C0 = 65535 - MIN_TICKS;
      T2CNA0_bit.TR2 = 1;        // Restart timer
    }
  }

高于base_temp阈值的每一度都有自己的速度,该速度基于所需的PWM占空比和两个阈值之间的范围。MIN_TICKS是最小定时器周期数,PWM 输出必须很高才能使风扇以最低速度运行。风扇速度随着温度的升高而线性增加。速度变化率仅在阈值变化时发生变化。例如,base_temp 和 max_temp 的默认阈值分别为 75 和 85。这意味着 xplier(每度风扇速度的增加)为 1350。如果阈值分别更改为 75 和 90,则 xplier 将为 900,这意味着每个度数的风扇速度增加较少。效率越低,风扇越平滑。

 

void main()
{
  initTimer1();             // Initialize timer1 for thermistor/iButton polling
  initTimer0();             // Initialize timer0 for PWM output
  init1407();               // Initialize the 1407
  initOW();                 // Initialize the 1-Wire subsystem
  initLCD();                // Initialize LCD display
  initPushButtons();        // Initialize pushbuttons

  __enable_interrupt();     // Enable global interrupts

  while(1);
}

main 函数调用初始化函数并启用全局中断。程序本身是中断驱动的,因此它会处于 while 循环中,直到中断触发。

结论

MAXQ2000是一款高性能微控制器,具有许多有用的功能。温度驱动的风扇控制应用是使用MAXQ2000的PWM、1-Wire和SPI功能的一个很好的例子。当这些功能与按钮和LCD显示屏等交互式元素相结合时,可能的应用数量几乎是无限的。

审核编辑:郭婷


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分