cancel
Showing results for 
Search instead for 
Did you mean: 

SPI with two MOSIs for SWD and SPMI protocol implementation?

ihouses ihouses
Associate III

Hello,

I need to implement the SWD protocol in a STM32 microcontroller. SWD as many other protocols has one bidirectional line that is driven push-pull. I am implementing the protocol using bit banging and 3 output and 1 input.

1 output is for the output data (MOSI)

1 output is to turn the output driver off/on and turn the output line in Hi-Z. (MOSI2?)

1 output is CLK

1 Input is for data in (MISO)

Is there any possibility to use some of the features from the STM32 SPMI module (quad SPI?) or something in order to speed up this? instead of bit banging.

I have read that the Quad sPI can be configured as two MOSI lines, but… what about the MISO? Still there is one? or two?

Best regards,

Jose

7 REPLIES 7
S.Ma
Principal

SPI should be usable to accelerate some of the SWD transmission, however, not fully

I think you only need to use MISO short to MOSI and play dynamically with MODE GPIO bits to disable MOSI when not needed and change the clock polarity between transmit and receive data.

The new gen SPI have MISO/MOSI swap so the short between MISO and MOSI may no longer be necessary and 2 GPIO would be all it takes. With the turnaround bits, few bits may be still done by SW and some others "accelerated" 8 bit or 16 bits at a time.

ihouses ihouses
Associate III

When MISO, CLK and MOSI are controlled by the SPI module, is not posible to control them as a GPIO, is that correct? Then if we mix between SPI and GPIO the transition Will be super slow, right?

Is there any way of generate two MOSIs and one MISO with the new generation SPI?

Thanks!

Uwe Bonnes
Principal II

It depends on the STM32 Family. F1/L1/F2/F4 are not good, as they can only do 8 bit transfers. Other families can do 4-bit to 16-bit data size selection. Use Simplex communication and switch between Transmit-only mode and Receive-only mode. If you look for a pure bitbanging solution, look at the black m*g*c debug probe https://github.com/blacksphere/blackm*g*c.

Sorry for the "*", but this incredible stupid forum software does reject the posting otherwise.

S.Ma
Principal

You can dynamically flip the pin as GPIO or SPI AF (using LL of course). Once you look at the osciloscope timings of SWD and the bit counts, I'm interested to find out how HW SPI can be used without changing to bitbang, especially for the turnaround bit. Please note that if you change the SPI settings (bit length, clock polarity), you probably have to wait the SCK is quiet before changing the SPI configuration.

Geoffrey1
Associate III

Yes, I've done this. You have to change the pin AF in order to bit-bang a few bits. It really doesn't work to change the spi word size in the middle of an swd transaction. Here's the key bit. Having done that, I went back to just bit-banging which I found to be more reliable as well as plenty fast enough for my application.

static inline void toAlternate(ioline_t line) {
  stm32_gpio_t *port = PAL_PORT(line);
  uint32_t pin       = PAL_PAD(line);
  MODIFY_REG(port->MODER, 3 << (pin * 2), 2 << (pin * 2));
}
 
static inline void toInput(ioline_t line) {
  stm32_gpio_t *port = PAL_PORT(line);
  uint32_t pin       = PAL_PAD(line);
  MODIFY_REG(port->MODER, 3 << (pin * 2), 0);
}
 
static inline void toOutput(ioline_t line) {
  stm32_gpio_t *port = PAL_PORT(line);
  uint32_t pin       = PAL_PAD(line);
  MODIFY_REG(port->MODER, 3 << (pin * 2), 1 << (pin * 2));
}
 
static void _SetSWPinsIdle(void){
  palClearLine(LINE_TGT_SWCLK);
  palSetLine(LINE_TGT_SWDIO);
  toAlternate(LINE_TGT_SWCLK);
  toAlternate(LINE_TGT_SWDIO);
}
 
static void _ResetDebugPins(void){
  toInput(LINE_TGT_SWDIO);
}
 
static inline void _SetSWDIOasOutput(void){
  toAlternate(LINE_TGT_SWDIO);
}
static inline void _SetSWDIOasInput(void){
  toInput(LINE_TGT_SWDIO);
}
 
static inline uint32_t SWDIO_IN(void){
  uint8_t b;
 
  palSetLine(LINE_TGT_SWCLK);
  b = palReadLine(LINE_TGT_SWDIO);
  palClearLine(LINE_TGT_SWCLK);
 
 
  //  delay();
 
  return b;
}
 
