본문 바로가기
Embedded SW/[Infineon] TC275 Lite Project

PWM이란 무엇인가? 인피니언 MCU를 이용한 PWM 설계 수행

by 방구석 임베디드 2021. 11. 21.
반응형

안녕하세요.

오늘은 인피니언 MCU를 이용하여 PWM(Pulse Width Modulation)을 만들어 보는 시간을 가지도록 하겠습니다.

 

참고로 이 글은 개발의 전체적인 내용을 연재하고 있는 글이 중에 하나입니다.

그래서, 앞에쓴 글을 보시면 더욱 도움이 되실것 같습니다.

아래 글을 링크 걸어둘께요. 필요하신 분은 한번 읽어 보세요.

(물론 이 글만 보셔도 크게 문제는 없습니다.)

 

1) 임베디드 SW, MCU에 대한 정리 (feat. 임베디드 SW 비전)

https://embeddedchallenge.tistory.com/210

2) MCU 개발 장비 구매 방법 정리 (인피니언 MCU TC275 Lite)

https://embeddedchallenge.tistory.com/211

3) 임베디드 SW 개발 환경 세팅 방법 정리 (소스코드 편집기, 컴파일러, 디버거 환경 설정 방법)

https://embeddedchallenge.tistory.com/212

4) 인피니언 IDE Tool 사용방법 정리 (프로젝트 생성 및 다운로드 수행)

https://embeddedchallenge.tistory.com/219

5) 인피니언 MCU LED Blinking (TC275 GPIO 설정)

https://embeddedchallenge.tistory.com/225

6) 인피니언 MCU Clock, 오실레이터 에 대한 이해 (TC275 보드에서 확인)

https://embeddedchallenge.tistory.com/226

7) 인피니언 MCU PLL 설정 및 주변기기 Clock 설정 (TC275)

https://embeddedchallenge.tistory.com/227

8) 인피니언 MCU 인터럽트/Interrupt 설정 (TC275 동작 확인)

https://embeddedchallenge.tistory.com/228

9) 스케줄링(scheduling)에 대한 정리, 인피니언 MCU Scheduler 설계 정리

https://embeddedchallenge.tistory.com/229

 

PWM을 직접 구현하기전, 우리는 PWM이 무엇을 의미하는지 먼저 알 필요가 있습니다.

1. PWM(Pulse Width Modulation)이란 무엇인가?

 

위키 백과에서는 PWM을 아래와 같이 정의하고 있습니다.

https://namu.wiki/w/PWM
펄스 변조의 일종으로 신호의 크기에 따라 펄스의 폭을 변조하는 방식이다.

펄스 파형의 High 상태와 Low 상태 파형의 비율을 듀티 사이클이라고 부르는데,
PWM은 이 듀티 사이클을 조정해서 변조하는 방식이다.
원래는 통신용으로 개발된 기술이었으나 전류, 전압 제어용으로 탁월한 방식이었기 때문에[1] 
현재는 통신보다는 DC쪽 전력 제어나 모터 제어 쪽에 쓰이는 기술이다. 
가장 유명한 예시라고 하면 초퍼제어, 스위칭 파워가 있다. 
요즘은 LED에서 PWM을 매우 자주 볼 수 있다. 
RGB로 색이 변화하는 LED 키보드나 마우스를 카메라로 찍으면 PWM에 의한 플리커링으로 세로줄이 생긴다. 
중국산 손전등도 밝기 조절을 PWM으로 한다. 
카메라에 플리커링 현상 (세로줄 현상)을 유발하는 범인이기도 하다. 
이 플리커링 현상은 안구에 피로를 주기 때문에 모니터나 TV같은 제품을 살 경우에는 
플리커 프리나 PWM 주사율이 최소 3000Hz 이상이 되는 제품을 사는게 좋다.

 

위의 내용을 조금 쉽게 설명해 보도록 하겠습니다.

 

MCU는 모든 정보를 디지털로 인식합니다.

MCU안의 CPU는 모든 정보를 1또는 0으로 인식을 합니다.

