新闻中心

EEPW首页>嵌入式系统>设计应用> 浅谈如果通过程序读取AT24系列芯片型号

浅谈如果通过程序读取AT24系列芯片型号

作者: 时间:2016-11-10 来源:网络 收藏
对于一般的AT24芯片来说,要获取型号并不复杂,那就是用眼睛看,这个是没有问题的。但是,如果我们用的是AT24系列的IC卡呢?如果我们用的是白卡呢?那么怎么来判断这张卡片究竟是什么型号的?

对于以上问题,我们想大家都有不同的看法,但是,不知道您是否真正尝试过呢?能正确读取AT24C01到AT24C1024之间的各种型号吗?

为了解决这个问题,本人思考了好几天,以通过多种实验去验证,今天终于获取结果了,不过本人先声明,我这里只有3中类型的卡(C02, C16, C64),每种类型有2张或以上,验证都是正确的。如果你有其他类型的卡不妨也试试,如果尝试了,请把结果告诉本人,在下先谢了。

下面我们先谈谈这类芯片的一些基础知识,得到这些基础知识后,看您能否想出解决这个问题的方法,再看看方法是否和我一样的。
对于24C系列的IC卡来说,其读写操作完全和24C系列芯片的读写操作一致,所有,下面我们就以此系列芯片为基础进行介绍。对于这系列芯片的资料特别多而且也非常详细,下面我们就借《嵌入式实时操作系统Small RTOS51原理及应用》中的第20章串行E2PROM芯片Cat24WCxx驱动程序的内容来描述。

虽然这一章中讲的芯片不是ATMEL的,但是和这一系列完全兼容。

1. 参数表




2. 器件地址表



3. 数据地址表

3.png(92.89 KB, 下载次数: 0)

本文引用地址: //m.amcfsurvey.com/article/201611/317396.htm

下载附件保存到相册

2013-10-25 07:58 上传




4. 操作时序

从上面的表格和图我们可以获取以下信息:

1. 不同型号其容量不同;
2. 不同型号的页写入不同;
3. 不同型号的扩展数量不同。

还可以看出,对于24C01/02/04/08/16的数据地址只有1字节,而24C32/64/128/256等的数据地址为两字节。我们仔细想想发现,1字节的数据地址对于24C01/02刚好够用,而对于24C04/08/16来说却不够用,所以,还必须配合器件地址实现读写操作。

至此,您是否想出分别型号的方法?
想法1:通过访问器件的最高地址实现.

我们知道AT24C01的最大容量为1Kbit,以就是128字节,如果我们读写128以后的地址不正确,我们就可以确定这个芯片的型号就为AT24C01了。如果用同样的方法,从大到小的访问,应该就可以区分这一系列芯片的不同型号了。

有了想法,那我们不妨试试吧。。。。。。。

结果如何呢?你是否猜到了?



。。。


通过验证我们可以获得结论:不管是什么型号的芯片都可以正确读写,根本无法分辨这一系列。24C01/02以及24C32/64/128/256无法通过24C04/08/16的程序,但其他程序都可以操作。而24C04/08/16可以通过全部型号的读写操作。

也就是说:我们只能把这一系列芯片分为两大类,而无法分辨其型号。

奇怪,这个 问题是怎样产生的呢?

查看芯片资料我们不难发现,如果读写操作超过芯片地址,它是不会返回错误的,而是地址回卷,又从最小的地址开始,所以,就是你写入地址超过芯片范围也无法获取错误。

至于24C04/08/16能够通过各个型号芯片的读写程序,是以为,这三个芯片的地址有特殊性。在读写超过8位地址的地方是通过与页地址配合实现的,以就是说这三个芯片的地址是由:0xA* + 8Bit构成;而24C01/02的地址是由0xA0 + 8位地址构成;24C32/64/128/256的地址是由:0xA0 + 16位地址构成。

