(学习日记)2024.03.04:UCOSIII第六节:main函数+前六节总结

2024-03-05 03:52

本文主要是介绍(学习日记)2024.03.04:UCOSIII第六节:main函数+前六节总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

写在前面:
由于时间的不足与学习的碎片化,写博客变得有些奢侈。
但是对于记录学习(忘了以后能快速复习)的渴望一天天变得强烈。
既然如此
不如以天为单位,以时间为顺序,仅仅将博客当做一个知识学习的目录,记录笔者认为最通俗、最有帮助的资料,并尽量总结几句话指明本质,以便于日后搜索起来更加容易。


标题的结构如下:“类型”:“知识点”——“简短的解释”
部分内容由于保密协议无法上传。


点击此处进入学习日记的总目录

2024.03.04

  • 十四、UCOSIII:main()函数
  • 十五、UCOSIII:前六节总结
    • 1、程序的关键
    • 2、PendSV异常的作用
    • 3、前六节代码的运行流程
      • 1. 手动配置任务1为 优先级最高的任务
      • 2.触发PendSV异常
      • 3. 运行异常代码,把当前运行的任务改成优先级最高的任务
      • 4. 运行任务1
      • 5. 手动配置任务2为 优先级最高的任务,然后触发PendSV异常
      • 6. 运行异常代码,把当前运行的任务改成优先级最高的任务
      • 7.运行任务2
      • 8. 手动配置任务1为 优先级最高的任务,然后触发PendSV异常
      • 9. 运行异常代码,把当前运行的任务改成优先级最高的任务
      • 10. 重复流程4 - 9,直至程序结束
    • 4、第六节之后代码的运行流程

十四、UCOSIII:main()函数

main()函数在文件app.c中编写,其中app.c文件如下

/*
*******************************************************************
*                          包含的头文件
*******************************************************************
*/
#include"os.h"
#include"ARMCM3.h"/*
*******************************************************************
*                            宏定义
*******************************************************************
*//*
*******************************************************************
*                          全局变量
*******************************************************************
*/uint32_t flag1;
uint32_t flag2;/*
*******************************************************************
*                        TCB & STACK &任务声明
*******************************************************************
*/
#define  TASK1_STK_SIZE       20
#define  TASK2_STK_SIZE       20static   CPU_STK   Task1Stk[TASK1_STK_SIZE];
static   CPU_STK   Task2Stk[TASK2_STK_SIZE];static   OS_TCB    Task1TCB;
static   OS_TCB    Task2TCB;void     Task1( void *p_arg );
void     Task2( void *p_arg );/*
*******************************************************************
*                            函数声明
*******************************************************************
*/
void delay(uint32_t count);/*
*******************************************************************
*                            main()函数
*******************************************************************
*/
/*
* 注意事项:1、该工程使用软件仿真,debug需选择 Ude Simulator
*         2、在Target选项卡里面把晶振Xtal(Mhz)的值改为25,默认是12,
*              改成25是为了跟system_ARMCM3.c中定义的__SYSTEM_CLOCK相同,
*              确保仿真的时候时钟一致
*/
int main(void)
{OS_ERR err;/* 初始化相关的全局变量 */OSInit(&err);/* 创建任务 */OSTaskCreate ((OS_TCB*)      &Task1TCB,(OS_TASK_PTR ) Task1,(void *)       0,(CPU_STK*)     &Task1Stk[0],(CPU_STK_SIZE) TASK1_STK_SIZE,(OS_ERR *)     &err);OSTaskCreate ((OS_TCB*)      &Task2TCB,(OS_TASK_PTR ) Task2,(void *)       0,(CPU_STK*)     &Task2Stk[0],(CPU_STK_SIZE) TASK2_STK_SIZE,(OS_ERR *)     &err);/* 将任务加入到就绪列表 */OSRdyList[0].HeadPtr = &Task1TCB;OSRdyList[1].HeadPtr = &Task2TCB;/* 启动OS,将不再返回 */OSStart(&err);
}/*
*******************************************************************
*                           函数实现
*******************************************************************
*/
/* 软件延时 */
void delay (uint32_t count)
{for (; count!=0; count--);
}/* 任务1 */
void Task1( void *p_arg )
{for ( ;; ) {flag1 = 1;delay( 100 );flag1 = 0;delay( 100 );/* 任务切换,这里是手动切换 */OSSched();}
}/* 任务2 */
void Task2( void *p_arg )
{for ( ;; ) {flag2 = 1;delay( 100 );flag2 = 0;delay( 100 );/* 任务切换,这里是手动切换 */OSSched();}
}

