You say you have little real-world experience in this area. Learn from experts to become one. Look around at what professionals of this field are doing to solve your problem. Open source PMBus implementations are out there. The Linux kernel is an example. But Freescale also has a PMBus library out there that is likely better for your background.
General advice about how to think about these things:
If the levels of the protocols are complex enough, then layering is a very good way to handle this complexity. However, layers create little mini-interfaces. So you don't want too many of them. Each of these can break up possible optimizations or slow performance. So, there is a balance to be made with layering to reduce complexity vs. performance. Remember that layering creates a rigid structure around the implementation. This rigidness helps to make it understandable, but the price is that you have by definition less flexibility.
Another way of looking at the problem is whether layers have state or not. In general, stateless layers are preferred. Many interacting state machines can lead to problems with keeping them in sync with one another. Don't take this too far. Obviously some layers have inherent state (such as the lower hardware I2C level with a multi packet message). But many layers do not. Often times, state is inherent at the hardware level and at the application layer. Try to keep any middle layers stateless. In your case, those extra bits of info from the I/O lines in the middle state might be a bit troublesome.
The most overlooked and complex part of these kinds of designs is the error handling. It may seem simple and straightforward at first, but then you realize all those little cases where the bus doesn't respond and you have to break out of that wait loop. Or the buffer is full and you received data, etc. This is where those interacting state machines can become a nightmare. Make sure you watch for those paths or you will have a lot to deal with.
Factorize logic, return early
As suggested in comments, it would be sufficient to simply wrap your logic in a function and exit early with return
's in order to simplify things a lot. Also, you could factorize a bit of functionnality by delegating tests to another function. More concretely:
bool mostly(max,u,v) {
return max > u && max > v;
}
status_t strictly_max_3(a,b,c)
{
if mostly(a,b,c) return MOSTLY_A;
if mostly(b,a,c) return MOSTLY_B;
if mostly(c,a,b) return MOSTLY_C;
return DONT_KNOW;
}
This is shorter than my previous attempt:
status_t index_of_max_3(a,b,c)
{
if (a > b) {
if (a == c)
return DONT_KNOW;
if (a > c)
return MOSTLY_A;
else
return MOSTLY_C;
} else {
if (a == b)
return DONT_KNOW;
if (b > c)
return MOSTLY_B;
else
return MOSTLY_C;
}
}
The above is a little more verbose, but is easy to read IMHO and does not recompute comparisons multiple times.
Visual confirmation
In your answer you say:
my issue was mostly visual confirmation that all comparisons used the
same variables
... also, in your question, you say:
This pattern occurs a few times, and with long variable names it gets
a little difficult to visually confirm that each if is correct.
I may not understand what you are trying to achieve: do you want to copy-paste the pattern everywhere you need it? With a function like the one above, you capture the pattern once, and you check once for all that all comparisons use a
, b
and c
as required. Then, you don't need to worry anymore when you call the function. Of course, maybe in practice your problem is a little more complex than the one you described: if so, please add some details if possible.
Best Answer
There are several reasons to have a protocol, even a very simple one.
There are some advantages in using ASCII chars, if you want to debug. I'll do it in binary, and include a length byte.
I would suggest a simple protocol of 6 bytes. Say:
Simple, easy to code, expandable. That would be my recommendation.
Here are some notes on my choices for these items.
c5,length,cmd,data...,5c
.As suggested in a comment, the additional of a simple checksum would improve the likelihood of detecting errors over a noisy line. A simple rotating XOR should be sufficient, and easy to code.