在STM32上创建一个自己的操作系统

2023-10-27

参考文章:http://www.cnblogs.com/ansersion/p/4328800.html

 

上面是我的微信和QQ群,欢迎新朋友的加入。

 

之前看了蛮多帖子,不过苦于自己对着基本上是门外汉,基本上只明白个大概,幸亏找到一个分享源码的帖子,在这上面我也实现了一个操作系统的移植;同时我也感觉到了自己学习的不足,以前总以为会用几款单片机就觉得单片机学完了,这段时间看系统的内容才发现自己原来在技术方面的欠缺这么大;

 

 

我的代码基本上和参考文章差不多,毕竟自己也不是很明白,门外汉嘛。

 

硬件平台:秉火开发板

软件平台:野火STM32工程模板(103系列)

系统参考来源:UCOS

 

我看源码的方式可能不一样,我是从ASM开始的

1.首先是:

 

    IMPORT     OSTCBCur
    IMPORT    OSTCBNext
    
    EXPORT    OS_ENTER_CRITICAL
    EXPORT    OS_EXIT_CRITICAL
    EXPORT    OSStart
    EXPORT    PendSV_Handler
    EXPORT    OSCtxSw

这里涉及到一个ASM的语法,就是IMPORT和EXPORT不能顶格写,我自己因为从来没接触过汇编,查了蛮久的资料才晓得的;

 

 

这有两个函数:

IMPORT ,定义表示这是一个外部变量的标号,不是在本程序定义的
EXPORT ,表示本程序里面用到的变量提供给其他模块调用的。

先说下IMPORT 的内容,汇编里面,他是从外面调用的

 

    IMPORT     OSTCBCur
    IMPORT    OSTCBNext

看一下我的代码里面这两个变量的来源(虽然是抄袭的别人的代码,我也不害臊的称之为我的吧)

 

 

typedef struct os_tcb {
    OS_STK    *OSTCBStkPtr;     // (OS Task Control Block Stack Pointer)
    INT8U     OSTCBStat;        // (OS Task Control Block Status)
} OS_TCB;                       // (OS Task Control Block)

extern OS_TCB *OSTCBCur;  // Pointer to the current running task(OS Task Control Block Current)
extern OS_TCB *OSTCBNext; // Pointer to the next running task(OS Task Control Block Next)

这里给结构体OS_TCB传入了两个参数,OSTCBCur指向当前运行任务的指针  OSTCBNext指向下一个运行任务的指针

 

这个东西其实我们早有接触,不知道大家有没没有注意,我们做硬件仿真的时候,点击复位,指针的指向位置:

 

; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDPLDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP

我们32运行的时候首先执行了一个sysyteminit,然后再去执行我们的main函数
 

 

然后是EXPORT 内容,这是本函数自己定义的,就没什么好说的了;

 

    EXPORT    OS_ENTER_CRITICAL
    EXPORT    OS_EXIT_CRITICAL
    EXPORT    OSStart
    EXPORT    PendSV_Handler
    EXPORT    OSCtxSw

 

 

2.NVIC中断向量

 

NVIC_INT_CTRL    EQU         0xE000ED04    ; Address of NVIC Interruptions Control Register
NVIC_PENDSVSET   EQU         0x10000000    ; Enable PendSV
NVIC_SYSPRI14    EQU         0xE000ED22  ; System priority register (priority 14).
NVIC_PENDSV_PRI  EQU         0xFF        ; PendSV priority value (lowest).

 

 

;/******************OSStart************/
OSStart
    ; disable interruptions
    CPSID    I                            ; OS_ENTER_CRITICAL();
    ; initialize PendSV
    ; Set the PendSV exception priority
    LDR     R0, =NVIC_SYSPRI14            ; R0 = NVIC_SYSPRI14;
    LDR     R1, =NVIC_PENDSV_PRI          ; R1 = NVIC_PENDSV_PRI;
    STRB    R1, [R0]                      ; *R0 = R1;
    
    ; initialize PSP as 0
    ; MOV    R4, #0
    LDR R4,  =0x0                            ; R4 = 0;
    MSR    PSP, R4                           ; PSP = R4;
    
    ; trigger PendSV
    LDR    R4, =NVIC_INT_CTRL              ; R4 = NVIC_INT_CTRL;
    LDR    R5, =NVIC_PENDSVSET             ; R5 = NVIC_PENDSVSET;
    STR    R5, [R4]                        ; *R4 = R5;
    
    ; enable interruptions
    CPSIE    I                            ; OS_EXIT_CRITICAL();


