news 2026/8/23 10:12:20

FT32F072X 移植U8G2 , mui

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FT32F072X 移植U8G2 , mui

上篇文章已经移植rtthread nano,boot,usb。今天开始移植u8g2到oled上,资源足够,完全没必要自己再重复造轮子。

我使用的oled是中景电子的0.96寸屏幕,128X64,显存芯片:sh1106.由于u8g2给我们做了大量的寄存器配置工作,我们不用关心寄存器功能(当然如果是做低功耗之类的,需要了解oled的省电原理),我们只需要注册回调初始化函数即可。

u8g2的库中我们使用csrc中文件,选择所有文件,注意这种u8x8_d_芯片名称.c的文件只要选择一个,我这里选择的是u8x8_d_ssd1306_128x64_noname.c,因为sh1106芯片就是调用这个文件,所以这个。这是我的目录结构

这里不要害怕选了这么多文件编译之后单片机存不下,在keil优化等级选择level 2,编译器会把没用到的直接丢弃。

1.u8g2_d_memory.c 这里我们选择下面这个函数,其他的全部删掉,如果U8G2_USE_DYNAMIC_ALLOC 这个开启了,请注释掉,这里不选择动态内存。

uint8_t *u8g2_m_16_8_f(uint8_t *page_cnt) { #ifdef U8G2_USE_DYNAMIC_ALLOC *page_cnt = 8; return 0; #else static uint8_t buf[1024]; *page_cnt = 8; return buf; #endif }

2. 找到u8g2_d_setup.c ,找到下面这个函数,其他的全部删掉。

/* sh1106 f */ void u8g2_Setup_sh1106_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb) { uint8_t tile_buf_height; uint8_t *buf; u8g2_SetupDisplay(u8g2, u8x8_d_sh1106_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, byte_cb, gpio_and_delay_cb); buf = u8g2_m_16_8_f(&tile_buf_height); u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation); }

3. 接下来新建一个gui_task.c,写入以下代码

static u8g2_t gui_u8g2; volatile char ui_key = 0; //由于用的是硬件IIC,这里啥都不用干。 uint8_t ft32_msg_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { #ifdef USING_SOFT_IIC switch(msg) { case U8X8_MSG_DELAY_MILLI: board_delay(1); // 延迟1ms break; case U8X8_MSG_DELAY_10MICRO: board_udelay(10);延迟10us break; case U8X8_MSG_DELAY_100NANO: __NOP(); // 1tick break; case U8X8_MSG_GPIO_I2C_CLOCK: if(arg_int) OLED_SCL_Set(); // 这里就是clk gpio的初始化。注意是开漏输出,推挽会把总线钳住,会通信异常 else OLED_SCL_Clr(); // break; case U8X8_MSG_GPIO_I2C_DATA: if(arg_int) OLED_SDA_Set(); // 这里就是data gpio的初始化。注意是开漏输出,推挽会把总线钳住,会通信异常 else OLED_SDA_Clr(); break; default: return 0; } return 1; #else switch(msg) { case U8X8_MSG_GPIO_AND_DELAY_INIT: break; case U8X8_MSG_DELAY_MILLI: // board_delay(arg_int); break; case U8X8_MSG_GPIO_I2C_CLOCK: break; case U8X8_MSG_GPIO_I2C_DATA: break; default: return 0; } return 1; // command processed successfully. #endif } //IIC回调函数 uint8_t u8x8_byte_oled_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { static uint8_t buffer[32]; /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */ static uint8_t buf_idx; uint8_t *data; switch(msg) { case U8X8_MSG_BYTE_SEND: data = (uint8_t *)arg_ptr; while( arg_int > 0 ) { buffer[buf_idx++] = *data; data++; arg_int--; } break; case U8X8_MSG_BYTE_INIT: /* add your custom code to init i2c subsystem */ //oled_IIC_init(); break; case U8X8_MSG_BYTE_START_TRANSFER: buf_idx = 0; break; case U8X8_MSG_BYTE_END_TRANSFER: IIC_Send(buffer, buf_idx); // 这里开始传数据,IIC通信请自己实现。 break; default: return 0; } return 1; } void gui_thread_entry(void *parameter) { rt_thread_delay(100); // 这里延迟是防止上电oled还没稳定。 // 这里U8G2_R2代表旋转角度,请根据自身的设备设置旋转角度。 u8g2_Setup_sh1106_i2c_128x64_noname_f(&gui_u8g2, U8G2_R2, u8x8_byte_oled_hw_i2c, ft32_msg_cb); // 初始化注册 u8g2_InitDisplay(&gui_u8g2); // 初始化屏幕 u8g2_SetPowerSave(&gui_u8g2, 0);// 关闭省电。 extern void my_mui_init(u8g2_t *u8g2); my_mui_init(&gui_u8g2); // 初始化mui 这里后面说明 for(;;) { //gui_process(); extern void draw_menu(u8g2_t *u8g2,char key); draw_menu(&gui_u8g2,ui_key); // 画图,ui_key 表示按键输入,自己实现。 ui_key = 0; // rt_thread_delay(150); } } int gui_task_int() { rt_thread_t tid; #ifdef RT_USING_HEAP tid = rt_thread_create("gui", gui_thread_entry, RT_NULL, RT_GUI_THREAD_STACK_SIZE, 5, 20); RT_ASSERT(tid != RT_NULL); #else rt_err_t result; tid = &gui_thread; result = rt_thread_init(tid, "gui", gui_thread_entry, RT_NULL, rt_thread_gui_stack, sizeof(rt_thread_gui_stack), 5, 20); RT_ASSERT(result == RT_EOK); /* if not define RT_USING_HEAP, using to eliminate the warning */ (void)result; #endif rt_thread_startup(tid); rt_kprintf("%s init\n", __func__); return 0; } INIT_APP_EXPORT(gui_task_int);

