MCU: PIC18F45K20

IDE: MPLAB X IDE v6.00

Compiler: XC8 v2.36

 

 

데이터시트를 참조하여 인터럽트를 사용하여 타이머를 구동해 보도록 해 보겠습니다.

 

File > New Project를 클릭하여 프로젝트를 생성합니다.

Standalone Project 선택 후 Next >

 

알맞는 Device를 선택하고 Next >

 

Compiler 선택하고 Next >

 

 

Project Name, Location 설정하고 Finish

 

 

프로젝트 생성 완료

 

main.c 파일을 생성합니다.

 

 

기본 설정 코드는 아래 링크에서 사용했던 것을 사용합니다.

[PIC][MPLABX][XC8] GPIO :: 취미 블로그 (tistory.com)

 

[PIC][MPLABX][XC8] GPIO

MCU: PIC18F45K20 IDE: MPLAB X IDE v6.00 Compiler: XC8 안녕하세요. 이번 시간에는 데이터시트를 보고 PIC18F45K20의 GPIO를 제어하도록 해 보겠습니다 보드의 정보는 아래 링크에서 확인 할 수 있습니다. DM164..

jeonhj.tistory.com

 

 

 

 

1. Timer0 레지스터

아래는 타이머0의 블럭 다이어그램입니다.

 

 

타이머 0 인터럽트 플래그인 TMR0IF는

8Bit Mode에서는 TMR0가 255 -> 0 으로 되었을 때

16Bit Mode에서는 TMR0가 65535 - > 0 으로 되었을 때 

1로 설정됩니다.

 

TMR0ON: Timer0 Enable / Stop 설정

T08BIT: Timer0 8 bit / 16 bit 설정

T0CS : 클럭 소스 설정

T0SE: Timer0 Source Edge 설정 (T0CS = 1일때 유효)

PSA: 분주비 Enable / Disable 설정

T0PS: 분주비 설정

 

여기서 저는 클럭 소스를 Fosc/4로 설정하고, 16Bit Timer에 1:8의 분주비를 사용하겠습니다.

 

T0CON = 0b00000010;

 

 

2. Timer0 시간계산

Time = 4 / Fosc * Prescaler * Counter

 

 

저는 

16Bit Timer에 

Fosc = 64000000

Prescaler = 8

Counter = 2000

으로 설정하면 

 

4 / 64000000 * 8 * 2000 = 0.001 = 1ms가 됩니다.

 

즉 Timer0가 2000을 세면 1ms마다 인터럽트가 걸리도록 세팅합니다.

 

PIC18F45K20의 타이머는 기본적으로 0, 1, 2, ... 처럼 숫자를 올리는 UP Timer 입니다.

16Bit 모드에서 2000을 세면 인터럽트가 걸리기 위해서는 65535 - 2000  값으로 설정해 주어야 합니다.

 

65535 - 2000 = 63535 = 0xF82F

 

이 값을 TMR0H와 TMR0L 에 값을 써줍니다.

void Initialize_Timer0(void)
{
    T0CON = 0b00000010;
    TMR0H = 0xF8;
    TMR0L = 0x2F;
}

 

 

3. 인터럽트 설정

다음은 PIC18F45K20의 인터럽트 로직입니다.

 

위 TMR0 인터럽트가 걸리기 위한 조건은 다음과 같습니다.

 

TMR0IF == 1 && TMR0IE == 1 && TMR0IP  == 1

&&

GIEH / GIE == 1

 

PIC 18F45K20 은 인터럽트 벡터가 0x0008h, 0x0018h 두개가 있습니다.

높은 우선순위로 설정된 인터럽트 (xxIP == 1)는 0x0008h 인터럽트 벡터 번지에서 발생되고

낮은 우선순위로 설정된 인터럽트 (xxIP == 0)는 0x0018h 인터럽트 벡터 번지에서 발생됩니다.

 

 

저는 TMR0 인터럽트를 사용하기 위해서 다음과 같이 설정합니다.

 

void Initialize_Interrupt(void)
{
    INTCON = 0b10100000;
    INTCON2 = 0b00000100;
}

 

