先了解下如何使用PendSV异常。(为何要使用PendSV而不是其他的异常,请参考《cortex-M3权威指南》)

1,如何设定PendSV优先级?

NVIC_SYSPRI14 EQU 0xE000ED22

NVIC_PENDSV_PRI EQU 0xFF

LDR R0, =NVIC_SYSPRI14 LDR R1, =NVIC_PENDSV_PRI

STRB R1, [R0]

2,如何触发PendSV异常?

往ICSR第28位写1,即可将PendSV异常挂起。若是当前没有高优先级中断产生,那么程序将会进入PendSV handler

NVIC_INT_CTRL EQU 0xE000ED04

NVIC_PENDSVSET EQU 0x10000000

LDR R0, =NVIC_INT_CTRL

LDR R1, =NVIC_PENDSVSET

STR R1, [R0]

3,编写PendSV异常handler

这里用PendSV_Handler来触发LED点亮,以此证明PendSV异常触发的设置是正确的。

#include "stm32f10x_conf.h"

#define LED0 *((volatile unsigned long *)(0x422101a0)) //PA8

unsigned char flag=;

void LEDInit(void)

{

RCC->APB2ENR|=<<;

GPIOA->CRH&=0XFFFFFFF0;

GPIOA->CRH|=0X00000003;

     GPIOA->ODR|=<<;

}

__asm void SetPendSVPro(void)

{

NVIC_SYSPRI14 EQU 0xE000ED22

NVIC_PENDSV_PRI EQU 0xFF

    LDR R1, =NVIC_PENDSV_PRI    

    LDR R0, =NVIC_SYSPRI14

    STRB R1, [R0]

    BX LR

}

__asm void TriggerPendSV(void)

{

NVIC_INT_CTRL EQU 0xE000ED04

NVIC_PENDSVSET EQU 0x10000000

    LDR R0, =NVIC_INT_CTRL

    LDR R1, =NVIC_PENDSVSET

    STR R1, [R0]

    BX LR

}

int main(void)

{

    SetPendSVPro();

    LEDInit();

    TriggerPendSV();

    while();

}

void PendSV_Handler(void)

{

    LED0=;

}

上述代码可以正常点亮LED,说明PendSV异常是正常触发了。

OK,是时候挑战任务切换了。

如何实现任务切换?三个步骤:

步骤一:在进入中断前先设置PSP。

curr_task = 0;

设置任务0为当前任务

__set_PSP((PSP_array[curr_task] + 16*4));

设置PSP指向task0堆栈的栈顶位置

__set_CONTROL(0x3);

设置为用户级,并使用PSP堆栈。

__ISB();

指令同步隔离,暂不知道干啥用

步骤二:将当前寄存器的内容保存到当前任务堆栈中。进入ISR时,cortex-m3会自动保存八个寄存器到PSP中,剩下的几个需要我们手动保存。

步骤三:在Handler中将下一个任务的堆栈中的内容加载到寄存器中,并将PSP指向下一个任务的堆栈。这样就完成了任务切换。

要在PendSV 的ISR中完成这两个步骤,我们先需了解下在进入PendSV ISR时,cortex-M3做了什么?

1,入栈。会有8个寄存器自动入栈。入栈内容及顺序如下:

在步骤一中,我们已经设置了PSP,那这8个寄存器就会自动入栈到PSP所指地址处。

2,取向量。找到PendSV ISR的入口地址,这样就能跳到ISR了。,

3,更新寄存器内容

做完这三步后,程序就进入ISR了。

进入ISR前,我们已经完成了步骤一,cortex-M3已经帮我们完成了步骤二的一部分,剩下的需要我们手动完成。

在ISR中添加代码如下:

MRS R0, PSP

保存PSP到R0。为什么是PSP而不是MSP。因为在OS启动的时候,我们已经把SP设置为PSP了。这样使得用户程序使用任务堆栈,OS使用主堆栈,不会互相干扰。不会因为用户程序导致OS崩溃。