4. 接下来新建一个menu.c,我们移植官方的例程,将菜单功能做进来。

#include <mui.h> #include <mui_u8g2.h> mui_t mui; /* global variables which form the communication gateway between the user interface and the rest of the code */ uint8_t number_input = 2; // variable where the user can input a number between 0 and 9 uint8_t number_input2 = 100; // variable where the user can input a number between 0 and 9 uint8_t fruit_input = 2; uint8_t fruit_input2 = 2; uint8_t my_value3 = 0; uint8_t color_input = 0; uint8_t food_input = 0; uint8_t checkbox_input = 0; uint8_t direction_input = 0; uint8_t text_input[4] = { ' ',' ',' ',' '} ; uint8_t exit_code = 0; uint16_t list_selection = 0; uint16_t list_selection2 = 0; uint16_t list_selection3 = 0; uint16_t list_selection4 = 0; uint8_t array_pos = 0; uint8_t array_led_off_time[4] = { 10, 5, 3, 1}; uint8_t led_off_time = 0; uint8_t array_led_on_time[4] = { 10, 5, 3, 1}; uint8_t led_on_time = 0; //处理函数 uint8_t muif_array_pos_selection(mui_t *ui, uint8_t msg) { uint8_t return_value = 0; switch(msg) { case MUIF_MSG_FORM_START: led_off_time = array_led_off_time[array_pos]; led_on_time = array_led_on_time[array_pos]; return_value = mui_u8g2_u8_min_max_wm_mse_pi(ui, msg); break; case MUIF_MSG_FORM_END: return_value = mui_u8g2_u8_min_max_wm_mse_pi(ui, msg); break; case MUIF_MSG_CURSOR_SELECT: case MUIF_MSG_EVENT_NEXT: case MUIF_MSG_EVENT_PREV: array_led_off_time[array_pos] = led_off_time; array_led_on_time[array_pos] = led_on_time; return_value = mui_u8g2_u8_min_max_wm_mse_pi(ui, msg); led_off_time = array_led_off_time[array_pos]; led_on_time = array_led_on_time[array_pos]; break; default: return_value = mui_u8g2_u8_min_max_wm_mse_pi(ui, msg); } return return_value; } const char *animals[] = { "Bird", "Bison", "Cat", "Crow", "Dog", "Elephant", "Fish", "Gnu", "Horse", "Koala", "Lion", "Mouse", "Owl", "Rabbit", "Spider", "Turtle", "Zebra" }; uint16_t list_get_cnt(void *data) { return sizeof(animals)/sizeof(*animals); /* number of animals */ } const char *list_get_str(void *data, uint16_t index) { return animals[index]; } uint16_t menu_get_cnt(void *data) { return 10; /* number of menu entries */ } const char *menu_get_str(void *data, uint16_t index) { static const char *menu[] = { MUI_0 "Goto Main Menu", MUI_10 "Enter a number", MUI_11 "Parent/Child Selection", MUI_13 "Checkbox", MUI_14 "Radio Selection", MUI_15 "Text Input", MUI_16 "Single Line Selection", MUI_17 "List Line Selection", MUI_18 "Parent/Child List", MUI_20 "Array Edit", }; return menu[index]; } //菜单结构 fds_t fds_data[] = /* top level main menu */ MUI_FORM(0) MUI_STYLE(1) MUI_LABEL(5,10, "Main Menu") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_DATA("GP", MUI_10 "Enter a number|" MUI_11 "Parent/Child Selection|" MUI_13 "Checkbox|" MUI_14 "Radio Selection|" MUI_15 "Text Input|" MUI_16 "Single Line Selection|" MUI_17 "List Line Selection|" MUI_18 "Parent/Child List|" MUI_20 "Array Edit|" MUI_3 "Alternative Menu" ) MUI_XYA("GC", 5, 25, 0) MUI_XYA("GC", 5, 37, 1) MUI_XYA("GC", 5, 49, 2) MUI_XYA("GC", 5, 61, 3) MUI_FORM(3) MUI_STYLE(1) MUI_LABEL(5,10, "Alternative Menu") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_XYA("LG", 5, 25, 0) MUI_XYA("LG", 5, 37, 1) MUI_XYA("LG", 5, 49, 2) MUI_XYA("LG", 5, 61, 3) /* number entry demo */ MUI_FORM(10) MUI_STYLE(1) MUI_LABEL(5,10, "Number Menu") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_LABEL(5,27, "Number [mse]:") MUI_XY("IN",76, 27) MUI_LABEL(5,41, "Number [mud]:") MUI_XY("IH",76, 41) MUI_XYAT("G1",64, 59, 0, " OK ") /* parent / child selection */ MUI_FORM(11) MUI_STYLE(1) MUI_LABEL(5,10, "Parent/Child Selection") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_LABEL(5,24, "Color:") MUI_XYAT("IC",80, 24, 12, "red|orange|yellow|green|cyan|azure|blue|violet|magenta|rose") /* jump to sub form 12 */ MUI_LABEL(5,42, "We need:") MUI_STYLE(3) MUI_XYAT("ID",80, 49, 21, "\x30|\x31|\x32|\x33|\x34|\x35|\x36|\x37|\x38|\x39|\x40|\x41|\x42|\x43") MUI_STYLE(0) MUI_XYAT("G1",64, 60, 0, " OK ") /* combo box color selection */ MUI_FORM(12) MUI_STYLE(1) MUI_LABEL(5,10, "Color Selection") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_XYA("OC", 5, 30, 0) /* takeover the selection text from calling field ("red") */ MUI_XYA("OC", 5, 42, 1) /* takeover the selection text from calling field ("green") */ MUI_XYA("OC", 5, 54, 2) /* */ /* no ok required, clicking on the selection, will jump back */ /* Checkbox demo */ MUI_FORM(13) MUI_STYLE(1) MUI_LABEL(5,10, "Checkbox Menu") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_LABEL(5,30, "Checkbox:") MUI_XY("CB",60, 30) MUI_XYAT("G1",64, 59, 0, " OK ") /* Radio selection demo */ MUI_FORM(14) MUI_STYLE(1) MUI_LABEL(5,10, "Radio Selection Menu") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_XYAT("RS",10, 28,0,"North") MUI_XYAT("RS",10, 40,1,"South") MUI_XYAT("RS",65, 28,2,"East") MUI_XYAT("RS",65, 40,3,"West") MUI_XYAT("G1",64, 59, 0, " OK ") /* text demo */ MUI_FORM(15) MUI_STYLE(1) MUI_LABEL(5,10, "Enter Text Menu") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_LABEL(5,30, "Text:") MUI_STYLE(2) MUI_XY("T0",40, 30) MUI_XY("T1",48, 30) MUI_XY("T2",56, 30) MUI_XY("T3",64, 30) MUI_STYLE(0) MUI_XYAT("G1",64, 59, 0, " OK ") /* single line selection */ MUI_FORM(16) MUI_STYLE(1) MUI_LABEL(5,10, "Single Line Selection") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_LABEL(5,29, "Fruit [mse]:") MUI_XYAT("IF",60, 29, 60, "Banana|Apple|Melon|Cranberry") MUI_LABEL(5,43, "Fruit [mud]:") MUI_XYAT("IG",60, 43, 60, "Banana|Apple|Melon|Cranberry") MUI_XYAT("G1",64, 59, 0, " OK ") /* long list example with list callback functions */ MUI_FORM(17) MUI_STYLE(1) MUI_LABEL(5,10, "List Line Selection") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_LABEL(5,29, "List [mse]:") MUI_XYA("L1",60, 29, 60) MUI_LABEL(5,43, "List [mud]:") MUI_XYA("L2",60, 43, 60) MUI_XYAT("G1",64, 59, 0, " OK ") /* parent / child selection */ MUI_FORM(18) MUI_STYLE(1) MUI_LABEL(5,10, "Parent/Child List") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_LABEL(5,29, "Animal:") MUI_XYA("LP",50, 29, 19) /* jump to sub form 19 */ MUI_XYAT("G1",64, 59, 0, " OK ") /* combo box color selection */ MUI_FORM(19) MUI_STYLE(1) MUI_LABEL(5,10, "Animal Selection") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_XYA("LC", 5, 30, 0) /* takeover the selection text from calling field ("red") */ MUI_XYA("LC", 5, 42, 1) /* takeover the selection text from calling field ("green") */ MUI_XYA("LC", 5, 54, 2) /* */ /* no ok required, clicking on the selection, will jump back */ MUI_FORM(20) MUI_STYLE(1) MUI_LABEL(5,10, "Array Edit") MUI_XY("HR", 0,13) MUI_STYLE(0) MUI_LABEL(5,24, "Position:") MUI_XY("AP",76, 24) MUI_LABEL(5,35, "LED off:") MUI_XY("AF",76, 35) MUI_LABEL(5,46, "LED on:") MUI_XY("AN",76, 46) MUI_XYAT("G1",64, 59, 0, " OK ") /* combo box Food & Drink Selection, called from form 11 */ MUI_FORM(21) MUI_STYLE(1) MUI_LABEL(5,10, "Food & Drink Selection") MUI_XY("HR", 0,13) MUI_STYLE(3) MUI_XYA("OD", 3, 45, 0) MUI_XYA("OD", 28, 45, 1) MUI_XYA("OD", 53, 45, 2) MUI_XYA("OD", 78, 45, 3) MUI_XYA("OD", 103, 45, 4) ; uint8_t mui_hrule(mui_t *ui, uint8_t msg) { u8g2_t *u8g2 = mui_get_U8g2(ui); switch(msg) { case MUIF_MSG_DRAW: u8g2_DrawHLine(u8g2, 0, mui_get_y(ui), u8g2_GetDisplayWidth(u8g2)); break; } return 0; } //注册表 muif_t muif_list[] = { /* normal text style */ MUIF_U8G2_FONT_STYLE(0, u8g2_font_helvR08_tr), /* bold text style */ MUIF_U8G2_FONT_STYLE(1, u8g2_font_helvB08_tr), /* monospaced font */ MUIF_U8G2_FONT_STYLE(2, u8g2_font_profont12_tr), /* food and drink */ MUIF_U8G2_FONT_STYLE(3, u8g2_font_streamline_food_drink_t), /* horizontal line (hrule) */ MUIF_RO("HR", mui_hrule), /* main menu */ MUIF_RO("GP",mui_u8g2_goto_data), MUIF_BUTTON("GC", mui_u8g2_goto_form_w1_pi), /* Goto Form Button where the width is equal to the size of the text, spaces can be used to extend the size */ MUIF_BUTTON("G1", mui_u8g2_btn_goto_wm_fi), /* input for a number between 0 to 9 */ MUIF_U8G2_U8_MIN_MAX("IN", &number_input, 0, 9, mui_u8g2_u8_min_max_wm_mse_pi), /* input for a number between 0 to 100 */ MUIF_U8G2_U8_MIN_MAX("IH", &number_input2, 0, 100, mui_u8g2_u8_min_max_wm_mud_pi), /* input for text with four chars */ MUIF_VARIABLE("T0", text_input+0, mui_u8g2_u8_char_wm_mud_pi), MUIF_VARIABLE("T1", text_input+1, mui_u8g2_u8_char_wm_mud_pi), MUIF_VARIABLE("T2", text_input+2, mui_u8g2_u8_char_wm_mud_pi), MUIF_VARIABLE("T3", text_input+3, mui_u8g2_u8_char_wm_mud_pi), /* input for a fruit (0..3), implements a selection, where the user can cycle through the options */ MUIF_VARIABLE("IF",&fruit_input,mui_u8g2_u8_opt_line_wa_mse_pi), MUIF_VARIABLE("IG",&fruit_input2,mui_u8g2_u8_opt_line_wa_mud_pi), /* checkbox */ MUIF_VARIABLE("CB",&checkbox_input,mui_u8g2_u8_chkbox_wm_pi), /* the following two fields belong together and implement a single selection combo box to select a color */ MUIF_VARIABLE("IC",&color_input,mui_u8g2_u8_opt_parent_wm_pi), MUIF_VARIABLE("OC",&color_input,mui_u8g2_u8_opt_radio_child_w1_pi), MUIF_VARIABLE("ID",&food_input,mui_u8g2_u8_opt_parent_wm_pi), MUIF_VARIABLE("OD",&food_input,mui_u8g2_u8_opt_child_wm_pi), /* radio button style */ MUIF_VARIABLE("RS",&direction_input,mui_u8g2_u8_radio_wm_pi), MUIF_U8G2_U16_LIST("L1", &list_selection, NULL, list_get_str, list_get_cnt, mui_u8g2_u16_list_line_wa_mse_pi), MUIF_U8G2_U16_LIST("L2", &list_selection2, NULL, list_get_str, list_get_cnt, mui_u8g2_u16_list_line_wa_mud_pi), MUIF_U8G2_U16_LIST("LP", &list_selection3, NULL, list_get_str, list_get_cnt, mui_u8g2_u16_list_parent_wm_pi), MUIF_U8G2_U16_LIST("LC", &list_selection3, NULL, list_get_str, list_get_cnt, mui_u8g2_u16_list_child_w1_pi), MUIF_U8G2_U16_LIST("LG", &list_selection4, NULL, menu_get_str, menu_get_cnt, mui_u8g2_u16_list_goto_w1_pi), /* MUI_GOTO uses the fixed ".G" id and is intended for goto buttons. This is a full display width style button */ MUIF_GOTO(mui_u8g2_btn_goto_w1_pi), /* register MUI_LABEL, which is used to place read only text on a form */ MUIF_U8G2_LABEL(), /* array example */ MUIF_U8G2_U8_MIN_MAX("AP", &array_pos, 0, 3, muif_array_pos_selection), MUIF_U8G2_U8_MIN_MAX("AF", &led_off_time, 0, 20, mui_u8g2_u8_min_max_wm_mse_pi), MUIF_U8G2_U8_MIN_MAX("AN", &led_on_time, 0, 20, mui_u8g2_u8_min_max_wm_mse_pi), }; void my_mui_init(u8g2_t *u8g2) { u8g2_SetFontMode(u8g2,1); // 设置字体,1表示正常,0应该是反色 //初始化 mui_Init(&mui,u8g2,fds_data,muif_list,sizeof(muif_list)/sizeof(muif_t)); // 初始进哪个表。 mui_GotoForm(&mui,0,0); } void draw_menu(u8g2_t *u8g2,char key) { switch(key) { case 'D': // 确认按键 mui_SendSelect(&mui); break; case 'B': // 下一个按键处理 mui_NextField(&mui); break; case 'A': // 上一个按键 mui_PrevField(&mui); break; default: // 不处理 break; } if(mui_IsFormActive(&mui)) { u8g2_ClearBuffer(u8g2); // 每次画图前最好清屏 mui_Draw(&mui); // 画菜单。 u8g2_SendBuffer(u8g2); // 发送给屏幕 } }

