DMA ile ilgili bir sorun

Başlatan robikod, 01 Temmuz 2021, 09:36:57

robikod

STM32f4 işlemci kullanıyorum, UART'dan data alırken DMA kullanmaktayım ancak bir problem yaşadım, aldığım dataların boyutları belirli değil o yüzden direk receive üzerinde çalışmıyorum, hattın IDLE olma durumunu takip edip, main loopta HAL_UART_RxCpltCallback fonksiyonunu manuel olarak tetikliyorum. Gönderici taraftan data gönderirken 17 bytelar halinde yollamayı denedim, bir kaç defa arka arkaya 17 bytelık farklı datalar yolladım:

        Length 17
       
        11 22 33 44 55 ......AA BB

        Length 34
       
        11 22 33 44 55 .... AA BB **CC DD EE FF ..... AA BB**
    until length 500 byte


Görüldüğü gibi önceki datanın üzerine ekliyor, ancak beklediğim şu şekilde:
    Length 17
    11 22 33 44 55 ..... AA BB
    Length 17
    CC DD EE FF .... AA BB

Şöyle de ilginç bir durum var, aşağıdaki fonksiyondaki HAL_UART_Receive_DMA(&huart2,(uint8_t*)dma_rx_buf,DMA_BUF_SIZE); fonksiyonundan sonra  tekrar DMA yı durdurup tekrar receive ettikten sonra istediğim gerçekleşiyor.
Neden tek seferde bu gerçekleşmiyor fikri olan var mı ?

  HAL_UART_Receive_DMA(&huart2,(uint8_t*)dma_rx_buf,DMA_BUF_SIZE);
  HAL_UART_DMAStop(&huart2);
  HAL_UART_Receive_DMA(&huart2,(uint8_t*)dma_rx_buf,DMA_BUF_SIZE);


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
      uint16_t currentValueCNDTR = __HAL_DMA_GET_COUNTER(huart->hdmarx);
      if((currentValueCNDTR != DMA_BUF_SIZE))
      {
        HAL_UART_DMAStop(&huart2);
        bufferLength =  DMA_BUF_SIZE - currentValueCNDTR;
        printf("Length %d\n",bufferLength);
    
        for(j = 0; j < bufferLength; j++)
        {
          printf("%02x ",dma_rx_buf[dummyCounter]);
        }
[b]            HAL_UART_Receive_DMA(&huart2,(uint8_t*)dma_rx_buf,DMA_BUF_SIZE);
[/b]      
     }
    }

    int main()
    {
    HAL_UART_Receive_DMA(&huart2,(uint8_t*)dma_rx_buf,DMA_BUF_SIZE);
    while(1)
    {
           if((recevieFlag == 1U))
            {
              hdma_usart2_rx.XferCpltCallback(&hdma_usart2_rx);
              receiveFlag = 0;
            } 
    
    }
    }

robikod


Erol YILMAZ

Gonderdiginiz uart datalarini logic analizorle goruntuler misiniz?

Data paketleri arasinda Ne kadar bosluk veriyorsunuz?

Idle time iniz ne kadar?
Bu sure ayarlanabiliyor sanirim...

Tagli

Alıntı yapılan: Erol YILMAZ - 01 Temmuz 2021, 11:12:31Bu sure ayarlanabiliyor sanirim...
F4'lerde ayarlanamıyor maalesef. Sabit 1 karakter süresi kadar.
Gökçe Tağlıoğlu

Erol YILMAZ

#4
STM32F072 VE F091 de ayarlaniyor...
F4 serisinin F0'dan geri ozellikleri mi var?

Neyse konumuz bu degil

kimlenbu

IDLE süresi konusunda benim uyguladığım teknik biraz çağ dışı ama işe yarıyor.

IDLE kesmesi geldiğinde timer başlatıyorum, istediğim IDLE süresi sonunda timer kesmesi tetikleniyor, hala yeni veri gelmemişse DMA buffer'dan head ve tail konumlarını bildiğimden gelen veriyi ayrı bir diziye alıp gerekli işlemleri orada hallediyorum. Programlar çokl daha stabil çalışıyor bu sayede.

DMA buffer'ını gelecek verinin boyutu bilinmese de 2-3 kat büyük seçiyorum en az.

robikod

Alıntı yapılan: kimlenbu - 01 Temmuz 2021, 14:01:02IDLE süresi konusunda benim uyguladığım teknik biraz çağ dışı ama işe yarıyor.

IDLE kesmesi geldiğinde timer başlatıyorum, istediğim IDLE süresi sonunda timer kesmesi tetikleniyor, hala yeni veri gelmemişse DMA buffer'dan head ve tail konumlarını bildiğimden gelen veriyi ayrı bir diziye alıp gerekli işlemleri orada hallediyorum. Programlar çokl daha stabil çalışıyor bu sayede.

DMA buffer'ını gelecek verinin boyutu bilinmese de 2-3 kat büyük seçiyorum en az.

Benimde aslında yapım bu şekilde bufferım ayrı değerlendiriliyor. Sorun DMA bufferının aşağıdaki şekilde sıfırlanması, zaten yukarda DMA'yı durduruyorum 1 kez receive yaptığımda sıfırlanması gerekirken tekrar durdurup tekrar başlatmam gerekiyor ilginç bir durum
  HAL_UART_Receive_DMA(&huart2,(uint8_t*)dma_rx_buf,DMA_BUF_SIZE);
  HAL_UART_DMAStop(&huart2);
  HAL_UART_Receive_DMA(&huart2,(uint8_t*)dma_rx_buf,DMA_BUF_SIZE);