메모리 및 레지스터에 전압이 채워져 있으면 CPU는 그것을 1로 인식하고

전압이 빠져있으면 0으로 인식을 합니다.

 

그리고 Output 출력 역시 디지털로 출력합니다.

5V, 0V 이렇게 출력을 합니다.

 

이제 저는 DC 모터를 MCU를 이용하여 돌려보려고 합니다.

위의 DC 모터는 0~5V의 직류전압에 따라서, 회전 속도가 달라지는 스펙을 가지고 있다고 가정해 봅시다.

2.5V의 전압을 주면 100RPM (1분당 100번 회전)

5V전압을 주면 200RPM (1분당 200번 회전)

의 회전을 하는 DC 모터라고 가정을 해보도록 하겠습니다.

저는 1분에 100번 회전을 하도록 만들고 싶습니다.

2.5V의 직류전압을 DC모터에 넣어야 합니다.

 

MCU는 어떻게 2.5V를 만들어 내어 DC 모터를 돌릴까요?

 

그런데 이렇게 하면 0~5V전압을 만들어 줄수 있습니다.

아래와 같이 회로 설계를 수행합니다.

5V의 외부 전원이 들어가 있고

스위치를 누르면 도통이 되는 트랜지스터가 존재합니다.

만일 아래와깉이 MCU에서 5V의 전압을 만들어서 스위치를 누르면

DC motor는 전원이 들어갔다가, 안들어갔다가 할것입니다.

그런데, 아래와 같이 빠른주기로 스위치를 키고 끄는것을 반복한다면

Dc motor의 평균 전압은 2.5V가 될것입니다.

MCU에서 만들어주는 출력 전압

 

다시정리하여 MCU에서 위와 같은 PWM을 만들어주고 5ms동안은 5V를 주고(Swtich ON),

그후 또 5ms동안은 0V를 주면 (Switch Off)

DC 모터는 평균적으로는 2.5V를 전달받게 됩니다.

 

따라서, 2.5V의 아날로그값이 DC모터에 입력으로 들어가는것과 대치되는 현상이 발생하게 됩니다.

아래 파형을 PWM (Pulse Width Modulation)이라고 부릅니다.

 

아래 파형은 PWM은 주기가 10ms이고 Duty가 50%인 PWM을 나타내고 있습니다.

결국 Duty값이 100%가 되면 MCU입장에서 Switch를 계속 눌러주는 꼴이 되겠네요.

그렇다면 5V의 전압이 모터에 인가되게 되고 200RPM의 회전을 하게 됩니다.

 

만일 제가 2.5V가 아니라, 4V를 만들고 싶다면

4/5*100 = 80

80%의 Duty를 만들어 내야 합니다.

 

Period는 10ms이고, Duty는 8ms인 파형을 만들어야 겠지요!!

그런데 MCU에서 직접 모터로 전압을 주면 되지,

왜 굳이 아래와 같은 회로를 만든 것일까요?

크게 2가지 이유가 있습니다.

1) DC Motor가 만일 0~9V 의 전압을 사용하는 Spec이라면

외부전원을 9V로 인가해 주어야 합니다.

MCU에 따라 다르지만 MCU가 낼수 있는 PWM의 전압은 3.3v 또는 5v의 전압발생이 가능합니다.

 

2) MCU안에는 아주 약한 전류가 흐르기 때문에

스위치를 전압으로 눌러주는 역할만이 가능합니다.

 

따라서, 모터를 돌리기에는 MCU에서 내보내는 전류가 부족하기 때문에

외부전원을 연결하여 그 외부전원의 전류를 이용하여 모터를 돌립니다.

그래서 모터 드리이버라는 장치가 따로 필요합니다.

아래는 DC 모터와 함께 장착되는 드라이버의 한 종류입니다.

.

이러한 모터드라이버에 추가적인 소자를 이용하여 정회전과 역회전이 가능합니다.

 

결국 DC Motor +와 - 단에 전압을 찍게 되면 아래와 같은 파형이 들어가게 된다는 것을 알수 있습니다.

