(学习日记)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

相关文章

Python itertools中accumulate函数用法及使用运用详细讲解

《Pythonitertools中accumulate函数用法及使用运用详细讲解》:本文主要介绍Python的itertools库中的accumulate函数,该函数可以计算累积和或通过指定函数... 目录1.1前言:1.2定义:1.3衍生用法:1.3Leetcode的实际运用:总结 1.1前言:本文将详

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

轻松上手MYSQL之JSON函数实现高效数据查询与操作

《轻松上手MYSQL之JSON函数实现高效数据查询与操作》:本文主要介绍轻松上手MYSQL之JSON函数实现高效数据查询与操作的相关资料,MySQL提供了多个JSON函数,用于处理和查询JSON数... 目录一、jsON_EXTRACT 提取指定数据二、JSON_UNQUOTE 取消双引号三、JSON_KE

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

Python中连接不同数据库的方法总结

《Python中连接不同数据库的方法总结》在数据驱动的现代应用开发中,Python凭借其丰富的库和强大的生态系统,成为连接各种数据库的理想编程语言,下面我们就来看看如何使用Python实现连接常用的几... 目录一、连接mysql数据库二、连接PostgreSQL数据库三、连接SQLite数据库四、连接Mo

Git提交代码详细流程及问题总结

《Git提交代码详细流程及问题总结》:本文主要介绍Git的三大分区,分别是工作区、暂存区和版本库,并详细描述了提交、推送、拉取代码和合并分支的流程,文中通过代码介绍的非常详解,需要的朋友可以参考下... 目录1.git 三大分区2.Git提交、推送、拉取代码、合并分支详细流程3.问题总结4.git push

Java function函数式接口的使用方法与实例

《Javafunction函数式接口的使用方法与实例》:本文主要介绍Javafunction函数式接口的使用方法与实例,函数式接口如一支未完成的诗篇,用Lambda表达式作韵脚,将代码的机械美感... 目录引言-当代码遇见诗性一、函数式接口的生物学解构1.1 函数式接口的基因密码1.2 六大核心接口的形态学

Kubernetes常用命令大全近期总结

《Kubernetes常用命令大全近期总结》Kubernetes是用于大规模部署和管理这些容器的开源软件-在希腊语中,这个词还有“舵手”或“飞行员”的意思,使用Kubernetes(有时被称为“... 目录前言Kubernetes 的工作原理为什么要使用 Kubernetes?Kubernetes常用命令总

Python中实现进度条的多种方法总结

《Python中实现进度条的多种方法总结》在Python编程中,进度条是一个非常有用的功能,它能让用户直观地了解任务的进度,提升用户体验,本文将介绍几种在Python中实现进度条的常用方法,并通过代码... 目录一、简单的打印方式二、使用tqdm库三、使用alive-progress库四、使用progres

Oracle的to_date()函数详解

《Oracle的to_date()函数详解》Oracle的to_date()函数用于日期格式转换,需要注意Oracle中不区分大小写的MM和mm格式代码,应使用mi代替分钟,此外,Oracle还支持毫... 目录oracle的to_date()函数一.在使用Oracle的to_date函数来做日期转换二.日