휴, 밤새 테스트해보다 겨우 됐네요.


이번에 다룰 내용은, STM32F4xx 의 uart Rx 에서 DMA 함수를 사용하면 발생하는 불편함을 수정하는 내용입니다.
DMA를 사용하면, RX data를 수신할 때, 1-Byte마다 인터럽트가 걸리지 않으므로 그 시간만큼 다른 일을 할 수가 있습니다.
일종의 코프로세서라고 말하는 사람도 있습니다. 또는 듀얼코어..

그런데 UART DMA RX 는 HAL 함수에서, 또는 다른 라이브러리 함수에서(다른 함수들은 안써봐서 잘 모름),
지정한 길이 만큼만 받아야 인터럽트가 발생해서,
지정한 길이만큼 받지 않으면 데이터를 갖고 올 수가 없었습니다.

또한 UART RX 시에는 언제, 몇개의 데이터가 들어 올지 미리 알 수가 없는 상황이 대부분이라,
저 같은 경우에는 UART RX DMA를 쓰는 경우는 전혀 없습니다. 쓰려면 TX DMA 를 쓰죠.

예를 들자면 
HAL_UART_Receive_DMA(&huart2,DMA_RX_Buffer,10); 
위와같이 특정 개수 만큼(10개) DMA 로 받겠다고 하면, 
10개 이하는 DMA 인터럽트가 안 떠서 데이터를 못 받고,
10개 이상 받으면, 10개는 받고 나머지 10개 이상은 받지 못하게 됩니다.

이와 같은 점을 수정한 방법이 UART_IDLE 인터럽트를 쓰는 방법입니다.
UART_IDLE 인터럽트는 데이터를 수신하다가 1개 이상 데이터가 수신되지 않을 시 걸리는 인터럽트입니다.
보통 연속으로 데이터가 오다가, 다 보냈을 경우 1개 이상의 데이터가 수신되지 않는 경우가 무조건 발생하게 됩니다.
이 때, UART RX DMA 인터럽트를 강제로 발생시키도록 하는 원리 입니다.

이 내용은, 예전부터 외국 블로그에서 공개한 내용인데 실제로 사용할 수 있도록 테스트를 해 보지 못했습니다.
참고로 한 사이트 주소는 다음과 같습니다.


사용한 H/W 는 ST 의 NUCLEO-F411RE 입니다.
UART만 테스트 하는 거라, 이미 내부 디버거(ST-LINK)에 의해 USB-to-UART 연결된 UART2를 사용했습니다.

H/W 구성 및 CUBEMX 설정은 다음과 같습니다.




이전에 쓴 글([STM32F4xx] I2C DMA 설정 (CUBEMX))에 이어서, 

프로그램에 대해서 설명해 보겠습니다.
I2C DMA 및 인터럽트, 추가로 시퀀셜 제어까지 모두 설명해 보겠습니다.

이번에도, 보안칩 회사의 철통같은 보안으로 메뉴얼 조차도 보안적으로 설명을 좀 빼먹어서,
I2C 시퀀셜 송/수신 함수까지 써 보게 되었습니다. ^^

열심이 공부해서, 보안 칩의 보안을 깨 버리겠습니다. ^^
이미 I2C DEVICE ADDRESS 안 알려줘서 찾아내 버렸습니다. ㅋㅋ

이 보안칩은 저희 사장님의 친구인 사장님이 만든 거라는데, 가장 싼 모델이랍니다.
그래서 인지, 이름도 없고 핀 번호 표시도 하얀 페인트로 칠해져 있어서 알았습니다.
보안상 그런게 아니라, 진짜 이름도 모릅니다. ^^

I2C 의 전송 HAL LIB 함수를 사용했는데, 여기서 사용한 함수는 다음과 같습니다.
모두 인터럽트를 사용하고, 같은 콜백함수(Tx 함수는 Tx 콜백,Rx 함수는 Rx 콜백)를 호출합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// DMA Function
// DMA Transmit Function
HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
// DMA Receive Function
HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
 
