Skocz do zawartości
atMegaTona

[STM32F] HAL_I2C_Mem_Write_DMA(...); nie wysyła STOPu

Pomocna odpowiedź

Napisano (edytowany)

Nieczęsto korzystam z I2C w swoich projektach i jak do tej pory nie miałem żadnych przygód z działaniem DMA. Jednak postanowiłem urozmaicić swój projekt o wyświetlacz oled i2c przy którym siłą rzeczy użycie DMA samo się sugeruje i.. spotkała mnie niemiła niespodzianka. Otóż przy wywołaniu funkcji po przekopiowaniu bufora do oleda program kończył swoje działanie w HardFault_Handler. Okazało się, że przyczyną jest brak stopu na końcu transmisji I2C.

Częściowo rozwiązałem problem wpisując stop do przerwania od końca transmisji ręcznie ale nie jestem pewien czy to rozwiązanie jest zupełnie prawidłowe. Być może da się zmusić HALa aby jednak ten stop wysyłał lub trzeba ustawić coś gdzieś tylko nie wiem jak. Może ktoś z was miał z tym podobny problem?

Pozostałe funkcje obsługi I2C działają bez problemu.

Edytowano przez atMegaTona
aktualizacja

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Jeśli chodzi o F0x2 to mamy taki wpis w RM:

"Automatic end mode (AUTOEND = ‘1’ in the I2C_CR2 register). In this mode, the master automatically sends a STOP condition once the number of bytes programmed in the NBYTES[7:0] bit field has been transferred."

Zacząłbym od sprawdzenia czy AUTOEND jest ustawiony w rejestrze, pod debuggerem, jeśli nie to oczywiście należy go zmodyfikować.

W innych rodzinach należałoby sprawdzić czy odpowiednia funkcja jest dostępna.

Edytowano przez Zealota

Udostępnij ten post


Link to post
Share on other sites

To nie tak. W L4 (tych ostatnio używam najczęściej) zdefiniowano max. liczbę bajtów do przesłania po I2C (MAX_NBYTE_SIZE) na 255. Jeżeli przekroczysz tę liczbę - a przy wyświetlaczu to żaden problem, to wywołanie funkcji ..._DMA() nie może wręcz wygenerować STOPu, bo przecież transfer nie został zakończony. HAL sprawdza czy to co zleciłeś do przesłania jest większe niż 255 bajtów i jeśli tak, ustawia tryb zakończenia na I2C_RELOAD_MODE oczekując dalszych paczek danych. W przeciwnym wypadku pierwszy transfer jest ostatnim i tryb jest ustawiany na I2C_AUTOEND_MODE (plik stm32l4xx_hal_i2c.c). Dla wyjaśnienia, poniższe XferCount zostało gdzieś wcześniej załadowane wartością parametru Size z wywołania funkcji ..._DMA():

   if(hi2c->XferCount > MAX_NBYTE_SIZE)
    {
      hi2c->XferSize = MAX_NBYTE_SIZE;
      xfermode = I2C_RELOAD_MODE;
    }
    else
    {
      hi2c->XferSize = hi2c->XferCount;
      xfermode = I2C_AUTOEND_MODE;
    }

A to co się stanie po zatrzymaniu (np. kontynuację) sam musisz sobie oprogramować (UM1884 "Description of STM32L4/L4+ HAL and low-layer drivers"):

DMA mode IO MEM operation
- Write an amount of data in non-blocking mode with DMA to a specific memory address using HAL_I2C_Mem_Write_DMA()
- At Memory end of write transfer, HAL_I2C_MemTxCpltCallback() is executed and user can add his own code by customization of function pointer
HAL_I2C_MemTxCpltCallback()
- Read an amount of data in non-blocking mode with DMA from a specific memory address using HAL_I2C_Mem_Read_DMA()
- At Memory end of read transfer, HAL_I2C_MemRxCpltCallback() is executed and user can add his own code by customization of function pointer
HAL_I2C_MemRxCpltCallback()
- In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can add his own code by customization of function pointer HAL_I2C_ErrorCallback()

