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

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-2018 Андрій Корягін, Кременчук - Київ, Україна