这里我参考的帖子是:http://www.cnblogs.com/13chfang/p/6151565.html

 

 

这是一段从ucos截取出来的代码,这段汇编程序其实特别简单,做了以下几个事情:

1.将pendSV中断设置为最低优先级

LDR ...=:伪指令,参数、地址传递用的

STRB指令用于从源寄存器中将一个8位的字节数据传送到存储器中。该字节数据为源寄存器中的低8位。

1      LDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priority
2      LDR     R1, =NVIC_PENDSV_PRI
3      STRB    R1, [R0]

2.将PSP置0

特权级下用MSP,用户级用PSP。

1     MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch call
2     MSR     PSP, R0

3.分配堆栈给MSR,这个堆栈的作用其实是在中断嵌套的时候可以将寄存器和局部变量等进行入栈。如果中断程序较大的话或者中断嵌套较多的话,建议将这个堆栈空间设置得更大一些,我们不能只是关心任务堆栈。PS.取最后一个数组元素地址的原因是因为我们CM3的堆栈方向是从高到低的。

简单普及一下:MSR的意思是move to special register from  register的缩写,可以将普通寄存器的数值保存到xpsr寄存器中。

复制代码

1 ;/*在前面的头文件里定义的,这里这是写出来容易看*/
2 ;unsigned int* OS_CPU_ExceptStackBase = &CPU_ExceptStack[1023];
3 
4 
5     LDR     R0, = OS_CPU_ExceptStackBase                          ; Initialize the MSP to the OS_CPU_ExceptStkBase
6     LDR     R1, [R0]
7     MSR     MSP, R1    

复制代码

4.触发pendSV异常,实现任务切换,顺便enable interrupts.

(查了资料CPSID/CPSIE就是开关终端的东西)

    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    
    CPSIE   I                                                   ; Enable interrupts at processor level

 


根据这段内容,知道我的代码里面内容的含义:

 

CPSID    I                            ; OS_ENTER_CRITICAL();

关闭中断
 

LDR     R0, =NVIC_SYSPRI14            ; R0 = NVIC_SYSPRI14;
    LDR     R1, =NVIC_PENDSV_PRI          ; R1 = NVIC_PENDSV_PRI;
    STRB    R1, [R0]                      ; *R0 = R1;

设置中断优先级

 

 

   ; initialize PSP as 0
    ; MOV    R4, #0
    LDR R4,  =0x0                            ; R4 = 0;
    MSR    PSP, R4                           ; PSP = R4;

通用寄存器把参数0送到状态寄存器里面

 

 

; trigger PendSV
    LDR    R4, =NVIC_INT_CTRL              ; R4 = NVIC_INT_CTRL;
    LDR    R5, =NVIC_PENDSVSET             ; R5 = NVIC_PENDSVSET;
    STR    R5, [R4]                        ; *R4 = R5;

使能PENSV悬起寄存器

 

 

; enable interruptions
    CPSIE    I                            ; OS_EXIT_CRITICAL();

开中断

 

 

这个内容在我的代码里面只执行一次

位置是主函数:

 

OSStart(); // start os!

开启系统任务

 

 

3.任务切换的实现

用的是OSStart(); // start os!里面初始化的PENSV异常服务;
先来段别人的解释吧,我解释的可能比较词穷

 

10.SVC(系统服务调用,亦简称系统调用)和 PendSV(可悬起系统调用),它们多用在上了操作系统的软件开发中。 SVC 用于产生系统函数的调用请求。例如,操作系统通常不让用户程序直接访问硬件,而是通过提供一些系统服务函数,让用户程序使用 SVC 发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就要产生一个SVC 异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再调用相关的操作系统函数,后者完成用户程序请求的服务。    

    这种“提出要求——得到满足”的方式,很好、很强大、很方便、很灵活、很能可持续发展。首先,它使用户程序从控制硬件的繁文缛节中解脱出来,而是由 OS 负责控制具体的硬件。第二,OS 的代码可以经过充分的测试,从而能使系统更加健壮和可靠。第三,它使用户程序无需在特权级序变得与硬件无关,因此在开发应用程序时无需了解硬件的操作细节,从而简化了开发的难度和繁琐度,并且使应用程序跨硬件平台移植成为可能。开发应用程序唯一需要知道的就是操作系统提供的应用编程接口( API),并且在了解了各个请求代号和参数表后,就可以使用 SVC 来提出要求了。SVC 异常通过执行”SVC”指令来产生。该指令需要一个立即数,充当系统调用代号。 SVC 异常服务例程稍后会提取出此代号,从而获知本次调用的具体要求,再调用相应的服务函数。例如,

                                                 SVC 0x3 ; 调用 3 号系统服务
      在 SVC 服务例程执行后,上次执行的 SVC 指令地址可以根据自动入栈的返回地址计算出。找到了 SVC 指令后,就可以读取该 SVC 指令的机器码,从机器码中萃取出立即数,就获知了请求执行的功能代号。如果用户程序使用的是 PSP,服务例程还需要先执行 MRS Rn, PSP 指令来获取应用程序的堆栈指针。通过分析 LR 的值,可以获知在 SVC指令执行时,正在使用哪个堆栈。

  11.另一个相关的异常是 PendSV(可悬起的系统调用),它和 SVC 协同使用。一方面, SVC 异常是必须在执行 SVC 指令后立即得到响应的(对于 SVC 异常来说,若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬 fault),应用程序执行 SVC 时都是希望所需的请求立即得到响应。另一方面, PendSV 则不同,它是可以像普通的中断一样被悬起的(不像SVC 那样会上访)。 OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。悬起 PendSV 的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写 1。悬起后,如果优先级不够高,则将缓期等待执行。PendSV 异常会自动延迟上下文切换的请求,直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把 PendSV 编程为最低优先级的异常。

    