// Interrupt Function
// Interrupt Transmit Function
HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
// Interrupt Receive Function
HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
 
// Interrupt Sequential Function
// Interrupt Sequential Transmit Function
HAL_StatusTypeDef HAL_I2C_Master_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions)
// Interrupt Sequential Receive Function
HAL_StatusTypeDef HAL_I2C_Master_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions)
cs

지금까지 Sequential 함수는 사용할 일이 없었는데, 보안칩 메뉴얼 때문에 반드시 사용해야 하는 줄 알고 쓰게 됐습니다. ㅠㅠ

보안 칩 메뉴얼에 Transmit 함수는 그냥 IT 함수나 DMA 함수를 쓰면 되는데,
Receive 할 때 Device Address 와 Sub Address를 쓰고 나서 STOP 을 하지 말라고 해서, 방법을 찾아보니 Sequential 함수라는 놈이 있었습니다.
다음은 제가 참고로 본 보안 칩 메뉴얼 중에서, Tx/Rx 송/수신 정보 입니다.





여기 아랫 쪽에, Non STOP 보이시죠. 젠장 욕나온다. ㅜㅜ
나중에 테스트를 해보니까, STOP 하고 읽어도 되는 것이었습니다. DMA 나 일반 IT 함수 쓰면 되는 것이죠.





아뭏든 DMA 함수의 사용 방법은 다음과 같습니다.

송신시, HAL_I2C_Master_Transmit_DMA() 함수를 사용합니다.
함수 프로토타입은 다음과 같습니다.
1
HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
cs
사용예는 다음과 같습니다.
1
HAL_I2C_Master_Transmit_DMA(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_wr_buff,9);
cs

수신시, HAL_I2C_Master_Receive_DMA() 함수를 사용합니다.
함수 프로토타입은 다음과 같습니다.
1
HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
cs
사용예는 다음과 같습니다.
1
HAL_I2C_Master_Receive_DMA(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_rd_buff,8);
cs


기본 IT 함수의 사용 방법은 다음과 같습니다.
송신시, HAL_I2C_Master_Transmit_IT() 함수를 사용합니다.
함수 프로토타입은 다음과 같습니다.
1
HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
cs
사용예는 다음과 같습니다.
1
HAL_I2C_Master_Transmit_IT(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_wr_buff,1);
cs

수신시, HAL_I2C_Master_Receive_IT() 함수를 사용합니다.
함수 프로토타입은 다음과 같습니다.
1
HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size)
cs
사용예는 다음과 같습니다.
1
HAL_I2C_Master_Receive_IT(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_rd_buff,8);
cs


Sequential IT 함수의 사용 방법은 다음과 같습니다.
송신시, HAL_I2C_Master_Sequential_Transmit_IT() 함수를 사용합니다.
함수 프로토타입은 다음과 같습니다.
1
HAL_StatusTypeDef HAL_I2C_Master_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions)
cs
사용예는 다음과 같습니다.
1
HAL_I2C_Master_Sequential_Transmit_IT(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_wr_buff,1,I2C_FIRST_FRAME);
cs

송신시, HAL_I2C_Master_Sequential_Receive_IT() 함수를 사용합니다.
함수 프로토타입은 다음과 같습니다.
1
HAL_StatusTypeDef HAL_I2C_Master_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions)
cs
사용예는 다음과 같습니다.
1
HAL_I2C_Master_Sequential_Receive_IT(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_rd_buff,8,I2C_LAST_FRAME);
cs

Sequential IT 함수에서 좀 설명할게 있는데, 함수의 마지막 입력 파라메터인 XferOptions 가 이 함수의 특징을 좌우합니다.
이 변수의 정의는 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/** @defgroup I2C_XferOptions_definition I2C XferOptions definition
  * @{
  */
