DMA for SPI

Related to the the forum.
ag123
Posts: 1917
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: DMA for SPI

Post by ag123 »

updated SPI.h
https://github.com/ag88/stm32duino_spi_ ... /SPI/SPI.h
  • added externs to SPI instance (this defaults to SPI 1)
  • SPIDMA classes are different for each series and SPIDMA uses a different/specific class for each SPI peripheral.
    Typedefs are defined for SPIClass1, SPIClass2, SPIClass3 in spi.h
    This is so that codes like

    Code: Select all

    SPIClass1 spi1;
    SPIClass2 spi2;
    SPIClass3 spi3;
    
    should work, and does not need to reference the specific class name for each series and peripheral.
    Note that each SPIClassN is actually derived from SPIClass, hence they can be passed to functions / methods
    that use SPIClass for the SPI api.
    each SPIClass1, SPIClass2, SPIClass3 connects to the specific SPI peripheral 1, 2, 3 respectively
This is still untested codes, but just compiled.
If you do test that, do drop a comment about it here or in github discussions etc.

edit:
there is a 'catch' in that actually for series that do not have a SPIDMA class, it uses SPIBasic which is generic (across SPI 1, 2, 3) and follows the original SPIClass definition instead. But that if no pin parameters are given, it defaults to SPI1.
I'd need to figure out how to make the declarations similar.
edit2:
this is done, SPIClass2 spi2; and SPIClass3 spi3; now works for SPIBasic based class as well, it is done by deriving a class and passing default pins to the constructor.

SPIBasic remains as a generic SPIClass implementation that can be used for any SPI peripherals.
SPIDMA implementations are specific to each SPI peripheral, i.e. it isn't possible to use SPIClass2 for SPIClass1 and vice versa.

I'm also thinking about in the constructor to add a parameter to specify the particular SPI peripheral used (e.g. using the SPI_BASE symbol for the register base address). This would deviate from the current constructor, but that it could make defaulting behaviour simpler to implement.
Such an implementation can enable specific pins tied to the SPI peripheral by default and helps avoid usage errors binding the incorrect pins,
ag123
Posts: 1917
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: DMA for SPI

Post by ag123 »

refactor getClkFreq, SPIDMA use generic getClkFreq from SPIBasic
https://github.com/ag88/stm32duino_spi_ ... A.cpp#L300
ag123
Posts: 1917
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: DMA for SPI

Post by ag123 »

added stm32f7xx
https://github.com/ag88/stm32duino_spi_ ... DMA_F7XX.h
https://github.com/ag88/stm32duino_spi_ ... A_F7XX.cpp

this is based on stm32f722rc generic variant, this is still untested codes, just compiled

stm32f7(22rc) is practically similar to/same as f4xx for the DMA
it has both channels and streams, but unlike H7, H5, it does not use a DMA MUX.
Hence, the channels/streams are pre-bound to DMA specifically as like in F4.

stm32f7(22rc) has an SPI with FIFO, but that instead of using a different structure / register as like H5, H7 it uses DR (data register),
and similar flags as TXE (transmit buf empty), RXNE (receive buf not empty), to flag the status.
ag123
Posts: 1917
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: DMA for SPI

Post by ag123 »

added stm32L4xx
https://github.com/ag88/stm32duino_spi_ ... DMA_L4XX.h
https://github.com/ag88/stm32duino_spi_ ... A_L4XX.cpp

this is based on stm32L431cc generic variant, this is still untested codes, just compiled

stm32L4(31cc) is again different, it looks a little like stm32f3xx, but different. There is a mux at each channel, which CubeMX generaged codes call it a 'request', yet this is different from streams as like those in F4xx which are parallel. But in L4xx, I think it is a single DMA thread, just that the priorities are different. I'd guess it is partly as it is after all a 'low power' mcu. Hence, using parallel streams would likely consume more power.

stm32L4(31cc) SPI peripheral is similar to F7, in that it has a FIFO and unlike H5, H7 which uses different register design for FIFO.
stm32L4(31cc) SPI uses the 'old' F4 style DR register and TXE (transmit buf empty), RXNE (receive buf not empty) flags.
I'd guess the FIFO if used correctly could help with latency issues. But that the current implementation is 'simple' in a sense that it is just reading the byte (buffer) in the same way as if it is f4/f3 etc.
ag123
Posts: 1917
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: DMA for SPI

Post by ag123 »

