Arduino 引导加载程序

发布于 2024-09-17 08:51:08 字数 913 浏览 7 评论 0 原文

有人可以解释一下Arduino 引导加载程序 工作吗?我不是在这里寻找高级答案,我已经阅读了代码并且了解了它的要点。

Arduino IDE 和引导加载程序代码之间会发生大量协议交互,最终产生许多内联汇编指令,这些指令通过通过串行接口传输的程序对闪存进行自编程。

我不清楚的是第 270 行:

void (*app_start)(void) = 0x0000; 

...我认为这是函数指针的声明和初始化为 NULL。在引导加载程序打算委托执行用户加载的代码的地方,有对 app_start 的后续调用。

当然,app_start 需要在某个时刻获得一个非 NULL 值才能将其组合在一起。我在引导加载程序代码中没有看到这一点...它是否被引导加载程序加载的程序神奇地链接起来?我认为引导加载程序的主要部分是芯片重置后软件的入口点。

包裹在大约 70 行汇编代码中的一定是秘密解码器环,它告诉主程序 app_start 到底在哪里?或者也许是 Arduino IDE 利用了一些隐式知识?我所知道的是,如果有人不将 app_start 更改为指向 0 以外的某个位置,引导加载程序代码将永远自行旋转......那么有什么技巧呢?

编辑

我有兴趣尝试将引导加载程序移植到 Tiny AVR,该 AVR 没有用于引导加载程序代码的单独内存空间。对我来说,引导加载程序代码显然依赖于某些保险丝设置和芯片支持,我想我真正感兴趣的是如何将引导加载程序移植到没有这些保险丝和硬件的芯片上支持(但仍具有自编程能力)?

Can someone please explain how the Arduino bootloader works? I'm not looking for a high level answer here, I've read the code and I get the gist of it.

There's a bunch of protocol interaction that happens between the Arduino IDE and the bootloader code, ultimately resulting in a number of inline assembly instructions that self-program the flash with the program being transmitted over the serial interface.

What I'm not clear on is on line 270:

void (*app_start)(void) = 0x0000; 

...which I recognize as the declaration, and initialization to NULL, of a function pointer. There are subsequent calls to app_start in places where the bootloader is intended to delegate to execution of the user-loaded code.

Surely, somehow app_start needs to get a non-NULL value at some point for this to all come together. I'm not seeing that in the bootloader code... is it magically linked by the program that gets loaded by the bootloader? I presume that main of the bootloader is the entry point into software after a reset of the chip.

Wrapped up in the 70 or so lines of assembly must be the secret decoder ring that tells the main program where app_start really is? Or perhaps it's some implicit knowlege being taken advantage of by the Arduino IDE? All I know is that if someone doesn't change app_start to point somewhere other than 0, the bootloader code would just spin on itself forever... so what's the trick?

Edit

I'm interested in trying to port the bootloader to an Tiny AVR that doesn't have separate memory space for boot loader code. As it becomes apparent to me that the bootloader code relies on certain fuse settings and chip support, I guess what I'm really interested in knowing is what does it take to port the bootloader to a chip that doesn't have those fuses and hardware support (but still has self-programming capability)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

山有枢 2024-09-24 08:51:08

On NULL

地址 0 不构成空指针。 “空指针”是更抽象的东西:适用的函数应将其识别为无效的特殊值。 C 表示特殊值为 0,虽然语言表示取消引用它是“未定义的行为”,但在微控制器的简单世界中,它通常具有非常明确的效果。

ATmega 引导加载程序

通常,复位时,AVR 的程序计数器 (PC) 初始化为 0,因此微控制器开始执行地址 0 处的代码。

但是,如果设置了引导复位熔丝(“BOOTRST”),则程序计数器将被初始化到存储器上端的块的地址(这取决于熔丝的设置方式,请参见数据表(PDF,7 MB)了解详细信息)。从那里开始的代码可以做任何事情——如果你真的想要的话,如果你使用ICSP,你可以把你自己的程序放在那里(引导加载程序通常不能覆盖自己)。

