안녕하세요,


요 근래, 파일 시스템 테스트 하다가 안되서 고생고생 했네요.

그 고생한 내용은 다음과 같습니다.

파일 관련 함수는 거의 다 테스트를 했는데, f_read() 함수만 유독 실행이 될 때도 있고, 
어떤 때는 계속 실행이 안되고 Hardfault Error 인터럽트로 점프해서 멈춰 있었습니다.

같은(거의 비슷한?) 입력 파라메터를 갖고 있는 f_write() 함수는 또 잘 된다.
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from a file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to a file */

하~.., 그래서 사용자 함수를 하나 만들어서 파라메터를 f_read() 와 같이 정의를 해서 테스트를 해보니 f_read()함수와 같은 증상이 발생. ㅜㅜ

안되는 원인을 찾아 보려고 f_read()함수의 파라메터의 입력을 각각 1가지씩 사용자 함수에 적용해서 테스트를 해 봤더니,
입력 인자 중 void* buff 가 문제였습니다.

원인은 입력 인자인 배열(void *buff)을 f_write()에서 보다 많이 잡아서 Stack overflow 가 걸리는 것이었습니다.

에러가 나면 스택 오버플로우 인지 확인도 못하고(혹시 아시는 분 있으시면 방법을 알려 주시면 감사하겠습니다),
그냥 프로그램이 멈춰버리니.. 며칠을 밤새고 나서야 찾을 수 있었습니다.

이번 글의 요점은, 파일 시스템 관련 함수를 사용할 경우에는 스텍을 충분히 확보하라 입니다.

그럼, 다음은 스텍을 늘리는 방법입니다. 방법은 2가지가 있네요.
1. startup_stm32f411xe.s 파일을 열어서 다음과 같이 스택 사이즈를 변경합니다.



2. CUBE 툴에서 project > Setting 메뉴에서 수정할 수도 있습니다. (저는 큐브 툴로 KEIL 소스를 만들어서 이런 방법도 있음을 알려 드립니다)



스택이 0x400 일 때 멈춰버린 프로그램이..



스택을 0x800 으로 고치니까 아주 잘 됩니다. 잘 되서 f_lseek()함수도 사용해서 파일의 offset를 막 이동해 가면서 읽어서 UART로 마구마구 뿌려 댔습니다. ^^



그런데, 나중에 알게된 사실인데... 

큐브 툴의 Help 메뉴를 누르면 UM1718.pdf 파일이 열리는데, 이 곳의 7번의 제목의 글에 스택을 충분이 할당하라는 내용이 약간 나옵니다. ㅜㅜ 저와 같은 문제로 고생하는 사람이 많진 않지만 약간 있나 봅니다.
많았으면 반드시 스택을 많이 잡고 시작하라고 했을 텐데.





그리고 이 PDF의 파일 시스템 예제가 인터넷에 돌아다니는 예제 보다, 가장 잘 나온 것 같습니다.
참고하시면 도움이 될 것 같습니다.


STM32F4xx에서 uart2 로 printf()함수를 사용하는 방법을 테스트 해보고 정보를 공유합니다.


다음과 같은 코드를 main.c 파일에 추가하면 됩니다.

/* USER CODE BEGIN 0 */
#ifdef __GNUC__
 #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
 #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
 
PUTCHAR_PROTOTYPE
{
 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 1);
 return ch;
}
/* USER CODE END 0 */



이 코드를 추가하기 전에 huart2 는 정의를 해야겠죠.
그리고 uart HAL 함수 HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 1); 를 사용해서 폴링으로 1Byte 전송하도록 되어 잇습니다.


이번에는 GPIO에 여러핀을 동시에 출력하는 방법을 알아보겠습니다.


핀 설정도 동시에 할 순 있는데, 좀 귀찮아서 핀설정은 각 핀마다 했습니다.

이제 제가 프로젝트를 시작해야 해서, 프로젝트에서 사용해야 할 핀들을 다 포함시킨 상태에서
GPIO 병렬 포트로 사용할 핀을 지정을 해 봤습니다.

GPIO 설정은 Cube 툴에서 할 수 없으니까, 핀이 표시가 되지 않아서 따로 사용할 핀을 정리해 봤습니다.




그리고 현재 사용한 Nucleo 보드에서 핀헤더의 위치도 표시해 봤습니다.



