сайт для палких паяльників

8. STM32. Программирование STM32F103. DMA
(на русском языке)

DMA (Direct Memory Access) контролер прямого доступу до пам’яті. Його головна задача: передача даних на апаратному рівні між пам’ятю і периферією без участі процесора. Мається на увазі, що при цьому наша програма може виконувати інші операції, не відволікаючись на передачу даних. В попередній статті ми задіяли DMA для роботи з АЦП. І це було круто. Тепер розглянемо роботу DMA докладніше і ще раз впевнимось у потужній користі DMA на прикладі ще однієї типової задачі: відправки даних через USART.

Ми вже використовували USART. Відправка даних через USART – досить тривалий процес, під час якого (у попередніх прикладах) процесор чекає, поки буде відправлений весь буфер. Дивись функцію USARTSend. Поки ця функція не закінчить відправку всього буфера, далі обробка у головному циклі програми не йде. Усі чекають. У нас були досить прості приклади і нам було байдуже. Але, рано чи пізно, нам знадобиться вся потужність контролера і треба буде оптимізувати цю операцію. Один з методів – використання DMA. Ми підготовимо дані на відправку, дамо завдання DMA, він буде собі відправляти байт за байтом, а процесор займеться чимось більш важливим.

Що взагалі може DMA?

Спочатку роздивимось схему і табличку з документації, щоб зрозуміти, як налаштувати DMA і які у нього є можливості. У DMA контролера – 7 каналів. У мікроконтролерів STM32 можуть бути декілька DMA контролерів. Звісно, вони можуть працювати паралельно. В нашому STM32F103C8 лише один 7-канальний DMA контролер.

stm32 dma структура

stm32 dma table

Зі схеми і таблиці видно, що певна периферія закріплена за певними каналами. І, якщо ми використовуємо ADC1, то ми його можемо використовувати тільки на каналі 1. У нас немає можливості перенести ADC1 на канал, який нам заманеться. Отже, якщо ми використовуємо перший канал під ADC1, тоді TIM2_CH3 і TIM4_CH1 (згідно таблиці) – в прольоті. Тобто, з DMA ми їх вже не зможемо задіяти. Для роботи з UART1 нам знадобиться канали 4, 5. Зверніть увагу, що, скажімо, ADC2 взагалі тут не фігурує. Тобто, не будь-яку периферію можна використовувати з DMA. Комбінацій, як бачимо, не так багато і треба все чітко планувати, щоб канали DMA були зайняті виключно по ділу. Також слід розуміти, що у DMA все ж є обмеження по швидкості.

Налаштування DMA

Налаштування DMA виконується через структуру InitTypeDef, яка описана у файлі stm32f10x_dma.h:

typedef struct
{
  uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */

  uint32_t DMA_MemoryBaseAddr;     /*!< Specifies the memory base address for DMAy Channelx. */

  uint32_t DMA_DIR;                /*!< Specifies if the peripheral is the source or destination.
                                        This parameter can be a value of @ref DMA_data_transfer_direction */

  uint32_t DMA_BufferSize;         /*!< Specifies the buffer size, in data unit, of the specified Channel. 
                                        The data unit is equal to the configuration set in DMA_PeripheralDataSize
                                        or DMA_MemoryDataSize members depending in the transfer direction. */

  uint32_t DMA_PeripheralInc;      /*!< Specifies whether the Peripheral address register is incremented or not.
                                        This parameter can be a value of @ref DMA_peripheral_incremented_mode */

  uint32_t DMA_MemoryInc;          /*!< Specifies whether the memory address register is incremented or not.
                                        This parameter can be a value of @ref DMA_memory_incremented_mode */

  uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.
                                        This parameter can be a value of @ref DMA_peripheral_data_size */

  uint32_t DMA_MemoryDataSize;     /*!< Specifies the Memory data width.
                                        This parameter can be a value of @ref DMA_memory_data_size */

  uint32_t DMA_Mode;               /*!< Specifies the operation mode of the DMAy Channelx.
                                        This parameter can be a value of @ref DMA_circular_normal_mode.
                                        @note: The circular buffer mode cannot be used if the memory-to-memory
                                              data transfer is configured on the selected Channel */

  uint32_t DMA_Priority;           /*!< Specifies the software priority for the DMAy Channelx.
                                        This parameter can be a value of @ref DMA_priority_level */

  uint32_t DMA_M2M;                /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.
                                        This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;

DMA_PeripheralBaseAddr – адреса периферійного пристрою
DMA_MemoryBaseAddr – адреса пам’яті
DMA_DIR – напрямок передачі. Дані можуть передаватися з периферії у пам’ять і навпаки: з пам’яті – у периферію(DMA_DIR_PeripheralDST | DMA_DIR_PeripheralSRC)
DMA_BufferSize – розмір буфера даних
DMA_PeripheralInc – вказує чи треба інкрементувати адреси на дані у периферії (DMA_PeripheralInc_Enable | DMA_PeripheralInc_Disable)
DMA_MemoryInc – вказує чи треба інкрементувати адреси на дані у пам’яті (DMA_MemoryInc_Enable | DMA_MemoryInc_Disable)

Якщо повернутися до прикладу роботи АЦП через DMA, то ми побачимо, що у налаштуваннях DMA
DMA_MemoryInc = Enable
DMA_PeripheralInc = Disable
Це тому, що ми розкладаємо дані АЦП у масив і нам потрібно увімкнути інкрементацію адрес у пам’яті. Щоб дані з різних каналів записувались на свої місця.
А вихідний регістр у АЦП один, і нам слід вимкнути інкрементацію на периферії.

DMA_PeripheralDataSize – розмір одиниці даних для периферії
DMA_MemoryDataSize – розмір одиниці даних для пам’яті

Ці поля можуть приймати наступні значення:

DMA_PeripheralDataSize_Byte
DMA_PeripheralDataSize_HalfWord
DMA_PeripheralDataSize_Word
DMA_MemoryDataSize_Byte
DMA_MemoryDataSize_HalfWord
DMA_MemoryDataSize_Word

DMA_Mode – режим роботи каналу DMA (DMA_Mode_Circular | DMA_Mode_Normal)
DMA_Priority – приорітет каналу DMA (DMA_Priority_VeryHigh | DMA_Priority_High | DMA_Priority_Medium | DMA_Priority_Low)
DMA_M2M – передача пам’ять>пам’ять (DMA_M2M_Enable | DMA_M2M_Disable)

Приклад. Відправка даних у USART через DMA

У цьому прикладі варто звернути увагу на роботу функції USARTSendDMA. Вона лише записує у буфер інформацію для відправки і запускає DMA канал на відправку. Вона не чекає поки буде відправлений буфер, як це робила функція USARTSend у попередніх прикладах. Тому, якщо визвати функцію USARTSendDMA до закінчення відправки буфера, вона перезапише буфер і почне відправку нових даних. Будь ласка, майте це на увазі.

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_dma.h"
#include "misc.h"
#include <string.h>

#define RX_BUF_SIZE 80
volatile char RX_FLAG_END_LINE = 0;
volatile char RXi;
volatile char RXc;
volatile char RX_BUF[RX_BUF_SIZE] = {'\0'};
volatile char buffer[80] = {'\0'};

void clear_RXBuffer(void) {
	for (RXi=0; RXi<RX_BUF_SIZE; RXi++)
		RX_BUF[RXi] = '\0';
	RXi = 0;
}

void usart_dma_init(void)
{
	/* Enable USART1 and GPIOA clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

	/* DMA */
	DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(USART1->DR);
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)&buffer[0];
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
	DMA_InitStruct.DMA_BufferSize = sizeof(buffer);
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
	DMA_InitStruct.DMA_Priority = DMA_Priority_Low;
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
	DMA_Init(DMA1_Channel4, &DMA_InitStruct);

	/* NVIC Configuration */
	NVIC_InitTypeDef NVIC_InitStructure;
	/* Enable the USARTx Interrupt */
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);

	/* Configure the GPIOs */
	GPIO_InitTypeDef GPIO_InitStructure;

	/* Configure USART1 Tx (PA.09) as alternate function push-pull */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* Configure USART1 Rx (PA.10) as input floating */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* Configure the USART1 */
	USART_InitTypeDef USART_InitStructure;

	/* USART1 configuration ------------------------------------------------------*/
	/* USART1 configured as follow:
		- BaudRate = 115200 baud
		- Word Length = 8 Bits
		- One Stop Bit
		- No parity
		- Hardware flow control disabled (RTS and CTS signals)
		- Receive and transmit enabled
		- USART Clock disabled
		- USART CPOL: Clock is active low
		- USART CPHA: Data is captured on the middle
		- USART LastBit: The clock pulse of the last data bit is not output to
			the SCLK pin
	 */
	USART_InitStructure.USART_BaudRate = 115200;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

	USART_Init(USART1, &USART_InitStructure);

	/* Enable USART1 */
	USART_Cmd(USART1, ENABLE);

	USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
	//DMA_Cmd(DMA1_Channel4, ENABLE);

	DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
	NVIC_EnableIRQ(DMA1_Channel4_IRQn);


	/* Enable the USART1 Receive interrupt: this interrupt is generated when the
	USART1 receive data register is not empty */
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}

