发帖
1 0 0

【BW20-12F 】开发板 WiFi LED 状态指示项目开发教程

xiaozhou
注册会员

0

主题

1

回帖

105

积分

注册会员

积分
105
BW系列 12 1 4 天前
[i=s] 本帖最后由 xiaozhou 于 2025-11-21 16:20 编辑 [/i]

【BW20-12F 】开发板 WiFi LED 状态指示项目开发教程

开发板:BW20-12F
开发环境:Ubuntu 22.04 (VMware虚拟机)
开发时间:2025年11月20日

一、项目简介

这个项目实现了一个基于 FreeRTOS 的 WiFi 连接状态指示系统。开发板上的 RGB LED
会根据 WiFi 连接状态显示不同的颜色:

  • 红灯快闪:未连接 WiFi
  • 黄灯慢闪:正在连接中
  • 绿灯常亮:WiFi 已连接并获取到 IP
  • 红灯常亮:连接断开
  • 紫灯闪烁:连接错误

这个项目很适合用来学习嵌入式开发的基础知识,包括 GPIO 控制、WiFi 连接、
FreeRTOS 任务管理等内容。

二、开发环境准备

2.1 硬件准备

  • BW20-12F 开发板 x1
  • 数据线
  • Ubuntu 虚拟机 (VMware Workstation)

image.png

2.2 软件环境

我使用的是 Ubuntu 22.04 系统,Ameba RTOS SDK 已经下载在桌面。

首先要解决一个常见问题:Python 包安装超时。因为默认使用国外的 PyPI 源,
下载速度很慢,所以我们先配置清华大学的镜像源:

image.png
命令:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

配置完成后,进入 SDK 目录并初始化环境:

image.png
命令:
cd ~/Desktop/ameba/ameba-rtos
source ameba.sh

第一次运行会自动安装一些 Python 依赖包,耐心等待安装完成。

2.3 解决串口权限问题

在虚拟机中使用 USB 串口时,经常会遇到设备无法识别的问题。这是因为一个叫
brltty 的盲文设备服务会占用串口。我们需要停用这个服务:

image.png
命令:
sudo systemctl stop brltty-udev.service
sudo systemctl mask brltty-udev.service
sudo systemctl disable brltty.service

然后在 VMware 菜单中连接 USB 设备:
VM -> Removable Devices -> QinHeng Electronics CH340 -> Connect

image.png
命令:
ls -la /dev/ttyUSB*

正常情况下会看到 /dev/ttyUSB0 设备。

三、项目目录结构

我在 SDK 的 my_project 目录下创建了项目文件夹:
命令:
mkdir -p ~/Desktop/ameba/ameba-rtos/my_project/wifi_led_component
cd ~/Desktop/ameba/ameba-rtos/my_project/wifi_led_component

项目的目录结构如下:

wifi_led_component/
├── CMakeLists.txt # CMake 构建配置文件
├── include/ # 头文件目录(可选)
├── wifi_led.h # LED 和 WiFi 控制头文件
├── wifi_led.c # LED 和 WiFi 控制实现
└── wifi_led_example.c # 示例程序主文件

四、代码实现

4.1 CMakeLists.txt 配置文件

这个文件告诉编译系统如何构建我们的项目。它分为两部分:

  1. 公共部分(public):定义给其他模块使用的头文件和宏
  2. 私有部分(private):定义本模块内部使用的源文件

需要注意的是,源文件路径必须正确。我一开始把文件放在项目根目录,但 CMakeLists.txt
中写的是 src/wifi_led.c,导致编译时找不到文件。后来我把 CMakeLists.txt 改成了:

ameba_list_append(private_sources
wifi_led.c # 注意:不是 src/wifi_led.c
wifi_led_example.c
)

这样编译器才能找到文件。

image.png

4.2 GPIO 和头文件问题

