Aaron Decker
Aaron Decker

Reputation: 558

STM32 Use DMA to generate bit pattern on GPIO PIN

I am trying to generate a bit pattern on a GPIO pin. I have set-up the DMA engine to transfer from an array of GPIO pin states to the GPIO BSRR register

Here is the code I am using to configure the DMA

hdma_tim16_ch1_up.Instance = DMA1_Channel3;
hdma_tim16_ch1_up.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_tim16_ch1_up.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim16_ch1_up.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim16_ch1_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_tim16_ch1_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_tim16_ch1_up.Init.Mode = DMA_NORMAL;
hdma_tim16_ch1_up.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_tim16_ch1_up) != HAL_OK)
{
  Error_Handler();
}

/* Several peripheral DMA handle pointers point to the same DMA handle.
 Be aware that there is only one channel to perform all the requested DMAs. */
__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_CC1],hdma_tim16_ch1_up);
__HAL_LINKDMA(tim_baseHandle,hdma[TIM_DMA_ID_UPDATE],hdma_tim16_ch1_up);

Here is the code I use to setup the transfer:

  uint32_t outputbuffer[] = {
  0x0000100,0x01000000,
  0x0000100,0x01000000,
  0x0000100,0x01000000,
  0x0000100,0x01000000,
  0x0000100,0x01000000,
  0x0000100,0x01000000,
  0x0000100,0x01000000
  /* ... */
  };

  if (HAL_DMA_Start_IT(htim16.hdma[TIM_DMA_ID_UPDATE], (uint32_t)outputbuffer,  (uint32_t)&GPIOG->BSRR, 14) != HAL_OK)
  {
    /* Return error status */
    return HAL_ERROR;
  }
  __HAL_TIM_ENABLE_DMA(&htim16,TIM_DMA_UPDATE);
  HAL_TIM_Base_Start_IT(&htim16);

I am expecting to see every time the counter overflows, the DMA transfers 32 bits from the array and increments to the next array position until the DMA CNDTR register reads 0.

I set up a GPIO pin to toggle every time the timer over flows and I setup an alternating bit pattern in the array. I would expect the two GPIO pins to be similar in their output shape but I get one longer pulse on the line connected to the DMA. Any tips would be greatly appreciated

Output Issue

Upvotes: 3

Views: 3916

Answers (2)

JavaLatte
JavaLatte

Reputation: 378

It looks like you are trying to use DMA to generate a bit-sequence on a single bit. The code below does exactly this, in order to simulate the TX side of a UART.

It uses timer 4 running from the internal clock to generate an update event every 104us (9600 baud). On an STM32F103, the update event, TIM4_UP, is linked with DMA1 Channel 7. The update event causes a 32-bit DMA transfer from a buffer to the GPIO BSRR register. The buffer contains the required bit sequence for one character (11 bits).

The upper 16 bits of BSRR turns bits off and the lower 16 bits turns them on. If you want to toggle just one bit, then each 32-bit value must contain only one one- at the same position in either the upper or the lower 16 bits. My LED is driven low-side, so I write to the upper bit to turn the LED on.

Once the 11-bit sequence is complete, the DMA interrupts sets up the DMA data buffer for the 11-bit sequence for the next character.

I use the code to write diagnostic information via GPIO to a LED: a hand-held opto-sensor can be used to read the diagnostic information without any need for an electrical connection.

// Soft serial port (send only) for STM32
// Uses timer and DMA

#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include "main.h"

#define RING_BUFFER_SIZE 128
static uint8_t ring [RING_BUFFER_SIZE];
static int ring_in = 0;
static int ring_out = 0;
#define DMA_BUFFER_SIZE 11
static uint32_t dma_buffer [DMA_BUFFER_SIZE];
static bool dma_active = false;
static uint32_t dma_irq_count;
static uint32_t tim4_irq_count;

#define led_off (DIAG_LED_Pin)
#define led_on (DIAG_LED_Pin << 16)

// Called once at start-up to initialise the timer and DMA
void soft_serial_init (void) {

    // Turn on power for timer 4
    SET_BIT(RCC->APB1ENR, RCC_APB1ENR_TIM4EN);

    // 9600 baud... 104us per bit
    TIM4->CR1 = TIM_COUNTERMODE_UP | ((uint32_t) TIM_CLOCKDIVISION_DIV1) | TIM_AUTORELOAD_PRELOAD_ENABLE;
    TIM4->ARR = 206;
    TIM4->PSC = 1;
    TIM4->EGR = TIM_EGR_UG;

    // Enable DMA on update
    TIM4->DIER |= TIM_DIER_UDE;
    // Enable timer
    TIM4->CR1 |= TIM_CR1_CEN;

    // Turn on the clock for DMA1
    __HAL_RCC_DMA1_CLK_ENABLE();

    DMA1_Channel7->CCR &= ~(1 << DMA_CCR_EN_Pos);

    // MEM2MEM 0 - disabled
    // PINC = disabled
    // CIRC disabled
    DMA1_Channel7->CCR =
             (1 << DMA_CCR_PL_Pos)          // Medium priority
            | (2 << DMA_CCR_MSIZE_Pos)      // 32 bits
            | (2 << DMA_CCR_PSIZE_Pos)      // 32 bits
            | (1 << DMA_CCR_MINC_Pos)       // Memory increment
            | (1 << DMA_CCR_DIR_Pos)        // Memory to peripheral
            | (1 << DMA_CCR_TCIE_Pos)       // transfer complete interrupt
            | (1 << DMA_CCR_TEIE_Pos);      // error interrupt

    HAL_NVIC_SetPriority(DMA1_Channel7_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel7_IRQn);

}

