Add USB mass storage device (WIP) + rework task state machines

This commit is contained in:
Anthony Rabine 2023-08-05 14:34:44 +02:00
parent c2d3a0ae67
commit 085bc9e8f7
18 changed files with 913 additions and 246 deletions

View file

@ -28,9 +28,7 @@ export default defineConfig({
collapsed: false,
items: [
{ text: 'Bundles introduction', link: '/guide-intro' },
{ text: 'Dev kit (Raspberry Pico)', link: '/guide-devkit-pico' },
{ text: 'SeeedStudio Wio Lite', link: '/guide-wio-lite' },
{ text: 'Arduino MKR Zero', link: '/guide-mkr-zero' }
{ text: 'Dev kit (Raspberry Pico)', link: '/guide-devkit-pico' }
]
},
{

View file

@ -26,16 +26,16 @@ Here is an example of components.
| UPS module or Pimoroni LiPo Shim | 15 € | Waveshare | [Buy on Amazon](https://amzn.to/44p8Exo) |
| LiPo battery 500mAh | 9 € | Any | [Buy on Amazon](https://amzn.to/3VCl3df) |
| GPIO Expander Pico 4 modules | 17 € | Waveshare | [Buy on Amazon](https://amzn.to/42ukJQ4) |
| SDCard breakout board | 5 € | Any | |
| SDCard breakout board | 5 € | Any | [Buy on Amazon](https://amzn.to/3qf3chr) | |
| **TOTAL** | **86 €** |
A much more optimization of price is possible using the Marble Pico board which is completely compatible to the original Pico pinout but with embedded battery management and SDCard holder. Price is still low: 5€!
| Part | Price | Shop | Link |
| ------------------------------------- | -------- | --------- | ---------------------------------------- |
| ------------------------------------- | -------- | --------- | ----------------------------------------------------------------------- |
| Audio board + speaker | 13 € | Waveshare | [Buy on Amazon](https://amzn.to/41nWgeB) |
| Marble Pico | 5 € | ArduShop | [Buy on Amazon](https://ardushop.ro/en/home/2652-marble-pico.html) |
| Marble Pico | 5 € | ArduShop | [Buy on ardushop.ro](https://ardushop.ro/en/home/2652-marble-pico.html) |
| 2inch LCD (320x240) | 14 € | Waveshare | [Buy on Amazon](https://amzn.to/3LyG5oJ) |
| Some push buttons and rotary switches | 4 € | Any | [Buy on Amazon](https://amzn.to/3AX6MOX) |
| LiPo battery 500mAh | 9 € | Any | [Buy on Amazon](https://amzn.to/3VCl3df) |

View file

@ -38,6 +38,9 @@
"critical_section.h": "c",
"serializers.h": "c",
"cstring": "c",
"typeinfo": "c"
"typeinfo": "c",
"mutex.h": "c",
"sem.h": "c",
"msc_disk.h": "c"
}
}

View file

@ -18,6 +18,9 @@ set(PICO_SRCS
${CMAKE_CURRENT_LIST_DIR}/pico_hal_wrapper.c
${CMAKE_CURRENT_LIST_DIR}/pico_lcd_spi.c
${CMAKE_CURRENT_LIST_DIR}/pico_i2s.c
${CMAKE_CURRENT_LIST_DIR}/msc_disk.c
${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c
${CMAKE_CURRENT_LIST_DIR}/pico_sdcard_spi.c
)
include_directories(../../src ../../hal ../../library .)
@ -30,7 +33,7 @@ add_library(
pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/pico_i2s.pio)
pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/i2s.pio)
target_link_libraries(${PROJECT_NAME} INTERFACE pico_stdlib hardware_exception cmsis_core)
target_link_libraries(${PROJECT_NAME} INTERFACE pico_stdlib hardware_exception cmsis_core tinyusb_device tinyusb_board)
target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_sources(${PROJECT_NAME} INTERFACE

View file

@ -0,0 +1,178 @@
#include "bsp/board.h"
#include "tusb.h"
#include "class/msc/msc.h"
#include "pico.h"
#include "filesystem.h"
// whether host does safe-eject
static bool ejected = false;
// Invoked when received SCSI_CMD_INQUIRY
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
{
(void)lun;
const char vid[] = "Microdia USB2.0 DSP";
const char pid[] = "Mass Storage";
const char rev[] = "1.0";
memcpy(vendor_id, vid, strlen(vid));
memcpy(product_id, pid, strlen(pid));
memcpy(product_rev, rev, strlen(rev));
}
// Invoked when received Test Unit Ready command.
// return true allowing host to read/write this LUN e.g SD card inserted
bool tud_msc_test_unit_ready_cb(uint8_t lun)
{
(void)lun;
// RAM disk is ready until ejected
if (ejected)
{
// Additional Sense 3A-00 is NOT_FOUND
tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00);
return false;
}
return true;
}
// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
// Application update block count and block size
void tud_msc_capacity_cb(uint8_t lun, uint32_t *block_count, uint16_t *block_size)
{
(void)lun;
*block_count = filesystem_get_capacity();
*block_size = 512;
}
// Invoked when received Start Stop Unit command
// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
// - Start = 1 : active mode, if load_eject = 1 : load disk storage
bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
{
(void)lun;
(void)power_condition;
if (load_eject)
{
if (start)
{
// load disk storage
}
else
{
// unload disk storage
ejected = true;
}
}
return true;
}
static uint8_t blockBuf[512];
#include "fs_task.h"
#include "qor.h"
static qor_mbox_t ReadFsMailBox;
static uint8_t *ReadFsEventQueue[10];
void msc_disk_initialize()
{
qor_mbox_init(&ReadFsMailBox, (void **)&ReadFsEventQueue, 10);
}
void fs_read_cb(bool success)
{
(void)success;
static uint8_t dummy;
qor_mbox_notify(&ReadFsMailBox, (void **)&dummy, QOR_MBOX_OPTION_SEND_BACK);
}
// Callback invoked when received READ10 command.
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void *buffer, uint32_t bufsize)
{
(void)lun;
// out of ramdisk
// if (lba >= DISK_BLOCK_NUM)
// return -1;
fs_task_read_block(lba, blockBuf, fs_read_cb);
uint8_t *ev;
uint32_t res = qor_mbox_wait(&ReadFsMailBox, (void **)&ev, 200);
// printf("lba 0x%x, bufsize %d, offset %d\n",lba, bufsize, offset);
// uint8_t const *addr = msc_disk[lba] + offset;
uint8_t const *addr = blockBuf + offset;
memcpy(buffer, addr, bufsize);
return (int32_t)bufsize;
}
bool tud_msc_is_writable_cb(uint8_t lun)
{
(void)lun;
return true;
}
// Callback invoked when received WRITE10 command.
// Process data in buffer to disk's storage and return number of written bytes
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t *buffer, uint32_t bufsize)
{
(void)lun;
printf("write - lba 0x%x, bufsize%d\n", lba, bufsize);
return (int32_t)bufsize;
}
// Callback invoked when received an SCSI command not in built-in list below
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
// - READ10 and WRITE10 has their own callbacks
int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void *buffer, uint16_t bufsize)
{
// read10 & write10 has their own callback and MUST not be handled here
void const *response = NULL;
int32_t resplen = 0;
// most scsi handled is input
bool in_xfer = true;
switch (scsi_cmd[0])
{
default:
// Set Sense = Invalid Command Operation
tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
// negative means error -> tinyusb could stall and/or response with failed status
resplen = -1;
break;
}
// return resplen must not larger than bufsize
if (resplen > bufsize)
resplen = bufsize;
if (response && (resplen > 0))
{
if (in_xfer)
{
memcpy(buffer, response, (size_t)resplen);
}
else
{
// SCSI output
}
}
return (int32_t)resplen;
}

View file

@ -0,0 +1,5 @@
#pragma once
void printReserveSectFat();
void msc_disk_initialize();

View file

@ -0,0 +1,71 @@
#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/spi.h"
#include "hardware/dma.h"
// Grab some unused dma channels
static uint dma_tx;
static uint dma_rx;
void pico_sdcard_dma_initialize()
{
dma_tx = dma_claim_unused_channel(true);
dma_rx = dma_claim_unused_channel(true);
// 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.
// printf("Configure TX DMA\n");
dma_channel_config c = dma_channel_get_default_config(dma_tx);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_dreq(&c, spi_get_dreq(spi0, true));
dma_channel_configure(dma_tx, &c,
&spi_get_hw(spi0)->dr, // write address
NULL, // read address
0, // element count will be set later
false); // don't start yet
// printf("Configure RX DMA\n");
// 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)
c = dma_channel_get_default_config(dma_rx);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_dreq(&c, spi_get_dreq(spi0, false));
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
dma_channel_configure(dma_rx, &c,
NULL, // write address
&spi_get_hw(spi0)->dr, // read address
0, // element count (each element is of size transfer_data_size)
false); // don't start yet
// printf("Starting DMAs...\n");
// start them exactly simultaneously to avoid races (in extreme cases the FIFO could overflow)
dma_start_channel_mask((1u << dma_tx) | (1u << dma_rx));
// printf("Wait for RX complete...\n");
/*
dma_channel_wait_for_finish_blocking(dma_rx);
if (dma_channel_is_busy(dma_tx))
{
panic("RX completed before TX");
}
*/
}
void pico_sdcard_dma_start_write(uint8_t *buffer, uint32_t size)
{
// irq_set_exclusive_handler(DMA_IRQ_1, dma_handler);
// // D'abord on va paramétrer les diverses interruptions
// dma_channel_set_irq0_enabled(i2s->dma_ch_out_data, true);
// irq_set_enabled(DMA_IRQ_1, true);
// dma_channel_start(i2s->dma_ch_out_ctrl);
}

View file

@ -0,0 +1 @@
#pragma once

View file

@ -0,0 +1,87 @@
#ifndef _TUSB_CONFIG_H_
#define _TUSB_CONFIG_H_
#ifdef __cplusplus
extern "C" {
#endif
//--------------------------------------------------------------------
// COMMON CONFIGURATION
//--------------------------------------------------------------------
// defined by board.mk
#ifndef CFG_TUSB_MCU
#error CFG_TUSB_MCU must be defined
#endif
// RHPort number used for device can be defined by board.mk, default to port 0
#ifndef BOARD_DEVICE_RHPORT_NUM
#define BOARD_DEVICE_RHPORT_NUM 0
#endif
// RHPort max operational speed can defined by board.mk
// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed
#ifndef BOARD_DEVICE_RHPORT_SPEED
#if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \
CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X)
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED
#else
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED
#endif
#endif
// Device mode with rhport and speed defined by board.mk
#if BOARD_DEVICE_RHPORT_NUM == 0
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
#elif BOARD_DEVICE_RHPORT_NUM == 1
#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
#else
#error "Incorrect RHPort configuration"
#endif
#ifndef CFG_TUSB_OS
#define CFG_TUSB_OS OPT_OS_NONE
#endif
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
// #define CFG_TUSB_DEBUG 0
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
* Tinyusb use follows macros to declare transferring memory so that they can be put
* into those specific section.
* e.g
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
*/
#ifndef CFG_TUSB_MEM_SECTION
#define CFG_TUSB_MEM_SECTION
#endif
#ifndef CFG_TUSB_MEM_ALIGN
#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
#endif
//--------------------------------------------------------------------
// DEVICE CONFIGURATION
//--------------------------------------------------------------------
#ifndef CFG_TUD_ENDPOINT0_SIZE
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
//------------- CLASS -------------//
#define CFG_TUD_HID 0
#define CFG_TUD_CDC 0
#define CFG_TUD_MSC 1
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_EP_BUFSIZE 16
#define CFG_TUD_MSC_EP_BUFSIZE 512
#ifdef __cplusplus
}
#endif
#endif /* _TUSB_CONFIG_H_ */

View file

@ -0,0 +1,220 @@
#include "tusb.h"
#include "class/msc/msc.h"
#include "device/usbd.h"
/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
* Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
*
* Auto ProductID layout's Bitmap:
* [MSB] HID | MSC | CDC [LSB]
*/
#define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n))
#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
_PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4))
#define USB_VID 0x0c45
#define USB_BCD 0x6840
// Bus 003 Device 075: ID 0c45:6840 Microdia USB2.0 DSP
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
tusb_desc_device_t const desc_device =
{
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = USB_BCD,
// Use Interface Association Descriptor (IAD) for CDC
// As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USB_VID,
.idProduct = USB_PID,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01};
// Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor
uint8_t const *tud_descriptor_device_cb(void)
{
return (uint8_t const *)&desc_device;
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
enum
{
ITF_NUM_MSC,
ITF_NUM_TOTAL
};
#define EPNUM_MSC_OUT 0x01
#define EPNUM_MSC_IN 0x81
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
// full speed configuration
uint8_t const desc_fs_configuration[] =
{
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
// Interface number, string index, EP Out & EP In address, EP size
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 3, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64),
};
#if TUD_OPT_HIGH_SPEED
// Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration
// high speed configuration
uint8_t const desc_hs_configuration[] =
{
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
// Interface number, string index, EP Out & EP In address, EP size
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 3, EPNUM_MSC_OUT, EPNUM_MSC_IN, 512),
};
// other speed configuration
uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
// device qualifier is mostly similar to device descriptor since we don't change configuration based on speed
tusb_desc_device_qualifier_t const desc_device_qualifier =
{
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = USB_BCD,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0x00};
// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete.
// device_qualifier descriptor describes information about a high-speed capable device that would
// change if the device were operating at the other speed. If not highspeed capable stall this request.
uint8_t const *tud_descriptor_device_qualifier_cb(void)
{
return (uint8_t const *)&desc_device_qualifier;
}
// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
// Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa
uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index)
{
(void)index; // for multiple configurations
// if link speed is high return fullspeed config, and vice versa
// Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG
memcpy(desc_other_speed_config,
(tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration,
CONFIG_TOTAL_LEN);
desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
return desc_other_speed_config;
}
#endif // highspeed
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const *tud_descriptor_configuration_cb(uint8_t index)
{
(void)index; // for multiple configurations
#if TUD_OPT_HIGH_SPEED
// Although we are highspeed, host may be fullspeed.
return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration;
#else
return desc_fs_configuration;
#endif
}
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
/*
T: Bus=03 Lev=03 Prnt=04 Port=00 Cnt=01 Dev#= 75 Spd=480 MxCh= 0
D: Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs= 1
P: Vendor=0c45 ProdID=6840 Rev=01.00
S: Product=USB2.0 DSP
S: SerialNumber=USB2.0 DSP
C: #Ifs= 1 Cfg#= 1 Atr=c0 MxPwr=500mA
I: If#= 0 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=06 Prot=50 Driver=usb-storage
E: Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E: Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
*/
// array of pointer to string descriptors
char const *string_desc_arr[] =
{
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"D8S EURL", // 1: Manufacturer
"USB2.0 DSP", // 2: Product
"USB2.0 DSP", // 3: MSC Interface
};
static uint16_t _desc_str[32];
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
(void)langid;
uint8_t chr_count;
if (index == 0)
{
memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1;
}
else
{
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])))
return NULL;
const char *str = string_desc_arr[index];
// Cap at max char
chr_count = (uint8_t)strlen(str);
if (chr_count > 31)
chr_count = 31;
// Convert ASCII string into UTF-16
for (uint8_t i = 0; i < chr_count; i++)
{
_desc_str[1 + i] = str[i];
}
}
// first byte is length (including header), second byte is string type
_desc_str[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
return _desc_str;
}

