M5 Stack Fire 2.6 开箱





5.00/5 (2投票s)
解锁这款精巧的 IoT 小工具的潜力。
引言
M5 Stack Fire 2.6 是一款功能强大、小巧实用的物联网小工具。
这是一个非常适合用来原型开发物联网代码的设备。
底座可以连接乐高积木。它内置电池组,配有可拆卸的充电座,但也可以通过 USB C 充电。该设备包含一个双核 240MHz RISC 处理器、一个 320x240x16bpp LCD 屏幕、16MB 闪存空间、512kB SRAM(约 360kB 可用)、4MB PSRAM、3 个可编程按钮、3 个 Grove 扩展端口、2 条 Neopixel LED 灯带、一个 SD 读卡器、一个适用于非常简单的噪音检测的扬声器和麦克风、一个陀螺仪、一个 LoRA 无线电以及 WiFi 2.4GHz 和蓝牙/BLE。该设备还可以与其他模块“堆叠”以增加广泛的功能,至少理论上是这样。实际上,我还没有尝试过任何这些模块。它们有点贵,而且我更喜欢 DIY 而不是即插即用的现成系统。
文档还提到该设备包含一个 BMM150 磁力计(基本上是基于 MEMS 的罗盘),但我即使使用工厂代码也无法使其正常工作。我不确定 Fire 2.6 版本是否包含它,但早期版本包含。我的设备可能存在缺陷。文档称其地址为 0x10,但我找到的代码将其地址设为 0x13。我都试过了。都没有成功。我想如果我在这东西的陪伴下在野外迷路了,我就麻烦了。
我对设备自带的代码功能集不满意,于是自己编写了代码。LCD 和 LED 灯带现在由 htcw_gfx 驱动,提供了我图形库的所有功能。整个项目都打包在 Platform IO 项目中,您可以下载它,将其复制到您的项目文件夹,并将其用作新的 M5 Stack Fire 项目的模板。我还没有支持该设备的电源管理或 LoRa 功能。目前您需要使用 M5 的库来实现这些功能,因为这仍然是进行中的项目。
我在项目中包含了几种 True Type/Open Type 字体作为可嵌入的头文件,但您可以使用上述链接中的 头文件生成器 将大多数 TTF 和 OTF 文件转换为头文件。您也可以使用它将 JPG 嵌入为头文件。我使用 fontsquirrel.com 来查找字体。
我们将一步步介绍如何使用提供的项目模板创建新应用程序。
开始使用这个项目
我们将使用 Arduino 框架,尽管也可以针对 ESP-IDF 来使用此设备。Arduino 框架享有广泛的设备支持,并且网上有很多关于它的示例代码,所以我推荐用于 ESP32 开发。此外,GFX 的显示驱动程序目前在 Arduino 下表现最佳。
我们将使用 Platform IO 进行开发。如果您还没有安装它,请立即安装,因为此项目无法与 Arduino IDE 配合使用。这是大学级别的,而 Arduino IDE 只是初中水平。它根本无法做到我们需要它做的事情,比如使用 GNU C++17。安装过程包括安装 Python、安装 Git、安装 VS Code,然后使用 VS Code 安装 Platform IO 扩展。
完成这些步骤后,您应该能够下载空项目,解压缩它,然后在 VS Code 中打开文件夹,Platform IO 最终会加载自身并围绕您的项目进行设置。第一次执行此操作可能需要很长时间,因为它只是在后台下载必需的组件才能使其正常工作 - 它直到第一次运行时才真正完成安装。据我记忆,其中一些组件超过 100MB,所以根据您的互联网连接情况,您可以去准备三明治。
打开您的 M5Stack Fire,然后使用提供的 USB 数据线将其连接到您的 PC。只要您没有连接其他物联网设备,Platform IO 就会自动检测其注册的 COM 端口。
空项目模板
这个空项目并不是完全空的,但它的目的是让您尽快上手。
让我们分解一下并检查各个组件。
platformio.ini
此文件包含项目的全局设置。基本上是项目的“项目文件”。其中包含您正在使用的芯片组、软件框架、依赖项、COM 端口信息、编译器设置以及其他类似内容。
[env:m5stack-fire]
platform = espressif32
board = m5stack-fire
framework = arduino
upload_speed = 921600
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
lib_ldf_mode = deep
lib_deps =
codewitch-honey-crisis/htcw_ili9341
codewitch-honey-crisis/htcw_mpu6886
codewitch-honey-crisis/htcw_w2812
codewitch-honey-crisis/htcw_button
build_unflags = -std=gnu++11
build_flags = -std=gnu++17
-DBOARD_HAS_PSRAM
-mfix-esp32-psram-cache-issue
此文件通常不应由您修改。您可以使用 PlatformIO IDE 的“快速任务”下的“库”功能添加依赖项。其中的依赖项用于使用 M5 Stack Fire 的外设,例如 ILI9342C 显示屏和 W2812 Neopixel LED 灯带。
字体头文件
/include 目录下有几个头文件。这些是可嵌入的 True Type/Open Type 字体。如果您需要渲染精美的抗锯齿文本,可以包含一个或多个这些文件到您的项目中。我们稍后将通过示例详细介绍。
**由于某种硬件问题以及 M5 公司可能奇怪的 SPI 接线方式,在此显示器上进行抗锯齿处理速度非常慢(可以说是不可接受)。解决方法是绘制带有不透明背景的文本,或者将文本绘制到位图,然后再将该位图绘制到屏幕上。
main.cpp
/src 目录下是我们的主文件,应用程序的核心内容就在这里。在此文件中,我们进行所有硬件设置,然后有一些样板代码用于连接和初始化硬件,再加上一些可以轻松丢弃的示例代码。
为了让您能够找到方向,我们简要介绍一下其中的内容。以下所有代码都是样板代码,除了 `#include "Ubuntu.hpp"` 行,它包含了我们的字体。您可以根据需要删除或替换该行以满足您的目的。
#include <Arduino.h>
#include <SPIFFS.h>
#include <SD.h>
#include <mpu6886.hpp>
#include <tft_io.hpp>
#include <ili9341.hpp>
#include <w2812.hpp>
#include <htcw_button.hpp>
#include <gfx.hpp>
// font for example
// not necessary
#include "Ubuntu.hpp"
using namespace arduino;
using namespace gfx;
// pin assignments
constexpr static const uint8_t spi_host = VSPI;
constexpr static const int8_t lcd_pin_bl = 32;
constexpr static const int8_t lcd_pin_dc = 27;
constexpr static const int8_t lcd_pin_rst = 33;
constexpr static const int8_t lcd_pin_cs = 14;
constexpr static const int8_t sd_pin_cs = 4;
constexpr static const int8_t speaker_pin_cs = 25;
constexpr static const int8_t mic_pin_cs = 34;
constexpr static const int8_t button_a_pin = 39;
constexpr static const int8_t button_b_pin = 38;
constexpr static const int8_t button_c_pin = 37;
constexpr static const int8_t led_pin = 15;
constexpr static const int8_t spi_pin_mosi = 23;
constexpr static const int8_t spi_pin_clk = 18;
constexpr static const int8_t spi_pin_miso = 19;
using bus_t = tft_spi_ex<spi_host,
lcd_pin_cs,
spi_pin_mosi,
-1,
spi_pin_clk,
SPI_MODE0,
true,
320 * 240 * 2 + 8, 2>;
using lcd_t = ili9342c<lcd_pin_dc,
lcd_pin_rst,
lcd_pin_bl,
bus_t,
1,
true,
400,
200>;
上面我们为相关的引脚分配定义了常量,然后通过 SPI 总线初始化了显示屏,并启用了 DMA 传输,这样就可以使用 GFX 的 `draw::bitmap_async<>()` 在后台将位图快速发送到显示屏,并在调用后立即返回 - 这绝对是一项高级技术,但需要时可用。
接下来,我们声明一些别名,以便访问我们显示屏的 16 位命名颜色和我们 Neopixel LED 灯带的 24 位命名颜色。之后,我们声明我们所有硬件的实例。
// lcd colors
using color_t = color<typename lcd_t::pixel_type>;
// led strip colors
using lscolor_t = color<typename w2812::pixel_type>;
lcd_t lcd;
// declare the MPU6886 that's attached
// to the first I2C host
mpu6886 gyro(i2c_container<0>::instance());
// the following is equiv at least on the ESP32
// mpu6886 gyro(Wire);
w2812 led_strips({5,2},led_pin,NEO_GBR);
button<button_a_pin,10,true> button_a;
button<button_b_pin,10,true> button_b;
button<button_c_pin,10,true> button_c;
上面,您将看到声明了 LCD 显示屏 `lcd`、MEMS 加速度计/陀螺仪 `gyro`、Neopixel 灯带 `led_strips`,最后是 3 个按钮 `button_a`、`button_b` 和 `button_c`。
接下来是我们的 M5 Stack 初始化例程,包括启动过程中的一个最小启动屏幕。
void initialize_m5stack_fire() {
Serial.begin(115200);
SPIFFS.begin(false);
SD.begin(4,spi_container<spi_host>::instance());
lcd.initialize();
led_strips.fill(led_strips.bounds(),lscolor_t::purple);
lcd.fill(lcd.bounds(),color_t::purple);
rect16 rect(0,0,64,64);
rect.center_inplace(lcd.bounds());
lcd.fill(rect,color_t::white);
lcd.fill(rect.inflate(-8,-8),color_t::purple);
gyro.initialize();
// see https://github.com/m5stack/m5-docs/blob/master/docs/en/core/fire.md
pinMode(led_pin, OUTPUT_OPEN_DRAIN);
led_strips.initialize();
button_a.initialize();
button_b.initialize();
button_c.initialize();
}
启动屏幕不会被初始化代码清除,允许您通过自己的代码延长启动时间(如果启动需要额外时间)。
接下来是您需要修改的代码。
// for the button callbacks
char button_states[3];
void buttons_callback(bool pressed, void* state) {
Serial.printf("Button %c %s\n",*(char*)state,pressed?"pressed":"released");
}
void setup() {
initialize_m5stack_fire();
// setup the button callbacks (optional)
button_states[0]='a';
button_states[1]='b';
button_states[2]='c';
button_a.callback(buttons_callback,button_states);
button_b.callback(buttons_callback,button_states+1);
button_c.callback(buttons_callback,button_states+2);
// your code here
// example - go ahead and delete
lcd.fill(lcd.bounds(),color_t::black);
const char* m5_text = "M5Stack";
constexpr static const uint16_t text_height = 80;
srect16 text_rect;
open_text_info text_draw_info;
const open_font &text_font = Ubuntu;
text_draw_info.text = m5_text;
text_draw_info.font = &text_font;
text_draw_info.scale = text_font.scale(text_height);
text_draw_info.transparent_background = false;
text_rect = text_font.measure_text(ssize16::max(),
spoint16::zero(),
m5_text,
text_draw_info.scale)
.bounds()
.center((srect16)lcd.bounds())
.offset(0,-text_height/2);
draw::text(lcd,text_rect,text_draw_info,color_t::gray);
draw::line(lcd,
srect16(text_rect.x1,text_rect.y1,text_rect.x1,text_rect.y2)
.offset(80,0),
color_t::white);
const char* fire_text = "Fire";
text_draw_info.text = fire_text;
text_rect = text_font.measure_text(
ssize16::max(),
spoint16::zero(),
fire_text,text_draw_info.scale)
.bounds()
.center((srect16)lcd.bounds())
.offset(0,text_height/2);
draw::text(lcd,text_rect,text_draw_info,color_t::red);
led_strips.fill({0,0,4,0},lscolor_t::red);
led_strips.fill({0,1,4,1},lscolor_t::blue);
}
void loop() {
// pump the buttons to make sure
// their callbacks (if any) get
// fired
button_a.update();
button_b.update();
button_c.update();
}
启动后,它会显示文章顶部的 M5|Stack Fire。它还将按钮按下和释放事件转储到串行端口。所有这些代码都可以替换为您自己的代码。
main.cpp(第二版)
从 `// for the button callbacks` 开始,删除所有内容。
向上滚动到顶部,将 `#include "Ubuntu.hpp"` 更改为 `#include "Robinette.hpp"`。
将以下内容粘贴到源文件底部。
const char* text = "hello!";
const open_font &text_font = Robinette;
constexpr static const uint16_t text_height = 125;
// The above line is because some fonts are kinda small
// in lowercase. Use the above for that. Otherwise
// use the following:
//constexpr static const uint16_t text_height = 100;
srect16 text_rect;
open_text_info text_draw_info;
const rgb_pixel<16> colors_a[] = {
color_t::red,
color_t::orange,
color_t::yellow,
color_t::green,
color_t::blue,
color_t::purple
};
constexpr const size_t color_count_a = sizeof(colors_a)/sizeof(rgb_pixel<16>);
const rgb_pixel<16> colors_b[] = {
color_t::cyan,
color_t::pink,
color_t::white,
color_t::pink
};
constexpr const size_t color_count_b = sizeof(colors_b)/sizeof(rgb_pixel<16>);
const rgb_pixel<16> colors_c[] = {
color_t::red,
color_t::white,
color_t::blue,
};
constexpr const size_t color_count_c = sizeof(colors_c)/sizeof(rgb_pixel<16>);
int color_height;
unsigned int color_offset;
size_t color_count;
const rgb_pixel<16>* colors;
uint32_t led_strip_ts;
uint32_t led_strip_offset;
// the frame buffer type is based on the LCD's pixel format
using frame_buffer_t = bitmap_type_from<lcd_t>;
uint8_t* frame_buffer_data;
frame_buffer_t frame_buffer;
void set_colors(int i) {
switch(i) {
case 0:
colors = colors_a;
color_count = color_count_a;
break;
case 1:
colors = colors_b;
color_count = color_count_b;
break;
default:
colors = colors_c;
color_count = color_count_c;
break;
}
color_height = lcd.dimensions().height/color_count;
}
void setup() {
initialize_m5stack_fire();
led_strip_ts=0;
led_strip_offset = 0;
color_offset = 0;
// framebuffer is in PSRAM
frame_buffer_data = (uint8_t*)ps_malloc(frame_buffer_t::sizeof_buffer(lcd.dimensions()));
if(frame_buffer_data==nullptr) {
// shouldn't happen
Serial.println("Out of memory.");
while(true) {delay(10000);}
}
button_a.callback([](bool value,void*state){
if(value) {
Serial.println("a");
set_colors(0);
}
},nullptr);
button_b.callback([](bool value,void*state){
if(value) {
Serial.println("b");
set_colors(1);
}
},nullptr);
button_c.callback([](bool value,void*state){
if(value) {
Serial.println("c");
set_colors(2);
}
},nullptr);
// create a bitmap with the same format as the LCD
frame_buffer = create_bitmap_from(lcd,lcd.dimensions(),frame_buffer_data);
// choose the first "palette"
set_colors(0);
// draw the stripes
rect16 r(0,0,lcd.bounds().x2,color_height-1);
frame_buffer.fill(r,colors[0]);
for(size_t i = 1;i<color_count;++i) {
r.offset_inplace(0,color_height);
frame_buffer.fill(r,colors[i]);
}
// blt to the LCD
draw::bitmap(lcd,lcd.bounds(),frame_buffer,frame_buffer.bounds());
// precompute our text info
text_draw_info.text = text;
text_draw_info.font = &text_font;
text_draw_info.scale = text_font.scale(text_height);
text_rect = text_font.measure_text(ssize16::max(),
spoint16::zero(),
text,
text_draw_info.scale)
.bounds()
.center((srect16)lcd.bounds());
}
void loop() {
button_a.update();
button_b.update();
button_c.update();
// draw a trailing line color for each horizontal bar,
// expanding it downward by one pixel
rect16 r(0,
(color_height-1+color_offset)%lcd.dimensions().height,
lcd.bounds().x2,
(color_height-1+color_offset)%lcd.dimensions().height);
frame_buffer.fill(r,colors[0]);
for(size_t i = 1;i<color_count;++i) {
r.offset_inplace(0,color_height);
r.y1=r.y2=(r.y1%lcd.dimensions().height);
frame_buffer.fill(r,colors[i]);
}
// draw the font
draw::text(frame_buffer,
text_rect,
text_draw_info,
color_t::black);
// now blt the frame buffer to the display
draw::bitmap(lcd,
lcd.bounds(),
frame_buffer,
frame_buffer.bounds());
uint32_t ms = millis();
if(ms>=led_strip_ts+250) {
led_strip_ts = ms;
// suspend so we update all at once on resume
draw::suspend(led_strips);
// update each color
for(int y = 0;y<led_strips.dimensions().height;++y) {
for(int x = 0;x<led_strips.dimensions().width;++x) {
draw::point(led_strips,
point16(x,y),
colors[(led_strip_offset+
(x+(y*led_strips.dimensions().width)))
%color_count]);
}
}
// finally, refresh the strips
draw::resume(led_strips);
++led_strip_offset;
}
++color_offset;
}
下面的视频向您展示了它的作用。我们正在循环切换三种调色板,同时控制屏幕和 LED 灯带,并显示一些文本。按下按钮时,调色板会发生变化。请注意,我们使用 PSRAM 中的一个帧缓冲区来进行所有绘图操作,然后定期将其绘制到显示屏。这可以减少闪烁,并且由于我在设备显示屏上遇到的问题,可以在不牺牲性能的情况下正确地抗锯齿字体。
结论
就是这样。现在带着您的 M5 Stack Fire,出发去创造吧!
历史
- 2022 年 7 月 14 日 - 首次提交