一开始我参考网上的代码,使用了很多不存在的头文件,比如 wifi_conf.h、
osdep_service.h 等,导致编译失败。

后来我查找 SDK 源码,发现正确的头文件应该是:

  • wifi_api.h:WiFi API 函数声明
  • wifi_api_types.h:WiFi 相关类型定义
  • wifi_api_event.h:WiFi 事件定义(虽然后来发现事件回调 API 不可用)

image.png
命令:
find ~/Desktop/ameba/ameba-rtos/component/wifi/api -name "*.h"
开发板上:

  • PB17 是绿色 LED
  • PB18 是红色 LED
  • PB19 是蓝色 LED

56f17ab88b927aa94375d04b2bc5dbb1.png

4.3 WiFi 连接的关键点

在实现 WiFi 连接功能时,我遇到了几个坑:

问题1:结构体初始化错误导致崩溃
一开始我用 strncpy 复制密码到结构体,但实际上 rtw_network_info 结构体中的
password 字段是一个指针,不是数组!正确的做法是直接赋值指针:

connect_param.password = (unsigned char *)password;

问题2:忘记调用 DHCP
WiFi 连接成功后,还需要调用 LwIP_DHCP() 来获取 IP 地址,否则无法正常通信。
这是我查看官方示例代码后才发现的。

image.png
命令:
cat ~/Desktop/ameba/ameba-rtos/component/example/wifi/wifi_user_reconnect/example_wifi_user_reconnect.c | grep -A 30 "wifi_connect"

最终的正确实现是:

// 1. 启用 WiFi
wifi_on(RTW_MODE_STA);

// 2. 设置连接参数
struct rtw_network_info connect_param = {0};
memcpy(connect_param.ssid.val, ssid, strlen(ssid));
connect_param.ssid.len = strlen(ssid);
connect_param.password = (unsigned char *)password;
connect_param.password_len = strlen(password);

// 3. 连接 WiFi
wifi_connect(&connect_param, 1);

// 4. 获取 IP 地址
LwIP_DHCP(0, DHCP_START);

4.4 FreeRTOS 任务自动启动

为了让程序启动时自动运行,我使用了 GCC 的 attribute((constructor)) 属性:

void attribute((constructor)) auto_start(void)
{
rtos_task_t task = NULL;
rtos_task_create(&task, "WIFI_TASK", (rtos_task_t)wifi_task,
NULL, 4096, 2);
}

这个函数会在 main() 之前自动执行,创建我们的 WiFi 连接任务。

五、编译和烧录

5.1 编译项目

image.png

命令:
cd ~/Desktop/ameba/ameba-rtos/amebadplus_gcc_project
build.py -a ~/Desktop/ameba/ameba-rtos/my_project/wifi_led_component/

编译过程可能会遇到各种警告和错误,比如:

  • 未使用的参数警告:在函数中添加 (void)param; 来消除
  • 类型不匹配:注意 s32 类型要用 %ld 格式化输出
  • 头文件找不到:检查 #include 的文件名是否正确

image.png

如果看到 "Image manipulating end" 说明编译成功了!

5.2 烧录固件

image.png
命令:
flash.py -p /dev/ttyUSB0

烧录过程大约需要几秒钟,看到 "Flash completed" 表示烧录成功。

5.3 查看串口输出

使用串口监视工具查看程序运行情况:
image.png

按下开发板的复位按钮,你会看到系统启动信息,然后是我们程序的输出:

=======================================
WiFi LED 示例程序

SSID: Mi 11

[WIFI_LED] LED初始化完成
[WIFI_LED] 组件初始化完成
[WIFI_LED] 状态: 未连接
[WIFI_LED] 连接WiFi: Mi 11
[WIFI_LED] 状态: 连接中
[WIFI_LED] 开始连接...
[WIFI_LED] WiFi连接成功,开始DHCP...
[WIFI_LED] DHCP成功!
[WIFI_LED] 状态: 已连接
[EXAMPLE] ✓ IP: 192.168.35.132

