Search code examples
cembeddedstm32uartspi

STM32 UART to SPI variable length data transmission


Hi I'm trying to communicate with my ES-WIFI module in STM32 IoT development kit. The wifi module by default is connected through SPI2 (to use UART I have to flash the module's firmware using SPI). I want to transmit AT commands from UART serial monitor to the wifi module and receive the response back to the PC's serial monitor. So, I want to receive the message from USART1 (pc-uart) and transmit the data to the SPI2. Likewise, the response should be read from SPI2 and transmitted to USART1. Also the key point is, data transmission should be of variable leangth.

When I run the program, the debugger stops at SPI status register TXE not empty. And the FIFO transmission level is full (FTLVM: 11). Can anyone please tell me what am I doing wrong.

    void USART1_IRQHandler(void)
{
  while (!((USART1->ISR) & USART_ISR_TXE));

  char temp1 = USART1->RDR;

  SPI2->DR = temp1;

  while (!(USART1->ISR & USART_ISR_TC)); // Checking transfer complete bit from UART status register

  while (!((SPI2->SR)& SPI_SR_TXE)); //Checking Transmit buffer empty from SPI status register

  while (((SPI2->SR)& SPI_SR_BSY)); //Checking SPI busy flag from status register

  uint8_t temp2 = SPI2->DR; // To clear Data and status register
          temp2 = SPI2->SR;
}

My SPI2 init function is

void MX_SPI2_Init(void)
{

  /* USER CODE BEGIN SPI2_Init 0 */

  /* USER CODE END SPI2_Init 0 */

  /* USER CODE BEGIN SPI2_Init 1 */

  /* USER CODE END SPI2_Init 1 */
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 7;
  hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi2.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI2_Init 2 */

  /* USER CODE END SPI2_Init 2 */

}

My UART init function is

void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_8;
  huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

Solution

  • Fundamentally, we need to handle two scenarios:

    1. Incoming data to UART. Send out over SPI.
    2. Incoming data to SPI. Send out over UART.

    I think you're getting that part OK.

    The problems start when you have continuous transmission and not individual bytes. Unless transmission timings are perfectly equal (which in real world is impossible), one peripheral will send/receive data faster than another peripheral. Consider exaggerated example to make it more intuitive:

    Let SPI be 1Mbit/s.
    Let UART be 9600b/s.

    You can see how if you receive more than one byte of SPI data to be wired to UART, UART won't be able to handle it, it simply doesn't shift data out quickly enough. It's shifting out the first byte, but there are already many more coming in over SPI. If you receive via slow UART, but send out via faster SPI, you can see that when a full byte of UART data comes in, SPI can easily shift it out and take a coffee break while the second byte of UART data is still coming in. When speeds are closer, there will still be a mismatch, and input will be faster than output either from UART to SPI or from SPI to UART.


    Let's consider for a moment, that SPI is faster than UART like in the example I made up above. Doesn't matter if it's the other way around in your case, it's about concepts.

    When a byte over quick SPI comes in, there are two scenarios possible:

    1. UART was idle. Begin transmission. Easy enough.
    2. UART was already shifting something out. And now we have a problem. I believe this is what you were trying to solve and where your code hangs, you mishandle this scenario.

    Therefore, I believe you need to step back a little, maybe take pen and paper and consider different scenarios that can occur in your application. You will have to choose which direction will be faster and therefore won't need any buffer, and the other one will have to use a FIFO buffer array of predetermined length. The MCU does process the incoming data almost immediately (comparing to physical communication speed), but the physical process of shifting the data out over another peripheral takes time, there is no way around it.

    Given your MCU is very quick with moving numbers around (probably at least an order of magnitude quicker than data being physically shifted out), and you can simply implement it in such a way, that you always send incoming SPI buffer SPI_RX_BUFFER[0] out over UART and immediately shift all other elements one element down. You only need a FIFO "fullness" counter to know when to stop. For example, call it SPI_RX_BUFFER_LAST_ELEMENT, and every time you send something via UART, you decrement it (so that it's -1 when you send out the last element in FIFO). Every time SPI data comes in, SPI_RX_BUFFER_LAST_ELEMENT just gets incremented by 1 and the new data is stored at that position.

    Even your ST-Link with its USB-UART bridge has this limited FIFO. If you flood it with USB data, you will overflow it, and some kind of error event will happen. For example, STM32F103-based ST-Link has max UART speed of 4.5Mbps, while its USB interface supports up to 12Mbps.

    Here, I took a little time to draw a little illustration, because talking about these things is easier with a picture.

    enter image description here


    Bottom line is, you need to take a step back and reconsider your algorithm, decide how deep your buffer has to be and implement it step by step. In one direction, your algorithm will be "I received data, just put it in TX of another interface", because there it's definitely already done with the previous data (although an "if" statement won't hurt). In another direction, it has to be "I put data into next free FIFO spot. If FIFO was empty, start transmission", and "I have sent out a byte and transmit data register is empty, I need to check if FIFO has anything else to be sent. If it does, load it into transmit register and shift all the data in the FIFO and pointer to the last element".