wtorek, 1 sierpnia 2017

[28] STM32F4 - USART RX oraz TX z DMA, biblioteki STD

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.

[Ź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:

  1. /* init clocks for USART and DMA */
  2. void InitClocksForUSART2DMA(void)
  3. {
  4.     /* Init clock for GPIOA, with USART connected */
  5.     RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
  6.     /* Init clock for DMA 1, with use RX transfer data */
  7.     RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
  8.     /* Init clock for USART2 */
  9.     RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
  10. }

Konfiguracja linii TX oraz RX jako funkcja alternatywna, ustawienie usartu w standardowe ustawienia oraz konfiguracja dwóch strumieni DMA:

  1. void InitUSART2DmaNVIC(void)
  2. {
  3.   InitClocksForUSART2DMA();
  4.    
  5.   /* Set alternate functions for USART Pins */
  6.   GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
  7.   GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
  8.    
  9.   /* Init GPIO pins */
  10.   GPIO_StructInit(&GPIO_InitStruct);
  11.   GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
  12.   GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
  13.   GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
  14.   GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
  15.   GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
  16.   GPIO_Init(GPIOA, &GPIO_InitStruct);
  17.    
  18.   /* Init USART */
  19.   USART_StructInit(&USART_InitStruct);
  20.   USART_InitStruct.USART_BaudRate = 115200;
  21.   USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  22.   USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
  23.   USART_InitStruct.USART_Parity = USART_Parity_No;
  24.   USART_InitStruct.USART_StopBits = USART_StopBits_1;
  25.   USART_InitStruct.USART_WordLength = USART_WordLength_8b;
  26.   USART_Init(USART2, &USART_InitStruct);
  27.    
  28.   /* Enable interrupts from usart */
  29.   NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
  30.   NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  31.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
  32.   NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
  33.   NVIC_Init(&NVIC_InitStruct);
  34.    
  35.   USART_Cmd(USART2, ENABLE);
  36.   USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
  37.   USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
  38.    
  39.   /* IDLE line protection */
  40.   USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
  41.    
  42.   /* Conigurate USART2 for TX and RX */
  43.   DMA_StructInit(&DMA_InitStruct);
  44.   DMA_InitStruct.DMA_Channel = DMA_Channel_4;
  45.   DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)DMA_Buffer;
  46.   DMA_InitStruct.DMA_BufferSize = DMA_BUFFER_SIZE;
  47.   DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USART2->DR;
  48.   DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory;
  49.   DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  50.   DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  51.   DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
  52.   DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  53.   DMA_Init(DMA1_Stream5, &DMA_InitStruct);
  54.    
  55.   /* Enable global interrupts for DMA */
  56.   NVIC_InitStruct.NVIC_IRQChannel = DMA1_Stream5_IRQn;
  57.   NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  58.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
  59.   NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
  60.   NVIC_Init(&NVIC_InitStruct);
  61.   NVIC_InitStruct.NVIC_IRQChannel = DMA1_Stream6_IRQn;
  62.   NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
  63.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
  64.   NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
  65.   NVIC_Init(&NVIC_InitStruct);
  66.    
  67.   DMA1->HIFCR = DMA_HIFCR_CTCIF6 | DMA_HIFCR_CTCIF5;
  68.      
  69.   /* Turn on transfer complete interupt */   
  70.   DMA_ITConfig(DMA1_Stream5, DMA_IT_TC, ENABLE);
  71.   DMA_ITConfig(DMA1_Stream6, DMA_IT_TC, ENABLE);  
  72.   DMA_Cmd(DMA1_Stream5, ENABLE);
  73.   DMA_Cmd(DMA1_Stream6, ENABLE);
  74. }

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.

  1. void USART2_IRQHandler(void)
  2. {
  3.     /* Check flag */
  4.     if (USART2->SR & ((uint16_t)0x0010))
  5.     {
  6.         volatile uint32_t tmp;                
  7.         tmp = USART2->SR;                       /* Status register read */
  8.         tmp = USART2->DR;                       /* Read data register */                        
  9.         DMA1_Stream5->CR &= ~DMA_SxCR_EN;       /* Disable DMA on stream 5 */
  10.     }  
  11. }

Przesłanie danych przez DMA:

  1. uint8_t USART_DMA_Send(USART_TypeDef* USARTx, uint8_t* DataArray, uint16_t length)
  2. {
  3.     txMessageCalculateLength = length;
  4.     /* Check if DMA works*/
  5.     if (DMA1_Stream6->NDTR) {
  6.         return 0;
  7.     }
  8.    
  9.     DMA_InitStruct.DMA_Channel = DMA_Channel_4;
  10.     DMA_InitStruct.DMA_BufferSize = length;
  11.     DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&USARTx->DR;
  12.     DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)&DataArray[0];
  13.    
  14.     DMA1->HIFCR = DMA_FLAG_DMEIF6 | DMA_FLAG_FEIF6 | DMA_FLAG_HTIF6 | DMA_FLAG_TCIF6 | DMA_FLAG_TEIF6;
  15.     DMA_Init(DMA1_Stream6, &DMA_InitStruct);
  16.    
  17.     DMA1_Stream6->CR |= DMA_SxCR_EN;
  18.     USARTx->CR3 |= USART_CR3_DMAT;
  19.    
  20.     return 1;
  21. }