Tagli

#7
Kim bilir HAL arkada neler yapıyor... Bence bu fonksiyonların içine girip bir incele. Normalde donanım bu şekilde çalışmıyor çünkü. Aynı yöntemi yine F4'te Modbus için kullanıyorum, ancak HAL kullanmadan doğrudan register'lara yazarak. Bu tür bir sorun yaşamadım.

Acaba Idle kesmesi bayrağı sıfırlanmıyor olabilir mi? Normalde bunun için iki tane boş okuma yapmak gerekli. Bunu senin kodunda göremedim. HAL, callback fonksiyonunu çağırmadan önce bu işi kendisi yapıyorsa bilemem.

Benim Idle kesmesi kodumun ilgili bölümü böyle:

// Idle detection - End of Modbus incoming package
if ((mb->USART->SR & USART_SR_IDLE) != 0) {
    mb->RX_DMA->CR &= ~DMA_SxCR_EN; // Disable DMA
    // Was the frame for us?
    uint8_t adr = std::to_integer<uint8_t>(mb->usartBuffer[0]);
    if ((adr == mb->slaveAddress) || (adr == 0)) {
        vTaskNotifyGiveFromISR(mb->modbusTask, &higherWoken); // Yes
    }
    else {
        mb->rearmRxDma(); // No
    }
    [[maybe_unused]] volatile uint32_t dummy;
    dummy = mb->USART->SR; // Dummy read for IDLE clear sequence
    dummy = mb->USART->DR; // Dummy read for IDLE clear sequence
}

Bu da kesme dışında RTOS task'i olarak çalışan kodun başlarından bir bölüm:

ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Wait for idle line
uint8_t txSize = 0;
uint16_t frameSize = BufferSize - RX_DMA->NDTR;

// Check CRC to determine if the frame is valid
uint16_t receivedCrc;
std::memcpy(&receivedCrc, usartBuffer + frameSize - 2, sizeof(receivedCrc));
if (receivedCrc != crc16(usartBuffer, frameSize - 2)) {
    rearmRxDma();
    continue;
}

Ekleme: Kodu ekledikten sonra fark ettim ki ben de DMA'yı kapatmışım. Ancak NDTR'yi okumakla ilgili bir sorun yaşadığım için yapmadım bunu. Ben paketi işlerken yanlışlıkla veri gelirse (ki normalde böyle bir şey olmaması lazım) buffer'a eklenmeye devam etmesin diye yapmıştım. Normalde DMA aktifken de NDTR register'ı sorunsuz olarak okunabilir. Bunu başka uygulamalarımda kullanıyorum.

Paketi işledikten sonra DMA'yı tekrar açtığım kod da bu:

void Modbus::rearmRxDma() {
    using namespace hw;
    RX_DMA.disable();
    RX_DMA->NDTR = BufferSize;
    RX_DMA.clearFlags(DmaFlag::FE | DmaFlag::TC | DmaFlag::HT);
    RX_DMA.enable();
}
Gökçe Tağlıoğlu

kimlenbu

Donanım kaynaklı sıkıntı olmadığından emin olmanızı öneririm. RS485 verilerini alırken bir USART ile sıkıntı yaşıyordum, protoipte RS485 transceiver çipi arızalı çıktı ama bulana kadar bana kafayı yedirtti.

Ayrıca Std Peripheral Library'den kalma alışkanlık olarak HAL Library'nin IRQ Handler'larına güvenmem, adama saç baş yoldurtur, tehlikeli bayrakların kontrolünü ve resetlenmesini kendim hallederim.

Ayrıca @Tagli'nin beklenmedik anda veri gelmesi için aldığı önlem eğer veri gelme periyodu biliniyorsa es geçilebilir. DMA'yı circular olarak ayarlarsınız ve bahsettiğim "head and tail" yapısını kullanırsınız.

Örnek kod şu şekilde, öncelikle DMA buffer'ını komple yedekliyorum, ardından yedeklenmiş array'de işlem yapıyorum ki beklenmedik anda veri gelirse yanlış veri çekilmesin diye. 216Mhz'de çalıştığım için array yedeklemek beni neredeyse hiç etkilemiyor.

void USER_UART3_IDLECallback(void){ //INPUT B

uint16_t i=0;

	HeadB=TailB;
	TailB=BufferBSize - __HAL_DMA_GET_COUNTER(huart3.hdmarx);   
	
		for(i=0;i<(uint16_t)BufferBSize;i++){tmpBufferB[i]=BufferInputB_NMEA[i];}

	if(TailB>HeadB){ //If buffer is not full yet, new data is through at the end of the buffer
		DataSizeB=TailB-HeadB;
		for(i=0;i<DataSizeB;i++){
			InputB[i]=tmpBufferB[HeadB+i];
		}
	}
	else{ //If buffer is full and some of the data is at the end of the buffer, rest is at the beginning of the buffer
		for(i=0;i<BufferBSize-HeadB;i++){ //Data positioned at the end of buffer
		InputB[i]=tmpBufferB[HeadB+i];
		
		}
		for(i=0;i<TailB;i++){
		InputB[BufferBSize-HeadB]=tmpBufferB[i];
		
		}
	}
	
	DmaBFirstCharacter=InputB[0];
	DmaBLastCharacter=InputB[i-1];

}