edit:
updated Readme.md
https://github.com/ag88/stm32duino_spi_dma
--
did a round of spihandle bug fixes
https://github.com/ag88/stm32duino_spi_ ... 0436f19a90

another round of bug fixes
https://github.com/ag88/stm32duino_spi_ ... d94fa76925
^ well, to get differentially faster speeds, I hit the registers this time round, and all the hairy bummers surface
avoiding stacking and unstacking by avoiding function calls can save quite a bit of cpu cycles when transmitting / receiving a whole buffer of data.

I'd stop changing or adding things (e.g. other series) at least for a while as after all, I've yet to test anything.
And that stm32{F,G,H,L}xxyy are 'cherry picked' single soc variants used to build and compile the codes.
It is unknown if within the series if they may vary.

The assumption is that 'cherry picking' a smaller mcu in the series, for the build, and 'bigger' chips would likely have same/similar architecture for the spi and dma peripherals, but still it is a guess.
trimarco232
Posts: 44
Joined: Wed Jul 12, 2023 11:09 am
Answers: 1

Re: DMA for SPI

Post by trimarco232 »

Hi,
I recently wrote a piece of code using DMA and SPI , the goal is to command WS2812b chips , using just a MOSI output of a BluePill+ (WeAct , hope this is an original STM32F103C8T6)
I am writing directly into the registers , so I know exactly what I am doing , at least I hope so.
But here , I had to set the SPI_CR2_SSOE bit , in order to finally make this to work ... and I don't know why ?

Code: Select all

/* spi_dma_ws.h */


void spi_dma_transmit();

extern uint16_t adc_pa5;

// some colours
const uint8_t ws_off[3] = { 0, 0, 0 };
const uint8_t ws_green[3] = { 25, 0, 0 };
const uint8_t ws_red[3] = { 0, 25, 0 };
const uint8_t ws_blue[3] = { 0, 0, 25 };
const uint8_t ws_white[3] = { 25, 25, 25 };
const uint8_t ws_purple[3] = { 0, 25, 25 };
const uint8_t ws_yell[3] = { 25, 25, 0 };
const uint8_t ws_turc[3] = { 25, 0, 25 };


uint8_t ws3[nb_ws][3];        // has the 3 colours of each RGB chip
uint8_t ws33[nb_ws * 3 * 3];  // has the 3 SPI bytes for each the 3 colours of each RGB chip
///  example with some colours
/*
void ws_3() {
  for (int c = 0; c < 3; c++) {
    ws3[0][c] = ws_green[c];
    ws3[1][c] = ws_red[c];
    ws3[2][c] = ws_blue[c];
    ws3[3][c] = ws_white[c];
    ws3[4][c] = ws_off[c];
    ws3[5][c] = ws_yell[c];
    ws3[6][c] = ws_turc[c];
    ws3[7][c] = ws_purple[c];
  }
}
*/
void ws3_green(uint8_t ws_chip) {
    for (int c = 0; c < 3; c++) {
    ws3[ws_chip-1][c] = ws_green[c];
  }
}
void ws3_red(uint8_t ws_chip) {
  for (int c = 0; c < 3; c++) {
    ws3[ws_chip-1][c] = ws_red[c];
  }
}
void ws3_blue(uint8_t ws_chip) {
  for (int c = 0; c < 3; c++) {
    ws3[ws_chip-1][c] = ws_blue[c];
  }
}
void ws3_white(uint8_t ws_chip) {
  for (int c = 0; c < 3; c++) {
    ws3[ws_chip-1][c] = ws_green[c];
  }
}
void ws3_off(uint8_t ws_chip) {
  for (int c = 0; c < 3; c++) {
    ws3[ws_chip-1][c] = 0;
  }
}
void ws3_yell(uint8_t ws_chip) {
  for (int c = 0; c < 3; c++) {
    ws3[ws_chip-1][c] = ws_yell[c];
  }
}
void ws3_turc(uint8_t ws_chip) {
  for (int c = 0; c < 3; c++) {
    ws3[ws_chip-1][c] = ws_turc[c];
  }
}
void ws3_purple(uint8_t ws_chip) {
  for (int c = 0; c < 3; c++) {
    ws3[ws_chip-1][c] = ws_purple[c];
  }
}