但通常情况下,它是一个特殊的程序 - 引导加载程序 - 能够从外部源读取数据(通常通过 UART、I2C 、CAN 等)来重写程序代码(存储在内部或外部存储器中,具体取决于微控制器)。引导加载程序通常会寻找“特殊事件”,它实际上可以是任何东西,但对于开发来说,最方便的是数据总线上的东西,它将从中提取新代码。 (对于生产,它可能是引脚上的特殊逻辑电平,因为它几乎可以立即检查。)如果引导加载程序看到特殊事件,它可以进入引导加载模式,在该模式下它将重新刷新程序存储器,否则它会传递控制权关闭到用户代码。

顺便说一句,引导加载程序熔丝和上部内存块的目的是允许使用引导加载程序,而无需对原始软件进行任何修改(只要它不一直延伸到引导加载程序的地址)。您可以刷新原始 HEX、引导加载程序和修改后的保险丝以及添加的引导加载程序,而不是仅使用原始 HEX 和所需的保险丝进行刷新。

无论如何,就 Arduino 而言,我相信它使用 STK500 中的协议,它尝试通过 UART 进行通信,如果在指定时间内没有得到响应:

uint32_t count = 0;
while(!(UCSRA & _BV(RXC))) { // loops until a byte received
    count++;
    if (count > MAX_TIME_COUNT) // 4 seconds or whatever
        app_start();
}

或者如果由于得到意外响应而出错过多:

if (++error_count == MAX_ERROR_COUNT)
    app_start();

它将控制权传递回位于 0 的主程序。在 Arduino 源代码中可以看到上面,这是通过调用 app_start(); 来完成的,定义为 void (*app_start)(void) = 0x0000;