Obsługa przerwania od RX wygląda następująco:

  1. void DMA1_Stream5_IRQHandler(void) {
  2.     /* Check transfer if is complete */
  3.     if (DMA1->HISR & DMA_FLAG_TCIF5)
  4.     {
  5.         uint8_t len;
  6.         uint8_t dataToCopy;
  7.         uint8_t* pointerToData;
  8.         DMA1->HIFCR |= DMA_FLAG_TCIF5;
  9.        
  10.         len = DMA_BUFFER_SIZE - DMA1_Stream5->NDTR;
  11.         dataToCopy = UART_BUFFER_SIZE - WriteData;    
  12.        
  13.         if (dataToCopy > len) { dataToCopy = len; }
  14.        
  15.         /* Write received data to main buffer */
  16.         pointerToData = DMA_Buffer;
  17.         memcpy(&UART_Buffer[WriteData], pointerToData, dataToCopy);  
  18.        
  19.         /* Correct values for remaining data */
  20.         WriteData += dataToCopy;
  21.         len -= dataToCopy;
  22.         pointerToData += dataToCopy;
  23.                
  24.         /* Copy rest data to buffer */
  25.         if (len) {
  26.             memcpy(&UART_Buffer[0], pointerToData, len);
  27.             WriteData = len;
  28.         }
  29.                
  30.         /* Prepare DMA for next transfer, clear all flags */
  31.         DMA1->HIFCR = DMA_FLAG_DMEIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_HTIF5 | DMA_FLAG_TCIF5 | DMA_FLAG_TEIF5;
  32.         DMA1_Stream5->M0AR = (uint32_t)DMA_Buffer;      /* Set memory address for DMA again */
  33.         DMA1_Stream5->NDTR = DMA_BUFFER_SIZE;               /* Set number of bytes to receive */
  34.         DMA1_Stream5->CR |= DMA_SxCR_EN;                /* Start DMA transfer */
  35.                
  36.         memset(DMA_Buffer, 0, sizeof(DMA_Buffer));
  37.     }
  38. }

Obsługa przerwania od TX wygląda tak:

  1. void DMA1_Stream6_IRQHandler(void)
  2. {
  3.     /* Check transfer complete flag */
  4.     if (DMA1->HISR & DMA_FLAG_TCIF6)
  5.     {
  6.         uint8_t dataLength;
  7.         uint8_t dataToCopy;
  8.         uint8_t* pointerToData;
  9.            
  10.         DMA1->HIFCR |= DMA_FLAG_TCIF6;        /* Clear transfer complete flag */
  11.         /* Calculate number of bytes to copy */
  12.         dataLength = txMessageCalculateLength;// - DMA1_Stream6->NDTR;
  13.         dataToCopy = UART_BUFFER_SIZE - WriteData;
  14.        
  15.         /* Check how many bytes to copy */
  16.         if (dataToCopy > dataLength) { dataToCopy = dataLength; }
  17.      
  18.         /* Write received data for UART main buffer for manipulation later */
  19.         pointerToData = DMA_TX_Buffer;
  20.         memcpy(&UART_Buffer[WriteData], pointerToData, dataToCopy);   /* Copy first part */
  21.        
  22.         /* Correct values for remaining data */
  23.         WriteData += dataToCopy;
  24.         dataLength -= dataToCopy;
  25.         pointerToData += dataToCopy;
  26.                
  27.                
  28.         /* Clear DMA Flags */
  29.         DMA1->HIFCR = DMA_FLAG_DMEIF6 | DMA_FLAG_FEIF6 | DMA_FLAG_HTIF6 | DMA_FLAG_TCIF6 | DMA_FLAG_TEIF6;
  30.         /* Write start data pointer */
  31.         DMA1_Stream6->M0AR = (uint32_t)DMA_TX_Buffer;
  32.         /* Write data length */
  33.         DMA1_Stream6->NDTR = dataLength;    
  34.         /* Enable transfer */
  35.         DMA1_Stream6->CR |= DMA_SxCR_EN;
  36.     }
  37. }

Natomiast pętla główna prezentuje się następyjąco:

  1. int main(void) {
  2.         /* Init system */
  3.         SystemInit();
  4.         /* Init USART with DMA and NVIC */
  5.         InitUSART2DmaNVIC();
  6.    
  7.         USART_DMA_Send(USART2, (uint8_t*)DMA_TX_Buffer, 17);
  8.         memset(DMA_TX_Buffer, 0, sizeof(DMA_TX_Buffer));
  9.         DMA_TX_Buffer[0] = 'a';
  10.         DMA_TX_Buffer[1] = 'b';
  11.         DMA_TX_Buffer[2] = 'c';
  12.         DMA_TX_Buffer[3] = 'd';
  13.         DMA_TX_Buffer[4] = 'e';
  14.         DMA_TX_Buffer[5] = 'a';
  15.         DMA_TX_Buffer[6] = 'b';
  16.         DMA_TX_Buffer[7] = 'c';
  17.         DMA_TX_Buffer[8] = 'd';
  18.         DMA_TX_Buffer[9] = 'e';
  19.         DMA_TX_Buffer[10] = '\n';
  20.         USART_DMA_Send(USART2, (uint8_t*)DMA_TX_Buffer, 10);
  21.            
  22.     while (1)
  23.         {
  24.             /* Do it until buffer is empty */
  25.             while (ReadData != WriteData)
  26.             {                
  27.                 USART2->DR = UART_Buffer[ReadData++];           /* Start byte transfer */
  28.                 while(USART_DMA_Sending())
  29.                 if (ReadData == UART_BUFFER_SIZE) {    
  30.                     ReadData = 0;
  31.                 }
  32.       }
  33.     }
  34. }

  1. int16_t USART_DMA_Sending(void) {
  2.     /* DMA works */
  3.     if (DMA1_Stream6->NDTR) {
  4.         return 1;
  5.     }
  6.     return !((USART2)->SR & USART_FLAG_TXE);
  7. }

Biblioteka jest do pobrania pod tym linkiem.