2.26更新
基本概念
#前置概念
所用外设:
AT24C02(EEPROM储存) 使用IIC协议
一、单片机通信协议(本文以IIC为重点)
全双工和半双工的区别:
一、全双工(Full Duplex)通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指A→B的同时B→A,是瞬时同步的。
二、半双工(Half Duplex)数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
MCU中常见的通信协议:
二、推挽输出和开漏输出
推挽输出:
推挽输出可以输出高电平和低电平,在两种电平下都具有驱动能力。
相比于后面介绍的开漏输出,输出高电平时的驱动能力强很多
缺点:推挽输出不能实现" 线与"。
开漏输出:
常说的与推挽输出相对的就是开漏输出,抽象地说就是COMS反相器去掉了NMOS部分,PMOS管漏极输出,即开漏输出。PMOS管由高电平导通输出低电平,高电平没有驱动能力,需要借助“上拉电阻” (一般上拉电阻值较大,即弱上拉模式)什么是弱上拉?
IIC为什么要开漏输出模式?
三、I2C总线介绍:
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步、半双工,带数据应答
通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度
四、I2C电路规范
五、I2C时序结构
总结:IIC的工作模块
起始条件
中止条件
发送一个字节
接收一个字节
接收应答
发送应答
#存储器/AT24C02
一、RAM和ROM
**RAM : 易失性存储器 (高速存储) **
单片机内RAM为中间数据存储器
SRAM:静态RAM,电脑中的CPU,高速缓存,容量少,成本高
DRAM:动态RAM,使用电容存储,电容容值小,需要补电能,成本相较于SRAM更低,容量更大
掉电丢失
ROM:非易失性存储器
单片机内ROM为指令存储器
Mask ROM(掩膜ROM),只能读,不能写
PROM:可编程ROM,可以写,但是只能写入一次
EPROM:可擦除可编程ROM,可以编程,也可以清除(30分钟紫外线)
E2PROM: 电可擦除可编程ROM,5V电可擦除,掉电不丢失
Flash(闪存):
硬盘、软盘、光盘等:磁介质、光信号
单片机烧录的由来:早期通过编程储存数据,实际上是将特殊的二极管击穿,相当于“烧毁”(PROM)
二、AT24C02
代码部分
一、给AT24C02写入数据
实现:AT24C02掉电不丢失
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <REGX52.H> #include "LCD1602.h" #include "Key.h" #include "AT24C02.h" #include "Delay.h" unsigned char Data;void main () { LCD_Init(); LCD_ShowString(1 ,1 ,"Hello World" ); Delay(5 ); Data=AT24C02_ReadByte(1 ); LCD_ShowNum(2 ,1 ,Data,3 ); while (1 ) { } }
完善代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <REGX52.H> #include "LCD1602.h" #include "Key.h" #include "AT24C02.h" #include "Delay.h" unsigned char KeyNum;unsigned int Num;void main () { LCD_Init(); LCD_ShowNum(1 ,1 ,Num,5 ); while (1 ) { KeyNum=Key(); if (KeyNum==1 ) { Num++; LCD_ShowNum(1 ,1 ,Num,5 ); } if (KeyNum==2 ) { Num--; LCD_ShowNum(1 ,1 ,Num,5 ); } if (KeyNum==3 ) { AT24C02_WriteByte(0 ,Num%256 ); Delay(5 ); AT24C02_WriteByte(1 ,Num/256 ); Delay(5 ); LCD_ShowString(2 ,1 ,"Write OK" ); Delay(1000 ); LCD_ShowString(2 ,1 ," " ); } if (KeyNum==4 ) { Num=AT24C02_ReadByte(0 ); Num|=AT24C02_ReadByte(1 )<<8 ; LCD_ShowNum(1 ,1 ,Num,5 ); LCD_ShowString(2 ,1 ,"Read OK" ); Delay(1000 ); LCD_ShowString(2 ,1 ," " ); } } }
为什么要写:
1 2 3 AT24C02_WriteByte(0 ,Num%256 ); Delay(5 ); AT24C02_WriteByte(1 ,Num/256 );
因为:int类型最高65535,对应的二进制为16位
二、定时器扫描按键
实验:定时器按键扫描,实现键值的数码管显示
实验结果:如果直接让Nixie在判断键码后显示Keynum值,只能显示一瞬间,数码管的显示方式不同于LCD1602,所以需要一个缓存量Temp
1 2 3 4 5 6 7 8 9 10 11 12 13 /*错误写法*/ Temp=Key(); if(KeyNum)//显示结果只在一瞬间显示 { Nixie(1,KeyNum); } /*正确写法*/ if(Temp) { KeyNum=Temp; } Nixie(1,KeyNum);
实验:定时器按键扫描实列2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <REGX52.H> #include "Key.h" #include "AT24C02.h" #include "Timer0.h" #include "Nixie.h" #include "Delay.h" unsigned char KeyNum; void main() { Timer0_Init(); while(1) { KeyNum=Key(); if(KeyNum) { Nixie_SetBuf(1,KeyNum); Nixie_SetBuf(2,KeyNum); Nixie_SetBuf(3,KeyNum); Delay(1000); } } } void Timer0_Routine() interrupt 1 //中断子程序 { static unsigned int T0Count1,T0Count2;//静态局部变量,保证退出函数之后不销毁 TL0 = 0x66; //设置定时初始值 TH0 = 0xFC; //设置定时初始值 T0Count1++;//每次进入中断子程序,秒控制器自加一 if(T0Count1>=20)//每隔20ms,按键函数会被调用一次 { T0Count1=0; Key_Loop(); } T0Count2++; if(T0Count2>=2) { T0Count2=0; Nixie_Loop(); } }
实验结果:
这时,按下按键,数码管显示会延时一秒改变,可见,使用定时器扫描按键,不会被延时函数影响,相较于延时函数使单片机的工作更具有可靠性
秒表源代码:
操作方式:
按键1暂停
按键2清零
按键3通过AT24C02写入数据
按键4通过AT24C02读取数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 #include <REGX52.H> #include "Key.h" #include "AT24C02.h" #include "Timer0.h" #include "Nixie.h" #include "Delay.h" unsigned char KeyNum;unsigned char Min,Sec,MiniSec;unsigned char RunFlag;void main () { P2_5=0 ; Timer0_Init(); while (1 ) { KeyNum=Key(); if (KeyNum==1 ) { RunFlag=!RunFlag; } if (KeyNum==2 ) { Min=0 ;Sec=0 ;MiniSec=0 ; } if (KeyNum==3 ) { AT24C02_WriteByte(0 ,Min); Delay(5 ); AT24C02_WriteByte(1 ,Sec); Delay(5 ); AT24C02_WriteByte(2 ,MiniSec); Delay(5 ); } if (KeyNum==4 ) { Min=AT24C02_ReadByte(0 ); Sec=AT24C02_ReadByte(1 ); MiniSec=AT24C02_ReadByte(2 ); } Nixie_SetBuf(1 ,Min/10 ); Nixie_SetBuf(2 ,Min%10 ); Nixie_SetBuf(3 ,11 ); Nixie_SetBuf(4 ,Sec/10 ); Nixie_SetBuf(5 ,Sec%10 ); Nixie_SetBuf(6 ,11 ); Nixie_SetBuf(7 ,MiniSec/10 ); Nixie_SetBuf(8 ,MiniSec%10 ); } } void Sec_Loop (void ) { if (RunFlag) { MiniSec++; if (MiniSec>=100 ) { MiniSec=0 ; Sec++; } if (Sec>=60 ) { Sec=0 ; Min++; } if (Min>=60 ) { Min=0 ; } } } void Timer0_Routine () interrupt 1 { static unsigned int T0Count1,T0Count2,T0Count3; TL0 = 0x66 ; TH0 = 0xFC ; T0Count1++; if (T0Count1>=20 ) { T0Count1=0 ; Key_Loop(); } T0Count2++; if (T0Count2>=2 ) { T0Count2=0 ; Nixie_Loop(); } T0Count3++; if (T0Count3>=10 ) { T0Count3=0 ; Sec_Loop(); } }
题外话: 6-1矩阵键盘密码锁:为什么密码不能设置为0开头?
因为0开头代表八进制,比如0123实际上对应的十进制数是83
#蓝桥杯(STC15)拓展
IIC通信底层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #include "iic.h" #define DELAY_TIME 5 void IIC_Delay (unsigned char i) { do { _nop_(); } while (i--); } void IIC_Start (void ) { SDA = 1 ; SCL = 1 ; IIC_Delay(DELAY_TIME); SDA = 0 ; IIC_Delay(DELAY_TIME); SCL = 0 ; } void IIC_Stop (void ) { SDA = 0 ; SCL = 1 ; IIC_Delay(DELAY_TIME); SDA = 1 ; IIC_Delay(DELAY_TIME); } void IIC_SendAck (bit ackbit) { SCL = 0 ; SDA = ackbit; IIC_Delay(DELAY_TIME); SCL = 1 ; IIC_Delay(DELAY_TIME); SCL = 0 ; SDA = 1 ; IIC_Delay(DELAY_TIME); } bit IIC_WaitAck (void ) { bit ackbit; SCL = 1 ; IIC_Delay(DELAY_TIME); ackbit = SDA; SCL = 0 ; IIC_Delay(DELAY_TIME); return ackbit; } void IIC_SendByte (unsigned char byt) { unsigned char i; for (i = 0 ; i < 8 ; i++) { SCL = 0 ; IIC_Delay(DELAY_TIME); if (byt & 0x80 ) SDA = 1 ; else SDA = 0 ; IIC_Delay(DELAY_TIME); SCL = 1 ; byt <<= 1 ; IIC_Delay(DELAY_TIME); } SCL = 0 ; } unsigned char IIC_RecByte (void ) { unsigned char i, da; for (i = 0 ; i < 8 ; i++) { SCL = 1 ; IIC_Delay(DELAY_TIME); da <<= 1 ; if (SDA) da |= 1 ; SCL = 0 ; IIC_Delay(DELAY_TIME); } return da; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 void EEPROM_Write (unsigned char *EEPROM_String,unsigned char addr,unsigned char num) { IIC_Start(); IIC_SendByte(0xA0 ); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck(); while (num--) { IIC_SendByte(*EEPROM_String++); IIC_WaitAck(); IIC_Delay(200 ); } IIC_Stop(); } void EEPROM_Read (unsigned char *EEPROM_String,unsigned char addr,unsigned char num) { IIC_Start(); IIC_SendByte(0xA0 ); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck(); IIC_Start(); IIC_SendByte(0xA1 ); IIC_WaitAck(); while (num--) { *EEPROM_String++=IIC_RecByte(); if (num) { IIC_SendAck(0 ); } else { IIC_SendAck(1 ); } } IIC_Stop(); }