본문 바로가기
카테고리 없음

[AVR128DA48]용 Modbus ASCII 슬레이브 구현 (Modbus ASCII Slave Implementation for AVR128DA48)

by linuxgo 2025. 8. 16.
반응형

1. 개요 (Overview)

이 문서는 AVR128DA48 마이크로컨트롤러를 위한 Modbus ASCII 슬레이브 구현을 설명합니다 (This document describes the implementation of a Modbus ASCII slave for the AVR128DA48 microcontroller). MPLAB X IDE와 XC8 컴파일러를 사용하며, 주요 Modbus 기능 코드를 모두 지원하고, UART 인터럽트와 링 버퍼를 활용하여 데이터를 효율적으로 처리합니다 (It uses MPLAB X IDE and XC8 compiler, supports all major Modbus function codes, and utilizes UART interrupts and a ring buffer for efficient data handling). 코드는 유지보수를 용이하게 하기 위해 모듈화된 파일로 나뉘어 있습니다 (The code is divided into modular files for easier maintenance).

  • 마이크로컨트롤러 (Microcontroller): AVR128DA48 (Curiosity Nano 보드) (AVR128DA48, Curiosity Nano board)
  • 통신 (Communication): 9600 보드레이트의 UART, PB2(TX) 및 PB3(RX) 사용 (UART with 9600 baud rate, using PB2 (TX) and PB3 (RX))
  • 지원 기능 코드 (Supported Function Codes): 01 (코일 읽기, Read Coils), 02 (입력 읽기, Read Discrete Inputs), 03 (홀딩 레지스터 읽기, Read Holding Registers), 04 (입력 레지스터 읽기, Read Input Registers), 05 (단일 코일 쓰기, Write Single Coil), 06 (단일 레지스터 쓰기, Write Single Register), 15 (다중 코일 쓰기, Write Multiple Coils), 16 (다중 레지스터 쓰기, Write Multiple Registers)
  • 오류 검출 (Error Detection): LRC(Longitudinal Redundancy Check) 사용 (Uses Longitudinal Redundancy Check (LRC))
  • 데이터 처리 (Data Handling): UART 수신을 위한 링 버퍼 (Ring buffer for UART reception)

2. 파일 구조 (File Structure)

구현은 세 개의 파일로 구성되어 있습니다 (The implementation consists of three files):

  • main.c: 메인 프로그램, UART 초기화, 링 버퍼 관리, 메인 루프 포함 (Main program, includes UART initialization, ring buffer management, and main loop)
  • modbus_ascii.h: 함수 선언과 상수를 정의한 헤더 파일 (Header file defining function prototypes and constants)
  • modbus_ascii.c: Modbus ASCII 프로토콜 처리 로직 포함 (Contains Modbus ASCII protocol logic)

3. 코드 구현 (Code Implementation)

3.1 main.c

이 파일은 UART를 초기화하고, 링 버퍼를 관리하며, 수신된 Modbus 프레임을 처리하기 위해 Modbus 모듈로 위임합니다 (This file initializes UART, manages the ring buffer, and delegates received Modbus frames to the Modbus module).


#include <avr/io.h> // AVR 입출력 헤더 (AVR I/O header)
#include <avr/interrupt.h> // 인터럽트 처리 헤더 (Interrupt handling header)
#include "modbus_ascii.h" // Modbus ASCII 헤더 파일 (Modbus ASCII header file)

// CPU 및 UART 설정 (CPU and UART configuration)
#define F_CPU 24000000UL // CPU 클럭 주파수: 24MHz (CPU clock frequency: 24MHz)
#define BAUD_RATE 9600 // UART 통신 속도: 9600 baud (UART baud rate: 9600 baud)
#define BAUD_VALUE ((F_CPU / (16UL * BAUD_RATE)) - 1) // Baud rate 계산 공식 (Baud rate calculation formula)

