当前位置: 首页 > news >正文

初学51单片机之I2C总线与E2PROM以及UART简单实例应用

      这是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工作的流程图不是全程序的,笔者自己编的,凑活看吧。

相关文章:

初学51单片机之I2C总线与E2PROM以及UART简单实例应用

这是I2C的系列的第三篇&#xff0c;这篇主要是写一个简单的程序来实践一下相关的内容。前面博主写过一个电子密码锁的程序初学51单片机之简易电子密码锁及PWM应用扩展_51单片机设计电子密码锁-CSDN博客 本篇主要是在此基础上修改下程序&#xff0c;让密码存储在E2PROM中&#…...

软考高级软件架构师论文——论Web系统的测试技术及其应用

【摘要】 本人于2023年8月参与了某地级市的市级机关电子政务信息系统的建设工作,该项目是该市机关的电子政务网建设计划的一部分,笔者在该项目中担任项目经理和系统分析师一职,主要负责项目的日常全面管理和质量保证与质量控制工作。该项目是基于WEB系统的,由于WEB系统具有…...

快速总结AFPN

AFPN: Asymptotic Feature Pyramid Network for Object Detection 解决的问题 特征金字塔架构的提出是为了解决尺度变化的问题&#xff0c;图像中物体真正有用的特征在顶部最高层需要通过多个中间尺度传播&#xff0c;并与这些尺度的特征交互&#xff0c;才能与底部的低层特征…...

Linux 内核中USB鼠标枚举失败问题总结

一、环境&#xff1a; 机器平台&#xff1a;linux 内核版本&#xff1a;linux-3.4 二、问题&#xff1a; USB鼠标接入后报错&#xff0c;log显示设备无法枚举 usb 1-1: new low-speed USB device number 10 using musb-hdrc hub 1-0:1.0: unable to enumerate USB device o…...

十六进制转二进制

128 64 32 16 8 4 2 1 十六进制&#xff1a;0~9ABCDEF&#xff08;A是10、B是11、C是12、D是13、E是14、F是15&#xff09; 每一个十六进制位转换成4个二进制位&#xff0c;左边不足4个补0 示例&#xff1a; 109CBE&#xff1a;0001 0000 1001 1100 1011 1110 8 4 2 1 1 …...

Python保存CSV文件,Excel打开后中文乱码

情况描述 在做多语言文件处理时&#xff0c; 使用 pandas&#xff0c; 并且指定了encoding为 UTF-8&#xff0c; 在 IDE&#xff0c; Sublime等编辑器上查看都显示正常&#xff0c;使用Excel打开非英文字符&#xff0c; 例如汉字&#xff0c; 阿拉伯文&#xff0c; 希伯来文等显…...

数据湖数据仓库数据集市数据清理以及DataOps

一提到大数据我们就知道是海量数据&#xff0c;但是我们并不了解需要从哪些维度去考虑这些数据的存储。比如 数据湖、数据仓库、数据集市&#xff0c;以及数据自动化应用DataOps有哪些实现方式和实际应用&#xff0c;这篇文章将浅显的做一次介绍。 数据湖 数据湖是一种以自然…...

「Ubuntu」文件权限说明(drwxr-xr-x)

我们在使用Ubuntu 查看文件信息时&#xff0c;常常使用 ll 命令查看&#xff0c;但是输出的详细信息有些复杂&#xff0c;特别是 类似与 drwxr-xr-x 的字符串&#xff0c;在此进行详细解释下 属主&#xff1a;所属用户 属组&#xff1a;文件所属组别 drwxr-xr-x 7 apps root 4…...

JS-学生管理系统(功能实现)

基础知识点掌握&#xff1a; 1.DOM节点 首先DOM树当做一颗到着生长的树&#xff0c;DOM树里面的每一个内容称为节点 节点类型&#xff1a; 属性节点元素节点文本节点其他 2.查找节点&#xff1a; 查找节点分为3个类型&#xff1a; 父节点子节点兄弟节点 &#xff08;1&…...

C# 屏幕录制工具

屏幕录制工具 开发语音&#xff1a;C# vb.net 下载地址&#xff1a;https://download.csdn.net/download/polloo2012/89879996 功能&#xff1a;屏幕录制&#xff0c;声卡采集&#xff0c;麦克风采集。 屏幕录制&#xff1a;录制屏幕所有操作&#xff0c;并转换视频格式&…...