由这三个地址可以看出,由于24C04/08/16支持0xA*地址,所以可以通过各种格式的读取,而其他两类不支持0xA0以外的地址,所以当通过24C04/08/16程序读写这两类芯片时就会出现错误。当然24C04/08/16这三个芯片的地址由有些区别,例如04的只有1位,08的有2位,16的有3位,我们可以通过程序进一步区分这三个型号。
想法2:通过页读写操作实现.

我们知道,不同型号其页大小是有区别的,当操作超过页面时,芯片或滚动覆盖,我们可以通过写入最大页面数据,根据读取的数据可以知道其滚动状态,从而读取芯片页面大小。

但是,AT24C01的页字节为8,AT24C02/04/08/16的页字节为16,AT24C32/64的页字节为32,AT24C128/256的页字节为64,所以我们只能分出这4类芯片,还是无法实现所有型号的判别。
通过上面的想法和实际可以得出,以上两种方法都很难实现对这一系列芯片的正确读取。下面我们通过连续写入多字节进行试验。

例如:同时在芯片的最后几个空间内同时写入4字节的数据:
  1. u8 tmpBuf[] = {0x00, 0x00, 0x00, 0x00};
  2. u8 tmpDat[] = {0xAA, 0x55, 0xFF, 0x00};
  3. ATReadDat(type, addr-3, tmpBuf, 4); // 数据暂存
  4. ATWriteDat(type, addr-3, tmpDat, 4); // 写入验证数据
  5. memset(tmpDat, 0, 4);
  6. ATReadDat(type, addr-3, tmpDat, 4); // 读取验证数据
  7. ATWriteDat(type, addr-3, tmpBuf, 4); // 恢复写入前
  8. return ((memcmp(tmpDat, "xAAx55xFFx00", 4) == 0) ? 0x00 : 0x01);
复制代码
通过以上试验可以发现,读写24C02和24C16已经没有问题,完全可以争取的区分这两类芯片。但还是不能读取24C64之类的芯片。


进一步修改代码:


我们知道,由于芯片超地址时会出现覆盖写入,那么我们能不能把基础可能会出现覆盖的地方写入不同值了,如果发现覆盖就可以说明这个型号是错的,如果没有覆盖就说这个型号是对的:
  1. u8 tmpBuf[] = {0x00, 0x00};
  2. u8 tmpDat[] = {0xAA, 0x55};
  3. ATReadDat(type, ((addr+1)/2)-1, &tmpBuf[0], 1); // 数据暂存
  4. ATReadDat(type, addr, &tmpBuf[1], 1);
  5. ATWriteDat(type, ((addr+1)/2)-1, &tmpDat[0], 1);
  6. ATWriteDat(type, addr, &tmpDat[1], 1); // 写入验证数据
  7. memset(tmpDat, 0, 2);
  8. ATReadDat(type, ((addr+1)/2)-1, &tmpDat[0], 1);
  9. ATReadDat(type, addr, &tmpDat[1], 1); // 读取验证数据
  10. ATWriteDat(type, ((addr+1)/2)-1, &tmpBuf[0], 1); // 恢复写入前
  11. ATWriteDat(type, addr, &tmpBuf[1], 1);
  12. return ((memcmp(tmpDat, "xAAx55", 2) == 0) ? 0x00 : 0x01);
复制代码
通过上面的代码验证获得,现在可以区别出24C64了,但却不能区别出24C02/16等。为什么会这样呢?连续的读写可以区别24C02/16但不能区别24C64,现在虽然能区别24C64了,可其他的反而不行了,能否把这种方法和连续写入多字节组合呢?
  1. u8 tmpBuf[] = {0x00, 0x00, 0x00, 0x00};
  2. u8 tmpDat[] = {0xAA, 0x55, 0xFF, 0x00};
  3. ATReadDat(type, ((addr+1)/2)-2, &tmpBuf[0], 2); // 数据暂存
  4. ATReadDat(type, addr-1, &tmpBuf[2], 2);
  5. ATWriteDat(type, ((addr+1)/2)-2, &tmpDat[0], 2);
  6. ATWriteDat(type, addr-1, &tmpDat[2], 2); // 写入验证数据
  7. memset(tmpDat, 0, 4);
  8. ATReadDat(type, ((addr+1)/2)-2, &tmpDat[0], 2);
  9. ATReadDat(type, addr-1, &tmpDat[2], 2); // 读取验证数据
  10. ATWriteDat(type, ((addr+1)/2)-2, &tmpBuf[0], 2); // 恢复写入前
  11. ATWriteDat(type, addr-1, &tmpBuf[2], 2);
  12. return ((memcmp(tmpDat, "xAAx55xFFx00", 4) == 0) ? 0x00 : 0x01);