// 링 버퍼 설정 (Ring buffer configuration)
#define RING_BUFFER_SIZE 256 // 링 버퍼 크기 (바이트) (Ring buffer size in bytes)

// 링 버퍼 구조체 정의 (Ring buffer structure definition)
typedef struct {
    char buffer[RING_BUFFER_SIZE]; // 수신 데이터를 저장하는 배열 (Array to store received data)
    uint16_t head; // 데이터 입력 위치 (쓰기 인덱스) (Data input position (write index))
    uint16_t tail; // 데이터 출력 위치 (읽기 인덱스) (Data output position (read index))
} RingBuffer;

// 링 버퍼 전역 변수 (Global ring buffer variable)
RingBuffer rxRingBuffer = { .head = 0, .tail = 0 }; // 링 버퍼 초기화 (Initialize ring buffer)

// 링 버퍼 초기화 함수 (Ring buffer initialization function)
void ring_buffer_init(RingBuffer* rb) {
    rb->head = 0; // 헤드 인덱스 초기화 (Initialize head index)
    rb->tail = 0; // 테일 인덱스 초기화 (Initialize tail index)
}

// 링 버퍼에 데이터 추가 (Add data to ring buffer)
// 반환: 성공(1), 실패(버퍼 가득 참, 0) (Returns: success (1), failure (buffer full, 0))
uint8_t ring_buffer_push(RingBuffer* rb, char data) {
    uint16_t next = (rb->head + 1) % RING_BUFFER_SIZE; // 다음 헤드 위치 계산 (Calculate next head position)
    if (next == rb->tail) return 0; // 버퍼가 가득 찬 경우 (If buffer is full)
    rb->buffer[rb->head] = data; // 데이터 저장 (Store data)
    rb->head = next; // 헤드 인덱스 업데이트 (Update head index)
    return 1; // 성공 (Success)
}

// 링 버퍼에서 데이터 추출 (Extract data from ring buffer)
// 반환: 성공(1), 실패(버퍼 비어 있음, 0) (Returns: success (1), failure (buffer empty, 0))
uint8_t ring_buffer_pop(RingBuffer* rb, char* data) {
    if (rb->head == rb->tail) return 0; // 버퍼가 비어 있는 경우 (If buffer is empty)
    *data = rb->buffer[rb->tail]; // 데이터 추출 (Extract data)
    rb->tail = (rb->tail + 1) % RING_BUFFER_SIZE; // 테일 인덱스 업데이트 (Update tail index)
    return 1; // 성공 (Success)
}

// UART 초기화 함수 (UART initialization function)
void UART_init(void) {
    USART0.BAUD = BAUD_VALUE; // Baud rate 설정 (Set baud rate)
    USART0.CTRLB = USART_RXEN_bm | USART_TXEN_bm; // 송신 및 수신 활성화 (Enable transmit and receive)
    USART0.CTRLA |= USART_RXCIE_bm; // 수신 완료 인터럽트 활성화 (Enable receive complete interrupt)
    PORTB.DIR |= PIN2_bm; // PB2 (TX) 핀을 출력으로 설정 (Set PB2 (TX) pin as output)
    PORTB.DIR &= ~PIN3_bm; // PB3 (RX) 핀을 입력으로 설정 (Set PB3 (RX) pin as input)
    sei(); // 글로벌 인터럽트 활성화 (Enable global interrupts)
}

// UART를 통해 단일 문자 전송 (Transmit single character via UART)
void UART_transmit(char c) {
    while (!(USART0.STATUS & USART_DREIF_bm)); // 데이터 레지스터가 비어 있을 때까지 대기 (Wait until data register is empty)
    USART0.TXDATAL = c; // 문자 전송 (Transmit character)
}

// UART 수신 인터럽트 서비스 루틴 (UART receive interrupt service routine)
ISR(USART0_RXC_vect) {
    char data = USART0.RXDATAL; // 수신된 데이터 읽기 (Read received data)
    ring_buffer_push(&rxRingBuffer, data); // 링 버퍼에 데이터 추가 (Add data to ring buffer)
}

int main(void) {
    UART_init(); // UART 초기화 (Initialize UART)
    ring_buffer_init(&rxRingBuffer); // 링 버퍼 초기화 (Initialize ring buffer)
    modbus_init(); // Modbus 초기화 (Initialize Modbus)
    
    // 테스트용 데이터 설정 (Set test data)
    modbus_set_holding_register(0, 1234); // 홀딩 레지스터 0번에 1234 설정 (Set holding register 0 to 1234)
    modbus_set_input_register(0, 5678); // 입력 레지스터 0번에 5678 설정 (Set input register 0 to 5678)
    modbus_set_coil(0, 1); // 코일 0번을 1로 설정 (Set coil 0 to 1)
    modbus_set_input(0, 1); // 입력 0번을 1로 설정 (Set input 0 to 1)
    
    char frame[MAX_FRAME_LENGTH]; // 수신 프레임 저장용 버퍼 (Buffer for received frame)
    uint8_t frame_idx = 0; // 프레임 인덱스 (Frame index)
    
    while (1) {
        char c;
        if (ring_buffer_pop(&rxRingBuffer, &c)) { // 링 버퍼에서 문자 추출 (Extract character from ring buffer)
            if (c == ':') { // 프레임 시작 문자 감지 (Detect frame start character)
                frame_idx = 0; // 프레임 인덱스 초기화 (Initialize frame index)
                frame[frame_idx++] = c; // 시작 문자 저장 (Store start character)
            } else if (frame_idx > 0 && frame_idx < MAX_FRAME_LENGTH - 1) {
                frame[frame_idx++] = c; // 문자 추가 (Add character)
                if (c == '\n' && frame[frame_idx - 2] == '\r') { // 프레임 종료 감지 (Detect frame end)
                    frame[frame_idx] = '\0'; // 문자열 종료 (Null-terminate string)
                    modbus_process_frame(frame, UART_transmit); // 프레임 처리 (Process frame)
                    frame_idx = 0; // 프레임 인덱스 초기화 (Reset frame index)
                }
            }
        }
    }
}
            

3.2 modbus_ascii.h

Modbus ASCII 모듈의 상수와 함수 선언을 정의한 헤더 파일입니다 (Header file defining constants and function prototypes for the Modbus ASCII module).


#ifndef MODBUS_ASCII_H
#define MODBUS_ASCII_H

#include <stdint.h> // 정수형 데이터 타입 정의 (Standard integer types definition)

// Modbus 설정 (Modbus configuration)
#define SLAVE_ID 0x01 // 슬레이브 주소 (1) (Slave address (1))
#define MAX_FRAME_LENGTH 256 // 최대 프레임 길이 (바이트) (Maximum frame length in bytes)
#define MAX_REGISTERS 100 // 최대 레지스터 수 (Maximum number of registers)
#define MAX_COILS 1000 // 최대 코일 수 (Maximum number of coils)

// Modbus 초기화 함수 (Modbus initialization function)
void modbus_init(void);

// 홀딩 레지스터 설정 함수 (Set holding register function)
// addr: 레지스터 주소, value: 설정할 값 (addr: register address, value: value to set)
void modbus_set_holding_register(uint16_t addr, uint16_t value);

// 입력 레지스터 설정 함수 (Set input register function)
// addr: 레지스터 주소, value: 설정할 값 (addr: register address, value: value to set)
void modbus_set_input_register(uint16_t addr, uint16_t value);

// 코일 설정 함수 (Set coil function)
// addr: 코일 주소, value: 설정할 값 (0 또는 1) (addr: coil address, value: value to set (0 or 1))
void modbus_set_coil(uint16_t addr, uint8_t value);

// 입력 설정 함수 (Set input function)
// addr: 입력 주소, value: 설정할 값 (0 또는 1) (addr: input address, value: value to set (0 or 1))
void modbus_set_input(uint16_t addr, uint8_t value);