이제 인터럽트 벡터 함수를 만들어 주어야 합니다.

MPLAB_XC8_C_Compiler_User_Guide_for_PIC.pdf 파일을 보면 인터럽트에 대한 내용이 있습니다.

XC8 User Guide 문서에 나와있는 interrupt 벡터 함수 예제

 

위를 바탕으로 다음과 같이 함수를 만들어 줍니다.

volatile uint32_t tick = 0;

void TMR0_ISR(void)
{
    tick++;
}

void __interrupt(high_priority) InterruptManager (void)
{
    if(INTCONbits.TMR0IE == 1 && INTCONbits.TMR0IF == 1)
    {
        TMR0H = 0xF8;
        TMR0L = 0x2F;
        INTCONbits.TMR0IF = 0;
        TMR0_ISR();
    }
}

 

타이머0 인터럽트가 발생하면 TMR0H, TMR0L 값을 63535로 다시 설정하고 tick 변수 값을 1 증가시키도록 하였습니다.

 

또 TMR0IF 비트 설명을 보면 

TMR0 register has overflowed (must be cleared by software)

라고 software로 clear 시켜주어야 한다고 되어 있기 때문에 

INTCONbits.TMR0IF = 0;

코드도 넣어주었습니다.

 

이제 Timer0와 인터럽트 설정이 끝났습니다.

 

이제 초기화 함수를 부르는 코드와 1초마다 PORTD를 토글 시키는 코드를 작성합니다.

// PIC18F45K20 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1H
#pragma config FOSC = INTIO67     // Oscillator Selection bits (HS oscillator, PLL enabled (Clock Frequency = 4 x FOSC1))
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRT = OFF       // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 18        // Brown Out Reset Voltage bits (VBOR set to 1.8 V nominal)

// CONFIG2H
#pragma config WDTEN = OFF      // Watchdog Timer Enable bit (WDT is controlled by SWDTEN bit of the WDTCON register)
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = PORTC   // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = ON      // PORTB A/D Enable bit (PORTB<4:0> pins are configured as analog input channels on Reset)
#pragma config LPT1OSC = OFF    // Low-Power Timer1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config HFOFST = ON      // HFINTOSC Fast Start-up (HFINTOSC starts clocking the CPU without waiting for the oscillator to stablize.)
#pragma config MCLRE = ON       // MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = ON         // Single-Supply ICSP Enable bit (Single-Supply ICSP enabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection Block 0 (Block 0 (000800-001FFFh) not code-protected)
#pragma config CP1 = OFF        // Code Protection Block 1 (Block 1 (002000-003FFFh) not code-protected)
#pragma config CP2 = OFF        // Code Protection Block 2 (Block 2 (004000-005FFFh) not code-protected)
#pragma config CP3 = OFF        // Code Protection Block 3 (Block 3 (006000-007FFFh) not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection Block 0 (Block 0 (000800-001FFFh) not write-protected)
#pragma config WRT1 = OFF       // Write Protection Block 1 (Block 1 (002000-003FFFh) not write-protected)
#pragma config WRT2 = OFF       // Write Protection Block 2 (Block 2 (004000-005FFFh) not write-protected)
#pragma config WRT3 = OFF       // Write Protection Block 3 (Block 3 (006000-007FFFh) not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection Block 0 (Block 0 (000800-001FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection Block 1 (Block 1 (002000-003FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection Block 2 (Block 2 (004000-005FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection Block 3 (Block 3 (006000-007FFFh) not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0007FFh) not protected from table reads executed in other blocks)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.


#include <xc.h>
#define _XTAL_FREQ (64000000UL)

volatile uint32_t tick = 0;

void Initialize_SystemClock(void)
{
    OSCCONbits.IRCF = 0b111;
    OSCCONbits.SCS = 0b00;
    OSCTUNEbits.PLLEN = 1;
}

void Initialize_Port(void)
{
    TRISD = 0x00;
}

void Initialize_Timer0(void)
{
    T0CON = 0b00000010;
    TMR0H = 0xF8;
    TMR0L = 0x2F;
}

void Run_Timer0(void)
{
    T0CONbits.TMR0ON = 1;
}

void Stop_Timer0(void)
{
    T0CONbits.TMR0ON = 0;
}

void Initialize_Interrupt(void)
{
    INTCON = 0b10100000;
    INTCON2 = 0b00000100;
}

void TMR0_ISR(void)
{
    tick++;
}

void __interrupt(high_priority) InterruptManager (void)
{
    if(INTCONbits.TMR0IE == 1 && INTCONbits.TMR0IF == 1)
    {
        TMR0H = 0xF8;
        TMR0L = 0x2F;
        INTCONbits.TMR0IF = 0;
        TMR0_ISR();
    }
}

void main(void)
{
    uint32_t time = 0;
    
    Initialize_SystemClock();
    Initialize_Port();
    
    Initialize_Timer0();
    Initialize_Interrupt();
    
    Run_Timer0();
    
    while (1) {
        if (tick - time > 1000) {
            time = tick;
            PORTD ^= 0xFF;
        }
    }
    return;
}

 

반응형

'MCU > PIC' 카테고리의 다른 글

Microchip Programmer & Debugger  (0) 2022.06.15
[PIC][MPLABX][XC8][MCC]TIMER0 + 인터럽트  (0) 2022.06.15
[PIC][MPLABX][XC8] GPIO  (0) 2022.06.14
[PIC][MPLABX][XC8] 개발환경 구축  (0) 2022.06.11
[PIC][XC8][MPLAB X][MCC] GPIO  (0) 2022.06.11
[XC8] 컴파일 Warning 메세지 띄우지 않기  (0) 2022.03.28

MCU: PIC18F45K20

IDE: MPLAB X IDE v6.00

Compiler: XC8 v2.36

 

 

MCC로 프로젝트를 생성하고 인터럽트를 사용하여 타이머를 구동해 보도록 해 보겠습니다.

 

File > New Project를 클릭하여 프로젝트를 생성합니다.

Standalone Project 선택 후 Next >

 

알맞는 Device를 선택하고 Next >

 

Compiler 선택하고 Next >

 

Project Name, Location 설정하고 Finish

 

프로젝트 생성 완료

 

MCC 버튼을 눌러 설정을 불러옵니다.

 

이 버튼이 없다면,  [PIC][XC8][MPLABX] 개발환경 구축 :: 취미 블로그 (tistory.com)

 

[PIC][XC8][MPLABX] 개발환경 구축

안녕하세요. Microchip사의 8bit PIC을 사용하기 위한 MPLABX 환경을 구성해 보겠습니다. 우선 PIC은 MPLABX 라고 불리우는 IDE를 사용합니다. 아래 링크에서 다운 받아 줍니다. MPLAB® X IDE | Microchip Techno..

jeonhj.tistory.com

여기를 참조하여 설치를 해 주세요.

 

 

잠시 기다리면 다음 화면이 나옵니다. Select MCC Classic 버튼을 클릭합니다

Select MCC Classic 클릭

 

라이브러리 선택 페이지인데 사용하지 않으므로 Finish 버튼을 눌러줍니다.

Finish 클릭

 

 

잠시 기다리면 MCC 프로젝트가 생성됩니다.

 

MCC 프로젝트 생성 완료

 

1. System Module 설정

저의 보드에는 크리스탈이 달려있지 않으므로 내부 Oscillator 블럭을 사용하고 PLL을 사용하도록 설정하겠습니다.

 

2. 타이머0 설정

Device Resources에 TMR0를 +클릭합니다

TMR0 Resource 추가

 

 

그러면 Peripherals에 TMR0가 나타나게 되고

저는 1ms마다 인터럽트가 발생되도록 아래와 같이 설정했습니다.

TIMER 0 설정

 

3. 인터럽트 설정

System > Interrupt Module 페이지로 가면 TMR0 인터럽트가 Enabled 된 것을 확인합니다.

 

TMR0 인터럽트 Enabled 설정

 

