Hi All,
Simply put I am trying to use the Xiao BLE Sense board connected to the Expansion board (with SD card slot) to write audio recorded data from the Xiao embedded PDM microphone to the microSD card.
Development setup:
HW: Apple M2 Ultra (silicon), Xiao BLE Sense (mbed) with 14 header pins soldered by me (appear functional), Xiao Expansion Board, SanDisk Ultra PLUS 64 GB microSD card, USB-C to USB-C cord connecting Xiao Sense to desktop system (cord good condition)
OS: Darwin arm64 23.6.0 (macOS Sonoma 14.6.1)
SW: VS Code 1.98.2, nRF Connect SDK v2.7.0 (not the most recent I know), toolchain 1.5.3
Description of issue:
I am working to access a microSD card formatted with fatfs (FAT 32) filesystem (64 GB capacity) but have been unable to initialize the card (see error log and screenshot below).
I have used the above configuration (Xiao Sense + Expansion board + inserted SD card) and successfully run the "CardInfo.ino" code example in the Arduino environment. From this I assume that the microSD card is usable, formatted correctly (using desktop setup described below), and power/voltage is correct.
There are other Arduino examples accessing/reading/writing an SD card but I have not tried these.
What I want to do is to create a codebase in which I can access (including initialize, open/close, read/write, and de-initialize) an SD card but everything done in the NRF Connect/Zephyr OS environment if at all possible.
I used the nRF Connect "blinky" sample to create the skeleton build configuration for the Xiao Sense and of course this compiled, assembled, linked, and executed as expected. Per multiple tutorials I simply copy the .uf2 file to the Xiao Sense to flash it to the nrf52840. I kept this Blink/LED code in the program to have evidence that the program flashed correctly.
To build the SD functionality I used the fat_fs.c code from the most recent nRF Connect sample and incorporated it into the "blinky" sample (see attached file). I would like to try to access the SD card through SPI (using the disk driver API with the SDMMC subsystem working transparently underneath).
However, when I flash this revised code I receive an error at the if (disk_access_init(disk_pdrv) != 0) {...} system call:
I have tried multiple configurations to solve the problem.
1. rechecked connections and power including that the microSD card is inserted into the SD card slot at all times/power stable to device/card throughout process as this is an fat system
2. tried suggestions from several nRF Connect Forum posts including this one without luck: https://devzone.nordicsemi.com/f/nordic-q-a/109575/xiao-ble-sense-round-display---zephyr-microsd
3. contacted the OP of the post from 2. as they report having resolved an identical (?) issue but I have not received a response
4. in prj.conf I have tried all 4 SPI interfaces (SPI0 through SPI3) though I am under the impression that SPI0 and SPI3 are preferred for SPI (note I am currently using SPI2 but I have received the same error mentioned above in initializing the SD card using all 4 interfaces.
5. I have added the "zephyr,sdmmc-disk" device tree node to each of the SPI interfaces to no avail
6. Adding CONFIG_NFCT_PINS_AS_GPIOS=y to prj.conf but this did not affect the error (got same error)
7. Seeed Studio (maker of Xiao board) Forum has not been helpful (extensive search through posts)
8. tried various filesystems (by enabled in prf.conf) including ext2 and littlefs but there does not seem to be a way to do this at this point
9. considered using NVMe but trying to use PCIe would seem to complicate this situation more
10. considered emulated block device of flash partition support (I assume this means the 2 MB onboard nRF25840 SoC flash) but I need to record/write to SD at least 3 MB of data (approximately 3 minutes of audio data) so this does not seem adequate (though it would be very cool to know how to do this). Of note, I did retain the msc_disk0 device note information (from here: https://docs.nordicsemi.com/bundle/ncs-2.4.3/page/zephyr/services/storage/disk/access.html#disk-access-api) in my overlay file but I am assuming this should not interfere with the spi2 DT configuration.
My related questions are:
* how does the SD host controller figure into this process and am I missing something in the configuration to enable this?
* how to use direct block level access (meaning using DMA?) to the SD card/is this possible as an alternative to using SPI?
* do I need to make changes to yaml and/or .json files in order to enable SD functionality for this board and/or create custom board support for the Xiao?
Any help would be greatly appreciated. If it is known that the Xiao BLE Sense (mbed) board simply cannot be used in the nRF Connect/Zephyr environment to interact with an SD card this would very helpful to know as well (but it seems as if there must be some way to do this). I have the generated VS Code support information .jsonc file that I can send to Nordic staff if that is helpful.
Kind Regards,
Rick
/* audioY */ #include <stdio.h> #include <zephyr/kernel.h> #include <zephyr/drivers/gpio.h> #include <zephyr/device.h> #include <zephyr/storage/disk_access.h> #include <zephyr/logging/log.h> #include <zephyr/fs/fs.h> /* Note the fatfs library is able to mount only strings inside _VOLUME_STRS in ffconf.h */ #if defined(CONFIG_FAT_FILESYSTEM_ELM) #include <ff.h> #define DISK_DRIVE_NAME "/SD:" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" static FATFS fat_fs; // static struct fs_file_t file; /* mounting info */ static struct fs_mount_t mp = { .type = FS_FATFS, .mountp_len = DISK_MOUNT_PT, // orgiginally .mountp .fs_data = &fat_fs, }; #elif defined(CONFIG_FILE_SYSTEM_EXT2) #include <zephyr/fs/ext2.h> #define DISK_DRIVE_NAME "SDMMC" #define DISK_MOUNT_PT "/ext" static struct fs_mount_t mp = { .type = FS_EXT2, .flags = FS_MOUNT_FLAG_NO_FORMAT, .storage_dev = (void *)DISK_DRIVE_NAME, .mnt_point = "/ext", }; /* little FS is what is enabled here, may need to change to ext2, fat, nffs, or other */ #elif defined(CONFIG_FILE_SYSTEM_NFFS) #include <nffs/nffs.h> #define DISK_DRIVE_NAME "/nffs" // change DISK_DRIVE_NAME to "/nffs:" to match ELM above ? #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_LITTLEFS) #include <fs/littlefs.h> #define DISK_DRIVE_NAME "/lfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_NATIVE_POSIX) #define DISK_DRIVE_NAME "/disk" // originally DISK_MOUNT_PT "/disk" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_MCUBFS) #include <fs/mcubfs.h> #define DISK_DRIVE_NAME "/mcubfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_FATFS) #include <fs/fatfs.h> #define DISK_DRIVE_NAME "/fatfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_FATFS_LFN) #include <fs/fatfs.h> #define DISK_DRIVE_NAME "/fatfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_FATFS_PETIT) #include <fs/fatfs.h> #define DISK_DRIVE_NAME "/fatfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_FATFS_FS) #include <fs/fatfs.h> #define DISK_DRIVE_NAME "/fatfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_FATFS_FS_LFN) #include <fs/fatfs.h> #define DISK_DRIVE_NAME "/fatfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_FATFS_FS_PETIT) #include <fs/fatfs.h> #define DISK_DRIVE_NAME "/fatfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #elif defined(CONFIG_FILE_SYSTEM_FATFS_FS_NFFS) #include <fs/fatfs.h> #define DISK_DRIVE_NAME "/fatfs" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #else // defaults here for now until prj.conf is updated to FS I want to use #define DISK_DRIVE_NAME "/default" #define DISK_MOUNT_PT "/"DISK_DRIVE_NAME":" #endif /* 1000 msec = 1 sec */ #define SLEEP_TIME_MS 500 /* The devicetree node identifier for the "led0" alias. */ #define LED0_NODE DT_ALIAS(led1) /* A build error on this line means your board is unsupported */ static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); LOG_MODULE_REGISTER(main); #define MAX_PATH 128 #define SOME_FILE_NAME "some.dat" #define SOME_DIR_NAME "some" #define SOME_REQUIRED_LEN MAX(sizeof(SOME_FILE_NAME), sizeof(SOME_DIR_NAME)) static int lsdir(const char *path); #ifdef CONFIG_FS_SAMPLE_CREATE_SOME_ENTRIES static bool createEntries(const char *base_path) { char path[MAX_PATH]; struct fs_file_t file; int base = strlen(base_path); fs_file_t_init(&file); if (base >= (sizeof(path) - SOME_REQUIRED_LEN)) { LOG_ERR("Not enough concatenation buffer to create file paths"); return false; } LOG_INF("Creating some dir entries in %s", base_path); strncpy(path, base_path, sizeof(path)); path[base++] = '/'; path[base] = 0; strcat(&path[base], SOME_FILE_NAME); if (fs_open(&file, path, FS_O_CREATE) != 0) { LOG_ERR("Failed to create file %s", path); return false; } fs_close(&file); path[base] = 0; strcat(&path[base], SOME_DIR_NAME); if (fs_mkdir(path) != 0) { LOG_ERR("Failed to create dir %s", path); /* If code gets here, it has at least successes to create the * file so allow function to return true. */ } return true; } #endif static const char *disk_mount_pt = DISK_MOUNT_PT; int main(void) { int ret; bool led_state = true; printk("Running...\n"); /* raw disk I/O */ do { static const char *disk_pdrv = DISK_DRIVE_NAME; uint64_t memory_size_mb; uint32_t block_count; uint32_t block_size; // uint32_t erase_block_size; // future use? // uint32_t write_block_size; if (disk_access_init(disk_pdrv) != 0) { LOG_ERR("SD card initialization failed!\n"); break; } if (disk_access_ioctl(disk_pdrv, DISK_IOCTL_GET_SECTOR_COUNT, &block_count)) { LOG_ERR("Unable to get block count!\n"); break; } LOG_INF("Block count %u", block_count); // printf("Block count inside printf: %u", block_count); if (disk_access_ioctl(disk_pdrv, DISK_IOCTL_GET_SECTOR_SIZE, &block_size)) { LOG_ERR("Unable to get sector size!\n"); break; } LOG_INF("Sector size %u\n", block_size); } while (0); if (!gpio_is_ready_dt(&led)) { return 0; } ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); if (ret < 0) { return 0; } while (1) { ret = gpio_pin_toggle_dt(&led); if (ret < 0) { return 0; } led_state = !led_state; printf("LED state: %s\n", led_state ? "ON" : "OFF"); k_msleep(SLEEP_TIME_MS); } return 0; }
Lastly (in a very long post I know), I tried a somewhat different approach/version of main.c/other codebase (attached below) adapted from a different source but in this case I get a timeout error (errno 116) trying to communicate with the SD card. The main.c, prj.conf, and overlay files for this build are below as well.
/* audio1: code to interface with SD card * Rick Krebs 2025 * * SPDX-License-Identifier: Apache-2.0 */ #include <stdio.h> #include <zephyr/kernel.h> #include <zephyr/drivers/gpio.h> #include <zephyr/fs/fs.h> #include <zephyr/drivers/sdhc.h> #include <errno.h> #include <zephyr/device.h> #include <zephyr/devicetree.h> #include <zephyr/sys/printk.h> #include <zephyr/drivers/spi.h> #include <zephyr/drivers/pinctrl.h> #include <zephyr/devicetree/gpio.h> #include <zephyr/storage/disk_access.h> #include <ff.h> #include <stdlib.h> /* 1000 msec = 1 sec */ #define SLEEP_TIME_MS 500 /* The devicetree node identifier for the "led0" alias. */ #define LED0_NODE DT_ALIAS(led0) #define DT_DRV_COMPAT xiao_spi // nordic_nrf_spim // #define SPI_DEVICE_NODE DT_NODELABEL(spi0) #define SD_CS_PIN 28 // chip select pin for SD card (P0.28 on Xiao Sense) #define SD_CARD_MOUNT_POINT "/SD:" #define LOG_FILENAME "adc_log.txt" static FATFS fat_fs; static struct fs_mount_t mp = { .type = FS_FATFS, .mnt_point = SD_CARD_MOUNT_POINT, }; /* struct spi_cs_control spi_cs = { .gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpioa)), .gpio_pin 28, .gpio_dt_flags = GPIO_ACTIVE_LOW, .delay = 10, }; */ static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); static const struct device *spi_dev; static struct spi_cs_control cs_ctrl; #define SPI_OP SPI_OP_MODE_MASTER | SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_WORD_SET(8) | SPI_LINES_SINGLE struct spi_dt_spec spispec = SPI_DT_SPEC_GET(DT_NODELABEL(sdhc0), SPI_OP, 0); /* spi_dev = device_get_binding(DT_LABEL(DT_DRV_INST(0))); struct spi_cs_control spi_cs = { #if DT_NODE_HAS_PROP(DT_DRV_INST(0), cs_gpios) .gpio = { .pin = DT_GPIO_PIN(DT_DRV_INST(0), cs_gpios), .dt_flags = GPIO_ACTIVE_LOW, }, #else .gpio = { .pin = 0, // Default value or handle error .dt_flags = GPIO_ACTIVE_LOW, }, #endif .delay = 0, }; static void spi_init(void) { spi_cs.gpio_dev = device_get_binding(DT_GPIO_LABEL(DT_DRV_INST(0), cs_gpios)); if (spi_cs.gpio.port == NULL) { printk("Could not get gpio device\n"); } else { printk("GPIO device: %s\n", DT_GPIO_LABEL(DT_DRV_INST(0), cs-gpios)); } spi_dev = device_get_binding(DT_LABEL(DT_DRV_INST(0))); if (spi_dev == NULL) { printk("Could not get %s device\n", DT_LABEL(DT_DRV_INST(0))); return; } else { printk("SPI Device: %s\n", DT_LABEL(DT_DRV_INST(0))); printk("SPI CSN %d, MISO %d, MOSI %d, CLK %d\n", DT_GPIO_PIN(DT_DRV_INST(0), cs_gpios), DT_PROP(DT_DRV_INST(0), miso_pin), DT_PROP(DT_DRV_INST(0), mosi_pin), DT_PROP(DT_DRV_INST(0), sck_pin)); } } void spi_test_send(void) { int err; static uint8_t tx_buffer[32]; static uint8_t rx_buffer[32]; const struct spi_buf tx_buf = { .buf = tx_buffer, .len = sizeof(tx_buffer) }; const struct spi_buf_set tx = { .buffers = &tx_buf, .count = 1 }; struct spi_buf rx_buf = { .buf = rx_buffer, .len = sizeof(rx_buffer), }; const struct spi_buf_set rx = { .buffers = &rx_buf, .count = 1 }; err = spi_transceive(spi_dev, &spi_cfg, &tx, &rx); if (err) { printk("SPI error: %d\n", err); } else { // Connect MISO to MOSI for loopback printk("TX sent: %x\n", tx_buffer[0]); printk("RX recv: %x\n", rx_buffer[0]); tx_buffer[0]++; } } */ static int init_sd_card(void) { printk("Inside init_sd_card function..."); static const char *disk_pdrv = "SD"; uint64_t memory_size_mb; uint32_t block_count; uint32_t block_size; int err; err = disk_access_init(disk_pdrv); if (err != 0) { printk("disk_access_init failed! Error code: %d (%s) \n", err, strerror(err)); return -1; } if (disk_access_ioctl(disk_pdrv, DISK_IOCTL_GET_SECTOR_COUNT, &block_count)) { printk("Unable to get sector count!\n"); return -1; } if (disk_access_ioctl(disk_pdrv, DISK_IOCTL_GET_SECTOR_SIZE, &block_size)) { printk("Unable to get sector size!\n"); return -1; } memory_size_mb = (uint64_t) block_count * block_size / (1024 * 1024); printk("Memory Size (MB): %u\n", (uint32_t) memory_size_mb); mp.fs_data = &fat_fs; err = fs_mount(&mp); if (err) { printk("Error mounting fat_fs [%d]\n", err); return err; } printk("Disk mounted!\n"); return 0; } /* static int log_to_sd_card(uint16_t adc_raw, float voltage) { struct fs_file_t file; char log_entry[100]; ssize_t bytes_written; fs_file_t_init(&file); int err = fs_open(&file, SD_CARD_MOUNT_POINT "/" LOG_FILENAME, FS_O_WRITE | FS_O_CREATE | FS_O_APPEND); if (err) { printk("Error opening file [%d]\n", err); return err; } snprintf(log_entry, sizeof(log_entry), "ADC raw: %d, Voltage: %.2f V\n", adc_raw, (double) voltage); bytes_written = fs_write(&file, log_entry, strlen(log_entry)); if (bytes_written < 0) { printk("Error writing to file [%zd]\n", bytes_written); fs_close(&file); return (int) bytes_written; } fs_close(&file); printk("Data logged to SD card\n"); return 0; } */ int main(void) { int ret; bool led_state = true; printk("Running...\n"); // initialize SPI device spi_dev = device_get_binding("sdhc_spi"); // (SPI_DEVICE_NODE); if (!device_is_ready(spi_dev)) { printk("SPI device is not ready!\n"); return -1; } else { printk("SPI initialization successful!\n"); } if (init_sd_card() != 0) { printk("Failed to initialize SD card!\n"); exit (0); } // configure chip select cs_ctrl.gpio.port = DEVICE_DT_GET(DT_NODELABEL(gpio0)); // cs_ctrl.gpio.pin = ADC_CS_PIN; cs_ctrl.gpio.dt_flags = GPIO_ACTIVE_LOW; cs_ctrl.delay = 0; // fs_open(zipfp, "Maxie.txt", FS_O_RDWR); /* if (sdhc_card_present(spi_dev)) printf("hey card is in there!\n"); else printf("error!\n"); if (!gpio_is_ready_dt(&led)) { return 0; } */ ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); if (ret < 0) { return 0; } while (1) { ret = gpio_pin_toggle_dt(&led); if (ret < 0) { return 0; } /* printk("Attempting to read ADC channel 0\n\r"); uint16_t adc_raw = read_adc_channel(0); // read channel 0 if (adc_raw == 0) { printk("ADC read failed or returned 0\n"); } else { float voltage = (adc_raw * 3.3f) / 1023.0f; // assuming 3.3 V reference printk("ADC raw value: %d, Voltage: %.2f V\n", adc_raw, voltage); if (log_to_sd_card(adc_raw, voltage) != 0) { printk("Failed to log data to SD card!\n"); } } */ printk("\n\r"); k_msleep(1000); led_state = !led_state; printf("LED state: %s\n", led_state ? "ON" : "OFF"); k_msleep(SLEEP_TIME_MS); } return 0; }