Packing bits in XC8

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
With the demise and continued aging of MPLab (in favor of X), I am considering porting some of my .asm libraries to XC8. But I've got a dilemma:

Each of my libraries has a set of library-dependent global flags (boolean bits) that are packed into flag bytes. In assembly, I do this in a .inc file. The register location and position of the bits are irrelevant, and tracked by the label. For example:

Code:
#define pwrup      flag0,0,0        ;0=normal/1=powering up
#define minboot    flag0,1,0        ;0=normal/1=minboot
#define shtdwn     flag0,2,0        ;1=Shutdown system
#define fsleep     flag0,3,0        ;0=Normal Operation/1=go to sleep
#define update     flag0,4,0        ;1=update display
#define fbcolon    flag0,5,0        ;1=blink colon when clock runs
#define fcold      flag0,6,0        ;1=temp too cold
#define fhot       flag0,7,0        ;1=temp too hot

#define bdead      flag1,0,0        ;1=Battery dead
#define _adrdy     flag1,1,0        ;1=On-chip A/D conversion ready
#define run        flag1,2,0        ;0=warmup/1=run
#define lbpdf      flag1,3,0        ;low battery power down flag
#define _spst      flag1,5,0        ;0=speaker lo, 1=speaker hi
#define ftest      flag1,6,0        ;0=normal, 1=test mode
#define fsecond    flag1,7,0        ;1=1 second elapsed
I do this manually, and pack all of the required bits into the fewest number of registers as possible.

I could likely do something similar in C:

C:
struct {
   unsigned pwrup   :1; //0=normal/1=powering up
   unsigned minboot :1; //0=normal/1=minboot
   unsigned shtdwn  :1; //1=Shutdown system
   unsigned fsleep  :1; //0=Normal Operation/1=go to sleep
   unsigned update  :1; //1=update display
   unsigned fbcolon :1; //1=blink colon when clock runs
   unsigned fcold   :1; //1=temp too cold
   unsigned fhot    :1; //1=temp too hot
} flag0;
But I would need to define my globals on a project (as opposed to library) basis -- depending upon the libraries I wanted to use -- and pack the bits manually into one struct per flag register, with each flag register possibly spanning multiple libraries. This is sub-optimal.

Is there a way to define global bits in XC8 in the library header files so that they are automatically packed into the smallest possible number of compiler-allocated global bytes?

Interestingly, this is easy to do in Pascal (using sets). Can C do it?
 
Last edited:

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
As an alternative, is it possible -- as a pre-compilation step -- to have MPLabX execute a (custom) script to identify the required global flags (by parsing the included library header files or extra flag definition files) and dynamically produce an appropriate header file?
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
Why can't you just put the definitions into a C header file?
I can...but to pack all the required bits from the various libraries into the fewest number of registers would require a custom header file for each project.

I'd rather define the bits in each library's header file and have the compiler pack them automatically at build time. Without packing, each library would require at least one flag register (holding up to 8 flags), even if, say, a particular library only require one flag bit. This wastes register space.
 

xox

Joined Sep 8, 2017
936
Maybe I'm misunderstanding you here. So you're trying to "merge" the flags?

Code:
typedef struct flag0 {
   unsigned pwrup   :1; //0=normal/1=powering up
   unsigned minboot :1; //0=normal/1=minboot
   unsigned shtdwn  :1; //1=Shutdown system
   unsigned fsleep  :1; //0=Normal Operation/1=go to sleep
   unsigned update  :1; //1=update display
   unsigned fbcolon :1; //1=blink colon when clock runs
   unsigned fcold   :1; //1=temp too cold
   unsigned fhot    :1; //1=temp too hot
} flag0;

typedef struct flag1 {
   unsigned foo   :1;
   unsigned bar   :1;
   unsigned baz   :1;
   unsigned qux   :1;
} flag1;

typedef union flags {
   flag0 f0;
   flag1 f1;
} flags;

int main(void) {
   flags settings;
   settings.f0.fhot = 1;
   settings.f1.qux = 1;
}
In the above, both f0 and f1 in settings occupy the same space in memory.

Or maybe you meant something else?
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
Maybe I'm misunderstanding you here. So you're trying to "merge" the flags?

Code:
typedef struct flag0 {
   unsigned pwrup   :1; //0=normal/1=powering up
   unsigned minboot :1; //0=normal/1=minboot
   unsigned shtdwn  :1; //1=Shutdown system
   unsigned fsleep  :1; //0=Normal Operation/1=go to sleep
   unsigned update  :1; //1=update display
   unsigned fbcolon :1; //1=blink colon when clock runs
   unsigned fcold   :1; //1=temp too cold
   unsigned fhot    :1; //1=temp too hot
} flag0;

typedef struct flag1 {
   unsigned foo   :1;
   unsigned bar   :1;
   unsigned baz   :1;
   unsigned qux   :1;
} flag1;

typedef union flags {
   flag0 f0;
   flag1 f1;
} flags;

