【方法】STM32用函数指针指向一个数组,然后执行这个数组里面存放的机器代码

本文介绍了如何在STM32F103C8单片机中,使用汇编语言编写GPIO函数,并将其转换为函数指针在C语言中调用,以模拟18MHz SPI信号。实验还探讨了不同存储区(SRAM与Flash)执行速度差异及其影响。
摘要由CSDN通过智能技术生成

在STM32里面,其实是可以把一个数组转换成函数指针来执行的。
以STM32F103C8单片机为例,首先我们新建一个test.s文件,用汇编语言编写一个GPIO模拟产生18MHz SPI信号的函数。
函数原理:CS片选为PA1,CLK时钟为PA2,DATA数据为PA3。通过写GPIOA->BSRR寄存器控制I/O口输出,GPIOA->BSRR寄存器的低16位决定PA0~15是否输出高电平,高16位决定PA0~15是否输出低电平。mov指令给CPU寄存器(指r0~r15这些寄存器)的低16位赋值,同时将高16位清零。movt指令只给CPU寄存器的高16位赋值。str指令负责给外设寄存器(如GPIOA->BSRR这样的寄存器)赋值,外设寄存器的地址和值CPU寄存器提供。

	area |.text|, code, readonly
	export io_test
		align ; 保证函数的地址能被4整除
io_test proc
		push {r0-r12, lr} ; 保存寄存器的原有内容, 以及函数的返回地址 (lr就是r14寄存器)
		                  ; 以免篡改原函数里面用寄存器存储的局部变量
		
		mov r0, #0x00 ; r0寄存器存放CLK=0, DATA=0的操作
		movt r0, #0x0c
		mov r1, #0x08 ; r1寄存器存放CLK=0, DATA=1的操作
		movt r1, #0x04
		mov r2, #0x00 ; r2寄存器存放CS=0, CLK=0, DATA=0的操作
		movt r2, #0x0e
		mov r4, #0x04 ; r4寄存器存放CLK=1的操作
		mov r5, #0x02 ; r5寄存器存放CS=1, CLK=0的操作
		movt r5, #0x04
		mov r6, #0x0810 ; r6寄存器保存GPIOA->BSRR寄存器的地址
		movt r6, #0x4001
		
		; 产生SPI时序
		str r2, [r6]
		str r4, [r6]
		str r1, [r6]
		str r4, [r6]
		str r0, [r6]
		str r4, [r6]
		str r0, [r6]
		str r4, [r6]
		str r1, [r6]
		str r4, [r6]
		str r0, [r6]
		str r4, [r6]
		str r5, [r6]
		
		pop {r0-r12, pc} ; 恢复寄存器的原有内容并返回 (pc就是r15寄存器)
	endp
	end

(写好这个汇编函数后,若要在C语言里面调用,先声明一下void io_test(void);,然后执行io_test();。不声明直接调用也可以,只是编译器会有警告)

编译Keil工程,打开Listings\test.lst文件,可以看到汇编程序的编译结果:

