Embedded Development in C: Function Pointers and the typedef Keyword

1. The typedef Keyword

In C language, the typedef keyword is used to define a new name (alias) for an existing data type. It does not create a new data type but provides a more readable and concise identifier for existing types, thereby improving code readability and maintainability.

The basic syntax of typedef is very simple, formatted as follows:

typedef original_data_type new_type_name;

For example, you can create an alias for the basic data type int as Integer:

typedef int Integer;

After defining the alias for int, you can use Integer to declare variables of type int in subsequent code:

Integer a = 10;

This is completely equivalent to int a = 10;, but using Integer makes it clearer for readers to understand the purpose of this variable.

2. typedef and Function Pointers

You can use typedef to define function pointer types:

// Define function pointer type
typedef int (*CompareFunc)(const void*, const void*);
// Example usage
int compare_ints(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}
CompareFunc cmp = compare_ints;  // Directly use the type name

The above code uses typedef to create a function pointer type named CompareFunc, which points to a function that takes two const void* parameters and returns an int value. Then, the function compare_ints() is defined, and the function name is assigned to the function pointer.

Using typedef to name function pointer types can significantly enhance code readability and flexibility. Below are specific examples.

Example 1: Simplifying Complex Function Pointer Declarations

Without typedef:

int (*func1)(int, float);          // Declare a function pointer variable
int (*func_array[5])(int, float);  // Declare an array of 5 function pointers

Using typedef:

typedef int (*MathFunc)(int, float);  // Define type alias
MathFunc func1;              // Directly declare variable
MathFunc func_array[5];      // Declare array

Benefits:

Avoids repetitive writing of complex function pointer syntax. The code is more concise, and the type meaning is clearer (MathFunc is easier to understand than int (*)(int, float)).

Example 2: Enhancing Maintainability of Callback Function Code

Without typedef:

void register_callback(int (*callback)(const char*, int)) {    // Use callback function}

When calling:

int my_callback(const char* s, int x) { /* ... */ }
register_callback(my_callback);

Using typedef:

typedef int (*CallbackType)(const char*, int);  // Define type alias
void register_callback(CallbackType callback) {  // Clear parameter type    // Use callback function}

When calling:

CallbackType my_callback = /* ... */;
register_callback(my_callback);

Benefits:

The function parameter type is clear, avoiding ambiguity. When modifying the callback function type, you only need to change the typedef definition without modifying each function declaration.

Example 3: As Struct Members

Without typedef:

struct Calculator {
    int (*add)(int, int);      // Addition function pointer
    int (*subtract)(int, int); // Subtraction function pointer
};

Using typedef:

typedef int (*ArithmeticOp)(int, int);  // Define unified operation type
struct Calculator {
    ArithmeticOp add;      // Member type directly uses alias
    ArithmeticOp subtract;
};

Benefits:

The struct member types are clear, explicitly expressing design intent. When adding operations like multiplication or division later, you only need to extend the use of the ArithmeticOp type.

Example 4: Function Returning Function Pointer

Without typedef:

int (*get_operation(char op))(int, int) {  // Return type is hard to read
    if (op == '+') return &add;
    else return &subtract;
}

Using typedef:

typedef int (*Operation)(int, int);  // Define type alias
Operation get_operation(char op) {  // Return type is clear
    if (op == '+') return &add;
    else return &subtract;
}

Benefits:

The function return value type is clear, avoiding nested pointer syntax.

3. Task Scheduler Example

Below is an implementation of a task scheduler, primarily implementing task registration, execution, and management functions.

1. Define Task Status Enumeration Type and Task Structure

// Task status enumeration
typedef enum {
    TASK_READY,
    TASK_RUNNING,
    TASK_BLOCKED,
    TASK_FINISHED
} TaskStatus;
// Task structure
typedef struct Task {
    TaskStatus status;
    void (*taskFunction)(); // Task function pointer
    struct Task *next;
} Task;

The above code uses typedef to define the TaskStatus enumeration type to represent different task states, while defining the Task structure, which includes the task’s status, a pointer to the task function taskFunction, and a pointer to the next task next. Here, taskFunction is a function pointer that points to the specific task execution function.

2. Implement the Core Functions of the Task Scheduler, Including Task Registration and Execution

// Task linked list head pointer
Task *taskList = NULL;
// Register task
void registerTask(void (*task)()) {
    Task *newTask = (Task *)malloc(sizeof(Task));
    newTask->status = TASK_READY;
    newTask->taskFunction = task;
    newTask->next = taskList;
    taskList = newTask;
}
// Execute tasks
void executeTasks() {
    Task *currentTask = taskList;
    while (currentTask != NULL) {
        if (currentTask->status == TASK_READY) {
            currentTask->status = TASK_RUNNING;
            currentTask->taskFunction(); // Call task function via function pointer
            currentTask->status = TASK_FINISHED;
        }
        currentTask = currentTask->next;
    }
}

In the registerTask function, memory is allocated using malloc to create a new task node, the passed task function pointer is assigned to newTask->taskFunction, and the new task node is inserted at the head of the task linked list. In the executeTasks function, the task linked list is traversed, and when a task in the TASK_READY state is found, its state is set to TASK_RUNNING, then the task function is called via the function pointer currentTask->taskFunction, and after the task execution is complete, the state is set to TASK_FINISHED.

3. Define Specific Task Functions and Test

// Specific task function 1
void task1() {
    printf("Task 1 is executing\n");
}
// Specific task function 2
void task2() {
    printf("Task 2 is executing\n");
}
int main() {
    // Register tasks
    registerTask(task1);
    registerTask(task2);
    // Execute tasks
    executeTasks();
    return 0;
}

In the main function, the registerTask function is called to register two tasks, task1 and task2, and then the executeTasks function is called to execute the task scheduler, sequentially executing the registered tasks.

Through this simple operating system task scheduler example, we can see the collaborative working mechanism of typedef and function pointers. typedef defines concise aliases for task status enumeration and task structure, improving code readability; while function pointers allow the task scheduler to flexibly call different task functions, achieving dynamic execution and management of tasks.

4. Interrupt Callback Example

Assuming we are in an embedded system based on the ARM Cortex-M3 core, using GPIO (General Purpose Input/Output) pins to detect external button press events, and handling them through interrupt callback functions. Below are the specific implementation steps and code examples.

1. Define Interrupt Callback Function Type

Use typedef to define a function pointer type for the interrupt callback function. In this case, the interrupt callback function has no return value and no parameters:

typedef void (*InterruptCallback)(void);

The above code defines a function pointer type named InterruptCallback, which points to a function that returns void and has a parameter list of void.

2. Initialize GPIO and Interrupt Configuration

Initialize the GPIO and interrupt configuration. Here, we assume the STM32 series microcontroller is used, and the relevant code is as follows:

#include "stm32f10x.h"
void GPIO_Interrupt_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    // Enable GPIO clock
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    // Configure GPIO pin as input mode
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    // Configure EXTI interrupt line
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // Falling edge triggers interrupt
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    // Configure NVIC interrupt priority
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

The above code implements the initialization configuration of the GPIO pin, setting the PA0 pin to floating input mode, and configuring the external interrupt line EXTI0 to trigger an interrupt on the falling edge, while also configuring the interrupt priority of the NVIC (Nested Vectored Interrupt Controller).

3. Register Interrupt Callback Function

Define a function to register the interrupt callback function. A global function pointer variable can be defined, and a function can be provided to set that pointer:

InterruptCallback currentCallback = NULL;
void Register_Interrupt_Callback(InterruptCallback callback) {
    currentCallback = callback;
}

The above code defines a global pointer variable currentCallback of type InterruptCallback, initialized to NULL, and provides the Register_Interrupt_Callback function to register the interrupt callback function, assigning the address of the passed callback function to currentCallback.

4. Write Interrupt Service Function

The interrupt service function is automatically called by the system when an interrupt occurs, and within this function, the registered interrupt callback function needs to be called:

void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_Line0) != RESET) {
        if (currentCallback != NULL) {
            currentCallback(); // Call registered interrupt callback function
        }
        EXTI_ClearITPendingBit(EXTI_Line0);
    }
}

In the above code, EXTI0_IRQHandler is the interrupt service function corresponding to the external interrupt line EXTI0. In this function, it first checks whether the interrupt flag is set, and if it is set and there is a registered interrupt callback function, it calls that callback function, and finally clears the interrupt flag.

5. Use Interrupt Callback Function

In the main function, you can perform the following operations to use the interrupt callback mechanism:

int main(void) {
    GPIO_Interrupt_Init();
    // Define a specific interrupt callback function
    void Button_Pressed_Callback(void) {
        // Write the logic for handling button press here, e.g., light up an LED
        // Assume the LED is connected to pin PB5
        GPIO_SetBits(GPIOB, GPIO_Pin_5);
    }
    Register_Interrupt_Callback(Button_Pressed_Callback);
    while (1) {
        // Main loop can perform other tasks
    }
}

In the above main function, the GPIO_Interrupt_Init function is first called to initialize the GPIO and interrupt configuration, then a specific interrupt callback function Button_Pressed_Callback is defined, which implements the logic for handling the button press (here assumed to light up the LED connected to pin PB5), and finally, the callback function is registered in the system through the Register_Interrupt_Callback function. The main loop can continue to perform other tasks, and when the external button is pressed, triggering the interrupt, the system will automatically call the interrupt service function, which in turn executes the registered interrupt callback function.

Embedded Development in C: Function Pointers and the typedef Keyword

Leave a Comment