文章
博客 网店

 AVR单片机制作射频遥控开关


一.智能电扇控制器


  炎热的夏天开着电扇睡觉是个不错的主意,但是半夜气温回落后从美梦中被吹醒是件糟糕的事情。下面这个小小的制作会提供您两个好办法,
  第一你可以用遥控器方便的关掉电扇,第二你还可以用温度来控制电扇的起停,温度下降后使电扇自动停止。
   功能:A键强制打开电扇,B键强制关闭电扇,D键记录当前的温度为参考控制温度,C进入温度控制模式
   温度控制模式下如果环境温度大于参考控制温度则打开电扇,如果环境温度降到参考控制温度-1℃则关闭风扇。测量温度分辨率(注意不是
  精度,这样的控制不需要太高的精度)0.1℃。
   有了这样一个控制器,在晚上睡觉前按一次D键记录当前温度为参考控制温度,再按一次C键进入温度控制模式,电扇打开,到半夜温度下降1℃
  时电扇自动被关闭,避免了被吹醒。



  从电子市场可以很容易的买到如上图所示的遥控组件,它的工作原理大体是这样的:遥控器内有一个编码芯片PT2262,当按一个键时它产生一组
 波形对应这个键的代码,这个波形被调制到315MHz的微波内之后从天线发送出去,而这个接收模块从315MHz的微波内解调出PT2262产生的波形。
 现在我要用单片机将此波形识别,之后执行相应的操作,这个被称为软件解码。


电路原理图


实物图


学生电扇

  温度传感器使用的是NTC(负温度系数)热敏电阻,它是很常用的测温元件,由于各厂家制作的参数不大相同,这里就不作介绍。关于PT2262在网
 络中介绍的文章很多,要注意的是它所产生的波形从时间上精度不是很大,不能按异步串行通信的思路去解码,另外接收到的波形根据现场环境的不
 同参杂着很多的噪声,为此软件解码PT2262波形对于初学者来讲绝对是一个挑战。
    下面是我在ATTINY26上实现的软件解码程序,ATTINY26使用了内部1MHz的时钟,PT2262每次按键时至少发送4次,不少网络上的文章建议连续接收
 2次再确认,但这个程序只需要接收一次即可。这个小小制作我已经使用了一年多了,至今没有发现误操作等异常现象。


//rf.c PT2262软件解码程序
#include
#include

#include"main.h"

#define KUAN_PLUS_CNT  16
#define ZHAI_PLUS_CNT 2

/*********************************
震荡电阻:Rosc=4.7MOhm
震荡频率:f=2*1000*16/Rosc = 6.81KHz
震荡周期:a=1/f=146.875us
位时间 = 32a = 4700us
窄脉冲时间=4a=587.5us  =7.3次中断
宽脉冲时间=12a=1762.5us =22次中断
同步码低电平宽度=128a-4a=124a=18212.5us = 227次中断
**********************************/

static volatile uint8_t g_bit;  //接收位计数器
static uint8_t g_begin_flag;    //开始标记
static uint8_t g_wait_state;    //等待电平状态
static uint8_t g_plus_code[24];  //脉冲宽度临时记录区
static uint8_t g_h_cnt,g_l_cnt;  //高低电平保持时间计数器
static uint8_t g_prev_h_cnt;    //一次扫描中高电平的计数个数

/*两个脉冲码对应的地址码表格,
两个窄脉冲g_mask_code[0][0]=0 两个宽脉冲g_mask_code[1][1]=1,
一窄一宽脉冲(悬空)g_mask_code[0][1]=2 一宽一窄脉冲非法数据=3*/
static uint8_t g_mask_code[2][2]={{0,2},{3,1}};

void recv_reset(void)
{
  g_bit=0;
  g_wait_state=1;
  g_h_cnt=0;
  g_l_cnt=0;
  g_begin_flag=0;
}