image.png

同时 LED 会从红灯快闪(未连接)变成黄灯慢闪(连接中),最后变成蓝灯常亮(可是应该是绿灯呀难道是引脚不对?可能是我引脚定义没看对)

64106884fc43c364419ec0e7df004422.jpeg

六、遇到的问题和解决方案

6.1 编译错误:找不到源文件

错误信息:
Cannot find source file: src/wifi_led.c

解决方案:
检查 CMakeLists.txt 中的源文件路径,确保与实际文件位置一致。如果文件在
项目根目录,就不要加 src/ 前缀。

6.2 编译错误:头文件不存在

错误信息:
fatal error: wifi_conf.h: No such file or directory

解决方案:
不要使用网上搜到的代码,要查看 SDK 实际提供了哪些头文件。使用 find 命令
查找正确的头文件名。

6.3 运行时崩溃:内存访问违规

错误信息:
MemManage Fault: Memory management fault is caused by data access violation

解决方案:
检查指针是否正确初始化。在我的案例中,是因为把 password 指针当成数组来
使用,导致访问了空地址。查看官方示例代码,发现应该直接赋值指针。

6.4 WiFi 连接失败

如果 WiFi 连接失败,检查以下几点:

  1. SSID 和密码是否正确
  2. WiFi 路由器是否开启了 2.4GHz 频段(BW20-12F 只支持 2.4GHz)
  3. 路由器是否设置了 MAC 地址过滤
  4. 是否调用了 wifi_on() 启用 WiFi

6.5 串口无法识别

在虚拟机中,USB 串口经常被 brltty 服务占用,导致无法使用。按照前面的步骤
停用 brltty 服务即可。

七、代码文件完整内容

7.1 wifi_led.h

#ifndef WIFI_LED_H
#define WIFI_LED_H

#ifdef __cplusplus
extern "C" {
#endif

#include "ameba_soc.h"
#include "os_wrapper.h"
#include "wifi_api.h" // WiFi API函数
#include "wifi_api_types.h" // WiFi类型定义
#include "wifi_api_event.h" // WiFi事件
#include <stdio.h>
#include <string.h>

// LED GPIO引脚定义 - 修正颜色
#define LED_GREEN_GPIO _PB_17 // 绿色
#define LED_RED_GPIO _PB_18 // 红色
#define LED_BLUE_GPIO _PB_19 // 蓝色

// WiFi状态枚举
typedef enum {
WIFI_LED_STATE_IDLE = 0,
WIFI_LED_STATE_CONNECTING,
WIFI_LED_STATE_CONNECTED,
WIFI_LED_STATE_DISCONNECTED,
WIFI_LED_STATE_ERROR
} wifi_led_state_t;

extern wifi_led_state_t current_led_state;

void wifi_led_init(void);
void wifi_led_set_state(wifi_led_state_t state);
void wifi_led_deinit(void);
int wifi_led_connect(const char *ssid, const char *password);

// WiFi配置记得改成你自己的!!!
#define WIFI_SSID "Mi 11"
#define WIFI_PASSWORD "12345678."

#ifdef __cplusplus
}
#endif

#endif /* WIFI_LED_H */

7.2 wifi_led.c

#include "wifi_led.h"
#include "lwip_netconf.h"

#define TAG "WIFI_LED"

wifi_led_state_t current_led_state = WIFI_LED_STATE_IDLE;
static rtos_task_t led_task_handle = NULL;
static uint8_t led_task_running = 0;