// Modbus 프레임 처리 함수 (Modbus frame processing function)
// frame: 수신된 프레임, transmit: 문자 전송 함수 포인터 (frame: received frame, transmit: character transmit function pointer)
void modbus_process_frame(char* frame, void (*transmit)(char));

#endif
            

3.3 modbus_ascii.c

이 파일은 Modbus ASCII 프로토콜의 핵심 로직을 포함하며, 프레임 파싱, 데이터 처리, 응답 생성을 처리합니다 (This file contains the core Modbus ASCII protocol logic, handling frame parsing, data processing, and response generation).


#include "modbus_ascii.h"
#include <string.h> // 문자열 처리 함수 (String handling functions)

// 데이터 저장소 (정적 변수로 캡슐화) (Data storage (encapsulated as static variables))
static uint16_t holdingRegisters[MAX_REGISTERS] = {0}; // 홀딩 레지스터 배열 (Holding registers array)
static uint16_t inputRegisters[MAX_REGISTERS] = {0}; // 입력 레지스터 배열 (Input registers array)
static uint8_t coils[MAX_COILS / 8] = {0}; // 코일 배열 (8코일/바이트) (Coil array (8 coils per byte))
static uint8_t inputs[MAX_COILS / 8] = {0}; // 입력 배열 (8입력/바이트) (Input array (8 inputs per byte))

// Modbus 초기화 함수 (Modbus initialization function)
void modbus_init(void) {
    // 필요 시 추가 초기화 로직 구현 (Implement additional initialization logic if needed)
    // 현재는 데이터 저장소가 배열로 초기화됨 (Currently, data storage is initialized as arrays)
}

// 홀딩 레지스터 설정 함수 (Set holding register function)
void modbus_set_holding_register(uint16_t addr, uint16_t value) {
    if (addr < MAX_REGISTERS) { // 주소 범위 확인 (Check address range)
        holdingRegisters[addr] = value; // 레지스터 값 설정 (Set register value)
    }
}

// 입력 레지스터 설정 함수 (Set input register function)
void modbus_set_input_register(uint16_t addr, uint16_t value) {
    if (addr < MAX_REGISTERS) { // 주소 범위 확인 (Check address range)
        inputRegisters[addr] = value; // 레지스터 값 설정 (Set register value)
    }
}

// 코일 설정 함수 (Set coil function)
void modbus_set_coil(uint16_t addr, uint8_t value) {
    if (addr < MAX_COILS) { // 주소 범위 확인 (Check address range)
        if (value) coils[addr / 8] |= (1 << (addr % 8)); // 코일 값 설정 (1) (Set coil value to 1)
        else coils[addr / 8] &= ~(1 << (addr % 8)); // 코일 값 설정 (0) (Set coil value to 0)
    }
}

// 입력 설정 함수 (Set input function)
void modbus_set_input(uint16_t addr, uint8_t value) {
    if (addr < MAX_COILS) { // 주소 범위 확인 (Check address range)
        if (value) inputs[addr / 8] |= (1 << (addr % 8)); // 입력 값 설정 (1) (Set input value to 1)
        else inputs[addr / 8] &= ~(1 << (addr % 8)); // 입력 값 설정 (0) (Set input value to 0)
    }
}

// 16진수 ASCII 문자 두 개를 바이트로 변환 (Convert two ASCII hex characters to a byte)
// high: 상위 4비트, low: 하위 4비트 (high: upper 4 bits, low: lower 4 bits)
static uint8_t hex_to_byte(char high, char low) {
    uint8_t value = 0;
    if (high >= '0' && high <= '9') value = (high - '0') << 4; // 숫자 처리 (Handle digits)
    else if (high >= 'A' && high <= 'F') value = (high - 'A' + 10) << 4; // 대문자 A-F 처리 (Handle uppercase A-F)
    if (low >= '0' && low <= '9') value += (low - '0'); // 숫자 처리 (Handle digits)
    else if (low >= 'A' && low <= 'F') value += (low - 'A' + 10); // 대문자 A-F 처리 (Handle uppercase A-F)
    return value;
}

// 바이트를 16진수 ASCII 문자 두 개로 변환 (Convert a byte to two ASCII hex characters)
// byte: 변환할 바이트, high/low: 결과 저장 위치 (byte: byte to convert, high/low: result storage)
static void byte_to_hex(uint8_t byte, char* high, char* low) {
    *high = (byte >> 4) & 0x0F; // 상위 4비트 추출 (Extract upper 4 bits)
    *low = byte & 0x0F; // 하위 4비트 추출 (Extract lower 4 bits)
    *high = *high < 10 ? *high + '0' : *high + 'A' - 10; // ASCII로 변환 (Convert to ASCII)
    *low = *low < 10 ? *low + '0' : *low + 'A' - 10; // ASCII로 변환 (Convert to ASCII)
}

// LRC(Longitudinal Redundancy Check) 계산 (Calculate Longitudinal Redundancy Check)
// frame: 입력 프레임, len: 계산할 길이 (frame: input frame, len: length to calculate)
static uint8_t calculate_LRC(const char* frame, uint8_t len) {
    uint8_t lrc = 0;
    for (uint8_t i = 1; i < len; i += 2) { // 시작 문자(':' 제외) (Skip start character ':')
        lrc += hex_to_byte(frame[i], frame[i + 1]); // 바이트 합산 (Sum bytes)
    }
    return (uint8_t)(-lrc); // 2의 보수 반환 (Return 2's complement)
}

// Modbus 프레임 파싱 (Parse Modbus frame)
// frame: 입력 프레임, slave_id/func_code/start_addr/quantity: 파싱 결과, data/data_len: 데이터 필드
// (frame: input frame, slave_id/func_code/start_addr/quantity: parsed results, data/data_len: data field)
static uint8_t parse_modbus_frame(char* frame, uint8_t* slave_id, uint8_t* func_code, uint16_t* start_addr, uint16_t* quantity, uint8_t* data, uint8_t* data_len) {
    if (frame[0] != ':') return 0; // 시작 문자 확인 (Check start character)
    *slave_id = hex_to_byte(frame[1], frame[2]); // 슬레이브 ID 추출 (Extract slave ID)
    *func_code = hex_to_byte(frame[3], frame[4]); // 기능 코드 추출 (Extract function code)
    *start_addr = (hex_to_byte(frame[5], frame[6]) << 8) | hex_to_byte(frame[7], frame[8]); // 시작 주소 추출 (Extract start address)
    *quantity = (hex_to_byte(frame[9], frame[10]) << 8) | hex_to_byte(frame[11], frame[12]); // 수량 추출 (Extract quantity)
    
    *data_len = 0;
    if (*func_code == 0x05 || *func_code == 0x06) { // 단일 코일/레지스터 쓰기 (Single coil/register write)
        data[0] = hex_to_byte(frame[9], frame[10]); // 데이터 값 상위 바이트 (Data value upper byte)
        data[1] = hex_to_byte(frame[11], frame[12]); // 데이터 값 하위 바이트 (Data value lower byte)
        *data_len = 2;
    } else if (*func_code == 0x0F || *func_code == 0x10) { // 다중 코일/레지스터 쓰기 (Multiple coils/registers write)
        uint8_t byte_count = hex_to_byte(frame[13], frame[14]); // 바이트 수 추출 (Extract byte count)
        for (uint8_t i = 0; i < byte_count; i++) {
            data[i] = hex_to_byte(frame[15 + i * 2], frame[16 + i * 2]); // 데이터 추출 (Extract data)
        }
        *data_len = byte_count;
    }
    
    uint8_t received_lrc = hex_to_byte(frame[strlen(frame) - 4], frame[strlen(frame) - 3]); // 수신된 LRC (Received LRC)
    return received_lrc == calculate_LRC(frame, strlen(frame) - 4); // LRC 검증 (Verify LRC)
}

