PIC18 C定时到期任务实现:自动复位输出位及任务跟踪
Alright, let's break down how to implement this delayed task system for your PIC18 using ANSI C, building on your existing TMR4 10ms timestamp setup. The core idea is to create a lightweight task scheduler that tracks pending actions, checks them on each timer tick, and executes them when their time is up.
First, we need a way to store all the details of each delayed task—when it should run, what action to take, and any parameters needed. We'll use a struct with a union to handle different task types (resetting a bit or running a custom function):
// Adjust this based on how many concurrent delayed tasks you need #define MAX_TASKS 8 // Enum to distinguish between different task types typedef enum { TASK_RESET_BIT, TASK_CUSTOM_FUNCTION } TaskType; // Struct to hold all info for a delayed task typedef struct { unsigned int trigger_timestamp; // When the task should execute (based on your global timestamp) TaskType type; // What kind of task this is union { // Parameters for resetting a specific bit struct { int byte; int bitnr; } bit_reset; // Parameters for running a custom function struct { void (*func)(void*); void* arg; } custom; } params; unsigned char active; // Flag to mark if the task is waiting to run } DelayedTask;
We'll use an array of the above struct as our task queue. Initialize all tasks as inactive on startup:
DelayedTask task_queue[MAX_TASKS]; void init_task_scheduler(void) { for (int i = 0; i < MAX_TASKS; i++) { task_queue[i].active = 0; // Mark all slots as empty initially } }
resetBit Function (and Custom Task Scheduling) Now, let's build the resetBit function you mentioned—it will add a bit-reset task to the queue. We'll also add a helper for scheduling custom functions, since you mentioned that as an alternative.
First, make sure you have your global timestamp variable (updated every 10ms by TMR4) declared as an extern here:
extern unsigned int global_timestamp; // This should be updated in your TMR4 ISR
Then the resetBit function:
void resetBit(int byte, int bitnr, int time) { // Calculate when the task should run: current timestamp + (time in ms / 10ms per tick) unsigned int trigger_time = global_timestamp + (time / 10); // Find an empty slot in the task queue for (int i = 0; i < MAX_TASKS; i++) { if (!task_queue[i].active) { // Fill in the task details task_queue[i].trigger_timestamp = trigger_time; task_queue[i].type = TASK_RESET_BIT; task_queue[i].params.bit_reset.byte = byte; task_queue[i].params.bit_reset.bitnr = bitnr; task_queue[i].active = 1; return; } } // If we hit this point, the queue is full—add error handling here (e.g., log, ignore, or overwrite) }
And a helper for scheduling custom functions:
void schedule_custom_task(void (*func)(void*), void* arg, int time) { unsigned int trigger_time = global_timestamp + (time / 10); for (int i = 0; i < MAX_TASKS; i++) { if (!task_queue[i].active) { task_queue[i].trigger_timestamp = trigger_time; task_queue[i].type = TASK_CUSTOM_FUNCTION; task_queue[i].params.custom.func = func; task_queue[i].params.custom.arg = arg; task_queue[i].active = 1; return; } } // Queue full error handling here }
The final piece is checking the task queue on every TMR4 tick (every 10ms) and executing tasks when their time comes. Add this logic inside your TMR4 ISR:
void __interrupt() isr(void) { if (PIR3bits.TMR4IF) { // Check if TMR4 triggered the interrupt // Update your global timestamp (you should already have this line) global_timestamp++; // Loop through all tasks and check if any are ready to run for (int i = 0; i < MAX_TASKS; i++) { if (task_queue[i].active) { // Handle timestamp overflow (for 16-bit timestamps, they wrap after ~655 seconds) unsigned char task_ready = 0; if (global_timestamp >= task_queue[i].trigger_timestamp) { task_ready = 1; } else if (task_queue[i].trigger_timestamp - global_timestamp > 32768) { // If the trigger time is "behind" current time but the difference is large, it's an overflow task_ready = 1; } if (task_ready) { switch (task_queue[i].type) { case TASK_RESET_BIT: // Replace this with your actual bit-reset logic // Example: If 'byte' maps to output registers like PORTB/PORTC // volatile unsigned char* output_regs[] = {&PORTB, &PORTC, ...}; // output_regs[task_queue[i].params.bit_reset.byte] &= ~(1 << task_queue[i].params.bit_reset.bitnr); break; case TASK_CUSTOM_FUNCTION: // Run the custom function with its argument task_queue[i].params.custom.func(task_queue[i].params.custom.arg); break; } // Mark the task as completed (inactive) task_queue[i].active = 0; } } } PIR3bits.TMR4IF = 0; // Clear the interrupt flag to re-enable future ticks } // Handle other interrupts here if needed }
- Timestamp Overflow: The code above includes basic handling for 16-bit timestamp overflow. If you're using a larger timestamp type (like 32-bit), you can skip this check.
- ISR Efficiency: Keep the code inside the ISR as lean as possible. If your custom function is long, consider setting a flag in the ISR and running the function in your main loop instead—this avoids blocking other interrupts.
- Register Safety: When accessing PIC18 I/O registers, use
volatileto prevent the compiler from optimizing away your writes/reads. - Queue Size: Adjust
MAX_TASKSto match how many concurrent delayed actions you expect to have.
内容的提问来源于stack exchange,提问作者Rian

