| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 嵌入式 -> 郭天祥的10天学会51单片机_第九节 -> 正文阅读 |
|
[嵌入式]郭天祥的10天学会51单片机_第九节 |
开发板上的蜂鸣器下面是温度传感器DS18B20 DA转换器的下面是SPI总线(RFR、IOUT、DI0和GND) I2C总线和SPI总线用的多。 I2C总线仲裁:具有?C总线接口的设备都接在总线上,主机和哪个设备进行通信时先在总线上发个地址码过去,总线上全部响应地址码,所有?C总线上的设备都检测这个地址码,哪个设备的地址码相符就和主机通信 见LESSON8_IIC总线协议的PPT的P3的图,各器件的SDA及SCL都是线“与”关系(各设备的SDA做线“与”运算,结果送到SDA总线上,SCL也同理),?I2C总线必须接上拉电阻,?一般为10K MSB为最高位,LSB为最低位 漏极开路相当于场效应管来说的,和三极管一样都具有开关的作用,场效应管还有放大管的作用,场效应管是压控压型 集电极开路对应三极管来说的,三极管的集电极不能输出,只有接上拉电阻时才能输出,开关和放大管的作用,压控流型,即PN结利用导通的压降来控制导通电流的大小 I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。 见LESSON8_IIC总线协议的PPT的P3的图,SDA上的交叉部分表示允许数据变化 SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。? 每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位);应答位必须是由从机发给主机的;第九位应答位如果很长时间主机没有收到就默认收到了 开发板上AD和DA是并口一次都传走八位数据,而I2C总线是串口,八位数据一个一个传 在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R),是主机传送完一个数据后用0或1表示是下一次是主机发送数据还是接收数据。 a、主机向从机发送数据,数据传送方向在整个传送过程中不变: S表示起始信号,后面是从机地址,0表示由主机要向从机发送数据,A表示从机的应答(低电平表示),A非表示非应答(高电平表示),P表示终止信号 有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送 A/?表示应不应答不管了 b、主机在第一个字节后,立即向从机读数据 从机接到主机的信号后,应答然后从机给主机传数据,主机再应答,从机再给主机传数据,主机不应答,主机再发送停止信号 c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相 主机起始信号,从机地址,0表示主机要向从机发送数据,从机应答,主机向从机发送数据,A/?表示应不应答不管了,读写方向要改变,主机要向从机读取数据了,就重新发出起始信号,从机地址,1表示主机要接收从机发送的数据,从机给了应答,从机向主机发送数据,主机不给应答,发出停止信号 参考LESSON8_IIC总线协议的PPT的P14和P15 主机可以采用不带I2C总线接口的单片机,如80C51、AT89C2051等单片机,利用软件实现I2C总线的数据传送,即软件与硬件结合的信号模拟,很多高级单片机都自带有?C总线,把数据放到寄存器入SBUF,单片机自动传送数据,不用软件实现 参考LESSON8_IIC总线协议的PPT的P17,P18,和P19 SomeNop( )是延时函数 见LESSON8_IIC总线协议的PPT的P20,总线连接的器件下面的A2、A1、A0等是地址线, E2PROM(Electric erase program read only memory)是电可擦除编程只读存储器,掉电后程序仍然保持在存储器当中,这里用的是AT24C02 写的时候要先写地址再写数据,读的时候要先写地址再读数据 MSB是最高位,MLB是最高位,ACK是应答 参考LESSON8_IIC总线协议的PPT的P22,P23,P24和P25 读出过程:单片机先发送该器件的7位地址码和写方向位“0”(“伪写”),发送完后释放SDA线并在SCL线上产生第9个时钟信号,被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为回应,然后,再发一个字节的要读出器件存储区的首地址(针对E2PROM的存储器有地址),收到应答后,单片机要重复一次起始信号并发出器件地址和读方向位(“1”),收到器件应答后就可以读出数据字节,每读出一个字节,单片机都要回复应答信号。当最后一个字节数据读完后,单片机应返回以“非应答”(高电平),并发出终止信号以结束读出操作。 TX-1C型单片机实验板原理图,上单片机下面的24C02(图中打字打错了,打成24C00)就是E2PROM,管脚1,2,3是地址线,这里管脚1,2,3都接地,管脚5是数据线,管脚6是时钟线,管脚5和6都通过上拉电阻接电源(这里用2.7V to 2.5V),管脚4接地,管脚7WP(Write Protect)是写保护,写保护为高就是写不进去,写保护为低就是可以进行读写,这里接地,始终可以进行读写,管脚8接电源;SDA连接单片机的P2.0口,SCL连接单片机的P2.1口 E2PROM的MAP封装就是表面看不到E2PROM的引脚图,引脚在E2PROM芯片的下方 开发板上AD转换器的下面的芯片就是24C02 见AT24C02A的PPT的P11的Byte Write图,DEVICE ADDRESS是设备地址(后面加一个传送方向位(R/T),高电平为由单片机向从机读数据,低电平为由单片机向从机写数据,以后只要不改变读写方向,确定一次设备地址和传送方向位(R/T)后就不用再添加传送方向位(R/T),当要改变读写方向即改变传送方向位(R/T)时,再重新写入设备地址和改变后的传送方向位(R/T)),ACK表示应答,WORD ADDRESS写的地址(八位地址位,这里是EEPROM内部存储区的地址, I2C总线上面有多个EEPROM,DEVICE ADDRESS设备地址就是选中哪个EEPROM,WORD ADDRESS写地址就是选中EEPROM后要确定在这个EEPROM中哪个地址上写数据,这里就不用“第8位数据的传送方向位(R/T)”,等要改变读写方向时重新给初始信号从机地址传送方向位(R/T)),DATA是数据(八位数据,这里就不用“第8位数据的传送方向位(R/T)”,等要改变读写方向时重新给初始信号从机地址传送方向位(R/T)) 见AT24C02A的PPT的P11的Current Address Read(当前地址的读)图,NO ACK是不给应答,主机不再读数据就不给从机应答 谁给应答:当主机给从机数据,从机接受数据后就由从机给应答,当从机给主机数据,主机接受数据后就由主机给应答 见AT24C02A的PPT的P11的Random Read(随机地址的读),只在同一个地址上先写时给了地址,再从该设备读时就不用再给地址了 思路就是把上面的几个时序图对应的几种方式的读写都编成函数,使用时,例如读一组数据可以直接调用读函数 写程序时,先添加头文件,再定义一些类型,然后写主函数,写的时候缺什么(例如缺子函数)就补充什么 把一个数据送到EEPROM里,然后读出来,再送到发光二极管,让发光二极管闪亮: 关键是把重要时间(LESSON8_IIC总线协议的PPT的P17图中的阴影区)的变化写在程序里,我尝试把起始信号的所以过程写全,显示的结果和只把关键地方写全相同: void start()? {???? ?????? sda=0; ?????? delay(); ?????? sda=1; ?????? delay(); ?????? scl=1; ?????? delay(); ?????? sda=0; ?????? delay(); ?????? scl=0; } 先编程序,能不能读入数据 #include<reg52.h> #define uchar unsigned char sbit sda=P2^0; sbit scl=P2^1; uchar a; void delay() { ;; }//空语句,延时时间大约5us void start()? //起始信号 {???? ?????? sda=1; ?????? delay(); ?????? scl=1; ?????? delay(); ?????? sda=0; ?????? delay();//见LESSON8_IIC总线协议的PPT的P17的起始信号的图,SCL在SDA变成 //低电平之后变不变成低电平都可以,不影响起始信号的写入,所以这句的下面不用加上//SCL=0 } void stop()?? //停止信号 { ?????? sda=0;//区分起始信号、停止信号、应答信号和非应答信号,与数据信号(在时钟scl //拉高时,送数据,数据也是有0和1组成的)的区别就是:数据信号的范围包含了在时钟//scl高电平范围,见LESSON8_IIC总线协议的PPT的P16,起始信号、停止信号是在时钟//scl由高到低变化之前就拉低,应答信号和非应答信号是在传送完数据再给的(每一个被传//送的字节后面都必须跟随一位应答位(即一帧共有9位)) ?????? delay(); ?????? scl=1; ?????? delay(); ?????? sda=1; ?????? delay();//见LESSON8_IIC总线协议的PPT的P17的终止信号的图,SDA由低电平变成//高电平就完成了终止信号,SDA再变不变成低电平都不影响终止信号的写入,所以这句的//下面不用加上SDA=0,关键是把重要时间(图中的阴影区)的变化写在程序里, } void respons() ?//应答信号,是在传送完数据再给的,所以不会和传送数据混淆 { ?????? uchar i; ?????? scl=1;//在时钟为高电平时读取信号 ?????? delay(); ?????? while((sda==1)&&(i<250)) i++;//第一种情况是在前八位数据传送完,当sda变成零说明 //有应答,第二种情况是第九个时钟等待SDA变成低电平,如果超过这段时间还没给应答,//就自动认为给了应答,i从0计数到250就是这个计时作用;sda==1和i<250有一个不满 //足就退出while循环,继续往下执行 ?????? scl=0;//把时钟拉低 ?????? delay(); } void init()//总线初始化 { ?????? sda=1;//见LESSON8_IIC总线协议的PPT的P7的图,起始时SCL为高电平,SDA也 //为高电平 ?????? delay(); ?????? scl=1; ?????? delay(); } void write_byte(uchar date)//写一个字节(单片机向EEPROM写) { ?????? uchar i,temp; ?????? temp=date; ?????? for(i=0;i<8;i++) ?????? { ????????????? temp=temp<<1;//temp左移一位就将最高位送入CY当中 ????????????? scl=0; //scl拉高时才会送数据(读数据或送数据),所以开始先将scl拉低 ?????? ??? delay(); ????????????? sda=CY;? //单片机控制sda口(单片机的P2.0口)将temp的八位数据一位一位的 //都送到EEPROM中,这里先准备数据,准备好后把scl拉高,将八位数据送走 ????????????? delay(); ????????????? scl=1;//sda=CY就是准备数据,延时一会,数据稳定之后再开始让时钟为高电平,//开始送数据 ????????????? delay(); ?????? } ?????? scl=0;//scl为高电平期间将数据送走,scl为低电平时数据全送走 ?????? delay(); ?????? sda=1;//将数据总线释放,为了读完数据再读取应答信号,应答信号对应的sda为低电 //平,所以这里让sda为高电平为应答的低电平做准备;时钟来控制送数据,即使这里sda //为高电平,时钟不给高电平,送不走sda,当想要送数据时将scl拉高,sda的高电平或低//电平(对应的一位数据)被送走 ?????? delay(); } uchar read_byte()//读一个字节(单片机从EEPROM读),一位一位的读回来,都放到一个字//节当中 { ?????? uchar i,k; ?????? scl=0;//scl拉高时才会送数据(读数据或送数据),所以开始先将scl拉低 ?????? delay(); ?????? sda=1;//这句可有可无 ?????? delay(); ?????? for(i=0;i<8;i++) ?????? { ????????????? scl=1;//scl为高电平时数据稳定,才开始读数据,这句可以放在k=(k<<1)|sda之下 ????????????? delay();?? ????????????? k=(k<<1)|sda;//要读入的数据会一位一位的送到sda里,再将八位数据都放到一个 //字节里 ????????????? scl=0;//见LESSON8_IIC总线协议的PPT的P7的图 ????????????? delay();?? ?????? } ?????? return k; } void delay1(uchar x) { ?????? uchar a,b; ?????? for(a=x;a>0;a--) ?????? ?for(b=100;b>0;b--); } void main() { ?????? init(); ?????? start();//先从单片机向EEPROM写入数据,再由单片机向EEPROM读入数据 ?????? write_byte(0xa0); //见LESSON8_IIC总线协议的PPT的P22,AT24C系列E2PROM芯 //片地址的固定部分为1010,在TX-1C型单片机实验板原理图中A2、A1和A0都接地,第//8位是数据的传送方向位(R/T),这里是由单片机向EEPROM写入数据所以第8位是0,//所以写入从机地址是0xa0,即选中哪个EEPROM ?????? respons(); ?????? write_byte(3);// EEPROM内部储存区的地址,即选中所用EEPROM中的哪个地址 ?????? respons(); ?????? write_byte(0xfe);//向EEPROM的地址3写入数据0xfe ?????? respons(); ?????? stop(); ?????? ?????? delay1(100);//读和写之间时间间隔要大一些,否则会出错 ?????? start();//由单片机向EEPROM读出数据,在指定地址处读数据,见AT24C02A的PDF //的P11的Random Read;要先写选中的总线上哪个EERPOM对应的地址和该EEPROM的//储存地址,再开始读取该EEPROM的数据 ?????? write_byte(0xa0); ?????? respons(); ?????? write_byte(3); ?????? respons(); ?????? start(); ?????? write_byte(0xa1); //单片机向EEPROM读数据,所以第8位是1,设备地址+第8位,写//入EEPROM的地址是0xa1 ?????? respons(); ?????? P1=read_byte(); ?????? stop(); ?????? while(1); } 编译时将晶振改为11.0592MHZ 用仿真芯片模拟,比如当调试中需要按下键盘,仿真芯片调试中可以,而在软件调试中则按不了键盘 调试时想跳出一个函数,可以在这个函数里设置断点 起始信号,终止信号,应答信号,写信号,读信号都是在SCL高电平期间内做的 调试按钮step into作用是进入一个函数中 完整函数: #include<reg52.h> #define uchar unsigned char sbit sda=P2^0; sbit scl=P2^1; uchar a; void delay() { ;; } void start()? //开始信号 {???? ?????? sda=1; ?????? delay(); ?????? scl=1; ?????? delay(); ?????? sda=0; ?????? delay(); } void stop()?? //停止 { ?????? sda=0; ?????? delay(); ?????? scl=1; ?????? delay(); ?????? sda=1; ?????? delay(); } void respons()? //应答 { ?????? uchar i; ?????? scl=1; ?????? delay(); ?????? while((sda==1)&&(i<250))i++; ?????? scl=0; ?????? delay(); } void init() { ?????? sda=1; ?????? delay(); ?????? scl=1; ?????? delay(); } void write_byte(uchar date) { ?????? uchar i,temp; ?????? temp=date; ?????? for(i=0;i<8;i++) ?????? { ????????????? temp=temp<<1; ????????????? scl=0;//读和写的精髓:数据先准备好,在时钟scl的高电平期间送走,即scl=1 //和scl=0之间送走数据 ?????? ??? delay(); ????????????? sda=CY; ????????????? delay(); ????????????? scl=1; ????????????? delay(); ?????? } ?????? scl=0; ?????? delay(); ?????? sda=1; ?????? delay(); } uchar read_byte() { ?????? uchar i,k; ?????? scl=0; ?????? delay(); ?????? sda=1; ?????? delay(); ?????? for(i=0;i<8;i++) ?????? { ????????????? scl=1; ????????????? delay();?? ????????????? k=(k<<1)|sda; ????????????? scl=0; ????????????? delay();?? ?????? } ?????? return k; } void delay1(uchar x) { ?????? uchar a,b; ?????? for(a=x;a>0;a--) ?????? ?for(b=100;b>0;b--); } void write_add(uchar address,uchar date)//在EEPROM存储区内部指定地址处写数据,见//AT24C02A的PDF的P11的Byte Write { ?????? start(); ?????? write_byte(0xa0); //见LESSON8_IIC总线协议的PPT的P22,AT24C系列E2PROM芯 //片地址的固定部分为1010,在TX-1C型单片机实验板原理图中A2、A1和A0都接地,第//8位是数据的传送方向位(R/T),这里是由单片机向EEPROM写入数据所以第8位是0,//所以写入从机地址是0xa0,即选中哪个EEPROM ?????? respons(); ?????? write_byte(address); // EEPROM内部储存区的地址,即选中所用EEPROM中的哪个地 //址 ?????? respons(); ?????? write_byte(date); //向EEPROM的地址address写入数据date ?????? respons(); ?????? stop(); } uchar read_add(uchar address)//在指定地址处读数据,见AT24C02A的PDF的P11的Random //Read { ?????? uchar date; ?????? start(); ?????? write_byte(0xa0); ?????? respons(); ?????? write_byte(address); ?????? respons(); ?????? start(); ?????? write_byte(0xa1);//单片机向EEPROM读数据,给设备地址,所以第8位是1,写入//EEPROM的地址是0xa1 ?????? respons();//上面write_byte(address)已经确定在EEPROM中的哪个地址读或写数据,所//以就不用再写write_byte(新EEPROM内部存储器的地址) ?????? date=read_byte();//读一个字节的数据 ?????? stop(); ?????? return date; } void main() { ?????? init(); ?????? write_add(23,0x55); ?????? delay1(100); ?????? P1=read_add(23); ?????? while(1); } EEPROM有掉电存储功能,开发板上数码管在做加1计数,比如加到8,关闭电源,再打开电源数码管从8开始继续走,即关闭电源时EEPROM把8存储起来了;电源检测芯片实时保存数据 以后用到I2C总线上面这个程序都可以直接拿来用,在主函数中修改就可以,这就是C语言的方便的可移植性,我们编过的很多程序都可以移植,加以修改就可以使用 见AT24C02A的PPT的P11的Current Address Read(当前地址的读)图,比如从第五个地址开始写,写完之后地址加1变成6,那么再读数据的时候,就会读出地址6的数据,而上面的程序uchar read_add(uchar address)是要在指定地址处读数据,所以Current Address Read不适用上面的程序 课件练习:数码管做加1计数,利用EEPROM的掉电存储功能,控制两个数码管显示,掉电保存数据,开电源继续显示: 思路:每次上电就从EEPROM读取数据回来,每加一秒就将数据放回存储的位置,同时在数码管显示,每过一秒再放回去 我的思路:先把各种信号,读写,读写过程都写好,再试试读写一个字节让发光二极管点亮,再用定时器 编程中,我先把各种信号,读写,读写过程都写好,读写一个字节让发光二极管点亮也实现了,然后我编写数码管显示函数(主函数中只让数码管显示,隔开总线函数,这样可以不让总线干扰,看显示函数是否正确),也实现了,再使用中断,在主函数中实现写入数据和读出数据 我的程序: #include<reg52.h> #define uint unsigned int #define uchar unsigned char sbit sda=P2^0; sbit scl=P2^1; sbit dula=P2^6; sbit wela=P2^7; uchar code table[]={ 0x3f,0x06,0x5b,0x4f, 0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c, 0x39,0x5e,0x79,0x71 }; uchar num,shi,ge,flag; void delay() {;;} void delay1(uint z) { ?????? uchar x,y; ?????? for(x=z;x>0;x--) ????????????? for(y=110;y>0;y--); } void start() { ?????? scl=1; ?????? delay(); ?????? sda=1; ?????? delay(); ?????? sda=0; ?????? delay(); } void stop() { ?????? scl=1; ?????? delay(); ?????? sda=0; ?????? delay(); ?????? sda=1; ?????? delay(); } void respons() { ?????? uchar i; ?????? scl=1; ?????? delay(); ?????? while((sda==1) | (i<250)) i++; ?????? delay(); ?????? scl=0; ?????? delay(); } void write(uchar date) { ?????? uchar i,temp; ?????? temp=date; ?????? for(i=0;i<8;i++) ?????? { ????????????? scl=0; ????????????? delay(); ????????????? temp=temp<<1; ????????????? sda=CY; ????????????? delay(); ????????????? scl=1; ????????????? delay(); ?????? } ?????? scl=0; ?????? delay(); } uchar read() { ?????? uchar i,kk; ?????? for(i=0;i<8;i++) ?????? { ????????????? scl=0; ????????????? delay(); ????????????? kk=(kk<<1)|sda; ????????????? delay(); ????????????? scl=1; ????????????? delay(); ?????? } ?????? scl=0; ?????? delay(); ?????? return kk; } void write_byte(uchar address,uchar date) { ?????? start(); ?????? write(0xa0); ?????? respons(); ?????? write(address); ?????? respons(); ?????? write(date); ?????? respons(); ?????? stop(); } uchar read_byte(uchar address) { ?????? uchar aa; ?????? start(); ?????? write(0xa0); ?????? respons(); ?????? write(address); ?????? respons(); ?????? start(); ?????? write(0xa1); ?????? respons(); ?????? aa=read(); ?????? stop(); ?????? return aa; } void init() { ?????? scl=1; ?????? delay(); ?????? sda=1; ?????? delay(); ?????? TMOD=0x01; ?????? TH0=(65536-50000)/256; ?????? TL0=(65536-50000)%256; ?????? EA=1; ?????? ET0=1; ?????? TR0=1; } void display() { ?????? dula=1; ?????? P0=table[shi]; ?????? dula=0; ?????? P0=0xff; ?????? wela=1; ?????? P0=0xfe; ?????? wela=0; ?????? delay1(5);//作用是让显示的数据保持住 ?????? dula=1; ?????? P0=table[ge]; ?????? dula=0; ?????? P0=0xff; ?????? wela=1; ?????? P0=0xfd; ?????? wela=0; ?????? delay1(5); } void main() { ?????? init(); ?????? shi=read_byte(3)/10; ?????? delay1(100); ?????? ge=read_byte(3)%10; ?????? delay1(100); ?????? display(); ?????? while(1) ?????? { ????????????? if(flag==1) ????????????? { ???????????????????? flag=0; ???????????????????? write_byte(3,shi*10+ge); ???????????????????? delay1(100); ????????????? } ????????????? display(); ?????? } } void timer0() interrupt 1 { ?????? TH0=(65536-50000)/256; ?????? TL0=(65536-50000)%256; ?????? num++; ?????? if(num==20) ?????? { ????????????? num=0; ????????????? ge++; ????????????? if(ge==10) ????????????? { ???????????????????? ge=0; ???????????????????? shi++; ???????????????????? if(shi==10) ??????????????????????????? shi=0; ????????????? } ????????????? flag=1;//为了不在定时器写数据,这里用一个标志位flag来控制些数据,因为写数据的延时很长会影响定时器的下次进入中断 ?????? } } |
|
嵌入式 最新文章 |
基于高精度单片机开发红外测温仪方案 |
89C51单片机与DAC0832 |
基于51单片机宠物自动投料喂食器控制系统仿 |
《痞子衡嵌入式半月刊》 第 68 期 |
多思计组实验实验七 简单模型机实验 |
CSC7720 |
启明智显分享| ESP32学习笔记参考--PWM(脉冲 |
STM32初探 |
STM32 总结 |
【STM32】CubeMX例程四---定时器中断(附工 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 8:33:19- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |