嵌入式开发(8)C与汇编混合运用

发布于:2022-12-06 ⋅ 阅读:(823) ⋅ 点赞:(0)

今天遇到了个问题,研究了一下。
当我初始化完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状态代码,不过还是有些限制:
只能用于v6T2v6 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) 普通处理中不应使用
CMSIS-Core内在函数和编译器相关的内在函数的相似性示例

在这里插入图片描述

问题解决

段错误的原因还是没有解决,谁搞定告诉我一声哈哈哈。
源码在这,0积分。
https://download.csdn.net/download/hyk687/86722931

总结

若要将软件移植到另外一个编译器上,且两者的习语识别特性不同,则由于代码使用的是标准的C语法,仍可以将代码编译成功,只是所生成指令的效率会比使用习语识别时要低。


网站公告

今日签到

点亮在社区的每一天
去签到