Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Communication with microcontroller via usart - using strings a good idea?

+4
−0

I have a PCB with an onboard microcontroller that I use to turn on and off a bunch of switches/relays. To control which relay should be on I use my computer to communicate via USART to the microcontroller, with a local python script sending commands.

Right now, I have it set up such that my python script sends two things. First, a string containing the command. Second, the switch number that the command affects.

#main.py
establish_usart_connection()
usart.write(b'RELAY_ON\n')
usart.write(bytes([1])) #Turn on relay 1
end_usart_connection()

Then in my microcontroller code I always wait to receive both a string-command and a number. Then, I compare the string-command with my cases using strcmp to determine what the microcontroller should do. Essentially something like this.

#include <string.h>

#define USART_BUFFER_SZ 64
static uint8_t usart_buffer[USART_BUFFER_SZ] = {0};
static uint8_t switch_number = 0;
int main(void)
{
    usart_receive_line(usart_buffer, USART_BUFFER_SZ);
    switch_number = usart_rx(); //Infinite while loop until receiving a number
    if(strcmp((const char*)usart_buffer, "RELAY_ON") == 0)
    {
        digital_write(relay_arr[switch_number],HIGH);
    }
    else if(strcmp((const char*)usart_buffer, "RELAY_OFF") == 0)
    {
        /* Turn off relay instead */
    }
}

Question: Is it a good approach to use strings when communicating via usart? In my opinion it makes the code easier to read, because you can clearly see that "okay this commands turns on a relay". The issue that I imagine though, is that this is not a robust approach. But maybe I'm wrong?

Also, with this approach I have to include the string.h library in my microcontroller code in order to use strcmp() which just seems a little silly/unnecessary because of the overhead.

History

0 comment threads

4 answers

You are accessing this answer with a direct link, so it's being shown above all other answers regardless of its score. You can return to the normal view.

+4
−0

If it works, you understand the tradeoffs, and you feel it is maintainable, then it's not wrong. However, I wouldn't and don't do it this way personally.

I have done many microcontroller projects. The vast majority of those communicate to a host over a UART, even if just during development, production test, or field diagnostics. What I have converged on is a simple binary protocol. Data is sent in both directions in packets that start with an opcode byte and are followed by whatever data bytes are specified for that opcode. For documentation clarity, I call the packets from the host to the microcontroller "commands", and from the micro to the host "responses". However, responses are not always in direct response to commands, and can be sent asynchronously.

There are a number of advantages to this scheme:

  1. It is simple to parse. You don't have to look for CR, LF, maybe NULL, and possibly other control characters. When the micro is ready for the next command, it interprets the next byte as a command opcode.
  2. It is simple to interpret. The "command name" is a single byte. The command opcode value is indexed into a dispatch table to run the routine to process the particular command. Each command routine knows what data bytes, if any, to expect.
  3. It's fast. In your example, turning on a relay would take two bytes. The first is the "relay on" command opcode, and the second the relay number.
  4. No ASCII to binary conversion is required. The micro will ultimately use data in binary. Sending it that way makes it much easier on the micro.
  5. Using this structure consistently allows for canned routines to re-use between projects, both in the micro and on the host. For example, I have canned routines that receive 1, 2, 3, and 4 byte values into registers in the micro.

Unfortunately much of my code is for specific customers and therefore private. I can show you some general "library" routines I have around this concept, and an example of complete firmware for a PIC 18F2550.

See the GitHub project RDY2T at https://github.com/EmbedInc/rdy2t. This one happens to communicate to the host via USB instead of a UART, but the command processing logic is identical. USB endpoint 1 is used as bi-directional streams of bytes, which is exactly what a UART provides.

See the "doc.txt" file. That's the documentation file for this firmware. Every firmware I create has one of these. One part of every firmware doc file describes the external protocol, if any. See the sections "USB Protocol", "Commands", and "Responses".

