From f42a53331f084ad64c2421d1825c3290afb2d262 Mon Sep 17 00:00:00 2001 From: ykx <123456@ykx.com> Date: Mon, 21 Jul 2025 17:50:43 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E5=A4=A9=E4=BD=9C?= =?UTF-8?q?=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...00\345\244\251\347\254\224\350\256\260.md" | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" new file mode 100644 index 0000000..03325c3 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" @@ -0,0 +1,49 @@ +# 第一天笔记 + +## env的使用 + +`pkgs --upgrade` 升级软件包 + +`pkgs --update` 更新软件包 + +使用 `scons -j*` 对文件进行编译(如果报错是没config文件导致的,先打开menuconfig再save退出即可) + +使用qemu时,输入`qemu-nograhic.bat`可以运行无UI的模拟器,使用`ctrl+a松开后再按 x`回到命令行;直接输入`qemu`进入带UI的模拟器时,使用`ctrl+c`退出 + +在qemu中,使用 `list` 查看相关命令 + +menuconfig图形化配置 + +![image-20250721171652020](C:\Users\lx\AppData\Roaming\Typora\typora-user-images\image-20250721171652020.png) + +## 关于scons工具 + +### sconscript语法 + +创建新文件夹需要添加sconscript文件,scons构建是基于sconstruct和sconscript的,后者每个文件夹都有一个,python通过该文件递归遍历每一个文件夹以控制编译内容。没有sconscript的话编译器是找不到文件的。 + +![image-20250721171753580](C:\Users\lx\AppData\Roaming\Typora\typora-user-images\image-20250721171753580.png) + +## git + +git是一个版本管理工具,组成为 工作区--暂存区--本地仓库--远端仓库 + +### git重要命令 + +`git push/pull` 推送/拉取 + +`git add .` 添加所有修改的文件到暂存区 + +`git commit -m "log"` commit,log是commit的标题 + +`git log` 查看修改日志 + +`git status` 查看文件状态 + +`git checkout -b first_branch `创建一个分支名为first_branch,可以通过`git switch`切换分支,`git branch` 查看分支 + +`git reset --hard HEAD~` 硬重置,强制删除上一个commit + +`git reset --soft HEAD~` 软重置,把上一个commit退回暂存区里,后面还可以重新commit + +命令后缀添加`--force`可以进行强制推送和拉取 \ No newline at end of file -- Gitee From b8690e3e47ce87bafe9181ca06076ec9110d8356 Mon Sep 17 00:00:00 2001 From: ykx <123456@ykx.com> Date: Sat, 2 Aug 2025 12:37:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BD=9C=E4=B8=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../\344\275\234\344\270\232/Day2-thread.c" | 132 +++++++ .../mqtt.c" | 313 +++++++++++++++++ .../event.c" | 125 +++++++ .../maibox.c" | 149 ++++++++ .../mutex.c" | 134 ++++++++ .../queue.c" | 177 ++++++++++ .../sem.c" | 135 ++++++++ .../sigal.c" | 117 +++++++ .../drv.c" | 322 ++++++++++++++++++ .../drv.h" | 55 +++ ...00\345\244\251\347\254\224\350\256\260.md" | 0 ...11\346\254\241\344\275\234\344\270\232.md" | 192 +++++++++++ ...14\345\244\251\347\254\224\350\256\260.md" | 143 ++++++++ ...24\345\244\251\344\275\234\344\270\232.md" | 184 ++++++++++ ...33\345\244\251\344\275\234\344\270\232.md" | 259 ++++++++++++++ 15 files changed, 2437 insertions(+) create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/Day2-thread.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\2545\345\244\251\344\275\234\344\270\232/mqtt.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/event.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/maibox.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/mutex.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/queue.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/sem.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/sigal.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv.c" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv.h" rename "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" => "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" (100%) create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232.md" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232.md" create mode 100644 "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232.md" diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/Day2-thread.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/Day2-thread.c" new file mode 100644 index 0000000..bc54cac --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/Day2-thread.c" @@ -0,0 +1,132 @@ +#include +#include +#include + +/* 线程优先级宏定义 */ +#define THREAD1_PRIORITY 11 +#define THREAD2_PRIORITY 11 +#define THREAD3_PRIORITY 10 // 最高优先级(数值越小优先级越高) + +/* 线程栈大小与时间片定义 */ +#define THREAD_STACK_SIZE 1024 +#define THREAD1_TIMESLICE 50 +#define THREAD2_TIMESLICE 100 +#define THREAD3_TIMESLICE 100 + +/* 线程声明 */ +static void user_thread1(void *parameter); +static void user_thread2(void *parameter); +static void user_thread3(void *parameter); + +/* 线程控制块指针(静态全局变量,避免野指针) */ +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; +static rt_thread_t tid3 = RT_NULL; + +/** + * 线程1入口函数(优先级11) + * 功能:周期性打印线程信息,延时5个tick + */ +static void user_thread1(void *parameter) +{ + RT_ASSERT(parameter == RT_NULL); // 检查参数有效性 + + while (1) + { + rt_kprintf("run in user1 thread\r\n"); + rt_thread_delay(5); // 让出CPU,等待5个时钟节拍 + } +} + +/** + * 线程2入口函数(优先级11) + * 功能:周期性打印线程信息,延时5个tick + */ +static void user_thread2(void *parameter) +{ + RT_ASSERT(parameter == RT_NULL); + + while (1) + { + rt_kprintf("run in user2 thread\r\n"); + rt_thread_delay(5); + } +} + +/** + * 线程3入口函数(优先级10,最高) + * 功能:周期性打印线程信息,延时5个tick + */ +static void user_thread3(void *parameter) +{ + RT_ASSERT(parameter == RT_NULL); + + while (1) + { + rt_kprintf("run in user3 thread\r\n"); + rt_thread_delay(5); + } +} + +/** + * 主函数:创建并启动三个线程 + * 线程3优先级最高,线程1与线程2优先级相同(按时间片轮转) + */ +int main(void) +{ + /* 创建线程1 */ + tid1 = rt_thread_create("user1", + user_thread1, + RT_NULL, + THREAD_STACK_SIZE, + THREAD1_PRIORITY, + THREAD1_TIMESLICE); + if (tid1 != RT_NULL) + { + rt_thread_startup(tid1); + rt_kprintf("Thread1 created and started successfully\r\n"); + } + else + { + rt_kprintf("Failed to create Thread1\r\n"); + return -RT_ERROR; + } + + /* 创建线程2 */ + tid2 = rt_thread_create("user2", + user_thread2, + RT_NULL, + THREAD_STACK_SIZE, + THREAD2_PRIORITY, + THREAD2_TIMESLICE); + if (tid2 != RT_NULL) + { + rt_thread_startup(tid2); + rt_kprintf("Thread2 created and started successfully\r\n"); + } + else + { + rt_kprintf("Failed to create Thread2\r\n"); + return -RT_ERROR; + } + + /* 创建线程3 */ + tid3 = rt_thread_create("user3", + user_thread3, + RT_NULL, + THREAD_STACK_SIZE, + THREAD3_PRIORITY, + THREAD3_TIMESLICE); + if (tid3 != RT_NULL) + { + rt_thread_startup(tid3); + rt_kprintf("Thread3 created and started successfully\r\n"); + } + else + { + rt_kprintf("Failed to create Thread3\r\n"); + return -RT_ERROR; + } + + return RT_EOK; +} \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\2545\345\244\251\344\275\234\344\270\232/mqtt.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\2545\345\244\251\344\275\234\344\270\232/mqtt.c" new file mode 100644 index 0000000..f27fa6c --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\2545\345\244\251\344\275\234\344\270\232/mqtt.c" @@ -0,0 +1,313 @@ +/* + * MQTT客户端示例:基于RT-Thread的JSON消息通信 + * 功能:连接MQTT服务器,支持JSON格式消息的发送、接收与解析 + */ +#include +#include +#include +#include +#include +#include +#include "mqttclient.h" +#include +#include "mqtt.h" + + +/* 日志宏定义(简化日志输出) */ +#define MQTT_LOG_D(...) rt_kprintf("[MQTT][D] " __VA_ARGS__ "\n") +#define MQTT_LOG_I(...) rt_kprintf("[MQTT][I] " __VA_ARGS__ "\n") +#define MQTT_LOG_E(...) rt_kprintf("[MQTT][E] " __VA_ARGS__ "\n") + +/* 全局MQTT客户端句柄 */ +static mqtt_client_t *mqtt_client = RT_NULL; +/* 连接状态标识 */ +static rt_bool_t is_connected = RT_FALSE; + +/** + * 创建JSON格式消息 + * @return 序列化后的JSON字符串(需用cJSON_free释放) + */ +static char *create_json_message(const char *name, const char *content) +{ + cJSON *root = cJSON_CreateObject(); + if (root == RT_NULL) + { + MQTT_LOG_E("创建JSON对象失败"); + return RT_NULL; + } + + /* 添加JSON字段 */ + cJSON_AddStringToObject(root, "device", name); + cJSON_AddStringToObject(root, "message", content); + cJSON_AddNumberToObject(root, "timestamp", rt_tick_get()); // 增加时间戳 + + /* 序列化JSON */ + char *json_str = cJSON_PrintUnformatted(root); + cJSON_Delete(root); // 释放JSON对象 + + if (json_str == RT_NULL) + { + MQTT_LOG_E("JSON序列化失败"); + } + return json_str; +} + +/** + * 解析接收到的JSON消息 + * @param payload 消息内容 + */ +static void parse_json_message(const char *payload) +{ + if (payload == RT_NULL) + { + MQTT_LOG_E("无效的消息内容"); + return; + } + + /* 解析JSON */ + cJSON *root = cJSON_Parse(payload); + if (root == RT_NULL) + { + MQTT_LOG_E("JSON格式错误: %s", payload); + return; + } + + /* 提取字段 */ + cJSON *device = cJSON_GetObjectItem(root, "device"); + cJSON *message = cJSON_GetObjectItem(root, "message"); + cJSON *timestamp = cJSON_GetObjectItem(root, "timestamp"); + + /* 验证字段有效性 */ + if (cJSON_IsString(device) && cJSON_IsString(message)) + { + MQTT_LOG_I("解析消息 - 设备: %s, 内容: %s", + device->valuestring, message->valuestring); + + /* 若包含时间戳则打印 */ + if (cJSON_IsNumber(timestamp)) + { + MQTT_LOG_I("消息时间戳: %d", timestamp->valueint); + } + } + else + { + MQTT_LOG_E("JSON字段缺失或类型错误"); + } + + cJSON_Delete(root); // 释放资源 +} + +/** + * MQTT消息接收回调函数 + */ +static void mqtt_message_callback(void *client, message_data_t *msg) +{ + RT_UNUSED(client); + const char *topic = msg->topic_name; + const char *payload = (const char *)msg->message->payload; + + /* 打印接收信息 */ + MQTT_LOG_I("\n--- 收到消息 ---"); + MQTT_LOG_I("主题: %s", topic); + MQTT_LOG_I("内容: %s", payload); + MQTT_LOG_I("----------------\n"); + + /* 解析JSON消息 */ + parse_json_message(payload); + + /* 自动回复确认消息 */ + char *reply_msg = create_json_message(DEFAULT_NAME, "已收到消息,谢谢"); + if (reply_msg != RT_NULL) + { + mqtt_message_t reply = { + .qos = QOS0, + .retained = 0, + .payload = reply_msg + }; + mqtt_publish(mqtt_client, MQTT_PUB_TOPIC, &reply); + MQTT_LOG_I("发送回复: %s", reply_msg); + cJSON_free(reply_msg); // 释放JSON字符串 + } +} + +/** + * 尝试重连MQTT服务器 + */ +static rt_err_t mqtt_reconnect(void) +{ + if (mqtt_client == RT_NULL) + { + MQTT_LOG_E("客户端未初始化"); + return -RT_ERROR; + } + + /* 断开现有连接 */ + if (is_connected) + { + mqtt_disconnect(mqtt_client); + is_connected = RT_FALSE; + } + + /* 重新连接 */ + MQTT_LOG_I("尝试连接到 %s:%s ...", MQTT_HOST, MQTT_PORT); + if (mqtt_connect(mqtt_client) == 0) + { + is_connected = RT_TRUE; + MQTT_LOG_I("连接成功,客户端ID: %s", MQTT_CLIENT_ID); + + /* 重新订阅主题 */ + if (mqtt_subscribe(mqtt_client, MQTT_SUB_TOPIC, QOS0, mqtt_message_callback) == 0) + { + MQTT_LOG_I("已订阅主题: %s", MQTT_SUB_TOPIC); + return RT_EOK; + } + else + { + MQTT_LOG_E("订阅主题失败"); + is_connected = RT_FALSE; + return -RT_ERROR; + } + } + else + { + MQTT_LOG_E("连接失败,请检查网络"); + return -RT_ERROR; + } +} + +/** + * 初始化MQTT客户端 + */ +static rt_err_t mqtt_client_init(void) +{ + /* 创建客户端实例 */ + mqtt_client = mqtt_lease(); + if (mqtt_client == RT_NULL) + { + MQTT_LOG_E("创建客户端失败"); + return -RT_ERROR; + } + + /* 配置客户端参数 */ + mqtt_set_host(mqtt_client, MQTT_HOST); + mqtt_set_port(mqtt_client, MQTT_PORT); + mqtt_set_client_id(mqtt_client, MQTT_CLIENT_ID); + mqtt_set_user_name(mqtt_client, MQTT_USERNAME); + mqtt_set_password(mqtt_client, MQTT_PASSWORD); + mqtt_set_keep_alive(mqtt_client, MQTT_KEEP_ALIVE); + mqtt_set_clean_session(mqtt_client, 1); // 清理会话 + + /* 首次连接 */ + return mqtt_reconnect(); +} + +/** + * 周期性发送测试消息 + */ +static void send_periodic_message(void) +{ + static rt_uint32_t count = 0; + char content[64]; + + /* 构造动态消息内容 */ + rt_snprintf(content, sizeof(content), "心跳消息 #%d", count++); + + /* 创建JSON消息 */ + char *json_msg = create_json_message(DEFAULT_NAME, content); + if (json_msg == RT_NULL) + { + return; + } + + /* 发布消息 */ + mqtt_message_t msg = { + .qos = QOS0, + .retained = 0, + .payload = json_msg + }; + + if (mqtt_publish(mqtt_client, MQTT_PUB_TOPIC, &msg) == 0) + { + MQTT_LOG_I("发送消息: %s", json_msg); + } + else + { + MQTT_LOG_E("消息发送失败"); + } + + cJSON_free(json_msg); // 释放资源 +} + +/** + * MQTT客户端主线程 + */ +static void mqtt_client_thread(void *parameter) +{ + RT_UNUSED(parameter); + rt_err_t ret; + + /* 初始化日志 */ + mqtt_log_init(); + MQTT_LOG_I("MQTT客户端启动中..."); + + /* 初始化客户端 */ + if (mqtt_client_init() != RT_EOK) + { + MQTT_LOG_E("初始化失败,线程退出"); + return; + } + + /* 主循环 */ + while (1) + { + if (is_connected) + { + /* 每30秒发送一次心跳消息 */ + send_periodic_message(); + rt_thread_mdelay(30000); + } + else + { + /* 连接断开时重试(5秒一次) */ + ret = mqtt_reconnect(); + if (ret != RT_EOK) + { + rt_thread_mdelay(5000); + } + } + } +} + +/** + * 导出为shell命令,用于手动启动MQTT客户端 + */ +static int mqtt_start(void) +{ + rt_thread_t tid = rt_thread_find("mqtt_client"); + if (tid != RT_NULL) + { + MQTT_LOG_I("MQTT客户端已运行"); + return RT_EOK; + } + + /* 创建MQTT客户端线程 */ + tid = rt_thread_create("mqtt_client", + mqtt_client_thread, + RT_NULL, + 4096, // 栈大小 + 15, // 优先级 + 10); // 时间片 + if (tid != RT_NULL) + { + rt_thread_startup(tid); + MQTT_LOG_I("MQTT客户端线程已创建"); + return RT_EOK; + } + else + { + MQTT_LOG_E("创建线程失败"); + return -RT_ERROR; + } +} +MSH_CMD_EXPORT(mqtt_start, 启动MQTT客户端); + diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/event.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/event.c" new file mode 100644 index 0000000..4151d7b --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/event.c" @@ -0,0 +1,125 @@ +/* + * 事件集示例:基于按键触发的LED控制 + * 功能:线程2检测按键状态并发送事件,线程1接收事件并控制LED翻转 + */ +#include "board.h" +#include "rtthread.h" + +/* 线程控制块与事件集句柄定义 */ +static rt_thread_t t1_handler = RT_NULL; // 线程1句柄 +static rt_thread_t t2_handler = RT_NULL; // 线程2句柄 +static rt_event_t key_event = RT_NULL; // 事件集句柄 + +/* 事件标志定义 */ +#define KEY1_PRESS_EVENT (1 << 0) // 按键1按下事件(bit0) +#define KEY2_PRESS_EVENT (1 << 1) // 按键2按下事件(bit1) + +/* 线程1入口函数:接收事件并控制LED */ +static void t1_entry(void *param) +{ + rt_uint32_t recv_events; // 接收事件的缓冲区 + + while (1) + { + /* 等待事件:KEY1或KEY2按下,接收后清除事件标志 */ + rt_err_t result = rt_event_recv(key_event, + KEY1_PRESS_EVENT | KEY2_PRESS_EVENT, + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, // 逻辑或 + 清除标志 + RT_WAITING_FOREVER, // 永久等待 + &recv_events); + + if (result == RT_EOK) + { + /* 根据接收的事件控制LED */ + if (recv_events & KEY1_PRESS_EVENT) + { + rt_kprintf("收到KEY1按下事件,翻转LED1\r\n"); + LED1_TOGGLE; // 翻转LED1状态 + } + if (recv_events & KEY2_PRESS_EVENT) + { + rt_kprintf("收到KEY2按下事件,翻转LED2\r\n"); + LED2_TOGGLE; // 翻转LED2状态(假设存在LED2) + } + } + } +} + +/* 线程2入口函数:检测按键状态并发送事件 */ +static void t2_entry(void *param) +{ + while (1) + { + /* 检测KEY1按下(消抖处理) */ + if (Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON) + { + rt_event_send(key_event, KEY1_PRESS_EVENT); // 发送KEY1事件 + rt_kprintf("检测到KEY1按下,发送事件\r\n"); + rt_thread_mdelay(200); // 简单消抖 + } + + /* 检测KEY2按下(消抖处理) */ + if (Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON) + { + rt_event_send(key_event, KEY2_PRESS_EVENT); // 发送KEY2事件 + rt_kprintf("检测到KEY2按下,发送事件\r\n"); + rt_thread_mdelay(200); // 简单消抖 + } + + rt_thread_delay(20); // 降低CPU占用 + } +} + +/* 初始化函数:创建事件集与线程 */ +static int event_demo_init(void) +{ + /* 创建事件集(优先级模式:高优先级线程优先获取) */ + key_event = rt_event_create("key_evt", RT_IPC_FLAG_PRIO); + if (key_event == RT_NULL) + { + rt_kprintf("事件集创建失败!\r\n"); + return -RT_ERROR; + } + rt_kprintf("事件集创建成功:key_evt\r\n"); + + /* 创建线程1(优先级3:处理事件) */ + t1_handler = rt_thread_create("t1", + t1_entry, + RT_NULL, + 1024, // 栈大小 + 3, // 优先级(数值越小优先级越高) + 20); // 时间片 + if (t1_handler != RT_NULL) + { + rt_thread_startup(t1_handler); + rt_kprintf("线程1创建成功:接收事件并控制LED\r\n"); + } + else + { + rt_kprintf("线程1创建失败!\r\n"); + return -RT_ERROR; + } + + /* 创建线程2(优先级2:检测按键,优先级高于线程1) */ + t2_handler = rt_thread_create("t2", + t2_entry, + RT_NULL, + 1024, + 2, // 优先级高于线程1,确保按键检测优先 + 20); + if (t2_handler != RT_NULL) + { + rt_thread_startup(t2_handler); + rt_kprintf("线程2创建成功:检测按键并发送事件\r\n"); + } + else + { + rt_kprintf("线程2创建失败!\r\n"); + return -RT_ERROR; + } + + return RT_EOK; +} + +/* 自动初始化:系统启动时执行初始化 */ +INIT_APP_EXPORT(event_demo_init); \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/maibox.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/maibox.c" new file mode 100644 index 0000000..deb0934 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/maibox.c" @@ -0,0 +1,149 @@ +/* + * 邮箱通信示例:按键 * 功能:线程2检测按键状态,通过邮箱发送字符串消息; + * 线程1接收消息并打印,实现按键事件的异步处理 + */ +#include "board.h" +#include "rtthread.h" + +/* 线程控制块与邮箱句柄 */ +static rt_thread_t recv_thread = RT_NULL; // 接收线程句柄 +static rt_thread_t send_thread = RT_NULL; // 发送线程句柄 +static rt_mailbox_t msg_mailbox = RT_NULL; // 邮箱句柄 + +/* 消息内容定义 */ +static char key1_msg[] = "KEY1被按下,这是一条邮箱消息!\r\n"; +static char key2_msg[] = "KEY2被按下,这是另一条邮箱消息!\r\n"; + +/* 线程1入口:接收邮箱消息并处理 */ +static void recv_thread_entry(void *param) +{ + rt_err_t result; + char *recv_buf; // 用于接收邮箱中的指针 + + while (1) + { + /* 等待接收邮件,超时时间1000ms */ + result = rt_mb_recv(msg_mailbox, + (rt_ubase_t *)&recv_buf, + 1000); + + if (result == RT_EOK) + { + /* 成功接收,打印消息内容 */ + rt_kprintf("接收到邮件: %s", recv_buf); + } + else if (result == -RT_ETIMEOUT) + { + + + } + else + { + rt_kprintf("邮箱接收失败,错误码: %d\r\n", result); + } + + rt_thread_delay(100); // 降低CPU占用 + } +} + +/* 线程2入口:检测按键并发送邮箱消息 */ +static void send_thread_entry(void *param) +{ + rt_err_t result; + + while (1) + { + /* 检测KEY1按下(带消抖) */ + if (Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON) + { + /* 发送KEY1消息(邮箱中传递字符串指针) */ + result = rt_mb_send(msg_mailbox, (rt_ubase_t)&key1_msg); + if (result == RT_EOK) + { + rt_kprintf("已发送KEY1消息到邮箱\r\n"); + } + else + { + rt_kprintf("KEY1消息发送失败,错误码: %d\r\n", result); + } + rt_thread_mdelay(200); // 按键消抖 + } + + /* 检测KEY2按下(带消抖) */ + if (Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON) + { + /* 发送KEY2消息 */ + result = rt_mb_send(msg_mailbox, (rt_ubase_t)&key2_msg); + if (result == RT_EOK) + { + rt_kprintf("已发送KEY2消息到邮箱\r\n"); + } + else + { + rt_kprintf("KEY2消息发送失败,错误码: %d\r\n", result); + } + rt_thread_mdelay(200); // 按键消抖 + } + + rt_thread_delay(20); // 扫描间隔,降低CPU占用 + } +} + +/* 初始化函数:创建邮箱和线程 */ +static int mailbox_demo_init(void) +{ + /* 创建邮箱:容量10封邮件,FIFO模式 */ + msg_mailbox = rt_mb_create("msg_box", + 10, + RT_IPC_FLAG_FIFO); + if (msg_mailbox == RT_NULL) + { + rt_kprintf("邮箱创建失败!\r\n"); + return -RT_ERROR; + } + rt_kprintf("邮箱创建成功:msg_box(容量10封邮件)\r\n"); + + /* 创建接收线程(优先级5) */ + recv_thread = rt_thread_create("recv_th", + recv_thread_entry, + RT_NULL, + 1024, // 栈大小 + 5, // 优先级 + 20); // 时间片 + if (recv_thread != RT_NULL) + { + rt_thread_startup(recv_thread); + rt_kprintf("接收线程创建成功:负责接收并打印消息\r\n"); + } + else + { + rt_kprintf("接收线程创建失败!\r\n"); + rt_mb_delete(msg_mailbox); // 清理资源 + return -RT_ERROR; + } + + /* 创建发送线程(优先级3,高于接收线程) */ + send_thread = rt_thread_create("send_th", + send_thread_entry, + RT_NULL, + 1024, + 3, // 更高优先级,确保按键响应及时 + 20); + if (send_thread != RT_NULL) + { + rt_thread_startup(send_thread); + rt_kprintf("发送线程创建成功:负责检测按键并发送消息\r\n"); + } + else + { + rt_kprintf("发送线程创建失败!\r\n"); + rt_thread_delete(recv_thread); // 清理资源 + rt_mb_delete(msg_mailbox); + return -RT_ERROR; + } + + return RT_EOK; +} + +/* 自动初始化:系统启动时执行 */ +INIT_APP_EXPORT(mailbox_demo_init); diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/mutex.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/mutex.c" new file mode 100644 index 0000000..630f674 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/mutex.c" @@ -0,0 +1,134 @@ +/* + * 互斥量示例:保护共享数据的一致性 + * 功能:通过互斥量确保确保两个线程对共享数组的操作不会导致数据不一致 + */ +#include "board.h" +#include "rtthread.h" + +/* 线程控制块与互斥量句柄 */ +static rt_thread_t high_prio_thread = RT_NULL; // 高优先级线程(优先级3) +static rt_thread_t low_prio_thread = RT_NULL; // 低优先级线程(优先级5) +static rt_mutex_t data_mutex = RT_NULL; // 互斥量句柄 + +/* 共享数据:需要被保护的临界资源 */ +static struct shared_data { + rt_uint32_t count_a; // 计数器A + rt_uint32_t count_b; // 计数器B +} shared_data = {0, 0}; + +/* 高优先级线程入口:检查共享数据一致性 */ +static void high_prio_thread_entry(void *param) +{ + rt_err_t result; + + while (1) + { + /* 获取互斥量,永久等待 */ + result = rt_mutex_take(data_mutex, RT_WAITING_FOREVER); + if (result == RT_EOK) + { + /* 检查数据一致性 */ + if (shared_data.count_a == shared_data.count_b) + { + rt_kprintf("数据一致:count_a=%d, count_b=%d\r\n", + shared_data.count_a, shared_data.count_b); + } + else + { + rt_kprintf("数据不一致!count_a=%d, count_b=%d\r\n", + shared_data.count_a, shared_data.count_b); + } + + /* 释放互斥量 */ + rt_mutex_release(data_mutex); + } + + rt_thread_delay(1000); // 1秒检查一次 + } +} + +/* 低优先级线程入口:修改共享数据 */ +static void low_prio_thread_entry(void *param) +{ + rt_err_t result; + + while (1) + { + /* 获取互斥量,永久等待 */ + result = rt_mutex_take(data_mutex, RT_WAITING_FOREVER); + if (result == RT_EOK) + { + /* 分步修改共享数据(模拟复杂操作) */ + shared_data.count_a++; + rt_kprintf("修改count_a为:%d\r\n", shared_data.count_a); + + /* 故意添加延迟,模拟耗时操作 */ + rt_thread_delay(100); + + shared_data.count_b++; + rt_kprintf("修改count_b为:%d\r\n", shared_data.count_b); + + /* 释放互斥量 */ + rt_mutex_release(data_mutex); + } + + rt_thread_yield(); // 主动让出CPU + } +} + +/* 初始化函数:创建互斥量和线程 */ +static int mutex_demo_init(void) +{ + /* 创建互斥量(FIFO模式) */ + data_mutex = rt_mutex_create("data_mutex", RT_IPC_FLAG_FIFO); + if (data_mutex == RT_NULL) + { + rt_kprintf("互斥量创建失败!\r\n"); + return -RT_ERROR; + } + rt_kprintf("互斥量创建成功:data_mutex\r\n"); + + /* 创建高优先级线程(检查数据) */ + high_prio_thread = rt_thread_create("check_th", + high_prio_thread_entry, + RT_NULL, + 1024, // 栈大小 + 3, // 优先级(较高) + 20); // 时间片 + if (high_prio_thread != RT_NULL) + { + rt_thread_startup(high_prio_thread); + rt_kprintf("高优先级线程创建成功:负责检查数据一致性\r\n"); + } + else + { + rt_kprintf("高优先级线程创建失败!\r\n"); + rt_mutex_delete(data_mutex); // 清理资源 + return -RT_ERROR; + } + + /* 创建低优先级线程(修改数据) */ + low_prio_thread = rt_thread_create("modify_th", + low_prio_thread_entry, + RT_NULL, + 1024, + 5, // 优先级(较低) + 20); + if (low_prio_thread != RT_NULL) + { + rt_thread_startup(low_prio_thread); + rt_kprintf("低优先级线程创建成功:负责修改共享数据\r\n"); + } + else + { + rt_kprintf("低优先级线程创建失败!\r\n"); + rt_thread_delete(high_prio_thread); // 清理资源 + rt_mutex_delete(data_mutex); + return -RT_ERROR; + } + + return RT_EOK; +} + +/* 自动初始化:系统启动时执行 */ +INIT_APP_EXPORT(mutex_demo_init); \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/queue.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/queue.c" new file mode 100644 index 0000000..c3a8bda --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/queue.c" @@ -0,0 +1,177 @@ +/* + * 消息队列示例:按键触发的数据传输系统 + * 功能:线程2检测按键事件并通过消息队列发送递增数据, + * 线程1接收数据并打印,支持多按键区分和队列状态提示 + */ +#include "board.h" +#include "rtthread.h" + +/* 线程控制块与消息队列句柄 */ +static rt_thread_t recv_thread = RT_NULL; // 接收线程句柄 +static rt_thread_t send_thread = RT_NULL; // 发送线程句柄 +static rt_mq_t data_mq = RT_NULL; // 消息队列句柄 + +/* 消息类型定义 */ +typedef struct { + rt_uint8_t key_id; // 按键ID:1表示KEY1,2表示KEY2 + rt_uint32_t counter; // 按键触发计数器 +} key_message_t; + +/* 全局计数器(记录各按键触发次数) */ +static rt_uint32_t key1_counter = 0; +static rt_uint32_t key2_counter = 0; + +/* 线程1入口:接收消息队列数据并处理 */ +static void recv_thread_entry(void *param) +{ + rt_err_t result; + key_message_t msg; // 消息缓冲区 + + while (1) + { + /* 接收消息,超时时间100ms */ + result = rt_mq_recv(data_mq, &msg, sizeof(key_message_t), 100); + + if (result == RT_EOK) + { + /* 成功接收,根据按键ID处理 */ + if (msg.key_id == 1) + { + rt_kprintf("收到KEY1消息:第%d次触发\r\n", msg.counter); + LED1_TOGGLE; // 翻转LED1提示 + } + else if (msg.key_id == 2) + { + rt_kprintf("收到KEY2消息:第%d次触发\r\n", msg.counter); + LED2_TOGGLE; // 翻转LED2提示 + } + } + else if (result == -RT_ETIMEOUT) + { + + + } + else + { + rt_kprintf("消息接收失败,错误码: %d\r\n", result); + } + + rt_thread_delay(50); // 降低CPU占用 + } +} + +/* 线程2入口:检测按键并发送消息队列数据 */ +static void send_thread_entry(void *param) +{ + rt_err_t result; + key_message_t msg; // 消息结构体 + + while (1) + { + /* 检测KEY1按下(带消抖) */ + if (Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON) + { + key1_counter++; + msg.key_id = 1; + msg.counter = key1_counter; + + /* 发送消息到队列 */ + result = rt_mq_send(data_mq, &msg, sizeof(key_message_t)); + if (result == RT_EOK) + { + rt_kprintf("KEY1消息发送成功(队列当前占用: %d/%d)\r\n", + rt_mq_info(data_mq)->entry, // 当前消息数 + rt_mq_info(data_mq)->size); // 队列总容量 + } + else + { + rt_kprintf("KEY1消息发送失败,错误码: %d(队列已满)\r\n", result); + } + rt_thread_mdelay(200); // 按键消抖 + } + + /* 检测KEY2按下(带消抖) */ + if (Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON) + { + key2_counter++; + msg.key_id = 2; + msg.counter = key2_counter; + + /* 发送消息到队列 */ + result = rt_mq_send(data_mq, &msg, sizeof(key_message_t)); + if (result == RT_EOK) + { + rt_kprintf("KEY2消息发送成功(队列当前占用: %d/%d)\r\n", + rt_mq_info(data_mq)->entry, + rt_mq_info(data_mq)->size); + } + else + { + rt_kprintf("KEY2消息发送失败,错误码: %d(队列已满)\r\n", result); + } + rt_thread_mdelay(200); // 按键消抖 + } + + rt_thread_delay(20); // 扫描间隔 + } +} + +/* 初始化函数:创建消息队列和线程 */ +static int mq_demo_init(void) +{ + /* 创建消息队列:每个消息40字节,最多20条消息,FIFO模式 */ + data_mq = rt_mq_create("data_mq", + 40, // 消息大小 + 20, // 队列长度 + RT_IPC_FLAG_FIFO); + if (data_mq == RT_NULL) + { + rt_kprintf("消息队列创建失败!\r\n"); + return -RT_ERROR; + } + rt_kprintf("消息队列创建成功:data_mq(单条消息40字节,共20条)\r\n"); + + /* 创建接收线程(优先级5) */ + recv_thread = rt_thread_create("recv_th", + recv_thread_entry, + RT_NULL, + 1024, // 栈大小 + 5, // 优先级 + 20); // 时间片 + if (recv_thread != RT_NULL) + { + rt_thread_startup(recv_thread); + rt_kprintf("接收线程创建成功:负责处理消息并控制LED\r\n"); + } + else + { + rt_kprintf("接收线程创建失败!\r\n"); + rt_mq_delete(data_mq); // 清理资源 + return -RT_ERROR; + } + + /* 创建发送线程(优先级3,高于接收线程) */ + send_thread = rt_thread_create("send_th", + send_thread_entry, + RT_NULL, + 1024, + 3, // 更高优先级,确保按键响应及时 + 20); + if (send_thread != RT_NULL) + { + rt_thread_startup(send_thread); + rt_kprintf("发送线程创建成功:负责检测按键并发送消息\r\n"); + } + else + { + rt_kprintf("发送线程创建失败!\r\n"); + rt_thread_delete(recv_thread); // 清理资源 + rt_mq_delete(data_mq); + return -RT_ERROR; + } + + return RT_EOK; +} + +/* 自动初始化:系统启动时执行 */ +INIT_APP_EXPORT(mq_demo_init); diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/sem.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/sem.c" new file mode 100644 index 0000000..d4cd60b --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/sem.c" @@ -0,0 +1,135 @@ +/* + * 信号量示例:基于计数信号量的共享数据保护 + * 功能:通过二值信号量实现两个线程对共享数据的同步访问, + * 确保数据修改过程不被中断,维持数据一致性 + */ +#include "board.h" +#include "rtthread.h" + +/* 线程控制块与信号量句柄 */ +static rt_thread_t check_thread = RT_NULL; // 数据检查线程(优先级5) +static rt_thread_t update_thread = RT_NULL; // 数据更新线程(优先级3) +static rt_sem_t data_sem = RT_NULL; // 信号量句柄 + +/* 共享数据结构体:需要同步访问的临界资源 */ +static struct shared_data { + rt_uint32_t counter_a; // 计数器A + rt_uint32_t counter_b; // 计数器B +} shared_data = {0, 0}; + +/* 线程1入口:检查共享数据一致性 */ +static void check_thread_entry(void *param) +{ + rt_err_t result; + + while (1) + { + /* 获取信号量(等待临界资源) */ + result = rt_sem_take(data_sem, RT_WAITING_FOREVER); + if (result == RT_EOK) + { + /* 检查数据一致性 */ + if (shared_data.counter_a == shared_data.counter_b) + { + rt_kprintf("数据一致:A=%d, B=%d(信号量保护生效)\r\n", + shared_data.counter_a, shared_data.counter_b); + } + else + { + rt_kprintf("数据不一致!A=%d, B=%d(异常状态)\r\n", + shared_data.counter_a, shared_data.counter_b); + } + + /* 释放信号量(释放临界资源) */ + rt_sem_release(data_sem); + } + + rt_thread_delay(1000); // 1秒检查一次 + } +} + +/* 线程2入口:更新共享数据 */ +static void update_thread_entry(void *param) +{ + rt_err_t result; + + while (1) + { + /* 获取信号量(独占临界资源) */ + result = rt_sem_take(data_sem, RT_WAITING_FOREVER); + if (result == RT_EOK) + { + /* 分步修改数据(模拟耗时操作) */ + shared_data.counter_a++; + rt_kprintf("更新计数器A:%d(持有信号量)\r\n", shared_data.counter_a); + + /* 延迟100ms,模拟中间被打断的风险 */ + rt_thread_delay(100); + + shared_data.counter_b++; + rt_kprintf("更新计数器B:%d(释放信号量)\r\n", shared_data.counter_b); + + /* 释放信号量(允许其他线程访问) */ + rt_sem_release(data_sem); + } + + rt_thread_yield(); // 主动让出CPU + } +} + +/* 初始化函数:创建信号量和线程 */ +static int semaphore_demo_init(void) +{ + /* 创建二值信号量(初始值1,FIFO模式) */ + data_sem = rt_sem_create("data_sem", 1, RT_IPC_FLAG_FIFO); + if (data_sem == RT_NULL) + { + rt_kprintf("信号量创建失败!\r\n"); + return -RT_ERROR; + } + rt_kprintf("二值信号量创建成功:data_sem(初始值1)\r\n"); + + /* 创建数据检查线程(低优先级) */ + check_thread = rt_thread_create("check_th", + check_thread_entry, + RT_NULL, + 1024, // 栈大小 + 5, // 优先级 + 20); // 时间片 + if (check_thread != RT_NULL) + { + rt_thread_startup(check_thread); + rt_kprintf("检查线程创建成功:负责验证数据一致性\r\n"); + } + else + { + rt_kprintf("检查线程创建失败!\r\n"); + rt_sem_delete(data_sem); // 清理资源 + return -RT_ERROR; + } + + /* 创建数据更新线程(高优先级) */ + update_thread = rt_thread_create("update_th", + update_thread_entry, + RT_NULL, + 1024, + 3, // 更高优先级,模拟抢占场景 + 20); + if (update_thread != RT_NULL) + { + rt_thread_startup(update_thread); + rt_kprintf("更新线程创建成功:负责修改共享数据\r\n"); + } + else + { + rt_kprintf("更新线程创建失败!\r\n"); + rt_thread_delete(check_thread); // 清理资源 + rt_sem_delete(data_sem); + return -RT_ERROR; + } + + return RT_EOK; +} + +/* 自动初始化:系统启动时执行 */ +INIT_APP_EXPORT(semaphore_demo_init); \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/sigal.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/sigal.c" new file mode 100644 index 0000000..23f0518 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\344\270\211\345\244\251\344\275\234\344\270\232/sigal.c" @@ -0,0 +1,117 @@ +/* + * 信号机制示例:线程间信号传递与处理 + * 功能:创建两个线程分别注册不同的信号处理函数,通过信号机制实现线程间异步通知 + */ +#include +#include +#include + +/* 线程控制块指针 */ +static rt_thread_t thread1 = RT_NULL; +static rt_thread_t thread2 = RT_NULL; + +/* 线程1的信号处理函数 */ +static void thread1_signal_handler(int sig) +{ + rt_kprintf("【线程1】收到信号 %d,执行自定义处理\n", sig); + // 可在此添加信号触发的业务逻辑(如状态切换、数据处理等) +} + +/* 线程1入口函数 */ +static void thread1_entry(void *parameter) +{ + const char *thread_name = (const char *)parameter; + rt_kprintf("%s", thread_name); // 打印线程启动信息 + + /* 安装信号处理函数(处理SIGUSR1信号) */ + rt_signal_install(SIGUSR1, thread1_signal_handler); + /* 解除信号屏蔽(允许接收SIGUSR1信号) */ + rt_signal_unmask(SIGUSR1); + + /* 线程主循环 */ + while (1) + { + rt_thread_mdelay(500); // 周期性休眠,降低CPU占用 + } +} + +/* 线程2入口函数 */ +static void thread2_entry(void *parameter) +{ + const char *thread_name = (const char *)parameter; + rt_kprintf("%s", thread_name); // 打印线程启动信息 + + /* 安装信号处理函数(忽略SIGUSR1信号) */ + rt_signal_install(SIGUSR1, SIG_IGN); + /* 解除信号屏蔽(允许接收SIGUSR1信号,但会被忽略) */ + rt_signal_unmask(SIGUSR1); + + /* 线程主循环 */ + while (1) + { + rt_thread_mdelay(500); // 周期性休眠 + } +} + +/* 线程名称标识 */ +static const char *thread1_name = "【线程1】启动成功,已注册SIGUSR1处理函数\n"; +static const char *thread2_name = "【线程2】启动成功,已设置忽略SIGUSR1信号\n"; + +/* 初始化LED引脚(替代原esp8266相关代码,作为硬件示例) */ +static int led_hw_init(void) +{ + /* 配置LED引脚为输出模式 */ + rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); + rt_pin_write(LED_PIN, 1); // 初始化为熄灭状态(假设高电平熄灭) + rt_kprintf("LED硬件初始化完成,引脚:%d\n", LED_PIN); + return 0; +} +INIT_BOARD_EXPORT(led_hw_init); // 注册为板级初始化函数 + +/* 主函数:创建线程并发送信号 */ +int main(void) +{ + /* 创建线程1(优先级4) */ + thread1 = rt_thread_create("thread1", + thread1_entry, + (void *)thread1_name, + 1024, // 栈大小 + 4, // 优先级(数值越小优先级越高) + 20); // 时间片 + if (thread1 != RT_NULL) + { + rt_thread_startup(thread1); + } + else + { + rt_kprintf("线程1创建失败!\n"); + } + + /* 创建线程2(优先级6,低于线程1) */ + thread2 = rt_thread_create("thread2", + thread2_entry, + (void *)thread2_name, + 1024, + 6, + 20); + if (thread2 != RT_NULL) + { + rt_thread_startup(thread2); + } + else + { + rt_kprintf("线程2创建失败!\n"); + } + + /* 等待线程启动完成 */ + rt_thread_mdelay(100); + + /* 向两个线程发送SIGUSR1信号 */ + rt_kprintf("\n主函数发送SIGUSR1信号给线程1...\n"); + rt_thread_kill(thread1, SIGUSR1); + + rt_kprintf("主函数发送SIGUSR1信号给线程2...\n"); + rt_thread_kill(thread2, SIGUSR1); // 线程2会忽略该信号 + + return RT_EOK; +} \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv.c" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv.c" new file mode 100644 index 0000000..d53d0f4 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv.c" @@ -0,0 +1,322 @@ +/* + * 键盘设备驱动 + * 功能:实现按键扫描、状态检测和事件上报 + * 支持多按键同时检测,提供标准设备接口 + */ +#include +#include +#include +#include + +#define LOG_TAG "drv.keyboard" +#include + +#include "drv_keyboard.h" + +/* 键盘设备常量定义 */ +#define MAX_KEYS 16 // 最大支持按键数量 +#define KEY_NAME_LEN 16 // 按键名称最大长度 +#define KEY_EVENT_QUEUE_LEN 32 // 按键事件队列长度 + +/* 按键状态定义 */ +#define KEY_STATE_RELEASED 0 // 按键释放 +#define KEY_STATE_PRESSED 1 // 按键按下 +#define KEY_STATE_LONG_PRESS 2 // 长按状态 + +/* 按键事件类型 */ +typedef enum { + KEY_EVENT_PRESS, // 按键按下 + KEY_EVENT_RELEASE, // 按键释放 + KEY_EVENT_LONG_PRESS // 长按事件 +} key_event_type; + +/* 按键事件结构体 */ +typedef struct { + char key_name[KEY_NAME_LEN]; // 按键名称 + key_event_type type; // 事件类型 + rt_tick_t timestamp; // 事件时间戳 +} key_event_t; + +/* 按键设备结构体 */ +struct keyboard_dev { + struct rt_device parent; // 继承RT-Thread设备基类 + struct rt_timer scan_timer; // 按键扫描定时器 + struct rt_mq event_queue; // 按键事件队列 + key_event_t event_buffer[KEY_EVENT_QUEUE_LEN]; // 事件缓冲区 + + struct key_config keys[MAX_KEYS]; // 按键配置数组 + rt_size_t key_count; // 按键数量 + char name[KEY_NAME_LEN]; // 设备名称 + + void *user_data; // 用户私有数据 +}; + +/* 全局键盘设备实例 */ +static struct keyboard_dev keyboard; + +/** + * 按键状态检测 + * @param key 按键配置 + * @return 当前状态 (1-按下, 0-释放) + */ +static rt_uint8_t key_scan(const struct key_config *key) +{ + rt_uint8_t state; + + // 根据按键电气特性选择读取方式(上拉/下拉) + if (key->pull_mode == KEY_PULL_UP) { + // 上拉模式:按下时为低电平 + state = !rt_pin_read(key->pin); + } else { + // 下拉模式:按下时为高电平 + state = rt_pin_read(key->pin); + } + + return state; +} + +/** + * 按键扫描定时器回调函数 + * 定期扫描所有按键状态并生成事件 + */ +static void keyboard_scan_timeout(void *parameter) +{ + struct keyboard_dev *dev = (struct keyboard_dev *)parameter; + rt_tick_t now = rt_tick_get(); + + for (rt_size_t i = 0; i < dev->key_count; i++) { + struct key_config *key = &dev->keys[i]; + rt_uint8_t current_state = key_scan(key); + + // 状态变化检测 + if (current_state != key->current_state) { + key->debounce_cnt = 0; + key->current_state = current_state; + } + // 消抖处理 + else if (key->debounce_cnt < key->debounce_ticks) { + key->debounce_cnt++; + } + // 消抖完成,状态稳定 + else if (key->stable_state != current_state) { + key->stable_state = current_state; + key_event_t event; + + // 复制按键名称 + rt_strncpy(event.key_name, key->name, KEY_NAME_LEN - 1); + event.timestamp = now; + + // 生成按下/释放事件 + if (current_state == KEY_STATE_PRESSED) { + event.type = KEY_EVENT_PRESS; + key->press_start_tick = now; // 记录按下开始时间 + key->is_long_pressed = RT_FALSE; + + LOG_D("按键[%s]按下", key->name); + } else { + event.type = KEY_EVENT_RELEASE; + + // 如果不是长按后释放,才记录释放事件 + if (!key->is_long_pressed) { + LOG_D("按键[%s]释放", key->name); + } else { + key->is_long_pressed = RT_FALSE; + continue; // 长按已经处理,跳过释放事件 + } + } + + // 发送事件到队列 + if (rt_mq_send(&dev->event_queue, &event, sizeof(key_event_t)) != RT_EOK) { + LOG_W("按键事件队列已满,丢弃事件"); + } + } + + // 长按检测 + if (key->stable_state == KEY_STATE_PRESSED && + key->long_press_ticks > 0 && + !key->is_long_pressed) { + + rt_tick_t press_duration = now - key->press_start_tick; + if (press_duration >= key->long_press_ticks) { + key_event_t event; + rt_strncpy(event.key_name, key->name, KEY_NAME_LEN - 1); + event.type = KEY_EVENT_LONG_PRESS; + event.timestamp = now; + + key->is_long_pressed = RT_TRUE; + LOG_D("按键[%s]长按", key->name); + + if (rt_mq_send(&dev->event_queue, &event, sizeof(key_event_t)) != RT_EOK) { + LOG_W("按键事件队列已满,丢弃长按事件"); + } + } + } + } +} + +/** + * 读取按键事件 + */ +static rt_size_t keyboard_read(rt_device_t dev, rt_off_t pos, + void *buffer, rt_size_t size) +{ + struct keyboard_dev *keyboard_dev = (struct keyboard_dev *)dev; + + // 检查缓冲区大小是否足够 + if (size < sizeof(key_event_t)) { + rt_set_errno(-RT_ENOSPC); + return 0; + } + + // 从消息队列读取事件 + return rt_mq_recv(&keyboard_dev->event_queue, buffer, sizeof(key_event_t), RT_WAITING_FOREVER); +} + +/** + * 设备控制接口 + */ +static rt_err_t keyboard_control(rt_device_t dev, int cmd, void *args) +{ + struct keyboard_dev *keyboard_dev = (struct keyboard_dev *)dev; + + switch (cmd) { + case KEYBOARD_CMD_START_SCAN: + // 启动按键扫描 + rt_timer_start(&keyboard_dev->scan_timer); + LOG_I("键盘扫描已启动"); + return RT_EOK; + + case KEYBOARD_CMD_STOP_SCAN: + // 停止按键扫描 + rt_timer_stop(&keyboard_dev->scan_timer); + LOG_I("键盘扫描已停止"); + return RT_EOK; + + case KEYBOARD_CMD_CLEAR_QUEUE: + // 清空事件队列 + rt_mq_reset(&keyboard_dev->event_queue); + LOG_I("事件队列已清空"); + return RT_EOK; + + default: + LOG_W("不支持的控制命令: %d", cmd); + return -RT_ENOSYS; + } +} + +/* 设备操作集 */ +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops keyboard_ops = +{ + RT_NULL, // init + RT_NULL, // open + RT_NULL, // close + keyboard_read, // read + RT_NULL, // write + keyboard_control // control +}; +#endif + +/** + * 注册按键到键盘设备 + * @param key 按键配置 + * @return 0-成功,-1-失败 + */ +int keyboard_register_key(struct key_config *key) +{ + if (key == RT_NULL || keyboard.key_count >= MAX_KEYS) { + LOG_E("注册按键失败,参数无效或已达最大数量"); + return -1; + } + + // 初始化按键引脚 + rt_pin_mode(key->pin, PIN_MODE_INPUT); + + // 配置上拉/下拉 + if (key->pull_mode == KEY_PULL_UP) { + rt_pin_mode(key->pin, PIN_MODE_INPUT_PULLUP); + } else if (key->pull_mode == KEY_PULL_DOWN) { + rt_pin_mode(key->pin, PIN_MODE_INPUT_PULLDOWN); + } + + // 设置默认消抖时间(10ms) + if (key->debounce_ticks == 0) { + key->debounce_ticks = RT_TICK_PER_SECOND / 100; + } + + // 初始化按键状态 + key->current_state = key_scan(key); + key->stable_state = key->current_state; + key->debounce_cnt = key->debounce_ticks; + key->is_long_pressed = RT_FALSE; + + // 添加到设备列表 + keyboard.keys[keyboard.key_count++] = *key; + LOG_I("按键[%s]已注册,引脚: %d", key->name, key->pin); + + return 0; +} + +/** + * 键盘设备初始化 + */ +static int drv_keyboard_init(void) +{ + rt_err_t ret; + + // 初始化设备名称 + rt_strncpy(keyboard.name, "keyboard", KEY_NAME_LEN - 1); + + // 初始化设备结构体 + keyboard.parent.type = RT_Device_Class_Miscellaneous; + keyboard.parent.rx_indicate = RT_NULL; + keyboard.parent.tx_complete = RT_NULL; + keyboard.key_count = 0; + keyboard.user_data = RT_NULL; + +#ifdef RT_USING_DEVICE_OPS + keyboard.parent.ops = &keyboard_ops; +#else + keyboard.parent.init = RT_NULL; + keyboard.parent.open = RT_NULL; + keyboard.parent.close = RT_NULL; + keyboard.parent.read = keyboard_read; + keyboard.parent.write = RT_NULL; + keyboard.parent.control = keyboard_control; +#endif + + // 初始化事件队列 + ret = rt_mq_init(&keyboard.event_queue, + "key_event", + keyboard.event_buffer, + sizeof(key_event_t), + sizeof(keyboard.event_buffer), + RT_IPC_FLAG_FIFO); + if (ret != RT_EOK) { + LOG_E("初始化事件队列失败: %d", ret); + return ret; + } + + // 初始化扫描定时器(10ms扫描一次) + rt_timer_init(&keyboard.scan_timer, + "key_scan", + keyboard_scan_timeout, + &keyboard, + RT_TICK_PER_SECOND / 100, + RT_TIMER_FLAG_PERIODIC); + + // 注册设备 + ret = rt_device_register(&keyboard.parent, keyboard.name, RT_DEVICE_FLAG_RDWR); + if (ret == RT_EOK) { + LOG_I("键盘设备初始化成功,设备名: %s", keyboard.name); + } else { + LOG_E("注册键盘设备失败: %d", ret); + rt_mq_detach(&keyboard.event_queue); + return ret; + } + + return RT_EOK; +} + +/* 注册为板级初始化函数 */ +INIT_BOARD_EXPORT(drv_keyboard_init); diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv.h" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv.h" new file mode 100644 index 0000000..aa58bc5 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/drv.h" @@ -0,0 +1,55 @@ +/* + * 键盘设备驱动头文件 + */ +#ifndef __DRV_KEYBOARD_H__ +#define __DRV_KEYBOARD_H__ + +#include +#include + +/* 按键上拉/下拉模式 */ +typedef enum { + KEY_PULL_NONE, // 无上下拉(需外部电路) + KEY_PULL_UP, // 上拉模式(按下为低电平) + KEY_PULL_DOWN // 下拉模式(按下为高电平) +} key_pull_mode; + +/* 按键配置结构体 */ +struct key_config { + char name[16]; // 按键名称 + rt_base_t pin; // 按键引脚 + key_pull_mode pull_mode; // 上拉/下拉模式 + rt_uint16_t debounce_ticks; // 消抖时间(单位:系统滴答) + rt_uint16_t long_press_ticks; // 长按判定时间(单位:系统滴答) + + /* 以下为内部状态,用户无需初始化 */ + rt_uint8_t current_state; // 当前状态(未消抖) + rt_uint8_t stable_state; // 稳定状态(已消抖) + rt_uint8_t debounce_cnt; // 消抖计数器 + rt_tick_t press_start_tick; // 按下开始时间戳 + rt_bool_t is_long_pressed; // 是否已触发长按 +}; + +/* 按键事件类型 */ +typedef enum { + KEY_EVENT_PRESS, // 按键按下 + KEY_EVENT_RELEASE, // 按键释放 + KEY_EVENT_LONG_PRESS // 长按事件 +} key_event_type; + +/* 按键事件结构体 */ +typedef struct { + char key_name[16]; // 按键名称 + key_event_type type; // 事件类型 + rt_tick_t timestamp; // 事件时间戳 +} key_event_t; + +/* 键盘控制命令 */ +#define KEYBOARD_CMD_START_SCAN 0x01 // 启动按键扫描 +#define KEYBOARD_CMD_STOP_SCAN 0x02 // 停止按键扫描 +#define KEYBOARD_CMD_CLEAR_QUEUE 0x03 // 清空事件队列 + +/* 函数声明 */ +int keyboard_register_key(struct key_config *key); + +#endif /* __DRV_KEYBOARD_H__ */ diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" similarity index 100% rename from "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" rename to "2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\270\200\345\244\251\347\254\224\350\256\260.md" diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232.md" new file mode 100644 index 0000000..7788f4b --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\270\211\346\254\241\344\275\234\344\270\232.md" @@ -0,0 +1,192 @@ +# 第三次作业:同步与通信机制及 API 调用 + +## 一、信号量(Semaphore) + +### 1. 基本操作 + +- **获取(take)**:信号量计数值减 1。若计数值为 0,当前任务会被阻塞 +- **释放(give)**:信号量计数值加 1。若有任务等待,会被唤醒 + +### 2. 常用 API + +``` +// 创建信号量 +rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); + +// 初始化静态信号量 +rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag); + +// 获取信号量 +rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout); + +// 释放信号量 +rt_err_t rt_sem_release(rt_sem_t sem); + +// 删除动态信号量 +rt_err_t rt_sem_delete(rt_sem_t sem); + +// 脱离静态信号量 +rt_err_t rt_sem_detach(rt_sem_t sem); +``` + +### 3. 示例代码 + +``` +// 创建一个初始值为1的信号量 +rt_sem_t sem = rt_sem_create("sem", 1, RT_IPC_FLAG_FIFO); + +// 任务中获取信号量 +if (rt_sem_take(sem, RT_WAITING_FOREVER) == RT_EOK) { + // 访问共享资源 + // ... + // 释放信号量 + rt_sem_release(sem); +} +``` + +## 二、队列(Queue) + +### 1. 核心特性 + +- **数据传递方式**:值传递(复制数据本身) +- **关键参数**:队列长度、项目大小 + +### 2. 常用 API + +``` +// 创建队列 +rt_mq_t rt_mq_create(const char *name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag); + +// 初始化静态队列 +rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag); + +// 发送消息到队列 +rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size); + +// 从队列接收消息 +rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout); + +// 删除动态队列 +rt_err_t rt_mq_delete(rt_mq_t mq); + +// 脱离静态队列 +rt_err_t rt_mq_detach(rt_mq_t mq); +``` + +### 3. 示例代码 + +``` +// 创建一个可存储5个int类型数据的队列 +rt_mq_t mq = rt_mq_create("data_q", sizeof(int), 5, RT_IPC_FLAG_FIFO); + +// 发送数据 +int data = 123; +rt_mq_send(mq, &data, sizeof(int)); + +// 接收数据 +int recv_data; +if (rt_mq_recv(mq, &recv_data, sizeof(int), RT_WAITING_FOREVER) == RT_EOK) { + // 处理接收到的数据 + // ... +} +``` + +## 三、互斥信号量(Mutex) + +### 1. 核心特性 + +- **所有权机制**:只有获取者才能释放 +- **优先级继承**:防止优先级翻转 + +### 2. 常用 API + +``` +// 创建互斥锁 +rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag); + +// 初始化静态互斥锁 +rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag); + +// 获取互斥锁 +rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout); + +// 释放互斥锁 +rt_err_t rt_mutex_release(rt_mutex_t mutex); + +// 删除动态互斥锁 +rt_err_t rt_mutex_delete(rt_mutex_t mutex); + +// 脱离静态互斥锁 +rt_err_t rt_mutex_detach(rt_mutex_t mutex); +``` + +### 3. 示例代码 + +``` +// 创建互斥锁 +rt_mutex_t mutex = rt_mutex_create("uart_mutex", RT_IPC_FLAG_FIFO); + +// 在访问串口前获取互斥锁 +if (rt_mutex_take(mutex, RT_WAITING_FOREVER) == RT_EOK) { + // 操作串口设备 + uart_send(data, len); + // 完成后释放互斥锁 + rt_mutex_release(mutex); +} +``` + +## 四、邮箱(Mailbox) + +### 1. 核心特点 + +- **数据容量**:每封邮件固定为 4 字节(32 位系统) +- **开销低、效率高** + +### 2. 常用 API + +``` +// 创建邮箱 +rt_mailbox_t rt_mb_create(const char *name, rt_size_t size, rt_uint8_t flag); + +// 初始化静态邮箱 +rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, void *msgpool, rt_size_t size, rt_uint8_t flag); + +// 发送邮件 +rt_err_t rt_mb_send(rt_mailbox_t mb, rt_uint32_t value); + +// 接收邮件 +rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_uint32_t *value, rt_int32_t timeout); + +// 删除动态邮箱 +rt_err_t rt_mb_delete(rt_mailbox_t mb); + +// 脱离静态邮箱 +rt_err_t rt_mb_detach(rt_mailbox_t mb); +``` + +### 3. 示例代码 + +``` +// 创建邮箱 +rt_mailbox_t mb = rt_mb_create("msg_mb", 10, RT_IPC_FLAG_FIFO); + +// 发送指针类型的邮件 +void *data_ptr = malloc(100); +rt_mb_send(mb, (rt_uint32_t)data_ptr); + +// 接收邮件 +rt_uint32_t recv_ptr; +if (rt_mb_recv(mb, &recv_ptr, RT_WAITING_FOREVER) == RT_EOK) { + // 转换为指针并处理数据 + void *data = (void *)recv_ptr; + // ... + free(data); +} +``` + +## 总结 + +- 信号量:用于任务同步,通过计数控制资源访问 +- 队列:用于数据传输,FIFO 机制保障数据有序性 +- 互斥信号量:保护共享资源,解决优先级翻转问题 +- 邮箱:轻量级通信,高效传递 4 字节数据(通常是指针) \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" new file mode 100644 index 0000000..589f6c2 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\272\214\345\244\251\347\254\224\350\256\260.md" @@ -0,0 +1,143 @@ +# 裸机与 RTOS 对比及 RTOS 内核入门 + +## 一、裸机与 RTOS 的核心区别 + +裸机与 RTOS 的本质差异在于系统架构设计,核心区别体现在复杂系统的处理能力上。RTOS 适用于功能丰富、逻辑复杂但代码耦合度低的工程;裸机则更适合单一功能的简单系统。 + +### 1. 裸机系统特点 + +- 优点 + + : + + - 运行效率高,无额外调度开销 + - 占用存储资源少,适合资源受限场景 + +- 缺点 + + - 代码耦合度高,复用性差 + - 逻辑设计不合理时易造成系统堵塞 + +- **运行模式**:采用**前后台方式**,前台为中断服务程序,后台为主要任务逻辑 + +### 2. RTOS 系统特点 + +- 优点 + + : + + - 代码模块化设计,耦合度低、复用性好 + - 支持复杂代码逻辑,多任务并行处理 + - 采用阻塞延时机制,挂起当前线程时可运行其他线程,不浪费 CPU 资源 + +- 缺点 + + : + + - 内核本身占用一定存储资源 + - 系统调度存在额外开销,不适合单一功能的嵌入式系统 + +- **运行模式**:通过时间分片模拟多任务 "同时" 运行 + +## 二、RTOS 内核核心概念 + +### 1. 临界区 + +指同一时间只能被一个线程使用的资源区域。当多线程访问同一资源(如串口)时,需通过临界区保护机制防止冲突(例如避免串口打印乱码)。 + +### 2. 线程基础要素 + +- **name**:线程名称,最大长度由`rtconfig.h`中`RT_NAME_MAX`宏定义,超出部分自动截断,可通过名称查找线程控制块 +- **entry**:线程入口函数 +- **parameter**:线程入口函数的参数 +- **stack_size**:线程栈大小(单位:字节),需满足地址对齐要求(如 ARM 架构需 4 字节对齐) +- **priority**:线程优先级,范围由`RT_THREAD_PRIORITY_MAX`宏定义(如 256 级优先级为 0~255,数值越小优先级越高) +- **tick**:时间片大小(单位:系统时钟节拍),用于同优先级线程的轮转调度,`RT_TICK_PER_SECOND`默认 1000(即 1ms) + +### 3. 线程栈结构 + +``` +struct stack_frame { + rt_uint32_t r4~r11; // 软件保存的寄存器 + struct exception_stack_frame exception_stack_frame; // 硬件自动压栈部分 +}; +struct exception_stack_frame { + rt_uint32_t r0~r3, r12, lr, pc, pse; // 异常发生时硬件自动保存的寄存器 +}; +``` + +## 三、系统启动流程 + +### 1. 启动文件差异(以 ARM 为例) + +- **裸机**:`.S`启动文件中`reset handler`执行后,经`SystemInit`(时钟配置)直接进入`main` + +``` +bl main +bx lr +``` + +**RTOS**:经`SystemInit`后进入`entry`函数启动系统 + +``` +bl entry +bx lr +``` + +### 2. RTOS 软件启动流程(以 RT-Thread 为例) + +1. `rt_hw_interrupt_disable()`:关闭中断,防止初始化被干扰 +2. `rt_hw_board_init()`:板级外设初始化 +3. `rt_show_version()`:打印版本信息(依赖前一步初始化的串口) +4. `rt_system_timer_init()`:初始化硬件定时器(基于中断) +5. `rt_system_scheduler_init()`:初始化调度器 +6. `rt_system_signal_init()`:初始化信号机制 +7. `rt_application_init()`:创建 main 线程(`RT_MAIN_THREAD_SIZE`定义栈大小) +8. `rt_system_timer_thread_init()`:初始化软件定时器(精度低于硬件定时器) +9. `rt_thread_idle_init()`:创建空闲线程(最低优先级,负责 CPU 统计、低功耗、回收僵尸线程) +10. `rt_system_scheduler_start()`:启动调度器,运行 main 线程 + +## 四、线程操作与调度 + +### 1. 线程创建方式 + +| 方式 | 特点 | 适用场景 | 对应销毁函数 | +| ----------------- | ------------------------ | ------------------------------ | ------------------------------------ | +| 静态创建(init) | 编译期分配内存,空间固定 | 安全类产品(不在乎内存开销) | detach(仅移除就绪列表,不释放内存) | +| 动态创建(creat) | 运行时分配内存 | 消费类电子(内存有限但功能多) | delete(释放线 | + +### 2. 核心线程 API + +- `startup`:将线程挂载到就绪列表,若新线程优先级更高则立即切换 +- `yield`:当前线程主动放弃 CPU,触发调度 +- `delay`:启动定时器,挂起当前线程 +- `control`:修改线程优先级 +- `suspend`:挂起线程 + +### 3. 调度机制 + +- **优先级抢占**:最高优先级的就绪线程立即运行 +- **时间片轮转**:同优先级线程按时间片轮流运行(仅在最高优先级线程间生效) +- **状态转换**:运行结束的线程通过`exit()`进入关闭状态;挂起状态可通过`delete()`/`detach()`进入关闭状态 + +> 关键原则:中断优先级(硬件)高于任何线程优先级(软件);调度依赖最低优先级的中断 + +## 五、实践技巧与作业设计 + +### 1. 开发技巧 + +- 线程栈初始值建议设为 1~2k,防止栈溢出导致系统崩溃 +- 通过`rtconfig.h`配置线程优先级范围(`RT_THREAD_PRIORITY_MAX`)和 main 线程参数(`RT_MAIN_THREAD_PRIORITY`默认 10,`FINSH_THREAD_PRIORITY`默认 20) +- 示例代码可参考:软件包→杂项软件包→sample + +### 2. 优先级调度示例设计 + +**目标**:体现优先级抢占与时间片轮询 +**优先级配置**:线程 1 > main 线程 > 线程 2 = 线程 3 +**执行逻辑**: + +1. main 线程启动线程 1 时,因线程 1 优先级更高,立即抢占 CPU 执行 +2. 线程 1 无循环,执行完毕后自动`exit()` +3. 线程 2 和线程 3 无`delay`,因优先级相同,按时间片轮询运行 + +通过该设计可直观观察高优先级线程的抢占行为和同优先级线程的轮转机制。 \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232.md" new file mode 100644 index 0000000..44cb216 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\344\272\224\345\244\251\344\275\234\344\270\232.md" @@ -0,0 +1,184 @@ +## Kconfig 配置系统详解 + +Kconfig 是 RT-Thread 实现灵活配置的核心机制,通过条件编译和宏定义实现系统功能的裁剪裁剪。在 Windows 环境下,我们主要通过 Env 工具中的 menuconfig 界面进行可视化配置。 + +### Kconfig 核心特点 + +- 配置项会自动映射到 rtconfig.h 中 +- 支持分散在各级目录,便于模块化管理 +- 提供图形化配置界面,简化配置过程 + +**配置流程映射关系**: + +``` +Kconfig 配置项 → .config 文件 → rtconfig.h 宏定义 +``` + +示例映射关系: + +``` +# .config 文件片段 +CONFIG_RT_USING_TIMER_SOFT=y +CONFIG_RT_TIMER_THREAD_PRIO=4 + +// 对应 rtconfig.h 片段 +#define RT_USING_TIMER_SOFT +#define RT_TIMER_THREAD_PRIO 4 +``` + +### Kconfig 语法元素 + +#### 1. config 语句(配置项) + +基本结构: + +``` +config BSP_USING_GPIO + bool "Enable GPIO" + select RT_USING_PIN + default y + help + Enable or disable GPIO driver support. + This option will enable pin device support automatically. +``` + +- **类型定义**:bool(布尔型)、tristate(三态)、string(字符串)、hex(十六进制)、int(整型) +- **select**:反向依赖,当前项选中时自动选中依赖项 +- **default**:默认值,布尔型用 y/n 表示 +- **help**:配置项说明信息 + +#### 2. menu/endmenu 语句(菜单) + +用于组织相关配置项: + +``` +menu "Hardware Drivers Config" + config BSP_USING_COM2 + bool "Enable COM2" + select BSP_USING_UART + default n + + config BSP_USING_COM3 + bool "Enable COM3" + select BSP_USING_UART3 + default n +endmenu +``` + +#### 3. if/endif 语句(条件判断) + +实现条件配置: + +``` +menuconfig BSP_USING_CAN + bool "Enable CAN" + default n + select RT_USING_CAN + + if BSP_USING_CAN + config BSP_USING_CAN1 + bool "Enable CAN1" + default n + endif +``` + +#### 4. choice/endchoice 语句(单选菜单) + +实现互斥选项: + +``` +choice + prompt "Select clock source" + default BSP_RTC_USING_LSE + + config BSP_RTC_USING_LSE + bool "RTC USING LSE" + + config BSP_RTC_USING_LSI + bool "RTC USING LSI" +endchoice +``` + +#### 5. comment 语句(注释说明) + +添加提示信息: + +``` +comment "RT-Thread Kernel Configuration" +menu "Basic Kernel Config" + # 配置项... +endmenu +``` + +## Cons 构建系统 + +### 什么是构建工具 + +构建工具用于将源代码按照一定规则编译为可执行程序,RT-Thread 采用 SCons 替代传统 Make,解决了跨平台问题并简化了构建配置。 + +与 IDE 的区别:IDE 是图形化界面工具,底层仍需调用构建工具;SCons 直接通过脚本控制构建过程。 + +### SCons 特点 + +- 基于 Python 语法,脚本即代码 +- 跨平台支持(Windows/Linux/macOS) +- 自动处理文件依赖关系 +- 支持并行编译 + +### RT-Thread 中 SCons 结构 + +``` +项目根目录/ +├── rtconfig.py # 构建配置(工具链、编译参数等) +├── SConstruct # 主构建脚本(入口文件) +├── SConscript # 根目录构建脚本 +├── Kconfig # 顶层配置文件 +└── rt-thread/ + ├── src/ + │ ├── SConscript # 子目录构建脚本 + │ └── Kconfig # 子目录配置文件 + └── ... +``` + +- **SConstruct**:构建入口,初始化环境 +- **SConscript**:各目录构建规则,支持分层构建 +- **rtconfig.py**:工具链和编译参数配置 + +### 常用 SCons 命令 + +| 命令 | 功能描述 | +| --------------------- | ---------------- | +| `scons` | 编译整个工程 | +| `scons -c` | 清除编译产物 | +| `scons -j4` | 4 线程并行编译 | +| `scons --target=mdk5` | 生成 MDK5 工程 | +| `scons --dist` | 生成发布版本工程 | +| `scons --dist-ide` | 导出 IDE 工程 | + +## 网络环境搭建 + +1. **获取网络工具包**: + +``` +pkgs --update +pkgs --install netutils +``` + +2.**配置网络功能**: +通过 `menuconfig` 配置: + +![image-20250802123330792](C:\Users\lx\AppData\Roaming\Typora\typora-user-images\image-20250802123330792.png) + +3.**保存配置并生成工程**: + +``` +scons --target=mdk5 +``` + +## 配置与构建流程总结 + +1. 通过 `menuconfig` 图形界面配置功能 +2. 配置结果保存到 `.config` 文件 +3. SCons 读取 `.config` 和 `rtconfig.py` +4. 根据 SConstruct 和 SConscript 脚本构建工程 +5. 生成的 rtconfig.h 提供宏定义供条件编译使用 \ No newline at end of file diff --git "a/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232.md" "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232.md" new file mode 100644 index 0000000..bbc34e4 --- /dev/null +++ "b/2025/\347\254\2544\347\273\204(FRDM-MCXA156)/\346\256\267\345\207\257\346\254\243/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232.md" @@ -0,0 +1,259 @@ +# RT-Thread 设备驱动框架及自定义设备开发 + +## 一、设备驱动分层架构 + +RT-Thread 的设备管理采用**分层设计**,通过指针实现类继承和多态特性,各层职责明确: + +| 层级 | 作用 | +| ---------- | ------------------------------------------------ | +| 应用层 | 调用标准化 IO 接口操作设备,无需关注硬件细节 | +| IO 管理层 | 提供统一设备操作 API,屏蔽底层差异 | +| 驱动框架层 | 定义设备类型规范,实现同类设备的通用逻辑 | +| 驱动层 | 针对具体硬件实现驱动细节,对接硬件寄存器与框架层 | +| 硬件层 | 实际物理设备(如 UART、SPI、GPIO 等) | + +**分层优势**:移植时只需修改驱动层和框架层的对接代码,应用层逻辑可完全复用,大幅降低移植成本。 + +## 二、核心设备管理 API + +IO 管理层提供标准化接口,所有设备均通过以下 API 操作: + +### 1. 设备查找与打开 + +``` +// 根据设备名称查找设备,返回设备句柄 +rt_device_t rt_device_find(const char *name); + +// 打开设备,oflag指定访问模式 +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag); +``` + +- oflag 常用标志 + + : + + - `RT_DEVICE_OFLAG_RDONLY`:只读模式 + - `RT_DEVICE_OFLAG_WRONLY`:只写模式 + - `RT_DEVICE_OFLAG_RDWR`:读写模式 + - `RT_DEVICE_FLAG_INT_RX`:中断接收模式 + - `RT_DEVICE_FLAG_DMA_TX`:DMA 发送模式 + +- 调用后设备的`ref_count`(引用计数)加 1 + +### 2. 数据读写 + +``` +// 从设备读取数据到缓冲区 +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, + void *buffer, rt_size_t size); + +// 将缓冲区数据写入设备 +rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, + const void *buffer, rt_size_t size); +``` + +返回值为实际读写的字节数,`pos`为操作偏移量 + +### 3. 设备控制与关闭 + +``` +// 发送控制命令(如配置设备、获取状态) +rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg); + +// 关闭设备(ref_count减1,为0时真正释放) +rt_err_t rt_device_close(rt_device_t dev); +``` + +- 常用控制命令:`RT_DEVICE_CTRL_RTC_SET_TIME`(设置 RTC 时间)、`RT_DEVICE_CTRL_BLK_GETGEOME`(获取块设备信息) + +## 三、自定义设备驱动开发流程 + +以创建`虚拟设备(rt_vir_device)`为例,完整流程如下: + +### 1. 定义设备结构体 + +基于`rt_device`扩展,实现类继承: + +``` +// 虚拟设备操作集(驱动层需实现的接口) +struct rt_vir_ops { + void (*print_info)(struct rt_device* device); // 打印设备信息 + void (*set_val)(struct rt_device* device, rt_uint32_t val); // 设置值 + void (*get_val)(struct rt_device* device, rt_uint32_t* val); // 获取值 +}; + +// 虚拟设备结构体(继承rt_device) +struct rt_vir_device { + struct rt_device parent; // 父类,必须放在首位(用于类型转换) + const struct rt_vir_ops *ops; // 操作集指针 +}; +typedef struct rt_vir_device* rt_vir_device_t; +``` + +### 2. 实现 IO 管理层接口 + +将`rt_device`的函数指针绑定到自定义实现: + +``` +// 设备初始化 +static rt_err_t _vir_init(rt_device_t dev) { + rt_kprintf("Virtual Device Initialized!\n"); + return RT_EOK; +} + +// 设备打开 +static rt_err_t _vir_open(rt_device_t dev, rt_uint16_t oflag) { + rt_kprintf("Hello From Virtual Device!\n"); + return RT_EOK; +} + +// 设备关闭 +static rt_err_t _vir_close(rt_device_t dev) { + rt_kprintf("Virtual Device is closed!\n"); + return RT_EOK; +} + +// 数据读取(调用自定义ops接口) +static rt_size_t _vir_read(rt_device_t dev, rt_off_t pos, + void *buffer, rt_size_t size) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if (vir->ops) { + vir->ops->get_val(dev, (rt_uint32_t*)buffer); + return 4; // 固定返回4字节(32位值) + } + return 0; +} + +// 数据写入(调用自定义ops接口) +static rt_size_t _vir_write(rt_device_t dev, rt_off_t pos, + const void *buffer, rt_size_t size) { + rt_vir_device_t vir = (rt_vir_device_t)dev; + if (vir->ops) { + vir->ops->set_val(dev, *(rt_uint32_t*)buffer); + return 4; // 固定返回4字节 + } + return 0; +} + +// 设备控制 +static rt_err_t _vir_control(rt_device_t dev, int cmd, void *args) { + rt_kprintf("Virtual Device received cmd: %d\n", cmd); + return RT_EOK; +} +``` + +### 3. 设备注册函数 + +将自定义设备注册到系统,完成与 IO 管理层的对接: + +``` +rt_err_t rt_hw_vir_register(rt_vir_device_t device, const char* name, + const struct rt_vir_ops* ops, const void* user_data) { + RT_ASSERT(ops != RT_NULL); // 确保操作集不为空 + rt_err_t result; + + // 绑定操作函数 + device->ops = ops; + device->parent.init = _vir_init; + device->parent.open = _vir_open; + device->parent.close = _vir_close; + device->parent.read = _vir_read; + device->parent.write = _vir_write; + device->parent.control = _vir_control; + + // 注册设备到系统 + result = rt_device_register(&device->parent, name, RT_DEVICE_FLAG_RDWR); + return result; +} +``` + +### 4. 驱动层实现(具体设备) + +定义具体设备实例并实现`rt_vir_ops`接口: + +``` +// 具体设备结构体(继承虚拟设备) +struct vir_device { + struct rt_vir_device parent; // 继承rt_vir_device + rt_uint32_t val; // 设备私有数据:存储值 + char* info; // 设备私有数据:描述信息 +}; + +// 实现打印信息接口 +static void vir_print_info(struct rt_device* device) { + struct vir_device* dev = (struct vir_device*)device; + if (dev->info) rt_kprintf("VIR Info: %s\n", dev->info); +} + +// 实现设置值接口 +static void vir_set_val(struct rt_device* device, rt_uint32_t val) { + struct vir_device* dev = (struct vir_device*)device; + dev->val = val; // 写入私有数据 +} + +// 实现获取值接口 +static void vir_get_val(struct rt_device* device, rt_uint32_t* val) { + struct vir_device* dev = (struct vir_device*)device; + *val = dev->val; // 读取私有数据 +} + +// 定义操作集(绑定具体实现) +static struct rt_vir_ops vir_ops = { + .print_info = vir_print_info, + .set_val = vir_set_val, + .get_val = vir_get_val +}; +``` + +### 5. 设备初始化与自动启动 + +通过 RT-Thread 自动初始化机制注册设备: + +``` +// 定义设备实例 +static struct vir_device vir_dev; + +// 设备初始化函数 +static int vir_device_init() { + // 初始化私有数据 + vir_dev.val = 0; + vir_dev.info = "This is a Virtual Device!"; + + // 注册设备(名称限制:RT_NAME_MAX默认为8字节) + rt_hw_vir_register(&vir_dev.parent, "VIRPipu", &vir_ops, &vir_dev.info); + return 0; +} + +// 自动初始化(系统启动时执行) +INIT_APP_EXPORT(vir_device_init); +``` + +## 四、应用层调用示例 + +``` +int main(void) { + // 1. 查找设备 + rt_device_t dev = rt_device_find("VIRPipu"); + if (dev == RT_NULL) { + rt_kprintf("Failed to find VIR Device\n"); + return -RT_ERROR; + } + + // 2. 打开设备 + rt_device_open(dev, RT_DEVICE_FLAG_RDWR); + + // 3. 写入数据 + rt_uint32_t send_val = 721; + rt_device_write(dev, 0, &send_val, 4); // 写入32位值 + + // 4. 读取数据 + rt_uint32_t recv_val; + rt_device_read(dev, 0, &recv_val, 4); + rt_kprintf("Read from VIR Device: %d\n", recv_val); + + // 5. 关闭设备 + rt_device_close(dev); + return 0; +} +``` + -- Gitee