Skip to content

Instantly share code, notes, and snippets.

@clydebarrow
Last active January 25, 2021 23:39
Show Gist options
  • Select an option

  • Save clydebarrow/8bcad81ea6ecf45750738df2a9527f25 to your computer and use it in GitHub Desktop.

Select an option

Save clydebarrow/8bcad81ea6ecf45750738df2a9527f25 to your computer and use it in GitHub Desktop.

Revisions

  1. clydebarrow revised this gist Jan 25, 2021. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions spiFlash.c
    Original file line number Diff line number Diff line change
    @@ -95,7 +95,7 @@ static void spiInit() {
    txDescriptor[0].xfer.doneIfs = 0;
    rxDescriptor[0].xfer.doneIfs = 0;
    txDescriptor[1].xfer.doneIfs = 1;
    rxDescriptor[1].xfer.doneIfs = 1;
    rxDescriptor[1].xfer.doneIfs = 1;
    rxDescriptor[0].xfer.dstInc = ldmaCtrlSrcIncNone; // descriptor 1 is used as a sink for unwanted bytes
    rxDescriptor[1].xfer.dstInc = ldmaCtrlSrcIncOne; // data is read into here.
    txDescriptor[0].xfer.srcInc = ldmaCtrlSrcIncOne; // this is the command descriptor, always has valid data.
    @@ -179,7 +179,7 @@ static bool spiFlashWaitIdle() {
    if(++timeout == 100000) {
    return false;
    }
    return timeout != 0;
    return true;
    }
    /**
    * Read a block of data from the flash at a given address
    @@ -271,7 +271,7 @@ bool spiFlashWriteData(uint32_t address, const uint8_t *buffer, size_t buflen) {
    txDescriptor[1].xfer.srcAddr = (uint32_t) buffer;
    txDescriptor[1].xfer.srcInc = ldmaCtrlSrcIncOne;
    dmaBusy = true;
    LDMA_StartTransfer(dmaChannelSpiPrimary, &txConfig, &txDescriptor[0]);
    LDMA_StartTransfer(dmaChannelSpiPrimary, &txConfig, &txDescriptor[0]);
    buffer += len;
    address += len;
    if (address == SPI_FLASH_SIZE) {
  2. clydebarrow revised this gist Jan 25, 2021. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions spiFlash.c
    Original file line number Diff line number Diff line change
    @@ -42,6 +42,12 @@
    #define CLK_PIN 10 // clock (to ROM)


    enum {
    dmaChannelSpiPrimary,
    dmaChannelSpiSecondary,
    dmaChannelCount
    };

    static const LDMA_TransferCfg_t txConfig = LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_USART1_TXBL);
    static const LDMA_TransferCfg_t rxConfig = LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_USART1_RXDATAV);
    static LDMA_Descriptor_t txDescriptor[2] = {
  3. clydebarrow created this gist Jan 25, 2021.
    294 changes: 294 additions & 0 deletions spiFlash.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,294 @@
    //
    // Created by Clyde Stubbs on 13/1/21.
    //

    #include <spiFlash.h>
    #include <em_gpio.h>
    #include <em_usart.h>
    #include <em_ldma.h>

    // Command bytes for the serial flash S25FL164K

    #define WAKEUP_CMD 0xAB
    #define WRITE_CMD 0x02 // write data command
    #define READ_CMD 0x03 // read data command
    #define READ_STATUS_1 0x05
    #define READ_STATUS_2 0x35
    #define READ_STATUS_3 0x33
    #define WRITE_ENABLE 0x06
    #define SECTOR_ERASE 0x20 // erase a 4K sector
    #define RESET_ENABLE 0x66 // enable SW_RESET
    #define SW_RESET 0x99 // reset chip to defaults
    #define JEDEC_ID_CMD 0x9F
    #define POWER_DOWN_CMD 0xB9


    #define CS_PORT gpioPortC
    #define CS_PIN 7 // chip select output

    #define WP_PORT gpioPortC // write protect port
    #define WP_PIN 6 // write protect output

    #define HOLD_PORT gpioPortC // write protect port
    #define HOLD_PIN 11 // write protect output

    #define SO_PORT gpioPortC // Serial output port (RX to us)
    #define SO_PIN 8 // Serial output pin

    #define SI_PORT gpioPortC // serial input port (TX to us)
    #define SI_PIN 9 // serial input (to ROM)

    #define CLK_PORT gpioPortC // clock port
    #define CLK_PIN 10 // clock (to ROM)


    static const LDMA_TransferCfg_t txConfig = LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_USART1_TXBL);
    static const LDMA_TransferCfg_t rxConfig = LDMA_TRANSFER_CFG_PERIPHERAL(ldmaPeripheralSignal_USART1_RXDATAV);
    static LDMA_Descriptor_t txDescriptor[2] = {
    LDMA_DESCRIPTOR_LINKREL_M2P_BYTE(NULL, &USART1->TXDATA, 1, 1),
    LDMA_DESCRIPTOR_SINGLE_M2P_BYTE(NULL, &USART1->TXDATA, 1),
    };
    static LDMA_Descriptor_t rxDescriptor[2] = {
    LDMA_DESCRIPTOR_LINKREL_P2M_BYTE(&USART1->RXDATA, NULL, 1, 1),
    LDMA_DESCRIPTOR_SINGLE_P2M_BYTE(&USART1->RXDATA, NULL, 1)
    };

    static const LDMA_Init_t dmaInit = LDMA_INIT_DEFAULT;
    static bool dmaBusy = false;

    // initialise the USART

    static void spiInit() {
    USART_InitSync_TypeDef usartInit = USART_INITSYNC_DEFAULT;
    usartInit.enable = usartDisable;
    usartInit.master = true; // master mode
    usartInit.msbf = true; // send msb first
    usartInit.baudrate = 10000000; // 10MHz bit rate. This is the maximum value that works.
    usartInit.autoCsEnable = true; // cs is auto-controlled
    usartInit.autoCsHold = 1; // probably unnecessary, but doesn't hurt much.
    usartInit.autoCsSetup = 0;
    USART_InitSync(USART1, &usartInit);
    USART1->ROUTEPEN = USART_ROUTEPEN_TXPEN
    | USART_ROUTEPEN_RXPEN
    | USART_ROUTEPEN_CLKPEN
    | USART_ROUTEPEN_CSPEN;

    USART1->ROUTELOC0 |=
    (14 << _USART_ROUTELOC0_TXLOC_SHIFT) //PC9
    | (12 << _USART_ROUTELOC0_RXLOC_SHIFT) // PC8
    | (13 << _USART_ROUTELOC0_CLKLOC_SHIFT) // PC10
    | (9 << _USART_ROUTELOC0_CSLOC_SHIFT);// PC7

    USART1->CTRL |= USART_CTRL_TXBIL_HALFFULL; // enable double buffering.
    GPIO_PinModeSet(WP_PORT, WP_PIN, gpioModePushPull, 1);
    GPIO_PinModeSet(HOLD_PORT, HOLD_PIN, gpioModePushPull, 1);
    GPIO_PinModeSet(SI_PORT, SI_PIN, gpioModePushPull, 0);
    GPIO_PinModeSet(SO_PORT, SO_PIN, gpioModeInputPull, 0);
    GPIO_PinModeSet(CLK_PORT, CLK_PIN, gpioModePushPull, 0);
    GPIO_PinModeSet(CS_PORT, CS_PIN, gpioModePushPull, 1);
    txDescriptor[0].xfer.doneIfs = 0;
    rxDescriptor[0].xfer.doneIfs = 0;
    txDescriptor[1].xfer.doneIfs = 1;
    rxDescriptor[1].xfer.doneIfs = 1;
    rxDescriptor[0].xfer.dstInc = ldmaCtrlSrcIncNone; // descriptor 1 is used as a sink for unwanted bytes
    rxDescriptor[1].xfer.dstInc = ldmaCtrlSrcIncOne; // data is read into here.
    txDescriptor[0].xfer.srcInc = ldmaCtrlSrcIncOne; // this is the command descriptor, always has valid data.
    USART_Enable(USART1, usartEnable);
    LDMA_Init(&dmaInit);
    }

    /**
    * Wait until the transmitter is idle, then clear the RX
    */
    static inline bool waitForIdle() {
    unsigned timeout = 5000;
    while (--timeout != 0 && (dmaBusy || !(USART1->STATUS & USART_STATUS_TXIDLE)))
    continue;
    if (timeout == 0) {
    int cnt = LDMA_TransferRemainingCount((int) (dmaChannelSpiPrimary));
    return false;
    }
    USART1->CMD = USART_CMD_CLEARRX | USART_CMD_CLEARTX;
    return true;
    }

    /**
    * Receive a single byte from the SPI
    * @return The byte
    */

    static inline uint8_t readByte() {
    while (!(USART1->STATUS & USART_STATUS_RXDATAV))
    continue;
    return (uint8_t) USART1->RXDATA;
    }


    /**
    * Write a 1 byte command, return the response.
    * @param cmd The command to send
    */

    static uint8_t cmdWithResult(uint8_t cmd) {
    if (!waitForIdle())
    return 0;
    USART1->TXDATA = (uint32_t) cmd;
    USART1->TXDATA = (uint32_t) 0;
    readByte();
    return readByte();
    }

    /**
    * Write a 1 byte command, no response required.
    */

    static inline bool cmd(uint8_t cmd) {
    if (!waitForIdle())
    return false;
    USART1->TXDATA = (uint32_t) cmd;
    return true;
    }

    /**
    * Initialise the SPI and flash chip
    */
    void spiFlashInit(void) {
    spiInit();

    // wake up the chip, in case it's in deep sleep.
    cmdWithResult(WAKEUP_CMD);
    // now do a software reset
    cmd(RESET_ENABLE);
    cmd(SW_RESET);
    waitForIdle();
    }

    /**
    * Wait until the flash is not busy
    * @return false if timeout occurred.
    */
    static bool spiFlashWaitIdle() {
    unsigned timeout = 0;
    while (cmdWithResult(READ_STATUS_1) & 1)
    if(++timeout == 100000) {
    return false;
    }
    return timeout != 0;
    }
    /**
    * Read a block of data from the flash at a given address
    * @param address The byte address to read data from
    * @param buffer A buffer to hold the result
    * @param buflen The length of the buffer.
    * @return True if the read was successful
    */
    bool spiFlashReadData(uint32_t address, uint8_t *buffer, size_t buflen) {
    uint8_t cmdBuf[4];
    if (buflen == 0)
    return false;
    spiFlashWaitIdle();

    cmdBuf[0] = READ_CMD;
    cmdBuf[1] = address >> 16;
    cmdBuf[2] = address >> 8;
    cmdBuf[3] = address;
    // use 2 linked descriptors. The first sends the command and address.
    txDescriptor[0].xfer.xferCnt = sizeof(cmdBuf) - 1;
    txDescriptor[0].xfer.srcAddr = (uint32_t) cmdBuf;
    // the second sends dummy bytes to clock in the read bytes
    txDescriptor[1].xfer.xferCnt = buflen - 1;
    txDescriptor[1].xfer.srcAddr = (uint32_t) cmdBuf;
    txDescriptor[1].xfer.srcInc = ldmaCtrlSrcIncNone;
    // first rx descriptor soaks up the bytes clocked out during command write
    rxDescriptor[0].xfer.xferCnt = sizeof(cmdBuf) - 1;
    rxDescriptor[0].xfer.dstAddr = (uint32_t) buffer;
    // then read the data directly to the buffer
    rxDescriptor[1].xfer.xferCnt = buflen - 1;
    rxDescriptor[1].xfer.dstAddr = (uint32_t) buffer;
    dmaBusy = true;
    LDMA_StartTransfer(dmaChannelSpiPrimary, &rxConfig, rxDescriptor);
    LDMA_StartTransfer(dmaChannelSpiSecondary, &txConfig, txDescriptor);
    return waitForIdle(); // wait for completion
    }

    static bool writeEnable() {
    if(!spiFlashWaitIdle())
    return false;
    cmd(WRITE_ENABLE);
    return waitForIdle();
    }

    /**
    * Erase a sector, starting at the given address.
    * @param address The linear address.The lower 12 bits of the address must be zero.
    */
    bool spiFlashEraseSector(uint32_t address) {
    uint8_t cmdBuf[4];
    if(!writeEnable())
    return false;
    cmdBuf[0] = SECTOR_ERASE;
    cmdBuf[1] = address >> 16;
    cmdBuf[2] = address >> 8;
    cmdBuf[3] = address;
    txDescriptor[1].xfer.xferCnt = sizeof(cmdBuf) - 1;
    txDescriptor[1].xfer.srcAddr = (uint32_t) cmdBuf;
    txDescriptor[1].xfer.srcInc = ldmaCtrlSrcIncOne;
    dmaBusy = true;
    LDMA_StartTransfer(dmaChannelSpiPrimary, &txConfig, &txDescriptor[1]);
    return true;
    }

    /**
    * Write data to the flash
    * @param address // the address to write to
    * @param buffer // the data
    * @param buflen // length of the data
    * @return true if the write succeeded
    */
    bool spiFlashWriteData(uint32_t address, const uint8_t *buffer, size_t buflen) {
    uint8_t cmdBuf[4];
    while (buflen != 0) {
    if(!writeEnable())
    return false;
    unsigned len = buflen;
    unsigned maxChunk = 256 - (address & 0xFF);
    if (len > maxChunk)
    len = maxChunk;
    cmdBuf[0] = WRITE_CMD;
    cmdBuf[1] = address >> 16;
    cmdBuf[2] = address >> 8;
    cmdBuf[3] = address;
    // linked descriptors write the command followed by the data.
    txDescriptor[0].xfer.xferCnt = sizeof(cmdBuf) - 1;
    txDescriptor[0].xfer.srcAddr = (uint32_t) cmdBuf;
    txDescriptor[1].xfer.xferCnt = len - 1;
    txDescriptor[1].xfer.srcAddr = (uint32_t) buffer;
    txDescriptor[1].xfer.srcInc = ldmaCtrlSrcIncOne;
    dmaBusy = true;
    LDMA_StartTransfer(dmaChannelSpiPrimary, &txConfig, &txDescriptor[0]);
    buffer += len;
    address += len;
    if (address == SPI_FLASH_SIZE) {
    address = 0;
    }
    buflen -= len;
    }
    // don't return until the transfer is complete, since the caller may not preserve the buffer.
    return waitForIdle();
    }

    void LDMA_IRQHandler(void) {
    /* Get all pending and enabled interrupts. */
    uint32_t pending = LDMA_IntGetEnabled();
    /* Iterate over all LDMA channels. */
    for (uint32_t ch = 0; ch != dmaChannelCount; ch++) {
    uint32_t mask = 0x1u << ch;
    if (pending & mask) {
    /* Clear interrupt flag. */
    LDMA->IFC = mask;
    if (ch == dmaChannelSpiPrimary)
    dmaBusy = false;
    }
    }
    }