I include commands NOP, PING, and FWINFO in pretty much every firmware. The remaining commands and responses depend on whatever the firmware does. In this case (RDY2T), it is meant to be example and template firmware for my ReadyBoard-02.

The command reception and dispatching is handled in the CMDUSB module, rdy2t_cmdusb.aspic. Note that all the logic is actually in the CMDUSB.INS.ASPIC file in the PIC repository (also available on GitHub). There is no need to re-write that every PIC project. The actual commands are in the CMD module.

Since I do this pretty much every PIC project, I have created "library" facilities to make it easy each instance. The commands and response for the RDY2T firmware are defined in the RDY2T_CMDRSP.INS.ASPIC include file. This keeps the definition of all command and response opcodes in one place. This is the only place where the mapping from command/response mnemonics to actual opcodes is defined. This may look confusing because it heavily uses my PIC preprocessor, PREPIC, but you should be able to follow the comments. The preprocessor state left around from processing this include file is used in the CMDUSB module to automatically populate the dispatch table. That's how it can be all "library" code without needing modification to the specific set of commands.

The CMDRSP.INS.ASPIC file is also used to create constant symbols for the host source code. The host code only refers to command and response opcodes by these constants, and not by their actual numbers. Reassigning a command or response to a different opcode only requires changing the CMDRSP.INS.ASPIC include file, then rebuilding the firmware and host software.

There are similar facilities for dsPICs in my DSPIC repository at https://github.com/EmbedInc/dspic. These are all open source and free to use with very minimal strings attached.

History

0 comment threads

+4
−0

Olin has posted a good answer regarding the pros and cons. To add to that, look at where your C code is going: if(strcmp...) else if (strcmp...) and so on. This is a common mistake in embedded C when parsing strings, I've scolded a fair amount of programmers for writing super-inefficient code like that - with such strcmpchains you are essentially iterating over the same string over and over again. It is super-slow look-up and not ok, as addressed by any algorithm class in uni.

The proper production-quality version is to instead implement a binary search across a bunch of alphabetically sorted strings sitting in flash, "O(log n)". Standard C bsearch is probably good enough for most purposes. For more advanced situations you'd perhaps implement a hash map of some kind since those perform better when there is a large amount of data.

As for the advantage of strings, it is that they are obviously more human-friendly and can get entered over an old school terminal, back in the days when we sill used terminals on PC. Something string-based like "AT commands" have been an industry standard for ages. If you need neither high performance nor safety, then why not.

Besides performance, the main arguments against using strings, or even UART, are all related to safety. If you don't need safety/data integrity, then fine. Otherwise:

  • Whenever you send a protocol over UART, you ought to have at least a simple kind of checksum. Sure, you could use "parity" hardware checks, but this is a naive kind of safety that doesn't really catch a whole lot of errors.

    In the realm of functional safety you'll come across the term "diagnostic coverage" meaning how large a percentage of all errors you are able to catch. If you consider that for one bit errors, two bit errors or burst errors, then mathematically both parity checks as well as simple checksums (add bytes together then invert/truncate etc) are immediately dismissed. The only thing good enough is CRC. And you can't send CRC as ASCII or that defeats how it works.

  • In systems with lots of traffic you need means to synchronize the protocol. How does the receiver know it didn't start receiving in the middle of a protocol? Some unique characters are typically used for this. For example you could start the protocol with a bit sequence that is not valid 7 bit ASCII like for example 0xAA. But then that too defeats all reasons to use strings to begin with, since it can't be trivially sent from a terminal.

  • In safety-related systems, protocols can't be allowed to have completely variable lengths. Rather, you would design them to have a fixed maximum length.

  • UART data link layer bit error checking is subpar - it starts off with a start byte then assumes everything in the data byte is clocked in at the expected baud rate from there. And then assumes the stop bits will end up where they should or otherwise it's a framing error, but what about the bits in between? Unlike for example Manchester encoding or CAN, where each and every bit length is checked individually.

