ESP32上的铁锈(2019年)

2020-07-05 20:55:04

大约六个月前,我在reddit上发了一篇帖子,强调Espressif';的llvm xtensa fork的发布,不久之后,我就有了一个可以生成xtensa汇编的rustc工具链。在这一点上,我不得不将这个项目搁置一边,以完成我的大学最后一年。有趣的是,我没有离题太远,我最后一年的项目使用铁锈制造了一款智能手表(如果有人感兴趣,我将来可能会写到这一点)。

从那以后,我看到一些帖子使用我的fork在ESP32上运行Rust(如果你还没有看到ctron写的这篇很棒的文章),其中大多数都是在用C编写的esp-idf之上构建的。在这篇帖子中,我将讨论我使用rustc为xtensa体系结构生成有效二进制文件的步骤,然后编写一些no_std代码,以便只使用Ruust!为ESP32构建一个闪烁的程序!(请参阅ctron撰写的这篇很棒的文章),在这篇文章中,我将讨论我使用rustc为xtensa体系结构生成有效二进制文件的步骤,然后编写一些no_std代码,以便仅使用Rust!为ESP32构建一个闪烁的程序。

2019年3月,Espressif发布了他们在支持xtensa架构的llvm分支上的第一次运行。不久之后,我开始用Bootstrapping Rust来使用这个新创建的叉子。在这个项目之前,我没有使用编译器的经验,幸运的是我遇到了RISCV PR,它让我对所需的东西有了一个大致的了解。经过多次构建尝试,我终于让它正常工作了;我现在能够从Rust源代码生成xtensa程序集了!

下一步是组装并链接生成的程序集。处于当前状态的llvm fork不能执行对象生成,因此我们必须使用外部汇编器。幸运的是,Rust允许我们这样做,方法是将LINKER_AMESS指定为GCC,并使用链接器目标选项提供到链接器的路径,在本例中为xtensa-esp32-ELF-GCC。之后,我创建了几个内置目标(您可以在这里看到);xtensa-esp32-None-ELF用于ESP32;xtensa-ESP8266-None-ELF用于ESP8266;最后xtensa-UNKNOWN-NONE-ELF目标用于通用的xtensa目标。

现在,让我们尝试让ESP32电路板仅使用铁锈来闪烁板载LED。首先,我们需要基本的程序结构。xtensa_lx6_rt板条箱完成了这方面的大部分繁重工作,我们只需要定义一个入口点和死机处理程序。其中一些可能看起来有点眼熟,如果你在Rust上有任何皮层-m开发的经验,我已经尽我所能地镜像了API。

#![no_std]#![no_main]Use xtensa_lx6_rt as_;use core::Panic::PanicInfo;/入口点-初始化后由xtensa_lx6_rt调用#[no_manger]FN main()->;!{loop{}}/简单死机处理程序#[Panic_Handler]FN PanicInfo(_info:&;PanicInfo)->;!{loop{}。

现在,让我们为要使用的外围设备添加一些寄存器定义。对于闪烁的程序,我们需要控制GPIO外围设备。在ESP32(和大多数现代处理器)中,外围设备映射到内存地址,通常称为内存映射外围设备。要控制外设,我们只需根据芯片制造商提供的参考手册将值写入存储器中的正确地址。

/GPIO输出启用REG CONST GPIO_ENABLE_W1TS_REG:U32=0x3FF44024;/GPIO输出设置寄存器CONST GPIO_OUT_W1TS_REG:U32=0x3FF44008;/GPIO输出清除寄存器CONST GPIO_OUT_W1TC_REG:U32=0x3FF4400C;/连接到板载LED CONST BE的GPIO。const GPIO_FUNX_OUT_SEL_CFG:u32=GPIO_FUNX_OUT_BASE+(Blinky_GPIO*4);

使用这些定义,应该可以通过更改Blinky_GPIO来更改主板1的GPIO;对于我的主板(NODEMCU ESP-32S),它是GPIO2。

