Modbus Protocol Implementation on STM32F4 Microcontrollers

Modbus Communication Architecture

Modbus implementation requires distinct approaches for slave and master devices:

Slave Device Operation

Slave devices maintain constant recieve readiness. The RS485 transceiver remains in receive mode, storing incoming data in a buffer. When a complete message is detected, the packet is parsed and appropriate responses are generated.

Master Device Operation

Masters receive requests via USART1, forward commands through RS485 to slaves, and relay responses back to the originator.

Slave Implementation

Main Application Loop

int main(void) {
  SystemInit();
  NVIC_SetPriorityGrouping(0x02);
  Timer_Init(100, 8400);  // 10ms interval
  UART_Setup(4800);
  RS485_Setup(4800);
  
  while(1) {
    if(completion_flag) {
      ParseModbusFrame();
      completion_flag = 0;
    }
  }
}

Timer Configuration

void TIM3_IRQHandler(void) {
  if(TIM_GetITStatus(TIM3, TIM_IT_Update)) {
    if(idle_counter < 60000) idle_counter++;
    
    if((idle_counter > 10) && (rx_length != 0)) {
      completion_flag = 1;
    }
    TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
  }
}

Modbus Frame Proecssing

void ParseModbusFrame() {
  uint16_t crc = CalculateCRC(rx_buffer, rx_length - 2);
  
  if(crc == (rx_buffer[rx_length-2] << 8 | rx_buffer[rx_length-1])) {
    if(device_id == rx_buffer[0]) {
      switch(rx_buffer[1]) {
        case MB_READ_HOLDING:
          // Construct response
          tx_buffer[0] = device_id;
          tx_buffer[1] = MB_READ_HOLDING;
          tx_buffer[2] = register_count * 2;
          // Populate data
          AddCRCChecksum(tx_buffer, response_size);
          TransmitRS485(tx_buffer, response_size);
          break;
      }
    }
  }
  rx_length = 0;
}

CRC-16 Implementation

uint16_t ComputeModbusCRC(uint8_t *data, uint16_t len) {
  uint16_t crc = 0xFFFF;
  for(uint16_t i = 0; i < len; i++) {
    crc ^= data[i];
    for(uint8_t j = 0; j < 8; j++) {
      if(crc & 0x0001) {
        crc = (crc >> 1) ^ 0xA001;
      } else {
        crc >>= 1;
      }
    }
  }
  return ((crc & 0xFF) << 8) | ((crc >> 8) & 0xFF);
}

Master Implementation

Main Application Logic

int main(void) {
  // Initialization
  while(1) {
    if(rs485_flag) {
      ForwardToUART(rs485_rx, rs485_len);
      rs485_len = 0;
    }
    else if(uart_flag) {
      ForwardToRS485(uart_rx, uart_len);
      uart_len = 0;
    }
  }
}

Dual-Port Forwarding

void ForwardToRS485(uint8_t *data, uint16_t len) {
  RS485_ENABLE_TX();
  for(uint16_t i = 0; i < len; i++) {
    while(!USART_GetFlagStatus(USART2, USART_FLAG_TXE));
    USART_SendData(USART2, data[i]);
  }
  RS485_ENABLE_RX();
}

UART Interrupt Handling

void USART1_IRQHandler(void) {
  if(USART_GetITStatus(USART1, USART_IT_RXNE)) {
    uart_rx[uart_len++] = USART_ReceiveData(USART1);
    idle_counter = 0;
  }
}

RS485 Hardware Control

void RS485_Init(void) {
  GPIO_InitTypeDef gpio;
  gpio.GPIO_Pin = GPIO_Pin_8;
  gpio.GPIO_Mode = GPIO_Mode_OUT;
  gpio.GPIO_OType = GPIO_OType_PP;
  gpio.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_Init(GPIOG, &gpio);
  RS485_ENABLE_RX();  // Default receive mode
}

Tags: STM32F4 Modbus-RTU RS485 UART CRC16

Posted on Fri, 07 Aug 2026 16:04:19 +0000 by Call-911