자.. 그럼 핀 설정은 정리해 보자면,

DATA[7:2] : PA15,14,13,12,11,10

DATA[1:0] : PC7,6

SYNC CLK : PB5


핀 설정 코드는 1핀마다 설정을 했더니 너무 길어서 소스코드 첨부해 놓을 테니 참고하시기 바람니다.

main.c 파일 안에 static void MX_GPIO_Init(void) 함수 보시면 되겠습니다.


이렇게 설정을 했습니다. 간단히 설명을 해 보자면, SYNC CLK 의 Rising Edge 마다 8비트 데이터를 출력 하는 테스트 입니다.

8비트 병렬 통신을 구현할 생각이고, STM 칩으로 8비트 데이터를 보내고 SYNC 출력을 1번 내보내는 겁니다.


8번 출력(0x01->0x02->0x04->0x08->0x10->0x20->0x40->0x80) 을 반복해서 내보내도록 했습니다.


다음은 main.c 의 main()함수의 주요 동작 코드입니다.


  for (i=0;i<8;i++)

  {

par_data[i] = (1<< i);

      // (0x01->0x02->0x04->0x08->0x10->0x20->0x40->0x80) 데이터 생성

  }


  while (1)

  {

par_ReAssemble(par_data,8);

       // (0x01->0x02->0x04->0x08->0x10->0x20->0x40->0x80) 데이터 출력 + SYNC_CLK 출력

HAL_Delay(1);

  }




현재 핀을 여러용도록 사용했더니, 출력할 포트가 2개로 나뉘어 버려서 8비트 데이터를 재정렬하도록 프로그램을 만들었습니다.

재정렬 함수의 코드는 다음과 같습니다.

void par_ReAssemble(uint8_t *par_data,uint16_t cnt_data)

{

uint16_t i;

for (i=0;i<8;i++)

{

// DATA[7:2] : PA15,14,13,12,11,10

GPIOA->ODR = (((uint32_t)par_data[i]) << 8)&0xFC00;

// DATA[1:0] : PC7,6

GPIOC->ODR = (((uint32_t)par_data[i]) << 6)&0x00C0;

//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_SET);

//HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_RESET);

GPIOB->BSRR = GPIO_PIN_5;

GPIOB->BSRR = (GPIO_PIN_5<<16);

}

}


위의 함수에서 GPIOA->ODR 이 보이시죠?

이 레지스터가 각각 16비트폭으로서, 병렬 포트를 출력하도록 하는 레지스터입니다.

쉬프트 연산과 마스크 연산을 해서 병렬포트 출력 레지스터에 값을 쓰면 해단 핀들로 0또는 1이 출력 됩니다.


1비트 출력은 2가지 방법이 있습니다.

1. HAL 드라이버에서 제공하는 함수를 사용.

HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5,GPIO_PIN_SET);

2. 직접 레지스터에 쓰기.

GPIOB->BSRR = GPIO_PIN_5;

GPIOB->BSRR = (GPIO_PIN_5<<16);


HAL_GPIO_WritePin 함수는 누구나 인식하기 쉬워서 보면 아시겠고,

BSRR 레지스터는 32비트 구조인데, 1을 해당 비트에 써주면 하위 16비트는 SET, 상위 16비트는 RESET 기능을 합니다.




위에서 2가지 방법이 있다고 했는데, 사실은 HAL.. 함수에서 BSRR 레지스터를 사용하고 있어서 본래는 1가지 방법입니다.

그런데 제가 왜 레지스터에 집접 썼는가 하면 HAL.. 함수가 너무 느려서 입니다. 

테스트를 해 본 결과, 레지스터를 사용한 방법보다 2배나 느리더군요.


다음은 같은 동작을 수행했을 때, 왼쪽이 HAL 함수, 오른쪽이 레지스터에 직접 쓴 방법을 사용한 결과 입니다.



다음은 8비트로 1바이트를 보냈을 때, 직접 속도를 계산해 봤습니다. 



나중에 설명을 하려고 했는데, 먼저 해 버렸네요.

이게 제가 알려드리고자 한 핵심이고 앞으로 설명할 나머지는 크게 중요하진 않습니다.


사용자의 편의성이냐? 속도냐? 둘 중 하나를 선택하시면 됩니다.


