-
-
Notifications
You must be signed in to change notification settings - Fork 81
[EV3] Implement a SPI flash driver for the EV3 #314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
db4567a
07dbbd7
9b18eb4
5cd1e7e
ee30608
85072c6
070953d
76723c0
8b7b749
0fb8389
4d2be45
bbfa7f5
214ad51
6f88e2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,13 +9,12 @@ | |
| #include <stdint.h> | ||
| #include <string.h> | ||
|
|
||
| #include <contiki.h> | ||
|
|
||
| #include <pbdrv/adc.h> | ||
| #include <pbdrv/clock.h> | ||
| #include <pbdrv/gpio.h> | ||
|
|
||
| #include <pbio/error.h> | ||
| #include <pbio/os.h> | ||
| #include <pbio/util.h> | ||
|
|
||
| #include <tiam1808/spi.h> | ||
|
|
@@ -25,15 +24,76 @@ | |
| #include <tiam1808/hw/hw_syscfg0_AM1808.h> | ||
| #include <tiam1808/armv5/am1808/interrupt.h> | ||
|
|
||
| #include "adc_ev3.h" | ||
| #include "../drv/block_device/block_device_ev3.h" | ||
| #include "../drv/gpio/gpio_ev3.h" | ||
|
|
||
| PROCESS(pbdrv_adc_process, "ADC"); | ||
|
|
||
| #define PBDRV_CONFIG_ADC_EV3_NUM_DELAY_SAMPLES (4) | ||
|
|
||
| static volatile uint16_t channel_data[PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS + PBDRV_CONFIG_ADC_EV3_NUM_DELAY_SAMPLES] = {0}; | ||
| static volatile uint8_t channel_data_index = 0; | ||
| static volatile bool adc_busy = false; | ||
| #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; | ||
|
|
@@ -48,127 +108,99 @@ 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; | ||
| } | ||
| // Values for the requested channel are received several samples later. | ||
| // The data only appears 12-bit but the last 2 bits are always zero. | ||
| *value = (channel_data[ch + PBDRV_CONFIG_ADC_EV3_NUM_DELAY_SAMPLES] - 4096 * ch) >> 2; | ||
| // 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. | ||
|
Comment on lines
+111
to
+112
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of worrying about tearing, I would do a double buffer. Just before we call |
||
| 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 void spi0_isr(void) { | ||
| uint32_t intCode = 0; | ||
| IntSystemStatusClear(SYS_INT_SPINT0); | ||
|
|
||
| while ((intCode = SPIInterruptVectorGet(SOC_SPI_0_REGS))) { | ||
| if (intCode != SPI_TX_BUF_EMPTY) { | ||
| continue; | ||
| } | ||
| // Payload encoding comes from the original EV3 sources, but we | ||
| // use the hardware SPI peripheral instead of bit-banging. | ||
| uint16_t payload = 0x1840 | (((channel_data_index % PBDRV_CONFIG_ADC_EV3_ADC_NUM_CHANNELS) & 0x000F) << 7); | ||
| HWREG(SOC_SPI_0_REGS + SPI_SPIDAT0) = payload; | ||
| channel_data[channel_data_index] = SPIDataReceive(SOC_SPI_0_REGS); | ||
|
|
||
| if (++channel_data_index == PBIO_ARRAY_SIZE(channel_data)) { | ||
| SPIIntDisable(SOC_SPI_0_REGS, SPI_TRANSMIT_INT); | ||
| adc_busy = false; | ||
| process_poll(&pbdrv_adc_process); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| static void pbdrv_adc_exit(void) { | ||
| SPIIntDisable(SOC_SPI_0_REGS, SPI_RECV_INT | SPI_TRANSMIT_INT); | ||
| } | ||
| static pbio_os_process_t pbdrv_adc_ev3_process; | ||
|
|
||
| // ADC / Flash SPI0 data MOSI | ||
| static const pbdrv_gpio_t pin_spi0_mosi = PBDRV_GPIO_EV3_PIN(3, 15, 12, 8, 5); | ||
| pbio_error_t pbdrv_adc_ev3_process_thread(pbio_os_state_t *state, void *context) { | ||
| static pbio_os_timer_t timer; | ||
|
|
||
| // ADC / Flash SPI0 data MISO | ||
| static const pbdrv_gpio_t pin_spi0_miso = PBDRV_GPIO_EV3_PIN(3, 11, 8, 8, 6); | ||
| PBIO_OS_ASYNC_BEGIN(state); | ||
|
|
||
| // LCD SPI0 Clock | ||
| static const pbdrv_gpio_t pin_spi0_clk = PBDRV_GPIO_EV3_PIN(3, 3, 0, 1, 8); | ||
| // HACK: This waits until storage is completely done with SPI flash before we start | ||
| PBIO_OS_AWAIT_UNTIL(state, pbsys_storage_settings_get_settings()); | ||
|
|
||
| // ADC / Flash SPI0 chip select (active low). | ||
| static const pbdrv_gpio_t pin_spi0_cs = PBDRV_GPIO_EV3_PIN(3, 27, 24, 8, 2); | ||
| // Once SPI flash init is finished, there is nothing further for us to do. | ||
| // We are ready to start sampling. | ||
|
|
||
| // ADCACK PIN | ||
| static const pbdrv_gpio_t pin_adc_ack = PBDRV_GPIO_EV3_PIN(19, 19, 16, 6, 2); | ||
| pbio_os_timer_set(&timer, ADC_SAMPLE_PERIOD); | ||
|
|
||
| // ADCBATEN | ||
| static const pbdrv_gpio_t pin_adc_bat_en = PBDRV_GPIO_EV3_PIN(1, 7, 4, 0, 6); | ||
|
|
||
| void pbdrv_adc_init(void) { | ||
|
|
||
| // 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_cs, SYSCFG_PINMUX3_PINMUX3_27_24_NSPI0_SCS3); | ||
|
|
||
| pbdrv_gpio_input(&pin_adc_ack); | ||
|
|
||
| pbdrv_gpio_out_high(&pin_adc_bat_en); | ||
| for (;;) { | ||
| PBIO_OS_AWAIT_UNTIL(state, shut_down_hack || adc_soon || pbio_os_timer_is_expired(&timer)); | ||
|
|
||
| // Waking up the SPI1 instance. | ||
| PSCModuleControl(SOC_PSC_1_REGS, HW_PSC_SPI0, PSC_POWERDOMAIN_ALWAYS_ON, PSC_MDCTL_NEXT_ENABLE); | ||
| if (shut_down_hack) { | ||
| shut_down_hack_done = 1; | ||
| break; | ||
| } | ||
|
|
||
| // Register the ISR in the Interrupt Vector Table. | ||
| IntRegister(SYS_INT_SPINT0, spi0_isr); | ||
| IntChannelSet(SYS_INT_SPINT0, 2); | ||
| IntSystemEnable(SYS_INT_SPINT0); | ||
| 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; | ||
| } | ||
|
|
||
| // Reset. | ||
| SPIReset(SOC_SPI_0_REGS); | ||
| SPIOutOfReset(SOC_SPI_0_REGS); | ||
| // 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()); | ||
|
|
||
| // Mode. | ||
| uint32_t spipc0 = SPI_SPIPC0_SOMIFUN | SPI_SPIPC0_SIMOFUN | SPI_SPIPC0_CLKFUN | SPI_SPIPC0_SCS0FUN3; | ||
| SPIModeConfigure(SOC_SPI_0_REGS, SPI_MASTER_MODE); | ||
| SPIPinControl(SOC_SPI_0_REGS, 0, 0, (unsigned int *)&spipc0); | ||
| for (uint32_t i = 0; i < pbdrv_adc_callback_count; i++) { | ||
| pbdrv_adc_callbacks[i](); | ||
| } | ||
| } | ||
|
|
||
| // Config. | ||
| SPIClkConfigure(SOC_SPI_0_REGS, SOC_SYSCLK_2_FREQ, 2000000, SPI_DATA_FORMAT0); | ||
| SPIConfigClkFormat(SOC_SPI_0_REGS, SPI_CLK_OUTOFPHASE | 0x00000010, SPI_DATA_FORMAT0); | ||
| SPIDelayConfigure(SOC_SPI_0_REGS, 10, 10, 10, 10); | ||
| SPIIntLevelSet(SOC_SPI_0_REGS, SPI_RECV_INTLVL | SPI_TRANSMIT_INTLVL); | ||
| SPIDefaultCSSet(SOC_SPI_0_REGS, 8); | ||
| PBIO_OS_ASYNC_END(PBIO_SUCCESS); | ||
| } | ||
|
|
||
| // Enable and loop around all channels. | ||
| SPIEnable(SOC_SPI_0_REGS); | ||
| 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. | ||
|
|
||
| process_start(&pbdrv_adc_process); | ||
| pbio_os_process_start(&pbdrv_adc_ev3_process, pbdrv_adc_ev3_process_thread, NULL); | ||
| } | ||
|
|
||
| void pbdrv_adc_update_soon(void) { | ||
| process_poll(&pbdrv_adc_process); | ||
| adc_soon = 1; | ||
| pbio_os_request_poll(); | ||
| } | ||
|
|
||
| PROCESS_THREAD(pbdrv_adc_process, ev, data) { | ||
| PROCESS_EXITHANDLER(pbdrv_adc_exit()); | ||
|
|
||
| static struct etimer etimer; | ||
|
|
||
| PROCESS_BEGIN(); | ||
|
|
||
| etimer_set(&etimer, 10); | ||
| for (;;) { | ||
| PROCESS_WAIT_EVENT_UNTIL((ev == PROCESS_EVENT_TIMER && etimer_expired(&etimer)) || ev == PROCESS_EVENT_POLL); | ||
|
|
||
| channel_data_index = 0; | ||
| adc_busy = true; | ||
| SPIEnable(SOC_SPI_0_REGS); | ||
| SPIIntEnable(SOC_SPI_0_REGS, SPI_TRANSMIT_INT); | ||
| PROCESS_WAIT_EVENT_UNTIL(ev == PROCESS_EVENT_POLL && !adc_busy); | ||
|
|
||
| for (uint32_t i = 0; i < pbdrv_adc_callback_count; i++) { | ||
| pbdrv_adc_callbacks[i](); | ||
| } | ||
|
|
||
| etimer_reset(&etimer); | ||
| } | ||
| void pbdrv_adc_ev3_configure_data_format() { | ||
ArcaneNibble marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would also set other config explicitly here even if it is the same as flash. For example,
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which other config settings were you thinking of?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CSHOLD was the main one. I guess I copied and pasted the wrong fucntion name. But it is nice to see what the full config is all in once place without relying on the flash memory driver setting up some of this for us. (And could save us some trouble if we ever tweak the flash memory driver without realizing it also affects the ADC). |
||
|
|
||
| PROCESS_END(); | ||
| 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // 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_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to have some macros or comments to explain these bits.