如何将项目从一个 STMFx 系列移植到同一系列中的另一个

How to port a project from one STMFx series to another in the same Series

总的来说,我对 language/lingo/terminology ST 系列的 MCU 有点困惑,我认为这阻碍了我的进步。

一点背景知识:我是一名 EE,通过使用 AVR 平台的必修大学课程学习了我对 FW 的所有了解。非常喜欢,非常简单易用。 快速浏览唯一的数据表,bang 您正在抽象化!宏指令、磅定义等...非常简单!你写了一个 main.c 和一个 Makefile,编译并用 avr-dude 开火......生活是美好的。

然后,我走进了STM32 ARM内核的thresher,天啊……STDPerifieral Libraries,HAL层,CubeMX,assembly start up files,CMSIS,IDE具体工程文件,还有一个十几种不同类型的STM32F4...我很快就不知所措了!

我真正的问题是我有这个 STM32F411 探索板,我需要一个 20x4 字符 LCD 运行ning。我在 github 等网站上找到了几个看起来不错的项目:

https://stm32f4-discovery.net/2014/06/library-16-interfacing-hd44780-lcd-controller-with-stm32f4/

https://github.com/EarToEarOak/STM32F4-HD44780

https://github.com/6thimage/hd44780

https://github.com/mirkoggl/STM32F4_LiquidCrystal

但是,我不知道如何正确编译其中的任何一个。 我一直在使用 CubeMx 生成一个 Makefile 作为起点,主要是因为它生成了一个链接描述文件和我显然需要的 .s 文件,但我完全没有经验或想法如何自己做。这就是我对 CubeMX 的依赖。

我认为我的大部分问题是我试图将 CubeMX 生成的特定于 MCU 的代码和各种在线项目代码粘合在一起时遇到了很多冲突...要么在线项目正在调用一些 .h文件或引用 Makefile 找不到的东西,这就是 rails 发生的事情...... 例如,其中一些项目正在调用 stm32f4xx.h 文件,但 CubeMX 未将其包含在其生成的代码中。

我不得不转到系统的其他地方并将该文件复制到 ./Inc 文件夹,但这似乎真的是倒退......而且,它仍然没有构建。

如果我要在这里保持头脑清醒,我需要一个 AVR 到 STM32 12 步康复计划或其他东西...

所以,大问题是:

你们如何从第一个 git clone xxxx 中获取这些项目中的一个,并仅使用 arm-none-eabi 和 Makefiles 在 STM32F411 探索板上将其构建到 运行...否 IDE的允许。

而且,为 8 位 AVR 和 STM32F4 开发之间最重要的区别是什么? 凭借有限的经验和我犯的新手错误数量,我认为最安全的前进道路是使用 CubeMX 制作一个项目,以便至少设置写入所有内容,然后仔细尝试手动添加源文件?但这仍然没有解决我从根本上误解的问题,即如果我将 Unix 用作 IDE.

,那么这些设备的正常工作流程应该是什么?

谢谢!

这是一个非常宽泛的问题。首先也是最重要的是,您的 avr 体验究竟是什么,很明显,如果您从 main.c 开始,那么其他人会为您构建 sandbox/tool 环境,包括 bootstrap。 avr 比 arm 更像 harvard-ish,所以实际上它更像是一个 PITA,可以在没有人为你工作的情况下真正构建。

没有理由不能完全一样的体验。您可以获取有关某些外围设备寄存器的 avr 文档阅读程序中该外围设备的寄存器并使其工作。您可以获取 st 文档,阅读一些外围设备的寄存器,在您的程序中查看该外围设备的寄存器并使其工作。

您可以为您的 avr 使用像 arduino 这样的库,阅读有关 api 调用的信息,编写一个程序来进行一些调用,对设备进行编程。可以为您的 st 芯片获取一些库并做同样的事情,api 调用将不一样。 arduino 库与 atmel 或其他方制作的其他 avr 库不同 api 调用。你可以跳到 mbed.org 并开始编写 api 调用你的或一些 st 芯片并 poof 一个工作二进制文件。