STMDB R0!,{R4-R11}

保存R4-R11到PSP中。C语言表达是*(--R0)={R4-R11},R0中值先自减1,然后将R4-R11的值保存到该值所指向的地址中,即PSP中。

STMDB Rd!,{寄存器列表} 连续存储多个字到Rd中的地址值所指地址处。每次存储前,Rd先自减一次。

若是ISR是从从task0进来,那么此时task0的堆栈中已经保存了该任务的寄存器参数。保存完成后,当前任务堆栈中的内容如下(假设是task0)

左边表格是预期值,右边是keil调试的实际值。可以看出,是一致的。在任务初始化时(步骤一),我们将PSP指向任务0的栈顶0x20000080。在进入PendSV之前,cortex-M3自动入栈八个值,此时PSP指向了0x20000060。然后我们再保存R4-R11到0x20000040~0x2000005C。

这样很容易看明白,如果需要下次再切换到task0,只需恢复R4~R11,再将PSP指向0x20000060即可。

所以切换到另一个任务的代码:

LDR R1,=__cpp(&curr_task)

LDR R3,=__cpp(&PSP_array)

LDR R4,=__cpp(&next_task)

LDR R4,[R4]

获取下一个任务的编号

STR R4,[R1]

Curr_task=next_task

LDR R0,[R3, R4, LSL #2]

获得任务堆栈地址,若是task0,那么R0=0x20000040( R0=R3+R4*4)

LDMIA R0!,{R4-R11}

恢复堆栈中的值到R4~R11。R4=*(R0++)。执行完后,R0中值变为0x20000060

LDMIA Rd! {寄存器列表} 先将Rd中值所指地址处的值送出寄存器中,Rd再自增1.

MSR PSP, R0

PSP=R0。

BX LR

中断返回

完整代码如下:

#include "stm32f10x.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stdio.h"
#include "misc.h" #define HW32_REG(ADDRESS) (*((volatile unsigned long *)(ADDRESS)))
#define LED0 *((volatile unsigned long *)(0x422101a0)) //PA8
void USART1_Init(void);
void task0(void) ;
unsigned char flag=; uint32_t curr_task=; // 当前执行任务
uint32_t next_task=; // 下一个任务
uint32_t task0_stack[];
uint32_t task1_stack[];
uint32_t PSP_array[]; u8 task0_handle=;
u8 task1_handle=; void task0(void)
{
while()
{
if(task0_handle==)
{
printf("task0\n");
task0_handle=;
task1_handle=;
}
}
} void task1(void)
{
while()
{
if(task1_handle==)
{
printf("task1\n");
task1_handle=;
task0_handle=;
}
}
} void LEDInit(void)
{
RCC->APB2ENR|=<<;
GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X00000003;
GPIOA->ODR|=<<;
} __asm void SetPendSVPro(void)
{
NVIC_SYSPRI14 EQU 0xE000ED22
NVIC_PENDSV_PRI EQU 0xFF LDR R1, =NVIC_PENDSV_PRI
LDR R0, =NVIC_SYSPRI14
STRB R1, [R0]
BX LR } __asm void TriggerPendSV(void)
{
NVIC_INT_CTRL EQU 0xE000ED04
NVIC_PENDSVSET EQU 0x10000000 LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
} int main(void)
{
USART1_Init(); SetPendSVPro();
LEDInit(); printf("OS test\n"); PSP_array[] = ((unsigned int) task0_stack) + (sizeof task0_stack) - *;
//PSP_array中存储的为task0_stack数组的尾地址-16*4,即task0_stack[1023-16]地址
HW32_REG((PSP_array[] + (<<))) = (unsigned long) task0; /* PC */
//task0的PC存储在task0_stack[1023-16]地址 +14<<2中,即task0_stack[1022]中
HW32_REG((PSP_array[] + (<<))) = 0x01000000; /* xPSR */ PSP_array[] = ((unsigned int) task1_stack) + (sizeof task1_stack) - *;
HW32_REG((PSP_array[] + (<<))) = (unsigned long) task1; /* PC */
HW32_REG((PSP_array[] + (<<))) = 0x01000000; /* xPSR */ /* 任务0先执行 */
curr_task = ; /* 设置PSP指向任务0堆栈的栈顶 */
__set_PSP((PSP_array[curr_task] + *)); SysTick_Config();
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);//72/8=9MHZ
/* 使用堆栈指针,非特权级状态 */
__set_CONTROL(0x3); /* 改变CONTROL后执行ISB (architectural recommendation) */
__ISB(); /* 启动任务0 */
task0();
//LED0=0;
while();
} __asm void PendSV_Handler(void)
{ // 保存当前任务的寄存器内容
MRS R0, PSP // 得到PSP R0 = PSP
// xPSR, PC, LR, R12, R0-R3已自动保存
STMDB R0!,{R4-R11}// 保存R4-R11共8个寄存器得到当前任务堆栈 // 加载下一个任务的内容
LDR R1,=__cpp(&curr_task)
LDR R3,=__cpp(&PSP_array)
LDR R4,=__cpp(&next_task)
LDR R4,[R4] // 得到下一个任务的ID
STR R4,[R1] // 设置 curr_task = next_task
LDR R0,[R3, R4, LSL #] // 从PSP_array中获取PSP的值
LDMIA R0!,{R4-R11}// 将任务堆栈中的数值加载到R4-R11中
//ADDS R0, R0, #0x20
MSR PSP, R0 // 设置PSP指向此任务
// ORR LR, LR, #0x04
BX LR // 返回
// xPSR, PC, LR, R12, R0-R3会自动的恢复
ALIGN
} void SysTick_Handler(void)
{
flag=~flag;
LED0=flag;
if(curr_task==)
next_task=;
else
next_task=;
TriggerPendSV();
} void USART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure; /* config USART1 clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); /* USART1 GPIO config */
/* Configure USART1 Tx (PA.09) as alternate function push-pull */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* Configure USART1 Rx (PA.10) as input floating */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure); /* USART1 mode config */
USART_InitStructure.USART_BaudRate = ;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No ;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
} int fputc(int ch, FILE *f)
{ USART_SendData(USART1, (unsigned char) ch);
while (!(USART1->SR & USART_FLAG_TXE)); return (ch);
}

