My idea of fully-portable C code

I've been talking about portable C programming lately, and I've claimed that I write fully-portable code. Many people here contested this claim, some more rudely than others, so instead of trash-talking back and forth I'm going to actually give an example of the way I code.

In my most recent embedded project, there was a severe memory restriction. I had to store in memory the state of 42 individual LED's. Each LED could have one of three states. To use the least amount of memory possible for each LED, I thought I'd go with the following strategy:

  • Use three bits per LED. 00 = Off. 01 = Red. 10 = Unused. 11 = Green.

I decided to go about writing a "module", a C source and header file couple that would handle this tight memory access and provide a nice, clean, easy-to-use interface. The module would need two pieces of information from the programmer:

  • The amount of combinations (i.e. 3 for my LEDs' states) * The amount of chunks (i.e. 42 because I've 42 individual LED's)

From there, it would handle all the data accessing, both reading and writing. All the programmer has to do is invoke "GetChunk" and "SetChunk".

In starting out to write this module, I knew I wanted it to be as portable as possible. I wanted it to work on machines that have an 8- Bit byte. Also those funky old machines that have a 9-Bit byte. Also supercomputers that have a 64-Bit byte.

First off, I started with a header file to provide an interface. I refer to the little 3-Bit chunks of memory as a "chunk", and so the functions are "SetChunk" and "GetChunk". (Of course you can set the chunk size bigger or smaller than 3 bits).

Here's the header file:

/* ------------------------ Begin Header File: data_acc.h

------------------------------ */ #ifndef H__DATA_ACC #define H__DATA_ACC

#include

void SetChunk(uint_fast16_t const i, uint_fast8_t const state); uint_fast8_t GetChunk(uint_fast16_t const i);

void SetEntireDataAllZeroes(void); void SetEntireDataAllOnes(void);

#endif /* ------------------------ End Header File: data_acc.h

------------------------------ */

Before I show the source code, I'm going to mention a complication. Let's take a canonical system such as an Intel PC that has an 8-Bit byte. If we were to use 3 bits per LED, then we'd be able to store 2 LED's in one byte and we'd have 2 bits left over (i.e. 2(3) + 2 = 8). Well, instead of discarding those bits, what my code does is it takes the last 2 bits from the previous byte in conjunction with the 1st bit of the next byte. No bits are wasted on any system, regardless of the amount of bits in a byte or the amount of bits per "chunk".

Now before I show the source file, I have to tell you about the two constants that are required from the programmer:

QUANTITY_CHUNKS (e.g. This is 42 for the 42 LED's) BITS_PER_CHUNK (e.g. This is 2 because I've 2 bits per LED, e.g. 01 = Red)

These two constants shall be defined in a file called "data_acc_specs.h". Here's a sample:

#define QUANTITY_CHUNKS 42u #define BITS_PER_CHUNK 3u

How the source code works is as follows. Internally, it has the concept of a "chunk pointer", i.e. a data type that gives it all the information it needs to locate a specific chunk in memory. The chunk pointer consists of two things: * The address of the byte in which the first bit resides * The index of the bit in that byte

When you invoke GetChunk for instance, it will convert the index of a chunk to a "chunk pointer", and this chunk pointer will give it the information it needs to locate and read the relevant chunk.

Here's the source code:

/* ------------------------ Begin Source File: data_acc.c

------------------------------ */ #include "data_acc.h" #include "data_acc_specs.h"

#include /* memset */ #include /* CHAR_BIT */ #include

#define TOTAL_BITS_NEEDED (QUANTITY_CHUNKS * BITS_PER_CHUNK)

#define BYTES_NEEDED (TOTAL_BITS_NEEDED / CHAR_BIT + !! (TOTAL_BITS_NEEDED % CHAR_BIT))

#define BITS_ALL_ONES(x) ((1u >= addrs.i_bit;

# if CHAR_BIT % BITS_PER_CHUNK || !defined(NDEBUG) /* Chunk can spill into next byte */

if (addrs.i_bit + BITS_PER_CHUNK > CHAR_BIT) /* Has spilt into next byte */ { # define bits_in_current_byte (CHAR_BIT - addrs.i_bit) # define bits_in_next_byte (BITS_PER_CHUNK - bits_in_current_byte)

char unsigned y = addrs.pbyte[1];

y &= BITS_ALL_ONES(bits_in_next_byte);

y
Reply to
Tomás Ó hÉilidhe
Loading thread data ...

Both of those 3's should be 2's. (I got mixed up between using 2 bits per LED and having 3 different combinations).

That 3 again should be a 2, but you can compile it as is and it still works. In fact, setting it to 3 lets you test out the "overflow" functionality whereby a chunk can spill into the next adjacent byte.

Reply to
Tomás Ó hÉilidhe

Snip...

Then do it in 8 lines of assembly code and be done with it.

Snip...

Sorry...

Reply to
Jim Stewart

It is a pleasure to see how you are standing for your words without=20 loosing your face. Good job, sir.

Hence there is a minimum of 67 bits. But this will require heavy weight=20 arithmetics.

:))))

