我的 PIC16 多任务 RTOS 内核不工作的原因是什么?

电器工程 微控制器 图片 C 集会 rtos
2022-01-28 01:39:47

我正在尝试为 PIC x16 微控制器创建一个半抢先式(合作)RTOS。在我之前的问题中,我了解到在这些内核中无法访问硬件堆栈指针。我在 PIClist 中查看了这个页面,这就是我试图使用 C 实现的。

我的编译器是 Microchip XC8,目前我正在研究 PIC16F616,在配置位中选择了 4MHz 内部 RC 振荡器。

通过查看编译器的头文件,我了解到我可以使用 C 访问 PCLATH 和 PCL 寄存器。所以,我尝试实现一个简单的任务切换器。

TRISA=0;如果我在重新启动、重置并在光标不在第一行 ( ) 而是在另一行 (例如)时将 PC 设置在光标处时暂停调试器,它可以在调试器中正常工作ANSEL=0;在调试器的第一次启动中,我在以下位置收到这些消息Debugger Console

Launching
Programming target
User program running
No source code lines were found at current PC 0x204

编辑:我不知道是什么让它工作,但调试器现在可以完美运行。所以,省略上面的输出和段落。

编辑:像这样更改主定义使下面的代码工作。这将在程序地址处启动主函数0x0099我不知道是什么原因造成的。这不是一个真正的解决方案。我现在猜测存在编译器特定的错误。

void main(void) @ 0x0099
{

这是我的 C 代码:

/* 
 * File:   main.c
 * Author: abdullah
 *
 * Created on 10 Haziran 2012 Pazar, 14:43
 */
#include <xc.h> // Include the header file needed by the compiler
__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & CP_OFF & IOSCFS_4MHZ & BOREN_ON);
/*
 * INTOSCIO oscillator: I/O function on RA4/OSC2/CLKOUT pin, I/O function on RA5/OSC1/CLKIN
 * WDT disabled and can be enabled by SWDTEN bit of the WDTCON register
 * PWRT enabled
 * MCLR pin function is digital input, MCLR internally tied to VDD
 * Program memory code protection is disabled
 * Internal Oscillator Frequency Select bit : 4MHz
 * Brown-out Reset Selection bits : BOR enabled
 */

/*
 * OS_initializeTask(); definition will copy the PCLATH register to the task's PCLATH holder, which is held in taskx.pch
 * This will help us hold the PCLATH at the point we yield.
 * After that, it will copy the (PCL register + 8) to current task's PCL holder which is held in taskx.pcl.
 * 8 is added to PCL because this line plus the "return" takes 8 instructions.
 * We will set the PCL after these instructions, because
 * we want to be in the point after OS_initializeTask when we come back to this task.
 * After all, the function returns without doing anything more. This will initialize the task's PCLATH and PCL.
 */
#define OS_initializeTask(); currentTask->pch = PCLATH;\
                             currentTask->pcl = PCL + 8;\
                             asm("return");

/*
 * OS_yield(); definition will do the same stuff that OS_initializeTask(); definition do, however
 * it will return to "taskswitcher" label, which is the start of OS_runTasks(); definition.
 */

#define OS_yield();          currentTask->pch = PCLATH;\
                             currentTask->pcl = PCL + 8;\
                             asm("goto _taskswitcher");

/*
 * OS_runTasks(); definition will set the "taskswitcher" label. After that it will change the
 * current task to the next task, by pointing the next item in the linked list of "TCB"s.
 * After that, it will change the PCLATH and PCL registers with the current task's. That will
 * make the program continue the next task from the place it left last time.
 */

#define OS_runTasks();       asm("_taskswitcher");\
                             currentTask = currentTask -> next;\
                             PCLATH = currentTask->pch;\
                             PCL = currentTask->pcl;

typedef struct _TCB // Create task control block and type define it as "TCB"
{
    unsigned char pch; // pch register will hold the PCLATH value of the task after the last yield.
    unsigned char pcl; // pcl register will hold the PCL value of the task after the last yield.
    struct _TCB* next; // This pointer points to the next task. We are creating a linked list.
} TCB;