static inline uint32_t SW_ShiftIn(uint8_t bits){
  int i;
  uint32_t in = 0;
  toOutput(LINE_TGT_SWCLK);
  for (i = 0; i < bits; i++) {
    in = (in >> 1) | ((SWDIO_IN()&1) << (bits - 1));
  }
  toAlternate(LINE_TGT_SWCLK);
 
  return in;
}
 
static inline uint32_t SW_ShiftInBytes(uint8_t bytes) {
 int i;
 uint32_t tmp;
 if (bytes > 4) return 0;
 for (i = 0; i < bytes; i++) {
   *((volatile uint8_t *) &SWD_SPI_DEVICE->DR)  = 0xff;
  }
 for (i = 0; i < bytes; i++) {
   while (!(SWD_SPI_DEVICE->SR & SPI_SR_RXNE));
   ((uint8_t *) &tmp)[i] = *((volatile uint8_t *) &SWD_SPI_DEVICE->DR);
 }
 return tmp;
}
 
static inline void SW_ShiftOutBytes(uint32_t data, uint8_t bytes)
{
  int i;
  if (bytes > 4) return;
  for (i = 0; i < bytes; i++) {
    *((volatile uint8_t *) &SWD_SPI_DEVICE->DR)  = data & 0xff;
    data = data >> 8;
  }
  for (i = 0; i < bytes; i++) {
   while (!(SWD_SPI_DEVICE->SR & SPI_SR_RXNE));
   *((volatile uint8_t *) &SWD_SPI_DEVICE->DR);
  }
}
 
static inline uint32_t Parity(uint32_t x) {
  uint32_t y;
  y = x ^ (x >> 1);
  y = y ^ (y >> 2);
  y = y ^ (y >> 4);
  y = y ^ (y >> 8);
  y = y ^ (y >>16);
  return y & 1;
}
 
static uint32_t SWD_TransactionBB(uint32_t req, uint32_t *data) {
 
  uint32_t ack;
  uint32_t pbit;
 
  SW_ShiftOutBytes(req,1);               // Send header
 
  _SetSWDIOasInput();                    // Set pin direction
  ack = (SW_ShiftIn(4) >> 1) & 7;        // ACK, toss the turnaround bit
 
  switch (ack) {
  case SW_ACK_OK :                       // good to go
    if (req & SW_REQ_RnW) {              // read
      *data = SW_ShiftInBytes(4);        // get data
      pbit = SW_ShiftIn(2)&1;            // get parity bit, toss turnaround
      if (pbit ^ Parity(*data)) {        // parity check
	ack = SW_ACK_PARITY_ERR;
	EPRINTF("parity error data 0x%x pbit %x\r\n", *data, pbit);
      }
      _SetSWDIOasOutput();               // restore direction
    } else {                             // write
      SW_ShiftIn(1);                     // turnaround
      _SetSWDIOasOutput();               // restore direction
      SW_ShiftOutBytes(*data,4);         // data
      SW_ShiftOutBytes(Parity(*data), 1);// parity 
    }
    break;
 
  case SW_ACK_WAIT  :
  case SW_ACK_FAULT :
      SW_ShiftIn(1);                      // turnaround
      _SetSWDIOasOutput();                // restore direction
      break;
 
  default :                               // no ack, back off in case of data phase
    SW_ShiftInBytes(4);                   // data
    SW_ShiftIn(2);                        // parity + turn
    _SetSWDIOasOutput();                  // restore direction
  }
  return ack;
}

ihouses ihouses
Associate III

Hello Geoffrey,

Thanks for you answer. I see that at the end you decided just bitbanging, I Will do the same. I al going to use a cortex M7 for that so I think I would be capable of driving SWD_ CLK more than 10 MHz. I am using a M4 at 120Mhz and I can go close to 6Mhz.

Thanks again.

I don't say the SPI in STM32 couldn't use some thorough rework, but sticking to reality, one way how to cope with unusual SPI-like protocols is, at a cost of one extra pin, to generate the clock by a timer and feed that into SPI's SCK (as well as the external peripheral's) while SPI is in slave mode. Data for SPI might need to be pre-processed/post-processed to accomodate for the extra clocks. The timer (and perhaps other linked timers if needed) then can be used also to generate the framing signal(s) (note that while SPI is slave, its MISO which here acts in fact as MOSI can be threestated by NSS which gives opportunity to output a signal of any polarity using internal or external pullup/down); and also for all sorts of DMA-based tricks...

JW