【方法】STM32新建一个空白的s文件,并完全用汇编实现一个函数

  • Post author:
  • Post category:其他


【main.c】

#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"

uint16_t test(void);

int main(void)
{
  uint16_t value;
  
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  
  value = test();
  printf("value=0x%04x\n", value);
  
  return 0;
}

新建一个test.s文件,其中的汇编代码如下:

	area |.text|, code, readonly
	export test
test proc
		mov r0, #0xabcd ; 指定函数返回值
		bx lr ; 返回
	endp
	end

注意,除了test proc要顶格写外,其他行前必须要加一个tab,否则编译不通过。

test函数执行后,返回值为0xabcd。

函数传入的参数保存在r0, r1, r2等寄存器中,可以直接在汇编代码里面使用。在下面的汇编代码中,用mla指令计算r0*r1+r2的值,然后存入r0作为函数的返回值。

	area |.text|, code, readonly
	export test
test proc
		mla r0, r0, r1, r2 ; r0=r0*r1+r2
		bx lr ; 返回
	endp
	end
#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"

uint32_t test(uint32_t a, uint32_t b, uint32_t c);

int main(void)
{
  uint32_t value;
  
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  
  value = test(8, 6, 10);
  printf("value=%u\n", value);
  
  return 0;
}

test函数返回值为8*6+10=58。

我们用汇编语言操作GPIOA->BSRR寄存器,同时翻转PA4和PA5两个IO口,看看STM32F1的GPIO最快翻转到多少MHz的频率。

#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"

uint32_t test(uint32_t a, uint32_t b, uint32_t c);
void io_test(void);

static void io_init(void)
{
  GPIO_InitTypeDef gpio;
  
  __HAL_RCC_GPIOA_CLK_ENABLE();
  
  gpio.Mode = GPIO_MODE_OUTPUT_PP;
  gpio.Pin = GPIO_PIN_4 | GPIO_PIN_5;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);
}

int main(void)
{
  uint32_t value;
  
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  
  value = test(8, 6, 10);
  printf("value=%u\n", value);
  
  io_init();
  io_test();
  
  return 0;
}
	area |.text|, code, readonly
	export test
	export io_test
test proc
		mla r0, r0, r1, r2 ; r0=r0*r1+r2
		bx lr ; 返回
	endp
		
io_test proc
		ldr r0, = 0x40010810 ; GPIOA->BSRR
		mov r1, #0x30 ; GPIO_BSRR_BS4 | GPIO_BSRR_BS5
		mov r2, #0x300000 ; GPIO_BSRR_BR4 | GPIO_BSRR_BR5
loop
		str r1, [r0] ; PA4和PA5设为高电平
		str r2, [r0] ; PA4和PA5设为低电平
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		str r1, [r0]
		str r2, [r0]
		b loop
	endp
	
	align ; 避免added x bytes of padding的警告
	end

最后,示波器测出来,翻转频率刚好是18MHz。

io_test函数目前是在Flash里面执行的,我们把它复制到SRAM里面执行试试看:

(这里顺便教大家怎样在STM32单片机里面,把Flash中的函数复制到内部SRAM执行)

#include <stdio.h>
#include <stm32f1xx.h>
#include <string.h>
#include "common.h"

typedef void (*Runnable)(void);

uint32_t test(uint32_t a, uint32_t b, uint32_t c);
void io_test(void);

static void io_init(void)
{
  GPIO_InitTypeDef gpio;
  
  __HAL_RCC_GPIOA_CLK_ENABLE();
  
  gpio.Mode = GPIO_MODE_OUTPUT_PP;
  gpio.Pin = GPIO_PIN_4 | GPIO_PIN_5;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);
}

int main(void)
{
  uint8_t buffer[256] __attribute__((aligned)); // 必须保证buffer的地址能被4整除
  uint32_t value;
  Runnable run;
  
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  
  value = test(8, 6, 10);
  printf("value=%u\n", value);
  
  memcpy(buffer, (char *)io_test - 1, sizeof(buffer)); // 将io_test函数的代码复制到buffer数组中
  run = (Runnable)(buffer + 1); // 函数指针指向buffer数组
  
  io_init();
  run(); // 执行buffer数组里面的代码
  
  return 0;
}

test.s里面的io_test proc前要加上align,保证io_test函数的地址能被4整除:

	align ; 保证函数的地址能被4整除
io_test proc

运行程序,示波器里面仍然还是18MHz的波形,速度并没有得到提升,而且时钟频率没有之前稳定了。

上面的汇编代码有一个问题,就是在汇编函数里面操作了r0, r1, r2寄存器,却没有提前保存这三个寄存器的原有内容,导致函数退出后,main函数里面的一些局部变量的值被篡改。

比如run()函数执行一次后,会发现函数指针run的指向改变了,再执行run()就会Hard Fault。

解决办法就是用push指令压栈,函数返回时不用bx lr,用pop出栈指令。

test proc

push {r0-r2, lr} ; 保存寄存器的原有内容,以及函数的返回地址(lr就是r14寄存器)



pop {r0-r2, pc} ; 恢复寄存器的原有内容,并返回(pc就是r15寄存器)

endp



版权声明:本文为ZLK1214原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。