所有 mcu 芯片供应商都需要提供库,因为不是每个人都愿意或(他们认为)能够通过裸机(并不是说库不是裸机,而只是 api 调用所以系统之类的)。他们不会在这个时代生存。同样,您必须使用新名称更新更好,以便库发生变化。 ARM 很棒,但同时也是皇家 PITA。因为他们制造核心而不是芯片,而且他们的核心被不同的东西包围着。有大量的 cortex-m4 解决方案使用相同的 cortex-m4 内核,但是您无法编写一个适用于所有这些的 cortex-m4 程序,因为芯片供应商特定的东西是完全不同的(确保大量的 if-then- else 程序将工作,如果你可以让它适合)。 ARM 正试图制作一个看起来相同的魔法层,并且供应商被拖着走,所以这个 CMSIS 和 MBED 是 ARM 驱动的概念来解决这个问题。 AVR没有这个问题,核心和芯片供应商是一个。现在有许多不同的 AVR 内核,即使你有相同的外围设备,你也可能无法编写一个适用于所有这些内核的二进制文件,或者可能有一个 (xmega) 的二进制文件不适用于另一个 (tiny) ) 因为即使外围设备相同,avr 核心实现也存在差异。虽然 avr 问题比 arm 问题小得多,但都包含在一家公司内。因此,如果外围设备发生变化,它们的变化将不会像 atmel vs nxp vs st vs ti vs 其他使用相同 arm 内核的设备(或至少名称相同,arm 源代码中有一些易于修改的项目,与或者没有带 16 位提取或 32 位提取等的浮点数,记录在该核心的 trm 中,造成更多痛苦)。

在像 ST 这样的公司内,他们只在 cortex-ms 上创建了不同的外围设备,包括许多定时器、gpio、uart、pll/clock 等。如果你使用裸机,请与寄存器交谈 no在其他库方法中,您会看到从 cortex-m3 到 cortex-m0 的过渡他们开始使用不同的 gpio 外设,但也许其他一些是相同的。快进到今天,我们有来自 ST 的基于 cortex-m3、cortex-m0、cortex-m0+、cortex-m4 和 cortex-m7 的设备,并且有混合搭配一个产品线的外设可能有一个类似于早期的定时器cortex-m3 产品,但 gpio 更像是第一个 cortex-m0 产品。他们似乎混合并匹配了他们从外围设备池中创建的每个新系列。所以我可能有一个特定芯片的代码,它在其他一些特定芯片上工作得很好,同一个系列,甚至可能有点不同。但是将其移至另一个 st stm32 系列,也许 uart 代码可以工作但 gpio 不工作,将第一个程序移至另一个系列,也许 gpio 可以工作而 uart 不工作。一旦您为每个不同的外围设备拥有自己的库,您就可以混合和匹配它,他们的库会尝试这样做,并使用一个通用的理想调用,这样代码端口会更好一些,但你必须构建不同的芯片得到不同的东西 linked in。不完美。

还要看看说在那个atmega328p之前非常流行的arduino和avr-freaks这东西是比较古老的。从那以后,所有的 stm32 都被创建了,并且由于各种原因 size/speed/internal politics/etc 创建了不同的外设选择。在 atmega328p 没有改变的时候,上面提到的所有 cortex-m 变体都是根据 mcu 世界中不同的目标用例创建的。

所以如果你有一份 avr 文档和一份 avr,并且有一个可以正常工作的工具链,你可以直接从 avr 文档编写程序。如果您为一个 stm32 芯片获取一个 stm32 文档,则为 arm(arm-none-eabi、arm-linux-gnueabi 等)获取几乎任何 gcc 交叉编译器,做您自己的 boostrap和 linker 脚本,这对于 cortex-m 来说非常简单,您可以直接从 stm32 文档和 arm 文档编写程序,没有问题,两者都写得很好。对不同的 stm32 芯片重复,对基于 cortex-m 的 nxp 芯片重复,对基于 cortex-m 的 ti 芯片重复。

作为一名程序员,尽管您想为两个芯片编写一个程序,但您需要查看这两个芯片并了解它们的共同点和不同点,然后设计您的程序来避免差异或使用 if-then-else 或使用link 时间 if-then-else 解决方案。

我发现芯片供应商的库比阅读文档和编程寄存器更难使用。 YMMV。我建议您继续尝试使用他们的旧图书馆和新图书馆,并尝试直接进入寄存器并找到最适合您的图书馆。手臂将 运行 以类似的价格、功率等围绕 avrs 旋转。因此尝试使用这些其他部件是有价值的。 avr、msp430 等已成为基于 cortex-m 的产品的替代品,因此您应该专业地研究一个或多个。

例如,用于 stm32f411 的简单 LED blinker,在 pa5

上有一个 LED

flash.s

.thumb


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

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

.align

.thumb_func
.globl PUT16
PUT16:
    strh r1,[r0]
    bx lr

.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

.end

不是main.c

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

#define RCCBASE 0x40023800
#define RCC_AHB1ENR (RCCBASE+0x30)

#define GPIOABASE 0x40020000
#define GPIOA_MODER     (GPIOABASE+0x00)
#define GPIOA_OTYPER    (GPIOABASE+0x04)
#define GPIOA_OSPEEDR   (GPIOABASE+0x08)
#define GPIOA_PUPDR     (GPIOABASE+0x0C)
#define GPIOA_BSRR      (GPIOABASE+0x18)

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