Combine the state of five leds into one byte. That totals to eight bytes =

plus four bits for the remaining two leds =3D 68 bits.

[...]

Being portable is not an ultimate goal by itself. The goal is get your=20 bills payed; the portability is only one of the tools in your toolbox.

[...]

How can you preach that boring stuff? Get a life :)

Really? I thought who would have care :)

That's the whole point of the newsgroups: proving that your opponent is=20 an idiot. The more technical you are, the more damage you are making :)

Oh, sure, yes you did :)

Wow! Tears in my eyes.

This is closer to the truth.

This is not an excuse. I am prepared to see how the lions and tigers=20 will tear you apart :)

Let's hope this will start a good flame!

Vladimir Vassilevsky DSP and Mixed Signal Design Consultant

formatting link

Reply to
Vladimir Vassilevsky

Nothing personal, just institional.

He claims to be the cream of the crop in his country/college. I really question the educational system over there, wherever it is.

Reply to
linnix

Now here is someone who truly understands the purpose of Usenet!

I don't have time to justice to the post now - perhaps later. But I can give a couple of points:

Portability between programmers, and between the same programmer six months after writing the code, is vastly more important than "portability" between targets with weird numbers of bits per character.

Thus the correct *portable* way to write this code is to assume that CHAR_BIT is 8. You have a little #if and #error at the start to check that assumption. Then your code is much smaller, much clearer, and probably much more efficient in its generated object code, as it cuts out much of the junk. If you have to use this same module on a target with other CHAR_BIT values, you can write a modified version then.

I think my claim was:

"I haven't seem much indication from your posts that you can write legible C code at all, never mind "fully portable" code (to the extent that such a thing really exists)."

Given that monstrosity of unnecessary preprocessor gymnastics (and it certainly doesn't help that you miswrite your directives by separating the # from the keyword), I stand by that claim.

Reply to
David Brown

Somebody is using strange arithmetic. At a minimum, using the state encoding prescribed, you need two bits per LED. Assuming

8-bit bytes, four LED's states can be packed in one byte. The states for 42 LEDs can then be packed in 11 bytes or 88 bits.
Reply to
Everett M. Greene

3^5 = 243. Hence the state of five LEDs can be packed into one byte.

But this is very dull.

Vladimir Vassilevsky DSP and Mixed Signal Design Consultant

formatting link

Reply to
Vladimir Vassilevsky

Even if the code is perfect, it's lots of overhead to save a few bytes. All these discussions of saving a few pins and bytes are just meaningless.

Reply to
linnix

More machine code also, and slower machine code at that.

Let me see...

3 combinations per LED. If I wanted the maximum efficient tight usage of memory (forgetting about the memory consumed by the sub routines, of course), then I'd need to find a multiple of 3 that is exactly equal to 2 ^ x. (where x can be any integer). I don't think there is such a number... is there? And if there is then it's HUGE, way too big for playing around with 42 chunks.

Of course there's other ways of doing that "leave a few bits over" though.

Reply to
Tomás Ó hÉilidhe

Do you mean "institutional"? (I looked up institional in the dictionary but didn't find an entry)

Chapter and verse please. You can have my car... actually you can have my first born too if you tell me when and where I posted that.

Be more specific. Tell me what I'm so crap at so that I may learn and be better at it.

=2E..unless of course you're just making blind one-way unsubstantial statements?

Reply to
Tomás Ó hÉilidhe

Yes, you are right. You are better at spellings than me.

s
)

I am not the only one saying that your ideas are misguided. You are picking the smallest uC and trying to show what you can do with it. In the real world, people just move on to the next size for a few more pennies. You are not really considering other posters opinion, just arguing for the hack of it.

Reply to
linnix

OK then what would you change in the code if CHAR_BIT were 8?

Please be _very_ specific because I'm the one who wrote the original code and I can't see anywhere I can take advantage of CHAR_BIT being 8 that will lead to cleaner, more simple code.

You're blatantly throwing portability out the window for the sake of throwing portability out the window. Blatantly. Unless of course you can provide me with an example of where it can be beneficial to assume that CHAR_BIT is 8.. ? I'm not being sarcastic or derisory either, I'd actually like to see what you'd do.

Again I'd like to see how you'd change the code to make it neater, shorter, faster, simpler.

Yes, that it was.

My "preprocessor gymnastics" as you call it, actually make the code much much easier to understand, for example the constant of "bits_in_current_byte" is far more descriptive than a few arithmetic operations.

Of course, if you like, you can replace those preprocessor with fully- fledged variables, e.g.:

uint_fast8_t bits_in_current_byte =3D ... ;

But seeing as how my code would be running on a microcontroller I wanted to minimise the amount of variables used.

Also you'll have to be specific about how I "miswrite" the preprocessor directives by "separating the # from the keyword". I did so because there's no other way of achieving proper indentation (the preprocessor only processes lines that begin with a #). And regarding the way in which I achieve indentation by "separating the # from the keyword", I find that the left-justified hash symbol does no harm whatsoever.

Given that you've actually seen my code now, you're in more of a position to form an opinion about it. If you think I can't program portably, then you're just plain wrong, and I think the original post in this thread is a testament to your wrongness. If you think my code is "illegible", then that's entirely up to you, but I myself am proud of it and will stand by it.

Reply to
Tomás Ó hÉilidhe

Please point out the overhead, and also show how you'd remove that overhead by making non-portable assumptions (e.g. that CHAR_BIT =3D=3D 8).

Reply to
Tomás Ó hÉilidhe

linnix:

If you have 12 pins and you need to perform 14 different functions, then surely it's not "meaningless" to try to save pins.

If your data memory has been exceed by 3 bytes, then surely it's not "meaningless" to try to save a few bytes.

Reply to
Tomás Ó hÉilidhe

I'm picking the PIC16F684 because it's the only one I'm familiar with, it's the one I've been using in my college course.

Of course I could get a chip with more pins, more data memory, more code memory, faster clock cycle, but I'm finding it enjoyable and educational to "work with the scraps I'm given".

Well you do make a good point about picking a better microcontroller, but then again if I wanted to mass-produce 17 million devices, I'd probably like to pick a cheaper micrcontroller if it can do the job just as well as the more expensive microcontroller.

And don't forget the enjoyment and educational value I'm getting from working with limited resources.

Reply to
Tomás Ó hÉilidhe

It's meaningless when you can just pick the 20 pins package with 18 I/ Os instead of the 14 pins package with 12 I/Os. How difficult for you to handle the 20 pins DIP? I am not even asking you to deal with TQFP, MLF and BGA.

Reply to
linnix

But surely you'd have the same problem when you want 18 pins to perform 22 functions?

Anyway look this exchange is going nowwhere. I acknowledge what you're saying about just picking a uC that has more I/O pins, but at the moment I'm just playing around with a patchboard, a few LED's, some wires and a snips. I find it interesting to use one sole pin in conjunction with an A-to-D in order to take input from 7 different push buttons.

So yes, you're right to a certain extent, we can just pick better uC's, but I'm liking the discussions pertaining to being "clever" with pins.

Reply to
Tomás Ó hÉilidhe

40 pins DIP are available. Real apps use 64 pins or 100 pins QFP/MLF anyway.

You can read 1024 levels with build-in A2D converters, but that's not the typical way to handle buttons either.

And you are using additional logic chips to do it. You think PCB areas are free?

Reply to
linnix

I've got 7 push buttons. My old design was that I fed these into a

74HCT148 encoder which had 3 output pins, and so my uC needed 3 input pins available in order to see if a button was pressed. (The buttons in my Connect4 game appear under the columns of LED's. If you press the button under Column 3 then a chip will fall into that column.

Anyway someone suggested to me about using the A2D converter so that I only needed _one_ input pin. Do you think this is a good way of going about it? The uC I'm using, the PIC16F684, has a 10-Bit A2D.

For some of the tricks, I am using additional logic chips. For instance I'm using a shift register for the display multiplexer. For other tricks, such as using the A2D for the push buttons, I'm actually getting rid of chips I don't need.

Of course though there's a trade off. I mean if I were to discard the shift register, then I'd need 84 I/O pins on my uC for the display, and maybe it's cheaper to instead get a shift register and a uC with

12 I/O pins.
Reply to
Tomás Ó hÉilidhe

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.