int main(void) {
   flags settings;
   settings.f0.fhot = 1;
   settings.f1.qux = 1;
}
In the above, both f0 and f1 in settings occupy the same space in memory.

Or maybe you meant something else?
Yes, I mean something else.

Let's say I've got three different devices, each with its own driver library.

As way of example, say the devices are 1) a temperature sensor, 2) a buzzer, and 3) an LCD display. Each requires one global flag:

1) temperature_ready: set when a new temperature reading has been acquired;
2) buzzer_on: set when the buzzer is to sound, reset when it is to be quiet;
3) lcd_update: set to request a repainting of the LCD;

Each of these require one unique bit, globally accessible to the main program and other device drivers, and are defined in the header file corresponding to the device.

Overall, this is easy. I can just declare flag bytes in each driver .h file and the associated bits: temperature_flags, buzzer_flags, and lcd_flags. Now, I can just compile in the appropriate .c and .h files for a project.

Unfortunately, this (standard) method requires three global bytes -- when only one byte is really required. When dealing with many libraries in a single project, this adds up.

Instead, I would like to define the bit names that are required for each module, and have the compiler pack and map them into as few bytes as possible during compile time. The actual register locations and bit positions are irrelevant.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
To put a finer point on it, I'd like to create a new type. Call it boolean, if you wish:

typedef bit boolean;

where bit signifies that a variable of type boolean requires one bit.

Then, in each module:

module 1:

boolean temperature_ready;

module 2:

boolean buzzer_on;

module 3:

boolean lcd_update;

And, for instance, in main


C:
main
{
   If (temperature_ready)
   {
      buzzer_on = 1;
      lcd_update = 1;

      temperature_ready = 0;
   }
}
And each bit is packed in a global register.
 
Last edited:

xox

Joined Sep 8, 2017
936
Okay, I see what you mean now. So you've got global variables defined in each driver library and you want to merge them all together into the fewest number of global variables. Correct? AFAICT that would require a custom script of some sort capable of parsing C header files...
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
If I do this:

C:
typedef enum { false, true } bool;
and then define variables:

C:
bool a,b,c;
Are these stored as bytes or packed bits?
 

xox

Joined Sep 8, 2017
936
This is what I think. Is it possible to run a custom script in MPLabX as an automatic precompile step?
I don't think the MPLabX provides a "hook" for that, but I could be wrong. If you're compiling your projects from the command line, it's pretty straightforward. Just write a "compile" script which first invokes the code-generator and then the MPLabX tool chain. If not, you'd probably have to modify the makefile for each and every project.

As far as the code-generator, it wouldn't necessarily be a major undertaking, but it wouldn't be a trivial task either. Maybe someone's already thought of this before and already made a tool for that? Not sure.
 
Last edited:

xox

Joined Sep 8, 2017
936
If I do this:

C:
typedef enum { false, true } bool;
and then define variables:

C:
bool a,b,c;
Are these stored as bytes or packed bits?
Depends on the compiler, but usually an entire int (per variable). In other words, terribly inefficient.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
Depends on the compiler, but usually an entire int (per variable). In other words, terribly inefficient.
Yes, that is what I thought. This just reinforces my belief that C has no place for coding small embedded processors.

Unfortunately, last I checked, MPLabX has lousy support for .asm code.
 

xox

Joined Sep 8, 2017
936
Yes, that is what I thought. This just reinforces my belief that C has no place for coding small embedded processors.
That's pretty harsh. :p

Writing code in C is a helluva lot more flexible than straight ASM, but it does have it's drawbacks.

Are you sure that you aren't over-optimizing? I mean, just how much of a memory constraint are you dealing with here?

Unfortunately, last I checked, MPLabX has lousy support for .asm code.
Section 9.7.6 of this document implies that you can intermix ASM statements with your C code.

Also, pretty sure MPLabX provides a means of invoking custom linker scripts, which would allow you to link a preassembled binary into your projects (and thus resolve any external symbols).
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
Writing code in C is a helluva lot more flexible than straight ASM, but it does have it's drawbacks.
Says you. But we've had this argument here before, many times. It all depends upon one's religion.

My take: the only rational reason to use C (in embedded designs with small MCUs) is for portability. But with embedded C, that all goes out the window as each compiler -- and their respective tool chains and libraries -- do things just different enough to destroy any concept of portability. Assembly assembles the same every time, regardless of the assembler.

The irrational reasons to use C are many -- including little .asm support in the various modern tool chains. I'd happily continue using MPLab IDE 8.92, except they've withdrawn continued support for the newer MCUs.

My biggest beef with MPLabX is that I cannot (could not, last time I checked) cast user defined datatypes in the debugger window. I do a lot of 32 and 56 bit IEEE floating point math in .asm, but I cannot view or edit IEEE floating point values in the MPLabX debugger in an .asm environment. This is stupidly arbitrary.

Are you sure that you aren't over-optimizing? I mean, just how much of a memory constraint are you dealing with here?
Not at all. If I can shave pennies off a design by using a smaller memory footprint, this is a big deal. My example of three libraries is trivial. My projects usually involve dozens.