그런데.. 저 파형이 링잉이 발생하는게 거슬리네요. 파형이 원래 그런것인지 오실로 스코프가 문제인지.. 궁금하네요. 내일 오실로 스코프 바꿔서 테스트 해 봐야겠습니다.



다음은 HAL 함수를 사용해서 프로그램을 돌려 본 결과 입니다.

검증을 위해 4채널 오실로 스코프를 이용해서 1로 예상되는 포인트만 찍었습니다. 9개의 신호를 한번에 찍을 수가 없어서 3개의 프로브를 나눠서 측정을 했습니다.


[ SYNC_CLK 와 DATA 0,1,2 ]



[ SYNC_CLK 와 DATA 2,3,4 ]



[ SYNC_CLK 와 DATA 5,6,7 ]



다행이 예상대로 잘 나왔네요.


그럼 이만 마치겠습니다.


소스 코드 첨부 합니다.

perallel_GPIO.zip



구글 크롬을 실행하고, 다음의 사이트로 갑니다.


여기서 platform -> Development Boards 로 가서, Boards 를 클릭해 주면 이 사이트에서 컴파일할 수 있는 보드들의 목록이 나옵니다.





저는 STM32F411 MCU로 된 NUCLEO-F411 을 갖고 있는데, 미리 지원하는 보드를 알아보고 국내 판매 사이트에서 1개 사셔도 괜찮겠습니다. 어떤 것은 1만원 정도밖에 안하네요.
게중에 NECLEO-F103RB는 아두이노를 이식할 수도 있으니 재밌는 작업을 많이 할 수 있겠네요.
제가 사용하는 보드가 여기 있네요. NECLEO-F411RE



$12.74 밖에 안한답니다.



다시 home 으로 이동해서 developer Resource 를 클릭한 다음 Compiler를 클릭합니다.



그러면, 회원 가입하는 화면이 나옵니다. 여기 가입해야만 컴파일러를 쓰실 수 있습니다. ^^



log in 하면, 크롬 웹에서 바로 컴파일러가 실행 되는데, 저는 이전에 Platform을 선택했나 봅니다. 이전에 테스트 한 프로젝트가 서버에 기록되어 있군요. 혹시 Platform 을 선택하지 않았거나 추가할 경우는 오른쪽 위의 그림을 클릭 해 주시면 됩니다. Platform을 선택하지 않았으면 자동으로 선택 창이 뜰 것 같은데.. 이전의 상황이 기억이 안나는군요.



다음은 위에서 클릭하면 뜬다고 설명했던 platform 선택 및 추가 하는 창입니다.



플랫폼 선택하고 나서, 새로운 프로그램을 하나 만들어 봅시다. 왼쪽 위에 new를 누르면 창이 하나 뜸니다.



해당하는 보드의 플랫폼을 선택하고 탬플릿을 눌러 보면 다양한 예제들이 나옵니다. 제가 Cube 툴과 Keil 툴로 개고생해서 만들었던 예제들이 다 들어 있네요. ㅠㅠ SDIO 로 구현한 u-SD-Card 예제는 없네요.



Blinky LED test for the ST Nucleo boards 플랫폼을 선택해서 프로그램을 하나 만들어 봅니다.



OK 를 누르면 다음과 같은 창이 뜨고, 컴파일 버튼을 눌러 봅니다.



컴파일 버튼을 누르면 프로그램이 컴파일되고 난 결과물인 Nucleo_blink_led_NUCLEO_F411RE.bin 파일이 자동으로 다운로드 폴더에 저장 됩니다.



이 파일을 폴더 열기로 열어서, bin 파일을 ST-LINK 디버거에 의해 자동으로 생성된 USB DISK인 NODE_F411RE(J:) 에 드래그-앤-드롭 해 주면 자동으로 프로그램이 STM32F411 MCU에 다운로드된 후, 프로그램이 실행됩니다.


Nucleo 보드의 녹색 LED가 깜박깜박 하고 있군요. ^^


이번에는 SPI 통신을 테스트 해 봤습니다.


원래는 WIFI 모듈을 연결하여 테스트를 진행하려고 했는데, 
회사에서 WIFI 모듈은 잠깐 미뤄두고 급한 일을 먼저 처리하라고 해서..
IC를 연결하지 않고 SPI Write만 동작시키고, 파형만 오실로 스코프로 찍어 봤습니다.


Nucleo 보드의 SPI 용 핀 할당은 다음과 같습니다.



