还是老规矩,我这边使用的是segger,这边建议新手也使用这一个集成的IDE。
一、环境准备
1.1 编译工具链下载
下载链接: https://nucleisys.com/download.php 下载版本按照下图标注的版本

1.2 配置系统环境
将下载后的压缩包解压,注意解压后的文件存放的路径不能有中文 ,\gcc\bin你解压后的文件里面有一个文件夹叫gcc,然后点击gcc这个文件夹里面有一个bin
的文件夹,你双击打开,这个时候把完整的路径复制下来就是有你硬盘盘符的那个路径。然后根据下图的步骤,开始配置。

在下面这个步骤二当中,把你复制好的路径粘贴到里面去,然后点击上移把它移到最前面。

这个时候你就可以编译WiFi蓝牙sdk了。
二、实验思路
本次实验是在官方的ap历程里面进行更改,我们可以看到官方的历程里面是。告诉了我们如何让我们的开发版作为主机发送WiFi的,历程里面的里面,它主要是tcp的连接,我们这边是HTTP。
static void wifi_ap_task(void *param) {
printf("正在启动 SoftAP:%s ...\n", AP_SSID);
int ret = wifi_management_ap_start((char *)AP_SSID, (char *)AP_PASSWORD, 1, AUTH_MODE_WPA_WPA2, 0);
if (ret != 0) {
printf("SoftAP 启动失败!错误码:%d\n", ret);
sys_task_delete(NULL);
return;
}
printf("SoftAP 已启动,SSID:%s,密码:%s\n", AP_SSID, AP_PASSWORD);
led_init();
http_server_task(NULL);
sys_task_delete(NULL);
}
可以看到我们主要是通过这个函数来建立起我们的ap连接wifi_management_ap_start((char *)AP_SSID, (char *)AP_PASSWORD, 1, AUTH_MODE_WPA_WPA2, 0);
static void http_server_task(void *arg) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
printf("创建 Socket 失败!\n");
return;
}
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(HTTP_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
printf("绑定端口失败!\n");
close(listen_fd);
return;
}
if (listen(listen_fd, 5) < 0) {
printf("监听失败!\n");
close(listen_fd);
return;
}
printf("HTTP 服务器已启动,请连接 Wi-Fi 后访问 http://192.168.237.1\n");
while (1) {
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &len);
if (conn_fd >= 0) {
printf("客户端已连接:%s\n", inet_ntoa(client_addr.sin_addr));
http_server_serve(conn_fd);
}
}
}
上面的代码就是我们的一个核心了,socket(int domain, int type, int protocol);这个函数是原用来创建通信端点(socket),domain有几个常用的量(AF_INET IPv4 网络通信、AF_INET6 IPv6 网络通信 、AF_UNIX 本地进程间通信),type是用来设置socket类型,我们这里的SOCK_STREAM 是面向连接的TCP socket(可靠、有序),最后一个变量是指协议,一般有tcp和udp,这里我使用零就是自适应的意思,也就是自动选择。
当我们创建好通信节点之后,我们需要给节点进行分配端口和IP也就是我们那边的一个结构体分配的,sockaddr_in:专门用于IPv4地址的结构体,
struct sockaddr_in {
sa_family_t sin_family; // 地址族(AF_INET)
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IP地址
char sin_zero[8]; // 填充字节
};
server_addr.sin_family = AF_INET;相当于告诉系统:"我要用IP地址版本4"
server_addr.sin_port = htons(HTTP_PORT); HTTP_PORT = 80 htons()将16位整数从主机字节序转换为网络字节序
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); - 监听所有可用的网络接口服务器会接受来自任何IP地址的连接,里面的INADDR_ANY可以改换成你想要的IP这样子只会接收来自这个IP的访问
把这部分配置想象成开一家商店:
sin_family = 确定商店类型(餐饮店/服装店)
sin_port = 商店的门牌号(80号)
sin_addr= 商店的接待政策
NADDR_ANY = 欢迎所有顾客(本地、外地都接待)
bind()函数的将socket与特定的IP地址和端口号关联,相当于给服务器"分配电话号码";listen()函数将socket设置为监听状态,准备接受连接相当于让电话机进入"等待来电"模式,listen_fd要监听的socket,5是等待队列的最大长度当有多个客户端同时连接时,最多允许5个在队列中等待,超过的连接请求会被拒绝。
可以把socket想象成电话系统(socket() → bind() → listen() → accept() → read/write → close()):
socket() = 安装一部电话机
bind() = 分配电话号码(IP+端口)
listen() = 让电话处于待机状态
accept() = 接听来电
read/write = 通话交流
close() = 挂断电话
然后我们这里的读和写其实就是。发送HTTP请求,而这个请求其实就是一个网页,实际上这个网页的渲染是由我们的浏览器进行的,我们只是单纯的把一串字符发给了客户端,而客户端的浏览器在接收到了这个数据的时候,它会自行判断他为网页,而当我们的开关这个行为会被被系统监听,手机操作 → 发送HTTP请求 → MCU接收 → 执行操作 → 返回响应 → 网页更新。
| 步骤 | 手机操作 | MCU接收原理 | 电话系统比喻 |
|---|---|---|---|
| 1 | 用户点击"点亮LED"按钮 | 点击按钮触发HTTP GET请求(GET /api/control?led=on) | 你说"请亮灯" |
| 2 | 浏览器将请求发送到MCU的IP地址和端口 | MCU的HTTP服务器监听80端口,接收请求数据 | 电话接通,听到你说的话 |
| 3 | MCU解析请求中的指令(led=on) | 从HTTP请求中提取参数,判断需要执行的操作 | 你朋友理解了"亮灯"指令 |
| 4 | MCU控制LED引脚(PB2) | 通过GPIO控制LED亮灭 | 朋友执行"亮灯"操作 |
| 5 | MCU生成HTML响应(LED已亮) | 将状态信息转换为HTML代码 | 朋友回复"灯已亮" |
| 6 | 浏览器接收响应并更新网页 | 浏览器解析HTML,显示新状态 | 你看到手机屏幕显示"灯已亮" |
三、试验结果
四、代码
源代码见附件,将pdf后缀改为.c或txt