4. GPIO 설정

타이머가 정상 동작하는지 눈으로 확인하기 위해 GPIO를 설정해줍니다.

저는 PORTD0~7번 핀을 Output으로 설정하겠습니다.

GPIO 설정

설정이 완료되었으면 Generate 버튼을 눌러 코드를 생성합니다.

 

코드 Generate

 

MCC 프로젝트를 종료하려면 MCC 버튼을 다시 누르면 됩니다.

 

 

이제 프로젝트 트리를 보면 다음과 같이 코드 파일이 생성된 것을 볼 수 있습니다.

 

Main.c에 다음과 같이 작성해주었습니다.

#include "mcc_generated_files/mcc.h"

/*
                         Main application
 *  1ms마다 인터럽트를 발생시켜 tick을 증가시키고,
 *  1초마다 PORTD 출력을 토글시키기
 */

volatile uint32_t tick = 0;

void TIMER0_InterruptHandler(void){
    tick ++;
}

void main(void)
{
    uint32_t time = 0;
    // Initialize the device
    SYSTEM_Initialize();

    // Timer0 인터럽트 핸들러 설정
    TMR0_SetInterruptHandler(TIMER0_InterruptHandler);
    // If using interrupts in PIC18 High/Low Priority Mode you need to enable the Global High and Low Interrupts
    // If using interrupts in PIC Mid-Range Compatibility Mode you need to enable the Global and Peripheral Interrupts
    // Use the following macros to:

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();

    // Disable the Global Interrupts
    //INTERRUPT_GlobalInterruptDisable();

    // Enable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptEnable();

    // Disable the Peripheral Interrupts
    //INTERRUPT_PeripheralInterruptDisable();

    while (1)
    {
        // 1초마다 PORTD 토글
        if (tick - time >= 1000) {
            time = tick;
            PORTD ^= 0xFF;
        }
        // Add your application code
    }
}
/**
 End of File
*/

 

컴파일 하고, 프로그램을 다운로드 해 줍니다.

 

다운로드가 완료되면 1초마다 PORTD가 토글되는 것을 볼 수 있습니다.

반응형

'MCU > PIC' 카테고리의 다른 글

[PIC][MPLABX][XC8]TIMER0 + 인터럽트  (0) 2022.06.17
Microchip Programmer & Debugger  (0) 2022.06.15
[PIC][MPLABX][XC8] GPIO  (0) 2022.06.14
[PIC][MPLABX][XC8] 개발환경 구축  (0) 2022.06.11
[PIC][XC8][MPLAB X][MCC] GPIO  (0) 2022.06.11
[XC8] 컴파일 Warning 메세지 띄우지 않기  (0) 2022.03.28

안녕하세요.

 

MCU에서 인터럽트 핸들러 안에서는 동작을 최대한 짧게 가져가야 합니다.

 

그래서 USART와 같은 통신 인터페이스를 사용할때에는 Queue Buffer 구조를 사용합니다.

 

큐(Queue)는 FIFO(First-In, First-Out)로 가장 먼저 들어온 데이터가 가장 먼저 나가는 자료 구조입니다.

Queue 구조, 먼저 들어간 데이터가 가장 먼저 나온다.

 

그 중에서도 원형큐(Circular Queue) 또는 링 버퍼(Ring Buffer)는 다음과 같은 구조를 가지고 있습니다.

원형 큐 구조

 

만약 원이 다 차서 새로운 데이터가 들어오게 된다면 어떻게 될까요?

-> Data1 자리에 덮어쓰게 됩니다. 그 다음 데이터는 Data2 자리에..

 

원으로 계속 반복하게 된다고 해서 원형 큐라고 합니다.

 

원형큐를 다음과 같이 구현할 수 있습니다.

/*
*  Queue 구조 예시
*  만약 head가 tail을 역전할 경우 처리는..?
*/

#define MAX_QUEUE_SIZE		(255)

uint8_t queue_buffer[MAX_QUEUE_SIZE] = {0,};

uint8_t head;
uint8_t tail;

