'전체 글'에 해당되는 글 40건

  1. 2022.02.28 STM32H7 DMA 사용 시 주의점
posted by 샛별의꿈 2022. 2. 28. 11:03

삽질을 안하면 하루도 편히 잠들 수 없는(아니 있는...)건지, 이번에도 또 삽질임.

 

STM32F1이나 F4, F7 계열로 쓸 때 겪어보지 못했던 문제였는데, 알고나서 생각해보니 F7에서도 충분히 발생할 만한 문제였던 것 같다.

 

UART RX를 DMA 로 사용하고, NDTR 레지스터 읽어가면서 버퍼가 채워지는 경우 처리를 했는데...

STM32H7로 했더니 갑자기 NDTR값은 바뀌는데 실제 버퍼가 채워지지 않거나 이상한 값이 나타난 것.

 

원인을 찾아보니 아래와 같은 내용 발견

https://community.st.com/s/article/FAQ-DMA-is-not-working-on-STM32H7-devices

 

Service Not Available

Your browser doesn’t support some features on this site. For the best experience, update your browser to the latest version, or switch to another browser.

community.st.com

내용에 따르면, D-Cache 를 켜는 순간 D-Cache가 SRAM에 내용을 쓰지 않고, Cache 에 데이터를 가지고 있는 문제로 인하여 SRAM에 할당된 UART 버퍼가 업데이트되지 않았던 것.

 

이를 해결하기 위해 몇 가지 방법이 있는데,

 

1. D-Cache를 사용하지 않고 모든 메모리 할당을 D1 도메인에 배치한다.

2. 캐싱이 필요한 변수 / 캐싱하지 않을 변수를 별도로 분리하고 분리배치한다. MPU에서 캐싱을 비활성화시킨다.

3. 캐시 유지관리 기능을 사용한다.

 

일단, 위와 같은 방법 중에서 1번은 손해볼 일이 많다.

실험을 위해 연산이 많으나 빨리 처리해야 하는 부분의 처리속도 측정 결과, 캐시를 사용하였을 때와 사용하지 않았을 때의 처리속도 차이가 5~6배 가량 발생하는 것을 확인하였다.

이런 속도차이를 포기하고 캐시를 비활성화시키는 건 그닥 올바른 방향은 아닌 것 같다.

 

그래서 2, 3번이 남는데 일단 3번은 메모리 할당 시 32바이트 단위로밖에 설정할 수가 없는 것 같고, 괜히 잘못 건드렸다가 Hardfault 에 빠질 수 있다는 내용이 있어 뒤로 미뤄두고(...)

 

2번을 테스트해 보기로 함.

 

이를 위해 위 문서에 자세히 설명이 되어 있지만 정리하면,

 

 1) 버퍼를 D2 Domain 에 할당한다. (DMA1/DMA2 채널이 모두 D2 Domain 에 가장 가까이 연결되어 있다)
   이를 위해, 링커스크립트 파일(*.ld) 에 아래 내용을 넣는다. 위치는 중요하지 않은 것 같지만 .bss 밑에 넣었다.
   STM32CubeIDE에서는 STM32xxxx_FLASH.ld 파일과 STM32xxxx_RAM.ld 파일 두개가 생성되는데, 처음엔 RAM쪽에만 넣어 보았으나 할당이 안되어서 FLASH 쪽에도 넣어보니 할당이 되었다. 왜 그런지는 아직 모르겠다.

   STM32CubeIDE에서 프로젝트를 기본 생성하면 링커 스크립트를 STM32xxxx_FLASH.ld 파일로 로드한다. (프로젝트 설정 -> Tool Settings -> MCU GCC Linker -> General 참고) 환경에 따라 바꿔주면 됨.

  .dma_buffer : /*Space before ':' is critical */
  {
  	*(.dma_buffer)
  } >RAM_D2

  다음으로, 버퍼로 사용할 변수를 .dma_buffer 영역에 할당하면 되는데 이 또한 위의 문서에 기록되어 있고 아래와 같이 하면 된다.

#if defined( __ICCARM__ )
  #define DMA_BUFFER \
      _Pragma("location=\".dma_buffer\"")
#else
  #define DMA_BUFFER \
      __attribute__((section(".dma_buffer")))
#endif

DMA_BUFFER uint8_t	U3_rxbuf[U3_DMA_RX_BUFSIZE];

 

 2) MPU를 설정하여 D2 Domain의 Cache 사용을 비활성화한다.

  MPU설정이 조금 헷갈리고, 아직 모르겠는 부분은 있지만 아래와 같이 하였다.

  MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  MPU_InitStruct.BaseAddress = RAM_D2_ADDR;
  MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
  MPU_InitStruct.Number = MPU_REGION_NUMBER0;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
  MPU_InitStruct.SubRegionDisable = 0x00;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;

  HAL_MPU_ConfigRegion(&MPU_InitStruct);
  
  HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);

  여기서 중요 포인트는 BaseAddress 속성을 D2주소값으로 설정, IsCacheable 속성을 MPU_ACCESS_NOT_CACHEABLE 로 설정, 그리고 Size를 정해줘야 하는 부분인데 D2가 288KB인데 설정가능한 크기가 32Byte 부터 배수 값으로밖에 안돼서 그냥 좀더 작은 256KB 로 선택함. Number 속성은 AN4838 문서에 구체적이진 않지만 나와있는걸 참고하면 내부메모리 영역이라서 Region 0 으로 설정하면 되는 것으로 보인다.

(아니 그렇다면 문서에 따르면 SDRAM이면 FMC 라서 Region 2여야 맞는데 예제 보면 Region 3으로 설정하던데...뭐가 맞는지 아직 모르겠다)

그외에는 우선 기본값인데 아직 문제가 발생하지는 않았다.

 

아무튼, 위와 같은 방법으로 설정하니 UART DMA RX 의 버퍼가 정상적으로 채워지는 것을 확인할 수 있었다.

 

시스템 인터럽트 부담 줄여서 마음이 편하다(이게 왜 편한지 모르겠지만)

 


2022-08-23 덧붙임.

 

위와 같이 Cache를 비활성화한 영역에 할당된 변수를 접근하려고 하니, 

Unaligned Access 에 의한 Hardfault 가 발생하였다.

처음엔 해당 변수를 사용하려는 코드의 문제라고 생각하였으나, 결론은 MPU 설정 문제.

 

MPU_RASR Register 설정 시, 각 속성들을 별도로 설정할 수 있으면 좋겠지만 원하는 기능에 따라 이미 정해진 Table 에서 설정값을 골라야 하는 것으로 보인다. 

AN4838 문서의 Table 4. Cache properties and shareability 를 참고하면,

Non-cacheable 로 설정하기 위해서는 TEX 001, Cacheable 0, Bufferable 0, Shareable(don't care) 

의 설정이 필요하다.

CubeMX 로 생성된 코드는 아래와 같다.

 

  MPU_InitStruct.Enable = MPU_REGION_ENABLE;
  MPU_InitStruct.Number = MPU_REGION_NUMBER0;
  MPU_InitStruct.BaseAddress = 0x30000000;
  MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
  MPU_InitStruct.SubRegionDisable = 0x0;
  MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
  MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
  MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE;
  MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
  MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;

 

위와 같이 설정하니 Hardfault 에 빠지지 않고 정상적으로 Access 되는 것을 확인함.

언제나 그랬듯 나중에 또 같은 실수를 반복할테니 그때 빠른 수습을 위해 기록한다.