效果展示:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 16:44:03

计算机毕业设计springboot基于Java的心理咨询系统 高校心理健康服务在线平台的设计与实现 基于SpringBoot的校园心理援助与咨询管理系统开发

计算机毕业设计springboot基于Java的心理咨询系统&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。随着社会竞争压力的加剧和生活节奏的加快&#xff0c;心理健康问题已成为影响公…

作者头像 李华
网站建设 2026/7/14 16:44:03

提升Umi-OCR多语言识别精准度:6大场景参数配置与性能优化指南

提升Umi-OCR多语言识别精准度&#xff1a;6大场景参数配置与性能优化指南 【免费下载链接】Umi-OCR Umi-OCR: 这是一个免费、开源、可批量处理的离线OCR软件&#xff0c;适用于Windows系统&#xff0c;支持截图OCR、批量OCR、二维码识别等功能。 项目地址: https://gitcode.c…

作者头像 李华
网站建设 2026/7/14 16:44:04

AI推理新范式:Groq LPU如何重塑大模型部署的成本与效率

1. 从“买得起”到“用得起”&#xff1a;企业AI推理的痛点与破局 最近和几个做AI应用的朋友聊天&#xff0c;大家聊得最多的不是模型有多厉害&#xff0c;而是“太贵了”。一个朋友的公司上线了一个智能客服系统&#xff0c;用的是市面上一个主流的70B参数模型&#xff0c;部署…

