Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Fundamentals of C Programming for Microcontrollers

Tech 2

Data Types and Variables

Data types define the storage format and size of data. Common primitive data types in microcontroller C programming include char, int, long, float, and their signed/unsigned variants.

Type Conversion Priority: During operations, data types are automatically promoted according to this hierarchy: bitcharintlongfloatsignedunsigned. For example, when a char variable operates with an int, the char is first promoted to int before the operation proceeds, yielding an int result.

Constants and Variables: Constants are immutable values, typically defined using the const keyword.

float sensor_reading;
sensor_reading = 3.14159265;

const uint8_t array_size = 10; // Must be initialized at definition

Extended Data Types (C51):

  • Bit Variables: Defined using bit or sbit.
  • Special Function Registers (SFRs): Accessed via sfr or sfr16 type specifiers.
bit flag_bit; // General bit variable
sbit LED_PIN = P1^0; // Bit at a specific address

sfr P0 = 0x80; // Define SFR P0 at address 0x80
sfr16 TMR0 = 0x8C; // Define 16-bit timer register

Storage Classes and Memory Types

Storage classes define the scope and lifetime of variables.

  • auto: Default for local variables. Allocated on stack when function enters.
  • register: Suggests compiler to store variable in a CPU register for speed.
  • static: Preserves value between function calls. File-scope static variables are limited to that file.
  • extern: Declares a variable/function defined in another translation unit.

Memory Types (C51): Specify the physical memory region.

Type Description
data Directly addressable internal RAM (128 bytes)
bdata Bit-addressable internal RAM (16 bytes)
idata Indirectly addressable internal RAM
pdata Page (256 bytes) of external RAM, accessed via Ri
xdata Full 64KB external RAM, accessed via DPTR
code Program memory (ROM)

Example: uint8_t xdata buffer[256];

Typedef: Creates aliases for existing types, commonly used for structures.

typedef struct {
    float max_voltage;
    float min_current;
} POWER_CONFIG;

struct STATUS_FLAGS {
    unsigned error : 1;
    unsigned ready : 1;
};

POWER_CONFIG sys_power; // No 'struct' keyword needed with typedef
struct STATUS_FLAGS dev_status;

Operators

  • Arithmetic: +, -, *, /, % (modulus).
  • Relational: ==, !=, >, <, >=, <=.
  • Logical: && (AND), || (OR), ! (NOT).
  • Bitwise: & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift).
  • Assignment: =, +=, -=, *=, /=, &=, |=, ^=, <<=, >>=.
  • Conditional (Ternary): condition ? expr1 : expr2.

Control Structures

Selection:

  • if, else if, else.
  • switch, case, default. Use break to exit a case.
if (adc_value > THRESHOLD_HIGH) {
    set_alarm();
} else if (adc_value < THRESHOLD_LOW) {
    clear_output();
} else {
    normal_operation();
}

switch (menu_selection) {
    case 1: display_settings(); break;
    case 2: calibrate_sensor(); break;
    default: show_main_menu();
}

Iteration:

  • while: Checks condition before loop body.
  • do...while: Executes body atleast once before checking condition.
  • for: for(init; condition; increment) { ... }.
uint8_t count = 0;
while (count < 10) {
    if (count == 5) break; // Exit loop
    if (count == 2) { count++; continue; } // Skip rest of this iteration
    transmit_data(count);
    count++;
}

for (uint8_t i = 0; i < ARRAY_LEN; i++) {
    process_buffer[i];
}

Functions

Functions encapsulate reusable code. Return type void indicates no return value.

uint16_t calculate_checksum(uint8_t *data, uint16_t len) {
    uint16_t sum = 0;
    for (uint16_t i = 0; i < len; i++) {
        sum += data[i];
    }
    return sum;
}

Interrupt Service Routines (ISRs) in C51: Use the interrupt keyword.

void ext_int0_isr(void) interrupt 0 { // Vector for external interrupt 0
    if (BUTTON_PIN == 0) {
        TOGGLE_LED;
    }
}

Function Prototypes: Declare functions before use, especially when defined in other files.

extern void init_peripheral(void); // Declare function from another module

Arrays

Arrays store multiple elements of the same type contiguously in memory.

uint16_t adc_samples[8] = {0}; // Initialize all to zero
char welcome_msg[] = "Hello"; // Null-terminated string

Pointers

Poinetrs store memory addresses.

  • * : Dereference operator (access value at address).
  • & : Address-of operator.
