赞
踩
在STM32F10x系列MCU中,如果我们启用了 TIM8 定时器 更新中断 (TIM_IT_Update),当 计数器 CNT 的值达到自动重装载的值时,即计数器CNT达到设定的自动重装载寄存器ARR的值时,由硬件产生一次更新(即产生一个时钟脉冲)。此时硬件会自动将更新事件标志位置 1,并向处理器发出定时器中断请求。如果在NVIC中配置相应的中断优先级且使能该中断,那么处理器将会进入中断服务函数:
TIM8_UP_IRQHandler
除了TIM8定时器更新中断之外,还有其他情况可能会导致中断的产生。例如GPIO口的中断模式、USART串口的中断模式、DMA传输完成中断等,只要这些中断都有相应的开关和中断处理程序,当对应的事件被触发时,就会进入相应的中断处理函数。
软中断(Software Interrupt)是通过软件产生的中断,也称为系统调用。在ARM Cortex-M系列MCU中,软中断可以通过调用SVC(Supervisor Call)指令来实现。当处理器执行到SVC指令时,会触发一个异常进入内核态,执行内核态下的中断服务程序(ISR),即SVC Handler。 在SVC Handler中,可以执行内核级别的操作,例如调用OS API接口、修改系统的状态等等。
下面是在C语言中实现软中断的步骤:
// 调用该函数就会进入中断
__asm void foo(void)
{
svc 0 // 0是软中断号(异常号),可以自定义
}
svc 0 是一条汇编指令,用于在ARM Cortex-M系列MCU中触发软中断(Software Interrupt),也称为系统调用。此指令将导致当前运行的应用程序中断,从用户模式切换到特权模式进入内核态,并将控制权转移给系统SVC Handler来执行内核级别的任务。
在执行svc 0指令时,CPU会将当前的程序状态保存到栈中,包括程序计数器PC、SR寄存器、SP堆栈指针等。接着,CPU会把异常向量地址设为SVC异常向量表的地址,进入异常处理流程。在SVC Handler中,可以执行一些需要在内核态下才能访问的操作,例如修改系统状态、调用OS API接口等。任务执行完毕后,使用BX LR指令退出SVC Handler,并将之前保存的状态恢复,回到用户模式继续执行应用程序。
需要注意的是,软中断号0到15被保留用于CPU内部使用,因此建议自定义软中断号时应该选择大于 15 的数字。同时,在使用SVC指令时,还需要编写相应的SVC Handler代码来处理软中断,否则程序会出现异常。
编
写SVC Handler函数的代码,该函数必须位于汇编语言文件中。
在SVC Handler中,我们可以从特定的寄存器中读取传递给软中断的参数,然后根据参数执行相应的任务。另外,还需要注意使用BX LR指令退出SVC Handler,返回用户模式。
SVC_Handler TST LR, #4 ; LR &= 0x10, 判断当前异常是从主堆栈(MSP)还是进程堆栈(PSP)进入的。LR bit[4] = 1 则 PSP, LR bit[4] = 0 则 MSP, ITE EQ ; if - else 条件语句,CPSR->z MRSEQ R0, MSP ; R0 = MSP; MRSNE R0, PSP ; 三句合成一个C 语言语句: if (CPSR->z){R0 = MSP} else {R0 = PSP} LDR R1, [R0, #24] ; R1 = *(R0 + 0x24) R0地址偏移0x24,取里面的值赋给R1 CMP R1, #0 BEQ Error_Handler ; 参数检测,如果传入参数错误,跳转到Error_Handler函数, if (0 == R1) { Error_Handler() } CMP R1, #1 BEQ Function1 ; 如果传入参数为1,则执行Function1任务r, if (1 == R1) { Function1() } CMP R1, #2 BEQ Function2 ; 如果传入参数为2,则执行Function2任务, if (2 == R1) { Function2() } BX LR Function1 ; do something BX LR ; Function1() { 做一些什么事情,具体实现代码 } Function2 ; do something BX LR Error_Handler ; do something BX LR
我这里没有用到软中断,所以中断服务函数SVC_Handler 是空的:
extern void foo(void);
void call_SVC(int num)
{
__asm(
"mov r0,%[num]\n\t"
"svc 0\n\t"
:
: [num] "r" (num)
: "memory"
);
}
在使用软中断的过程中,需要注意以下几个问题:
异常号不能与硬件中断号冲突。
软中断不支持向量表实现,需要手动编写SVC Handler。
使用SVC指令切换到内核态将导致一定的性能开销,因此应尽量避免在中频率场景下频繁地使用软中断。
TST LR, #4是一条汇编指令,用于测试指定寄存器的某个位是否为1。在ARM Cortex-M系列MCU中的SVC Handler中,这条指令通常用于判断当前异常是从主堆栈(MSP)还是进程堆栈(PSP)进入的。
具体来说,当CPU进入异常模式时,会将当前的堆栈指针SP和状态寄存器SR等信息保存到内核堆栈中,并将LR寄存器保存为返回地址。在SVC Handler开始执行前,CPU会自动将LR寄存器的第4位(也就是Thumb模式的标志位)设置为1,然后跳转到SVC Handler代码的起始地址。
在SVC Handler中,我们可以通过检查LR寄存器的第4位,来确定CPU进入SVC Handler时使用的是哪个堆栈:如果LR寄存器的第4位为1,则说明使用的是进程堆栈PSP;否则,使用的是主堆栈MSP。
因此,TST LR, #4指令的作用是将LR寄存器与4进行按位与运算,结果为0则表示LR寄存器的第4位为0,也就是使用的是主堆栈MSP;结果为4则表示LR寄存器的第4位为1,使用的是进程堆栈PSP。由于该指令不会改变任何寄存器的值,因此通常和IT指令联合使用,根据条件执行不同的代码块。
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
ITE EQ是条件执行指令(Conditional Execution)中的一种,在ARM和Thumb-2指令集中均有支持。
该指令用于实现"if-then-else"结构,其中"EQ"表示条件为相等(Equal)。具体来说,该指令会先判断CPSR(当前程序状态寄存器)中的Z标志位(零标志位),如果该标志位被置位(值为1),则执行后续的指令段"T1";否则,执行指令段"T2"。整个指令的语法格式如下:
ITE EQ
T1 ; if (Z == 1)
T2 ; else
需要注意的是,ITE EQ指令必须与后续的指令段"T1"和"T2"一起使用,形成完整的条件代码块。指令段"T1"和"T2"的长度可以不同,但它们必须满足以下要求:
指令段"T1"必须以THEN或ELSE开头,表示在条件为真时执行的指令段;指令段"T2"必须以ELSE或ENDIF结尾,表示在条件为假时执行的指令段。
指令段"T1"和"T2"的长度不能超过256条指令,并且不能包含其他条件执行指令。
在ARM Cortex-M系列MCU中,由于省略了传统ARM架构中的条件执行指令,因此不支持ITE EQ指令。需要使用传统的if-then-else结构或者嵌套的条件代码块来实现类似的功能。
使用LDR指令取得堆栈指针中的值,即将R0的地址偏移0x24处的值取出来并赋给R1。
CMP R1, #0
BEQ Error_Handler
这段汇编代码的意思是:将R1寄存器的值与0进行比较,如果相等(Z标志位为1),则跳转到Error_Handler标签处执行后续代码。
具体解释如下:
CMP表示比较指令,执行完该指令后不会更新目标寄存器的值;
R1是要进行比较的寄存器,#0表示立即数0;
比较指令会根据目标寄存器和操作数之间的差异更新条件码(CPSR标志寄存器),如果目标寄存器的值等于操作数,则Z标志位(零标志位)被设置为1,表示结果为零(即两个数相等)。如果Z标志位为1,则说明R1的值为0,跳转到Error_Handler标签处执行后续代码。
BEQ表示“等于则跳转”指令,即如果Z标志位为1,则跳转到Error_Handler标签处。
这段汇编代码的意思是:跳转到LR寄存器中保存的地址处执行代码。
具体解释如下:
BX表示跳转指令,可以用于跳转到另一个程序或子程序中执行。LR寄存器是链接寄存器,用于保留函数调用前的返回地址。因此,跳转到LR寄存器中保存的地址处,就实现了函数调用后返回到调用函数的下一条指令。
在ARM处理器中,跳转指令有多种形式,以BX为例,它有两种格式:BX Rm和BX{cond} Rm。其中,Rm为要跳转到的寄存器;{cond}为条件码,表示跳转的条件。如果没有指定{cond},则默认为无条件跳转。
在这里,BX LR指令相当于无条件跳转到LR寄存器中保存的地址处执行代码,也就是返回到调用函数的下一条指令。
Function1
; do something
BX LR
这段汇编代码的意思是:执行Function1函数中的代码,然后跳转到LR寄存器中保存的地址处继续执行后续代码。中断返回
具体解释如下:
Function1是一个函数标签(类似于C语言中的函数名),表示后面的指令是该函数内的代码,具体内容可能是一些数据处理、逻辑运算、I/O操作等。
在执行完Function1函数中的代码之后,执行BX LR指令,实现返回到LR寄存器中保存的地址处继续执行后续代码。也就是说,Function1函数执行完毕后,会返回到调用该函数的下一条指令处继续执行。这里的BX LR相当于结束了当前函数的执行,并返回到调用该函数的位置继续执行后续代码。
__asm( ; 在C 语言里面嵌入了汇编语言
"mov r0,%[num]\n\t" ;R0 = num, \n 换行 \t 回车
"svc 0\n\t" ; 触发SVC 中断(异常)
:
: [num] "r" (num)
: "memory"
);
这段嵌入式汇编代码的意思是:将一个变量num存储到R0寄存器中,并触发系统调用(svc)。
具体解释如下:
__asm表示嵌入式汇编的起始标识,括号中为汇编指令的字符串表示形式。在这里,使用了多条汇编指令,通过"\n\t"实现分隔。
mov指令表示将操作数([num]所对应的变量num)移动到目标寄存器(R0)中。"[num]“是操作数约束,表示将变量num的值作为操作数传递给mov指令。”%[num]"表示操作数名字,是标识符的形式,和C代码中的变量名是一致的。
svc指令是软中断指令,用于在用户态(用户程序)与内核态(操作系统内核)之间转换。当用户需要访问系统资源时,不能直接执行特权指令(例如读写I/O设备、申请系统资源等),而需要通过系统调用方式向操作系统发出请求。系统调用的实现方式就是通过触发软中断来进入内核态执行特权代码,完成相应的操作。
最后的冒号表示输出操作数列表(没有输出操作数),空格后面的冒号表示输入操作数列表,这里只有一个输入操作数[num],表示将变量num的值作为输入操作数传递给汇编代码。"memory"表示内存约束,指示编译器不要优化与内存相关的代码。
因此,这段汇编代码的作用是将变量num的值存储到R0寄存器中,并触发系统调用,向操作系统发出请求。具体的请求内容需要根据实际需求来确定。
这段汇编代码的意思是:将一个变量num存储到R0寄存器中。
具体解释如下:
mov指令表示将操作数移动到目标寄存器中。在这里,"[num]“是操作数约束,表示将变量num的值作为操作数传递给mov指令。”%[num]"表示操作数名字,是标识符的形式,和C语言中的变量名是一致的。
R0寄存器是ARM处理器中的一种通用寄存器,用于存储操作数或函数返回值。因此,"mov r0,%[num]"表示将变量num的值存储到R0寄存器中,具体实现方式是将变量num的值复制到R0寄存器中。
该段汇编代码可能用于嵌入式系统的底层编程中,需要直接操作寄存器以实现某些功能。为确保寄存器被正确地操作,汇编代码需要指定操作数和寄存器名称等信息,通过[]和%[]的形式完成操作数约束。
在嵌入式汇编的代码中,冒号":"是约束符号,用于指定输入、输出、和被修改的程序状态,以及使用的寄存器。它的作用相当于C语言中函数的参数列表。
冒号的使用方法如下:
单冒号":":用于指示操作数的类型和位置,例如:
mov %[dest], %[src] : [dest] “=r” (dest) : [src] “r” (src);
在这里,冒号前面的部分:“mov %[dest], %[src]” 是汇编指令;
冒号后面的部分:" [dest] “=r” (dest) : [src] “r” (src)" 是操作数约束,表示将输入的变量src寄存器中的值作为源操作数传递给mov指令,将目标寄存器中的值存储到输出变量dest中。
双冒号"::":用于指示被修改的程序状态和使用的寄存器,例如:
asm volatile("mov r0, #0x04; mov r1, #0x0a; ldr r2, [r1]; add r2, r2, #1; str r2, [r1] ":“r0”, “r1”, “r2”);
在这里,冒号前面的部分:“mov r0, #0x04; mov r1, #0x0a; ldr r2, [r1]; add r2, r2, #1; str r2, [r1]” 是汇编指令;
冒号后面的部分:“r0”, “r1”, “r2” 是使用的寄存器约束。
冒号在嵌入式汇编中是非常重要的,它可以让编译器对汇编代码进行正确的解析和优化。因此,在编写嵌入式汇编代码时,必须严格遵守冒号的使用规则。
分号";"是汇编语言中的注释符号,用于标识注释内容。在一行汇编代码中,从分号开始到行末的所有内容都被视为注释,不会被汇编器处理和编译。
例如:
mov r0, #0x1 ; 将1写入R0寄存器
在这个例子中,注释内容“将1写入R0寄存器”不会被编译器处理,只起到了解释和说明代码作用的作用。分号";"在程序中使用非常广泛,可以帮助开发人员更好的理解代码的目的,提高代码可读性。
这是一种汇编代码中的操作数约束写法,表示将变量num的值作为源操作数传递到汇编指令中,并在执行中使用"r"寄存器来存储该操作数。
具体解释如下:
冒号":"用于分隔不同的操作数约束信息。
" [num] " 表示操作数的名称或标识符,这里是变量num。
“r” 表示寄存器的类型或约束,这里是使用"r"寄存器来存储该操作数。"r"代表“register”,即可通用寄存器。
“(num)” 表示操作数的值,采用了内联汇编语言风格,即使用括号将操作数值括起来,并放在约束字符串的末尾。这里表示将变量num的值传递给R寄存器来执行指令的操作。
操作数约束是嵌入式汇编中非常重要的一部分,它定义了汇编指令所需的输入和输出参数,以及如何将它们传递给指令。正确的操作数约束对指令的正确性、安全性和可维护性都有很大的影响。
": "memory"是一种特殊的操作数约束,它表示在执行当前的嵌入式汇编代码时,需要使用内存屏障(memory barrier)来确保对内存操作的同步和一致性。
内存屏障是一种硬件机制,用于控制不同线程或进程之间对内存操作的顺序和可见性。当CPU遇到内存屏障指令时,会立即停止所有正在进行的内存操作,并等待所有先前的内存操作完成后再继续执行下一条指令,以此来确保内存操作的一致性。
在嵌入式系统中,经常需要对多个并发线程或中断服务程序访问同一块共享内存进行管理和同步,此时就需要使用内存屏障来保证内存操作的有序性和正确性。": "memory"操作数约束在某些汇编指令中可以通过向CPU发出内存屏障指令的方式来满足这种需求。
例如,在Linux内核中,通常使用带有": "memory"操作数约束的汇编指令来实现对临界区的加锁和解锁操作,保证多个线程对共享资源的访问顺序和正确性。这种内存屏障的使用可以有效地提高系统的可靠性和稳定性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。