因为它是 C 函数调用,所以在 PC 跳转到 0 之前,它会将当前 PC 值推送到堆栈上,该堆栈还包含引导加载程序中使用的其他变量(例如 count error_count(来自上面)。这会从你的程序中窃取 RAM 吗?好吧,在 PC 设置为 0 后,所执行的操作公然“违反”了正确的 C 函数(最终会返回)应该执行的操作。在其他初始化步骤中,它重置堆栈指针(有效地删除调用堆栈和所有局部变量),回收 RAM。全局/静态变量初始化为 0,其地址可以与引导加载程序使用的任何内容自由重叠,因为引导加载程序和用户程序是独立编译的。

引导加载程序唯一持久的影响是对硬件(外设)寄存器的修改,好的引导加载程序不会使其处于有害状态(打开外设,当您尝试睡眠时可能会浪费电力)。通常最好的做法是完全初始化您将使用的外围设备,因此即使引导加载程序做了一些奇怪的事情,您也会按照您想要的方式设置它。

ATtiny 引导加载程序

在 ATtinys 上,正如您所提到的,引导加载程序熔丝或内存并不奢侈,因此您的代码将始终从地址 0 开始。您也许可以将引导加载程序放入一些更高的内存页中,并将 RESET 向量指向然后,每当您收到要闪存的新十六进制文件时,请获取地址 0:1 处的命令,将其替换为引导加载程序地址,然后将替换的地址存储在其他位置以调用正常执行。 (如果是RJMP(“相对跳转”),则该值显然需要重新计算)

On NULL

Address 0 does not a null pointer make. A "null pointer" is something more abstract: a special value that applicable functions should recognize as being invalid. C says the special value is 0, and while the language says dereferencing it is "undefined behavior", in the simple world of microcontrollers it usually has a very well-defined effect.

ATmega Bootloaders

Normally, on reset, the AVR's program counter (PC) is initialized to 0, thus the microcontroller begins executing code at address 0.

However, if the Boot Reset Fuse ("BOOTRST") is set, the program counter is instead initialized to an address of a block at the upper end of the memory (where that is depends on how the fuses are set, see a datasheet (PDF, 7 MB) for specifics). The code that begins there can do anything—if you really wanted you could put your own program there if you use an ICSP (bootloaders generally can't overwrite themselves).

Often though, it's a special program—a bootloader—that is able to read data from an external source (often via UART, I2C, CAN, etc.) to rewrite program code (stored in internal or external memory, depending on the micro). The bootloader will typically look for a "special event" which can literally be anything, but for development is most conveniently something on the data bus it will pull the new code from. (For production it might be a special logic level on a pin as it can be checked nearly-instantly.) If the bootloader sees the special event, it can enter bootloading-mode, where it will reflash the program memory, otherwise it passes control off to user code.

As an aside, the point of the bootloader fuse and upper memory block is to allow the use of a bootloader with no modifications to the original software (so long as it doesn't extend all the way up into the bootloader's address). Instead of flashing with just the original HEX and desired fuses, one can flash the original HEX, bootloader, and modified fuses, and presto, bootloader added.

Anyways, in the case of the Arduino, which I believe uses the protocol from the STK500, it attempts to communicate over the UART, and if it gets either no response in the allotted time:

uint32_t count = 0;
while(!(UCSRA & _BV(RXC))) { // loops until a byte received
    count++;
    if (count > MAX_TIME_COUNT) // 4 seconds or whatever
        app_start();
}

or if it errors too much by getting an unexpected response:

if (++error_count == MAX_ERROR_COUNT)
    app_start();

It passes control back to the main program, located at 0. In the Arduino source seen above, this is done by calling app_start();, defined as void (*app_start)(void) = 0x0000;.

Because it's couched as a C function call, before the PC hops over to 0, it will push the current PC value onto the stack which also contains other variables used in the bootloader (e.g. count and error_count from above). Does this steal RAM from your program? Well, after the PC is set to 0, the operations that are executed blatantly "violate" what a proper C function (that would eventually return) should do. Among other initialization steps, it resets the stack pointer (effectively obliterating the call stack and all local variables), reclaiming RAM. Global/static variables are initialized to 0, the address of which can freely overlap with whatever the bootloader was using because the bootloader and user programs were compiled independently.

The only lasting effects from the bootloader are modifications to hardware (peripheral) registers, which a good bootloader won't leave in a detrimental state (turning on peripherals that might waste power when you try to sleep). It's generally good practice to also fully initialize peripherals you will use, so even if the bootloader did something strange you'll set it how you want.

ATtiny Bootloaders

On ATtinys, as you mentioned, there is no luxury of the bootloader fuses or memory, so your code will always start at address 0. You might be able to put your bootloader into some higher pages of memory and point your RESET vector at it, then whenever you receive a new hex file to flash with, take the command that's at address 0:1, replace it with the bootloader address, then store the replaced address somewhere else to call for normal execution. (If it's an RJMP ("relative jump") the value will obviously need to be recalculated)

擦肩而过的背影 2024-09-24 08:51:08

编辑

我有兴趣尝试将引导加载程序移植到小型 AVR,该 AVR 没有用于引导加载程序代码的单独内存空间。对我来说,引导加载程序代码显然依赖于某些保险丝设置和芯片支持,我想我真正感兴趣的是如何将引导加载程序移植到没有这些保险丝和硬件的芯片上支持(但仍具有自编程能力)?

根据您的最终目标,创建自己的引导加载程序可能比尝试移植引导加载程序更容易。您实际上只需要学习该部分的一些内容。

1) uart tx

2) uart rx

3) 自闪存编程

可以单独学习,然后组合成 bootloader。您将需要一个可以使用 spi 或其他任何东西来写入闪存的部件,这样,如果您的引导加载程序无法工作或附带的任何部件弄乱了,您仍然可以继续开发。

无论您移植还是自己推出,您仍然需要了解与该部分相关的三个基本知识。

Edit

I'm interested in trying to port the bootloader to an Tiny AVR that doesn't have separate memory space for boot loader code. As it becomes apparent to me that the bootloader code relies on certain fuse settings and chip support, I guess what I'm really interested in knowing is what does it take to port the bootloader to a chip that doesn't have those fuses and hardware support (but still has self-programming capability)?

Depending on your ultimate goal it may be easier to just create your own bootloader rather than try to port one. You really only need to learn a few items for that part.

1) uart tx

2) uart rx

3) self-flash programming

Which can be learned separately and then combined into a bootloader. You will want a part that you can use spi or whatever to write the flash, so that if your bootloader doesnt work or whatever the part came with gets messed up you can still continue development.

Whether you port or roll your own you will still need to understand those three basic things with respect to that part.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文