diff --git a/F3:F303/InterfaceBoard/Makefile b/F3:F303/InterfaceBoard/Makefile index 6659b99..c98577d 100644 --- a/F3:F303/InterfaceBoard/Makefile +++ b/F3:F303/InterfaceBoard/Makefile @@ -3,6 +3,7 @@ BINARY := multiiface MCU := F303xc # change this linking script depending on particular MCU model, LDSCRIPT := stm32f303xB.ld +# add define "-DSPIDMA" to use DMA for SSI encoder, in that case IF1 (USART3) would be interrupt-driven! DEFINES := -DUSB1_16 include ../makefile.f3 diff --git a/F3:F303/InterfaceBoard/Readme.md b/F3:F303/InterfaceBoard/Readme.md index 1a43a71..0d747be 100644 --- a/F3:F303/InterfaceBoard/Readme.md +++ b/F3:F303/InterfaceBoard/Readme.md @@ -104,7 +104,7 @@ Interfaces: DMA1 channels: -- Ch2: USART3_Tx +- Ch2: USART3_Tx or SPI1_Rx - Ch3: USART3_Rx - Ch4: USART1_Tx - Ch5: USART1_Rx diff --git a/F3:F303/InterfaceBoard/hardware.c b/F3:F303/InterfaceBoard/hardware.c index e6c2623..b00ad51 100644 --- a/F3:F303/InterfaceBoard/hardware.c +++ b/F3:F303/InterfaceBoard/hardware.c @@ -22,7 +22,7 @@ uint8_t Config_mode = 0; static inline void gpio_setup(){ RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_GPIOCEN | RCC_AHBENR_GPIODEN - | RCC_AHBENR_DMA1EN | RCC_AHBENR_DMA2EN; + | RCC_AHBENR_DMA1EN | RCC_AHBENR_DMA2EN | RCC_APB2ENR_SPI1EN; RCC->APB1ENR |= RCC_APB1ENR_CANEN | RCC_APB1ENR_USART2EN | RCC_APB1ENR_USART3EN | RCC_APB1ENR_UART4EN | RCC_APB1ENR_UART5EN; RCC->APB2ENR |= RCC_APB2ENR_USART1EN; diff --git a/F3:F303/InterfaceBoard/main.c b/F3:F303/InterfaceBoard/main.c index 89447ad..a102909 100644 --- a/F3:F303/InterfaceBoard/main.c +++ b/F3:F303/InterfaceBoard/main.c @@ -22,6 +22,7 @@ #include "flash.h" #include "hardware.h" #include "proto.h" +#include "spi.h" #include "strfunc.h" #include "usart.h" #include "usb_dev.h" @@ -48,6 +49,7 @@ int main(void){ USBPU_OFF(); USB_setup(); CAN_setup(the_conf.CANspeed); + spi_setup(); //uint32_t ctr = Tms; //usb_LineCoding lc = {9600, 0, 0, 8}; //for(int i = 0; i < 5; ++i) usart_config(i, &lc); // configure all U[S]ARTs for default data @@ -63,13 +65,21 @@ int main(void){ int l = USB_receive(i, (uint8_t*)inbuff, MAXSTRLEN); if(l) USB_send(i, (uint8_t*)inbuff, l); }*/ - if(CDCready[ICAN]){ + if(CDCready[ICAN]){ // process CAN bus int l = USB_receivestr(ICAN, inbuff, MAXSTRLEN); if(l < 0) USB_sendstr(ICAN, "ERROR: USB buffer overflow or string was too long\n"); else if(l) CANcmd_parser(inbuff); canproto_process(); } - + if(CDCready[ISPI]){ // process encoder + if(USB_rcvlen(ISPI)){ // new data in USB - ask for new encoder data + if(spi_start_enc()) USB_receive(ISPI, (uint8_t*)inbuff, MAXSTRLEN); // clear incoming data + } + uint32_t val; + if(spi_read_enc(&val)){ + USB_sendstr(ISPI, u2str(val)); + } + } if(Config_mode && CDCready[ICFG]){ /*if(Tms - ctr > 4999){ ctr = Tms; diff --git a/F3:F303/InterfaceBoard/multiiface.bin b/F3:F303/InterfaceBoard/multiiface.bin index 3ac4516..a55eb4c 100755 Binary files a/F3:F303/InterfaceBoard/multiiface.bin and b/F3:F303/InterfaceBoard/multiiface.bin differ diff --git a/F3:F303/InterfaceBoard/multiiface.creator.user b/F3:F303/InterfaceBoard/multiiface.creator.user index 0edb3ce..d728581 100644 --- a/F3:F303/InterfaceBoard/multiiface.creator.user +++ b/F3:F303/InterfaceBoard/multiiface.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/F3:F303/InterfaceBoard/multiiface.files b/F3:F303/InterfaceBoard/multiiface.files index 66efa66..9b0bdaf 100644 --- a/F3:F303/InterfaceBoard/multiiface.files +++ b/F3:F303/InterfaceBoard/multiiface.files @@ -13,6 +13,8 @@ proto.c proto.h ringbuffer.c ringbuffer.h +spi.c +spi.h strfunc.c strfunc.h usart.c diff --git a/F3:F303/InterfaceBoard/spi.c b/F3:F303/InterfaceBoard/spi.c new file mode 100644 index 0000000..23609d1 --- /dev/null +++ b/F3:F303/InterfaceBoard/spi.c @@ -0,0 +1,108 @@ +/* + * This file is part of the multiiface project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Debug.h" +#include "hardware.h" +#include "spi.h" + +typedef enum{ + SPI_NOTREADY, + SPI_IDLE, + SPI_READY, +#ifdef SPIDMA + SPI_BUSY +#endif +} spiStatus; + +static spiStatus spi_status = SPI_NOTREADY; +static uint32_t spidata = 0; + +#define WAITX(x) do{volatile uint32_t wctr = 0; while((x) && (++wctr < 360000)) IWDG->KR = IWDG_REFRESH; if(wctr==360000){ DBG("timeout"); return 0;}}while(0) + +// init SPI to work with and without DMA +// DMA1Channel2 - SPI1 Rx +void spi_setup(){ + SPI1->CR1 = 0; // clear EN + SPI1->CR2 = 0; + // SPI for SSI: PA5/PA6, without MOSI; suppose that clocking and GPIO OK (hardware.c) + RCC->APB2RSTR = RCC_APB2RSTR_SPI1RST; // reset SPI before start + RCC->APB2RSTR = 0; // clear reset + SPI1->CR1 = SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_RXONLY; // software slave management (without hardware NSS pin); RX only +#ifdef SPIDMA + // setup SPI DMA + SPI11->CR2 = SPI_CR2_RXDMAEN; + // Rx + DMA1_Channel2->CPAR = (uint32_t)&(SPI1->DR); + DMA1_Channel2->CCR = DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_TEIE; // mem inc, hw->mem, Rx complete and error interrupt + NVIC_EnableIRQ(DMA1_Channel2_IRQn); // enable Rx interrupt +#endif + // Master, baudrate = 0b110 - fpclk/128 (562.5 kHz) + SPI1->CR1 |= SPI_CR1_MSTR | SPI_CR1_BR_2 | SPI_CR1_BR_1; + // DS=8bit; RXNE generates after 8bit of data in FIFO; + SPI1->CR2 |= SPI_CR2_FRXTH | SPI_CR2_DS_2|SPI_CR2_DS_1|SPI_CR2_DS_0; + spi_status = SPI_IDLE; + DBG("SPI setup OK"); +} + +// return TRUE if data ready and change `encval` +int spi_read_enc(uint32_t *encval){ + if(spi_status != SPI_READY) return FALSE; + spi_status = SPI_IDLE; +#ifndef SPIDMA + // clear SPI Rx FIFO + for(int i = 0; i < 4; ++i) (void) SPI1->DR; + SPI1->CR1 |= SPI_CR1_SPE; + uint8_t *data = (uint8_t*) &spidata; + for(uint32_t x = 0; x < ENCODERBYTES; ++x){ + if(x == ENCODERBYTES - 1) SPI1->CR1 &= ~SPI_CR1_RXONLY; // clear RXonly bit to stop CLK generation after next byte + WAITX(!(SPI1->SR & SPI_SR_RXNE)); + data[x] = *((volatile uint8_t*)&SPI1->DR); + } + SPI1->CR1 &= ~SPI_CR1_SPE; + SPI1->CR1 |= SPI_CR1_RXONLY; // and return RXonly bit +#endif + if(encval) *encval = spidata; + return TRUE; +} + +#ifdef SPIDMA +// start encoder reading over DMA +// @return FALSE if SPI is busy +int spi_start_enc(){ + if(spi_status == SPI_BUSY || spi_status == SPI_NOTREADY) return FALSE; + if(SPI1->SR & SPI_SR_BSY) return FALSE; + DMA1_Channel2->CMAR = (uint32_t) &spidata; + DMA1_Channel2->CNDTR = 4; + DMA1_Channel2->CCR |= DMA_CCR_EN; + SPI1->CR1 |= SPI_CR1_SPE; + spi_status = SPI_BUSY; + return TRUE; +} + +// SSI got fresh data +void dma1_channel2_isr(){ + SPI1->CR1 &= ~SPI_CR1_SPE; + spi_status = SPI_READY; // ready independent on errors or Rx ready +} +#else +int spi_start_enc(){ // simple stub + if(spi_status == SPI_NOTREADY) return FALSE; + spi_status = SPI_READY; // user asks to read data + return TRUE; +} +#endif diff --git a/F3:F303/InterfaceBoard/spi.h b/F3:F303/InterfaceBoard/spi.h new file mode 100644 index 0000000..72d78a7 --- /dev/null +++ b/F3:F303/InterfaceBoard/spi.h @@ -0,0 +1,27 @@ +/* + * This file is part of the multiiface project. + * Copyright 2026 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +// amount of bytes to read from encoder +#define ENCODERBYTES 4 + +void spi_setup(); +int spi_start_enc(); +int spi_read_enc(uint32_t *encval); diff --git a/F3:F303/InterfaceBoard/usart.c b/F3:F303/InterfaceBoard/usart.c index c1a247a..6226a8b 100644 --- a/F3:F303/InterfaceBoard/usart.c +++ b/F3:F303/InterfaceBoard/usart.c @@ -52,7 +52,11 @@ typedef struct { // IF6[5]: (CAN) // IF7[6]: (SPI) static const USART_Config UC[USARTSNO] = { +#ifdef SPIDMA + [0] = {.instance = USART3, .pclk_freq = 36000000, .UIRQn = USART3_IRQn, .dma_controller = NULL, .DEport = GPIOB, .DEpin = 1<<14 }, +#else [0] = {.instance = USART3, .pclk_freq = 36000000, .UIRQn = USART3_IRQn, .DIRQn = DMA1_Channel2_IRQn, .dma_controller = DMA1, .dma_rx_channel = DMA1_Channel3, .dma_tx_channel = DMA1_Channel2, .TTCflag = DMA_ISR_TCIF2, .DEport = GPIOB, .DEpin = 1<<14 }, +#endif [1] = {.instance = USART1, .pclk_freq = 72000000, .UIRQn = USART1_IRQn, .DIRQn = DMA1_Channel4_IRQn, .dma_controller = DMA1, .dma_rx_channel = DMA1_Channel5, .dma_tx_channel = DMA1_Channel4, .TTCflag = DMA_ISR_TCIF4, .DEport = GPIOB, .DEpin = 1<<0 }, [2] = {.instance = USART2, .pclk_freq = 36000000, .UIRQn = USART2_IRQn, .DIRQn = DMA1_Channel7_IRQn, .dma_controller = DMA1, .dma_rx_channel = DMA1_Channel6, .dma_tx_channel = DMA1_Channel7, .TTCflag = DMA_ISR_TCIF7, .DEport = GPIOA, .DEpin = 1<<1 }, [3] = {.instance = UART4, .pclk_freq = 36000000, .UIRQn = UART4_IRQn, .DIRQn = DMA2_Channel5_IRQn, .dma_controller = DMA2, .dma_rx_channel = DMA2_Channel3, .dma_tx_channel = DMA2_Channel5, .TTCflag = DMA_ISR_TCIF5 }, @@ -410,7 +414,9 @@ void uart4_exti34_isr(){ usart_isr(3); } void uart5_exti35_isr(){ usart_isr(4); } // DMA Tx interrupts (to arm ready flag) +#ifndef SPIDMA void dma1_channel2_isr(){ TXrdy[0] = 1; DMA1->IFCR = DMA_IFCR_CTCIF2; } +#endif void dma1_channel4_isr(){ TXrdy[1] = 1; DMA1->IFCR = DMA_IFCR_CTCIF4; } void dma1_channel7_isr(){ TXrdy[2] = 1; DMA1->IFCR = DMA_IFCR_CTCIF7; } void dma2_channel5_isr(){ TXrdy[3] = 1; DMA2->IFCR = DMA_IFCR_CTCIF5; } diff --git a/F3:F303/InterfaceBoard/usb_dev.c b/F3:F303/InterfaceBoard/usb_dev.c index 0bc3cd5..3fa8ab8 100644 --- a/F3:F303/InterfaceBoard/usb_dev.c +++ b/F3:F303/InterfaceBoard/usb_dev.c @@ -303,6 +303,10 @@ int USB_sendstr(uint8_t ifno, const char *string){ return USB_send(ifno, (const uint8_t*)string, len); } +int USB_rcvlen(uint8_t ifno){ + return RB_datalen((ringbuffer*)&rbin[ifno]); +} + /** * @brief USB_receive - get binary data from receiving ring-buffer * @param buf (i) - buffer for received data @@ -336,7 +340,7 @@ int USB_receivestr(uint8_t ifno, char *buf, int len){ } int l = RB_readto((ringbuffer*)&rbin[ifno], '\n', (uint8_t*)buf, len); if(l < 1){ - if(rbin[ifno].length == RB_datalen((ringbuffer*)&rbin[ifno])){ // buffer is full but no '\n' found + if(rbin[ifno].length >= RB_datalen((ringbuffer*)&rbin[ifno]) - 1){ // buffer is full but no '\n' found while(1 != RB_clearbuf((ringbuffer*)&rbin[ifno])); return -1; } diff --git a/F3:F303/InterfaceBoard/usb_dev.h b/F3:F303/InterfaceBoard/usb_dev.h index 303d30e..1b50818 100644 --- a/F3:F303/InterfaceBoard/usb_dev.h +++ b/F3:F303/InterfaceBoard/usb_dev.h @@ -61,6 +61,7 @@ int USB_send(uint8_t ifno, const uint8_t *buf, int len); //int USB_adddata(uint8_t ifno, const uint8_t *buf, int len); int USB_putbyte(uint8_t ifno, uint8_t byte); int USB_sendstr(uint8_t ifno, const char *string); +int USB_rcvlen(uint8_t ifno); int USB_receive(uint8_t ifno, uint8_t *buf, int len); int USB_receivestr(uint8_t ifno, char *buf, int len); uint8_t IFconfig(uint8_t ifno, usb_LineCoding *l); diff --git a/F3:F303/InterfaceBoard/version.inc b/F3:F303/InterfaceBoard/version.inc index 30fb985..9542a9b 100644 --- a/F3:F303/InterfaceBoard/version.inc +++ b/F3:F303/InterfaceBoard/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "151" -#define BUILD_DATE "2026-02-19" +#define BUILD_NUMBER "154" +#define BUILD_DATE "2026-02-20"