ARM Macro Assembler    Page 1 


    1 00000000                 area             |.text|, code, readonly
    2 00000000                 export           io_test
    3 00000000                 align                        ; 保证函数的地址能?
                                                            ?整除
    4 00000000         io_test proc
    5 00000000 E92D 5FFF       push             {r0-r12, lr} ; 保存寄存器的原有
                                                            内容, 以及函数的返?
                                                            氐刂?(lr就是r14寄?
                                                            嫫?
    6 00000004         ; 以免篡改原函数里面用寄存器存储的局部变量
    7 00000004         
    8 00000004 F04F 0000       mov              r0, #0x00   ; r0寄存器存放CLK=0
                                                            , DATA=0的操作
    9 00000008 F2C0 000C       movt             r0, #0x0c
   10 0000000C F04F 0108       mov              r1, #0x08   ; r1寄存器存放CLK=0
                                                            , DATA=1的操作
   11 00000010 F2C0 0104       movt             r1, #0x04
   12 00000014 F04F 0200       mov              r2, #0x00   ; r2寄存器存放CS=0,
                                                             CLK=0, DATA=0的操?
                                                            ?
   13 00000018 F2C0 020E       movt             r2, #0x0e
   14 0000001C F04F 0404       mov              r4, #0x04   ; r4寄存器存放CLK=1
                                                            的操作
   15 00000020 F04F 0502       mov              r5, #0x02   ; r5寄存器存放CS=1,
                                                             CLK=0的操作
   16 00000024 F2C0 0504       movt             r5, #0x04
   17 00000028 F44F 6601       mov              r6, #0x0810 ; r6寄存器保存GPIOA
                                                            ->BSRR寄存器的地址
   18 0000002C F2C4 0601       movt             r6, #0x4001
   19 00000030         
   20 00000030         ; 产生SPI时序
   21 00000030 6032            str              r2, [r6]
   22 00000032 6034            str              r4, [r6]
   23 00000034 6031            str              r1, [r6]
   24 00000036 6034            str              r4, [r6]
   25 00000038 6030            str              r0, [r6]
   26 0000003A 6034            str              r4, [r6]
   27 0000003C 6030            str              r0, [r6]
   28 0000003E 6034            str              r4, [r6]
   29 00000040 6031            str              r1, [r6]
   30 00000042 6034            str              r4, [r6]
   31 00000044 6030            str              r0, [r6]
   32 00000046 6034            str              r4, [r6]
   33 00000048 6035            str              r5, [r6]
   34 0000004A         
   35 0000004A E8BD 9FFF       pop              {r0-r12, pc} ; 恢复寄存器的原有
                                                            内容并返回 (pc就是r
                                                            15寄存器)
   36 0000004E                 endp
   37 0000004E                 end
Command Line: --debug --xref --diag_suppress=9931 --cpu=Cortex-M3 --apcs=interw
ork --depend=.\objects\test.d -o.\objects\test.o -I.\RTE\_Target_1 -IC:\Keil_v5
\ARM\PACK\Keil\STM32F1xx_DFP\2.3.0\Device\Include -IC:\Keil_v5\ARM\CMSIS\Includ
e --predefine="__UVISION_VERSION SETA 527" --predefine="STM32F10X_MD SETA 1" --
list=.\listings\test.lst test.s



ARM Macro Assembler    Page 1 Alphabetic symbol ordering
Relocatable symbols

.text 00000000

Symbol: .text
   Definitions
      At line 1 in file test.s
   Uses
      None
Comment: .text unused
io_test 00000000

Symbol: io_test
   Definitions
      At line 4 in file test.s
   Uses
      At line 2 in file test.s
Comment: io_test used once
2 symbols
334 symbols in table

把编译出来的机器代码放到C语言数组里面,转换成函数指针并执行:(系统时钟和串口的初始化代码已省略)

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

typedef void (*Runnable)(void);

uint16_t buffer[] = {
  0xe92d, 0x5fff, // push {r0-r12, lr}
  0xf04f, 0x0000, // mov r0, #0x00
  0xf2c0, 0x000c, // movt r0, #0x0c
  0xf04f, 0x0108, // mov r1, #0x08
  0xf2c0, 0x0104, // movt r1, #0x04
  0xf04f, 0x0200, // mov r2, #0x00
  0xf2c0, 0x020e, // movt r2, #0x0e
  0xf04f, 0x0404, // mov r4, #0x04
  0xf04f, 0x0502, // mov r5, #0x02
  0xf2c0, 0x0504, // movt r5, #0x04
  0xf44f, 0x6601, // mov r6, #0x0810
  0xf2c4, 0x0601, // movt r6, #0x4001
  0x6032, // str r2, [r6]
  0x6034, // str r4, [r6]
  0x6031, // str r1, [r6]
  0x6034, // str r4, [r6]
  0x6030, // str r0, [r6]
  0x6034, // str r4, [r6]
  0x6030, // str r0, [r6]
  0x6034, // str r4, [r6]
  0x6031, // str r1, [r6]
  0x6034, // str r4, [r6]
  0x6030, // str r0, [r6]
  0x6034, // str r4, [r6]
  0x6035, // str r5, [r6]
  0xe8bd, 0x9fff // pop {r0-r12, pc}
};

static void io_init(void)
{
  GPIO_InitTypeDef gpio;
  
  __HAL_RCC_GPIOA_CLK_ENABLE();
  
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
  gpio.Mode = GPIO_MODE_OUTPUT_PP;
  gpio.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
  gpio.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &gpio);
}

int main(void)
{
  Runnable run;
  
  HAL_Init();
  
  clock_init();
  usart_init(115200);
  printf("STM32F103C8 FuncTest\n");
  printf("SystemCoreClock=%u\n", SystemCoreClock);
  
  io_init();
  run = (Runnable)((uintptr_t)buffer + 1);
  while (1)
  {
    run();
    HAL_Delay(1);
  }
}

