Code fonctionnel, on écrit sur la carte SD et sur la clé USB

This commit is contained in:
Samuel 2025-06-14 20:02:46 +02:00
parent c877fd05f6
commit e2d49bac4f
43 changed files with 7885 additions and 12 deletions

View File

@ -6,7 +6,8 @@
"${workspaceFolder}/build/generated/pico_base",
"${env:PICO_SDK_PATH}/src/**/include",
"${env:PICO_SDK_PATH}/lib/**/src",
"${workspaceFolder}/lib/FatFs/source"
"${workspaceFolder}/lib/FatFs/source",
"${workspaceFolder}/lib/sd_driver"
],
"myCompilerPath": "/usr/bin/arm-none-eabi-gcc"

View File

@ -9,6 +9,11 @@
"tusb_common.h": "c",
"diskio.h": "c",
"ff.h": "c",
"time.h": "c"
"time.h": "c",
"hw_config.h": "c",
"sd_card.h": "c",
"rp2040_sdio.h": "c",
"util.h": "c",
"stddef.h": "c"
}
}

View File

@ -12,6 +12,8 @@ pico_sdk_init()
add_executable(host_cdc_msc_hid)
pico_generate_pio_header(host_cdc_msc_hid ${CMAKE_CURRENT_LIST_DIR}/lib/sd_driver/SDIO/rp2040_sdio.pio)
# Example source
target_sources(host_cdc_msc_hid PUBLIC
hid_app.c
@ -19,19 +21,52 @@ target_sources(host_cdc_msc_hid PUBLIC
msc_app.c
cdc_app.c
diskio_USB.c
diskio_SDIO.c
lib/FatFs/source/ff.c
lib/FatFs/source/ffsystem.c
lib/FatFs/source/diskio.c
lib/sd_driver/my_debug.c
lib/sd_driver/dma_interrupts.c
lib/sd_driver/sd_card.c
lib/sd_driver/crc.c
lib/sd_driver/crash.c
lib/sd_driver/f_util.c
lib/sd_driver/my_rtc.c
lib/sd_driver/sd_timeouts.c
lib/sd_driver/SDIO/rp2040_sdio.c
lib/sd_driver/SDIO/sd_card_sdio.c
lib/sd_driver/SPI/my_spi.c
lib/sd_driver/SPI/sd_card_spi.c
lib/sd_driver/SPI/sd_spi.c
)
# Make sure TinyUSB can find tusb_config.h
target_include_directories(host_cdc_msc_hid PUBLIC
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/lib/FatFs/source/)
${CMAKE_CURRENT_LIST_DIR}/
${CMAKE_CURRENT_LIST_DIR}/lib/FatFs/source/
${CMAKE_CURRENT_LIST_DIR}/lib/sd_driver/)
if(PICO_RISCV)
set(HWDEP_LIBS hardware_watchdog)
else()
set(HWDEP_LIBS cmsis_core)
endif()
# In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_host
# for TinyUSB device support and tinyusb_board for the additional board support library used by the example
target_link_libraries(host_cdc_msc_hid PUBLIC pico_stdlib tinyusb_host tinyusb_board)
target_link_libraries( host_cdc_msc_hid PUBLIC
pico_stdlib
tinyusb_host
tinyusb_board
hardware_dma
hardware_pio
hardware_spi
hardware_sync
pico_aon_timer
pico_stdlib
${HWDEP_LIBS}
)
pico_add_extra_outputs(host_cdc_msc_hid)

173
diskio_SDIO.c Normal file
View File

@ -0,0 +1,173 @@
/* glue.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.
*/
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
//
//
#include "lib/sd_driver/hw_config.h"
#include "lib/sd_driver/my_debug.h"
#include "lib/sd_driver/sd_card.h"
//
#include "diskio.h" /* Declarations of disk functions */
//#define TRACE_PRINTF(fmt, args...)
#define TRACE_PRINTF printf
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS SDIO_disk_status(BYTE pdrv /* Physical drive number to identify the drive */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
sd_card_detect(sd_card_p); // Fast: just a GPIO read
return sd_card_p->state.m_Status; // See http://elm-chan.org/fsw/ff/doc/dstat.html
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS SDIO_disk_initialize(
BYTE pdrv /* Physical drive number to identify the drive */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
bool ok = sd_init_driver();
if (!ok) return RES_NOTRDY;
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
DSTATUS ds = disk_status(pdrv);
if (STA_NODISK & ds)
return ds;
// See http://elm-chan.org/fsw/ff/doc/dstat.html
return sd_card_p->init(sd_card_p);
}
static int sdrc2dresult(int sd_rc) {
switch (sd_rc) {
case SD_BLOCK_DEVICE_ERROR_NONE:
return RES_OK;
case SD_BLOCK_DEVICE_ERROR_UNUSABLE:
case SD_BLOCK_DEVICE_ERROR_NO_RESPONSE:
case SD_BLOCK_DEVICE_ERROR_NO_INIT:
case SD_BLOCK_DEVICE_ERROR_NO_DEVICE:
return RES_NOTRDY;
case SD_BLOCK_DEVICE_ERROR_PARAMETER:
case SD_BLOCK_DEVICE_ERROR_UNSUPPORTED:
return RES_PARERR;
case SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED:
return RES_WRPRT;
case SD_BLOCK_DEVICE_ERROR_CRC:
case SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK:
case SD_BLOCK_DEVICE_ERROR_ERASE:
case SD_BLOCK_DEVICE_ERROR_WRITE:
default:
return RES_ERROR;
}
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT SDIO_disk_read(BYTE pdrv, /* Physical drive number to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) {
TRACE_PRINTF("Error: !sd_card_p, pdrv: %d\n", pdrv);
return RES_PARERR;
}
int rc = sd_card_p->read_blocks(sd_card_p, buff, sector, count);
return sdrc2dresult(rc);
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT SDIO_disk_write(BYTE pdrv, /* Physical drive number to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
int rc = sd_card_p->write_blocks(sd_card_p, buff, sector, count);
return sdrc2dresult(rc);
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT SDIO_disk_ioctl(BYTE pdrv, /* Physical drive number (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
) {
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
sd_card_t *sd_card_p = sd_get_by_num(pdrv);
if (!sd_card_p) return RES_PARERR;
switch (cmd) {
case GET_SECTOR_COUNT: { // Retrieves number of available sectors, the
// largest allowable LBA + 1, on the drive
// into the LBA_t variable pointed by buff.
// This command is used by f_mkfs and f_fdisk
// function to determine the size of
// volume/partition to be created. It is
// required when FF_USE_MKFS == 1.
static LBA_t n;
n = sd_card_p->get_num_sectors(sd_card_p);
*(LBA_t *)buff = n;
if (!n) return RES_ERROR;
return RES_OK;
}
case GET_BLOCK_SIZE: { // Retrieves erase block size of the flash
// memory media in unit of sector into the DWORD
// variable pointed by buff. The allowable value
// is 1 to 32768 in power of 2. Return 1 if the
// erase block size is unknown or non flash
// memory media. This command is used by only
// f_mkfs function and it attempts to align data
// area on the erase block boundary. It is
// required when FF_USE_MKFS == 1.
static DWORD bs = 1;
*(DWORD *)buff = bs;
return RES_OK;
}
case CTRL_SYNC:
sd_card_p->sync(sd_card_p);
return RES_OK;
default:
return RES_PARERR;
}
}

View File

@ -110,6 +110,7 @@ DRESULT USB_disk_ioctl (
return RES_OK;
}
/*
DWORD get_fattime (void){
return
(2025-1970) << 25 | // Année
@ -119,4 +120,4 @@ DWORD get_fattime (void){
29 << 5 | // Minutes
30 << 0 // Secondes
;
}
}*/

View File

@ -26,7 +26,7 @@ DSTATUS disk_status (
int result;
if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){
SDIO_disk_status(pdrv - DEV_USB_MIN);
return 0; // OK
}
@ -50,8 +50,9 @@ DSTATUS disk_initialize (
{
DSTATUS stat;
int result;
printf("disk_initialize : %d\n", pdrv);
if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){
SDIO_disk_initialize(pdrv - DEV_SDIO_MIN);
return 0; // OK
}
@ -79,7 +80,7 @@ DRESULT disk_read (
int result;
if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){
SDIO_disk_read(pdrv - DEV_SDIO_MIN, buff, sector, count);
return 0; // OK
}
@ -110,7 +111,7 @@ DRESULT disk_write (
int result;
if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){
result = SDIO_disk_write(pdrv - DEV_SDIO_MIN, buff, sector, count);
return 0; // OK
}
@ -140,7 +141,7 @@ DRESULT disk_ioctl (
if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){
SDIO_disk_ioctl(pdrv - DEV_SDIO_MIN, cmd, buff);
return 0; // OK
}

View File

@ -2,6 +2,8 @@
/ Low level disk interface modlue include file (C)ChaN, 2019 /
/-----------------------------------------------------------------------*/
#include "ff.h"
#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED
@ -38,6 +40,12 @@ DRESULT USB_disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
DRESULT USB_disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
DRESULT USB_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
DSTATUS SDIO_disk_initialize (BYTE pdrv);
DSTATUS SDIO_disk_status (BYTE pdrv);
DRESULT SDIO_disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
DRESULT SDIO_disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
DRESULT SDIO_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
/* Disk Status Bits (DSTATUS) */

214
lib/sd_driver/SDIO/SdioCard.h Executable file
View File

@ -0,0 +1,214 @@
/**
* Copyright (c) 2011-2022 Bill Greiman
* This file is part of the SdFat library for SD memory cards.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef sd_sdio_h
#define sd_sdio_h
#include "sd_card.h"
/** Initialize the SD card.
* \return true for success or false for failure.
*/
bool sd_sdio_begin(sd_card_t *sd_card_p);
/** CMD6 Switch mode: Check Function Set Function.
* \param[in] arg CMD6 argument.
* \param[out] status return status data.
*
* \return true for success or false for failure.
*/
bool sd_sdio_cardCMD6(sd_card_t *sd_card_p, uint32_t arg, uint8_t *status);
/** Disable an SDIO card.
* not implemented.
*/
// void sd_sdio_end() {}
#ifndef DOXYGEN_SHOULD_SKIP_THIS
uint32_t __attribute__((error("use sectorCount()"))) cardSize();
#endif // DOXYGEN_SHOULD_SKIP_THIS
/** Erase a range of sectors.
*
* \param[in] firstSector The address of the first sector in the range.
* \param[in] lastSector The address of the last sector in the range.
*
* \note This function requests the SD card to do a flash erase for a
* range of sectors. The data on the card after an erase operation is
* either 0 or 1, depends on the card vendor. The card must support
* single sector erase.
*
* \return true for success or false for failure.
*/
bool sd_sdio_erase(sd_card_t *sd_card_p, uint32_t firstSector, uint32_t lastSector);
/**
* \return code for the last error. See SdCardInfo.h for a list of error codes.
*/
uint8_t sd_sdio_errorCode(sd_card_t *sd_card_p) /* const */;
/** \return error data for last error. */
uint32_t sd_sdio_errorData() /* const */;
/** \return error line for last error. Tmp function for debug. */
uint32_t sd_sdio_errorLine(sd_card_t *sd_card_p) /* const */;
/**
* Check for busy with CMD13.
*
* \return true if busy else false.
*/
bool sd_sdio_isBusy(sd_card_t *sd_card_p);
/** \return the SD clock frequency in kHz. */
//uint32_t sd_sdio_kHzSdClk();
/**
* Read a 512 byte sector from an SD card.
*
* \param[in] sector Logical sector to be read.
* \param[out] dst Pointer to the location that will receive the data.
* \return true for success or false for failure.
*/
bool sd_sdio_readSector(sd_card_t *sd_card_p, uint32_t sector, uint8_t *dst);
/**
* Read multiple 512 byte sectors from an SD card.
*
* \param[in] sector Logical sector to be read.
* \param[in] ns Number of sectors to be read.
* \param[out] dst Pointer to the location that will receive the data.
* \return true for success or false for failure.
*/
bool sd_sdio_readSectors(sd_card_t *sd_card_p, uint32_t sector, uint8_t *dst, size_t ns);
// Read 512-bit SD status
bool rp2040_sdio_get_sd_status(sd_card_t *sd_card_p, uint8_t response[64]);
/** Read one data sector in a multiple sector read sequence
*
* \param[out] dst Pointer to the location for the data to be read.
*
* \return true for success or false for failure.
*/
bool sd_sdio_readData(sd_card_t *sd_card_p, uint8_t *dst);
/** Read OCR register.
*
* \param[out] ocr Value of OCR register.
* \return true for success or false for failure.
*/
bool sd_sdio_readOCR(sd_card_t *sd_card_p, uint32_t *ocr);
/** Read SCR register.
*
* \param[out] scr Value of SCR register.
* \return true for success or false for failure.
*/
// bool sd_sdio_readSCR(sd_card_t *sd_card_p, scr_t *scr);
/** Start a read multiple sectors sequence.
*
* \param[in] sector Address of first sector in sequence.
*
* \note This function is used with readData() and readStop() for optimized
* multiple sector reads. SPI chipSelect must be low for the entire sequence.
*
* \return true for success or false for failure.
*/
// bool sd_sdio_readStart(uint32_t sector);
/** Start a read multiple sectors sequence.
*
* \param[in] sector Address of first sector in sequence.
* \param[in] count Maximum sector count.
* \note This function is used with readData() and readStop() for optimized
* multiple sector reads. SPI chipSelect must be low for the entire sequence.
*
* \return true for success or false for failure.
*/
bool sd_sdio_readStart(sd_card_t *sd_card_p, uint32_t sector, uint32_t count);
/** End a read multiple sectors sequence.
*
* \return true for success or false for failure.
*/
bool sd_sdio_readStop(sd_card_t *sd_card_p);
/** \return SDIO card status. */
uint32_t sd_sdio_status(sd_card_t *sd_card_p);
/**
* Determine the size of an SD flash memory card.
*
* \return The number of 512 byte data sectors in the card
* or zero if an error occurs.
*/
uint32_t sd_sdio_sectorCount(sd_card_t *sd_card_p);
/**
* Send CMD12 to stop read or write.
*
* \param[in] blocking If true, wait for command complete.
*
* \return true for success or false for failure.
*/
bool sd_sdio_stopTransmission(sd_card_t *sd_card_p, bool blocking);
/** \return success if sync successful. Not for user apps. */
//bool sd_sdio_syncDevice(sd_card_t *sd_card_p);
/** Return the card type: SD V1, SD V2 or SDHC
* \return 0 - SD V1, 1 - SD V2, or 3 - SDHC.
*/
uint8_t sd_sdio_type(sd_card_t *sd_card_p) /* const */;
/**
* Writes a 512 byte sector to an SD card.
*
* \param[in] sector Logical sector to be written.
* \param[in] src Pointer to the location of the data to be written.
* \return true for success or false for failure.
*/
bool sd_sdio_writeSector(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src);
/**
* Write multiple 512 byte sectors to an SD card.
*
* \param[in] sector Logical sector to be written.
* \param[in] ns Number of sectors to be written.
* \param[in] src Pointer to the location of the data to be written.
* \return true for success or false for failure.
*/
bool sd_sdio_writeSectors(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src, size_t ns);
/** Write one data sector in a multiple sector write sequence.
* \param[in] src Pointer to the location of the data to be written.
* \return true for success or false for failure.
*/
bool sd_sdio_writeData(sd_card_t *sd_card_p, const uint8_t *src);
/** Start a write multiple sectors sequence.
*
* \param[in] sector Address of first sector in sequence.
*
* \note This function is used with writeData() and writeStop()
* for optimized multiple sector writes.
*
* \return true for success or false for failure.
*/
// bool sd_sdio_writeStart(sd_card_t *sd_card_p, uint32_t sector);
/** Start a write multiple sectors sequence.
*
* \param[in] sector Address of first sector in sequence.
* \param[in] count Maximum sector count.
* \note This function is used with writeData() and writeStop()
* for optimized multiple sector writes.
*
* \return true for success or false for failure.
*/
bool sd_sdio_writeStart(sd_card_t *sd_card_p, uint32_t sector, uint32_t count);
/** End a write multiple sectors sequence.
*
* \return true for success or false for failure.
*/
bool sd_sdio_writeStop(sd_card_t *sd_card_p);
void sd_sdio_ctor(sd_card_t *sd_card_p);
#endif // sd_sdio_h

View File

@ -0,0 +1,181 @@
// Platform-specific definitions for ZuluSCSI RP2040 hardware.
#pragma once
#include <stdint.h>
// #include <Arduino.h>
// #include "ZuluSCSI_platform_gpio.h"
// #include "scsiHostPhy.h"
#include "SdioCard.h"
#include "pico/stdlib.h"
#ifdef __cplusplus
extern "C" {
#endif
#define delayMicroseconds sleep_us
/* These are used in debug output and default SCSI strings */
extern const char *g_azplatform_name;
#define PLATFORM_NAME "ZuluSCSI RP2040"
#define PLATFORM_REVISION "2.0"
#define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_10
#define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 32768
#define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 65536
#define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 8192
#define SD_USE_SDIO 1
#define PLATFORM_HAS_INITIATOR_MODE 1
// NOTE: The driver supports synchronous speeds higher than 10MB/s, but this
// has not been tested due to lack of fast enough SCSI adapter.
// #define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_TURBO
// Debug logging function, can be used to print to e.g. serial port.
// May get called from interrupt handlers.
void azplatform_log(const char *s);
void azplatform_emergency_log_save();
#if 0
// Timing and delay functions.
// Arduino platform already provides these
// unsigned long millis(void);
void delay(unsigned long ms);
// Short delays, can be called from interrupt mode
static inline void delay_ns(unsigned long ns)
{
delayMicroseconds((ns + 999) / 1000);
}
#endif
// Approximate fast delay
static inline void delay_100ns()
{
asm volatile ("nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop");
}
// Initialize SD card and GPIO configuration
void azplatform_init();
// Initialization for main application, not used for bootloader
void azplatform_late_init();
// Disable the status LED
void azplatform_disable_led(void);
// Query whether initiator mode is enabled on targets with PLATFORM_HAS_INITIATOR_MODE
bool azplatform_is_initiator_mode_enabled();
// Setup soft watchdog if supported
void azplatform_reset_watchdog();
// Set callback that will be called during data transfer to/from SD card.
// This can be used to implement simultaneous transfer to SCSI bus.
typedef void (*sd_callback_t)(uint32_t bytes_complete);
void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer);
// Reprogram firmware in main program area.
#ifndef RP2040_DISABLE_BOOTLOADER
#define AZPLATFORM_BOOTLOADER_SIZE (128 * 1024)
#define AZPLATFORM_FLASH_TOTAL_SIZE (1024 * 1024)
#define AZPLATFORM_FLASH_PAGE_SIZE 4096
bool azplatform_rewrite_flash_page(uint32_t offset, uint8_t buffer[AZPLATFORM_FLASH_PAGE_SIZE]);
void azplatform_boot_to_main_firmware();
#endif
// ROM drive in the unused external flash area
#ifndef RP2040_DISABLE_ROMDRIVE
#define PLATFORM_HAS_ROM_DRIVE 1
// Check maximum available space for ROM drive in bytes
uint32_t azplatform_get_romdrive_maxsize();
// Read ROM drive area
bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count);
// Reprogram ROM drive area
#define AZPLATFORM_ROMDRIVE_PAGE_SIZE 4096
bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count);
#endif
// Parity lookup tables for write and read from SCSI bus.
// These are used by macros below and the code in scsi_accel_rp2040.cpp
extern const uint16_t g_scsi_parity_lookup[256];
extern const uint16_t g_scsi_parity_check_lookup[512];
// Below are GPIO access definitions that are used from scsiPhy.cpp.
// Write a single SCSI pin.
// Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state.
#define SCSI_OUT(pin, state) \
*(state ? &sio_hw->gpio_clr : &sio_hw->gpio_set) = 1 << (SCSI_OUT_ ## pin)
// Read a single SCSI pin.
// Example use: SCSI_IN(ATN), returns 1 for active low state.
#define SCSI_IN(pin) \
((sio_hw->gpio_in & (1 << (SCSI_IN_ ## pin))) ? 0 : 1)
// Set pin directions for initiator vs. target mode
#define SCSI_ENABLE_INITIATOR() \
(sio_hw->gpio_oe_set = (1 << SCSI_OUT_ACK) | \
(1 << SCSI_OUT_ATN)), \
(sio_hw->gpio_oe_clr = (1 << SCSI_IN_IO) | \
(1 << SCSI_IN_CD) | \
(1 << SCSI_IN_MSG) | \
(1 << SCSI_IN_REQ))
// Enable driving of shared control pins
#define SCSI_ENABLE_CONTROL_OUT() \
(sio_hw->gpio_oe_set = (1 << SCSI_OUT_CD) | \
(1 << SCSI_OUT_MSG))
// Set SCSI data bus to output
#define SCSI_ENABLE_DATA_OUT() \
(sio_hw->gpio_clr = (1 << SCSI_DATA_DIR), \
sio_hw->gpio_oe_set = SCSI_IO_DATA_MASK)
// Write SCSI data bus, also sets REQ to inactive.
#define SCSI_OUT_DATA(data) \
gpio_put_masked(SCSI_IO_DATA_MASK | (1 << SCSI_OUT_REQ), \
g_scsi_parity_lookup[(uint8_t)(data)] | (1 << SCSI_OUT_REQ)), \
SCSI_ENABLE_DATA_OUT()
// Release SCSI data bus and REQ signal
#define SCSI_RELEASE_DATA_REQ() \
(sio_hw->gpio_oe_clr = SCSI_IO_DATA_MASK, \
sio_hw->gpio_set = (1 << SCSI_DATA_DIR) | (1 << SCSI_OUT_REQ))
// Release all SCSI outputs
#define SCSI_RELEASE_OUTPUTS() \
SCSI_RELEASE_DATA_REQ(), \
sio_hw->gpio_oe_clr = (1 << SCSI_OUT_CD) | \
(1 << SCSI_OUT_MSG), \
sio_hw->gpio_set = (1 << SCSI_OUT_IO) | \
(1 << SCSI_OUT_CD) | \
(1 << SCSI_OUT_MSG) | \
(1 << SCSI_OUT_RST) | \
(1 << SCSI_OUT_BSY) | \
(1 << SCSI_OUT_REQ) | \
(1 << SCSI_OUT_SEL)
// Read SCSI data bus
#define SCSI_IN_DATA() \
(~sio_hw->gpio_in & SCSI_IO_DATA_MASK) >> SCSI_IO_SHIFT
// SD card driver for SdFat
#ifdef SD_USE_SDIO
struct SdioConfig;
// extern SdioConfig g_sd_sdio_config;
// #define SD_CONFIG g_sd_sdio_config
#define SD_CONFIG_CRASH g_sd_sdio_config
#else
struct SdSpiConfig;
extern SdSpiConfig g_sd_spi_config;
#define SD_CONFIG g_sd_spi_config
#define SD_CONFIG_CRASH g_sd_spi_config
#endif
#ifdef __cplusplus
}
#endif

860
lib/sd_driver/SDIO/rp2040_sdio.c Executable file
View File

@ -0,0 +1,860 @@
// Implementation of SDIO communication for RP2040
//
// The RP2040 official work-in-progress code at
// https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_sd_card
// may be useful reference, but this is independent implementation.
//
// For official SDIO specifications, refer to:
// https://www.sdcard.org/downloads/pls/
// "SDIO Physical Layer Simplified Specification Version 8.00"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
//
#include "hardware/dma.h"
#include "hardware/gpio.h"
#include "hardware/pio.h"
#if !PICO_RISCV
# if PICO_RP2040
# include "RP2040.h"
# endif
# if PICO_RP2350
# include "RP2350.h"
# endif
#endif
//
#include "dma_interrupts.h"
#include "hw_config.h"
#include "rp2040_sdio.h"
#include "rp2040_sdio.pio.h"
#include "delays.h"
#include "sd_card.h"
#include "sd_timeouts.h"
#include "my_debug.h"
#include "util.h"
//
#include "rp2040_sdio.h"
#define azdbg(arg1, ...) {\
DBG_PRINTF("%s,%s:%d %s\n", __func__, __FILE__, __LINE__, arg1); \
}
#define STATE sd_card_p->sdio_if_p->state
#define SDIO_PIO sd_card_p->sdio_if_p->SDIO_PIO
#define SDIO_CMD_SM STATE.SDIO_CMD_SM
#define SDIO_DATA_SM STATE.SDIO_DATA_SM
#define SDIO_DMA_CH STATE.SDIO_DMA_CH
#define SDIO_DMA_CHB STATE.SDIO_DMA_CHB
#define SDIO_CMD sd_card_p->sdio_if_p->CMD_gpio
#define SDIO_CLK sd_card_p->sdio_if_p->CLK_gpio
#define SDIO_D0 sd_card_p->sdio_if_p->D0_gpio
#define SDIO_D1 sd_card_p->sdio_if_p->D1_gpio
#define SDIO_D2 sd_card_p->sdio_if_p->D2_gpio
#define SDIO_D3 sd_card_p->sdio_if_p->D3_gpio
// Force everything to idle state
static sdio_status_t rp2040_sdio_stop(sd_card_t *sd_card_p);
/*******************************************************
* Checksum algorithms
*******************************************************/
// Table lookup for calculating CRC-7 checksum that is used in SDIO command packets.
// Usage:
// uint8_t crc = 0;
// crc = crc7_table[crc ^ byte];
// .. repeat for every byte ..
static const uint8_t crc7_table[256] = {
0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, 0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee,
0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c, 0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc,
0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a,
0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28, 0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8,
0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6, 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26,
0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84, 0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14,
0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2, 0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42,
0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0, 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70,
0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc, 0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c,
0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce, 0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e,
0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98, 0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08,
0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa, 0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a,
0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34, 0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4,
0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06, 0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96,
0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50, 0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0,
0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62, 0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2
};
// Calculate the CRC16 checksum for parallel 4 bit lines separately.
// When the SDIO bus operates in 4-bit mode, the CRC16 algorithm
// is applied to each line separately and generates total of
// 4 x 16 = 64 bits of checksum.
__attribute__((optimize("Ofast")))
uint64_t sdio_crc16_4bit_checksum(uint32_t *data, uint32_t num_words)
{
uint64_t crc = 0;
uint32_t *end = data + num_words;
while (data < end)
{
for (int unroll = 0; unroll < 4; unroll++)
{
// Each 32-bit word contains 8 bits per line.
// Reverse the bytes because SDIO protocol is big-endian.
uint32_t data_in = __builtin_bswap32(*data++);
// Shift out 8 bits for each line
uint32_t data_out = crc >> 32;
crc <<= 32;
// XOR outgoing data to itself with 4 bit delay
data_out ^= (data_out >> 16);
// XOR incoming data to outgoing data with 4 bit delay
data_out ^= (data_in >> 16);
// XOR outgoing and incoming data to accumulator at each tap
uint64_t xorred = data_out ^ data_in;
crc ^= xorred;
crc ^= xorred << (5 * 4);
crc ^= xorred << (12 * 4);
}
}
return crc;
}
/*******************************************************
* Basic SDIO command execution
*******************************************************/
static void sdio_send_command(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t response_bits)
{
// azdbg("SDIO Command: ", (int)command, " arg ", arg);
// Format the arguments in the way expected by the PIO code.
uint32_t word0 =
(47 << 24) | // Number of bits in command minus one
( 1 << 22) | // Transfer direction from host to card
(command << 16) | // Command byte
(((arg >> 24) & 0xFF) << 8) | // MSB byte of argument
(((arg >> 16) & 0xFF) << 0);
uint32_t word1 =
(((arg >> 8) & 0xFF) << 24) |
(((arg >> 0) & 0xFF) << 16) | // LSB byte of argument
( 1 << 8); // End bit
// Set number of bits in response minus one, or leave at 0 if no response expected
if (response_bits)
{
word1 |= ((response_bits - 1) << 0);
}
// Calculate checksum in the order that the bytes will be transmitted (big-endian)
uint8_t crc = 0;
crc = crc7_table[crc ^ ((word0 >> 16) & 0xFF)];
crc = crc7_table[crc ^ ((word0 >> 8) & 0xFF)];
crc = crc7_table[crc ^ ((word0 >> 0) & 0xFF)];
crc = crc7_table[crc ^ ((word1 >> 24) & 0xFF)];
crc = crc7_table[crc ^ ((word1 >> 16) & 0xFF)];
word1 |= crc << 8;
// Transmit command
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
pio_sm_put(SDIO_PIO, SDIO_CMD_SM, word0);
pio_sm_put(SDIO_PIO, SDIO_CMD_SM, word1);
}
sdio_status_t rp2040_sdio_command_R1(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response)
{
sdio_send_command(sd_card_p, command, arg, response ? 48 : 0);
// Wait for response
uint32_t start = millis();
uint32_t wait_words = response ? 2 : 1;
while (pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM) < wait_words)
{
if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R1)
{
if (command != 8) // Don't log for missing SD card
{
azdbg("Timeout waiting for response in rp2040_sdio_command_R1(", (int)command, "), ",
"PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset,
" RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
" TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
EMSG_PRINTF("%s: Timeout waiting for response in rp2040_sdio_command_R1(0x%hx)\n", __func__, command);
}
// Reset the state machine program
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset));
return SDIO_ERR_RESPONSE_TIMEOUT;
}
}
if (response)
{
// Read out response packet
uint32_t resp0 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
uint32_t resp1 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
// azdbg("SDIO R1 response: ", resp0, " ", resp1);
// Calculate response checksum
uint8_t crc = 0;
crc = crc7_table[crc ^ ((resp0 >> 24) & 0xFF)];
crc = crc7_table[crc ^ ((resp0 >> 16) & 0xFF)];
crc = crc7_table[crc ^ ((resp0 >> 8) & 0xFF)];
crc = crc7_table[crc ^ ((resp0 >> 0) & 0xFF)];
crc = crc7_table[crc ^ ((resp1 >> 8) & 0xFF)];
uint8_t actual_crc = ((resp1 >> 0) & 0xFE);
if (crc != actual_crc)
{
// azdbg("rp2040_sdio_command_R1(", (int)command, "): CRC error, calculated ", crc, " packet has ", actual_crc);
EMSG_PRINTF("rp2040_sdio_command_R1(%d): CRC error, calculated 0x%hx, packet has 0x%hx\n", command, crc, actual_crc);
return SDIO_ERR_RESPONSE_CRC;
}
uint8_t response_cmd = ((resp0 >> 24) & 0xFF);
if (response_cmd != command && command != 41)
{
// azdbg("rp2040_sdio_command_R1(", (int)command, "): received reply for ", (int)response_cmd);
EMSG_PRINTF("%d rp2040_sdio_command_R1(%d): received reply for %d\n", __LINE__, command, response_cmd);
return SDIO_ERR_RESPONSE_CODE;
}
*response = ((resp0 & 0xFFFFFF) << 8) | ((resp1 >> 8) & 0xFF);
}
else
{
// Read out dummy marker
pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
}
return SDIO_OK;
}
sdio_status_t rp2040_sdio_command_R2(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t *response)
{
// The response is too long to fit in the PIO FIFO, so use DMA to receive it.
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
uint32_t response_buf[5];
dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, false);
channel_config_set_write_increment(&dmacfg, true);
channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_CMD_SM, false));
dma_channel_configure(SDIO_DMA_CH, &dmacfg, &response_buf, &SDIO_PIO->rxf[SDIO_CMD_SM], 5, true);
sdio_send_command(sd_card_p, command, arg, 136);
uint32_t start = millis();
while (dma_channel_is_busy(SDIO_DMA_CH))
{
if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R2)
{
azdbg("Timeout waiting for response in rp2040_sdio_command_R2(", (int)command, "), ",
"PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset,
" RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
" TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
// Reset the state machine program
dma_channel_abort(SDIO_DMA_CH);
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset));
return SDIO_ERR_RESPONSE_TIMEOUT;
}
}
dma_channel_abort(SDIO_DMA_CH);
// Copy the response payload to output buffer
response[0] = ((response_buf[0] >> 16) & 0xFF);
response[1] = ((response_buf[0] >> 8) & 0xFF);
response[2] = ((response_buf[0] >> 0) & 0xFF);
response[3] = ((response_buf[1] >> 24) & 0xFF);
response[4] = ((response_buf[1] >> 16) & 0xFF);
response[5] = ((response_buf[1] >> 8) & 0xFF);
response[6] = ((response_buf[1] >> 0) & 0xFF);
response[7] = ((response_buf[2] >> 24) & 0xFF);
response[8] = ((response_buf[2] >> 16) & 0xFF);
response[9] = ((response_buf[2] >> 8) & 0xFF);
response[10] = ((response_buf[2] >> 0) & 0xFF);
response[11] = ((response_buf[3] >> 24) & 0xFF);
response[12] = ((response_buf[3] >> 16) & 0xFF);
response[13] = ((response_buf[3] >> 8) & 0xFF);
response[14] = ((response_buf[3] >> 0) & 0xFF);
response[15] = ((response_buf[4] >> 0) & 0xFF);
// Calculate checksum of the payload
uint8_t crc = 0;
for (int i = 0; i < 15; i++)
{
crc = crc7_table[crc ^ response[i]];
}
uint8_t actual_crc = response[15] & 0xFE;
if (crc != actual_crc)
{
azdbg("rp2040_sdio_command_R2(", (int)command, "): CRC error, calculated ", crc, " packet has ", actual_crc);
return SDIO_ERR_RESPONSE_CRC;
}
uint8_t response_cmd = ((response_buf[0] >> 24) & 0xFF);
if (response_cmd != 0x3F)
{
azdbg("rp2040_sdio_command_R2(", (int)command, "): Expected reply code 0x3F");
return SDIO_ERR_RESPONSE_CODE;
}
return SDIO_OK;
}
sdio_status_t rp2040_sdio_command_R3(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response)
{
sdio_send_command(sd_card_p, command, arg, 48);
// Wait for response
uint32_t start = millis();
while (pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM) < 2)
{
if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R3)
{
azdbg("Timeout waiting for response in rp2040_sdio_command_R3(", (int)command, "), ",
"PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset,
" RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM),
" TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM));
// Reset the state machine program
pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM);
pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset));
return SDIO_ERR_RESPONSE_TIMEOUT;
}
}
// Read out response packet
uint32_t resp0 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
uint32_t resp1 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM);
*response = ((resp0 & 0xFFFFFF) << 8) | ((resp1 >> 8) & 0xFF);
// azdbg("SDIO R3 response: ", resp0, " ", resp1);
return SDIO_OK;
}
/*******************************************************
* Data reception from SD card
*******************************************************/
sdio_status_t rp2040_sdio_rx_start(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t num_blocks, size_t block_size)
{
// Buffer must be aligned
assert(((uint32_t)buffer & 3) == 0 && num_blocks <= SDIO_MAX_BLOCKS);
STATE.transfer_state = SDIO_RX;
STATE.transfer_start_time = millis();
STATE.data_buf = (uint32_t*)buffer;
STATE.blocks_done = 0;
STATE.total_blocks = num_blocks;
STATE.blocks_checksumed = 0;
STATE.checksum_errors = 0;
// Create DMA block descriptors to store each block of 512 bytes of data to buffer
// and then 8 bytes to STATE.received_checksums.
for (uint32_t i = 0; i < num_blocks; i++)
{
STATE.dma_blocks[i * 2].write_addr = buffer + i * block_size;
STATE.dma_blocks[i * 2].transfer_count = block_size / sizeof(uint32_t);
STATE.dma_blocks[i * 2 + 1].write_addr = &STATE.received_checksums[i];
STATE.dma_blocks[i * 2 + 1].transfer_count = 2;
}
STATE.dma_blocks[num_blocks * 2].write_addr = 0;
STATE.dma_blocks[num_blocks * 2].transfer_count = 0;
// Configure first DMA channel for reading from the PIO RX fifo
dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, false);
channel_config_set_write_increment(&dmacfg, true);
channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, false));
channel_config_set_bswap(&dmacfg, true);
channel_config_set_chain_to(&dmacfg, SDIO_DMA_CHB);
dma_channel_configure(SDIO_DMA_CH, &dmacfg, 0, &SDIO_PIO->rxf[SDIO_DATA_SM], 0, false);
// Configure second DMA channel for reconfiguring the first one
dmacfg = dma_channel_get_default_config(SDIO_DMA_CHB);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, true);
channel_config_set_write_increment(&dmacfg, true);
channel_config_set_ring(&dmacfg, true, 3);
dma_channel_configure(SDIO_DMA_CHB, &dmacfg, &dma_hw->ch[SDIO_DMA_CH].al1_write_addr,
STATE.dma_blocks, 2, false);
// Initialize PIO state machine
pio_sm_init(SDIO_PIO, SDIO_DATA_SM, STATE.pio_data_rx_offset, &STATE.pio_cfg_data_rx);
pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_DATA_SM, SDIO_D0, 4, false);
// Write number of nibbles to receive to Y register
pio_sm_put(SDIO_PIO, SDIO_DATA_SM, block_size * 2 + 16 - 1);
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_y, 32));
// Enable RX FIFO join because we don't need the TX FIFO during transfer.
// This gives more leeway for the DMA block switching
SDIO_PIO->sm[SDIO_DATA_SM].shiftctrl |= PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS;
// Start PIO and DMA
dma_channel_start(SDIO_DMA_CHB);
pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, true);
return SDIO_OK;
}
// Check checksums for received blocks
static void sdio_verify_rx_checksums(sd_card_t *sd_card_p, uint32_t maxcount, size_t block_size_words)
{
while (STATE.blocks_checksumed < STATE.blocks_done && maxcount-- > 0)
{
// Calculate checksum from received data
int blockidx = STATE.blocks_checksumed++;
uint64_t checksum = sdio_crc16_4bit_checksum(STATE.data_buf + blockidx * block_size_words,
block_size_words);
// Convert received checksum to little-endian format
uint32_t top = __builtin_bswap32(STATE.received_checksums[blockidx].top);
uint32_t bottom = __builtin_bswap32(STATE.received_checksums[blockidx].bottom);
uint64_t expected = ((uint64_t)top << 32) | bottom;
if (checksum != expected)
{
STATE.checksum_errors++;
if (STATE.checksum_errors == 1)
{
EMSG_PRINTF("SDIO checksum error in reception: block %d calculated 0x%llx expected 0x%llx\n",
blockidx, checksum, expected);
dump_bytes(block_size_words, (uint8_t *)STATE.data_buf + blockidx * block_size_words);
}
}
}
}
sdio_status_t rp2040_sdio_rx_poll(sd_card_t *sd_card_p, size_t block_size_words)
{
// Was everything done when the previous rx_poll() finished?
if (STATE.blocks_done >= STATE.total_blocks)
{
STATE.transfer_state = SDIO_IDLE;
}
else
{
// Use the idle time to calculate checksums
sdio_verify_rx_checksums(sd_card_p, 4, block_size_words);
// Check how many DMA control blocks have been consumed
uint32_t dma_ctrl_block_count = (dma_hw->ch[SDIO_DMA_CHB].read_addr - (uint32_t)&STATE.dma_blocks);
dma_ctrl_block_count /= sizeof(STATE.dma_blocks[0]);
// Compute how many complete SDIO blocks have been transferred
// When transfer ends, dma_ctrl_block_count == STATE.total_blocks * 2 + 1
STATE.blocks_done = (dma_ctrl_block_count - 1) / 2;
// NOTE: When all blocks are done, rx_poll() still returns SDIO_BUSY once.
// This provides a chance to start the SCSI transfer before the last checksums
// are computed. Any checksum failures can be indicated in SCSI status after
// the data transfer has finished.
}
if (STATE.transfer_state == SDIO_IDLE)
{
// Verify all remaining checksums.
sdio_verify_rx_checksums(sd_card_p, STATE.total_blocks, block_size_words);
if (STATE.checksum_errors == 0)
return SDIO_OK;
else
return SDIO_ERR_DATA_CRC;
}
else if (millis() - STATE.transfer_start_time >= sd_timeouts.rp2040_sdio_rx_poll)
{
azdbg("rp2040_sdio_rx_poll() timeout, "
"PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_DATA_SM) - (int)STATE.pio_data_rx_offset,
" RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_DATA_SM),
" TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_DATA_SM),
" DMA CNT: ", dma_hw->ch[SDIO_DMA_CH].al2_transfer_count);
rp2040_sdio_stop(sd_card_p);
return SDIO_ERR_DATA_TIMEOUT;
}
return SDIO_BUSY;
}
/*******************************************************
* Data transmission to SD card
*******************************************************/
static void sdio_start_next_block_tx(sd_card_t *sd_card_p)
{
// Initialize PIO
pio_sm_init(SDIO_PIO, SDIO_DATA_SM, STATE.pio_data_tx_offset, &STATE.pio_cfg_data_tx);
// Configure DMA to send the data block payload (512 bytes)
dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, true);
channel_config_set_write_increment(&dmacfg, false);
channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, true));
channel_config_set_bswap(&dmacfg, true);
channel_config_set_chain_to(&dmacfg, SDIO_DMA_CHB);
dma_channel_configure(SDIO_DMA_CH, &dmacfg,
&SDIO_PIO->txf[SDIO_DATA_SM], STATE.data_buf + STATE.blocks_done * SDIO_WORDS_PER_BLOCK,
SDIO_WORDS_PER_BLOCK, false);
// Prepare second DMA channel to send the CRC and block end marker
uint64_t crc = STATE.next_wr_block_checksum;
STATE.end_token_buf[0] = (uint32_t)(crc >> 32);
STATE.end_token_buf[1] = (uint32_t)(crc >> 0);
STATE.end_token_buf[2] = 0xFFFFFFFF;
channel_config_set_bswap(&dmacfg, false);
dma_channel_configure(SDIO_DMA_CHB, &dmacfg,
&SDIO_PIO->txf[SDIO_DATA_SM], STATE.end_token_buf, 3, false);
// Enable IRQ to trigger when block is done
switch (sd_card_p->sdio_if_p->DMA_IRQ_num) {
case DMA_IRQ_0:
// Clear any pending interrupt service request:
dma_hw->ints0 = 1 << SDIO_DMA_CHB;
dma_channel_set_irq0_enabled(SDIO_DMA_CHB, true);
break;
case DMA_IRQ_1:
// Clear any pending interrupt service request:
dma_hw->ints1 = 1 << SDIO_DMA_CHB;
dma_channel_set_irq1_enabled(SDIO_DMA_CHB, true);
break;
default:
assert(false);
}
// Initialize register X with nibble count and register Y with response bit count
pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 1048);
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_x, 32));
pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 31);
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_y, 32));
// Initialize pins to output and high
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_set(pio_pins, 15));
pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_set(pio_pindirs, 15));
// Write start token and start the DMA transfer.
pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 0xFFFFFFF0);
dma_channel_start(SDIO_DMA_CH);
// Start state machine
pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, true);
}
static void sdio_compute_next_tx_checksum(sd_card_t *sd_card_p)
{
assert (STATE.blocks_done < STATE.total_blocks && STATE.blocks_checksumed < STATE.total_blocks);
int blockidx = STATE.blocks_checksumed++;
STATE.next_wr_block_checksum = sdio_crc16_4bit_checksum(STATE.data_buf + blockidx * SDIO_WORDS_PER_BLOCK,
SDIO_WORDS_PER_BLOCK);
}
// Start transferring data from memory to SD card
sdio_status_t rp2040_sdio_tx_start(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t num_blocks)
{
// Buffer must be aligned
assert(((uint32_t)buffer & 3) == 0 && num_blocks <= SDIO_MAX_BLOCKS);
STATE.transfer_state = SDIO_TX;
STATE.transfer_start_time = millis();
STATE.data_buf = (uint32_t*)buffer;
STATE.blocks_done = 0;
STATE.total_blocks = num_blocks;
STATE.blocks_checksumed = 0;
STATE.checksum_errors = 0;
// Compute first block checksum
sdio_compute_next_tx_checksum(sd_card_p);
// Start first DMA transfer and PIO
sdio_start_next_block_tx(sd_card_p);
if (STATE.blocks_checksumed < STATE.total_blocks)
{
// Precompute second block checksum
sdio_compute_next_tx_checksum(sd_card_p);
}
return SDIO_OK;
}
static sdio_status_t check_sdio_write_response(uint32_t card_response)
{
// Shift card response until top bit is 0 (the start bit)
// The format of response is poorly documented in SDIO spec but refer to e.g.
// http://my-cool-projects.blogspot.com/2013/02/the-mysterious-sd-card-crc-status.html
uint32_t resp = card_response;
if (!(~resp & 0xFFFF0000)) resp <<= 16;
if (!(~resp & 0xFF000000)) resp <<= 8;
if (!(~resp & 0xF0000000)) resp <<= 4;
if (!(~resp & 0xC0000000)) resp <<= 2;
if (!(~resp & 0x80000000)) resp <<= 1;
uint32_t wr_status = (resp >> 28) & 7;
if (wr_status == 2)
{
return SDIO_OK;
}
else if (wr_status == 5)
{
EMSG_PRINTF("SDIO card reports write CRC error, status %lx\n", card_response);
return SDIO_ERR_WRITE_CRC;
}
else if (wr_status == 6)
{
EMSG_PRINTF("SDIO card reports write failure, status %lx\n", card_response);
return SDIO_ERR_WRITE_FAIL;
}
else
{
EMSG_PRINTF("SDIO card reports unknown write status %lx\n", card_response);
return SDIO_ERR_WRITE_FAIL;
}
}
// When a block finishes, this IRQ handler starts the next one
void sdio_irq_handler(sd_card_t *sd_card_p) {
if (STATE.transfer_state == SDIO_TX)
{
if (!dma_channel_is_busy(SDIO_DMA_CH) && !dma_channel_is_busy(SDIO_DMA_CHB))
{
// Main data transfer is finished now.
// When card is ready, PIO will put card response on RX fifo
STATE.transfer_state = SDIO_TX_WAIT_IDLE;
if (!pio_sm_is_rx_fifo_empty(SDIO_PIO, SDIO_DATA_SM))
{
// Card is already idle
STATE.card_response = pio_sm_get(SDIO_PIO, SDIO_DATA_SM);
}
else
{
// Use DMA to wait for the response
dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CHB);
channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32);
channel_config_set_read_increment(&dmacfg, false);
channel_config_set_write_increment(&dmacfg, false);
channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, false));
dma_channel_configure(SDIO_DMA_CHB, &dmacfg,
&STATE.card_response, &SDIO_PIO->rxf[SDIO_DATA_SM], 1, true);
}
}
}
if (STATE.transfer_state == SDIO_TX_WAIT_IDLE)
{
if (!dma_channel_is_busy(SDIO_DMA_CHB))
{
STATE.wr_status = check_sdio_write_response(STATE.card_response);
if (STATE.wr_status != SDIO_OK)
{
rp2040_sdio_stop(sd_card_p);
return;
}
STATE.blocks_done++;
if (STATE.blocks_done < STATE.total_blocks)
{
sdio_start_next_block_tx(sd_card_p);
STATE.transfer_state = SDIO_TX;
if (STATE.blocks_checksumed < STATE.total_blocks)
{
// Precompute the CRC for next block so that it is ready when
// we want to send it.
sdio_compute_next_tx_checksum(sd_card_p);
}
}
else
{
rp2040_sdio_stop(sd_card_p);
}
}
}
}
// Check if transmission is complete
sdio_status_t rp2040_sdio_tx_poll(sd_card_t *sd_card_p, uint32_t *bytes_complete)
{
#if !PICO_RISCV
if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)
{
// Verify that IRQ handler gets called even if we are in hardfault handler
sdio_irq_handler(sd_card_p);
}
#endif
if (bytes_complete)
{
*bytes_complete = STATE.blocks_done * SDIO_BLOCK_SIZE;
}
if (STATE.transfer_state == SDIO_IDLE)
{
rp2040_sdio_stop(sd_card_p);
return STATE.wr_status;
}
else if (millis() - STATE.transfer_start_time >= sd_timeouts.rp2040_sdio_tx_poll)
{
EMSG_PRINTF("rp2040_sdio_tx_poll() timeout\n");
DBG_PRINTF("rp2040_sdio_tx_poll() timeout, "
"PIO PC: %d"
" RXF: %d"
" TXF: %d"
" DMA CNT: %lu\n",
(int)pio_sm_get_pc(SDIO_PIO, SDIO_DATA_SM) - (int)STATE.pio_data_tx_offset,
(int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_DATA_SM),
(int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_DATA_SM),
dma_hw->ch[SDIO_DMA_CH].al2_transfer_count
);
rp2040_sdio_stop(sd_card_p);
return SDIO_ERR_DATA_TIMEOUT;
}
return SDIO_BUSY;
}
// Force everything to idle state
static sdio_status_t rp2040_sdio_stop(sd_card_t *sd_card_p)
{
dma_channel_abort(SDIO_DMA_CH);
dma_channel_abort(SDIO_DMA_CHB);
switch (sd_card_p->sdio_if_p->DMA_IRQ_num) {
case DMA_IRQ_0:
dma_channel_set_irq0_enabled(SDIO_DMA_CHB, false);
break;
case DMA_IRQ_1:
dma_channel_set_irq1_enabled(SDIO_DMA_CHB, false);
break;
default:
myASSERT(false);
}
pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, false);
pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_DATA_SM, SDIO_D0, 4, false);
STATE.transfer_state = SDIO_IDLE;
return SDIO_OK;
}
bool rp2040_sdio_init(sd_card_t *sd_card_p, float clk_div) {
// Mark resources as being in use, unless it has been done already.
if (!STATE.resources_claimed) {
if (!SDIO_PIO)
SDIO_PIO = pio0; // Default
if (!sd_card_p->sdio_if_p->DMA_IRQ_num)
sd_card_p->sdio_if_p->DMA_IRQ_num = DMA_IRQ_0; // Default
// pio_sm_claim(SDIO_PIO, SDIO_CMD_SM);
// int pio_claim_unused_sm(PIO pio, bool required);
SDIO_CMD_SM = pio_claim_unused_sm(SDIO_PIO, true);
// pio_sm_claim(SDIO_PIO, SDIO_DATA_SM);
SDIO_DATA_SM = pio_claim_unused_sm(SDIO_PIO, true);
// dma_channel_claim(SDIO_DMA_CH);
SDIO_DMA_CH = dma_claim_unused_channel(true);
// dma_channel_claim(SDIO_DMA_CHB);
SDIO_DMA_CHB = dma_claim_unused_channel(true);
/* Set up IRQ handler for when DMA completes. */
dma_irq_add_handler(sd_card_p->sdio_if_p->DMA_IRQ_num,
sd_card_p->sdio_if_p->use_exclusive_DMA_IRQ_handler);
STATE.resources_claimed = true;
}
dma_channel_abort(SDIO_DMA_CH);
dma_channel_abort(SDIO_DMA_CHB);
pio_sm_set_enabled(SDIO_PIO, SDIO_CMD_SM, false);
pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, false);
// Load PIO programs
pio_clear_instruction_memory(SDIO_PIO);
// Command & clock state machine
STATE.pio_cmd_clk_offset = pio_add_program(SDIO_PIO, &sdio_cmd_clk_program);
pio_sm_config cfg = sdio_cmd_clk_program_get_default_config(STATE.pio_cmd_clk_offset);
sm_config_set_out_pins(&cfg, SDIO_CMD, 1);
sm_config_set_in_pins(&cfg, SDIO_CMD);
sm_config_set_set_pins(&cfg, SDIO_CMD, 1);
sm_config_set_jmp_pin(&cfg, SDIO_CMD);
sm_config_set_sideset_pins(&cfg, SDIO_CLK);
sm_config_set_out_shift(&cfg, false, true, 32);
sm_config_set_in_shift(&cfg, false, true, 32);
sm_config_set_clkdiv(&cfg, clk_div);
sm_config_set_mov_status(&cfg, STATUS_TX_LESSTHAN, 2);
pio_sm_init(SDIO_PIO, SDIO_CMD_SM, STATE.pio_cmd_clk_offset, &cfg);
pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_CMD_SM, SDIO_CLK, 1, true);
pio_sm_set_enabled(SDIO_PIO, SDIO_CMD_SM, true);
// Data reception program
STATE.pio_data_rx_offset = pio_add_program(SDIO_PIO, &sdio_data_rx_program);
STATE.pio_cfg_data_rx = sdio_data_rx_program_get_default_config(STATE.pio_data_rx_offset);
sm_config_set_in_pins(&STATE.pio_cfg_data_rx, SDIO_D0);
sm_config_set_in_shift(&STATE.pio_cfg_data_rx, false, true, 32);
sm_config_set_out_shift(&STATE.pio_cfg_data_rx, false, true, 32);
sm_config_set_clkdiv(&STATE.pio_cfg_data_rx, clk_div);
// Data transmission program
STATE.pio_data_tx_offset = pio_add_program(SDIO_PIO, &sdio_data_tx_program);
STATE.pio_cfg_data_tx = sdio_data_tx_program_get_default_config(STATE.pio_data_tx_offset);
sm_config_set_in_pins(&STATE.pio_cfg_data_tx, SDIO_D0);
sm_config_set_set_pins(&STATE.pio_cfg_data_tx, SDIO_D0, 4);
sm_config_set_out_pins(&STATE.pio_cfg_data_tx, SDIO_D0, 4);
sm_config_set_in_shift(&STATE.pio_cfg_data_tx, false, false, 32);
sm_config_set_out_shift(&STATE.pio_cfg_data_tx, false, true, 32);
sm_config_set_clkdiv(&STATE.pio_cfg_data_tx, clk_div);
// Disable SDIO pins input synchronizer.
// This reduces input delay.
// Because the CLK is driven synchronously to CPU clock,
// there should be no metastability problems.
SDIO_PIO->input_sync_bypass |= (1 << SDIO_CLK) | (1 << SDIO_CMD) | (1 << SDIO_D0) | (1 << SDIO_D1) | (1 << SDIO_D2) | (1 << SDIO_D3);
// Redirect GPIOs to PIO
#if PICO_SDK_VERSION_MAJOR < 2
typedef enum gpio_function gpio_function_t;
#endif
gpio_function_t fn;
if (pio1 == SDIO_PIO)
fn = GPIO_FUNC_PIO1;
else
fn = GPIO_FUNC_PIO0;
gpio_set_function(SDIO_CMD, fn);
gpio_set_function(SDIO_CLK, fn);
gpio_set_function(SDIO_D0, fn);
gpio_set_function(SDIO_D1, fn);
gpio_set_function(SDIO_D2, fn);
gpio_set_function(SDIO_D3, fn);
gpio_set_slew_rate(SDIO_CMD, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_CLK, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_D0, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_D1, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_D2, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(SDIO_D3, GPIO_SLEW_RATE_FAST);
if (sd_card_p->sdio_if_p->set_drive_strength) {
gpio_set_drive_strength(SDIO_CMD, sd_card_p->sdio_if_p->CMD_gpio_drive_strength);
gpio_set_drive_strength(SDIO_CLK, sd_card_p->sdio_if_p->CLK_gpio_drive_strength);
gpio_set_drive_strength(SDIO_D0, sd_card_p->sdio_if_p->D0_gpio_drive_strength);
gpio_set_drive_strength(SDIO_D1, sd_card_p->sdio_if_p->D1_gpio_drive_strength);
gpio_set_drive_strength(SDIO_D2, sd_card_p->sdio_if_p->D2_gpio_drive_strength);
gpio_set_drive_strength(SDIO_D3, sd_card_p->sdio_if_p->D3_gpio_drive_strength);
}
return true;
}

