rtos最小任务切换的实现 keil软件仿真 stm32 PendSV

最小任务切换的实现

本例子实现了一个 rtos 最小的任务切换功能,使用 keil 仿真功能,在模拟的 stm32f103 的器件上实现了使用 PendSV 中断切换线程的效果。
git 源码仓库:https://github.com/yutianos/rtos-little
本文链接:csdn@LeiCoder 将持续更新
https://blog.csdn.net/qq_29832469/article/details/139330311

环境基础

  • win11
  • keil v5.39
  • ArmClang.exe V6.21

技术基础

  • cortex-m3 架构中,在中断模式下(Handler)使用 MSP,在用户模式(Thread)模式下使用 PSP 作为栈指针。使用 msr msp, r0 指令来设置 MSP 寄存器,寄存器的值为栈的高地址。
  • cortex-m3 架构中,栈的增长方向为递减,向栈内压入数据,栈指针的地址的数值减小,并且栈的指针指向最新的数据。
  • cortex-m3 架构中,在进入中断模式时,硬件会自动将 [r0 r1 r2 r3 r12 r14(LR) r15(pc) xpsr] 一共 8 个寄存器压入栈内,且压入顺序固定
  • cortex-m3 架构中,从中断模式返回时,Cortex-M3 处理器支持不同的异常返回方式,这些方式由 LR 寄存器中的特定值指示。当处理器从异常(例如中断或系统调用)返回时,它会检查 LR 寄存器的值以确定返回方式和堆栈指针。常见的异常返回值包括:
0xFFFFFFF1:返回到特权模式,使用 MSP(Main Stack Pointer)。
0xFFFFFFF9:返回到特权模式,使用 MSP(这是硬件自动保存的值)。
0xFFFFFFFD:返回到线程模式,使用 PSP(Process Stack Pointer)。
  • cortex-m3 架构中,通常使用 PendSV 中断来切换线程,由于它可以方便通过软件触发,是一个系统级中断,而且使用中我们将其优先级配置为最低,保证其他中断事务处理完成之后才进行任务切换。
  • cortex-m3 架构中,有一个 systick 定时器,用它作为系统的时基。

实现步骤

  1. 设计使用 systick 作为时基。设置成为一个周期触发的事件,用来检查是否需要切换任务,并触发 PendSV 中断。代码如下,这个配置是来自 freertos,详细内容见 systick.c 文件。
