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

news2024/10/6 10:29:31

最小任务切换的实现

本例子实现了一个 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 task
	getCanditask ();

	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 schedule
	while(1){}
}
  1. nSysTick_Handler 里触发 PendSV 中断
void nSysTick_Handler (void) 
{
	trigger_pendsv();
}
  1. 在 PendSV 里面进行线程切换,需要 2 个变量,一个是当前的任务控制块,用来保存当前的寄存器到它的任务栈。一个是候选的任务控制块,用来恢复寄存器的数据。
AREA    |.text|, CODE, READONLY
	EXPORT  PendSV_Handler
	IMPORT  pcurtasktcb
	IMPORT  pcanditasktcb
		
PendSV_Handler
; save r4 - r11 ---> stack
	mrs r0, psp
	STMDB r0!, {r4-r11}  ; r0 = new stack top; save to task ctx 
	
	ldr r1, =pcurtasktcb   ; pcurtasktcb = point to current task ctx
	ldr r3, [r1]           ; r3 = *pcurtasktcb
	str r0, [r3]           ; save new stack to 
	
; switch to new task
 	ldr r2, =pcanditasktcb  ; 
	ldr r2, [r2]  ; r2 = *pcanditasktcb
	str r2, [r1]  ; save 
	ldr r2, [r2]
	
	LDMIA r2!, {r4-r11}
	
	; update psp reg
	msr psp, r2
	BX  lr
	
	END

工程使用方法:

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

keil仿真

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

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

相关文章

VMware虚拟机安装Ubuntu-Server版教程(超详细)

目录 1. 下载2. 安装 VMware3. 安装 Ubuntu3.1 新建虚拟机3.2 安装操作系统 4. SSH方式连接操作系统4.1 好用的SSH工具下载&#xff1a;4.2 测试SSH连接 5. 开启root用户登录5.1 设置root用户密码5.2 传统方式切换root用户5.3 直接用root用户登录5.4 SSH启用root用户登录 6. 安…

Java-----Comparable接口和Comparator接口

在Java中&#xff0c;我们会经常使用到自定义类&#xff0c;那我们如何进行自定义类的比较呢? 1.Comparable接口 普通数据的比较 int a10;int b91;System.out.println(a<b); 那自定义类型可不可以这样比较呢&#xff1f;看一下代码 我们发现会报错&#xff0c;因为自定义…

OSError: [Errno 117] Structure needs cleaning

一 问题描述 OSError: [Errno 117] Structure needs cleaning: /tmp/pymp-wafeatri 我重新使用SSH登录也会提示这个类似问题 二 解决方法 2.1 尝试删除报错的文件 &#xff08;想直接看最终解决方法的可忽略此处&#xff09; sudo rm -rf /tmp/pymp-wafeatri 此种方法只能保证…

C++:SLT容器-->vector

C:SLT容器-->vector 1. vector 构造函数2. vector 赋值操作3. vector 容器和大小4. vector 插入和删除5. vector 数据存取6. vector 互换容器7. vector 预留空间 vector数据结构和数组非常相似&#xff0c;也称为单端数组。不同的是数组是静态空间&#xff0c;而vector可以动…

深入分析 Android Service (三)

文章目录 深入分析 Android Service (三)1. Service 与 Activity 之间的通信2. 详细示例&#xff1a;通过绑定服务进行通信2.1 创建一个绑定服务2.2 绑定和通信 3. 优化建议4. 使用场景5. 总结 深入分析 Android Service (三) 1. Service 与 Activity 之间的通信 在 Android …

网页截图并添加美观外壳:无需PS轻松实现的方法

在日常生活和工作中&#xff0c;我们经常需要截取网页的屏幕快照&#xff0c;以便于分享、保存或用于其他用途。尽管许多人认为使用Photoshop&#xff08;PS&#xff09;是最佳选择&#xff0c;但实际上&#xff0c;有许多更简单、快捷的方法可以帮助我们实现这一目标&#xff…

【C++题解】1446. 人口增长问题

问题&#xff1a;1446. 人口增长问题 类型&#xff1a;循环应用 题目描述&#xff1a; 我国现有 x 亿人口&#xff0c;按照每年 0.1% 的增长速度&#xff0c;n 年后将有多少人&#xff1f; 输入&#xff1a; 一行&#xff0c;包含两个整数 x 和 n &#xff0c;分别是人口基…

Oracle导出clob字段到csv