TCB* currentTask; // This TCB pointer will point to the current task's TCB.

TCB task1; // Define the TCB for task1.
TCB task2; // Define the TCB for task2.

void fTask1(void); // Prototype the function for task1.
void fTask2(void); // Prototype the function for task2.

void main(void)
{
    TRISA = 0; // Set all of the PORTA pins as outputs.
    ANSEL = 0; // Set all of the analog input pins as digital i/o.
    PORTA = 0; // Clear PORTA bits.

    currentTask = &task1; // We will point the currentTask pointer to point the first task.

    task1.next = &task2; // We will create a ringed linked list as follows:
    task2.next = &task1; // task1 -> task2 -> task1 -> task2 ....

    /*
     * Before running the tasks, we should initialize the PCL and PCLATH registers for the tasks.
     * In order to do this, we could have looked up the absolute address with a function pointer.
     * However, it seems like this is not possible with this compiler (or all the x16 PICs?)
     * What this compiler creates is a table of the addresses of the functions and a bunch of GOTOs.
     * This will not let us get the absolute address of the function by doing something like:
     * "currentTask->pcl=low(functionpointer);"
     */
    fTask1(); // Run task1 so that we get the address of it and initialize pch and pcl registers.
    currentTask = currentTask -> next; // Point the currentTask pointer to the next pointer which
    fTask2(); // is task2. And run task2 so that we get the correct pch and pcl.

    OS_runTasks(); // Task switcher. See the comments in the definitions above.
}

void fTask1(void)
{
    OS_initializeTask(); // Initialize the task
    while (1)
    {
        RA0 = ~RA0; // Toggle PORTA.0
        OS_yield(); // Yield
        RA0 = ~RA0; // Toggle PORTA.0
    }
}

void fTask2(void)
{
    OS_initializeTask(); // Initialize the task
    while (1)
    {
        RA1 = ~RA1; // Toggle PORTA.1
        OS_yield(); // Yield
        RA1 = ~RA1; // Toggle PORTA.1
    }
}

是我的编译器创建的反汇编列表文件开始于line 74

我已经对实际的芯片进行了编程,并且在 PORTA 上没有任何变化;它不起作用。

我的程序不起作用的原因是什么?

4个回答

您正在尝试做的事情很棘手,但很有教育意义(如果您准备花费大量精力)。

首先,您必须意识到这种仅限 PC(与 PC+SP 相对)的任务切换(这是您在普通 12 位或 14 位 PIC 内核上唯一可以做的事情)只有在所有 yield( ) 任务中的语句在同一个函数中:它们不能在被调用的函数中,并且编译器不能弄乱函数结构(优化可能会这样做)。

下一个:

currentTask->pch = PCLATH;\
currentTask->pcl = PCL + 8;\
asm("goto _taskswitcher");
  • 您似乎假设 PCLATH 是程序计数器的高位,因为 PCL 是低位。不是这种情况。当您写入 PCL 时,PCLATH 位被写入 PC,但高位 PC 位永远不会(隐式)写入 PCLATH。重新阅读数据表的相关部分。
  • 即使 PCLATH 是 PC 的高位,当 goto 之后的指令与第一条指令不在同一个 256 指令“页”上时,这也会给您带来麻烦。
  • 当 _taskswitcher 不在当前 PCLATH 页面中时,普通 goto 将不起作用,您将需要 LGOTO 或同等产品。

PCLATH 问题的解决方案是在 goto 之后声明一个标签,并将该标签的低位和高位写入您的 pch 和 pcl 位置。但我不确定您是否可以在内联汇编中声明“本地”标签。你肯定可以在简单的 MPASM 中(奥林会微笑)。

最后,对于这种上下文切换,您必须保存和恢复编译器可能依赖的所有上下文,其中可能包括

  • 间接寄存器
  • 状态标志
  • 暂存记忆位置
  • 可能在内存中重叠的局部变量,因为编译器没有意识到您的任务必须是独立的
  • 其他我现在无法想象但编译器作者可能会在下一个版本的编译器中使用的东西(它们往往很有想象力)