static void led_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.GPIO_Pin = LED_GREEN_GPIO;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_Init(&GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin = LED_RED_GPIO;
GPIO_Init(&GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin = LED_BLUE_GPIO;
GPIO_Init(&GPIO_InitStruct);

GPIO_WriteBit(LED_GREEN_GPIO, 0);
GPIO_WriteBit(LED_RED_GPIO, 0);
GPIO_WriteBit(LED_BLUE_GPIO, 0);

printf("\r\n[%s] LED初始化完成\r\n", TAG);

}

static void set_led_color(uint8_t red, uint8_t green, uint8_t blue)
{
GPIO_WriteBit(LED_RED_GPIO, red);
GPIO_WriteBit(LED_GREEN_GPIO, green);
GPIO_WriteBit(LED_BLUE_GPIO, blue);
}

static void led_control_task(void *param)
{
uint32_t tick = 0;
(void)param;

while (led_task_running) {
    tick++;
  
    switch (current_led_state) {
        case WIFI_LED_STATE_IDLE:
            set_led_color((tick % 2), 0, 0);
            rtos_time_delay_ms(200);
            break;
  
        case WIFI_LED_STATE_CONNECTING:
            if (tick % 2) {
                set_led_color(1, 1, 0);
            } else {
                set_led_color(0, 0, 0);
            }
            rtos_time_delay_ms(500);
            break;
  
        case WIFI_LED_STATE_CONNECTED:
            set_led_color(0, 1, 0);
            rtos_time_delay_ms(500);
            break;
  
        case WIFI_LED_STATE_DISCONNECTED:
            set_led_color(1, 0, 0);
            rtos_time_delay_ms(500);
            break;
  
        case WIFI_LED_STATE_ERROR:
            set_led_color((tick % 2), 0, (tick % 2));
            rtos_time_delay_ms(300);
            break;
  
        default:
            rtos_time_delay_ms(100);
            break;
    }
}

set_led_color(0, 0, 0);
rtos_task_delete(NULL);

}

void wifi_led_init(void)
{
led_gpio_init();

led_task_running = 1;
if (rtos_task_create(&led_task_handle, "led_task", 
                     (rtos_task_t)led_control_task, 
                     NULL, 1024, 1) != 0) {
    printf("\r\n[%s] LED任务创建失败!\r\n", TAG);
    led_task_running = 0;
    return;
}

printf("\r\n[%s] 组件初始化完成\r\n", TAG);

}

void wifi_led_set_state(wifi_led_state_t state)
{
const char *states[] = {"未连接", "连接中", "已连接", "已断开", "错误"};
current_led_state = state;
printf("[%s] 状态: %s\r\n", TAG, states[state]);
}

// 完全基于官方示例的WiFi连接
int wifi_led_connect(const char *ssid, const char *password)
{
int ret;
struct rtw_network_info connect_param = {0};

printf("\r\n[%s] 连接WiFi: %s\r\n", TAG, ssid);
wifi_led_set_state(WIFI_LED_STATE_CONNECTING);

// 启用WiFi
ret = wifi_on(RTW_MODE_STA);
if (ret < 0) {
    printf("[%s] WiFi启动失败: %d\r\n", TAG, ret);
    wifi_led_set_state(WIFI_LED_STATE_ERROR);
    return -1;
}

rtos_time_delay_ms(1000);

// 按照官方示例设置参数
memcpy(connect_param.ssid.val, ssid, strlen(ssid));
connect_param.ssid.len = strlen(ssid);
connect_param.password = (unsigned char *)password;
connect_param.password_len = strlen(password);

printf("[%s] 开始连接...\r\n", TAG);

// 连接WiFi (阻塞模式)
ret = wifi_connect(&connect_param, 1);

if (ret != RTK_SUCCESS) {
    printf("[%s] WiFi连接失败: %d\r\n", TAG, ret);
    wifi_led_set_state(WIFI_LED_STATE_ERROR);
    return -1;
}

printf("[%s] WiFi连接成功,开始DHCP...\r\n", TAG);

// DHCP获取IP地址
ret = LwIP_DHCP(0, DHCP_START);
if (ret == DHCP_ADDRESS_ASSIGNED) {
    printf("[%s] DHCP成功!\r\n", TAG);
    wifi_led_set_state(WIFI_LED_STATE_CONNECTED);
    return 0;
} else {
    printf("[%s] DHCP失败: %d\r\n", TAG, ret);
    wifi_disconnect();
    wifi_led_set_state(WIFI_LED_STATE_ERROR);
    return -1;
}

}

void wifi_led_deinit(void)
{
led_task_running = 0;
rtos_time_delay_ms(200);
set_led_color(0, 0, 0);
printf("[%s] 已反初始化\r\n", TAG);
}

7.3 wifi_led_example.c

#include "wifi_led.h"
#include "lwip_netconf.h"

#define TAG "EXAMPLE"

// WiFi连接任务
static void wifi_task(void *param)
{
(void)param;

rtos_time_delay_ms(3000);

printf("\r\n");
printf("=======================================\r\n");
printf("     WiFi LED 示例程序\r\n");
printf("=======================================\r\n");
printf("SSID: %s\r\n", WIFI_SSID);
printf("=======================================\r\n\r\n");

// 初始化LED
wifi_led_init();
wifi_led_set_state(WIFI_LED_STATE_IDLE);

rtos_time_delay_ms(2000);

// 连接WiFi
if (wifi_led_connect(WIFI_SSID, WIFI_PASSWORD) == 0) {
    // 等待获取IP
    printf("[%s] 等待获取IP...\r\n", TAG);
    int retry = 30;
    while (retry-- > 0) {
        uint8_t *ip = LwIP_GetIP(0); // 修正:传入索引而不是指针
        if (ip && ip[0] != 0) {
            printf("\r\n[%s] ✓ IP: %d.%d.%d.%d\r\n\r\n", TAG, 
                   ip[0], ip[1], ip[2], ip[3]);
            break;
        }
        rtos_time_delay_ms(1000);
    }
  
    if (retry <= 0) {
        printf("[%s] × 获取IP超时\r\n", TAG);
    }
}

// 保持运行
while (1) {
    rtos_time_delay_ms(5000);
}

}

// 自动启动
void attribute((constructor)) auto_start(void)
{
rtos_task_t task = NULL;
rtos_task_create(&task, "WIFI_TASK", (rtos_task_t)wifi_task, NULL, 4096, 2);
}

int wifi_led_example(void)
{
return 0;
}

7.4 CMakeLists.txt

##########################################################################################

/* This part defines public part of the component

/* Public part will be used as global build configures for all component

set(public_includes) #public include directories, NOTE: relative path is OK
set(public_definitions) #public definitions
set(public_libraries) #public libraries(files), NOTE: linked with whole-archive options

#----------------------------------------#
/.Component public part, user config begin

#添加公共包含目录

ameba_list_append(public_includes
include
)
添加公共宏定义(可选)
ameba_list_append(public_definitions

ENABLE_WIFI_LED_DEBUG=1
)
Component public part, user config end

#----------------------------------------#

#WARNING: Fixed section, DO NOT change!
ameba_global_include(${public_includes})
ameba_global_define(${public_definitions})
ameba_global_library(${public_libraries}) #default: whole-archived

##########################################################################################

##* This part defines private part of the component

##* Private part is used to build target of current component

#* NOTE: The build API guarantees the global build configures(mentioned above)

##* applied to the target automatically. So if any configure was already added

* to public above, it's unnecessary to add again below.

#NOTE: User defined section, add your private build configures here

#You may use if-else condition to set these predefined variable

#They are only for ameba_add_internal_library/ameba_add_external_app_library/ameba_add_external_soc_library

set(private_sources) #private source files, NOTE: relative path is OK
set(private_includes) #private include directories, NOTE: relative path is OK
set(private_definitions) #private definitions
set(private_compile_options) #private compile_options

#------------------------------#

#Component private part, user config begin

#添加源文件

ameba_list_append(private_sources
wifi_led.c
wifi_led_example.c
)

#添加私有包含目录

ameba_list_append(private_includes
include
)

#添加私有宏定义(可选)

#ameba_list_append(private_definitions

#DEBUG_WIFI_LED=1

)

#Component private part, user config end

#------------------------------#

#WARNING: Select right API based on your component's release/not-release/standalone

###NOTE: For open-source component, always build from source
ameba_add_internal_library(wifi_led_component
p_SOURCES
${private_sources}
p_INCLUDES
${private_includes}
p_DEFINITIONS
${private_definitions}
p_COMPILE_OPTIONS
${private_compile_options}
)

八、总结和心得

通过这个项目,我学到了很多嵌入式开发的知识:

  1. 阅读官方文档和示例代码很重要
    网上的资料可能过时或不适用,最可靠的还是官方的示例代码。遇到问题时,
    先去 SDK 的 example 目录找找有没有类似的实现。

  2. 编译错误要逐个解决,不要急
    刚开始编译时各种报错,看起来很吓人。但仔细看错误信息,都会告诉你哪里
    出了问题。一个个解决,最后一定能编译通过。

  3. 指针和结构体要特别小心
    嵌入式开发中,一个小的指针错误就会导致程序崩溃。要养成初始化变量的
    好习惯,使用 memset() 清零结构体。

  4. 查找 API 的技巧
    使用 grep 和 find 命令可以快速找到函数定义和头文件位置:

    查找函数定义

    grep -r "wifi_connect" component/wifi --include="*.h"

    查找结构体定义

    grep -A 30 "struct rtw_network_info" component/wifi/api/*.h

    查找示例代码

    find component/example -name "*.c" -exec grep -l "wifi_connect" {} ;

  5. 虚拟机开发的注意事项
    在虚拟机中开发要注意 USB 设备的连接和权限问题。遇到串口无法识别时,
    首先检查是否在 VMware 中连接了设备,然后检查是否有 brltty 冲突。

这个项目从开始到完成花了我一个下午的时间,遇到了各种问题,但最终都解决了。
最有成就感的时刻就是看到 LED 随着 WiFi 状态变化而改变颜色!

希望这个教程能帮助到其他学习 Ameba 开发的朋友。如果有问题,欢迎交流讨论。

==============================================
九、参考资料

  1. Ameba D Plus 数据手册
  2. Ameba RTOS SDK 官方文档
  3. FreeRTOS 官方文档:https://www.freertos.org/
  4. LwIP 文档:https://www.nongnu.org/lwip/

==============================================
附录:完整的命令清单

环境准备

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
cd ~/Desktop/ameba/ameba-rtos
source ameba.sh

解决串口问题

sudo systemctl stop brltty-udev.service
sudo systemctl mask brltty-udev.service
sudo systemctl disable brltty.service
ls -la /dev/ttyUSB*

创建项目

mkdir -p ~/Desktop/ameba/ameba-rtos/my_project/wifi_led_component
cd ~/Desktop/ameba/ameba-rtos/my_project/wifi_led_component

编译和烧录

cd ~/Desktop/ameba/ameba-rtos/amebadplus_gcc_project
build.py -a ~/Desktop/ameba/ameba-rtos/my_project/wifi_led_component/
flash.py -p /dev/ttyUSB0

查找 API(调试用)

find ~/Desktop/ameba/ameba-rtos/component/wifi/api -name ".h"
grep -r "wifi_connect" ~/Desktop/ameba/ameba-rtos/component/wifi --include="
.h"
grep -A 30 "struct rtw_network_info" ~/Desktop/ameba/ameba-rtos/component/wifi/api/wifi_api_types.h

END

──── 0人觉得很赞 ────

使用道具 举报

4 天前
是我对按可信论坛的编辑器第一次用,一断网保存不了,连上了后差点连刚写完的文档都没有了,能不能边写边自动保存啊
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 30186 个
  • 话题数: 44382 篇