以上内容为基于CM3内核开发一个实时操作系统我们需要知道的一些关于CM3的知识,建议去看《CM3权威指南Cn

 

这就是前辈对这个异常服务的解释,在代码里面如果把这内容拆解:

 

;/******************PendSV_Handler************/
PendSV_Handler
    CPSID    I                            ; OS_ENTER_CRITICAL();
    ; judge if PSP is 0 which means the task is first invoked
    MRS     R0, PSP                            ; R0 = PSP;
    CBZ     R0, PendSV_Handler_NoSave          ; if(R0 == 0) goto PendSV_Handler_NoSave;
    
    ;     R12, R3, R2, R1
    SUB     R0, R0, #0x20            ; R0 = R0 - 0x20;
    
    ; store R4 
    STR     R4 , [R0]                ; *R0 = R4;
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
    ; store R5 
    STR     R5 , [R0]                ; *R0 = R5;
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
    ; store R6 
    STR     R6 , [R0]                ; *R0 = R6;
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
    ; store R7 
    STR     R7 , [R0]                ; *R0 = R7;
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
    ; store R8 
    STR     R8 , [R0]                ; *R0 = R8;
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;
    ; store R9
    STR     R9, [R0]                ; *R0 = R4;
    ADD     R0, R0, #0x4            ; R0 = R0 + 0x4;
    ; store R10 
    STR     R10, [R0]               ; *R0 = R10;
    ADD     R0, R0, #0x4            ; R0 = R0 + 0x4;
    ; store R11 
    STR     R11, [R0]               ; *R0 = R11;
    ADD     R0, R0, #0x4            ; R0 = R0 + 0x4;


    SUB     R0, R0, #0x20           ; R0 = R0 - 0x20;
    
    ; easy method
    ;SUB     R0, R0, #0x20
    ;STM     R0, {R4-R11}
    
    LDR     R1, =OSTCBCur            ; R1 = OSTCBCur;
    LDR     R1, [R1]                 ; R1 = *R1;(R1 = OSTCBCur->OSTCBStkPtr)
    STR     R0, [R1]                 ; *R1 = R0;(*(OSTCBCur->OSTCBStkPrt) = R0)


