电子商务网站建设各项费用预算是多少,旅游网站的功能结构图,phpstudy配置网站,流量套餐汇总网站这是I2C的系列的第三篇#xff0c;这篇主要是写一个简单的程序来实践一下相关的内容。前面博主写过一个电子密码锁的程序初学51单片机之简易电子密码锁及PWM应用扩展_51单片机设计电子密码锁-CSDN博客
本篇主要是在此基础上修改下程序#xff0c;让密码存储在E2PROM中#… 这是I2C的系列的第三篇这篇主要是写一个简单的程序来实践一下相关的内容。前面博主写过一个电子密码锁的程序初学51单片机之简易电子密码锁及PWM应用扩展_51单片机设计电子密码锁-CSDN博客
本篇主要是在此基础上修改下程序让密码存储在E2PROM中并且可以通过UART串口通信在线修改E2PROM存储的密码。
简单的介绍下程序的功能
1笔者的开发版在烧录该程序后一上电除了数字键可以使能外其他按键是没有功能的。因此需要输入相应的密码才能使能其他按键的功能。
2程序的密码是存放在24C02这个E2PROM器件“非易失区”的4个存储地址中0x000x010x020x03以密码2024为例它是以字符形式‘2’、‘0’、‘2’、‘4’分别按顺序存入的。由于密码锁的写法有点问题该程序目前只支持4位的密码
3当输入正确的密码后键盘的其它按键功能就能使用。在设置好倒计时比如10秒按下entel键倒计时开始10s后LED小灯就会被点亮当然这个秒表还不是非常准确没仔细做时间补偿蜂鸣器蜂鸣。按下ESC会复位。
4通过UART串口可以通过输入命令使能蜂鸣器。“buzz on”、buzz off能分别打开和关闭蜂鸣器。“reset password ” 命令能修改24C02存储的数据以输入命令“reset password 2024”为例通信软件接收收区会显示命令语句reset password 2024并且液晶上会显示“2024”同时修改了E2PROM密码存储区的数据修改后的密码只会在下次开机启动后使能。在线修改密码上电后就可以修改。如果UART传输未定义命令会在接收区显示字符串“bad command”用以提醒输入错误的指令。
5在键盘输入错误密码后。数码管基本显示的是65526这几个数字这时板子无法再继续输入密码。需要重启板子才能再次尝试输入密码。
6该程序实现了UART串口通信I2C通信的基本用法。对于UART串口通信前面笔者有一篇博文使用!号作为命令结束的通用标识本篇采用的是另外一种。它是基于对总线空闲时间的监控来确定数据帧是否传输接收。注意这个数据帧不是单指一个字节的数据帧也可能是多个字节一起构成一段数据帧这个时间本案是30ms即UART总线上空闲了30ms就确认一段数据帧结束。并开始处理数据帧命令。
7这个密码锁的写法是笔者之前写的个人觉得不行太麻烦。个人觉得把键盘输入的键码转换为字节方式存入数组中然后和E2PROM中的存储的字节比较会快一点。
8程序的主干来自开发版老师一些功能扩展是笔者自己写的。这个程序还是初版有些功能还能优化一下的比如该程序目前只支持密码修改按密码时的长短键要求还无法通过UART修改目前该程序按密码的时候都是短键使能。
看代码
main.c
#include reg52.hsbit BUZZ P1^6;
sbit ADDR3 P1^3;
sbit ENLED P1^4;
sbit KEY_IN_1 P2^4;
sbit KEY_IN_2 P2^5;
sbit KEY_IN_3 P2^6;
sbit KEY_IN_4 P2^7;
sbit KEY_OUT_1 P2^3;
sbit KEY_OUT_2 P2^2;
sbit KEY_OUT_3 P2^1;
sbit KEY_OUT_4 P2^0;unsigned char code LedChar[] { //数码管显示字符转换表0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[7] { //数码管独立LED显示缓冲区0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char code KeyCodeMap[4][4] { //矩阵按键编号到标准键盘键码的映射表{ 0x31, 0x32, 0x33, 0x26 }, //数字键1、数字键2、数字键3、向上键{ 0x34, 0x35, 0x36, 0x25 }, //数字键4、数字键5、数字键6、向左键{ 0x37, 0x38, 0x39, 0x28 }, //数字键7、数字键8、数字键9、向下键{ 0x30, 0x1B, 0x0D, 0x27 } //数字键0、ESC键、 回车键、 向右键
};unsigned char KeySta[4][4] { //全部矩阵按键的当前状态{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}};
pdata unsigned long KeyDownTime[4][4] {{0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0}};bit enBuzz 0; //蜂鸣器使能标记bit flag1s 0; //1s定时标志bit flagStart 0; //倒计时启动标志bit EntelLongPress 0; //Entel长按标志bit LongPress 0; //长按标志bit Locksta 0; //按键转换状态防出错标志bit PasswordLock 0; //使能定时器键盘标志bit KeyLock 1; //使能密码键盘标志bit PressMark 0; //长按标记unsigned char PressStyle 0;//按键方式unsigned int backword 0; //键盘密码值unsigned char T0RH 0; //T0重载值高字节unsigned char T0RL 0; //T0重载值低字节unsigned char CountDown 0; //倒计时计数器unsigned int keybuf 0;//E2prom储存的密码extern void UartDriver();
extern void ConfigUART(unsigned int baud);
extern void UartRxMonitor(unsigned char ms);
extern void UartWrite(unsigned char *buf, unsigned char len);
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);unsigned int DataConversion(unsigned char addr);extern void E2Read(unsigned char* buf,unsigned char addr,unsigned char len );extern void E2Write(unsigned char* buf,unsigned char addr, unsigned char len);void ConfigTimer0(unsigned int ms); //定时器0初值设定函数void ShowNumber(unsigned long num); //倒计时调整时间数码管显示函数void KeyDriver();void Password(unsigned int Num);void main(){unsigned int keybuf 0;InitLcd1602(); //初始化液晶EA 1;ENLED 0;ADDR3 1;ConfigUART(9600); //配置波特率为9600keybuf DataConversion(0x00);ConfigTimer0(2); //定时2msShowNumber(0); //数码管显示0while(1){KeyDriver(); //调用按键驱动函数Password(keybuf);UartDriver(); //调用串口驱动if(flagStart flag1s) //倒计时启动且1秒定时到达时处理倒计时{flag1s 0;if(CountDown 0) //倒计时未到0时计时器递减{CountDown--; //ShowNumber(CountDown); //刷新倒计时数字显示if(CountDown 0){enBuzz 1; //启动蜂鸣器LedBuff[6] 0x00; //点亮独立LED} }}}}/* 内存比较函数比较两个指针所指向的内存数据是否相同ptr1-待比较指针1ptr2-待比较指针2len-待比较长度返回值-两段内存数据完全相同时返回1不同返回0 */
bit CmpMemory(unsigned char *ptr1, unsigned char *ptr2, unsigned char len)
{while (len--){if (*ptr1 ! *ptr2) //遇到不相等数据时即刻返回0{return 0;}}return 1; //比较完全部长度数据都相等则返回1
}/* 串口动作函数根据接收到的命令帧执行响应的动作buf-接收到的命令帧指针len-命令帧长度 */
void UartAction(unsigned char *buf, unsigned char len)
{unsigned char i;unsigned char j;unsigned char pdata keybuf[4];unsigned int pdata PasswordBuf 0;unsigned char code cmd0[] buzz on; //开蜂鸣器命令unsigned char code cmd1[] buzz off; //关蜂鸣器命令unsigned char code cmd2[] reset password ; //字符串显示命令unsigned char code cmdLen[] { //命令长度汇总表sizeof(cmd0)-1, sizeof(cmd1)-1, sizeof(cmd2)-1,//去掉字符串结束符};unsigned char code *cmdPtr[] { //命令指针汇总表cmd0[0], cmd1[0], cmd2[0],};for (i0; isizeof(cmdLen); i) //遍历命令列表查找相同命令{if (len cmdLen[i]) //首先接收到的数据长度要不小于命令长度{if (CmpMemory(buf, cmdPtr[i], cmdLen[i])) //比较相同时退出循环{break;}}}switch (i) //循环退出时i的值即是当前命令的索引值{case 0:enBuzz 1; //开启蜂鸣器break;case 1:enBuzz 0; //关闭蜂鸣器break;case 2:buf[len] \0; //为接收到的字符串添加结束符LcdShowStr(0, 0, bufcmdLen[2]); //显示命令后的字符串i len - cmdLen[2]; //计算有效字符个数if (i 16) //有效字符少于16时清除液晶上的后续字符位{LcdAreaClear(i, 0, 16-i);}for(j 0; j4;j) //约定是设置4位密码因此输入密码的时候要注意长度输入5位密码也只使能前4位{keybuf[j] *(bufcmdLen[2]j);//把串口写入的密码存入数组}E2Write(keybuf,0x00,sizeof(keybuf));//往0x00地址写入密码break;default: //未找到相符命令时给上机发送“错误命令”的提示UartWrite(bad command.\r\n, sizeof(bad command.\r\n)-1);return;}buf[len] \r; //有效命令被执行后在原命令帧之后添加buf[len] \n; //回车换行符后返回给上位机表示已执行UartWrite(buf, len);
}/* 取出E2PROM 0x00 0x01地址的值转换为16进制的数作为密码 addr为起始地址该密码是4位数 */unsigned int DataConversion(unsigned char addr){ // unsigned char tmp;unsigned int key;unsigned char buf[4];//定义一个数组存储密码pdata unsigned int str[4]; //注意这个数组一定要用intE2Read(buf,addr,sizeof(buf));//由该函数buf[4]数组已经取得E2PROM 0x00 0x01 0x02 0x03地址的值分别//存储在buf[4] 数组中str[0] (buf[0] - 0)*1000;str[1] (buf[1] - 0)*100;str[2] (buf[2] - 0)*10;str[3] (buf[3] - 0)*1;key str[0] str[1] str[2] str[3] ;return key;}/*配置并启动T0,ms-T0定时时间 */void ConfigTimer0(unsigned int ms){ unsigned long tmp; //临时变量tmp 11059200 / 12; //每秒机器周期数tmp (tmp * ms)/1000; //计算传递实参的机器周期数tmp 65536 - tmp ; //设置定时器重载初值tmp tmp 28; //初值补偿T0RH (unsigned char)(tmp 8); //初值高低字节分离T0RL (unsigned char)tmp;TMOD 0xF0; //清零定时器0控制位TMOD | 0x01; //选择定时器0的工作模式TH0 T0RH; //定时器0高低字节赋值TL0 T0RL; ET0 1; //定时器0中断使能TR0 1; //使能定时器0}/*将一个无符号长整型的数字显示到数码管伤num位待显示数字 */void ShowNumber(unsigned long num){signed char i;unsigned char buf[6]; //把长整形数每个进制位上的数转化成十进制的数共6个存入数组for(i 0; i 6; i){buf[i] num %10;num num / 10;}for(i 5;i 1;i--) //从高位起遇到0转换为0xff不显示遇到非零则退出循环{if(buf[i] 0 )LedBuff[i] 0xFF; // 作用高位是零则不显示elsebreak;}for(; i 0; i--) //剩余低位都如实转换成数码管要显示的数{LedBuff[i] LedChar[buf[i]];}}/* 按键动作函数根据密码锁键码执行相应的操作passcode 为按键键码 */void PasswordAction(unsigned char passcode){ static unsigned char cnt 0;static unsigned int buf[4] {0,0,0,0};static unsigned i 0xFF;if(KeyLock 1) //为1可以输入密码为0密码输入屏蔽{ShowNumber(passcode - 0x30);if(passcode 0x30 passcode 0x39 | passcode 0x1B){ //确定输入的是数字键cnt;switch(cnt){case 1: buf[0] (passcode - 0x30)*1000 ; i i 1 | PressMark; break;case 2: buf[1] (passcode - 0x30)*100 ; i i 1 | PressMark; break;case 3: buf[2] (passcode - 0x30)*10 ; i i 1 | PressMark; break;case 4: buf[3] (passcode - 0x30)*1 ; i i 1 | PressMark; break;default: break;}}cnt 0x03;if(cnt 0){PressStyle i 0x0F;backword (buf[0]buf[1]buf[2]buf[3]);buf[0] 0;buf[1] 0;buf[2] 0;buf[3] 0;i 0xFF;}if(passcode 0x1B) //初始化键{buf[0] 0;buf[1] 0;buf[2] 0;buf[3] 0;cnt 0;i 0xFF;}}}/* 密码设置函数 */void Password(unsigned int Num){if(Num backword PressStyle 0x00)//按键方式目前都是短键PasswordLock 1;}/* 按键动作函数根据键码执行相应的操作keycode 为按键键码 */void KeyAction(unsigned char keycode){if(PasswordLock 1) //为1使能定时操作为0屏蔽按键操作{KeyLock 0;if(keycode 0x26) //向上键倒计时设定值每按一下加1{ if(CountDown 9999) //最大计数9999{// LongPress 0;CountDown;ShowNumber(CountDown);}}else if (keycode 0x28) //向下键 倒计时设定值递减{ if(CountDown 1) //最小计时1s{// LongPress 0;CountDown--;ShowNumber(CountDown);}}else if(keycode 0x0D) //回车键 启动倒计时{if(EntelLongPress | Locksta 1){flagStart 0;EntelLongPress 0;LongPress 0;}else{flagStart 1;LongPress 0;}}else if(keycode 0x1B) //ESC 键 取消倒计时{LongPress 0;enBuzz 0;LedBuff[6] 0xFF;flagStart 0;CountDown 0;ShowNumber(0);}}}/*按键驱动函数检测按键动作调度相应动作函数需要在主函数中调用 */ void KeyDriver(){unsigned char i,j,cnt; static unsigned char pdata backup[4][4] { //按键值备份保存前一次的值{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};static unsigned long pdata TimeThr[4][4] { //快速输入执行的时间阈值{500,500,500,500},{500,500,500,500},{500,500,500,500},{500,500,500,500}};for(i 0; i4; i) //循环扫描4*4的矩阵按键{for(j 0; j4; j){if(backup[i][j] ! KeySta[i][j]) //按键动作检查{ if(PasswordLock){if(backup[i][j] 0 LongPress 0 ) //前态如果是0那么现态是1,开关从按住弹起{if( Locksta 0)KeyAction(KeyCodeMap[i][j]); //调用按键动作函数Locksta 0;}if(backup[i][j] 0 LongPress 1 ) //前态如果是0那么现态是1,开关从按住弹起{LongPress 0;}}if(KeyLock 1)//锁住密码锁{if(backup[i][j] 0 PressMark 0) //前态如果是0那么现态是1,开关从按住弹起{ PasswordAction(KeyCodeMap[i][j]); //调用密码锁按键动作}if(backup[i][j] 0 PressMark 1){PressMark 0; }} backup[i][j] KeySta[i][j]; //刷新前一次备份值cnt 0;}if(KeyDownTime[i][j] 0) //检测执行快速输入{if(KeyDownTime[i][j] TimeThr[i][j]){ if(KeyLock 1) //密码锁键盘使能{ if( PressMark 0) { PressMark 1;PasswordAction(KeyCodeMap[i][j]); //调用密码锁按键动作 }TimeThr[i][j] 100; //时间阈值增加200ms以准备下一次执行}if(PasswordLock 1) { LongPress 1; //达到阈值时执行一次动作KeyAction(KeyCodeMap[i][j]); //调用按键动作函数 TimeThr[i][j] 100; //时间阈值增加200ms以准备下一次执行cnt;if(cnt 11) {cnt 0;if(i 3 j 2) //注意entel键是3行2列不是4行3列因为第1行一1列是0,0{EntelLongPress 1;Locksta 1; //按键锁标志防止弹起进入短按函数}} } }} else // 按键弹起时复位阈值时间{TimeThr[i][j] 500; // 恢复1s的初始阈值时间//矩阵函数cnt 0复位语句语句不能放在该处// 每4次中断才能扫描到一次对应按住的按键} //其他时间一直进入的都是else函数把它的比较阈值赋值为500因此cnt0 不能放在这个位置。}}}/*按键扫描函数 需要在定时中断中调用 */void KeyScan(){unsigned char i;static unsigned char keyout 0;static unsigned char keybuf[4][4] {{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},};//将一行的4个按键值移入缓冲区keybuf[keyout][0] (keybuf[keyout][0] 1) | KEY_IN_1;keybuf[keyout][1] (keybuf[keyout][1] 1) | KEY_IN_2;keybuf[keyout][2] (keybuf[keyout][2] 1) | KEY_IN_3;keybuf[keyout][3] (keybuf[keyout][3] 1) | KEY_IN_4;//消抖后更新按键状态for(i 0; i 4; i){if((keybuf[keyout][i] 0x0F) 0x00){//连续4次烧苗值为0即4x4ms内都是按下状态时可以认为按键已稳定的按下KeySta[keyout][i] 0;KeyDownTime[keyout][i] 4;//按下的持续时间累加}else if((keybuf[keyout][i] 0x0F) 0x0F){ //连续4次扫描值为1即4x4ms内都是弹起状态时可认为按键已稳定的弹起KeySta[keyout][i] 1;KeyDownTime[keyout][i] 0;//按下的持续时间清零}}keyout; //输出索引递增keyout 0x03; //索引值逢4归0switch(keyout) //根据索引释放当前输出引脚拉低下次的输出引脚{case 0: KEY_OUT_4 1; KEY_OUT_1 0; break;case 1: KEY_OUT_1 1; KEY_OUT_2 0; break;case 2: KEY_OUT_2 1; KEY_OUT_3 0; break;case 3: KEY_OUT_3 1; KEY_OUT_4 0; break;default: break;}}/* LED动态扫描函数需要在定时中断中调用 */void LedScan(){static unsigned char i 0; //动态扫描索引P0 0xFF; //消除鬼影P1 (P1 0xF8) | i; // 0xF8 1111 1000位选索引值赋值到P1口低3位P0 LedBuff[i]; //缓冲区中索引位置的数据送到P0口if(i 6) //索引递增循环遍历整个缓冲区i;elsei 0;}/* T0中断服务函数完成数码管、按键扫描与定时 */void interruptTimer0() interrupt 1{static unsigned int tmr1s 0; //1秒定时器TH0 T0RH;TL0 T0RL;UartRxMonitor(2); //串口接收监控if(enBuzz)BUZZ ~BUZZ; //蜂鸣器发声处理else //驱动蜂鸣器发声BUZZ 1;LedScan(); //关闭蜂鸣器KeyScan(); //LED 扫描显示if(flagStart) //按键扫描{ //倒计时启动时处理1秒定时tmr1s;if(tmr1s 500){tmr1s 0;flag1s 1;}}else{tmr1s 0; //倒计时未启动时1秒定时器始终归零}}
Uart.c #include reg52.hbit flagFrame 0; //帧接收完成标志即接收到一帧新数据
bit flagTxd 0; //单字节发送完成标志用来替代TXD中断标志位
unsigned char cntRxd 0; //接收字节计数器
unsigned char pdata bufRxd[64]; //接收字节缓冲区extern void UartAction(unsigned char *buf, unsigned char len);/* 串口配置函数baud-通信波特率 */
void ConfigUART(unsigned int baud)
{SCON 0x50; //配置串口为模式1TMOD 0x0F; //清零T1的控制位TMOD | 0x20; //配置T1为模式2TH1 256 - (11059200/12/32)/baud; //计算T1重载值TL1 TH1; //初值等于重载值ET1 0; //禁止T1中断ES 1; //使能串口中断TR1 1; //启动T1
}
/* 串口数据写入即串口发送函数buf-待发送数据的指针len-指定的发送长度 */
void UartWrite(unsigned char *buf, unsigned char len)
{while (len--) //循环发送所有字节{flagTxd 0; //清零发送标志SBUF *buf; //发送一个字节数据while (!flagTxd); //等待该字节发送完成}
}
/* 串口数据读取函数buf-接收指针len-指定的读取长度返回值-实际读到的长度 */
unsigned char UartRead(unsigned char *buf, unsigned char len)
{unsigned char i;if (len cntRxd) //指定读取长度大于实际接收到的数据长度时{ //读取长度设置为实际接收到的数据长度len cntRxd;}for (i0; ilen; i) //拷贝接收到的数据到接收指针上{*buf bufRxd[i];}cntRxd 0; //接收计数器清零return len; //返回实际读取长度
}
/* 串口接收监控由空闲时间判定帧结束需在定时中断中调用ms-定时间隔 */
void UartRxMonitor(unsigned char ms)
{static unsigned char cntbkp 0;static unsigned char idletmr 0;if (cntRxd 0) //接收计数器大于零时监控总线空闲时间{if (cntbkp ! cntRxd) //接收计数器改变即刚接收到数据时清零空闲计时{cntbkp cntRxd;idletmr 0;}else //接收计数器未改变即总线空闲时累积空闲时间{if (idletmr 15) //空闲计时小于30ms时持续累加{idletmr ms;if (idletmr 15) //空闲时间达到30ms时即判定为一帧接收完毕,该程序是2ms进入一次T0中断{flagFrame 1; //设置帧接收完成标志}}}}else{cntbkp 0;}
}
/* 串口驱动函数监测数据帧的接收调度功能函数需在主循环中调用 */
void UartDriver()
{unsigned char len;unsigned char pdata buf[40];if (flagFrame) //有命令到达时读取处理该命令{flagFrame 0;len UartRead(buf, sizeof(buf)); //将接收到的命令读取到缓冲区中UartAction(buf, len); //传递数据帧调用动作执行函数}
}
/* 串口中断服务函数 */
void InterruptUART() interrupt 4
{if (RI) //接收到新字节{RI 0; //清零接收中断标志位if (cntRxd sizeof(bufRxd)) //接收缓冲区尚未用完时{ //保存接收字节并递增计数器bufRxd[cntRxd] SBUF;}}if (TI) //字节发送完毕{TI 0; //清零发送中断标志位flagTxd 1; //设置字节发送完成标志}
}I2C.c
# includereg52.h
# includeintrins.h# define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
// # define I2CDelay() {_nop_();}
sbit I2C_SCL P3^7;
sbit I2C_SDA P3^6; /* 产生总线起始信号 */
void I2CStart()
{I2C_SDA 1; //首先确保SDASCL都是高电平I2C_SCL 1;I2CDelay();I2C_SDA 0; //先拉低SDAI2CDelay();I2C_SCL 0; //再拉低SCL}/* 产生总线停止信号 */
void I2CStop()
{I2C_SCL 0; //首先确保SDASCL都是低电平I2C_SDA 0;I2CDelay();I2C_SCL 1; //先拉高SCL的电平I2CDelay();I2C_SDA 1; //再拉高SDA的电平I2CDelay();}/*I2C总线写操作dat为待写入字节返回值为从机的应答位的值 */
bit I2CWrite(unsigned char dat)
{bit ack; //用于暂存应带位的值unsigned char mask; //用于探测字节内一位值的掩码变量for(mask 0x80; mask ! 0; mask 1)//从高位依次进行{if((maskdat) 0)I2C_SDA 0;elseI2C_SDA 1; //通过上述语句把dat的8位电平信息从最高位开始依次发出I2CDelay();I2C_SCL 1;I2CDelay();I2C_SCL 0; //再拉低SCL完成一个位周期}I2C_SDA 1; //8位数据发送完后主机释放SDA以检测从机应答I2CDelay();I2C_SCL 1; //拉高SCLack I2C_SDA;//读取此时的SDA的值即为从机的应答值I2CDelay();I2C_SCL 0; //再拉低SCL完成应答位并保持住总线return(~ack); //应答值取反符合通常的逻辑0 不纯在//或忙或写入失败1 纯在且空闲或者写入成功}
/* I2C总线读操作并发送非应答信号返回值为读到的字节 */
unsigned char I2CReadNAK()
{unsigned char mask;unsigned char dat;I2C_SDA 1; //首先确保主机释放SDAfor(mask 0x80; mask ! 0; mask 1) //从高位到低位依次进行{I2CDelay();I2C_SCL 1; //拉高SCLif(I2C_SDA 0) //读取SDA的值dat ~mask; //为0时dat中对应位清零elsedat | mask; //为1时dat中对应位置1I2CDelay();I2C_SCL 0; //再拉低SCL以使从机发送下一位}I2C_SDA 1; //8位数据发送完后拉高SDA发送非应答信号I2CDelay();I2C_SCL 1; //拉高SCLI2CDelay();I2C_SCL 0; //再拉低SCL完成非应答位并保持住总线return dat;}/* I2C总线操作并发送应答信号返回值为读到的字节 */
unsigned char I2CReadACK()
{unsigned char mask;unsigned char dat;I2C_SDA 1;for(mask 0x80; mask ! 0; mask 1){I2CDelay();I2C_SCL 1;if(I2C_SDA 0)dat ~mask;elsedat | mask;I2CDelay();I2C_SCL 0;//再拉低SCL以使从机发送出下一位}I2C_SDA 0; //8位数据发送完后拉低SDA。发送应答信号I2CDelay();I2C_SCL 1; //拉高SCLI2CDelay();I2C_SCL 0; //再拉低SCL完成应答并保持住总线return dat;}
E2PROM.c
# includereg52.hextern void I2CStart();
extern void I2CStop();
extern unsigned char I2CReadACK();
extern unsigned char I2CReadNAK();
extern bit I2CWrite(unsigned char dat);/*E2读取函数buf为数据接收指针addr为E2中的起始地址len为读取长度 ,这是一个读取相应地址并写入buf数组 的程序 */void E2Read(unsigned char* buf,unsigned char addr,unsigned char len )
{do{I2CStart(); //用寻址操作查询当前是否可以进行读写操作if(I2CWrite(0x501)) //应答则跳出循环非应答则进行下一次查询{break;}I2CStop();}while(1);I2CWrite(addr); //写入起始地址I2CStart(); //发送重复启动信号I2CWrite((0x501) | 0x01);//寻址器件后续为读操作while(len 1) //连续读取len-1个字节{*buf I2CReadACK(); //最后字节前为读取操作应答len--;}*buf I2CReadNAK(); //最后一个字节为读操作非应答I2CStop();
}/* E2写入函数buf为数据指针addr为E2中的起始地址len为写入长度 */
void E2Write(unsigned char* buf,unsigned char addr, unsigned char len)
{while(len 0){ //等待上次写入操作完成do{ //用寻址操作查询当前是否可以进行读写操作I2CStart(); if(I2CWrite(0x50 1)) //应答则跳出循环非应答则进行下一次查询{break;}I2CStop();}while(1);
//按页写入模式连续写入字节 I2CWrite(addr); //写入起始地址while(len 0){I2CWrite(*buf); //写入一个字节数据len--; //待写入长度计数递减addr; //E2地址递增if((addr0x07) 0)//检查地址是否到达页边界24C02每页8字节{ //所以检测低3位是否为0即可break; //到达页边界时跳出循环结束本次写操作}}I2CStop();}}
Lcd1602.c
#include reg52.h#define LCD1602_DB P0
sbit LCD1602_RS P1^0;
sbit LCD1602_RW P1^1;
sbit LCD1602_E P1^5;/* 等待液晶准备好 */
void LcdWaitReady()
{unsigned char sta;LCD1602_DB 0xFF;LCD1602_RS 0;LCD1602_RW 1;do {LCD1602_E 1;sta LCD1602_DB; //读取状态字LCD1602_E 0;} while (sta 0x80); //bit7等于1表示液晶正忙重复检测直到其等于0为止
}
/* 向LCD1602液晶写入一字节命令cmd-待写入命令值 */
void LcdWriteCmd(unsigned char cmd)
{LcdWaitReady();LCD1602_RS 0;LCD1602_RW 0;LCD1602_DB cmd;LCD1602_E 1;LCD1602_E 0;
}
/* 向LCD1602液晶写入一字节数据dat-待写入数据值 */
void LcdWriteDat(unsigned char dat)
{LcdWaitReady();LCD1602_RS 1;LCD1602_RW 0;LCD1602_DB dat;LCD1602_E 1;LCD1602_E 0;
}
/* 设置显示RAM起始地址亦即光标位置(x,y)-对应屏幕上的字符坐标 */
void LcdSetCursor(unsigned char x, unsigned char y)
{unsigned char addr;if (y 0) //由输入的屏幕坐标计算显示RAM的地址addr 0x00 x; //第一行字符地址从0x00起始elseaddr 0x40 x; //第二行字符地址从0x40起始LcdWriteCmd(addr | 0x80); //设置RAM地址
}
/* 在液晶上显示字符串(x,y)-对应屏幕上的起始坐标str-字符串指针 */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str)
{LcdSetCursor(x, y); //设置起始地址while (*str ! \0) //连续写入字符串数据直到检测到结束符{LcdWriteDat(*str);}
}
/* 区域清除清除从(x,y)坐标起始的len个字符位 */
void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len)
{LcdSetCursor(x, y); //设置起始地址while (len--) //连续写入空格{LcdWriteDat( );}
}
/* 初始化1602液晶 */
void InitLcd1602()
{// LcdWriteCmd(0x38); //16*2显示5*7点阵8位数据接口// LcdWriteCmd(0x0C); //显示器开光标关闭// LcdWriteCmd(0x06); //文字不动地址自动1// LcdWriteCmd(0x01); //清屏LcdWriteCmd(0x38);//0x38 0011 1000 16*2显示5*7点阵8位数据接口LcdWriteCmd(0x08);//显示关闭LcdWriteCmd(0x01);//清屏LcdWriteCmd(0x06);//0x04 0000 0100 文字不动地址自动加1LcdWriteCmd(0x0C);//显示器开 光标关闭
}贴个相关操作视频
UART和I2C通信综合应用_哔哩哔哩_bilibili UART串口工作
简易的流程图 https://docs.qq.com/s/sUMg3jiBzjcUANe_68SHnq
只是UART工作的流程图不是全程序的笔者自己编的凑活看吧。