Add sd card support + savegames
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
/* spi.c
|
||||
Copyright 2021 Carl John Kugler III
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the License); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
//
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/mutex.h"
|
||||
#include "pico/sem.h"
|
||||
//
|
||||
#include "my_debug.h"
|
||||
//
|
||||
#include "spi.h"
|
||||
|
||||
static bool irqChannel1 = false;
|
||||
static bool irqShared = true;
|
||||
|
||||
void spi_irq_handler(spi_t *pSPI) {
|
||||
if (irqChannel1) {
|
||||
if (dma_hw->ints1 & 1u << pSPI->rx_dma) { // Ours?
|
||||
dma_hw->ints1 = 1u << pSPI->rx_dma; // clear it
|
||||
myASSERT(!dma_channel_is_busy(pSPI->rx_dma));
|
||||
sem_release(&pSPI->sem);
|
||||
}
|
||||
} else {
|
||||
if (dma_hw->ints0 & 1u << pSPI->rx_dma) { // Ours?
|
||||
dma_hw->ints0 = 1u << pSPI->rx_dma; // clear it
|
||||
myASSERT(!dma_channel_is_busy(pSPI->rx_dma));
|
||||
sem_release(&pSPI->sem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void set_spi_dma_irq_channel(bool useChannel1, bool shared) {
|
||||
irqChannel1 = useChannel1;
|
||||
irqShared = shared;
|
||||
}
|
||||
|
||||
// SPI Transfer: Read & Write (simultaneously) on SPI bus
|
||||
// If the data that will be received is not important, pass NULL as rx.
|
||||
// If the data that will be transmitted is not important,
|
||||
// pass NULL as tx and then the SPI_FILL_CHAR is sent out as each data
|
||||
// element.
|
||||
bool spi_transfer(spi_t *pSPI, const uint8_t *tx, uint8_t *rx, size_t length) {
|
||||
// myASSERT(512 == length || 1 == length);
|
||||
myASSERT(tx || rx);
|
||||
// myASSERT(!(tx && rx));
|
||||
|
||||
// tx write increment is already false
|
||||
if (tx) {
|
||||
channel_config_set_read_increment(&pSPI->tx_dma_cfg, true);
|
||||
} else {
|
||||
static const uint8_t dummy = SPI_FILL_CHAR;
|
||||
tx = &dummy;
|
||||
channel_config_set_read_increment(&pSPI->tx_dma_cfg, false);
|
||||
}
|
||||
|
||||
// rx read increment is already false
|
||||
if (rx) {
|
||||
channel_config_set_write_increment(&pSPI->rx_dma_cfg, true);
|
||||
} else {
|
||||
static uint8_t dummy = 0xA5;
|
||||
rx = &dummy;
|
||||
channel_config_set_write_increment(&pSPI->rx_dma_cfg, false);
|
||||
}
|
||||
// Clear the interrupt request.
|
||||
dma_hw->ints0 = 1u << pSPI->rx_dma;
|
||||
|
||||
dma_channel_configure(pSPI->tx_dma, &pSPI->tx_dma_cfg,
|
||||
&spi_get_hw(pSPI->hw_inst)->dr, // write address
|
||||
tx, // read address
|
||||
length, // element count (each element is of
|
||||
// size transfer_data_size)
|
||||
false); // start
|
||||
dma_channel_configure(pSPI->rx_dma, &pSPI->rx_dma_cfg,
|
||||
rx, // write address
|
||||
&spi_get_hw(pSPI->hw_inst)->dr, // read address
|
||||
length, // element count (each element is of
|
||||
// size transfer_data_size)
|
||||
false); // start
|
||||
|
||||
// start them exactly simultaneously to avoid races (in extreme cases
|
||||
// the FIFO could overflow)
|
||||
dma_start_channel_mask((1u << pSPI->tx_dma) | (1u << pSPI->rx_dma));
|
||||
|
||||
/* Timeout 1 sec */
|
||||
uint32_t timeOut = 1000;
|
||||
/* Wait until master completes transfer or time out has occured. */
|
||||
bool rc = sem_acquire_timeout_ms(
|
||||
&pSPI->sem, timeOut); // Wait for notification from ISR
|
||||
if (!rc) {
|
||||
// If the timeout is reached the function will return false
|
||||
DBG_PRINTF("Notification wait timed out in %s\n", __FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
// Shouldn't be necessary:
|
||||
dma_channel_wait_for_finish_blocking(pSPI->tx_dma);
|
||||
dma_channel_wait_for_finish_blocking(pSPI->rx_dma);
|
||||
|
||||
myASSERT(!dma_channel_is_busy(pSPI->tx_dma));
|
||||
myASSERT(!dma_channel_is_busy(pSPI->rx_dma));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void spi_lock(spi_t *pSPI) {
|
||||
myASSERT(mutex_is_initialized(&pSPI->mutex));
|
||||
mutex_enter_blocking(&pSPI->mutex);
|
||||
}
|
||||
void spi_unlock(spi_t *pSPI) {
|
||||
myASSERT(mutex_is_initialized(&pSPI->mutex));
|
||||
mutex_exit(&pSPI->mutex);
|
||||
}
|
||||
|
||||
bool my_spi_init(spi_t *pSPI) {
|
||||
auto_init_mutex(my_spi_init_mutex);
|
||||
mutex_enter_blocking(&my_spi_init_mutex);
|
||||
if (!pSPI->initialized) {
|
||||
//// The SPI may be shared (using multiple SSs); protect it
|
||||
//pSPI->mutex = xSemaphoreCreateRecursiveMutex();
|
||||
//xSemaphoreTakeRecursive(pSPI->mutex, portMAX_DELAY);
|
||||
if (!mutex_is_initialized(&pSPI->mutex)) mutex_init(&pSPI->mutex);
|
||||
spi_lock(pSPI);
|
||||
|
||||
// For the IRQ notification:
|
||||
sem_init(&pSPI->sem, 0, 1);
|
||||
|
||||
/* Configure component */
|
||||
// Enable SPI at 100 kHz and connect to GPIOs
|
||||
spi_init(pSPI->hw_inst, 100 * 1000);
|
||||
spi_set_format(pSPI->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
|
||||
|
||||
gpio_set_function(pSPI->miso_gpio, GPIO_FUNC_SPI);
|
||||
gpio_set_function(pSPI->mosi_gpio, GPIO_FUNC_SPI);
|
||||
gpio_set_function(pSPI->sck_gpio, GPIO_FUNC_SPI);
|
||||
// ss_gpio is initialized in sd_init_driver()
|
||||
|
||||
// Slew rate limiting levels for GPIO outputs.
|
||||
// enum gpio_slew_rate { GPIO_SLEW_RATE_SLOW = 0, GPIO_SLEW_RATE_FAST = 1 }
|
||||
// void gpio_set_slew_rate (uint gpio,enum gpio_slew_rate slew)
|
||||
// Default appears to be GPIO_SLEW_RATE_SLOW.
|
||||
|
||||
// Drive strength levels for GPIO outputs.
|
||||
// enum gpio_drive_strength { GPIO_DRIVE_STRENGTH_2MA = 0, GPIO_DRIVE_STRENGTH_4MA = 1, GPIO_DRIVE_STRENGTH_8MA = 2,
|
||||
// GPIO_DRIVE_STRENGTH_12MA = 3 }
|
||||
// enum gpio_drive_strength gpio_get_drive_strength (uint gpio)
|
||||
if (pSPI->set_drive_strength) {
|
||||
gpio_set_drive_strength(pSPI->mosi_gpio, pSPI->mosi_gpio_drive_strength);
|
||||
gpio_set_drive_strength(pSPI->sck_gpio, pSPI->sck_gpio_drive_strength);
|
||||
}
|
||||
|
||||
// SD cards' DO MUST be pulled up.
|
||||
gpio_pull_up(pSPI->miso_gpio);
|
||||
|
||||
// Grab some unused dma channels
|
||||
pSPI->tx_dma = dma_claim_unused_channel(true);
|
||||
pSPI->rx_dma = dma_claim_unused_channel(true);
|
||||
|
||||
pSPI->tx_dma_cfg = dma_channel_get_default_config(pSPI->tx_dma);
|
||||
pSPI->rx_dma_cfg = dma_channel_get_default_config(pSPI->rx_dma);
|
||||
channel_config_set_transfer_data_size(&pSPI->tx_dma_cfg, DMA_SIZE_8);
|
||||
channel_config_set_transfer_data_size(&pSPI->rx_dma_cfg, DMA_SIZE_8);
|
||||
|
||||
// We set the outbound DMA to transfer from a memory buffer to the SPI
|
||||
// transmit FIFO paced by the SPI TX FIFO DREQ The default is for the
|
||||
// read address to increment every element (in this case 1 byte -
|
||||
// DMA_SIZE_8) and for the write address to remain unchanged.
|
||||
channel_config_set_dreq(&pSPI->tx_dma_cfg, spi_get_index(pSPI->hw_inst)
|
||||
? DREQ_SPI1_TX
|
||||
: DREQ_SPI0_TX);
|
||||
channel_config_set_write_increment(&pSPI->tx_dma_cfg, false);
|
||||
|
||||
// We set the inbound DMA to transfer from the SPI receive FIFO to a
|
||||
// memory buffer paced by the SPI RX FIFO DREQ We coinfigure the read
|
||||
// address to remain unchanged for each element, but the write address
|
||||
// to increment (so data is written throughout the buffer)
|
||||
channel_config_set_dreq(&pSPI->rx_dma_cfg, spi_get_index(pSPI->hw_inst)
|
||||
? DREQ_SPI1_RX
|
||||
: DREQ_SPI0_RX);
|
||||
channel_config_set_read_increment(&pSPI->rx_dma_cfg, false);
|
||||
|
||||
/* Theory: we only need an interrupt on rx complete,
|
||||
since if rx is complete, tx must also be complete. */
|
||||
|
||||
// Configure the processor to run dma_handler() when DMA IRQ 0/1 is
|
||||
// asserted:
|
||||
int irq = irqChannel1 ? DMA_IRQ_1 : DMA_IRQ_0;
|
||||
if (irqShared) {
|
||||
irq_add_shared_handler(
|
||||
irq, pSPI->dma_isr,
|
||||
PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
|
||||
} else {
|
||||
irq_set_exclusive_handler(irq, pSPI->dma_isr);
|
||||
}
|
||||
|
||||
// Tell the DMA to raise IRQ line 0/1 when the channel finishes a block
|
||||
if (irqChannel1) {
|
||||
dma_channel_set_irq1_enabled(pSPI->rx_dma, true);
|
||||
} else {
|
||||
dma_channel_set_irq0_enabled(pSPI->rx_dma, true);
|
||||
}
|
||||
irq_set_enabled(irq, true);
|
||||
LED_INIT();
|
||||
pSPI->initialized = true;
|
||||
spi_unlock(pSPI);
|
||||
}
|
||||
mutex_exit(&my_spi_init_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* [] END OF FILE */
|
||||
Reference in New Issue
Block a user