STM32F103 GPIO 端口

Posted

技术标签:

【中文标题】STM32F103 GPIO 端口【英文标题】:STM32F103 GPIO Ports 【发布时间】:2020-04-20 02:42:43 【问题描述】:

我有一个 STM32F103C8 MCU,我想在没有 Cube MX 的情况下控制 GPIO 寄存器。 MCU 有一个嵌入式 LED,我想控制它。我目前正在使用 CubeMX 和 IAR 软件,我使用以下代码将引脚作为输出(在 CubeMX 中):

HAL_GPIO_TogglePin(Ld2_GPIO_Port,Ld2_Pin); 
HAL_Delay(1000); 

这可行,但我想在没有 Cube 和 HAL 库的情况下这样做;我想直接编辑注册文件。

【问题讨论】:

【参考方案1】:

很高兴知道如何在没有固定库的情况下使用裸机,或者能够通读这些库并了解使用它们的目的。

这会使端口 C 引脚 13 闪烁,您通常可以在 stm32 蓝色药丸板上找到用户引导的位置。您可以从这里和 STM32F103C8 的文档中找出答案。

flash.s

.thumb

.thumb_func
.global _start
_start:
stacktop: .word 0x20001000
.word reset
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop
.word loop

.thumb_func
reset:
    bl notmain
    b loop
.thumb_func
loop:   b .

.thumb_func
.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.thumb_func
.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

so.c

void PUT32 ( unsigned int, unsigned int );
unsigned int GET32 ( unsigned int );

#define GPIOCBASE 0x40011000
#define RCCBASE 0x40021000

#define STK_CSR 0xE000E010
#define STK_RVR 0xE000E014
#define STK_CVR 0xE000E018
#define STK_MASK 0x00FFFFFF

static int delay ( unsigned int n )

    unsigned int ra;

    while(n--)
    
        while(1)
        
            ra=GET32(STK_CSR);
            if(ra&(1<<16)) break;
        
    
    return(0);


int notmain ( void )

    unsigned int ra;
    unsigned int rx;

    ra=GET32(RCCBASE+0x18);
    ra|=1<<4; //enable port c
    PUT32(RCCBASE+0x18,ra);
    //config
    ra=GET32(GPIOCBASE+0x04);
    ra&=~(3<<20);   //PC13
    ra|=1<<20;      //PC13
    ra&=~(3<<22);   //PC13
    ra|=0<<22;      //PC13
    PUT32(GPIOCBASE+0x04,ra);

    PUT32(STK_CSR,4);
    PUT32(STK_RVR,1000000-1);
    PUT32(STK_CVR,0x00000000);
    PUT32(STK_CSR,5);

    for(rx=0;;rx++)
    
        PUT32(GPIOCBASE+0x10,1<<(13+0));
        delay(50);
        PUT32(GPIOCBASE+0x10,1<<(13+16));
        delay(50);
    
    return(0);

flash.ld

MEMORY

    rom : ORIGIN = 0x08000000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000

SECTIONS

    .text :  *(.text*)  > rom
    .rodata :  *(.rodata*)  > rom
    .bss :  *(.bss*)  > ram

构建

arm-none-eabi-as --warn --fatal-warnings flash.s -o flash.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mthumb -c so.c -o so.o
arm-none-eabi-ld -o so.elf -T flash.ld flash.o so.o
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy so.elf so.bin -O binary

PUT32/GET32 是 IMO 强烈推荐的一种抽象风格,拥有数十年的经验,它比 volatile 指针或更糟糕的是当前 FAD 的滥用联合事物有很多好处。不是一个库,而是显示不需要任何库的代码,只需要提供的文件。

大多数 mcus 需要先启用外围设备的时钟,然后才能与外围设备通信。你可以看到一个 RCC 寄存器的读-修改-写。

大多数 MCU 的 GPIO 引脚重置为输入,因此您需要将一个设置为输出以驱动/闪烁 LED。即使在 STM32 世界中,但肯定在不同品牌/系列中,GPIO(和其他)外设预计不会相同甚至不兼容,因此您必须参考该部分的文档,它将展示如何使引脚成为输出。读-修改-写而不是写是个好主意,但是由于您完全控制芯片,如果您愿意,您可以只写,稍后再试。

该芯片有一个很好的寄存器,允许我们在一次写入中更改一个或多个但不一定是所有 GPIO 输出的输出状态,无需读取-修改-写入。所以我可以在不影响其他GPIOC管脚状态的情况下设置或清除GPIOC的13号管脚。

一些 cortex-ms 有一个 systick 计时器,例如,并非所有的 cortex-m3 都必须有一个,这通常取决于芯片人员,而某些内核可能没有选项。该芯片确实如此,您可以使用它。在这个例子中,定时器设置为每 100 万个时钟翻转一次,延迟函数在返回之前等待 N 次翻转。所以 LED 状态变化之间有 50,000,000 个时钟。由于此代码从复位开始运行而不会干扰时钟或其他系统,因此在 LED 状态更改之间使用了 50/8 = 6.25 秒的内部 HSI 8MHz 时钟。 systick 非常易于使用,但请记住,它是 24 位计数器而不是 32,所以如果你想现在做 vs 然后你必须屏蔽它。

我不记得是不是向上计数器

经过 = (现在 - 当时) & 0x00FFFFFF;

或向下

经过 = (当时 - 现在) & 0x00FFFFFF;

(现在 = GET32(systick 计数寄存器地址))

systick 计时器在 arm 文档中,而不是在芯片文档中,尽管有时 ST 会生产自己的版本,但您肯定想要 arm 一个,然后可能是 st 一个。 infocenter.arm.com(你必须放弃一个电子邮件地址,或者你可以谷歌有时你很幸运,有人会在某个地方非法发布它们)这个芯片会告诉你它使用的是 cortex-m3,所以找到 cortex 的技术参考手册-m3 因为你会发现它是基于架构 armv7-m 所以在架构下找到 armv7-m 文档,在这些文档之间你会看到向量表是如何工作的,systick 计时器及其地址等。

