Author Topic: C89 : bitfields with enumerated options  (Read 648 times)

0 Members and 1 Guest are viewing this topic.

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 6924
  • Country: us
    • SiliconValleyGarage
C89 : bitfields with enumerated options
« on: July 12, 2018, 02:16:15 am »
Note :this is C , not C++ !! can't switch compilers. this is microchip C compiler. No arguing over compilers please.

consider this:
an 8 bit register holding a 2 bit variable, a single bit variable and 5 dummy bits.
Code: [Select]
typedef struct {
   unsigned int twobits   :2;
   unsigned int singlebit :1;
   unsigned int fivebits   :5;
} some_register;

volatile some_register register_one;

void test(void){
 register_one.singlebit =1;
 register_one.fivebits = 0x03;

}

That all works fine
Now, i want to define the possible combinations for 'twobits' using an enum so it makes my life easier and the code editor can use autocompletion.

Code: [Select]
typedef enum {
    setting_a = 0x00,
    setting_b= 0x01 ,
    setting_c = 0x02 ,
    setting_d = 0x03) bitmodes



so i can write

Code: [Select]
register_one.twobits = setting_c;
...
if register_one.twobits = setting_a then ...
...
case register_one.twobits
   setting_a
   setting_b
   setting_c
 .....

how can i do this ?
The key is that i want autocomplete in the editor to pick up the possible settings. i do not want to have to guess what setting goes with what register.
i am trying to avoid coding errors. there may be other enums for 2 bits that have nothing to do.

real life example : a configuration register for a io pin

Code: [Select]
typedef struct {
   unsigned int pinmode :2; // 3 possible settings : analog = 0, input = 2 , output= 3 . 1 is an illegal setting !
   unsigned int pullup :1; // 2 possible settings enabled = 0 , disabled = 1  <= note reverse logic here !
   unsigned int interruptmode :2; // 4 possible settings : none, rising, falling, both
   unsigned int state :1;  // 2 possible settings high, low
   unsigned int drivestrength :2 // 4 possible settings none,weak,medium,high
} iopin;

volatile iopin pin1;
volatile iopin pin2;

i want to write

Code: [Select]
  pin1.strength = weak;
  pin2.interruptmode = rising;
  if pin1.state = high then pin2.pullup = enabled;

how can i define this ?
again : it is VERY important that the code editor automatically suggest the list of allowable settings. that is the functionality i am searching for.
if i write

Code: [Select]
  pin1.strength = enabled
  ...
   pin2.interuptmode = 0x02
i want the compiler to stop and throw a warning since 'enabled' is not an allowable value for 'strength'.
same for assigning values directly. i am ok if that does not stop the compiler. but using wrong constants should stop compilation
ideally it should be blocked. sing pin mode has only 3 possible states and 0x01 is illegal you should not be allowed to write

Code: [Select]
pin1.pinmode = analog; //0
pin1.pinmode = input;  // 2
pin1.pinmode = output;  //3

pin1.pinmode = 0x01; as that is NOT a valid combination. <-throws error during compilation
« Last Edit: July 12, 2018, 02:21:42 am by free_electron »
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Online dmills

  • Super Contributor
  • ***
  • Posts: 1346
Re: C89 : bitfields with enumerated options
« Reply #1 on: July 12, 2018, 03:03:39 am »
In C an enumeration is not really a type, it is a set of integers, so there is no really good way to avoid
Code: [Select]
pin1.strength = enabledbecause enabled and low could translate to the same integer.

This is why the microchip headers make you do things like
Code: [Select]
TRISAbits.TRISA7 = 0;
Note that C does not define the order of bits in a bitfield (Which are horrifically badly defined), so using this to map an IO register or such is living in sin per MISRA at the least.

I can think of some UGLY, UGLY macro hackery that would sort of get you the semantics you want, but it would not get you the IDE/CTAGS behaviour that you seem to be after.



« Last Edit: July 12, 2018, 03:08:18 am by dmills »
 

Offline ajb

  • Super Contributor
  • ***
  • Posts: 1436
  • Country: us
Re: C89 : bitfields with enumerated options
« Reply #2 on: July 12, 2018, 03:47:15 am »
Error detection like this is why MCU vendor libs require fifty lines of code and a twelve byte config struct to setup an IO pin.  There simply isn't the sophisticated type safety in the C language to support the kind of checking that you're after natively, so you're stuck just checking the values at run time, or via static asserts.  But even then, you won't be able to get an error on "pin1.stength = enabled" if "enabled" represents a valid numeric value for ".strength", because C enums are just integers with fancy labels.

An alternative that at least indicates whether or not a value is valid is to use verbose names for your enums, like
Code: [Select]
typedef enum {
    PIN_STRENGTH_WEAK = 0x00,
    PIN_STRENGTH_MEDIUM = 0x01 ,
    PIN_STRENGTH_STRONG = 0x02 ,
    PIN_STRENGTH_HULK = 0x03
} pin_strength;

Then at least you can tell whether or not a value is valid by inspecting the assignment:
Code: [Select]
pin1.strength = PIN_STRENGTH_WEAK // okay, .strength is assigned to "PIN_STRENGTH_"
pin1.strength = PIN_MODE_ALTFUNCTION // not okay, because .strength is assigned to "PIN_MODE_"
You'll see lots of header files that use this style.  As a bonus, as long as you're consistent, when you go to set pin1.strength, you know that you can type "PIN_STRENGTH_" and autocomplete will show you the valid options.  If you wanted to get fancy, you could probably write a pre-build script that scans the code and checks for violations of this pattern.  The downside is that it's verbose in a way that is anathema to many C developers, but in general the time taken to bang out a few more characters is well worth the time saved in reinterpreting or debugging the code later on.
 

Online JPortici

  • Super Contributor
  • ***
  • Posts: 2292
  • Country: it
Re: C89 : bitfields with enumerated options
« Reply #3 on: July 12, 2018, 03:55:16 am »
I think the most common approach to your main problem (using keywords instead of register bits) is to use a set of defines, or at least it's how i always saw it implemented in any mcu i worked with.

Since i don't like typing lots and lots and lots of defines and macros and multiline statements my usual approach is to write the register value directly and comment the settings.
If i can spare a few cycles during initialization i can just set each bit(s) individually and comment with what am i setting.
There is also the case of a peripheral which requires a specific sequence of operations, like first you have to set one bit, then you can set the registers and so on.

For the rest, Since you mention C89 i assume you are using XC8.
If I remember correctly in XC8 the enum type is implemented with a char (8 bit) type, unless the number of states or explicit assignments of values forces the compiler to use larger data types.

I created a small project using XC8 v1.35 and PIC18F26K22 (Simulator)
Please correct me if i'm wrong

Quote
i want the compiler to stop and throw a warning since 'enabled' is not an allowable value for 'strength'.
i don't think you can do that in C. In C enums are just fancy integers, and all enums are the same. there is no type checking. XC8 makes no difference, just tried it.

Quote
same for assigning values directly. i am ok if that does not stop the compiler. but using wrong constants should stop compilation
same because of the reason above

Quote
ideally it should be blocked. sing pin mode has only 3 possible states and 0x01 is illegal you should not be allowed to write
you can't do that either. when you write to bitfields the value is masked, so if your bitfield is two bits wide and you write 5 the bitfield will assume value "1".
You can however implement a function like this
Code: [Select]
char SetNewSettings(enum_t newSetting, whatever_t* register) {
  swhitch (newSetting) {
    case valid1:
    case valid2:
    ...
    case validLast:
      register->field = newSetting;
      return 0;
      break;
    default:
      return -1;  //illegal operation
      break;
  }
}

which is... er... a tedious task :D go fetch the intern...

I also tried declaring a structure with a bitfield defined as a union. i'm sure you tried it as well and you also got this error message :(
Code: [Select]
main.c:97: error: (264) bad bitfield type

my small project..
Code: [Select]
// PIC18F26K22 Configuration Bit Settings

// 'C' source line config statements

// CONFIG1H
#pragma config FOSC = ECHPIO6   // Oscillator Selection bits (EC oscillator (high power, >16 MHz))
#pragma config PLLCFG = OFF     // 4X PLL Enable (Oscillator used directly)
#pragma config PRICLKEN = ON    // Primary clock enable bit (Primary clock enabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRTEN = OFF     // Power-up Timer Enable bit (Power up timer disabled)
#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 190       // Brown Out Reset Voltage bits (VBOR set to 1.90 V nominal)

// CONFIG2H
#pragma config WDTEN = ON       // Watchdog Timer Enable bits (WDT is always enabled. SWDTEN bit has no effect)
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = PORTC1  // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = ON      // PORTB A/D Enable bit (PORTB<5:0> pins are configured as analog input channels on Reset)
#pragma config CCP3MX = PORTB5  // P3A/CCP3 Mux bit (P3A/CCP3 input/output is multiplexed with RB5)
#pragma config HFOFST = ON      // HFINTOSC Fast Start-up (HFINTOSC output and ready status are not delayed by the oscillator stable status)
#pragma config T3CMX = PORTC0   // Timer3 Clock input mux bit (T3CKI is on RC0)
#pragma config P2BMX = PORTB5   // ECCP2 B output mux bit (P2B is on RB5)
#pragma config MCLRE = EXTMCLR  // MCLR Pin Enable bit (MCLR pin enabled, RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = ON         // Single-Supply ICSP Enable bit (Single-Supply ICSP enabled if MCLRE is also 1)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection Block 0 (Block 0 (000800-003FFFh) not code-protected)
#pragma config CP1 = OFF        // Code Protection Block 1 (Block 1 (004000-007FFFh) not code-protected)
#pragma config CP2 = OFF        // Code Protection Block 2 (Block 2 (008000-00BFFFh) not code-protected)
#pragma config CP3 = OFF        // Code Protection Block 3 (Block 3 (00C000-00FFFFh) not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection Block 0 (Block 0 (000800-003FFFh) not write-protected)
#pragma config WRT1 = OFF       // Write Protection Block 1 (Block 1 (004000-007FFFh) not write-protected)
#pragma config WRT2 = OFF       // Write Protection Block 2 (Block 2 (008000-00BFFFh) not write-protected)
#pragma config WRT3 = OFF       // Write Protection Block 3 (Block 3 (00C000-00FFFFh) not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection Block 0 (Block 0 (000800-003FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection Block 1 (Block 1 (004000-007FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection Block 2 (Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection Block 3 (Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0007FFh) not protected from table reads executed in other blocks)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>

typedef enum {
  fE_State0,
  fE_State1,
  fE_State2,
  fE_State3
} firstEnum_t;


typedef enum {
  sE_State0,
  sE_State1,
  sE_State2,
  sE_State3,
  sE_State4,
  sE_State5,
  sE_State6,
  sE_State7,
} secondEnum_t;

typedef struct {
  firstEnum_t a;
  secondEnum_t b;
} structA;

typedef struct {
  firstEnum_t a     :2;
  unsigned padding  :3;
  secondEnum_t b    :3;
} structB;

void main() {
  structA structure;
 
  structure.a = sE_State7;
  structure.b = 11;
 
  while(1);
}


if anyone has a solution however, i'd like to hear it as well :)
** OT: i don't know how i failed to get notifications in one month, but XC8 V2.0 is apparently C99 now.
« Last Edit: July 12, 2018, 03:58:04 am by JPortici »
 

Online dmills

  • Super Contributor
  • ***
  • Posts: 1346
Re: C89 : bitfields with enumerated options
« Reply #4 on: July 12, 2018, 05:22:57 am »
Got me wondering about some sort of funky combination of a macro, some #defines and the ## operator now, but bleh.

Regards, Dan.
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 6924
  • Country: us
    • SiliconValleyGarage
Re: C89 : bitfields with enumerated options
« Reply #5 on: July 12, 2018, 06:17:21 am »
A couple of clarifications.:

These are not real CPU registers. They are bytes stored in RAM that happen to contain bitfields. These bytes are overlaid in a union with an array so the array can easily be accessed using an I2C command parser. They are, in essence, the command and control registers of a virtual I2C device.

I am making an I2 cslave. For example i want to emulate a PCA9555 16 bit io register

that thing has 8 registers
0x00 : 8 bits : input port 0
0x01 : 8 bits : input port 1
0x02 : 8 bits : output port 0
0x03 : 8 bits : output port 1
0x04 : 8 bits : polarity port 0
0x05 : 8 bits : polarity port 1
0x06 : 8 bits : direction 0
0x07 : 8 bits : direction 1

i want to add an 8th register
0x08 : interrupt  [global_enable,nibble3,nibble2,nibble1,nibble0,mode1,mode0}

the nibble bits enable sensing on nibbles in the io registers. nibble3 enables top nibble of input 1. nibble 2 enables lower nibble of input1

so i want to do things like:

interrupt_register.nibbles = nibble_3 | nibble_0;
interrupt_register.mode = rising_only;

since interrupt_register is part of a union it is also available as registers[8]

My transport routines become very simply. On receive i dump data in registers[] and increment a pointer as long as no STOP is received.

In the actual guts of the emulation code i can now use symbol names to access the stream= and i don't need to muck with bit-twiddling the registers[] array. like
Code: [Select]
if (registers[8] & 0x80) then // global interrupt is set
// or
registers[8] |= 0x80
i can simply say
Code: [Select]
if (interrupts.global_enable) then ....

I want to make this bulletproof. not for someone else, but for myself. I can never remember all the bits and fields and their correct possible settings. i hate to dig through datasheets and look at header files. I want the code editor to show me the possible settings automatically. just like in visual studio.

I spend sometimes hours finding a mistake in code that could simply be avoided if the damn language , ide or compiler would just be a bit smarter and tell me : this here , that's not the correct thing that fits in there ...

And this testing should happen at compile time. not at runtime. none of the stuff is computation based. it is all bit masking and testing. something most CPU's can do in 1 or 2 instructions.

I also don't want to write the typical crappy library code that needs to execute 20 instructions to set a bit.
In short i want to find a way to define a memory map of bits and bytes where i can assign symbolic names that automatically resolve to possible settings.

here is what i did now, but this does not foolproof the possible states.
Code: [Select]
#define GP_PINMODE_OUT_PWM      0x00
#define GP_PINMODE_OUT_PUSHPULL 0x01
#define GP_PINMODE_IN_ANALOG    0x02
#define GP_PINMODE_IN_DIGITAL   0x03

#define GP_INTMODE_DISABLED     0x00
#define GP_INTMODE_POSEDGE      0x01
#define GP_INTMODE_NEGEDGE      0x02
#define GP_INTMODE_BOTHEDGE     0x03

typedef struct {
      unsigned int   GP_PINMODE :2;  // Pinmode : see GPPINMODE
      unsigned int   GP_PUL     :1;  // Pull-up enable = 1=enables, 0= disabled
      unsigned int   GP_INTMODE :2;  // Interrupt : see GPINTMODE
      unsigned int   DUMMY      :3;  // Dummy bits, not used
     } GPxCONF;
     
typedef union _memory_map_ {
struct {
         volatile char POINTER;
         volatile GPxCONF GP1CONF;
         volatile GPxCONF GP2CONF;
         volatile GPxCONF GP3CONF;
       } REGISTER;
         volatile char ARRAY[3];
} m_map;