#define  I2C_FIRST_FRAME                0x00000001U
#define  I2C_NEXT_FRAME                 0x00000002U
#define  I2C_FIRST_AND_LAST_FRAME       0x00000004U
#define  I2C_LAST_FRAME                 0x00000008U
 
/*
(+) A specific option field manage the different steps of a sequential transfer
(+) Option field values are defined through @ref I2C_XFEROPTIONS and are listed below:
(++) I2C_FIRST_AND_LAST_FRAME: No sequential usage, functionnal is same as associated interfaces in no sequential mode 
(++) I2C_FIRST_FRAME: Sequential usage, this option allow to manage a sequence with start condition, address
                      and data to transfer without a final stop condition
(++) I2C_NEXT_FRAME: Sequential usage, this option allow to manage a sequence with a restart condition, address
                     and with new data to transfer if the direction change or manage only the new data to transfer
                     if no direction change and without a final stop condition in both cases
(++) I2C_LAST_FRAME: Sequential usage, this option allow to manage a sequance with a restart condition, address
                     and with new data to transfer if the direction change or manage only the new data to transfer
                     if no direction change and with a final stop condition in both cases
*/
cs

I2C_FIRST_AND_LAST_FRAME은 시퀀스 없이 사용하는 것과 마찮가지란 말입니다. 그냥 일반 IT 함수죠.
I2C_FIRST_FRAME은 처음 일반 IT 처럼 시작하지만, 마지막에 STOP 이 아닌 상태로 됩니다.
I2C_NEXT_FRAME은 중간에 Restart 상태로 시작하고, 마지막에 STOP 이 아닌상태로 됩니다.
I2C_LAST_FRAME은 중간에 Restart 상태로 시작하고, 마지막에 STOP 상태로 됩니다.


그래서 저는 보안칩 메뉴얼 상, 수신시 패킷 구조가 1번 전송하고 NO STOP 으로 중간에 읽고 끝내기 위해서,
HAL_I2C_Master_Sequential_Transmit_IT() 함수는 I2C_FIRST_FRAME ,
HAL_I2C_Master_Sequential_Receive_IT() 함수는 I2C_LAST_FRAME으로 사용했습니다.
즉 다음과 같은 예처럼 사용했습니다.
아래 내용을 참고하실 때, // wait for Tx Callbackfunction process이 있는데, 
저는 귀찮아서 HAL_Delay()를 써버렸는데, Ready 를 체크한다던지, 콜백함수로 부터 플레그 변수가 변하는 것을 감지해서
완료가 된는지를 검사하셔야 합니다. (그냥 똑같이 주석만 처리하시까봐 알려 드립니다)
저는 테스트 용도로 코드를 만들었기 때문에 HAL_Delay(10); 을 넣었었습니다. ^^ 
1
2
3
4
HAL_I2C_Master_Sequential_Transmit_IT(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_wr_buff,1,I2C_FIRST_FRAME);
// wait for Tx Callbackfunction process
HAL_I2C_Master_Sequential_Receive_IT(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_rd_buff,8,I2C_LAST_FRAME);
// wait for Rx Callbackfunction process
cs

그런데, 나중에 테스트 해보니까.. 중간 상태가 반드시 NO STOP 이 아니라, STOP 이어도 무방했습니다.
그래서 DMA 와 일반 IT 함수를 사용해도 상관이 없었습니다. 아휴.. 회사 일이 좀 늦어졌고, 업무상 필요없는 짓을 한거죠.
그래도 앞으로 쓸 일이 있겠죠. 뭐. ^^
보안 칩에서, 위의 예와 같은 동작을 하는 DMA 코드는 다음과 같습니다.
1
2
3
4
HAL_I2C_Master_Transmit_DMA(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_wr_buff,1);
// wait for Tx Callbackfunction process
HAL_I2C_Master_Receive_DMA(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_rd_buff,8);
// wait for Rx Callbackfunction process
cs
보안 칩에서, 위의 예와 같은 동작을 하는 IT 코드는 다음과 같습니다.
1
2
3
4
HAL_I2C_Master_Transmit_IT(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_wr_buff,1);
// wait for Tx Callbackfunction process
HAL_I2C_Master_Receive_IT(&hi2c1,(uint16_t)(SECU_SL_ADD<<1),i2c_rd_buff,8);
// wait for Rx Callbackfunction process
cs


