m051的i2c读写24cxx,如何连续读?
点击此处下载 ourdev_590514B9D7K3.zip(文件大小:388K) (原文件名:AN_1009_IIC_for_24LC64 V1.00.zip)这是芯唐官方的应用笔记。有单字节读写代码,连续读取一直没能实现,大家看看。
//resive data
I2C0->DATA = 0X00; //接收数据为何要先写数据???????
DrvI2C_Ctrl(I2C_PORT0, 0, 0, 1, 0);//clr si
while( I2C0->CON.SI == 0 ); //poll si flag 呵呵,这就是我一直以来对使用“官方库函数”非常感冒的原因之一。
就拿LZ位的例子看,
1。代码中开始有:#include "Driver\DrvI2C.h",说明你用了它的“库”,否则 DrvI2C_Open()、DrvI2C_Ctrl()、DrvI2C_Close()来自何处?
2。既然有了DrvI2C_Open()、DrvI2C_Ctrl()、DrvI2C_Close(),为什么不实现DrvI2C_Write_Byte()和DrvI2C_Recive_Byte()?可见这个库只是帮助你设置寄存器,根本没有用户层面的接口。
3。因此你还是要看器件手册,了解各个位的意义和作用。否则DrvI2C_Ctrl(I2C_PORT0, 0, 0, 1, 0)里面的几个0/1就无法确定。所以那些说使用库可以跳过低层寄存器,(32位MCU的寄存器比8位多的多,而且性能更强)让用户很快上手的说法是片面的。
4。除了看手册,你还要看它的库函数是什么功能,不了解根本也用不好的(这里已经不考虑库函数有BUG以及局限性的问题了)。
=============================================================================================================
对LZ位的例子,我没有看过其库函数到第是如何写的。根据判断,应该使用M051的硬件I2C口,不是I/O口模拟的。这个从DrvI2C_Ctrl(I2C_PORT0, 0, 0, 1, 0)已经看出。
硬件I2C口需要一个启动工作的过程,应该在DrvI2C_Ctrl(I2C_PORT0, 0, 0, 1, 0)中实现,它清除SI,然后启动I2C工作。建议你还是要了解这个函数的具体作用。
对于读取数据
//resive data
I2C0->DATA = 0X00; //接收数据为何要先写数据???????
DrvI2C_Ctrl(I2C_PORT0, 0, 0, 1, 0);//clr si
while( I2C0->CON.SI == 0 ); //poll si flag
我认为有两种可能。一是写个0,配合下面DrvI2C_Ctrl()启动I2C读数据过程,二是根本就是乱搬的写过程代码。但不管是为了什么,这样的NOTES,太低级了。
应该后面有一句 A = I2C0->DATA;A中就是读到的数据。
你应该先测试和实现是否能读到一个字节。如果能正确的读到一个字节,那么连续读就是这段代码的重复。
最后建议你购买我编写的AVR教程,里面有关于I2C详细的介绍和多种实现的方法,包括I/O模拟和使用硬件I2C,同时还有相应低层驱动函数如何建立,以及用户层面的使用。你掌握了方法,在M051上、在STM32上都是一样使用的。
你曾经说过经过锻炼和培训。我不知道是在何时,何地。但就今天你问的问题来讲,我不客气的说,你还需要经过一些真正的培训,才能有质的提高。 马老师
这段代码不是我写的,这是官方的文档。
读I2C->DATA实际测试时我加了,重复这段代码确实能实现读多字节,但eeprom协议并不是这样,可以连续读的,这个您知道。
单字节测试过,可以到,多字节写也可以,但多字节读不行,发现源代码多个写数据寄存器。 您写的avr教程我看过了,不过那是N年前的事了,您的端口模拟i2c我的产品还在用着呢。
记得好像端口使用上于别人不同,没来回更改输入输出方式。记不太清了。
老师教导的对,我应该加强基础,谨记了。 我认为有两种可能。一是写个0,配合下面DrvI2C_Ctrl()启动I2C读数据过程,二是根本就是乱搬的写过程代码
-------------------
测试发现如不加写数据寄存器这句,读到的是前面写的命令字0xa1,但试过先读一次,后面的就正确了,可是再启动读会跑飞。
硬件I2C口需要一个启动工作的过程,应该在DrvI2C_Ctrl(I2C_PORT0, 0, 0, 1, 0)中实现,它清除SI,然后启动I2C工作。建议你还是要了解这个函数的具体作用。
---------------------
DrvI2C_Ctrl(I2C_PORT0, 0, 0, 1, 0)这个是清标志
DrvI2C_Ctrl(I2C_PORT1, 0, 0, 1, 0)这个是发送启动位
但就今天你问的问题来讲,我不客气的说,你还需要经过一些真正的培训,才能有质的提高。
---------------
这个我不做过多解释。
您觉得我说的内容不实,我要是再说我们的原理图,pcb,驱动层,应用层都是分开做的,您可能更不信,不过我们确实这样做的。
多说一句:技术员的项目一般都是从头做到尾,我们正好相反,必须分工,并不是我们的技术员不会,这样有利于整体发展,不让部分人控制。
我还是倡导分工的,把自己的部分做好,否则什么都做不精。这点也正是我们设计的产品不如国外的一个原因。 回sde_arm9:
看你的回贴感觉你还是有点不服气,正好我也利用这个机会深入了解一下M051。
首先我知道这段代码是官方的。不客气的讲,你(或你们的团队)是否有能自己从底层,从零开始写代码的能力我都表示怀疑。
看看你的失误吧:
1。这段代码不是针对M051的,是针对UNC系列的。尽管都是M0的内核,I2C的控制基本相同,但库函数肯定有区别的!你如果使用M051,那么应该采用M051的库,至少下面这个函数就不一样:
DrvI2C_Ctrl(I2C_PORT0, 0, 0, 1, 0)==》这个是NUC系列的,因为它支持2个或2个以上的I2C,所以参数中多了个I2C口的选择。
而我查看了官方提供的M051函数库,它的函数为 DrvI2C_Ctrl(0, 0, 1, 0)!
你到底是在用NUC还是用M051?还是有意将他们混在一起,要提高问题的难度,考考我?
2。你的主题是“m051的i2c读写”,那么使用M051,就应该看MO51相关的东西,比如M051的手册。这里不是说的那个简单的介绍,是指NuMicro M051 Series Technical Reference Manual EN.pdf。你看了几遍?至少关于I2C的部分应该全面看吧。另外官站已经有M051的支持库和例子。其中也有对I2C操作的,你参考过吗?
不管你分工负责哪个部分,至少这个部分的工作就是没有做好。这个是最低层的接口,这个做不好,上面再好也是空的。
下面具体分析一下如何连续读数据,具体你去测试。
请看下图:这个是我从NuMicro M051 Series Technical Reference Manual EN.pdf中找到的,在151页。153页有I2C主机读数据的流程。
http://cache.amobbs.com/bbs_upload782111/files_34/ourdev_590675ZUTYJK.jpg
(原文件名:未标题-1 拷贝.jpg)
请注意右上部的文字说明部分!
看到这里,明显的看出M051的I2C启动与AVR的不同,AVR是通过一个控制位启动一次新的I2C工作,而MO51是通过操作数据寄存器加上配合控制位的设置启动一次新的I2C操作。这个与我前面分析的相同“配合下面DrvI2C_Ctrl()启动I2C读数据过程”
在官方提供的例子中,我也没有找到I2C连续读操作的DEMO。根据NuMicro M051 Series Technical Reference Manual EN.pdf手册上151页和153页的解释,建议你做如下尝试:
如果你准备连续读10个数据:
//resive data
I2C->DATA = 0X00; //接收数据为何要先写数据?==》随便写个数据,表示操作了I2C数据寄存器,用于配合下一次的I2C启动
for(i=0;i<9;i++)
{
DrvI2C_Ctrl(0, 0, 1, 1); //clr si,启动一次工作 ==》收到数据返回ACK为1,此时才能连续读。这个是I2C规程规定的!
while( I2C0->CON.SI == 0 ); //poll si flag
r_data = I2C->DATA; //读数据,同时操作了I2C数据寄存器,也配合了下次启动
} // 这个循环只能读9个数据,因为读第10个数据后,返回ACK要为0
DrvI2C_Ctrl(0, 0, 1, 0); //clr si,启动第10次读数据,设置读到后返回ACK=0,表示连续读结束。
while( I2C0->CON.SI == 0 ); //poll si flag
r_data = I2C->DATA; //读第10个数据。
=============================================================
以上供你参考。
你在4楼写到:“您写的avr教程我看过了,不过那是N年前的事了,您的端口模拟i2c我的产品还在用着呢。记得好像端口使用上于别人不同,没来回更改输入输出方式。记不太清了。”
实际你并没有认真的学习和体会,也没真正彻底的了解I2C!只是搬了段代码。后面还有使用AVR的I2C硬件接口的例子,有批量读和写的例子,你都没看。 上面的参考与AVR书中的方法是一样的,只是I2C的启动方式不同。
建议你重新仔细再看看,N年后这本书的思想和方法仍旧没有落后,比现在的许多新书或什么DEMO都要好。 M051可以玩了?
明天我也去拿个板子回来玩玩~~~~~但愿这次没烧得太快 没有考马老师的意识,您别误会,昨天在家,没有测试的代码,官方提供的都是nuc100的代码,和m051没什么区别,下面是从官方修改过来用于m051的代码
/*i2c写数据*/
void write_i2c_byte(uint8_t *byte,uint32_t address,uint8_t count)
{
uint8_t n;
uint16_t i;
//send i2c start
DrvI2C_Ctrl(1, 0, 1, 0); //set start 发送启动位
while (I2C->CON.SI == 0); //poll si flag
//send writer command
I2C->DATA = 0XA0; //send writer command 发送写命令
DrvI2C_Ctrl(0, 0, 1, 0); //clr si flag
while( I2C->CON.SI == 0 ); //poll si flag
//send address high
//I2C->DATA = (address>>8)&0XFF;
//DrvI2C_Ctrl(0, 0, 1, 1); //clr si and set ack
//while( I2C->CON.SI == 0 ); //poll si flag
//send address low
I2C->DATA = address&0XFF; //发送数据地址
DrvI2C_Ctrl(0, 0, 1, 1); //clr si and set ack
while( I2C->CON.SI == 0 ); //poll si flag
for(n=0;n<count;n++){
//send data
I2C->DATA = *(byte+n); //write data to 发送数据
DrvI2C_Ctrl(0, 0, 1, 1); //clr si and set ack
while( I2C->CON.SI == 0 ); //poll si flag
}
//send i2c stop
DrvI2C_Ctrl(0, 1, 1, 0); //send stop 发送停止位
//while( I2C0->CON.SI == 0 );
for(i=0;i<60;i++); //延时
//DrvI2C_Close(I2C_PORT0); //关闭I2C
for(i=0;i<6000;i++);
for(i=0;i<6000;i++);
}
/*i2c读数据*/
void read_i2c_byte(uint8_t *byte,uint32_t address,uint8_t count)
{
uint8_t n;
uint16_t i;
for(n=0;n<count;n++){
//send i2c start
DrvI2C_Ctrl(1, 0, 1, 0); //set start 发送启动位
while (I2C->CON.SI == 0); //poll si flag
//send writer command
I2C->DATA = 0XA0; //发送写命令
DrvI2C_Ctrl(0, 0, 1, 0); //clr si
while( I2C->CON.SI == 0 ); //poll si flag
//send address high
//I2C->DATA = (address>>8)&0XFF;
//DrvI2C_Ctrl(0, 0, 1, 1); //clr si and set ack
//while( I2C->CON.SI == 0 ); //poll si flag
//send address low
I2C->DATA = (address+n)&0XFF; //发送地址
DrvI2C_Ctrl(0, 0, 1, 1); //clr si and set ack
while( I2C->CON.SI == 0 ); //poll si flag
//send start flag
DrvI2C_Ctrl(1, 0, 1, 0); //clr si and send start 发送启动位
while( I2C->CON.SI == 0 ); //poll si flag
//send read command
I2C->DATA = 0XA1; //发送读命令
DrvI2C_Ctrl(0, 0, 1, 0); //clr si
while( I2C->CON.SI == 0 ); //poll si flag
//resive data
I2C->DATA = 0XFF;
DrvI2C_Ctrl(0, 0, 1, 0); //clr si
while( I2C->CON.SI == 0 ); //poll si flag
*(byte+n)= I2C->DATA; //读取数据
//DrvI2C_Ctrl(0, 0, 1, 0); //clr si and set ack
//while( I2C->CON.SI == 0 ); //poll si flag
//send i2c stop
DrvI2C_Ctrl(0, 1, 1, 0); //clr si and set stop 发送停止位
//DrvI2C_Close(I2C_PORT0); //关闭I2C
}
} 代码:
//resive data
I2C->DATA = 0XFF;
for(n=0;n<(count-1);n++){
DrvI2C_Ctrl(0, 0, 1, 1); //clr si
while( I2C->CON.SI == 0 ); //poll si flag
*(byte+n)= I2C->DATA; //读取数据
}
DrvI2C_Ctrl(0, 0, 1, 0); //clr si
while( I2C->CON.SI == 0 ); //poll si flag
*(byte+n)= I2C->DATA; //读取数据
读里程数据,测试结果:
应该输出 09 81 00 00实际输出 01 00 00 06 http://cache.amobbs.com/bbs_upload782111/files_34/ourdev_590731EE0GIH.jpg
(原文件名:未标题-1 拷贝.jpg)
根据上面的M051描述的过程和你提供的代码,我给出一段完整连续读代码,请你参考。
1。不知道你的24CXX是多大的,你把送高位地址字节屏蔽了,应该是小容量的EEPROM
2。注意:address(开始地址) 以及address+count(结束地址)的值必须是在该芯片同一个页地址范围内,不的跨页
/*i2c读数据*/
void read_i2c_byte(uint8_t *byte,uint32_t address,uint8_t count)
{
uint8_t n;
uint16_t i;
//send i2c start
DrvI2C_Ctrl(1, 0, 1, 0); //set start 发送启动位
while (I2C->CON.SI == 0); //poll si flag
//send writer command
I2C->DATA = 0XA0; //发送写命令
DrvI2C_Ctrl(0, 0, 1, 1); //clr si ===>最好应该 set ack,表示24CXX应该回ACK=1
while( I2C->CON.SI == 0 ); //poll si flag
//send address high
// I2C->DATA = (address>>8)&0XFF;
// DrvI2C_Ctrl(0, 0, 1, 1); //clr si and set ack
// while( I2C->CON.SI == 0 ); //poll si flag
//send address low
I2C->DATA = (address)&0XFF; //发送地址
DrvI2C_Ctrl(0, 0, 1, 1); //clr si and set ack
while( I2C->CON.SI == 0 ); //poll si flag
//send start flag
DrvI2C_Ctrl(1, 0, 1, 0); //clr si and send start 发送启动位
while( I2C->CON.SI == 0 ); //poll si flag
//send read command
I2C->DATA = 0XA1; //发送读命令
DrvI2C_Ctrl(0, 0, 1, 1); //clr si ===>最好应该 set ack,表示24CXX应该回ACK=1
while( I2C->CON.SI == 0 ); //poll si flag
//resive data
I2C->DATA = 0XFF;
for(n=0;n<(count-1);n++){
DrvI2C_Ctrl(0, 0, 1, 1); //clr si===>这里肯定set ack,表示M051收到数据回ACK=1
while( I2C->CON.SI == 0 ); //poll si flag
*(byte+n)= I2C->DATA; //读取数据
}
DrvI2C_Ctrl(0, 0, 1, 0); //clr si
while( I2C->CON.SI == 0 ); //poll si flag
*(byte+n)= I2C->DATA; //读取数据
//send i2c stop
DrvI2C_Ctrl(0, 1, 1, 0); //clr si and set stop 发送停止位
DrvI2C_Close(I2C_PORT0); //关闭I2C
} 谢谢马潮老师,您是对的,代码已验证是能正确读写多字节。
再次表示感谢!
//send read command
I2C->DATA = 0XA1; //发送读命令
DrvI2C_Ctrl(0, 0, 1, 1); //clr si ===>最好应该 set ack,表示24CXX应该回ACK=1
while( I2C->CON.SI == 0 ); //poll si flag
--------------------
试了,这里设不设应答都能得到正确结果。
呵呵,确信您能提供我们需要的培训! 恭喜我通过你的考核。
下面还是要提醒的是关于是否使用“官方库”的问题。
你的代码肯定使用了官方的库,函数DrvI2C_Ctrl()就是这个库中的。下面是它的原形。
void DrvI2C_Ctrl(uint8_t start, uint8_t stop, uint8_t intFlag, uint8_t ack)
{
uint32_t Reg = 0;
if (start)
Reg |= I2C_STA;
if (stop)
Reg |= I2C_STO;
if (intFlag)
Reg |= I2C_SI;
if (ack)
Reg |= I2C_AA;
*((__IO uint32_t *)&I2C->CON) = (*((__IO uint32_t *)&I2C->CON) & ~0x3C) | Reg;
}
其实就是一个对I2C->CON寄存器赋值,从这里可以看出使用库效率是如何的。
官方同时还提供了直接操作寄存器的例子,我也贴出代码,你可以比较代码的效率,选择采用什么方式来编写自己的代码
/*-----------------------------------------------------------------------------
Function: Read_Page
Parameter:
None
Returns:
None
Description:
Read out pages have been written and verify them.
*-----------------------------------------------------------------------------*/
void Read_Page(void)
{
uint8_t ucPage, ucRead_Byte;
// START
I2CON |= STA;
I2CON |= SI; //Clear SI
while ((I2CON & SI) != SI); //Check SI set or not
I2CON &= ((~STA)&(~SI)); //STA needs to be cleared after START codition is generated
if (I2STATUS != 0x08) //Check status value after every step
{
LED = ERROR_CODE;
while (1);
}
//Control byte (SLA+W)
I2DAT = EEPROM_SLA | EEPROM_WR;
I2CON |= SI;
while ((I2CON & SI) != SI);
if (I2STATUS != 0x18)
{
LED = ERROR_CODE;
while(1);
}
//Address High (DATA)
I2DAT = 0;
I2CON |= SI;
while ((I2CON & SI) != SI);
if (I2STATUS != 0x28)
{
LED = ERROR_CODE;
while (1);
}
//Address Low (DATA)
I2DAT = 0;
I2CON |= SI;
while ((I2CON & SI) != SI);
if (I2STATUS != 0x28)
{
LED = ERROR_CODE;
while (1);
}
// Repeated START
I2CON |= STA;
I2CON |= SI; //Clear SI
while ((I2CON & SI) != SI); //Check SI set or not
I2CON &= ((~STA)&(~SI)); //STA needs to be cleared after START codition is generated
if (I2STATUS != 0x10) //Check status value after every step
{
LED = ERROR_CODE;
while (1);
}
//Control byte (SLA+R)
I2DAT = EEPROM_SLA | EEPROM_RD;
I2CON |= SI;
while ((I2CON & SI) != SI);
if (I2STATUS != 0x40)
{
LED = ERROR_CODE;
while (1);
}
I2CON |= AA;
for (ucPage = 0; ucPage <= 15; ucPage++) //Read Microchip 24xx256 first 16 pages
{
//Data
for (ucRead_Byte = 0; ucRead_Byte <= (PAGE_SIZE - 1); ucRead_Byte++)
{
I2CON |= SI;
while ((I2CON & SI) != SI);
if (I2STATUS != 0x50)
{
LED = ERROR_CODE;
while (1);
}
if (I2DAT != (ucPage)) //Verify
{
LED = ERROR_CODE;
while (1);
}
}
}
//send a NACK to disconnect 24xx256
I2CON &= (~AA);
I2CON |= SI;
while ((I2CON & SI) != SI);
//STOP
I2CON |= STO;
I2CON |= SI;
while (I2CON & STO); //To ensure STOP condition is generated, polling STO flag
}
实际上,不管采用哪种方式,都需要了解I2C的规程,了解M051的I2C接口是如何工作的,在这个方面是相同的。好象使用库可以少记几个具体位的名称和作用,但换得的代价是效率低,同时你也需要化时间去知道这个库函数的使用。更加可怕的是,如果库函数中有BUG,。。。。。 系统如果需要大批量的连续读/写操作I2C,高效率的方法是使用I2C的中断。
这样大量的 while( I2C->CON.SI == 0 ); //poll si flag时间都可以省掉。要知道硬件I2C的速度在怎么高,也比不上MCU的指令执行速度。
如果你使用UNC系列,那么效率最高的是I2C中断触发DMA传送,这样,在连续的一个批量写/读过程中,CPU的干涉处理降到最少。 对马老师的无私奉献精神表示钦佩!
受您的指点是我们的福分。
有些仪表有钟表功能,我们使用外部主晶振作为时钟源,通过定时器定时唤醒芯片来处理计时,看了半天M051手册,休眠时只有10K时钟能工作,实现不了钟表功能精度。看来只能使用nuc100的芯片了,这个有32k外部晶振。
仪表非工作状态电流要求:<2mA
有钟表的一般做到4mA 如果需要做真正的RTC,我建议还是采用专用的RTC芯片,精度好,耗电少,系统软件设计还剩了不少麻烦事情。一旦MCU跑非,时钟总是正确的。(有些系统当MCU跑飞后,可以通过DOG等措施拉回正常,尽管时间短,外观上没有影响系统工作,但实际时钟系统已经不正常了,至少需要重新设置正确的RTC时间)
专用RTC芯片也不贵,核算M051+RTC芯片与NUC100不会差多少,但RTC系统好多了,系统设计也简单很多。
带RTC功能的MCU做不到实际上的真正的RTC,你能保证MCU永远运行正常吗,系统不会掉电吗。我做过实验,断电后,用一个470u的电容供RTC专用芯片工作,可以保证48小时正常。
如果不是真正的RTC,而且系统经常处于断电不使用状态,那么10K的时钟也能做RTC。
拿一个手机,把电池拿掉,放上几小时,然后上电池打开,如果发现时钟不对了,需要设置,就是假的RTC。如果时钟正确,就是真的RTC。
你的仪表需要哪种? 仪表上只显示小时和分钟
仪表不会断电,但芯片干扰复位是有的。
10K时钟精度很差,+-50%
rtc芯片不错,但不知价格如何?
-----------------------------
这个应该可以
PCF8563,价格0.70元, i2c接口
点击此处下载 ourdev_591426WXBS0L.pdf(文件大小:232K) (原文件名:pcf8563.pdf) 回复【16楼】sde_arm9
仪表上只显示小时和分钟
仪表不会断电,但芯片干扰复位是有的。
10k时钟精度很差,+-50%
rtc芯片不错,但不知价格如何?
-----------------------------
这个应该可以
pcf8563,价格0.70元, i2c接口
点击此处下载
-----------------------------------------------------------------------
仪表不会断电,但芯片干扰复位是有的。===》那么你用MCU构成的RTC也就失效了。
既然仪表不会断电,那么串个二极管给RTC芯片供电,并上470u电容。如果万一仪表断电,至少RTC还能工作48小时。
一片RTC才0.7元(我的概念在1-2元,零售),系统软件相应也简单了,自己盘算。
再下去要付培训费了。这样的培训上哪找,价格很高的。 哈哈!
这样的培训真的没处找,付费付费 请问马老师,
为什么我在vista下面先装MDK4.12,然后装Nu-Link_Keil_Driver.exe,打开MDK,然后新建project,找不到新塘的M0系列,只能找到NUC系列呢??? 你们都犀利!我无语。 华邦的这个例程不怎么样,没一点i2c的风格,那个回调函数的例程还有些模样。 和马老师的想法一样,坚持使用寄存器的路过 路过路过了顺便看看
页:
[1]