Ten post chciałbym poświęcić na opisanie sposobu wysyłania danych przez USART z wykorzystaniem DMA. Transmisja danych odbywa się w trybie blokującym.
W mikrokontrolerze znajdują się dwa układy DMA:
DMA1 dla szyny AHB1 i APB1. Pozwala na transmisję z pamięci do układu peryferyjnego bądź odwrotnie. DMA2 dla szyny AHB1 oraz APB2 pozwala dodatkowo na transmisję z pamięci do pamięci. Każdy z tych układów może obsługiwać 8 niezależnych strumieni z 1 do 8 kanałów. Dodatkowo można także ustalić priorytety transmisji z zakresu od 1 do 4.
Przesyłanie danych może się odbywać po 8, 16 lub 32 bity. Dodatkowo można je wysyłać w paczkach z pojedynczym przesłaniem, 4, 8 bądź 16 przesłań.
Jeśli będzie wykorzystywany układ USART2 to należy postępować zgodnie z kodem przedstawionym poniżej. Dla innego układu należy kod delikatnie zmodyfikować:
Włączenie zegara:
Konfiguracja linii TX oraz RX jako funkcja alternatywna, ustawienie usartu w standardowe ustawienia oraz konfiguracja dwóch strumieni DMA:
Linia TX od USART2 korzysta z strumienia 6 oraz kanału 4. Pin RX natomiast wykorzystuje ten sam kanał tylko ze strumienia 5. Adresy układu peryferyjnego nie ulegają zmianie.
Obsługa przerwania dla RX.
Przesłanie danych przez DMA:
Obsługa przerwania od RX wygląda następująco:
Obsługa przerwania od TX wygląda tak:
Natomiast pętla główna prezentuje się następyjąco:
Biblioteka jest do pobrania pod tym linkiem.
[Źródło: http://www.st.com/en/evaluation-tools/stm32f4discovery.html]
W mikrokontrolerze znajdują się dwa układy DMA:
DMA1 dla szyny AHB1 i APB1. Pozwala na transmisję z pamięci do układu peryferyjnego bądź odwrotnie. DMA2 dla szyny AHB1 oraz APB2 pozwala dodatkowo na transmisję z pamięci do pamięci. Każdy z tych układów może obsługiwać 8 niezależnych strumieni z 1 do 8 kanałów. Dodatkowo można także ustalić priorytety transmisji z zakresu od 1 do 4.
Przesyłanie danych może się odbywać po 8, 16 lub 32 bity. Dodatkowo można je wysyłać w paczkach z pojedynczym przesłaniem, 4, 8 bądź 16 przesłań.
Jeśli będzie wykorzystywany układ USART2 to należy postępować zgodnie z kodem przedstawionym poniżej. Dla innego układu należy kod delikatnie zmodyfikować:
Włączenie zegara:
- /* init clocks for USART and DMA */
- void InitClocksForUSART2DMA(void)
- {
- /* Init clock for GPIOA, with USART connected */
- RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
- /* Init clock for DMA 1, with use RX transfer data */
- RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
- /* Init clock for USART2 */
- RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
- }
Konfiguracja linii TX oraz RX jako funkcja alternatywna, ustawienie usartu w standardowe ustawienia oraz konfiguracja dwóch strumieni DMA:
- void InitUSART2DmaNVIC(void)
- {
- InitClocksForUSART2DMA();
- /* Set alternate functions for USART Pins */
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
- GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
- /* Init GPIO pins */
- GPIO_StructInit(&GPIO_InitStruct);
- GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
- GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
- GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
- GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
- GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
- GPIO_Init(GPIOA, &GPIO_InitStruct);
- /* Init USART */
- USART_StructInit(&USART_InitStruct);
- USART_InitStruct.USART_BaudRate = 115200;
- USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
- USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
- USART_InitStruct.USART_Parity = USART_Parity_No;
- USART_InitStruct.USART_StopBits = USART_StopBits_1;
- USART_InitStruct.USART_WordLength = USART_WordLength_8b;
- USART_Init(USART2, &USART_InitStruct);
- /* Enable interrupts from usart */
- NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
- NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
- NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
- NVIC_Init(&NVIC_InitStruct);
- USART_Cmd(USART2, ENABLE);
- USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
- USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
- /* IDLE line protection */
- USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
- /* Conigurate USART2 for TX and RX */
- DMA_StructInit(&DMA_InitStruct);
- DMA_InitStruct.DMA_Channel = DMA_Channel_4;
- DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)DMA_Buffer;
- DMA_InitStruct.DMA_BufferSize = DMA_BUFFER_SIZE;
- DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR;
- DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory;
- DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
- DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
- DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
- DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
- DMA_Init(DMA1_Stream5, &DMA_InitStruct);
- /* Enable global interrupts for DMA */
- NVIC_InitStruct.NVIC_IRQChannel = DMA1_Stream5_IRQn;
- NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
- NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
- NVIC_Init(&NVIC_InitStruct);
- NVIC_InitStruct.NVIC_IRQChannel = DMA1_Stream6_IRQn;
- NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
- NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
- NVIC_Init(&NVIC_InitStruct);
- DMA1->HIFCR = DMA_HIFCR_CTCIF6 | DMA_HIFCR_CTCIF5;
- /* Turn on transfer complete interupt */
- DMA_ITConfig(DMA1_Stream5, DMA_IT_TC, ENABLE);
- DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE);
- DMA_Cmd(DMA1_Stream5, ENABLE);
- DMA_Cmd(DMA1_Stream6, ENABLE);
- }
Linia TX od USART2 korzysta z strumienia 6 oraz kanału 4. Pin RX natomiast wykorzystuje ten sam kanał tylko ze strumienia 5. Adresy układu peryferyjnego nie ulegają zmianie.
Obsługa przerwania dla RX.
- void USART2_IRQHandler(void)
- {
- /* Check flag */
- if (USART2->SR & ((uint16_t)0x0010))
- {
- volatile uint32_t tmp;
- tmp = USART2->SR; /* Status register read */
- tmp = USART2->DR; /* Read data register */
- DMA1_Stream5->CR &= ~DMA_SxCR_EN; /* Disable DMA on stream 5 */
- }
- }
Przesłanie danych przez DMA:
- uint8_t USART_DMA_Send(USART_TypeDef* USARTx, uint8_t* DataArray, uint16_t length)
- {
- txMessageCalculateLength = length;
- /* Check if DMA works*/
- if (DMA1_Stream6->NDTR) {
- return 0;
- }
- DMA_InitStruct.DMA_Channel = DMA_Channel_4;
- DMA_InitStruct.DMA_BufferSize = length;
- DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USARTx->DR;
- DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&DataArray[0];
- DMA1->HIFCR = DMA_FLAG_DMEIF6 | DMA_FLAG_FEIF6 | DMA_FLAG_HTIF6 | DMA_FLAG_TCIF6 | DMA_FLAG_TEIF6;
- DMA_Init(DMA1_Stream6, &DMA_InitStruct);
- DMA1_Stream6->CR |= DMA_SxCR_EN;
- USARTx->CR3 |= USART_CR3_DMAT;
- return 1;
- }
Obsługa przerwania od RX wygląda następująco:
- void DMA1_Stream5_IRQHandler(void) {
- /* Check transfer if is complete */
- if (DMA1->HISR & DMA_FLAG_TCIF5)
- {
- uint8_t len;
- uint8_t dataToCopy;
- uint8_t* pointerToData;
- DMA1->HIFCR |= DMA_FLAG_TCIF5;
- len = DMA_BUFFER_SIZE - DMA1_Stream5->NDTR;
- dataToCopy = UART_BUFFER_SIZE - WriteData;
- if (dataToCopy > len) { dataToCopy = len; }
- /* Write received data to main buffer */
- pointerToData = DMA_Buffer;
- memcpy(&UART_Buffer[WriteData], pointerToData, dataToCopy);
- /* Correct values for remaining data */
- WriteData += dataToCopy;
- len -= dataToCopy;
- pointerToData += dataToCopy;
- /* Copy rest data to buffer */
- if (len) {
- memcpy(&UART_Buffer[0], pointerToData, len);
- WriteData = len;
- }
- /* Prepare DMA for next transfer, clear all flags */
- DMA1->HIFCR = DMA_FLAG_DMEIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_HTIF5 | DMA_FLAG_TCIF5 | DMA_FLAG_TEIF5;
- DMA1_Stream5->M0AR = (uint32_t)DMA_Buffer; /* Set memory address for DMA again */
- DMA1_Stream5->NDTR = DMA_BUFFER_SIZE; /* Set number of bytes to receive */
- DMA1_Stream5->CR |= DMA_SxCR_EN; /* Start DMA transfer */
- memset(DMA_Buffer, 0, sizeof(DMA_Buffer));
- }
- }
Obsługa przerwania od TX wygląda tak:
- void DMA1_Stream6_IRQHandler(void)
- {
- /* Check transfer complete flag */
- if (DMA1->HISR & DMA_FLAG_TCIF6)
- {
- uint8_t dataLength;
- uint8_t dataToCopy;
- uint8_t* pointerToData;
- DMA1->HIFCR |= DMA_FLAG_TCIF6; /* Clear transfer complete flag */
- /* Calculate number of bytes to copy */
- dataLength = txMessageCalculateLength;// - DMA1_Stream6->NDTR;
- dataToCopy = UART_BUFFER_SIZE - WriteData;
- /* Check how many bytes to copy */
- if (dataToCopy > dataLength) { dataToCopy = dataLength; }
- /* Write received data for UART main buffer for manipulation later */
- pointerToData = DMA_TX_Buffer;
- memcpy(&UART_Buffer[WriteData], pointerToData, dataToCopy); /* Copy first part */
- /* Correct values for remaining data */
- WriteData += dataToCopy;
- dataLength -= dataToCopy;
- pointerToData += dataToCopy;
- /* Clear DMA Flags */
- DMA1->HIFCR = DMA_FLAG_DMEIF6 | DMA_FLAG_FEIF6 | DMA_FLAG_HTIF6 | DMA_FLAG_TCIF6 | DMA_FLAG_TEIF6;
- /* Write start data pointer */
- DMA1_Stream6->M0AR = (uint32_t)DMA_TX_Buffer;
- /* Write data length */
- DMA1_Stream6->NDTR = dataLength;
- /* Enable transfer */
- DMA1_Stream6->CR |= DMA_SxCR_EN;
- }
- }
Natomiast pętla główna prezentuje się następyjąco:
- int main(void) {
- /* Init system */
- SystemInit();
- /* Init USART with DMA and NVIC */
- InitUSART2DmaNVIC();
- USART_DMA_Send(USART2, (uint8_t*)DMA_TX_Buffer, 17);
- memset(DMA_TX_Buffer, 0, sizeof(DMA_TX_Buffer));
- DMA_TX_Buffer[0] = 'a';
- DMA_TX_Buffer[1] = 'b';
- DMA_TX_Buffer[2] = 'c';
- DMA_TX_Buffer[3] = 'd';
- DMA_TX_Buffer[4] = 'e';
- DMA_TX_Buffer[5] = 'a';
- DMA_TX_Buffer[6] = 'b';
- DMA_TX_Buffer[7] = 'c';
- DMA_TX_Buffer[8] = 'd';
- DMA_TX_Buffer[9] = 'e';
- DMA_TX_Buffer[10] = '\n';
- USART_DMA_Send(USART2, (uint8_t*)DMA_TX_Buffer, 10);
- while (1)
- {
- /* Do it until buffer is empty */
- while (ReadData != WriteData)
- {
- USART2->DR = UART_Buffer[ReadData++]; /* Start byte transfer */
- while(USART_DMA_Sending())
- if (ReadData == UART_BUFFER_SIZE) {
- ReadData = 0;
- }
- }
- }
- }
- int16_t USART_DMA_Sending(void) {
- /* DMA works */
- if (DMA1_Stream6->NDTR) {
- return 1;
- }
- return !((USART2)->SR & USART_FLAG_TXE);
- }
Biblioteka jest do pobrania pod tym linkiem.