120
lib/sd_driver/SDIO/rp2040_sdio.h Executable file
View File

@ -0,0 +1,120 @@
// SD card access using SDIO for RP2040 platform.
// This module contains the low-level SDIO bus implementation using
// the PIO peripheral. The high-level commands are in sd_card_sdio.cpp.
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "sd_card.h"
//FIXME: why?
typedef struct sd_card_t sd_card_t;
typedef
enum sdio_status_t {
SDIO_OK = 0,
SDIO_BUSY = 1,
SDIO_ERR_RESPONSE_TIMEOUT = 2, // Timed out waiting for response from card
SDIO_ERR_RESPONSE_CRC = 3, // Response CRC is wrong
SDIO_ERR_RESPONSE_CODE = 4, // Response command code does not match what was sent
SDIO_ERR_DATA_TIMEOUT = 5, // Timed out waiting for data block
SDIO_ERR_DATA_CRC = 6, // CRC for data packet is wrong
SDIO_ERR_WRITE_CRC = 7, // Card reports bad CRC for write
SDIO_ERR_WRITE_FAIL = 8, // Card reports write failure
} sdio_status_t;
#define SDIO_BLOCK_SIZE 512
#define SDIO_WORDS_PER_BLOCK (SDIO_BLOCK_SIZE / 4) // 128
// Maximum number of 512 byte blocks to transfer in one request
#define SDIO_MAX_BLOCKS 256
typedef enum sdio_transfer_state_t { SDIO_IDLE, SDIO_RX, SDIO_TX, SDIO_TX_WAIT_IDLE} sdio_transfer_state_t;
typedef struct sd_sdio_if_state_t {
bool resources_claimed;
uint32_t ocr; // Operating condition register from card
uint32_t rca; // Relative card address
int error_line;
sdio_status_t error;
uint32_t dma_buf[128];
int SDIO_DMA_CH;
int SDIO_DMA_CHB;
int SDIO_CMD_SM;
int SDIO_DATA_SM;
uint32_t pio_cmd_clk_offset;
uint32_t pio_data_rx_offset;
pio_sm_config pio_cfg_data_rx;
uint32_t pio_data_tx_offset;
pio_sm_config pio_cfg_data_tx;
sdio_transfer_state_t transfer_state;
uint32_t transfer_start_time;
uint32_t *data_buf;
uint32_t blocks_done; // Number of blocks transferred so far
uint32_t total_blocks; // Total number of blocks to transfer
uint32_t blocks_checksumed; // Number of blocks that have had CRC calculated
uint32_t checksum_errors; // Number of checksum errors detected
// Variables for block writes
uint64_t next_wr_block_checksum;
uint32_t end_token_buf[3]; // CRC and end token for write block
sdio_status_t wr_status;
uint32_t card_response;
// Variables for extended block writes
bool ongoing_wr_mlt_blk;
uint32_t wr_mlt_blk_cnt_sector;
// Variables for block reads
// This is used to perform DMA into data buffers and checksum buffers separately.
struct {
void * write_addr;
uint32_t transfer_count;
} dma_blocks[SDIO_MAX_BLOCKS * 2];
struct {
uint32_t top;
uint32_t bottom;
} received_checksums[SDIO_MAX_BLOCKS];
} sd_sdio_if_state_t;
// Execute a command that has 48-bit reply (response types R1, R6, R7)
// If response is NULL, does not wait for reply.
sdio_status_t rp2040_sdio_command_R1(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response);
// Execute a command that has 136-bit reply (response type R2)
// Response buffer should have space for 16 bytes (the 128 bit payload)
sdio_status_t rp2040_sdio_command_R2(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t *response);
// Execute a command that has 48-bit reply but without CRC (response R3)
sdio_status_t rp2040_sdio_command_R3(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response);
// Start transferring data from SD card to memory buffer
sdio_status_t rp2040_sdio_rx_start(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t num_blocks, size_t block_size);
// Check if reception is complete
// Returns SDIO_BUSY while transferring, SDIO_OK when done and error on failure.
sdio_status_t rp2040_sdio_rx_poll(sd_card_t *sd_card_p, size_t block_size_words);
// Start transferring data from memory to SD card
sdio_status_t rp2040_sdio_tx_start(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t num_blocks);
// Check if transmission is complete
sdio_status_t rp2040_sdio_tx_poll(sd_card_t *sd_card_p, uint32_t *bytes_complete /* = nullptr */);
// (Re)initialize the SDIO interface
bool rp2040_sdio_init(sd_card_t *sd_card_p, float clk_div);
void __not_in_flash_func(sdio_irq_handler)(sd_card_t *sd_card_p);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,157 @@
; RP2040 PIO program for implementing SD card access in SDIO mode
; Run "pioasm rp2040_sdio.pio rp2040_sdio.pio.h" to regenerate the C header from this.
; The RP2040 official work-in-progress code at
; https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_sd_card
; may be useful reference, but this is independent implementation.
;
; For official SDIO specifications, refer to:
; https://www.sdcard.org/downloads/pls/
; "SDIO Physical Layer Simplified Specification Version 8.00"
; Clock settings
; For 3.3V communication the available speeds are:
; - Default speed: max. 25 MHz clock
; - High speed: max. 50 MHz clock
;
; From the default RP2040 clock speed of 125 MHz, the closest dividers
; are 3 for 41.7 MHz and 5 for 25 MHz. The CPU can apply further divider
; through state machine registers for the initial handshake.
;
; Because data is written on the falling edge and read on the rising
; edge, it is preferrable to have a long 0 state and short 1 state.
;.define CLKDIV 3
;.define CLKDIV 5
;.define D0 ((CLKDIV + 1) / 2 - 1)
;.define D1 (CLKDIV/2 - 1)
.define D0 1
.define D1 1
.define PUBLIC CLKDIV D0 + 1 + D1 + 1
; .define PUBLIC SDIO_CLK_GPIO 17
; This is relative to D0 GPIO number.
; The pin is selected by adding Index to the
; PINCTRL_IN_BASE configuration, modulo 32.
; This is used as a WAIT index, and must be between 4 and 31.
; (Offsets 0-3 are D0, D1, D2, and D3.)
.define PUBLIC SDIO_CLK_PIN_D0_OFFSET 30 ; (-2 in mod32 arithmetic)
; State machine 0 is used to:
; - generate continuous clock on SDIO_CLK
; - send CMD packets
; - receive response packets
;
; Pin mapping for this state machine:
; - Sideset : CLK
; - IN/OUT/SET : CMD
; - JMP_PIN : CMD
;
; The commands to send are put on TX fifo and must have two words:
; Word 0 bits 31-24: Number of bits in command minus one (usually 47)
; Word 0 bits 23-00: First 24 bits of the command packet, shifted out MSB first
; Word 1 bits 31-08: Last 24 bits of the command packet, shifted out MSB first
; Word 1 bits 07-00: Number of bits in response minus one (usually 47), or 0 if no response
;
; The response is put on RX fifo, starting with the MSB.
; Partial last word will be padded with zero bits at the top.
;
; The state machine EXECCTRL should be set so that STATUS indicates TX FIFO < 2
; and that AUTOPULL and AUTOPUSH are enabled.
.program sdio_cmd_clk
.side_set 1
mov OSR, NULL side 1 [D1] ; Make sure OSR is full of zeros to prevent autopull
wait_cmd:
mov Y, !STATUS side 0 [D0] ; Check if TX FIFO has data
jmp !Y wait_cmd side 1 [D1]
load_cmd:
out NULL, 32 side 0 [D0] ; Load first word (trigger autopull)
out X, 8 side 1 [D1] ; Number of bits to send
set pins, 1 side 0 [D0] ; Initial state of CMD is high
set pindirs, 1 side 1 [D1] ; Set SDIO_CMD as output
send_cmd:
out pins, 1 side 0 [D0] ; Write output on falling edge of CLK
jmp X-- send_cmd side 1 [D1]
prep_resp:
set pindirs, 0 side 0 [D0] ; Set SDIO_CMD as input
out X, 8 side 1 [D1] ; Get number of bits in response
nop side 0 [D0] ; For clock alignment
jmp !X resp_done side 1 [D1] ; Check if we expect a response
wait_resp:
nop side 0 [D0]
jmp PIN wait_resp side 1 [D1] ; Loop until SDIO_CMD = 0
; Note: input bits are read at the same time as we write CLK=0.
; Because the host controls the clock, the read happens before
; the card sees the falling clock edge. This gives maximum time
; for the data bit to settle.
read_resp:
in PINS, 1 side 0 [D0] ; Read input data bit
jmp X-- read_resp side 1 [D1] ; Loop to receive all data bits
resp_done:
push side 0 [D0] ; Push the remaining part of response
; State machine 1 is used to send and receive data blocks.
; Pin mapping for this state machine:
; - IN / OUT: SDIO_D0-D3
; - GPIO defined at beginning of this file: SDIO_CLK
; Data reception program
; This program will wait for initial start of block token and then
; receive a data block. The application must set number of nibbles
; to receive minus 1 to Y register before running this program.
.program sdio_data_rx
wait_start:
mov X, Y ; Reinitialize number of nibbles to receive
wait 0 pin 0 ; Wait for zero state on D0
wait 1 pin SDIO_CLK_PIN_D0_OFFSET [CLKDIV-1] ; Wait for rising edge and then whole clock cycle
rx_data:
in PINS, 4 [CLKDIV-2] ; Read nibble
jmp X--, rx_data
; Data transmission program
;
; Before running this program, pindirs should be set as output
; and register X should be initialized with the number of nibbles
; to send minus 1 (typically 8 + 1024 + 16 + 1 - 1 = 1048)
; and register Y with the number of response bits minus 1 (typically 31).
;
; Words written to TX FIFO must be:
; - Word 0: start token 0xFFFFFFF0
; - Word 1-128: transmitted data (512 bytes)
; - Word 129-130: CRC checksum
; - Word 131: end token 0xFFFFFFFF
;
; After the card reports idle status, RX FIFO will get a word that
; contains the D0 line response from card.
.program sdio_data_tx
wait 0 pin SDIO_CLK_PIN_D0_OFFSET
wait 1 pin SDIO_CLK_PIN_D0_OFFSET [CLKDIV + D1 - 1]; Synchronize so that write occurs on falling edge
tx_loop:
out PINS, 4 [D0] ; Write nibble and wait for whole clock cycle
jmp X-- tx_loop [D1]
set pindirs, 0x00 [D0] ; Set data bus as input
.wrap_target
response_loop:
in PINS, 1 [D1] ; Read D0 on rising edge
jmp Y--, response_loop [D0]
wait_idle:
wait 1 pin 0 [D1] ; Wait for card to indicate idle condition
push [D0] ; Push the response token
.wrap