PIC 架构在这方面存在更多问题,因为大量资源分布在整个内存映射中,而更传统的架构将它们放在寄存器或堆栈中。因此,PIC 编译器通常不会生成可重入代码,而这是您绝对需要做的事情(同样,Olin 可能会微笑并一起组装。)

如果您喜欢编写任务切换器,我建议您切换到具有更传统组织的 CPU,例如 ARM 或 Cortex。如果您的脚被卡在混凝土板中,请研究现有的 PIC 切换器(例如 salvo/pumkin?)。

我浏览了您提供的程序集列表,但没有任何明显损坏的东西跳出来。

如果我是你,我的下一步将是:

(1) 我会选择其他一些闪烁 LED 的方法。臭名昭著的“读-修改-写问题”可能(也可能不会)由程序集列表中的“XORWF PORTA, F”触发。

也许是这样的:

// Partial translation of code from abdullah kahraman
// untested code
// feel free to use however you see fit
void fTask2(void)
{
    OS_initializeTask(2); // Initialize task 2
    while (1)
    {
        PORTC = 0xAA;
        OS_yield(2); // Yield from task 2
        PORTC = 0x55;
        OS_yield(2); // Yield from task 2
    }
}

(如果您真的想详细了解为什么“XORWF PORTA, F”经常会导致问题,请参阅“打开 Microchip PIC16F690 上的单个输出引脚会自发关闭同一端口上的另一个引脚的原因是什么? ”;“会发生什么?何时将数据写入 LATCH? ";" Read-Modify-Write 问题";" Read before Write ")

(2) 我将单步执行代码,确保将变量设置为预期值并按预期​​顺序。我不确定是否存在PIC16F616的单步硬件调试器,但是有很多出色的PIC单片机模拟器,例如 可以模拟PIC16系列芯片的PICsim 。

单步代码(在模拟器中或使用单步硬件调试器)是了解实际情况的详细信息、确认事情是否按照您的预期发生的好方法,它可以让您看到正在发生的事情全速运行程序时几乎看不到。

(3) 如果我仍然感到困惑,我会尝试翻译代码以使用数组而不是指针。有些人发现使用指针有点棘手且难以调试。我经常发现,在将棘手的指针代码转换为面向数组的代码的过程中,我发现了错误是什么。即使我最终恢复到原始指针代码并丢弃数组版本,该练习也很有用,因为它帮助我找到并修复了错误。(有时编译器可以从面向数组的代码生成更短、更快的代码,所以有时我会丢弃原始指针代码并保留数组版本)。

也许像

// Partial translation of code from abdullah kahraman
// untested code
// feel free to use however you see fit
struct TCB_t // Create task control block and type define it as "TCB_t"
{
    unsigned char pch; // PCLATH value
    unsigned char pcl; // PCL value
    int next; // This array index points to the next task. We are creating a linked list.
};

int currentTask = 1; // This TCB index will point to the current task's TCB.

struct TCB_t tasks[3]; // Define the TCB for task1 and task2.

#define OS_initializeTask(x); tasks[x].pch = PCLATH;\
                             tasks[x].pcl = PCL + 8;\
                             asm("return");

#define OS_runTasks();       asm("_taskswitcher");\
                             currentTask = tasks[currentTask].next;\
                             PCLATH = tasks[currentTask].pch;\
                             PCL = tasks[currentTask].pcl;

#define OS_yield(x);         tasks[x].pch = PCLATH;\
                             tasks[x].pcl = PCL + 8;\
                             asm("goto _taskswitcher");

我基本上同意大卫卡里的观点。看起来它可以工作。

我不知道是什么让它工作的,但调试器现在工作得很好。

我猜你的意思是它在模拟器中完美运行。

1) 检查您的任务是否在真实芯片的非 RTOS 环境中独立运行。

2) 进行在线调试。在真实芯片上逐步执行程序,并观察所有相关变量以确保一切按计划进行。

我只是简单地看了你的代码,但这没有任何意义。在几个地方,您正在写入 PCL,然后期望它执行其他指令。

正如我之前所说,C 不适合这种对基本硬件寄存器的低级访问。你真的需要为此使用汇编。试图弄清楚为什么 C 代码不起作用只是浪费时间。