// push: 새로운 데이터를 Queue buffer에 담는다.
void push(uint8_t new_data)
{
  // 새로운 데이터를 queue에 넣는다.
  queue_buffer[head] = new_data;
  
  head++;
  
  // head가 queue 마지막 위치에 도달했다면, 0으로 초기화
  if (head >= MAX_QUEUE_SIZE) {
    head = 0;
  }
}

// pop: 가장 오래된 데이터를 가져온다.
uint8_t pop(void)
{
  // tail의 위치에 데이터를 가져온다
  uint8_t pop_data = queue_buffer[tail];
  
  tail++;
  
  // tail이 queue 마지막 위치에 도달했다면, 0으로 초기화
  if (tail >= MAX_QUEUE_SIZE) {
    tail = 0;
  }
  
  return pop_data;
}

uint8_t isEmpty(void)
{
  // head와 tail의 위치가 같으면 queue가 비어있음
  return head == tail;
}

 

 

위의 코드를 이용하여 USART 데이터를 처리하는 방식을 알아봅시다.

 


/* USER CODE BEGIN PFP */

uint8_t rx_data;

/*Interrupts RX Callback************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1) {
    push(rx_data);
    HAL_UART_Receive_IT(&huart1, &rx_data, 1);
  }
}

int main(void)
{
  /* USER CODE BEGIN 1 */
  uint8_t data;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart1, &rx_data, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (isEmpty() == 0) {
      data = pop();
      
      // 데이터 처리
      // ...
    }
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

상세히 살펴볼게요

HAL_UART_RxCpltCallback 함수는 Uart Rx 인터럽트가 발생하면 호출되는 함수입니다.

 

1. huart 인스턴스가 USART1로부터 Rx 인터럽트가 발생되었다면

2. rx_data를 queue buffer에 넣고

3. 1개 USART 데이터를 수신했다면 인터럽트를 발생시켜라

 

가 되겠습니다.

 

main을 분석해보면

 

1. 1개 데이터를 수신했다면 인터럽트를 발생시켜라

2. 만약 Queue Buffer에 데이터가 있다면

3. 데이터를 꺼내와라

4. - 데이터 처리 -

5. 2 반복

 

이 되겠습니다.

 

데이터 처리는 제품에 맞는 프로토콜에 따라 프로그램하면 되겠습니다.

 

 

 

이를 모듈화 하기 위해서 구조체를 사용하여 구조를 잡아주면 좋을거 같습니다.

// module_uart.h

#define MAX_BUFFER_SIZE    (255)

typedef struct{
  uint8_t head;
  uint8_t tail;
  uint8_t buffer[MAX_BUFFER_SIZE];
}uart_t;

void push(uart_t*, uint8_t);
uint8_t pop(uart_t*);
uint8_t isEmpty(uart_t*);



// module_uart.c

void init_uart(uart_t* u)
{
  u->head = 0;
  u->tail = 0;
  memset(u->buffer, 0, sizeof(u->buffer));
}

void push(uart_t* u, uint8_t data)
{
  u->buffer[u->head] = data;
  
  u->head++;
  
  if (u->head >= MAX_BUFFER_SIZE) {
    u->head = 0;
  }
}

uint8_t pop(uart_t* u)
{
  uint8_t data = u->buffer[u->tail];
  
  u->tail++;
  
  if (u->tail >= MAX_BUFFER_SIZE) {
    u->tail = 0;
  }
  
  return data;
}

uint8_t isEmpty(uart_t* u)
{
  return u->head == u->tail;
}




// main.c

#include "module_uart.h"

uint8_t rx_data;
uart_t uart;

/*Interrupts RX Callback************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1) {
    push(&uart, rx_data);
    HAL_UART_Receive_IT(&huart1, &rx_data, 1);
  }
}

void main() {
  uint8_t data;


  // ..
  
  
  init_uart(&uart);
  
  HAL_UART_Receive_IT(&huart1, &rx_data, 1);
    
  while(1) {
    if (isEmpty(&uart) == 0) {
      data = pop(&uart);
    
    }
  }
}

 

반응형

+ Recent posts