아래는 MCU에서 발생하는 PWM이 아니고 바로 모터쪽으로 들어가는 전압을 측정한 것입니다.

그리고 100RPM으로 돌아가겠죠! 

 

이제 인피니언 MCU를 이용하여 PWM을 발생시켜보도록 하겠습니다.

 

2. 인피니언 MCU를 이용하여 PWM을 발생

 

우선 우리가 구매했던 TC275 Lite 개발 보드를 살펴 보아요!

아래 검은 박스가 MCU라고 이야기 했지요? ㅎ

이 MCU에서 PWM 포드가 빨강 박스와 연결이 되어 있습니다.

오른쪽 사진은 뒷면 사진입니다.

회로도를 살펴 보도록 하겠습니다.

 

이제 저는 P02.4, P02.5, P02.6, P02.7 이렇게 4개의 포트를 이용하여 PWM을 만들 것입니다.

4개의 PWM이 필요한 이유는 이 PWM을 이용하여 자동차 4개의 바퀴를 각각 제어하도록 만들기 위함입니다.

 

자 그러면 이제 SW를 설계해야 합니다.

어떻게 설계해야 할까요?

인피니언 Aurix MCU 안에는 GTM(Generic Timer Module)이라는 주변기기가 들어 있습니다.

저 검은 박스가 MCU라고 이야기 했지요?

그 안에 GTM이라는 주변기기 모듈이 들어가 있습니다.

GTM은 Timer입니다.

보통 PWM은 Timer를 통해서 만들어 냅니다.

 

GTM 안에서도 다양한 모듈이 존재하는데

저는 TOM(Timer OUtput Moudle)을 이용하여 PWM을 만들 것입니다.

 

 

GTM의 TOM 모듈에는 100MHz의 Clock이 들어도록 설정해 놓았습니다.

이 부분을 다시 확인하고 싶으신 분들은 위에 제가 쓴 글들을 모은 리스트에서 

Clock 설정 글을 다시한번 읽어 보시기를 바랍니다.

 

그리고 저는 이 100MHz Clock을 이용하여 PWM을 만들 것입니다.

원리는 간단합니다.

 

GTM은 TOM에서 사용하는 Clock을 Count해서 저 CN0라는 레지스터에 값을 넣어둡니다.

결국 CN0를 계속 Clock이 들어오는한 계속 올라갈수 밖에 없습니다.

 

그리고 CN0의 Count가 CM1에 도달하면 MCU는 High에서 Low로 Voltage Level을 아래로 내립니다.

그리고 CM0에 도달하면 MCU 다시 High로 올리고 CN0를 초기화 한다.

예를 들어

CM0 = 100, CM1 = 50 이라고 하면

이것은 Duty 50%의 PWM이 나가도록 설정한 것입니다.

 

결국 CM0는 주파수를 조절하는 factor이고

CM1는 Duty를 조절하는 factor라는 것을 알수 있습니다.

 

그런데 저기 CM0, CM1은 아래와 같이 16비트의 크기를 가집니다.

따라서 넣을수 있는 최대 Counter는 65536 입니다.

CN0도 16비트입니다.

따라서, 오버플로우가 되지 않도록 잘 설계할 필요가 있습니다.

 

저는 주기가 10ms인 PWM을 만들어주려고 합니다.

따라서, CM0가 1000000 를 넣어주면

1000000/ 100000000 = 1/100 = 0.01 (10ms) 가 됩니다.

 

그런데 CM0의 레지스터의 크기는 16비트이니까

1000000을 넣을수가 없습니다.

 

따라서 GTM TOM은 이것으 더 낮은 Clock으로 만들어 주고

이것을 이용해야 10ms의 주기를 가지는 PWM을 만들수 있습니다.

그래서 100MHz/ 256 의 크기의 Clock을 사용하도록 설정할 것입니다.

따라서 초기화 코드는 아래와 같습니다.

 