uint8_t data = 0xAA;
uint8_t *ptr;   // Pointer to uint8_t
ptr = &data;   // ptr now holds address of 'data'
uint8_t value = *ptr; // value = 0xAA

Pointer Types:

  • Pointer to Array: Points to an entire array.
  • Array of Pointers: Array where each element is a pointer.
  • Function Pointer: Points to executable code.
  • Pointer Function: A function that returns a pointer.
// Function pointer example
void (*task_ptr)(void); // Declare function pointer
task_ptr = &scheduled_task; // Assign address of function
(*task_ptr)(); // Call function via pointer

// Pointer function example
uint8_t* get_buffer_base(void) {
    static uint8_t local_buf[64];
    return local_buf; // Returns address of array
}

Composite Data Types

Structures (struct): Group variables of diffferent types.

struct UART_CONFIG {
    uint32_t baud_rate;
    uint8_t data_bits;
    uint8_t parity;
    uint8_t stop_bits;
};

struct UART_CONFIG uart1;
uart1.baud_rate = 115200;

// Structure pointer
struct UART_CONFIG *cfg_ptr = &uart1;
cfg_ptr->data_bits = 8; // Access member via pointer

Unions (union): All members share the same memory location. Size is that of the largest member.

typedef union {
    uint32_t raw;
    struct {
        uint8_t byte0;
        uint8_t byte1;
        uint8_t byte2;
        uint8_t byte3;
    } bytes;
} DATA_PACKET;

DATA_PACKET packet;
packet.raw = 0x12345678;
// packet.bytes.byte0 now contains 0x78 (on little-endian systems)

Enumerations (enum): Define named integer constants.

enum OPERATION_MODE {
    MODE_IDLE,
    MODE_ACTIVE,
    MODE_SLEEP,
    MODE_ERROR = 0xFF
};

enum OPERATION_MODE current_mode = MODE_ACTIVE;

Microcontroller-Specific Concepts

Logic Levels: Digital circuits use HIGH and LOW voltages. Common standards:

  • TTL: ~0V for LOW (<0.8V), ~5V for HIGH (>2.4V).
  • CMOS: Wider noise margins. 5V CMOS HIGH > 3.5V, LOW < 1.5V.

Bitwise Operations for Hardware Control:

PORTB &= ~(1 << 3); // Clear bit 3 of PORTB (set to 0)
PORTB |= (1 << 5);  // Set bit 5 of PORTB (set to 1)
PORTB ^= (1 << 2);  // Toggle bit 2 of PORTB

Absolute Addressing (@ in IAR): Place variable at specific memory address.

uint8_t control_reg @ 0x4000; // Variable at address 0x4000

Bit Fields: Structure members with specified bit widths.

struct {
    unsigned enable : 1;
    unsigned mode   : 2;
    unsigned        : 5; // Unnamed padding bits
} control_register;

volatile Qualifier: Prevents compiler optimization for variables that may change outside program flow (e.g., by hardware or ISRs).

volatile uint8_t *status_reg = (uint8_t*)0x2000;

Preprocessor Directives:

  • Macro Definition (#define): Text substitution before compilation.
#define BUFFER_SIZE 256
#define CLOCK_FREQ (16000000UL)
#define SET_BIT(reg, bit) ((reg) |= (1 << (bit)))
  • Conditional Compilation (#ifdef, #ifndef, #if):
#ifdef DEBUG_MODE
    log_debug("Value: %d", sensor_val);
#endif

Data Alignment: Ensure variables are placed on specific memory boundaries for efficient access.

__attribute__((aligned(4))) uint32_t aligned_data; // GCC/MDK
alignas(4) uint8_t buffer[128]; // C11 standard

Coding Conventions

  1. Portability: Abstract hardware dependencies using macros.
    #define LED_ON()  (PORTB |= (1 << LED_PIN))
    #define LED_OFF() (PORTB &= ~(1 << LED_PIN))
    
  2. Standard Integer Types: Use <stdint.h> types for clarity.
    • uint8_t : Unsigned 8-bit (replaces unsigned char).
    • int16_t : Signed 16-bit.
    • uint32_t : Unsigned 32-bit.
  3. Naming:
    • Macros: ALL_CAPS_WITH_UNDERSCORES.
    • Functions, Variables: camelCase or snake_case.
    • Global Variables: Consider prefix like g_ or module name.
  4. Header Guards: Prevent multiple inclusions.
    // peripheral.h
    #ifndef PERIPHERAL_H
    #define PERIPHERAL_H
    /* Header content */
    #endif
    

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.