所有代码在本小节之前都有循序渐进的讲解,这里这是融合在一起放在main()函数中。
其实现在Task1和Task2并不会真正的自动切换,而是在各自的函数体里面加入了OSSched()函数来实现手动切换

/* 任务切换,实际就是触发PendSV异常,然后在PendSV异常中进行上下文切换 */
void OSSched (void)
{if( OSTCBCurPtr == OSRdyList[0].HeadPtr ){OSTCBHighRdyPtr = OSRdyList[1].HeadPtr;}else{OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;}OS_TASK_SW();
}

OSSched()函数的调度算法很简单,即如果当前任务是任务1,那么下一个任务就是任务2,如果当前任务是任务2,那么下一个任务就是任务1, 然后再调用OS_TASK_SW()函数触发PendSV异常,最后在PendSV异常里面实现任务的切换。
在往后的章节中,我们将继续完善,加入SysTick中断, 从而实现系统调度的自动切换。

OS_TASK_SW()函数其实是一个宏定义,具体是往中断及状态控制寄存器SCB_ICSR的位28(PendSV异常启用位)写入1, 从而触发PendSV异常。OS_TASK_SW()函数在os_cpu.h文件中实现
在这里插入图片描述

仿真后可得flag1 和 flag2的图像为
在这里插入图片描述

十五、UCOSIII:前六节总结

1、程序的关键

如果从头到尾把前六节做下来的话,可以体会到目前程序的关键
那就是 触发PendSV异常
即以下语句:

NVIC_INT_CTRL = NVIC_PENDSVSET

其中NVIC_INT_CTRL声明如下,即中断控制及状态寄存器 SCB_ICSR

#ifndef  NVIC_INT_CTRL
#define  NVIC_INT_CTRL    *((CPU_REG32 *)0xE000ED04)   /* 中断控制及状态寄存器 SCB_ICSR */
#endif

NVIC_PENDSVSET声明如下,即一个第28位为1的十六进制数

#ifndef  NVIC_PENDSVSET
#define  NVIC_PENDSVSET   0x10000000    /* 触发PendSV异常的值 Bit28:PENDSVSET */
#endif

不管是在每个任务中都会用到的OS_TASK_SW()函数

#define  OS_TASK_SW()    NVIC_INT_CTRL = NVIC_PENDSVSET

还是再启动函数OSStart(&err)中的任务切换函数OSStartHighRdy()

OSStartHighRdyLDR		R0, = NVIC_SYSPRI14              ; 设置  PendSV 异常优先级为最低LDR     R1, = NVIC_PENDSV_PRISTRB    R1, [R0]MOVS    R0, #0                           ; 设置psp的值为0,开始第一次上下文切换MSR     PSP, R0LDR     R0, =NVIC_INT_CTRL               ; 触发PendSV异常LDR     R1, =NVIC_PENDSVSETSTR     R1, [R0]CPSIE   I                                 ; 开中断

全部都是通过给中断控制及状态寄存器 SCB_ICSR赋值0x10000000触发PendSV异常

2、PendSV异常的作用

如果说程序的关键是触发PendSV异常
那么PendSV异常的作用就是 切换任务
在PendSV异常函数中,代码如下