// Modbus 응답 생성 (Generate Modbus response)
// slave_id: 슬레이브 ID, func_code: 기능 코드, start_addr: 시작 주소, quantity: 수량
// data/data_len: 데이터 필드, transmit: 문자 전송 함수
// (slave_id: slave ID, func_code: function code, start_addr: start address, quantity: quantity, data/data_len: data field, transmit: character transmit function)
static void create_modbus_response(uint8_t slave_id, uint8_t func_code, uint16_t start_addr, uint16_t quantity, uint8_t* data, uint8_t data_len, void (*transmit)(char)) {
    char txBuffer[MAX_FRAME_LENGTH]; // 응답 프레임 버퍼 (Response frame buffer)
    uint8_t tx_len = 0; // 응답 프레임 길이 (Response frame length)
    txBuffer[tx_len++] = ':'; // 프레임 시작 문자 (Frame start character)
    byte_to_hex(slave_id, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 슬레이브 ID 추가 (Add slave ID)
    byte_to_hex(func_code, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 기능 코드 추가 (Add function code)
    
    if (func_code == 0x01 || func_code == 0x02) { // 코일/입력 상태 읽기 (Read coils/inputs)
        uint8_t byte_count = (quantity + 7) / 8; // 필요한 바이트 수 계산 (Calculate required byte count)
        byte_to_hex(byte_count, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 바이트 수 추가 (Add byte count)
        for (uint8_t i = 0; i < byte_count; i++) {
            uint8_t byte = 0;
            for (uint8_t j = 0; j < 8 && (i * 8 + j) < quantity; j++) {
                uint8_t value = (func_code == 0x01) ?
                    (coils[(start_addr + i * 8 + j) / 8] >> ((start_addr + i * 8 + j) % 8)) & 0x01 :
                    (inputs[(start_addr + i * 8 + j) / 8] >> ((start_addr + i * 8 + j) % 8)) & 0x01; // 코일/입력 값 읽기 (Read coil/input value)
                byte |= (value << j); // 비트 단위로 바이트 구성 (Build byte bit by bit)
            }
            byte_to_hex(byte, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 바이트 추가 (Add byte)
        }
    } else if (func_code == 0x03 || func_code == 0x04) { // 홀딩/입력 레지스터 읽기 (Read holding/input registers)
        uint8_t byte_count = quantity * 2; // 필요한 바이트 수 (레지스터당 2바이트) (Required byte count (2 bytes per register))
        byte_to_hex(byte_count, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 바이트 수 추가 (Add byte count)
        for (uint16_t i = 0; i < quantity; i++) {
            uint16_t value = (func_code == 0x03) ? holdingRegisters[start_addr + i] : inputRegisters[start_addr + i]; // 레지스터 값 읽기 (Read register value)
            byte_to_hex(value >> 8, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 상위 바이트 (Upper byte)
            byte_to_hex(value & 0xFF, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 하위 바이트 (Lower byte)
        }
    } else if (func_code == 0x05 || func_code == 0x06) { // 단일 코일/레지스터 쓰기 (Write single coil/register)
        byte_to_hex(start_addr >> 8, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 시작 주소 상위 바이트 (Start address upper byte)
        byte_to_hex(start_addr & 0xFF, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 시작 주소 하위 바이트 (Start address lower byte)
        byte_to_hex(data[0], &txBuffer[tx_len++], &txBuffer[tx_len++]); // 데이터 상위 바이트 (Data upper byte)
        byte_to_hex(data[1], &txBuffer[tx_len++], &txBuffer[tx_len++]); // 데이터 하위 바이트 (Data lower byte)
    } else if (func_code == 0x0F || func_code == 0x10) { // 다중 코일/레지스터 쓰기 (Write multiple coils/registers)
        byte_to_hex(start_addr >> 8, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 시작 주소 상위 바이트 (Start address upper byte)
        byte_to_hex(start_addr & 0xFF, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 시작 주소 하위 바이트 (Start address lower byte)
        byte_to_hex(quantity >> 8, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 수량 상위 바이트 (Quantity upper byte)
        byte_to_hex(quantity & 0xFF, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 수량 하위 바이트 (Quantity lower byte)
    } else { // 지원하지 않는 기능 코드 (Unsupported function code)
        byte_to_hex(func_code | 0x80, &txBuffer[3], &txBuffer[4]); // 에러 코드 (0x80 추가) (Error code (add 0x80))
        byte_to_hex(0x01, &txBuffer[tx_len++], &txBuffer[tx_len++]); // 예외 코드 (0x01: 지원하지 않음) (Exception code (0x01: not supported))
    }
    
    uint8_t lrc = calculate_LRC(txBuffer, tx_len); // LRC 계산 (Calculate LRC)
    byte_to_hex(lrc, &txBuffer[tx_len++], &txBuffer[tx_len++]); // LRC 추가 (Add LRC)
    txBuffer[tx_len++] = '\r'; // 캐리지 리턴 (Carriage return)
    txBuffer[tx_len++] = '\n'; // 라인 피드 (Line feed)
    txBuffer[tx_len] = '\0'; // 문자열 종료 (Null-terminate string)
    
    for (uint8_t i = 0; txBuffer[i]; i++) transmit(txBuffer[i]); // 응답 전송 (Transmit response)
}

// Modbus 프레임 처리 (Process Modbus frame)
void modbus_process_frame(char* frame, void (*transmit)(char)) {
    uint8_t slave_id, func_code, data_len;
    uint16_t start_addr, quantity;
    uint8_t data[MAX_FRAME_LENGTH];
    
    if (parse_modbus_frame(frame, &slave_id, &func_code, &start_addr, &quantity, data, &data_len)) { // 프레임 파싱 및 검증 (Parse and verify frame)
        if (slave_id == SLAVE_ID) { // 슬레이브 ID 일치 확인 (Check if slave ID matches)
            if (func_code == 0x05) { // 단일 코일 쓰기 (Write single coil)
                uint16_t value = (data[0] << 8) | data[1]; // 데이터 값 추출 (Extract data value)
                if (start_addr < MAX_COILS) modbus_set_coil(start_addr, value == 0xFF00); // 코일 설정 (Set coil)
            } else if (func_code == 0x06) { // 단일 레지스터 쓰기 (Write single register)
                uint16_t value = (data[0] << 8) | data[1]; // 데이터 값 추출 (Extract data value)
                if (start_addr < MAX_REGISTERS) modbus_set_holding_register(start_addr, value); // 레지스터 설정 (Set register)
            } else if (func_code == 0x0F) { // 다중 코일 쓰기 (Write multiple coils)
                for (uint16_t i = 0; i < quantity; i++) {
                    if (start_addr + i < MAX_COILS) {
                        uint8_t value = (data[i / 8] >> (i % 8)) & 0x01; // 비트 단위로 값 추출 (Extract value bit by bit)
                        modbus_set_coil(start_addr + i, value); // 코일 설정 (Set coil)
                    }
                }
            } else if (func_code == 0x10) { // 다중 레지스터 쓰기 (Write multiple registers)
                for (uint16_t i = 0; i < quantity; i++) {
                    if (start_addr + i < MAX_REGISTERS) {
                        modbus_set_holding_register(start_addr + i, (data[i * 2] << 8) | data[i * 2 + 1]); // 레지스터 설정 (Set register)
                    }
                }
            }
            create_modbus_response(slave_id, func_code, start_addr, quantity, data, data_len, transmit); // 응답 생성 및 전송 (Generate and transmit response)
        }
    }
}
            

4. 사용 방법 (Usage Instructions)

4.1 하드웨어 설정 (Hardware Setup)

  •    AVR128DA48 Curiosity Nano 보드를 사용하세요 (Use the AVR128DA48 Curiosity Nano board).
  •    PB2(TX)와 PB3(RX)를 RS-232 또는 RS-485 모듈에 연결하세요 (예: USB-to-TTL 모듈로 PC와 통신) (Connect PB2 (TX) and PB3 (RX) to an RS-232 or RS-485 module, e.g., USB-to-TTL module for PC communication).

4.2 소프트웨어 설정 (Software Setup)

  •    MPLAB X IDE에서 새 프로젝트를 생성하고 AVR128DA48 마이크로컨트롤러를 선택하세요 (Create a new project in MPLAB X IDE, selecting the AVR128DA48 microcontroller).
  •    XC8 컴파일러를 사용하세요 (Use the XC8 compiler).
  •    main.c, modbus_ascii.h, modbus_ascii.c를 프로젝트에 추가하세요 (Add main.c, modbus_ascii.h, and modbus_ascii.c to the project).
  •    modbus_ascii.hmain.cmodbus_ascii.c에 포함되도록 설정하세요 (Ensure modbus_ascii.h is included in both main.c and modbus_ascii.c).
  •    코드를 빌드하고 AVR128DA48에 플래시하세요 (Build and flash the code to the AVR128DA48).

4.3 테스트 (Testing)

Modbus 마스터 소프트웨어(예: QModbus, ModScan)를 사용하여 슬레이브 구현을 테스트 바랍니다.(Use Modbus master software, e.g., QModbus or ModScan, to test the slave implementation).

  •    코일 읽기 (01, Read Coils): 요청 (Request): :010100000001FA → 응답 (Response): :01010101F8 (코일 0 = 1) (Coil 0 = 1)
  •    홀딩 레지스터 읽기 (03, Read Holding Registers): 요청 (Request): :010300000001C4 → 응답 (Response): :01030204D2 (레지스터 0 = 1234) (Register 0 = 1234)
  •    단일 레지스터 쓰기 (06, Write Single Register): 요청 (Request): :0106000004D2B8 → 응답 (Response): :0106000004D2B8

5. 주의사항 (Notes)

  • 메모리 사용 (Memory Usage): AVR128DA48은 128KB 플래시와 16KB SRAM을 제공합니다. 필요 시 MAX_REGISTERSMAX_COILS를 조정하여 메모리를 최적화 하면 됩니다.(The AVR128DA48 has 128KB Flash and 16KB SRAM. Adjust MAX_REGISTERS and MAX_COILS to optimize memory usage if needed).
  • 타이밍 (Timing): 9600 보드레이트를 사용합니다. 더 높은 속도(예: 19200)로 변경하려면 BAUD_VALUE를 재계산 하시면 됩니다. (Uses 9600 baud rate. Recalculate BAUD_VALUE for higher speeds, e.g., 19200).
  • 확장성 (Extensibility): 잘못된 주소나 수량 초과에 대한 추가 오류 처리나 타이머 기반 타임아웃을 추가할 수 있습니다 (Additional error handling for invalid addresses or quantities, or timer-based timeouts, can be added).
  • 인터럽트 충돌 (Interrupt Conflicts): 다른 인터럽트를 사용할 경우 우선순위를 확인하여 충돌을 방지 바랍니다. (If using other interrupts, ensure proper priority configuration to avoid conflicts).

6. 참고 자료 (References)

  • Microchip AVR128DA48 데이터시트 (Microchip AVR128DA48 Datasheet)
  • Modbus 프로토콜 사양 (modbus.org) (Modbus Protocol Specification (modbus.org))
  • MPLAB X IDE 및 XC8 컴파일러 문서 (MPLAB X IDE and XC8 Compiler Documentation)

 

반응형