Comunicação RS485, Modbus e Verificação CRC16

O RS-485 é um padrão de interface elétrica para comunicação serial (também conhecido como TIA/EIA-485) que define as especificações da camada física para a transmissão de dados entre dispositivos usando sinais diferenciais. O objetivo principal é permitir comunicação confiável em longas distâncias, com múltiplos dispostiivos e alta resistência a interferências.

Características do RS-485

  • Utiliza sinais diferenciais, o que melhora a imunidade a ruídos e intefrerências.
  • Permite comunicação em topologia de barramento, suportando até 32 nós em uma única linha (podendo ser expandido para 256 nós com repetidores).
  • Oferece maior distância de transmissão comparado ao RS-232, podendo chegar a 1200 metros em baixas taxas de transmissão.

Diferenças antre RS-232 e RS-485

Característica RS-232 RS-485
Taxa de Transmissão 9600 bps a 15m, 115200 bps a 5m Até 10 Mbps a 50m, 100 kbps a 1200m
Topologia Ponto a Ponto Barramento (Multiponto)
Sinal Unipolar (Referenciado a GND) Diferencial (A e B)

Modbus sobre RS485

Modbus é um protocolo de comunicação de nível de aplicação amplamente utilizado em sistemas industriais. Ele pode ser implementado sobre várias interfaces físicas, incluindo RS485, RS232 e Ethernet. A combinação de Modbus com RS485 é comum em aplicações industriais devido à sua robustez e capacidade de comunicação em longas distâncias.

Estrutura de Dados Modbus RTU

Modbus RTU (Remote Terminal Unit) usa transmissão binária e é o modo mais comum para comunicação via RS485. A estrutura de um quadro Modbus RTU é a seguinte:

  1. Endereço (1 byte)
  2. Código de Função (1 byte)
  3. Dados (0-252 bytes)
  4. CRC16 (2 bytes)

Verificação CRC16

A verificação CRC16 (Cyclic Redundancy Check) é usada para garantir a integridade dos dados transmitidos. Ela é calculada sobre o conteúdo do quadro, excluindo os dois últimos bytes (que são reservados para o próprio CRC).

Exemplo de Implementação de CRC16


