让我试着解释一下我在寻找什么,因为我找不到更好的标题措辞。
假设我正在对一个RP 2040微控制器进行编程,我可以使用gdb
和openocd
与它建立一个调试会话。(注意,即使我在这里讨论的是一个具体的MCU平台,我感兴趣的是这种方法是否可以实现 * 一般 * -使用任何类型的“外部微控制器”,gdb
可能能够针对)
现在,假设我想用外部硬件执行一些(相对简单的)过程:为了举例,假设我想打开某个GPIO引脚,等待2000个CPU周期,然后关闭同一个GPIO。即使是这样一个简单的例子,这也需要硬件初始化,因此,在固件代码中,我必须做类似于(C使用pico-sdk)的事情:
#define MY_PIN_NR 12
static inline void my_hardware_init(void) {
gpio_init(MY_PIN_NR);
gpio_set_dir(MY_PIN_NR, GPIO_OUT);
}
static inline void my_hardware_do_process(void) {
// raise pin high:
gpio_put(MY_PIN_NR, 1);
// wait for 2000 CPU cycles
uint16_t cycles_to_wait = 2000;
while(cycles_to_wait--) {
asm volatile("nop");
}
// set pin low:
gpio_put(MY_PIN_NR, 0);
}
void my_hardware_full_process(void) {
// ensure hardware is initialized
my_hardware_init();
// do process:
my_hardware_do_process();
}
如果这是在固件中编译并在Flash中刻录的,我可以在GDB会话中直接在目标微控制器上调用它,say:
(gdb) call my_hardware_full_process()
(or甚至只是p my_hardware_full_process()
);则即使调试器使微控制器在断点处暂停,函数仍执行,且接着返回到调试器。
现在,这意味着闪存上刻录了实际代码(从gdb
解析为符号my_hardware_full_process
的地址开始)。
所以,我的问题是-我可以以某种方式做类似的事情,也就是说,执行相同的代码在my_hardware_full_process
,但如果微控制器闪存完全擦除/未初始化?(这意味着微控制器没有要运行的代码,因此不运行任何代码-注意gdb
通过openocd
仍然可以挂钩到这种状态)。在这种情况下,即使gdb
从.elf文件中获得地址my_hardware_full_process
,它仍然是一个不包含可运行代码的地址,因此使用(gdb) call function-symbol()
的方法失败。
考虑到这一点,我推测,也许有可能编译一个“二进制blob”,它将包含my_hardware_full_process()
函数的程序集-例如,arm-none-eabi-objdump -S --disassemble=my_hardware_full_process firmware.elf
在这里将给予:
Disassembly of section .text:
10000310 <my_hardware_full_process>:
}
// set pin low:
gpio_put(MY_PIN_NR, 0);
}
void my_hardware_full_process(void) {
10000310: b510 push {r4, lr}
gpio_init(MY_PIN_NR);
10000312: 200c movs r0, #12
10000314: f003 fcf2 bl 10003cfc <gpio_init>
* Switch all GPIOs in "mask" to output
*
* \param mask Bitmask of GPIO to set to output, as bits 0-29
*/
static inline void gpio_set_dir_out_masked(uint32_t mask) {
sio_hw->gpio_oe_set = mask;
10000318: 23d0 movs r3, #208 ; 0xd0
1000031a: 061b lsls r3, r3, #24
1000031c: 2280 movs r2, #128 ; 0x80
1000031e: 0152 lsls r2, r2, #5
10000320: 625a str r2, [r3, #36] ; 0x24
sio_hw->gpio_set = mask;
10000322: 615a str r2, [r3, #20]
uint16_t cycles_to_wait = 2000;
10000324: 22fa movs r2, #250 ; 0xfa
10000326: 00d2 lsls r2, r2, #3
while(cycles_to_wait--) {
10000328: e001 b.n 1000032e <my_hardware_full_process+0x1e>
asm volatile("nop");
1000032a: 46c0 nop ; (mov r8, r8)
while(cycles_to_wait--) {
1000032c: 001a movs r2, r3
1000032e: 1e53 subs r3, r2, #1
10000330: b29b uxth r3, r3
10000332: 2a00 cmp r2, #0
10000334: d1f9 bne.n 1000032a <my_hardware_full_process+0x1a>
sio_hw->gpio_clr = mask;
10000336: 23d0 movs r3, #208 ; 0xd0
10000338: 061b lsls r3, r3, #24
1000033a: 2280 movs r2, #128 ; 0x80
1000033c: 0152 lsls r2, r2, #5
1000033e: 619a str r2, [r3, #24]
// ensure hardware is initialized
my_hardware_init();
// do process:
my_hardware_do_process();
}
10000340: bd10 pop {r4, pc}
Disassembly of section .data:
因此,基本上,我需要这段代码,以及<gpio_init>
和依赖项跳转到的任何地方-本质上,一个“静态构建”,就像在PC上所知道的那样。原则上,我可以想象一个“静态构建”blob,它“包括”运行(在本例中)my_hardware_full_process
函数所需的所有需求/依赖项。
那么问题就变成了:我是否可以使用gdb
在PC上读取这种“静态构建二进制blob”文件,然后以某种方式将指令及其数据“推”到微控制器,并在那里执行blob的指令(即“即时”),因此硬件执行预期的功能(之后,控制返回到gdb
提示)-即使闪存被完全擦除?
如果是这样的话,我如何创建这样一个“静态构建二进制blob”?我如何指示gdb
在目标微控制器上运行它?
1条答案
按热度按时间31moq8wy1#
一般来说,当然。这就是你用调试器做的事情。一般来说......没有必要从flash运行它。MCU有ram,特别是皮科(芯片)没有flash,只有ram。(flash在芯片外和板上)。如果你愿意,你可以像其他任何人一样使用gdb下载它并运行和停止它。
现在C函数不能独立运行,即使你认为它们是静态构建的,库调用也不应该作为一个函数孤立地运行。C必须被引导. stack,.data,.bss等。这些供应商的库往往有额外的东西被加载到链接器脚本中,并在后台引导库调用。就像试图在不启动汽车的情况下驾驶汽车一样,靠在方向盘上踩油门而不发动汽车是不会让你到达任何地方的(如果你松开驻车制动器可能会撞车)。
您需要为此用例设计独立的函数并解决先决条件。
你应该做的只是做一个普通的二进制文件,它可以做你想做的最小的事情,下载并在RAM中运行它。(就像在Link中一样,它是为RAM而不是Flash构建的)
现在有了物联网的问题,MCU开始得到保护,我认为皮科已经得到了,什么不一定是保护,但它如何引导是一个rom引导加载程序为基础的东西,面向试图找到一个闪存,然后解析,如果你将一个文件系统关闭它,其中包含二进制加载到sram和运行。我将不得不查阅我的笔记/示例和文档,看看你是否可以只释放重置和加载代码到sram中。当然,虽然你可以在flash上有一个最小的程序,如果没有其他东西通过这个 Boot 过程,并离开程序在一个无限循环,然后从调试器你可以停止,将代码加载到SRAM中并在一个地址处恢复。
你选择了一个比较难的mcu来尝试这个练习,有很多种方法。Nucleo板有一个内置的调试器,和较大的(仍然为10美元左右),调试器可以用于其他cortex-m板,甚至其他品牌的芯片。stm32 g部分与g往往有一些保护,可能使它更难运行只在ram上。stm32 f和stm32 l和旧的部分根本不是问题。你可以得到一个蓝色的药丸,然后花5美元左右得到一个调试器板(jlink clone)。那个品牌和其他领先的cortex-m品牌有比rp 2040更好的文档。Broadcom芯片有一些非常酷的功能,但如果你想编写一些不依赖于库的干净代码,那么就需要经验和挖掘。
闪存最初只是英特尔的一部分。本质上它被Map到处理器的地址空间。如果你有某种调试接口,运行时,调试器(openocd等)只需要知道基地址和协议是一样的它都是英特尔闪存部件。但我们有spi和i2c部件用于隔离部件,在mcu内部它是芯片供应商自己的接口,所以我们再也不能有任何形式的处理器,给予我地址,我可以编程闪存,现在我们有,产品线中的每个子系列或单个部件的编程都与任何其他部件不同(有些重叠,但比你希望的要少)现在调试器必须知道无数组合中的每一个。而那些人并不关心。因此,如果某个特定公司的某个人选择为openocd这样的开源项目做贡献,为他们的特定产品集添加对特定闪存控制器的支持发生了这种情况。例如,蓝色药丸中的stm32f103是支持的,如果我没记错的话。DFU在某种程度上有所帮助,但是需要在部件上运行引导加载程序,以将通用DFU命令转换为芯片特定例程。
然后你有很多的mcu有一个闪存银行的问题,所以你不能从它执行,而擦除/编程它,即使它是一个页面,你不使用.你通常必须复制和跳转到ram和接口与调试器或任何这样的闪存可以编程.(即或者只是停止处理器并从调试器控制它,这可能是您的使用情况)。一些现在具有多个存储体并且在运行时通告 Flink 。RP 2040根本不具有 Flink ,电路板供应商选择一个并填充它,您可以在片上引导加载程序源代码中看到旋转,以试图找出那里的部分。
所以我不确定你问闪存问题是因为你想看看你是否可以从RAM加载和运行,或者你想知道你是否可以在闪存上编程一个小blob,或者你是否必须向闪存写入一个小blob。你当然可以,因为你已经停止了处理器,但为什么?对于这个用例,如果我理解它,如果可以的话,使用RAM。对于大多数产品,尤其是那些基于Cortex-M的系统,你可以
正如上面所暗示的,我不认为这是真实的的问题,真正的问题是假设一个函数可以独立执行,即使main()也不能独立执行,一般来说,这是你需要关注的。
至于确认运行从ram只是采取一个简单的程序
构建它,将它加载到RAM中的某个地址(它是位置无关的,所以它只需要在偶数地址上),启动它,等待,停止它,然后使用调试器读取寄存器0,恢复,停止,读取寄存器0。
制作一个可用的blob是另一套SO问题和/或只是理解你应该只做一个小的,完整的,程序,做你想做的最小的事情。并加载,运行它,然后停止。当然,不幸的是,如果你使用供应商库,每个二进制文件将重置/擦除前一个程序的设置(一个程序启用GPIO输出,另一个程序使其 Flink 而不启用它不太可能在供应商的沙箱中工作)。所以你可能需要自己卷。如上所述,你需要设计“功能”或者实际上整个blob是独立的(读:不要使用别人的图书馆)
所以我强烈推荐picoprobe路径,两个Picos.你不需要的UART最初,所以从图中的顶部两个引脚在左边,探针,到目标pico上的较低引脚. SWD信号.现在我有问题,直到我实际上从探针供电的目标,所以两个电源引脚在探针的右边的两个电源引脚在目标的右边.
我下载了flash_nuke.uf2并在目标MCU上使用它来擦除闪存。
我只是下载了picoprobe.uf2文件,我按照说明为pico克隆了openocd并构建了它。
然后cd到tcl目录并
一切都好
然后在另一个窗口
telnet到openocd服务器并停止目标。
开始.s
memmap.ld
建造
从telnet,停止后/停止时
现在从telnet会话恢复并停止
我们可以看到r 0在增加,pc在我们预期的地方,所以它正在运行这个程序,这个程序被下载到一个有擦除闪存的Pi中。
开始.s
notmain.c
memmap.ld
建造它
现在在telnet提示符下你可以
然后load_image这个新的notmain.elf
简历0x 20000000
并且在目标皮科上LED将 Flink 。
从技术上讲,你应该能够使用任何swd调试器,但我忘记了如果失败。与picoprobe我有两个板由同一个usb集线器供电,它没有工作是得到一些dap错误或什么。只有探针板插入故障使它看起来像它找到了探针,但无法找到目标。所以看目标侧,不确定是否有一些论坛或文档或什么决定尝试从探头供电,这工作。
这个例子是一个可以为flash或sram构建的。对于flash,第一阶段的引导加载程序在器件上,第二阶段来自闪存上第一个uf 2分区上的252字节。2所以我的第一次闪存尝试我做了这个小闪光灯。3当你移动到更大的程序时,我会忘记血淋淋的细节。有一个更高的地址sram是从flash拷贝的一部分,然后0x 20000000是sram的一个非常典型的地址。(Cortex-M具有用于芯片供应商的地址空间规则0x 40000000是外围设备开始的地方,有些将执行0x 10000000,但大多数为0x 20000000,有些将镜像低于0x 10000000以满足其他规则,但您可以从0x 20000000空间执行)
我没有使用过gdb,所以经验几乎为零,已经有十年或二十年了。我只是telnet到openocd,我经常这样做,有时也经常使用load_image
对于支持的部分,然后在命令行上重置或重置板。我经常会在像这个皮科这样的板上焊接一个重置按钮,这样就不需要拔出usb并重新插入,但是使用openocd,如果你想让它重置部分但不释放处理器执行(允许你下载代码到sram中,然后继续),你可以做一个重置或重置停止
无论如何,如果你已经了解了这么多,那么你就可以解决gdb加载和运行的方式了。这是非常可行的,毫无疑问,我只是没有理由知道,也不能帮助你。
从你的一个评论和修改,我得到了上述。
当闪存被擦除时,它确实显示出第一阶段引导加载程序正在运行并达到一个点,它加快了处理器上的时钟。基于延迟计数,以获得一个既不太快也不太慢的视觉 Flink 。但是,如果你或让我说,当我用程序构建闪存映像时:
然后把那个放在闪光灯上。然后使用探头,加载上面的闪光灯程序,速度要慢得多。
我强烈推荐这条路,因为谁知道 Bootstrap 还搞砸了什么,你想开发一个干净的,重置后的系统,而不是一个停止了 Bootstrap 的系统。