static void led_init ( void )
{
    unsigned int ra;

    ra=GET32(RCC_AHB1ENR);
    ra|=1<<0; //enable GPIOA
    PUT32(RCC_AHB1ENR,ra);

    ra=GET32(GPIOA_MODER);
    ra&=~(3<<10); //PA5
    ra|=1<<10; //PA5
    PUT32(GPIOA_MODER,ra);

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

    ra=GET32(GPIOA_OSPEEDR);
    ra|=3<<10; //PA5
    PUT32(GPIOA_OSPEEDR,ra);
    //pupdr
    ra=GET32(GPIOA_PUPDR);
    ra&=~(3<<10); //PA5
    PUT32(GPIOA_PUPDR,ra);
}

static void led_on ( void )
{
    PUT32(GPIOA_BSRR,((1<<5)<<0));
}

static void led_off ( void )
{
    PUT32(GPIOA_BSRR,((1<<5)<<16));
}

void do_delay ( unsigned int sec )
{
    unsigned int ra,rb,rc,rd;

    rb=GET32(STK_CVR);
    for(rd=0;rd<sec;)
    {
        ra=GET32(STK_CVR);
        rc=(rb-ra)&0x00FFFFFF;
        if(rc>=16000000)
        {
            rb=ra;
            rd++;
        }
    }
}

int notmain ( void )
{
    unsigned int rx;

    led_init();

    PUT32(STK_CSR,0x00000004);
    PUT32(STK_RVR,0xFFFFFFFF);
    PUT32(STK_CSR,0x00000005);

    for(rx=0;rx<5;rx++)
    {
        led_on();
        while(1) if(GET32(STK_CVR)&0x200000) break;
        led_off();
        while(1) if((GET32(STK_CVR)&0x200000)==0) break;
    }

    while(1)
    {
        led_on();
        do_delay(10);
        led_off();
        do_delay(1);
    }
    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-m4 flash.s -o flash.o
arm-none-eabi-gcc -Wall -Werror -O2 -nostdlib -nostartfiles -ffreestanding  -mthumb -mcpu=cortex-m4 -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-whatever-linux-gnueabi 替换并且仍然会构建并且 运行.

有些工具链在看到 main() 时会添加额外的东西,所以我个人一直避免这样做。我不在 mcus/etc 上使用从闪存启动的 .data,所以不必复制它(烧掉 .text 是的),并且我不假设 .bss 为零我在使用它们之前初始化了东西。所以我可以在我的 bootstrap 上作弊,但是有很多稍微复杂的 linker 脚本的例子,它们为您提供 bss 偏移量和大小以及 .data 偏移量和大小以及目的地,以将其提升到一个新的水平如果你想让你的C更纯净。我也更喜欢控制用于加载和存储的指令,some/many 希望编译器选择正确,看到失败。 YMMV。所以有很多个人风格,但是拿你的 stm32f11 文档看看那些地址和寄存器,即使你完全讨厌我的代码或风格,你仍然应该看到使用那个外设是多么容易。同样,计时器在 arm 文档中。如今,作为其中之一,一些供应商拥有自己的 arm 文档版本,其形式经过修改,因此涵盖了很多 arm 信息,但仍有一些差距。

作为 arm 的一般规则,请从芯片供应商文档中找出您的芯片中有哪些 arm 内核。然后转到武器站点并找到该内核(在本例中为 cortex-m4)的技术参考手册 (TRM)。然后在该文档中,arm 提到架构 armv7-m 获取 armv7-m 的架构参考手册。这三个文档是您的主要信息来源,在您使用第一个 cortex-m4 之后,您可能 99% 的时间只需要芯片供应商文档,当然是在芯片供应商内部。还可以找到 cpuid 寄存器或芯片 id 或文档调用它的任何内容,并将其与您从 chip/arm 核心读取的内容进行比较。有时会有不同版本的 arm 核心(r1p3 表示修订版 1.3)并且很少见,但修订版之间发生变化意味着将最新的文档与旧的核心一起使用可能会导致细微的差异。同样,基于 arm 和 arm 的芯片 improving/changing 比基于 atmel avr 的芯片快得多。在第一次或第二次之后你就掌握了窍门...

例如,如果您要使用 PIC32,那么对于 pic32 文档来说,它是一个类似的故事微芯片,然后将核心文档转为 mips(然后希望该微芯片记录其外围设备的性能甚至是 pic32 文档的一半) atmel(他们现在拥有)、ti、st、nxp 等)。购买一个处理器核心并将我自己的东西包裹在它周围的另一种混合。 pic32 以这种方式编程很痛苦,确实需要埋在微芯片工具链中的库,并使用更多的功率等。没有理由为什么基于 mips 的不能与基于 arm 的竞争,但它们不......mips 或芯片供应商或组合在那里有错。