测试后结果如图:

可以看出,两个任务可以切换了。

上述代码参考《cortex-M3权威指南》和《安富莱_STM32-V5开发板_μCOS-III教程》得来。

进入OS前的两步之PendSV(任务切换)的更多相关文章

  1. 进入OS前的两步之System tick

    OK,继续向操作系统迈进.由简入繁,先实现两个小功能.第一个是system tick,第二个是任务切换(PendSV).一个是操作系统的心跳,一个是操作系统的并发处理的具体实现. System tic ...

  2. 挑子学习笔记:两步聚类算法(TwoStep Cluster Algorithm)——改进的BIRCH算法

    转载请标明出处:http://www.cnblogs.com/tiaozistudy/p/twostep_cluster_algorithm.html 两步聚类算法是在SPSS Modeler中使用的 ...

  3. 你知道怎么使用Google两步验证保护账户安全吗?

    目录 为什么我们需要使用它? 对有些人来说,盗取密码比您想象的更简单 什么是Google两步验证? 多一道安全防线 什么是Google Authenticator ? 使用Google两步验证的好处 ...

  4. 每次Xcode 升级之后 插件失效,两步解决

    以下内容来源:http://www.cocoachina.com/bbs/read.php?tid=296269 每次Xcode 升级之后 插件失效,两步解决 1.打开终端,输入以下代码获取到DVTP ...

  5. 两步验证Authy时间同步问题

    Authy是我常用的软件之一,通常用于Google的两步验证,或者是其他基于Google两步验证的原理的衍生程序.比如Namesilo.印象笔记等均有使用. 先说说什么是两步验证. 两步验证 两步验证 ...

  6. Github两步认证

    获取密钥:ssh-keygen -t rsa  切换到公钥所在路径:cd .ssh 查看该路径下的所有文件:ls 查看公钥:cat id_rsa.pub 获取密钥之后,去https://github. ...

  7. Google 推出全新的两步验证机制

    近日 Google 在官方的 Apps Updates 博客公布了全新的两步验证功能--Google 提示,新的功能通过与 Google App 联动,进一步将验证确认工作缩减到仅有两步,同时支持 A ...

  8. 只需两步!Eclipse+Maven快速构建第一个Spring Boot项目

     随着使用Spring进行开发的个人和企业越来越多,Spring从一个单一简介的框架变成了一个大而全的开源软件,最直观的变化就是Spring需要引入的配置也越来越多.配置繁琐,容易出错,让人无比头疼, ...

  9. SecureCRT两步验证自动登录脚本

    简介 用于解决 Google Authenticator 的两步验证登录.涉及到密码,不建议脚本保存到公共环境. 安装oathtool Mac $ brew install oath-toolkit ...

