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

M95128-W EEPROM. First byte of each page not writing or reading correctly

我正在开发一个用于从 STM32 设备控制 M95128-W EEPROM 的库。我让库写入和读回数据,但是每页的第一个字节与预期不符,似乎固定在 0x04.

例如,我在两页上写了 128 个字节,从 0x00 地址开始,值为 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 并确认正在发送正确的字节。当在读取命令上使用逻辑分析仪时,mysterios 0x04 从 EEPROM 传输。

这是我的代码:

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 个字节,同样的问题仍然存在,所以我认为我们可以排除翻转。

我试过删除调试 printConsole 但没有效果。

这是写入命令的 SPI 逻辑跟踪:

以及未正常工作的第一个字节的特写:

代码可以在 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 模块),它按预期发送和接收数据。

警告:我没有确定的解决方案,只有一些观察和建议[对于评论来说太大了]。

From 6.6: Each time a new data byte is shifted in, the least significant bits of the internal address counter are incremented. If more bytes are sent than will fit up to the end of the page, a condition known as “roll-over” occurs. In case of roll-over, the bytes exceeding the page size are overwritten from location 0 of the same page.

因此,在您的编写循环代码中,您执行:for (i = 0; i < 64; i++)。如果地址 (xlow) 的 LSB 不为零,这在一般情况下是不正确的。您需要执行以下操作:for (i = xlow % 64; i < 64; i++)

换句话说,您可能发生页面边界翻转。但是,您提到您正在使用地址 0x0000,因此它 应该 工作,即使代码存在。

我可能会从循环中删除打印语句,因为它们可能会影响序列化时间。

我可能会尝试使用递增数据模式:(例如)0x01,0x02,0x03,... 这样,您可以看到哪个字节正在翻转 [如果有的话]。

此外,尝试从地址零写入 单个 页,并写入 小于 的完整页面大小(即小于 64 字节) ) 以保证您不会翻车。

此外,从图 13 [WRITE 的时序图] 看来,一旦您断言芯片 select,ROM 就需要一个精确计时的连续比特流,因此您可能会遇到竞争条件没有在所需的时钟边沿准确提供数据。您可能希望使用逻辑分析仪来验证数据是否按要求与时钟边沿完全同步(即在时钟上升沿)

您可能已经注意到,偏移量 0 和偏移量 64 得到的是 0x04。因此,这增加了翻转的概念。

或者,可能是每页的第一个数据字节正在写入 "late",而 0x04 是其结果。

我不知道您的输出端口是否有 SILO,以便您可以像在传统串行 I/O 端口中一样发送数据,或者您是否必须保持精确的位对位计时(我认为_spi->transfer 会做)

另一件事是从非零地址(例如 xhigh = 0; xlow = 4)开始写一个较短的模式(例如 10 字节)和递增模式,看看情况如何变化。


更新:

根据您的更新,它似乎是每个页面的 第一个 字节[显然]。

从时序分解图中,我注意到SCLK并不是严格统一的。脉冲宽度略微不稳定。由于写入数据是在时钟上升沿采样的,因此这无关紧要。但是,我想知道这是从哪里来的。也就是说,SCLK asserted/deasserted 是由软件(即 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
           ___
          /   \
         /     \

这是我最初预期的 (CPOL=0,CPHA=1),基于 ROM 文档中较早的内容:

SCLK
          __
         |  |
      ___|  |___

DATA
        ___
       /   \
      /     \

ROM 可以配置为使用 CPOL=0,CPHA=0CPOL=1,CPHA=1。因此,您可能需要配置这些值以匹配传递函数(反之亦然),并且验证传递函数的延迟时间是否适合您的 ROM。 SDK 可能会为您完成所有这些,但是,由于您遇到了麻烦,仔细检查这可能是值得的(例如,请参阅 ROM 文档中的 Table 18 et. al.)。

但是,由于 ROM 似乎对大多数字节位置响应良好,时间可能已经足够了。

你也可以尝试一件事。因为这是问题的第一个字节,这里我的意思是第一个字节 LSB 地址字节之后,内存可能需要一些额外的 [and undocumented] 设置时间。

因此,在 transfer(xlow) 之后,在进入数据传输循环之前添加一个小的自旋循环,让 ROM 有时间设置突发写入 [或读取突发]。

这可以通过以非零值(例如 3)开始 xlow 并缩短传输来确认。如果问题字节跟踪 xlow,这是验证是否需要设置时间的一种方法。您需要为每个测试使用不同的数据值,以确保您不只是从先前的测试中读回过时的值。

其实Craig Estey在他的回答中已经给出了解释。你确实有翻车。您写入整页,然后 - 无需循环 CS 引脚 - 您发送 INSTRUCTION_WRDI 命令。猜猜这个命令的二进制代码是什么?如果你猜到是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 页:

The Write Enable Latch (WEL) bit, in fact, becomes reset by any of the following events:

• Power-up

• WRDI instruction execution

• WRSR instruction completion

• WRITE instruction completion.