在 ARM STM32G030K6 上启用 GPIO

Enable GPIO on ARM STM32G030K6

我正在尝试通过直接操作寄存器(不依赖于 CubeMX)来学习对 STM32G030K6 进行编程。我的程序旨在将引脚 PA5 设置为高电平。

// Target:  STM32G030K6T6
// Goal:    Set pin PA5 to high

#include "stm32g0xx.h"          // Device header

int main(void)
{
    RCC->IOPENR |= 1;           // Enable GPIOA Clock
    GPIOA->MODER |= 0x400;      // Set GPIOA MODE5 to a general purpose output
    GPIOA->ODR = 0x20;          // Set PA5 high

    while(1)
    {

    }
}

程序完全不影响PA5。 我已经使用 CubeMX 闪烁程序成功测试了设置,证明这不是硬件问题。

STM32G030K6: Data Sheet

STM32G030K6: Reference Manual

到目前为止我从你那里了解到的是你bought/acquired把这部分放在了分线板上。已施加电源和接地,添加了一个 LED 和电阻器,并连接了一个 stlink。可以使用 CubeMX 并使用 Kiel 使其工作。

所以我制作了很多分线板,将 LED 等放在板上,因为我厌倦了单独接线。我用过的部件需要确保 VDD 和 VDDA 已连接,但你的是同一个引脚,请检查。 VDD 和 VSS 毫无疑问,如果你有它的工作。 NRST 拉高了很好的措施,虽然我认为不需要,因为有一个内部拉高,但 BOOT0 确实需要拉低,但这是一个 STM32G,你已经指出 SWCLK 和 BOOT0 共享相同的引脚。遗憾的是,ST 正在放弃片上引导加载程序,或者至少它被工厂禁用了

ST production value: 0xDFFF E1AA

Bit 24 nBOOT_SEL

0: BOOT0 signal is defined by BOOT0 pin value (legacy mode)

1: BOOT0 signal is defined by nBOOT0 option bit

因此,新部件 BOOT0 不是您可以依赖的进入引导加载程序并使用 uart 解决方案将代码下载到闪存中的东西,也不能在执行此级别时使用它来让自己畅通无阻工作。

所以 stlink 已连接,您说 Kiel 可以与该部件通话,所以理论上一切都很好,不是问题。

我手边没有 Kiel,但每个人都可以获得 gnu 交叉编译器或从源代码构建一个。

apt-get install binutils-arm-linux-gnueabi  gcc-arm-linux-gnueabi

下面的代码不关心 arm-non-eabi- vs arm-linux-gnueabi- 交叉编译器的变化它独立于这些差异,它只需要编译器汇编器和链接器。

现在这可能会再次与某些其他 SO 用户展开个人意见战。在噪音中工作。我特别避免 CMSIS,我已经看到了实现,你应该检查它,因为现在你不想将这种风险添加到你的代码中,删除它并在以后根据需要添加它。这是我的风格它专门控制用于访问的指令,一切都是基于大量经验即使你没有看到,设计用于reader有很高的成功机会。让它成为你自己的 if/when 你让它工作 and/or 我真正的目标是旁注,它可以帮助你检查你正在用你自己的工具构建的二进制文件,以消除常见的陷阱。

这不仅仅是让 main() 中的 C 代码正确以使 bare-metal 代码正常工作的情况,您需要从重置开始的整个过程都是正确的。

基于 Flash 的版本:

flash.s

.cpu cortex-m0
.thumb

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

.thumb_func
reset:
    bl notmain
    b hang
.thumb_func
hang:   b .

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

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

.thumb_func
.globl dummy
dummy:
    bx lr

notmain.c

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

#define RCC_BASE        0x40021000
#define RCC_IOPENR      (RCC_BASE+0x34)

#define GPIOA_BASE      0x50000000
#define GPIOA_MODER     (GPIOA_BASE+0x00)
#define GPIOA_OTYPER    (GPIOA_BASE+0x04)
#define GPIOA_BSRR      (GPIOA_BASE+0x18)

#define DCOUNT 2000000