Section 9.7.6 of this document implies that you can intermix ASM statements with your C code.
Of course. But then I have to work within the constraints of how C deals with the stack, function calls, and local/global variables, and to deal with the need to custom integrate libraries (as the above example) for each project. Like Frank Sinatra, I like to do it my way.
 

nsaspook

Joined Aug 27, 2009
16,259
You could use the C preprocessor to create a custom flag header using a series of ifdefs to conditionally add elements to a flag structure header by looking for library specific defines during the preprocessing stage of the compile.
C:
typedef struct junk_s {
#ifdef PAT_H_INCLUDED
    uint8_t valid : 1;
    uint8_t comm : 1;
    uint8_t spinning : 1;
    uint8_t boot_code : 1;
    uint8_t line_num : 2;
    uint8_t c_line_num : 2;
#endif
#ifdef JUNK_H_INCLUDED
    uint8_t down : 1; // rotation direction
    uint8_t R : 1;
    uint8_t G : 1;
    uint8_t B : 1;
    uint8_t end : 1; // last line in sequence
    uint8_t skip : 1; // don't light led
#endif
    uint8_t keeper :1;
} junk_s;
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
You could use the C preprocessor to create a custom flag header using a series of ifdefs to conditionally add elements to a flag structure header by looking for library specific defines during the preprocessing stage of the compile.
C:
typedef struct junk_s {
#ifdef PAT_H_INCLUDED
    uint8_t valid : 1;
    uint8_t comm : 1;
    uint8_t spinning : 1;
    uint8_t boot_code : 1;
    uint8_t line_num : 2;
    uint8_t c_line_num : 2;
#endif
#ifdef JUNK_H_INCLUDED
    uint8_t down : 1; // rotation direction
    uint8_t R : 1;
    uint8_t G : 1;
    uint8_t B : 1;
    uint8_t end : 1; // last line in sequence
    uint8_t skip : 1; // don't light led
#endif
    uint8_t keeper :1;
} junk_s;
That could work, assuming I kept a master header file that contained all the definitions used by each of my libraries. But, some of my libraries have conditional builds where certain variables are used or not based upon other definitions. I'd have to add additional switches to the master file to track the conditionals and maintain it as the libraries change. Seems like a lot of work.

I'd rather just code my intent into each library .h file and let the compiler do the work.

Maybe I'll just write my own compiler. How hard could it be? Here, hold my beer.
 

nsaspook

Joined Aug 27, 2009
16,259
That could work, assuming I kept a master header file that contained all the definitions used by each of my libraries. But, some of my libraries have conditional builds where certain variables are used or not based upon other definitions. I'd have to add additional switches to the master file to track the conditionals and maintain it as the libraries change. Seems like a lot of work.

I'd rather just code my intent into each library .h file and let the compiler do the work.

Maybe I'll just write my own compiler. How hard could it be? Here, hold my beer.
If you are assigning global variables and namespaces then a master header of those (and maybe a program master config.h header) might be a good thing if organised into a C compatible namespace construct that gets customized for the library modules used in the program. Personally I wouldn't spend a lot of time optimizing out single-bit flags if a worst case include all was under a few tens of bytes unless you really are cutting it close for space.
 

Thread Starter

joeyd999

Joined Jun 6, 2011
6,204
...unless you really are cutting it close for space.
See, this is where I disagree.

I don't think I should need a reason to write code the way I want, except that I want to write it that way.

There are no hardware limitations that force me to spread individual bits over multiple register locations. Why should I accept that I can't do it in a sane way just because a compiler tells me I can't?

It's the old canard: when all you've got is a hammer, everything looks like a nail.

I like the way I write code. I really don't want to change my style just because a compiler -- written by some other human who has his own styles/desires -- tells me I have to.

This is the magic of .asm: There's no third party between the hardware and my code.
 

nsaspook

Joined Aug 27, 2009
16,259
See, this is where I disagree.

I don't think I should need a reason to write code the way I want, except that I want to write it that way.

There are no hardware limitations that force me to spread individual bits over multiple register locations. Why should I accept that I can't do it in a sane way just because a compiler tells me I can't?

It's the old canard: when all you've got is a hammer, everything looks like a nail.

I like the way I write code. I really don't want to change my style just because a compiler -- written by some other human who has his own styles/desires -- tells me I have to.

This is the magic of .asm: There's no third party between the hardware and my code.

No problem with ASM magic, but if you want to play in the C (or any HLL) world there are rules and limitations of its abstract machine that compromise absolute control for portability, structure, code safety, etc .... A massive project like the Linux kernel and drivers uses C and a little ASM for direct control outside of its abstract machine. They manage at least several orders of magnitude higher number of flags in a C structured way.

A proficient C programmer might have written the library functions with a level of scope or encapsulation that might have eliminated the need for many ad hoc global flags. ASM is light hammer, it's not surprising a heavier hammer like C feels clumsy at first.
 
Top