M95128-W EEPROM。每页的第一个字节不能正确写入或读取

4
我正在开发一个控制M95128-W EEPROM的库,用于STM32设备。我已经实现了库中的数据写入和读取,但是每个页面的第一个字节不符合预期,似乎固定为0x04
例如,我从地址0x00开始写入128个字节,值为0x80,但在读取时得到的结果为:
byte[0] = 0x04;
byte[1] = 0x80;
byte[2] = 0x80;
byte[3] = 0x80;
.......
byte[64] = 0x04;
byte[65] = 0x80;
byte[66] = 0x80;
byte[67] = 0x80;

我已经使用逻辑分析仪调试了SPI,并确认了正确的字节被发送。在读取命令时,使用逻辑分析仪时,来自EEPROM的神秘0x04被传输。

以下是我的代码:

void FLA::write(const void* data, unsigned int dataLength, uint16_t address)
{
    int pagePos = 0;
    int pageCount = (dataLength + 64 - 1) / 64;
    int bytePos = 0;
    int startAddress = address;

    while (pagePos < pageCount)
    {
        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_SET); // WP High
        chipSelect();

        _spi->transfer(INSTRUCTION_WREN);
        chipUnselect();

        uint8_t status = readRegister(INSTRUCTION_RDSR);

        chipSelect();

        _spi->transfer(INSTRUCTION_WRITE);
        uint8_t xlow = address & 0xff;
        uint8_t xhigh = (address >> 8);
        _spi->transfer(xhigh); // part 1 address MSB
        _spi->transfer(xlow); // part 2 address LSB


        for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
        {
            uint8_t byte = ((uint8_t*)data)[bytePos];
            _spi->transfer(byte);

            printConsole("Wrote byte to ");
            printConsoleInt(startAddress + bytePos);
            printConsole("with value ");
            printConsoleInt(byte);
            printConsole("\n");

            bytePos ++;
        }

        _spi->transfer(INSTRUCTION_WRDI);

        chipUnselect();
        HAL_GPIO_WritePin(GPIOB,GPIO_PIN_2, GPIO_PIN_RESET); //WP LOW

        bool writeComplete = false;
        while (writeComplete == false)
        {
            uint8_t status = readRegister(INSTRUCTION_RDSR);

            if(status&1<<0)
            {
                printConsole("Waiting for write to complete....\n");
            }

            else
            {
                writeComplete = true;
                printConsole("Write complete to page ");
                printConsoleInt(pagePos);
                printConsole("@ address ");
                printConsoleInt(bytePos);
                printConsole("\n");
            }
        }

        pagePos++;
        address = address + 64;
    }

    printConsole("Finished writing all pages total bytes ");
    printConsoleInt(bytePos);
    printConsole("\n");
}
void FLA::read(char* returndata, unsigned int dataLength, uint16_t address)
{
    chipSelect();
          _spi->transfer(INSTRUCTION_READ);
            uint8_t xlow = address & 0xff;
                    uint8_t xhigh = (address >> 8);
          _spi->transfer(xhigh); // part 1 address
          _spi->transfer(xlow); // part 2 address

            for (unsigned int i = 0; i < dataLength; i++)
                returndata[i] = _spi->transfer(0x00);
               chipUnselect();


}

希望得到任何建议或帮助。

更新:

我尝试编写连续的 255 字节递增数据来检查循环。结果如下:

byte[0] = 4; // Incorrect Mystery Byte 
byte[1] = 1; 
byte[2] = 2;
byte[3] = 3; 
....... 
byte[63] = 63; 
byte[64] = 4; // Incorrect Mystery Byte 
byte[65] = 65; 
byte[66] = 66;
....... 
byte[127] = 127; 
byte[128] = 4; // Incorrect Mystery Byte 
byte[129} = 129;

模式继续。我也尝试从地址0x00只写入8个字节,但问题仍然存在,所以我认为我们可以排除翻转的可能性。
我已经尝试删除debug printConsole,但没有效果。
这是写命令的SPI逻辑跟踪:

enter image description here

以下是第一个字节的近距离照片,它无法正常工作:

enter image description here

代码可以在gitlab上查看,链接如下: https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/flash.cpp

SPI的初始化代码可以在MX_SPI_Init()中看到。

https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/main.cpp

我在SPI总线上有另一个设备(RFM69HW RF模块),它按预期发送和接收数据。


也许也提供FLA::read - a3f
我已经添加了谢谢。 - Daniel Beyzade
附注 - 这是一个可以与这种EEPROM一起使用的代码,经过了类似于您的设备(M95512)的测试 - https://github.com/DISTORTEC/distortos/blob/master/source/devices/memory/SpiEeprom.cpp https://github.com/DISTORTEC/distortos/blob/master/include/distortos/devices/memory/SpiEeprom.hpp - Freddie Chopin
你的代码不完整(没有SPI初始化、内部函数、精确使用示例等),所以在这里帮助你真的很困难... - Freddie Chopin
请在MX_SPI1_Init()中查看此处 https://gitlab.com/DanielBeyzade/stm32f107vc-home-control-master/blob/master/Src/main.cpp - Daniel Beyzade
我应该在我的板子上添加另一个模块,它位于同一条SPI总线上,是一个已经确认工作的RFM69HW射频模块。 - Daniel Beyzade
2个回答

3
注意:我没有确定性的解决方案,只有一些观察和建议[对于注释来说太长了]。
从6.6开始:每次移入一个新的数据字节时,内部地址计数器的最低有效位会增加。如果发送的字节数超过了页面末尾所能容纳的字节数,就会出现一种称为“翻转”的情况。在翻转的情况下,超过页面大小的字节就会从同一页的位置0处被覆盖掉。
所以,在你的写入循环代码中,你这样做:for (i = 0; i < 64; i++)。如果地址(xlow)的最低有效位非零,则在一般情况下这是不正确的。你需要做的是像这样:for (i = xlow % 64; i < 64; i++) 换句话说,你可能会遇到页面边界翻转问题。但是,你提到你正在使用地址0x0000,所以即使代码存在,它也应该可以工作。
我可能会把循环中的打印语句去掉,因为它们可能会影响序列化的时间。
我可能会尝试使用递增的数据模式:(例如)0x01、0x02、0x03......这样,你可以看到哪个字节正在翻转[如果有的话]。
此外,请尝试从地址0开始写入一个页面,并写入小于完整页面大小的内容(即小于64个字节),以确保您没有遇到翻转问题。
此外,从图13 [WRITE的时序图]中可以看出,在你断言芯片选择后,ROM需要一个连续的、精确定时的比特流,所以你可能存在在提供数据时未提供与时钟边缘完全同步的情况。你可能需要使用逻辑分析仪来验证数据是否恰好按照所需的时钟边缘(即在时钟上升沿)出现。
正如你可能已经注意到的,偏移量0和偏移量64都得到了0x04。因此,这增加了翻转的概念。
或者,可能是每个页面的第一个数据字节被“延迟”写入,而0x04是其结果。
我不知道你的输出端口是否有SILO,这样你就可以像传统串行I/O端口一样发送数据,还是你必须保持精确的位对位定时(我认为_spi->transfer会这样做)。
另一个尝试的方法是从非零地址(例如xhigh = 0;xlow = 4)开始写入长度较短的模式(例如10个字节)和递增的模式,看看事情会如何变化。

更新:

根据您的更新,每个页面的第一个字节似乎是关键。

从时序图中可以看出,我注意到SCLK并不完全一致。脉冲宽度稍有波动。由于写入数据是在时钟上升沿采样的,这应该没有关系。但是我想知道这是从哪里来的。即,SCLK是否由软件(即transfer)断言/取消断言,SCLK是否连接到另一个GPIO引脚?我很想看看transfer函数的源代码[或反汇编代码]。

我刚刚在这里查阅了SPI:https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus,它回答了我的问题。

从那里,这是一个示例传输函数:

/*
 * Simultaneously transmit and receive a byte on the SPI.
 *
 * Polarity and phase are assumed to be both 0, i.e.:
 *   - input data is captured on rising edge of SCLK.
 *   - output data is propagated on falling edge of SCLK.
 *
 * Returns the received byte.
 */
uint8_t SPI_transfer_byte(uint8_t byte_out)
{
    uint8_t byte_in = 0;
    uint8_t bit;

    for (bit = 0x80; bit; bit >>= 1) {
        /* Shift-out a bit to the MOSI line */
        write_MOSI((byte_out & bit) ? HIGH : LOW);

        /* Delay for at least the peer's setup time */
        delay(SPI_SCLK_LOW_TIME);

        /* Pull the clock line high */
        write_SCLK(HIGH);

        /* Shift-in a bit from the MISO line */
        if (read_MISO() == HIGH)
            byte_in |= bit;

        /* Delay for at least the peer's hold time */
        delay(SPI_SCLK_HIGH_TIME);

        /* Pull the clock line low */
        write_SCLK(LOW);
    }

    return byte_in;
}

所以,延迟时间至少需要ROM所需的时间。希望您可以验证一下这是否是这种情况。
但是,我还注意到在有问题的字节上,数据的第一个位似乎滞后于其上升沿。也就是说,我希望在时钟上升边缘之前,数据线要稳定下来。
但是,这假设 CPOL=0,CPHA=1。您的ROM可以编程为该模式,或者 CPOL=0,CPHA=0,这是上面示例代码使用的模式。
这是我从时序图中看到的。它意味着传输函数执行CPOL=0,CPHA=0
SCLK
          __
         |  |
      ___|  |___

DATA
           ___
          /   \
         /     \

这是我根据ROM文档中的早期信息所预期的(CPOL=0, CPHA=1):
SCLK
          __
         |  |
      ___|  |___

DATA
        ___
       /   \
      /     \

ROM可以配置为使用CPOL=0,CPHA=0CPOL=1,CPHA=1。因此,您可能需要配置这些值以匹配传输功能(反之亦然),并验证传输功能的延迟时间是否足够ROM。SDK可能会为您完成所有这些工作,但是,由于您遇到了困难,因此再次检查可能是值得的(例如,请参见ROM文档中的表18等)。
然而,由于ROM似乎对大多数字节位置都有良好的响应,因此时序可能已经足够。
您还可以尝试一件事情。由于问题出现在第一个字节,我指的是LSB地址字节后的第一个字节,因此存储器可能需要一些额外的[未记录]设置时间。
因此,在transfer(xlow)之后,在进入数据传输循环之前添加一个小的自旋循环,以给ROM时间准备写入突发[或读取突发]。
通过从非零值(例如3)开始的xlow,缩短传输时间,可以确认这一点。如果问题字节跟踪xlow,那么这是验证可能需要设置时间的一种方法。您需要为每个测试使用不同的数据值,以确保您不仅仅是从先前的测试中读取过时的值。

我已经尝试了很多方法。我将在问题中添加一些更多的信息,这可能有助于回答其中的一些问题。如果您不介意看一下,我会非常感激。 - Daniel Beyzade
“ROM 可以配置为使用 CPOL=0,CPHA=0 或 CPOL=0,CPHA=1。" - 你错了。只允许使用 0-0 和 1-1 模式。请参考数据手册第 12 页。” - Freddie Chopin
@FreddieChopin 是的,你说得对。我误读了那个页面[这是我从中提取的一个页面]。 - Craig Estey
@CraigEstey。非常感谢您的详细回复!正如您所看到的,由于您和Freddie的帮助,问题现在已经得到解决。 - Daniel Beyzade

3

其实,Craig Estey在他的回答中已经给出了解释。您确实有一个rollover。您需要先编写完整页面,然后发送INSTRUCTION_WRDI命令,而不需要循环CS引脚。那么这个命令的二进制代码是多少呢?如果你猜到它是4,那么你绝对正确。

在这里检查您的代码:

    chipSelect();

    _spi->transfer(INSTRUCTION_WRITE);
    uint8_t xlow = address & 0xff;
    uint8_t xhigh = (address >> 8);
    _spi->transfer(xhigh); // part 1 address MSB
    _spi->transfer(xlow); // part 2 address LSB


    for (unsigned int i = 0; i < 64 && bytePos < dataLength; i++ )
    {
        uint8_t byte = ((uint8_t*)data)[bytePos];
        _spi->transfer(byte);

        // ...

        bytePos ++;
    }

    _spi->transfer(INSTRUCTION_WRDI); // <-------------- ROLLOEVER!

    chipUnselect();

使用这些设备时,每个命令都必须以循环CS开始。当CS变低后,第一个字节将被解释为命令。在CS再次循环之前的所有剩余字节都将被解释为数据。因此,您无法在CS不断下拉的单个“块”中发送多个命令。
另一件事是,您根本不需要WRDI命令 - 写指令终止后(通过CS变高),WEL位会自动复位。请查看数据表的第18页:
“写使能锁存器(WEL)位实际上会因以下任何事件而重置: • 上电 • WRDI指令执行 • WRSR指令完成 • 写入指令完成。”

只需简单地删除 _spi->transfer(INSTRUCTION_WRDI) 就解决了问题。我知道这一点,但只是忽略了代码。谢谢! - Daniel Beyzade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接