int notmain ( void )
{
    unsigned int ra;
    unsigned int rx;

    ra=GET32(RCC_IOPENR);
    ra|=1<<0; //enable port a
    PUT32(RCC_IOPENR,ra);

    ra=GET32(GPIOA_MODER);
    ra&=~(3<<(5<<1)); //clear bits 10,11
    ra|= (1<<(5<<1)); //set bit 10
    PUT32(GPIOA_MODER,ra);

    ra=GET32(GPIOA_OTYPER);
    ra&=~(1<<5); //clear bit 5
    PUT32(GPIOA_OTYPER,ra);

    for(rx=0;;rx++)
    {
        PUT32(GPIOA_BSRR, (1<<(5+ 0)) );
        for(ra=0;ra<DCOUNT;ra++) dummy(ra);
        PUT32(GPIOA_BSRR, (1<<(5+16)) );
        for(ra=0;ra<DCOUNT;ra++) dummy(ra);
    }

    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 -mcpu=cortex-m0 flash.s -o flash.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mcpu=cortex-m0 -mthumb -c notmain.c -o notmain.o
arm-none-eabi-ld -o notmain.elf -T flash.ld flash.o notmain.o
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf notmain.bin -O binary

同样,您可以将 arm-none-eabi 替换为 arm-linux-gnueabi,如果您 have/found 是 arm-linux-gnueabi。此代码不关心差异。

这里的重点是让处理器启动:

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000011    stmdaeq r0, {r0, r4}
 8000008:   08000017    stmdaeq r0, {r0, r1, r2, r4}
 800000c:   08000017    stmdaeq r0, {r0, r1, r2, r4}

08000010 <reset>:
 8000010:   f000 f808   bl  8000024 <notmain>
 8000014:   e7ff        b.n 8000016 <hang>

08000016 <hang>:
 8000016:   e7fe        b.n 8000016 <hang>

应用程序闪存从 ARM 内存中的 0x08000000 开始 space,在参考手册中称为 Main Flash Memory。根据引导设置,0x08000000 将在 0x00000000 处进行镜像,如 ARM 手册中所述,这是向量 table 所在的位置。第一个字是复位时加载到堆栈指针中的值,地址 0x00000004 处的字(将镜像到 0x08000004)是复位向量。

上面使用了反汇编程序,因此它试图将这些值作为指令进行反汇编 values/vectors 忽略 table 的反汇编。

假设我们可以获得将此二进制文件放入闪存中所需位置的工具,那么

08000000 <_start>:
 8000000:   20001000    value loaded into sp on reset
 8000004:   08000011    reset vector

重置向量是为该异常执行的代码的地址,其中 lsbit 设置为指示拇指模式,lsbit 被剥离,它不会进入 pc。所以这里的复位向量地址是0x08000010是正确的:

08000010 <reset>:
 8000010:   f000 f808   bl  8000024 <notmain>
 8000014:   e7ff        b.n 8000016 <hang>

并且可以按照这个到 notmain,C 入口点的名称并不重要,一些工具会添加额外的东西,它会看到标签 main(),已经很多年没见过了,但会继续这样做也证明一点没关系。

因此,如果将其放入主闪存中的 arm 地址 0x08000000,则该代码将启动并且 运行 直到 C 代码。

注意 sram 从 0x20000000 开始并且 RM 显示这部分有 32MBytes 的 sram 所以它至少有 0x1000 字节来覆盖这个项目有足够的额外空间。

 8000026:   481b        ldr r0, [pc, #108]  ; (8000094 <notmain+0x70>)
 8000028:   f7ff fff8   bl  800001c <GET32>
 800002c:   2101        movs    r1, #1
 800002e:   4301        orrs    r1, r0
 8000030:   4818        ldr r0, [pc, #96]   ; (8000094 <notmain+0x70>)
 8000032:   f7ff fff1   bl  8000018 <PUT32>
...
 8000094:   40021034    andmi   r1, r2, r4, lsr r0

无论是我编写的程序还是通过您的程序和 CMSIS 或 HAL headers,您应该会看到 0x40021034 以某种形式被使用。请注意,您的这部分是 cortex-m0+,因此它只有有限数量的 thumb2 扩展,请注意 bl 是两个独立的 16 条指令,可以 spaced 分开,但几乎总是成对出现,它们是两条指令,其余指令需要是 16 位,如果您在反汇编中看到 something.w 或 bl 以外的指令是 32 或 16*2 位,那么这可能是 thumb2 指令,但不会运行 在此处理器上,可能是您在构建此代码时使用的一些设置,您可以通过此工具链看到我有特定的从指令集的角度(架构 armv6-m)来看,m0 实际上与 m0+ 相同。您不希望 armv7-m 因为该芯片无法工作,armv7-m 中大约有 100 条左右的指令无法在基于 armv6-m 的芯片上工作。

io 使能寄存器中位的 orring 应该类似于从 0x40021034 读取 (ldr)、修改读取值和写入 (str) 到同一地址。

您发布的代码适用于其他 STM32 部件,因为它们中的许多部件将 MODER 寄存器(如果该部件使用那种 GPIO 外设)初始化为大多数输入引脚的零。这部分记录了大多数引脚重置为模拟模式的 0b11,好奇为什么但无论如何。

Reset value:

0xEBFF FFFF for port A

0xFFFF FFFF for other ports

所以你不能简单地设置两个位之一来改变模式,如果这些位开始为 0b00 然后设置一个可以把它变成 0b01,但是对于这部分你可以只清除位 11 或更好控制两个位而不依赖于复位状态,因此清除两个位并设置其中一个或清除一个并设置另一个

5<<1 表示 5 左移一个 0b101 从右移一个零得到 0b1010,它是一个 0xA,它是 10 这是一种直观的方式,可以看出我正在弄乱 PA5 并且数字 5 在那里, 但对于此寄存器引脚 5 模式设置是第 10 位和第 11 位。3 << (5<<1) 表示 3<<10,即第 10 位和第 11 位。波浪号表示反转整个事物,因此 00000C00 是 3<< 10 反转你得到的 FFFFF3FF 与现代值的 anded 将使第 10 位和第 11 位归零。现在用 00000400 或 1<<10 设置第 10 位。

我们希望输出至少现在是 push-pull 而非开漏,因此即使重置值已经是 push-pull,我还是将其清除以备不时之需。现在我通常不理会上拉或其他 gpio 设置寄存器,我为使用此 GPIO 外围设备的 STM32 部件弄乱了这两个 MODER 和 OTYPER(您会看到并非所有 STM32 部件都使用相同的 IP,STM32F103例如使用不同的,请查看。

因此,以某种方式确认 CMSIS 是否生成的代码与这些寄存器混淆。从文档 GPIOA 开始于 0x50000000。所以 0x50000000 和 0x50000004 寄存器。

因为这部分有一个 GPIO BSRR 注册它是一个很好的功能,现在就使用它,这样你就不会不小心弄乱其他引脚。

虚拟循环会消耗时间,因此在这种情况下,led 会闪烁,当你得到这个 运行ning 时,你必须根据处理器使用的时钟调整 DCOUNT 不会太快太慢了,刚刚好。使用外部函数以这种方式执行它不再是死代码( for(ra=0;ra

不,代码实际上并没有命中 return(0);一些编译器不那么聪明并且抱怨。 (有些人那么聪明,抱怨你无法到达那里,YMMV)

所有这些部分都需要到位才能有一半的机会发挥作用。它不仅仅是几行 C 代码。

使用 stlink kiel 工具很好,我希望有一种方法可以检查内存 space,您需要检查 0x08000000 并将其与该工具生成的二进制文件进行比较,希望有是一种检查工具输出以及查看它构建了什么的方法,使用 gnu 很容易做到。

您可以使用 openocd 而不是 kiel 从命令行加载和检查内容,形式为

openocd -f stlink.cfg -f target.cfg

然后在另一个 window

telnet localhost 4444

gdb 添加了更多未知数...

那么你可以使用

mdw 0x08000000 40 

在 telnet window 中查看主闪存中有什么,然后将其与二进制文件的可加载部分进行比较,以查看您的程序是否真的存在,如果您的程序实际上不存在则没有无论您对 C 代码做什么,它都不会闪烁。

有一些方法可以使用 openocd 来刷写部件,但它非常 vendor/part 具体,因为他们必须将这种功能添加到 openocd,而且你必须有正确的版本,从记忆中它是一些东西行

flash write_image erase notmain.elf

如果使用其中包含地址信息的“二进制文件”,如果您使用的是内存映像,则需要将地址放在该命令行上 0x08000000

一些 st 零件被锁定,或者说像一些蓝色药丸这样的电路板在这不起作用,原始零件我不知道我看到过锁定,您购买的零件看起来很松散,所以它们不应该被锁定.

如果您可以使用 openocd 和 gnu,那么您也可以尝试使用 sram,而无需最初支持 flash。

sram.s

.cpu cortex-m0
.thumb

.thumb_func
.global _start
_start:
    ldr r0,=0x20001000
    mov sp,r0
    bl notmain
    b .

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

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

.thumb_func
.globl dummy
dummy:
    bx lr

sram.ld

MEMORY
{
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > ram
    .rodata : { *(.rodata*) } > ram
    .bss : { *(.bss*) } > ram
}

由于这部分使用了vectortable,而接下来要介绍的是使用调试器在sram,volatile中放置和运行一个程序,所以当你reset/reboot 它丢失了,但它提供了一种无需让 flash 写入工作即可进行实验的方法。

我们将告诉调试器从 0x20000000 处开始执行,因此我们希望那里有一条指令而不是向量 table。

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 sram.s -o sram.o
arm-none-eabi-ld -o notmain.elf -T sram.ld sram.o notmain.o
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf notmain.bin -O binary

总是在 运行ning

之前检查新项目上的二进制文件
Disassembly of section .text:

20000000 <_start>:
20000000:   4804        ldr r0, [pc, #16]   ; (20000014 <dummy+0x2>)
20000002:   4685        mov sp, r0
20000004:   f000 f808   bl  20000018 <notmain>
20000008:   e7fe        b.n 20000008 <_start+0x8>

2000000a <PUT32>:
2000000a:   6001        str r1, [r0, #0]
2000000c:   4770        bx  lr

2000000e <GET32>:
2000000e:   6800        ldr r0, [r0, #0]
20000010:   4770        bx  lr

20000012 <dummy>:
20000012:   4770        bx  lr
20000014:   20001000    andcs   r1, r0, r0

20000018 <notmain>:
20000018:   b570        push    {r4, r5, r6, lr}

看起来不错。

您现在可以使用 openocd

重置暂停 load_image notmain.elf 恢复 0x20000000

到运行程序(可能需要一个路径,如果你运行openocd在elf文件所在的目录and/or你将elf文件复制到你所在的目录启动openocd(不是telnet,openocd)然后你通常不需要放一个路径。

这是在 sram 中而不是闪存中,因此可能 运行 更快并且可能需要在延迟循环中使用更大的值。

如果你只是想让输出高或低,那么只需使用所需的 bsrr 行并摆脱循环,当你从 notmain return , 一个不会干扰 gpio 端口的,作为你用你的工具构建的二进制文件的调查的一部分,你需要确认你放置的 while 循环实际上不是死代码并且已经实现(已知 clang死代码,所以其他人也可以)和一些沙箱当你从 main return 撤消一些东西,所以你的代码现在可能没问题,但是从 main 退出并且 bootstrap 撤消你所做的PA5比你看到的还要快

到目前为止我能做的就是这些,我有一个 stm32 cortex-m0+ 部分和一个工作的 openocd 配置,如果有帮助的话,这是一个不同的部分,但核心是相同的,如果没有另一个水龙头那么它应该只是工作,但你永远不知道。

简短回答,您的现代代码将无法工作,否则它看起来不错,但 C 代码只是成功所需故事的一部分。这个长答案强调了成功启动和设置 LED 灯所必须具备的要点。有可能我们都错过了一个额外的启用,我没有这个部分,所以我不能真正拉出一个并且 运行 这个代码就可以了。