PWM using EMIOS (Enhanced Modular Input Output Subsystem)#
PWM Example (PWM Duty Cycle에 따른 LED 밝기 변화)#
- 새로운 예제를 위한 프로젝트를 생성한다.
- 원하는 동작을 위해 레지스터와 메모리에 직접 접근해서 값을 써야한다.
- Board Schematic을 통해 해당 보드의 PWM 출력에 대한 정보를 파악한다.
- PWM 신호 생성을 위해 사용할 FTM 모듈의 동작 원리를 파악하고 메모리 맵을 분석한다.
- 분석 결과를 활용해 임베디드 프로그래밍을 한다.
1. Datasheet 분석#
Easy Module Shield V1의 RGB LED 중 RED를 이용하기 위해서 PTC10을 사용한다.
해당 예제에는 PTC10에 해당하는 EMIOS_0_CH6를 이용할 것이다.
1-1. MSCR#
MSCR(Multiplexed Signal Configuration Register)의 SSS(Source Signal Select) 비트를 0001b로 설정하면 EMIOS_Channel로 동작한다.
MSCR의 OBE 비트를 1로 설정하면 출력으로 동작한다.
PTCm을 제어하기 위한 MSCRn 번호는 \(\rm n=(m+64)\)로 구할 수 있다.
MSCRn의 주소는 \(\rm SIUL2\ base + MSCR\ offset+ (n \times 4)\)로 구할 수 있다.
MSCR74 주소: 4029_0368h (4029_0000h + 240h + 128h)
1-2. IMCR#
S32K312는 EMIOS 모듈을 사용할 때 IMCRn의 SSS 비트를 따로 설정할 필요가 있다. PTC10의 경우 IMCR566의 SSS 비트를 0100b로 설정해야 한다.
하지만 IMCR566이라는 레지스터는 없다. 512의 offset이 적용된 상태이므로 실제로는 \(566-512=54\)이므로 IMCR54에 접근하면 된다.
IMCRn의 주소는 \(\rm SIUL2\ base + IMCR\ offset+ (n \times 4)\)로 구할 수 있다.
IMCR54 주소: 4029_0B18h (4029_0000h + A40h + D8h)
IMCR54의 SSS 비트에 앞서 확인한 값(0100b)을 쓰면 된다.
1-3. EMIOS#
EMIOS는 S32K3보드에서 제공하는 다목적 모듈로 PWM 생성과 관련된 기능도 제공한다.
우리가 사용할 eMIOS_0은 16비트 타이머이며 클락은 CORE_CLK으로부터 공급된다.
EMIOS 에서는 PWM 신호 생성과 관련해 4가지 모드를 제공하는데, 우리는 Duty Cycle만 변경하면 되므로 OPWMB 모드를 사용할 것이다.
OPWMB 모드에서는 AS1과 BS1 값을 조정하여 Duty Cycle을 지정할 수 있다.
1-4. EMIOS 레지스터#
다음은 출력 모드 설정을 위해 채널을 초기화하는 기본 단계이다. 해당 순서에 맞게 설정을 해주어야 정상 작동한다.
설정에 필요한 레지스터 주소 오프셋은 위와 같다.
1-5. EMIOS 레지스터 설정#
- MCR
이름 | 설명 |
---|---|
GTBE(Global Timebase Enable) | GTBE_OUT이 하나 이상의 eMIOS 인스턴스의 GTBE_IN 입력에 연결된 경우, 신호를 활성화하면 해당 eMIOS 인스턴스의 내부 카운터가 동시에 활성화된다. 내부 카운터를 활성화하기 위해 1b로 설정한다. |
GPREN(Global Prescaler Enable) | 글로벌 프리스케일러 카운터를 활성화하거나 비활성화한다. 프리스케일러를 활성화하여 글로벌 프리스케일러 카운터가 동작하고 클럭 신호를 처리한다. 이를 위해 1b로 설정한다. |
GPRE(Global Prescaler) | 글로벌 클럭 프리스케일러의 분주 비율을 설정한다. 원하는 클럭 분주 비율에서 1을 뺀 값을 이 필드에 설정해야 한다. 프리스케일러 값은 1부터 256까지 설정 가능하다. 해당 예제에서는 분주비를 8로 설정하기 위해 111b로 설정한다. |
MCR의 주소는 \(\rm EMIOS0\ base + IMCR\ offset\) 로 구할 수 있다.
MCR 주소: 4008_8000 (4008_8000h + 0h)
- UC Control
이름 | 설명 |
---|---|
UCPREN(Prescaler Enable) | 프리스케일러를 활성화한다. |
BSL(Bus Select) | eMIOS의 각 채널이 사용할 타이머 버스 또는 내부 카운터를 선택한다. 해당 예제에서는 단일 채널을 사용하므로 11b로 설정하여 내부 카운터를 이용하게끔 한다. |
MODE(Mode Selection) | UC 모드를 선택한다. OPWMB 모드를 사용하기 위해 1100000b로 설정해준다. |
EDPOL(Edge Polarity) | 내부 카운터, 입력 캡처, 또는 입력 캡처 플래그를 트리거하는 신호의 엣지를 선택한다. 해당 예제에서는 상승 엣지 트리거를 사용하기 위해 1b로 설정해준다. 0b: Falling Edge (하강 엣지)에서 트리거 1b: Rising Edge (상승 엣지)에서 트리거 |
UC Control n의 주소는 \(\rm EMIOS0\ base + UC \ Control \ offset+ (n \times 20)\)로 구할 수 있다.
UC Control 6 주소: 4008_80EC (4008_8000h + 2Ch + C0h)
- UC A
이름 | 설명 |
---|---|
A | 레지스터에 AS1 값을 설정하여 PWM 신호의 첫 번째 엣지 위치를 정의한다. |
UC A n의 주소는 \(\rm EMIOS0\ base + UC \ A \ offset+ (n \times 20)\)로 구할 수 있다.
UC A 6 주소: 4008_80E0 (4008_8000h + 20h + C0h)
- UC B
이름 | 설명 |
---|---|
B | 레지스터에 BS1 값을 설정하여 PWM 신호의 두 번째 엣지 위치를 정의한다. |
UC B n의 주소는 \(\rm EMIOS0\ base + UC \ B \ offset+ (n \times 20)\)로 구할 수 있다.
UC B 6 주소: 4008_80E4 (4008_8000h + 24h + C0h)
AS1과 BS1에 적절한 값을 대입하여 PWM 신호의 Duty Cycle을 설정해준다.
* 16비트 타이머이므로 0~65535 사이의 값으로 정해준다.
예로들어 A 레지스터에 1000을 설정하면 카운터가 1000에 도달할 때 첫 번째 엣지가 발생하고, B 레지스터에 2000을 설정하면 카운터가 2000에 도달할 때 두 번째 엣지가 발생하여 PWM 신호가 완성된다.
1-6. CLOCK 설정#
세부 설정은 이전 강의에서 진행한 것과 동일하나 REQ34에 대한 PRTN0_COFB1_CLKEN 레지스터 설정을 해주어 eMIOS0 모듈에 클럭 공급을 활성화시켜준다.
2. 프로그래밍#
2-1. 매크로 정의#
#include "S32K312.h"
#include <stdio.h>
// SIUL2 Registers - PTC10 (10+64=74)
#define SIUL2_BASE (0x40290000UL)
#define SIUL2_MSCR_BASE (SIUL2_BASE + 0x240)
#define SIUL2_MSCR74 (*(volatile unsigned *)(SIUL2_MSCR_BASE + 0x128))
#define SIUL2_IMCR_BASE (SIUL2_BASE + 0xA40)
#define SIUL2_IMCR54 (*(volatile unsigned *)(SIUL2_IMCR_BASE + 0xD8)) // 566 - 512
// MSCR & IMCR Bits
#define OBE_BIT 21
#define SSS_BITS 0
// MC_ME Registers
#define MC_ME_BASE (0x402DC000UL)
#define MC_ME_PRTN0_PCONF (*(volatile uint32_t *)(MC_ME_BASE + 0x100))
#define MC_ME_PRTN0_PUPD (*(volatile uint32_t *)(MC_ME_BASE + 0x104))
#define MC_ME_PRTN0_COFB1_CLKEN (*(volatile uint32_t *)(MC_ME_BASE + 0x134))
#define MC_ME_MCTL (*(volatile uint32_t *)(MC_ME_BASE + 0x000))
#define MC_ME_KEY 0x5AF0
#define MC_ME_KEY_INV 0xA50F
// EMIOS0 Registers - CH6_G
#define EMIOS0_BASE (0x40088000UL)
#define EMIOS0_MCR (*(volatile unsigned *)(EMIOS0_BASE + 0x0))
#define EMIOS0_UC_A6 (*(volatile unsigned *)(EMIOS0_BASE + 0x20 + 0xC0)) // 6x32=192->C0
#define EMIOS0_UC_B6 (*(volatile unsigned *)(EMIOS0_BASE + 0x24 + 0xC0))
#define EMIOS0_UC_Control6 (*(volatile unsigned *)(EMIOS0_BASE + 0x2C + 0xC0))
// MCR Bits
#define GPRE_BIT 8
#define UCPREN_BIT 25
#define GPREN_BIT 26
#define GTBE_BIT 28
// Control Bits
#define MODE_BIT 0
#define EDPOL_BIT 7
#define BSL_BIT 9
2-2. SIUL2_init()#
- 포트 설정을 위해 SIUL2_init() 함수를 구현한다.
PTC10(MSCR74)는 출력으로, PTD2(MSCR98 & IMCR26)은 입력 및 EIRQ로 설정한다.
void SIUL2_Init() { SIUL2_MSCR74 &= ~(0b1111 << SSS_BITS); SIUL2_MSCR74 |= (0b0001 << SSS_BITS); SIUL2_MSCR74 |= (1 << OBE_BIT); SIUL2_IMCR54 &= ~(0b1111 << SSS_BITS); SIUL2_IMCR54 |= (0b0100 << SSS_BITS); }
2-3. EMIOS0_Clock()#
- 클럭 활성화를 위한 함수를 구현한다.
void EMIOS0_Clock(void) { MC_ME_PRTN0_PCONF |= 0x1; MC_ME_PRTN0_PUPD |= 0x1; MC_ME_PRTN0_COFB1_CLKEN |= (1 << 2); MC_ME_MCTL = MC_ME_KEY | (0x4 << 16); MC_ME_MCTL = MC_ME_KEY_INV | (0x4 << 16); }
2-4. EMIOS0_CH6()#
- EMIOS의 PWM을 사용하기 위한 함수를 구현한다.
void EMIOS0_CH6() { EMIOS0_MCR &= ~(1 << GPREN_BIT); EMIOS0_UC_Control6 &= ~(1 << UCPREN_BIT); EMIOS0_UC_A6 = 0; EMIOS0_UC_B6 = 1000; EMIOS0_UC_Control6 |= (0b11 << BSL_BIT); EMIOS0_UC_Control6 |= (0b1100000 << MODE_BIT); EMIOS0_UC_Control6 |= (1 << EDPOL_BIT); EMIOS0_UC_Control6 |= (1 << UCPREN_BIT); EMIOS0_MCR |= (7 << GPRE_BIT); EMIOS0_MCR |= (1 << GPREN_BIT); EMIOS0_MCR |= (1 << GTBE_BIT); }
2-5. main#
- 위에서 작성한 함수들을 호출한다.
int main(void) { SIUL2_Init(); EMIOS0_Clock(); EMIOS0_CH6(); for (;;) { ; } return 0; }
3. 동작 모습#
빌드, 디버그 후 동작시켰을 때 Duty Cycle에 따라 LED의 밝기가 변하는 것을 확인할 수 있다.
4. 실습 예제 (가변 저항에 걸리는 전압 값에 따라 LED 밝기 조절하기)#
- 지난 강의 시간에 실습한 ADC 예제를 활용한다.
- ADC로 읽은 값을 이용해 PWM Duty Cycle을 조절한다.
- 가변 저항을 돌렸을 때 LED의 밝기가 바뀌는 것을 확인한다.