View File

@ -0,0 +1,664 @@
// Driver for accessing SD card in SDIO mode on RP2040.
#include "ZuluSCSI_platform.h"
#include <stdint.h>
#include <string.h>
//
// Hardware
//
#include <hardware/dma.h>
#include <hardware/gpio.h>
#include <hardware/clocks.h>
#include <hardware/pio.h>
//
// Platform
//
#include "pico/stdlib.h"
//
// Project
//
#include "diskio.h"
#include "my_debug.h"
#include "delays.h"
#include "rp2040_sdio.h"
#include "rp2040_sdio.pio.h" // build\build\rp2040_sdio.pio.h
#include "sd_card_constants.h"
#include "sd_card.h"
#include "sd_timeouts.h"
#include "SdioCard.h"
#include "util.h"
#define STATE sd_card_p->sdio_if_p->state
static char const *errstr(sdio_status_t error) {
switch (error) {
case SDIO_OK:
return "SDIO: OK";
case SDIO_BUSY:
return "SDIO: busy";
case SDIO_ERR_RESPONSE_TIMEOUT:
return "SDIO: Timed out waiting for response from card";
case SDIO_ERR_RESPONSE_CRC:
return "SDIO: Response CRC is wrong";
case SDIO_ERR_RESPONSE_CODE:
return "SDIO: Response command code does not match what was sent";
case SDIO_ERR_DATA_TIMEOUT:
return "SDIO: Timed out waiting for data block";
case SDIO_ERR_DATA_CRC:
return "SDIO: CRC for data packet is wrong";
case SDIO_ERR_WRITE_CRC:
return "SDIO: Card reports bad CRC for write";
case SDIO_ERR_WRITE_FAIL:
return "SDIO: Card reports write failure";
}
return "Unknown error";
}
//FIXME
#define azdbg(arg1, ...) {\
DBG_PRINTF("%s,%d: %s\n", __func__, __LINE__, arg1); \
}
#define TRACE_PRINTF(fmt, args...)
//#define TRACE_PRINTF DBG_PRINTF
#define checkReturnOk(call) ((STATE.error = (call)) == SDIO_OK ? true : logSDError(sd_card_p, __LINE__))
static bool logSDError(sd_card_t *sd_card_p, int line)
{
STATE.error_line = line;
EMSG_PRINTF("%s at line %d; error code %d\n",
errstr(STATE.error), line, (int)STATE.error);
return false;
}
/*
CLKDIV is from sd_driver\SDIO\rp2040_sdio.pio
baud = clk_sys / (CLKDIV * clk_div)
baud * CLKDIV * clk_div = clk_sys;
clk_div = clk_sys / (CLKDIV * baud)
*/
static float calculate_clk_div(uint baud) {
float div = (float)clock_get_hz(clk_sys) / (CLKDIV * baud);
/* Baud rate cannot exceed clk_sys frequency divided by CLKDIV! */
DBG_PRINTF("clk_div = %f\n", div);
myASSERT(div >= 1 && div <= 65536);
return div;
}
bool sd_sdio_begin(sd_card_t *sd_card_p)
{
uint32_t reply;
sdio_status_t status;
// Initialize at 400 kHz clock speed
if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(400 * 1000)))
return false;
// Establish initial connection with the card
for (int retries = 0; retries < 5; retries++)
{
delay_ms(1);
reply = 0;
rp2040_sdio_command_R1(sd_card_p, CMD0_GO_IDLE_STATE, 0, NULL); // GO_IDLE_STATE
status = rp2040_sdio_command_R1(sd_card_p, CMD8_SEND_IF_COND, 0x1AA, &reply); // SEND_IF_COND
if (status == SDIO_OK && reply == 0x1AA)
{
break;
}
}
if (reply != 0x1AA || status != SDIO_OK)
{
// azdbg("SDIO not responding to CMD8 SEND_IF_COND, status ", (int)status, " reply ", reply);
EMSG_PRINTF("%s,%d SDIO not responding to CMD8 SEND_IF_COND, status 0x%x reply 0x%lx\n",
__func__, __LINE__, status, reply);
return false;
}
// Send ACMD41 to begin card initialization and wait for it to complete
uint32_t start = millis();
do {
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, 0, &reply)) || // APP_CMD
!checkReturnOk(rp2040_sdio_command_R3(sd_card_p, ACMD41_SD_SEND_OP_COND, 0xD0040000, &STATE.ocr))) // 3.0V voltage
// !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD41, 0xC0100000, &STATE.ocr)))
{
return false;
}
if ((uint32_t)(millis() - start) > sd_timeouts.sd_sdio_begin)
{
EMSG_PRINTF("SDIO card initialization timeout\n");
return false;
}
} while (!(STATE.ocr & (1 << 31)));
// Get CID
// CMD2 is valid only in "ready" state;
// Transitions to "ident" state
// Note: CMD10 is valid only in "stby" state
if (!checkReturnOk(rp2040_sdio_command_R2(sd_card_p, CMD2_ALL_SEND_CID, 0, (uint8_t *)&sd_card_p->state.CID)))
{
azdbg("SDIO failed to read CID");
return false;
}
// Get relative card address
// Valid in "ident" or "stby" state; transitions to "stby"
// Transitions from "card-identification-mode" to "data-transfer-mode"
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD3_SEND_RELATIVE_ADDR, 0, &STATE.rca)))
{
azdbg("SDIO failed to get RCA");
return false;
}
// Get CSD
// Valid in "stby" state; stays in "stby" state
if (!checkReturnOk(rp2040_sdio_command_R2(sd_card_p, CMD9_SEND_CSD, STATE.rca, sd_card_p->state.CSD)))
{
azdbg("SDIO failed to read CSD");
return false;
}
sd_card_p->state.sectors = CSD_sectors(sd_card_p->state.CSD);
// Select card
// Valid in "stby" state;
// If card is addressed, transitions to "tran" state
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD7_SELECT_CARD, STATE.rca, &reply)))
{
azdbg("SDIO failed to select card");
return false;
}
/* At power up CD/DAT3 has a 50KOhm pull up enabled in the card.
This resistor serves two functions Card detection and Mode Selection.
For Mode Selection, the host can drive the line high or let it be pulled high to select SD mode.
If the host wants to select SPI mode it should drive the line low.
For Card detection, the host detects that the line is pulled high.
This pull-up should be disconnected by the user, during regular data transfer,
with SET_CLR_CARD_DETECT (ACMD42) command. */
// Disconnect the 50 KOhm pull-up resistor on CD/DAT3
// Valid in "tran" state; stays in "tran" state
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) ||
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD42_SET_CLR_CARD_DETECT, 0, &reply)))
{
azdbg("SDIO failed to disconnect pull-up");
return false;
}
// Set 4-bit bus mode
// Valid in "tran" state; stays in "tran" state
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) ||
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD6_SET_BUS_WIDTH, 2, &reply)))
{
azdbg("SDIO failed to set bus width");
return false;
}
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply))) // SET_BLOCKLEN
{
EMSG_PRINTF("%s,%d SDIO failed to set BLOCKLEN\n", __func__, __LINE__);
return false;
}
// Increase to high clock rate
if (!sd_card_p->sdio_if_p->baud_rate)
sd_card_p->sdio_if_p->baud_rate = clock_get_hz(clk_sys) / 12; // Default
if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(sd_card_p->sdio_if_p->baud_rate)))
return false;
return true;
}
uint8_t sd_sdio_errorCode(sd_card_t *sd_card_p) // const
{
return STATE.error;
}
uint32_t sd_sdio_errorData() // const
{
return 0;
}
uint32_t sd_sdio_errorLine(sd_card_t *sd_card_p) // const
{
return STATE.error_line;
}
bool sd_sdio_isBusy(sd_card_t *sd_card_p)
{
// return (sio_hw->gpio_in & (1 << SDIO_D0)) == 0;
return (sio_hw->gpio_in & (1 << sd_card_p->sdio_if_p->D0_gpio)) == 0;
}
bool sd_sdio_readOCR(sd_card_t *sd_card_p, uint32_t* ocr)
{
// SDIO mode does not have CMD58, but main program uses this to
// poll for card presence. Return status register instead.
return checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, ocr));
}
uint32_t sd_sdio_status(sd_card_t *sd_card_p)
{
uint32_t reply;
if (checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, &reply)))
return reply;
else
return 0;
}
bool sd_sdio_stopTransmission(sd_card_t *sd_card_p, bool blocking)
{
STATE.ongoing_wr_mlt_blk = false;
uint32_t reply;
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD12_STOP_TRANSMISSION, 0, &reply)))
{
return false;
}
if (!blocking)
{
return true;
}
else
{
uint32_t start = millis();
while (millis() - start < 200 && sd_sdio_isBusy(sd_card_p));
if (sd_sdio_isBusy(sd_card_p))
{
EMSG_PRINTF("sd_sdio_stopTransmission() timeout\n");
return false;
}
else
{
return true;
}
}
}
uint8_t sd_sdio_type(sd_card_t *sd_card_p) // const
{
if (STATE.ocr & (1 << 30))
return SDCARD_V2HC;
else
return SDCARD_V2;
}
/* Writing and reading */
bool sd_sdio_writeSector(sd_card_t *sd_card_p, uint32_t sector, const uint8_t* src)
{
if (STATE.ongoing_wr_mlt_blk)
// Stop any ongoing write transmission
if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
if (((uint32_t)src & 3) != 0) {
// Buffer is not aligned, need to memcpy() the data to a temporary buffer.
memcpy(STATE.dma_buf, src, sizeof(STATE.dma_buf));
src = (uint8_t*)STATE.dma_buf;
}
uint32_t reply;
if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD24_WRITE_BLOCK, sector, &reply)) || // WRITE_BLOCK
!checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, 1))) // Start transmission
{
return false;
}
do {
uint32_t bytes_done;
STATE.error = rp2040_sdio_tx_poll(sd_card_p, &bytes_done);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK)
{
EMSG_PRINTF("sd_sdio_writeSector(%lu) failed: %s (%d)\n",
sector, errstr(STATE.error), (int)STATE.error);
}
return STATE.error == SDIO_OK;
}
bool sd_sdio_writeSectors(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src, size_t n) {
if (((uint32_t)src & 3) != 0) {
// Unaligned write, execute sector-by-sector
for (size_t i = 0; i < n; i++) {
if (!sd_sdio_writeSector(sd_card_p, sector + i, src + 512 * i)) {
return false;
}
}
return true;
}
if (STATE.ongoing_wr_mlt_blk && sector == STATE.wr_mlt_blk_cnt_sector) {
/* Continue a multiblock write */
if (!checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, n))) // Start transmission
return false;
} else {
// Stop any previous transmission
if (STATE.ongoing_wr_mlt_blk) {
if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
}
uint32_t reply;
if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD25_WRITE_MULTIPLE_BLOCK, sector, &reply)) ||
!checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, n))) // Start transmission
{
return false;
}
}
do {
uint32_t bytes_done;
STATE.error = rp2040_sdio_tx_poll(sd_card_p, &bytes_done);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK) {
EMSG_PRINTF("sd_sdio_writeSectors(,%lu,,%zu) failed: %s (%d)\n", sector, n, errstr(STATE.error), (int)STATE.error);
sd_sdio_stopTransmission(sd_card_p, true);
return false;
} else {
STATE.wr_mlt_blk_cnt_sector = sector + n;
STATE.ongoing_wr_mlt_blk = true;
return true;
}
/* Optimization:
To optimize large contiguous writes,
postpone stopping transmission until it is
clear that the next operation is not a continuation.
Any transactions other than a `sd_sdio_writeSectors`
continuation must stop any ongoing transmission
before proceding.
*/
}
bool sd_sdio_readSector(sd_card_t *sd_card_p, uint32_t sector, uint8_t* dst)
{
if (STATE.ongoing_wr_mlt_blk)
// Stop any ongoing transmission
if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
uint8_t *real_dst = dst;
if (((uint32_t)dst & 3) != 0)
{
// Buffer is not aligned, need to memcpy() the data from a temporary buffer.
dst = (uint8_t*)STATE.dma_buf;
}
uint32_t reply;
if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
!checkReturnOk(rp2040_sdio_rx_start(sd_card_p, dst, 1, SDIO_BLOCK_SIZE)) || // Prepare for reception
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD17_READ_SINGLE_BLOCK, sector, &reply))) // READ_SINGLE_BLOCK
{
return false;
}
do {
STATE.error = rp2040_sdio_rx_poll(sd_card_p, SDIO_WORDS_PER_BLOCK);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK)
{
EMSG_PRINTF("sd_sdio_readSector(,%lu,) failed: %s (%d)\n",
sector, errstr(STATE.error), (int)STATE.error);
}
if (dst != real_dst)
{
memcpy(real_dst, STATE.dma_buf, sizeof(STATE.dma_buf));
}
return STATE.error == SDIO_OK;
}
bool sd_sdio_readSectors(sd_card_t *sd_card_p, uint32_t sector, uint8_t* dst, size_t n)
{
if (STATE.ongoing_wr_mlt_blk)
// Stop any ongoing transmission
if (!sd_sdio_stopTransmission(sd_card_p, true)) return false;
if (((uint32_t)dst & 3) != 0 || sector + n >= sd_card_p->state.sectors)
{
// Unaligned read or end-of-drive read, execute sector-by-sector
for (size_t i = 0; i < n; i++)
{
if (!sd_sdio_readSector(sd_card_p, sector + i, dst + 512 * i))
{
return false;
}
}
return true;
}
uint32_t reply;
if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */
!checkReturnOk(rp2040_sdio_rx_start(sd_card_p, dst, n, SDIO_BLOCK_SIZE)) || // Prepare for reception
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD18_READ_MULTIPLE_BLOCK, sector, &reply))) // READ_MULTIPLE_BLOCK
{
return false;
}
do {
STATE.error = rp2040_sdio_rx_poll(sd_card_p, SDIO_WORDS_PER_BLOCK);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK)
{
EMSG_PRINTF("sd_sdio_readSectors(%ld,...,%d) failed: %s (%d)\n",
sector, n, errstr(STATE.error), STATE.error);
sd_sdio_stopTransmission(sd_card_p, true);
return false;
}
else
{
return sd_sdio_stopTransmission(sd_card_p, true);
}
}
// Get 512 bit (64 byte) SD Status
bool rp2040_sdio_get_sd_status(sd_card_t *sd_card_p, uint8_t response[64]) {
uint32_t reply;
if (!checkReturnOk(rp2040_sdio_rx_start(sd_card_p, response, 1, 64)) || // Prepare for reception
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) || // APP_CMD
!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD13_SD_STATUS, 0, &reply))) // SD Status
{
EMSG_PRINTF("ACMD13 failed\n");
return false;
}
// Read 512 bit block on DAT bus (not CMD)
do {
STATE.error = rp2040_sdio_rx_poll(sd_card_p, 64 / 4);
} while (STATE.error == SDIO_BUSY);
if (STATE.error != SDIO_OK)
{
EMSG_PRINTF("ACMD13 failed: %s (%d)\n", errstr(STATE.error), (int)STATE.error);
}
return STATE.error == SDIO_OK;
}
static bool sd_sdio_test_com(sd_card_t *sd_card_p) {
bool success = false;
if (!(sd_card_p->state.m_Status & STA_NOINIT)) {
// SD card is currently initialized
// Get status
uint32_t reply = 0;
sdio_status_t status = rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, &reply);
// Only care that communication succeeded
success = (status == SDIO_OK);
if (!success) {
// Card no longer sensed - ensure card is initialized once re-attached
sd_card_p->state.m_Status |= STA_NOINIT;
}
} else {
// Do a "light" version of init, just enough to test com
// Initialize at 400 kHz clock speed
if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(400 * 1000)))
return false;
// Establish initial connection with the card
rp2040_sdio_command_R1(sd_card_p, CMD0_GO_IDLE_STATE, 0, NULL); // GO_IDLE_STATE
uint32_t reply = 0;
sdio_status_t status = rp2040_sdio_command_R1(sd_card_p, CMD8_SEND_IF_COND, 0x1AA, &reply); // SEND_IF_COND
success = (reply == 0x1AA && status == SDIO_OK);
}
return success;
}
#if PICO_SDK_VERSION_MAJOR < 2
typedef enum gpio_function gpio_function_t;
#endif
// Helper function to configure whole GPIO in one line
static void gpio_conf(uint gpio, gpio_function_t fn, bool pullup, bool pulldown, bool output, bool initial_state)
{
gpio_put(gpio, initial_state);
gpio_set_dir(gpio, output);
gpio_set_pulls(gpio, pullup, pulldown);
gpio_set_function(gpio, fn);
// See rp2040_sdio_init
}
static DSTATUS sd_sdio_init(sd_card_t *sd_card_p) {
sd_lock(sd_card_p);
// Make sure there's a card in the socket before proceeding
sd_card_detect(sd_card_p);
if (sd_card_p->state.m_Status & STA_NODISK) {
sd_unlock(sd_card_p);
return sd_card_p->state.m_Status;
}
// Make sure we're not already initialized before proceeding
if (!(sd_card_p->state.m_Status & STA_NOINIT)) {
sd_unlock(sd_card_p);
return sd_card_p->state.m_Status;
}
// Initialize the member variables
sd_card_p->state.card_type = SDCARD_NONE;
// pin function pup pdown out state
gpio_conf(sd_card_p->sdio_if_p->CLK_gpio, GPIO_FUNC_PIO1, true, false, true, true);
gpio_conf(sd_card_p->sdio_if_p->CMD_gpio, GPIO_FUNC_PIO1, true, false, true, true);
gpio_conf(sd_card_p->sdio_if_p->D0_gpio, GPIO_FUNC_PIO1, true, false, false, true);
gpio_conf(sd_card_p->sdio_if_p->D1_gpio, GPIO_FUNC_PIO1, true, false, false, true);
gpio_conf(sd_card_p->sdio_if_p->D2_gpio, GPIO_FUNC_PIO1, true, false, false, true);
gpio_conf(sd_card_p->sdio_if_p->D3_gpio, GPIO_FUNC_PIO1, true, false, false, true);
bool ok = sd_sdio_begin(sd_card_p);
if (ok) {
// The card is now initialized
sd_card_p->state.m_Status &= ~STA_NOINIT;
}
sd_unlock(sd_card_p);
return sd_card_p->state.m_Status;
}
static void sd_sdio_deinit(sd_card_t *sd_card_p) {
sd_lock(sd_card_p);
sd_card_p->state.m_Status |= STA_NOINIT;
sd_card_p->state.card_type = SDCARD_NONE;
// pin function pup pdown out state
gpio_conf(sd_card_p->sdio_if_p->CLK_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->CMD_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->D0_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->D1_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->D2_gpio, GPIO_FUNC_NULL, false, false, false, false);
gpio_conf(sd_card_p->sdio_if_p->D3_gpio, GPIO_FUNC_NULL, false, false, false, false);
//TODO: free other resources: PIO, SMs, etc.
sd_unlock(sd_card_p);
}
uint32_t sd_sdio_sectorCount(sd_card_t *sd_card_p) {
myASSERT(!(sd_card_p->state.m_Status & STA_NOINIT));
return CSD_sectors(sd_card_p->state.CSD);
}
static block_dev_err_t sd_sdio_write_blocks(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t ulSectorNumber,
uint32_t blockCnt) {
TRACE_PRINTF("%s(,,,%zu)\n", __func__, blockCnt);
bool ok = true;
sd_lock(sd_card_p);
if (1 == blockCnt)
ok = sd_sdio_writeSector(sd_card_p, ulSectorNumber, buffer);
else
ok = sd_sdio_writeSectors(sd_card_p, ulSectorNumber, buffer, blockCnt);
sd_unlock(sd_card_p);
if (ok)
return SD_BLOCK_DEVICE_ERROR_NONE;
else
return SD_BLOCK_DEVICE_ERROR_WRITE;
}
static block_dev_err_t sd_sdio_read_blocks(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t ulSectorNumber,
uint32_t ulSectorCount) {
bool ok = true;
sd_lock(sd_card_p);
if (1 == ulSectorCount)
ok = sd_sdio_readSector(sd_card_p, ulSectorNumber, buffer);
else
ok = sd_sdio_readSectors(sd_card_p, ulSectorNumber, buffer, ulSectorCount);
sd_unlock(sd_card_p);
if (ok)
return SD_BLOCK_DEVICE_ERROR_NONE;
else
return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
}
static block_dev_err_t sd_sync(sd_card_t *sd_card_p) {
sd_lock(sd_card_p);
block_dev_err_t err = SD_BLOCK_DEVICE_ERROR_NONE;
if (STATE.ongoing_wr_mlt_blk)
if (!sd_sdio_stopTransmission(sd_card_p, true))
err = SD_BLOCK_DEVICE_ERROR_NO_RESPONSE;
sd_unlock(sd_card_p);
return err;
}
void sd_sdio_ctor(sd_card_t *sd_card_p) {
myASSERT(sd_card_p->sdio_if_p); // Must have an interface object
/*
Pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio.
The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio.
*/
myASSERT(!sd_card_p->sdio_if_p->CLK_gpio);
myASSERT(!sd_card_p->sdio_if_p->D1_gpio);
myASSERT(!sd_card_p->sdio_if_p->D2_gpio);
myASSERT(!sd_card_p->sdio_if_p->D3_gpio);
sd_card_p->sdio_if_p->CLK_gpio = (sd_card_p->sdio_if_p->D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32;
sd_card_p->sdio_if_p->D1_gpio = sd_card_p->sdio_if_p->D0_gpio + 1;
sd_card_p->sdio_if_p->D2_gpio = sd_card_p->sdio_if_p->D0_gpio + 2;
sd_card_p->sdio_if_p->D3_gpio = sd_card_p->sdio_if_p->D0_gpio + 3;
sd_card_p->state.m_Status = STA_NOINIT;
sd_card_p->init = sd_sdio_init;
sd_card_p->deinit = sd_sdio_deinit;
sd_card_p->write_blocks = sd_sdio_write_blocks;
sd_card_p->read_blocks = sd_sdio_read_blocks;
sd_card_p->sync = sd_sync;
sd_card_p->get_num_sectors = sd_sdio_sectorCount;
sd_card_p->sd_test_com = sd_sdio_test_com;
}

371
lib/sd_driver/SPI/my_spi.c Executable file
View File

@ -0,0 +1,371 @@
/* 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 <stdint.h>
#include <stdio.h>
//
#include "hardware/clocks.h"
#include "hardware/spi.h"
#include "hardware/structs/clocks.h"
//#include "hardware/structs/dma_debug.h"
#include "pico.h"
#include "pico/mutex.h"
#include "pico/platform.h"
#include "pico/stdlib.h"
//
#include "delays.h"
#include "hw_config.h"
#include "my_debug.h"
#include "util.h"
//
#include "my_spi.h"
#ifndef USE_DBG_PRINTF
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
static bool chk_spi(spi_t *spi_p) {
spi_inst_t *hw_spi = spi_p->hw_inst;
bool ok = true;
if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_BSY_BITS) {
DBG_PRINTF("SPI is busy\n");
ok = false;
}
if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_RFF_BITS) {
DBG_PRINTF("SPI Receive FIFO full\n");
ok = false;
}
if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_RNE_BITS) {
DBG_PRINTF("SPI Receive FIFO not empty\n");
ok = false;
}
if (!(spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_TNF_BITS)) {
DBG_PRINTF("SPI Transmit FIFO is full\n");
ok = false;
}
if (!(spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_TFE_BITS)) {
DBG_PRINTF("SPI Transmit FIFO is not empty\n");
ok = false;
}
return ok;
}
static bool chk_dma(uint chn) {
dma_channel_hw_t *channel = &dma_hw->ch[chn];
bool ok = true;
uint32_t ctrl = channel->ctrl_trig;
if (ctrl & DMA_CH0_CTRL_TRIG_AHB_ERROR_BITS) {
DBG_PRINTF("\tDMA bus error\n");
ok = false;
}
if (ctrl & DMA_CH0_CTRL_TRIG_READ_ERROR_BITS) {
DBG_PRINTF("\tDMA read error\n");
ok = false;
}
if (ctrl & DMA_CH0_CTRL_TRIG_WRITE_ERROR_BITS) {
DBG_PRINTF("\tDMA write error\n");
ok = false;
}
if (ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS) {
DBG_PRINTF("\tDMA is busy\n");
ok = false;
}
//if (!ok) {
// dma_debug_channel_hw_t *dbg_ch_p = &dma_debug_hw->ch[chn];
// DBG_PRINTF("\tTRANSFER_COUNT: %lu\n", channel->transfer_count);
// DBG_PRINTF("\tTRANS_COUNT reload value (DBG_TCR): %lu\n", dbg_ch_p->dbg_tcr);
// DBG_PRINTF("\tDREQ counter: %lu\n", dbg_ch_p->dbg_ctdreq);
//}
return ok;
}
static bool chk_dmas(spi_t *spi_p) {
bool tx_ok = chk_dma(spi_p->tx_dma);
if (!tx_ok) DBG_PRINTF("TX DMA error\n");
bool rx_ok = chk_dma(spi_p->rx_dma);
if (!rx_ok) DBG_PRINTF("RX DMA error\n");
return tx_ok && rx_ok;
}
/**
* @brief Start a SPI transfer by configuring and starting the DMA channels.
*
* @param spi_p Pointer to the SPI object.
* @param tx Pointer to the transmit buffer. If NULL, data will be filled with SPI_FILL_CHAR.
* @param rx Pointer to the receive buffer. If NULL, data will be ignored.
* @param length Length of the transfer.
*/
void spi_transfer_start(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length) {
myASSERT(spi_p);
myASSERT(tx || rx);
// tx write increment is already false
if (tx) {
channel_config_set_read_increment(&spi_p->tx_dma_cfg, true);
} else {
static const uint8_t dummy __attribute__((section(".time_critical."))) = SPI_FILL_CHAR;
tx = &dummy;
channel_config_set_read_increment(&spi_p->tx_dma_cfg, false);
}
// rx read increment is already false
if (rx) {
channel_config_set_write_increment(&spi_p->rx_dma_cfg, true);
} else {
static uint8_t dummy = 0xA5;
rx = &dummy;
channel_config_set_write_increment(&spi_p->rx_dma_cfg, false);
}
dma_channel_configure(spi_p->tx_dma, &spi_p->tx_dma_cfg,
&spi_get_hw(spi_p->hw_inst)->dr, // write address
tx, // read address
length, // element count (each element is of
// size transfer_data_size)
false); // start
dma_channel_configure(spi_p->rx_dma, &spi_p->rx_dma_cfg,
rx, // write address
&spi_get_hw(spi_p->hw_inst)->dr, // read address
length, // element count (each element is of
// size transfer_data_size)
false); // start
myASSERT(chk_dmas(spi_p));
myASSERT(chk_spi(spi_p));
// Start the DMA channels:
// start them exactly simultaneously to avoid races (in extreme cases
// the FIFO could overflow)
dma_start_channel_mask((1u << spi_p->tx_dma) | (1u << spi_p->rx_dma));
}
/**
* Calculate the time in milliseconds to transfer the given number of blocks
* over the SPI bus at the given baud rate.
* @param block_count The number of blocks to transfer, each 512 bytes.
* @param spi_p Pointer to the SPI object.
* @return The time in milliseconds to transfer the given number of blocks.
*/
uint32_t calculate_transfer_time_ms(spi_t *spi_p, uint32_t bytes) {
// Calculate the total number of bits to transfer
uint32_t total_bits = bytes * 8;
// Get the baud rate from the SPI interface
uint32_t baud_rate = spi_get_baudrate(spi_p->hw_inst);
// Calculate the time to transfer all bits in seconds
float transfer_time_sec = (double)total_bits / baud_rate;
// Convert the time to milliseconds
float transfer_time_ms = transfer_time_sec * 1000;
transfer_time_ms *= 1.5f; // Add 50% for overhead
transfer_time_ms += 4.0f; // For fixed overhead
return (uint32_t)transfer_time_ms;
}
/**
* @brief Wait until SPI transfer is complete.
* @details This function waits until the SPI master completes the transfer
* or a timeout has occurred. The timeout is specified in milliseconds.
* If the timeout is reached the function will return false.
* This function uses busy waiting to check for completion of the transfer.
*
* @param spi_p The SPI configuration.
* @param timeout_ms The timeout in milliseconds.
* @return true if the transfer is complete, false if the timeout is reached.
*/
bool __not_in_flash_func(spi_transfer_wait_complete)(spi_t *spi_p, uint32_t timeout_ms) {
myASSERT(spi_p);
bool timed_out = false;
// Record the start time in milliseconds
uint32_t start = millis();
// Wait until DMA channels are not busy or timeout is reached
while ((dma_channel_is_busy(spi_p->rx_dma) || dma_channel_is_busy(spi_p->tx_dma)) &&
millis() - start < timeout_ms)
tight_loop_contents();
// Check if the DMA channels are still busy
timed_out = dma_channel_is_busy(spi_p->rx_dma) || dma_channel_is_busy(spi_p->tx_dma);
// Print debug information if the DMA channels are still busy
if (timed_out) {
DBG_PRINTF("DMA busy wait timed out in %s\n", __FUNCTION__);
} else {
// If the DMA channels are not busy, wait for the SPI peripheral to become idle
start = millis();
while (spi_is_busy(spi_p->hw_inst) && millis() - start < timeout_ms)
tight_loop_contents();
// Check if the SPI peripheral is still busy
timed_out = spi_is_busy(spi_p->hw_inst);
// Print debug information if the SPI peripheral is still busy
if (timed_out) {
DBG_PRINTF("SPI busy wait timed out in %s\n", __FUNCTION__);
}
}
// Check the status of the SPI peripheral
bool spi_ok = chk_spi(spi_p);
if (timed_out || !spi_ok) {
chk_dmas(spi_p);
DBG_PRINTF("DMA_INTR: 0b%s\n", uint_binary_str(dma_hw->intr));
DBG_PRINTF("TX DMA CTRL_TRIG: 0b%s\n",
uint_binary_str(dma_hw->ch[spi_p->tx_dma].ctrl_trig));
DBG_PRINTF("RX DMA CTRL_TRIG: 0b%s\n",
uint_binary_str(dma_hw->ch[spi_p->rx_dma].ctrl_trig));
DBG_PRINTF("SPI SSPCR0: 0b%s\n", uint_binary_str(spi_get_hw(spi_p->hw_inst)->cr0));
DBG_PRINTF("SPI SSPCR1: 0b%s\n", uint_binary_str(spi_get_hw(spi_p->hw_inst)->cr1));
DBG_PRINTF("SPI_SSPSR: 0b%s\n", uint_binary_str(spi_get_const_hw(spi_p->hw_inst)->sr));
DBG_PRINTF("SPI_SSPDMACR: 0b%s\n",
uint_binary_str(spi_get_const_hw(spi_p->hw_inst)->dmacr));
dma_channel_abort(spi_p->rx_dma);
dma_channel_abort(spi_p->tx_dma);
}
// Return true if the transfer is complete and the SPI peripheral is in a good state
return !(timed_out || !spi_ok);
}
/**
* SPI Transfer: Read & Write (simultaneously) on SPI bus
* @param spi_p Pointer to the SPI object.
* @param tx Pointer to the transmit buffer. If NULL, SPI_FILL_CHAR is sent as each data
* element.
* @param rx Pointer to the receive buffer. If NULL, data is ignored.
* @param length Number of data elements to transfer.
* @return true if the transfer is completed successfully within the timeout.
* @return false if the transfer times out or encounters an error.
*/
bool __not_in_flash_func(spi_transfer)(spi_t *spi_p, const uint8_t *tx, uint8_t *rx,
size_t length) {
spi_transfer_start(spi_p, tx, rx, length);
// Related to timeouts in spi_lock and sd_lock
uint32_t timeout = calculate_transfer_time_ms(spi_p, length);
return spi_transfer_wait_complete(spi_p, timeout);
}
/**
* @brief Initialize the SPI peripheral and DMA channels.
*
* @param spi_p Pointer to the SPI object.
* @return true if the initialization is successful, false otherwise.
*/
bool my_spi_init(spi_t *spi_p) {
auto_init_mutex(my_spi_init_mutex);
mutex_enter_blocking(&my_spi_init_mutex);
if (!spi_p->initialized) {
//// The SPI may be shared (using multiple SSs); protect it
if (!mutex_is_initialized(&spi_p->mutex)) mutex_init(&spi_p->mutex);
spi_lock(spi_p);
// Defaults:
if (!spi_p->hw_inst) spi_p->hw_inst = spi0;
if (!spi_p->baud_rate) spi_p->baud_rate = clock_get_hz(clk_sys) / 12;
/* Configure component */
// Enable SPI at 100 kHz and connect to GPIOs
spi_init(spi_p->hw_inst, 100 * 1000);
myASSERT(spi_p->spi_mode < 4);
switch (spi_p->spi_mode) {
case 0:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
break;
case 1:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_1, SPI_MSB_FIRST);
break;
case 2:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_1, SPI_CPHA_0, SPI_MSB_FIRST);
break;
case 3:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST);
break;
default:
spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
break;
}
gpio_set_function(spi_p->miso_gpio, GPIO_FUNC_SPI);
gpio_set_function(spi_p->mosi_gpio, GPIO_FUNC_SPI);
gpio_set_function(spi_p->sck_gpio, GPIO_FUNC_SPI);
// ss_gpio is initialized in sd_spi_ctor()
// 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.
gpio_set_slew_rate(spi_p->sck_gpio, GPIO_SLEW_RATE_FAST);
/* 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 (spi_p->set_drive_strength) {
gpio_set_drive_strength(spi_p->mosi_gpio, spi_p->mosi_gpio_drive_strength);
gpio_set_drive_strength(spi_p->sck_gpio, spi_p->sck_gpio_drive_strength);
}
// SD cards' DO MUST be pulled up. However, it might be done externally.
if (!spi_p->no_miso_gpio_pull_up) gpio_pull_up(spi_p->miso_gpio);
// gpio_set_input_hysteresis_enabled(spi_p->miso_gpio, false);
// Check if the user has provided DMA channels
if (spi_p->use_static_dma_channels) {
// Claim the channels provided
dma_channel_claim(spi_p->tx_dma);
dma_channel_claim(spi_p->rx_dma);
} else {
// Grab some unused dma channels
spi_p->tx_dma = dma_claim_unused_channel(true);
spi_p->rx_dma = dma_claim_unused_channel(true);
}
spi_p->tx_dma_cfg = dma_channel_get_default_config(spi_p->tx_dma);
spi_p->rx_dma_cfg = dma_channel_get_default_config(spi_p->rx_dma);
channel_config_set_transfer_data_size(&spi_p->tx_dma_cfg, DMA_SIZE_8);
channel_config_set_transfer_data_size(&spi_p->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(&spi_p->tx_dma_cfg, spi_get_dreq(spi_p->hw_inst, true));
channel_config_set_write_increment(&spi_p->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 configure 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(&spi_p->rx_dma_cfg, spi_get_dreq(spi_p->hw_inst, false));
channel_config_set_read_increment(&spi_p->rx_dma_cfg, false);
LED_INIT();
spi_p->initialized = true;
spi_unlock(spi_p);
}
mutex_exit(&my_spi_init_mutex);
return true;
}
/* [] END OF FILE */

