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

Posted

技术标签:

【中文标题】如何将项目从一个 STMFx 系列移植到同一系列中的另一个【英文标题】:How to port a project from one STMFx series to another in the same Series 【发布时间】:2018-02-01 14:31:31 【问题描述】:

总的来说,我对 ST 系列 MCU 的语言/术语/术语有些困惑,我认为这阻碍了我的进步。

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

然后,我走进了 STM32 ARM 内核的脱粒机,然后冒出了烟……STDPerifieral 库、HAL 层、CubeMX、汇编启动文件、CMSIS、IDE 特定项目文件,以及十几种不同类型的STM32F4……我已经快被淹没了!

我真正的问题是我有这个 STM32F411 探索板,我需要运行一个 20x4 字符 LCD。我在 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 找不到的东西,这就是事情出轨的地方...... 例如,其中一些项目正在调用 stm32f4xx.h 文件,但 CubeMX 并未在其生成的代码中包含该文件。

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

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

所以,最大的问题是:

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

并且,为 8 位 AVR 和 STM32F4 开发之间最重要的区别是什么? 以我有限的经验和新手错误的数量,我认为最安全的前进道路是使用 CubeMX 制作一个项目,以便至少将所有内容设置好写入,然后仔细尝试手动添加源文件?但这仍然不能解决我从根本上误解,如果我使用 Unix 作为 IDE,这些设备的健全工作流程应该是什么。

谢谢!

【问题讨论】:

大多数嵌入式项目的问题在于,它们从一些 helloworld.c 的某个端口开始,来自电子期刊或类似内容中的某个 maker 项目。它们从来没有围绕硬件可移植性的想法构建,因为这需要前期的努力,这很乏味,而且永远不会为单个项目带来回报。此外,硅供应商不会以这样的想法构建他们的库(如果他们提供的话),而是针对特定部分进行编程。 【参考方案1】:

这是一个非常广泛的问题。首先,您的 avr 体验究竟是什么,很明显,如果您从 main.c 开始,那么其他人为您构建了您的沙盒/工具环境,包括引导程序。 avr 比 arm 更像哈佛,所以实际上它更像是一个 PITA,可以在没有人为你工作的情况下真正构建。

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

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

所有 mcu 芯片供应商都需要提供库,因为不是每个人都愿意或(所以他们认为)能够裸机通过(并不是说库不是裸机,而只是像系统一样的 api 调用)。他们不会在这个时代生存。同样,您必须使用一些新名称来更新更好的库,以便库更改。 ARM 很棒,但同时也是皇家 PITA。因为他们制造的是核心而不是芯片,而且他们的核心被不同的东西包围着。有大量的 cortex-m4 解决方案使用相同的 cortex-m4 内核,但是您不能编写一个适用于所有这些内核的 cortex-m4 程序,因为芯片供应商特定的东西都是不同的(肯定会有大量的 if-then-否则程序会工作,如果你能让它适应)。 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 等。如果您使用裸机,请与寄存器交谈,没有其他库方法,您会看到从 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 不能。一旦您为每个不同的外围设备拥有了自己的库,您就可以混合和匹配它,并且他们的库会尝试这样做,并使用常见的理想调用,这样代码端口会更好一些,但您必须构建不同的芯片来连接不同的东西。并不完美。

另外看看有多老说非常流行的感谢 arduino 和也许 avr-freaks 在 atmega328p 之前那个东西是相对古老的。在那之后,所有的 stm32 都被创建了,并且由于各种原因,大小/速度/内部政治/等不同的外围设备选择被创建。上面提到的所有 cortex-m 变体都是在 atmega328p 没有改变的时候在 mcu 世界中针对不同的目标用例创建的。

因此,如果您使用一个 avr 文档和一个 avr,并且有一个可以工作的工具链,那么您可以直接从 avr 文档编写程序。如果您为一个 stm32 芯片使用一个 stm32 文档,则只需为 arm 使用任何 gcc 交叉编译器(arm-none-eabi、arm-linux-gnueabi 等),执行您自己的 boostrap 和链接器脚本,这对于cortex-m,您可以直接从 stm32 文档和 arm 文档编写程序,没有问题,两者都写得很好。对不同的 stm32 芯片重复,对基于 cortex-m 的 nxp 芯片重复,对基于 cortex-m 的 ti 芯片重复。

作为一名程序员,尽管您想为两个芯片编写一个程序,但您需要查看这两个芯片,看看有哪些共同点和不同之处,并设计您的程序以避免差异或 if-then-else 或使用链接时间 if-then-else 解决方案。

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

例如一个简单的用于 stm32f411 的 LED 闪光灯,在 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

notmain.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 为零我在使用它们之前对其进行了初始化。所以我可以在我的引导程序上作弊,但是有很多稍微复杂一点的链接器脚本的例子,如果你想让你的 C 更纯粹.我也更喜欢控制用于加载和存储的指令,一些/很多希望编译器正确选择,已经看到失败。 YMMV。所以有很多个人风格,但是拿你的 stm32f11 文档看看那些地址和寄存器,即使你完全讨厌我的代码或风格,你仍然应该看到使用那个外围设备是多么容易。同样,计时器在 arm 文档中。如今,作为其中之一,一些供应商拥有自己的修改形式的 arm 文档版本,因此涵盖了很多 arm 信息,但仍有一些差距。

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

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

【讨论】:

对不起,奥拉夫教他们钓鱼而不是给他们一条鱼。 (虽然我也给过鱼)。 添加到此“您将如何使用其中之一......”。我会剪切并粘贴我自己的 spi 控制器或该芯片的 i2c 代码,然后从 HD44780 的芯片文档中查看协议是什么并进行相应修改,然后从一些仓库中获取代码,在那里有人让它作为这些 lcd 驱动程序工作倾向于有一定范围的控制和使用/检查初始化顺序和这个第三方回购使用的值。可能会发现他们的显示器适用于不同像素大小的显示器并且无法正常工作,可能会发现我的对比度设置需要更高/更低等,但这是一个起点 ...lcd 驱动程序文档往往会遗漏初始化顺序和值的良好/工作示例,您可能会花很长时间直接从文档中猜测。通常你需要调整,第三方库不能包含用那个东西制作的所有面板,只有作者拥有和测试过的面板。 在任何微控制器上使用任何 spi 或 i2c 我从位敲击 gpio 开始,而不使用片上外围设备,我找出外部设备/目标(在这种情况下是 lcd 面板)然后我决定从那里做什么。 bit banging 非常便携,各种 mcus 中的 spi 控制器不是,它们在芯片供应商之间差异很大,有时在不同家族的供应商中差异很大。因为 spi slaves 和 i2c slaves 通常不完全符合 a/the 标准,但有时使用标准术语,涉及黑客攻击,如果它试图符合,使用库 esp 会更糟...... 这个答案是......壮观。非常感谢!连续两个凌晨 4 点的夜晚,读完这篇文章后,我感到精神焕发。谢谢,我将从最底层开始,参考参考文档,而不是试图欺骗抽象的库。再次感谢!

以上是关于如何将项目从一个 STMFx 系列移植到同一系列中的另一个的主要内容,如果未能解决你的问题,请参考以下文章

ffmpeg系列之移植到安卓中调用

ffmpeg系列之移植到安卓中调用

将一个系列中的数字添加到同一数据框中的一系列字母,可能使用正则表达式

如何重复一系列承诺中的步骤以及如何从失败的步骤中恢复?

从零开始创建GD32F4系列单片机工程模板&移植涂鸦MCU-SDK

Python 系列练习