接下来,让我们将引脚设置为GPIO输出。对于ESP32,这是一个两步过程1。首先,这只是设置GPIO输出使能寄存器中的一个位的情况。其次,引脚必须配置为GPIO模式。芯片中所有可能的外围设备都没有足够的引脚,为了解决这个问题,每个引脚可以有多种功能模式。在ESP32的情况下,每个引脚具有多达256个不同的功能,尽管并非所有引脚都被映射。要将引脚置于GPIO模式,需要将其置于模式256(0x100),方法是写入功能选择寄存器。发出这两个寄存器写入后,我们应该能够通过设置GPIO设置寄存器2内的相关位来打开GPIO。

#[NO_MANGLE]FN Main()->;!{//将引脚配置为输出不安全{CORE::PTR::WRITE_VOLILAR(GPIO_ENABLE_W1TS_REG AS*MUT_,0x1<;<;Blinky_GPIO);//0x100使此引脚成为简单的GPIO引脚-有关更多信息,请参阅技术参考CORE::PTR::WRITE_VARILAR(GPIO_FUNX_OUT_SEL。}//打开LED不安全{CORE::PTR::WRITE_VILAR(GPIO_OUT_W1TS_REG AS*mut_,0x1<;<;idx);}LOOP{}}。

对于闪烁程序的下一阶段,我们需要一种延迟的方法;一种简单的方法可以使用for循环,如下所示。

发布FN延迟(时钟:u32){let ummy_var:u32=0;for_in 0..时钟{不安全{core::ptr::read_volatile(&;ummy_var)};}}。

我们添加了易失性读取,这样编译器就不会优化我们的延迟。这种方法的问题在于,根据优化级别的不同,循环的每次迭代的时钟周期数都会改变。我们需要一种周期精确的延迟方式,幸运的是,ESP32有一个内部时钟计数寄存器,可以通过读取专用寄存器RSR指令访问该寄存器。现在的延迟函数是这样的。

/使用周期计数器寄存器pub FN Delay(时钟:u32)进行周期精确延迟{//注意:不考虑翻转//省略:ASM读取时钟let target=get_ccount()+时钟;loop{if get_ccount()>;target{Break;}}。

现在我们有了精确的周期计数,我们可以通过等待处理器在一秒内执行的周期数来延迟一秒。大多数ESP板上的默认时钟速度为40 MHz,因此等待4000万个周期相当于一秒的延迟。

将代码片段放在一起并将代码清理成函数,我们现在就有了如下所示的Main。

#[NO_MANGLE]fn main()->;!{//将管脚配置为输出configure_pin_as_output(Blinky_GPIO);loop{set_led(Blinky_GPIO,TRUE);DELAY(CORE_HZ);SET_LED(Blinky_GPIO,FALSE);DELAY(CORE_HZ);}}。

在向电路板闪烁并启动我们的JTAG调试器3之后,我们看到一个闪烁的LED!

如果您想亲自尝试一下,可以在xtensa快速入门资源库中找到完整的源代码。

现在我知道你们大多数人现在在想什么,它不是很生锈;它包含了成堆的不安全因素,这里没有真正的抽象,你们是对的;但它是让球滚动起来的东西。

有一些小的初期问题,但是到目前为止最大的问题是fork很难生成调试信息;外部汇编程序不支持CFI指令,这是所有llvm目标都需要支持的。通过一些预处理可以很容易地删除CFI指令,但是当然会增加一个额外的步骤。在解决了这个问题之后,我仍然收到重新定位链接器错误。我开了一期来记录我的发现,希望它能在下一次的llvm fork迭代中被分类。

一旦调试信息问题得到解决,我希望开始开发一个由HAL和驱动程序组成的生态系统,类似于STM32-RS和NRF-RS;我已经启动了ESP-RS组织,这也是xtensa-lx6-rt目前所在的组织。Espressif已经启动了上游过程,前十个补丁现在正在审核中,他们的分支应该会有一个更新,从旧的llvm6升级到llvm8(希望还会有一些其他的补充和修复!)。

1请注意,这仅适用于GPIO 0至31,其余的GPIO使用不同的寄存器。

2如果LED连接的引脚未默认为正确的IOMUX功能,情况并不总是如此,有关详细信息,请参阅快速入门示例。

3这一步实际上非常重要,目前主板将在引导时无休止地重置(我假设是因为看门狗没有被禁用),但是使用调试器启动可以正常工作。