3
\$\begingroup\$

In steady state the line (output) is high and we have to create pulses that are microseconds long and are low. It is going to be a custom sequence with different pulse lengths (for example the line would look like all high, then 3 μs low, then 4 μs high, then 1 μs low, then 5 μs high - 2 μs low, then all high etc.).

We have tried standard PWM and PWM one shot, but we always failed to make it work as PWM functions do not work as intented. I mean we cannot simply close PWM and re-run it with new configs as I understood. We also tried to use DMA. We are aware that microseconds long pulses cannot be created by HAL_Delay function.

We are using NUCLEO-L452RE-P board. How can we make it work efficiently?

As I am not allowed to share work place material, I cannot post the code we wrote.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ You could use the SPI interface to generate the output pulse on the COPI line. You just have to configure the SPI clock appropriately, and send DATA according to the pulse to be generated. \$\endgroup\$
    – Velvet
    Commented Sep 8, 2023 at 9:50

3 Answers 3

6
\$\begingroup\$

As proposed by Velvel, using SPI (in DMA mode) is probably the easiest approach.

The SPI peripheral should be configured like so:

  • Mode: Transmit Only Master
  • Hardware NSS Signal: Disable
  • Data Size: 8 Bit
  • First Bit: LSB First (to make it easier for software)
  • NSSP Mode: Disable
  • NSS Signal Type: Software

The prescaler should be set such that the baud rate is either 1 MHz exactly or the lowest possible multiple of 1 MHz.

In addition, DMA for SPI TX should be added (default settings with data width byte).

The two challenges are now:

  1. Generate the bit pattern from the pulse sequence
  2. Ensure that MOSI (the output pin) is high on idle

For the idle state: The idle state is determine by one of the bits in the SPI data shift register. For all or most of the STM32L4 chips, the shift register is 32 bit longs. So by sending 4 FF bytes initially and after each pulse sequence, this is ensured.

The code on a L476RG looks like so. SPI baud rate is 5 MHz. So the bit pattern has 5 bits per 1µs of pulse:

main.c

  pulses_init(&hspi2);

  // pulse sequence, length in µs, staring with low
  uint8_t pulse_lengths[] = { 3, 4, 1, 4, 2, 5 };
  pulses_prepare_buffer(pulse_lengths, sizeof(pulse_lengths));

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    pulses_send_buffer(&hspi2);

    HAL_Delay(1000);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

pulses.h

#pragma once

#include "stm32l4xx_hal.h"

void pulses_init(SPI_HandleTypeDef* spi);

void pulses_prepare_buffer(const uint8_t* pulse_lengths, int num_pulses);

void pulses_send_buffer(SPI_HandleTypeDef* spi);

pulses.c

#include "pulses.h"
#include <stdbool.h>

#define BUFFER_LEN 1024
static uint32_t pulse_buffer[BUFFER_LEN];
static int num_words;

// SPI clock is 5 MHz; send same bit 5 times to make it 1 µs long
#define REPEAT 5

void pulses_init(SPI_HandleTypeDef* spi)
{
    // Send 4 bytes of all high bits to initialize idle state
    uint32_t data = 0xffffffff;
    HAL_SPI_Transmit(spi, (uint8_t*)&data, 4, 100);
}

void pulses_prepare_buffer(const uint8_t* pulse_lengths, int num_pulses)
{
    num_words = 0;
    uint8_t bit = 0;
    uint32_t word = 0xffffffff;
    bool is_low = true;

    for (int i = 0; i < num_pulses; i += 1)
    {
        int len = pulse_lengths[i] * REPEAT;
        for (int j = 0; j < len; j += 1) {
            if (is_low)
                word ^= 1 << bit;
            bit += 1;

            if (bit == 32) {
                pulse_buffer[num_words] = word;
                num_words += 1;
                bit = 0;
                word = 0xffffffff;
            }
        }

        is_low = !is_low;
    }

    pulse_buffer[num_words] = word;
    num_words += 1;

    // Another 4 bytes of all high bits for the idle state
    pulse_buffer[num_words] = 0xffffffff;
    num_words += 1;
}

void pulses_send_buffer(SPI_HandleTypeDef* spi)
{
    HAL_SPI_Transmit_DMA(spi, (uint8_t*)pulse_buffer, num_words * 4);
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ He can use also more flexible SAI in a similar way. \$\endgroup\$ Commented Sep 8, 2023 at 20:27
2
\$\begingroup\$

There are many ways of doing it - using SPI, a PLD or PIO (programmable IO state machine) hardware block if the chip has those, or even just loading new compare values into a timer using DMA. Each time a timer compare matches, timer flips the output and raises the interrupt. DMA handles the request and loads new compare value. The compare values have to be precomputed to give you the sequence you want.

\$\endgroup\$
2
\$\begingroup\$

Simplest method Use DMA to transfer to the GPIO. It is much more precise than SPI. Simple use a timer to trigger the DMA transmissions. It is very precise. I use it in our test devices where jitter has to be extremely minimal.

Run the code from RAM and place data in another RAM (most STM32 uC have more than one SRAM on different busses)

\$\endgroup\$
3
  • \$\begingroup\$ Can you explain why DMA to GPIO is more precise than SPI? \$\endgroup\$
    – Codo
    Commented Sep 9, 2023 at 15:33
  • \$\begingroup\$ @Codo The delay between words transmitted is not guaranteed. And if you investigate more precisely you will see that sometimes there is no delay sometimes there is. When using GPIO you have everything under control and the timings are from a timer - ie very precise and consistent \$\endgroup\$ Commented Sep 9, 2023 at 17:07
  • \$\begingroup\$ In my past projects, I've never seen any delay at all (when NSSP Mode is set to Disable). It was perfectly aligned with the SPI clock. The main advantage of your method is that the base clock can be freely chosen while the choice for SPI is quite limited. \$\endgroup\$
    – Codo
    Commented Sep 9, 2023 at 17:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.