;********************************************************************************************************
;                                          PendSVHandler异常
;********************************************************************************************************
PendSV_Handler
; 任务的保存,即把CPU寄存器的值存储到任务的堆栈中	CPSID   I                                 ; 关中断,NMI和HardFault除外,防止上下文切换被中断	MRS     R0, PSP                           ; 将psp的值加载到R0CBZ     R0, OS_CPU_PendSVHandler_nosave   ; 判断R0,如果值为0则跳转到OS_CPU_PendSVHandler_nosave; 进行第一次任务切换的时候,R0肯定为0;-----------------------一、保存上文-----------------------------
; 任务的切换,即把下一个要运行的任务的栈内容加载到CPU寄存器中
; 在进入PendSV异常的时候,当前CPU的xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0会自动存储到当前任务堆栈,同时递减PSP的值
;--------------------------------------------------------------STMDB   R0!, {R4-R11}                     ; 手动存储CPU寄存器R4-R11的值到当前任务的堆栈LDR     R1, = OSTCBCurPtr                 ; 加载 OSTCBCurPtr 指针的地址到R1,这里LDR属于伪指令LDR     R1, [R1]                          ; 加载 OSTCBCurPtr 指针到R1,这里LDR属于ARM指令STR     R0, [R1]                          ; 存储R0的值到	OSTCBCurPtr->OSTCBStkPtr,这个时候R0存的是任务空闲栈的栈顶;-----------------------二、切换下文-----------------------------
; 实现 OSTCBCurPtr = OSTCBHighRdyPtr
; 把下一个要运行的任务的栈内容加载到CPU寄存器中
; 任务的切换,即把下一个要运行的任务的堆栈内容加载到CPU寄存器中
;--------------------------------------------------------------
OS_CPU_PendSVHandler_nosave  ; OSTCBCurPtr = OSTCBHighRdyPtr;LDR     R0, = OSTCBCurPtr                 ; 加载 OSTCBCurPtr 指针的地址到R0,这里LDR属于伪指令LDR     R1, = OSTCBHighRdyPtr             ; 加载 OSTCBHighRdyPtr 指针的地址到R1,这里LDR属于伪指令LDR     R2, [R1]                          ; 加载 OSTCBHighRdyPtr 指针到R2,这里LDR属于ARM指令STR     R2, [R0]                          ; 存储 OSTCBHighRdyPtr 到 OSTCBCurPtrLDR     R0, [R2]                          ; 加载 OSTCBHighRdyPtr 到 R0LDMIA   R0!, {R4-R11}                     ; 加载需要手动保存的信息到CPU寄存器R4-R11MSR     PSP, R0                           ; 更新PSP的值,这个时候PSP指向下一个要执行的任务的堆栈的栈底(这个栈底已经加上刚刚手动加载到CPU寄存器R4-R11的偏移)ORR     LR, LR, #0x04                     ; 确保异常返回使用的堆栈指针是PSP,即LR寄存器的位2要为1CPSIE   I                                 ; 开中断BX      LR                                ; 异常返回,这个时候任务堆栈中的剩下内容将会自动加载到xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参); 同时PSP的值也将更新,即指向任务堆栈的栈顶。在STM32中,堆栈是由高地址向低地址生长的。NOP                                       ; 为了汇编指令对齐,不然会有警告END                                       ; 汇编文件结束

可以清楚看到系统在这里只完成了一个操作

  • 实现OSTCBCurPtr = OSTCBHighRdyPtr,即把当前运行的任务改成优先级最高的任务,实现任务的切换

然后在每个任务完成后再触发PendSV异常,实现整个系统的流转运行

3、前六节代码的运行流程

省略初始化、宏定义、变量定义等等一系列流程,我们只看任务运行流程

1. 手动配置任务1为 优先级最高的任务

void OSStart (OS_ERR *p_err)
{if ( OSRunning == OS_STATE_OS_STOPPED ) {(1)/* 手动配置任务1先运行 */OSTCBHighRdyPtr = OSRdyList[0].HeadPtr;(2)/* 启动任务切换,不会返回 */OSStartHighRdy();(3)/* 不会运行到这里,运行到这里表示发生了致命的错误 */*p_err = OS_ERR_FATAL_RETURN;}else{*p_err = OS_STATE_OS_RUNNING;}
}

2.触发PendSV异常