PendSV_Handler_NoSave
    LDR     R0, =OSTCBCur           ; R0 = OSTCBCur;
    LDR     R1, =OSTCBNext          ; R1 = OSTCBNext;
    LDR     R2, [R1]                ; R2 = OSTCBNext->OSTCBStkPtr;
    STR     R2, [R0]                ; *R0 = R2;(OSTCBCur->OSTCBStkPtr = OSTCBNext->OSTCBStkPtr)
    
    LDR     R0, [R2]                 ; R0 = *R2;(R0 = OSTCBNext->OSTCBStkPtr)
    ; LDM     R0, {R4-R11}
    ; load R4 
    LDR     R4, [R0]                 ; R4 = *R0;(R4 = *(OSTCBNext->OSTCBStkPtr))
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
    ; load R5 
    LDR     R5, [R0]                 ; R5 = *R0;(R5 = *(OSTCBNext->OSTCBStkPtr))
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
    ; load R6
    LDR     R6, [R0]                 ; R6 = *R0;(R6 = *(OSTCBNext->OSTCBStkPtr))
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
    ; load R7 
    LDR     R7 , [R0]                ; R7 = *R0;(R7 = *(OSTCBNext->OSTCBStkPtr))
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
    ; load R8 
    LDR     R8 , [R0]                ; R8 = *R0;(R8 = *(OSTCBNext->OSTCBStkPtr))
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
    ; load R9 
    LDR     R9 , [R0]                ; R9 = *R0;(R9 = *(OSTCBNext->OSTCBStkPtr))
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
    ; load R10 
    LDR     R10 , [R0]               ; R10 = *R0;(R10 = *(OSTCBNext->OSTCBStkPtr))
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
    ; load R11 
    LDR     R11 , [R0]               ; R11 = *R0;(R11 = *(OSTCBNext->OSTCBStkPtr))
    ADD     R0, R0, #0x4             ; R0 = R0 + 0x4;(OSTCBNext->OSTCBStkPtr++)
    
    MSR     PSP, R0                 ; PSP = R0;(PSP = OSTCBNext->OSTCBStkPtr)
    ; P42
    ; P139 (key word: EXC_RETURN)
    ; use PSP
    ORR     LR, LR, #0x04           ; LR = LR | 0x04;
    CPSIE     I                     ; OS_EXIT_CRITICAL();
    BX    LR                        ; return;

 

 

太长了,我根据注解自己尝试着修改了下

 

 

;/******************PendSV_Handler************/
PendSV_Handler
    CPSID    I                            ; OS_ENTER_CRITICAL();
    ; judge if PSP is 0 which means the task is first invoked
    MRS     R0, PSP                            ; R0 = PSP;
    CBZ     R0, PendSV_Handler_NoSave          ; if(R0 == 0) goto PendSV_Handler_NoSave;
    
    SUB     R0, R0, #0x20            ; R0 = R0 - 0x20;
    
    ; easy method
    STM     R0, {R4-R11}
    
    LDR     R1, =OSTCBCur            ; R1 = OSTCBCur;
    LDR     R1, [R1]                 ; R1 = *R1;(R1 = OSTCBCur->OSTCBStkPtr)
    STR     R0, [R1]                 ; *R1 = R0;(*(OSTCBCur->OSTCBStkPrt) = R0)

PendSV_Handler_NoSave
    LDR     R0, =OSTCBCur           ; R0 = OSTCBCur;
    LDR     R1, =OSTCBNext          ; R1 = OSTCBNext;
    LDR     R2, [R1]                ; R2 = OSTCBNext->OSTCBStkPtr;
    STR     R2, [R0]                ; *R0 = R2;(OSTCBCur->OSTCBStkPtr = OSTCBNext->OSTCBStkPtr)
    
    LDR     R0, [R2]                 ; R0 = *R2;(R0 = OSTCBNext->OSTCBStkPtr)
    LDM     R0, {R4-R11}
    ADDS    R0, R0, #0x20

    MSR     PSP, R0                 ; PSP = R0;(PSP = OSTCBNext->OSTCBStkPtr)
    ; P42
    ; P139 (key word: EXC_RETURN)
    ; use PSP
    ORR     LR, LR, #0x04           ; LR = LR | 0x04;
    CPSIE     I                     ; OS_EXIT_CRITICAL();
    BX    LR                        ; return;


这样汇编部分基本做完了,

 

我代码里面其他的汇编内容好像影响不是特别大,不是核心的,就不说了

4.任务创建

 

OS_STK* OSTaskStkInit(void (*task)(void *p_arg),
          void *p_arg,
          OS_STK *p_tos)
{
    OS_STK *stk;
    stk = p_tos;

    *(stk)    = (INT32U)0x01000000L;             // xPSR                                               
    *(--stk)  = (INT32U)task;                    // Entry Point  

    // Don't be serious with the value below. They are of random
    *(--stk)  = (INT32U)0xFFFFFFFEL;             // R14 (LR) 
    *(--stk)  = (INT32U)0x12121212L;             // R12                                                
    *(--stk)  = (INT32U)0x03030303L;             // R3                                                 
    *(--stk)  = (INT32U)0x02020202L;             // R2                                                 
    *(--stk)  = (INT32U)0x01010101L;             // R1                                                 

    // pointer of the argument
    *(--stk)  = (INT32U)p_arg;                   // R0

    // Don't be serious with the value below. They are of random
    *(--stk)  = (INT32U)0x11111111L;             // R11 
    *(--stk)  = (INT32U)0x10101010L;             // R10 
    *(--stk)  = (INT32U)0x09090909L;             // R9  
    *(--stk)  = (INT32U)0x08080808L;             // R8  
    *(--stk)  = (INT32U)0x07070707L;             // R7  
    *(--stk)  = (INT32U)0x06060606L;             // R6  
    *(--stk)  = (INT32U)0x05050505L;             // R5  
    *(--stk)  = (INT32U)0x04040404L;             // R4  
    return stk;
}

借用别人的一个描述

 

 

 void vTaskCreate(TCB* tcb,void (*task)(void),unsigned int* stack)
16 {
17         unsigned int *pstrStack;
18         pstrStack = stack;
19         pstrStack = (unsigned int*)    ((unsigned int)(pstrStack)&0xfffffff8u);/* 8字节对齐 */
20         *(--pstrStack) = (unsigned int)0x01000000ul; /* XPSR*/
21         *(--pstrStack) = (unsigned int)task;       /* r15 */
22         *(--pstrStack) = (unsigned int) Task_End;       /* r14 */
23         *(--pstrStack) = (unsigned int)0x12121212ul;    /*r12*/
24         *(--pstrStack) = (unsigned int)0x03030303ul;    /*r3*/
25         *(--pstrStack) = (unsigned int)0x02020202ul;    /*r2*/
26         *(--pstrStack) = (unsigned int)0x01010101ul;    /*r1*/
27         *(--pstrStack) = (unsigned int)0x00000000ul;    /*r0*/
28     
29         *(--pstrStack) = (unsigned int)0x11111111ul;    /*r11*/
30         *(--pstrStack) = (unsigned int)0x10101010ul;    /*r10*/
31         *(--pstrStack) = (unsigned int)0x09090909ul;    /*r9*/
32         *(--pstrStack) = (unsigned int)0x08080808ul;    /*r8*/
33         *(--pstrStack) = (unsigned int)0x07070707ul;    /*r7*/
34         *(--pstrStack) = (unsigned int)0x06060606ul;    /*r6*/
35         *(--pstrStack) = (unsigned int)0x05050505ul;    /*r5*/
36         *(--pstrStack) = (unsigned int)0x04040404ul;    /*r4*/
37         
38         tcb->pstrStack = pstrStack;
39 }

复制代码

 

我们程序做得工作主要如下:
(1)传进了三个参数,参数1:任务TCB指针,这是一个结构体指针,此时首地址是我们存的是pstrStack;参数2是任务函数指针,也就是我们希望调用一个任务后他执行的函数; 参数3是我们分配的堆栈栈顶,可以使用动态分配或者静态分配,我们这里其实是定义了一个数组,传进来的数组的最后一个元素的地址(因为栈的生长方向是从高到低的)。

(2)定义一个变量pstrStack指针指向栈顶,接下来程序里做的事情是初始化中断返回后从栈中恢复的8个寄存器。首先初始化的是xPSP寄存器,将它的第24位置1,表示处于Thumb状态;在c语言中,我们的函数名就是函数的首地址,从这个地址开始存放着函数的指令,我们只需跳转到这个地址就可以执行函数,所以我们开始运行一个任务需要做的事情就是跳转到这个任务的函数名,所以我们接下来做的事就是让PC寄存器指向该函数的首地址;接下来我们初始化的是LR寄存器,用来保存函数的返回地址, 我们任务执行到最后会跳转到LR寄存器指向的地址,所以如果我们的任务没有写成无限循环的形式的话,最后就会跳转到LR指向的地址。为了防止因为我们忘记将任务写成无限循环而出现系统奔溃情况,我们将LR寄存器指向了一个无限循环的函数Task_End()的地址,这增加了我们代码的健壮性。在ucos中,系统在这个函数里面可以将任务删除掉。

(3)后面的寄存器我们都是简单地随便赋值,其实是为了debug可以方便点。但是其实我们还是要关注R0~R3这四个寄存器的。在ARM中(如果我没记错的话),函数传参的时候,前四个形参都是直接都过R0~R3这四个寄存器实现参数传递的,当形参个数大于4个的话,其余的入口参数则依次压入当前栈,通过栈传参。还有一个比较重要的,我们子函数通过R0寄存器将函数返回值传递给父函数。所以,我们如果要给我们的任务函数传参,我们需要把传进来的形参存放到R0~R3寄存器中。比如uCOS和freeRTOS就都用R0寄存器传参给任务函数,uCOS还通过R1存放堆栈限制增长到的内存地址。

(4)最后,我们将我们初始化好的任务堆栈地址赋值给我们任务TCB的pstrStack指针。我们只要将这个指针指向的地址赋值给我们的OSTCBHighRdyPtr就可以任务的切换了。

看一下在主函数里面对任务的创建

 

