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 контролер.Зі схеми і таблиці видно, що певна периферія закріплена за певними каналами. І, якщо ми використовуємо 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();
}
}
}
Бажаю успіхів!
Дивись також:
- 1. STM32. Програмування STM32F103. Тестова плата. Прошивка через UART та через ST-Link
- 2. STM32. Програмування. IDE для STM32
- 3. STM32. Програмування STM32F103. GPIO
- 4. STM32. Програмування STM32F103. Тактування
- 5. STM32. Програмування STM32F103. USART
- 6. STM32. Програмування STM32F103. NVIC
- 7. STM32. Програмування STM32F103. ADC
- 8. STM32. Програмування STM32F103. DMA
- 9. STM32. Програмування STM32F103. TIMER
- 10. STM32. Програмування STM32F103. TIMER. Захоплення сигналу
- 11. STM32. Програмування STM32F103. TIMER. Encoder
- 12. STM32. Програмування STM32F103. TIMER. PWM
- 13. STM32. Програмування STM32F103. EXTI
- 14. STM32. Програмування STM32F103. RTC
- 15. STM32. Програмування STM32F103. BKP
- 16. STM32. Програмування STM32F103. Flash
- 17. STM32. Програмування STM32F103. Watchdog
- 18. STM32. Програмування STM32F103. Remap
- 19. STM32. Програмування STM32F103. I2C Master
- 20. STM32. Програмування STM32F103. I2C Slave
- 21. STM32. Програмування STM32F103. USB
- 22. STM32. Програмування STM32F103. PWR
- 23. STM32. Програмування STM32F103. Option bytes
- 24. STM32. Програмування STM32F103. Bootloader
- STM32. Скачати приклади
- System Workbench for STM32 Інсталяція на Ubuntu
- Keil uVision5 – IDE для STM32
- IAR Workbench – IDE для STM32
- Керування безколекторним двигуном постійного струму (BLDC) за допомогою STM32
- Керування PMSM за допомогою STM32
Недавні записи
- LCD Display ST7567S (IIC)
- Розпізнавання мови (Speech recognition)
- Selenium
- Комп'ютерний зір (Computer Vision)
- Деякі думки про точність вимірювань в електроприводі
- Датчики Холла 120/60 градусів
- Модуль драйверів напівмосту IGBT транзисторів
- Драйвер IGBT транзисторів на A316J
- AS5600. Варіант встановлення на BLDC мотор
- DC-DC для IGBT драйверів ізольований 2 W +15 -8 вольт
Tags
battery soldering java-script ngnix adc rs-232 flask esp8266 watchdog web exti solar mongodb rtc sms pwm usart books ethernet smd git websocket meteo css python dc-dc displays led ssd1306 eeprom gpio barometer max1674 mpx4115a brushless motor mpu-6050 timer sensors remap servo bldc atmega pmsm 3d-printer flash encoder examples dma raspberry-pi tim ssd1331 piezo mpu-9250 rfid eb-500 foc bme280 gps nvic dht11 bluetooth hih-4000 stm32 st-link docker uart avr html wifi bmp280 bkp nodemcu options usb lcd programmator i2c capture
Архіви