124
lib/sd_driver/SPI/my_spi.h Executable file
View File

@ -0,0 +1,124 @@
/* spi.h
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.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
//
// Pico includes
#include "pico/stdlib.h"
#include "pico/mutex.h"
#include "pico/types.h"
//
#include "hardware/dma.h"
#include "hardware/gpio.h"
#include "hardware/irq.h"
#include "hardware/spi.h"
//
#include "my_debug.h"
#include "sd_timeouts.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SPI_FILL_CHAR (0xFF)
// "Class" representing SPIs
typedef struct spi_t {
spi_inst_t *hw_inst; // SPI HW
uint miso_gpio; // SPI MISO GPIO number (not pin number)
uint mosi_gpio;
uint sck_gpio;
uint baud_rate;
/* The different modes of the Motorola SPI protocol are:
- Mode 0: When CPOL and CPHA are both 0, data sampled at the leading rising edge of the
clock pulse and shifted out on the falling edge. This is the most common mode for SPI bus
communication.
- Mode 1: When CPOL is 0 and CPHA is 1, data sampled at the trailing falling edge and
shifted out on the rising edge.
- Mode 2: When CPOL is 1 and CPHA is 0, data sampled at the leading falling edge
and shifted out on the rising edge.
- Mode 3: When CPOL is 1 and CPHA is 1, data sampled at the trailing rising edge and
shifted out on the falling edge. */
uint spi_mode;
bool no_miso_gpio_pull_up;
/* Drive strength levels for GPIO outputs:
GPIO_DRIVE_STRENGTH_2MA,
GPIO_DRIVE_STRENGTH_4MA,
GPIO_DRIVE_STRENGTH_8MA,
GPIO_DRIVE_STRENGTH_12MA */
bool set_drive_strength;
enum gpio_drive_strength mosi_gpio_drive_strength;
enum gpio_drive_strength sck_gpio_drive_strength;
bool use_static_dma_channels;
uint tx_dma;
uint rx_dma;
/* The following fields are not part of the configuration. They are dynamically assigned. */
dma_channel_config tx_dma_cfg;
dma_channel_config rx_dma_cfg;
mutex_t mutex;
bool initialized;
} spi_t;
void spi_transfer_start(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length);
uint32_t calculate_transfer_time_ms(spi_t *spi_p, uint32_t bytes);
bool spi_transfer_wait_complete(spi_t *spi_p, uint32_t timeout_ms);
bool spi_transfer(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length);
bool my_spi_init(spi_t *spi_p);
static inline void spi_lock(spi_t *spi_p) {
myASSERT(mutex_is_initialized(&spi_p->mutex));
mutex_enter_blocking(&spi_p->mutex);
}
static inline void spi_unlock(spi_t *spi_p) {
myASSERT(mutex_is_initialized(&spi_p->mutex));
mutex_exit(&spi_p->mutex);
}
/*
This uses the Pico LED to show SD card activity.
You can use it to get a rough idea of utilization.
Warning: Pico W uses GPIO 25 for SPI communication to the CYW43439.
You can enable this by putting something like
add_compile_definitions(USE_LED=1)
in CMakeLists.txt, for example.
*/
#if !defined(NO_PICO_LED) && defined(USE_LED) && USE_LED && defined(PICO_DEFAULT_LED_PIN)
# define LED_INIT() \
{ \
gpio_init(PICO_DEFAULT_LED_PIN); \
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); \
}
# define LED_ON() gpio_put(PICO_DEFAULT_LED_PIN, 1)
# define LED_OFF() gpio_put(PICO_DEFAULT_LED_PIN, 0)
#else
# define LED_ON()
# define LED_OFF()
# define LED_INIT()
#endif
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,29 @@
/* sd_card_spi.h
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.
*/
#pragma once
#include "sd_card.h"
#ifdef __cplusplus
extern "C" {
#endif
void sd_spi_ctor(sd_card_t *sd_card_p); // Constructor for sd_card_t
uint32_t sd_go_idle_state(sd_card_t *sd_card_p);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

65
lib/sd_driver/SPI/sd_spi.c Executable file
View File

@ -0,0 +1,65 @@
/* sd_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.
*/
/* Standard includes. */
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
//
#include "hardware/gpio.h"
//
#include "my_debug.h"
#include "delays.h"
#include "my_spi.h"
//
#if !defined(USE_DBG_PRINTF) || defined(NDEBUG)
# pragma GCC diagnostic ignored "-Wunused-variable"
#endif
//
#include "sd_spi.h"
// #define TRACE_PRINTF(fmt, args...)
// #define TRACE_PRINTF printf
void sd_spi_go_high_frequency(sd_card_t *sd_card_p) {
uint actual = spi_set_baudrate(sd_card_p->spi_if_p->spi->hw_inst, sd_card_p->spi_if_p->spi->baud_rate);
DBG_PRINTF("%s: Actual frequency: %lu\n", __FUNCTION__, (long)actual);
}
void sd_spi_go_low_frequency(sd_card_t *sd_card_p) {
uint actual = spi_set_baudrate(sd_card_p->spi_if_p->spi->hw_inst, 400 * 1000); // Actual frequency: 398089
DBG_PRINTF("%s: Actual frequency: %lu\n", __FUNCTION__, (long)actual);
}
/*
After power up, the host starts the clock and sends the initializing sequence on the CMD line.
This sequence is a contiguous stream of logical 1s. The sequence length is the maximum of 1msec,
74 clocks or the supply-ramp-uptime; the additional 10 clocks
(over the 64 clocks after what the card should be ready for communication) is
provided to eliminate power-up synchronization problems.
*/
void sd_spi_send_initializing_sequence(sd_card_t *sd_card_p) {
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
bool old_ss = gpio_get(sd_card_p->spi_if_p->ss_gpio);
// Set DI and CS high and apply 74 or more clock pulses to SCLK:
gpio_put(sd_card_p->spi_if_p->ss_gpio, 1);
uint8_t ones[10];
memset(ones, 0xFF, sizeof ones);
uint32_t start = millis();
do {
spi_transfer(sd_card_p->spi_if_p->spi, ones, NULL, sizeof ones);
} while (millis() - start < 1);
gpio_put(sd_card_p->spi_if_p->ss_gpio, old_ss);
}
/* [] END OF FILE */

135
lib/sd_driver/SPI/sd_spi.h Executable file
View File

@ -0,0 +1,135 @@
/* sd_spi.h
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.
*/
#pragma once
#include <stdint.h>
//
#include "pico/stdlib.h"
//
#include "delays.h"
#include "my_debug.h"
#include "my_spi.h"
#include "sd_card.h"
#include "sd_timeouts.h"
#ifdef NDEBUG
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
#ifdef __cplusplus
extern "C" {
#endif
void sd_spi_go_low_frequency(sd_card_t *this);
void sd_spi_go_high_frequency(sd_card_t *this);
/*
After power up, the host starts the clock and sends the initializing sequence on the CMD line.
This sequence is a contiguous stream of logical 1s. The sequence length is the maximum of
1msec, 74 clocks or the supply-ramp-uptime; the additional 10 clocks (over the 64 clocks after
what the card should be ready for communication) is provided to eliminate power-up
synchronization problems.
*/
void sd_spi_send_initializing_sequence(sd_card_t *sd_card_p);
//FIXME: sd_spi_read, sd_spi_write, and sd_spi_write_read should return an error code on timeout.
static inline uint8_t sd_spi_read(sd_card_t *sd_card_p) {
uint8_t received = SPI_FILL_CHAR;
uint32_t start = millis();
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
millis() - start < sd_timeouts.sd_spi_read)
tight_loop_contents();
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
int num = spi_read_blocking(sd_card_p->spi_if_p->spi->hw_inst, SPI_FILL_CHAR, &received, 1);
myASSERT(1 == num);
return received;
}
static inline void sd_spi_write(sd_card_t *sd_card_p, const uint8_t value) {
uint32_t start = millis();
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
millis() - start < sd_timeouts.sd_spi_write)
tight_loop_contents();
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
int num = spi_write_blocking(sd_card_p->spi_if_p->spi->hw_inst, &value, 1);
myASSERT(1 == num);
}
static inline uint8_t sd_spi_write_read(sd_card_t *sd_card_p, const uint8_t value) {
uint8_t received = SPI_FILL_CHAR;
uint32_t start = millis();
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
millis() - start < sd_timeouts.sd_spi_write_read)
tight_loop_contents();
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
int num = spi_write_read_blocking(sd_card_p->spi_if_p->spi->hw_inst, &value, &received, 1);
myASSERT(1 == num);
return received;
}
// Would do nothing if sd_card_p->spi_if_p->ss_gpio were set to GPIO_FUNC_SPI.
static inline void sd_spi_select(sd_card_t *sd_card_p) {
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
gpio_put(sd_card_p->spi_if_p->ss_gpio, 0);
// See http://elm-chan.org/docs/mmc/mmc_e.html#spibus
sd_spi_write(sd_card_p, SPI_FILL_CHAR);
LED_ON();
}
static inline void sd_spi_deselect(sd_card_t *sd_card_p) {
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
gpio_put(sd_card_p->spi_if_p->ss_gpio, 1);
LED_OFF();
/*
MMC/SDC enables/disables the DO output in synchronising to the SCLK. This
means there is a posibility of bus conflict with MMC/SDC and another SPI
slave that shares an SPI bus. Therefore to make MMC/SDC release the MISO
line, the master device needs to send a byte after the CS signal is
deasserted.
*/
sd_spi_write(sd_card_p, SPI_FILL_CHAR);
}
static inline void sd_spi_lock(sd_card_t *sd_card_p) { spi_lock(sd_card_p->spi_if_p->spi); }
static inline void sd_spi_unlock(sd_card_t *sd_card_p) { spi_unlock(sd_card_p->spi_if_p->spi); }
static inline void sd_spi_acquire(sd_card_t *sd_card_p) {
sd_spi_lock(sd_card_p);
sd_spi_select(sd_card_p);
}
static inline void sd_spi_release(sd_card_t *sd_card_p) {
sd_spi_deselect(sd_card_p);
sd_spi_unlock(sd_card_p);
}
static inline void sd_spi_transfer_start(sd_card_t *sd_card_p, const uint8_t *tx, uint8_t *rx,
size_t length) {
return spi_transfer_start(sd_card_p->spi_if_p->spi, tx, rx, length);
}
static inline bool sd_spi_transfer_wait_complete(sd_card_t *sd_card_p, uint32_t timeout_ms) {
return spi_transfer_wait_complete(sd_card_p->spi_if_p->spi, timeout_ms);
}
/* Transfer tx to SPI while receiving SPI to rx.
tx or rx can be NULL if not important. */
static inline bool sd_spi_transfer(sd_card_t *sd_card_p, const uint8_t *tx, uint8_t *rx,
size_t length) {
return spi_transfer(sd_card_p->spi_if_p->spi, tx, rx, length);
}
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

312
lib/sd_driver/crash.c Normal file
View File

@ -0,0 +1,312 @@
/* crash.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 <string.h>
#include <time.h>
//
#include "pico/stdlib.h"
#include "hardware/sync.h"
#if !PICO_RISCV
# if PICO_RP2040
# include "RP2040.h"
# endif
# if PICO_RP2350
# include "RP2350.h"
# endif
#else
# include "hardware/watchdog.h"
#endif
//
#include "crc.h"
#include "my_debug.h"
#include "my_rtc.h"
#include "util.h"
//
#include "crash.h"
#if defined(NDEBUG) || !USE_DBG_PRINTF
# pragma GCC diagnostic ignored "-Wunused-variable"
#endif
static volatile crash_info_t crash_info_ram __attribute__((section(".uninitialized_data")));
static crash_info_t volatile *crash_info_ram_p = &crash_info_ram;
static crash_info_t previous_crash_info;
static bool _previous_crash_info_valid = false;
__attribute__((noreturn, always_inline))
static inline void reset() {
// if (debugger_connected()) {
__breakpoint();
// } else {
#if !PICO_RISCV
NVIC_SystemReset();
#else
watchdog_reboot(0, 0, 0);
for (;;) {
__nop();
}
#endif
// }
__builtin_unreachable();
}
void crash_handler_init() {
if (crash_info_ram.magic == crash_magic_hard_fault ||
crash_info_ram.magic == crash_magic_stack_overflow ||
crash_info_ram.magic == crash_magic_reboot_requested ||
crash_info_ram.magic == crash_magic_assert ||
crash_info_ram.magic == crash_magic_debug_mon) {
uint8_t xor_checksum = crc7((uint8_t *)crash_info_ram_p,
offsetof(crash_info_t, xor_checksum));
if (xor_checksum == crash_info_ram.xor_checksum) {
// valid crash record
memcpy(&previous_crash_info, (void *)crash_info_ram_p, sizeof previous_crash_info);
_previous_crash_info_valid = true;
}
}
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
}
const crash_info_t *crash_handler_get_info() {
if (_previous_crash_info_valid) {
return &previous_crash_info;
}
return NULL;
}
__attribute__((noreturn))
void system_reset_func(char const *const func) {
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
crash_info_ram.magic = crash_magic_reboot_requested;
crash_info_ram.timestamp = epochtime;
snprintf((char *)crash_info_ram.calling_func, sizeof crash_info_ram.calling_func, "%s",
func);
crash_info_ram.xor_checksum =
crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
__dsb();
reset();
__builtin_unreachable();
}
__attribute__((noreturn))
void capture_assert(const char *file, int line, const char *func, const char *pred) {
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
crash_info_ram.magic = crash_magic_assert;
crash_info_ram.timestamp = epochtime;
// If the filename is too long, take the end:
size_t full_len = strlen(file) + 1;
size_t offset = 0;
if (full_len > sizeof crash_info_ram.assert.file) {
offset = full_len - sizeof crash_info_ram.assert.file;
}
snprintf((char *)crash_info_ram.assert.file, sizeof crash_info_ram.assert.file, "%s",
file + offset);
snprintf((char *)crash_info_ram.assert.func, sizeof crash_info_ram.assert.func, "%s", func);
snprintf((char *)crash_info_ram.assert.pred, sizeof crash_info_ram.assert.pred, "%s", pred);
crash_info_ram.assert.line = line;
crash_info_ram.xor_checksum =
crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
__dsb();
reset();
__builtin_unreachable();
}
#if !PICO_RISCV
__attribute__((used)) extern void DebugMon_HandlerC(uint32_t const *faultStackAddr) {
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
crash_info_ram.magic = crash_magic_debug_mon;
crash_info_ram.timestamp = epochtime;
/* Stores general registers */
crash_info_ram.cy_faultFrame.r0 = faultStackAddr[R0_Pos];
crash_info_ram.cy_faultFrame.r1 = faultStackAddr[R1_Pos];
crash_info_ram.cy_faultFrame.r2 = faultStackAddr[R2_Pos];
crash_info_ram.cy_faultFrame.r3 = faultStackAddr[R3_Pos];
crash_info_ram.cy_faultFrame.r12 = faultStackAddr[R12_Pos];
crash_info_ram.cy_faultFrame.lr = faultStackAddr[LR_Pos];
crash_info_ram.cy_faultFrame.pc = faultStackAddr[PC_Pos];
crash_info_ram.cy_faultFrame.psr = faultStackAddr[PSR_Pos];
///* Stores the Configurable Fault Status Register state with the fault cause */
//crash_info_ram.cy_faultFrame.cfsr.cfsrReg = SCB->CFSR;
///* Stores the Hard Fault Status Register */
//crash_info_ram.cy_faultFrame.hfsr.hfsrReg = SCB->HFSR;
///* Stores the System Handler Control and State Register */
//crash_info_ram.cy_faultFrame.shcsr.shcsrReg = SCB->SHCSR;
///* Store MemMange fault address */
//crash_info_ram.cy_faultFrame.mmfar = SCB->MMFAR;
///* Store Bus fault address */
//crash_info_ram.cy_faultFrame.bfar = SCB->BFAR;
volatile uint8_t __attribute__((unused)) watchpoint_number = 0;
// if (DWT->FUNCTION0 & DWT_FUNCTION_MATCHED_Msk) {
// watchpoint_number = 0;
//} else if (DWT->FUNCTION1 & DWT_FUNCTION_MATCHED_Msk) {
// watchpoint_number = 1;
//} else if (DWT->FUNCTION2 & DWT_FUNCTION_MATCHED_Msk) {
// watchpoint_number = 2;
//}
crash_info_ram.xor_checksum =
crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
__dsb(); // make sure all data is really written into the memory before
// doing a reset
reset();
}
extern void DebugMon_Handler(void);
__attribute__((naked)) void DebugMon_Handler(void) {
__asm volatile(
" movs r0,#4 \n"
" movs r1, lr \n"
" tst r0, r1 \n"
" beq _MSP2 \n"
" mrs r0, psp \n"
" b _HALT2 \n"
"_MSP2: \n"
" mrs r0, msp \n"
"_HALT2: \n"
" ldr r1,[r0,#20] \n"
" b DebugMon_HandlerC \n");
}
void Hardfault_HandlerC(uint32_t const *faultStackAddr) {
memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram);
crash_info_ram.magic = crash_magic_hard_fault;
crash_info_ram.timestamp = epochtime;
/* Stores general registers */
crash_info_ram.cy_faultFrame.r0 = faultStackAddr[R0_Pos];
crash_info_ram.cy_faultFrame.r1 = faultStackAddr[R1_Pos];
crash_info_ram.cy_faultFrame.r2 = faultStackAddr[R2_Pos];
crash_info_ram.cy_faultFrame.r3 = faultStackAddr[R3_Pos];
crash_info_ram.cy_faultFrame.r12 = faultStackAddr[R12_Pos];
crash_info_ram.cy_faultFrame.lr = faultStackAddr[LR_Pos];
crash_info_ram.cy_faultFrame.pc = faultStackAddr[PC_Pos];
crash_info_ram.cy_faultFrame.psr = faultStackAddr[PSR_Pos];
crash_info_ram.xor_checksum =
crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum));
__dsb(); // make sure all data is really written into the memory before
// doing a reset
reset();
}
__attribute__((naked)) void isr_hardfault(void) {
__asm volatile(
" movs r0,#4 \n"
" movs r1, lr \n"
" tst r0, r1 \n"
" beq _MSP3 \n"
" mrs r0, psp \n"
" b _HALT3 \n"
"_MSP3: \n"
" mrs r0, msp \n"
"_HALT3: \n"
" ldr r1,[r0,#20] \n"
" b Hardfault_HandlerC \n");
}
#endif // !PICO_RISCV
enum {
crash_info_magic,
crash_info_hf_lr,
crash_info_hf_pc,
crash_info_reset_request_line,
crash_info_assert
};
int dump_crash_info(crash_info_t const *const crash_info_p, int next, char *const buf,
size_t const buf_sz) {
int nwrit = 0;
switch (next) {
case crash_info_magic:
nwrit = snprintf(buf, buf_sz, "Event: ");
switch (crash_info_p->magic) {
case crash_magic_none:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tNone.");
next = 0;
break;
case crash_magic_bootloader_entry:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tBootloader Entry.");
next = 0;
break;
case crash_magic_hard_fault:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tCM4 Hard Fault.");
next = crash_info_hf_lr;
break;
case crash_magic_debug_mon:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit,
"\tDebug Monitor Watchpoint Triggered.");
next = crash_info_hf_lr;
break;
case crash_magic_reboot_requested:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tReboot Requested.");
next = crash_info_reset_request_line;
break;
case crash_magic_assert:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tAssertion Failed.");
next = crash_info_assert;
break;
default:
DBG_ASSERT_CASE_NOT(crash_info_p->magic);
next = 0;
}
{
struct tm tmbuf;
struct tm *ptm = localtime_r(&crash_info_p->timestamp, &tmbuf);
char tsbuf[32];
size_t n = strftime(tsbuf, sizeof tsbuf, "\n\tTime: %Y-%m-%d %H:%M:%S\n", ptm);
myASSERT(n);
nwrit = snprintf(buf + nwrit, buf_sz - nwrit, "%s", tsbuf);
}
break;
case crash_info_hf_lr:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tLink Register (LR): %p\n",
(void *)crash_info_p->cy_faultFrame.lr);
next = crash_info_hf_pc;
break;
case crash_info_hf_pc:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tProgram Counter (PC): %p\n",
(void *)crash_info_p->cy_faultFrame.pc);
next = 0;
break;
case crash_info_reset_request_line:
nwrit +=
snprintf(buf + nwrit, buf_sz - nwrit, "\tReset request calling function: %s\n",
crash_info_p->calling_func);
next = 0;
break;
case crash_info_assert:
nwrit += snprintf(buf + nwrit, buf_sz - nwrit,
"\tAssertion \"%s\" failed: file \"%s\", line "
"%d, function: %s\n",
crash_info_p->assert.pred, crash_info_p->assert.file,
crash_info_p->assert.line, crash_info_p->assert.func);
next = 0;
break;
default:
ASSERT_CASE_NOT(crash_info_p->magic);
}
return next;
}
/* [] END OF FILE */

