본문 바로가기
Programming/Embedded

[Arduino Due]-basic PWM control code

by No Brainer 2022. 2. 18.

/*

 * -20KHZ의 PWM 신호를 발생시키는 예제

 * -두개의 LED를 COMPLEMENTARY PWM에 연결하여 하나는 밝아질때 하나는 어두워지는 것을 관찰할 

 * 수 있음

 * -modification1: configure ADC- PWM신호의 듀티사이클이 ADC값을 반영하게 변경

 * -modification2: 

 */

 

/*

 * ATSAM3X8E가 가지고 있는 8개의 PWM들은 모두 COMPLEMENTARY로 생성가능함.

 * 듀에에는 8개의 PWM이 모두 나와있는 것은 아니고 7개까지만 나와있는데, 그중에서도 PWML4는 

 * 없기때문에 실제로 COMPLEMENTARY로 사용가능한 PWM은 6개임. 

 * 20KHZ의 PWM을 생성하기 때문에 PWM resolution으로 2100을 사용해보자. 

 * pwm생성에 사용되는 clock은 20000*2100*2=84mhz가 되어 마스터 클럭을 그대로 사용하면 된다. 

 * 위에서 2를 곱한 것은 center aligned방식은 left aligned방식에 비해 주파수가 2배가 되기 때문. 

 * 

 */

 

/* pin34   PWML0(PC2)   LED1

 * pin35   PWMH0(PC3)   LED2

 * pin36   PWML1(PC4)

 * pin37   PWMH1(PC5)

 * pin38   PWML2(PC6)

 * pin39   PWMH2(PC7)

 */

 

float sim_time =0;//simulation time 초기값.

uint32_t duty=0; 

 

void setup()

