├── 01_第十二届 └── 省赛 │ ├── 十二届省赛客观题.pdf │ ├── 十二届省赛程序设计题.pdf │ └── 第十二届省赛真题参考代码.zip ├── 02_第十三届 └── 省赛 │ ├── 13届省赛客观题.pdf │ ├── 13届省赛程序设计题.pdf │ └── 第十三届省赛真题参考代码.zip ├── 03_第十四届 ├── 国赛 │ ├── Readme.txt │ └── 第十四届国赛程序设计题.pdf └── 省赛 │ ├── 14届省赛客观题.pdf │ ├── 14届省赛程序设计题.pdf │ └── 第十四届省赛真题参考代码.zip ├── 04_第十五届 ├── 国赛 │ ├── 15F1D_ES.pdf │ ├── 15F1S_ES.pdf │ ├── 嵌入式第十五届国赛上位机参考代码.zip │ ├── 第十五届国赛真题参考代码.zip │ └── 第十五届国赛真题笔记.md ├── 模拟题 │ ├── ADC采集控制系统模拟题 │ │ ├── ADC采集控制系统.pdf │ │ └── ADC采集控制系统参考代码.zip │ └── 第十六届模拟二 │ │ ├── 第十六届模拟二参考代码.zip │ │ └── 第十六届模拟二客观题.pdf └── 省赛 │ ├── 15届省赛客观题赛题一.pdf │ ├── 15届省赛程序设计题赛题一.pdf │ └── 第十五届省赛真题参考代码.zip └── README.md /01_第十二届/省赛/十二届省赛客观题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/01_第十二届/省赛/十二届省赛客观题.pdf -------------------------------------------------------------------------------- /01_第十二届/省赛/十二届省赛程序设计题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/01_第十二届/省赛/十二届省赛程序设计题.pdf -------------------------------------------------------------------------------- /01_第十二届/省赛/第十二届省赛真题参考代码.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/01_第十二届/省赛/第十二届省赛真题参考代码.zip -------------------------------------------------------------------------------- /02_第十三届/省赛/13届省赛客观题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/02_第十三届/省赛/13届省赛客观题.pdf -------------------------------------------------------------------------------- /02_第十三届/省赛/13届省赛程序设计题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/02_第十三届/省赛/13届省赛程序设计题.pdf -------------------------------------------------------------------------------- /02_第十三届/省赛/第十三届省赛真题参考代码.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/02_第十三届/省赛/第十三届省赛真题参考代码.zip -------------------------------------------------------------------------------- /03_第十四届/国赛/Readme.txt: -------------------------------------------------------------------------------- 1 | 由于从第十五届开始就全部使用的新的开发板,然后本届国赛题目上的有些功能需要拓展版实现,所以这里就用其他方式代替实现: 2 | 1. 测量输入到 PA1引脚的脉冲信号频率和占空比——约定 PA1 替换成 PB4 3 | 2. 通过资源扩展板上的DS18B20(PA6-DS18B20:DQ)获取环境温度数据——这里采用另一路 ADC 模拟实现 -------------------------------------------------------------------------------- /03_第十四届/国赛/第十四届国赛程序设计题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/03_第十四届/国赛/第十四届国赛程序设计题.pdf -------------------------------------------------------------------------------- /03_第十四届/省赛/14届省赛客观题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/03_第十四届/省赛/14届省赛客观题.pdf -------------------------------------------------------------------------------- /03_第十四届/省赛/14届省赛程序设计题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/03_第十四届/省赛/14届省赛程序设计题.pdf -------------------------------------------------------------------------------- /03_第十四届/省赛/第十四届省赛真题参考代码.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/03_第十四届/省赛/第十四届省赛真题参考代码.zip -------------------------------------------------------------------------------- /04_第十五届/国赛/15F1D_ES.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/国赛/15F1D_ES.pdf -------------------------------------------------------------------------------- /04_第十五届/国赛/15F1S_ES.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/国赛/15F1S_ES.pdf -------------------------------------------------------------------------------- /04_第十五届/国赛/嵌入式第十五届国赛上位机参考代码.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/国赛/嵌入式第十五届国赛上位机参考代码.zip -------------------------------------------------------------------------------- /04_第十五届/国赛/第十五届国赛真题参考代码.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/国赛/第十五届国赛真题参考代码.zip -------------------------------------------------------------------------------- /04_第十五届/国赛/第十五届国赛真题笔记.md: -------------------------------------------------------------------------------- 1 | ```c 2 | typedef struct CoordinateNode 3 | { 4 | uint16_t x; // 节点的x坐标 5 | uint16_t y; // 节点的y坐标 6 | struct CoordinateNode *next; // 指向下一个节点的指针 7 | } CoordinateNode; 8 | 9 | CoordinateNode *waypoint_head = NULL; // 定义链表的头指针,初始化为空 10 | ``` 11 | 12 | **解释:** 13 | 14 | 1. `typedef struct CoordinateNode`: 15 | 16 | 定义了链表节点的数据结构。 17 | 18 | - 每个节点存储两个信息:坐标值 `x` 和 `y`。 19 | - 每个节点通过 `next` 指针连接到链表的下一个节点。 20 | 21 | 2. **`waypoint_head`:** 链表的头指针,用来指向链表的第一个节点。如果链表为空,则 `waypoint_head == NULL`。 22 | 23 | ------ 24 | 25 | ## 函数 1 26 | 27 | **功能:** 在链表中添加一个新的坐标点 `(x, y)`。 28 | 29 | **代码:** 30 | 31 | ```c 32 | uint8_t add_waypoint(uint16_t x, uint16_t y) 33 | { 34 | CoordinateNode *current = waypoint_head; // 从链表头开始遍历 35 | while (current != NULL) // 遍历链表,检查是否存在相同坐标 36 | { 37 | if (current->x == x && current->y == y) // 如果当前节点已存在相同坐标 38 | { 39 | return 0; // 坐标已存在,返回失败 40 | } 41 | current = current->next; // 移动到下一个节点 42 | } 43 | 44 | // 分配新节点内存 45 | CoordinateNode *new_node = (CoordinateNode *)malloc(sizeof(CoordinateNode)); 46 | if (new_node == NULL) // 如果内存分配失败 47 | { 48 | return 0; // 返回失败 49 | } 50 | 51 | // 初始化新节点的数据 52 | new_node->x = x; // 设置x坐标 53 | new_node->y = y; // 设置y坐标 54 | new_node->next = waypoint_head; // 新节点的next指针指向当前链表头 55 | 56 | waypoint_head = new_node; // 更新链表头为新节点 57 | return 1; // 返回成功 58 | } 59 | ``` 60 | 61 | 1. **检查是否有重复节点:** 62 | 63 | ```c 64 | CoordinateNode *current = waypoint_head; 65 | while (current != NULL) 66 | { 67 | if (current->x == x && current->y == y) 68 | { 69 | return 0; // 如果发现已有节点和新点相同,返回失败 70 | } 71 | current = current->next; 72 | } 73 | ``` 74 | 75 | - 使用指针 `current` 遍历链表,从 `waypoint_head` 开始。 76 | - 如果发现链表中已有节点的 `x` 和 `y` 坐标与新坐标相同,则退出。 77 | 78 | 2. **分配新节点:** 79 | 80 | ```c 81 | CoordinateNode *new_node = (CoordinateNode *)malloc(sizeof(CoordinateNode)); 82 | if (new_node == NULL) 83 | { 84 | return 0; // 内存不足,无法分配 85 | } 86 | ``` 87 | 88 | - 通过 `malloc` 动态分配新节点的内存,大小为 `sizeof(CoordinateNode)`。 89 | 90 | 3. **初始化新节点:** 91 | 92 | ```c 93 | new_node->x = x; // 设置新节点的x值 94 | new_node->y = y; // 设置新节点的y值 95 | new_node->next = waypoint_head; // 新节点的next指针指向当前链表头 96 | ``` 97 | 98 | - 将新节点的 `next` 指针指向当前链表头部(即 `waypoint_head`)。 99 | 100 | 4. **更新链表头:** 101 | 102 | ```c 103 | waypoint_head = new_node; // 更新链表头为新节点 104 | ``` 105 | 106 | - 将新节点作为新的链表头。 107 | 108 | ------ 109 | 110 | ## 函数 2 111 | 112 | **功能:** 从链表中移除指定的坐标点 `(x, y)`。 113 | 114 | **代码:** 115 | 116 | ```c 117 | uint8_t remove_waypoint(uint16_t x, uint16_t y) 118 | { 119 | CoordinateNode *current = waypoint_head; // 当前节点指针 120 | CoordinateNode *prev = NULL; // 当前节点的前驱节点指针 121 | 122 | while (current != NULL) // 遍历链表 123 | { 124 | if (current->x == x && current->y == y) // 找到目标节点 125 | { 126 | if (prev == NULL) // 如果目标节点是头节点 127 | { 128 | waypoint_head = current->next; // 更新头指针 129 | } 130 | else // 如果目标节点不是头节点 131 | { 132 | prev->next = current->next; // 让前驱节点跳过当前节点 133 | } 134 | 135 | free(current); // 释放当前节点内存 136 | return 1; // 返回成功 137 | } 138 | 139 | prev = current; // 更新前驱节点为当前节点 140 | current = current->next; // 遍历下一个节点 141 | } 142 | 143 | return 0; // 未找到目标节点,返回失败 144 | } 145 | ``` 146 | 147 | 1. **遍历链表,查找目标节点:** 148 | 149 | ```c 150 | CoordinateNode *current = waypoint_head; 151 | CoordinateNode *prev = NULL; 152 | 153 | while (current != NULL) 154 | { 155 | if (current->x == x && current->y == y) 156 | { 157 | // 删除逻辑 158 | } 159 | prev = current; 160 | current = current->next; 161 | } 162 | ``` 163 | 164 | - 从头指针 `waypoint_head` 开始遍历链表。 165 | - 如果 `current->x == x` 且 `current->y == y`,说明找到了目标节点。 166 | - 使用两个指针: 167 | - `current`:表示当前遍历到的节点。 168 | - `prev`:表示当前节点的前一个节点,便于调整前驱节点的 `next` 指针。 169 | 170 | 2. **删除节点的逻辑:** 171 | 172 | - **如果删除的是头节点:** 173 | 174 | ```c 175 | if (prev == NULL) // 如果目标节点是头节点 176 | { 177 | waypoint_head = current->next; // 直接更新链表头 178 | } 179 | ``` 180 | 181 | - 如果前驱节点为 `NULL`,说明当前节点是头节点,直接将头指针指向下一个节点。 182 | 183 | - **如果删除的是非头节点:** 184 | 185 | ```c 186 | else 187 | { 188 | prev->next = current->next; // 前驱节点的next指向当前节点的next 189 | } 190 | ``` 191 | 192 | - 将前驱节点 `prev->next` 跳过当前节点,指向 `current->next`。 193 | 194 | - **释放节点:** 195 | 196 | ```c 197 | free(current); // 释放目标节点内存 198 | return 1; // 删除成功 199 | ``` 200 | 201 | 3. **未找到目标节点:** 202 | 203 | ```c 204 | return 0; // 遍历结束未找到目标节点 205 | ``` 206 | 207 | ------ 208 | 209 | ## 函数 3 210 | 211 | **功能:** 遍历链表并打印所有节点的坐标点。 212 | 213 | **代码:** 214 | 215 | ```c 216 | void print_waypoints() 217 | { 218 | CoordinateNode *current = waypoint_head; // 从链表头开始 219 | while (current != NULL) // 遍历链表 220 | { 221 | debug_debug("(%d, %d)\n", current->x, current->y); // 打印当前节点的坐标 222 | current = current->next; // 指向下一个节点 223 | } 224 | } 225 | ``` 226 | 227 | 1. **从头指针开始遍历:** 228 | 229 | ```c 230 | CoordinateNode *current = waypoint_head; 231 | while (current != NULL) 232 | { 233 | debug_debug("(%d, %d)\n", current->x, current->y); 234 | current = current->next; 235 | } 236 | ``` 237 | 238 | - 使用 `current` 指针从头节点开始逐个遍历。 239 | - 在每次循环中,打印当前节点的坐标值。 240 | 241 | 2. **打印方式:** 242 | 243 | ```c 244 | debug_debug("(%d, %d)\n", current->x, current->y); 245 | ``` 246 | 247 | - 调用 `debug_debug` 函数打印当前节点的坐标。 248 | 249 | ------ 250 | 251 | 1. **单向链表结构:** 每个节点存储数据(坐标 `x, y`),并通过 `next` 指针连接到下一个节点。 252 | 2. **动态内存管理:** 使用 `malloc` 动态分配节点,使用 `free` 释放节点内存。 253 | 3. 操作复杂度: 254 | - 插入操作(头插法):时间复杂度为 **O(1)**。 255 | - 删除操作:需要遍历链表,时间复杂度为 **O(n)**。 256 | - 打印操作:需要遍历链表,时间复杂度为 **O(n)**。 257 | 258 | ------ 259 | 260 | ## **函数4** 261 | 262 | **功能:** 263 | 解析通过 UART 接收到的命令,根据命令类型对链表执行不同操作,包括添加坐标点、删除坐标点等操作。 264 | 265 | **代码:** 266 | 267 | ```c 268 | void paese_uart_cmd(uint8_t *cmd) 269 | { 270 | if (strstr((const char *)cmd, "(") != NULL && strstr((const char *)cmd, ")") != NULL) 271 | { 272 | uint16_t length = strstr((const char *)cmd, ")") - strstr((const char *)cmd, "(") - 1; 273 | char *data = (char *)malloc(length + 1); 274 | strncpy(data, strstr((const char *)cmd, "(") + 1, length); 275 | 276 | char *token = strtok(data, ","); 277 | uint16_t coord_count = get_coord_count((char *)cmd); 278 | uint16_t *coordinates = (uint16_t *)malloc(coord_count * sizeof(uint16_t)); 279 | 280 | uint8_t index = 0; 281 | while (token != NULL) 282 | { 283 | coordinates[index++] = atoi(token); 284 | token = strtok(NULL, ","); 285 | } 286 | 287 | if (index % 2 == 0) 288 | { 289 | for (int i = 0; i < index / 2; i++) 290 | { 291 | if (!add_waypoint(coordinates[i * 2], coordinates[i * 2 + 1])) 292 | { 293 | free(data); 294 | free(coordinates); 295 | return; 296 | } 297 | } 298 | } 299 | free(data); 300 | free(coordinates); 301 | } 302 | else if (strstr((const char *)cmd, "{") != NULL && strstr((const char *)cmd, "}") != NULL) 303 | { 304 | char data[16]; 305 | uint16_t length = strstr((const char *)cmd, "}") - strstr((const char *)cmd, "{") - 1; 306 | strncpy(data, strstr((const char *)cmd, "{") + 1, length); 307 | 308 | uint16_t x = atoi(strtok(data, ",")); 309 | uint16_t y = atoi(strtok(NULL, ",")); 310 | remove_waypoint(x, y); 311 | } 312 | else 313 | { 314 | debug_debug("Error\n"); 315 | } 316 | } 317 | ``` 318 | 319 | ------ 320 | 321 | ### **核心功能详解** 322 | 323 | 1. **解析命令格式:** 324 | - 使用 `strstr` 检查命令是否包含特定的标志(如 `"("`、`")"` 或 `"{`、`"}"`)。 325 | - 根据标志区分是添加坐标还是删除坐标的操作。 326 | 2. **添加坐标点(格式:`(x1,y1,x2,y2,...)`):** 327 | - 提取括号中的内容,解析出多个坐标点,并调用 `add_waypoint` 添加到链表。 328 | 3. **删除坐标点(格式:`{x,y}`):** 329 | - 提取大括号中的内容,解析出目标坐标,并调用 `remove_waypoint` 从链表中移除。 330 | 4. **错误处理:** 331 | - 如果命令格式不符合要求,打印错误信息。 332 | 333 | ------ 334 | 335 | #### **添加坐标点** 336 | 337 | ```c 338 | if (strstr((const char *)cmd, "(") != NULL && strstr((const char *)cmd, ")") != NULL) 339 | { 340 | uint16_t length = strstr((const char *)cmd, ")") - strstr((const char *)cmd, "(") - 1; 341 | char *data = (char *)malloc(length + 1); 342 | strncpy(data, strstr((const char *)cmd, "(") + 1, length); 343 | 344 | char *token = strtok(data, ","); 345 | uint16_t coord_count = get_coord_count((char *)cmd); 346 | uint16_t *coordinates = (uint16_t *)malloc(coord_count * sizeof(uint16_t)); 347 | 348 | uint8_t index = 0; 349 | while (token != NULL) 350 | { 351 | coordinates[index++] = atoi(token); 352 | token = strtok(NULL, ","); 353 | } 354 | } 355 | ``` 356 | 357 | **解析步骤:** 358 | 359 | 1. **检查命令格式:** 360 | 361 | ```c 362 | strstr((const char *)cmd, "(") != NULL && strstr((const char *)cmd, ")") != NULL 363 | ``` 364 | 365 | - 使用 `strstr` 检查命令中是否包含 `(` 和 `)`。 366 | - 如果包含,则说明是添加坐标命令。 367 | 368 | 2. **提取括号中的内容:** 369 | 370 | ```c 371 | uint16_t length = strstr((const char *)cmd, ")") - strstr((const char *)cmd, "(") - 1; 372 | char *data = (char *)malloc(length + 1); 373 | strncpy(data, strstr((const char *)cmd, "(") + 1, length); 374 | ``` 375 | 376 | - 计算括号内数据的长度。 377 | - 分配动态内存存储括号内的数据。 378 | - 使用 `strncpy` 提取括号中的内容。 379 | 380 | 3. **解析坐标数据:** 381 | 382 | ```c 383 | char *token = strtok(data, ","); 384 | uint16_t coord_count = get_coord_count((char *)cmd); 385 | uint16_t *coordinates = (uint16_t *)malloc(coord_count * sizeof(uint16_t)); 386 | 387 | uint8_t index = 0; 388 | while (token != NULL) 389 | { 390 | coordinates[index++] = atoi(token); // 将每个字符串转为整数 391 | token = strtok(NULL, ","); // 获取下一个数据 392 | } 393 | ``` 394 | 395 | - 使用 `strtok` 按 `,` 分隔数据,逐一提取坐标点。 396 | - 每次提取一个数据后,将其转换为整数并存储到 `coordinates` 数组中。 397 | 398 | ------ 399 | 400 | #### **坐标点添加链表** 401 | 402 | ```c 403 | if (index % 2 == 0) // 检查坐标数量是否为偶数,确保成对 404 | { 405 | for (int i = 0; i < index / 2; i++) 406 | { 407 | if (!add_waypoint(coordinates[i * 2], coordinates[i * 2 + 1])) 408 | { 409 | free(data); 410 | free(coordinates); 411 | return; 412 | } 413 | } 414 | } 415 | free(data); 416 | free(coordinates); 417 | ``` 418 | 419 | **解析步骤:** 420 | 421 | 1. **检查数据合法性:** 422 | 423 | ```c 424 | if (index % 2 == 0) 425 | ``` 426 | 427 | - 如果坐标数量为偶数(必须成对出现),才进行下一步操作。 428 | 429 | 2. **逐对添加坐标点:** 430 | 431 | ```c 432 | for (int i = 0; i < index / 2; i++) 433 | { 434 | add_waypoint(coordinates[i * 2], coordinates[i * 2 + 1]); 435 | } 436 | ``` 437 | 438 | - 遍历坐标数组,每两个元素组成一个 `(x, y)` 坐标。 439 | - 调用 `add_waypoint` 将坐标点添加到链表。 440 | 441 | 3. **释放动态内存:** 442 | 443 | ```c 444 | free(data); 445 | free(coordinates); 446 | ``` 447 | 448 | - 使用 `malloc` 分配的内存在使用完成后必须释放,避免内存泄漏。 449 | 450 | ------ 451 | 452 | #### **删除坐标点** 453 | 454 | ```c 455 | else if (strstr((const char *)cmd, "{") != NULL && strstr((const char *)cmd, "}") != NULL) 456 | { 457 | char data[16]; 458 | uint16_t length = strstr((const char *)cmd, "}") - strstr((const char *)cmd, "{") - 1; 459 | strncpy(data, strstr((const char *)cmd, "{") + 1, length); 460 | 461 | uint16_t x = atoi(strtok(data, ",")); 462 | uint16_t y = atoi(strtok(NULL, ",")); 463 | remove_waypoint(x, y); 464 | } 465 | ``` 466 | 467 | **解析步骤:** 468 | 469 | 1. **检查命令格式:** 470 | 471 | ```c 472 | strstr((const char *)cmd, "{") != NULL && strstr((const char *)cmd, "}") != NULL 473 | ``` 474 | 475 | - 使用 `strstr` 检查命令中是否包含 `{` 和 `}`。 476 | - 如果包含,则说明是删除坐标命令。 477 | 478 | 2. **提取大括号中的内容:** 479 | 480 | ```c 481 | uint16_t length = strstr((const char *)cmd, "}") - strstr((const char *)cmd, "{") - 1; 482 | strncpy(data, strstr((const char *)cmd, "{") + 1, length); 483 | ``` 484 | 485 | - 提取 `{}` 中的数据。 486 | 487 | 3. **解析坐标数据:** 488 | 489 | ```c 490 | uint16_t x = atoi(strtok(data, ",")); 491 | uint16_t y = atoi(strtok(NULL, ",")); 492 | ``` 493 | 494 | - 使用 `strtok` 按 `,` 分隔数据,提取 `x` 和 `y` 坐标值。 495 | 496 | 4. **调用删除函数:** 497 | 498 | ```c 499 | remove_waypoint(x, y); 500 | ``` 501 | 502 | - 调用 `remove_waypoint` 从链表中移除目标坐标点。 503 | 504 | ------ 505 | 506 | #### **处理错误命令** 507 | 508 | ```c 509 | else 510 | { 511 | debug_debug("Error\n"); 512 | } 513 | ``` 514 | 515 | - 如果命令格式既不包含 `(...)` 也不包含 `{}`,说明命令格式不正确,打印错误信息。 516 | 517 | ------ 518 | 519 | ## **将插入操作改为尾插法** 520 | 521 | **尾插法**是将新节点插入到链表的末尾(尾部),与头插法不同,它需要遍历整个链表,找到最后一个节点,然后将新节点连接到该节点的 `next` 指针上。 522 | 523 | ```c 524 | uint8_t add_waypoint_tail(uint16_t x, uint16_t y) 525 | { 526 | CoordinateNode *current = waypoint_head; // 用于遍历链表的指针 527 | 528 | // 遍历链表,检查是否有重复的坐标 529 | while (current != NULL) 530 | { 531 | if (current->x == x && current->y == y) // 检查当前节点是否有重复坐标 532 | { 533 | return 0; // 如果坐标已存在,返回失败 534 | } 535 | if (current->next == NULL) // 如果到达链表末尾,停止遍历 536 | { 537 | break; 538 | } 539 | current = current->next; // 移动到下一个节点 540 | } 541 | 542 | // 创建新节点 543 | CoordinateNode *new_node = (CoordinateNode *)malloc(sizeof(CoordinateNode)); 544 | if (new_node == NULL) // 检查内存分配是否成功 545 | { 546 | return 0; // 如果分配失败,返回失败 547 | } 548 | 549 | // 初始化新节点的数据 550 | new_node->x = x; // 设置新节点的 x 坐标 551 | new_node->y = y; // 设置新节点的 y 坐标 552 | new_node->next = NULL; // 新节点的 next 指针置为 NULL,因为它将是链表尾部 553 | 554 | // 如果链表为空,新节点直接成为链表头 555 | if (waypoint_head == NULL) 556 | { 557 | waypoint_head = new_node; // 更新链表头指针 558 | } 559 | else 560 | { 561 | current->next = new_node; // 将新节点链接到链表的最后一个节点 562 | } 563 | 564 | return 1; // 返回成功 565 | } 566 | ``` 567 | 568 | ------ 569 | 570 | 1. **尾插法与头插法的区别:** 571 | - 头插法: 572 | - 将新节点插入到链表头部。 573 | - 不需要遍历链表,效率高,但新插入的节点会改变链表的节点顺序(后插入的节点会排在链表前面)。 574 | - 尾插法: 575 | - 将新节点插入到链表尾部。 576 | - 遍历整个链表找到尾节点,将 `next` 指针指向新节点,可以保持节点的插入顺序。 577 | 2. **为什么需要遍历到链表尾部?** 578 | - 因为单向链表的每个节点只有一个指向下一个节点的 `next` 指针,无法直接找到尾节点,必须从头节点开始,依次遍历到 `next == NULL` 的节点。 579 | 3. **尾插法中新增节点必须将 `next` 置为 `NULL`:** 580 | - 因为新节点是链表的最后一个节点,后面没有其他节点,所以它的 `next` 指针必须指向 `NULL`。 581 | 582 | ------ 583 | 584 | ### **代码分段详解** 585 | 586 | #### **第一部分:检查重复并找到尾节点** 587 | 588 | ```c 589 | CoordinateNode *current = waypoint_head; // 从链表头开始 590 | while (current != NULL) // 遍历链表 591 | { 592 | if (current->x == x && current->y == y) // 检查是否有重复的坐标 593 | { 594 | return 0; // 如果已存在坐标,返回失败 595 | } 596 | if (current->next == NULL) // 如果到达链表尾节点 597 | { 598 | break; // 停止遍历 599 | } 600 | current = current->next; // 移动到下一个节点 601 | } 602 | ``` 603 | 604 | **解释:** 605 | 606 | 1. 用 `current` 指针从链表头开始遍历每个节点。 607 | 2. 在遍历过程中: 608 | - 检查当前节点是否与待插入的坐标重复。 609 | - 如果 `current->next == NULL`,说明当前节点是尾节点,停止遍历。 610 | 611 | #### **第二部分:分配并初始化新节点** 612 | 613 | ```c 614 | CoordinateNode *new_node = (CoordinateNode *)malloc(sizeof(CoordinateNode)); 615 | if (new_node == NULL) // 检查内存分配是否成功 616 | { 617 | return 0; // 分配失败,返回失败 618 | } 619 | 620 | new_node->x = x; // 初始化新节点的 x 坐标 621 | new_node->y = y; // 初始化新节点的 y 坐标 622 | new_node->next = NULL; // 新节点的 next 必须指向 NULL 623 | ``` 624 | 625 | **解释:** 626 | 627 | 1. 动态分配一个新节点的内存,并初始化其数据。 628 | 2. 设置 `next = NULL`,因为新节点将是链表的最后一个节点。 629 | 630 | #### **第三部分:将新节点插入到链表尾部** 631 | 632 | ```c 633 | if (waypoint_head == NULL) // 如果链表为空 634 | { 635 | waypoint_head = new_node; // 新节点直接作为链表头 636 | } 637 | else 638 | { 639 | current->next = new_node; // 将新节点链接到尾节点 640 | } 641 | ``` 642 | 643 | **解释:** 644 | 645 | 1. 特殊情况:链表为空(`waypoint_head == NULL`): 646 | - 如果链表为空,直接将新节点设置为链表头。 647 | 2. 一般情况:链表非空: 648 | - 将 `current->next`(尾节点的 `next`)指向新节点,将新节点链接到链表尾部。 649 | 650 | ------ 651 | 652 | ### **修改后的复杂度** 653 | 654 | #### **时间复杂度** 655 | 656 | 1. 遍历链表查找尾节点和检查重复,时间复杂度为 **O(n)**,其中 `n` 是链表长度。 657 | 2. 插入操作本身(将尾节点的 `next` 指针指向新节点)是 **O(1)**。 658 | 659 | #### **空间复杂度** 660 | 661 | - 动态分配新节点的空间,空间复杂度为 **O(1)**。 662 | 663 | ------ 664 | 665 | ### **头插法与尾插法的对比** 666 | 667 | | **对比项** | **头插法** | **尾插法** | 668 | | :------------: | :------------------------: | :--------------------------: | 669 | | **插入位置** | 新节点插入到链表头部 | 新节点插入到链表尾部 | 670 | | **实现复杂度** | 无需遍历,直接插入(O(1)) | 需要遍历到尾节点(O(n)) | 671 | | **节点顺序** | 插入顺序与链表顺序相反 | 插入顺序与链表顺序一致 | 672 | | **应用场景** | 更适合对顺序无要求的场景 | 更适合对插入顺序有要求的场景 | 673 | 674 | ------ 675 | 676 | 1. **保持插入顺序:** 尾插法可以保证链表中节点的顺序与插入顺序一致,适合需要按顺序存储数据的场景。 677 | 2. **逻辑清晰:** 尾插法逻辑上符合自然的插入顺序,适合链表末尾需要不断添加新数据的场景,比如队列。 678 | 3. **代码可扩展性更好:** 修改为尾插法后,可以更方便地拓展链表操作,如同时处理大量数据。 679 | 680 | > ```c 681 | > // 数组容量 682 | > #define MAX_COORDINATES 100 683 | > 684 | > // 定义坐标存储的数组 685 | > uint16_t coordinate_array[MAX_COORDINATES][2]; // 每行保存一个坐标点 (x, y) 686 | > uint16_t coordinate_count = 0; // 当前有效的坐标点数量 687 | > 688 | > /** 689 | > * @brief 向数组中添加一个坐标点 690 | > * @param x 坐标 x 691 | > * @param y 坐标 y 692 | > * @return 1 添加成功,0 添加失败(例如坐标重复或数组已满) 693 | > */ 694 | > uint8_t coordinate_add(uint16_t x, uint16_t y) 695 | > { 696 | > // 检查是否已经存在相同的坐标点 697 | > for (uint16_t i = 0; i < coordinate_count; i++) 698 | > { 699 | > if (coordinate_array[i][0] == x && coordinate_array[i][1] == y) 700 | > { 701 | > return 0; // 坐标重复,添加失败 702 | > } 703 | > } 704 | > 705 | > // 检查是否超出容量 706 | > if (coordinate_count >= MAX_COORDINATES) 707 | > { 708 | > return 0; // 数组已满,添加失败 709 | > } 710 | > 711 | > // 添加新坐标点到数组末尾 712 | > coordinate_array[coordinate_count][0] = x; 713 | > coordinate_array[coordinate_count][1] = y; 714 | > coordinate_count++; // 更新当前坐标数量 715 | > return 1; // 添加成功 716 | > } 717 | > 718 | > /** 719 | > * @brief 从数组中删除一个坐标点 720 | > * @param x 坐标 x 721 | > * @param y 坐标 y 722 | > * @return 1 删除成功,0 删除失败(例如坐标点不存在) 723 | > */ 724 | > uint8_t coordinate_remove(uint16_t x, uint16_t y) 725 | > { 726 | > for (uint16_t i = 0; i < coordinate_count; i++) 727 | > { 728 | > if (coordinate_array[i][0] == x && coordinate_array[i][1] == y) 729 | > { 730 | > // 找到目标坐标点,将其后面的所有元素前移 731 | > for (uint16_t j = i; j < coordinate_count - 1; j++) 732 | > { 733 | > coordinate_array[j][0] = coordinate_array[j + 1][0]; 734 | > coordinate_array[j][1] = coordinate_array[j + 1][1]; 735 | > } 736 | > coordinate_count--; // 更新坐标数量 737 | > return 1; // 删除成功 738 | > } 739 | > } 740 | > return 0; // 未找到目标坐标点,删除失败 741 | > } 742 | > 743 | > /** 744 | > * @brief 打印数组中所有的坐标点 745 | > */ 746 | > void coordinate_print() 747 | > { 748 | > if (coordinate_count == 0) 749 | > { 750 | > printf("No coordinates available.\n"); 751 | > return; 752 | > } 753 | > 754 | > printf("Coordinates:\n"); 755 | > for (uint16_t i = 0; i < coordinate_count; i++) 756 | > { 757 | > printf("(%d, %d)\n", coordinate_array[i][0], coordinate_array[i][1]); 758 | > } 759 | > } 760 | > 761 | > /** 762 | > * @brief 解析 UART 输入命令并执行相应操作 763 | > * @param cmd 接收到的命令字符串 764 | > */ 765 | > void coordinate_parse_command(const char *cmd) 766 | > { 767 | > if (strstr(cmd, "(") != NULL && strstr(cmd, ")") != NULL) 768 | > { 769 | > // 添加坐标点的命令,格式:"(x1,y1,x2,y2,...)" 770 | > uint16_t length = strstr(cmd, ")") - strstr(cmd, "(") - 1; 771 | > char *data = (char *)malloc(length + 1); 772 | > strncpy(data, strstr(cmd, "(") + 1, length); 773 | > data[length] = '\0'; 774 | > 775 | > char *token = strtok(data, ","); 776 | > uint16_t coordinates[MAX_COORDINATES * 2]; 777 | > uint16_t index = 0; 778 | > 779 | > while (token != NULL) 780 | > { 781 | > coordinates[index++] = atoi(token); 782 | > token = strtok(NULL, ","); 783 | > } 784 | > 785 | > if (index % 2 == 0) // 检查坐标是否成对 786 | > { 787 | > for (uint16_t i = 0; i < index; i += 2) 788 | > { 789 | > if (!coordinate_add(coordinates[i], coordinates[i + 1])) 790 | > { 791 | > printf("Failed to add coordinate (%d, %d)\n", coordinates[i], coordinates[i + 1]); 792 | > } 793 | > } 794 | > } 795 | > else 796 | > { 797 | > printf("Error: Incomplete coordinate pair.\n"); 798 | > } 799 | > 800 | > free(data); 801 | > } 802 | > else if (strstr(cmd, "{") != NULL && strstr(cmd, "}") != NULL) 803 | > { 804 | > // 删除坐标点的命令,格式:"{x,y}" 805 | > uint16_t length = strstr(cmd, "}") - strstr(cmd, "{") - 1; 806 | > char data[16]; 807 | > strncpy(data, strstr(cmd, "{") + 1, length); 808 | > data[length] = '\0'; 809 | > 810 | > uint16_t x = atoi(strtok(data, ",")); 811 | > uint16_t y = atoi(strtok(NULL, ",")); 812 | > 813 | > if (!coordinate_remove(x, y)) 814 | > { 815 | > printf("Failed to remove coordinate (%d, %d)\n", x, y); 816 | > } 817 | > } 818 | > else if (strcmp(cmd, "print_coords") == 0) 819 | > { 820 | > // 打印坐标点命令 821 | > coordinate_print(); 822 | > } 823 | > else 824 | > { 825 | > printf("Unknown command: %s\n", cmd); 826 | > } 827 | > } 828 | > ``` 829 | 830 | > ### **什么是队列 (Queue)** 831 | > 832 | > 队列是一种**抽象数据结构**,它遵循**先进先出(FIFO, First In First Out)**原则。这意味着: 833 | > 834 | > - **插入(enqueue):** 数据只能从队尾插入。 835 | > - **删除(dequeue):** 数据只能从队首删除。 836 | > - 队列是一种线性数据结构,常用于需要按顺序处理数据的场景。 837 | > 838 | > ------ 839 | > 840 | > ### **队列的主要特性** 841 | > 842 | > 1. **FIFO(先进先出):** 队列中第一个进入的元素是第一个被删除的元素。 843 | > 2. **受限访问:** 队列中只能访问队首和队尾,不能随机访问中间数据。 844 | > 3. 典型操作: 845 | > - **`enqueue`:** 将元素插入到队尾。 846 | > - **`dequeue`:** 从队首移除元素。 847 | > - **`peek`:** 查看队首元素,但不删除。 848 | > 849 | > ------ 850 | > 851 | > ### **链表队列 (Linked List Queue)** 852 | > 853 | > **定义:** 基于链表实现的队列,其中队列的每个元素是一个链表节点,每个节点包含数据和一个指向下一个节点的指针。 854 | > 855 | > #### **链表队列的结构:** 856 | > 857 | > - 使用两个指针: 858 | > - **`front`(队首):** 指向链表的头节点,用于删除操作。 859 | > - **`rear`(队尾):** 指向链表的尾节点,用于插入操作。 860 | > - 每个节点包含: 861 | > - **数据(data):** 存储队列元素。 862 | > - **指向下一个节点的指针(next):** 链接到下一个节点。 863 | > 864 | > #### **实现方式:** 865 | > 866 | > ```c 867 | > typedef struct Node 868 | > { 869 | > int data; 870 | > struct Node *next; 871 | > } Node; 872 | > 873 | > typedef struct Queue 874 | > { 875 | > Node *front; // 队首指针 876 | > Node *rear; // 队尾指针 877 | > int size; // 队列中元素数量 878 | > } Queue; 879 | > 880 | > // 初始化队列 881 | > void initQueue(Queue *q) 882 | > { 883 | > q->front = NULL; 884 | > q->rear = NULL; 885 | > q->size = 0; 886 | > } 887 | > 888 | > // 插入元素到队尾 889 | > void enqueue(Queue *q, int value) 890 | > { 891 | > Node *newNode = (Node *)malloc(sizeof(Node)); 892 | > newNode->data = value; 893 | > newNode->next = NULL; 894 | > 895 | > if (q->rear == NULL) 896 | > { // 队列为空 897 | > q->front = newNode; 898 | > q->rear = newNode; 899 | > } 900 | > else 901 | > { 902 | > q->rear->next = newNode; 903 | > q->rear = newNode; 904 | > } 905 | > q->size++; 906 | > } 907 | > 908 | > // 删除队首元素 909 | > int dequeue(Queue *q) 910 | > { 911 | > if (q->front == NULL) 912 | > { 913 | > printf("Queue is empty.\n"); 914 | > return -1; 915 | > } 916 | > int value = q->front->data; 917 | > Node *temp = q->front; 918 | > q->front = q->front->next; 919 | > 920 | > if (q->front == NULL) 921 | > { // 队列为空 922 | > q->rear = NULL; 923 | > } 924 | > 925 | > free(temp); 926 | > q->size--; 927 | > return value; 928 | > } 929 | > ``` 930 | > 931 | > #### **链表队列的优点:** 932 | > 933 | > 1. **动态大小:** 队列大小不受限制(受内存限制),可以动态扩展。 934 | > 2. **插入和删除效率高:** 不需要移动数据,只需调整指针。 935 | > 3. **灵活性:** 更适合频繁插入和删除的场景。 936 | > 937 | > #### **链表队列的缺点:** 938 | > 939 | > 1. **内存开销大:** 每个节点需要额外存储指针。 940 | > 2. **无法随机访问:** 必须从队首依次遍历。 941 | > 942 | > ------ 943 | > 944 | > ### **数组队列 (Array Queue)** 945 | > 946 | > **定义:** 基于数组实现的队列,使用固定大小的数组存储数据,并通过两个指针(`front` 和 `rear`)来管理队列。 947 | > 948 | > #### **数组队列的结构:** 949 | > 950 | > - 使用一个固定大小的数组: 951 | > 952 | > ```c 953 | > int queue[MAX_SIZE]; 954 | > ``` 955 | > 956 | > - 使用两个索引指针: 957 | > 958 | > - **`front`:** 指向队首位置。 959 | > - **`rear`:** 指向队尾位置。 960 | > 961 | > - **环形队列:** 为避免数据搬移,通常实现为循环队列。 962 | > 963 | > #### **实现方式:** 964 | > 965 | > ```c 966 | > #define MAX_SIZE 100 967 | > 968 | > typedef struct ArrayQueue 969 | > { 970 | > int data[MAX_SIZE]; // 数组存储数据 971 | > int front; // 队首索引 972 | > int rear; // 队尾索引 973 | > int size; // 当前队列中的元素数量 974 | > } ArrayQueue; 975 | > 976 | > // 初始化队列 977 | > void initArrayQueue(ArrayQueue *q) 978 | > { 979 | > q->front = 0; 980 | > q->rear = -1; 981 | > q->size = 0; 982 | > } 983 | > 984 | > // 插入元素到队尾 985 | > void enqueue(ArrayQueue *q, int value) 986 | > { 987 | > if (q->size == MAX_SIZE) { // 队列已满 988 | > printf("Queue is full.\n"); 989 | > return; 990 | > } 991 | > q->rear = (q->rear + 1) % MAX_SIZE; 992 | > q->data[q->rear] = value; 993 | > q->size++; 994 | > } 995 | > 996 | > // 删除队首元素 997 | > int dequeue(ArrayQueue *q) 998 | > { 999 | > if (q->size == 0) { // 队列为空 1000 | > printf("Queue is empty.\n"); 1001 | > return -1; 1002 | > } 1003 | > int value = q->data[q->front]; 1004 | > q->front = (q->front + 1) % MAX_SIZE; 1005 | > q->size--; 1006 | > return value; 1007 | > } 1008 | > ``` 1009 | > 1010 | > #### **数组队列的优点:** 1011 | > 1012 | > 1. **实现简单:** 基于数组的队列逻辑更清晰,操作更直接。 1013 | > 2. **高效的随机访问:** 可通过索引访问数组中的元素。 1014 | > 3. **固定内存开销:** 数组内存分配固定,无需动态分配。 1015 | > 1016 | > #### **数组队列的缺点:** 1017 | > 1018 | > 1. **固定大小:** 队列大小受限于数组容量,不支持动态扩展。 1019 | > 2. **数据搬移或浪费:** 如果不使用环形队列,频繁插入和删除会导致队列中的数据需要搬移,或浪费空间。 1020 | > 3. **灵活性差:** 如果数据量变化较大,可能需要动态调整数组大小。 1021 | > 1022 | > ------ 1023 | > 1024 | > ### **队列与链表、数组的区别** 1025 | > 1026 | > | **特性** | **队列** | **链表** | **数组** | 1027 | > | :------------: | :------------------------------------------: | :----------------------------------: | :-------------------------------------------------: | 1028 | > | **结构类型** | 线性结构,FIFO | 线性结构,节点通过指针连接 | 线性结构,元素存储在连续内存中 | 1029 | > | **数据访问** | 只能访问队首或队尾 | 可通过遍历访问任意节点 | 支持随机访问,时间复杂度 O(1) | 1030 | > | **插入和删除** | 只能在队尾插入,队首删除 | 在任意位置插入和删除,但需要调整指针 | 在任意位置插入和删除,需要移动数据,时间复杂度 O(n) | 1031 | > | **动态扩展** | 链表实现可以动态扩展,数组实现需重新分配内存 | 动态扩展 | 不支持动态扩展(静态数组),但动态数组支持 | 1032 | > | **内存使用** | 链表实现需要额外存储指针,数组实现较紧凑 | 每个节点需要额外存储指针 | 需要分配连续内存,大小固定 | 1033 | > | **效率** | 插入和删除操作效率较高 | 插入和删除效率高,尤其是头部和尾部 | 插入和删除效率低,中间操作需要移动数据 | 1034 | > | **适用场景** | 任务调度、数据流处理 | 动态数据管理,频繁插入和删除 | 固定大小、频繁查找、随机访问的场景 | 1035 | > 1036 | > ------ 1037 | > 1038 | > ### **队列实现方案:** 1039 | > 1040 | > #### 1. **队列定义** 1041 | > 1042 | > - 队列的大小是固定的,通过一个数组存储坐标点。 1043 | > - 维护两个指针:`front`(队首)和 `rear`(队尾),用于记录当前队列的首部和尾部位置。 1044 | > 1045 | > #### 2. **操作逻辑** 1046 | > 1047 | > - 添加坐标(enqueue): 1048 | > - 将新坐标点放在队尾位置,更新 `rear`。 1049 | > - 如果队列已满,则拒绝插入。 1050 | > - 删除坐标(dequeue): 1051 | > - 从队首位置移除坐标点,更新 `front`。 1052 | > - 如果队列为空,则拒绝删除。 1053 | > - 打印坐标: 1054 | > - 从 `front` 到 `rear` 遍历队列,打印所有坐标。 1055 | > 1056 | > ------ 1057 | > 1058 | > ```c 1059 | > #include 1060 | > #include 1061 | > #include 1062 | > #include 1063 | > 1064 | > // 队列最大容量 1065 | > #define MAX_QUEUE_SIZE 100 1066 | > 1067 | > // 队列数据结构 1068 | > typedef struct 1069 | > { 1070 | > uint16_t data[MAX_QUEUE_SIZE][2]; // 队列存储的坐标 (x, y) 1071 | > int front; // 队首索引 1072 | > int rear; // 队尾索引 1073 | > int size; // 当前队列中的元素数量 1074 | > } CoordinateQueue; 1075 | > 1076 | > // 初始化队列 1077 | > void queue_init(CoordinateQueue *queue) 1078 | > { 1079 | > queue->front = 0; 1080 | > queue->rear = -1; 1081 | > queue->size = 0; 1082 | > } 1083 | > 1084 | > // 检查队列是否为空 1085 | > int queue_is_empty(CoordinateQueue *queue) 1086 | > { 1087 | > return queue->size == 0; 1088 | > } 1089 | > 1090 | > // 检查队列是否已满 1091 | > int queue_is_full(CoordinateQueue *queue) 1092 | > { 1093 | > return queue->size >= MAX_QUEUE_SIZE; 1094 | > } 1095 | > 1096 | > /** 1097 | > * @brief 向队列中添加一个坐标点 (enqueue) 1098 | > * @param queue 队列指针 1099 | > * @param x 坐标 x 1100 | > * @param y 坐标 y 1101 | > * @return 1 成功添加,0 添加失败(例如队列已满) 1102 | > */ 1103 | > int queue_enqueue(CoordinateQueue *queue, uint16_t x, uint16_t y) 1104 | > { 1105 | > if (queue_is_full(queue)) 1106 | > { 1107 | > printf("Queue is full. Cannot enqueue (%d, %d).\n", x, y); 1108 | > return 0; // 队列已满,添加失败 1109 | > } 1110 | > 1111 | > queue->rear = (queue->rear + 1) % MAX_QUEUE_SIZE; // 更新队尾索引 1112 | > queue->data[queue->rear][0] = x; // 存储坐标 x 1113 | > queue->data[queue->rear][1] = y; // 存储坐标 y 1114 | > queue->size++; // 更新队列大小 1115 | > return 1; // 添加成功 1116 | > } 1117 | > 1118 | > /** 1119 | > * @brief 从队列中移除一个坐标点 (dequeue) 1120 | > * @param queue 队列指针 1121 | > * @param x 返回移除的坐标 x 1122 | > * @param y 返回移除的坐标 y 1123 | > * @return 1 成功移除,0 移除失败(例如队列为空) 1124 | > */ 1125 | > int queue_dequeue(CoordinateQueue *queue, uint16_t *x, uint16_t *y) 1126 | > { 1127 | > if (queue_is_empty(queue)) 1128 | > { 1129 | > printf("Queue is empty. Cannot dequeue.\n"); 1130 | > return 0; // 队列为空,移除失败 1131 | > } 1132 | > 1133 | > *x = queue->data[queue->front][0]; // 获取队首的 x 坐标 1134 | > *y = queue->data[queue->front][1]; // 获取队首的 y 坐标 1135 | > queue->front = (queue->front + 1) % MAX_QUEUE_SIZE; // 更新队首索引 1136 | > queue->size--; // 更新队列大小 1137 | > return 1; // 移除成功 1138 | > } 1139 | > 1140 | > /** 1141 | > * @brief 打印队列中的所有坐标点 1142 | > * @param queue 队列指针 1143 | > */ 1144 | > void queue_print(CoordinateQueue *queue) 1145 | > { 1146 | > if (queue_is_empty(queue)) 1147 | > { 1148 | > printf("Queue is empty.\n"); 1149 | > return; 1150 | > } 1151 | > 1152 | > printf("Queue contents:\n"); 1153 | > int index = queue->front; 1154 | > for (int i = 0; i < queue->size; i++) 1155 | > { 1156 | > printf("(%d, %d)\n", queue->data[index][0], queue->data[index][1]); 1157 | > index = (index + 1) % MAX_QUEUE_SIZE; // 循环处理队列索引 1158 | > } 1159 | > } 1160 | > 1161 | > /** 1162 | > * @brief 解析 UART 输入命令并执行队列操作 1163 | > * @param queue 队列指针 1164 | > * @param cmd 接收到的命令字符串 1165 | > */ 1166 | > void queue_parse_command(CoordinateQueue *queue, const char *cmd) 1167 | > { 1168 | > if (strstr(cmd, "(") != NULL && strstr(cmd, ")") != NULL) 1169 | > { 1170 | > // 添加坐标点的命令,格式:"(x1,y1,x2,y2,...)" 1171 | > uint16_t length = strstr(cmd, ")") - strstr(cmd, "(") - 1; 1172 | > char *data = (char *)malloc(length + 1); 1173 | > strncpy(data, strstr(cmd, "(") + 1, length); 1174 | > data[length] = '\0'; 1175 | > 1176 | > char *token = strtok(data, ","); 1177 | > uint16_t coordinates[MAX_QUEUE_SIZE * 2]; 1178 | > uint16_t index = 0; 1179 | > 1180 | > while (token != NULL) 1181 | > { 1182 | > coordinates[index++] = atoi(token); 1183 | > token = strtok(NULL, ","); 1184 | > } 1185 | > 1186 | > if (index % 2 == 0) // 检查坐标是否成对 1187 | > { 1188 | > for (uint16_t i = 0; i < index; i += 2) 1189 | > { 1190 | > if (!queue_enqueue(queue, coordinates[i], coordinates[i + 1])) 1191 | > { 1192 | > printf("Failed to enqueue coordinate (%d, %d)\n", coordinates[i], coordinates[i + 1]); 1193 | > } 1194 | > } 1195 | > } 1196 | > else 1197 | > { 1198 | > printf("Error: Incomplete coordinate pair.\n"); 1199 | > } 1200 | > 1201 | > free(data); 1202 | > } 1203 | > else if (strcmp(cmd, "dequeue") == 0) 1204 | > { 1205 | > // 删除队首的命令 1206 | > uint16_t x, y; 1207 | > if (queue_dequeue(queue, &x, &y)) 1208 | > { 1209 | > printf("Dequeued coordinate: (%d, %d)\n", x, y); 1210 | > } 1211 | > } 1212 | > else if (strcmp(cmd, "print_queue") == 0) 1213 | > { 1214 | > // 打印队列内容的命令 1215 | > queue_print(queue); 1216 | > } 1217 | > else 1218 | > { 1219 | > printf("Unknown command: %s\n", cmd); 1220 | > } 1221 | > } 1222 | > ``` 1223 | > 1224 | > ------ 1225 | > 1226 | > ### **测试用例** 1227 | > 1228 | > ```c 1229 | > int main() 1230 | > { 1231 | > CoordinateQueue queue; 1232 | > queue_init(&queue); 1233 | > 1234 | > // 测试添加坐标 1235 | > queue_parse_command(&queue, "(1,2,3,4,5,6)"); 1236 | > queue_parse_command(&queue, "(7,8)"); 1237 | > 1238 | > // 打印队列内容 1239 | > queue_parse_command(&queue, "print_queue"); 1240 | > 1241 | > // 测试移除队首坐标 1242 | > queue_parse_command(&queue, "dequeue"); 1243 | > queue_parse_command(&queue, "dequeue"); 1244 | > 1245 | > // 再次打印队列内容 1246 | > queue_parse_command(&queue, "print_queue"); 1247 | > 1248 | > return 0; 1249 | > } 1250 | > ``` 1251 | > 1252 | > ------ 1253 | > 1254 | > ### **运行结果示例** 1255 | > 1256 | > ```plaintext 1257 | > Queue contents: 1258 | > (1, 2) 1259 | > (3, 4) 1260 | > (5, 6) 1261 | > (7, 8) 1262 | > Dequeued coordinate: (1, 2) 1263 | > Dequeued coordinate: (3, 4) 1264 | > Queue contents: 1265 | > (5, 6) 1266 | > (7, 8) 1267 | > ``` 1268 | > 1269 | > ------ 1270 | > 1271 | > ### **实现说明** 1272 | > 1273 | > 1. **队列存储:** 1274 | > - 使用二维数组存储 `(x, y)` 坐标。 1275 | > - 使用 `front` 和 `rear` 指针管理队列的头尾位置。 1276 | > - 队列支持循环索引(`(rear + 1) % MAX_QUEUE_SIZE`),避免数据搬移。 1277 | > 2. **操作函数:** 1278 | > - **`queue_enqueue`:** 添加数据到队尾。 1279 | > - **`queue_dequeue`:** 删除数据从队首。 1280 | > - **`queue_print`:** 遍历打印队列内容。 1281 | > 3. **优点:** 1282 | > - 队列的操作顺序是 **先进先出**,适合管理具有 FIFO 特性的任务。 1283 | > - 使用循环数组避免内存移动,提高效率。 1284 | > 4. **局限:** 1285 | > - 固定大小限制(`MAX_QUEUE_SIZE`)。 1286 | > - 如果队列满了,需要处理数据丢失或扩展。 1287 | > 1288 | > ------ 1289 | > 1290 | > ### **适用场景** 1291 | > 1292 | > - **队列适合场景:** 1293 | > - 坐标数据按顺序处理(如任务队列)。 1294 | > - 要求插入和删除的顺序为先进先出(FIFO)。 1295 | > - **如果需要动态扩展:** 1296 | > - 可以将数组实现替换为动态分配的链表队列。 1297 | 1298 | > 队列数据结构的一个基本特性——它遵循**先进先出(FIFO)**原则,意味着数据按插入顺序被处理,只有队首元素可以被删除,而队尾元素只能插入。 1299 | > 1300 | > ------ 1301 | > 1302 | > ### **队列删除队首的逻辑** 1303 | > 1304 | > 在队列中: 1305 | > 1306 | > - **插入(enqueue):** 只能在队尾插入元素。 1307 | > - **删除(dequeue):** 只能在队首删除元素。 1308 | > 1309 | > 这是队列操作的核心规则,设计为这样是因为队列的作用通常是处理任务、数据流等,按**顺序**依次执行。 1310 | > 1311 | > ------ 1312 | > 1313 | > ### **队首删除的实现逻辑** 1314 | > 1315 | > #### **1. 基于数组实现的 `queue_dequeue`** 1316 | > 1317 | > ```c 1318 | > int queue_dequeue(CoordinateQueue *queue, uint16_t *x, uint16_t *y) 1319 | > { 1320 | > if (queue_is_empty(queue)) 1321 | > { 1322 | > printf("Queue is empty. Cannot dequeue.\n"); 1323 | > return 0; // 队列为空,删除失败 1324 | > } 1325 | > 1326 | > // 取出队首元素 1327 | > *x = queue->data[queue->front][0]; 1328 | > *y = queue->data[queue->front][1]; 1329 | > 1330 | > // 更新队首指针 1331 | > queue->front = (queue->front + 1) % MAX_QUEUE_SIZE; 1332 | > 1333 | > // 减少队列大小 1334 | > queue->size--; 1335 | > 1336 | > return 1; // 删除成功 1337 | > } 1338 | > ``` 1339 | > 1340 | > #### **解释:** 1341 | > 1342 | > 1. **检查队列是否为空:** 1343 | > 1344 | > ```c 1345 | > if (queue_is_empty(queue)) 1346 | > ``` 1347 | > 1348 | > - 如果队列为空,直接返回错误信息,无法删除。 1349 | > 1350 | > 2. **获取队首元素:** 1351 | > 1352 | > ```c 1353 | > *x = queue->data[queue->front][0]; 1354 | > *y = queue->data[queue->front][1]; 1355 | > ``` 1356 | > 1357 | > - 通过 `front` 指针获取队首的 `x` 和 `y` 坐标。 1358 | > 1359 | > 3. **更新队首指针:** 1360 | > 1361 | > ```c 1362 | > queue->front = (queue->front + 1) % MAX_QUEUE_SIZE; 1363 | > ``` 1364 | > 1365 | > - 队首指针向后移动一位,使用循环数组的计算方式确保不会超出数组范围。 1366 | > - 如果队首移动到数组末尾,则自动循环回到起点。 1367 | > 1368 | > 4. **减少队列大小:** 1369 | > 1370 | > ```c 1371 | > queue->size--; 1372 | > ``` 1373 | > 1374 | > ------ 1375 | > 1376 | > #### **2. 基于链表实现的 `queue_dequeue`** 1377 | > 1378 | > ```c 1379 | > int queue_dequeue_linked(CoordinateNode **front, CoordinateNode **rear, uint16_t *x, uint16_t *y) 1380 | > { 1381 | > if (*front == NULL) 1382 | > { 1383 | > printf("Queue is empty. Cannot dequeue.\n"); 1384 | > return 0; // 队列为空,删除失败 1385 | > } 1386 | > 1387 | > CoordinateNode *temp = *front; 1388 | > 1389 | > // 取出队首元素 1390 | > *x = temp->x; 1391 | > *y = temp->y; 1392 | > 1393 | > // 更新队首指针 1394 | > *front = temp->next; 1395 | > 1396 | > // 如果队列中只有一个元素,更新队尾指针为 NULL 1397 | > if (*front == NULL) 1398 | > { 1399 | > *rear = NULL; 1400 | > } 1401 | > 1402 | > // 释放队首节点的内存 1403 | > free(temp); 1404 | > 1405 | > return 1; // 删除成功 1406 | > } 1407 | > ``` 1408 | > 1409 | > #### **解释:** 1410 | > 1411 | > 1. **检查队列是否为空:** 1412 | > 1413 | > ```c 1414 | > if (*front == NULL) 1415 | > ``` 1416 | > 1417 | > - 如果队列为空,直接返回错误信息。 1418 | > 1419 | > 2. **取出队首元素:** 1420 | > 1421 | > ```c 1422 | > *x = temp->x; 1423 | > *y = temp->y; 1424 | > ``` 1425 | > 1426 | > - 通过 `front` 指针访问队首节点,并获取其 `x` 和 `y` 坐标。 1427 | > 1428 | > 3. **更新队首指针:** 1429 | > 1430 | > ```c 1431 | > *front = temp->next; 1432 | > ``` 1433 | > 1434 | > - 将队首指针移动到下一个节点。 1435 | > 1436 | > 4. **处理特殊情况:** 1437 | > 1438 | > ```c 1439 | > if (*front == NULL) 1440 | > { 1441 | > *rear = NULL; 1442 | > } 1443 | > ``` 1444 | > 1445 | > - 如果删除后队列变为空,更新队尾指针为 `NULL`。 1446 | > 1447 | > 5. **释放内存:** 1448 | > 1449 | > ```c 1450 | > free(temp); 1451 | > ``` 1452 | > 1453 | > ------ 1454 | > 1455 | > ### **为什么只能删除队首** 1456 | > 1457 | > 这是队列设计的核心特性:**先进先出(FIFO)**。 1458 | > 1459 | > - **队首是最早进入队列的元素**,根据 FIFO 的规则,应该最先处理和删除。 1460 | > - **队尾是最新插入的元素**,需要等待队首的数据被删除后才能被处理。 1461 | > 1462 | > 删除中间节点或队尾元素,违背了队列的 FIFO 规则。如果需要删除中间或队尾的元素,则需要用其他数据结构(如双向链表、数组)或者修改队列的规则。 1463 | > 1464 | > ------ 1465 | > 1466 | > ### **如何删除中间节点或队尾元素** 1467 | > 1468 | > 1. **删除中间节点:** 1469 | > - 遍历队列找到目标节点及其前驱节点。 1470 | > - 修改前驱节点的指针(对于链表)或移动数组中的元素(对于数组)。 1471 | > 2. **删除队尾元素:** 1472 | > - 对于链表,需要从头遍历到倒数第二个节点,然后释放最后一个节点。 1473 | > - 对于数组,直接减少队列大小,并更新 `rear` 指针。 1474 | > 1475 | > #### 示例代码(删除队尾元素): 1476 | > 1477 | > ##### **基于数组:** 1478 | > 1479 | > ```c 1480 | > int queue_remove_tail(CoordinateQueue *queue, uint16_t *x, uint16_t *y) 1481 | > { 1482 | > if (queue_is_empty(queue)) 1483 | > { 1484 | > printf("Queue is empty. Cannot remove tail.\n"); 1485 | > return 0; 1486 | > } 1487 | > 1488 | > // 取出队尾元素 1489 | > *x = queue->data[queue->rear][0]; 1490 | > *y = queue->data[queue->rear][1]; 1491 | > 1492 | > // 更新队尾指针 1493 | > queue->rear = (queue->rear - 1 + MAX_QUEUE_SIZE) % MAX_QUEUE_SIZE; 1494 | > 1495 | > // 减少队列大小 1496 | > queue->size--; 1497 | > 1498 | > return 1; // 删除成功 1499 | > } 1500 | > ``` 1501 | > 1502 | > ##### **基于链表:** 1503 | > 1504 | > ```c 1505 | > int queue_remove_tail_linked(CoordinateNode **front, CoordinateNode **rear, uint16_t *x, uint16_t *y) 1506 | > { 1507 | > if (*front == NULL) 1508 | > { 1509 | > printf("Queue is empty. Cannot remove tail.\n"); 1510 | > return 0; 1511 | > } 1512 | > 1513 | > CoordinateNode *current = *front; 1514 | > CoordinateNode *prev = NULL; 1515 | > 1516 | > // 遍历到队尾节点 1517 | > while (current->next != NULL) 1518 | > { 1519 | > prev = current; 1520 | > current = current->next; 1521 | > } 1522 | > 1523 | > // 取出队尾元素 1524 | > *x = current->x; 1525 | > *y = current->y; 1526 | > 1527 | > if (prev == NULL) 1528 | > { 1529 | > // 队列只有一个节点 1530 | > *front = NULL; 1531 | > *rear = NULL; 1532 | > } 1533 | > else 1534 | > { 1535 | > // 删除队尾节点 1536 | > prev->next = NULL; 1537 | > *rear = prev; 1538 | > } 1539 | > 1540 | > free(current); 1541 | > return 1; 1542 | > } 1543 | > ``` 1544 | 1545 | > **方案1:当前的单位向量方案** 1546 | > ```c 1547 | > float dx = target_x - pos_x; 1548 | > float dy = target_y - pos_y; 1549 | > float distance = sqrtf(dx * dx + dy * dy); 1550 | > float unit_dx = dx / distance; 1551 | > float unit_dy = dy / distance; 1552 | > step_distance = speed * dt; 1553 | > pos_x += unit_dx * step_distance; 1554 | > pos_y += unit_dy * step_distance; 1555 | > ``` 1556 | > 1557 | > **方案2:三角函数分解方案** 1558 | > ```c 1559 | > float dx = target_x - pos_x; 1560 | > float dy = target_y - pos_y; 1561 | > float angle = atan2(dy, dx); // 计算角度 1562 | > float vx = speed * cos(angle); // x方向速度分量 1563 | > float vy = speed * sin(angle); // y方向速度分量 1564 | > pos_x += vx * dt; 1565 | > pos_y += vy * dt; 1566 | > ``` 1567 | > 1568 | > 比较分析: 1569 | > 1570 | > 1. **计算复杂度** 1571 | > - 方案1:一次开方,两次除法 1572 | > - 方案2:一次反正切,两次三角函数(更复杂) 1573 | > 1574 | > 2. **数值精度** 1575 | > - 方案1:直接使用比例关系,精度损失较小 1576 | > - 方案2:涉及三角函数计算,可能有更多的精度损失 1577 | > 1578 | > 3. **运算效率** 1579 | > - 方案1:基本算术运算,效率高 1580 | > - 方案2:三角函数运算,计算量大 1581 | > 1582 | > 4. **代码可读性** 1583 | > - 方案1:向量概念直观,易理解 1584 | > - 方案2:角度和三角函数概念,较为复杂 1585 | > 1586 | > 5. **实际应用** 1587 | > - 方案1: 1588 | > * 直接计算移动距离 1589 | > * 运动更平滑 1590 | > * 不需要考虑角度边界问题 1591 | > 1592 | > - 方案2: 1593 | > * 需要处理角度边界情况(如0°和360°) 1594 | > * 可能在某些角度出现精度问题 1595 | > * 三角函数计算可能导致微小抖动 1596 | > 1597 | > 6. **在单片机上的表现** 1598 | > - 方案1: 1599 | > * 运算简单,适合单片机 1600 | > * 内存占用小 1601 | > * 执行时间稳定 1602 | > - 方案2: 1603 | > * 三角函数库占用更多空间 1604 | > * 计算时间可能不稳定 1605 | > * 浮点运算更多 1606 | > 1607 | 1608 | > 1. **数值溢出问题**: 1609 | > ```c 1610 | > // 方案2(三角函数) 1611 | > float angle = atan2(dy, dx); 1612 | > // 当dx和dy都非常大时,比如: 1613 | > dx = 999; 1614 | > dy = 999; 1615 | > // 三角函数计算可能会溢出或失去精度 1616 | > ``` 1617 | > 1618 | > 2. **死区问题**: 1619 | > ```c 1620 | > // 方案2在接近目标点时 1621 | > float vx = speed * cos(angle); 1622 | > float vy = speed * sin(angle); 1623 | > // 当角度接近45度时,两个方向的速度几乎相等 1624 | > // 可能导致一个轴先到达目标,另一个轴还在运动 1625 | > // 造成阶梯状轨迹 1626 | > ``` 1627 | > 1628 | > 3. **计算资源消耗**: 1629 | > ```c 1630 | > // 方案2每次都需要: 1631 | > atan2(dy, dx); // 反三角函数计算 1632 | > cos(angle); // 余弦计算 1633 | > sin(angle); // 正弦计算 1634 | > 1635 | > // 而当前方案只需要: 1636 | > dx/distance; // 一次除法 1637 | > dy/distance; // 一次除法 1638 | > ``` 1639 | > 1640 | > 4. **速度不均匀问题**: 1641 | > ```c 1642 | > // 方案2在转向时 1643 | > float old_angle = atan2(dy1, dx1); 1644 | > float new_angle = atan2(dy2, dx2); 1645 | > // 角度变化时,分解后的速度会突变 1646 | > // 导致运动不平滑 1647 | > ``` 1648 | > 1649 | > 5. **累积误差问题**: 1650 | > ```c 1651 | > // 假设路径:(0,0) -> (100,100) -> (200,0) 1652 | > 1653 | > // 方案2(三角函数) 1654 | > // 第一段: 1655 | > angle1 = atan2(100, 100); // 45度 1656 | > vx1 = speed * cos(45°); 1657 | > vy1 = speed * sin(45°); 1658 | > // 每次计算都有小数点误差 1659 | > 1660 | > // 第二段: 1661 | > angle2 = atan2(-100, 100); // -45度 1662 | > vx2 = speed * cos(-45°); 1663 | > vy2 = speed * sin(-45°); 1664 | > // 误差继续累积 1665 | > 1666 | > // 而当前方案(单位向量) 1667 | > // 第一段: 1668 | > distance1 = sqrt(100*100 + 100*100); 1669 | > unit_dx1 = 100/distance1; // 精确的0.707... 1670 | > unit_dy1 = 100/distance1; 1671 | > 1672 | > // 第二段: 1673 | > distance2 = sqrt(100*100 + 100*100); 1674 | > unit_dx2 = 100/distance2; 1675 | > unit_dy2 = -100/distance2; 1676 | > // 每段都是独立计算,误差不累积 1677 | > ``` 1678 | > 1679 | > 6. **特殊情况处理**: 1680 | > ```c 1681 | > // 方案2在处理以下情况时需要特殊处理: 1682 | > - 原地转向 1683 | > - 零速度 1684 | > - 角度突变 1685 | > - 方向反转 1686 | > 1687 | > // 而单位向量方案不需要特殊处理这些情况 1688 | > ``` 1689 | > 1690 | > 7. **实时性问题**: 1691 | > ```c 1692 | > // 在单片机这样的实时系统中 1693 | > // 三角函数计算耗时不确定 1694 | > // 可能影响控制周期的稳定性 1695 | > // 而单位向量方法计算时间稳定 1696 | > ``` 1697 | > 1698 | > 这些问题在实际工程应用中都很重要,特别是在需要高精度、高可靠性的运动控制系统中。单位向量方法通过简单的数学运算就能避免这些问题,这也是它被广泛使用的原因。 1699 | 1700 | > 以(0,0)到(100,200)的运动为例: 1701 | > 1702 | > **方案2(三角函数分解)**: 1703 | > ```c 1704 | > // 起点(0,0),终点(100,200) 1705 | > dx = 100; 1706 | > dy = 200; 1707 | > angle = atan2(dy, dx); // 约63.43度 1708 | > 1709 | > // 速度分解 1710 | > speed = 10; // 假设速度10cm/s 1711 | > vx = speed * cos(63.43°); // 约4.47 cm/s 1712 | > vy = speed * sin(63.43°); // 约8.94 cm/s 1713 | > 1714 | > // 假设dt = 0.1s,那么每次更新: 1715 | > x += 4.47 * 0.1; // x每次增加约0.447cm 1716 | > y += 8.94 * 0.1; // y每次增加约0.894cm 1717 | > ``` 1718 | > 1719 | > 问题出现: 1720 | > 1. x轴和y轴的速度比约为1:2 1721 | > 2. 当x接近目标(比如x=99.8)时: 1722 | > - x还需要移动0.2cm 1723 | > - y还需要移动0.4cm 1724 | > 3. 由于位置精度限制(取整),x可能先到达目标 1725 | > 4. 这时运动变成纯y轴运动,造成轨迹拐角 1726 | > 1727 | > 实际轨迹可能变成: 1728 | > ``` 1729 | > 目标点(100,200) 1730 | > | 1731 | > | <-- 纯y轴运动(拐角) 1732 | > | 1733 | > / 1734 | > / 1735 | > / <-- 开始时的斜线运动 1736 | > / 1737 | > 起点(0,0) 1738 | > ``` 1739 | > 1740 | > **当前方案(单位向量)**: 1741 | > ```c 1742 | > // 起点(0,0),终点(100,200) 1743 | > dx = 100; 1744 | > dy = 200; 1745 | > distance = sqrt(100*100 + 200*200); // 约223.61 1746 | > 1747 | > // 计算单位向量 1748 | > unit_dx = 100/223.61; // 约0.447 1749 | > unit_dy = 200/223.61; // 约0.894 1750 | > 1751 | > // 每次更新(speed=10, dt=0.1): 1752 | > step = speed * dt; // 1cm 1753 | > x += 0.447 * 1; // x每次增加0.447cm 1754 | > y += 0.894 * 1; // y每次增加0.894cm 1755 | > ``` 1756 | > 1757 | > 优势: 1758 | > 1. x和y的运动比例始终保持100:200 1759 | > 2. 即使接近目标点,运动方向也不会改变 1760 | > 3. 轨迹始终保持直线 1761 | > 4. 不会出现拐角 1762 | > 1763 | > 实际轨迹: 1764 | > ``` 1765 | > 目标点(100,200) 1766 | > / 1767 | > / 1768 | > / <-- 完美的直线运动 1769 | > / 1770 | > / 1771 | > 起点(0,0) 1772 | > ``` 1773 | > 1774 | > 这就是为什么单位向量方案能避免死区问题: 1775 | > 1. 保持运动比例恒定 1776 | > 2. 不依赖于角度计算 1777 | > 3. 不会出现轴向运动分离 1778 | > 4. 轨迹更加平滑准确 1779 | > 1780 | > 在实际应用中,这种平滑的直线运动对于: 1781 | > - 机械系统的稳定性 1782 | > - 电机的使用寿命 1783 | > - 定位精度 1784 | 1785 | -------------------------------------------------------------------------------- /04_第十五届/模拟题/ADC采集控制系统模拟题/ADC采集控制系统.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/模拟题/ADC采集控制系统模拟题/ADC采集控制系统.pdf -------------------------------------------------------------------------------- /04_第十五届/模拟题/ADC采集控制系统模拟题/ADC采集控制系统参考代码.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/模拟题/ADC采集控制系统模拟题/ADC采集控制系统参考代码.zip -------------------------------------------------------------------------------- /04_第十五届/模拟题/第十六届模拟二/第十六届模拟二参考代码.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/模拟题/第十六届模拟二/第十六届模拟二参考代码.zip -------------------------------------------------------------------------------- /04_第十五届/模拟题/第十六届模拟二/第十六届模拟二客观题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/模拟题/第十六届模拟二/第十六届模拟二客观题.pdf -------------------------------------------------------------------------------- /04_第十五届/省赛/15届省赛客观题赛题一.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/省赛/15届省赛客观题赛题一.pdf -------------------------------------------------------------------------------- /04_第十五届/省赛/15届省赛程序设计题赛题一.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/省赛/15届省赛程序设计题赛题一.pdf -------------------------------------------------------------------------------- /04_第十五届/省赛/第十五届省赛真题参考代码.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsecss/lq-embedded-solutions/bdae3f0a00dcf41e787a1e870d9630df24df01c0/04_第十五届/省赛/第十五届省赛真题参考代码.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 蓝桥杯嵌入式真题参考代码 2 | 3 | ## Introduction 4 | 5 | 本仓库旨在提供蓝桥杯嵌入式历届真题和部分模拟题的参考代码,涵盖省赛和国赛以及模拟题,帮助学生和爱好者学习嵌入式编程并备战比赛。这些代码按年份和题目编号组织,采用的是官方的 **CT117E-M4** 嵌入式平台,该平台采用的主控芯片是 **STM32G431RBT6**,同时采用 **HAL 库** 进行代码的编写,开发平台采用的是 **STM32CubeMX+MDK**。目标是为用户提供实用的代码示例,提升编程能力并加深对蓝桥杯嵌入式竞赛的理解。 6 | 7 | >如果需要相关的工程模版和参考资料,可前往 [蓝桥杯嵌入式比赛模板](https://github.com/rsecss/lq-hal) 下载查看 8 | 9 | ## Directory structure 10 | 11 | 为了方便用户快速找到所需代码,本项目按年份和题目编号进行分类。以下是目录结构的示例: 12 | 13 | ```plaintxt 14 | |-- 第十二届(2021)/ 15 | |-- 省赛(已更新) 16 | |-- 国赛 17 | |-- 第十三届(2022) 18 | |-- 省赛(已更新) 19 | |-- 国赛 20 | |-- 第十四届(2023)/ 21 | |-- 省赛(已更新) 22 | |-- 国赛 23 | |-- 第十五届(2024)/ 24 | |-- 省赛(已更新) 25 | |-- 模拟题/ 26 | |-- ADC 采集控制(已更新) 27 | |-- 第十六届模拟二 (已更新) 28 | |-- 国赛(已更新) 29 | ``` 30 | 31 | ## Usage 32 | 33 | 以下是如何下载和使用这些参考代码的步骤: 34 | 35 | - 访问 GitHub 仓库:[lq-embedded-solutions](https://github.com/rsecss/lq-embedded-solutions) 36 | - 通过 git clone 克隆仓库,或直接下载 ZIP 文件 37 | - 打开参考代码下的 MDK -ARM 文件下的工程文件进行编译调试即可 38 | 39 | ## Contributions 40 | 41 | 欢迎社区贡献新代码或改进现有代码。具体步骤如下: 42 | 43 | 1. 新增题目 44 | - 在对应年份下创建新题目目录,保持现有结构 45 | - 提供完整的源代码和使用说明 46 | 2. 改进现有代码 47 | - 通过 PR 提交代码优化或错误修复 48 | 3. 贡献指南 49 | - 代码需有清晰的注释,遵循项目编码规范 50 | - 在提交 PR 时,需说明更改内容和目的 51 | 4. 如有疑问或建议,请通过以下方式联系 52 | - **邮箱**:[FengYu](mailto:rsecss@outlook.com) 53 | - **Issues**:在仓库中提交 Issue 以寻求帮助或提出建议。 54 | 55 | ## Disclaimer 56 | 57 | - 本项目所有资源仅限学习和参考使用,未经许可不得用于商业目的。 58 | - 本项目针对特定蓝桥杯题目设计,未必适用于其他场景,使用前请核对题目要求。 59 | - 作者 **不提供任何保证**,请自行评估内容的适用性。 60 | - 继续使用本项目,即表明你已同意本免责声明 61 | --------------------------------------------------------------------------------