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

时间:2017-02-21 17:51:17

标签: c microcontroller stm32 spi eeprom

我正在开发一个用于控制STM32设备M95128-W EEPROM的库。我有库编写和回读数据,但每页的第一个字节不符合预期,似乎在D:\inetpub\wwwroot\ApplicationName\pictures\修复。

例如,我在两个页面上写入128个字节,从0x04地址开始,值为0x00。回读后我得到:

0x80

我已经使用逻辑分析仪调试了SPI,并确认正在发送正确的字节。在读取命令上使用逻辑分析仪时,mysterios byte[0] = 0x04; byte[1] = 0x80; byte[2] = 0x80; byte[3] = 0x80; ....... byte[64] = 0x04; byte[65] = 0x80; byte[66] = 0x80; byte[67] = 0x80; 将从EEPROM传输。

这是我的代码:

0x04

任何建议或帮助表示赞赏。

更新

我尝试按顺序写入255个字节来增加数据以检查翻转。结果如下:

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();


}

模式继续。我也试过从地址0x00写入8个字节,同样的问题仍然存在,所以我认为我们可以排除翻转。

我尝试删除调试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模块),可以按预期发送和接收数据。

2 个答案:

答案 0 :(得分:3)

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

  

从6.6:每次移入新数据字节时,内部地址计数器的最低有效位递增。如果发送的字节数超过了页面末尾的字节数,则会出现称为“翻转”的情况。在翻转的情况下,超过页面大小的字节将从同一页面的位置0覆盖。

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

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

我可能会从循环中删除print语句,因为它们可能会对序列化时序产生影响。

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

另外,尝试从地址0写入单个页面,并将 less 写入比完整页面大小(即少于64个字节)保证

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

正如您可能已经注意到的那样,偏移0和偏移64正在获得0x04。因此,这增加了翻转的概念。

或者,可能是每个页面的第一个数据字节正在写入&#34;迟到&#34;而0x04就是这样的结果。

我不知道您的输出端口是否具有SILO,因此您可以像传统的串行I / O端口一样发送数据,或者您是否必须保持精确的逐位定时(我认为{ {1}}会这样做)

要尝试的另一件事是从非零地址(例如_spi->transfer)开始写一个较短的模式(例如10个字节)和递增模式,看看事情是如何变化的。

<强>更新

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

从时间的分解视角来看,我注意到xhigh = 0; xlow = 4并非严格统一。脉冲宽度略微不稳定。由于写数据是在时钟上升沿采样的,所以这不重要。但是,我想知道这是从哪里来的。也就是说,软件SCLK是否置位/置为无效(即SCLK),而transfer是否连接到另一个GPIO引脚?我有兴趣查看SCLK函数[或反汇编]的来源。

我刚刚在这里查看了SPI:https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus并且它回答了我自己的问题。

由此,这是一个样本传递函数:

transfer

因此,延迟时间至少需要ROM所需的时间。希望您可以验证是否属实。

但是,我还注意到在问题字节上,数据的第一位似乎滞后于其上升的时钟边沿。也就是说,我希望数据线在时钟上升沿之前稳定

但是,假定为/* * 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进行编程,这是上述示例代码使用的模式。

这是我从时序图中看到的。这意味着传递函数执行CPOL=0,CPHA=0

CPOL=0,CPHA=0

这是我最初期望的(SCLK __ | | ___| |___ DATA ___ / \ / \ )基于ROM文档中较早的内容:

CPOL=0,CPHA=1

可以将ROM配置为使用 SCLK __ | | ___| |___ DATA ___ / \ / \ CPOL=0,CPHA=0。因此,您可能需要配置这些值以匹配传递函数(反之亦然)并且,验证传递函数的延迟时间是否适合您的ROM。 SDK可以为您完成所有这些操作,但是,由于您遇到了问题,因此可能需要仔细检查(例如,请参阅ROM文档中的表18等)。

然而,由于ROM似乎对大多数字节位置反应良好,因此时序可能已足够。

你也可以尝试一件事。因为它是问题的第一个字节,这里我指的是 LSB地址字节后的第一个字节,内存可能需要一些额外的[和未记录的]设置时间。

因此,在CPOL=1,CPHA=1之后,在进入数据传输循环之前添加一个小旋转循环,给ROM时间设置写突发[或读突发]。

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

答案 1 :(得分:3)

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再次循环 - 被解释为数据。所以你不能在一个&#34;块&#34;中发送多个命令。随着CS不断拉低。

另一件事是你根本不需要WRDI命令 - 在写指令终止后(通过CS变高),WEL位自动复位。请参见数据表的第18页:

  事实上,写使能锁存器(WEL)位会被任何一个复位   以下活动:

     

•加电

     

•WRDI指令执行

     

•WRSR指令完成

     

•WRITE指令完成。