Alexis Draussin


µC firmware: interacting with bare-systems

2023-07-18


Address an hardware register

Raw

int main(void)
{
	unsigned char addr = 0xff; // reg addr
	volatile unsigned char *reg = (volatile unisgned char *)addr; // reg ptr
	unsigned char val; // reg value
	// change the integer type if needed
	
read:
	val = *(volatile unsigned char *)reg;
write:
	*(volatile unsigned char *)reg = val;
	
		return 0;
}

Macro

#define REG                     0xff
#define READ_H(reg)		(*(volatile unsigned char *)(reg))
#define WRITE_H(reg, val)	(*(volatile unsigned char *)(reg)) = (val)

Bit manipulation

Raw

int main(void)
{
	int val; // signed or unsigned
	unsigned char position; // max at bit-length of val
	unsigned char bit; // 0 or 1
	unsigned char x; // unknown value, 0 or 1
	
setBit:
	val |= (1 << position);
unsetBit:
	val &= ~(1 << position);
toggleBit:
	val ^= (1 << position);
verifyBit:
	bit = (val >> position) & 0x1;

bit_to_x: // works with both x = 1 or 0
	val = (val & ~(1 << position)) | (x << position); 
	
	return 0;
}

Macro

#define SET_BIT(val, bitIndex)		(val) |= (1 << (bitIndex))
#define CLEAR_BIT(val, bitIndex)	(val) &= ~(1 << (bitIndex))
#define TOGGLE_BIT(val, bitIndex)	(val) ^= (1 << (bitIndex))
#define BIT_VALUE(val, bitIndex)	(((val) >> (bitIndex)) & 1)
#define BIT_TO_X(val, x, bitIndex)	(val) = ((val) & ~(1 << (bitIndex)) | ((x) << (bitIndex))

Declaration and use of hardware register

Following the last two sections:

#define REGD (*(volatile unsigned char *)(0xff))
#define D0 0
#define D1 1
#define D2 2 // etc

int main(void)
{
	REGD = 0x00; // whole register
	REGD |= (1 << D1); // set bit 1 of REGD
	REGD &= ~(1 << D0); // unset bit 2 of REGD
	return 0;
}

Transpose a dataBus value on spread registers' bits

void Xpos(unsigned char x, unsigned char xIndex, unsigned char *dest, unsigned char destIndex, unsigned char bitLength)
{
	unsigned char mask = 0x1;
	unsigned char i = 1;
	while (i++ < bitLength)
		mask = (mask << 1) | 0x1;
	*dest &= ~(mask << destIndex); // set the 0s
	*dest |= (((x >> xIndex) & mask) << destIndex); // set the 1s
}


/*** Example ***/
// Registers
unsigned char E = 0xa5;
unsigned char D = 0xa5;
unsigned char C = 0xa5;

void drawBar(unsigned char x) // outputs x value on spread bits of E, D and C
{
	Xpos(x, 7, &E, 6, 1); // x7 is on E6
	Xpos(x, 6, &D, 7, 1); // x6 is on D7
	Xpos(x, 5, &C, 6, 1); // x5 is on C6
	Xpos(x, 4, &D, 4, 1); // x4 is on D4
	Xpos(x, 3, &D, 0, 1); // x3 is on D0
	Xpos(x, 2, &D, 1, 1); // x2 is on D1
	Xpos(x, 0, &D, 2, 2); // x1-0 is on D3-2
}

int main(void)
{
	unsigned char x = 0xff;
	drawBar(x);
	
	return 0;
}

Some macros

#define MIN(A, B) ((A) <= (B) ? (A) : (B))
#define MAX(A, B) ((A) >= (B) ? (A) : (B))

#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif

#ifndef NULL
#define NULL ((void *)0)
#endif

Bit-fields

Careful: the bit order is compiler-dependent, this implementation is not portable.

#include <stdio.h>

union byte_t {
	unsigned char val;
	struct {
		unsigned char A : 2;
		unsigned char B : 1;
		unsigned char C : 3;
		};
	struct {
		unsigned char A0 : 1;
		unsigned char A1 : 1;
		unsigned char : 1;
		unsigned char C0 : 1;
		unsigned char C1 : 1;
		unsigned char C2 : 1;
	};
};

int main(void)
{
	union byte_t reg = {0x2a}; // 0b101010
	
	printf("%hhu\t0x%x\t0x%x\n", reg.C1, reg.A, reg.val);
	// gives: 1, 0x2, 0x2a
	
	return 0;
}

Tricky definitions

Static

Static has three distinct uses in C:

  1. A variable declared static within the body of a function maintains its value between function invocations.
  2. A variable declared static within a module, (but outside the body of a function) is accessible by all functions within that module. It is not accessible by functions within any other module. That is, it is a localized global.
  3. Functions declared static within a module may only be called by other functions within that module. That is, the scope of the function is localized to the module within which it is declared.

Volatile

Examples of volatile variables are:

  1. Hardware registers in peripherals (e.g., status registers).
  2. Non-stack variables referenced within an interrupt service routine.
  3. Variables shared by multiple tasks in a multi-threaded application.