孤独的蛇 发表于 2016-7-25 00:58:55

【MEGA64+ESP8266】之智能家居

本帖最后由 Genius 于 2016-8-25 14:51 编辑

就是辣么棒!一个app就能控制所有家电、家居。
原创**,转载请注明来自:http://club.gizwits.com/thread-2939-1-1.html
作者:孤独的蛇
【开源硬件】机智云智能硬件创新大赛http://club.gizwits.com/thread-2646-1-1.html
看到这个活动,心里痒痒的,尝试参加一下。左等右等申请的GOKIT3还没到,手头正好有一块N年前自己焊接的MEGA64最小系统板和一块ESP8266-01,索性就用这两个设备来设计一个智能家居小系统。整体还在构思中,待构思完毕后再制作过程发布。



   通过几天的考虑,为本次设计的智能家居系统做出如下的设计:
1、通过手机APP等移动设备,连接互联网机智云,通过ESP8266进行互联网通信,获取控制命令,并将命令通过RS232串口通信传给MCU:MEGA64,最终由MEGA64对具体的设备进行控制;
2、本次智能家居可进行操作或获取信息的内容主要为:
    a、智能开关:可以通过移动客户端远程对智能开关进行控制开关的通电与断电的操作。由此可衍伸出很多电器设备的控制,只要控制电源的开和关的,都可以通过智能开关进行控制。
    b、智能调光器:可以通过移动客户端,控制各电灯的亮暗,也可以通过环境亮度来进行自动控制。
    c、智能插座:和智能开关类似,只不过这个控制的是插座的通电和断电。
    d、智能窗帘:可以对窗帘的打开和关闭进行操作,也可以设定窗帘打开的多少,从而满足亮度的需要。
    e、智能自动收衣服:通过雨滴传感器传来的数据,判断现在是否下雨来对衣架的回收或伸展,保证衣服的干燥。
    f、智能风扇:可以控制风扇的转速。
    g、智能心情灯:可根据需要对RGB灯的三种颜色进行调节,从而控制灯的颜色,随心而变,增加氛围。
    h、智能音乐播放器:可以进行音乐的播放、暂停、上一曲、下一曲、随机播放、循环播放灯操作。
    i、智能安防窗户打开报警器:可进行安防监控,如果窗户被打开了,则会远程发送报警通知。
    j、智能红外解码转发器:可对各种电器的遥控器进行解码,并存储,然后通过远程控制发出相应的红外遥控信号进行控制电器,相当于遥控器。
    k、火焰防火灾报警器:监测家里的环境是否有着火现象,如有则进行消息通知。
    l、可燃气体报警器:监测环境中是否含有可燃气体,并达到一定的浓度后进行消息通知。
    m、自动浇花器:通过检测花盆中土壤的湿度进行控制是否对花浇水。
    n、温度、湿度表:通过检测环境的温度和湿度,并上报到移动客户端显示。


以上是对本次智能家居的任务设计方案,后面则对方案进行实际的选材设计与制作。
由于实际情况的限制,本次设计的一些内容,不是实际的220V交流电的操作,而是通过模拟的方式用5V直流电进行操作。其模拟的方法和220V交流电的实际操作方法是一致的,只是有些地方需要进行电路的设计,在后期的制作过程中也会给出这些设计。【MEGA64+ESP8266】之智能家居系统结构图







孤独的蛇 发表于 2016-8-24 17:02:18

本帖最后由 孤独的蛇 于 2016-8-24 17:08 编辑

前段时间一直跑医 院,导 致没有什么时间和心 思在上面,加上没有足够的工具,所以本次的设计只能到这里了,只是一个框架,具体的应用还是要靠自己设计。
手机APP现在没有时间去研究了,毕竟不是这方面的人。。。。只能将就使用demoAPP来进行控制了。。。。

手机APP连接设备的视频,为什么是倒立的???
http://v.youku.com/v_show/id_XMTY5NzkwMzk5Ng==.html

远程控制LED灯开关:
http://v.youku.com/v_show/id_XMTY5NzkwNzEyMA==.html

远程插座控制:这里使用LED灯来模拟开关
http://v.youku.com/v_show/id_XMTY5NzkwNzg3Mg==.html

RGB灯的控制:
http://v.youku.com/v_show/id_XMTY5NzkwOTI0MA==.html

音乐的控制:
http://v.youku.com/v_show/id_XMTY5NzkxMTM2NA==.html

直流马达的控制:
http://v.youku.com/v_show/id_XMTY5NzkxMDAwMA==.html

各种传感器:
http://v.youku.com/v_show/id_XMTY5NzkxMDcyMA==.html

遥控器的学习与控制:
http://v.youku.com/v_show/id_XMTY5NzkxMjAwNA==.html

至此,本次设计的所有东西完毕,对于手机APP,只能等到以后有时间的时候再进行研究了,弄好后我也会在这里附上的

孤独的蛇 发表于 2016-8-24 17:40:24

[ 项目名称 ] MEGA64+ESP8266之智能家居
[ 项目概述 ]本项目使用最平常的单片机和常见的wifi模块制作一个智能家居的简单模型,通过机智云联网进行控制操作。
[ 硬件准备 ]
1、MEGA64最小系统板
2、ESP8266wifi模块
3、RGB三色灯,LED灯数个
4、音乐播放器(WTV020-SD语音模块)
5、直流马达及驱动
6、火焰传感器、雨滴传感器、土壤湿度传感器、光传感器、可燃气体传感器、温湿度传感器
7、红外接收一体芯片B18838和红外发射二极管
8、限位开关、编码器等
[ 使用软件环境 ] AvrStudio编写C语音程序访问密码 2eff
[ 相关源码 ]MEGA64_ESP8266_GOKIT.c访问密码 2889、M64_BIT_OPERATION.H访问密码 c82d
[ 项目介绍 ]

各种资料集合:https://yunpan.cn/cMuJYXrUqqKZy访问密码 787e
























































孤独的蛇 发表于 2016-7-28 23:21:27


    通过几天的考虑,为本次设计的智能家居系统做出如下的设计:
1、通过手机APP等移动设备,连接互联网机智云,通过ESP8266进行互联网通信,获取控制命令,并将命令通过RS232串口通信传给MCU:MEGA64,最终由MEGA64对具体的设备进行控制;
2、本次智能家居可进行操作或获取信息的内容主要为:
    a、智能开关:可以通过移动客户端远程对智能开关进行控制开关的通电与断电的操作。由此可衍伸出很多电器设备的控制,只要控制电源的开和关的,都可以通过智能开关进行控制。
    b、智能调光器:可以通过移动客户端,控制各电灯的亮暗,也可以通过环境亮度来进行自动控制。
    c、智能插座:和智能开关类似,只不过这个控制的是插座的通电和断电。
    d、智能窗帘:可以对窗帘的打开和关闭进行操作,也可以设定窗帘打开的多少,从而满足亮度的需要。
    e、智能自动收衣服:通过雨滴传感器传来的数据,判断现在是否下雨来对衣架的回收或伸展,保证衣服的干燥。
    f、智能风扇:可以控制风扇的转速。
    g、智能心情灯:可根据需要对RGB灯的三种颜色进行调节,从而控制灯的颜色,随心而变,增加氛围。
    h、智能音乐播放器:可以进行音乐的播放、暂停、上一曲、下一曲、随机播放、循环播放灯操作。
    i、智能安防窗户打开报警器:可进行安防监控,如果窗户被打开了,则会远程发送报警通知。
    j、智能红外解码转发器:可对各种电器的遥控器进行解码,并存储,然后通过远程控制发出相应的红外遥控信号进行控制电器,相当于遥控器。
    k、火焰防火灾报警器:监测家里的环境是否有着火现象,如有则进行消息通知。
    l、可燃气体报警器:监测环境中是否含有可燃气体,并达到一定的浓度后进行消息通知。
    m、自动浇花器:通过检测花盆中土壤的湿度进行控制是否对花浇水。
    n、温度、湿度表:通过检测环境的温度和湿度,并上报到移动客户端显示。


以上是对本次智能家居的任务设计方案,后面则对方案进行实际的选材设计与制作。
由于实际情况的限制,本次设计的一些内容,不是实际的220V交流电的操作,而是通过模拟的方式用5V直流电进行操作。其模拟的方法和220V交流电的实际操作方法是一致的,只是有些地方需要进行电路的设计,在后期的制作过程中也会给出这些设计。


孤独的蛇 发表于 2016-8-17 16:38:02

【三、智能插座】的设计
智能插座和智能开关的原理是一样的,这里就不再阐述。

孤独的蛇 发表于 2016-8-17 17:34:11

【五、音乐MP3控制】
这里使用的是WTV020-SD语音模块(因为手上就有这个模块了。。。有好的模块可以替换掉)
1、产品特征
Ø产品支持外挂最大1G容量的SD卡;Ø支持播放4Bit ADPCM格式文件;Ø自动识别语音文件;Ø可装载6KHz~32KHz、36KHz采样率AD4音频;Ø可装载6KHz~16KHz采样率WAV音频;Ø16bitDAC及PWM音频输出;Ø最多可存放512段语音;ØWTV020-SD-20S,WTV020-SD-16P两种模块类型; Ø支持微型处理器和按键控制;Ø可以调用任意段落的语音进行播放;Ø掉电保存操作数据功能;Ø加载语音无需软件辅助,直接放置语音到SD卡便可;Ø支持文件组合播放,包括静音组合;Ø工作电压:DC2.5~3.6V;Ø静态电流:16uA(不插SD卡)
2、产品概述WTV020-SD模块是一款可重复擦写语音内容的大容量存储类型的语音模块,可外挂最大容量为1GB的SD卡存储器。能加载WAV格式语音和AD4格式语音。WTV020-SD模块以WTV020SD-20S语音芯片为主控核心,具有MP3控制模式,按键一对一控制模式(3段语音跟5段语音两种),上电循环播放控制模式以及二线串口控制模式。控制模式是在芯片制样时设置的,在操作过程中不能切换各种控制模式,如需要使用哪种模式进行控制,可向我司订做。MP3控制模式:具有播放/停止,下一曲,上一曲,音量+,音量-等功能。按键一对一控制模式(3段语音):一个按键对应触发一个语音,具备播放3段语音及调节音量加减的功能,所有按键被默认为脉冲不可重复触发。按键一对一控制模式(5段语音):具有三种控制方式,⑴、所有按键均为脉冲可重复触发;⑵、所有按键均为播放/停止触发(单曲不循环);⑶、所有按键均为播放/停止(单曲可循环)。上电循环播放控制模式:上电后,不需要触发任何I/O口,直接自动播放SD卡存储器内的所有语音,并拥有断电记忆点播放功能,当断电后再上电,自动从上次的断电处继续播放语音。具有两种控制方式,⑴、P04拥有脉冲播放/暂停功能;⑵、P05拥有电平播放/暂停功能。二线串口控制模式:由单片机通过CLK时钟和DI数据线发送数据对WTV020-SD模块进行控制。可随意播放任何一个地址的语音。此状态下,能进行语音组合播放。语音内容更新直接通过SD卡读卡器在PC上更换。该模块支持FAT文件系统。支持6KHz~32KHz、36KHz采样率的AD4语音和6KHz~16KHz采样率的WAV音频,能自动识别语音采样率以及语音文件格式。
点击这里可以下载此文档手册 (访问密码 8861 )