//  7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
// |‾|_._|‾|_._|‾|_._|‾|_._|‾|_._|‾|_._|‾|_._|‾|_._  // 146 73 36
//  128   64    32    16    8     4     2     1
// |‾.‾|_|‾.‾|_|‾.‾|_|‾.‾|_|‾.‾|_|‾.‾|_|‾.‾|_|‾.‾|_  // 219 109 182
void ws_33() {

  uint8_t p = 0, w;
  ///ws_3();
  ///ws_green_adc[0] = adc_pa5 / 4;
  for (int ws = 0; ws < sizeof(ws3) / 3; ws++) {
    for (int c = 0; c < 3; c++) {
      w = ws3[ws][c];
      ws33[p] = 0b10010010;
      if (w & 128) ws33[p] += 0b01000000;
      if (w & 64) ws33[p] += 0b00001000;
      if (w & 32) ws33[p] += 1;
      p++;
      ws33[p] = 0b01001001;
      if (w & 16) ws33[p] += 0b00100000;
      if (w & 8) ws33[p] += 0b00000100;
      p++;
      ws33[p] = 0b00100100;
      if (w & 4) ws33[p] += 0b10000000;
      if (w & 2) ws33[p] += 0b00010000;
      if (w & 1) ws33[p] += 0b00000010;
      p++;
    }
  }
}


void send_ws() {
  ws_33();
  spi_dma_transmit();
}


void spi_dma_init() {
  // Enable Port A clock // Enable SPI Clock
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_SPI1EN;
  // Mode: Output, Speed: 10MHz // Alternate function push-pull
  GPIOA->CRL &= ~(GPIO_CRL_CNF7 | GPIO_CRL_MODE7);  // MOSI = PA7
  GPIOA->CRL |= (GPIO_CRL_CNF7_1 | GPIO_CRL_MODE7_0);  // 10.01

  // 100: BaudRate = fPCLK/32 // + CPOL + CPHA
  SPI1->CR1 |= SPI_CR1_BR_2 | SPI_CR1_CPOL | SPI_CR1_CPHA;  ///
  // Set TXDMA bit in CR2 → enable DMA transmit
  SPI1->CR2 |= SPI_CR2_TXDMAEN | SPI_CR2_SSOE; /// pourquoi le SSOE ??
  // SPI Mode: Master // SPI Enable
  SPI1->CR1 |= SPI_CR1_MSTR | SPI_CR1_SPE;

  // DMA Configuration  // Enable DMA Clock
  RCC->AHBENR |= RCC_AHBENR_DMA1EN;
  // Priority Level = 0b10 → High
  DMA1_Channel3->CCR |= DMA_CCR_PL_1;
  // Memory Increment mode + Direction Read from Memory
  DMA1_Channel3->CCR |= DMA_CCR_MINC | DMA_CCR_DIR;  // no need for interrupt
  // Set Memory Address → Peripheral Address
  DMA1_Channel3->CMAR = (uint32_t)ws33;
  DMA1_Channel3->CPAR = (uint32_t)&SPI1->DR;
}

void spi_dma_transmit() {
  // stop DMA to refill CNDTR
  DMA1_Channel3->CCR &= ~DMA_CCR_EN;
  DMA1_Channel3->CNDTR = sizeof(ws33);
  // launch DMA
  DMA1_Channel3->CCR |= DMA_CCR_EN;
  ///Serial.print(sizeof(ws33));
}

ag123
Posts: 1917
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: DMA for SPI

Post by ag123 »

I googled for that, gemini chipped in and this is the response:
The SSOE bit (Slave Select Output Enable) in the SPI_CR2 register of an STM32 microcontroller is used to enable or disable the automatic management of the NSS (Slave Select) pin by the hardware in Master mode. When enabled (SSOE = 1), the NSS pin is driven low when the SPI is enabled and remains low until the SPI is disabled. This behavior is specific to master mode.
normally /CS can be manually managed, e.g. use a gpio pin (which can even be the NSS pin), I tend to have codes that works without SSOE, and I manually control the /CS using gpio e.g. digitalWrite() etc. but that SSOE could be 'more convenient' if it is appropriate.
trimarco232
Posts: 44
Joined: Wed Jul 12, 2023 11:09 am
Answers: 1

Re: DMA for SPI

Post by trimarco232 »

but in this case , I don't use NSS , nor MISO , nor CLK
What happened , is that without setting SPI_CR2_SSOE , my MOSI did not work , and even the SPI peripheral lasts its config !
I don't know what happens :
- undocumented silicon error
- no original chip
- me living in 5th dimension
(AI can't actually answer this)
Post Reply

Return to “Ideas & suggestions”