그리고 인터럽트는 (송/수신이 완료된 시점에서) 일반 IT 함수,시퀀셜 IT 함수DMA 함수 모두 걸리고,
송/수신 완료시 실행되는 콜벡함수는 다음과 같습니다. stm32f4xx_hal_i2c.c 파일 안에 __weak 으로 되어 있는데,
main.c 에다 카피한 후, __weak 지우고 인터럽트에 의해 콜되었을 때 실행할 코드를 넣어 주면 됩니다.
1
2
3
4
5
6
7
8
9
10
11
// Tx INT Complete
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
// insert user code
}
 
// Rx INT Complete
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
// insert user code
}
cs


이번에 사용하는 보안 칩이 I2C로 되어 있어서, I2C 통신에 대해서 써 보겠습니다.


예전에 적은 글이 너무 허접해서, 다른분들이 봐도 도움이 안될 듯 해서 다시 한번 써 봅니다.

회사에 아주 많이 굴러다니는 F4 개발 키트중에 골라 잡으니, STM32F411(NUCLEO-F411RE) 이네요.
일단 요놈에다가 I2C 로 보안칩을 연결했습니다.

다음은 STM32F411 NUCLEO 보드의 핀팹을 CUBEMX에서 설정한 내용입니다.


 
다음은 NUCLEO 보드의 I2C 포트의 위치를 표시한 그림입니다. 개발 보드에서 최대한 모여있는 위치로 , CUBEMX 에서 핀을 선택하고 이동했습니다. UART2 는 USB-to-UART 로 NUCLEO 보드와 연결되어 있어서 디버깅 하기가 좋습니다. ^^





이렇게 I2C 에 보안칩을 연결해 봤습니다. 작아서 , 큰 선들을 연결하기 힘들었습니다.




보안칩 핀 넘버 .





다음은 CUBEMX 의 CLK 설정입니다. NUCLEO 보드의 입력 클럭이 8MHz 이므로, HSE 를 선택하고, 최종 HCLK는 100MHz





이제 I2C 설정을 해 보겠습니다. configuration 에서 I2C1을 클릭.





I2C1 설정 중, Parameter Setting 에서 수정한 것은 Clock speed 입니다. 보안 칩이 400KHz 까지 지원을 한다는데, 
오실로 스코프 파형이 별로라서 50KHz로 설정. ^^





다음은 DMA를 설정 합니다.
I2C1_TX , I2C1_RX 둘 다 선택합니다.




다음은 인터럽트설정 입니다. 여기서 주의할 점은 , I2C1_event interrupt 와 I2C1_error interrupt 2개도 선택해 줘야,
나중에 s/w 에서 DMA 함수가 동작합니다. 왜냐하면, DMA 함수에서 인터럽트 함수를 쓰고 있습니다.





다음은, GPIO 설정입니다. 포트를 PULL UP 해 줍니다. 이렇게 하면, 따로 풀업 저항이 필요 없습니다.




다음은 인터럽트를 설정해 보겠습니다. NVIC 를 클릭 합니다.





인터럽트 설정의 우선 순위를 정합니다. 아직 아무엏게나 번호를 붙이고 있습니다. 
나중에 된통 당해봐야 , 어떻게 번호를 붙이는 줄 알 것 같은데.. 막 나눠 주는 중입니다. ^^





마지막으로 프로젝트 설정입니다. 그냥 저장 위치하고, 프로젝트 이름, 컴파일러 종류 등을 설정해 줍니다.




이렇게 H/W 와 I2C DMA 프로그램의 설정은 완료 되었고, 글이 길어지니까.. I2C S/W 테스트는 다음에 올리겠습니다.


+ Recent posts