复制代码
通过上面的代码修改和试验,我们现在可以区别这三种型号了,通过我的推算应该是这种方法已经可以读取这一系列的各种型号,不过由于本人手里只有这3种卡片,其他的没有办法试验。

至于为什么这样写可以实现,本人也还没有一个完整的理论依据,大家不妨一起想想,如果你先想出来,请告诉我一下。
以上的程序却是能够实现型号辨别,但一个新的问题出现了。例如:向24C02等单地址卡片里边写入双地址数据时,由于时序的不同,芯片处理时,会把双地址的第2字节作为数据写入到单地址芯片中,由于我们的程序没有考虑这一点,所以,会导致其他地址的数据出现错误。

下面我们举例说明:

用上面的程序,我们判别型号的顺序是256->128->64->32->16->08->04->02->01,写入的地址是芯片的最大地址的最后两个字节和芯片最大地址的一半的最后两个字节,这样做的目的是试图通过数据覆盖来判断型号,例如,如果最大地址的最后两个字节覆盖了一半的最后两个字节,固然不是这个型号。

所以在按照24c256来想AT24C02写入数据是,我们是通过在地址:16382(0x3FFE)写入两个字节(0xAA, 0x55),再在地址32766(7FFE)写入两个字节(0xFF, 0x00),通过读取整片AT24C02芯片获得:

[48] = 0xAA,
[49] = 0x55,
[63] = 0xFE,

[112] = 0xFF,
[113] = 0x00,
[127] = 0xFE,

通过仔细分析我们发现,芯片处理时,首先把地址16382(0x3FFE)分为2字节处理,高字节为地址即63(0x3F),低字节为数据0xFE,再加上AT24C02的页面大小为16字节,地址0x3F已经是页面的最高地址,后面再写入数据时就会发生页面翻转现象,而页面的起始地址正好是48(0x30),故而后面发生的两字节数据就写入了48和49.

后面两个字节的数据也是完全一致的现象,所以,这个程序破坏来原始数据。
判断出卡片型号是必须的,但绝对不能破坏数据,为此我们还必须想办法解决这一问题:

下面我们谈谈另外一思路:通过页和地址来实现。

我们知道AT24C01为8字节一页, AT24C02/04/08/16为16字节一页, AT24C32/64为32字节为一页, AT24C128/256为64字节为一页。我们完全可以通过写页数据,通过判断是否有数据被覆盖实现,过程如下:

写入16字节数据->读写一致为AT24C02/04/08/16中一种,否则判断8字节是否一致,一致为AT24C01,否则为其他->通过写最大地址判断是否覆盖,来判断究竟是AT24C02/04/08/16中的那一致。其他型号过程一致,代码如下:
  1. ATC_TYP ATCReadType(void)
  2. {
  3. u8 i;
  4. u8 tmpBuf[64] = {0};
  5. u8 tmpDat[64] = {0};
  6. u8 cmpDat[64] = {0};
  7. //--------------------------------- 单地址判断 -----------------------------
  8. for (i=0; i<16; i++) // 初始化
  9. {
  10. tmpDat[i] = i;
  11. cmpDat[i] = i;
  12. }
  13. // AT24C01的页为8字节,AT24C02/04/08/16的页为16字节
  14. // 通过读写16来判断页大小,从而区分AT24C01
  15. ATCReadNByte(AT24C02, 0, tmpBuf, 16); // 数据暂存
  16. ATCWriteNByte(AT24C02, 0, tmpDat, 16); // 写入验证数据
  17. memset(tmpDat, 0, 16);
  18. ATCReadNByte(AT24C02, 0, tmpDat, 16); // 读验证数据
  19. if (memcmp(tmpDat, cmpDat, 16) == 0) // AT24C02/04/08/16
  20. {
  21. ATCWriteNByte(AT24C02, 0, tmpBuf, 16); // 恢复数据
  22. // AT24C02/04/08/16中,通过页地址共同组成地址,故可以通过页区别型号
  23. for (i=4; i>0; i--)
  24. {
  25. ATCReadByte((ATC_TYP)(i), ATC_Par[(ATC_TYP)(i)].MaxAddr, &tmpDat[0]);
  26. ATCWriteByte((ATC_TYP)(i), ATC_Par[(ATC_TYP)(i)].MaxAddr, 0xAA);
  27. ATCReadByte((ATC_TYP)(i), ATC_Par[(ATC_TYP)(i)].MaxAddr, &tmpDat[1]);
  28. if (tmpDat[1] == 0xAA)
  29. {
  30. ATCWriteByte((ATC_TYP)(i), ATC_Par[(ATC_TYP)(i)].MaxAddr, tmpDat[0]);
  31. return ((ATC_TYP)(i));
  32. }
  33. }
  34. }
  35. else
  36. {
  37. if (memcmp(&tmpDat[8], cmpDat, 8) == 0) // AT24C01
  38. {
  39. ATCWriteNByte(AT24C01, 0, tmpBuf, 8); // 恢复数据
  40. return AT24C01;
  41. }
  42. }
  43. //--------------------------------- 双地址判断 -----------------------------
  44. for (i=0; i<64; i++) // 初始化
  45. {
  46. tmpDat[i] = i;
  47. cmpDat[i] = i;
  48. }
  49. ATCReadNByte(AT24C128, 0, tmpBuf, 64); // 数据暂存
  50. ATCWriteNByte(AT24C128, 0, tmpDat, 64); // 写入验证数据
  51. memset(tmpDat, 0, 64);
  52. ATCReadNByte(AT24C128, 0, tmpDat, 64); // 读验证数据
  53. if (memcmp(tmpDat, cmpDat, 64) == 0) // AT24C128/256
  54. {
  55. ATCWriteNByte(AT24C128, 0, tmpBuf, 64); // 恢复数据
  56. ATCReadByte(AT24C256, 0, &tmpDat[0]);
  57. ATCReadByte(AT24C256, ATC_Par[AT24C128].Capacity, &tmpDat[1]);
  58. ATCWriteByte(AT24C256, 0, 0xAA);
  59. ATCWriteByte(AT24C256, ATC_Par[AT24C128].Capacity, 0x55);
  60. ATCReadByte(AT24C256, 0, &tmpDat[2]);
  61. ATCReadByte(AT24C256, ATC_Par[AT24C128].Capacity, &tmpDat[3]);
  62. if ((tmpDat[2] == 0xAA) && (tmpDat[3] == 0x55))
  63. {
  64. ATCWriteByte(AT24C256, 0, tmpDat[0]);
  65. ATCWriteByte(AT24C256, ATC_Par[AT24C128].Capacity, tmpDat[1]);
  66. return AT24C256;
  67. }
  68. else
  69. {
  70. ATCWriteByte(AT24C128, 0, tmpDat[0]);
  71. return AT24C128;
  72. }
  73. }
  74. else // AT24C128/256
  75. {
  76. if (memcmp(&tmpDat[32], cmpDat, 32) == 0)
  77. {
  78. ATCWriteNByte(AT24C64, 0, tmpBuf, 32);
  79. ATCReadByte(AT24C64, 0, &tmpDat[0]);
  80. ATCReadByte(AT24C64, ATC_Par[AT24C32].Capacity, &tmpDat[1]);
  81. ATCWriteByte(AT24C64, 0, 0xAA);
  82. ATCWriteByte(AT24C64, ATC_Par[AT24C32].Capacity, 0x55);
  83. ATCReadByte(AT24C64, 0, &tmpDat[2]);
  84. ATCReadByte(AT24C64, ATC_Par[AT24C32].Capacity, &tmpDat[3]);
  85. if ((tmpDat[2] == 0xAA) && (tmpDat[3] == 0x55))
  86. {
  87. ATCWriteByte(AT24C64, 0, tmpDat[0]);
  88. ATCWriteByte(AT24C64, ATC_Par[AT24C32].Capacity, tmpDat[1]);
  89. return AT24C64;
  90. }
  91. else
  92. {
  93. ATCWriteByte(AT24C32, 0, tmpDat[0]);
  94. return AT24C32;
  95. }
  96. }
  97. }
  98. return ATC_TYP_MAX;
  99. }