OSTaskCreate(Task1, (void*)0, (OS_STK*)&Task1Stk[TASK_STACK_SIZE-1]); // create task 1
    OSTaskCreate(Task2, (void*)0, (OS_STK*)&Task2Stk[TASK_STACK_SIZE-1]); // create task 2
    OSTaskCreate(Task3, (void*)0, (OS_STK*)&Task3Stk[TASK_STACK_SIZE-1]); // create task 3


主函数里面就是很普通的系统使用了,跟UCOS差不多,没什么好讲的;

 

本文上传代码:(可能大神们觉得这很简单吧,都不传代码,那我这种小白就乖乖的发出来了)
资源名称:我的第一个实时操作系统

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在STM32上创建一个自己的操作系统 的相关文章

  • 51单片机 数码管中断操作

    实践目的 1 掌握中断的概念和思想 2 掌握51单片机中断系统和相关软硬件设计 实践内容 1 利用单片机的P0口接数码管的字段脚 P1 0脚接共阴极 P3 2 P3 3引脚接独立按键产生外部中断信号 编写程序 当程序正常运行时数码管显示H字
  • 处理器指令周期执行时间

    我的猜测是 no operation 内在 ARM 指令应花费 1 168 MHz 来执行 前提是每个NOP在一个时钟周期内执行 我想通过文档验证这一点 有关处理器指令周期执行时间的信息是否有标准位置 我试图确定 STM32f407IGh6
  • 在地址“0xXXXXXX”处中断,没有可用的调试信息,或在程序代码之外

    配置 使用 Nucleo L476RG 使用 GNU ARM Eclipse 我从 STM32CubeMX 生成了一个极简代码 我已经在我的板载 ST Link 中刷新了 J link 驱动程序 一直在尝试为我的代码运行调试器 但我的程序计
  • c项目makefile多重定义错误

    这个问题是一个对应于创建的repexthis问题 在我的嵌入式 C 项目中 我有两个独立的板 我想为每个板创建两个 c 文件 master c 和 Slave c 其中包含自己的特定main 功能 我使用 stm32cumbemx 生成带有
  • GCC - 如何停止链接 malloc?

    我正在努力将我的代码缩减到最小的骨架大小 我使用的是只有 32k 闪存的 STM32F0 需要很大一部分闪存用于数据存储 我的代码已经有大约 20k 闪存大小 其中一些是由于使用了 STM32 HAL 函数 我可以在以后需要时对其进行解释和
  • 如何让printf在STM32F103上工作?

    我是 STM32F103 世界的新手 我有一个STM32F103的演示代码 我正在使用arm none eabi来编译它 我尝试了在谷歌上可以找到的内容 但到目前为止没有任何效果 我已经花了三天时间来解决这个问题 任何人都可以给我一个运行良
  • 133-基于stm32单片机停车场车位管理系统Proteus仿真+源程序

    资料编号 133 一 功能介绍 1 采用stm32单片机 4位数码管 独立按键 制作一个基于stm32单片机停车场车位管理系统Proteus仿真 2 通过按键进行模拟车辆进出 并且通过程序计算出当前的剩余车位数量 3 将剩余的车位数量显示到
  • HAL库STM32常用外设教程(二)—— GPIO输入\输出

    HAL库STM32常用外设教程 二 GPIO输入 输出 文章目录 HAL库STM32常用外设教程 二 GPIO输入 输出 前言 一 GPIO功能概述 二 GPIO的HAl库驱动 三 GPIO使用示例 1 示例功能 四 代码讲解 五 总结
  • rt-thread studio中新建5.0不能用

    文章目录 一 版本对比 二 文件和文件夹打斜杠 在使用RT Thread studio创建新工程5 0版本的时候 结果发现新建完成之后程序不能正常运行 但是创建4 10版本的时候却能运行 那肯定是新版本出现了BUG 一 版本对比 首先对比了
  • Push_back() 导致程序在进入 main() 之前停止

    我正在为我的 STM32F3 Discovery 板使用 C 进行开发 并使用 std deque 作为队列 在尝试调试我的代码 直接在带有 ST link 的设备上或在模拟器中 后 代码最终在 main 中输入我的代码之前在断点处停止 然
  • 匹配 STM32F0 和 zlib 中的 CRC32

    我正在研究运行 Linux 的计算机和 STM32F0 之间的通信链路 我想对我的数据包使用某种错误检测 并且由于 STM32F0 有 CRC32 硬件 并且我在 Linux 上有带有 CRC32 的 zlib 所以我认为在我的项目中使用
  • STM32用一个定时器执行多任务写法

    文章目录 main c include stm32f4xx h uint32 t Power check times 电量检测周期 uint32 t RFID Init Check times RFID检测周期 int main Timer
  • STM32超声波——HC_SR04

    文章目录 一 超声波图片 二 时序图 三 超声波流程 四 单位换算 五 取余计算 六 换算距离 七 超声波代码 一 超声波图片 测量距离 2cm 400cm 二 时序图 1 以下时序图要先提供一个至少10us的脉冲触发信号 告诉单片机我准备
  • 硬件基础-电容

    电容 本质 电容两端电压不能激变 所以可以起到稳定电压作用 充放电 电容量的大小 想使电容容量大 使用介电常数高的介质 增大极板间的面积 减小极板间的距离 品牌 国外 村田 muRata 松下 PANASONIC 三星 SAMSUNG 太诱
  • 最终启动顺序错误 - STM32L476 的 Eclipse System Workbench 调试

    我正在尝试调试和运行 STM32L476 的简单汇编代码 我已经设置了 Eclipse Oxygen 在 Eclipse 中安装了最新版本的 System Workbench 插件并安装了 ST Link 驱动程序 IDE 成功构建了程序
  • 嵌入式开发--STM32G4系列片上FLASH的读写

    这个玩意吧 说起来很简单 就是几行代码的事 但楞是折腾了我大半天时间才搞定 原因后面说 先看代码吧 读操作 读操作很简单 以32位方式读取的时候是这样的 data IO uint32 t 0x0800F000 需要注意的是 当以32位方式读
  • 核心耦合内存在 STM32F4xx 上可执行吗?

    尝试从 STM32F429s CCM 运行代码 但每当我命中 CCM 中的第一条指令时 我总是会遇到硬故障 并且 IBUSERR 标志被设置 该指令有效且一致 STM32F4xx 是否可能不允许从 CCM 执行 数据访问效果良好 alios
  • STM32内部时钟

    我对 STM32F7 设备 意法半导体的 Cortex M7 微控制器 上的时钟系统感到困惑 参考手册没有充分阐明这些时钟之间的差异 SYSCLK HCLK FCLK 参考手册中阅读章节 gt RCC 为 Cortex 系统定时器 SysT
  • 在 Contiki 程序中使用 malloc

    考虑以下 Contiki 程序 include
  • stm32l0: 执行MI命令失败。使用 vFlashErase 数据包擦除闪存时出错

    我正在使用 Nucleo STM32L031 和 AC6 STM32 工作台 eclipse 我编写应用程序并进入调试模式 一切正常 直到我在应用程序中添加另一个功能 我注意到当我删除 评论 新函数 软件可以再次进入调试模式 但是当我添加