void RFInit(void)
{
  DDRB&=~_BV(PB6);
  PORTB&=~_BV(PB6);  
  
  TCCR0=_BV(CS00);  //不分频
  TCNT0=0;  
  TIMSK=_BV(TOIE0); 
  
  recv_reset();
}

//地址码接收定时处理
void recv_ad_bit(uint8_t lv)
{
    if(g_wait_state)  //正在计时检测高电平时间
    {
      if(lv)          //测到高电平
      {
        g_h_cnt++;
        if(g_h_cnt>KUAN_PLUS_CNT)  //高电平保持时间太长
          recv_reset();
        if(g_l_cnt>0)//有过一次低电平噪声
        {
          g_h_cnt++;
          g_l_cnt=0;
        }
      }
      else                //出现了低电平 
      {
        if(g_h_cnt < ZHAI_PLUS_CNT) //高电平保持时间不够长
        { recv_reset(); return ; }
        if(g_l_cnt++ > 0)//低电平保持两个周期认为确实检测到低电平
        {  
          g_wait_state=0;    //进入检测低电平状态
          g_prev_h_cnt=g_h_cnt;
          g_h_cnt=0;
        }
      }
    }
    else              //正在计时检测低电平时间
    {
      if(lv)          //出现了高电平
      {
        if(g_l_cnt0)//高电平保持两个周期认为确实检测到高电平
        {
          g_plus_code[g_bit]=(g_prev_h_cnt>8) ? 1:0;  //保存脉冲宽度
          g_wait_state=1;      //进入检测高电平状态  
          g_bit++;
          g_l_cnt=0;
        }
      }
      else             //低电平计数
      {
        g_l_cnt++;
        if(g_l_cnt > KUAN_PLUS_CNT) //低电平保持时间太长
          recv_reset();
        if(g_h_cnt>0)//有过一次高电平噪声
        {
          g_l_cnt++;
          g_h_cnt=0;
        }
      }
    }
}

//同步码接收定时处理
void recv_syn_bit(uint8_t lv)
{
    if(g_wait_state)  //正在计时高电平时间
    {
      if(lv)
      {
        g_h_cnt++;
        if(g_h_cnt>15)
          recv_reset();
      }
      else
      {
        if(g_h_cnt < 2)
        {
          recv_reset();
          return ;
        }
        g_wait_state=0;
      }
    }
    else //正在计时低电平时间
    {
      if(lv)
      {
        if(g_l_cnt<70)
        {
          recv_reset(); return ;
        }
        g_wait_state=1;
        g_bit++;
      }
      else
      {
        g_l_cnt++;
        if(g_l_cnt > 100) //低电平保持时间满足要求
          g_bit++; 
      }
    }  
}

//约80us中断一次
ISR(TIMER0_OVF0_vect)
{
  uint8_t lv=0;
  
  TCNT0=175;  //255-80
  
  if(PINB&_BV(PB6))
    lv=1;

  if(g_bit > 24) //接收完一次并没有处理完
    return ;
  
  if((lv==0)&&(g_begin_flag==0))//接收前低电平
    return ;
  g_begin_flag=1;
  
  if(g_bit==24)        //正在接收同步位
    recv_syn_bit(lv);  
  else                //正在接收地址位
    recv_ad_bit(lv);
}

uint8_t RfRecv(uint16_t *addr,uint8_t *key)
{
  uint8_t i,tmp;
  uint32_t ret=0;
  
  if(g_bit<25)
   return 0;
  for(i=0;i<24;i+=2)
  {
    ret<<=2;
    tmp=g_mask_code[g_plus_code[i]][g_plus_code[i+1]];
    ret+=tmp;
    if(tmp>2)   //有非法脉冲码
    {
      recv_reset();
      return 0;
    }
  }
  *key=ret;
  *addr=ret>>8;
  recv_reset();
  
  return 1;
}


主程序文件


uint8_t g_Mode;//当前模式
uint16_t g_ConfigTmp;//设定温度
uint8_t EEMEM eep_Mode;
uint16_t EEMEM eep_ConfigTemp;
uint16_t g_CurTemp;//当前温度

