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
Tags
bme280 bmp280 gps mpu-6050 options stm32 ssd1331 ssd1306 eb-500 3d-printer soldering tim mpu-9250 dma watchdog piezo exti web raspberry-pi docker ngnix solar bluetooth foc html css brushless flask dc-dc capture gpio avr rs-232 mpx4115a atmega mongodb st-link barometer pwm nvic git java-script programmator dht11 hih-4000 pmsm encoder max1674 smd sensors rtc adc lcd motor timer meteo examples i2c usb flash sms rfid python esp8266 servo books bldc remap eeprom bkp battery ethernet uart usart displays led websocket nodemcu wifi
Архіви