#define configKERNEL_INTERRUPT_PRIORITY    255
/* Constants required to manipulate the core.  Registers first... */
#define portNVIC_SYSTICK_CTRL_REG             ( *( ( volatile uint32_t * ) 0xe000e010 ) )
#define portNVIC_SYSTICK_LOAD_REG             ( *( ( volatile uint32_t * ) 0xe000e014 ) )
#define portNVIC_SYSTICK_CURRENT_VALUE_REG    ( *( ( volatile uint32_t * ) 0xe000e018 ) )
#define portNVIC_SHPR3_REG                    ( *( ( volatile uint32_t * ) 0xe000ed20 ) )
/* ...then bits in the registers. */
#define portNVIC_SYSTICK_CLK_BIT              ( 1UL << 2UL )
#define portNVIC_SYSTICK_INT_BIT              ( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT           ( 1UL << 0UL )
#define portNVIC_SYSTICK_COUNT_FLAG_BIT       ( 1UL << 16UL )
#define portNVIC_PENDSVCLEAR_BIT              ( 1UL << 27UL )
#define portNVIC_PEND_SYSTICK_SET_BIT         ( 1UL << 26UL )
#define portNVIC_PEND_SYSTICK_CLEAR_BIT       ( 1UL << 25UL )#define portNVIC_PENDSV_PRI                   ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI                  ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )/* Constants required to check the validity of an interrupt priority. */
#define portFIRST_USER_INTERRUPT_NUMBER       ( 16 )
#define portNVIC_IP_REGISTERS_OFFSET_16       ( 0xE000E3F0 )
#define portAIRCR_REG                         ( *( ( volatile uint32_t * ) 0xE000ED0C ) )
#define portMAX_8_BIT_VALUE                   ( ( uint8_t ) 0xff )
#define portTOP_BIT_OF_BYTE                   ( ( uint8_t ) 0x80 )
#define portMAX_PRIGROUP_BITS                 ( ( uint8_t ) 7 )
#define portPRIORITY_GROUP_MASK               ( 0x07UL << 8UL )
#define portPRIGROUP_SHIFT                    ( 8UL )/* Masks off all bits but the VECTACTIVE bits in the ICSR register. */
#define portVECTACTIVE_MASK                   ( 0xFFUL )#define portNVIC_INT_CTRL_REG     ( *( ( volatile uint32_t * ) 0xe000ed04 ) )
#define portNVIC_PENDSVSET_BIT    ( 1UL << 28UL )static long g_lsystimetick = 0;
int getCanditask (void);/* Scheduler utilities. */
#define portYIELD()                                 \
{                                                   \/* Set a PendSV to request a context switch. */ \portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \__asm( "	dsb");                                \__asm( "	isb");                                \
}void trigger_pendsv(void) {portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}void init_pendsv_priority(void) {portNVIC_SHPR3_REG |= portNVIC_PENDSV_PRI;
}
void init_ticktimer_priority(void) {portNVIC_SHPR3_REG |= portNVIC_SYSTICK_PRI;
}#define configSYSTICK_CLOCK_HZ  (74 * 1000 * 100)
#define configTICK_RATE_HZ      (1000) 
#define portMAX_24_BIT_NUMBER                 ( 0xffffffUL )
void vPortSetupTimerInterrupt( void )
{int ulTimerCountsForOneTick;int xMaximumPossibleSuppressedTicks; /* Calculate the constants required to configure the tick interrupt. */{ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;//ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );}/* Stop and clear the SysTick. */portNVIC_SYSTICK_CTRL_REG = 0UL;portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;/* Configure SysTick to interrupt at the requested rate. */portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}
void nSysTick_Handler (void)
{g_lsystimetick++;// select candiate taskgetCanditask ();trigger_pendsv();
  1. 创建任务。
    需要配置任务的入口地址,任务的栈空间,且必须将栈空间初始化。下面的代码使用简单的方法
struct taskpcb {void *pstktop;void *pentry;void *param;char stack[512-12];
};#define TASK_MAX  5
#define DEFAULT_XPSR   (0x01000000)
#define DEFAULT_LD     (0xFFFFFFFD)
static struct taskpcb TCB[TASK_MAX];
static struct taskpcb NullTask;static struct taskpcb *tasklist[TASK_MAX];struct taskpcb *pcurtasktcb;
struct taskpcb *pcanditasktcb;struct taskpcb * getfreetcb (void);
int regitserTask (struct taskpcb *ptcb);
int create_task (void *pentry,void *param);int getCanditask (void)
{static int i = -1;do {if (i < TASK_MAX - 1) i++;else i = 0;pcanditasktcb = tasklist[i];}while (pcanditasktcb == NULL);//printf("pcurtasktcb: [%p] = %p  -->%p\r\n", &pcurtasktcb, pcurtasktcb, pcurtasktcb->pstktop);return  0;
}
struct taskpcb * getfreetcb (void)
{int  i;for (i = 0; i < TASK_MAX; i++){if(TCB[i].pentry == NULL) return (&TCB[i]);}return  NULL;
}int regitserTask (struct taskpcb *ptcb)
{int  i;for (i = 0; i < TASK_MAX; i++) {if(tasklist[i] == NULL) {tasklist[i] = ptcb;return  0;}}return -1;
}int create_task (void *pentry,void *param)
{struct taskpcb *ptcb = NULL;long  *pstktop = NULL;ptcb = getfreetcb();pstktop = (long *)&ptcb->stack[sizeof(ptcb->stack) - 32];ptcb->pentry = pentry;*(pstktop--) = 0x01000000L;*(pstktop--) = (intptr_t) pentry; //pc*(pstktop--) = DEFAULT_LD;*(pstktop--) = 0x12121212L;*(pstktop--) = 0x03030303;*(pstktop--) = 0x02020202;*(pstktop--) = 0x01010101;*(pstktop--) = (intptr_t) param; // r0 param
// save r4 - r11*(pstktop--) = 0x11111111;*(pstktop--) = 0x10101010;*(pstktop--) = 0x09090909;*(pstktop--) = 0x08080808;*(pstktop--) = 0x77777777;*(pstktop--) = 0x66666666;*(pstktop--) = 0x55555555;*(pstktop) = 0x44444444;ptcb->pstktop = pstktop;printf("task: 0x%p stack: 0x%p\r\n", ptcb, pstktop);return  regitserTask(ptcb);
}void start_task1 (void)
{int  *new_psp;void *taskentry;pcurtasktcb = &NullTask;pcanditasktcb = NULL;NullTask.pstktop = &NullTask.stack[sizeof(NullTask.stack)];//wait systick schedulewhile(1){}
}
  1. nSysTick_Handler 里触发 PendSV 中断
void nSysTick_Handler (void) 
{trigger_pendsv();
}
  1. 在 PendSV 里面进行线程切换,需要 2 个变量,一个是当前的任务控制块,用来保存当前的寄存器到它的任务栈。一个是候选的任务控制块,用来恢复寄存器的数据。
AREA    |.text|, CODE, READONLYEXPORT  PendSV_HandlerIMPORT  pcurtasktcbIMPORT  pcanditasktcbPendSV_Handler
; save r4 - r11 ---> stackmrs r0, pspSTMDB r0!, {r4-r11}  ; r0 = new stack top; save to task ctx ldr r1, =pcurtasktcb   ; pcurtasktcb = point to current task ctxldr r3, [r1]           ; r3 = *pcurtasktcbstr r0, [r3]           ; save new stack to ; switch to new taskldr r2, =pcanditasktcb  ; ldr r2, [r2]  ; r2 = *pcanditasktcbstr r2, [r1]  ; save ldr r2, [r2]LDMIA r2!, {r4-r11}; update psp regmsr psp, r2BX  lrEND

工程使用方法:

下载 keil v5.39,打开本工程,编译通过。点击仿真按钮进行仿真,可以在控制输出的窗口中看到循环打印。

keil仿真

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/19640.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数学函数,字符串

目录 Math类 三角函数 指数函数 取整方法 其他方法 String类 常见方法 字符串比较方法 子串和数字与字符串的转换 Math类 Math类在java.lang中&#xff0c;不用显式引入。 三角函数 private static void triangleFunc() {double degree Math.toDegrees(Math.PI / 3…

神经网络与深度学习——第4章 前馈神经网络

本文讨论的内容参考自《神经网络与深度学习》https://nndl.github.io/ 第4章 前馈神经网络 前馈神经网络 神经元 Sigmoid型函数 Hard-Logistic函数和Hard-Tanh函数 ReLU函数 带泄露的ReLU 带参数的ReLU ELU函数 Softplus函数 Swish函数 GELU函数 Maxout单元 网络结构 前馈网络…

Qt图像处理技术九:得到QImage图像的灰度直方图

效果 原理 得到灰度化值&#xff0c;将灰度化的值带入0-255内&#xff0c;增加&#xff0c;得到可视化图形 源码 // 绘制直方图 QImage drawHistogram(const QImage &image) {QVector<int> histogram(256, 0);// 计算图像的灰度直方图for (int y 0; y < image…

【SpringBoot】SpringBoot整合JWT

目录 先说token单点登录&#xff08;SSO&#xff09;简介原理单点登录的优势单点登录流程分布式单点登录方式方式一&#xff1a;session广播机制实现方式二&#xff1a;使用cookieredis实现。方式三&#xff1a;token认证 JWT数字签名JWT的作用JWT和传统Session1、无状态&#…

linux nohup命令详解:持久运行命令,无视终端退出

nohup &#xff08;全称为 “no hang up”&#xff09;&#xff0c;用于运行一个命令&#xff0c;使其在你退出 shell 或终端会话后继续运行。 基本语法 nohup command [arg1 ...] [&> output_file] &command 是你想要运行的命令。[arg1 ...] 是该命令的参数。&am…

微服务学习Day8

文章目录 Sentinel雪崩问题服务保护框架Sentinel配置 限流规则快速入门流控模式流控效果热点参数限流 隔离和降级FeignClient整合Sentinel线程隔离&#xff08;舱壁模式&#xff09;熔断降级 授权规则及规则持久化授权规则自定义异常结果持久化 Sentinel 雪崩问题 服务保护框架…

让WSL内核使用BBR拥塞控制算法

使用git命令从Linux内核的Git仓库中获取源代码,$ git clone --depth 1 https://github.com/microsoft/WSL2-Linux-Kernel.git,找到对应的内核版本$ git log --grep="5.15.146.1-microsoft-standard-WSL2",回退到本机安装的内核版本$ git checkout <commit-id&…

无界延迟队列DelayQueue

一:介绍 DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。 二: DelayQueue基本原理 DelayQueue是一个没有边界…

代码随想录算法训练营第十天|232.用栈实现队列、225. 用队列实现栈

232.用栈实现队列 题目链接&#xff1a;232. 用栈实现队列 文档讲解&#xff1a;代码随想录 状态&#xff1a;写出来 &#xff0c;但差强人意 思路&#xff1a; 定义两个容器&#xff0c;可以是Stack&#xff0c;也可以是Deque&#xff0c;stackIn相当于临时容器,用来存放元素&…

git随记

git status 查看文件状态 git status -s 比较简洁的查看文件状态。如下代表此文件是新建的&#xff0c;没有被git跟踪的文件&#xff1a; $ git status -s ?? abc.txtgit add abc.txt 将abc添加到暂存区。后再次git status -s $ git status -s A abc.txtgit reset 将暂存…

嵌入式开发--stm32cubeprogrammer写入选项字节

需要在批量烧写时写入选项字节&#xff0c;操作如下&#xff1a; 在下载页面&#xff0c;勾选选项字节命令&#xff0c; 输入以下命令&#xff0c;后面的0xBB表示选项字节是bb&#xff0c;表示读保护&#xff0c;也可以按需要写其他的字符 -ob rdp0xBB在需要的功能前打上勾&a…

知识图谱抽取实战

相关代码见文末 1.知识图谱应用场景 知识图谱是一种先进的数据组织形式,它通过图数据结构来表示实体(如人、地点、概念)及其之间的复杂关系,便于机器理解和处理。这种结构化知识库允许高效的信息检索、推理和知识发现,尤其适用于处理高度关联且需要深度理解的领域,如医学…

解决wireshark无法抓取mysql数据报文

我前几天在用wireshark抓取mysql协议&#xff0c;只能看到登录信息&#xff0c;完全看不到具体报文。显示如下&#xff1a; 找了多资料&#xff0c;我也没解决这问题。但用公司测试环境的数据库就能抓取到mysql数据报文&#xff0c;观察了下公司的数据库就只发现连接url上多了…

使用 Django Channels 构建实时聊天应用(包含用户认证和消息持久化)

文章目录 准备工作创建 Django 项目创建应用程序配置项目编写 Consumer编写路由创建 URL 路由运行应用用户认证消息持久化显示历史消息结论 Django Channels 是 Django 的一个扩展&#xff0c;允许在 Web 应用中添加实时功能&#xff0c;例如 Websockets、HTTP2 和其他协议。本…

oracle mysql索引区别

文章目录 1.引言1.1 索引的基本概念1.2 Oracle和MySQL的简介 2.Oracle索引2.1 Oracle索引的类型**B-Tree索引****Bitmap索引****Function-Based索引****Partitioned索引****Text索引** 2.2 Oracle索引的工作原理2.3 Oracle索引的实例代码 3.MySQL索引3.1 MySQL索引的类型**B-Tr…

[PyQt5] 窗口接收WM_COPY消息

#本程序是python qt5 创建的窗口&#xff0c;拦截外部消息给窗口发送的WM_COPY消息并显示出来。一般是用来作为窗口之间的通讯机制之一。 python文件如下&#xff1a;qt5拦截消息 #!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys from PyQt5.QtWidgets import QAp…

STM32-14-FSMC_LCD

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 STM32-13-MPU 文章目录 1. 显示器分类2. LCD简…

掌握 NestJS 10.x:从零开始构建高效可扩展的服务器端应用详解

NestJS 是一个用于构建高效、可扩展的 Node.js 服务端应用的框架&#xff0c;基于 TypeScript 构建&#xff0c;并且受 Angular 的启发&#xff0c;提供了模块化、易测试、易维护的架构。NestJS 10.x 引入了一些新特性和改进&#xff0c;进一步提升了开发体验。本文将详细介绍如…

协方差和协方差矩阵是什么

协方差矩阵&#xff08;Covariance Matrix&#xff09;是一个矩阵&#xff0c;它包含多个随机变量之间的协方差。 协方差是衡量两个随机变量如何一起变化的度量。 协方差矩阵在多元统计分析和机器学习中非常重要&#xff0c;特别是在处理多元正态分布时。 详细解释 协方差&am…

生态融合促发展 YashanDB与丰图科技完成兼容性认证

近日&#xff0c;深圳计算科学研究院崖山数据库系统YashanDB V23与丰图科技智域城市数字孪生平台顺利完成兼容性互认证。经严格测试&#xff0c;双方产品完全兼容&#xff0c;稳定运行&#xff0c;充分满足企事业单位在高性能、高可用性、高稳定性及高可控性方面的核心需求&…