114
lib/sd_driver/crash.h Normal file
View File

@ -0,0 +1,114 @@
/* crash.h
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.
*/
// Original from M0AGX (blog@m0agx.eu), "Preserving debugging breadcrumbs across
// reboots in Cortex-M,"
// https://m0agx.eu/2018/08/18/preserving-debugging-breadcrumbs-across-reboots-in-cortex-m/
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
//
#include "pico/stdlib.h"
#ifdef __cplusplus
extern "C" {
#endif
/* The crash info section is at the beginning of the RAM,
* that is not initialized by the linker to preserve
* information across reboots.
*/
/**
* These are the positions of the fault frame elements in the
* fault frame structure.
*/
enum {
R0_Pos = 0, /**< The position of the R0 content in a fault structure */
R1_Pos, /**< The position of the R1 content in a fault structure */
R2_Pos, /**< The position of the R2 content in a fault structure */
R3_Pos, /**< The position of the R3 content in a fault structure */
R12_Pos, /**< The position of the R12 content in a fault structure */
LR_Pos, /**< The position of the LR content in a fault structure */
PC_Pos, /**< The position of the PC content in a fault structure */
PSR_Pos, /**< The position of the PSR content in a fault structure */
NUM_REGS, /**< The number of registers in the fault frame */
};
typedef struct {
uint32_t r0; /**< R0 register content */
uint32_t r1; /**< R1 register content */
uint32_t r2; /**< R2 register content */
uint32_t r3; /**< R3 register content */
uint32_t r12; /**< R12 register content */
uint32_t lr; /**< LR register content */
uint32_t pc; /**< PC register content */
uint32_t psr; /**< PSR register content */
} __attribute__((packed)) cy_stc_fault_frame_t;
typedef enum {
crash_magic_none = 0,
crash_magic_bootloader_entry = 0xB000B000,
crash_magic_hard_fault = 0xCAFEBABE,
crash_magic_debug_mon = 0x01020304,
crash_magic_reboot_requested = 0x00ABCDEF,
crash_magic_stack_overflow = 0x0BADBEEF,
crash_magic_assert = 0xDEBDEBDE
} crash_magic_t;
typedef struct {
char file[32];
int line;
char func[32];
char pred[32];
} crash_assert_t;
typedef struct {
uint32_t magic;
time_t timestamp;
union {
cy_stc_fault_frame_t cy_faultFrame;
crash_assert_t assert;
char calling_func[64];
};
uint8_t xor_checksum; // last to avoid including in calculation
} crash_info_t;
// Trick to find struct size at compile time:
// char (*__kaboom)[sizeof(crash_info_flash_t)] = 1;
// warning: initialization of 'char (*)[132]' from 'int' makes ...
void crash_handler_init();
const crash_info_t *crash_handler_get_info();
volatile const crash_info_t *crash_handler_get_info_flash();
#define SYSTEM_RESET() system_reset_func(__FUNCTION__)
void system_reset_func(char const *const func) __attribute__((noreturn));
void capture_assert(const char *file, int line, const char *func, const char *pred)
__attribute__((noreturn));
void capture_assert_case_not(const char *file, int line, const char *func, int v)
__attribute__((noreturn));
int dump_crash_info(crash_info_t const *const pCrashInfo, int next, char *const buf,
size_t const buf_sz);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

379
lib/sd_driver/crc.c Executable file
View File

@ -0,0 +1,379 @@
/* crc.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.
*/
/* Partially derived from:
* crcany (https://github.com/madler/crcany)
* License
* This code is under the zlib license, permitting free commercial use.
*/
#include "crc.h"
const char m_Crc7Table[] = {0x00, 0x09, 0x12, 0x1B, 0x24, 0x2D, 0x36,
0x3F, 0x48, 0x41, 0x5A, 0x53, 0x6C, 0x65, 0x7E, 0x77, 0x19, 0x10, 0x0B,
0x02, 0x3D, 0x34, 0x2F, 0x26, 0x51, 0x58, 0x43, 0x4A, 0x75, 0x7C, 0x67,
0x6E, 0x32, 0x3B, 0x20, 0x29, 0x16, 0x1F, 0x04, 0x0D, 0x7A, 0x73, 0x68,
0x61, 0x5E, 0x57, 0x4C, 0x45, 0x2B, 0x22, 0x39, 0x30, 0x0F, 0x06, 0x1D,
0x14, 0x63, 0x6A, 0x71, 0x78, 0x47, 0x4E, 0x55, 0x5C, 0x64, 0x6D, 0x76,
0x7F, 0x40, 0x49, 0x52, 0x5B, 0x2C, 0x25, 0x3E, 0x37, 0x08, 0x01, 0x1A,
0x13, 0x7D, 0x74, 0x6F, 0x66, 0x59, 0x50, 0x4B, 0x42, 0x35, 0x3C, 0x27,
0x2E, 0x11, 0x18, 0x03, 0x0A, 0x56, 0x5F, 0x44, 0x4D, 0x72, 0x7B, 0x60,
0x69, 0x1E, 0x17, 0x0C, 0x05, 0x3A, 0x33, 0x28, 0x21, 0x4F, 0x46, 0x5D,
0x54, 0x6B, 0x62, 0x79, 0x70, 0x07, 0x0E, 0x15, 0x1C, 0x23, 0x2A, 0x31,
0x38, 0x41, 0x48, 0x53, 0x5A, 0x65, 0x6C, 0x77, 0x7E, 0x09, 0x00, 0x1B,
0x12, 0x2D, 0x24, 0x3F, 0x36, 0x58, 0x51, 0x4A, 0x43, 0x7C, 0x75, 0x6E,
0x67, 0x10, 0x19, 0x02, 0x0B, 0x34, 0x3D, 0x26, 0x2F, 0x73, 0x7A, 0x61,
0x68, 0x57, 0x5E, 0x45, 0x4C, 0x3B, 0x32, 0x29, 0x20, 0x1F, 0x16, 0x0D,
0x04, 0x6A, 0x63, 0x78, 0x71, 0x4E, 0x47, 0x5C, 0x55, 0x22, 0x2B, 0x30,
0x39, 0x06, 0x0F, 0x14, 0x1D, 0x25, 0x2C, 0x37, 0x3E, 0x01, 0x08, 0x13,
0x1A, 0x6D, 0x64, 0x7F, 0x76, 0x49, 0x40, 0x5B, 0x52, 0x3C, 0x35, 0x2E,
0x27, 0x18, 0x11, 0x0A, 0x03, 0x74, 0x7D, 0x66, 0x6F, 0x50, 0x59, 0x42,
0x4B, 0x17, 0x1E, 0x05, 0x0C, 0x33, 0x3A, 0x21, 0x28, 0x5F, 0x56, 0x4D,
0x44, 0x7B, 0x72, 0x69, 0x60, 0x0E, 0x07, 0x1C, 0x15, 0x2A, 0x23, 0x38,
0x31, 0x46, 0x4F, 0x54, 0x5D, 0x62, 0x6B, 0x70, 0x79};
/* The CRC check sum is a 16-bit value and is computed as follows:
Generator polynomial G(x) = x^16 +x^12 +x^5 +1
M(x) = (first bit) * x^n + (second bit)* x^(n-1) +...+ (last bit) * x^0
CRC[15...0] = Remainder [(M(x) * x^16)/G(x)]
The first bit is the first data bit of the corresponding block. The degree n of the polynomial denotes the
number of bits of the data block decreased by one (e.g. n = 4095 for a block length of 512 bytes). The
generator polynomial G(x) is a standard CCITT polynomial. The code has a minimal distance d=4 and is
used for a payload length of up to 2048 Bytes (n <= 16383).
A CRC is called an n-bit CRC when its check value is n-bits.
For a given n, multiple CRCs are possible, each with a different polynomial.
Such a polynomial has highest degree n, and hence n + 1 terms (the polynomial has a length of n + 1).
The remainder has length n.
The CRC has a name of the form CRC-n-XXX.
Omission of the high-order bit of the divisor polynomial:
Since the high-order bit is always 1, and since an n-bit CRC must be defined by
an (n + 1)-bit divisor which overflows an n-bit register,
some writers assume that it is unnecessary to mention the divisor's high-order bit.
Omission of the low-order bit of the divisor polynomial:
Since the low-order bit is always 1, authors such as Philip Koopman represent polynomials
with their high-order bit intact, but without the low-order bit (the x^0 or 1 term).
This convention encodes the polynomial complete with its degree in one integer.
https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-16-ibm-3740
https://github.com/madler/crcany
16-bit CRC-CCITT specification, is: (CRC-16/IBM-3740? An algorithm commonly misidentified as CRC-CCITT.)
Width = 16 bits
Truncated polynomial = 0x1021
Initial value = 0xFFFF (This doesn't seem to be how it works for SD cards!)
Input data is NOT reflected
Output CRC is NOT reflected
No XOR is performed on the output CRC
Polynomial representations
Normal Reversed Reciprocal Reversed reciprocal
0x1021 0x8408 0x811 0x8810
1
2109876543210
0x1021 = 0b1000000100001 = 4129
*/
static uint16_t const table_byte[] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129,
0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252,
0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672,
0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861,
0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5,
0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b,
0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9,
0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3,
0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3,
0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676,
0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16,
0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b,
0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36,
0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
static uint16_t const table_word[][256] = {
{0x0000, 0x2110, 0x4220, 0x6330, 0x8440, 0xa550, 0xc660, 0xe770, 0x0881, 0x2991,
0x4aa1, 0x6bb1, 0x8cc1, 0xadd1, 0xcee1, 0xeff1, 0x3112, 0x1002, 0x7332, 0x5222,
0xb552, 0x9442, 0xf772, 0xd662, 0x3993, 0x1883, 0x7bb3, 0x5aa3, 0xbdd3, 0x9cc3,
0xfff3, 0xdee3, 0x6224, 0x4334, 0x2004, 0x0114, 0xe664, 0xc774, 0xa444, 0x8554,
0x6aa5, 0x4bb5, 0x2885, 0x0995, 0xeee5, 0xcff5, 0xacc5, 0x8dd5, 0x5336, 0x7226,
0x1116, 0x3006, 0xd776, 0xf666, 0x9556, 0xb446, 0x5bb7, 0x7aa7, 0x1997, 0x3887,
0xdff7, 0xfee7, 0x9dd7, 0xbcc7, 0xc448, 0xe558, 0x8668, 0xa778, 0x4008, 0x6118,
0x0228, 0x2338, 0xccc9, 0xedd9, 0x8ee9, 0xaff9, 0x4889, 0x6999, 0x0aa9, 0x2bb9,
0xf55a, 0xd44a, 0xb77a, 0x966a, 0x711a, 0x500a, 0x333a, 0x122a, 0xfddb, 0xdccb,
0xbffb, 0x9eeb, 0x799b, 0x588b, 0x3bbb, 0x1aab, 0xa66c, 0x877c, 0xe44c, 0xc55c,
0x222c, 0x033c, 0x600c, 0x411c, 0xaeed, 0x8ffd, 0xeccd, 0xcddd, 0x2aad, 0x0bbd,
0x688d, 0x499d, 0x977e, 0xb66e, 0xd55e, 0xf44e, 0x133e, 0x322e, 0x511e, 0x700e,
0x9fff, 0xbeef, 0xdddf, 0xfccf, 0x1bbf, 0x3aaf, 0x599f, 0x788f, 0x8891, 0xa981,
0xcab1, 0xeba1, 0x0cd1, 0x2dc1, 0x4ef1, 0x6fe1, 0x8010, 0xa100, 0xc230, 0xe320,
0x0450, 0x2540, 0x4670, 0x6760, 0xb983, 0x9893, 0xfba3, 0xdab3, 0x3dc3, 0x1cd3,
0x7fe3, 0x5ef3, 0xb102, 0x9012, 0xf322, 0xd232, 0x3542, 0x1452, 0x7762, 0x5672,
0xeab5, 0xcba5, 0xa895, 0x8985, 0x6ef5, 0x4fe5, 0x2cd5, 0x0dc5, 0xe234, 0xc324,
0xa014, 0x8104, 0x6674, 0x4764, 0x2454, 0x0544, 0xdba7, 0xfab7, 0x9987, 0xb897,
0x5fe7, 0x7ef7, 0x1dc7, 0x3cd7, 0xd326, 0xf236, 0x9106, 0xb016, 0x5766, 0x7676,
0x1546, 0x3456, 0x4cd9, 0x6dc9, 0x0ef9, 0x2fe9, 0xc899, 0xe989, 0x8ab9, 0xaba9,
0x4458, 0x6548, 0x0678, 0x2768, 0xc018, 0xe108, 0x8238, 0xa328, 0x7dcb, 0x5cdb,
0x3feb, 0x1efb, 0xf98b, 0xd89b, 0xbbab, 0x9abb, 0x754a, 0x545a, 0x376a, 0x167a,
0xf10a, 0xd01a, 0xb32a, 0x923a, 0x2efd, 0x0fed, 0x6cdd, 0x4dcd, 0xaabd, 0x8bad,
0xe89d, 0xc98d, 0x267c, 0x076c, 0x645c, 0x454c, 0xa23c, 0x832c, 0xe01c, 0xc10c,
0x1fef, 0x3eff, 0x5dcf, 0x7cdf, 0x9baf, 0xbabf, 0xd98f, 0xf89f, 0x176e, 0x367e,
0x554e, 0x745e, 0x932e, 0xb23e, 0xd10e, 0xf01e},
{0x0000, 0x3133, 0x6266, 0x5355, 0xc4cc, 0xf5ff, 0xa6aa, 0x9799, 0xa989, 0x98ba,
0xcbef, 0xfadc, 0x6d45, 0x5c76, 0x0f23, 0x3e10, 0x7303, 0x4230, 0x1165, 0x2056,
0xb7cf, 0x86fc, 0xd5a9, 0xe49a, 0xda8a, 0xebb9, 0xb8ec, 0x89df, 0x1e46, 0x2f75,
0x7c20, 0x4d13, 0xe606, 0xd735, 0x8460, 0xb553, 0x22ca, 0x13f9, 0x40ac, 0x719f,
0x4f8f, 0x7ebc, 0x2de9, 0x1cda, 0x8b43, 0xba70, 0xe925, 0xd816, 0x9505, 0xa436,
0xf763, 0xc650, 0x51c9, 0x60fa, 0x33af, 0x029c, 0x3c8c, 0x0dbf, 0x5eea, 0x6fd9,
0xf840, 0xc973, 0x9a26, 0xab15, 0xcc0d, 0xfd3e, 0xae6b, 0x9f58, 0x08c1, 0x39f2,
0x6aa7, 0x5b94, 0x6584, 0x54b7, 0x07e2, 0x36d1, 0xa148, 0x907b, 0xc32e, 0xf21d,
0xbf0e, 0x8e3d, 0xdd68, 0xec5b, 0x7bc2, 0x4af1, 0x19a4, 0x2897, 0x1687, 0x27b4,
0x74e1, 0x45d2, 0xd24b, 0xe378, 0xb02d, 0x811e, 0x2a0b, 0x1b38, 0x486d, 0x795e,
0xeec7, 0xdff4, 0x8ca1, 0xbd92, 0x8382, 0xb2b1, 0xe1e4, 0xd0d7, 0x474e, 0x767d,
0x2528, 0x141b, 0x5908, 0x683b, 0x3b6e, 0x0a5d, 0x9dc4, 0xacf7, 0xffa2, 0xce91,
0xf081, 0xc1b2, 0x92e7, 0xa3d4, 0x344d, 0x057e, 0x562b, 0x6718, 0x981b, 0xa928,
0xfa7d, 0xcb4e, 0x5cd7, 0x6de4, 0x3eb1, 0x0f82, 0x3192, 0x00a1, 0x53f4, 0x62c7,
0xf55e, 0xc46d, 0x9738, 0xa60b, 0xeb18, 0xda2b, 0x897e, 0xb84d, 0x2fd4, 0x1ee7,
0x4db2, 0x7c81, 0x4291, 0x73a2, 0x20f7, 0x11c4, 0x865d, 0xb76e, 0xe43b, 0xd508,
0x7e1d, 0x4f2e, 0x1c7b, 0x2d48, 0xbad1, 0x8be2, 0xd8b7, 0xe984, 0xd794, 0xe6a7,
0xb5f2, 0x84c1, 0x1358, 0x226b, 0x713e, 0x400d, 0x0d1e, 0x3c2d, 0x6f78, 0x5e4b,
0xc9d2, 0xf8e1, 0xabb4, 0x9a87, 0xa497, 0x95a4, 0xc6f1, 0xf7c2, 0x605b, 0x5168,
0x023d, 0x330e, 0x5416, 0x6525, 0x3670, 0x0743, 0x90da, 0xa1e9, 0xf2bc, 0xc38f,
0xfd9f, 0xccac, 0x9ff9, 0xaeca, 0x3953, 0x0860, 0x5b35, 0x6a06, 0x2715, 0x1626,
0x4573, 0x7440, 0xe3d9, 0xd2ea, 0x81bf, 0xb08c, 0x8e9c, 0xbfaf, 0xecfa, 0xddc9,
0x4a50, 0x7b63, 0x2836, 0x1905, 0xb210, 0x8323, 0xd076, 0xe145, 0x76dc, 0x47ef,
0x14ba, 0x2589, 0x1b99, 0x2aaa, 0x79ff, 0x48cc, 0xdf55, 0xee66, 0xbd33, 0x8c00,
0xc113, 0xf020, 0xa375, 0x9246, 0x05df, 0x34ec, 0x67b9, 0x568a, 0x689a, 0x59a9,
0x0afc, 0x3bcf, 0xac56, 0x9d65, 0xce30, 0xff03},
{0x0000, 0x3037, 0x606e, 0x5059, 0xc0dc, 0xf0eb, 0xa0b2, 0x9085, 0xa1a9, 0x919e,
0xc1c7, 0xf1f0, 0x6175, 0x5142, 0x011b, 0x312c, 0x6343, 0x5374, 0x032d, 0x331a,
0xa39f, 0x93a8, 0xc3f1, 0xf3c6, 0xc2ea, 0xf2dd, 0xa284, 0x92b3, 0x0236, 0x3201,
0x6258, 0x526f, 0xc686, 0xf6b1, 0xa6e8, 0x96df, 0x065a, 0x366d, 0x6634, 0x5603,
0x672f, 0x5718, 0x0741, 0x3776, 0xa7f3, 0x97c4, 0xc79d, 0xf7aa, 0xa5c5, 0x95f2,
0xc5ab, 0xf59c, 0x6519, 0x552e, 0x0577, 0x3540, 0x046c, 0x345b, 0x6402, 0x5435,
0xc4b0, 0xf487, 0xa4de, 0x94e9, 0xad1d, 0x9d2a, 0xcd73, 0xfd44, 0x6dc1, 0x5df6,
0x0daf, 0x3d98, 0x0cb4, 0x3c83, 0x6cda, 0x5ced, 0xcc68, 0xfc5f, 0xac06, 0x9c31,
0xce5e, 0xfe69, 0xae30, 0x9e07, 0x0e82, 0x3eb5, 0x6eec, 0x5edb, 0x6ff7, 0x5fc0,
0x0f99, 0x3fae, 0xaf2b, 0x9f1c, 0xcf45, 0xff72, 0x6b9b, 0x5bac, 0x0bf5, 0x3bc2,
0xab47, 0x9b70, 0xcb29, 0xfb1e, 0xca32, 0xfa05, 0xaa5c, 0x9a6b, 0x0aee, 0x3ad9,
0x6a80, 0x5ab7, 0x08d8, 0x38ef, 0x68b6, 0x5881, 0xc804, 0xf833, 0xa86a, 0x985d,
0xa971, 0x9946, 0xc91f, 0xf928, 0x69ad, 0x599a, 0x09c3, 0x39f4, 0x5a3b, 0x6a0c,
0x3a55, 0x0a62, 0x9ae7, 0xaad0, 0xfa89, 0xcabe, 0xfb92, 0xcba5, 0x9bfc, 0xabcb,
0x3b4e, 0x0b79, 0x5b20, 0x6b17, 0x3978, 0x094f, 0x5916, 0x6921, 0xf9a4, 0xc993,
0x99ca, 0xa9fd, 0x98d1, 0xa8e6, 0xf8bf, 0xc888, 0x580d, 0x683a, 0x3863, 0x0854,
0x9cbd, 0xac8a, 0xfcd3, 0xcce4, 0x5c61, 0x6c56, 0x3c0f, 0x0c38, 0x3d14, 0x0d23,
0x5d7a, 0x6d4d, 0xfdc8, 0xcdff, 0x9da6, 0xad91, 0xfffe, 0xcfc9, 0x9f90, 0xafa7,
0x3f22, 0x0f15, 0x5f4c, 0x6f7b, 0x5e57, 0x6e60, 0x3e39, 0x0e0e, 0x9e8b, 0xaebc,
0xfee5, 0xced2, 0xf726, 0xc711, 0x9748, 0xa77f, 0x37fa, 0x07cd, 0x5794, 0x67a3,
0x568f, 0x66b8, 0x36e1, 0x06d6, 0x9653, 0xa664, 0xf63d, 0xc60a, 0x9465, 0xa452,
0xf40b, 0xc43c, 0x54b9, 0x648e, 0x34d7, 0x04e0, 0x35cc, 0x05fb, 0x55a2, 0x6595,
0xf510, 0xc527, 0x957e, 0xa549, 0x31a0, 0x0197, 0x51ce, 0x61f9, 0xf17c, 0xc14b,
0x9112, 0xa125, 0x9009, 0xa03e, 0xf067, 0xc050, 0x50d5, 0x60e2, 0x30bb, 0x008c,
0x52e3, 0x62d4, 0x328d, 0x02ba, 0x923f, 0xa208, 0xf251, 0xc266, 0xf34a, 0xc37d,
0x9324, 0xa313, 0x3396, 0x03a1, 0x53f8, 0x63cf},
{0x0000, 0xb476, 0x68ed, 0xdc9b, 0xf1ca, 0x45bc, 0x9927, 0x2d51, 0xc385, 0x77f3,
0xab68, 0x1f1e, 0x324f, 0x8639, 0x5aa2, 0xeed4, 0xa71b, 0x136d, 0xcff6, 0x7b80,
0x56d1, 0xe2a7, 0x3e3c, 0x8a4a, 0x649e, 0xd0e8, 0x0c73, 0xb805, 0x9554, 0x2122,
0xfdb9, 0x49cf, 0x4e37, 0xfa41, 0x26da, 0x92ac, 0xbffd, 0x0b8b, 0xd710, 0x6366,
0x8db2, 0x39c4, 0xe55f, 0x5129, 0x7c78, 0xc80e, 0x1495, 0xa0e3, 0xe92c, 0x5d5a,
0x81c1, 0x35b7, 0x18e6, 0xac90, 0x700b, 0xc47d, 0x2aa9, 0x9edf, 0x4244, 0xf632,
0xdb63, 0x6f15, 0xb38e, 0x07f8, 0x9c6e, 0x2818, 0xf483, 0x40f5, 0x6da4, 0xd9d2,
0x0549, 0xb13f, 0x5feb, 0xeb9d, 0x3706, 0x8370, 0xae21, 0x1a57, 0xc6cc, 0x72ba,
0x3b75, 0x8f03, 0x5398, 0xe7ee, 0xcabf, 0x7ec9, 0xa252, 0x1624, 0xf8f0, 0x4c86,
0x901d, 0x246b, 0x093a, 0xbd4c, 0x61d7, 0xd5a1, 0xd259, 0x662f, 0xbab4, 0x0ec2,
0x2393, 0x97e5, 0x4b7e, 0xff08, 0x11dc, 0xa5aa, 0x7931, 0xcd47, 0xe016, 0x5460,
0x88fb, 0x3c8d, 0x7542, 0xc134, 0x1daf, 0xa9d9, 0x8488, 0x30fe, 0xec65, 0x5813,
0xb6c7, 0x02b1, 0xde2a, 0x6a5c, 0x470d, 0xf37b, 0x2fe0, 0x9b96, 0x38dd, 0x8cab,
0x5030, 0xe446, 0xc917, 0x7d61, 0xa1fa, 0x158c, 0xfb58, 0x4f2e, 0x93b5, 0x27c3,
0x0a92, 0xbee4, 0x627f, 0xd609, 0x9fc6, 0x2bb0, 0xf72b, 0x435d, 0x6e0c, 0xda7a,
0x06e1, 0xb297, 0x5c43, 0xe835, 0x34ae, 0x80d8, 0xad89, 0x19ff, 0xc564, 0x7112,
0x76ea, 0xc29c, 0x1e07, 0xaa71, 0x8720, 0x3356, 0xefcd, 0x5bbb, 0xb56f, 0x0119,
0xdd82, 0x69f4, 0x44a5, 0xf0d3, 0x2c48, 0x983e, 0xd1f1, 0x6587, 0xb91c, 0x0d6a,
0x203b, 0x944d, 0x48d6, 0xfca0, 0x1274, 0xa602, 0x7a99, 0xceef, 0xe3be, 0x57c8,
0x8b53, 0x3f25, 0xa4b3, 0x10c5, 0xcc5e, 0x7828, 0x5579, 0xe10f, 0x3d94, 0x89e2,
0x6736, 0xd340, 0x0fdb, 0xbbad, 0x96fc, 0x228a, 0xfe11, 0x4a67, 0x03a8, 0xb7de,
0x6b45, 0xdf33, 0xf262, 0x4614, 0x9a8f, 0x2ef9, 0xc02d, 0x745b, 0xa8c0, 0x1cb6,
0x31e7, 0x8591, 0x590a, 0xed7c, 0xea84, 0x5ef2, 0x8269, 0x361f, 0x1b4e, 0xaf38,
0x73a3, 0xc7d5, 0x2901, 0x9d77, 0x41ec, 0xf59a, 0xd8cb, 0x6cbd, 0xb026, 0x0450,
0x4d9f, 0xf9e9, 0x2572, 0x9104, 0xbc55, 0x0823, 0xd4b8, 0x60ce, 0x8e1a, 0x3a6c,
0xe6f7, 0x5281, 0x7fd0, 0xcba6, 0x173d, 0xa34b},
{0x0000, 0x51aa, 0x8344, 0xd2ee, 0x0689, 0x5723, 0x85cd, 0xd467, 0x2d02, 0x7ca8,
0xae46, 0xffec, 0x2b8b, 0x7a21, 0xa8cf, 0xf965, 0x5a04, 0x0bae, 0xd940, 0x88ea,
0x5c8d, 0x0d27, 0xdfc9, 0x8e63, 0x7706, 0x26ac, 0xf442, 0xa5e8, 0x718f, 0x2025,
0xf2cb, 0xa361, 0xb408, 0xe5a2, 0x374c, 0x66e6, 0xb281, 0xe32b, 0x31c5, 0x606f,
0x990a, 0xc8a0, 0x1a4e, 0x4be4, 0x9f83, 0xce29, 0x1cc7, 0x4d6d, 0xee0c, 0xbfa6,
0x6d48, 0x3ce2, 0xe885, 0xb92f, 0x6bc1, 0x3a6b, 0xc30e, 0x92a4, 0x404a, 0x11e0,
0xc587, 0x942d, 0x46c3, 0x1769, 0x6811, 0x39bb, 0xeb55, 0xbaff, 0x6e98, 0x3f32,
0xeddc, 0xbc76, 0x4513, 0x14b9, 0xc657, 0x97fd, 0x439a, 0x1230, 0xc0de, 0x9174,
0x3215, 0x63bf, 0xb151, 0xe0fb, 0x349c, 0x6536, 0xb7d8, 0xe672, 0x1f17, 0x4ebd,
0x9c53, 0xcdf9, 0x199e, 0x4834, 0x9ada, 0xcb70, 0xdc19, 0x8db3, 0x5f5d, 0x0ef7,
0xda90, 0x8b3a, 0x59d4, 0x087e, 0xf11b, 0xa0b1, 0x725f, 0x23f5, 0xf792, 0xa638,
0x74d6, 0x257c, 0x861d, 0xd7b7, 0x0559, 0x54f3, 0x8094, 0xd13e, 0x03d0, 0x527a,
0xab1f, 0xfab5, 0x285b, 0x79f1, 0xad96, 0xfc3c, 0x2ed2, 0x7f78, 0xd022, 0x8188,
0x5366, 0x02cc, 0xd6ab, 0x8701, 0x55ef, 0x0445, 0xfd20, 0xac8a, 0x7e64, 0x2fce,
0xfba9, 0xaa03, 0x78ed, 0x2947, 0x8a26, 0xdb8c, 0x0962, 0x58c8, 0x8caf, 0xdd05,
0x0feb, 0x5e41, 0xa724, 0xf68e, 0x2460, 0x75ca, 0xa1ad, 0xf007, 0x22e9, 0x7343,
0x642a, 0x3580, 0xe76e, 0xb6c4, 0x62a3, 0x3309, 0xe1e7, 0xb04d, 0x4928, 0x1882,
0xca6c, 0x9bc6, 0x4fa1, 0x1e0b, 0xcce5, 0x9d4f, 0x3e2e, 0x6f84, 0xbd6a, 0xecc0,
0x38a7, 0x690d, 0xbbe3, 0xea49, 0x132c, 0x4286, 0x9068, 0xc1c2, 0x15a5, 0x440f,
0x96e1, 0xc74b, 0xb833, 0xe999, 0x3b77, 0x6add, 0xbeba, 0xef10, 0x3dfe, 0x6c54,
0x9531, 0xc49b, 0x1675, 0x47df, 0x93b8, 0xc212, 0x10fc, 0x4156, 0xe237, 0xb39d,
0x6173, 0x30d9, 0xe4be, 0xb514, 0x67fa, 0x3650, 0xcf35, 0x9e9f, 0x4c71, 0x1ddb,
0xc9bc, 0x9816, 0x4af8, 0x1b52, 0x0c3b, 0x5d91, 0x8f7f, 0xded5, 0x0ab2, 0x5b18,
0x89f6, 0xd85c, 0x2139, 0x7093, 0xa27d, 0xf3d7, 0x27b0, 0x761a, 0xa4f4, 0xf55e,
0x563f, 0x0795, 0xd57b, 0x84d1, 0x50b6, 0x011c, 0xd3f2, 0x8258, 0x7b3d, 0x2a97,
0xf879, 0xa9d3, 0x7db4, 0x2c1e, 0xfef0, 0xaf5a},
{0x0000, 0xa045, 0x408b, 0xe0ce, 0xa106, 0x0143, 0xe18d, 0x41c8, 0x420d, 0xe248,
0x0286, 0xa2c3, 0xe30b, 0x434e, 0xa380, 0x03c5, 0x841a, 0x245f, 0xc491, 0x64d4,
0x251c, 0x8559, 0x6597, 0xc5d2, 0xc617, 0x6652, 0x869c, 0x26d9, 0x6711, 0xc754,
0x279a, 0x87df, 0x0835, 0xa870, 0x48be, 0xe8fb, 0xa933, 0x0976, 0xe9b8, 0x49fd,
0x4a38, 0xea7d, 0x0ab3, 0xaaf6, 0xeb3e, 0x4b7b, 0xabb5, 0x0bf0, 0x8c2f, 0x2c6a,
0xcca4, 0x6ce1, 0x2d29, 0x8d6c, 0x6da2, 0xcde7, 0xce22, 0x6e67, 0x8ea9, 0x2eec,
0x6f24, 0xcf61, 0x2faf, 0x8fea, 0x106a, 0xb02f, 0x50e1, 0xf0a4, 0xb16c, 0x1129,
0xf1e7, 0x51a2, 0x5267, 0xf222, 0x12ec, 0xb2a9, 0xf361, 0x5324, 0xb3ea, 0x13af,
0x9470, 0x3435, 0xd4fb, 0x74be, 0x3576, 0x9533, 0x75fd, 0xd5b8, 0xd67d, 0x7638,
0x96f6, 0x36b3, 0x777b, 0xd73e, 0x37f0, 0x97b5, 0x185f, 0xb81a, 0x58d4, 0xf891,
0xb959, 0x191c, 0xf9d2, 0x5997, 0x5a52, 0xfa17, 0x1ad9, 0xba9c, 0xfb54, 0x5b11,
0xbbdf, 0x1b9a, 0x9c45, 0x3c00, 0xdcce, 0x7c8b, 0x3d43, 0x9d06, 0x7dc8, 0xdd8d,
0xde48, 0x7e0d, 0x9ec3, 0x3e86, 0x7f4e, 0xdf0b, 0x3fc5, 0x9f80, 0x20d4, 0x8091,
0x605f, 0xc01a, 0x81d2, 0x2197, 0xc159, 0x611c, 0x62d9, 0xc29c, 0x2252, 0x8217,
0xc3df, 0x639a, 0x8354, 0x2311, 0xa4ce, 0x048b, 0xe445, 0x4400, 0x05c8, 0xa58d,
0x4543, 0xe506, 0xe6c3, 0x4686, 0xa648, 0x060d, 0x47c5, 0xe780, 0x074e, 0xa70b,
0x28e1, 0x88a4, 0x686a, 0xc82f, 0x89e7, 0x29a2, 0xc96c, 0x6929, 0x6aec, 0xcaa9,
0x2a67, 0x8a22, 0xcbea, 0x6baf, 0x8b61, 0x2b24, 0xacfb, 0x0cbe, 0xec70, 0x4c35,
0x0dfd, 0xadb8, 0x4d76, 0xed33, 0xeef6, 0x4eb3, 0xae7d, 0x0e38, 0x4ff0, 0xefb5,
0x0f7b, 0xaf3e, 0x30be, 0x90fb, 0x7035, 0xd070, 0x91b8, 0x31fd, 0xd133, 0x7176,
0x72b3, 0xd2f6, 0x3238, 0x927d, 0xd3b5, 0x73f0, 0x933e, 0x337b, 0xb4a4, 0x14e1,
0xf42f, 0x546a, 0x15a2, 0xb5e7, 0x5529, 0xf56c, 0xf6a9, 0x56ec, 0xb622, 0x1667,
0x57af, 0xf7ea, 0x1724, 0xb761, 0x388b, 0x98ce, 0x7800, 0xd845, 0x998d, 0x39c8,
0xd906, 0x7943, 0x7a86, 0xdac3, 0x3a0d, 0x9a48, 0xdb80, 0x7bc5, 0x9b0b, 0x3b4e,
0xbc91, 0x1cd4, 0xfc1a, 0x5c5f, 0x1d97, 0xbdd2, 0x5d1c, 0xfd59, 0xfe9c, 0x5ed9,
0xbe17, 0x1e52, 0x5f9a, 0xffdf, 0x1f11, 0xbf54},
{0x0000, 0x61b8, 0xe360, 0x82d8, 0xc6c1, 0xa779, 0x25a1, 0x4419, 0xad93, 0xcc2b,
0x4ef3, 0x2f4b, 0x6b52, 0x0aea, 0x8832, 0xe98a, 0x7b37, 0x1a8f, 0x9857, 0xf9ef,
0xbdf6, 0xdc4e, 0x5e96, 0x3f2e, 0xd6a4, 0xb71c, 0x35c4, 0x547c, 0x1065, 0x71dd,
0xf305, 0x92bd, 0xf66e, 0x97d6, 0x150e, 0x74b6, 0x30af, 0x5117, 0xd3cf, 0xb277,
0x5bfd, 0x3a45, 0xb89d, 0xd925, 0x9d3c, 0xfc84, 0x7e5c, 0x1fe4, 0x8d59, 0xece1,
0x6e39, 0x0f81, 0x4b98, 0x2a20, 0xa8f8, 0xc940, 0x20ca, 0x4172, 0xc3aa, 0xa212,
0xe60b, 0x87b3, 0x056b, 0x64d3, 0xecdd, 0x8d65, 0x0fbd, 0x6e05, 0x2a1c, 0x4ba4,
0xc97c, 0xa8c4, 0x414e, 0x20f6, 0xa22e, 0xc396, 0x878f, 0xe637, 0x64ef, 0x0557,
0x97ea, 0xf652, 0x748a, 0x1532, 0x512b, 0x3093, 0xb24b, 0xd3f3, 0x3a79, 0x5bc1,
0xd919, 0xb8a1, 0xfcb8, 0x9d00, 0x1fd8, 0x7e60, 0x1ab3, 0x7b0b, 0xf9d3, 0x986b,
0xdc72, 0xbdca, 0x3f12, 0x5eaa, 0xb720, 0xd698, 0x5440, 0x35f8, 0x71e1, 0x1059,
0x9281, 0xf339, 0x6184, 0x003c, 0x82e4, 0xe35c, 0xa745, 0xc6fd, 0x4425, 0x259d,
0xcc17, 0xadaf, 0x2f77, 0x4ecf, 0x0ad6, 0x6b6e, 0xe9b6, 0x880e, 0xf9ab, 0x9813,
0x1acb, 0x7b73, 0x3f6a, 0x5ed2, 0xdc0a, 0xbdb2, 0x5438, 0x3580, 0xb758, 0xd6e0,
0x92f9, 0xf341, 0x7199, 0x1021, 0x829c, 0xe324, 0x61fc, 0x0044, 0x445d, 0x25e5,
0xa73d, 0xc685, 0x2f0f, 0x4eb7, 0xcc6f, 0xadd7, 0xe9ce, 0x8876, 0x0aae, 0x6b16,
0x0fc5, 0x6e7d, 0xeca5, 0x8d1d, 0xc904, 0xa8bc, 0x2a64, 0x4bdc, 0xa256, 0xc3ee,
0x4136, 0x208e, 0x6497, 0x052f, 0x87f7, 0xe64f, 0x74f2, 0x154a, 0x9792, 0xf62a,
0xb233, 0xd38b, 0x5153, 0x30eb, 0xd961, 0xb8d9, 0x3a01, 0x5bb9, 0x1fa0, 0x7e18,
0xfcc0, 0x9d78, 0x1576, 0x74ce, 0xf616, 0x97ae, 0xd3b7, 0xb20f, 0x30d7, 0x516f,
0xb8e5, 0xd95d, 0x5b85, 0x3a3d, 0x7e24, 0x1f9c, 0x9d44, 0xfcfc, 0x6e41, 0x0ff9,
0x8d21, 0xec99, 0xa880, 0xc938, 0x4be0, 0x2a58, 0xc3d2, 0xa26a, 0x20b2, 0x410a,
0x0513, 0x64ab, 0xe673, 0x87cb, 0xe318, 0x82a0, 0x0078, 0x61c0, 0x25d9, 0x4461,
0xc6b9, 0xa701, 0x4e8b, 0x2f33, 0xadeb, 0xcc53, 0x884a, 0xe9f2, 0x6b2a, 0x0a92,
0x982f, 0xf997, 0x7b4f, 0x1af7, 0x5eee, 0x3f56, 0xbd8e, 0xdc36, 0x35bc, 0x5404,
0xd6dc, 0xb764, 0xf37d, 0x92c5, 0x101d, 0x71a5},
{0x0000, 0xd347, 0xa68f, 0x75c8, 0x6d0f, 0xbe48, 0xcb80, 0x18c7, 0xda1e, 0x0959,
0x7c91, 0xafd6, 0xb711, 0x6456, 0x119e, 0xc2d9, 0xb43d, 0x677a, 0x12b2, 0xc1f5,
0xd932, 0x0a75, 0x7fbd, 0xacfa, 0x6e23, 0xbd64, 0xc8ac, 0x1beb, 0x032c, 0xd06b,
0xa5a3, 0x76e4, 0x687b, 0xbb3c, 0xcef4, 0x1db3, 0x0574, 0xd633, 0xa3fb, 0x70bc,
0xb265, 0x6122, 0x14ea, 0xc7ad, 0xdf6a, 0x0c2d, 0x79e5, 0xaaa2, 0xdc46, 0x0f01,
0x7ac9, 0xa98e, 0xb149, 0x620e, 0x17c6, 0xc481, 0x0658, 0xd51f, 0xa0d7, 0x7390,
0x6b57, 0xb810, 0xcdd8, 0x1e9f, 0xd0f6, 0x03b1, 0x7679, 0xa53e, 0xbdf9, 0x6ebe,
0x1b76, 0xc831, 0x0ae8, 0xd9af, 0xac67, 0x7f20, 0x67e7, 0xb4a0, 0xc168, 0x122f,
0x64cb, 0xb78c, 0xc244, 0x1103, 0x09c4, 0xda83, 0xaf4b, 0x7c0c, 0xbed5, 0x6d92,
0x185a, 0xcb1d, 0xd3da, 0x009d, 0x7555, 0xa612, 0xb88d, 0x6bca, 0x1e02, 0xcd45,
0xd582, 0x06c5, 0x730d, 0xa04a, 0x6293, 0xb1d4, 0xc41c, 0x175b, 0x0f9c, 0xdcdb,
0xa913, 0x7a54, 0x0cb0, 0xdff7, 0xaa3f, 0x7978, 0x61bf, 0xb2f8, 0xc730, 0x1477,
0xd6ae, 0x05e9, 0x7021, 0xa366, 0xbba1, 0x68e6, 0x1d2e, 0xce69, 0x81fd, 0x52ba,
0x2772, 0xf435, 0xecf2, 0x3fb5, 0x4a7d, 0x993a, 0x5be3, 0x88a4, 0xfd6c, 0x2e2b,
0x36ec, 0xe5ab, 0x9063, 0x4324, 0x35c0, 0xe687, 0x934f, 0x4008, 0x58cf, 0x8b88,
0xfe40, 0x2d07, 0xefde, 0x3c99, 0x4951, 0x9a16, 0x82d1, 0x5196, 0x245e, 0xf719,
0xe986, 0x3ac1, 0x4f09, 0x9c4e, 0x8489, 0x57ce, 0x2206, 0xf141, 0x3398, 0xe0df,
0x9517, 0x4650, 0x5e97, 0x8dd0, 0xf818, 0x2b5f, 0x5dbb, 0x8efc, 0xfb34, 0x2873,
0x30b4, 0xe3f3, 0x963b, 0x457c, 0x87a5, 0x54e2, 0x212a, 0xf26d, 0xeaaa, 0x39ed,
0x4c25, 0x9f62, 0x510b, 0x824c, 0xf784, 0x24c3, 0x3c04, 0xef43, 0x9a8b, 0x49cc,
0x8b15, 0x5852, 0x2d9a, 0xfedd, 0xe61a, 0x355d, 0x4095, 0x93d2, 0xe536, 0x3671,
0x43b9, 0x90fe, 0x8839, 0x5b7e, 0x2eb6, 0xfdf1, 0x3f28, 0xec6f, 0x99a7, 0x4ae0,
0x5227, 0x8160, 0xf4a8, 0x27ef, 0x3970, 0xea37, 0x9fff, 0x4cb8, 0x547f, 0x8738,
0xf2f0, 0x21b7, 0xe36e, 0x3029, 0x45e1, 0x96a6, 0x8e61, 0x5d26, 0x28ee, 0xfba9,
0x8d4d, 0x5e0a, 0x2bc2, 0xf885, 0xe042, 0x3305, 0x46cd, 0x958a, 0x5753, 0x8414,
0xf1dc, 0x229b, 0x3a5c, 0xe91b, 0x9cd3, 0x4f94}
};
static inline uint16_t swaplow(uint16_t crc) {
return
((crc & 0xff) << 8) +
((crc & 0xff00) >> 8);
}
// This code assumes that integers are stored little-endian.
__attribute__((optimize("Ofast")))
static uint16_t crc16ibm_3740_word(uint16_t crc, void const *mem, size_t len) {
unsigned char const *data = mem;
if (data == NULL)
return 0xffff;
while (len && ((ptrdiff_t)data & 0x7)) {
len--;
crc = (crc << 8) ^
table_byte[((crc >> 8) ^ *data++) & 0xff];
}
crc = swaplow(crc);
size_t n = len >> 3;
for (size_t i = 0; i < n; i++) {
uint64_t word = crc ^ ((uint64_t const *)data)[i];
crc = table_word[7][word & 0xff] ^
table_word[6][(word >> 8) & 0xff] ^
table_word[5][(word >> 16) & 0xff] ^
table_word[4][(word >> 24) & 0xff] ^
table_word[3][(word >> 32) & 0xff] ^
table_word[2][(word >> 40) & 0xff] ^
table_word[1][(word >> 48) & 0xff] ^
table_word[0][word >> 56];
}
data += n << 3;
len &= 7;
crc = swaplow(crc);
while (len) {
len--;
crc = (crc << 8) ^
table_byte[((crc >> 8) ^ *data++) & 0xff];
}
return crc;
}
uint16_t crc16(uint8_t const *data, int const length)
{
//Calculate the CRC16 checksum for the specified data block
unsigned short crc = 0;
//Return the calculated checksum
return crc16ibm_3740_word(crc, data, length);
}
/* [] END OF FILE */

73
lib/sd_driver/crc.h Executable file
View File

@ -0,0 +1,73 @@
/* crc.h
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.
*/
/* Derived from:
* SD/MMC File System Library
* Copyright (c) 2016 Neil Thiessen
*
* 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.
*/
#ifndef SD_CRC_H
#define SD_CRC_H
#include <stddef.h>
#include <stdint.h>
/**
* @brief Calculate the CRC7 checksum for the specified data block.
*
* This function calculates the CRC7 checksum for the specified data block
* using the lookup table defined in the m_Crc7Table array.
*
* @param data The data block to be checked.
* @param length The length of the data block in bytes.
* @return The calculated checksum.
*/
__attribute__((optimize("Ofast")))
static inline char crc7(uint8_t const *data, int const length) {
extern const char m_Crc7Table[];
char crc = 0;
for (int i = 0; i < length; i++) {
crc = m_Crc7Table[(crc << 1) ^ data[i]];
}
//Return the calculated checksum
return crc;
}
/**
* @brief Calculate the CRC16 checksum for the specified data block.
*
* This function calculates the CRC16 checksum for the specified data block
* using the lookup table defined in the m_Crc7Table array.
*
* @param data The data block to be checked.
* @param length The length of the data block in bytes.
* @return The calculated checksum.
*/
uint16_t crc16(uint8_t const *data, int const length);
#endif
/* [] END OF FILE */

79
lib/sd_driver/delays.h Normal file
View File

@ -0,0 +1,79 @@
/*
* delays.h
*
* Created on: Apr 25, 2022
* Author: carlk
*/
/* Using millis() or micros() for timeouts
For example,
uint32_t start = millis();
do {
// ...
}
} while (millis() - start < TIMEOUT);
There is no problem if the millis() counter wraps,
due to the properties of unsigned integer modulo arithmetic.
"A computation involving unsigned operands can never overflow,
because a result that cannot be represented by the resulting
unsigned integer type is reduced modulo the number that is
one greater than the largest value that can be represented
by the resulting type."
-- ISO/IEC 9899:1999 (E) §6.2.5/9
In other words, a uint32_t will wrap at 0 and UINT_MAX.
So, for example,
0x00000000 - 0xFFFFFFFF = 0x00000001
Remember that an unsigned integer will never be negative!
Be careful with comparisons. In the example above,
if 0x00000000 is the result of the counter wrapping,
and 0xFFFFFFFF is the start timestamp, a comparison like
millis() - start < TIMEOUT
is OK, but the following code is problematic if, say, the first call
to millis() returns 0xFFFFFFF0 and the second
call to millis() returns 0xFFFFFFFF:
uint32_t end = millis() + 100; // end = 0x00000054
while (millis() < end) // while (0xFFFFFFFF < 0x00000054)
*/
#pragma once
#include <stdint.h>
//
#include "pico.h"
#include "pico/stdlib.h"
#ifdef __cplusplus
extern "C" {
#endif
static inline uint32_t millis() {
__compiler_memory_barrier();
return time_us_64() / 1000;
__compiler_memory_barrier();
}
static inline void delay_ms(uint32_t ulTime_ms) {
sleep_ms(ulTime_ms);
}
static inline uint64_t micros() {
__compiler_memory_barrier();
return to_us_since_boot(get_absolute_time());
__compiler_memory_barrier();
}
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

View File

@ -0,0 +1,85 @@
#include "hw_config.h"
#include "sd_card.h"
//
#include "dma_interrupts.h"
static void dma_irq_handler(const uint DMA_IRQ_num, io_rw_32 *dma_hw_ints_p) {
// Iterate through all of the SD cards
for (size_t i = 0; i < sd_get_num(); ++i) {
sd_card_t *sd_card_p = sd_get_by_num(i);
if (!sd_card_p)
continue;
uint irq_num = 0, channel = 0;
if (SD_IF_SDIO == sd_card_p->type) {
irq_num = sd_card_p->sdio_if_p->DMA_IRQ_num;
channel = sd_card_p->sdio_if_p->state.SDIO_DMA_CHB;
}
// Is this channel requesting interrupt?
if (irq_num == DMA_IRQ_num && (*dma_hw_ints_p & (1 << channel))) {
*dma_hw_ints_p = 1 << channel; // Clear it.
if (SD_IF_SDIO == sd_card_p->type) {
sdio_irq_handler(sd_card_p);
}
}
}
}
static void __not_in_flash_func(dma_irq_handler_0)() {
dma_irq_handler(DMA_IRQ_0, &dma_hw->ints0);
}
static void __not_in_flash_func(dma_irq_handler_1)() {
dma_irq_handler(DMA_IRQ_1, &dma_hw->ints1);
}
/* Adding the interrupt request handler
Only add it once.
Otherwise, space is wasted in irq_add_shared_handler's table.
Also, each core maintains its own table of interrupt vectors,
and we don't want both cores to call the IRQ handler.
*/
typedef struct ih_added_rec_t {
uint num;
bool added;
} ih_added_rec_t;
static ih_added_rec_t ih_added_recs[] = {
{DMA_IRQ_0, false},
{DMA_IRQ_1, false}};
static bool is_handler_added(const uint num) {
for (size_t i = 0; i < count_of(ih_added_recs); ++i)
if (num == ih_added_recs[i].num)
return ih_added_recs[i].added;
return false;
}
static void mark_handler_added(const uint num) {
size_t i;
for (i = 0; i < count_of(ih_added_recs); ++i)
if (num == ih_added_recs[i].num) {
ih_added_recs[i].added = true;
break;
}
myASSERT(i < count_of(ih_added_recs));
}
void dma_irq_add_handler(const uint num, bool exclusive) {
if (!is_handler_added(num)) {
static void (*irq_handler)();
switch (num) {
case DMA_IRQ_0:
irq_handler = dma_irq_handler_0;
break;
case DMA_IRQ_1:
irq_handler = dma_irq_handler_1;
break;
default:
myASSERT(false);
}
if (exclusive) {
irq_set_exclusive_handler(num, *irq_handler);
} else {
irq_add_shared_handler(
num, *irq_handler,
PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
}
irq_set_enabled(num, true); // Enable IRQ in NVIC
mark_handler_added(num);
}
}

View File

@ -0,0 +1,14 @@
#pragma once
#include "pico.h"
#ifdef __cplusplus
extern "C" {
#endif
void dma_irq_add_handler(const uint num, bool exclusive);
#ifdef __cplusplus
}
#endif

155
lib/sd_driver/f_util.c Normal file
View File

@ -0,0 +1,155 @@
/* f_util.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 <assert.h>
#include <stdio.h>
//
#include "ff.h"
const char *FRESULT_str(FRESULT i) {
switch (i) {
case FR_OK:
return "Succeeded";
case FR_DISK_ERR:
return "A hard error occurred in the low level disk I/O layer";
case FR_INT_ERR:
return "Assertion failed";
case FR_NOT_READY:
return "The physical drive cannot work";
case FR_NO_FILE:
return "Could not find the file";
case FR_NO_PATH:
return "Could not find the path";
case FR_INVALID_NAME:
return "The path name format is invalid";
case FR_DENIED:
return "Access denied due to prohibited access or directory full";
case FR_EXIST:
return "Access denied due to prohibited access (exists)";
case FR_INVALID_OBJECT:
return "The file/directory object is invalid";
case FR_WRITE_PROTECTED:
return "The physical drive is write protected";
case FR_INVALID_DRIVE:
return "The logical drive number is invalid";
case FR_NOT_ENABLED:
return "The volume has no work area (mount)";
case FR_NO_FILESYSTEM:
return "There is no valid FAT volume";
case FR_MKFS_ABORTED:
return "The f_mkfs() aborted due to any problem";
case FR_TIMEOUT:
return "Could not get a grant to access the volume within defined "
"period";
case FR_LOCKED:
return "The operation is rejected according to the file sharing "
"policy";
case FR_NOT_ENOUGH_CORE:
return "LFN working buffer could not be allocated";
case FR_TOO_MANY_OPEN_FILES:
return "Number of open files > FF_FS_LOCK";
case FR_INVALID_PARAMETER:
return "Given parameter is invalid";
default:
return "Unknown";
}
}
FRESULT delete_node (
TCHAR* path, /* Path name buffer with the sub-directory to delete */
UINT sz_buff, /* Size of path name buffer (items) */
FILINFO* fno /* Name read buffer */
)
{
UINT i, j;
FRESULT fr;
DIR dir;
fr = f_opendir(&dir, path); /* Open the sub-directory to make it empty */
if (fr != FR_OK) return fr;
for (i = 0; path[i]; i++) ; /* Get current path length */
path[i++] = '/';
for (;;) {
fr = f_readdir(&dir, fno); /* Get a directory item */
if (fr != FR_OK || !fno->fname[0]) break; /* End of directory? */
j = 0;
do { /* Make a path name */
if (i + j >= sz_buff) { /* Buffer over flow? */
fr = 100; break; /* Fails with 100 when buffer overflow */
}
path[i + j] = fno->fname[j];
} while (fno->fname[j++]);
if (fno->fattrib & AM_DIR) { /* Item is a sub-directory */
fr = delete_node(path, sz_buff, fno);
} else { /* Item is a file */
fr = f_unlink(path);
}
if (fr != FR_OK) break;
}
path[--i] = 0; /* Restore the path name */
f_closedir(&dir);
if (fr == FR_OK) fr = f_unlink(path); /* Delete the empty sub-directory */
return fr;
}
void ls(const char *dir) {
char cwdbuf[FF_LFN_BUF] = {0};
FRESULT fr; /* Return value */
char const *p_dir;
if (dir[0]) {
p_dir = dir;
} else {
fr = f_getcwd(cwdbuf, sizeof cwdbuf);
if (FR_OK != fr) {
printf("f_getcwd error: %s (%d)\n", FRESULT_str(fr), fr);
return;
}
p_dir = cwdbuf;
}
printf("Directory Listing: %s\n", p_dir);
DIR dj = {}; /* Directory object */
FILINFO fno = {}; /* File information */
assert(p_dir);
fr = f_findfirst(&dj, &fno, p_dir, "*");
if (FR_OK != fr) {
printf("f_findfirst error: %s (%d)\n", FRESULT_str(fr), fr);
return;
}
while (fr == FR_OK && fno.fname[0]) { /* Repeat while an item is found */
/* Create a string that includes the file name, the file size and the
attributes string. */
const char *pcWritableFile = "writable file",
*pcReadOnlyFile = "read only file",
*pcDirectory = "directory";
const char *pcAttrib;
/* Point pcAttrib to a string that describes the file. */
if (fno.fattrib & AM_DIR) {
pcAttrib = pcDirectory;
} else if (fno.fattrib & AM_RDO) {
pcAttrib = pcReadOnlyFile;
} else {
pcAttrib = pcWritableFile;
}
/* Create a string that includes the file name, the file size and the
attributes string. */
printf("%s [%s] [size=%llu]\n", fno.fname, pcAttrib, fno.fsize);
fr = f_findnext(&dj, &fno); /* Search for next item */
}
f_closedir(&dj);
}

32
lib/sd_driver/f_util.h Executable file
View File

@ -0,0 +1,32 @@
/* f_util.h
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.
*/
#pragma once
#include "ff.h"
#ifdef __cplusplus
extern "C" {
#endif
const char *FRESULT_str(FRESULT i);
FRESULT delete_node (
TCHAR* path, /* Path name buffer with the sub-directory to delete */
UINT sz_buff, /* Size of path name buffer (items) */
FILINFO* fno /* Name read buffer */
);
void ls(const char *dir);
#ifdef __cplusplus
}
#endif

44
lib/sd_driver/hw_config.h Normal file
View File

@ -0,0 +1,44 @@
/* hw_config.h
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.
*/
#pragma once
#include <stddef.h>
#include "sd_card.h"
#ifdef __cplusplus
extern "C" {
#endif
/* FatFS supports up to 10 logical drives. By default, each logical
drive is associated with the physical drive in same drive number. */
/* Return the number of physical drives (SD card sockets) in the configuration */
size_t sd_get_num();
/* Return a pointer to the SD card "object" at the given physical drive number.
(See http://elm-chan.org/fsw/ff/doc/filename.html#vol.)
Parameter `num` must be less than sd_get_num(). */
sd_card_t* sd_get_by_num(size_t num);
/* See http://elm-chan.org/fsw/ff/doc/config.html#str_volume_id */
#if FF_STR_VOLUME_ID
extern const char* VolumeStr[FF_VOLUMES]; /* User defined volume ID */
#endif
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

223
lib/sd_driver/my_debug.c Normal file
View File

@ -0,0 +1,223 @@
/* my_debug.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 <stdarg.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
//
#include "pico/stdlib.h"
#include "hardware/sync.h"
//
#include "crash.h"
//
#include "my_debug.h"
/* Function Attribute ((weak))
The weak attribute causes a declaration of an external symbol to be emitted as a weak symbol
rather than a global. This is primarily useful in defining library functions that can be
overridden in user code, though it can also be used with non-function declarations. The
overriding symbol must have the same type as the weak symbol.
https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
You can override these functions in your application to redirect "stdout"-type messages.
*/
/* Single string output callbacks */
void __attribute__((weak)) put_out_error_message(const char *s) { (void)s; }
void __attribute__((weak)) put_out_info_message(const char *s) { (void)s; }
void __attribute__((weak)) put_out_debug_message(const char *s) { (void)s; }
/* "printf"-style output callbacks */
#if defined(USE_PRINTF) && USE_PRINTF
int __attribute__((weak))
error_message_printf(const char *func, int line,
const char *fmt, ...)
{
printf("%s:%d: ", func, line);
va_list args;
va_start(args, fmt);
int cw = vprintf(fmt, args);
va_end(args);
stdio_flush();
return cw;
}
int __attribute__((weak)) error_message_printf_plain(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int cw = vprintf(fmt, args);
va_end(args);
stdio_flush();
return cw;
}
int __attribute__((weak)) info_message_printf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int cw = vprintf(fmt, args);
va_end(args);
return cw;
}
int __attribute__((weak))
debug_message_printf(const char *func, int line,
const char *fmt, ...)
{
(void) func;
(void) line;
va_list args;
va_start(args, fmt);
int cw = vprintf(fmt, args);
va_end(args);
stdio_flush();
return cw;
}
#else
/* These will truncate at 256 bytes. You can tell by checking the return code. */
int __attribute__((weak))
error_message_printf(const char *func, int line,
const char *fmt, ...)
{
(void) func;
(void) line;
char buf[256] = {0};
va_list args;
va_start(args, fmt);
int cw = vsnprintf(buf, sizeof(buf), fmt, args);
put_out_error_message(buf);
va_end(args);
return cw;
}
int __attribute__((weak)) error_message_printf_plain(const char *fmt, ...) {
char buf[256] = {0};
va_list args;
va_start(args, fmt);
int cw = vsnprintf(buf, sizeof(buf), fmt, args);
put_out_info_message(buf);
va_end(args);
return cw;
}
int __attribute__((weak)) info_message_printf(const char *fmt, ...) {
char buf[256] = {0};
va_list args;
va_start(args, fmt);
int cw = vsnprintf(buf, sizeof(buf), fmt, args);
put_out_info_message(buf);
va_end(args);
return cw;
}
int __attribute__((weak))
debug_message_printf(const char *func, int line,
const char *fmt, ...)
{
(void) func;
(void) line;
char buf[256] = {0};
va_list args;
va_start(args, fmt);
int cw = vsnprintf(buf, sizeof(buf), fmt, args);
put_out_debug_message(buf);
va_end(args);
return cw;
}
#endif
void __attribute__((weak)) my_assert_func(const char *file, int line, const char *func,
const char *pred) {
error_message_printf_plain("assertion \"%s\" failed: file \"%s\", line %d, function: %s\n",
pred, file, line, func);
(void)save_and_disable_interrupts(); /* Disable global interrupts. */
capture_assert(file, line, func, pred);
}
void assert_case_not_func(const char *file, int line, const char *func, int v) {
char pred[128];
snprintf(pred, sizeof pred, "case not %d", v);
my_assert_func(file, line, func, pred);
}
void assert_case_is(const char *file, int line, const char *func, int v, int expected) {
char pred[128];
snprintf(pred, sizeof pred, "%d is %d", v, expected);
my_assert_func(file, line, func, pred);
}
void dump8buf(char *buf, size_t buf_sz, uint8_t *pbytes, size_t nbytes) {
int n = 0;
for (size_t byte_ix = 0; byte_ix < nbytes; ++byte_ix) {
for (size_t col = 0; col < 32 && byte_ix < nbytes; ++col, ++byte_ix) {
n += snprintf(buf + n, buf_sz - n, "%02hhx ", pbytes[byte_ix]);
myASSERT(0 < n && n < (int)buf_sz);
}
n += snprintf(buf + n, buf_sz - n, "\n");
myASSERT(0 < n && n < (int)buf_sz);
}
}
void hexdump_8(const char *s, const uint8_t *pbytes, size_t nbytes) {
IMSG_PRINTF("\n%s(%s, 0x%p, %zu)\n", __FUNCTION__, s, pbytes, nbytes);
stdio_flush();
size_t col = 0;
for (size_t byte_ix = 0; byte_ix < nbytes; ++byte_ix) {
IMSG_PRINTF("%02hhx ", pbytes[byte_ix]);
if (++col > 31) {
IMSG_PRINTF("\n");
col = 0;
}
stdio_flush();
}
}
// nwords is size in WORDS!
void hexdump_32(const char *s, const uint32_t *pwords, size_t nwords) {
IMSG_PRINTF("\n%s(%s, 0x%p, %zu)\n", __FUNCTION__, s, pwords, nwords);
stdio_flush();
size_t col = 0;
for (size_t word_ix = 0; word_ix < nwords; ++word_ix) {
IMSG_PRINTF("%08lx ", pwords[word_ix]);
if (++col > 7) {
IMSG_PRINTF("\n");
col = 0;
}
stdio_flush();
}
}
// nwords is size in bytes
bool compare_buffers_8(const char *s0, const uint8_t *pbytes0, const char *s1,
const uint8_t *pbytes1, const size_t nbytes) {
/* Verify the data. */
if (0 != memcmp(pbytes0, pbytes1, nbytes)) {
hexdump_8(s0, pbytes0, nbytes);
hexdump_8(s1, pbytes1, nbytes);
return false;
}
return true;
}
// nwords is size in WORDS!
bool compare_buffers_32(const char *s0, const uint32_t *pwords0, const char *s1,
const uint32_t *pwords1, const size_t nwords) {
/* Verify the data. */
if (0 != memcmp(pwords0, pwords1, nwords * sizeof(uint32_t))) {
hexdump_32(s0, pwords0, nwords);
hexdump_32(s1, pwords1, nwords);
return false;
}
return true;
}
/* [] END OF FILE */

140
lib/sd_driver/my_debug.h Executable file
View File

@ -0,0 +1,140 @@
/* my_debug.h
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.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
/* USE_PRINTF
If this is defined and not zero,
these message output functions will use the Pico SDK's stdout.
*/
/* USE_DBG_PRINTF
If this is not defined or is zero
DBG_PRINTF statements will be effectively stripped from the code.
*/
/* Single string output callbacks: send message output somewhere.
To use these, do not define the USE_PRINTF compile definition,
and override these "weak" functions by strongly implementing them in user code.
The weak implementations do nothing.
*/
void put_out_error_message(const char *s);
void put_out_info_message(const char *s);
void put_out_debug_message(const char *s);
// https://gcc.gnu.org/onlinedocs/gcc-3.2.3/cpp/Variadic-Macros.html
int error_message_printf(const char *func, int line, const char *fmt, ...)
__attribute__((format(__printf__, 3, 4)));
#ifndef EMSG_PRINTF
#define EMSG_PRINTF(fmt, ...) error_message_printf(__func__, __LINE__, fmt, ##__VA_ARGS__)
#endif
int error_message_printf_plain(const char *fmt, ...) __attribute__((format(__printf__, 1, 2)));
int debug_message_printf(const char *func, int line, const char *fmt, ...)
__attribute__((format(__printf__, 3, 4)));
#ifndef DBG_PRINTF
# if defined(USE_DBG_PRINTF) && USE_DBG_PRINTF // && !defined(NDEBUG)
# define DBG_PRINTF(fmt, ...) debug_message_printf(__func__, __LINE__, fmt, ##__VA_ARGS__)
# else
# define DBG_PRINTF(fmt, ...) (void)0
# endif
#endif
int info_message_printf(const char *fmt, ...) __attribute__((format(__printf__, 1, 2)));
#ifndef IMSG_PRINTF
#define IMSG_PRINTF(fmt, ...) info_message_printf(fmt, ##__VA_ARGS__)
#endif
void lock_printf();
void unlock_printf();
void my_assert_func(const char *file, int line, const char *func, const char *pred)
__attribute__((noreturn));
#ifdef NDEBUG /* required by ANSI standard */
# define myASSERT(__e) ((void)0)
#else
# define myASSERT(__e) ((__e) ? (void)0 : my_assert_func(__FILE__, __LINE__, __func__, #__e))
#endif
void assert_always_func(const char *file, int line, const char *func, const char *pred)
__attribute__((noreturn));
#define ASSERT_ALWAYS(__e) \
((__e) ? (void)0 : my_assert_func(__FILE__, __LINE__, __func__, #__e))
void assert_case_is(const char *file, int line, const char *func, int v, int expected)
__attribute__((noreturn));
#define ASSERT_CASE_IS(__v, __e) \
((__v == __e) ? (void)0 : assert_case_is(__FILE__, __LINE__, __func__, __v, __e))
void assert_case_not_func(const char *file, int line, const char *func, int v)
__attribute__((noreturn));
#define ASSERT_CASE_NOT(__v) (assert_case_not_func(__FILE__, __LINE__, __func__, __v))
#ifdef NDEBUG /* required by ANSI standard */
#define DBG_ASSERT_CASE_NOT(__e) ((void)0)
#else
#define DBG_ASSERT_CASE_NOT(__v) (assert_case_not_func(__FILE__, __LINE__, __func__, __v))
#endif
static inline void dump_bytes(size_t num, uint8_t bytes[]) {
(void)num;
(void)bytes;
DBG_PRINTF(" ");
for (size_t j = 0; j < 16; ++j) {
DBG_PRINTF("%02hhx", j);
if (j < 15)
DBG_PRINTF(" ");
else {
DBG_PRINTF("\n");
}
}
for (size_t i = 0; i < num; i += 16) {
DBG_PRINTF("%04x ", i);
for (size_t j = 0; j < 16 && i + j < num; ++j) {
DBG_PRINTF("%02hhx", bytes[i + j]);
if (j < 15)
DBG_PRINTF(" ");
else {
DBG_PRINTF("\n");
}
}
}
DBG_PRINTF("\n");
}
void dump8buf(char *buf, size_t buf_sz, uint8_t *pbytes, size_t nbytes);
void hexdump_8(const char *s, const uint8_t *pbytes, size_t nbytes);
bool compare_buffers_8(const char *s0, const uint8_t *pbytes0, const char *s1,
const uint8_t *pbytes1, const size_t nbytes);
// sz is size in BYTES!
void hexdump_32(const char *s, const uint32_t *pwords, size_t nwords);
// sz is size in BYTES!
bool compare_buffers_32(const char *s0, const uint32_t *pwords0, const char *s1,
const uint32_t *pwords1, const size_t nwords);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

159
lib/sd_driver/my_rtc.c Normal file
View File

@ -0,0 +1,159 @@
/* my_rtc.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 <assert.h>
#include <stdbool.h>
#include <time.h>
//
#include "pico/aon_timer.h"
#include "pico/stdio.h"
#include "pico/stdlib.h"
#include "pico/util/datetime.h"
#if HAS_RP2040_RTC
# include "hardware/rtc.h"
#endif
//
#include "crc.h"
#include "ff.h"
//
#include "my_rtc.h"
time_t epochtime;
// Make an attempt to save a recent time stamp across reset:
typedef struct rtc_save {
uint32_t signature;
struct timespec ts;
char checksum; // last, not included in checksum
} rtc_save_t;
static rtc_save_t rtc_save __attribute__((section(".uninitialized_data")));
static bool get_time(struct timespec *ts) {
if (!aon_timer_is_running()) return false;
aon_timer_get_time(ts);
return true;
}
/**
* @brief Update the epochtime variable from the always-on timer
*
* If the always-on timer is running, copy its current value to the epochtime variable.
* Also, copy the current always-on timer value to the rtc_save structure and
* calculate a checksum of the structure.
*/
static void update_epochtime() {
bool ok = get_time(&rtc_save.ts);
if (!ok) return;
// Set the signature to the magic number
rtc_save.signature = 0xBABEBABE;
// Calculate the checksum of the structure
rtc_save.checksum = crc7((uint8_t *)&rtc_save, offsetof(rtc_save_t, checksum));
// Copy the seconds part of the always-on timer to the epochtime variable
epochtime = rtc_save.ts.tv_sec;
}
/**
* @brief Get the current time in seconds since the Epoch.
*
* @param[in] pxTime If not NULL, the current time is copied here.
* @return The current time in seconds since the Epoch.
*/
time_t time(time_t *pxTime) {
update_epochtime();
if (pxTime) {
*pxTime = epochtime;
}
return epochtime;
}
/**
* @brief Initialize the always-on timer and save its value to the rtc_save structure
*
* If the always-on timer is already running, this function does nothing.
* Otherwise, it initializes the RTC if it is available and checks if the saved
* time is valid. If the saved time is valid, it sets the always-on timer to the saved
* value.
*/
void time_init() {
// If the always-on timer is already running, return immediately
if (aon_timer_is_running()) return;
// Initialize the RTC if it is available
#if HAS_RP2040_RTC
rtc_init();
#endif
// Check if the saved time is valid
char xor_checksum = crc7((uint8_t *)&rtc_save, offsetof(rtc_save_t, checksum));
bool ok = rtc_save.signature == 0xBABEBABE && rtc_save.checksum == xor_checksum;
// If the saved time is valid, set the always-on timer
if (ok) aon_timer_set_time(&rtc_save.ts);
}
/**
* @brief Get the current time in the FAT time format
*
* The FAT file system uses a specific date and time format that is different
* from the one used by the standard C library. This function converts the
* current time to the FAT time format.
*
* @return The current time in the FAT time format (DWORD)
*/
DWORD get_fattime(void) {
struct timespec ts;
bool ok = get_time(&ts);
if (!ok) return 0;
struct tm t;
localtime_r(&ts.tv_sec, &t);
DWORD fattime = 0;
// bit31:25
// Year origin from the 1980 (0..127, e.g. 37 for 2017)
// tm_year int years since 1900
int yr = t.tm_year + 1900 - 1980;
assert(yr >= 0 && yr <= 127);
fattime |= (0b01111111 & yr) << 25;
// bit24:21
// Month (1..12)
// tm_mon int months since January 0-11
uint8_t mo = t.tm_mon + 1;
assert(mo >= 1 && mo <= 12);
fattime |= (0b00001111 & mo) << 21;
// bit20:16
// Day of the month (1..31)
// tm_mday int day of the month 1-31
uint8_t da = t.tm_mday;
assert(da >= 1 && da <= 31);
fattime |= (0b00011111 & da) << 16;
// bit15:11
// Hour (0..23)
// tm_hour int hours since midnight 0-23
uint8_t hr = t.tm_hour;
assert(hr <= 23);
fattime |= (0b00011111 & hr) << 11;
// bit10:5
// Minute (0..59)
// tm_min int minutes after the hour 0-59
uint8_t mi = t.tm_min;
assert(mi <= 59);
fattime |= (0b00111111 & mi) << 5;
// bit4:0
// Second / 2 (0..29, e.g. 25 for 50)
// tm_sec int seconds after the minute 0-61*
uint8_t sd = t.tm_sec / 2;
assert(sd <= 30); // The extra range is to accommodate for leap seconds
fattime |= (0b00011111 & sd);
return fattime;
}

27
lib/sd_driver/my_rtc.h Executable file
View File

@ -0,0 +1,27 @@
/* my_rtc.h
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.
*/
#pragma once
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
extern time_t epochtime;
void time_init();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,124 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
#define CLKDIV 4
#define SDIO_CLK_PIN_D0_OFFSET 30
// ------------ //
// sdio_cmd_clk //
// ------------ //
#define sdio_cmd_clk_wrap_target 0
#define sdio_cmd_clk_wrap 17
static const uint16_t sdio_cmd_clk_program_instructions[] = {
// .wrap_target
0xb1e3, // 0: mov osr, null side 1 [1]
0xa14d, // 1: mov y, !status side 0 [1]
0x1161, // 2: jmp !y, 1 side 1 [1]
0x6160, // 3: out null, 32 side 0 [1]
0x7128, // 4: out x, 8 side 1 [1]
0xe101, // 5: set pins, 1 side 0 [1]
0xf181, // 6: set pindirs, 1 side 1 [1]
0x6101, // 7: out pins, 1 side 0 [1]
0x1147, // 8: jmp x--, 7 side 1 [1]
0xe180, // 9: set pindirs, 0 side 0 [1]
0x7128, // 10: out x, 8 side 1 [1]
0xa142, // 11: nop side 0 [1]
0x1131, // 12: jmp !x, 17 side 1 [1]
0xa142, // 13: nop side 0 [1]
0x11cd, // 14: jmp pin, 13 side 1 [1]
0x4101, // 15: in pins, 1 side 0 [1]
0x114f, // 16: jmp x--, 15 side 1 [1]
0x8120, // 17: push block side 0 [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program sdio_cmd_clk_program = {
.instructions = sdio_cmd_clk_program_instructions,
.length = 18,
.origin = -1,
};
static inline pio_sm_config sdio_cmd_clk_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + sdio_cmd_clk_wrap_target, offset + sdio_cmd_clk_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#endif
// ------------ //
// sdio_data_rx //
// ------------ //
#define sdio_data_rx_wrap_target 0
#define sdio_data_rx_wrap 4
static const uint16_t sdio_data_rx_program_instructions[] = {
// .wrap_target
0xa022, // 0: mov x, y
0x2020, // 1: wait 0 pin, 0
0x23be, // 2: wait 1 pin, 30 [3]
0x4204, // 3: in pins, 4 [2]
0x0043, // 4: jmp x--, 3
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program sdio_data_rx_program = {
.instructions = sdio_data_rx_program_instructions,
.length = 5,
.origin = -1,
};
static inline pio_sm_config sdio_data_rx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + sdio_data_rx_wrap_target, offset + sdio_data_rx_wrap);
return c;
}
#endif
// ------------ //
// sdio_data_tx //
// ------------ //
#define sdio_data_tx_wrap_target 5
#define sdio_data_tx_wrap 8
static const uint16_t sdio_data_tx_program_instructions[] = {
0x203e, // 0: wait 0 pin, 30
0x24be, // 1: wait 1 pin, 30 [4]
0x6104, // 2: out pins, 4 [1]
0x0142, // 3: jmp x--, 2 [1]
0xe180, // 4: set pindirs, 0 [1]
// .wrap_target
0x4101, // 5: in pins, 1 [1]
0x0185, // 6: jmp y--, 5 [1]
0x21a0, // 7: wait 1 pin, 0 [1]
0x8120, // 8: push block [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program sdio_data_tx_program = {
.instructions = sdio_data_tx_program_instructions,
.length = 9,
.origin = -1,
};
static inline pio_sm_config sdio_data_tx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + sdio_data_tx_wrap_target, offset + sdio_data_tx_wrap);
return c;
}
#endif

386
lib/sd_driver/sd_card.c Executable file
View File

@ -0,0 +1,386 @@
/* sd_card.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.
*/
/* Standard includes. */
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
//
#include "pico/mutex.h"
//
#include "SDIO/SdioCard.h"
#include "SPI/sd_card_spi.h"
#include "hw_config.h" // Hardware Configuration of the SPI and SD Card "objects"
#include "my_debug.h"
#include "sd_card_constants.h"
#include "sd_regs.h"
#include "sd_timeouts.h"
#include "util.h"
//
#include "diskio.h" /* Declarations of disk functions */ // Needed for STA_NOINIT, ...
//
#include "sd_card.h"
#define TRACE_PRINTF(fmt, args...)
// #define TRACE_PRINTF printf
#ifdef NDEBUG
# pragma GCC diagnostic ignored "-Wunused-variable"
#endif
static bool driver_initialized;
// An SD card can only do one thing at a time.
void sd_lock(sd_card_t *sd_card_p) {
myASSERT(mutex_is_initialized(&sd_card_p->state.mutex));
mutex_enter_blocking(&sd_card_p->state.mutex);
}
void sd_unlock(sd_card_t *sd_card_p) {
myASSERT(mutex_is_initialized(&sd_card_p->state.mutex));
mutex_exit(&sd_card_p->state.mutex);
}
bool sd_is_locked(sd_card_t *sd_card_p) {
myASSERT(mutex_is_initialized(&sd_card_p->state.mutex));
uint32_t owner_out;
return !mutex_try_enter(&sd_card_p->state.mutex, &owner_out);
}
sd_card_t *sd_get_by_drive_prefix(const char *const drive_prefix) {
// Numeric drive number is always valid
if (2 == strlen(drive_prefix) && isdigit((unsigned char)drive_prefix[0]) &&
':' == drive_prefix[1])
return sd_get_by_num(atoi(drive_prefix));
#if FF_STR_VOLUME_ID
for (size_t i = 0; i < sd_get_num(); ++i) {
// Ignore '/', trailing ':'
if (strstr(drive_prefix, VolumeStr[i])) return sd_get_by_num(i);
}
EMSG_PRINTF("%s: unknown drive prefix %s\n", __func__, drive_prefix);
#endif
return NULL;
}
/* Return non-zero if the SD-card is present. */
bool sd_card_detect(sd_card_t *sd_card_p) {
TRACE_PRINTF("> %s\r\n", __FUNCTION__);
if (!sd_card_p->use_card_detect) {
sd_card_p->state.m_Status &= ~STA_NODISK;
return true;
}
/*!< Check GPIO to detect SD */
if (gpio_get(sd_card_p->card_detect_gpio) == sd_card_p->card_detected_true) {
// The socket is now occupied
sd_card_p->state.m_Status &= ~STA_NODISK;
TRACE_PRINTF("SD card detected!\r\n");
return true;
} else {
// The socket is now empty
sd_card_p->state.m_Status |= (STA_NODISK | STA_NOINIT);
sd_card_p->state.card_type = SDCARD_NONE;
EMSG_PRINTF("No SD card detected!\r\n");
return false;
}
}
void sd_set_drive_prefix(sd_card_t *sd_card_p, size_t phy_drv_num) {
#if FF_STR_VOLUME_ID == 0
int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix,
"%d:", phy_drv_num);
#elif FF_STR_VOLUME_ID == 1 /* Arbitrary string is enabled */
// Add ':'
int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix,
"%s:", VolumeStr[phy_drv_num]);
#elif FF_STR_VOLUME_ID == 2 /* Unix style drive prefix */
// Add '/'
int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix,
"/%s", VolumeStr[phy_drv_num]);
#else
#error "Unknown FF_STR_VOLUME_ID"
#endif
// Notice that only when this returned value is non-negative and less than n,
// the string has been completely written.
myASSERT(0 <= rc && (size_t)rc < sizeof sd_card_p->state.drive_prefix);
}
char const *sd_get_drive_prefix(sd_card_t *sd_card_p) {
myASSERT(driver_initialized);
myASSERT(sd_card_p);
if (!sd_card_p) return "";
return sd_card_p->state.drive_prefix;
}
bool sd_init_driver() {
auto_init_mutex(initialized_mutex);
mutex_enter_blocking(&initialized_mutex);
bool ok = true;
if (!driver_initialized) {
myASSERT(sd_get_num());
for (size_t i = 0; i < sd_get_num(); ++i) {
sd_card_t *sd_card_p = sd_get_by_num(i);
if (!sd_card_p) continue;
myASSERT(sd_card_p->type);
if (!mutex_is_initialized(&sd_card_p->state.mutex))
mutex_init(&sd_card_p->state.mutex);
sd_lock(sd_card_p);
sd_card_p->state.m_Status = STA_NOINIT;
sd_set_drive_prefix(sd_card_p, i);
// Set up Card Detect
if (sd_card_p->use_card_detect) {
if (sd_card_p->card_detect_use_pull) {
if (sd_card_p->card_detect_pull_hi) {
gpio_pull_up(sd_card_p->card_detect_gpio);
} else {
gpio_pull_down(sd_card_p->card_detect_gpio);
}
}
gpio_init(sd_card_p->card_detect_gpio);
}
switch (sd_card_p->type) {
case SD_IF_NONE:
myASSERT(false);
break;
case SD_IF_SPI:
myASSERT(sd_card_p->spi_if_p); // Must have an interface object
myASSERT(sd_card_p->spi_if_p->spi);
sd_spi_ctor(sd_card_p);
if (!my_spi_init(sd_card_p->spi_if_p->spi)) {
ok = false;
}
/* At power up the SD card CD/DAT3 / CS line has a 50KOhm pull up enabled
* in the card. This resistor serves two functions Card detection and Mode
* Selection. For Mode Selection, the host can drive the line high or let it
* be pulled high to select SD mode. If the host wants to select SPI mode it
* should drive the line low.
*
* There is an important thing needs to be considered that the MMC/SDC is
* initially NOT the SPI device. Some bus activity to access another SPI
* device can cause a bus conflict due to an accidental response of the
* MMC/SDC. Therefore the MMC/SDC should be initialized to put it into the
* SPI mode prior to access any other device attached to the same SPI bus.
*/
sd_go_idle_state(sd_card_p);
break;
case SD_IF_SDIO:
myASSERT(sd_card_p->sdio_if_p);
sd_sdio_ctor(sd_card_p);
break;
default:
myASSERT(false);
} // switch (sd_card_p->type)
sd_unlock(sd_card_p);
} // for
driver_initialized = true;
}
mutex_exit(&initialized_mutex);
return ok;
}
void cidDmp(sd_card_t *sd_card_p, printer_t printer) {
// +-----------------------+-------+-------+-----------+
// | Name | Field | Width | CID-slice |
// +-----------------------+-------+-------+-----------+
// | Manufacturer ID | MID | 8 | [127:120] | 15
(*printer)(
"\nManufacturer ID: "
"0x%x\n",
ext_bits16(sd_card_p->state.CID, 127, 120));
// | OEM/Application ID | OID | 16 | [119:104] | 14
(*printer)("OEM ID: ");
{
char buf[3];
ext_str(16, sd_card_p->state.CID, 119, 104, sizeof buf, buf);
(*printer)("%s", buf);
}
// | Product name | PNM | 40 | [103:64] | 12
(*printer)("Product: ");
{
char buf[6];
ext_str(16, sd_card_p->state.CID, 103, 64, sizeof buf, buf);
(*printer)("%s", buf);
}
// | Product revision | PRV | 8 | [63:56] | 7
(*printer)(
"\nRevision: "
"%d.%d\n",
ext_bits16(sd_card_p->state.CID, 63, 60), ext_bits16(sd_card_p->state.CID, 59, 56));
// | Product serial number | PSN | 32 | [55:24] | 6
// (*printer)("0x%lx\n", __builtin_bswap32(ext_bits16(sd_card_p->state.CID, 55, 24))
(*printer)(
"Serial number: "
"0x%lx\n",
ext_bits16(sd_card_p->state.CID, 55, 24));
// | reserved | -- | 4 | [23:20] | 2
// | Manufacturing date | MDT | 12 | [19:8] |
// The "m" field [11:8] is the month code. 1 = January.
// The "y" field [19:12] is the year code. 0 = 2000.
(*printer)(
"Manufacturing date: "
"%d/%d\n",
ext_bits16(sd_card_p->state.CID, 11, 8),
ext_bits16(sd_card_p->state.CID, 19, 12) + 2000);
(*printer)("\n");
// | CRC7 checksum | CRC | 7 | [7:1] | 0
// | not used, always 1- | 1 | [0:0] | |
// +-----------------------+-------+-------+-----------+
}
void csdDmp(sd_card_t *sd_card_p, printer_t printer) {
uint32_t c_size, c_size_mult, read_bl_len;
uint32_t block_len, mult, blocknr;
uint32_t hc_c_size;
uint64_t blocks = 0, capacity = 0;
bool erase_single_block_enable = 0;
uint8_t erase_sector_size = 0;
// csd_structure : CSD[127:126]
int csd_structure = ext_bits16(sd_card_p->state.CSD, 127, 126);
switch (csd_structure) {
case 0:
c_size = ext_bits16(sd_card_p->state.CSD, 73, 62); // c_size : CSD[73:62]
c_size_mult =
ext_bits16(sd_card_p->state.CSD, 49, 47); // c_size_mult : CSD[49:47]
read_bl_len =
ext_bits16(sd_card_p->state.CSD, 83, 80); // read_bl_len : CSD[83:80] - the
// *maximum* read block length
block_len = 1 << read_bl_len; // BLOCK_LEN = 2^READ_BL_LEN
mult = 1 << (c_size_mult + 2); // MULT = 2^C_SIZE_MULT+2 (C_SIZE_MULT < 8)
blocknr = (c_size + 1) * mult; // BLOCKNR = (C_SIZE+1) * MULT
capacity = (uint64_t)blocknr * block_len; // memory capacity = BLOCKNR * BLOCK_LEN
blocks = capacity / sd_block_size;
(*printer)("Standard Capacity: c_size: %" PRIu32 "\r\n", c_size);
(*printer)("Sectors: 0x%llx : %llu\r\n", blocks, blocks);
(*printer)("Capacity: 0x%llx : %llu MiB\r\n", capacity,
(capacity / (1024U * 1024U)));
break;
case 1:
hc_c_size =
ext_bits16(sd_card_p->state.CSD, 69, 48); // device size : C_SIZE : [69:48]
blocks = (hc_c_size + 1) << 10; // block count = C_SIZE+1) * 1K
// byte (512B is block size)
/* ERASE_BLK_EN
The ERASE_BLK_EN defines the granularity of the unit size of the data to be erased.
The erase operation can erase either one or multiple units of 512 bytes or one or
multiple units (or sectors) of SECTOR_SIZE. If ERASE_BLK_EN=0, the host can erase
one or multiple units of SECTOR_SIZE. If ERASE_BLK_EN=1 the host can erase one or
multiple units of 512 bytes.
*/
erase_single_block_enable = ext_bits16(sd_card_p->state.CSD, 46, 46);
/* SECTOR_SIZE
The size of an erasable sector. The content of this register is a 7-bit binary coded
value, defining the number of write blocks. The actual size is computed by
increasing this number by one. A value of zero means one write block, 127 means 128
write blocks.
*/
erase_sector_size = ext_bits16(sd_card_p->state.CSD, 45, 39) + 1;
(*printer)("SDHC/SDXC Card: hc_c_size: %" PRIu32 "\r\n", hc_c_size);
(*printer)("Sectors: %llu\r\n", blocks);
(*printer)("Capacity: %llu MiB (%llu MB)\r\n", blocks / 2048,
blocks * sd_block_size / 1000000);
(*printer)("ERASE_BLK_EN: %s\r\n", erase_single_block_enable
? "units of 512 bytes"
: "units of SECTOR_SIZE");
(*printer)("SECTOR_SIZE (size of an erasable sector): %d (%lu bytes)\r\n",
erase_sector_size,
(uint32_t)(erase_sector_size ? 512 : 1) * erase_sector_size);
break;
default:
(*printer)("CSD struct unsupported\r\n");
};
}
#define KB 1024
#define MB (1024 * 1024)
/* AU (Allocation Unit):
is a physical boundary of the card and consists of one or more blocks and its
size depends on each card. */
bool sd_allocation_unit(sd_card_t *sd_card_p, size_t *au_size_bytes_p) {
if (SD_IF_SPI == sd_card_p->type) return false; // SPI can't do full SD Status
uint8_t status[64] = {0};
bool ok = rp2040_sdio_get_sd_status(sd_card_p, status);
if (!ok) return false;
// 431:428 AU_SIZE
uint8_t au_size = ext_bits(64, status, 431, 428);
switch (au_size) {
// AU_SIZE Value Definition
case 0x0:
*au_size_bytes_p = 0;
break; // Not Defined
case 0x1:
*au_size_bytes_p = 16 * KB;
break;
case 0x2:
*au_size_bytes_p = 32 * KB;
break;
case 0x3:
*au_size_bytes_p = 64 * KB;
break;
case 0x4:
*au_size_bytes_p = 128 * KB;
break;
case 0x5:
*au_size_bytes_p = 256 * KB;
break;
case 0x6:
*au_size_bytes_p = 512 * KB;
break;
case 0x7:
*au_size_bytes_p = 1 * MB;
break;
case 0x8:
*au_size_bytes_p = 2 * MB;
break;
case 0x9:
*au_size_bytes_p = 4 * MB;
break;
case 0xA:
*au_size_bytes_p = 8 * MB;
break;
case 0xB:
*au_size_bytes_p = 12 * MB;
break;
case 0xC:
*au_size_bytes_p = 16 * MB;
break;
case 0xD:
*au_size_bytes_p = 24 * MB;
break;
case 0xE:
*au_size_bytes_p = 32 * MB;
break;
case 0xF:
*au_size_bytes_p = 64 * MB;
break;
default:
myASSERT(false);
}
return true;
}
/* [] END OF FILE */