作者头像 李华
网站建设 2026/7/14 16:44:01

fx3u PLC 连接变频器:RDA → 485+,RDB → 485-‌

在RS-485半双工两线制通信中&#xff0c;‌RDA和RDB通常分别对应接收端的正&#xff08;&#xff09;和负&#xff08;-&#xff09;信号线‌。接线规则如下&#xff1a;‌RDA 接 485&#xff08;即 Data 或 A&#xff09;‌‌RDB 接 485-&#xff08;即 Data- 或 B-&#xff0…

作者头像 李华
网站建设 2026/7/14 16:44:02

备课到凌晨才懂,备好课的核心从不是写满教案

引言在教育的道路上&#xff0c;每一位教师都是一盏明灯&#xff0c;照亮学生前行的道路。然而&#xff0c;在这条路上&#xff0c;我们常常会遇到各种挑战和困惑。备课&#xff0c;作为教学工作的重要环节&#xff0c;往往让很多老师感到头疼。不少老师为了备好一节课&#xf…

作者头像 李华
网站建设 2026/7/14 16:44:02

BetaFlight BeeRotorF3 四轴飞行器F450机架从零配置指南

1. 从零开始的四轴飞行器之旅 第一次接触四轴飞行器时&#xff0c;我和大多数新手一样既兴奋又迷茫。F450机架作为经典的入门选择&#xff0c;搭配BetaFlight飞控和BeeRotorF3硬件&#xff0c;确实是个性价比极高的组合。这套配置最大的优势在于&#xff1a;既能满足新手学习需…

作者头像 李华