View file

@ -6,6 +6,7 @@
#include "filesystem.h"
#include "mini_qoi.h"
#include "serializers.h"
#include "sdcard.h"
#ifdef OST_USE_FF_LIBRARY
#include "ff.h"
@ -28,6 +29,8 @@ static FIL File[2]; /* File object */
static DIR Dir; /* Directory object */
static FILINFO Finfo;
static SD_CardInfo cardinfo;
file_t file_open(const char *filename)
{
#ifdef OST_USE_FF_LIBRARY
@ -95,9 +98,24 @@ void filesystem_mount()
debug_printf("[OST] SD Card File System = %d\r\n", fs.fs_type); // FS_EXFAT = 4
// debug_printf("[OST] Starting with CPU=%d\r\n", (int)SystemCoreClock);
// Get SD Card info
if (sdcard_get_card_info(&cardinfo) == SD_RESPONSE_NO_ERROR)
{
debug_printf("[OST] Sectors: %d, sector size: %d\r\n", cardinfo.CardCapacity, cardinfo.CardBlockSize);
}
else
{
debug_printf("[OST] Cannot read SDCard info\r\n");
}
scan_files("");
}
uint32_t filesystem_get_capacity()
{
return cardinfo.CardCapacity;
}
/*
// Loop in all directories
void disk_start()

View file

@ -9,5 +9,6 @@ void filesystem_mount();
void filesystem_display_image(const char *filename);
void filesystem_load_rom(uint8_t *mem, const char *filename);
void filesystem_get_story_title(ost_context_t *ctx);
uint32_t filesystem_get_capacity();
#endif // FILESYSTEM_H

View file

@ -23,55 +23,56 @@
#include "system.h"
#include "vm_task.h"
#include "fs_task.h"
#include "sdcard.h"
// ===========================================================================================================
// DEFINITIONS
// ===========================================================================================================
typedef struct
{
uint8_t ev;
} ost_audio_event_t;
typedef enum
{
FS_WAIT_FOR_EVENT,
FS_NO_EVENT,
FS_PLAY_SOUND,
FS_DISPLAY_IMAGE,
FS_LOAD_INDEX,
FS_LOAD_STORY
FS_LOAD_STORY,
FS_READ_SDCARD_BLOCK,
FS_AUDIO_NEXT_SAMPLES
} fs_state_t;
typedef struct
{
fs_state_t ev;
uint8_t *mem;
uint32_t addr;
ost_button_t button;
char *image;
char *sound;
fs_result_cb_t cb;
} ost_fs_event_t;
#define STORY_DIR_OFFSET (UUID_SIZE + 1)
// ===========================================================================================================
// PRIVATE GLOBAL VARIABLES
// ===========================================================================================================
static qor_tcb_t FsTcb;
static uint32_t FsStack[4096];
static qor_mbox_t AudioMailBox;
static ost_audio_event_t wake_up;
static ost_audio_event_t *AudioQueue[10];
static int dbg_state = 0;
static fs_state_t FsState = FS_WAIT_FOR_EVENT;
static qor_mbox_t FsMailBox;
static ost_fs_event_t *FsEventQueue[10];
static ost_context_t OstContext;
static int PacketCounter = 0;
static char ScratchFile[260];
static const char *ImagesDir = "/images/";
static const char *SoundsDir = "/sounds/";
static uint8_t LedState = 0;
// ===========================================================================================================
// FILE SYSTEM TASK
// ===========================================================================================================
@ -79,8 +80,9 @@ static ost_context_t OstContext;
// End of DMA transfer callback
static void audio_callback(void)
{
dbg_state = 1 - dbg_state;
qor_mbox_notify(&AudioMailBox, (void **)&wake_up, QOR_MBOX_OPTION_SEND_BACK);
static ost_fs_event_t NextSamplesEv = {
.ev = FS_AUDIO_NEXT_SAMPLES};
qor_mbox_notify(&FsMailBox, (void **)&NextSamplesEv, QOR_MBOX_OPTION_SEND_BACK);
}
static void show_duration(uint32_t millisecondes)
@ -97,90 +99,68 @@ static void show_duration(uint32_t millisecondes)
debug_printf("Temps : %d minutes, %d secondes, %d millisecondes\r\n", minutes, secondes, reste);
}
static void play_sound_file(const char *filename)
{
debug_printf("\r\n-------------------------------------------------------\r\nPlaying: %s\r\n", filename);
ost_system_stopwatch_start();
ost_audio_play(filename);
ost_audio_event_t *e = NULL;
int isPlaying = 0;
int count = 0;
uint32_t res = 0;
do
{
uint32_t res = qor_mbox_wait(&AudioMailBox, (void **)&e, 300); // On devrait recevoir un message toutes les 3ms (durée d'envoi d'un buffer I2S)
if (res == QOR_MBOX_OK)
{
isPlaying = ost_audio_process();
}
count++;
} while (isPlaying);
uint32_t executionTime = ost_system_stopwatch_stop();
ost_audio_stop();
debug_printf("\r\nPackets: %d\r\n", count);
show_duration(executionTime);
}
static char ScratchFile[260];
static const char *ImagesDir = "/images/";
static const char *SoundsDir = "/sounds/";
#define STORY_DIR_OFFSET (UUID_SIZE + 1)
static uint8_t LedState = 0;
void FsTask(void *args)
{
ost_fs_event_t *fs_ev = NULL;
ost_fs_event_t *message = NULL;
uint32_t res = 0;
filesystem_read_index_file(&OstContext);
FsState = FS_LOAD_INDEX;
int isPlaying = 0;
while (1)
{
switch (FsState)
res = qor_mbox_wait(&FsMailBox, (void **)&message, 1000);
if (res == QOR_MBOX_OK)
{
switch (message->ev)
{
case FS_PLAY_SOUND:
if (OstContext.sound != NULL)
{
ScratchFile[STORY_DIR_OFFSET] = 0;
strcat(ScratchFile, SoundsDir);
strcat(ScratchFile, OstContext.sound);
play_sound_file(ScratchFile);
strcat(ScratchFile, message->sound);
debug_printf("\r\n-------------------------------------------------------\r\nPlaying: %s\r\n", ScratchFile);
ost_system_stopwatch_start();
ost_audio_play(ScratchFile);
PacketCounter = 0;
isPlaying = 1;
}
FsState = FS_WAIT_FOR_EVENT;
break;
case FS_AUDIO_NEXT_SAMPLES:
isPlaying = ost_audio_process();
PacketCounter++;
if (isPlaying == 0)
{
uint32_t executionTime = ost_system_stopwatch_stop();
ost_audio_stop();
debug_printf("\r\nPackets: %d\r\n", PacketCounter);
show_duration(executionTime);
vm_task_sound_finished();
}
break;
case FS_DISPLAY_IMAGE:
if (OstContext.image != NULL)
{
ScratchFile[STORY_DIR_OFFSET] = 0;
strcat(ScratchFile, ImagesDir);
strcat(ScratchFile, OstContext.image);
strcat(ScratchFile, message->image);
filesystem_display_image(ScratchFile);
}
if (OstContext.sound != NULL)
{
FsState = FS_PLAY_SOUND;
}
else
{
FsState = FS_WAIT_FOR_EVENT;
}
break;
case FS_LOAD_INDEX:
{
bool success = false;
if (OstContext.number_of_stories > 0)
{
filesystem_get_story_title(&OstContext);
@ -189,73 +169,103 @@ void FsTask(void *args)
ScratchFile[0] = '/';
memcpy(&ScratchFile[1], OstContext.uuid, UUID_SIZE);
ScratchFile[1 + UUID_SIZE] = 0;
FsState = FS_DISPLAY_IMAGE; // Always display image (then sound), if there is one
success = true;
}
else
if (message->cb != NULL)
{
FsState = FS_WAIT_FOR_EVENT;
message->cb(success);
}
}
break;
case FS_LOAD_STORY:
ScratchFile[STORY_DIR_OFFSET] = 0;
strcat(ScratchFile, "/story.c32");
filesystem_load_rom(fs_ev->mem, ScratchFile);
filesystem_load_rom(message->mem, ScratchFile);
// ROM loaded, execute story
vm_task_start_story();
FsState = FS_WAIT_FOR_EVENT;
break;
case FS_WAIT_FOR_EVENT:
default:
res = qor_mbox_wait(&FsMailBox, (void **)&fs_ev, 1000);
if (res == QOR_MBOX_OK)
case FS_READ_SDCARD_BLOCK:
sdcard_sector_read(message->addr, message->mem);
if (message->cb != NULL)
{
// valid event, accept it
FsState = fs_ev->ev;
OstContext.image = fs_ev->image;
OstContext.sound = fs_ev->sound;
message->cb(true);
}
break;
default:
break;
}
}
else
{
LedState = 1 - LedState;
ost_hal_gpio_set(OST_GPIO_DEBUG_LED, LedState);
}
break;
}
}
}
void fs_task_scan_index()
void fs_task_read_block(uint32_t addr, uint8_t *block, fs_result_cb_t cb)
{
static ost_fs_event_t ReadBlockEv = {
.ev = FS_READ_SDCARD_BLOCK};
ReadBlockEv.mem = block;
ReadBlockEv.addr = addr;
ReadBlockEv.cb = cb;
qor_mbox_notify(&FsMailBox, (void **)&ReadBlockEv, QOR_MBOX_OPTION_SEND_BACK);
}
void fs_task_scan_index(fs_result_cb_t cb)
{
static ost_fs_event_t ScanIndexEv = {
.ev = FS_LOAD_INDEX};
.ev = FS_LOAD_INDEX,
.cb = NULL};
ScanIndexEv.cb = cb;
qor_mbox_notify(&FsMailBox, (void **)&ScanIndexEv, QOR_MBOX_OPTION_SEND_BACK);
}
void fs_task_play_index()
{
fs_task_image_start(OstContext.image);
fs_task_sound_start(OstContext.sound);
}
void fs_task_load_story(uint8_t *mem)
{
static ost_fs_event_t LoadRomxEv = {
.ev = FS_LOAD_STORY};
.ev = FS_LOAD_STORY,
.cb = NULL};
LoadRomxEv.mem = mem;
qor_mbox_notify(&FsMailBox, (void **)&LoadRomxEv, QOR_MBOX_OPTION_SEND_BACK);
}
void fs_task_media_start(char *image, char *sound)
void fs_task_sound_start(char *sound)
{
static ost_fs_event_t MediaStartEv = {
.ev = FS_DISPLAY_IMAGE};
.ev = FS_PLAY_SOUND,
.cb = NULL};
MediaStartEv.image = NULL;
MediaStartEv.sound = sound;
qor_mbox_notify(&FsMailBox, (void **)&MediaStartEv, QOR_MBOX_OPTION_SEND_BACK);
}
void fs_task_image_start(char *image)
{
static ost_fs_event_t MediaStartEv = {
.ev = FS_DISPLAY_IMAGE,
.cb = NULL};
MediaStartEv.image = image;
MediaStartEv.sound = sound;
MediaStartEv.sound = NULL;
qor_mbox_notify(&FsMailBox, (void **)&MediaStartEv, QOR_MBOX_OPTION_SEND_BACK);
}
void fs_task_initialize()
{
qor_mbox_init(&AudioMailBox, (void **)&AudioQueue, 10);
qor_mbox_init(&FsMailBox, (void **)&FsEventQueue, 10);
ost_audio_register_callback(audio_callback);

View file

@ -1,9 +1,16 @@
#ifndef FS_TASK_H
#define FS_TASK_H
void fs_task_scan_index();
#include <stdint.h>
typedef void (*fs_result_cb_t)(bool);
void fs_task_scan_index(fs_result_cb_t cb);
void fs_task_initialize();
void fs_task_load_story(uint8_t *mem);
void fs_task_media_start(char *image, char *sound);
void fs_task_image_start(char *image);
void fs_task_sound_start(char *sound);
void fs_task_play_index();
void fs_task_read_block(uint32_t addr, uint8_t *block, fs_result_cb_t cb);
#endif // FS_TASK_H

View file

@ -22,6 +22,7 @@
#include "system.h"
#include "vm_task.h"
#include "fs_task.h"
#include "tusb.h"
// ===========================================================================================================
// DEFINITIONS
@ -63,51 +64,55 @@ void HmiTask(void *args)
ost_hmi_event_t *e = NULL;
// filesystem_display_image("/ba869e4b-03d6-4249-9202-85b4cec767a7/images/bird.qoi");
// Start by scanning the index file
// fs_task_scan_index();
// init device stack on configured roothub port
tusb_init();
while (1)
{
uint32_t res = qor_mbox_wait(&HmiMailBox, (void **)&e, 1000);
if (res == QOR_MBOX_OK)
{
switch (OstState)
{
case OST_SYS_PLAY_STORY_TITLE:
break;
case OST_SYS_WAIT_USER_EVENT:
break;
case OST_SYS_WAIT_INDEX:
default:
break;
}
}
else
{
debug_printf("H"); // pour le debug only
}
// tud_task(); // tinyusb device task
qor_sleep(10);
}
}
void hmi_task_ost_ready(uint32_t number_of_stories)
{
static ost_hmi_event_t OsReadyEv = {
.ev = OST_SYS_PLAY_STORY_TITLE};
OstContext.number_of_stories = number_of_stories;
OstContext.current_story = 0;
qor_mbox_notify(&HmiMailBox, (void **)&OsReadyEv, QOR_MBOX_OPTION_SEND_BACK);
}
#include "msc_disk.h"
void hmi_task_initialize()
{
OstState = OST_SYS_WAIT_INDEX;
qor_mbox_init(&HmiMailBox, (void **)&HmiQueue, 10);
msc_disk_initialize();
qor_create_thread(&HmiTcb, HmiTask, HmiStack, sizeof(HmiStack) / sizeof(HmiStack[0]), HMI_TASK_PRIORITY, "HmiTask"); // less priority is the HMI (user inputs and LCD)
}
//--------------------------------------------------------------------+
// Device callbacks
//--------------------------------------------------------------------+
// Invoked when device is mounted
void tud_mount_cb(void)
{
// blink_interval_ms = BLINK_MOUNTED;
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
// blink_interval_ms = BLINK_NOT_MOUNTED;
}
// Invoked when usb bus is suspended
// remote_wakeup_en : if host allow us to perform remote wakeup
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
void tud_suspend_cb(bool remote_wakeup_en)
{
(void)remote_wakeup_en;
// blink_interval_ms = BLINK_SUSPENDED;
}
// Invoked when usb bus is resumed
void tud_resume_cb(void)
{
// blink_interval_ms = BLINK_MOUNTED;
}

View file

@ -260,10 +260,23 @@ bool __attribute__((naked)) qor_start(qor_tcb_t *idle_tcb, thread_func_t idle_ta
qor_create_thread(idle_tcb, idle_task, idle_stack, idle_stack_size, 0, "IdleTask");
// FIXME: use the scheduler to find the best first thread to start
IdleTcb = idle_tcb;
IdleTask = idle_task;
RunPt = TcbHead;
RunPt = IdleTcb;
// Find the best first thread to start (highest priority)
qor_tcb_t *t = TcbHead;
if (t != NULL)
{
while (t->next != NULL)
{
if (t->priority > RunPt->priority)
{
RunPt = t;
}
t = t->next;
}
}
timer_init();

View file

@ -20,7 +20,10 @@ typedef enum
{
VM_EV_NO_EVENT = 0,
VM_EV_START_STORY_EVENT = 0xA1,
VM_EV_BUTTON_EVENT = 0x88
VM_EV_EXEC_HOME_INDEX = 0xB5,
VM_EV_BUTTON_EVENT = 0x88,
VM_EV_END_OF_SOUND = 0x4E,
VM_EV_ERROR = 0xE0
} ost_vm_ev_type_t;
typedef struct
{
@ -31,9 +34,12 @@ typedef struct
typedef enum
{
OST_VM_STATE_WAIT_EVENT,
OST_VM_STATE_HOME,
OST_VM_STATE_HOME_WAIT_FS,
OST_VM_STATE_RUN_STORY,
OST_VM_STATE_WAIT_BUTTON,
OST_VM_STATE_ERROR //!< General error, cannot continue
} ost_vm_state_t;
// ===========================================================================================================
@ -51,8 +57,6 @@ static char CurrentStory[260]; // Current story path
static char ImageFile[260];
static char SoundFile[260];
static ost_vm_state_t VmState = OST_VM_STATE_WAIT_EVENT;
// ===========================================================================================================
// VIRTUAL MACHINE TASK
// ===========================================================================================================
@ -80,19 +84,13 @@ uint8_t vm_syscall(chip32_ctx_t *ctx, uint8_t code)
// Media
if (code == 1) // Execute media
{
char *image_ptr = NULL;
char *sound_ptr = NULL;
if (m_chip32_ctx.registers[R0] != 0)
{
// image file name address is in R0
// QString imageFile = m_model.BuildFullImagePath(GetFileNameFromMemory(m_chip32_ctx.registers[R0]));
// m_ostHmiDock->SetImage(imageFile);
get_file_from_memory(ImageFile, m_chip32_ctx.registers[R0]);
image_ptr = ImageFile;
}
else
{
// m_ostHmiDock->ClearImage();
fs_task_image_start(ImageFile);
}
if (m_chip32_ctx.registers[R1] != 0)
@ -102,9 +100,8 @@ uint8_t vm_syscall(chip32_ctx_t *ctx, uint8_t code)
// qDebug() << ", Sound: " << soundFile;
// m_model.PlaySoundFile(soundFile);
get_file_from_memory(SoundFile, m_chip32_ctx.registers[R1]);
sound_ptr = SoundFile;
fs_task_sound_start(SoundFile);
}
fs_task_media_start(image_ptr, sound_ptr);
retCode = SYSCALL_RET_WAIT_EV; // set the VM in pause
}
// WAIT EVENT bits:
@ -135,6 +132,16 @@ static void button_callback(uint32_t flags)
qor_mbox_notify(&VmMailBox, (void **)&ButtonEv, QOR_MBOX_OPTION_SEND_BACK);
}
static void read_index_callback(bool success)
{
static ost_vm_event_t ReadIndexEv = {
.ev = VM_EV_BUTTON_EVENT,
.story_dir = NULL};
ReadIndexEv.ev = success ? VM_EV_EXEC_HOME_INDEX : VM_EV_ERROR;
qor_mbox_notify(&VmMailBox, (void **)&ReadIndexEv, QOR_MBOX_OPTION_SEND_BACK);
}
void VmTask(void *args)
{
// VM Initialize
@ -151,57 +158,37 @@ void VmTask(void *args)
m_chip32_ctx.syscall = vm_syscall;
chip32_result_t run_result;
ost_vm_event_t *e = NULL;
ost_vm_event_t *message = NULL;
uint32_t res = 0;
bool isHome = true;
ost_vm_state_t VmState = OST_VM_STATE_HOME;
fs_task_scan_index(read_index_callback);
isHome = true;
while (1)
{
switch (VmState)
{
case OST_VM_STATE_RUN_STORY:
do
{
run_result = chip32_step(&m_chip32_ctx);
} while (run_result == VM_OK);
// pour le moment, dans tous les cas on attend un événement
// Que ce soit par erreur, ou l'attente un appui boutton...
VmState = OST_VM_STATE_WAIT_EVENT;
break;
case OST_VM_STATE_WAIT_EVENT:
default:
res = qor_mbox_wait(&VmMailBox, (void **)&e, 300); // On devrait recevoir un message toutes les 3ms (durée d'envoi d'un buffer I2S)
res = qor_mbox_wait(&VmMailBox, (void **)&message, 300); // On devrait recevoir un message toutes les 3ms (durée d'envoi d'un buffer I2S)
if (res == QOR_MBOX_OK)
{
if (isHome)
switch (VmState)
{
if (e->ev == VM_EV_BUTTON_EVENT)
case OST_VM_STATE_HOME:
switch (message->ev)
{
if ((e->button_mask & OST_BUTTON_OK) == OST_BUTTON_OK)
{
// Load story from disk
debug_printf("Clicked OK\r\n");
fs_task_load_story(m_rom_data);
}
}
else if (e->ev == VM_EV_START_STORY_EVENT)
{
// Launch the execution of a story
chip32_initialize(&m_chip32_ctx);
VmState = OST_VM_STATE_RUN_STORY;
}
}
else
{
}
}
case VM_EV_EXEC_HOME_INDEX:
// La lecture de l'index est terminée, on demande l'affichage des médias
fs_task_play_index();
break;
case VM_EV_ERROR:
default:
break;
}
default:
break;
}
}
}
}
@ -213,12 +200,66 @@ void vm_task_start_story()
qor_mbox_notify(&VmMailBox, (void **)&VmStartEvent, QOR_MBOX_OPTION_SEND_BACK);
}
void vm_task_sound_finished()
{
static ost_vm_event_t VmEndOfSoundEvent;
VmEndOfSoundEvent.ev = VM_EV_END_OF_SOUND;
qor_mbox_notify(&VmMailBox, (void **)&VmEndOfSoundEvent, QOR_MBOX_OPTION_SEND_BACK);
}
void vm_task_initialize()
{
VmState = OST_VM_STATE_WAIT_EVENT;
qor_mbox_init(&VmMailBox, (void **)&VmQueue, 10);
qor_create_thread(&VmTcb, VmTask, VmStack, sizeof(VmStack) / sizeof(VmStack[0]), VM_TASK_PRIORITY, "VmTask");
ost_button_register_callback(button_callback);
}
/*
case OST_VM_STATE_RUN_STORY:
do
{
run_result = chip32_step(&m_chip32_ctx);
} while (run_result == VM_OK);
// pour le moment, dans tous les cas on attend un événement
// Que ce soit par erreur, ou l'attente un appui boutton...
VmState = OST_VM_STATE_WAIT_BUTTON;
break;
case OST_VM_STATE_ERROR:
qor_sleep(20);
break;
case OST_VM_STATE_WAIT_BUTTON:
default:
if (isHome)
{
if (message->ev == VM_EV_BUTTON_EVENT)
{
if ((message->button_mask & OST_BUTTON_OK) == OST_BUTTON_OK)
{
// Load story from disk
debug_printf("Clicked OK\r\n");
fs_task_load_story(m_rom_data);
}
}
else if (message->ev == VM_EV_START_STORY_EVENT)
{
// Launch the execution of a story
chip32_initialize(&m_chip32_ctx);
isHome = false;
VmState = OST_VM_STATE_RUN_STORY;
}
}
else
{
// VM en cours d'exécution
if (message->ev == VM_EV_END_OF_SOUND)
{
VmState = OST_VM_STATE_RUN_STORY;
}
}
}
*/

View file

@ -1,7 +1,13 @@
#ifndef VM_TASK_H
#define VM_TASK_H
#include <stdint.h>
#include <stdbool.h>
typedef void (*vm_result_cb_t)(bool);
void vm_task_start_story();
void vm_task_initialize();
void vm_task_sound_finished();
#endif // VM_TASK_H