So, RTFM pls, a jeśli nie lubisz czytać, to zawsze możesz obejrzeć kod funkcji w bibliotece - to zwykle wszystko wyjaśnia.

Udostępnij ten post


Link to post
Share on other sites
19 godzin temu, marek1707 napisał:

a jeśli nie lubisz czytać, to zawsze możesz obejrzeć kod funkcji w bibliotece - to zwykle wszystko wyjaśnia.

Cóż, nie każdy ma czas żeby dokładnie i wnikliwie studiować meandry bibliotek ale faktycznie pomogło obejrzenie tych funkcji.

Poradziłem sobie z tym w sposób następujący. w pliku stm32f1xx_it.c nadpisałem funkcję przerwania od zakończenia transferu DMA na kanale przypisanym do I2C Tx, żeby nie łamać konwencji zawartej w HALu wiec po kolei jak leciało:

void DMA1_Channel4_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel4_IRQn 0 */

__HAL_DMA_DISABLE_IT(hdma, DMA_IT_TE | DMA_IT_TC); 
hdma->State = HAL_DMA_STATE_READY;
/* Clear the transfer complete flag */
__HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma));

/* Process Unlocked */
__HAL_UNLOCK(hdma);

I2C_HandleTypeDef *hi2c = (I2C_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent; /* Derogation MISRAC2012-Rule-11.5 */

__HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR);

/* Disable DMA Request */
CLEAR_BIT(hi2c->Instance->CR2, I2C_CR2_DMAEN);

/* Disable Last DMA */
CLEAR_BIT(hi2c->Instance->CR2, I2C_CR2_LAST);

/* Generate Stop */
SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);

hi2c->XferCount = 0U;

if (hi2c->ErrorCode != HAL_I2C_ERROR_NONE)
      HAL_I2C_ErrorCallback(hi2c);
    
hi2c->State = HAL_I2C_STATE_READY;

hi2c->Mode = HAL_I2C_MODE_NONE;
hi2c->PreviousState = I2C_STATE_NONE;
/////
/// Tutaj własny kod przerwania 
////
return;

  /* USER CODE END DMA1_Channel4_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_i2c2_tx);
  /* USER CODE BEGIN DMA1_Channel4_IRQn 1 */

  /* USER CODE END DMA1_Channel4_IRQn 1 */
}

Czyli wydłubałem bebechy z funkcji obsługujących koniec transferu. Analizując HALa wygląda na to, że do zwrotnego callbacka w funkcji HAL_I2C_MemWrite_DMA jest przypisywana niepoprawna funkcja I2C_DMAXferCplt. Nie rozumiem dlaczego nikt tego nie poprawił do tej pory.
Błąd był powodowany przekroczeniem timeoutu na wysłanie stopu na linie I2C przy prędkoci 400kbps, nie sprawdzałem z mniejszymi prędkościami ale jest szansa, że przy niższych prędkociach funkcja I2C_DMAXferCplt zadziała prawidłowo, tj. zdąży dojechać do generowania stopu.
Zaproponowane rozwiązanie dotyczy najnowszej biblioteki HAL dla F1 ale powinno działać również na innych prockach na których ten problem występuje a przynajmniej wiadomo jak sobie z tym radzić.

Teraz działa prawidłowo bez względu na ilość wysyłanych danych.

Udostępnij ten post


Link to post
Share on other sites

Teraz dopiero znalazłem na gicie rozwiązanie tego problemu. Okazuje się, że nie timeout a zerowanie wskaźników miało na to wpływ, które to linie wyciąłem w tym swoim improwizowanym rozwiązaniu. Wygląda na to, że wystarczy zmodyfikować plik z biblioteki w sposób przedstawiony w tej dyskusji:

https://github.com/STMicroelectronics/STM32CubeF1/issues/6

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Gość
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...