//音乐设置
#define STOP 0xffff        //停止播放
#define PLAY 0xfffe        //播放
#define PAUSE 0xfffe        //暂停
#define SINGLE_CYCLE 0xfffd        //单曲循环
#define STOP_CYCLE 0xfffc        //停止循环
#define ALL_CYCLE 0xfffb        //所有循环
#define REST PORTG_2
#define DI PORTG_1
#define CLK PORTG_0
volatile unsigned int voice_buf = {0xfff0,0xfff1,0xfff2,0xfff3,0xfff4,0xfff5,0xfff6,0xfff7};        //音量 0xf0为静音0xf7 为音量最大
volatile unsigned char voice;
volatile unsigned int song_number;void init_fun()        //初始化模块
{
        voice = 1;        //初始音量设置为3,        0为最小,即静音;        7为最高
   
    song_number = 0;        //第1首歌曲
    REST = 1;
    DI = 1;
    CLK = 1;
    reset_fun();
    delay_ms(300);
    sent_data(voice_buf);
    sent_data(ALL_CYCLE);
}

void reset_fun()        //复位模块
{
        REST = 0;
        delay_ms(6);
        REST = 1;
        delay_ms(6);
}

void sent_data(uint dat)        //发送数据
{
        unsigned int i;
        uint j;
        for(i=0;i<16;i++)
        {
                CLK = 0;
                j = (dat >> (15 - i)) & 0x01;
                DI = j;
                delay_ms(2);
                CLK = 1;
                delay_ms(2);
        }
        DI = 1;
        CLK = 1;
}
void next_song()        //下一曲
{
        song_number++;
        if(song_number > 0x1ff) song_number = 0x00;
        sent_data(song_number);
}
void pre_song()        //上一曲
{
        if(song_number == 0x00)
        {
                song_number = 0x200;
        }
        song_number--;
        sent_data(song_number);
}
void voice_up()        //音量加
{
        voice++;
        if(voice >= 7) voice = 7;
        sent_data(voice_buf);
}

void voice_down()        //音量减
{
        if(voice <= 0) voice = 1;
        voice--;
        sent_data(voice_buf);
}
void play_song(int song_num)
{
song_number = song_num;
if(song_number > 0x1ff) song_number = 0x00;
sent_data(PLAY);
sent_data(song_number);
}


串口数据处理部分程序
case 0x20://设置musicok
                                                                temp1 = ((status1 & 0b11100000) >> 5);
                                                                status = status1;
                                                                status = status1;
                                                                temp2 = (((status1 & 0b00000001) << 3) | temp1);
                                                                if(temp2 != 0)
                                                                {
                                                                        switch(temp2)
                                                                        {
                                                                                case 0x01://播放/暂停
                                                                                        sent_data(PLAY);break;
                                                                                case 0x02://上一曲
                                                                                        pre_song();break;
                                                                                case 0x03://下一曲
                                                                                        next_song();break;
                                                                                case 0x04://音量+
                                                                                        voice_up();break;
                                                                                case 0x05://音量-
                                                                                        voice_down();break;
                                                                                case 0x06://停止播放
                                                                                        sent_data(STOP);break;
                                                                                case 0x07://单曲循环
                                                                                        sent_data(SINGLE_CYCLE);break;
                                                                                case 0x08://所有循环
                                                                                        sent_data(ALL_CYCLE);break;
                                                                                case 0x09://停止循环
                                                                                        sent_data(STOP_CYCLE);break;
                                                                                case 0x0a://随机播放一首歌
                                                                                        sent_data(random_fun(0,512));
                                                                                        break;//暂时不设置
                                                                        }
                                                                        status = (status1 & 0b00011111);
                                                                        status = (status1 & 0b11111110);
                                                                }
                                                                break;主函数中音乐相关处理程序:
//音乐处理
                if(command_music!=0)
                {
                        switch(command_music)
                        {
                                case 0x00:break;//无动作
                                case 0x01://播放/暂停
                                sent_data(PLAY);
                                break;
                                case 0x02://上一曲
                                pre_song();
                                break;
                                case 0x03://下一曲
                                next_song();
                                break;
                                case 0x04://音量加
                                        voice_up();
                                        break;
                                case 0x05://音量减
                                        voice_down();
                                        break;
                                case 0x06://停止
                                        sent_data(STOP);
                                        break;
                                case 0x07://单曲循环
                                        sent_data(SINGLE_CYCLE);
                                        break;
                                case 0x08://所有循环
                                        sent_data(ALL_CYCLE);
                                        break;
                                case 0x09://停止循环
                                        sent_data(STOP_CYCLE);
                                        break;
                                case 0x0a://随机播放一首歌曲
                                        play_song(random_fun(0,512));
                                        break;
                        }
                command_music = 0;
                }





Genius 发表于 2016-7-25 01:07:36

周一我在问问

孤独的蛇 发表于 2016-7-25 10:11:55

Genius 发表于 2016-7-25 01:07
周一我在问问

好的,谢谢!:)

孤独的蛇 发表于 2016-8-8 22:27:26

本次设计使用的单片机型号是MEGA64,首先来了解一下它的特性吧!(以下内容是copy于数据文档手册)
产品特性
• 高性能、低功耗的 8 位AVR® 微处理器
• 先进的RISC 结构
– 130 条指令 – 大多数指令执行时间为单个时钟周期
– 32个8 位通用工作寄存器
– 全静态工作
– 工作于16 MHz 时性能高达16 MIPS
– 只需两个时钟周期的硬件乘法器
• 非易失性程序和数据存储器
– 64K 字节的系统内可编程 Flash
擦写寿命: 10,000 次
– 具有独立锁定位的可选Boot 代码区
通过片上Boot 程序实现系统内编程
真正的同时读写操作
– 2K字节的EEPROM
擦写寿命: 100,000 次
– 4K字节片内SRAM
– 64K 字节可选外部存储空间
– 可以对锁定位进行编程以实现用户程序的加密
– 通过SPI 接口进行系统内编程
• JTAG 接口( 与IEEE 1149.1 标准兼容)
– 符合JTAG 标准的边界扫描功能
– 支持扩展的片内调试功能
– 通过JTAG 接口实现对Flash、EEPROM、熔丝位和锁定位的编程
• 外设特点
– 两个具有独立预分频器和比较器功能的8 位定时器/ 计数器
– 两个具有预分频器、比较功能和捕捉功能的扩展16 位定时器/ 计数器
– 具有独立振荡器的实时计数器RTC
– 两路8 位PWM 通道
– 6路编程分辨率从1 到 16 位可变的PWM 通道
– 8路10 位ADC
8 个单端通道
7 个差分通道
2 个具有可编程增益(1x, 10x, 或200x)的差分通道
– 面向字节的两线接口
– 可编程的串行USART
– 可工作于主机/ 从机模式的SPI 串行接口
– 具有独立片内振荡器的可编程看门狗定时器
– 片内模拟比较器
• 特殊的处理器特点
– 上电复位以及可编程的掉电检测
– 片内经过标定的RC 振荡器
– 片内/ 片外中断源
– 6种睡眠模式: 空闲模式、ADC 噪声抑制模式、省电模式、掉电模式、Standby 模式以及
扩展的Standby 模式
– 软件选择时钟频率
– 熔丝位选择的ATmega103 兼容模式
– 全局上拉禁止
• I/O 和封装
– 53个可编程的I/O 口
– 64引脚TQFP 封装, 与 64 引脚MLF 封装
• 工作电压
– ATmega64L:2.7 - 5.5V
– ATmega64:4.5 - 5.5V
• 速度等级
– ATmega64L:0 - 8 MHz

– ATmega64:0 - 16 MHz





本次设计整体IO引脚使用情况如下:


后面的发文,将会一个一个的进行阐述,并附上源代码。

孤独的蛇 发表于 2016-8-8 22:57:18

一、数据点的设置

本次设计中暂时使用35个数据点进行监测,分别是:

[*]名称:LED灯开关1标识名:LED1读写类型:可写类型:布尔值






[*]名称:LED灯开关2标识名:LED2读写类型:可写类型:布尔值




[*]名称:LED灯开关3标识名:LED3读写类型:可写类型:布尔值




[*]名称:LED灯开关4标识名:LED4读写类型:可写类型:布尔值




[*]名称:插座开关1标识名:JACK1读写类型:可写类型:布尔值




[*]名称:插座开关2标识名:JACK2读写类型:可写类型:布尔值




[*]名称:插座开关3标识名:JACK3读写类型:可写类型:布尔值




[*]名称:插座开关4标识名:JACK4读写类型:可写类型:布尔值




[*]名称:空气温度标识名:temperature读写类型:只读类型:数值
数据范围:0 - 50分辨率:1增量:0




[*]名称:空气湿度标识名:humidity读写类型:只读类型:数值
数据范围:20 - 90分辨率:1增量:20




[*]名称:红光亮度标识名:R读写类型:可写类型:数值
数据范围:0 - 255分辨率:1增量:0




[*]名称:绿光亮度标识名:G读写类型:可写类型:数值
数据范围:0 - 255分辨率:1增量:0




[*]名称:蓝光亮度标识名:B读写类型:可写类型:数值
数据范围:0 - 255分辨率:1增量:0




[*]名称:PWM1标识名:PWM1读写类型:可写类型:数值
数据范围:0 - 255分辨率:1增量:0




[*]名称:PWM2标识名:PWM2读写类型:可写类型:数值
数据范围:0 - 255分辨率:1增量:0




[*]名称:马达转动方向标识名:motodir读写类型:可写类型:布尔值




[*]名称:马达转动速度标识名:motospeed读写类型:可写类型:数值
数据范围:0 - 255分辨率:1增量:0




[*]名称:火焰传感器值标识名:fireval读写类型:只读类型:数值
数据范围:0 - 1023分辨率:1增量:0




[*]名称:可燃气体传感器值标识名:gasval读写类型:只读类型:数值
数据范围:0 - 1023分辨率:1增量:0




[*]名称:雨滴传感器值标识名:rainval读写类型:只读类型:数值
数据范围:0 - 1023分辨率:1增量:0
备注: 雨滴传感器数值




[*]名称:土壤湿度传感器值标识名:soilval读写类型:只读类型:数值
数据范围:0 - 1023分辨率:1增量:0




[*]名称:光传感器值标识名:lightval读写类型:只读类型:数值
数据范围:0 - 1023分辨率:1增量:0
备注: 光传感器值




[*]名称:窗帘打开位置标识名:curtainpos读写类型:可写类型:数值
数据范围:0 - 100分辨率:1增量:0




[*]名称:自动收衣位置标识名:dresspos读写类型:可写类型:数值
数据范围:0 - 100分辨率:1增量:0




[*]名称:窗户是否被打开标识名:windowsopen读写类型:报警类型:布尔值




名称:电视遥控器1标识名:TVremote1读写类型:可写类型:枚举
枚举范围:0.无操作 1.开机/关机 2.频道+ 3.频道- 4.音量+ 5.音量- 6.静音 7.数字0 8.数字1 9.数字2 10.数字3 11.数字4 12.数字5 13.数字6 14.数字7 15.数字8 16.数字9 17.-/-- 18.返回 19.菜单 20.↑ 21.↓ 22.← 23.→ 24.确认 25.伴音 26.睡眠 27.制式 28.信号源 29.画中画