163
lib/sd_driver/sd_card.h Normal file
View File

@ -0,0 +1,163 @@
/* sd_card.h
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.
*/
// Note: The model used here is one FatFS per SD card.
// Multiple partitions on a card are not supported.
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
//
#include <hardware/pio.h>
#include "hardware/gpio.h"
#include "pico/mutex.h"
//
#include "ff.h"
//
#include "SDIO/rp2040_sdio.h"
#include "SPI/my_spi.h"
#include "SPI/sd_card_spi.h"
#include "diskio.h"
#include "sd_card_constants.h"
#include "sd_regs.h"
#include "util.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum { SD_IF_NONE, SD_IF_SPI, SD_IF_SDIO } sd_if_t;
typedef struct sd_spi_if_state_t {
bool ongoing_mlt_blk_wrt;
uint32_t cont_sector_wrt;
uint32_t n_wrt_blks_reqd;
} sd_spi_if_state_t;
typedef struct sd_spi_if_t {
spi_t *spi;
// Slave select is here instead of in spi_t because multiple SDs can share an SPI.
uint ss_gpio; // Slave select for this SD card
// Drive strength levels for GPIO outputs:
// GPIO_DRIVE_STRENGTH_2MA
// GPIO_DRIVE_STRENGTH_4MA
// GPIO_DRIVE_STRENGTH_8MA
// GPIO_DRIVE_STRENGTH_12MA
bool set_drive_strength;
enum gpio_drive_strength ss_gpio_drive_strength;
sd_spi_if_state_t state;
} sd_spi_if_t;
typedef struct sd_sdio_if_t {
// See sd_driver\SDIO\rp2040_sdio.pio for SDIO_CLK_PIN_D0_OFFSET
uint CLK_gpio; // Must be (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32
uint CMD_gpio;
uint D0_gpio; // D0
uint D1_gpio; // Must be D0 + 1
uint D2_gpio; // Must be D0 + 2
uint D3_gpio; // Must be D0 + 3
PIO SDIO_PIO; // either pio0 or pio1
uint DMA_IRQ_num; // DMA_IRQ_0 or DMA_IRQ_1
bool use_exclusive_DMA_IRQ_handler;
uint baud_rate;
// Drive strength levels for GPIO outputs:
// GPIO_DRIVE_STRENGTH_2MA
// GPIO_DRIVE_STRENGTH_4MA
// GPIO_DRIVE_STRENGTH_8MA
// GPIO_DRIVE_STRENGTH_12MA
bool set_drive_strength;
enum gpio_drive_strength CLK_gpio_drive_strength;
enum gpio_drive_strength CMD_gpio_drive_strength;
enum gpio_drive_strength D0_gpio_drive_strength;
enum gpio_drive_strength D1_gpio_drive_strength;
enum gpio_drive_strength D2_gpio_drive_strength;
enum gpio_drive_strength D3_gpio_drive_strength;
/* The following fields are not part of the configuration.
They are state variables, and are dynamically assigned. */
sd_sdio_if_state_t state;
} sd_sdio_if_t;
typedef struct sd_card_state_t {
DSTATUS m_Status; // Card status
card_type_t card_type; // Assigned dynamically
CSD_t CSD; // Card-Specific Data register.
CID_t CID; // Card IDentification register
uint32_t sectors; // Assigned dynamically
mutex_t mutex;
FATFS fatfs;
bool mounted;
#if FF_STR_VOLUME_ID
char drive_prefix[32];
#else
char drive_prefix[4];
#endif
} sd_card_state_t;
typedef struct sd_card_t sd_card_t;
// "Class" representing SD Cards
struct sd_card_t {
sd_if_t type; // Interface type
union {
sd_spi_if_t *spi_if_p;
sd_sdio_if_t *sdio_if_p;
};
bool use_card_detect;
uint card_detect_gpio; // Card detect; ignored if !use_card_detect
uint card_detected_true; // Varies with card socket; ignored if !use_card_detect
bool card_detect_use_pull;
bool card_detect_pull_hi;
/* The following fields are state variables and not part of the configuration.
They are dynamically assigned. */
sd_card_state_t state;
DSTATUS (*init)(sd_card_t *sd_card_p);
void (*deinit)(sd_card_t *sd_card_p);
block_dev_err_t (*write_blocks)(sd_card_t *sd_card_p, const uint8_t *buffer,
uint32_t ulSectorNumber, uint32_t blockCnt);
block_dev_err_t (*read_blocks)(sd_card_t *sd_card_p, uint8_t *buffer,
uint32_t ulSectorNumber, uint32_t ulSectorCount);
block_dev_err_t (*sync)(sd_card_t *sd_card_p);
uint32_t (*get_num_sectors)(sd_card_t *sd_card_p);
// Useful when use_card_detect is false - call periodically to check for presence of SD card
// Returns true if and only if SD card was sensed on the bus
bool (*sd_test_com)(sd_card_t *sd_card_p);
};
void sd_lock(sd_card_t *sd_card_p);
void sd_unlock(sd_card_t *sd_card_p);
bool sd_is_locked(sd_card_t *sd_card_p);
bool sd_init_driver();
bool sd_card_detect(sd_card_t *sd_card_p);
void cidDmp(sd_card_t *sd_card_p, printer_t printer);
void csdDmp(sd_card_t *sd_card_p, printer_t printer);
bool sd_allocation_unit(sd_card_t *sd_card_p, size_t *au_size_bytes_p);
sd_card_t *sd_get_by_drive_prefix(const char *const name);
// sd_init_driver() must be called before this:
char const *sd_get_drive_prefix(sd_card_t *sd_card_p);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