Traditionally UART protocols with safety concerns were always built in the manner of: 1 or several synch bytes, a packet size, the payload, then at least CRC-16 at the end. That's not a format encouraging strings as payload.

On the hardware side, anything requiring a bit of safety/integrity should always use RS422/RS485. There isn't a reason why you wouldn't use that. RS232 is more error prone and therefore obsolete since many decades back, not to mention raw "TTL" UART which is very EMI-prone and should never be allowed to leave a PCB.

In general, I feel like UART is old, obsolete technology. Where safety matters it is replaced by CAN, which is superior in every single way. Where it doesn't, it is replaced by USB or Ethernet. The only thing UART has going for it still is the simplicity of implementation - you can cut down development times a fair bit compared to CAN, let alone USB or Ethernet. (Higher layer protocol stacks is another story and they tend to be quite complicated no matter what hardware layer you are going for.)

It used to be that every PC had a RS232 port and that was a big reason to still use UART, but that's no longer the case since a decade or two. If you need an icky USB-to-RS232 adapter (...that never works), you may as well use an USB-to-CAN adapter or pure USB all the way. I think I started to abandon UART in new projects for bootloader-like stuff somewhere around ~2008, in favor of CAN. Good riddance. But then I work pretty much exclusively with safety-related products only. One example of that since relays were mentioned would be relay control boards for industrial use.

History

0 comment threads

+3
−0

Command protocols which use strings are a norm. They use more bytes compared to binary protocols, but they are human readable. If the UART itself isn’t a bottleneck, then command strings are a good choice. Some examples of mainstream protocols which send ASCII strings: NMEA 0183 and SCPI.

The data frames either have start and stop markers. or a start marker and length field. There’s always a CRC or checksum field to detect possible data corruption.

Right now, I have it set up such that my python script sends two things. First, a string containing the command. Second, the switch number that the command affects.

It’s better to send the command and the number of the relay in the same packet. You wouldn’t have to figure out how to handle the command if the second packet doesn’t get through.

You like commands which are English words, rather than opcodes, because that’s more readable in the Python and C code. That’s alright. If someday you need to switch to opcodes, that can be made readable in the code too.

typedef enum {
    OPCODE_STATUS = 0x00,  // Named constants make the numeric opcodes more readable in the source
    OPCODE_RELAY  = 0x01,
    // more opcodes...
} Opcodes;
switch (receivedOpcode) {
        case OPCODE_RELAY:
            // do your things
            break;
        
        // more opcode cases ...
}
History

0 comment threads

+0
−0

Regarding string-based common protocol vs binary, there are indisputable reasons why binary is simpler, faster (these days rarely limiting), and arguably less trouble prone up to the level of complexity implied by the OP's scenario.

However a common scenario is that the data itself has string form, particularly metadata in systems with lots of configuration options.

Especially when families of devices are created with configuration schema that are analogous yet not interchangeable, this can make named parameters (eg "config.sensor[3].scale.min") nicer to work with than numbered registers which have different mappings across product variations or versions. To be clear this is for in-house software integration and developer use, whereas if there is a customer facing protocol IME remains better off as the equivalent of "read register 3", "write register 5", just from simplicity.

Anyhow, if the scenario is one where you are transmitting string values, there isn't much reason not to go with string commands as well.

Regarding the implementation, I would suggest the bounded strncmp() rather than strcmp().

Regarding UART as a physical medium, the case where it can work out is serial-over-USB, where the physical length of raw UART signal is a few centimeters to a galvanically isolated USB interface section (or none at all if built into the MCU). In that scenario, any customer with a computer and generic terminal software can help you troubleshoot over phone/screenchat.

Also a mention to encodings like modbus ASCII, which are binary encoded into two characters per byte, with often fixed line length, and a checksum byte at the end ... eg ":A2B2FCECC12523D5BF" . Tho this isn't exactly human readable, it remains accessible via terminal without dedicated software.

History

0 comment threads

Sign up to answer this question »