I'm facing a super strange issue regarding passing a pointer of an array which is created locally inside a function to another function. This second function have to edit the same array.
So basically I've implemented on an STM microcontroller (uC) the MODBUS protocol. The uC has only to respond to reading holding resiter and writing register functions (0x3 and 0x10 function).
Clearly, it have to send back a response to the master.
When the function requested is reading the holding register, all works fine. The function is processed as (frame_buffer is the RX baffer of the serial port)
uint8_t function_code = frame_buffer[1];
uint16_t start_address = (frame_buffer[2] << 8) | frame_buffer[3];
uint16_t quantity = (frame_buffer[4] << 8) | frame_buffer[5];
HandleReadHoldingRegisters(start_address, quantity);
void HandleReadHoldingRegisters(uint16_t start_addr, uint16_t quantity)
{
// Validate address and quantity
if ((start_addr + quantity) > MODBUS_HOLDING_REGISTERS_SIZE || quantity == 0 || quantity > 125) {
SendModbusException(0x03, 0x02); // Illegal Data Address
return;
}
uint8_t response_buffer[256];
uint8_t byte_count = quantity * 2;
response_buffer[0] = MODBUS_SLAVE_ADDRESS;
response_buffer[1] = 0x03;
response_buffer[2] = byte_count;
// Copy register data into the response buffer
for (int i = 0; i < quantity; i++) {
response_buffer[3 + (i * 2)] = (modbus_holding_registers[start_addr + i] >> 8) & 0xFF; // High byte
response_buffer[4 + (i * 2)] = modbus_holding_registers[start_addr + i] & 0xFF; // Low byte
}
uint16_t response_length = 3 + byte_count;
SendModbusResponse(response_buffer, response_length);
}
void SendModbusResponse(uint8_t* data, uint16_t length)
{
uint16_t crc = crc16_test((char*)data, length);
data[length] = crc & 0xFF; // CRC Low
data[length + 1] = (crc >> 8) & 0xFF; // CRC High
// Assumes RS485 direction control pin is handled, if necessary, before transmitting
HAL_UART_Transmit_IT(&huart4, data, length + 2);
}
everything works correctly.
When it has to respond to a write request the code is more or less similar
HandleWriteMultipleRegisters(start_address, quantity, &frame_buffer[7]);
void HandleWriteMultipleRegisters(uint16_t start_addr, uint16_t quantity, uint8_t* data)
{
// Validate address and quantity
if ((start_addr + quantity) > MODBUS_HOLDING_REGISTERS_SIZE || quantity == 0 || quantity > 123) {
SendModbusException(0x10, 0x02); // Illegal Data Address
return;
}
// Write the data from the request into our holding registers
for (int i = 0; i < quantity; i++) {
uint16_t reg_value = (data[i * 2] << 8) | data[(i * 2) + 1];
modbus_holding_registers[start_addr + i] = reg_value;
}
// Send the acknowledgment response
uint8_t response_buffer[8];
response_buffer[0] = MODBUS_SLAVE_ADDRESS;
response_buffer[1] = 0x10;
response_buffer[2] = (start_addr >> 8) & 0xFF;
response_buffer[3] = start_addr & 0xFF;
response_buffer[4] = (quantity >> 8) & 0xFF;
response_buffer[5] = quantity & 0xFF;
SendModbusResponse(response_buffer, 6);
}
but, in this case, the data send back to the master ar wrong. Only response_buffer[0] and response_buffer[1] are correct, all the other values are wrong in response_buffer
The strange thing is that if I change the SendModbusResponse(response_buffer, 6);
with
uint16_t crc = crc16_test((char*)response_buffer, 6);
response_buffer[6] = crc & 0xFF; // CRC Low
response_buffer[7] = (crc >> 8) & 0xFF; // CRC High
// Transmit complete response with CRC
HAL_UART_Transmit_IT(&huart4, response_buffer, 8);
Everything works correctly.
I think there's an issue on how the array pointer is sent and processed in SendModbusResponse
or HandleWriteMultipleRegisters
but I can't understand what's going on.
I have also removed all the optimization of the compiler but still, same issue. In debug mode response_buffer
seems to be generated corrected before being sent to the serial.
HAL_UART_Transmit_IT
is a non-blocking function according to its sources. Therefore the function SendModbusResponse
(and it's callers) will return before the whole buffer is transmitted. But when it return, all locally allocated arrays are ceasing to exist (that is the memory is being reused for something else) and the asynchronous transmission is picking up the already corrupted data.