[*]

[*]名称:电视遥控2标识名:TVremote2读写类型:可写类型:枚举
枚举范围:0.无操作 1.开机/关机 2.频道+ 3.频道- 4.音量+ 5.音量- 6.静音 7.数字0 8.数字1 9.数字2 10.数字3 11.数字4 12.数字5 13.数字6 14.数字7 15.数字8 16.数字9 17.-/-- 18.返回 19.菜单 20.↑ 21.↓ 22.← 23.→ 24.确认 25.伴音 26.睡眠 27.制式 28.信号源 29.画中画




[*]名称:自动亮度控制标识名:autolight1读写类型:可写类型:布尔值




[*]名称:音乐标识名:music读写类型:可写类型:枚举
枚举范围:0.无操作 1.播放/暂停 2.上一曲 3.下一曲 4.音量加 5.音量减 6.停止 7.单曲循环 8.所有循环 9.停止循环 10.随机播放一首歌曲




[*]名称:是否监测窗户标识名:chuanghujiance读写类型:可写类型:布尔值




[*]名称:电视遥控器3标识名:TVremote3读写类型:可写类型:枚举
枚举范围:0.无操作 1.开机/关机 2.频道+ 3.频道- 4.音量+ 5.音量- 6.静音 7.数字0 8.数字1 9.数字2 10.数字3 11.数字4 12.数字5 13.数字6 14.数字7 15.数字8 16.数字9 17.-/-- 18.返回 19.菜单 20.↑ 21.↓ 22.← 23.→ 24.确认 25.伴音 26.睡眠 27.制式 28.信号源 29.画中画




[*]名称:空调遥控器1标识名:KTremote1读写类型:可写类型:枚举
枚举范围:0.无操作 1.开机/关机 2.模式 3.温度+ 4.温度- 5.健康模式 6.睡眠模式 7.保留1 8.保留2 9.保留3 10.保留4
备注: 空调遥控




[*]名称:空调遥控器2标识名:KTremote2读写类型:可写类型:枚举
枚举范围:




[*]名称:空调遥控器3标识名:KTremote3读写类型:可写类型:枚举
枚举范围:




[*]名称:设置遥控器标识名:remotenum读写类型:可写类型:枚举
枚举范围:0.无操作 1.退出遥控学习 2.电视遥控器1 3.电视遥控器2 4.电视遥控器3 5.空调遥控器1 6.空调遥控器2 7.空调遥控器3














孤独的蛇 发表于 2016-8-8 23:07:06

二、【智能开关】设计

本帖最后由 孤独的蛇 于 2016-8-8 23:31 编辑

首先,智能开关的主要功能是控制用电器的通电与断电,可以通过MCU对继电器进行的操作进行控制。
这里使用到的是MEGA64的PA4/PA5/PA6/PA7口进行4路控制。
欲使用MCU的IO口,首先要对IO口进行配置,这里需要配置成输出模式:
DDRA |= 0b00001111;//将PA4-PA7设置为输出模式
PORTA &= 0b11110000;//将PA4-PA7设置为低电平当MCU上电之后,将PA4-PA7设置为输出模式,并将对应的IO口进行拉低处理,因为这里设计的智能开关的控制是高电平控制的,所有当MCU上电之后进行拉低,防止上电时开关就打开。对于如何控制IO口的高低电平则非常简单:
PORTA |= _BV(4);//让PA4输出高电平
PORTA &=~_BV(4);//让PA4输出低电平当收到远程发送过来的命令时,就可以对智能开关进行控制了。
下面代码中的attr_flags4是“是否设置标志位”中的一字节数据,查看相应的通信协议。
status1数组是存放串口发送过来的数据,status数组则是整个系统中的状态数组。LED1、2、3、4等是智能开关的引脚,CHAZUO1 、2、3、4是智能插座的引脚。
switch(attr_flags4)
{
      case 0x01://设置LED1
                if(status1==0x01)
                {
                        LED1 = 1;
                         temp1= status;
                         status = (temp1 | 0b00000001);
               }
               else
                  {
                           LED1 = 0;
                           temp1= status;
                           status = (temp1 & 0b11111110);
                  }
                   break;
         case 0x02://设置LED2
               if(status1==0x02)
               {
                     LED2 = 1;
                     temp1= status;
                     status = (temp1 | 0b00000010);
                }
                else
                {
                         LED2 = 0;
                         temp1= status;
                         status = (temp1 & 0b11111101);
               }
               break;
         case 0x04://设置LED3
               if(status1==0x04)
               {
                        LED3 = 1;
                        temp1= status;
                        status = (temp1 | 0b00000100);
                   }
                   else
                   {
                            LED3 = 0;
                            temp1= status;
                            status = (temp1 & 0b11111011);
                  }
                  break;
            case 0x08://设置LED4
                  if(status1==0x08)
                  {
                           LED4 = 1;
                           temp1= status;
                           status = (temp1 | 0b00001000);
                      }
                      else
                     {
                              LED4 = 0;
                              temp1= status;
                              status = (temp1 & 0b11110111);
                      }
                      break;
            case 0x10://设置JACK1
                      if(status1==0x10)
                      {
                              CHAZUO1 = 1;
                                 temp1= status;
                                 status = (temp1 | 0b00010000);
                        }
                        else
                        {
                                 CHAZUO1 = 0;
                                    temp1 = status;
                                    status = (temp1 & 0b11101111);
                        }
                           break;
                  case 0x20://设置JACK2
                        if(status1==0x20)
                        {
                                     CHAZUO2 = 1;
                                    temp1 = status;
                                       status = (temp1 | 0b00100000);
                            }
                            else
                            {
                                        CHAZUO2 = 0;
                                        temp1 = status;
                                        status = (temp1 & 0b11011111);
                              }
                      case 0x40://设置JACK3
                              if(status1==0x40)
                              {
                                          CHAZUO3 = 1;
                                           temp1 = status;
                                           status = (temp1 | 0b01000000);
                              }
                              else
                              {
                                           CHAZUO3 = 0;
                                          temp1 = status;
                                             status = (temp1 & 0b10111111);
                                 }
                     case 0x80://设置JACK4
                                 if(status1==0x80)
                                 {
                                          CHAZUO4 = 1;
                                          temp1 = status;
                                           status = (temp1 | 0b10000000);
                                 }
                                 else
                                    {
                                             CHAZUO4 = 0;
                                             temp1 = status;
                                                status = (temp1 & 0b01111111);
                                    }
}


【PS】本次设计是对于远程进行控制的,没有进行直接硬件操作控制,如果需要直接硬件操作控制,其实也很简单,直接再加上一个按键进行控制。
思路如下:
加入一个按键,比如接到PA5,智能开关输出接PA4,在进行IO配置时,把PA5设置为输入,PA4设置为输出。在程序中监控PA5是否被按下即可进行控制,同时也可以进行远程控制,这样才打到真正智能家居的目的。



孤独的蛇 发表于 2016-8-9 12:24:37

MCU



红外发射和接收模块


智能插座模块


智能开关模块


晶振和复位模块


RGB和自动调光模块


按键和指示灯模块


调速风扇和窗帘/自动收衣服电机控制模块




孤独的蛇 发表于 2016-8-17 17:20:16

【四、自动收衣服与自动窗帘】的设计
自动窗帘的自动收衣服的原理是一样的,这里就介绍自动窗帘。
自动窗帘的原理是:当系统首次运行或重启后,窗帘会强制先进行回到限位点;当我们给定一个窗帘打开的位置数值时,MCU会计算此时窗帘的位置和欲设定的的位置进行比较,比较结果用来判断是打开还是关闭窗帘。
这里,我使用的演示电机是一个小型直流电机,电机驱动芯片是LB1836M,使用镂空编码盘和红外对管进行位置检测。
通过马达的旋转,经过传输带带动窗帘的运动,马达正转则窗帘打开,马达反转则窗帘关闭。在马达的输出轴上,固定一个编码器码盘,通过红外对管进行马达转动的角度,即是窗帘移动的位置。


首先,程序运行时,先进行初始化:(执行结果是程序运行初始时,窗帘先全部关闭)
//窗帘位置初始化
void curtain_init()
{
        while(PIND_5 == 0)//关闭窗帘,直到窗帘限位开关检测到触碰信号
        {
                PORTC_0=1;
                PORTC_1=0;
        }
        PORTC_0=0;
        PORTC_1=0;
        curtain_current_val=100;//设置当前打开位置百分百为100,即全关闭
}初始化完毕后,如果传来调整的命令,则进行窗帘位置调整:
/*
窗帘位置设置curtainpos_set(unsigned char val)
输入参数:val 窗帘打开位置百分比
*/
void curtainpos_set(unsigned char val)
{
        if( val > curtain_current_val)
        {
                //关闭窗帘
                PORTC_0 = 1;
                PORTC_1 = 0;

        }
        else if(val < curtain_current_val)
        {
                //打开窗帘
                PORTC_0 = 0;
                PORTC_1 = 1;
        }
        else
        {
                //正好处于设置位置
                PORTC_0 = 0;
                PORTC_1 = 0;
        }
}
//外部中断0中断函数,对窗帘位置进行监测
ISR(INT0_vect)
{
        if(curtain_dir == 1)//打开窗帘
        {
                curtain_count++;
                if(curtain_count >= CURTAIN_MAX)//如果窗帘位置大于最大值时,把位置设置为最大值
                {
                        curtain_count = CURTAIN_MAX;
                }
        }
        else
        {
                curtain_count--;
                if(curtain_count <= 0)//如果窗帘位置大于最小值时,把位置设置为最小值
                {
                        curtain_count = 0;
                }
        }
        curtain_current_val = (unsigned char) (curtain_count / CURTAIN_MAX);//获取窗帘当前打开百分比
}下面是自动收衣服的程序:
//自动收衣服初始化
void dress_init()
{
        while(PIND_6 == 0)//收回衣服,直到衣服限位开关检测到触碰信号
        {
                PORTC_2=1;
                PORTC_3=0;
        }
        PORTC_2=0;
        PORTC_3=0;
        dress_current_val=0;//设置当前打开位置百分百为0,即全收回
}

/*
窗帘位置设置void dresspos_set(unsigned char val)
输入参数:val 衣服打开位置百分比
*/
void dresspos_set(unsigned char val)
{
        if( val > dress_current_val)
        {
                //关闭窗帘
                PORTC_2 = 1;
                PORTC_3 = 0;

        }
        else if(val < dress_current_val)
        {
                //打开窗帘
                PORTC_2 = 0;
                PORTC_3 = 1;
        }
        else
        {
                //正好处于设置位置
                PORTC_2 = 0;
                PORTC_3 = 0;
        }
}
//外部中断1中断函数
ISR(INT1_vect)
{
        if(dress_dir == 1)//打开衣服
        {
                dress_count++;
                if(dress_count >= DRESS_MAX)//如果自动收衣服位置大于最大值时,把位置设置为最大值
                {
                        dress_count = DRESS_MAX;
                }
        }
        else
        {
                dress_count--;
                if(dress_count <= 0)//如果自动收衣服位置大于最小值时,把位置设置为最小值
                {
                        dress_count = 0;
                }
        }
        dress_current_val = (unsigned char) (curtain_count / CURTAIN_MAX);//获取衣服当前打开百分比

}








