SPI DMA를 사용하여 NRF24L01 모듈을 동작시켰다.
첨부해둔 파일들은 정리가 되어있지 않다. 주석 처리된 부분들을 테스트를 위해 처리해둔 부분이 많고 필요 없는 코드들이 있을 수 있다.
SPI.c의 코드 중 SPI_DMA_TransmitReceive함수는 제대로 된 동작을 하지만 SPI_DMA_Transmit() 함수는 작동은 하지만 문제점이 있어 그 점에 대해 밑에서 설명할 것이고 SPI_DMA_Receive() 함수는 작동을 하지 않아 나중을 위해 남겨놨다.
따라서 SPI_DMA_TransmitReceive() 위주로 사용할 것인데 코드를 사용할 때 DMA TX, RX 둘 다 설정해 줘야 한다.
코드는 하나의 MCU로 동작하며 SPI가 2개 필요하다.
SPI 코드 생성 - (0)
nrf24l01모듈을 제어하기 위해 SPI1, SPI3코드를 생성한다. SPI1과 SPI3는 동일한 설정을 하므로 똑같이 설정하여 코드를 생성하면 된다.
DMA를 사용하며 Normal모드를 사용한다. SPI_DMA_TransmitReceive()를 사용하려면 TX, RX 모두 가능하도록 설정해야 한다.
추가로 PB8 pin의 경우 Falling Edge Detection Interrupt를 설정해야 한다. nrf24l01 모듈의 RX모드일 경우 데이터를 받았다는 것을 Interrupt를 사용하여 확인할 것이다.
TIM2 코드 생성 - (1)
nrf24l01 모듈에서 데이터를 보내기 위해서는 약 20us Pulse 신호를 CE핀에 입력해야 한다.
Delay를 사용하지 않을 것이므로 timer를 사용하여 만들 생각이다.
Timer Interrupt를 사용할 것이고 TIM2와 연결되어있는 APB1 Timer Clock이 84MHz이므로 Prescaler에 83 값을 넣어 1us를 만들 수 있게 한다.
Interrupt를 사용하여 약 20us가 지나면 Interrupt가 발생하여 Pulse신호를 제어할 수 있게 한다.
코드 - main.c (2)
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
45
46
47
|
/* USER CODE BEGIN 2 */
typedef struct __attribute__((packed)){
uint8_t reg_address;
uint8_t counter; //1
uint8_t data1[3]; //3 //4
uint16_t data2[4]; //8 //12
uint32_t data3[3]; //12 //24
float data4[2]; //8 //32
}nrf_struct;
nrf_struct txstruct, rxstruct;
NRF24L01 nrf1, nrf2;
uint8_t counter=0;
uint8_t status=0;
uint8_t txdata1[33]={0,};
uint8_t rxdata1[33]={0,};
uint8_t txdata2[33]={0,};
uint8_t rxdata2[33]={0,};
LL_SPI_Enable(SPI1);
LL_SPI_Enable(SPI3);
LL_mDelay(300); //
printf("start\r\n");
nrf_init(&nrf1, SPI1, RX,NRF1_CS_GPIO_Port,NRF1_CS_Pin,NRF1_CE_GPIO_Port,NRF1_CE_Pin,32);
nrf_init(&nrf2, SPI3, TX,NRF2_CS_GPIO_Port,NRF2_CS_Pin,NRF2_CE_GPIO_Port,NRF2_CE_Pin,32);
printf("======= 1\r\n ");
dump_reg(&nrf1);
printf("======= 2\r\n ");
dump_reg(&nrf2);
LL_TIM_EnableIT_UPDATE(TIM2);
printf("size:%d\r\n",sizeof(txstruct));
/* USER CODE END 2 */
|
3번째 줄은 테스트용 데이터를 보낼 구조체 nrf_struct를 정의한다. __attribute__((packed))를 해주는 이유는
구조체의 크기를 지정해 주기 위해서 이다.
43번째 줄은 Pulse를 생성하기 위한 TIM2의 Interrupt를 설정한다.
그 외의 부분은 기존 nrf24l01코드와 비슷하다.
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
while (1)
{
counter++;
txstruct.reg_address=0xA0;
txstruct.counter=counter;
txstruct.data1[0]=counter+1;
txstruct.data1[1]=txstruct.data1[0]+2;
txstruct.data1[2]=txstruct.data1[1]+4;
txstruct.data2[0]=(uint16_t)counter+4;
txstruct.data2[1]=txstruct.data2[0]+4;
txstruct.data2[2]=txstruct.data2[1]+8;
txstruct.data2[3]=txstruct.data2[2]+16;
txstruct.data3[0]=(uint32_t)counter+8;
txstruct.data3[1]=txstruct.data3[0]+16;
txstruct.data3[2]=txstruct.data3[1]+32;
txstruct.data4[0]=(float)counter+0.5;
txstruct.data4[1]=txstruct.data4[0]+2.5;
LL_GPIO_ResetOutputPin(nrf2.chip_select_port,nrf2.chip_select_pin);
SPI_DMA_TransmitReceive(SPI3,DMA1,DMA1,LL_DMA_STREAM_5,LL_DMA_STREAM_0,(uint8_t*)&txstruct,rxdata1,33);
//---------------------------------------------------------------------------------------------------- RX
LL_mDelay(100);
rxdata2[0]=0x61;
txdata2[0]=0x61;
rxstruct.reg_address=0x61;
LL_GPIO_ResetOutputPin(nrf1.chip_select_port,nrf1.chip_select_pin);
SPI_DMA_TransmitReceive(SPI1,DMA2,DMA2,LL_DMA_STREAM_3,LL_DMA_STREAM_0,(uint8_t*)&rxstruct,(uint8_t*)&rxstruct,33);
//------------------------------------------------------------------------
LL_mDelay(400);
nrf_status(&nrf1,&status);
printf("2:%.2X\r\n",status);
if(status&(0x40)){
status|= (0x01<<6);
}
nrf_Write(&nrf1,0x07,&status);
nrf_status(&nrf1,&status);
printf("3:%.2X\r\n",status);
printf("%d\r\n",counter);
if(nrf_condition==1){
printf("status:%.2X\r\n",rxstruct.reg_address);
printf("counter:%d\r\n",rxstruct.counter);
printf("data1: %d %d %d\r\n",rxstruct.data1[0],rxstruct.data1[1],rxstruct.data1[2]);
printf("data2: %d %d %d %d\r\n",rxstruct.data2[0],rxstruct.data2[1],rxstruct.data2[2],rxstruct.data2[3]);
printf("data3: %d %d %d\r\n",rxstruct.data3[0],rxstruct.data3[1],rxstruct.data3[2]);
printf("data3: %.2f %.2f\r\n",rxstruct.data4[0],rxstruct.data4[1]);
nrf_condition=0;
}
printf("\n\n\r");
//=====================================================================================================================
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
|
3~22번째 줄은 테스트용 구조체에 값을 넣는 과정이다.
24~25번째 줄은 SPI DMA를 사용하여 nrf24l01모듈의 TX payload 레지스터에 값을 넣는 코드이다. 이때 CS핀을 RESET 하고 SET 하는 코드가 없는 것을 볼 수 있는데, Polling 방식이 아닌 DMA를 사용하기 때문에 SET코들 넣어버리면 데이터를 전송하는 도중에 CS핀이 SET 되어 제대로 데이터가 들어가지 않을 수 있다.
실제 SET 하는 코드는 DMA의 TC Interrupt가 발생하면 SET 하게 만들었다.
28, 36번째 줄의 LL_mDelay는 테스트를 위해 일부로 넣어뒀다.
nrf_condition 값은 stm32fxx_it.c파일에서 확인할 수 있듯이 PB8 핀에서 Falling Edge Interrupt가 발생하면 1로 SET 된다.
그 외의 코드들은 결과를 출력하고 nrf24l01의 status 레지스터 설정을 하는 코드이다.
코드 - stm32f4xx_it.c (3)
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
45
46
47
|
void DMA1_Stream0_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Stream0_IRQn 0 */
if(LL_DMA_IsActiveFlag_TC0(DMA1)){
LL_DMA_ClearFlag_TC0(DMA1);
LL_GPIO_SetOutputPin(NRF2_CS_GPIO_Port,NRF2_CS_Pin);
TIM2->ARR=20;
LL_TIM_EnableCounter(TIM2);
LL_GPIO_SetOutputPin(NRF2_CE_GPIO_Port,NRF2_CE_Pin);
}
/* USER CODE END DMA1_Stream0_IRQn 0 */
}
void DMA2_Stream0_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream0_IRQn 0 */
if(LL_DMA_IsActiveFlag_TC0(DMA2)){
LL_DMA_ClearFlag_TC0(DMA2);
LL_GPIO_SetOutputPin(NRF1_CS_GPIO_Port,NRF1_CS_Pin);
}
/* USER CODE END DMA2_Stream0_IRQn 0 */
}
void TIM2_IRQHandler(void)
{
/* USER CODE BEGIN TIM2_IRQn 0 */
if(LL_TIM_IsActiveFlag_UPDATE(TIM2)){
LL_TIM_ClearFlag_UPDATE(TIM2);
LL_TIM_DisableCounter(TIM2);
LL_GPIO_ResetOutputPin(NRF2_CE_GPIO_Port,NRF2_CE_Pin);
}
/* USER CODE END TIM2_IRQn 0 */
}
void EXTI9_5_IRQHandler(void)
{
/* USER CODE BEGIN EXTI9_5_IRQn 0 */
/* USER CODE END EXTI9_5_IRQn 0 */
if (LL_EXTI_IsActiveFlag_0_31(LL_EXTI_LINE_8) != RESET)
{
LL_EXTI_ClearFlag_0_31(LL_EXTI_LINE_8);
/* USER CODE BEGIN LL_EXTI_LINE_8 */
nrf_condition=1;
/* USER CODE END LL_EXTI_LINE_8 */
}
}
|
DMA1_Stream0와 DMA2_Stream0는 RX DMA Stream들이고 데이터를 전송하는 nrf24l01모듈이 사용하는 DAM1_Stream0에는 TIM2를 사용하여 Pulse를 만드는 코드가 추가로 작성되어 있다.
SPI RX만 DMA를 사용하는 모습을 볼 수 있는데 이는 SPI_DMA_Transmit을 사용하기 힘든 점과 관련이 있다.
데이터시트에 나오는 그림이다. 중요하게 봐야 하는 부분은 DMA TCIF flag가 DATA2를 보낼 때 SET 된다는 점이다. 즉 SPI TX DMA에서 TC Interrupt는 데이터가 전부 전송되지 않았는데 발생한다. 따라서 CS 핀을 다시 SET 하는 타이밍을 찾기가 힘들기 때문에 SPI_DMA_Transmit 함수는 사용하기 힘들다.
결과 - (4)
실제 코드의 결과보다 헥사콥터에 적용하여 데이터를 얻는 모습이다.
헥사콥터의 모터를 변경했지만 아직 발열이 있어 직접 데이터를 얻어 확인해 보려고 만든 nrf24l01코드이므로 헥사콥터에서 데이터를 전송하는 결과를 대신 첨부한다.
motor값과 각도 값들이 출력된다.
-
SPI_DMA_Transmit, SPI_DMA_Receive 함수들이 아직 완벽하지 않아 개선을 해야 한다.
Transmit의 경우 SET을 제대로 해주기만 하면 제대로 작동하므로 CS핀을 SET 하는 타이밍을 맞추면 될 것 같다.
하지만 SPI_DMA_Receive의 경우 통신도 제대로 되지 않아 개선할 점이 많다.
아직 비행은 하지 않고 테스트해본 결과 이므로 실제로 비행을 해보면서 데이터를 제대로 보내는지, 데이터를 보내면서 비행은 안정적으로 하는지에 대해 테스트해봐야 한다.
'임베디드 > STM32' 카테고리의 다른 글
[STM32] 간단한 ICM20948 쿼터니언 (0) | 2020.09.18 |
---|---|
[STM32] LL 드라이버 - MS5611 (0) | 2020.08.28 |
[STM32] LL 드라이버 - ICM20948 (0) | 2020.08.14 |
[STM32] LL 드라이버 - M8N GPS (0) | 2020.08.04 |
[STM32] LL 드라이버 - nrf24l01 (0) | 2020.07.28 |