使用UTL_FILE ref: How to Export The Table with a CLOB Column Into a CSV File using UTL_FILE ?(Doc ID 1967617.1) --preapre data CREATE TABLE TESTCLOB(ID NUMBER, MYCLOB1 CLOB, MYCLOB2 CLOB ); INSERT INTO TESTCLOB(ID,MYCLOB1,MYCLOB2) VALUES(1,Sample row 11…

Go 优雅的爬虫框架 - Colly

Colly 是一款用 Go 语言编写的优雅网络爬虫框架,速度快、灵活且易于使用 关键特性包括: 线程安全。用户友好的 API。支持 XHR(Ajax)和 WebSocket。缓存和持久化。支持速度限制和分布式爬取。强大的可扩展性。colly采集器配置 AllowedDomains: 设置收集器使用的域白名单,设…

rtl8723DU移植 android4.4 4418

一、 linux 的移植。 首先编译一遍确保没有问题。 将驱动拷贝到 driver/net/wireless 目录下。 使用的是&#xff1a; 改写 makefile Kconfig 去改写 8723 的makefile 设置menuconfig 使能固有的 库。 使能USB部分 ieee 部分 编译一遍 有报错。 解决&#xff1a; …

NPDP(New Product Development Professional)

NPDP&#xff08;New Product Development Professional&#xff09; NPDP考试介绍 NPDP证书介绍

这款信创FTP软件,可实现安全稳定的文件传输

信创&#xff0c;即信息技术应用创新&#xff0c;2018年以来&#xff0c;受“华为、中兴事件”影响&#xff0c;国家将信创产业纳入国家战略&#xff0c;并提出了“28n”发展体系。“8”具体指金融、石油、电力、电信、交通、航空航天、医院、教育等主要行业。目前企业使用比较…

四川汇聚荣聚荣科技有限公司在市场评价好吗?

随着科技行业的迅猛发展&#xff0c;越来越多的科技公司如雨后春笋般涌现&#xff0c;其中不乏一些优秀的企业。四川汇聚荣聚荣科技有限公司便是其中的一员。那么&#xff0c;这家公司在市场上的评价如何呢?接下来&#xff0c;我们将从四个方面进行详细的阐述。 一、公司概况四…

Sapphire开发日志(三)

任务描述 本周实现并封装了模型的运行&#xff0c;需要在前端安装依赖的库&#xff0c;编写对应的处理函数。同时需要能够展示出抠图效果。 依赖与环境配置 尝试在前端接入并运行onnx。 在前端运行onnx依赖于onnxruntime-web这个包&#xff0c;这个包可以在浏览器进行模型推…

Gartner发布电信运营商应对持续变化的网络安全环境指南:现代云安全与网络安全的五大核心挑战

所有组织的云和网络都面临着高级威胁。作为网络安全的关键参与者&#xff0c;电信运营商的 CIO 需要了解行业面临的挑战&#xff0c;并了解应采用哪些解决方案来实现方法的现代化。 主要发现 电信运营商 (CSP) CIO 如果不能调整其安全策略来保护其环境&#xff0c;那么他们将会…

python在cmd中运行.exe文件时报错:不是内部或外部命令,也不是可运行的程序或批处理文件。的解决办法

添加系统环境变量&#xff1a; 设置环境变量&#xff0c;在用户变量里面添加 【PATH&#xff1a;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;C:\Windows\SysWOW64】 在系统变量里面添加,【变量名&#xff1a;ComSpec】 【变量值&#xff1a;%SystemRoo…

动态添加xxljob任务-xxljob源码改造

在实际开发中&#xff0c;很多时候需要在代码中动态添加和执行xxljob的任务&#xff0c;那么原来的xxljob任务调度平台不能完成这项需求&#xff0c;就需要对源码进行改造。 1. 分析源码 1.1 请求路径 在任务调度平台新建一个任务&#xff0c;保存&#xff0c;查看发送的请求…

Blink基准测试:挑战多模态大型语言模型的视觉感知能力

随着人工智能技术的飞速发展&#xff0c;多模态大型语言模型&#xff08;LLMs&#xff09;在理解和生成文本方面取得了显著成就。然而&#xff0c;这些模型在核心视觉感知任务上的表现仍远落后于人类。本文介绍了Blink基准测试&#xff0c;这是一套针对多模态LLMs的视觉感知能力…

Leetcode:寻找两个正序数组的中位数

题目链接&#xff1a;4. 寻找两个正序数组的中位数 - 力扣&#xff08;LeetCode&#xff09; 题目分析 1、当只有一个有序数组时&#xff0c;该数组的中位数会将该数组分为两份&#xff1a;左子数组 和 右子数组 2、当有两个有序数组时&#xff0c; 我们仍然可以通过一条分隔…

第5章 锁与进程间通信(3)

目录 5.4 其他IPC机制 5.4.1 信号 5.4.2 管道和套接字 5.5 小结 本专栏文章将有70篇左右&#xff0c;欢迎关注&#xff0c;查看后续文章。 5.4 其他IPC机制 5.4.1 信号 kill命令&#xff1a; 作用&#xff1a;发送指定信号。 信号分为&#xff1a; 传统32个信号。 用于实…