volatile m_map REGISTERS;

void main(void){
   REGISTERS.ARRAY[2] = 0x55; // this touches GP2CONF
   REGISTERS.REGISTER.GP2CONF.GP_INTMODE = GP_INTMODE_POSEDGE;
}
the autocomplete correctly suggests me the field and subfield names. so happy until there.
i just want it to suggest me GP_INTMODE_POSEDGE, and the other 3.

who knows how ?
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Online JPortici

  • Super Contributor
  • ***
  • Posts: 2292
  • Country: it
Re: C89 : bitfields with enumerated options
« Reply #6 on: July 12, 2018, 06:59:01 am »
I wonder if one could write a netbeans plugin to do this..
like parse the line up to the cursor, if it's right after a "case" or a "=" check variable name,type and suggest appropriate defines or enums?
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 294
Re: C89 : bitfields with enumerated options
« Reply #7 on: July 12, 2018, 08:06:35 am »
Quote
the autocomplete correctly suggests me the field and subfield names. so happy until there.
I think it will also show comments for the field, so when you type the period you see the field options and when you highlight the option you will see the comment for the option. Not perfect, but if you comment each struct field, you will see the available options. (I'm assuming MPLABX)
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 6924
  • Country: us
    • SiliconValleyGarage
Re: C89 : bitfields with enumerated options
« Reply #8 on: July 12, 2018, 10:52:41 am »
yup, MPLABx

i am one step further now.

It now correctly suggests me the options.
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 6924
  • Country: us
    • SiliconValleyGarage
Re: C89 : bitfields with enumerated options
« Reply #9 on: July 12, 2018, 11:02:32 am »
except it doesnt compile. AAARGH. I HATE C(rap) language !
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline obiwanjacobi

  • Frequent Contributor
  • **
  • Posts: 828
  • Country: nl
  • What's this yippee-yayoh pin you talk about!?
Re: C89 : bitfields with enumerated options
« Reply #10 on: July 12, 2018, 05:13:46 pm »
I haven't done C in ages.
I think this might be C++ but perhaps it will inspire...
https://github.com/obiwanjacobi/atl/blob/master/Source/Code/ArduinoTemplateLibrary/EnumScope.h

[2c]

Offline ajb

  • Super Contributor
  • ***
  • Posts: 1436
  • Country: us
Re: C89 : bitfields with enumerated options
« Reply #11 on: July 13, 2018, 04:15:39 am »
You could get the same syntax and autocompletion by creating a const struct that holds the allowable values (but you still won't get compile-time checking, because it's still C):

Code: [Select]
typedef struct {
struct {
uint8_t OUT_PWM;
uint8_t OUT_PUSHPULL;
uint8_t IN_ANALOG;
uint8_t IN_DIGITAL;
} GPxPINMODES;
struct {
uint8_t DISABLED;
uint8_t POSEDGE;
} GP_INTMODES;
} GPxCONF_t;

typedef struct {
uint8_t PINMODE : 2;
uint8_t PUL : 1;
uint8_t INTMODE : 2;
uint8_t DUMMY : 3;
} GPxCONF_reg;


static const GPxCONF_t GPxCONF = {
// these could be bitfields as well if you want to save a few bytes
.GPxPINMODES = {
.OUT_PWM = 0,
.OUT_PUSHPULL = 1,
.IN_ANALOG = 2,
.IN_DIGITAL = 3,
},
.GP_INTMODES = {
.DISABLED = 0,
.POSEDGE = 1,
},
};

void init(void){
GPxCONF_reg mem_map_reg;

mem_map_reg.INTMODE = GPxCONF.GP_INTMODES.DISABLED;
}

You won't be able to use a tagged initializer in vanilla C89, and the value struct should be decorated as necessary to keep it out of RAM if that's important to you.  I'm not sure how well it will optimize in MPLABX, and in the end it's not any different from using defines or regular enums, just a different presentation style.

I think this might be C++ but perhaps it will inspire...
https://github.com/obiwanjacobi/atl/blob/master/Source/Code/ArduinoTemplateLibrary/EnumScop
Yes, that's C++.
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 6924
  • Country: us
    • SiliconValleyGarage
Re: C89 : bitfields with enumerated options
« Reply #12 on: July 13, 2018, 05:11:29 am »
i have tried the same using an enumerated list and that failed. i will try your approach.

i need to check what that compiles to though ... i don't want to waste RAM or ROM ( my CPU has 1.5K of ROM and 64 bytes of ram .... )
if i perform a bit test i expect the compiler to use BSN <location> <bit> (bit test and skip next if set)

so the check becomes :

: subroutine : handle pullup set

BSN <GP1CONF> <GP_PUL> , check gp_pul bit in gp1conf.
RET // return if not set
blabla . handling code
RET  // done

Code optimisation is very important .
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 6924
  • Country: us
    • SiliconValleyGarage
Re: C89 : bitfields with enumerated options
« Reply #13 on: July 14, 2018, 02:39:07 pm »
Right. I figured some things out and got it to work.

The guiding principles behind this experiment
- no RAM usage
- smallest code
- predicatable execution. no wiggle room for the optimizers.
- no libraries that call call call.
- symbolic names
- everything hardcoded. no mathematical acrobatics at runtime. compile time is fine
- no bleeping pointers or pointer arithmetic.
- no room for user to things like writing to nonvolatile or loading data of wrong type in system.
- things like switch case , == and != need to work correctly


Think about a register that consists of 8 flipflops.
in an FPGA i can write REGISTER[7:6] = 2'b01 and this will write those two flipflops in one clocktick.
The equivalent code in a CPU
becomes
MOV B Register          // get contents of register
AND B 0b0011_1111  // msk off 2 top bits
MOV A 0b0000_0001  // load 01 in accumulator
SHL A,6 // shift accumaltor left 6 positions
OR   A,B  or b and accumulator
MOV Register A // send it back into 'REGISTER'

this is sheer insanity. just to twiddle some bits ... oodles of code, moves , or register usages.

The functions i need are
- test a bit for true
- test a bit for false
- set a bit
- clear a bit
- toggle a bit
- check a subfield for a value from a predefined list , kind of like an enumerated list , allowing usage of standard flow control like 'switch' , if == /  != operators
- set a subfield to a value of a predefined list.

I want to approach this as close as possible to what is happening in an FPGA
- compiler should be able to resolve most arithmetic at compile time as everything is constant based.
- shortest possible code.
- no ram usage
- inline compilation so no calls, push pop and other overhead
- interrupt safe. no risk of context switch loss. no usage of stack to preserve intermediates.

Why all of this ? well , to create 'virtual devices' in a microcontroller.

Think of a peripheral like an I2C or SPI chip.
Those things are essentially  number of registers with data in them. some registers pack multiple fields of varying size.
To code these kind of things it is nice to have these registers as a continuous block of memory so you can access it using a read and write pointer from the transport handler routines.
The incoming commands tell you what byte to write and what the byte is. nothing more , nothing less. short and good.

To code the logic that forms the devices it would be nice to have symbolic access to the individual chunks without every time having to write all the bit-twiddling code.

consider  a virtual device with a memory map like this :
0x00 : CONFIG   [GLOBAL_INT],-,-,-,-,[ADC_INT],[GP2INT][GP1INT]
0x01 : TEMPERATURE [d7..d0]
0x02 : GPCONF1 -,-,-,INTMODE1,INTMODE0,PULLUP_ENABLE,PINMODE0,PINMODE1
0x03 : GPCONF2 -,-,-,INTMODE1,INTMODE0,PULLUP_ENABLE,PINMODE0,PINMODE1
0x04 : GPDATA1 8 bits
0x05 : GPDATA2 8 bits

the config bits are flags. If a flag is SET then that device will generate an interrupt_on_change
For example :
if GP2INT is set , and the pinmode is input , and intpmode it is set to rising edge then an interuupt will be generated if that pin goes from low to high.

possible modes for INMODE
- none
- rising
- falling
- both

pinmode :
digital input -> state is stored in GPDATAx as 0x00 or 0xFF
analog input -> adc result is stored in GPDATAx as a number between 0x00 and 0xff
digital output -> pin is follows bit0 of GPDATAx
PWM output -> pin has duty cycle set by GPDATAx

all in all something that is easily cobbled up in an FPGA , but in a microcontroller becomes spaghetticode unless you have symbolic access to the 'flipflops'


SO, here we go:

First, lets make our registerbank so the transport routines can read and write based on a pointer. The simplest construction : an array of unsigned chars.
Code: [Select]
unsigned char REGISTERSET[6]

So far so good. We have allocated all the ram we ever need.
Let's create some symbolic names so we can easily access these things from the logic side.

Code: [Select]
unsigned char REGISTERSET[5];
#define CONFIG      REGISTERSET[0]
#define TEMPERATURE REGISTERSET[1]
#define GPCONF1     REGISTERSET[2]
#define GPCONF2     REGISTERSET[3]
#define GPDATA1     REGISTERSET[4]
#define GPDATA2     REGISTERSET[5]

let's set up the interrupt flag mapping.
Code: [Select]
#define INTENABLE_MASK 0b10000000   // bit 7 of CONFIG register
#define GP1INT_MASK    0b00000001   // bit 0 of CONFIG register
#define GP2INT_MASK    0b00000010   // bit 1 of CONFIG register
#define ADCINT_MASK    0b00000100   // bit 2 of CONFIG register

and the mapping for the INTmode and PINMODE of the GPCONFx registers.

Code: [Select]
#define PINMODE_MASK  0b000000011
#define PULLUP_MASK   0b000000100
#define INTMODE_MASK  0b000011000

Next let's define the possible states INTMODE and PINMODE and pullup

Code: [Select]
#define PINMODE_IN_ANALOG    0b00000000
#define PINMODE_IN_DIGITAL   0b00000001   
#define PINMODE_OUT_PUSHPULL 0b00000010
#define PINMODE_OUT_PWM      0b00000011
#define PINMODE_PULLUP       0b00000100
#define INTMODE_NONE         0b00000000
#define INTMODE_RISING       0b00001000
#define INTMODE_FALLING      0b00010000
#define INTMODE_BOTH         0b00011000

Note that the above are NOT masks, they are the settings of possible fields, aligned to their mask. This is done intentionally as i don't want any risk of the compiler injecting unnecessary >> and << operations. the fields are pre-aligned.

now we need some code to 'extract' the individual fields.
Code: [Select]

// reading
#define GPCONF1_PINMODE (GPCONF1 & PINMODE_MASK)
#define GPCONF1_INTMODE (GPCONF1 & INTMODE_MASK)
#define GPCONF2_PINMODE (GPCONF2 & PINMODE_MASK)
#define GPCONF2_INTMODE (GPCONF2 & INTMODE_MASK)
// writing
#define GPCONF1_INTMODE_SET (GPCONF1 & ~INTMODE_MASK)|=
#define GPCONF2_INTMODE_SET (GPCONF2 & ~INTMODE_MASK)|=
#define GPCONF1_PINMODE_SET (GPCONF1 & ~INTMODE_MASK)|=
#define GPCONF2_PINMODE_SET (GPCONF2 & ~INTMODE_MASK)|=

and some for the individual bits
Code: [Select]
#define GP1INT_SET     FLAGS |= GP1INT_MASK
#define GP1INT_ISSET   (FLAGS & GP1INT_MASK)!=0
#define GP1INT_CLEAR FLAGS &= ~GP1INT_MASK
#define GP1INT_ISCLEAR   (FLAGS & GP1INT_MASK)==0
#define GP1INT_TOGGLE  FLAGS ^= GP1INT_MASK

#define GP2INT_SET     FLAGS |= GP1INT_MASK
#define GP2INT_ISSET   (FLAGS & GP1INT_MASK)!=0
#define GP2INT_CLEAR FLAGS &= ~GP1INT_MASK
#define GP2INT_ISCLEAR   (FLAGS & GP1INT_MASK)==0
#define GP2INT_TOGGLE  FLAGS ^= GP2INT_MASK

#define ADCINT_SET     FLAGS |= GP1INT_MASK
#define ADCINT_ISSET   (FLAGS & GP1INT_MASK)!=0
#define ADCINT_CLEAR FLAGS &= ~GP1INT_MASK
#define ADCINT_ISCLEAR   (FLAGS & GP1INT_MASK)==0
#define ADCINT_TOGGLE  FLAGS ^= GP2INT_MASK

#define INTENABLE_SET     FLAGS |= GP1INT_MASK
#define INTENABLE_ISSET   (FLAGS & GP1INT_MASK)!=0
#define INTENABLE_CLEAR FLAGS &= ~GP1INT_MASK
#define INTENABLE_ISCLEAR   (FLAGS & GP1INT_MASK)==0
#define INTENABLE_TOGGLE  FLAGS ^= GP2INT_MASK


This seems like a lot of work to build, but it is easily creatable using a script that reads a definition table.
using a simply notation like
Code: [Select]
// MEMMAPPER
// REG 0, CONFIG [7] INTENABLE, [6:3] - , [2] ADC_INT , [1] GP2INT , [0] GP1INT
// REG 2, GPCONF1[7:5] - , [4:3] INTMODE , [2], PULLUP , [1:0] PINMODE
// REG 3, GPCONF2[7:5] - , [4:3] INTMODE , [2], PULLUP , [1:0] PINMODE
// MODE INTMODE {NONE:00,  RISING:01,FALLING:10,BOTH:11}
// MODE PINMODE {IN_ANALOG:00,  IN_DIGITAL:01,OUT_PUSHPULL:10, OUT_PWM:11}
// TARGET
#include "registermap.inc"
i can have a simple script that scans my source files for the magic keyword 'MEMMAPPER' then parses the comment lines until it hits the keyword TARGET , and spits the data into the filename given in the include.

all the #define stuff is automatically generated from a simple notation syntax.

But why you ask.  Well . becasue now i can write the logic for this virtual devices using symbolic names and not have to bother if i got the mapping of the fileds right, the correct shift offsets , i dont need ot dig in header files to see what is what as autocomplete will suggest me possible settings.

An example :
Code: [Select]
    GP1INT_SET;       // set the GP1INT flag true
    GP1INT_CLEAR;   // clear the GP1INT flag
    if (GP1INT_ISCLEAR) {   // if GP1int is clear
        go_do_this;
    }
    if (GP1INT_ISSET) {  // if set
        go_do_that;
    }
  switch (GPCONF1_PINMODE)
    {
        case PINMODE_IN_ANALOG    : GPDATA1= ADCread;  break;
        case PINMODE_IN_DIGITAL   : GPDATA1 = porta.1; break;
        case PINMODE_OUT_PUSHPULL : PORTa.mode = pushpull; PORTa.1 = GPDATA1; break;
        case PINMODE_OUT_PWM      : PORTa.mode = MODE_pmwy; PORTaPWMvalue = GPDATA1; break;
    };

....

and so on


This the process of coding the control logic very simple. If in the future the memory map changes ( i allocate the individual fields and registers differently ) my logic still keeps working correctly !
« Last Edit: July 14, 2018, 05:16:36 pm by free_electron »
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf