Open hitecSmartHome opened 2 weeks ago
Humm... so the feature request is about using UART in Half Duplex mode. While receiving, no writing. While writing, no receiving.
You want a callback for when all data has left UART FIFO?
Yes
Currently when I get a cb about the data receive, the app immediately wants to write the next packet. It cant write since the uart is still busy. I have to wait until the uart is available so I can write the next packet. This results in a busy while waiting loop on one of the task. If there would be a cb about the rts pin I could write the next packet in that cb because that would signal to the app that the uart is available
You can also look into hardware RS485 support of the ESP32. This should then toggle some DE/RE pin and you could then register a callback on change of this pin (or rise/fall) to handle the next data.
... or do the same on the RTS pin (as suggested in the issue title)
Oh that is a good idea. Thank you very much will definitely try this
Well. This is not good because I literally can't do anything in an ISR routin. Also I can't bind any class method and can't use any std::function.
While this could work
void IRAM_ATTR packetWriteDone(){
}
attachInterrupt(MBUS_RTS, packetWriteDone, FALLING);
It would be even slower than the current implementation. I must set a flag inside the interrupt and watch that flag in the loop. By the time any task reaches the flag check code in it's loop the busy while loop approach could write three packets
Current flow
This would be better
It looks like you want implement the Modbus Protocol. It gaves a lot of working librarys for this. i.e.: https://github.com/yaacov/ArduinoModbusSlave
Have you tried Serial.flush()? It does similar like "uart_wait_tx_done", without a timeout.
Why you don't use Serial.available() for the incoming Bytes? Serial.onReceive() is always a blocking Function. And you can use the RTS Pin normaly before and after sending the packet. You don't must set the Serial to Half-Duplex.
@device111 If you need to implement RS485, then the ESP32-xx all do have hardware support for toggling the DE/RE pin and even have collision detection in hardware. No need to make it complex.
Ok, you can use it for auto-toggle the DE Pin, but the rest of the implementation must affect to this. :wink:
Yes, I have implemented the modbus protocoll but not the traditional so I can't use any library for this.
How is the onReceive
blocking? It loops in the UART
task and calls my callback when the whole packet is received. It does not block anything. I have configured the hardware uart manager just like you said. Look at the init
function
void Modbus::init() {
Serial1.begin(MBUS_BAUD, SERIAL_8N1, MBUS_RX, MBUS_TX);
Serial1.setPins(-1, -1, -1, MBUS_RTS);
Serial1.setMode(UART_MODE_RS485_HALF_DUPLEX);
Serial1.setRxTimeout(MBUS_RX_TIMEOUT);
Serial1.onReceive(
std::bind(&Modbus::handlePacket, this),
PACKET_TRIGGER_ONLY_ON_TIMEOUT
);
Serial1.onReceiveError(std::bind(&Modbus::handleReceiveError, this, std::placeholders::_1));
}
This uses UART_MODE_RS485_HALF_DUPLEX
and I have also configured the pins
Serial1.begin(MBUS_BAUD, SERIAL_8N1, MBUS_RX, MBUS_TX);
Serial1.setPins(-1, -1, -1, MBUS_RTS);
so I don't really have to do any pin toggling.
In my application, when I got a packet, I immidiately send the next packet. ( it is a master on the bus ) When this happens, sometimes the bus is busy and the write won't happen. That is why I implemented this function
bool Modbus::isUartBusy(){
return uart_wait_tx_done(UART_NUM_1, 0) == ESP_ERR_TIMEOUT;
}
So it waits for the TX done. But I would prefer a callback style like onReceive
about the transmit done event so I don't have to implement any busy loop.
This basically looks like this in a simple form
void Modbus::init() {
Serial1.begin(MBUS_BAUD, SERIAL_8N1, MBUS_RX, MBUS_TX);
Serial1.setPins(-1, -1, -1, MBUS_RTS);
Serial1.setMode(UART_MODE_RS485_HALF_DUPLEX);
Serial1.setRxTimeout(MBUS_RX_TIMEOUT);
Serial1.onReceive(
std::bind(&Modbus::handlePacket, this),
PACKET_TRIGGER_ONLY_ON_TIMEOUT
);
Serial1.onReceiveError(std::bind(&Modbus::handleReceiveError, this, std::placeholders::_1));
}
void Modbus::handlePacket() {
// Check how many bytes are available
int available = Serial1.available();
// Check if it would overflow our buffer
if (available >= MAX_MBUS_DATA_LENGTH) {
ESP_LOGE(MBUS_DEBUG_TAG, "Packet is too big: %d bytes. Can't process it.", available);
lastPacketError = BUFF_OVERFLOW;
callErrorCb();
return;
}
// Get all the data
uint8_t rawPacket[available];
int readBytes = Serial1.readBytes(rawPacket, available);
// Check CRC and other error bytes.
if (!isPacketValid(rawPacket, readBytes)) {
ESP_LOGE(MBUS_DEBUG_TAG, "Invalid packet. Can't process it.");
printRawPacket(rawPacket, readBytes);
callErrorCb();
return;
}
// Parse the packet if it was a scan packet.
parseScanPacket(rawPacket, readBytes);
// Call the packet callback if it wasn't a scan and we have a valid callback
if (packetCallback && !isScanning) {
packetCallback(rawPacket, readBytes);
}
}
After these checks are done, the modbus calls the packetCallback
.
This immidiately sends the next packet to the next slave but the write has to wait because there are cases when the bus is still busy. I need continous communication between the slaves and the master. It can not be interrupted or waited on. I have to write the next packet as soon as I can. The onTxDone
or onAvailable
callback would simplify this because I could send the next packet as soon as the bus
is free.
Something like this would be really good
void Modbus::init() {
Serial1.begin(MBUS_BAUD, SERIAL_8N1, MBUS_RX, MBUS_TX);
Serial1.setPins(-1, -1, -1, MBUS_RTS);
Serial1.setMode(UART_MODE_RS485_HALF_DUPLEX);
Serial1.setRxTimeout(MBUS_RX_TIMEOUT);
Serial1.onReceive(
std::bind(&Modbus::handlePacket, this),
PACKET_TRIGGER_ONLY_ON_TIMEOUT
);
Serial1.onReceiveError(std::bind(&Modbus::handleReceiveError, this, std::placeholders::_1));
Serial1.onTransmitDone(std::bind(&Modbus::handleTransmitDone, this, std::placeholders::_1));
}
I could send the next packet inside handleTransmitDone
because at this point it is sure that the bus is free.
Currently: got packet -> parse packet -> wait for uart free -> send next packet
Ideally: got packet -> parse packet got uart free -> send next packet
If the UART becomes free while a task parses the packet it would be event better because we don't have to wait for parsing the packet. We could send the next packet immidiately regardless of the latest data.
Hi @hitecSmartHome did you find any solution for this?. I'm actually trying to adapt profibus stack for esp32 based on this project github. I need to adapt USART data register empty and uart receive interrupts for esp32.
Well, as I said, I'm doing it like this now:
Serial1.onReceive([](){
// Got a whole packet. That callback means the uart triggered a byte timeout.
// Read the whole UART buffer into my own.
int available = Serial1.available();
uint8_t rawPacket[available];
int readBytes = Serial1.readBytes(rawPacket, available);
// Do some checks on the packet like CRC and things like that...
if( isPacketValid() ){
// If the packet was valid, process it.
processValidPacket();
}
// Write the next packet but wait for uart busy in `writeNextPacket`
writeNextPacket();
},PACKET_TRIGGER_ONLY_ON_TIMEOUT);
@hitecSmartHome uart receive interrupt is ok, but what about uart data register empty interrupt?
There is none.
Related area
UART
Hardware specification
UART
Is your feature request related to a problem?
Currently ( with arduino v3 and idf v5 ) I can register callback functions for
onReceive
andonReceiveError
. When I receive a packet I immidiately want to write the next. It is not possible because theUART
is still busy. I had to implement a logic to wait for theUART
to become available before I can write.A cb function would be good if the
UART
is available again.Describe the solution you'd like
Describe alternatives you've considered
Before I want to write I call this wrapper function
This is the implementation
This is not ideal because it freezes the task which wants to write.
Additional context
Here is how I use the UART right now.
I have checked existing list of Feature requests and the Contribution Guide