wordpress音乐站代做seo关键词排名
这是I2C的系列的第三篇,这篇主要是写一个简单的程序来实践一下相关的内容。前面博主写过一个电子密码锁的程序初学51单片机之简易电子密码锁及PWM应用扩展_51单片机设计电子密码锁-CSDN博客
本篇主要是在此基础上修改下程序,让密码存储在E2PROM中,并且可以通过UART串口通信在线修改E2PROM存储的密码。
简单的介绍下程序的功能:
1:笔者的开发版在烧录该程序后一上电,除了数字键可以使能外其他按键是没有功能的。因此需要输入相应的密码才能使能其他按键的功能。
2:程序的密码是存放在24C02这个E2PROM器件“非易失区”的4个存储地址中(0x00,0x01,0x02,0x03),以密码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.h>sbit 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-待比较指针1,ptr2-待比较指针2,len-待比较长度返回值-两段内存数据完全相同时返回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 (i=0; i<sizeof(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, buf+cmdLen[2]); //显示命令后的字符串i = len - cmdLen[2]; //计算有效字符个数if (i < 16) //有效字符少于16时,清除液晶上的后续字符位{LcdAreaClear(i, 0, 16-i);}for(j = 0; j<4;j++) //约定是设置4位密码,因此输入密码的时候要注意长度,输入5位密码也只使能前4位{keybuf[j] = *(buf+cmdLen[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; i<4; i++) //循环扫描4*4的矩阵按键{for(j = 0; j<4; 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,因此cnt=0 不能放在这个位置。}}}/*按键扫描函数 ,需要在定时中断中调用 */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.h>bit 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 (i=0; i<len; 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
# include<reg52.h>
# include<intrins.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; //首先确保SDA,SCL都是高电平I2C_SCL = 1;I2CDelay();I2C_SDA = 0; //先拉低SDAI2CDelay();I2C_SCL = 0; //再拉低SCL}/* 产生总线停止信号 */
void I2CStop()
{I2C_SCL = 0; //首先确保SDA,SCL都是低电平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((mask&dat) == 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
# include<reg52.h>extern 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(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询{break;}I2CStop();}while(1);I2CWrite(addr); //写入起始地址I2CStart(); //发送重复启动信号I2CWrite((0x50<<1) | 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((addr&0x07) == 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工作的流程图不是全程序的,笔者自己编的,凑活看吧。