#define MODE_MANUAL    1      //手动控制模式
#define MODE_TEMP      0      //温度控制模式

/*
  地址码:0xaa46
  a键:0x40
  b键:0x10
  c键:0x04
  d键:0x01
*/

void RFInit(void);
uint8_t RfRecv(uint16_t *addr,uint8_t *key);
void AdcInit(void);
uint8_t AdConvert(uint16_t *val);

void DelayMs(uint16_t t)
{
  uint16_t i;
  for(i=0;i=g_ConfigTmp)
  {
    RELAY_OPEN;
    LED_Y_SET;  
  }
  else
  {
    RELAY_CLOSE;
    LED_Y_CLR;
  }  
}

int main(void)
{
  uint8_t key;
  uint16_t addr;
  
  RELAY_INIT;
  LED_PORT_INIT;
  LED_G_SET;
  LED_Y_CLR;
  
  RFInit();
  AdcInit();
  
  while(AdConvert(&g_CurTemp)==0);//转换完一次
  while(AdConvert(&g_CurTemp)==0);//转换完一次
  
  //装入掉电保持数据
  g_Mode=eeprom_read_byte(&eep_Mode);
  g_ConfigTmp=eeprom_read_word(&eep_ConfigTemp);
  if(g_Mode!=MODE_TEMP)
  {
    RELAY_OPEN;
    LED_Y_SET;    
  }
  else
    EnterTempControlMode();
  
  TCCR1A=0;
  TCCR1B=_BV(CS10)|_BV(CS11)|_BV(CS12)|_BV(CS13);//CK/16384
  
  sei();

  while(1)
  {
    if(RfRecv(&addr,&key))
    {
      if(addr==0x46aa)
      {
        cli();
        switch(key)
        {
          case 0x40: //A 强制打开
            RELAY_OPEN;
            LED_Y_SET;
            LED_G_SET;
            save_mode(MODE_MANUAL);
            break;
          case 0x10://B  强制关闭
            RELAY_CLOSE;
            LED_Y_CLR;
            LED_G_SET;
            save_mode(MODE_MANUAL);
            break;
          case 0x04://C 温度控制模式
            if(g_Mode!=MODE_TEMP)
              EnterTempControlMode();
            save_mode(MODE_TEMP);
            break;
          case 0x01://D 设定当前温度
            if(g_Mode == MODE_TEMP)
            {
              g_ConfigTmp=g_CurTemp;
              eeprom_busy_wait();
              eeprom_write_word(&eep_ConfigTemp,g_CurTemp);
              for(key=0;key<4;key++)
              {
                LED_Y_FLASH;
                DelayMs(200);
              }
            }
            break;
          default:
            break;
          }
          sei();
         }
       }
       if(AdConvert(&g_CurTemp))
       {
         if(g_Mode==MODE_TEMP)
         {
         if(g_CurTemp>=g_ConfigTmp)
         {
            RELAY_OPEN;
            LED_Y_SET;  
         }
         if(g_CurTemp <= (g_ConfigTmp-10))
         {
            RELAY_CLOSE;
            LED_Y_CLR;
         }
         if(TCNT1 > 15)
         {
           TCNT1=0;
           LED_G_FLASH;
         }
      }
    }
  }//while
}

//温度检测程序略!!!


二.家用水泵遥控开关



 实际上接收电路增加一个PT2272这个解码芯片很容易就可以实现按键信号的接收,这便是硬件解码,PT2272是和PT2262是配对使用的。尽管
 它们用起来非常简单, 甚至不需要使用单片机,但用处却非常的广泛。下面这个示例是为东北农村仍没有通自来水的地区使用而设计,在那里
 每家每户都有自己的井, 使用的都是220V潜水泵,那些刀闸开关让一部分没有相关经验的人(如妇女儿童)操作时存在着安全隐患,而这个小
 小制作恰好解决了这个问题。


