diff --git a/lib/pbio/drv/adc/adc_ev3.c b/lib/pbio/drv/adc/adc_ev3.c index 88da773dc..e18082311 100644 --- a/lib/pbio/drv/adc/adc_ev3.c +++ b/lib/pbio/drv/adc/adc_ev3.c @@ -1,206 +1,18 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2024 The Pybricks Authors +// Copyright (c) 2024-2025 The Pybricks Authors #include #if PBDRV_CONFIG_ADC_EV3 -#include -#include -#include +#if !PBDRV_CONFIG_BLOCK_DEVICE_EV3 +#error "EV3 block device driver must be enabled" +#endif -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "adc_ev3.h" -#include "../drv/block_device/block_device_ev3.h" -#include "../drv/gpio/gpio_ev3.h" - -#include "../sys/storage.h" - -#define PBDRV_ADC_EV3_NUM_DELAY_SAMPLES (2) - -/** - * Constants. - */ -enum { - // The maximum ADC clock speed according to the datasheet is 20 MHz. - // However, because the SPI peripheral does not have a fractional clock generator, - // the closest achievable in-spec speed is a division factor of 8. - // - // 150 MHz / 8 = 18.75 MHz actual - SPI_CLK_SPEED_ADC = 20000000, - - ADC_SAMPLE_PERIOD = 10, -}; - -// Construct both SPI peripheral settings (data format, chip select) -// and ADC chip settings (manual mode, 2xVref) in one go, -// so that DMA can be used efficiently. -// -// NOTE: CSHOLD is *not* set here, so that CS is deasserted between each 16-bit unit -#define MANUAL_ADC_CHANNEL(x) \ - (1 << 26) | \ - (SPI_SPIDAT1_DFSEL_FORMAT1 << SPI_SPIDAT1_DFSEL_SHIFT) | \ - (0 << (SPI_SPIDAT1_CSNR_SHIFT + PBDRV_EV3_SPI0_ADC_CS)) | \ - (1 << (SPI_SPIDAT1_CSNR_SHIFT + PBDRV_EV3_SPI0_FLASH_CS)) | \ - (1 << 12) | \ - (1 << 11) | \ - (((x) & 0xf) << 7) | \ - (1 << 6) - -static const uint32_t channel_cmd[PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES] = { - MANUAL_ADC_CHANNEL(0), - MANUAL_ADC_CHANNEL(1), - MANUAL_ADC_CHANNEL(2), - MANUAL_ADC_CHANNEL(3), - MANUAL_ADC_CHANNEL(4), - MANUAL_ADC_CHANNEL(5), - MANUAL_ADC_CHANNEL(6), - MANUAL_ADC_CHANNEL(7), - MANUAL_ADC_CHANNEL(8), - MANUAL_ADC_CHANNEL(9), - MANUAL_ADC_CHANNEL(10), - MANUAL_ADC_CHANNEL(11), - MANUAL_ADC_CHANNEL(12), - MANUAL_ADC_CHANNEL(13), - MANUAL_ADC_CHANNEL(14), - MANUAL_ADC_CHANNEL(15), - // We need two additional commands here because of how the ADC works. - // In every given command frame, a new analog channel is selected in the ADC frontend multiplexer. - // In frame n+1, that value actually gets converted to a digital value. - // In frame n+2, the converted digital value is finally output, and we are able to receive it. - // These requests are _pipelined_, so there is a latency of 2 frames, but we get a new sample on each frame. - // - // For more information, see figures 1 and 51 in the ADS7957 datasheet. - MANUAL_ADC_CHANNEL(15), - MANUAL_ADC_CHANNEL(15), -}; -static volatile uint16_t channel_data[PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES]; - -static int adc_soon; -// Used to block ADC from interfering with flash upon shutdown -static int shut_down_hack = 0; -static int shut_down_hack_done = 0; - -static pbdrv_adc_callback_t pbdrv_adc_callbacks[1]; -static uint32_t pbdrv_adc_callback_count = 0; - -void pbdrv_adc_set_callback(pbdrv_adc_callback_t callback) { - if (pbdrv_adc_callback_count < PBIO_ARRAY_SIZE(pbdrv_adc_callbacks)) { - pbdrv_adc_callbacks[pbdrv_adc_callback_count++] = callback; - } -} - -pbio_error_t pbdrv_adc_get_ch(uint8_t ch, uint16_t *value) { - if (ch >= PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS) { - return PBIO_ERROR_INVALID_ARG; - } - // XXX We probably need to figure out how atomicity works between the DMA and the CPU. - // For now, read the value twice and assume it's good (not torn) if the values are the same. - uint16_t a, b; - do { - // Values for the requested channel are received several samples later. - a = channel_data[ch + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES]; - b = channel_data[ch + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES]; - } while (a != b); - - // Mask the data to 10 bits - *value = (a >> 2) & 0x3ff; - return PBIO_SUCCESS; -} - -static pbio_os_process_t pbdrv_adc_ev3_process; - -pbio_error_t pbdrv_adc_ev3_process_thread(pbio_os_state_t *state, void *context) { - static pbio_os_timer_t timer; - - PBIO_OS_ASYNC_BEGIN(state); - - // HACK: This waits until storage is completely done with SPI flash before we start - PBIO_OS_AWAIT_UNTIL(state, pbsys_storage_settings_get_settings()); - - // Once SPI flash init is finished, there is nothing further for us to do. - // We are ready to start sampling. - - pbio_os_timer_set(&timer, ADC_SAMPLE_PERIOD); - - for (;;) { - PBIO_OS_AWAIT_UNTIL(state, shut_down_hack || adc_soon || pbio_os_timer_is_expired(&timer)); - - if (shut_down_hack) { - shut_down_hack_done = 1; - break; - } - - if (adc_soon) { - adc_soon = 0; - pbio_os_timer_set(&timer, ADC_SAMPLE_PERIOD); - } else { - // TODO: There should probably be a pbio OS function for this - timer.start += timer.duration; - } - - // Do a sample of all channels - pbdrv_block_device_ev3_spi_begin_for_adc( - channel_cmd, - channel_data, - PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES); - PBIO_OS_AWAIT_WHILE(state, pbdrv_block_device_ev3_is_busy()); - - for (uint32_t i = 0; i < pbdrv_adc_callback_count; i++) { - pbdrv_adc_callbacks[i](); - } - } - - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} +// NB: This driver is implemented by pbdrv/block_device because it is on the +// same SPI bus. void pbdrv_adc_init(void) { - // Immediately go into async mode so that we can wait for the SPI flash driver. - // We *don't* want to block the initial init phase, or else things will deadlock. - - pbio_os_process_start(&pbdrv_adc_ev3_process, pbdrv_adc_ev3_process_thread, NULL); -} - -void pbdrv_adc_update_soon(void) { - adc_soon = 1; - pbio_os_request_poll(); -} - -void pbdrv_adc_ev3_configure_data_format() { - SPIClkConfigure(SOC_SPI_0_REGS, SOC_SYSCLK_2_FREQ, SPI_CLK_SPEED_ADC, SPI_DATA_FORMAT1); - // NOTE: Cannot be CPOL=1 CPHA=1 like SPI flash - // The ADC seems to use the last falling edge to trigger conversions (see Figure 1 in the datasheet). - SPIConfigClkFormat(SOC_SPI_0_REGS, SPI_CLK_POL_LOW | SPI_CLK_OUTOFPHASE, SPI_DATA_FORMAT1); - SPIShiftMsbFirst(SOC_SPI_0_REGS, SPI_DATA_FORMAT1); - SPICharLengthSet(SOC_SPI_0_REGS, 16, SPI_DATA_FORMAT1); - // In order to compensate for analog impedance issues and capacitor charging time, - // we set all SPI delays to the maximum for the ADC. This helps get more accurate readings. - // This includes both this delay (the delay where CS is held inactive), - // as well as the CS-assert-to-clock-start and clock-end-to-CS-deassert delays - // (which are global and set in block_device_ev3.c). - SPIWdelaySet(SOC_SPI_0_REGS, 0x3f << SPI_SPIFMT_WDELAY_SHIFT, SPI_DATA_FORMAT1); -} - -void pbdrv_adc_ev3_shut_down_hack() { - shut_down_hack = 1; - pbio_os_request_poll(); -} -int pbdrv_adc_ev3_is_shut_down_hack() { - return shut_down_hack_done; } #endif // PBDRV_CONFIG_ADC_EV3 diff --git a/lib/pbio/drv/adc/adc_ev3.h b/lib/pbio/drv/adc/adc_ev3.h deleted file mode 100644 index 5e1550fd4..000000000 --- a/lib/pbio/drv/adc/adc_ev3.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2025 The Pybricks Authors - -#ifndef _INTERNAL_PBDRV_ADC_EV3_H_ -#define _INTERNAL_PBDRV_ADC_EV3_H_ - -void pbdrv_adc_ev3_configure_data_format(); - -void pbdrv_adc_ev3_shut_down_hack(); -int pbdrv_adc_ev3_is_shut_down_hack(); - -#endif // _INTERNAL_PBDRV_ADC_EV3_H_ diff --git a/lib/pbio/drv/adc/adc_stm32_hal.c b/lib/pbio/drv/adc/adc_stm32_hal.c index 7d6d0eaef..c1263ae76 100644 --- a/lib/pbio/drv/adc/adc_stm32_hal.c +++ b/lib/pbio/drv/adc/adc_stm32_hal.c @@ -47,10 +47,8 @@ static uint32_t pbdrv_adc_last_error; PROCESS(pbdrv_adc_process, "ADC"); -void pbdrv_adc_set_callback(pbdrv_adc_callback_t callback) { -} - -void pbdrv_adc_update_soon(void) { +pbio_error_t pbdrv_adc_await_new_samples(pbio_os_state_t *state, pbio_os_timer_t *timer, uint32_t future) { + return PBIO_ERROR_NOT_SUPPORTED; } pbio_error_t pbdrv_adc_get_ch(uint8_t ch, uint16_t *value) { diff --git a/lib/pbio/drv/adc/adc_stm32f0.c b/lib/pbio/drv/adc/adc_stm32f0.c index 8e57c7d3f..6f802415a 100644 --- a/lib/pbio/drv/adc/adc_stm32f0.c +++ b/lib/pbio/drv/adc/adc_stm32f0.c @@ -66,10 +66,8 @@ void pbdrv_adc_init(void) { process_start(&pbdrv_adc_process); } -void pbdrv_adc_set_callback(pbdrv_adc_callback_t callback) { -} - -void pbdrv_adc_update_soon(void) { +pbio_error_t pbdrv_adc_await_new_samples(pbio_os_state_t *state, pbio_os_timer_t *timer, uint32_t future) { + return PBIO_ERROR_NOT_SUPPORTED; } // does a single conversion for the specified channel diff --git a/lib/pbio/drv/block_device/block_device_ev3.c b/lib/pbio/drv/block_device/block_device_ev3.c index 03bf7e0c0..028fd8af7 100644 --- a/lib/pbio/drv/block_device/block_device_ev3.c +++ b/lib/pbio/drv/block_device/block_device_ev3.c @@ -1,7 +1,16 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2025 The Pybricks Authors -// Block device driver for N25Q128 SPI flash memory chip connected to TI AM1808. +// Block device driver for N25Q128 SPI flash memory chip and the ADS7957SRHB +// ADC converter, both connected to SPI0 on the TIAM1808. +// +// This driver has these main parts: +// +// Part 1: Common SPI setup and DMA configuration. +// Part 2: Awaitable SPI flash read and write operations. +// Part 3: ADC read operations using DMA. Implements pbdrv/adc for this platform. +// Part 4: A process that coordinates SPI for flash and ADC since they share +// the same SPI bus. Implements pbdrv/block_device for this platform. #include @@ -14,6 +23,7 @@ #include "../core.h" #include +#include #include #include @@ -27,10 +37,14 @@ #include "block_device_ev3.h" #include "../drv/gpio/gpio_ev3.h" -#include "../drv/adc/adc_ev3.h" #include #include +#include + +// +// Part 1: Common SPI setup and DMA configuration. +// /** * SPI bus state. @@ -49,7 +63,7 @@ typedef enum { } spi_status_t; /** - * Driver-specific sizes. + * SPI flash driver sizes. */ enum { // This is large enough for all flash commands we use @@ -58,13 +72,26 @@ enum { SPI_MAX_DATA_SZ = 0xffff, }; +/** + * SPI ADC constants. + */ +enum { + // The maximum ADC clock speed according to the datasheet is 20 MHz. + // However, because the SPI peripheral does not have a fractional clock generator, + // the closest achievable in-spec speed is a division factor of 8. + // + // 150 MHz / 8 = 18.75 MHz actual + ADC_SPI_CLK_SPEED = 20000000, + // Time between the end of one SPI operation and start of the next. + ADC_SAMPLE_PERIOD = 2, +}; + /** * The block device driver state. */ static struct { /** HAL Transfer status */ - volatile spi_status_t spi_status; - + volatile spi_status_t status; // This is used when SPI only needs to receive. It should always stay as 0. uint8_t tx_dummy_byte; // This is used when received data is to be discarded. Its value should be ignored. @@ -75,36 +102,41 @@ static struct { uint8_t spi_cmd_buf_tx[SPI_CMD_BUF_SZ]; // This is used to hold the replies to commands to the SPI peripheral. uint8_t spi_cmd_buf_rx[SPI_CMD_BUF_SZ]; -} bdev; +} spi_dev; +static uint32_t last_spi_dma_complete_time; + +static void spi_dma_complete(void) { + // Only complete once RX and TX complete. + if (spi_dev.status & SPI_STATUS_WAIT_ANY) { + return; + } + SPIIntDisable(SOC_SPI_0_REGS, SPI_DMA_REQUEST_ENA_INT); + pbio_os_request_poll(); + last_spi_dma_complete_time = pbdrv_clock_get_ms(); +} /** * Tx transfer complete. */ void pbdrv_block_device_ev3_spi_tx_complete(void) { - bdev.spi_status &= ~SPI_STATUS_WAIT_TX; - if (!(bdev.spi_status & SPI_STATUS_WAIT_ANY)) { - SPIIntDisable(SOC_SPI_0_REGS, SPI_DMA_REQUEST_ENA_INT); - pbio_os_request_poll(); - } + spi_dev.status &= ~SPI_STATUS_WAIT_TX; + spi_dma_complete(); } /** * Rx transfer complete. */ void pbdrv_block_device_ev3_spi_rx_complete(void) { - bdev.spi_status &= ~SPI_STATUS_WAIT_RX; - if (!(bdev.spi_status & SPI_STATUS_WAIT_ANY)) { - SPIIntDisable(SOC_SPI_0_REGS, SPI_DMA_REQUEST_ENA_INT); - pbio_os_request_poll(); - } + spi_dev.status &= ~SPI_STATUS_WAIT_RX; + spi_dma_complete(); } /** * Transfer error. // TODO: hook this up */ void pbdrv_block_device_ev3_spi_error(void) { - bdev.spi_status = SPI_STATUS_ERROR; + spi_dev.status = SPI_STATUS_ERROR; pbio_os_request_poll(); } @@ -120,8 +152,8 @@ static void spi0_isr(void) { continue; } - bdev.spi_cmd_buf_rx[0] = HWREG(SOC_SPI_0_REGS + SPI_SPIBUF); - bdev.spi_status &= ~SPI_STATUS_WAIT_RX; + spi_dev.spi_cmd_buf_rx[0] = HWREG(SOC_SPI_0_REGS + SPI_SPIBUF); + spi_dev.status &= ~SPI_STATUS_WAIT_RX; SPIIntDisable(SOC_SPI_0_REGS, SPI_RECV_INT); pbio_os_request_poll(); } @@ -154,6 +186,72 @@ enum { SPI_CLK_SPEED_FLASH = 50000000, }; +static void spi_bus_init(void) { + // SPI module basic init + PSCModuleControl(SOC_PSC_0_REGS, HW_PSC_SPI0, PSC_POWERDOMAIN_ALWAYS_ON, PSC_MDCTL_NEXT_ENABLE); + SPIReset(SOC_SPI_0_REGS); + SPIOutOfReset(SOC_SPI_0_REGS); + SPIModeConfigure(SOC_SPI_0_REGS, SPI_MASTER_MODE); + unsigned int spipc0 = SPI_SPIPC0_SOMIFUN | SPI_SPIPC0_SIMOFUN | SPI_SPIPC0_CLKFUN | SPI_SPIPC0_SCS0FUN0 | SPI_SPIPC0_SCS0FUN3; + SPIPinControl(SOC_SPI_0_REGS, 0, 0, &spipc0); + SPIDefaultCSSet(SOC_SPI_0_REGS, (1 << PBDRV_EV3_SPI0_FLASH_CS) | (1 << PBDRV_EV3_SPI0_ADC_CS)); + + // SPI module data formats + SPIClkConfigure(SOC_SPI_0_REGS, SOC_SPI_0_MODULE_FREQ, SPI_CLK_SPEED_FLASH, SPI_DATA_FORMAT0); + // For reasons which have not yet been fully investigated, attempting to switch between + // SPI_CLK_POL_HIGH and SPI_CLK_POL_LOW seems to not work correctly (possibly causing a glitch?). + // The suspected cause is that partial writes to SPI_SPIDAT1 do not update *anything*, + // not even the clock idle state, until *after* the data field is written to. + // Since multiple options work for SPI flash but the ADC requires one particular setting, + // set this CPOL/CPHA to match what the ADC needs. + SPIConfigClkFormat(SOC_SPI_0_REGS, SPI_CLK_POL_LOW | SPI_CLK_OUTOFPHASE, SPI_DATA_FORMAT0); + SPIShiftMsbFirst(SOC_SPI_0_REGS, SPI_DATA_FORMAT0); + SPICharLengthSet(SOC_SPI_0_REGS, 8, SPI_DATA_FORMAT0); + + // ADC configuration + SPIClkConfigure(SOC_SPI_0_REGS, SOC_SYSCLK_2_FREQ, ADC_SPI_CLK_SPEED, SPI_DATA_FORMAT1); + // NOTE: Cannot be CPOL=1 CPHA=1 like SPI flash + // The ADC seems to use the last falling edge to trigger conversions (see Figure 1 in the datasheet). + SPIConfigClkFormat(SOC_SPI_0_REGS, SPI_CLK_POL_LOW | SPI_CLK_OUTOFPHASE, SPI_DATA_FORMAT1); + SPIShiftMsbFirst(SOC_SPI_0_REGS, SPI_DATA_FORMAT1); + SPICharLengthSet(SOC_SPI_0_REGS, 16, SPI_DATA_FORMAT1); + // In order to compensate for analog impedance issues and capacitor charging time, + // we set all SPI delays to the maximum for the ADC. This helps get more accurate readings. + // This includes both this delay (the delay where CS is held inactive), + // as well as the CS-assert-to-clock-start and clock-end-to-CS-deassert delays + // (which are global and set in block_device_ev3.c). + SPIWdelaySet(SOC_SPI_0_REGS, 0x3f << SPI_SPIFMT_WDELAY_SHIFT, SPI_DATA_FORMAT1); + + // Configure the GPIO pins. + pbdrv_gpio_alt(&pin_spi0_mosi, SYSCFG_PINMUX3_PINMUX3_15_12_SPI0_SIMO0); + pbdrv_gpio_alt(&pin_spi0_miso, SYSCFG_PINMUX3_PINMUX3_11_8_SPI0_SOMI0); + pbdrv_gpio_alt(&pin_spi0_clk, SYSCFG_PINMUX3_PINMUX3_3_0_SPI0_CLK); + pbdrv_gpio_alt(&pin_spi0_ncs0, SYSCFG_PINMUX4_PINMUX4_7_4_NSPI0_SCS0); + pbdrv_gpio_alt(&pin_spi0_ncs3, SYSCFG_PINMUX3_PINMUX3_27_24_NSPI0_SCS3); + + // Configure the flash control pins and put them with the values we want + pbdrv_gpio_alt(&pin_flash_nwp, SYSCFG_PINMUX12_PINMUX12_23_20_GPIO5_2); + pbdrv_gpio_out_high(&pin_flash_nwp); + pbdrv_gpio_alt(&pin_flash_nhold, SYSCFG_PINMUX6_PINMUX6_31_28_GPIO2_0); + pbdrv_gpio_out_high(&pin_flash_nhold); + + // Set up interrupts + SPIIntLevelSet(SOC_SPI_0_REGS, SPI_RECV_INTLVL | SPI_TRANSMIT_INTLVL); + IntRegister(SYS_INT_SPINT0, spi0_isr); + IntChannelSet(SYS_INT_SPINT0, 2); + IntSystemEnable(SYS_INT_SPINT0); + + // Request DMA channels. This only needs to be done for the initial events (and not for linked parameter sets) + EDMA3RequestChannel(SOC_EDMA30CC_0_REGS, EDMA3_CHANNEL_TYPE_DMA, EDMA3_CHA_SPI0_TX, EDMA3_CHA_SPI0_TX, 0); + EDMA3RequestChannel(SOC_EDMA30CC_0_REGS, EDMA3_CHANNEL_TYPE_DMA, EDMA3_CHA_SPI1_RX, EDMA3_CHA_SPI1_RX, 0); + + // Enable! + SPIEnable(SOC_SPI_0_REGS); + + spi_dev.status = SPI_STATUS_COMPLETE; +} + + // -Hardware resource allocation notes- // // The SPI peripheral can be configured with multiple "data formats" which can be used for different peripherals. @@ -179,15 +277,18 @@ enum { // This declaration also forces alignment typedef union { EDMA3CCPaRAMEntry p; - uint32_t u[32 / 4]; + uint32_t u[sizeof(EDMA3CCPaRAMEntry) / 4]; } EDMA3CCPaRAMEntry_; static void edma3_set_param(unsigned int slot, EDMA3CCPaRAMEntry_ *p) { - for (int i = 0; i < 32 / 4; i++) { + for (uint32_t i = 0; i < PBIO_ARRAY_SIZE(p->u); i++) { HWREG(SOC_EDMA30CC_0_REGS + EDMA3CC_PaRAM_BASE + slot * 32 + i * 4) = p->u[i]; } } +// +// Part 2: Implementation for SPI flash. +// // Helper functions for setting up the high control bits of a data transfer static inline void spi0_setup_for_flash() { @@ -233,11 +334,11 @@ static pbio_error_t spi_begin_for_flash( // Maximum size exceeded return PBIO_ERROR_INVALID_ARG; } - if (bdev.spi_status & SPI_STATUS_WAIT_ANY) { + if (spi_dev.status & SPI_STATUS_WAIT_ANY) { // Another read operation is already in progress. return PBIO_ERROR_BUSY; } - if (bdev.spi_status == SPI_STATUS_ERROR) { + if (spi_dev.status == SPI_STATUS_ERROR) { // Previous transmission went wrong. return PBIO_ERROR_IO; } @@ -248,21 +349,21 @@ static pbio_error_t spi_begin_for_flash( // There is no point in using DMA here // (this is used for e.g. WREN) - bdev.spi_status = SPI_STATUS_WAIT_RX; + spi_dev.status = SPI_STATUS_WAIT_RX; uint32_t tx = spi0_last_dat1_for_flash(cmd[0]); SPIIntEnable(SOC_SPI_0_REGS, SPI_RECV_INT); HWREG(SOC_SPI_0_REGS + SPI_SPIDAT1) = tx; } else { - memcpy(&bdev.spi_cmd_buf_tx, cmd, cmd_len); + memcpy(&spi_dev.spi_cmd_buf_tx, cmd, cmd_len); if (user_data_len == 0) { // Only a command, no user data - bdev.tx_last_word = spi0_last_dat1_for_flash(cmd[cmd_len - 1]); + spi_dev.tx_last_word = spi0_last_dat1_for_flash(cmd[cmd_len - 1]); // TX everything except last byte - ps.p.srcAddr = (unsigned int)(&bdev.spi_cmd_buf_tx); + ps.p.srcAddr = (unsigned int)(&spi_dev.spi_cmd_buf_tx); ps.p.destAddr = SOC_SPI_0_REGS + SPI_SPIDAT1; ps.p.aCnt = 1; ps.p.bCnt = cmd_len - 1; @@ -277,7 +378,7 @@ static pbio_error_t spi_begin_for_flash( edma3_set_param(EDMA3_CHA_SPI0_TX, &ps); // TX last byte, clearing CSHOLD - ps.p.srcAddr = (unsigned int)(&bdev.tx_last_word); + ps.p.srcAddr = (unsigned int)(&spi_dev.tx_last_word); ps.p.aCnt = 4; ps.p.bCnt = 1; ps.p.linkAddr = 0xffff; @@ -286,7 +387,7 @@ static pbio_error_t spi_begin_for_flash( // RX all bytes ps.p.srcAddr = SOC_SPI_0_REGS + SPI_SPIBUF; - ps.p.destAddr = (unsigned int)(&bdev.spi_cmd_buf_rx); + ps.p.destAddr = (unsigned int)(&spi_dev.spi_cmd_buf_rx); ps.p.aCnt = 1; ps.p.bCnt = cmd_len; ps.p.cCnt = 1; @@ -302,7 +403,7 @@ static pbio_error_t spi_begin_for_flash( // Command *and* user data // TX the command - ps.p.srcAddr = (unsigned int)(&bdev.spi_cmd_buf_tx); + ps.p.srcAddr = (unsigned int)(&spi_dev.spi_cmd_buf_tx); ps.p.destAddr = SOC_SPI_0_REGS + SPI_SPIDAT1; ps.p.aCnt = 1; ps.p.bCnt = cmd_len; @@ -317,7 +418,7 @@ static pbio_error_t spi_begin_for_flash( edma3_set_param(EDMA3_CHA_SPI0_TX, &ps); if (user_data_tx) { - bdev.tx_last_word = spi0_last_dat1_for_flash(user_data_tx[user_data_len - 1]); + spi_dev.tx_last_word = spi0_last_dat1_for_flash(user_data_tx[user_data_len - 1]); // TX all but the last byte ps.p.srcAddr = (unsigned int)(user_data_tx); @@ -326,24 +427,24 @@ static pbio_error_t spi_begin_for_flash( edma3_set_param(126, &ps); // TX the last byte, clearing CSHOLD - ps.p.srcAddr = (unsigned int)(&bdev.tx_last_word); + ps.p.srcAddr = (unsigned int)(&spi_dev.tx_last_word); ps.p.aCnt = 4; ps.p.bCnt = 1; ps.p.linkAddr = 0xffff; ps.p.opt = EDMA3CC_OPT_TCINTEN | (EDMA3_CHA_SPI0_TX << EDMA3CC_OPT_TCC_SHIFT); edma3_set_param(127, &ps); } else { - bdev.tx_last_word = spi0_last_dat1_for_flash(0); + spi_dev.tx_last_word = spi0_last_dat1_for_flash(0); // TX all but the last byte - ps.p.srcAddr = (unsigned int)(&bdev.tx_dummy_byte); + ps.p.srcAddr = (unsigned int)(&spi_dev.tx_dummy_byte); ps.p.bCnt = user_data_len - 1; ps.p.srcBIdx = 0; ps.p.linkAddr = 127 * 32; edma3_set_param(126, &ps); // TX the last byte, clearing CSHOLD - ps.p.srcAddr = (unsigned int)(&bdev.tx_last_word); + ps.p.srcAddr = (unsigned int)(&spi_dev.tx_last_word); ps.p.aCnt = 4; ps.p.bCnt = 1; ps.p.linkAddr = 0xffff; @@ -353,7 +454,7 @@ static pbio_error_t spi_begin_for_flash( // RX the command ps.p.srcAddr = SOC_SPI_0_REGS + SPI_SPIBUF; - ps.p.destAddr = (unsigned int)(&bdev.spi_cmd_buf_rx); + ps.p.destAddr = (unsigned int)(&spi_dev.spi_cmd_buf_rx); ps.p.aCnt = 1; ps.p.bCnt = cmd_len; ps.p.cCnt = 1; @@ -375,7 +476,7 @@ static pbio_error_t spi_begin_for_flash( edma3_set_param(125, &ps); } else { // RX dummy - ps.p.destAddr = (unsigned int)(&bdev.rx_dummy_byte); + ps.p.destAddr = (unsigned int)(&spi_dev.rx_dummy_byte); ps.p.bCnt = user_data_len; ps.p.destBIdx = 0; ps.p.linkAddr = 0xffff; @@ -384,7 +485,7 @@ static pbio_error_t spi_begin_for_flash( } } - bdev.spi_status = SPI_STATUS_WAIT_TX | SPI_STATUS_WAIT_RX; + spi_dev.status = SPI_STATUS_WAIT_TX | SPI_STATUS_WAIT_RX; // TODO: pbio probably needs a framework for memory barriers and DMA cache management __asm__ volatile ("" ::: "memory"); @@ -397,74 +498,6 @@ static pbio_error_t spi_begin_for_flash( return PBIO_SUCCESS; } -/** - * Initiates an SPI transfer via DMA, specifically designed for the ADC. - * - * @param [in] cmds Data (both ADC chip commands and SPI peripheral commands) to be sent. - * Lifetime must remain valid until after the completion of the entire transfer. - * @param [in] data Buffer for ADC chip outputs. - * Lifetime must remain valid until after the completion of the entire transfer. - * @param [in] len Length of \p cmds and \p data - * @return ::PBIO_SUCCESS on success. - * ::PBIO_ERROR_BUSY if SPI is busy. - * ::PBIO_ERROR_INVALID_ARG if argument is too big - * ::PBIO_ERROR_IO for other errors. - */ -pbio_error_t pbdrv_block_device_ev3_spi_begin_for_adc(const uint32_t *cmds, volatile uint16_t *data, unsigned int len) { - EDMA3CCPaRAMEntry_ ps; - - if (len > SPI_MAX_DATA_SZ) { - // Maximum size exceeded - return PBIO_ERROR_INVALID_ARG; - } - if (bdev.spi_status & SPI_STATUS_WAIT_ANY) { - // Another read operation is already in progress. - return PBIO_ERROR_BUSY; - } - if (bdev.spi_status == SPI_STATUS_ERROR) { - // Previous transmission went wrong. - return PBIO_ERROR_IO; - } - - // Max out delays for ADC, see comment in adc_ev3.c - HWREG(SOC_SPI_0_REGS + SPI_SPIDELAY) = (0xff << SPI_SPIDELAY_C2TDELAY_SHIFT) | (0xff << SPI_SPIDELAY_T2CDELAY_SHIFT); - - ps.p.srcAddr = (unsigned int)(cmds); - ps.p.destAddr = SOC_SPI_0_REGS + SPI_SPIDAT1; - ps.p.aCnt = sizeof(uint32_t); - ps.p.bCnt = len; - ps.p.cCnt = 1; - ps.p.srcBIdx = sizeof(uint32_t); - ps.p.destBIdx = 0; - ps.p.srcCIdx = 0; - ps.p.destCIdx = 0; - ps.p.linkAddr = 0xffff; - ps.p.bCntReload = 0; - ps.p.opt = EDMA3CC_OPT_TCINTEN | (EDMA3_CHA_SPI0_TX << EDMA3CC_OPT_TCC_SHIFT); - edma3_set_param(EDMA3_CHA_SPI0_TX, &ps); - - ps.p.srcAddr = SOC_SPI_0_REGS + SPI_SPIBUF; - ps.p.destAddr = (unsigned int)(data); - ps.p.aCnt = sizeof(uint16_t); - ps.p.bCnt = len; - ps.p.srcBIdx = 0; - ps.p.destBIdx = sizeof(uint16_t); - ps.p.opt = EDMA3CC_OPT_TCINTEN | (EDMA3_CHA_SPI0_RX << EDMA3CC_OPT_TCC_SHIFT); - edma3_set_param(EDMA3_CHA_SPI0_RX, &ps); - - bdev.spi_status = SPI_STATUS_WAIT_TX | SPI_STATUS_WAIT_RX; - - // TODO: pbio probably needs a framework for memory barriers and DMA cache management - __asm__ volatile ("" ::: "memory"); - - EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_TX, EDMA3_TRIG_MODE_EVENT); - EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_RX, EDMA3_TRIG_MODE_EVENT); - SPIIntEnable(SOC_SPI_0_REGS, SPI_DMA_REQUEST_ENA_INT); - - return PBIO_SUCCESS; -} - - static void set_address_be(uint8_t *buf, uint32_t address) { buf[0] = address >> 16; buf[1] = address >> 8; @@ -522,7 +555,7 @@ static uint8_t write_address[4] = {FLASH_CMD_WRITE_DATA}; // Request sector erase at address. Buffer: erase command + address. static uint8_t erase_address[4] = {FLASH_CMD_ERASE_BLOCK}; -pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, uint8_t *buffer, uint32_t size) { +static pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, uint8_t *buffer, uint32_t size) { static uint32_t size_done; static uint32_t size_now; @@ -545,7 +578,7 @@ pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, ui if (err != PBIO_SUCCESS) { return err; } - PBIO_OS_AWAIT_WHILE(state, bdev.spi_status & SPI_STATUS_WAIT_ANY); + PBIO_OS_AWAIT_WHILE(state, spi_dev.status & SPI_STATUS_WAIT_ANY); } PBIO_OS_ASYNC_END(PBIO_SUCCESS); @@ -565,15 +598,15 @@ static pbio_error_t flash_wait_write(pbio_os_state_t *state) { if (err != PBIO_SUCCESS) { return err; } - PBIO_OS_AWAIT_WHILE(state, bdev.spi_status & SPI_STATUS_WAIT_ANY); + PBIO_OS_AWAIT_WHILE(state, spi_dev.status & SPI_STATUS_WAIT_ANY); - status = bdev.spi_cmd_buf_rx[1]; + status = spi_dev.spi_cmd_buf_rx[1]; } while (status & FLASH_STATUS_BUSY); PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, uint32_t size) { +static pbio_error_t pbdrv_block_device_write_disk(pbio_os_state_t *state, const uint8_t *buffer, uint32_t size) { static pbio_os_state_t sub; static uint32_t offset; @@ -588,13 +621,6 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u return PBIO_ERROR_INVALID_ARG; } - #if PBDRV_CONFIG_ADC_EV3 - // HACK - // We only store on shutdown. Block ADC. - pbdrv_adc_ev3_shut_down_hack(); - PBIO_OS_AWAIT_UNTIL(state, pbdrv_adc_ev3_is_shut_down_hack()); - #endif - // Erase sector by sector. for (offset = 0; offset < size; offset += FLASH_SIZE_ERASE) { // Enable writing @@ -602,7 +628,7 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u if (err != PBIO_SUCCESS) { return err; } - PBIO_OS_AWAIT_WHILE(state, bdev.spi_status & SPI_STATUS_WAIT_ANY); + PBIO_OS_AWAIT_WHILE(state, spi_dev.status & SPI_STATUS_WAIT_ANY); // Erase this block set_address_be(&erase_address[1], PBDRV_CONFIG_BLOCK_DEVICE_EV3_START_ADDRESS + offset); @@ -610,7 +636,7 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u if (err != PBIO_SUCCESS) { return err; } - PBIO_OS_AWAIT_WHILE(state, bdev.spi_status & SPI_STATUS_WAIT_ANY); + PBIO_OS_AWAIT_WHILE(state, spi_dev.status & SPI_STATUS_WAIT_ANY); // Wait for completion PBIO_OS_AWAIT(state, &sub, err = flash_wait_write(&sub)); @@ -628,7 +654,7 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u if (err != PBIO_SUCCESS) { return err; } - PBIO_OS_AWAIT_WHILE(state, bdev.spi_status & SPI_STATUS_WAIT_ANY); + PBIO_OS_AWAIT_WHILE(state, spi_dev.status & SPI_STATUS_WAIT_ANY); // Write this block set_address_be(&write_address[1], PBDRV_CONFIG_BLOCK_DEVICE_EV3_START_ADDRESS + size_done); @@ -636,7 +662,7 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u if (err != PBIO_SUCCESS) { return err; } - PBIO_OS_AWAIT_WHILE(state, bdev.spi_status & SPI_STATUS_WAIT_ANY); + PBIO_OS_AWAIT_WHILE(state, spi_dev.status & SPI_STATUS_WAIT_ANY); // Wait for completion PBIO_OS_AWAIT(state, &sub, err = flash_wait_write(&sub)); @@ -648,90 +674,261 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -static pbio_os_process_t pbdrv_block_device_ev3_init_process; +// +// Part 3: Implementation for pbdrv/adc. +// + +// Construct both SPI peripheral settings (data format, chip select) +// and ADC chip settings (manual mode, 2xVref) in one go, +// so that DMA can be used efficiently. +// +// NOTE: CSHOLD is *not* set here, so that CS is deasserted between each 16-bit unit +#define MANUAL_ADC_CHANNEL(x) \ + (1 << 26) | \ + (SPI_SPIDAT1_DFSEL_FORMAT1 << SPI_SPIDAT1_DFSEL_SHIFT) | \ + (0 << (SPI_SPIDAT1_CSNR_SHIFT + PBDRV_EV3_SPI0_ADC_CS)) | \ + (1 << (SPI_SPIDAT1_CSNR_SHIFT + PBDRV_EV3_SPI0_FLASH_CS)) | \ + (1 << 12) | \ + (1 << 11) | \ + (((x) & 0xf) << 7) | \ + (1 << 6) + +#define PBDRV_ADC_EV3_NUM_DELAY_SAMPLES (2) + +static const uint32_t channel_cmd[PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES] = { + MANUAL_ADC_CHANNEL(0), + MANUAL_ADC_CHANNEL(1), + MANUAL_ADC_CHANNEL(2), + MANUAL_ADC_CHANNEL(3), + MANUAL_ADC_CHANNEL(4), + MANUAL_ADC_CHANNEL(5), + MANUAL_ADC_CHANNEL(6), + MANUAL_ADC_CHANNEL(7), + MANUAL_ADC_CHANNEL(8), + MANUAL_ADC_CHANNEL(9), + MANUAL_ADC_CHANNEL(10), + MANUAL_ADC_CHANNEL(11), + MANUAL_ADC_CHANNEL(12), + MANUAL_ADC_CHANNEL(13), + MANUAL_ADC_CHANNEL(14), + MANUAL_ADC_CHANNEL(15), + // We need two additional commands here because of how the ADC works. + // In every given command frame, a new analog channel is selected in the ADC frontend multiplexer. + // In frame n+1, that value actually gets converted to a digital value. + // In frame n+2, the converted digital value is finally output, and we are able to receive it. + // These requests are _pipelined_, so there is a latency of 2 frames, but we get a new sample on each frame. + // + // For more information, see figures 1 and 51 in the ADS7957 datasheet. + MANUAL_ADC_CHANNEL(15), + MANUAL_ADC_CHANNEL(15), +}; +static volatile uint16_t channel_data[PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES]; + +pbio_error_t pbdrv_adc_get_ch(uint8_t ch, uint16_t *value) { + if (ch >= PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS) { + return PBIO_ERROR_INVALID_ARG; + } + // XXX We probably need to figure out how atomicity works between the DMA and the CPU. + // For now, read the value twice and assume it's good (not torn) if the values are the same. + uint16_t a, b; + do { + // Values for the requested channel are received several samples later. + a = channel_data[ch + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES]; + b = channel_data[ch + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES]; + } while (a != b); + + // Mask the data to 10 bits + *value = (a >> 2) & 0x3ff; + return PBIO_SUCCESS; +} + +pbio_error_t pbdrv_adc_await_new_samples(pbio_os_state_t *state, pbio_os_timer_t *timer, uint32_t future) { + PBIO_OS_ASYNC_BEGIN(state); + pbio_os_timer_set(timer, 0); + PBIO_OS_AWAIT_UNTIL(state, pbio_util_time_has_passed(last_spi_dma_complete_time, timer->start + future)); + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + +/** + * Initiates an SPI transfer via DMA, specifically designed for the ADC. + * + * @param [in] cmds Data (both ADC chip commands and SPI peripheral commands) to be sent. + * Lifetime must remain valid until after the completion of the entire transfer. + * @param [in] data Buffer for ADC chip outputs. + * Lifetime must remain valid until after the completion of the entire transfer. + * @param [in] len Length of \p cmds and \p data + * @return ::PBIO_SUCCESS on success. + * ::PBIO_ERROR_BUSY if SPI is busy. + * ::PBIO_ERROR_INVALID_ARG if argument is too big + * ::PBIO_ERROR_IO for other errors. + */ +static pbio_error_t pbdrv_block_device_ev3_spi_begin_for_adc(const uint32_t *cmds, volatile uint16_t *data, unsigned int len) { + EDMA3CCPaRAMEntry_ ps; + + if (len > SPI_MAX_DATA_SZ) { + // Maximum size exceeded + return PBIO_ERROR_INVALID_ARG; + } + if (spi_dev.status & SPI_STATUS_WAIT_ANY) { + // Another read operation is already in progress. + return PBIO_ERROR_BUSY; + } + if (spi_dev.status == SPI_STATUS_ERROR) { + // Previous transmission went wrong. + return PBIO_ERROR_IO; + } + + // Max out delays for ADC, see comment in adc_ev3.c + HWREG(SOC_SPI_0_REGS + SPI_SPIDELAY) = (0xff << SPI_SPIDELAY_C2TDELAY_SHIFT) | (0xff << SPI_SPIDELAY_T2CDELAY_SHIFT); + + ps.p.srcAddr = (unsigned int)(cmds); + ps.p.destAddr = SOC_SPI_0_REGS + SPI_SPIDAT1; + ps.p.aCnt = sizeof(uint32_t); + ps.p.bCnt = len; + ps.p.cCnt = 1; + ps.p.srcBIdx = sizeof(uint32_t); + ps.p.destBIdx = 0; + ps.p.srcCIdx = 0; + ps.p.destCIdx = 0; + ps.p.linkAddr = 0xffff; + ps.p.bCntReload = 0; + ps.p.opt = EDMA3CC_OPT_TCINTEN | (EDMA3_CHA_SPI0_TX << EDMA3CC_OPT_TCC_SHIFT); + edma3_set_param(EDMA3_CHA_SPI0_TX, &ps); + + ps.p.srcAddr = SOC_SPI_0_REGS + SPI_SPIBUF; + ps.p.destAddr = (unsigned int)(data); + ps.p.aCnt = sizeof(uint16_t); + ps.p.bCnt = len; + ps.p.srcBIdx = 0; + ps.p.destBIdx = sizeof(uint16_t); + ps.p.opt = EDMA3CC_OPT_TCINTEN | (EDMA3_CHA_SPI0_RX << EDMA3CC_OPT_TCC_SHIFT); + edma3_set_param(EDMA3_CHA_SPI0_RX, &ps); + + spi_dev.status = SPI_STATUS_WAIT_TX | SPI_STATUS_WAIT_RX; + + // TODO: pbio probably needs a framework for memory barriers and DMA cache management + __asm__ volatile ("" ::: "memory"); + + EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_TX, EDMA3_TRIG_MODE_EVENT); + EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_RX, EDMA3_TRIG_MODE_EVENT); + SPIIntEnable(SOC_SPI_0_REGS, SPI_DMA_REQUEST_ENA_INT); + + return PBIO_SUCCESS; +} + +// +// Part 4: Block device and ADC coordination process. +// + +static struct { + /** + * How much data to write on shutdown and load on the next boot. Includes + * the size of this field, because it is also saved. + */ + uint32_t saved_size; + /** + * A copy of the data loaded from flash and application heap. The first + * portion of this, up to pbdrv_block_device_get_writable_size() bytes, + * gets saved to flash at shutdown. + */ + uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE]; +} ramdisk __attribute__((section(".noinit"), used)); + +uint32_t pbdrv_block_device_get_writable_size(void) { + return PBDRV_CONFIG_BLOCK_DEVICE_EV3_SIZE - sizeof(ramdisk.saved_size); +} + +static pbio_error_t pbdrv_block_device_load_err = PBIO_ERROR_FAILED; + +pbio_error_t pbdrv_block_device_get_data(uint8_t **data) { + *data = ramdisk.data; + + // Higher level code can use the ramdisk data if initialization completed + // successfully. Otherwise it should reset to factory default data. + return pbdrv_block_device_load_err; +} + +static pbio_os_process_t ev3_spi_process; + +pbio_error_t ev3_spi_process_thread(pbio_os_state_t *state, void *context) { -pbio_error_t pbdrv_block_device_ev3_init_process_thread(pbio_os_state_t *state, void *context) { pbio_error_t err; + static pbio_os_timer_t timer; + static pbio_os_state_t sub; + PBIO_OS_ASYNC_BEGIN(state); - // Write the ID getter command + // Write the SPI flash device ID getter command and verify result. err = spi_begin_for_flash(cmd_rdid, sizeof(cmd_rdid), 0, 0, 0); if (err != PBIO_SUCCESS) { return err; } - PBIO_OS_AWAIT_WHILE(state, bdev.spi_status & SPI_STATUS_WAIT_ANY); - - // Verify flash device ID - if (memcmp(device_id, &bdev.spi_cmd_buf_rx[1], sizeof(device_id))) { + PBIO_OS_AWAIT_WHILE(state, spi_dev.status & SPI_STATUS_WAIT_ANY); + if (memcmp(device_id, &spi_dev.spi_cmd_buf_rx[1], sizeof(device_id))) { return PBIO_ERROR_FAILED; } - // Initialization done. + // Read size of stored data and available data into RAM. + PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_read(&sub, 0, (uint8_t *)&ramdisk.saved_size, sizeof(ramdisk.saved_size))); + if (err != PBIO_SUCCESS) { + return err; + } + PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_read(&sub, 0, (uint8_t *)&ramdisk, ramdisk.saved_size)); + + // Reading may fail with PBIO_ERROR_INVALID_ARG if the size is too big. + // This happens when the size value was uninitialized or another firmware + // was used before. We still want to proceed with the boot process. The + // higher level code sees this error when requesting the RAM disk. On + // failure, it can reset the user data to factory defaults, and save it + // properly on shutdown. + pbdrv_block_device_load_err = err; pbdrv_init_busy_down(); - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} + // Poll ADC continuously until cancellation is requested. + while (!(ev3_spi_process.request & PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL)) { -void pbdrv_block_device_init(void) { - // SPI module basic init - PSCModuleControl(SOC_PSC_0_REGS, HW_PSC_SPI0, PSC_POWERDOMAIN_ALWAYS_ON, PSC_MDCTL_NEXT_ENABLE); - SPIReset(SOC_SPI_0_REGS); - SPIOutOfReset(SOC_SPI_0_REGS); - SPIModeConfigure(SOC_SPI_0_REGS, SPI_MASTER_MODE); - unsigned int spipc0 = SPI_SPIPC0_SOMIFUN | SPI_SPIPC0_SIMOFUN | SPI_SPIPC0_CLKFUN | SPI_SPIPC0_SCS0FUN0 | SPI_SPIPC0_SCS0FUN3; - SPIPinControl(SOC_SPI_0_REGS, 0, 0, &spipc0); - SPIDefaultCSSet(SOC_SPI_0_REGS, (1 << PBDRV_EV3_SPI0_FLASH_CS) | (1 << PBDRV_EV3_SPI0_ADC_CS)); + // Start a sample of all channels + pbdrv_block_device_ev3_spi_begin_for_adc( + channel_cmd, + channel_data, + PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS + PBDRV_ADC_EV3_NUM_DELAY_SAMPLES); - // SPI module data formats - SPIClkConfigure(SOC_SPI_0_REGS, SOC_SPI_0_MODULE_FREQ, SPI_CLK_SPEED_FLASH, SPI_DATA_FORMAT0); - // For reasons which have not yet been fully investigated, attempting to switch between - // SPI_CLK_POL_HIGH and SPI_CLK_POL_LOW seems to not work correctly (possibly causing a glitch?). - // The suspected cause is that partial writes to SPI_SPIDAT1 do not update *anything*, - // not even the clock idle state, until *after* the data field is written to. - // Since multiple options work for SPI flash but the ADC requires one particular setting, - // set this CPOL/CPHA to match what the ADC needs. - SPIConfigClkFormat(SOC_SPI_0_REGS, SPI_CLK_POL_LOW | SPI_CLK_OUTOFPHASE, SPI_DATA_FORMAT0); - SPIShiftMsbFirst(SOC_SPI_0_REGS, SPI_DATA_FORMAT0); - SPICharLengthSet(SOC_SPI_0_REGS, 8, SPI_DATA_FORMAT0); - #if PBDRV_CONFIG_ADC_EV3 - pbdrv_adc_ev3_configure_data_format(); - #endif + // Await for transfer to complete. + PBIO_OS_AWAIT_WHILE(state, (spi_dev.status & SPI_STATUS_WAIT_ANY)); - // Configure the GPIO pins. - pbdrv_gpio_alt(&pin_spi0_mosi, SYSCFG_PINMUX3_PINMUX3_15_12_SPI0_SIMO0); - pbdrv_gpio_alt(&pin_spi0_miso, SYSCFG_PINMUX3_PINMUX3_11_8_SPI0_SOMI0); - pbdrv_gpio_alt(&pin_spi0_clk, SYSCFG_PINMUX3_PINMUX3_3_0_SPI0_CLK); - pbdrv_gpio_alt(&pin_spi0_ncs0, SYSCFG_PINMUX4_PINMUX4_7_4_NSPI0_SCS0); - pbdrv_gpio_alt(&pin_spi0_ncs3, SYSCFG_PINMUX3_PINMUX3_27_24_NSPI0_SCS3); + pbio_os_timer_set(&timer, ADC_SAMPLE_PERIOD); + PBIO_OS_AWAIT_UNTIL(state, ev3_spi_process.request || pbio_os_timer_is_expired(&timer)); + } - // Configure the flash control pins and put them with the values we want - pbdrv_gpio_alt(&pin_flash_nwp, SYSCFG_PINMUX12_PINMUX12_23_20_GPIO5_2); - pbdrv_gpio_out_high(&pin_flash_nwp); - pbdrv_gpio_alt(&pin_flash_nhold, SYSCFG_PINMUX6_PINMUX6_31_28_GPIO2_0); - pbdrv_gpio_out_high(&pin_flash_nhold); + // Now that the ADC loop has ended, we can use the SPI bus to save user + // data to persistent storage. + PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_write_disk(&sub, (uint8_t *)&ramdisk, ramdisk.saved_size)); - // Set up interrupts - SPIIntLevelSet(SOC_SPI_0_REGS, SPI_RECV_INTLVL | SPI_TRANSMIT_INTLVL); - IntRegister(SYS_INT_SPINT0, spi0_isr); - IntChannelSet(SYS_INT_SPINT0, 2); - IntSystemEnable(SYS_INT_SPINT0); + // Poll the process that awaits on us to complete. + pbio_os_request_poll(); - // Request DMA channels. This only needs to be done for the initial events (and not for linked parameter sets) - EDMA3RequestChannel(SOC_EDMA30CC_0_REGS, EDMA3_CHANNEL_TYPE_DMA, EDMA3_CHA_SPI0_TX, EDMA3_CHA_SPI0_TX, 0); - EDMA3RequestChannel(SOC_EDMA30CC_0_REGS, EDMA3_CHANNEL_TYPE_DMA, EDMA3_CHA_SPI1_RX, EDMA3_CHA_SPI1_RX, 0); + PBIO_OS_ASYNC_END(err); +} - // Enable! - SPIEnable(SOC_SPI_0_REGS); +pbio_error_t pbdrv_block_device_write_all(pbio_os_state_t *state, uint32_t used_data_size) { + PBIO_OS_ASYNC_BEGIN(state); - bdev.spi_status = SPI_STATUS_COMPLETE; + // Store the new size so we know how much to load on next boot. + ramdisk.saved_size = used_data_size + sizeof(ramdisk.saved_size); - pbdrv_init_busy_up(); - pbio_os_process_start(&pbdrv_block_device_ev3_init_process, pbdrv_block_device_ev3_init_process_thread, NULL); + // Rather than write here, we ask the common SPI process to start writing + // when it is ready for it, and wait for the whole process to complete. + pbio_os_process_make_request(&ev3_spi_process, PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL); + PBIO_OS_AWAIT_UNTIL(state, ev3_spi_process.err != PBIO_ERROR_AGAIN); + + PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -int pbdrv_block_device_ev3_is_busy() { - return bdev.spi_status & SPI_STATUS_WAIT_ANY; +void pbdrv_block_device_init(void) { + spi_bus_init(); + pbdrv_init_busy_up(); + pbio_os_process_start(&ev3_spi_process, ev3_spi_process_thread, NULL); } #endif // PBDRV_CONFIG_BLOCK_DEVICE_EV3 diff --git a/lib/pbio/drv/block_device/block_device_ev3.h b/lib/pbio/drv/block_device/block_device_ev3.h index 56ba8d57d..4816f59cf 100644 --- a/lib/pbio/drv/block_device/block_device_ev3.h +++ b/lib/pbio/drv/block_device/block_device_ev3.h @@ -10,15 +10,7 @@ #define PBDRV_EV3_SPI0_FLASH_CS (0) #define PBDRV_EV3_SPI0_ADC_CS (3) -void pbdrv_block_device_ev3_spi_tx_complete(); -void pbdrv_block_device_ev3_spi_rx_complete(); - -// XXX The SPI flash and ADC both use the SPI0 peripheral. -// Since we do not have a fully-fledged "bus" abstraction, -// these two drivers have tight dependencies on each other. -// These functions orchestrate the connection. - -int pbdrv_block_device_ev3_is_busy(); -pbio_error_t pbdrv_block_device_ev3_spi_begin_for_adc(const uint32_t *cmds, volatile uint16_t *data, unsigned int len); +void pbdrv_block_device_ev3_spi_tx_complete(void); +void pbdrv_block_device_ev3_spi_rx_complete(void); #endif // _INTERNAL_PBDRV_BLOCK_DEVICE_EV3_H_ diff --git a/lib/pbio/drv/block_device/block_device_flash_stm32.c b/lib/pbio/drv/block_device/block_device_flash_stm32.c index 300edbce4..20e3b6220 100644 --- a/lib/pbio/drv/block_device/block_device_flash_stm32.c +++ b/lib/pbio/drv/block_device/block_device_flash_stm32.c @@ -21,39 +21,104 @@ #include STM32_HAL_H -void pbdrv_block_device_init(void) { +extern uint8_t _pbdrv_block_device_storage_start[]; + +static struct { + /** + * How much data to write on shutdown and load on the next boot. Includes + * the size of this field, because it is also saved. + */ + uint32_t saved_size; + /** + * Checksum complement to satisfy bootloader requirements. This ensures + * that words in the scanned area still add up to precisely 0 after user + * data was written. Needs to be volatile as it gets optimized out when it + * is never read by our code. + */ + volatile uint32_t checksum_complement; + /** + * A copy of the data loaded from flash and application heap. The first + * portion of this, up to pbdrv_block_device_get_writable_size() bytes, + * gets saved to flash at shutdown. + */ + uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE]; +} ramdisk __attribute__((section(".noinit"), used)); + +const uint32_t header_size = sizeof(ramdisk.saved_size) + sizeof(ramdisk.checksum_complement); + +uint32_t pbdrv_block_device_get_writable_size(void) { + return PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE - header_size; } -extern uint8_t _pbdrv_block_device_storage_start[]; +static pbio_error_t init_err; -pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, uint8_t *buffer, uint32_t size) { +void pbdrv_block_device_init(void) { - // NB: This function is called as an awaitable for compatibility with other - // external storage mediums. It blocks during the memcpy, but we only use - // this at boot. + uint32_t size; + + memcpy(&size, _pbdrv_block_device_storage_start, sizeof(size)); // Exit on invalid size. - if (size == 0 || offset + size > PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE) { - return PBIO_ERROR_INVALID_ARG; + if (size == 0 || size > PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE) { + // This error will be retrieved when higher level code requests the + // ramdisk, so that it can reset data to firmware defaults. + init_err = PBIO_ERROR_INVALID_ARG; + return; } - // Copy requested data to RAM. - memcpy(buffer, _pbdrv_block_device_storage_start + offset, size); + // Load requested amount of data to RAM. Also re-reads the size value. + memcpy(&ramdisk, _pbdrv_block_device_storage_start, size); +} - return PBIO_SUCCESS; +pbio_error_t pbdrv_block_device_get_data(uint8_t **data) { + *data = ramdisk.data; + return init_err; } -typedef union _double_word_t { - uint8_t data[8]; - uint64_t dword; -} double_word_t; +// Updates checksum in data map to satisfy bootloader requirements. +static void pbdrv_block_device_update_ramdisk_size_and_checksum(uint32_t used_data_size) { + + // Total size includes used data and header. + ramdisk.saved_size = used_data_size + header_size; -pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, uint32_t size) { + // Align writable data by a double word, to simplify checksum + // computation and storage drivers that write double words. + while (ramdisk.saved_size % 8) { + *((uint8_t *)&ramdisk + ramdisk.saved_size++) = 0; + } + + // The area scanned by the bootloader adds up to 0 when all user data + // is 0xFFFFFFFF. So the bootloader value up until just before the user + // data is always 0 + the number of words in the scanned user data. + extern uint32_t _pbsys_storage_checked_size; + uint32_t checksize = (uint32_t)&_pbsys_storage_checked_size; + uint32_t checksum = checksize / sizeof(uint32_t); + + // Don't count existing value. + ramdisk.checksum_complement = 0; + + // Add checksum for each word in the written data and empty checked size. + for (uint32_t offset = 0; offset < checksize; offset += sizeof(uint32_t)) { + uint32_t *word = (uint32_t *)((uint8_t *)&ramdisk + offset); + // Assume that everything after written data is erased by the block + // device driver prior to writing. + checksum += offset < ramdisk.saved_size ? *word : 0xFFFFFFFF; + } + + // Set the checksum complement to cancel out user data checksum. + ramdisk.checksum_complement = 0xFFFFFFFF - checksum + 1; +} + +pbio_error_t pbdrv_block_device_write_all(pbio_os_state_t *state, uint32_t used_data_size) { // NB: This function is called as an awaitable for compatibility with other // external storage mediums. This implementation is blocking, but we only // use it during shutdown so this is acceptable. + // Account for header size and make valid checksum. + pbdrv_block_device_update_ramdisk_size_and_checksum(used_data_size); + uint32_t size = ramdisk.saved_size; + static const uint32_t base_address = (uint32_t)(&_pbdrv_block_device_storage_start[0]); // Exit if size is 0, too big, or not a multiple of double-word size. @@ -103,7 +168,7 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u __disable_irq(); // Write the data and re-enable interrupts. - hal_err = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, base_address + done, *(uint64_t *)(buffer + done)); + hal_err = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, base_address + done, *(uint64_t *)((void *)&ramdisk + done)); __set_PRIMASK(irq); if (hal_err != HAL_OK) { HAL_FLASH_Lock(); @@ -111,7 +176,7 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u } // Update write progress. - done += sizeof(double_word_t); + done += sizeof(uint64_t); } // Lock flash on completion. diff --git a/lib/pbio/drv/block_device/block_device_test.c b/lib/pbio/drv/block_device/block_device_test.c index aae2ee2de..496aa49e8 100644 --- a/lib/pbio/drv/block_device/block_device_test.c +++ b/lib/pbio/drv/block_device/block_device_test.c @@ -10,14 +10,10 @@ #include #include -#include - #include #include -#include - /** The following script is compiled using pybricksdev compile hello.py in MULTI_MPY_V6. @@ -58,43 +54,42 @@ static const uint8_t _program_data[] = { 0x63, }; +// Information from MicroPython should not be used in the pbdrv drivers but it +// is permissible for this test. It ensures we can place the expected git +// version at the right place. FIXME: Move the git version to pybricks build +// system, since it isn't actually the micropython git version. +#include "genhdr/mpversion.h" +#include + static struct { - uint32_t write_size; uint8_t user_data[PBSYS_CONFIG_STORAGE_USER_DATA_SIZE]; char stored_firmware_hash[8]; pbsys_storage_settings_t settings; uint32_t program_offset; uint32_t program_size; uint8_t program_data[sizeof(_program_data)]; -} blockdev = { 0 }; +} ramdisk __attribute__((section(".noinit"), used)) = { 0 }; -// Information from MicroPython should not be used in the pbdrv drivers but it -// is permissible for this test. It ensures we can place the expected git -// version at the right place. FIXME: Move the git version to pybricks build -// system, since it isn't actually the micropython git version. -#include "genhdr/mpversion.h" - -void pbdrv_block_device_init(void) { - blockdev.write_size = sizeof(blockdev) + sizeof(_program_data); - blockdev.program_size = sizeof(_program_data); - memcpy(&blockdev.stored_firmware_hash[0], MICROPY_GIT_HASH, sizeof(blockdev.stored_firmware_hash)); - memcpy(&blockdev.program_data[0], _program_data, sizeof(_program_data)); +uint32_t pbdrv_block_device_get_writable_size(void) { + return 0; } -pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, uint8_t *buffer, uint32_t size) { - - // Exit on invalid size. - if (size == 0 || offset + size > PBDRV_CONFIG_BLOCK_DEVICE_TEST_SIZE) { - return PBIO_ERROR_INVALID_ARG; - } +pbio_error_t pbdrv_block_device_get_data(uint8_t **data) { + *data = (void *)&ramdisk; - // Copy requested data to RAM. - memcpy(buffer, (uint8_t *)&blockdev + offset, size); + // Higher level code can use the ramdisk data if initialization completed + // successfully. Otherwise it should reset to factory default data. return PBIO_SUCCESS; } +void pbdrv_block_device_init(void) { + ramdisk.program_size = sizeof(_program_data); + memcpy(&ramdisk.stored_firmware_hash[0], MICROPY_GIT_HASH, sizeof(ramdisk.stored_firmware_hash)); + memcpy(&ramdisk.program_data[0], _program_data, sizeof(_program_data)); +} + // Don't store any data in this implementation. -pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, uint32_t size) { +pbio_error_t pbdrv_block_device_write_all(pbio_os_state_t *state, uint32_t used_data_size) { return PBIO_ERROR_NOT_IMPLEMENTED; } diff --git a/lib/pbio/drv/block_device/block_device_w25qxx_stm32.c b/lib/pbio/drv/block_device/block_device_w25qxx_stm32.c index 723ea9ec9..73f250c97 100644 --- a/lib/pbio/drv/block_device/block_device_w25qxx_stm32.c +++ b/lib/pbio/drv/block_device/block_device_w25qxx_stm32.c @@ -23,6 +23,34 @@ #include #include +static pbio_os_process_t pbdrv_block_device_w25qxx_stm32_init_process; + +static struct { + /** + * How much data to write on shutdown and load on the next boot. Includes + * the size of this field, because it is also saved. + */ + uint32_t saved_size; + /** + * A copy of the data loaded from flash and application heap. The first + * portion of this, up to pbdrv_block_device_get_writable_size() bytes, + * gets saved to flash at shutdown. + */ + uint8_t data[PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE]; +} ramdisk __attribute__((section(".noinit"), used)); + +uint32_t pbdrv_block_device_get_writable_size(void) { + return PBDRV_CONFIG_BLOCK_DEVICE_W25QXX_STM32_SIZE - sizeof(ramdisk.saved_size); +} + +pbio_error_t pbdrv_block_device_get_data(uint8_t **data) { + *data = ramdisk.data; + + // Higher level code can use the ramdisk data if initialization completed + // successfully. Otherwise it should reset to factory default data. + return pbdrv_block_device_w25qxx_stm32_init_process.err; +} + /** * SPI bus state. */ @@ -336,7 +364,7 @@ static spi_command_t cmd_data_read = { .operation = SPI_RECV, }; -pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, uint8_t *buffer, uint32_t size) { +static pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, uint8_t *buffer, uint32_t size) { static pbio_os_state_t sub; static uint32_t size_done; @@ -433,7 +461,7 @@ static pbio_error_t flash_erase_or_write(pbio_os_state_t *state, uint32_t addres PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, uint32_t size) { +pbio_error_t pbdrv_block_device_write_all(pbio_os_state_t *state, uint32_t used_data_size) { static pbio_os_state_t sub; static uint32_t offset; @@ -441,6 +469,11 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u static uint32_t size_done; pbio_error_t err; + // We're going to write the used portion of the ramdisk to flash. Includes + // the size field itself, so add it to the total write size. + uint8_t *buffer = (void *)&ramdisk; + uint32_t size = used_data_size + sizeof(ramdisk.saved_size); + PBIO_OS_ASYNC_BEGIN(state); // Exit on invalid size. @@ -448,6 +481,9 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u return PBIO_ERROR_INVALID_ARG; } + // Store the new size so we know how much to load on next boot. + ramdisk.saved_size = size; + // Erase sector by sector. for (offset = 0; offset < size; offset += FLASH_SIZE_ERASE) { // Writing size 0 means erase. @@ -471,8 +507,6 @@ pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, u PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -static pbio_os_process_t pbdrv_block_device_w25qxx_stm32_init_process; - pbio_error_t pbdrv_block_device_w25qxx_stm32_init_process_thread(pbio_os_state_t *state, void *context) { pbio_error_t err; @@ -497,10 +531,24 @@ pbio_error_t pbdrv_block_device_w25qxx_stm32_init_process_thread(pbio_os_state_t return PBIO_ERROR_FAILED; } - // Initialization done. + // Read size of stored data. + PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_read(&sub, 0, (uint8_t *)&ramdisk.saved_size, sizeof(ramdisk.saved_size))); + if (err != PBIO_SUCCESS) { + return err; + } + + // Read the available data into RAM. + PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_read(&sub, 0, (uint8_t *)&ramdisk, ramdisk.saved_size)); + + // Reading may fail with PBIO_ERROR_INVALID_ARG if the size is too big. + // This happens when the size value was uninitialized or another firmware + // was used before. We still want to proceed with the boot process. The + // higher level code sees this error when requesting the RAM disk. On + // failure, it can reset the user data to factory defaults, and save it + // properly on shutdown. pbdrv_init_busy_down(); - PBIO_OS_ASYNC_END(PBIO_SUCCESS); + PBIO_OS_ASYNC_END(err); } void pbdrv_block_device_init(void) { diff --git a/lib/pbio/drv/counter/counter_ev3.c b/lib/pbio/drv/counter/counter_ev3.c index 8e4a4166d..4f6aeab14 100644 --- a/lib/pbio/drv/counter/counter_ev3.c +++ b/lib/pbio/drv/counter/counter_ev3.c @@ -130,10 +130,11 @@ static lego_device_type_id_t pbdrv_counter_ev3_get_type(uint16_t adc) { return LEGO_DEVICE_TYPE_ID_NONE; } +#define PBDRV_COUNTER_EV3_TYPE_LOOP_TIME (10) +#define PBDRV_COUNTER_EV3_TYPE_MIN_STABLE_COUNT (20) + /** * Updates the type of all EV3 motors based on the current ADC values. - * - * This is called periodically by the ADC process. */ static void pbdrv_counter_ev3_update_type(void) { @@ -155,12 +156,32 @@ static void pbdrv_counter_ev3_update_type(void) { // Update stable type if we have seen enough identical detections, // including none detections. - if (dev->type_id_count >= 20) { + if (dev->type_id_count >= PBDRV_COUNTER_EV3_TYPE_MIN_STABLE_COUNT) { dev->stable_type_id = type_id; } } } +static pbio_os_process_t pbdrv_counter_device_detect_process; + +/** + * Background process to periodically read the ADC values to detect devices. + */ +static pbio_error_t pbdrv_counter_device_detect_process_thread(pbio_os_state_t *state, void *context) { + + static pbio_os_timer_t timer; + + PBIO_OS_ASYNC_BEGIN(state); + + for (;;) { + pbdrv_counter_ev3_update_type(); + PBIO_OS_AWAIT_MS(state, &timer, PBDRV_COUNTER_EV3_TYPE_LOOP_TIME); + } + + PBIO_OS_ASYNC_END(PBIO_SUCCESS); +} + + pbio_error_t pbdrv_counter_assert_type(pbdrv_counter_dev_t *dev, lego_device_type_id_t *expected_type_id) { if (dev->stable_type_id == LEGO_DEVICE_TYPE_ID_NONE) { @@ -256,7 +277,8 @@ void pbdrv_counter_init(void) { HWREG(baseAddr + GPIO_SET_RIS_TRIG(3)) = HWREG(baseAddr + GPIO_SET_RIS_TRIG(3)) | 0x00000200; HWREG(baseAddr + GPIO_SET_FAL_TRIG(3)) = HWREG(baseAddr + GPIO_SET_FAL_TRIG(3)) | 0x00000200; - pbdrv_adc_set_callback(pbdrv_counter_ev3_update_type); + // Monitor attached counter devices + pbio_os_process_start(&pbdrv_counter_device_detect_process, pbdrv_counter_device_detect_process_thread, NULL); } #endif // PBDRV_CONFIG_COUNTER_EV3 diff --git a/lib/pbio/include/pbdrv/adc.h b/lib/pbio/include/pbdrv/adc.h index cb6117ff2..995f13913 100644 --- a/lib/pbio/include/pbdrv/adc.h +++ b/lib/pbio/include/pbdrv/adc.h @@ -13,6 +13,7 @@ #include #include +#include typedef void (*pbdrv_adc_callback_t)(void); @@ -29,20 +30,15 @@ typedef void (*pbdrv_adc_callback_t)(void); pbio_error_t pbdrv_adc_get_ch(uint8_t ch, uint16_t *value); /** - * Sets a callback to be called when the ADC has new data. + * Awaits for ADC to have new samples ready to be read. * - * NB: Not implemented on all platforms. + * Not implemented on all platforms. * - * @param [in] callback The callback function. + * @param [in] state Protothread state. + * @param [in] timer Parent process timer, used to store time of calling this. + * @param [in] future How far into the future the sample should be (ms). */ -void pbdrv_adc_set_callback(pbdrv_adc_callback_t callback); - -/** - * Requests the ADC to update soon. - * - * NB: Not implemented on all platforms. - */ -void pbdrv_adc_update_soon(void); +pbio_error_t pbdrv_adc_await_new_samples(pbio_os_state_t *state, pbio_os_timer_t *timer, uint32_t future); #else @@ -51,10 +47,8 @@ static inline pbio_error_t pbdrv_adc_get_ch(uint8_t ch, uint16_t *value) { return PBIO_ERROR_NOT_SUPPORTED; } -static inline void pbdrv_adc_set_callback(pbdrv_adc_callback_t callback) { -} - -static inline void pbdrv_adc_update_soon(void) { +static inline pbio_error_t pbdrv_adc_await_new_samples(pbio_os_state_t *state, pbio_os_timer_t *timer, uint32_t future) { + return PBIO_ERROR_NOT_SUPPORTED; } #endif diff --git a/lib/pbio/include/pbdrv/block_device.h b/lib/pbio/include/pbdrv/block_device.h index 6641aa2df..a29e9261e 100644 --- a/lib/pbio/include/pbdrv/block_device.h +++ b/lib/pbio/include/pbdrv/block_device.h @@ -19,40 +19,26 @@ #if PBDRV_CONFIG_BLOCK_DEVICE /** - * Read data from a storage device. + * Gets the block device data. * - * On systems with data storage on an external chip, this is implemented with - * non-blocking I/O operations. - * - * On systems where data is memory accessible, this may be implemented with - * a single (blocking) memcpy operation. - * - * @param [in] state Protothread state. - * @param [in] offset Offset from the base address for this block device. - * @param [in] buffer Buffer to read the data into. - * @param [in] size How many bytes to read. + * @param [out] disk Pointer to the loaded data. * @return ::PBIO_SUCCESS on success. - * ::PBIO_INVALID_ARGUMENT if offset + size is too big. - * ::PBIO_ERROR_BUSY (driver-specific error) - * ::PBIO_ERROR_TIMEDOUT (driver-specific error) - * ::PBIO_ERROR_IO (driver-specific error) + * ::PBIO_ERROR_AGAIN if the RAM disk is still loading. + * ::PBIO_ERROR_INVALID_ARG if the detected size was invalid. + * ::PBIO_ERROR_IO (driver-specific error), though boot + * does not proceed if this happens. */ -pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, uint8_t *buffer, uint32_t size); +pbio_error_t pbdrv_block_device_get_data(uint8_t **data); /** - * Store data on storage device, starting from the base address. - * - * This erases as many sectors as needed for the given size prior to writing. + * Writes the "RAM Disk" to storage. May erase entire disk prior to writing. * * On systems with data storage on an external chip, this is implemented with - * non-blocking I/O operations. - * - * On systems where data is saved on internal flash, this may be partially - * implemented with blocking operations, so this function should only be used - * when this is permissible. + * non-blocking I/O operations. On systems where data is saved on internal + * flash, this may be partially implemented with blocking operations, so this + * function should only be used when this is permissible. * * @param [in] state Protothread state. - * @param [in] buffer Data buffer to write. * @param [in] size How many bytes to write. * @return ::PBIO_SUCCESS on success. * ::PBIO_INVALID_ARGUMENT if size is too big. @@ -60,17 +46,29 @@ pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, ui * ::PBIO_ERROR_TIMEDOUT (driver-specific error) * ::PBIO_ERROR_IO (driver-specific error) */ -pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, uint32_t size); +pbio_error_t pbdrv_block_device_write_all(pbio_os_state_t *state, uint32_t used_data_size); + +/** + * Gets the maximum writable size for user data that can be saved to the device. + * + * @return Size in bytes. + */ +uint32_t pbdrv_block_device_get_writable_size(void); #else -static inline pbio_error_t pbdrv_block_device_read(pbio_os_state_t *state, uint32_t offset, uint8_t *buffer, uint32_t size) { +static inline pbio_error_t pbdrv_block_device_get_data(uint8_t **data) { return PBIO_ERROR_NOT_SUPPORTED; } -static inline pbio_error_t pbdrv_block_device_store(pbio_os_state_t *state, uint8_t *buffer, uint32_t size) { + +static inline pbio_error_t pbdrv_block_device_write_all(pbio_os_state_t *state, uint32_t used_data_size) { return PBIO_ERROR_NOT_SUPPORTED; } +static inline uint32_t pbdrv_block_device_get_writable_size(void) { + return 0; +} + #endif #endif // _PBDRV_BLOCK_DEVICE_H_ diff --git a/lib/pbio/include/pbdrv/config.h b/lib/pbio/include/pbdrv/config.h index 43135c34e..c8ca90ea5 100644 --- a/lib/pbio/include/pbdrv/config.h +++ b/lib/pbio/include/pbdrv/config.h @@ -57,4 +57,11 @@ #define PBDRV_CONFIG_HAS_PORT_4 (0) #endif +#if PBDRV_CONFIG_BLOCK_DEVICE +// Application RAM must enough to load ROM and still do something useful. +#if PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE < PBDRV_CONFIG_BLOCK_DEVICE_ROM_SIZE + 2048 +#error "Application RAM must be at least ROM size + 2K." +#endif +#endif + #endif // _PBDRV_CONFIG_H_ diff --git a/lib/pbio/include/pbio/control_settings.h b/lib/pbio/include/pbio/control_settings.h index dc6866c6d..1ac3211f0 100644 --- a/lib/pbio/include/pbio/control_settings.h +++ b/lib/pbio/include/pbio/control_settings.h @@ -135,7 +135,6 @@ int32_t pbio_control_settings_app_to_ctl(const pbio_control_settings_t *s, int32 void pbio_control_settings_app_to_ctl_long(const pbio_control_settings_t *s, int32_t input, pbio_angle_t *output); int32_t pbio_control_settings_actuation_ctl_to_app(int32_t input); int32_t pbio_control_settings_actuation_app_to_ctl(int32_t input); -bool pbio_control_settings_time_is_later(uint32_t sample, uint32_t base); // Scale values by given constants: diff --git a/lib/pbio/include/pbio/os.h b/lib/pbio/include/pbio/os.h index 5b510d854..d0f07e6b6 100644 --- a/lib/pbio/include/pbio/os.h +++ b/lib/pbio/include/pbio/os.h @@ -98,7 +98,7 @@ typedef enum { /** * Ask the process to cancel/exit. */ - PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL = 1 << 1, + PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL = 1 << 0, } pbio_os_process_request_type_t; /** @@ -290,6 +290,8 @@ struct _pbio_os_process_t { } \ } while (0) \ +void pbio_os_process_make_request(pbio_os_process_t *process, pbio_os_process_request_type_t request); + bool pbio_os_run_processes_once(void); void pbio_os_run_processes_and_wait_for_event(void); diff --git a/lib/pbio/include/pbio/util.h b/lib/pbio/include/pbio/util.h index 0ff8a38f7..f41f1c49a 100644 --- a/lib/pbio/include/pbio/util.h +++ b/lib/pbio/include/pbio/util.h @@ -130,6 +130,8 @@ void pbio_uuid128_reverse_copy(uint8_t *dst, const uint8_t *src); bool pbio_oneshot(bool value, bool *state); +bool pbio_util_time_has_passed(uint32_t sample, uint32_t base); + #endif // _PBIO_UTIL_H_ /** @} */ diff --git a/lib/pbio/platform/city_hub/pbdrvconfig.h b/lib/pbio/platform/city_hub/pbdrvconfig.h index 0a84c141a..7d6a99a5d 100644 --- a/lib/pbio/platform/city_hub/pbdrvconfig.h +++ b/lib/pbio/platform/city_hub/pbdrvconfig.h @@ -25,6 +25,7 @@ #define PBDRV_CONFIG_BUTTON_GPIO_DEBOUNCE (1) #define PBDRV_CONFIG_BLOCK_DEVICE (1) +#define PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE (20 * 1024) #define PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32 (1) #define PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE (16 * 1024) // Must match FLASH_USER_0 + FLASH_USER_1 in linker script diff --git a/lib/pbio/platform/city_hub/pbsysconfig.h b/lib/pbio/platform/city_hub/pbsysconfig.h index a22917c08..8e0de1fdd 100644 --- a/lib/pbio/platform/city_hub/pbsysconfig.h +++ b/lib/pbio/platform/city_hub/pbsysconfig.h @@ -16,9 +16,6 @@ #define PBSYS_CONFIG_MAIN (1) #define PBSYS_CONFIG_STORAGE (1) #define PBSYS_CONFIG_STORAGE_NUM_SLOTS (1) -#define PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM (1) -#define PBSYS_CONFIG_STORAGE_RAM_SIZE (20 * 1024) -#define PBSYS_CONFIG_STORAGE_ROM_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE) #define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (128) #define PBSYS_CONFIG_STATUS_LIGHT (1) #define PBSYS_CONFIG_STATUS_LIGHT_BATTERY (0) diff --git a/lib/pbio/platform/city_hub/platform.ld b/lib/pbio/platform/city_hub/platform.ld index 4cbad00ca..08f2364ee 100644 --- a/lib/pbio/platform/city_hub/platform.ld +++ b/lib/pbio/platform/city_hub/platform.ld @@ -25,7 +25,7 @@ MEMORY "MAGIC_OFFSET" = 0x100; /* Minimal stack size to allow the build to proceed. The actual stack size is whatever is left after - statically allocated memory. It can be tuned by setting PBSYS_CONFIG_STORAGE_RAM_SIZE. */ + statically allocated memory. It can be tuned by setting PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE. */ _minimal_stack_size = 4K; /* Start of data storage. */ diff --git a/lib/pbio/platform/essential_hub/pbdrvconfig.h b/lib/pbio/platform/essential_hub/pbdrvconfig.h index 073f235f4..4fc41e004 100644 --- a/lib/pbio/platform/essential_hub/pbdrvconfig.h +++ b/lib/pbio/platform/essential_hub/pbdrvconfig.h @@ -34,6 +34,7 @@ #undef PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_VARIANT_ADDR #define PBDRV_CONFIG_BLOCK_DEVICE (1) +#define PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE (258 * 1024) #define PBDRV_CONFIG_BLOCK_DEVICE_W25QXX_STM32 (1) #define PBDRV_CONFIG_BLOCK_DEVICE_W25QXX_STM32_W25Q32 (1) // Carve out 256K from the reserved 1M area at the start of the flash. diff --git a/lib/pbio/platform/essential_hub/pbsysconfig.h b/lib/pbio/platform/essential_hub/pbsysconfig.h index 73a24df95..8c479ecb3 100644 --- a/lib/pbio/platform/essential_hub/pbsysconfig.h +++ b/lib/pbio/platform/essential_hub/pbsysconfig.h @@ -14,9 +14,6 @@ #define PBSYS_CONFIG_MAIN (1) #define PBSYS_CONFIG_STORAGE (1) #define PBSYS_CONFIG_STORAGE_NUM_SLOTS (1) -#define PBSYS_CONFIG_STORAGE_RAM_SIZE (258 * 1024) -#define PBSYS_CONFIG_STORAGE_ROM_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_W25QXX_STM32_SIZE) -#define PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM (0) #define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (512) #define PBSYS_CONFIG_STATUS_LIGHT (1) #define PBSYS_CONFIG_STATUS_LIGHT_BATTERY (1) diff --git a/lib/pbio/platform/essential_hub/platform.ld b/lib/pbio/platform/essential_hub/platform.ld index 9af6f6e70..7cfcc4498 100644 --- a/lib/pbio/platform/essential_hub/platform.ld +++ b/lib/pbio/platform/essential_hub/platform.ld @@ -20,5 +20,5 @@ MEMORY "FW_INFO_OFFSET" = 0x200; /* Minimal stack size to allow the build to proceed. The actual stack size is whatever is left after - statically allocated memory. It can be tuned by setting PBSYS_CONFIG_STORAGE_RAM_SIZE. */ + statically allocated memory. It can be tuned by setting PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE. */ _minimal_stack_size = 12K; diff --git a/lib/pbio/platform/ev3/pbdrvconfig.h b/lib/pbio/platform/ev3/pbdrvconfig.h index 1f85e7e49..9ef46f313 100644 --- a/lib/pbio/platform/ev3/pbdrvconfig.h +++ b/lib/pbio/platform/ev3/pbdrvconfig.h @@ -17,6 +17,7 @@ #define PBDRV_CONFIG_BATTERY_EV3 (1) #define PBDRV_CONFIG_BLOCK_DEVICE (1) +#define PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE (10 * 1024) #define PBDRV_CONFIG_BLOCK_DEVICE_EV3 (1) #define PBDRV_CONFIG_BLOCK_DEVICE_EV3_SIZE (8 * 1024) // TBD, can be a few MB #define PBDRV_CONFIG_BLOCK_DEVICE_EV3_SIZE_USER (512) diff --git a/lib/pbio/platform/ev3/pbsysconfig.h b/lib/pbio/platform/ev3/pbsysconfig.h index 201c278c9..f3ad6fc19 100644 --- a/lib/pbio/platform/ev3/pbsysconfig.h +++ b/lib/pbio/platform/ev3/pbsysconfig.h @@ -10,9 +10,6 @@ #define PBSYS_CONFIG_MAIN (1) #define PBSYS_CONFIG_STORAGE (1) #define PBSYS_CONFIG_STORAGE_NUM_SLOTS (1) -#define PBSYS_CONFIG_STORAGE_RAM_SIZE (10 * 1024) -#define PBSYS_CONFIG_STORAGE_ROM_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_EV3_SIZE) -#define PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM (0) #define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (512) #define PBSYS_CONFIG_STATUS_LIGHT (1) #define PBSYS_CONFIG_STATUS_LIGHT_BATTERY (0) diff --git a/lib/pbio/platform/move_hub/pbdrvconfig.h b/lib/pbio/platform/move_hub/pbdrvconfig.h index 4ae4a5291..9be8dece7 100644 --- a/lib/pbio/platform/move_hub/pbdrvconfig.h +++ b/lib/pbio/platform/move_hub/pbdrvconfig.h @@ -19,6 +19,7 @@ #define PBDRV_CONFIG_BATTERY_ADC_TYPE (1) #define PBDRV_CONFIG_BLOCK_DEVICE (1) +#define PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE (7 * 1024) #define PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32 (1) #define PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE (4 * 1024) // Must match FLASH_USER_0 + FLASH_USER_1 in linker script diff --git a/lib/pbio/platform/move_hub/pbsysconfig.h b/lib/pbio/platform/move_hub/pbsysconfig.h index df741599d..2a8a49948 100644 --- a/lib/pbio/platform/move_hub/pbsysconfig.h +++ b/lib/pbio/platform/move_hub/pbsysconfig.h @@ -16,9 +16,6 @@ #define PBSYS_CONFIG_MAIN (1) #define PBSYS_CONFIG_STORAGE (1) #define PBSYS_CONFIG_STORAGE_NUM_SLOTS (1) -#define PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM (1) -#define PBSYS_CONFIG_STORAGE_RAM_SIZE (7 * 1024) -#define PBSYS_CONFIG_STORAGE_ROM_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE) #define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (128) #define PBSYS_CONFIG_STATUS_LIGHT (1) #define PBSYS_CONFIG_STATUS_LIGHT_BATTERY (0) diff --git a/lib/pbio/platform/move_hub/platform.ld b/lib/pbio/platform/move_hub/platform.ld index 6d1ade7a6..f9efc61b4 100644 --- a/lib/pbio/platform/move_hub/platform.ld +++ b/lib/pbio/platform/move_hub/platform.ld @@ -25,7 +25,7 @@ MEMORY "MAGIC_OFFSET" = 0x100; /* Minimal stack size to allow the build to proceed. The actual stack size is whatever is left after - statically allocated memory. It can be tuned by setting PBSYS_CONFIG_STORAGE_RAM_SIZE. */ + statically allocated memory. It can be tuned by setting PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE. */ _minimal_stack_size = 3K; /* Start of data storage. */ diff --git a/lib/pbio/platform/nxt/pbdrvconfig.h b/lib/pbio/platform/nxt/pbdrvconfig.h index 2a0131a73..d901b70fe 100644 --- a/lib/pbio/platform/nxt/pbdrvconfig.h +++ b/lib/pbio/platform/nxt/pbdrvconfig.h @@ -9,6 +9,7 @@ #define PBDRV_CONFIG_BATTERY_NXT (1) #define PBDRV_CONFIG_BLOCK_DEVICE (1) +#define PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE (10 * 1024) #define PBDRV_CONFIG_BLOCK_DEVICE_TEST (1) #define PBDRV_CONFIG_BLOCK_DEVICE_TEST_SIZE (8 * 1024) #define PBDRV_CONFIG_BLOCK_DEVICE_TEST_SIZE_USER (512) diff --git a/lib/pbio/platform/nxt/pbsysconfig.h b/lib/pbio/platform/nxt/pbsysconfig.h index 28813bbda..50e4e07cf 100644 --- a/lib/pbio/platform/nxt/pbsysconfig.h +++ b/lib/pbio/platform/nxt/pbsysconfig.h @@ -10,8 +10,6 @@ #define PBSYS_CONFIG_MAIN (1) #define PBSYS_CONFIG_STORAGE (1) #define PBSYS_CONFIG_STORAGE_NUM_SLOTS (1) -#define PBSYS_CONFIG_STORAGE_RAM_SIZE (10 * 1024) -#define PBSYS_CONFIG_STORAGE_ROM_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_TEST_SIZE) #define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_TEST_SIZE_USER) #define PBSYS_CONFIG_STATUS_LIGHT (0) #define PBSYS_CONFIG_STATUS_LIGHT_BATTERY (0) diff --git a/lib/pbio/platform/prime_hub/pbdrvconfig.h b/lib/pbio/platform/prime_hub/pbdrvconfig.h index 07e35040f..c6717d4d4 100644 --- a/lib/pbio/platform/prime_hub/pbdrvconfig.h +++ b/lib/pbio/platform/prime_hub/pbdrvconfig.h @@ -34,6 +34,7 @@ #define PBDRV_CONFIG_BLUETOOTH_BTSTACK_HUB_VARIANT_ADDR 0x08007d80 #define PBDRV_CONFIG_BLOCK_DEVICE (1) +#define PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE (258 * 1024) #define PBDRV_CONFIG_BLOCK_DEVICE_W25QXX_STM32 (1) #define PBDRV_CONFIG_BLOCK_DEVICE_W25QXX_STM32_W25Q256 (1) // Carve out 256K from the reserved 1M area at the start of the flash. diff --git a/lib/pbio/platform/prime_hub/pbsysconfig.h b/lib/pbio/platform/prime_hub/pbsysconfig.h index 756ae3e2b..b5bfc7082 100644 --- a/lib/pbio/platform/prime_hub/pbsysconfig.h +++ b/lib/pbio/platform/prime_hub/pbsysconfig.h @@ -16,9 +16,6 @@ #define PBSYS_CONFIG_MAIN (1) #define PBSYS_CONFIG_STORAGE (1) #define PBSYS_CONFIG_STORAGE_NUM_SLOTS (5) -#define PBSYS_CONFIG_STORAGE_RAM_SIZE (258 * 1024) -#define PBSYS_CONFIG_STORAGE_ROM_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_W25QXX_STM32_SIZE) -#define PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM (0) #define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (512) #define PBSYS_CONFIG_STATUS_LIGHT (1) #define PBSYS_CONFIG_STATUS_LIGHT_BATTERY (1) diff --git a/lib/pbio/platform/prime_hub/platform.ld b/lib/pbio/platform/prime_hub/platform.ld index 746595c64..ba433b5b2 100644 --- a/lib/pbio/platform/prime_hub/platform.ld +++ b/lib/pbio/platform/prime_hub/platform.ld @@ -18,5 +18,5 @@ MEMORY } /* Minimal stack size to allow the build to proceed. The actual stack size is whatever is left after - statically allocated memory. It can be tuned by setting PBSYS_CONFIG_STORAGE_RAM_SIZE. */ + statically allocated memory. It can be tuned by setting PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE. */ _minimal_stack_size = 16K; diff --git a/lib/pbio/platform/technic_hub/pbdrvconfig.h b/lib/pbio/platform/technic_hub/pbdrvconfig.h index 91126ad77..e0e90a48f 100644 --- a/lib/pbio/platform/technic_hub/pbdrvconfig.h +++ b/lib/pbio/platform/technic_hub/pbdrvconfig.h @@ -24,6 +24,7 @@ #define PBDRV_CONFIG_BATTERY_ADC_TYPE (3) #define PBDRV_CONFIG_BLOCK_DEVICE (1) +#define PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE (32 * 1024) #define PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32 (1) #define PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE (16 * 1024) // Must match FLASH_USER_0 + FLASH_USER_1 in linker script diff --git a/lib/pbio/platform/technic_hub/pbsysconfig.h b/lib/pbio/platform/technic_hub/pbsysconfig.h index 8bf0de7d7..8e0de1fdd 100644 --- a/lib/pbio/platform/technic_hub/pbsysconfig.h +++ b/lib/pbio/platform/technic_hub/pbsysconfig.h @@ -16,9 +16,6 @@ #define PBSYS_CONFIG_MAIN (1) #define PBSYS_CONFIG_STORAGE (1) #define PBSYS_CONFIG_STORAGE_NUM_SLOTS (1) -#define PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM (1) -#define PBSYS_CONFIG_STORAGE_RAM_SIZE (32 * 1024) -#define PBSYS_CONFIG_STORAGE_ROM_SIZE (PBDRV_CONFIG_BLOCK_DEVICE_FLASH_STM32_SIZE) #define PBSYS_CONFIG_STORAGE_USER_DATA_SIZE (128) #define PBSYS_CONFIG_STATUS_LIGHT (1) #define PBSYS_CONFIG_STATUS_LIGHT_BATTERY (0) diff --git a/lib/pbio/platform/technic_hub/platform.ld b/lib/pbio/platform/technic_hub/platform.ld index 1b5b35ea7..87d0fe3ed 100644 --- a/lib/pbio/platform/technic_hub/platform.ld +++ b/lib/pbio/platform/technic_hub/platform.ld @@ -25,7 +25,7 @@ MEMORY "MAGIC_OFFSET" = 0x200; /* Minimal stack size to allow the build to proceed. The actual stack size is whatever is left after - statically allocated memory. It can be tuned by setting PBSYS_CONFIG_STORAGE_RAM_SIZE. */ + statically allocated memory. It can be tuned by setting PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE. */ _minimal_stack_size = 8K; /* Start of data storage. */ diff --git a/lib/pbio/src/control.c b/lib/pbio/src/control.c index 2a6fa18e2..41be16b09 100644 --- a/lib/pbio/src/control.c +++ b/lib/pbio/src/control.c @@ -14,6 +14,7 @@ #include #include #include +#include /** * Gets the wall time in control unit time ticks (1e-4 seconds). @@ -108,7 +109,7 @@ static bool pbio_control_check_completion(const pbio_control_t *ctl, uint32_t ti } // Check if we are passed the nominal maneuver time. - bool time_completed = pbio_control_settings_time_is_later(time, end->time); + bool time_completed = pbio_util_time_has_passed(time, end->time); if (pbio_control_type_is_time(ctl)) { // Infinite maneuvers are always done (should never block). @@ -354,7 +355,7 @@ void pbio_control_update( // without resetting any controllers. This avoids accumulating errors // in sequential relative maneuvers. (pbio_control_on_completion_is_passive_smart(ctl->on_completion) && - !pbio_control_settings_time_is_later(ref->time, ref_end.time + ctl->settings.smart_passive_hold_time))) { + !pbio_util_time_has_passed(ref->time, ref_end.time + ctl->settings.smart_passive_hold_time))) { // Keep actuating, so apply calculated PID torque value. *actuation = PBIO_DCMOTOR_ACTUATION_TORQUE; *control = torque; diff --git a/lib/pbio/src/control_settings.c b/lib/pbio/src/control_settings.c index 803c63450..22e606251 100644 --- a/lib/pbio/src/control_settings.c +++ b/lib/pbio/src/control_settings.c @@ -165,17 +165,6 @@ int32_t pbio_control_settings_mul_by_loop_time(int32_t input) { return input / (1000 / PBIO_CONFIG_CONTROL_LOOP_TIME_MS); } -/** - * Checks if a time sample is equal to or newer than a given base time stamp. - * - * @param [in] sample Sample time. - * @param [in] base Base time to compare to. - * @return True if sample time is equal to or newer than base time, else false. - */ -bool pbio_control_settings_time_is_later(uint32_t sample, uint32_t base) { - return sample - base < UINT32_MAX / 2; -} - /** * Gets the control limits for movement and actuation, in application units. * diff --git a/lib/pbio/src/integrator.c b/lib/pbio/src/integrator.c index 9d05a59dc..88a37df60 100644 --- a/lib/pbio/src/integrator.c +++ b/lib/pbio/src/integrator.c @@ -11,6 +11,7 @@ #include #include #include +#include /** * Pauses the speed integrator at the current position error. @@ -124,7 +125,7 @@ bool pbio_speed_integrator_stalled(const pbio_speed_integrator_t *itg, uint32_t } // If the integrator is paused for less than the stall time, we're still not stalled for now. - if (!pbio_control_settings_time_is_later(time_now, itg->time_pause_begin + itg->settings->stall_time)) { + if (!pbio_util_time_has_passed(time_now, itg->time_pause_begin + itg->settings->stall_time)) { return false; } @@ -299,7 +300,7 @@ bool pbio_position_integrator_stalled(const pbio_position_integrator_t *itg, uin } // If the integrator is paused for less than the stall time, we're still not stalled for now. - if (!pbio_control_settings_time_is_later(time_now, itg->time_pause_begin + itg->settings->stall_time)) { + if (!pbio_util_time_has_passed(time_now, itg->time_pause_begin + itg->settings->stall_time)) { return false; } diff --git a/lib/pbio/src/port_dcm_ev3.c b/lib/pbio/src/port_dcm_ev3.c index d064d8381..52cb0e6b4 100644 --- a/lib/pbio/src/port_dcm_ev3.c +++ b/lib/pbio/src/port_dcm_ev3.c @@ -317,25 +317,6 @@ static pbio_port_dcm_t dcm_state[PBIO_CONFIG_PORT_DCM_NUM_DEV]; #define DCM_LOOP_STEADY_STATE_COUNT (20) #define DCM_LOOP_DISCONNECT_COUNT (5) -pbio_error_t pbio_port_dcm_await_new_nxt_analog_sample(pbio_port_dcm_t *dcm, pbio_os_timer_t *timer, const pbdrv_ioport_pins_t *pins, uint32_t *value) { - pbio_os_state_t *state = &dcm->child; - - PBIO_OS_ASYNC_BEGIN(state); - - // Wait for LED to settle. - PBIO_OS_AWAIT_MS(state, timer, 1); - - // Request a new ADC sample. Revisit: Call back on completion instead of time. - pbdrv_adc_update_soon(); - PBIO_OS_AWAIT_MS(state, timer, 4); - - // Get the value. - uint8_t pin = dcm->category == DCM_CATEGORY_NXT_COLOR ? 6 : 1; - *value = pbio_port_dcm_get_mv(pins, pin); - - PBIO_OS_ASYNC_END(PBIO_SUCCESS); -} - /** * Thread that detects the device type. It monitors the ID1 and ID2 pins * on the port to see when devices are connected or disconnected. @@ -387,6 +368,23 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer continue; } + if (dcm->category == DCM_CATEGORY_NXT_LIGHT) { + debug_pr("Reading NXT Light Sensor until disconnected.\n"); + // While plugged in, get reflected and ambient light intensity. + while (!pbdrv_gpio_input(&pins->p2)) { + // Reflected intensity. + pbdrv_gpio_out_high(&pins->p5); + PBIO_OS_AWAIT(state, &dcm->child, pbdrv_adc_await_new_samples(&dcm->child, timer, 2)); + dcm->nxt_rgba.r = pbio_port_dcm_get_mv(pins, 1); + + // Ambient intensity. + pbdrv_gpio_out_low(&pins->p5); + PBIO_OS_AWAIT(state, &dcm->child, pbdrv_adc_await_new_samples(&dcm->child, timer, 2)); + dcm->nxt_rgba.a = pbio_port_dcm_get_mv(pins, 1); + } + continue; + } + if (dcm->category == DCM_CATEGORY_NXT_COLOR) { debug_pr("Initializing NXT Color Sensor.\n"); @@ -405,30 +403,27 @@ pbio_error_t pbio_port_dcm_thread(pbio_os_state_t *state, pbio_os_timer_t *timer pbio_port_dcm_nxt_color_rx_msg(&dcm->child, &dcm->nxt_color_state, pins, timer, (uint8_t *)&dcm->nxt_color_state.data + dcm->count)); } - // Checksum and continue on failure. + // REVISIT: Test checksum and exit on failure. debug_pr("Finished initializing NXT Color Sensor.\n"); - } - if (dcm->category == DCM_CATEGORY_NXT_LIGHT || dcm->category == DCM_CATEGORY_NXT_COLOR) { - debug_pr("Reading NXT Light/Color Sensor until disconnected.\n"); - // While plugged in, toggle through available colors. + // While plugged in, toggle through available colors and measure intensity. while (!pbdrv_gpio_input(&pins->p2)) { pbdrv_gpio_out_low(&pins->p5); - PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.a)); - pbdrv_gpio_out_high(&pins->p5); - PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.r)); + PBIO_OS_AWAIT(state, &dcm->child, pbdrv_adc_await_new_samples(&dcm->child, timer, 2)); + dcm->nxt_rgba.a = pbio_port_dcm_get_mv(pins, 6); - if (dcm->category == DCM_CATEGORY_NXT_LIGHT) { - // Light sensor doesn't have green and blue. - continue; - } + pbdrv_gpio_out_high(&pins->p5); + PBIO_OS_AWAIT(state, &dcm->child, pbdrv_adc_await_new_samples(&dcm->child, timer, 2)); + dcm->nxt_rgba.r = pbio_port_dcm_get_mv(pins, 6); pbdrv_gpio_out_low(&pins->p5); - PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.g)); - pbdrv_gpio_out_high(&pins->p5); - PBIO_OS_AWAIT(state, &dcm->child, pbio_port_dcm_await_new_nxt_analog_sample(dcm, timer, pins, &dcm->nxt_rgba.b)); + PBIO_OS_AWAIT(state, &dcm->child, pbdrv_adc_await_new_samples(&dcm->child, timer, 2)); + dcm->nxt_rgba.g = pbio_port_dcm_get_mv(pins, 6); + pbdrv_gpio_out_high(&pins->p5); + PBIO_OS_AWAIT(state, &dcm->child, pbdrv_adc_await_new_samples(&dcm->child, timer, 2)); + dcm->nxt_rgba.b = pbio_port_dcm_get_mv(pins, 6); } pbdrv_gpio_out_low(&pins->p5); continue; diff --git a/lib/pbio/src/util.c b/lib/pbio/src/util.c index efdd1d3ac..0eefd7b95 100644 --- a/lib/pbio/src/util.c +++ b/lib/pbio/src/util.c @@ -80,3 +80,14 @@ bool pbio_oneshot(bool value, bool *state) { return ret; } + +/** + * Checks if a time sample is equal to or newer than a given base time stamp. + * + * @param [in] sample Sample time. + * @param [in] base Base time to compare to. + * @return True if sample time is equal to or newer than base time, else false. + */ +bool pbio_util_time_has_passed(uint32_t sample, uint32_t base) { + return sample - base < UINT32_MAX / 2; +} diff --git a/lib/pbio/sys/core.c b/lib/pbio/sys/core.c index a3d02d088..26d29a02d 100644 --- a/lib/pbio/sys/core.c +++ b/lib/pbio/sys/core.c @@ -45,10 +45,14 @@ PROCESS_THREAD(pbsys_system_process, ev, data) { } void pbsys_init(void) { + + // Makes user data and settings available to modules below, so must be done first. + pbsys_storage_init(); + pbsys_battery_init(); pbsys_host_init(); pbsys_hmi_init(); - pbsys_storage_init(); + process_start(&pbsys_system_process); while (pbsys_init_busy()) { diff --git a/lib/pbio/sys/storage.c b/lib/pbio/sys/storage.c index c3da7aab1..daa13e57b 100644 --- a/lib/pbio/sys/storage.c +++ b/lib/pbio/sys/storage.c @@ -52,19 +52,6 @@ static uint8_t incoming_slot = 0; * Map of loaded data. All data types are little-endian. */ typedef struct { - /** - * How much to write on shutdown (and how much to load on boot). - * This must always remain the first element of this structure. - */ - uint32_t saved_data_size; - #if PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM - /** - * Checksum complement to satisfy bootloader requirements. This ensures - * that words in the scanned area still add up to precisely 0 after user - * data was written. - */ - volatile uint32_t checksum_complement; - #endif /** * End-user read-write accessible data. Everything after this is also * user-readable but not writable. @@ -92,10 +79,14 @@ typedef struct { } pbsys_storage_data_map_t; /** - * Map of loaded data. + * Map of loaded data. This is kept in memory between successive runs of the + * end user application (like MicroPython). * - * Throughout this file, "ROM" is used to indicate - * non-volatile storage such as internal or external flash. + * When any of this pbsys code is running, we know that pbdrv initialization + * has completed, which has preloaded stored data into RAM. + * + * NB: We assume that the reference to 'map' below has been set by pbsys init + * and that we can use it safely throughout, so we will not check for NULL. * * ▲ text / other * │ @@ -103,30 +94,14 @@ typedef struct { * │ * ram │ size info and settings ▲ saved on ▲ * │ │ poweroff │ - * │ user program(s) ▼ to "rom" │ pbsys_user_ram_data_map + * │ user program(s) ▼ to flash │ user RAM * │ │ - * │ remaining user ram: application heap ▼ + * │ remaining application heap ▼ * │ * ▼ stack * - * The pbsys_user_ram_data_map union ensures that pbsys_storage_data_map_t.program_data - * has the correct size. */ -static union { - /** Fully saved to ROM on poweroff. */ - pbsys_storage_data_map_t data_map; - /** This RAM component contains several consecutive user program blobs. - * Those programs are saved to ROM. The remaining user heap is not saved. */ - uint8_t _size_placeholder[PBSYS_CONFIG_STORAGE_RAM_SIZE]; -} pbsys_user_ram_data_map __attribute__((section(".noinit"), used)); - -// Application RAM must enough to load ROM and still do something useful. -#if PBSYS_CONFIG_STORAGE_RAM_SIZE < PBSYS_CONFIG_STORAGE_ROM_SIZE + 2048 -#error "Application RAM must be at least ROM size + 2K." -#endif - -static pbsys_storage_data_map_t *map = &pbsys_user_ram_data_map.data_map; -static bool data_map_is_loaded = false; +static pbsys_storage_data_map_t *map; static bool data_map_write_on_shutdown = false; /** @@ -151,7 +126,7 @@ uint32_t pbsys_storage_get_maximum_program_size(void) { // FIXME: This is the total size of *all* slots. This is not a // good indicator of the free space for multi-slot hubs. We need to inform // the host dynamically about the available size of the current slot. - return PBSYS_CONFIG_STORAGE_ROM_SIZE - sizeof(pbsys_storage_data_map_t); + return pbdrv_block_device_get_writable_size() - sizeof(pbsys_storage_data_map_t); } /** @@ -160,9 +135,6 @@ uint32_t pbsys_storage_get_maximum_program_size(void) { * @returns The user settings or NULL if they are not yet loaded. */ pbsys_storage_settings_t *pbsys_storage_settings_get_settings(void) { - if (!data_map_is_loaded) { - return NULL; - } return &map->settings; } @@ -241,40 +213,6 @@ pbio_error_t pbsys_storage_get_user_data(uint32_t offset, uint8_t **data, uint32 return PBIO_SUCCESS; } -#if PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM -// Updates checksum in data map to satisfy bootloader requirements. -// NB: saved_data_size must be set before calculating this. -static void pbsys_storage_update_checksum(void) { - - // Align writable data by a double word, to simplify checksum - // computation and storage drivers that write double words. - while (map->saved_data_size % 8) { - *((uint8_t *)map + map->saved_data_size++) = 0; - } - - // The area scanned by the bootloader adds up to 0 when all user data - // is 0xFFFFFFFF. So the bootloader value up until just before the user - // data is always 0 + the number of words in the scanned user data. - extern uint32_t _pbsys_storage_checked_size; - uint32_t checksize = (uint32_t)&_pbsys_storage_checked_size; - uint32_t checksum = checksize / sizeof(uint32_t); - - // Don't count existing value. - map->checksum_complement = 0; - - // Add checksum for each word in the written data and empty checked size. - for (uint32_t offset = 0; offset < checksize; offset += sizeof(uint32_t)) { - uint32_t *word = (uint32_t *)((uint8_t *)map + offset); - // Assume that everything after written data is erased by the block - // device driver prior to writing. - checksum += offset < map->saved_data_size ? *word : 0xFFFFFFFF; - } - - // Set the checksum complement to cancel out user data checksum. - map->checksum_complement = 0xFFFFFFFF - checksum + 1; -} -#endif // PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM - static pbio_error_t pbsys_storage_prepare_receive(void) { #if PBSYS_CONFIG_STORAGE_NUM_SLOTS == 1 @@ -429,48 +367,7 @@ void pbsys_storage_get_program_data(pbsys_main_program_t *program) { // User ram starts after the last slot. program->user_ram_start = map->program_data + pbsys_storage_get_used_program_data_size(); - program->user_ram_end = ((void *)&pbsys_user_ram_data_map) + sizeof(pbsys_user_ram_data_map); -} - -/** - * This process loads data from storage on boot. - */ -static pbio_error_t pbsys_storage_init_process_thread(pbio_os_state_t *state, void *context) { - - pbio_error_t err; - - static pbio_os_state_t sub; - - PBIO_OS_ASYNC_BEGIN(state); - - // Read size of stored data. - PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_read(&sub, 0, (uint8_t *)map, sizeof(map->saved_data_size))); - if (err != PBIO_SUCCESS) { - return err; - } - - // Read the available data into RAM. - PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_read(&sub, 0, (uint8_t *)map, map->saved_data_size)); - - bool is_bad_version = strncmp(map->stored_firmware_hash, pbsys_main_get_application_version_hash(), sizeof(map->stored_firmware_hash)); - - // Test that storage successfully loaded and matches current firmware, - // otherwise reset storage. - if (err != PBIO_SUCCESS || is_bad_version) { - pbsys_storage_reset_storage(); - } - - // Apply loaded settings as necesary. - pbsys_storage_settings_apply_loaded_settings(&map->settings); - data_map_is_loaded = true; - - // Poke processes that await on system settings to become available. - pbio_os_request_poll(); - - // Initialization done. - pbsys_init_busy_down(); - - PBIO_OS_ASYNC_END(PBIO_SUCCESS); + program->user_ram_end = ((void *)map) + PBDRV_CONFIG_BLOCK_DEVICE_RAM_SIZE; } /** @@ -482,17 +379,14 @@ static pbio_error_t pbsys_storage_deinit_process_thread(pbio_os_state_t *state, static pbio_os_state_t sub; - PBIO_OS_ASYNC_BEGIN(state); + static uint32_t write_size; - map->saved_data_size = sizeof(pbsys_storage_data_map_t) + pbsys_storage_get_used_program_data_size(); + PBIO_OS_ASYNC_BEGIN(state); - #if PBSYS_CONFIG_STORAGE_OVERLAPS_BOOTLOADER_CHECKSUM - pbsys_storage_update_checksum(); - #endif + write_size = sizeof(pbsys_storage_data_map_t) + pbsys_storage_get_used_program_data_size(); // Write the data. - PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_store(&sub, (uint8_t *)map, map->saved_data_size)); - + PBIO_OS_AWAIT(state, &sub, err = pbdrv_block_device_write_all(&sub, write_size)); // Deinitialization done. pbsys_init_busy_down(); @@ -500,15 +394,21 @@ static pbio_error_t pbsys_storage_deinit_process_thread(pbio_os_state_t *state, PBIO_OS_ASYNC_END(err); } - -static pbio_os_process_t pbsys_storage_init_process; - /** * Starts loading the user data from storage to RAM. */ void pbsys_storage_init(void) { - pbsys_init_busy_up(); - pbio_os_process_start(&pbsys_storage_init_process, pbsys_storage_init_process_thread, NULL); + + pbio_error_t err = pbdrv_block_device_get_data((uint8_t **)&map); + + // Test that storage successfully loaded and matches current firmware, + // otherwise reset storage. + if (err != PBIO_SUCCESS || strncmp(map->stored_firmware_hash, pbsys_main_get_application_version_hash(), sizeof(map->stored_firmware_hash))) { + pbsys_storage_reset_storage(); + } + + // Apply loaded settings as necesary. + pbsys_storage_settings_apply_loaded_settings(&map->settings); } static pbio_os_process_t pbsys_storage_deinit_process; @@ -518,8 +418,8 @@ static pbio_os_process_t pbsys_storage_deinit_process; */ void pbsys_storage_deinit(void) { - // If loading failed or writing not requested, don't write. - if (pbsys_storage_init_process.err != PBIO_SUCCESS || !data_map_write_on_shutdown) { + // If writing not requested, don't write. + if (!data_map_write_on_shutdown) { return; }