Cortex-M3
最近写一个程序,需要在用户模式下关中断,但ARM 7的体系结构决定了中断必须在特权模式下才可以更改,所以想到使用ARM的软中断来实现关中断和开中断。
使用软中断,首先要有硬件指令的支持,ARM有条指令是SWI。
SWI 指令的格式为:
SWI {条件} 24 位的立即数
SWI 指令用于产生软件中断,以便用户程序能调用操作系统的系统例程。操作系统在 SWI 的异常处理程序中提供相应的系统服务,指令中 24 位的立即数指定用户程序调用系统例程的类型,相关参数通过通用寄存器传递,当指令中 24 位的立即数被忽略时,用户程序调用系统例程的类型由通用寄存器 R0 的内容决定,同时,参数通过其他通用寄存器传递。
指令示例:
SWI 0x02 ;该指令调用操作系统编号位02的系统例程。
在keil MDK中,关键字__svc可以产生硬件SWI指令,使得处理器能响应软件中断。关键字__svc、keil MDK帮助文件中这样描述:
__svc 关键字声明超级用户调用 (SVC) 函数,该函数最多使用四个类似于整数的参数,并通过 value_in_regs 结构最多返回四个结果。
__svc 是一个函数限定符。它影响函数的类型。
语法
__svc(int svc_num) return-type function-name([argument-list]);
其中:
svc_num 是在 SVC 指令中使用的立即值。
它是一个表达式,其计算结果为以下范围内的整数:
• 在 ARM 指令中为 0 到 224–1 (24 位值)
• 在 16 位 Thumb 指令中为 0-255 (8 位值)。
要在keil MDK中使用软件中断,要做好两件事:第一件,更改启动文件,编写软件中断的汇编入口。在这个汇编入口中主要根据软件中断命令号进行相应的函数跳转;第二件,编写相应命令号的C语言服务函数。下面举例怎么样用软件中断实现开中断和关中断:
第一步:更改启动代码
keil MDK自带的启动代码有类似下面的语句:
Vectors LDR PC, Reset_Addr
LDR PC, Undef_Addr
LDR PC, SWI_Addr
LDR PC, PAbt_Addr
LDR PC, DAbt_Addr
蓝色语句是程序复位后要执行的第一条指令,即复位异常入口;而红色语句就是执行一个软件中断指令后,要跳转到的软件中断异常入口。通过语句"SWI_Addr DCD SWI_Handler"进行中转,软件中断会跳转到标号为SWI_Handler的语句处,该处即处理软件中断的命令号、源代码如下:
EXPORT SWI_Handler
extern EnableIrqFunc ;使能中断函数名,用C语言实现
extern DisableIrqFunc ;禁止中断函数名,用C语言实现
SWI_Handler
STMFD SP!, {R0,R12,LR} ;入栈
LDR R0, [LR,#-4] ;取指令
BIC R0,R0,#0xFF000000 ;取软件中断命令号
CMP R0,#0 ;和0比较,因为我的使能中断用了软件中断命令0,禁止中断使用了软件中断命令1
BLEQ EnableIrqFunc ;为零调用使能中断函数
BLNE DisableIrqFunc ;不为零调用禁止中断函数
LDMFD SP!,{R0,R12,PC}^ ;出栈
第二部:编写相应命令号的C语言服务函数。
声明软件中断:
__svc(0x00) void EnableIrq(void); //使能中断
__svc(0x01) void DisableIrq(void); //禁止中断
编写服务函数:
void DisableIrqFunc(void)
{
int temp;
__asm
{
MRS temp,SPSR
ORR temp,temp,#0x80
MSR SPSR_c,temp
}
}
void EnableIrqFunc(void)
{
int temp;
__asm
{
MRS temp,SPSR
BIC temp,temp,#0x80
MSR SPSR_c,temp
}
}
到此,使能和禁止中断的软中断就结束了,下面看一下执行过程。
在程序中,如果想关中断,只需使用:DisableIrq();
MDK编译器在执行这句函数时,自动用软中断指令代替,即: SWI 0x01
ARM执行这条软件中断指令后,发生软件中断异常,程序跳转到软件中断异常服务函数处,即汇编代码标号为SWI_Handler处,在这里判断软件中断命令号是0x01,然后执行 BLNE DisableIrqFunc 语句,调用禁止中断函数,实现关中断。
文章来源:博客园
一、Cortex M3的GPIO口特性
在介绍GPIO口功能前,有必要先说明一下M3的结构框图,这样能够更好理解总线结构和GPIO所处的位置。
从图中可以看出,GPIO口都是接在APB总线上的,而且M3具有两个AHB到APB桥,GPIO则直接接在AHB矩阵上,这样可以减少CPU和DMA控制器之间的竞争冲入,获得较高性能。APB总线桥配置为写缓冲区,使得CPU或DMA控制器可直接操作APB外设,而无需等待总线写操作完成。
M3数字I/O功能:
高速GPIO口,其寄存器被移到外设AHB总线,可以字节,半字和字寻址。
位电平置位和清零寄存器允许单指令置位和清零一个端口的任意位。
所有GPIO口寄存器支持M3位带操作。
整个端口值可以用一条指令写入。
GPIO口寄存器可由GPDMA控制器进行访问,可以进行DMA数据操作,使之与DMA请求同步。
单个I/O口方向可以控制。
所有I/O口在复位后默认作为上拉输入。(Why?因为微控制器连接了很多设备,如果复位后作为输出,则由于控制器电平状态不定,可能会导致外围设备产生动作,从而产生不利影响,故复位后一半都作为输入状态。)
M3可产生中断的数字端口:
PORT0 和 PORT2 端口的每个引脚都可以提供中断功能;
每个端口上的中断可被编程为上升沿、下降沿或边沿产生中断;
边沿检测是异步的,因此可以在没有时钟的情况下(例如掉电模式)操作。使用这种特性,就无需电平触发中断;
可掉电唤醒;
寄存器为软件提供挂起的上升沿中断、挂起的下降沿中断和整个挂起的 GPIO中断;
GPIO0 和 GPIO2 中断与外部中断 3 事件共用相同的 NVIC 通道。
二、GPIO口的寄存器描述
Cortex M3引脚的寄存器描述:
PINMODEx表示引脚模式选择寄存器,在使用前要配置好。
引脚模式选择寄存器位
PINMODEx
|
功能
|
复位后的值
|
00
|
引脚使能上拉电阻
|
00
|
01
|
中继模式
|
|
10
|
引脚无片内上拉或下拉电阻
|
|
11
|
引脚使能下拉电阻
|
中继模式说明:当引脚处于逻辑高电平,中继模式能使能上拉电阻;当引脚处于逻辑低电平时,中继模式会使能下拉电阻,这样当引脚配置为输入且没有外部驱动时,能够保持上一个已知状态。
PINSELx:功能选择寄存器。用来设定PORT引脚的功能,当PINSELx各位为0时,才用作GPIO端口。
FIOxDIR:GPIO口方向寄存器,单独控制每个端口管脚的方向,可作为字节(8位),半字(16位)和字长(32位)的数据进行访问。
FIOxMASK:屏蔽寄存器。任何写、读的操作只在该寄存器对应位为“0“时才有效。
FIOxPIN:管脚值寄存器。只要管脚不配置为ADC,其他所有方式都可以从该位读出端口当前的实际状态。注:如果读FIOPIN寄存器,那么不管物理引脚的状态如何,在 FIOMASK寄存器中被“1”屏蔽的位将始终读出0。
FIOxSET:输出引脚的状态。写 1 使相应的端口引脚产生高电平。写 0 没有影响。读该寄存器,返回端口输出寄存器的当前内容。只可以更改 FIOMASK 中为 0 的位,即非屏蔽位。
FIOxCLR:控制输出引脚的状态。写 1 使相应的端口引脚产生低电平。写 0 没有影响。只可以更改 FIOMASK 中为 0 的位,即非屏蔽位。
2.1 GPIO端口方向寄存器FIOxDIR(FIO0DIR??FIO4DIR- 0x2009 C000??0x2009 C080)
当引脚被配置为 GPIO功能时,该寄存器可用来控制引脚的方向。务必根据引脚功能来设置每个引脚的方向。
注:GPIO引脚 P0.29和P0.30 与USB D+/-引脚共用,并且具有相同的方向。如果FP0DIR位29或位30在FIO0DIR寄存器中被配置为零,则P0.29 和P0.30都为输入。如果FP0DIR位29和位30被配置为1,则P0.29和P0.30都为输出。
高速GPIO端口方向寄存器位描述
位
|
符号
|
值
|
描述
|
复位值
|
31:0(字长数据)
|
FP0DIR
FP1DIR
FP2DIR
FP3DIR
FP4DIR
|
0
1
|
控制的引脚为输入引脚
控制的引脚为输出引脚
|
0
|
字和半字的操作基本类似,只是可以通过8位寄存器或者16位的寄存器分别控制方向而已。
2.2 GPIO端口输出设置寄存器FIOxSET(FIO0SET??FIO7SET - 0x2009 C018??0x2009 C098)
当引脚在输出模式中被配置为 GPIO 时,该寄存器在端口引脚产生高电平输出。向该寄存器的某些位写入“1”时,对应的引脚产生高电平。写入“0”无效。如果需要引脚输出低电平或第二种功能,那么写 1 到 FIOxSET 的相应位无效。 读FIOxSET 寄存器返回该寄存器的值,该值由前一次对 FIOxSET 和 FIOxCLR(或前面提到的 FIOxPIN)的写操作确定,它不反映任何外部环境对 I/O引脚的影响。 通过 FIOxSET 寄存器访问的端口引脚受到 FIOxMASK 寄存器相应位的限制。
高速GPIO端口输出设置寄存器位描述
位
|
符号
|
值
|
描述
|
复位值
|
31:0(字长数据)
|
FP0SET
FP1SET
FP2SET
FP3SET
FP4SET
|
0
1
|
控制的引脚输出不改变
控制的引脚输出被设为高电平
|
0
|
2.3 GPIO端口输出清零寄存器FIOxCLR(FIO0CLR??FIO07CLR - 0x2009 C01C??0x2009 C09C)
当引脚在输出模式中被配置为 GPIO 时,该寄存器在端口引脚产生低电平输出。向某些位写入“1”会使相应的引脚产生低电平,同时清零 FIOxSET 寄存器的相应位。写入“0”无效。如果引脚被配置为输入或其它功能,那么写 FIOxCLR 对引脚没有影响。 通过FIOxCLR寄存器访问的端口引脚受到FIOxMASK寄存器相应位的限制.
高速GPIO端口输出清零寄存器位描述
位
|
符号
|
值
|
描述
|
复位值
|
31:0(字长数据)
|
FP0CLR
FP1CLR
FP2CLR
FP3CLR
FP4CLR
|
0
1
|
控制的引脚输出不改变
控制的引脚输出被设为低电平
|
0
|
2.4 GPIO端口引脚值寄存器FIOxPIN(FIO0PIN??FIO7PIN- 0x2009 C014??0x2009 C094)
该寄存器提供了端口引脚的值,可配置这些值来执行仅为数字的功能。该寄存器将给出引脚的当前逻辑值,而不管引脚是否配置为输入或输出,或作为 GPIO或作为其它可选的数字功能。
例如,特殊的端口引脚可能具有 GPIO输入、GPIO 输出、UART 接收和 PWM 输出等可选功能。无论该引脚配置成何种功能,都可以从相应的 FIOxPIN 寄存器中读出其当前的逻辑状态。 如果引脚配置为模拟功能,当选择了模拟配置时,引脚状态不能被读出。将引脚选择用作A/D输入会断开与引脚数字部分的连接。在这种情况下,从 FIOxPIN 寄存器中读出的引脚值无效。
写 FIOxPIN 寄存器时, FIOxPIN 寄存器的值会保存到端口输出寄存器,而无需使用 FIOxSET和 FIOxCLR寄存器来获得整个写入值。由于这种特性影响整个端口,因此在应用中时要小心。 通过 FIOxPIN 寄存器访问的端口引脚受到 FIOxMASK 寄存器相应位的限制。 只有在屏蔽寄存器中用0 屏蔽的引脚与高速 GPIO 端口引脚值寄存器的当前内容相互关联。
高速GPIO端口引脚值寄存器位描述
位
|
符号
|
值
|
描述
|
复位值
|
31:0(字长数据)
|
FP0VAL
FP1VAL
FP2VAL
FP3VAL
FP4VAL
|
0
1
|
控制的引脚输出设为低电平
控制的引脚输出设为高电平
|
0
|
2.5 高速GPIO端口屏蔽寄存器FIOxMASK (FIO0MASK??FIO7MASK - 0x2009 C010??0x2009 C090)
该寄存器用来屏蔽某些端口引脚,被屏蔽的引脚将无法通过 FIOxPIN、 FIOxSET 或 FIOxCLR寄存器写访问。当读FIOxPIN 寄存器时,屏蔽寄存器还将过滤相应端口的内容。
通过读或写访问,该寄存器中为“0”的位使能相应物理引脚的访问。如果该寄存器中的位为“1”,则相应位将不会通过写访问改变,并且读操作时将不会在更新的 FIOxPIN 寄存器中反映出来。
高速GPIO端口引脚值寄存器位描述
位
|
符号
|
值
|
描述
|
复位值
|
31:0(字长数据)
|
FP0VAL
FP1VAL
FP2VAL
FP3VAL
FP4VAL
|
0
1
|
控制的引脚输出设为低电平
控制的引脚输出设为高电平 |
0
|
对于常见的ARM处理器,它们的GPIO口基本上可以配置为输入模式、输出模式、开漏或推挽模式。
开漏输出与推挽输出的区别:
推挽输出:推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。可以输出高,低电平,连接数字器件,正常的拉出/灌入电流为4mA,短时间极限值可以达到40mA,但不是每个引脚都能输出这么多
开漏输出:输出端相当于三极管的集电极。要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。
开漏电路概念中提到的“漏”就是指MOS FET的漏极。同理,开集电路中的“集”就是指三极管的集电极。开漏电路就是指以MOS FET的漏极为输出的电路。一般的用法是会在漏极外部的电路添加上拉电阻。完整的开漏电路应该由开漏器件和开漏上拉电阻组成。
组成开漏形式的电路有以下几个特点:
1. 利用外部电路的驱动能力,减少IC内部的驱动。当IC内部MOSFET导通时,驱动电流是从外部的VCC流经R pull-up,MOSFET到GND。IC内部仅需很小的栅极驱动电流。如图1。
2. 可以将多个开漏输出的Pin,连接到一条线上。形成 “与逻辑” 关系。如图,当PIN_A、PIN_B、PIN_C任意一个变低后,开漏线上的逻辑就为0了。这也是I2C,SMBus等总线判断总线占用状态的原理。
3. 可以利用改变上拉电源的电压,改变传输电平。IC的逻辑电平由电源Vcc1决定,而输出高电平则由Vcc2决定。这样我们就可以用低电平逻辑控制输出高电平逻辑了。
4. 开漏Pin不连接外部的上拉电阻,则只能输出低电平(因此对于经典的51单片机的P0口而言,要想做输入输出功能必须加外部上拉电阻,否则无法输出高电平逻辑)。
问题集锦
一、准双向IO结构的特点是
1 输出结构类似 OC门,输出低电平时,内部NMOS导通,驱动能力较强(800uA);输出高电平靠内部上拉电阻,驱动能力弱(60uA)。
2 永远有内部电阻上拉,高电平输出电流能力很弱,所以即使IO口长时间短路到地也不会损坏IO口(同理,IO口低电平输出能力较强,作低电平输出时不能长时间短路到VCC)
3 作输入时,因为OC门有"线与"特性,必须把IO口设为高电平(所以按键多为共地接法)
4 作输出时,输出低电平可以推动LED(也是很弱的),输出高电平通常需要外接缓冲电路(所以LED多为共阳接法)
5 软件模拟 OC结构的总线反而比较方便-----例如 IIC总线
* OC门:三极管的叫集电极开路,场效应管的叫漏极开路,简称开漏输出。具备"线与"能力,有0得0。
* 为什么设计成输出时高电平弱,低电平强----是考虑了当年流行的TTL器件输入
二、IO不同模式的区别?
简单的说:
“准双向IO口”在读前必须先用写指令置"1",才能读入;写则无须此步。
“真正的双向IO口”可直接读写。
“三态IO口”有高,低电平,高阻状态,高阻本人理解:相当此脚与内部电路断开。
文章来源:博客园
1、基本概念(CMSIS):
Cortex Micro-controller Software Interface Standard,微控制器软件接口标准。
2、CMSIS标准的文件结构:
a) core_cm.c (stdint.h)
b) system_.c (core_cm, system_)
c) startup_.s
其中core_cm.c以及core_cm中为内核外设访问层,其中定义了内核中的外设以及一些内核的访问及控制函数。
startup_.s文件是系统的启动文件,其包括堆和栈的初始化配置、中断向量表的配置以及将程序引导到main()函数等功能。
system_和system_.c文件则是由ARM公司提供模版,各芯片制造商根据自己芯片的特点来编写的。
3、注解startup_.s文件
此文件主要完成三项工作:堆栈以及堆的初始化、定位中断向量表、调用Reset Handler
a) 堆栈以及堆的初始化
; Stack Configuration
; Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
Stack_Size EQU 0x00000200
AREA STACK, NOINIT, READWRITE, ALIGN=3
;指明8字节对齐(ALIGN=3)
Stack_Mem SPACE Stack_Size
__initial_sp ;此标号有一层隐含的意思那,就是在M3中堆栈是满递减堆栈,
;因为它指定了堆栈指针位于堆栈的高地址(在//Stack_Mem之后)
; Heap Configuration
; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
;
Heap_Size EQU 0x00000000
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
以上堆和栈的具体地址可以在工程编译后产生的*.map文件中看到。
b) 定位中断向量表
PRESERVE8 ;PRESERVE8指定了以下的代码位8字节对齐
THUMB ;THUMB指定了接下来的代码为THUMB指令集
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY ;此语句声明RESET数据段
EXPORT __Vectors ;导出向量表标号,EXPORT作用类似于C语言中的extern
__Vectors
DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WDT_IRQHandler ; 16: Watchdog Timer
DCD TIMER0_IRQHandler ; 17: Timer0
DCD TIMER1_IRQHandler ; 18: Timer1
DCD TIMER2_IRQHandler ; 19: Timer2
DCD TIMER3_IRQHandler ; 20: Timer3
DCD UART0_IRQHandler ; 21: UART0
;(……省略)
c) 调用Reset Handler
; Reset Handler
Reset_Handler
PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
注释:引导程序进入__main(此__main是C_Library中的函数,非main())
d) 堆栈以及堆的初始化行为
; User Initial Stack & Heap
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, = (Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
e) ARM m3 C是怎么调用startup.s文件的?
startup.s大部分内容不是被C调用的,而是在C程序之前运行的。m3上电之后会先从0地址处读取MSP,然后从0x4地址处读取复位向量,接着跳转到复位异常服务程序Reset_Handler(这些是m3内核自动做的,不需要编程)。如果你用MDK的话,在工程设置Debug下的run to main()上的勾去掉,然后进入调试,你会发现你运行的第一句就是Reset_Handler的第一句。
Reset_Handler就是在startup.s中定义的,它一般会对系统初始化,然后进入main函数。
向量表也是在startup.s中定义的,至于为什么程序编译之后向量表一定在0地址处,是因为向量表的前面会有类似AREA RESET, CODE, READONLY的声明。在MDK生成的分散加载文件中,RESET被设置在flash的0地址处,这样就规定了向量表的地址。
文章来源 : 博客园
keil MDK也是可以借助h-jtag进行单步调试,写出来与大家一起分享一下:
keil MDK编译器使用V4.01版本,下载地址:
http://www.embedinfo.com/down-list.asp?id=714 (需要注册一下)
h-jtag使用V1.0版本(请注意,一定要用V1.0或者以上版本才可以与mdk兼容),下载地址
http://www.hjtag.com/download/H-JTAG%20V1.0%20Preview.zip
分别安装MDK与h-jtag
两个软件都安装完成后,首先,需要运行H-JTAG 安装目录下的TOOLCONF.EXE 程序,对KEIL 安装目录下的TOOLS.INI 配置文件进行更新。如下图所示。点击Config 按钮,对TOOLS.INI 进行配置,然后退出。注意在点击Config之前,要退出keil编译器。
接下来,就可以在 KEIL 下对项目进行配置。首先,重新运行KEIL,并在KEIL 中打开一个项目,然
后点击Project -> Options for Target…菜单,接下来,会弹出下图所示的Options 配置窗口。
在上图所示的Options 配置窗口中,选择Debug 设置,点击标签下的如图所示下拉按键,就可以看到H-JTAG ARM 调试驱动。如下图所示:
选中使用h-jtag硬件仿真,点击 OK 按钮,回到KEIL 的主窗口,KEIL 的设置就完成了。
然后编译程序,生成hex的目标文件,利用h-jtag的flash下载工具将目标文件下载到硬件。
下载完毕后,使用Crtl+F5进入单步调试界面。