STM32_Embeded(FirmWare)

공이지·2024년 9월 25일
post-thumbnail

STM32를 세팅하는 방법은 어떻게 사용하느냐에 따라서 모두 다르므로 실습내용만 정리하겠습니다.

LED

  • PA5번을 GPIO_Output으로 설정하고 위의 TogglePin의 매개변수의 GPIOA, GPIO_PIN_5를 할당해주면 PA5번과 연결된 STM32보드의 LED에 불이 들어오는것을 확인할 수 있습니다.
  • 다음은 여러개의 LED를 제어합니다. 아래 사진은 STM32보드의 포트 정보입니다. 간편하게 PA_5 부터 PB_10까지의 GPIO포트를 사용하기위해서 각 핀들을 GPIO_Output으로 설정합니다. 이후 LED를 순차적으로 반복해서 켜기위해서 다음 처럼 구조체와 함수를 선언합니다. 이후 main에서 다음처럼 반복문으로 함수를 실행시키면 순차적으로 LED가 On,Off되는 것을 볼 수 있습니다.

UART통신

Polling? PipeLine? 내용정리 나중에.

  • UART통신 설정
    UART통신을 UART2를 사용했습니다. 만약 인터럽트를 활성화 시키고 싶다면 아래 사진의 Enabled로 활성화 시켜주면 됩니다.
  • 먼저 우리가 사용하고자하는 UART2는 다음과 같이 UART_HandleTypeDef huart2라고 정의 되어있습니다. 따라서 우리가 아래와 같이 UART통신을 통해서 전송받을때 다음 UART2통신의 HandleType의 주소값으로 설정해야합니다. 이후 간단한 실습을 통해서 전송받은 데이터를 저장할 배열을 8비트 정수형으로 선언합니다. 그리고 main의 반복문에서 UART_Receive를 통해서 데이터를 전송받습니다.
  • 결과 :comportMaster라는 프로그램을 통해 컴퓨터와 연결(serial연결)된 STM32의 포트번호와 bps를 맞게 설정해주고 a라는 데이터를 전송하게되면 Debuger를 통해서 확인하고 있는 rxData의 배열에 'a' 라는 데이터를 전송 받았음을 확인할 수 있습니다.

UART_인터럽트

  • 위에서 UART통신하는 과정과 비슷합니다. 한가지 주의할점은 우리가 디버그를 통해서 rxData배열에 들어오는 데이터를 확인하고 있습니다. 이는 사진과 같이 main함수 밖에 전역함수로 선언해야 디버그를 통해서 값을 확인할 수 있습니다. UART인터럽트는 다음과 같습니다. 한가지 주요사항은 위에서는 HAL_UART_RxCpltCallback함수에 HAL_UART_Receive_IT 함수를 선언했다면 아래에는 HAL_UART_Receive_DMA를 선언했습니다. 이 둘은 모두 같은 인터럽트 기능을 동작합니다. 하지만 HAL_UART_Receive_IT CPU를 사용하고 소용량 데이터를 송수신할 때 사용합니다. HAL_UART_Receive_DMA는 STM32내부의 DMA컨트롤러를 사용하며, 대용량 데이터를 송수신할 때 사용합니다. 하지만 UART(Serial)통신으로 대용량 데이터를 받는 경우는 거의 없으므로 무엇을 사용해도 무방합니다. 여기서 제가 의아했던점은 main에서 HAL_UART_Receive_DMA를 받고 콜백함수에서 또 다시 HAL_UART_Receive_DMA를 선언했습니다. 그 이유는 main에서의 Recieve는 인터럽트를 활성화 시켜주는 역할을 하고 콜백함수에서 전송 받은 데이터를 rxData에 받습니다!
  • 아래와 같이 전송할 데이터 배열에 문자열을 할당한뒤 main의 while문안에서HAL_UART_Transmit을 해주면 반복해서 문자열을 전송합니다. 중요한점!! HAL_UART_Transmit는 활성화가 필요없이 txData에 할당한 값을 전송합니다! 하지만 HAL_UART_Receive는 활성화를 한번 해주면 인터럽트가 발생하면 다시 비활성화되기때문에 아래 콜백함수에서 받은 데이터를 전송해주고 다시 활성화를 시켜줘야 다음 인터럽트가 발생했을 때 콜백함수를 실행할 수 있습니다!
  • 전송받은 데이터를 전달해주는 동작을 합니다. 콜백함수로 만약 인터럽트를 발생한 UART_HandleTypeDEfUSART2라면 전송 받은 데이터를 보내줍니다. 인터럽트 활성화를 위한 선언입니다!결과입니다. 'a'를 전송하면 'a'가 출력되는것을 확인할 수 있습니다.

