今天遇到了个问题,研究了一下。
当我初始化完LED,想尝试去使用位带操作来操控GPIO脚,从而控制高低电平去控制LED的亮灭。
参考正点原子的代码,sys.h代码如下,都是一些宏定义的位带操作。不了解位带操作,详情看看这:嵌入式开发(5)位带(位段)操作。再进行编译的时候sys.c文件一堆报错。
在 mdk 下,内联汇编仅支持 ARM 汇编语言,不支持 Thumb 或者 Thumb-2
汇编语言,但内嵌汇编器支持 Thumb 和 Thumb-2
汇编指令,STM32 的 core cortex-M3是 Thumb-2指令,所以采用内嵌汇编的方式进行汇编调用。
在 armcc V5
的版本中,如下形式的内联汇编函数不能够通过编译:
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
改为 armcc V6 的版本中,需要将形式修改为:
void MSR_MSP(u32 addr)
{
__ASM volatile("MSR MSP, r0"); //set Main Stack value
__ASM volatile("BX r14");
}
全部改为一下格式,编译通过
#include "sys.h"
void WFI_SET(void)
{
__ASM volatile("wfi");
}
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
__ASM volatile("BX LR");
}
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
__ASM volatile("BX LR");
}
void MSR_MSP(u32 addr)
{
__ASM volatile("MSR MSP, r0"); //set Main Stack value
__ASM volatile("BX r14");
}
然而当我下载到开发板,出现了硬件错误/段错误,程序跑飞的情况。以下是main的主函数
#include "main.h"
int main(void)
{
Delay_init();
Led_Init();
USART_Config();
while(1)
{
printf("FUNCTION:%s LINE:%d \n\r", FUNCTION, LINE);
LED0=!LED0;
printf("FUNCTION:%s LINE:%d \n\r", FUNCTION, LINE);
delay_ms(300);
//LED0=1;
delay_ms(300);
}
}
改成这样使用库函数就可以正常亮灯。
while(1)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_5);
delay_ms(300);
GPIO_SetBits(GPIOA,GPIO_Pin_5);
delay_ms(300);
}
sys.h文件是一些基本的位带操作,详情看“嵌入式开发(5)位带操作”。
1、串口调试。
主函数的printf
(“FUNCTION:%s LINE:%d \n\r”, FUNCTION, LINE);我是采取串口观察得方法看他到底跳转在哪里卡住。因为这里代码量比较少的程序时候用,当你检查复杂程序(代码量多)的时候建议直接debug,打断点得方法来测试。
2、keil或者IAR等等软件里的debug
调试工具。
使用调试标记
在很多商家提供的一些例程当中,你会发现很多#ifdef / #endif #define
#if defined(MCU_IS_STM32) && !defined(MCU_IS_STM32H7XX)
#ifndef SPI_BaudRatePrescaler_2
#define SPI_BaudRatePrescaler_2 ((uint32_t)0x00000000U)
#define SPI_BaudRatePrescaler_4 ((uint32_t)0x00000008U)
#endif
#elif defined(MCU_IS_STM32H7XX)
#ifndef SPI_BAUDRATEPRESCALER_2
#define SPI_BAUDRATEPRESCALER_2 (0x00000000U)
#endif
#elif defined(MCU_IS_NXP_LPC11XX) || defined(MCU_IS_NXP_LPC17XX)
#ifndef SPI_BaudRatePrescaler_2
#define SPI_BaudRatePrescaler_2 ((uint32_t)0x00000002U)
#define SPI_BaudRatePrescaler_4 ((uint32_t)0x00000004U)
#endif
#elif defined (MCU_IS_NXP_MK27_28) || defined (MCU_IS_NXP_MKV58)
#ifndef SPI_BaudRatePrescaler_2
#define SPI_BaudRatePrescaler_2 ((uint32_t)0x00000000U)
#define SPI_BaudRatePrescaler_4 ((uint32_t)0x00000001U)
#endif
在源代码中使用相应的辅助代码,debug 完成之后 隐藏 这些代码的一种调试策略。借助 #define/#ifdef/#endif
三个编译指令。相当于使用 #define
定义宏,#ifdef / #endif
之间包含调试代码。
学会调试也是一项必备技能。
回到sys.c代码这边,思考为什么会跑进void HardFault_Handler(void),想想出现HardFault_Handler的原因,要么是栈堆没有设置分配好,要么是中断问题,再好好理解这几个代码。
//使用WFI指令进入休眠
//THUMB指令不支持汇编内联
//采用如下方法实现执行汇编指令WFI
void WFI_SET(void)
{
__ASM volatile("wfi");
}
//关闭所中断(但是不包括fault和NMI中断)
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
__ASM volatile("BX LR");
}
//开启所有中断
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
__ASM volatile("BX LR");
}
//设置栈顶地址
//addr:栈顶地址
void MSR_MSP(u32 addr)
{
__ASM volatile("MSR MSP, r0"); //set Main Stack value
__ASM volatile("BX r14");
}
首先外面先了解下
C/C++语言和汇编语言的混合程序设计
多数工具链使用汇编语言作为启动代码,这样会给钱操作等低级控制带来更大的灵活性。
参考权威指南
1、从汇编中调用C语言
再汇编代码内,可以调用外部的C函数,例如,下面的C函数具有四个输入参数并返回32位结果;
int my_add_c(int x1 ,int x2, int x3, int x4)
{
return (x1 + x2 + x3 + x4);
}
对于ARM工具链,可以利用下面的代码调用该函数:
MOVS R0, #0x1; 第1个参数(x1)
MOVS R1, #0x2; 第2个参数(x2)
MOVS R2, #0x3; 第3个参数(x3)
MOVS R3, #0x4; 第4个参数(x4)
IMPORT my_add_c
BL_my_add_c;调用my_add_c函数,结果位于R0中
若该代码是C程序文件中的汇编代码,且是用ARM工具链
中的嵌入式汇编或内联汇编实现,则应该使用一cpp关键字代替IMPORT。
MOVS R0, #0x1; 第1个参数(x1)
MOVS R1, #0x2; 第2个参数(x2)
MOVS R2, #0x3; 第3个参数(x3)
MOVS R3, #0x4; 第4个参数(x4)
BL_cpp(my_add_c) ;调用my_add_c函数,结果位于RO中
对于GNU工具链
,可以使用global
,使某个标号对其他文件可见。
BL_global(my_add_c) ;调用my_add_c函数,结果位于RO中
2、从C语言中调用汇编函数
下面将整个过程反过来,用汇编实现My_Add函数,并在C代码中对其进行调用。
My_Add汇编函数可以如下实现:
EXPORT My_Add
My_Add FUNCTION
ADDS RO, RO, Rl
ADDS RO, RO, R2
ADDS RO, RO, R3
BX LR;返回结果位于RO中
ENDFUNC
在C程序代码内,需要利用extern声明My_Add函数:
extern int My_Add(int xl, int x2, int x3, int x4);
int y;
y= My_Add(l, 2, 3, 4); //调用My_Add函数
若汇编代码需要访问C代码中的一些数据变量,则还可以使用IMPORT关键字(用于 ARM工具链)或“.global气用于GNU工具链)。
了解汇编与C语言的相互调用后回到前面问题的重点
3、嵌入汇编
ARM工具链中有一个名为嵌入汇编 的特性,若利用其在C文件中实现汇编函数/子程序,则需要在函数声明前增加_asm
关键字。
定义:_asm
关键字用于调用内联汇编程序,并且可在 C 或 C++ 语句合法时出现。 _asm
是 __ASM
的同义词。
例如,将4个寄存器相加的函数可以如下实现:
_asm int My Add(int xl, int x2, int x3, int x4)
{
ADDS RO, RO, Rl
ADDS RO, RO, R2
ADDS RO, RO, R3
BX LR,返回结果位于RO
}
现在可以理解到,在c语言中调用void MSR_MSP(u32 addr),对栈顶地址进行设置
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
嵌入汇编代码中,可以利用_cpp关键字引人数据符号或地址值。例如:
_asm void function_A(void)
{
PUSH { R0 - R2, LR}
BL _cpp(LCD_clr_screen),调用C函数- 第一种方法
LDR R0, = _cpp(ιpos_x) ;得到C变量的地址
LDR R0, [R0]
LR R1, = _cpp(&pos_y) ;得到j c变量的地址
LR R1, [R1]
LDR R2, = _cpp(LCD_pixel_set) ;引人函数的地址
BLX R2;调用C函数
POP { RO - R2, PC}
}
4、内联汇编
ARM
C
编译器中还有内联汇编特性,不过早期的ARMC
编译器不支持Thumb
状态的 内联汇编。 从ARMC编译器5.01和Keil MDK-ARM 4. 60开始,内联汇编已经支持了Thumb状态代码,不过还是有些限制:
只能用于v6T2
、v6 M和v7/v7-M
内核。
不支持TBB、TBH、CBZ和CBNZ
指令。
与之前的版本相同,不允许SETEND
等一些系统指令。例如,可以在C代码中使用内联汇编:
int qaddB ( int i,int j)
{
int res;
_asm
{
QADDB res,i,j
}
return res;
}
之前我们会发现这段嵌入汇编函数的确编译不通过
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
或者内联汇编也不通过
void MSR_MSP(u32 addr)
{
__asm ("MSR MSP, r0"); //set Main Stack value
__asm ("BX r14");
}
由于版本的不支持,改成内嵌汇编就可以实现
void MSR_MSP(u32 addr)
{
__ASM volatile("MSR MSP, r0"); //set Main Stack value
__ASM volatile("BX r14");
}
5、内嵌汇编
Linux
操作系统内核代码绝大部分使用C语言编写,只有一小部分使用汇编语言编写,例如与特定体系结构相关的代码和对性能影响很大的代码。GCC
提供了内嵌汇编的功能,可以在C代码中直接内嵌汇编语言语句,大大方便了程序设计。
asm volatile(“hlt”); "asm"表示后面的代码为内嵌汇编,"asm
"是"asm"的别名。"volatile"表示编译器不要优化代码,后面的指令 保留原样,"volatile"是它的别名。括号里面是汇编指令。
基本行内汇编很容易理解,一般是按照下面的格式:asm(“statements”);
在“asm”后面有时也会加上“volatile”表示编译器不要优化代码,后面的指令保留原样__asm__ volatile(“hlt”);如果有很多行汇编,则每一行后要加上“\n\t
” :
asm( "pushl %eax\n\t"
"movl $0,%eax\n\t"
"popl %eax");
或者我们也可以分成几行来写,如:
asm("movl %eax,%ebx");
asm("xorl %ebx,%edx");
asm("movl $0,_booga);
通常使用汇编语句最方便的方式是把它们放在一个宏内,而宏语句需要在一行上定义,因此使用反斜杠\将这些语句连成一行,所以上述语句如果在宏中定义的话就是:
asm( "pushl %eax; \
movl $0,%eax; \
popl %eax");
拓展:
语法如下:
asm(
(asm code)
:(output)
:(input)
:(clobber)
)
asm(“汇编语句模块” :输出寄存器
:输入寄存器
:会被修改的寄存器);
注意:
栈对齐。 若汇编函数需要调用C函数,就应该确保当前选择的技指针指向了双字对齐的地址( 如 0x20002000、 0x20002008和 Ox20002010等),这是 EABI标准规定的。 符合EABI的C编译器生成的程序代码会假定枝指针指向了双字对齐的位置,若汇编代码未调用任何C函数(直接或间接),则不必严格遵循这个要求。
若开发的汇编函数需要被C代码调用,则需要确保“被调用者保存寄存器”的内容不会改变。 若使用了这些寄存器,则需要将它们的内容压入栈中并在函数结束时恢复。
寄存器 | 函数调用行为 |
---|---|
RO~R3,Rl2,SO~Sl5 | 调用者保存寄存器,这些寄存器的内容可以被函数修改。汇编代码如果在之后的操作中使用这些数值,就需要保存这些寄存器 |
R4~Rll.Sl6~S31 | 调用者保存寄存器,函数必须保存这些寄存器的内容。如果函数要在处理中使用这些寄存器,就需要将它们保存到校中,并且在函数返回前将它们恢复 |
R14(LR) | 如果函数中包含BL/BLX指令,那么链接寄存器的内容就需要被保存到楼中,这是因为LR在BL\BLX执行时会被覆盖 |
Rl3CSP) ,Rl5CPC) | 普通处理中不应使用 |
问题解决
段错误的原因还是没有解决,谁搞定告诉我一声哈哈哈。
源码在这,0积分。
https://download.csdn.net/download/hyk687/86722931
总结
若要将软件移植到另外一个编译器上,且两者的习语识别特性不同,则由于代码使用的是标准的C语法,仍可以将代码编译成功,只是所生成指令的效率会比使用习语识别时要低。