View File

@ -0,0 +1,103 @@
/* sd_card_constants.h
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.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/*!< Block size supported for SD card is 512 bytes */
// Only HC block size is supported.
static const size_t sd_block_size = 512;
typedef enum {
SD_BLOCK_DEVICE_ERROR_NONE = 0,
SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK = 1 << 0, /*!< operation would block */
SD_BLOCK_DEVICE_ERROR_UNSUPPORTED = 1 << 1, /*!< unsupported operation */
SD_BLOCK_DEVICE_ERROR_PARAMETER = 1 << 2, /*!< invalid parameter */
SD_BLOCK_DEVICE_ERROR_NO_INIT = 1 << 3, /*!< uninitialized */
SD_BLOCK_DEVICE_ERROR_NO_DEVICE = 1 << 4, /*!< device is missing or not connected */
SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED = 1 << 5, /*!< write protected */
SD_BLOCK_DEVICE_ERROR_UNUSABLE = 1 << 6, /*!< unusable card */
SD_BLOCK_DEVICE_ERROR_NO_RESPONSE = 1 << 7, /*!< No response from device */
SD_BLOCK_DEVICE_ERROR_CRC = 1 << 8, /*!< CRC error */
SD_BLOCK_DEVICE_ERROR_ERASE = 1 << 9, /*!< Erase error: reset/sequence */
SD_BLOCK_DEVICE_ERROR_WRITE = 1 << 10 /*!< Write error: !SPI_DATA_ACCEPTED */
} block_dev_err_t;
/** Represents the different SD/MMC card types */
typedef enum {
SDCARD_NONE = 0, /**< No card is present */
SDCARD_V1 = 1, /**< v1.x Standard Capacity */
SDCARD_V2 = 2, /**< v2.x Standard capacity SD card */
SDCARD_V2HC = 3, /**< v2.x High capacity SD card */
CARD_UNKNOWN = 4 /**< Unknown or unsupported card */
} card_type_t;
/* On the wire, convert to hex and add 0x40 for transmitter bit.
e.g.: CMD17_READ_SINGLE_BLOCK: 17 = 0x11; 0x11 | 0x40 = 0x51 */
// Supported SD Card Commands
typedef enum { /* Number on wire in parens */
CMD_NOT_SUPPORTED = -1, /* Command not supported error */
CMD0_GO_IDLE_STATE = 0, /* Resets the SD Memory Card */
CMD1_SEND_OP_COND = 1, /* Sends host capacity support */
CMD2_ALL_SEND_CID = 2, /* Asks any card to send the CID. */
CMD3_SEND_RELATIVE_ADDR = 3, /* Ask the card to publish a new RCA. */
CMD6_SWITCH_FUNC = 6, /* Check and Switches card function */
CMD7_SELECT_CARD = 7, /* SELECT/DESELECT_CARD - toggles between the stand-by and transfer states. */
CMD8_SEND_IF_COND = 8, /* Supply voltage info */
CMD9_SEND_CSD = 9, /* Provides Card Specific data */
CMD10_SEND_CID = 10, /* Provides Card Identification */
CMD12_STOP_TRANSMISSION = 12, /* Forces the card to stop transmission */
CMD13_SEND_STATUS = 13, /* (0x4D) Card responds with status */
CMD16_SET_BLOCKLEN = 16, /* Length for SC card is set */
CMD17_READ_SINGLE_BLOCK = 17, /* (0x51) Read single block of data */
CMD18_READ_MULTIPLE_BLOCK = 18, /* (0x52) Continuously Card transfers data blocks to host
until interrupted by a STOP_TRANSMISSION command */
CMD24_WRITE_BLOCK = 24, /* (0x58) Write single block of data */
CMD25_WRITE_MULTIPLE_BLOCK = 25, /* (0x59) Continuously writes blocks of data
until 'Stop Tran' token is sent */
CMD27_PROGRAM_CSD = 27, /* Programming bits of CSD */
CMD32_ERASE_WR_BLK_START_ADDR = 32, /* Sets the address of the first write
block to be erased. */
CMD33_ERASE_WR_BLK_END_ADDR = 33, /* Sets the address of the last write
block of the continuous range to be erased.*/
CMD38_ERASE = 38, /* Erases all previously selected write blocks */
CMD55_APP_CMD = 55, /* Extend to Applications specific commands */
CMD56_GEN_CMD = 56, /* General Purpose Command */
CMD58_READ_OCR = 58, /* Read OCR register of card */
CMD59_CRC_ON_OFF = 59, /* Turns the CRC option on or off*/
// App Commands
ACMD6_SET_BUS_WIDTH = 6,
ACMD13_SD_STATUS = 13,
ACMD22_SEND_NUM_WR_BLOCKS = 22,
ACMD23_SET_WR_BLK_ERASE_COUNT = 23,
ACMD41_SD_SEND_OP_COND = 41,
ACMD42_SET_CLR_CARD_DETECT = 42,
ACMD51_SEND_SCR = 51,
} cmdSupported;
//------------------------------------------------------------------------------
///* Disk Status Bits (DSTATUS) */
// See diskio.h.
// enum {
// STA_NOINIT = 0x01, /* Drive not initialized */
// STA_NODISK = 0x02, /* No medium in the drive */
// STA_PROTECT = 0x04 /* Write protected */
//};
#ifdef __cplusplus
}
#endif