STM32F411 의 SPI용  핀할당 내용입니다.
SPI_CS 는 일반 GPIO로 제어했습니다.



아무 설정도 안하고 기본 SPI 스피드는 42MHz 입니다. Cube 툴에 표시 되네요.



다음은 SPI_CS 를 일반 GPIO 출력으로 설정하는 코드 예제입니다.
PA8 핀을 사용했습니다. 설정은 내부 풀업,출력,Fast Speed,초기값 HIGH  입니다.



기본 SPI_CLK 스피드인 42MHz 로 출력을 테스트 해 보니, 오실로 스코프가 파형을 못 쫒아가서 파형이 엉망으로 나오네요.



다음은 42MHz SPI CLK 확대한 그림입니다. 스코프가 못 쫒아갑니다. 오실로 스코프 회사에 연락해 보니, 측정할 클럭의 10배는 되야 보인다 하더군요. 42MHz 이면 500MHz 오실로 스코프가 필요하단 말이네요. 제가 갖고 있는 건, 200MHz 입니다.



다음은 SPI_CLK를 21MHz로 설정한 소스 코드입니다. 



이 코드로 동작시킨 21MHz SPI_CLK 과 파형입니다.



그리고 다음은 21MHz SPI_CLK 확대 파형입니다. 클럭이 잘 보이네요.



그런데 이상한 점은 42MHz 나 21MHz 나 통신 속도가 똑같다는 점입니다.
42MHz가 전송 속도는 21MHz 보다 2배는 빨라야 하는데, 10 Byte 전송 테스트시 같은 시간이 걸린다는 게 좀 의아하네요.

좀 전에, 스코프 회사에서 점검하고 갔는데.. 아무리 해도 개선이 안되네요.
금요일에 300MHz 오실로 스코프를 가지고 온다니까, 스코프가 문제인지 파형이 문제인지 확인할 수 있겠습니다.


DMA 나 인터럽트로 동작시켜야 SPI_CLK 사이의 구간이 짧아 지려나? 
현재는 회사 다음 일정이 바빠서 인터럽트나 DMA 테스트는 나중에 진행하겠고, 
테스트 후에 자료를 다시 올리겠습니다.





테스트 한 소스코드 첨부합니다.

SPI_test.zip


일단 동작이 되서 아무생각 없이 넘어 갔는데,

데이터 전송률을 계산하려다보니 SDIO 클럭을 계산하는 방법을 찾아 봤습니다.

설명이 명확하게 안나온 것인지, 제가 이해를 못 한 것인지 ... 해서 또 노가다를 뛰었습니다.

레퍼런스 메뉴얼에는 다음과 같은 블럭도가 있었습니다.



내용은 SDIO 는 PCLK2 와 SDIOCLK 2개를 사용한다는 것이고,
실제 외부에 나와있는 SDIO_CK 는 SDIOCLK 와 관계가 있고,
SDIOCLK 는 48MHz 이다.

그런데, "SDIOCLK 는 PCLK 와 관계가 없는것인가? " 라는 내용을 잘 찾을 수가 없었습니다.
관계가 있는지 없는지 확인을 해 봤습니다.



APB2CLK 을 2분주하고 SDIO_CK 를 오실로 스코프로 찍어 보니, 아무런 변화가 없었습니다.
즉 PCLK2 하고는 관계가 없다고 볼 수 있겠죠. 그렇다면 SDIOCLK 는 48MHz로 내부에서 PCLK와 상관없이 고정된 틀럭이라고 보면 될 것 같습니다.


그러면 SDIO_CK 는 어떻게 설정을 하는가를 찾아 보니, CUBE 툴에 다음과 같은 내용이 있었습니다.



SDIO_CK = SDIOCLK / [CLKDIV + 2] 라고 되어 있습니다.
현재 설정은 void MX_SDIO_SD_Init() 함수에서 CLKDIV 가 0으로 되어 있으니 SDIO_CK = SDIOCLK(48MHz) / [0 + 2] = 24MHz 입니다.
실제로 오실로 스코프로 봤습니다.



계산상으로 21MHz 이니까, 거의 24MHz 가 나오네요. 대략 맞네요.

그럼 CLKDIV를  2로 해서 4분주를 하면 12MHz 정도 나올 것으로 예상하고 오실로 스코프로 측정을 해 봤습니다.