孤独的蛇 发表于 2016-8-17 17:43:18

【程序中使用到的一个头文件】这个头文件是在网上看到的,稍作修改后使用。在此谢过原作者,让我们轻松实现AVR单片机的位操作!
下面是头文件的部分内容,由于字数限制,所以不能完全写上。需要源文件的,请点击→→这里←← (访问密码 fa59)
/*******************************************
*      源文件作用:      AVR GCC下的ATMEMGA64的I/O位域定义方便位操作      *                                                                                                *
*      使用方法:                方法1:#define LED BITFIELD(PORTB_BIT).bit0      *
*                                        方法2:#define LED PORTB_0                                       *
*                                        (推荐使用方法2更直观如要LED置1则:LED = 1;)      *
*      版本:                        Ver:0.1                                                                              *
*      更新日期:                2009-01-13                                                                        *
*      作者:                        Zeng                                                                              *
*      支持文件:                无                                                                                        *
*******************************************/
#ifndef      _M164_BIT_OPERATION_H_
#define _M128_BIT_OPERATION_H_

//定义一个带参的宏代替位域结构体方便使用
#define BITFIELD(addr) (*((volatile bit_field *)(addr)))
//位域定义结构体
typedef struct _bit_struct
{
      unsigned Bit0:1;
      unsigned Bit1:1;
      unsigned Bit2:1;
      unsigned Bit3:1;
      unsigned Bit4:1;
      unsigned Bit5:1;
      unsigned Bit6:1;
      unsigned Bit7:1;
}bit_field;

// I/O PORT地址是在GCC头文件基础上加了0x20的
#define PORTA_BIT0x3b
#define PORTB_BIT0x38
#define PORTC_BIT0x35
#define PORTD_BIT0x32
#define PORTE_BIT0x23
#define PORTF_BIT0x62
#define PORTG_BIT0x65

// I/O DDR地址是在GCC头文件基础上加了0x20的
#define DDRA_BIT0x3A
#define DDRB_BIT0x37
#define DDRC_BIT0x34
#define DDRD_BIT0x31
#define DDRE_BIT0x22
#define DDRF_BIT0x61
#define DDRG_BIT0x64

// I/O PIN地址是在GCC头文件基础上加了0x20的
#define PINA_BIT0x39
#define PINB_BIT0x36
#define PINC_BIT0x33
#define PIND_BIT0x30
#define PINE_BIT0x21
#define PINF_BIT0x20
#define PING_BIT0x63


//进行位域定义后的DDRA可按位直接操作
#define DDRA_0   ((*(volatile bit_field *)(DDRA_BIT)).Bit0)
#define DDRA_1   ((*(volatile bit_field *)(DDRA_BIT)).Bit1)
#define DDRA_2   ((*(volatile bit_field *)(DDRA_BIT)).Bit2)
#define DDRA_3   ((*(volatile bit_field *)(DDRA_BIT)).Bit3)
#define DDRA_4   ((*(volatile bit_field *)(DDRA_BIT)).Bit4)
#define DDRA_5   ((*(volatile bit_field *)(DDRA_BIT)).Bit5)
#define DDRA_6   ((*(volatile bit_field *)(DDRA_BIT)).Bit6)
#define DDRA_7   ((*(volatile bit_field *)(DDRA_BIT)).Bit7)
//进行位域定义后的DDRB可按位直接操作
#define DDRB_0   ((*(volatile bit_field *)(DDRB_BIT)).Bit0)
#define DDRB_1   ((*(volatile bit_field *)(DDRB_BIT)).Bit1)
#define DDRB_2   ((*(volatile bit_field *)(DDRB_BIT)).Bit2)
#define DDRB_3   ((*(volatile bit_field *)(DDRB_BIT)).Bit3)
#define DDRB_4   ((*(volatile bit_field *)(DDRB_BIT)).Bit4)
#define DDRB_5   ((*(volatile bit_field *)(DDRB_BIT)).Bit5)
#define DDRB_6   ((*(volatile bit_field *)(DDRB_BIT)).Bit6)
#define DDRB_7   ((*(volatile bit_field *)(DDRB_BIT)).Bit7)
//进行位域定义后的DDRC可按位直接操作
#define DDRC_0   ((*(volatile bit_field *)(DDRC_BIT)).Bit0)
#define DDRC_1   ((*(volatile bit_field *)(DDRC_BIT)).Bit1)
#define DDRC_2   ((*(volatile bit_field *)(DDRC_BIT)).Bit2)
#define DDRC_3   ((*(volatile bit_field *)(DDRC_BIT)).Bit3)
#define DDRC_4   ((*(volatile bit_field *)(DDRC_BIT)).Bit4)
#define DDRC_5   ((*(volatile bit_field *)(DDRC_BIT)).Bit5)
#define DDRC_6   ((*(volatile bit_field *)(DDRC_BIT)).Bit6)
#define DDRC_7   ((*(volatile bit_field *)(DDRC_BIT)).Bit7)
//进行位域定义后的DDRD可按位直接操作
#define DDRD_0   ((*(volatile bit_field *)(DDRD_BIT)).Bit0)
#define DDRD_1   ((*(volatile bit_field *)(DDRD_BIT)).Bit1)
#define DDRD_2   ((*(volatile bit_field *)(DDRD_BIT)).Bit2)
#define DDRD_3   ((*(volatile bit_field *)(DDRD_BIT)).Bit3)
#define DDRD_4   ((*(volatile bit_field *)(DDRD_BIT)).Bit4)
#define DDRD_5   ((*(volatile bit_field *)(DDRD_BIT)).Bit5)
#define DDRD_6   ((*(volatile bit_field *)(DDRD_BIT)).Bit6)
#define DDRD_7   ((*(volatile bit_field *)(DDRD_BIT)).Bit7)
//进行位域定义后的DDRE可按位直接操作
#define DDRE_0   ((*(volatile bit_field *)(DDRE_BIT)).Bit0)
#define DDRE_1   ((*(volatile bit_field *)(DDRE_BIT)).Bit1)
#define DDRE_2   ((*(volatile bit_field *)(DDRE_BIT)).Bit2)
#define DDRE_3   ((*(volatile bit_field *)(DDRE_BIT)).Bit3)
#define DDRE_4   ((*(volatile bit_field *)(DDRE_BIT)).Bit4)
#define DDRE_5   ((*(volatile bit_field *)(DDRE_BIT)).Bit5)
#define DDRE_6   ((*(volatile bit_field *)(DDRE_BIT)).Bit6)
#define DDRE_7   ((*(volatile bit_field *)(DDRE_BIT)).Bit7)
//进行位域定义后的DDRF可按位直接操作
#define DDRF_0   ((*(volatile bit_field *)(DDRF_BIT)).Bit0)
#define DDRF_1   ((*(volatile bit_field *)(DDRF_BIT)).Bit1)
#define DDRF_2   ((*(volatile bit_field *)(DDRF_BIT)).Bit2)
#define DDRF_3   ((*(volatile bit_field *)(DDRF_BIT)).Bit3)
#define DDRF_4   ((*(volatile bit_field *)(DDRF_BIT)).Bit4)
#define DDRF_5   ((*(volatile bit_field *)(DDRF_BIT)).Bit5)
#define DDRF_6   ((*(volatile bit_field *)(DDRF_BIT)).Bit6)
#define DDRF_7   ((*(volatile bit_field *)(DDRF_BIT)).Bit7)
//进行位域定义后的DDRG可按位直接操作
#define DDRG_0   ((*(volatile bit_field *)(DDRG_BIT)).Bit0)
#define DDRG_1   ((*(volatile bit_field *)(DDRG_BIT)).Bit1)
#define DDRG_2   ((*(volatile bit_field *)(DDRG_BIT)).Bit2)
#define DDRG_3   ((*(volatile bit_field *)(DDRG_BIT)).Bit3)
#define DDRG_4   ((*(volatile bit_field *)(DDRG_BIT)).Bit4)
//进行位域定义后的PORTA可按位直接操作
#define PORTA_0   ((*(volatile bit_field *)(PORTA_BIT)).Bit0)
#define PORTA_1   ((*(volatile bit_field *)(PORTA_BIT)).Bit1)
#define PORTA_2   ((*(volatile bit_field *)(PORTA_BIT)).Bit2)
#define PORTA_3   ((*(volatile bit_field *)(PORTA_BIT)).Bit3)
#define PORTA_4   ((*(volatile bit_field *)(PORTA_BIT)).Bit4)
#define PORTA_5   ((*(volatile bit_field *)(PORTA_BIT)).Bit5)
#define PORTA_6   ((*(volatile bit_field *)(PORTA_BIT)).Bit6)
#define PORTA_7   ((*(volatile bit_field *)(PORTA_BIT)).Bit7)
//进行位域定义后的PORTB可按位直接操作
#define PORTB_0   ((*(volatile bit_field *)(PORTB_BIT)).Bit0)
#define PORTB_1   ((*(volatile bit_field *)(PORTB_BIT)).Bit1)
#define PORTB_2   ((*(volatile bit_field *)(PORTB_BIT)).Bit2)
#define PORTB_3   ((*(volatile bit_field *)(PORTB_BIT)).Bit3)
#define PORTB_4   ((*(volatile bit_field *)(PORTB_BIT)).Bit4)
#define PORTB_5   ((*(volatile bit_field *)(PORTB_BIT)).Bit5)
#define PORTB_6   ((*(volatile bit_field *)(PORTB_BIT)).Bit6)
#define PORTB_7   ((*(volatile bit_field *)(PORTB_BIT)).Bit7)
//进行位域定义后的PORTC可按位直接操作
#define PORTC_0   ((*(volatile bit_field *)(PORTC_BIT)).Bit0)
#define PORTC_1   ((*(volatile bit_field *)(PORTC_BIT)).Bit1)
#define PORTC_2   ((*(volatile bit_field *)(PORTC_BIT)).Bit2)
#define PORTC_3   ((*(volatile bit_field *)(PORTC_BIT)).Bit3)
#define PORTC_4   ((*(volatile bit_field *)(PORTC_BIT)).Bit4)
#define PORTC_5   ((*(volatile bit_field *)(PORTC_BIT)).Bit5)
#define PORTC_6   ((*(volatile bit_field *)(PORTC_BIT)).Bit6)
#define PORTC_7   ((*(volatile bit_field *)(PORTC_BIT)).Bit7)
//进行位域定义后的PORTD可按位直接操作
#define PORTD_0   ((*(volatile bit_field *)(PORTD_BIT)).Bit0)
#define PORTD_1   ((*(volatile bit_field *)(PORTD_BIT)).Bit1)
#define PORTD_2   ((*(volatile bit_field *)(PORTD_BIT)).Bit2)
#define PORTD_3   ((*(volatile bit_field *)(PORTD_BIT)).Bit3)
#define PORTD_4   ((*(volatile bit_field *)(PORTD_BIT)).Bit4)
#define PORTD_5   ((*(volatile bit_field *)(PORTD_BIT)).Bit5)
#define PORTD_6   ((*(volatile bit_field *)(PORTD_BIT)).Bit6)
#define PORTD_7   ((*(volatile bit_field *)(PORTD_BIT)).Bit7)
//进行位域定义后的PORTE可按位直接操作
#define PORTE_0   ((*(volatile bit_field *)(PORTE_BIT)).Bit0)
#define PORTE_1   ((*(volatile bit_field *)(PORTE_BIT)).Bit1)
#define PORTE_2   ((*(volatile bit_field *)(PORTE_BIT)).Bit2)
#define PORTE_3   ((*(volatile bit_field *)(PORTE_BIT)).Bit3)
#define PORTE_4   ((*(volatile bit_field *)(PORTE_BIT)).Bit4)
#define PORTE_5   ((*(volatile bit_field *)(PORTE_BIT)).Bit5)
#define PORTE_6   ((*(volatile bit_field *)(PORTE_BIT)).Bit6)
#define PORTE_7   ((*(volatile bit_field *)(PORTE_BIT)).Bit7)

...这里不是所有内容<span style="line-height: 1.5;">------------------------------------------------------------------------------------</span>将以上代码保存为:“M64_BIT_OPERATION.H”,在主程序中调用
#include "M64_BIT_OPERATION.H"再次感谢作者Zeng!

孤独的蛇 发表于 2016-8-17 20:55:35

【六、PWM输出】RGB调光、马达控制、调光PWM信号输出
本次设计中的PWM输出5路,其中1路+PG4被用于直流马达的控制,3路用于RGB调光,1路用于自动调光,1路调光PWM输出。
MEGA64一共有4个定时器,其中T0、T2定时器是8位定时器,一共可输出2路PWM信号,但是T0用于红外发射的38KHz载波输出,为保证载波输出精准,故不使用其产生PWM信号;T1、T3定时器是16位定时器,一共可输出6路PWM信号,但这里只使用了5路PWM输出。
下面,看看PWM输出的定时器初始化:
//PWM寄存器设置初始化
/*
T0:PWM输出,红外发射38KHz的方波计时器,2.89351us溢出中断一次
T1:PWM_A输出,PWM_B输出,此处不用PWM_C,如果需要再进行编程,可以转移到其它端口进行操作 2路独立的PWM信号输出,调光
T2:PWM输出,电机PWM控制,溢出中断进行转向控制
T3:3路PWM输出,进行RGB灯的控制
*/
void PWM_Ini()
{
        /*IO端口设置,其中PWM2 为电机PWM控制,PWM1A/B/C 为RGB_LED灯亮度控制,PWM3A/B/C为三路LED灯亮度控制*/
        DDRB |= ( _BV(P4) | _BV(P5) | _BV(P6) | _BV(P7) );//设置PB4/PB5/PB6/PB7为输出,分别为PWM0,PWM1A,PWM1B,PWM1C
        DDRE |= ( _BV(P3) | _BV(P4) | _BV(P5) );//设置PE3/PE4/PE5为输出,分别为PWM3A,PWM3B,PWM3C
        DDRG |= _BV(P4);//PG4作为电机控制的另外一个引脚,一个引脚为PWM2 OC2(PB7)
        PORTB |= ( _BV(P4) | _BV(P5) | _BV(P6) | _BV(P7) );//上拉
        PORTE |= ( _BV(P3) | _BV(P4) | _BV(P5) );
        PORTG |= _BV(P4);
        TCCR0 |= ( _BV(COM00) | _BV(WGM01) | _BV(CS01) );//CTC模式,比较匹配时取反,8分频,IR发送38KHz
        /*TC1的设置在ICP1_Ini()里边设置了部分 8位快速PWM模式,256分频 */
        TCCR1A |= ( _BV(COM1A1) | _BV(COM1B1) );//比较匹配时清零OCnA/OCnB/OCnC,在 TOP 时置位OCnA/OCnB/OCnC OC1C未连接
        TCCR2 |= ( _BV(COM21) | _BV(WGM21) | _BV(WGM20) | _BV(CS22) | _BV(CS20) );//8位快速PWM 1024分频 OC2输出PWM波形,比较匹配发生时OC2 清零 ,计数到TOP 时OC2 置位
        TCCR3A |= ( _BV(COM3A1) | _BV(COM3B1) |_BV(COM3C1) | _BV(WGM30) );//比较匹配时清零OCnA/OCnB/OCnC,在 TOP 时置位OCnA/OCnB/OCnC, 配合TCCR3B设置8位快速PWM
        TCCR3B |= ( _BV(WGM32) | _BV(CS32) );//8位快速PWM模式,256分频
        TIMSK |= ( _BV(TOIE2) | _BV(TOIE1) | _BV(TICIE1) );//T2溢出中断,T1输入捕捉中断,T1溢出中断
//        ETIMSK |= _BV(TOIE3);//T3溢出中断
}
在直流马达的控制中,使用的是LB1836M芯片作为驱动芯片。下面是电路图:

下面是LB1836的控制逻辑表:

这里控制电机转速的原理为:
根据逻辑表中可以知道,当IN1和IN2同时为低电平时,电机的状态是自由转动。
当两个输入引脚同时为低电平时,电机的状态是刹车。
当一个输入引脚被固定拉低,另外一个引脚为高电平时,电机就会转动(正转或反转)。由此,我们令一个引脚为低电平,另外一个引脚为PWM信号,就可以控制电机的转速了。
(PS:为什么我们不用一个引脚高电平,一个PWM信号呢?根据逻辑表可知,当两个输入引脚为高电平时,电机会制动刹车。而PWM信号则是一个高低变化的方波信号,即有低电平也有高电平,那么电机的效果就是:转动,刹车,转动,刹车。。。如此循环,那么电机是转动呢?还是刹车?所以马达表现出来的就是可能不转,也可能非常缓慢的转动。那么如果我们让一个引脚固定低电平,一个引脚PWM时,则会:转动,自由转动,转动,自由转动。。。如此下去,电机的效果就是在转动。)
当我们需要电机正转时,则把IN2拉低,IN1给PWM信号;当我们需要电机反转时,则把IN1拉低,IN2给PWM信号。
下面是定时器2的中断函数,其中:
T2的输出比较中断函数主要的作用就是:当我们的PWM信号不是通过MCU内部连接引脚输出的时候,需要另外的引脚输出时,在比较匹配的时候,把引脚拉低,即PWM信号的低电平 |``````````|__________|``````````|__________
T2溢出中断的作用是:当低电平输出到一定的时间后,就把引脚拉高。即高电平占整个方波周期的多少就是电机转速的快慢。

//定时器2输出比较B中断函数下面的函数是马达控制函数:
/*
直流马达控制void Motor_Control(unsigned char speed_val,unsigned char dir)
speed_val: 马达速度,值为0-255,0为最小停止,255为最大
dir: 转动方向,0为正转,否则为反转
*/
void Motor_Control(unsigned char speed_val,unsigned char dir)
{
        if(speed_val==0)
        {
                TCCR2 &=~_BV(COM21);//关闭OC2引脚关联
                TIMSK &= ~_BV(OCIE2);//关闭输出比较中断
                //TIMSK &= ~_BV(TOIE2);//关闭T2溢出中断
                pg4_connet = 0;//不关联PG4引脚
                PORTG &= ~_BV(P4);//拉低
                PORTB &= ~_BV(P7);//拉低,关闭直流马达,停止
        }
        else
        {
                OCR2 = speed_val;//设置PWM2为转速值
                //TIMSK |= _BV(TOIE2);//开启T2溢出中断
                if(dir==1)//如果方向为反转
                {
               
                        TCCR2 |=_BV(COM21);//开启OC2引脚关联
                        TIMSK &= ~_BV(OCIE2);//关闭输出比较中断
                        PORTG &= ~_BV(P4);
                        pg4_connet = 0;//不关联PG4引脚
                }
                else
                {
                        TCCR2 &=~_BV(COM21);//关闭OC2引脚关联
                        PORTB &= ~_BV(P7);
                        TIMSK |= _BV(OCIE2);//开启输出比较中断
                        TIMSK |= _BV(TOIE2);//开启T2溢出中断
                        PORTG |= _BV(P4);
                        pg4_connet = 1;//关联PG4引脚
                       
                }
        }
}


PWM调光信号的输出:
/*PWM信号输出 void PWM_OUT(unsigned char num,unsigned char val)
输入参数1:unsigned char num 哪一路PWM信号。1:OC1A第一路PWM 2:OC1B第二路PWM 3:OC3A(RGB中的R) 4:OC3B(RGB中的G) 5:OC3C(RGB中的B)
输入参数2:unsigned char val PWM信号值
*/
void PWM_OUT(unsigned char num,unsigned char val)
{
        switch(num)
        {
                case 1://OC1A PB5 PWM1
                        if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
                        {
                                TCCR1A &=~_BV(COM1A1);//关闭OC1A引脚关联
                                //TIMSK &= ~_BV(OCIE1A);//关闭输出比较A中断
                                PORTB &=~_BV(P5);//输出低电平
                        }
                        else
                        {
                                OCR1A = val;
                                TCCR1A |= _BV(COM1A1);//打开OC1A引脚关联
                                //TIMSK &= ~_BV(OCIE1A);//关闭输出比较A中断
                        }
                        break;
                case 2://OC1B PB6 PWM2
                        if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
                        {
                                TCCR1A &=~_BV(COM1B1);//关闭OC1B引脚关联
                                //TIMSK &= ~_BV(OCIE1B);//关闭输出比较B中断
                                PORTB &=~_BV(P6);//输出低电平
                        }
                        else
                        {
                                OCR1B = val;
                                TCCR1A |= _BV(COM1B1);//打开OC1B引脚关联
                                //TIMSK &= ~_BV(OCIE1B);//关闭输出比较B中断
                        }
                        break;
                case 3://OC3A PE3 R
                        if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
                        {
                                TCCR3A &=~_BV(COM3A1);//关闭OC3A引脚关联
                                //TIMSK &= ~_BV(OCIE3A);//关闭输出比较A中断
                                PORTE &=~_BV(P3);//输出低电平
                        }
                        else
                        {
                                OCR3A = val;
                                TCCR3A |= _BV(COM3A1);//打开OC3A引脚关联
                                //TIMSK &= ~_BV(OCIE3A);//关闭输出比较A中断
                        }
                        break;
                case 4://OC3B PE4 G
                        if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
                        {
                                TCCR3A &=~_BV(COM3B1);//关闭OC3B引脚关联
                                //TIMSK &= ~_BV(OCIE3B);//关闭输出比较A中断
                                PORTE &=~_BV(P4);//输出低电平
                        }
                        else
                        {
                                OCR3B = val;
                                TCCR3A |= _BV(COM3B1);//打开OC3B引脚关联
                                //TIMSK &= ~_BV(OCIE3B);//关闭输出比较A中断
                        }
                        break;
                case 5://OC3C PE5 B
                        if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
                        {
                                TCCR3A &=~_BV(COM3C1);//关闭OC3C引脚关联
                                //TIMSK &= ~_BV(OCIE3C);//关闭输出比较A中断
                                PORTE &=~_BV(P5);//输出低电平
                        }
                        else
                        {
                                OCR3C = val;
                                TCCR3A |= _BV(COM3C1);//打开OC3C引脚关联
                                //TIMSK &= ~_BV(OCIE3C);//关闭输出比较A中断
                        }
                        break;
                default:break;
        }
}




孤独的蛇 发表于 2016-8-17 20:56:52

续【六、PWM】


续:

自动调光原理:
当光敏电阻输入的电信号越大,则说明环境越暗,则需要把灯调的更亮,即把PWM值调的更高。
#define auto_PWM_MIN 600 //自动调光限度最小值,当低于这个值的时候,直接熄灭,PWM为0
#define auto_PWM_MAX 1000 //自动调光限度最大值,当超过这个值的时候,直接点亮,PWM为255映射函数:
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}PWM信号输出函数:
/* PWM信号输出 void PWM_OUT(unsigned char num,unsigned char val)
输入参数1:unsigned char num 哪一路PWM信号。1:OC1A第一路PWM 2:OC1B第二路PWM 3:OC3A(RGB中的R) 4:OC3B(RGB中的G) 5:OC3C(RGB中的B)
输入参数2:unsigned char val PWM信号值
*/
void PWM_OUT(unsigned char num,unsigned char val)
{
switch(num)
{
case 1://OC1A PB5 PWM1
if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
{
TCCR1A &=~_BV(COM1A1);//关闭OC1A引脚关联
//TIMSK &= ~_BV(OCIE1A);//关闭输出比较A中断
PORTB &=~_BV(P5);//输出低电平
}
else
{
OCR1A = val;
TCCR1A |= _BV(COM1A1);//打开OC1A引脚关联
//TIMSK &= ~_BV(OCIE1A);//关闭输出比较A中断
}
break;
case 2://OC1B PB6 PWM2
if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
{
TCCR1A &=~_BV(COM1B1);//关闭OC1B引脚关联
//TIMSK &= ~_BV(OCIE1B);//关闭输出比较B中断
PORTB &=~_BV(P6);//输出低电平
}
else
{
OCR1B = val;
TCCR1A |= _BV(COM1B1);//打开OC1B引脚关联
//TIMSK &= ~_BV(OCIE1B);//关闭输出比较B中断
}
break;
case 3://OC3A PE3 R
if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
{
TCCR3A &=~_BV(COM3A1);//关闭OC3A引脚关联
//TIMSK &= ~_BV(OCIE3A);//关闭输出比较A中断
PORTE &=~_BV(P3);//输出低电平
}
else
{
OCR3A = val;
TCCR3A |= _BV(COM3A1);//打开OC3A引脚关联
//TIMSK &= ~_BV(OCIE3A);//关闭输出比较A中断
}
break;
case 4://OC3B PE4 G
if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
{
TCCR3A &=~_BV(COM3B1);//关闭OC3B引脚关联
//TIMSK &= ~_BV(OCIE3B);//关闭输出比较A中断
PORTE &=~_BV(P4);//输出低电平
}
else
{
OCR3B = val;
TCCR3A |= _BV(COM3B1);//打开OC3B引脚关联
//TIMSK &= ~_BV(OCIE3B);//关闭输出比较A中断
}
break;
case 5://OC3C PE5 B
if(val == 0)//如果PWM值为0,即关闭,则直接把相应的管脚设为0
{
TCCR3A &=~_BV(COM3C1);//关闭OC3C引脚关联
//TIMSK &= ~_BV(OCIE3C);//关闭输出比较A中断
PORTE &=~_BV(P5);//输出低电平
}
else
{
OCR3C = val;
TCCR3A |= _BV(COM3C1);//打开OC3C引脚关联
//TIMSK &= ~_BV(OCIE3C);//关闭输出比较A中断
}
break;
default:break;
}
}自动调光函数:
//自动调光,输入为光敏电阻的值,这里最好是取几次的平均值
void auto_PWM(unsigned int val)
{
unsigned int x;
x = (unsigned int)map(val,auto_PWM_MIN,auto_PWM_MAX,0,255);
PWM_OUT(1,x);
}取5次ADC的输入值求平均值:
//获取ADC5次结果,去掉最大最小值后的平均值,输入为ADC序号,0-7
unsigned int getADC_average_value(unsigned char num)
{
unsigned int max;
unsigned int min;
unsigned int val;
unsigned char x,y,j;
unsigned long sum_val;
unsigned int average_value;//取5次ADC结果,去掉最大最小的平均值
for(y=0;y<5;y++)
{
val = ADC_Read(num);
}
max = val;
min = val;//把ary都赋值给max和min
x = 0;
y = 0;
for (j=1; j<5; j++) //求最大、最小
{
if (max<val)
{
max = val;//有比max大的就赋值给max
y = j;
}
if (min>val)
{
min = val;//有比min小的就赋值给min
x = j;
}
}
//已经寻找出最大最小值了
sum_val = 0;
for(j=0;j<5;j++)
{
if((j != x) && (j != y) )//不是最大最小值
{
sum_val = sum_val + val;
}
}
average_value = (unsigned int)sum_val / 3;//取平均值
return average_value;
}



孤独的蛇 发表于 2016-8-17 21:24:34

本帖最后由 孤独的蛇 于 2016-8-17 21:26 编辑

【七、红外解码】
1838一体化红外接收头是我们最常用的红外接收元器件。它被广泛的应用于电视机、空调、电冰箱以及电视机顶盒等需要红外遥控的电器上。根据名称后面的“38”可知,这个是38KHz载波的器件。
我国基本上是用的遥控器都是遵循NEC的编码方式。所以,这里以NEC红外遥控的编码方式为例。
NEC红外遥控编码方式:它的指令格式依次为,引导码+设备码高位+设备码低位+数据码+数据反码。引导码用于通知红外遥控信号的来临,由9毫秒的低电平加4.5毫秒的高电平组成;设备码高位、设备码低位、数据码、数据反码都是1个字节。设备码高位与设备码低位组成一个16位的设备码,一般用来识别红外遥控器,就是说,假如你的空调的遥控器设备码是88,你家的电视遥控设备码是55,你用电视的遥控对空调就不起作用,如果你家的电视遥控和空调遥控的设备码相同,那你拿上电视的遥控对着空调按一番,说不定就出什么乱子了。数据码用于识别用户的功能,如加减声音、换台等按键发出的数据码都是不一样的。数据反码与数据码是反码的关系,用于校验数据码接收正确与否。口述一下红外遥控按下以后,接收头OUT引脚的变化:首先是9ms的低电平,然后是4.5ms的高电平,然后将会出现设备码高位,设备码低位,数据码,数据反码,这4个码的逻辑1是560us低电平+1680us高电平,逻辑0是560us低电平+560us高电平。

上图是从网上获取的一张图。
此外,用户如果一直按着一个键不放,将会发送重发码。重发码是跟在按键码后面的,它由9ms低电平+2.5ms高电平+560us低电平+97ms高电平组成,由重发码的规律,我们可以看出,当单片机观察到9ms低电平时,后面如果是4.5ms高电平,就是第一次按的按键码,如果9ms后面跟的是2.5ms高电平,就是重发码。


上图是单片机MEGA64接1838的电路图,1838输出端接入的是PD4(IC1),即MEGA64的输入捕捉引脚1,通过单片机的输入捕捉功能,就可以把红外数据解码出来。其原理是:
当收到引导码信号时,开始计算时间长度,是否符合引导码规律,然后把捕捉的电平设置为下降沿触发,计算4.5ms宽度,再次反转触发方式,变为上升沿触发,计算宽度,再翻转电平触发方式,一次类推,直到接收完32位数据。通过解码得到的数据,即可知道发射的是什么了。
MEGA64红外解码程序如下:
定时器1输入捕捉初始化:

/*
红外线解码外部捕捉初始化,使用TC1的外部捕捉
PD4为红外接收器数据引脚,设置PD4为输入方式
*/
void ICP1_Init()
{
      DDRD&=~_BV(P4);
      PORTD|=_BV(P4);//设置为上拉模式,提高解码可靠性
      
      TCCR1A |= _BV(WGM10);
      TCCR1B |= (_BV(WGM12) | _BV(CS12) | _BV(ICES1));//8为PWM 256分频
      TIMSK |= _BV(TICIE1);//输入捕捉中断
//      COM_TX(TIMSK);
      E_UP;
}
定时器1溢出中断,一个作用是用来判断是否红外接收超时,另外一个作用就是180秒的心跳信号
//定时器1溢出中断函数
ISR(TIMER1_OVF_vect)   
{
      time_count++;
      time_xintiao++;

      if((time_count > 1) && (start_ir == 1))//定时器1溢出中断时间,根据8ms的解码超时来算,2 * 23.14814 * 255 ==> 11805us 溢出一次需要23.14814 * 255us = 5902.777us
      {
                IRReceiveCurrentBit = 0;//重置IR接收位为第0位,为下次接收做准备
                E_UP; //设置输入捕获 上升沿有效
                ICP_Parity = 0;   
                Stop_T1;
                time_count = 0;
                //start_ir = 0;//关闭IR超时计数
                CLR_IR_LED;
//                COM_TX('-');
      }
      if(time_xintiao > 30494)//如果心跳计时时间大于180秒,则重启wifi模块
      {
                time_xintiao = 0;
                //重启wifi
                PORTC_5 = 0;
                delay_ms(30);
                PORTC_5 = 1;
      }
}
解码程序:
//定时器计数器1输入捕捉中断函数
ISR(TIMER1_CAPT_vect)
{
if(ir_in)
{
            if(ICP_Parity==0)//捕获中断奇偶次计数 1时为偶次 并在此时判断脉宽
            {
            ICP_Parity++;
            E_DOWN; //设置输入捕获 下降沿有效
            Start_T1;
            }
            else
            {
                  Stop_T1;
            ICP_Parity=0;
            E_UP;//设置输入捕获 上升沿有效
                  Pulse_length = ICR1L;
                  Pulse_length =((ICR1H<<8) | Pulse_length);
            //Pulse_length=ICR1;
            if(IRReceiveCurrentBit==0)
            {
                if(Pulse_length>=152&&Pulse_length<239)// 如果是引导码 (4.5ms) 进入下一个bitde读取
                        //根据定时器的分频来设置,如16MHz的,8分频,0.5us中断一次,则4500us / 0.5us = 9000 ,我们这里取3.5ms - 5.5ms都默认是对的
                {
                              IRReceiveCurrentBit++;
                        }
            }
            else if(IRReceiveCurrentBit<33) //接收32位数据
            {
                        IRcode>>=1;
                if(Pulse_length<83 && Pulse_length>61) //判断shi否为 1 ( 1.685 ms) 1.4-1.9ms都对
                IRcode|=0x80000000;
                IRReceiveCurrentBit++;
                if(IRReceiveCurrentBit==33)
                {
                        IRCOM = (unsigned char) IRcode;
                        IRCOM = (unsigned char) (IRcode >> 8);
                        IRCOM = (unsigned char) (IRcode >> 16);
                        IRCOM = (unsigned char) (IRcode >> 24);

                  if(IRCOM==(unsigned char)(~IRCOM))
                        {
                  SET_IR_LED;    //开启IR信号指示灯
                  IRReceiveEffective=1; //数据有效
                              if(ir_first)
                              {
                                        EEPROM_write(ir_addr, IRCOM);//存放设备码高位
                                        ir_addr++;
                                        EEPROM_write(ir_addr, IRCOM);//存放设备码低位
                                        ir_addr++;
                                        EEPROM_write(ir_addr, IRCOM);//存放数据键值
                                        ir_addr++;
                                        ir_first = 0;
                              }
                              else
                              {
                                        EEPROM_write(ir_addr, IRCOM);//存放数据键值
                                        ir_addr++;
                              }
                  }
                  delay_ms(5); //因为32位数据后面还有一个信号上跳变,所以要适当延时,延时0.65ms以上即可
                }
            }
            }
      }//if(ir_in)
}
以上程序会将接收到的红外数据存放在MRGA64的EEPROM中保存,为下次控制使用。

至此,红外解码结束。





孤独的蛇 发表于 2016-8-19 12:40:37

【八、红外发射】红外学习并发射

通过【七、红外解码】后,我们将解码得到的遥控器数据存放在MCU里的EEPROM中,其存放规则为:
1、一个电视遥控器最多存放29个遥控键值,另外加上遥控器的设备码高位和设备码低位,一个电视遥控器一共占用31个字节空间。这里设计可以存放电视遥控器3组,则存放在EEPROM中的地址分别是:
      电视遥控器1:0-30;
      电视遥控器2:31-61;
      电视遥控器3:62-92;
2、一个空调遥控器最多存放10个遥控键值,另外再加上遥控器的设备码高位和设备码低位,一个空调遥控器一共占用12个字节空间。这里同样也设计了3组空调遥控器,存放在EEPROM中的地址分别是:
      空调遥控器1:93-104;
      空调遥控器2:105-116;
      空调遥控器3:117-128;
下面是这些遥控器存放的地址定义:
#define DS_IR_ADDR_1 0        //电视1红外数据存放首地址
#define DS_IR_ADDR_2 31        //电视2红外数据存放首地址
#define DS_IR_ADDR_3 62        //电视3红外数据存放首地址
#define KT_IR_ADDR_1 93        //空调1红外数据存放首地址
#define KT_IR_ADDR_2 105        //空调2红外数据存放首地址
#define KT_IR_ADDR_4 117        //空调3红外数据存放首地址这里把数据存放在MEGA64的EEPROM中,则需要相应的读写程序:
<font color="#ff0000">/*</font>在红外解码的程序中,把解码得到的数据存放在EEPROM中,其中存放的代码为:
if(ir_first)<font color="#ff0000">//这里判断是否是解码的第一个数据,如果是,则需要存放设备码高地位在前两个地址</font>
                                {
                                        EEPROM_write(ir_addr, IRCOM);//存放设备码高位
                                        ir_addr++;
                                        EEPROM_write(ir_addr, IRCOM);//存放设备码低位
                                        ir_addr++;
                                        EEPROM_write(ir_addr, IRCOM);//存放数据键值
                                        ir_addr++;
                                        ir_first = 0;
                                }
                                else<font color="#ff0000">//这里不是第一个数据了,则不需要存储设备码了,直接存储数据值</font>
                                {
                                        EEPROM_write(ir_addr, IRCOM);//存放数据键值
                                        ir_addr++;
                                }下面,我们来看看红外发射的原理:
红外接收是接收38KHz的载波,如果接收到38KHz的信号,则输出低电平;如果没有接收到38KHz的载波信号,则输出高电平。
根据上述原理,我们要发射的信号则需要38KHz的载波,那么数据值中,需要输出高电平的我们就停止发射38KHz载波,数据值中是低电平的,我们就发射38KHz的载波,这样就可以把数据发射出去了。
38KHz载波的发生:
38KHz载波的发生,这里用到T0定时器:
TCCR0 |= ( _BV(COM00) | _BV(WGM01) | _BV(CS01) );//CTC模式,比较匹配时取反,8分频,IR发送38KHz
//T0比较匹配中断,13.02066us中断一次,即13.02066us改变一次输出的电平,38KHz的方波
ISR(TIMER0_COMP_vect)
{
        start_count++;//这里计算时间长度
}/************<font color="#ff0000">红外发射函数 </font> **************/
void IR_Send_Data(uchar *IR_Data)<font color="#ff0000">//4字节数组,分别是高地址,低地址,数据,数据反码</font>
{
        uchar i,j,dat;
        uint end_count;
TCNT0 = 0;
        OCR0 = 17;
        TIMSK |= _BV(OCIE0);//打开T0比较匹配中断
        TCCR0 |= _BV(COM00);//引脚关联,发射载波
<font color="#ff0000">        //发送9ms的起始码</font>
start_count = 0;
        while(start_count < MS_9);//发送9ms的载波信号,即9ms的低电平
//发送4.5ms的结果码,解码后的高电平
        TCCR0 &=~_BV(COM00);//引脚不关联,停止发射载波
        ES_IR = 0;
        start_count = 0;
        while(start_count < MS_4_5);//发送4.5ms的解码后的高电平
        for(j=0;j<4;j++)
        {
                dat = IR_Data;
                for(i=0;i<8;i++)
                {
                        //先发送0.56ms的载波信号(即解码后的低电平)
                        TCCR0 |= _BV(COM00);//引脚关联
                        start_count = 0;
                        while(start_count < DAT_0);
                        //停止发送载波信号
                        if((dat & 0x01) == 1)        //判断是否为1
                        {
                                end_count = DAT_1;
                        }
                        else
                        {
                                end_count = DAT_0;
                        }
TCCR0 &=~_BV(COM00);//引脚不关联,即停止载波发射
                        ES_IR = 0;
                        start_count = 0;
                        while(start_count < end_count);
                        dat = dat >> 1;
                }
        }
        //发送1位结束码
        TCCR0 |=_BV(COM00);//引脚关联,发射载波
        start_count = 0;
        while(start_count < DAT_0);
        //结束编码
        TCCR0 &=~_BV(COM00);//引脚不关联,停止载波发射
        ES_IR = 0;
        TIMSK &= ~_BV(OCIE0);//关闭TO比较匹配中断
        TCCR0 &=~_BV(COM00);//引脚不关联,停止载波发射
        ES_IR = 0;        //关闭红外发射器
}

<font color="#ff0000">/*</font>下面是在处理串口数据时的部分关于遥控器的处理代码:
case 0x08://设置TVremote1ok
                                                                temp1 = status1;
                                                                temp2 = ((temp1 & 0b11111000) >> 3);
                                                                status = temp1;
                                                                COM_TX(0xaa);COM_TX(0xaa);COM_TX(temp2);COM_TX(0xaa);COM_TX(0xaa);
                                                                if(temp2 != 0)
                                                                {
                                                                        get_ircode_send(DS_IR_ADDR_1 , temp2);
                                                                        status = (temp1 & 0b00000111);
                                                                }
                                                                break;
                                                        case 0x10://设置TVremote2ok
                                                                temp1 = status1;
                                                                temp2 = (temp1 & 0b00011111);
                                                                status = temp1;
                                                                COM_TX(0xaa);COM_TX(0xaa);COM_TX(temp2);COM_TX(0xaa);COM_TX(0xaa);
                                                                if(temp2 != 0)
                                                                {
                                                                        get_ircode_send(DS_IR_ADDR_2 , temp2);
                                                                        status = (temp1 & 0b11100000);
                                                                }
                                                                break;case 0x40://设置TVremote3 OK
                                                                temp1 = status1;
                                                                temp2 = ((temp1 & 0b00111110) >> 1);
                                                                status = temp1;
                                                                COM_TX(0xaa);COM_TX(0xaa);COM_TX(temp2);COM_TX(0xaa);COM_TX(0xaa);
                                                                if(temp2 != 0)
                                                                {
                                                                        get_ircode_send(DS_IR_ADDR_3 , temp2);
                                                                        status = (temp1 & 0b11000001);
                                                                }
                                                                break;
                                                        case 0x80://设置KTremote1 OK
                                                                temp1 = ((status1 & 0b11000000) >> 6);
                                                                status = status1;
                                                                status = status1;
                                                                temp2 = (((status1 & 0b11111100) << 2) | temp1);
                                                                COM_TX(0xaa);COM_TX(0xaa);COM_TX(temp2);COM_TX(0xaa);COM_TX(0xaa);
                                                                if(temp2 != 0)
                                                                {
                                                                        get_ircode_send(KT_IR_ADDR_1 , temp2);
                                                                        status = (status1 & 0b00111111);
                                                                        status = (status1 & 0b11111100);
                                                                }
                                                                break;详细请看所有代码。


孤独的蛇 发表于 2016-8-19 12:51:22

【九、ADC模数转换】各种传感器的数据值获取

在本次设计中,有5个传感器值是模拟信号,需要将其转换成为数字值才能进行处理。
在MEGA64中,模数转换器的特点如下:
• 10 位 精度
• 0.5 LSB 的非线性度
• ± 2 LSB 的绝对精度
• 65 - 260 μs 的转换时间
• 最高分辨率时采样率高达15 kSPS
• 8 路复用的单端输入通道
• 7 路差分输入通道
• 2 路可选增益为10x 与200x 的差分输入通道
• 可选的左对齐ADC 读数
• 0 - VCC 的 ADC 输入电压范围
• 可选的2.56V ADC 参考电压
• 连续转换或单次转换模式
• 中断源自动触发ADC 启动
• ADC 转换结束中断
• 基于睡眠模式的噪声抑制器


这里,需要5路ADC转换,分别是:
1、火焰传感器
2、可燃气体传感器
3、土壤湿度传感器
4、雨水传感器
5、亮度传感器

//ADC单通道读取数据函数
//输入:通道编号,如0,1,2-7
//返回值:0-1023
unsigned int ADC_Read(uchar pinx)
{
        uint ADC_Temp=0;
        uchar ADC_L,ADC_H;
        //ADCSRA=0;//关闭ADC
        ADMUX= (0x40 | pinx);   //AD的转换结果右对齐(ADLAR=0),
                                        //参考电压是AVCC,AD的输入通道是pinx,pinx=0 --> PC0(ADC0),
        ADCSRA=0xc1;    //ADC使能(ADEN),单次转换模式(ADFR),2分频
    delay_ms(1);
        ADCSRA=0;//关闭ADC
        ADC_L = ADCL;
        ADC_H = ADCH;
        ADC_Temp =(unsigned int) ((ADC_H << 8) | ADC_L);
        //输出16位的ADC值,精度只有高10位,低6位无效,如果只需要8位精度,则只需要提取高8位即可
        return(ADC_Temp);
}


孤独的蛇 发表于 2016-8-19 12:58:55

【十、温湿度传感器】温湿度的获取

本次设计使用的温湿度传感器的型号为:DHT11。
      DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达20米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选则。产品为 4 针单排引脚封装。连接方便,特殊封装形式可根据用户需求而提供。


/**************************
作用:读取8位数据
说明:有两种方法实现
***************************/
unsigned char DHT11_Read_Byte()
{
        unsigned char i,U8comdata=0,U8temp;
        unsigned char U8FLAG;
        unsigned int i1=0;
        for(i=0;i<8;i++)          
        {
                U8FLAG=2;       
                while((!DHT)&&U8FLAG++);
                if(U8FLAG==1){break;}
                /**********方法一**********/               
                i1=1;
                while(i1)
                {
                i1++;
                if(!DHT)break;
                }
                U8temp=0;
                //26-28us高电平为0 70us高电平为1
                if(i1>50)U8temp=1;
/***********方法二***********/       
/*
                delay_us(30);
                  U8temp=0;
            if(DHT)U8temp=1;
                   U8FLAG=2;
      while((DHT)&&U8FLAG++);          
                   if(U8FLAG==1){break;}//超时则跳出循环
*/   
                U8comdata <<= 1;
                U8comdata |= U8temp;         
        }//rof
        return U8comdata;
}
/**********************************
作用:读取温湿度值
说明:DHT11无小数位,所以只需提取高8位即可
返回值:高8位湿度,低8位温度
**********************************/       
unsigned int DHT11_Read()
{       
        unsigned char U8FLAG;
        unsigned char U8T_data_H_temp,U8T_data_L_temp,U8RH_data_H_temp,
        U8RH_data_L_temp,U8checkdata_temp;
        //主机拉低18ms
        DHTout;
        DHTL;
        delay_ms(18);
        DHTH;
        DHTin;
        DHTH;
        //总线由上拉电阻拉高 主机延时>20us
        delay_us(30);
        //主机设为输入 判断从机响应信号
        //判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行          
        if(!DHT)               
        {
                U8FLAG=2;
                //判断从机是否发出 80us 的低电平响应信号是否结束       
                while((!DHT)&&U8FLAG++);
                if(U8FLAG==1){return 0;}
                U8FLAG=2;
                /************************/
                //注意:假如你还有中断,要考虑中断服务函数运行所需时间以及中断周期
                //中断服务程序运行大于20us或定时周期较短(小于几十毫秒)最好在此加中断关闭函数,否则数据读取出错
                /************************/
                cli();//关总中断注意要放在这若不初始化定时器则数据一切正常
                //判断从机是否发出 80us 的高电平,如发出则进入数据接收状态
                while((DHT)&&U8FLAG++);
                if(U8FLAG==1){return 0;}
                //数据接收状态       
                /********************************/
                //注意:
                //关中断不可放在此,此处可能使数据为原来的2倍(第一位丢失没读到:此时还在中断服务程序内,全部数据左移1位):间歇性出现
                /********************************/
                U8RH_data_H_temp=DHT11_Read_Byte();        //湿度整数
                U8RH_data_L_temp=DHT11_Read_Byte();        //湿度小数
                U8T_data_H_temp=DHT11_Read_Byte();        //温度整数
                U8T_data_L_temp=DHT11_Read_Byte();        //温度小数
                U8checkdata_temp=DHT11_Read_Byte();        //校验和
                sei();//开总中断
                DHTin;
                DHTH;
                //数据校验
                U8FLAG=(U8T_data_H_temp+U8T_data_L_temp+U8RH_data_H_temp+U8RH_data_L_temp);
                if(U8FLAG==U8checkdata_temp)
                {
                        return ((U8RH_data_H_temp<<8)+U8T_data_H_temp);//U8checkdata=U8checkdata_temp;
                }
        }
        return 0;//错误数据返回0,主函数可根据此来判断数据可靠性
}
