// Called to set up the DMA data to send one UART character (11 bits)
static void soft_serial_start (void) {
    char ch;

    dma_active = true;

    // Get the next character from the ring buffer
    ring_out++;
        if (ring_out >= RING_BUFFER_SIZE)
            ring_out = 0;
    ch = ring [ring_out];

    // set up an 11-bit sequence
    //  the first bit is random length,
    //   probably because there's an outstanding timer update
    //   could deal with this by stopping the timer
    // the second bit is the start bit
    // then eight data bits
    // then a stop bit
    dma_buffer [0] = led_off;
    dma_buffer [1] = led_on;
    for (int i = 0; i < 8; i++) {
        dma_buffer [i+2] = (ch & (1 << i)) ? led_off : led_on;
    }
    dma_buffer [10] = led_off;

    // Start the DMA transfer
    DMA1_Channel7->CCR &= ~(1 << DMA_CCR_EN_Pos);
    DMA1_Channel7->CPAR = (uint32_t) (&MY_LED_GPIO_Port->BSRR);
    DMA1_Channel7->CMAR = (uint32_t) (dma_buffer);
    DMA1_Channel7->CNDTR = DMA_BUFFER_SIZE;
    DMA1_Channel7->CCR |= (1 << DMA_CCR_EN_Pos);

}


// Puts a character into the ring buffer.
void soft_serial_putch (char ch) {
    ring_in++;
    if (ring_in >= RING_BUFFER_SIZE)
        ring_in = 0;
    ring [ring_in] = ch;

    // If DMA isn't active, start it now
    if (!dma_active)
       soft_serial_start ();
}


// Interrupt routine to set up the next character or shut down
void DMA1_Channel7_IRQHandler(void)
{
    dma_irq_count++;

    // Start the next character, if available
    if (ring_out != ring_in)
        soft_serial_start ();
    else {
        dma_active = false;
        DMA1_Channel7->CCR &= ~(1 << DMA_CCR_EN_Pos);
    }

  // Clear the interrupt flag
  uint32_t isr = DMA1->ISR;
  if (isr & (1 << DMA_ISR_TCIF7_Pos))
      DMA1->IFCR |= (1 << DMA_IFCR_CTCIF7_Pos);
  if (isr & (1 << DMA_ISR_TEIF7_Pos))
      DMA1->IFCR |= (1 << DMA_IFCR_CTEIF7_Pos);

}

Upvotes: 0

Georgy Moshkin
Georgy Moshkin

Reputation: 46

  1. configure TIM2 as input capture direct mode (TIM2_CH1)

  2. configure TIM2 DMA direction "memory to peripheral"

  3. configure TIM2 data width Half word / Half word

  4. configure GPIO pins as GPIO_OUTPUT, for example 16 pins GPIOD0..GPIOD15

  5. copy and paste HAL_TIM_IC_Start_DMA() function from HAL library and give it a new name MY_TIM_IC_Start_DMA()

  6. find HAL_DMA_Start_IT() function call in MY_TIM_IC_Start_DMA()

  7. replace (uint32_t)&htim->Instance->CCR1 with (uint32_t)&GPIOD->ODR

    if (HAL_DMA_Start_IT(htim->hdma[TIM_DMA_ID_CC1], (uint32_t)&GPIOD->ODR, (uint32_t)pData, Length) != HAL_OK)

Now you can start DMA to GPIO transfer by calling MY_TIM_IC_Start_DMA(&htim2, TIM_CHANNEL_1,(uint32_t*)gpioBuffer,GPIO_BUFFER_SIZE); Actual transfer must be triggered by providing pulses on TIM2_CH1 input pin (for example, by using output compare pin from other timer channel). Those pulses originally was used to save Timer2 CCR1 register values to DMA buffer. Code was tweaked to transfer DMA buffer value to GPIOD ODR register.

For GPIO to Memory transfer change TIM2 DMA direction to "peripheral to memory", configure GPIO pins as GPIO_INPUT and use GPIOD->IDR instead of ODR in HAL_DMA_Start_IT parameters in modified MY_TIM_IC_Start_DMA() function.

Upvotes: 0

Related Questions