;********************************************************************************************************
;                                          开始第一次上下文切换
; 1、配置PendSV异常的优先级为最低
; 2、在开始第一次上下文切换之前,设置psp=0
; 3、触发PendSV异常,开始上下文切换
;********************************************************************************************************
OSStartHighRdyLDR		R0, = NVIC_SYSPRI14              ; 设置  PendSV 异常优先级为最低LDR     R1, = NVIC_PENDSV_PRISTRB    R1, [R0]MOVS    R0, #0                           ; 设置psp的值为0,开始第一次上下文切换MSR     PSP, R0LDR     R0, =NVIC_INT_CTRL               ; 触发PendSV异常LDR     R1, =NVIC_PENDSVSETSTR     R1, [R0]CPSIE   I                                 ; 开中断

在这里插入图片描述

3. 运行异常代码,把当前运行的任务改成优先级最高的任务

在这里插入图片描述
在这里插入图片描述

4. 运行任务1

在这里插入图片描述
在这里插入图片描述

5. 手动配置任务2为 优先级最高的任务,然后触发PendSV异常

在这里插入图片描述
在这里插入图片描述

#define  OS_TASK_SW()   NVIC_INT_CTRL = NVIC_PENDSVSET

6. 运行异常代码,把当前运行的任务改成优先级最高的任务

在这里插入图片描述

7.运行任务2

在这里插入图片描述
在这里插入图片描述

8. 手动配置任务1为 优先级最高的任务,然后触发PendSV异常

在这里插入图片描述
在这里插入图片描述

9. 运行异常代码,把当前运行的任务改成优先级最高的任务

在这里插入图片描述

10. 重复流程4 - 9,直至程序结束

在这里插入图片描述

4、第六节之后代码的运行流程

在第七节及之后,我们将逐渐把任务切换交给SysTick中断, 从而实现系统调度的自动切换。

这篇关于(学习日记)2024.03.04:UCOSIII第六节:main函数+前六节总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/775258

相关文章

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

java常见报错及解决方案总结

《java常见报错及解决方案总结》:本文主要介绍Java编程中常见错误类型及示例,包括语法错误、空指针异常、数组下标越界、类型转换异常、文件未找到异常、除以零异常、非法线程操作异常、方法未定义异常... 目录1. 语法错误 (Syntax Errors)示例 1:解决方案:2. 空指针异常 (NullPoi

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

kotlin的函数forEach示例详解

《kotlin的函数forEach示例详解》在Kotlin中,forEach是一个高阶函数,用于遍历集合中的每个元素并对其执行指定的操作,它的核心特点是简洁、函数式,适用于需要遍历集合且无需返回值的场... 目录一、基本用法1️⃣ 遍历集合2️⃣ 遍历数组3️⃣ 遍历 Map二、与 for 循环的区别三、高

C语言字符函数和字符串函数示例详解

《C语言字符函数和字符串函数示例详解》本文详细介绍了C语言中字符分类函数、字符转换函数及字符串操作函数的使用方法,并通过示例代码展示了如何实现这些功能,通过这些内容,读者可以深入理解并掌握C语言中的字... 目录一、字符分类函数二、字符转换函数三、strlen的使用和模拟实现3.1strlen函数3.2st

Java反转字符串的五种方法总结

《Java反转字符串的五种方法总结》:本文主要介绍五种在Java中反转字符串的方法,包括使用StringBuilder的reverse()方法、字符数组、自定义StringBuilder方法、直接... 目录前言方法一:使用StringBuilder的reverse()方法方法二:使用字符数组方法三:使用自

Java进阶学习之如何开启远程调式

《Java进阶学习之如何开启远程调式》Java开发中的远程调试是一项至关重要的技能,特别是在处理生产环境的问题或者协作开发时,:本文主要介绍Java进阶学习之如何开启远程调式的相关资料,需要的朋友... 目录概述Java远程调试的开启与底层原理开启Java远程调试底层原理JVM参数总结&nbsMbKKXJx

MySQL中COALESCE函数示例详解

《MySQL中COALESCE函数示例详解》COALESCE是一个功能强大且常用的SQL函数,主要用来处理NULL值和实现灵活的值选择策略,能够使查询逻辑更清晰、简洁,:本文主要介绍MySQL中C... 目录语法示例1. 替换 NULL 值2. 用于字段默认值3. 多列优先级4. 结合聚合函数注意事项总结C