{  

  Serial.begin(115200);

  //pwm 을 사용하기 위해서는 해당 pin을 PIO 가 아니라 Peripheral function 기능을 사용해야 한다.

  //pin의 peripheral function기능을 활성화 시키기 위해서는 해당 pin의 PIO 를 disable하여야 한다.

  PIOC->PIO_PDR = PIO_PC2 | PIO_PC3 | PIO_PC4 | PIO_PC5 | PIO_PC6 | PIO_PC7;//pc2~pc7의 pio(gpio사용기능해제)

 

  //PC2~7을 pwm출력 pin으로 설정하자. pin은 PIO가 아니라 peripheral function기능으로 사용되어야 한다.

  //pc2~7을 모두 peripheral B로 설정해야 pwm 출력 pin으로 사용할 수 있다.

  //PIO_ABSR레지스터의 해당 비트에 0을 쓰면 peripheral A function, 1을 쓰면 B function으로 설정된다.

  PIOC->PIO_ABSR |= PIO_PC2 | PIO_PC3 | PIO_PC4 | PIO_PC5 | PIO_PC6 | PIO_PC7 ;//PC2~7을 peripheral B로 설정

 

 

  pmc_enable_periph_clk(ID_PWM);//prtipheral pwm 에 공급되는 clock을 enable시킨다

 

  PWM-> PWM_DIS = (1u<<0) | (1u<<1) | (1u<<2) ;//pwm channel 0,1,2번을 disable시킨다. 

 

//pwm을 발생시키기 위해선 clock generator 설정을 해주어야 한다.

//이 설정은 PWM_CLK에서 하는데, 여기서 하는일은 다음과 같다.

//<1>마스터클럭을 그대로 쓸지, 아니면 분주된 클럭을 쓸지를 결정한다.(이것은 PREA,PREB를 통해 설정된다)

//<2>이렇게 일차적으로 분주된 클럭을 다시 한번 linear division할 수 있는데 DIVA,DIVB를 통해 분주비를

//설정할 수 있다.

//<3>clock generator의 경우 클럭A와 클럭B를 생성할 수 있는데 2개가 모두 사용될 수 있고, 하나만 사용할 수 있다

//<4>마스터클럭을 분주하지 않으려면 PREA를 0b0000으로 설정하고 linear division을 1로 설정하려면 DIVA=1로 하면 된다.

//CLKB를 사용하지 않으려면 DIVB=0으로 설정하면 된다

  PWM->PWM_CLK &= ~0x0F000F00; //PREB,PREA = 0000 -> MCK(84MHz)를 사용하겠다

  PWM->PWM_CLK &= ~0x00FF0000; //DIVB = 0x00 clkb turn off

  PWM->PWM_CLK &= ~0x000000FF; //DIVA = 0x00 clka역시 turn off

  PWM->PWM_CLK |= (0x000000FF & 1);  //DIVA = 1   DIVA=1

  //위와 같이 설정하면, 클럭 B는 사용하지 않는 상태가 되고 클럭A의 PREA는 0이므로 마스터클럭을 그대로 이용

  //하게 된다. DIVA가 1이므로 PREA에서 설정한 클럭(마스터클럭)그대로 사용하게 된다.

 

  //각 채널별로 처리해주어야 할것들이 있다. 이것들은 PWM_CMR레지스터를 이용하여 처리된다.

  //PWM_CMR의 CPRE 비트에서는 어떤 클럭을 사용할 것인지 선택하는데, 마스터클럭을 분주한 하위클럭들과 CLKA,CLKB

  //중 하나를 선택하게 된다. 

  //PWM WAVE FORM을 center aligned 하게 되면 left aligned 보다 주기가 2배 늘어나서 pwm 주파수가 느려지게 된다.

  //polarity설정의 경우, polarity의 비트가 0일때 듀티가 10%가 나오면 1일때 듀티가 90이 나오게 된다.

  PWM-> PWM_CH_NUM[0].PWM_CMR &= ~0x0000000F; //CPRE를 모두 0으로 CLEAR

  PWM-> PWM_CH_NUM[0].PWM_CMR |= 0xB; //CPRE=0XB=0b1011로 설정, 즉 clka를 선택함.

  PWM-> PWM_CH_NUM[0].PWM_CMR |= 1u<<8; //CALG=1, center aligned

  PWM-> PWM_CH_NUM[0].PWM_CMR &= ~(1u<<9); //CPOL=0

  //PWM-> PWM_CH_NUM[0].PWM_CMR |= 1u<<9; //CPOL=1

  //PWM-> PWM_CH_NUM[0].PWM_CMR |= 1u<<17;//DTL=1

  //PWM-> PWM_CH_NUM[0].PWM_CMR |= 1u<<18;//DTH=1

 

  

//CH0,CH1,CH2의 dead time generator 를 enable시킴.

  PWM-> PWM_CH_NUM[0].PWM_CMR |= (1u<<16); //channel mode resistor deadtime generator enable

  PWM-> PWM_CH_NUM[1].PWM_CMR |= (1u<<16);

  PWM-> PWM_CH_NUM[2].PWM_CMR |= (1u<<16);

  

  

//데드타임의 값을 설정함.

//PWM_DT레지스터는 DTL(PWML의 데드타임)과 DTH(PWMH의 데드타임)으로 나뉜다.

  PWM-> PWM_CH_NUM[0].PWM_DT = 0x00030003;//CHO: DTL=3, DTH=3

  PWM-> PWM_CH_NUM[1].PWM_DT = 0x00050005;//CH1: DTL=5. DTH=5

  PWM-> PWM_CH_NUM[2].PWM_DT = 0x000F000F;//CH2: DTL=15 DTH=15

 

 

 

 //PWM channel period 설정

 //만일 pwm channel 이 disable된 상태라면 PWM_CPRD에 period값을 쓴다.

 //그렇지 않고 enable된 상태라면 PWM_CPRDUPD에 period값을 쓴다. 뒤에 UPD가 붙은것은 버퍼이다.

 //우리는 disable한 상태이다.

  PWM-> PWM_CH_NUM[0].PWM_CPRD = 2100; //tpo value -> 84M/(2*2100) = 20000Hz 마스터클럭을 그대로 사용했고  centeraligned 방식이므로 2를 나눈다.

 

 

//pwm channel duty설정.  

//PWM CHANNEL 이 DISABLE된 상태라면 PWM_CDTY에 DUTY값을 작성한다.

//그렇지 않고 ENABLE된 상태라면 PWM_CDTYUPD에 DUTY값을 작성한다. 우리는 DISABLE한 상태이다.

//이때 듀티값은 period값보다 작거나 같아야 한다.

//이곳에 작성한 듀티값은 초기값이다.

  PWM-> PWM_CH_NUM[0].PWM_CDTY = 0;

  PWM-> PWM_CH_NUM[1].PWM_CDTY = 0;

  PWM-> PWM_CH_NUM[2].PWM_CDTY = 0;

 

  PWM-> PWM_SCM |= 0x00000007; //same Source Sync pwm sync channels mode registor

  PWM-> PWM_SCM &= ~0x00030000;

 

 

//PWM_ENA는 하위 8비트가 CHIDx(x=0~7)을 구성한다. 여기서 해당 비트에 0을 쓰면 아무일도 안일어나고 해당비트

//에 1을 쓰면 해당 채널의 pwm output이 enable된다.

  PWM->PWM_ENA=1u<<0; //0번 채널을 enable한 것.

 

 

}

 

void loop()

{

 

  //sim_time += 0.005;   //클수록 빨라짐 simulation time 갱신

  //duty = (uint32_t)(1050*cos(sim_time)+1050); //0<=듀티<=2100

 

  duty=2000;//(0~2100사이의 값을 가져야 함)

  

  PWM-> PWM_CH_NUM[0].PWM_CDTYUPD = duty;//PWM Channel Duty Cycle Update Register

  PWM-> PWM_CH_NUM[1].PWM_CDTYUPD = duty;//CDTYUPD는 This register acts as a double buffer for the CDTY value.

  PWM-> PWM_CH_NUM[2].PWM_CDTYUPD = duty;

 

  PWM->PWM_SCUC =1;//pwm 과 관련한 모든 정보를 갱신하게 해주는 레지스터이다. 항상 1로 해놓자.

  

  Serial.println(duty);

  delay(1);

}