前端开发攻略---前端ocr图片文字提取功能

1、引入资源 通过链接引用 <script src"https://cdn.bootcdn.net/ajax/libs/tesseract.js/5.1.0/tesseract.min.js"></script> npm或其他方式下载 npm i tesseract 2、示例 <!DOCTYPE html> <html lang"en"><head><meta…...

平凯星辰亮相 2024开放原子开源生态大会,分享开源教育及社区治理经验

9 月 25-27 日&#xff0c;2024 开放原子开源生态大会在北京成功举办&#xff0c;本次大会以“开源赋能产业&#xff0c;生态共筑未来”为主题&#xff0c;由开放原子开源基金会主办&#xff0c;聚焦地方开源实践、企业开源建设思路&#xff0c;围绕开源生态建设&#xff0c;突…...

OCR经典神经网络(二)文本检测算法DBNet算法原理及其在icdar15数据集上的应用

OCR经典神经网络(二)文本检测算法DBNet算法原理及其在icdar15数据集上的应用 场景文本检测任务&#xff0c;一直以来是OCR整个任务中最为重要的一环。虽然有一些相关工作是端对端的&#xff0c;但是从工业界来看&#xff0c;相关落地应用较为困难。因此&#xff0c;两阶段的OC…...

论文笔记:Prompt-Based Meta-Learning For Few-shot Text Classification

论文来源&#xff1a;EMNLP 2022 论文地址&#xff1a;2022.emnlp-main.87.pdf (aclanthology.org) 代码地址&#xff1a;GitHub - MGHZHANG/PBML GB/T 7714 Zhang H, Zhang X, Huang H, et al. Prompt-Based Meta-Learning For Few-shot Text Classification[C]//Proceedi…...

光耦知识分享 | AC晶体管光耦在照明控制领域的应用与选型推荐

随着智能建筑与智能家居技术的迅猛崛起&#xff0c;照明控制技术亦随之日新月异。传统的照明控制手段已难以适应现代化、智能化的新需求。在这样的技术革新浪潮中&#xff0c;AC晶体管光耦以其卓越的性能和可靠性&#xff0c;成为了照明控制领域中不可或缺的一环。AC晶体管光耦…...

RBTree(红黑树)的介绍和实现

欢迎来到杀马特的主页&#xff1a;羑悻的小杀马特.-CSDN博客 目录 ​编辑 一红黑树介绍&#xff1a; 1.1红黑树概念&#xff1a; 1.2红黑树遵循的原则&#xff1a; 1.3红黑树效率分析&#xff1a; 二.红黑树的实现&#xff1a; 2.1红黑树结构&#xff1a; 2.2红黑树节点…...

信息安全数学基础(30)指数及其基本性质

一、指数的定义 数学定义&#xff1a; 指数是幂运算a3表示3个a相乘。 经济学定义&#xff1a; 广义地讲&#xff0c;任何两个数值对比形成的相对数都可以称为指数。狭义地讲&#xff0c;指数是用于测定多个项目在不同场合下综合变动的一种特殊相对数。 二、指数的基本性质…...

Android开发首页底部tab切换图标有动画效果

Android开发首页底部tab切换图标有动画效果 主页tab切换很正常&#xff0c;但往往加上写动画更好看 一、思路&#xff1a; 用属性动画&#xff0c;并且事先准备多张图片&#xff0c;用于切换后播放动画 二、效果图&#xff1a; 单纯图看不出来&#xff0c;看下视频效果 An…...

Web前端高级工程师培训:异步处理专题

异步处理专题 课前准备 工具 编辑器 VSCode浏览器 Chorme 前置知识 ES6基础语法 课堂主题 同步及异步概念方块运动的实现promise的用法then的返还值Async 函数 和 await 课堂目标 理解并学会使用promise使用方式以及async 、await的使用 同步异步概念 js是单线程 单线程…...

ESP32-C3实现热点并在浏览器中输入域名访问网页

源代码 #include <WiFi.h> // 引入 WiFi 库&#xff0c;用于处理网络连接 #include <WebServer.h> // 引入 WebServer 库&#xff0c;用于创建 HTTP 服务器 #include <DNSServer.h> // 引入 DNSServer 库&#xff0c;用于处理 DNS 请…...