随机推荐

  • 情感分析 Python:使用自然语言处理进行情感分类

    情感分析是自然语言处理中的一个重要任务 它旨在确定文本中表达的情感倾向 如正面 负面或中性 在本文中 我们将介绍如何使用Python进行情感分析 借助一些常用的库和技术来实现这一目标 准备工作 在开始之前 我们需要安装一些Python库 以
  • halcon微积分原理生成卡尺,异形产品宽度测量

    1 普通测量项目中 我们可以利用halcon的测量模型 例如add metrology object line measure 很方便的测量直线 圆 椭圆 矩形等 这些工具都有一个缺点是 需要提前绘制测量位置 然后利用仿射变换跟随 或者在项
  • scala.collection.map 和 scala.collection.mutable.map有什么区别

    一 类型 1 Map 映射 是一种可迭代的键值对 key value 结构 2 所有的值都可以通过键 key 来获取 3 Map 中的键都是唯一的 Map 也叫哈希表 Hash tables 二 两种类型 scala collection
  • 将Kali Linux2020.3设置为中文汉化

    打开虚拟机后进入终端输入 vim etc apt sources list 更新源文件 个人建议 使用中科大 在文件内容末尾添加 中科大 deb http mirrors ustc edu cn kali kali rolling main
  • synchronized同步关键字三种写法和开发中如何解决线程安全问题

    文章目录 前言 一 同步代码块 二 在实例方法上使用synchronized 三 在静态方法上使用synchronized 总结 开发中如何解决线程安全问题 第一种方案 第二种方案 第三种方案 前言 为了保证线程安全 我们可以采用synch
  • JavaScript试题总结

    1 我们可以在下列哪个HTML元素中放置JavaScript代码 A A
  • C++基础知识 - 函数模板的概念

    数模板语法 所谓函数模板 实际上是建立一个通用函数 其函数类型和形参类型不具体指定 用一个虚拟的类型来代表 这个通用函数就称为函数模板 所有函数体相同的函数都可以用这个模板来代替 不必定义多个函数 只需在模板中定义一次即可 在调用函数时系统
  • RabbitMQ常见问题

    一 RabbitMQ如何保证消息不丢失 这是面试时最喜欢问的问题 其实这是个所有MQ的一个共性的问题 大致的解 决思路也是差不多的 但是针对不同的MQ产品会有不同的解决方案 而RabbitMQ 设计之处就是针对企业内部系统之间进行调用设计的
  • 合肥未来计算机技术,重磅!连夜宣布!中国首个量子计算机操作系统在合肥发布!...

    2月8日晚8点 位于合肥的本源量子举行线上发布会 发布首款国产量子计算机操作系统 本源司南 该系统实现量子资源系统化管理 量子计算任务并行化执行 量子芯片自动化校准等全新功能 助力量子计算机高效稳定运行 相对于传统计算机 一台强大的量子计算
  • Hum Brain Mapp:用于功能连接体指纹识别和认知状态解码的高精度机器学习技术

    摘要 人脑是一个复杂的网络 由功能和解剖上相互连接的脑区组成 越来越多的研究表明 对脑网络的实证估计可能有助于发现疾病和认知状态的生物标志物 然而 实现这一目标的先决条件是脑网络还必须是个体的可靠标记 在这里 本研究利用人类连接组项目数据
  • apt和aptitude_如何使用Apt,Apt-Get,Aptitude命令列出可用的更新和可更新软件包?...

    apt和aptitude apt and apt get provides online package update for the deb based distributions We can list currently availa
  • iOS开发-ScrollView图片缩放

    智能手机一般常用常用的操作触摸 滑动 缩放 感觉对于生活而言就是手机在手 天下我有 看网页的时候字体太小 缩放一下 看美女的看的不爽 缩放一下 地图看的不清 缩放一下 缩放是一个很常见的操作 不论是从生活还是写程序而言 都是一个绕不开的东西
  • 数字IC手撕代码-乐鑫科技笔试真题(4倍频)

    前言 本专栏旨在记录高频笔面试手撕代码题 以备数字前端秋招 本专栏所有文章提供原理分析 代码及波形 所有代码均经过本人验证 目录如下 1 数字IC手撕代码 分频器 任意偶数分频 2 数字IC手撕代码 分频器 任意奇数分频 3 数字IC手撕代
  • idea报错:fatal: –author ‘user@mail.com’ is not ‘Name ’ and matches no existing author

    需求阐述 在本地项目上传到gitlab时 到了Commit Directory这一步 控制台报错 fatal author user mail com is not Name and matches no existing author 解
  • 代码:如何在 C# 中实现将大型 Excel 文件导出为 CSV ?

    在本主题中 我们将介绍如何在 C 中将大型 Excel 文件导出为CSV的问题 下面给出的在 C 应用程序中以编程方式将 Excel 文件转换为 CSV 格式的步骤以及简单易行的代码将为您提供所需的解决方案 开发人员在处理像XLSX或XLS
  • Python request-html cv2获取网络图片【canvas base64图片】

    测试网站 http www porters vip captcha clicks html import cv2 import base64 import numpy as np import nest asyncio nest async
  • 电子设计大赛需要具备的知识

    具体的说 有 一 基础知识1 电路原理2 数字电路3 模拟电路 重点 4 元器件的简介二 软件方面 总体编程能力 1 单片机基础与编程 重点 单片机内部结构与工作原理 单片机接口电路 单片机程序设计 单片机开发系统 51系列或AVR单片机
  • 【转】Stephen Wolfram写的乔布斯的回忆录

    无意间在微博上看到Stephen Wolfram也写了回忆Jobs的博客 感觉这个人的名字是相当熟悉 后来看到Mathematica这个软件的名字时就感到非常亲切了 这款软件是以前用过的一款非常强大的数学工具软件 可以解决公式计算 解方程组
  • 产品命名规则(自用)

    产品命名规则 自用 产品id命名规则 共8 型号 3 relay类型 1 计量计类型 1 最大值 1 阶段 1 注 型号 根据产品形态定义 如smartplus 可以定义成sp1 sp是smartplus缩写 1是序号 如果有相同类型 sp
  • 在STM32上创建一个自己的操作系统

    参考文章 http www cnblogs com ansersion p 4328800 html 上面是我的微信和QQ群 欢迎新朋友的加入 之前看了蛮多帖子 不过苦于自己对着基本上是门外汉 基本上只明白个大概 幸亏找到一个分享源码的帖子