单片机,大概三年前,就买了一本 《爱上单片机》 最后就学会,用面包板了,编程书上基本没讲。 看原理图,看时序图,看数据手册, 都没讲。
而且书上自带的代码写的很烂。 1,缩近控制不好 2,命名混乱 3,做if 的时候 不变的常量放在左侧,这是很基本的约定 。。。
最后,还是什么也没有学会。
直到去年,开始学 ARM 了。 学完了 ARM 前面发的(s3c2440)以后, 在回头看单片机,发现单片机真是,简单的不得了!
但是也发现,单片机,不如 ARM 功能强大。速度也慢。很多控制器,没有,要用GPIO 来模拟,但是单片机的时序太精确的又不好做。定时器就那么几个。
ARM 上面,有linux 内核,里面的 Time 就很好用了。 HCLK 100M ,能达到 ns 级。
回顾结束了, 下面说这个开发板。 这个板子,真不错,各个IO 都用上了,外部中断,温度传感器,红外线一体接收头,按键,数码管,峰鸣器,LCD 接口,
流水灯了,USB 转口串口,都有了,体织还小。
下面的源码,用到的知识, UART 串口,发送字符串, 结构体定义数据类型,指针,定时器,中断,EEPROM 读取 擦除 写入 , 按键仿抖,知识点挺多的。
制作期间,遇到一些问题,有通过逻辑分析仪解决的,有从网上找源码修改的。 网上找的源码中,有的有不少错误,或是多余的代码,本人都对比数据手册,和
逻辑分析仪, 逐一,调试重写。保证代码是尽量最小。
为了能发射 红外线,把 LED 发光管,焊接在,峰鸣器的两端, 原因, 普通的 IO 口,拉力太小,而这款 stc89c52 不支持 推挽输出。
而峰鸣器这里,正好有个三极管来驱动。
题外话: 红外线LED 的耐压值 和 电流测试, 之前用 5V 直接点过,红色发光二极管,烧掉过,高亮度的白光二极管,试验过,烧不坏。
在网上找了几个人说,红外线的LED 最大可以接12V ,所以就试着接到5V上,结果,烧黑了。(网上的事,不可信)
峰鸣器这里三极管来驱动可以看到,发射极上并没有接限流电阻, 所以我这里用了一个,精密可调电位器,调到大约80欧。
接入。防止工作的时候烧坏。
测试输出 遥控器编码,到串口。
下面是原理图
本编码,能识别标准的 NEC 码,经过测试,发现家中的 有线机顶盒是这种编码。 空调,电视机,并不适用,需要做修改。
标准的 NEC 码规范:
首次发送的是9ms的高电平脉冲,其后是4.5ms的低电平,接下来就是8bit的地址码(从低有效位开始发),而后是8bit的地址码的反码(主要是用于校验是否出错)。然后是8bit 的命令码(也是从低有效位开始发),而后也是8bit 的命令码的反码。其“0”为载波发射0.56ms,不发射0.565ms,其“1”为载波发射0.56ms,不发射1.69ms。
keil uvision4 工程
UART 串口相关:
uart.h
1 #include "IR.h" 2 #ifndef __UART__ 3 #define __UART__ 4 void init_uart(); 5 void send_hex(unsigned char); 6 void send_str(unsigned char *); 7 void send_code(IR_CODE); 8 #endif
uart.c
1 #include <reg52.h> 2 #include "uart.h" 3 void init_uart() 4 { 5 //定时器1 溢出决定波特率 6 EA = 1; //总中断开 7 TMOD |= 1<<5; //定时器1 自动重装模式 8 TH1 = 0xfd; //当TL1中溢出时 TH1 的值自动重装进去 9 TL1 = 0xfd; //省去一个中断处理函数 10 TR1 = 1; //开始计数 11 SM0 = 0; 12 SM1 = 1; //8bit UART 波特率可变 13 } 14 15 void send_str(unsigned char *str) 16 { 17 while(*str) 18 { 19 SBUF = *str; 20 while(! TI); 21 TI = 0; 22 str++; 23 } 24 } 25 26 void send_hex(unsigned char hex) 27 { 28 SBUF = hex; 29 while(! TI); 30 TI = 0; 31 } 32 33 void send_code(IR_CODE ir_code) 34 { 35 unsigned char c; 36 unsigned char *p; 37 int i,j; 38 p = (unsigned char *)&ir_code; 39 send_str("custom:0x"); 40 for(i=0; i<4; i++) 41 { 42 if(2 == i) 43 { 44 send_str(" code:0x"); 45 } 46 for(j=1; j>=0; j--) 47 { 48 c = (*p>>(4*(j))) & 0xf; 49 if(0<=c && c<=9) 50 { 51 send_hex(‘0‘ + c); 52 } 53 else 54 { 55 send_hex(‘A‘ + c - 0xa); 56 } 57 } 58 p++; 59 } 60 send_str("\r\n"); 61 }
EEPROM 相关
eeprom.h
1 #include <reg52.h> 2 #ifndef __EEPROM__ 3 #define __EEPROM__ 4 /** 5 * STC90C52 结尾是 90C 6 * EEPROM 5K 7 * SRAM 215字节 8 * 每个扇区512字节 5K / 512 = 10 个扇区 9 * 扇区首地址 2000h 结束地址 33ffh 10 */ 11 12 /* FLASH 首地址 */ 13 #define BASE_ADDR 0x2000 14 15 /* 特殊功能寄存器声明 */ 16 sfr ISP_DATA = 0xe2; 17 sfr ISP_ADDRH = 0xe3; 18 sfr ISP_ADDRL = 0xe4; 19 sfr ISP_CMD = 0xe5; 20 sfr ISP_TRIG = 0xe6; 21 sfr ISP_CONTR = 0xe7; 22 23 /* 定义命令字节 */ 24 #define CMD_Read 0x01 //字节读数据命令 25 #define CMD_Prog 0x02 //字节编程数据命令 26 #define CMD_Erase 0x03 //扇区擦除数据命令 27 #define En_Wait_ISP 1<<7 | 1<<1 //设置等待时间 ,并使能ISP/IAP 11.0592 晶振 28 29 void lock_ISP(); 30 void erase(unsigned int); 31 unsigned char read(unsigned int); 32 void prog(unsigned int, unsigned char); 33 34 #endif
eeprom.c
1 #include "eeprom.h" 2 3 /* 执行完操作以后安全锁 */ 4 void lock_ISP() 5 { 6 ISP_CONTR = 0; 7 ISP_CMD = 0; 8 ISP_TRIG = 0; 9 ISP_ADDRH = 0xff; 10 ISP_ADDRL = 0xff; 11 } 12 13 /* 擦除指定地址所在的整个扇区 */ 14 void erase(unsigned int addr) 15 { 16 addr += BASE_ADDR; 17 18 //发送地址 19 ISP_ADDRH = addr >> 8; 20 ISP_ADDRL = addr; 21 22 //发送解锁命令 23 ISP_CONTR = En_Wait_ISP; 24 25 //发擦除命令 26 ISP_CMD = CMD_Erase; 27 28 //发送触发命令 29 ISP_TRIG = 0x46; 30 ISP_TRIG = 0xB9; 31 32 //最后锁定 ISP 仿误操作 33 lock_ISP(); 34 } 35 36 unsigned char read(unsigned int addr) 37 { 38 addr += BASE_ADDR; 39 40 //发送地址 41 ISP_ADDRH = addr >> 8; 42 ISP_ADDRL = addr; 43 44 //发送解锁命令 45 ISP_CONTR = En_Wait_ISP; 46 47 //发读命令 48 ISP_CMD = CMD_Read; 49 50 //发送触发命令 51 ISP_TRIG = 0x46; 52 ISP_TRIG = 0xB9; 53 54 //最后锁定 ISP 仿误操作 55 lock_ISP(); 56 57 return ISP_DATA; 58 } 59 60 void prog(unsigned int addr, unsigned char dat) 61 { 62 addr += BASE_ADDR; 63 64 //发送要保存的数据 65 ISP_DATA = dat; 66 67 //发送地址 68 ISP_ADDRH = addr >> 8; 69 ISP_ADDRL = addr; 70 71 //发送解锁命令 72 ISP_CONTR = En_Wait_ISP; 73 74 //发编程命令 75 ISP_CMD = CMD_Prog; 76 77 //发送触发命令 78 ISP_TRIG = 0x46; 79 ISP_TRIG = 0xB9; 80 81 //最后锁定 ISP 仿误操作 82 lock_ISP(); 83 }
延时: 新版本的 STC 下载工具上,有个延时计算器,精度不错。以下代码来源于它。
delay.h
1 #ifndef __DELAY__ 2 #define __DELAY__ 3 void delay700us(); 4 void delayms(int); 5 #endif
delay.c
1 #include <intrins.h> 2 #include "delay.h" 3 void delayms(int ms) //@11.0592MHz 4 { 5 unsigned char i, j; 6 while(ms--) 7 { 8 _nop_(); 9 i = 2; 10 j = 199; 11 do 12 { 13 while (--j); 14 } 15 while (--i); 16 } 17 } 18 19 void delay700us() //@11.0592MHz 20 { 21 unsigned char i, j; 22 _nop_(); 23 i = 2; 24 j = 61; 25 do 26 { 27 while (--j); 28 } while (--i); 29 }
重头戏 红外编码 与 解码,这2个写在一起了,使用的时候,可以很方便的分离开。
IR.h
1 #include <reg52.h> 2 #include "delay.h" 3 #ifndef __IR__ 4 #define __IR__ 5 //公用 6 typedef struct { 7 unsigned char custom_height; 8 unsigned char custom_lower; 9 unsigned char ir_code; 10 unsigned char re_ir_code; 11 } IR_CODE, *pIR_CODE; 12 13 //接收 14 sbit IR = P3 ^ 2;//红外线一体接收头 OUT 15 16 void init_IR(); 17 IR_CODE IR_recv(); 18 19 //发射延时 20 #define m9 (65536-9000) //约9mS 21 #define m4_5 (65536-4500) //约4.5mS 22 #define m1_6 (65536-1630) //约1.65mS 23 #define m_65 (65536-580) //约0.65mS 24 #define m_56 (65536-560) //约0.56mS 25 #define m40 (65536-40000) //约40mS 26 #define m56 (65536-56000) //56mS 27 #define m2_25 (65536-2250) //约2.25mS 28 29 sbit LaunchLED = P0 ^ 4;//红外线发射LED 接PNP三极管基极, 经过试验不使用三极管时有效距离是40厘米, 可见 IO 下拉电流很小 30 void IR_launch(IR_CODE); 31 void IR_launch_time(bit,unsigned int); 32 void IR_launch_frame(unsigned char); 33 #endif
IR.c
1 #include <string.h> 2 #include "IR.h" 3 4 void init_IR() 5 { 6 //接收 7 EA = 1; //总中断开 8 EX0 = 1; //IR 接收头使用外部中断0 来处理 9 IT0 = 1; //下降沿触发 10 11 //发射 12 TMOD |= 0x01; //T0 16位工作方式 13 LaunchLED = 1; //发射端口常态为高电平 14 } 15 16 //发射 17 void IR_launch(IR_CODE ir_code) 18 { 19 IR_launch_time(1, m9); //高电平9mS 20 IR_launch_time(0, m4_5); //低电平4.5mS 21 22 /*┈ 发送4帧数据┈*/ 23 IR_launch_frame(ir_code.custom_height); 24 IR_launch_frame(ir_code.custom_lower); 25 IR_launch_frame(ir_code.ir_code); 26 IR_launch_frame(ir_code.re_ir_code); 27 28 /*┈┈ 结束码 ┈┈*/ 29 IR_launch_time(1, m_65); 30 IR_launch_time(0, m40); 31 } 32 33 //发送 1 帧数据 34 void IR_launch_frame(unsigned char frame) 35 { 36 char i = 0; 37 for(i=0; i<8; i++) //循环8次移位 38 { 39 IR_launch_time(1, m_65); //高电平0.65ms 40 if(frame >>i & 0x1) 41 IR_launch_time(0, m1_6); //发送最低位 42 else 43 IR_launch_time(0, m_56); 44 } 45 } 46 47 //38KHz脉冲发射 + 延时程序 48 void IR_launch_time(bit status,unsigned int t) 49 { 50 TH0 = t>>8; //输入T0初始值 51 TL0 = t; 52 TF0 = 0; //清0 53 TR0 = 1; //启动定时器0 54 if(0 == status) 55 { 56 //BT=0时不发射38KHz脉冲只延时;BT=1发射38KHz脉冲且延时; 57 while(! TF0); 58 } 59 else 60 { 61 while(1) 62 { 63 /** 64 * 38KHz脉冲,占空比5:26 65 * 以下是逻辑分析仪测试结果 66 * 3:23us 识别正常 67 * 6:23us 识别正常 68 * 10:23us 识别失败 69 * 12:23us 识别失败 70 * 16:23us 识别失败 71 */ 72 LaunchLED = 0; 73 if(TF0)break; 74 if(TF0)break; 75 LaunchLED = 1; 76 if(TF0)break; 77 if(TF0)break; 78 if(TF0)break; 79 if(TF0)break; 80 if(TF0)break; 81 if(TF0)break; 82 if(TF0)break; 83 if(TF0)break; 84 if(TF0)break; 85 if(TF0)break; 86 } 87 } 88 TR0=0; //关闭定时器0 89 TF0=0; //标志位溢出则清0 90 LaunchLED =1; //脉冲停止后,发射端口常态为高电平 91 } 92 93 //接收 94 IR_CODE IR_recv() 95 { 96 /** 97 * 数据格式: 98 * 9ms低电平 4.5ms高电平 头部 99 * 定制高位 定制低位 数据码 数据反码 100 * 1: 560us低电平 1680us高电平 0.56ms 1.7ms 101 * 0: 560us低电平 560us高电平 0.56ms 0.56ms 102 */ 103 IR_CODE ir_code; 104 unsigned char i,k; 105 unsigned char *ir_char_p; 106 unsigned char ir_char; 107 ir_char_p = (unsigned char *)&ir_code; 108 109 //栈分配 IR_CODE 竟然还要手动清0 110 memset(&ir_code, 0, 4); 111 112 delayms(5); //9ms 内必须是低电平否则就不是头信息 113 if(0 == IR) 114 { 115 while(! IR);//等待4.5ms的高电平 116 117 //检测是否是 2.5ms 重码 118 delayms(3); 119 if(1 == IR) 120 { 121 //k 4位编码 122 for(k=0; k<4; k++) 123 { 124 ir_char = 0x0; 125 //i 每一个编码的 8bit 126 for(i=0;i<8;i++) 127 { 128 while(IR); //等待变为低电平时 129 while(! IR); //等待变为高电平后 130 delay700us(); //休眠700us 后读值 131 ir_char |= (char)IR << i;//先存低位 132 //使用下面指针操作就会失败出现不稳定 133 //*ir_char_p |= (char)IR << i; 134 } 135 *ir_char_p = ir_char; 136 ir_char_p++; 137 } 138 139 //计算反码 code码是否正确 140 if(ir_code.ir_code != ~(ir_code.re_ir_code)) 141 { 142 memset(&ir_code, 0, 4); 143 } 144 } 145 } 146 return ir_code; 147 }
主操作函数,在这个里面,先是从 EEPROM 中读到了,设置的值。
main.c
1 #include <reg52.h> 2 #include <intrins.h> 3 #include <string.h> 4 #include "uart.h" 5 #include "delay.h" 6 #include "IR.h" 7 #include "eeprom.h" 8 9 IR_CODE read_ir_code(unsigned char); 10 void save_block_ir_code(); 11 void save_ir_code(IR_CODE ir_code,unsigned int addr); 12 13 //全局接收红外线信号存放 14 IR_CODE global_ir_code; 15 16 //从eeprom 中读取 按键值 17 IR_CODE k3_ir_code; 18 IR_CODE k4_ir_code; 19 IR_CODE k5_ir_code; 20 IR_CODE k6_ir_code; 21 22 //3个按键 23 sbit K3 = P3 ^ 5; 24 sbit K4 = P3 ^ 4; 25 sbit K5 = P3 ^ 3; 26 27 #ifdef DEBUG 28 //main 中 EX1 = 1; 29 //外部中断1 按下时发送红外线信号 30 void infrared_led_int1() interrupt 2 31 { 32 EX1 = 0; 33 IR_launch(global_ir_code); 34 EX1 = 1; 35 } 36 #endif 37 38 void main() 39 { 40 //总中断开关 41 EA = 1; 42 init_uart(); 43 init_IR(); 44 45 //全局接收红外线清0 46 memset(&global_ir_code, 0, 4); 47 48 //从eeprom 中读取 按键值 49 k3_ir_code = read_ir_code(0); 50 k4_ir_code = read_ir_code(4); 51 k5_ir_code = read_ir_code(8); 52 53 while(1) 54 { 55 //按键按下 56 if(0 == K3) 57 { 58 //去抖动后 59 delayms(30); 60 if(0 == K3) 61 { 62 //关中断 中断打开时,时钟可能不准 63 EA = 0; 64 65 //如果有全局接收 则存入 eeprom 66 if(global_ir_code.ir_code) 67 { 68 k3_ir_code = global_ir_code; 69 save_block_ir_code(); 70 memset(&global_ir_code, 0, 4); 71 72 //闪灯2次表示 存入成功 73 P1 = 0x0; 74 delayms(300); 75 P1 = 0xff; 76 delayms(300); 77 P1 = 0x0; 78 delayms(300); 79 P1 = 0xff; 80 } 81 else 82 { 83 IR_launch(k3_ir_code); 84 85 //闪灯1次表示 发射成功 86 P1 = 0x0; 87 delayms(300); 88 P1 = 0xff; 89 } 90 //开中断 91 EA = 1; 92 } 93 } 94 //按键按下 95 if(0 == K4) 96 { 97 //去抖动后 98 delayms(30); 99 if(0 == K4) 100 { 101 //关中断 中断打开时,时钟可能不准 102 EA = 0; 103 104 //如果有全局接收 则存入 eeprom 105 if(global_ir_code.ir_code) 106 { 107 k4_ir_code = global_ir_code; 108 save_block_ir_code(); 109 memset(&global_ir_code, 0, 4); 110 111 //闪灯2次表示 存入成功 112 P1 = 0x0; 113 delayms(300); 114 P1 = 0xff; 115 delayms(300); 116 P1 = 0x0; 117 delayms(300); 118 P1 = 0xff; 119 } 120 else 121 { 122 IR_launch(k4_ir_code); 123 124 //闪灯1次表示 发射成功 125 P1 = 0x0; 126 delayms(300); 127 P1 = 0xff; 128 } 129 //开中断 130 EA = 1; 131 } 132 } 133 //按键按下 134 if(0 == K5) 135 { 136 //去抖动后 137 delayms(30); 138 if(0 == K5) 139 { 140 //关中断 中断打开时,时钟可能不准 141 EA = 0; 142 143 //如果有全局接收 则存入 eeprom 144 if(global_ir_code.ir_code) 145 { 146 k5_ir_code = global_ir_code; 147 save_block_ir_code(); 148 memset(&global_ir_code, 0, 4); 149 150 //闪灯2次表示 存入成功 151 P1 = 0x0; 152 delayms(300); 153 P1 = 0xff; 154 delayms(300); 155 P1 = 0x0; 156 delayms(300); 157 P1 = 0xff; 158 } 159 else 160 { 161 IR_launch(k5_ir_code); 162 163 //闪灯1次表示 发射成功 164 P1 = 0x0; 165 delayms(300); 166 P1 = 0xff; 167 } 168 //开中断 169 EA = 1; 170 } 171 } 172 } 173 } 174 175 void IR_int() interrupt 0 176 { 177 IR_CODE ir_code; 178 EX0 = 0;//处理过程中 关中断 179 ir_code = IR_recv(); 180 if(ir_code.ir_code) 181 { 182 //点亮P1 显示收到了编码 183 P1 = 0x0; 184 delayms(300); 185 P1 = 0xff; 186 187 //发送到串口 188 send_code(ir_code); 189 190 //给全局编码赋值 191 global_ir_code = ir_code; 192 } 193 EX0 = 1;//处理结束后 开中断 194 } 195 196 //读取 eeprom 中的值 197 IR_CODE read_ir_code(unsigned char addr) 198 { 199 IR_CODE ir_code; 200 ir_code.custom_height = read(addr); 201 ir_code.custom_lower = read(addr+1); 202 ir_code.ir_code = read(addr+2); 203 ir_code.re_ir_code = read(addr+3); 204 return ir_code; 205 } 206 207 //保存所有编码 208 void save_block_ir_code() 209 { 210 erase(); 211 save_ir_code(k3_ir_code, 0); 212 save_ir_code(k4_ir_code, 4); 213 save_ir_code(k5_ir_code, 8); 214 } 215 216 //保存一个编码 217 void save_ir_code(IR_CODE ir_code,unsigned int addr) 218 { 219 prog(addr, ir_code.custom_height); 220 prog(addr+1, ir_code.custom_lower); 221 prog(addr+2, ir_code.ir_code); 222 prog(addr+3, ir_code.re_ir_code); 223 }
最后是使用说明:
先打开串口,工具,推荐用 putty ,开机后,对着红外线一体化接收头,按遥控器, 这里putty 上就会显示出来,编码的十六进制。 你可以把它保存到电脑上,以后遥控器丢了,也不怕。
设置 3个铵键的编码方法是: 开机,对着接收头,按遥控器, 这里除了串口上有输出外,流水灯会全部亮一下。 这时,按下 K3 K4 K5 中的其中一个, 那么这个编码就被保存
到了 单片机内部的 EEPROM 中,之后, 流水灯会闪二下。 说明设置完成。 三个按键,都是如此设置。
特别说明, 此编码,解码,仅用于 标准NFC 码规范, 不是所有遥控器都能识别。 后期,博主会更新加入识别其它编码的功能。