unsigned short crc16(unsigned char *data, unsigned int len) {
    unsigned short crc = 0xFFFF;
    for (unsigned int i = 0; i < len; i++) {
        crc ^= data[i];
        for (int j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc >>= 1;
                crc ^= 0xA001;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

Implementação de CRC16 com Tabela de Consulta


const unsigned short crc16_table[256] = {
    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
    // ... (restante da tabela)
};

unsigned short crc16_lookup(unsigned char *data, unsigned int len) {
    unsigned short crc = 0xFFFF;
    for (unsigned int i = 0; i < len; i++) {
        crc = (crc >> 8) ^ crc16_table[(crc ^ data[i]) & 0xFF];
    }
    return crc;
}

Configuração de Comunicação RS485

Para configurar a comunicação RS485 em um microcontrolador, é necessário configurar a UART e controlar os pinos de direção (DE e RE). Abaixo está um exemplo de configuração para STM32F4xx.

Inicialização da UART


void USART3_485_Init(uint32_t baudrate) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    USART_InitTypeDef USART_InitStruct = {0};

    // Habilita o clock
    __HAL_RCC_USART3_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    // Configura os pinos TX e RX
    GPIO_InitStruct.Pin = GPIO_PIN_10 | GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // Configura a UART
    USART_InitStruct.BaudRate = baudrate;
    USART_InitStruct.WordLength = UART_WORDLENGTH_8B;
    USART_InitStruct.StopBits = UART_STOPBITS_1;
    USART_InitStruct.Parity = UART_PARITY_NONE;
    USART_InitStruct.Mode = UART_MODE_TX_RX;
    HAL_UART_Init(&huart3, &USART_InitStruct);
}

Função de Envio de Dados


void RS485_SendByte(uint8_t byte) {
    // Define o pino DE para enviar
    HAL_GPIO_WritePin(GPIOG, GPIO_PIN_8, GPIO_PIN_SET);

    // Envia o byte
    HAL_UART_Transmit(&huart3, &byte, 1, HAL_MAX_DELAY);

    // Restaura o pino DE para receber
    HAL_GPIO_WritePin(GPIOG, GPIO_PIN_8, GPIO_PIN_RESET);
}

Exemplo de Aplicação Modbus RTU

Abaixo está um exemplo de implementação de um escravo Modbus RTU utilizando o protocolo RS485.

Inicialização do Modbus


void Modbus_Init(void) {
    modbus.myAddr = 0x01;  // Endereço do dispositivo
    USART3_485_Init(115200);  // Inicializa a UART

    // Inicializa os registradores
    for (uint8_t i = 0; i < REGISTER_COUNT; i++) {
        reg[i] = i * 10;  // Valores iniciais
    }
}

Processamento de Solicitações Modbus


void Modbus_Fun(void) {
    uint16_t crc = 0;
    uint16_t crc_val = 0;

    if (modbus.finishFlag == 0) {
        return;
    }

    // Calcula o CRC
    crc = CRC_16(modbus.recBuff, modbus.reccount - 2);
    crc_val = (modbus.recBuff[modbus.reccount - 2] << 8) | modbus.recBuff[modbus.reccount - 1];

    // Verifica o CRC
    if (crc_val == crc) {
        // Verifica o endereço
        if (modbus.recBuff[0] == modbus.myAddr) {
            // Processa o código de função
            if (modbus.recBuff[1] == MODBUS_FUNC_READ_HOLDING_REG) {
                Modbus_Fun3();
            } else {
                Modbus_SendError(MODBUS_ERR_ILLEGAL_FUNCTION);
            }
        }
    }

    // Limpa os buffers
    memset(modbus.recBuff, 0, sizeof(modbus.recBuff));
    memset(modbus.sendBuff, 0, sizeof(modbus.sendBuff));
    modbus.finishFlag = 0;
    modbus.reccount = 0;
}

Tratamento do Código de Função 03


void Modbus_Fun3(void) {
    uint8_t i = 0;
    uint16_t crc;
    uint16_t start_add, len;

    if (modbus.finishFlag == 0) {
        return;
    }

    // Decodifica os parâmetros
    start_add = (modbus.recBuff[2] << 8) | modbus.recBuff[3];
    len = (modbus.recBuff[4] << 8) | modbus.recBuff[5];

    // Valida os parâmetros
    if (len < 1 || len > 125) {
        Modbus_SendError(MODBUS_ERR_ILLEGAL_DATA);
        return;
    }

    if (start_add + len > REGISTER_COUNT) {
        Modbus_SendError(MODBUS_ERR_ILLEGAL_ADDRESS);
        return;
    }

    // Constrói a resposta
    modbus.sendBuff[i++] = modbus.myAddr;
    modbus.sendBuff[i++] = MODBUS_FUNC_READ_HOLDING_REG;
    modbus.sendBuff[i++] = len * 2;

    for (uint8_t j = 0; j < len; j++) {
        modbus.sendBuff[i++] = (reg[start_add + j] >> 8) & 0xFF;
        modbus.sendBuff[i++] = reg[start_add + j] & 0xFF;
    }

    // Calcula o CRC
    crc = CRC_16(modbus.sendBuff, i);
    modbus.sendBuff[i++] = (crc >> 8) & 0xFF;
    modbus.sendBuff[i++] = crc & 0xFF;

    // Envia a resposta
    RS485_SEND_MOD();
    for (uint8_t j = 0; j < i; j++) {
        RS485_Send(modbus.sendBuff[j]);
    }
    RS485_REC_MOD();
}

Loop Principal


int main() {
    // Inicializa o sistema
    SystemInit();

    // Inicializa o timer para detecção de timeout
    TIM2_Init(10, 8400);  // 1ms interrupt

    // Inicializa o Modbus
    Modbus_Init();

    printf("Modbus Slave Ready\r\n");

    // Loop principal
    while (1) {
        // Processa as transações Modbus
        Modbus_Fun();
    }
}

Tags: RS485 Modbus CRC16 STM32 UART

Publicado em 9-9 07:25