void DrvGtmInit(void)
{
    /*disable interrupts*/
    boolean  interruptState = IfxCpu_disableInterrupts();

    /*obtain GTM clock frequency*/
    Ifx_GTM *gtm = &MODULE_GTM;
    g_GtmTomTimer.info.gtmFreq = IfxGtm_Cmu_getModuleFrequency(gtm);

    /*Enable GTM*/
    IfxGtm_enable(gtm);
    
    /*setting the global clock to 100MHz*/
    IfxGtm_Cmu_setGclkFrequency(&MODULE_GTM, g_GtmTomTimer.info.gtmFreq);
    /*get Global clock frequency*/
    g_GtmTomTimer.info.gtmGclkFreq = IfxGtm_Cmu_getGclkFrequency(gtm);

    /*setting CMU0 frequency*/
    IfxGtm_Cmu_setClkFrequency(&MODULE_GTM, IfxGtm_Cmu_Clk_0, g_GtmTomTimer.info.gtmFreq);
    
    /*Tom1 Init*/
    GtmTom1Init();

    /*enable interrupts again*/
    IfxCpu_restoreInterrupts(interruptState);

    /*enable Cmu clock*/
    IfxGtm_Cmu_enableClocks(gtm, IFXGTM_CMU_CLKEN_FXCLK | IFXGTM_CMU_CLKEN_CLK0);
}

static void GtmTom1Init(void)
{
    IfxGtm_PinMap_setTomTout(&IfxGtm_TOM1_12_TOUT4_P02_4_OUT, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed4); /*Rear Left*/
    IfxGtm_PinMap_setTomTout(&IfxGtm_TOM1_13_TOUT5_P02_5_OUT, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed4); /*Rear Right*/
    IfxGtm_PinMap_setTomTout(&IfxGtm_TOM1_14_TOUT6_P02_6_OUT, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed4);  /*Front Left*/
    IfxGtm_PinMap_setTomTout(&IfxGtm_TOM1_15_TOUT7_P02_7_OUT, IfxPort_OutputMode_pushPull, IfxPort_PadDriver_cmosAutomotiveSpeed4);  /*Front Right*/

    GTM_TOM1_CH12_CTRL.B.RST_CCU0 = 0u;
    GTM_TOM1_CH12_CTRL.B.TRIGOUT = 1u;
    GTM_TOM1_CH12_CTRL.B.SL = 1u;

    GTM_TOM1_CH13_CTRL.B.RST_CCU0 = 1u;
    GTM_TOM1_CH13_CTRL.B.TRIGOUT = 0u;
    GTM_TOM1_CH13_CTRL.B.SL = 1u;

    GTM_TOM1_CH14_CTRL.B.RST_CCU0 = 1u;
    GTM_TOM1_CH14_CTRL.B.TRIGOUT = 0u;
    GTM_TOM1_CH14_CTRL.B.SL = 1u;

    GTM_TOM1_CH15_CTRL.B.RST_CCU0 = 1u;
    GTM_TOM1_CH15_CTRL.B.TRIGOUT = 0u;
    GTM_TOM1_CH15_CTRL.B.SL = 1u;

    GTM_TOM1_TGC1_GLB_CTRL.U = 0xAA000000u; 
    GTM_TOM1_TGC1_ENDIS_CTRL.U = 0xAA00u;    
    GTM_TOM1_TGC1_OUTEN_CTRL.U = 0xAA00u;  

    GTM_TOM1_CH12_CTRL.B.CLK_SRC_SR = IfxGtm_Tom_Ch_ClkSrc_cmuFxclk2;
    GTM_TOM1_CH13_CTRL.B.CLK_SRC_SR = IfxGtm_Tom_Ch_ClkSrc_cmuFxclk2;    
    GTM_TOM1_CH14_CTRL.B.CLK_SRC_SR = IfxGtm_Tom_Ch_ClkSrc_cmuFxclk2;
    GTM_TOM1_CH15_CTRL.B.CLK_SRC_SR = IfxGtm_Tom_Ch_ClkSrc_cmuFxclk2;

    GTM_TOM1_TGC1_GLB_CTRL.B.HOST_TRIG = 1u;  
}

 

위의 코드를 라인별로 설명을 하도록 하겠습니다.

 

저는 Tom1 모듈의 채널12,13,14,15번을 사용하여 PWM을 만들것이고

10ms의 Period를 가지도록 설계할 것입니다.

GTM안에서 TOM 모듈이 사용하는 Clock은 현재 100MHz입니다. 

그리고 100Mhz를 다 사용해서는 CM0,CM1, CN0의 레지스터가 넘치니까!

이것을 256으로 나누어 좀더 느려진 클락을 사용할것입니다.

 

이제 PWM API를 살펴 보도록 하겠습니다.

 

아래 코드의 define 값은 아래와 같습니다.

 

#define TOM_BASE_FREQ    (100000000.0f/256.0f)
#define PWM_HZ           (100.0f)
#define PWM_PERIOD_CNT   TOM_BASE_FREQ/PWM_HZ

void DrvGtmPwmTest(float32_t param_Ch12Duty, float32_t param_Ch13Duty, float32_t param_Ch14Duty, float32_t param_Ch15Duty)
{
    float32_t fPeriodCnt = PWM_PERIOD_CNT;
    uint32_t ulPeriodCnt = (uint32_t)fPeriodCnt;
    
    GTM_TOM1_TGC1_GLB_CTRL.U = 0xAA000000u; 
    
    GTM_TOM1_CH12_SR0.B.SR0 = ulPeriodCnt;
    GTM_TOM1_CH12_SR1.B.SR1 = (uint32_t)(fPeriodCnt*param_Ch12Duty);

    GTM_TOM1_CH13_SR0.B.SR0 = ulPeriodCnt;
    GTM_TOM1_CH13_SR1.B.SR1 = (uint32_t)(fPeriodCnt*param_Ch13Duty);

    GTM_TOM1_CH14_SR0.B.SR0 = ulPeriodCnt;
    GTM_TOM1_CH14_SR1.B.SR1 = (uint32_t)(fPeriodCnt*param_Ch14Duty);

    GTM_TOM1_CH15_SR0.B.SR0 = ulPeriodCnt;
    GTM_TOM1_CH15_SR1.B.SR1 = (uint32_t)(fPeriodCnt*param_Ch15Duty);
    
    GTM_TOM1_TGC1_GLB_CTRL.U = 0xAA000000u;
}

 

그리고 위의 PWM API는 10ms Task에 할당을 하였습니다.

Duty값은 0.5로 50%가 나가도록 설정하였습니다.

이제 준비가 다 되었습니다.

TOM1 12,13,14,15채널과 연결된 포트를 찍어 보도록 하겠습니다.

 

그런데, 우리는 PWM 어떻게 볼수 있을까?

 

오실로스코프라는 것으로 볼수 있는데 장비가 너무 비쌉니다.

그래서 Logic Analyzer를 집에서 하나 구매하면 아주 값싸게 디버깅이 가능합니다.

위의 장비는 추후 더 포스팅 하겠습니다.

P02.4, P02.5, P02.6, P02.7 포트를 위의 Logic Analyze 채널에 연결합니다.

그러면, 아날로그 값이 저장비를 통해 신호가 들어와서 PC에서 분석된 파형을 볼 수 있습니다. 

즉, PC에서 Logic Analyzer 프로그램을 깔고 동작시키면 실시간으로 파형이 저장되어 보여지게 됩니다.

 

그렇다면 한번 분석해 보도록 하겠습니다.

아래와 같은 파형을 얻게 되었습니다.

주기 10ms  Duty가 50%인 파형을 확인할수 있습니다.

4개의 파형의 Duty 시작지점이 모두 동일한 것을 확인 할 수 있습니다.

이제 우리는 Duty값을 얼마든지 조절하여 DC모터를 Control 할수 있게 되었습니다.

더 자세한 코드 설명은 추후에 다시 정리하도록 하겠습니다.

감사합니다.

반응형

댓글