DMA방식(polling)과 인터럽트 다시 한번 완벽하게 정리하기.

헤더파일 생성

  • 위는 순서도와같이 stm32main.c는 복잡하기 때문에 다음 같이 ap.h를 사용해서 main을 헤더파일과 소스파일로 작성해주고 빌드를 하게되면 다음 처럼 간편하게 main을 만들어서 빌드를 시 킬 수 있습니다.

GPIO Input(Button)

  • stm32에서는 풀업저항, 풀다운 저항을 해도 항상 버튼을 누르지 않는경우는 그 값은 1이다, 버튼을 누르게 되면 그 값이 0이된다, 버튼을 이용해서 a포트의 5번핀에 연결된 led를 활성화 시킬 수 있다.

PreScaler_Timer

  • 우리는 프로젝트를 만들 때 기본 세팅을 다음과 같이 100Mhz로 설정했습니다.
    하지만 우리는 위 사진 처럼 timer의 인터럽트와 prescaler, periode를 "10000 - 1" (컴퓨터는 0부터!! 그래서 -1)로 설정했기 때문에 우리 처음 설정한 100Mhz주기10000 - 1, prescaler를 10000 -1로 세팅해주면 우리는 1Hz(1초)라는 타이머를 갖게 됩니다.

    • 즉! 간단하게 생각하면 prescaler로 설정해준 값은 처음에 우리가 설정한 100Mhz즉 10ns를 prescaler에서 설정한 값. 예를들어서 prescaler를 100으로 설정하면 10ns를 100번 카운트한다는 의미이다. 쉬운말로는 10ns를 100번 묶는다. 그리고 periode는 기본적으로 65535(16bit, 이는 어떤 Timer를 사용하느냐에 따라서 비트수가 다르다 우리가 사용하는 stm32에는 16bit와 32bit가 있다.) 이며 이는 아래 사진의 초록색에 해당한다. 이는 우리가 설정한 prescale를 총 얼마나 카운트 할 수 있는지 결정하는, 즉 Top값이다!

1us 카운터

  • prescaler를 100으로 설정해서 100주기. 즉 1us 카운터가 되는 것이다, periode는 상관없다. 몇번을 세든 1us만 필요하기때문!!
    그래서 코딩한 내용을 보면 1us delay함수를 만들어서 내가 delay함수에 할당하는 매개변수 값에 따라서 while문으로 시간을 기다린다. 이때 delay함수가 시작하면 강제적으로 시간 타이머의 레지스터를 SET시켜 0으로 떨어트립니다(이는 periode를 강제로 0으로 떨어뜨린다고 생각!!).이후 다시 카운트 하면서 __HAL_TIM_GET_COUNTER으로 받아오는 시간을 비교하여 delay시킵니다.
  • delay.c에서 사용하는 __HAL~~~ 은 기본적으로 함수가 아닙니다. 아래 사진을 보면 define으로 정의되었으며 우리가 이를 사용해서 타이머 register의 값을 세팅해줄 수 있습니다

PWM제어

  • 다음과 같이 TIM3의 채널을 PWM Generation으로 세팅해주면 PWM으로 사용할 수 있습니다. 그리고 아래 Pulse를 제어해주면 우리가 원하는 PWMduty을 설정해 줄 수 있습니다(이는 IDE에서 구조체로 접근할 수 있습니다. Ex. TIM3->CCR1 = i).

  • 아래 HAL_TIM_PWM_Start()함수를 통해 PWM을 사용할 수 있습니다.

  • 쉽게 설명하면 다음과 같다. 현재 실습에서는 필요한 주파수가 정해진 것이 없으므로 PrescalerCounterPeriod의 설정을 사진과 같이 정했지만, 만약 어떤 모듈을 사용할 때 사용해야할 주파수 대역이 정해져있다면 이 둘의 값을 세팅해줘야 할 것이다. 사진처럼 PrescalerCounterPeriode를 설정해주면 50Hz의 주파수를 얻을 수 있습니다(자세한 설명은 위를 참고하시면 되겠습니다!).

  • 즉 우리는 위 사진처럼 CCR1, 우리가 설정한 Chanel1CCR(Pulse),PSC(Prescaler),ARR(CounterPeriode)을 코딩을 통해서도 세팅이 가능합니다.

실습내용

- UART통신으로 받은 데이터를 비교해서 CCR1을 제어하면서 모터의 속도(duty)를 제어할 수 있습니다.

ADC제어

  • ADC제어를 위해서는 세팅을 해줘야합니다. 여기선 Resolution과 우리가 사용할 ADC채널의 SamplingTime을 세팅해 줄 수 있습니다.

    • Resolution은 해상도라고 생각하면 됩니다. 다음과 같이 아날로그 신호를 원래 아날로그 신호와 가깝게 표현 할 수 있도록 합니다.
      samplingRate(SamplingTime)는 사진과 같습니다. 예를들어서 소리의 경우 samplingRate(SamplingTime)가 높을 수록 더욱 정교하게 신호를 변환하여 소리의 품질이 높아집니다!

ADC(HAL_ADC_PollForConversion)

  • UART통신으로 가변저항의 아날로그 값을 디지털로 변환합니다. 여기서 중요한점은 인터럽트나, DMA가 없어도 HAL_ADC_PollForConversion()(아래 사진참조.)함수를 통해서 가변저항의 값을 변환하여 UART통신으로 그 값을 전송해줍니다.

    ADC(인터럽트)

  • 인터럽트는 위에서 설명한 것과 같이 콜백함수를 호출해 16비트로 선언한 adcValue에 가변저항의 ADC변환 값을 할당하여 출력해줍니다.(또한 세팅에서 인터럽트를 활성화 시켜줘야합니다.)

    ADC(DMA)

  • DMA 또한 맨 위 사진과 같이 DMA를 세팅해줍니다. 이후 HAL_ADC_Start_DMA()를 사용해 adcValue에 가변저항의 변환된 값을 받아 출력합니다.

ADC_조이스틱

  • ADC 세팅에서는 Prescaler를 다음과 같이 세팅할 수 있습니다. 또한 실습에서 사용할 조이스틱에는 x축, y축 2가지가 존재하기에 2개의 채널을 사용해줍니다. SamplingTime은 최대로 설정해 더욱 정확한 값을 받아주도록 합니다.
  • 2번째 사진은 채널별 SamplingTime세팅의 대한 데이터 시트의 정보입니다.
  • 조이스틱에 연결하면 x축 y축의 변화를 확인할 수 있습니다. 왜 코드를 변경하지 않아도 x축, y축의 변화가 배열에 보일 수 있냐? 실습에서는 adcValue를 16비트로 선언했는데 ADC변환하여 16비트로 값을 저장하는데 이를 각각 연결된 채널, 즉 조이스틱의 x축과 y축의 값이 저장이 됩니다. 여기서 While문으로 값을 받습니다.
    간단하게 정리하면 조이스틱의 x축, y축의 변화는 16비트의 데이터로 저장을 하는데 이는 adcValue의 배열로 선언되어있고, x축의 변화를 16비트의 값으로 저장하므로 첫번 째 16비트 배열로 선언한 adcValue에 첫번 째 방에는 x축의 변화가 저장이되고, 두번째 방에는 y축의 변화가 저장이 됩니다.

