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:
- Endereço (1 byte)
- Código de Função (1 byte)
- Dados (0-252 bytes)
- 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();
}
}