随机推荐

  1. [已解决]Teamviewer VPN 连接上,但无法ping

    用Teamveiwer 可以进行远程控制连接.用了VPN功能后,起先也正常.可以PING和其他网络操作. 后来忽然始终VPN连接上后,无法PING和做其他的网络操作了. 检查缘由是对方TeamView ...

  2. netty4虚拟内存不断飙升

    去年升级过一个老的netty3的程序到netty4,近期突然注意到一个问题,就是这个程序随着时间虚拟内存会不断升高.之前升级的时候担心存在内存泄露,所以还特意用jstate跟踪过gc回收的情况,并没有 ...

  3. makefile之VPATH和vpath的使用

    来自阅读陈皓的<跟我一起写makefile> VPATH变量 在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中.所以,当 make 需要去找寻文 ...

  4. 连载《一个程序猿的生命周期》- 44.感谢,我从事了IT相关的工作

    感谢博客园一直以来的支持,写连载都是在这里首发,相比较CSDN和开源中国气氛要好的多. 节前,想以此篇文章结束<一个程序猿的生命周期>的<生存>篇,对过10的年做一个了断,准备 ...

  5. RS-232 vs. TTL Serial Communication(转载)

    RS-232串口一度像现在的USB接口一样,是PC的标准接口,用来连接打印机.Modem和其他一些外设.后来逐渐被USB接口所取代,现在PC上已经看不到它的身影了.开发调试时如果用到串口,一般都是用U ...

  6. Java — JTree and JTable以及sqlServer的两种连接

    使用JTree的步骤: 暂时只能创建一个头结点,创建一个树的结点作为头结点(其子结点也是相同的创建方法):DefaultMutableTreeNode headNode = new DefaultMu ...

  7. 分页ajax+springmvc的简单实现

    页面部分源码: <li class="paginItem"><a href="javascript:getNewsList(2);">2 ...

  8. angular学习笔记(二十九)-$q服务

    angular中的$q是用来处理异步的(主要当然是http交互啦~). $q采用的是promise式的异步编程.什么是promise异步编程呢? 异步编程最重要的核心就是回调,因为有回调函数,所以才构 ...

  9. 关于SQL SERVER数据库学习总结

    对于SQL SERFVER数据库也学了有一阵子了,自己也对自己所学做了一些总结. 我们首先学习数据库设计的一些知识点和用SQL语句建库. 设计数据库步骤:需求分析阶段,概要设计阶段,详细设计阶段, 建 ...

  10. React官网学习笔记

    欢迎指导与讨论 : ) 前言 本文主要是笔者在React英文官网学习时整理的笔记.由于笔者水平有限,如有错误恳请指出 O(∩_∩)O 一 .Tutoial 篇 1 . React的组件类名的首字母必须 ...