复制代码
具体代码不再分析,代码确实能够实现型号识别,但是否破坏数据,暂时还没有发现,大家不妨试试,如果破坏了数据,我们再做进一步分析并改善。
下面我们再介绍一种比较简单,而且不会破坏数据的方法。

我们知道从AT24C01~256之间,由于容量和地址的区别,我们可以把这一系列分为三大类:

1. 单地址,直接8位地址操作:AT24C01/02
2. 单地址,8位地址加3位页地址组合操作:AT24C04/08/16
3. 双地址,直接16位地址操作:AT24C32/64/128/256

由上面的三大类我们可以看出,要区别型号,可以通过先分类,再分别通过每一类中型号的差异进行进一步的区别。

首先我们可以看出,从地址上来分可以把以上芯片分为单地址和双地址,从页面组合上来分我们可以把他们分为有组合和无组合两种,所以,我们可以通过这两种方法先把这系列芯片分为2类,之后再进行细分。例如:

从地址上来分->单地址为AT24C01/02/04/08/16,双地址为AT24C32/64/128/256:

1. 单地址5种芯片,有页组合的有3种,这三种中 AT24C16由3位组合,AT24C08由2位组合,AT24C04由一位组合,所以当我们读写0xAE地址正确时一定是AT24C16,如果不正确,读写0xA6正确时一定是AT24C08,如果还不正确,读写0xA2正确时一定是AT24C04,如果还不正确那,一定是AT24C01/02中的一种,而对于AT24C01/02来说,不同的只是地址范围,如果写地址0和地址128,如果数据覆盖,那一定是AT24C01,如果没有覆盖那一定是AT24C02.