孤独的蛇 发表于 2016-8-19 13:07:00

【十一、串口设置】串口通信设置

MEGA64有两个串口,由于我的这块板子的串口0坏了,所以本次使用的是串口1进行与wifi模块通信。

<font color="#ff0000">//串口设置</font>
#define baud 9600        //设置波特率的大小
#define baud_setting (uint)(((ulong)F_CPU/(16*(ulong)baud))-1)        //波特率计算公式
#define baud_H        (uchar)(baud_setting >> 8)        //提取高位
#define baud_L        (uchar)(baud_setting)        //低位#define start_usart1 (UCSR1B |=_BV(RXCIE1))//开启串口1中断
#define stop_usart1 (UCSR1B &=~_BV(RXCIE1))//关闭串口1中断
<font color="#ff0000">//串口初始化</font>
<font color="#ff0000">//串口接收中断函数</font>
ISR(USART1_RX_vect)
{
        uchar temp,check=0;
//        UCSR1B &=~_BV(RXCIE1);        //关闭串口接收中断
        stop_usart1;
        temp = UDR1;
        COM_TX(temp);
        if(is_header == 0)//是否已经收到数据头0xffff
        {
                if((last_com_dat==0xff) && (temp ==0xff))//判断上一个数据是否是0xff并且当前接收到的数据是0xff
                {
                        is_header = 1;//标志已经接收到数据头了
                }
        }
        else//如果已经接收到了数据头0xffff
        {
                if(!((last_com_dat==0xff) && (temp ==0x55)))//判断数据中是否含有0xff的数据,含有则把0xff后面的0x55给去掉
                {
                        if(is_len == 0)//如果没接收完数据长度2字节数据,则继续接收
                        {
                                len_count++;
                                if(len_count == 2)
                                {
                                        is_len = 1;
                                        dat_len = temp;//保存长度数据的低字节
                                        dat_count = 0;
                                        len_count = 0;
                                }
                                else
                                {
                                        dat_len = (temp << 8);//保存长度数据的高字节
                                }
                        }
                        else//已经接收完毕长度2字节数据,后面紧接着接收数据
                        {
                                COM_DAT = temp;
                                dat_count++;
                                if(dat_count >= dat_len)//接收数据完毕
                                {
                                        dat_count = 0;
                                        is_len = 0;
                                        is_header = 0;
                                        last_com_dat = 0;
                                        check = check_fun((unsigned char *) COM_DAT,dat_len - 1);//注意,要把数据中的校验和去掉
                                        check =(uchar) ((check + dat_len) % 256);
                                        if(check == COM_DAT)//如果校验和正确则进行数据处理
                                        {
                                               
                                                last_com_dat = 0;
                                                dat_count = 0;
                                                is_len = 0;
                                                is_header = 0;
                                                command_count2 = COM_DAT;//获取序列号
                                                if(init_ok)
                                                {
                                                        if(command_count1 != command_count2)//为了排除重复接收的3次
                                                        {
                                                                com_dat_ok_flag = 1;
                                                        }
                                                        else
                                                        {
                                                                com_dat_ok_flag = 0;
                                                        }
                                                }
                                                else
                                                {
                                                        com_dat_ok_flag = 1;
                                                }
                                        }
                                        else//如果校验和不正确,则返回错误消息
                                        {
                                                COM_TX_Str((unsigned char *) mcu_send_ffxx,10);//发送校验和错误消息
                                        }
                                        command_count1 = command_count2;
                                }
                        }
                }
        }
        last_com_dat = temp;
        //UCSR1B |=_BV(RXCIE1);        //打开串口接收中断
        start_usart1;
}
主函数中的串口数据处理的部分代码:
//        **********************   串口数据处理    **************************

                if(com_dat_ok_flag==1)//串口有数据传来
                {
                        stop_usart1;
                        com_dat_ok_flag = 0;
                        init_ok = 1;
                        cmd = COM_DAT;//获取命令值
                        sn = COM_DAT;//获取序列号
switch (cmd)
                        {
                                case 0x01://回复设备信息
                                        mcu_send_sbxx=0;
                                        mcu_send_sbxx=0;//把0xff改成00,方便计算校验和,后面再改回来
                                        mcu_send_sbxx = sn;
                                        check = check_fun((unsigned char *) mcu_send_sbxx,74);//获取校验值
                                        mcu_send_sbxx=0xff;//更改回正确的头数据
                                        mcu_send_sbxx=0xff;
                                        mcu_send_sbxx=check;//填写正确的校验和
                                        COM_TX_Str((unsigned char *) mcu_send_sbxx,75);
                                        init_ok = 1;
                                        break;
                               
                                case 0x03://WiFi模组读取设备的当前状态
                                        action = COM_DAT;
                                        if(action == 0x01)//WiFi模组控制设备
                                        {
                                                attr_flags1 = COM_DAT;
                                                attr_flags2 = COM_DAT;
                                                attr_flags3 = COM_DAT;
                                                attr_flags4 = COM_DAT;
                                                for(i=0;i<14;i++)
                                                {
                                                        status1 = COM_DAT;
                                                }
                                                //开始控制MCU
                                                switch(attr_flags1)
                                                {
       


孤独的蛇 发表于 2016-8-19 13:14:21

本帖最后由 孤独的蛇 于 2016-8-19 13:24 编辑

【十二、按键处理】按键处理程序

这里设置了3个按键,分别是KEY1,KEY2,KEY3
其功能分别是:
KEY1:短按:Ari模式配置wifi;长按:AP模式配置wifi
KEY2:短按:手动上传数据;长按:重置wifi
KEY3:短按:自定义;长按:自定义
这里的功能都可以自己定义。
按键处理代码:
if(KEY1 == 1 && KEY2 == 0 && KEY3 == 0)//单独按下KEY1键
                {
                        init_ok = 0;
                        time_count=0;
                        while(KEY1 == 1 && KEY2 == 0 && KEY3 == 0)
                        {
                                delay_ms(10);
                                time_count++;
                                if(time_count>300)
                                {
                                        SET_IR_LED;//打开LED提示是长按了
                                }
                        }
                        if(time_count>300)//时间为3秒,即长按按键
                        {
                                //长按按键处理程序
                                SET_IR_LED;
                                delay_ms(1000);
                                CLR_IR_LED;
                                mcu_send_peizhi = 0x01;
                                mcu_send_peizhi = 0x10;
                                COM_TX_Str((unsigned char *) mcu_send_peizhi,10);//配置AP模式
                        }
                        else
                        {
                                //短按按键处理程序
                                SET_IR_LED;
                                COM_TX_Str((unsigned char *) mcu_send_resetwifi,9);//重置wifi
                                delay_ms(2500);
                                CLR_IR_LED;
                                COM_TX_Str((unsigned char *) mcu_send_peizhi,10);//配置airlink模式
                        }
                }
                if(KEY2 == 1 && KEY1 == 0 && KEY3 == 0)
                {
                        time_count=0;
                        while(KEY2 == 1 && KEY1 == 0 && KEY3 == 0)
                        {
                                delay_ms(10);
                                time_count++;
                                if(time_count>300)
                                {
                                        SET_LED_1;//打开LED提示是长按了
                                }
                        }
                        if(time_count>300)//时间为3秒,即长按按键
                        {
                                //长按按键处理程序
                                SET_LED_1;
                                delay_ms(1000);
                                CLR_LED_1;
                                COM_TX_Str((unsigned char *) mcu_send_resetwifi,9);//重置wifi
                                init_ok = 0;
                        }
                        else
                        {
                                //短按按键处理程序
                                SET_LED_1;
                                delay_ms(500);
                                CLR_LED_1;
                                MCU_send_mast(0x05);//MCU主动上报数据
                        }
                }
                if(KEY3 == 1 && KEY1 == 0 && KEY2 == 0)
                {
                        time_count=0;
                        while(KEY3 == 1 && KEY1 == 0 && KEY2 == 0)
                        {
                                delay_ms(10);
                                time_count++;
                                if(time_count>300)
                                {
                                        SET_LED_2;//打开LED提示是长按了
                                }
                        }
                        if(time_count>300)//时间为3秒,即长按按键
                        {
                                //长按按键处理程序
                                SET_LED_2;
//这里写长按处理程序
                                CLR_LED_2;
                        }
                        else
                        {
                                SET_LED_2;       
//这里写短按处理程序
                                CLR_LED_2;
                               
                        }
                }


页: [1] 2
查看完整版本: 【MEGA64+ESP8266】之智能家居