基于Python的自然语言处理系列(32):spaCy属性扩展

1. 介绍 在 spaCy 中,自定义扩展属性让我们能够为 Doc、Token 和 Span 对象添加元数据。通过这些扩展属性,开发者可以根据需要存储额外的上下文信息,或者动态计算属性值。 自定义属性通过点下划线(dot-underscore)属性访问,例如 token._.is_color。这种命名方式确保这些属…...

STM32 输入捕获模式详解:PWM 输入捕获与 PWI 模式(续篇)

在前两篇文章中&#xff0c;我们探讨了 STM32 输入捕获的基础和 PWI 模式的工作原理&#xff0c;特别是定时器的两个通道如何协同工作以捕获 PWM 信号。本文将进一步结合 STM32 标准库函数中的 TIM_PWMIConfig()&#xff0c;来讲解如何通过库函数配置定时器实现 PWI 模式。 我…...

【C++】set/map(重点解析)

目录 一、关联式容器和序列式容器 二、C中的键值对——pair 1.概念 2.定义 3.构造pair 三.set 1.construct构造 2.iterator迭代器 3.insert插入 4.erase删除 5.find查找 6.lower_bound和upper_bound 7.count 四.multiset 五.map 1.insert 2.operator[] 一、…...

【算法篇】动态规划类(1)(笔记)

目录 一、理论基础 1. 大纲 2. 动态规划的解题步骤 二、LeetCode 题目 1. 斐波那契数 2. 爬楼梯 3. 使用最小花费爬楼梯 4. 不同路径 5. 不同路径 II 6. 整数拆分 7. 不同的二叉搜索树 一、理论基础 1. 大纲 动态规划&#xff0c;英文&#xff1a;Dynamic Programm…...

mysql学习教程,从入门到精通,SQL 约束(Constraints)(41)

在数据库设计中&#xff0c;约束&#xff08;Constraints&#xff09;用于确保数据的准确性和完整性。它们通过限制可以插入到数据库表中的数据类型来防止无效数据。SQL 中有几种常见的约束类型&#xff0c;包括主键约束&#xff08;Primary Key&#xff09;、外键约束&#xf…...

使用CSS3与JavaScript实现炫酷的3D旋转魔方及九宫格交换动效

文章目录 前言一、项目需求背景二、CSS3 3D基础知识介绍2.1 什么是CSS3 3D&#xff1f;2.2 主要使用的CSS属性 三、使用HTML和CSS搭建魔方结构四、让魔方动起来&#xff1a;CSS3动画五、九宫格数字交换的JavaScript实现5.1 九宫格布局5.2 随机交换数字 六、随机交换与相邻格子的…...

springboot项目通过maven的profile功能实现通过不同文件夹的方式来组织不同环境配置文件

写在前面 本文看下springboot项目如何通过文件夹的方式来组织不同环境配置文件。 1&#xff1a;正文 一般的我们写springboot项目时配置文件是这个样子的&#xff1a; appliction.yaml --> 通过spring.profiles.activexxx来激活某个指定后缀的配置文件 application-evn1…...

GAN(Generative Adversarial Nets)

GAN(Generative Adversarial Nets) 引言 GAN由Ian J. Goodfellow等人提出&#xff0c;是Ian J. Goodfellow的代表作之一&#xff0c;他还出版了大家耳熟能详的花书&#xff08;Deep Learning深度学习&#xff09;&#xff0c;GAN主要的思想是同时训练两个模型&#xff0c;生成…...

linux下使用mpi求自然数和

搭建MPI并行计算环境&#xff0c;编写 MPI程序&#xff0c;求和 1 23....1 0000。 要求: 1.使用100个进程; 2.进程0计算1 2...100, 进程1计算101 102... 200, ..... 进程99计算9901 9902... 10000; 3.调用计时函数,分别输出每个进程的计算时间; 4.需使用MPI集群通信函数和同…...

WebGl学习使用attribute变量绘制一个水平移动的点

在WebGL编程中&#xff0c;attribute变量是一种特殊类型的变量&#xff0c;用于从客户端传递数据到顶点着色器。这些数据通常包括顶点的位置、颜色、纹理坐标等&#xff0c;它们是与每个顶点直接相关的信息。attribute变量在顶点着色器中声明&#xff0c;并且对于每个顶点来说都…...