diff --git a/.gitignore b/.gitignore index 3ec4cab..5ce8d86 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,7 @@ build-story-player-Desktop_Qt_GCC_64bit-Debug/ software/build/ software/.vscode/.cortex-debug.registers.state.json + +story-player/build/ + +story-editor/tools/build-audio-System_Qt5_15_8-Debug/ diff --git a/software/.vscode/launch.json b/software/.vscode/launch.json index 1682083..ce2c7a0 100644 --- a/software/.vscode/launch.json +++ b/software/.vscode/launch.json @@ -23,6 +23,7 @@ "executable": "${workspaceRoot}/build/RaspberryPico/open-story-teller.elf", "request": "launch", "type": "cortex-debug", + "showDevDebugOutput": "raw", "BMPGDBSerialPort": "/dev/ttyACM0", "servertype": "bmp", "interface": "swd", diff --git a/software/.vscode/settings.json b/software/.vscode/settings.json index 1714e50..805af03 100644 --- a/software/.vscode/settings.json +++ b/software/.vscode/settings.json @@ -9,6 +9,23 @@ "sdcard.h": "c", "ff.h": "c", "debug.h": "c", - "ffconf.h": "c" + "ffconf.h": "c", + "picture.h": "c", + "ost_tasker.h": "c", + "audio_pio.h": "c", + "audio_player.h": "c", + "compare": "c", + "pio.h": "c", + "*.tcc": "c", + "memory_resource": "c", + "memory": "c", + "ostream": "c", + "streambuf": "c", + "random": "c", + "algorithm": "c", + "gpio.h": "c", + "pico_i2s.h": "c", + "stdlib.h": "c", + "os.h": "c" } } \ No newline at end of file diff --git a/software/CMakeLists.txt b/software/CMakeLists.txt index c0f6ac9..ecdbe61 100644 --- a/software/CMakeLists.txt +++ b/software/CMakeLists.txt @@ -35,7 +35,9 @@ set(OST_SRCS system/debug.c system/picture.c system/filesystem.c - system/ost_tasker.c + system/os.c + system/qor_armv6m.s + system/audio_player.c system/ff/ff.c system/ff/ffsystem.c system/ff/ff_stubs.c @@ -132,7 +134,7 @@ elseif(${OST_BUNDLE} STREQUAL "RASPI_PICO") # ------ Link target_link_libraries(${PROJECT_NAME} PUBLIC "-Wl,--start-group" - gcc m c stdc++ raspberry-pico-w pico_stdlib hardware_spi hardware_dma "-Wl,--end-group") + gcc m c stdc++ raspberry-pico-w pico_stdlib hardware_spi hardware_dma hardware_pio "-Wl,--end-group") set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME "${PROJECT_NAME}") pico_add_extra_outputs(${PROJECT_NAME}) diff --git a/software/include/ost_hal.h b/software/include/ost_hal.h index d5cd941..b833f09 100644 --- a/software/include/ost_hal.h +++ b/software/include/ost_hal.h @@ -41,12 +41,17 @@ extern "C" } ost_hal_gpio_t; // ---------------------------------------------------------------------------- - // SYSTEM HAL + // HIGH LEVEL API // ---------------------------------------------------------------------------- void ost_system_initialize(); void system_putc(char ch); void ost_system_delay_ms(uint32_t delay); + void ost_audio_play(const char *filename); + + // ---------------------------------------------------------------------------- + // GPIO HAL + // ---------------------------------------------------------------------------- int ost_hal_gpio_get(ost_hal_gpio_t gpio); void ost_hal_gpio_set(ost_hal_gpio_t gpio, int value); @@ -93,6 +98,13 @@ extern "C" uint8_t ost_display_transfer_byte(uint8_t dat); void ost_display_transfer_multi(uint8_t *buff, uint32_t btr); + // ---------------------------------------------------------------------------- + // AUDIO HAL + // ---------------------------------------------------------------------------- + void ost_hal_audio_frame_end(); + void ost_hal_audio_frame_start(const volatile void *, int dma_trans_number); + void ost_hal_audio_loop(); + #ifdef __cplusplus } #endif diff --git a/software/platform/raspberry-pico-w/CMakeLists.txt b/software/platform/raspberry-pico-w/CMakeLists.txt index 630e8fb..e91746f 100644 --- a/software/platform/raspberry-pico-w/CMakeLists.txt +++ b/software/platform/raspberry-pico-w/CMakeLists.txt @@ -18,17 +18,19 @@ set(PICO_SRCS ${CMAKE_CURRENT_LIST_DIR}/pico_hal_wrapper.c ${CMAKE_CURRENT_LIST_DIR}/pico_lcd_spi.c ${CMAKE_CURRENT_LIST_DIR}/pico_sdcard_spi.c + ${CMAKE_CURRENT_LIST_DIR}/pico_i2s.c ) -include_directories(../../src ../../hal ../../library) +include_directories(../../src ../../hal ../../library .) add_library( ${PROJECT_NAME} INTERFACE - - # ${PICO_SRCS} ) +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) target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) diff --git a/software/platform/raspberry-pico-w/i2s.pio b/software/platform/raspberry-pico-w/i2s.pio new file mode 100644 index 0000000..05d914c --- /dev/null +++ b/software/platform/raspberry-pico-w/i2s.pio @@ -0,0 +1,253 @@ +; i2s.pio +; +; Author: Daniel Collins +; Date: 2022-02-25 +; +; Copyright (c) 2022 Daniel Collins +; +; This file is part of rp2040_i2s_example. +; +; rp2040_i2s_example is free software: you can redistribute it and/or modify it under +; the terms of the GNU General Public License, version 3 as published by the +; Free Software Foundation. +; +; rp2040_i2s_example is distributed in the hope that it will be useful, but WITHOUT ANY +; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +; A PARTICULAR PURPOSE. See the GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License along with +; rp2040_i2s_example. If not, see . + +; An I2S bi-directional peripheral with master clock (SCK) output. + +.program i2s_sck +; Transmit an I2S system / master clock. +; +; Usually this needs to be 256 * fs (the word clock rate). +; (e.g. for 48kHz audio, this should be precisely 12.288000 MHz.) +; Since all of these transmit at 1 I2S-clock per 2 sys-clock cycles, +; you need to set the clock divider at 2x the desired rate +; (for the 48kHz example, this should be 2 * 12,288,000 = 24.5760000 MHz). +; +; Use this as one state machine of the I2S PIO unit as a whole for perfect +; synchronization, e.g.: +; state machine 0 = clock (divide to 48000 * 256 * 2 = 24.576000 MHz for 48kHz stereo audio) +; state machine 1 = out (divide to 48000 * 64 * 2 = 6.144000 MHz for 48kHz stereo audio) +; state machine 2 = in (divide to 48000 * 256 * 2 = 24.576000 MHz for 48kHz stereo audio) +; +; One output pin is used for the clock output. + +.wrap_target + set pins 1 + set pins 0 +.wrap + +.program i2s_out_master +; I2S audio output block. Synchronous with clock and input. +; Must run at BCK * 2. +; +; This block also outputs the word clock (also called frame or LR clock) and +; the bit clock. +; +; Set register x to (bit depth - 2) (e.g. for 24 bit audio, set to 22). +; Note that if this is needed to be synchronous with the SCK module, +; it is not possible to run 24-bit frames with an SCK of 256x fs. You must either +; run SCK at 384x fs (if your codec permits this) or use 32-bit frames, which +; work fine with 24-bit codecs. + +.side_set 2 + +public entry_point: + ; /--- LRCLK + ; |/-- BCLK +frameL: ; || + set x, 30 side 0b00 ; start of Left frame + pull noblock side 0b01 ; One clock after edge change with no data +dataL: + out pins, 1 side 0b00 + jmp x-- dataL side 0b01 + +frameR: + set x, 30 side 0b10 + pull noblock side 0b11 ; One clock after edge change with no data +dataR: + out pins, 1 side 0b10 + jmp x-- dataR side 0b11 + +.program i2s_bidi_slave +; I2S audio bidirectional (input/output) block, both in slave mode. +; Requires external BCK and LRCK, usually from the codec directly. +; This block provides both the output and input components together, +; so should not be used in combination with either of the other output or +; input units on the same I2S bus. +; +; Input pin order: DIN, BCK, LRCK +; Set JMP pin to LRCK. +; +; Clock synchronously with the system clock, or *at least* 4x the usual +; bit clock for a given fs (e.g. for 48kHz 24-bit, clock at at least +; (48000 * 24 * 2 (stereo)) * 4 = 9.216 MHz. Ideally 8x or more. + +start_l: + wait 1 pin 1 ; DIN should be sampled on rising transition of BCK + in pins, 1 ; first "bit" of new frame is actually LSB of last frame, per I2S + pull noblock ; + push noblock ; + wait 0 pin 1 ; ignore first BCK transition after edge +public_entry_point: + wait 0 pin 2 ; wait for L frame to come around before we start + out pins 1 ; update DOUT on falling BCK edge +loop_l: + wait 1 pin 1 ; DIN should be sampled on rising transition of BCK + in pins, 1 ; read DIN + wait 0 pin 1 ; DOUT should be updated on falling transition of BCK + out pins 1 ; update DOUT + jmp pin start_r ; if LRCK has gone high, we're "done" with this word (1 bit left to read) + jmp loop_l ; +start_r: + wait 1 pin 1 ; wait for the last bit of the previous frame + in pins, 1 ; first "bit" of new frame is actually LSB of last frame, per I2S + pull noblock ; pull next output word from FIFO + push noblock ; push the completed word to the FIFO + wait 0 pin 1 ; wait for next clock cycle + out pins 1 ; update DOUT on falling edge +loop_r: + wait 1 pin 1 ; + in pins 1 ; + wait 0 pin 1 ; + out pins 1 ; update DOUT + jmp pin loop_r ; if LRCK is still high, we're still sampling this word + ; implicit jmp to start_l: otherwise, start the loop over + + +; I2S Audio Input - Slave or Synchronous with Output Master +; Inputs must be sequential in order: DIN, BCK, LRCK +; Must run at same speed of SCK block, or at least 4x BCK. +; +; NOTE: Set JMP pin to LRCK pin. +; NOTE: The very first word read is potentially corrupt, since there is a +; chance to start in the middle of an L frame and only read part of it. +; Nevertheless, the frame order should be synchronized (first word is L, +; second is R, etc.) + +.program i2s_in_slave + +start_sample_l: + wait 1 pin 1 ; DIN should be sampled on rising transition of BCK + in pins, 1 ; first "bit" of new frame is actually LSB of last frame, per I2S + push noblock ; push however many bits we read into the FIFO + wait 0 pin 1 ; ignore first BCK transition after edge +public_entry_point: + wait 0 pin 2 ; wait for L frame to come around before we start +sample_l: + wait 1 pin 1 ; DIN should be sampled on rising transition of BCK + in pins, 1 ; read DIN + wait 0 pin 1 ; don't sample more than once per rising edge, wait for next clock + jmp pin start_sample_r ; if LRCK has gone high, we're "done" with this word (1 bit left to read) + jmp sample_l +start_sample_r: + wait 1 pin 1 ; wait for the last bit of the previous frame + in pins, 1 ; first "bit" of new frame is actually LSB of last frame, per I2S + push noblock ; push the completed word to the FIFO + wait 0 pin 1 ; wait for next clock cycle +sample_r: + wait 1 pin 1 + in pins, 1 + wait 0 pin 1 + jmp pin sample_r ; if LRCK is still high, we're still sampling this word + ; implicit jmp to start_sample_l: otherwise, start the loop over + +% c-sdk { + +// These constants are the I2S clock to pio clock ratio +const int i2s_sck_program_pio_mult = 2; +const int i2s_out_master_program_pio_mult = 2; + +/* + * System ClocK (SCK) is only required by some I2S peripherals. + * This outputs it at 1 SCK per 2 PIO clocks, so scale the dividers correctly + * first. + * NOTE: Most peripherals require that this is *perfectly* aligned in ratio, + * if not phase, to the bit and word clocks of any master peripherals. + * It is up to you to ensure that the divider config is set up for a + * precise (not approximate) ratio between the BCK, LRCK, and SCK outputs. + */ +static void i2s_sck_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t sck_pin) { + pio_gpio_init(pio, sck_pin); + pio_sm_config sm_config = i2s_sck_program_get_default_config(offset); + sm_config_set_set_pins(&sm_config, sck_pin, 1); + + uint pin_mask = (1u << sck_pin); + pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // zero output + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); + + pio_sm_init(pio, sm, offset, &sm_config); +} + +static inline void i2s_out_master_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t bit_depth, uint8_t dout_pin, uint8_t clock_pin_base) { + pio_gpio_init(pio, dout_pin); + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + + pio_sm_config sm_config = i2s_out_master_program_get_default_config(offset); + sm_config_set_out_pins(&sm_config, dout_pin, 1); + sm_config_set_sideset_pins(&sm_config, clock_pin_base); + sm_config_set_out_shift(&sm_config, false, false, bit_depth); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX); + pio_sm_init(pio, sm, offset, &sm_config); + + uint32_t pin_mask = (1u << dout_pin) | (3u << clock_pin_base); + pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // zero output + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); +} + +static inline void i2s_bidi_slave_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t dout_pin, uint8_t in_pin_base) { + pio_gpio_init(pio, dout_pin); + pio_gpio_init(pio, in_pin_base); + pio_gpio_init(pio, in_pin_base + 1); + pio_gpio_init(pio, in_pin_base + 2); + + pio_sm_config sm_config = i2s_bidi_slave_program_get_default_config(offset); + sm_config_set_out_pins(&sm_config, dout_pin, 1); + sm_config_set_in_pins(&sm_config, in_pin_base); + sm_config_set_jmp_pin(&sm_config, in_pin_base + 2); + sm_config_set_out_shift(&sm_config, false, false, 0); + sm_config_set_in_shift(&sm_config, false, false, 0); + pio_sm_init(pio, sm, offset, &sm_config); + + // Setup output pins + uint32_t pin_mask = (1u << dout_pin); + pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // zero output + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); + + // Setup input pins + pin_mask = (7u << in_pin_base); // Three input pins + pio_sm_set_pindirs_with_mask(pio, sm, 0, pin_mask); +} + +/* + * Designed to be used with output master module, requiring overlapping pins: + * din_pin_base + 0 = input pin + * din_pin_base + 1 = out_master clock_pin_base + * din_pin_base + 2 = out_master clock_pin_base + 1 + * + * Intended to be run at SCK rate (4x BCK), so clock same as SCK module if using + * it, or 4x the BCK frequency (BCK is 64x fs, so 256x fs). + */ +static inline void i2s_in_slave_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t din_pin_base) { + pio_gpio_init(pio, din_pin_base); + gpio_set_pulls(din_pin_base, false, false); + gpio_set_dir(din_pin_base, GPIO_IN); + + pio_sm_config sm_config = i2s_in_slave_program_get_default_config(offset); + sm_config_set_in_pins(&sm_config, din_pin_base); + sm_config_set_in_shift(&sm_config, false, false, 0); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_RX); + sm_config_set_jmp_pin(&sm_config, din_pin_base + 2); + pio_sm_init(pio, sm, offset, &sm_config); + + uint32_t pin_mask = (7u << din_pin_base); // Three input pins + pio_sm_set_pindirs_with_mask(pio, sm, 0, pin_mask); +} + +%} \ No newline at end of file diff --git a/software/platform/raspberry-pico-w/pico_hal_wrapper.c b/software/platform/raspberry-pico-w/pico_hal_wrapper.c index 0882b05..155f5a7 100644 --- a/software/platform/raspberry-pico-w/pico_hal_wrapper.c +++ b/software/platform/raspberry-pico-w/pico_hal_wrapper.c @@ -10,13 +10,22 @@ #include "pico/stdlib.h" #include "hardware/uart.h" #include "hardware/spi.h" +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/pio.h" +#include "hardware/clocks.h" #include "pico.h" // Local #include "pico_lcd_spi.h" #include "pico_sdcard_spi.h" +#include "audio_player.h" +#include "pico_i2s.h" static volatile uint32_t msTicks = 0; +static audio_ctx_t audio_ctx; + +void __isr __time_critical_func(audio_i2s_dma_irq_handler)(); // ---------------------------------------------------------------------------- // SYSTEM HAL @@ -32,6 +41,10 @@ static volatile uint32_t msTicks = 0; static struct repeating_timer sys_timer; +// static audio_i2s_config_t i2s_config = {28, 26, 0}; + +static __attribute__((aligned(8))) pio_i2s i2s; + void ost_system_delay_ms(uint32_t delay) { sleep_ms(delay); @@ -45,24 +58,44 @@ const uint8_t LCD_BL = 13; const uint8_t ROTARY_A = 6; const uint8_t ROTARY_B = 7; +const uint8_t ROTARY_BUTTON = 3; const uint8_t SD_CARD_CS = 17; const uint8_t SD_CARD_PRESENCE = 24; -extern void disk_timerproc(); +#include "os.h" static bool sys_timer_callback(struct repeating_timer *t) { msTicks++; - // disk_timerproc(); + // qor_switch_context(); + return true; } +extern void init_spi(void); + +void dma_init(); + +void gpio_callback(uint gpio, uint32_t events) +{ + static bool one_time = true; + + // if (one_time) + { + one_time = false; + // debouncer + debug_printf("G\n"); + + qor_switch_context(); + } +} + void ost_system_initialize() { - // stdio_init_all(); + set_sys_clock_khz(125000, true); ////------------------- Init DEBUG LED gpio_init(LED_PIN); @@ -107,18 +140,42 @@ void ost_system_initialize() gpio_init(ROTARY_B); gpio_set_dir(ROTARY_B, GPIO_IN); + gpio_init(ROTARY_BUTTON); + gpio_set_dir(ROTARY_BUTTON, GPIO_IN); + gpio_disable_pulls(ROTARY_BUTTON); + + gpio_set_irq_enabled_with_callback(ROTARY_BUTTON, GPIO_IRQ_EDGE_FALL, true, &gpio_callback); + //------------------- Init SDCARD - gpio_init(SD_CARD_CS); - gpio_put(SD_CARD_CS, 1); - gpio_set_dir(SD_CARD_CS, GPIO_OUT); + // gpio_init(SD_CARD_CS); + // gpio_put(SD_CARD_CS, 1); + // gpio_set_dir(SD_CARD_CS, GPIO_OUT); - gpio_init(SD_CARD_PRESENCE); - gpio_set_dir(SD_CARD_PRESENCE, GPIO_IN); + // gpio_init(SD_CARD_PRESENCE); + // gpio_set_dir(SD_CARD_PRESENCE, GPIO_IN); - pico_sdcard_spi_init(10000); + // pico_sdcard_spi_init(1000000); + + //------------------- Init Sound + static const audio_i2s_config_t config = { + .freq = 44100, + .bps = 32, + .data_pin = 28, + .clock_pin_base = 26}; + + // i2s_program_start_synched(pio0, audio_i2s_dma_irq_handler, &i2s, &config); + + // pico_i2s_setup(&config); + + init_spi(); + + audio_init(&audio_ctx); //------------------- System timer (1ms) - add_repeating_timer_ms(1, sys_timer_callback, NULL, &sys_timer); + // add_repeating_timer_ms(1, sys_timer_callback, NULL, &sys_timer); + + // ------------ Everything is initialized, print stuff here + debug_printf("System Clock: %lu\n", clock_get_hz(clk_sys)); } void system_putc(char ch) @@ -234,3 +291,128 @@ void ost_display_transfer_multi(uint8_t *buff, uint32_t btr) { pico_lcd_spi_transfer(buff, btr); } + +// ---------------------------------------------------------------------------- +// AUDIO HAL +// ---------------------------------------------------------------------------- + +// extern shared_state_t shared_state; + +void hal_audio_test(); + +#include "pico/critical_section.h" + +critical_section_t acrit; + +void ost_audio_play(const char *filename) +{ + +#ifndef OST_AUDIO_TEST + + audio_play(&audio_ctx, filename); + + critical_section_init(&acrit); + + // audio_i2s_set_enabled(true); + audio_process(&audio_ctx); +#else + hal_audio_test(); +#endif + + // audio_step = 1; +} + +#ifndef OST_AUDIO_TEST + +void ost_hal_audio_frame_end() +{ + + critical_section_enter_blocking(&acrit); + critical_section_exit(&acrit); + + // uint dma_channel = shared_state.dma_channel; + // if (dma_irqn_get_channel_status(PICO_AUDIO_I2S_DMA_IRQ, dma_channel)) + // { + // dma_irqn_acknowledge_channel(PICO_AUDIO_I2S_DMA_IRQ, dma_channel); + // } +} + +void ost_hal_audio_frame_start(const volatile void *buff, int dma_trans_number) +{ + // dma_channel_transfer_from_buffer_now(shared_state.dma_channel, buff, dma_trans_number); + // dma_channel_start(shared_state.dma_channel); + + dma_hw->ints0 = 1u << i2s.dma_ch_out_data; // clear the IRQ +} + +void __isr __time_critical_func(audio_i2s_dma_irq_handler)() +{ + /* + uint dma_channel = shared_state.dma_channel; + if (dma_irqn_get_channel_status(PICO_AUDIO_I2S_DMA_IRQ, dma_channel)) + { + dma_irqn_acknowledge_channel(PICO_AUDIO_I2S_DMA_IRQ, dma_channel); + } + + audio_process(&audio_ctx); + */ +} + +void ost_hal_audio_loop() +{ +} + +#else + +#define SAMPLE_RATE (44100) +#define DMA_BUF_LEN (32) +#define I2S_NUM (0) +#define WAVE_FREQ_HZ (235.0f) +#define TWOPI (6.28318531f) +#define PHASE_INC (TWOPI * WAVE_FREQ_HZ / SAMPLE_RATE) + +// Accumulated phase +static float p = 0.0f; + +// Output buffer (2ch interleaved) +static uint32_t out_buf[DMA_BUF_LEN * 2]; + +uint32_t audio_count = 0; + +#include + +// Fill the output buffer and write to I2S DMA +static void write_buffer() +{ + // out_buf[0] = 0x70010001; + // out_buf[1] = 0xAAAA0001; + + dma_channel_transfer_from_buffer_now(shared_state.dma_channel, out_buf, 2); + dma_channel_start(shared_state.dma_channel); + + // You could put a taskYIELD() here to ensure other tasks always have a chance to run. + // taskYIELD(); +} + +void hal_audio_test() +{ + audio_i2s_set_enabled(true); + audio_count = 0; + write_buffer(); +} + +// irq handler for DMA +void __isr __time_critical_func(audio_i2s_dma_irq_handler)() +{ + uint dma_channel = shared_state.dma_channel; + if (dma_irqn_get_channel_status(PICO_AUDIO_I2S_DMA_IRQ, dma_channel)) + { + dma_irqn_acknowledge_channel(PICO_AUDIO_I2S_DMA_IRQ, dma_channel); + } + + write_buffer(); + // busy_wait_ms(1); + // audio_process(&audio_ctx); +} + +#endif diff --git a/software/platform/raspberry-pico-w/pico_i2s.c b/software/platform/raspberry-pico-w/pico_i2s.c new file mode 100644 index 0000000..8a75014 --- /dev/null +++ b/software/platform/raspberry-pico-w/pico_i2s.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include + +#include "pico_i2s.h" +#include "pico_i2s.pio.h" +#include "hardware/pio.h" +#include "hardware/gpio.h" +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/clocks.h" + +#include "debug.h" + +void pico_i2s_set_frequency(const audio_i2s_config_t *config) +{ + float bitClk = config->freq * config->bps * 2.0 /* channels */ * 2.0 /* edges per clock */; + // pio_sm_set_clkdiv(audio_pio, shared_state.pio_sm, (float)clock_get_hz(clk_sys) / bitClk); +} + +#if 0 + +static bool audio_enabled; + +void audio_i2s_set_enabled(bool enabled) +{ + if (enabled != audio_enabled) + { +#ifndef NDEBUG + if (enabled) + { + puts("Enabling PIO I2S audio\n"); + printf("(on core %d\n", get_core_num()); + } +#endif + irq_set_enabled(DMA_IRQ_0 + PICO_AUDIO_I2S_DMA_IRQ, enabled); + + if (enabled) + { + // audio_start_dma_transfer(); + } + else + { + /* + // if there was a buffer in flight, it will not be freed by DMA IRQ, let's do it manually + if (shared_state.playing_buffer) + { + give_audio_buffer(audio_i2s_consumer, shared_state.playing_buffer); + shared_state.playing_buffer = NULL; + } + */ + } + + pio_sm_set_enabled(audio_pio, shared_state.pio_sm, enabled); + + audio_enabled = enabled; + } +} +#endif + +//--------------------------------------------------------------------------------------------------------------------------- + +static void dma_double_buffer_init(pio_i2s *i2s, void (*dma_handler)(void)) +{ + i2s->dma_ch_out_ctrl = dma_claim_unused_channel(true); + i2s->dma_ch_out_data = dma_claim_unused_channel(true); + + i2s->out_ctrl_blocks[0] = i2s->output_buffer; + i2s->out_ctrl_blocks[1] = &i2s->output_buffer[STEREO_BUFFER_SIZE]; + + dma_channel_config c = dma_channel_get_default_config(i2s->dma_ch_out_ctrl); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_ring(&c, false, 3); + channel_config_set_transfer_data_size(&c, DMA_SIZE_32); + dma_channel_configure(i2s->dma_ch_out_ctrl, &c, &dma_hw->ch[i2s->dma_ch_out_data].al3_read_addr_trig, i2s->out_ctrl_blocks, 1, false); + + c = dma_channel_get_default_config(i2s->dma_ch_out_data); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_chain_to(&c, i2s->dma_ch_out_ctrl); + channel_config_set_dreq(&c, pio_get_dreq(i2s->pio, i2s->sm_dout, true)); + + dma_channel_configure(i2s->dma_ch_out_data, + &c, + &i2s->pio->txf[i2s->sm_dout], + NULL, + STEREO_BUFFER_SIZE, + false); + + dma_channel_set_irq0_enabled(i2s->dma_ch_out_data, true); + irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); + irq_set_enabled(DMA_IRQ_0, true); + + dma_channel_start(i2s->dma_ch_out_ctrl); +} + +static void i2s_sync_program_init(PIO pio, pio_i2s *i2s, audio_i2s_config_t *config) +{ + uint offset = 0; + i2s->pio = pio; + i2s->sm_mask = 0; + + i2s->sm_dout = pio_claim_unused_sm(pio, true); + i2s->sm_mask |= (1u << i2s->sm_dout); + offset = pio_add_program(pio0, &i2s_out_master_program); + // 4th argument is bit depth, 5th dout, 6th bclk pin base (lrclk is bclk pin + 1) + i2s_out_master_program_init(pio, i2s->sm_dout, offset, config->bps, config->data_pin, config->clock_pin_base); + // pio_sm_set_clkdiv(pio, i2s->sm_dout, 89); // Approximately 11KHz audio +} + +void i2s_program_start_synched(PIO pio, void (*dma_handler)(void), pio_i2s *i2s, audio_i2s_config_t *config) +{ + // i2s_sync_program_init(pio, i2s); + pico_i2s_set_frequency(config); + dma_double_buffer_init(i2s, dma_handler); + pio_enable_sm_mask_in_sync(i2s->pio, i2s->sm_mask); +} diff --git a/software/platform/raspberry-pico-w/pico_i2s.h b/software/platform/raspberry-pico-w/pico_i2s.h new file mode 100644 index 0000000..033b9c5 --- /dev/null +++ b/software/platform/raspberry-pico-w/pico_i2s.h @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef PICO_I2S_H +#define PICO_I2S_H + +#include + +#include "hardware/dma.h" +#include "hardware/irq.h" +#include "hardware/pio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if 0 + +/** \file audio_i2s.h + * \defgroup pico_audio_i2s pico_audio_i2s + * I2S audio output using the PIO + * + * This library uses the \ref hardware_pio system to implement a I2S audio interface + * + * \todo Must be more we need to say here. + * \todo certainly need an example + * + */ + +#ifndef PICO_AUDIO_I2S_DMA_IRQ +#ifdef PICO_AUDIO_DMA_IRQ +#define PICO_AUDIO_I2S_DMA_IRQ PICO_AUDIO_DMA_IRQ +#else +#define PICO_AUDIO_I2S_DMA_IRQ 0 +#endif +#endif + +#ifndef PICO_AUDIO_I2S_PIO +#ifdef PICO_AUDIO_PIO +#define PICO_AUDIO_I2S_PIO PICO_AUDIO_PIO +#else +#define PICO_AUDIO_I2S_PIO 0 +#endif +#endif + +#if !(PICO_AUDIO_I2S_DMA_IRQ == 0 || PICO_AUDIO_I2S_DMA_IRQ == 1) +#error PICO_AUDIO_I2S_DMA_IRQ must be 0 or 1 +#endif + +#if !(PICO_AUDIO_I2S_PIO == 0 || PICO_AUDIO_I2S_PIO == 1) +#error PICO_AUDIO_I2S_PIO ust be 0 or 1 +#endif + +#ifndef PICO_AUDIO_I2S_MAX_CHANNELS +#ifdef PICO_AUDIO_MAX_CHANNELS +#define PICO_AUDIO_I2S_MAX_CHANNELS PICO_AUDIO_MAX_CHANNELS +#else +#define PICO_AUDIO_I2S_MAX_CHANNELS 2u +#endif +#endif + +#ifndef PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL +#ifdef PICO_AUDIO_BUFFERS_PER_CHANNEL +#define PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL PICO_AUDIO_BUFFERS_PER_CHANNEL +#else +#define PICO_AUDIO_I2S_BUFFERS_PER_CHANNEL 3u +#endif +#endif + +#ifndef PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH +#ifdef PICO_AUDIO_BUFFER_SAMPLE_LENGTH +#define PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH PICO_AUDIO_BUFFER_SAMPLE_LENGTH +#else +#define PICO_AUDIO_I2S_BUFFER_SAMPLE_LENGTH 576u +#endif +#endif + +#ifndef PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH +#ifdef PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH +#define PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH PICO_AUDIO_SILENCE_BUFFER_SAMPLE_LENGTH +#else +#define PICO_AUDIO_I2S_SILENCE_BUFFER_SAMPLE_LENGTH 256u +#endif +#endif + +// Allow use of pico_audio driver without actually doing anything much +#ifndef PICO_AUDIO_I2S_NOOP +#ifdef PICO_AUDIO_NOOP +#define PICO_AUDIO_I2S_NOOP PICO_AUDIO_NOOP +#else +#define PICO_AUDIO_I2S_NOOP 0 +#endif +#endif + +#ifndef PICO_AUDIO_I2S_MONO_INPUT +#define PICO_AUDIO_I2S_MONO_INPUT 0 +#endif +#ifndef PICO_AUDIO_I2S_MONO_OUTPUT +#define PICO_AUDIO_I2S_MONO_OUTPUT 0 +#endif + +#ifndef PICO_AUDIO_I2S_DATA_PIN +// #warning PICO_AUDIO_I2S_DATA_PIN should be defined when using AUDIO_I2S +#define PICO_AUDIO_I2S_DATA_PIN 28 +#endif + +#ifndef PICO_AUDIO_I2S_CLOCK_PIN_BASE +// #warning PICO_AUDIO_I2S_CLOCK_PIN_BASE should be defined when using AUDIO_I2S +#define PICO_AUDIO_I2S_CLOCK_PIN_BASE 26 +#endif + + /** \brief Base configuration structure used when setting up + * \ingroup pico_audio_i2s + */ + typedef struct + { + uint32_t freq; + uint32_t bps; + uint8_t data_pin; + uint8_t clock_pin_base; + + } audio_i2s_config_t; + + typedef struct + { + uint32_t freq; + uint8_t pio_sm; + uint8_t dma_channel; + } shared_state_t; + + typedef struct audio_format + { + uint32_t sample_freq; ///< Sample frequency in Hz + uint16_t format; ///< Audio format \ref audio_formats + uint16_t channel_count; ///< Number of channels + } audio_format_t; + + /** \brief Set up system to output I2S audio + * \ingroup pico_audio_i2s + * + * \param intended_audio_format \todo + * \param config The configuration to apply. + */ + void pico_i2s_setup(const audio_i2s_config_t *config); + + /** \brief Set up system to output I2S audio + * \ingroup pico_audio_i2s + * + * \param enable true to enable I2S audio, false to disable. + */ + void audio_i2s_set_enabled(bool enabled); + + void audio_start_dma_transfer(const int32_t *bytes, uint32_t count); + +#endif + //---------------------------------------------------------------------------------- + //---------------------------------------------------------------------------------- + //---------------------------------------------------------------------------------- + //---------------------------------------------------------------------------------- + //---------------------------------------------------------------------------------- + +#define AUDIO_BUFFER_FRAMES 48 +#define STEREO_BUFFER_SIZE AUDIO_BUFFER_FRAMES * 2 + + typedef struct + { + uint32_t freq; + uint32_t bps; + uint8_t data_pin; + uint8_t clock_pin_base; + + } audio_i2s_config_t; + + typedef struct pio_i2s + { + PIO pio; + uint8_t sm_mask; + uint8_t sm_dout; + uint dma_ch_out_ctrl; + uint dma_ch_out_data; + int32_t *out_ctrl_blocks[2]; + int32_t output_buffer[STEREO_BUFFER_SIZE * 2]; + } pio_i2s; + + void i2s_program_start_synched(PIO pio, void (*dma_handler)(void), pio_i2s *i2s, audio_i2s_config_t *config); + +#ifdef __cplusplus +} +#endif + +#endif //_AUDIO_I2S_H diff --git a/software/platform/raspberry-pico-w/pico_i2s.pio b/software/platform/raspberry-pico-w/pico_i2s.pio new file mode 100644 index 0000000..582cf7f --- /dev/null +++ b/software/platform/raspberry-pico-w/pico_i2s.pio @@ -0,0 +1,82 @@ +; i2s.pio +; +; Author: Daniel Collins +; Date: 2022-02-25 +; +; Copyright (c) 2022 Daniel Collins +; +; This file is part of rp2040_i2s_example. +; +; rp2040_i2s_example is free software: you can redistribute it and/or modify it under +; the terms of the GNU General Public License, version 3 as published by the +; Free Software Foundation. +; +; rp2040_i2s_example is distributed in the hope that it will be useful, but WITHOUT ANY +; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +; A PARTICULAR PURPOSE. See the GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License along with +; rp2040_i2s_example. If not, see . + +; An I2S bi-directional peripheral with master clock (SCK) output. + + +.program i2s_out_master +; I2S audio output block. Synchronous with clock and input. +; Must run at BCK * 2. +; +; This block also outputs the word clock (also called frame or LR clock) and +; the bit clock. +; +; Set register x to (bit depth - 2) (e.g. for 24 bit audio, set to 22). +; Note that if this is needed to be synchronous with the SCK module, +; it is not possible to run 24-bit frames with an SCK of 256x fs. You must either +; run SCK at 384x fs (if your codec permits this) or use 32-bit frames, which +; work fine with 24-bit codecs. + +.side_set 2 + +public entry_point: + ; /--- LRCLK + ; |/-- BCLK +frameL: ; || + set x, 30 side 0b00 ; start of Left frame + pull noblock side 0b01 ; One clock after edge change with no data +dataL: + out pins, 1 side 0b00 + jmp x-- dataL side 0b01 + +frameR: + set x, 30 side 0b10 + pull noblock side 0b11 ; One clock after edge change with no data +dataR: + out pins, 1 side 0b10 + jmp x-- dataR side 0b11 + + +% c-sdk { + +// These constants are the I2S clock to pio clock ratio +const int i2s_sck_program_pio_mult = 2; +const int i2s_out_master_program_pio_mult = 2; + + +static inline void i2s_out_master_program_init(PIO pio, uint8_t sm, uint8_t offset, uint8_t bit_depth, uint8_t dout_pin, uint8_t clock_pin_base) { + pio_gpio_init(pio, dout_pin); + pio_gpio_init(pio, clock_pin_base); + pio_gpio_init(pio, clock_pin_base + 1); + + pio_sm_config sm_config = i2s_out_master_program_get_default_config(offset); + sm_config_set_out_pins(&sm_config, dout_pin, 1); + sm_config_set_sideset_pins(&sm_config, clock_pin_base); + sm_config_set_out_shift(&sm_config, false, false, bit_depth); + sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX); + pio_sm_init(pio, sm, offset, &sm_config); + + uint32_t pin_mask = (1u << dout_pin) | (3u << clock_pin_base); + pio_sm_set_pins_with_mask(pio, sm, 0, pin_mask); // zero output + pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask); +} + + +%} \ No newline at end of file diff --git a/software/platform/raspberry-pico-w/pico_lcd_spi.c b/software/platform/raspberry-pico-w/pico_lcd_spi.c index e978034..44e7ab7 100644 --- a/software/platform/raspberry-pico-w/pico_lcd_spi.c +++ b/software/platform/raspberry-pico-w/pico_lcd_spi.c @@ -13,7 +13,7 @@ static uint dma_tx; void pico_lcd_spi_init() { - spi_init(spi1, 1000 * 1000); + spi_init(spi1, 10000000); // gpio_set_function(LCD_RX, GPIO_FUNC_SPI); gpio_set_function(LCD_SCK, GPIO_FUNC_SPI); diff --git a/software/system/audio_player.c b/software/system/audio_player.c new file mode 100644 index 0000000..b82c480 --- /dev/null +++ b/software/system/audio_player.c @@ -0,0 +1,315 @@ +// Copyright (c) 2023, Anthony Rabine +// Copyright (c) 2020, Elehobica (original version) +// Released under the BSD-2-Clause +// refer to https://opensource.org/licenses/BSD-2-Clause + +#include "audio_player.h" +#include "debug.h" +#include + +#include "ost_hal.h" +#include "libutil.h" + +// 1 sample = one 16-bit data for left, one 16-bit data for right (stereo example) +#define SIZE_OF_SAMPLES (1024) + +// Audio Double Buffer for DMA transfer +int32_t audio_buf[2][SIZE_OF_SAMPLES]; + +// int16_t audio_buf16[2][SIZE_OF_SAMPLES]; +// Audio Buffer for File Read +uint8_t raw_buf[SIZE_OF_SAMPLES * 2 * 2]; // x2 for 16-bit, L+R + +int32_t DAC_ZERO_VALUE = 1; // + +union U +{ + uint32_t i; + uint16_t s[2]; +} u; + +static const uint32_t vol_table[101] = { + 0, 4, 8, 12, 16, 20, 24, 27, 29, 31, + 34, 37, 40, 44, 48, 52, 57, 61, 67, 73, + 79, 86, 94, 102, 111, 120, 131, 142, 155, 168, + 183, 199, 217, 236, 256, 279, 303, 330, 359, 390, // vol_table[34] = 256; + 424, 462, 502, 546, 594, 646, 703, 764, 831, 904, + 983, 1069, 1163, 1265, 1376, 1496, 1627, 1770, 1925, 2094, + 2277, 2476, 2693, 2929, 3186, 3465, 3769, 4099, 4458, 4849, + 5274, 5736, 6239, 6785, 7380, 8026, 8730, 9495, 10327, 11232, + 12216, 13286, 14450, 15716, 17093, 18591, 20220, 21992, 23919, 26015, + 28294, 30773, 33470, 36403, 39592, 43061, 46835, 50938, 55402, 60256, + 65536}; + +static uint32_t swap16b(uint32_t in_val) +{ + u.i = in_val; + return ((uint32_t)u.s[0] << 16) | ((uint32_t)u.s[1]); +} + +void audio_init(audio_ctx_t *ctx) +{ + memset(ctx->audio_info.filename, 0, sizeof(ctx->audio_info.filename)); + memset(ctx->audio_info.artist, 0, sizeof(ctx->audio_info.artist)); + memset(ctx->audio_info.title, 0, sizeof(ctx->audio_info.title)); + memset(ctx->audio_info.album, 0, sizeof(ctx->audio_info.album)); + memset(ctx->audio_info.number, 0, sizeof(ctx->audio_info.number)); + + ctx->idx_play = 0; + ctx->next_is_end = 0; + ctx->data_offset = 0; + ctx->playing = 0; + ctx->pausing = 0; + ctx->volume = 65; + ctx->count = 0; + + for (int i = 0; i < SIZE_OF_SAMPLES; i++) + { + audio_buf[0][i] = DAC_ZERO_VALUE; + audio_buf[1][i] = DAC_ZERO_VALUE; + + // audio_buf16[0][i] = DAC_ZERO_VALUE; + // audio_buf16[1][i] = DAC_ZERO_VALUE; + } + + ctx->dma_trans_number = SIZE_OF_SAMPLES / 4; +} + +static int list_chunk_is_info_type(audio_ctx_t *ctx) +{ + FRESULT fr; /* FatFs return code */ + UINT br; + char chunk_id[4]; + + fr = f_read(&ctx->fil, chunk_id, 4, &br); + // if (fr) printf("ERROR D: f_read %d\n\r", (int) fr); + return (memcmp(chunk_id, "info", 4) == 0 || memcmp(chunk_id, "INFO", 4) == 0); +} + +static int load_next_file(audio_ctx_t *ctx, const char *fname_ptr) +{ + int len; + FRESULT fr; /* FatFs return code */ + UINT br; + char chunk_id[4]; + uint32_t size; + uint32_t offset; + + len = strlen(fname_ptr); + if (strncmp(&fname_ptr[len - 4], ".wav", 4) == 0 || strncmp(&fname_ptr[len - 4], ".WAV", 4) == 0) + { + memcpy(ctx->audio_info.filename, fname_ptr, 256); + ctx->audio_info.info_start = 0; + fr = f_open(&ctx->fil, ctx->audio_info.filename, FA_READ); + if (fr != FR_OK) + { + debug_printf("ERROR: f_open %d\n\r", (int)fr); + } + ctx->idx_play++; + FRESULT res = f_read(&ctx->fil, ctx->header, sizeof(ctx->header), &br); + // Find 'data' chunk + + offset = 0; + while (1) + { + res = f_read(&ctx->fil, chunk_id, 4, &br); + res = f_read(&ctx->fil, &size, 4, &br); + offset += 8; + if (res == FR_OK) + { + if (memcmp(chunk_id, "data", 4) == 0 || memcmp(chunk_id, "DATA", 4) == 0) + { + break; + } + } + else + { + break; + } + } + ctx->audio_info.data_size = size; + // printf("Audio data size = %d\n\r", (int) audio_info.data_size); + ctx->audio_info.data_start = offset; + ctx->audio_info.data_offset = 0; + return 1; + } + + return 0; +} + +static int get_level(uint32_t val) +{ + int i; + for (i = 0; i < 101; i++) + { + if (val * 2 < vol_table[i]) + break; + } + return i; +} + +int32_t swp(int16_t data) +{ + int32_t r = (data & 0xFF) << 8; + r = r | ((data >> 8) & 0xFF); + return r; +} + +// trans_number: DMA transfer count of 16bit->32bit transfer (NOT Byte count) +// but it equals Byte count of 16bit RAW data (actually equals (Byte count of 16bit RAW data)*2/2) +// because 16bit RAW data is expanded into 32bit data for 24bit DAC +static int get_audio_buf(audio_ctx_t *ctx, int32_t *buf_32b) +{ + int i; + FRESULT fr; /* FatFs return code */ + UINT br; + int _next_is_end = 0; /* 0: continue, 1: next is end */ + uint32_t number; + uint32_t file_rest; + uint32_t trans_rest; + uint32_t trans; + uint32_t lvl_l = 0; + uint32_t lvl_r = 0; + + number = 0; // number to transfer (en octets) + while (number < sizeof(raw_buf)) + { + file_rest = ctx->audio_info.data_size - ctx->audio_info.data_offset; + trans_rest = sizeof(raw_buf) - number; // en octets, 2048 au début + trans = (file_rest >= trans_rest) ? trans_rest : file_rest; + // LEDR(1); + fr = f_read(&ctx->fil, &raw_buf[number], trans, &br); + // LEDR(0); + if (fr == FR_OK) + { + number += trans; + ctx->audio_info.data_offset += trans; + } + else + { + debug_printf("ERROR: f_read %d, data_offset = %d\n\r", (int)fr, (int)ctx->audio_info.data_offset); + f_close(&ctx->fil); + ctx->dma_trans_number = number / 4; + return 1; + } + if (ctx->audio_info.data_size <= ctx->audio_info.data_offset) + { + f_close(&ctx->fil); + _next_is_end = 1; + break; + } + } + + static bool onetime = true; + + // samples : total bytes devided by 2 (16 bits) and by two again (2 channels) + for (i = 0; i < number / 4; i++) + { + // buf_32b[i * 2 + 0] = (int32_t)swap16b((int32_t)buf_16b[i * 2 + 0] * vol_table[ctx->volume]) + DAC_ZERO_VALUE; // L + // buf_32b[i * 2 + 1] = (int32_t)swap16b((int32_t)buf_16b[i * 2 + 1] * vol_table[ctx->volume]) + DAC_ZERO_VALUE; // R + + // Avec le AUDIO PICO de waveshare, on entend un truc + + buf_32b[i * 2] = ((int32_t)((int16_t)leu16_get(&raw_buf[i * 4]))); + buf_32b[i * 2 + 1] = ((int32_t)((int16_t)leu16_get(&raw_buf[i * 4 + 2]))); + + // buf_32b[i * 2] = 1; + // buf_32b[i * 2 + 1] = 4; + + // lvl_l += ((int32_t)buf_16b[i * 2 + 0] * buf_16b[i * 2 + 0]) / 32768; + // lvl_r += ((int32_t)buf_16b[i * 2 + 1] * buf_16b[i * 2 + 1]) / 32768; + } + /* + if (onetime) + { + onetime = false; + + for (int i = 0; i < 10; i++) + { + debug_printf("Sample left: %d\r\n", buf_32b[i * 2]); + debug_printf("Sample right: %d\r\n", buf_32b[i * 2 + 1]); + } + } + */ + // ctx->audio_info.lvl_l = get_level(lvl_l / (number / 4)); + // ctx->audio_info.lvl_r = get_level(lvl_r / (number / 4)); + ctx->dma_trans_number = (number / 4); // 32 bytes tranfers + return _next_is_end; +} + +void audio_process(audio_ctx_t *ctx) +{ + int nxt1 = (ctx->count & 0x1) ^ 0x1; + int nxt2 = 1 - nxt1; + // dma_flag_clear(DMA1, DMA_CH1, DMA_FLAG_FTF); + // dma_channel_disable(DMA1, DMA_CH1); + + ost_hal_audio_frame_end(); + + if (ctx->next_is_end) + { + ctx->playing = 0; + ctx->pausing = 0; + } + // init_dma_i2s2(audio_buf[nxt1], dma_trans_number); + // dma_channel_enable(DMA1, DMA_CH1); + + ost_hal_audio_frame_start(audio_buf[nxt1], ctx->dma_trans_number); + + if (ctx->playing && !ctx->pausing) + { + ctx->next_is_end = get_audio_buf(ctx, audio_buf[nxt2]); + } + else + { + for (int i = 0; i < SIZE_OF_SAMPLES; i++) + { + audio_buf[nxt2][i] = DAC_ZERO_VALUE; + } + ctx->dma_trans_number = SIZE_OF_SAMPLES / 4; + } + ctx->count++; + // dma_interrupt_flag_clear(DMA1, DMA_CH1, DMA_INT_FLAG_G); /* not needed */ +} + +int audio_play(audio_ctx_t *ctx, const char *fname_ptr) +{ + if (ctx->playing) + return 0; + + memset(ctx->audio_info.filename, 0, sizeof(ctx->audio_info.filename)); + if (!load_next_file(ctx, fname_ptr)) + { + ctx->finished = 1; + return 0; + } + memset(ctx->audio_info.artist, 0, sizeof(ctx->audio_info.artist)); + memset(ctx->audio_info.title, 0, sizeof(ctx->audio_info.title)); + memset(ctx->audio_info.album, 0, sizeof(ctx->audio_info.album)); + memset(ctx->audio_info.number, 0, sizeof(ctx->audio_info.number)); + + ctx->count = 0; + ctx->playing = 1; // After playing is set to 1, only IRQ routine can access file otherwise conflict occurs + ctx->pausing = 0; + ctx->next_is_end = 0; + ctx->finished = 0; + + return 1; +} + +void audio_pause(audio_ctx_t *ctx) +{ + if (!ctx->playing) + return; + ctx->pausing = 1 - ctx->pausing; +} + +void audio_stop(audio_ctx_t *ctx) +{ + if (ctx->playing || ctx->pausing) + { + ctx->playing = 0; + ctx->pausing = 0; + f_close(&ctx->fil); + } +} diff --git a/software/system/audio_player.h b/software/system/audio_player.h new file mode 100644 index 0000000..dcc0e79 --- /dev/null +++ b/software/system/audio_player.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include + +#define FILENAME_MAX_SIZE 260 + +typedef struct +{ + char filename[FILENAME_MAX_SIZE]; + uint32_t info_start; + uint32_t info_size; + uint32_t info_offset; + uint32_t data_start; + uint32_t data_size; + uint32_t data_offset; + char artist[256]; + char title[256]; + char album[256]; + char number[8]; + uint32_t lvl_l; + uint32_t lvl_r; +} audio_info_type_t; + +typedef struct +{ + FIL fil; + audio_info_type_t audio_info; + int32_t dma_trans_number; + uint16_t idx_play; + int next_is_end; + int playing; + int pausing; + int finished; // means the player has finished to play whole files in the folder (by not stop) + uint32_t data_offset; + uint8_t header[36]; + + int count; + int volume; // 0 ~ 100; + +} audio_ctx_t; + +void audio_init(audio_ctx_t *ctx); +void audio_process(audio_ctx_t *ctx); +int audio_play(audio_ctx_t *ctx, const char *fname_ptr); +void audio_pause(audio_ctx_t *ctx); +void audio_stop(audio_ctx_t *ctx); diff --git a/software/system/ff_diskio_sdcard.c b/software/system/ff_diskio_sdcard.c index 255ffe1..4c959d1 100644 --- a/software/system/ff_diskio_sdcard.c +++ b/software/system/ff_diskio_sdcard.c @@ -1,149 +1,644 @@ -/*------------------------------------------------------------------------*/ -/* STM32F100: MMCv3/SDv1/SDv2 (SPI mode) control module */ -/*------------------------------------------------------------------------*/ -/* -/ Copyright (C) 2018, ChaN, all right reserved. -/ -/ * This software is a free software and there is NO WARRANTY. -/ * No restriction on use. You can use, modify and redistribute it for -/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. -/ * Redistributions of source code must retain the above copyright notice. -/ -/-------------------------------------------------------------------------*/ +#define PIN_SPI0_CS 17 +#define PIN_SPI0_SCK 18 +#define PIN_SPI0_MOSI 19 +#define PIN_SPI0_MISO 16 -/*-------------------------------------------------------------------------- - Module Private Functions ----------------------------------------------------------------------------*/ +// const uint8_t SD_CARD_CS = 17; +// #define SDCARD_SCK 18 +// #define SDCARD_MOSI 19 +// #define SDCARD_MISO 16 -#include "ost_hal.h" -#include "debug.h" +#include "pico.h" +#include "pico/stdlib.h" +#include "hardware/clocks.h" +#include "hardware/spi.h" +#include "hardware/gpio.h" #include "ff.h" #include "diskio.h" -#include "sdcard.h" -#include "ffconf.h" + +/*-------------------------------------------------------------------------- + + Module Private Functions + +---------------------------------------------------------------------------*/ + +/* MMC/SD command */ +#define CMD0 (0) /* GO_IDLE_STATE */ +#define CMD1 (1) /* SEND_OP_COND (MMC) */ +#define ACMD41 (0x80 + 41) /* SEND_OP_COND (SDC) */ +#define CMD8 (8) /* SEND_IF_COND */ +#define CMD9 (9) /* SEND_CSD */ +#define CMD10 (10) /* SEND_CID */ +#define CMD12 (12) /* STOP_TRANSMISSION */ +#define ACMD13 (0x80 + 13) /* SD_STATUS (SDC) */ +#define CMD16 (16) /* SET_BLOCKLEN */ +#define CMD17 (17) /* READ_SINGLE_BLOCK */ +#define CMD18 (18) /* READ_MULTIPLE_BLOCK */ +#define CMD23 (23) /* SET_BLOCK_COUNT (MMC) */ +#define ACMD23 (0x80 + 23) /* SET_WR_BLK_ERASE_COUNT (SDC) */ +#define CMD24 (24) /* WRITE_BLOCK */ +#define CMD25 (25) /* WRITE_MULTIPLE_BLOCK */ +#define CMD32 (32) /* ERASE_ER_BLK_START */ +#define CMD33 (33) /* ERASE_ER_BLK_END */ +#define CMD38 (38) /* ERASE */ +#define CMD55 (55) /* APP_CMD */ +#define CMD58 (58) /* READ_OCR */ + +/* MMC card type flags (MMC_GET_TYPE) */ +#define CT_MMC 0x01 /* MMC ver 3 */ +#define CT_SD1 0x02 /* SD ver 1 */ +#define CT_SD2 0x04 /* SD ver 2 */ +#define CT_SDC (CT_SD1 | CT_SD2) /* SD */ +#define CT_BLOCK 0x08 /* Block addressing */ + +#define CLK_SLOW (100 * KHZ) +#define CLK_FAST (40 * MHZ) + +static volatile DSTATUS Stat = STA_NOINIT; /* Physical drive status */ + +static BYTE CardType; /* Card type flags */ + +static inline uint32_t _millis(void) +{ + return to_ms_since_boot(get_absolute_time()); +} /*-----------------------------------------------------------------------*/ -/* Initialize a Drive */ +/* SPI controls (Platform dependent) */ /*-----------------------------------------------------------------------*/ + +static inline void cs_select(uint cs_pin) +{ + asm volatile("nop \n nop \n nop"); // FIXME + gpio_put(cs_pin, 0); + asm volatile("nop \n nop \n nop"); // FIXME +} + +static inline void cs_deselect(uint cs_pin) +{ + asm volatile("nop \n nop \n nop"); // FIXME + gpio_put(cs_pin, 1); + asm volatile("nop \n nop \n nop"); // FIXME +} + +static void FCLK_SLOW(void) +{ + spi_set_baudrate(spi0, CLK_SLOW); +} + +static void FCLK_FAST(void) +{ + spi_set_baudrate(spi0, CLK_FAST); +} + +static void CS_HIGH(void) +{ + cs_deselect(PIN_SPI0_CS); +} + +static void CS_LOW(void) +{ + cs_select(PIN_SPI0_CS); +} + +/* Initialize MMC interface */ +void init_spi(void) +{ + /* GPIO pin configuration */ + /* pull up of MISO is MUST (10Kohm external pull up is recommended) */ + /* Set drive strength and slew rate if needed to meet wire condition */ + gpio_init(PIN_SPI0_SCK); + gpio_disable_pulls(PIN_SPI0_SCK); + // gpio_pull_up(PIN_SPI0_SCK); + // gpio_set_drive_strength(PIN_SPI0_SCK, PADS_BANK0_GPIO0_DRIVE_VALUE_4MA); // 2mA, 4mA (default), 8mA, 12mA + // gpio_set_slew_rate(PIN_SPI0_SCK, 0); // 0: SLOW (default), 1: FAST + gpio_set_function(PIN_SPI0_SCK, GPIO_FUNC_SPI); + + gpio_init(PIN_SPI0_MISO); + gpio_disable_pulls(PIN_SPI0_MISO); + // gpio_pull_up(PIN_SPI0_MISO); + // gpio_set_schmitt(PIN_SPI0_MISO, 1); // 0: Off, 1: On (default) + gpio_set_function(PIN_SPI0_MISO, GPIO_FUNC_SPI); + + gpio_init(PIN_SPI0_MOSI); + gpio_disable_pulls(PIN_SPI0_MOSI); + // gpio_pull_up(PIN_SPI0_MOSI); + // gpio_set_drive_strength(PIN_SPI0_MOSI, PADS_BANK0_GPIO0_DRIVE_VALUE_4MA); // 2mA, 4mA (default), 8mA, 12mA + // gpio_set_slew_rate(PIN_SPI0_MOSI, 0); // 0: SLOW (default), 1: FAST + gpio_set_function(PIN_SPI0_MOSI, GPIO_FUNC_SPI); + + gpio_init(PIN_SPI0_CS); + gpio_disable_pulls(PIN_SPI0_CS); + // gpio_pull_up(PIN_SPI0_CS); + // gpio_set_drive_strength(PIN_SPI0_CS, PADS_BANK0_GPIO0_DRIVE_VALUE_4MA); // 2mA, 4mA (default), 8mA, 12mA + // gpio_set_slew_rate(PIN_SPI0_CS, 0); // 0: SLOW (default), 1: FAST + gpio_set_dir(PIN_SPI0_CS, GPIO_OUT); + + /* chip _select invalid*/ + CS_HIGH(); + + spi_init(spi0, CLK_SLOW); + + /* SPI0 parameter config */ + spi_set_format(spi0, + 8, /* data_bits */ + SPI_CPOL_0, /* cpol */ + SPI_CPHA_0, /* cpha */ + SPI_MSB_FIRST /* order */ + ); +} + +/* Exchange a byte */ +static BYTE xchg_spi( + BYTE dat /* Data to send */ +) +{ + uint8_t *buff = (uint8_t *)&dat; + spi_write_read_blocking(spi0, buff, buff, 1); + return (BYTE)*buff; +} + +/* Receive multiple byte */ +static void rcvr_spi_multi( + BYTE *buff, /* Pointer to data buffer */ + UINT btr /* Number of bytes to receive (even number) */ +) +{ + uint8_t *b = (uint8_t *)buff; + spi_read_blocking(spi0, 0xff, b, btr); +} + +/*-----------------------------------------------------------------------*/ +/* Wait for card ready */ +/*-----------------------------------------------------------------------*/ + +static int wait_ready( /* 1:Ready, 0:Timeout */ + UINT wt /* Timeout [ms] */ +) +{ + BYTE d; + + uint32_t t = _millis(); + do + { + d = xchg_spi(0xFF); + /* This loop takes a time. Insert rot_rdq() here for multitask envilonment. */ + } while (d != 0xFF && _millis() < t + wt); /* Wait for card goes ready or timeout */ + + return (d == 0xFF) ? 1 : 0; +} + +/*-----------------------------------------------------------------------*/ +/* Deselect card and release SPI */ +/*-----------------------------------------------------------------------*/ + +static void deselect(void) +{ + CS_HIGH(); /* Set CS# high */ + xchg_spi(0xFF); /* Dummy clock (force DO hi-z for multiple slave SPI) */ +} + +/*-----------------------------------------------------------------------*/ +/* Select card and wait for ready */ +/*-----------------------------------------------------------------------*/ + +static int _select(void) /* 1:OK, 0:Timeout */ +{ + CS_LOW(); /* Set CS# low */ + xchg_spi(0xFF); /* Dummy clock (force DO enabled) */ + if (wait_ready(500)) + return 1; /* Wait for card ready */ + + deselect(); + return 0; /* Timeout */ +} + +/*-----------------------------------------------------------------------*/ +/* Receive a data packet from the MMC */ +/*-----------------------------------------------------------------------*/ + +static int rcvr_datablock( /* 1:OK, 0:Error */ + BYTE *buff, /* Data buffer */ + UINT btr /* Data block length (byte) */ +) +{ + BYTE token; + + const uint32_t timeout = 200; + uint32_t t = _millis(); + do + { /* Wait for DataStart token in timeout of 200ms */ + token = xchg_spi(0xFF); + /* This loop will take a time. Insert rot_rdq() here for multitask envilonment. */ + } while (token == 0xFF && _millis() < t + timeout); + if (token != 0xFE) + return 0; /* Function fails if invalid DataStart token or timeout */ + + rcvr_spi_multi(buff, btr); /* Store trailing data to the buffer */ + xchg_spi(0xFF); + xchg_spi(0xFF); /* Discard CRC */ + + return 1; /* Function succeeded */ +} + +/*-----------------------------------------------------------------------*/ +/* Send a command packet to the MMC */ +/*-----------------------------------------------------------------------*/ + +static BYTE send_cmd( /* Return value: R1 resp (bit7==1:Failed to send) */ + BYTE cmd, /* Command index */ + DWORD arg /* Argument */ +) +{ + BYTE n, res; + + if (cmd & 0x80) + { /* Send a CMD55 prior to ACMD */ + cmd &= 0x7F; + res = send_cmd(CMD55, 0); + if (res > 1) + return res; + } + + /* Select the card and wait for ready except to stop multiple block read */ + if (cmd != CMD12) + { + deselect(); + if (!_select()) + return 0xFF; + } + + /* Send command packet */ + xchg_spi(0x40 | cmd); /* Start + command index */ + xchg_spi((BYTE)(arg >> 24)); /* Argument[31..24] */ + xchg_spi((BYTE)(arg >> 16)); /* Argument[23..16] */ + xchg_spi((BYTE)(arg >> 8)); /* Argument[15..8] */ + xchg_spi((BYTE)arg); /* Argument[7..0] */ + n = 0x01; /* Dummy CRC + Stop */ + if (cmd == CMD0) + n = 0x95; /* Valid CRC for CMD0(0) */ + if (cmd == CMD8) + n = 0x87; /* Valid CRC for CMD8(0x1AA) */ + xchg_spi(n); + + /* Receive command resp */ + if (cmd == CMD12) + xchg_spi(0xFF); /* Diacard following one byte when CMD12 */ + n = 10; /* Wait for response (10 bytes max) */ + do + { + res = xchg_spi(0xFF); + } while ((res & 0x80) && --n); + + return res; /* Return received response */ +} + +/*-------------------------------------------------------------------------- + + Public Functions + +---------------------------------------------------------------------------*/ + +/*-----------------------------------------------------------------------*/ +/* Initialize disk drive */ +/*-----------------------------------------------------------------------*/ + DSTATUS disk_initialize( - BYTE drv /* Physical drive number (0..) */ + BYTE drv /* Physical drive number (0) */ ) { + BYTE n, cmd, ty, ocr[4]; + const uint32_t timeout = 1000; /* Initialization timeout = 1 sec */ + uint32_t t; + if (drv) - return STA_NOINIT; + return STA_NOINIT; /* Supports only drive 0 */ + init_spi(); /* Initialize SPI */ + sleep_ms(10); - if (sdcard_init() != SD_RESPONSE_NO_ERROR) - return STA_NOINIT; + if (Stat & STA_NODISK) + return Stat; /* Is card existing in the soket? */ - return 0; + FCLK_SLOW(); + for (n = 10; n; n--) + xchg_spi(0xFF); /* Send 80 dummy clocks */ + + ty = 0; + if (send_cmd(CMD0, 0) == 1) + { /* Put the card SPI/Idle state */ + t = _millis(); + if (send_cmd(CMD8, 0x1AA) == 1) + { /* SDv2? */ + for (n = 0; n < 4; n++) + ocr[n] = xchg_spi(0xFF); /* Get 32 bit return value of R7 resp */ + if (ocr[2] == 0x01 && ocr[3] == 0xAA) + { /* Is the card supports vcc of 2.7-3.6V? */ + while (_millis() < t + timeout && send_cmd(ACMD41, 1UL << 30)) + ; /* Wait for end of initialization with ACMD41(HCS) */ + if (_millis() < t + timeout && send_cmd(CMD58, 0) == 0) + { /* Check CCS bit in the OCR */ + for (n = 0; n < 4; n++) + ocr[n] = xchg_spi(0xFF); + ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2; /* Card id SDv2 */ + } + } + } + else + { /* Not SDv2 card */ + if (send_cmd(ACMD41, 0) <= 1) + { /* SDv1 or MMC? */ + ty = CT_SD1; + cmd = ACMD41; /* SDv1 (ACMD41(0)) */ + } + else + { + ty = CT_MMC; + cmd = CMD1; /* MMCv3 (CMD1(0)) */ + } + while (_millis() < t + timeout && send_cmd(cmd, 0)) + ; /* Wait for end of initialization */ + if (_millis() >= t + timeout || send_cmd(CMD16, 512) != 0) /* Set block length: 512 */ + ty = 0; + } + } + CardType = ty; /* Card type */ + deselect(); + + if (ty) + { /* OK */ + FCLK_FAST(); /* Set fast clock */ + Stat &= ~STA_NOINIT; /* Clear STA_NOINIT flag */ + } + else + { /* Failed */ + Stat = STA_NOINIT; + } + + return Stat; } /*-----------------------------------------------------------------------*/ -/* Return Drive Status */ +/* Get disk status */ /*-----------------------------------------------------------------------*/ + DSTATUS disk_status( - BYTE drv /* Physical drive number (0..) */ + BYTE drv /* Physical drive number (0) */ ) { if (drv) - return STA_NOINIT; + return STA_NOINIT; /* Supports only drive 0 */ + + return Stat; /* Return disk status */ +} + +/*-----------------------------------------------------------------------*/ +/* Read sector(s) */ +/*-----------------------------------------------------------------------*/ + +DRESULT disk_read( + BYTE drv, /* Physical drive number (0) */ + BYTE *buff, /* Pointer to the data buffer to store read data */ + LBA_t sector, /* Start sector number (LBA) */ + UINT count /* Number of sectors to read (1..128) */ +) +{ + if (drv || !count) + return RES_PARERR; /* Check parameter */ + if (Stat & STA_NOINIT) + return RES_NOTRDY; /* Check if drive is ready */ + + if (!(CardType & CT_BLOCK)) + sector *= 512; /* LBA ot BA conversion (byte addressing cards) */ + + if (count == 1) + { /* Single sector read */ + if ((send_cmd(CMD17, sector) == 0) /* READ_SINGLE_BLOCK */ + && rcvr_datablock(buff, 512)) + { + count = 0; + } + } + else + { /* Multiple sector read */ + if (send_cmd(CMD18, sector) == 0) + { /* READ_MULTIPLE_BLOCK */ + do + { + if (!rcvr_datablock(buff, 512)) + break; + buff += 512; + } while (--count); + send_cmd(CMD12, 0); /* STOP_TRANSMISSION */ + } + } + deselect(); + + return count ? RES_ERROR : RES_OK; /* Return result */ +} + +#if !FF_FS_READONLY && !FF_FS_NORTC +/* get the current time */ +DWORD get_fattime(void) +{ return 0; } +#endif -/*-----------------------------------------------------------------------*/ -/* Read Sector(s) */ -/*-----------------------------------------------------------------------*/ -DRESULT disk_read( - BYTE pdrv, /* Physical drive nmuber 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 */ +#if FF_FS_READONLY == 0 +/* Transmit multiple byte */ +static void xmit_spi_multi( + const BYTE *buff, /* Pointer to data buffer */ + UINT btx /* Number of bytes to transmit (even number) */ ) { - DRESULT res; - if (pdrv || !count) - return RES_PARERR; - - if (count == 1) - res = (DRESULT)sdcard_sector_read(sector, buff); - else - res = (DRESULT)sdcard_sectors_read(sector, buff, count); - - if (res == 0x00) - return RES_OK; - return RES_ERROR; + const uint8_t *b = (const uint8_t *)buff; + spi_write_blocking(spi0, b, btx); } /*-----------------------------------------------------------------------*/ -/* Write Sector(s) */ +/* Transmit a data packet to the MMC */ /*-----------------------------------------------------------------------*/ -#if _READONLY == 0 + +static int xmit_datablock( /* 1:OK, 0:Error */ + const BYTE *buff, /* 512 byte data block to be transmitted */ + BYTE token /* Data/Stop token */ +) +{ + BYTE resp; + if (!wait_ready(500)) + return 0; + xchg_spi(token); /* Xmit data token */ + if (token != 0xFD) + { /* Is data token */ + xmit_spi_multi(buff, 512); /* Xmit the data block to the MMC */ + xchg_spi(0xFF); /* CRC (Dummy) */ + xchg_spi(0xFF); + resp = xchg_spi(0xFF); /* Reveive data response */ + if ((resp & 0x1F) != 0x05) /* If not accepted, return with error */ + return 0; + } + return 1; +} + +/*-----------------------------------------------------------------------*/ +/* Write sector(s) */ +/*-----------------------------------------------------------------------*/ + DRESULT disk_write( - BYTE pdrv, /* Physical drive nmuber 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 */ + BYTE drv, /* Physical drive number (0) */ + const BYTE *buff, /* Ponter to the data to write */ + LBA_t sector, /* Start sector number (LBA) */ + UINT count /* Number of sectors to write (1..128) */ ) { - DRESULT res; - if (pdrv || !count) - return RES_PARERR; + if (drv || !count) + return RES_PARERR; /* Check parameter */ + if (Stat & STA_NOINIT) + return RES_NOTRDY; /* Check drive status */ + if (Stat & STA_PROTECT) + return RES_WRPRT; /* Check write protect */ + + if (!(CardType & CT_BLOCK)) + sector *= 512; /* LBA ==> BA conversion (byte addressing cards) */ + + if (!_select()) + return RES_NOTRDY; if (count == 1) - res = (DRESULT)sdcard_sector_write(sector, buff); + { /* Single sector write */ + if ((send_cmd(CMD24, sector) == 0) /* WRITE_BLOCK */ + && xmit_datablock(buff, 0xFE)) + { + count = 0; + } + } else - res = (DRESULT)sdcard_sectors_write(sector, buff, count); + { /* Multiple sector write */ + if (CardType & CT_SDC) + send_cmd(ACMD23, count); /* Predefine number of sectors */ + if (send_cmd(CMD25, sector) == 0) + { /* WRITE_MULTIPLE_BLOCK */ + do + { + if (!xmit_datablock(buff, 0xFC)) + break; + buff += 512; + } while (--count); + if (!xmit_datablock(0, 0xFD)) + count = 1; /* STOP_TRAN token */ + } + } + deselect(); - if (res == 0) - return RES_OK; - return RES_ERROR; + return count ? RES_ERROR : RES_OK; /* Return result */ } -#endif /* _READONLY */ +#endif /*-----------------------------------------------------------------------*/ -/* Miscellaneous Functions */ +/* Miscellaneous drive controls other than data read/write */ /*-----------------------------------------------------------------------*/ DRESULT disk_ioctl( - BYTE drv, /* Physical drive number (0..) */ - BYTE ctrl, /* Control code */ - void *buff /* Buffer to send/receive control data */ + BYTE drv, /* Physical drive number (0) */ + BYTE cmd, /* Control command code */ + void *buff /* Pointer to the conrtol data */ ) { - SD_CardInfo cardinfo; DRESULT res; - if (drv) - return RES_PARERR; + BYTE n, csd[16]; + DWORD *dp, st, ed, csize; - switch (ctrl) + if (drv) + return RES_PARERR; /* Check parameter */ + if (Stat & STA_NOINIT) + return RES_NOTRDY; /* Check if drive is ready */ + + res = RES_ERROR; + + switch (cmd) { - case CTRL_SYNC: - // res = ( SD_WaitReady() == SD_RESPONSE_NO_ERROR ) ? RES_OK : RES_ERROR - res = RES_OK; + case CTRL_SYNC: /* Wait for end of internal write process of the drive */ + if (_select()) + res = RES_OK; break; - case GET_BLOCK_SIZE: - *(WORD *)buff = FF_MAX_SS; - res = RES_OK; - break; - case GET_SECTOR_COUNT: - if (sdcard_get_card_info(&cardinfo) != SD_RESPONSE_NO_ERROR) - res = RES_ERROR; - else + + case GET_SECTOR_COUNT: /* Get drive capacity in unit of sector (DWORD) */ + if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { - *(DWORD *)buff = cardinfo.CardCapacity; - res = (*(DWORD *)buff > 0) ? RES_OK : RES_PARERR; + if ((csd[0] >> 6) == 1) + { /* SDC ver 2.00 */ + csize = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1; + *(DWORD *)buff = csize << 10; + } + else + { /* SDC ver 1.XX or MMC ver 3 */ + n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; + csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1; + *(DWORD *)buff = csize << (n - 9); + } + res = RES_OK; } break; - case CTRL_TRIM: - res = (sdcard_sectors_erase(((DWORD *)buff)[0], ((DWORD *)buff)[1]) == SD_RESPONSE_NO_ERROR) - ? RES_OK - : RES_PARERR; + + case GET_BLOCK_SIZE: /* Get erase block size in unit of sector (DWORD) */ + if (CardType & CT_SD2) + { /* SDC ver 2.00 */ + if (send_cmd(ACMD13, 0) == 0) + { /* Read SD status */ + xchg_spi(0xFF); + if (rcvr_datablock(csd, 16)) + { /* Read partial block */ + for (n = 64 - 16; n; n--) + xchg_spi(0xFF); /* Purge trailing data */ + *(DWORD *)buff = 16UL << (csd[10] >> 4); + res = RES_OK; + } + } + } + else + { /* SDC ver 1.XX or MMC */ + if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) + { /* Read CSD */ + if (CardType & CT_SD1) + { /* SDC ver 1.XX */ + *(DWORD *)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1); + } + else + { /* MMC */ + *(DWORD *)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1); + } + res = RES_OK; + } + } break; + + case CTRL_TRIM: /* Erase a block of sectors (used when _USE_ERASE == 1) */ + if (!(CardType & CT_SDC)) + break; /* Check if the card is SDC */ + if (disk_ioctl(drv, MMC_GET_CSD, csd)) + break; /* Get CSD */ + if (!(csd[0] >> 6) && !(csd[10] & 0x40)) + break; /* Check if sector erase can be applied to the card */ + dp = buff; + st = dp[0]; + ed = dp[1]; /* Load sector block */ + if (!(CardType & CT_BLOCK)) + { + st *= 512; + ed *= 512; + } + if (send_cmd(CMD32, st) == 0 && send_cmd(CMD33, ed) == 0 && send_cmd(CMD38, 0) == 0 && wait_ready(30000)) + { /* Erase sector block */ + res = RES_OK; /* FatFs does not check result of this command */ + } + break; + default: res = RES_PARERR; - break; } + deselect(); + return res; } diff --git a/software/system/loop.cpp b/software/system/loop.cpp deleted file mode 100644 index 043d142..0000000 --- a/software/system/loop.cpp +++ /dev/null @@ -1,346 +0,0 @@ -/* - This reads a wave file from an SD card and plays it using the I2S interface to - a MAX08357 I2S Amp Breakout board. - -Circuit: - * Arduino/Genuino Zero, MKRZero or MKR1000 board - * SD breakout or shield connected - * MAX08357: - * GND connected GND - * VIN connected 5V - * LRC connected to pin 0 (Zero) or pin 3 (MKR1000, MKRZero) - * BCLK connected to pin 1 (Zero) or pin 2 (MKR1000, MKRZero) - * DIN connected to pin 9 (Zero) or pin A6 (MKR1000, MKRZero) - - created 15 November 2016 - by Sandeep Mistry - */ - -#include -#include - -// filename of wave file to play -const char filename[] = "castemere_mono.wav"; - -// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, -// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. -#define SD_FAT_TYPE 2 -// -// Set DISABLE_CHIP_SELECT to disable a second SPI device. -// For example, with the Ethernet shield, set DISABLE_CHIP_SELECT -// to 10 to disable the Ethernet controller. -const int8_t DISABLE_CHIP_SELECT = -1; -// -// Test with reduced SPI speed for breadboards. SD_SCK_MHZ(4) will select -// the highest speed supported by the board that is not over 4 MHz. -// Change SPI_SPEED to SD_SCK_MHZ(50) for best performance. -#define SPI_SPEED SD_SCK_MHZ(24) -//------------------------------------------------------------------------------ - -SdFat sd; -// SD card chip select -int chipSelect = 28; - -// variable representing the Wave File -SDWaveFile waveFile(sd, filename); - -void setup() { - // Open serial communications and wait for port to open: - Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } - - if (!sd.begin(chipSelect, SPI_SPEED)) { - if (sd.card()->errorCode()) { - Serial.println("SD initialization failed."); -// cout << F( -// "\nSD initialization failed.\n" -// "Do not reformat the card!\n" -// "Is the card correctly inserted?\n" -// "Is chipSelect set to the correct value?\n" -// "Does another SPI device need to be disabled?\n" -// "Is there a wiring/soldering problem?\n"); -// cout << F("\nerrorCode: ") << hex << showbase; -// cout << int(sd.card()->errorCode()); -// cout << F(", errorData: ") << int(sd.card()->errorData()); -// cout << dec << noshowbase << endl; - return; - } - Serial.println("Card successfully initialized."); - if (sd.vol()->fatType() == 0) { - Serial.println("Can't find a valid FAT16/FAT32 partition."); - return; - } - Serial.println("Can't determine error type."); - return; - } - - // check if the WaveFile is valid - if (!waveFile) { - Serial.println("wave file is invalid!"); - while (1); // do nothing - } - - // print out some info. about the wave file - Serial.print("Bits per sample = "); - Serial.println(waveFile.bitsPerSample()); - - long channels = waveFile.channels(); - Serial.print("Channels = "); - Serial.println(channels); - - long sampleRate = waveFile.sampleRate(); - Serial.print("Sample rate = "); - Serial.print(sampleRate); - Serial.println(" Hz"); - - long duration = waveFile.duration(); - Serial.print("Duration = "); - Serial.print(duration); - Serial.println(" seconds"); - - // adjust the playback volume - AudioOutI2S.volume(5); - - // check if the I2S output can play the wave file - if (!AudioOutI2S.canPlay(waveFile)) { - Serial.println("unable to play wave file using I2S!"); - while (1); // do nothing - } - - // start playback - Serial.println("starting playback"); - AudioOutI2S.play(waveFile); -} - -void loop() { - // check if playback is still going on - if (!AudioOutI2S.isPlaying()) { - // playback has stopped - - Serial.println("playback stopped"); - while (1); // do nothing - } -} - - -/** - * - -// ============================================ SD EXAMPLE - -// Quick hardware test for SPI card access. -// -#include -#include "SdFat.h" -#include "sdios.h" - -// SD_FAT_TYPE = 0 for SdFat/File as defined in SdFatConfig.h, -// 1 for FAT16/FAT32, 2 for exFAT, 3 for FAT16/FAT32 and exFAT. -#define SD_FAT_TYPE 2 -// -// Set DISABLE_CHIP_SELECT to disable a second SPI device. -// For example, with the Ethernet shield, set DISABLE_CHIP_SELECT -// to 10 to disable the Ethernet controller. -const int8_t DISABLE_CHIP_SELECT = -1; -// -// Test with reduced SPI speed for breadboards. SD_SCK_MHZ(4) will select -// the highest speed supported by the board that is not over 4 MHz. -// Change SPI_SPEED to SD_SCK_MHZ(50) for best performance. -#define SPI_SPEED SD_SCK_MHZ(24) -//------------------------------------------------------------------------------ -#if SD_FAT_TYPE == 0 -SdFat sd; -File file; -#elif SD_FAT_TYPE == 1 -SdFat32 sd; -File32 file; -#elif SD_FAT_TYPE == 2 -SdExFat sd; -ExFile file; -#elif SD_FAT_TYPE == 3 -SdFs sd; -FsFile file; -#else // SD_FAT_TYPE -#error Invalid SD_FAT_TYPE -#endif // SD_FAT_TYPE -// Serial streams -ArduinoOutStream cout(Serial); - -// input buffer for line -char cinBuf[40]; -ArduinoInStream cin(Serial, cinBuf, sizeof(cinBuf)); - -// SD card chip select -int chipSelect = 28; - -void cardOrSpeed() { - cout << F("Try another SD card or reduce the SPI bus speed.\n"); - cout << F("Edit SPI_SPEED in this program to change it.\n"); -} - -void reformatMsg() { - cout << F("Try reformatting the card. For best results use\n"); - cout << F("the SdFormatter program in SdFat/examples or download\n"); - cout << F("and use SDFormatter from www.sdcard.org/downloads.\n"); -} - -void setup() { - Serial.begin(115200); - - // Wait for USB Serial - while (!Serial) { - SysCall::yield(); - } - cout << F("\nSPI pins:\n"); - cout << F("MISO: ") << int(MISO) << endl; - cout << F("MOSI: ") << int(MOSI) << endl; - cout << F("SCK: ") << int(SCK) << endl; - cout << F("SS: ") << int(SS) << endl; -#ifdef SDCARD_SS_PIN - cout << F("SDCARD_SS_PIN: ") << int(SDCARD_SS_PIN) << endl; -#endif // SDCARD_SS_PIN - - if (DISABLE_CHIP_SELECT < 0) { - cout << F( - "\nBe sure to edit DISABLE_CHIP_SELECT if you have\n" - "a second SPI device. For example, with the Ethernet\n" - "shield, DISABLE_CHIP_SELECT should be set to 10\n" - "to disable the Ethernet controller.\n"); - } - cout << F( - "\nSD chip select is the key hardware option.\n" - "Common values are:\n" - "Arduino Ethernet shield, pin 4\n" - "Sparkfun SD shield, pin 8\n" - "Adafruit SD shields and modules, pin 10\n"); -} - -bool firstTry = true; -void loop() { - // Read any existing Serial data. - do { - delay(10); - } while (Serial.available() && Serial.read() >= 0); - - if (!firstTry) { - cout << F("\nRestarting\n"); - } - firstTry = false; - - -// cout << F("\nEnter the chip select pin number: "); -// while (!Serial.available()) { -// SysCall::yield(); -// } -// cin.readline(); -// if (cin >> chipSelect) { -// cout << chipSelect << endl; -// } else { -// cout << F("\nInvalid pin number\n"); -// return; -// } - - - if (DISABLE_CHIP_SELECT < 0) { - cout << F( - "\nAssuming the SD is the only SPI device.\n" - "Edit DISABLE_CHIP_SELECT to disable another device.\n"); - } else { - cout << F("\nDisabling SPI device on pin "); - cout << int(DISABLE_CHIP_SELECT) << endl; - pinMode(DISABLE_CHIP_SELECT, OUTPUT); - digitalWrite(DISABLE_CHIP_SELECT, HIGH); - } - if (!sd.begin(chipSelect, SPI_SPEED)) { - if (sd.card()->errorCode()) { - cout << F( - "\nSD initialization failed.\n" - "Do not reformat the card!\n" - "Is the card correctly inserted?\n" - "Is chipSelect set to the correct value?\n" - "Does another SPI device need to be disabled?\n" - "Is there a wiring/soldering problem?\n"); - cout << F("\nerrorCode: ") << hex << showbase; - cout << int(sd.card()->errorCode()); - cout << F(", errorData: ") << int(sd.card()->errorData()); - cout << dec << noshowbase << endl; - return; - } - cout << F("\nCard successfully initialized.\n"); - if (sd.vol()->fatType() == 0) { - cout << F("Can't find a valid FAT16/FAT32 partition.\n"); - reformatMsg(); - return; - } - cout << F("Can't determine error type\n"); - return; - } - cout << F("\nCard successfully initialized.\n"); - cout << endl; - - uint32_t size = sd.card()->sectorCount(); - if (size == 0) { - cout << F("Can't determine the card size.\n"); - cardOrSpeed(); - return; - } - uint32_t sizeMB = 0.000512 * size + 0.5; - cout << F("Card size: ") << sizeMB; - cout << F(" MB (MB = 1,000,000 bytes)\n"); - cout << endl; - cout << F("Volume is FAT") << int(sd.vol()->fatType()); - cout << F(", Cluster size (bytes): ") << sd.vol()->bytesPerCluster(); - cout << endl << endl; - - cout << F("Files found (date time size name):\n"); - sd.ls(LS_R | LS_DATE | LS_SIZE); - - if ((sizeMB > 1100 && sd.vol()->sectorsPerCluster() < 64) - || (sizeMB < 2200 && sd.vol()->fatType() == 32)) { - cout << F("\nThis card should be reformatted for best performance.\n"); - cout << F("Use a cluster size of 32 KB for cards larger than 1 GB.\n"); - cout << F("Only cards larger than 2 GB should be formatted FAT32.\n"); - reformatMsg(); - return; - } - // Read any extra Serial data. - do { - delay(10); - } while (Serial.available() && Serial.read() >= 0); - cout << F("\nSuccess! Type any character to restart.\n"); - while (!Serial.available()) { - SysCall::yield(); - } -} - -*/ - - - - -/* - * EXEMPLE 1 - -#include "Arduino.h" - -// the setup function runs once when you press reset or power the board -void setup() { - // initialize digital pin LED_BUILTIN as an output. - pinMode(0, OUTPUT); - - SerialUSB.begin(9600); -} - -// the loop function runs over and over again forever -void loop() -{ - digitalWrite(0, HIGH); // turn the LED on (HIGH is the voltage level) - delay(1000); // wait for a second - digitalWrite(0, LOW); // turn the LED off by making the voltage LOW - delay(1000); // wait for a second - SerialUSB.println("Hello, Arduino!"); -} - -*/ diff --git a/software/system/main.c b/software/system/main.c index d045cc5..c03acd9 100644 --- a/software/system/main.c +++ b/software/system/main.c @@ -3,7 +3,7 @@ #include "debug.h" #include "filesystem.h" #include "picture.h" -#include "ost_tasker.h" +#include "os.h" #include "rotary-button.h" #define RUN_TESTS 1 @@ -14,7 +14,7 @@ int main(void) // Low level initialization, mainly platform stuff // After this call, debug_printf *MUST* be available ost_system_initialize(); - debug_printf("\r\n>>>>> Starting OpenStoryTeller: V%d.%d <<<<<\n", 1, 0); + debug_printf("\r\n [OST] Starting OpenStoryTeller tests: V%d.%d\r\n", 1, 0); // File system access filesystem_mount(); @@ -38,24 +38,127 @@ int main(void) #include "sdcard.h" +const uint16_t tones[3][8] = + { + {0xff, 131, 147, 165, 175, 196, 220, 247}, + {0xff, 262, 294, 330, 349, 392, 440, 494}, + {0xff, 524, 988, 660, 698, 784, 880, 988}, +}; + +const uint8_t Happy_birthday[] = + { + 6, 1, 3, 5, 1, 1, 3, 1, 2, 5, 1, 2, 1, 2, 2, 6, 1, 1, 5, 1, 1, 6, 1, 4, 3, 1, 1, + 5, 1, 1, 6, 1, 1, 5, 1, 2, 3, 1, 2, 1, 1, 1, 6, 0, 1, 5, 1, 1, 3, 1, 1, 2, 1, 4, + 2, 1, 3, 3, 1, 1, 5, 1, 2, 5, 1, 1, 6, 1, 1, 3, 1, 2, 2, 1, 2, 1, 1, 4, 5, 1, 4, + 3, 1, 1, 2, 1, 1, 1, 1, 1, 6, 0, 1, 1, 1, 1, 5, 0, 8, 0}; + +#include +#include +#include + +#include "audio_player.h" + +void ost_hal_panic() +{ +} + +extern void qor_sleep(); + +void UserTask_0(void *args) +{ + // InstrumentTriggerPE11_Init(); + // uint32_t count = 0; + while (1) + { + + ost_hal_gpio_set(OST_GPIO_DEBUG_LED, 1); + + // qor_sleep(); + + for (int i = 0; i < 65500; i++) + { + for (int j = 0; j < 100; j++) + ; + } + + // ost_system_delay_ms(500); + ost_hal_gpio_set(OST_GPIO_DEBUG_LED, 0); + // ost_system_delay_ms(500); + + for (int i = 0; i < 65500; i++) + { + for (int j = 0; j < 100; j++) + ; + } + } +} + +void UserTask_1(void *args) +{ + while (1) + { + ost_system_delay_ms(1000); + debug_printf("X\n"); + ost_system_delay_ms(1000); + } +} +/* +void UserTask_2(void) +{ + InstrumentTriggerPE13_Init(); + uint32_t count = 0; + while (1) + { + InstrumentTriggerPE13_Toggle(); + count++; + if (count % 35 == 0) + OS_Thread_Sleep(4500); + else + HAL_Delay(70); + } +} + +void UserTask_3(void) +{ + InstrumentTriggerPE14_Init(); + while (1) + { + InstrumentTriggerPE14_Toggle(); + HAL_Delay(60); + } +} +*/ +#include "pico/stdlib.h" +extern size_t __StackTop; int main() { ost_system_initialize(); // 1. Test the printf output debug_printf("\r\n [OST] Starting OpenStoryTeller tests: V%d.%d\r\n", 1, 0); + /* + filesystem_mount(); - filesystem_mount(); + picture_show("example.bmp"); + */ + // ost_audio_play("out2.wav"); - // 2. Unit test the SD Card - // sdcard_init(); + OS_Init(THREADFREQ); + qor_create_thread(UserTask_0, 1, "UserTask_0"); + // qor_create_thread(UserTask_1, 2, "UserTask_1"); + // OS_Thread_Create(UserTask_2, OS_SCHEDL_PRIO_MAIN_THREAD, "UserTask_2"); + // OS_Thread_Create(OnboardUserButton_Task, OS_SCHEDL_PRIO_EVENT_THREAD, "OnboardUserButton_Task"); + qor_start(); for (;;) { - ost_hal_gpio_set(OST_GPIO_DEBUG_LED, 1); - ost_system_delay_ms(1000); - ost_hal_gpio_set(OST_GPIO_DEBUG_LED, 0); - ost_system_delay_ms(1000); + + // ost_hal_audio_loop(); + + // ost_hal_gpio_set(OST_GPIO_DEBUG_LED, 1); + // ost_system_delay_ms(1000); + // ost_hal_gpio_set(OST_GPIO_DEBUG_LED, 0); + // ost_system_delay_ms(1000); } return 0; } diff --git a/software/system/os.c b/software/system/os.c new file mode 100644 index 0000000..9dc2753 --- /dev/null +++ b/software/system/os.c @@ -0,0 +1,299 @@ +/** + * @brief + * + * // Quite OK RTOS scheduler is a very simple real-time, pre-emptive, tickless tasker + * Design goals: + * - Easily portable (limited assembly) + * - Tick-less + * - Preemptive + * - Only one inter-thread resource: mailboxes (no mutex, semaphore...) + + */ + +#include "ost_hal.h" +#include "debug.h" +#include "os.h" +#include + +#include "pico/critical_section.h" + +/** + * The fn OSAsm_ThreadSwitch, implemented in os_asm.s, is periodically called by the SchedlTimer (ISR). + * It preemptively switches to the next thread, that is, it stores the stack of the running + * thread and restores the stack of the next thread. + * It calls OS_Schedule to determine which thread is run next and update RunPt. + */ +extern void OSAsm_ThreadSwitch(void); + +static critical_section_t acrit; + +inline static void enable_irq() +{ + critical_section_exit(&acrit); +} + +inline static void disable_irq() +{ + critical_section_enter_blocking(&acrit); +} + +void timer_set_period(uint16_t i); + +void ost_tasker_sleep_for(uint32_t ms) +{ + timer_set_period(ms); +} + +void ost_tasker_init() +{ + ost_tasker_sleep_for(5000); // 5 seconds +} + +static uint32_t counter = 0; + +// void ost_tasker_timer_callback() +// { +// // debug_printf("%d\n", counter++); + +// qor_switch_context(); +// } + +#define OS_DEBUG + +/** + * TCBState indicates whether the TCB can be used by OS_ThreadCreate + * to create a new thread. + */ +typedef enum +{ + TCBStateFree, + TCBStateActive +} TCBState_t; + +void qor_sleep_ms(uint8_t svc, uint32_t ms) +{ + + __wfi; +} + +/** + * Thread Control Block + * + * IMPORTANT! + * The fn OSAsm_Start and OSAsm_ThreadSwitch, implemented in os_asm.s, expect the stack pointer + * to be placed first in the struct. Don't shuffle it! + */ +typedef struct TCB +{ + uint32_t *sp; /* Stack pointer, valid for threads not running */ + struct TCB *next; /* Pointer to circular-linked-list of TCBs */ + uint32_t sleep; /* Sleep duration in ms, zero means not sleeping */ + TCBState_t status; /* TCB active or free */ + Semaphore_t *blocked; /* Pointer to semaphore on which the thread is blocked, NULL if not blocked */ + uint8_t priority; /* Thread priority, 0 is highest, 255 is lowest */ + const char *name; /* Descriptive name to facilitate debugging */ + uint32_t pc; +} TCB_t; + +//================================================================================================== +// GLOBAL AND STATIC VARIABLES +//================================================================================================== + +static TCB_t TCBs[MAXNUMTHREADS]; +static uint32_t Stacks[MAXNUMTHREADS][STACKSIZE]; + +/* Pointer to the currently running thread */ +TCB_t *RunPt; + +static uint32_t global_stack[1000]; + +/* The variable ActiveTCBsCount tracks the number of TCBs in use by the OS */ +static uint32_t ActiveTCBsCount; + +static void OS_InitTCBsStatus(void) +{ + for (uint32_t idx = 0; idx < MAXNUMTHREADS; idx++) + { + TCBs[idx].status = TCBStateFree; + } +} + +void OS_Init(uint32_t scheduler_frequency_hz) +{ + critical_section_init(&acrit); + OS_InitTCBsStatus(); +} + +void OS_ExitLoop() +{ + for (;;) + ; +} + +extern void qor_go(); + +uint32_t *qor_initialize_stack(uint32_t *top_of_stack, thread_func_t task, void *args) +{ + // ARM Calling convention: the folowwing registers are automatically saved onto the stack by the processor (in this ordoer on the stack) + // DDI0419C_arm_architecture_v6m_reference_manual-1.pdf B1.5.6 Exception entry behavior + top_of_stack--; + /* From the "STM32 Cortex-M4 Programming Manual" on page 23: + * attempting to execute instructions when the T bit is 0 results in a fault or lockup */ + *top_of_stack = 0x01000000; /* Thumb Bit (PSR) */ + top_of_stack--; + *top_of_stack = (uint32_t)task; // PC Program Counter (R15) + top_of_stack--; + *top_of_stack = (uint32_t)OS_ExitLoop; /* (LR) Link Register (Return address) R14 */ + top_of_stack -= 5; // skip R12, R3, R2, R1 + *top_of_stack = (uint32_t)args; // R0 + top_of_stack -= 8; // R11 -> R4 + return top_of_stack; +} + +void qor_create_thread(thread_func_t task, uint8_t priority, const char *name) +{ + assert_or_panic(ActiveTCBsCount >= 0 && ActiveTCBsCount < MAXNUMTHREADS); + disable_irq(); + + /* Find next available TCB */ + int32_t new_tcb_idx = -1; + for (new_tcb_idx = 0; new_tcb_idx < MAXNUMTHREADS; new_tcb_idx++) + { + if (TCBs[new_tcb_idx].status == TCBStateFree) + { + break; + } + } + + if (new_tcb_idx >= 0) + { + if (new_tcb_idx == 0) + { + RunPt = &(TCBs[0]); + } + else + { + TCBs[new_tcb_idx].next = RunPt->next; + } + + TCBs[new_tcb_idx].sleep = 0; + TCBs[new_tcb_idx].status = TCBStateActive; + TCBs[new_tcb_idx].blocked = NULL; + TCBs[new_tcb_idx].priority = priority; + TCBs[new_tcb_idx].name = name; + TCBs[new_tcb_idx].sp = qor_initialize_stack(&Stacks[new_tcb_idx][STACKSIZE], task, (void *)name); + + RunPt->next = &(TCBs[new_tcb_idx]); + ActiveTCBsCount++; + } + + enable_irq(); +} + +void qor_start(void) +{ + assert_or_panic(ActiveTCBsCount > 0); + + /* Prevent the timer's ISR from firing before OSAsm_Start is called */ + disable_irq(); + + qor_go(); + + /* This statement should not be reached */ + ost_hal_panic(); +} + +void qor_scheduler(void) +{ + TCB_t *next_pt = RunPt->next; + TCB_t *iterating_pt = next_pt; + + /* Search for highest priority thread not sleeping or blocked */ + uint32_t max_priority = RunPt->priority; + TCB_t *best_pt = next_pt; + do + { + if ((iterating_pt->priority > max_priority) && (iterating_pt->sleep == 0) && (iterating_pt->blocked == NULL)) + { + best_pt = iterating_pt; + max_priority = best_pt->priority; + } + iterating_pt = iterating_pt->next; + } while (iterating_pt != next_pt); + + RunPt = best_pt; +} + +void OS_Thread_Suspend(void) +{ + // SchedlTimer_ResetCounter(); +} + +void OS_Thread_Sleep(uint32_t sleep_duration_ms) +{ + RunPt->sleep = sleep_duration_ms; + OS_Thread_Suspend(); +} + +void OS_DecrementTCBsSleepDuration(void) +{ + for (size_t tcb_idx = 0; tcb_idx < MAXNUMTHREADS; tcb_idx++) + { + if (TCBs[tcb_idx].sleep > 0) + { + TCBs[tcb_idx].sleep -= 1; + } + } +} + +void OS_Thread_Kill(void) +{ + assert_or_panic(ActiveTCBsCount > 1); + disable_irq(); + + TCB_t *previous_tcb = RunPt; + while (1) + { + previous_tcb = previous_tcb->next; + if (previous_tcb->next == RunPt) + break; + } + TCB_t *next_tcb = RunPt->next; + + previous_tcb->next = next_tcb; + RunPt->status = TCBStateFree; + + ActiveTCBsCount--; + enable_irq(); + OS_Thread_Suspend(); +} + +void OS_Semaphore_Wait(Semaphore_t *sem) +{ + disable_irq(); + (*sem) = (*sem) - 1; + if ((*sem) < 0) + { + RunPt->blocked = sem; /* Reason the thread is blocked */ + enable_irq(); + OS_Thread_Suspend(); + } + enable_irq(); +} + +void OS_Semaphore_Signal(Semaphore_t *sem) +{ + disable_irq(); + (*sem) = (*sem) + 1; + if ((*sem) <= 0) + { + /* Search for a TCB blocked on this semaphore and wake it up */ + TCB_t *a_tcb = RunPt->next; + while (a_tcb->blocked != sem) + { + a_tcb = a_tcb->next; + } + a_tcb->blocked = 0; + } + enable_irq(); +} diff --git a/software/system/os.h b/software/system/os.h new file mode 100644 index 0000000..7857e63 --- /dev/null +++ b/software/system/os.h @@ -0,0 +1,62 @@ +#ifndef OS_H +#define OS_H + +void ost_tasker_init(); + +extern void ost_hal_panic(); + +/** + * Assert, or panic. + */ +#define assert_or_panic(expr) ((expr) ? (void)0U : ost_hal_panic()) + +/** + * The module os encapsulates the core functionality of the operating system and + * exposes the functions for interacting with it. + */ + +#include + +#define MAXNUMTHREADS 10 /* Maximum number of threads, allocated at compile time */ +#define STACKSIZE 100 /* Number of 32-bit words in each TCB's stack */ +#define THREADFREQ 1 /* Maximum time-slice, in Hz, before the scheduler is run */ + +#define OS_SCHEDL_PRIO_MIN 1 /* Lowest priority that can be assigned to a thread */ +#define OS_SCHEDL_PRIO_MAX UINT8_MAX /* Highest priority that can be assigned to a thread */ + +/** + * The type Semaphore_t abstracts the semaphore's counter. + * A value of type *Semaphore_t should only be updated through the fn OS_Semaphore_Wait + * and OS_Semaphore_Signal. + */ +typedef int32_t Semaphore_t; + +typedef void (*thread_func_t)(void *args); + +/** + * Function descriptions are provided in os.c + */ + +void OS_Init(uint32_t scheduler_frequency_hz); + +// void OS_Thread_CreateFirst(thread_func_t task, uint8_t priority, const char *name); + +void qor_create_thread(thread_func_t task, uint8_t priority, const char *name); + +void qor_switch_context(); + +void qor_start(void); + +void OS_Thread_Suspend(void); + +void OS_Thread_Sleep(uint32_t sleep_duration_ms); + +void OS_DecrementTCBsSleepDuration(void); + +void OS_Thread_Kill(void); + +void OS_Semaphore_Wait(Semaphore_t *sem); + +void OS_Semaphore_Signal(Semaphore_t *sem); + +#endif // OST_TASKER_H diff --git a/software/system/ost_tasker.c b/software/system/ost_tasker.c deleted file mode 100644 index 753c113..0000000 --- a/software/system/ost_tasker.c +++ /dev/null @@ -1,42 +0,0 @@ -/** - * @brief - * - * // OST scheduler is a very simple real-time, pre-emptive, tickless tasker - * Design goals: - * - Easily portable (limited assembly) - * - Tick-less - * - Preemptive - * - Everything runs in interrupts - * - The background task is calling platform specific sleep modes - */ - -#include "ost_hal.h" -#include "debug.h" - -typedef struct -{ - uint8_t regs; - -} cpu_t; - -void timer_set_period(uint16_t i); - -void ost_tasker_sleep_for(uint32_t ms) -{ - timer_set_period(ms); -} - -void ost_tasker_init() -{ - ost_tasker_sleep_for(5000); // 5 seconds -} - -static uint32_t counter = 0; -void ost_tasker_timer_callback() -{ - debug_printf("%d\n", counter++); -} - -void ost_tasker_schedule(void) -{ -} diff --git a/software/system/ost_tasker.h b/software/system/ost_tasker.h deleted file mode 100644 index 3d2bdb9..0000000 --- a/software/system/ost_tasker.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef OST_TASKER_H -#define OST_TASKER_H - -void ost_tasker_init(); - -#endif // OST_TASKER_H diff --git a/software/system/picture.c b/software/system/picture.c index ae0ce62..b94d868 100644 --- a/software/system/picture.c +++ b/software/system/picture.c @@ -14,7 +14,7 @@ typedef FIL file_t; #else // Use standard library -typedef FILE* file_t; +typedef FILE *file_t; typedef int FRESULT; #define F_OK #endif @@ -26,7 +26,7 @@ file_t ost_file_open(const char *filename) FRESULT fr = f_open(&fil, filename, FA_READ); if (fr != FR_OK) { - debug_printf("ERROR: f_open %d\n\r", (int) fr); + debug_printf("ERROR: f_open %d\n\r", (int)fr); } return fil; #else @@ -37,30 +37,32 @@ file_t ost_file_open(const char *filename) static uint8_t bmpImage[512]; static uint8_t decompressed[1024]; -static uint8_t palette[16*4]; +static uint8_t palette[16 * 4]; -typedef struct { - uint16_t type; /* Magic identifier */ - uint32_t size; /* File size in bytes */ - uint16_t reserved1; - uint16_t reserved2; - uint32_t offset; /* Offset to image data, bytes */ +typedef struct +{ + uint16_t type; /* Magic identifier */ + uint32_t size; /* File size in bytes */ + uint16_t reserved1; + uint16_t reserved2; + uint32_t offset; /* Offset to image data, bytes */ } bmp_header_t; -typedef struct { - uint32_t size; /* Header size in bytes */ - uint32_t width; - uint32_t height; /* Width and height of image */ - uint16_t planes; /* Number of colour planes */ - uint16_t bits; /* Bits per pixel */ - uint32_t compression; /* Compression type */ - uint32_t imagesize; /* Image size in bytes */ - uint32_t xresolution; - uint32_t yresolution; /* Pixels per meter */ - uint32_t ncolours; /* Number of colours */ - uint32_t importantcolours; /* Important colours */ - uint32_t rgb; - uint32_t rgb2; +typedef struct +{ + uint32_t size; /* Header size in bytes */ + uint32_t width; + uint32_t height; /* Width and height of image */ + uint16_t planes; /* Number of colour planes */ + uint16_t bits; /* Bits per pixel */ + uint32_t compression; /* Compression type */ + uint32_t imagesize; /* Image size in bytes */ + uint32_t xresolution; + uint32_t yresolution; /* Pixels per meter */ + uint32_t ncolours; /* Number of colours */ + uint32_t importantcolours; /* Important colours */ + uint32_t rgb; + uint32_t rgb2; } bmp_infoheader_t; static const uint32_t HEADER_SIZE = 14; @@ -92,7 +94,7 @@ uint8_t parse_bmp(const uint8_t *data, bmp_header_t *header, bmp_infoheader_t *i return isBmp; } -void decompress(const char *filename) +void picture_show(const char *filename) { file_t fil; uint32_t offset; @@ -108,7 +110,7 @@ void decompress(const char *filename) fseek(fil, offset, SEEK_SET); fread(bmpImage, sizeof(uint8_t), 512, fil); #endif - int nblines=0; + int nblines = 0; bmp_header_t header; bmp_infoheader_t info_header; @@ -146,7 +148,7 @@ void decompress(const char *filename) // buffer de sortie, bitmap décompressé memset(decompressed, 0, sizeof(decompressed)); - // btea((uint32_t*) bmpImage, -128, key); + // btea((uint32_t*) bmpImage, -128, key); uint32_t pixel = 0; // specify the pixel offset bool end = false; @@ -174,7 +176,7 @@ void decompress(const char *filename) compressed = &bmpImage[0]; i = 0; } - + uint8_t rleCmd = compressed[i]; if (rleCmd > 0) { @@ -184,7 +186,7 @@ void decompress(const char *filename) { if ((j & 1) == 0) { - decompressed[pixel] = (val & 0xF0) >>4; + decompressed[pixel] = (val & 0xF0) >> 4; } else { @@ -245,7 +247,7 @@ void decompress(const char *filename) if (pixel >= info_header.width) { debug_printf("!"); - // pixel = 0; + // pixel = 0; } } i += 2 + (second / 2); @@ -261,7 +263,7 @@ void decompress(const char *filename) { // enough pixels to write a line to the screen ost_display_draw_h_line(pos.y, decompressed, palette); - debug_printf("POS Y: %d", pos.y); + // debug_printf("POS Y: %d", pos.y); memset(decompressed, 0, sizeof(decompressed)); // ili9341_write(&pos, decompressed); @@ -280,15 +282,14 @@ void decompress(const char *filename) if (totalPixels > (info_header.width * info_header.height)) { - end = true; // error + end = true; // error } - } - while((offset < fileSize) && !end); + } while ((offset < fileSize) && !end); - if (end) - { - debug_printf("\r\n>>>>> Decoding error !! <<<<\r\n"); - } + // if (end) + // { + // debug_printf("\r\n>>>>> Decoding error !! <<<<\r\n"); + // } // Fill missing lines if (nblines < info_header.height) @@ -297,7 +298,7 @@ void decompress(const char *filename) int missing_lines = (info_header.height - nblines); for (int i = 0; i < missing_lines; i++) { - //ili9341_draw_h_line(pos.y, decompressed, palette); + // ili9341_draw_h_line(pos.y, decompressed, palette); nblines++; } } @@ -308,9 +309,5 @@ void decompress(const char *filename) fclose(fil); #endif - debug_printf("\r\nNb lines :%d\r\nTotal pixels: %d", (uint32_t)nblines, (uint32_t)totalPixels); } - - - diff --git a/software/system/picture.h b/software/system/picture.h index d34e9cf..aa8e3a8 100644 --- a/software/system/picture.h +++ b/software/system/picture.h @@ -3,14 +3,14 @@ #define PICTURE_H #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -void decompress(const char *filename); + void picture_show(const char *filename); #ifdef __cplusplus } #endif #endif // PICTURE_H - diff --git a/software/system/qor_armv6m.s b/software/system/qor_armv6m.s new file mode 100644 index 0000000..0885b9b --- /dev/null +++ b/software/system/qor_armv6m.s @@ -0,0 +1,171 @@ +.syntax unified +.cpu cortex-m0 +.fpu softvfp +.thumb +.code 16 +.thumb_func + +@ The .global directive gives the symbols external linkage. +@ For clarity, the fn OSAsm_ThreadSwitch is exported as TIM2_IRQHandler, so that the vector table +@ in startup.s doesn't need to be modified. +.global qor_go +.global qor_switch_context +.global qor_sleep + +.extern RunPt +.extern qor_scheduler +.extern qor_svc_call + +.section .text.qor_go +.type qor_go, %function + +@ The Cortex-M0 is limited on the thumb number of instructionss +@ the context restore is therefore a bit more complex that M3 or M4 CPU +@ One limitation is the POP instruction that cannot access to the HI registers (registers after R7) +@ +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ | | +@ ------------- +@ +@ +@ +@ + + +qor_go: + + LDR r2, =RunPt @ R0 = &RunPt; // TCB_t** R0 = &RunPt // R0 contient l'adresse de la variable 'RunPt', qui est un pointeur vers la structure TCB courante + LDR r3, [r2] @ R1 = *R0; // TCB_t* R1 = RunPt // R1 pointe maintenant sur RunPt + LDR r0, [r3] @ R2 = *R1 // On veut la vleur pointée par RunPt (premier élément, donc même adresse) qui correspon à la variable *sp (c'est un pointeur vers la stack) + + adds r0, #32 @ Discard everything up to r0. + msr psp, r0 @ SP = R2; // uint32_t SP = R2 + @ now we switched to the thread's stack, which we populated before + + movs r0, #2 + msr CONTROL, r0 @ ensure we use PSP not MSP + + + isb @ throw away any prefetched instructions at this point + pop {r0-r5} @ R0 contains the argument, R1 the link register + mov lr, r5 @ Update LR + pop {r3} @ Task code + pop {r2} @ Pop and discard XPSR. */ + cpsie i @ Enable interrupts before launching the + bx r3 @ /* Finally, jump to the user defined task code. */ + + +@ .section .text.qor_sleep +@ .type qor_sleep, %function + +@ qor_sleep: +@ mov r1, r0 @ copy sleep value in second argument +@ movs r0, #1 @ sleep is SVC 1 +@ mov r2, lr @ remember where we are +@ bl qor_svc_call +@ wfi +@ bx lr + +@ EnterCriticalSection: +@ MRS r0, PRIMASK /* Save interrupt state. */ +@ CPSID i /* Turn off interrupts. */ +@ BX lr /* Return. */ + +@ ExitCriticalSection: +@ MSR PRIMASK, r0 /* Restore interrupt states. */ +@ BX lr /* Return. */ + + + +@ EXC_RETURN value to return to Thread mode, while restoring state from PSP. +.equ EXC_RETURN, 0xfffffffd + +.section .text.qor_switch_context +.type qor_switch_context, %function +qor_switch_context: + + .thumb + .syntax unified + + +@ Les registers r0-r3 r12 etc. sont sauvegardés par le processeur + + mrs r0, psp @ get the current stack address () + + ldr r1, =RunPt + ldr r2, [r1] + + @ Subtract room for the registers we are going to store. We have to pre-subtract + @ and use the incrementing store multiple instruction because the CM0+ doesn't + @ have the decrementing variant. + subs r0, r0, #32 + str r0, [r2] @ Save the new top of stack + + @ Save registers on the stack. This has to be done in two stages because + @ the stmia instruction cannot access the upper registers on the CM0+. + stmia r0!, {r4-r7} + mov r4, r8 + mov r5, r9 + mov r6, r10 + mov r7, r11 + stmia r0!, {r4-r7} + + cpsid i + bl qor_scheduler + cpsie i + + ldr R0, =RunPt + ldr r1, [r0] + ldr r0, [r1] @ R0 is the stack address + + @ On est obligé de poper les registres haut d'abord (on n'a pas accès directement à ceux-ci via ldmia) + adds r0, r0, #16 @ move to hi regs + ldmia r0!, {r4-r7} @ pop + mov r8, r4 + mov r9, r5 + mov r10, r6 + mov r11, r7 + + + msr psp, r0 @ new task stack top address + + subs r0, r0, #32 + ldmia r0!, {r4-r7} + + // Exit handler. Using a bx to the special EXC_RETURN values causes the + // processor to perform the exception return behavior. + ldr r0, =EXC_RETURN + + bx r0 + diff --git a/software/system/sdcard.c b/software/system/sdcard.c index 5634420..8a448ad 100644 --- a/software/system/sdcard.c +++ b/software/system/sdcard.c @@ -155,8 +155,8 @@ static SD_Error SD_SendCmd(uint8_t cmd, uint32_t arg, uint8_t crc) ost_hal_sdcard_spi_transfer((uint8_t)arg); /*!< byte 5 */ ost_hal_sdcard_spi_transfer(crc | 0x01); /*!< byte 6: CRC */ /* a byte received immediately after CMD12 should be discarded... */ - // if (cmd == SD_CMD_STOP_TRANSMISSION) - // ost_hal_sdcard_spi_transfer(); + if (cmd == SD_CMD_STOP_TRANSMISSION) + ost_hal_sdcard_spi_transfer(0xFF); /* SD Card responds within Ncr (response time), which is 0-8 bytes for SDSC cards, 1-8 bytes for MMC cards */ do @@ -538,7 +538,7 @@ SD_Error sdcard_sectors_read(uint32_t readAddr, uint8_t *pBuffer, uint32_t nbSec { SD_Error state; - debug_printf("--> reading %lu sectors from %lu ...", nbSectors, readAddr); + // debug_printf("--> reading %lu sectors from %lu ...", nbSectors, readAddr); /* non High Capacity cards use byte-oriented addresses */ if (cardType != SD_Card_SDHC) @@ -733,7 +733,7 @@ SD_Error sdcard_sectors_erase(uint32_t eraseAddrFrom, uint32_t eraseAddrTo) return SD_ILLEGAL_COMMAND; } - debug_printf("--> erasing sectors from %lu to %lu ...", eraseAddrFrom, eraseAddrTo); + // debug_printf("--> erasing sectors from %lu to %lu ...", eraseAddrFrom, eraseAddrTo); /* non High Capacity cards use byte-oriented addresses */ if (cardType != SD_Card_SDHC) diff --git a/software/system/sst.c b/software/system/sst.c deleted file mode 100644 index 714576b..0000000 --- a/software/system/sst.c +++ /dev/null @@ -1,173 +0,0 @@ - -/*-------------------------------------------------------------------------- - IMPORTATION DES FICHIERS DE DEFINITION DE LIBRAIRIE ---------------------------------------------------------------------------*/ -#include "sst_conf.h" -#include - -/*-------------------------------------------------------------------------- - PROTOTYPE DES FONCTIONS INTERNES ---------------------------------------------------------------------------*/ - - -/*-------------------------------------------------------------------------- - VARIABLES GLOBALES ---------------------------------------------------------------------------*/ -/* Public-scope objects ----------------------------------------------------*/ -uint16_t SST_currPrio_ = (uint16_t)0xFF; /* current SST priority */ -uint16_t SST_readySet_ = (uint16_t)0; /* SST ready-set */ - -typedef struct { - SSTTask task__; - SSTEvent *queue__; - uint16_t end__; - uint16_t head__; - uint16_t tail__; - uint16_t nUsed__; - uint16_t mask__; -} TaskCB; - -/* Local-scope objects -----------------------------------------------------*/ -static TaskCB l_taskCB[SST_MAX_PRIO]; - - -/*========================================================================== -====================== I N T E R R U P T I O N S ========================= -==========================================================================*/ - -/*========================================================================== -========================== F O N C T I O N S ============================= -==========================================================================*/ - -/*..........................................................................*/ -void SST_Task(SSTTask task, uint16_t prio, SSTEvent *queue, uint16_t qlen, - SSTSignal sig, SSTParam par) -{ - SSTEvent ie; /* initialization event */ - TaskCB *tcb = &l_taskCB[prio - 1]; - tcb->task__ = task; - tcb->queue__ = queue; - tcb->end__ = qlen; - tcb->head__ = (uint16_t)0; - tcb->tail__ = (uint16_t)0; - tcb->nUsed__ = (uint16_t)0; - tcb->mask__ = (1 << (prio - 1)); - ie.sig = sig; - ie.par = par; - tcb->task__(ie); /* initialize the task */ -} -/*..........................................................................*/ -void SST_Run(void) { - SST_Start(); /* start ISRs */ - - SST_INT_LOCK(); - SST_currPrio_ = (uint16_t)0; /* set the priority for the SST idle loop */ - SST_Schedule_(); /* process all events produced so far */ - SST_INT_UNLOCK(); - - for (;;) { /* the SST idle loop */ - SST_OnIdle(); /* invoke the on-idle callback */ - } -} -/*..........................................................................*/ -uint16_t SST_Post(uint16_t prio, SSTSignal sig, SSTParam par) { - TaskCB *tcb = &l_taskCB[prio - 1]; - SST_INT_LOCK(); - if (tcb->nUsed__ < tcb->end__) { - tcb->queue__[tcb->head__].sig = sig;/* insert the event at the head */ - tcb->queue__[tcb->head__].par = par; - if ((++tcb->head__) == tcb->end__) { - tcb->head__ = (uint16_t)0; /* wrap the head */ - } - if ((++tcb->nUsed__) == (uint16_t)1) { /* the first event? */ - SST_readySet_ |= tcb->mask__; /* insert task to the ready set */ - SST_Schedule_(); /* check for synchronous preemption */ - } - SST_INT_UNLOCK(); - return (uint16_t)1; /* event successfully posted */ - } - else { - SST_INT_UNLOCK(); - return (uint16_t)0; /* queue full, event posting failed */ - } -} -/*..........................................................................*/ -uint16_t SST_MutexLock(uint16_t prioCeiling) { - uint16_t p; - SST_INT_LOCK(); - p = SST_currPrio_; /* the original SST priority to return */ - if (prioCeiling > SST_currPrio_) { - SST_currPrio_ = prioCeiling; /* raise the SST priority */ - } - SST_INT_UNLOCK(); - return p; -} -/*..........................................................................*/ -void SST_MutexUnlock(uint16_t orgPrio) { - SST_INT_LOCK(); - if (orgPrio < SST_currPrio_) { - SST_currPrio_ = orgPrio; /* restore the saved priority to unlock */ - SST_Schedule_(); /* invoke scheduler after lowering the priority */ - } - SST_INT_UNLOCK(); -} -/*..........................................................................*/ -/* NOTE: the SST scheduler is entered and exited with interrupts LOCKED */ -void SST_Schedule_(void) { - uint16_t pin = SST_currPrio_; /* save the initial priority */ - uint16_t p; /* the new priority */ - - static const uint8_t log2Lkup[] = { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 - }; - - - while ((p = log2Lkup[SST_readySet_]) > pin) { - - /* is the new priority higher than the initial? */ -// for( p=SST_MAX_PRIO; p>pin; p--) { - // if( SST_readySet_ & (1<<(p-1)) ) { - // if( p > pin ) { - - // reprise de l'ancien code - TaskCB *tcb = &l_taskCB[p - 1]; - /* get the event out of the queue */ - SSTEvent e = tcb->queue__[tcb->tail__]; - if ((++tcb->tail__) == tcb->end__) { - tcb->tail__ = (uint16_t)0; - } - if ((--tcb->nUsed__) == (uint16_t)0) {/* is the queue becoming empty?*/ - SST_readySet_ &= ~tcb->mask__; /* remove from the ready set */ - } - SST_currPrio_ = p; /* this becomes the current task priority */ - SST_INT_UNLOCK(); /* unlock the interrupts */ - - (*tcb->task__)(e); /* call the SST task */ - - SST_INT_LOCK(); /* lock the interrupts for the next pass */ - // } - // } - } - SST_currPrio_ = pin; /* restore the initial priority */ -} - -/*-------------------------------------------------------------------------- - FIN FICHIER ---------------------------------------------------------------------------*/ -/** @} */ - diff --git a/software/system/sst.h b/software/system/sst.h deleted file mode 100644 index d50433a..0000000 --- a/software/system/sst.h +++ /dev/null @@ -1,66 +0,0 @@ - -#ifndef _SST_H -#define _SST_H - -#include "global_defines.h" - -/*-------------------------------------------------------------------------- - DEFINITIONS ---------------------------------------------------------------------------*/ -typedef uint16_t SSTSignal; -typedef uint16_t SSTParam; - -typedef struct SSTEventTag SSTEvent; -struct SSTEventTag { - SSTSignal sig; - SSTParam par; -}; - -typedef void (*SSTTask)(SSTEvent e); - -/* SST interrupt entry and exit */ -#define SST_ISR_ENTRY(pin_, isrPrio_) do { \ - (pin_) = SST_currPrio_; \ - SST_currPrio_ = (isrPrio_); \ - SST_INT_UNLOCK(); \ -} while (0) - -#define SST_ISR_EXIT(pin_) do { \ - SST_INT_LOCK(); \ - SST_currPrio_ = (pin_); \ - SST_Schedule_(); \ -} while (0) - -/*-------------------------------------------------------------------------- - VARIABLES EXPORTEES ---------------------------------------------------------------------------*/ -/* public-scope objects */ -extern uint16_t SST_currPrio_; /* current priority of the executing task */ -extern uint16_t SST_readySet_; /* SST ready-set */ - - -/*-------------------------------------------------------------------------- - FONCTIONS EXPORTEES ---------------------------------------------------------------------------*/ -void SST_Init(void); -void SST_Task(SSTTask task, uint16_t prio, SSTEvent *queue, uint16_t qlen, - SSTSignal sig, SSTParam par); -void SST_Start(void); -void SST_Run(void); -void SST_OnIdle(void); -void SST_Exit(void); - -uint16_t SST_Post(uint16_t prio, SSTSignal sig, SSTParam par); - -uint16_t SST_MutexLock(uint16_t prioCeiling); -void SST_MutexUnlock(uint16_t orgPrio); - -void SST_Schedule_(void); - -#endif /* _SST_H */ - -/*-------------------------------------------------------------------------- - FIN FICHIER ---------------------------------------------------------------------------*/ - - diff --git a/software/system/sst_conf.c b/software/system/sst_conf.c deleted file mode 100644 index b263601..0000000 --- a/software/system/sst_conf.c +++ /dev/null @@ -1,127 +0,0 @@ - -// Scheduler -#include "sst_conf.h" - -#include "board.h" -#include "app_airserenity.h" -#include "app_clock.h" -#include "esp32.h" -#include "time_server.h" -#include "uart1.h" -#include "data_storage.h" - -// TCP/IP -#include "tcp_client.h" - -// C library -#include -#include -#include - - -// Event queue, one per task -static SSTEvent TaskAppSerenityQueue[10]; -static SSTEvent TaskTcpIpQueue[10]; - -AirParam_t AirParam; - -#define CONSOLE_RCV_BUF_SIZE 1024 - -static unsigned char consoleRcvBuf[CONSOLE_RCV_BUF_SIZE]; -static uint32_t consoleRcvSize = 0; -static uint8_t gEndOfFrameTimerId = 0U; - -void HandleUart1Rx(uint8_t data) -{ - if (consoleRcvSize < CONSOLE_RCV_BUF_SIZE) - { - consoleRcvBuf[consoleRcvSize] = data; - consoleRcvSize++; - TimerStart(gEndOfFrameTimerId); // restart timer from zero each time we receive data - // GpioDebug = 1; - } -} - -// Called when uart data has been received -void Console_ReceiveSignal(void) -{ - // GpioDebug = 0; - SST_Post(TASK_APP_SERENITY, RUN_SIG, APP_SIG_CONSOLE_DATA); -} - - -// ============================================================================= -// FONCTIONS DU MODULE -// ============================================================================= -void TASK_AppSerenity(SSTEvent e) -{ - switch (e.sig) { - case INIT_SIG: - - - break; - - // Called each second - case RUN_SIG: - if ((e.par == APP_SIG_LONG_PUSH) || (e.par == APP_SIG_SHORT_PUSH)) - { - APP_GestionBoutons(e.par); - } - else if (e.par == APP_SIG_CONSOLE_DATA) - { - // On envoie la donnée reçu pour traitement - APP_ReceptionConsole(consoleRcvBuf, consoleRcvSize); - consoleRcvSize = 0U; - } - - APP_SequencePrincipale(); - break; - - default: - break; - } -} - - -void TASK_Initialize(void) -{ - BOARD_Initialize(); - // Ok alors on va lire très très tôt les paramètres manuf - // Comme cela, on considère que AirParam est initialisé pour toutes les - // Tâches, au pire avec des valeurs par défaut - ReadAirParam(&AirParam); - - APP_Initialize(); - - // On utilise le port série en interruption, pour la réception uniquement - UART1_EnableRxIT(); - // 50ms end of receive detection - gEndOfFrameTimerId = TimerInit(50, Console_ReceiveSignal); - - // Initialisation des tâches - SST_Task(&TASK_AppSerenity, TASK_APP_SERENITY, TaskAppSerenityQueue, sizeof(TaskAppSerenityQueue)/sizeof(TaskAppSerenityQueue[0]), INIT_SIG, 0); - SST_Task(&net_task, TASK_TCP_IP, TaskTcpIpQueue, sizeof(TaskTcpIpQueue)/sizeof(TaskTcpIpQueue[0]), INIT_SIG, 0); - -} - -void TASK_TopSecond(void) -{ - CLOCK_Update(); - - APP_TopSecond(); - BOARD_TopSecond(); - - SST_Post(TASK_APP_SERENITY, RUN_SIG, 0); - SST_Post(TASK_TCP_IP, RUN_SIG, 0); -} - - -void TASK_EmergencyStop(void) -{ - // FIXME: implémenter quelque chose lorsque tout va mal (interrptions non gérées, dépassement mémoire ...) - // Idéalement: - // 1. Mettre un flag en mémoire non volatile (RAM non intialisée) - // 2. Redémarrer - // 3. Au démarrage suivant, détecter ce flag et logguer l'erreur (date, compteur, etc.) -} - diff --git a/software/system/sst_conf.h b/software/system/sst_conf.h deleted file mode 100644 index 8fb6e50..0000000 --- a/software/system/sst_conf.h +++ /dev/null @@ -1,52 +0,0 @@ -/** - * File: sst_conf.h - */ -#ifndef _SST_CONF_h -#define _SST_CONF_h - -#include "global_defines.h" -#include "sst.h" - -/*-------------------------------------------------------------------------- - DEFINITIONS ---------------------------------------------------------------------------*/ -/* SST interrupt locking/unlocking */ -#define SST_INT_LOCK() {_DISI = 1;} -#define SST_INT_UNLOCK() { _GIE = 1; } - -/* maximum SST task priority */ -#define SST_MAX_PRIO 8 - -/* the events used in the application */ -enum Events { - INIT_SIG, /* initialization event */ - REINIT_SIG, - RUN_SIG /* normal run */ -}; - -/* les priorités vont dans l'ordre croissant d'importance */ -enum SSTPriorities { /* the SST priorities don't need to be consecutive */ - TASK_IDLE = 0, - /* task priorities from low to high */ - ISR_TIMER2_PRIO, - ISR_TIMER3_PRIO, - ISR_UART1_PRIO, - ISR_UART2_PRIO, - - TASK_ESP32, - TASK_TCP_IP, - TASK_APP_SERENITY, -}; - -/*-------------------------------------------------------------------------- - FONCTIONS EXPORTEES ---------------------------------------------------------------------------*/ -void TASK_EmergencyStop(void); -void TASK_TopSecond(void); -void TASK_Initialize(void); - -#endif /* _SST_CONF_h */ - -/*-------------------------------------------------------------------------- - FIN FICHIER ---------------------------------------------------------------------------*/ diff --git a/story-player/.vscode/launch.json b/story-player/.vscode/launch.json new file mode 100644 index 0000000..969687f --- /dev/null +++ b/story-player/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "C/C++ debug", + "cwd": "${workspaceFolder}", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/story-player", // Binary to exec + "args": [ + "build/config.yml" + ], // Arguments passed + "stopAtEntry": false, + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "${defaultBuildTask}", + "miDebuggerPath": "/usr/bin/gdb" + } + ] +} \ No newline at end of file diff --git a/story-player/build_android.sh b/story-player/build_android.sh new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/story-player/build_android.sh @@ -0,0 +1 @@ + diff --git a/story-player/raygui.h b/story-player/raygui.h index 9acb173..8dcfb9b 100644 --- a/story-player/raygui.h +++ b/story-player/raygui.h @@ -1942,7 +1942,7 @@ int GuiToggleGroup(Rectangle bounds, const char *text, int *active) int result = 0; float initBoundsX = bounds.x; - bool temp = false; + int temp = 0; if (active == NULL) active = &temp; bool toggle = false; // Required for individual toggles