ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

嵌入式自学路线:MCU、RTOS、Linux驱动与项目实战

嵌入式自学路线:MCU、RTOS、Linux驱动与项目实战 1. 自学嵌入式这条路上倒下的人大多死在同一个地方先说一句可能不太讨喜的话自学嵌入式这件事卡住绝大多数人的从来不是智商也不是数学而是节奏和反馈。你打开任何一个视频合集前二十集看着都很爽讲指针、讲寄存器、讲点灯跟着敲也能跑通。但到了第三十集左右代码量突然上来工程结构变得复杂你发现自己再也无法照着敲一遍就懂于是开始收藏、开始跳集、开始换教程。收藏夹越来越厚手上的板子越来越凉。我带过几个想转行的朋友也一起做过几个外包项目观察下来的规律非常一致学不会的人不是学得慢而是从来没让代码给过他正反馈。他们花了大量时间在看视频、抄笔记、整理思维导图唯独没有花时间在自己从零写一个能跑起来的小东西上。而学得会的人往往起点并不高甚至跨专业但他们会反复做一件很笨的事——把一个功能从寄存器级别啃通然后不断加需求让项目长大。所以这篇内容我想干的事很具体把从零基础到能接活、能面试、能独立带一块板子做产品这条路按我能验证的顺序拆开告诉你每个阶段该做什么、不该做什么、做到什么程度算过。它不承诺看完就会因为那是不可能的但它能让你少走至少半年的弯路把时间花在真正产生复利的地方。先给三个我见过最多的阵亡姿势你可以对号入座板子买错。一上来就买一块复杂的高端开发板资料全是英文例程跑不通一周后失去兴趣。或者反过来买一块只有几块钱的单片机学完点灯就没事可做了。路线抄别人的。看到别人说先学 Linux 再学驱动你就一头扎进内核源码看到另一篇说先啃 RTOS你又换方向。半年下来每一块都只学了个开头。只编译不调试。代码跑不起来就到处问问不到就弃坑。真正拉开差距的能力是定位问题而不是写出漂亮代码。1.1 判断自己是不是在假学习的三个信号我给你三个自检项中两条以上说明你当前的学习方式需要调整你能讲清楚昨天写的那段代码编译之后内存里发生了什么吗比如一个static变量放在哪个段一个数组越界为什么有时崩有时不崩。讲不清说明你还在抄。遇到新器件你能在不看示例的情况下从手册里找到寄存器定义并写出初始化代码吗如果每次都必须找到现成例程才能动说明你依赖的是模仿而不是方法。你的项目是功能叠加还是需求驱动前者是把教程里的例子一个个抄下来拼在一起后者是你有了一个新需求然后去查手册、查协议、查资料把它实现。我自己当年的第一个项目是做一个带屏幕的小温控器。前三天我一直在纠结用哪个 GUI 库后来发现真正卡住我的是 I2C 读传感器的时序——从机地址算错了一位读出来永远是 0xFF。那一刻我才明白嵌入式的时间大部分花在硬件和软件的边界上而不是花在写业务逻辑上。这个认知是我整个学习过程的转折点。2. 一条能真正走通的阶段路线以及每个阶段的验收标准我不太喜欢学习路线图这种说法因为它容易让人产生走完就毕业的错觉。我更愿意把它描述成四到五个有明确交付物的阶段每个阶段你必须产出一样东西产出不了就不往下走。下面这张表是我认为最省时间的顺序你可以根据自己的目标偏单片机还是偏 Linux做裁剪但不建议跳过。阶段核心内容建议投入验收标准做不到就别推进零C 语言 基础硬件常识1-2 个月能手写位操作、链表、环形缓冲区能看懂原理图上的上拉电阻作用一MCU 裸机开发2-4 个月不依赖例程独立写出定时器 中断 串口收发 ADC 采集二RTOS 与工程化1-2 个月能用任务/信号量/消息队列重构裸机项目并解释清优先级反转三Linux 应用层2-3 个月能交叉编译、能写多进程/多线程程序、能用 socket 与设备通信四驱动 设备树 系统裁剪3-6 个月能写一个字符设备驱动能改设备树让板子认识新外设能裁剪出一个能启动的最小系统五方向分叉长期端侧 AI 部署 / 内核与性能优化 / 系统集成任选一条深入2.1 阶段零C 语言不是学过而是用得住很多人对 C 的掌握停留在语法层面能写循环、能定义结构体但一问到volatile的意义、结构体内存对齐、函数指针数组、内存四区就模糊了。嵌入式对 C 的要求和写后端不一样你要能精确预测每一行代码在内存和寄存器上的效果。必须掌握的几块我列一下缺哪补哪指针与二级指针、函数指针。回调机制、驱动表的本质都是函数指针。位运算。|置位、 ~清零、^取反是操作寄存器的基础动作。volatile、const、static、extern的准确语义。尤其是volatile在中断共享变量和硬件寄存器场景下不加就是玄学 bug 的来源。结构体对齐与#pragma pack。协议解析、跨平台通信里必须用。手写环形缓冲区FIFO。串口接收、音频采集到处都在用自己写一遍胜过看十遍。提示C 语言不要学完再动手正确姿势是先用 MCU 写起来遇到不懂的语法回头查。脱离硬件场景学 C效率极低。2.2 阶段一MCU 裸机从寄存器到库顺序不能反这个阶段最容易走偏。我的建议是先寄存器后库但不要死磕寄存器。第一步找一颗资料齐全的主流 MCU用寄存器方式点亮一个 LED。注意这里有三个新手必踩的坑时钟没使能、GPIO 模式配错、LED 是低电平点亮。我第一块板子点灯点了三天最后发现是共阳极接法输出高电平反而灭。第二步把点灯扩展成完整的输入输出闭环按键中断控制 LED用定时器实现呼吸灯PWM用串口把状态打印出来。这三件事做完你对 MCU 的运行机制就有了骨架级的理解。第三步再转到标准库或 HAL 库。这时候你再用库函数会有一种我知道它底下干什么的踏实感而不是把它当黑盒。后面遇到库不支持的外设你也能自己查手册补上。第四步把常用外设全部过一遍并且每个外设都配一个最小可运行例程定时器定时、PWM、输入捕获中断系统外部中断、中断优先级分组通信接口UART、SPI、I2C模拟接口ADC 采集、DAC 输出、内部温度传感器存储Flash 读写、EEPROM 模拟看门狗与低功耗模式独立看门狗、待机唤醒RTC实时时钟与备份域这里我要强调一点不要为了过完外设而过外设。每学一个外设就把它接到你的小项目里。比如学了 ADC就把电位器接上去做成旋钮调光学了 SPI就驱动一块 OLED 屏显示传感器数据。有载体知识才留得住。2.3 阶段二RTOS 不是高级玩具是工程必需品裸机写多了你会遇到一个绕不开的问题多个任务抢一个资源谁来保证实时性这时候状态机写法开始变得臃肿delay阻塞主循环导致按键不灵敏你自然会想要 RTOS。学习 RTOS 我推荐一个顺序先理解任务切换到底切了什么栈、寄存器上下文再学 API。必学的几个概念任务与优先级、时间片调度信号量、互斥锁、事件标志组消息队列、邮箱优先级反转及其解决方式优先级继承内存管理方式静态分配优先动态分配要谨慎验收标准很简单把你阶段一做的裸机项目用 RTOS 重写一遍并且能说清每个任务的优先级是怎么定的、为什么这么定。我在做环境采集设备时把采集、显示、通信拆成三个任务采集任务优先级最高、周期最短显示任务用队列接收数据避免阻塞通信任务放在最低优先级且做超时重传。重构之后代码量少了三分之一维护性好了不止一倍。2.4 阶段三到四Linux 应用、驱动、设备树、系统裁剪如果你的目标是嵌入式 Linux 岗位这两块是主战场。我的经验是应用层先走通再下沉到驱动因为应用层能立刻看到效果反馈快。应用层要掌握的清单交叉编译工具链的使用理解sysroot、动态库与静态库的区别文件 IO、多线程、进程间通信管道、共享内存、消息队列、信号网络编程基础TCP/UDP、socket 阻塞与非阻塞、select/poll/epoll构建系统Makefile 手写一遍然后转 CMake驱动层则是另一个世界。建议从最简单的字符设备驱动开始一步步走到平台设备驱动/* 一个最小字符设备驱动的骨架重点是理解 file_operations 的注册过程 */ #include linux/module.h #include linux/fs.h #include linux/cdev.h static dev_t devno; static struct cdev my_cdev; static int my_open(struct inode *inode, struct file *filp) { return 0; } static int my_release(struct inode *inode, struct file *filp) { return 0; } static ssize_t my_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { char kbuf[] hello from kernel\n; if (*off 0) return 0; /* 简单处理读到尾返回 0 */ if (copy_to_user(buf, kbuf, sizeof(kbuf))) return -EFAULT; *off sizeof(kbuf); return sizeof(kbuf); } static const struct file_operations my_fops { .owner THIS_MODULE, .open my_open, .release my_release, .read my_read, };写完之后一定要自己加载、mknod、cat、卸载把完整链路跑一遍。接下来就是设备树/* 以 I2C 从设备为例关键是 reg 地址与驱动里的 of_match_table 必须对上 */ i2c1 { status okay; clock-frequency 100000; mysensor: mysensor48 { compatible vendor,mysensor; reg 0x48; interrupt-parent gpio1; interrupts 5 2; /* 引脚编号 触发类型 */ }; };设备树这块最大的坑是驱动 probe 不起来的时候90% 的问题出在compatible字符串不匹配、reg地址错、或者总线节点没使能。排查方法后文会详细讲。系统裁剪是另一个高频考点。用构建工具从零配一份最小根文件系统把不需要的驱动和组件裁掉让系统从能启动变成刚好够用。这个过程会逼着你去理解启动流程BootROM → 引导程序 → 内核 → 根文件系统 → 应用。走通一次你对整个系统的理解会上一个台阶。3. 工具链与开发环境别把两周时间花在折腾编辑器上我见过太多人卡在第一步装环境。装系统、配插件、编译工具链报错、串口驱动不认、烧录器连接失败……折腾一周热情耗完。这里给你一份够用就好的配置原则是先跑通再折腾。3.1 编辑器与 IDE怎么选不纠结工具适合场景优点注意点Keil / IAR传统 MCU 项目生态成熟、调试顺手界面老、跨平台支持一般、部分版本容量受限厂商 IDE官方开发板起步例程即开即用工程结构封闭容易形成依赖VSCode 插件全平台通用、写代码爽轻量、插件丰富、可统一管理 C/C 与 Markdown需要自己配调试配置前期有学习成本CLion大型 C/C 工程重构和跳转体验好资源占用高嵌入式调试配置偏繁琐我的实际做法是混用MCU 阶段用厂商 IDE 保证例程能跑同时用 VSCode 写自己的代码到了 Linux 阶段全面切到 VSCode CMake gdb。VSCode 里必装的几类插件C/C 语言支持、调试适配器、代码格式化、Git 集成、Markdown 预览。别装太多插件越多越容易互相打架。3.2 调试手段的优先级排序新手最常见的问题是不会调试只会printf。printf当然有用但在中断里用会打乱时序在驱动里用可能直接把系统搞崩。真正靠谱的调试手段按我使用的频率排序串口日志。第一手信息几乎所有板子都有。注意波特率、时钟源、TTL 电平、共地这几个坑。LED 与 GPIO 打点。最简单粗暴适合看时序和判断代码有没有跑到某一行。硬件调试器。单步、断点、寄存器查看。用调试器看寄存器是理解外设工作原理最快的方式。逻辑分析仪。SPI、I2C、UART 通信不通用它抓波形一眼就能看出是时序问题还是数据问题。示波器。看电源纹波、看信号完整性、看晶振有没有起振。内核日志与跟踪。Linux 下用dmesg、printk分级输出必要时上动态跟踪工具。注意逻辑分析仪是性价比极高的投入。一个几百块的入门款能帮你省下几十个小时的瞎猜。3.3 代码管理这件事越早做越好不管你是一个人还是团队开发从第一天开始就用 Git。哪怕只是本地仓库也能让你在改崩代码时一键回退。我自己的习惯是每跑通一个功能就提交一次提交信息写清楚做了什么、怎么验证的。半年后回头看这些提交记录本身就是你的学习档案。另外强烈建议养成写笔记的习惯但别写抄书式笔记。要写的是这个 bug 的现象是什么、我怀疑的原因是什么、我验证了哪些、最后原因是什么。这种问题-排查-结论格式的笔记在你面试和换项目时价值极高。4. 项目怎么挑、怎么做才真的能写进简历这一节是全文最想说的部分。因为面试官判断你会不会不是看你学了多少而是看你做过什么、踩过什么坑。4.1 什么样的项目算有含金量一个能拿得出手的嵌入式项目我总结出四个特征你可以拿它当筛子有闭环从传感器采集到数据处理再到输出或上报完整走通而不是只做了中间一段。有约束有明确的性能、功耗或成本约束并且你为满足约束做过取舍。比如要在 10ms 内响应所以把重活从主循环挪到 DMA。有难点至少有一个你自己排查了很久才解决的问题并且你能讲清楚排查过程。有数据能给出量化结果。响应时间多少毫秒、功耗从多少降到多少、内存占用降了多少。反面例子我照着教程做了一个 STM32 温度计把温度显示在 OLED 上。——这不是项目这是练习。正面例子我做一个电池供电的环境监测终端采集温湿度与光照本地存储断网时的数据联网后补传整机待机电流控制在两百微安以内连续运行三个月不需要换电池。——这里面有采集、有存储、有通信、有低功耗设计、有异常处理全是可聊的点。4.2 一个可复现的项目拆解低功耗环境监测终端我把这个项目的实现路径拆成几步你可以直接照着做做完就是简历上的一条硬货。第一步确定硬件方案。主控选一颗带低功耗模式的 MCU传感器走 I2C 总线存储用片内 Flash 或外挂存储芯片通信选一种无线方式。要注意的是传感器尽量选带中断输出的这样 MCU 可以长时间睡眠靠传感器中断唤醒。第二步先做功能版再做低功耗版。很多人一上来就追求低功耗结果连功能都跑不通。正确顺序是先用轮询方式把采集、显示、上报全部跑通功能稳定之后再逐个模块替换成中断驱动和睡眠模式。第三步处理异常场景。通信失败怎么办传感器读不到数据怎么办存储写满怎么办断电重启后怎么恢复这些不顺的路径才是项目真正的价值所在。我的做法是给每个模块都加一个状态机把超时、重试、降级都显式写出来。第四步量化与优化。用电流表测不同模式下的功耗用示波器看唤醒时间用日志统计丢包率。有了数据你才知道该优化哪里。低功耗这块有几个具体技巧都是踩坑换来的把未使用的外设时钟全部关掉未使用的 GPIO 配成模拟输入或带上拉输出避免悬空漏电传感器的上拉电阻阻值适当加大延长采集间隔用 RTC 定时唤醒而不是系统节拍通信模块在非发送期间彻底断电。4.3 硬件那一半别丢以 RTC 电路为例很多做软件的同学觉得硬件是别人的事结果调试时吃了大亏。我举个例子把嵌入式 RTC 常见硬件电路这个话题讲一下因为它出现的频率实在太高。RTC 要能断电继续走时核心是三点独立供电、低功耗时钟源、正确的匹配电路。时钟源常用 32.768kHz 晶振因为分频 15 次刚好得到 1Hz。晶振的负载电容必须按规格书配。公式大致是CL (C1 × C2) / (C1 C2) Cstray其中Cstray是 PCB 走线的杂散电容通常取几个皮法。如果你的两个匹配电容都取 12pF实际负载电容会偏大晶振走时就会偏慢。这就是为什么很多板子一个月差好几分钟。供电主电源掉电后由纽扣电池供电通过两个二极管或专用电源切换电路实现。要注意二极管的反向漏电流漏电流大了电池很快就没电了。有些方案会串一个电阻限流阻值太大会影响起振。PCB 布局晶振要靠近芯片走线尽量短且包地远离高频信号和电源走线。这一条我吃过亏——一块板子晶振不起振最后发现是走线太长且从电源下方穿过。调试 RTC 的实用方法用示波器或频率计测晶振引脚是否有稳定振荡读芯片的振荡器停止标志位判断是否起振失败如果走时不准用一个已知准确的时间源做长时间比对算出偏差率再调整匹配电容。4.4 开源项目怎么读才有收获很多人知道要看开源项目但打开一看几千个文件就放弃了。我给你一个可操作的方法不要从入口main开始读。先找它解决的那个核心问题然后顺着配置 → 初始化 → 主循环/任务 → 关键数据结构这条链去读。比如你要看一个 RTOS先读它的任务创建和调度切换你要看一个协议栈先读它的帧结构定义和收发状态机。读的时候带着问题如果让我实现这个功能我会怎么做它为什么不一样每次读完写一段两百字的小结说明它的设计取舍。三个月下来你对架构的感觉会有质的变化。5. 高频卡点的完整排查链路别急着问人这一节我想把几个最常见的卡住场景按实际排查顺序还原给你。重点是学排查思路不是记结论。5.1 板子上电不跑从这五个地方顺着查现象烧录成功但程序不运行串口没有任何输出。排查顺序我实际就是按这个顺序来的电源。先测各路电压是否正常纹波是否过大。我曾经遇到 3.3V 只有 2.7V芯片在低压下工作不稳定。复位。复位引脚电平是否正确有没有被外部电路一直拉低。有些板子的复位电路需要专门的电容配置。时钟。外部晶振是否起振。如果起振失败芯片可能回退到内部时钟导致串口波特率全错、输出全是乱码。这就解释了很多乱码问题。启动模式。启动引脚的电平决定了从哪里取指令。引脚配置错了程序在 Flash 里芯片却去别的地方找自然不跑。烧录地址。链接脚本里的地址和实际烧录地址不一致程序会被写到错误位置。这五步走完绝大多数不跑的问题都能定位。关键是按顺序不要跳步。5.2 驱动加载了但 probe 不执行怎么一层层剥这是 Linux 驱动新手最常见的困惑insmod没报错dmesg也没有 probe 的打印。排查链条我整理成下面这样检查点具体验证方法典型问题内核是否真的加载了模块lsmod看模块在不在依赖模块没先加载compatible是否匹配对比设备树字符串和驱动of_match_table一个字母、一个中划线写错就匹配不上设备树节点是否生效查看内核启动日志里是否解析到该节点覆盖层没编译进去或节点status是disabled总线是否使能确认父节点status okayI2C/SPI 控制器没打开资源是否申请成功看devm_*系列函数返回值引脚被别的驱动占用返回值是否被忽略给每个初始化步骤加日志中间某步失败函数提前返回但没打印错误我实际踩过的一个坑是设备树里compatible我写的是vendor,sensor驱动里写的是vendor,sensor 多了个空格。当时找了两个小时各种检查寄存器、检查时钟都没问题最后是导出实际设备树一对比才发现。所以能用工具验证的就别靠人眼比。另外一个小技巧如果是在内核阶段就出问题把printk的日志级别调低让调试信息全部输出如果是用户态程序用好strace看系统调用卡在哪里。5.3 面试题和真实能力的差距在哪嵌入式面试常问的那些题比如volatile的作用、中断为什么快、优先级反转、内存对齐、进程和线程的区别其实都不是背答案的题而是验证你有没有真正被坑过的题。举个具体的volatile的意义在于告诉编译器这个变量可能被你看不见的力量修改不要优化它。如果你只用过printf打印变量你永远不会理解这句话。但如果你写过一个主循环判断标志位、中断里修改标志位的程序编译优化打开之后主循环卡死你就会刻骨铭心。这就是差别。我的建议是不要专门刷题而是把项目里每个做过的技术点都往深挖一层问自己为什么这么做不这么做会怎样。项目做扎实了八股只是把经验翻译成术语而已。反过来只背八股、没有项目面试官两个追问就能问穿。至于各类竞赛题目我建议抱着检验基础的心态去做它们对寄存器操作、时序控制、外设综合运用的考核确实到位。但不要为了比赛而学比赛只是一个阶段性体检。6. 端侧 AI 与算法部署这条路现在值不值得走最近两年嵌式和 AI 的结合是涨得最快的方向之一。但我要先把现实说清楚大部分所谓嵌入式 AI 岗位做的不是训模型而是把别人训好的模型在资源受限的设备上跑起来并把它调到能用的程度。6.1 端侧推理的完整链路一个典型的部署流程是这样的模型准备拿到训练好的模型文件确认输入输出格式、是否含自定义算子。图优化与量化合并算子、裁掉推理用不到的分支然后把浮点权重转成定点。这一步是提升速度最有效的环节通常能带来几倍的速度提升和数倍的内存下降。转换到目标运行时用厂商或社区的转换工具把模型转成设备上能加载的格式。这一步最常见的错误是算子不支持需要自己实现或者回退到部分精度。集成到业务代码做前后处理。前处理包括缩放、归一化、颜色空间转换后处理包括非极大值抑制、坐标还原。前后处理在 CPU 上花的时间经常比推理本身还多这是新手最容易忽略的点。调试与验证先把模型在 PC 上跑出的结果和设备上跑出的结果做对比确认量化没有引入过大的精度损失再用真实数据做端到端验证。6.2 性能调优从哪下手调优的次序我一般是这样的从收益最大的开始量化。从浮点转定点通常是量级上的收益。减少数据搬运。把推理输入直接放在零拷贝的内存里避免中间多次拷贝。算子替换。用设备支持加速的算子替换通用实现注意精度是否能接受。并行与流水线。推理、采集、后处理分给不同线程用双缓冲把耗时隐藏起来。底层指令优化。用向量指令加速卷积和矩阵乘或者调用厂商提供的加速库。模型裁剪。如果前面都做完了还不够快那就该回去改模型了。实测下来前两步能解决八成以上的性能问题后面的优化投入产出比急剧下降。所以在没有量化之前不要急着去写指令级优化。6.3 想走这条路需要补什么我给的建议是嵌入式基础不能丢AI 部分是加分项而不是替代项。你要补的主要是这几块基本的深度学习概念卷积、池化、激活、张量维度变换。不用会推导反向传播但要能看懂网络结构和数据流。数据处理能力会用 Python 做数据清洗、可视化、批量推理对比。部署工具链的实操经验完整跑通一次从模型到设备的流程比看十篇文章有用。硬件认知知道设备的算力边界在哪什么模型能跑、什么跑不动这个判断力比技术细节更值钱。提示不要一上来就买最贵的带加速器的开发板。先用一块普通板子把量化和部署流程跑通理解瓶颈在哪再去上专用硬件。另外提醒一句算法部署和传统嵌入式在技能栈上有重合也有分叉。传统嵌入式的核心是控制和时序AI 部署的核心是数据流和吞吐。两者都懂的人在市场上确实更吃香但那意味着你要付出两份学习成本别被一年转行 AI的说法带偏。7. 一些零碎但很值钱的实操习惯最后聊几个具体的习惯都是我这些年踩坑换来的看起来小影响巨大。第一个永远先让程序跑起来再去优化结构。新手最容易犯的错是一开始就想写出架构完美的代码结果三天没跑通一个功能热情耗尽。正确做法是先写能跑的丑代码跑通之后再重构。重构的前提是它有正确的行为作为基线。第二个给自己留一份环境建档。每配好一台开发机、一套工具链就写一份从零开始的搭建文档包括版本号、下载源、遇到的报错和解决办法。换电脑或者带新人时这份文档能省你一整天。第三个器件手册比任何教程都权威。网上的例程可能是错的、可能针对的是另一个批次、可能省略了关键步骤。当你和一个 bug 纠缠超过两小时就该回去翻手册的时序图和寄存器说明。我解决的大部分疑难问题最后答案都写在手册的某个脚注里。第四个把能跑和可靠分开看待。能跑只是及格线。真正的工程能力体现在异常处理、边界条件、长时间运行的稳定性上。我给自己定的规矩是任何要交付的代码必须经过断电重启、通信断开、数据超长、时间跨天这四类异常测试。第五个别怕慢怕的是停。自学最怕的就是断档。哪怕每天只投入一小时只要不断半年后你会惊讶于自己的变化。我自己在学驱动的阶段有段时间工作很忙只能每天看二十分钟代码但就是这二十分钟让我没有把手感丢掉。至于学习资源的选择我的建议是视频用来入门和建立框架手册和源码用来精进。视频的优势是直观劣势是节奏不由你控制而且容易让人产生看懂了等于会了的错觉。真正让能力增长的动作永远是你自己打开一个空文件从零把它写出来然后看着它跑起来。我到现在还保留着一个习惯每接一个新方向先用最少的时间做一个能跑的最小闭环哪怕它很粗糙。因为我清楚打通一次完整的链路比在某个环节上钻研十天更有价值。这个习惯让我在每次技术切换时都能比较快地站稳也推荐给你。
返回列表