注意,buffer的地址要加1后才能赋给函数指针run。不能把(uintptr_t)buffer + 1写成buffer + 1或者&buffer[1],否则加的是sizeof(uint16_t)=2而不是1。

程序运行结果:

(1)buffer变量不加const,在SRAM里面执行函数:
(注意:全局变量加const表示把此变量存放到Flash里面,不加const表示把此变量存放到SRAM里面。对于局部变量,无论是否加const,都是存放到SRAM里面。)

(2)buffer变量加const,在Flash里面执行函数:

这说明,在Flash里面执行函数,比在SRAM里面执行快。
在Flash里面我们可以得到频率为18.2MHz,占空比为47.27%的SPI信号,但是在SRAM里面我们只能得到不规则频率的SPI信号。


从F103的datasheet里面可以看到,GPIO的最大翻转频率就是18MHz。

知道了怎样在数组里面执行代码,我们就有办法在STM32单片机上模拟操作系统运行应用程序。
我们把STM32函数代码以文件的形式保存到W25Q128或者SD卡中,命名为exe文件。然后用FatFs的f_open函数读取出来,放入SRAM的数组里面。用一个函数指针指向这个数组,最后执行。这样,我们就实现了STM32单片机动态运行保存在磁盘上的exe程序。
我们甚至还可以运行FreeRTOS操作系统,读取多个exe文件到SRAM里面,然后用xTaskCreate函数以创建任务的方式运行。
甚至同一个exe文件可以用xTaskCreate多次创建任务。

你好,以下是利用 DMA 通道发送一个数组的示例代码: ``` #include "stm32xxxx.h" #define ARRAY_SIZE 10 // 数组大小 void DMA_Config(void) { /* 使能 DMA1 时钟 */ RCC->AHBENR |= RCC_AHBENR_DMA1EN; /* 配置 DMA1 通道 x */ DMA1_ChannelX->CCR &= ~DMA_CCR_EN; // 关闭 DMA1 通道 x DMA1_ChannelX->CCR |= DMA_CCR_DIR; // 设置为内存到外设模式 DMA1_ChannelX->CCR |= DMA_CCR_MINC; // 设置为增量模式 DMA1_ChannelX->CCR |= DMA_CCR_MSIZE_0; // 存储器数据宽度为 16 位 DMA1_ChannelX->CCR |= DMA_CCR_PSIZE_0; // 外设数据宽度为 16 位 DMA1_ChannelX->CCR |= DMA_CCR_TCIE; // 开启传输完成中断 DMA1_ChannelX->CNDTR = ARRAY_SIZE; // 设置转移数据量大小 DMA1_ChannelX->CMAR = (uint32_t) array; // 存储器地址 DMA1_ChannelX->CPAR = (uint32_t) &USARTx->TDR; // 外设地址 DMA1_ChannelX->CCR |= DMA_CCR_EN; // 开启 DMA1 通道 x } void USART_Send_DMA(void) { /* 配置 DMA1 通道 x */ DMA_Config(); /* 开始 USART 对应 GPIO 端口的时钟 */ RCC->APB2ENR |= RCC_APB2ENR_USART1EN; /* 配置 USART 为 9600, 8N1 */ USARTx->BRR = USART_DIV_SAMPLING16(SystemCoreClock, 9600); USARTx->CR1 |= USART_CR1_UE; USARTx->CR1 &= ~(USART_CR1_M | USART_CR1_PCE); USARTx->CR2 &= ~USART_CR2_STOP; USARTx->CR3 |= USART_CR3_DMAT; /* 启动 DMA 传输 */ DMA1_ChannelX->CCR &= ~DMA_CCR_EN; DMA1_ChannelX->CNDTR = ARRAY_SIZE; DMA1_ChannelX->CCR |= DMA_CCR_EN; /* 循环检查 DMA 传输是否完成 */ while (!(DMA1->ISR & DMA_ISR_TCIFX)); DMA1->IFCR |= DMA_IFCR_CTCIFX; /* 关闭 DMA1 */ DMA1_ChannelX->CCR &= ~DMA_CCR_EN; /* 关闭 USART1 */ USARTx->CR1 &= ~USART_CR1_TE; USARTx->CR1 &= ~USART_CR1_UE; } ``` 请注意,本代码仅供示例使用,您需要根据实际需求,修改对应的寄存器和参数。 如果您对代码有任何疑问,请随时和我交流。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值