Quantcast
Channel: James Grenning's Blog » Embedded
Viewing all articles
Browse latest Browse all 5

Now I’ll really use test driven development to write device driver code

$
0
0

In the last article, I added tests to existing code. So I did not really do Test Driven Development. I did Test After Development. Let’s do some TDD now and design the block erase function. I’ll go from the spec, to the test to the code.

The data sheet for the device describes block erase like this:

Block Erase Command

The Block Erase command can be used to erase
a block. It sets all the bits within the selected block
to ’1′. All previous data in the block is lost. If the
block is protected then the Erase operation will
abort, the data in the block will not be changed and
the Status Register will output the error.

Two Bus Write cycles are required to issue the
command.

  • The first bus cycle sets up the Erase
    command.
  • The second latches the block address in the
    internal state machine and starts the Program/
    Erase Controller.

If the second bus cycle is not Write Erase Confirm
(D0h), Status Register bits b4 and b5 are set and
the command aborts.

Erase aborts if Reset turns to VIL. As data integrity
cannot be guaranteed when the Erase operation is
aborted, the block must be erased again.

During Erase operations the memory will accept
the Read Status Register command and the Program/Erase
Suspend command, all other commands will be ignored.
Typical Erase times are given in Table 7.,Program,
Erase Times and Program/Erase Endurance Cycles.
See APPENDIX C., Figure 20.,Erase Flowchart
and Pseudo Code, for a suggested flowchart for
using the Erase command.




*** Thanks to STMicroelectronics for use of this chart***

From the flow chart it looks like we will need these tests

  • Normal case where everything works fine (a.k.a. the happy path)
  • Invalid programming voltage
  • Invalid command sequence
  • Erase error
  • Erase a protected block error

Here are a few words on how the read status register works.

Read Status Register Command

The Status Register indicates when a program or
erase operation is complete and the success or
failure of the operation itself. Issue a Read Status
Register command to read the Status Register’s
contents. Subsequent Bus Read operations read
the Status Register at any address, until another
command is issued.

The Read Status Register command may be is-
sued at any time, even during a Program/Erase
operation. Any Read attempt during a Program/
Erase operation will automatically output the con-
tent of the Status Register.

This table is a helpful reference on how the device is operated.



Here is initial happy path test. To keep the test short, we can pretend that the block erase operation is complete after three polls.

enum  {
    eraseBlock = 0x20,
    blockNumber = 4,
    flashDone = 1<<7,
    flashNotDone = 0,
    vppError = 1<<2,
    clearStatusWord = 0x50,
};
 
TEST(Flash, EraseBlockHappyPath)
{
 
    Expect_FlashWrite(0x0, eraseBlock);
    Expect_FlashWrite(BlockOffset[blockNumber], 0xD0);
    Expect_FlashRead(0x0, flashNotDone);
    Expect_FlashRead(0x0, flashNotDone);
    Expect_FlashRead(0x0, flashDone);
    Expect_FlashWrite(0x0, clearStatusWord); 
 
    ReturnType result = MyFlashBlockErase(blockNumber);
 
    LONGS_EQUAL(Flash_Success, result);
    Check_FlashWrite_Expectations();
}

BTW I’m ignoring the timeout possibility in this article. The
code to make that test pass looks like this:

ReturnType MyFlashBlockErase(uBlockType blockNumber)
{
    FlashWrite(ANY_ADDR, 0x20);
    FlashWrite(BlockOffset[blockNumber], 0xD0); 
 
    while (((FlashRead(ANY_ADDR) & 0x80) != 0x80))
        ;
 
    FlashWrite(ANY_ADDR, 0x50);
    return Flash_Success;
}

The next decision in the flow chart is about detecting and reporting
incorrect programming voltage during block erase. Here is the test:

TEST(Flash, EraseBlockVppError)
{
    Expect_FlashWrite(0x0, eraseBlock);
    Expect_FlashWrite(BlockOffset[blockNumber], writeEraseConfirm);
    Expect_FlashRead(0x0, flashDone | vppError);
    Expect_FlashWrite(0x0, clearStatusWord); 
 
    ReturnType result = MyFlashBlockErase(blockNumber);
 
    LONGS_EQUAL(Flash_VppInvalid, result);
    Check_FlashWrite_Expectations();
}

If I was worried about how many reads it took to detect the Vpp problem I could add some more Expect_FlashRead(0x0, flashNotDone) calls. Knowing the implementation of the driver function I an not worried about it.

I made some bit twiddling mistakes while trying to get this test working, nicely the tests caught my mistakes.

ReturnType MyFlashBlockErase(uBlockType blockNumber)
{
    FlashWrite(ANY_ADDR, 0x20);
    FlashWrite(BlockOffset[blockNumber], 0xD0); 
 
    uCPUBusType status;
    do {
        status = FlashRead(ANY_ADDR);
    } while ((status & 0x80) != 0x80);
 
    FlashWrite(ANY_ADDR, 0x50);
 
    status &= ~0x80;
    if ((status & 0x08) == 0x08)
        return Flash_VppInvalid;
    return Flash_Success;
}

According to the spec, the device can generate a command sequence error. This test does not try to make an actual sequence error, it just makes the fake IO port return the command sequence error bits and makes sure that the driver detects it.

TEST(Flash, EraseBlockCommandSequenceErrorDetected)
{
    Expect_FlashWrite(0x0, eraseBlock);
    Expect_FlashWrite(BlockOffset[blockNumber], writeEraseConfirm);
    Expect_FlashRead(0x0, flashDone | commandSequenceError);
    Expect_FlashWrite(0x0, clearStatusWord); 
 
    ReturnType result = MyFlashBlockErase(blockNumber);
 
    LONGS_EQUAL(Flash_BlockEraseFailed, result);
    Check_FlashWrite_Expectations();
}

The Device error test, and the write to a protected block test are basically the same as the prior test, so I’ll leave them to your imagination. Here is my finished block erase function.

ReturnType MyFlashBlockErase(uBlockType blockNumber)
{
    FlashWrite(ANY_ADDR, 0x20);
    FlashWrite(BlockOffset[blockNumber], 0xD0); 
 
    uCPUBusType status;
    do {
        status = FlashRead(ANY_ADDR);
    } while ((status & 0x80) != 0x80);
 
    FlashWrite(ANY_ADDR, 0x50);
 
    status &= ~0x80;
    if ((status & 0x08) != 0)
        return Flash_VppInvalid;
    else if ((status & 0x30) != 0)
        return Flash_BlockEraseFailed;
    else if ((status & 1) != 0)
        return Flash_BlockProtected;
    return Flash_Success;
}

I have no idea if this will work on real hardware. But I do know the driver is doing what I expect it to do. I made some coding mistakes while I worked that I would otherwise have had to Debug On the Hardware (DOH!). I avoided DOH! for those mistakes. I expect that alone has paid for my effort to write the tests.

Next time we’ll compare my block write with the one put together by the pros and see what mistakes I made.

Oh yeah, here are the final set of enums too.

enum  {
    eraseBlock = 0x20,
    writeEraseConfirm = 0xD0,
    blockNumber = 4,
    flashDone = 1<<7,
    flashNotDone = 0,
    vppError = 1<<3,
    commandSequenceError = 1<<4 | 1<<5,
    eraseError = 1<<5,
    writeToProtectedBlock = 1,
    clearStatusWord = 0x50,
};

©2013 James Grenning's Blog. All Rights Reserved.

.

Viewing all articles
Browse latest Browse all 5

Trending Articles