void USART1_IRQHandler(void)
{
    if ((USART1->SR & USART_FLAG_RXNE) != (u16)RESET)
	{
    		RXc = USART_ReceiveData(USART1);
    		RX_BUF[RXi] = RXc;
    		RXi++;

    		if (RXc != 13) {
    			if (RXi > RX_BUF_SIZE-1) {
    				clear_RXBuffer();
    			}
    		}
    		else {
    			RX_FLAG_END_LINE = 1;
    		}

			//Echo
    		USART_SendData(USART1, RXc);
	}
}

void USARTSendDMA(const unsigned char *pucBuffer)
{
	strcpy(buffer, pucBuffer);

	/* Restart DMA Channel*/
	DMA_Cmd(DMA1_Channel4, DISABLE);
	DMA1_Channel4->CNDTR = strlen(pucBuffer);
	DMA_Cmd(DMA1_Channel4, ENABLE);
}

void DMA1_Channel4_IRQHandler(void)
{
	DMA_ClearITPendingBit(DMA1_IT_TC4);
	DMA_Cmd(DMA1_Channel4, DISABLE);
}

void SetSysClockTo72(void)
{
	ErrorStatus HSEStartUpStatus;
    /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* RCC system reset(for debug purpose) */
    RCC_DeInit();

    /* Enable HSE */
    RCC_HSEConfig( RCC_HSE_ON);

    /* Wait till HSE is ready */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();

    if (HSEStartUpStatus == SUCCESS)
    {
        /* Enable Prefetch Buffer */
    	//FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable);

        /* Flash 2 wait state */
        //FLASH_SetLatency( FLASH_Latency_2);

        /* HCLK = SYSCLK */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);

        /* PCLK2 = HCLK */
        RCC_PCLK2Config( RCC_HCLK_Div1);

        /* PCLK1 = HCLK/2 */
        RCC_PCLK1Config( RCC_HCLK_Div2);

        /* PLLCLK = 8MHz * 9 = 72 MHz */
        RCC_PLLConfig(0x00010000, RCC_PLLMul_9);

        /* Enable PLL */
        RCC_PLLCmd( ENABLE);

        /* Wait till PLL is ready */
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }

        /* Select PLL as system clock source */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK);

        /* Wait till PLL is used as system clock source */
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock configuration.
     User can add here some code to deal with this error */

        /* Go to infinite loop */
        while (1)
        {
        }
    }
}

int main(void)
{
	// Set System clock
	SetSysClockTo72();

	/* Initialize LED which connected to PC13 */
	GPIO_InitTypeDef  GPIO_InitStructure;
	// Enable PORTC Clock
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
	/* Configure the GPIO_LED pin */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);

	GPIO_ResetBits(GPIOC, GPIO_Pin_13); // Set C13 to Low level ("0")

    // Initialize USART
    usart_dma_init();
    USARTSendDMA("Hello.\r\nUSART1 is ready.\r\n");

    while (1)
    {
    	if (RX_FLAG_END_LINE == 1) {
    		// Reset END_LINE Flag
    		RX_FLAG_END_LINE = 0;

    		/* !!! This lines is not have effect. Just a last command USARTSendDMA(":\r\n"); !!!! */
    		USARTSendDMA("\r\nI has received a line:\r\n"); // no effect
    		USARTSendDMA(RX_BUF); // no effect
    		USARTSendDMA(":\r\n"); // This command does not wait for the finish of the sending of buffer. It just write to buffer new information and restart sending via DMA.

    		if (strncmp(strupr(RX_BUF), "ON\r", 3) == 0) {
    			USARTSendDMA("THIS IS A COMMAND \"ON\"!!!\r\n");
    			GPIO_ResetBits(GPIOC, GPIO_Pin_13);
    		}

    		if (strncmp(strupr(RX_BUF), "OFF\r", 4) == 0) {
    			USARTSendDMA("THIS IS A COMMAND \"OFF\"!!!\r\n");
    			GPIO_SetBits(GPIOC, GPIO_Pin_13);
    		}

    		clear_RXBuffer();
    	}
    }
}

Бажаю успіхів!

Дивись також:

Translate
Архіви

© 2011-2019 Андрій Корягін, Кременчук - Київ, Україна