SDIO_CK 가 계산상으로는 10MHz 로 대략 12MHz 정도로 일치한다고 봅니다. 눈으로 보고 측정을 하니 오차가 좀 있네요.


따라서 결론은,
1. SDIO_CK 는 SDIOCLK 하고만 관계가 있고, 
2. SDIOCLK는 48MHz로 고정된 독립된 클락이다.
3. SDIO_CK 의 속도를 조정하는 공식은 SDIO_CK = SDIOCLK / [CLKDIV + 2] 이다.


STM32F4xx 의 SDIO 통신방식으로 u-SD Card 를 다뤄 보겠습니다.


여러가지 예제를 보고 그대로 했는데도 자꾸 실수를 하는 바람에,
동작 시키기까지 하루 종일 걸렸네요. ^^

SD 카드 모듈은 Waveshare 에서 만든 그냥 u-SD Card 모듈인데, 엘레파츠에서 샀습니다.



Nucleo 보드와 SD Card 모듈의 H/W 연결은 다음과 같습니다.



실제 연결한 그림입니다. 사진을 잘 못 찍어서 이렇네요. ^^



SDIO의 큐브에서 설정은 다음과 같습니다. SDIO 를 넣었더니 clock 설정에서 APB1 peripheral clocks(MHz) 가 42MHz 이하로 줄여야 한다고 에러가 나서 42MHz로 맞추다 보니 시스템 클럭은 84MHz로 줄어 버렸습니다.



SDIO 4Bit 핀아웃은 다음과 같습니다. 데이터버스 4개,CLK,CMD 로 총 6개의 I/O 가 사용되고 경우에 따라서 CD(Card Detect) 입력을 1개 쓸 수도 있습니다. 일단은 테스트니까, CD는 사용 안했습니다.



프로그램은 그냥 Polling으로 처리했습니다. 나중에 DMA를 사용하면 아주 빠르다고 하네요.
프로그램은 
1. SD card 초기화하고, 
2. 텍스트 파일을 1개 만들어서 , 
3. 파일에 스트링을 몇자 쓰고 ,
4. 파일을 닫아서,
5. PC에서 SD Card를 읽어서 확인해 봤습니다.

다른 것은 별 어려움이 없었는데, 4Bit Wire로 바꾸는 것이 좀 어려웠습니다.
어딘가에서 바뀔 수도 있는지 모르겠는데, BSP_SD_Init(); 함수를 실행해야 4비트 방식으로 바뀌는 것인데..
프로그램 어디에도 BSP_SD_Init();함수를 실행하지를 않아서 SD Card 초기화 하고 나서 main()함수안에 BSP_SD_Init(); 함수를 실행하도록 수정했습니다.

int main(void)
{

  /* USER CODE BEGIN 1 */
    uint8_t i;
    char tx_buf[100];
    uint8_t file_path[13] = "testfile.txt";
    file_path[12] = '\0';
    uint8_t testBuffer[16] = "SD write success";
    UINT testBytes;
  /* USER CODE END 1 */

  /* MCU Configuration----------------------------------------------------------*/
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM3_Init();
  MX_USART2_UART_Init();
  MX_I2C1_Init();
  MX_SDIO_SD_Init();
  MX_FATFS_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();

  /* USER CODE BEGIN 2 */
for (i=0;i<10;i++)
{
sprintf(tx_buf,"UART Test ... [%3d] \n\r",i);
HAL_UART_Transmit(&huart2,(uint8_t *)tx_buf,cus_Size_of(tx_buf),3);
}
HAL_TIM_Base_Start_IT(&htim3);

BSP_SD_Init();

   :
   :
   :
}




다음은 이 프로그램에 의해 UART로 response 를 출력해 본 내용입니다. (제가 디버그용으로 UART를 주로 쓰는 편이라..)
response 가 0 이면 정상입니다.



다음은 SD card 내용과 그안에 만들어진 텍스트 파일의 내용입니다.





소스파일 첨부 합니다.


I2C 통신으로 간단하게 EEPROM(AT24C04) 를 읽기/쓰기 테스트 해 봤습니다.


예전에 PSOC4로 해 놓은 자료가 있으니까, 자세한 설명은 빼고 동작 테스트만 해 보겠습니다.

먼저 I2C 인터럽트를 사용하여 데이터를 보내고 받는 함수들은 다음과 같습니다. 
1. HAL_I2C_Master_Transmit_IT()
   : I2C 인터럽트를 사용하여 데이터를 보내는(Write) 함수
2. HAL_I2C_MasterTxCpltCallback()
   : I2C 인터럽트를 사용하여 데이터를 보내고 다 보내면 인터럽트에 의해 호출되는 함수
3. HAL_I2C_Master_Receive_IT()
   : I2C 인터럽트를 사용하여 데이터를 받는(Receive) 함수
4. HAL_I2C_MasterRxCpltCallback()
   : I2C 인터럽트를 사용하여 정해진 개수의 데이터를 다 받으면 인터럽트에 의해 호출되는 함수

위의 함수들은 stm32f4xx_hal_i2c.c 파일에 정의되어 있습니다. 
HAL_I2C_MasterTxCpltCallback(), HAL_I2C_MasterRxCpltCallback() 과 같은 콜백함수는 __weak 키워드를 없애고
main.c 파일에 복사해서 내부 내용을 바꿔서 쓰면 인터럽트가 걸릴때 마다 콜벡함수를 호출하게 됩니다.

이 파일의 위치는 다음과 같습니다.



친구 돌잔치를 가봐야 해서 main.c 에서 EEPROM 읽고 쓰는 부분을 그림으로 올려 놓겠습니다.







[16 Byte Write 오실로 스코프 파형]





[Word Address Write 오실로 스코프 파형]





[16 Byte Read 오실로 스코프 파형]



소스 파일 첨부합니다.

I2C_test (2).zip


이제 I2C 통신 테스트를 해 보려고 합니다.


먼저 Cube 툴에서 I2C1 을 선택했더니, PB6(I2C1_SDA) , PB7(I2C_SCL) 로 핀아웃이 배정되는군요.



Nucleo 보드의 핀위치는 다음과 같습니다.



현재 제가 갖고 있는 I2C 테스트하기 가장 좋은 놈이 하나 있군요. 예전에 PSOC4로 I2C를 테스트하려고 산 EEPROM 모듈(AT24C04)가 있어서 Nucleo 보드와 위의 핀에 연결해 봤습니다.



I2C 테스트 코드를 IRQ 용으로 작성을 했는데, 아직은 IRQ 처럼 사용하지는 않았습니다.
그저 Write 테스트를 해 보려했습니다.

I2C Write 방식은 
'시작 + 7비트 DEVICE ADDRESS + R/W + ACK(EEPROM 에서 응답) + 끝' 이 기본이고,

여기서 추가로 데이터를 몇개 더 보낼 때는,
'시작 + 7비트 DEVICE ADDRESS + R/W + ACK(EEPROM 에서 응답) + 8비트 데이터#1 + ACK(EEPROM 에서 응답)
 + 8비트 데이터#2 + ACK(EEPROM 에서 응답) + ....+ 8비트 데이터#n + ACK(EEPROM 에서 응답) + 끝' 과 같이 하면 됩니다.

I2C 디바이스는 각각 고유의 DEVICE ADDRESS가 있는데, 위의 AT24C04 모듈의 DEV ADDR 은 0x50 입니다.
따라서 main.c 에 어드레스를 다음과 같이 정의했습니다.
#define I2C_ADDRESS        0x50

그리고 main() 함수에 다음을 추가해서 오실로 스코프로 찍어볼 준비를 했구요.
(I2C 설정은 Cube 툴에서 해주니 설정에 관한 설명은 생략합니다)

   i2c_tx_buf[0] = 0x01;
   i2c_tx_buf[1] = 0x02;
   i2c_tx_buf[2] = 0x03;
    if(HAL_I2C_Master_Transmit_IT(&hi2c1, (uint16_t)I2C_ADDRESS, i2c_tx_buf, 3)!= HAL_OK)
    {
      /* Error_Handler() function is called in case of error. */
      Error_Handler();
    }

그런데 스코프로 찍어 보니, 7비트 어드레스만 우측으로 1비트 밀려서 나가고 더이상 데이터가 나오지 않아서
자세히 보니, 어드레스를 좌측으로 1비트 밀어야 ACK가 응답으로 오면서 후속 데이터가 제대로 나가겠구나 .. 라는 생각이 드네요.

그래서 DEVICE Address 를 다음과 같이 수정했고,
#define I2C_ADDRESS        (0x50 << 1)

오실로 스코프의 파형도 정상으로 나왔습니다.



소스 파일 첨부합니다.

I2C_test.zip


이번에는, 이전 소스 코드에, UART2 인터럽트 처리 함수를 추가해 봤습니다.


STM32F4xx 의 UART 데이터 처림 방식에는 3가지가 있습니다.
1. Polling mode
 보내고 받는 동작에서 다른 일을 못하고 상태를 지켜 보고 있어야 해서 효율이 떨어진다.
2. Interrupt mode
 보내고 받는 동작이 완료되면, 인터럽트 함수로 점프해서 특정 동작을 처리하고 돌아온다.
보낼 경우는 몇개의 데이터를 보내는지 알아서 딜레이를 주던가, 주기적으로 상태를 체크하면서 동작 시키면 되는데,
받을 경우에는 가변 길이의 프로토콜일 경우 1개의 데이터가 받아질 때마다 인터럽트 처리를 해야 하는데 
데이터 전송률이 높을 수록, 또 수신 데이터가 많아질 수록 함수를 자주 호출하므로 다른일을 못하게 되어 효율이 떨어진다.
3. DMA mode
가장 효율이 좋은 방식으로, 인터럽트 처리 함수를 호출할 필요가 없이 메모리에 직접 데이터가 받아지므로,
주기적으로 받은 데이터를 링버퍼에 쌓아서 처리하면 된다.
외국에서 DMA방식을 써서 많은 잇점이 있다고 하는 글을 첨부해 봅니다.

DMA allows the UART to store data in memory WITHOUT interrupting the processor until the buffer is full saving lots of overhead.

In my case, I told PDC to store UART data into an buffer (byte array) and specified the length. When UART via PDC(Peripheral DMA controller) filled the buffer the PDC issued an interrupt.

In PDC ISR:

  1. Give PDC new empty buffer
  2. Restart UART PDC (so can collect data while we do other stuff in isr)
  3. memcpy full buffer into RINGBUFFER
  4. Exit ISR

As swineone recommended above, implement DMA and you'll love life.


저는 DMA는 링버퍼 구현 때문에 시간이 좀 걸릴 것 같아서, 
우선은 다른 구현해야할 동작을 먼저 하고 일단은 RX 인터럽트만 동작을 시켜봤습니다.

이전에 Cube툴에서 UART2 인터럽트를 설정해 놓고 코드를 생성했기 때문에,
이전 글에서 첨부한 소스코드에 Rx 인터럽트 처리함수만 추가해서 1바이트 받으면 바로 TX로 뿌려주는 동작만 구현했습니다.

타이머 인터럽트 처리함수처럼 UART인터럽트도 사용자 콜백함수가 있었습니다.
이 함수 역시도 __weak 키워드가 붙어 있고 이것을 사용하려면 사용할 사용자 파일에 복사해서 붙여넣기를 해서
__weak 키워드를 지워주고 함수 안의 내용을 입맛에 맞게 고쳐주면 됩니다.

처음에 할 일은, 설정은 이미 자동으로 코드가 생성되어 완료가 됐고 
1. "내가 uart2 포트(huart2)로 인터럽트 방식을 사용해서 특정 변수에(uart2_rx_ch) 1바이트를 받겠다" 라는 명령을 실행시킵니다.
 HAL_UART_Receive_IT(&huart2,&uart2_rx_ch,1);

2. 위의 코드가 실행되면 uart2로 1바이트의 데이터가 수신될 때마다 인터럽트가 걸리는데,
이 때마다 위의 설명에서 말씀드린 사용자 콜백함수로 점프해서 해당 코드를 실행하고 복귀합니다.
아까 말씀 드린 stm324xx_hal.c 파일에 있던 __weak 키워드가 붙어있는 사용자함수를 main.c 파일에 붙여 넣고 함수 내부 코드를 수정한 함수는 다음과 같습니다.



코드의 내용은 "받은 데이터를 폴링방식으로 TX로 출력하고, 다시 인터럽트로 1바이트 받겠다" 입니다.
일종의 echo 동작이죠.
DMA 까지 필요없는 분들은 이 함수를 수정해서 링버퍼에 쌓아서 쓰시던가 그냥 입맛에 맞게 적당히 고쳐서 쓰시면 되겠습니다.

테스트 결과는 다음과 같습니다.



소스코드 첨부 합니다.

timer3_test2.zip




+ Recent posts