STM32_초음파센서

  • 초음파센서를 동작하기위해 1us의 타이머가 필요하기에 TIM11을 타이머 카운터로 사용합니다. TIM3 는초음파센서의 데이터 시트의 정보를 바탕으로 트리거와 에코신호를 주고받기위해서, 즉 에코와 트리거 신호의 상승, 하강엣지를 받기위한 타이머 카운터로 사용합니다.

코드설명

  • 초음파센서를 사용하기 위해선 1us 타이머가 필요하기에 HAL_TIM_Base_Start(&htim11); 를 사용해서 delay_us()함수를 만들어줍니다 (TIM11을 사용해 줍니다).
    이에 TIM3은 초음파센서의 데이터 시트의 정보를 바탕으로 트리거와 에코신호를 주고받기위해서, 즉 에코와 트리거 신호의 상승, 하강엣지를 받기위해서 HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);(초기화 개념!)를 사용해줍니다(인터럽트도 활성화 시켜줍니다.(사진참조)).

  • void HCSR04() 함수를 사용해서 GPIO로 선언하여 PA7로 세팅한 트리거신호를 보내줍니다. 또한 함수 아래 __HAL_TIM_ENABLE_IT(&htim3, TIM_IT_CC1);INPUT캡쳐 인터럽트 가 발생하면 콜백함수에서 초음파센서가 측정한 거리를 측정한 뒤 __HAL_TIM_DISABLE_IT(&htim3, TIM_IT_CC1);INPUT캡쳐 인터럽트를 비활성화 하는데 이를 다시 활성화 시켜주기 위함입니다. 이는 거리를 측정하기 위한 INPUT캡쳐를 활성화 시켜주기 위한 함수입니다!

  • void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) INPUT캡쳐 인터럽트가 발생하면 IC_value1에 초기에 TIM3 를 설정할 때 상승엣지로 설정했기에 상승엣지가 발생하면 HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1); 에서 상승엣지가 발생한 시점의 값을 IC_value1 에 저장합니다. 이후 한번 캡쳐가 이루어졌음을 알리기 위해 flag를 1로 설정해주고 에코신호의 하강엣지를 받아야 거리를 측정할 수 있으므로 __HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);를 이용해서 하강엣지를 인식하도록 합니다.

  • 이후 초음파가 돌아올때, 즉 하강엣지가 발생했을때 IC_value2HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1); 발생한 시점의 값을 저장하여 거리를 구하기 위해 이 둘의 값을 빼서 계산합니다. 일반적으로는 16비트 타이머로 설정하면 65535를 셀 수 있는 타이머 카운터가 됩니다. 만약 이 범위 안에 IC_value1IC_value2가 있으면 뺄셈으로 계산을 하면 되지만, 만약 IC_value2오버플로우가 발생하면서 첫번째 사진과 같이 IC_value1과 IC_value2가 값이 할당이 되었다면, 즉 IC_value1의 값이 더 크다면 IC_value1을 타이머 카운터의 최대 값인 65535에서 뺄셈을 한뒤 IC_value2와 더해주면 해결이 됩니다. 이후 다시 flag를 0으로 세팅하고, __HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); 와 같이 거리를 지속적으로 측정하기위해서 상승엣지를 인식하도록 변경해줍니다.

  • __HAL_TIM_DISABLE_IT(&htim3, TIM_IT_CC1); 혹시 모를 오류를 대비하여 한번 거리 측정을 마무리 했으면 INPUT캡쳐 인터럽트를 비활성화 해줍니다.(이는 다시 활성화됩니다!)

profile
화이팅..!

0개의 댓글