原理图

 为了晚上打水方便,增加了两路照明灯控制,但实践证明BT139这个可控硅在灯泡栈凳弊苁且黄鸹档簦刮易芙岢鲆桓鼍椋赫彰鞯频目刂凭×渴褂�
 电磁继电器,而电机等负载可以使用双向可控硅控制。


遥控发射器与接收模块


实物图

程序如下:

/*
   文件名:main.c
   功能:控制家用水泵程序
   MCU:AT90S2313-10PI
   编译:WINAVR20050214

   作者:芯艺

   日期:2005-05-04
*/
/********************************
   操作说明:
   按A键:水泵开
   按D键:水泵关
   按B键:控制灯开或关
   按C键:控制灯开或关
********************************/

#include
#include
#include

#define uchar unsigned char
#define uint unsigned int

#define A 1
#define B 2
#define C 3
#define D 4
#define N 0

#define WATER_PUMP PD2
#define LIGHT_1 PD6
#define LIGHT_2 PD3

uchar g_bFlagB=0,g_bFlagC=0;

#define SET_RED_LED PORTD|=_BV(PD5)
#define CLR_RED_LED PORTD&=~_BV(PD5)

#define SET_YEL_LED PORTD|=_BV(PD4)
#define CLR_YEL_LED PORTD&=~_BV(PD4)

void IoInit(void)
{
  DDRB=0x0;
  PORTD=_BV(LIGHT_1)|_BV(LIGHT_2);
  DDRD=_BV(PD6)|_BV(PD3)|_BV(PD2)|_BV(PD5)|_BV(PD4);
}

void DelayMs(uint ms)
{
  uint i;
  for(i=0;i   {
   _delay_loop_2(250 * 4);
   wdt_reset();
  }
}

uchar ScanKey(void)

 uchar tmp,Ret;
 tmp=PINB;
 if(tmp &_BV(PB1))
 Ret=C;
 else if(tmp & _BV(PB2))
   Ret=D;
 else if(tmp & _BV(PB3))
   Ret=B;
 else if(tmp & _BV(PB4))
   Ret=A;
 else 
   Ret=N;
 return Ret;
}

uchar WaitKey(void)
{
   uchar Ret;
   while(1)
   {
     Ret=ScanKey();
     if(Ret==N)
       DelayMs(1);
     else
     {
        //延时消抖
        DelayMs(10);
        if(ScanKey()!=Ret)
          continue;

        while(ScanKey() == Ret) //等待弹出
        DelayMs(1);
        break;
     }
   }
   return Ret;
}

int main(void)
{
   uchar i;

   SET_RED_LED; 
   DelayMs(1000);
   CLR_RED_LED;

   IoInit();
   wdt_enable(WDTO_1S);

   while(1)
   {
     i=WaitKey();
     SET_YEL_LED;
     switch(i)
     {
       case A: //开水泵
         PORTD|=_BV(WATER_PUMP);
         break;
       case D: //关水泵
         PORTD&=~_BV(WATER_PUMP);
         break;
       case B: //灯2开关,远处照明灯
         if(g_bFlagB)
         {
         CLR_RED_LED; 
         PORTD|=_BV(LIGHT_2);
         }
         else
         {
         SET_RED_LED;
         PORTD&=~_BV(LIGHT_2);
         }
         g_bFlagB=!g_bFlagB; 
         break;
       case C: //灯1开关,控制盒上的灯
         if(g_bFlagC)
            PORTD|=_BV(LIGHT_1);
         else
            PORTD&=~_BV(LIGHT_1);
         g_bFlagC=!g_bFlagC; 
         break;
       default:
         break;
      }//switch
      DelayMs(300);
      CLR_YEL_LED;
   }//main loop
}//main



 2009.06.23


芯艺工作室    蒙ICP备06005492号
Copyright© 2004-2023 ChipArt Studio All Rights Reserved