一.智能电扇控制器
炎热的夏天开着电扇睡觉是个不错的主意,但是半夜气温回落后从美梦中被吹醒是件糟糕的事情。下面这个小小的制作会提供您两个好办法,
第一你可以用遥控器方便的关掉电扇,第二你还可以用温度来控制电扇的起停,温度下降后使电扇自动停止。
功能: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
|
|