이전에 쓴 글([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 테스트는 다음에 올리겠습니다.


STM32F411 에서 타이머2 인터럽트를 사용하다가, 

동작이 생각처럼 안되어서 쌩쑈를 하다가 나중에 동작이 되긴 했지만 아직까지 이해가 안되는 부분이 있었습니다.

인터럽트를 처리하는 콜백 함수와 함수 내에서 사용하는 전역 변수들은 다음과 같고, main.c 파일 안에 있습니다.
이 콜백함수는 1ms 마다 호출되도록 설정했고, 
콜백 함수 내에 f_tim2_cpl 가 SET 되는 부분에 LED 와 연결된 포트를 토글하면 잘 동작 됐습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
uint8_t f_tim2_cpl=0;
uint16_t cnt_tim2[5= {0,0,0,0,0};
uint16_t ref_tim2[5= {50,2000,40,0,0};
 
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    uint8_t cnt_tmr;
    
    if (htim->Instance == TIM2)
    {
        for(cnt_tmr=0;cnt_tmr<5;cnt_tmr++)
        {
            if (cnt_tim2[cnt_tmr] < ref_tim2[cnt_tmr])
            {
                cnt_tim2[cnt_tmr]++;
            }
            if (cnt_tim2[cnt_tmr] == ref_tim2[cnt_tmr])
            {
                cnt_tim2[cnt_tmr] = 0;
                switch (cnt_tmr)
                {
                    case 0:
                        f_tim2_cpl |= F_TIMCMPL_0;
                        break;
                    case 1:
                        f_tim2_cpl |= F_TIMCMPL_1;
                        break;
                    case 2:
                        f_tim2_cpl |= F_TIMCMPL_2;
                        break;
                    case 3:
                        f_tim2_cpl |= F_TIMCMPL_3;
                        break;
                    case 4:
                        f_tim2_cpl |= F_TIMCMPL_4;
                        break;
                }
            }
        }
    }
    else if (htim->Instance == TIM1)
    {
    }
}
cs

그런데, main 함수 내에서 다음과 같이 f_tim2_cpl 의 상태를 while 문의 조건문 안에서 읽어서 비교하면 동작이 안되고,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int main(void)
{
  uint16_t i;
  /* 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_USART1_UART_Init();
  MX_TIM2_Init();
 
  /* USER CODE BEGIN 2 */
  for(i=0;i<5;i++)
    cnt_tim2[i] = 0;
  f_tim2_cpl=0;
  HAL_TIM_Base_Start_IT(&htim2);
 
  printf("uart test\n\r");
  while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
  {
  }
  printf("2 sec delay\n\r");
 
  while(1);
}
cs


while() 문 안에, 여러가지 내용을 넣어 봤는데 동작이 되는 경우도 있고 안되는 경우도 있고,
안에 넣은 코드에 따라서 각각 다릅니다.
1. HAL_delay(1); 을 넣은 경우. (정상 동작)
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        HAL_Delay(1);
    }
    printf("2 sec delay\n\r");
 
cs
2. __wfi(); //wait for interrupt 을 넣은 경우. (정상 동작)
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        __wfi();
    }
    printf("2 sec delay\n\r");
 
cs
3. __nop(); // no opration 을 넣은 경우. (동작 안함)
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        __nop();
    }
    printf("2 sec delay\n\r");
 
cs
4. printf() 문 을 넣은 경우, (정상 동작)
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        printf("timer 2 Whay?\n\r");
    }
    printf("2 sec delay\n\r");
 
cs
5. i++; (변수 1씩 증가). (동작 안함).
1
2
3
4
5
6
7
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        i++;
    }
    printf("2 sec delay\n\r");
 
cs
6. for() 루프로 딜레이. (동작 안함)
1
2
3
4
5
6
7
8
9
10
    printf("uart test\n\r");
    while((f_tim2_cpl&F_TIMCMPL_1) == 0)    // 2sec
    {
        for(i=0;i<1000;i++)
        {
            __nop();
        }
    }
    printf("2 sec delay\n\r");
 
cs

지금까지의 테스트로 봤을 때, 
인터럽트에 의한 전역 변수의 상태를 읽으려면
- 단순한 for 루프나 폴링 방식의 딜레이로는 안되고,
- __wfi(), HAL_delay(), printf() 의 함수가 사용되어야 읽을 수 있다고 볼 수 있겠습니다.

흠, 지금까지 이런 것을 염두에 두고 인터럽트를 쓰지 않았는데.. 거의 무리없이 지나왔었습니다.
그런데 이제와서 이런 경우를 겪어서 좀 황당하네요.

ㅜㅜ 뭔가 기초가 부족한 것 같습니다. 혹시 누가 정확한 원인을 아시는 분 설명 좀 부탁 드려요.

최종적으로 다음과 같이 사용하니, 별 문제가 없네요. (__wfi() 사용)
1
2
3
4
5
6
7
8
9
10
while(1)
{
    __wfi();
    if (f_tim2_cpl & F_TIMCMPL_2)         // 400ms T compare
    {
        cnt_tim2[2= 0;                        // 400ms counter
        f_tim2_cpl &= ~F_TIMCMPL_2;  // 400ms Flag clear
        HAL_GPIO_TogglePin(IND_LED_FL_GPIO_Port,IND_LED_FL_Pin);    // LED toggle
    }
}
cs


+ Recent posts