检查向量表

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000041    stmdaeq r0, r0, r6
 8000008:   08000047    stmdaeq r0, r0, r1, r2, r6
 800000c:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000010:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000014:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000018:   08000047    stmdaeq r0, r0, r1, r2, r6
 800001c:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000020:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000024:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000028:   08000047    stmdaeq r0, r0, r1, r2, r6
 800002c:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000030:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000034:   08000047    stmdaeq r0, r0, r1, r2, r6
 8000038:   08000047    stmdaeq r0, r0, r1, r2, r6
 800003c:   08000047    stmdaeq r0, r0, r1, r2, r6

08000040 <reset>:
 8000040:   f000 f806   bl  8000050 <notmain>
 8000044:   e7ff        b.n 8000046 <loop>

08000046 <loop>:
 8000046:   e7fe        b.n 8000046 <loop>

向量表的入口点代码以我们希望在复位时放入堆栈指针的值开始,然后是向量表,它是处理程序的地址 ORRed 和 1(不是有时在文档中很容易找到)。这些地址的反汇编是因为我使用反汇编程序查看它们不是向量表中的实际指令,它是向量表。该工具正在尽最大努力反汇编所有内容,如果您查看输出的其余部分,它还会反汇编 ascii 表和其他也不是代码的东西。

.data 在此示例中不受支持,需要做更多工作。

我建议如果/当你开始工作时,你检查 HAL 库源代码,看看当你挖掘有时臃肿或可怕的代码层时,你最终会得到相同的核心寄存器,他们可能会选择始终配置所有的 gpio 寄存器,例如,速度和上拉/下拉,关闭备用功能等。或不。上面知道它正在退出重置和系统状态,因此对于某些外围设备不会达到这些长度,您可以弹出该外围设备的重置并将其置于已知状态,而不是尝试创建一个预期的库它处于任何状态并尝试从该状态进行配置。 YMMV。

知道如何在这个级别上工作以及如何使用库是很好的专业知识。一个 MCU 芯片供应商通常会有两个库,当然对于像这样的旧部件,当前库产品和旧库产品,当一个新库出现以保持新鲜和竞争力(看起来)时,最旧的库将不再支持有时你有当前的和以前的。取决于供应商,取决于部件,取决于他们管理软件产品的方式(他们的 IDE 和其他工具也是如此)。

大多数 stm32 部件,尤其是蓝色药丸和其他您可以获得的板不需要花哨的 IDE 进行编程,但有时需要外部硬件,除非您获得 NUCLEO 或 Discovery 板,然后您至少有足够的资源来对部件进行编程使用不附属于 ST 的免费软件。使用 nucleo,它是 mbed 样式,您只需将 .bin 文件复制到虚拟 USB 驱动器,然后开发板负责对开发 MCU 进行编程。

【讨论】:

这个答案太过分了。 OP 没有问 - 如何创建我自己的启动和 libc op 可以忽略该部分并查看 C 代码。如果您想替换延迟功能,那么您想从启动(控制时钟速率)控制事物,不能在中间启动和/或需要知道预先设置了什么才能知道如何撤消它或使用它来两者都设置了gpio并进行时间测量。还不如提供一个完整的几十行代码示例,它可以 100% 完成。提供了 100% 的代码,并且从大约 15 年前到现在的任何版本的 gcc/binutils 都应该构建它。导航混合动力车太难在这里展示,也没有那么简单。 OP 确实询问了如何在没有库的情况下做到这一点,启动通常是环境的一部分,包括来自芯片供应商的库,它是一个交钥匙包。从库中解放出来意味着从包中解放出来,您会发现它们紧密相连且不可分离的实现。 没有。 OP 询问如何在没有 STM HAL 库的情况下做到这一点。 STM 提供的启动代码不是它的一部分,也不是它的一部分。顺便说一句,您的创业公司不符合 C 标准。 其次,CMSIS 标头不是库的一部分,即使您想成为 100% 裸机,也强烈建议使用这些定义。 #defines 不是库【参考方案2】:

通过寄存器使用 GPIO 非常简单。您不必编写自己的启动(作为@old_timer 答案)。只需两步

您将需要 STM 提供的 CMSIS 标头,其中包含数据类型声明和人类可读的#defines 以及 reference manual

    启用 GPIO 端口时钟。 示例:RCC -&gt; APB2ENR |= RCC_APB2ENR_IOPAEN; 使用 CRL/CRH GPIO 寄存器配置管脚
#define GPIO_OUTPUT_2MHz (0b10)
#define GPIO_OUTPUT_PUSH_PULL (0 << 2)
  GPIOA -> CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
  GPIOA -> CRL |= GPIO_OUTPUT_2MHz | GPIO_OUTPUT_PUSH_PULL; 
    处理输出
  /* to toggle */
  GPIOA -> ODR ^= (1 << pinNummer);
  /* to set */
  GPIOA -> BSRR = (1 << pinNummer);
  /* to reset */
  GPIOA -> BRR = (1 << pinNummer);
  //or
  GPIOA -> BSRR = (1 << (pinNummer + 16));

【讨论】:

以上是关于STM32F103 GPIO 端口的主要内容,如果未能解决你的问题,请参考以下文章

STM32F103 实例应用(3.1)——GPIO(增加深度)

STM32F103 实例应用(3.1)——GPIO(增加深度)

STM32F103(二十)DAC(贼详细)

STM32F103(十八)ADC总结(贼详细)

stm32F103寄存器点灯

STM32F103(二十一)DMA(超详细的~)