152
lib/sd_driver/sd_regs.h Normal file
View File

@ -0,0 +1,152 @@
#pragma once
#include <stdint.h>
#include "util.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
+-----------------------+-------+-------+-----------+
| Name | Field | Width | CID-slice |
+-----------------------+-------+-------+-----------+
| Manufacturer ID | MID | 8 | [127:120] |
| OEM/Application ID | OID | 16 | [119:104] |
| Product name | PNM | 40 | [103:64] |
| Product revision | PRV | 8 | [63:56] |
| Product serial number | PSN | 32 | [55:24] |
| reserved | -- | 4 | [23:20] |
| Manufacturing date | MDT | 12 | [19:8] |
| CRC7 checksum | CRC | 7 | [7:1] |
| not used, always 1- | 1 | [0:0] | |
+-----------------------+-------+-------+-----------+
*/
// Table 5-2: The CID Fields
typedef uint8_t CID_t[16];
/*
+---------------+-----------------------+-------------------------------------+
| CSD_STRUCTURE | CSD structure version | Card Capacity |
+---------------+-----------------------+-------------------------------------+
| 0 | CSD Version 1.0 | Standard Capacity |
| 1 | CSD Version 2.0 | High Capacity and Extended Capacity |
| 2-3 | reserved | |
+---------------+-----------------------+-------------------------------------+
*/
// Table 5-3: CSD Register Structure
/*
+--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
| Name | Field | Width | Value | Cell Type | CSD-slice |
+--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
| CSD structure | CSD_STRUCTURE | 2 | 00b | R | [127:126] |
| reserved | - | 6 | 00 0000b | R | [125:120] |
| data read access-time-1 | TAAC | 8 | xxh | R | [119:112] |
| data read access-time-2 in CLK cycles (NSAC*100) | NSAC | 8 | xxh | R | [111:104] |
| max. data transfer rate | TRAN_SPEED | 8 | 32h or 5Ah | R | [103:96] |
| card command classes | CCC | 12 | 01x110110101b | R | [95:84] |
| max. read data block length | READ_BL_LEN | 4 | xh | R | [83:80] |
| partial blocks for read allowed | READ_BL_PARTIAL | 1 | 1b | R | [79:79] |
| write block misalignment | WRITE_BLK_MISALIGN | 1 | xb | R | [78:78] |
| read block misalignment | READ_BLK_MISALIGN | 1 | xb | R | [77:77] |
| DSR implemented | DSR_IMP | 1 | xb | R | [76:76] |
| reserved | - | 2 | 00b | R | [75:74] |
| device size | C_SIZE | 12 | xxxh | R | [73:62] |
| max. read current @VDD min | VDD_R_CURR_MIN | 3 | xxxb | R | [61:59] |
| max. read current @VDD max | VDD_R_CURR_MAX | 3 | xxxb | R | [58:56] |
| max. write current @VDD min | VDD_W_CURR_MIN | 3 | xxxb | R | [55:53] |
| max. write current @VDD max | VDD_W_CURR_MAX | 3 | xxxb | R | [52:50] |
| device size multiplier | C_SIZE_MULT | 3 | xxxb | R | [49:47] |
| erase single block enable | ERASE_BLK_EN | 1 | xb | R | [46:46] |
| erase sector size | SECTOR_SIZE | 7 | xxxxxxxb | R | [45:39] |
| write protect group size | WP_GRP_SIZE | 7 | xxxxxxxb | R | [38:32] |
| write protect group enable | WP_GRP_ENABLE | 1 | xb | R | [31:31] |
| reserved | (Do not use) | 2 | 00b | R | [30:29] |
| write speed factor | R2W_FACTOR | 3 | xxxb | R | [28:26] |
| max. write data block length | WRITE_BL_LEN | 4 | xxxxb | R | [25:22] |
| partial blocks for write allowed | WRITE_BL_PARTIAL | 1 | xb | R | [21:21] |
| reserved | - | 5 | 00000b | R | [20:16] |
| File format group | FILE_FORMAT_GRP | 1 | xb | R/W(1) | [15:15] |
| copy flag | COPY | 1 | xb | R/W(1) | [14:14] |
| permanent write protection | PERM_WRITE_PROTECT | 1 | xb | R/W(1) | [13:13] |
| temporary write protection | TMP_WRITE_PROTECT | 1 | xb | R/W | [12:12] |
| File format | FILE_FORMAT | 2 | xxb | R/W(1) | [11:10] |
| reserved | | 2 | 00b | R/W | [9:8] |
| CRC | CRC | 7 | xxxxxxxb | R/W | [7:1] |
| not used, always'1' | - | 1 | 1b | - | [0:0] |
+--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
*/
// Table 5-4: The CSD Register Fields (CSD Version 1.0)
/*
+------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+
| Name | Field | Width | Value | Cell Type | CSD-slice |
+------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+
| CSD structure | CSD_STRUCTURE | 2 | 01b | R | [127:126] |
| reserved | - | 6 | 00 0000b | R | [125:120] |
| data read access-time | (TAAC) | 8 | 0Eh | R | [119:112] |
| data read access-time in CLK cycles (NSAC*100) | (NSAC) | 8 | 00h | R | [111:104] |
| max. data transfer rate | (TRAN_SPEED) | 8 | 32h, 5Ah, 0Bh or 2Bh | R | [103:96] |
| card command classes | CCC | 12 | 01x110110101b | R | [95:84] |
| max. read data block length | (READ_BL_LEN) | 4 | 9 | R | [83:80] |
| partial blocks for read allowed | (READ_BL_PARTIAL) | 1 | 0 | R | [79:79] |
| write block misalignment | (WRITE_BLK_MISALIGN) | 1 | 0 | R | [78:78] |
| read block misalignment | (READ_BLK_MISALIGN) | 1 | 0 | R | [77:77] |
| DSR implemented | DSR_IMP | 1 | x | R | [76:76] |
| reserved | - | 6 | 00 0000b | R | [75:70] |
| device size | C_SIZE | 22 | xxxxxxh | R | [69:48] |
| reserved | - | 1 | 0 | R | [47:47] |
| erase single block enable | (ERASE_BLK_EN) | 1 | 1 | R | [46:46] |
| erase sector size | (SECTOR_SIZE) | 7 | 7Fh | R | [45:39] |
| write protect group size | (WP_GRP_SIZE) | 7 | 0000000b | R | [38:32] |
| write protect group enable | (WP_GRP_ENABLE) | 1 | 0 | R | [31:31] |
| reserved | | 2 | 00b | R | [30:29] |
| write speed factor | (R2W_FACTOR) | 3 | 010b | R | [28:26] |
| max. write data block length | (WRITE_BL_LEN) | 4 | 9 | R | [25:22] |
| partial blocks for write allowed | (WRITE_BL_PARTIAL) | 1 | 0 | R | [21:21] |
| reserved | - | 5 | 00000b | R | [20:16] |
| File format group | (FILE_FORMAT_GRP) | 1 | 0 | R | [15:15] |
| copy flag | COPY | 1 | x | R/W(1) | [14:14] |
| permanent write protection | PERM_WRITE_PROTECT | 1 | x | R/W(1) | [13:13] |
| temporary write protection | TMP_WRITE_PROTECT | 1 | x | R/W | [12:12] |
| File format | (FILE_FORMAT) | 2 | 00b | R | [11:10] |
| reserved | - | 2 | 00b | R | [9:8] |
| CRC | CRC | 7 | xxxxxxxb | R/W | [7:1] |
| not used, always'1' | - | 1 | 1 | - | [0:0] |
+------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+
*/
// Table 5-16: The CSD Register Fields (CSD Version 2.0)
typedef uint8_t CSD_t[16];
/* return Capacity in sectors */
static inline uint32_t CSD_sectors(CSD_t csd) /* const */ {
uint32_t c_size;
// +--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
// | Name | Field | Width | Value | Cell Type | CSD-slice |
// +--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+
// | CSD structure | CSD_STRUCTURE | 2 | 00b | R | [127:126] |
uint8_t ver = ext_bits16(csd, 127, 126);
if (ver == 0) {
// | device size | C_SIZE | 12 | xxxh | R | [73:62] |
c_size = ext_bits16(csd, 73, 62);
// | device size multiplier | C_SIZE_MULT | 3 | xxxb | R | [49:47] |
uint8_t c_size_mult = ext_bits16(csd, 49, 47);
// MULT = 2^(C_SIZE_MULT+2)
uint32_t mult = 1UL << (c_size_mult + 2);
// BLOCKNR = (C_SIZE+1) * MULT
return (c_size + 1) * mult;
} else if (ver == 1) {
// | device size | C_SIZE | 22 | xxxxxxh | R | [69:48] |
c_size = ext_bits16(csd, 69, 48);
/* The user data area capacity is calculated from C_SIZE as follows:
memory capacity = (C_SIZE+1) * 512K byte */
return (c_size + 1) * 1024; // sectors
} else {
return 0;
}
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,19 @@
#include "sd_timeouts.h"
sd_timeouts_t sd_timeouts __attribute__((weak)) = {
.sd_command = 2000, // Timeout in ms for response
.sd_command_retries = 3, // Times SPI cmd is retried when there is no response
.sd_lock = 8000, // Timeout in ms for response
.sd_spi_read = 1000, // Timeout in ms for response
.sd_spi_write = 1000, // Timeout in ms for response
.sd_spi_write_read = 1000, // Timeout in ms for response
.spi_lock = 4000, // Timeout in ms for response
.rp2040_sdio_command_R1 = 10, // Timeout in ms for response
.rp2040_sdio_command_R2 = 2, // Timeout in ms for response
.rp2040_sdio_command_R3 = 2, // Timeout in ms for response
.rp2040_sdio_rx_poll = 1000, // Timeout in ms for response
.rp2040_sdio_tx_poll = 5000, // Timeout in ms for response
.sd_sdio_begin = 1000, // Timeout in ms for response
.sd_sdio_stopTransmission = 200, // Timeout in ms for response
};

View File

@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
typedef struct {
uint32_t sd_command;
unsigned sd_command_retries;
unsigned sd_lock;
unsigned sd_spi_read;
unsigned sd_spi_write;
unsigned sd_spi_write_read;
unsigned spi_lock;
unsigned rp2040_sdio_command_R1;
unsigned rp2040_sdio_command_R2;
unsigned rp2040_sdio_command_R3;
unsigned rp2040_sdio_rx_poll;
unsigned rp2040_sdio_tx_poll;
unsigned sd_sdio_begin;
unsigned sd_sdio_stopTransmission;
} sd_timeouts_t;
extern sd_timeouts_t sd_timeouts;

91
lib/sd_driver/util.h Executable file
View File

@ -0,0 +1,91 @@
/* util.h
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.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <string.h>
//
#include "my_debug.h"
#ifdef __cplusplus
extern "C" {
#endif
// Greatest Common Divisor: Euclidian Algorithm
// https://www.freecodecamp.org/news/euclidian-gcd-algorithm-greatest-common-divisor/
int gcd(int a,int b);
typedef int (*printer_t)(const char* format, ...);
// works with negative index
static inline int wrap_ix(int index, int n)
{
return ((index % n) + n) % n;
}
// Calculate arr indices with wrap around (+ and -)
static inline int mod_floor(int a, int n) {
return ((a % n) + n) % n;
}
__attribute__((always_inline)) static inline uint32_t calculate_checksum(uint32_t const *p, size_t const size){
uint32_t checksum = 0;
for (uint32_t i = 0; i < (size/sizeof(uint32_t))-1; i++){
checksum ^= *p;
p++;
}
return checksum;
}
static inline void ext_str(size_t const data_sz,
uint8_t const data[],
size_t const msb,
size_t const lsb,
size_t const buf_sz,
char buf[]) {
memset(buf, 0, buf_sz);
size_t size = (1 + msb - lsb) / 8; // bytes
size_t byte = (data_sz - 1) - (msb / 8);
for (uint32_t i = 0; i < size; i++) {
myASSERT(i < buf_sz);
myASSERT(byte < data_sz);
buf[i] = data[byte++];
}
}
static inline uint32_t ext_bits(size_t n_src_bytes, unsigned char const *data, int msb, int lsb) {
uint32_t bits = 0;
uint32_t size = 1 + msb - lsb;
for (uint32_t i = 0; i < size; i++) {
uint32_t position = lsb + i;
uint32_t byte = (n_src_bytes - 1) - (position >> 3);
uint32_t bit = position & 0x7;
uint32_t value = (data[byte] >> bit) & 1;
bits |= value << i;
}
return bits;
}
static inline uint32_t ext_bits16(unsigned char const *data, int msb, int lsb) {
return ext_bits(16, data, msb, lsb);
}
char const* uint8_binary_str(uint8_t number);
char const* uint_binary_str(unsigned int number);
#ifdef __cplusplus
}
#endif
/* [] END OF FILE */

101
main.c
View File

@ -28,7 +28,10 @@
#include <string.h>
#include "bsp/board_api.h"
#include "lib/sd_driver/sd_card.h"
#include "tusb.h"
#include "ff.h"
#include "f_util.h"
//--------------------------------------------------------------------+
// MACRO CONSTANT TYPEDEF PROTYPES
@ -43,6 +46,59 @@ extern uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr);
extern bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr);
#endif
//--------------------------------------------------------------------+
// SD CARD
//--------------------------------------------------------------------+
void log_to_sd(const char * message);
FIL fil_sdcard; // File to log on the SD Card
/* SDIO Interface */
static sd_sdio_if_t sdio_if = {
/*
Pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio.
The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio.
CLK_gpio = (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32;
As of this writing, SDIO_CLK_PIN_D0_OFFSET is 30,
which is -2 in mod32 arithmetic, so:
CLK_gpio = D0_gpio -2.
D1_gpio = D0_gpio + 1;
D2_gpio = D0_gpio + 2;
D3_gpio = D0_gpio + 3;
*/
.CMD_gpio = 3,
.D0_gpio = 4,
.baud_rate = 125 * 1000 * 1000 / 6 // 20833333 Hz
};
/* Hardware Configuration of the SD Card socket "object" */
static sd_card_t sd_card = {.type = SD_IF_SDIO, .sdio_if_p = &sdio_if};
/**
* @brief Get the number of SD cards.
*
* @return The number of SD cards, which is 1 in this case.
*/
size_t sd_get_num() { return 1; }
/**
* @brief Get a pointer to an SD card object by its number.
*
* @param[in] num The number of the SD card to get.
*
* @return A pointer to the SD card object, or @c NULL if the number is invalid.
*/
sd_card_t* sd_get_by_num(size_t num) {
if (0 == num) {
// The number 0 is a valid SD card number.
// Return a pointer to the sd_card object.
return &sd_card;
} else {
// The number is invalid. Return @c NULL.
return NULL;
}
}
/*------------- MAIN -------------*/
int main(void) {
board_init();
@ -62,6 +118,27 @@ int main(void) {
tuh_max3421_reg_write(BOARD_TUH_RHPORT, IOPINS1_ADDR, 0x01, false);
#endif
// Init SD Card for log
FATFS fs;
char drive_path[3] = "0:";
drive_path[0] += DEV_SDIO_MIN;
FRESULT fr = f_mount(&fs, drive_path, 1);
if (FR_OK != fr) {
panic("f_mount error: %s (%d)\n", FRESULT_str(fr), fr);
return -1;
}else{
printf("SD Card mounted on %s\n", drive_path);
}
char* const filepath = "0:/log.txt";
filepath[0] = drive_path[0];
fr = f_open(&fil_sdcard, filepath, FA_OPEN_ALWAYS | FA_WRITE);
if (FR_OK != fr && FR_EXIST != fr) {
panic("f_open(%s) error: %s (%d)\n", filepath, FRESULT_str(fr), fr);
return -1;
}
while (1) {
// tinyusb host task
tuh_task();
@ -78,12 +155,18 @@ int main(void) {
void tuh_mount_cb(uint8_t dev_addr) {
// application set-up
char message[100];
printf("A device with address %d is mounted\r\n", dev_addr);
sprintf(message, "A device with address %d is mounted\r\n", dev_addr);
log_to_sd(message);
}
void tuh_umount_cb(uint8_t dev_addr) {
// application tear-down
char message[100];
printf("A device with address %d is unmounted \r\n", dev_addr);
sprintf(message, "A device with address %d is unmounted\r\n", dev_addr);
log_to_sd(message);
}
@ -103,3 +186,21 @@ void led_blinking_task(void) {
board_led_write(led_state);
led_state = 1 - led_state; // toggle
}
void log_to_sd(const char * chaine){
static int i=0;
unsigned int nb_char_written, strlen_chaine;
absolute_time_t current_time, start_time;
FRESULT error;
strlen_chaine = strlen(chaine);
start_time = get_absolute_time();
error = f_write(&fil_sdcard, chaine, strlen_chaine, &nb_char_written);
if (error != FR_OK) {
printf("f_write failed, error: %d\n", error);
}
f_sync(&fil_sdcard);
current_time = get_absolute_time();
printf("Ecrit: %d octets en %llu us\n", nb_char_written, current_time - start_time);
printf(">vitesse(Mo/s):%d:%.1f\n", i++, (float)(nb_char_written) / (float)(current_time - start_time));
}

View File

@ -70,7 +70,7 @@ bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_da
// Monter le système de fichier avec FatFs
uint8_t const drive_num = dev_addr-1;
char drive_path[3] = "0:";
drive_path[0] += drive_num;
drive_path[0] += drive_num + DEV_USB_MIN;
printf("dev_addr: %d\ndrive_num: %d\n", dev_addr, drive_num);
error = f_mount(&fatfs[drive_num], drive_path, 1);
if ( error != FR_OK )