2. 双地址4种芯片,没有也组合,而不同的只有地址范围,这一定和AT24C01/02是完全一致的,所以,由于区别的方法也和这两个芯片一样,通过覆盖可以轻松的判断出 芯片型号
  1. /**************************************************************************************
  2. * FunctionName : ATCReadType()
  3. * Description : 写器件型号
  4. * EntryParameter : None
  5. * ReturnValue : None
  6. **************************************************************************************/
  7. u8 ATCReadType(void)
  8. {
  9. u8 i;
  10. u8 tmpBuf[3] = {0};
  11. u8 tmpDat[3] = {0};
  12. ATCReadNByte(AT24C32, 0, tmpBuf, 1); // 读取双地址0的一字节暂存
  13. ATCReadNByte(AT24C16, 0, &tmpBuf[1], 2); // 读取单地址0的二字节暂存
  14. ATCWriteNByte(AT24C32, 0, "xA5", 1); // 按照双地址格式写入一字节数据
  15. ATCReadNByte(AT24C32, 0, tmpDat, 1); // 按照双地址格式读取一字节数据
  16. ATCReadNByte(AT24C16, 0, &tmpDat[1], 2); // 按照单地址格式读取二字节数据
  17. if ((tmpDat[1] == 0x00) && (tmpDat[2] == 0xA5)) // 单地址芯片
  18. {
  19. ATCWriteNByte(AT24C16, 0, &tmpBuf[1], 2); // 恢复数据
  20. //-------------------------------- AT24c04/08/16 -----------------------
  21. for (i=AT24C16; i>AT24C02; i--) // AT24c04/08/16
  22. {
  23. ATCReadByte(i, ATC_Par[i].MaxAddr, &tmpBuf[0]);
  24. ATCWriteByte(i, ATC_Par[i].MaxAddr, 0xAA);
  25. ATCReadByte(i, ATC_Par[i].MaxAddr, &tmpDat[0]);
  26. ATCWriteByte(i, ATC_Par[i].MaxAddr, tmpBuf[0]);
  27. if (tmpDat[0] == 0xAA)
  28. {
  29. return i;
  30. }
  31. }
  32. //-------------------------------- AT24c01/02 --------------------------
  33. ATCReadByte(AT24C02, 0, &tmpBuf[0]);
  34. ATCReadByte(AT24C02, 128, &tmpBuf[1]);
  35. ATCWriteByte(AT24C02, 0, 0xAA);
  36. ATCWriteByte(AT24C02, 128, 0x55);
  37. ATCReadByte(AT24C02, 0, &tmpDat[0]);
  38. ATCReadByte(AT24C02, 128, &tmpDat[1]);
  39. ATCWriteByte(AT24C02, 0, tmpBuf[0]);
  40. ATCWriteByte(AT24C02, 128, tmpBuf[1]);
  41. return (tmpDat[0] == 0x55) ? AT24C01 : AT24C02;
  42. }
  43. else
  44. {
  45. if (tmpDat[0] == 0xA5) // 双地址芯片
  46. {
  47. ATCWriteNByte(AT24C256, 0, &tmpBuf[0], 1); // 恢复数据
  48. //-------------------------------- AT24c32/64/128/256 --------------
  49. for (i=AT24C256; i>AT24C16; i--)
  50. {
  51. ATCReadByte(i, 0, &tmpBuf[0]);
  52. ATCReadByte(i, ATC_Par[i-1].Capacity, &tmpBuf[1]);
  53. ATCWriteByte(i, 0, 0xAA);
  54. ATCWriteByte(i, ATC_Par[i-1].Capacity, 0x55);
  55. ATCReadByte(i, 0, &tmpDat[0]);
  56. ATCReadByte(i, ATC_Par[i-1].Capacity, &tmpDat[1]);
  57. ATCWriteByte(i, 0, tmpBuf[0]);
  58. ATCWriteByte(i, ATC_Par[i-1].Capacity, tmpBuf[1]);
  59. if ((tmpDat[0] == 0xAA) && (tmpDat[1] == 0x55))
  60. {
  61. return i;
  62. }
  63. }
  64. return AT24C256;
  65. }
  66. else // 非AT系列芯片
  67. {
  68. return ATC_TYP_MAX;
  69. }
  70. }
  71. }
复制代码
对于上面的程序需要注意,我们的方法是通过写入数据之后读取进行判断的,所以在写入数据之前必须把要写入的地址数据暂存,完成判断后必须恢复,绝对不能破坏芯片类的数据。

在单地址和双地址的判断中,我们按照两种方法进行数据暂存,因为我们刚开始并不知道卡片究竟是什么型号,所以,在判断出来后,按照单双地址分别恢复也保证数据的正确性。

还有一点需要强调,我们在按照双地址方式,在地址0的地方写入0xA5数据,如果芯片确实是双地址,那么该数据一定写入地址0,数据数据为0xA5,但是如果该芯片为单地址,那么地址的低8为就被作为数据一起写入了,所以会导致,地址0开始写入两字节数据,一字节为0(地址的低8位被当做第一个数据了),一字节为0xA5(数据却作为第2字节数据写入了)。所以通过这两字节数据可以轻松的判断出究竟是单地址还是双地址。


评论


技术专区

关闭