diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6b8824cd..6a4b2e8d 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -40,10 +40,10 @@ jobs: run: eval "$BUILD" - name: Test python package installer run: ${{ env.RUN }} "cd /tmp/openpilot/panda && python setup.py install" - - name: Build panda STM image and bootstub - run: ${{ env.RUN }} "cd /tmp/openpilot/panda && scons" - - name: Build pedal STM image and bootstub - run: ${{ env.RUN }} "cd /tmp/openpilot/panda && PEDAL=1 scons" + - name: Build panda + pedal images and bootstub + run: ${{ env.RUN }} "cd /tmp/openpilot/panda && scons -j4" + - name: Build panda with SPI support + run: ${{ env.RUN }} "cd /tmp/openpilot/panda && ENABLE_SPI=1 scons -j4" unit_tests: name: unit tests @@ -66,7 +66,7 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Run safety tests - timeout-minutes: 3 + timeout-minutes: 4 run: | ${{ env.RUN }} "cd /tmp/openpilot && \ scons -c && \ diff --git a/board/SConscript b/board/SConscript index 634c8178..6eeadab5 100644 --- a/board/SConscript +++ b/board/SConscript @@ -152,6 +152,9 @@ for project_name in build_projects: "-std=gnu11", ] + project["PROJECT_FLAGS"] + common_flags + if "ENABLE_SPI" in os.environ and not project_name.startswith('pedal'): + flags.append('-DENABLE_SPI') + project_env = Environment( ENV=os.environ, CC=PREFIX + 'gcc', diff --git a/board/boards/dos.h b/board/boards/dos.h index d0b00ef0..68c19d78 100644 --- a/board/boards/dos.h +++ b/board/boards/dos.h @@ -139,6 +139,15 @@ void dos_init(void) { set_gpio_output(GPIOC, 10, 1); set_gpio_output(GPIOC, 11, 1); +#ifdef ENABLE_SPI + // A4-A7: SPI + set_gpio_alternate(GPIOA, 4, GPIO_AF5_SPI1); + set_gpio_alternate(GPIOA, 5, GPIO_AF5_SPI1); + set_gpio_alternate(GPIOA, 6, GPIO_AF5_SPI1); + set_gpio_alternate(GPIOA, 7, GPIO_AF5_SPI1); + register_set_bits(&(GPIOA->OSPEEDR), GPIO_OSPEEDER_OSPEEDR4 | GPIO_OSPEEDER_OSPEEDR5 | GPIO_OSPEEDER_OSPEEDR6 | GPIO_OSPEEDER_OSPEEDR7); +#endif + // C8: FAN PWM aka TIM3_CH3 set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3); diff --git a/board/bootstub_declarations.h b/board/bootstub_declarations.h index 8e9f0731..06a24a2b 100644 --- a/board/bootstub_declarations.h +++ b/board/bootstub_declarations.h @@ -3,6 +3,7 @@ void puts(const char *a){ UNUSED(a); } void puth(uint8_t i){ UNUSED(i); } void puth2(uint8_t i){ UNUSED(i); } void puth4(uint8_t i){ UNUSED(i); } +void hexdump(const void *a, int l){ UNUSED(a); UNUSED(l); } typedef struct board board; typedef struct harness_configuration harness_configuration; // No CAN support on bootloader diff --git a/board/drivers/spi.h b/board/drivers/spi.h new file mode 100644 index 00000000..ae91de49 --- /dev/null +++ b/board/drivers/spi.h @@ -0,0 +1,37 @@ +#pragma once + +#define SPI_BUF_SIZE 1024U +uint8_t spi_buf_rx[SPI_BUF_SIZE]; +uint8_t spi_buf_tx[SPI_BUF_SIZE]; + +#define SPI_CHECKSUM_START 0xABU +#define SPI_SYNC_BYTE 0x5AU +#define SPI_HACK 0x79U +#define SPI_DACK 0x85U +#define SPI_NACK 0x1FU + +// SPI states +enum { + SPI_RX_STATE_HEADER=0U, + SPI_RX_STATE_HEADER_ACK=1U, + SPI_RX_STATE_HEADER_NACK=2U, + SPI_RX_STATE_DATA_RX=3U, + SPI_RX_STATE_DATA_RX_ACK=4U, + SPI_RX_STATE_DATA_TX=5U +}; + +uint8_t spi_state = SPI_RX_STATE_HEADER; +uint8_t spi_endpoint; +uint16_t spi_data_len_mosi; +uint16_t spi_data_len_miso; + +#define SPI_HEADER_SIZE 7U + +bool check_checksum(uint8_t *data, uint16_t len) { + // TODO: can speed this up by casting the bulk to uint32_t and xor-ing the bytes afterwards + uint8_t checksum = SPI_CHECKSUM_START; + for(uint16_t i = 0U; i < len; i++){ + checksum ^= data[i]; + } + return checksum == 0U; +} diff --git a/board/faults.h b/board/faults.h index 8f6ee28f..ad938b9d 100644 --- a/board/faults.h +++ b/board/faults.h @@ -26,6 +26,7 @@ #define FAULT_INTERRUPT_RATE_CLOCK_SOURCE (1U << 20) #define FAULT_INTERRUPT_RATE_TICK (1U << 21) #define FAULT_INTERRUPT_RATE_EXTI (1U << 22) +#define FAULT_INTERRUPT_RATE_SPI (1U << 23) // Permanent faults #define PERMANENT_FAULTS 0U diff --git a/board/main.c b/board/main.c index 48ed4c7f..73eb2a79 100644 --- a/board/main.c +++ b/board/main.c @@ -375,6 +375,10 @@ int main(void) { // enable USB (right before interrupts or enum can fail!) usb_init(); +#ifdef ENABLE_SPI + spi_init(); +#endif + puts("**** INTERRUPTS ON ****\n"); enable_interrupts(); diff --git a/board/main_declarations.h b/board/main_declarations.h index c1fc8703..208afadf 100644 --- a/board/main_declarations.h +++ b/board/main_declarations.h @@ -3,6 +3,7 @@ void puts(const char *a); void puth(unsigned int i); void puth2(unsigned int i); void puth4(unsigned int i); +void hexdump(const void *a, int l); typedef struct board board; typedef struct harness_configuration harness_configuration; void can_flip_buses(uint8_t bus1, uint8_t bus2); diff --git a/board/stm32fx/llspi.h b/board/stm32fx/llspi.h index d07bce35..83b65d93 100644 --- a/board/stm32fx/llspi.h +++ b/board/stm32fx/llspi.h @@ -1,131 +1,176 @@ -// IRQs: DMA2_Stream2, DMA2_Stream3, EXTI4 - -void spi_init(void); -int spi_cb_rx(uint8_t *data, int len, uint8_t *data_out); - -// end API - -#define SPI_BUF_SIZE 256 -uint8_t spi_buf[SPI_BUF_SIZE]; -int spi_buf_count = 0; -int spi_total_count = 0; - -void spi_tx_dma(void *addr, int len) { +void spi_miso_dma(uint8_t *addr, int len) { // disable DMA + DMA2_Stream3->CR &= ~DMA_SxCR_EN; register_clear_bits(&(SPI1->CR2), SPI_CR2_TXDMAEN); - register_clear_bits(&(DMA2_Stream3->CR), DMA_SxCR_EN); - // DMA2, stream 3, channel 3 + // setup source and length register_set(&(DMA2_Stream3->M0AR), (uint32_t)addr, 0xFFFFFFFFU); DMA2_Stream3->NDTR = len; - register_set(&(DMA2_Stream3->PAR), (uint32_t)&(SPI1->DR), 0xFFFFFFFFU); - - // channel3, increment memory, memory -> periph, enable - register_set(&(DMA2_Stream3->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_EN), 0x1E077EFEU); - delay(0); - register_set_bits(&(DMA2_Stream3->CR), DMA_SxCR_TCIE); + // enable DMA register_set_bits(&(SPI1->CR2), SPI_CR2_TXDMAEN); - - // signal data is ready by driving low - // esp must be configured as input by this point - set_gpio_output(GPIOB, 0, 0); + DMA2_Stream3->CR |= DMA_SxCR_EN; } -void spi_rx_dma(void *addr, int len) { +void spi_mosi_dma(uint8_t *addr, int len) { // disable DMA register_clear_bits(&(SPI1->CR2), SPI_CR2_RXDMAEN); - register_clear_bits(&(DMA2_Stream2->CR), DMA_SxCR_EN); + DMA2_Stream2->CR &= ~DMA_SxCR_EN; // drain the bus volatile uint8_t dat = SPI1->DR; (void)dat; - // DMA2, stream 2, channel 3 + // setup destination and length register_set(&(DMA2_Stream2->M0AR), (uint32_t)addr, 0xFFFFFFFFU); DMA2_Stream2->NDTR = len; - register_set(&(DMA2_Stream2->PAR), (uint32_t)&(SPI1->DR), 0xFFFFFFFFU); - - // channel3, increment memory, periph -> memory, enable - register_set(&(DMA2_Stream2->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_EN), 0x1E077EFEU); - delay(0); - register_set_bits(&(DMA2_Stream2->CR), DMA_SxCR_TCIE); + // enable DMA + DMA2_Stream2->CR |= DMA_SxCR_EN; register_set_bits(&(SPI1->CR2), SPI_CR2_RXDMAEN); } -// ***************************** SPI IRQs ***************************** -// can't go on the stack cause it's DMAed -uint8_t spi_tx_buf[0x44]; - -// SPI RX +// SPI MOSI DMA FINISHED void DMA2_Stream2_IRQ_Handler(void) { - int *resp_len = (int*)spi_tx_buf; - (void)memset(spi_tx_buf, 0xaa, 0x44); - *resp_len = spi_cb_rx(spi_buf, 0x14, spi_tx_buf+4); - #ifdef DEBUG_SPI - puts("SPI write: "); - puth(*resp_len); - puts("\n"); - #endif - spi_tx_dma(spi_tx_buf, *resp_len + 4); - - // ack + // Clear interrupt flag + ENTER_CRITICAL(); DMA2->LIFCR = DMA_LIFCR_CTCIF2; -} -// SPI TX -void DMA2_Stream3_IRQ_Handler(void) { - #ifdef DEBUG_SPI - puts("SPI handshake\n"); - #endif + uint8_t next_rx_state = SPI_RX_STATE_HEADER; - // reset handshake back to pull up - set_gpio_mode(GPIOB, 0, MODE_INPUT); - set_gpio_pullup(GPIOB, 0, PULL_UP); + // parse header + spi_endpoint = spi_buf_rx[1]; + spi_data_len_mosi = spi_buf_rx[3] << 8 | spi_buf_rx[2]; + spi_data_len_miso = spi_buf_rx[5] << 8 | spi_buf_rx[4]; - // ack - DMA2->LIFCR = DMA_LIFCR_CTCIF3; -} + if (spi_state == SPI_RX_STATE_HEADER) { + if (spi_buf_rx[0] == SPI_SYNC_BYTE && check_checksum(spi_buf_rx, SPI_HEADER_SIZE)) { + // response: ACK and start receiving data portion + spi_buf_tx[0] = SPI_HACK; + next_rx_state = SPI_RX_STATE_HEADER_ACK; + } else { + // response: NACK and reset state machine + puts("SPI: incorrect header sync or checksum "); /*hexdump(spi_buf_rx, SPI_HEADER_SIZE);*/ + spi_buf_tx[0] = SPI_NACK; + next_rx_state = SPI_RX_STATE_HEADER_NACK; + } + spi_miso_dma(spi_buf_tx, 1); + } else if (spi_state == SPI_RX_STATE_DATA_RX) { + // We got everything! Based on the endpoint specified, call the appropriate handler + uint16_t response_len = 0U; + bool reponse_ack = false; + if (check_checksum(spi_buf_rx + SPI_HEADER_SIZE, spi_data_len_mosi + 1)) { + if (spi_endpoint == 0U) { + if (spi_data_len_mosi >= 8U) { + response_len = comms_control_handler((ControlPacket_t *)(spi_buf_rx + SPI_HEADER_SIZE), spi_buf_tx + 3); + reponse_ack = true; + } else { + puts("SPI: insufficient data for control handler\n"); + } + } else if (spi_endpoint == 1U || spi_endpoint == 0x81U) { + if (spi_data_len_mosi == 0U) { + response_len = comms_can_read(spi_buf_tx + 3, spi_data_len_miso); + reponse_ack = true; + } else { + puts("SPI: did not expect data for can_read\n"); + } + } else if (spi_endpoint == 2U) { + comms_endpoint2_write(spi_buf_rx + SPI_HEADER_SIZE, spi_data_len_mosi); + reponse_ack = true; + } else if (spi_endpoint == 3U) { + if (spi_data_len_mosi > 0U) { + comms_can_write(spi_buf_rx + SPI_HEADER_SIZE, spi_data_len_mosi); + reponse_ack = true; + } else { + puts("SPI: did expect data for can_write\n"); + } + } + } else { + // Checksum was incorrect + reponse_ack = false; + puts("SPI: incorrect data checksum\n"); + } -void EXTI4_IRQ_Handler(void) { - volatile unsigned int pr = EXTI->PR & (1U << 4); - #ifdef DEBUG_SPI - puts("exti4\n"); - #endif - // SPI CS falling - if ((pr & (1U << 4)) != 0U) { - spi_total_count = 0; - spi_rx_dma(spi_buf, 0x14); + // Setup response header + spi_buf_tx[0] = reponse_ack ? SPI_DACK : SPI_NACK; + spi_buf_tx[1] = response_len & 0xFFU; + spi_buf_tx[2] = (response_len >> 8) & 0xFFU; + + // Add checksum + uint8_t checksum = SPI_CHECKSUM_START; + for(uint16_t i = 0U; i < response_len + 3; i++) { + checksum ^= spi_buf_tx[i]; + } + spi_buf_tx[response_len + 3] = checksum; + + // Write response + spi_miso_dma(spi_buf_tx, response_len + 4); + + next_rx_state = SPI_RX_STATE_DATA_TX; + } else { + puts("SPI: RX unexpected state: "); puth(spi_state); puts("\n"); + } + + spi_state = next_rx_state; + EXIT_CRITICAL(); +} + +// SPI MISO DMA FINISHED +void DMA2_Stream3_IRQ_Handler(void) { + // Clear interrupt flag + DMA2->LIFCR = DMA_LIFCR_CTCIF3; + + // Wait until the transaction is actually finished and clear the DR + // TODO: needs a timeout here, otherwise it gets stuck with no master clock! + while (!(SPI1->SR & SPI_SR_TXE)); + volatile uint8_t dat = SPI1->DR; + (void)dat; + SPI1->DR = 0U; + + if (spi_state == SPI_RX_STATE_HEADER_ACK) { + // ACK was sent, queue up the RX buf for the data + checksum + spi_state = SPI_RX_STATE_DATA_RX; + spi_mosi_dma(spi_buf_rx + SPI_HEADER_SIZE, spi_data_len_mosi + 1); + } else if (spi_state == SPI_RX_STATE_HEADER_NACK) { + // Reset state + spi_state = SPI_RX_STATE_HEADER; + spi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); + } else if (spi_state == SPI_RX_STATE_DATA_TX) { + // Reset state + spi_state = SPI_RX_STATE_HEADER; + spi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); + } else { + puts("SPI: TX unexpected state: "); puth(spi_state); puts("\n"); } - EXTI->PR = pr; } // ***************************** SPI init ***************************** void spi_init(void) { - // Max SPI clock the ESP can produce is 80MHz. At buffer size of 256 bytes, that's a max of about 40k buffers per second - REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, 50000U, FAULT_INTERRUPT_RATE_SPI_DMA) - REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, 50000U, FAULT_INTERRUPT_RATE_SPI_DMA) - REGISTER_INTERRUPT(EXTI4_IRQn, EXTI4_IRQ_Handler, 50000U, FAULT_INTERRUPT_RATE_SPI_CS) // TODO: Figure out if this is a reasonable limit + // We expect less than 50 transactions (including control messages and CAN buffers) at the 100Hz boardd interval. Can be raised if needed. + REGISTER_INTERRUPT(DMA2_Stream2_IRQn, DMA2_Stream2_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) + REGISTER_INTERRUPT(DMA2_Stream3_IRQn, DMA2_Stream3_IRQ_Handler, 5000U, FAULT_INTERRUPT_RATE_SPI_DMA) - //puts("SPI init\n"); + // Clear buffers (for debugging) + memset(spi_buf_rx, 0, SPI_BUF_SIZE); + memset(spi_buf_tx, 0, SPI_BUF_SIZE); + + // Setup MOSI DMA + register_set(&(DMA2_Stream2->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_TCIE), 0x1E077EFEU); + register_set(&(DMA2_Stream2->PAR), (uint32_t)&(SPI1->DR), 0xFFFFFFFFU); + + // Setup MISO DMA + register_set(&(DMA2_Stream3->CR), (DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_TCIE), 0x1E077EFEU); + register_set(&(DMA2_Stream3->PAR), (uint32_t)&(SPI1->DR), 0xFFFFFFFFU); + + // Enable SPI and the error interrupts + // TODO: verify clock phase and polarity register_set(&(SPI1->CR1), SPI_CR1_SPE, 0xFFFFU); + register_set(&(SPI1->CR2), 0U, 0xF7U); - // enable SPI interrupts - //SPI1->CR2 = SPI_CR2_RXNEIE | SPI_CR2_ERRIE | SPI_CR2_TXEIE; - register_set(&(SPI1->CR2), SPI_CR2_RXNEIE, 0xF7U); + // Start the first packet! + spi_state = SPI_RX_STATE_HEADER; + spi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); NVIC_EnableIRQ(DMA2_Stream2_IRQn); NVIC_EnableIRQ(DMA2_Stream3_IRQn); - //NVIC_EnableIRQ(SPI1_IRQn); - - // reset handshake back to pull up - set_gpio_mode(GPIOB, 0, MODE_INPUT); - set_gpio_pullup(GPIOB, 0, PULL_UP); - - // setup interrupt on falling edge of SPI enable (on PA4) - register_set(&(SYSCFG->EXTICR[2]), SYSCFG_EXTICR2_EXTI4_PA, 0xFFFFU); - register_set_bits(&(EXTI->IMR), (1U << 4)); - register_set_bits(&(EXTI->FTSR), (1U << 4)); - NVIC_EnableIRQ(EXTI4_IRQn); } diff --git a/board/stm32fx/stm32fx_config.h b/board/stm32fx/stm32fx_config.h index cbdff1da..9361f79a 100644 --- a/board/stm32fx/stm32fx_config.h +++ b/board/stm32fx/stm32fx_config.h @@ -66,6 +66,11 @@ #include "stm32fx/board.h" #include "stm32fx/clock.h" +#ifdef ENABLE_SPI + #include "drivers/spi.h" + #include "stm32fx/llspi.h" +#endif + #if !defined(BOOTSTUB) && (defined(PANDA) || defined(PEDAL_USB)) #include "drivers/uart.h" #include "stm32fx/lluart.h" diff --git a/board/stm32h7/llspi.h b/board/stm32h7/llspi.h new file mode 100644 index 00000000..f9973b49 --- /dev/null +++ b/board/stm32h7/llspi.h @@ -0,0 +1,3 @@ +void spi_init(void) { + +} diff --git a/board/stm32h7/stm32h7_config.h b/board/stm32h7/stm32h7_config.h index abaed681..eb713a0b 100644 --- a/board/stm32h7/stm32h7_config.h +++ b/board/stm32h7/stm32h7_config.h @@ -72,6 +72,11 @@ #include "stm32h7/llusb.h" +#ifdef ENABLE_SPI + #include "drivers/spi.h" + #include "stm32h7/llspi.h" +#endif + void early_gpio_float(void) { RCC->AHB4ENR = RCC_AHB4ENR_GPIOAEN | RCC_AHB4ENR_GPIOBEN | RCC_AHB4ENR_GPIOCEN | RCC_AHB4ENR_GPIODEN | RCC_AHB4ENR_GPIOEEN | RCC_AHB4ENR_GPIOFEN | RCC_AHB4ENR_GPIOGEN | RCC_AHB4ENR_GPIOHEN; GPIOA->MODER = 0; GPIOB->MODER = 0; GPIOC->MODER = 0; GPIOD->MODER = 0; GPIOE->MODER = 0; GPIOF->MODER = 0; GPIOG->MODER = 0; GPIOH->MODER = 0; diff --git a/tests/health_test.py b/tests/health_test.py index 1298d6b4..e101e2c6 100755 --- a/tests/health_test.py +++ b/tests/health_test.py @@ -5,7 +5,7 @@ from panda import Panda if __name__ == "__main__": spi = "SPI" in os.environ - print("using SPI" if spi else "using SPI") + print("using SPI" if spi else "using USB") i = 0