diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..7b35fd8f4f598d2065509d3d3507f966d1d0b324 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,11 @@ +pages: + stage: deploy + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + only: + - master \ No newline at end of file diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/README.md b/README.md index 0d8c493b58e2237ea8f67b91aa8552607143474c..d2dbc01bb54d898735fa556e7cf92328dc6b860d 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,93 @@ -# docs-online +# RT-Thread 文档中心 -#### 介绍 -在线文档的markdown版本 +[![License: Apache License 2.0](/_media/license-Apache2-0.svg)](https://github.com/RT-Thread/rt-thread/blob/master/LICENSE) +Star +Fork +star +fork -#### 软件架构 -软件架构说明 +## 简介 +使用 docsify 搭建,文档中心全民参与在线预览提交,详情参见[在线文档开发介绍](#在线文档开发介绍) -#### 安装教程 +## 目录说明 -1. xxxx -2. xxxx -3. xxxx +如下是RT-Thread 在线文档中心的主要目录说明: -#### 使用说明 +development-tools:RT-Thread Studio IDE 和开发辅助工具 Env 的文档。 -1. xxxx -2. xxxx -3. xxxx +rt-thread-version:RT-Thread 各种版本,标准版本、nano版本、smart版本。 -#### 参与贡献 +```tree +├─development-tools # RT-Thread 工具 +│ ├─env # Env 工具 +│ ├─kconfig # Kconfig 工具 +│ ├─rtthread-studio # RT-Thread Studio IDE +│ │ ├─applications # 应用笔记 +│ │ ├─changelog # 版本信息 +│ │ ├─drivers # 驱动开发 +│ │ ├─faq # 常见问题 +│ │ └─um # 用户手册 +│ └─scons # Scons 工具 +│ +├─rt-thread-version # RT-Thread 版本 +│ ├─rt-thread-nano # nano 版本 +│ ├─rt-thread-smart # smart 版本 +│ └─rt-thread-standard # 标准版本 +│ ├─application-note # 应用笔记 +│ ├─development-guide # 开发指南 +│ ├─programming-manual # 编程手册 +│ └─tutorial # 系列教程 +``` -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +## 在线文档开发介绍 +### 修改文档 -#### 特技 +当发现某文档一处错误时,在该文档页面最上方,点击 “修改此文档” -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +![image-20210129101544289](other/figures/image-20210129101544289.png) + +跳转至该文档的 gitee 页面,点击 “编辑” + +![image-20210129101810207](other/figures/image-20210129101736085.png) + +进入可编辑状态,如将 12 改为 15 + +![image-20210129102019156](other/figures/image-20210129102019156.png) + +拉到最下面填写 提交信息,扩展信息可以根据情况填写,无误后,点击提交审核 + +![image-20210129104928115](other/figures/image-20210129102214629.png) + +> [!NOTE] +> 注:提交信息规范如下 +> +> 对某文档某段描述进行更新或完善,或删除某段描述:【更新文档】更新/增加/删除了 xxx +> +> 修改文档中的错误:【修改错误】修改 xxx 为 xxx + +然后等待审核即可,若审核不通过,还需要再次修改。 + +### 增加 / 删除文档 + +增加文档、删除文档则需要通过正常的 PR 流程进行提交(`fork -> clone -> 分支上开发 -> commit -> PR`)。 + +当需要增加文档时,最重要的是判断增加的是什么类型的文档,放在什么位置,以及文档名称等,然后进行提交,增加 / 删除 文档注意更新左侧栏。 + +- 如果不确定新增文档应该存放的位置,请新建 issue 咨询,或在论坛提问咨询,或在任意官方微信群咨询,确认后可以提交新文档。 +- 如果非常确定新文档的存放位置,可以直接新增并提交。 + +> [!NOTE] +> 注:提交信息规范如下 +> 增加一篇文档:【增加文档】xxxx 文档 +> 删除一篇文档:【删除文档】删除 xxx 文档,由于 xxx 原因删除 + +## 注意事项 + +- 文档命名使用英文,有必要时候使用中杠。 +- 每篇文档使用一个 figures 文件夹。 +- 文档内容注意中英文之间增加一个空格。 +- 网站文档已开启缓存,测试没有效果时,请使用 CTRL+F5 强制刷新页面,或让浏览器进入 无痕模式 + + diff --git a/_coverpage.md b/_coverpage.md new file mode 100644 index 0000000000000000000000000000000000000000..d35c2f017c9c1eb075723024bd525baea1451357 --- /dev/null +++ b/_coverpage.md @@ -0,0 +1,11 @@ +# RT-Thread 文档中心 内测版 + +![logo](_media/icon.png) + +> Document Center + +[新手指导](/other/novice-guide/README.md) +[标准版本](/rt-thread-version/rt-thread-standard/README.md) +[Nano版本](/rt-thread-version/rt-thread-nano/an0038-nano-introduction.md) +[Smart版本](/rt-thread-version/rt-thread-smart/rt-smart-quickstart/rt-smart-quickstart.md) +[开发工具](/development-tools/rtthread-studio/README.md) diff --git a/_media/icon.png b/_media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..de86be545a6acbdea8865ca86aa89b4df6cfe794 Binary files /dev/null and b/_media/icon.png differ diff --git a/_media/license-Apache2-0.svg b/_media/license-Apache2-0.svg new file mode 100644 index 0000000000000000000000000000000000000000..8e072eeae65cae7a306a2af8da1d2b6d5f910177 --- /dev/null +++ b/_media/license-Apache2-0.svg @@ -0,0 +1 @@ +license: Apache-2.0licenseApache-2.0 \ No newline at end of file diff --git a/_media/logo.png b/_media/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b37e19d8bc9b2a3de6f8ab14817dba9020ca1da9 Binary files /dev/null and b/_media/logo.png differ diff --git a/_navbar.md b/_navbar.md new file mode 100644 index 0000000000000000000000000000000000000000..82b31af940f3f22f4913b3314e25d347b6e91294 --- /dev/null +++ b/_navbar.md @@ -0,0 +1,5 @@ + + + diff --git a/_scripts/buttons.js b/_scripts/buttons.js new file mode 100644 index 0000000000000000000000000000000000000000..9059e9dcff2a07766a659119ea83bc95b215a6eb --- /dev/null +++ b/_scripts/buttons.js @@ -0,0 +1,6 @@ +/*! + * github-buttons v2.14.2 + * (c) 2021 なつき + * @license BSD-2-Clause + */ +!function(){"use strict";var e=window.document,t=e.location,o=window.Math,r=window.HTMLElement,a=window.XMLHttpRequest,n="github-button",i="https://buttons.github.io/buttons.html",l="github.com",c=a&&"prototype"in a&&"withCredentials"in a.prototype,d=c&&r&&"attachShadow"in r.prototype&&!("prototype"in r.prototype.attachShadow),s=function(e,t,o,r){null==t&&(t="&"),null==o&&(o="="),null==r&&(r=window.decodeURIComponent);for(var a={},n=e.split(t),i=0,l=n.length;i'}}},eye:{heights:{16:{width:16,path:''}}},heart:{heights:{16:{width:16,path:''}}},"issue-opened":{heights:{16:{width:16,path:''}}},"mark-github":{heights:{16:{width:16,path:''}}},"repo-forked":{heights:{16:{width:16,path:''}}},"repo-template":{heights:{16:{width:16,path:''}}},star:{heights:{16:{width:16,path:''}}}},z=function(e,t){e=w(e).replace(/^octicon-/,""),m(k,e)||(e="mark-github");var o=t>=24&&24 in k[e].heights?24:16,r=k[e].heights[o];return'"},C={},F=function(e,t){var o=C[e]||(C[e]=[]);if(!(o.push(t)>1)){var r=v((function(){for(delete C[e];t=o.shift();)t.apply(null,arguments)}));if(c){var n=new a;f(n,"abort",r),f(n,"error",r),f(n,"load",(function(){var e;try{e=JSON.parse(this.responseText)}catch(e){return void r(e)}r(200!==this.status,e)})),n.open("GET",e),n.send()}else{var i=this||window;i._=function(e){i._=null,r(200!==e.meta.status,e.data)};var l=p(i.document)("script",{async:!0,src:e+(-1!==e.indexOf("?")?"&":"?")+"callback=_"}),d=function(){i._&&i._({meta:{}})};f(l,"load",d),f(l,"error",d),l.readyState&&g(l,/de|m/,d),i.document.getElementsByTagName("head")[0].appendChild(l)}}},A=function(e,t,o){var r=p(e.ownerDocument),a=e.appendChild(r("style",{type:"text/css"})),n="body{margin:0}a{text-decoration:none;outline:0}.widget{display:inline-block;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif;font-size:0;line-height:0;white-space:nowrap}.btn,.social-count{position:relative;display:inline-block;height:14px;padding:2px 5px;font-size:11px;font-weight:600;line-height:14px;vertical-align:bottom;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-repeat:repeat-x;background-position:-1px -1px;background-size:110% 110%;border:1px solid}.btn{border-radius:.25em}.btn:not(:last-child){border-radius:.25em 0 0 .25em}.social-count{border-left:0;border-radius:0 .25em .25em 0}.widget-lg .btn,.widget-lg .social-count{height:20px;padding:3px 10px;font-size:12px;line-height:20px}.octicon{display:inline-block;vertical-align:text-top;fill:currentColor}"+function(e){if(null==e)return x.light;if(m(x,e))return x[e];var t=s(e,";",":",(function(e){return e.replace(/^[ \t\n\f\r]+|[ \t\n\f\r]+$/g,"")}));return x[m(x,t["no-preference"])?t["no-preference"]:"light"]+y("light",t.light)+y("dark",t.dark)}(t["data-color-scheme"]);a.styleSheet?a.styleSheet.cssText=n:a.appendChild(e.ownerDocument.createTextNode(n));var i="large"===w(t["data-size"]),c=r("a",{className:"btn",href:t.href,rel:"noopener",target:"_blank",title:t.title||void 0,"aria-label":t["aria-label"]||void 0,innerHTML:z(t["data-icon"],i?16:14)},[" ",r("span",{},[t["data-text"]||""])]),d=e.appendChild(r("div",{className:"widget"+(i?" widget-lg":"")},[c])),f=c.hostname.replace(/\.$/,"");if(f.length1?o.ceil(o.round(e*M)/M*2)/2:o.ceil(e))||0},E=function(e,t){e.style.width=t[0]+"px",e.style.height=t[1]+"px"},_=function(t,r){if(null!=t&&null!=r)if(t.getAttribute&&(t=function(e){for(var t={href:e.href,title:e.title,"aria-label":e.getAttribute("aria-label")},o=["icon","color-scheme","text","size","show-count"],r=0,a=o.length;r + * MIT license + */ +!function(){"use strict";function s(o){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o})(o)}!function(o,e){void 0===e&&(e={});var t=e.insertAt;if(o&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],c=document.createElement("style");c.type="text/css","top"===t&&n.firstChild?n.insertBefore(c,n.firstChild):n.appendChild(c),c.styleSheet?c.styleSheet.cssText=o:c.appendChild(document.createTextNode(o))}}(".docsify-copy-code-button,.docsify-copy-code-button span{cursor:pointer;transition:all .25s ease}.docsify-copy-code-button{position:absolute;z-index:1;top:0;right:0;overflow:visible;padding:.65em .8em;border:0;border-radius:0;outline:0;font-size:0.9em;background:grey;background:var(--theme-color,grey);color:#fff;opacity:0}.docsify-copy-code-button span{border-radius:3px;background:inherit;pointer-events:none}.docsify-copy-code-button .error,.docsify-copy-code-button .success{position:absolute;z-index:-100;top:50%;right:0;padding:.5em .65em;font-size:.825em;opacity:0;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.docsify-copy-code-button.error .error,.docsify-copy-code-button.success .success{right:100%;opacity:1;-webkit-transform:translate(-115%,-50%);transform:translate(-115%,-50%)}.docsify-copy-code-button:focus,pre:hover .docsify-copy-code-button{opacity:1}"),document.querySelector('link[href*="docsify-copy-code"]')&&console.warn("[Deprecation] Link to external docsify-copy-code stylesheet is no longer necessary."),window.DocsifyCopyCodePlugin={init:function(){return function(o,e){o.ready(function(){console.warn("[Deprecation] Manually initializing docsify-copy-code using window.DocsifyCopyCodePlugin.init() is no longer necessary.")})}}},window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(o,r){o.doneEach(function(){var o=Array.apply(null,document.querySelectorAll("pre[data-lang]")),c={buttonText:"复制",errorText:"错误",successText:"复制成功"};r.config.copyCode&&Object.keys(c).forEach(function(t){var n=r.config.copyCode[t];"string"==typeof n?c[t]=n:"object"===s(n)&&Object.keys(n).some(function(o){var e=-1',''.concat(c.buttonText,""),''.concat(c.errorText,""),''.concat(c.successText,""),""].join("");o.forEach(function(o){o.insertAdjacentHTML("beforeend",e)})}),o.mounted(function(){document.querySelector(".content").addEventListener("click",function(o){if(o.target.classList.contains("docsify-copy-code-button")){var e="BUTTON"===o.target.tagName?o.target:o.target.parentNode,t=document.createRange(),n=e.parentNode.querySelector("code"),c=window.getSelection();t.selectNode(n),c.removeAllRanges(),c.addRange(t);try{document.execCommand("copy")&&(e.classList.add("success"),setTimeout(function(){e.classList.remove("success")},1e3))}catch(o){console.error("docsify-copy-code: ".concat(o)),e.classList.add("error"),setTimeout(function(){e.classList.remove("error")},1e3)}"function"==typeof(c=window.getSelection()).removeRange?c.removeRange(t):"function"==typeof c.removeAllRanges&&c.removeAllRanges()}})})}].concat(window.$docsify.plugins||[])}(); +//# sourceMappingURL=docsify-copy-code.min.js.map diff --git a/_scripts/docsify-plugin-flexible-alerts.min.js b/_scripts/docsify-plugin-flexible-alerts.min.js new file mode 100644 index 0000000000000000000000000000000000000000..f295b3adee42b9abde71a833c32cd1e59dec05ae --- /dev/null +++ b/_scripts/docsify-plugin-flexible-alerts.min.js @@ -0,0 +1,9 @@ +/*! + * docsify-plugin-flexible-alerts + * v1.1.0 + * https://github.com/fzankl/docsify-plugin-flexible-alerts#readme + * (c) 2020 Fabian Zankl + * MIT license + */ +!function(){"use strict";function h(t){return(h="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var e;!function(t,e){void 0===e&&(e={});var a,l,o=e.insertAt;t&&"undefined"!=typeof document&&(a=document.head||document.getElementsByTagName("head")[0],(l=document.createElement("style")).type="text/css","top"===o&&a.firstChild?a.insertBefore(l,a.firstChild):a.appendChild(l),l.styleSheet?l.styleSheet.cssText=t:l.appendChild(document.createTextNode(t)))}(".alert{display:block;position:relative;word-wrap:break-word;word-break:break-word;padding:.75rem 1.25rem!important;margin-bottom:1rem!important}.alert>*{max-width:100%}.alert>:first-child{margin-top:0}.alert>:last-child{margin-bottom:0}.alert:before{content:unset!important}.alert+.alert{margin-top:-.25rem!important}.alert p{margin-top:.5rem;margin-bottom:.5rem}.alert .title{display:flex;align-items:center;flex-wrap:wrap;font-weight:600;margin:0}.icon{display:inline-block;width:16px;height:16px;background-repeat:no-repeat;margin-right:.5rem}.alert.callout{border:1px solid #eee;border-left-width:.25rem;border-radius:.25rem;background:var(--background)}.alert.callout.note{border-left-color:#17a2b8!important}.alert.callout.note .title{color:#17a2b8}.alert.callout.note .icon-note{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='1em' height='1em' viewBox='0 0 16 16' fill='%2317a2b8' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' d='M8 16A8 8 0 108 0a8 8 0 000 16zm.93-9.412l-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM8 5.5a1 1 0 100-2 1 1 0 000 2z'/%3E%3C/svg%3E\")}.alert.callout.tip{border-left-color:#28a745!important}.alert.callout.tip .title{color:#28a745}.alert.callout.tip .icon-tip{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='1em' height='1em' viewBox='0 0 352 512' fill='%2328a745' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0026.64 14.28h61.71a31.99 31.99 0 0026.64-14.28l17.09-25.69a31.989 31.989 0 005.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.52 18.85 42.36 58.23 52.21 91.45.04.26.07.52.11.78h160.24c.04-.26.07-.51.11-.78 9.85-33.22 35.69-72.6 52.21-91.45C335.55 260.85 352 220.37 352 176 352 78.61 272.91-.3 175.45 0 73.44.31 0 82.97 0 176zm176-80c-44.11 0-80 35.89-80 80 0 8.84-7.16 16-16 16s-16-7.16-16-16c0-61.76 50.24-112 112-112 8.84 0 16 7.16 16 16s-7.16 16-16 16z'/%3E%3C/svg%3E\")}.alert.callout.warning{border-left-color:#f0ad4e!important}.alert.callout.warning .title{color:#f0ad4e}.alert.callout.warning .icon-warning{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='1em' height='1em' viewBox='0 0 17 16' fill='%23f0ad4e' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' d='M8.982 1.566a1.13 1.13 0 00-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5a.905.905 0 00-.9.995l.35 3.507a.552.552 0 001.1 0l.35-3.507A.905.905 0 008 5zm.002 6a1 1 0 100 2 1 1 0 000-2z'/%3E%3C/svg%3E\")}.alert.callout.attention{border-left-color:#dc3545!important}.alert.callout.attention .title{color:#dc3545}.alert.callout.attention .icon-attention{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='1em' height='1em' viewBox='0 0 16 16' fill='%23dc3545' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' d='M8 15A7 7 0 108 1a7 7 0 000 14zm0 1A8 8 0 108 0a8 8 0 000 16z'/%3E%3Cpath fill-rule='evenodd' d='M11.354 4.646a.5.5 0 010 .708l-6 6a.5.5 0 01-.708-.708l6-6a.5.5 0 01.708 0z'/%3E%3C/svg%3E\")}.alert.flat{border-radius:.125rem;color:#383d41;background-color:#e2e3e5;border:1px solid #d6d8db}.alert.flat.note{color:#02587f;background-color:#cdeefd;border-color:#b4e6fc}.alert.flat.note .title{color:#01354d}.alert.flat.note .icon-note{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='1em' height='1em' viewBox='0 0 16 16' fill='%2301354d' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' d='M8 16A8 8 0 108 0a8 8 0 000 16zm.93-9.412l-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM8 5.5a1 1 0 100-2 1 1 0 000 2z'/%3E%3C/svg%3E\")}.alert.flat.tip{color:#285b2a;background-color:#dbefdc;border-color:#c9e7cb}.alert.flat.tip .title{color:#18381a}.alert.flat.tip .icon-tip{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='1em' height='1em' viewBox='0 0 352 512' fill='%2318381a' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M96.06 454.35c.01 6.29 1.87 12.45 5.36 17.69l17.09 25.69a31.99 31.99 0 0026.64 14.28h61.71a31.99 31.99 0 0026.64-14.28l17.09-25.69a31.989 31.989 0 005.36-17.69l.04-38.35H96.01l.05 38.35zM0 176c0 44.37 16.45 84.85 43.56 115.78 16.52 18.85 42.36 58.23 52.21 91.45.04.26.07.52.11.78h160.24c.04-.26.07-.51.11-.78 9.85-33.22 35.69-72.6 52.21-91.45C335.55 260.85 352 220.37 352 176 352 78.61 272.91-.3 175.45 0 73.44.31 0 82.97 0 176zm176-80c-44.11 0-80 35.89-80 80 0 8.84-7.16 16-16 16s-16-7.16-16-16c0-61.76 50.24-112 112-112 8.84 0 16 7.16 16 16s-7.16 16-16 16z'/%3E%3C/svg%3E\")}.alert.flat.warning{color:#852d12;background-color:#ffddd3;border-color:#ffc9ba}.alert.flat.warning .title{color:#581e0c}.alert.flat.warning .icon-warning{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='1em' height='1em' viewBox='0 0 17 16' fill='%23581e0c' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' d='M8.982 1.566a1.13 1.13 0 00-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5a.905.905 0 00-.9.995l.35 3.507a.552.552 0 001.1 0l.35-3.507A.905.905 0 008 5zm.002 6a1 1 0 100 2 1 1 0 000-2z'/%3E%3C/svg%3E\")}.alert.flat.attention{color:#7f231c;background-color:#fdd9d7;border-color:#fcc2bf}.alert.flat.attention .title{color:#551713}.alert.flat.attention .icon-attention{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg width='1em' height='1em' viewBox='0 0 16 16' fill='%23551713' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' d='M8 15A7 7 0 108 1a7 7 0 000 14zm0 1A8 8 0 108 0a8 8 0 000 16z'/%3E%3Cpath fill-rule='evenodd' d='M11.354 4.646a.5.5 0 010 .708l-6 6a.5.5 0 01-.708-.708l6-6a.5.5 0 01.708 0z'/%3E%3C/svg%3E\")}"),e={style:"callout",note:{label:"Note",icon:"icon-note",className:"note"},tip:{label:"Tip",icon:"icon-tip",className:"tip"},warning:{label:"Warning",icon:"icon-warning",className:"warning"},attention:{label:"Attention",icon:"icon-attention",className:"attention"},typeMappings:{info:"note",danger:"attention"}},window.$docsify=window.$docsify||{},window.$docsify.plugins=[].concat(function(t,f){function p(t,e,a,l){var o=(t||"").match(new RegExp("".concat(e,":(([\\s\\w\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF-]*))")));return o?l?l(o[1]):o[1]:l?l(a):a}var w=function t(e,a,l){var o,r=2]*>(?:

|[\S\n]*)?\[!(\w*)((?:\|[\w*:[\s\w\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF-]*)*?)\]([\s\S]*?)(?:<\/p>)?<\s*\/\s*blockquote>/g,function(t,e,a,l){!w[e.toLowerCase()]&&w.typeMappings[e.toLowerCase()]&&(e=w.typeMappings[e.toLowerCase()]);var o=w[e.toLowerCase()];if(!o)return t;var r,n=p(a,"style",w.style),i=p(a,"iconVisibility","visible",function(t){return"hidden"!==t}),c=p(a,"labelVisibility","visible",function(t){return"hidden"!==t}),d=p(a,"label",o.label),s=p(a,"icon",o.icon),g=p(a,"className",o.className);"object"===h(d)&&((r=Object.keys(d).filter(function(t){return-1'),u='

'.concat(i?m:"").concat(c?d:"","

");return'
\n ').concat(i||c?u:"","\n

").concat(l,"

\n
")}))})},window.$docsify.plugins)}(); +//# sourceMappingURL=docsify-plugin-flexible-alerts.min.js.map diff --git a/_scripts/docsify-scroll-to-top.min.js b/_scripts/docsify-scroll-to-top.min.js new file mode 100644 index 0000000000000000000000000000000000000000..b54d5b69ce43b7be8e7df3d84af6f91df33c7652 --- /dev/null +++ b/_scripts/docsify-scroll-to-top.min.js @@ -0,0 +1 @@ +var CONFIG={auto:true,text:"Top",right:15,bottom:15,offset:500};var install=function(hook,vm){var opts=vm.config.scrollToTop||CONFIG;CONFIG.auto=opts.auto&&typeof opts.auto==="boolean"?opts.auto:CONFIG.auto;CONFIG.text=opts.text&&typeof opts.text==="string"?opts.text:CONFIG.text;CONFIG.right=opts.right&&typeof opts.right==="number"?opts.right:CONFIG.right;CONFIG.bottom=opts.bottom&&typeof opts.bottom==="number"?opts.bottom:CONFIG.bottom;CONFIG.offset=opts.offset&&typeof opts.offset==="number"?opts.offset:CONFIG.offset;var onScroll=function(e){if(!CONFIG.auto){return}var offset=window.document.documentElement.scrollTop;var $scrollBtn=Docsify.dom.find("span.scroll-to-top");$scrollBtn.style.display=offset>=CONFIG.offset?"block":"none"};hook.mounted(function(){var scrollBtn=document.createElement("span");scrollBtn.className="scroll-to-top";scrollBtn.style.display=CONFIG.auto?"none":"block";scrollBtn.style.overflow="hidden";scrollBtn.style.position="fixed";scrollBtn.style.right=CONFIG.right+"px";scrollBtn.style.bottom=CONFIG.bottom+"px";scrollBtn.style.width="50px";scrollBtn.style.height="50px";scrollBtn.style.background="#";scrollBtn.style.color="#34495E";scrollBtn.style.border="1px solid #ddd";scrollBtn.style.borderRadius="4px";scrollBtn.style.lineHeight="42px";scrollBtn.style.fontWeight="600";scrollBtn.style.fontSize="16px";scrollBtn.style.textAlign="center";scrollBtn.style.boxShadow="0px 0px 6px #000";scrollBtn.style.cursor="pointer";var textNode=document.createTextNode(CONFIG.text);scrollBtn.appendChild(textNode);document.body.appendChild(scrollBtn);window.addEventListener("scroll",onScroll);scrollBtn.onclick=function(e){e.stopPropagation();var step=window.scrollY/15;var scroll=function(){window.scrollTo(0,window.scrollY-step);if(window.scrollY>0){setTimeout(scroll,15)}};scroll()}})};$docsify.plugins=[].concat(install,$docsify.plugins); \ No newline at end of file diff --git a/_scripts/docsify-sidebar-collapse.min.js b/_scripts/docsify-sidebar-collapse.min.js new file mode 100644 index 0000000000000000000000000000000000000000..fb2d8506d21af413a2fddefa4effe07edc689f92 --- /dev/null +++ b/_scripts/docsify-sidebar-collapse.min.js @@ -0,0 +1 @@ +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n():"function"==typeof define&&define.amd?define(n):n()}(0,function(){"use strict";function e(e,n){void 0===n&&(n={});var t=n.insertAt;if(e&&"undefined"!=typeof document){var a=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css","top"===t&&a.firstChild?a.insertBefore(o,a.firstChild):a.appendChild(o),o.styleSheet?o.styleSheet.cssText=e:o.appendChild(document.createTextNode(e))}}var t;function a(e){if(e&&null!=t){var n=e.getBoundingClientRect().top;document.querySelector(".sidebar").scrollBy(0,n-t)}}function n(){requestAnimationFrame(function(){var e=document.querySelector(".app-sub-sidebar > .active");if(e)for(e.parentNode.parentNode.querySelectorAll(".app-sub-sidebar").forEach(function(e){return e.classList.remove("open")});e.parentNode.classList.contains("app-sub-sidebar")&&!e.parentNode.classList.contains("open");)e.parentNode.classList.add("open"),e=e.parentNode})}function o(e){t=e.target.getBoundingClientRect().top;var n=s(e.target,"LI",2);n&&(n.classList.contains("open")?(n.classList.remove("open"),setTimeout(function(){n.classList.add("collapse")},0)):(!function(e){if(e)for(e.classList.remove("open","active");e&&"sidebar-nav"!==e.className&&e.parentNode;)"LI"!==e.parentNode.tagName&&"app-sub-sidebar"!==e.parentNode.className||e.parentNode.classList.remove("open"),e=e.parentNode}(r()),i(n),setTimeout(function(){n.classList.remove("collapse")},0)),a(n))}function r(){var e=document.querySelector(".sidebar-nav .active");e||(e=s(document.querySelector('.sidebar-nav a[href="'.concat(decodeURIComponent(location.hash).replace(/ /gi,"%20"),'"]')),"LI",2))&&e.classList.add("active");return e}function i(e){if(e)for(e.classList.add("open","active");e&&"sidebar-nav"!==e.className&&e.parentNode;)"LI"!==e.parentNode.tagName&&"app-sub-sidebar"!==e.parentNode.className||e.parentNode.classList.add("open"),e=e.parentNode}function s(e,n,t){if(e&&e.tagName===n)return e;for(var a=0;e;){if(t<++a)return;if(e.parentNode.tagName===n)return e.parentNode;e=e.parentNode}}e(".sidebar-nav > ul > li ul {\n display: none;\n}\n\n.app-sub-sidebar {\n display: none;\n}\n\n.app-sub-sidebar.open {\n display: block;\n}\n\n.sidebar-nav .open > ul:not(.app-sub-sidebar),\n.sidebar-nav .active:not(.collapse) > ul {\n display: block;\n}\n\n/* 抖动 */\n.sidebar-nav li.open:not(.collapse) > ul {\n display: block;\n}\n\n.active + ul.app-sub-sidebar {\n display: block;\n}\n"),document.addEventListener("DOMContentLoaded",function(){document.querySelector(".sidebar-nav").addEventListener("click",o)}),document.addEventListener("scroll",n);e("@media screen and (max-width: 768px) {\n /* 移动端适配 */\n .markdown-section {\n max-width: none;\n padding: 16px;\n }\n /* 改变原来按钮热区大小 */\n .sidebar-toggle {\n padding: 0 0 10px 10px;\n }\n /* my pin */\n .sidebar-pin {\n appearance: none;\n outline: none;\n position: fixed;\n bottom: 0;\n border: none;\n width: 40px;\n height: 40px;\n background: transparent;\n }\n}\n");var d="DOCSIFY_SIDEBAR_PIN_FLAG";function c(){var e=localStorage.getItem(d);e="true"===e,localStorage.setItem(d,!e),document.querySelector(".content").style.transform=e?document.querySelector(".sidebar").style.transform="translateX(0px)":document.querySelector(".sidebar").style.transform="translateX(300px)"}!function(){if(!(768)[\r\n]+([\s|\S]*?)[\r\n\s]+()/m,tabCommentMarkup:/[\r\n]*(\s*)[\r\n]+([\s\S]*?)[\r\n]*\s*(?=)/m},m={persist:!0,sync:!0,theme:"classic",tabComments:!0,tabHeadings:!0};function g(t,a){var o=1 --\x3e'),a="\n".concat(s,"\x3c!-- ").concat(u," --\x3e");for(var i=function(){var t=(f[2]||"[Tab]").trim(),a=(f[3]||"").trim();o=o.replace(f[0],function(){return["\n".concat(s,"\x3c!-- ").concat(u,' --\x3e"),"\n".concat(s,"\x3c!-- ").concat(u,'
--\x3e'),"\n\n".concat(s).concat(a),"\n\n".concat(s,"\x3c!-- ").concat(u,"
--\x3e")].join("")})};null!==(f=(m.tabComments?p.tabCommentMarkup.exec(o):null)||(m.tabHeadings?p.tabHeadingMarkup.exec(o):null));)i()}o=(o=o.replace(r,function(){return t})).replace(n,function(){return a}),d=d.replace(b[0],function(){return o})};null!==(b=p.tabBlockMarkup.exec(d));)a();return t.forEach(function(t,a){d=d.replace(t,function(){return o[a]})}),d}(t)),t}),t.afterEach(function(t,a){o&&(t=function(o){for(var c,t=function(){var t=c[0],a=c[1]||"";o=o.replace(t,function(){return a})};null!==(c=p.commentReplaceMarkup.exec(o));)t();return o}(t)),a(t)}),t.doneEach(function(){var t,a,c,e;o&&(t=document.querySelector(".".concat(y.tabsContainer)),a=t?Array.apply(null,t.querySelectorAll(".".concat(y.tabBlock))):[],c=JSON.parse(sessionStorage.getItem(window.location.href))||{},e=JSON.parse(sessionStorage.getItem("*"))||[],s(),a.forEach(function(a,t){var o=a.querySelector(".".concat(y.tabButtonActive));o||(m.sync&&e.length&&(o=e.map(function(t){return a.querySelector(".".concat(y.tabButton,'[data-tab="').concat(t,'"]'))}).filter(function(t){return t})[0]),!o&&m.persist&&(o=a.querySelector(".".concat(y.tabButton,'[data-tab="').concat(c[t],'"]'))),(o=o||a.querySelector(".".concat(y.tabButton)))&&o.classList.add(y.tabButtonActive))}))}),t.mounted(function(){var t=document.querySelector(".".concat(y.tabsContainer));t&&t.addEventListener("click",function(t){g(t.target)}),window.addEventListener("hashchange",s,!1)})},window.$docsify.plugins||[])))}(); +//# sourceMappingURL=docsify-tabs.min.js.map \ No newline at end of file diff --git a/_scripts/docsify.min.js b/_scripts/docsify.min.js new file mode 100644 index 0000000000000000000000000000000000000000..13ee7221c1ea4aebc66dd5de0a2367dc45e0da19 --- /dev/null +++ b/_scripts/docsify.min.js @@ -0,0 +1 @@ +!function(){function s(n){var r=Object.create(null);return function(e){var t=c(e)?e:JSON.stringify(e);return r[t]||(r[t]=n(e))}}var a=s(function(e){return e.replace(/([A-Z])/g,function(e){return"-"+e.toLowerCase()})}),l=Object.prototype.hasOwnProperty,f=Object.assign||function(e){for(var t=arguments,n=1;n/gm),Ve=$(/^data-[\-\w.\u00B7-\uFFFF]/),Xe=$(/^aria-[\-\w]+$/),Ke=$(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Qe=$(/^(?:\w+script|data):/i),Je=$(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g),et="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function tt(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t

').querySelector("svg img")&&(o=!0)}catch(e){}}(),function(){try{var e=$("</title><img>");ze(/<\/title/,e.querySelector("title").innerHTML)&&(s=!0)}catch(e){}}());function fe(e){return _.call(e.ownerDocument||e,e,r.SHOW_ELEMENT|r.SHOW_COMMENT|r.SHOW_TEXT,function(){return r.FILTER_ACCEPT},!1)}function he(e){return"object"===(void 0===f?"undefined":et(f))?e instanceof f:e&&"object"===(void 0===e?"undefined":et(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName}function ge(e,t,n){E[e]&&we(E[e],function(e){e.call(d,t,n,pe)})}function me(e){var t=void 0;if(ge("beforeSanitizeElements",e,null),function(e){return!(e instanceof h||e instanceof g||"string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof a&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute&&"string"==typeof e.namespaceURI)}(e))return R(e),!0;var n=Le(e.nodeName);if(ge("uponSanitizeElement",e,{tagName:n,allowedTags:j}),("svg"===n||"math"===n)&&0!==e.querySelectorAll("p, br").length)return R(e),!0;if(j[n]&&!U[n])return"noscript"===n&&ze(/<\/noscript/i,e.innerHTML)?(R(e),!0):"noembed"===n&&ze(/<\/noembed/i,e.innerHTML)?(R(e),!0):(!G||e.firstElementChild||e.content&&e.content.firstElementChild||!ze(/</g,e.textContent)||(Te(d.removed,{element:e.cloneNode()}),e.innerHTML?e.innerHTML=Ce(e.innerHTML,/</g,"<"):e.innerHTML=Ce(e.textContent,/</g,"<")),V&&3===e.nodeType&&(t=e.textContent,t=Ce(t,F," "),t=Ce(t,z," "),e.textContent!==t&&(Te(d.removed,{element:e.cloneNode()}),e.textContent=t)),ge("afterSanitizeElements",e,null),!1);if(ie&&!se[n]&&"function"==typeof e.insertAdjacentHTML)try{var r=e.innerHTML;e.insertAdjacentHTML("AfterEnd",y?y.createHTML(r):r)}catch(e){}return R(e),!0}function ve(e,t,n){if(re&&("id"===t||"name"===t)&&(n in l||n in de))return!1;if(W&&ze(O,t));else if(Z&&ze(M,t));else{if(!I[t]||B[t])return!1;if(ce[t]);else if(ze(D,Ce(n,P,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==$e(n,"data:")||!le[e])if(Y&&!ze(N,Ce(n,P,"")));else if(n)return!1}return!0}function be(e){var t=void 0,n=void 0,r=void 0,i=void 0,a=void 0;ge("beforeSanitizeAttributes",e,null);var o=e.attributes;if(o){var s={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:I};for(a=o.length;a--;){var l=t=o[a],c=l.name,u=l.namespaceURI;if(n=Fe(t.value),r=Le(c),s.attrName=r,s.attrValue=n,s.keepAttr=!0,s.forceKeepAttr=void 0,ge("uponSanitizeAttribute",e,s),n=s.attrValue,!s.forceKeepAttr){if("name"===r&&"IMG"===e.nodeName&&o.id)i=o.id,o=Ee(o,[]),C("id",e),C(c,e),_e(o,i)>a&&e.setAttribute("id",i.value);else{if("INPUT"===e.nodeName&&"type"===r&&"file"===n&&s.keepAttr&&(I[r]||!B[r]))continue;"id"===c&&e.setAttribute(c,""),C(c,e)}if(s.keepAttr)if(G&&ze(/\/>/i,n))C(c,e);else if(ze(/svg|math/i,e.namespaceURI)&&ze(Oe("</("+Se(ke(se),"|")+")","i"),n))C(c,e);else{V&&(n=Ce(n,F," "),n=Ce(n,z," "));var p=e.nodeName.toLowerCase();if(ve(p,r,n))try{u?e.setAttributeNS(u,c,n):e.setAttribute(c,n),Ae(d.removed)}catch(e){}}}}ge("afterSanitizeAttributes",e,null)}}function ye(e){var t=void 0,n=fe(e);for(ge("beforeSanitizeShadowDOM",e,null);t=n.nextNode();)ge("uponSanitizeShadowNode",t,null),me(t)||(t.content instanceof p&&ye(t.content),be(t));ge("afterSanitizeShadowDOM",e,null)}return d.sanitize=function(e,t){var n=void 0,r=void 0,i=void 0,a=void 0,o=void 0;if("string"!=typeof(e=e||"\x3c!--\x3e")&&!he(e)){if("function"!=typeof e.toString)throw Me("toString is not a function");if("string"!=typeof(e=e.toString()))throw Me("dirty is not a string, aborting")}if(!d.isSupported){if("object"===et(c.toStaticHTML)||"function"==typeof c.toStaticHTML){if("string"==typeof e)return c.toStaticHTML(e);if(he(e))return c.toStaticHTML(e.outerHTML)}return e}if(K||L(t),d.removed=[],"string"==typeof e&&(ae=!1),ae);else if(e instanceof f)1===(r=(n=$("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===r.nodeName?n=r:"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!J&&!V&&!X&&ne&&-1===e.indexOf("<"))return y?y.createHTML(e):e;if(!(n=$(e)))return J?null:k}n&&Q&&R(n.firstChild);for(var s=fe(ae?e:n);i=s.nextNode();)3===i.nodeType&&i===a||me(i)||(i.content instanceof p&&ye(i.content),be(i),a=i);if(a=null,ae)return e;if(J){if(ee)for(o=A.call(n.ownerDocument);n.firstChild;)o.appendChild(n.firstChild);else o=n;return te&&(o=T.call(u,o,!0)),o}var l=X?n.outerHTML:n.innerHTML;return V&&(l=Ce(l,F," "),l=Ce(l,z," ")),y&&ne?y.createHTML(l):l},d.setConfig=function(e){L(e),K=!0},d.clearConfig=function(){pe=null,K=!1},d.isValidAttribute=function(e,t,n){pe||L({});var r=Le(e),i=Le(t);return ve(r,i,n)},d.addHook=function(e,t){"function"==typeof t&&(E[e]=E[e]||[],Te(E[e],t))},d.removeHook=function(e){E[e]&&Ae(E[e])},d.removeHooks=function(e){E[e]&&(E[e]=[])},d.removeAllHooks=function(){E={}},d}();function H(e){var t,n=e.loaded,r=e.total,i=e.step;P||function(){var e=g("div");e.classList.add("progress"),o(v,e),P=e}(),t=i?80<(t=parseInt(P.style.width||0,10)+i)?80:t:Math.floor(n/r*100),P.style.opacity=1,P.style.width=95<=t?"100%":t+"%",95<=t&&(clearTimeout(D),D=setTimeout(function(e){P.style.opacity=0,P.style.width="0%"},200))}var I={};function q(a,e,t){void 0===e&&(e=!1),void 0===t&&(t={});function n(){o.addEventListener.apply(o,arguments)}var o=new XMLHttpRequest,r=I[a];if(r)return{then:function(e){return e(r.content,r.opt)},abort:p};for(var i in o.open("GET",a),t)l.call(t,i)&&o.setRequestHeader(i,t[i]);return o.send(),{then:function(r,i){if(void 0===i&&(i=p),e){var t=setInterval(function(e){return H({step:Math.floor(5*Math.random()+1)})},500);n("progress",H),n("loadend",function(e){H(e),clearInterval(t)})}n("error",i),n("load",function(e){var t=e.target;if(400<=t.status)i(t);else{var n=I[a]={content:t.response,opt:{updatedAt:o.getResponseHeader("last-modified")}};r(n.content,n.opt)}})},abort:function(e){return 4!==o.readyState&&o.abort()}}}function U(e,t){e.innerHTML=e.innerHTML.replace(/var\(\s*--theme-color.*?\)/g,t)}function B(e,t,r,i){void 0===i&&(i=p);var a=e._hooks[t],o=function(t){var e=a[t];if(t>=a.length)i(r);else if("function"==typeof e)if(2===e.length)e(r,function(e){r=e,o(t+1)});else{var n=e(r);r=void 0===n?r:n,o(t+1)}else o(t+1)};o(0)}var Z=u.title;function W(){var e=m("section.cover");if(e){var t=e.getBoundingClientRect().height;window.pageYOffset>=t||e.classList.contains("hidden")?_(v,"add","sticky"):_(v,"remove","sticky")}}function Y(e,t,r,n){var i=[];null!=(t=m(t))&&(i=y(t,"a"));var a,o=decodeURI(e.toURL(e.getCurrentPath()));return i.sort(function(e,t){return t.href.length-e.href.length}).forEach(function(e){var t=e.getAttribute("href"),n=r?e.parentNode:e;e.title=e.innerText,0!==o.indexOf(t)||a?_(n,"remove","active"):(a=e,_(n,"add","active"))}),n&&(u.title=a?a.title||a.innerText+" - "+Z:Z),a}var G=decodeURIComponent,V=encodeURIComponent;function X(e){var n={};return(e=e.trim().replace(/^(\?|#|&)/,""))&&e.split("&").forEach(function(e){var t=e.replace(/\+/g," ").split("=");n[t[0]]=t[1]&&G(t[1])}),n}function K(e,t){void 0===t&&(t=[]);var n=[];for(var r in e)-1<t.indexOf(r)||n.push(e[r]?(V(r)+"="+V(e[r])).toLowerCase():V(r));return n.length?"?"+n.join("&"):""}var Q=s(function(e){return/(:|(\/{2}))/g.test(e)}),J=s(function(e){return e.split(/[?#]/)[0]}),ee=s(function(e){if(/\/$/g.test(e))return e;var t=e.match(/(\S*\/)[^/]+$/);return t?t[1]:""}),te=s(function(e){return e.replace(/^\/+/,"/").replace(/([^:])\/{2,}/g,"$1/")}),ne=s(function(e){for(var t=e.replace(/^\//,"").split("/"),n=[],r=0,i=t.length;r<i;r++){var a=t[r];".."===a?n.pop():"."!==a&&n.push(a)}return"/"+n.join("/")});function re(){for(var e=[],t=arguments.length;t--;)e[t]=arguments[t];return te(e.join("/"))}var ie=s(function(e){return e.replace("#","?id=")});function ae(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}var oe=(function(e,t,n){return t&&ae(e.prototype,t),n&&ae(e,n),e}(se,[{key:"getIntermediateValue",value:function(e){return this.decimal?e:Math.round(e)}},{key:"getFinalValue",value:function(){return this.end}}]),se);function se(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,se),this.start=e.start,this.end=e.end,this.decimal=e.decimal}function le(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}var ce=(function(e,t,n){return t&&le(e.prototype,t),n&&le(e,n),e}(ue,[{key:"begin",value:function(){return this.isRunning||this.next===this.end||(this.frame=window.requestAnimationFrame(this._tick.bind(this))),this}},{key:"stop",value:function(){return window.cancelAnimationFrame(this.frame),this.isRunning=!1,this.frame=null,this.timeStart=null,this.next=null,this}},{key:"on",value:function(e,t){return this.events[e]=this.events[e]||[],this.events[e].push(t),this}},{key:"_emit",value:function(e,t){var n=this,r=this.events[e];r&&r.forEach(function(e){return e.call(n,t)})}},{key:"_tick",value:function(e){this.isRunning=!0;var t=this.next||this.start;this.timeStart||(this.timeStart=e),this.timeElapsed=e-this.timeStart,this.next=this.ease(this.timeElapsed,this.start,this.end-this.start,this.duration),this._shouldTick(t)?(this._emit("tick",this.tweener.getIntermediateValue(this.next)),this.frame=window.requestAnimationFrame(this._tick.bind(this))):(this._emit("tick",this.tweener.getFinalValue()),this._emit("done",null))}},{key:"_shouldTick",value:function(e){return{up:this.next<this.end&&e<=this.next,down:this.next>this.end&&e>=this.next}[this.direction]}},{key:"_defaultEase",value:function(e,t,n,r){return(e/=r/2)<1?n/2*e*e+t:-n/2*(--e*(e-2)-1)+t}}]),ue);function ue(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,ue),this.duration=e.duration||1e3,this.ease=e.easing||this._defaultEase,this.tweener=e.tweener||new oe(e),this.start=this.tweener.start,this.end=this.tweener.end,this.frame=null,this.next=null,this.isRunning=!1,this.events={},this.direction=this.start<this.end?"up":"down"}var pe={},de=!1,fe=null,he=!0,ge=0;function me(e){if(he){for(var t,n=m(".sidebar"),r=y(".anchor"),i=b(n,".sidebar-nav"),a=b(n,"li.active"),o=document.documentElement,s=(o&&o.scrollTop||document.body.scrollTop)-ge,l=0,c=r.length;l<c;l+=1){var u=r[l];if(u.offsetTop>s){t=t||u;break}t=u}if(t){var p=pe[ve(e,t.getAttribute("data-id"))];if(p&&p!==a&&(a&&a.classList.remove("active"),p.classList.add("active"),a=p,!de&&v.classList.contains("sticky"))){var d=n.clientHeight,f=a.offsetTop+a.clientHeight+40,h=a.offsetTop>=i.scrollTop&&f<=i.scrollTop+d,g=f-0<d;n.scrollTop=h?i.scrollTop:g?0:f-d}}}}function ve(e,t){return decodeURIComponent(e)+"?id="+decodeURIComponent(t)}function be(e,t){if(t){var n=A().topMargin,r=b("#"+t);r&&function(e,t){void 0===t&&(t=0),fe&&fe.stop(),he=!1,fe=new ce({start:window.pageYOffset,end:e.getBoundingClientRect().top+window.pageYOffset-t,duration:500}).on("tick",function(e){return window.scrollTo(0,e)}).on("done",function(){he=!0,fe=null}).begin()}(r,n);var i=pe[ve(e,t)],a=b(m(".sidebar"),"li.active");a&&a.classList.remove("active"),i&&i.classList.add("active")}}var ye=u.scrollingElement||u.documentElement;var it="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function at(e,t){return e(t={exports:{}},t.exports),t.exports}function ot(e){return dt[e]}var st=at(function(t){function e(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1}}t.exports={defaults:{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1},getDefaults:e,changeDefaults:function(e){t.exports.defaults=e}}}),lt=(st.defaults,st.getDefaults,st.changeDefaults,/[&<>"']/),ct=/[&<>"']/g,ut=/[<>"']|&(?!#?\w+;)/,pt=/[<>"']|&(?!#?\w+;)/g,dt={"&":"&","<":"<",">":">",'"':""","'":"'"};var ft=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function ht(e){return e.replace(ft,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}var gt=/(^|[^\[])\^/g;var mt=/[^\w:]/g,vt=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;var bt={},yt=/^[^:]+:\/*[^/]*$/,kt=/^([^:]+:)[\s\S]*$/,xt=/^([^:]+:\/*[^/]*)[\s\S]*$/;function wt(e,t){bt[" "+e]||(yt.test(e)?bt[" "+e]=e+"/":bt[" "+e]=_t(e,"/",!0));var n=-1===(e=bt[" "+e]).indexOf(":");return"//"===t.substring(0,2)?n?t:e.replace(kt,"$1")+t:"/"===t.charAt(0)?n?t:e.replace(xt,"$1")+t:e+t}function _t(e,t,n){var r=e.length;if(0===r)return"";for(var i=0;i<r;){var a=e.charAt(r-i-1);if(a!==t||n){if(a===t||!n)break;i++}else i++}return e.substr(0,r-i)}var St=function(e,t){if(t){if(lt.test(e))return e.replace(ct,ot)}else if(ut.test(e))return e.replace(pt,ot);return e},At=ht,Tt=function(n,e){n=n.source||n,e=e||"";var r={replace:function(e,t){return t=(t=t.source||t).replace(gt,"$1"),n=n.replace(e,t),r},getRegex:function(){return new RegExp(n,e)}};return r},Et=function(e,t,n){if(e){var r;try{r=decodeURIComponent(ht(n)).replace(mt,"").toLowerCase()}catch(e){return null}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:")||0===r.indexOf("data:"))return null}t&&!vt.test(n)&&(n=wt(t,n));try{n=encodeURI(n).replace(/%25/g,"%")}catch(e){return null}return n},Lt={exec:function(){}},Rt=function(e){for(var t,n,r=arguments,i=1;i<arguments.length;i++)for(n in t=r[i])Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e},Ct=function(e,t){var n=e.replace(/\|/g,function(e,t,n){for(var r=!1,i=t;0<=--i&&"\\"===n[i];)r=!r;return r?"|":" |"}).split(/ \|/),r=0;if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;r<n.length;r++)n[r]=n[r].trim().replace(/\\\|/g,"|");return n},$t=_t,Ft=function(e,t){if(-1===e.indexOf(t[1]))return-1;for(var n=e.length,r=0,i=0;i<n;i++)if("\\"===e[i])i++;else if(e[i]===t[0])r++;else if(e[i]===t[1]&&--r<0)return i;return-1},zt=function(e){e&&e.sanitize&&!e.silent&&console.warn("marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options")},Ot=st.defaults,Mt=$t,Nt=Ct,Pt=St,Dt=Ft;function jt(e,t,n){var r=t.href,i=t.title?Pt(t.title):null,a=e[1].replace(/\\([\[\]])/g,"$1");return"!"!==e[0].charAt(0)?{type:"link",raw:n,href:r,title:i,text:a}:{type:"image",raw:n,href:r,title:i,text:Pt(a)}}var Ht=function(){function e(e){this.options=e||Ot}return e.prototype.space=function(e){var t=this.rules.block.newline.exec(e);if(t)return 1<t[0].length?{type:"space",raw:t[0]}:{raw:"\n"}},e.prototype.code=function(e,t){var n=this.rules.block.code.exec(e);if(n){var r=t[t.length-1];if(r&&"paragraph"===r.type)return{raw:n[0],text:n[0].trimRight()};var i=n[0].replace(/^ {4}/gm,"");return{type:"code",raw:n[0],codeBlockStyle:"indented",text:this.options.pedantic?i:Mt(i,"\n")}}},e.prototype.fences=function(e){var t=this.rules.block.fences.exec(e);if(t){var n=t[0],r=function(e,t){var n=e.match(/^(\s+)(?:```)/);if(null===n)return t;var r=n[1];return t.split("\n").map(function(e){var t=e.match(/^\s+/);return null===t?e:t[0].length>=r.length?e.slice(r.length):e}).join("\n")}(n,t[3]||"");return{type:"code",raw:n,lang:t[2]?t[2].trim():t[2],text:r}}},e.prototype.heading=function(e){var t=this.rules.block.heading.exec(e);if(t)return{type:"heading",raw:t[0],depth:t[1].length,text:t[2]}},e.prototype.nptable=function(e){var t=this.rules.block.nptable.exec(e);if(t){var n={type:"table",header:Nt(t[1].replace(/^ *| *\| *$/g,"")),align:t[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:t[3]?t[3].replace(/\n$/,"").split("\n"):[],raw:t[0]};if(n.header.length===n.align.length){var r,i=n.align.length;for(r=0;r<i;r++)/^ *-+: *$/.test(n.align[r])?n.align[r]="right":/^ *:-+: *$/.test(n.align[r])?n.align[r]="center":/^ *:-+ *$/.test(n.align[r])?n.align[r]="left":n.align[r]=null;for(i=n.cells.length,r=0;r<i;r++)n.cells[r]=Nt(n.cells[r],n.header.length);return n}}},e.prototype.hr=function(e){var t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:t[0]}},e.prototype.blockquote=function(e){var t=this.rules.block.blockquote.exec(e);if(t){var n=t[0].replace(/^ *> ?/gm,"");return{type:"blockquote",raw:t[0],text:n}}},e.prototype.list=function(e){var t=this.rules.block.list.exec(e);if(t){for(var n,r,i,a,o,s,l,c=t[0],u=t[2],p=1<u.length,d=")"===u[u.length-1],f={type:"list",raw:c,ordered:p,start:p?+u.slice(0,-1):"",loose:!1,items:[]},h=t[0].match(this.rules.block.item),g=!1,m=h.length,v=0;v<m;v++)r=(c=n=h[v]).length,~(n=n.replace(/^ *([*+-]|\d+[.)]) */,"")).indexOf("\n ")&&(r-=n.length,n=this.options.pedantic?n.replace(/^ {1,4}/gm,""):n.replace(new RegExp("^ {1,"+r+"}","gm"),"")),v!==m-1&&(i=this.rules.block.bullet.exec(h[v+1])[0],(p?1===i.length||!d&&")"===i[i.length-1]:1<i.length||this.options.smartLists&&i!==u)&&(a=h.slice(v+1).join("\n"),f.raw=f.raw.substring(0,f.raw.length-a.length),v=m-1)),o=g||/\n\n(?!\s*$)/.test(n),v!==m-1&&(g="\n"===n.charAt(n.length-1),o=o||g),o&&(f.loose=!0),l=void 0,(s=/^\[[ xX]\] /.test(n))&&(l=" "!==n[1],n=n.replace(/^\[[ xX]\] +/,"")),f.items.push({type:"list_item",raw:c,task:s,checked:l,loose:o,text:n});return f}},e.prototype.html=function(e){var t=this.rules.block.html.exec(e);if(t)return{type:this.options.sanitize?"paragraph":"html",raw:t[0],pre:!this.options.sanitizer&&("pre"===t[1]||"script"===t[1]||"style"===t[1]),text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(t[0]):Pt(t[0]):t[0]}},e.prototype.def=function(e){var t=this.rules.block.def.exec(e);if(t)return t[3]&&(t[3]=t[3].substring(1,t[3].length-1)),{tag:t[1].toLowerCase().replace(/\s+/g," "),raw:t[0],href:t[2],title:t[3]}},e.prototype.table=function(e){var t=this.rules.block.table.exec(e);if(t){var n={type:"table",header:Nt(t[1].replace(/^ *| *\| *$/g,"")),align:t[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:t[3]?t[3].replace(/\n$/,"").split("\n"):[]};if(n.header.length===n.align.length){n.raw=t[0];var r,i=n.align.length;for(r=0;r<i;r++)/^ *-+: *$/.test(n.align[r])?n.align[r]="right":/^ *:-+: *$/.test(n.align[r])?n.align[r]="center":/^ *:-+ *$/.test(n.align[r])?n.align[r]="left":n.align[r]=null;for(i=n.cells.length,r=0;r<i;r++)n.cells[r]=Nt(n.cells[r].replace(/^ *\| *| *\| *$/g,""),n.header.length);return n}}},e.prototype.lheading=function(e){var t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1]}},e.prototype.paragraph=function(e){var t=this.rules.block.paragraph.exec(e);if(t)return{type:"paragraph",raw:t[0],text:"\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1]}},e.prototype.text=function(e,t){var n=this.rules.block.text.exec(e);if(n){var r=t[t.length-1];return r&&"text"===r.type?{raw:n[0],text:n[0]}:{type:"text",raw:n[0],text:n[0]}}},e.prototype.escape=function(e){var t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:Pt(t[1])}},e.prototype.tag=function(e,t,n){var r=this.rules.inline.tag.exec(e);if(r)return!t&&/^<a /i.test(r[0])?t=!0:t&&/^<\/a>/i.test(r[0])&&(t=!1),!n&&/^<(pre|code|kbd|script)(\s|>)/i.test(r[0])?n=!0:n&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(r[0])&&(n=!1),{type:this.options.sanitize?"text":"html",raw:r[0],inLink:t,inRawBlock:n,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(r[0]):Pt(r[0]):r[0]}},e.prototype.link=function(e){var t=this.rules.inline.link.exec(e);if(t){var n=Dt(t[2],"()");if(-1<n){var r=(0===t[0].indexOf("!")?5:4)+t[1].length+n;t[2]=t[2].substring(0,n),t[0]=t[0].substring(0,r).trim(),t[3]=""}var i=t[2],a="";if(this.options.pedantic){var o=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(i);a=o?(i=o[1],o[3]):""}else a=t[3]?t[3].slice(1,-1):"";return jt(t,{href:(i=i.trim().replace(/^<([\s\S]*)>$/,"$1"))?i.replace(this.rules.inline._escapes,"$1"):i,title:a?a.replace(this.rules.inline._escapes,"$1"):a},t[0])}},e.prototype.reflink=function(e,t){var n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){var r=(n[2]||n[1]).replace(/\s+/g," ");if((r=t[r.toLowerCase()])&&r.href)return jt(n,r,n[0]);var i=n[0].charAt(0);return{type:"text",raw:i,text:i}}},e.prototype.strong=function(e,t,n){void 0===n&&(n="");var r=this.rules.inline.strong.start.exec(e);if(r&&(!r[1]||r[1]&&(""===n||this.rules.inline.punctuation.exec(n)))){t=t.slice(-1*e.length);var i,a="**"===r[0]?this.rules.inline.strong.endAst:this.rules.inline.strong.endUnd;for(a.lastIndex=0;null!=(r=a.exec(t));)if(i=this.rules.inline.strong.middle.exec(t.slice(0,r.index+3)))return{type:"strong",raw:e.slice(0,i[0].length),text:e.slice(2,i[0].length-2)}}},e.prototype.em=function(e,t,n){void 0===n&&(n="");var r=this.rules.inline.em.start.exec(e);if(r&&(!r[1]||r[1]&&(""===n||this.rules.inline.punctuation.exec(n)))){t=t.slice(-1*e.length);var i,a="*"===r[0]?this.rules.inline.em.endAst:this.rules.inline.em.endUnd;for(a.lastIndex=0;null!=(r=a.exec(t));)if(i=this.rules.inline.em.middle.exec(t.slice(0,r.index+2)))return{type:"em",raw:e.slice(0,i[0].length),text:e.slice(1,i[0].length-1)}}},e.prototype.codespan=function(e){var t=this.rules.inline.code.exec(e);if(t){var n=t[2].replace(/\n/g," "),r=/[^ ]/.test(n),i=n.startsWith(" ")&&n.endsWith(" ");return r&&i&&(n=n.substring(1,n.length-1)),n=Pt(n,!0),{type:"codespan",raw:t[0],text:n}}},e.prototype.br=function(e){var t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}},e.prototype.del=function(e){var t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[1]}},e.prototype.autolink=function(e,t){var n,r,i=this.rules.inline.autolink.exec(e);if(i)return r="@"===i[2]?"mailto:"+(n=Pt(this.options.mangle?t(i[1]):i[1])):n=Pt(i[1]),{type:"link",raw:i[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}},e.prototype.url=function(e,t){var n;if(n=this.rules.inline.url.exec(e)){var r,i;if("@"===n[2])i="mailto:"+(r=Pt(this.options.mangle?t(n[0]):n[0]));else{for(var a;a=n[0],n[0]=this.rules.inline._backpedal.exec(n[0])[0],a!==n[0];);r=Pt(n[0]),i="www."===n[1]?"http://"+r:r}return{type:"link",raw:n[0],text:r,href:i,tokens:[{type:"text",raw:r,text:r}]}}},e.prototype.inlineText=function(e,t,n){var r,i=this.rules.inline.text.exec(e);if(i)return r=t?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):Pt(i[0]):i[0]:Pt(this.options.smartypants?n(i[0]):i[0]),{type:"text",raw:i[0],text:r}},e}(),It=Lt,qt=Tt,Ut=Rt,Bt={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?\\?>\\n*|<![A-Z][\\s\\S]*?>\\n*|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>\\n*|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|</(?!script|pre|style)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *<?([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,nptable:It,table:It,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};Bt.def=qt(Bt.def).replace("label",Bt._label).replace("title",Bt._title).getRegex(),Bt.bullet=/(?:[*+-]|\d{1,9}[.)])/,Bt.item=/^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/,Bt.item=qt(Bt.item,"gm").replace(/bull/g,Bt.bullet).getRegex(),Bt.list=qt(Bt.list).replace(/bull/g,Bt.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+Bt.def.source+")").getRegex(),Bt._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",Bt._comment=/<!--(?!-?>)[\s\S]*?-->/,Bt.html=qt(Bt.html,"i").replace("comment",Bt._comment).replace("tag",Bt._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),Bt.paragraph=qt(Bt._paragraph).replace("hr",Bt.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)").replace("tag",Bt._tag).getRegex(),Bt.blockquote=qt(Bt.blockquote).replace("paragraph",Bt.paragraph).getRegex(),Bt.normal=Ut({},Bt),Bt.gfm=Ut({},Bt.normal,{nptable:"^ *([^|\\n ].*\\|.*)\\n *([-:]+ *\\|[-| :]*)(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)",table:"^ *\\|(.+)\\n *\\|?( *[-:]+[-| :]*)(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),Bt.gfm.nptable=qt(Bt.gfm.nptable).replace("hr",Bt.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)").replace("tag",Bt._tag).getRegex(),Bt.gfm.table=qt(Bt.gfm.table).replace("hr",Bt.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)").replace("tag",Bt._tag).getRegex(),Bt.pedantic=Ut({},Bt.normal,{html:qt("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:\"[^\"]*\"|'[^']*'|\\s[^'\"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",Bt._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,fences:It,paragraph:qt(Bt.normal._paragraph).replace("hr",Bt.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",Bt.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});var Zt={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:It,tag:"^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",strong:{start:/^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/,middle:/^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/,endAst:/[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation\s]|$))/,endUnd:/[^\s]__(?!_)(?:(?=[punctuation\s])|$)/},em:{start:/^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/,middle:/^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/,endAst:/[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation\s]|$))/,endUnd:/[^\s]_(?!_)(?:(?=[punctuation\s])|$)/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:It,text:/^(`+|[^`])(?:[\s\S]*?(?:(?=[\\<!\[`*]|\b_|$)|[^ ](?= {2,}\n))|(?= {2,}\n))/,punctuation:/^([\s*punctuation])/,_punctuation:"!\"#$%&'()+\\-.,/:;<=>?@\\[\\]`^{|}~"};Zt.punctuation=qt(Zt.punctuation).replace(/punctuation/g,Zt._punctuation).getRegex(),Zt._blockSkip="\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>",Zt._overlapSkip="__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*",Zt.em.start=qt(Zt.em.start).replace(/punctuation/g,Zt._punctuation).getRegex(),Zt.em.middle=qt(Zt.em.middle).replace(/punctuation/g,Zt._punctuation).replace(/overlapSkip/g,Zt._overlapSkip).getRegex(),Zt.em.endAst=qt(Zt.em.endAst,"g").replace(/punctuation/g,Zt._punctuation).getRegex(),Zt.em.endUnd=qt(Zt.em.endUnd,"g").replace(/punctuation/g,Zt._punctuation).getRegex(),Zt.strong.start=qt(Zt.strong.start).replace(/punctuation/g,Zt._punctuation).getRegex(),Zt.strong.middle=qt(Zt.strong.middle).replace(/punctuation/g,Zt._punctuation).replace(/blockSkip/g,Zt._blockSkip).getRegex(),Zt.strong.endAst=qt(Zt.strong.endAst,"g").replace(/punctuation/g,Zt._punctuation).getRegex(),Zt.strong.endUnd=qt(Zt.strong.endUnd,"g").replace(/punctuation/g,Zt._punctuation).getRegex(),Zt.blockSkip=qt(Zt._blockSkip,"g").getRegex(),Zt.overlapSkip=qt(Zt._overlapSkip,"g").getRegex(),Zt._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,Zt._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,Zt._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,Zt.autolink=qt(Zt.autolink).replace("scheme",Zt._scheme).replace("email",Zt._email).getRegex(),Zt._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,Zt.tag=qt(Zt.tag).replace("comment",Bt._comment).replace("attribute",Zt._attribute).getRegex(),Zt._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Zt._href=/<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*/,Zt._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,Zt.link=qt(Zt.link).replace("label",Zt._label).replace("href",Zt._href).replace("title",Zt._title).getRegex(),Zt.reflink=qt(Zt.reflink).replace("label",Zt._label).getRegex(),Zt.reflinkSearch=qt(Zt.reflinkSearch,"g").replace("reflink",Zt.reflink).replace("nolink",Zt.nolink).getRegex(),Zt.normal=Ut({},Zt),Zt.pedantic=Ut({},Zt.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:qt(/^!?\[(label)\]\((.*?)\)/).replace("label",Zt._label).getRegex(),reflink:qt(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Zt._label).getRegex()}),Zt.gfm=Ut({},Zt.normal,{escape:qt(Zt.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^~+(?=\S)([\s\S]*?\S)~+/,text:/^(`+|[^`])(?:[\s\S]*?(?:(?=[\\<!\[`*~]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))|(?= {2,}\n|[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@))/}),Zt.gfm.url=qt(Zt.gfm.url,"i").replace("email",Zt.gfm._extended_email).getRegex(),Zt.breaks=Ut({},Zt.gfm,{br:qt(Zt.br).replace("{2,}","*").getRegex(),text:qt(Zt.gfm.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()});var Wt={block:Bt,inline:Zt},Yt=st.defaults,Gt=Wt.block,Vt=Wt.inline;function Xt(e){return e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")}function Kt(e){var t,n,r="",i=e.length;for(t=0;t<i;t++)n=e.charCodeAt(t),.5<Math.random()&&(n="x"+n.toString(16)),r+="&#"+n+";";return r}var Qt=function(){function n(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||Yt,this.options.tokenizer=this.options.tokenizer||new Ht,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options;var t={block:Gt.normal,inline:Vt.normal};this.options.pedantic?(t.block=Gt.pedantic,t.inline=Vt.pedantic):this.options.gfm&&(t.block=Gt.gfm,this.options.breaks?t.inline=Vt.breaks:t.inline=Vt.gfm),this.tokenizer.rules=t}var e={rules:{configurable:!0}};return e.rules.get=function(){return{block:Gt,inline:Vt}},n.lex=function(e,t){return new n(t).lex(e)},n.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," "),this.blockTokens(e,this.tokens,!0),this.inline(this.tokens),this.tokens},n.prototype.blockTokens=function(e,t,n){var r,i,a,o;for(void 0===t&&(t=[]),void 0===n&&(n=!0),e=e.replace(/^ +$/gm,"");e;)if(r=this.tokenizer.space(e))e=e.substring(r.raw.length),r.type&&t.push(r);else if(r=this.tokenizer.code(e,t))e=e.substring(r.raw.length),r.type?t.push(r):((o=t[t.length-1]).raw+="\n"+r.raw,o.text+="\n"+r.text);else if(r=this.tokenizer.fences(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.heading(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.nptable(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.hr(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.blockquote(e))e=e.substring(r.raw.length),r.tokens=this.blockTokens(r.text,[],n),t.push(r);else if(r=this.tokenizer.list(e)){for(e=e.substring(r.raw.length),a=r.items.length,i=0;i<a;i++)r.items[i].tokens=this.blockTokens(r.items[i].text,[],!1);t.push(r)}else if(r=this.tokenizer.html(e))e=e.substring(r.raw.length),t.push(r);else if(n&&(r=this.tokenizer.def(e)))e=e.substring(r.raw.length),this.tokens.links[r.tag]||(this.tokens.links[r.tag]={href:r.href,title:r.title});else if(r=this.tokenizer.table(e))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.lheading(e))e=e.substring(r.raw.length),t.push(r);else if(n&&(r=this.tokenizer.paragraph(e)))e=e.substring(r.raw.length),t.push(r);else if(r=this.tokenizer.text(e,t))e=e.substring(r.raw.length),r.type?t.push(r):((o=t[t.length-1]).raw+="\n"+r.raw,o.text+="\n"+r.text);else if(e){var s="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(s);break}throw new Error(s)}return t},n.prototype.inline=function(e){var t,n,r,i,a,o,s=e.length;for(t=0;t<s;t++)switch((o=e[t]).type){case"paragraph":case"text":case"heading":o.tokens=[],this.inlineTokens(o.text,o.tokens);break;case"table":for(o.tokens={header:[],cells:[]},i=o.header.length,n=0;n<i;n++)o.tokens.header[n]=[],this.inlineTokens(o.header[n],o.tokens.header[n]);for(i=o.cells.length,n=0;n<i;n++)for(a=o.cells[n],o.tokens.cells[n]=[],r=0;r<a.length;r++)o.tokens.cells[n][r]=[],this.inlineTokens(a[r],o.tokens.cells[n][r]);break;case"blockquote":this.inline(o.tokens);break;case"list":for(i=o.items.length,n=0;n<i;n++)this.inline(o.items[n].tokens)}return e},n.prototype.inlineTokens=function(e,t,n,r,i){var a;void 0===t&&(t=[]),void 0===n&&(n=!1),void 0===r&&(r=!1),void 0===i&&(i="");var o,s=e;if(this.tokens.links){var l=Object.keys(this.tokens.links);if(0<l.length)for(;null!=(o=this.tokenizer.rules.inline.reflinkSearch.exec(s));)l.includes(o[0].slice(o[0].lastIndexOf("[")+1,-1))&&(s=s.slice(0,o.index)+"["+"a".repeat(o[0].length-2)+"]"+s.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(o=this.tokenizer.rules.inline.blockSkip.exec(s));)s=s.slice(0,o.index)+"["+"a".repeat(o[0].length-2)+"]"+s.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;e;)if(a=this.tokenizer.escape(e))e=e.substring(a.raw.length),t.push(a);else if(a=this.tokenizer.tag(e,n,r))e=e.substring(a.raw.length),n=a.inLink,r=a.inRawBlock,t.push(a);else if(a=this.tokenizer.link(e))e=e.substring(a.raw.length),"link"===a.type&&(a.tokens=this.inlineTokens(a.text,[],!0,r)),t.push(a);else if(a=this.tokenizer.reflink(e,this.tokens.links))e=e.substring(a.raw.length),"link"===a.type&&(a.tokens=this.inlineTokens(a.text,[],!0,r)),t.push(a);else if(a=this.tokenizer.strong(e,s,i))e=e.substring(a.raw.length),a.tokens=this.inlineTokens(a.text,[],n,r),t.push(a);else if(a=this.tokenizer.em(e,s,i))e=e.substring(a.raw.length),a.tokens=this.inlineTokens(a.text,[],n,r),t.push(a);else if(a=this.tokenizer.codespan(e))e=e.substring(a.raw.length),t.push(a);else if(a=this.tokenizer.br(e))e=e.substring(a.raw.length),t.push(a);else if(a=this.tokenizer.del(e))e=e.substring(a.raw.length),a.tokens=this.inlineTokens(a.text,[],n,r),t.push(a);else if(a=this.tokenizer.autolink(e,Kt))e=e.substring(a.raw.length),t.push(a);else if(n||!(a=this.tokenizer.url(e,Kt))){if(a=this.tokenizer.inlineText(e,r,Xt))e=e.substring(a.raw.length),i=a.raw.slice(-1),t.push(a);else if(e){var c="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(c);break}throw new Error(c)}}else e=e.substring(a.raw.length),t.push(a);return t},Object.defineProperties(n,e),n}(),Jt=st.defaults,en=Et,tn=St,nn=function(){function e(e){this.options=e||Jt}return e.prototype.code=function(e,t,n){var r=(t||"").match(/\S*/)[0];if(this.options.highlight){var i=this.options.highlight(e,r);null!=i&&i!==e&&(n=!0,e=i)}return r?'<pre><code class="'+this.options.langPrefix+tn(r,!0)+'">'+(n?e:tn(e,!0))+"</code></pre>\n":"<pre><code>"+(n?e:tn(e,!0))+"</code></pre>\n"},e.prototype.blockquote=function(e){return"<blockquote>\n"+e+"</blockquote>\n"},e.prototype.html=function(e){return e},e.prototype.heading=function(e,t,n,r){return this.options.headerIds?"<h"+t+' id="'+this.options.headerPrefix+r.slug(n)+'">'+e+"</h"+t+">\n":"<h"+t+">"+e+"</h"+t+">\n"},e.prototype.hr=function(){return this.options.xhtml?"<hr/>\n":"<hr>\n"},e.prototype.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+"</"+r+">\n"},e.prototype.listitem=function(e){return"<li>"+e+"</li>\n"},e.prototype.checkbox=function(e){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox"'+(this.options.xhtml?" /":"")+"> "},e.prototype.paragraph=function(e){return"<p>"+e+"</p>\n"},e.prototype.table=function(e,t){return"<table>\n<thead>\n"+e+"</thead>\n"+(t=t&&"<tbody>"+t+"</tbody>")+"</table>\n"},e.prototype.tablerow=function(e){return"<tr>\n"+e+"</tr>\n"},e.prototype.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+"</"+n+">\n"},e.prototype.strong=function(e){return"<strong>"+e+"</strong>"},e.prototype.em=function(e){return"<em>"+e+"</em>"},e.prototype.codespan=function(e){return"<code>"+e+"</code>"},e.prototype.br=function(){return this.options.xhtml?"<br/>":"<br>"},e.prototype.del=function(e){return"<del>"+e+"</del>"},e.prototype.link=function(e,t,n){if(null===(e=en(this.options.sanitize,this.options.baseUrl,e)))return n;var r='<a href="'+tn(e)+'"';return t&&(r+=' title="'+t+'"'),r+=">"+n+"</a>"},e.prototype.image=function(e,t,n){if(null===(e=en(this.options.sanitize,this.options.baseUrl,e)))return n;var r='<img src="'+e+'" alt="'+n+'"';return t&&(r+=' title="'+t+'"'),r+=this.options.xhtml?"/>":">"},e.prototype.text=function(e){return e},e}(),rn=function(){function e(){}return e.prototype.strong=function(e){return e},e.prototype.em=function(e){return e},e.prototype.codespan=function(e){return e},e.prototype.del=function(e){return e},e.prototype.html=function(e){return e},e.prototype.text=function(e){return e},e.prototype.link=function(e,t,n){return""+n},e.prototype.image=function(e,t,n){return""+n},e.prototype.br=function(){return""},e}(),an=function(){function e(){this.seen={}}return e.prototype.slug=function(e){var t=e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-");if(this.seen.hasOwnProperty(t))for(var n=t;this.seen[n]++,t=n+"-"+this.seen[n],this.seen.hasOwnProperty(t););return this.seen[t]=0,t},e}(),on=st.defaults,sn=At,ln=function(){function n(e){this.options=e||on,this.options.renderer=this.options.renderer||new nn,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new rn,this.slugger=new an}return n.parse=function(e,t){return new n(t).parse(e)},n.prototype.parse=function(e,t){void 0===t&&(t=!0);var n,r,i,a,o,s,l,c,u,p,d,f,h,g,m,v,b,y,k="",x=e.length;for(n=0;n<x;n++)switch((p=e[n]).type){case"space":continue;case"hr":k+=this.renderer.hr();continue;case"heading":k+=this.renderer.heading(this.parseInline(p.tokens),p.depth,sn(this.parseInline(p.tokens,this.textRenderer)),this.slugger);continue;case"code":k+=this.renderer.code(p.text,p.lang,p.escaped);continue;case"table":for(l=c="",a=p.header.length,r=0;r<a;r++)l+=this.renderer.tablecell(this.parseInline(p.tokens.header[r]),{header:!0,align:p.align[r]});for(c+=this.renderer.tablerow(l),u="",a=p.cells.length,r=0;r<a;r++){for(l="",o=(s=p.tokens.cells[r]).length,i=0;i<o;i++)l+=this.renderer.tablecell(this.parseInline(s[i]),{header:!1,align:p.align[i]});u+=this.renderer.tablerow(l)}k+=this.renderer.table(c,u);continue;case"blockquote":u=this.parse(p.tokens),k+=this.renderer.blockquote(u);continue;case"list":for(d=p.ordered,f=p.start,h=p.loose,a=p.items.length,u="",r=0;r<a;r++)v=(m=p.items[r]).checked,b=m.task,g="",m.task&&(y=this.renderer.checkbox(v),h?0<m.tokens.length&&"text"===m.tokens[0].type?(m.tokens[0].text=y+" "+m.tokens[0].text,m.tokens[0].tokens&&0<m.tokens[0].tokens.length&&"text"===m.tokens[0].tokens[0].type&&(m.tokens[0].tokens[0].text=y+" "+m.tokens[0].tokens[0].text)):m.tokens.unshift({type:"text",text:y}):g+=y),g+=this.parse(m.tokens,h),u+=this.renderer.listitem(g,b,v);k+=this.renderer.list(u,d,f);continue;case"html":k+=this.renderer.html(p.text);continue;case"paragraph":k+=this.renderer.paragraph(this.parseInline(p.tokens));continue;case"text":for(u=p.tokens?this.parseInline(p.tokens):p.text;n+1<x&&"text"===e[n+1].type;)u+="\n"+((p=e[++n]).tokens?this.parseInline(p.tokens):p.text);k+=t?this.renderer.paragraph(u):u;continue;default:var w='Token with "'+p.type+'" type was not found.';if(this.options.silent)return void console.error(w);throw new Error(w)}return k},n.prototype.parseInline=function(e,t){t=t||this.renderer;var n,r,i="",a=e.length;for(n=0;n<a;n++)switch((r=e[n]).type){case"escape":i+=t.text(r.text);break;case"html":i+=t.html(r.text);break;case"link":i+=t.link(r.href,r.title,this.parseInline(r.tokens,t));break;case"image":i+=t.image(r.href,r.title,r.text);break;case"strong":i+=t.strong(this.parseInline(r.tokens,t));break;case"em":i+=t.em(this.parseInline(r.tokens,t));break;case"codespan":i+=t.codespan(r.text);break;case"br":i+=t.br();break;case"del":i+=t.del(this.parseInline(r.tokens,t));break;case"text":i+=t.text(r.text);break;default:var o='Token with "'+r.type+'" type was not found.';if(this.options.silent)return void console.error(o);throw new Error(o)}return i},n}(),cn=Rt,un=zt,pn=St,dn=st.getDefaults,fn=st.changeDefaults,hn=st.defaults;function gn(e,n,r){if(null==e)throw new Error("marked(): input parameter is undefined or null");if("string"!=typeof e)throw new Error("marked(): input parameter is of type "+Object.prototype.toString.call(e)+", string expected");if("function"==typeof n&&(r=n,n=null),n=cn({},gn.defaults,n||{}),un(n),r){var i,a=n.highlight;try{i=Qt.lex(e,n)}catch(e){return r(e)}function o(t){var e;if(!t)try{e=ln.parse(i,n)}catch(e){t=e}return n.highlight=a,t?r(t):r(null,e)}if(!a||a.length<3)return o();if(delete n.highlight,!i.length)return o();var s=0;return gn.walkTokens(i,function(n){"code"===n.type&&(s++,setTimeout(function(){a(n.text,n.lang,function(e,t){if(e)return o(e);null!=t&&t!==n.text&&(n.text=t,n.escaped=!0),0===--s&&o()})},0))}),void(0===s&&o())}try{var t=Qt.lex(e,n);return n.walkTokens&&gn.walkTokens(t,n.walkTokens),ln.parse(t,n)}catch(e){if(e.message+="\nPlease report this to https://github.com/markedjs/marked.",n.silent)return"<p>An error occurred:</p><pre>"+pn(e.message+"",!0)+"</pre>";throw e}}gn.options=gn.setOptions=function(e){return cn(gn.defaults,e),fn(gn.defaults),gn},gn.getDefaults=dn,gn.defaults=hn,gn.use=function(a){var e=cn({},a);if(a.renderer){function t(r){var i=o[r];o[r]=function(){for(var e=[],t=arguments.length;t--;)e[t]=arguments[t];var n=a.renderer[r].apply(o,e);return!1===n&&(n=i.apply(o,e)),n}}var o=gn.defaults.renderer||new nn;for(var n in a.renderer)t(n);e.renderer=o}if(a.tokenizer){function r(e){var r=i[s];i[s]=function(){for(var e=[],t=arguments.length;t--;)e[t]=arguments[t];var n=a.tokenizer[s].apply(i,e);return!1===n&&(n=r.apply(i,e)),n}}var i=gn.defaults.tokenizer||new Ht;for(var s in a.tokenizer)r();e.tokenizer=i}if(a.walkTokens){var l=gn.defaults.walkTokens;e.walkTokens=function(e){a.walkTokens(e),l&&l(e)}}gn.setOptions(e)},gn.walkTokens=function(e,t){for(var n=0,r=e;n<r.length;n+=1){var i=r[n];switch(t(i),i.type){case"table":for(var a=0,o=i.tokens.header;a<o.length;a+=1){var s=o[a];gn.walkTokens(s,t)}for(var l=0,c=i.tokens.cells;l<c.length;l+=1)for(var u=0,p=c[l];u<p.length;u+=1){var d=p[u];gn.walkTokens(d,t)}break;case"list":gn.walkTokens(i.items,t);break;default:i.tokens&&gn.walkTokens(i.tokens,t)}}},gn.Parser=ln,gn.parser=ln.parse,gn.Renderer=nn,gn.TextRenderer=rn,gn.Lexer=Qt,gn.lexer=Qt.lex,gn.Tokenizer=Ht,gn.Slugger=an;var mn=gn.parse=gn;function vn(e,t){if(void 0===t&&(t='<ul class="app-sub-sidebar">{inner}</ul>'),!e||!e.length)return"";var n="";return e.forEach(function(e){n+='<li><a class="section-link" href="'+e.slug+'" title="'+e.title+'">'+e.title+"</a></li>",e.children&&(n+=vn(e.children,t))}),t.replace("{inner}",n)}function bn(e,t){return'<p class="'+e+'">'+t.slice(5).trim()+"</p>"}function yn(e,r){var i=[],a={};return e.forEach(function(e){var t=e.level||1,n=t-1;r<t||(a[n]?a[n].children=(a[n].children||[]).concat(e):i.push(e),a[t]=e)}),i}var kn={},xn=/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g;function wn(e){return e.toLowerCase()}function _n(e){if("string"!=typeof e)return"";var t=e.trim().replace(/[A-Z]+/g,wn).replace(/<[^>\d]+>/g,"").replace(xn,"").replace(/\s/g,"-").replace(/-+/g,"-").replace(/^(\d)/,"_$1"),n=kn[t];return n=l.call(kn,t)?n+1:0,(kn[t]=n)&&(t=t+"-"+n),t}function Sn(e,t){return'<img class="emoji" src="https://github.githubassets.com/images/icons/emoji/'+t+'.png" alt="'+t+'" />'}function An(e){void 0===e&&(e="");var r={};return{str:e=e&&e.replace(/^'/,"").replace(/'$/,"").replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g,function(e,t,n){return-1===t.indexOf(":")?(r[t]=n&&n.replace(/"/g,"")||!0,""):e}).trim(),config:r}}_n.clear=function(){kn={}};var Tn,En=at(function(e){var a=function(c){var u=/\blang(?:uage)?-([\w-]+)\b/i,t=0,z={manual:c.Prism&&c.Prism.manual,disableWorkerMessageHandler:c.Prism&&c.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof O?new O(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function n(e,r){var i,t;switch(r=r||{},z.util.type(e)){case"Object":if(t=z.util.objId(e),r[t])return r[t];for(var a in i={},r[t]=i,e)e.hasOwnProperty(a)&&(i[a]=n(e[a],r));return i;case"Array":return t=z.util.objId(e),r[t]?r[t]:(i=[],r[t]=i,e.forEach(function(e,t){i[t]=n(e,r)}),i);default:return e}},getLanguage:function(e){for(;e&&!u.test(e.className);)e=e.parentElement;return e?(e.className.match(u)||[,"none"])[1].toLowerCase():"none"},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var t=(/at [^(\r\n]*\((.*):.+:.+\)$/i.exec(e.stack)||[])[1];if(t){var n=document.getElementsByTagName("script");for(var r in n)if(n[r].src==t)return n[r]}return null}},isActive:function(e,t,n){for(var r="no-"+t;e;){var i=e.classList;if(i.contains(t))return!0;if(i.contains(r))return!1;e=e.parentElement}return!!n}},languages:{extend:function(e,t){var n=z.util.clone(z.languages[e]);for(var r in t)n[r]=t[r];return n},insertBefore:function(n,e,t,r){var i=(r=r||z.languages)[n],a={};for(var o in i)if(i.hasOwnProperty(o)){if(o==e)for(var s in t)t.hasOwnProperty(s)&&(a[s]=t[s]);t.hasOwnProperty(o)||(a[o]=i[o])}var l=r[n];return r[n]=a,z.languages.DFS(z.languages,function(e,t){t===l&&e!=n&&(this[e]=a)}),a},DFS:function e(t,n,r,i){i=i||{};var a=z.util.objId;for(var o in t)if(t.hasOwnProperty(o)){n.call(t,o,t[o],r||o);var s=t[o],l=z.util.type(s);"Object"!==l||i[a(s)]?"Array"!==l||i[a(s)]||(i[a(s)]=!0,e(s,n,o,i)):(i[a(s)]=!0,e(s,n,null,i))}}},plugins:{},highlightAll:function(e,t){z.highlightAllUnder(document,e,t)},highlightAllUnder:function(e,t,n){var r={callback:n,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};z.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),z.hooks.run("before-all-elements-highlight",r);for(var i,a=0;i=r.elements[a++];)z.highlightElement(i,!0===t,r.callback)},highlightElement:function(e,t,n){var r=z.util.getLanguage(e),i=z.languages[r];e.className=e.className.replace(u,"").replace(/\s+/g," ")+" language-"+r;var a=e.parentElement;a&&"pre"===a.nodeName.toLowerCase()&&(a.className=a.className.replace(u,"").replace(/\s+/g," ")+" language-"+r);var o={element:e,language:r,grammar:i,code:e.textContent};function s(e){o.highlightedCode=e,z.hooks.run("before-insert",o),o.element.innerHTML=o.highlightedCode,z.hooks.run("after-highlight",o),z.hooks.run("complete",o),n&&n.call(o.element)}if(z.hooks.run("before-sanity-check",o),!o.code)return z.hooks.run("complete",o),void(n&&n.call(o.element));if(z.hooks.run("before-highlight",o),o.grammar)if(t&&c.Worker){var l=new Worker(z.filename);l.onmessage=function(e){s(e.data)},l.postMessage(JSON.stringify({language:o.language,code:o.code,immediateClose:!0}))}else s(z.highlight(o.code,o.grammar,o.language));else s(z.util.encode(o.code))},highlight:function(e,t,n){var r={code:e,grammar:t,language:n};return z.hooks.run("before-tokenize",r),r.tokens=z.tokenize(r.code,r.grammar),z.hooks.run("after-tokenize",r),O.stringify(z.util.encode(r.tokens),r.language)},tokenize:function(e,t){var n=t.rest;if(n){for(var r in n)t[r]=n[r];delete t.rest}var i=new a;return M(i,i.head,e),function e(t,n,r,i,a,o){for(var s in r)if(r.hasOwnProperty(s)&&r[s]){var l=r[s];l=Array.isArray(l)?l:[l];for(var c=0;c<l.length;++c){if(o&&o.cause==s+","+c)return;var u=l[c],p=u.inside,d=!!u.lookbehind,f=!!u.greedy,h=0,g=u.alias;if(f&&!u.pattern.global){var m=u.pattern.toString().match(/[imsuy]*$/)[0];u.pattern=RegExp(u.pattern.source,m+"g")}for(var v=u.pattern||u,b=i.next,y=a;b!==n.tail&&!(o&&y>=o.reach);y+=b.value.length,b=b.next){var k=b.value;if(n.length>t.length)return;if(!(k instanceof O)){var x=1;if(f&&b!=n.tail.prev){v.lastIndex=y;var w=v.exec(t);if(!w)break;var _=w.index+(d&&w[1]?w[1].length:0),S=w.index+w[0].length,A=y;for(A+=b.value.length;A<=_;)b=b.next,A+=b.value.length;if(A-=b.value.length,y=A,b.value instanceof O)continue;for(var T=b;T!==n.tail&&(A<S||"string"==typeof T.value);T=T.next)x++,A+=T.value.length;x--,k=t.slice(y,A),w.index-=y}else{v.lastIndex=0;var w=v.exec(k)}if(w){d&&(h=w[1]?w[1].length:0);var _=w.index+h,E=w[0].slice(h),S=_+E.length,L=k.slice(0,_),R=k.slice(S),C=y+k.length;o&&C>o.reach&&(o.reach=C);var $=b.prev;L&&($=M(n,$,L),y+=L.length),N(n,$,x);var F=new O(s,p?z.tokenize(E,p):E,g,E);b=M(n,$,F),R&&M(n,b,R),1<x&&e(t,n,r,b.prev,y,{cause:s+","+c,reach:C})}}}}}}(e,i,t,i.head,0),function(e){var t=[],n=e.head.next;for(;n!==e.tail;)t.push(n.value),n=n.next;return t}(i)},hooks:{all:{},add:function(e,t){var n=z.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=z.hooks.all[e];if(n&&n.length)for(var r,i=0;r=n[i++];)r(t)}},Token:O};function O(e,t,n,r){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length}function a(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function M(e,t,n){var r=t.next,i={value:n,prev:t,next:r};return t.next=i,r.prev=i,e.length++,i}function N(e,t,n){for(var r=t.next,i=0;i<n&&r!==e.tail;i++)r=r.next;(t.next=r).prev=t,e.length-=i}if(c.Prism=z,O.stringify=function t(e,n){if("string"==typeof e)return e;if(Array.isArray(e)){var r="";return e.forEach(function(e){r+=t(e,n)}),r}var i={type:e.type,content:t(e.content,n),tag:"span",classes:["token",e.type],attributes:{},language:n},a=e.alias;a&&(Array.isArray(a)?Array.prototype.push.apply(i.classes,a):i.classes.push(a)),z.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=" "+s+'="'+(i.attributes[s]||"").replace(/"/g,""")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+o+">"+i.content+"</"+i.tag+">"},!c.document)return c.addEventListener&&(z.disableWorkerMessageHandler||c.addEventListener("message",function(e){var t=JSON.parse(e.data),n=t.language,r=t.code,i=t.immediateClose;c.postMessage(z.highlight(r,z.languages[n],n)),i&&c.close()},!1)),z;var e=z.util.currentScript();function n(){z.manual||z.highlightAll()}if(e&&(z.filename=e.src,e.hasAttribute("data-manual")&&(z.manual=!0)),!z.manual){var r=document.readyState;"loading"===r||"interactive"===r&&e&&e.defer?document.addEventListener("DOMContentLoaded",n):window.requestAnimationFrame?window.requestAnimationFrame(n):window.setTimeout(n,16)}return z}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});e.exports&&(e.exports=a),void 0!==it&&(it.Prism=a),a.languages.markup={comment:/<!--[\s\S]*?-->/,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata:/<!\[CDATA\[[\s\S]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},a.languages.markup.tag.inside["attr-value"].inside.entity=a.languages.markup.entity,a.languages.markup.doctype.inside["internal-subset"].inside=a.languages.markup,a.hooks.add("wrap",function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(a.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:a.languages[t]},n.cdata=/^<!\[CDATA\[|\]\]>$/i;var r={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:a.languages[t]};var i={};i[e]={pattern:RegExp(/(<__[\s\S]*?>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:r},a.languages.insertBefore("markup","cdata",i)}}),a.languages.html=a.languages.markup,a.languages.mathml=a.languages.markup,a.languages.svg=a.languages.markup,a.languages.xml=a.languages.extend("markup",{}),a.languages.ssml=a.languages.xml,a.languages.atom=a.languages.xml,a.languages.rss=a.languages.xml,function(e){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\((?!\s*\))\s*)(?:[^()]|\((?:[^()]|\([^()]*\))*\))+?(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+t.source+")*?(?=\\s*\\{)"),string:{pattern:t,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),e.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:n.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:e.languages.css}},alias:"language-css"}},n.tag))}(a),a.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},a.languages.javascript=a.languages.extend("clike",{"class-name":[a.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|(?:get|set)(?=\s*[\[$\w\xA0-\uFFFF])|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),a.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,a.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:a.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:a.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:a.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:a.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),a.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:a.languages.javascript}},string:/[\s\S]+/}}}),a.languages.markup&&a.languages.markup.tag.addInlined("script","javascript"),a.languages.js=a.languages.javascript,function(){if("undefined"!=typeof self&&self.Prism&&self.document){var l=window.Prism,c={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},u="data-src-status",p="loading",d="pre[data-src]:not(["+u+'="loaded"]):not(['+u+'="'+p+'"])',r=/\blang(?:uage)?-([\w-]+)\b/i;l.hooks.add("before-highlightall",function(e){e.selector+=", "+d}),l.hooks.add("before-sanity-check",function(e){var t=e.element;if(t.matches(d)){e.code="",t.setAttribute(u,p);var n=t.appendChild(document.createElement("CODE"));n.textContent="Loading…";var r=t.getAttribute("data-src"),i=e.language;if("none"===i){var a=(/\.(\w+)$/.exec(r)||[,"none"])[1];i=c[a]||a}f(n,i),f(t,i);var o=l.plugins.autoloader;o&&o.loadLanguages(i);var s=new XMLHttpRequest;s.open("GET",r,!0),s.onreadystatechange=function(){4==s.readyState&&(s.status<400&&s.responseText?(t.setAttribute(u,"loaded"),n.textContent=s.responseText,l.highlightElement(n)):(t.setAttribute(u,"failed"),400<=s.status?n.textContent=function(e,t){return"✖ Error "+e+" while fetching file: "+t}(s.status,s.statusText):n.textContent="✖ Error: File does not exist or is empty"))},s.send(null)}});var e=!(l.plugins.fileHighlight={highlight:function(e){for(var t,n=(e||document).querySelectorAll(d),r=0;t=n[r++];)l.highlightElement(t)}});l.fileHighlight=function(){e||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),e=!0),l.plugins.fileHighlight.highlight.apply(this,arguments)}}function f(e,t){var n=e.className;n=n.replace(r," ")+" language-"+t,e.className=n.replace(/\s+/g," ").trim()}}()});function Ln(e,t){return"___"+e.toUpperCase()+t+"___"}Tn=Prism,Object.defineProperties(Tn.languages["markup-templating"]={},{buildPlaceholders:{value:function(r,i,e,a){if(r.language===i){var o=r.tokenStack=[];r.code=r.code.replace(e,function(e){if("function"==typeof a&&!a(e))return e;for(var t,n=o.length;-1!==r.code.indexOf(t=Ln(i,n));)++n;return o[n]=e,t}),r.grammar=Tn.languages.markup}}},tokenizePlaceholders:{value:function(f,h){if(f.language===h&&f.tokenStack){f.grammar=Tn.languages[h];var g=0,m=Object.keys(f.tokenStack);!function e(t){for(var n=0;n<t.length&&!(g>=m.length);n++){var r=t[n];if("string"==typeof r||r.content&&"string"==typeof r.content){var i=m[g],a=f.tokenStack[i],o="string"==typeof r?r:r.content,s=Ln(h,i),l=o.indexOf(s);if(-1<l){++g;var c=o.substring(0,l),u=new Tn.Token(h,Tn.tokenize(a,f.grammar),"language-"+h,a),p=o.substring(l+s.length),d=[];c&&d.push.apply(d,e([c])),d.push(u),p&&d.push.apply(d,e([p])),"string"==typeof r?t.splice.apply(t,[n,1].concat(d)):r.content=d}}else r.content&&e(r.content)}return t}(f.tokens)}}}});var Rn={},Cn={markdown:function(e){return{url:e}},mermaid:function(e){return{url:e}},iframe:function(e,t){return{html:'<iframe src="'+e+'" '+(t||"width=100% height=400")+"></iframe>"}},video:function(e,t){return{html:'<video src="'+e+'" '+(t||"controls")+">Not Support</video>"}},audio:function(e,t){return{html:'<audio src="'+e+'" '+(t||"controls")+">Not Support</audio>"}},code:function(e,t){var n=e.match(/\.(\w+)$/);return"md"===(n=t||n&&n[1])&&(n="markdown"),{url:e,lang:n}}},$n=function(i,e){var a=this;this.config=i,this.router=e,this.cacheTree={},this.toc=[],this.cacheTOC={},this.linkTarget=i.externalLinkTarget||"_blank",this.linkRel="_blank"===this.linkTarget?i.externalLinkRel||"noopener":"",this.contentBase=e.getBasePath();var o,t=this._initRenderer();this.heading=t.heading;var n=i.markdown||{};o=r(n)?n(mn,t):(mn.setOptions(f(n,{renderer:f(t,n.renderer)})),mn),this._marked=o,this.compile=function(n){var r=!0,e=s(function(e){r=!1;var t="";return n?(t=c(n)?o(n):o.parser(n),t=i.noEmoji?t:function(e){return e.replace(/:\+1:/g,":thumbsup:").replace(/:-1:/g,":thumbsdown:").replace(/<(pre|template|code)[^>]*?>[\s\S]+?<\/(pre|template|code)>/g,function(e){return e.replace(/:/g,"__colon__")}).replace(/:(\w+?):/gi,window.emojify||Sn).replace(/__colon__/g,":")}(t),_n.clear(),t):n})(n),t=a.router.parse().file;return r?a.toc=a.cacheTOC[t]:a.cacheTOC[t]=[].concat(a.toc),e}};$n.prototype.compileEmbed=function(e,t){var n,r=An(t),i=r.str,a=r.config;if(t=i,a.include){var o;if(Q(e)||(e=re(this.contentBase,ee(this.router.getCurrentPath()),e)),a.type&&(o=Cn[a.type]))(n=o.call(this,e,t)).type=a.type;else{var s="code";/\.(md|markdown)/.test(e)?s="markdown":/\.mmd/.test(e)?s="mermaid":/\.html?/.test(e)?s="iframe":/\.(mp4|ogg)/.test(e)?s="video":/\.mp3/.test(e)&&(s="audio"),(n=Cn[s].call(this,e,t)).type=s}return n.fragment=a.fragment,n}},$n.prototype._matchNotCompileLink=function(e){for(var t=this.config.noCompileLinks||[],n=0;n<t.length;n++){var r=t[n];if((Rn[r]||(Rn[r]=new RegExp("^"+r+"$"))).test(e))return e}},$n.prototype._initRenderer=function(){var e=new mn.Renderer,t=this.linkTarget,n=this.linkRel,l=this.router,r=this.contentBase,c=this,i={};return i.heading=e.heading=function(e,t){var n=An(e),r=n.str,i=n.config,a={level:t,title:r};/<!-- {docsify-ignore} -->/g.test(r)&&(r=r.replace("\x3c!-- {docsify-ignore} --\x3e",""),a.title=r,a.ignoreSubHeading=!0),/{docsify-ignore}/g.test(r)&&(r=r.replace("{docsify-ignore}",""),a.title=r,a.ignoreSubHeading=!0),/<!-- {docsify-ignore-all} -->/g.test(r)&&(r=r.replace("\x3c!-- {docsify-ignore-all} --\x3e",""),a.title=r,a.ignoreAllSubs=!0),/{docsify-ignore-all}/g.test(r)&&(r=r.replace("{docsify-ignore-all}",""),a.title=r,a.ignoreAllSubs=!0);var o=_n(i.id||r),s=l.toURL(l.getCurrentPath(),{id:o});return a.slug=s,c.toc.push(a),"<h"+t+' id="'+o+'"><a href="'+s+'" data-id="'+o+'" class="anchor"><span>'+r+"</span></a></h"+t+">"},i.code=function(e){return e.renderer.code=function(e,t){void 0===t&&(t="");var n=En.languages[t]||En.languages.markup;return'<pre v-pre data-lang="'+t+'"><code class="lang-'+t+'">'+En.highlight(e.replace(/@DOCSIFY_QM@/g,"`"),n)+"</code></pre>"}}({renderer:e}),i.link=function(e){var t=e.renderer,s=e.router,l=e.linkTarget,c=e.linkRel,u=e.compilerClass;return t.link=function(e,t,n){void 0===t&&(t="");var r=[],i=An(t),a=i.str,o=i.config;return l=o.target||l,c="_blank"===l?u.config.externalLinkRel||"noopener":"",t=a,Q(e)||u._matchNotCompileLink(e)||o.ignore?(!Q(e)&&e.startsWith("./")&&(e=document.URL.replace(/\/(?!.*\/).*/,"/").replace("#/./","")+e),r.push(0===e.indexOf("mailto:")?"":'target="'+l+'"'),r.push(0===e.indexOf("mailto:")?"":""!==c?' rel="'+c+'"':"")):(e===u.config.homepage&&(e="README"),e=s.toURL(e,null,s.getCurrentPath())),o.crossorgin&&"_self"===l&&"history"===u.config.routerMode&&-1===u.config.crossOriginLinks.indexOf(e)&&u.config.crossOriginLinks.push(e),o.disabled&&(r.push("disabled"),e="javascript:void(0)"),o.class&&r.push('class="'+o.class+'"'),o.id&&r.push('id="'+o.id+'"'),t&&r.push('title="'+t+'"'),'<a href="'+e+'" '+r.join(" ")+">"+n+"</a>"}}({renderer:e,router:l,linkTarget:t,linkRel:n,compilerClass:c}),i.paragraph=function(e){return e.renderer.paragraph=function(e){return/^!>/.test(e)?bn("tip",e):/^\?>/.test(e)?bn("warn",e):"<p>"+e+"</p>"}}({renderer:e}),i.image=function(e){var t=e.renderer,p=e.contentBase,d=e.router;return t.image=function(e,t,n){var r=e,i=[],a=An(t),o=a.str,s=a.config;if(t=o,s["no-zoom"]&&i.push("data-no-zoom"),t&&i.push('title="'+t+'"'),s.size){var l=s.size.split("x"),c=l[0],u=l[1];u?i.push('width="'+c+'" height="'+u+'"'):i.push('width="'+c+'"')}return s.class&&i.push('class="'+s.class+'"'),s.id&&i.push('id="'+s.id+'"'),Q(e)||(r=re(p,ee(d.getCurrentPath()),e)),0<i.length?'<img src="'+r+'" data-origin="'+e+'" alt="'+n+'" '+i.join(" ")+" />":'<img src="'+r+'" data-origin="'+e+'" alt="'+n+'"'+i+">"}}({renderer:e,contentBase:r,router:l}),i.list=function(e){return e.renderer.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+" "+[/<li class="task-list-item">/.test(e.split('class="task-list"')[0])?'class="task-list"':"",n&&1<n?'start="'+n+'"':""].join(" ").trim()+">"+e+"</"+r+">"}}({renderer:e}),i.listitem=function(e){return e.renderer.listitem=function(e){return/^(<input.*type="checkbox"[^>]*>)/.test(e)?'<li class="task-list-item"><label>'+e+"</label></li>":"<li>"+e+"</li>"}}({renderer:e}),e.origin=i,e},$n.prototype.sidebar=function(e,t){var n=this.toc,r=this.router.getCurrentPath(),i="";if(e)i=this.compile(e);else{for(var a=0;a<n.length;a++)if(n[a].ignoreSubHeading){var o=n[a].level;n.splice(a,1);for(var s=a;o<n[s].level&&s<n.length;s++)n.splice(s,1)&&s--&&a++;a--}var l=this.cacheTree[r]||yn(n,t);i=vn(l,"<ul>{inner}</ul>"),this.cacheTree[r]=l}return i},$n.prototype.subSidebar=function(e){if(e){var t=this.router.getCurrentPath(),n=this.cacheTree,r=this.toc;r[0]&&r[0].ignoreAllSubs&&r.splice(0),r[0]&&1===r[0].level&&r.shift();for(var i=0;i<r.length;i++)r[i].ignoreSubHeading&&r.splice(i,1)&&i--;var a=n[t]||yn(r,e);return n[t]=a,this.toc=[],vn(a)}this.toc=[]},$n.prototype.header=function(e,t){return this.heading(e,t)},$n.prototype.article=function(e){return this.compile(e)},$n.prototype.cover=function(e){var t=this.toc.slice(),n=this.compile(e);return this.toc=t.slice(),n};var Fn=function(e){var t=function(e){var t=e.match(/^[ \t]*(?=\S)/gm);return t?Math.min.apply(Math,t.map(function(e){return e.length})):0}(e);if(0===t)return e;var n=new RegExp("^[ \\t]{"+t+"}","gm");return e.replace(n,"")},zn={};function On(e,i){var o=e.compiler,a=e.raw;void 0===a&&(a="");var t=e.fetch,n=zn[a];if(n){var r=n.slice();return r.links=n.links,i(r)}var s=o._marked,l=s.lexer(a),c=[],u=s.Lexer.rules.inline.link,p=l.links;l.forEach(function(e,a){"paragraph"===e.type&&(e.text=e.text.replace(new RegExp(u.source,"g"),function(e,t,n,r){var i=o.compileEmbed(n,r);return i&&c.push({index:a,embed:i}),e}))});var d=[];!function(e,o){var t,n=e.embedTokens,s=e.compile,l=(e.fetch,0),c=1;if(!n.length)return o({});for(;t=n[l++];){var r=function(a){return function(e){var t;if(e)if("markdown"===a.embed.type){var n=a.embed.url.split("/");n.pop(),n=n.join("/"),e=e.replace(/\[([^[\]]+)\]\(([^)]+)\)/g,function(e){var t=e.indexOf("(");return e.substring(t).startsWith("(.")?e.substring(0,t)+"("+window.location.protocol+"//"+window.location.host+n+"/"+e.substring(t+1,e.length-1)+")":e}),!0===(($docsify.frontMatter||{}).installed||!1)&&(e=$docsify.frontMatter.parseMarkdown(e)),t=s.lexer(e)}else if("code"===a.embed.type){if(a.embed.fragment){var r=a.embed.fragment,i=new RegExp("(?:###|\\/\\/\\/)\\s*\\["+r+"\\]([\\s\\S]*)(?:###|\\/\\/\\/)\\s*\\["+r+"\\]");e=Fn((e.match(i)||[])[1]||"").trim()}t=s.lexer("```"+a.embed.lang+"\n"+e.replace(/`/g,"@DOCSIFY_QM@")+"\n```\n")}else"mermaid"===a.embed.type?(t=[{type:"html",text:'<div class="mermaid">\n'+e+"\n</div>"}]).links={}:(t=[{type:"html",text:e}]).links={};o({token:a,embedToken:t}),++c>=l&&o({})}}(t);t.embed.url?q(t.embed.url).then(r):r(t.embed.html)}}({compile:s,embedTokens:c,fetch:t},function(e){var t=e.embedToken,n=e.token;if(n){var r=n.index;d.forEach(function(e){r>e.start&&(r+=e.length)}),f(p,t.links),l=l.slice(0,r).concat(t,l.slice(r+1)),d.push({start:r,length:t.length-1})}else zn[a]=l.concat(),l.links=zn[a].links=p,i(l)})}function Mn(){var e=y(".markdown-section>script").filter(function(e){return!/template/.test(e.type)})[0];if(!e)return!1;var t=e.innerText.trim();if(!t)return!1;setTimeout(function(e){window.__EXECUTE_RESULT__=new Function(t)()},0)}function Nn(e,t,n){return t="function"==typeof n?n(t):"string"==typeof n?function(r,i){var a=[],o=0;return r.replace(T,function(t,e,n){a.push(r.substring(o,n-1)),o=n+=t.length+1,a.push(i&&i[t]||function(e){return("00"+("string"==typeof E[t]?e[E[t]]():E[t](e))).slice(-t.length)})}),o!==r.length&&a.push(r.substring(o)),function(e){for(var t="",n=0,r=e||new Date;n<a.length;n++)t+="string"==typeof a[n]?a[n]:a[n](r);return t}}(n)(new Date(t)):t,e.replace(/{docsify-updated}/g,t)}function Pn(e){e=e||"<h1>404 - Not found</h1>",this._renderTo(".markdown-section",e),this.config.loadSidebar||this._renderSidebar(),!1===this.config.executeScript||void 0===window.Vue||Mn()?this.config.executeScript&&Mn():setTimeout(function(e){var t=window.__EXECUTE_RESULT__;t&&t.$destroy&&t.$destroy(),window.__EXECUTE_RESULT__=(new window.Vue).$mount("#main")},0)}function Dn(e){var t=e.config;e.compiler=new $n(t,e.router),window.__current_docsify_compiler__=e.compiler;var n=t.el||"#app",r=b("nav")||g("nav"),i=b(n),a="",o=v;if(i){if(t.repo&&(a+=function(e,t){return e?(/\/\//.test(e)||(e="https://github.com/"+e),'<a href="'+(e=e.replace(/^git\+/,""))+'" target="'+(t=t||"_blank")+'" class="github-corner" aria-label="View source on Github"><svg viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>'):""}(t.repo,t.cornerExternalLinkTarge)),t.coverpage&&(a+=function(){var e=", 100%, 85%";return'<section class="cover show" style="background: '+("linear-gradient(to left bottom, hsl("+Math.floor(255*Math.random())+e+") 0%,hsl("+Math.floor(255*Math.random())+e+") 100%)")+'"><div class="mask"></div><div class="cover-main">\x3c!--cover--\x3e</div></section>'}()),t.logo){var s=/^data:image/.test(t.logo),l=/(?:http[s]?:)?\/\//.test(t.logo),c=/^\./.test(t.logo);s||l||c||(t.logo=re(e.router.getBasePath(),t.logo))}a+=function(e){var t=e.name?e.name:"";return"<main>"+('<button class="sidebar-toggle" aria-label="Menu"><div class="sidebar-toggle-button"><span></span><span></span><span></span></div></button><aside class="sidebar">'+(e.name?'<h1 class="app-name"><a class="app-name-link" data-nosearch>'+(e.logo?'<img alt="'+t+'" src='+e.logo+">":t)+"</a></h1>":"")+'<div class="sidebar-nav">\x3c!--sidebar--\x3e</div></aside>')+'<section class="content"><article class="markdown-section" id="main">\x3c!--main--\x3e</article></section></main>'}(t),e._renderTo(i,a,!0)}else e.rendered=!0;t.mergeNavbar&&h?o=b(".sidebar"):(r.classList.add("app-nav"),t.repo||r.classList.add("no-badge")),t.loadNavbar&&k(o,r),t.themeColor&&(u.head.appendChild(g("div",function(e){return"<style>:root{--theme-color: "+e+";}</style>"}(t.themeColor)).firstElementChild),function(n){if(!(window.CSS&&window.CSS.supports&&window.CSS.supports("(--v:red)"))){var e=y("style:not(.inserted),link");[].forEach.call(e,function(e){if("STYLE"===e.nodeName)U(e,n);else if("LINK"===e.nodeName){var t=e.getAttribute("href");if(!/\.css$/.test(t))return;q(t).then(function(e){var t=g("style",e);d.appendChild(t),U(t,n)})}})}}(t.themeColor)),e._updateRender(),_(v,"ready")}var jn={};function Hn(e){this.config=e}function In(e){var t=location.href.indexOf("#");location.replace(location.href.slice(0,0<=t?t:0)+"#"+e)}Hn.prototype.getBasePath=function(){return this.config.basePath},Hn.prototype.getFile=function(e,t){void 0===e&&(e=this.getCurrentPath());var n=this.config,r=this.getBasePath(),i="string"==typeof n.ext?n.ext:".md";return e=(e=function(e,t){return new RegExp("\\.("+t.replace(/^\./,"")+"|html)$","g").test(e)?e:/\/$/g.test(e)?e+"README"+t:""+e+t}(e=n.alias?function e(t,n,r){var i=Object.keys(n).filter(function(e){return(jn[e]||(jn[e]=new RegExp("^"+e+"$"))).test(t)&&t!==r})[0];return i?e(t.replace(jn[i],n[i]),n,t):t}(e,n.alias):e,i))==="/README"+i&&n.homepage||e,e=Q(e)?e:re(r,e),t&&(e=e.replace(new RegExp("^"+r),"")),e},Hn.prototype.onchange=function(e){void 0===e&&(e=p),e()},Hn.prototype.getCurrentPath=function(){},Hn.prototype.normalize=function(){},Hn.prototype.parse=function(){},Hn.prototype.toURL=function(e,t,n){var r=n&&"#"===e[0],i=this.parse(ie(e));if(i.query=f({},i.query,t),e=(e=i.path+K(i.query)).replace(/\.md(\?)|\.md$/,"$1"),r){var a=n.indexOf("?");e=(0<a?n.substring(0,a):n)+e}if(this.config.relativePath&&0!==e.indexOf("/")){var o=n.substring(0,n.lastIndexOf("/")+1);return te(ne(o+e))}return te("/"+e)};var qn=function(r){function e(e){r.call(this,e),this.mode="hash"}return r&&(e.__proto__=r),((e.prototype=Object.create(r&&r.prototype)).constructor=e).prototype.getBasePath=function(){var e=window.location.pathname||"",t=this.config.basePath;return/^(\/|https?:)/g.test(t)?t:te(e+"/"+t)},e.prototype.getCurrentPath=function(){var e=location.href,t=e.indexOf("#");return-1===t?"":e.slice(t+1)},e.prototype.onchange=function(n){void 0===n&&(n=p);var r=!1;x("click",function(e){var t="A"===e.target.tagName?e.target:e.target.parentNode;t&&"A"===t.tagName&&!/_blank/.test(t.target)&&(r=!0)}),x("hashchange",function(e){var t=r?"navigate":"history";r=!1,n({event:e,source:t})})},e.prototype.normalize=function(){var e=this.getCurrentPath();if("/"===(e=ie(e)).charAt(0))return In(e);In("/"+e)},e.prototype.parse=function(e){void 0===e&&(e=location.href);var t="",n=e.indexOf("#");0<=n&&(e=e.slice(n+1));var r=e.indexOf("?");return 0<=r&&(t=e.slice(r+1),e=e.slice(0,r)),{path:e,file:this.getFile(e,!0),query:X(t)}},e.prototype.toURL=function(e,t,n){return"#"+r.prototype.toURL.call(this,e,t,n)},e}(Hn),Un=function(t){function e(e){t.call(this,e),this.mode="history"}return t&&(e.__proto__=t),((e.prototype=Object.create(t&&t.prototype)).constructor=e).prototype.getCurrentPath=function(){var e=this.getBasePath(),t=window.location.pathname;return e&&0===t.indexOf(e)&&(t=t.slice(e.length)),(t||"/")+window.location.search+window.location.hash},e.prototype.onchange=function(r){var i=this;void 0===r&&(r=p),x("click",function(e){var t="A"===e.target.tagName?e.target:e.target.parentNode;if("A"===t.tagName&&!/_blank/.test(t.target)){e.preventDefault();var n=t.href;-1!==i.config.crossOriginLinks.indexOf(n)?window.open(n,"_self"):window.history.pushState({key:n},"",n),r({event:e,source:"navigate"})}}),x("popstate",function(e){r({event:e,source:"history"})})},e.prototype.parse=function(e){void 0===e&&(e=location.href);var t="",n=e.indexOf("?");0<=n&&(t=e.slice(n+1),e=e.slice(0,n));var r=re(location.origin),i=e.indexOf(r);return-1<i&&(e=e.slice(i+r.length)),{path:e,file:this.getFile(e),query:X(t)}},e}(Hn);var Bn={};function Zn(e){e.router.normalize(),e.route=e.router.parse(),v.setAttribute("data-page",e.route.file)}function Wn(e){!function(e){function t(e){return v.classList.toggle("close")}null!=(e=m(e))&&(x(e,"click",function(e){e.stopPropagation(),t()}),h&&x(v,"click",function(e){return v.classList.contains("close")&&t()}))}("button.sidebar-toggle",e.router),function(e){null!=(e=m(e))&&x(e,"click",function(e){var t=e.target;"A"===t.nodeName&&t.nextSibling&&t.nextSibling.classList&&t.nextSibling.classList.contains("app-sub-sidebar")&&_(t.parentNode,"collapse")})}(".sidebar",e.router),e.config.coverpage?h||x("scroll",W):v.classList.add("sticky")}function Yn(t,n,r,i,a,e){t=e?t:t.replace(/\/$/,""),(t=ee(t))&&q(a.router.getFile(t+r)+n,!1,a.config.requestHeaders).then(i,function(e){return Yn(t,n,r,i,a)})}var Gn=Object.freeze({__proto__:null,cached:s,hyphenate:a,hasOwn:l,merge:f,isPrimitive:c,noop:p,isFn:r,inBrowser:!0,isMobile:h,supportsPushState:i,parseQuery:X,stringifyQuery:K,isAbsolutePath:Q,removeParams:J,getParentPath:ee,cleanPath:te,resolvePath:ne,getPath:re,replaceSlug:ie});function Vn(){this._init()}var Xn,Kn,Qn,Jn=Vn.prototype;function er(e,t,n){return Qn&&Qn.abort&&Qn.abort(),Qn=q(e,!0,n)}Jn._init=function(){var e=this;e.config=A(e),function(n){n._hooks={},n._lifecycle={},["init","mounted","beforeEach","afterEach","doneEach","ready"].forEach(function(e){var t=n._hooks[e]=[];n._lifecycle[e]=function(e){return t.push(e)}})}(e),function(t){[].concat(t.config.plugins).forEach(function(e){return r(e)&&e(t._lifecycle,t)})}(e),B(e,"init"),function(t){var e,n=t.config;e="history"===(n.routerMode||"hash")&&i?new Un(n):new qn(n),t.router=e,Zn(t),Bn=t.route,e.onchange(function(e){Zn(t),t._updateRender(),Bn.path!==t.route.path?(t.$fetch(p,t.$resetEvents.bind(t,e.source)),Bn=t.route):t.$resetEvents(e.source)})}(e),Dn(e),Wn(e),function(t){var e=t.config.loadSidebar;if(t.rendered){var n=Y(t.router,".sidebar-nav",!0,!0);e&&n&&(n.parentNode.innerHTML+=window.__SUB_SIDEBAR__),t._bindEventOnRendered(n),t.$resetEvents(),B(t,"doneEach"),B(t,"ready")}else t.$fetch(function(e){return B(t,"ready")})}(e),B(e,"mounted")},Jn.route={},(Xn=Jn)._renderTo=function(e,t,n){var r=m(e);r&&(r[n?"outerHTML":"innerHTML"]=t)},Xn._renderSidebar=function(e){var t=this.config,n=t.maxLevel,r=t.subMaxLevel,i=t.loadSidebar;if(t.hideSidebar)return document.querySelector("aside.sidebar").remove(),document.querySelector("button.sidebar-toggle").remove(),document.querySelector("section.content").style.right="unset",document.querySelector("section.content").style.left="unset",document.querySelector("section.content").style.position="relative",document.querySelector("section.content").style.width="100%",null;this._renderTo(".sidebar-nav",this.compiler.sidebar(e,n));var a=Y(this.router,".sidebar-nav",!0,!0);i&&a?a.parentNode.innerHTML+=this.compiler.subSidebar(r)||"":this.compiler.subSidebar(),this._bindEventOnRendered(a)},Xn._bindEventOnRendered=function(e){var t=this.config.autoHeader;if(function(e){var t=b(".cover.show");ge=t?t.offsetHeight:0;var n=m(".sidebar"),r=[];null!=n&&(r=y(n,"li"));for(var i=0,a=r.length;i<a;i+=1){var o=r[i],s=o.querySelector("a");if(s){var l=s.getAttribute("href");if("/"!==l){var c=e.parse(l),u=c.query.id,p=c.path;u&&(l=ve(p,u))}l&&(pe[decodeURIComponent(l)]=o)}}if(!h){var d=J(e.getCurrentPath());w("scroll",function(){return me(d)}),x("scroll",function(){return me(d)}),x(n,"mouseover",function(){de=!0}),x(n,"mouseleave",function(){de=!1})}}(this.router),t&&e){var n=m("#main"),r=n.children[0];r&&"H1"!==r.tagName&&k(n,g("div",this.compiler.header(e.innerText,1)).children[0])}},Xn._renderNav=function(e){e&&this._renderTo("nav",this.compiler.compile(e)),this.config.loadNavbar&&Y(this.router,"nav")},Xn._renderMain=function(r,i,a){var o=this;if(void 0===i&&(i={}),!r)return Pn.call(this,r);B(this,"beforeEach",r,function(e){function t(){i.updatedAt&&(n=Nn(n,i.updatedAt,o.config.formatUpdated)),B(o,"afterEach",n,function(e){return Pn.call(o,e)})}var n;o.isHTML?(n=o.result=r,t(),a()):On({compiler:o.compiler,raw:e},function(e){n=o.compiler.compile(e),n=o.isRemoteUrl?j.sanitize(n):n,t(),a()})})},Xn._renderCover=function(e,t){var n=m(".cover");if(_(m("main"),t?"add":"remove","hidden"),e){_(n,"add","show");var r=this.coverIsHTML?e:this.compiler.cover(e),i=r.trim().match('<p><img.*?data-origin="(.*?)"[^a]+alt="(.*?)">([^<]*?)</p>$');if(i){if("color"===i[2])n.style.background=i[1]+(i[3]||"");else{var a=i[1];_(n,"add","has-mask"),Q(i[1])||(a=re(this.router.getBasePath(),i[1])),n.style.backgroundImage="url("+a+")",n.style.backgroundSize="cover",n.style.backgroundPosition="center center"}r=r.replace(i[0],"")}this._renderTo(".cover-main",r),W()}else _(n,"remove","show")},Xn._updateRender=function(){!function(e){var t=m(".app-name-link"),n=e.config.nameLink,r=e.route.path;if(t)if(c(e.config.nameLink))t.setAttribute("href",n);else if("object"==typeof n){var i=Object.keys(n).filter(function(e){return-1<r.indexOf(e)})[0];t.setAttribute("href",n[i])}}(this)},(Kn=Jn)._loadSideAndNav=function(e,t,n,r){var i=this;return function(){if(!n)return r();Yn(e,t,n,function(e){i._renderSidebar(e),r()},i,!0)}},Kn._fetch=function(n){var r=this;void 0===n&&(n=p);var e=this.route,i=e.path,a=K(e.query,["id"]),t=this.config,o=t.loadNavbar,s=t.requestHeaders,l=t.loadSidebar,c=this.router.getFile(i),u=er(c+a,0,s);this.isRemoteUrl=function(e){var t=e.match(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/);return"string"==typeof t[1]&&0<t[1].length&&t[1].toLowerCase()!==location.protocol||"string"==typeof t[2]&&0<t[2].length&&t[2].replace(new RegExp(":("+{"http:":80,"https:":443}[location.protocol]+")?$"),"")!==location.host}(c),this.isHTML=/\.html$/g.test(c),u.then(function(e,t){return r._renderMain(e,t,r._loadSideAndNav(i,a,l,n))},function(e){r._fetchFallbackPage(i,a,n)||r._fetch404(c,a,n)}),o&&Yn(i,a,o,function(e){return r._renderNav(e)},this,!0)},Kn._fetchCover=function(){var t=this,e=this.config,n=e.coverpage,r=e.requestHeaders,i=this.route.query,a=ee(this.route.path);if(n){var o=null,s=this.route.path;if("string"==typeof n)"/"===s&&(o=n);else if(Array.isArray(n))o=-1<n.indexOf(s)&&"_coverpage";else{var l=n[s];o=!0===l?"_coverpage":l}var c=Boolean(o)&&this.config.onlyCover;return o?(o=this.router.getFile(a+o),this.coverIsHTML=/\.html$/g.test(o),q(o+K(i,["id"]),!1,r).then(function(e){return t._renderCover(e,c)})):this._renderCover(null,c),c}},Kn.$fetch=function(e,t){var n=this;function r(){B(n,"doneEach"),e()}void 0===e&&(e=p),void 0===t&&(t=this.$resetEvents.bind(this)),this._fetchCover()?r():this._fetch(function(){t(),r()})},Kn._fetchFallbackPage=function(n,r,i){var a=this;void 0===i&&(i=p);var e=this.config,t=e.requestHeaders,o=e.fallbackLanguages,s=e.loadSidebar;if(!o)return!1;var l=n.split("/")[1];if(-1===o.indexOf(l))return!1;var c=this.router.getFile(n.replace(new RegExp("^/"+l),""));return er(c+r,0,t).then(function(e,t){return a._renderMain(e,t,a._loadSideAndNav(n,r,s,i))},function(){return a._fetch404(n,r,i)}),!0},Kn._fetch404=function(e,t,n){var r=this;void 0===n&&(n=p);var i=this.config,a=i.loadSidebar,o=i.requestHeaders,s=i.notFoundPage,l=this._loadSideAndNav(e,t,a,n);if(s){var c=function(t,e){var n,r,i=e.notFoundPage,a="_404"+(e.ext||".md");switch(typeof i){case"boolean":r=a;break;case"string":r=i;break;case"object":r=(n=Object.keys(i).sort(function(e,t){return t.length-e.length}).find(function(e){return t.match(new RegExp("^"+e))}))&&i[n]||a}return r}(e,this.config);return er(this.router.getFile(c),0,o).then(function(e,t){return r._renderMain(e,t,l)},function(){return r._renderMain(null,{},l)}),!0}return this._renderMain(null,{},l),!1},Jn.$resetEvents=function(e){var t=this,n=this.config.auto2top;"history"!==e&&(t.route.query.id&&be(t.route.path,t.route.query.id),"navigate"===e&&n&&function(e){void 0===e&&(e=0),ye.scrollTop=!0===e?0:Number(e)}(n)),this.config.loadNavbar&&Y(this.router,"nav")},window.Docsify={util:Gn,dom:t,get:q,slugify:_n,version:"4.11.6"},window.DocsifyCompiler=$n,window.marked=mn,window.Prism=En,e(function(e){return new Vn})}(); diff --git a/_scripts/emoji.min.js b/_scripts/emoji.min.js new file mode 100644 index 0000000000000000000000000000000000000000..af23090e90883f83516e9d9f87f109be2a874aeb --- /dev/null +++ b/_scripts/emoji.min.js @@ -0,0 +1 @@ +!function(){var o={100:"unicode/1f4af",1234:"unicode/1f522","+1":"unicode/1f44d","-1":"unicode/1f44e","1st_place_medal":"unicode/1f947","2nd_place_medal":"unicode/1f948","3rd_place_medal":"unicode/1f949","8ball":"unicode/1f3b1",a:"unicode/1f170",ab:"unicode/1f18e",abacus:"unicode/1f9ee",abc:"unicode/1f524",abcd:"unicode/1f521",accept:"unicode/1f251",adhesive_bandage:"unicode/1fa79",adult:"unicode/1f9d1",aerial_tramway:"unicode/1f6a1",afghanistan:"unicode/1f1e6-1f1eb",airplane:"unicode/2708",aland_islands:"unicode/1f1e6-1f1fd",alarm_clock:"unicode/23f0",albania:"unicode/1f1e6-1f1f1",alembic:"unicode/2697",algeria:"unicode/1f1e9-1f1ff",alien:"unicode/1f47d",ambulance:"unicode/1f691",american_samoa:"unicode/1f1e6-1f1f8",amphora:"unicode/1f3fa",anchor:"unicode/2693",andorra:"unicode/1f1e6-1f1e9",angel:"unicode/1f47c",anger:"unicode/1f4a2",angola:"unicode/1f1e6-1f1f4",angry:"unicode/1f620",anguilla:"unicode/1f1e6-1f1ee",anguished:"unicode/1f627",ant:"unicode/1f41c",antarctica:"unicode/1f1e6-1f1f6",antigua_barbuda:"unicode/1f1e6-1f1ec",apple:"unicode/1f34e",aquarius:"unicode/2652",argentina:"unicode/1f1e6-1f1f7",aries:"unicode/2648",armenia:"unicode/1f1e6-1f1f2",arrow_backward:"unicode/25c0",arrow_double_down:"unicode/23ec",arrow_double_up:"unicode/23eb",arrow_down:"unicode/2b07",arrow_down_small:"unicode/1f53d",arrow_forward:"unicode/25b6",arrow_heading_down:"unicode/2935",arrow_heading_up:"unicode/2934",arrow_left:"unicode/2b05",arrow_lower_left:"unicode/2199",arrow_lower_right:"unicode/2198",arrow_right:"unicode/27a1",arrow_right_hook:"unicode/21aa",arrow_up:"unicode/2b06",arrow_up_down:"unicode/2195",arrow_up_small:"unicode/1f53c",arrow_upper_left:"unicode/2196",arrow_upper_right:"unicode/2197",arrows_clockwise:"unicode/1f503",arrows_counterclockwise:"unicode/1f504",art:"unicode/1f3a8",articulated_lorry:"unicode/1f69b",artificial_satellite:"unicode/1f6f0",artist:"unicode/1f9d1-1f3a8",aruba:"unicode/1f1e6-1f1fc",ascension_island:"unicode/1f1e6-1f1e8",asterisk:"unicode/002a-20e3",astonished:"unicode/1f632",astronaut:"unicode/1f9d1-1f680",athletic_shoe:"unicode/1f45f",atm:"unicode/1f3e7",atom:"atom",atom_symbol:"unicode/269b",australia:"unicode/1f1e6-1f1fa",austria:"unicode/1f1e6-1f1f9",auto_rickshaw:"unicode/1f6fa",avocado:"unicode/1f951",axe:"unicode/1fa93",azerbaijan:"unicode/1f1e6-1f1ff",b:"unicode/1f171",baby:"unicode/1f476",baby_bottle:"unicode/1f37c",baby_chick:"unicode/1f424",baby_symbol:"unicode/1f6bc",back:"unicode/1f519",bacon:"unicode/1f953",badger:"unicode/1f9a1",badminton:"unicode/1f3f8",bagel:"unicode/1f96f",baggage_claim:"unicode/1f6c4",baguette_bread:"unicode/1f956",bahamas:"unicode/1f1e7-1f1f8",bahrain:"unicode/1f1e7-1f1ed",balance_scale:"unicode/2696",bald_man:"unicode/1f468-1f9b2",bald_woman:"unicode/1f469-1f9b2",ballet_shoes:"unicode/1fa70",balloon:"unicode/1f388",ballot_box:"unicode/1f5f3",ballot_box_with_check:"unicode/2611",bamboo:"unicode/1f38d",banana:"unicode/1f34c",bangbang:"unicode/203c",bangladesh:"unicode/1f1e7-1f1e9",banjo:"unicode/1fa95",bank:"unicode/1f3e6",bar_chart:"unicode/1f4ca",barbados:"unicode/1f1e7-1f1e7",barber:"unicode/1f488",baseball:"unicode/26be",basecamp:"basecamp",basecampy:"basecampy",basket:"unicode/1f9fa",basketball:"unicode/1f3c0",basketball_man:"unicode/26f9-2642",basketball_woman:"unicode/26f9-2640",bat:"unicode/1f987",bath:"unicode/1f6c0",bathtub:"unicode/1f6c1",battery:"unicode/1f50b",beach_umbrella:"unicode/1f3d6",bear:"unicode/1f43b",bearded_person:"unicode/1f9d4",bed:"unicode/1f6cf",bee:"unicode/1f41d",beer:"unicode/1f37a",beers:"unicode/1f37b",beetle:"unicode/1f41e",beginner:"unicode/1f530",belarus:"unicode/1f1e7-1f1fe",belgium:"unicode/1f1e7-1f1ea",belize:"unicode/1f1e7-1f1ff",bell:"unicode/1f514",bellhop_bell:"unicode/1f6ce",benin:"unicode/1f1e7-1f1ef",bento:"unicode/1f371",bermuda:"unicode/1f1e7-1f1f2",beverage_box:"unicode/1f9c3",bhutan:"unicode/1f1e7-1f1f9",bicyclist:"unicode/1f6b4",bike:"unicode/1f6b2",biking_man:"unicode/1f6b4-2642",biking_woman:"unicode/1f6b4-2640",bikini:"unicode/1f459",billed_cap:"unicode/1f9e2",biohazard:"unicode/2623",bird:"unicode/1f426",birthday:"unicode/1f382",black_circle:"unicode/26ab",black_flag:"unicode/1f3f4",black_heart:"unicode/1f5a4",black_joker:"unicode/1f0cf",black_large_square:"unicode/2b1b",black_medium_small_square:"unicode/25fe",black_medium_square:"unicode/25fc",black_nib:"unicode/2712",black_small_square:"unicode/25aa",black_square_button:"unicode/1f532",blond_haired_man:"unicode/1f471-2642",blond_haired_person:"unicode/1f471",blond_haired_woman:"unicode/1f471-2640",blonde_woman:"unicode/1f471-2640",blossom:"unicode/1f33c",blowfish:"unicode/1f421",blue_book:"unicode/1f4d8",blue_car:"unicode/1f699",blue_heart:"unicode/1f499",blue_square:"unicode/1f7e6",blush:"unicode/1f60a",boar:"unicode/1f417",boat:"unicode/26f5",bolivia:"unicode/1f1e7-1f1f4",bomb:"unicode/1f4a3",bone:"unicode/1f9b4",book:"unicode/1f4d6",bookmark:"unicode/1f516",bookmark_tabs:"unicode/1f4d1",books:"unicode/1f4da",boom:"unicode/1f4a5",boot:"unicode/1f462",bosnia_herzegovina:"unicode/1f1e7-1f1e6",botswana:"unicode/1f1e7-1f1fc",bouncing_ball_man:"unicode/26f9-2642",bouncing_ball_person:"unicode/26f9",bouncing_ball_woman:"unicode/26f9-2640",bouquet:"unicode/1f490",bouvet_island:"unicode/1f1e7-1f1fb",bow:"unicode/1f647",bow_and_arrow:"unicode/1f3f9",bowing_man:"unicode/1f647-2642",bowing_woman:"unicode/1f647-2640",bowl_with_spoon:"unicode/1f963",bowling:"unicode/1f3b3",bowtie:"bowtie",boxing_glove:"unicode/1f94a",boy:"unicode/1f466",brain:"unicode/1f9e0",brazil:"unicode/1f1e7-1f1f7",bread:"unicode/1f35e",breast_feeding:"unicode/1f931",bricks:"unicode/1f9f1",bride_with_veil:"unicode/1f470",bridge_at_night:"unicode/1f309",briefcase:"unicode/1f4bc",british_indian_ocean_territory:"unicode/1f1ee-1f1f4",british_virgin_islands:"unicode/1f1fb-1f1ec",broccoli:"unicode/1f966",broken_heart:"unicode/1f494",broom:"unicode/1f9f9",brown_circle:"unicode/1f7e4",brown_heart:"unicode/1f90e",brown_square:"unicode/1f7eb",brunei:"unicode/1f1e7-1f1f3",bug:"unicode/1f41b",building_construction:"unicode/1f3d7",bulb:"unicode/1f4a1",bulgaria:"unicode/1f1e7-1f1ec",bullettrain_front:"unicode/1f685",bullettrain_side:"unicode/1f684",burkina_faso:"unicode/1f1e7-1f1eb",burrito:"unicode/1f32f",burundi:"unicode/1f1e7-1f1ee",bus:"unicode/1f68c",business_suit_levitating:"unicode/1f574",busstop:"unicode/1f68f",bust_in_silhouette:"unicode/1f464",busts_in_silhouette:"unicode/1f465",butter:"unicode/1f9c8",butterfly:"unicode/1f98b",cactus:"unicode/1f335",cake:"unicode/1f370",calendar:"unicode/1f4c6",call_me_hand:"unicode/1f919",calling:"unicode/1f4f2",cambodia:"unicode/1f1f0-1f1ed",camel:"unicode/1f42b",camera:"unicode/1f4f7",camera_flash:"unicode/1f4f8",cameroon:"unicode/1f1e8-1f1f2",camping:"unicode/1f3d5",canada:"unicode/1f1e8-1f1e6",canary_islands:"unicode/1f1ee-1f1e8",cancer:"unicode/264b",candle:"unicode/1f56f",candy:"unicode/1f36c",canned_food:"unicode/1f96b",canoe:"unicode/1f6f6",cape_verde:"unicode/1f1e8-1f1fb",capital_abcd:"unicode/1f520",capricorn:"unicode/2651",car:"unicode/1f697",card_file_box:"unicode/1f5c3",card_index:"unicode/1f4c7",card_index_dividers:"unicode/1f5c2",caribbean_netherlands:"unicode/1f1e7-1f1f6",carousel_horse:"unicode/1f3a0",carrot:"unicode/1f955",cartwheeling:"unicode/1f938",cat:"unicode/1f431",cat2:"unicode/1f408",cayman_islands:"unicode/1f1f0-1f1fe",cd:"unicode/1f4bf",central_african_republic:"unicode/1f1e8-1f1eb",ceuta_melilla:"unicode/1f1ea-1f1e6",chad:"unicode/1f1f9-1f1e9",chains:"unicode/26d3",chair:"unicode/1fa91",champagne:"unicode/1f37e",chart:"unicode/1f4b9",chart_with_downwards_trend:"unicode/1f4c9",chart_with_upwards_trend:"unicode/1f4c8",checkered_flag:"unicode/1f3c1",cheese:"unicode/1f9c0",cherries:"unicode/1f352",cherry_blossom:"unicode/1f338",chess_pawn:"unicode/265f",chestnut:"unicode/1f330",chicken:"unicode/1f414",child:"unicode/1f9d2",children_crossing:"unicode/1f6b8",chile:"unicode/1f1e8-1f1f1",chipmunk:"unicode/1f43f",chocolate_bar:"unicode/1f36b",chopsticks:"unicode/1f962",christmas_island:"unicode/1f1e8-1f1fd",christmas_tree:"unicode/1f384",church:"unicode/26ea",cinema:"unicode/1f3a6",circus_tent:"unicode/1f3aa",city_sunrise:"unicode/1f307",city_sunset:"unicode/1f306",cityscape:"unicode/1f3d9",cl:"unicode/1f191",clamp:"unicode/1f5dc",clap:"unicode/1f44f",clapper:"unicode/1f3ac",classical_building:"unicode/1f3db",climbing:"unicode/1f9d7",climbing_man:"unicode/1f9d7-2642",climbing_woman:"unicode/1f9d7-2640",clinking_glasses:"unicode/1f942",clipboard:"unicode/1f4cb",clipperton_island:"unicode/1f1e8-1f1f5",clock1:"unicode/1f550",clock10:"unicode/1f559",clock1030:"unicode/1f565",clock11:"unicode/1f55a",clock1130:"unicode/1f566",clock12:"unicode/1f55b",clock1230:"unicode/1f567",clock130:"unicode/1f55c",clock2:"unicode/1f551",clock230:"unicode/1f55d",clock3:"unicode/1f552",clock330:"unicode/1f55e",clock4:"unicode/1f553",clock430:"unicode/1f55f",clock5:"unicode/1f554",clock530:"unicode/1f560",clock6:"unicode/1f555",clock630:"unicode/1f561",clock7:"unicode/1f556",clock730:"unicode/1f562",clock8:"unicode/1f557",clock830:"unicode/1f563",clock9:"unicode/1f558",clock930:"unicode/1f564",closed_book:"unicode/1f4d5",closed_lock_with_key:"unicode/1f510",closed_umbrella:"unicode/1f302",cloud:"unicode/2601",cloud_with_lightning:"unicode/1f329",cloud_with_lightning_and_rain:"unicode/26c8",cloud_with_rain:"unicode/1f327",cloud_with_snow:"unicode/1f328",clown_face:"unicode/1f921",clubs:"unicode/2663",cn:"unicode/1f1e8-1f1f3",coat:"unicode/1f9e5",cocktail:"unicode/1f378",coconut:"unicode/1f965",cocos_islands:"unicode/1f1e8-1f1e8",coffee:"unicode/2615",coffin:"unicode/26b0",cold_face:"unicode/1f976",cold_sweat:"unicode/1f630",collision:"unicode/1f4a5",colombia:"unicode/1f1e8-1f1f4",comet:"unicode/2604",comoros:"unicode/1f1f0-1f1f2",compass:"unicode/1f9ed",computer:"unicode/1f4bb",computer_mouse:"unicode/1f5b1",confetti_ball:"unicode/1f38a",confounded:"unicode/1f616",confused:"unicode/1f615",congo_brazzaville:"unicode/1f1e8-1f1ec",congo_kinshasa:"unicode/1f1e8-1f1e9",congratulations:"unicode/3297",construction:"unicode/1f6a7",construction_worker:"unicode/1f477",construction_worker_man:"unicode/1f477-2642",construction_worker_woman:"unicode/1f477-2640",control_knobs:"unicode/1f39b",convenience_store:"unicode/1f3ea",cook:"unicode/1f9d1-1f373",cook_islands:"unicode/1f1e8-1f1f0",cookie:"unicode/1f36a",cool:"unicode/1f192",cop:"unicode/1f46e",copyright:"unicode/00a9",corn:"unicode/1f33d",costa_rica:"unicode/1f1e8-1f1f7",cote_divoire:"unicode/1f1e8-1f1ee",couch_and_lamp:"unicode/1f6cb",couple:"unicode/1f46b",couple_with_heart:"unicode/1f491",couple_with_heart_man_man:"unicode/1f468-2764-1f468",couple_with_heart_woman_man:"unicode/1f469-2764-1f468",couple_with_heart_woman_woman:"unicode/1f469-2764-1f469",couplekiss:"unicode/1f48f",couplekiss_man_man:"unicode/1f468-2764-1f48b-1f468",couplekiss_man_woman:"unicode/1f469-2764-1f48b-1f468",couplekiss_woman_woman:"unicode/1f469-2764-1f48b-1f469",cow:"unicode/1f42e",cow2:"unicode/1f404",cowboy_hat_face:"unicode/1f920",crab:"unicode/1f980",crayon:"unicode/1f58d",credit_card:"unicode/1f4b3",crescent_moon:"unicode/1f319",cricket:"unicode/1f997",cricket_game:"unicode/1f3cf",croatia:"unicode/1f1ed-1f1f7",crocodile:"unicode/1f40a",croissant:"unicode/1f950",crossed_fingers:"unicode/1f91e",crossed_flags:"unicode/1f38c",crossed_swords:"unicode/2694",crown:"unicode/1f451",cry:"unicode/1f622",crying_cat_face:"unicode/1f63f",crystal_ball:"unicode/1f52e",cuba:"unicode/1f1e8-1f1fa",cucumber:"unicode/1f952",cup_with_straw:"unicode/1f964",cupcake:"unicode/1f9c1",cupid:"unicode/1f498",curacao:"unicode/1f1e8-1f1fc",curling_stone:"unicode/1f94c",curly_haired_man:"unicode/1f468-1f9b1",curly_haired_woman:"unicode/1f469-1f9b1",curly_loop:"unicode/27b0",currency_exchange:"unicode/1f4b1",curry:"unicode/1f35b",cursing_face:"unicode/1f92c",custard:"unicode/1f36e",customs:"unicode/1f6c3",cut_of_meat:"unicode/1f969",cyclone:"unicode/1f300",cyprus:"unicode/1f1e8-1f1fe",czech_republic:"unicode/1f1e8-1f1ff",dagger:"unicode/1f5e1",dancer:"unicode/1f483",dancers:"unicode/1f46f",dancing_men:"unicode/1f46f-2642",dancing_women:"unicode/1f46f-2640",dango:"unicode/1f361",dark_sunglasses:"unicode/1f576",dart:"unicode/1f3af",dash:"unicode/1f4a8",date:"unicode/1f4c5",de:"unicode/1f1e9-1f1ea",deaf_man:"unicode/1f9cf-2642",deaf_person:"unicode/1f9cf",deaf_woman:"unicode/1f9cf-2640",deciduous_tree:"unicode/1f333",deer:"unicode/1f98c",denmark:"unicode/1f1e9-1f1f0",department_store:"unicode/1f3ec",derelict_house:"unicode/1f3da",desert:"unicode/1f3dc",desert_island:"unicode/1f3dd",desktop_computer:"unicode/1f5a5",detective:"unicode/1f575",diamond_shape_with_a_dot_inside:"unicode/1f4a0",diamonds:"unicode/2666",diego_garcia:"unicode/1f1e9-1f1ec",disappointed:"unicode/1f61e",disappointed_relieved:"unicode/1f625",diving_mask:"unicode/1f93f",diya_lamp:"unicode/1fa94",dizzy:"unicode/1f4ab",dizzy_face:"unicode/1f635",djibouti:"unicode/1f1e9-1f1ef",dna:"unicode/1f9ec",do_not_litter:"unicode/1f6af",dog:"unicode/1f436",dog2:"unicode/1f415",dollar:"unicode/1f4b5",dolls:"unicode/1f38e",dolphin:"unicode/1f42c",dominica:"unicode/1f1e9-1f1f2",dominican_republic:"unicode/1f1e9-1f1f4",door:"unicode/1f6aa",doughnut:"unicode/1f369",dove:"unicode/1f54a",dragon:"unicode/1f409",dragon_face:"unicode/1f432",dress:"unicode/1f457",dromedary_camel:"unicode/1f42a",drooling_face:"unicode/1f924",drop_of_blood:"unicode/1fa78",droplet:"unicode/1f4a7",drum:"unicode/1f941",duck:"unicode/1f986",dumpling:"unicode/1f95f",dvd:"unicode/1f4c0","e-mail":"unicode/1f4e7",eagle:"unicode/1f985",ear:"unicode/1f442",ear_of_rice:"unicode/1f33e",ear_with_hearing_aid:"unicode/1f9bb",earth_africa:"unicode/1f30d",earth_americas:"unicode/1f30e",earth_asia:"unicode/1f30f",ecuador:"unicode/1f1ea-1f1e8",egg:"unicode/1f95a",eggplant:"unicode/1f346",egypt:"unicode/1f1ea-1f1ec",eight:"unicode/0038-20e3",eight_pointed_black_star:"unicode/2734",eight_spoked_asterisk:"unicode/2733",eject_button:"unicode/23cf",el_salvador:"unicode/1f1f8-1f1fb",electric_plug:"unicode/1f50c",electron:"electron",elephant:"unicode/1f418",elf:"unicode/1f9dd",elf_man:"unicode/1f9dd-2642",elf_woman:"unicode/1f9dd-2640",email:"unicode/2709",end:"unicode/1f51a",england:"unicode/1f3f4-e0067-e0062-e0065-e006e-e0067-e007f",envelope:"unicode/2709",envelope_with_arrow:"unicode/1f4e9",equatorial_guinea:"unicode/1f1ec-1f1f6",eritrea:"unicode/1f1ea-1f1f7",es:"unicode/1f1ea-1f1f8",estonia:"unicode/1f1ea-1f1ea",ethiopia:"unicode/1f1ea-1f1f9",eu:"unicode/1f1ea-1f1fa",euro:"unicode/1f4b6",european_castle:"unicode/1f3f0",european_post_office:"unicode/1f3e4",european_union:"unicode/1f1ea-1f1fa",evergreen_tree:"unicode/1f332",exclamation:"unicode/2757",exploding_head:"unicode/1f92f",expressionless:"unicode/1f611",eye:"unicode/1f441",eye_speech_bubble:"unicode/1f441-1f5e8",eyeglasses:"unicode/1f453",eyes:"unicode/1f440",face_with_head_bandage:"unicode/1f915",face_with_thermometer:"unicode/1f912",facepalm:"unicode/1f926",facepunch:"unicode/1f44a",factory:"unicode/1f3ed",factory_worker:"unicode/1f9d1-1f3ed",fairy:"unicode/1f9da",fairy_man:"unicode/1f9da-2642",fairy_woman:"unicode/1f9da-2640",falafel:"unicode/1f9c6",falkland_islands:"unicode/1f1eb-1f1f0",fallen_leaf:"unicode/1f342",family:"unicode/1f46a",family_man_boy:"unicode/1f468-1f466",family_man_boy_boy:"unicode/1f468-1f466-1f466",family_man_girl:"unicode/1f468-1f467",family_man_girl_boy:"unicode/1f468-1f467-1f466",family_man_girl_girl:"unicode/1f468-1f467-1f467",family_man_man_boy:"unicode/1f468-1f468-1f466",family_man_man_boy_boy:"unicode/1f468-1f468-1f466-1f466",family_man_man_girl:"unicode/1f468-1f468-1f467",family_man_man_girl_boy:"unicode/1f468-1f468-1f467-1f466",family_man_man_girl_girl:"unicode/1f468-1f468-1f467-1f467",family_man_woman_boy:"unicode/1f468-1f469-1f466",family_man_woman_boy_boy:"unicode/1f468-1f469-1f466-1f466",family_man_woman_girl:"unicode/1f468-1f469-1f467",family_man_woman_girl_boy:"unicode/1f468-1f469-1f467-1f466",family_man_woman_girl_girl:"unicode/1f468-1f469-1f467-1f467",family_woman_boy:"unicode/1f469-1f466",family_woman_boy_boy:"unicode/1f469-1f466-1f466",family_woman_girl:"unicode/1f469-1f467",family_woman_girl_boy:"unicode/1f469-1f467-1f466",family_woman_girl_girl:"unicode/1f469-1f467-1f467",family_woman_woman_boy:"unicode/1f469-1f469-1f466",family_woman_woman_boy_boy:"unicode/1f469-1f469-1f466-1f466",family_woman_woman_girl:"unicode/1f469-1f469-1f467",family_woman_woman_girl_boy:"unicode/1f469-1f469-1f467-1f466",family_woman_woman_girl_girl:"unicode/1f469-1f469-1f467-1f467",farmer:"unicode/1f9d1-1f33e",faroe_islands:"unicode/1f1eb-1f1f4",fast_forward:"unicode/23e9",fax:"unicode/1f4e0",fearful:"unicode/1f628",feelsgood:"feelsgood",feet:"unicode/1f43e",female_detective:"unicode/1f575-2640",female_sign:"unicode/2640",ferris_wheel:"unicode/1f3a1",ferry:"unicode/26f4",field_hockey:"unicode/1f3d1",fiji:"unicode/1f1eb-1f1ef",file_cabinet:"unicode/1f5c4",file_folder:"unicode/1f4c1",film_projector:"unicode/1f4fd",film_strip:"unicode/1f39e",finland:"unicode/1f1eb-1f1ee",finnadie:"finnadie",fire:"unicode/1f525",fire_engine:"unicode/1f692",fire_extinguisher:"unicode/1f9ef",firecracker:"unicode/1f9e8",firefighter:"unicode/1f9d1-1f692",fireworks:"unicode/1f386",first_quarter_moon:"unicode/1f313",first_quarter_moon_with_face:"unicode/1f31b",fish:"unicode/1f41f",fish_cake:"unicode/1f365",fishing_pole_and_fish:"unicode/1f3a3",fist:"unicode/270a",fist_left:"unicode/1f91b",fist_oncoming:"unicode/1f44a",fist_raised:"unicode/270a",fist_right:"unicode/1f91c",five:"unicode/0035-20e3",flags:"unicode/1f38f",flamingo:"unicode/1f9a9",flashlight:"unicode/1f526",flat_shoe:"unicode/1f97f",fleur_de_lis:"unicode/269c",flight_arrival:"unicode/1f6ec",flight_departure:"unicode/1f6eb",flipper:"unicode/1f42c",floppy_disk:"unicode/1f4be",flower_playing_cards:"unicode/1f3b4",flushed:"unicode/1f633",flying_disc:"unicode/1f94f",flying_saucer:"unicode/1f6f8",fog:"unicode/1f32b",foggy:"unicode/1f301",foot:"unicode/1f9b6",football:"unicode/1f3c8",footprints:"unicode/1f463",fork_and_knife:"unicode/1f374",fortune_cookie:"unicode/1f960",fountain:"unicode/26f2",fountain_pen:"unicode/1f58b",four:"unicode/0034-20e3",four_leaf_clover:"unicode/1f340",fox_face:"unicode/1f98a",fr:"unicode/1f1eb-1f1f7",framed_picture:"unicode/1f5bc",free:"unicode/1f193",french_guiana:"unicode/1f1ec-1f1eb",french_polynesia:"unicode/1f1f5-1f1eb",french_southern_territories:"unicode/1f1f9-1f1eb",fried_egg:"unicode/1f373",fried_shrimp:"unicode/1f364",fries:"unicode/1f35f",frog:"unicode/1f438",frowning:"unicode/1f626",frowning_face:"unicode/2639",frowning_man:"unicode/1f64d-2642",frowning_person:"unicode/1f64d",frowning_woman:"unicode/1f64d-2640",fu:"unicode/1f595",fuelpump:"unicode/26fd",full_moon:"unicode/1f315",full_moon_with_face:"unicode/1f31d",funeral_urn:"unicode/26b1",gabon:"unicode/1f1ec-1f1e6",gambia:"unicode/1f1ec-1f1f2",game_die:"unicode/1f3b2",garlic:"unicode/1f9c4",gb:"unicode/1f1ec-1f1e7",gear:"unicode/2699",gem:"unicode/1f48e",gemini:"unicode/264a",genie:"unicode/1f9de",genie_man:"unicode/1f9de-2642",genie_woman:"unicode/1f9de-2640",georgia:"unicode/1f1ec-1f1ea",ghana:"unicode/1f1ec-1f1ed",ghost:"unicode/1f47b",gibraltar:"unicode/1f1ec-1f1ee",gift:"unicode/1f381",gift_heart:"unicode/1f49d",giraffe:"unicode/1f992",girl:"unicode/1f467",globe_with_meridians:"unicode/1f310",gloves:"unicode/1f9e4",goal_net:"unicode/1f945",goat:"unicode/1f410",goberserk:"goberserk",godmode:"godmode",goggles:"unicode/1f97d",golf:"unicode/26f3",golfing:"unicode/1f3cc",golfing_man:"unicode/1f3cc-2642",golfing_woman:"unicode/1f3cc-2640",gorilla:"unicode/1f98d",grapes:"unicode/1f347",greece:"unicode/1f1ec-1f1f7",green_apple:"unicode/1f34f",green_book:"unicode/1f4d7",green_circle:"unicode/1f7e2",green_heart:"unicode/1f49a",green_salad:"unicode/1f957",green_square:"unicode/1f7e9",greenland:"unicode/1f1ec-1f1f1",grenada:"unicode/1f1ec-1f1e9",grey_exclamation:"unicode/2755",grey_question:"unicode/2754",grimacing:"unicode/1f62c",grin:"unicode/1f601",grinning:"unicode/1f600",guadeloupe:"unicode/1f1ec-1f1f5",guam:"unicode/1f1ec-1f1fa",guard:"unicode/1f482",guardsman:"unicode/1f482-2642",guardswoman:"unicode/1f482-2640",guatemala:"unicode/1f1ec-1f1f9",guernsey:"unicode/1f1ec-1f1ec",guide_dog:"unicode/1f9ae",guinea:"unicode/1f1ec-1f1f3",guinea_bissau:"unicode/1f1ec-1f1fc",guitar:"unicode/1f3b8",gun:"unicode/1f52b",guyana:"unicode/1f1ec-1f1fe",haircut:"unicode/1f487",haircut_man:"unicode/1f487-2642",haircut_woman:"unicode/1f487-2640",haiti:"unicode/1f1ed-1f1f9",hamburger:"unicode/1f354",hammer:"unicode/1f528",hammer_and_pick:"unicode/2692",hammer_and_wrench:"unicode/1f6e0",hamster:"unicode/1f439",hand:"unicode/270b",hand_over_mouth:"unicode/1f92d",handbag:"unicode/1f45c",handball_person:"unicode/1f93e",handshake:"unicode/1f91d",hankey:"unicode/1f4a9",hash:"unicode/0023-20e3",hatched_chick:"unicode/1f425",hatching_chick:"unicode/1f423",headphones:"unicode/1f3a7",health_worker:"unicode/1f9d1-2695",hear_no_evil:"unicode/1f649",heard_mcdonald_islands:"unicode/1f1ed-1f1f2",heart:"unicode/2764",heart_decoration:"unicode/1f49f",heart_eyes:"unicode/1f60d",heart_eyes_cat:"unicode/1f63b",heartbeat:"unicode/1f493",heartpulse:"unicode/1f497",hearts:"unicode/2665",heavy_check_mark:"unicode/2714",heavy_division_sign:"unicode/2797",heavy_dollar_sign:"unicode/1f4b2",heavy_exclamation_mark:"unicode/2757",heavy_heart_exclamation:"unicode/2763",heavy_minus_sign:"unicode/2796",heavy_multiplication_x:"unicode/2716",heavy_plus_sign:"unicode/2795",hedgehog:"unicode/1f994",helicopter:"unicode/1f681",herb:"unicode/1f33f",hibiscus:"unicode/1f33a",high_brightness:"unicode/1f506",high_heel:"unicode/1f460",hiking_boot:"unicode/1f97e",hindu_temple:"unicode/1f6d5",hippopotamus:"unicode/1f99b",hocho:"unicode/1f52a",hole:"unicode/1f573",honduras:"unicode/1f1ed-1f1f3",honey_pot:"unicode/1f36f",honeybee:"unicode/1f41d",hong_kong:"unicode/1f1ed-1f1f0",horse:"unicode/1f434",horse_racing:"unicode/1f3c7",hospital:"unicode/1f3e5",hot_face:"unicode/1f975",hot_pepper:"unicode/1f336",hotdog:"unicode/1f32d",hotel:"unicode/1f3e8",hotsprings:"unicode/2668",hourglass:"unicode/231b",hourglass_flowing_sand:"unicode/23f3",house:"unicode/1f3e0",house_with_garden:"unicode/1f3e1",houses:"unicode/1f3d8",hugs:"unicode/1f917",hungary:"unicode/1f1ed-1f1fa",hurtrealbad:"hurtrealbad",hushed:"unicode/1f62f",ice_cream:"unicode/1f368",ice_cube:"unicode/1f9ca",ice_hockey:"unicode/1f3d2",ice_skate:"unicode/26f8",icecream:"unicode/1f366",iceland:"unicode/1f1ee-1f1f8",id:"unicode/1f194",ideograph_advantage:"unicode/1f250",imp:"unicode/1f47f",inbox_tray:"unicode/1f4e5",incoming_envelope:"unicode/1f4e8",india:"unicode/1f1ee-1f1f3",indonesia:"unicode/1f1ee-1f1e9",infinity:"unicode/267e",information_desk_person:"unicode/1f481",information_source:"unicode/2139",innocent:"unicode/1f607",interrobang:"unicode/2049",iphone:"unicode/1f4f1",iran:"unicode/1f1ee-1f1f7",iraq:"unicode/1f1ee-1f1f6",ireland:"unicode/1f1ee-1f1ea",isle_of_man:"unicode/1f1ee-1f1f2",israel:"unicode/1f1ee-1f1f1",it:"unicode/1f1ee-1f1f9",izakaya_lantern:"unicode/1f3ee",jack_o_lantern:"unicode/1f383",jamaica:"unicode/1f1ef-1f1f2",japan:"unicode/1f5fe",japanese_castle:"unicode/1f3ef",japanese_goblin:"unicode/1f47a",japanese_ogre:"unicode/1f479",jeans:"unicode/1f456",jersey:"unicode/1f1ef-1f1ea",jigsaw:"unicode/1f9e9",jordan:"unicode/1f1ef-1f1f4",joy:"unicode/1f602",joy_cat:"unicode/1f639",joystick:"unicode/1f579",jp:"unicode/1f1ef-1f1f5",judge:"unicode/1f9d1-2696",juggling_person:"unicode/1f939",kaaba:"unicode/1f54b",kangaroo:"unicode/1f998",kazakhstan:"unicode/1f1f0-1f1ff",kenya:"unicode/1f1f0-1f1ea",key:"unicode/1f511",keyboard:"unicode/2328",keycap_ten:"unicode/1f51f",kick_scooter:"unicode/1f6f4",kimono:"unicode/1f458",kiribati:"unicode/1f1f0-1f1ee",kiss:"unicode/1f48b",kissing:"unicode/1f617",kissing_cat:"unicode/1f63d",kissing_closed_eyes:"unicode/1f61a",kissing_heart:"unicode/1f618",kissing_smiling_eyes:"unicode/1f619",kite:"unicode/1fa81",kiwi_fruit:"unicode/1f95d",kneeling_man:"unicode/1f9ce-2642",kneeling_person:"unicode/1f9ce",kneeling_woman:"unicode/1f9ce-2640",knife:"unicode/1f52a",koala:"unicode/1f428",koko:"unicode/1f201",kosovo:"unicode/1f1fd-1f1f0",kr:"unicode/1f1f0-1f1f7",kuwait:"unicode/1f1f0-1f1fc",kyrgyzstan:"unicode/1f1f0-1f1ec",lab_coat:"unicode/1f97c",label:"unicode/1f3f7",lacrosse:"unicode/1f94d",lantern:"unicode/1f3ee",laos:"unicode/1f1f1-1f1e6",large_blue_circle:"unicode/1f535",large_blue_diamond:"unicode/1f537",large_orange_diamond:"unicode/1f536",last_quarter_moon:"unicode/1f317",last_quarter_moon_with_face:"unicode/1f31c",latin_cross:"unicode/271d",latvia:"unicode/1f1f1-1f1fb",laughing:"unicode/1f606",leafy_green:"unicode/1f96c",leaves:"unicode/1f343",lebanon:"unicode/1f1f1-1f1e7",ledger:"unicode/1f4d2",left_luggage:"unicode/1f6c5",left_right_arrow:"unicode/2194",left_speech_bubble:"unicode/1f5e8",leftwards_arrow_with_hook:"unicode/21a9",leg:"unicode/1f9b5",lemon:"unicode/1f34b",leo:"unicode/264c",leopard:"unicode/1f406",lesotho:"unicode/1f1f1-1f1f8",level_slider:"unicode/1f39a",liberia:"unicode/1f1f1-1f1f7",libra:"unicode/264e",libya:"unicode/1f1f1-1f1fe",liechtenstein:"unicode/1f1f1-1f1ee",light_rail:"unicode/1f688",link:"unicode/1f517",lion:"unicode/1f981",lips:"unicode/1f444",lipstick:"unicode/1f484",lithuania:"unicode/1f1f1-1f1f9",lizard:"unicode/1f98e",llama:"unicode/1f999",lobster:"unicode/1f99e",lock:"unicode/1f512",lock_with_ink_pen:"unicode/1f50f",lollipop:"unicode/1f36d",loop:"unicode/27bf",lotion_bottle:"unicode/1f9f4",lotus_position:"unicode/1f9d8",lotus_position_man:"unicode/1f9d8-2642",lotus_position_woman:"unicode/1f9d8-2640",loud_sound:"unicode/1f50a",loudspeaker:"unicode/1f4e2",love_hotel:"unicode/1f3e9",love_letter:"unicode/1f48c",love_you_gesture:"unicode/1f91f",low_brightness:"unicode/1f505",luggage:"unicode/1f9f3",luxembourg:"unicode/1f1f1-1f1fa",lying_face:"unicode/1f925",m:"unicode/24c2",macau:"unicode/1f1f2-1f1f4",macedonia:"unicode/1f1f2-1f1f0",madagascar:"unicode/1f1f2-1f1ec",mag:"unicode/1f50d",mag_right:"unicode/1f50e",mage:"unicode/1f9d9",mage_man:"unicode/1f9d9-2642",mage_woman:"unicode/1f9d9-2640",magnet:"unicode/1f9f2",mahjong:"unicode/1f004",mailbox:"unicode/1f4eb",mailbox_closed:"unicode/1f4ea",mailbox_with_mail:"unicode/1f4ec",mailbox_with_no_mail:"unicode/1f4ed",malawi:"unicode/1f1f2-1f1fc",malaysia:"unicode/1f1f2-1f1fe",maldives:"unicode/1f1f2-1f1fb",male_detective:"unicode/1f575-2642",male_sign:"unicode/2642",mali:"unicode/1f1f2-1f1f1",malta:"unicode/1f1f2-1f1f9",man:"unicode/1f468",man_artist:"unicode/1f468-1f3a8",man_astronaut:"unicode/1f468-1f680",man_cartwheeling:"unicode/1f938-2642",man_cook:"unicode/1f468-1f373",man_dancing:"unicode/1f57a",man_facepalming:"unicode/1f926-2642",man_factory_worker:"unicode/1f468-1f3ed",man_farmer:"unicode/1f468-1f33e",man_firefighter:"unicode/1f468-1f692",man_health_worker:"unicode/1f468-2695",man_in_manual_wheelchair:"unicode/1f468-1f9bd",man_in_motorized_wheelchair:"unicode/1f468-1f9bc",man_in_tuxedo:"unicode/1f935",man_judge:"unicode/1f468-2696",man_juggling:"unicode/1f939-2642",man_mechanic:"unicode/1f468-1f527",man_office_worker:"unicode/1f468-1f4bc",man_pilot:"unicode/1f468-2708",man_playing_handball:"unicode/1f93e-2642",man_playing_water_polo:"unicode/1f93d-2642",man_scientist:"unicode/1f468-1f52c",man_shrugging:"unicode/1f937-2642",man_singer:"unicode/1f468-1f3a4",man_student:"unicode/1f468-1f393",man_teacher:"unicode/1f468-1f3eb",man_technologist:"unicode/1f468-1f4bb",man_with_gua_pi_mao:"unicode/1f472",man_with_probing_cane:"unicode/1f468-1f9af",man_with_turban:"unicode/1f473-2642",mandarin:"unicode/1f34a",mango:"unicode/1f96d",mans_shoe:"unicode/1f45e",mantelpiece_clock:"unicode/1f570",manual_wheelchair:"unicode/1f9bd",maple_leaf:"unicode/1f341",marshall_islands:"unicode/1f1f2-1f1ed",martial_arts_uniform:"unicode/1f94b",martinique:"unicode/1f1f2-1f1f6",mask:"unicode/1f637",massage:"unicode/1f486",massage_man:"unicode/1f486-2642",massage_woman:"unicode/1f486-2640",mate:"unicode/1f9c9",mauritania:"unicode/1f1f2-1f1f7",mauritius:"unicode/1f1f2-1f1fa",mayotte:"unicode/1f1fe-1f1f9",meat_on_bone:"unicode/1f356",mechanic:"unicode/1f9d1-1f527",mechanical_arm:"unicode/1f9be",mechanical_leg:"unicode/1f9bf",medal_military:"unicode/1f396",medal_sports:"unicode/1f3c5",medical_symbol:"unicode/2695",mega:"unicode/1f4e3",melon:"unicode/1f348",memo:"unicode/1f4dd",men_wrestling:"unicode/1f93c-2642",menorah:"unicode/1f54e",mens:"unicode/1f6b9",mermaid:"unicode/1f9dc-2640",merman:"unicode/1f9dc-2642",merperson:"unicode/1f9dc",metal:"unicode/1f918",metro:"unicode/1f687",mexico:"unicode/1f1f2-1f1fd",microbe:"unicode/1f9a0",micronesia:"unicode/1f1eb-1f1f2",microphone:"unicode/1f3a4",microscope:"unicode/1f52c",middle_finger:"unicode/1f595",milk_glass:"unicode/1f95b",milky_way:"unicode/1f30c",minibus:"unicode/1f690",minidisc:"unicode/1f4bd",mobile_phone_off:"unicode/1f4f4",moldova:"unicode/1f1f2-1f1e9",monaco:"unicode/1f1f2-1f1e8",money_mouth_face:"unicode/1f911",money_with_wings:"unicode/1f4b8",moneybag:"unicode/1f4b0",mongolia:"unicode/1f1f2-1f1f3",monkey:"unicode/1f412",monkey_face:"unicode/1f435",monocle_face:"unicode/1f9d0",monorail:"unicode/1f69d",montenegro:"unicode/1f1f2-1f1ea",montserrat:"unicode/1f1f2-1f1f8",moon:"unicode/1f314",moon_cake:"unicode/1f96e",morocco:"unicode/1f1f2-1f1e6",mortar_board:"unicode/1f393",mosque:"unicode/1f54c",mosquito:"unicode/1f99f",motor_boat:"unicode/1f6e5",motor_scooter:"unicode/1f6f5",motorcycle:"unicode/1f3cd",motorized_wheelchair:"unicode/1f9bc",motorway:"unicode/1f6e3",mount_fuji:"unicode/1f5fb",mountain:"unicode/26f0",mountain_bicyclist:"unicode/1f6b5",mountain_biking_man:"unicode/1f6b5-2642",mountain_biking_woman:"unicode/1f6b5-2640",mountain_cableway:"unicode/1f6a0",mountain_railway:"unicode/1f69e",mountain_snow:"unicode/1f3d4",mouse:"unicode/1f42d",mouse2:"unicode/1f401",movie_camera:"unicode/1f3a5",moyai:"unicode/1f5ff",mozambique:"unicode/1f1f2-1f1ff",mrs_claus:"unicode/1f936",muscle:"unicode/1f4aa",mushroom:"unicode/1f344",musical_keyboard:"unicode/1f3b9",musical_note:"unicode/1f3b5",musical_score:"unicode/1f3bc",mute:"unicode/1f507",myanmar:"unicode/1f1f2-1f1f2",nail_care:"unicode/1f485",name_badge:"unicode/1f4db",namibia:"unicode/1f1f3-1f1e6",national_park:"unicode/1f3de",nauru:"unicode/1f1f3-1f1f7",nauseated_face:"unicode/1f922",nazar_amulet:"unicode/1f9ff",neckbeard:"neckbeard",necktie:"unicode/1f454",negative_squared_cross_mark:"unicode/274e",nepal:"unicode/1f1f3-1f1f5",nerd_face:"unicode/1f913",netherlands:"unicode/1f1f3-1f1f1",neutral_face:"unicode/1f610",new:"unicode/1f195",new_caledonia:"unicode/1f1f3-1f1e8",new_moon:"unicode/1f311",new_moon_with_face:"unicode/1f31a",new_zealand:"unicode/1f1f3-1f1ff",newspaper:"unicode/1f4f0",newspaper_roll:"unicode/1f5de",next_track_button:"unicode/23ed",ng:"unicode/1f196",ng_man:"unicode/1f645-2642",ng_woman:"unicode/1f645-2640",nicaragua:"unicode/1f1f3-1f1ee",niger:"unicode/1f1f3-1f1ea",nigeria:"unicode/1f1f3-1f1ec",night_with_stars:"unicode/1f303",nine:"unicode/0039-20e3",niue:"unicode/1f1f3-1f1fa",no_bell:"unicode/1f515",no_bicycles:"unicode/1f6b3",no_entry:"unicode/26d4",no_entry_sign:"unicode/1f6ab",no_good:"unicode/1f645",no_good_man:"unicode/1f645-2642",no_good_woman:"unicode/1f645-2640",no_mobile_phones:"unicode/1f4f5",no_mouth:"unicode/1f636",no_pedestrians:"unicode/1f6b7",no_smoking:"unicode/1f6ad","non-potable_water":"unicode/1f6b1",norfolk_island:"unicode/1f1f3-1f1eb",north_korea:"unicode/1f1f0-1f1f5",northern_mariana_islands:"unicode/1f1f2-1f1f5",norway:"unicode/1f1f3-1f1f4",nose:"unicode/1f443",notebook:"unicode/1f4d3",notebook_with_decorative_cover:"unicode/1f4d4",notes:"unicode/1f3b6",nut_and_bolt:"unicode/1f529",o:"unicode/2b55",o2:"unicode/1f17e",ocean:"unicode/1f30a",octocat:"octocat",octopus:"unicode/1f419",oden:"unicode/1f362",office:"unicode/1f3e2",office_worker:"unicode/1f9d1-1f4bc",oil_drum:"unicode/1f6e2",ok:"unicode/1f197",ok_hand:"unicode/1f44c",ok_man:"unicode/1f646-2642",ok_person:"unicode/1f646",ok_woman:"unicode/1f646-2640",old_key:"unicode/1f5dd",older_adult:"unicode/1f9d3",older_man:"unicode/1f474",older_woman:"unicode/1f475",om:"unicode/1f549",oman:"unicode/1f1f4-1f1f2",on:"unicode/1f51b",oncoming_automobile:"unicode/1f698",oncoming_bus:"unicode/1f68d",oncoming_police_car:"unicode/1f694",oncoming_taxi:"unicode/1f696",one:"unicode/0031-20e3",one_piece_swimsuit:"unicode/1fa71",onion:"unicode/1f9c5",open_book:"unicode/1f4d6",open_file_folder:"unicode/1f4c2",open_hands:"unicode/1f450",open_mouth:"unicode/1f62e",open_umbrella:"unicode/2602",ophiuchus:"unicode/26ce",orange:"unicode/1f34a",orange_book:"unicode/1f4d9",orange_circle:"unicode/1f7e0",orange_heart:"unicode/1f9e1",orange_square:"unicode/1f7e7",orangutan:"unicode/1f9a7",orthodox_cross:"unicode/2626",otter:"unicode/1f9a6",outbox_tray:"unicode/1f4e4",owl:"unicode/1f989",ox:"unicode/1f402",oyster:"unicode/1f9aa",package:"unicode/1f4e6",page_facing_up:"unicode/1f4c4",page_with_curl:"unicode/1f4c3",pager:"unicode/1f4df",paintbrush:"unicode/1f58c",pakistan:"unicode/1f1f5-1f1f0",palau:"unicode/1f1f5-1f1fc",palestinian_territories:"unicode/1f1f5-1f1f8",palm_tree:"unicode/1f334",palms_up_together:"unicode/1f932",panama:"unicode/1f1f5-1f1e6",pancakes:"unicode/1f95e",panda_face:"unicode/1f43c",paperclip:"unicode/1f4ce",paperclips:"unicode/1f587",papua_new_guinea:"unicode/1f1f5-1f1ec",parachute:"unicode/1fa82",paraguay:"unicode/1f1f5-1f1fe",parasol_on_ground:"unicode/26f1",parking:"unicode/1f17f",parrot:"unicode/1f99c",part_alternation_mark:"unicode/303d",partly_sunny:"unicode/26c5",partying_face:"unicode/1f973",passenger_ship:"unicode/1f6f3",passport_control:"unicode/1f6c2",pause_button:"unicode/23f8",paw_prints:"unicode/1f43e",peace_symbol:"unicode/262e",peach:"unicode/1f351",peacock:"unicode/1f99a",peanuts:"unicode/1f95c",pear:"unicode/1f350",pen:"unicode/1f58a",pencil:"unicode/1f4dd",pencil2:"unicode/270f",penguin:"unicode/1f427",pensive:"unicode/1f614",people_holding_hands:"unicode/1f9d1-1f91d-1f9d1",performing_arts:"unicode/1f3ad",persevere:"unicode/1f623",person_bald:"unicode/1f9d1-1f9b2",person_curly_hair:"unicode/1f9d1-1f9b1",person_fencing:"unicode/1f93a",person_in_manual_wheelchair:"unicode/1f9d1-1f9bd",person_in_motorized_wheelchair:"unicode/1f9d1-1f9bc",person_red_hair:"unicode/1f9d1-1f9b0",person_white_hair:"unicode/1f9d1-1f9b3",person_with_probing_cane:"unicode/1f9d1-1f9af",person_with_turban:"unicode/1f473",peru:"unicode/1f1f5-1f1ea",petri_dish:"unicode/1f9eb",philippines:"unicode/1f1f5-1f1ed",phone:"unicode/260e",pick:"unicode/26cf",pie:"unicode/1f967",pig:"unicode/1f437",pig2:"unicode/1f416",pig_nose:"unicode/1f43d",pill:"unicode/1f48a",pilot:"unicode/1f9d1-2708",pinching_hand:"unicode/1f90f",pineapple:"unicode/1f34d",ping_pong:"unicode/1f3d3",pirate_flag:"unicode/1f3f4-2620",pisces:"unicode/2653",pitcairn_islands:"unicode/1f1f5-1f1f3",pizza:"unicode/1f355",place_of_worship:"unicode/1f6d0",plate_with_cutlery:"unicode/1f37d",play_or_pause_button:"unicode/23ef",pleading_face:"unicode/1f97a",point_down:"unicode/1f447",point_left:"unicode/1f448",point_right:"unicode/1f449",point_up:"unicode/261d",point_up_2:"unicode/1f446",poland:"unicode/1f1f5-1f1f1",police_car:"unicode/1f693",police_officer:"unicode/1f46e",policeman:"unicode/1f46e-2642",policewoman:"unicode/1f46e-2640",poodle:"unicode/1f429",poop:"unicode/1f4a9",popcorn:"unicode/1f37f",portugal:"unicode/1f1f5-1f1f9",post_office:"unicode/1f3e3",postal_horn:"unicode/1f4ef",postbox:"unicode/1f4ee",potable_water:"unicode/1f6b0",potato:"unicode/1f954",pouch:"unicode/1f45d",poultry_leg:"unicode/1f357",pound:"unicode/1f4b7",pout:"unicode/1f621",pouting_cat:"unicode/1f63e",pouting_face:"unicode/1f64e",pouting_man:"unicode/1f64e-2642",pouting_woman:"unicode/1f64e-2640",pray:"unicode/1f64f",prayer_beads:"unicode/1f4ff",pregnant_woman:"unicode/1f930",pretzel:"unicode/1f968",previous_track_button:"unicode/23ee",prince:"unicode/1f934",princess:"unicode/1f478",printer:"unicode/1f5a8",probing_cane:"unicode/1f9af",puerto_rico:"unicode/1f1f5-1f1f7",punch:"unicode/1f44a",purple_circle:"unicode/1f7e3",purple_heart:"unicode/1f49c",purple_square:"unicode/1f7ea",purse:"unicode/1f45b",pushpin:"unicode/1f4cc",put_litter_in_its_place:"unicode/1f6ae",qatar:"unicode/1f1f6-1f1e6",question:"unicode/2753",rabbit:"unicode/1f430",rabbit2:"unicode/1f407",raccoon:"unicode/1f99d",racehorse:"unicode/1f40e",racing_car:"unicode/1f3ce",radio:"unicode/1f4fb",radio_button:"unicode/1f518",radioactive:"unicode/2622",rage:"unicode/1f621",rage1:"rage1",rage2:"rage2",rage3:"rage3",rage4:"rage4",railway_car:"unicode/1f683",railway_track:"unicode/1f6e4",rainbow:"unicode/1f308",rainbow_flag:"unicode/1f3f3-1f308",raised_back_of_hand:"unicode/1f91a",raised_eyebrow:"unicode/1f928",raised_hand:"unicode/270b",raised_hand_with_fingers_splayed:"unicode/1f590",raised_hands:"unicode/1f64c",raising_hand:"unicode/1f64b",raising_hand_man:"unicode/1f64b-2642",raising_hand_woman:"unicode/1f64b-2640",ram:"unicode/1f40f",ramen:"unicode/1f35c",rat:"unicode/1f400",razor:"unicode/1fa92",receipt:"unicode/1f9fe",record_button:"unicode/23fa",recycle:"unicode/267b",red_car:"unicode/1f697",red_circle:"unicode/1f534",red_envelope:"unicode/1f9e7",red_haired_man:"unicode/1f468-1f9b0",red_haired_woman:"unicode/1f469-1f9b0",red_square:"unicode/1f7e5",registered:"unicode/00ae",relaxed:"unicode/263a",relieved:"unicode/1f60c",reminder_ribbon:"unicode/1f397",repeat:"unicode/1f501",repeat_one:"unicode/1f502",rescue_worker_helmet:"unicode/26d1",restroom:"unicode/1f6bb",reunion:"unicode/1f1f7-1f1ea",revolving_hearts:"unicode/1f49e",rewind:"unicode/23ea",rhinoceros:"unicode/1f98f",ribbon:"unicode/1f380",rice:"unicode/1f35a",rice_ball:"unicode/1f359",rice_cracker:"unicode/1f358",rice_scene:"unicode/1f391",right_anger_bubble:"unicode/1f5ef",ring:"unicode/1f48d",ringed_planet:"unicode/1fa90",robot:"unicode/1f916",rocket:"unicode/1f680",rofl:"unicode/1f923",roll_eyes:"unicode/1f644",roll_of_paper:"unicode/1f9fb",roller_coaster:"unicode/1f3a2",romania:"unicode/1f1f7-1f1f4",rooster:"unicode/1f413",rose:"unicode/1f339",rosette:"unicode/1f3f5",rotating_light:"unicode/1f6a8",round_pushpin:"unicode/1f4cd",rowboat:"unicode/1f6a3",rowing_man:"unicode/1f6a3-2642",rowing_woman:"unicode/1f6a3-2640",ru:"unicode/1f1f7-1f1fa",rugby_football:"unicode/1f3c9",runner:"unicode/1f3c3",running:"unicode/1f3c3",running_man:"unicode/1f3c3-2642",running_shirt_with_sash:"unicode/1f3bd",running_woman:"unicode/1f3c3-2640",rwanda:"unicode/1f1f7-1f1fc",sa:"unicode/1f202",safety_pin:"unicode/1f9f7",safety_vest:"unicode/1f9ba",sagittarius:"unicode/2650",sailboat:"unicode/26f5",sake:"unicode/1f376",salt:"unicode/1f9c2",samoa:"unicode/1f1fc-1f1f8",san_marino:"unicode/1f1f8-1f1f2",sandal:"unicode/1f461",sandwich:"unicode/1f96a",santa:"unicode/1f385",sao_tome_principe:"unicode/1f1f8-1f1f9",sari:"unicode/1f97b",sassy_man:"unicode/1f481-2642",sassy_woman:"unicode/1f481-2640",satellite:"unicode/1f4e1",satisfied:"unicode/1f606",saudi_arabia:"unicode/1f1f8-1f1e6",sauna_man:"unicode/1f9d6-2642",sauna_person:"unicode/1f9d6",sauna_woman:"unicode/1f9d6-2640",sauropod:"unicode/1f995",saxophone:"unicode/1f3b7",scarf:"unicode/1f9e3",school:"unicode/1f3eb",school_satchel:"unicode/1f392",scientist:"unicode/1f9d1-1f52c",scissors:"unicode/2702",scorpion:"unicode/1f982",scorpius:"unicode/264f",scotland:"unicode/1f3f4-e0067-e0062-e0073-e0063-e0074-e007f",scream:"unicode/1f631",scream_cat:"unicode/1f640",scroll:"unicode/1f4dc",seat:"unicode/1f4ba",secret:"unicode/3299",see_no_evil:"unicode/1f648",seedling:"unicode/1f331",selfie:"unicode/1f933",senegal:"unicode/1f1f8-1f1f3",serbia:"unicode/1f1f7-1f1f8",service_dog:"unicode/1f415-1f9ba",seven:"unicode/0037-20e3",seychelles:"unicode/1f1f8-1f1e8",shallow_pan_of_food:"unicode/1f958",shamrock:"unicode/2618",shark:"unicode/1f988",shaved_ice:"unicode/1f367",sheep:"unicode/1f411",shell:"unicode/1f41a",shield:"unicode/1f6e1",shinto_shrine:"unicode/26e9",ship:"unicode/1f6a2",shipit:"shipit",shirt:"unicode/1f455",shit:"unicode/1f4a9",shoe:"unicode/1f45e",shopping:"unicode/1f6cd",shopping_cart:"unicode/1f6d2",shorts:"unicode/1fa73",shower:"unicode/1f6bf",shrimp:"unicode/1f990",shrug:"unicode/1f937",shushing_face:"unicode/1f92b",sierra_leone:"unicode/1f1f8-1f1f1",signal_strength:"unicode/1f4f6",singapore:"unicode/1f1f8-1f1ec",singer:"unicode/1f9d1-1f3a4",sint_maarten:"unicode/1f1f8-1f1fd",six:"unicode/0036-20e3",six_pointed_star:"unicode/1f52f",skateboard:"unicode/1f6f9",ski:"unicode/1f3bf",skier:"unicode/26f7",skull:"unicode/1f480",skull_and_crossbones:"unicode/2620",skunk:"unicode/1f9a8",sled:"unicode/1f6f7",sleeping:"unicode/1f634",sleeping_bed:"unicode/1f6cc",sleepy:"unicode/1f62a",slightly_frowning_face:"unicode/1f641",slightly_smiling_face:"unicode/1f642",slot_machine:"unicode/1f3b0",sloth:"unicode/1f9a5",slovakia:"unicode/1f1f8-1f1f0",slovenia:"unicode/1f1f8-1f1ee",small_airplane:"unicode/1f6e9",small_blue_diamond:"unicode/1f539",small_orange_diamond:"unicode/1f538",small_red_triangle:"unicode/1f53a",small_red_triangle_down:"unicode/1f53b",smile:"unicode/1f604",smile_cat:"unicode/1f638",smiley:"unicode/1f603",smiley_cat:"unicode/1f63a",smiling_face_with_three_hearts:"unicode/1f970",smiling_imp:"unicode/1f608",smirk:"unicode/1f60f",smirk_cat:"unicode/1f63c",smoking:"unicode/1f6ac",snail:"unicode/1f40c",snake:"unicode/1f40d",sneezing_face:"unicode/1f927",snowboarder:"unicode/1f3c2",snowflake:"unicode/2744",snowman:"unicode/26c4",snowman_with_snow:"unicode/2603",soap:"unicode/1f9fc",sob:"unicode/1f62d",soccer:"unicode/26bd",socks:"unicode/1f9e6",softball:"unicode/1f94e",solomon_islands:"unicode/1f1f8-1f1e7",somalia:"unicode/1f1f8-1f1f4",soon:"unicode/1f51c",sos:"unicode/1f198",sound:"unicode/1f509",south_africa:"unicode/1f1ff-1f1e6",south_georgia_south_sandwich_islands:"unicode/1f1ec-1f1f8",south_sudan:"unicode/1f1f8-1f1f8",space_invader:"unicode/1f47e",spades:"unicode/2660",spaghetti:"unicode/1f35d",sparkle:"unicode/2747",sparkler:"unicode/1f387",sparkles:"unicode/2728",sparkling_heart:"unicode/1f496",speak_no_evil:"unicode/1f64a",speaker:"unicode/1f508",speaking_head:"unicode/1f5e3",speech_balloon:"unicode/1f4ac",speedboat:"unicode/1f6a4",spider:"unicode/1f577",spider_web:"unicode/1f578",spiral_calendar:"unicode/1f5d3",spiral_notepad:"unicode/1f5d2",sponge:"unicode/1f9fd",spoon:"unicode/1f944",squid:"unicode/1f991",sri_lanka:"unicode/1f1f1-1f1f0",st_barthelemy:"unicode/1f1e7-1f1f1",st_helena:"unicode/1f1f8-1f1ed",st_kitts_nevis:"unicode/1f1f0-1f1f3",st_lucia:"unicode/1f1f1-1f1e8",st_martin:"unicode/1f1f2-1f1eb",st_pierre_miquelon:"unicode/1f1f5-1f1f2",st_vincent_grenadines:"unicode/1f1fb-1f1e8",stadium:"unicode/1f3df",standing_man:"unicode/1f9cd-2642",standing_person:"unicode/1f9cd",standing_woman:"unicode/1f9cd-2640",star:"unicode/2b50",star2:"unicode/1f31f",star_and_crescent:"unicode/262a",star_of_david:"unicode/2721",star_struck:"unicode/1f929",stars:"unicode/1f320",station:"unicode/1f689",statue_of_liberty:"unicode/1f5fd",steam_locomotive:"unicode/1f682",stethoscope:"unicode/1fa7a",stew:"unicode/1f372",stop_button:"unicode/23f9",stop_sign:"unicode/1f6d1",stopwatch:"unicode/23f1",straight_ruler:"unicode/1f4cf",strawberry:"unicode/1f353",stuck_out_tongue:"unicode/1f61b",stuck_out_tongue_closed_eyes:"unicode/1f61d",stuck_out_tongue_winking_eye:"unicode/1f61c",student:"unicode/1f9d1-1f393",studio_microphone:"unicode/1f399",stuffed_flatbread:"unicode/1f959",sudan:"unicode/1f1f8-1f1e9",sun_behind_large_cloud:"unicode/1f325",sun_behind_rain_cloud:"unicode/1f326",sun_behind_small_cloud:"unicode/1f324",sun_with_face:"unicode/1f31e",sunflower:"unicode/1f33b",sunglasses:"unicode/1f60e",sunny:"unicode/2600",sunrise:"unicode/1f305",sunrise_over_mountains:"unicode/1f304",superhero:"unicode/1f9b8",superhero_man:"unicode/1f9b8-2642",superhero_woman:"unicode/1f9b8-2640",supervillain:"unicode/1f9b9",supervillain_man:"unicode/1f9b9-2642",supervillain_woman:"unicode/1f9b9-2640",surfer:"unicode/1f3c4",surfing_man:"unicode/1f3c4-2642",surfing_woman:"unicode/1f3c4-2640",suriname:"unicode/1f1f8-1f1f7",sushi:"unicode/1f363",suspect:"suspect",suspension_railway:"unicode/1f69f",svalbard_jan_mayen:"unicode/1f1f8-1f1ef",swan:"unicode/1f9a2",swaziland:"unicode/1f1f8-1f1ff",sweat:"unicode/1f613",sweat_drops:"unicode/1f4a6",sweat_smile:"unicode/1f605",sweden:"unicode/1f1f8-1f1ea",sweet_potato:"unicode/1f360",swim_brief:"unicode/1fa72",swimmer:"unicode/1f3ca",swimming_man:"unicode/1f3ca-2642",swimming_woman:"unicode/1f3ca-2640",switzerland:"unicode/1f1e8-1f1ed",symbols:"unicode/1f523",synagogue:"unicode/1f54d",syria:"unicode/1f1f8-1f1fe",syringe:"unicode/1f489","t-rex":"unicode/1f996",taco:"unicode/1f32e",tada:"unicode/1f389",taiwan:"unicode/1f1f9-1f1fc",tajikistan:"unicode/1f1f9-1f1ef",takeout_box:"unicode/1f961",tanabata_tree:"unicode/1f38b",tangerine:"unicode/1f34a",tanzania:"unicode/1f1f9-1f1ff",taurus:"unicode/2649",taxi:"unicode/1f695",tea:"unicode/1f375",teacher:"unicode/1f9d1-1f3eb",technologist:"unicode/1f9d1-1f4bb",teddy_bear:"unicode/1f9f8",telephone:"unicode/260e",telephone_receiver:"unicode/1f4de",telescope:"unicode/1f52d",tennis:"unicode/1f3be",tent:"unicode/26fa",test_tube:"unicode/1f9ea",thailand:"unicode/1f1f9-1f1ed",thermometer:"unicode/1f321",thinking:"unicode/1f914",thought_balloon:"unicode/1f4ad",thread:"unicode/1f9f5",three:"unicode/0033-20e3",thumbsdown:"unicode/1f44e",thumbsup:"unicode/1f44d",ticket:"unicode/1f3ab",tickets:"unicode/1f39f",tiger:"unicode/1f42f",tiger2:"unicode/1f405",timer_clock:"unicode/23f2",timor_leste:"unicode/1f1f9-1f1f1",tipping_hand_man:"unicode/1f481-2642",tipping_hand_person:"unicode/1f481",tipping_hand_woman:"unicode/1f481-2640",tired_face:"unicode/1f62b",tm:"unicode/2122",togo:"unicode/1f1f9-1f1ec",toilet:"unicode/1f6bd",tokelau:"unicode/1f1f9-1f1f0",tokyo_tower:"unicode/1f5fc",tomato:"unicode/1f345",tonga:"unicode/1f1f9-1f1f4",tongue:"unicode/1f445",toolbox:"unicode/1f9f0",tooth:"unicode/1f9b7",top:"unicode/1f51d",tophat:"unicode/1f3a9",tornado:"unicode/1f32a",tr:"unicode/1f1f9-1f1f7",trackball:"unicode/1f5b2",tractor:"unicode/1f69c",traffic_light:"unicode/1f6a5",train:"unicode/1f68b",train2:"unicode/1f686",tram:"unicode/1f68a",triangular_flag_on_post:"unicode/1f6a9",triangular_ruler:"unicode/1f4d0",trident:"unicode/1f531",trinidad_tobago:"unicode/1f1f9-1f1f9",tristan_da_cunha:"unicode/1f1f9-1f1e6",triumph:"unicode/1f624",trolleybus:"unicode/1f68e",trollface:"trollface",trophy:"unicode/1f3c6",tropical_drink:"unicode/1f379",tropical_fish:"unicode/1f420",truck:"unicode/1f69a",trumpet:"unicode/1f3ba",tshirt:"unicode/1f455",tulip:"unicode/1f337",tumbler_glass:"unicode/1f943",tunisia:"unicode/1f1f9-1f1f3",turkey:"unicode/1f983",turkmenistan:"unicode/1f1f9-1f1f2",turks_caicos_islands:"unicode/1f1f9-1f1e8",turtle:"unicode/1f422",tuvalu:"unicode/1f1f9-1f1fb",tv:"unicode/1f4fa",twisted_rightwards_arrows:"unicode/1f500",two:"unicode/0032-20e3",two_hearts:"unicode/1f495",two_men_holding_hands:"unicode/1f46c",two_women_holding_hands:"unicode/1f46d",u5272:"unicode/1f239",u5408:"unicode/1f234",u55b6:"unicode/1f23a",u6307:"unicode/1f22f",u6708:"unicode/1f237",u6709:"unicode/1f236",u6e80:"unicode/1f235",u7121:"unicode/1f21a",u7533:"unicode/1f238",u7981:"unicode/1f232",u7a7a:"unicode/1f233",uganda:"unicode/1f1fa-1f1ec",uk:"unicode/1f1ec-1f1e7",ukraine:"unicode/1f1fa-1f1e6",umbrella:"unicode/2614",unamused:"unicode/1f612",underage:"unicode/1f51e",unicorn:"unicode/1f984",united_arab_emirates:"unicode/1f1e6-1f1ea",united_nations:"unicode/1f1fa-1f1f3",unlock:"unicode/1f513",up:"unicode/1f199",upside_down_face:"unicode/1f643",uruguay:"unicode/1f1fa-1f1fe",us:"unicode/1f1fa-1f1f8",us_outlying_islands:"unicode/1f1fa-1f1f2",us_virgin_islands:"unicode/1f1fb-1f1ee",uzbekistan:"unicode/1f1fa-1f1ff",v:"unicode/270c",vampire:"unicode/1f9db",vampire_man:"unicode/1f9db-2642",vampire_woman:"unicode/1f9db-2640",vanuatu:"unicode/1f1fb-1f1fa",vatican_city:"unicode/1f1fb-1f1e6",venezuela:"unicode/1f1fb-1f1ea",vertical_traffic_light:"unicode/1f6a6",vhs:"unicode/1f4fc",vibration_mode:"unicode/1f4f3",video_camera:"unicode/1f4f9",video_game:"unicode/1f3ae",vietnam:"unicode/1f1fb-1f1f3",violin:"unicode/1f3bb",virgo:"unicode/264d",volcano:"unicode/1f30b",volleyball:"unicode/1f3d0",vomiting_face:"unicode/1f92e",vs:"unicode/1f19a",vulcan_salute:"unicode/1f596",waffle:"unicode/1f9c7",wales:"unicode/1f3f4-e0067-e0062-e0077-e006c-e0073-e007f",walking:"unicode/1f6b6",walking_man:"unicode/1f6b6-2642",walking_woman:"unicode/1f6b6-2640",wallis_futuna:"unicode/1f1fc-1f1eb",waning_crescent_moon:"unicode/1f318",waning_gibbous_moon:"unicode/1f316",warning:"unicode/26a0",wastebasket:"unicode/1f5d1",watch:"unicode/231a",water_buffalo:"unicode/1f403",water_polo:"unicode/1f93d",watermelon:"unicode/1f349",wave:"unicode/1f44b",wavy_dash:"unicode/3030",waxing_crescent_moon:"unicode/1f312",waxing_gibbous_moon:"unicode/1f314",wc:"unicode/1f6be",weary:"unicode/1f629",wedding:"unicode/1f492",weight_lifting:"unicode/1f3cb",weight_lifting_man:"unicode/1f3cb-2642",weight_lifting_woman:"unicode/1f3cb-2640",western_sahara:"unicode/1f1ea-1f1ed",whale:"unicode/1f433",whale2:"unicode/1f40b",wheel_of_dharma:"unicode/2638",wheelchair:"unicode/267f",white_check_mark:"unicode/2705",white_circle:"unicode/26aa",white_flag:"unicode/1f3f3",white_flower:"unicode/1f4ae",white_haired_man:"unicode/1f468-1f9b3",white_haired_woman:"unicode/1f469-1f9b3",white_heart:"unicode/1f90d",white_large_square:"unicode/2b1c",white_medium_small_square:"unicode/25fd",white_medium_square:"unicode/25fb",white_small_square:"unicode/25ab",white_square_button:"unicode/1f533",wilted_flower:"unicode/1f940",wind_chime:"unicode/1f390",wind_face:"unicode/1f32c",wine_glass:"unicode/1f377",wink:"unicode/1f609",wolf:"unicode/1f43a",woman:"unicode/1f469",woman_artist:"unicode/1f469-1f3a8",woman_astronaut:"unicode/1f469-1f680",woman_cartwheeling:"unicode/1f938-2640",woman_cook:"unicode/1f469-1f373",woman_dancing:"unicode/1f483",woman_facepalming:"unicode/1f926-2640",woman_factory_worker:"unicode/1f469-1f3ed",woman_farmer:"unicode/1f469-1f33e",woman_firefighter:"unicode/1f469-1f692",woman_health_worker:"unicode/1f469-2695",woman_in_manual_wheelchair:"unicode/1f469-1f9bd",woman_in_motorized_wheelchair:"unicode/1f469-1f9bc",woman_judge:"unicode/1f469-2696",woman_juggling:"unicode/1f939-2640",woman_mechanic:"unicode/1f469-1f527",woman_office_worker:"unicode/1f469-1f4bc",woman_pilot:"unicode/1f469-2708",woman_playing_handball:"unicode/1f93e-2640",woman_playing_water_polo:"unicode/1f93d-2640",woman_scientist:"unicode/1f469-1f52c",woman_shrugging:"unicode/1f937-2640",woman_singer:"unicode/1f469-1f3a4",woman_student:"unicode/1f469-1f393",woman_teacher:"unicode/1f469-1f3eb",woman_technologist:"unicode/1f469-1f4bb",woman_with_headscarf:"unicode/1f9d5",woman_with_probing_cane:"unicode/1f469-1f9af",woman_with_turban:"unicode/1f473-2640",womans_clothes:"unicode/1f45a",womans_hat:"unicode/1f452",women_wrestling:"unicode/1f93c-2640",womens:"unicode/1f6ba",woozy_face:"unicode/1f974",world_map:"unicode/1f5fa",worried:"unicode/1f61f",wrench:"unicode/1f527",wrestling:"unicode/1f93c",writing_hand:"unicode/270d",x:"unicode/274c",yarn:"unicode/1f9f6",yawning_face:"unicode/1f971",yellow_circle:"unicode/1f7e1",yellow_heart:"unicode/1f49b",yellow_square:"unicode/1f7e8",yemen:"unicode/1f1fe-1f1ea",yen:"unicode/1f4b4",yin_yang:"unicode/262f",yo_yo:"unicode/1fa80",yum:"unicode/1f60b",zambia:"unicode/1f1ff-1f1f2",zany_face:"unicode/1f92a",zap:"unicode/26a1",zebra:"unicode/1f993",zero:"unicode/0030-20e3",zimbabwe:"unicode/1f1ff-1f1fc",zipper_mouth_face:"unicode/1f910",zombie:"unicode/1f9df",zombie_man:"unicode/1f9df-2642",zombie_woman:"unicode/1f9df-2640",zzz:"unicode/1f4a4"};window.emojify=function(e,n){return!1===o.hasOwnProperty(n)?e:'<img class="emoji" src="https://github.githubassets.com/images/icons/emoji/'+o[n]+'.png" alt="'+n+'" />'}}(); diff --git a/_scripts/external-script.min.js b/_scripts/external-script.min.js new file mode 100644 index 0000000000000000000000000000000000000000..c4f80268250fb1d53efdcdf92aa6274195040f50 --- /dev/null +++ b/_scripts/external-script.min.js @@ -0,0 +1 @@ +!function(){function e(){for(var o=Docsify.dom.getNode("#main"),e=Docsify.dom.findAll(o,"script"),n=e.length;n--;){var i=e[n];if(i&&i.src){var t=document.createElement("script");Array.prototype.slice.call(i.attributes).forEach(function(o){t[o.name]=o.value}),i.parentNode.insertBefore(t,i),i.parentNode.removeChild(i)}}}window.$docsify.plugins=[].concat(function(o){o.doneEach(e)},window.$docsify.plugins)}(); diff --git a/_scripts/prism-c.min.js b/_scripts/prism-c.min.js new file mode 100644 index 0000000000000000000000000000000000000000..ec001aed714ba2622204bc466272c62845b97004 --- /dev/null +++ b/_scripts/prism-c.min.js @@ -0,0 +1 @@ +Prism.languages.c=Prism.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+/,lookbehind:!0},keyword:/\b(?:__attribute__|define|_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,function:/[a-z_]\w*(?=\s*\()/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/,number:/(?:\b0x(?:[\da-f]+\.?[\da-f]*|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?)[ful]*/i}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+(?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},Prism.languages.c.string],comment:Prism.languages.c.comment,directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:Prism.languages.c}}},constant:/\b(?:__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/}),delete Prism.languages.c.boolean; \ No newline at end of file diff --git a/_scripts/search.min.js b/_scripts/search.min.js new file mode 100644 index 0000000000000000000000000000000000000000..f74474415d827d53d0f0ba3249774255b2a342bf --- /dev/null +++ b/_scripts/search.min.js @@ -0,0 +1 @@ +!function(){var h={},f={EXPIRE_KEY:"docsify.search.expires",INDEX_KEY:"docsify.search.index"};function l(e){var n={"&":"&","<":"<",">":">",'"':""","'":"'"};return String(e).replace(/[&<>"']/g,function(e){return n[e]})}function p(e){return e.text||"table"!==e.type||(e.cells.unshift(e.header),e.text=e.cells.map(function(e){return e.join(" | ")}).join(" |\n ")),e.text}function u(r,e,i,o){void 0===e&&(e="");var s,n=window.marked.lexer(e),c=window.Docsify.slugify,d={};return n.forEach(function(e){if("heading"===e.type&&e.depth<=o){var n=function(e){void 0===e&&(e="");var a={};return{str:e=e&&e.replace(/^'/,"").replace(/'$/,"").replace(/(?:^|\s):([\w-]+:?)=?([\w-%]+)?/g,function(e,n,t){return-1===n.indexOf(":")?(a[n]=t&&t.replace(/"/g,"")||!0,""):e}).trim(),config:a}}(e.text),t=n.str,a=n.config;s=a.id?i.toURL(r,{id:c(a.id)}):i.toURL(r,{id:c(l(e.text))}),d[s]={slug:s,title:t,body:""}}else{if(!s)return;d[s]?d[s].body?(e.text=p(e),d[s].body+="\n"+(e.text||"")):(e.text=p(e),d[s].body=d[s].body?d[s].body+e.text:e.text):d[s]={slug:s,title:"",body:""}}}),c.clear(),d}function c(e){var r=[],i=[];Object.keys(h).forEach(function(n){i=i.concat(Object.keys(h[n]).map(function(e){return h[n][e]}))});var o=(e=e.trim()).split(/[\s\-,\\/]+/);1!==o.length&&(o=[].concat(e,o));function n(e){var n=i[e],s=0,c="",d=n.title&&n.title.trim(),p=n.body&&n.body.trim(),t=n.slug||"";if(d&&(o.forEach(function(e){var n,t=new RegExp(e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&"),"gi"),a=-1;if(n=d?d.search(t):-1,a=p?p.search(t):-1,0<=n||0<=a){s+=0<=n?3:0<=a?2:0,a<0&&(a=0);var r,i=0;i=0==(r=a<11?0:a-10)?70:a+e.length+60,p&&i>p.length&&(i=p.length);var o="..."+l(p).substring(r,i).replace(t,function(e){return'<em class="search-keyword">'+e+"</em>"})+"...";c+=o}}),0<s)){var a={title:l(d),content:p?c:"",url:t,score:s};r.push(a)}}for(var t=0;t<i.length;t++)n(t);return r.sort(function(e,n){return n.score-e.score})}function i(t,a){var e="auto"===t.paths,n=e?function(r){var i=[];return Docsify.dom.findAll(".sidebar-nav a:not(.section-link):not([data-nosearch])").forEach(function(e){var n=e.href,t=e.getAttribute("href"),a=r.parse(n).path;a&&-1===i.indexOf(a)&&!Docsify.util.isAbsolutePath(t)&&i.push(a)}),i}(a.router):t.paths,r="";if(e&&t.pathNamespaces){var i=n[0];if(Array.isArray(t.pathNamespaces))r=t.pathNamespaces.find(function(e){return i.startsWith(e)})||r;else if(t.pathNamespaces instanceof RegExp){var o=i.match(t.pathNamespaces);o&&(r=o[0])}}var s=function(e){return e?f.EXPIRE_KEY+"/"+e:f.EXPIRE_KEY}(t.namespace)+r,c=function(e){return e?f.INDEX_KEY+"/"+e:f.INDEX_KEY}(t.namespace)+r,d=localStorage.getItem(s)<Date.now();if(h=JSON.parse(localStorage.getItem(c)),d)h={};else if(!e)return;var p=n.length,l=0;n.forEach(function(n){if(h[n])return l++;Docsify.get(a.router.getFile(n),!1,a.config.requestHeaders).then(function(e){h[n]=u(n,e,a.router,t.depth),p===++l&&function(e,n,t){localStorage.setItem(n,Date.now()+e),localStorage.setItem(t,JSON.stringify(h))}(t.maxAge,s,c)})})}var d,m="";function r(e){var n=Docsify.dom.find("div.search"),t=Docsify.dom.find(n,".results-panel"),a=Docsify.dom.find(n,".clear-button"),r=Docsify.dom.find(".sidebar-nav"),i=Docsify.dom.find(".app-name");if(!e)return t.classList.remove("show"),a.classList.remove("show"),t.innerHTML="",void(d.hideOtherSidebarContent&&(r.classList.remove("hide"),i.classList.remove("hide")));var o=c(e),s="";o.forEach(function(e){s+='<div class="matching-post">\n<a href="'+e.url+'">\n<h2>'+e.title+"</h2>\n<p>"+e.content+"</p>\n</a>\n</div>"}),t.classList.add("show"),a.classList.add("show"),t.innerHTML=s||'<p class="empty">'+m+"</p>",d.hideOtherSidebarContent&&(r.classList.add("hide"),i.classList.add("hide"))}function a(e){d=e}function o(e,n){var t=n.router.parse().query.s;a(e),Docsify.dom.style("\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .input-wrap {\n display: flex;\n align-items: center;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 0 7px;\n line-height: 36px;\n font-size: 14px;\n border: 1px solid transparent;\n}\n\n.search input:focus {\n box-shadow: 0 0 5px var(--theme-color, #42b983);\n border: 1px solid var(--theme-color, #42b983);\n}\n\n.search input::-webkit-search-decoration,\n.search input::-webkit-search-cancel-button,\n.search input {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n.search .clear-button {\n cursor: pointer;\n width: 36px;\n text-align: right;\n display: none;\n}\n\n.search .clear-button.show {\n display: block;\n}\n\n.search .clear-button svg {\n transform: scale(.5);\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}\n\n.app-name.hide, .sidebar-nav.hide {\n display: none;\n}"),function(e){void 0===e&&(e="");var n='<div class="input-wrap">\n <input type="search" value="'+e+'" aria-label="Search text" />\n <div class="clear-button">\n <svg width="26" height="24">\n <circle cx="12" cy="12" r="11" fill="#ccc" />\n <path stroke="white" stroke-width="2" d="M8.25,8.25,15.75,15.75" />\n <path stroke="white" stroke-width="2"d="M8.25,15.75,15.75,8.25" />\n </svg>\n </div>\n </div>\n <div class="results-panel"></div>\n </div>',t=Docsify.dom.create("div",n),a=Docsify.dom.find("aside");Docsify.dom.toggleClass(t,"search"),Docsify.dom.before(a,t)}(t),function(){var e,n=Docsify.dom.find("div.search"),t=Docsify.dom.find(n,"input"),a=Docsify.dom.find(n,".input-wrap");Docsify.dom.on(n,"click",function(e){return-1===["A","H2","P","EM"].indexOf(e.target.tagName)&&e.stopPropagation()}),Docsify.dom.on(t,"input",function(n){clearTimeout(e),e=setTimeout(function(e){return r(n.target.value.trim())},100)}),Docsify.dom.on(a,"click",function(e){"INPUT"!==e.target.tagName&&(t.value="",r())})}(),t&&setTimeout(function(e){return r(t)},500)}function s(e,n){a(e),function(e,n){var t=Docsify.dom.getNode('.search input[type="search"]');if(t)if("string"==typeof e)t.placeholder=e;else{var a=Object.keys(e).filter(function(e){return-1<n.indexOf(e)})[0];t.placeholder=e[a]}}(e.placeholder,n.route.path),function(e,n){if("string"==typeof e)m=e;else{var t=Object.keys(e).filter(function(e){return-1<n.indexOf(e)})[0];m=e[t]}}(e.noData,n.route.path)}var g={placeholder:"Type to search",noData:"No Results!",paths:"auto",depth:2,maxAge:864e5,hideOtherSidebarContent:!1,namespace:void 0,pathNamespaces:void 0};$docsify.plugins=[].concat(function(e,n){var t=Docsify.util,a=n.config.search||g;Array.isArray(a)?g.paths=a:"object"==typeof a&&(g.paths=Array.isArray(a.paths)?a.paths:"auto",g.maxAge=t.isPrimitive(a.maxAge)?a.maxAge:g.maxAge,g.placeholder=a.placeholder||g.placeholder,g.noData=a.noData||g.noData,g.depth=a.depth||g.depth,g.hideOtherSidebarContent=a.hideOtherSidebarContent||g.hideOtherSidebarContent,g.namespace=a.namespace||g.namespace,g.pathNamespaces=a.pathNamespaces||g.pathNamespaces);var r="auto"===g.paths;e.mounted(function(e){o(g,n),r||i(g,n)}),e.doneEach(function(e){s(g,n),r&&i(g,n)})},$docsify.plugins)}(); diff --git a/_scripts/sweetalert.min.js b/_scripts/sweetalert.min.js new file mode 100644 index 0000000000000000000000000000000000000000..5c997b4451129fa21b655c7b87ab8c2fbd1d4def --- /dev/null +++ b/_scripts/sweetalert.min.js @@ -0,0 +1 @@ +!function(e,t,n){"use strict";!function o(e,t,n){function a(s,l){if(!t[s]){if(!e[s]){var i="function"==typeof require&&require;if(!l&&i)return i(s,!0);if(r)return r(s,!0);var u=new Error("Cannot find module '"+s+"'");throw u.code="MODULE_NOT_FOUND",u}var c=t[s]={exports:{}};e[s][0].call(c.exports,function(t){var n=e[s][1][t];return a(n?n:t)},c,c.exports,o,e,t,n)}return t[s].exports}for(var r="function"==typeof require&&require,s=0;s<n.length;s++)a(n[s]);return a}({1:[function(o,a,r){var s=function(e){return e&&e.__esModule?e:{"default":e}};Object.defineProperty(r,"__esModule",{value:!0});var l,i,u,c,d=o("./modules/handle-dom"),f=o("./modules/utils"),p=o("./modules/handle-swal-dom"),m=o("./modules/handle-click"),v=o("./modules/handle-key"),y=s(v),h=o("./modules/default-params"),b=s(h),g=o("./modules/set-params"),w=s(g);r["default"]=u=c=function(){function o(e){var t=a;return t[e]===n?b["default"][e]:t[e]}var a=arguments[0];if(d.addClass(t.body,"stop-scrolling"),p.resetInput(),a===n)return f.logStr("SweetAlert expects at least 1 attribute!"),!1;var r=f.extend({},b["default"]);switch(typeof a){case"string":r.title=a,r.text=arguments[1]||"",r.type=arguments[2]||"";break;case"object":if(a.title===n)return f.logStr('Missing "title" argument!'),!1;r.title=a.title;for(var s in b["default"])r[s]=o(s);r.confirmButtonText=r.showCancelButton?"Confirm":b["default"].confirmButtonText,r.confirmButtonText=o("confirmButtonText"),r.doneFunction=arguments[1]||null;break;default:return f.logStr('Unexpected type of argument! Expected "string" or "object", got '+typeof a),!1}w["default"](r),p.fixVerticalPosition(),p.openModal(arguments[1]);for(var u=p.getModal(),v=u.querySelectorAll("button"),h=["onclick","onmouseover","onmouseout","onmousedown","onmouseup","onfocus"],g=function(e){return m.handleButton(e,r,u)},C=0;C<v.length;C++)for(var S=0;S<h.length;S++){var x=h[S];v[C][x]=g}p.getOverlay().onclick=g,l=e.onkeydown;var k=function(e){return y["default"](e,r,u)};e.onkeydown=k,e.onfocus=function(){setTimeout(function(){i!==n&&(i.focus(),i=n)},0)},c.enableButtons()},u.setDefaults=c.setDefaults=function(e){if(!e)throw new Error("userParams is required");if("object"!=typeof e)throw new Error("userParams has to be a object");f.extend(b["default"],e)},u.close=c.close=function(){var o=p.getModal();d.fadeOut(p.getOverlay(),5),d.fadeOut(o,5),d.removeClass(o,"showSweetAlert"),d.addClass(o,"hideSweetAlert"),d.removeClass(o,"visible");var a=o.querySelector(".sa-icon.sa-success");d.removeClass(a,"animate"),d.removeClass(a.querySelector(".sa-tip"),"animateSuccessTip"),d.removeClass(a.querySelector(".sa-long"),"animateSuccessLong");var r=o.querySelector(".sa-icon.sa-error");d.removeClass(r,"animateErrorIcon"),d.removeClass(r.querySelector(".sa-x-mark"),"animateXMark");var s=o.querySelector(".sa-icon.sa-warning");return d.removeClass(s,"pulseWarning"),d.removeClass(s.querySelector(".sa-body"),"pulseWarningIns"),d.removeClass(s.querySelector(".sa-dot"),"pulseWarningIns"),setTimeout(function(){var e=o.getAttribute("data-custom-class");d.removeClass(o,e)},300),d.removeClass(t.body,"stop-scrolling"),e.onkeydown=l,e.previousActiveElement&&e.previousActiveElement.focus(),i=n,clearTimeout(o.timeout),!0},u.showInputError=c.showInputError=function(e){var t=p.getModal(),n=t.querySelector(".sa-input-error");d.addClass(n,"show");var o=t.querySelector(".sa-error-container");d.addClass(o,"show"),o.querySelector("p").innerHTML=e,setTimeout(function(){u.enableButtons()},1),t.querySelector("input").focus()},u.resetInputError=c.resetInputError=function(e){if(e&&13===e.keyCode)return!1;var t=p.getModal(),n=t.querySelector(".sa-input-error");d.removeClass(n,"show");var o=t.querySelector(".sa-error-container");d.removeClass(o,"show")},u.disableButtons=c.disableButtons=function(){var e=p.getModal(),t=e.querySelector("button.confirm"),n=e.querySelector("button.cancel");t.disabled=!0,n.disabled=!0},u.enableButtons=c.enableButtons=function(){var e=p.getModal(),t=e.querySelector("button.confirm"),n=e.querySelector("button.cancel");t.disabled=!1,n.disabled=!1},"undefined"!=typeof e?e.sweetAlert=e.swal=u:f.logStr("SweetAlert is a frontend module!"),a.exports=r["default"]},{"./modules/default-params":2,"./modules/handle-click":3,"./modules/handle-dom":4,"./modules/handle-key":5,"./modules/handle-swal-dom":6,"./modules/set-params":8,"./modules/utils":9}],2:[function(e,t,n){Object.defineProperty(n,"__esModule",{value:!0});var o={title:"",text:"",type:null,allowOutsideClick:!1,showConfirmButton:!0,showCancelButton:!1,closeOnConfirm:!0,closeOnCancel:!0,confirmButtonText:"OK",confirmButtonColor:"#8CD4F5",cancelButtonText:"Cancel",imageUrl:null,imageSize:null,timer:null,customClass:"",html:!1,animation:!0,allowEscapeKey:!0,inputType:"text",inputPlaceholder:"",inputValue:"",showLoaderOnConfirm:!1};n["default"]=o,t.exports=n["default"]},{}],3:[function(t,n,o){Object.defineProperty(o,"__esModule",{value:!0});var a=t("./utils"),r=(t("./handle-swal-dom"),t("./handle-dom")),s=function(t,n,o){function s(e){m&&n.confirmButtonColor&&(p.style.backgroundColor=e)}var u,c,d,f=t||e.event,p=f.target||f.srcElement,m=-1!==p.className.indexOf("confirm"),v=-1!==p.className.indexOf("sweet-overlay"),y=r.hasClass(o,"visible"),h=n.doneFunction&&"true"===o.getAttribute("data-has-done-function");switch(m&&n.confirmButtonColor&&(u=n.confirmButtonColor,c=a.colorLuminance(u,-.04),d=a.colorLuminance(u,-.14)),f.type){case"mouseover":s(c);break;case"mouseout":s(u);break;case"mousedown":s(d);break;case"mouseup":s(c);break;case"focus":var b=o.querySelector("button.confirm"),g=o.querySelector("button.cancel");m?g.style.boxShadow="none":b.style.boxShadow="none";break;case"click":var w=o===p,C=r.isDescendant(o,p);if(!w&&!C&&y&&!n.allowOutsideClick)break;m&&h&&y?l(o,n):h&&y||v?i(o,n):r.isDescendant(o,p)&&"BUTTON"===p.tagName&&sweetAlert.close()}},l=function(e,t){var n=!0;r.hasClass(e,"show-input")&&(n=e.querySelector("input").value,n||(n="")),t.doneFunction(n),t.closeOnConfirm&&sweetAlert.close(),t.showLoaderOnConfirm&&sweetAlert.disableButtons()},i=function(e,t){var n=String(t.doneFunction).replace(/\s/g,""),o="function("===n.substring(0,9)&&")"!==n.substring(9,10);o&&t.doneFunction(!1),t.closeOnCancel&&sweetAlert.close()};o["default"]={handleButton:s,handleConfirm:l,handleCancel:i},n.exports=o["default"]},{"./handle-dom":4,"./handle-swal-dom":6,"./utils":9}],4:[function(n,o,a){Object.defineProperty(a,"__esModule",{value:!0});var r=function(e,t){return new RegExp(" "+t+" ").test(" "+e.className+" ")},s=function(e,t){r(e,t)||(e.className+=" "+t)},l=function(e,t){var n=" "+e.className.replace(/[\t\r\n]/g," ")+" ";if(r(e,t)){for(;n.indexOf(" "+t+" ")>=0;)n=n.replace(" "+t+" "," ");e.className=n.replace(/^\s+|\s+$/g,"")}},i=function(e){var n=t.createElement("div");return n.appendChild(t.createTextNode(e)),n.innerHTML},u=function(e){e.style.opacity="",e.style.display="block"},c=function(e){if(e&&!e.length)return u(e);for(var t=0;t<e.length;++t)u(e[t])},d=function(e){e.style.opacity="",e.style.display="none"},f=function(e){if(e&&!e.length)return d(e);for(var t=0;t<e.length;++t)d(e[t])},p=function(e,t){for(var n=t.parentNode;null!==n;){if(n===e)return!0;n=n.parentNode}return!1},m=function(e){e.style.left="-9999px",e.style.display="block";var t,n=e.clientHeight;return t="undefined"!=typeof getComputedStyle?parseInt(getComputedStyle(e).getPropertyValue("padding-top"),10):parseInt(e.currentStyle.padding),e.style.left="",e.style.display="none","-"+parseInt((n+t)/2)+"px"},v=function(e,t){if(+e.style.opacity<1){t=t||16,e.style.opacity=0,e.style.display="block";var n=+new Date,o=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){e.style.opacity=+e.style.opacity+(new Date-n)/100,n=+new Date,+e.style.opacity<1&&setTimeout(o,t)});o()}e.style.display="block"},y=function(e,t){t=t||16,e.style.opacity=1;var n=+new Date,o=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){e.style.opacity=+e.style.opacity-(new Date-n)/100,n=+new Date,+e.style.opacity>0?setTimeout(o,t):e.style.display="none"});o()},h=function(n){if("function"==typeof MouseEvent){var o=new MouseEvent("click",{view:e,bubbles:!1,cancelable:!0});n.dispatchEvent(o)}else if(t.createEvent){var a=t.createEvent("MouseEvents");a.initEvent("click",!1,!1),n.dispatchEvent(a)}else t.createEventObject?n.fireEvent("onclick"):"function"==typeof n.onclick&&n.onclick()},b=function(t){"function"==typeof t.stopPropagation?(t.stopPropagation(),t.preventDefault()):e.event&&e.event.hasOwnProperty("cancelBubble")&&(e.event.cancelBubble=!0)};a.hasClass=r,a.addClass=s,a.removeClass=l,a.escapeHtml=i,a._show=u,a.show=c,a._hide=d,a.hide=f,a.isDescendant=p,a.getTopMargin=m,a.fadeIn=v,a.fadeOut=y,a.fireClick=h,a.stopEventPropagation=b},{}],5:[function(t,o,a){Object.defineProperty(a,"__esModule",{value:!0});var r=t("./handle-dom"),s=t("./handle-swal-dom"),l=function(t,o,a){var l=t||e.event,i=l.keyCode||l.which,u=a.querySelector("button.confirm"),c=a.querySelector("button.cancel"),d=a.querySelectorAll("button[tabindex]");if(-1!==[9,13,32,27].indexOf(i)){for(var f=l.target||l.srcElement,p=-1,m=0;m<d.length;m++)if(f===d[m]){p=m;break}9===i?(f=-1===p?u:p===d.length-1?d[0]:d[p+1],r.stopEventPropagation(l),f.focus(),o.confirmButtonColor&&s.setFocusStyle(f,o.confirmButtonColor)):13===i?("INPUT"===f.tagName&&(f=u,u.focus()),f=-1===p?u:n):27===i&&o.allowEscapeKey===!0?(f=c,r.fireClick(f,l)):f=n}};a["default"]=l,o.exports=a["default"]},{"./handle-dom":4,"./handle-swal-dom":6}],6:[function(n,o,a){var r=function(e){return e&&e.__esModule?e:{"default":e}};Object.defineProperty(a,"__esModule",{value:!0});var s=n("./utils"),l=n("./handle-dom"),i=n("./default-params"),u=r(i),c=n("./injected-html"),d=r(c),f=".sweet-alert",p=".sweet-overlay",m=function(){var e=t.createElement("div");for(e.innerHTML=d["default"];e.firstChild;)t.body.appendChild(e.firstChild)},v=function(e){function t(){return e.apply(this,arguments)}return t.toString=function(){return e.toString()},t}(function(){var e=t.querySelector(f);return e||(m(),e=v()),e}),y=function(){var e=v();return e?e.querySelector("input"):void 0},h=function(){return t.querySelector(p)},b=function(e,t){var n=s.hexToRgb(t);e.style.boxShadow="0 0 2px rgba("+n+", 0.8), inset 0 0 0 1px rgba(0, 0, 0, 0.05)"},g=function(n){var o=v();l.fadeIn(h(),10),l.show(o),l.addClass(o,"showSweetAlert"),l.removeClass(o,"hideSweetAlert"),e.previousActiveElement=t.activeElement;var a=o.querySelector("button.confirm");a.focus(),setTimeout(function(){l.addClass(o,"visible")},500);var r=o.getAttribute("data-timer");if("null"!==r&&""!==r){var s=n;o.timeout=setTimeout(function(){var e=(s||null)&&"true"===o.getAttribute("data-has-done-function");e?s(null):sweetAlert.close()},r)}},w=function(){var e=v(),t=y();l.removeClass(e,"show-input"),t.value=u["default"].inputValue,t.setAttribute("type",u["default"].inputType),t.setAttribute("placeholder",u["default"].inputPlaceholder),C()},C=function(e){if(e&&13===e.keyCode)return!1;var t=v(),n=t.querySelector(".sa-input-error");l.removeClass(n,"show");var o=t.querySelector(".sa-error-container");l.removeClass(o,"show")},S=function(){var e=v();e.style.marginTop=l.getTopMargin(v())};a.sweetAlertInitialize=m,a.getModal=v,a.getOverlay=h,a.getInput=y,a.setFocusStyle=b,a.openModal=g,a.resetInput=w,a.resetInputError=C,a.fixVerticalPosition=S},{"./default-params":2,"./handle-dom":4,"./injected-html":7,"./utils":9}],7:[function(e,t,n){Object.defineProperty(n,"__esModule",{value:!0});var o='<div class="sweet-overlay" tabIndex="-1"></div><div class="sweet-alert"><div class="sa-icon sa-error">\n <span class="sa-x-mark">\n <span class="sa-line sa-left"></span>\n <span class="sa-line sa-right"></span>\n </span>\n </div><div class="sa-icon sa-warning">\n <span class="sa-body"></span>\n <span class="sa-dot"></span>\n </div><div class="sa-icon sa-info"></div><div class="sa-icon sa-success">\n <span class="sa-line sa-tip"></span>\n <span class="sa-line sa-long"></span>\n\n <div class="sa-placeholder"></div>\n <div class="sa-fix"></div>\n </div><div class="sa-icon sa-custom"></div><h2>Title</h2>\n <p>Text</p>\n <fieldset>\n <input type="text" tabIndex="3" />\n <div class="sa-input-error"></div>\n </fieldset><div class="sa-error-container">\n <div class="icon">!</div>\n <p>Not valid!</p>\n </div><div class="sa-button-container">\n <button class="cancel" tabIndex="2">Cancel</button>\n <div class="sa-confirm-button-container">\n <button class="confirm" tabIndex="1">OK</button><div class="la-ball-fall">\n <div></div>\n <div></div>\n <div></div>\n </div>\n </div>\n </div></div>';n["default"]=o,t.exports=n["default"]},{}],8:[function(e,t,o){Object.defineProperty(o,"__esModule",{value:!0});var a=e("./utils"),r=e("./handle-swal-dom"),s=e("./handle-dom"),l=["error","warning","info","success","input","prompt"],i=function(e){var t=r.getModal(),o=t.querySelector("h2"),i=t.querySelector("p"),u=t.querySelector("button.cancel"),c=t.querySelector("button.confirm");if(o.innerHTML=e.html?e.title:s.escapeHtml(e.title).split("\n").join("<br>"),i.innerHTML=e.html?e.text:s.escapeHtml(e.text||"").split("\n").join("<br>"),e.text&&s.show(i),e.customClass)s.addClass(t,e.customClass),t.setAttribute("data-custom-class",e.customClass);else{var d=t.getAttribute("data-custom-class");s.removeClass(t,d),t.setAttribute("data-custom-class","")}if(s.hide(t.querySelectorAll(".sa-icon")),e.type&&!a.isIE8()){var f=function(){for(var o=!1,a=0;a<l.length;a++)if(e.type===l[a]){o=!0;break}if(!o)return logStr("Unknown alert type: "+e.type),{v:!1};var i=["success","error","warning","info"],u=n;-1!==i.indexOf(e.type)&&(u=t.querySelector(".sa-icon.sa-"+e.type),s.show(u));var c=r.getInput();switch(e.type){case"success":s.addClass(u,"animate"),s.addClass(u.querySelector(".sa-tip"),"animateSuccessTip"),s.addClass(u.querySelector(".sa-long"),"animateSuccessLong");break;case"error":s.addClass(u,"animateErrorIcon"),s.addClass(u.querySelector(".sa-x-mark"),"animateXMark");break;case"warning":s.addClass(u,"pulseWarning"),s.addClass(u.querySelector(".sa-body"),"pulseWarningIns"),s.addClass(u.querySelector(".sa-dot"),"pulseWarningIns");break;case"input":case"prompt":c.setAttribute("type",e.inputType),c.value=e.inputValue,c.setAttribute("placeholder",e.inputPlaceholder),s.addClass(t,"show-input"),setTimeout(function(){c.focus(),c.addEventListener("keyup",swal.resetInputError)},400)}}();if("object"==typeof f)return f.v}if(e.imageUrl){var p=t.querySelector(".sa-icon.sa-custom");p.style.backgroundImage="url("+e.imageUrl+")",s.show(p);var m=80,v=80;if(e.imageSize){var y=e.imageSize.toString().split("x"),h=y[0],b=y[1];h&&b?(m=h,v=b):logStr("Parameter imageSize expects value with format WIDTHxHEIGHT, got "+e.imageSize)}p.setAttribute("style",p.getAttribute("style")+"width:"+m+"px; height:"+v+"px")}t.setAttribute("data-has-cancel-button",e.showCancelButton),e.showCancelButton?u.style.display="inline-block":s.hide(u),t.setAttribute("data-has-confirm-button",e.showConfirmButton),e.showConfirmButton?c.style.display="inline-block":s.hide(c),e.cancelButtonText&&(u.innerHTML=s.escapeHtml(e.cancelButtonText)),e.confirmButtonText&&(c.innerHTML=s.escapeHtml(e.confirmButtonText)),e.confirmButtonColor&&(c.style.backgroundColor=e.confirmButtonColor,c.style.borderLeftColor=e.confirmLoadingButtonColor,c.style.borderRightColor=e.confirmLoadingButtonColor,r.setFocusStyle(c,e.confirmButtonColor)),t.setAttribute("data-allow-outside-click",e.allowOutsideClick);var g=e.doneFunction?!0:!1;t.setAttribute("data-has-done-function",g),e.animation?"string"==typeof e.animation?t.setAttribute("data-animation",e.animation):t.setAttribute("data-animation","pop"):t.setAttribute("data-animation","none"),t.setAttribute("data-timer",e.timer)};o["default"]=i,t.exports=o["default"]},{"./handle-dom":4,"./handle-swal-dom":6,"./utils":9}],9:[function(t,n,o){Object.defineProperty(o,"__esModule",{value:!0});var a=function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},r=function(e){var t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?parseInt(t[1],16)+", "+parseInt(t[2],16)+", "+parseInt(t[3],16):null},s=function(){return e.attachEvent&&!e.addEventListener},l=function(t){e.console&&e.console.log("SweetAlert: "+t)},i=function(e,t){e=String(e).replace(/[^0-9a-f]/gi,""),e.length<6&&(e=e[0]+e[0]+e[1]+e[1]+e[2]+e[2]),t=t||0;var n,o,a="#";for(o=0;3>o;o++)n=parseInt(e.substr(2*o,2),16),n=Math.round(Math.min(Math.max(0,n+n*t),255)).toString(16),a+=("00"+n).substr(n.length);return a};o.extend=a,o.hexToRgb=r,o.isIE8=s,o.logStr=l,o.colorLuminance=i},{}]},{},[1]),"function"==typeof define&&define.amd?define(function(){return sweetAlert}):"undefined"!=typeof module&&module.exports&&(module.exports=sweetAlert)}(window,document); \ No newline at end of file diff --git a/_scripts/zoom-image.min.js b/_scripts/zoom-image.min.js new file mode 100644 index 0000000000000000000000000000000000000000..8e713ce7361a02c3f9cdbcc0805a6a10f0639f12 --- /dev/null +++ b/_scripts/zoom-image.min.js @@ -0,0 +1 @@ +!function(){function t(e){return"IMG"===e.tagName}function w(e){return e&&1===e.nodeType}function L(e){return".svg"===(e.currentSrc||e.src).substr(-4).toLowerCase()}function p(e){try{return Array.isArray(e)?e.filter(t):function(e){return NodeList.prototype.isPrototypeOf(e)}(e)?[].slice.call(e).filter(t):w(e)?[e].filter(t):"string"==typeof e?[].slice.call(document.querySelectorAll(e)).filter(t):[]}catch(e){throw new TypeError("The provided selector is invalid.\nExpects a CSS selector, a Node element, a NodeList or an array.\nSee: https://github.com/francoischalifour/medium-zoom")}}function g(e,t){var o=H({bubbles:!1,cancelable:!1,detail:void 0},t);if("function"==typeof window.CustomEvent)return new CustomEvent(e,o);var n=document.createEvent("CustomEvent");return n.initCustomEvent(e,o.bubbles,o.cancelable,o.detail),n}function v(e,t){function o(){for(var e=arguments,t=arguments.length,o=Array(t),n=0;n<t;n++)o[n]=e[n];var i=o.reduce(function(e,t){return[].concat(e,p(t))},[]);return i.filter(function(e){return-1===c.indexOf(e)}).forEach(function(e){c.push(e),e.classList.add("medium-zoom-image")}),d.forEach(function(e){var t=e.type,o=e.listener,n=e.options;i.forEach(function(e){e.addEventListener(t,o,n)})}),f}function n(e){function r(){var e={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},t=void 0,o=void 0;if(b.container)if(b.container instanceof Object)t=(e=H({},e,b.container)).width-e.left-e.right-2*b.margin,o=e.height-e.top-e.bottom-2*b.margin;else{var n=(w(b.container)?b.container:document.querySelector(b.container)).getBoundingClientRect(),i=n.width,r=n.height,d=n.left,a=n.top;e=H({},e,{width:i,height:r,left:d,top:a})}t=t||e.width-2*b.margin,o=o||e.height-2*b.margin;var m=E.zoomedHd||E.original,c=L(m)?t:m.naturalWidth||t,l=L(m)?o:m.naturalHeight||o,u=m.getBoundingClientRect(),s=u.top,f=u.left,p=u.width,g=u.height,v=Math.min(c,t)/p,h=Math.min(l,o)/g,z=Math.min(v,h),y="scale("+z+") translate3d("+((t-p)/2-f+b.margin+e.left)/z+"px, "+((o-g)/2-s+b.margin+e.top)/z+"px, 0)";E.zoomed.style.transform=y,E.zoomedHd&&(E.zoomedHd.style.transform=y)}var d=(0<arguments.length&&void 0!==e?e:{}).target;return new a(function(t){if(d&&-1===c.indexOf(d))t(f);else{if(E.zoomed)t(f);else{if(d)E.original=d;else{if(!(0<c.length))return void t(f);var e=c;E.original=e[0]}if(E.original.dispatchEvent(g("medium-zoom:open",{detail:{zoom:f}})),u=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,l=!0,E.zoomed=function(e){var t=e.getBoundingClientRect(),o=t.top,n=t.left,i=t.width,r=t.height,d=e.cloneNode(),a=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,m=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;return d.removeAttribute("id"),d.style.position="absolute",d.style.top=o+a+"px",d.style.left=n+m+"px",d.style.width=i+"px",d.style.height=r+"px",d.style.transform="",d}(E.original),document.body.appendChild(s),b.template){var o=w(b.template)?b.template:document.querySelector(b.template);E.template=document.createElement("div"),E.template.appendChild(o.content.cloneNode(!0)),document.body.appendChild(E.template)}if(document.body.appendChild(E.zoomed),window.requestAnimationFrame(function(){document.body.classList.add("medium-zoom--opened")}),E.original.classList.add("medium-zoom-image--hidden"),E.zoomed.classList.add("medium-zoom-image--opened"),E.zoomed.addEventListener("click",m),E.zoomed.addEventListener("transitionend",function e(){l=!1,E.zoomed.removeEventListener("transitionend",e),E.original.dispatchEvent(g("medium-zoom:opened",{detail:{zoom:f}})),t(f)}),E.original.getAttribute("data-zoom-src")){E.zoomedHd=E.zoomed.cloneNode(),E.zoomedHd.removeAttribute("srcset"),E.zoomedHd.removeAttribute("sizes"),E.zoomedHd.src=E.zoomed.getAttribute("data-zoom-src"),E.zoomedHd.onerror=function(){clearInterval(n),console.warn("Unable to reach the zoom image target "+E.zoomedHd.src),E.zoomedHd=null,r()};var n=setInterval(function(){E.zoomedHd.complete&&(clearInterval(n),E.zoomedHd.classList.add("medium-zoom-image--opened"),E.zoomedHd.addEventListener("click",m),document.body.appendChild(E.zoomedHd),r())},10)}else if(E.original.hasAttribute("srcset")){E.zoomedHd=E.zoomed.cloneNode(),E.zoomedHd.removeAttribute("sizes"),E.zoomedHd.removeAttribute("loading");var i=E.zoomedHd.addEventListener("load",function(){E.zoomedHd.removeEventListener("load",i),E.zoomedHd.classList.add("medium-zoom-image--opened"),E.zoomedHd.addEventListener("click",m),document.body.appendChild(E.zoomedHd),r()})}else r()}}})}var i=1<arguments.length&&void 0!==t?t:{},a=window.Promise||function(e){function t(){}e(t,t)},m=function(){return new a(function(t){if(!l&&E.original){l=!0,document.body.classList.remove("medium-zoom--opened"),E.zoomed.style.transform="",E.zoomedHd&&(E.zoomedHd.style.transform=""),E.template&&(E.template.style.transition="opacity 150ms",E.template.style.opacity=0),E.original.dispatchEvent(g("medium-zoom:close",{detail:{zoom:f}})),E.zoomed.addEventListener("transitionend",function e(){E.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(E.zoomed),E.zoomedHd&&document.body.removeChild(E.zoomedHd),document.body.removeChild(s),E.zoomed.classList.remove("medium-zoom-image--opened"),E.template&&document.body.removeChild(E.template),l=!1,E.zoomed.removeEventListener("transitionend",e),E.original.dispatchEvent(g("medium-zoom:closed",{detail:{zoom:f}})),E.original=null,E.zoomed=null,E.zoomedHd=null,E.template=null,t(f)})}else t(f)})},r=function(e){var t=(0<arguments.length&&void 0!==e?e:{}).target;return E.original?m():n({target:t})},c=[],d=[],l=!1,u=0,b=i,E={original:null,zoomed:null,zoomedHd:null,template:null};"[object Object]"===Object.prototype.toString.call(e)?b=e:!e&&"string"!=typeof e||o(e);var s=function(e){var t=document.createElement("div");return t.classList.add("medium-zoom-overlay"),t.style.background=e,t}((b=H({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},b)).background);document.addEventListener("click",function(e){var t=e.target;t!==s?-1!==c.indexOf(t)&&r({target:t}):m()}),document.addEventListener("keyup",function(e){var t=e.key||e.keyCode;"Escape"!==t&&"Esc"!==t&&27!==t||m()}),document.addEventListener("scroll",function(){if(!l&&E.original){var e=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(u-e)>b.scrollOffset&&setTimeout(m,150)}}),window.addEventListener("resize",m);var f={open:n,close:m,toggle:r,update:function(e){var t=0<arguments.length&&void 0!==e?e:{},o=t;if(t.background&&(s.style.background=t.background),t.container&&t.container instanceof Object&&(o.container=H({},b.container,t.container)),t.template){var n=w(t.template)?t.template:document.querySelector(t.template);o.template=n}return b=H({},b,o),c.forEach(function(e){e.dispatchEvent(g("medium-zoom:update",{detail:{zoom:f}}))}),f},clone:function(e){return v(H({},b,0<arguments.length&&void 0!==e?e:{}))},attach:o,detach:function(){for(var e=arguments,t=arguments.length,o=Array(t),n=0;n<t;n++)o[n]=e[n];E.zoomed&&m();var i=0<o.length?o.reduce(function(e,t){return[].concat(e,p(t))},[]):c;return i.forEach(function(e){e.classList.remove("medium-zoom-image"),e.dispatchEvent(g("medium-zoom:detach",{detail:{zoom:f}}))}),c=c.filter(function(e){return-1===i.indexOf(e)}),f},on:function(t,o,e){var n=2<arguments.length&&void 0!==e?e:{};return c.forEach(function(e){e.addEventListener("medium-zoom:"+t,o,n)}),d.push({type:"medium-zoom:"+t,listener:o,options:n}),f},off:function(t,o,e){var n=2<arguments.length&&void 0!==e?e:{};return c.forEach(function(e){e.removeEventListener("medium-zoom:"+t,o,n)}),d=d.filter(function(e){return!(e.type==="medium-zoom:"+t&&e.listener.toString()===o.toString())}),f},getOptions:function(){return b},getImages:function(){return c},getZoomedImage:function(){return E.original}};return f}var H=Object.assign||function(e){for(var t=arguments,o=1;o<arguments.length;o++){var n=t[o];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e};!function(e,t){void 0===t&&(t={});var o=t.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===o&&n.firstChild?n.insertBefore(i,n.firstChild):n.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}(".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}");var n=Element.prototype.matches||Element.prototype.webkitMatchesSelector||Element.prototype.msMatchesSelector;$docsify.plugins=[].concat(function(e){var o;e.doneEach(function(e){var t=Array.apply(null,document.querySelectorAll(".markdown-section img:not(.emoji):not([data-no-zoom])"));t=t.filter(function(e){return!1===n.call(e,"a img")}),o&&o.detach(),o=v(t)})},$docsify.plugins)}(); diff --git a/_sidebar.md b/_sidebar.md new file mode 100644 index 0000000000000000000000000000000000000000..56f41fcb777290b67cfc6c72d8303c13085bfeca --- /dev/null +++ b/_sidebar.md @@ -0,0 +1,4 @@ + +<!-- docs/_sidebar.md --> + +- [文档在线提交说明](/README.md) diff --git a/_styles/sidebar.min.css b/_styles/sidebar.min.css new file mode 100644 index 0000000000000000000000000000000000000000..2238a684cf99667049956d140fb904c5c96ac517 --- /dev/null +++ b/_styles/sidebar.min.css @@ -0,0 +1 @@ +.sidebar-nav li{position:relative;margin:0;cursor:pointer}.sidebar-nav ul:not(.app-sub-sidebar)>li:not(.file)::before{content:'';display:block;position:absolute;top:13px;left:-12px;height:6px;width:6px;border-right:2px solid #3f6b85;border-bottom:2px solid #3f6b85;transform:rotate(-45deg);transition:transform .1s}.sidebar-nav ul:not(.app-sub-sidebar)>li.open::before{transform:rotate(45deg)}.sidebar-nav ul:not(.app-sub-sidebar)>li.collapse::before{transform:rotate(-45deg)} \ No newline at end of file diff --git a/_styles/sweetalert.min.css b/_styles/sweetalert.min.css new file mode 100644 index 0000000000000000000000000000000000000000..f698e78dba302859dbcb251c1d916f3dbf452046 --- /dev/null +++ b/_styles/sweetalert.min.css @@ -0,0 +1,5 @@ +body.stop-scrolling{height:100%;overflow:hidden}.sweet-overlay{background-color:black;-ms-filter:"alpha(opacity=40)";background-color:rgba(0,0,0,0.4);position:fixed;left:0;right:0;top:0;bottom:0;display:none;z-index:10000}.sweet-alert{background-color:white;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;width:478px;padding:17px;border-radius:5px;text-align:center;position:fixed;left:50%;top:50%;margin-left:-256px;margin-top:-200px;overflow:hidden;display:none;z-index:99999}@media all and (max-width:540px){.sweet-alert{width:auto;margin-left:0;margin-right:0;left:15px;right:15px}}.sweet-alert h2{color:#575757;font-size:30px;text-align:center;font-weight:600;text-transform:none;position:relative;margin:25px 0;padding:0;line-height:40px;display:block}.sweet-alert p{color:#797979;font-size:16px;text-align:center;font-weight:300;position:relative;text-align:inherit;float:none;margin:0;padding:0;line-height:normal}.sweet-alert fieldset{border:0;position:relative}.sweet-alert .sa-error-container{background-color:#f1f1f1;margin-left:-17px;margin-right:-17px;overflow:hidden;padding:0 10px;max-height:0;webkit-transition:padding .15s,max-height .15s;transition:padding .15s,max-height .15s}.sweet-alert .sa-error-container.show{padding:10px 0;max-height:100px;webkit-transition:padding .2s,max-height .2s;transition:padding .25s,max-height .25s}.sweet-alert .sa-error-container .icon{display:inline-block;width:24px;height:24px;border-radius:50%;background-color:#ea7d7d;color:white;line-height:24px;text-align:center;margin-right:3px}.sweet-alert .sa-error-container p{display:inline-block}.sweet-alert .sa-input-error{position:absolute;top:29px;right:26px;width:20px;height:20px;opacity:0;-webkit-transform:scale(0.5);transform:scale(0.5);-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transition:all .1s;transition:all .1s}.sweet-alert .sa-input-error::before,.sweet-alert .sa-input-error::after{content:"";width:20px;height:6px;background-color:#f06e57;border-radius:3px;position:absolute;top:50%;margin-top:-4px;left:50%;margin-left:-9px}.sweet-alert .sa-input-error::before{-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.sweet-alert .sa-input-error::after{-webkit-transform:rotate(45deg);transform:rotate(45deg)}.sweet-alert .sa-input-error.show{opacity:1;-webkit-transform:scale(1);transform:scale(1)}.sweet-alert input{width:100%;box-sizing:border-box;border-radius:3px;border:1px solid #d7d7d7;height:43px;margin-top:10px;margin-bottom:17px;font-size:18px;box-shadow:inset 0 1px 1px rgba(0,0,0,0.06);padding:0 12px;display:none;-webkit-transition:all .3s;transition:all .3s}.sweet-alert input:focus{outline:0;box-shadow:0 0 3px #c4e6f5;border:1px solid #b4dbed}.sweet-alert input:focus::-moz-placeholder{transition:opacity .3s .03s ease;opacity:.5}.sweet-alert input:focus:-ms-input-placeholder{transition:opacity .3s .03s ease;opacity:.5}.sweet-alert input:focus::-webkit-input-placeholder{transition:opacity .3s .03s ease;opacity:.5}.sweet-alert input::-moz-placeholder{color:#bdbdbd}.sweet-alert input:-ms-input-placeholder{color:#bdbdbd}.sweet-alert input::-webkit-input-placeholder{color:#bdbdbd}.sweet-alert.show-input input{display:block}.sweet-alert .sa-confirm-button-container{display:inline-block;position:relative}.sweet-alert .la-ball-fall{position:absolute;left:50%;top:50%;margin-left:-27px;margin-top:4px;opacity:0;visibility:hidden}.sweet-alert button{background-color:#8cd4f5;color:white;border:0;box-shadow:none;font-size:17px;font-weight:500;-webkit-border-radius:4px;border-radius:5px;padding:10px 32px;margin:26px 5px 0 5px;cursor:pointer}.sweet-alert button:focus{outline:0;box-shadow:0 0 2px rgba(128,179,235,0.5),inset 0 0 0 1px rgba(0,0,0,0.05)}.sweet-alert button:hover{background-color:#7ecff4}.sweet-alert button:active{background-color:#5dc2f1}.sweet-alert button.cancel{background-color:#c1c1c1}.sweet-alert button.cancel:hover{background-color:#b9b9b9}.sweet-alert button.cancel:active{background-color:#a8a8a8}.sweet-alert button.cancel:focus{box-shadow:rgba(197,205,211,0.8) 0 0 2px,rgba(0,0,0,0.0470588) 0 0 0 1px inset !important}.sweet-alert button[disabled]{opacity:.6;cursor:default}.sweet-alert button.confirm[disabled]{color:transparent}.sweet-alert button.confirm[disabled] ~ .la-ball-fall{opacity:1;visibility:visible;transition-delay:0}.sweet-alert button::-moz-focus-inner{border:0}.sweet-alert[data-has-cancel-button=false] button{box-shadow:none !important}.sweet-alert[data-has-confirm-button=false][data-has-cancel-button=false]{padding-bottom:40px}.sweet-alert .sa-icon{width:80px;height:80px;border:4px solid gray;-webkit-border-radius:40px;border-radius:40px;border-radius:50%;margin:20px auto;padding:0;position:relative;box-sizing:content-box}.sweet-alert .sa-icon.sa-error{border-color:#f27474}.sweet-alert .sa-icon.sa-error .sa-x-mark{position:relative;display:block}.sweet-alert .sa-icon.sa-error .sa-line{position:absolute;height:5px;width:47px;background-color:#f27474;display:block;top:37px;border-radius:2px}.sweet-alert .sa-icon.sa-error .sa-line.sa-left{-webkit-transform:rotate(45deg);transform:rotate(45deg);left:17px}.sweet-alert .sa-icon.sa-error .sa-line.sa-right{-webkit-transform:rotate(-45deg);transform:rotate(-45deg);right:16px}.sweet-alert .sa-icon.sa-warning{border-color:#f8bb86}.sweet-alert .sa-icon.sa-warning .sa-body{position:absolute;width:5px;height:47px;left:50%;top:10px;-webkit-border-radius:2px;border-radius:2px;margin-left:-2px;background-color:#f8bb86}.sweet-alert .sa-icon.sa-warning .sa-dot{position:absolute;width:7px;height:7px;-webkit-border-radius:50%;border-radius:50%;margin-left:-3px;left:50%;bottom:10px;background-color:#f8bb86}.sweet-alert .sa-icon.sa-info{border-color:#c9dae1}.sweet-alert .sa-icon.sa-info::before{content:"";position:absolute;width:5px;height:29px;left:50%;bottom:17px;border-radius:2px;margin-left:-2px;background-color:#c9dae1}.sweet-alert .sa-icon.sa-info::after{content:"";position:absolute;width:7px;height:7px;border-radius:50%;margin-left:-3px;top:19px;background-color:#c9dae1}.sweet-alert .sa-icon.sa-success{border-color:#a5dc86}.sweet-alert .sa-icon.sa-success::before,.sweet-alert .sa-icon.sa-success::after{content:'';-webkit-border-radius:40px;border-radius:40px;border-radius:50%;position:absolute;width:60px;height:120px;background:white;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.sweet-alert .sa-icon.sa-success::before{-webkit-border-radius:120px 0 0 120px;border-radius:120px 0 0 120px;top:-7px;left:-33px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:60px 60px;transform-origin:60px 60px}.sweet-alert .sa-icon.sa-success::after{-webkit-border-radius:0 120px 120px 0;border-radius:0 120px 120px 0;top:-11px;left:30px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg);-webkit-transform-origin:0 60px;transform-origin:0 60px}.sweet-alert .sa-icon.sa-success .sa-placeholder{width:80px;height:80px;border:4px solid rgba(165,220,134,0.2);-webkit-border-radius:40px;border-radius:40px;border-radius:50%;box-sizing:content-box;position:absolute;left:-4px;top:-4px;z-index:2}.sweet-alert .sa-icon.sa-success .sa-fix{width:5px;height:90px;background-color:white;position:absolute;left:28px;top:8px;z-index:1;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.sweet-alert .sa-icon.sa-success .sa-line{height:5px;background-color:#a5dc86;display:block;border-radius:2px;position:absolute;z-index:2}.sweet-alert .sa-icon.sa-success .sa-line.sa-tip{width:25px;left:14px;top:46px;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.sweet-alert .sa-icon.sa-success .sa-line.sa-long{width:47px;right:8px;top:38px;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.sweet-alert .sa-icon.sa-custom{background-size:contain;border-radius:0;border:0;background-position:center center;background-repeat:no-repeat}@-webkit-keyframes showSweetAlert{0{transform:scale(0.7);-webkit-transform:scale(0.7)}45%{transform:scale(1.05);-webkit-transform:scale(1.05)}80%{transform:scale(0.95);-webkit-transform:scale(0.95)}100%{transform:scale(1);-webkit-transform:scale(1)}}@keyframes showSweetAlert{0{transform:scale(0.7);-webkit-transform:scale(0.7)}45%{transform:scale(1.05);-webkit-transform:scale(1.05)}80%{transform:scale(0.95);-webkit-transform:scale(0.95)}100%{transform:scale(1);-webkit-transform:scale(1)}}@-webkit-keyframes hideSweetAlert{0{transform:scale(1);-webkit-transform:scale(1)}100%{transform:scale(0.5);-webkit-transform:scale(0.5)}}@keyframes hideSweetAlert{0{transform:scale(1);-webkit-transform:scale(1)}100%{transform:scale(0.5);-webkit-transform:scale(0.5)}}@-webkit-keyframes slideFromTop{0{top:0}100%{top:50%}}@keyframes slideFromTop{0{top:0}100%{top:50%}}@-webkit-keyframes slideToTop{0{top:50%}100%{top:0}}@keyframes slideToTop{0{top:50%}100%{top:0}}@-webkit-keyframes slideFromBottom{0{top:70%}100%{top:50%}}@keyframes slideFromBottom{0{top:70%}100%{top:50%}}@-webkit-keyframes slideToBottom{0{top:50%}100%{top:70%}}@keyframes slideToBottom{0{top:50%}100%{top:70%}}.showSweetAlert[data-animation=pop]{-webkit-animation:showSweetAlert .3s;animation:showSweetAlert .3s}.showSweetAlert[data-animation=none]{-webkit-animation:none;animation:none}.showSweetAlert[data-animation=slide-from-top]{-webkit-animation:slideFromTop .3s;animation:slideFromTop .3s}.showSweetAlert[data-animation=slide-from-bottom]{-webkit-animation:slideFromBottom .3s;animation:slideFromBottom .3s}.hideSweetAlert[data-animation=pop]{-webkit-animation:hideSweetAlert .2s;animation:hideSweetAlert .2s}.hideSweetAlert[data-animation=none]{-webkit-animation:none;animation:none}.hideSweetAlert[data-animation=slide-from-top]{-webkit-animation:slideToTop .4s;animation:slideToTop .4s}.hideSweetAlert[data-animation=slide-from-bottom]{-webkit-animation:slideToBottom .3s;animation:slideToBottom .3s}@-webkit-keyframes animateSuccessTip{0{width:0;left:1px;top:19px}54%{width:0;left:1px;top:19px}70%{width:50px;left:-8px;top:37px}84%{width:17px;left:21px;top:48px}100%{width:25px;left:14px;top:45px}}@keyframes animateSuccessTip{0{width:0;left:1px;top:19px}54%{width:0;left:1px;top:19px}70%{width:50px;left:-8px;top:37px}84%{width:17px;left:21px;top:48px}100%{width:25px;left:14px;top:45px}}@-webkit-keyframes animateSuccessLong{0{width:0;right:46px;top:54px}65%{width:0;right:46px;top:54px}84%{width:55px;right:0;top:35px}100%{width:47px;right:8px;top:38px}}@keyframes animateSuccessLong{0{width:0;right:46px;top:54px}65%{width:0;right:46px;top:54px}84%{width:55px;right:0;top:35px}100%{width:47px;right:8px;top:38px}}@-webkit-keyframes rotatePlaceholder{0{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}5%{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}12%{transform:rotate(-405deg);-webkit-transform:rotate(-405deg)}100%{transform:rotate(-405deg);-webkit-transform:rotate(-405deg)}}@keyframes rotatePlaceholder{0{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}5%{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}12%{transform:rotate(-405deg);-webkit-transform:rotate(-405deg)}100%{transform:rotate(-405deg);-webkit-transform:rotate(-405deg)}}.animateSuccessTip{-webkit-animation:animateSuccessTip .75s;animation:animateSuccessTip .75s}.animateSuccessLong{-webkit-animation:animateSuccessLong .75s;animation:animateSuccessLong .75s}.sa-icon.sa-success.animate::after{-webkit-animation:rotatePlaceholder 4.25s ease-in;animation:rotatePlaceholder 4.25s ease-in}@-webkit-keyframes animateErrorIcon{0{transform:rotateX(100deg);-webkit-transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);-webkit-transform:rotateX(0);opacity:1}}@keyframes animateErrorIcon{0{transform:rotateX(100deg);-webkit-transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);-webkit-transform:rotateX(0);opacity:1}}.animateErrorIcon{-webkit-animation:animateErrorIcon .5s;animation:animateErrorIcon .5s}@-webkit-keyframes animateXMark{0{transform:scale(0.4);-webkit-transform:scale(0.4);margin-top:26px;opacity:0}50%{transform:scale(0.4);-webkit-transform:scale(0.4);margin-top:26px;opacity:0}80%{transform:scale(1.15);-webkit-transform:scale(1.15);margin-top:-6px}100%{transform:scale(1);-webkit-transform:scale(1);margin-top:0;opacity:1}}@keyframes animateXMark{0{transform:scale(0.4);-webkit-transform:scale(0.4);margin-top:26px;opacity:0}50%{transform:scale(0.4);-webkit-transform:scale(0.4);margin-top:26px;opacity:0}80%{transform:scale(1.15);-webkit-transform:scale(1.15);margin-top:-6px}100%{transform:scale(1);-webkit-transform:scale(1);margin-top:0;opacity:1}}.animateXMark{-webkit-animation:animateXMark .5s;animation:animateXMark .5s}@-webkit-keyframes pulseWarning{0{border-color:#f8d486}100%{border-color:#f8bb86}}@keyframes pulseWarning{0{border-color:#f8d486}100%{border-color:#f8bb86}}.pulseWarning{-webkit-animation:pulseWarning .75s infinite alternate;animation:pulseWarning .75s infinite alternate}@-webkit-keyframes pulseWarningIns{0{background-color:#f8d486}100%{background-color:#f8bb86}}@keyframes pulseWarningIns{0{background-color:#f8d486}100%{background-color:#f8bb86}}.pulseWarningIns{-webkit-animation:pulseWarningIns .75s infinite alternate;animation:pulseWarningIns .75s infinite alternate}@-webkit-keyframes rotate-loading{0{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes rotate-loading{0{transform:rotate(0)}100%{transform:rotate(360deg)}}.sweet-alert .sa-icon.sa-error .sa-line.sa-left{-ms-transform:rotate(45deg) \9}.sweet-alert .sa-icon.sa-error .sa-line.sa-right{-ms-transform:rotate(-45deg) \9}.sweet-alert .sa-icon.sa-success{border-color:transparent\9}.sweet-alert .sa-icon.sa-success .sa-line.sa-tip{-ms-transform:rotate(45deg) \9}.sweet-alert .sa-icon.sa-success .sa-line.sa-long{-ms-transform:rotate(-45deg) \9}/*! + * Load Awesome v1.1.0 (http://github.danielcardoso.net/load-awesome/) + * Copyright 2015 Daniel Cardoso <@DanielCardoso> + * Licensed under MIT + */.la-ball-fall,.la-ball-fall>div{position:relative;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.la-ball-fall{display:block;font-size:0;color:#fff}.la-ball-fall.la-dark{color:#333}.la-ball-fall>div{display:inline-block;float:none;background-color:currentColor;border:0 solid currentColor}.la-ball-fall{width:54px;height:18px}.la-ball-fall>div{width:10px;height:10px;margin:4px;border-radius:100%;opacity:0;-webkit-animation:ball-fall 1s ease-in-out infinite;-moz-animation:ball-fall 1s ease-in-out infinite;-o-animation:ball-fall 1s ease-in-out infinite;animation:ball-fall 1s ease-in-out infinite}.la-ball-fall>div:nth-child(1){-webkit-animation-delay:-200ms;-moz-animation-delay:-200ms;-o-animation-delay:-200ms;animation-delay:-200ms}.la-ball-fall>div:nth-child(2){-webkit-animation-delay:-100ms;-moz-animation-delay:-100ms;-o-animation-delay:-100ms;animation-delay:-100ms}.la-ball-fall>div:nth-child(3){-webkit-animation-delay:0;-moz-animation-delay:0;-o-animation-delay:0;animation-delay:0}.la-ball-fall.la-sm{width:26px;height:8px}.la-ball-fall.la-sm>div{width:4px;height:4px;margin:2px}.la-ball-fall.la-2x{width:108px;height:36px}.la-ball-fall.la-2x>div{width:20px;height:20px;margin:8px}.la-ball-fall.la-3x{width:162px;height:54px}.la-ball-fall.la-3x>div{width:30px;height:30px;margin:12px}@-webkit-keyframes ball-fall{0{opacity:0;-webkit-transform:translateY(-145%);transform:translateY(-145%)}10%{opacity:.5}20%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}80%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}90%{opacity:.5}100%{opacity:0;-webkit-transform:translateY(145%);transform:translateY(145%)}}@-moz-keyframes ball-fall{0{opacity:0;-moz-transform:translateY(-145%);transform:translateY(-145%)}10%{opacity:.5}20%{opacity:1;-moz-transform:translateY(0);transform:translateY(0)}80%{opacity:1;-moz-transform:translateY(0);transform:translateY(0)}90%{opacity:.5}100%{opacity:0;-moz-transform:translateY(145%);transform:translateY(145%)}}@-o-keyframes ball-fall{0{opacity:0;-o-transform:translateY(-145%);transform:translateY(-145%)}10%{opacity:.5}20%{opacity:1;-o-transform:translateY(0);transform:translateY(0)}80%{opacity:1;-o-transform:translateY(0);transform:translateY(0)}90%{opacity:.5}100%{opacity:0;-o-transform:translateY(145%);transform:translateY(145%)}}@keyframes ball-fall{0{opacity:0;-webkit-transform:translateY(-145%);-moz-transform:translateY(-145%);-o-transform:translateY(-145%);transform:translateY(-145%)}10%{opacity:.5}20%{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}80%{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}90%{opacity:.5}100%{opacity:0;-webkit-transform:translateY(145%);-moz-transform:translateY(145%);-o-transform:translateY(145%);transform:translateY(145%)}} \ No newline at end of file diff --git a/_styles/vue.css b/_styles/vue.css new file mode 100644 index 0000000000000000000000000000000000000000..80e62ef132592ac438e3279796da70f6e9957093 --- /dev/null +++ b/_styles/vue.css @@ -0,0 +1,872 @@ +@import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600"); +* { + -webkit-font-smoothing: antialiased; + -webkit-overflow-scrolling: touch; + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-text-size-adjust: none; + -webkit-touch-callout: none; + box-sizing: border-box; +} +body:not(.ready) { + overflow: hidden; +} +body:not(.ready) [data-cloak], +body:not(.ready) .app-nav, +body:not(.ready) > nav { + display: none; +} +div#app { + font-size: 30px; + font-weight: lighter; + margin: 40vh auto; + text-align: center; +} +div#app:empty::before { + content: 'Loading...'; +} +.emoji { + height: 1.2rem; + vertical-align: middle; +} +.progress { + background-color: var(--theme-color, #42b983); + height: 2px; + left: 0px; + position: fixed; + right: 0px; + top: 0px; + transition: width 0.2s, opacity 0.4s; + width: 0%; + z-index: 999999; +} +.search a:hover { + color: var(--theme-color, #42b983); +} +.search .search-keyword { + color: var(--theme-color, #42b983); + font-style: normal; + font-weight: bold; +} +html, +body { + height: 100%; +} +body { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + color: #34495e; + font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; + font-size: 18px; + letter-spacing: 0.2px; + margin: 0; + overflow-x: hidden; +} +img { + max-width: 100%; +} +a[disabled] { + cursor: not-allowed; + opacity: 0.6; +} +kbd { + border: solid 1px #ccc; + border-radius: 3px; + display: inline-block; + font-size: 12px !important; + line-height: 12px; + margin-bottom: 3px; + padding: 3px 5px; + vertical-align: middle; +} +li input[type='checkbox'] { + margin: 0 0.2em 0.25em 0; + vertical-align: middle; +} +.app-nav { + margin: 25px 60px 0 0; + position: absolute; + right: 0; + text-align: right; + z-index: 10; +/* navbar dropdown */ +} +.app-nav.no-badge { + margin-right: 25px; +} +.app-nav p { + margin: 0; +} +.app-nav > a { + margin: 0 1rem; + padding: 5px 0; +} +.app-nav ul, +.app-nav li { + display: inline-block; + list-style: none; + margin: 0; +} +.app-nav a { + color: inherit; + font-size: 16px; + text-decoration: none; + transition: color 0.3s; +} +.app-nav a:hover { + color: var(--theme-color, #42b983); +} +.app-nav a.active { + border-bottom: 2px solid var(--theme-color, #42b983); + color: var(--theme-color, #42b983); +} +.app-nav li { + display: inline-block; + margin: 0 1rem; + padding: 5px 0; + position: relative; + cursor: pointer; +} +.app-nav li ul { + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: #ccc; + border-radius: 4px; + box-sizing: border-box; + display: none; + max-height: calc(100vh - 61px); + overflow-y: auto; + padding: 10px 0; + position: absolute; + right: -15px; + text-align: left; + top: 100%; + white-space: nowrap; +} +.app-nav li ul li { + display: block; + font-size: 14px; + line-height: 1rem; + margin: 0; + margin: 8px 14px; + white-space: nowrap; +} +.app-nav li ul a { + display: block; + font-size: inherit; + margin: 0; + padding: 0; +} +.app-nav li ul a.active { + border-bottom: 0; +} +.app-nav li:hover ul { + display: block; +} +.github-corner { + border-bottom: 0; + position: fixed; + right: 0; + text-decoration: none; + top: 0; + z-index: 1; +} +.github-corner:hover .octo-arm { + -webkit-animation: octocat-wave 560ms ease-in-out; + animation: octocat-wave 560ms ease-in-out; +} +.github-corner svg { + color: #fff; + fill: var(--theme-color, #42b983); + height: 80px; + width: 80px; +} +main { + display: block; + position: relative; + width: 100vw; + height: 100%; + z-index: 0; +} +main.hidden { + display: none; +} +.anchor { + display: inline-block; + text-decoration: none; + transition: all 0.3s; +} +.anchor span { + color: #34495e; +} +.anchor:hover { + text-decoration: underline; +} +.sidebar { + border-right: 15px solid rgba(0,0,0,0.07); + overflow-y: auto; + padding: 40px 5px 0 0; + position: absolute; + font-size: 1.0rem; + font-weight: 700; + top: 0; + bottom: 0; + left: 30px; + transition: transform 250ms ease-out; + width: 300; + z-index: 20; +} +.sidebar > h1 { + margin: 0 auto 1rem; + font-size: 1.5rem; + font-weight: 700; + text-align: center; +} +.sidebar > h1 a { + color: inherit; + color: #2973b7; + text-decoration: none; +} +.sidebar > h1 .app-nav { + display: block; + position: static; +} +.sidebar .sidebar-nav { + line-height: 2em; + padding-bottom: 40px; +} +.sidebar li.collapse .app-sub-sidebar { + display: none; +} +.sidebar ul { + margin: 0 0 0 15px; + padding: 0; +} +.sidebar li > p { + font-weight: 700; + margin: 0; +} +.sidebar ul, +.sidebar ul li { + list-style: none; +} +.sidebar ul li a { + border-bottom: none; + display: block; +} +.sidebar ul li ul { + padding-left: 20px; +} +.sidebar::-webkit-scrollbar { + width: 4px; +} +.sidebar::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 4px; +} +.sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(136,136,136,0.4); +} +.sidebar:hover::-webkit-scrollbar-track { + background: rgba(136,136,136,0.1); +} +.sidebar-toggle { + background-color: transparent; + background-color: rgba(255,255,255,0.8); + border: 0; + outline: none; + padding: 10px; + position: absolute; + bottom: 0; + left: 0; + text-align: center; + transition: opacity 0.3s; + width: 284px; + z-index: 30; + cursor: pointer; +} +.sidebar-toggle:hover .sidebar-toggle-button { + opacity: 0.4; +} +.sidebar-toggle span { + background-color: var(--theme-color, #42b983); + display: block; + margin-bottom: 4px; + width: 20px; + height: 4px; +} +body.sticky .sidebar, +body.sticky .sidebar-toggle { + position: fixed; +} +.content { + padding-top: 60px; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 300px; + transition: left 250ms ease; +} +.markdown-section { + margin: 0 auto; + max-width: 80%; + padding: 30px 15px 40px 15px; + position: relative; +} +.markdown-section > * { + box-sizing: border-box; + font-size: inherit; +} +.markdown-section > :first-child { + margin-top: 0 !important; +} +.markdown-section hr { + border: none; + border-bottom: 1px solid #eee; + margin: 2em 0; +} +.markdown-section iframe { + border: 1px solid #eee; +/* fix horizontal overflow on iOS Safari */ + width: 1px; + min-width: 100%; +} +.markdown-section table { + border-collapse: collapse; + border-spacing: 0; + display: block; + margin-bottom: 1rem; + overflow: auto; + width: 100%; +} +.markdown-section th { + border: 1px solid #ddd; + font-weight: bold; + padding: 6px 13px; +} +.markdown-section td { + border: 1px solid #ddd; + padding: 6px 13px; +} +.markdown-section tr { + border-top: 1px solid #ccc; +} +.markdown-section tr:nth-child(2n) { + background-color: #f8f8f8; +} +.markdown-section p.tip { + background-color: #f8f8f8; + border-bottom-right-radius: 2px; + border-left: 4px solid #f66; + border-top-right-radius: 2px; + margin: 2em 0; + padding: 12px 24px 12px 30px; + position: relative; +} +.markdown-section p.tip:before { + background-color: #f66; + border-radius: 100%; + color: #fff; + content: '!'; + font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; + font-size: 14px; + font-weight: bold; + left: -12px; + line-height: 20px; + position: absolute; + height: 20px; + width: 20px; + text-align: center; + top: 14px; +} +.markdown-section p.tip code { + background-color: #efefef; +} +.markdown-section p.tip em { + color: #34495e; +} +.markdown-section p.warn { + background: rgba(66,185,131,0.1); + border-radius: 2px; + padding: 1rem; +} +.markdown-section ul.task-list > li { + list-style-type: none; +} +body.close .sidebar { + transform: translateX(-1000px); +} +body.close .sidebar-toggle { + width: auto; +} +body.close .content { + left: 0; +} +@media print { + .github-corner, + .sidebar-toggle, + .sidebar, + .app-nav { + display: none; + } +} +@media screen and (max-width: 768px) { + .github-corner, + .sidebar-toggle, + .sidebar { + position: fixed; + } + .app-nav { + margin-top: 16px; + } + .app-nav li ul { + top: 30px; + } + main { + height: auto; + overflow-x: hidden; + } + .sidebar { + left: -300px; + transition: transform 250ms ease-out; + } + .content { + left: 0; + max-width: 100vw; + position: static; + padding-top: 20px; + transition: transform 250ms ease; + } + .app-nav, + .github-corner { + transition: transform 250ms ease-out; + } + .sidebar-toggle { + background-color: transparent; + width: auto; + padding: 30px 30px 10px 10px; + } + body.close .sidebar { + transform: translateX(300px); + } + body.close .sidebar-toggle { + background-color: rgba(255,255,255,0.8); + transition: 1s background-color; + width: 284px; + padding: 10px; + } + body.close .content { + transform: translateX(300px); + } + body.close .app-nav, + body.close .github-corner { + display: none; + } + .github-corner:hover .octo-arm { + -webkit-animation: none; + animation: none; + } + .github-corner .octo-arm { + -webkit-animation: octocat-wave 560ms ease-in-out; + animation: octocat-wave 560ms ease-in-out; + } +} +@-webkit-keyframes octocat-wave { + 0%, 100% { + transform: rotate(0); + } + 20%, 60% { + transform: rotate(-25deg); + } + 40%, 80% { + transform: rotate(10deg); + } +} +@keyframes octocat-wave { + 0%, 100% { + transform: rotate(0); + } + 20%, 60% { + transform: rotate(-25deg); + } + 40%, 80% { + transform: rotate(10deg); + } +} +section.cover { + align-items: center; + background-position: center center; + background-repeat: no-repeat; + background-size: cover; + height: 100vh; + width: 100vw; + display: none; +} +section.cover.show { + display: flex; +} +section.cover.has-mask .mask { + background-color: #fff; + opacity: 0.8; + position: absolute; + top: 0; + height: 100%; + width: 100%; +} +section.cover .cover-main { + flex: 1; + margin: -20px 16px 0; + text-align: center; + position: relative; +} +section.cover a { + color: inherit; + text-decoration: none; +} +section.cover a:hover { + text-decoration: none; +} +section.cover p { + line-height: 1.5rem; + margin: 1em 0; +} +section.cover h1 { + color: inherit; + font-size: 2.5rem; + font-weight: 500; + margin: 0.625rem 0 2.5rem; + position: relative; + text-align: center; +} +section.cover h1 a { + display: block; +} +section.cover h1 small { + bottom: -0.4375rem; + font-size: 1rem; + position: absolute; +} +section.cover blockquote { + font-size: 1.5rem; + text-align: center; +} +section.cover ul { + line-height: 1.8; + list-style-type: none; + margin: 1em auto; + max-width: 500px; + padding: 0; +} +/* section.cover .cover-main > p:last-child a { + border-color: var(--theme-color, #42b983); + border-radius: 2rem; + border-style: solid; + border-width: 1px; + box-sizing: border-box; + color: var(--theme-color, #42b983); + display: inline-block; + font-size: 1.05rem; + letter-spacing: 0.1rem; + margin: 0.5rem 1rem; + padding: 0.75em 2rem; + text-decoration: none; + transition: all 0.15s ease; +} +section.cover .cover-main > p:last-child a:last-child { + background-color: var(--theme-color, #42b983); + color: #fff; +} +section.cover .cover-main > p:last-child a:last-child:hover { + color: inherit; + opacity: 0.8; +} +section.cover .cover-main > p:last-child a:hover { + color: inherit; +} */ +section.cover .cover-main > p:last-child a { + border-color: var(--theme-color, #42b983); + border-radius: 2rem; + border-style: solid; + border-width: 2px; + box-sizing: border-box; + color: var(--theme-color, #42b983); + display: inline-block; + font-size: 1.25rem; + letter-spacing: 0.1rem; + margin: 0.5rem 1rem; + padding: 0.75em 2rem; + text-decoration: none; + transition: all 0.15s ease; +} +section.cover .cover-main > p:last-child a:first-child { + background-color: var(--theme-color, #42b983); + color: #fff; +} +section.cover .cover-main > p:last-child a:last-child:hover { + color: inherit; + opacity: 0.8; +} +section.cover .cover-main > p:last-child a:hover { + color: inherit; +} + +section.cover blockquote > p > a { + border-bottom: 2px solid var(--theme-color, #42b983); + transition: color 0.3s; +} +section.cover blockquote > p > a:hover { + color: var(--theme-color, #42b983); +} +body { + background-color: #fff; +} +/* sidebar */ +.sidebar { + background-color: #fff; + color: #3f6b85; + width:300px; + padding-left:5px; +} +.sidebar li { + margin: 6px 0 6px 0; +} +.sidebar ul li a { + display:inline-block; + width: 100%; + color: #3f6b85; + font-size: 15px; + font-weight: 500; + overflow: visible; + text-decoration: none; + text-overflow: ellipsis; + word-break: break-all; + line-height:22px; + padding-bottom:5px; + padding-top:5px; +} +.sidebar ul li a:hover { + text-decoration: underline; +} +.sidebar ul li ul { + padding: 0; +} +.sidebar ul li.active > a { + border-right: 3px solid; + color: var(--theme-color, #3f6b85); + font-size: 18px; + font-weight: 700; + background-color: #e3e6e9; +} +/* .app-sub-sidebar li::before { + content: '•'; + padding-right: 4px; + float: left; +} */ +/* markdown content found on pages */ +.markdown-section h1, +.markdown-section h2, +.markdown-section h3, +.markdown-section h4, +.markdown-section strong { + color: #2c3e50; + font-weight: 600; +} +.markdown-section a { + color: var(--theme-color, #42b983); + font-weight: 600; +} +.markdown-section h1 { + font-size: 2.2rem; + margin: 0 0 1rem; +} +.markdown-section h2 { + font-size: 2.0rem; + margin: 45px 0 0.8rem; +} +.markdown-section h3 { + font-size: 1.75rem; + margin: 40px 0 0.6rem; +} +.markdown-section h4 { + font-size: 1.5rem; +} +.markdown-section h5 { + font-size: 1rem; +} +.markdown-section h6 { + color: #777; + font-size: 1rem; +} +.markdown-section figure, +.markdown-section p { + margin: 1.2em 0; +} +.markdown-section p, +.markdown-section ul, +.markdown-section ol { + line-height: 1.6rem; + word-spacing: 0.05rem; +} +.markdown-section ul, +.markdown-section ol { + padding-left: 1.5rem; +} +.markdown-section blockquote { + border-left: 4px solid var(--theme-color, #42b983); + color: #858585; + margin: 2em 0; + padding-left: 20px; +} +.markdown-section blockquote p { + font-weight: 600; + margin-left: 0; +} +.markdown-section iframe { + margin: 1em 0; +} +.markdown-section em { + color: #7f8c8d; +} +.markdown-section code { + background-color: #f8f8f8; + border-radius: 2px; + color: #e96900; + font-family: 'Roboto Mono', Monaco, courier, monospace; + font-size: 0.8rem; + margin: 0 2px; + padding: 3px 5px; + white-space: pre-wrap; +} +.markdown-section pre { + -moz-osx-font-smoothing: initial; + -webkit-font-smoothing: initial; + background-color: #f8f8f8; + font-family: 'Roboto Mono', Monaco, courier, monospace; + line-height: 1.5rem; + margin: 1.2em 0; + overflow: auto; + padding: 0 1.4rem; + position: relative; + word-wrap: normal; +} +/* code highlight */ +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #997b19; +} +.token.namespace { + opacity: 0.7; +} +.token.boolean, +.token.number { + color: #c76b29; +} +.token.punctuation { + color: #525252; +} +.token.tag { + color: #2973b7; +} +.token.string { + color: var(--theme-color, #04834a); +} +.token.selector { + color: #6679cc; +} +.token.attr-name { + color: #2973b7; +} +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #22a2c9; +} +.token.attr-value, +.token.control, +.token.directive, +.token.unit { + color: var(--theme-color, #42b983); +} +.token.property, +.token.keyword, +.token.function { + color: #960063; +} +.token.statement, +.token.regex, +.token.atrule { + color: #22a2c9; +} +.token.placeholder, +.token.variable { + color: #3d8fd1; +} +.token.deleted { + text-decoration: line-through; +} +.token.inserted { + border-bottom: 1px dotted #202746; + text-decoration: none; +} +.token.italic { + font-style: italic; +} +.token.important, +.token.bold { + font-weight: bold; +} +.token.important { + color: #c94922; +} +.token.entity { + cursor: help; +} +.markdown-section pre > code { + -moz-osx-font-smoothing: initial; + -webkit-font-smoothing: initial; + background-color: #f8f8f8; + border-radius: 2px; + color: #8c972b; + display: block; + font-family: 'Roboto Mono', Monaco, courier, monospace; + font-size: 0.8rem; + line-height: inherit; + margin: 0 2px; + max-width: inherit; + overflow: inherit; + padding: 2.2em 5px; + white-space: inherit; +} +.markdown-section code::after, +.markdown-section code::before { + letter-spacing: 0.05rem; +} +code .token { + -moz-osx-font-smoothing: initial; + -webkit-font-smoothing: initial; + min-height: 1.5rem; + position: relative; + left: auto; +} +pre::after { + color: #ccc; + content: attr(data-lang); + font-size: 0.6rem; + font-weight: 600; + height: 15px; + line-height: 15px; + padding: 5px 10px 0; + position: absolute; + right: 0; + text-align: right; + top: 0; +} +.docsify-tabs--classic .docsify-tabs__content{ + margin-top: -3px; +} +.docsify-tabs--classic .docsify-tabs__tab~.docsify-tabs__tab{ + background:#fff; +} diff --git a/development-tools/_navbar.md b/development-tools/_navbar.md new file mode 100644 index 0000000000000000000000000000000000000000..5982080c0c2a4644e9039165eab3195305afad94 --- /dev/null +++ b/development-tools/_navbar.md @@ -0,0 +1,5 @@ +<!-- _navbar.md --> + +<!-- - Translations + - [:uk: English](/) + - [:cn: 中文](/zh-cn/) --> \ No newline at end of file diff --git a/development-tools/_sidebar.md b/development-tools/_sidebar.md new file mode 100644 index 0000000000000000000000000000000000000000..c70680b377e890927bf6bbe91ec97f7b712940f2 --- /dev/null +++ b/development-tools/_sidebar.md @@ -0,0 +1,32 @@ + +<!-- docs/_sidebar.md --> + +- RT-Thread Studio + - [快速开始](/development-tools/rtthread-studio/um/studio-user-begin.md) + - [用户手册](/development-tools/rtthread-studio/um/studio-user-manual.md) + - 应用开发 + - [快速上手](/development-tools/rtthread-studio/applications/quick-start/rtthread-studio-quick-start.md) + - [应用开发示例 - 线程](/development-tools/rtthread-studio/applications/thread/rtthread-studio-thread.md) + - [应用开发示例 - 串口设备](/development-tools/rtthread-studio/applications/uart/rtthread-studio-uart.md) + - 驱动开发 + - [驱动概况](/development-tools/rtthread-studio/drivers/drv-list/support-driver-list.md) + - [PIN 设备](/development-tools/rtthread-studio/drivers/pin/rtthread-studio-pin.md) + - 串口设备 + - [nano 版](/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/rtthread-studio-uart-nano-v3.1.3.md) + - [完整版](/development-tools/rtthread-studio/drivers/uart/v4.0.2/rtthread-studio-uart-v4.0.2.md) + - I2C 设备 + - [软件 I2C](/development-tools/rtthread-studio/drivers/soft-i2c/rtthread-studio-soft-i2c.md) + - [SPI 设备](/development-tools/rtthread-studio/drivers/spi/rtthread-studio-spi.md) + - [ETH 设备](/development-tools/rtthread-studio/drivers/eth/rtthread-studio-eth.md) + - [USB Device 设备](/development-tools/rtthread-studio/drivers/usb-device/rtthread-studio-usb-device.md) + - [其它驱动](/development-tools/rtthread-studio/drivers/cubemx/rtthread-studio-cubemx.md) + - [精彩项目集合](/development-tools/rtthread-studio/applications/project-collection/project-collection.md) + - [常见问题](/development-tools/rtthread-studio/faq/studio-faq.md) + - [更新日志](/development-tools/rtthread-studio/changelog/changelog.md) + +- Env 开发工具 + - [Env 用户手册](/development-tools/env/env.md) +- Scons 介绍 + - [Scons 构建工具](/development-tools/scons/scons.md) +- Kconfig 介绍 + - [Kconfig 语法](/development-tools/kconfig/kconfig.md) diff --git a/development-tools/env/env.md b/development-tools/env/env.md new file mode 100644 index 0000000000000000000000000000000000000000..c133baf5b0d8a4a36988cc96d6b2e89925492241 --- /dev/null +++ b/development-tools/env/env.md @@ -0,0 +1,284 @@ +# Env 用户手册 + +Env 是 RT-Thread 推出的开发辅助工具,针对基于 RT-Thread 操作系统的项目工程,提供编译构建环境、图形化系统配置及软件包管理功能。 + +其内置的 menuconfig 提供了简单易用的配置剪裁工具,可对内核、组件和软件包进行自由裁剪,使系统以搭积木的方式进行构建。 + +## 主要特性 + +- menuconfig 图形化配置界面,交互性好,操作逻辑强; +- 丰富的文字帮助说明,配置无需查阅文档; +- 使用灵活,自动处理依赖,功能开关彻底; +- 自动生成 rtconfig.h,无需手动修改; +- 使用 scons 工具生成工程,提供编译环境,操作简单; +- 提供多种软件包,模块化软件包耦合关联少,可维护性好; +- 软件包可在线下载,软件包持续集成,包可靠性高; + +## 准备工作 + +Env 工具包含了 RT-Thread 源代码开发编译环境和软件包管理系统。 + +- 从 RT-Thread 官网下载 [Env 工具](https://www.rt-thread.org/page/download.html)。 +- 在电脑上装好 git,软件包管理功能需要 git 的支持。git 的下载地址为`https://git-scm.com/downloads`,根据向导正确安装 git,并将 git 添加到系统环境变量。 +- 注意在工作环境中,所有的路径都不可以有中文字符或者空格。 + +## Env 的使用方法 + +### 打开 Env 控制台 + +RT-Thread 软件包环境主要以命令行控制台为主,同时以字符型界面来进行辅助,使得尽量减少修改配置文件的方式即可搭建好 RT-Thread 开发环境的方式。 +打开 Env 控制台有两种方式: + +#### 方法一:点击 Env 目录下可执行文件 + +进入 Env 目录,可以运行本目录下的 `env.exe`,如果打开失败可以尝试使用 `env.bat`。 + +#### 方法二:在文件夹中通过右键菜单打开 Env 控制台 + +Env 目录下有一张 `Add_Env_To_Right-click_Menu.png`(添加 Env 至右键菜单.png) 的图片,如下: + +![添加 Env 控制台到右键菜单](./figures/Add_Env_To_Right-click_Menu.png) + +根据图片上的步骤操作,就可以在任意文件夹下通过右键菜单来启动 Env 控制台。效果如下: + +![通过右键菜单来启动 Env 控制台](./figures/console.png) + +> [!NOTE] +> 注:因为需要设置 Env 进程的环境变量,第一次启动可能会出现杀毒软件误报的情况,如果遇到了 **杀毒软件误报** ,允许 Env 相关程序运行,然后将相关程序添加至白名单即可。 + +### 编译 BSP + +scons 是 RT-Thread 使用的编译构建工具,可以使用 scons 相关命令来编译 RT-Thread 。 + +#### 第一步:切换到 BSP 根目录 + +- 打开控制台后,可以在命令行模式下使用 cd 命令切换到你想要配置的 BSP 根目录中。 + +例如工程目录为: `rt-thread\bsp\stm32f429-apollo` : + +![stm32f429-apollo 工程目录](./figures/cd_cmd.png) + +#### 第二步:bsp 的编译 + +- Env 中携带了 `Python & scons` 环境,只需在 `rt-thread\bsp\stm32f429-apollo` 目录中运行 `scons` 命令即可使用默认的 ARM_GCC 工具链编译 bsp。 + +![scons 命令编译工程](./figures/use_scons.png) + +编译成功: + +![编译工程成功](./figures/scons_done.png) + +如果使用 mdk/iar 来进行项目开发,可以直接使用 BSP 中的工程文件或者使用以下命令中的其中一种,重新生成工程,再进行编译下载。 + +``` +scons --target=iar +scons --target=mdk4 +scons --target=mdk5 +``` + +更多 scons 教程,请参考 [《Scons 构建工具》](../scons/scons.md) + +### BSP 配置:menuconfig + +menuconfig 是一种图形化配置工具,RT-Thread 使用其对整个系统进行配置、裁剪。 + +#### 快捷键介绍 + +进入 BSP 根目录,输入 `menuconfig` 命令后即可打开其界面。 menuconfig 常用快捷键如图所示: + +![menuconfig 常用快捷键](./figures/hotkey.png) + +#### 修改配置 + +menuconfig 有多种类型的配置项,修改方法也有所不同,常见类型如下: + +- 开/关 型:使用空格键来选中或者关闭 +- 数值、字符串型:按下回车键后会出现对话框,在对话框中对配置项进行修改 + +#### 保存配置 + +选择好配置项之后按 ESC 键退出,选择保存修改即可自动生成 rtconfig.h 文件。此时再次使用 scons 命令就会根据新的 rtconfig.h 文件重新编译工程了。 + +### 软件包管理:package + +RT-Thread 提供一个软件包管理平台,这里存放了官方提供或开发者提供的软件包。该平台为开发者提供了众多可重用软件包的选择,这也是 RT-Thread 生态的重要组成部分。 + +[点击这里](https://github.com/RT-Thread-packages) 可以查看到 RT-Thread 官方的提供的软件包,绝大多数软件包都有详细的说明文档及使用示例。 + +> 提示:截止到 2018-03-13 ,当前软件包数量达到 **40+** + +**package** 工具作为 Env 的组成部分,为开发者提供了软件包的下载、更新、删除等管理功能。 + +Env 命令行输入 `pkgs` 可以看到命令简介: + +``` +> pkgs +usage: env.py package [-h] [--update] [--list] [--wizard] [--upgrade] + [--printenv] + +optional arguments: + -h, --help show this help message and exit + --update update packages, install or remove the packages as you set in + menuconfig + --list list target packages + --wizard create a package with wizard + --upgrade update local packages list from git repo + --printenv print environmental variables to check +``` + +#### 下载、更新、删除软件包 + +在下载、更新软件包前,需要先在 `menuconfig` 中 **开启** 你想要操作的软件包 + +这些软件包位于 `RT-Thread online packages` 菜单下,进入该菜单后,则可以看如下软件包分类: + +![软件包分类](./figures/menuconfig_packages_list.png) + +找到你需要的软件包然后选中开启,保存并退出 menuconfig 。此时软件包已被标记选中,但是还没有下载到本地,所以还无法使用。 + +- **下载** :如果软件包在本地已被选中,但是未下载,此时输入:`pkgs --update` ,该软件包自动下载; +- **更新** :如果选中的软件包在服务器端有更新,并且版本号选择的是 **latest** 。此时输入: `pkgs --update` ,该软件包将会在本地进行更新; +- **删除** :某个软件包如果无需使用,需要先在 menuconfig 中取消其的选中状态,然后再执行: `pkgs --update` 。此时本地已下载但未被选中的软件包将会被删除。 + +#### 升级本地软件包信息 + +随着 package 系统的不断壮大,会有越来越多的软件包加入进来,所以本地看到 menuconfig 中的软件包列表可能会与服务器 **不同步** 。使用 `pkgs --upgrade` 命令即可解决该问题,这个命令不仅会对本地的包信息进行更新同步,还会对 Env 的功能脚本进行升级,建议定期使用。 + +### Env 工具配置 + +- 新版本的 Env 工具中加入了自动更新软件包和自动生成 mdk/iar 工程的选项,默认是不开启的。可以使用 `menuconfig -s/--setting` 命令来进行配置。 + +* 使用 `menuconfig -s` 命令进入 Env 配置界面 + + ![Env 配置界面](./figures/menuconfig_s.png) + + 按下回车进入配置菜单,里面共有 3 个配置选项 + + ![配置选项](./figures/menuconfig_s_auto_update.png) + +3 个选项分别为: + +* **软件包自动更新功能**:在退出 menuconfig 功能后,会自动使用`pkgs --update`命令来下载并安装软件包,同时删除旧的软件包。本功能在下载在线软件包时使用。 + +* **自动创建 MDK 或 IAR 工程功能**:当修改 menuconfig 配置后 ,必须输入 `scons --target=xxx` 来重新生成工程。开启此功能,就会在退出 menuconfig 时,自动重新生成工程,无需再手动输入 scons 命令来重新生成工程。 + +* **使用镜像服务器下载软件包**:由于大部分软件包目前均存放在 GitHub 上,所以在国内的特殊环境下,下载体验非常差。开启此功能,可以通过 **国内镜像服务器** 下载软件包,大幅提高软件包的下载速度和稳定性,减少更新软件包和 submodule 时的等待时间,提升下载体验。 + +## 在项目中使用 Env + +### 使用 Env 的要求 + +- menuconfig 是 RT-Thread 3.0 以上版本的特性,推荐将 RT-Thread 更新到 3.0 以上版本。 +- 目前 RT-Thread 还没有对所有的 BSP 做 menuconfig 的支持,也就是说有些 BSP 暂时还不能使用 menuconfig 来进行配置,但常用的 BSP 都已经支持。 + +### menuconfig 中选项的修改方法 + +如果想在 menuconfig 的配置项中添加宏定义,则可以修改 BSP 下的 Kconfig 文件,修改方法可以在网络中搜索`Kconfig语法`关键字获得详细的说明文档,也可以参考 RT-Thread 中的 Kconfig 文件或者已经支持过 menuconfig 的 BSP 中的 Kconfig 文件。 + +### 新的项目添加 menuconfig 功能 + +这里的新项目指的是,**还未生成 .config 和 rtconfig.h** 的全新开发的项目。因为这两个文件,只有在 menuconfig 第一次保存时才会创建。具体流程如下: + + 1. 将已经支持 menuconfig 功能的 BSP 里面的 kconfig 文件拷贝到新的项目根目录中。 + 2. 注意修改 Kconfig 中的 RTT_ROOT 值为 RT-Thread 所在目录,否则可能提示找不到 RTT_ROOT 。 + 3. 使用 menuconfig 命令开始配置即可。 + +### 旧项目添加 menuconfig 功能 + +这里的旧项目指的是已经经过一段时间的开发,而且项目中存在已经修改过的 rtconfig.h文件 ,但是没有使用过 menuconfig 来配置的项目。具体流程如下: + + 1. 首先备份旧项目内的 rtconfig.h 文件。 + 2. 使用 `scons --genconfig` 命令根据已有的 rtconfig.h 生成 .config 文件,这里生成的 .config 文件保存了旧项目中 rtconfig.h 文件对项目的配置参数。 + 3. 将已经支持 menuconfig 功能的 BSP 里面的 kconfig 文件拷贝到要修改项目的根目录中。 + 4. 注意修改 Kconfig 中的 RTT_ROOT 值为 RT-Thread 所在目录,否则可能提示找不到 RTT_ROOT 。 + 5. 使用 menuconfig 命令来配置我们要修改的旧项目。menuconfig 会读取第二步生成的 .config 文件,并根据旧项目的配置参数生成新的 .config 文件和 rtconfig.h 文件 。 + 6. 对比检查新旧两份 rtconfig.h 文件,如果有不一致的地方,可以使用 menuconfig 命令对配置项进行调整。 + +### 用户软件包管理功能 + +实际开发项目时,开发者可能想要将已下载的软件包加入 git 管理,或者想自己管理该软件包。不希望 Env 工具再拉取该软件包的最新版本,此时可以使用用户软件包管理功能。 + +如果用户手动将 `EasyFlash-v4.1.0` 文件夹的后缀,也就是软件包的版本号删除,修改为 `EasyFlash`,此时再次使用 `pkgs --update` 命令将不会再拉取 `EasyFlash-v4.0.0` 软件包。Env 工具此时认为 EasyFlash 软件包由用户管理,此时使用 `pkgs --force-update` 命令才可以重新拉取附带 version 的新版本软件包。 + +![user_manage_package](figures/user_manage_package.png) + +## 使用 pip 扩展更多功能 + +在 Env 环境下暂时不能直接使用 Python 提供的 pip 工具来安装更多模块。如果需要在 Env 环境下使用 pip 功能,可以按照如下方法重新安装 pip 工具: + +1. 从地址 https://bootstrap.pypa.io/get-pip.py 下载 get-pip.py 文件,存放在磁盘中。 + +2. 在 **Env 环境下**执行 `python get-pip.py` 命令来重新安装 pip 工具。 + +3. pip 工具重新安装成功后,可以使用 `pip install module-name` 命令来安装所需模块。 + +## Env 工具使用注意事项 + +- 第一次使用 Env 推荐去官网下载最新版本的 Env 工具,新版本的 Env 会有更好的兼容性,也支持自动更新的命令。 +- 可以使用 Env 内置命令 pkgs --upgrade 来更新软件包列表和 Env 的功能代码,这样可以最大程度避免遇到已经修复的问题。 +- Env 所在路径不要有中文或者空格存在。 +- BSP 工程所在的路径不要有中文或者空格存在。 + +## 常见问题 + +### Q: Env 工具出现乱码怎么办? + +**A:** 首先检查是否有中文路径。 +检查 chcp 命令是否加入了系统环境变量,尝试使用 chcp 437 命令将字符格式改为英文。如果提示没有 chcp 命令,则考虑是没有加入到环境变量中。 +chcp 命令所在的目录可能在 system32 目录,添加到环境变量即可。 +[Env 工具乱码问题传送门](https://www.rt-thread.org/qa/forum.php?mod=viewthread&tid=5763&page=1#pid32213) 。 + +### Q: 提示找不到 git 命令? + + 'git' is not recognized as an internal or external command, operable program or batch file. + +**A:** 没安装 git,需要安装 git 并加入环境变量。 + +### Q: 提示找不到 CMD 命令? + +**A:** 计算机右键–>> 属性—>> 高级系统设置—->> 环境变量,`C:\Windows\System32`; 加入系统环境变量即可 + +### Q: 运行 python 的时候提示 no module named site 怎么办? + +**A:** 计算机右键–>> 属性—>> 高级系统设置—->> 环境变量,在管理员的用户变量中,新建变量名为 PYTHONHOME,变量值为:`F:\git_repositories\env\tools\Python27` (是 Env 里面 Python 的安装路径),注意后面不要加 “;”,否则会无效。 如果添加 PYTHONHOME 没好,再用同样的方法添加 PYTHONPATH。就可以解决这个问题了。 + +有一篇博文详细的描述了这个问题:[传送门在这里](http://blog.csdn.net/nullzeng/article/details/45293333),如果想了解原理可以看一看。 + +### Q: 在 Env 下能生成哪些类型的工程? + + **A:** + + 1. 目前在 Env 下可以使用 scons 工具生成 mdk/iar 的工程,还没有支持 eclipse 工程的自动生成。 + 2. 一般在使用 Env 的开发,使用 gcc 的工具链,那么只需要一个 source insight 或者 vs code 之类的编辑器来看代码,使用 scons 编译即可。 + +### Q: 自己制作的 BSP 如何能支持 menuconfig? + +**A:** 可以查阅本章 **在项目中使用 Env** 章节。 + +### Q: pkgs --upgrade 命令和 pkgs --update 命令有什么区别? + + **A:** + + 1. pkgs --upgrade 命令是用来升级 Env 功能脚本本身和软件包列表的。没有最新的包列表就不能选择最近更新的软件包。 + 2. pkgs --update 命令是用来更新软件包本身的,比如说你在 menuconfig 中选中了 json 和 mqtt 的软件包,但是退出 menuconfig 时并没有下载这些软件包。你需要使用 pkgs --update 命令,这时候 Env 就会下载你选中的软件包并且加入到你的工程中去。 + 3. 新版本的 Env 支持 menuconfig -s/--setting 命令,如果你不想每次更换软件包后使用 pkgs --update 命令,在使用 menuconfig -s/--setting 命令后配置 Env 选择每次使用 menuconfig 后自动更新软件包即可。 + +### Q: VC98 文件夹问题 + +详细描述:出现错误 MissingConfiguration: registry dir `D:\Program Files (x86)\Microsoft Visual Studio\VC98` not found on the filesystem + +![VC98问题](figures/q1.png) + +**A:** 在划线的目录新建一个 VC98 的空文件夹,就可以使用 scons 了。 + +### Q: 使用 menuconfig 命令提示“can't find file Kconfig”。 + +**A:** 当前工作的 BSP 目录下缺少 Kconfig 文件,参考本文《新的项目添加 menuconfig 功能》 和 《旧项目添加 menuconfig 功能》。 + +### Q: IOError: [Errno 2] No such file or directory: 'nul' + +**A:** 这是由于 windows 系统没有开启 `Null Service` 服务的缘故,常见于在 win10 的早期版本中(如版本号 1703),该问题有两种解决方法,第一种是开启 windows 更新将 windows 更新到最新版本,因为在后续的补丁中 windows 默认开启了该服务,第二种是参考该 [link](http://revertservice.com/10/null/) 手动开启 `Null Service` 服务。 + +## 常用资料链接 + +* [论坛持续更新的 Env 常见问题问答帖](https://www.rt-thread.org/qa/thread-5699-1-1.html) diff --git a/development-tools/env/figures/Add_Env_To_Right-click_Menu.png b/development-tools/env/figures/Add_Env_To_Right-click_Menu.png new file mode 100644 index 0000000000000000000000000000000000000000..607a819349f5967bf6f3d0186de44fe69ad04ef7 Binary files /dev/null and b/development-tools/env/figures/Add_Env_To_Right-click_Menu.png differ diff --git a/development-tools/env/figures/cd_cmd.png b/development-tools/env/figures/cd_cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..fbf358443ba8ceda5c33bba2183e10866a3f6dfa Binary files /dev/null and b/development-tools/env/figures/cd_cmd.png differ diff --git a/development-tools/env/figures/console.png b/development-tools/env/figures/console.png new file mode 100644 index 0000000000000000000000000000000000000000..23f1163a2fd3458b5884683198343c879eadc40a Binary files /dev/null and b/development-tools/env/figures/console.png differ diff --git a/development-tools/env/figures/hotkey.png b/development-tools/env/figures/hotkey.png new file mode 100644 index 0000000000000000000000000000000000000000..f46239c73ba54e39ea4b4f83a082d6ca7232ee4c Binary files /dev/null and b/development-tools/env/figures/hotkey.png differ diff --git a/development-tools/env/figures/menuconfig_packages_list.png b/development-tools/env/figures/menuconfig_packages_list.png new file mode 100644 index 0000000000000000000000000000000000000000..073456bc130d36d8c3598b8da07234218e794e2c Binary files /dev/null and b/development-tools/env/figures/menuconfig_packages_list.png differ diff --git a/development-tools/env/figures/menuconfig_s.png b/development-tools/env/figures/menuconfig_s.png new file mode 100644 index 0000000000000000000000000000000000000000..0c087b1fde5352f3f46b4d2c094aa81492d6282d Binary files /dev/null and b/development-tools/env/figures/menuconfig_s.png differ diff --git a/development-tools/env/figures/menuconfig_s_auto_prj.png b/development-tools/env/figures/menuconfig_s_auto_prj.png new file mode 100644 index 0000000000000000000000000000000000000000..57ac4ae52d124f43a55c4107b8971fc02762e74f Binary files /dev/null and b/development-tools/env/figures/menuconfig_s_auto_prj.png differ diff --git a/development-tools/env/figures/menuconfig_s_auto_update.png b/development-tools/env/figures/menuconfig_s_auto_update.png new file mode 100644 index 0000000000000000000000000000000000000000..462fa74b7ed9805cb6e9a238befdba632b1e992f Binary files /dev/null and b/development-tools/env/figures/menuconfig_s_auto_update.png differ diff --git a/development-tools/env/figures/q1.png b/development-tools/env/figures/q1.png new file mode 100644 index 0000000000000000000000000000000000000000..186362f0cf771c09f91cd4ae4e443a0b62cd92e7 Binary files /dev/null and b/development-tools/env/figures/q1.png differ diff --git a/development-tools/env/figures/scons_done.png b/development-tools/env/figures/scons_done.png new file mode 100644 index 0000000000000000000000000000000000000000..d819bd894aa3ea16a84c4f835ae0cb17c7c539bf Binary files /dev/null and b/development-tools/env/figures/scons_done.png differ diff --git a/development-tools/env/figures/use_scons.png b/development-tools/env/figures/use_scons.png new file mode 100644 index 0000000000000000000000000000000000000000..a57995b210e90d04eb5d7a8f449cde26e0699e99 Binary files /dev/null and b/development-tools/env/figures/use_scons.png differ diff --git a/development-tools/env/figures/user_manage_package.png b/development-tools/env/figures/user_manage_package.png new file mode 100644 index 0000000000000000000000000000000000000000..89c60bd3c4213a40eb62db6b22620a448e798302 Binary files /dev/null and b/development-tools/env/figures/user_manage_package.png differ diff --git a/development-tools/kconfig/figures/bool.png b/development-tools/kconfig/figures/bool.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed985e78d7f395686cfb8883bcc9f1e19114bea Binary files /dev/null and b/development-tools/kconfig/figures/bool.png differ diff --git a/development-tools/kconfig/figures/choice.png b/development-tools/kconfig/figures/choice.png new file mode 100644 index 0000000000000000000000000000000000000000..5d6ef18d9ad0723f6f4f2ff5c6b5c948bfa727e5 Binary files /dev/null and b/development-tools/kconfig/figures/choice.png differ diff --git a/development-tools/kconfig/figures/comment.png b/development-tools/kconfig/figures/comment.png new file mode 100644 index 0000000000000000000000000000000000000000..b292a5e687e5e25baebeed5290a03af097070e2a Binary files /dev/null and b/development-tools/kconfig/figures/comment.png differ diff --git a/development-tools/kconfig/figures/config.png b/development-tools/kconfig/figures/config.png new file mode 100644 index 0000000000000000000000000000000000000000..9b9c79b2bed3444a1b4452a06a13b8fdc0b1c330 Binary files /dev/null and b/development-tools/kconfig/figures/config.png differ diff --git a/development-tools/kconfig/figures/help.png b/development-tools/kconfig/figures/help.png new file mode 100644 index 0000000000000000000000000000000000000000..3c53605dd8e29c1c55a34a03a745c7e1cbeabce6 Binary files /dev/null and b/development-tools/kconfig/figures/help.png differ diff --git a/development-tools/kconfig/figures/hex.png b/development-tools/kconfig/figures/hex.png new file mode 100644 index 0000000000000000000000000000000000000000..83b1ec88efebaa4da6daedf0695348676a7aad39 Binary files /dev/null and b/development-tools/kconfig/figures/hex.png differ diff --git a/development-tools/kconfig/figures/if0.png b/development-tools/kconfig/figures/if0.png new file mode 100644 index 0000000000000000000000000000000000000000..c0c11665936c8c0f98feb11763de51a75f52e0fe Binary files /dev/null and b/development-tools/kconfig/figures/if0.png differ diff --git a/development-tools/kconfig/figures/if1.png b/development-tools/kconfig/figures/if1.png new file mode 100644 index 0000000000000000000000000000000000000000..cc3a53c7ffadf66b244c21bd79c91d658ca49a70 Binary files /dev/null and b/development-tools/kconfig/figures/if1.png differ diff --git a/development-tools/kconfig/figures/if11.png b/development-tools/kconfig/figures/if11.png new file mode 100644 index 0000000000000000000000000000000000000000..671942c5600d7009d3e11493908403bd45fce650 Binary files /dev/null and b/development-tools/kconfig/figures/if11.png differ diff --git a/development-tools/kconfig/figures/if2.png b/development-tools/kconfig/figures/if2.png new file mode 100644 index 0000000000000000000000000000000000000000..f24ebc83c4e0c8a059df593999eea6e6cc738cfd Binary files /dev/null and b/development-tools/kconfig/figures/if2.png differ diff --git a/development-tools/kconfig/figures/int.png b/development-tools/kconfig/figures/int.png new file mode 100644 index 0000000000000000000000000000000000000000..b415032659fb001e043d4ae80a3dd12610e11d07 Binary files /dev/null and b/development-tools/kconfig/figures/int.png differ diff --git a/development-tools/kconfig/figures/menu.png b/development-tools/kconfig/figures/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..e1be5a56f1e248bfc425117858762b28142d64bd Binary files /dev/null and b/development-tools/kconfig/figures/menu.png differ diff --git a/development-tools/kconfig/figures/menu1.png b/development-tools/kconfig/figures/menu1.png new file mode 100644 index 0000000000000000000000000000000000000000..408c9dc2be787ba1d3bcfe256d79ba03c2128dd1 Binary files /dev/null and b/development-tools/kconfig/figures/menu1.png differ diff --git a/development-tools/kconfig/figures/menuconfig1.png b/development-tools/kconfig/figures/menuconfig1.png new file mode 100644 index 0000000000000000000000000000000000000000..08a540e06c3c59241b9e4803b125ccb933425fbf Binary files /dev/null and b/development-tools/kconfig/figures/menuconfig1.png differ diff --git a/development-tools/kconfig/figures/menuconfig2.png b/development-tools/kconfig/figures/menuconfig2.png new file mode 100644 index 0000000000000000000000000000000000000000..63a54e76ca192fd7de050e7cacae62a3df2a6d57 Binary files /dev/null and b/development-tools/kconfig/figures/menuconfig2.png differ diff --git a/development-tools/kconfig/figures/string.png b/development-tools/kconfig/figures/string.png new file mode 100644 index 0000000000000000000000000000000000000000..b2c88824f1272efa425ff2a9f02d6b290542fc98 Binary files /dev/null and b/development-tools/kconfig/figures/string.png differ diff --git a/development-tools/kconfig/figures/tristate.png b/development-tools/kconfig/figures/tristate.png new file mode 100644 index 0000000000000000000000000000000000000000..a42eed73c318f2936a85327453c91ac7f90417ed Binary files /dev/null and b/development-tools/kconfig/figures/tristate.png differ diff --git a/development-tools/kconfig/kconfig.md b/development-tools/kconfig/kconfig.md new file mode 100644 index 0000000000000000000000000000000000000000..5a96fa13c60ca55bc00d6cd00e81faeed91d99f5 --- /dev/null +++ b/development-tools/kconfig/kconfig.md @@ -0,0 +1,311 @@ +# Kconfig 语法 + +## Kconfig 简介 + +RT-Thread 借助 Kconfig 文件生成的配置文件 rtconfig.h 来配置系统,Kconfig 文件是各种配置界面的源文件。当在 bsp 目录下使用 env 工具执行 menuconfig 命令时会出现 RT-Thread 系统的配置界面,所有配置工具都是通过读取当前 bsp 目录下的 Kconfig 文件来生成配置界面的,这个文件就是所有配置的总入口,它会包含其他目录的 Kconfig 文件。配置工具读取各个 Kconfig 文件,生成配置界面供开发人员配置系统,最终生成 RT-Thread 系统的配置文件 rtconfig.h。 + +## Kconfig 基本语法 + +### config 语句 + +config 定义了一组新的配置选项 + +以下为 RT-Thread 系统中 config 语句的示例 + +```c +config BSP_USING_GPIO + bool "Enable GPIO" + select RT_USING_PIN + default y + help + config gpio +``` + +以上代码对应的配置界面如下所示 + +![config](figures/config.png) + +对应的帮助信息界面如下所示 + +![help](figures/help.png) + +语句分析: +- config 表示一个配置选项的开始,紧跟着的 BSP_USING_GPIO 是配置选项的名称,config 下面几行定义了该配置选项的属性。属性可以是该配置选项的 + - 类型 + - 输入提示 + - 依赖关系 + - 默认值 + - 帮助信息 + +- bool 表示配置选项的类型,每个 config 菜单项都要有类型定义,变量有5种类型 + - bool 布尔类型 + - tristate 三态类型 + - string 字符串 + - hex 十六进制 + - int 整型 +- select 是反向依赖关系的意思,即当前配置选项被选中,则 RT_USING_PIN 就会被选中。 +- default 表示配置选项的默认值,bool 类型的默认值可以是 y/n。 +- help 帮助信息。 + +通过 env 选中以上配置界面的选项后,最终可在 rtconfig.h 文件中生成如下两个宏 + +```c +#define RT_USING_PIN +#define BSP_USING_GPIO +``` + +### 变量类型 + +#### bool 类型 + +布尔类型变量的取值范围是 y/n ,其使用示例如下 +```c +config BSP_USING_WDT + bool "Enable Watchdog Timer" + select RT_USING_WDT + default n +``` + + +上述语句对应的配置界面如下所示 + +![bool](figures/bool.png) + +以上配置项在 rtconfig.h 文件中生成的宏如下所示 + +```c +#define BSP_USING_WDT +#define RT_USING_WDT +``` + +#### string 类型 + +字符串变量的默认值是一个字符串,其使用示例如下 + +```c +config RT_CONSOLE_DEVICE_NAME + string "the device name for console" + default "uart1" +``` + +上述语句对应的配置界面如下所示 + +![string](figures/string.png) + +以上配置项在 rtconfig.h 文件中生成的宏如下所示 + +```c +#define RT_CONSOLE_DEVICE_NAME "uart1" +``` + +#### int 类型 + +整型变量的取值范围是一个整型的数,其使用示例如下 + +```c +config BSP_I2C1_SCL_PIN + int "I2C1 scl pin number" + range 1 176 + default 116 +``` + +上述语句对应的配置界面如下所示 + +![int](figures/int.png) + +以上配置项在 rtconfig.h 文件中生成的宏如下所示 + +```c +#define BSP_I2C1_SCL_PIN 116 +``` + + +#### hex 类型 + +十六进制类型变量的取值范围是一个十六进制的数,其使用方法和整型变量用法一致,整型变量生成的是十进制的数,而十六进制生成的是十六进制的数。 + + +#### tristate 类型 + +三态类型变量的取值范围是 y、n 和 m。tristate 代表在内核中有三种状态,一种是不选中,一种是选中直接编译进内核,还有一种是 m 手动添加驱动,而布尔类型变量只有两种状态,即选中和不选中。其使用方法和布尔类型变量类似。 + + +### menu/endmenu 语句 + +menu 语句用于生成菜单。 + +以下为 RT-Thread 系统中 menu/endmenu 语句的示例 + +```c +menu "Hardware Drivers Config" + config BSP_USING_COM2 + bool "Enable COM2 (uart2 pin conflict with Ethernet and PWM)" + select BSP_USING_UART + select BSP_USING_UART2 + default n + config BSP_USING_COM3 + bool "Enable COM3 (uart3 pin conflict with Ethernet)" + select BSP_USING_UART3 + default n +endmenu + +``` +menu 之后的字符串是菜单名。menu 和 endmenu 间有很多 config 语句,以上代码对应的配置界面如下所示 + +![menu1](figures/menu1.png) + +其中 Hardware Drivers Config 就是菜单名,然后进入这个菜单有 Enable COM2、Enable COM3 等选项,如下图所示 + +![menu](figures/menu.png) + + +通过 env 选中以上配置界面的所有选项后,最终可在 rtconfig.h 文件中生成如下五个宏 + +```c +#define BSP_USING_UART +#define BSP_USING_UART2 +#define BSP_USING_UART3 +#define BSP_USING_COM2 +#define BSP_USING_COM3 +``` + +### if/endif 语句 + +if/endif 语句是一个条件判断,定义了一个 if 结构,示例代码如下 + +```c +menu "Hardware Drivers Config" + 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 +endmenu +``` + +当没有选中 "Enable CAN" 选项时,下面通过 if 判断的 Enable CAN1 选项并不会显示出来,如下图所示 + +![if0](figures/if0.png) +![if1](figures/if1.png) + +当上一级菜单选中 "Enable CAN" 时 + + +![if11](figures/if11.png) +![if2](figures/if2.png) + + +### menuconfig 语句 + +menuconfig 语句表示带菜单的配置项 + +以下为 RT-Thread 系统 menuconfig 语句的示例 +```c +menu "Hardware Drivers Config" + menuconfig BSP_USING_UART + bool "Enable UART" + default y + select RT_USING_SERIAL + if BSP_USING_UART + config BSP_USING_UART1 + bool "Enable UART1" + default y + + config BSP_UART1_RX_USING_DMA + bool "Enable UART1 RX DMA" + depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA + default n + endif +endmenu +``` + +当没有打开串口 DMA 配置时,以上代码对应的界面为 + +![menuconfig1](figures/menuconfig1.png) + +当打开串口 DMA 配置时,以上代码对应的界面为 + +![menuconfig2](figures/menuconfig2.png) + +语句分析: +- menuconfig 这个语句和 config 语句很相似,但它在 config 的基础上要求所有的子选项作为独立的行显示。 +- depends on 表示依赖某个配置选项,`depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA` 表示只有当 BSP_USING_UART1 和 RT_SERIAL_USING_DMA 配置选项同时被选中时,当前配置选项的提示信息才会出现,才能设置当前配置选项 + +通过 env 选中以上配置界面的所有选项后,最终可在 rtconfig.h 文件中生成如下五个宏 + +```c +#define RT_USING_SERIAL +#define BSP_USING_UART +#define BSP_USING_UART1 +#define RT_SERIAL_USING_DMA +#define BSP_UART1_RX_USING_DMA +``` + +### choice/endchoice 语句 + +choice 语句将多个类似的配置选项组合在一起,供用户选择一组配置项 + +RT-Thread Kconfig 文件中 choice 代码示例如下 +```c +menu "Hardware Drivers Config" + menuconfig BSP_USING_ONCHIP_RTC + bool "Enable RTC" + select RT_USING_RTC + select RT_USING_LIBC + default n + if BSP_USING_ONCHIP_RTC + 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 + endif +endmenu +``` +以上代码对应的配置界面为 + +![choice](figures/choice.png) + +语句解析: +- prompt 给出提示信息,光标选中后回车进入就可以看到多个 config 条目定义的配置选项; +- choice/endchoice 给出选择项,中间可以定义多个配置项供选择,但是在配置界面只能选择一个配置项。 + +### comment 语句 + +comment 语句出现在界面的第一行,用于定义一些提示信息。 + +comment 代码示例如下 +```c +menu "Hardware Drivers Config" + comment "uart2 pin conflict with Ethernet and PWM" + config BSP_USING_COM2 + bool "Enable COM2" + select BSP_USING_UART + select BSP_USING_UART2 + default n +endmenu +``` + +以上代码对应的配置界面为 + +![comment](figures/comment.png) + +### source 语句 + +source 语句用于读取另一个文件中的 Kconfig 文件,如: + +```c +source "../libraries/HAL_Drivers/Kconfig" +``` + +上述语句用于读取当前 Kconfig 文件所在路径的上一级文件夹 libraries/HAL_Drivers 下的 Kconfig 文件。 + diff --git a/development-tools/rtthread-studio/README.md b/development-tools/rtthread-studio/README.md new file mode 100644 index 0000000000000000000000000000000000000000..99b404b6d67d00c34bf6de546e2fc28252f083d3 --- /dev/null +++ b/development-tools/rtthread-studio/README.md @@ -0,0 +1,8 @@ +# 概览 + + + + +RT-Thread Studio 作为一个开发工具软件,需要有一个从了解到熟悉,从熟悉到能熟练应用的过程,特别是对于以前没有用过基于 eclipse 的开发工具软件的用户,建议先熟悉软件基本使用方法和主要功能入口,然后再尝试进行项目开发,遇到问题可以先参考查阅相关文档和视频教程以及 FAQs,若找不到解决办法,可以在<a href="https://jq.qq.com/?_wv=1027&k=9puKBDMF" target="_blank"> Studio QQ 群</a>(941959043)或<a href="https://club.rt-thread.org/ask/tag/59.html" target="_blank"> Studio 论坛</a>发帖,Studio 支持人员会协助解决问题。 + +![image-20210127181544774](figures/image-20210127181325681.png) \ No newline at end of file diff --git a/development-tools/rtthread-studio/applications/project-collection/project-collection.md b/development-tools/rtthread-studio/applications/project-collection/project-collection.md new file mode 100644 index 0000000000000000000000000000000000000000..b482a89061cb98292a54852c4a175f31daf4a25e --- /dev/null +++ b/development-tools/rtthread-studio/applications/project-collection/project-collection.md @@ -0,0 +1,31 @@ + +# 精彩项目集合 + +[DIY 迷你桌面时钟(一)| 基于STM32芯片创建HelloWorld工程](https://blog.csdn.net/Mculover666/article/details/104146623) + +[DIY 迷你桌面时钟(二)| 获取温湿度传感器数据(I2C设备驱动+SHT3x软件包)](https://blog.csdn.net/Mculover666/article/details/104153715) + +[DIY 迷你桌面时钟(三)| 获取NTP时间(at_device软件包 + netutils软件包)](https://blog.csdn.net/Mculover666/article/details/104418075) + +[DIY 迷你桌面时钟(四)| OLED显示时钟和温湿度(cpp组件 + u8g2软件包)](https://blog.csdn.net/Mculover666/article/details/104422501) + +[DIY 迷你桌面时钟(五)| 使用内置 Git 插件管理项目](https://blog.csdn.net/Mculover666/article/details/104427445) + + + +[使用笔记(六)| 获取光传感器数据(I2C设备驱动+BH1750手写驱动代码分享)](https://blog.csdn.net/Mculover666/article/details/104675712) + +[使用笔记(七)| 配合STM32CubeMX添加裸机驱动(以ADC为例)](https://blog.csdn.net/Mculover666/article/details/104677481) + +[使用笔记(八)| 使用MQTT对接EMQ-X服务器(使用 pahomqtt 包)](https://blog.csdn.net/Mculover666/article/details/104680797) + +[使用笔记(九)| 开启OLED显示(使用 u8g2 软件包 c-latest 版本)](https://blog.csdn.net/Mculover666/article/details/104685289) + +[项目实战教程 | 快速打造一个桌面mini网络时钟](https://blog.csdn.net/Mculover666/article/details/104428132) + +[项目实战教程 | STM32F4在RT-Thread Studio中使用CCM SRAM](https://www.rt-thread.org/qa/thread-423945-1-1.html) + +[项目实战教程 | 基于STM32+RT-Thread的新冠肺炎疫情监控平台](https://mp.weixin.qq.com/s/ax7aL460rbFRwztHtDfnqQ) + + + diff --git a/development-tools/rtthread-studio/applications/quick-start/figures/build.png b/development-tools/rtthread-studio/applications/quick-start/figures/build.png new file mode 100644 index 0000000000000000000000000000000000000000..2cc598d4cbf52be4a31625676c0e5a20c9d19f65 Binary files /dev/null and b/development-tools/rtthread-studio/applications/quick-start/figures/build.png differ diff --git a/development-tools/rtthread-studio/applications/quick-start/figures/compile.png b/development-tools/rtthread-studio/applications/quick-start/figures/compile.png new file mode 100644 index 0000000000000000000000000000000000000000..6931f30e5933e0c1e27c9ac500dc7533816dd311 Binary files /dev/null and b/development-tools/rtthread-studio/applications/quick-start/figures/compile.png differ diff --git a/development-tools/rtthread-studio/applications/quick-start/figures/debug.png b/development-tools/rtthread-studio/applications/quick-start/figures/debug.png new file mode 100644 index 0000000000000000000000000000000000000000..fdb048b5bf1f7bf619f15ed67aad785b1ae23fdc Binary files /dev/null and b/development-tools/rtthread-studio/applications/quick-start/figures/debug.png differ diff --git a/development-tools/rtthread-studio/applications/quick-start/figures/download.png b/development-tools/rtthread-studio/applications/quick-start/figures/download.png new file mode 100644 index 0000000000000000000000000000000000000000..ea8ccf633e40d6d15b0054bb87793990128eebaf Binary files /dev/null and b/development-tools/rtthread-studio/applications/quick-start/figures/download.png differ diff --git a/development-tools/rtthread-studio/applications/quick-start/figures/new-project.png b/development-tools/rtthread-studio/applications/quick-start/figures/new-project.png new file mode 100644 index 0000000000000000000000000000000000000000..92cea65138f43c6c1dab5ad2e81743641505ba63 Binary files /dev/null and b/development-tools/rtthread-studio/applications/quick-start/figures/new-project.png differ diff --git a/development-tools/rtthread-studio/applications/quick-start/figures/printf.png b/development-tools/rtthread-studio/applications/quick-start/figures/printf.png new file mode 100644 index 0000000000000000000000000000000000000000..2ace06e599b9a33991d61f76ca26a9510c8f2645 Binary files /dev/null and b/development-tools/rtthread-studio/applications/quick-start/figures/printf.png differ diff --git a/development-tools/rtthread-studio/applications/quick-start/figures/red-download.png b/development-tools/rtthread-studio/applications/quick-start/figures/red-download.png new file mode 100644 index 0000000000000000000000000000000000000000..303e658438542befe5362112efc1527568fd9071 Binary files /dev/null and b/development-tools/rtthread-studio/applications/quick-start/figures/red-download.png differ diff --git a/development-tools/rtthread-studio/applications/quick-start/figures/terminal.png b/development-tools/rtthread-studio/applications/quick-start/figures/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..5d36cb0f182e82adc121f69e006cc49abc708628 Binary files /dev/null and b/development-tools/rtthread-studio/applications/quick-start/figures/terminal.png differ diff --git a/development-tools/rtthread-studio/applications/quick-start/rtthread-studio-quick-start.md b/development-tools/rtthread-studio/applications/quick-start/rtthread-studio-quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..7aeabea3ef20b240e17c4be5826d2ec63bf3e43b --- /dev/null +++ b/development-tools/rtthread-studio/applications/quick-start/rtthread-studio-quick-start.md @@ -0,0 +1,107 @@ +# RT-Thread Studio 快速上手 + +## 简介 + +此文档主要介绍如何基于 RT-Thread Studio 新建 RT-Thread 完整版工程,帮助开发者快速上手 RT-Thread Studio。 + +本文主要内容如下 + +- 新建 RT-Thread 工程 + +- 修改 `main.c` 中的引脚信息 + +- 编译、下载与验证 + +## 创建 RT-Thread 完整版工程 + +使用 RT-Thread Studio 新建基于 v4.0.2 的工程,界面如下图所示 + +![new-project](figures/new-project.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径。 + +- 选择`基于芯片`创建工程,选择的 RT-Thread 版本为 v4.0.2 + +- 选择厂商及芯片型号。 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的`完成`按钮即可创建 RT-Thread 完整版工程。创建 nano 版工程是步骤和上述步骤一致,只需选择 RT-Thread 版本为 nano 即可,这里需要注意的是 nano 版无设备的概念。 + +## 修改 `main.c` + +基于 RT-Thread 完整版创建工程时,会自动启用 PIN 设备,在 `main.c` 函数里面会自动生成如下代码 + +```c +/* PLEASE DEFINE the LED0 pin for your board, such as: PA5 */ +#define LED0_PIN GET_PIN(A, 5) + +int main(void) +{ + int count = 1; + /* set LED0 pin mode to output */ + rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT); + + while (count++) + { + /* set LED0 pin level to high or low */ + rt_pin_write(LED0_PIN, count % 2); + LOG_D("Hello RT-Thread!"); + rt_thread_mdelay(1000); + } + + return RT_EOK; +} +``` + +使用 PIN 设备时,只需要使用 `GET_PIN` 获取相应的引脚编号,并使用 `rt_pin_write` 等函数来操作引脚编号即可。 + +例如,`stm32l475-atk-pandora` 开发板的 LED 所接的引脚为 PE7,所以修改为 + +```c +#define LED0_PIN GET_PIN(E, 7) +``` + +## 编译 + +鼠标左键单击选中当前工程,在菜单栏单击 `为项目"quick-start"构建"Debug"`按钮![comepile](figures/compile.png)即可编译工程,`quick-start` 为自己的工程名。或者使用`右键->构建项目`也可以编译工程。 + +工程编译结果如下图 + +![build](figures/build.png) + +## 下载 + +工程编译无错误后即可下载程序到自己的板卡。 + +下载程序可以通过菜单栏的`下载程序`按钮![red-download](figures/red-download.png)来下载。也可以通过`右键->下载程序`来进行下载,下载程序默认使用的是新建工程时选择的下载方式,如果需要更改,点击菜单栏`下载程序`按钮旁边的倒三角即可根据自己的调试器来选择下载方式。目前 Studio 支持 ST-Link 和 J-Link 这两种下载方式。选择仿真器的示例如下 + +![debug](figures/debug.png) + +选择好仿真器后直接点击下载按钮即可下载 + +程序下载结果如下图所示 + +![download](figures/download.png) + +## 验证 + +程序下载成功后按下开发板的复位键,并使用 Studio 菜单栏的 `Open a Terminal` 选项来打开一个串口终端,如下图所示 + +![terminal](figures/terminal.png) + +终端中打印的信息如下 + +![printf](figures/printf.png) + +从终端打印的信息中可以看到,RT-Thread 已经成功运行起来了,同时观察板载的 LED 也可以看到 LED 每隔 1 s 状态就翻转一次。 + +## 注意事项 + +- LED 的引脚需要根据自己的板卡进行修改 + +- nano 版本无设备概念,所以无法使用 PIN 设备 diff --git a/development-tools/rtthread-studio/applications/thread/figures/configuration.png b/development-tools/rtthread-studio/applications/thread/figures/configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..2bf645b7eaa7d0233192e6f1cbb991ba7061328a Binary files /dev/null and b/development-tools/rtthread-studio/applications/thread/figures/configuration.png differ diff --git a/development-tools/rtthread-studio/applications/thread/figures/include.png b/development-tools/rtthread-studio/applications/thread/figures/include.png new file mode 100644 index 0000000000000000000000000000000000000000..3894aea4937a1883d144e0879b53a5665dd64c6c Binary files /dev/null and b/development-tools/rtthread-studio/applications/thread/figures/include.png differ diff --git a/development-tools/rtthread-studio/applications/thread/figures/new-file.png b/development-tools/rtthread-studio/applications/thread/figures/new-file.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1808b771c4fda535db4b649ac37faefc1676b9 Binary files /dev/null and b/development-tools/rtthread-studio/applications/thread/figures/new-file.png differ diff --git a/development-tools/rtthread-studio/applications/thread/figures/studio-file.png b/development-tools/rtthread-studio/applications/thread/figures/studio-file.png new file mode 100644 index 0000000000000000000000000000000000000000..22fe7befb978a73fd19e95c1755f1145ed37968e Binary files /dev/null and b/development-tools/rtthread-studio/applications/thread/figures/studio-file.png differ diff --git a/development-tools/rtthread-studio/applications/thread/figures/terminal.png b/development-tools/rtthread-studio/applications/thread/figures/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..2d4f5f57c2645290c6c9274c3b37d886b96d6b69 Binary files /dev/null and b/development-tools/rtthread-studio/applications/thread/figures/terminal.png differ diff --git a/development-tools/rtthread-studio/applications/thread/figures/uart-level.png b/development-tools/rtthread-studio/applications/thread/figures/uart-level.png new file mode 100644 index 0000000000000000000000000000000000000000..39f4df6b33a9d1e6e085bc831c3d0340e34649a8 Binary files /dev/null and b/development-tools/rtthread-studio/applications/thread/figures/uart-level.png differ diff --git a/development-tools/rtthread-studio/applications/thread/figures/uart-uml.png b/development-tools/rtthread-studio/applications/thread/figures/uart-uml.png new file mode 100644 index 0000000000000000000000000000000000000000..7d1969f8276b514a04bdcebc95b0197aadd7ff3b Binary files /dev/null and b/development-tools/rtthread-studio/applications/thread/figures/uart-uml.png differ diff --git a/development-tools/rtthread-studio/applications/thread/rtthread-studio-thread.md b/development-tools/rtthread-studio/applications/thread/rtthread-studio-thread.md new file mode 100644 index 0000000000000000000000000000000000000000..7f9ea3378fec70c8e60e0a3af082ee2783621ff2 --- /dev/null +++ b/development-tools/rtthread-studio/applications/thread/rtthread-studio-thread.md @@ -0,0 +1,142 @@ +# RT-Thread Studio 应用开发示例 - 线程 + +## 简介 + +本文档将主要介绍如何基于 RT-Thread Studio 创建并启动第一个线程。 + +## 启动第一个线程 + +创建线程时,可以在 `main.c` 文件中加入自己创建线程的代码,也可以另外新建源文件和头文件来存放创建线程的代码,本文将以新建文件的方式创建线程。 + +启动第一个线程的步骤主要如下 + +- 新建源文件及头文件 + +- 将头文件路径添加到工程 + +- 创建线程 + +- `main` 函数中调用 + + +### 新建文件 + +创建文件夹、源文件及头文件时,可以基于 Studio 进行创建,也可以在本地创建好之后拷贝到工程路径中。这里以 Studio 新建文件为例。 + +使用 Studio 新建文件时,参考以下步骤 + +- 在新文件需要存放的目录下右键 + +- 选择 `新建` 选项 + +- 选择自己需要新建的文件类型,如:文件夹、源文件和头文件等。 + +如下图所示 + +![studio-file](figures/studio-file.png) + + +本例中,在 application 目录下新建 `my-application` 文件夹,在该文件夹下,分别存放 `my_application.c` 源文件和 `my_applications.h` 头文件。 + +创建文件结果如如下图所示 + +![new-file](figures/new-file.png) + +这里需要注意的是,基于 Studio 构建工程时,Studio 会默认将工程路径下的所有源文件都参与编译,所以新建的源文件无需做任何配置即可参与工程的编译,而头文件则需要手动添加到工程中。 + +### 添加头文件路径 + +应用程序中如果需要包含新添加的头文件,那么就需要将新添加的头文件路径添加到工程中。添加头文件路径的过程如下 + +- 选择菜单栏的 ![configuration](figures/configuration.png) 选项(打开构建配置) + +- C/C++ 构建 + +- 设置 + +- 如果是 C 的头文件选择 `GNU ARM Cross Compiler`,如果是汇编的头文件则选择 `GNU ARM Cross Assembler` + +- 选择头文件包含 `Includes` + +- 添加自己的头文件路径 + +示例工程添加头文件的结果如下图所示 + +![include](figures/include.png) + + +### 创建线程 + +在 `my_application.c` 文件中创建示例线程,如下代码所示 +```c +#include <rtthread.h> + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* 使用静态线程时,线程的栈需要设置字节对齐 */ +ALIGN(RT_ALIGN_SIZE) +static rt_uint8_t thread_stack[THREAD_STACK_SIZE]; +static struct rt_thread tid1; + +/* 线程 1 的入口函数 */ +static void thread1_entry(void *parameter) +{ + rt_uint32_t count = 0; + + while (1) + { + rt_kprintf("thread1 count: %d\n", count ++); + rt_thread_mdelay(500); + } +} + +/* 线程示例 */ +int thread_sample(void) +{ + /* 静态创建线程 */ + + /* 初始化线程 1,名称是 thread1,入口是 thread1_entry*/ + rt_thread_init(&tid1, + "thread1", + thread1_entry, + RT_NULL, + thread_stack, + THREAD_STACK_SIZE, + THREAD_PRIORITY, + THREAD_TIMESLICE); + /* 启动线程 */ + rt_thread_startup(&tid1); + + return 0; +} +``` + +在 `my_application.h` 头文件中加入函数声明 +```c +int thread_sample(void); +``` + +### 编译下载&验证 + +在 `main.c` 文件中包含 `my_application.h` 头文件,同时在 main 函数中调用 `thread_sample` 函数,如下所示 +```c +#include <rtthread.h> +#include "my_application.h" + +int main(void) +{ + thread_sample(); + + return RT_EOK; +} +``` + +编译并下载程序,打开串口终端,输出信息如下 + +![terminal](figures/terminal.png) + +从终端输出的信息可以看出,创建的第一个线程已经成功运行了。 + +更多关于线程操作和示例请参考 [线程管理](https://www.rt-thread.org/document/site/programming-manual/thread/thread/) diff --git a/development-tools/rtthread-studio/applications/uart/figures/dev-precsss.vsdx b/development-tools/rtthread-studio/applications/uart/figures/dev-precsss.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..83e20869a4452f557c74390e784fa1991b521c69 Binary files /dev/null and b/development-tools/rtthread-studio/applications/uart/figures/dev-precsss.vsdx differ diff --git a/development-tools/rtthread-studio/applications/uart/figures/list_device.png b/development-tools/rtthread-studio/applications/uart/figures/list_device.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c81842a032493effa644c770932cca17d07a0d Binary files /dev/null and b/development-tools/rtthread-studio/applications/uart/figures/list_device.png differ diff --git a/development-tools/rtthread-studio/applications/uart/figures/new-file.png b/development-tools/rtthread-studio/applications/uart/figures/new-file.png new file mode 100644 index 0000000000000000000000000000000000000000..354f1cd4919dc4da9c16044af03e6480968ee209 Binary files /dev/null and b/development-tools/rtthread-studio/applications/uart/figures/new-file.png differ diff --git a/development-tools/rtthread-studio/applications/uart/figures/process.png b/development-tools/rtthread-studio/applications/uart/figures/process.png new file mode 100644 index 0000000000000000000000000000000000000000..24c66de67de49778f24e81832433a8d3fb80e259 Binary files /dev/null and b/development-tools/rtthread-studio/applications/uart/figures/process.png differ diff --git a/development-tools/rtthread-studio/applications/uart/figures/uart-recv.png b/development-tools/rtthread-studio/applications/uart/figures/uart-recv.png new file mode 100644 index 0000000000000000000000000000000000000000..c481d6c10299ee2890e22cb1a05548f1e0eb5452 Binary files /dev/null and b/development-tools/rtthread-studio/applications/uart/figures/uart-recv.png differ diff --git a/development-tools/rtthread-studio/applications/uart/figures/uart_sample.png b/development-tools/rtthread-studio/applications/uart/figures/uart_sample.png new file mode 100644 index 0000000000000000000000000000000000000000..c21ca30a5ad10f5a5af8c9addc6144ccf4feb47c Binary files /dev/null and b/development-tools/rtthread-studio/applications/uart/figures/uart_sample.png differ diff --git a/development-tools/rtthread-studio/applications/uart/rtthread-studio-uart.md b/development-tools/rtthread-studio/applications/uart/rtthread-studio-uart.md new file mode 100644 index 0000000000000000000000000000000000000000..3597bf15e9da812801c3d84ec5e9d51ff7249d1f --- /dev/null +++ b/development-tools/rtthread-studio/applications/uart/rtthread-studio-uart.md @@ -0,0 +1,125 @@ +# RT-Thread Studio 应用开发示例 - 串口设备 + +## 简介 + +在 RT-Thread 中设备的开发总体上可以分为两类:设备的驱动开发和设备的应用开发。 + +- 设备驱动开发的主要工作是操作硬件,对接设备驱动框架,并注册设备到系统中。 + +- 设备应用开发的主要工作是直接调用操作系统提供的接口,例如:`rt_device_find`、`rt_device_open` 等函数,完成上层的业务逻辑。 + +所以在 RT-Thread 中,对于一个具体的设备,其自底向上的开发流程可总结为如下步骤 + +- 打开设备驱动框架支持 + +- 对接设备驱动框架提供的接口函数 + +- 注册设备到系统中 + +- 使用操作系统提供 API 操作设备 + +流程图如下所示 + +![process](figures/process.png) + +## 新增设备 + +本文将以串口为例,主要讲解串口设备的应用开发。串口设备驱动的开发和新增串口设备请参考 [串口设备驱动开发](https://www.rt-thread.org/document/site/rtthread-studio/drivers/uart/v4.0.2/rtthread-studio-uart-v4.0.2/) + +## 查找设备 + +使用设备前需要确保系统中已经成功注册了该设备,查找设备是否已经注册,可以在命令行中使用 `list_device` 命令来查找设备,例如本例中需要使用串口 2 ,命令行查询结果如下 + +![list_device](figures/list_device.png) + +从结果可以看出设备已经成功注册到系统中了。 + +应用程序也可以根据设备名称来查找设备。查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +|**参数**|**描述** | +|----------|------------------------------------| +| name | 设备名称 | +|**返回**| —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +## 使用设备 + +当需要使用的设备成功注册到系统中时,该设备就可以直接通过系统提供的接口来进行访问了。 + +例如通用设备可以使用 `rt_device_read`、`rt_device_write` 等函数来进行数据的传输, I2C 设备可以使用 `rt_i2c_transfer` 等函数进行数据传输。更多设备相关的 API 请查看 [设备与驱动程序](https://www.rt-thread.org/document/site/programming-manual/device/device/) 中对应的设备。 + +本例中需要使用的是串口 2,在 `application` 目录下新建 `my_uart.c` 源文件,如下图所示 + +![new-file](figures/new-file.png) + +并在 `my_uart.c` 文件中添加如下串口设备的示例程序 +```c +#include <rtthread.h> + +#define SAMPLE_UART_NAME "uart2" /* 需要操作的设备 */ +static rt_device_t serial; /* 设备句柄 */ +static char str[] = "hello RT-Thread!\r\n"; /* 需要发送的数据 */ + +static int uart_sample(void) +{ + rt_err_t ret = RT_EOK; + rt_size_t send_len = 0; + + /* 查找系统中的串口设备 */ + serial = rt_device_find(SAMPLE_UART_NAME); + if (!serial) + { + rt_kprintf("find %s failed!\n", SAMPLE_UART_NAME); + return -RT_ERROR; + } + + /* 以中断接收及轮询发送模式打开串口设备 */ + ret = rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); + if (ret != RT_EOK) + { + rt_kprintf("open device failed\r\n"); + return -RT_ERROR; + } + /* 发送字符串 */ + send_len = rt_device_write(serial, 0, str, (sizeof(str) - 1)); + if (send_len != sizeof(str) - 1) + { + rt_kprintf("send data failed\r\n"); + return -RT_ERROR; + } + /* 关闭设备 */ + ret = rt_device_close(serial); + if (ret != RT_EOK) + { + rt_kprintf("close device failed\r\n"); + return -RT_ERROR; + } + + rt_kprintf("serial device test successful\r\n"); + + return RT_EOK; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(uart_sample, uart device sample); +``` + +编译并下载程序。在控制台中输入 `uart_sample` 命令,结果如下图 + +![uart-sample](figures/uart_sample.png) + +使用串口工具将开发板上的串口 2 连接到电脑,可以看到开发板串口 2 收到的数据如下 + +![uart-recv](figures/uart-recv.png) + +从上面的实验中可以看到我们成功使用系统提供的接口向串口 2 输出了数据,更多关于串口设备的使用请参看 [串口设备](https://www.rt-thread.org/document/site/programming-manual/device/uart/uart/) + +## 注意事项 + +- 设备能够使用的前提是已经注册成功 + +- nano 版没有设备的概念 diff --git a/development-tools/rtthread-studio/changelog/changelog.md b/development-tools/rtthread-studio/changelog/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..7064ce76d32c89166996d99426d846780b16a0d8 --- /dev/null +++ b/development-tools/rtthread-studio/changelog/changelog.md @@ -0,0 +1,199 @@ +## 更新日志 + +##### [V2.0.0更新发布](https://club.rt-thread.org/ask/article/2393.html) + +- 新增集成platformio裸机框架功能 +- 新增Rebuild功能(自动先清理工程再重新构建工程) +- 新增开发板详细信息查看入口 +- 新增Studio工程模板支持包方便制作开发板支持包的模板工程 +- 新增支持jlink通过tcp/ip连接下载和调试 +- 新增QEMU支持网络功能 +- 新增支持arm-linux-musleabi编译RT-Thread Smart内核 +- 新增FAQ-添加软件包后在packages目录找不到的解决方法 +- 新增FAQ-如何解决清理工程时命令行超出windows长度限制 +- 完善链接脚本编辑器兼容更多格式链接脚本 +- 解决QEMU网络配置没有正常生效的问题 +- 解决RT-Thread配置项出现重复配置节点问题 +- 解决RT-Thread配置节点展开时不停重复创建配置项节点问题 + +##### [V1.1.5更新发布](https://club.rt-thread.org/ask/article/2290.html) + +- 新增ART-PI开发板支持(请先升级至V1.1.5,再去sdk下载ART-PI开发板资源包V1.0.0版本) +- 新增STLINK Debugger调试软件v1.4.0版本资源包 +- 新增STLINK调试软件的版本切换 +- 新增SDK资源包显示更新时间 +- 新增GD32VF103-NUCLEI-RVSTAR开发板v0.1.1版本资源包 +- 完善RISCV工程构建信息展示和调试外设寄存器显示问题 +- 完善开发板大图查看 +- 解决开发板信息展示页面购买链接不显示的问题 +- 解决工程构建时链接命令长度超出windows长度限制问题(见[FAQ](https://www.rt-thread.org/document/site/rtthread-studio/faq/studio-faq/#studio-windows)) +- 解决只修改了链接脚本构建无效的问题 +- 解决完整安装包体积过大问题 +- 解决pyocd烧写问题(切换成烧写bin文件) + +##### [V1.1.4更新发布](https://club.rt-thread.org/ask/article/306.html) + +- 工程向导开发和完善 + 1.新增基于开发板创建工程模式替代老的基于bsp模式 + +- 工具链支持 + 1.新增RISC-V工具链支持 + +- 构建功能完善 + 1.解决停止并启动调试没自动构建的问题 + 2.解决只修改了链接脚本后构建无效的问题 + +- SDK Manager完善 + 1.新增开发板类型资源包 + 2.完善美化SDK Manger图标 + 3.解决SDK资源包安装失败的问题 + +- 新开发板支持 + 1.新增IMXRT1064/1052开发板的支持 + +- RT-Thread配置完善 + 1.解决LWIP配置选项ICMP名称问题 + 2.解决RT-Thread配置页面在构建时没有自动保存的问题 + +- QEMU功能完善 + 1.新增芯来rvstar开发板qemu仿真支持 + 2.新增vexpress-a9的qemu仿真支持 + 3.新增qemu点击下载直接启动运行模式 + 4.新增STM32 sdio模拟支持 + +- 调试器功能完善 + 1.新增ST-LINK调试支持复位 + 2.新增ST-LINK调试支持查看外设寄存器 + 3.新增ST-LINK外部FLASH下载算法支持 + 4.解决ST-LINK下载Verify时日志出现乱码的问题 + 5.新增DAP-LINK对多下载算法的支持 + 6.新增DAP-LINK调试器对雅特力芯片下载和调试的支持 + 7.新增DAP-LINK下载调试参数配置便于加快下载速度 + 8.解决J-Link下载时工程路径有空格弹框需要手动输入起始地址的问题 + +- 语言切换完善 + 1.解决切换到英文后部分窗口仍有中文的问题 + +##### [V1.1.3更新发布](https://club.rt-thread.org/ask/article/45.html) + +- 新增支持新建工程时选择DAP-Link调试器功能(暂时不支持stm32H7 系列) +- 新增支持新建工程时选择QEMU模拟处理器功能 +- 新增新建RT-Thread Nano项目功能 +- 新增在 “关于RT-Thread Studio” 菜单查看更新日志功能 +- 完善切换语言的功能,切换后自动重启studio +- 解决导入工程未识别到芯片时无法退出的问题 + +##### [V1.1.2 更新发布](https://club.rt-thread.org/ask/article/17.html) + +- 新增新建工程向导"添加更多"芯片系列选项 +- 新增启动时自动检测并安装必需的内置资源包 +- 新增J-Link软件支持包6.80d版本(支持雅特力芯片) +- 完善切换芯片支持包版本功能 +- 完善J-Link调试配置提示信息 +- 完善SDK Manager离线资源包导入功能 +- 解决生成工程时board.h少了大括号的问题 + +##### [V1.1.1更新发布](https://www.rt-thread.org/qa/thread-424893-1-1.html) + +- 新增切换工程芯片支持包功能 +- 新增切换工程RT-Thread版本功能 +- 新增启动调试时先自动构建功能 +- 新增创建工程默认设置为UTF8编码 +- 完善切换工程芯片功能 +- 完善下载和调试快捷键 +- 完善新建工程向导 +- 完善新建工程失败信息提示框 +- 完善调试配置内路径保存为相对路径 +- 完善SDK Manager离线资源包导入功能 +- 解决树形配置列宽拖动会抖动问题 +- 解决配置树点击过快弹框问题 +- 解决部分软件包添加失效问题 +- 解决SDK Manager安装状态图标显示异常问题 + +##### [V1.1.0更新发布](https://www.rt-thread.org/qa/forum.php?mod=viewthread&tid=424576&page=1#pid478987) + +- 新增ST-Link指定扇区擦除功能 +- 新增Code Analysis功能 +- SDK Manager全新升级 +- 新建工程向导全新升级 + +##### [V1.0.6更新发布](https://www.rt-thread.org/qa/thread-424221-1-1.html) + +- 新增导入MDK/IAR工程入口和功能 +- 新增编译结果显示ROM/RAM使用情况 +- 完善基于芯片的工程创建向导 +- 完善搜索和替换功能 +- 优化配置器打开速度 + + +##### [V1.0.5更新发布](https://www.rt-thread.org/qa/thread-424084-1-1.html) + +- 新增STM32L1系列芯片支持(仅latest版本支持) +- 完善工程生成添加更多可用外设驱动 +- 完善链接脚本编辑器 +- 完善软件包下载日志 +- 解决导入带空格路径下工程下载失败问题 +- 解决导入工程后下载提示无调试配置的问题 +- 优化配置器打开速度 修复部分显示问题 + + +##### [V1.0.4更新发布](https://www.rt-thread.org/qa/thread-423911-1-1.html) + +- 新增编译日志简化输出和详细输出功能 +- 新增显示当前工程RT-Thread版本功能 +- 新增link.lds文件图形编辑器功能 +- 完善导入工程时中文路径检查 +- 优化调整RT-Thread配置界面 +- 解决软件包版本显示不一致问题 +- 解决导入工程后下载提示无调试配置的问题 +- 解决5.0以下版本JLink下载结束后进程驻留问题 + + +##### [V1.0.3更新发布](https://www.rt-thread.org/qa/thread-423751-1-1.html) + +- 新建工程向导芯片列表更新 +- 调整工程向导基于芯片创建优先 +- 调整F1系列芯片默认选择103VE +- 完善树形配置界面提示框中信息展示 +- 解决软件包版本显示不正确的问题 +- 解决工程重命名后芯片切换和调试配置打不开问题 +- 解决删除工程时占用异常的问题 +- 升级工程生成器 + + +##### [V1.0.2更新发布](https://www.rt-thread.org/qa/thread-423613-1-1.html) + +- 新增美观完善的DevStyle黑色主题 +- 新增ST-Link程序下载后自动运行和配置功能 +- 新增问题反馈菜单导向Studio论坛 +- 新增配置项依赖信息查看 +- 优化完善串口终端功能 +- 解决C++支持问题 + + +##### V1.0.1更新发布 + +- 新增项目创建向导控制台串口LPUART1选项 +- 新增树形配置搜索功能右键菜单(Ctrl+F),便于查找配置项 +- 新增树形配置界面一键最大化功能按钮,便于树形配置操作 +- 新增Drivers组件开发文档右键功能菜单,便于查看开发文档 +- 新增moonrise主题功能插件 +- 新增RT-Thread类别的工程导入入口 +- 新增启用C++组件时工程自动添加C++支持 +- 解决树形配置出现重复节点的问题 +- 解决欢迎页在部分电脑不显示的问题 +- 解决软件包版本无法修改的问题 +- 简化JLink配置,去掉JLink下载配置页 +- 采用新的终端软件putty +- 采用带封装命名的芯片名形式 +- 优化完善工程生成器 + + + + +​ + + + + + diff --git a/development-tools/rtthread-studio/drivers/cubemx/figures/adc-gnd.png b/development-tools/rtthread-studio/drivers/cubemx/figures/adc-gnd.png new file mode 100644 index 0000000000000000000000000000000000000000..53974f56f7d24d94c5ecebb868f962be5bcc8d42 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/cubemx/figures/adc-gnd.png differ diff --git a/development-tools/rtthread-studio/drivers/cubemx/figures/adc-hal.png b/development-tools/rtthread-studio/drivers/cubemx/figures/adc-hal.png new file mode 100644 index 0000000000000000000000000000000000000000..72cdc6ffe53fcf2d1bc957ea1471ebf53f287ae8 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/cubemx/figures/adc-hal.png differ diff --git a/development-tools/rtthread-studio/drivers/cubemx/figures/adc-vcc.png b/development-tools/rtthread-studio/drivers/cubemx/figures/adc-vcc.png new file mode 100644 index 0000000000000000000000000000000000000000..66bd878d87f7acf0383cc8ff173d5220b3e1c146 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/cubemx/figures/adc-vcc.png differ diff --git a/development-tools/rtthread-studio/drivers/cubemx/figures/board-paste.png b/development-tools/rtthread-studio/drivers/cubemx/figures/board-paste.png new file mode 100644 index 0000000000000000000000000000000000000000..8e0ea7d1578a0b355a19e6824a0d470716e75f6e Binary files /dev/null and b/development-tools/rtthread-studio/drivers/cubemx/figures/board-paste.png differ diff --git a/development-tools/rtthread-studio/drivers/cubemx/figures/cubemx.png b/development-tools/rtthread-studio/drivers/cubemx/figures/cubemx.png new file mode 100644 index 0000000000000000000000000000000000000000..c27eb87a3f8c149c10e104accd3afeee152f4db8 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/cubemx/figures/cubemx.png differ diff --git a/development-tools/rtthread-studio/drivers/cubemx/figures/new-adc.png b/development-tools/rtthread-studio/drivers/cubemx/figures/new-adc.png new file mode 100644 index 0000000000000000000000000000000000000000..d7960d23e3c226f0f013f636f78cf4c10546d11a Binary files /dev/null and b/development-tools/rtthread-studio/drivers/cubemx/figures/new-adc.png differ diff --git a/development-tools/rtthread-studio/drivers/cubemx/rtthread-studio-cubemx.md b/development-tools/rtthread-studio/drivers/cubemx/rtthread-studio-cubemx.md new file mode 100644 index 0000000000000000000000000000000000000000..f7bbeef31523d5a1fefd331d488231d108137e87 --- /dev/null +++ b/development-tools/rtthread-studio/drivers/cubemx/rtthread-studio-cubemx.md @@ -0,0 +1,198 @@ +# RT-Thread Studio 结合 STM32CubeMx 开发其他驱动文档 + +使用 `RT-Thread Studio` 新建 RT-Thread 的项目时直接就将 RT-Thread 实时操作系统移植到对应的芯片上了,省去了系统移植的步骤。 + +使用 `STM32CubeMx` 配置工具可以方便快速的配置芯片外设的时钟和引脚,使驱动的开发变得简单。 + +所以本文将结合这两个 IDE 的优点,介绍基于 `RT-Thread Studio` 和 `STM32CubeMx` 的驱动开发。 + +需要注意的是这里开发的驱动是不基于 RT-Thread 设备驱动框架的,即直接使用 `STM32CubeMx` 生成的 HAL 库文件来开发外设驱动。 + +## 简介 + +使用 `RT-Thread Studio` 和 `STM32CubeMx` 开发驱动可分为以下几个步骤 + +- 使用 `RT-Thread Studio` 新建 RT-Thread 工程 + +- 使用 `STM32CubeMx` 配置外设和系统时钟 + +- 复制 `stm32xxxx_hal_msp.c` 函数 + +- 修改 `stm32xxxx_hal_config.h` 文件,打开相应外设支持。 + +- 替换 `board.c ` 文件中时钟配置函数 + +- 使用外设 + +## 新建 RT-Thread 项目 + +使用 `RT-Thread Studio` 新建基于 `nano-v3.1.3` 的工程,界面如下图所示 + +![new-adc](figures/new-adc.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径 + +- 选择 `基于芯片` 创建工程,选择的 RT-Thread 版本为 `nano-v3.1.3`。 + +- 选择厂商及芯片型号 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的 `完成` 按钮即可创建 RT-Thread 的工程。 + +基于 Studio 创建 RT-Thread 工程后,就可以基于创建的工程开发自己的驱动。下面将以 `stm32l475-atk-pandora` 开发板为例,讲解如何开发 ADC 驱动。 + +## 配置外设 + +新建基于目标板卡的 `CubeMx` 工程,并配置自己需要使用的外设。 + +例如,示例开发板的 PC2 连接的是 ADC1 的通道 3,使用 `CubeMx` 生成 ADC 的驱动代码的配置结果如下所示: + +![cubemx](figures/cubemx.png) + + +## 复制 `stm32xxxx_hal_msp.c` 函数 + +将 `CubeMx` 生成的代码 `stm32l4xx_hal_msp.c` 函数复制到 RT-Thread Studio 生成的工程中,并参与工程编译。复制完成后的结果如下图所示 + +![adc-hal](figures/adc-hal.png) + +由于我们并没有使用 CubeMx 生成的工程,所以这里需要将 `stm32l4xx_hal_msp.c` 文件中 `#include "main.h"` 替换为 `#include "board.h"`。 + +## 打开 HAL 库配置文件对应外设的支持宏 + +这里我们使用了 ADC 外设,所以需要在 `stm32l4xx_hal_config.h` 文件中将 ADC 模块使能,取消 ADC 模块的注释即可,示例代码如下 + +```c +#define HAL_ADC_MODULE_ENABLED +``` + +## 修改系统时钟(可选) + +使用 RT-Thread Studio 创建 RT-Thread 工程时默认使用的是系统内部时钟 HSI,这里需要根据自己的板卡配置将 `STM32CubeMx` 生成的时钟配置函数拷贝到 RT-hread 的工程中。步骤如下 + +- 使用 CubeMx 配置自己板卡的系统时钟,并生成代码 + +- 复制 CubeMx 工程中 `main.c` 文件的 `void SystemClock_Config(void)` 系统时钟初始化函数 + +- 替换 `RT-Thread Studio` 生成的工程中的 `drv_clk.c` 文件中的系统时钟配置函数`void system_clock_config(int target_freq_mhz)` ,如下图所示。 + + ![board-paste](figures/board-paste.png) + +- 如果使用外部时钟,则需要更新工程中的`stm32xxxx_hal_conf.h` 中的对应的外部时钟频率的值,以 HSE 为例,需要修改下面的时钟频率为实际使用的值: + + ```c + + #define HSE_VALUE ((uint32_t)8000000U) /*!< Value of the External oscillator in Hz */ + + ``` + + + +## 使用外设 + +上述文件配置完成之后,ADC 外设就可以使用的,在 `main.c` 中添加外设的使用代码 + +ADC 外设的使用示例代码如下 + +```c +#include <rtthread.h> +#include "board.h" + +ADC_HandleTypeDef hadc1; + +/* ADC1 init function */ +void MX_ADC1_Init(void) +{ + ADC_MultiModeTypeDef multimode = {0}; + ADC_ChannelConfTypeDef sConfig = {0}; + + /** Common config + */ + hadc1.Instance = ADC1; + hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; + hadc1.Init.Resolution = ADC_RESOLUTION_12B; + hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; + hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + hadc1.Init.LowPowerAutoWait = DISABLE; + hadc1.Init.ContinuousConvMode = DISABLE; + hadc1.Init.NbrOfConversion = 1; + hadc1.Init.DiscontinuousConvMode = DISABLE; + hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc1.Init.DMAContinuousRequests = DISABLE; + hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED; + hadc1.Init.OversamplingMode = DISABLE; + if (HAL_ADC_Init(&hadc1) != HAL_OK) + { + Error_Handler(); + } + /** Configure the ADC multi-mode + */ + multimode.Mode = ADC_MODE_INDEPENDENT; + if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK) + { + Error_Handler(); + } + /** Configure Regular Channel + */ + sConfig.Channel = ADC_CHANNEL_3; + sConfig.Rank = ADC_REGULAR_RANK_1; + sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5; + sConfig.SingleDiff = ADC_SINGLE_ENDED; + sConfig.OffsetNumber = ADC_OFFSET_NONE; + sConfig.Offset = 0; + if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) + { + Error_Handler(); + } + +} + +rt_uint32_t get_adc_value(void) +{ + HAL_ADC_Start(&hadc1); + HAL_ADC_PollForConversion(&hadc1, 10); + + return (rt_uint32_t)HAL_ADC_GetValue(&hadc1); +} + +int main(void) +{ + int count = 1; + rt_uint32_t read_value = 0; + + MX_ADC1_Init(); + while (count++) + { + read_value = get_adc_value(); + rt_thread_mdelay(1000); + rt_kprintf("adc value = %d\r\n", read_value); + } + + return RT_EOK; +} +``` + +编译下载工程,将开发板的 PC2 引脚连接到开发板上的地,终端打印信息如下 + +![adc-gnd](figures/adc-gnd.png) + +将开发板的 PC2 引脚连接到开发板的 3.3V 引脚,终端打印信息如下 + +![adc-vcc](figures/adc-vcc.png) + +从上面两个实验打印结果可以看出我们成功使用了 ADC 外设。 + +## 注意事项 + +- `board.c` 文件中的系统时钟配置函数需要根据自己的板卡进行修改 + +- `stm32xxxx_hal_msp.c` 函数中主要完成的是外设引脚和时钟的初始化,所以在使用 `CubeMx` 生成外设的配置代码时不能选择为每个外设都生成 `.c/.h` 文件 + +- 使用 `CubeMx` 外设时只需要配置实际使用的外设,如果 `stm32xxxx_hal_msp.c` 文件和 `drv_uart.c` 文件或者 `drv_spi.c` 文件外设的初始化函数重定义,需要删除 `stm32xxxx_hal_msp.c` 文件中外设的初始化函数。 \ No newline at end of file diff --git a/development-tools/rtthread-studio/drivers/drv-list/support-driver-list.md b/development-tools/rtthread-studio/drivers/drv-list/support-driver-list.md new file mode 100644 index 0000000000000000000000000000000000000000..2ead247995cbfda5a6bfea561422b14e68aeacf1 --- /dev/null +++ b/development-tools/rtthread-studio/drivers/drv-list/support-driver-list.md @@ -0,0 +1,58 @@ +# RT-Thread Studio 驱动支持概况 + +本文将介绍 RT-Thread Studio 对于硬件外设驱动的支持情况。由于 RT-Thread nano 版本不具有设备概念,所以本文提到的 RT-Thread 为完整版本。nano 版建议直接使用 HAL 库进行驱动开发。 + +## 驱动概况 + +当前 RT-Thread Studio 驱动开发方式主要分为二种 + +- 1、由 RT Thread Studio 自动生成,无需修改任何文件或者简单定义几个宏即可直接使用的驱动,如 GPIO,UART,I2C,SPI,SDIO 和 ETH 等。 + +- 2、没有对接到设备驱动框架,可直接使用 HAL 库函数进行开发的驱动,如 DAC,FSMC 等。 + +RT-Thread Studio (截止V1.0.4)的驱动支持情况见下表 + +| 外设 | Studio 自动生成 | HAL 库函数开发 | +|------------|-----------------------|----------------| +| PIN | 支持 | 支持 | +| UART | 支持 | 支持 | +| I2C | 支持 | 支持 | +| SPI | 支持 | 支持 | +| QSPI | 支持 | 支持 | +| WDT | 支持 | 支持 | +| PWM | 支持 | 支持 | +| RTC | 支持 | 支持 | +| FLASH | 支持 | 支持 | +| SDIO | 支持 | 支持 | +| USB HOST | 支持 | 支持 | +| USB DEVICE | 支持 | 支持 | +| ETH | 支持 | 支持 | +| TIMER | 支持(推荐使用 HAL 库) | 支持 | +| ADC | 支持(推荐使用 HAL 库) | 支持 | +| DAC | 不支持 | 支持 | +| FSMC | 不支持 | 支持 | +| CAN | 不支持 | 支持 | + +## 使用简介 + +### Studio 自动生成的驱动 + +使用 RT-Thread Studio 新建完整版工程时,用户不需要修改任何代码,例如: + +- PIN :该驱动对接到了设备驱动框架并且可以直接使用,具体 PIN 的使用参考 [PIN设备](https://www.rt-thread.org/document/site/rtthread-studio/drivers/pin/rtthread-studio-pin/) + +- UART :对于串口驱动来说,生成工程的时候串口驱动也自动对接到了设备驱动框架,使用时需要自己定义串口号以及串口引脚对应的宏,具体参考 [串口设备](https://www.rt-thread.org/document/site/rtthread-studio/drivers/uart/v4.0.2/rtthread-studio-uart-v4.0.2/) + +- I2C :软件 I2C 的驱动在使用 RT-Thread Studio 自动生成工程时也对接到了设备驱动框架,软件 I2C 驱动的开发参考 [软件模拟I2C设备](https://www.rt-thread.org/document/site/rtthread-studio/drivers/soft-i2c/rtthread-studio-soft-i2c/) + +- SPI :SPI 设备驱动的开发参考 [SPI设备](https://www.rt-thread.org/document/site/rtthread-studio/drivers/spi/rtthread-studio-spi/) + +- 更多设备:更多设备驱动的开发请参考 RT-Thread 官网文档中心的相关章节。 + +> 提示:虽然 ADC 及 Timer 外设已经对接到了设备驱动框架,但是其功能不够完善,例如:不支持 DMA,定时器的一些高级功能无法使用等。对于这类驱动,目前推荐使用 HAL 库函数方式进行开发。 + +### 直接使用 HAL 库函数开发的驱动 + +虽然可以通过 Studio 自动或者手动方式添加设备驱动代码,但是还是有些外设暂时无法对接到设备框架 + +此时用户直接按照 HAL 库开发的方式,开发对应的驱动即可。具体开发方式可以参考[RT-Thread Studio 结合 STM32CubeMx 开发其他驱动文档](https://www.rt-thread.org/document/site/rtthread-studio/drivers/cubemx/rtthread-studio-cubemx/) diff --git a/development-tools/rtthread-studio/drivers/eth/figures/detail-config.png b/development-tools/rtthread-studio/drivers/eth/figures/detail-config.png new file mode 100644 index 0000000000000000000000000000000000000000..88083cc984b2fbcc6661baa1b864d4bfe745dc01 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/eth/figures/detail-config.png differ diff --git a/development-tools/rtthread-studio/drivers/eth/figures/new-pro.png b/development-tools/rtthread-studio/drivers/eth/figures/new-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..2d1884d6659815b1d18f730bf24c05daa8e4012e Binary files /dev/null and b/development-tools/rtthread-studio/drivers/eth/figures/new-pro.png differ diff --git a/development-tools/rtthread-studio/drivers/eth/figures/open-lwIP.png b/development-tools/rtthread-studio/drivers/eth/figures/open-lwIP.png new file mode 100644 index 0000000000000000000000000000000000000000..eb108749792f94a59428843cef39e1ef175f1a2a Binary files /dev/null and b/development-tools/rtthread-studio/drivers/eth/figures/open-lwIP.png differ diff --git a/development-tools/rtthread-studio/drivers/eth/figures/ping-test.png b/development-tools/rtthread-studio/drivers/eth/figures/ping-test.png new file mode 100644 index 0000000000000000000000000000000000000000..dc11e27f757d3e282968ff199b318e653bb9ac1d Binary files /dev/null and b/development-tools/rtthread-studio/drivers/eth/figures/ping-test.png differ diff --git a/development-tools/rtthread-studio/drivers/eth/rtthread-studio-eth.md b/development-tools/rtthread-studio/drivers/eth/rtthread-studio-eth.md new file mode 100644 index 0000000000000000000000000000000000000000..e71fe0c30cd069259371b1978a3e4719b5fcc790 --- /dev/null +++ b/development-tools/rtthread-studio/drivers/eth/rtthread-studio-eth.md @@ -0,0 +1,180 @@ +# 基于 RT-Thread Studio 的 ETH 驱动开发文档 + +## 简介 + +`RT-Thread` 为了方便用户开发网络应用,引入了网络设备框架,同时 `RT-Thread` 还提供了数量丰富的网络组件包,方便用户快速开发自己的网络应用。 + +本文将基于正点原子 `stm32f407-atk-explorer` 开发板主要介绍如何使用 `RT-Thread Studio` 来添加以太网驱动和 `lwIP` 协议栈。 + +`ETH` 设备驱动的开发可总结为如下: + +- 新建 `RT-Thread` 完整版项目 +- `board.h`中定义 `BSP_USING_ETH` 和 `PHY` 相关的宏 +- `board.c`中初始化 `ETH` 相关的引脚和时钟 +- `stm32xxxx_hal_config.h`中打开 `HAL` 库函数对 `ETH` 的支持 +- `board.c` 中实现自己的 `PHY` 复位函数 +- 配置 `lwIP` 协议栈 + +更多 `ETH` 的配置及添加步骤也可以参考相应工程文件 `board.h` 中对 `ETH` 部分的描述。 + +## 新建 RT-Thread 项目 + +使用 `RT-Thread Studio` 新建基于 `v4.0.2` 的工程,界面如下图所示: + +![new-project](./figures/new-pro.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径 + +- 选择 `基于芯片` 创建工程,选择的 `RT-Thread` 版本为 `v4.0.2` + +- 选择厂商及芯片型号 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的 `Finish` 按钮即可创建 `RT-Thread` 完整版工程。 + +## 定义 ETH 相关的宏 + +定位到工程文件 `board.h` 中 `ETH` 配置说明部分,按照注释部分的说明分别定义 `BSP_USING_ETH` 和 `PHY` 相关的宏,本例中使用板载以太网 `PHY` 芯片为 `LAN8720A`, 所以 `ETH` 相关的宏定义如下 : + +```c +#define BSP_USING_ETH +#ifdef BSP_USING_ETH +#define PHY_USING_LAN8720A +#endif +``` + +## 初始化引脚和时钟 + +定义了 `BSP_USING_ETH` 宏之后,`drv_eth.c` 文件就会参与编译,该文件只是配置了 `ETH` 的工作方式和传输函数等,具体 `ETH` 外设的时钟和引脚的初始化需要借助 `STM32CubeMx` 生成的代码。 + +将 `STM32CubeMx` 工具生成的 `ETH` 引脚和时钟初始化代码(一般在 `stm32_xxxx_hal_msp.c` 文件中)复制到自己工程的 `board.c` 文件的末尾,使之参与编译,如下所示: + +```c +void HAL_ETH_MspInit(ETH_HandleTypeDef* heth) +{ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + if(heth->Instance==ETH) + { + /* USER CODE BEGIN ETH_MspInit 0 */ + + /* USER CODE END ETH_MspInit 0 */ + /* Peripheral clock enable */ + __HAL_RCC_ETH_CLK_ENABLE(); + + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + /**ETH GPIO Configuration + PC1 ------> ETH_MDC + PA1 ------> ETH_REF_CLK + PA2 ------> ETH_MDIO + PA7 ------> ETH_CRS_DV + PC4 ------> ETH_RXD0 + PC5 ------> ETH_RXD1 + PG11 ------> ETH_TX_EN + PG13 ------> ETH_TXD0 + PG14 ------> ETH_TXD1 + */ + GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF11_ETH; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF11_ETH; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_13|GPIO_PIN_14; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF11_ETH; + HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); + + /* USER CODE BEGIN ETH_MspInit 1 */ + + /* USER CODE END ETH_MspInit 1 */ + } + +} +``` + +## 打开 HAL 库对 ETH 的支持 + +在 `stm32_xxxx_hal_config.h` 文件中打开对 `ETH` 的支持,也就是取消掉 `HAL_ETH_MODULE_ENABLED` 这个宏定义的注释,如下所示: + +```c +#define HAL_ETH_MODULE_ENABLED +``` + +## 实现 PHY 复位函数 + +在 `drv_eth.c` 文件中会调用 `phy_reset` 函数,该函数需要根据自己的实际情况进行实现,本例中 `PHY` 的复位引脚接在了 `PD3` 引脚,所以复位函数的实现如下所示 : + +```c +#include <rtdevice.h> +#define RESET_IO GET_PIN(D, 3) + +void phy_reset(void) +{ + rt_pin_mode(RESET_IO, PIN_MODE_OUTPUT); + rt_pin_write(RESET_IO, PIN_HIGH); + rt_thread_mdelay(50); + rt_pin_write(RESET_IO, PIN_LOW); + rt_thread_mdelay(50); + rt_pin_write(RESET_IO, PIN_HIGH); +} +``` + +> [!NOTE] +> 注:不同的 `PHY` 芯片复位方式不同,需要根据具体自己板卡的实际情况进行实现。 + +## 配置 lwIP 协议栈 + +在前面的配置中我们已经配置好了 `ETH` 相关的硬件部分驱动,接下来就需要配置 `lwIP` 协议栈相关的内容。 + +打开 `RT-Thread Settings` 文件,在图形化配置界面中左键单击 `lwIP` 图标即可打开 `lwIP` 协议栈的支持(组件开启,相应的图标会高亮),如下图所示: + +![open-lwIP](./figures/open-lwIP.png) + +在该选项上右键,可查看 `lwIP` 的详细配置,具体配置路径如下所示: +```c +RT-Thread Settings +---- 组件 +--------网络 +------------轻量级 TCP/IP 堆栈 +``` + +配置结果如下图所示: + +![detail-config](./figures/detail-config.png) + +本例中 `lwIP` 协议栈的配置直接选择默认配置,没有做任何修改。实际使用中可以根据自己的板卡情况进行修改。 + +## 使用网络 + +将 `ETH` 和 `lwIP` 配置好之后编译下载程序,打开串口终端工具,在命令行中输入如下网络测试命令 + +```c +ping www.baidu.com +``` + +终端输出结果如下所示: + +![ping-test](figures/ping-test.png) + +从上图可以看出开发板已经成功连接到了网络。 + +## 注意事项 + +- 本例中只配置了 `lwIP` 协议栈以及 `ETH` 相关的驱动,更多关于 `RT-Thread` 网络部分的使用及介绍可以参考 [netdev 网卡](https://www.rt-thread.org/document/site/programming-manual/netdev/netdev/),[SAL套接字抽象层](https://www.rt-thread.org/document/site/programming-manual/sal/sal/)。 \ No newline at end of file diff --git a/development-tools/rtthread-studio/drivers/pin/figures/pin-device.png b/development-tools/rtthread-studio/drivers/pin/figures/pin-device.png new file mode 100644 index 0000000000000000000000000000000000000000..1533d7a76928ddc450a90eaaace90cdfb14dd519 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/pin/figures/pin-device.png differ diff --git a/development-tools/rtthread-studio/drivers/pin/figures/pin-project.png b/development-tools/rtthread-studio/drivers/pin/figures/pin-project.png new file mode 100644 index 0000000000000000000000000000000000000000..d7694c1cc896cdfe4cce7441b7193a286fcf3c85 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/pin/figures/pin-project.png differ diff --git a/development-tools/rtthread-studio/drivers/pin/rtthread-studio-pin.md b/development-tools/rtthread-studio/drivers/pin/rtthread-studio-pin.md new file mode 100644 index 0000000000000000000000000000000000000000..30752ebd042353dcddb1d7d990322151a3916f00 --- /dev/null +++ b/development-tools/rtthread-studio/drivers/pin/rtthread-studio-pin.md @@ -0,0 +1,69 @@ +# 基于 RT-Thread Studio 的 PIN 设备应用文档 + +## 简介 + +一般情况下 MCU 引出供用户使用的引脚有很多个,RT-Thread 提供的 PIN 设备驱动将这些 GPIO 引脚抽象为了一个 PIN 设备,应用程序通过 PIN 设备管理接口就可以访问控制引脚。PIN 设备驱动有以下特点: + +- 在 PIN 驱动文件中为每个引脚重新编号,这不同于芯片手册中的编号。使用时可以通过 PIN 驱动中的引脚号操作 PIN 设备。 +- 可设置引脚输入/输出模式、可读取/设置引脚电平状态、可设置引脚中断回调函数等。 + +使用 RT-Thread Studio 创建基于 RT-Thread 完整版的工程时,默认开启了 RT-Thread 的 PIN 设备,所以用户无须重新配置或修改源码,即可直接使用 PIN 设备。 + + +## 创建 RT-Thread 完整版工程 + +使用 RT-Thread Studio 新建基于 v4.0.2 的工程,界面如下图所示 + +![pin-project](figures/pin-project.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径 + +- 选择`基于芯片` 创建工程,选择的 RT-Thread 版本为 v4.0.2 + +- 选择厂商及芯片型号 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的`完成`按钮即可创建 RT-Thread 的工程。 + +## 使用 PIN 设备 + +基于 RT-Thread 完整版创建的工程中,main.c 函数里面会自动生成如下定义 + +```c +#define LED0_PIN GET_PIN(A, 5) + +int main(void) +{ + int count = 1; + /* set LED0 pin mode to output */ + rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT); + + while (count++) + { + /* set LED0 pin level to high or low */ + rt_pin_write(LED0_PIN, count % 2); + LOG_D("Hello RT-Thread!"); + rt_thread_mdelay(1000); + } + + return RT_EOK; +} +``` + +使用 PIN 驱动需要使用 `GET_PIN` 获取相应的引脚编号,获取到引脚编号后,可使用 `rt_pin_write` 等函数来操作引脚。 +例如,`stm32l475-atk-pandora` 开发板的 LED 所接的引脚为 PE7,所以修改为 + +```c +#define LED0_PIN GET_PIN(E, 7) +``` + +编译并下载代码,可以看到开发板上面的 LED 每间隔 1000 ms 闪烁一次。在终端中输入 `list_device` 命令可以看到 pin 设备已经成功注册到系统中了,如下图所示 + +![pin-device](figures/pin-device.png) + +PIN 设备的更多使用说明请参考 [PIN 设备](https://www.rt-thread.org/document/site/programming-manual/device/pin/pin/) \ No newline at end of file diff --git a/development-tools/rtthread-studio/drivers/soft-i2c/figures/aht10-data.png b/development-tools/rtthread-studio/drivers/soft-i2c/figures/aht10-data.png new file mode 100644 index 0000000000000000000000000000000000000000..1adebe82845ccd68fd89c4b64115835c068a1f18 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/soft-i2c/figures/aht10-data.png differ diff --git a/development-tools/rtthread-studio/drivers/soft-i2c/figures/f5-project.png b/development-tools/rtthread-studio/drivers/soft-i2c/figures/f5-project.png new file mode 100644 index 0000000000000000000000000000000000000000..448cf93543aebc0c6f206d506feeb3709ee3f655 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/soft-i2c/figures/f5-project.png differ diff --git a/development-tools/rtthread-studio/drivers/soft-i2c/figures/i2c-project.png b/development-tools/rtthread-studio/drivers/soft-i2c/figures/i2c-project.png new file mode 100644 index 0000000000000000000000000000000000000000..5f4fb7e5220bf9955cad9bee2fb42d21a01baa8c Binary files /dev/null and b/development-tools/rtthread-studio/drivers/soft-i2c/figures/i2c-project.png differ diff --git a/development-tools/rtthread-studio/drivers/soft-i2c/figures/list_device.png b/development-tools/rtthread-studio/drivers/soft-i2c/figures/list_device.png new file mode 100644 index 0000000000000000000000000000000000000000..7dd00d0c63bb69177b6e19332365eab07f95cd0b Binary files /dev/null and b/development-tools/rtthread-studio/drivers/soft-i2c/figures/list_device.png differ diff --git a/development-tools/rtthread-studio/drivers/soft-i2c/figures/open-frame.png b/development-tools/rtthread-studio/drivers/soft-i2c/figures/open-frame.png new file mode 100644 index 0000000000000000000000000000000000000000..61bc2976cf8ae7fcff653fae241632926ed6f927 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/soft-i2c/figures/open-frame.png differ diff --git a/development-tools/rtthread-studio/drivers/soft-i2c/figures/open-picture.png b/development-tools/rtthread-studio/drivers/soft-i2c/figures/open-picture.png new file mode 100644 index 0000000000000000000000000000000000000000..fda891db25a1954c4283e7701e1a2a4f95420e6b Binary files /dev/null and b/development-tools/rtthread-studio/drivers/soft-i2c/figures/open-picture.png differ diff --git a/development-tools/rtthread-studio/drivers/soft-i2c/rtthread-studio-soft-i2c.md b/development-tools/rtthread-studio/drivers/soft-i2c/rtthread-studio-soft-i2c.md new file mode 100644 index 0000000000000000000000000000000000000000..59422ad7b6251c6f56a079b5571b453e24034ffd --- /dev/null +++ b/development-tools/rtthread-studio/drivers/soft-i2c/rtthread-studio-soft-i2c.md @@ -0,0 +1,89 @@ +# 基于 RT-Thread Studio 的软件 I2C 驱动开发文档 + +## 简介 + +I2C 总线是 PHILIPS 公司开发的一种半双工、双向二线制同步串行总线。I2C 总线传输数据时只需两根信号线,一根是双向数据线 SDA,另一根是双向时钟线 SCL。 + +I2C 总线可以通过芯片上板载的 I2C 外设实现,也可以通过 GPIO 引脚模拟 I2C 总线协议来实现。 + +硬件 I2C 对应芯片上的 I2C 外设,由相应的 I2C 控制器和驱动电路组成,其所使用的引脚也是专用的。软件 I2C 使用的是普通的 GPIO 引脚,用软件控制 GPIO 管脚状态来模拟 I2C 通信波形。所以软件 I2C 不受管脚限制,接口也比较灵活。 + +RT-Thread 的 I2C 设备驱动框架即支持硬件 I2C 也支持软件模拟 I2C。本文将基于 `stm32l475-atk-pandora` 开发板就软件 I2C 的驱动开发展开讲解。 + +软件 I2C 的配置步骤总结如下: + +- 新建 RT-Thread 完整版项目 + +- 打开软件 I2C 设备驱动框架 + +- 定义软件 I2C 总线相关端口和引脚的宏 + +- 使用 I2C 总线 + +更多软件 I2C 的配置及添加步骤也可以参考相应工程文件 `board.h` 中对软件模拟 I2C 部分的描述。 + + +## 新建 RT-Thread 项目 + +使用 RT-Thread Studio 新建基于 v4.0.2 的工程,界面如下图所示 + +![i2c-project](figures/i2c-project.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径 + +- 选择 `基于芯片` 创建工程,选择的 RT-Thread 版本为 v4.0.2 + +- 选择厂商及芯片型号 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的 `完成` 按钮即可创建 RT-Thread 的工程。 + +## 打开 I2C 设备驱动框架 + +在 `RT-Thread Setting` 文件中借助图形化配置工具打开软件 I2C 的驱动框架,如下图所示 + +![open-picture](figures/open-picture.png) + +左键单击即可开启 `软件模拟 I2C`(组件开启,相应的图标会高亮),在该选项上右键,可以查看软件模拟 I2C 的 `详细配置`,具体配置如下所示 + +```c +RT-Thread Setting +----组件 +--------设备驱动程序 +------------使用 I2C 设备驱动程序 +----------------使用 GPIO 模拟 I2C +``` + +配置结果如下图所示 + +![open-frame](figures/open-frame.png) + +## 定义软件 I2C 相关的宏 + +在 board.h 文件中定义软件 I2C 相关的宏 + +```c +#define BSP_USING_I2C1 /* 使用 I2C1 总线 */ +#define BSP_I2C1_SCL_PIN GET_PIN(C, 1) /* SCL -> PC1 */ +#define BSP_I2C1_SDA_PIN GET_PIN(D, 6) /* SDA -> PD6 */ +``` + +上述 I2C 总线相关的宏表示 I2C1 总线的 SCL 时钟线连接在 PC1 引脚,SDA 数据线连接在 PD6 引脚。如果需要使用多个 I2C 总线,相关的宏定义参考 board.h 文件中 I2C1 的宏进行定义即可 + +## I2C 总线的使用 + +编译并下载程序在终端中输入 `list_device` 测试命令可以看到 i2c1 已经成功注册到系统中了,如下图所示 + +![list_device](figures/list_device.png) + +更多 I2C 总线的应用请参考 [I2C 总线设备应用示例](https://www.rt-thread.org/document/site/programming-manual/device/i2c/i2c/#i2c_4) + + +## 注意事项 + +- STM32 软件 I2C 的驱动依赖于 PIN 设备驱动。 diff --git a/development-tools/rtthread-studio/drivers/spi/figures/list_device.png b/development-tools/rtthread-studio/drivers/spi/figures/list_device.png new file mode 100644 index 0000000000000000000000000000000000000000..5fb2db265dda3a959fe4675a67dc5952db0c95d8 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/spi/figures/list_device.png differ diff --git a/development-tools/rtthread-studio/drivers/spi/figures/open-frame.png b/development-tools/rtthread-studio/drivers/spi/figures/open-frame.png new file mode 100644 index 0000000000000000000000000000000000000000..8c11885fff253000c181e5ad735e0c543ce12a9b Binary files /dev/null and b/development-tools/rtthread-studio/drivers/spi/figures/open-frame.png differ diff --git a/development-tools/rtthread-studio/drivers/spi/figures/open-spi.png b/development-tools/rtthread-studio/drivers/spi/figures/open-spi.png new file mode 100644 index 0000000000000000000000000000000000000000..16cf812cfb6db22b2eebcb72253cf1f2f3f3c57b Binary files /dev/null and b/development-tools/rtthread-studio/drivers/spi/figures/open-spi.png differ diff --git a/development-tools/rtthread-studio/drivers/spi/figures/spi-project.png b/development-tools/rtthread-studio/drivers/spi/figures/spi-project.png new file mode 100644 index 0000000000000000000000000000000000000000..9df5b0c58782026399d2b14a34f1ced85b6c5c24 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/spi/figures/spi-project.png differ diff --git a/development-tools/rtthread-studio/drivers/spi/figures/spi-source.png b/development-tools/rtthread-studio/drivers/spi/figures/spi-source.png new file mode 100644 index 0000000000000000000000000000000000000000..d0d71d197b971c10d8c6ceba4463dcebacf2370b Binary files /dev/null and b/development-tools/rtthread-studio/drivers/spi/figures/spi-source.png differ diff --git a/development-tools/rtthread-studio/drivers/spi/rtthread-studio-spi.md b/development-tools/rtthread-studio/drivers/spi/rtthread-studio-spi.md new file mode 100644 index 0000000000000000000000000000000000000000..5e68361bc47114af0479245e714387cc4948a530 --- /dev/null +++ b/development-tools/rtthread-studio/drivers/spi/rtthread-studio-spi.md @@ -0,0 +1,131 @@ +# 基于 RT-Thread Studio 的 SPI 驱动开发文档 + +## 简介 + +SPI 是一种高速、全双工、同步串行通信总线,常用于 MCU 与数字芯片之间的短距离通讯。RT-Thread 的 SFUD 组件,RW007 WIFI 模块均使用到了 SPI 驱动。下面将基于 `stm32l475-atk-pandora` 开发板,讲解基于 RT-Thread Studio 开发 SPI 驱动。 + +SPI 设备驱动的开发可总结为如下: + +- 新建 RT-Thread 完整版项目 + +- 打开 SPI 设备驱动框架 + +- 定义 SPI 总线相关的宏 + +- 打开 HAL 库函数对 SPI 总线的支持 + +- 复制 SPI 引脚初始化函数到工程 + +更多 SPI 总线的配置及添加步骤也可以参考相应工程文件 `board.h` 中对 SPI 总线部分的描述。 + +## 新建 RT-Thread 项目 + +使用 RT-Thread Studio 新建基于 v4.0.2 的工程,界面如下图所示 + +![spi-project](figures/spi-project.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径 + +- 选择 `基于芯片` 创建工程,选择的 RT-Thread 版本为 v4.0.2 + +- 选择厂商及芯片型号 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的 `完成` 按钮即可创建 RT-Thread 的工程。 + +## 打开 SPI 设备驱动框架 + +在 `RT-Thread Setting` 文件中借助图形化配置工具打开软件 SPI 的驱动框架,如下图所示 + +![open-frame](figures/open-frame.png) + +左键单击即可开启 `SPI` 驱动框架(组件开启,相应的图标会高亮),在该选项上右键,可查看 SPI 的 `详细配置`,具体配置路径如下所示 + +```c +RT-Thread Setting +----组件 +--------设备驱动程序 +------------使用 SPI 总线/设备驱动程序 +``` + +配置结果如下图所示 + +![open-spi](figures/open-spi.png) + +## 定义 SPI 总线相关的宏 + +在 board.h 文件中定义 SPI 总线相关的宏,本例中使用 SPI3 总线,只需定义如下宏即可 + +```c +#define BSP_USING_SPI3 +``` + +## 打开 HAL 库对 SPI 的支持 + +在 stm32xxxx_hal_config.h 文件中打开对 SPI 的支持,也就是取消掉 HAL_SPI_MODULE_ENABLED 这个宏定义的注释,如下所示: +```c +#define HAL_SPI_MODULE_ENABLED +``` + +## 初始化引脚和时钟 + +定义了 `BSP_USING_SPI3` 宏之后,`drv_spi.c` 文件就会参与编译,该文件只是配置了 SPI 的工作方式和传输函数,具体 SPI 外设的时钟和引脚的初始化需要借助 `STM32CubeMx` 生成的代码。 + +例如 `stm32l475-atk-pandora` 开发板的 `SPI3` 外设连接了一个 `LCD` 屏幕,所以需要将 CubeMx 生成的 `SPI3` 的初始化代码(一般在 `stm32_xxxx_hal_msp.c` 文件中)复制到自己工程的 `board.c` 文件的末尾,使之参与编译,如下所示 + +```c +void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi) +{ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + if(hspi->Instance == SPI3) + { + /* USER CODE BEGIN SPI3_MspInit 0 */ + + /* USER CODE END SPI3_MspInit 0 */ + /* Peripheral clock enable */ + __HAL_RCC_SPI3_CLK_ENABLE(); + + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + /**SPI3 GPIO Configuration + PC11 ------> SPI3_MISO + PB3 (JTDO-TRACESWO) ------> SPI3_SCK + PB5 ------> SPI3_MOSI + */ + GPIO_InitStruct.Pin = GPIO_PIN_11; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF6_SPI3; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_5; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF6_SPI3; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + + /* USER CODE BEGIN SPI3_MspInit 1 */ + + /* USER CODE END SPI3_MspInit 1 */ + } +} +``` + +如果需要注册更多的 SPI 总线设备,只需参考 `board.h` 文件中 SPI 相关的宏定义并拷贝引脚初始化函数即可。 + +## SPI 总线的使用 + +编译并下载程序输入 `list_device` 测试命令可以看到 SPI 总线设备已经成功注册到系统中了,如下图所示 + +![list_device](figures/list_device.png) + +> 提示:这里只是注册了一个 SPI 总线设备,SPI 从设备的挂载请参考 [挂载 SPI 从设备](https://www.rt-thread.org/document/site/programming-manual/device/spi/spi/#spi_2) + +更多关于 SPI 总线的使用请参考 [SPI 总线设备](https://www.rt-thread.org/document/site/programming-manual/device/spi/spi/)。 diff --git a/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/nano-putc.png b/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/nano-putc.png new file mode 100644 index 0000000000000000000000000000000000000000..24eadca0970499a13ae3a40fc792cb1a35d9c853 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/nano-putc.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/nano-putc2.png b/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/nano-putc2.png new file mode 100644 index 0000000000000000000000000000000000000000..1826054d34a53d9963adc53ec915ec419d799371 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/nano-putc2.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/new-project.png b/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/new-project.png new file mode 100644 index 0000000000000000000000000000000000000000..2b9b6e34c88ffeab6be50c05de7f1eee57853895 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/figures/new-project.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/rtthread-studio-uart-nano-v3.1.3.md b/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/rtthread-studio-uart-nano-v3.1.3.md new file mode 100644 index 0000000000000000000000000000000000000000..ca190176bba582d589cd352a8efa5e43a6b565d0 --- /dev/null +++ b/development-tools/rtthread-studio/drivers/uart/nano-v3.1.3/rtthread-studio-uart-nano-v3.1.3.md @@ -0,0 +1,93 @@ +# 基于 RT-Thread Studio 的串口驱动开发文档 + +## 简介 + +使用 RT-Thread Studio 新建基于 RT-Thread 的项目时,串口驱动已经自动添加到工程中,不需要用户手动添加和串口驱动相关的源码,用户可以很方便的使用串口进行输入输出。同时,基于 Studio 的图形化配置界面,更改串口的配置也更加简洁,用户不需要更改和驱动相关的源代码,只需要更改或新增相关的宏即可,下面将基于 `stm32l475-atk-pandora` 开发板就 RT-Thread 的 nano 版和完整版分别展开讲解。完整版串口驱动开发文档参见 [完整版串口驱动开发](../v4.0.2/rtthread-studio-uart-v4.0.2.md)。 + +## RT-Thread nano 版串口的驱动开发 + +nano 版串口驱动的使用主要用于控制台的输入与输出。 + +### 配置默认串口 + +使用 RT-Thread Studio 新建基于 nano 的工程,界面如下图所示 + +![new-project](figures/new-project.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径 + +- 选择 `基于芯片` 创建工程,并选择 RT-Thread 的版本为 nano 版 + +- 选择厂商及芯片型号 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的`完成`按钮即可创建 RT-Thread 的工程。在 RT-Thread 工程的 main.c 文件中会自动生成如下代码: + +```c +int main(void) +{ + int count = 1; + + while (count++) + { + LOG_D("Hello RT-Thread!"); + rt_thread_mdelay(1000); + } + + return RT_EOK; +} +``` + +编译并下载工程后,打开串口 Studio 自带的串口工具即可看到串口输出的打印信息,如下所示 + +![nano-putc](figures/nano-putc.png) + +从串口输出的信息中可以看到我们已经成功使用串口输出了打印信息。 + + +### 修改默认串口 + +上节中基于 Studio 的图形化界面成功配置了串口的输出,但是一些实际的应用的可能需要使用其他串口输出信息,这种情况则可以通过修改一些宏定义实现。 + +#### 修改 `board.h` 宏 + +演示所用的开发板 `stm32l475-atk-pandora` 默认使用 UART1 进行输出,若要改为 UART2(`TX->PA2`、`RX->PA3`)进行输出,,则将 `board.h` 中串口 1 的 5 个宏 + +```c +#define BSP_USING_UART1 +#define BSP_UART1_TX_PIN "PA9" +#define BSP_UART1_RX_PIN "PA10" +``` + +修改为串口 2 的以下宏 +```c +#define BSP_USING_UART2 +#define BSP_UART2_TX_PIN "PA2" +#define BSP_UART2_RX_PIN "PA3" +``` + +宏定义的修改主要分为三个方面 + +- 修改串口对应的宏定义,如 `BSP_USING_UART1`、`BSP_USING_UART2`等。 + +- 修改串口 `TX/RX` 所使用的端口,如 `"PA2"`、`"PA3"`等。 + + +#### 使用更改后的串口输出 + +board.h 中的宏修改完成之后,编译、下载。使用 USB 转串口线连接好 PA2、和 PA3 的引脚,并打开 Studio 中自带的串口工具,串口输出的 log 如下所示 + +![nano-putc2](figures/nano-putc2.png) + +从输出的信息可以看出控制台切换到串口 2 后成功进行了输出。 + +## 常见问题 + +### Q:如何给 Nano 添加 Finsh? + +A: 参考 [基于 Nano 添加 Finsh 章节](https://www.rt-thread.org/document/site/tutorial/nano/nano-port-studio/an0047-nano-port-studio/#nano-finsh)。 diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/add-uart.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/add-uart.png new file mode 100644 index 0000000000000000000000000000000000000000..f160e958a1c48aaceef4b0df54c9a841f0a04f88 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/add-uart.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/change-pin.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/change-pin.png new file mode 100644 index 0000000000000000000000000000000000000000..c7dc88f9b0a93c28628004b6e417d14556c66a83 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/change-pin.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/change-terminal.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/change-terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..c56917bcfd09c49b0e5dd511dadbc3b379c71c19 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/change-terminal.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/dma-config.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/dma-config.png new file mode 100644 index 0000000000000000000000000000000000000000..62642727f270c48baa4c72ba6b3fbfc10ca1c37f Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/dma-config.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/dma-put.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/dma-put.png new file mode 100644 index 0000000000000000000000000000000000000000..6cc3a68153fd7d21ddc48ef1c9b76ecbc55cb2b7 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/dma-put.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list-device3.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list-device3.png new file mode 100644 index 0000000000000000000000000000000000000000..906b3977d4ebb3a15fecdea300f9cbf2e65eaf2e Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list-device3.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list_device1.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list_device1.png new file mode 100644 index 0000000000000000000000000000000000000000..ae1afd6de1b7efd9de8ece1b987fe0d894c762e5 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list_device1.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list_device2.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list_device2.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c401a7ca765287b0777aaec76298d31a4e7f2b Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/list_device2.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/open-dma.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/open-dma.png new file mode 100644 index 0000000000000000000000000000000000000000..096bf44f531daf80309a9977b44787a9565f8fbe Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/open-dma.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-project.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-project.png new file mode 100644 index 0000000000000000000000000000000000000000..20ab3e8b6eae477dd87e010332c7e52b2c19498e Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-project.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-putc1.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-putc1.png new file mode 100644 index 0000000000000000000000000000000000000000..ac457cc4ef3bdb9b7e1cd91174a97ab98a62da68 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-putc1.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-putc2.png b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-putc2.png new file mode 100644 index 0000000000000000000000000000000000000000..7725c98a5daaff11340741cce84c96a2948358d0 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/uart/v4.0.2/figures/v4.0.2-putc2.png differ diff --git a/development-tools/rtthread-studio/drivers/uart/v4.0.2/rtthread-studio-uart-v4.0.2.md b/development-tools/rtthread-studio/drivers/uart/v4.0.2/rtthread-studio-uart-v4.0.2.md new file mode 100644 index 0000000000000000000000000000000000000000..ab36d17e19d31c686be2573e4f430b03d70f3e6d --- /dev/null +++ b/development-tools/rtthread-studio/drivers/uart/v4.0.2/rtthread-studio-uart-v4.0.2.md @@ -0,0 +1,164 @@ +# 基于 RT-Thread Studio 的串口驱动开发文档 + +## RT-Thread 完整版串口的驱动开发 + +与 nano 版串口不同的是,完整版的串口基于设备驱动框架,使用完整版的串口可以使用 RT-Thread 丰富的组件及软件包。如 AT 组件,modbus 软件包等 + +### 配置默认串口 + +使用 RT-Thread Studio 新建基于 v4.0.2 的工程,界面如下图所示 + +![v4.0.2-project](figures/v4.0.2-project.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径 + +- 选择`基于芯片` 创建工程,选择的 RT-Thread 版本为 v4.0.2 + +- 选择厂商及芯片型号 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的`完成`按钮即可创建 RT-Thread 的工程。在 RT-Thread 工程的 main.c 文件中会自动生成如下代码: + +```c +/* PLEASE DEFINE the LED0 pin for your board, such as: PA5 */ +#define LED0_PIN GET_PIN(A, 5) + +int main(void) +{ + int count = 1; + /* set LED0 pin mode to output */ + rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT); + + while (count++) + { + /* set LED0 pin level to high or low */ + rt_pin_write(LED0_PIN, count % 2); + LOG_D("Hello RT-Thread!"); + rt_thread_mdelay(1000); + } + + return RT_EOK; +} +``` + +代码中会每间隔 1000 ms 在控制台中打印 `Hello RT-Thread!` 。编译并下载工程后,打开串口 Studio 自带的串口工具即可看到串口输出的打印信息,如下所示 + +![nano-putc](figures/v4.0.2-putc1.png) + +从串口输出的信息中可以看到我们已经成功使用串口输出了打印信息。 + +在控制台中输入 `list_device` 可以看到 `uart1` 设备已经成功注册到系统中,如下图所示 + +![list_device1](figures/list_device1.png) + +### 修改默认串口 + +上节中基于 Studio 的图形化界面成功配置了串口的输出,但是一些实际的应用的可能需要使用其他串口输出信息,这种情况则可以通过修改一些宏定义及 RT-Thread Setting 文件来实现。 + +#### 修改 `board.h` 宏 + +演示所用的开发板 `stm32l475-atk-pandora` 默认使用 UART1 进行输出,若要修改为串口 2 (`TX->PA2`、`RX->PA3`)进行输出,则在 board.h 中定义宏 `BSP_USING_UART2`,并将串口 2 对应的引脚信息修改为实际所使用的引脚,如下所示 + +![change-pin](figures/change-pin.png) + +宏定义的修改主要分为三个方面 + +- 修改串口对应的宏定义,如 `BSP_USING_UART1`、`BSP_USING_UART2`等。 + +- 修改串口 `TX/RX` 所使用的端口,如 `"PA2"`、`"PA3"`等。 + + + +#### 修改 `RT-Thread Setting` 文件 + +在 RT-Thread Setting 文件中修改输出控制台的串口设备名称,修改路径如下: +```c +RT-Thread Setting +----内核 +--------RT-Thread 内核 +------------内核设备对象 +----------------为 rt_kprintf 使用控制台 +--------------------控制设备台的名称 +``` + +配置如下图所示 + +![change-terminal](figures/change-terminal.png) + +保存工程配置并编译、下载,串口输出如下图所示 + +![v4.0.2-putc](figures/v4.0.2-putc2.png) + +输入 `list_device` 命令可查看已注册的设备,如下图所示 + +![list_device2](figures/list_device2.png) + +从终端打印的信息可以看到控制台的串口设备已经成功的从串口 1 更换到了串口 2。 + +### 新增串口 + +新增串口只需要在 `board.h` 文件中定义相关串口的宏定义 `BSP_USING_UARTx` 及修改引脚信息即可 + +新增串口的步骤总结如下 + +- 新增对应串口的宏定义,如 BSP_USING_UART1、BSP_USING_UART2等。 + +- 修改串口 `TX/RX` 所使用的端口,如 `"PA9"`、`"PA10"`等。 + + +基于修改控制台章节新增串口 1 的示例如下 + +![add-uart](figures/add-uart.png) + +编译并下载程序,在控制台输入 `list_device` 命令可以看到已经注册了两个串口设备,串口 1 和串口 2。如下图所示 + +![list-device](figures/list-device3.png) + +从控制台输出的信息可以看到两个串口设备均已注册到系统中了。 + + +### 串口 DMA 的使用 + +#### RT-Thread Setting 配置 DMA + +如果要使用串口 DMA 的功能,需要使用 RT-Thread Setting 打开 DMA 的支持。配置路径为 + +```c +RT-Thread Setting +----组件 +--------设备驱动程序 +------------使用 UART 设备驱动程序 +----------------使能串口 DMA 模式。 +``` + +配置过程如下图所示 + +![open-dma](figures/open-dma.png) + +#### 配置 `board.h` 中的宏 + +如果需要使用串口 DMA 只需要在 `board.h` 文件中定义如下宏即可 + +```c +#define BSP_UARTx_RX_USING_DMA +#define BSP_UARTx_TX_USING_DMA +``` + +- `UARTx` 表示的是哪个串口需要使用 DMA,使用的是 DMA 的发送还是接收功能。此例中使用的是串口 2 的 DMA 接收功能,所以定义了宏 `BSP_UART2_RX_USING_DMA`,串口 2 使用 DMA 的配置如下所示 + +![dma-config](figures/dma-config.png) + +将 [DMA 接收及轮询发送](https://www.rt-thread.org/document/site/programming-manual/device/uart/uart/#dma) 章节中的DMA 的测试代码添加到工程中 + +编译并下载程序,在控制台中输入 `uart_dma_sample` 命令,并使用 USB 转串口线连接串口 2,在串口 2 中可以看到如下打印信息 + +![dma-put](figures/dma-put.png) + +测试程序已经成功使用 DMA 进行了接收。 + +更多关于串口的使用请查看 [UART设备](https://www.rt-thread.org/document/site/programming-manual/device/uart/uart/) \ No newline at end of file diff --git a/development-tools/rtthread-studio/drivers/usb-device/figures/cdc-usb.png b/development-tools/rtthread-studio/drivers/usb-device/figures/cdc-usb.png new file mode 100644 index 0000000000000000000000000000000000000000..f796acfa325fb9dcefa7959a339dcc05a3c7d292 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/usb-device/figures/cdc-usb.png differ diff --git a/development-tools/rtthread-studio/drivers/usb-device/figures/list-device.png b/development-tools/rtthread-studio/drivers/usb-device/figures/list-device.png new file mode 100644 index 0000000000000000000000000000000000000000..c372ac643a37fd062886fdc8b6938c362fe9cd65 Binary files /dev/null and b/development-tools/rtthread-studio/drivers/usb-device/figures/list-device.png differ diff --git a/development-tools/rtthread-studio/drivers/usb-device/figures/new-project.png b/development-tools/rtthread-studio/drivers/usb-device/figures/new-project.png new file mode 100644 index 0000000000000000000000000000000000000000..cf820b17e42fc8e44f22d3306e3a5e454676993f Binary files /dev/null and b/development-tools/rtthread-studio/drivers/usb-device/figures/new-project.png differ diff --git a/development-tools/rtthread-studio/drivers/usb-device/figures/usb-device.png b/development-tools/rtthread-studio/drivers/usb-device/figures/usb-device.png new file mode 100644 index 0000000000000000000000000000000000000000..e3bd27a839147c5d0c9a4816d922d2f15e715e7d Binary files /dev/null and b/development-tools/rtthread-studio/drivers/usb-device/figures/usb-device.png differ diff --git a/development-tools/rtthread-studio/drivers/usb-device/rtthread-studio-usb-device.md b/development-tools/rtthread-studio/drivers/usb-device/rtthread-studio-usb-device.md new file mode 100644 index 0000000000000000000000000000000000000000..de8334a327565a53ee177ded5072dbaba58ba9e3 --- /dev/null +++ b/development-tools/rtthread-studio/drivers/usb-device/rtthread-studio-usb-device.md @@ -0,0 +1,219 @@ +# 基于 RT-Thread Studio 的 USB Device 驱动开发文档 + + +## 简介 + +`USB` 即 `Universal Serial Bus` 是一种支持热插拔的通用串行总线,在 `USB` 体系中又分为 `USB Host` 和 `USB Device`。本文将基于 `stm32l475-atk-pandora` 开发板,讲解基于 `RT-Thread Studio` 开发 `USB Device` 驱动。 + +`USB Device` 设备驱动的开发可总结为如下几个步骤: + +- 新建 `RT-Thread` 完整版项目 + +- 打开 `USB Device` 设备驱动框架,并配置相关的子类 + +- `board.h`中定义 `USB Device` 相关的宏 + +- `board.c` 中初始化 `USB Device` 的引脚及外设时钟 + +- `stm32xxxx_hal_config.h` 中打开 `HAL` 库函数对 `USB Device` 的支持 + + + +更多关于`USB Device` 的配置及添加步骤也可以参考相应工程文件 `board.h` 中对 `USB Device` 部分的描述。 + +## 新建 RT-Thread 项目 + +使用 `RT-Thread Studio` 新建基于 `v4.0.2` 的工程,界面如下图所示: + +![new-project](./figures/new-project.png) + +配置过程可总结为以下步骤: + +- 定义自己的工程名及工程生成文件的存放路径 + +- 选择 `基于芯片` 创建工程,选择的 `RT-Thread` 版本为 `v4.0.2` + +- 选择厂商及芯片型号 + +- 配置串口信息 + +- 配置调试器信息 + +工程配置完成后点击下方的 `Finish` 按钮即可创建 `RT-Thread` 的工程。 + +## 打开 USB Device 设备驱动框架 + +在 `RT-Thread Settings` 文件中配置 `USB Device` ,配置路径如下: + +```c +组件 +---- 设备驱动程序 +--------使用 USB +------------使用 USB 设备 +----------------选择设备类型 +``` + +本例中将 `USB Device` 配置为一个 `CDC` 的子类—虚拟串口,配置图如下所示: + +![cdc-usb](./figures/cdc-usb.png) + +虚拟串口的属性等信息这里选择默认并未做任何修改,实际使用过程中按照需要修改即可。 + +## 定义 USB Device 宏 + +定位到工程文件 `board.h` 中关于 `USB Device` 的配置说明部分,按照注释部分的说明定义 `BSP_USING_USBDEVICE` 的宏,如下所示: + +```c +#define BSP_USING_USBDEVICE +``` + +## 初始化引脚和时钟 + +定义了 `BSP_USING_USBDEVICE` 宏之后,`drv_usbd.c` 文件就会参与编译,该文件只是配置了 `USB Device` 的工作方式和传输函数等,具体 `USB Device` 引脚和时钟的初始化需要借助 `STM32CubeMx` 生成的代码。 + +将 `STM32CubeMx` 工具生成的 `USB Device` 引脚和时钟初始化代码(一般在 `stm32_xxxx_hal_msp.c` 文件中)复制到自己工程的 `board.c` 文件的末尾,使之参与编译,如下所示: + +```c +void HAL_PCD_MspInit(PCD_HandleTypeDef* hpcd) +{ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + if(hpcd->Instance==USB_OTG_FS) + { + /* USER CODE BEGIN USB_OTG_FS_MspInit 0 */ + + /* USER CODE END USB_OTG_FS_MspInit 0 */ + + __HAL_RCC_GPIOA_CLK_ENABLE(); + /**USB_OTG_FS GPIO Configuration + PA11 ------> USB_OTG_FS_DM + PA12 ------> USB_OTG_FS_DP + */ + GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + /* Peripheral clock enable */ + __HAL_RCC_USB_OTG_FS_CLK_ENABLE(); + + /* Enable VDDUSB */ + if(__HAL_RCC_PWR_IS_CLK_DISABLED()) + { + __HAL_RCC_PWR_CLK_ENABLE(); + HAL_PWREx_EnableVddUSB(); + __HAL_RCC_PWR_CLK_DISABLE(); + } + else + { + HAL_PWREx_EnableVddUSB(); + } + /* USB_OTG_FS interrupt Init */ + HAL_NVIC_SetPriority(OTG_FS_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(OTG_FS_IRQn); + /* USER CODE BEGIN USB_OTG_FS_MspInit 1 */ + + /* USER CODE END USB_OTG_FS_MspInit 1 */ + } +} +``` + +## 配置 USB 外设时钟 + +使用 `RT-Thread Studio` 自动生成工程的时候,在 `board.c` 文件中的系统时钟初始化函数 `SystemClock_Config` 中,有些外设的时钟是默认没有开启的。当需要使用这些外设的时候,同样需要借助 `STM32CubeMX` 工具生成的代码来初始化外设时钟。 + +本例中需要使用到 `USB` 这个外设,使用 `STM32CubeMX` 工具配置好系统时钟以后将工程 `board.c` 文件中的 `SystemClock_Config` 函数替换为自己使用 `STM32CubeMX` 工具生成的函数,如下所示: + +```c +void SystemClock_Config(void) +{ + RCC_OscInitTypeDef RCC_OscInitStruct = {0}; + RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; + RCC_PeriphCLKInitTypeDef PeriphClkInit = {0}; + + /** Configure LSE Drive Capability + */ + HAL_PWR_EnableBkUpAccess(); + __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW); + /** Initializes the CPU, AHB and APB busses clocks + */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_HSE + |RCC_OSCILLATORTYPE_LSE; + RCC_OscInitStruct.HSEState = RCC_HSE_ON; + RCC_OscInitStruct.LSEState = RCC_LSE_ON; + RCC_OscInitStruct.LSIState = RCC_LSI_ON; + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; + RCC_OscInitStruct.PLL.PLLM = 1; + RCC_OscInitStruct.PLL.PLLN = 20; + RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; + RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; + RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; + if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) + { + Error_Handler(); + } + /** Initializes the CPU, AHB and APB busses clocks + */ + RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK + |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; + + if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) + { + Error_Handler(); + } + PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_USART1 + |RCC_PERIPHCLK_USART2|RCC_PERIPHCLK_USB + |RCC_PERIPHCLK_ADC; + PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; + PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1; + PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1; + PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; + PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_PLLSAI1; + PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE; + PeriphClkInit.PLLSAI1.PLLSAI1M = 1; + PeriphClkInit.PLLSAI1.PLLSAI1N = 12; + PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7; + PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2; + PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV2; + PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_48M2CLK|RCC_PLLSAI1_ADC1CLK; + if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) + { + Error_Handler(); + } + /** Configure the main internal regulator output voltage + */ + if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) + { + Error_Handler(); + } +} +``` + +## 打开 HAL 库对 USB Device 的支持 + +在 `stm32_xxxx_hal_config.h` 文件中打开对 `USB Device` 的支持,也就是取消掉 `HAL_PCD_MODULE_ENABLED` 这个宏定义的注释,如下所示: +```c +#define HAL_PCD_MODULE_ENABLED +``` + +## 使用 + +上述步骤配置完成以后,编译下载程序,在终端中输入 `list_device` 命令,结果如下: + +![list-device](./figures/list-device.png) + +从终端中输出的结果可以看到 `USB Device` 设备和虚拟串口设备已经成功注册到系统中了。 + +在 `Windows` 上打开设备管理器,并将开发板上的 `USB Device` 接口通过数据线连接到电脑,在电脑的设备管理器中可以看到下图: + +![usb-device](./figures/usb-device.png) + +从上图可以看到电脑已经成功的枚举了上面配置的虚拟串口设备。 + + diff --git a/development-tools/rtthread-studio/faq/figures/binfile.png b/development-tools/rtthread-studio/faq/figures/binfile.png new file mode 100644 index 0000000000000000000000000000000000000000..f91160c399e64157d6b81cbde074f3094f4ca8b3 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/binfile.png differ diff --git a/development-tools/rtthread-studio/faq/figures/build-data.png b/development-tools/rtthread-studio/faq/figures/build-data.png new file mode 100644 index 0000000000000000000000000000000000000000..edff6e8b48d644cb88297702872c69ff129f7e40 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/build-data.png differ diff --git a/development-tools/rtthread-studio/faq/figures/build-dfs.png b/development-tools/rtthread-studio/faq/figures/build-dfs.png new file mode 100644 index 0000000000000000000000000000000000000000..ff63f142887067b14db4fffb3f7c7d7cc7a0cc24 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/build-dfs.png differ diff --git a/development-tools/rtthread-studio/faq/figures/buildhe.png b/development-tools/rtthread-studio/faq/figures/buildhe.png new file mode 100644 index 0000000000000000000000000000000000000000..320cd750cd40b864345ba1fb3fcfb36f1244d69b Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/buildhe.png differ diff --git a/development-tools/rtthread-studio/faq/figures/bulid_cancel.png b/development-tools/rtthread-studio/faq/figures/bulid_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..4ee29d75d0786e04a2784bc1abcca7c4be3168fa Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/bulid_cancel.png differ diff --git a/development-tools/rtthread-studio/faq/figures/clean_error.png b/development-tools/rtthread-studio/faq/figures/clean_error.png new file mode 100644 index 0000000000000000000000000000000000000000..b4bc31a1bbffd85667901c69ac6732bcf1f939fa Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/clean_error.png differ diff --git a/development-tools/rtthread-studio/faq/figures/clean_script.exe b/development-tools/rtthread-studio/faq/figures/clean_script.exe new file mode 100644 index 0000000000000000000000000000000000000000..6262e9b264529eefc36e75f2b1222f23d9b216cc Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/clean_script.exe differ diff --git a/development-tools/rtthread-studio/faq/figures/devstyle1.png b/development-tools/rtthread-studio/faq/figures/devstyle1.png new file mode 100644 index 0000000000000000000000000000000000000000..fb6940f0520a87fc523a5d44266d0c3e55a75bea Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/devstyle1.png differ diff --git a/development-tools/rtthread-studio/faq/figures/devstyle2.png b/development-tools/rtthread-studio/faq/figures/devstyle2.png new file mode 100644 index 0000000000000000000000000000000000000000..00b77c33332448d3d9968e4e3639f2d2fdb8951f Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/devstyle2.png differ diff --git a/development-tools/rtthread-studio/faq/figures/exclude_build_0.png b/development-tools/rtthread-studio/faq/figures/exclude_build_0.png new file mode 100644 index 0000000000000000000000000000000000000000..5aa9a9443e83d22b4b2a748a18390669a9c440d6 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/exclude_build_0.png differ diff --git a/development-tools/rtthread-studio/faq/figures/exclude_build_1.png b/development-tools/rtthread-studio/faq/figures/exclude_build_1.png new file mode 100644 index 0000000000000000000000000000000000000000..9017df20fa2b753ad8ca462f7e3ac248bf6dfd6c Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/exclude_build_1.png differ diff --git a/development-tools/rtthread-studio/faq/figures/exclude_build_2.png b/development-tools/rtthread-studio/faq/figures/exclude_build_2.png new file mode 100644 index 0000000000000000000000000000000000000000..7687960a70fd5cba1d7ba9603cef3965cbdea3b4 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/exclude_build_2.png differ diff --git a/development-tools/rtthread-studio/faq/figures/hexfile.png b/development-tools/rtthread-studio/faq/figures/hexfile.png new file mode 100644 index 0000000000000000000000000000000000000000..3d8a9355d0f09b38f6cf1fb58fca78140c98a0ff Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/hexfile.png differ diff --git a/development-tools/rtthread-studio/faq/figures/importdone.png b/development-tools/rtthread-studio/faq/figures/importdone.png new file mode 100644 index 0000000000000000000000000000000000000000..ed58379600aeaec1ab4aa02d1327cd186f31b702 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/importdone.png differ diff --git a/development-tools/rtthread-studio/faq/figures/importpro.png b/development-tools/rtthread-studio/faq/figures/importpro.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ac8c372b662a6802a2dcf63973b53a41107f1a Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/importpro.png differ diff --git a/development-tools/rtthread-studio/faq/figures/link-cmd.png b/development-tools/rtthread-studio/faq/figures/link-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..668ac70ed27965df197c59addbcb3698499198e6 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/link-cmd.png differ diff --git a/development-tools/rtthread-studio/faq/figures/look_key1.png b/development-tools/rtthread-studio/faq/figures/look_key1.png new file mode 100644 index 0000000000000000000000000000000000000000..a4977811fc5ff0d58a10b32d021ef9ea7662591f Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/look_key1.png differ diff --git a/development-tools/rtthread-studio/faq/figures/look_key2.png b/development-tools/rtthread-studio/faq/figures/look_key2.png new file mode 100644 index 0000000000000000000000000000000000000000..8cb1d568c6fbaa6efd68dd4abccb05c311718676 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/look_key2.png differ diff --git a/development-tools/rtthread-studio/faq/figures/open-dfs.png b/development-tools/rtthread-studio/faq/figures/open-dfs.png new file mode 100644 index 0000000000000000000000000000000000000000..0534220c6dfa7a7d1e0f3936969006eda91b3c30 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/open-dfs.png differ diff --git a/development-tools/rtthread-studio/faq/figures/open_settings.png b/development-tools/rtthread-studio/faq/figures/open_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..e86300f950ae62dc160310308ed81783f35d86cf Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/open_settings.png differ diff --git a/development-tools/rtthread-studio/faq/figures/openproject.png b/development-tools/rtthread-studio/faq/figures/openproject.png new file mode 100644 index 0000000000000000000000000000000000000000..b668ce7db7daaaa49b4849f5167493e9a48d31a9 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/openproject.png differ diff --git a/development-tools/rtthread-studio/faq/figures/pkg_cfg.png b/development-tools/rtthread-studio/faq/figures/pkg_cfg.png new file mode 100644 index 0000000000000000000000000000000000000000..2c6615392f6ebff89760e90662483a75ac433073 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/pkg_cfg.png differ diff --git a/development-tools/rtthread-studio/faq/figures/pkg_open.png b/development-tools/rtthread-studio/faq/figures/pkg_open.png new file mode 100644 index 0000000000000000000000000000000000000000..6fcdf163a4bd08b0201ad403dff2215647646e3b Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/pkg_open.png differ diff --git a/development-tools/rtthread-studio/faq/figures/project_linker.exe b/development-tools/rtthread-studio/faq/figures/project_linker.exe new file mode 100644 index 0000000000000000000000000000000000000000..045f8fe9c17e9bb0df17773bed47685b28b3671a Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/project_linker.exe differ diff --git a/development-tools/rtthread-studio/faq/figures/project_linker.jpg b/development-tools/rtthread-studio/faq/figures/project_linker.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e81f22c99ac82a988e6acc11d17592bfb53663d0 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/project_linker.jpg differ diff --git a/development-tools/rtthread-studio/faq/figures/project_root.png b/development-tools/rtthread-studio/faq/figures/project_root.png new file mode 100644 index 0000000000000000000000000000000000000000..b2d5be37f577df991bc98a6469843a50e592cbb9 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/project_root.png differ diff --git a/development-tools/rtthread-studio/faq/figures/python36.png b/development-tools/rtthread-studio/faq/figures/python36.png new file mode 100644 index 0000000000000000000000000000000000000000..2d8366d6aa5f0705ede75fbf340577de5f6539fd Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/python36.png differ diff --git a/development-tools/rtthread-studio/faq/figures/resetpage.png b/development-tools/rtthread-studio/faq/figures/resetpage.png new file mode 100644 index 0000000000000000000000000000000000000000..0f6224be37ece2a9714513a5bea561a76a5d5862 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/resetpage.png differ diff --git a/development-tools/rtthread-studio/faq/figures/restartpro.png b/development-tools/rtthread-studio/faq/figures/restartpro.png new file mode 100644 index 0000000000000000000000000000000000000000..f38e288546bd653857712e008a18a1cf0ec235f5 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/restartpro.png differ diff --git a/development-tools/rtthread-studio/faq/figures/settings-link-cmd.png b/development-tools/rtthread-studio/faq/figures/settings-link-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..fc464b9ff9bd935bafcffcd981bb22a4159d2ed5 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/settings-link-cmd.png differ diff --git a/development-tools/rtthread-studio/faq/figures/showline.png b/development-tools/rtthread-studio/faq/figures/showline.png new file mode 100644 index 0000000000000000000000000000000000000000..8ada6aea37afc2436700da1eaef0de83d6bc9bc4 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/showline.png differ diff --git a/development-tools/rtthread-studio/faq/figures/stlinkport.png b/development-tools/rtthread-studio/faq/figures/stlinkport.png new file mode 100644 index 0000000000000000000000000000000000000000..5ee1b79f2bfe78992ce284ef2df608dc9f282e51 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/stlinkport.png differ diff --git a/development-tools/rtthread-studio/faq/figures/switchdebug.png b/development-tools/rtthread-studio/faq/figures/switchdebug.png new file mode 100644 index 0000000000000000000000000000000000000000..8f4de8757afeffcf79cfca4660180c4fde7cb3df Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/switchdebug.png differ diff --git a/development-tools/rtthread-studio/faq/figures/upadte_error1.png b/development-tools/rtthread-studio/faq/figures/upadte_error1.png new file mode 100644 index 0000000000000000000000000000000000000000..46cdeb0ffb8fcd6462f2f44cb7c29e61fd00c569 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/upadte_error1.png differ diff --git a/development-tools/rtthread-studio/faq/figures/upadte_error2.png b/development-tools/rtthread-studio/faq/figures/upadte_error2.png new file mode 100644 index 0000000000000000000000000000000000000000..275868d4eb7a60668b822c16395a9f8a859daa14 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/upadte_error2.png differ diff --git a/development-tools/rtthread-studio/faq/figures/upadte_error3.png b/development-tools/rtthread-studio/faq/figures/upadte_error3.png new file mode 100644 index 0000000000000000000000000000000000000000..b0aa99e8a5f4eb534ba7d7dbea1a5d04d65f4eb4 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/upadte_error3.png differ diff --git a/development-tools/rtthread-studio/faq/figures/upgrade.png b/development-tools/rtthread-studio/faq/figures/upgrade.png new file mode 100644 index 0000000000000000000000000000000000000000..a8f5b9a8b9c5f2b9999041cc5c62083ac63c4117 Binary files /dev/null and b/development-tools/rtthread-studio/faq/figures/upgrade.png differ diff --git a/development-tools/rtthread-studio/faq/studio-faq.md b/development-tools/rtthread-studio/faq/studio-faq.md new file mode 100644 index 0000000000000000000000000000000000000000..93b8438d2915dd05f5139f6beb82503733f52a4f --- /dev/null +++ b/development-tools/rtthread-studio/faq/studio-faq.md @@ -0,0 +1,240 @@ +# 常见问题 + + + +## 如何添加文件到工程 + +可以直接通过复制粘贴往RT-Thread Studio工程里添加文件,甚至可以直接可以往工程里复制粘贴整个文件夹,也可以通过右键新建向导选择新建文件夹,新建C源码文件或H头文件,如果添加文件后构建提示文件找不到,请到工程构建配置里将添加的文件添加Include包含头文件路径 + +## 如何让源码显示行号 + +在源码编辑窗口的左边栏上右键,选择`显示行号`,即可显示行号,如下图所示: + +![showline](figures/showline.png) + +## 导入项目的入口在哪里 + +通过`项目资源管理器`窗口右键`导入`菜单打开导入向导窗口,选择 `现有项目到工作空间中`,如下图所示: + +![importpro](figures/importpro.png) + +点击`下一步`后,通过`浏览`按钮,选择要导入的项目所在目录,向导会自动扫描目录下所有可导入的工程并列在项目列表中,勾选要导入的工程,然后点击`完成`即可完成导入工程,如下图所示: + +![importdone](figures/importdone.png) + +## 如何生成 HEX 文件 + +选中工程后,点击工具栏上的`打开构建配置`按钮,将相应的输出文件格式设置成 hex 文件格式,即可实现输出 hex 文件,如下图所示: + +![hexfile](figures/hexfile.png) + +如果需要同时生成 bin 文件和 hex 文件,需要在`构建后步骤`里添加构建后生成 HEX 文件的命令,如下图所示: + +![binfile](figures/binfile.png) + +构建后生成的 hex 文件,在工程的`Debug`目录下,如下图所示: + +![buildhe](figures/buildhe.png) + +## 串口出现丢失字符怎么办 + +排除程序的原因外,串口线的质量,波特率是否设置过高都是需要考虑的因素,可以尝试换个串口线,或者将波特率调低点试试。 + +## 删除工程的时候删不掉怎么办 + +由于工程里可能有 git 文件被 git 程序占用造成工程有时删除部分后失败,提示占用问题,可以试试删除工程先前先关闭工程,等待一会后再删除,或者删除失败后,通过重启菜单重启一下 RT-Thread Studio 就可以正常删除了,重新启动菜单入口(菜单栏文件菜单内)和关闭项目(项目上右键菜单内)入口如下图所示: + +![restartpro](figures/restartpro.png) + +## 用户是否可以修改 rt-thread 及 packages 目录下的文件及配置 + +不可以。rt-thread 及 packages 目录下的文件统一使用 RT-Thread Setting 文件来管理和配置,当需要这两个目录下的相关文件参与编译时,应该在 RT-Thread Setting 文件中来打开相关功能。 + +例如用户想让当前 rt-thread 目录下的 dfs 参与编译,如下图所示 + +![build-dfs](figures/build-dfs.png) + +用户不能直接修改文件属性让 dfs 文件参与编译,而是通过 RT-Thread Setting 文件打开 DFS 的配置,保存配置后 dfs 的配置后,Studio 会自动将 dfs 文件添加到构建文件中,如下图所示 + +![open-dfs](figures/open-dfs.png) + +## Studio 编译结果中 text,data,bss,dec 和 hex 的各个含义是什么 + +Studio 的编译结果如下图所示 + +![build-data](figures/build-data.png) + +- text:代码段,用来存放代码及一些只读常量,一般是只读的区域 + +- data:数据段,用来存放全局初始化变量,以及全局或局部静态变量 + +- bss:BSS 段,用来存放所有未初始化的数据,用 0 来初始化 + +- dec:是 decimal 即十进制的缩写,是 text,data 和 bss 的算术和 +本例中:51344 + 372 + 2808 = 54524 + +- hex:是 hexadecimal 即十六进制的缩写,本例中:十进制数 54524 对应的十六进制就是 d4fc + +- filename:编译生成的目标文件名,本例中即为:rtthread.elf + +编译生成的目标文件占用内存的计算方法 + +程序占用的 FLASH 大小 = text 段的大小 + data 段的大小 + +程序占用的 RAM 大小 = data 段的大小 + bss 段的大小 + +## ST-LINK烧写端口被占用怎么办 + + 当弹出错误对话框提示“TCP Port 61234 not available”时, 打开调试配置修改 ST-LINK端口号即可, 如下图所示: + +![stlinkport](figures/stlinkport.png) + +## 打开RT-thread settings窗口,为什么看不到图标界面 + +新建工程选择RT-Thread非Nano版本源码即可, Nano是纯净版,没有组件概念 + +## RT-Thread Studio 升级失败 + +总共分两种情况: + +- 如果更新失败提示信息里有 “org.eclipse.equinox.internal.p2.engine.phases.CheckTrust” +RT-Thread Studio 升级前请先关闭翻墙工具,或尝试删除安装目录下的“artifacts.xml”文件重启再试 + +- 如果更新失败提示信息里有 “No Repository found”或发生下图所示错误,请手动探测更新。在更新页面内,取消勾选 “Group items by category”和“Contact all update sites during install to find required software” 并勾选"Eclipse Platform Launcher Executables",然后尝试安装studio更新,若安装过程中出错,关闭后重试一次即可 。 + + ![upadte_error1](figures/upadte_error1.png) + + ![upadte_error2](figures/upadte_error2.png) + + ![upadte_error3](figures/upadte_error3.png) + +## 如何启用黑色主题和设置编辑器配色 + +通过`首选项`的`外观`配置项选择“DevStyle Theme”即可启用新的黑色主题,切换主题后需要重启Studio后才会生效 + +![devstyle1](figures/devstyle1.png) + +![devstyle2](figures/devstyle2.png) + +## 如何查看和修改快捷键 + +通过`帮助`菜单打开`键辅助`可查看快捷键 + +![lookkey1](figures/look_key1.png) + +![lookkey2](figures/look_key2.png) + +## 如何恢复界面 + +![resetpage](figures/resetpage.png) + +## 如何打开关闭的项目 +右键菜单,`打开项目`即可,如下图所示: + +![openproject](figures/openproject.png) + +## 创建工程失败 装载 python DLL 出错 怎么办 + +- 问题描述: Error loading Python DLL `xxx/MEI85262/python36.dll`. LoadLibrary:找不到指定程序 + +![python36.png](figures/python36.png) + +- 解决方法:请手动安装 Visual C++ Redistributable, [点我下载](https://realthread.cowtransfer.com/s/96d5e5928fc54e) + +## 如何取消启动调试前的自动构建 + +首先通过`窗口`菜单打开`首选项`窗口,然后展开`运行/调试`选项并点击进入`启动`选项,最后将`在启动之前构建(如必需)`选项取消勾选即可取消启动调试前的自动构建,如下图所示: + +![bulid_cancel](figures/bulid_cancel.png) + +## 项目资源管理器如何 显示/隐藏 被排除构建的资源 + +RT-Thread Studio 从 V 1.1.4 版本开始支持`显示/隐藏`被排除构建的资源,如下图所示,这些被排除构建的资源显示为灰色,并且资源图标上有斜杠标志,被排除构建的资源将不会在启动编译时参与编译,Studio 默认隐藏了这些资源。 + +![exclude_build_0](figures/exclude_build_0.png) + +- 如果想`显示/隐藏`这些资源,请先点击项目资源管理器右上角的倒三角,会展示如下菜单,点击过滤器和定制: + +![exclude_build_1](figures/exclude_build_1.png) + +- 在过滤器和定制中,有很多可以选择过滤的资源,`勾选/取消勾选` RTT Excluded Resource ,即可`隐藏/显示`被排除构建的资源。 + +![exclude_build_2](figures/exclude_build_2.png) + +## 如何解决 studio 编译链接时命令行超出 windows 长度限制的问题 + +编译工程链接的时候,若出现如下图所示【 CreateProcess : No such file or directory 】的错误,可能是因为工程文件过多或者目录名过长等原因导致链接时命令行过长,部分 .o 被截断产生的问题。 + +![link-cmd](figures/link-cmd.png) + +Windows 下命令行的字符串长度限制为 8191 个字符,当然我们也有办法去解决此限制,请使用以下一种或者多种方法来解决这个问题(根据您的具体情况): + + - 缩短链接文件的路径 + - 为文件夹和文件使用较短的名称 + - 减少文件夹树的深度 + - 将文件存储在更少的文件夹中 + + - 修改工程链接配置命令**(推荐)** + - 在 C/C++ Build/Settings/Gnu ARM Cross C++ Linker 中配置 Command line pattern 为 + + ``` + @$(file >link.temp,${cross_toolchain_flags} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}) ../project_linker.exe link.temp ${COMMAND} + ``` + + ![settings-link-cmd](figures/settings-link-cmd.png) + - 在工程目录下添加 project_link.exe 可执行文件,可在此下载 [project_linker.exe](../figures/project_linker.exe) + + ![project_linker](figures/project_linker.jpg) + + - 重新编译工程,即可正常进行链接 + +## 如何解决 studio 清理工程时命令行超出 windows 限制的问题 + +在清理 ( Clean ) 工程的时候,当工程过大时,可能会和链接时一样出现 process_begin : CreateProcess 的问题,由于命令 rm 后的参数过长,导致 windows 不能处理此命令。 + +![clean_error](figures/clean_error.png) + +推荐您使用以下方法解决这个问题: + +- 点击下载 [clean_script.exe](../figures/clean_script.exe) + +- 将 clean_script.exe 置于工程根目录下 + +- 修改工程根目录下的 makefile.targets 中的 clean2 命令为 : + + ``` + clean2: + @$(file > clean, $(OBJS) $(C_DEPS) $(CPP_DEPS))../clean_script.exe clean + -$(RM) $(CC_DEPS) $(C++_DEPS) $(C_UPPER_DEPS) $(CXX_DEPS) $(SECONDARY_FLASH) $(SECONDARY_SIZE) $(ASM_DEPS) $(S_UPPER_DEPS) *.elf + -@echo ' ' + ``` + + 注:clean2 下的命令前的空格是 Tab ,不能使用空格键 + +- 重新清理工程 + +## 添加软件包后在 packages 目录找不到的解决办法 + +如果在 RT-Thread Settings 添加软件包后,在 packages 目录下找不到软件包或者软件包被排除编译,请尝试以下方法: + +- 以 network_samples-v0.3.0 软件包为例: + + - 双击打开 RT-Thread Settings + + ![image-20201111183423826](figures/open_settings.png) + + - 右键不能显示的软件包,打开详细配置 + + ![image-20201111182737509](figures/pkg_open.png) + + - 为该软件包配置至少配置一个子选项,不然和没勾选的参与编译情况是一样的 + + ![image-20201111182930239](figures/pkg_cfg.png) + + - 保存该配置即可 + + ![image-20201111183636804](figures/project_root.png) + + + +​ \ No newline at end of file diff --git a/development-tools/rtthread-studio/figures/image-20210127181325681.png b/development-tools/rtthread-studio/figures/image-20210127181325681.png new file mode 100644 index 0000000000000000000000000000000000000000..6e01972022642674a0727f221c4f6fdd7c29cbff Binary files /dev/null and b/development-tools/rtthread-studio/figures/image-20210127181325681.png differ diff --git a/development-tools/rtthread-studio/um/figures/1587034536740.png b/development-tools/rtthread-studio/um/figures/1587034536740.png new file mode 100644 index 0000000000000000000000000000000000000000..4b54f4d1de04ed950400fffe762d84017f2e76cd Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/1587034536740.png differ diff --git a/development-tools/rtthread-studio/um/figures/1587034628405.png b/development-tools/rtthread-studio/um/figures/1587034628405.png new file mode 100644 index 0000000000000000000000000000000000000000..3b1de7851af1e595757351a5ed6e7c766d145171 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/1587034628405.png differ diff --git a/development-tools/rtthread-studio/um/figures/1587034767730.png b/development-tools/rtthread-studio/um/figures/1587034767730.png new file mode 100644 index 0000000000000000000000000000000000000000..a17b45ad24a333d08bb24b7a37d16112dd5ce7f2 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/1587034767730.png differ diff --git a/development-tools/rtthread-studio/um/figures/1587034943959.png b/development-tools/rtthread-studio/um/figures/1587034943959.png new file mode 100644 index 0000000000000000000000000000000000000000..dc1727d604a852edb527db74eb277c4dac8795ea Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/1587034943959.png differ diff --git a/development-tools/rtthread-studio/um/figures/1587034996637.png b/development-tools/rtthread-studio/um/figures/1587034996637.png new file mode 100644 index 0000000000000000000000000000000000000000..86a08c23b903f6cefe76290b1e7975c41b5835d4 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/1587034996637.png differ diff --git a/development-tools/rtthread-studio/um/figures/1587036090606.png b/development-tools/rtthread-studio/um/figures/1587036090606.png new file mode 100644 index 0000000000000000000000000000000000000000..95f94dfa9af627c3125f544cbc298bee1bed852c Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/1587036090606.png differ diff --git a/development-tools/rtthread-studio/um/figures/Encoding_01.png b/development-tools/rtthread-studio/um/figures/Encoding_01.png new file mode 100644 index 0000000000000000000000000000000000000000..36acf9da292e829822759219eee38731095d2c51 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/Encoding_01.png differ diff --git a/development-tools/rtthread-studio/um/figures/Encoding_02.png b/development-tools/rtthread-studio/um/figures/Encoding_02.png new file mode 100644 index 0000000000000000000000000000000000000000..6c858cbb0e52e323c7e37088cece4d27112a31da Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/Encoding_02.png differ diff --git a/development-tools/rtthread-studio/um/figures/Encoding_03.png b/development-tools/rtthread-studio/um/figures/Encoding_03.png new file mode 100644 index 0000000000000000000000000000000000000000..37b01db52253b16164315d8da9cbdde9f0d57693 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/Encoding_03.png differ diff --git a/development-tools/rtthread-studio/um/figures/Encoding_04.png b/development-tools/rtthread-studio/um/figures/Encoding_04.png new file mode 100644 index 0000000000000000000000000000000000000000..b5380a0f404c110f4de876bb0574b51550bf3564 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/Encoding_04.png differ diff --git a/development-tools/rtthread-studio/um/figures/Encoding_05.png b/development-tools/rtthread-studio/um/figures/Encoding_05.png new file mode 100644 index 0000000000000000000000000000000000000000..adfef5dd2d7e302ae3922c0e1326c0e9972ec103 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/Encoding_05.png differ diff --git a/development-tools/rtthread-studio/um/figures/RTOS-API.png b/development-tools/rtthread-studio/um/figures/RTOS-API.png new file mode 100644 index 0000000000000000000000000000000000000000..c3960abb7db6022f8ed1d6150dcfd15cc578d6b5 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/RTOS-API.png differ diff --git a/development-tools/rtthread-studio/um/figures/SDK-Manager.png b/development-tools/rtthread-studio/um/figures/SDK-Manager.png new file mode 100644 index 0000000000000000000000000000000000000000..8a069216a12cad133bb99f4599a5705ff230129e Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/SDK-Manager.png differ diff --git a/development-tools/rtthread-studio/um/figures/add-binary-library-re.png b/development-tools/rtthread-studio/um/figures/add-binary-library-re.png new file mode 100644 index 0000000000000000000000000000000000000000..e658541ad9652f3ef3f9d6b04a05f29ddfe27598 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/add-binary-library-re.png differ diff --git a/development-tools/rtthread-studio/um/figures/add-binary-library.png b/development-tools/rtthread-studio/um/figures/add-binary-library.png new file mode 100644 index 0000000000000000000000000000000000000000..60803deb03ec3a8409481c22928e73347dd70604 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/add-binary-library.png differ diff --git a/development-tools/rtthread-studio/um/figures/add-packages.png b/development-tools/rtthread-studio/um/figures/add-packages.png new file mode 100644 index 0000000000000000000000000000000000000000..db20938e3142cdb450a5b0b22ae8c3dd45e3d154 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/add-packages.png differ diff --git a/development-tools/rtthread-studio/um/figures/add_file_to_studio.gif b/development-tools/rtthread-studio/um/figures/add_file_to_studio.gif new file mode 100644 index 0000000000000000000000000000000000000000..7314d89de022db4e567167b1583c7969e4191892 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/add_file_to_studio.gif differ diff --git a/development-tools/rtthread-studio/um/figures/addformat.png b/development-tools/rtthread-studio/um/figures/addformat.png new file mode 100644 index 0000000000000000000000000000000000000000..baa6cfe6edd649eeabe6b40af42e36fb38076134 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/addformat.png differ diff --git a/development-tools/rtthread-studio/um/figures/apollo_project.png b/development-tools/rtthread-studio/um/figures/apollo_project.png new file mode 100644 index 0000000000000000000000000000000000000000..6a5b47d7238facd8db3d9e642711413d80bd8515 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/apollo_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/asmstep.png b/development-tools/rtthread-studio/um/figures/asmstep.png new file mode 100644 index 0000000000000000000000000000000000000000..90d1108502225193a6e80ac2665b5c4c402b6a6b Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/asmstep.png differ diff --git a/development-tools/rtthread-studio/um/figures/breakpoint.png b/development-tools/rtthread-studio/um/figures/breakpoint.png new file mode 100644 index 0000000000000000000000000000000000000000..5e73782568e21a02ee62925e7033400290dec347 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/breakpoint.png differ diff --git a/development-tools/rtthread-studio/um/figures/build-entry.png b/development-tools/rtthread-studio/um/figures/build-entry.png new file mode 100644 index 0000000000000000000000000000000000000000..f4a7faa9b28c355c3db62e0338e1f4bcc1eb1b83 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/build-entry.png differ diff --git a/development-tools/rtthread-studio/um/figures/build-info.png b/development-tools/rtthread-studio/um/figures/build-info.png new file mode 100644 index 0000000000000000000000000000000000000000..ae527ee22d8dcadd604fab38ae488ab78bbbedca Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/build-info.png differ diff --git a/development-tools/rtthread-studio/um/figures/build-pro.png b/development-tools/rtthread-studio/um/figures/build-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..1eb7d1f9311fd6b4866bd44db3dbd2be5ee622ca Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/build-pro.png differ diff --git a/development-tools/rtthread-studio/um/figures/build_project.png b/development-tools/rtthread-studio/um/figures/build_project.png new file mode 100644 index 0000000000000000000000000000000000000000..349bcd1306f4e7f70f0ecc39adf5f1566f09ca21 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/build_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/can-close.png b/development-tools/rtthread-studio/um/figures/can-close.png new file mode 100644 index 0000000000000000000000000000000000000000..25601d6b16bb99f7ccb8f3a2252e6347148768fd Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/can-close.png differ diff --git a/development-tools/rtthread-studio/um/figures/code-edit.png b/development-tools/rtthread-studio/um/figures/code-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..57d92be462354bcb571442e028ec1366019fb55c Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/code-edit.png differ diff --git a/development-tools/rtthread-studio/um/figures/compile.png b/development-tools/rtthread-studio/um/figures/compile.png new file mode 100644 index 0000000000000000000000000000000000000000..8df332c3292170c7d0e725ac32c688bebe125714 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/compile.png differ diff --git a/development-tools/rtthread-studio/um/figures/component.png b/development-tools/rtthread-studio/um/figures/component.png new file mode 100644 index 0000000000000000000000000000000000000000..3ec044c6c4291a147740fff4a98dd04737b627c3 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/component.png differ diff --git a/development-tools/rtthread-studio/um/figures/create_new_folder.gif b/development-tools/rtthread-studio/um/figures/create_new_folder.gif new file mode 100644 index 0000000000000000000000000000000000000000..391cbccbdbd1802640aa0d478d537a3429187fda Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/create_new_folder.gif differ diff --git a/development-tools/rtthread-studio/um/figures/debug-pro.png b/development-tools/rtthread-studio/um/figures/debug-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..252b024b9c06c32ec9d78dadb34b264e18af5f89 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/debug-pro.png differ diff --git a/development-tools/rtthread-studio/um/figures/debug-see.png b/development-tools/rtthread-studio/um/figures/debug-see.png new file mode 100644 index 0000000000000000000000000000000000000000..e8a1aef5ee36fb7176fbad83a469168d9ec55d99 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/debug-see.png differ diff --git a/development-tools/rtthread-studio/um/figures/debugentry.png b/development-tools/rtthread-studio/um/figures/debugentry.png new file mode 100644 index 0000000000000000000000000000000000000000..f549245b9468777001ccc0968939f45d4621a0b4 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/debugentry.png differ diff --git a/development-tools/rtthread-studio/um/figures/debuger.png b/development-tools/rtthread-studio/um/figures/debuger.png new file mode 100644 index 0000000000000000000000000000000000000000..f0e0c36cc2152d8a90c56289194966df6e6bbf07 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/debuger.png differ diff --git a/development-tools/rtthread-studio/um/figures/debugopt.png b/development-tools/rtthread-studio/um/figures/debugopt.png new file mode 100644 index 0000000000000000000000000000000000000000..f898d4276af17876c2a3456840ca14e9a1a2ad09 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/debugopt.png differ diff --git a/development-tools/rtthread-studio/um/figures/debugset.png b/development-tools/rtthread-studio/um/figures/debugset.png new file mode 100644 index 0000000000000000000000000000000000000000..bbad0f572b9f8158dcd2f56d50947378135ae38d Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/debugset.png differ diff --git a/development-tools/rtthread-studio/um/figures/deletesdk.png b/development-tools/rtthread-studio/um/figures/deletesdk.png new file mode 100644 index 0000000000000000000000000000000000000000..9ad4abc05c0acc6e06bdae3ca68cfa8294a4b805 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/deletesdk.png differ diff --git a/development-tools/rtthread-studio/um/figures/detail-set.png b/development-tools/rtthread-studio/um/figures/detail-set.png new file mode 100644 index 0000000000000000000000000000000000000000..c94609ecd89d8aba17ed488f5276c94c7b3ad144 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/detail-set.png differ diff --git a/development-tools/rtthread-studio/um/figures/devstyle1.png b/development-tools/rtthread-studio/um/figures/devstyle1.png new file mode 100644 index 0000000000000000000000000000000000000000..67b13339598f6ad7e8860e827a5183125fc3e625 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/devstyle1.png differ diff --git a/development-tools/rtthread-studio/um/figures/devstyle2.png b/development-tools/rtthread-studio/um/figures/devstyle2.png new file mode 100644 index 0000000000000000000000000000000000000000..ff43ae95f9400d342bebf015eec3065509b9b53d Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/devstyle2.png differ diff --git a/development-tools/rtthread-studio/um/figures/done-pro.png b/development-tools/rtthread-studio/um/figures/done-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..ceab326bb9a0bf71c5e6a86c8274dc52b4bc4da9 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/done-pro.png differ diff --git a/development-tools/rtthread-studio/um/figures/download-info.png b/development-tools/rtthread-studio/um/figures/download-info.png new file mode 100644 index 0000000000000000000000000000000000000000..78eee462888bd4f2e46947276f83d4eb9b650a37 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/download-info.png differ diff --git a/development-tools/rtthread-studio/um/figures/download-pro.png b/development-tools/rtthread-studio/um/figures/download-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..56a14b028842cb112bf50faf6f5d0b38e6c8ed97 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/download-pro.png differ diff --git a/development-tools/rtthread-studio/um/figures/editmd.png b/development-tools/rtthread-studio/um/figures/editmd.png new file mode 100644 index 0000000000000000000000000000000000000000..5f52fc570ca5e342b016805ed154d54818254517 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/editmd.png differ diff --git a/development-tools/rtthread-studio/um/figures/editreg.png b/development-tools/rtthread-studio/um/figures/editreg.png new file mode 100644 index 0000000000000000000000000000000000000000..e5a1871b8be21c9d32acc652bb964e69d1dcfaa7 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/editreg.png differ diff --git a/development-tools/rtthread-studio/um/figures/firstopt.png b/development-tools/rtthread-studio/um/figures/firstopt.png new file mode 100644 index 0000000000000000000000000000000000000000..64263e2a2c9021d1c7126520abc8a0d227dda320 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/firstopt.png differ diff --git a/development-tools/rtthread-studio/um/figures/generate_dirs.png b/development-tools/rtthread-studio/um/figures/generate_dirs.png new file mode 100644 index 0000000000000000000000000000000000000000..b98754f6f8d8f4cc443456b30ae526101770bbd6 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/generate_dirs.png differ diff --git a/development-tools/rtthread-studio/um/figures/help-bottom.png b/development-tools/rtthread-studio/um/figures/help-bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..635bfda0e8d68a5b9ebb60fec9f200030bade908 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/help-bottom.png differ diff --git a/development-tools/rtthread-studio/um/figures/import_project_name.png b/development-tools/rtthread-studio/um/figures/import_project_name.png new file mode 100644 index 0000000000000000000000000000000000000000..4a2e329d110eff055a7aef22b3d1379b72ace94b Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/import_project_name.png differ diff --git a/development-tools/rtthread-studio/um/figures/import_success.png b/development-tools/rtthread-studio/um/figures/import_success.png new file mode 100644 index 0000000000000000000000000000000000000000..a0f9b7055e06082343e484e13a911967cecf3127 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/import_success.png differ diff --git a/development-tools/rtthread-studio/um/figures/importdone.png b/development-tools/rtthread-studio/um/figures/importdone.png new file mode 100644 index 0000000000000000000000000000000000000000..ed58379600aeaec1ab4aa02d1327cd186f31b702 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/importdone.png differ diff --git a/development-tools/rtthread-studio/um/figures/importpro.png b/development-tools/rtthread-studio/um/figures/importpro.png new file mode 100644 index 0000000000000000000000000000000000000000..b0ac8c372b662a6802a2dcf63973b53a41107f1a Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/importpro.png differ diff --git a/development-tools/rtthread-studio/um/figures/install-studio.png b/development-tools/rtthread-studio/um/figures/install-studio.png new file mode 100644 index 0000000000000000000000000000000000000000..809bffe0b88b4f3b7d8d1a8d78f67510ced4b480 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/install-studio.png differ diff --git a/development-tools/rtthread-studio/um/figures/installsdk.png b/development-tools/rtthread-studio/um/figures/installsdk.png new file mode 100644 index 0000000000000000000000000000000000000000..b0fa56d6d69d968a386fd9e4af05f13c6655185d Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/installsdk.png differ diff --git a/development-tools/rtthread-studio/um/figures/license.png b/development-tools/rtthread-studio/um/figures/license.png new file mode 100644 index 0000000000000000000000000000000000000000..376f545ca1e4cc109f7a55475b6dd6b16fb6fe93 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/license.png differ diff --git a/development-tools/rtthread-studio/um/figures/look-detail.png b/development-tools/rtthread-studio/um/figures/look-detail.png new file mode 100644 index 0000000000000000000000000000000000000000..378313be9779806013aa48d15fb5ac51e355afae Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/look-detail.png differ diff --git a/development-tools/rtthread-studio/um/figures/max-pic.png b/development-tools/rtthread-studio/um/figures/max-pic.png new file mode 100644 index 0000000000000000000000000000000000000000..2fc36f3f9fc4d77ccf4b135f40318f939818697b Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/max-pic.png differ diff --git a/development-tools/rtthread-studio/um/figures/max-size.png b/development-tools/rtthread-studio/um/figures/max-size.png new file mode 100644 index 0000000000000000000000000000000000000000..3a9b68bd1fab5ef3b1ed700388b8330810d6ae1b Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/max-size.png differ diff --git a/development-tools/rtthread-studio/um/figures/mdk_iar_import.png b/development-tools/rtthread-studio/um/figures/mdk_iar_import.png new file mode 100644 index 0000000000000000000000000000000000000000..d01194ccb4119c81797f8fccb14f6ace4a30c057 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/mdk_iar_import.png differ diff --git a/development-tools/rtthread-studio/um/figures/new-pro.png b/development-tools/rtthread-studio/um/figures/new-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..5e200a205d4194241cdda298b658aa7b0d7837f6 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/new-pro.png differ diff --git a/development-tools/rtthread-studio/um/figures/new-resource.png b/development-tools/rtthread-studio/um/figures/new-resource.png new file mode 100644 index 0000000000000000000000000000000000000000..ce2e008e172b29c450b71bd31870a33b2f313559 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/new-resource.png differ diff --git a/development-tools/rtthread-studio/um/figures/open-detail.png b/development-tools/rtthread-studio/um/figures/open-detail.png new file mode 100644 index 0000000000000000000000000000000000000000..06808da196b4b4f8906041ace279a72dc397542f Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/open-detail.png differ diff --git a/development-tools/rtthread-studio/um/figures/open-element.png b/development-tools/rtthread-studio/um/figures/open-element.png new file mode 100644 index 0000000000000000000000000000000000000000..31778cf547988c9c6f9ccc6c3304b6e2f22a95a4 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/open-element.png differ diff --git a/development-tools/rtthread-studio/um/figures/open-set.png b/development-tools/rtthread-studio/um/figures/open-set.png new file mode 100644 index 0000000000000000000000000000000000000000..5b225f953a198b6e1f8a74a477f5fafcc6b37e50 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/open-set.png differ diff --git a/development-tools/rtthread-studio/um/figures/optmenu.png b/development-tools/rtthread-studio/um/figures/optmenu.png new file mode 100644 index 0000000000000000000000000000000000000000..03aeb36c635c0d491b7df8d5e20ae51c2bb5c675 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/optmenu.png differ diff --git a/development-tools/rtthread-studio/um/figures/pahomqtt.png b/development-tools/rtthread-studio/um/figures/pahomqtt.png new file mode 100644 index 0000000000000000000000000000000000000000..b7586c4557507ddd61eb0515d80519876deffbe9 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/pahomqtt.png differ diff --git a/development-tools/rtthread-studio/um/figures/path-include.png b/development-tools/rtthread-studio/um/figures/path-include.png new file mode 100644 index 0000000000000000000000000000000000000000..341a0921c24fed7924580fb1ac6d019dbcd8c138 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/path-include.png differ diff --git a/development-tools/rtthread-studio/um/figures/path-install.png b/development-tools/rtthread-studio/um/figures/path-install.png new file mode 100644 index 0000000000000000000000000000000000000000..637c79ad183329cea09a099b46b3a030a8186c1e Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/path-install.png differ diff --git a/development-tools/rtthread-studio/um/figures/platformio_build_project.png b/development-tools/rtthread-studio/um/figures/platformio_build_project.png new file mode 100644 index 0000000000000000000000000000000000000000..8c8569168ea24385e88503ad7511160928e0d08b Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/platformio_build_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/platformio_debug_project.png b/development-tools/rtthread-studio/um/figures/platformio_debug_project.png new file mode 100644 index 0000000000000000000000000000000000000000..50cb00b892fbaff010ccc6b4a9fcbd142d69bb2a Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/platformio_debug_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/platformio_debug_view.png b/development-tools/rtthread-studio/um/figures/platformio_debug_view.png new file mode 100644 index 0000000000000000000000000000000000000000..de40130dcca9204693ccbebb23f1e7fa6ba3fca2 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/platformio_debug_view.png differ diff --git a/development-tools/rtthread-studio/um/figures/platformio_download.png b/development-tools/rtthread-studio/um/figures/platformio_download.png new file mode 100644 index 0000000000000000000000000000000000000000..62a29cd2ebf44da9d09dc853414d8e5b59cbeaa2 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/platformio_download.png differ diff --git a/development-tools/rtthread-studio/um/figures/platformio_download_project.png b/development-tools/rtthread-studio/um/figures/platformio_download_project.png new file mode 100644 index 0000000000000000000000000000000000000000..5c294b651c39423e1a4f2a2fc1764da7aae331b9 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/platformio_download_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/platformio_new_project.png b/development-tools/rtthread-studio/um/figures/platformio_new_project.png new file mode 100644 index 0000000000000000000000000000000000000000..6a4075b9c8589a9a06ba9f52cb5ff3b0ef5d2ed5 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/platformio_new_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/platformio_open_serial.png b/development-tools/rtthread-studio/um/figures/platformio_open_serial.png new file mode 100644 index 0000000000000000000000000000000000000000..9666c7ce263dae7899f40ec336ac33ec6c6fe0c1 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/platformio_open_serial.png differ diff --git a/development-tools/rtthread-studio/um/figures/pro-set.png b/development-tools/rtthread-studio/um/figures/pro-set.png new file mode 100644 index 0000000000000000000000000000000000000000..5b3a2eb300cd84ee47a038c76183f1c062528ede Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/pro-set.png differ diff --git a/development-tools/rtthread-studio/um/figures/project_structure.png b/development-tools/rtthread-studio/um/figures/project_structure.png new file mode 100644 index 0000000000000000000000000000000000000000..78d031f1a2b75754af69e841c499fc788faf2579 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/project_structure.png differ diff --git a/development-tools/rtthread-studio/um/figures/putty.png b/development-tools/rtthread-studio/um/figures/putty.png new file mode 100644 index 0000000000000000000000000000000000000000..d03346dc8dfc8e6541bd738c822ac2b377a37a18 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/putty.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_configure.png b/development-tools/rtthread-studio/um/figures/qemu_configure.png new file mode 100644 index 0000000000000000000000000000000000000000..894a8c30656a6dfbf1d9737e9cdabaf05ac1bbaa Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_configure.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_debug.png b/development-tools/rtthread-studio/um/figures/qemu_debug.png new file mode 100644 index 0000000000000000000000000000000000000000..fe50075b4f7d3213ff1229833219620b2d58f2cf Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_debug.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_network_configure_stm32f4.png b/development-tools/rtthread-studio/um/figures/qemu_network_configure_stm32f4.png new file mode 100644 index 0000000000000000000000000000000000000000..880987124ef156996857fad7377abf873b519d30 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_network_configure_stm32f4.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_network_debug_stm32f4.png b/development-tools/rtthread-studio/um/figures/qemu_network_debug_stm32f4.png new file mode 100644 index 0000000000000000000000000000000000000000..bc140924de8e666049a7c95944f5bbd47c741e4f Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_network_debug_stm32f4.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_network_new_stm32f4.png b/development-tools/rtthread-studio/um/figures/qemu_network_new_stm32f4.png new file mode 100644 index 0000000000000000000000000000000000000000..25ca5f9b2cf44637f43338671f5d6d91b50eb24e Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_network_new_stm32f4.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_network_rt-thread_settings.png b/development-tools/rtthread-studio/um/figures/qemu_network_rt-thread_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..e68a04ccf2906debb65baf18a0aad8b27be8aa48 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_network_rt-thread_settings.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_network_rtthread_setting2.png b/development-tools/rtthread-studio/um/figures/qemu_network_rtthread_setting2.png new file mode 100644 index 0000000000000000000000000000000000000000..9b837be952a68d306a53ae5872f6cce8afff00fe Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_network_rtthread_setting2.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_network_test_stm32f4.png b/development-tools/rtthread-studio/um/figures/qemu_network_test_stm32f4.png new file mode 100644 index 0000000000000000000000000000000000000000..b99723fa22c5c5a69f2e17a052015fa819990b53 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_network_test_stm32f4.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_new_project.png b/development-tools/rtthread-studio/um/figures/qemu_new_project.png new file mode 100644 index 0000000000000000000000000000000000000000..82cbfe7d5d9d3e5435959c5ea79b6bd0b166b11d Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_new_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/qemu_run.png b/development-tools/rtthread-studio/um/figures/qemu_run.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c5ae459db2e20f5ce3f5854c60b1b46a609ff4 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/qemu_run.png differ diff --git a/development-tools/rtthread-studio/um/figures/rebuild.png b/development-tools/rtthread-studio/um/figures/rebuild.png new file mode 100644 index 0000000000000000000000000000000000000000..757ad04acab7e00a1eabaa3648f677583d0207a3 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/rebuild.png differ diff --git a/development-tools/rtthread-studio/um/figures/rebuild_project.png b/development-tools/rtthread-studio/um/figures/rebuild_project.png new file mode 100644 index 0000000000000000000000000000000000000000..9f76950093da3863d99a9c6771fab58e33246393 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/rebuild_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/reset-see.png b/development-tools/rtthread-studio/um/figures/reset-see.png new file mode 100644 index 0000000000000000000000000000000000000000..e9382362960022dbf9b935e763d44c84a956e325 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/reset-see.png differ diff --git a/development-tools/rtthread-studio/um/figures/restore.png b/development-tools/rtthread-studio/um/figures/restore.png new file mode 100644 index 0000000000000000000000000000000000000000..1779c2f53b948102b98d6738506353cd2bf55ba6 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/restore.png differ diff --git a/development-tools/rtthread-studio/um/figures/rtthread-pro.png b/development-tools/rtthread-studio/um/figures/rtthread-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..e61d13a8c0b387e2154e24f3258636e6c917e6b9 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/rtthread-pro.png differ diff --git a/development-tools/rtthread-studio/um/figures/save-set.png b/development-tools/rtthread-studio/um/figures/save-set.png new file mode 100644 index 0000000000000000000000000000000000000000..cff03ffadf3f8592650c9f8f9986017d397907b5 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/save-set.png differ diff --git a/development-tools/rtthread-studio/um/figures/sdkmanager.png b/development-tools/rtthread-studio/um/figures/sdkmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..c7eb56f0fea82633b334fc7a7ead0b515892b47b Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/sdkmanager.png differ diff --git a/development-tools/rtthread-studio/um/figures/search-menu.png b/development-tools/rtthread-studio/um/figures/search-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..e7ab5ad4ea8afe9633832bc50f988adfe66a8459 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/search-menu.png differ diff --git a/development-tools/rtthread-studio/um/figures/search-set.png b/development-tools/rtthread-studio/um/figures/search-set.png new file mode 100644 index 0000000000000000000000000000000000000000..4b5915311cb9497acc67fc64fc0ac46a571fc544 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/search-set.png differ diff --git a/development-tools/rtthread-studio/um/figures/see-depend.png b/development-tools/rtthread-studio/um/figures/see-depend.png new file mode 100644 index 0000000000000000000000000000000000000000..7e968e561c4172531ee07e444de1d6706e3668c4 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/see-depend.png differ diff --git a/development-tools/rtthread-studio/um/figures/seemem.png b/development-tools/rtthread-studio/um/figures/seemem.png new file mode 100644 index 0000000000000000000000000000000000000000..d5e3a2a4cec5eda784f4106dc5514a1520eef98a Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/seemem.png differ diff --git a/development-tools/rtthread-studio/um/figures/seememreg.png b/development-tools/rtthread-studio/um/figures/seememreg.png new file mode 100644 index 0000000000000000000000000000000000000000..7e35d6e88da9f7c063f3219ce89baa26790b62cc Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/seememreg.png differ diff --git a/development-tools/rtthread-studio/um/figures/seeperial.png b/development-tools/rtthread-studio/um/figures/seeperial.png new file mode 100644 index 0000000000000000000000000000000000000000..239446daf3df519b3b2af91770b0db67a77b13db Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/seeperial.png differ diff --git a/development-tools/rtthread-studio/um/figures/seereg.png b/development-tools/rtthread-studio/um/figures/seereg.png new file mode 100644 index 0000000000000000000000000000000000000000..1f929003e5cbad6e8371381bbd5c69febeb6ef14 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/seereg.png differ diff --git a/development-tools/rtthread-studio/um/figures/seeterminal.png b/development-tools/rtthread-studio/um/figures/seeterminal.png new file mode 100644 index 0000000000000000000000000000000000000000..3d1edece58b77a202367173a3eb67644d74cd202 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/seeterminal.png differ diff --git a/development-tools/rtthread-studio/um/figures/seevalue.png b/development-tools/rtthread-studio/um/figures/seevalue.png new file mode 100644 index 0000000000000000000000000000000000000000..f2945428026a9204922be5fcba0061e76cb210c4 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/seevalue.png differ diff --git a/development-tools/rtthread-studio/um/figures/select_import.png b/development-tools/rtthread-studio/um/figures/select_import.png new file mode 100644 index 0000000000000000000000000000000000000000..0b0fef9f22261c7e171189803bc16b166334965e Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/select_import.png differ diff --git a/development-tools/rtthread-studio/um/figures/select_rtt_project.png b/development-tools/rtthread-studio/um/figures/select_rtt_project.png new file mode 100644 index 0000000000000000000000000000000000000000..a9cc0b0effc480fdeff5c9e6f9fa9d66365fe08c Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/select_rtt_project.png differ diff --git a/development-tools/rtthread-studio/um/figures/set-ok.png b/development-tools/rtthread-studio/um/figures/set-ok.png new file mode 100644 index 0000000000000000000000000000000000000000..10766a2998c149d8d62186e5ccaf52c66114756e Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/set-ok.png differ diff --git a/development-tools/rtthread-studio/um/figures/setlinkscripts.png b/development-tools/rtthread-studio/um/figures/setlinkscripts.png new file mode 100644 index 0000000000000000000000000000000000000000..79e4d884c8c84a9a6fea8207735581268ca904f7 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/setlinkscripts.png differ diff --git a/development-tools/rtthread-studio/um/figures/setmicro.png b/development-tools/rtthread-studio/um/figures/setmicro.png new file mode 100644 index 0000000000000000000000000000000000000000..0f50ccd1f6db217f644370acc59bd767f193b2b7 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/setmicro.png differ diff --git a/development-tools/rtthread-studio/um/figures/setother.png b/development-tools/rtthread-studio/um/figures/setother.png new file mode 100644 index 0000000000000000000000000000000000000000..82114d94285c46fc3bb1f56a3ec4e59eeca208d4 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/setother.png differ diff --git a/development-tools/rtthread-studio/um/figures/setting.png b/development-tools/rtthread-studio/um/figures/setting.png new file mode 100644 index 0000000000000000000000000000000000000000..642447282eb1397545e7be613e8ac86d75999305 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/setting.png differ diff --git a/development-tools/rtthread-studio/um/figures/shuxing.png b/development-tools/rtthread-studio/um/figures/shuxing.png new file mode 100644 index 0000000000000000000000000000000000000000..0f9e9bc319b3d00854f54bec8be4db8f8d53bafb Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/shuxing.png differ diff --git a/development-tools/rtthread-studio/um/figures/sign-in.png b/development-tools/rtthread-studio/um/figures/sign-in.png new file mode 100644 index 0000000000000000000000000000000000000000..9f25de8dc85b404e5c152453bfa7c2c8651f0243 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/sign-in.png differ diff --git a/development-tools/rtthread-studio/um/figures/source.png b/development-tools/rtthread-studio/um/figures/source.png new file mode 100644 index 0000000000000000000000000000000000000000..5c5d811cbb5ada5b6998a315db8d7c1f08d17708 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/source.png differ diff --git a/development-tools/rtthread-studio/um/figures/specific_linker_file.png b/development-tools/rtthread-studio/um/figures/specific_linker_file.png new file mode 100644 index 0000000000000000000000000000000000000000..896d2f01dcb3a81f49ad4389572933ac4a5f1e62 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/specific_linker_file.png differ diff --git a/development-tools/rtthread-studio/um/figures/start-install.png b/development-tools/rtthread-studio/um/figures/start-install.png new file mode 100644 index 0000000000000000000000000000000000000000..a3f1bf61c6e5559dffaa9a5c5277ce523dbb7999 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/start-install.png differ diff --git a/development-tools/rtthread-studio/um/figures/start-menu.png b/development-tools/rtthread-studio/um/figures/start-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..e14238c2b3131c0c5b509b97187acd132f4f893c Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/start-menu.png differ diff --git a/development-tools/rtthread-studio/um/figures/start-name.png b/development-tools/rtthread-studio/um/figures/start-name.png new file mode 100644 index 0000000000000000000000000000000000000000..3c327871f9b9686ef19538e3614f99cee43cc9a2 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/start-name.png differ diff --git a/development-tools/rtthread-studio/um/figures/start-studio.png b/development-tools/rtthread-studio/um/figures/start-studio.png new file mode 100644 index 0000000000000000000000000000000000000000..2b6d2aa7fbb9ae88d80cca4b83cb9a8c6dac059c Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/start-studio.png differ diff --git a/development-tools/rtthread-studio/um/figures/studio-frame.png b/development-tools/rtthread-studio/um/figures/studio-frame.png new file mode 100644 index 0000000000000000000000000000000000000000..37fda07631c4631a9c44244d144d866e33ba5bcb Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/studio-frame.png differ diff --git a/development-tools/rtthread-studio/um/figures/studio-pic.png b/development-tools/rtthread-studio/um/figures/studio-pic.png new file mode 100644 index 0000000000000000000000000000000000000000..c2a83f2364758a18d9a03453e64110502bbdac5b Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/studio-pic.png differ diff --git a/development-tools/rtthread-studio/um/figures/switch_qemu_prompt.png b/development-tools/rtthread-studio/um/figures/switch_qemu_prompt.png new file mode 100644 index 0000000000000000000000000000000000000000..bda8cb60286cb63c79438b8b534f91c3cd845432 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/switch_qemu_prompt.png differ diff --git a/development-tools/rtthread-studio/um/figures/switch_to_qemu.png b/development-tools/rtthread-studio/um/figures/switch_to_qemu.png new file mode 100644 index 0000000000000000000000000000000000000000..198765d7bedde25cc4b652c42f3d8ca21d6a115b Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/switch_to_qemu.png differ diff --git a/development-tools/rtthread-studio/um/figures/switchdebug.png b/development-tools/rtthread-studio/um/figures/switchdebug.png new file mode 100644 index 0000000000000000000000000000000000000000..8ccc5e958553cf2376bf8edf99c4dd58dbb3ea99 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/switchdebug.png differ diff --git a/development-tools/rtthread-studio/um/figures/terminal.png b/development-tools/rtthread-studio/um/figures/terminal.png new file mode 100644 index 0000000000000000000000000000000000000000..d3c50e99dcfe98d0b7a310975acfcbc5f1a40dca Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/terminal.png differ diff --git a/development-tools/rtthread-studio/um/figures/test-pro.png b/development-tools/rtthread-studio/um/figures/test-pro.png new file mode 100644 index 0000000000000000000000000000000000000000..d03570019ba5dbb6cf5e62141c1c23f6c68a9360 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/test-pro.png differ diff --git a/development-tools/rtthread-studio/um/figures/tree-set.png b/development-tools/rtthread-studio/um/figures/tree-set.png new file mode 100644 index 0000000000000000000000000000000000000000..9c684d3d34d2ca326377da604dfb67d491536d1e Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/tree-set.png differ diff --git a/development-tools/rtthread-studio/um/figures/updatesdk.png b/development-tools/rtthread-studio/um/figures/updatesdk.png new file mode 100644 index 0000000000000000000000000000000000000000..9157be56e46019ab4d5bf9573fc954cc201c8a35 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/updatesdk.png differ diff --git a/development-tools/rtthread-studio/um/figures/waitting.png b/development-tools/rtthread-studio/um/figures/waitting.png new file mode 100644 index 0000000000000000000000000000000000000000..a87ff6501eed95b3664f2a19fcefcfcc57c6db5e Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/waitting.png differ diff --git a/development-tools/rtthread-studio/um/figures/welcome-page.png b/development-tools/rtthread-studio/um/figures/welcome-page.png new file mode 100644 index 0000000000000000000000000000000000000000..cd4d685a16e6166fba140139104fa0b6a3d013fe Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/welcome-page.png differ diff --git a/development-tools/rtthread-studio/um/figures/windows_express.png b/development-tools/rtthread-studio/um/figures/windows_express.png new file mode 100644 index 0000000000000000000000000000000000000000..4599f4be5921ea6ee599946d8236db2194acfba2 Binary files /dev/null and b/development-tools/rtthread-studio/um/figures/windows_express.png differ diff --git a/development-tools/rtthread-studio/um/studio-user-begin.md b/development-tools/rtthread-studio/um/studio-user-begin.md new file mode 100644 index 0000000000000000000000000000000000000000..74f01c54a45edb06f1e8f2cc0eb76720c5017b04 --- /dev/null +++ b/development-tools/rtthread-studio/um/studio-user-begin.md @@ -0,0 +1,111 @@ +## 快速开始 + +### 安装 RT-Thread Studio + +#### 下载 RT-Thread Studio 安装包 + +访问官网 [RT-Thread Studio 下载地址](https://www.rt-thread.org/page/studio.html),在官网下载最新的 RT-Thread Studio 软件安装包。 + +#### 安装 RT-Thread Studio + +双击安装包的 `.exe` 文件进行安装,安装界面如下图所示: + +![install-studio](figures/install-studio.png) + +安装前需要接受许可协议,如下图所示: + +![license](figures/license.png) + +指定安装路径时不要带有空格和中文字符,如下图所示: + +![path-install](figures/path-install.png) + +指定开始菜单文件夹名,如下图所示: + +![start-name](figures/start-name.png) + +开始安装 + +![start-install](figures/start-install.png) + +一直点击`下一步`直到最后点击`安装`按钮可开始进行安装,待安装完成后可直接点击`确定`即可启动 RT-Thread Studio,如下图所示: + +![start-studio](figures/start-studio.png) + +或者取消`运行RT-Thread Studio`勾选,点击完成后,从桌面快捷方式启动 RT-Thread Studio。桌面快捷方式如下图所示: + +![studio-pic](figures/studio-pic.png) + +第一次启动 RT-Thread Studio 需要进行账户登录,登录一次后会自动记住账号,后续不需要再登录,登录支持第三方账号登陆,登录界面如下: + +![sign-in](figures/sign-in.png) + +### 新建项目 + +在`项目资源管理器`窗口内点击右键,选择`新建`子菜单`项目`,如下图所示: + +![new-pro](figures/new-pro.png) + +在弹出的新建项目向导对话框中选择`RT-Thread项目`类型,然后点击`下一步`如下图所示: + +![rtthread-pro](figures/rtthread-pro.png) + +填写工程名,选择 RT-Thread 源码版本,选择对应的 BSP,然后点击`完成`按钮,如下图所示: + +![done-pro](figures/done-pro.png) + +点击`完成`后,等待工程创建过程如下图所示: + +![waitting](figures/waitting.png) + +工程创建成功后`项目资源管理器`窗口会出现刚创建的工程`test`,如下图所示: + +![test-pro](figures/test-pro.png) + +### 配置项目 + +双击`RT-Thread Settings`文件,打开 RT-Thread 项目配置界面,配置界面默认显示软件包以及组件和服务层的架构配置图界面,如下图所示: + +![setting](figures/setting.png) + +点击架构图配置界面右侧侧栏按钮,即可打开配置树配置界面,如下图所示: + +![tree-set](figures/tree-set.png) + +配置完成后,保存配置退出配置界面后,RT-Thread Studio 会自动将配置应用到项目中,比如会自动下载相关资源文件到项目中并设置好项目配置,确保项目配置后能够构建成功,如下图所示: + +![set-ok](figures/set-ok.png) + +### 构建项目 + +点击工具栏上的`构建`按钮对项目进行构建。如下图所示: + +![build-pro](figures/build-pro.png) + +构建的过程日志在控制台进行打印,如下图所示: + +![build-info](figures/build-info.png) + +### 下载程序 + +当项目构建成功后,点击工具栏`下载程序`按钮旁的三角下拉框选择相应的烧写器,以`J-Link`烧写器为例,如下图所示: + +![download-pro](figures/download-pro.png) + +选择完烧写器后可直接点击`下载程序`按钮进行程序下载,下载日志会在控制台窗口打印,如下图所示: + +![download-info](figures/download-info.png) + +### 启动调试 + +选中`test`工程,然后点击工具栏`调试`按钮,如下图所示: + +![debug-pro](figures/debug-pro.png) + +当成功启动调试后,RT-Thread Studio 会自动跳转到调试透视图,在调试透视图可以进行各种调试功能操作。当停止调试后会自动跳转会 C 透视图,如下图所示: + +![debug-see](figures/debug-see.png) + +### 视频教程 + +访问官网 [RT-Thread Studio 视频教程](https://www.rt-thread.org/page/video.html),在官网观看视频教程。 \ No newline at end of file diff --git a/development-tools/rtthread-studio/um/studio-user-manual.md b/development-tools/rtthread-studio/um/studio-user-manual.md new file mode 100644 index 0000000000000000000000000000000000000000..09dce1f75cad118b75ba57bc44ab7d124d6e0376 --- /dev/null +++ b/development-tools/rtthread-studio/um/studio-user-manual.md @@ -0,0 +1,727 @@ +# RT-Thread Studio 用户手册 + +## 界面介绍 + +### 界面简介 + +RT-Thread Studio 基于 eclipse 平台开发,界面设计和风格继承自 eclipse,RT-Thread Studio 启动后主界面结构如下图所示: + +![studio-frame](figures/studio-frame.png) + +### 透视图简介 + +透视图定义了当前界面呈现的菜单栏,工具栏,以及功能窗口集合及其布局。不同透视图提供了完成特定类型任务的功能集合。例如 C 透视图组合了项目开发,源文件编辑,项目构建等常用的开发功能窗口,菜单和功能按钮,调试透视图包含了调试项目程序常用的调试功能窗口,菜单和功能按钮。 + +RT-Thread Studio 已实现启动调试时自动切换到调试透视图,停止调试时自动恢复到 C 透视图,用户平时也可以根据需要从 ` 透视图切换栏 ` 手动进行透视图切换,切换到其它透视图进行相关工作。 + +### 功能窗口特性 + +#### 可移动 + +RT-Thread Studio 在初次打开的时候功能窗口位置呈现的是默认布局,但所有功能窗口位置都不是固定的,可以在窗口标题处按住鼠标左键,随意拖动窗口的位置,如下图所示左键按住 ` 属性 ` 窗口,拖动到 ` 项目资源管理器 ` 窗口下方,会出现一个灰色方框指示 ` 属性 ` 窗口将要被放置的位置,此时松开鼠标按键即可将 ` 属性 ` 窗口放置在该位置: + +![shuxing](figures/shuxing.png) + +当窗口拖乱了,或者整体布局不满意想恢复回默认布局的样子时,可以通过 ` 复位透视图 ` 菜单功能恢复默认窗口布局,如下图所示: + +![reset-see](figures/reset-see.png) + +#### 可关闭 + +每个功能窗口标题旁边都有一个 `X` 可以通过点击该处,关闭功能窗口,如下图所示: + +![can-close](figures/can-close.png) + +若想再次打开已关闭的功能窗口,可通过菜单栏的 ` 窗口 ` 菜单的子菜单 ` 显示视图 ` 菜单中再次打开对应功能窗口,如果当前菜单中没显示想要打开的功能窗口,可以点击从 ` 其他 ` 菜单中查找。 + +![open-set](figures/open-set.png) + +#### 最大化 + +每个功能窗口都有自己单独的工具栏,工具栏最右边是最小化和最大化功能按钮,如下图所示: + +![max-size](figures/max-size.png) + +在功能窗口的标题上双击或者点击功能窗口栏上最大化按钮,即可将窗口最大化,占满整个功能窗口区域,其它窗口将会暂时最小化到侧栏内,如下图所示: + +![max-pic](figures/max-pic.png) + +再次双击 ` 项目资源管理器 ` 功能窗口即可恢复之前的功能窗口位置和状态。 + +#### 最小化 + +点击功能窗口最小化按钮,功能窗口将会暂时缩小到侧栏位置放置,点击 ` 恢复 ` 按钮即可恢复原来状态,如下图所示: + +![restore](figures/restore.png) + +### 工具栏按钮介绍 + +#### 编译 + +选中一个项目,然后点击 ` 编译 ` 按钮即可完成编译,如下图所示: + +![](figures/compile.png) + +#### 重构建 + +选中一个项目,然后点击 ` 重构建 ` 按钮即可完成重构建,如下图所示: + +![](figures/rebuild_project.png) + +#### 构建配置 + +构建项目之前如果需要对项目进行构建参数配置,点击工具栏上 ` 打开构建配置 ` 按钮对项目进行构建参数配置,如下图所示: + +![build-entry](figures/build-entry.png) + +#### 调试配置 + +进行下载或启动调试之前如果需要对项目进行相关调试参数配置,通过点击工具栏上的 ` 调试配置 ` 按钮,可打开调试配置对话框界面,如下图所示: + +![debugentry](figures/debugentry.png) + +#### 启动调试 + +选中一个项目,然后点击 ` 启动调试 ` 按钮即可进入调试模式,如下图所示: + +![](figures/debuger.png) + +#### 打开元素 + +` 打开元素 ` 按钮其实就是一个搜索功能,可以指定搜索的类型,如下图所示: + +![](figures/open-element.png) + + + +#### 搜索 + +通过搜索菜单或者搜索按钮,选择对应的搜索功能,如下图所示: + +![search-menu](figures/search-menu.png) + +#### 打开终端 + +通过点击工具栏 ` 打开终端 ` 按钮即可打开终端选择界面,如下图所示: + +![terminal](figures/terminal.png) + +#### 打开 RT-Thread RTOS API 文档 + +通过点击工具栏 ` 打开 RT-Thread RTOS API 文档 ` 按钮即可打开 RT-Thread API 参考手册,如下图所示: + +![](figures/RTOS-API.png) + +#### 下载程序 + +` 下载程序 ` 按钮除了可以下载程序以外,还可以通过旁边的三角下拉按钮来切换调试器,如下图所示: + +![switchdebug](figures/switchdebug.png) + +#### SDK Manger + +`SDK Manger` 可以管理源码包、芯片支持包、开发板资源包、工具链资源包、调试工具包、第三方资源包,可以根据需要进行下载相应的资源。 + +![](figures/SDK-Manager.png) + +## 欢迎页 + +RT-Thread Studio 每次启动打开软件主界面后会展示一个最大化的欢迎页窗口,如下图所示: + +![welcome-page](figures/welcome-page.png) + +欢迎页左侧有四个便利的功能入口:` 创建 RT-Thread 项目 `,`RT-Thread 论坛 `,` 视频教程 `,` 帮助文档 `,直接点击相应的功能名称即可使用对应功能。欢迎页右侧展示了三类内容:` 最新动态 `,` 视频教程 `,` 最新 PR`,点击对应的标签即可查看或者浏览对应标签页的内容。 + + +## 新建 + +新建资源功能包括新建各类资源,例如工程,文件,文件夹等,新建入口有菜单,工具栏按钮,和工程右键菜单如下图所示: + +![new-resource](figures/new-resource.png) + + +## 导入 + +RT-Thread Studio 的导入功能不仅支持导入现有的 RT-Thread Studio 工程,还支持用户将 MDK/IAR 格式的工程导入到 RT-Thread Studio 中,便于用户迁移开发环境。 + +### RT-Thread Studio 项目导入 + +在 Studio 资源管理器窗口中点击右键,在下拉菜单中选择导入功能: + +![select_import](figures/select_import.png) + +打开导入功能向导,选择导入 RT-Thread Studio 工程: + +![select_rtt_project](figures/select_rtt_project.png) + +点击 **下一步** 后,点击 **浏览** 按钮选择要导入项目所在的工程目录,导入程序会自动扫描该目录下所有可导入的工程,将结果列出在项目列表中。在工程列表中勾选要导入的工程,然后点击 ` 完成 ` 即可。 + +![importdone](figures/importdone.png) + +### MDK/IAR 项目导入 + +开发者可以将现有的 RT-Thread MDK/IAR 工程直接导入到 RT-Thread Studio 中,然后就可以使用 RT-Thread Studio 提供的更多工程配置功能。 + +`MDK/IAR` 工程在导入到 RT-Thread Studio 后,将有如下特性: + +- 保持原有项目的目录结构 +- 保持保持原有项目的源文件 +- 保持原有项目的头文件路径 +- 保持原有项目的宏定义 +- 将原工程中使用的 libc 库相关配置转换为 newlibc +- 可以使用 RT-Thread Studio 提供的 RT-Thread 系统配置及软件包配置功能 + +注意 :目前仅支持 STM32 系列芯片的工程导入,后续会支持更多芯片。 + +#### 导入示例 + +本小节将以 `bsp/stm32/stm32f429-atk-apollo` 工程为例,演示如何导入一个 MDK 工程到 RT-Thread Studio 中,导入前工程目录如下图所示: + +![apollo_project](figures/apollo_project.png) + +在 Studio 资源管理器窗口中点击右键,在下拉菜单中选择导入功能,然后选择导入 MDK/IAR 项目到工作空间,然后点击下一步, 如下图所示: + +![mdk_iar_import](figures/mdk_iar_import.png) + +点击浏览选择工程目录下要导入的 MDK/IAR 工程,然后输入导入后的工程名,点击完成即可, 如下图所示: + +![import_project_name](figures/import_project_name.png) + +导入成功后,会在原工程目录下创建 RT-Thread Studio 的工程目录文件夹,如下图所示: + +![generate_dirs](figures/generate_dirs.png) + +项目资源管理器此时显示界面, 如下图所示 : + +![windows_express](figures/windows_express.png) + +此时直接点击编译按钮编译成功后,如下图所示: + +![build_project](figures/build_project.png) + +#### 导入工程管理 + +当一个 MDK/IAR 工程被导入到 RT-Thread Studio 之后,原工程的组织结构会保持不变,如下图所示: + +![project_structure](figures/project_structure.png) + +与原 MDK 工程相同,RT-Thread Studio Group 中的源文件也可以存放在工程的各个位置,而不必实际上按照这种组织结构而存放文件。 + +可以注意到导入的 Group 和源文件右下角有一个小方块和箭头指示,表示区别于原生 eclipse 那种所见即所得的文件组织形式。这种右下角带方框或者箭头标识的文件夹或者文件,在 RT-Thread Studio 中分别称为虚拟文件夹和链接文件。 + +##### 添加与删除源文件 + +如果想在导入的工程中添加源文件,此时只需要保证该文件存在于工程目录中,然后手动拖入到相应的 group 中。如果想要从工程中删除某个源文件,则可以右键点击该文件,在下拉菜单中选择删除即可,如下图所示: + +![add_file_1](figures/1587036090606.png) + +即可在工程中看到 README.md 文件: + +![add_file_2](figures/1587034628405.png) + +如果想要创建一个虚拟文件夹 (虚拟文件夹并不是真实存在的文件夹,并没有实际的逻辑结构,其显示的内容可能实际上是分散于若干个真实的文件夹中,虚拟文件夹只是起到了一个归纳和汇总的作用),可以采用如下方式: + +![create_folder_1](figures/1587034767730.png) + +![create_folder_2](figures/1587034943959.png) + +即可在工程中看到新创建的文件夹: + +![create_folder_3](figures/1587034996637.png) + +#### 导入错误说明 + +本小节将介绍在导入过程中可能出现的错误,以及遇到这类问题该如何解决。由于在导入的过程中要根据用户所导入的工程进行芯片检查以及一些文件的替换,有时会遇到芯片不支持或者文件找不到的情况,此时用户可以自行手动替换某些缺少的文件,使得工程可以构建成功。 + +常见的错误提示信息如下: + +- **ERROR STM32MP157AAAx doesn't support import to RT-Thread Studio now** + + 说明 RT-Thread Studio 暂不支持导入当前系列的芯片。 + +- **ERROR get IAR version failed. Please update the IAR installation path in rtconfig.py!** + + 说明找不到 IAR 的安装路径 打开工程目录下 rtconfig.py 文件 , 修改 IAR 的 **EXEC_PATH** 。 + + ```c + elif CROSS_TOOL == 'iar': + PLATFORM = 'iar' + EXEC_PATH = r'C:/Program Files (x86)/IAR Systems/Embedded Workbench 8.0' + ``` + +- **WARNING Can't auto specific link.lds file, Please specific a linker file mannually.** + + 出现这种错误意味着在导入过程中不能自动替换链接脚本文件,需要手动指定链接脚本的位置。 + + ![specific_linker_file](figures/specific_linker_file.png) + +- **WARNING Can't find xxx_startup_stm32fxxx.s, replace startup files failed.** + + 出现这种错误意味着在导入过程中不能找到可自动替换的芯片启动文件,需要手动添加启动文件到工程中。此时直接将可用的 gcc 启动文件拷贝到工程中,然后拖到相应的 group 中即可。 + + +## RT-Thread 配置 + +### 打开 RT-Thread 配置界面 + +通过双击工程根目录下的 `RT-Thread Settings` 文件,可以打开 RT-Thread 配置界面,如下图所示: + +![pro-set](figures/pro-set.png) + +### 软件包中心 + +通过点击 ` 立即查看 ` 进入软件包中心,软件包中心首先展示了软件包的大分类,在软件包中心,可以先选择一个分类,也可以直接搜索软件包,点击搜索到的软件包进入软件包详情页面后,可以通过点击 ` 添加软件包到工程 ` 按钮将软件包添加到工程,如下图所示: + +![add-packages](figures/add-packages.png) + +当软件包成功添加到工程后,软件包中心会提示 ` 软件包添加成功 `,同时添加的软件包会显示在软件包层,该软件包依赖的组件也会被自动启用,例如添加 `pahomqtt` 软件包,`DFS`,`SAL`,`POSIX`,`libc` 组件会自动被启用,如下图所示: + +![pahomqtt](figures/pahomqtt.png) + +### 组件和服务层 + +在图标上双击可直接启用该组件。启用的组件是亮色图标,未启用组件为灰色图标。通过在组件和服务层的图标上右键弹出可操作的右键菜单,如果该组件已经启用,则该组件的右键菜单有 ` 停用 `、` 查看依赖 `、` 详情配置 ` 三个选项,如果该组件未启用,则该组件右键菜单只有 ` 启用 ` 选项,如下图所示: + +![component](figures/component.png) + +### 查看依赖 + +在启用的组件上右键选择`查看依赖`,可以查看该组件被哪些组件依赖,例如查看`pin`组件的依赖,`依赖关系图`窗口显示`pin`组件依赖了`gpio`,如下图所示: + +![see-depend](figures/see-depend.png) + +### 查看详细配置 + +在启用的组件上右键选择 ` 详细配置 `,可以打开该组件的详细配置树形界面,例如在 `DFS` 上右键选择 ` 详细配置 `,打开的属性配置界面如下图所示: + +![detail-set](figures/detail-set.png) + +#### 详细配置 + +当打开 RT-Thread 配置界面的时候,详细配置默认是隐藏的,通过启用的组件的右键菜单 ` 详细配置 ` 或者 RT-Thread 配置界面侧栏按钮,可以将详细配置界面调出来,侧栏按钮位置如下图所示: + +![open-detail](figures/open-detail.png) + +详细配置界面即右侧的树形配置界面,树形配置界面分成了四大类配置: `内核`, `组件`, `软件包`, `硬件` 。通过标签可以切换不同的配置类别,点击侧栏按钮可以隐藏该属性配置界面,如下图所示: + +![look-detail](figures/look-detail.png) + +#### 搜索配置 + +当需要搜索某个配置的时候,需要在详细配置里选中任意配置树节点右键,会弹出 ` 搜索 ` 菜单,或者在详细配置里选中任意配置树节点后,按下快捷键 `Ctrl + F` 即可弹出配置搜索对话框,输入搜索关键词点击搜索即可搜索出所有匹配关键词的配置,在结果列表里选择不同结果查看时,配置树会自动跳转到对应配置位置,如下图所示: + +![search-set](figures/search-set.png) + +#### 保存配置 + +当配置修改后,`RT-Thread Configuration` 标签会有脏标记,配置完后要记得点击 ` 保存 ` 按钮,将配置保存并应用到工程中。保存的时候会弹出进度提示框,提示保存进度,如下图所示: + +![save-set](figures/save-set.png) + +## 代码编辑 + +### 编码修改 + +- **设置当前文件的编码格式** + + 在当前文件中,按`Alt+Enter`,会出现下图所示界面。可以看到设置编码格式的选项(如图中红色矩形所示)。下拉列表中可以选择想要的编码格式。 + +![](figures/Encoding_01.png) + +- **设置当前项目工程(Project)的编码格式** + + 选中你所创建的项目,右键点击会弹出以下界面,选择最下面的一个选项`属性`(图中红色矩形样式),点击进入。 + +![](figures/Encoding_02.png) + +点击`属性`之后,弹出以下页面,默认的是`从容器继承`,我们选择`其他`, 下拉框选择编码,然后点击`Apply and Close`即可 + +![](figures/Encoding_03.png) + +- **设置工作区间的编码格式** + + 通过`窗口`菜单打开`首选项`窗口弹出如下图界面: + +![](figures/Encoding_04.png) + +​ 点击`常规`选项后再点击`工作空间`会出现设置编码格式的选项。默认的是`缺省值(GBK)`,这里我们选择`其他`,下拉框选择编码,然后点击`Apply and Close`即可 + +![](figures/Encoding_05.png) + +### 编辑 + +通过 ` 编辑 ` 菜单或者直接在源码编辑器内右键菜单,可以选择对应的编辑系列功能,如下图所示: + +![code-edit](figures/code-edit.png) + +### 源码 + +通过 ` 源码 ` 菜单或者直接在源码编辑器内右键菜单,可以选择对应的源码系列功能,如下图所示: + +![source](figures/source.png) + +### 重构 + +通过 ` 重构 ` 菜单或者直接在源 码编辑器内右键菜单,可以选择对应的重构系列功能,如下图所示: + +![rebuild](figures/rebuild.png) + +### 导航 + +通过 ` 导航 ` 菜单或者直接在源码编辑器内右键菜单,可以选择对应的导航系列功能,如下图所示: + +![start-menu](figures/start-menu.png) + +### 搜索 + +通过搜索菜单或者搜索按钮,选择对应的搜索功能,如下图所示: + +![search-menu](figures/search-menu.png) + +### 辅助键 + +通过 ` 帮助 ` 菜单的子菜单 ` 辅助键 ` 查看所有的快捷键,如下图所示: + +![help-bottom](figures/help-bottom.png) + +## 构建配置 + +### 构建配置入口 + +构建项目之前如果需要对项目进行构建参数配置,点击工具栏上 ` 打开构建配置 ` 按钮对项目进行构建参数配置,如下图所示: + +![build-entry](figures/build-entry.png) + +### 配置头文件包含 + +若要增删改头文件路径,在 ` 工具设置 ` 配置页,点击 `GNU ARM Cross C Compiler` 下的 `Includes` 配置项即可打开头文件路径配置参数,点击 `Inlucde paths(-I)` 配置栏相应的按钮即可进行头文件的增删改操作,如下图所示: + +![path-include](figures/path-include.png) + +### 配置宏定义 + +若要增删改宏定义,在 ` 工具设置 ` 配置页,点击 `GNU ARM Cross C Compiler` 下的 `Preprocessor` 配置项即可打开宏定义配置参数,点击 `Define symbols(-D)` 配置栏相应的按钮即可进行宏定义的增删改操作,如下图所示: + +![setmicro](figures/setmicro.png) + +### 配置链接脚本 + +若要增删改链接脚本配置,在 ` 工具设置 ` 配置页,点击 `Cross ARM C Linker` 下的 `General` 配置项即可设置链接脚本文件,点击 `Script files(-T)` 配置栏相应的按钮即可进行链接脚本的增删改操作,在 `Script files(-T)` 下方有一些基本的链接参数可配置,如下图所示: + +![setlinkscripts](figures/setlinkscripts.png) + +### 配置外部二进制库文件 + +若要增删改外部二进制库文件,在 ` 工具设置 ` 配置页,点击 `Cross ARM C Linker` 下的 `Libraries` 配置项即可设置外部二进制库文件,点击 `Libraries(-l)` 配置栏相应的按钮即可进行库文件的增删改操作,在 `Library search path(-L)` 配置栏配置库文件相应的路径。 + +如下图所示: + +- 项目本地新增 GCC 二进制库文件,命名为:libxxx.a(如图中示例 libwifi_1.0.0_gcc.a)。 +- 在 `Libraries(-l)` 配置栏增加二进制库文件名称:xxx(如图中示例 wifi_1.0.0_gcc),注意需要去掉前缀 `lib` 与后缀 `.a` 。 +- 在 `Library search path(-L)` 配置栏,添加该库文件所在的路径。 + +![add binary library](figures/add-binary-library-re.png) + +![add binary library cfg](figures/add-binary-library.png) + +### 配置其它 + +配置其它构建参数可直接在 ` 工具设置 ` 配置页面中选择相应类型配置树节点,并设置其提供的详细配置项,配置完成后,点击 ` 应用并关闭 ` 按钮配置即可生效。如下图所示: + +![setother](figures/setother.png) + +## 调试配置 + +### 调试配置入口 + +进行下载或启动调试之前如果需要对项目进行相关调试参数配置,通过点击工具栏上的 ` 调试配置 ` 按钮,可打开调试配置对话框界面,如下图所示: + +![debugentry](figures/debugentry.png) + +### 调试配置项 + +选中一个调试配置后,调试配置对话框将展示所有配置项,配置项通过 ` 配置项分类标签页 ` 进行了分类,通过点击不同 ` 标签页 ` 展示不同类别配置项,修改配置项后,点击 ` 确定 ` 按钮即可保存配置修改,如下图所示: + +![debugset](figures/debugset.png) + +## 下载功能 + +### 切换调试器 + +目前 RT-Thread Studio 支持 JLink 、ST-Link、DAP-Link 以及软件仿真器 QEMU,新建工程的时候可以在新建工程向导里选择硬件调试器也可以选择软件仿真器 QEMU。工程创建好之后,如果想切换硬件调试器或直接进行软件仿真,可以通过工具栏下载程序按钮旁边的三角下拉按钮来切换硬件调试器或软件仿真器 QEMU,如下图所示: + +![switchdebug](figures/switchdebug.png) + + +## 调试 + +### 调试常用操作 + +当调试启动成功后,程序会在 main 方法处挂起,这时可以通过工具栏上的调试相关操作按钮或者快捷键进行常用的调试操作,如下图所示: + +![debugopt](figures/debugopt.png) + +### 启用汇编单步调试模式 + +点击工具栏上的 ` 汇编单步模式 ` 按钮,会自动打开 ` 反汇编 ` 功能窗口,此时 ` 汇编单步模式 ` 按钮呈凹下去的状态,代表此时处于汇编单步模式,如下图所示: + +![asmstep](figures/asmstep.png) + +当进入 ` 汇编单步模式 ` 后,所有单步调试操作将变为以一条汇编指令为单位进行单步执行,此时指令跳转情况可以在 ` 反汇编 ` 窗口进行查看。 + +若要退出 ` 汇编单步模式 `,直接再次点击 ` 汇编单步模式 ` 按钮即可。 + +### 查看寄存器 + +通过 ` 窗口 ` 菜单的 ` 显示视图 ` 子菜单,选择打开 ` 寄存器 ` 窗口即可查看核心寄存器,如下图所示: + +![seereg](figures/seereg.png) + +### 查看外设寄存器 + +点击 `Peripherals` 窗口,让 `Peripherals` 窗口显示在最前面,若 RT-Thread Studio 存在相应的 svd 文件,该窗口将会显示所有外设名称及其地址和描述。可在 `Peripherals` 窗口勾选要查看的外设,内存窗口将会显示该外设的所有寄存器的名称及其地址和当前值,如下图所示: + +![seeperial](figures/seeperial.png) + +若需要修改某个寄存器的当前值,可以直接点击进入寄存器 `Value` 那一列,输入想要修改的值后,敲击回车键即可执行修改。(注:只有可读可写的寄存器可以修改值,只读寄存器无法修改值。) + +![editreg](figures/editreg.png) + +### 查看变量 + +点击 ` 变量 ` 窗口,让 ` 变量 ` 窗口显示在最前面,即可查看当前程序挂起时所有可见的变量,点击 ` 变量 ` 窗口最右边的三角下拉菜单,可以设置变量显示的数值格式,如下图所示: + +![seevalue](figures/seevalue.png) + +### 查看内存 + +点击 ` 内存 ` 窗口,让 ` 内存 ` 窗口显示在最前面。点击 ` 添加内存监视器 ` 按钮,在弹出的输入框内,输入要查看内存的起始地址,点击 ` 确定 ` 即可添加要查看的内存,如下图所示: + +![seememreg](figures/seememreg.png) + +添加内存监视器后,内存窗口会立即展示刚输入的内存起始地址的一段内存,如下图所示: + +![seemem](figures/seemem.png) + +### 断点 + +在源码编辑窗口边栏,双击即可设置断点,再次双击即可删除断点,打开 ` 断点 ` 窗口即可查看和管理所有断点,通过 ` 断点 ` 窗口工具栏可以进行删除,取消等断点管理操作,如下图所示: + +![breakpoint](figures/breakpoint.png) + +### 表达式 + +在源码内选中表达式后点击右键,选择 ` 添加监看表达式 ` 即可将表达式添加到 ` 表达式 ` 窗口,或者直接点击 ` 表达式 ` 窗口内的 ` 添加新的表达式 ` 通过直接输入的方式,添加想要查看的表达式的值。 + +![addformat](figures/addformat.png) + +## 模拟器仿真 + +### QEMU 模拟器仿真 + +QEMU 是一个支持跨平台虚拟化的虚拟机,它可以虚拟很多开发板。为了方便在没有开发板的情况下体验 RT-Thread,RT-Thread Studio 提供了 QEMU 模拟仿真调试器。 本文主要介绍在 Windows 平台上使用 RT-Thread Studio QEMU 模拟器进行仿真。 + +#### 创建工程 + +点击新建一个工程,选择或者设置各个配置选项,Adapter 配置项可以先选择 QEMU,并且配置适合的模拟器。目前的最新版本中,系列、子系列需要手动选择,还未与模拟器支持的系列关联起来。点击完成,即会在工作空间中创建一个 QEMU 调式的工程。 + +![qemu](figures/qemu_new_project.png) + +#### 切换调试器到 QEMU + +如果当前工程为老工程或者当前选择的调试器是 QEMU 以外的调试器,想使用 QEMU 进行调试,可点击下载按钮右边的下拉框,选择 QEMU : + +![qemu](figures/switch_to_qemu.png) + +若当前工程还未配置 QEMU ,那么在选择 QEMU 后,会弹出【跳转到配置界面】的提示,点击【是】,会显示 QEMU 配置界面,具体详情将在下一节介绍。 + +![qemu](figures/switch_qemu_prompt.png) + +#### QEMU 配置 + +点击【打开调试配置】或者其他情况下跳转到 QEMU 配置界面,需要配置的参数如下: + +| 参数 | 英文 | 命令 | +| :-----------------------: | :------------------------: | :-------: | +| 开发板型号 | Board Name | -M | +| 支持 Cpu 的数量 | Cpu Quantity | -smp | +| 是否支持网络 | Enable Network | -net | +| 是否支持图形 | Don't open grapgic windows | -nographic | +| SD 卡内存大小 | SD Card Memory | -sd | +| 额外的命令 | Extra Commands | 无 | + +填写完整后,点击确定,即可正常进行调试和运行。 + +![qemu](figures/qemu_configure.png) + + + +#### 仿真调试 + +在编译正常的情况下,点击调式按钮,IDE 会自动启动 QEMU 并打开串口,并进入断点调试模式,在此模式中,可一步一步观察每次断点的输出情况,亦可在串口中执行自己的命令。 + +- ** 注:在 windows 上 QEMU 不支持上下键查看历史命令。** + +![qemu](figures/qemu_debug.png) + +#### 网络仿真 + +- 基于开发板创建一个 stm32f4 的工程 + +![](figures/qemu_network_new_stm32f4.png) + +- 打开 RT-Thread Settings ,使能 SAL, 切换到 【硬件】栏,使能以太网,Ctrl + S 保存配置,编译 + + ![](figures/qemu_network_rt-thread_settings.png) + +![](figures/qemu_network_rtthread_setting2.png) + +- 配置 QEMU,选择模拟器,配置网卡(配置TAP 过程可参考 https://www.rt-thread.org/document/site/tutorial/qemu-network/qemu_setup/qemu_setup/) + + ![](figures/qemu_network_configure_stm32f4.png) + +- 点击下载按钮,程序会自动启动并下载,进入到终端页面,显示 lwip initialized. + + ![image-20210112094953008](figures/qemu_network_debug_stm32f4.png) + +- 输入网络命令测试一下 + +![](figures/qemu_network_test_stm32f4.png) + +## PlatformIO + +RT-Thread Studio 自 V2.0.0 版本开始支持 PlatformIO 工程的创建、编译和调试。下面,我们一步步的来实践这个过程 + +- 在 SDK Manager 下载 PlatformIO 资源包,此下载安装过程可能需要花费一定时间,请耐心等待 + + ![](figures/platformio_download.png) + +- 新建 RT-Thread 工程,选择基于 PlatformIO,选择您需要的开发板、框架,点击 **Finish**,自动创建了一个 PlatformIO 的工程 + + ![](figures/platformio_new_project.png) + +- 打开 src/main.cpp, 添加您自己的代码,然后点击编译按钮编译工程 + + ![](figures/platformio_build_project.png) + +- 编译成功后,点击下载按钮,下载程序到开发板 + + ![](figures/platformio_download_project.png) + +- 下载成功后,打开串口查看输出内容 + + ![](figures/platformio_open_serial.png) + +- 调试。点击调试按钮,自动进入调试界面,您可以查看寄存器信息,进行单步调试操作 + + ![](figures/platformio_debug_view.png) + + ![](figures/platformio_debug_project.png) + +## 终端 + +通过点击工具栏 ` 终端 ` 按钮即可打开终端选择界面如下图所示: + +![terminal](figures/terminal.png) + +点击 ` 确定 ` 按钮后,即会自动打开对应的终端功能窗口,如下图所示: + +![seeterminal](figures/seeterminal.png) + + +## SDK Manager + +### SDK Manager 简介 + +通过 SDK Manager 维护 RT-Thread Studio 内部的 RT-Thread SDK 资源包,包括安装,卸载,升级各类资源包。通过工具栏的 `SDK Manager` 按钮即可打开 SDK Manager 功能界面,如下图所示: + +![sdkmanager](figures/SDK-Manager.png) + +### SDK Manager 功能 + +#### 安装资源包 + +勾选状态为 `Not installed` 的资源包,点击 `Install packages` 按钮即可启动资源包安装过程,如下图所示: + +![installsdk](figures/installsdk.png) + +#### 卸载资源包 + +勾选状态为 `Installed` 的资源包,点击 `Delete packages` 按钮即可启动卸载过程,如下图所示: + +![deletesdk](figures/deletesdk.png) + +#### 升级资源包 + +点击资源包对应的 ` 升级 ` 按钮,可以将资源包同步更新到最新状态。如下图所示: + +![updatesdk](figures/updatesdk.png) + +## MarkDown 编辑 + +RT-Thread Studio 自带 MarkDown 编辑器,若工程里有 md 文件,只需要双击即可打开 md 文件进行编辑,如下图所示: + +![editmd](figures/editmd.png) + + +## 常用快捷键 + +**代码阅读** + +- Ctrl+H 全局 打开搜索对话框 +- Ctrl+Shift+T 全局 打开类型 +- Ctrl+Shift+R 全局 打开资源 +- Ctrl+Shift+"+" 放大字体 +- Ctrl+"-" 缩小字体 + +**代码编辑** + +- Ctrl+D 删除当前行 + +- Ctrl+/ 注释当前行, 再按则取消注释 + +- Ctrl+Shift+F 格式化 + +- Alt+→ /← 全局 前进 / 后退历史记录 + +- Ctrl+Q 定位到最后编辑的地方 + +- Ctrl+K 参照选中的 Word 快速定位到下一个 + +- Ctrl+L 定位在某行 + +- Alt+→ /← 前一个 / 下一个编辑的页面 + +- Shift+Enter 在当前行的下一行插入空行 + + +## 首选项 + +### 首选项介绍 + +RT-Thread Studio 基于 eclipse 平台开发,eclipse 是一个高度可定制的平台,基于 eclipse 实现的功能都会提供大量的配置项,来定制功能的行为方式,来满足用户自身的使用习惯,这些配置项 eclipse 称为首选项。 + +通过点击 ` 窗口 ` 菜单的字菜单 ` 首选项 ` 即可进入首选项配置界面,如下图所示: + +![firstopt](figures/firstopt.png) + +由于首选项数量大,种类多,首选项对话框左侧以树形的形式展示所有的首选项,点击对应的首选项类别即可展开对应的首选项配置树,若要查找某个首选项,可直接在 ` 输入过滤文本 ` 框中,输入关键字,进行查找。 + +首选项右侧即为具体的可配置项页面,修改 后直接点击 ` 应用 ` 即可保存配置,若想恢复当前配置页默认值,点击 ` 恢复默认值 ` 按钮即可。 + +首选项配置还可以通过左下角 ` 导入 `,` 导出 ` 功能,将配置进行保存或者在不同用户间传递或者共享配置,导出功能将把所有配置导出到 `.epf` 文件中,其它用户直接导入这个 `.epf` 文件,即可使用该文件记录的所有配置。 + +![optmenu](figures/optmenu.png) + +### 设置主题 + +通过 ` 首选项 ` 的 ` 外观 ` 配置项选择 “DevStyle Theme” 即可启用新的黑色主题,切换主题后需要重启 Studio 后才会生效 + +![devstyle1](figures/devstyle1.png) + +通过 `DevStyle Theme` 的 `Color Themes` 配置项选择 “Editor theme” 可以切换其他黑色主题,切换主题后需要重启 Studio 后才会生效 + +![devstyle2](figures/devstyle2.png) + + + diff --git a/development-tools/scons/figures/1149b400a917cdd7906affecf08503f6.png b/development-tools/scons/figures/1149b400a917cdd7906affecf08503f6.png new file mode 100644 index 0000000000000000000000000000000000000000..e1238809314a1fa21edd5c75c0837ccf04625b1b Binary files /dev/null and b/development-tools/scons/figures/1149b400a917cdd7906affecf08503f6.png differ diff --git a/development-tools/scons/figures/b1e0b39d090bfc4640958ffdb11d9741.png b/development-tools/scons/figures/b1e0b39d090bfc4640958ffdb11d9741.png new file mode 100644 index 0000000000000000000000000000000000000000..1d533c22ff41fffadaf4f8cf54998aa0cd47100d Binary files /dev/null and b/development-tools/scons/figures/b1e0b39d090bfc4640958ffdb11d9741.png differ diff --git a/development-tools/scons/figures/caef0674c4a148411789c42f8f8b70b7.png b/development-tools/scons/figures/caef0674c4a148411789c42f8f8b70b7.png new file mode 100644 index 0000000000000000000000000000000000000000..3a5e50fb07b24bd374a0aa5ac1d2a9c390bb8df5 Binary files /dev/null and b/development-tools/scons/figures/caef0674c4a148411789c42f8f8b70b7.png differ diff --git a/development-tools/scons/figures/cb02d48b93098e73681818fd62f22f8b.png b/development-tools/scons/figures/cb02d48b93098e73681818fd62f22f8b.png new file mode 100644 index 0000000000000000000000000000000000000000..f3a0ba2726b7c9adb8df3434b96d49009ac6efc2 Binary files /dev/null and b/development-tools/scons/figures/cb02d48b93098e73681818fd62f22f8b.png differ diff --git a/development-tools/scons/figures/f42ee8cdfd03c1782679c759f70f072f.png b/development-tools/scons/figures/f42ee8cdfd03c1782679c759f70f072f.png new file mode 100644 index 0000000000000000000000000000000000000000..0842ada7755f50ec11bb0b9aee5455a71405124d Binary files /dev/null and b/development-tools/scons/figures/f42ee8cdfd03c1782679c759f70f072f.png differ diff --git a/development-tools/scons/figures/f96388acd1264b4ffcd391c3fd399a50.png b/development-tools/scons/figures/f96388acd1264b4ffcd391c3fd399a50.png new file mode 100644 index 0000000000000000000000000000000000000000..40f592af35bc80eca619aec7c2f714319b813dff Binary files /dev/null and b/development-tools/scons/figures/f96388acd1264b4ffcd391c3fd399a50.png differ diff --git a/development-tools/scons/scons.md b/development-tools/scons/scons.md new file mode 100644 index 0000000000000000000000000000000000000000..78ac27d8a2b7a548a0377e1ba2a31843e30208f0 --- /dev/null +++ b/development-tools/scons/scons.md @@ -0,0 +1,610 @@ +# SCons 构建工具 + +## SCons 简介 + +SCons 是一套由 Python 语言编写的开源构建系统,类似于 GNU Make。它采用不同于通常 Makefile 文件的方式,而是使用 SConstruct 和 SConscript 文件来替代。这些文件也是 Python 脚本,能够使用标准的 Python 语法来编写。所以在 SConstruct、SConscript 文件中可以调用 Python 标准库进行各类复杂的处理,而不局限于 Makefile 设定的规则。 + +在 [SCons](http://www.scons.org/doc/production/HTML/scons-user/index.html) 的网站上可以找到详细的 SCons 用户手册,本章节讲述 SCons 的基本用法,以及如何在 RT-Thread 中用好 SCons 工具。 + +### 什么是构建工具 + +构建工具 (software construction tool) 是一种软件,它可以根据一定的规则或指令,将源代码编译成可执行的二进制程序。这是构建工具最基本也是最重要的功能。实际上构建工具的功能不止于此,通常这些规则有一定的语法,并组织成文件。这些文件用来控制构建工具的行为,在完成软件构建之外,也可以做其他事情。 + +目前最流行的构建工具是 GNU Make。很多知名开源软件,如 Linux 内核就采用 Make 构建。Make 通过读取 Makefile 文件来检测文件的组织结构和依赖关系,并完成 Makefile 中所指定的命令。 + +由于历史原因,Makefile 的语法比较混乱,不利于初学者学习。此外在 Windows 平台上使用 Make 也不方便,需要安装 Cygwin 环境。为了克服 Make 的种种缺点,人们开发了其他构建工具,如 CMake 和 SCons 等。 + +### RT-Thread 构建工具 + +RT-Thread 早期使用 Make/Makefile 构建。从 0.3.x 开始,RT-Thread 开发团队逐渐引入了 SCons 构建系统,引入 SCons 唯一的目是:使大家从复杂的 Makefile 配置、IDE 配置中脱离出来,把精力集中在 RT-Thread 功能开发上。 + +有些读者可能会有些疑惑,这里介绍的构建工具与 IDE 有什么不同呢?IDE 通过图形化界面的操作来完成构建。大部分 IDE 会根据用户所添加的源码生成类似 Makefile 或 SConscript 的脚本文件,在底层调用类似 Make 或 SCons 的工具来构建源码。 + +### 安装 SCons + +在使用 SCons 系统前需要在 PC 主机中安装它,因为它是 Python 语言编写的,所以在使用 SCons 之前需要安装 Python 运行环境。 + +RT-Thread 提供的 Env 配置工具带有 SCons 和 Python,因此在 windows 平台使用 SCons 则不需要安装这两个软件。 + +在 Linux、BSD 环境中 Python 应该已经默认安装了,一般也是 2.x 版本系列的 Python 环境。这时只需要安装 SCons 即可,例如在 Ubuntu 中可以使用如下命令安装 SCons: + +`sudo apt-get install scons` + +## SCons 基本功能 + +RT-Thread 构建系统支持多种编译器。目前支持的编译器包括 ARM GCC、MDK、IAR、VisualStudio、Visual DSP。主流的 ARM Cortex M0、M3、M4 平台,基本上 ARM GCC、MDK、IAR 都是支持的。有一些 BSP 可能仅支持一种,读者可以阅读该 BSP 目录下的 rtconfig.py 里的 CROSS_TOOL 选项查看当前支持的编译器。 + +如果是 ARM 平台的芯片,则可以使用 Env 工具,输入 scons 命令直接编译 BSP,这时候默认使用的是 ARM GCC 编译器,因为 Env 工具带有 ARM GCC 编译器。 如下图所示使用 `scons` 命令编译 stm32f10x-HAL BSP,后文讲解 SCons 也将基于这个 BSP。 + +![使用 scons 命令编译 stm32f10x-HAL BSP](figures/b1e0b39d090bfc4640958ffdb11d9741.png) + +如果用户要使用其他的 BSP 已经支持的编译器编译工程,或者 BSP 为非 ARM 平台的芯片,那么不能直接使用 scons 命令编译工程,需要自己安装对应的编译器,并且指定使用的编译器路径。在编译工程前,可以在 Env 命令行界面使用下面的 2 个命令指定编译器为 MDK 和编译器路径为 MDK 的安装路径。 + +```c +set RTT_CC=keil +set RTT_EXEC_PATH=C:/Keilv5 +``` + +### SCons 基本命令 + +本节介绍 RT-Thread 中常用的 SCons 命令。SCons 不仅完成基本的编译,还可以生成 MDK/IAR/VS 工程。 + +#### scons + +在 Env 命令行窗口进入要编译的 BSP 工程目录,然后使用此命令可以直接编译工程。如果执行过 `scons` 命令后修改了一些源文件,再次执行 scons 命令时,则 SCons 会进行增量编译,仅编译修改过的源文件并链接。 + +如果在 Windows 上执行 `scons` 输出以下的警告信息: + +```c +scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly. +``` + +说明 scons 并没在你的机器上找到 Visual Studio 编译器,但实际上我们主要是针对设备开发,和 Windows 本地没什么关系,请直接忽略掉它。 + +`scons` 命令后面还可以增加一个 - s 参数,即命令 `scons -s`,和 scons 命令不同的是此命令不会打印具体的内部命令。 + +#### scons -c + +清除编译目标。这个命令会清除执行 scons 时生成的临时文件和目标文件。 + +#### scons --target=XXX + +如果使用 mdk/iar 来进行项目开发,当修改了 rtconfig.h 打开或者关闭某些组件时,需要使用以下命令中的其中一种重新生成对应的定制化的工程,然后在 mdk/iar 进行编译下载。 + +```c +scons --target=iar +scons --target=mdk4 +scons --target=mdk5 +``` + +在命令行窗口进入要编译的 BSP 工程目录,使用 `scons --target=mdk5` 命令后会在 BSP 目录生成一个新的 MDK 工程文件名为 project.uvprojx。双击它打开,就可以使用 MDK 来编译、调试。使用 `scons --target=iar` 命令后则会生成一个新的 IAR 工程文件名为 project.eww。不习惯 SCons 的用户可以使用这种方式。如果打开 project.uvproj 失败,请删除 project.uvopt 后,重新生成工程。 + +在 bsp/simulator 下,可以使用下面的命令生成 vs2012 的工程或 vs2005 的工程。 + +```c +scons --target=vs2012 +Scons --target=vs2005 +``` + +如果 BSP 目录下提供其他 IDE 工程的模板文件也可以使用此命令生成对应的新工程,比如 ua、vs、cb、cdk。 + +这个命令后面同样可以增加一个 -s 参数,如命令 `scons –target=mdk5 -s`,执行此命令时不会打印具体的内部命令。 + +> [!NOTE] +> 注:要生成 MDK 或者 IAR 的工程文件,前提条件是 BSP 目录存在一个工程模版文件,然后 scons 才会根据这份模版文件加入相关的源码,头文件搜索路径,编译参数,链接参数等。而至于这个工程是针对哪颗芯片的,则直接由这份工程模版文件指定。所以大多数情况下,这个模版文件是一份空的工程文件,用于辅助 SCons 生成 project.uvprojx 或者 project.eww。 + +#### scons -jN + +多线程编译目标,在多核计算机上可以使用此命令加快编译速度。一般来说一颗 cpu 核心可以支持 2 个线程。双核机器上使用 `scons -j4` 命令即可。 + +> [!NOTE] +> 注:如果你只是想看看编译错误或警告,最好是不使用 - j 参数,这样错误信息不会因为多个文件并行编译而导致出错信息夹杂在一起。 + +#### scons --dist + +搭建项目框架,使用此命令会在 BSP 目录下生成 dist 目录,这便是开发项目的目录结构,包含了RT-Thread源码及BSP相关工程,不相关的BSP文件夹及libcpu都会被移除,并且可以随意拷贝此工程到任何目录下使用。 + +#### scons --verbose + +默认情况下,使用 scons 命令编译的输出不会显示编译参数,如下所示: + +```c +D:\repository\rt-thread\bsp\stm32f10x>scons +scons: Reading SConscript files ... +scons: done reading SConscript files. +scons: Building targets ... +scons: building associated VariantDir targets: build +CC build\applications\application.o +CC build\applications\startup.o +CC build\components\drivers\serial\serial.o +... +``` + +使用 scons –verbose 命令的效果如下: + +```c +armcc -o build\src\mempool.o -c --device DARMSTM --apcs=interwork -ID:/Keil/ARM/ +RV31/INC -g -O0 -DUSE_STDPERIPH_DRIVER -DSTM32F10X_HD -Iapplications -IF:\Projec +t\git\rt-thread\applications -I. -IF:\Project\git\rt-thread -Idrivers -IF:\Proje +ct\git\rt-thread\drivers -ILibraries\STM32F10x_StdPeriph_Driver\inc -IF:\Project +\git\rt-thread\Libraries\STM32F10x_StdPeriph_Driver\inc -ILibraries\STM32_USB-FS +-Device_Driver\inc -IF:\Project\git\rt-thread\Libraries\STM32_USB-FS-Device_Driv +er\inc -ILibraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x -IF:\Project\git\rt-thre +... +``` + +## SCons 进阶 + +SCons 使用 SConscript 和 SConstruct 文件来组织源码结构,通常来说一个项目只有一 SConstruct,但是会有多个 SConscript。一般情况下,每个存放有源代码的子目录下都会放置一个 SConscript。 + +为了使 RT-Thread 更好的支持多种编译器,以及方便的调整编译参数,RT-Thread 为每个 BSP 单独创建了一个名为 rtconfig.py 的文件。因此每一个 RT-Thread BSP 目录下都会存在下面三个文件:rtconfig.py、SConstruct 和 SConscript,它们控制 BSP 的编译。一个 BSP 中只有一个 SConstruct 文件,但是却会有多个 SConscript 文件,可以说 SConscript 文件是组织源码的主力军。 + +RT-Thread 大部分源码文件夹下也存在 SConscript 文件,这些文件会被 BSP 目录下的 SConscript 文件 “找到” 从而将 rtconfig.h 中定义的宏对应的源代码加入到编译器中来。后文将以 stm32f10x-HAL BSP 为例,讲解 SCons 是如何构建工程。 + +### SCons 内置函数 + +如果想要将自己的一些源代码加入到 SCons 编译环境中,一般可以创建或修改已有 SConscript 文件。SConscript 文件可以控制源码文件的加入,并且可以指定文件的 Group(与 MDK/IAR 等 IDE 中的 Group 的概念类似)。 + +SCons 提供了很多内置函数可以帮助我们快速添加源码程序,利用这些函数,再配合一些简单的 Python 语句我们就能随心所欲向项目中添加或者删除源码。下面将简单介绍一些常用函数。 + +#### GetCurrentDir() + +获取当前路径。 + +#### Glob('\*.c') + +获取当前目录下的所有 C 文件。修改参数的值为其他后缀就可以匹配当前目录下的所有某类型的文件。 + +#### GetDepend(macro) + +该函数定义在 tools 目录下的脚本文件中,它会从 rtconfig.h 文件读取配置信息,其参数为 rtconfig.h 中的宏名。如果 rtconfig.h 打开了某个宏,则这个方法(函数)返回真,否则返回假。 + +#### Split(str) + +将字符串 str 分割成一个列表 list。 + +#### DefineGroup(name, src, depend,**parameters) + +这是 RT-Thread 基于 SCons 扩展的一个方法(函数)。DefineGroup 用于定义一个组件。组件可以是一个目录(下的文件或子目录),也是后续一些 IDE 工程文件中的一个 Group 或文件夹。 + +`DefineGroup() ` 函数的参数描述: + +|**参数**|**描述** | +|-------|------------------------------------| +| name | Group 的名字 | +| src | Group 中包含的文件,一般指的是 C/C++ 源文件。方便起见,也能够通过 Glob 函数采用通配符的方式列出 SConscript 文件所在目录中匹配的文件 | +| depend | Group 编译时所依赖的选项(例如 FinSH 组件依赖于 RT_USING_FINSH 宏定义)。编译选项一般指 rtconfig.h 中定义的 RT_USING_xxx 宏。当在 rtconfig.h 配置文件中定义了相应宏时,那么这个 Group 才会被加入到编译环境中进行编译。如果依赖的宏并没在 rtconfig.h 中被定义,那么这个 Group 将不会被加入编译。相类似的,在使用 scons 生成为 IDE 工程文件时,如果依赖的宏未被定义,相应的 Group 也不会在工程文件中出现 | +| parameters | 配置其他参数,可取值见下表,实际使用时不需要配置所有参数 | + +parameters 可加入的参数: + +|**参数**|**描述** | +|------------|--------------------------------------------------| +| CCFLAGS | C 源文件编译参数 | +| CPPPATH | 头文件路径 | +| CPPDEFINES | 链接时参数 | +| LIBRARY | 包含此参数,则会将组件生成的目标文件打包成库文件 | + +#### SConscript(dirs,variant_dir,duplicate) + +读取新的 SConscript 文件,SConscript() 函数的参数描述如下所示: + +|**参数** |**描述** | +|-------------|---------------------------------------| +| dirs | SConscript 文件路径 | +| variant_dir | 指定生成的目标文件的存放路径 | +| duiplicate | 设定是否拷贝或链接源文件到 variant_dir | + +## SConscript 示例 + +下面我们将以几个 SConscript 为例讲解 scons 构建工具的使用方法。 + +### SConscript 示例 1 + +我们先从 stm32f10x-HAL BSP 目录下的 SConcript 文件开始讲解,这个文件管理 BSP 下面的所有其他 SConscript 文件,内容如下所示。 + +```c +import os +cwd = str(Dir('#')) +objs = [] +list = os.listdir(cwd) +for d in list: + path = os.path.join(cwd, d) + if os.path.isfile(os.path.join(path, 'SConscript')): + objs = objs + SConscript(os.path.join(d, 'SConscript')) +Return('objs') +``` + +* `import os:` 导入 Python 系统编程 os 模块,可以调用 os 模块提供的函数用于处理文件和目录。 + +* `cwd = str(Dir('#')):` 获取工程的顶级目录并赋值给字符串变量 cwd,也就是工程的 SConstruct 所在的目录,在这里它的效果与 `cwd = GetCurrentDir()` 相同。 + +* `objs = []:` 定义了一个空的 list 型变量 objs。 + +* `list = os.listdir(cwd):` 得到当前目录下的所有子目录,并保存到变量 list 中。 + +* 随后是一个 python 的 for 循环,这个 for 循环会遍历一遍 BSP 的所有子目录并运行这些子目录的 SConscript 文件。具体操作是取出一个当前目录的子目录,利用 `os.path.join(cwd,d)` 拼接成一个完整路径,然后判断这个子目录是否存在一个名为 SConscript 的文件,若存在则执行 `objs = objs + SConscript(os.path.join(d,'SConscript'))。` 这一句中使用了 SCons 提供的一个内置函数 `SConscript()`,它可以读入一个新的 SConscript 文件,并将 SConscript 文件中所指明的源码加入到了源码编译列表 objs 中来。 + +通过这个 SConscript 文件,BSP 工程所需要的源代码就被加入了编译列表中。 + +### SConscript 示例 2 + +那么 stm32f10x-HAL BSP 其他的 SConcript 文件又是怎样的呢?我们再看一下 drivers 目录下 SConcript 文件,这个文件将管理 drivers 目录下面的源代码。drivers 目录用于存放根据 RT-Thread 提供的驱动框架实现的底层驱动代码。 + +```c +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() + +# add the general drivers. +src = Split(""" +board.c +stm32f1xx_it.c +""") + +if GetDepend(['RT_USING_PIN']): + src += ['drv_gpio.c'] +if GetDepend(['RT_USING_SERIAL']): + src += ['drv_usart.c'] +if GetDepend(['RT_USING_SPI']): + src += ['drv_spi.c'] +if GetDepend(['RT_USING_USB_DEVICE']): + src += ['drv_usb.c'] +if GetDepend(['RT_USING_SDCARD']): + src += ['drv_sdcard.c'] + +if rtconfig.CROSS_TOOL == 'gcc': + src += ['gcc_startup.s'] + +CPPPATH = [cwd] + +group = DefineGroup('Drivers', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') + +``` + +* `Import('rtconfig'):` 导入 rtconfig 对象,后面用到的 rtconfig.CROSS_TOOL 定义在这个 rtconfig 模块。 + +* `from building import *:` 把 building 模块的所有内容全都导入到当前模块,后面用到的 DefineGroup 定义在这个模块。 + +* `cwd = GetCurrentDir():` 获得当前路径并保存到字符串变量 cwd 中。 + +后面一行使用 `Split()` 函数来将一个文件字符串分割成一个列表,其效果等价于 + +`src = ['board.c','stm32f1xx_it.c']` + +后面使用了 if 判断和 `GetDepend()` 检查 rtconfig.h 中的某个宏是否打开,如果打开,则使用 `src += [src_name]` 来往列表变量 src 中追加源代码文件。 + +* `CPPPATH = [cwd]:` 将当前路径保存到一个列表变量 CPPPATH 中。 + +最后一行使用 DefineGroup 创建一个名为 Drivers 的组,这个组也就对应 MDK 或者 IAR 中的分组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏。 + +`CPPPATH =CPPPATH` 表示将当前路径添加到系统的头文件路径中。左边的 CPPPATH 是 DefineGroup 中内置参数,表示头文件路径。右边的 CPPPATH 是本文件上面一行定义的。这样我们就可以在其他源码中引用 drivers 目录下的头文件了。 + +### SConscript 示例 3 + +我们再看一下 applications 目录下的 SConcript 文件,这个文件将管理 applications 目录下面的源代码,用于存放用户自己的应用代码。 + +```c +from building import * + +cwd = GetCurrentDir() +src = Glob('*.c') +CPPPATH = [cwd, str(Dir('#'))] + +group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') +``` + +`src = Glob('*.c'):` 得到当前目录下所有的 C 文件。 + +`CPPPATH = [cwd, str(Dir('#'))]:` 将当前路径和工程的 SConstruct 所在的路径保存到列表变量 CPPPATH 中。 + +最后一行使用 DefineGroup 创建一个名为 Applications 的组。这个组的源代码文件为 src 指定的文件,depend 为空表示该组不依赖任何 rtconfig.h 的宏,并将 CPPPATH 保存的路径添加到了系统头文件搜索路径中。这样 applications 目录和 stm32f10x-HAL BSP 目录里面的头文件在源代码的其他地方就可以引用了。 + +总结:这个源程序会将当前目录下的所有 c 程序加入到组 Applications 中,因此如果在这个目录下增加或者删除文件,就可以将文件加入工程或者从工程中删除。它适用于批量添加源码文件。 + +### SConscript 示例 4 + +下面是 RT-Thread 源代码 component/finsh/SConscript 文件的内容,这个文件将管理 finsh 目录下面的源代码。 + +```c +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = Split(''' +shell.c +symbol.c +cmd.c +''') + +fsh_src = Split(''' +finsh_compiler.c +finsh_error.c +finsh_heap.c +finsh_init.c +finsh_node.c +finsh_ops.c +finsh_parser.c +finsh_var.c +finsh_vm.c +finsh_token.c +''') + +msh_src = Split(''' +msh.c +msh_cmd.c +msh_file.c +''') + +CPPPATH = [cwd] +if rtconfig.CROSS_TOOL == 'keil': + LINKFLAGS = '--keep *.o(FSymTab)' + + if not GetDepend('FINSH_USING_MSH_ONLY'): + LINKFLAGS = LINKFLAGS + '--keep *.o(VSymTab)' +else: + LINKFLAGS = '' + +if GetDepend('FINSH_USING_MSH'): + src = src + msh_src +if not GetDepend('FINSH_USING_MSH_ONLY'): + src = src + fsh_src + +group = DefineGroup('finsh', src, depend = ['RT_USING_FINSH'], CPPPATH = CPPPATH, LINKFLAGS = LINKFLAGS) + +Return('group') +``` + +我们来看一下文件中第一个 Python 条件判断语句的内容,如果编译工具是 keil,则变量 `LINKFLAGS = '--keep *.o(FSymTab)'` 否则置空。 + +DefinGroup 同样将 finsh 目录下的 src 指定的文件创建为 finsh 组。`depend = ['RT_USING_FINSH']` 表示这个组依赖 rtconfig.h 中的宏 RT_USING_FINSH。当 rtconfig.h 中打开宏 RT_USING_FINSH 时,finsh 组内的源码才会被实际编译,否则 SCons 不会编译。 + +然后将 finsh 目录加入到系统头文件目录中,这样我们就可以在其他源码中引用 finsh 目录下的头文件。 + +`LINKFLAGS = LINKFLAGS` 的含义与 `CPPPATH = CPPPATH` 类似。左边的 LINKFLAGS 表示链接参数,右边的 LINKFLAGS 则是前面 if else 语句所定义的值。也就是给工程指定链接参数。 + +## 使用 SCons 管理工程 + +前面小节对 RT-Thread 源代码的相关 SConscript 做了详细讲解,大家也应该知道了 SConscript 文件的一些常见写法,本小节将指导大家如何使用 SCons 管理自己的工程。 + +### 添加应用代码 + +前文提到过 BSP 下的 Applications 文件夹用于存放用户自己的应用代码,目前只有一个 main.c 文件。如果用户的应用代码不是很多,建议相关源文件都放在这个文件夹下面。在 Applications 文件夹下新增了 2 个简单的文件 hello.c 和 hello.h,内容如下所示。 + +```c +/* file: hello.h */ + +#ifndef _HELLO_H_ +#define _HELLO_H_ + +int hello_world(void); + +#endif /* _HELLO_H_ */ + +/* file: hello.c */ +#include <stdio.h> +#include <finsh.h> +#include <rtthread.h> + +int hello_world(void) +{ + rt_kprintf("Hello, world!\n"); + + return 0; +} + +MSH_CMD_EXPORT(hello_world, Hello world!) +``` + +applications 目录下的 SConcript 文件会把当前目录下的所有源文件都添加到工程中。需要使用 `scons --target=xxx` 命令才会把新增的 2 个文件添加到工程项目中。注意每次新增文件都要重新生成工程。 + +### 添加模块 + +前文提到在自己源代码文件不多的情况下,建议所有源代码文件都放在 applications 文件夹里面。如果用户源代码很多了,并且想创建自己的工程模块,或者需要使用自己获取的其他模块,怎么做会比较合适呢? + +同样以上文提到的 hello.c 和 hello.h 为例,这两个文件将会放到一个单独的文件夹里管理,并且在 MDK 工程文件里有自己的分组,且可以通过 menuconfig 选择是否使用这个模块。在 BSP 下新增 hello 文件夹。 + +![新增 hello 文件夹](figures/f42ee8cdfd03c1782679c759f70f072f.png) + +大家注意到文件夹里多了一个 SConscript 文件,如果想要将自己的一些源代码加入到 SCons 编译环境中,一般可以创建或修改已有的 SConscript 文件。参考上文对 RT-Thread 源代码的一些对 SConscript 文件的分析,这个新增的 hello 模块 SConscript 文件内容如下所示: + +```c +from building import * + +cwd = GetCurrentDir() +include_path = [cwd] +src = [] + +if GetDepend(['RT_USING_HELLO']): + src += ['hello.c'] + +group = DefineGroup('hello', src, depend = [''], CPPPATH = include_path) + +Return('group') +``` + +通过上面几行简单的代码,就创建了一个新组 hello,并且可以通过宏定义控制要加入到组里面的源文件,还将这个组所在的目录添加到了系统头文件路径中。那么自定义宏 RT_USING_HELLO 又是通过怎样的方式定义呢?这里要介绍一个新的文件 Kconfig。Kconfig 用来配置内核,使用 Env 配置系统时使用的 menuconfig 命令生成的配置界面就依赖 Kconfig 文件。menuconfig 命令通过读取工程的各个 Kconfig 文件,生成配置界面供用户配置内核,最后所有配置相关的宏定义都会自动保存到 BSP 目录里的 rtconfig.h 文件中,每一个 BSP 都有一个 rtconfig.h 文件,也就是这个 BSP 的配置信息。 + +在 stm32f10x-HAL BSP 目录下已经有了关于这个 BSP 的 Kconfig 文件,我们可以基于这个文件添加自己需要的配置选项。关于 hello 模块添加了如下配置选项,# 号后面为注释。 + +![hello 模块相关配置选项](figures/caef0674c4a148411789c42f8f8b70b7.png) + +使用 Env 工具进入 stm32f10x-HAL BSP 目录后,使用 menuconfig 命令在主页面最下面就可以看到新增的 hello 模块的配置菜单,进入菜单后如下图所示。 + +![hello 模块配置菜单](figures/1149b400a917cdd7906affecf08503f6.png) + +还可以修改 hello value 的值。 + +![修改 hello value 的值](figures/f96388acd1264b4ffcd391c3fd399a50.png) + +保存配置后退出配置界面,打开 stm32f10x-HAL BSP 目录下的 rtconfig.h 文件可以看到 hello 模块的配置信息已经有了。 + +![hello 模块相关宏定义](figures/cb02d48b93098e73681818fd62f22f8b.png) + +**注意:每次 menuconfig 配置完成后都要使用 scons --target=XXX 命令生成新工程。** + +因为 rtconfig.h 中已经定义了 RT_USING_HELLO 宏,所以新生成工程时就会把 hello.c 的源文件添加到新工程中。 + +上面只是简单列举了在 Kconfig 文件中添加自己模块的配置选项,用户还可以参考[《Env 用户手册》](../env/env.md),里面也有对配置选项修改和添加的讲解,也可以自己百度查看 Kconfig 的相关文档,实现其他更复杂的配置选项。 + +### 添加库 + +如果要往工程中添加一个额外的库,需要注意不同的工具链对二进制库的命名。 + +- ARMCC 工具链下的库名称应该是 xxx.lib,一个以 .lib 为后缀的文件。 +- IAR 工具链下的库名称应该是 xxx.a,一个以 .a 为后缀的文件。 +- GCC 工具链下的库名称应该是 libxxx.a,一个以 .a 为后缀的文件,并且有 lib 前缀。 + +ARMCC / IAR 工具链下,若添加库名为 libabc.lib / libabc_iar.a 时,在指定库时指定全名 libabc。 + +GCC 工具链比较特殊,它识别的是 libxxx.a 这样的库名称,若添加库名为 libabc.a 时,在指定库时是指定 abc,而不是 libabc。 + +例如,`/libs` 下有以下库文件需要添加: + +``` +libabc_keil.lib +libabc_iar.a +libabc_gcc.a +``` + +则对应的 SConscript 如下: + +``` +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() +src = Split(''' +''') + +LIBPATH = [cwd + '/libs'] # LIBPATH 指定库的路径,表示库的搜索路径是当前目录下的'libs'目录 + +if rtconfig.CROSS_TOOL == 'gcc': + LIBS = ['abc_gcc'] # GCC 下 LIBS 指定库的名称 +elif rtconfig.CROSS_TOOL == 'keil': + LIBS = ['libabc_keil'] # ARMCC 下 LIBS 指定库的名称 +else: + LIBS = ['libabc_iar'] # IAR 下 LIBS 指定库的名称 + +group = DefineGroup('ABC', src, depend = [''], LIBS = LIBS, LIBPATH=LIBPATH) + +Return('group') +``` + +### 编译器选项 + +rtconfig.py 是一个 RT-Thread 标准的编译器配置文件,控制了大部分编译选项,是一个使用 python 语言编写的脚本文件,主要用于完成以下工作: + +* 指定编译器(从支持的多个编译器中选择一个你现在使用的编译器)。 + +* 指定编译器参数,如编译选项、链接选项等。 + +当我们使用 scons 命令编译工程时,就会按照 rtconfig.py 的编译器配置选项编译工程。下面的代码为 stm32f10x-HAL BSP 目录下 rtconfig.py 的部分代码。 + +```c +import os + +# toolchains options +ARCH='arm' +CPU='cortex-m3' +CROSS_TOOL='gcc' + +if os.getenv('RTT_CC'): + CROSS_TOOL = os.getenv('RTT_CC') + +# cross_tool provides the cross compiler +# EXEC_PATH is the compiler execute path, for example, CodeSourcery, Keil MDK, IAR + +if CROSS_TOOL == 'gcc': + PLATFORM = 'gcc' + EXEC_PATH = '/usr/local/gcc-arm-none-eabi-5_4-2016q3/bin/' +elif CROSS_TOOL == 'keil': + PLATFORM = 'armcc' + EXEC_PATH = 'C:/Keilv5' +elif CROSS_TOOL == 'iar': + PLATFORM = 'iar' + EXEC_PATH = 'C:/Program Files/IAR Systems/Embedded Workbench 6.0 Evaluation' + +if os.getenv('RTT_EXEC_PATH'): + EXEC_PATH = os.getenv('RTT_EXEC_PATH') + +BUILD = 'debug' + +if PLATFORM == 'gcc': + # toolchains + PREFIX = 'arm-none-eabi-' + CC = PREFIX + 'gcc' + AS = PREFIX + 'gcc' + AR = PREFIX + 'ar' + LINK = PREFIX + 'gcc' + TARGET_EXT = 'elf' + SIZE = PREFIX + 'size' + OBJDUMP = PREFIX + 'objdump' + OBJCPY = PREFIX + 'objcopy' + + DEVICE = '-mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections' + CFLAGS = DEVICE + AFLAGS = '-c' + DEVICE + '-x assembler-with-cpp' + LFLAGS = DEVICE + '-Wl,--gc-sections,-Map=rtthread-stm32.map,-cref,-u,Reset_Handler -T stm32_rom.ld' +``` + +其中 CFLAGS 是 C 文件的编译选项,AFLAGS 则是汇编文件的编译选项,LFLAGS 是链接选项。BUILD 变量控制代码优化的级别。默认 BUILD 变量取值为'debug',即使用 debug 方式编译,优化级别 0。如果将这个变量修改为其他值,就会使用优化级别 2 编译。下面几种都是可行的写法(总之只要不是'debug'就可以了)。 + +```c +BUILD = '' +BUILD = 'release' +BUILD = 'hello, world' +``` + +建议在开发阶段都使用 debug 方式编译,不开优化,等产品稳定之后再考虑优化。 + +关于这些选项的具体含义需要参考编译器手册,如上面使用的 armcc 是 MDK 的底层编译器。其编译选项的含义在 MDK help 中有详细说明。 + +前文提到过如果用户执行 scons 命令时希望使用其他编译器编译工程,可以在 Env 的命令行端使用相关命令指定编译器和编译器路径。但是这样修改只对当前的 Env 进程有效,再次打开时又需要重新使用命令设置,我们可以直接修改 rtconfig.py 文件达到永久配置编译器的目的。一般来说,我们只需要修改 CROSS_TOOL 和下面的 EXEC_PATH 两个选项。 + +* CROSS_TOOL:指定编译器。可选的值有 keil、gcc、iar,浏览 rtconfig.py 可以查看当前 BSP 所支持的编译器。如果您的机器上安装了 MDK,那么可以将 CROSS_TOOL 修改为 keil,则使用 MDK 编译工程。 + +* EXEC_PATH:编译器的安装路径。这里有两点需要注意: + +安装编译器时(如 MDK、GNU GCC、IAR 等),不要安装到带有中文或者空格的路径中。否则,某些解析路径时会出现错误。有些程序默认会安装到 `C:\Program Files` 目录下,中间带有空格。建议安装时选择其他路径,养成良好的开发习惯。 + +修改 EXEC_PATH 时,需要注意路径的格式。在 windows 平台上,默认的路径分割符号是反斜杠 `“\”`,而这个符号在 C 语言以及 Python 中都是用于转义字符的。所以修改路径时,可以将`“\”` 改为 `“/”`,或者在前面加 r(python 特有的语法,表示原始数据)。 + +假如某编译器安装位置为 `D:\Dir1\Dir2` 下。下面几种是正确的写法: + +* EXEC_PATH = `r'D:\Dir1\Dir2'` 注意,字符串前带有 r,则可正常使用 `“\”`。 + +* EXEC_PATH = `'D:/Dir1/Dir2'` 注意,改用 `“/”`,前面没有 r。 + +* EXEC_PATH = `'D:\\Dir1\\Dir2'` 注意,这里使用 `“\”` 的转义性来转义 `“\”` 自己。 + +* 这是错误的写法:EXEC_PATH = `'D:\Dir1\Dir2'`。 + +如果 rtconfig.py 文件有以下代码,在配置自己的编译器时请将下列代码注释掉。 + +```c +if os.getenv('RTT_CC'): + CROSS_TOOL = os.getenv('RTT_CC') +... ... +if os.getenv('RTT_EXEC_PATH'): + EXEC_PATH = os.getenv('RTT_EXEC_PATH') +``` + +上面 2 个 if 判断会设置 CROSS_TOOL 和 EXEC_PATH 为 Env 的默认值。 + +编译器配置完成之后,我们就可以使用 SCons 来编译 RT-Thread 的 BSP 了。在 BSP 目录打开命令行窗口,执行 `scons` 命令就会启动编译过程。 + +### RT-Thread 辅助编译脚本 + +在 RT-Thread 源代码的 tools 目录下存放有 RT-Thread 自己定义的一些辅助编译的脚本,例如用于自动生成 RT-Thread 针对一些 IDE 集成开发环境的工程文件。其中最主要的是 building.py 脚本。 + +### SCons 更多使用 + +对于复杂、大型的系统,显然不仅仅是一个目录下的几个文件就可以搞定的,很可能是由数个文件夹一级一级组合而成。 + +在 SCons 中,可以编写 SConscript 脚本文件来编译这些相对独立目录中的文件,同时也可以使用 SCons 中的 Export 和 Import 函数在 SConstruct 与 SConscript 文件之间共享数据(也就是 Python 中的一个对象数据)。更多 SCons 的使用方法请参考 [SCons 官方文档](https://scons.org/documentation.html)。 diff --git a/index.html b/index.html new file mode 100644 index 0000000000000000000000000000000000000000..6c7b8c8f91b1d8aa8b440ba76b8fa9919d327e22 --- /dev/null +++ b/index.html @@ -0,0 +1,124 @@ +<!DOCTYPE html> +<html> + +<head> + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + <meta charset="UTF-8"> + <title>RT-Thread 文档中心 + + + + + + + + +

Loading ...
+ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/other/figures/image-20210129101544289.png b/other/figures/image-20210129101544289.png new file mode 100644 index 0000000000000000000000000000000000000000..c1074471d98fa7d187b429c7f7f6fcefef36ce52 Binary files /dev/null and b/other/figures/image-20210129101544289.png differ diff --git a/other/figures/image-20210129101736085.png b/other/figures/image-20210129101736085.png new file mode 100644 index 0000000000000000000000000000000000000000..72f6552beeacd3e1b7e5224df1f93d5f16377218 Binary files /dev/null and b/other/figures/image-20210129101736085.png differ diff --git a/other/figures/image-20210129102019156.png b/other/figures/image-20210129102019156.png new file mode 100644 index 0000000000000000000000000000000000000000..9ed7a5de4ea2ca036bad5c71a14263e388a61a1d Binary files /dev/null and b/other/figures/image-20210129102019156.png differ diff --git a/other/figures/image-20210129102214629.png b/other/figures/image-20210129102214629.png new file mode 100644 index 0000000000000000000000000000000000000000..555db45173762b2fdf4700616306d20aff127b76 Binary files /dev/null and b/other/figures/image-20210129102214629.png differ diff --git a/other/novice-guide/README.md b/other/novice-guide/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c49ff8152a3f08b95ffaa4c8abe2102db138be5e --- /dev/null +++ b/other/novice-guide/README.md @@ -0,0 +1,216 @@ + +[![入门学习](figures/1.png)](#入门学习) +[![进阶学习](figures/2.png)](#进阶学习) +[![应用开发](figures/3.png)](#应用开发) + +

+初次使用 RT-Thread 操作系统进行开发的小伙伴,可以参考本篇文档的学习路线进行上手入门。 +

+ +##### +# 版本简介 + + + +## ** 标准版本 ** + +RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。事实上,一个处理器核心在某一时刻只能运行一个任务,由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行非常快速地切换(调度器根据优先级决定此刻该执行的任务),给人造成多个任务在一个时刻同时运行的错觉。在 RT-Thread 系统中,任务通过线程实现的,RT-Thread 中的线程调度器也就是以上提到的任务调度器。 + +RT-Thread 主要采用 C 语言编写,浅显易懂,方便移植。它把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。针对资源受限的微控制器(MCU)系统,可通过方便易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 版本(NANO 是 RT-Thread 官方于 2017 年 7 月份发布的一个极简版内核);而对于资源丰富的物联网设备,RT-Thread 又能使用在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。 + +相较于 Linux 操作系统,RT-Thread 体积小,成本低,功耗低、启动快速,除此以外 RT-Thread 还具有实时性高、占用资源小等特点,非常适用于各种资源受限(如成本、功耗限制等)的场合。虽然 32 位 MCU 是它的主要运行平台,实际上很多带有 MMU、基于 ARM9、ARM11 甚至 Cortex-A 系列级别 CPU 的应用处理器在特定应用场合也适合使用 RT-Thread。 + +适用于需要使用 RT-Thread 的丰富功能,如各类外设、物联网组件、软件包等的场景。[更多...](/rt-thread-version/rt-thread-standard/README.md) + +![Software_framework_diagram](figures/4.png) + +# ** Nano 版本 ** + +RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发,采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小,功能包括任务处理、软件定时器、信号量、邮箱和实时调度等相对完整的实时操作系统特性。适用于家电、消费电子、医疗设备、工控等领域大量使用的 32 位 ARM 入门级 MCU 的场合。 + +下图是 RT-Thread Nano 的软件框图,包含支持的 CPU 架构与内核源码,还有可拆卸的 FinSH 组件: + +![架构](figures/framework.png) + +**支持架构**:ARM:Cortex M0/ M3/ M4/ M7 等、RISC-V 及其他。 + +**功能**:线程管理、线程间同步与通信、时钟管理、中断管理、内存管理。[更多...](/rt-thread-version/rt-thread-nano/an0038-nano-introduction.md) + +## ** Smart 版本 ** + + +RT-Thread Smart 是基于 RT-Thread 操作系统上的混合操作系统,简称为 rt-smart,它把应用从内核中独立出来,形成独立的用户态应用程序,并具备独立的地址空间(32 位系统上是 4G 的独立地址空间)。 + +以下是 rt-smart 的整体结构框图,在硬件平台的基础上通过 MMU、系统调用的方式把整个系统分成了内核态及用户态。[更多...](/rt-thread-version/rt-thread-smart/rt-smart-quickstart/rt-smart-quickstart.md) + +![Software_framework_diagram](figures/5.png) + + + + +# 学习路线 + +从版本简介中可以看出,**Nano 版本** 是 **标准版本** 的极简内核版本,而 **Smart 版本** 是由 **标准版本** 创造而来,所以学习 **标准版本** 是学习 RT-Thread 的基础。本篇文章以学习 **RT-Thread 标准版本** 为例,为初学者制定学习路线如下(供参考),分为入门学习、进阶学习、应用开发三部分。 +## 入门学习 + + + +### ** 无 RTOS 经验 ** + + + +**针对人群:有 C 语言、嵌入式基础,想系统学习 RT-Thread 操作系统** + +#### 模拟运行 + +[Keil MDK 模拟器 STM32F103 体验](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/stm32f103-simulator.md) + +#### 快速上手 + +推荐使用 [潘多拉开发板](https://item.taobao.com/item.htm?id=583527145598) 配套使用 [潘多拉开发板教程. pdf](https://www.rt-thread.org/document/site/tutorial/iot_board_tutorial.pdf),或者以下主流的学习板进行学习,不建议没有任何基础就将 RT-Thread 移植到一块开发板上。 + +- [RT-Thread 潘多拉 STM32L475 上手指南](/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/quick-start.md) +- [野火霸道 STM32F103 上手指南](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/quick-start.md) +- [正点原子 nanoSTM32F103 上手指南](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/quick-start) +- [野火挑战者 STM32F429 上手指南](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/quick-start.md) +- [正点原子探索者 STM32F407 上手指南](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/quick-start.md) +- [正点原子阿波罗 STM32F429 上手指南](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/quick-start.md) +- [野火 I.MX RT1052 上手指南](/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/quick-start.md) +- [正点原子 I.MX RT1052 号令者上手指南](/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/quick-start.md) +- [其他...](/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/quick-start.md) + +#### 内核学习 + +[内核视频教程](https://www.rt-thread.org/page/video.html) + +[《内核实验手册》](https://www.rt-thread.org/document/site/tutorial/experimental-manual/experimental-manual.pdf) + +### ** 有 RTOS 经验 ** + + + +**针对人群:学过 FreeRTOS 或 uC/OS, 想把 RT-Thread 使用起来** + +#### 快速上手 + +准备一块板子,根据 RT-Thread 支持的板子 BSP 进行 [快速上手](/rt-thread-version/rt-thread-standard/tutorial/quick-start/more.md),或者根据 [STM32 系列 BSP 制作教程进行移植](https://github.com/RT-Thread/rt-thread/blob/master/bsp/stm32/README.md)。 + +如果使用 Ubuntu 进行开发,可以参考:[在 Ubuntu 下开发 RT-Thread](/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/an0006-qemu-windows.md)。 + +#### 编程指南 + +快速学习内核,参考:[《RT-Thread 编程指南》](https://www.rt-thread.org/document/site/um4003-rtthread-programming-manual.pdf)。 + +#### API 手册 +查看 [在线 API 手册](https://www.rt-thread.org/document/api/) 或 [下载 API 手册](https://www.rt-thread.org/document/api/api.zip)。 + + + +## 进阶学习 + + + +### ** 开发工具 ** +#### Env 工具 + +Env 工具:Env 工具用于对源码功能进行配置或裁减,可以生成 MDK/IAR/GCC 工程,需要配合 DK/IAR/GCC 使用,详见 [Env 用户手册](/development-tools/env/env.md)。 + +#### RT-Thread IDE + +RT-Thread Studio :可以在 Studio 中下载源码包并创建 rt-thread 工程,独立完成开发、编译、下载、调试等功能,并能进行功能裁剪,详见 [RT-Thread Studio 用户手册](/development-tools/rtthread-studio/um/studio-user-manual.md)。 + +### ** 设备与驱动 ** + + + +[IO 设备模型](/rt-thread-version/rt-thread-standard/programming-manual/device/device.md) + +[PIN 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin.md) + +[UART 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart.md) + +[CAN 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/can/can.md) + +[HWTIMER 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/hwtimer/hwtimer.md) + +[I2C 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/i2c.md) + +[PWM 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/pwm.md) + +[RTC 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/rtc/rtc) + +[SPI 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi) + +[WATCHDOG 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/watchdog/watchdog) + +[SENSOR 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/sensor/sensor) + +[更多...](/rt-thread-version/rt-thread-standard/programming-manual/device/device.md) +### ** 组件 ** + + + +[FinSH 控制台](/rt-thread-version/rt-thread-standard/programming-manual/finsh/finsh) + +[文件系统](/rt-thread-version/rt-thread-standard/programming-manual/filesystem/filesystem) + +[netdev 网卡](/rt-thread-version/rt-thread-standard/programming-manual/netdev/netdev) + +[SAL 套接字抽象层](/rt-thread-version/rt-thread-standard/programming-manual/sal/sal) + +[AT 命令](/rt-thread-version/rt-thread-standard/programming-manual/at/at) + +[ulog 日志](/rt-thread-version/rt-thread-standard/programming-manual/ulog/ulog) + +[utest 测试框架](/rt-thread-version/rt-thread-standard/programming-manual/utest/utest) + +[动态模块](/rt-thread-version/rt-thread-standard/programming-manual/dlmodule/dlmodule) + +[POSIX 接口](/rt-thread-version/rt-thread-standard/programming-manual/posix/posix) + +[电源管理](/rt-thread-version/rt-thread-standard/programming-manual/pm/pm) + +[更多...](/rt-thread-version/rt-thread-standard/programming-manual/finsh/finsh) + + + +## 应用开发 + +| 应用开发列表 | 应用开发列表 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [使用 Eclipse 开发 RT-Thread](/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/an0020-qemu-eclipse) | [CmBacktrace应用](https://www.rt-thread.org/document/site/application-note/debug/cmbacktrace/an0013-CmBacktrace/) | +| [使用 VS Code 开发 RT-Thread](/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/an0021-qemu-vscode) | [在STM32 Nucleo 开发板上使用 RW007 WiFi 模块](https://www.rt-thread.org/document/site/application-note/packages/rw007_module_using/an0034-rw007-module-using) | +| [使用 Env 创建 RT-Thread 项目工程](/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/an0017-standard-project) | [在 STM32L4 上应用 littlefs 文件系统](https://www.rt-thread.org/document/site/application-note/components/dfs/an0027-littlefs/) | +| [搭建RT-Thread项目框架](https://www.rt-thread.org/document/site/application-note/setup/standard-project/an0017-standard-project/) | [在潘多拉上使用 SFUD 操作 Flash](https://www.rt-thread.org/document/site/application-note/components/sfud/an0048-sfud/) | +| [在IoT Board上实现电源管理](https://www.rt-thread.org/document/site/application-note/system/pm/an0025-pm/) | [STM32 通用 Bootloader](https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/) | +| [网络协议栈驱动移植](https://www.rt-thread.org/document/site/application-note/components/network/an0010-lwip-driver-porting/) | [wireshark 抓取 tls 数据包](https://www.rt-thread.org/document/site/application-note/packages/mbedtls_wireshark_sniffer/an0029-mbedtls_wireshark_sniffer) | +| [在STM32F429上应用网络功能](https://www.rt-thread.org/document/site/application-note/components/network/an0011-network-started/) | [在 STM32 上应用 C++](https://www.rt-thread.org/document/site/application-note/components/cplusplus/an0035-cpp/) | +| [在STM32F429上应用文件系统](https://www.rt-thread.org/document/site/application-note/components/dfs/an0012-dfs/) | [STM32 上使用 PWM](https://www.rt-thread.org/document/site/application-note/driver/pwm/an0037-rtthread-driver-pwm/) | +| [FreeModbus 应用笔记](https://www.rt-thread.org/document/site/application-note/packages/freemodbus/an0036-freemodbus/) | [STM32 上使用 USB Host 读写 U 盘](https://www.rt-thread.org/document/site/application-note/driver/usb/an0046-rtthread-driver-usbh/) | +| [应用AT组件连接ESP8266模块](https://www.rt-thread.org/document/site/application-note/components/at/an0014-at-client/) | [QEMU网络视频教程](https://www.rt-thread.org/document/site/tutorial/qemu-network/) | +| [多线程非阻塞网络编程](https://www.rt-thread.org/document/site/application-note/components/network/an0019-tcpclient-socket/) | [使用QEMU运行动态模块组件](https://www.rt-thread.org/document/site/application-note/components/dlmodule/an0023-dlmodule/) | + +[应用设计参考...](https://www.rt-thread.org/page/projects.html) + +## Demo 示例 + +![演示效果图](figures/i12.gif) + +| Demo演示和教程 | Sample示例 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [基于RT-Thread和i.MX RT1052的开源AutoQuad飞控](https://mp.weixin.qq.com/s/OYyE1QmtFLp17IKHPEDUfg) | [内核示例代码](https://github.com/RT-Thread-packages/kernel-sample) | +| [基于RT-Thread的开源飞控StarryPilot](https://mp.weixin.qq.com/s/j3ihGjkZ5Jt0hwUkgY9AdQ) | [设备示例代码](https://github.com/RT-Thread-packages/peripheral-sample) | +| [基于RT-Thread的人体健康监测系统](https://mp.weixin.qq.com/s/ptiz9UFzbVH-jt2gNVvlHg) | [文件系统示例代码](https://github.com/RT-Thread-packages/filesystem-sample) | +| [基于RT-Thread的激光雷达避障小车](https://mp.weixin.qq.com/s/rjKExoGqhI1cPErGogEHDQ) | [网络示例代码](https://github.com/RT-Thread-packages/network-sample) | +| [基于RT-Thread的蓝牙遥控平衡小车](https://mp.weixin.qq.com/s/bslr8Z2vyoT5uOVNXsafjA) | | +| [蜂鸣器播放器](https://www.rt-thread.org/document/site/tutorial/beep-player/) | | +| [分布式温度监控系统](https://www.rt-thread.org/document/site/tutorial/temperature-system/) | | +| [智能车教程](https://www.rt-thread.org/document/site/tutorial/smart-car/) | | + +## 代码贡献 + +| 开发指南 | 代码规范 | 提交代码 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| [软件包开发指南](https://www.rt-thread.org/document/site/development-guide/package/package/) | [RT-Thread编程风格](https://github.com/RT-Thread/rt-thread/blob/master/documentation/coding_style_cn.md) | [向RT-Thread贡献代码](https://www.rt-thread.org/document/site/development-guide/github/github/) | +| [STM32系列BSP制作教程](https://github.com/RT-Thread/rt-thread/blob/master/bsp/stm32/docs/STM32系列BSP制作教程.md) | [BSP开发规范](https://github.com/RT-Thread/rtthread-specification) | | +| [传感器驱动开发指南](https://www.rt-thread.org/document/site/development-guide/sensor/sensor_driver_development/) | | | diff --git a/other/novice-guide/_sidebar.md b/other/novice-guide/_sidebar.md new file mode 100644 index 0000000000000000000000000000000000000000..e1c48b326fb3c31a058c6eb117f3b613178fd49d --- /dev/null +++ b/other/novice-guide/_sidebar.md @@ -0,0 +1,3 @@ + + +- [入门指南](/other/novice-guide/README.md) diff --git a/other/novice-guide/figures/1.png b/other/novice-guide/figures/1.png new file mode 100644 index 0000000000000000000000000000000000000000..958f11816b5b413b943c34bfd38edec6ae2c790b Binary files /dev/null and b/other/novice-guide/figures/1.png differ diff --git a/other/novice-guide/figures/2.png b/other/novice-guide/figures/2.png new file mode 100644 index 0000000000000000000000000000000000000000..b87c4bad401f28218f5705a4bc0c220c854a938d Binary files /dev/null and b/other/novice-guide/figures/2.png differ diff --git a/other/novice-guide/figures/3.png b/other/novice-guide/figures/3.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8841d07f5ab39ca970f8ea303790e01b5016a1 Binary files /dev/null and b/other/novice-guide/figures/3.png differ diff --git a/other/novice-guide/figures/4.png b/other/novice-guide/figures/4.png new file mode 100644 index 0000000000000000000000000000000000000000..1db37a71c6200c81dff25079c5424effc7e7a2a4 Binary files /dev/null and b/other/novice-guide/figures/4.png differ diff --git a/other/novice-guide/figures/5.png b/other/novice-guide/figures/5.png new file mode 100644 index 0000000000000000000000000000000000000000..922cf5588427f43234c0ea005898e703ebba02d1 Binary files /dev/null and b/other/novice-guide/figures/5.png differ diff --git a/other/novice-guide/figures/contents.png b/other/novice-guide/figures/contents.png new file mode 100644 index 0000000000000000000000000000000000000000..8234ce0e1a1dfbc8966e09d9379b4aa4bb18fc15 Binary files /dev/null and b/other/novice-guide/figures/contents.png differ diff --git a/other/novice-guide/figures/framework.png b/other/novice-guide/figures/framework.png new file mode 100644 index 0000000000000000000000000000000000000000..577507b224edce7cb96c52e60077ab991e277f06 Binary files /dev/null and b/other/novice-guide/figures/framework.png differ diff --git a/other/novice-guide/figures/i1.png b/other/novice-guide/figures/i1.png new file mode 100644 index 0000000000000000000000000000000000000000..f8d199613a35174e5de89caefe55f52552f7fd03 Binary files /dev/null and b/other/novice-guide/figures/i1.png differ diff --git a/other/novice-guide/figures/i12.gif b/other/novice-guide/figures/i12.gif new file mode 100644 index 0000000000000000000000000000000000000000..bdc0e702a2c162558a64e6f9729c3a11dc6ed8f5 Binary files /dev/null and b/other/novice-guide/figures/i12.gif differ diff --git a/other/novice-guide/figures/i2.png b/other/novice-guide/figures/i2.png new file mode 100644 index 0000000000000000000000000000000000000000..3793f91dfa2e33f24959bbea720e46d28139526d Binary files /dev/null and b/other/novice-guide/figures/i2.png differ diff --git a/other/novice-guide/figures/i3.png b/other/novice-guide/figures/i3.png new file mode 100644 index 0000000000000000000000000000000000000000..ffeb415ee69b1b182f2034ba241b38ab0166d30e Binary files /dev/null and b/other/novice-guide/figures/i3.png differ diff --git a/other/novice-guide/figures/i4.png b/other/novice-guide/figures/i4.png new file mode 100644 index 0000000000000000000000000000000000000000..bcb7a1a72c0356c2c78ab1cf15a82a6eda7a0772 Binary files /dev/null and b/other/novice-guide/figures/i4.png differ diff --git a/other/novice-guide/figures/i5.png b/other/novice-guide/figures/i5.png new file mode 100644 index 0000000000000000000000000000000000000000..3320fdd3cf1159118d15a8f030a8ddbe6c14b99e Binary files /dev/null and b/other/novice-guide/figures/i5.png differ diff --git a/other/novice-guide/figures/i6.png b/other/novice-guide/figures/i6.png new file mode 100644 index 0000000000000000000000000000000000000000..b571b375ae746903376829fe1448fbed44ca184c Binary files /dev/null and b/other/novice-guide/figures/i6.png differ diff --git a/other/novice-guide/figures/size.png b/other/novice-guide/figures/size.png new file mode 100644 index 0000000000000000000000000000000000000000..d34b6d5a9a08dea934b9b67cfc3401d83cf0d3fa Binary files /dev/null and b/other/novice-guide/figures/size.png differ diff --git a/other/novice-guide/figures/startup.png b/other/novice-guide/figures/startup.png new file mode 100644 index 0000000000000000000000000000000000000000..4128df8d949bc2e7dd062a176beb280cff75d3cf Binary files /dev/null and b/other/novice-guide/figures/startup.png differ diff --git a/rt-thread-version/rt-thread-nano/README.md b/rt-thread-version/rt-thread-nano/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4b606b78cbc1cb5a9eee88972e80726598053a99 --- /dev/null +++ b/rt-thread-version/rt-thread-nano/README.md @@ -0,0 +1,65 @@ +# RT-Thread Nano 简介 + +RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发,采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小,功能包括任务处理、软件定时器、信号量、邮箱和实时调度等相对完整的实时操作系统特性。适用于家电、消费电子、医疗设备、工控等领域大量使用的 32 位 ARM 入门级 MCU 的场合。 + +下图是 RT-Thread Nano 的软件框图,包含支持的 CPU 架构与内核源码,还有可拆卸的 FinSH 组件: + +![架构](figures/framework.png) + +**支持架构**:ARM:Cortex M0/ M3/ M4/ M7 等、RISC-V 及其他。 + +**功能**:线程管理、线程间同步与通信、时钟管理、中断管理、内存管理。 + +## Nano 的特点 + +### 简单 + +**1、下载简单** + +RT-Thread Nano 以软件包的方式集成在 Keil MDK 与 CubeMX 中,可以直接在软件中下载 Nano 软件包获取源码,获取方式详见 [基于 Keil MDK 移植 RT-Thread Nano](nano-port-keil/an0039-nano-port-keil.md) 与 [基于 CubeMX 移植 RT-Thread Nano](nano-port-cube/an0041-nano-port-cube.md) 。 + +同时也提供 [下载 Nano 源码压缩包](https://www.rt-thread.org/download/nano/rt-thread-3.1.3.zip) 的途径,方便在其他开发环境移植 RT-Thread Nano,如 [基于 IAR 移植 RT-Thread Nano](nano-port-iar/an0040-nano-port-iar.md)。 + +**2、代码简单** + +与 RT-Thread 完整版不同的是,Nano 不含 Scons 构建系统,不需要 Kconfig 以及 Env 配置工具,也去除了完整版特有的 device 框架和组件,仅是一个纯净的内核。 + +**3、移植简单** + +由于 Nano 的极简特性,使 Nano 的移植过程变得极为简单。添加 Nano 源码到工程,就已完成 90% 的移植工作。 + +在 Keil MDK 与 Cube MX 中还提供了 Nano 的软件包,可以一键下载加入到工程。另外,在 RT-Thread Studio 中可以基于 Nano 创建工程直接使用。以下是使用不同开发环境时,可以选择移植或使用 Nano 的方法: + +- [在 RT-Thread Studio 上使用 RT-Thread Nano](nano-port-studio/an0047-nano-port-studio.md) +- [基于 KEIL MDK 移植 RT-Thread Nano](nano-port-keil/an0039-nano-port-keil.md) +- [基于 CubeMX 移植 RT-Thread Nano](nano-port-cube/an0041-nano-port-cube.md) +- [基于 IAR 移植 RT-Thread Nano](nano-port-iar/an0040-nano-port-iar.md) +- [移植 RT-Thread Nano 到 RISC-V](nano-port-gcc-riscv/an0042-nano-port-gcc-riscv.md) + +**4、使用简单** + +RT-Thread Nano 在使用上也非常简单,带给开发者友好的开发体验。 + +- 易裁剪:Nano 的配置文件为 rtconfig.h,该文件中列出了内核中的所有宏定义,有些默认没有打开,如需使用,打开即可。具体的配置可见 Nano 版块的 [RT-Thread Nano 配置](nano-config/an0043-nano-config.md) 教程。 +- 易添加 FinSH 组件:[FinSH 组件](../../programming-manual/finsh/finsh.md) 可以很方便的在 Nano 上进行移植,而不再依赖 device 框架,只需要对接两个必要的函数即可完成 [FinSH 移植](finsh-port/an0045-finsh-port.md)。 +- 自选驱动库:可以使用厂商提供的固件驱动库,如 ST 的 STD 库、HAL 库、LL 库等,可以自行选择。 +- 完善的文档:包含 [内核基础](../../programming-manual/basic/basic.md)、[线程管理 (例程)](../../programming-manual/thread/thread.md)、[时钟管理 (例程)](../../programming-manual/timer/timer.md)、[线程间同步 (例程)](../../programming-manual/ipc1/ipc1.md)、[线程间通信 (例程)](../../programming-manual/ipc2/ipc2.md)、[内存管理 (例程)](../../programming-manual/memory/memory.md)、[中断管理](../../programming-manual/interrupt/interrupt.md) ,以及 Nano 版块的移植教程。 + +### 小巧 + +**资源占用小**:对 RAM 与 ROM 的开销非常小,在支持 semaphore 和 mailbox 特性,并运行两个线程 (main 线程 + idle 线程) 情况下,ROM 和 RAM 依然保持着极小的尺寸,RAM 占用约 1K 左右,ROM 占用 4K 左右。 + +Nano 资源占用情况举例:在运行两个线程 (main 线程 + idle 线程) 情况下,ROM 和 RAM 依然保持着极小的尺寸。以下是基于 Cortex M3 的 MDK 工程编译结果(优化等级 3)。 + +``` + Total RO Size (Code + RO Data) 4000 ( 3.91kB) + Total RW Size (RW Data + ZI Data) 1168 ( 1.14kB) + Total ROM Size (Code + RO Data + RW Data) 4092 ( 4.00kB) +``` + + +> 注:如果需要丰富的组件、驱动以及软件包等功能,则建议使用 [RT-Thread 完整版](../../index.md)。 + +### 开源免费(Apache 2.0) + +RT-Thread Nano 实时操作系统遵循 Apache 许可证 2.0 版本,实时操作系统内核及所有开源组件可以免费在商业产品中使用,不需要公布应用程序源码,没有潜在商业风险。 diff --git a/rt-thread-version/rt-thread-nano/_navbar.md b/rt-thread-version/rt-thread-nano/_navbar.md new file mode 100644 index 0000000000000000000000000000000000000000..0ad4782e64805bd8ddd0b68593c9895e297b105c --- /dev/null +++ b/rt-thread-version/rt-thread-nano/_navbar.md @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/rt-thread-version/rt-thread-nano/_sidebar.md b/rt-thread-version/rt-thread-nano/_sidebar.md new file mode 100644 index 0000000000000000000000000000000000000000..a8a1ec56aa6e1321e32a0085f00e863fabcf7062 --- /dev/null +++ b/rt-thread-version/rt-thread-nano/_sidebar.md @@ -0,0 +1,12 @@ + + +- RT-Thread Nano版本 + - [Nano 简介](/rt-thread-version/rt-thread-nano/an0038-nano-introduction.md) + - [使用 RT-Thread Studio 移植](/rt-thread-version/rt-thread-nano/nano-port-studio/an0047-nano-port-studio.md) + - [使用 MDK 移植](/rt-thread-version/rt-thread-nano/nano-port-keil/an0039-nano-port-keil.md) + - [使用 IAR 移植](/rt-thread-version/rt-thread-nano/nano-port-iar/an0040-nano-port-iar.md) + - [使用 CubeMX 移植](/rt-thread-version/rt-thread-nano/nano-port-cube/an0041-nano-port-cube.md) + - [移植 Nano 到 RISC-V](/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/an0042-nano-port-gcc-riscv.md) + - [Nano 配置](/rt-thread-version/rt-thread-nano/nano-config/an0043-nano-config.md) + - [Nano 移植原理](/rt-thread-version/rt-thread-nano/nano-port-principle/an0044-nano-port-principle.md) + - [移植控制台/FinSH](/rt-thread-version/rt-thread-nano/finsh-port/an0045-finsh-port.md) diff --git a/rt-thread-version/rt-thread-nano/an0038-nano-introduction.md b/rt-thread-version/rt-thread-nano/an0038-nano-introduction.md new file mode 100644 index 0000000000000000000000000000000000000000..30ecfbd10da1cd3f187df13e10336b7a3bd78a7e --- /dev/null +++ b/rt-thread-version/rt-thread-nano/an0038-nano-introduction.md @@ -0,0 +1,64 @@ +# RT-Thread Nano 简介 + +RT-Thread Nano 是一个极简版的硬实时内核,它是由 C 语言开发,采用面向对象的编程思维,具有良好的代码风格,是一款可裁剪的、抢占式实时多任务的 RTOS。其内存资源占用极小,功能包括任务处理、软件定时器、信号量、邮箱和实时调度等相对完整的实时操作系统特性。适用于家电、消费电子、医疗设备、工控等领域大量使用的 32 位 ARM 入门级 MCU 的场合。 + +下图是 RT-Thread Nano 的软件框图,包含支持的 CPU 架构与内核源码,还有可拆卸的 FinSH 组件: + +![架构](figures/framework.png) + +**支持架构**:ARM:Cortex M0/ M3/ M4/ M7 等、RISC-V 及其他。 + +**功能**:线程管理、线程间同步与通信、时钟管理、中断管理、内存管理。 + +## Nano 的特点 + +### 简单 + +**1、下载简单** + +RT-Thread Nano 以软件包的方式集成在 Keil MDK 与 CubeMX 中,可以直接在软件中下载 Nano 软件包获取源码,获取方式详见 [基于 Keil MDK 移植 RT-Thread Nano](nano-port-keil/an0039-nano-port-keil.md) 与 [基于 CubeMX 移植 RT-Thread Nano](nano-port-cube/an0041-nano-port-cube.md) 。 + +同时也提供 [下载 Nano 源码压缩包](https://www.rt-thread.org/download/nano/rt-thread-3.1.3.zip) 的途径,方便在其他开发环境移植 RT-Thread Nano,如 [基于 IAR 移植 RT-Thread Nano](nano-port-iar/an0040-nano-port-iar.md)。 + +**2、代码简单** + +与 RT-Thread 完整版不同的是,Nano 不含 Scons 构建系统,不需要 Kconfig 以及 Env 配置工具,也去除了完整版特有的 device 框架和组件,仅是一个纯净的内核。 + +**3、移植简单** + +由于 Nano 的极简特性,使 Nano 的移植过程变得极为简单。添加 Nano 源码到工程,就已完成 90% 的移植工作。 + +在 Keil MDK 与 Cube MX 中还提供了 Nano 的软件包,可以一键下载加入到工程。另外,在 RT-Thread Studio 中可以基于 Nano 创建工程直接使用。以下是使用不同开发环境时,可以选择移植或使用 Nano 的方法: + +- [在 RT-Thread Studio 上使用 RT-Thread Nano](nano-port-studio/an0047-nano-port-studio.md) +- [基于 KEIL MDK 移植 RT-Thread Nano](nano-port-keil/an0039-nano-port-keil.md) +- [基于 CubeMX 移植 RT-Thread Nano](nano-port-cube/an0041-nano-port-cube.md) +- [基于 IAR 移植 RT-Thread Nano](nano-port-iar/an0040-nano-port-iar.md) +- [移植 RT-Thread Nano 到 RISC-V](nano-port-gcc-riscv/an0042-nano-port-gcc-riscv.md) + +**4、使用简单** + +RT-Thread Nano 在使用上也非常简单,带给开发者友好的开发体验。 + +- 易裁剪:Nano 的配置文件为 rtconfig.h,该文件中列出了内核中的所有宏定义,有些默认没有打开,如需使用,打开即可。具体的配置可见 Nano 版块的 [RT-Thread Nano 配置](nano-config/an0043-nano-config.md) 教程。 +- 易添加 FinSH 组件:[FinSH 组件](../../programming-manual/finsh/finsh.md) 可以很方便的在 Nano 上进行移植,而不再依赖 device 框架,只需要对接两个必要的函数即可完成 [FinSH 移植](finsh-port/an0045-finsh-port.md)。 +- 自选驱动库:可以使用厂商提供的固件驱动库,如 ST 的 STD 库、HAL 库、LL 库等,可以自行选择。 +- 完善的文档:包含 [内核基础](../../programming-manual/basic/basic.md)、[线程管理 (例程)](../../programming-manual/thread/thread.md)、[时钟管理 (例程)](../../programming-manual/timer/timer.md)、[线程间同步 (例程)](../../programming-manual/ipc1/ipc1.md)、[线程间通信 (例程)](../../programming-manual/ipc2/ipc2.md)、[内存管理 (例程)](../../programming-manual/memory/memory.md)、[中断管理](../../programming-manual/interrupt/interrupt.md) ,以及 Nano 版块的移植教程。 + +### 小巧 + +**资源占用小**:对 RAM 与 ROM 的开销非常小,在支持 semaphore 和 mailbox 特性,并运行两个线程 (main 线程 + idle 线程) 情况下,ROM 和 RAM 依然保持着极小的尺寸,RAM 占用约 1K 左右,ROM 占用 4K 左右。 + +Nano 资源占用情况举例:在运行两个线程 (main 线程 + idle 线程) 情况下,ROM 和 RAM 依然保持着极小的尺寸。以下是基于 Cortex M3 的 MDK 工程编译结果(优化等级 3)。 + +``` + Total RO Size (Code + RO Data) 4000 ( 3.91kB) + Total RW Size (RW Data + ZI Data) 1168 ( 1.14kB) + Total ROM Size (Code + RO Data + RW Data) 4092 ( 4.00kB) +``` + +> 注:如果需要丰富的组件、驱动以及软件包等功能,则建议使用 [RT-Thread 完整版](../../index.md)。 + +### 开源免费(Apache 2.0) + +RT-Thread Nano 实时操作系统遵循 Apache 许可证 2.0 版本,实时操作系统内核及所有开源组件可以免费在商业产品中使用,不需要公布应用程序源码,没有潜在商业风险。 diff --git a/rt-thread-version/rt-thread-nano/figures/framework.png b/rt-thread-version/rt-thread-nano/figures/framework.png new file mode 100644 index 0000000000000000000000000000000000000000..577507b224edce7cb96c52e60077ab991e277f06 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/figures/framework.png differ diff --git a/rt-thread-version/rt-thread-nano/figures/size.png b/rt-thread-version/rt-thread-nano/figures/size.png new file mode 100644 index 0000000000000000000000000000000000000000..d34b6d5a9a08dea934b9b67cfc3401d83cf0d3fa Binary files /dev/null and b/rt-thread-version/rt-thread-nano/figures/size.png differ diff --git a/rt-thread-version/rt-thread-nano/figures/startup.png b/rt-thread-version/rt-thread-nano/figures/startup.png new file mode 100644 index 0000000000000000000000000000000000000000..4128df8d949bc2e7dd062a176beb280cff75d3cf Binary files /dev/null and b/rt-thread-version/rt-thread-nano/figures/startup.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/an0045-finsh-port.md b/rt-thread-version/rt-thread-nano/finsh-port/an0045-finsh-port.md new file mode 100644 index 0000000000000000000000000000000000000000..77e24f8ee0c0d5b1da4046e7a92837b08f41837d --- /dev/null +++ b/rt-thread-version/rt-thread-nano/finsh-port/an0045-finsh-port.md @@ -0,0 +1,467 @@ +# 在 RT-Thread Nano 上添加控制台与 FinSH + +本片文档分为两部分:第一部分是实现 UART 控制台,该部分只需要实现两个函数即可完成 UART 控制台打印功能。第二部分是实现移植 FinSH 组件,实现在控制台输入命令调试系统,该部分实现基于第一部分,只需要添加 FinSH 组件源码并再对接一个系统函数即可实现。下面将对这两部分进行说明。 + +## 在 Nano 上添加 UART 控制台 + +在 RT-Thread Nano 上添加 UART 控制台打印功能后,就可以在代码中使用 RT-Thread 提供的打印函数 rt_kprintf() 进行信息打印,从而获取自定义的打印信息,方便定位代码 bug 或者获取系统当前运行状态等。实现控制台打印(需要确认 rtconfig.h 中已使能 `RT_USING_CONSOLE` 宏定义),需要完成基本的硬件初始化,以及对接一个系统输出字符的函数,本小节将详细说明。 + +### 实现串口初始化 + +使用串口对接控制台的打印,首先需要初始化串口,如引脚、波特率等。 uart_init() 需要在 board.c 中的 `rt_hw_board_init()` 函数中调用。 + +```c +/* 实现 1:初始化串口 */ +static int uart_init(void); +``` + +**示例代码**:如下是基于 HAL 库的 STM32F103 串口驱动,完成添加控制台的示例代码,仅做参考。 + +```c +static UART_HandleTypeDef UartHandle; +static int uart_init(void) +{ + /* 初始化串口参数,如波特率、停止位等等 */ + UartHandle.Instance = USART1; + UartHandle.Init.BaudRate = 115200; + UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; + UartHandle.Init.Mode = UART_MODE_TX_RX; + UartHandle.Init.OverSampling = UART_OVERSAMPLING_16; + UartHandle.Init.WordLength = UART_WORDLENGTH_8B; + UartHandle.Init.StopBits = UART_STOPBITS_1; + UartHandle.Init.Parity = UART_PARITY_NONE; + + /* 初始化串口引脚等 */ + if (HAL_UART_Init(&UartHandle) != HAL_OK) + { + while(1); + } + + return 0; +} +INIT_BOARD_EXPORT(uart_init); +``` + +```c +/* board.c */ +void rt_hw_board_init(void) +{ + .... + uart_init(); // 在 rt_hw_board_init 函数中调用 串口初始化 函数 + .... +} +``` + +### 实现 rt_hw_console_output + +实现 finsh 组件输出一个字符,即在该函数中实现 uart 输出字符: + +```c +/* 实现 2:输出一个字符,系统函数,函数名不可更改 */ +void rt_hw_console_output(const char *str); +``` + +> [!NOTE] +> 注:注意:RT-Thread 系统中已有的打印均以 `\n` 结尾,而并非 `\r\n`,所以在字符输出时,需要在输出 `\n` 之前输出 `\r`,完成回车与换行,否则系统打印出来的信息将只有换行。 + +**示例代码**:如下是基于STM32F103 HAL 串口驱动对接的 rt_hw_console_output() 函数,实现控制台字符输出,示例仅做参考。 + +```c +void rt_hw_console_output(const char *str) +{ + rt_size_t i = 0, size = 0; + char a = '\r'; + + __HAL_UNLOCK(&UartHandle); + + size = rt_strlen(str); + for (i = 0; i < size; i++) + { + if (*(str + i) == '\n') + { + HAL_UART_Transmit(&UartHandle, (uint8_t *)&a, 1, 1); + } + HAL_UART_Transmit(&UartHandle, (uint8_t *)(str + i), 1, 1); + } +} +``` + +### 结果验证 + +在应用代码中编写含有 rt_kprintf() 打印的代码,编译下载,打开串口助手进行验证。如下图是一个在 main() 函数中每隔 1 秒进行循环打印 `Hello RT-Thread` 的示例效果: + +![示例](figures/ok1.png) + +## 在 Nano 上添加 FinSH 组件 + +[RT-Thread FinSH](https://www.rt-thread.org/document/site/programming-manual/finsh/finsh/) 是 RT-Thread 的命令行组件(shell),提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口 / 以太网 / USB 等与 PC 机进行通信,使用 FinSH 组件基本命令的效果图如下所示: + +![效果图](figures/i12.gif) + +本文以串口 UART 作为 FinSH 的输入输出端口与 PC 进行通信,描述如何在 Nano 上实现 FinSH shell 功能。 + +在 RT-Thread Nano 上添加 FinSH 组件,实现 FinSH 功能的步骤主要如下: + +1. 添加 FinSH 源码到工程。 +2. 实现函数对接。 + +### 添加 FinSH 源码到工程 + +#### KEIL 添加 FinSH 源码 + +点击 Manage Run-Environment: + +![Manage Run-Environment](figures/keil-add-src.png) + +勾选 shell,这将自动把 FinSH 组件的源码到工程: + +![勾选 shell](figures/add.png) + +#### Cube MX 添加 FinSH 源码 + +打开一个 cube 工程,点击 Additional Software,在 Pack Vendor 中可勾选 RealThread 快速定位 RT-Thread 软件包,然后在 RT-Thread 软件包中勾选 shell,即可添加 FinSH 组件的源码到工程中。 + +![Cube MX 添加 FinSH 源码](figures/cube-add-finsh-src.png) + +#### 其他 IDE 添加 FinSH 源码 + +其他 IDE 添加 FinSH 源码,需要手动添加 FinSH 源码以及头文件路径到工程中,以 IAR IDE 为例进行结介绍。 + +1、复制 FinSH 源码到目标裸机工程:直接复制 Nano 源码中 rtthread-nano/components 文件夹下的 `finsh` 文件夹到工程中,如图: + +![复制 finsh 源码](figures/add-finsh-src.png) + +2、目标工程添加 FinSH 源码: + +- 打开工程,新建 `finsh` 分组,添加工程中 `finsh` 文件夹下的所有. c 文件,如下图; +- 添加 `finsh` 文件夹的头文件路径(点击 `Project -> Options... ` 进入弹窗进行添加,如下图); +- 在 rtconfig.h 中添加 `#define RT_USING_FINSH` 宏定义,这样 FinSH 将生效,如下图。 + +![添加 finsh 源码](figures/finsh-src.png) + +![添加 finsh 头文件路径](figures/iar-finsh-h.png) + +![添加 finsh 所需宏定义](figures/rtconfig-add.png) + +### 实现 rt_hw_console_getchar + +要实现 FinSH 组件功能:既可以打印也能输入命令进行调试,控制台已经实现了打印功能,现在还需要在 board.c 中对接控制台输入函数,实现字符输入: + +```c +/* 实现 3:finsh 获取一个字符,系统函数,函数名不可更改 */ +char rt_hw_console_getchar(void); +``` + +- rt_hw_console_getchar():控制台获取一个字符,即在该函数中实现 uart 获取字符,可以使用查询方式获取(注意不要死等,在未获取到字符时,需要让出 CPU),也可以使用中断方式获取。 + +**示例代码**:如下是基于 STM32F103 HAL 串口驱动对接的 rt_hw_console_getchar(),完成对接 FinSH 组件,其中获取字符采用查询方式,示例仅做参考。 + +```c +char rt_hw_console_getchar(void) +{ + int ch = -1; + + if (__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_RXNE) != RESET) + { + ch = UartHandle.Instance->DR & 0xff; + } + else + { + if(__HAL_UART_GET_FLAG(&UartHandle, UART_FLAG_ORE) != RESET) + { + __HAL_UART_CLEAR_OREFLAG(&UartHandle); + } + rt_thread_mdelay(10); + } + return ch; +} +``` + +### 结果验证 + +编译下载代码,打开串口助手,可以在串口助手中打印输入 help 命令,回车查看系统支持的命令: + +![下载验证 finsh](figures/finsh-ok.png) + +如果没有成功运行,请检查对接的函数实现是否正确。 + +## 移植示例代码 + +### 中断示例 + +如下是基于 STM32F103 HAL 串口驱动,实现控制台输出与 FinSH Shell,其中获取字符采用中断方式。原理是,在 uart 接收到数据时产生中断,在中断中把数据存入 ringbuffer 缓冲区,然后释放信号量,tshell 线程接收信号量,然后读取存在 ringbuffer 中的数据。示例仅做参考。 + +```c +/* 第一部分:ringbuffer 实现部分 */ +#include +#include + +#define rt_ringbuffer_space_len(rb) ((rb)->buffer_size - rt_ringbuffer_data_len(rb)) + +struct rt_ringbuffer +{ + rt_uint8_t *buffer_ptr; + + rt_uint16_t read_mirror : 1; + rt_uint16_t read_index : 15; + rt_uint16_t write_mirror : 1; + rt_uint16_t write_index : 15; + + rt_int16_t buffer_size; +}; + +enum rt_ringbuffer_state +{ + RT_RINGBUFFER_EMPTY, + RT_RINGBUFFER_FULL, + /* half full is neither full nor empty */ + RT_RINGBUFFER_HALFFULL, +}; + +rt_inline enum rt_ringbuffer_state rt_ringbuffer_status(struct rt_ringbuffer *rb) +{ + if (rb->read_index == rb->write_index) + { + if (rb->read_mirror == rb->write_mirror) + return RT_RINGBUFFER_EMPTY; + else + return RT_RINGBUFFER_FULL; + } + return RT_RINGBUFFER_HALFFULL; +} + +/** + * get the size of data in rb + */ +rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb) +{ + switch (rt_ringbuffer_status(rb)) + { + case RT_RINGBUFFER_EMPTY: + return 0; + case RT_RINGBUFFER_FULL: + return rb->buffer_size; + case RT_RINGBUFFER_HALFFULL: + default: + if (rb->write_index > rb->read_index) + return rb->write_index - rb->read_index; + else + return rb->buffer_size - (rb->read_index - rb->write_index); + }; +} + +void rt_ringbuffer_init(struct rt_ringbuffer *rb, + rt_uint8_t *pool, + rt_int16_t size) +{ + RT_ASSERT(rb != RT_NULL); + RT_ASSERT(size > 0); + + /* initialize read and write index */ + rb->read_mirror = rb->read_index = 0; + rb->write_mirror = rb->write_index = 0; + + /* set buffer pool and size */ + rb->buffer_ptr = pool; + rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE); +} + +/** + * put a character into ring buffer + */ +rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch) +{ + RT_ASSERT(rb != RT_NULL); + + /* whether has enough space */ + if (!rt_ringbuffer_space_len(rb)) + return 0; + + rb->buffer_ptr[rb->write_index] = ch; + + /* flip mirror */ + if (rb->write_index == rb->buffer_size-1) + { + rb->write_mirror = ~rb->write_mirror; + rb->write_index = 0; + } + else + { + rb->write_index++; + } + + return 1; +} +/** + * get a character from a ringbuffer + */ +rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch) +{ + RT_ASSERT(rb != RT_NULL); + + /* ringbuffer is empty */ + if (!rt_ringbuffer_data_len(rb)) + return 0; + + /* put character */ + *ch = rb->buffer_ptr[rb->read_index]; + + if (rb->read_index == rb->buffer_size-1) + { + rb->read_mirror = ~rb->read_mirror; + rb->read_index = 0; + } + else + { + rb->read_index++; + } + + return 1; +} + + +/* 第二部分:finsh 移植对接部分 */ +#define UART_RX_BUF_LEN 16 +rt_uint8_t uart_rx_buf[UART_RX_BUF_LEN] = {0}; +struct rt_ringbuffer uart_rxcb; /* 定义一个 ringbuffer cb */ +static UART_HandleTypeDef UartHandle; +static struct rt_semaphore shell_rx_sem; /* 定义一个静态信号量 */ + +/* 初始化串口,中断方式 */ +static int uart_init(void) +{ + /* 初始化串口接收 ringbuffer */ + rt_ringbuffer_init(&uart_rxcb, uart_rx_buf, UART_RX_BUF_LEN); + + /* 初始化串口接收数据的信号量 */ + rt_sem_init(&(shell_rx_sem), "shell_rx", 0, 0); + + /* 初始化串口参数,如波特率、停止位等等 */ + UartHandle.Instance = USART2; + UartHandle.Init.BaudRate = 115200; + UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; + UartHandle.Init.Mode = UART_MODE_TX_RX; + UartHandle.Init.OverSampling = UART_OVERSAMPLING_16; + UartHandle.Init.WordLength = UART_WORDLENGTH_8B; + UartHandle.Init.StopBits = UART_STOPBITS_1; + UartHandle.Init.Parity = UART_PARITY_NONE; + + /* 初始化串口引脚等 */ + if (HAL_UART_Init(&UartHandle) != HAL_OK) + { + while (1); + } + + /* 中断配置 */ + __HAL_UART_ENABLE_IT(&UartHandle, UART_IT_RXNE); + HAL_NVIC_EnableIRQ(USART2_IRQn); + HAL_NVIC_SetPriority(USART2_IRQn, 3, 3); + + return 0; +} +INIT_BOARD_EXPORT(uart_init); + +/* 移植控制台,实现控制台输出, 对接 rt_hw_console_output */ +void rt_hw_console_output(const char *str) +{ + rt_size_t i = 0, size = 0; + char a = '\r'; + + __HAL_UNLOCK(&UartHandle); + + size = rt_strlen(str); + for (i = 0; i < size; i++) + { + if (*(str + i) == '\n') + { + HAL_UART_Transmit(&UartHandle, (uint8_t *)&a, 1, 1); + } + HAL_UART_Transmit(&UartHandle, (uint8_t *)(str + i), 1, 1); + } +} + +/* 移植 FinSH,实现命令行交互, 需要添加 FinSH 源码,然后再对接 rt_hw_console_getchar */ +/* 中断方式 */ +char rt_hw_console_getchar(void) +{ + char ch = 0; + + /* 从 ringbuffer 中拿出数据 */ + while (rt_ringbuffer_getchar(&uart_rxcb, (rt_uint8_t *)&ch) != 1) + { + rt_sem_take(&shell_rx_sem, RT_WAITING_FOREVER); + } + return ch; +} + +/* uart 中断 */ +void USART2_IRQHandler(void) +{ + int ch = -1; + rt_base_t level; + /* enter interrupt */ + rt_interrupt_enter(); //在中断中一定要调用这对函数,进入中断 + + if ((__HAL_UART_GET_FLAG(&(UartHandle), UART_FLAG_RXNE) != RESET) && + (__HAL_UART_GET_IT_SOURCE(&(UartHandle), UART_IT_RXNE) != RESET)) + { + while (1) + { + ch = -1; + if (__HAL_UART_GET_FLAG(&(UartHandle), UART_FLAG_RXNE) != RESET) + { + ch = UartHandle.Instance->DR & 0xff; + } + if (ch == -1) + { + break; + } + /* 读取到数据,将数据存入 ringbuffer */ + rt_ringbuffer_putchar(&uart_rxcb, ch); + } + rt_sem_release(&shell_rx_sem); + } + + /* leave interrupt */ + rt_interrupt_leave(); //在中断中一定要调用这对函数,离开中断 +} + +#define USART_TX_Pin GPIO_PIN_2 +#define USART_RX_Pin GPIO_PIN_3 + +void HAL_UART_MspInit(UART_HandleTypeDef *huart) +{ + GPIO_InitTypeDef GPIO_InitStruct = {0}; + if (huart->Instance == USART2) + { + __HAL_RCC_USART2_CLK_ENABLE(); + + __HAL_RCC_GPIOA_CLK_ENABLE(); + /**USART2 GPIO Configuration + PA2 ------> USART2_TX + PA3 ------> USART2_RX + */ + GPIO_InitStruct.Pin = USART_TX_Pin | USART_RX_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + } +} +``` + +## 常见问题 + +### Q: rt_kprintf() 不能打印浮点数吗? + +A: 不可以。但是可以通过其他方法实现打印浮点数的目的,比如成倍扩大数值后,分别打印整数与小数部分。 + +### Q: 在实现 FinSH 完整功能时,却不能输入。 + +![示例](figures/ok1.png) + +A:可能的原因有:UART 驱动未实现字符输入函数、未打开 FinSH 组件等;如果手动开启了 HEAP,需要确定 HEAP 是否过小,导致 tshell 线程创建失败 。 + +### Q: 出现 hard fault。 + +A: ps 后关注各个线程栈的最大利用率,若某线程出现 100% 的情况,则表示该线程栈过小,需要将值调大。 + diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/add-finsh-src.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/add-finsh-src.png new file mode 100644 index 0000000000000000000000000000000000000000..c36b4ae6a198b3e3d5a0931c9ec8a7aa1658ac4c Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/add-finsh-src.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/add.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/add.png new file mode 100644 index 0000000000000000000000000000000000000000..d48da40295c2747b30a0fe533886e34a5125fbe5 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/add.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/cube-add-finsh-src.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/cube-add-finsh-src.png new file mode 100644 index 0000000000000000000000000000000000000000..82ebb128ddae57bda9cce69c01fcaa616d1f5a16 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/cube-add-finsh-src.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/enable.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/enable.png new file mode 100644 index 0000000000000000000000000000000000000000..cb25c476bc7b58d50a8178c0c237c5afa9a07bc9 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/enable.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/finsh-ok.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/finsh-ok.png new file mode 100644 index 0000000000000000000000000000000000000000..1b3b66dcfeb9af0d6053bbddd30abdb594116c36 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/finsh-ok.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/finsh-src.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/finsh-src.png new file mode 100644 index 0000000000000000000000000000000000000000..5cbff5cae7dd437ad07a4e0a038abf80c5edf8f4 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/finsh-src.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/i12.gif b/rt-thread-version/rt-thread-nano/finsh-port/figures/i12.gif new file mode 100644 index 0000000000000000000000000000000000000000..bdc0e702a2c162558a64e6f9729c3a11dc6ed8f5 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/i12.gif differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-add-finsh-src.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-add-finsh-src.png new file mode 100644 index 0000000000000000000000000000000000000000..a2abd27101db8ca8af45befdf3e88e96c6022419 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-add-finsh-src.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-finsh-h.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-finsh-h.png new file mode 100644 index 0000000000000000000000000000000000000000..ab47dacc99469059bbe30856937e89e310246db4 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-finsh-h.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-finsh.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-finsh.png new file mode 100644 index 0000000000000000000000000000000000000000..94c8986a20c9a2e6648f49048efc2a376faed9aa Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/iar-finsh.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/keil-add-src.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/keil-add-src.png new file mode 100644 index 0000000000000000000000000000000000000000..e493726600d31b0cb3fc8a0d70b595108a581455 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/keil-add-src.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/ok1.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/ok1.png new file mode 100644 index 0000000000000000000000000000000000000000..8b9cda954e94501d6fb5f6b652eeeed27e93fd7c Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/ok1.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig-add.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig-add.png new file mode 100644 index 0000000000000000000000000000000000000000..50893dabb53d89e7dadf6b91461f318d34a5647a Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig-add.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig-fini.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig-fini.png new file mode 100644 index 0000000000000000000000000000000000000000..f1f17adfc96be50c162e7f055f0f940981a9e58f Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig-fini.png differ diff --git a/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig.png b/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..8406f9a2405715e8b7f27340a7a2ad23a43515c2 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/finsh-port/figures/rtconfig.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-config/an0043-nano-config.md b/rt-thread-version/rt-thread-nano/nano-config/an0043-nano-config.md new file mode 100644 index 0000000000000000000000000000000000000000..cf9620a860d2a8fbf6dbcc9682b5d7fd97b81cc8 --- /dev/null +++ b/rt-thread-version/rt-thread-nano/nano-config/an0043-nano-config.md @@ -0,0 +1,151 @@ +# RT-Thread Nano 配置 + +RT-Thread Nano 的配置在 rtconfig.h 中进行,通过开关宏定义来使能或关闭某些功能,接下来对该配置文件中的宏定义进行说明。 + +## 头文件 + +头文件 `RTE_Components.h` 仅由 Keil MDK 工程生成,其中仅定义了一个打开 FinSH 组件的宏 `RTE_USING_FINSH`。 + +```c + +#if defined (__CC_ARM) || (__CLANG_ARM) +#include "RTE_Components.h" /* 用来开关 FinSH 组件,仅 MDK 会产生该文件 */ + +#if defined(RTE_USING_FINSH) +#define RT_USING_FINSH +#endif //RTE_USING_FINSH + +#endif //(__CC_ARM) || (__CLANG_ARM) + +``` + +非 Keil MDK 则不需要该头文件,若需打开 FinSH 组件,可直接在 rtconfig.h 中手动定义 `RT_USING_FINSH` 打开 FinSH 组件。 + +## 基础配置 + +1、设置系统最大优先级,可设置范围 8 到 256,默认值 8,可修改。 + +```c +#define RT_THREAD_PRIORITY_MAX 8 +``` +2、设置 RT-Thread 操作系统节拍,表示多少 tick 每秒,如默认值为 100 ,表示一个时钟节拍(os tick)长度为 10ms。常用值为 100 或 1000。时钟节拍率越快,系统的额外开销就越大。 + +```c +#define RT_TICK_PER_SECOND 100 +``` +3、字节对齐时设定对齐的字节个数,默认 4,常使用 ALIGN(RT_ALIGN_SIZE) 进行字节对齐。 + +```c +#define RT_ALIGN_SIZE 4 +``` +4、设置对象名称的最大长度,默认 8 个字符,一般无需修改。 + +```c +#define RT_NAME_MAX 8 +``` +5、设置使用组件自动初始化功能,默认需要使用,开启该宏则可以使用自动初始化功能。 + +```c +#define RT_USING_COMPONENTS_INIT +``` +6、开启 `RT_USING_USER_MAIN` 宏,则打开 user_main 功能,默认需要开启,这样才能调用 RT-Thread 的启动代码;main 线程的栈大小默认为 256,可修改。 + +```c +#define RT_USING_USER_MAIN +#define RT_MAIN_THREAD_STACK_SIZE 256 +``` +## 内核调试功能配置 + + +定义 `RT_DEBUG` 宏则开启 debug 模式,默认关闭。若开启系统调试,则可以打印系统 LOG 日志。 + +```c +//#define RT_DEBUG // 关闭 debug + +#define RT_DEBUG_INIT 0 // 启用组件初始化调试配置,设置为 1 则会打印自动初始化的函数名称 + +//#define RT_USING_OVERFLOW_CHECK // 关闭栈溢出检查 +``` +## 钩子函数配置 + +设置是否使用钩子函数,默认关闭。 + +```c +//#define RT_USING_HOOK // 是否 开启系统钩子功能 + +//#define RT_USING_IDLE_HOOK // 是否 开启空闲线程钩子功能 +``` +## 软件定时器配置 + +设置是否启用软件定时器,以及相关参数的配置,默认关闭。 + +```c +#define RT_USING_TIMER_SOFT 0 // 关闭软件定时器功能,为 1 则打开 +#if RT_USING_TIMER_SOFT == 0 +#undef RT_USING_TIMER_SOFT +#endif + +#define RT_TIMER_THREAD_PRIO 4 // 设置软件定时器线程的优先级,默认为 4 + +#define RT_TIMER_THREAD_STACK_SIZE 512 // 设置软件定时器线程的栈大小,默认为 512 字节 +``` +## IPC 配置 + +系统支持的 IPC 有:信号量、互斥量、事件集、邮箱、消息队列。通过定义相应的宏打开或关闭该 IPC 的使用。 + +```c +#define RT_USING_SEMAPHORE // 设置是否使用 信号量 + +//#define RT_USING_MUTEX // 设置是否使用 互斥量 + +//#define RT_USING_EVENT // 设置是否使用 事件集 + +#define RT_USING_MAILBOX // 设置是否使用 邮箱 + +//#define RT_USING_MESSAGEQUEUE // 设置是否使用 消息队列 +``` +## 内存配置 + +RT-Thread 内存管理包含:内存池、内存堆、小内存算法。通过开启相应的宏定义使用相应的功能。 + +```c +//#define RT_USING_MEMPOOL // 是否使用 内存池 + +//#define RT_USING_HEAP // 是否使用 内存堆 + +#define RT_USING_SMALL_MEM // 是否使用 小内存管理 + +//#define RT_USING_TINY_SIZE // 是否使用 小体积的算法,牵扯到 rt_memset、rt_memcpy 所产生的体积 +``` +## FinSH 控制台配置 + +定义 RT_USING_CONSOLE 则开启控制台功能,失能该宏则关闭控制台,不能实现打印;修改 RT_CONSOLEBUF_SIZE 可配置控制台缓冲大小。 + +```c +#define RT_USING_CONSOLE // 控制台宏开关 +#define RT_CONSOLEBUF_SIZE 128 // 设置控制台数据 buf 大小,默认 128 byte +``` +FinSH 组件的使用通过定义 RT_USING_FINSH 开启,开启后可对 FinSH 组件相关的参数进行配置修改,FINSH_THREAD_STACK_SIZE 的值默认较小,请根据实际情况改大。 +```c +#if defined (RT_USING_FINSH) // 开关 FinSH 组件 + + #define FINSH_USING_MSH // 使用 FinSH 组件 MSH 模式 + #define FINSH_USING_MSH_ONLY // 仅使用 MSH 模式 + + #define __FINSH_THREAD_PRIORITY 5 // 设置 FinSH 组件优先级,配置该值后通过下面的公式进行计算 + #define FINSH_THREAD_PRIORITY (RT_THREAD_PRIORITY_MAX / 8 * __FINSH_THREAD_PRIORITY + 1) + + #define FINSH_THREAD_STACK_SIZE 512 // 设置 FinSH 线程栈大小,范围 1-4096 + + #define FINSH_HISTORY_LINES 1 // 设置 FinSH 组件记录历史命令个数,值范围 1-32 + + #define FINSH_USING_SYMTAB // 使用符号表,需要打开,默认打开 + +#endif +``` + +## 常见问题 + +Q:移植完成之后出现 hard fault。 + +A:在默认情况下,系统配置的各种线程栈大小均较小,若不能正常运行,很有可能是栈不够用,可将栈值调大。例如 main 线程栈大小默认为 256,在实际使用时,main 中可能加入其它代码导致栈不够用的情况;FinSH 组件的线程 tshell,默认栈 512 也比较小,在使用时可以调大。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/an0041-nano-port-cube.md b/rt-thread-version/rt-thread-nano/nano-port-cube/an0041-nano-port-cube.md new file mode 100644 index 0000000000000000000000000000000000000000..9f316ef1b8fb70d446be87fb0ee101495d3d434a --- /dev/null +++ b/rt-thread-version/rt-thread-nano/nano-port-cube/an0041-nano-port-cube.md @@ -0,0 +1,189 @@ +# 基于 CubeMX 移植 RT-Thread Nano + +本文介绍了如何基于 CubeMX 移植 RT-Thread Nano,并说明生成代码工程的步骤。 + +RT-Thread Nano 已集成在 CubeMX 中,可以直接在 IDE 中进行下载添加。本文档介绍了如何使用 CubeMX 移植 RT-Thread Nano,并以一个 stm32f103 的基础工程作为示例进行讲解。 + +移植 Nano 的主要步骤: + +1. 准备一个 CubeMX 基础工程,并获取 RT-Thread Nano pack 安装包进行安装。 +2. 在基础工程中添加 RT-Thread Nano 源码。 +3. 适配 Nano,主要从 中断、时钟、内存、应用 这几个方面进行适配,实现移植。 +4. 最后可对 Nano 进行配置:Nano 是可裁剪的,可以通过配置文件 rtconfig.h 实现对系统的裁剪。 + +## 准备工作 + +- 下载 Cube MX 5.0 ,下载地址 。 +- 在 CubeMX 上下载 RT-Thread Nano pack 安装包。 + +### Nano pack 安装 + +要获取 RT-Thread Nano 软件包,需要在 CubeMX 中添加 。 + +具体步骤:进入打开 CubeMX,从菜单栏 `help` 进入 `Manage embedded software packages` 界面,点击 `From Url` 按钮,进入 `User Defined Packs Manager` 界面,其次点击 `new`,填入上述网址,然后点击 `check`,如下图所示: + +![完成安装](figures/from_url.png) + +`check` 通过后,点击 OK 回到 `User Defined Packs Manager` 界面,再次点击 OK,CubeMX 自动连接服务器,获取包描述文件。回到 `Manage embedded software packages` 界面,就会发现 `RT-Thread Nano 3.1.3` 软件包,选择该软件包,点击 `Install Now`,如下图所示: + +![选择版本](figures/exisit_pack.png) + +点击安装之后,弹出 `Licensing Agreement` ,同意协议,点击 `Finish`,如下图所示: + +![选择版本](figures/finish.png) + +等待安装完成,成功安装后,版本前面的小蓝色框变成填充的黄绿色,现象如下图所示: + +![完成安装](figures/installed_url.png) + +至此,RT-Thread Nano 软件包安装完毕,退出 `Manage embedded software packages` 界面,进入 CubeMX 主界面。 + +### 创建基础工程 + +在 CubeMX 主界面的菜单栏中 `File` 选择 `New Project`,如下图所示 + +![创建新工程](figures/new_project.png) + +新建工程之后,在弹出界面芯片型号中输入某一芯片型号,方便锁定查找需要的芯片,双击被选中的芯片,如下图所示 + +![创建新工程](figures/mcu_choice.png) + +时钟树的配置直接使用默认即可,然后还需要配置下载方式。 + +## 添加 RT-Thread Nano 到工程 + +### 选择 Nano 组件 + +选中芯片型号之后,点击 `Additional Softwares`,进入 `Additional Software Components selection` 界面,在 `Pack Vendor` 中选择 `RealThread`, 然后根据需求选择 RT-Thread 组件(此处只移植 Nano,只选择 kernel 即可),然后点击 OK 按钮,如下图所示: + +![选择软件包](figures/pack_choice.png) + +> 注意:RT-Thread Nano 软件包中包含 kernel 与 shell 两个部分,仅选择 kernel 表示只使用 RT-Thread 内核,工程中会添加内核代码;选择 kernel 与 shell 表示在使用 RT-Thread Nano 的基础上使用 FinSH Shell 组件,工程中会添加内核代码与 FinSH 组件的代码,FinSH 的移植详见 [《在 RT-Thread Nano 上添加控制台与 FinSH》](../finsh-port/an0045-finsh-port.md)。 + +### 配置 Nano + +选择组件之后,对组件参数进行配置。在工程界面 `Pinout & Configuration` 中,进入所选组件参数配置区,按照下图进行配置 + +![配置 rt-thread](figures/pack_config.png) + +### 工程管理 + +给工程取名、选择代码存放位置、选择生成代码的 `Toolchain/IDE`。`Cube MX` 不仅能够生成 `Keil4/Keil5` 的工程,而且还能够生成 `IAR7/IAR8` 等 IDE 的工程,功能强大,本文从下拉框中选择 MDK5,操作如图所示 + +![工程管理](figures/project_manager.png) + +### 配置 MCU + +根据需求配置 MCU 的功能。 + +## 适配 RT-Thread Nano + +### 中断与异常处理 + +RT-Thread 操作系统重定义 `HardFault_Handler`、`PendSV_Handler`、`SysTick_Handler` 中断函数,为了避免重复定义的问题,在生成工程之前,需要在中断配置中,代码生成的选项中,取消选择三个中断函数(对应注释选项是 `Hard fault interrupt`, `Pendable request`, `Time base :System tick timer`),最后点击生成代码,具体操作如下图中步骤 11-15 所示: + +![中断配置](figures/nvic_config.png) + +等待工程生成完毕,点击打开工程,如下图所示,即可进入 MDK5 工程中。 + +![打开工程](figures/open_project.png) + +### 系统时钟配置 + +需要在 board.c 中实现 ` 系统时钟配置 `(为 MCU、外设提供工作时钟)与 `OS Tick 的配置 `(为操作系统提供心跳 / 节拍)。 + +如下代码所示, `HAL_Init()` 初始化 HAL 库, `SystemClock_Config() `配置了系统时钟, `SystemCoreClockUpdate()` 对系统时钟进行更新,`_SysTick_Config()` 配置了 OS Tick。此处 OS Tick 使用滴答定时器 systick 实现,需要用户在 board.c 中实现 `SysTick_Handler()` 中断服务例程,调用 RT-Thread 提供的 `rt_tick_increase()` ,如下图所示。 + +```c +/* board.c */ +void rt_hw_board_init() +{ + HAL_Init(); + SystemClock_Config(); + + /* System Clock Update */ + SystemCoreClockUpdate(); + + /* System Tick Configuration */ + _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); + + /* Call components board initial (use INIT_BOARD_EXPORT()) */ +#ifdef RT_USING_COMPONENTS_INIT + rt_components_board_init(); +#endif + +#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) + rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get()); +#endif +} +``` + +![OS Tick 的实现](figures/systick.png) + +### 内存堆初始化 + +系统内存堆的初始化在 board.c 中的 rt_hw_board_init() 函数中完成,内存堆功能是否使用取决于宏 RT_USING_HEAP 是否开启,RT-Thread Nano 默认不开启内存堆功能,这样可以保持一个较小的体积,不用为内存堆开辟空间。 + +开启系统 heap 将可以使用动态内存功能,如使用 rt_malloc、rt_free 以及各种系统动态创建对象的 API。若需要使用系统内存堆功能,则打开 RT_USING_HEAP 宏定义即可,此时内存堆初始化函数 rt_system_heap_init() 将被调用,如下所示: + +![系统 heap 初始化](figures/heap1.png) + +初始化内存堆需要堆的起始地址与结束地址这两个参数,系统中默认使用数组作为 heap,并获取了 heap 的起始地址与结束地址,该数组大小可手动更改,如下所示: + +![默认 heap 的实现](figures/6-2.png) + +注意:开启 heap 动态内存功能后,heap 默认值较小,在使用的时候需要改大,否则可能会有申请内存失败或者创建线程失败的情况,修改方法有以下两种: + +- 可以直接修改数组中定义的 RT_HEAP_SIZE 的大小,至少大于各个动态申请内存大小之和,但要小于芯片 RAM 总大小。 +- 也可以参考[《RT-Thread Nano 移植原理》——实现动态内存堆](../nano-port-principle/an0044-nano-port-principle.md) 章节进行修改,使用 RAM ZI 段结尾处作为 HEAP 的起始地址,使用 RAM 的结尾地址作为 HEAP 的结尾地址,这是 heap 能设置的最大值的方法。 + +## 编写第一个应用 + +移植好 RT-Thread Nano 之后,则可以开始编写第一个应用代码。此时 main() 函数就转变成 RT-Thread 操作系统的一个线程,现在可以在 main() 函数中实现第一个应用:板载 LED 指示灯闪烁。 + +1. 首先在文件首部包含 RT-Thread 的相关头文件 `` 。 +2. 在 main() 函数中(也就是在 main 线程中)写 LED 闪烁代码:初始化 LED 引脚、在循环中点亮 / 熄灭 LED。 +3. 延时函数使用 RT-Thread 提供的延时函数 rt_thread_mdelay(),该函数会引起系统调度,切换到其他线程运行,体现了线程实时性的特点。 + +![RT-THREAD main](figures/rtos-main.png) + +编译程序之后下载到芯片就可以看到基于 RT-Thread 的程序运行起来了,LED 正常闪烁。 + +> [!NOTE] +> 注:当添加 RT-Thread 之后,裸机中的 main() 函数会自动变成 RT-Thread 系统中 main 线程 的入口函数。由于线程不能一直独占 CPU,所以此时在 main() 中使用 while(1) 时,需要有让出 CPU 的动作,比如使用 `rt_thread_mdelay()` 系列的函数让出 CPU。 + +**与裸机 LED 闪烁应用代码的不同**: + +1). 延时函数不同: RT-Thread 提供的 `rt_thread_mdelay()` 函数可以引起操作系统进行调度,当调用该函数进行延时时,本线程将不占用 CPU,调度器切换到系统的其他线程开始运行。而裸机的 delay 函数是一直占用 CPU 运行的。 + +2). 初始化系统时钟的位置不同:移植好 RT-Thread Nano 之后,不需要再在 main() 中做相应的系统配置(如 hal 初始化、时钟初始化等),这是因为 RT-Thread 在系统启动时,已经做好了系统时钟初始化等的配置,这在上一小节 “系统时钟配置” 中有讲解。 + +## 配置 RT-Thread Nano + +配置 RT-Thread Nano 可以在上面小节 ` 添加 RT-Thread Nano -> 配置 Nano` 中,这是在生成工程之前做的配置。如果生成工程之后,想直接在目标工程的 IDE 中配置,那么直接修改工程中 rtconfig.h 文件即可,完整配置详见 [《 RT-Thread Nano 配置》](../nano-config/an0043-nano-config.md)。 + +## 常见问题 + +### Q: 出现三个中断重复定义 + +**A**: 参考生成工程章节中 **中断配置** 小节。 + +### Q: 生成的工程不包含 RT-Thread + +**A**: 可能是没有添加 RT-Thread Nano 组件到工程,参考生成工程章节中 **选择 Nano 组件** 小节。 + +### Q: 在添加 Nano 时,选择 shell 后生成工程,编译工程报错。 + +**A**: 报错 "Undefined symbol rt_hw_console_getchar (referrred from shell.o)"。这是由于添加 FinSH 组件源码之后,还需要自行定义与实现该函数才能完成 FinSH 的移植,详见 [《在 RT-Thread Nano 上添加控制台与 FinSH》](../finsh-port/an0045-finsh-port.md)。 + +### Q: 生成的工程不包含 .S 文件 + +**A**: 生成的工程中发现 `context_rvds.S`, `context_iar.S` 或者其他文件存在丢失的情况,建议重新使用 CubeMX 生成工程。 + +### Q: check 网址失败 + +**A**: 建议升级 Cube MX 版本至 5.0。 + +### Q: CubeMX 如何升级 + +**A**:本文创建工程基于 CubeMX 5.0.0,如果比较低的版本,建议升级,升级方式: `help -> Check for updates`,进入后,点击 Refresh,CubeMX 自动去获取最新的程序,成功获取后选择版本,点击 `Install now`,等待完成安装。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/3-5.jpg b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/3-5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..62a6ec89c58212b4e8ff80ef5b099387f3830043 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/3-5.jpg differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/4-1.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/4-1.png new file mode 100644 index 0000000000000000000000000000000000000000..eba9746543da0527a1b0d270705f498bf24e0480 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/4-1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/6-2.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/6-2.png new file mode 100644 index 0000000000000000000000000000000000000000..9be4c302e3ae1a748a5dfad858db0016c26f89eb Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/6-2.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/exisit_pack.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/exisit_pack.png new file mode 100644 index 0000000000000000000000000000000000000000..a6e53960a94f759d11beb57d5e3dc5a9c359bc73 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/exisit_pack.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/finish.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/finish.png new file mode 100644 index 0000000000000000000000000000000000000000..7eae30ed16775866dbc5e0a256433215169139a5 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/finish.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/from_url.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/from_url.png new file mode 100644 index 0000000000000000000000000000000000000000..a7cd9595c2310d24e59c665eb4a7e3f97164109b Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/from_url.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/heap1.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/heap1.png new file mode 100644 index 0000000000000000000000000000000000000000..88280781fcacf92aff963064215cca73895eced7 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/heap1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/installed_url.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/installed_url.png new file mode 100644 index 0000000000000000000000000000000000000000..d2a9de0c1989a63ee79191d994420791f8841bb5 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/installed_url.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/mcu_choice.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/mcu_choice.png new file mode 100644 index 0000000000000000000000000000000000000000..8d34dc6fc2bda439c5f77a80ae13ecf71b77339f Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/mcu_choice.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/new_project.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/new_project.png new file mode 100644 index 0000000000000000000000000000000000000000..f43ed4c535d886ccb43159e97be2dffe00208307 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/new_project.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/nvic_config.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/nvic_config.png new file mode 100644 index 0000000000000000000000000000000000000000..b1b208968d9488fcb2aedf435b5f55a402b10adb Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/nvic_config.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/open_project.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/open_project.png new file mode 100644 index 0000000000000000000000000000000000000000..1e89a7b8c1a6607828c62a5d2f80a4f78400e8bf Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/open_project.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/pack_choice.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/pack_choice.png new file mode 100644 index 0000000000000000000000000000000000000000..42b62596b4c044d1bfb489aaf5b9f90b78bf8439 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/pack_choice.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/pack_config.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/pack_config.png new file mode 100644 index 0000000000000000000000000000000000000000..7d1aa5c4d4093e8952eb401013ea59353fdcf4c3 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/pack_config.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/project_manager.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/project_manager.png new file mode 100644 index 0000000000000000000000000000000000000000..2c9957e4c641e0f93485d9019d816cc06264aecb Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/project_manager.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/rtos-main.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/rtos-main.png new file mode 100644 index 0000000000000000000000000000000000000000..150edbf261e76c9327200e52d0aa6d530c99fbdd Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/rtos-main.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-cube/figures/systick.png b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/systick.png new file mode 100644 index 0000000000000000000000000000000000000000..b456a1a9d9d204e7861653517286306335e29e4a Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-cube/figures/systick.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/an0042-nano-port-gcc-riscv.md b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/an0042-nano-port-gcc-riscv.md new file mode 100644 index 0000000000000000000000000000000000000000..6b9064032522ca52545a2fa37d4b105f914a0654 --- /dev/null +++ b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/an0042-nano-port-gcc-riscv.md @@ -0,0 +1,201 @@ +# 移植 RT-Thread Nano 到 RISC-V + +本文介绍了如何移植 RT-Thread Nano 到 RISC-V 架构,以 Eclipse GCC 环境为例,基于一个 GD32V103 MCU 的基础工程作为示例进行讲解。 + +移植 Nano 的主要步骤: + +1. 准备一个基础的 Eclipse 工程,并获取 RT-Thread Nano 源码压缩包。 +2. 在基础工程中添加 RT-Thread Nano 源码,添加相应头文件路径。 +3. 适配 Nano,主要从 中断、时钟、内存、应用 这几个方面进行适配,实现移植。 +4. 最后可对 Nano 进行配置:Nano 是可裁剪的,通过配置文件 rtconfig.h 实现对系统的裁剪。 + +## 准备工作 + +- 下载 RT-Thread Nano 发布版本代码。 +- 准备一份基础的裸机源码工程,如 LED 指示灯闪烁示例代码。 + +### 下载 Nano 源码 + +[点击此处](https://www.rt-thread.org/download/nano/rt-thread-3.1.3.zip) 下载 RT-Thread Nano 源码。 + +### 基础工程准备 + +在移植 RT-Thread Nano 之前,我们需要准备一个能正常运行的裸机工程。作为示例,本文使用的是基于 GD32V103 的一个 LED 闪烁程序。程序的主要截图如下: + +![裸机示例代码](figures/mcu-main.png) + +在我们的例程中主要做了系统初始化与 LED 闪烁功能,编译下载程序后,就可以看到开发板上的 LED 在闪烁了。读者可以根据自己的需要使用的芯片,完成一个类似的裸机工程。 + +## 添加 RT-Thread Nano 到工程 + +### 添加 Nano 源文件 + +在准备好的 Eclipse 工程下面新建 rtthread 文件夹,并在该文件中添加以下文件: + +- Nano 源码中的 include、libcpu、src 文件夹。注意 libcpu 仅保留与该芯片架构相关的文件,如示例中使用的是 `bunblebee` 与 `common`。 +- 配置文件:示例代码 rtthread-nano/bsp 中的两个文件:`board.c` 与 `rtconfig.h`。 + +![给裸机工程添加 Nano 源码以及必要的配置文件](figures/add-nano.png) + +重新打开 eclipse 工作空间,导入工程,rtthread 已经加载到工程中: + +![eclipse 工程](figures/add-nano-project.png) + +Cortex-M 芯片内核移植代码: + +``` +context_gcc.s +cpuport.c +``` + +Kernel 文件包括: + +``` +clock.c +components.c +device.c +idle.c +ipc.c +irq.c +kservice.c +mem.c +object.c +scheduler.c +thread.c +timer.c +``` + +板级配置代码及配置文件: + +``` +board.c +rtconfig.h +``` + +### 添加头文件路径 + +右击工程,点击 `properties` 进入下图所示界面,点击 ` C/C++ Build` -> `settings` ,分别添加汇编与 C 的头文件路径:添加 rtconfig.h 头文件所在位置的路径,添加 include 文件夹下的头文件路径。然后点击 ` C/C++ General` -> `Path and Symbols` ,添加相应的头文件,最后点击应用即可。 + +![添加头文件路径](figures/path.png) + +## 适配 RT-Thread Nano + +### 修改 start.S + +修改启动文件,实现 RT-Thread 的启动:由于 RT-Thread Nano 在 GCC 环境下的启动是由 entrry() 函数调用了启动函数 rt_thread_startup(),所以需要修改启动文件 start.S,使其在启动时先跳转至 entry() 函数执行,而不是跳转至 main(),这样就实现了 RT-Thread 的启动。 + +```c +/* RT-Thread 在 GCC 下的启动方式 */ +int entry(void) +{ + rtthread_startup(); + return 0; +} +``` + +![修改 start.S](figures/startS.png) + +### 中断与异常处理 + +RT-Thread 提供中断管理方法,当系统没有实现类似中断向量表的功能,物理中断要和用户的中断服务例程相关联,就需要使用中断管理接口对中断进行管理,这样当发生中断时就可以触发相应的中断,执行中断服务例程。 + +本例程中的 gd32f103 芯片在启动文件中提供了中断向量表,用户可直接使用中断向量提供的函数实现对应 IRQ。当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理,不需要再自行实现中断管理。 + +### 系统时钟配置 + +需要在 board.c 中实现 ` 系统时钟配置 `(为 MCU、外设提供工作时钟)与 `OS Tick 的配置 `(为操作系统提供心跳 / 节拍)。 + +配置示例如下图所示,`riscv_clock_init()` 配置了系统时钟,`ostick_config()` 配置了 OS Tick。 + +![系统时钟与 OS Tick 配置](figures/clock-config.png) + +`riscv_clock_init()` 配置了系统时钟,示例如下: + +![riscv_clock_init](figures/clock.png) + +`ostick_config()` 配置了 OS Tick,示例如下,此处 OS Tick 使用硬件定时器实现,需要用户在 board.c 中实现该硬件定时器的中断服务例程 `eclic_mtip_handler()` ,调用 RT-Thread 提供的 `rt_tick_increase()` 。 + +![OS Tick 初始化配置](figures/ostick-config.png) + +![OS Tick 的实现:硬件定时器 ISR 实现](figures/systick.png) + +由于 `eclic_mtip_handler()` 中断服务例程由用户在 board.c 中重新实现,做了系统 OS Tick,所以需要将自定义的 `eclic_mtip_handler` 删除,避免在编译时产生重复定义。如果此时对工程进行编译,没有出现函数重复定义的错误,则不用做修改。 + +### 内存堆初始化 + +系统内存堆的初始化在 board.c 中的 rt_hw_board_init() 函数中完成,内存堆功能是否使用取决于宏 RT_USING_HEAP 是否开启,RT-Thread Nano 默认不开启内存堆功能,这样可以保持一个较小的体积,不用为内存堆开辟空间。 + +开启系统 heap 将可以使用动态内存功能,如使用 rt_malloc、rt_free 以及各种系统动态创建对象的 API。若需要使用系统内存堆功能,则打开 RT_USING_HEAP 宏定义即可,此时内存堆初始化函数 rt_system_heap_init() 将被调用,如下所示: + +![系统 heap 初始化](figures/heap1.png) + +初始化内存堆需要堆的起始地址与结束地址这两个参数,系统中默认使用数组作为 heap,并获取了 heap 的起始地址与结束地址,该数组大小可手动更改,如下所示: + +![默认 heap 的实现](figures/heap-def.png) + +注意:开启 heap 动态内存功能后,heap 默认值较小,在使用的时候需要改大,否则可能会有申请内存失败或者创建线程失败的情况,修改方法有以下两种: + +- 可以直接修改数组中定义的 RT_HEAP_SIZE 的大小,至少大于各个动态申请内存大小之和,但要小于芯片 RAM 总大小。 +- 也可以参考[《RT-Thread Nano 移植原理》——实现动态内存堆](../nano-port-principle/an0044-nano-port-principle.md) 章节进行修改,使用 RAM ZI 段结尾处作为 HEAP 的起始地址,使用 RAM 的结尾地址作为 HEAP 的结尾地址,这是 heap 能设置的最大值的方法。 + +## 编写第一个应用 + +移植好 RT-Thread Nano 之后,则可以开始编写第一个应用代码。此时 main() 函数就转变成 RT-Thread 操作系统的一个线程,现在可以在 main() 函数中实现第一个应用:板载 LED 指示灯闪烁,这里直接基于裸机 LED 指示灯进行修改。 + +1. 首先在文件首部增加 RT-Thread 的相关头文件 `` 。 +2. 在 main() 函数中(也就是在 main 线程中)实现 LED 闪烁代码:初始化 LED 引脚、在循环中点亮 / 熄灭 LED。 +3. 将延时函数替换为 RT-Thread 提供的延时函数 rt_thread_mdelay()。该函数会引起系统调度,切换到其他线程运行,体现了线程实时性的特点。 + +![RT-Thread 第一个应用](figures/gcc-rtos-main.png) + +编译程序之后下载到芯片就可以看到基于 RT-Thread 的程序运行起来了,LED 正常闪烁。 + +> [!NOTE] +> 注:当添加 RT-Thread 之后,裸机中的 main() 函数会自动变成 RT-Thread 系统中 main 线程 的入口函数。由于线程不能一直独占 CPU,所以此时在 main() 中使用 while(1) 时,需要有让出 CPU 的动作,比如使用 `rt_thread_mdelay()` 系列的函数让出 CPU。 + +**与裸机 LED 闪烁应用代码的不同**: + +1). 延时函数不同: RT-Thread 提供的 `rt_thread_mdelay()` 函数可以引起操作系统进行调度,当调用该函数进行延时时,本线程将不占用 CPU,调度器切换到系统的其他线程开始运行。而裸机的 delay 函数是一直占用 CPU 运行的。 + +2). 初始化系统时钟的位置不同:移植好 RT-Thread Nano 之后,不需要再在 main() 中做相应的系统配置(如 hal 初始化、时钟初始化等),这是因为 RT-Thread 在系统启动时,已经做好了系统时钟初始化等的配置,这在上一小节 “系统时钟配置” 中有讲解。 + +## 配置 RT-Thread Nano ## + +用户可以根据自己的需要通过打开或关闭 rtconfig.h 文件里面的宏定义,配置相应功能,如下是 rtconfig.h 的代码片段: + +```c +... + +// IPC(Inter-process communication) Configuration +// Using Semaphore +// Using Semaphore +#define RT_USING_SEMAPHORE +// +// Using Mutex +// Using Mutex +//#define RT_USING_MUTEX // 打开此宏则使能互斥量的使用 +// +// Using Event +// Using Event +//#define RT_USING_EVENT // 打开此宏则使能事件集的使用 +// +// Using MailBox +// Using MailBox +//#define RT_USING_MAILBOX // 打开此宏则使能邮箱的使用 +// +// Using Message Queue +// Using Message Queue +//#define RT_USING_MESSAGEQUEUE // 打开此宏则使能消息队列的使用 +// +// + +// Memory Management Configuration +// Using Memory Pool Management +// Using Memory Pool Management +//#define RT_USING_MEMPOOL // 打开此宏则使能内存池的使用 + +... +``` + +RT-Thread Nano 默认未开启宏 RT_USING_HEAP,故只支持静态方式创建任务及信号量。若要通过动态方式创建对象则需要在 rtconfig.h 文件里开启 RT_USING_HEAP 宏定义。完整配置详见 [《 RT-Thread Nano 配置》](../nano-config/an0043-nano-config.md)。 + diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano-proj.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano-proj.png new file mode 100644 index 0000000000000000000000000000000000000000..2e5b7f550a31839acaddba4c930157ce9a7ab9fb Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano-proj.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano-project.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano-project.png new file mode 100644 index 0000000000000000000000000000000000000000..da880ed91eff04f9833f7459654ef64272835717 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano-project.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano.png new file mode 100644 index 0000000000000000000000000000000000000000..306df599c525977ea14e436d01a1068296a1d6ba Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/add-nano.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/clock-config.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/clock-config.png new file mode 100644 index 0000000000000000000000000000000000000000..9c78ed9bac412cff8ae206421384631d82a29739 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/clock-config.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/clock.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/clock.png new file mode 100644 index 0000000000000000000000000000000000000000..17fbf73a86b979097a08f9bcd3b1742a895821ce Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/clock.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/gcc-rtos-main.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/gcc-rtos-main.png new file mode 100644 index 0000000000000000000000000000000000000000..56d52a6721f2a3b5bb957ffeec2381253e700a05 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/gcc-rtos-main.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/heap-def.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/heap-def.png new file mode 100644 index 0000000000000000000000000000000000000000..aa2892a3057600a3be0c66be5a4fdd50a0c45fd5 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/heap-def.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/heap1.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/heap1.png new file mode 100644 index 0000000000000000000000000000000000000000..c540188a7dec0b5b756f76a6a4f899d2fb6bf3bd Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/heap1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/mcu-main.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/mcu-main.png new file mode 100644 index 0000000000000000000000000000000000000000..f5f42eb9479945f2da43bbac2366e2f1b779c635 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/mcu-main.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/ostick-config.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/ostick-config.png new file mode 100644 index 0000000000000000000000000000000000000000..39c0513a56407fa860e20500e0972bdde35a5d1d Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/ostick-config.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/path.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/path.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f9214bd4f7764f19f56e8aafd324c01c4d7bcd Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/path.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/startS.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/startS.png new file mode 100644 index 0000000000000000000000000000000000000000..476f74dd18ac2d8c9ad9a06bee7f191c9a7dd2e5 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/startS.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/systick.png b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/systick.png new file mode 100644 index 0000000000000000000000000000000000000000..d814b51f896fea16f1bb1a0687d59ad13f810141 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-gcc-riscv/figures/systick.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/an0040-nano-port-iar.md b/rt-thread-version/rt-thread-nano/nano-port-iar/an0040-nano-port-iar.md new file mode 100644 index 0000000000000000000000000000000000000000..7921007252365adf12742755da6cf1b09e90b345 --- /dev/null +++ b/rt-thread-version/rt-thread-nano/nano-port-iar/an0040-nano-port-iar.md @@ -0,0 +1,201 @@ +# 基于 IAR 移植 RT-Thread Nano + +本文介绍了如何基于 IAR 移植 RT-Thread Nano,并以一个 stm32f103 的基础工程作为示例进行讲解。 + +移植 Nano 的主要步骤: + +1. 准备一个基础的 IAR 工程,并获取 RT-Thread Nano 压缩包源码。 +2. 在基础工程中添加 RT-Thread Nano 源码,添加相应头文件路径。 +3. 适配 Nano,主要从 中断、时钟、内存、应用 这几个方面进行适配,实现移植。 +4. 最后可对 Nano 进行配置:Nano 是可裁剪的,通过配置文件 rtconfig.h 实现对系统的裁剪。 + +## 准备工作 + +- 下载 RT-Thread Nano 发布版本代码。 +- 准备一份基础的裸机源码工程,如 LED 指示灯闪烁示例代码。 + +### 下载 Nano 源码 + +[点击此处](https://www.rt-thread.org/download/nano/rt-thread-3.1.3.zip) 下载 RT-Thread Nano 源码。 + +### 基础工程准备 ### + +在移植 RT-Thread Nano 之前,我们需要准备一个能正常运行的裸机工程。作为示例,本文使用的是基于 STM32F103 的一个 LED 闪烁程序。程序的主要截图如下: + +![裸机示例代码](figures/sample.png) + +在我们的例程中主要做了系统初始化与 LED 闪烁功能,编译下载程序后,就可以看到开发板上的 LED 在闪烁了。读者可以根据自己的需要使用的芯片,准备一个类似的裸机工程。 + +## 添加 RT-Thread Nano 到工程 ## + +### 添加 Nano 源文件 + +在准备好的 IAR 裸机工程下面新建 rtthread 文件夹,并在该文件中添加以下文件: + +- Nano 源码中的 include、libcpu、src 文件夹。 +- 配置文件:源码代码 rtthread/bsp 文件夹中的两个文件:`board.c` 与 `rtconfig.h`。 + +![给裸机工程添加 Nano 源码以及必要的配置文件](figures/nano-port-src.png) + +双击打开 IAR 裸机工程,新建 rtthread 分组,并在该分组下添加以下源码: + +- 添加工程下 rtthread/src/ 文件夹中所有文件到工程; +- 添加工程下 rtthread/libcpu/ 文件夹中相应内核的 CPU 移植文件及上下文切换文件: `cpuport.c` 以及 `context_iar.S`; +- 添加 rtthread/ 文件夹下的 `board.c` 。 + +![nano 源码与配置文件](figures/add-nano.png) + +Cortex-M 芯片内核移植代码: + +``` +context_iar.s +cpuport.c +``` + +Kernel 文件包括: + +``` +clock.c +components.c +device.c +idle.c +ipc.c +irq.c +kservice.c +mem.c +object.c +scheduler.c +thread.c +timer.c +``` + +板级配置代码: + +``` +board.c +``` + +### 添加头文件路径 + +点击 `Project -> Options... ` 进入下图所示界面,添加 rtconfig.h 头文件所在位置的路径,添加 include 文件夹下的头文件路径。 + +![添加头文件路径](figures/h-file.png) + +## 适配 RT-Thread Nano + +### 中断与异常处理 + +RT-Thread 会接管异常处理函数 `HardFault_Handler()` 和悬挂处理函数 `PendSV_Handler()`,这两个函数已由 RT-Thread 实现,所以需要删除工程里中断服务例程文件中的这两个函数,避免在编译时产生重复定义。如果此时对工程进行编译,没有出现函数重复定义的错误,则不用做修改。 + +### 系统时钟配置 + +需要在 board.c 中实现 ` 系统时钟配置 `(为 MCU、外设提供工作时钟)与 `OS Tick 的配置 `(为操作系统提供心跳 / 节拍)。 + +如下代码所示, `HAL_Init()` 初始化 HAL 库, `SystemClock_Config() `配置了系统时钟, `SystemCoreClockUpdate()` 对系统时钟进行更新,`_SysTick_Config()` 配置了 OS Tick。此处 OS Tick 使用滴答定时器 systick 实现,需要用户在 board.c 中实现 `SysTick_Handler()` 中断服务例程,调用 RT-Thread 提供的 `rt_tick_increase()` ,如下图所示。 + +```c +/* board.c */ +void rt_hw_board_init() +{ + HAL_Init(); + SystemClock_Config(); + + /* System Clock Update */ + SystemCoreClockUpdate(); + + /* System Tick Configuration */ + _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); + + /* Call components board initial (use INIT_BOARD_EXPORT()) */ +#ifdef RT_USING_COMPONENTS_INIT + rt_components_board_init(); +#endif + +#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) + rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get()); +#endif +} +``` + +![OS Tick 的实现](figures/systick.png) + +由于 `SysTick_Handler()` 中断服务例程由用户在 board.c 中重新实现,做了系统 OS Tick,所以还需要删除工程里中断服务例程文件中的 `SysTick_Handler()` ,避免在编译时产生重复定义。如果此时对工程进行编译,没有出现函数重复定义的错误,则不用做修改。 + +### 内存堆初始化 + +系统内存堆的初始化在 board.c 中的 rt_hw_board_init() 函数中完成,内存堆功能是否使用取决于宏 RT_USING_HEAP 是否开启,RT-Thread Nano 默认不开启内存堆功能,这样可以保持一个较小的体积,不用为内存堆开辟空间。 + +开启系统 heap 将可以使用动态内存功能,如使用 rt_malloc、rt_free 以及各种系统动态创建对象的 API。若需要使用系统内存堆功能,则打开 RT_USING_HEAP 宏定义即可,此时内存堆初始化函数 rt_system_heap_init() 将被调用,如下所示: + +![系统 heap 初始化](figures/heap1.png) + +初始化内存堆需要堆的起始地址与结束地址这两个参数,系统中默认使用数组作为 heap,并获取了 heap 的起始地址与结束地址,该数组大小可手动更改,如下所示: + +![默认 HEAP 的实现](figures/heap.png) + +注意:开启 heap 动态内存功能后,heap 默认值较小,在使用的时候需要改大,否则可能会有申请内存失败或者创建线程失败的情况,修改方法有以下两种: + +- 可以直接修改数组中定义的 RT_HEAP_SIZE 的大小,至少大于各个动态申请内存大小之和,但要小于芯片 RAM 总大小。 +- 也可以参考[《RT-Thread Nano 移植原理》——实现动态内存堆](../nano-port-principle/an0044-nano-port-principle.md) 章节进行修改,使用 RAM ZI 段结尾处作为 HEAP 的起始地址,使用 RAM 的结尾地址作为 HEAP 的结尾地址,这是 heap 能设置的最大值的方法。 + +## 编写第一个应用 + +移植好 RT-Thread Nano 之后,则可以开始编写第一个应用代码。此时 main() 函数就转变成 RT-Thread 操作系统的一个线程,现在可以在 main() 函数中实现第一个应用:板载 LED 指示灯闪烁,这里直接基于裸机 LED 指示灯进行修改。 + +1. 首先在文件首部增加 RT-Thread 的相关头文件 `` 。 +2. 在 main() 函数中(也就是在 main 线程中)实现 LED 闪烁代码:初始化 LED 引脚、在循环中点亮 / 熄灭 LED。 +3. 将延时函数替换为 RT-Thread 提供的延时函数 rt_thread_mdelay()。该函数会引起系统调度,切换到其他线程运行,体现了线程实时性的特点。 + +![rt-thread main](figures/rtos-main.png) + +编译程序之后下载到芯片就可以看到基于 RT-Thread 的程序运行起来了,LED 正常闪烁。 + +> [!NOTE] +> 注:当添加 RT-Thread 之后,裸机中的 main() 函数会自动变成 RT-Thread 系统中 main 线程 的入口函数。由于线程不能一直独占 CPU,所以此时在 main() 中使用 while(1) 时,需要有让出 CPU 的动作,比如使用 `rt_thread_mdelay()` 系列的函数让出 CPU。 + +**与裸机 LED 闪烁应用代码的不同**: + +1). 延时函数不同: RT-Thread 提供的 `rt_thread_mdelay()` 函数可以引起操作系统进行调度,当调用该函数进行延时时,本线程将不占用 CPU,调度器切换到系统的其他线程开始运行。而裸机的 delay 函数是一直占用 CPU 运行的。 + +2). 初始化系统时钟的位置不同:移植好 RT-Thread Nano 之后,不需要再在 main() 中做相应的系统配置(如 hal 初始化、时钟初始化等),这是因为 RT-Thread 在系统启动时,已经做好了系统时钟初始化等的配置,这在上一小节 “系统时钟配置” 中有讲解。 + +## 配置 RT-Thread Nano ## + +用户可以根据自己的需要通过打开或关闭 rtconfig.h 文件里面的宏定义,配置相应功能,如下是 rtconfig.h 的代码片段: + +```c +... + +// IPC(Inter-process communication) Configuration +// Using Semaphore +// Using Semaphore +#define RT_USING_SEMAPHORE +// +// Using Mutex +// Using Mutex +//#define RT_USING_MUTEX // 打开此宏则使能互斥量的使用 +// +// Using Event +// Using Event +//#define RT_USING_EVENT // 打开此宏则使能事件集的使用 +// +// Using MailBox +// Using MailBox +//#define RT_USING_MAILBOX // 打开此宏则使能邮箱的使用 +// +// Using Message Queue +// Using Message Queue +//#define RT_USING_MESSAGEQUEUE // 打开此宏则使能消息队列的使用 +// +// + +// Memory Management Configuration +// Using Memory Pool Management +// Using Memory Pool Management +//#define RT_USING_MEMPOOL // 打开此宏则使能内存池的使用 + +... +``` + +RT-Thread Nano 默认未开启宏 RT_USING_HEAP,故只支持静态方式创建任务及信号量。若要通过动态方式创建对象则需要在 rtconfig.h 文件里开启 RT_USING_HEAP 宏定义。完整配置详见 [《 RT-Thread Nano 配置》](../nano-config/an0043-nano-config.md)。 + diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/4-1.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/4-1.png new file mode 100644 index 0000000000000000000000000000000000000000..98e03edf65d7c0bd241bde128059c139213119ef Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/4-1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/add-nano.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/add-nano.png new file mode 100644 index 0000000000000000000000000000000000000000..7a2ff969e3647ca56d284b292e65f8909d0bfa9f Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/add-nano.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/h-file.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/h-file.png new file mode 100644 index 0000000000000000000000000000000000000000..afbf00bbbfdb728fcd3dbbe8d1682c65a5e9587c Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/h-file.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/heap.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/heap.png new file mode 100644 index 0000000000000000000000000000000000000000..683ba6f32aaaf3d07133cfa1e4ac8c4a7c187056 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/heap.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/heap1.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/heap1.png new file mode 100644 index 0000000000000000000000000000000000000000..73d604a84b98adbeea9fbfea434d2a453b311049 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/heap1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/main.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/main.png new file mode 100644 index 0000000000000000000000000000000000000000..e97feb89a96dc60e43d882b3e91e4b7ac4e7a5a9 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/main.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/nano-port-src.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/nano-port-src.png new file mode 100644 index 0000000000000000000000000000000000000000..8173ee4c8e3e8094d22c1566895f705fbdbe62d1 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/nano-port-src.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/nano-src.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/nano-src.png new file mode 100644 index 0000000000000000000000000000000000000000..b3399d753b715468570602a39aac0519d535a711 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/nano-src.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/rtos-main.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/rtos-main.png new file mode 100644 index 0000000000000000000000000000000000000000..2caf9e82cb9f9803f9269cf7b8693c3143b017cd Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/rtos-main.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/sample.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/sample.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4c627a7a7c219cfa5a8bfdc521c150c13ff156 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/sample.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-iar/figures/systick.png b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/systick.png new file mode 100644 index 0000000000000000000000000000000000000000..9e53050e38139c03966e78065ca14d480415dd32 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-iar/figures/systick.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/an0039-nano-port-keil.md b/rt-thread-version/rt-thread-nano/nano-port-keil/an0039-nano-port-keil.md new file mode 100644 index 0000000000000000000000000000000000000000..61ecdf0968dfbe5c47547e87420177c189e1e90a --- /dev/null +++ b/rt-thread-version/rt-thread-nano/nano-port-keil/an0039-nano-port-keil.md @@ -0,0 +1,220 @@ +# 基于 Keil MDK 移植 RT-Thread Nano + +本文介绍如何基于 Keil MDK 移植 RT-Thread Nano ,并以一个 stm32f103 的基础工程作为示例进行讲解。 + +RT-Thread Nano 已集成在 Keil MDK 中,可以直接在 IDE 中进行下载添加。本文档介绍了如何使用 MDK 移植 RT-Thread Nano,并以一个 stm32f103 的基础工程作为示例进行讲解。 + +移植 Nano 的主要步骤: + +1. 准备一个基础的 keil MDK 工程,并获取 RT-Thread Nano pack 安装包并进行安装。 +2. 在基础工程中添加 RT-Thread Nano 源码。 +3. 适配 Nano,主要从 中断、时钟、内存这几个方面进行适配,实现移植。 +4. 验证移植结果:编写第一个应用代码,基于 RT-Thread Nano 闪烁 LED。 +5. 最后可对 Nano 进行配置:Nano 是可裁剪的,通过配置文件 rtconfig.h 实现对系统的裁剪。 + +## 准备工作 + +- 准备一份基础的裸机源码工程,如一份 stm32 的 LED 指示灯闪烁示例代码。 +- 在 KEIL 上安装 RT-Thread Nano Pack。 + +### 基础工程准备 + +在移植 RT-Thread Nano 之前,我们需要准备一个能正常运行的裸机工程。作为示例,本文使用的是基于 STM32F103 的一个 LED 闪烁程序。程序的主要截图如下: + +![STM32 裸机代码示例](figures/project-eg.png) + +在我们的例程中主要做了系统初始化与 LED 闪烁功能,编译下载程序后,就可以看到 LED 闪烁了。读者可以根据自己的需要使用的芯片,准备一个类似的裸机工程。 + +### Nano Pack 安装 + +Nano Pack 可以通过在 Keil MDK IDE 内进行安装,也可以手动安装。下面开始介绍两种安装方式。 + +#### 方法一:在 IDE 内安装 + +打开 MDK 软件,点击工具栏的 Pack Installer 图标: + +![Packs 安装](figures/packs.png) + +点击右侧的 Pack,展开 Generic,可以找到 RealThread::RT-Thread,点击 Action 栏对应的 Install ,就可以在线安装 Nano Pack 了。另外,如果需要安装其他版本,则需要展开 RealThread::RT-Thread,进行选择。 + +![Packs 管理](figures/packs-install.png) + +#### 方法二:手动安装 + +我们也可以从官网下载安装文件,[RT-Thread Nano 离线安装包下载](https://download.rt-thread.org/download/mdk/RealThread.RT-Thread.3.1.3.pack),下载结束后双击文件进行安装: + +![Packs 手动安装](figures/packs-install-man.png) + + + +## 添加 RT-Thread Nano 到工程 ## + +打开已经准备好的可以运行的裸机程序,将 RT-Thread 添加到工程。如下图,点击 Manage Run-Time Environment。 + +![MDK RTE](figures/3-1.png) + +在 Manage Rum-Time Environment 里 "Software Component" 栏找到 RTOS,Variant 栏选择 RT-Thread,然后勾选 kernel,点击 "OK" 就添加 RT-Thread 内核到工程了。 + +![添加 Nano 内核](figures/3-2.png) + +现在可以在 Project 看到 RT-Thread RTOS 已经添加进来了,展开 RTOS,可以看到添加到工程的文件: + +![添加了 RTOS 的工程](figures/rtos2.jpg) + +Cortex-M 芯片内核移植代码: + +``` +context_rvds.s +cpuport.c +``` + +Kernel 文件包括: + +``` +clock.c +components.c +device.c +idle.c +ipc.c +irq.c +kservice.c +mem.c +object.c +scheduler.c +thread.c +timer.c +``` + +配置文件: + +``` +board.c +rtconfig.h +``` + +## 适配 RT-Thread Nano + +### 中断与异常处理 + +RT-Thread 会接管异常处理函数 `HardFault_Handler()` 和悬挂处理函数 `PendSV_Handler()`,这两个函数已由 RT-Thread 实现,所以需要删除工程里中断服务例程文件中的这两个函数,避免在编译时产生重复定义。如果此时对工程进行编译,没有出现函数重复定义的错误,则不用做修改。 + +### 系统时钟配置 + +需要在 board.c 中实现 ` 系统时钟配置 `(为 MCU、外设提供工作时钟)与 `os tick 的配置 `(为操作系统提供心跳 / 节拍)。 + +如下代码所示, `HAL_Init()` 初始化 HAL 库, `SystemClock_Config() `配置了系统时钟, `SystemCoreClockUpdate()` 对系统时钟进行更新,`_SysTick_Config()` 配置了 OS Tick。此处 OS Tick 使用滴答定时器 systick 实现,需要用户在 board.c 中实现 `SysTick_Handler()` 中断服务例程,调用 RT-Thread 提供的 `rt_tick_increase()` ,如下图所示。 + +```c +/* board.c */ +void rt_hw_board_init() +{ + HAL_Init(); + SystemClock_Config(); + + /* System Clock Update */ + SystemCoreClockUpdate(); + + /* System Tick Configuration */ + _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); + + /* Call components board initial (use INIT_BOARD_EXPORT()) */ +#ifdef RT_USING_COMPONENTS_INIT + rt_components_board_init(); +#endif + +#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) + rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get()); +#endif +} +``` + +![OS Tick 的实现](figures/systick.png) + +由于 `SysTick_Handler()` 中断服务例程由用户在 board.c 中重新实现,做了系统 OS Tick,所以还需要删除工程里中原本已经实现的 `SysTick_Handler()` ,避免在编译时产生重复定义。如果此时对工程进行编译,没有出现函数重复定义的错误,则不用做修改。 + +### 内存堆初始化 + +系统内存堆的初始化在 board.c 中的 rt_hw_board_init() 函数中完成,内存堆功能是否使用取决于宏 RT_USING_HEAP 是否开启,RT-Thread Nano 默认不开启内存堆功能,这样可以保持一个较小的体积,不用为内存堆开辟空间。 + +开启系统 heap 将可以使用动态内存功能,如使用 rt_malloc、rt_free 以及各种系统动态创建对象的 API。若需要使用系统内存堆功能,则打开 RT_USING_HEAP 宏定义即可,此时内存堆初始化函数 rt_system_heap_init() 将被调用,如下所示: + +![系统 heap 初始化](figures/heap1.png) + +初始化内存堆需要堆的起始地址与结束地址这两个参数,系统中默认使用数组作为 heap,并获取了 heap 的起始地址与结束地址,该数组大小可手动更改,如下所示: + +![默认 heap 的实现](figures/6-2.png) + +注意:开启 heap 动态内存功能后,heap 默认值较小,在使用的时候需要改大,否则可能会有申请内存失败或者创建线程失败的情况,修改方法有以下两种: + +- 可以直接修改数组中定义的 RT_HEAP_SIZE 的大小,至少大于各个动态申请内存大小之和,但要小于芯片 RAM 总大小。 +- 也可以参考[《RT-Thread Nano 移植原理》——实现动态内存堆](../nano-port-principle/an0044-nano-port-principle.md) 章节进行修改,使用 RAM ZI 段结尾处作为 HEAP 的起始地址,使用 RAM 的结尾地址作为 HEAP 的结尾地址,这是 heap 能设置的最大值的方法。 + +## 编写第一个应用 + +移植好 RT-Thread Nano 之后,则可以开始编写第一个应用代码验证移植结果。此时 main() 函数就转变成 RT-Thread 操作系统的一个线程,现在可以在 main() 函数中实现第一个应用:板载 LED 指示灯闪烁,这里直接基于裸机 LED 指示灯进行修改。 + +1. 首先在文件首部增加 RT-Thread 的相关头文件 `` 。 +2. 在 main() 函数中(也就是在 main 线程中)实现 LED 闪烁代码:初始化 LED 引脚、在循环中点亮 / 熄灭 LED。 +3. 将延时函数替换为 RT-Thread 提供的延时函数 rt_thread_mdelay()。该函数会引起系统调度,切换到其他线程运行,体现了线程实时性的特点。 + +![RT-THREAD main](figures/rtos-main.png) + +编译程序之后下载到芯片就可以看到基于 RT-Thread 的程序运行起来了,LED 正常闪烁。 + +> [!NOTE] +> 注:当添加 RT-Thread 之后,裸机中的 main() 函数会自动变成 RT-Thread 系统中 main 线程 的入口函数。由于线程不能一直独占 CPU,所以此时在 main() 中使用 while(1) 时,需要有让出 CPU 的动作,比如使用 `rt_thread_mdelay()` 系列的函数让出 CPU。 + +**与裸机 LED 闪烁应用代码的不同**: + +1). 延时函数不同: RT-Thread 提供的 `rt_thread_mdelay()` 函数可以引起操作系统进行调度,当调用该函数进行延时时,本线程将不占用 CPU,调度器切换到系统的其他线程开始运行。而裸机的 delay 函数是一直占用 CPU 运行的。 + +2). 初始化系统时钟的位置不同:移植好 RT-Thread Nano 之后,不需要再在 main() 中做相应的系统配置(如 hal 初始化、时钟初始化等),这是因为 RT-Thread 在系统启动时,已经做好了系统时钟初始化等的配置,这在上一小节 “系统时钟配置” 中有讲解。 + +## 配置 RT-Thread Nano ## + +用户可以根据自己的需要通过修改 rtconfig.h 文件里面的宏定义配置相应功能。 + +RT-Thread Nano 默认未开启宏 RT_USING_HEAP,故只支持静态方式创建任务及信号量。若要通过动态方式创建对象则需要在 rtconfig.h 文件里开启 RT_USING_HEAP 宏定义。 + +MDK 的配置向导 configuration Wizard 可以很方便的对工程进行配置,Value 一栏可以选中对应功能及修改相关值,等同于直接修改配置文件 rtconfig.h。更多细节配置详见 [《 RT-Thread Nano 配置》](../nano-config/an0043-nano-config.md)。 + +![Nano 配置](figures/rtconfig.png) + +## 获取示例代码 + +Keil MDK 中集成的 RT-Thread Nano 软件包附带示例代码,如果需要参照示例代码,则可以在 Keil 中打开相应的示例代码工程。 + +首先点击 Pack Installer,进入下图所示界面: + +![在 keil 中打开示例代码](figures/keil-samples.png) + +右侧界面切换到 Examples,然后在左侧界面搜索 Device 或者 Boards,点击搜索出的芯片或者开发板,会显示与其相关的所有示例代码,同时可以看到 RT-Thread 的示例代码也在其中,点击 Copy,选择一个路径,然后点击 OK 即可打开示例代码工程。 + + + +## 常见问题 + +### Q: 如何升级 pack? + +**A**: Pack 升级步骤基本如同软件包,展开 RealThread::RT-Thread 后,选择比较新的 Nano 版本,点击 Install 进行安装。如下图所示,点击红色框中的 Install 进行升级,即可将 3.1.2 版本升级到 3.1.3。 + +> 需要注意的是,若多个版本同时安装,则最终向工程添加 Nano 时,只能选择高版本进行添加。 + +![Nano 升级](figures/pack-upgrade.png) + +### Q: 在安装 pack 后,未找到可选的 RT-Thread pack。 + +**A**: 点击下图中的下拉小三角即可找到 RT-Thread,选择 RT-Thread 即可。 + +![1576492587873](figures/keil_pack.png) + +### Q: 在添加 Nano 时,选择 shell 后,编译报错。 + +**A**: 报错 "Undefined symbol rt_hw_console_getchar (referrred from shell.o)"。这是由于添加 FinSH 组件源码之后,还需要自行定义与实现该函数才能完成 FinSH 的移植,详见 [《在 RT-Thread Nano 上添加控制台与 FinSH》](../finsh-port/an0045-finsh-port.md)。 + + + + + + + diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-1.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-1.png new file mode 100644 index 0000000000000000000000000000000000000000..bb65fa0a614c71c33d2c8cc2c640fd39c92bb861 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-2.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-2.png new file mode 100644 index 0000000000000000000000000000000000000000..3c420dbd0a9049f61800b969535d86a2f7de272a Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-2.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-3.jpg b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bc05debb4971d1be2920d65f3b2dcb813f640f70 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-3.jpg differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-5.jpg b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..62a6ec89c58212b4e8ff80ef5b099387f3830043 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/3-5.jpg differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/4-1.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/4-1.png new file mode 100644 index 0000000000000000000000000000000000000000..eba9746543da0527a1b0d270705f498bf24e0480 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/4-1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/6-2.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/6-2.png new file mode 100644 index 0000000000000000000000000000000000000000..9be4c302e3ae1a748a5dfad858db0016c26f89eb Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/6-2.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/heap1.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/heap1.png new file mode 100644 index 0000000000000000000000000000000000000000..88280781fcacf92aff963064215cca73895eced7 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/heap1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/keil-samples.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/keil-samples.png new file mode 100644 index 0000000000000000000000000000000000000000..0f93aef21b8c0bc73a793ba941f9228afacf7f27 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/keil-samples.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/keil_pack.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/keil_pack.png new file mode 100644 index 0000000000000000000000000000000000000000..03b903fc426fdf1ca9e16547056c12bc059a5dd0 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/keil_pack.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/pack-upgrade.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/pack-upgrade.png new file mode 100644 index 0000000000000000000000000000000000000000..7886d8116de67f7686af3aeffb825d4acb655bd1 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/pack-upgrade.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs-install-man.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs-install-man.png new file mode 100644 index 0000000000000000000000000000000000000000..25118bae6baaca440e887d81ee2852bb5765c6f3 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs-install-man.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs-install.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs-install.png new file mode 100644 index 0000000000000000000000000000000000000000..279bd93ae0d9edfa7487915ed8b18f71ead7d412 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs-install.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs.png new file mode 100644 index 0000000000000000000000000000000000000000..d287c52c1446e1eae44659b4bf8049e6a8f6d07b Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/packs.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/project-eg.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/project-eg.png new file mode 100644 index 0000000000000000000000000000000000000000..79753375a7627ba4e5c7be9425081e51891b99b4 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/project-eg.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtconfig.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..25f7523d0e5b6c7b2b62d045012ab86b3cce1cf8 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtconfig.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtos-main.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtos-main.png new file mode 100644 index 0000000000000000000000000000000000000000..150edbf261e76c9327200e52d0aa6d530c99fbdd Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtos-main.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtos2.jpg b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtos2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa603a6530b714f002f3f53af29b435fd46eed0a Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/rtos2.jpg differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-keil/figures/systick.png b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/systick.png new file mode 100644 index 0000000000000000000000000000000000000000..b456a1a9d9d204e7861653517286306335e29e4a Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-keil/figures/systick.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-principle/an0044-nano-port-principle.md b/rt-thread-version/rt-thread-nano/nano-port-principle/an0044-nano-port-principle.md new file mode 100644 index 0000000000000000000000000000000000000000..8a55b25526fa3e74dcd206db46c4ad3b61b6adc2 --- /dev/null +++ b/rt-thread-version/rt-thread-nano/nano-port-principle/an0044-nano-port-principle.md @@ -0,0 +1,315 @@ +# RT-Thread Nano 移植原理 + +本片文档介绍 Nano 移植原理,针对的是不同 MCU 的移植,如 Cortex M,RISC-V,或者是其他 MCU 的移植。移植过程主要分为两个部分:libcpu 移植与板级移植,在讲解移植之前,本文档对 RT-Thread Nano 的启动流程与移植目录结构先进行说明。 + +## 启动流程 + +RT-Thread 启动流程如下所示,在图中标出颜色的部分需要用户特别注意(黄色表示 libcpu 移植相关的内容,绿色部分表示板级移植相关的内容)。 + +![RT-Thread 启动流程](figures/startup1.png) + +RT-Thread 启动代码统一入口为 `rtthread_startup() ` ,芯片启动文件在完成必要工作(如初始化时钟、配置中断向量表、初始化堆栈等)后,最终会在程序跳转时,跳转至 RT-Thread 的启动入口中。RT-Thread 的启动流程如下: + +1. 全局关中断,初始化与系统相关的硬件。 +2. 打印系统版本信息,初始化系统内核对象(如定时器、调度器)。 +3. 初始化用户 main 线程(同时会初始化线程栈),在 main 线程中对各类模块依次进行初始化。 +4. 初始化软件定时器线程、初始化空闲线程。 +5. 启动调度器,系统切换到第一个线程开始运行(如 main 线程),并打开全局中断。 + +## 移植目录结构 + +在 `rtthread-nano` 源码中,与移植相关的文件位于下图中有颜色标记的路径下(黄色表示 libcpu 移植相关的文件,绿色部分表示板级移植相关的文件): + +![与移植相关的文件](figures/nano-file.png) + +## libcpu 移植 + +RT-Thread 的 libcpu 抽象层向下提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容,RT-Thread 支持的 cpu 架构在源码的 `libcpu` 文件夹下。 + +### 启动文件 startup.s + +启动文件由芯片厂商提供,位于芯片固件库中。每款芯片都有相对应的启动文件,在不同开发环境下启动文件也不相同。当系统加入 RT-Thread 之后,会将 RT-Thread 的启动放在调用 main() 函数之前,如下图所示: + +![RT-Thread 启动](figures/mcu-rtt-start.png) + +startup.s:主要完成初始化时钟、配置中断向量表;完成全局 / 静态变量的初始化工作;初始化堆栈;库函数的初始化;程序的跳转等内容。 + +程序跳转:芯片在 KEIL MDK 与 IAR 下的启动文件不用做修改,会自动转到 RT-Thread 系统启动函数 `rtthread_startup()` 。GCC 下的启动文件需要修改,让其跳转到 RT-Thread 提供的 entry() 函数,其中 entry() 函数调用了 RT-Thread 系统启动函数 `rtthread_startup()`。 + +**举例**: stm32 在 GCC 开发环境下的启动文件,修改 GCC 启动文件,使其跳转到 entry 函数。以下是启动文件的代码片段: + +```c +//修改前: + bl SystemInit + bl main + +//修改后: + bl SystemInit + bl entry /* 修改此处,由 main 改为 entry */ +``` + +RT-Thread 在 entry 函数中实现了 GCC 环境下的 RT-Thread 启动: + +```c +int entry(void) +{ + rtthread_startup(); + return 0; +} +``` + +最终调用 main() 函数进入用户 main()。 + +### 上下文切换 context_xx.s + +上下文切换表示 CPU 从一个线程切换到另一个线程、或者线程与中断之间的切换等。在上下文切换过程中,CPU 一般会停止处理当前运行的代码,并保存当前程序运行的具体位置以便之后继续运行。 + +在该文件中除了实现上下文切换的函数外,还需完成全局开关中断函数,详见编程指南 [《内核移植》 - CPU 架构移植](../../../programming-manual/porting/porting/) 章节中的 “实现全局开关中断 ” 小节与 “实现上下文切换” 小节。 + +| 需实现的函数 | 描述 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| rt_base_t rt_hw_interrupt_disable(void); | 关闭全局中断 | +| void rt_hw_interrupt_enable(rt_base_t level); | 打开全局中断 | +| void rt_hw_context_switch_to(rt_uint32 to); | 没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用 | +| void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); | 从 from 线程切换到 to 线程,用于线程和线程之间的切换 | +| void rt_hw_context_switch_interrupt(rt_uint32 from, rt_uint32 to); | 从 from 线程切换到 to 线程,用于中断里面进行切换的时候使用 | + +注意:在 Cortex-M 中,PendSV 中断处理函数是 PendSV_Handler(),线程切换的实际工作在 PendSV_Handler() 里完成。 + +### 线程栈初始化 cpuport.c + +在 RT-Thread 中,线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。 + +故障异常处理函数 rt_hw_hard_fault_exception(),在发生硬件错误时,执行 HardFault_Handler 中断,会执行该函数。 + +该文件中主要实现线程栈的初始化 rt_hw_stack_init() 与 hard fault 异常处理函数,线程栈初始化函数的参数以及实现的步骤详见编程指南 [《内核移植》 - CPU 架构移植](../../../programming-manual/porting/porting/) 章节中的 ”实现线程栈初始化“ 小节。 + +| 需实现的函数 | 描述 | +| ---------------------------- | ---------------------- | +| rt_hw_stack_init() | 实现线程栈的初始化 | +| rt_hw_hard_fault_exception() | 异常函数:系统硬件错误 | + +### 中断与异常挂接 interrupt.c + +> [!NOTE] +> 注:注意:在 Cortex-M 内核上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理,不需要再自行实现中断管理。 + +在一些非 Cortex-M 架构中,系统没有实现类似中断向量表的功能,物理中断要和用户的中断服务例程相关联,就需要使用中断管理接口对中断进行管理,这样当发生中断时就可以触发相应的中断,执行中断服务例程。 + +详见编程指南 [《中断管理》](../../../programming-manual/interrupt/interrupt.md) 章节。 + +| 需实现的中断管理接口 | 描述 | +| ------------------------- | ------------------ | +| rt_hw_interrupt_init() | 硬件中断初始化 | +| rt_hw_interrupt_install() | 中断服务程序挂接 | +| rt_hw_interrupt_mask() | 屏蔽指定的中断源 | +| rt_hw_interrupt_umask() | 打开被屏蔽的中断源 | + +## 板级移植 board.c + +> [!NOTE] +> 注:board.c、rtconfig.h 是与硬件 / 板级相关的文件,在移植时需自行实现。Cortex M 架构可参考 Nano 源码 bsp 文件夹中已有的的 board.c、rtconfig.h 。 + +板级移植主要是针对 `rt_hw_board_init()` 函数内容的实现,该函数在板级配置文件 board.c 中,函数中做了许多系统启动必要的工作,其中包含: + +1. 配置系统时钟。 +2. 实现 OS 节拍。 +3. 初始化外设:如 GPIO/UART 等等。 +4. 初始化系统内存堆,实现动态堆内存管理。 +5. 板级自动初始化,使用 INIT_BOARD_EXPORT() 自动初始化的函数会在此处被初始化。 +6. 其他必要的初始化,如 MMU 配置(需要时请自行在 rt_hw_board_init 函数中调用应用函数实现)。 + +```c +/* board.c */ +void rt_hw_board_init(void) +{ + /* System Clock Update */ + SystemCoreClockUpdate(); + + /* System Tick Configuration */ + _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); + +#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) + rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get()); +#endif + + /* Call components board initial (use INIT_BOARD_EXPORT()) */ +#ifdef RT_USING_COMPONENTS_INIT + rt_components_board_init(); +#endif +} +``` + +### 配置系统时钟 + +系统时钟是给各个硬件模块提供工作时钟的基础,一般在 `rt_hw_board_init()` 函数中完成,可以调用库函数实现配置,也可以自行实现。 + +如下是 stm32 配置系统时钟调用示例(调用库函数 SystemCoreClockUpdate()): + +```c +/* board.c */ +void rt_hw_board_init() +{ + SystemCoreClockUpdate(); // 在无库函数使用时,一般使用 rt_hw_clock_init() 配置,函数名不做要求,函数自行实现 + ... +} +``` + +### 实现 OS 节拍 + +OS 节拍也叫时钟节拍或 OS tick。任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件。 + +**时钟节拍的实现**:通过硬件 timer 实现周期性中断,在定时器中断中调用 `rt_tick_increase()` 函数实现全局变量 rt_tick 自加,从而实现时钟节拍。一般地,在 Cortex M 上直接使用内部的滴答定时器 Systick 实现。 + +**示例**:如下是 stm32 配置 OS 节拍示例,在初始化时钟节拍后,直接在 SysTick_Handler() 中断服务例程中调用 `rt_tick_increase()`。 + +```c +/* board.c */ +void rt_hw_board_init() +{ + ... + _SysTick_Config(SystemCoreClock / RT_TICK_PER_SECOND); // 使用 SysTick 实现时钟节拍 + ... +} + +/* 中断服务例程 */ +void SysTick_Handler(void) +{ + /* enter interrupt */ + rt_interrupt_enter(); + + rt_tick_increase(); + + /* leave interrupt */ + rt_interrupt_leave(); +} +``` + +对于使用了 RT-Thread 中断管理的 CPU 架构,中断服务例程需要通过 `rt_hw_interrupt_install()` 进行装载(关于中断及其装载,详见本文档的” 中断管理 “ 小节),如下示例: + +```c +/* board.c */ +void rt_hw_board_init() +{ + ... + rt_hw_timer_init(); // 使用 硬件定时器 实现时钟节拍,一般命名为 rt_hw_timer_init() + ... +} + +int rt_hw_timer_init(void) // 函数自行实现,并需要装载中断服务例程 +{ + ... + rt_hw_interrupt_install(IRQ_PBA8_TIMER2_3, rt_hw_timer_isr, RT_NULL, "tick"); + rt_hw_interrupt_umask(IRQ_PBA8_TIMER2_3); +} + +/* 中断服务例程 */ +static void rt_hw_timer_isr(int vector, void *param) +{ + rt_interrupt_enter(); + rt_tick_increase(); + rt_interrupt_leave(); +} +``` + +> [!NOTE] +> 注:在初始化时钟节拍的时候,会用到宏 `RT_TICK_PER_SECOND`。通过修改该宏的值,可以修改系统中一个时钟节拍的时间长度。 + +### 硬件外设初始化 + +硬件初始化,如 UART 初始化等(对接控制台),需要在 rt_hw_board_init() 函数中手动调用 UART 初始化函数。 + +```c +/* board.c */ +void rt_hw_board_init(void) +{ + .... + uart_init(); + .... +} +``` + +### 实现动态内存堆 + +RT-Thread Nano 默认不开启动态内存堆功能,开启 RT_USING_HEAP 将可以使用动态内存功能,即可以使用 rt_malloc、rt_free 以及各种系统动态创建对象的 API。动态内存堆管理功能的初始化是通过 rt_system_heap_init() 函数完成的,动态内存堆的初始化需要指定堆内存的起始地址和结束地址,函数原型如下: + +```c +void rt_system_heap_init(void *begin_addr, void *end_addr) +``` + +开启 RT_USING_HEAP 后,系统默认使用数组作为 heap,heap 的起始地址与结束地址作为参数传入 heap 初始化函数,heap 初始化函数 rt_system_heap_init() 将在 rt_hw_board_init() 中被调用。 + +开启 heap 后,系统中默认使用数组作为 heap(heap 默认较小,实际使用时请根据芯片 RAM 情况改大),获得的 heap 的起始地址与结束地址,作为参数传入 heap 初始化函数: + +```c +#define RT_HEAP_SIZE 1024 +static uint32_t rt_heap[RT_HEAP_SIZE]; +RT_WEAK void *rt_heap_begin_get(void) +{ + return rt_heap; +} + +RT_WEAK void *rt_heap_end_get(void) +{ + return rt_heap + RT_HEAP_SIZE; +} + +void rt_hw_board_init(void) +{ + .... +#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) + rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get()); //传入 heap 的起始地址与结束地址 +#endif + .... +} +``` + +如果不想使用数组作为动态内存堆,则可以重新指定系统 HEAP 的大小,例如使用 RAM ZI 段结尾处作为 HEAP 的起始地址(这里需检查与链接脚本是否对应),使用 RAM 的结尾地址作为 HEAP 的结尾地址,这样可以将空余RAM 全部作为动态内存 heap 使用。如下示例重新定义了 HEAP 的起始地址与结尾地址,并作为初始化参数进行系统 HEAP 初始化。 + +```c +#define STM32_SRAM1_START (0x20000000) +#define STM32_SRAM1_END (STM32_SRAM1_START + 20 * 1024) // 结束地址 = 0x20000000(基址) + 20K(RAM大小) + +#if defined(__CC_ARM) || defined(__CLANG_ARM) +extern int Image$$RW_IRAM1$$ZI$$Limit; // RW_IRAM1,需与链接脚本中运行时域名相对应 +#define HEAP_BEGIN ((void *)&Image$$RW_IRAM1$$ZI$$Limit) +#endif + +#define HEAP_END STM32_SRAM1_END +``` + +```c +void rt_hw_board_init(void) +{ + .... +#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP) + rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END); +#endif + .... +} +``` + +#### 链接脚本 + +链接脚本,也称分散加载文件,决定在生成 image 文件时如何来分配相关数据的存放基址,如果不指定特定的链接脚本,连接器就会自动采用默认的链接脚本来生成镜像。 + +举例 stm32 在 KEIL MDK 开发环境下的链接脚本文件 xxx.sct: + +```c +LR_IROM1 0x08000000 0x00020000 { ; load region size_region + ER_IROM1 0x08000000 0x00020000 { ; load address = execution address + *.o (RESET, +First) + *(InRoot$$Sections) + .ANY (+RO) + } + RW_IRAM1 0x20000000 0x00005000 { ; RW data + .ANY (+RW +ZI) + } +} +``` + +其中 `RW_IRAM1 0x20000000 0x00005000` 表示定义一个运行时域 RW_IRAM1(默认域名),域基址为 0x20000000,域大小为 0x00005000(即 20K ),对应实际 RAM 大小。`.ANY (+RW +ZI)` 表示加载所有匹配目标文件的可读写数据 RW-Data、清零数据 ZI-Data。所以运行时所占内存的结尾处就是 ZI 段结尾处,可以将 ZI 结尾处之后的内存空间作为系统动态内存堆使用。 + + + + diff --git a/rt-thread-version/rt-thread-nano/nano-port-principle/figures/low_level.png b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/low_level.png new file mode 100644 index 0000000000000000000000000000000000000000..deee294493e2f2fff89da9df01fc2b1e2bc74694 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/low_level.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-principle/figures/mcu-rtt-start.png b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/mcu-rtt-start.png new file mode 100644 index 0000000000000000000000000000000000000000..5150a51259995059940d2d6c0915d93f5f7319e4 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/mcu-rtt-start.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-principle/figures/mcu-start.png b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/mcu-start.png new file mode 100644 index 0000000000000000000000000000000000000000..5a267934989a59c38c8dfacc3ebd990a2a7367b5 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/mcu-start.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-principle/figures/nano-file.png b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/nano-file.png new file mode 100644 index 0000000000000000000000000000000000000000..0cefad7b0a5b1e467013580468be766b4160f981 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/nano-file.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-principle/figures/startup.png b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/startup.png new file mode 100644 index 0000000000000000000000000000000000000000..6e1296038c402789116ca57f7ce9d8c0e7d35a76 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/startup.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-principle/figures/startup1.png b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/startup1.png new file mode 100644 index 0000000000000000000000000000000000000000..6879750f929ebe01a3f858d7cb97e5557857c888 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-principle/figures/startup1.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-studio/an0047-nano-port-studio.md b/rt-thread-version/rt-thread-nano/nano-port-studio/an0047-nano-port-studio.md new file mode 100644 index 0000000000000000000000000000000000000000..14736d99e3d8f5a32470d71db39fc4cb85b1c7e9 --- /dev/null +++ b/rt-thread-version/rt-thread-nano/nano-port-studio/an0047-nano-port-studio.md @@ -0,0 +1,45 @@ +# 在 RT-Thread Studio 上使用 RT-Thread Nano + +本文介绍了如何在 RT-Thread Studio 上使用 RT-Thread Nano,并以创建 stm32f103RB 的 Nano 工程为例。 + +## 准备工作 + +[安装 RT-Thread Studio](https://www.rt-thread.org/page/studio.html)。 + +## 新建 Nano 工程 + +打开 IDE,点击【文件】-【新建】-【RT-Thread 项目】: + +![新建 Nano 项目](figures/creat-proj.png) + +进入新建工程的配置向导: + +![配置](figures/proj-guide.png) + +注:可以通过修改 board.c 的 `SystemClock_Config()` 更改系统时钟。 + +工程创建完毕,连接硬件,可直接进行编译下载,如下所示: + +![编译下载](figures/nano-proj.png) + +由于在创建工程向导中配置了控制台串口号及其引脚号,所以工程中已经实现了 uart 的驱动以及 `rt_hw_console_output()` ,默认可以进行打印。打开串口终端,可以发现在终端中执行了打印。 + +![打印](figures/rt_kprintf.png) + +## 基于 Nano 添加 FinSH + +双击 `RT-Thread Settings` 进入配置,打开组件,勾选 FinSH Shell,保存配置。此操作将把 FinSH 组件的源码加入工程中。 + +其中,`rt_hw_console_getchar()` 已经在 drv_uart.c 中实现,无需再实现对接 FinSH 的代码。 + +![添加 FinSH](figures/finsh-config.png) + +链接硬件,编译下载后,在串口终端中按下 Tab 键,可查看系统中的命令: + +![查看与使用命令](figures/finsh.png) + +## 常见问题 + +### Q:如何修改系统时钟? + +A: 可以通过修改 board.c 的 `SystemClock_Config()` 更改系统时钟。 diff --git a/rt-thread-version/rt-thread-nano/nano-port-studio/figures/config-guide.png b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/config-guide.png new file mode 100644 index 0000000000000000000000000000000000000000..f2e6ef7a83f7551d9e05cc755a4c786c74a0fe6c Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/config-guide.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-studio/figures/creat-proj.png b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/creat-proj.png new file mode 100644 index 0000000000000000000000000000000000000000..8eb1da5696132e803d55601fc3c5301c3d03bc4e Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/creat-proj.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-studio/figures/finsh-config.png b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/finsh-config.png new file mode 100644 index 0000000000000000000000000000000000000000..7d2d76b3854fcd51fcc10612af9dadfbc6a9342c Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/finsh-config.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-studio/figures/finsh.png b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/finsh.png new file mode 100644 index 0000000000000000000000000000000000000000..93e83197e5dd9678b41e01c8d091286611bcd526 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/finsh.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-studio/figures/nano-proj.png b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/nano-proj.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e3fec6ed05350b4d722094fd2b5b0149a19109 Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/nano-proj.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-studio/figures/proj-guide.png b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/proj-guide.png new file mode 100644 index 0000000000000000000000000000000000000000..ea3f31939e50956a194c93de1f8ada45db62960c Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/proj-guide.png differ diff --git a/rt-thread-version/rt-thread-nano/nano-port-studio/figures/rt_kprintf.png b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/rt_kprintf.png new file mode 100644 index 0000000000000000000000000000000000000000..26267374c3c6272018bd6a1eb112bd8d9d40adff Binary files /dev/null and b/rt-thread-version/rt-thread-nano/nano-port-studio/figures/rt_kprintf.png differ diff --git a/rt-thread-version/rt-thread-smart/_navbar.md b/rt-thread-version/rt-thread-smart/_navbar.md new file mode 100644 index 0000000000000000000000000000000000000000..559c01e680e85979b8da20409a893fc5b20f8660 --- /dev/null +++ b/rt-thread-version/rt-thread-smart/_navbar.md @@ -0,0 +1,5 @@ + + + diff --git a/rt-thread-version/rt-thread-smart/_sidebar.md b/rt-thread-version/rt-thread-smart/_sidebar.md new file mode 100644 index 0000000000000000000000000000000000000000..f9c2ca61896b536c04f174a4c7728bb4f0f23ff3 --- /dev/null +++ b/rt-thread-version/rt-thread-smart/_sidebar.md @@ -0,0 +1,6 @@ + + +- RT-Thread Smart版本 + + - [入门指南](/rt-thread-version/rt-thread-smart/rt-smart-quickstart/rt-smart-quickstart.md) + - [架构说明](/rt-thread-version/rt-thread-smart/architecture/architecture.md) diff --git a/rt-thread-version/rt-thread-smart/architecture/architecture.md b/rt-thread-version/rt-thread-smart/architecture/architecture.md new file mode 100644 index 0000000000000000000000000000000000000000..5c10aceec7158a9a9806480c9a6e092d76d0d598 --- /dev/null +++ b/rt-thread-version/rt-thread-smart/architecture/architecture.md @@ -0,0 +1,225 @@ +# RT-Thread Smart 架构说明 + +## RT-Thread Smart 的架构 + +RT-Thread Smart 是基于 RT-Thread 操作系统上的混合操作系统,简称为 rt-smart,它把应用从内核中独立出来,形成独立的用户态应用程序,并具备独立的地址空间(32 位系统上是 4G 的独立地址空间)。 + +以下是 rt-smart 的整体结构框图,在硬件平台的基础上通过 MMU、系统调用的方式把整个系统分成了内核态及用户态。 + +![arch](figures/rt-smart.drawio.png) + +RT-Thread Smart 的核心实现是 lwP,进程管理模块( `rtthread-smart/kernel/components/lwp` )。它包括了如下的几个部分: + +* 面向用户态的系统调用(system call,`lwp_syscall.c/h`); +* 用户态进程管理(`lwp_pid.c/h, lwp.c/h`); +* elf 可执行应用程序加载器; +* 基于 MMU 的虚拟内存管理,地址空间管理; +* 进程间的 channel 通信机制及共享内存机制; + +而在整体操作系统中,内核中还额外的包括了(原 RT-Thread 操作系统上的): + +* 文件系统接口(DFS) - 虚拟文件系统接口; +* BSD socket 接口(SAL/socket) - 抽象套接字; +* 设备驱动框架接口; +* 可选的设备驱动(如 UART,GPIO,IIC 等); + +## 用户态环境 + +用户态应用是一份 elf(Executable Linkable Format)文件,由 GNU GCC 编译链接而产生。在 RT-Thread Smart 中,它被固定加载到虚拟地址 0x100000 处执行。 + +一般来说,应用程序具备自己独立的 3G 地址空间,而高 1G 地址空间则留给了内核。对于一个 32 位芯片来说,典型的 RT-Thread Smart 应用程序及内核空间的内存分布如下图所示: + +![arch](figures/memlayout.drawio.png) + +RT-Thread Smart 的用户态是固定地址方式运行,当需要系统服务时通过系统调用的方式陷入到内核中。用户态应用环境也存在对应的 API 环境: + +* libc,rt-smart 选择的是 [musl libc](https://musl.libc.org)(在内核中目前也是 musl libc)。它提供了常规意义上的 POSIX 接口调用及 C 运行环境; +* 原 RT-Thread API 环境,也称为 RT-Thread CRT 环境。在这套 API 中,具备了原 RT-Thread 的 API 接口,例如 rt_thread_create,rt_malloc 等。一些原 RT-Thread 软件包,应用程序也非常方便的移植到 rt-smart 用户态环境中执行。同样的,因为 RT-Thread 内核中也存在一套 POSIX 环境,所以一些用户态应用也可以经过重新编译的方式,和内核编译在一起,从而在内核中执行。 + +## 基本的 IPC 客户端与服务端 + +IPC 服务(`rtthread-smart/kernel/components/lwp/lwp_ipc.c/h`)是实现用户应用程序和其他服务的桥梁,同时也可以是用户进程与用户进程之间的通信机制、内核与用户进程之间的通信机制。 + +在使用 IPC 服务时,需要先创建出对应的通道(channel),然后在通道上进行数据收发。一个 IPC 通道是一个双向数据传递的软件抽象,数据收发过程包括如下几种操作: + +| 函数名 | 说明 | +| --- | --- | +| rt_channel_send | 向指定的 IPC channel 发送消息,当接收任务从 channel 中取走消息,并处理完毕后返回 | +| rt_channel_send_recv | 向指定的 IPC channel 发送消息,同时等待对端回复相应的消息 | +| rt_channel_notify | 向指定的 IPC channel 发送消息,并且不管后续情况直接返回 | +| rt_channel_recv | 从指定的 IPC channel 接收消息,直到接收到消息然后返回 | + +以下是一份最基本的客户端与服务端,他们之间通过 IPC channel 的方式进行交互。先看 pong 服务端的代码情况,它实现的是一个消息接收,当收到消息数据时,再返回给客户端。 + +```c +#include +/* 使用 IPC 服务,需要包含 lwp.h 头文件 */ +#include +/* 包含 rtthread.h,可以使用原来的 RT-Thread API */ +#include + +int main(int argc, char **argv) +{ + int i; + int channel; + char *str; + int shmid; /* 数据传输的共享内存块 ID */ + struct rt_channel_msg msg_text; + + /* 创建一个名字是 pong 的通道 */ + channel = rt_channel_open("pong", O_CREAT); + if (channel == -1) + { + printf("Error: channel_open: fail to create the IPC channel for pong!\n"); + return -1; + } + /* 输出等待的通道号 */ + printf("\nPong: wait on the IPC channel: %d\n", channel); + + /* 接收 100 次消息,然后退出 */ + for (i = 0; i < 100; i++) + { + /* 从 pong 通道中接收消息,消息会放置在 msg_text 中 */ + rt_channel_recv(channel, &msg_text); + + /* 对应的共享内存 id */ + shmid = (int)msg_text.u.d; + /* 通过共享内存 id,拿到对应的数据块 */ + if (shmid < 0 || !(str = (char *)lwp_shmat(shmid, NULL))) + { + /* 接收错误,恢复错误信息 */ + msg_text.u.d = (void *)-1; + printf("Pong: receive an invalid data.\n"); + rt_channel_reply(channel, &msg_text); + continue; + } + + /* 在终端上输出接收到的字符串 */ + printf("Pong: receive %s\n", str); + /* 脱离数据块,代表本端不再使用这块数据 */ + lwp_shmdt(str); + + /* 准备回复信息 */ + printf("Pong: reply count = %d\n", i); + msg_text.type = RT_CHANNEL_RAW; + msg_text.u.d = (void *)i; + /* 恢复给发送端 */ + rt_channel_reply(channel, &msg_text); + } + + /* 关闭这个通道 */ + rt_channel_close(channel); + + return 0; +} +``` + +以下是 ping 客户端的代码,它会把一段字符串数据发送给 pong 进程。它会先准备这段字符串数据,然后从共享内存中创建出一块共享内存数据块,并把数据复制到共享内存数据块上,然后把共享内存数据 id 通过通道发送给 pong 进程: + +```c +#include +#include + +/* 使用 IPC 服务,需要包含 lwp.h 头文件 */ +#include +#include + +/* + * 因为需要把一段数据发送到接收端,而这段数据是在自己的进程空间中,所以 + * 需要以共享内存的方式,把数据放到共享内存上,然后把对应的 id 发送到接收 + * 端。这个过程中包括了开辟对应的共享内存页,复制数据,并返回对应的共享 + * 内存 id。 + */ +rt_inline int prepare_data(void *data, size_t len) +{ + int shmid; + void *shm_vaddr; + + /* 以当前的任务 ID 来做为共享内存标识的 key */ + size_t key = (size_t) rt_thread_self(); + + /* 创建新的共享内存,并返回 id */ + shmid = lwp_shmget(key, len, 1); + if (shmid == -1) + { + printf("Fail to allocate a shared memory!\n"); + return -1; + } + + /* 通过 id 获得共享内存数据块映射在本进程空间的地址 */ + shm_vaddr = lwp_shmat(shmid, NULL); + if (shm_vaddr == RT_NULL) + { + printf("invalid address!\n"); + lwp_shmrm(shmid); + return -1; + } + + /* 把数据复制到共享内存上 */ + memcpy(shm_vaddr, data, len); + /* 脱离共享内存,表示不再使用它了 */ + lwp_shmdt(shm_vaddr); + + return shmid; +} + +int main(int argc, char **argv) +{ + int i; + int channel; + + /* 用于放置消息的字符串数组 */ + char ping[256] = { 0 }; + size_t len = 0; + + /* 定义发送的消息结构体和收到回复的消息结构体 */ + struct rt_channel_msg ch_msg, ch_msg_ret; + + /* 打开名字是 pong 的数据通道 */ + channel = rt_channel_open("pong", 0); + if (channel == -1) + { + printf("Error: could not find the pong channel!\n"); + return -1; + } + + /* 100 次的经过 IPC 通道发送 ping 消息 */ + for (i = 0; i < 100; i++) + { + printf("\n"); + + /* 初始化通道上要发送的消息结构体 */ + ch_msg.type = RT_CHANNEL_RAW; + snprintf(ping, 255, "count = %d", i); + len = strlen(ping) + 1; + ping[len] = '\0'; + + /* 复制数据到共享内存中,并返回共享内存数据块的 id */ + int shmid = prepare_data(ping, len); + if (shmid < 0) + { + printf("Ping: fail to prepare the ping message.\n"); + continue; + } + /* 把共享内存块 id 赋值到消息结构体上 */ + ch_msg.u.d = (void *)shmid; + + printf("Ping: send %s\n", ping); + /* 发送消息并等待回复 */ + rt_channel_send_recv(channel, &ch_msg, &ch_msg_ret); + printf("Ping: receive the reply %d\n", (int) ch_msg_ret.u.d); + + /* 删除对应的共享内存块 */ + lwp_shmrm(shmid); + } + + /* 关闭通道 */ + rt_channel_close(channel); + + return 0; +} +``` + +整体的消息流图如下所示 + +![IPC 消息图](figures/ipc-seq.png) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-smart/architecture/figures/ipc-seq.png b/rt-thread-version/rt-thread-smart/architecture/figures/ipc-seq.png new file mode 100644 index 0000000000000000000000000000000000000000..0f12f7596b425ad90fe866e759194db514108d08 Binary files /dev/null and b/rt-thread-version/rt-thread-smart/architecture/figures/ipc-seq.png differ diff --git a/rt-thread-version/rt-thread-smart/architecture/figures/memlayout.drawio.png b/rt-thread-version/rt-thread-smart/architecture/figures/memlayout.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..3ef30cd2159be213935418e2de7c030541911e34 Binary files /dev/null and b/rt-thread-version/rt-thread-smart/architecture/figures/memlayout.drawio.png differ diff --git a/rt-thread-version/rt-thread-smart/architecture/figures/rt-smart.drawio.png b/rt-thread-version/rt-thread-smart/architecture/figures/rt-smart.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..922cf5588427f43234c0ea005898e703ebba02d1 Binary files /dev/null and b/rt-thread-version/rt-thread-smart/architecture/figures/rt-smart.drawio.png differ diff --git a/rt-thread-version/rt-thread-smart/architecture/figures/user_signal.drawio.png b/rt-thread-version/rt-thread-smart/architecture/figures/user_signal.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..18c9e312f4dcffc3d17356ed739f19f2680747a9 Binary files /dev/null and b/rt-thread-version/rt-thread-smart/architecture/figures/user_signal.drawio.png differ diff --git a/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/Rapi4.png b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/Rapi4.png new file mode 100644 index 0000000000000000000000000000000000000000..d355c00146bcdf092b5e02fca0c6412649208381 Binary files /dev/null and b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/Rapi4.png differ diff --git a/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/Rapi4B.jpg b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/Rapi4B.jpg new file mode 100644 index 0000000000000000000000000000000000000000..067a4b97a914e50100f18cdb6215d05700ebc85d Binary files /dev/null and b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/Rapi4B.jpg differ diff --git a/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/boot.png b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/boot.png new file mode 100644 index 0000000000000000000000000000000000000000..5ef2839ab0d711e42ce511f8e58f1eeb53b40b4a Binary files /dev/null and b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/boot.png differ diff --git a/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/contents.png b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/contents.png new file mode 100644 index 0000000000000000000000000000000000000000..8234ce0e1a1dfbc8966e09d9379b4aa4bb18fc15 Binary files /dev/null and b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/contents.png differ diff --git a/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/uart.png b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/uart.png new file mode 100644 index 0000000000000000000000000000000000000000..64d5f77aad33de446a7a3ea08007164d4c1f5d9c Binary files /dev/null and b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/figures/uart.png differ diff --git a/rt-thread-version/rt-thread-smart/rt-smart-quickstart/rt-smart-quickstart.md b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/rt-smart-quickstart.md new file mode 100644 index 0000000000000000000000000000000000000000..6164da43efe413e10db94981e52a8e7ca2dae268 --- /dev/null +++ b/rt-thread-version/rt-thread-smart/rt-smart-quickstart/rt-smart-quickstart.md @@ -0,0 +1,256 @@ +# RT-Thread Smart 入门指南 + +RT-Thread Smart(简称 rt-smart)是基于 RT-Thread 操作系统衍生的新分支,面向带 MMU,中高端应用的芯片,例如 ARM Cortex-A 系列芯片,MIPS 芯片,带 MMU 的 RISC-V 芯片等。rt-smart 在 RT-Thread 操作系统的基础上启用独立、完整的进程方式,同时以混合微内核模式执行。 + +rt-smart 软件包是 RT-Thread Smart 的用户体验软件包,可在 QEMU 模拟的 VExpress-A9 机器中或树莓派 4B 开发板上执行。本文档是针对 QEMU、树莓派 4B 开发板上快速上手 rt-smart 入门指南文档。 + +## 软件包说明 +### 下载 rt-smart 软件包 + +下载 [rt-smart.zip](http://117.143.63.254:9012/www/rt-smart/rt-smart-20201125.zip) 软件包,解压后的目录说明如下图所示: + +![contents](figures/contents.png) + +### 准备工具链 + +针对 Linux 和 Windows 环境需要下载对应的 arm-linux-musleabi 工具链,如果是 rt-smart 的软件包,则自带 Linux 环境下的工具链。 + +- Linux 版本工具链下载:[Linux 版本工具链](http://117.143.63.254:9012/www/rt-smart/install_arm-linux-musleabi_for_x86_64-pc-linux-gnu.tar.bz2) + +- Windows 版本工具链下载:[Windows 版本工具链](http://117.143.63.254:9012/www/rt-smart/install_arm-linux-musleabi_for_i686-w64-mingw32.zip) + +请根据自己的开发环境选择对用的工具链下载使用。 + +下载下来后分别解压展开到 `rt-smart/tools/gnu_gcc` 目录下,rt-smart 目录下的 smart-env.bat/sh 设置的环境变量,其中工具链路径都指向到这个目录下。 + +## Linux 下编译并在 QEMU 模拟环境上执行 + +在 Linux 系统下,需要安装一些基本的环境,然后才能编译 rt-smart。本文档是基于 Ubuntu16.04 系统环境操作,其它 Linux 版本类似。 + +### 安装编译环境 + +首先安装编译时需要用到的其他工具,软件包,可以通过如下的一些命令来安装: + +```bash +sudo apt-get update +sudo apt-get install vim scons git bzip2 net-tools +sudo apt-get install python-all python-all-dev +sudo apt-get install qemu-system-arm qemu-system-common qemu-utils +``` + +以上命令会安装一些系统基础工具、python 环境、scons、qemu 工具等。 + +### 编译应用程序 + +```bash +# 进入到 rt-smart 目录 +cd rt-smart + +# 设置对应的环境变量,和原 RT-Thread 相比,多了 RTT_CC_PREFIX 环境变量 +source smart-env.sh + +# 编译用户态程序 +cd userapps +scons + +scons: Reading SConscript files ... +scons: done reading SConscript files. +scons: Building targets ... +CC build/hello/main.o +CC build/ping/main.o +CC build/pong/main.o +CC build/vi/optparse-v1.0.0/optparse.o +CC build/vi/vi.o +CC build/vi/vi_utils.o +CC build/webclient/main.o +LINK root/bin/hello.elf +LINK root/bin/ping.elf +LINK root/bin/pong.elf +LINK root/bin/vi.elf +LINK root/bin/webclient.elf +scons: done building targets. +``` + +编译成功后,`userapps/apps` 下的应用程序会编译成一个个的 elf 可执行文件,并放置于 `userapps/root/bin` 目录下,可以把它转换成 romfs、C 语言数组的方式给 rt-smart 内核使用,这样可以不依赖于其他文件系统就可以直接执行,生成 romfs、C 语言数组可以用如下的命令行: + +```bash +cd userapps +python ../tools/mkromfs.py root ../kernel/bsp/qemu-vexpress-a9/applications/romfs.c +``` + +### 编译 rt-smart 内核 + +```bash +cd rt-smart/kernel/bsp/qemu-vexpress-a9 +scons +... ... +CC build/kernel/src/thread.o +CC build/kernel/src/timer.o +LINK rtthread.elf +arm-linux-musleabi-objcopy -O binary rtthread.elf rtthread.bin +arm-linux-musleabi-size rtthread.elf + text data bss dec hex filename +1219480 40652 122444 1382576 1518b0 rtthread.elf +scons: done building targets. +``` + +如果编译无误,会生成 rtthread.elf 内核文件。 + +### 模拟执行 + +通过 qemu 模拟的 vexpress-a9 开发板来直接运行: + +```bash +cd rt-smart/kernel/bsp/qemu-vexpress-a9 +./qemu-nographic.sh + + \ | / +- RT - Thread Smart Operating System + / | \ 5.0.0 build Nov 15 2020 + 2006 - 2020 Copyright by rt-thread team +lwIP-2.0.2 initialized! +try to allocate fb... | w - 640, h - 480 | done! +fb => 0x61100000 +[I/sal.skt] Socket Abstraction Layer initialize success. +[I/SDIO] SD card capacity 65536 KB. +Dir /mnt mount failed! +hello rt-thread +msh /> +msh />/bin/hello.elf +msh />hello world! +``` + +退出 qemu 的方法: "Ctrl+A, X"。上面我们也运行了这次编译的应用程序 `/bin/hello.elf`,并看到它输出 `hello world!`。 + +在第一次执行时,会输出 `Dir /mnt mount failed!`,这个是因为最开始执行时,qemu 的脚本会生成一个空的 sd.bin 文件做为一个 sd 卡给到 vexpress-a9 使用。所以在第一次执行时,需要对 sd0 进行格式化: + +```bash + \ | / +- RT - Thread Smart Operating System + / | \ 5.0.0 build Nov 24 2020 + 2006 - 2020 Copyright by rt-thread team +lwIP-2.0.2 initialized! +try to allocate fb... | w - 640, h - 480 | done! +fb => 0x61100000 +[I/sal.skt] Socket Abstraction Layer initialize success. +[I/SDIO] SD card capacity 65536 KB. +[I/SDIO] switching card to high speed failed! +Dir /mnt mount failed! +hello rt-thread +msh />mkfs sd0 +msh /> + +# Ctrl + A, X 退出 +QEMU: Terminated + +bernard@fuchsia-NUC8i7BEH:~/workspace/rt-smart/smart/kernel/bsp/qemu-vexpress-a9$ ./qemu-nographic.sh + \ | / +- RT - Thread Smart Operating System + / | \ 5.0.0 build Nov 24 2020 + 2006 - 2020 Copyright by rt-thread team +lwIP-2.0.2 initialized! +try to allocate fb... | w - 640, h - 480 | done! +fb => 0x61100000 +[I/sal.skt] Socket Abstraction Layer initialize success. +[I/SDIO] SD card capacity 65536 KB. +[I/SDIO] switching card to high speed failed! +file system initialization done! +hello rt-thread +msh /> +``` +## Windows 下编译并在树莓派 4B 上执行 + +在 Windows 上同样也可以进行编译,在 qemu 或树莓派 4B 开发板上执行起来,这里主要提及如何在树莓派 4B 上执行的方式。 + +![Rapi4](figures/Rapi4B.jpg) + +### 准备编译环境 + +在 Windows 上编译 rt-smart,可以借助 RT-Thread 的 env 工具,env 工具下载及安装请参考 [这里](https://www.rt-thread.org/page/download.html), 请确保 env 可以正常使用。因为 rt-smart 软件包并不携带 Windows 环境下的工具链,所以务必记得安装前面描述的 准备工具链 章节下载 Windows 工具链并在 `rt-smart\tools\gnu_gcc` 下解压展开。打开 env 的终端窗口,切换到这个 rt-smart 代码包根目录,运行 smart-env.bat,它会设置一定的环境变量,然后整体的 smart 开发环境就可以使用了。 + +```bash +# 进入 rt-smart 目录,设置环境变量 +cd rt-smart +> smart-env.bat +``` + +注:此处运行 smart-env.bat 设置环境,它包括编译器设置,同时它也会设置工具链的前缀,可以在 env 终端下输入以下命令查看返回结果是否生效: + +```bash +# 查看环境变量是否生效 +> set RTT_CC_PREFIX +RTT_CC_PREFIX=arm-linux-musleabi- +``` + +### 编译应用程序 +当要编译应用程序时,使用方式和 Linux 的类似: + +```bash +# 进入 userapps 目录进行编译 +cd rt-smart\userapps +scons +``` + +### 编译 rt-smart 内核 + +```bash +# 进入 raspberry-pi\raspi4-32 目录进行编译 +cd rt-smart\kernel\bsp\raspberry-pi\raspi4-32 +scons +... ... +CC build/kernel/src/signal.o +CC build/kernel/src/thread.o +CC build/kernel/src/timer.o +LINK rtthread.elf +arm-linux-musleabi-objcopy -O binary rtthread.elf kernel7.img +arm-linux-musleabi-size rtthread.elf + text data bss dec hex filename + 710780 40448 64730 815958 c7356 rtthread.elf +scons: done building targets. +``` + +编译无误后,会在当前目录下生成 kernel7.img 文件,这个是树莓派上 32 位的版本。目前 rt-smart 还只支持 32 位系统,所以在树莓派 4B 上是以 32 位模式来执行。 + +### 在树莓派上执行 +#### 准备硬件连接 + +为了在树莓派 4B 上执行,需要准备如下硬件清单,并连接串口线到开发板,连接图示如下: + +- 树莓派 4B +- SD 卡(32GB 或 32GB 以下) +- USB 转 TTL 串口线 +- 网线 +- TYPE-C(用于供电) +- 读卡器(用于把编译好的文件写入到 SD 卡中) + +![uart](figures/uart.png) + +#### 准备 SD 卡上的软件 + +树莓派的加载需要将一些 boot 文件放到 sd 卡中。[rpi4_rt-smart_boot.zip](http://117.143.63.254:9012/www/rt-smart/rpi4_rt-smart_boot.zip) 为树莓派的加载需要的一些 boot 文件,将下载后的文件解压后和 kernel7.img 一起放入空的 SD 卡根目录,如下图所示,其中 bin 文件夹中存放 `userapps\root\bin` 目录下已编译好的可执行 elf 文件。 + +![boot](figures/boot.png) + +打开串口调试助手,插上电源,可以看到程序已经正常的运行起来,进入 bin 目录下即可执行示例程序: + +```bash +heap: 0xc00c9a0a - 0xc40c9a0a +\ | / +- RT - Thread Smart Operating System +/ | \ 5.0.0 build Nov 15 2020 + 2006 - 2020 Copyright by rt-thread team +lwIP-2.0.2 initialized! +version is B1 +bcmgenet: PHY startup ok! +[I/sal.skt] Socket Abstraction Layer initialize success. +[I/SDIO] SD card capacity 31465472 KB. +found part[0], begin: 4194304, size: 256.0MB +found part[1], begin: 272629760, size: 29.772GB +file system initialization done! +hello rt-thread! +msh />/bin/hello.elf +msh />hello world! +``` + + + diff --git a/rt-thread-version/rt-thread-standard/README.md b/rt-thread-version/rt-thread-standard/README.md new file mode 100644 index 0000000000000000000000000000000000000000..18bfa87e2b2c0e51a6ecb3bd862efa1f58215113 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/README.md @@ -0,0 +1,36 @@ +# RT-Thread 简介 + +作为一名 RTOS 的初学者,也许你对 RT-Thread 还比较陌生。然而,随着你的深入接触,你会逐渐发现 RT-Thread 的魅力和它相较于其他同类型 RTOS 的种种优越之处。RT-Thread 是一款完全由国内团队开发维护的嵌入式实时操作系统(RTOS),具有完全的自主知识产权。经过近 15 个年头的沉淀,伴随着物联网的兴起,它正演变成一个功能强大、组件丰富的物联网操作系统。 + +## RT-Thread 概述 + +RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。事实上,一个处理器核心在某一时刻只能运行一个任务,由于每次对一个任务的执行时间很短、任务与任务之间通过任务调度器进行非常快速地切换(调度器根据优先级决定此刻该执行的任务),给人造成多个任务在一个时刻同时运行的错觉。在 RT-Thread 系统中,任务通过线程实现的,RT-Thread 中的线程调度器也就是以上提到的任务调度器。 + +RT-Thread 主要采用 C 语言编写,浅显易懂,方便移植。它把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。针对资源受限的微控制器(MCU)系统,可通过方便易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 版本(NANO 是 RT-Thread 官方于 2017 年 7 月份发布的一个极简版内核);而对于资源丰富的物联网设备,RT-Thread 又能使用在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。 + +相较于 Linux 操作系统,RT-Thread 体积小,成本低,功耗低、启动快速,除此以外 RT-Thread 还具有实时性高、占用资源小等特点,非常适用于各种资源受限(如成本、功耗限制等)的场合。虽然 32 位 MCU 是它的主要运行平台,实际上很多带有 MMU、基于 ARM9、ARM11 甚至 Cortex-A 系列级别 CPU 的应用处理器在特定应用场合也适合使用 RT-Thread。 + +## 许可协议 + +RT-Thread 系统完全开源,3.1.0 及以前的版本遵循 GPL V2 + 开源许可协议。从 3.1.0 以后的版本遵循 Apache License 2.0 开源许可协议,可以免费在商业产品中使用,并且不需要公开私有代码。 + +## RT-Thread 的架构 + +近年来,物联网(Internet Of Things,IoT)概念广为普及,物联网市场发展迅猛,嵌入式设备的联网已是大势所趋。终端联网使得软件复杂性大幅增加,传统的 RTOS 内核已经越来越难满足市场的需求,在这种情况下,物联网操作系统(IoT OS)的概念应运而生。**物联网操作系统是指以操作系统内核(可以是 RTOS、Linux 等)为基础,包括如文件系统、图形库等较为完整的中间件组件,具备低功耗、安全、通信协议支持和云端连接能力的软件平台,**RT-Thread 就是一个 IoT OS。 + +RT-Thread 与其他很多 RTOS 如 FreeRTOS、uC/OS 的主要区别之一是,它**不仅仅是一个实时内核,还具备丰富的中间层组件**,如下图所示。 + +![RT-Thread 软件框架图](figures/02Software_framework_diagram.png) + +它具体包括以下部分: + +- 内核层:RT-Thread 内核,是 RT-Thread 的核心部分,包括了内核系统中对象的实现,例如多线程及其调度、信号量、邮箱、消息队列、内存管理、定时器等;libcpu/BSP(芯片移植相关文件 / 板级支持包)与硬件密切相关,由外设驱动和 CPU 移植构成。 +- 组件与服务层:组件是基于 RT-Thread 内核之上的上层软件,例如虚拟文件系统、FinSH 命令行界面、网络框架、设备框架等。采用模块化设计,做到组件内部高内聚,组件之间低耦合。 +- RT-Thread 软件包:运行于 RT-Thread 物联网操作系统平台上,面向不同应用领域的通用软件组件,由描述信息、源代码或库文件组成。RT-Thread 提供了开放的软件包平台,这里存放了官方提供或开发者提供的软件包,该平台为开发者提供了众多可重用软件包的选择,这也是 RT-Thread 生态的重要组成部分。软件包生态对于一个操作系统的选择至关重要,因为这些软件包具有很强的可重用性,模块化程度很高,极大的方便应用开发者在最短时间内,打造出自己想要的系统。RT-Thread 已经支持的软件包数量已经达到 60+,如下举例: +- 物联网相关的软件包:Paho MQTT、WebClient、mongoose、WebTerminal 等等。 +- 脚本语言相关的软件包:目前支持 JerryScript、MicroPython。 +- 多媒体相关的软件包:Openmv、mupdf。 +- 工具类软件包:CmBacktrace、EasyFlash、EasyLogger、SystemView。 +- 系统相关的软件包:RTGUI、Persimmon UI、lwext4、partition、SQLite 等等。 +- 外设库与驱动类软件包:RealTek RTL8710BN SDK。 +- 其他。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/_navbar.md b/rt-thread-version/rt-thread-standard/_navbar.md new file mode 100644 index 0000000000000000000000000000000000000000..9d2007a29799597c638037335e61ca6a8777b549 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/_navbar.md @@ -0,0 +1,3 @@ + diff --git a/rt-thread-version/rt-thread-standard/_sidebar.md b/rt-thread-version/rt-thread-standard/_sidebar.md new file mode 100644 index 0000000000000000000000000000000000000000..6294927cc0ba3538242af973dee08f32fe0f7281 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/_sidebar.md @@ -0,0 +1,147 @@ + + + + +- **RT-Thread 标准版本** +- 快速上手 + - [Keil模拟器STM32F103](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/stm32f103-simulator.md) + - [RT-Thread潘多拉STM32L475](/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/quick-start.md) + - [野火霸道STM32F103](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/quick-start.md) + - [正点原子nanoSTM32F103](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/quick-start.md) + - [野火挑战者STM32F429](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/quick-start.md) + - [正点原子探索者STM32F407](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/quick-start.md) + - [正点原子阿波罗STM32F429](/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/quick-start.md) + - [野火I.MX RT1052](/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/quick-start.md) + - [正点原子号令者I.MX RT1052](/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/quick-start.md) + - [其他开发板...](/rt-thread-version/rt-thread-standard/tutorial/quick-start/more.md) +- 内核 + - [内核基础](/rt-thread-version/rt-thread-standard/programming-manual/basic/basic.md) + - [线程管理](/rt-thread-version/rt-thread-standard/programming-manual/thread/thread.md) + - [时钟管理](/rt-thread-version/rt-thread-standard/programming-manual/timer/timer.md) + - [线程间同步](/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1.md) + - [线程间通信](/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2.md) + - [内存管理](/rt-thread-version/rt-thread-standard/programming-manual/memory/memory.md) + - [中断管理](/rt-thread-version/rt-thread-standard/programming-manual/interrupt/interrupt.md) + - [内核移植](/rt-thread-version/rt-thread-standard/programming-manual/porting/porting.md) +- 设备和驱动 + - [I/O设备模型](/rt-thread-version/rt-thread-standard/programming-manual/device/device.md) + - [UART设备](/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart.md) + - [PIN设备](/rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin.md) + - [ADC设备](/rt-thread-version/rt-thread-standard/programming-manual/device/adc/adc.md) + - [DAC设备](/rt-thread-version/rt-thread-standard/programming-manual/device/dac/dac.md) + - [CAN设备](/rt-thread-version/rt-thread-standard/programming-manual/device/can/can.md) + - [HWTIMER设备](/rt-thread-version/rt-thread-standard/programming-manual/device/hwtimer/hwtimer.md) + - [I2C总线设备](/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/i2c.md) + - [PWM设备](/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/pwm.md) + - [RTC设备](/rt-thread-version/rt-thread-standard/programming-manual/device/rtc/rtc.md) + - [SPI设备](/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi.md) + - [WATCHDOG设备](/rt-thread-version/rt-thread-standard/programming-manual/device/watchdog/watchdog.md) + - [WLAN设备](/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/wlan.md) + - [SENSOR设备](/rt-thread-version/rt-thread-standard/programming-manual/device/sensor/sensor.md) + - [TOUCH设备](/rt-thread-version/rt-thread-standard/programming-manual/device/touch/touch.md) + - [CRYPTO 设备](/rt-thread-version/rt-thread-standard/programming-manual/device/crypto/crypto.md) + - [AUDIO设备](/rt-thread-version/rt-thread-standard/programming-manual/device/audio/audio.md) + - [Pulse Encoder设备](/rt-thread-version/rt-thread-standard/programming-manual/device/pulse_encoder/pulse_encoder.md) +- 组件 + - [FinSH控制台](/rt-thread-version/rt-thread-standard/programming-manual/finsh/finsh.md) + - [虚拟文件系统](/rt-thread-version/rt-thread-standard/programming-manual/filesystem/filesystem.md) + - [netdev网卡](/rt-thread-version/rt-thread-standard/programming-manual/netdev/netdev.md) + - [SAL套接字抽象层](/rt-thread-version/rt-thread-standard/programming-manual/sal/sal.md) + - [AT命令](/rt-thread-version/rt-thread-standard/programming-manual/at/at.md) + - [ulog日志](/rt-thread-version/rt-thread-standard/programming-manual/ulog/ulog.md) + - [utest测试框架](/rt-thread-version/rt-thread-standard/programming-manual/utest/utest.md) + - [动态模块](/rt-thread-version/rt-thread-standard/programming-manual/dlmodule/dlmodule.md) + - [POSIX接口](/rt-thread-version/rt-thread-standard/programming-manual/posix/posix.md) + - [电源管理](/rt-thread-version/rt-thread-standard/programming-manual/pm/pm.md) +- 软件包 + - 物联网 + - [网络工具集 (NetUtils) 应用](/rt-thread-version/rt-thread-standard/application-note/packages/netutils/an0018-system-netutils.md) + - [rw007 SPI WiFi 模块使用](/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/an0034-rw007-module-using.md) + - 工具 + - [SystemView分析工具](/rt-thread-version/rt-thread-standard/application-note/debug/systemview/an0009-systemview.md) + - MicroPython 用户手册 + - [介绍](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/introduction.md) + - [潘多拉 MicroPython 开发板](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_pandora_iot_board.md) + - [W601 MicroPython 开发板](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_w601_iot_board.md) + - [麻雀一号音视频开发板](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_sparrow_one_board.md) + - [MicroPython IDE](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython-ide.md) + - [MicroPython 库介绍](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython-librarys.md) + - [MicroPython 固件开发指南](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/firmware-develop.md) + - [MicroPython C 模块扩展](/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/external_c_modules.md) + - [更多软件包...](/rt-thread-version/rt-thread-standard/packages-manual/more.md) +- 开发环境搭建 + - [在windows平台使用QEMU运行RT-Thread](/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/an0006-qemu-windows.md) + - [在Ubuntu平台开发RT-Thread](/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/an0005-qemu-ubuntu.md) + - [使用Eclipse开发RT-Thread](/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/an0020-qemu-eclipse.md) + - [使用VS Code开发RT-Thread](/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/an0021-qemu-vscode.md) + - [使用Env创建RT-Thread项目工程](/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/an0017-standard-project.md) + - [固件尺寸优化](/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/an0049-optimize-code-size.md) + - [在RT-Thread潘多拉开发板上实现电源管理](/rt-thread-version/rt-thread-standard/application-note/system/pm/an0025-pm.md) + - [网络协议栈驱动移植](/rt-thread-version/rt-thread-standard/application-note/components/network/an0010-lwip-driver-porting.md) + - [在STM32F429上应用网络功能](/rt-thread-version/rt-thread-standard/application-note/components/network/an0011-network-started.md) + - [在STM32F429上应用文件系统](/rt-thread-version/rt-thread-standard/application-note/components/dfs/an0012-dfs.md) + - [在潘多拉上使用SFUD操作Flash](/rt-thread-version/rt-thread-standard/application-note/components/sfud/an0048-sfud.md) + - [FreeModbus应用笔记](/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/an0036-freemodbus.md) + - [应用AT组件连接ESP8266模块](/rt-thread-version/rt-thread-standard/application-note/components/at/an0014-at-client.md) + - [多线程非阻塞网络编程](/rt-thread-version/rt-thread-standard/application-note/components/network/an0019-tcpclient-socket.md) + - [使用QEMU运行动态模块组件](/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/an0023-dlmodule.md) + - [CmBacktrace应用](/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/an0013-CmBacktrace.md) + - [在STM32L4上应用littlefs文件系统](/rt-thread-version/rt-thread-standard/application-note/components/dfs/an0027-littlefs.md) + - [STM32通用Bootloader](/rt-thread-version/rt-thread-standard/application-note/system/rtboot/an0028-rtboot.md) + - [wireshark抓取tls数据](/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/an0029-mbedtls_wireshark_sniffer.md) + - [在STM32上应用C++](/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/an0035-cpp.md) + - [STM32上使用PWM](/rt-thread-version/rt-thread-standard/application-note/driver/pwm/an0037-rtthread-driver-pwm.md) + - [STM32上使用USB Host读写U盘](/rt-thread-version/rt-thread-standard/application-note/driver/usb/an0046-rtthread-driver-usbh.md) + - QEMU网络视频教程 + - [教程简介](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/README.md) + - [基础知识之TCP/IP协议](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/tcp_ip.md) + - [基础知识之BSD socket](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/socket.md) + - [1.QEMU环境搭建](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/qemu_setup.md) + - [2.QEMU调试](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/qemu_vscode.md) + - [3.文件系统](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/filesystems.md) + - [4.网络抓包](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/wireshark.md) + - [5.Ping分析](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/ping_principle.md) + - [6.TCP客户端](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/tcpclient.md) + - [7.TCP服务器](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/tcpserver.md) + - [8.UDP客户端](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/udpclient.md) + - [9.UDP服务器](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/udpserver.md) + - [10.TCP/UDP通信原理](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/tcp_udp_principle.md) + - [11.Select](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/tcpclient_select.md) + - [12.MQTT协议](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/mqtt.md) + - [13.HTTP协议](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/httpclient.md) + - [14.连接OneNet云](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/onenet.md) + - [15.NTP网络时间](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ntp/ntp.md) + - [16.TFTP文件传输](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/tftp.md) + - [17.Telnet远程控制](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/telnet.md) + - [常见问题及解决方法](/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/faq.md) + +- Demo示例 + - 蜂鸣器播放器 + - [简介](/rt-thread-version/rt-thread-standard/tutorial/beep-player/README.md) + - [使用PIN设备控制LED](/rt-thread-version/rt-thread-standard/tutorial/beep-player/pin.md) + - [使用PIN设备进行按键控制](/rt-thread-version/rt-thread-standard/tutorial/beep-player/button.md) + - [使用PWM设备驱动蜂鸣器](/rt-thread-version/rt-thread-standard/tutorial/beep-player/pwm.md) + - [音乐数据的编码与解码](/rt-thread-version/rt-thread-standard/tutorial/beep-player/decode.md) + - [播放器实现](/rt-thread-version/rt-thread-standard/tutorial/beep-player/player.md) + - [按键控制](/rt-thread-version/rt-thread-standard/tutorial/beep-player/key.md) + - 分布式温度监控系统: + - [简介](/rt-thread-version/rt-thread-standard/tutorial/temperature-system/README.md) + - [Sensor框架对接实战](/rt-thread-version/rt-thread-standard/tutorial/temperature-system/sensor.md) + - [邮箱与消息队列实战](/rt-thread-version/rt-thread-standard/tutorial/temperature-system/ipc.md) + - [文件系统实战](/rt-thread-version/rt-thread-standard/tutorial/temperature-system/dfs.md) + - [OneNet连云实战](/rt-thread-version/rt-thread-standard/tutorial/temperature-system/onenet.md) + - 智能车: + - [简介](/rt-thread-version/rt-thread-standard/tutorial/smart-car/README.md) + - [RT-Thread卷积神经网络手写体识别](/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/cnn-mnist.md) + - [Darknet训练目标检测模型](/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/darknet-yolov2.md) + - [RT-Thread连接ROS](/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/ros-connect.md) + - [RT-Thread连接ROS控制小车](/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/ros-camera-car.md) + - [RT-Thread连接RPLidar激光雷达](/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/rplidar-connect.md) + - [RT-Thread搭配ROS实现目标检测小车](/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/object-detection.md) +- 代码贡献 + - [驱动开发](/rt-thread-version/rt-thread-standard/development-guide/package/package.md) + - [传感器驱动开发指南](/rt-thread-version/rt-thread-standard/development-guide/sensor/sensor_driver_development.md) + - [软件包开发](/rt-thread-version/rt-thread-standard/development-guide/package/package.md) + - [向RT-Thread贡献代码](/rt-thread-version/rt-thread-standard/development-guide/github/github.md) +- [RT-Thread 标准版的版本选择](/rt-thread-version/rt-thread-standard/application-note/setup/rt-thread-version/an0030-rtthread-version.md) +- [API参考手册](https://www.rt-thread.org/document/api/) diff --git a/rt-thread-version/rt-thread-standard/application-note/README.md b/rt-thread-version/rt-thread-standard/application-note/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0bcf310463e7d9e504fca86350812b91c1731dec --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/README.md @@ -0,0 +1,5 @@ +# RT-Thread应用笔记 # + +---------- + +RT-Thread应用笔记是针对某一技术点的专题介绍,方便开发者更快更好的学习和掌握该技术点。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/an0014-at-client.md b/rt-thread-version/rt-thread-standard/application-note/components/at/an0014-at-client.md new file mode 100644 index 0000000000000000000000000000000000000000..028dcc23bdb5901a11d3618a0b62288821b1d72f --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/at/an0014-at-client.md @@ -0,0 +1,312 @@ +# 应用 AT 组件连接 ESP8266 模块 + +本文介绍了 RT-Thread AT 组件的基本知识和 AT 客户端的使用方法,帮助开发者更好地使用 RT-Thread AT 组件。 + +## 简介 + +为了方便用户使用 AT 命令,简单的适配不同的 AT 模块, RT-Thread 提供了 AT 组件用于 AT 设备的连接和数据通讯。**AT 组件的实现包括客户端的和服务器两部分**。对于嵌入式设备而言,更多的情况下设备使用 AT 组件作为客户端连接服务器设备,所以本文将为大家重点介绍 AT 组件中客户端的主要功能、移植方式和实现原理,并介绍在客户端的基础上实现标准 BSD Socket API,使用 AT 命令完成复杂网络通讯。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +## AT Client 功能 + +本文将基于正点原子 STM32F4 探索者开发板和乐鑫 ESP8266 开发板,给出了 AT 组件中 AT Client 功能的配置、移植和使用方式。 + +**AT Client 功能主要用于完成 AT 命令的数据收发和解析过程。** + +下图为本文使用的两个开发板的底板图,开发者可以使用 ESP8266 开发板或模组,若缺少正点原子 STM32F4 探索者开发板可使用其他带额外串口的开发板代替,需确保开发板正常运行 RT-Thread 系统且串口使用正常: + +![STM32F4 底板图](figures/stm32f4.jpg) + +![ESP8266 底板图](figures/esp8266.jpg) + +AT 组件中 AT Client 主要完成 AT 命令的发送和响应数据的接收与解析。这里我们使用正点原子 STM32F4 探索者开发板串口 3 作为 AT Client 连接 ESP8266 开发板的串口 2,ESP8266 开发板的串口 2 作为 AT Server,完成 AT Client 数据收发和解析的功能,下面就具体给出配置和使用方式的介绍。 + +### AT Client 配置 + +开启 Env 工具,进入 `rt-thread\bsp\stm32\stm32f407-atk-explorer` 目录,在 Env 命令行输入 menuconfig 进入配置界面配置工程。 + +- 配置串口支持:配置开启 UART3 选项; + +![配置串口支持](figures/config_uart.jpg) + +- 开启 AT Client 功能:RT-Thread Components ---> Network ---> AT commands --> 开启 AT DEBUG,开启 AT Client 支持,目前 AT Client 支持多连接功能,后面需要手动初始化 AT Client。 + +![AT Client 配置](figures/config_client.jpg) + +AT Client 配置选项介绍如下: + +- Enable debug log output:配置开启调试日志; +- Enable AT commands client:配置开启 AT 客户端; +- The muxinum number of supported clients: 配置最大同时支持的客户端数量,该例程使用单客户端连接,配置为 1 即可。 +- Enable BSD Socket API support by AT commands: 配置开启 BSD Socket API 支持,下面 AT Client 例程没有使用可暂时不开启。 +- Enable CLI(Command-Line Interface) for AT commands: 配置开启 AT 命令行交互模式。 +- Enable print RAW format AT command communication data: 配置开启收发数据实时打印功能。 +- The maxinum length of AT Commonds:配置发送 AT 命令的最大数据长度 + +配置完成,保存并退出配置选项,输入命令 scons --target=mdk5 生成 MDK 工程; + +### AT Client 添加示例 + +下载 [AT Client 示例代码](https://github.com/RT-Thread-packages/at_device/blob/master/samples/at_sample_client.c),添加到打开的 MDK 工程中,如下图所示: + +![AT Client 示例添加](figures/add_sample.jpg) + +示例添加完成,就可以编译、下载程序到开发板,之后打开 PC 上串口工具,这里使用 xshell 工具,选择正确的串口(配置串口参数为 115200-8-1-N、无流控),然后按下复位后就可以在串口 1 连接的终端上看到 RT-Thread 系统启动日志。 + +系统初始化成功之后,在 shell 中执行 `at_client_init uart3 ` 命令,这里的 `uart3` 为开发板中作为 AT client 的设备名。然后可以看到 AT Client 的初始化日志,说明 AT Client 功能配置启动成功,如下图所示: + +![AT Client 成功启动](figures/client_start.jpg) + +### AT Client 运行示例 + +### AT Client 模式 + +该模式下正点原子 STM32F4 探索者开发板串口 3 作为 AT Client, ESP8266 开发板作为 AT Server,进行数据交互模式,在本地 shell 中输入 `at_client_test` 命令,该 shell 命令用于发送 AT 命令到服务器,并且接收和解析服务器响应数据,如下图所示过程: + +![AT Client 运行示例](figures/client_sample.jpg) + +### AT Client CLI 模式 + +AT Client CLI 功能可以转发本地 shell 输入的数据到设备连接的 AT Server 串口设备上,并在本地 shell 上实时显示 AT Client 串口接收到的数据。在本地 shell 中执行 `at client` 命令进入 AT Client CLI 模式即可进行数据的收发。通过 AT Client CLI 模式,用户可以很方便的完成与 AT Server 的连接与调试,极大的提高开发效率。 + +下图演示了 AT Client CLI 功能的使用和退出: + +![AT Client CLI 运行示例](figures/client_cli.jpg) + +### AT Client 示例详解 + +本文使用的 AT Client 示例代码演示了 AT Client 的整个使用流程,示例代码完成 STM32F4 设备 AT 命令的发送并接收和解析 ESP8266 设备的响应数据。代码的使用和平台有关,开发者可以根据自己使用的平台修改示例代码并运行,主要修改命令的名称和解析的方式。下面通过示例代码介绍一下 AT Client 的具体使用流程: + +```c +#include +#include +#include +#include + +/* AT+CIFSR Query local IP address and MAC */ +int at_client_test(int argc, char**argv) +{ + at_response_t resp = RT_NULL; + int result = 0; + + if (argc != 1) + { + LOG_E("at_client_test - AT client send commands to AT server."); + return -1; + } + + /* 创建响应结构体,设置最大支持响应数据长度为 256 字节 + (最大响应长度用户根据实际需求自定义),响应数据行数无限制,超时时间为 5 秒 */ + resp = at_create_resp(256, 0, rt_tick_from_millisecond(5000)); + if (resp == RT_NULL) + { + LOG_E("No memory for response structure!"); + return -2; + } + + /* 关闭回显功能 */ + at_exec_cmd(resp, "ATE0"); + + /* AT Client 发送查询 IP 地址命令并接收 AT Server 响应 */ + /* 响应数据及信息存放在 resp 结构体中 */ + result = at_exec_cmd(resp, "AT+CIFSR"); + if (result != RT_EOK) + { + LOG_E("AT client send commands failed or return response error!"); + goto __exit; + } + + /* 按行数循环打印接收到的响应数据 */ + { + const char *line_buffer = RT_NULL; + + LOG_D("Response buffer"); + for(rt_size_t line_num = 1; line_num <= resp->line_counts; line_num++) + { + if((line_buffer = at_resp_get_line(resp, line_num)) != RT_NULL) + { + LOG_D("line %d buffer : %s", line_num, line_buffer); + } + else + { + LOG_E("Parse line buffer error!"); + } + } + } + /* 按自定义表达式(sscanf 解析方式)解析数据,得到对应数据 */ + { + char resp_arg[AT_CMD_MAX_LEN] = { 0 }; + /* 自定义数据解析表达式 ,用于解析两双引号之间字符串信息 */ + const char * resp_expr = "%*[^\"]\"%[^\"]\""; + + LOG_D("Parse arguments"); + /* 解析响应数据中第一行数据,得到对应 IP 地址 */ + if (at_resp_parse_line_args(resp, 1, resp_expr, resp_arg) == 1) + { + LOG_D("Station IP : %s", resp_arg); + memset(resp_arg, 0x00, AT_CMD_MAX_LEN); + } + else + { + LOG_E("Parse error, current line buff : %s", at_resp_get_line(resp, 4)); + } + + /* 解析响应数据中第二行数据,得到对应 MAC 地址 */ + if (at_resp_parse_line_args(resp, 2, resp_expr, resp_arg) == 1) + { + LOG_D("Station MAC : %s", resp_arg); + } + else + { + LOG_E("Parse error, current line buff : %s", at_resp_get_line(resp, 5)); + goto __exit; + } + } +__exit: + if(resp) + { + /* 删除 resp 结构体 */ + at_delete_resp(resp); + } + + return result; +} +/* 设置当前 AT 客户端最大支持的一次接收数据的长度 */ +#define AT_CLIENT_RECV_BUFF_LEN 512 +int at_client_test_init(int argc, char**argv) +{ + if (argc != 2) + { + rt_kprintf("at_client_init -- AT client initialize.\n"); + return -RT_ERROR; + } + + at_client_init(argv[1], AT_CLIENT_RECV_BUFF_LEN); + + return RT_EOK; +} +#ifdef FINSH_USING_MSH +#include +/* 添加 AT Client 测试命令到 shell */ +MSH_CMD_EXPORT(at_client_test, AT client send cmd and get response); +/* 添加 AT Client 初始化命令到 shell */ +MSH_CMD_EXPORT_ALIAS(at_client_test_init, at_client_init, initialize AT client); +#endif +``` +- 整个示例为单客户端示例,可以直接使用单客户端模式 API。 + +- AT Client 使用流程大致如下:at_create_resp() 创建响应结构体 ---> at_exec_cmd() 发送命令并接收响应 ---> at_resp_get_line()/at_resp_parse_line_args() 打印或解析响应数据 ---> at_delete_resp() 删除响应结构体。 + + - at_exec_cmd() 函数完成对传入 AT 命令的发送和响应数据的接收,响应数据以按行的形式存放与结构体中,便于数据按行打印或及解析。 + + - 打印或解析数据时对于不同的命令的响应数据有不同的数据解析方式,需要自定义数据解析的表达式,这要求开发者提前知道发送命令的具体响应结构,可以通过查看设备 AT 命令手册了解。 + +## AT Socket 功能 + +为了方便开发者使用 AT 组件进行网络相关操作,降低 RT-Thread 系统对单独协议栈网络连接的依赖,RT-Thread 系统在 AT 组件和 SAL 组件的基础上推出了 AT Socket 功能。 + +**AT Socket 功能是建立在 AT Client 功能基础上,主要作用是完成 AT 设备连接网络并进行数据通讯,对应用层提供标准 BSD Socket API 接口,方便应用层代码移植和使用。** + + AT Socket 功能使设备无需实现其他网络连接方式,直接使用串口完成设备联网功能,简化了设备开发的软硬件设计,方便开发者开发。此外,不同于传统的软件网络协议栈,AT Socket 网络功能的运行主要是在串口连接的 AT Server 设备上完成,根据不同的 AT Server 设备,可同时支持 5-6 个 socket,这样极大了降低了 AT Client 设备上 MCU 资源占用,提高 MCU 工作效率,确保数据通讯的质量和硬件的资源的合理分配。 + +AT Socket 功能目前占用最少资源体积约为**20K ROM 、 3K RAM**(支持 5 个 Socket)。 + +AT Socket 功能对于不同的 AT 设备需要完成移植适配过程,目前已经完成多种设备的适配,包括:ESP8266、 M26 、MC20、EC20、SIM800、SIM76XX、RW007、MW31 等,各种适配的方式通过 [AT Device 软件包](https://github.com/RT-Thread-packages/at_device) 给出,所以 AT Socket 功能的实现基于 AT Device 软件包。下面主要通过 ESP8266 设备,AT Socket 功能使用和 AT Device 软件包配置进行介绍。 + +### AT Socket 配置 + +AT Socket 功能的使用依赖于如下几个组件: + +- **AT 组件**:AT Socket 功能基于 AT Client 功能的实现; +- **SAL 组件**:SAL 组件主要是 AT Socket 接口的抽象,实现标准 BSD Socket API; +- **netdev 组件**:用于抽象和管理 AT 设备生成的网卡设备相关信息,提供 ping、ifconfig、netstat 等网络命令; +- **AT Device 软件包**:针对不同设备的 AT Socket 移植和示例文件,以软件包的形式给出; + +下面主要介绍 Env 中配置 AT Socket 功能的整个流程: + +1. 开启 Env 工具,进入`rt-thread\bsp\stm32\stm32f407-atk-explorer` 目录,在 Env 命令行输入 menuconfig 进入配置界面配置工程。 + +2. 开启 AT Device 软件包,示例中使用 `laster` 最新版本,需要配置使用的 AT 模块型号(ESP8266)和 AT Client 设备名称(UART3): + +- `RT-Thread online packages ---> IoT - internet of things ---> AT Device`配置开启 AT DEVICE 软件包支持; +- 配置使用的设备为 ESP8266 设备; +- 配置 AT Client 设备名称和最大支持的接收数据长度; +- 配置 wifi ssid 和 wifi password 用于设备联网; +- 配置使用 `laster` 版本软件包; + +![配置 at device 软件包](figures/config_at_device.jpg) + +3. AT Device 软件包开启,并且选择指定 AT 设备之后,会默认选上 AT 组件中 AT Client 功能已经 AT Socket 功能支持,如下图所示: + +![配置开启 AT Socket](figures/config_at_socket.jpg) + +**AT Device 软件包中每种 AT 设备配置选项,都是 AT Socket 功能针对该设备的实现方式。** + +4. 之后需要开启 SAL 组件支持,用于抽象统一标准网络接口,`RT-Thread Components ---> Network ---> Socket abstraction laye ---> Support AT Commands stack`,开启 SAL 组件功能支持,然后开启 `SAL_USING_POSIX` 支持,支持使用 read/write、poll/select 等文件系统接口函数。 + +![开启 SAL 支持](figures/config_sal.jpg) + +5. 配置完成,保存并退出配置选项,输入命令 scons --target=mdk5 生成 keil 工程。 + +6. 打开 MDK 工程,编译、下载代码到开发板中。 + +7. 打开 PC 上串口工具 xshell,配置打开串口(配置串口参数为 115200-8-1-N、无流控),然后按下复位后就可以在串口 1 连接的终端上看到 RT-Thread 系统启动日志,并可以看到 AT Client 的启动日志、SAL 的启动日志且设备自动连接网络成功,说明 AT Socket 功能初始化成功,如下图所示。 + +![AT Socket 启动](figures/at_scoket_start.jpg) + +### AT Socket 使用 + +#### 网络连接测试 + +AT Socket 功能提供 `ping` 或者 `ifconfig`命令用于测试设备网络连接环境,,`ping` 命令原理是通过 AT 命令发送请求到服务器,服务器响应数据,客户端解析 ping 数据并显示。`ifocnfig` 命令可以查看当前设备网络状态和 AT 设备生成的网卡基本信息。如下图所示,设备网络连接成功之后,执行网络测试命令: + +![at_ping 功能使用](figures/at_ping.jpg) + +#### MQTT 组件示例测试 + +AT Socket 功能完成设备通过串口 AT 命令进行网络数据通讯,设备可以通过 AT Socket 功能启动 MQTT 协议并运行 MQTT 示例代码,具体配置步骤和示例使用方式如下: + +- AT Device 软件包开启的基础上,配置下载 MQTT 组件包及示例代码,具体配置方式:RT-Thread online packages ---> IOT - internet of things ---> 开启 paho MQTT 组件包,配置开启 MQTT 示例代码。 + +![MQTT 组件配置](figures/config_mqtt.jpg) + +- 配置完成,保存并退出配置选项,scons 重新生成工程,编译下载代码到开发板中。 + +- 打开串口工具,系统启动成功,输入 `mqtt_start` 命令启动 MQTT 协议,启动完成之后输入 `mqtt_publish mqtt_test_data` 命令,用于向固定的 MQTT Topic 发送数据,同时 MQTT 服务器会立刻向该 Topic 发送同样数据,MQTT 示例测试完成,如下图所示: + +![MQTT 示例运行](figures/mqtt_sample.jpg) + +上述展示了正点原子 STM32F4 设备在未连接网络的情况下使用 AT Socket 功能运行 MQTT 网络示例,实现了 AT Socket 网络数据收发的功能,目前 AT Socket 功能只支持设备作为网络客户端连接服务器,这也符合嵌入式设备多用于客户端设备的特性。AT Socket 目前已经支持多种网络相关组软件包和功能,如下所示: + +- tcpclient/udpclient 功能 +- MQTT 软件包 +- webclient 软件包 +- mbedtls 软件包 +- onenet 软件包 +- ali-linkkit 软件包 +- NTP 时间查询功能 +- iperf 网络测试功能 +- ping/ifconfig/netstat 网络测试功能 + +## 参考资料 + +* [《AT 组件编程指南》](../../../programming-manual/at/at.md) + +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +### Q: esp8266 设备连上网络后过一段时间 wifi 自动断开重连怎么办? + +**A:** 该错误一般为 esp8266 设备供电问题,可以使用万用表查看设备当前电压情况,如果出现供电不足问题,可以为 esp8266 vin 接口添加额外供电。 + +### Q: esp8266 设备一直显示连接超时,命令发送失败怎么办? + +**A:** 检查 esp8266 设备串口接线,反接 RX/TX 接线,检查设备供电,进 AT Client CLI 模式确定命令发送是否正常。 + +更多 AT 组件或者 AT Device 软件包相关问题请查看 [ AT 相关问题汇总贴](https://www.rt-thread.org/qa/thread-11919-1-1.html)。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/add_sample.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/add_sample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bc1a1b2440989056521a8a565dfbf169339ed82a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/add_sample.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_frame.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_frame.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5c868dc6c26a7715b9946817c68ca24d9be68cd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_frame.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_ping.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_ping.jpg new file mode 100644 index 0000000000000000000000000000000000000000..101a6987060c796006a9e7dfc8a28e0e1b04bdbe Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_ping.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_scoket_start.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_scoket_start.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8749dfede6b71c41d3023b76f9435147f1cb7047 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/at_scoket_start.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_cli.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_cli.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c1994c9d6a3c53b51e317e85c46deec5d65289a2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_cli.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_sample.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_sample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..087f8aac2b9038fcdcfeb95bc034e8eeff240e33 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_sample.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_start.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_start.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df8e5d0a70b39dd29aa0adab0422f4fc88b5cbf0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/client_start.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_at_device.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_at_device.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fd5ea7281e492bd1b5cb9fc88e5dd585ec7ed5a5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_at_device.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_at_socket.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_at_socket.jpg new file mode 100644 index 0000000000000000000000000000000000000000..975bccc4e0b3520f51b680789cbf8d45f75700f2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_at_socket.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_client.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_client.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bcc16b083c9c0d7e26c1fd3a550ee0d16d726151 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_client.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_mqtt.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_mqtt.jpg new file mode 100644 index 0000000000000000000000000000000000000000..28ce9e7c8f19feb1f6edd95f541eb2007e0f07ff Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_mqtt.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_sal.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_sal.jpg new file mode 100644 index 0000000000000000000000000000000000000000..65009d237433e709378d84a98d7e8cd8ac665987 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_sal.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_uart.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_uart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0eb14a45af444ae46565130bd50195fc8b6853cd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/config_uart.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/esp8266.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/esp8266.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3506413a4240c683e2446a9af6a4a04eb26c7463 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/esp8266.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/mqtt_sample.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/mqtt_sample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6bfb9e98f2997f4dcb5f8bb581b83a9b507a4d4a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/mqtt_sample.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/at/figures/stm32f4.jpg b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/stm32f4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..00a33ef881871a000a0cd65b5447cf14a5e1ef0f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/at/figures/stm32f4.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/an0035-cpp.md b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/an0035-cpp.md new file mode 100644 index 0000000000000000000000000000000000000000..7536b56f9bb6b14e8d7a5849d47b2cfd9aec3f9d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/an0035-cpp.md @@ -0,0 +1,254 @@ +# 在 STM32 上使用 C++ 指南 + +本文描述了如何使用在搭载了 `RT-Thread` 系统的 `STM32` 平台上使用 `C++`,包括 `C++` 的配置和应用等。并给出了在意法半导体 `STM32F411 nucleo` 开发板上验证的代码示例。 + +## 硬件平台简介 + +本文基于意法半导体 `STM32F411 nucleo` 开发板,给出了 `C++` 的具体应用示例代码,由于 `RT-Thread` 上层应用 `API` 的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。 + +`STM32F411 nucleo` 是意法半导体推出的一款基于 `ARM Cortex-M4` 内核的开发板,最高主频为 `100Mhz`,该开发板具有丰富的板载资源,可以充分发挥 `STM32F411RE` 的芯片性能。 + +![意法半导体开发板](figures/board.png) + +## 如何在 `STM32` 上使用 `C++` + +准备工作: + +1. 下载 [RT-Thread 源码](https://github.com/RT-Thread/rt-thread) + +2. 下载 [ENV 工具](https://www.rt-thread.org/page/download.html) + + +3. 进入 `rt-thread\bsp\stm32f411-st-nucleo` 目录,检查 BSP `rtconfig.py` 文件和 `SConstruct` 文件是否支持 `C++` 配置,如下图所示 + +检查 `rtconfig.py` 文件中对 `C++` 的支持 + +![rtconfig.py](figures/rtconfig_py.png) + +检查 `SConstruct` 文件中对 `C++` 的支持 + +![SConstruct](figures/SConstruct.png) + +打开 `C++` 支持: + +1. 打开 `Env` 工具,在 `Env` 命令行中输入 `menuconfig`,进入配置界面,使用 `menuconfig` 工具(学习如何使用)配置工程。在 `menuconfig` 配置界面依次选择 `RT-Thread Components ---> C++ features ---> Support C++ features`,如图所示: + +![menuconfig 中开启 C++ 支持](figures/support_cpp.png) + +编译工程: +`scons --target=mdk5` +1. 生成 `mdk5` 工程,将附录章节的 `main.cpp` 文件替换掉 `BSP` 中的 `main.c` 文件并重新加入到工程中,如图所示: + +![加入测试代码](figures/main_cpp.png) + +2. 编译,下载程序,在终端输入 `help` 命令可以看到 `test_cpp` 已经添加成功了。 + +![help](figures/help_cpp.png) + + +3. 运行 `C++` 程序: + + 在终端输入 `test_cpp` 运行结果如下图所示。 + +![运行C++程序](figures/run_cpp.png) + +## C++ 全局对象构造函数的调用 + +`RT-Thread` 中对全局对象构造函数的实现中,以 `GNUC` 为例,在 `rt-thread\components\cplusplus` 目录下的 `crt_init.c` 文件中对 `C++` 进行了系统初始化, +在特定的 `BSP` 目录下,连接脚本文件 `link.lds` 为 `C++` 全局构造函数的代码分配了段,使 `C++` 全局对象构造函数链接后能够存放在指定的段中。如下图所示: + + ![全局构造函数](figures/build_func.png) + +1. `crt_init.c` 文件完成了 `C++` 系统的初始化工作 + +2. `C++` 系统初始化部分: + + RT_WEAK int cplusplus_system_init(void) + { + typedef void(*pfunc)(); + extern pfunc __ctors_start__[]; + extern pfunc __ctors_end__[]; + pfunc *p; + + for (p = __ctors_start__; p < __ctors_end__; p++) + (*p)(); + + return 0; + } + INIT_COMPONENT_EXPORT(cplusplus_system_init); + +在 `cplusplus_system_init` 函数中,将全局对象的构造函数依次链接到了链接脚本文件中为其分配的段中,并且调用了 `RT-Thread` 组件自动初始化的宏 `INIT_COMPONENT_EXPORT`,所以在链接的时候,`C++` 全局对象构造函数所产生的目标文件就被链接到了 `__ctors_start__` 和 `__ctors_end__`组成的段中。 + +3. 链接脚本中为 `C++` 全局构造函数分配的段部分: + + + PROVIDE(__ctors_start__ = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE(__ctors_end__ = .); + + +`__ctors_start__` 分配了 `C++` 全局构造函数段的起始地址, `__ctors_end__` 分配了 `C++` 全局构造函数段的结束地址,所以全局构造函数在系统初始化的时候,就会被链接到这里分配的段地址中。 + + + + + +## RT-Thread C++ 异常说明 + +同样,在链接脚本文件 `link.lds` 中,也为 `C++` 异常分配了段地址: + + __exidx_start = .; + ARM.exidx : + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + _sidata = .; + } > CODE + __exidx_end = .; + + +`__exidx_start` 分配了 `C++` 异常的起始地址, `__exidx_end` 分配了 `C++` 异常的结束地址,当异常产生的时候,就会被分配到指定的段地址中. + +这里以一个 `C++` 除零异常的抛出和捕获为例: + + + #include + + #define MIN_VALUE (1e-4) + #define IS_DOUBLE_ZERO(d) (abs(d) < MIN_VALUE) + + double div_func(double x, double y) + { + if (IS_DOUBLE_ZERO(y)) + { + throw y; /* throw exception */ + } + + return x / y; + } + + void throw_exceptions(void *args) + { + try + { + div_func(6, 3); + rt_kprintf("there is no err\n"); + div_func(4, 0); /* create exception*/ + rt_kprintf("you can run here?\n"); + } + catch(double) /* catch exception */ + { + rt_kprintf("error of dividing zero\n"); + } + } + + MSH_CMD_EXPORT(throw_exceptions, throw cpp exceptions); + + +当除零异常发生的时候 `div_func` 函数会抛出一个异常,在 `throw_exceptions` 函数中会去捕获这个异常。 + + 下载代码,并在终端输入 `throw_exceptions` 运行结果如下图所示。 + + ![抛出与捕获异常](figures/throw_exceptions.png) + + +到这一步为止,如何在搭载了 `RT-Thread` 系统的 `STM32` 平台上如何使用 `C++` 的介绍就结束了。 + +## 附录 + +`main.cpp` 源码 + +```c +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2019-05-23 tyustli first version + */ + +#include + +int main(void) +{ + int count = 1; + while (count++) + { + rt_thread_mdelay(500); + rt_kprintf("hello rtthread\r\n"); + } + + return RT_EOK; +} +using namespace rtthread; + +class tran +{ +public: + void getnumber(int a, int b) + { + x = a; + y = b; + } + void out(tran & s) + { + rt_kprintf("x = %d, y = %d\n", x, y); + } +private: + int x, y; +}; + +int test_cpp(void) +{ + tran s; + + s.getnumber(13, 54); + s.out(s); + + return 0; +} + +MSH_CMD_EXPORT(test_cpp, test cpp); + +#include +#include + +#define MIN_VALUE (1e-4) +#define IS_DOUBLE_ZERO(d) (abs(d) < MIN_VALUE) + +double div_func(double x, double y) +{ + if (IS_DOUBLE_ZERO(y)) + { + throw y; /* throw exception */ + } + + return x / y; +} + +void throw_exceptions(void *args) +{ + try + { + div_func(6, 3); + rt_kprintf("there is no err\n"); + div_func(4, 0); /* create exception*/ + rt_kprintf("you can run here\n"); + } + catch(double) /* catch exception */ + { + rt_kprintf("error of dividing zero\n"); + } +} + +MSH_CMD_EXPORT(throw_exceptions, throw cpp exceptions); + +``` + +## 参考资料 +* [ENV 用户手册](https://www.rt-thread.org/document/site/programming-manual/env/env/) + +* [STM32F411-ST-NUCLEO BSP 源码](https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32f411-st-nucleo) + diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/SConstruct.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/SConstruct.png new file mode 100644 index 0000000000000000000000000000000000000000..fdf869876d2bb04393316acd49dac3e112c4b487 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/SConstruct.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/board.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/board.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a4e57a7f03caab5b9ddd7b4a3b70275d3db75e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/board.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/build_func.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/build_func.png new file mode 100644 index 0000000000000000000000000000000000000000..f19aaee8272d27ce0922e73c2c16bc8d0d6b1a56 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/build_func.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/help_cpp.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/help_cpp.png new file mode 100644 index 0000000000000000000000000000000000000000..7ad3f5f5becff94479567c4043337692ea2dd4ce Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/help_cpp.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/main_cpp.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/main_cpp.png new file mode 100644 index 0000000000000000000000000000000000000000..6a89c8df8ea1f86bb0fe7d6ced4e664988680feb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/main_cpp.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/rtconfig_py.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/rtconfig_py.png new file mode 100644 index 0000000000000000000000000000000000000000..4b5da68e85abd713f39eca55b6ae57543fc902db Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/rtconfig_py.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/run_cpp.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/run_cpp.png new file mode 100644 index 0000000000000000000000000000000000000000..6892f30c0d8a26a9058c2d34327dc61901d14c7c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/run_cpp.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/support_cpp.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/support_cpp.png new file mode 100644 index 0000000000000000000000000000000000000000..99b0687b5615af0201c8db1e4bcb666484ab7f05 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/support_cpp.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/throw_exceptions.png b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/throw_exceptions.png new file mode 100644 index 0000000000000000000000000000000000000000..18bba441594cedf14bb0cd9cd019f700ab19fab5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/figures/throw_exceptions.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/main.zip b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/main.zip new file mode 100644 index 0000000000000000000000000000000000000000..bb87301a971408fdca0e211351816d47c4a76f5d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/main.zip differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/main/main.cpp b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/main/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4340da9596532d48ff6dc013867aebdd3d385438 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/cplusplus/main/main.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2019-05-23 tyustli first version + */ + +#include + +int main(void) +{ + int count = 1; + while (count++) + { + rt_thread_mdelay(500); + rt_kprintf("hello rtthread\r\n"); + } + + return RT_EOK; +} +using namespace rtthread; + +class tran +{ +public: + void getnumber(int a, int b) + { + x = a; + y = b; + } + void out(tran & s) + { + rt_kprintf("x = %d, y = %d\n", x, y); + } +private: + int x, y; +}; + +int test_cpp(void) +{ + tran s; + + s.getnumber(13, 54); + s.out(s); + + return 0; +} + +MSH_CMD_EXPORT(test_cpp, test cpp); + +#include + +#define MIN_VALUE (1e-4) +#define IS_DOUBLE_ZERO(d) (abs(d) < MIN_VALUE) + +double div_func(double x, double y) +{ + if (IS_DOUBLE_ZERO(y)) + { + throw y; /* throw exception */ + } + + return x / y; +} + +void throw_exceptions(void *args) +{ + try + { + div_func(6, 3); + rt_kprintf("there is no err\n"); + div_func(4, 0); /* create exception*/ + rt_kprintf("you can run here\n"); + } + catch(double) /* catch exception */ + { + rt_kprintf("error of dividing zero\n"); + } +} + +MSH_CMD_EXPORT(throw_exceptions, throw cpp exceptions); diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/an0012-dfs.md b/rt-thread-version/rt-thread-standard/application-note/components/dfs/an0012-dfs.md new file mode 100644 index 0000000000000000000000000000000000000000..42eb81a338961c8ee010d38b720863041f90039d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/dfs/an0012-dfs.md @@ -0,0 +1,240 @@ +# 在 STM32F429 上应用文件系统 + +本文介绍了 RT-Thread 文件系统的基本知识和使用方法,帮助开发者更好地使用 RT-Thread 文件系统。并给出了在正点原子 `STM32F429-apollo` 开发板上验证的代码示例。 + +## 简介 + +第一次接触 RT-Thread 文件系统的开发者可能觉得 RT-Thread 文件系统过于复杂,不知道该从何入手。想要在项目中使用文件系统,却不知道该怎么做。本文将介绍 RT-Thread 文件系统的移植及文件系统的使用方法。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +## 文件系统的移植 + +本次演示使用正点原子开发板 `STM32F429-Apollo` ,选择的文件系统类型是 `elm FatFS` 。由于 RT-Thread 自带了这个文件系统,所以移植工作较为简单,只需要通过 Env 工具对系统进行合适的配置既可。其他 RT-Thread 支持的文件系统,移植过程也是类似的,只需要对系统进行合适的配置即可使用。 + +![stm32f429-apollo 开发板](figures/stm32f429-apollo.png) + +文件系统的移植主要包括下面几个方面: + +- 开启 / 配置 DFS 框架 +- 开启 / 配置 指定的文件系统 +- 确保开发板上的存储设备驱动正常工作 + +通过 Env 工具可以方便地开启文件系统,将所需的文件系统类型添加到工程中。 + +对存储设备进行功能测试,可以确保存储设备驱动是正常工作的。驱动程序的稳定工作是文件系统正常使用的基础。 + +### 配置文件系统 + +使用 Env 工具进入 `rt-thread\bsp\stm32f429-apollo` 目录,在命令行中输入 `menuconfig` 命令进入配置界面。 + +在 `menuconfig` 配置界面依次选择 `RT-Thread Components → Device virtual file system`,如下图所示: + +![menuconfig 配置界面](figures/1528457058001.png) + +进入到 DFS 的配置界面,开启下图所示的选项,就可以将 `FatFS` 添加到系统中。如图所示: + +![DFS 配置界面](figures/1528354745063.png) + +这里需要注意的是还需要进入到 `elm-chan's FatFs, Generic FAT Filesystem Module` 选项中修改关于长文件名支持的选项,否则在后面使用文件系统的过程中,创建的文件或者文件夹的名称不能超过 8 个字符。修改方式如下图所示: + +![配置长文件名选项](figures/1528355204158.png) + +![选择选项 3](figures/1528355291434.png) + +设置文件系统扇区大小,表示可处理的最大字节数,这里设置为 4096 字节。设置的值需要根据存储器件手册确定,不能小于存储器件的最小可擦除扇区。 + +![设置扇区大小](figures/set_sector_size.png) + +因为要使用一些 C 库函数,所以需要打开 `libc` 功能: + +![开启 libc](figures/1528680175178.png) + +保存选项后即可退出,此时 `elm FatFS` 已经添加到项目中 。 + +### 存储设备初始化 + +#### 开启 SPI 设备驱动 + +DFS 框架的文件系统实现层需要存储设备驱动层提供驱动接口用于对接,本次使用的存储设备为 `SPI Flash`。 + +重新打开 menuconfig 配置界面,在 `RT-Thread Components → Device Drivers` 界面中选中 `Using SPI Bus/Device device drivers` 以及 `Using Serial Flash Universal Driver` 选项,如下图所示: + +![打开 SPI 驱动](figures/1528356393167.png) + +为了方便地使用 shell 命令,我们在 `RT-Thread Components → Command shell` 选项中开启 `Using module shell` 选项,如下图所示: + +![开启 msh 选项](figures/1528356861018.png) + +保存选项并退出,在 Env 中输入命令 `scons --target=mdk5 -s` 生成 mdk5 工程,编译并下载程序。 + +#### 检查存储设备驱动 + +在 stm32f429-apollo 开发板上 ` SPI Flash` 挂在了 SPI5 总线上,对应的 `SPI Device` 的设备名为 `spi50`。在终端输入 `list_device` 命令可以看到名为 `spi50` 的设备类型为 `SPI Device`,就说明 SPI 设备添加成功。如果没有出现相应的设备,则需要检查驱动程序,查找错误。 + +![查看设备列表](figures/1528449652153.png) + +为了确保该驱动工作正常,可以使用 `sf` 命令对该设备做 `benchmark` 测试。该功能由 `sfud` 组件提供,可以通过检查存储设备的读、写和擦除功能来判断存储设备的驱动程序是否正常。 如果像下图一样提示成功,所示则认为该驱动工作正常。如果无法通过测试,则需要检查驱动程序,使用逻辑分析仪对存储设备的接口波形进行分析。测试过程如下图: + +![benchmark 测试](figures/1528449677236.png) + +#### 创建存储设备 + +由于只有块设备类型的设备才能和文件系统对接,所以需要根据 `SPI Device` 找到 `SPI Flash` 设备,并创建与其对应的 `Block Device`。 + +这里需要使用到万能 SPI Flash 驱动库:[SFUD](https://github.com/armink/SFUD) ,RT-Thread 已经集成了该组件,在上面的配置过程中我们已经开启这个功能。此时只需要使用 SFUD 提供的 `rt_sfud_flash_probe` 函数即可。该函数将执行如下操作: + + - 根据名为 `spi50` 的 `SPI Device` 设备找到对应的 `Flash` 存储设备。 + + - 初始化 `Flash` 设备。 + + - 在 Flash 存储设备上创建名为 `W25Q256` 的 `Block Device`。 + +- 如果开启了组件自动初始化功能,该函数会被自动执行,否则需要手动调用运行。 + +```c +static int rt_hw_spi_flash_with_sfud_init(void) +{ + if (RT_NULL == rt_sfud_flash_probe("W25Q256", "spi50")) + { + return RT_ERROR; + }; + + return RT_EOK; +} +INIT_COMPONENT_EXPORT(rt_hw_spi_flash_with_sfud_init) +``` + +在终端输入 `list_device` 命令如果看到名为 `W25Q256` 的设备类型为 `Block Device`,这说明块设备已经创建成功,如果失败则需要对 `spi50` 设备进行检查。如下图所示: + +![查看块设备](figures/1528362120705.png) + +获得可以用于挂载的块类型设备,那么移植的工作就算完成了。 + +## 文件系统的使用 + +### 文件系统初始化 + +RT-Thread 文件系统初始化过程一般按以下流程来进行: + +1. 初始化 DFS 框架 +2. 初始化具体文件系统 +3. 初始化存储设备 + +下面我们按照这样的顺序来逐步讲解文件系统的初始化过程: + +#### DFS 框架的初始化 + +DFS 框架的初始化主要是对内部数据结构以及资源的初始化。这一过程包括初始化文件系统必须的数据表,以及互斥锁。该功能由如下函数完成。如果开启了组件自动初始化功能,该函数会被自动执行,否则需要手动调用运行。 + +![DFS 框架的初始化](figures/1528451222822.png) + +#### 中间层文件系统的初始化 + +这一步的初始化主要是将 `elm FatFS` 的操作函数注册到 DFS 框架中。该功能由如下函数完成。如果开启了组件自动初始化功能,该函数会被自动执行,否则需要手动调用运行。 + +![文件系统初始化](figures/1528451278720.png) + +#### 存储设备的初始化 + +存储设备的初始化可以参考创建存储设备小节。 + +### 创建文件系统 + +第一次使用 `SPI Flash` 作为文件系统地存储设备时,如果我们直接重启开发板来挂载文件系统,就会看到 `spi flash mount to /spi failed!` 的提示。这是因为此时在 SPI Flash 中还没有创建相应类型的文件系统,这就用到了创建文件系统 shell 命令:`mkfs`。 + +`mkfs` 命令的功能是在指定的存储设备上创建指定类型的文件系统。使用格式为:`mkfs [-t type] device` 。第一次挂载文件系统前需要使用 `mkfs` 命令在存储设备上创建相应的文件系统,否则就会挂载失败。如果要在 `W25Q256` 设备上创建 `elm` 类型的文件系统,就可以使用 `mkfs -t elm W25Q256` 命令,使用方法如下图: + +![创建文件系统](figures/1528416425397.png) + +文件系统创建完成后需要重启设备。 + +### 文件系统的挂载 + +文件系统的挂载指的是将文件系统和具体的存储设备关联起来,并挂载到某个挂载点,这个挂载点即为这个文件系统的根目录。在下面的示例中,我们将 `elm FatFS` 文件系统和名为 `W25Q256` 的存储设备关联起来,并且挂载到 `/spi` 文件夹中。(这里可以挂载到 `/spi` 文件夹的原因是 `stm32f429-apollo BSP` 的文件系统根目录已经挂载了 `RomFS`,并且已经创建了 `/spi` 文件夹。如果没有特殊情况,文件系统可以直接挂载到根目录 `/` 上。) + +挂载文件系统的操作由 `dfs_mount()` 函数完成,`dfs_mount()` 函数的参数分别为:块设备名、文件系统挂载点路径、挂载文件系统类型、读写标志位以及文件系统的私有数据,使用方法如下图所示: + +![挂载文件系统](figures/1528450972107.png) + +经过了上面的创建文件系统操作,我们重启开发板(会自动重新执行挂载函数),就可以成功地挂载文件系统了。可以看到提示 `spi flash mount to /spi !` 。这时再次使用 `list_device` 命令可以看到 `W25Q256` 设备已经被挂载成功。如下图所示: + +![查看挂载状态](figures/1528417402476.png) + +到这一步为止,文件系统已经初始化完成,接下来可以对文件和目录进行操作了。 + +### FinSH 命令 + +在这一小节介绍关于文件和目录操作常用的 shell 命令: + +使用 `ls` 命令显示文件和目录的信息: + +![ls 命令](figures/1528419313880.png) + +使用 `cd` 命令切换到指定工作目录: + +![cd 命令](figures/1528419330733.png) + +使用 `cp` 命令 copy 文件: + +![cp 命令](figures/1528419369526.png) + +使用 `rm` 命令删除文件或目录: + +![rm 命令](figures/1528419422313.png) + +使用 `mv` 命令将文件移动位置或者改名: + +![mv 命令](figures/1528419519371.png) + +使用 `echo` 命令将指定内容写入文件: + +![echo 命令](figures/1528460010565.png) + +使用 `cat` 命令展示文件的内容: + +![cat 命令](figures/1528419537678.png) + +使用 `pwd` 命令打印出当前目录地址: + +![pwd 命令](figures/1528419554110.png) + +使用 `mkdir` 命令创建文件夹: + +![mkdir 命令](figures/1528419584942.png) + +### 文件操作示例 + +本节以创建文件夹操作为例,介绍如何使用 RT-Thread 文件系统 Sample 来对文件系统进行操作。 + +- 在 `menuconfig` 配置界面依次选择 `RT-Thread online packages → miscellaneous packages → filesystem sample options`,选中 `[filesystem] mkdir` 选项,如下图所示: + +![选中 mkdir 功能](figures/1528420167135.png) + +- 保存并退出后,使用 `pkgs --update` 命令更新软件包,然后使用 `scons --target=mdk5 -s` 命令重新生成工程。可以看到该 Sample 已经添加到工程中: + +![已添加 mkdir sample](figures/1528421204197.png) + +- 这里需要注意的是由于我们文件系统的根目录挂载了 `RomFS`,不可修改,所以我们不能直接在根目录创建文件夹。因此,我们需要对程序进行简单的修改,如下图所示: + +![创建目录](figures/1528423169454.png) + +- 重新编译后下载运行,在 msh 中可以使用 `mkdir_sample_init` 命令来创建 web 文件夹,效果如下图所示: + +![创建成功](figures/1528423305271.png) + +- 此时切换到 `/spi` 文件夹中可以看到 web 文件夹已经被创建。 + +![查看目录](figures/1528423429971.png) + +- 文件系统提供的 Sample 还有 `openfile`、`readwrite`、`stat`、`rename`、`opendir`、`readdir` 、`tell_seek_dir`,大家可以用上面的方法来使用这些功能。 + +## 参考资料 + +* [《虚拟文件系统》](../../../programming-manual/filesystem/filesystem.md) + +* [《Env 用户手册》](../../../programming-manual/env/env.md) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/an0027-littlefs.md b/rt-thread-version/rt-thread-standard/application-note/components/dfs/an0027-littlefs.md new file mode 100644 index 0000000000000000000000000000000000000000..d4b3dae96208d4aaf6b6f1209b7e95a54a3d990b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/dfs/an0027-littlefs.md @@ -0,0 +1,242 @@ +# 在 STM32L4 上应用 littlefs 文件系统 + +本文介绍了 littlefs 文件系统的基本知识和使用方法,帮助开发者更好地使用 littlefs 文件系统。并给出了基于 FAL 移植的代码示例。 + +## littlefs 简介 + +littlefs 是 ARM 官方推出的,专为嵌入式系统设计的文件系统,相比传统的文件系统,littlefs 具有以下优点: + +- 自带擦写均衡 +- 支持掉电保护 +- 占用的 RAM/ROM 少 + +littlefs 自带的擦写均衡和掉电保护使开发者可以放心的将文件系统挂载到 nor flash 上。 + +## 名词解释 + +在 RT-Thread 上使用 littlefs,会依赖一些软件包或者系统组件,下面会对这些需要用到的名词做出解释: + +1. DFS 框架 + + [DFS 框架](https://www.rt-thread.org/document/site/programming-manual/filesystem/filesystem/) 是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System,即设备虚拟文件系统。DFS 框架为应用程序提供统一的 POSIX 文件和目录操作接口,如 read、write、poll/select 等。DFS 框架支持多种类型的文件系统,如 FatFS、RomFS、DevFS 等,并提供普通文件、设备文件、网络文件描述符的管理。 + +1. MTD 设备 + + MTD 设备,全称为 Memory Technology Device,使用 MTD 设备为 NOR FLASH 和 NAND FLASH 提供统一接口,将文件系统与底层 FLASH 存储器进行了隔离。 + +1. fal 组件 + + [fal](https://github.com/RT-Thread-packages/fal) 全称为 Flash Abstraction Layer,即 Flash 抽象层,是对 Flash 及基于 Flash 的分区进行管理、操作的抽象层,对上层统一了 Flash 及 分区操作的 API。并提供了将分区创建成 MTD 设备的 API。 + +1. SFUD 组件 + + [SFUD](https://github.com/armink/SFUD) 是一款开源的串行 SPI Flash 通用驱动库。现有市面的大部分串行 Flash,用户只需要提供 SPI 或 QSPI 的读写接口,SFUD 就可以识别并驱动。同时 RT-Thread 提供了 FAL 针对 SFUD 的驱动移植,可以使两个组件无缝连接。 + +## 层级关系 + +littlefs 在 RT-Thread 上运行的层级关系图如下所示: + +![hierarchical](figures/hierarchical.png) + +开发者使用的是 DFS 框架提供的统一的 POSIX API,DFS 框架会调用 littlefs 的 API,littlefs 会使用 MTD 设备的读写接口,开发者可以使用 RT-Thread 提供的 fal 组件和 SFUD 组件来完成对 FLASH 的读写任务,也可以自己实现 MTD 设备的驱动程序,使 littlefs 可以挂载到更多的存储介质上。 + +## littlefs 的移植 + +社区热心开发者 [geniusgogo](https://github.com/geniusgogo) 为 RT-Thread 移植了 [littlefs](http://packages.rt-thread.org/itemDetail.html?package=littlefs) 的软件包,开发者只需要提供一个 MTD 设备即可使用 littlefs。开发者可以自己实现一个 MTD 设备,也可以利用 RT-Thread 提供的 fal 组件,非常方便的创建出一个 MTD 设备。相比于直接使用 flash 底层函数来自己构造一个 MTD 设备,使用 fal 创建 MTD 设备有以下三个优点: + +- 创建 MTD 设备方便 +- 驱动的可移植性和可重用性强 +- fal 的分区功能可以让 littlefs 只使用指定区域的 flash + +fal 的详细介绍可以点击 [此处](https://github.com/RT-Thread-packages/fal) 查看。 + +本次演示使用 stm32l475-atk-pandora,由于 RT-Thread 有 littlefs 软件包,所以移植工作较为简单。littlefs 的移植主要包括下面几个方面: + +- 开启 / 配置 DFS 框架 + - 为应用程序提供统一的 POSIX 文件和目录操作接口 +- 使能 littlefs 软件包 +- 使能 MTD 设备 + - 默认不开启 +- 使能 fal + - 用来创建 MTD 设备 +- 使能外置 flash + - 会自动启用 SFUD + - 文件系统的物理载体 +- 创建 MTD 设备 +- 确保开发板上的存储设备驱动正常工作 + +### 使能DFS框架 + +在 BSP 目录 `rt-thread\bsp\stm32\stm32l475-atk-pandora` 下打开 env,输入 menuconfig,在 ` RT-Thread Components → Device virtual file system` 中打开 DFS 框架。 + +![dfs](figures/dfs.png) + +### 配置 littlefs +在 `RT-Thread online packages → system packages → Littlefs: A high-integrity embedded file system` 中打开 littlefs。 + +![littlefs](figures/littlefs.png) + +### 使能 MTD 设备 + +在 `RT-Thread Components → Device Drivers` 中使能 MTD 设备。 + +![mtd_device](figures/mtd_device.png) + +### 配置 fal + +在 `RT-Thread online packages → system packages → fal: Flash Abstraction Layer implement` 中打开 fal。使能 `FAL uses SFUD drivers`,并修改 `FLASH device name` 为 `W25Q128`(SFUD 初始化 FLASH 后创建的设备名)。 + +![fal](figures/fal.png) + +### 使能外置 FLASH + +在 `Hardware Drivers Config → Onboard Peripheral Drivers` 中使能外置 flash。 + +![flash](figures/flash.png) + +### 更新软件包并生成工程 + +设置完成后,退出配置界面,在 env 中输入 pkgs --update,env 会自动把需要用到的软件包下载下来。然后在 env 中输入 scons --target=mdk5,等待自动生成 mdk5 的工程。 + +### 检查分区表 + +打开工程中的 fal_cfg.h,检查宏 `SPI_FLASH_PARTITION` 是否如下所示: + +```c +#define SPI_FLASH_PARTITION {FAL_PART_MAGIC_WROD, "filesystem", "W25Q128", 9 * 1024 * 1024, 16 * 1024 * 1024, 0}, +``` + +默认的 filesystem 分区起始地址是 FLASH 零地址往后偏移 9MB,这是为了兼容 RT-Thread 提供的针对潘多拉开发板的一系列例程。如果开发者需要更大的分区,可以参考 fal 软件包的介绍自行修改,这里不再赘述。 + +### 创建 MTD 设备并挂载文件系统 + +fal 组件并没有加入自动初始化的代码,所以我们需要在 main 函数中初始化 fal,并使用 fal 提供的 API 来创建一个 MTD 设备。创建 MTD 设备后,就可以将 littlefs 挂载到刚刚生成的 MTD 设备上了。 + +在 main.c 文件中添加的代码如下所示: + +```c +... +/* 添加 fal 头文件 */ +#include +/* 添加文件系统头文件 */ +#include + +/* 添加 DEBUG 头文件 */ +#define DBG_SECTION_NAME "main" +#define DBG_LEVEL DBG_INFO +#include +/* 定义要使用的分区名字 */ +#define FS_PARTITION_NAME "filesystem" + +int main(void) +{ + ... + struct rt_device *mtd_dev = RT_NULL; + + ... + /* 初始化 fal */ + fal_init(); + /* 生成 mtd 设备 */ + mtd_dev = fal_mtd_nor_device_create(FS_PARTITION_NAME); + if (!mtd_dev) + { + LOG_E("Can't create a mtd device on '%s' partition.", FS_PARTITION_NAME); + } + else + { + /* 挂载 littlefs */ + if (dfs_mount(FS_PARTITION_NAME, "/", "lfs", 0, 0) == 0) + { + LOG_I("Filesystem initialized!"); + } + else + { + /* 格式化文件系统 */ + dfs_mkfs("lfs", FS_PARTITION_NAME); + /* 挂载 littlefs */ + if (dfs_mount("filesystem", "/", "lfs", 0, 0) == 0) + { + LOG_I("Filesystem initialized!"); + } + else + { + LOG_E("Failed to initialize filesystem!"); + } + } + } + + while (1) + { + ... + } + + return RT_EOK; +} + +``` + +### 验证 fal 分区和 MTD 设备 + +移植工作已经完成,我们需要将代码下载到开发板,检查 fal 分区是否正常,MTD 设备是否创建成功,文件系统有没有挂载成功。将工程编译下载,查看系统启动时是否打印了 fal 分区、 MTD 设备创建成功和文件系统初始化成功的提示信息。 + +分区表的打印和 MTD 设备创建成功的提示信息如下所示: + +```c + \ | / +- RT - Thread Operating System + / | \ 4.0.1 build Mar 26 2019 + 2006 - 2019 Copyright by rt-thread team +[D/drv.qspi] qspi init succsee! +[SFUD] Find a Winbond flash chip. Size is 16777216 bytes. +[SFUD] W25Q128 flash device is initialize success. +[D/drv.qspi] qspi init succsee! +[D/FAL] (fal_flash_init:61) Flash device | W25Q128 | addr: 0x00000000 | len: 0x01000000 | blk_size: 0x00001000 |initialized finish. +[I/FAL] ==================== FAL partition table ==================== +[I/FAL] | name | flash_dev | offset | length | +[I/FAL] ------------------------------------------------------------- +[I/FAL] | filesystem | W25Q128 | 0x00900000 | 0x01000000 | +[I/FAL] ============================================================= +[I/FAL] RT-Thread Flash Abstraction Layer (V0.4.0) initialize success. +[I/FAL] The FAL MTD NOR device (filesystem) created successfully +[I/main] Filesystem initialized! +``` + +## 文件系统的使用 + +RT-thread 支持的文件系统很多,但是所有的文件系统都对接到 DFS 框架。对于上层应用,DFS 框架提供了统一的 POSIX 文件和目录操作接口。开发者更换文件系统后,可以将原来的代码无缝的移植到新的文件系统上而不需要修改代码。有关文件系统的示例代码可以点击 [此处](https://www.rt-thread.org/document/site/programming-manual/filesystem/filesystem/#dfs_4) 查看。 + +## FinSH 命令 + +启用 DFS 框架后,RT-Thread 的 FinSH 控制台就会新添加一些命令,我们可以利用这些命令快速的来验证文件系统是否正常工作。 + +使用 `ls` 命令查看当前目录信息,运行结果如下所示: + +```msh +msh />ls # 使用 ls 命令查看文件系统目录信息 +Directory /: # 可以看到已经存在根目录 / +``` + +使用 `echo` 命令将输入的字符串输出到指定输出位置,运行结果如下所示: + +```msh +msh />echo "hello RT-Thread!!!" hello.txt # 将字符串出输出到 hello.txt 文件 +msh />ls +Directory /: +hello.txt 18 +msh /> +``` + +使用 `cat` 命令查看文件内容,运行结果如下所示: + +```msh +msh />cat hello.txt # 查看 hello.txt 文件的内容并输出 +hello RT-Thread!!! +``` + +## 参考资料 + +* [《虚拟文件系统》](../../../programming-manual/filesystem/filesystem.md) + +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +* [《在 STM32F429 上应用文件系统》](./an0012-dfs.md) diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528354745063.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528354745063.png new file mode 100644 index 0000000000000000000000000000000000000000..878f3371f7fcb2592dbda99955949f326b724acf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528354745063.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528355204158.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528355204158.png new file mode 100644 index 0000000000000000000000000000000000000000..d40470ca5a3b0316e417e5e4969dbe88065d4869 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528355204158.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528355291434.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528355291434.png new file mode 100644 index 0000000000000000000000000000000000000000..984ce56767759f1ac53fe1c670959fb96b413f3a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528355291434.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528356393167.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528356393167.png new file mode 100644 index 0000000000000000000000000000000000000000..a0913beec775946a93128a21aad2e228a67136e7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528356393167.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528356861018.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528356861018.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd188e83d474d9291d11d7150a8398e82f14c66 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528356861018.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528362120705.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528362120705.png new file mode 100644 index 0000000000000000000000000000000000000000..f1713c134b80f3383322e6912b87fc91d032e1cf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528362120705.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528416425397.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528416425397.png new file mode 100644 index 0000000000000000000000000000000000000000..fd17fe40fd0abf597da69cdad27537e0195a3784 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528416425397.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528417402476.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528417402476.png new file mode 100644 index 0000000000000000000000000000000000000000..925f7461f33d5bd9e58170400b3d530e1dc05853 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528417402476.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419313880.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419313880.png new file mode 100644 index 0000000000000000000000000000000000000000..66ef837d02245674cba3e1fc63b8a68d91092d88 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419313880.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419330733.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419330733.png new file mode 100644 index 0000000000000000000000000000000000000000..c71ce97f3cff0d549586508fe1780c2c617b95c0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419330733.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419369526.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419369526.png new file mode 100644 index 0000000000000000000000000000000000000000..a00b6a20d087c05f5ad304091fb45f60342b0df1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419369526.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419422313.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419422313.png new file mode 100644 index 0000000000000000000000000000000000000000..04c6079dca87f042102b650cdd021811d57384f7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419422313.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419519371.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419519371.png new file mode 100644 index 0000000000000000000000000000000000000000..c2e5dc12a9a75b8055b93ecc2fbb42cd846eb5ab Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419519371.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419537678.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419537678.png new file mode 100644 index 0000000000000000000000000000000000000000..4a8383eac58e3339d04f82cab648b7c9470052a7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419537678.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419554110.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419554110.png new file mode 100644 index 0000000000000000000000000000000000000000..c6b34a6ae504ff6bc5b1df748908e86f19142c1c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419554110.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419584942.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419584942.png new file mode 100644 index 0000000000000000000000000000000000000000..921e0411927dc4826865ee1af97329257c34afaa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528419584942.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528420167135.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528420167135.png new file mode 100644 index 0000000000000000000000000000000000000000..c88365ca327c5eaa78418dad4309ba856369b537 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528420167135.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528421204197.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528421204197.png new file mode 100644 index 0000000000000000000000000000000000000000..0031fd31d3523b7063223a867581d076646dc3ce Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528421204197.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423169454.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423169454.png new file mode 100644 index 0000000000000000000000000000000000000000..f6a4d73cda5aa69d029e6a271952c5c978e467f5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423169454.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423305271.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423305271.png new file mode 100644 index 0000000000000000000000000000000000000000..dea73bd76b52d6c96c1517a003a37421a76de004 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423305271.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423429971.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423429971.png new file mode 100644 index 0000000000000000000000000000000000000000..f910a8d6a85b2a69be1dfb4be4a1e111a2fe1994 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528423429971.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528449652153.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528449652153.png new file mode 100644 index 0000000000000000000000000000000000000000..e91b1dbee5b681ad4e9f40d42f712550ecba13d6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528449652153.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528449677236.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528449677236.png new file mode 100644 index 0000000000000000000000000000000000000000..2e415d403b195859f6fd65d192d061ae0415eec4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528449677236.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528450972107.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528450972107.png new file mode 100644 index 0000000000000000000000000000000000000000..7528808f82a738495258e521a6aac2eaeb535230 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528450972107.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528451222822.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528451222822.png new file mode 100644 index 0000000000000000000000000000000000000000..fcb0343808c5b4e1469aec50823cc336793aaef8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528451222822.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528451278720.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528451278720.png new file mode 100644 index 0000000000000000000000000000000000000000..c618127c53b123668c86c01eaa9b4faeeab69650 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528451278720.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528457058001.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528457058001.png new file mode 100644 index 0000000000000000000000000000000000000000..da383bdc97e2f77e804dd79d3ede3cdfc18b6fce Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528457058001.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528459997690.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528459997690.png new file mode 100644 index 0000000000000000000000000000000000000000..5c8d59beab83592d708e58b9f37fc182bba7a298 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528459997690.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528460010565.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528460010565.png new file mode 100644 index 0000000000000000000000000000000000000000..5c8d59beab83592d708e58b9f37fc182bba7a298 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528460010565.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528680175178.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528680175178.png new file mode 100644 index 0000000000000000000000000000000000000000..82ca239d8f39ab586d2f8b8a52c245f5f2f2ba28 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/1528680175178.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/dfs.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/dfs.png new file mode 100644 index 0000000000000000000000000000000000000000..85db1cbad52399071edd7fde137ce67d3b7ae28f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/dfs.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/fal.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/fal.png new file mode 100644 index 0000000000000000000000000000000000000000..bef1309e777b97ab31cb47aaf37197e3bd54ed78 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/fal.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/flash.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/flash.png new file mode 100644 index 0000000000000000000000000000000000000000..157f8aa08ea465d1cbe6e7903a491d71e60e2ec2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/flash.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/hierarchical.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/hierarchical.png new file mode 100644 index 0000000000000000000000000000000000000000..15c9cf2f6af632df96c6848b06db2238c52fefc1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/hierarchical.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/littlefs.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/littlefs.png new file mode 100644 index 0000000000000000000000000000000000000000..2a742524a7b67e407d681d8d8df00b3357ac867a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/littlefs.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/mtd_device.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/mtd_device.png new file mode 100644 index 0000000000000000000000000000000000000000..f8f3606cd95737f92cbc186a8bb9e52dc5e3b580 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/mtd_device.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/rt_fs_structure.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/rt_fs_structure.png new file mode 100644 index 0000000000000000000000000000000000000000..bc652a5ea08a3d353e6a078df6c55c3ca6da1dc2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/rt_fs_structure.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/set_sector_size.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/set_sector_size.png new file mode 100644 index 0000000000000000000000000000000000000000..6b319de52590604e9b43e5491325f0e571458479 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/set_sector_size.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/stm32f429-apollo.png b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/stm32f429-apollo.png new file mode 100644 index 0000000000000000000000000000000000000000..ab9ffea70c8ecea4fdf9f218b1f56570dd02f1ba Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dfs/figures/stm32f429-apollo.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/an0023-dlmodule.md b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/an0023-dlmodule.md new file mode 100644 index 0000000000000000000000000000000000000000..2681684e05a4c96eb3023140fadbb9c1867dad3d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/an0023-dlmodule.md @@ -0,0 +1,348 @@ +# 使用 QEMU 运行动态模块组件 # + +本文描述了在 Windows 平台使用 QEMU 运行 RT-Thread 动态模块及动态库。 + +## 简介 + +RT-Thread 动态模块组件 `dlmodule` 提供了动态加载程序模块的机制。dlmodule 组件更多的是一个 ELF 格式加载器,把单独编译的一个 elf 文件的代码段,数据段加载到内存中,并对其中的符号进行解析,绑定到内核导出的 API 地址上。动态模块 elf 文件主要放置于 RT-Thread 下的文件系统上。 + +RT-Thread 的动态模块组件目前支持两种格式: + +* `.mo` 则是编译出来时以 `.mo` 做为后缀名的可执行动态模块。它可以被加载,并且系统中会自动创建一个主线程执行这个动态模块中的 `main` 函数;同时这个 `main(int argc, char** argv)` 函数也可以接受命令行上的参数。 +* `.so` 则是编译出来时以 `.so` 做为后缀名的动态库。它可以被加载,并驻留在内存中,并提供一些函数集由其他程序(内核里的代码或动态模块)来使用。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +* [rtthread-apps](https://github.com/RT-Thread/rtthread-apps) + +> [!NOTE] +> 注:动态模块组件部分源代码编译当前只支持 GNU GCC 工具链编译,暂不支持 MDK 或者 IAR。 + +## 使能动态模块组件 + +### 配置工程 + +在 Env 控制台切换到 qemu-vexpress-a9 BSP 根目录,然后输入 `menuconfig` 命令打开配置菜单。 + +![menuconfig 打开配置菜单](figures/menuconfig.png) + +进入“ RT-Thread Components → POSIX layer and C standard library”菜单,按下图箭头所示打开 libc 和动态模块的配置选项。 + +![开启动态模块](figures/enable-dlmodule.png) + +进入“RT-Thread Components → Device virtual file system”菜单打开文件系统的配置选项。退出 menuconfig 并保存配置。 + +![开启文件系统](figures/dfs.png) + +### 编译工程 + +使用 `scons` 命令编译工程。 + +![编译工程](figures/scons.png) + +### 运行动态模块命令 + +编译完成后使用 `qemu.bat` 命令运行工程。按 Tab 键查看所有命令可以看到动态模块的两个命令 `list_module` 和 `list_symbols`,表明动态模块组件配置成功。 + +* `list_module` 命令可以查看当前正在运行的动态模块。 +* `list_symbols` 命令可以查看动态模块可以使用的函数及其对应的内存地址。加载动态模块的时候会对其中的符号进行解析,并绑定到对应的函数地址上。 + +![运行动态模块命令](figures/add-dlmodule.png) + +### 生成动态模块编译依赖环境 + +关闭运行的程序,在 Env 控制台使用 `scons --target=ua -s` 命令生成编译动态模块时需要包括的内核头文件搜索路径及全局宏定义。 + +![生成动态模块编译依赖环境](figures/scons-ua.png) + +## 运行动态模块 + +### 运行最简单的动态模块 + +#### 创建动态模块 + +##### 获取示例 + + 下载 RT-Thread 动态模块工具库 [rtthread-apps](https://github.com/RT-Thread/rtthread-apps),rtthread-apps 的 tools 目录放置了编译动态模块需要使用到的 Python 和 SConscript 脚本。hello 目录下的 main.c 是一个简单的动态模块使用示例,源代码如下所示。 + +``` +#include + +int main(int argc, char *argv[]) +{ + printf("Hello, world\n"); + + return 0; +} +``` + +这段代码实现了一个最简单的 main 函数,打印字符串“Hello world”。 + +##### 设置环境变量 + +在 Env 控制台切换到 rtthread-apps 根目录(目录所在全路径不包含空格和中文字符),然后通过下面 2 条命令设置环境变量。 + +* `set RTT_ROOT=d:\repository\rt-thread`,设置 RTT_ROOT 为 RT-Thread 源代码根目录。 +* `set BSP_ROOT=d:\repository\rt-thread\bsp\qemu-vexpress-a9`,设置 BSP_ROOT 为 qemu-vexpress-a9 BSP 根目录。 + +![设置环境变量](figures/set-env.png) + +##### 编译动态模块 + +使用 `scons --app=hello` 命令编译动态模块。 + +![编译动态模块](figures/scons-app.png) + +在 rtthread-apps/hello 目录下会生成动态模块文件 hello.mo。 + +#### 将动态模块放入文件系统 + +编译好的动态模块 hello.mo 需要放到文件系统下。qemu-vexpress-a9 BSP 会使用一个虚拟的 sd 卡设备 sd.bin,我们需要把动态模块放到这个虚拟的 sd 卡里面。对于物理设备来说,直接将动态模块添加到文件系统管理的存储设备中就可以。这里需要使用到 Env 工具里面的一个小工具 fatdisk,它位于 Env 的 tools 目录下,里面也提供了一份 fatdisk 的使用说明。这里使用 fatdisk 用于把 PC 上本地的一个目录转换成 sd.bin 映像文件,这个映像文件是做为一个 fat 文件系统而存在。 + +##### 新建目录 + +在 fatdisk 目录下新建一个 sd 目录,并复制刚刚编译的动态模块 hello.mo 文件到 sd 目录。 + +![增加 fatdisk 配置文件](figures/add-hello.png) + +##### 修改配置文件 + +按照下面的配置修改 fatdisk 目录下的配置文件 fatdisk.xml。 + +* 映像文件空间大小 disk_size 配置为了 5120Kbytes(大小可根据需要配置)。 +* 映像文件的扇区大小 sector_size 需要配置为 512 KBytes。 +* 要转换目录名 root_dir 配置为 sd,表示当前目录下的 sd 目录。 +* 指定生成的映像文件名称 output 配置为 sd.bin。 +* strip 需要配置为 0。 + +``` + + + 5120 + 512 + sd + sd.bin + 0 + +``` + +##### 生成映像文件 + +在 Env 控制台切换到 fatdisk 根目录,运行 `fatdisk` 命令则会按照配置文件 fatdisk.xml 中的配置,把里面指定的目录转换成 flash 映像文件。 + +![运行 fatdisk 命令](figures/fatdisk-hello.png) + +运行成功则会在 fatdisk 目录生成一个 sd.bin 文件,大小为 5MB。 + +![生成 sd.bin 文件](figures/sd-bin-new.png) + +生成的映像文件 sd.bin 需要复制到 qemu-vexpress-a9 BSP 目录。 + +#### 运行动态模块 + +在 Env 控制台切换到 qemu-vexpress-a9 BSP 根目录输入 `qemu.bat` 命令运行工程。 + +![运行 qemu 工程](figures/hello.png) + +* 系统运行起来后会看到文件系统初始化成功信息 “file system initialization done!”。 +* 使用 `ls` 命令可以看到根目录下的动态模块文件 `hello.mo`。 +* 输入 `hello` 命令运行动态模块 hello.mo。可以看到动态模块main函数打印的字符串“Hello,world” + +使用动态模块组件运行动态模块的主要原理如下图所示: + +![动态模块的运行原理](figures/relation.png) + +### 动态模块的初始化和清理函数 + +动态模块组件提供了2个扩展的函数供用户使用,分别是`module_init()`和`module_cleanup()`。 + +* `module_init()`函数会在动态模块运行前被执行,用户可以根据需要做一些初始化工作。 +* `module_cleanup()`函数会在动态模块运行结束后在 idle 线程里回调一次,执行用户设置的清理工作。 + +RT-Thread 系统会自动创建一个线程执行动态模块中的 main 函数,同时这个 `main(int argc, char* argv[])` 函数也可以接受命令行上的参数。这个线程默认的优先级等同空闲线程的优先级,线程堆栈默认为2048字节。用户可以在`module_init()`函数里修改这个线程的优先级和堆栈。 + +#### 示例代码 + +基于前面简单的动态模块示例代码 main.c 增加`module_init()`和`module_cleanup()`函数的使用,示例代码如下所示。 + +``` +#include +#include + +/* 动态模块的初始化函数 */ +void module_init(struct rt_dlmodule *module) +{ + module->priority = 8; + module->stack_size = 4096; + + printf("this is module %s initial function!\n",module->parent.name); +} + +/* 动态模块的清理函数 */ +void module_cleanup(struct rt_dlmodule *module) +{ + printf("this is module %s cleanup function!\n",module->parent.name); +} + +int main(int argc, char *argv[]) +{ + int i; + + printf("hello world from RTT::dynamic module!\n"); + + /* 打印命令行参数 */ + for(i = 0;i < argc;i ++) + { + printf("argv[%d]:%s\n",i,argv[i]); + } + + return 0; +} +``` + +示例代码主要实现了如下功能: + +* 在动态模块的初始化函数里可以设置这个线程的优先级和堆栈。 +* 清理函数简单的打印信息。 +* main 函数解析命令行参数并打印出来。 + +请参考前面小节将此示例代码生成的动态模块文件放到文件系统里,并将生成的映像文件 sd.bin 复制到 qemu-vexpress-a9 BSP 目录。 + +#### 运行结果 + +在 Env 控制台切换到 qemu-vexpress-a9 BSP 根目录输入 `qemu.bat` 命令运行工程。 + +![运行 qemu 工程](figures/rundlmodule.png) + +* 系统运行起来后会看到文件系统初始化成功信息 “file system initialization done!”。 +* 使用 `ls` 命令可以看到根目录下的动态模块文件 `hello.mo`。 +* 输入 `hello this is rt-thread!` 命令运行动态模块 hello.mo。hello 后面的字符串为参数。 +* 执行到动态模块初始化函数 `module_init` 时会打印字符串 "this is module hello initial function!"。 +* 执行动态模块的 main 函数时会打印字符串 “hello world from RTT::dynamic module!”,命令行参数也依次打印了出来。 +* 动态模块运行结束后又执行清理函数 `module_cleanup`,打印字符串 "this is module hello cleanup function!"。 + +## 运行动态库 + +### 创建动态库 + +#### 获取示例 + + 下载 RT-Thread 动态模块工具库 [rtthread-apps](https://github.com/RT-Thread/rtthread-apps),rtthread-apps 的 lib 目录下有一个简单的动态库示例的 lib.c,源代码如下所示,它实现了 2 个简单的函数供使用。 + +``` +#include + +int lib_func(void) +{ + printf("hello world from RTT::dynamic library!\n"); + + return 0; +} + +int add_func(int a, int b) +{ + return (a + b); +} +``` + +#### 编译动态库 + +编译动态库之前需要先设置环境变量。然后使用 `scons --lib=lib` 命令编译动态库。 + +![编译动态模块](figures/scons-lib.png) + +在 rtthread-apps/lib 目录下会生成动态库文件 lib.so。 + +请参考前面小节将动态库文件 lib.so 放到文件系统里,并将生成的映像文件 sd.bin 复制到 qemu-vexpress-a9 BSP 目录。 + +### 运行动态库 + +#### 添加示例代码 + +将以下示例代码添加到 qemu-vexpress-a9 BSP applications 目录下的 main.c 里。 + +``` +#include +#include +#include + +/* 动态库文件路径 */ +#define APP_PATH "/lib.so" + +/* 函数指针类型 */ +typedef int (*add_func_t)(int, int); +typedef void (*lib_func_t)(void); + +int dlmodule_sample(void) +{ + void* handle; + lib_func_t lib_function; + add_func_t add_function; + /* 以RTLD_LAZY模式打开动态库文件,并获取动态库操作句柄 */ + handle = dlopen(APP_PATH,RTLD_LAZY); + + if(!handle) + { + printf("dlopen %s failed!\n",APP_PATH); + return -1; + } + + /* 根据动态库操作句柄handle,返回动态库函数lib_func()对应的地址 */ + lib_function = (lib_func_t)dlsym(handle,"lib_func"); + if(!lib_function) + { + printf("dlsym %p failed!\n",handle); + return -1; + } + /* 运行动态库函数 */ + lib_function(); + /* 根据动态库操作句柄handle,返回动态库函数add_func()对应的地址 */ + add_function = (add_func_t)dlsym(handle,"add_func"); + if(!add_function) + { + printf("dlsym %p failed!\n",handle); + return -1; + } + /* 运行动态库函数计算 3+4 并打印结果 */ + printf("add_function result is:%d\n",add_function(3,4)); + /* 运行完毕根据操作句柄handle关闭动态库 */ + dlclose(handle); + + return 0; +} + +MSH_CMD_EXPORT(dlmodule_sample, dlmodule sample); + +int main(void) +{ + printf("hello rt-thread!\n"); + + return 0; +} +``` + +RT-Thread 动态模块组件也支持 POSIX 标准的 libdl API,此示例代码调用 libdl API 运行动态库。示例代码首先根据动态库的路径打开动态库文件 lib.so,然后获取动态库的 lib_func() 函数的地址并运行此函数。之后获取动态库的 add_func() 函数的地址,并传入参数 3 和 4 运行函数计算结果。最后关闭动态库。 + +#### 运行动态库 + +在 Env 控制台切换到 qemu-vexpress-a9 BSP 根目录,输入 `scons` 命令重新编译工程。编译完成后输入 `qemu.bat` 命令运行工程。按 Tab 键可以看到新增的示例代码命令 `dlmodule_Sample`。 + +![运行 qemu 工程](figures/run-lib.png) + +使用 `ls` 命令可以看到根目录下的动态库文件 lib.so,输入 `dlmodule_sample` 命令就可以运行动态库示例代码。 + +![运行动态库示例](figures/run-lib2.png) + +* 第一行运行了 lib_func() 函数打印了字符串 “hello world from RTT::dynamic library!” +* 第二行运行了 add_func() 函数计算了 3+4 并打印了相加结果 7。 + +## 参考资料 + +* [《动态模块》](../../../programming-manual/dlmodule/dlmodule.md) + +* [《Env 用户手册》](../../../programming-manual/env/env.md) + diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/add-dlmodule.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/add-dlmodule.png new file mode 100644 index 0000000000000000000000000000000000000000..3374f8924caf713f48deac0afed12cbf29679e67 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/add-dlmodule.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/add-hello.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/add-hello.png new file mode 100644 index 0000000000000000000000000000000000000000..cd56ae7b18df08c951f8b87b17b4bd8c706d4055 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/add-hello.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/dfs.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/dfs.png new file mode 100644 index 0000000000000000000000000000000000000000..6c77d3481b6f9e7ebb9e7741edd0a0ae3f20f40c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/dfs.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/enable-dlmodule.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/enable-dlmodule.png new file mode 100644 index 0000000000000000000000000000000000000000..8a37ac0bee19aa8172c346bf8cef4e5041f046fa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/enable-dlmodule.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/fatdisk-hello.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/fatdisk-hello.png new file mode 100644 index 0000000000000000000000000000000000000000..ab5688fb00a174e1e6a3062c782951a8f930d0b7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/fatdisk-hello.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/hello.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/hello.png new file mode 100644 index 0000000000000000000000000000000000000000..32ccfa10aa81d96a16bde4d15afcf6f53142f483 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/hello.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..a726b97b6f3555e5d14157a8c4182264fa725a62 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/relation.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/relation.png new file mode 100644 index 0000000000000000000000000000000000000000..ca7fb00fbb0942562d41747efc343e5789c48dbd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/relation.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/run-lib.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/run-lib.png new file mode 100644 index 0000000000000000000000000000000000000000..be8197eed3f29311c308f7e5075fe2df545315f9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/run-lib.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/run-lib2.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/run-lib2.png new file mode 100644 index 0000000000000000000000000000000000000000..151f3a2037fc8a20124b9ee76694406a10248fe8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/run-lib2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/rundlmodule.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/rundlmodule.png new file mode 100644 index 0000000000000000000000000000000000000000..70197de165dbfe76e6aece83954f23d80979dc09 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/rundlmodule.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-app.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-app.png new file mode 100644 index 0000000000000000000000000000000000000000..9dfefb66709b78c7fcc0de40b6a8333e97777719 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-app.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-lib.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-lib.png new file mode 100644 index 0000000000000000000000000000000000000000..c8db74071e92e7705d96f823eaccca8ac5aa1242 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-lib.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-ua.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-ua.png new file mode 100644 index 0000000000000000000000000000000000000000..1735f56553dd8461a76821e83ab945e1ac919318 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons-ua.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons.png new file mode 100644 index 0000000000000000000000000000000000000000..aa1acfc3a5d5e36759bf94c021b82eb10dac2971 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/scons.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/sd-bin-new.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/sd-bin-new.png new file mode 100644 index 0000000000000000000000000000000000000000..81e96bcba821498a9a0879083e32ee72acf107f7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/sd-bin-new.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/set-env.png b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/set-env.png new file mode 100644 index 0000000000000000000000000000000000000000..9a4a6d35806de586d91b2aa960add3f4a2f87fdb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/dlmodule/figures/set-env.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/an0010-lwip-driver-porting.md b/rt-thread-version/rt-thread-standard/application-note/components/network/an0010-lwip-driver-porting.md new file mode 100644 index 0000000000000000000000000000000000000000..c5ab7c1310f18063679419507896b497969f78d8 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/network/an0010-lwip-driver-porting.md @@ -0,0 +1,722 @@ +# 网络协议栈驱动移植笔记 # + +本文描述了如何在 RT-Thread 中,如何根据具体的硬件配置网络驱动,并灵活运用调试手段解决问题。 + +## 简介 + +在 RT-Thread 所支持的 BSP 中,大部分都有支持以太网驱动。但具体到用户的硬件中,可能会和默认的代码有所差异。本文选择相对以太网驱动比较完善的 stm32 BSP,介绍了驱动的主要实现方式,以及针对不同硬件的修改方法。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +## 以太网相关概念简介 + +### 常见的以太网芯片种类 + +以太网芯片有很多种,大致可以分成 3 种: + +- 以太网芯片只有 PHY(物理接口收发器 ),需要单片机带 MAC(以太网媒体接入控制器 ),通过 MII 或者 RMII 接口和单片机通讯。例如 LAN8720。 +- 以太网芯片带 MAC 和 PHY,通过 SPI 接口和单片机通讯。例如 ENC28J60。 +- 以太网芯片带 MAC 和 PHY,通过 SPI 接口和单片机通讯,同时内置硬件协议栈,适合低速单片机。例如 W5500。 + +### 常见名词解释 + +[MAC](https://baike.baidu.com/item/MAC/329671):媒体介入控制层,属于 OSI 模型中数据链路层下层子层 。 + +[PHY](https://baike.baidu.com/item/PHY):PHY 指物理层,OSI 的最底层。 一般指与外部信号接口的芯片。 + +[MII](https://baike.baidu.com/item/MII/16017003):MII (Media Independent Interface),介质无关接口,也被称为媒体独立接口,它是 IEEE-802.3 定义的以太网行业标准,支持 10Mbit/s 和 100Mbit/s 数据传输模式 。 + +[RMII](https://baike.baidu.com/item/RMII/8989175):RMII (Reduced Media Independent Interface) ,简化媒体独立接口,是 IEEE 802.3u 标准中除 MII 接口之外的另一种实现,支持 10Mbit/s 和 100Mbit/s 数据传输模式 。相比 MII,精简了引脚数量。 + +[lwIP](https://baike.baidu.com/item/lwip):lwIP 是瑞典计算机科学院 (SICS) 的 Adam Dunkels 开发的一个小型开源的 [TCP/IP](https://baike.baidu.com/item/TCP%2FIP) 协议栈。实现的重点是在保持 TCP 协议主要功能的基础上减少对 RAM 的占用。 + +pbuf:lwIP 中用来管理数据包的结构体。 + +## 驱动架构图 + +![驱动架构图](figures/an010_lwip_block.png) + +RT-Thread 的 lwIP 移植在原版的基础上,添加了网络设备层以替换原来的驱动层。和原来的驱动层不同的是,对于以太网数据的收发采用了独立的双线程结构,erx 线程和 etx 线程在正常情况下,两者的优先级设置成相同,用户可以根据自身实际要求进行微调以侧重接收或发送。 + +### 数据接收流程 + +![数据接收流程](figures/an010_rt_xxx_eth_rx.png) + +当以太网硬件设备收到网络报文产生中断时,接收到的数据会被存放到接收缓冲区,然后以太网中断程序会发送邮件来唤醒 erx 线程,erx 线程会按照接收到的数据长度来申请 pbuf,并将数据放入 pbuf 的 payload 中,然后将 pbuf 通过邮件发送给 去处理。 + +### 数据发送流程 + +![数据发送流程](figures/etx.png) + +当有数据需要发送时,LwIP 会将数据通过邮件发送给 etx 线程,然后永久等待在 tx_ack 信号量上。etx 线程接收到邮件后,通过调用驱动中的 rt_stm32_eth_tx() 函数发送数据,发送完成之后再发送一次 tx_ack 信号量唤醒 LwIP 。 + +> [!NOTE] +> 注:在一定条件下也可以把 RT-Thread 中加入的 etx/erx 任务移除掉,当移除掉 RX_THREAD 时,需要由其他线程或中断把接收到的 pbuf 数据包提交给 lwIP 主任务,这里不做详细介绍。 + +## 网络设备介绍 + +RT-Thread 网络设备继承了标准设备,是由 eth_device 结构体定义的,这里贴出 eth_device 结构体代码提供参考 + +``` +struct eth_device +{ + /* 标准设备 */ + struct rt_device parent; + + /* lwIP 网络接口 */ + struct netif *netif; + /* 发送应答信号量 */ + struct rt_semaphore tx_ack; + + /* 网络状态标志 */ + rt_uint16_t flags; + rt_uint8_t link_changed; + rt_uint8_t link_status; + + /* 数据包收发接口 */ + struct pbuf* (*eth_rx)(rt_device_t dev); + rt_err_t (*eth_tx)(rt_device_t dev, struct pbuf* p); +}; +``` + +本文以太网驱动比较完善 stm32f407 为例进行讲解,除 ST 固件库以外,需要实现以下驱动: + +### 标准设备接口 + +标准设备接口需要提供给 eth_device 结构体中的 parent 元素: + +```c +static rt_err_t rt_stm32_eth_init(rt_device_t dev); +static rt_err_t rt_stm32_eth_open(rt_device_t dev, rt_uint16_t oflag); +static rt_err_t rt_stm32_eth_close(rt_device_t dev); +static rt_size_t rt_stm32_eth_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +static rt_size_t rt_stm32_eth_write (rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size); +static rt_err_t rt_stm32_eth_control(rt_device_t dev, int cmd, void *args); +``` + +` rt_stm32_eth_init` 用于初始化 DMA 和 MAC 控制器。 + +`rt_stm32_eth_open` 用于上层应用打开网络设备,目前未使用到,直接返回 RT_EOK。 + +`rt_stm32_eth_close` 用于上层应用关闭网络设备,目前未使用到,直接返回 RT_EOK。 + +`rt_stm32_eth_read` 用于上层应用向底层设备进行直接读写的情况,对于网络设备,每个报文都有固定的格式,所以这个接口目前并未使用,直接返回 0 值。 + +`rt_stm32_eth_write` 用于上层应用向底层设备进行直接读写的情况,对于网络设备,每个报文都有固定的格式,所以这个接口目前并未使用,直接返回 0 值。 + +`rt_stm32_eth_control` 用于控制以太网接口设备,目前用于获取以太网接口的 mac 地址。如果需要,也可以通过增加控制字的方式来扩展其他控制功能。 + +### 数据包收发接口 + +对应了 eth_device 结构体中的 `eth_rx` 及 `eth_tx` 元素,实现数据包收发功能,如下所示: + +```c +rt_err_t rt_stm32_eth_tx(rt_device_t dev, struct pbuf* p); +struct pbuf *rt_stm32_eth_rx(rt_device_t dev); +``` + +`rt_stm32_eth_tx` 函数被 etx 线程调用,实现了数据发送的功能。这里贴出部分代码片段提供参考: + +```c +rt_err_t rt_stm32_eth_tx(rt_device_t dev, struct pbuf *p) +{ + ... + + /* 从 pbuf 复制数据到驱动中的缓冲区 */ + for (q = p; q != NULL; q = q->next) + { + /* 获取当前 LWIP 缓冲区中的字节 */ + byteslefttocopy = q->len; + payloadoffset = 0; + + /* 检查要复制的数据长度是否大于 Tx 缓冲区大小 */ + while ((byteslefttocopy + bufferoffset) > ETH_TX_BUF_SIZE) + { + /* 将数据复制到 Tx 缓冲区 */ + memcpy((uint8_t *)((uint8_t *)buffer + bufferoffset), (uint8_t *)((uint8_t *)q->payload + payloadoffset), (ETH_TX_BUF_SIZE - bufferoffset)); + + /* 指向下一个描述符 */ + DmaTxDesc = (ETH_DMADescTypeDef *)(DmaTxDesc->Buffer2NextDescAddr); + + buffer = (uint8_t *)(DmaTxDesc->Buffer1Addr); + + byteslefttocopy = byteslefttocopy - (ETH_TX_BUF_SIZE - bufferoffset); + payloadoffset = payloadoffset + (ETH_TX_BUF_SIZE - bufferoffset); + framelength = framelength + (ETH_TX_BUF_SIZE - bufferoffset); + bufferoffset = 0; + } + + /* 复制剩余的字节 */ + memcpy((uint8_t *)((uint8_t *)buffer + bufferoffset), (uint8_t *)((uint8_t *)q->payload + payloadoffset), byteslefttocopy); + bufferoffset = bufferoffset + byteslefttocopy; + framelength = framelength + byteslefttocopy; + } + + LOG_D("transmit frame lenth :%d", framelength); + + /* 等待锁 */ + while (EthHandle.Lock == HAL_LOCKED); + + /* 发送数据帧 */ + state = HAL_ETH_TransmitFrame(&EthHandle, framelength); + if (state != HAL_OK) + { + LOG_E("eth transmit frame faild: %d", state); + } + + ret = ERR_OK; + + ... + + return ret; +} +``` + +`rt_stm32_eth_rx` 函数被 erx 线程调用,实现了接收数据的功能,这里贴出部分代码片段提供参考: + +```c +struct pbuf *rt_stm32_eth_rx(rt_device_t dev) +{ + .... + + /* 接收数据帧 */ + state = HAL_ETH_GetReceivedFrame_IT(&EthHandle); + if (state != HAL_OK) + { + LOG_D("receive frame faild"); + return NULL; + } + + /* 获取数据长度 */ + len = EthHandle.RxFrameInfos.length; + buffer = (uint8_t *)EthHandle.RxFrameInfos.buffer; + + LOG_D("receive frame len : %d", len); + + if (len> 0) + { + /* 分配 pbuf */ + p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); + } + + if (p != NULL) + { + dmarxdesc = EthHandle.RxFrameInfos.FSRxDesc; + bufferoffset = 0; + for (q = p; q != NULL; q = q->next) + { + byteslefttocopy = q->len; + payloadoffset = 0; + + /* 检查当前 pbuf 中要复制的字节长度是否大于 rx 缓冲区大小 */ + while ((byteslefttocopy + bufferoffset) > ETH_RX_BUF_SIZE) + { + /* 将数据复制到 pbuf */ + memcpy((uint8_t *)((uint8_t *)q->payload + payloadoffset), (uint8_t *)((uint8_t *)buffer + bufferoffset), (ETH_RX_BUF_SIZE - bufferoffset)); + + /* 指向下一个描述符 */ + dmarxdesc = (ETH_DMADescTypeDef *)(dmarxdesc->Buffer2NextDescAddr); + buffer = (uint8_t *)(dmarxdesc->Buffer1Addr); + + byteslefttocopy = byteslefttocopy - (ETH_RX_BUF_SIZE - bufferoffset); + payloadoffset = payloadoffset + (ETH_RX_BUF_SIZE - bufferoffset); + bufferoffset = 0; + } + /* 复制剩余数据 */ + memcpy((uint8_t *)((uint8_t *)q->payload + payloadoffset), (uint8_t *)((uint8_t *)buffer + bufferoffset), byteslefttocopy); + bufferoffset = bufferoffset + byteslefttocopy; + } + } + + ... + + return p; +} +``` + +### 驱动初始化 + +```c +void rt_hw_stm32_eth_init(void); +``` + +`rt_hw_stm32_eth_init` 用于注册以太网设备,以太网硬件,配置 MAC 地址等。 + +## 使能 lwIP 与 net dev + +首先使能以太网外设: + +![使能以太网外设](figures/eth1.png) + +启用 lwIP 与 net device: + +![开启网卡](figures/net_device.png) + +![开启 lwip](figures/lwip_0.png) + +- 输入命令 scons --target=mdk5 -s 生成 mdk5 工程。 +- 打开工程,打开 drv_eth.c 文件。 + +## 驱动移植 + +STM32F407 芯片自带以太网模块,该模块包括带专用 DMA 控制器的 MAC 802.3(介质访问控制)控制器,支持介质独立接口 (MII) 和简化介质独立接口 (RMII),并自带了一个用于外部 PHY 通信的 SMI 接口,通过一组配置寄存器,用户可以为 MAC 控制器和 DMA 控制器选择所需模式和功能。 + +因为同系列 stm32 的 MAC 控制器初始化基本一样,所以同系列的驱动 MAC 部分的代码不需要做修改,要修改的只是 PHY 的部分,这里以 LAN8720 为例讲解。 + +### 开启日志 + +在调试驱动时,建议先打开 drv_eth.c 中的日志功能。 + +```c +/* debug 设置 */ +#define ETH_DEBUG +#define ETH_RX_DUMP +#define ETH_TX_DUMP +``` + +### 更新 PHY 复位引脚 + +查看原理图,RESET 引脚为 PD3,修改复位管脚为 PD3。 + +![reset 引脚](figures/an010_reset_pin.png) + +```c +static rt_err_t rt_stm32_eth_init(rt_device_t dev) +{ + ... + phy_reset(); + .... +} +``` + +![phy_reset.c](figures/phy_reset.png) + +在 `rt_hw_stm32_eth_init` 函数中,使用 phy_reset() 对 PHY 芯片进行了复位。如果不修改为正确的引脚,除了 PHY 可能没有被正确复位外,还可能造成引脚冲突而损坏板子。 + +> 注:phy_reset.c 是移植文件,一般会位于 board/ports 文件夹下。如果 BSP 中没有,则需要自己实现。 + +### 确认 MII/RMII 模式 + +根据原理图,确认外接的 PHY 是使用 MII 还是 RMII 模式。 + +![RMII 模式](figures/an010_LAN8720.png) + +修改 drv_eth.c 中的模式设置: + +- RMII 模式: ETH_MEDIA_INTERFACE_RMII +- MII 模式: ETH_MEDIA_INTERFACE_MII + +```c +static rt_err_t rt_stm32_eth_init(rt_device_t dev) +{ + ... + EthHandle.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; + ... +} +``` + +### 引脚初始化 + +下面列出正点原子探索者用于连接外部 PHY 的引脚, 用户需要按照自己的原理图对相应的管脚进行初始化。STM32 可以直接在 CubeMX 中进行引脚配置,然后生成代码。以下是生成的初始化代码,该函数最终被 rt_stm32_eth_init() 所调用。 + +```c +/* stm32f4xx_hal_msp.c */ + +void HAL_ETH_MspInit(ETH_HandleTypeDef* heth) +{ + + GPIO_InitTypeDef GPIO_InitStruct = {0}; + if(heth->Instance==ETH) + { + /* USER CODE BEGIN ETH_MspInit 0 */ + + /* USER CODE END ETH_MspInit 0 */ + /* Peripheral clock enable */ + __HAL_RCC_ETH_CLK_ENABLE(); + + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + /**ETH GPIO Configuration + PC1 ------> ETH_MDC + PA1 ------> ETH_REF_CLK + PA2 ------> ETH_MDIO + PA7 ------> ETH_CRS_DV + PC4 ------> ETH_RXD0 + PC5 ------> ETH_RXD1 + PG11 ------> ETH_TX_EN + PG13 ------> ETH_TXD0 + PG14 ------> ETH_TXD1 + */ + GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF11_ETH; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_7; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF11_ETH; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_13|GPIO_PIN_14; + GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF11_ETH; + HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); + + /* USER CODE BEGIN ETH_MspInit 1 */ + + /* USER CODE END ETH_MspInit 1 */ + } + +} +``` +### 更新 PHY 管理程序 + +PHY 是 IEEE802.3 中定义的一个标准模块。PHY 寄存器的地址空间为 5 位,因此寄存器范围是 0 到 31 ,最多有 32 个寄存器。IEEE802.3 定义了地址为 0-15 这 16 个基础寄存器的功能,因此只需要修改少量寄存器的定义即可完成移植。 + +驱动默认使用 LAN8720,如果使用的是其它型号的 PHY,应该修改 rtconfig.h 中的宏定义(在 drv_eth.h 中有多个 PHY 的宏定义),使程序能正确设置自动协商并读取 PHY 的连接状态和速率。 + +```c +/* drv_eth.h */ + +#ifdef PHY_USING_LAN8720A +#define PHY_INTERRUPT_FLAG_REG 0x1DU +#define PHY_INTERRUPT_MSAK_REG 0x1EU +... +#endif /* PHY_USING_LAN8720A */ + +#ifdef PHY_USING_DM9161CEP +#define PHY_Status_REG 0x11U +#define PHY_10M_MASK ((1<<12) || (1<<13)) +#define PHY_100M_MASK ((1<<14) || (1<<15)) +... +#endif /* PHY_USING_DM9161CEP */ +``` + +RT-Thread 的驱动实现中,做了 PHY 地址搜索的功能,可以正确搜索出 PHY 的地址,所以不必定义 PHY 地址。 + +这里贴出 drv_eth.c 文件中 `phy_monitor_thread_entry` 函数中的地址搜索函数供用户参考: + +```c +static void phy_monitor_thread_entry(void *parameter) +{ + uint8_t phy_addr = 0xFF; + uint8_t detected_count = 0; + + while(phy_addr == 0xFF) + { + /* 搜索 PHY */ + rt_uint32_t i, temp; + for (i = 0; i <= 0x1F; i++) + { + EthHandle.Init.PhyAddress = i; + HAL_ETH_ReadPHYRegister(&EthHandle, PHY_ID1_REG, (uint32_t *)&temp); + + if (temp != 0xFFFF && temp != 0x00) + { + phy_addr = i; + break; + } + } + + detected_count++; + rt_thread_mdelay(1000); + + if (detected_count> 10) + { + LOG_E("No PHY device was detected, please check hardware!"); + } + } + + LOG_D("Found a phy, address:0x%02X", phy_addr); + ... +} +``` + +如果提示 `No PHY device was detected, please check hardware!` 或者 `link_down` 则应该检查 IO 配置和硬件。 + +> 提示:现在主流的 PHY 一般在复位后都默认工作在自动协商模式下,且现在的交换机和网线,一般都可以支持支持 100M 全双工。所以在末正确适配 PHY 以前,也可以临时修改 PHY 的工作模式为 100M 全双工供测试用(修改基础控制寄存器中相应的控制位)。 + +### 中断回调函数 + +接收回调函数:中断函数在接收到数据时,会调用回调函数发邮件来通知 “erx” 线程来读取数据。 + +```c +void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth) +{ + rt_err_t result; + /* 发送邮件通知 erx 线程 */ + result = eth_device_ready(&(stm32_eth_device.parent)); + if (result != RT_EOK) + LOG_I("RxCpltCallback err = %d", result); +} +``` + +### ETH 设备初始化 + +RT-Thread 实时操作系统提供了一套设备管理框架 ,应用程序通过 RT-Thread 的设备操作接口实现通用的设备驱动。 我们这里对 ETH 设备,实现 `Network Interface` 类型的设备驱动,然后注册到 RT-Thread。 + +drv_eth.c 中的 `rt_hw_stm32_eth_init` 是 ETH 设备初始化入口,负责 stm32_eth_device 结构体的初始化,并将其注册到 RT-Thread。 + +```c + /* 设置工作速度和模式 */ + stm32_eth_device.ETH_Speed = ETH_Speed_100M; + stm32_eth_device.ETH_Mode = ETH_Mode_FullDuplex; + + /* 利用 STM32 全球唯一 ID 设置 MAC 地址 */ + stm32_eth_device.dev_addr[0] = 0x00; + stm32_eth_device.dev_addr[1] = 0x80; + stm32_eth_device.dev_addr[2] = 0xE1; + stm32_eth_device.dev_addr[3] = *(rt_uint8_t*)(0x1FFF7A10+4); + stm32_eth_device.dev_addr[4] = *(rt_uint8_t*)(0x1FFF7A10+2); + stm32_eth_device.dev_addr[5] = *(rt_uint8_t*)(0x1FFF7A10+0); + + /* 设置标准驱动接口 */ + stm32_eth_device.parent.parent.init = rt_stm32_eth_init; + stm32_eth_device.parent.parent.open = rt_stm32_eth_open; + stm32_eth_device.parent.parent.close = rt_stm32_eth_close; + stm32_eth_device.parent.parent.read = rt_stm32_eth_read; + stm32_eth_device.parent.parent.write = rt_stm32_eth_write; + stm32_eth_device.parent.parent.control = rt_stm32_eth_control; + stm32_eth_device.parent.parent.user_data = RT_NULL; + + /* 设置网络驱动接收和发送接口 */ + stm32_eth_device.parent.eth_rx = rt_stm32_eth_rx; + stm32_eth_device.parent.eth_tx = rt_stm32_eth_tx; + + /* 注册网络设备 */ + state = eth_device_init(&(stm32_eth_device.parent), "e0"); +``` + +#### 设置标准驱动接口 + +##### rt_stm32_eth_init() + +`rt_stm32_eth_init` 是初始化以太网外设的,应按照实际需求初始化。 + +这里只贴出 MAC 配置和 DMA 配置的代码: + +```c +/* EMAC initialization function */ +static rt_err_t rt_stm32_eth_init(rt_device_t dev) +{ + __HAL_RCC_ETH_CLK_ENABLE(); + + /* 复位 PHY 芯片 */ + phy_reset(); + + /* ETHERNET 配置 */ + EthHandle.Instance = ETH; + EthHandle.Init.MACAddr = (rt_uint8_t *)&stm32_eth_device.dev_addr[0]; + EthHandle.Init.AutoNegotiation = ETH_AUTONEGOTIATION_DISABLE; + EthHandle.Init.Speed = ETH_SPEED_100M; + EthHandle.Init.DuplexMode = ETH_MODE_FULLDUPLEX; + EthHandle.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; + EthHandle.Init.RxMode = ETH_RXINTERRUPT_MODE; +#ifdef RT_LWIP_USING_HW_CHECKSUM + EthHandle.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; +#else + EthHandle.Init.ChecksumMode = ETH_CHECKSUM_BY_SOFTWARE; +#endif + + HAL_ETH_DeInit(&EthHandle); + + /* configure ethernet peripheral (GPIOs, clocks, MAC, DMA) */ + if (HAL_ETH_Init(&EthHandle) != HAL_OK) + { + LOG_E("eth hardware init failed"); + return -RT_ERROR; + } + else + { + LOG_D("eth hardware init success"); + } + + /* Initialize Tx Descriptors list: Chain Mode */ + HAL_ETH_DMATxDescListInit(&EthHandle, DMATxDscrTab, Tx_Buff, ETH_TXBUFNB); + + /* Initialize Rx Descriptors list: Chain Mode */ + HAL_ETH_DMARxDescListInit(&EthHandle, DMARxDscrTab, Rx_Buff, ETH_RXBUFNB); + + /* ETH interrupt Init */ + HAL_NVIC_SetPriority(ETH_IRQn, 0x07, 0); + HAL_NVIC_EnableIRQ(ETH_IRQn); + + /* Enable MAC and DMA transmission and reception */ + if (HAL_ETH_Start(&EthHandle) == HAL_OK) + { + LOG_D("emac hardware start"); + } + else + { + LOG_E("emac hardware start faild"); + return -RT_ERROR; + } + + return RT_EOK; +} +``` + +##### rt_stm32_eth_control() + +`rt_stm32_eth_control` 函数需要实现获取 MAC 地址的功能 + +```c +static rt_err_t rt_stm32_eth_control(rt_device_t dev, int cmd, void *args) +{ + switch (cmd) + { + case NIOCTL_GADDR: + /* 获取 MAC 地址 */ + if (args) rt_memcpy(args, stm32_eth_device.dev_addr, 6); + else return -RT_ERROR; + break; + + default : + break; + } + + return RT_EOK; +} +``` + +别的设置标准驱动接口可以不实现,先写个空函数即可。 + +#### 数据包收发接口 + +##### rt_stm32_eth_rx() + +`rt_stm32_eth_rx` 会去读取接收缓冲区中的数据,并放入 pbuf(lwIP 中利用结构体 pbuf 来管理数据包 )中,并返回 pbuf 指针。 + +“erx” 接收线程会阻塞在获取 `eth_rx_thread_mb` 邮箱上,当它接收到邮件时,会调用 `rt_stm32_eth_rx` 去接收数据。 + +##### rt_stm32_eth_tx() + +`rt_stm32_eth_tx` 会将要发送的数据放入发送缓冲区,等待 DMA 来发送数据。 + +“etx” 发送线程会阻塞在获取 `eth_tx_thread_mb` 邮箱上, 当它接收到邮件时,会调用 `rt_stm32_eth_tx` 来发送数据。 + +## EMAC 驱动调试 + +### 实验环境搭建 + +工程默认启用了 DHCP 功能,需要有 DHCP 服务器来分配 IP 地址,常见的连接拓展如图: + +![连接拓展图](figures/an010_f4_eth_RJ45.png) + +如果没有方便的实际环境,也可以先通过 ENV 配置固定 IP,然后用网线直接连接到调试用的电脑。 + +![配置 IP 地址](figures/an010_ipadress.png) + +电脑和开发板需要设置同网段的 IP 地址。 + +### 确认 PHY 连接状态 + +当支持网络的固件在开发板上面运行起来后,应该首先检查 RJ45 指示灯的状态。 + +正常应该是有灯常亮,且有数据收发时会出现闪烁。 + +如果发现灯没有亮,请先确认开发板硬件是否完好;然后检查供电是否充足,网线是否接好。 + +然后程序上配合硬件确认是否有正确复位 PHY,直到正常闪烁为止。 + +### 确认 IP 地址 + +在 shell 命令行执行 `ifconfig` 命令即可查看网卡的 IP 地址。 + +如果启用了 DHCP 功能,且此时显示已经获取到了 IP 地址,说明驱动的收发功能都已经正常。 + +如果是静态 IP 或是没有获取到 IP,请留意网卡 FLAG 中的 UP 和 LINK_UP 标志。 +如果显示有 LINK_DOWN,请确认 PHY 的管理程序有正确识别到 PHY 的速率和双工状态。 +并正确通过 `eth_device_linkchange` 通知到 lwIP。 + +如果启用了 DHCP 功能,且已经显示 LINK_UP。但没能正确获取到 IP。 +说明开发板与 DHCP 服务器通信不畅,需要进一步调试。 + +![ifconfig 确认 IP 地址](figures/an010_ifconfig.png) + +可以通过拔插网线观察 LINK 状态来判断是否正确读取了 PHY 寄存器的值。 + +### 打印数据包 + +通过打开驱动中的 ETH_RX_DUMP 和 ETH_TX_DUMP 功能。 +可以把收发的数据包打印出来。 + +![dump 打印数据包](figures/an010_dump.png) + +当 RJ45 指示灯正常闪烁时,说明有数据包在收发。 +因为网络中经常会有广播数据包,如果此时有数据包进来,会有 RX_DUMP 的打印。 +如果一直没有打印,则重点检查两点: + +* MII/RMII 的 RX 线路有问题,包括硬件问题或 IO 映射错误。 + +* EMAC 和 PHY 的速率和双工模式不配置,如 EMAC 工作在 10M,而 PHY 连接为 100M。 + +需要确认有正确获取 PHY 的速率和双工模式,同时可以与网线另外一端对比。 +如电脑上在显示 10M,而板子上面没有更新 PHY 管理程序,默认为 100M。 + +如果有必要,也可以打印 PHY 的 LOOPBACK 功能。以确认 MII/RMII 总线是完好的。 + +### ping 测试 + +可以通过电脑 ping 板子或者板子 ping 电脑(需要开启 [netutils](https://github.com/RT-Thread-packages/netutils) 组件包中的 ping 功能)来测试驱动是否移植成功。 + +![ping 测试](figures/an010_ping1.png) + +![ping 测试](figures/an010_ping2.png) + +> [!NOTE] +> 注:- 部分 PHY 芯片需要按照手册进行额外的初始化。 + - 板子和电脑要在一个网段内。 + - 如果电脑同时连着网线和 wifi,会出现 ping 不通板子的现象。 + - 查看防火墙是否禁用了 ping 功能。 + - 有些企业网络会禁止 ping 功能,建议更换网络环境。 + - MAC 地址不规范,板子无法 ping 通外部网络。 + - 如果 erx 栈大小设置的太小,会造成 erx 线程栈溢出 。 + +### wireshark 抓包 + +如果板子有 RX DUMP,但依然无法通信时,可以在电脑上面使用 [wireshark](https://www.wireshark.org/) 抓包。 + +电脑 ping 开发板,开发板收到目标地址为自己的 IP 地址的请求包 (request),然后,板子会给电脑做出回应,发送一个目标地址为电脑 IP 地址的回应包 (reply)。 + +根据是否有回应 (reply),可以分两种情况检查 + +- 板子是否收到了请求包 (request)。 +- 板子是否有发送回应 (reply)。 + +如果有回应,但是 ping 不成功,可以检查数据包内容是否符合规范。 + +#### 按 MAC 地址过滤 + +当板子有发出广播包,在电脑上面可以收到。(如 DHCP Discoverer) + +在使用 wireshark 抓包过程中,主要是灵活使用各种过滤器,过滤出我们所关心的数据包。 + +当开发板还没有拿到 IP 地址时,可以使用开发板的 MAC 地址作为过滤器条件。 + +![wireshark MAC 过滤](figures/an010_wireshark_mac.png) + +当开发板成功 link_up 时,会主动发出 DHCP 请求包,源地址就是开发板自己的 MAC 地址。 + +#### 按 IP 地址过滤 + +![wireshark IP 地址过滤](figures/an010_wireshark_ip.png) + +或配置开发板为静态 IP,然后 PC 上面执行 ping 命令,ping 开发板的 IP 地址。 + +这里把开发板的 IP 地址作为过滤条件,正常情况下,PC 会先发出请求包 (request)。 + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/an0011-network-started.md b/rt-thread-version/rt-thread-standard/application-note/components/network/an0011-network-started.md new file mode 100644 index 0000000000000000000000000000000000000000..64bb1bcd9c6686268bb359b755bce823892d7963 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/network/an0011-network-started.md @@ -0,0 +1,430 @@ +# 在 STM32F407 上应用网络功能 + +本文描述了如何在 RT-Thread 中利用标准 BSD Socket API 来开发网络应用。并给出了在正点原子 STM32F4 探索者开发板上运行 NTP(通过网络获取时间)和 MQTT(通过 MQTT 收发数据) 的代码示例。 + +## 简介 + +越来越多的单片机需要接入以太网来收发数据,市面上也有非常多的接入方案,可以用单片机加自带硬件协议栈的 PHY 芯片来接入网络,也可以单片机跑软件协议栈加 PHY 芯片来接入网络,不同的接入方案需要调用不同的 API,降低了上层应用的可移植性。 + +为了方便用户开发网络应用,RT-Thread 中引入了网络框架。并提供标准 BSD Socket API 用于开发网络应用,同时,RT-Thread 还提供了数量丰富的网络组件包,方便用户快速开发自己的应用。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +* 一块能上网的开发板, 这里以正点原子 STM32F4 探索者开发板为例 + +* 移植好网络底层驱动,驱动移植可以参考 [网络协议栈驱动移植笔记](an0010-lwip-driver-porting.md) + +* 网络调试工具 + +## 主要调试命令 + +这里介绍下 RT-Thread 提供的三个网络信息查看命令,在 shell 中输入命令即可很方便的查看网络连接状况,方便用户进行调试。 + +**ifconfig** + +`ifconfig` 可以打印出板子现在的网络连接状态,IP 地址,网关地址,dns 等信息。 + +![an010_ifconfig](figures/an010_ifconfig.png) + +**netstate** + +`netstate` 可以打印出板子所有的 TCP / IP 连接信息 + +![an011_netstate](figures/an011_netstate.png) + +**dns** + +`dns` 命令可以打印出现在使用的 dns 服务器地址。 + +`dns [dns_num] ` 命令可以手动设置 dns 服务器地址。 + +![查看与设置 dns](figures/dns.png) + +## 硬件连接准备 + +工程默认启用了 DHCP 功能,需要有 DHCP 服务器来分配 IP 地址,常见的连接拓展如图: + +![eth_RJ45](figures/an010_f4_eth_RJ45.png) + +注:如果没有方便的实际环境,也可以先通过 ENV 配置固定 IP,然后用网线直接连接到调试用的电脑。电脑和开发板需要设置同网段的 IP 地址。配置静态 IP 如下: + +``` + -> RT-Thread Components + -> Network + -> light weight TCP/IP stack + -> Enable lwIP stack + -> Static IPv4 Address +``` + +![ip](figures/static_ip.png) + +## ENV 配置 + +RT-Thread 可以很方便的通过 ENV 来配置和生成工程 + +1. 打开板载外设 ethernet,选中之后, LWIP 也将自动被开启: + +![ethernet](figures/ethernet.png) + +![lwip_auto_open](figures/lwip_auto_open.png) + +2. 打开 SAL 层,并打开 BSD socket: + +![enable sal](figures/BSD soc.png) + +此时 net device 也将自动被打开: + +![net_device](figures/net_device.png) + +文件系统也将自动被打开(fd 的管理在文件系统中,所以需要文件系统): + +![filesystem](figures/filesystem.png) + +4. 基础应用:tcp client,udp client。在软件包中开启基础示例代码 tcp client 与 udp client。 + +``` + -> RT-Thread online packages + -> miscellaneous packages + -> samples: kernel and components samples + -> a network_samples package for rt-thread +``` + +![enable sample](figures/net_sample.png) + +5. 高级应用 1:MQTT。在软件包中开启高级应用软件包: + +``` + -> RT-Thread online packages + -> IoT - internet of things + -> Paho MQTT: Eclipse Paho MQTT C/C++ client for Embedded platforms +``` + +选中 MQTT 示例代码: + +![mqtt](figures/mqtt.png) + +6. 高级应用 2:NTP。在软件包中开启高级应用软件包: + +``` +-> RT-Thread online packages + -> IoT - internet of things + -> netutils: Networking utilities for RT-Thread +``` + +![ntp](figures/ntp.png) + +7. 按 ESC 退出配置界面。 + +8. 在 Env 命令行中输入 pkgs --update 下载软件包。 + +9. 在 Env 命令行中输入 scons --target=mdk5 -s 生成 mdk5 工程。 + +10. 打开工程,编译,下载代码。 + +## 网络测试 + +将 Env 生成的工程编译后下载到板子上,可以看到网口的两盏灯会亮起,一盏会闪烁,说明 PHY 已经正常初始化了。 + +在 shell 中输入 ifconfig 可以打印板子的网络状态,正常获取到 ip 即表示网络驱动正常,准备工作完成。 + +![ifconfig](figures/an010_ifconfig.png) + +## 基础应用示例 + +在实际应用中,单片机一般作为客户端去和服务器进行数据交换,在这里,以 tcp client 和 udp client 为例进行讲解。 + +### tcpclient 示例 + +这个例程展示了如何创建一个 TCP 客户端,跟远端服务器进行通信。 在 shell 中输入 tcpclient URL PORT 来连接服务器,程序接收并显示从服务端发送过来的信息,接收到开头是'q' 或'Q' 的信息则退出程序。 + +源码解析如下所示: + +```c +void tcpclient(int argc, char **argv) +{ + int ret; + char *recv_data; + struct hostent *host; + int sock, bytes_received; + struct sockaddr_in server_addr; + const char *url; + int port; + + /* 接收到的参数小于 3 个 */ + if (argc < 3) + { + rt_kprintf("Usage: tcpclient URL PORT\n"); + rt_kprintf("Like: tcpclient 192.168.12.44 5000\n"); + return ; + } + + url = argv[1]; + port = strtoul(argv[2], 0, 10); + + /* 通过函数入口参数 url 获得 host 地址(如果是域名,会做域名解析) */ + host = gethostbyname(url); + + /* 分配用于存放接收数据的缓冲 */ + recv_data = rt_malloc(BUFSZ); + if (recv_data == RT_NULL) + { + rt_kprintf("No memory\n"); + return; + } + + /* 创建一个 socket,类型是 SOCKET_STREAM,TCP 类型 */ + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + /* 创建 socket 失败 */ + rt_kprintf("Socket error\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + return; + } + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 连接到服务端 */ + if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) + { + /* 连接失败 */ + rt_kprintf("Connect fail!\n"); + closesocket(sock); + + /* 释放接收缓冲 */ + rt_free(recv_data); + return; + } + + while (1) + { + /* 从 sock 连接中接收最大 BUFSZ - 1 字节数据 */ + bytes_received = recv(sock, recv_data, BUFSZ - 1, 0); + if (bytes_received < 0) + { + /* 接收失败,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nreceived error,close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + else if (bytes_received == 0) + { + /* 打印 recv 函数返回值为 0 的警告信息 */ + rt_kprintf("\nReceived warning,recv function return 0.\r\n"); + + continue; + } + + /* 有接收到数据,把末端清零 */ + recv_data[bytes_received] = '\0'; + + if (strncmp(recv_data, "q", 1) == 0 || strncmp(recv_data, "Q", 1) == 0) + { + /* 如果是首字母是 q 或 Q,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\n got a'q'or'Q',close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + else + { + /* 在控制终端显示收到的数据 */ + rt_kprintf("\nReceived data = %s", recv_data); + } + + /* 发送数据到 sock 连接 */ + ret = send(sock, send_data, strlen(send_data), 0); + if (ret < 0) + { + /* 接收失败,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nsend error,close the socket.\r\n"); + + rt_free(recv_data); + break; + } + else if (ret == 0) + { + /* 打印 send 函数返回值为 0 的警告信息 */ + rt_kprintf("\n Send warning,send function return 0.\r\n"); + } + } + return; +} +``` + +用网络调试工具在电脑上搭建一个 TCP 服务器,记录下打开的端口 + +![TCP Server_set](figures/an011_tcp_set.png) + +在 shell 中输入 tcpclient PC 的 IP 地址 刚才记录下的端口号 + +``` +msh />tcpclient 192.168.12.45 5000 +``` + +利用服务器发送 Hello RT-Thread! ,shell 中会显示收到的信息 + +![tcpclient_shell](figures/an011_tcpclient_shell.png) + +服务器会收到 This is TCP Client from RT-Thread. 的消息 + +![tcpclient](figures/an011_tcpclient.png) + +### udpclient 示例 + +这个例程展示了如何创建一个 UDP 客户端,给远端服务器发送数据。在 shell 中输入 udpclient URL PORT 来连接服务器。程序会给服务端发送信息(默认 10 条)。 + +源码解析如下所示: + +```c +void udpclient(int argc, char **argv) +{ + int sock, port, count; + struct hostent *host; + struct sockaddr_in server_addr; + const char *url; + + /* 接收到的参数小于 3 个 */ + if (argc < 3) + { + rt_kprintf("Usage: udpclient URL PORT [COUNT = 10]\n"); + rt_kprintf("Like: tcpclient 192.168.12.44 5000\n"); + return ; + } + + url = argv[1]; + port = strtoul(argv[2], 0, 10); + + if (argc> 3) + count = strtoul(argv[3], 0, 10); + else + count = 10; + + /* 通过函数入口参数 url 获得 host 地址(如果是域名,会做域名解析) */ + host = (struct hostent *) gethostbyname(url); + + /* 创建一个 socket,类型是 SOCK_DGRAM,UDP 类型 */ + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + { + rt_kprintf("Socket error\n"); + return; + } + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 总计发送 count 次数据 */ + while (count) + { + /* 发送数据到服务远端 */ + sendto(sock, send_data, strlen(send_data), 0, + (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); + + /* 线程休眠一段时间 */ + rt_thread_delay(50); + + /* 计数值减一 */ + count --; + } + + /* 关闭这个 socket */ + closesocket(sock); +} +``` + +用网络调试工具在电脑上搭建一个 UDP 服务器,记录下打开的端口 + +![udp_set](figures/an011_udp_set.png) + +在 shell 中输入 udpclient PC 的 IP 地址 刚才记录下的端口号 + +``` +udpclient 192.168.12.45 1001 +``` + +服务器会收到 10 条 This is UDP Client from RT-Thread. 的消息 + +![udpclient](figures/an011_udpclient.png) + +## 高级应用示例 + +为了方便网络应用开发,RT-Thread 提供了丰富的网络组件包,例如:[netutils 网络小工具集](https://github.com/RT-Thread-packages/netutils),[webclient](https://github.com/RT-Thread-packages/webclient),[cJSON](https://github.com/RT-Thread-packages/cJSON),[paho-mqtt](https://github.com/RT-Thread-packages/paho-mqtt) 等等,用户可以根据需求直接在 Env 中使能即可使用各个组件包,省去了自己移植的过程,加速网络应用开发。 + +![ENV_IoT](figures/an011_IoT.png) + +我们这里以 netutils 网络小工具集中的 [NTP](https://github.com/RT-Thread-packages/netutils/blob/master/ntp/README.md)(时间同步)小工具和 [paho-mqtt](https://github.com/RT-Thread-packages/paho-mqtt) 为例进行讲解 + +### NTP + +NTP(Network Time Protocol) 是网络时间协议,它是用来同步网络中各个计算机时间的协议。 + +RT-Thread 实现了 NTP 客户端,可以通过网络获取本地时间,并同步板子的 RTC 时间。 + +ENV 的配置参考前面准备工作章节的 ENV 配置。 + +在 msh 中输入 `ntp_sync` 即可从默认的 NTP 服务器 (cn.ntp.org.cn) 获取本地时间,默认时区为东八时区 + +``` +msh />ntp_sync +``` + +如果输入 `ntp_sync` 后提示超时或者连接失败,可以在 `ntp_sync` 后面输入 NTP 服务器地址,程序将从新的服务器获取时间。 + +``` +msh />ntp_sync edu.ntp.org.cn +``` + +![gettime](figures/an011_gettime.png) + +### MQTT + +[Paho MQTT](http://www.eclipse.org/paho/downloads.php) 是 Eclipse 实现的 MQTT 协议的客户端,本软件包是在 Eclipse [paho-mqtt](https://github.com/eclipse/paho.mqtt.embedded-c) 源码包的基础上设计的一套 MQTT 客户端程序。 + +MQTT 使用发布 / 订阅消息模式,发送消息时要指定发给哪个主题名(Topic Name),接收消息前要订阅一个主题名,然后才能接收到发送给这个主题名的消息内容。 + +RT-Thread MQTT 客户端功能特点: + +- 断线自动重连 +- pipe 模型,非阻塞 API +- 事件回调机制 +- TLS 加密传输 + +ENV 的配置参考前面准备工作章节的 ENV 配置 + +在 msh 中输入 `mqtt_start` 命令, 客户端会自动连接服务器,并订阅 `/mqtt/test` 主题 + +``` +msh />mqtt_start +``` + +通过 `mqtt_publish` 命令 可以发送消息给所有订阅了 `/mqtt/test` 的客户端,我们利用 `mqtt_publish` 发送 RT-Thread! + +``` +msh />mqtt_publish RT-Thread! +``` + +由于我们之前订阅了 `/mqtt/test` 主题,shell 很快会显示服务器发来的 RT-Thread! 消息。 + +![mqtttest](figures/an011_mqtttest.png) + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/an0019-tcpclient-socket.md b/rt-thread-version/rt-thread-standard/application-note/components/network/an0019-tcpclient-socket.md new file mode 100644 index 0000000000000000000000000000000000000000..a0f261c65334e95a51e140f980e47e3f03598899 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/network/an0019-tcpclient-socket.md @@ -0,0 +1,280 @@ +# 多线程非阻塞网络编程 + +本文描述了使用 QEMU 运行 RT-Thread 提供的基于多线程的非阻塞 socket 编程示例。 + +## 简介 + +随着物联网的发展,越来越多产品需要基于网络进行数据传输。在实际开发中,往往要求网络传输时不能阻塞当前线程,以致无法及时处理其他消息。在用户无法直接套用简单的 socket demo 时,RT-Thread 提供基于多线程的非阻塞 socket 编程示例,方便用户进行应用程序开发。 + +在 RT-Thread 使用 socket 网络编程时,当一个任务调用 socket的 recv()函数接收数据时,如果 socket 上并没有接收到数据,这个任务将阻塞在这个 recv() 函数里。这个时候,这个任务想要处理一些其他事情,例如进行一些数据采集,发送一些额外数据到网络上等,将变得不可能了。与此同时,其他线程也需要将数据上传同一个服务器,如果直接多个线程共同使用一个 socket 操作,这将会破坏底层 lwip 的消息事件模型。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +* [README.md](https://github.com/neverxie/tcpclient) + +* [tcpclient 源码](https://github.com/neverxie/tcpclient/blob/master/src/tcpclient.c) + +* [example 源码](https://github.com/neverxie/tcpclient/blob/master/examples/tcpclient_example.c) + +## socket 编程模型简介 + +socket 编程模型如下图所示: + +![socket 编程模型](figures/an0019_socket.png) + +客户端使用流程: + +1. `socket()` 创建一个 socket,返回套接字的描述符,并为其分配系统资源。 +2. `connect()` 向服务器发出连接请求。 +3. `send()/recv()` 与服务器进行通信。 +4. `closesocket()` 关闭 socket,回收资源。 + +服务器使用流程: + +1. `socket()` 创建一个 socket,返回套接字的描述符,并为其分配系统资源。 +2. `bind()` 将套接字绑定到一个本地地址和端口上。 +3. `listen()` 将套接字设为监听模式并设置监听数量,准备接收客户端请求。 +4. `accept()` 等待监听的客户端发起连接,并返回已接受连接的新套接字描述符。 +5. `recv()/send()` 用新套接字与客户端进行通信。 +6. `closesocket()` 关闭 socket,回收资源。 + +例如在上面网络客户端操作过程中,当进行 recv 操作时,如果对应的通道数据没有准备好,那系统就会让当前任务进入阻塞状态,当前任务不能再进行其他的操作。 + +## 非阻塞 socket 编程简介 + +在 RT-Thread 中,自 v3.0.0 以来更标准化,支持更多的 POSIX API。这其中就包括 poll / select 接口实现,并且可以进行 socket 和设备文件的联合 poll / select。select、poll的内部实现机制相似,由于本文选用 select 方式,故在此不对 poll 展开介绍。 + +下面结合框图进一步说明如何使用 select 和 pipe 来解决这类问题。 + +![非阻塞 socket 编程](figures/an0019_tcpclient.png) + +图中存在有三个线程:应用线程 `thread1`、`thread2` 和客户端线程 `thread client`,其中 `thread client` 完成 select 功能。 + +- 数据发送过程: + - 应用线程通过 pipe 往 `thread client` 发送数据 data1,select 探测到 pipe 有数据可读,`thread client` 被唤醒,然后读取 pipe 中的数据并通过 TCP socket 发送到 server +- 数据接收过程: + - server 通过 TCP socket 发送数据 data2 到 `thread client`,select 探测到 socket 有数据可读,`thread client` 被唤醒,`thread client` 可以获得接收到的数据 + +下面将详细介绍 select 和 pipe 的使用方法。 + +### select + +`select()` 可以阻塞地同时探测一组支持非阻塞的 I / O 设备是否有事件发生(如可读,可写,出现异常等等),直至某一个设备触发了事件或者超过了指定的等待时间。此时我们可以把需要的数据源通道放到 select 的探测范围内,只要相应的数据源准备好 select 就会返回,这时就能无阻塞地读取到数据。 + +`select()` 主要用来处理 I / O 多路复用的情况,适用如下场合: + +- 客户端处理多个描述符时(一般是交互式输入和网络套接口) +- 服务器既要处理监听套接口,又要处理已连接套接口 +- 服务器既要处理 TCP,又要处理 UDP +- 服务器要处理多个服务或多个协议 + +select()函数原型及介绍如下所示: + +```c +int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout); +``` + +| 参数 | 描述 | +| -------- | ------------------------------------------------------- | +| nfds | 集合中所有文件描述符的范围,即所有文件描述符的最大值加1 | +| readfds | 需要监视读变化的文件描述符集合 | +| writefds | 需要监视写变化的文件描述符集合 | +| errorfds | 需要监视出现异常的文件描述符集合 | +| timeout | select 的超时时间 | +| **返回** | -- | +| 正值 | 监视的文件集合出现可读写事件或异常事件 | +| 0 | 等待超时,没有可读写或异常的事件 | +| 负值 | select 出现错误 | + +### pipe + +pipe 是一个基于文件描述符的单向数据通道,可用于线程间的通信。 + +在 RT-Thread 里面,pipe 支持文件描述符的形式操作,而且 pipe 不需要控制协议,操作简单。 + +> 提示:在 msh />中,输入 `list_fd` 可查看当前打开的文件描述符,详情如下: + +```c +msh />list_fd +fd type ref magic path + +-- ------ --- ----- ------ + + 0 file 1 fdfd /uart0 + 1 socket 1 fdfd + 2 file 1 fdfd /pipe0 + 3 file 1 fdfd /pipe0 +msh /> +``` + +下面将详细介绍代码的实现情况。 + +## tcpclient 示例 + +`tcpclient.c` 是上文提出的 select、pipe 方案的具体实现代码,该源码采用面向对象的思想实现,提供 TCP 连接、发送、关闭以及注册接收回调四个 API 提供用户使用。 + +下面的序列图为 `tcpclient.c`的运行流程: + +![tcpclient uml图](figures/an0019_uml.png) + +各流程详细解释如下所示: + +1. 调用 `rt_tcpclient_start()` 设置服务器 ip 地址 & 端口号,以及完成 pipe、socket 初始化和 TCP 连接、select 配置等工作。 +2. 注册接收回调函数 `rt_tc_rx_cb()`。 +3. 调用 `rt_tcpclient_send()` 通过 pipe 发送数据(*图中绿线表示 select 探测到 pipe 可读事件*)。 +4. 图中绿线表示 select 探测到 pipe 可读事件, tcpclient 被唤醒并读取 pipe 的数据。 +5. tcpclient 通过 socket 发送数据给 server。 +6. server 通过 socket 发送数据给 tcpclient。 +7. 图中蓝线表示 select 探测到 socket 可读事件,tcpclient 被唤醒并读取 socket 的数据。 +8. app 通过 `rt_tc_rx_cb()` 获得 tcpclient 读取到的数据。 +9. 通信完毕,app 调用 `rt_tcpclient_close()` 关闭 pipe、socket,并清理相关资源。 + +### 源码详解 + +下面代码的核心代码: + +```c +static void select_handle(rt_tcpclient_t *thiz, char *pipe_buff, char *sock_buff) +{ + fd_set fds; + + rt_int32_t max_fd = 0, res = 0; + max_fd = MAX_VAL(thiz->sock_fd, thiz->pipe_read_fd) + 1; + + /* 清空可读事件描述符列表 */ + FD_ZERO(&fds); + + while (1) + { + /* 将需要监听可读事件的描述符加入列表 */ + FD_SET(thiz->sock_fd, &fds); + FD_SET(thiz->pipe_read_fd, &fds); + + /* 等待设定的网络描述符有事件发生 */ + res = select(max_fd, &fds, RT_NULL, RT_NULL, RT_NULL); + + /* select 返回错误及超时处理 */ + EXCEPTION_HANDLE(res, "select handle", "error", "timeout"); + + /* 查看 sock 描述符上有没有发生可读事件 */ + if (FD_ISSET(thiz->sock_fd, &fds)) + { + /* 从 sock 连接中接收最大BUFSZ - 1字节数据 */ + res = recv(thiz->sock_fd, sock_buff, BUFF_SIZE, 0); + + /* recv 返回异常 */ + EXCEPTION_HANDLE(res, "socket recv handle", "error", "TCP disconnected"); + + /* 有接收到数据,把末端清零 */ + sock_buff[res] = '\0'; + + /* 通过回调函数的方式,数据发给 thread1 */ + RX_CB_HANDLE(sock_buff, res); + + /* 如果接收的是exit,关闭这个连接 */ + EXIT_HANDLE(sock_buff); + } + + /* 查看 pipe 描述符上有没有发生可读事件 */ + if (FD_ISSET(thiz->pipe_read_fd, &fds)) + { + /* 从 pipe 连接中接收最大BUFSZ - 1字节数据 */ + res = read(thiz->pipe_read_fd, pipe_buff, BUFF_SIZE); + + /* recv 返回异常 */ + EXCEPTION_HANDLE(res, "pipe recv handle", "error", RT_NULL); + + /* 有接收到数据,把末端清零 */ + pipe_buff[res] = '\0'; + + /* 读取 pipe 的数据,转发给 server */ + send(thiz->sock_fd, pipe_buff, res, 0); + + /* recv 返回异常 */ + EXCEPTION_HANDLE(res, "socket write handle", "error", "warning"); + + /* 如果接收的是 exit,关闭这个连接 */ + EXIT_HANDLE(pipe_buff); + } + } + +exit: + /* 释放接收缓冲 */ + free(pipe_buff); + free(sock_buff); +} +``` + +这段代码是 tcpclient 线程的核心部分,按照例程配置 select,根据 `FD_ISSET()` 宏检查描述符。 + +- 假如 socket 有数据可读,采用回调函数的方式把数据发送给应用线程。 +- 假如 pipe 有数据可读,处理数据,通过 socket 发送到服务器。 + +### 准备工作 + +首先在 github 上拉取 `tcpclient.c` 的源码,然后将tcpclient 文件夹放在 `rt-thread\bsp\qemu-vexpress-a9`目录下,详情如下: + +![添加到QEMU工程](figures/an0019_url.png) + +在 Env 里使用 `scons` 命令编译 QEMU 工程,详情如下: + +![QEMU工程编译](figures/an0019_scons.png) + +在 Env 里使用 `.\qemu.bat` 命令启动,详情如下: + +![QEMU启动](figures/an0019_qemu.png) + +QEMU 成功启动,下面来介绍代码运行情况。 + +设置网络调试助手端口号,详情如下: + +![网络助手设置](figures/an0019_server_1.png) + +在 cmd 命令行输入 `ipconfig` 查看本机 ip 地址,详情如下: + +```c +> ipconfig +... +IPv4 Address. . . . . . . . . . . : 192.168.12.53 +... +``` + +example 代码中通过 `rt_tcpclient_start()` API 设置服务器 IP 地址和端口号,详情如下: + +```c +rt_tcpclient_start("192.168.12.53", 9008); +``` + +> [!NOTE] +> 注:这里需要根据自己的环境设置 ip 地址和端口号!!! + +在 `msh />` 里,输入 `rt_tc_test_init` 详情如下: + +```c +msh />rt_tc_test_init +``` + +### 运行效果 + +在 example.c 里建立两个线程,一个是 thread1,另一个是 thread2,两个线程交替给服务端发送数据。服务端每秒钟往客户端发送数据。 + + ![运行效果图](figures/an0019_server_2.png) + +网络助手发送 `i am server` ,thread1 接收并且打印出来,详情如下: +``` +msh />D/tc_rx_cb [-30-01-01 00:00:00 tcpc] (packages\tcpclient\examples\tcpclient_example.c:52)recv data: i am server +``` + +## 总结 + +- select() 也是阻塞模式,它的好处在于可以同时选择多个数据源通道:只要通道里数据有效时,就可以进行操作;在没有数据需要处理时,则操作线程会被挂起。 +- 通过使用 pipe / select 的方式,让 tcpclient 网络任务实现了在等待网络数据的同时额外处理其他消息的目的。 + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) + diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/BSD soc.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/BSD soc.png new file mode 100644 index 0000000000000000000000000000000000000000..f6fe03df537e91b15393bcbd4b65e2446b9946bd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/BSD soc.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_qemu.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_qemu.png new file mode 100644 index 0000000000000000000000000000000000000000..e4f64a3d130972ce298c533416a81ed2ac0708c2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_qemu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_scons.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_scons.png new file mode 100644 index 0000000000000000000000000000000000000000..e04086cd4c6f3b1e3f36094ca426728ba5b2576f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_scons.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_server_1.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_server_1.png new file mode 100644 index 0000000000000000000000000000000000000000..109708f985fe891f0bae83a4d91ece089589165b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_server_1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_server_2.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_server_2.png new file mode 100644 index 0000000000000000000000000000000000000000..174e11dd50347a71102e11e814a2ce29e4a209f2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_server_2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_socket.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_socket.png new file mode 100644 index 0000000000000000000000000000000000000000..cb1e6d4317e0b131929e471f76159319d5c186cd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_socket.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_tcpclient.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_tcpclient.png new file mode 100644 index 0000000000000000000000000000000000000000..8f6e92ee0076159d7ce373f429a9c0e8a6594ba1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_tcpclient.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_uml.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_uml.png new file mode 100644 index 0000000000000000000000000000000000000000..2ef9a0d911f0e59ae87a0e5e9c2e7d92d4bb7777 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_uml.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_url.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_url.png new file mode 100644 index 0000000000000000000000000000000000000000..fd90163dfe2941296353a24e039910dee59ccd9d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an0019_url.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an009_menuconfig_lwip.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an009_menuconfig_lwip.png new file mode 100644 index 0000000000000000000000000000000000000000..91ac7c0d14ea74e0b0666780aec7ccb07e589e5f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an009_menuconfig_lwip.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_LAN8720.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_LAN8720.png new file mode 100644 index 0000000000000000000000000000000000000000..39480ff8119d52f90698863eca56932785dd05f1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_LAN8720.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_dump.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_dump.png new file mode 100644 index 0000000000000000000000000000000000000000..7983f601a677ec5be6647251c3d31cb4b98e7a0a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_dump.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_f4_eth_RJ45.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_f4_eth_RJ45.png new file mode 100644 index 0000000000000000000000000000000000000000..4abf9c436e6f16cdcb3f590d0d933fa8774aefcf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_f4_eth_RJ45.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_f4_eth_driver.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_f4_eth_driver.png new file mode 100644 index 0000000000000000000000000000000000000000..052ac6233589fa552d09d160a3f8797395e67021 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_f4_eth_driver.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ifconfig.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ifconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..3303edad348c1a52e095ede614bb606c7b676e83 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ifconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ipadress.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ipadress.png new file mode 100644 index 0000000000000000000000000000000000000000..d0130236523ce987d036c2b7b4f82241c27f5c13 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ipadress.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_lwip.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_lwip.png new file mode 100644 index 0000000000000000000000000000000000000000..8052651235ab00397b16489a9a210ac22b523a0d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_lwip.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_lwip_block.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_lwip_block.png new file mode 100644 index 0000000000000000000000000000000000000000..795b29297c1bc2084ca1f9d9176bd6c675e17467 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_lwip_block.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ping1.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ping1.png new file mode 100644 index 0000000000000000000000000000000000000000..ac433872c91ff4e405c6f602ad5472cb7dc6f9b9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ping1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ping2.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ping2.png new file mode 100644 index 0000000000000000000000000000000000000000..4ffd240c4ca2c7389ed64b26381afe7db9a93a9e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_ping2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_reset_pin.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_reset_pin.png new file mode 100644 index 0000000000000000000000000000000000000000..3390c6e3841e92590ddcbdd015ca05636c01891b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_reset_pin.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_rt_xxx_erh_tx.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_rt_xxx_erh_tx.png new file mode 100644 index 0000000000000000000000000000000000000000..02ac505fd0a7b2ac0843356d9fbde977b08b4ffd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_rt_xxx_erh_tx.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_rt_xxx_eth_rx.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_rt_xxx_eth_rx.png new file mode 100644 index 0000000000000000000000000000000000000000..18ae21ea4b6bd812807e606fb06756ba5d005ee7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_rt_xxx_eth_rx.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_tcpclient.jpg b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_tcpclient.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e5175fd13bd42ec2589980211f74d200aafd99cb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_tcpclient.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_uart.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_uart.png new file mode 100644 index 0000000000000000000000000000000000000000..8df3314958f40b029463de61c553483e64b2178e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_uart.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_wireshark_ip.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_wireshark_ip.png new file mode 100644 index 0000000000000000000000000000000000000000..61dcb8573d34f4a651e29716476f880cb66f8e77 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_wireshark_ip.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_wireshark_mac.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_wireshark_mac.png new file mode 100644 index 0000000000000000000000000000000000000000..3731ac62f43d77c1e27d0410f359dd9bee760a09 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an010_wireshark_mac.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_IoT.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_IoT.png new file mode 100644 index 0000000000000000000000000000000000000000..7b7678a247298a9c59b69d7f51c4614bbf642deb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_IoT.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_client.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_client.png new file mode 100644 index 0000000000000000000000000000000000000000..493f0ef7e99f6c112b875ac8c5459aaad7a4d96e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_client.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_dns.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_dns.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d11d5b300ca324f6c7ca1bcd085a09ecfab0b5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_dns.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_frame.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..62d8d3694ffebed5fea36003d85cb8d4db8abc0c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_frame.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_gettime.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_gettime.png new file mode 100644 index 0000000000000000000000000000000000000000..b0e3a214dfacd253b549a5341c6e6b5b66e4d784 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_gettime.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_lwip.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_lwip.png new file mode 100644 index 0000000000000000000000000000000000000000..b1b1bffc799875526f7031cfdd8570ea4de89c1f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_lwip.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_menuconfig_filesystem.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_menuconfig_filesystem.png new file mode 100644 index 0000000000000000000000000000000000000000..438f4cb9901ff98eafd5d6b6ea68a99061c90793 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_menuconfig_filesystem.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_mqtt.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_mqtt.png new file mode 100644 index 0000000000000000000000000000000000000000..5d8140a5627172dc425e6f2286f086a805ae4a9f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_mqtt.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_mqtttest.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_mqtttest.png new file mode 100644 index 0000000000000000000000000000000000000000..627336dfce204a43129a953f16879ee91c95ba0e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_mqtttest.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_netstate.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_netstate.png new file mode 100644 index 0000000000000000000000000000000000000000..a326d74f61ff795977718e49aee55f7619fc2a5f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_netstate.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_ntp.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_ntp.png new file mode 100644 index 0000000000000000000000000000000000000000..dda7ebce8dc1f458d8a378a173b37bf08b83293f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_ntp.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_sai.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_sai.png new file mode 100644 index 0000000000000000000000000000000000000000..06d0e28fd8c67fe6b548f5e3996b09bb30fb6881 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_sai.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcp_set.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcp_set.png new file mode 100644 index 0000000000000000000000000000000000000000..aca0900d4acc9862d00022c8dc25c61d1b04d94d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcp_set.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcpclient.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcpclient.png new file mode 100644 index 0000000000000000000000000000000000000000..aec33b859c789b99083d059002550651894d4d7e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcpclient.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcpclient_shell.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcpclient_shell.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f8f7f4700fecd0d31294092a112e27f4db8e55 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_tcpclient_shell.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_udp_set.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_udp_set.png new file mode 100644 index 0000000000000000000000000000000000000000..093e160d05cd5269704fed8a72dda61c334413f4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_udp_set.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_udpclient.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_udpclient.png new file mode 100644 index 0000000000000000000000000000000000000000..c0e916b4217de1204589257d85349cf8e2e0bcbc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/an011_udpclient.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/dns.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/dns.png new file mode 100644 index 0000000000000000000000000000000000000000..4adb4826c1f79aa63f119ce546e95c7ceadc57d6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/dns.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/eth1.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/eth1.png new file mode 100644 index 0000000000000000000000000000000000000000..f348bb847dbf55bf0f7fc3c8163709da41f08cd7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/eth1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/ethernet.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/ethernet.png new file mode 100644 index 0000000000000000000000000000000000000000..84e20f70d186d424feadae1b231acaae8af63572 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/ethernet.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/etx.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/etx.png new file mode 100644 index 0000000000000000000000000000000000000000..40dd277ba115bf07d8ab24bb57da7cf42be21f80 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/etx.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/filesystem.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/filesystem.png new file mode 100644 index 0000000000000000000000000000000000000000..5c271a45861d8d7e1f3477951c2b3fc54f80bf02 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/filesystem.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/lwip_0.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/lwip_0.png new file mode 100644 index 0000000000000000000000000000000000000000..15465aec34aca25a72222fd9f72486821a73fc33 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/lwip_0.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/lwip_auto_open.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/lwip_auto_open.png new file mode 100644 index 0000000000000000000000000000000000000000..5a69317ec5d86be8e829568b1ca07b0adbacd5c1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/lwip_auto_open.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/mqtt.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/mqtt.png new file mode 100644 index 0000000000000000000000000000000000000000..c5274455143181baf317e0ccf55d7b3c0a3f22bb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/mqtt.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/net_device.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/net_device.png new file mode 100644 index 0000000000000000000000000000000000000000..15cdb6a35c9b8561056cdcdf4d5b8c03cc554902 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/net_device.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/net_sample.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/net_sample.png new file mode 100644 index 0000000000000000000000000000000000000000..ea73629dbc241547e5a583533647f0b914e9ccb4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/net_sample.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/ntp.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/ntp.png new file mode 100644 index 0000000000000000000000000000000000000000..d716bee6b86ebd1b27b6fd377dc04f04ac05ac6c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/ntp.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/phy_reset.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/phy_reset.png new file mode 100644 index 0000000000000000000000000000000000000000..f03daa15d021a3bc0d55d9a28c25c8a4d16213b5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/phy_reset.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/sal_frame.jpg b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/sal_frame.jpg new file mode 100644 index 0000000000000000000000000000000000000000..18513726f68ca2df6b722b17b2e07b9b48557c67 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/sal_frame.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/network/figures/static_ip.png b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/static_ip.png new file mode 100644 index 0000000000000000000000000000000000000000..0fad733801d2d02c836cf8e6670f0f64fcf639ba Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/network/figures/static_ip.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/sfud/an0048-sfud.md b/rt-thread-version/rt-thread-standard/application-note/components/sfud/an0048-sfud.md new file mode 100644 index 0000000000000000000000000000000000000000..72fb543888cc529c897fe129cd5b3b5186b175a1 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/components/sfud/an0048-sfud.md @@ -0,0 +1,287 @@ +# 在潘多拉上使用 SFUD 操作 Flash + +[SFUD](https://github.com/armink/SFUD) 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多,各个 Flash 的规格及命令存在差异, SFUD 就是为了解决这些 Flash 的差异现状而设计,让我们的产品能够支持不同品牌及规格的 Flash,提高了涉及到 Flash 功能的软件的可重用性及可扩展性,同时也可以规避 Flash 缺货或停产给产品所带来的风险。 + +在使用 SFUD 时候需要进行移植,可以参考文件 [sfud/port/sfud_port.c](https://github.com/armink/SFUD/blob/master/sfud/port/sfud_port.c)。本篇文档将说明如何在潘多拉上使用 SFUD 操作 Flash(SFUD 部分已经移植完成),本文准备资料如下: + +- [RT-Thread 源码](https://www.rt-thread.org/page/download.html) +- [Env 工具](https://www.rt-thread.org/page/download.html) +- 板载 spi flash 的开发板,以潘多拉为例 + +## Env 配置 + +使用 SFUD 操作 Flash,需要在 Env 中打开: QSPI 或 SPI 总线、SFUD 组件。 + +(1)在片上外设中,打开 QSPI/SPI 总线:根据实际情况选择 SPI/QSPI 总线,在本示例中使用的是 QSPI 总线。 + +![enable-qspi-bus](figures/spi-bus.png) + +(2)在组件中,选中 SFUD 组件,保存并生成工程。 + +![enable sfud](figures/sfud-comp.png) + +## 使用流程 + +如下图是 SFUD 的使用流程图,首先需要移植 SFUD 组件、对 flash 进行初始化,然后再进行应用:根据名称获取 sfud_dev,对 sfud_dev 进行擦写读的操作。 + +![process](figures/process.png) + +(1)SFUD 移植: + +- SFUD 组件移植:可以参考文件 [sfud/port/sfud_port.c](https://github.com/armink/SFUD/blob/master/sfud/port/sfud_port.c) 或 RT-Thread 已实现的 [spi_flash_sfud.c](https://github.com/RT-Thread/rt-thread/blob/master/components/drivers/spi/spi_flash_sfud.c)。 +- Flash 设备支持:当目标 Flash 设备不支持 SFDP 功能时,需要在 sfud_flash_def.h 中定义 Flash 设备参数: + +```c +// | name | mf_id | type_id | capacity_id | capacity | write_mode | erase_gran | erase_gran_cmd | +#define SFUD_FLASH_CHIP_TABLE \ +{ \ + {"AT45DB161E", SFUD_MF_ID_ATMEL, 0x26, 0x00, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_DUAL_BUFFER, 512, 0x81}, \ + {"W25Q40BV", SFUD_MF_ID_WINBOND, 0x40, 0x13, 512L*1024L, SFUD_WM_PAGE_256B, 4096, 0x20}, \ + .... + {"F25L004", SFUD_MF_ID_ESMT, 0x20, 0x13, 512L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20}, \ + {"PCT25VF016B", SFUD_MF_ID_SST, 0x25, 0x41, 2L*1024L*1024L, SFUD_WM_BYTE|SFUD_WM_AAI, 4096, 0x20}, \ +} +#endif /* SFUD_USING_FLASH_INFO_TABLE */ +``` + +(2)SPI flash 驱动实现([参考潘多拉 flash 驱动](https://github.com/RT-Thread/rt-thread/blob/master/bsp/stm32/stm32l475-atk-pandora/board/ports/drv_qspi_flash.c)):在 SPI flash 驱动中完成了 flash 的初始化(可以导出到自动初始化),执行了 spi 从设备的挂载与探测,如下是本次示例中的 flash 初始化代码: + +```c +/* QSPI Flash 驱动 */ +static int rt_hw_qspi_flash_with_sfud_init(void) +{ + /* 往总线 qspi1 上挂载一个 qspi10 从设备 */ + stm32_qspi_bus_attach_device("qspi1", "qspi10", RT_NULL, 4, w25qxx_enter_qspi_mode, RT_NULL); + + /* 使用 SFUD 探测 qspi10 从设备,并将 qspi10 连接的 flash 初始化为块设备,名称 W25Q128 */ + if (RT_NULL == rt_sfud_flash_probe("W25Q128", "qspi10")) + { + return -RT_ERROR; + } + + return RT_EOK; +} +/* 导出到自动初始化 */ +INIT_COMPONENT_EXPORT(rt_hw_qspi_flash_with_sfud_init); +``` + +其中 qspi 模式需要实现进入 qspi 模式(如上面的 w25qxx_enter_qspi_mode),退出 qspi 模式为 RT_NULL 表示不再退出。stm32_qspi_bus_attach_device 原型如下: + +```c +rt_err_t stm32_qspi_bus_attach_device(const char *bus_name, + const char *device_name, + rt_uint32_t pin, // CS 引脚编号 + rt_uint8_t data_line_width, // QSPI 数据宽度:如 1/2/4 + void (*enter_qspi_mode)(), // 进入 qspi 模式 + void (*exit_qspi_mode)()); // 退出 qspi 模式 +``` + +注:如果是 SPI 模式,则 SPI flash 驱动如下所示: + +```c +/* SPI Flash 驱动 */ +static int rt_hw_spi_flash_init(void) +{ + /* 往总线 spi1 上挂载一个 spi10 从设备 */ + rt_hw_spi_device_attach("spi1", "spi10", GPIOB, GPIO_PIN_14); // CS 脚:PB14 + + /* 使用 SFUD 探测 spi10 从设备,并将 spi10 连接的 flash 初始化为块设备,名称 W25Q128 */ + if (RT_NULL == rt_sfud_flash_probe("W25Q128", "spi10")) + { + return -RT_ERROR; + }; + + return RT_EOK; +} +/* 导出到自动初始化 */ +INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init); +``` + +(3)sfud_dev 的获取有两种方法:通过 flash 名获取、通过从设备名获取,如下所示: + +```c +// sfud_dev = rt_sfud_flash_find_by_dev_name("W25Q128"); +sfud_flash_t rt_sfud_flash_find_by_dev_name(const char *flash_dev_name); + +// sfud_dev = rt_sfud_flash_find("qspi10"); +sfud_flash_t rt_sfud_flash_find(const char *spi_dev_name); +``` + +## 应用示例 + +使用 SFUD 组件,可通过命令行与编写应用两种方式对 flash 进行操作。 + +### sf 命令操作 Flash + +使用 sf 查看 sf 命令的使用方法,并且对 qspi10 进行探测(sf probe 后再经过转换可以得到 sfud_dev): + +```c + \ | / +- RT - Thread Operating System + / | \ 4.0.3 build Apr 26 2020 + 2006 - 2020 Copyright by rt-thread team +[D/drv.qspi] qspi init success! +[SFUD] Find a Winbond flash chip. Size is 16777216 bytes. +[SFUD] W25Q128 flash device is initialize success. +[D/drv.qspi] qspi init success! +msh > +msh >sf +Usage: +sf probe [spi_device] - probe and init SPI flash by given 'spi_device' +sf read addr size - read 'size' bytes starting at 'addr' +sf write addr data1 ... dataN - write some bytes 'data' to flash starting at 'addr' +sf erase addr size - erase 'size' bytes starting at 'addr' +sf status [ ] - read or write '1:volatile|0:non-volatile' 'status' +sf bench - full chip benchmark. DANGER: It will erase full chip! +msh > +msh >sf probe qspi10 +[D/drv.qspi] qspi init success! +[SFUD] Find a Winbond flash chip. Size is 16777216 bytes. +[SFUD] sf_cmd flash device is initialize success. +[D/drv.qspi] qspi init success! +16 MB sf_cmd is current selected device. +msh > +``` + +sf 擦、写、读操作测试(通过 sf 命令对获取到的 sfud_dev 进行擦写读操作): + +```c +msh >sf erase 0 10 +Erase the sf_cmd flash data success. Start from 0x00000000, size is 10. + +msh >sf write 0 1 2 3 +Write the sf_cmd flash data success. Start from 0x00000000, size is 3. +Write data: 1 2 3 . + +msh >sf read 0 10 +Read the sf_cmd flash data success. Start from 0x00000000, size is 10. The data is: +Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +[00000000] 01 02 03 FF FF FF FF FF FF FF .......... +``` + +### 编写应用操作 Flash + +可以在应用中对 flash 进行读写擦操作,读写擦的操作均是基于 `sfud_dev` 设备,然后在对设备进行擦读写。如下是一个简单的示例: + +```c +sfud_err result; +uint8_t *read_data; // 读取到的数据 +uint8_t *write_data; // 将要写入的数据 +sfud_flash *sfud_dev = NULL; + +sfud_dev = rt_sfud_flash_find("qspi10"); // 获取 sfud_dev +// 或者 sfud_dev = rt_sfud_flash_find_by_dev_name("W25Q128"); + +sfud_erase(sfud_dev, 0, 4096); // 擦除从 0 开始的 4096 字节 + +write_data = rt_malloc(32); +rt_memset(write_data, 1, 32); +sfud_write(sfud_dev, 0, 32, write_data); // 将数据 32 字节的 write_data 从 0 开始写入 flash + +read_data = rt_malloc(32); +sfud_read(sfud_dev, 0, 32, read_data); // 读取从 0 开始的 32 字节,存入 read_data +``` + +这样 SFUD 操作 Flash 的应用就完成了。 + +## 注意事项 + +### 1. Flash 先擦后写 + +写入之前请先擦除,这是 flash 特性决定的,因为 flash 的编程原理就是只能将 1 写为 0,而不能将 0 写为 1。擦除动作就是相应的页 / 块的所有位变为 1(所有字节均为 0xFF),所以不擦除直接写入会有问题。 + +例如以下示例,对 qspi10 进行探测: + +```c + \ | / +- RT - Thread Operating System + / | \ 4.0.3 build Apr 26 2020 + 2006 - 2020 Copyright by rt-thread team +[D/drv.qspi] qspi init success! +[SFUD] Find a Winbond flash chip. Size is 16777216 bytes. +[SFUD] W25Q128 flash device is initialize success. +[D/drv.qspi] qspi init success! +msh > +msh >sf probe qspi10 +[D/drv.qspi] qspi init success! +[SFUD] Find a Winbond flash chip. Size is 16777216 bytes. +[SFUD] sf_cmd flash device is initialize success. +[D/drv.qspi] qspi init success! +16 MB sf_cmd is current selected device. +msh > +``` + +(1)首先进行不擦先写的测试,可以发现读出来的数据并非写入的数据: + +```c +msh >sf write 0 1 2 3 +Write the sf_cmd flash data success. Start from 0x00000000, size is 3. +Write data: 1 2 3 . +msh > +msh >sf read 0 10 +Read the sf_cmd flash data success. Start from 0x00000000, size is 10. The data is: +Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +[00000000] 00 00 00 00 23 00 00 00 00 00 ....#..... +``` + +(2)然后进行先擦后写的测试,可以发现读出来的数据与写入数据一致: + +```c +msh >sf erase 0 10 +Erase the sf_cmd flash data success. Start from 0x00000000, size is 10. +msh > +msh >sf read 0 20 +Read the sf_cmd flash data success. Start from 0x00000000, size is 20. The data is: +Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +[00000000] FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF ................ +[00000010] FF FF FF FF .... +msh > +msh >sf read 4090 20 +Read the sf_cmd flash data success. Start from 0x00000FFA, size is 20. The data is: +Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +[00000FFA] FF FF FF FF FF FF 2C 10 00 00 22 00 00 00 00 00 ......,..."..... +[0000100A] 00 00 DD 3A ...: + +msh >sf write 0 1 2 3 +Write the sf_cmd flash data success. Start from 0x00000000, size is 3. +Write data: 1 2 3 . +msh > +msh >sf read 0 10 +Read the sf_cmd flash data success. Start from 0x00000000, size is 10. The data is: +Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +[00000000] 01 02 03 FF FF FF FF FF FF FF .......... +``` + +### 2. Flash 按页 / 块擦除 + +Flash 页大小一般是 4K、8K、16K 等,一个块也可能有 192、256、384、512 个页。Flash 按页擦除、按块擦除也是 flash 的特性之一。 + +在上面步骤(2)中使用了 sf erase 命令进行擦除。虽然 `sf erase 0 10 ` 命令的意思是从 0 地址开始擦除 10 个字节,但是该 flash 是按照 4K 字节进行擦除的,所以会擦出从 0 开始的 4K 字节。使用读取 `sf read 4090 20` 命令进行验证,读取从 4090 开始的 20 个字节时,发现从 4096 及之后均未进行擦除,也就是擦除了从 0 开始的 4K 字节。 + +`sf erase 2 10 ` 也是擦除从 0 开始的 4K 字节,而不是从 2 开始的 4K 字节。 + +```c +msh >sf erase 2 10 +Erase the sf_cmd flash data success. Start from 0x00000002, size is 10. +msh > +msh >sf read 0 10 +Read the sf_cmd flash data success. Start from 0x00000000, size is 10. The data is: +Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +[00000000] FF FF FF FF FF FF FF FF FF FF .......... + +msh >sf read 4090 20 +Read the sf_cmd flash data success. Start from 0x00000FFA, size is 20. The data is: +Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +[00000FFA] FF FF FF FF FF FF 2C 10 00 00 22 00 00 00 00 00 ......,..."..... +[0000100A] 00 00 DD 3A +``` + +### 3. Flash 写粒度 + +在上面的示例中我们使用的是 spi nor flash,每次支持写入一个 bit 的数据。而不同的 flash 支持的写入粒度不尽相同,必须一次性写入写粒度的整数倍才可以写入成功。以下列举几种常见 Flash 写粒度: + +- nor flash: 1 bit +- stm32f4: 8 bit +- stm32f1: 32 bit +- stm32l4: 64 bit + diff --git a/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/flash-select.png b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/flash-select.png new file mode 100644 index 0000000000000000000000000000000000000000..2b27c898d01d1c7b128f1e905ead824004b1c350 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/flash-select.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/process.png b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/process.png new file mode 100644 index 0000000000000000000000000000000000000000..7f714c4467502e8c02cb3e4cc658f2c25ed68bf5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/process.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/qspi-flash.png b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/qspi-flash.png new file mode 100644 index 0000000000000000000000000000000000000000..fa0058cb728793cca5eb9e150bca286ff054c6ea Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/qspi-flash.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/sfud-comp.png b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/sfud-comp.png new file mode 100644 index 0000000000000000000000000000000000000000..25bc9605efa942e81cb7e71c3b84e707a47b153d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/sfud-comp.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/spi-bus.png b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/spi-bus.png new file mode 100644 index 0000000000000000000000000000000000000000..4a339d73784afab77fde0e1a9997bdd8864d406c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/components/sfud/figures/spi-bus.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/README.md b/rt-thread-version/rt-thread-standard/application-note/debug/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/an0013-CmBacktrace.md b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/an0013-CmBacktrace.md new file mode 100644 index 0000000000000000000000000000000000000000..94f25baa1deba8492c089b4639c900b7423ded82 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/an0013-CmBacktrace.md @@ -0,0 +1,233 @@ +# CmBacktrace 应用 + +本文描述了在 RT-Thread 中使用 CmBacktrace。 + +## 简介 + +对于从 C51 、MSP430 等简单单片机转而使用更加复杂的 ARM 新人来说,时不时出现的 "hard falut" 死机会让新人瞬间懵掉。定位错误的方法也往往是连接上仿真器,一步步 F10/F11 单步,定位到具体的错误代码,再去猜测、排除、推敲错误原因,这种过程十分痛苦,且花费的时间很长。 当然,也有部分开发者通过故障寄存器信息来定位故障原因及故障代码地址,虽然这样能解决一小部分问题,但是重复的、繁琐的分析过程也会耽误很多时间。而且对于一些复杂问题,只依靠代码地址是无法解决的,必须得还原错误现场的函数调用逻辑关系。虽然连接仿真器可以查看到的函数调用栈,但故障状态下是无法显示的,所以还是得一步步 F10/F11 单步去定位错误代码的位置。另外,很多产品真机调试时必须断开仿真器,这又使定位错误代码雪上加霜。 + +为了能让开发者更快的知道造成 hard falut 的原因,更快的定位到错误代码的位置,本文将一步步介绍 CmBacktrace 的相关知识和使用方法,让开发者能不费吹灰之力就找出代码中的问题所在。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +* 在 https://github.com/armink/CmBacktrace/tree/master/tools/addr2line 页面中下载 addr2line(需要按照自己的系统版本下载),然后将下载下来的 addr2line 拷贝至 `C:\Windows` 下 ,这样就可以使用 addr2line 了。 + +## CmBacktrace 简介 + +[CmBacktrace](https://github.com/armink/CmBacktrace) (Cortex Microcontroller Backtrace)是一款针对 ARM Cortex-M 系列 MCU 的错误代码自动追踪、定位,错误原因自动分析的开源库。 + +主要特性如下: + +- 支持的错误包括: + + - 断言(assert) + + - 故障(Hard Fault, Memory Management Fault, Bus Fault, Usage Fault, Debug Fault) + +- 故障原因 **自动诊断** :可在故障发生时,自动分析出故障的原因,定位发生故障的代码位置,而无需再手动分析繁杂的故障寄存器; + +- 输出错误现场的 **函数调用栈**(需配合 addr2line 工具进行精确定位),还原发生错误时的现场信息,定位问题代码位置、逻辑更加快捷、精准。也可以在正常状态下使用该库,获取当前的函数调用栈; + +- 支持 裸机 及以下操作系统平台: + + - [RT-Thread](http://www.rt-thread.org) + + - UCOS + + - FreeRTOS(需修改源码) + +- 根据错误现场状态,输出对应的 线程栈 或 C 主栈; + +- 故障诊断信息支持多国语言(目前:简体中文、英文); + +- 适配 Cortex-M0/M3/M4/M7 MCU; + +- 支持 IAR、MDK、GCC 编译器; + +## 配置选项 + +### ENV 配置 + +RT-Thread 已经对 CmBacktrace 做了适配,直接在 ENV 使能 CmBacktrace 就可以使用了。 + +下面介绍如何在 ENV 中配置 CmBacktrace: + +- 打开 ENV,进入相应的 BSP 目录 +- 输入 menuconfig +- 进入 RT-Thread online packages -> tools packages +- 使能 CmBacktrace +- 进入 CmBacktrace 配置界面 +- 选择自己的 CPU 平台 +- 选择打印的语言 +- 选择版本,推荐使用最新版 + +![CmBacktrace_env](figures/CmBacktrace_env.png) + +### 确认宏定义 + +CmBacktrace 的运行需要知道存放代码的 SECTION 的开始地址和结束地址以及栈的 SECTION 的开始地址和结束地址。用户只需要查看 cmb_def.h 文件里默认定义的 CMB_CSTACK_BLOCK_NAME 和 CMB_CODE_SECTION_NAME 这两个宏是否正确即可。如不正确,用户需要根据分散加载文件和启动文件来确定这两个宏的值并在 cmb_cfg.h 里重新定义这两个宏。 + +这里以 rt1052 的 mdk 工程为例进行讲解如何在工程里找到这两个宏的值。首先找 CMB_CSTACK_BLOCK_NAME 的值,我们打开工程里的启动文件,可以在文件的开头看到这样一段代码 + +``` + AREA RESET, DATA, READONLY + EXPORT __Vectors + EXPORT __Vectors_End + EXPORT __Vectors_Size + IMPORT |Image$$ARM_LIB_STACK$$ZI$$Limit| + +__Vectors DCD |Image$$ARM_LIB_STACK$$ZI$$Limit| ; Top of Stack +``` + +Image$$ARM_LIB_STACK$$ZI$$Limit 就是我们要找的内容了,因为代码里会自动拼接上最后的 $$Limit,所以 CMB_CSTACK_BLOCK_NAME 的值应该是 Image$$ARM_LIB_STACK$$ZI。 + +CMB_CODE_SECTION_NAME 的值在分散加载文件里寻找,分散加载文件可以点击 MDK 的 Options -> Linker 选项面板里的 Edit... 按纽打开 + +![MDK_scf](figures/MDK_scf.png) + +我们可以找到这样一段代码 + +``` + ER_IROM1 m_text_start m_text_size ; load address = execution address + { + * (RESET,+FIRST) + * (InRoot$$Sections) + .ANY (+RO) + } +``` + +保存有 .ANY (+RO) 的 SECTION 名字就是我们要找的值,所以,CMB_CODE_SECTION_NAME 的值为 ER_IROM1。 + +### 开启 C99 + +CmBacktrace 的使用需要 C99 的支持,使用 MDK 的开发者可以在 Options -> C/C++ 面板中勾选 C99 Mode 选项。 + +![MDK_C99](figures/MDK_C99.png) + +使用 IAR 的开发者,可以在 Options -> C/C++ Compiler 中选择 C99。 + +![IAR_C99](figures/IAR_C99.png) + +使用 GCC 进行编译的用户,在编译配置中增加 `-std=c99` 即可 。 + +### 确定初始化参数 + +在使用 CmBacktrace 之前需要先调用下初始化函数,函数原型如下: + +``` +void cm_backtrace_init(const char *firmware_name, const char *hardware_ver, const char *software_ver) +``` +CmBacktrace 的初始化函数需要 3 个参数,第一个参数是固件名字,第二个参数是硬件版本,第三个参数是软件版本。这三个参数会在发生 hard fault 时打印出来,firmware_name 需要填写生成的固件名称,错误填写会导致在使用 addr2line 时无法找到文件。hardware_ver 和 software_ver 建议填写真实的软硬件版本号,方便后期调试和维护。在 cmb_port.c 文件中,我们可以看到 RT-Thread 已经将 rt_cm_backtrace_init 函数进行了自动初始化,默认的三个参数分别是 `rtthread`,`1.0`,`1.0`,开发者需要按照实际情况进行更改。 + +## 运行示例代码 + +CmBacktrace 提供了一个测试函数,提供除零测试和执行非对齐访问的测试。当做完上面的准备工作后,开发者可以直接将工程编译,下载进板子里,进行测试,判断 CmBacktrace 是否正常工作。 + +CmBacktrace 导出到 finsh shell 中的测试函数命令为 `cmb_test`,输入 `cmb_test DIVBYZERO` 就是进行除零测试,输入 `cmb_test UNALIGNED` 就是执行非对齐访问的测试。 + +我们看下运行完除零测试的结果: + +``` +msh />cmb_test DIVBYZERO +thread pri status sp stack size max used left tick error +-------- --- ------- ---------- ---------- ------ ---------- --- +tshell 20 ready 0x00000100 0x00001000 23% 0x00000009 000 +phy 30 suspend 0x0000006c 0x00000200 30% 0x00000001 000 +tcpip 10 suspend 0x000000b4 0x00000800 17% 0x00000014 000 +etx 12 suspend 0x00000088 0x00000400 13% 0x00000010 000 +erx 12 suspend 0x00000088 0x00000400 13% 0x00000010 000 +mmcsd_de 22 suspend 0x00000090 0x00000400 49% 0x00000013 000 +tidle 31 ready 0x00000054 0x00000100 32% 0x00000018 000 +main 10 suspend 0x00000064 0x00000800 35% 0x00000012 000 + +Firmware name: rtthread-imxrt, hardware version: 1.0, software version: 1.0 +Fault on thread tshell +===== Thread stack information ===== + addr: 80002ad0 data: 00000012 + addr: 80002ad4 data: 6002ae58 + addr: 80002ad8 data: 80001a40 + addr: 80002adc data: 6000b575 + addr: 80002ae0 data: 80001a40 + addr: 80002ae4 data: 80001a49 + addr: 80002ae8 data: 00000000 + addr: 80002aec data: 00000000 + addr: 80002af0 data: 00000000 + addr: 80002af4 data: 00000000 + addr: 80002af8 data: 00000000 + addr: 80002afc data: 00000000 + addr: 80002b00 data: 00000000 + addr: 80002b04 data: 00000000 + addr: 80002b08 data: 20000c7c + addr: 80002b0c data: 00000012 + addr: 80002b10 data: 80001a40 + addr: 80002b14 data: 20000c7c + addr: 80002b18 data: 00000001 + addr: 80002b1c data: deadbeef + addr: 80002b20 data: deadbeef + addr: 80002b24 data: deadbeef + addr: 80002b28 data: deadbeef + addr: 80002b2c data: 60019ffb + addr: 80002b30 data: 00000001 + addr: 80002b34 data: 0000000d + addr: 80002b38 data: 00000000 + addr: 80002b3c data: 60015a7b + addr: 80002b40 data: 23232323 +==================================== +=================== Registers information ==================== + R0 : 0000000a R1 : 00000000 R2 : 0000004f R3 : 80808000 + R12: 01010101 LR : 6000c5ad PC : 6000c5c8 PSR: 41000000 +============================================================== +Usage fault is caused by Indicates a divide by zero has taken place (can be set only if DIV_0_TRP is set) +Show more call stack info by run: addr2line -e rtthread-imxrt.axf -a -f 6000c5c8 6000c5a9 6002ae54 6000b571 60019ff7 60015a77 + +``` + +CmBacktrace 首先打印出了发生 hard falut 时的所有线程信息,接着打印了固件名字和软硬件版本号,再打印了错误是发生在 tshell 这个线程里面的(因为我们是在 tshell 这个线程里调用的除零测试函数),紧接着打印的是线程的栈信息和寄存器信息。最后两行信息是最重要的,倒数第二行介绍了发生故障的原因,是因为除零造成的。最后一行提示如果需要获取函数调用栈,需要在 addr2line 中运行 CmBacktrace 给出的参数。 + +在使用 addr2line 之前我们要先确认保存了工程对象文件的文件夹位置。使用 mdk 的开发者,可以在 Options -> Output 面板中查看,设置对象文件的保存路径。 + +![mdk_object](figures/mdk_object.png) + +使用 IAR 的开发者,可以在 Options -> General Options 面板的 Output 选项中查看和设置。 + +![IAR_object](figures/IAR_object.png) + +我们将 run:后面的所有内容都复制下来,然后进入保存了工程生成的对象文件的文件夹,打开 env,将刚刚复制的内容粘贴上去,按下回车,错误现场的函数调用栈就会输出出来,我们看下刚刚进行除零测试时的函数调用栈的信息 + +``` +> addr2line -e rtthread-imxrt.axf -a -f 6000c5c8 6000c5a9 6002ae54 6000b571 60019ff7 60015a77 +0x6000c5c8 +cmb_test +D:\rt-thread\bsp\imxrt1052-evk/packages\CmBacktrace-v1.2.0\/cmb_port.c:87 +0x6000c5a9 +cmb_test +D:\rt-thread\bsp\imxrt1052-evk/packages\CmBacktrace-v1.2.0\/cmb_port.c:82 +0x6002ae54 +FSymTab$$Base +??:? +0x6000b571 +msh_get_cmd +D:\rt-thread\bsp\imxrt1052-evk/..\..\components\finsh\/msh.c:312 +0x60019ff7 +msh_exec +D:\rt-thread\bsp\imxrt1052-evk/..\..\components\finsh\/msh.c:335 +0x60015a77 +finsh_thread_entry +D:\rt-thread\bsp\imxrt1052-evk/..\..\components\finsh\/shell.c:613 +``` + +我们可以看到,CmBacktrace 不仅仅定位出了是 cmb_port.c 里第 87 行产生的问题,还打印出了函数调用逻辑关系,方便开发者进行 BUG 修复。 + +> [!NOTE] +> 注:* 使用前必须确认自己的 MCU 是 ARM Cortex-M0(+)/M3/M4/M7 架构,其他架构暂不支持。 + * 使用前要确定 CMB_CSTACK_BLOCK_NAME 和 CMB_CODE_SECTION_NAME 两个宏的宏定义,如果定义错误,CmBacktrace 会无法正确使用。 + * 初始化时要输入正确的固件名称,不然使用 addr2line 时会提示找不到文件 + * 当线程的栈被写穿时,CmBacktrace 无法正常使用。 + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/CmBacktrace_env.png b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/CmBacktrace_env.png new file mode 100644 index 0000000000000000000000000000000000000000..fd10ea9541f424f33ad90c8db210712ed742bc14 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/CmBacktrace_env.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/IAR_C99.png b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/IAR_C99.png new file mode 100644 index 0000000000000000000000000000000000000000..ae9b527e3951c99d711bd915f26d01d4958c1485 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/IAR_C99.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/IAR_object.png b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/IAR_object.png new file mode 100644 index 0000000000000000000000000000000000000000..f006551f353a09612ca01b4dd1e1cc481140eb2d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/IAR_object.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/MDK_C99.png b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/MDK_C99.png new file mode 100644 index 0000000000000000000000000000000000000000..6784b4336904b515ecc00c2148b250147dda918f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/MDK_C99.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/MDK_scf.png b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/MDK_scf.png new file mode 100644 index 0000000000000000000000000000000000000000..9620a3fb21768a6e2401fff52c23af1bcacc1236 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/MDK_scf.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/mdk_object.png b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/mdk_object.png new file mode 100644 index 0000000000000000000000000000000000000000..51df8e75c9b07b69636c2c39bc0a3ee1d3dde667 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/cmbacktrace/figures/mdk_object.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/an0009-systemview.md b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/an0009-systemview.md new file mode 100644 index 0000000000000000000000000000000000000000..b762cfd3ccb99322fef09110722b84d2ce2651c0 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/an0009-systemview.md @@ -0,0 +1,230 @@ +# SystemView 使用指南 + +> [!NOTE] +> 本文主要介绍 SystemView 可视化分析工具,以及如何在 RT-Thread 上使用它对系统进行调试分析。 + +## 简介 + +随着 MCU 的性能越来越强,嵌入式产品的功能越来越复杂,对于系统的调试和分析提出了新挑战,调试某个功能或问题通常需要花费大量精力,SystemView 是一款帮助用户进行系统调试和分析的强大工具,能够显著缩短开发和调试时间,提高开发效率。本文的目的在于帮助大家在 RT-Thread 上使用 SystemView 工具对系统进行调试和分析。 + +本文准备资料如下: + +* [SystemView 软件包](https://github.com/RT-Thread-packages/SEGGER_SystemView) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +## SystemView 简介 + +SystemView 是一个可以在线调试嵌入式系统的工具,它可以分析有哪些中断、任务执行了,以及这些中断、任务执行的先后关系。还可以查看一些内核对象持有和释放的时间点,比如信号量、互斥量、事件、消息队列等。这在开发和处理具有多个线程和事件的复杂系统时尤其有效。 + +SystemView 由两个部分组成: + +* PC端程序,收集并展示来自嵌入端传来的数据,也可以将这些数据保存到本地供以后分析。 +* 嵌入式端程序,收集嵌入式系统的运行数据,并将它们通过 J-Link 的 RTT 模块传输给 PC 端 + +> [!NOTE] +> 因 SystemView 的数据传输利用了 J-Link 的 RTT 技术,所以只有用 J-Link 连接开发板的时候才能使用 SystemView + +![SystemView 的主界面](figures/view.png) + +RT-Thread 提供的 [SystemView 软件包](https://github.com/RT-Thread/segger_debug) 是 SystemView 工具的嵌入式端程序实现,主要功能有:配置 SYSTEMVIEW 和 RTT 的具体参数,收集和格式化监视数据,将数据通过 J-Link 发送给 PC 端等。只需要利用 RT-Thread 推出的 [Env 工具](https://www.rt-thread.org/page/download.html) 使能 SystemView 软件包,并对其进行简单的配置,就能完成 SystemView 的嵌入式端程序的配置。 + +## 配置 SystemView 软件包 + +以正点原子 RT1052 开发板为例 + +步骤一:在 Env 工具中进入 menuconfig 图形化配置工具 + +打开 Env 工具,使用命令 `cd D:\rt-thread\bsp\imxrt1052-evk` 切换到 RT-Thread 源码 BSP 根目录下的 imxrt1052-evk 目录,然后输入命令 `menuconfig` 配置工程。 + +> [!NOTE] +> menuconfig 是一种图形化配置工具,RT-Thread 使用其对整个系统进行配置、裁剪。 + +![menuconfig 工具的界面](figures/env1.png) + +步骤二:使能 SystemView 软件包。 + +利用上下键选中 RT-Thread online packages,按回车键进入下级菜单,然后在 tools packages 中打开 SystemView 。 + +![使能SystemView](figures/env3.png) + +步骤三:具体的配置。 + +按回车键进入下级菜单,进行具体的配置(输入 ?可以显示选项的具体信息)。 + +下面介绍两个常用的选项,其他的选项可以根据需要自己进行配置(参考最后一部分参数目录): + +(1) 将下图红框圈住的选项,更改为自己开发板对应的内核,如 I.MX-RT1052 对应 M7 内核 + +![具体的配置](figures/systemview.png) + +(2) 进入 SystemView buffer configuration 下级菜单,关闭事后分析模式 + +![关闭事后分析模式](figures/systemview1.png) + +开启事后分析模式之后,**需要**在工程里调用函数 SEGGER_SYSVIEW_Start() 启动录制。系统事件会被不断的记录下来,并在缓冲区满时覆盖旧的事件。所以当读取缓冲区时,只能读取到最新的事件。 + +> [!NOTE] +> 当系统运行了很长时间并突然崩溃时,事后分析可能会很有用。在这种情况下,可以从目标中的缓冲区中读取最新的事件,SystemView 可以显示系统崩溃之前发生的情况。 + +关闭事后分析模式之后,就处于连续记录模式,可以在 PC 端控制录制的开始和结束,连续记录系统的运行。要分析下面的 demo 的运行,需要开启连续记录模式。 + +配置好选项之后,按 ESC 返回,退出并保存配置,这样 SystemView 软件包的使能和相关配置就完成了。 + +后面我们以一个具体的 demo 来讲解 SystemView 工具的使用 + +## 添加示例代码 + +在文件 main.c 中添加以下代码,然后在 main 函数中调用 demo 初始化函数 `demo_init();` 运行 demo。 + +```c +/* +* 程序清单:systemview 演示代码 +* +* 这个例子中将创建一个动态信号量(初始值为0)及两个动态线程,在这个两个动态线程中 +* 线程2将试图采用永远等待方式去持有信号量,持有成功之后发送运行标志。 +* 线程1将先发送正在运行标志,然后释放一次信号量,因线程2的优先级较高,线程2持有到信号量将线程1抢断。 +* 然后线程2发送运行标志之后,获取不到信号量,被挂起,线程1继续运行 +*/ + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 +/* 指向信号量的指针 */ +rt_sem_t sem_food; +/* 线程1入口 */ +void thread1_entry(void* parameter) +{ + while (1) + { + /* 线程1第一次运行 */ + rt_kprintf("thread1 is run!\n"); + /* 释放一次信号量 */ + rt_sem_release(sem_food); + /* 线程1第二次运行 */ + rt_kprintf("thread1 run again!\n"); + /* 线程1延时1秒 */ + rt_thread_delay(RT_TICK_PER_SECOND); + } +} +/* 线程2入口 */ +void thread2_entry(void* parameter) +{ + while (1) + { + /* 试图持有信号量,并永远等待直到持有到信号量 */ + rt_sem_take(sem_food, RT_WAITING_FOREVER); + /* 线程2正在运行 */ + rt_kprintf("thread2 is run!\n"); + } +} +/* DEMO初始化函数 */ +void demo_init(void) +{ + /* 指向线程控制块的指针 */ + rt_thread_t thread1_id, thread2_id; + /* 创建一个信号量,初始值是0 */ + sem_food = rt_sem_create("sem_food", 0, RT_IPC_FLAG_FIFO); + if (sem_food == RT_NULL) + { + rt_kprintf("sem created fail!\n"); + return ; + } + /* 创建线程1 */ + thread1_id = rt_thread_create("thread1", + thread1_entry, RT_NULL,/* 线程入口是thread1_entry, 参数RT_NULL */ + THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); + if (thread1_id != RT_NULL) + rt_thread_startup(thread1_id); + /* 创建线程2 */ + thread2_id = rt_thread_create("thread2", + thread2_entry, RT_NULL,/* 线程入口是thread2_entry, 参数RT_NULL */ + THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE); + if (thread2_id != RT_NULL) + rt_thread_startup(thread2_id); +} +``` + +## SystemView 配置及使用 + +步骤一:下载 SystemView 分析工具 [下载链接](https://www.segger.com/products/development-tools/systemview/) + +步骤二:为 RT-Thread 添加系统描述文件 + +首先找到开发板目录下的 packages 目录,然后在 packages 目录下找到 segger_debug-xxx 目录,在这个目录里面有一个 SystemView_Description 文件夹,RT-Thread 系统的描述文件就在里面,具体的目录结构如下所示: + +`bsp\\你自己的开发板\\packages\\segger_debug-xxx\\SystemView_Description\\SYSVIEW_RT-Thread.txt` + +将这个文件复制到 SystemView 工具安装目录下的 Description 目录下,这样 SystemView 就可以识别出 RT-Thread 系统了。 + +步骤三:配置设备信息,开始录制 + +双击打开 SystemView PC端程序,下面是主界面上一些常用按钮的功能介绍。 + +![PC 端程序按钮介绍](figures/PC1.png) + +当处于连续记录模式时,点击开始录制按钮,会弹出一个配置设备信息的窗口。 + +![配置设备信息的窗口](figures/PC2.png) + +图中 RTT 控制块的地址已经通过串口打印出来了,只要打开终端软件将打印出来的地址复制到上方即可。 + +![从终端中获取RTT控制块的地址](figures/RTT.png) + +点击 OK ,现在 SystemView 就开始实时录制系统信息了,下面是系统实时的运行情况。 + +![系统实时的运行情况](figures/run.png) + +步骤四:结束录制,分析系统 + +点击结束录制按钮,结束录制。将鼠标放置到时间轴窗口里,利用滚轮将事件放大到适合分析的大小 + +![分析线程1、2运行](figures/run1.png) + +利用 SystemView 工具我们可以看出来,系统的运行确实如我们设想的那样,线程1先开始运行,然后在释放信号量之后被线程2抢断。然后线程2运行,之后因获取不到信号量被挂起,线程1继续运行。从上面的事件列表还可以看到每个线程获取或释放信号量的具体时间。 + +> [!NOTE] +> 如果开启了事后分析模式,就不能点击开始录制按钮了,而是要点击读记录数据的按钮,其他的的操作和实时分析模式一样。 + +看完这篇文档,相信你已经学会如何使用 RT-Thread 上 的 SystemView 分析工具了。 + +## menuconfig 配置选项 + +| **参数** | **描述** | +| ---------------------------- | ------------------------------------------------------------ | +| App name | 应用程序的名字 | +| Device name | 设备所用内核 | +| Timestap freq | 时间戳频率 (0 表示使用系统默认频率) | +| cpu freq | cpu频率(0 表示使用系统默认频率) | +| RAM base | RAM基地址 默认值:0x2000 0000 | +| Event ID offset | 事件ID的偏移 默认值:32 | +| Using the Cortex-M cycle ... | 使用系统频率作为时间戳 | +| System description 0-2 | 系统描述符 "I#num=name, ..." num 是中断标号, name 是中断名称 | + +| **参数** | **描述** | +| ----------------------------------------- | -------------------------------------------------- | +| Max num of up buffer | RTT输出缓冲区的最大数目 默认值:2 | +| Max num of dowm buffer | RTT输入缓冲区的最大数目 默认值:2 | +| buffer size up | 用于RTT终端输出通道的字节数 默认值:1024 字节 | +| buffer size down | 用于RTT终端输入通道的字节数 默认值:16 字节 | +| Segger RTT printf buffer size | 通过RTT printf发送字符块时的缓冲区大小 默认值:64 | +| Mode for pre-initialized terminal channel | 预初始RTT终端通道模式 默认值: NO_BLOCK_SKIP | +| Max Interrupt priority | 中断优先级的最大值 | +| Use RTT ASM | 使用汇编版本的RTT | +| memcpy use byte-loop | 使用一个简单的byte-loop代替memcpy | + +| **参数** | **描述** | +| -------------------------------- | ---------------------------------------------------- | +| RTT buffer size | SystemView用于记录缓冲区的字节数 | +| RTT channel | RTT通道是用于SystemView事件记录和通信,0表示自动选择 | +| Use static buffer | 使用静态缓冲区 ,能够节省空间 | +| Enable post mortem analysis mode | 使能事后分析模式 | + +| **参数** | **描述** | +| -------- | ------------------------------------------------------- | +| ID Base | 从SystemView包中记录的ID中减去的值 默认值:0x1000 0000 | +| ID Shift | 在SystemView包中记录的ID偏移的位数 默认值:2 | + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/PC1.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/PC1.png new file mode 100644 index 0000000000000000000000000000000000000000..75e2bbd2db8c965946549eb49fad967b749438ef Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/PC1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/PC2.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/PC2.png new file mode 100644 index 0000000000000000000000000000000000000000..e7f47a62b7ed8b640f8fee34a9f2f495341fca65 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/PC2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/RTT.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/RTT.png new file mode 100644 index 0000000000000000000000000000000000000000..4afd632152cb51b21f1ca2c1cdf6b1137f2acfcb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/RTT.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/env1.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/env1.png new file mode 100644 index 0000000000000000000000000000000000000000..46c795681031c35b9c4e71cb4cb1b1d5adb41b1b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/env1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/env3.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/env3.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc49173e2de2028b612467ecbbdcd5871a55fc6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/env3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/run.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/run.png new file mode 100644 index 0000000000000000000000000000000000000000..4904b2fba251b3e361c427a0b1968e19af6a5d7e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/run.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/run1.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/run1.png new file mode 100644 index 0000000000000000000000000000000000000000..fdc44d42cae9e4f0a1c1546a98af09d735a3ac6a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/run1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/systemview.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/systemview.png new file mode 100644 index 0000000000000000000000000000000000000000..b31db2773e15d6934cd17eaea3b8ede289ef2c4a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/systemview.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/systemview1.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/systemview1.png new file mode 100644 index 0000000000000000000000000000000000000000..7a1464773f473e95d7b9d3e6c687c389b218579e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/systemview1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/view.png b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/view.png new file mode 100644 index 0000000000000000000000000000000000000000..fd65692041c4679053795564100b87b899a79c61 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/debug/systemview/figures/view.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/an0002-rtthread-driver-gpio.md b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/an0002-rtthread-driver-gpio.md new file mode 100644 index 0000000000000000000000000000000000000000..7835a61835a5df1f0d299f48f2c3f5e8629553bd --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/an0002-rtthread-driver-gpio.md @@ -0,0 +1,210 @@ +# PIN 设备应用笔记 # + +本文描述了如何使用 RT-Thread 的PIN 设备驱动,包括驱动的配置、相关 API 的应用。并给出了在正点原子 STM32F4 探索者开发板上验证的代码示例。 + +## 本文的目的和结构 ## + +### 本文的目的和背景 ### + +为了给用户提供操作 GPIO 的通用 API,方便应用程序开发,RT-Thread 提供了类似 Arduino 风格的 API 用于操作 GPIO,如设置 GPIO 模式和输出电平、读取 GPIO 输入电平、配置 GPIO 外部中断。本文说明了如何使用 RT-Thread 提供的 PIN 设备接口。 + +### 本文的结构 ### + +本文给出了在正点原子 STM32F4 探索者开发板上验证的代码示例。 + +## 硬件平台简介 + +本文基于正点原子 STM32F4 探索者开发板,给出了PIN 设备的具体应用示例代码,包含管脚输入、输出和外部中断的使用方法。由于 RT-Thread 上层应用 API 的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。 + +正点原子 STM32F4 探索者开发板使用的 MCU 是 STM32F407ZGT6,板载 2 颗 LED 和 4 个独立按键。LED 分别连接到 MCU 的 GPIOF9、GPIOF10,KEY0 按键连接到 GPIOE4,KEY1 按键连接到 GPIOE3,KEY2 按键连接到 GPIOE2,WK_UP 按键连接到 GPIOA0,2 颗 LED 均为低电平点亮,独立按键 KEY0、KEY1、KEY2 按下为低电平;WK_UP 按下为高电平。 + +![正点原子开发板](figures/an0002_2.png) + +## 准备和配置工程 + +1. 下载 [RT-Thread 源码](https://github.com/RT-Thread/rt-thread) + +2. 下载 [示例代码](main.rar) + +3. + +4. 进入 `rt-thread\bsp\stm32f4xx-HAL` 目录,在 Env 命令行中输入 menuconfig,进入配置界面,使用 menuconfig 工具(学习如何使用)配置工程。 + +(1) 在 menuconfig 配置界面依次选择 RT-Thread Components ---> Device Drivers ---> Using generic GPIO device drivers,如图所示: + +![menuconfig 中开启 GPIO 驱动](figures/an0002_3.png) + +(2) 输入命令 +`scons --target=mdk5 -s` +生成 mdk5 工程。将示例代码附带的 main.c 替换掉 BSP 中的 main.c,如图所示: + +![加入测试代码](figures/an0002_4.png) + +(3) 编译,下载程序,在终端输入 list_device 命令可以看到 device 是 pin、类型是 Miscellaneous Device 就说明PIN 设备驱动添加成功了。 + +![查看 pin 设备](figures/an0002_5.png) + +下面是 3 个PIN 设备驱动 API 应用示例,分别是:GPIO 输出、GPIO 输入、GPIO 外部中断,这些代码在正点原子 STM32F4 探索者开发板上验证通过。 + +## GPIO 输出配置 + +示例 1:配置 GPIO 为输出,点亮 LED。根据原理图,GPIOF9 连接到了板载红色 LED,丝印为 DS0;GPIOF10 连接到了板载绿色 LED,丝印为 DS1。GPIOF9 输出低电平则点亮 DS0,GPIOF9 输出高电平则 DS0 不亮;GPIOF10 输出低电平则点亮 DS1,GPIOF10 输出高电平则 DS1 不亮。 + +![LED 原理图](figures/an0002_6.png) + +```c +#define LED0 21 //PF9--21,在 drv_gpio.c 文件 pin_index pins[] 中查到 PF9 编号为 21 +#define LED1 22 //PF10--22,在 drv_gpio.c 文件 pin_index pins[] 中查到 PF10 编号为 22 + void led_thread_entry(void* parameter) +{ + // 设置管脚为输出模式 + rt_pin_mode(LED0, PIN_MODE_OUTPUT); + // 设置管脚为输出模式 + rt_pin_mode(LED1, PIN_MODE_OUTPUT); + while (1) + { + // 输出低电平,LED0 亮 + rt_pin_write(LED0, PIN_LOW); + // 输出低电平,LED1 亮 + rt_pin_write(LED1, PIN_LOW); + // 挂起 500ms + rt_thread_delay(rt_tick_from_millisecond(500)); + + // 输出高电平,LED0 灭 + rt_pin_write(LED0, PIN_HIGH); + // 输出高电平,LED1 灭 + rt_pin_write(LED1, PIN_HIGH); + // 挂起 500ms + rt_thread_delay(rt_tick_from_millisecond(500)); + } +} +``` + +在线程入口函数 led_thread_entry 里首先调用 rt_pin_mode 设置管脚模式为输出模式,然后就进入 while(1) 循环,间隔 500ms 调用 rt_pin_write 来改变 GPIO 输出电平。 +下面是创建线程的代码: + +```c + rt_thread_t tid;// 线程句柄 + /* 创建 led 线程 */ + tid = rt_thread_create("led", + led_thread_entry, + RT_NULL, + 1024, + 3, + 10); + /* 创建成功则启动线程 */ + if (tid != RT_NULL) + rt_thread_startup(tid); +``` + +编译、下载程序,我们将看到 LED 间隔 500ms 闪烁的现象。 + +## GPIO 输入配置 + +示例 2:配置 GPIOE3、GPIOE2 为上拉输入,GPIOA0 为下拉输入,检测按键信号。根据原理图,GPIOE3 连接到按键 KEY1,按键被按下时 GPIOE3 应读取到低电平,按键没有被按下时 GPIOE3 应读取到高电平;GPIOE2 连接到按键 KEY2,按键被按下时 GPIOE2 应读取到低电平,按键没有被按下时 GPIOE2 应读取到高电平;GPIOA0 连接到按键 WK_UP,按键被按下时 GPIOA0 应读取到高电平,按键没有被按下时 GPIOA0 应读取到低电平。 + +![按键原理图](figures/an0002_7.png) + +```c +#define KEY1 2 //PE3--2,在 drv_gpio.c 文件 pin_index pins[] 中查到 PE3 编号为 2 +#define KEY2 1 //PE2--1,在 drv_gpio.c 文件 pin_index pins[] 中查到 PE2 编号为 1 +#define WK_UP 34 //PA0--34,在 drv_gpio.c 文件 pin_index pins[] 中查到 PA0 编号为 34 +void key_thread_entry(void* parameter) +{ + //PE2、PE3 设置上拉输入 + rt_pin_mode(KEY1, PIN_MODE_INPUT_PULLUP); + rt_pin_mode(KEY2, PIN_MODE_INPUT_PULLUP); + //PA0 设置为下拉输入 + rt_pin_mode(WK_UP, PIN_MODE_INPUT_PULLDOWN); + + while (1) + { + // 检测到低电平,即按键 1 按下了 + if (rt_pin_read(KEY1) == PIN_LOW) + { + rt_kprintf("key1 pressed!\n"); + } + // 检测到低电平,即按键 2 按下了 + if (rt_pin_read(KEY2) == PIN_LOW) + { + rt_kprintf("key2 pressed!\n"); + } + // 检测到高电平,即按键 wp 按下了 + if (rt_pin_read(WK_UP) == PIN_HIGH) + { + rt_kprintf("WK_UP pressed!\n"); + } + // 挂起 10ms + rt_thread_delay(rt_tick_from_millisecond(10)); + } +} +``` + +在线程入口函数 key_thread_entry 里首先调用 rt_pin_mode 设置管脚 GPIOE3 为上拉输入模式。这样当用户按下按键 KEY1 时,GPIOE3 读取到的电平是低电平;按键未被按下时,GPIOE3 读取到的电平是高电平。然后进入 while(1) 循环,调用 rt_pin_read 读取管脚 GPIOE3 电平,如果读取到低电平则表示按键 KEY1 被按下,就在终端打印字符串 "key1 pressed!"。每隔 10ms 检测一次按键输入情况。 +下面是创建线程的代码: + +```c + rt_thread_t tid; + /* 创建 key 线程 */ + tid = rt_thread_create("key", + key_thread_entry, + RT_NULL, + 1024, + 2, + 10); + /* 创建成功则启动线程 */ + if (tid != RT_NULL) + rt_thread_startup(tid); +``` + +编译、下载程序,我们按下开发板上的用户按键,终端将打印提示字符。 + +## GPIO 中断配置 + +示例 3:配置 GPIO 为外部中断模式、下降沿触发,检测按键信号。根据原理图,GPIOE4 连接到按键 KEY0,按键被按下时 MCU 应探测到电平下降沿。 + +```c +#define KEY0 3 //PE4--3,在 gpio.c 文件 pin_index pins[] 中查到 PE4 编号为 3 +void hdr_callback(void *args)// 回调函数 +{ + char *a = args;// 获取参数 + rt_kprintf("key0 down! %s\n",a); +} + +void irq_thread_entry(void* parameter) +{ + // 上拉输入 + rt_pin_mode(KEY0, PIN_MODE_INPUT_PULLUP); + // 绑定中断,下降沿模式,回调函数名为 hdr_callback + rt_pin_attach_irq(KEY0, PIN_IRQ_MODE_FALLING, hdr_callback, (void*)"callback +args"); + // 使能中断 + rt_pin_irq_enable(KEY0, PIN_IRQ_ENABLE); + +} +``` + +在线程入口函数 irq_thread_entry 里首先调用 rt_pin_attach_irq 设置管脚 GPIOE4 为下降沿中断模式,并绑定了中断回调函数,还传入了字符串 "callback args"。然后调用 rt_pin_irq_enable 使能中断,这样按键 KEY0 被按下时 MCU 会检测到电平下降沿,触发外部中断,在中断服务程序中会调用回调函数 hdr_callback,在回调函数中打印传入的参数和提示信息。 +下面是创建线程的代码: + +```c + rt_thread_t tid;// 线程句柄 + /* 创建 irq 线程 */ + tid = rt_thread_create("exirq", + irq_thread_entry, + RT_NULL, + 1024, + 4, + 10); + /* 创建成功则启动线程 */ + if (tid != RT_NULL) + rt_thread_startup(tid); +``` + +编译、下载程序,我们按下按键 KEY0,终端将打印提示字符。 + +## 参考资料 + +* 《PIN设备》 + + diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_2.png b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_2.png new file mode 100644 index 0000000000000000000000000000000000000000..8341cac5a0a414e8bbf635b32e22b28f30b9065d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_3.png b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_3.png new file mode 100644 index 0000000000000000000000000000000000000000..aa67376d164811c3f9f3f7c63a2b0261281620ed Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_4.png b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_4.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6c9ab5bad96348c5e11b77e5265f6205197b6f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_4.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_5.png b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_5.png new file mode 100644 index 0000000000000000000000000000000000000000..9776247c524688a7906573465ce22f3428f07faa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_5.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_6.png b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_6.png new file mode 100644 index 0000000000000000000000000000000000000000..2c4c9be400eeb73271421e6ab4b70098743fb871 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_6.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_7.png b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_7.png new file mode 100644 index 0000000000000000000000000000000000000000..71d624f409123eaec67a03335de9a87a906d82e9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_7.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_9.png b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_9.png new file mode 100644 index 0000000000000000000000000000000000000000..44aa67d5a59d5c59cb076fc98ab73299cbea8b9f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/figures/an0002_9.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/gpio/main.rar b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/main.rar new file mode 100644 index 0000000000000000000000000000000000000000..a5ed59540bfedf3e812f7ec53c6d894c96c08a99 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/gpio/main.rar differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/an0003-rtthread-driver-i2c.md b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/an0003-rtthread-driver-i2c.md new file mode 100644 index 0000000000000000000000000000000000000000..85a3fac86f3e2a1eefdb533b12794cc417258421 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/an0003-rtthread-driver-i2c.md @@ -0,0 +1,306 @@ +# I2C 设备应用笔记 # + +本文以驱动 I2C 接口的 6 轴传感器 MPU6050 为例,说明了如何使用 I2C 设备驱动接口开发应用程序,并详细讲解了 RT-Thread I2C 设备驱动框架及相关函数。 + +## 本文的目的和结构 ## + +### 本文的目的和背景 ### + +I2C(或写作 i2c、IIC、iic)总线是由 Philips 公司开发的一种简单、双向二线制(时钟 SCL、数据 SDA)同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息,是半导体芯片使用最为广泛的通信接口之一。RT-Thread 中引入了 I2C 设备驱动框架,I2C 设备驱动框架提供了基于 GPIO 模拟和硬件控制器的 2 种底层硬件接口。 + +### 本文的结构 ### + +本文描述了使用 I2C 设备驱动接口编写 MPU6050 的驱动程序,并给出了在正点原子 STM32F4 探索者开发板上验证的代码示例。 + +## 运行 I2C 设备驱动示例代码 ## + +### 示例代码软硬件平台 ### + +1. [正点原子 STM32F4 探索者开发板](http://www.openedv.com/thread-13912-1-1.html) +2. GY-521 MPU-6050 模块 +3. MDK5 +4. [RT-Thread 源码](https://github.com/RT-Thread/rt-thread) +5. [示例代码](i2c-mpu6050.rar) +6. + +正点原子探索者 STM32F4 开发板的 MCU 是 STM32F407ZGT6,本示例使用 USB 串口(USART1)发送数据及供电,使用 SEGGER JLINK 连接 JTAG 调试。 + +本次实验用的 GY521 模块是一款 6 轴惯性传感器模块,板载 MPU6050。我们使用开发板的 PD6(SCL)、PD7(SDA)作为模拟 I2C 管脚,用杜邦线将 GY521 模块的 SCL 硬件连接到 PD6、SDA 连接到 PD7、GND 连接到开发板的 GND、VCC 连接到 3.3V。 + +![正点原子开发板](figures/image2.png) + +![GY521 模快](figures/image3.png) + +本文基于正点原子 STM32F4 探索者开发板,给出了底层 I2C 驱动(GPIO 模拟方式)的添加方法和 I2C 设备的具体应用示例代码(以驱动 MPU6050 为例),包含寄存器读、写操作方法。由于 RT-Thread 上层应用 API 的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。 + +### 启用 I2C 设备驱动 ### + +1. 使用 [Env 工具](https://www.rt-thread.org/document/site/zh/5chapters/01-chapter_env_manual/) 命令行进入 `rt-thread\bsp\stm32f4xx-HAL` 目录,然后输入 `menuconfig` 命令进入配置界面。 +2. 配置 shell 使用串口 1:选中 Using UART1,进入 RT-Thread Kernel ---> Kernel Device Object 菜单,修改 the device name for console 为 uart1。 +3. 进入 RT-Thread Components ---> Device Drivers 菜单,选中 Using I2C device drivers,本示例使用 GPIO 模拟 I2C,因此还要开启 Use GPIO to simulate I2C。 + +![使用 menuconfig 开启 i2c](figures/image4.png) + +4. 退出 menuconfig 配置界面并保存配置,在 Env 命令行输入 `scons --target=mdk5 -s` 命令生成 mdk5 工程,新工程名为 project。使用 MDK5 打开工程,修改 MCU 型号为 STM32F407ZGTx,修改调试选项为 J-LINK。 + +![修改 MCU](figures/mcu.png) + +![修改调试选项](figures/jlink.png) + +5. 编译工程后下载程序至开发板运行。在终端 PuTTY(打开对应端口,波特率配置为 115200) 输入 `list_device` 命令可以看到名为 i2c2 的设备,设备类型是 I2C Bus,说明 I2C 设备驱动添加成功了。如图所示: + +![使用 list_device 命令查看 i2c 总线设备](figures/image5.png) + +### 运行示例代码 ### + +将 I2C 示例代码里的 `main.c` 拷贝到 `\rt-thread\bsp\stm32f4xx-HAL\applications` 目录,替换原有的 `main.c`。 +`drv_mpu6050.c、drv_mpu6050.h` 拷贝到 `\rt-thread\bsp\stm32f4xx-HAL\drivers` 目录,并将它们添加到工程中对应分组。如图所示: + +![添加驱动](figures/image6.png) + +本例使用 GPIO PD6 作为 SCL、GPIO PD7 作为 SDA,I2C 总线设置名字是 i2c2,读者可根据需要修改 `drv_i2c.c` 件中如下参数以适配自己的板卡,确保 `drv_mpu6050.c` 中定义的宏 MPU6050_I2C_BUS_NAME 与 `drv_i2c.c` 中的宏 I2C_BUS_NAME 相同。本示例需要将 `drv_i2c.c` 默认驱动端口 GPIOB 改为 GPIOD,如下图所示: + +![drv_i2c.c 中的 i2c 板级配置](figures/image7.png) + +连接好 MPU6050 模块和开发板,编译工程并下载程序至开发板,复位 MCU,终端 PuTTY 会打印出读取到的 MPU6050 传感器数据,依次是温度,三轴加速度,三轴角速度: + +![终端打印信息](figures/image9.png) + +## 示例代码详解 ## + +按照前文的步骤,相信读者能很快的将 RT-ThreadI2C 设备驱动运行起来,那么如何使用 I2C 设备驱动接口开发应用程序呢? + +RT-Thread I2C 设备驱动目前只支持主机模式,使用 RT-Thread I2C 设备驱动需要使用 menuconfig 工具开启宏 RT_USING_DEVICE 和 RT_USING_I2C,如果要使用 GPIO 模拟 I2C 还需开启宏 RT_USING_I2C_BITOPS。 + +使用 I2C 设备驱动的大致流程如下: + +1. 用户可以在 msh shell 输入 `list_device` 命令查看已有的 I2C 总线设备,确定 I2C 总线设备名称。 + +2. 查找设备使用 `rt_i2c_bus_device_find()` 或者 `rt_device_find()`,传入 I2C 设备名称获取 i2c 总线设备句柄。 + +3. 使用 `rt_i2c_transfer()` 即可以发送数据也可以接收数据。 + +接下来本章将详细讲解 I2C 设备驱动接口的使用。 + +### 查找设备 ### + +应用程序要使用已经由操作系统管理的 I2C 设备需要调用查找设备函数,找到 I2C 设备后才可以对该设备进行信息传送。可使用`rt_device_find()`查找 I2C 设备。 + +本文示例代码底层驱动 `drv_mpu6050.c` 中 `mpu6050_hw_init()` 查找设备源码如下: + +```c +#define MPU6050_I2CBUS_NAME "i2c2" /* I2C 设备名称, 必须和 drv_i2c.c 注册的 I2C 设备名称一致 */ +static struct rt_i2c_bus_device *mpu6050_i2c_bus; /* I2C 设备句柄 */ +... ... +... ... + +int mpu6050_hw_init(void) +{ + rt_uint8_t res; + + mpu6050_i2c_bus = rt_device_find(MPU6050_I2CBUS_NAME); /* 查找 I2C 设备 */ + + if (mpu6050_i2c_bus == RT_NULL) + { + MPUDEBUG("can't find mpu6050 %s device\r\n",MPU6050_I2CBUS_NAME); + return -RT_ERROR; + } + +... ... +... ... +} + +``` + +### 数据传输 ### + +RT-Thread I2C 设备驱动的核心 API 是 `rt_i2c_transfer()`,它传递的消息是链式结构的。可以通过消息链,实现调用一次完成多次数据的收发,此函数既可以用于发送数据,也可以用于接收数据。 + +#### 发送数据 #### + +`drv_mpu6050.c` 中的 `mpu6050_write_reg()` 函数是 MCU 向 mpu6050 寄存器写数据。此函数的实现共有 2 种,分别调用了 I2C 设备驱动接口 `rt_i2c_transfer()` 和 `rt_i2c_master_send()` 实现。 + +本文示例使用的 MPU6050 数据手册中提到 7 位从机地址是 110100X,X 由芯片的 AD0 管脚决定,GY521 模块的 AD0 连接到了 GND,因此 MPU6050 作为从机时地址是 1101000,16 进制形式是 0x68。写 MPU6050 某个寄存器,主机首先发送从机地址 MPU6050_ADDR、读写标志 R/W 为 RT_I2C_WR(0 为写,1 为读),然 后主机发送从机寄存器地址 reg 及数据 data。 + +1) 使用 rt_i2c_transfer() 发送数据 + +本文示例代码底层驱动 `drv_mpu6050.c` 发送数据源码如下: + +```c +#define MPU6050_ADDR 0X68 + +// 写 mpu6050 单个寄存器 +//reg: 寄存器地址 +//data: 数据 +// 返回值: 0, 正常 / -1, 错误代码 +rt_err_t mpu6050_write_reg(rt_uint8_t reg, rt_uint8_t data) +{ + struct rt_i2c_msg msgs; + rt_uint8_t buf[2] = {reg, data}; + + msgs.addr = MPU6050_ADDR; /* 从机地址 */ + msgs.flags = RT_I2C_WR; /* 写标志 */ + msgs.buf = buf; /* 发送数据指针 */ + msgs.len = 2; + + if (rt_i2c_transfer(mpu6050_i2c_bus, &msgs, 1) == 1) + { + return RT_EOK; + } + else + { + return -RT_ERROR; + } +} +``` + +以本文示例代码其中一次调用 `rt_i2c_transfer()` 发送数据为例,从机 MPU6050 地址 16 进制值为 0X68,寄存器地址 reg 16 进制值为 0X6B,发送的数据 data 16 进制值为 0X80。示例波形如下图所示,第一个发送的数据是 0XD0,第一个数据的高 7 位是从机地址,最低位是读写位为写(值为 0),所以第一个数据为:0X68 << 1|0 = 0XD0,然后依次发送寄存器地址 0X6B 和数据 0X80。 + +![I2C 发送数据波形示例](figures/image10.png) + +2) 使用 rt_i2c_master_send() 发送数据 + +本文示例代码底层驱动 `drv_mpu6050.c` 发送数据源码如下: + +```c +#define MPU6050_ADDR 0X68 + +// 写 mpu6050 单个寄存器 +//reg: 寄存器地址 +//data: 数据 +// 返回值: 0, 正常 / -1, 错误代码 +rt_err_t mpu6050_write_reg(rt_uint8_t reg, rt_uint8_t data) +{ + rt_uint8_t buf[2]; + + buf[0] = reg; + buf[1] = data; + + if (rt_i2c_master_send(mpu6050_i2c_bus, MPU6050_ADDR, 0, buf ,2) == 2) + { + return RT_EOK; + } + else + { + return -RT_ERROR; + } +} +``` + +#### 接收数据 #### + +用户可以调用 I2C 设备驱动接口 `rt_i2c_master_recv()` 或者 `rt_i2c_transfer()` 接受数据。 + +本文示例代码 `drv_mpu6050.c` 中的 `mpu6050_read_reg()` 函数是 MCU 从 MPU6050 寄存器读取数据,此函数的实现同样有 2 种方式,分别调用了 I2C 设备驱动接口 `rt_i2c_transfer()` 和 `rt_i2c_master_recv()` 实现。 + +读 MPU6050 某个寄存器,主机首先发送从机地址 MPU6050_ADDR、读写标志 R/W 为 RT_I2C_WR(0 为写,1 为读)、从机寄存器地址 reg 之后才能开始读设备。然后发送从机地址 MPU6050_ADDR、读写标志 R/W 为 RT_I2C_RD(0 为写,1 为读)、保存读取数据指针。 + +1) 使用 rt_i2c_transfer() 接收数据 + +本文示例代码底层驱动 `drv_mpu6050.c` 接收数据源码如下: + +```c +#define MPU6050_ADDR 0X68 + +// 读取寄存器数据 +//reg: 要读取的寄存器地址 +//len: 要读取的数据字节数 +//buf: 读取到的数据存储区 +// 返回值: 0, 正常 / -1, 错误代码 +rt_err_t mpu6050_read_reg(rt_uint8_t reg, rt_uint8_t len, rt_uint8_t *buf) +{ + struct rt_i2c_msg msgs[2]; + + msgs[0].addr = MPU6050_ADDR; /* 从机地址 */ + msgs[0].flags = RT_I2C_WR; /* 写标志 */ + msgs[0].buf = ® /* 从机寄存器地址 */ + msgs[0].len = 1; /* 发送数据字节数 */ + + msgs[1].addr = MPU6050_ADDR; /* 从机地址 */ + msgs[1].flags = RT_I2C_RD; /* 读标志 */ + msgs[1].buf = buf; /* 读取数据指针 */ + msgs[1].len = len; /* 读取数据字节数 */ + + if (rt_i2c_transfer(mpu6050_i2c_bus, msgs, 2) == 2) + { + return RT_EOK; + } + else + { + return -RT_ERROR; + } +} +``` + +以本文示例代码其中一次调用 rt_i2c_transfer() 接收数据为例,从机 MPU6050 地址 16 进制值为 0X68,寄存器地址 reg 16 进制值为 0X75。示例波形如下图所示,第一个发送的数据是 0XD0,第一个数据的高 7 位是从机地址,最低位是读写位是写(值为 0),所以第一个数据值为:0X68 << 1|0 = 0XD0,然后发送寄存器地址 0X75。第二次发送的第一个数据为 0XD1,读写位是读(值为 1),值为:0X68 << 1 | 1 = 0XD1,然后收到读取到的数据 0X68。 + +![I2C 发送数据波形示例](figures/image11.png) + +2) 使用 rt_i2c_master_recv() 接收数据 + +本文示例代码底层驱动 `drv_mpu6050.c` 接收数据源码如下: + +```c +#define MPU6050_ADDR 0X68 + +// 读取寄存器数据 +//reg: 要读取的寄存器地址 +//len: 要读取的数据字节数 +//buf: 读取到的数据存储区 +// 返回值: 0, 正常 / -1, 错误代码 +rt_err_t mpu6050_read_reg(rt_uint8_t reg, rt_uint8_t len, rt_uint8_t *buf) +{ + if (rt_i2c_master_send(mpu6050_i2c_bus, MPU6050_ADDR, 0, ®, 1) == 1) + { + if (rt_i2c_master_recv(mpu6050_i2c_bus, MPU6050_ADDR, 0, buf, len) == len) + { + return RT_EOK; + } + else + { + return -RT_ERROR; + } + } + else + { + return -RT_ERROR; + } + +} +``` + +### I2C 设备驱动应用 ### + +通常 I2C 接口芯片的只读寄存器分为 2 种情况,一种是单一功能寄存器,另一种是地址连续,功能相近的寄存器。例如 MPU6050 的寄存器 0X3B、0X3C、0X3D、0X3E、0X3F、0X40 依次存放的是三轴加速度 X、Y、Z 轴的高 8 位、低 8 位数据。 + +本文示例代码底层驱动 `drv_mpu6050.c` 使用 `mpu6050_read_reg()` 函数读取 MPU6050 的 3 轴加速度数据: + +```c +#define MPU_ACCEL_XOUTH_REG 0X3B // 加速度值, X 轴高 8 位寄存器 + +// 得到加速度值 (原始值) +//gx,gy,gz: 陀螺仪 x,y,z 轴的原始读数 (带符号) +// 返回值: 0, 成功 / -1, 错误代码 +rt_err_t mpu6050_accelerometer_get(rt_int16_t *ax, rt_int16_t *ay, rt_int16_t *az) +{ + rt_uint8_t buf[6], ret; + + ret = mpu6050_read_reg(MPU_ACCEL_XOUTH_REG, 6, buf); + if (ret == 0) + { + *ax = ((rt_uint16_t)buf[0] << 8) | buf[1]; + *ay = ((rt_uint16_t)buf[2] << 8) | buf[3]; + *az = ((rt_uint16_t)buf[4] << 8) | buf[5]; + + return RT_EOK; + } + else + { + return -RT_ERROR; + } +} +``` + +## 参考资料 + +* 《I2C 设备》 diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/flow.docx b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/flow.docx new file mode 100644 index 0000000000000000000000000000000000000000..3c4f56156a7dd2aec48590d3f26415b2ee4b63db Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/flow.docx differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image10.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image10.png new file mode 100644 index 0000000000000000000000000000000000000000..8e4d402e2819ef1f7c25703c8b3cfb7d25f406b8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image10.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image11.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image11.png new file mode 100644 index 0000000000000000000000000000000000000000..1bf473a5a160ac3dfda41934d951a87fbdd0128f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image11.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image2.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image2.png new file mode 100644 index 0000000000000000000000000000000000000000..3c69a680a578c8a4f0244ea7fb33b703e854066a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image3.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image3.png new file mode 100644 index 0000000000000000000000000000000000000000..705b44ca92ebbeb86da30c93ef5e4b4111ecfe5c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image4.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image4.png new file mode 100644 index 0000000000000000000000000000000000000000..7a89a79108a81f0292553e5ce6edd0927325983f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image4.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image5.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image5.png new file mode 100644 index 0000000000000000000000000000000000000000..1d2a32a91f0942b6f43c01ad8a39fc192fa09e97 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image5.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image6.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image6.png new file mode 100644 index 0000000000000000000000000000000000000000..63c7b1097820164d4e6a5cf2d51b53fea78200ac Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image6.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image7.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image7.png new file mode 100644 index 0000000000000000000000000000000000000000..e7f444ff7369c7ef0d19a162b3cc7a49511e712e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image7.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image9.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image9.png new file mode 100644 index 0000000000000000000000000000000000000000..9a0f0156f3c16ff137dc133355d89c5f99d1b851 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/image9.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/jlink.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/jlink.png new file mode 100644 index 0000000000000000000000000000000000000000..a54c6a7d79881bc2d2c766d9daaaf14bc1ddd762 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/jlink.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/mcu.png b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/mcu.png new file mode 100644 index 0000000000000000000000000000000000000000..c1acf1e57631bc58e4d0ed89b6d2e59f8223d71e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/figures/mcu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/i2c/i2c-mpu6050.rar b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/i2c-mpu6050.rar new file mode 100644 index 0000000000000000000000000000000000000000..a49789d9903c830c7e7122cded4af8b2a7ac9b0c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/i2c/i2c-mpu6050.rar differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/an0037-rtthread-driver-pwm.md b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/an0037-rtthread-driver-pwm.md new file mode 100644 index 0000000000000000000000000000000000000000..1f4b3502f7fe4f3e252e10b48a1bb82546c0b7dc --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/an0037-rtthread-driver-pwm.md @@ -0,0 +1,195 @@ +# STM32 上使用 PWM + +本文描述了如何在搭载了 RT-Thread 操作系统的平台上使用 PWM 输出波形,包括 PWM 的应用、配置和驱动的添加等。并给出了在正点原子 `STM32L475 pandora` 开发板上验证的代码示例。 + +## 硬件平台简介 + +本文基于正点原子 `STM32L475 pandora` 开发板,给出了 PWM 的具体应用示例代码,由于 RT-Thread 上层应用 API 的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。 + +`STM32L475 pandora` 是正点原子推出的一款基于 ARM Cortex-M4 内核的开发板,最高主频为 80Mhz,该开发板具有丰富的板载资源,可以充分发挥 STM32L475 的芯片性能。 + +![正点原子开发板](figures/board.png) + +## 使用 PWM + +### 在 menuconfig 中打开 PWM 通道 + +打开 Env 工具,使用 menuconfig 工具配置工程,在 Env 命令行中输入 menuconfig 进入配置界面。在 menuconfig 配置界面依次选择 `Hardware Driver Config ---> On-chip Peripheral Drivers ---> Enable pwm ---> Enable timer2 output pwm` 如下图所示: + +![menuconfig 中开启 PWM 支持](figures/open_channel.png) + + 选中需要使用的 PWM 通道后保存退出,使用 `scons --target=mdk5` 生成 mdk5 工程,打开工程进行编译并下载程序,在终端输入 list_device 命令可以看到 PWM2 设备已经成功添加了,如下图所示: + + ![list_device](figures/list_device.png) + +### 使用 PWM 输出波形 +应用程序可以通过 RT-Thread 提供的设备管理接口来访问 PWM 设备硬件,相关接口如下所示: + +| **函数** | **描述** | +| ----------------- | ---------------------------------- | +| rt_device_find | 根据 PWM 设备名称查找设备获取设备句柄 | +| rt_pwm_set | 设置 PWM 周期和脉冲宽度 | +| rt_pwm_enable | 使能 PWM 设备 | +| rt_pwm_disable | 关闭 PWM 设备 | + +接口参数的具体描述请参考官网 [PWM 设备](https://www.rt-thread.org/document/site/programming-manual/device/pwm/pwm/) + +#### PWM 设备使用步骤 + +PWM 设备的具体使用方式可以参考如下步骤: + +1. 初始化 PWM 设备。 + + * 使用 `rt_device_find` 查找指定的 PWM 设备。 + * 使用 `rt_pwm_set` 设置通道的默认 PWM 周期和脉冲宽度。 + * 使用 `rt_pwm_enable` 使能需要输出波形的 PWM 通道。 + +2. 使用 PWM 设备输出波形。 + + * 使用 `rt_pwm_set` 输出特定的波形。 + +3. 关闭 PWM 输出通道。 + + * 当不再需要使用 PWM 通道输出波形时,可以调用 `rt_pwm_disable` 关闭对应的输出通道。 + +代码如下所示: + +```c +#define PWM_DEV_NAME "pwm2" /* PWM设备名称 */ +#define PWM_DEV_CHANNEL 3 /* PWM通道 */ +#define THREAD_PRIORITY 25 /* 线程优先级 */ +#define THREAD_STACK_SIZE 512 /* 线程栈大小 */ +#define THREAD_TIMESLICE 5 /* 线程时间片大小 */ + +static rt_thread_t tid1 = RT_NULL; /* 线程句柄 */ +struct rt_device_pwm *pwm_dev; /* PWM设备句柄 */ +static rt_uint32_t period = 500000; /* 周期为0.5ms,单位为纳秒ns */ +static rt_uint32_t pulse = 0; /* PWM脉冲宽度值的增减方向 */ + +/* 线程 pwm_entry 的入口函数 */ +static void pwm_entry(void *parameter) +{ + rt_uint32_t count = 0; + + while (count++ < 1000) + { + rt_thread_mdelay(50); + /* step 2、设置 PWM 周期和脉冲宽度,输出特定的波形 */ + rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse++); + } + /* step 3、如果不再使用该通道,可以关闭 PWM 通道的输出 */ + rt_pwm_disable(pwm_dev, PWM_DEV_CHANNEL); +} + +static int pwm_test(int argc, char *argv[]) +{ + /* step 1.1、查找 PWM 设备 */ + pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); + if (pwm_dev == RT_NULL) + { + rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM_DEV_NAME); + return RT_ERROR; + } + + /* step 1.2、设置 PWM 周期和脉冲宽度默认值 */ + rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); + /* step 1.3、使能 PWM 设备的输出通道 */ + rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL); + + /* 创建线程,名称是 pwm_thread ,入口是 pwm_entry*/ + tid1 = rt_thread_create("pwm_thread", + pwm_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, + THREAD_TIMESLICE); + + /* 如果获得线程控制块,启动这个线程 */ + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + return RT_EOK; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(pwm_test, pwm sample); +``` + +编译、下载程序,在终端输入 help 命令可以看到 pwm_test 命令已经成功导出,如下图所示: + +![help](figures/help.png) + +#### 运行 PWM 测试程序 + +要运行 PWM 测试程序,需要在终端输入 pwm_test 由于此 BSP 的 PWM2 通道 3 的输出并没有连接到外设上,无法直观的看到现象,所以这里使用逻辑分析仪来抓取 PWM 输出的波形,波形输出如下图所示: + +![运行PWM程序](figures/pwm_test.png) + +从逻辑分析仪抓取的波形可以看到,PWM 波形已经成功输出。 +## 添加 PWM 驱动 + +如果使用的 BSP 在 menuconfig 中没有给出 PWM 通道的配置项,那么就需要自己添加 PWM 的驱动,下面就如何自己添加 PWM 驱动展开讲解。 + +### 检查驱动文件是否支持 PWM + +进入 `rt-thread\bsp\stm32\libraries\HAL_Drivers` 目录检查 drv_pwm.c 文件是否支持相应的 PWM 外设输出。 + +检查驱动文件是否支持相应的 PWM 外设(PWM1、2、n) + +![pwm_num](figures/pwm_num.png) + +检查驱动文件是否支持相应的 PWM 输出通道(1、2、3、4) + +![pwm_channel](figures/pwm_channel.png) + +### 初始化 PWM 通道引脚 + +进入 `rt-thread\bsp\stm32l475-atk-pandora\board\CubeMX_Config` 目录,双击打开 `STM32L475VE.ioc` 文件初始化 PWM 通道对应的引脚,这里以 PWM2 通道 3 为例,如下图所示: + +![init_pin](figures/init_pin.png) + +点击 `GENERATE CODE` 按钮生成代码,虽然 `STM32CubeMX` 生成了多个文件用来初始化外设,但 RT-Thread 只使用了 `STM32CubeMX` 生成的 `stm32fxx_hal_msp.c` 文件和 `stm32fxx_hal_conf.h` 文件,生成的 PWM 代码如下所示: + +![hal_code](figures/hal_code.png) + +### 配置 Kconfig 文件 + +进入 `rt-thread\bsp\stm32l475-atk-pandora\board` 目录,添加 Kconfig 选项,如下图所示: + +![pwm_kconfig](figures/pwm_kconfig.png) + +使用 `scons --target=mdk5` 命令生成 mdk5 工程,打开工程并编译,如果工程提示 PWMn_CONFIG 未定义。 可以在 `stm32/libraries/HAL_Drivers/config/f4/pwm_config.h` 中进行定义,如下图所示: + + ![配置参数](figures/config_para.png) + +完成以上步骤就可以在 menuconfig 菜单中添加支持的 PWM 输出通道,至于如何使用 PWM 通道输出波形请参考上一章节。 + +到这一步为止,如何在搭载了 RT-Thread 操作系统的平台上如何使用 PWM 的介绍就结束了。 + + +## 参考资料 +* [ENV 用户手册](https://www.rt-thread.org/document/site/programming-manual/env/env/) +* [PWM 设备](https://www.rt-thread.org/document/site/programming-manual/device/pwm/pwm/) +* [STM32L475-atk-pandora BSP 源码](https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32l475-atk-pandora) +* [STM32 系列驱动添加指南](https://github.com/RT-Thread/rt-thread/blob/master/bsp/stm32/docs/STM32%E7%B3%BB%E5%88%97%E5%A4%96%E8%AE%BE%E9%A9%B1%E5%8A%A8%E6%B7%BB%E5%8A%A0%E6%8C%87%E5%8D%97.md) + +## 常见问题 + +### Q: 按照上述使用 PWM 章节进行操作,无法输出 PWM 波形怎么办? + +**A:** 该问题一般是 menuconfig 菜单中给出了 PWM 的配置选项,但是相应的 PWM 输出通道和时钟没有初始化引起的。使用 `STM32CubeMX` 工具使能相应的输出通道和时钟即可。 + +### Q: 编译提示 PWMn_CONFIG 未定义怎么办? + +**A:** 在 `stm32/libraries/HAL_Drivers/config/f1/pwm_config.h` 文件中参考已定义的配置文件来定义自己需要的 PWM 输出通道。 + +### Q: 当使用 I/O 作为 PWM 输出通道时是否还需要使用 `rt_pin_mode` 设置 I/O 引脚的工作模式? + +**A:** 不需要。在使用 `STM32CubeMX` 配置 PWM 输出通道时已经生成了对应通道引脚的初始化文件,所以不再需要对引脚做初始化。 + +### Q: 当前 PWM 驱动是否支持同一通道的反向输出? + +**A:** PWM 驱动暂未支持同一通道的反向输出。 + +### Q:使用 `STM32CubeMX` 工具将 PWM 通道重映射后,需要修改驱动文件吗? + +**A:** 不需要。驱动文件初始化的是 PWM 外设和引脚无关。不管通道引脚如何映射,对应的 PWM 通道是不会改变的。 diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/board.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/board.png new file mode 100644 index 0000000000000000000000000000000000000000..3d8f0a6e269c6a3c4ff9dfb7f5bf2cbaa1e416ba Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/board.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/config_para.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/config_para.png new file mode 100644 index 0000000000000000000000000000000000000000..32f89db50225b5b247da5b89e824cd40c48df2e4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/config_para.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/hal_code.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/hal_code.png new file mode 100644 index 0000000000000000000000000000000000000000..f0cd8f5c98105d3ed5d2a0dc63bdce3914c8ac34 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/hal_code.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/help.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/help.png new file mode 100644 index 0000000000000000000000000000000000000000..70dec3b24b8c66b4c298ebd540cbddc1d65d43f0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/help.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/init_pin.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/init_pin.png new file mode 100644 index 0000000000000000000000000000000000000000..108b7409fe75f84623648f073f4cfb1adb66e417 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/init_pin.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/list_device.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/list_device.png new file mode 100644 index 0000000000000000000000000000000000000000..8e8230791be5602ae0d521fa32e6003679896049 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/list_device.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/open_channel.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/open_channel.png new file mode 100644 index 0000000000000000000000000000000000000000..c6694dd7740b830ced457b7843425f6c68eecda8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/open_channel.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_channel.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_channel.png new file mode 100644 index 0000000000000000000000000000000000000000..e073a4adf80fd47fbf303c74208f45978adb463e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_channel.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_kconfig.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_kconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..7ce62238dade3de40642799070f69759f12b2828 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_kconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_num.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_num.png new file mode 100644 index 0000000000000000000000000000000000000000..dc93adee5705f8001177bcbcf899edaff1f98be0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_num.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_test.png b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_test.png new file mode 100644 index 0000000000000000000000000000000000000000..b1006c2697d0b81a283654da50ec28d67918f412 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/pwm/figures/pwm_test.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/an0004-rtthread-driver-spi.md b/rt-thread-version/rt-thread-standard/application-note/driver/spi/an0004-rtthread-driver-spi.md new file mode 100644 index 0000000000000000000000000000000000000000..c8bfcb6d2c5ca19552385ef6395bb6636271fec2 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/driver/spi/an0004-rtthread-driver-spi.md @@ -0,0 +1,239 @@ +# SPI设备应用笔记 # + +本文以驱动SPI接口的OLED显示屏为例,说明了如何添加SPI设备驱动框架及底层硬件驱动,使用SPI设备驱动接口开发应用程序。并给出了在正点原子STM32F4探索者开发板上验证的代码示例。 + +## 本文的目的和结构 ## + +### 本文的目的和背景 ### + +为了方便应用层程序开发,RT-Thread中引入了SPI设备驱动框架。本文说明了如何使用RT-Thread SPI设备驱动。 + +### 本文的结构 ### + +本文首先介绍了在正点原子STM32F4探索者开发板上运行了SPI设备驱动示例代码。 + +## 运行示例代码 ## + +本章节基于正点原子探索者STM32F4 开发板及SPI示例代码,给出了RT-Thread SPI设备驱动框架的使用方法。 + +### 示例代码软硬件资源 ### + +1. [RT-Thread 源码](https://github.com/RT-Thread/rt-thread) +2. [ENV工具](https://www.rt-thread.org/document/site/docs/tools/env/env-user-manual/) +3. [SPI设备驱动示例代码](spi-oled.rar) +4. +5. [正点原子STM32F4探索者开发板](http://www.openedv.com/thread-13912-1-1.html) +6. 1.5寸彩色OLED显示屏(SSD1351控制器) +7. MDK5 + +正点原子探索者STM32F4 开发板的MCU是STM32F407ZGT6,本示例使用USB转串口(USART1)发送数据及供电,使用SEGGER J-LINK连接JTAG调试,STM32F4 有多个硬件SPI控制器,本例使用 SPI1。彩色OLED显示屏板载SSD1351控制器,分辨率128*128。 + +STM32F4 与 OLED 显示屏管脚连接如下表所示: + +STM32管脚 | OLED显示屏管脚 | 说明 +- | - | - +PA5 | D0 | SPI1 SCK,时钟 +PA6 | | SPI1 MISO,未使用 +PA7 | D1 | SPI1 MOSI,主机输出,从机输入 +PC6 | D/C | GPIO,输出,命令0/数据1选择 +PC7 | RES | GPIO,输出,复位,低电平有效 +PC8 | CS | GPIO,输出,片选,低电平有效 +3.3V | VCC | 供电 +GND | GND | 接地 + +![正点原子开发板](figures/board.png) + +![彩色OLED显示屏](figures/ssd1351.png) + +SPI设备驱动示例代码包括`app.c、drv_ssd1351.c、drv_ssd1351.h` 3个文件,`drv_ssd1351.c`是OLED显示屏驱动文件,此驱动文件包含了SPI设备ssd1351的初始化、挂载到系统及通过命令控制OLED显示的操作方法。由于RT-Thread上层应用API的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。 + +### 配置工程 ### + +使用menuconfig配置工程:在env工具命令行使用cd 命令进入 rt-thread/bsp/stm32f4xx-HAL 目录,然后输入`menuconfig` 命令进入配置界面。 + +![使用menuconfig开启SPI](figures/enable-spi.png) + +* 修改工程芯片型号:修改 Device type为STM32F407ZG。 +* 配置shell使用串口1:选中Using UART1,进入RT-Thread Kernel ---> Kernel Device Object菜单,修改the device name for console为uart1。 +* 开启SPI总线及设备驱动并注册SPI总线到系统:进入RT-Thread Components ---> Device Drivers菜单,选中Using SPI Bus/Device device drivers,RT-Thread Configuration界面会默认选中Using SPI1,spi1总线设备会注册到操作系统。 +* 开启GPIO驱动:进入RT-Thread Components ---> Device Drivers菜单,选中Using generic GPIO device drivers。OLED屏需要2个额外的GPIO用于DC、RES信号,SPI总线驱动也需要对片选管脚进行操作,都需要调用系统的GPIO驱动接口。 + +生成新工程及修改调试选项:退出menuconfig配置界面并保存配置,在ENV命令行输入`scons --target=mdk5 -s` 命令生成mdk5工程,新工程名为project。使用MDK5打开工程,修改调试选项为J-LINK。 + +![修改调试选项](figures/jlink.png) + +使用list_device命令查看SPI总线:添加SPI底层硬件驱动无误后,在终端PuTTY(打开对应端口,波特率配置为115200)使用`list_device`命令就能看到SPI总线。同样可以看到我们使用的UART设备和PIN设备。 + +![使用list_device命令查看系统设备](figures/spi-bus.png) + +### 添加示例代码 ### + +将SPI设备驱动示例代码里的`app.c`拷贝到`/rt-thread/bsp/stm32f4xx-HAL/applications`目录。`drv_ssd1351.c、drv_ssd1351.h`拷贝到`/rt-thread/bsp/stm32f4xx-HAL/drivers`目录,并将它们添加到工程中对应分组。如图所示: + +![添加示例代码到工程](figures/add-sample.png) + +在`main.c`中调用`app_init()`,`app_init()`会创建一个oled线程,线程会循环展示彩虹颜色图案和正方形颜图案。 + +![实验现象](figures/pheno.png) + +`main.c`调用测试代码源码如下: + +```c +#include +#include + +extern int app_init(void); + +int main(void) +{ + /* user app entry */ + + app_init(); + + return 0; +} +``` + +![使用list_device命令查看SPI设备驱动](figures/spi-drv.png) + +## SPI设备驱动接口使用详解 ## + +按照前文的步骤,相信读者能很快的将RT-Thread SPI设备驱动运行起来,那么如何使用SPI设备驱动接口开发应用程序呢? + +RT-Thread SPI设备驱动使用流程大致如下: + +1. 定义SPI设备对象,调用`rt_spi_bus_attach_device()`挂载SPI设备到SPI总线。 +1. 调用`rt_spi_configure()`配置SPI总线模式。 +1. 使用`rt_spi_send()`等相关数据传输接口传输数据。 + +接下来本章节将详细讲解示例代码使用到的主要的SPI设备驱动接口。 + +### 挂载SPI设备到总线 ### + +用户定义了SPI设备对象后就可以调用此函数挂载SPI设备到SPI总线。可使用函数`rt_spi_bus_attach_device()`。 + +本文示例代码底层驱动`drv_ssd1351.c` 中 `rt_hw_ssd1351_config()`挂载ssd1351设备到SPI总线源码如下: + +```c +#define SPI_BUS_NAME "spi1" /* SPI总线名称 */ +#define SPI_SSD1351_DEVICE_NAME "spi10" /* SPI设备名称 */ + +... ... + +static struct rt_spi_device spi_dev_ssd1351; /* SPI设备ssd1351对象 */ +static struct stm32_hw_spi_cs spi_cs; /* SPI设备CS片选引脚 */ + +... ... + +static int rt_hw_ssd1351_config(void) +{ + rt_err_t res; + + /* oled use PC8 as CS */ + spi_cs.pin = CS_PIN; + rt_pin_mode(spi_cs.pin, PIN_MODE_OUTPUT); /* 设置片选管脚模式为输出 */ + + res = rt_spi_bus_attach_device(&spi_dev_ssd1351, SPI_SSD1351_DEVICE_NAME, SPI_BUS_NAME, (void*)&spi_cs); + if (res != RT_EOK) + { + OLED_TRACE("rt_spi_bus_attach_device!\r\n"); + return res; + } + + ... ... +} +``` + +### 配置SPI模式 ### + +挂载SPI设备到SPI总线后,为满足不同设备的时钟、数据宽度等要求,通常需要配置SPI模式、频率参数。SPI从设备的模式决定主设备的模式,所以SPI主设备的模式必须和从设备一样两者才能正常通讯。可使用函数`rt_spi_configure()`配置模式。 + +本文示例代码底层驱动`drv_ssd1351.c` 中`rt_hw_ssd1351_config()`配置SPI传输参数源码如下: + +```c +static int rt_hw_ssd1351_config(void) +{ + ... ... + + /* config spi */ + { + struct rt_spi_configuration cfg; + cfg.data_width = 8; + cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; + cfg.max_hz = 20 * 1000 *1000; /* 20M,SPI max 42MHz,ssd1351 4-wire spi */ + + rt_spi_configure(&spi_dev_ssd1351, &cfg); + } + + ... ... + +``` + +### 数据传输 ### + +本文示例代码底层驱动`drv_ssd1351.c`调用`rt_spi_send()`向SSD1351发送指令和数据的函数源码如下: +``` +rt_err_t ssd1351_write_cmd(const rt_uint8_t cmd) +{ + rt_size_t len; + + rt_pin_write(DC_PIN, PIN_LOW); /* 命令低电平 */ + + len = rt_spi_send(&spi_dev_ssd1351, &cmd, 1); + + if (len != 1) + { + OLED_TRACE("ssd1351_write_cmd error. %d\r\n",len); + return -RT_ERROR; + } + else + { + return RT_EOK; + } + +} + +rt_err_t ssd1351_write_data(const rt_uint8_t data) +{ + rt_size_t len; + + rt_pin_write(DC_PIN, PIN_HIGH); /* 数据高电平 */ + + len = rt_spi_send(&spi_dev_ssd1351, &data, 1); + + if (len != 1) + { + OLED_TRACE("ssd1351_write_data error. %d\r\n",len); + return -RT_ERROR; + } + else + { + return RT_EOK; + } +} + +``` + +### SPI设备驱动应用 ### + +本文示例使用SSD1351显示图像信息,首先需要确定信息在显示器上的行列起始地址,调用`ssd1351_write_cmd()`向SSD1351发送指令,调用`ssd1351_write_data()`向SSD1351发送数据,源代码如下: + +```c +void set_column_address(rt_uint8_t start_address, rt_uint8_t end_address) +{ + ssd1351_write_cmd(0x15); // Set Column Address + ssd1351_write_data(start_address); // Default => 0x00 (Start Address) + ssd1351_write_data(end_address); // Default => 0x7F (End Address) +} +void set_row_address(rt_uint8_t start_address, rt_uint8_t end_address) +{ + ssd1351_write_cmd(0x75); // Set Row Address + ssd1351_write_data(start_address); // Default => 0x00 (Start Address) + ssd1351_write_data(end_address); // Default => 0x7F (End Address) +} +``` + +## 参考资料 + +* 《SPI 设备》 + diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/add-sample.png b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/add-sample.png new file mode 100644 index 0000000000000000000000000000000000000000..5e438f0a18125879d17a3f8e6bd9c5cbcb96ecf3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/add-sample.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/board.png b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/board.png new file mode 100644 index 0000000000000000000000000000000000000000..57099a65f8d0b25b924eecfcb1be1bf7473b8881 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/board.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/enable-spi.png b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/enable-spi.png new file mode 100644 index 0000000000000000000000000000000000000000..17a3def43c4a92d11c5e4ed306f08b2235fc73ca Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/enable-spi.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/flow.docx b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/flow.docx new file mode 100644 index 0000000000000000000000000000000000000000..d32a242e306888fdd5a6a7b9d257f24c83147818 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/flow.docx differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/jlink.png b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/jlink.png new file mode 100644 index 0000000000000000000000000000000000000000..a54c6a7d79881bc2d2c766d9daaaf14bc1ddd762 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/jlink.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/pheno.png b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/pheno.png new file mode 100644 index 0000000000000000000000000000000000000000..20ad08bc3c41669f320d7ca16b3f704cf2a65c68 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/pheno.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/spi-bus.png b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/spi-bus.png new file mode 100644 index 0000000000000000000000000000000000000000..7b9764cd1b7913abd674edc2e702316a96b05cfd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/spi-bus.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/spi-drv.png b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/spi-drv.png new file mode 100644 index 0000000000000000000000000000000000000000..96a5379d6f20205fb235872106e5a1b59293ff06 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/spi-drv.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/ssd1351.png b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/ssd1351.png new file mode 100644 index 0000000000000000000000000000000000000000..7adc5f45e6e680de4b96e3618e8e064b7b7934f5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/figures/ssd1351.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/spi/spi-oled.rar b/rt-thread-version/rt-thread-standard/application-note/driver/spi/spi-oled.rar new file mode 100644 index 0000000000000000000000000000000000000000..e79a400d4078a090faa4be4d26e91365b1a71ea9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/spi/spi-oled.rar differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/an0001-rtthread-driver-uart.md b/rt-thread-version/rt-thread-standard/application-note/driver/uart/an0001-rtthread-driver-uart.md new file mode 100644 index 0000000000000000000000000000000000000000..7829e3df4cce80d8d829a73213dfb7d140d5f084 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/driver/uart/an0001-rtthread-driver-uart.md @@ -0,0 +1,255 @@ +# 串口设备应用笔记 # + +本文描述了如何使用 RT-Thread 的串口设备,包括串口配置、设备操作接口的应用。并给出了在正点原子 STM32F4 探索者开发板上验证的代码示例。 + +## 本文的目的和结构 ## + +### 本文的目的和背景 ### + +串口(通用异步收发器,常写作 UART、uart)是最为广泛使用的通信接口之一。在裸机平台或者是没有设备管理框架的 RTOS 平台上,我们通常只需要根据官方手册编写串口硬件初始化代码即可。引入了带设备管理框架的实时操作系统 RT-Thread 后,串口的使用则与裸机或者其它 RTOS 有很大的不同之处。RT-Thread 中自带 I/O 设备管理层,将各种各样的硬件设备封装成具有统一接口的逻辑设备,方便管理及使用。本文说明了如何在 RT-Thread 中使用串口。 + +### 本文的结构 ### + +本文首先给出使用 RT-Thread 的设备操作接口开发串口收、发数据程序的示例代码,并在正点原子 STM32F4 探索者开发板上验证。接着分析了示例代码的实现,最后深入地描述了 RT-Thread 设备管理框架与串口的联系。 + +## 运行示例代码 ## + +本文基于正点原子 STM32F4 探索者开发板,给出了串口的配置流程和应用代码示例。由于 RT-Thread 设备操作接口的通用性,因此这些代码与硬件平台无关,读者可以直接将它用在自己使用的硬件平台上。 +正点原子 STM32F4 探索者开发板使用的是 STM32F407ZGT6,具有多路串口。我们使用串口 1 作为 shell 终端,串口 2 作为实验用串口,测试数据收发。终端软件使用 putty。板载串口 1 带有 USB 转串口芯片,因此使用 USB 线连接串口 1 和 PC 即可;串口 2 则需要使用 USB 转串口模块连接到 PC。 + +![正点原子 STM32F4 探索者](figures/an0001_2.png) + +### 准备和配置工程 ### + +1. 下载 [RT-Thread 源码](https://github.com/RT-Thread/rt-thread) + +2. 进入 `rt-thread\bsp\stm32f4xx-HAL` 目录,在 Env 命令行中输入 menuconfig,进入配置界面,使用 menuconfig 工具(学习如何使用)配置工程。 + +(1) 配置 shell 使用串口 1:RT-Thread Kernel ---> Kernel Device Object ---> 修改 the device name for console 为 uart1。 + +(2) 勾选 Using UART1、Using UART2,选择芯片型号为 STM32F407ZG,时钟源为外部 8MHz,如图所示: + +![使用 menuconfig 配置串口](figures/menuconfig.png) + +3. 输入命令 scons --target=mdk5 -s 生成 keil 工程,打开工程后先修改 MCU 型号为 STM32F407ZETx,如图所示: + +![检查芯片型号](figures/mcu.png) + +4. 打开 putty,选择正确的串口,软件参数配置为 115200-8-1-N、无流控。如图所示: + +![putty 配置](figures/an0001_5.png) + +5. 编译、下载程序,按下复位后就可以在串口 1 连接的终端上看到 RT-Thread 标志 log 了,输入 list_device 命令能查看到 uart1、uart2 Character Device 就表示串口配置好了。 + +![使用 list_device 命令查看 uart 设备](figures/an0001_6.png) + +### 加入串口相关代码 ### + +[下载串口示例代码](uart.rar) + +![添加示例代码到工程](figures/an0001_7.png) + +本文示例代码 app_uart.c、app_uart.h,app_uart.c 中是串口相关操作的代码,方便阅读。app_uart.c 中提供了 4 个函数 uart_open、uart_putchar、uart_putstring、uart_getchar 以方便使用串口。app_uart.c 中的代码与硬件平台无关,读者可以把它直接添加到自己的工程。利用这几个函数在 main.c 中编写测试代码。 +main.c 源码如下: + +```c +#include "app_uart.h" +#include "board.h" +void test_thread_entry(void* parameter) +{ + rt_uint8_t uart_rx_data; + /* 打开串口 */ + if (uart_open("uart2") != RT_EOK) + { + rt_kprintf("uart open error.\n"); + while (1) + { + rt_thread_delay(10); + } + } + /* 单个字符写 */ + uart_putchar('2'); + uart_putchar('0'); + uart_putchar('1'); + uart_putchar('8'); + uart_putchar('\n'); + /* 写字符串 */ + uart_putstring("Hello RT-Thread!\r\n"); + while (1) + { + /* 读数据 */ + uart_rx_data = uart_getchar(); + /* 错位 */ + uart_rx_data = uart_rx_data + 1; + /* 输出 */ + uart_putchar(uart_rx_data); + } +} +int main(void) +{ + rt_thread_t tid; + /* 创建 test 线程 */ + tid = rt_thread_create("test", + test_thread_entry, + RT_NULL, + 1024, + 2, + 10); + /* 创建成功则启动线程 */ + if (tid != RT_NULL) + rt_thread_startup(tid); + return 0; +} +``` + +这段程序实现了如下功能: + +1. main 函数里面创建并启动了测试线程 test_thread_entry。 + +2. 测试线程调用 uart_open 函数打开指定的串口后,首先使用 uart_putchar 函数发送字符和 uart_putstring 函数发送字符串。 + +3. 接着在 while 循环里面调用 uart_getchar 函数读取接收到的数据并保存到局部变量 uart_rx_data 中,最后将数据错位后输出。 + +### 运行结果 ### + +编译、将代码下载到板卡,复位,串口 2 连接的终端软件 putty(软件参数配置为 115200-8-1-N、无流控)输出了字符 2、0、1、8 和字符串 Hello RT-Thread!。输入字符 ‘A’,串口 2 接收到将其错位后输出。实验现象如图所示: + +![实验现象](figures/result.png) + +> 提示:图中 putty 连接开发板的串口 2 作为测试串口。 + +## 进阶阅读 ## + +串口通常被配置为接收中断和轮询发送模式。在中断模式下,CPU 不需要一直查询等待串口相关标志寄存器,串口接收到数据后触发中断,我们在中断服务程序进行数据处理,效率较高。RT-Thread 官方 BSP 默认便是这种模式。 + +### 使用哪个串口 ### + +uart_open 函数用于打开指定的串口,它完成了串口设备回调函数设置、串口设备的开启和事件的初始化。源码如下: + +```c +rt_err_t uart_open(const char *name) +{ + rt_err_t res; + /* 查找系统中的串口设备 */ + uart_device = rt_device_find(name); + /* 查找到设备后将其打开 */ + if (uart_device != RT_NULL) + { + res = rt_device_set_rx_indicate(uart_device, uart_intput); + /* 检查返回值 */ + if (res != RT_EOK) + { + rt_kprintf("set %s rx indicate error.%d\n",name,res); + return -RT_ERROR; + } + /* 打开设备,以可读写、中断方式 */ + res = rt_device_open(uart_device, RT_DEVICE_OFLAG_RDWR | + RT_DEVICE_FLAG_INT_RX ); + /* 检查返回值 */ + if (res != RT_EOK) + { + rt_kprintf("open %s device error.%d\n",name,res); + return -RT_ERROR; + } + } + else + { + rt_kprintf("can't find %s device.\n",name); + return -RT_ERROR; + } + /* 初始化事件对象 */ + rt_event_init(&event, "event", RT_IPC_FLAG_FIFO); + return RT_EOK; +} +``` + +简要流程如下: + +![uart_open 函数流程图](figures/an0001_9.png) + +uart_open 函数使用到的设备操作接口有:rt_device_find、rt_device_set_rx_indicate、rt_device_open。 +uart_open 函数首先调用 rt_device_find 根据串口名字获得串口句柄,保存在静态全局变量 uart_device 中,后面关于串口的操作都是基于这个串口句柄。这里的名字是在 drv_usart.c 中调用注册函数 rt_hw_serial_register 决定的,该函数将串口硬件驱动和 RT-Thread 设备管理框架联系起来了。 + +```c + /* register UART2 device */ + rt_hw_serial_register(&serial2, + "uart2", + RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, + uart); +``` + +接着调用 rt_device_set_rx_indicate 设置串口接收中断的回调函数。 +最后调用 rt_device_open 以可读写、中断接收方式打开串口。它的第二个参数为标志,与上面提到的注册函数 rt_hw_serial_register 保持一致即可。 + +```c +rt_device_open(uart_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX); +``` + +最后调用 rt_event_init 初始化事件。 +RT-Thread 中默认开启了自动初始化机制,因此用户不需要在应用程序中手动调用串口的初始化函数(drv_usart.c 中的 INIT_BOARD_EXPORT 实现了自动初始化)。用户实现的由宏 RT_USING_UARTx 选定的串口硬件驱动将自动关联到 RT-Thread 中来(drv_usart.c 中的 rt_hw_serial_register 实现了串口硬件注册)。 + +### 串口发送 ### + +uart_putchar 函数用于发送 1 字节数据。uart_putchar 函数实际上调用的是 rt_device_write 来发送一个字节,并采取了防出错处理,即检查返回值,失败则重新发送,并限定了超时。源码如下: + +```c +void uart_putchar(const rt_uint8_t c) +{ + rt_size_t len = 0; + rt_uint32_t timeout = 0; + do + { + len = rt_device_write(uart_device, 0, &c, 1); + timeout++; + } + while (len != 1 && timeout < 500); +} +``` + +### 串口接收 ### + +uart_getchar 函数用于接收数据,uart_getchar 函数的实现采用了串口接收中断回调机制和事件用于异步通信,它具有阻塞特性。 +相关源码如下: + +```c +/* 串口接收事件标志 */ +#define UART_RX_EVENT (1 << 0) +/* 事件控制块 */ +static struct rt_event event; +/* 设备句柄 */ +static rt_device_t uart_device = RT_NULL; + +/* 回调函数 */ +static rt_err_t uart_intput(rt_device_t dev, rt_size_t size) +{ + /* 发送事件 */ + rt_event_send(&event, UART_RX_EVENT); + return RT_EOK; +} +rt_uint8_t uart_getchar(void) +{ + rt_uint32_t e; + rt_uint8_t ch; + /* 读取 1 字节数据 */ + while (rt_device_read(uart_device, 0, &ch, 1) != 1) +{ + /* 接收事件 */ + rt_event_recv(&event, UART_RX_EVENT,RT_EVENT_FLAG_AND | + RT_EVENT_FLAG_CLEAR,RT_WAITING_FOREVER, &e); +} + return ch; +} +``` + +uart_getchar 函数内部有一个 while() 循环,先调用 rt_device_read 去读取一字节数据,没有读到则调用 rt_event_recv 等待事件标志,挂起调用线程;串口接收到一字节数据后产生中断,调用回调函数 uart_intput,回调函数里面调用了 rt_event_send 发送事件标志以唤醒等待该 event 事件的线程。 +调用 uart_getchar 函数发生的数据流向示意图如下: + +![uart_getchar 数据流](figures/an0001_11.png) + +应用程序调用 uart_getchar 时,实际调用关系为:rt_device_read ==> rt_serial_read ==> drv_getc,最终从串口数据寄存器读取到数据。 + +## 参考资料 + +* 《串口设备》 + + diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_10.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_10.png new file mode 100644 index 0000000000000000000000000000000000000000..5912b22206a904ed38a44108d0af88b56350f940 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_10.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_11.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_11.png new file mode 100644 index 0000000000000000000000000000000000000000..3895ffe9fccd1b33e62448a1009e9a9788b475f9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_11.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_12.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_12.png new file mode 100644 index 0000000000000000000000000000000000000000..d19ac7c50264786e8255d7a071efb361f5868dd2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_12.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_2.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_2.png new file mode 100644 index 0000000000000000000000000000000000000000..bd3fda20741494f6b6f1b5dd2b78fe8f53666778 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_3.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_3.png new file mode 100644 index 0000000000000000000000000000000000000000..2038f1b0a84b5847234cb6dc1e762cd1a329c4ec Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_4.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_4.png new file mode 100644 index 0000000000000000000000000000000000000000..6d09d8aaf90e925ba6fab0d5bced3d32ffb2daf2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_4.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_5.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_5.png new file mode 100644 index 0000000000000000000000000000000000000000..f3df46c745a697ef9a3718c018ff996123923b63 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_5.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_6.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_6.png new file mode 100644 index 0000000000000000000000000000000000000000..fe6c7e6a93d13b8dd02fa034dec81f83f9d6493d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_6.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_7.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_7.png new file mode 100644 index 0000000000000000000000000000000000000000..2ca46b50dd15f2308faed24735f04bfb1f372490 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_7.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_9.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_9.png new file mode 100644 index 0000000000000000000000000000000000000000..7f5c5d17ea740663dacabc56e55c8c9da07a15b9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/an0001_9.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/mcu.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/mcu.png new file mode 100644 index 0000000000000000000000000000000000000000..c1acf1e57631bc58e4d0ed89b6d2e59f8223d71e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/mcu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..41bb5460d434cec415d2fad14b0307fad15bc228 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/result.png b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/result.png new file mode 100644 index 0000000000000000000000000000000000000000..ffeecbba39b93b8e5d29e77982eb97319f56455d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/figures/result.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/uart/uart.rar b/rt-thread-version/rt-thread-standard/application-note/driver/uart/uart.rar new file mode 100644 index 0000000000000000000000000000000000000000..00ed6452557fd62aabd63e0e665a544e01bcc67f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/uart/uart.rar differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/an0046-rtthread-driver-usbh.md b/rt-thread-version/rt-thread-standard/application-note/driver/usb/an0046-rtthread-driver-usbh.md new file mode 100644 index 0000000000000000000000000000000000000000..1b006c38a2f29ac345ae1f31d366bb457b8f409d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/driver/usb/an0046-rtthread-driver-usbh.md @@ -0,0 +1,177 @@ +# STM32 上使用 USB Host 读写 U 盘 + +本文描述了如何在搭载了 RT-Thread 操作系统的平台上使用 USB Host 读写 U 盘,包括 USB Host 的应用、配置等。并给出了在正点原子 `STM32F767 apollo` 开发板上验证的演示。 + +## 简介 + +USB(Universal Serial Bus)是一种支持热插拔的通用串行总线。它使用差分信号来传输数据,在 USB 1.0和 USB 1.1 版本中,只支持 1.5Mb/s 的低速(low-speed)模式和 12Mb/s 的全速(full-speed)模式,在 USB 2.0 中,又加入了480Mb/s 的高速模式,USB 3.0(super speed),传输速率最大5Gbps。 + +在 USB 体系中又包括 USB Host(主机)和USB Device(设备) + +- USB Host + + - 任何USB系统中只有一个主机。 主机系统的USB接口被称为主机控制器。 主机控制器可以以硬件,固件或软件的组合来实现。 根集线器集成在主机系统内以提供一个或多个连接点。 + +- USB Device + USB Device 可以分为 USB Hub 和 USB Function。 + + USB Hub 提供了一种低成本、低复杂度的 USB接口扩展方法。Hub 的上行端口面向 HOST,下行端口面向设备(Hub 或功能设备)。在下行端口上,Hub 提供了设备连接检测和设备移除检测的能力,并给各下行端口供电。Hub 可以单独使能各下行端口。不同端口可以工作在不同的速度等级(高速/全速/低速)。 + + USB Function 能够通过总线传输或接收数据或控制信息的设备,在 USB2.0 标准中,别称为 Class + +本文主要是基于正点原子 `stm32f767-atk-apollo` 开发板,给出了 USB Host 读写 U 盘的配置和使用示例。 + +本文准备资料如下: + +- [RT-Thread 源码](https://github.com/RT-Thread/rt-thread) +- [Env 工具](https://www.rt-thread.org/page/download.html) +- U 盘 + +依赖 + +- RT-Thread 4.0.2+ +- RT-Thread 设备驱动框架 + +## 硬件连接准备 + +本文是基于 U 盘的读写,所以需要准备好一个 U 盘,并插入开发板上的 U 盘接口。 + +## ENV 配置 + +### 打开 USB Host +RT-Thread 可以很方便的通过 ENV 来配置和生成工程。在 `rt-thread\bsp\stm32\stm32f767-atk-apollo` 目录下打开 ENV 工具,使用 menuconfig 进入如下配置界面并选中。 + + ---- Hardware Drivers Config + ----On-Chip Peripheral Drivers + ----Enable USH Host + ----Enable Udisk Drivers + +配置界面如下图所示 + +![menuconfig](figures/menuconfig.png) + +### 打开文件系统 + +本文使用的是 USB Host 读写 U 盘的功能,所以需要打开 RT-Thread 的虚拟文件系统功能,打开文件系统的操作如下 + + ---- RT-Thread Components + ----Device virtual file system + ----Using device virtual file system + +配置界面如下图所示 + +![file](figures/filesystem.png) + +### 生成工程并下载 + +在 ENV 中打开 USB Host 和虚拟文件系统的功能之后,工程配置就结束了,退出配置界面并保存。在 ENV 工具中使用 `scons --target=mdk5` 命令重新生成工程并打开。工程打开之后可以看到 USB Host 的框架代码和驱动代码都已经自动加入到工程里面了,如下图所示 + +![project](figures/project.png) + +在 `main.c` 文件中加入以下测试代码并下载 +```c +#include +#define TEST_FN "/test_usbh.c" +static char test_data[120], buffer[120]; + +void readwrite(const char* filename) +{ + int fd; + int index, length; + + fd = open(TEST_FN, O_WRONLY | O_CREAT | O_TRUNC, 0); + if (fd < 0) + { + rt_kprintf("open file for write failed\n"); + return; + } + + for (index = 0; index < sizeof(test_data); index ++) + { + test_data[index] = index + 27; + } + + length = write(fd, test_data, sizeof(test_data)); + if (length != sizeof(test_data)) + { + rt_kprintf("write data failed\n"); + close(fd); + return; + } + + close(fd); + + fd = open(TEST_FN, O_RDONLY, 0); + if (fd < 0) + { + rt_kprintf("check: open file for read failed\n"); + return; + } + + length = read(fd, buffer, sizeof(buffer)); + if (length != sizeof(buffer)) + { + rt_kprintf("check: read file failed\n"); + close(fd); + return; + } + + for (index = 0; index < sizeof(test_data); index ++) + { + if (test_data[index] != buffer[index]) + { + rt_kprintf("check: check data failed at %d\n", index); + close(fd); + return; + } + } + + rt_kprintf("usb host read/write udisk successful\r\n"); + + close(fd); +} + +MSH_CMD_EXPORT(readwrite, usb host read write test); +``` + +### 运行测试程序 + +将以上程序下载入开发板之后连接串口调试工具可以看到如下调试信息 + +![usize](figures/usize.png) + +这里可以看到 U 盘的大小约为 7.4G,说明 U 盘已经成功挂载到开发板上面的文件系统了。使用文件系统的 ls 命令查看 U 盘目录的结果如下图: + +![ls](figures/ls.png) + +在串口运行导出的 readwrite 测试函数的结果如下图所示 + +![readwrite](figures/readwrite.png) + +再次使用 ls 命令可以看到在 U 盘的根目录下已经新建了一个 test_usbh.c 文件,并且大小为 120 个字节,如下图所示 + +![ls2](figures/ls2.png) + +我们可以将 U 盘拔出并插在电脑上验证一下我们刚才写入的文件。 + +![pc](figures/pc.png) + +从电脑上我们可以看到已经成功创建了一个 `test_usbh.c` 的文件。到这一步为止,在 STM32 上使用 USB Host 读写 U 盘的介绍就结束了 + +## 参考资料 + +[ENV 用户手册](https://www.rt-thread.org/document/site/programming-manual/env/env/) + +[虚拟文件系统](https://www.rt-thread.org/document/site/programming-manual/filesystem/filesystem/) + +[RT-Thread源码](https://github.com/RT-Thread/rt-thread) + +## 常见问题 + +### Q: 在 `rt-thread\bsp\stm32\libraries` 目录下没有 drv_usbh.c 文件? + +**A:** 该问题一般是当前版本还没有支持 USB Host 的驱动,请使用 RT-Thread 4.0.2+ 的版本进行测试。 + +### Q:工程目录下没有 drv_usbh.c 文件? + +**A:** 该问题一般是当前 BSP 没有添加 drv_usbh.c 的驱动,请参考[STM32 外设添加指南](https://github.com/RT-Thread/rt-thread/blob/master/bsp/stm32/docs/STM32%E7%B3%BB%E5%88%97%E5%A4%96%E8%AE%BE%E9%A9%B1%E5%8A%A8%E6%B7%BB%E5%8A%A0%E6%8C%87%E5%8D%97.md)。 diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/filesystem.png b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/filesystem.png new file mode 100644 index 0000000000000000000000000000000000000000..6ddea7d71e13c358f264672ffcc75eaf5d5b62d6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/filesystem.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/ls.png b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/ls.png new file mode 100644 index 0000000000000000000000000000000000000000..72f941c58bd51bb1a9b71e357a3015f3a86bb8e6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/ls.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/ls2.png b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/ls2.png new file mode 100644 index 0000000000000000000000000000000000000000..d85f3136c3c61eafdb3466568b57a6d604d8f017 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/ls2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..de12a65f2a4af41192d67bc667bd1e8eaf88692a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/pc.png b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/pc.png new file mode 100644 index 0000000000000000000000000000000000000000..3033c87de7ea579aff0f87212d3841884855f76a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/pc.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/project.png b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/project.png new file mode 100644 index 0000000000000000000000000000000000000000..01504d18040dcb1e25fb9f17cf235250fd5582ae Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/project.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/readwrite.png b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/readwrite.png new file mode 100644 index 0000000000000000000000000000000000000000..aa2402eb50b45c93225fe475ab3972afccd3691d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/readwrite.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/usize.png b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/usize.png new file mode 100644 index 0000000000000000000000000000000000000000..a4f80b7a5856a73928506df50936182a9f063f4a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/driver/usb/figures/usize.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/an0036-freemodbus.md b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/an0036-freemodbus.md new file mode 100644 index 0000000000000000000000000000000000000000..3d0309f82008ce695663576fc6d8568ebf85b883 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/an0036-freemodbus.md @@ -0,0 +1,372 @@ +# FreeModbus 应用笔记 + +## 简介 + +FreeModbus 是一款开源的 Modbus 协议栈,但是只有从机开源,主机源码是需要**收费**的。同时网上也没有比较好的开源的 Modbus 主机协议栈,在这样的背景下,armink 大神开发了这款支持主机模式的 FreeModbus 协议栈。 + +本文的目的是介绍如何在潘多拉开发板上运行 Modbus 主机与从机。首先介绍串口方式通信的 Modbus 主机和从机。然后介绍如何使用网络进行 Modbus 通信。 + +## 准备工作 + +1. 首先演示使用串口作为 Modbus 通信通道的方式,将潘多拉开发板的 `uart2` 通过 usb 转串口线连接到电脑上 + +2. 使用 usb 线连接开发板的 st-link 接口到电脑上,打开电源开发,开发板上电。 + +3. 查看设备管理器,可以看到两个串口设备,一个用来 shell 通信,一个用来 Modbus 通信。 + +![设备管理器](figures/hw_com.png) + +## 运行 Modbus 主机 + +### 配置工程 + +在 RT-Thread 源码目录下找到潘多拉的bsp `rt-thread\bsp\stm32\stm32l475-atk-pandora` ,在此目录下打开 ENV 工具。 + +#### 配置 FreeModbus 软件包 + +1. 输入 menuconfig 命令打开配置工具 + +![配置菜单](figures/menu.png) + +2. 按照下面的路径进入 FreeModbus 软件包的配置菜单,并开启`主机`模式 + + ``` + RT-Thread online packages ---> + IoT - internet of things ---> + [*] FreeModbus: Modbus master and slave stack ---> + [*] Master mode ---> + [ ] Slave mode ---- + Version (latest) ---> + ``` + +3. 配置主机模式选项 + +进入 `Mastar mode` 配置菜单,然后开启主机示例程序,如下图所示: + +![配置 FreeModbus 主机](figures/master_menu.png) + +- **advanced configuration**:高级配置选项 +- **Enable RTU master mode**:开启 RTU 模式支持(主机暂只支持 RTU 模式) +- **Enable master sample**:开启主机示例程序 +- **Test slave device address**:测试用的从机设备地址 +- **uart number used by master sample, e.g. 2 means uart2**:表示使用串口几进行通信,默认使用 uart2 +- **uart baudrate used by master sample**:通信用的波特率 + +#### 配置硬件 uart2 + +然后返回到主菜单,进入硬件配置的菜单里开启 uart2 。 + +``` +Hardware Drivers Config ---> + On-chip Peripheral Drivers ---> + -*- Enable UART ---> + [*] Enable UART2 +``` + +![硬件配置](figures/uart2_menu.png) + +退出 `menuconfig` 配置工具并保存。然后 `pkgs --update` 下载软件包,然后 `scons --target=mdk5` 生成工程。 + +![生成工程](figures/scons_project.png) + +### 运行示例程序 + +打开工程,在 FreeModbus 分组里可以看到主机的示例代码 `sample_mb_master.c` 关键代码如下所示: + +``` +#define MB_POLL_CYCLE_MS 500 + +static void send_thread_entry(void *parameter) +{ + eMBMasterReqErrCode error_code = MB_MRE_NO_ERR; + rt_uint16_t error_count = 0; + USHORT data[2] = {0}; + + while (1) + { + /* 准备要写入的数据 */ + data[0] = (USHORT)(rt_tick_get() / 10); + data[1] = (USHORT)(rt_tick_get() % 10); + /* 向从机写多个保持寄存器 */ + error_code = eMBMasterReqWriteMultipleHoldingRegister(SLAVE_ADDR, /* salve address */ + MB_SEND_REG_START, /* register start address */ + MB_SEND_REG_NUM, /* register total number */ + data, /* data to be written */ + RT_WAITING_FOREVER); /* timeout */ + + /* Record the number of errors */ + if (error_code != MB_MRE_NO_ERR) + { + error_count++; + } + } +} + +static void mb_master_poll(void *parameter) +{ + /* Modbus 主机协议栈初始化,初始化为 RTU 模式 */ + eMBMasterInit(MB_RTU, PORT_NUM, PORT_BAUDRATE, PORT_PARITY); + eMBMasterEnable(); + + while (1) + { + /* 定时轮询 */ + eMBMasterPoll(); + rt_thread_mdelay(MB_POLL_CYCLE_MS); + } +} +``` + +编译下载,程序开始运行。 + +![程序运行](figures/master_run1.png) + +输入 `mb_master_samlpe` 运行主机示例程序。 + +### 运行 Modbus Slave + +Modbus Slave 是一个 Modbus 从设备仿真器,可以仿真 32 个从设备/地址域。每个接口都提供了对 EXCEL 报表的 OLE 自动化支持。主要用来模拟 Modbus 从站设备,接收主站的命令包,回送数据包。帮助 Modbus 通讯设备开发人员进行 Modbus 通讯协议的模拟和测试。安装运行 Modbus Slave。下载安装软件 [Modbus Slave]() 。 + +Modbus Slave 需要先配置从机参数,然后连接主机。 + +**modbus slave 程序主窗口介绍** + +其中:ID = 1表示模拟的Modbus子设备的设备地址;F = 03表示所使用的Modbus功能码,图中为03功能码。红字部分,表示当前的错误状态,“No Connection”表示未连接状态。 + +单击菜单【Setup】中【Slave Definition.. F8】进行参数设置,会弹出参数设置对话框。 + +![从机配置](figures/slave_setup.png) + +![配置参数](figures/slave_setup2.png) + +然后点击连接(Connection),连接对应开发板上 Modbus 主机控制的 uart2 的端口即可。 + +![连接主机](figures/slave_connection1.png) + +### 运行结果 + +可以看到 Modbus Slave 与开发板上运行的主机通信成功,并且可以看到其对应的保持寄存器的寄存器 2,3 的数据被不断改变。 + +![运行结果](figures/master_run2.png) + +## 运行 Modbus 从机 + +### 配置工程 + +在 RT-Thread 源码目录下找到潘多拉的bsp `rt-thread\bsp\stm32\stm32l475-atk-pandora` ,在此目录下打开 ENV 工具。 + +#### 配置 FreeModebus 软件包 + +1. 输入 menuconfig 命令打开配置工具 + +![配置菜单](figures/menu.png) + +2. 按照下面的路径进入 FreeModbus 软件包的配置菜单,并开启`从机`模式 + + ``` + RT-Thread online packages ---> + IoT - internet of things ---> + [*] FreeModbus: Modbus master and slave stack ---> + [ ] Master mode ---- + [*] Slave mode ---> + Version (latest) ---> + ``` + +3. 配置从机模式选项 + +进入 `Slave mode` 配置菜单,然后开启从机示例程序,如下图所示: + +![从机配置菜单](figures/slave_menu.png) + +- **advanced configuration**:高级配置选项 +- **Enable RTU slave mode**:开启 RTU 模式支持 +- **Enable ASCII slave mode**:开启 ASCII 模式支持 +- **Enable TCP slave mode**:开启 TCP 模式支持(需要设备可以连接网络,且可做服务器使用) +- **Enable slave sample**:开启主机示例程序 +- **Test slave device address**:测试用的从机设备地址 +- **uart number used by master sample, e.g. 2 means uart2**:表示使用串口几进行通信,默认使用 uart2 +- **uart baudrate used by master sample**:通信用的波特率 + +#### 配置硬件 uart2 + +然后返回到主菜单,进入硬件配置的菜单里开启 uart2 。 + +``` +Hardware Drivers Config ---> + On-chip Peripheral Drivers ---> + -*- Enable UART ---> + [*] Enable UART2 +``` + +![硬件配置](figures/uart2_menu.png) + +退出 `menuconfig` 配置工具并保存。然后 `pkgs --update` 下载软件包,然后 `scons --target=mdk5` 生成工程。 + +![生成工程](figures/scons_project.png) + +### 运行示例程序 + +打开工程,在 FreeModbus 分组里可以看到从机的示例代码 `sample_mb_slave.c` 关键代码如下所示: + +```c +#define MB_POLL_CYCLE_MS 200 +extern USHORT usSRegHoldBuf[S_REG_HOLDING_NREGS]; /* 存储保持寄存器的数组 */ + +static void send_thread_entry(void *parameter) +{ + USHORT *usRegHoldingBuf; + usRegHoldingBuf = usSRegHoldBuf; + rt_base_t level; + + while (1) + { + level = rt_hw_interrupt_disable(); + /* 改变保持寄存器 3 的数据 */ + usRegHoldingBuf[3] = (USHORT)(rt_tick_get() / 100); + + rt_hw_interrupt_enable(level); + /* 数据产生的速率为 1个/秒 */ + rt_thread_mdelay(1000); + } +} + +static void mb_slave_poll(void *parameter) +{ + if (rt_strstr(parameter, "RTU")) + { +#ifdef PKG_MODBUS_SLAVE_RTU # 如果开启了 RTU 模式就检测 RTU 参数 + eMBInit(MB_RTU, SLAVE_ADDR, PORT_NUM, PORT_BAUDRATE, PORT_PARITY); +#else + rt_kprintf("Error: Please open RTU mode first"); +#endif + } + else if (rt_strstr(parameter, "ASCII")) + { +#ifdef PKG_MODBUS_SLAVE_ASCII # 如果开启了 ASCII 模式就检测 ASCII 参数 + eMBInit(MB_ASCII, SLAVE_ADDR, PORT_NUM, PORT_BAUDRATE, PORT_PARITY); +#else + rt_kprintf("Error: Please open ASCII mode first"); +#endif + } + else if (rt_strstr(parameter, "TCP")) + { +#ifdef PKG_MODBUS_SLAVE_TCP # 如果开启了 TCP 模式就检测 TCP 参数 + eMBTCPInit(0); # TCP 模式下使用默认端口 502 +#else + rt_kprintf("Error: Please open TCP mode first"); +#endif + } + else + { + rt_kprintf("Error: unknown parameter"); + } + eMBEnable(); + while (1) + { + eMBPoll(); + rt_thread_mdelay(MB_POLL_CYCLE_MS); + } +} +``` + +编译下载,程序开始运行。 + +![程序运行](figures/run1.png) + +输入 `mb_slave_samlpe ` 运行示例程序。如运行 `RTU` 模式的从机示例程序。 + +``` + \ | / +- RT - Thread Operating System + / | \ 4.0.2 build Jul 11 2019 + 2006 - 2019 Copyright by rt-thread team +msh />mb_slave_samlpe RTU # 运行 RTU 模式的从机示例程序 +msh /> +``` + +### 运行 Modbus Poll + +Modbus Poll 是一个 Modbus 主机仿真器,用于测试和调试Modbus从设备。该软件支持ModbusRTU、ASCII、TCP/IP。用来帮助开发人员测试Modbus从设备,或者其它Modbus协议的测试和仿真。下载安装软件 [Modbus Poll]() 。 + +![Modbus](figures/poll_main.png) + +**modbus poll 程序主窗口介绍** + +其中:Tx = 0表示向主站发送数据帧次数; Error = 0表示通讯错误次数; ID = 1表示模拟的Modbus子设备的设备地址;F = 03表示所使用的Modbus功能码,图中为03功能码; SR = 1000ms表示扫描周期。红字部分,表示当前的错误状态,“No Connection”表示未连接状态。 + +单击菜单【Setup】中【Read/Write Definition.. F8】进行参数设置,会弹出参数设置对话框。 + +![主机配置](figures/poll_setup.png) + +![配置从机参数](figures/poll_setup2.png) + +然后点击 connection,连接从机。 + +![连接从机](figures/poll_connection1.png) + +![1562211262411](figures/poll_connection2.png) + +### 运行结果 + +可以看到 Modbus Poll 与开发板上运行的从机通信成功,并且可以查看到从机 1 保持寄存器寄存器 3 的数据每秒改变一次。 + +![运行结果](figures/poll_run.png) + +## 使用网络进行 Modbus 通信 + +FreeModbus 的**从机**支持 TCP 模式,可以在**已经连接网络,且可做服务端**的设备上运行,并利用 TCP 协议和 远端主机进行通讯。 + +### 配置 FreeModebus 软件包 + +按上一节的操作,打开并配置 FreeModbus 软件包,配置支持 TCP 模式。 + +![开启 TCP 模式](figures/tcp_menu.png) + +退出 `menuconfig` 配置工具并保存。然后 `pkgs --update` 下载软件包,然后 `scons --target=mdk5` 生成工程。 + +![生成工程](figures/tcp_update.png) + +### 运行示例程序 + +编译下载,程序开始运行。输入 `mb_slave_samlpe TCP` 运行 `TCP` 模式下的示例程序。 + +``` + \ | / +- RT - Thread Operating System + / | \ 4.0.2 build Jul 11 2019 + 2006 - 2019 Copyright by rt-thread team +lwIP-2.0.2 initialized! +[I/sal.skt] Socket Abstraction Layer initialize success. +msh />ifconfig # 查看设备 IP 地址 +network interface device: e0 (Default) +MTU: 1500 +MAC: 00 80 e1 14 2e 34 +FLAGS: UP LINK_UP INTERNET_UP DHCP_ENABLE ETHARP BROADCAST IGMP +ip address: 192.168.12.162 # 设备的 IP 地址 +gw address: 192.168.10.1 +net mask : 255.255.0.0 +dns server #0: 192.168.10.1 +dns server #1: 223.5.5.5 +msh />mb_slave_samlpe TCP # 运行 TCP 模式的从机示例程序 +msh /> +``` + +### 运行 Modbus Poll + +单击菜单【Connection】中【connect.. F3】进行连接参数设置,会弹出参数设置对话框。选择 TCP/IP 连接方式,配置 从机设备的 IP 地址和端口号,点击 OK 即可。 + +![连接 TCP](figures/tcp_poll.png) + +### 运行结果 + +可以看到 Modbus Poll 与开发板上运行的从机通信成功,并且可以查看到从机 1 保持寄存器寄存器 3 的数据每秒改变一次。 + +![运行结果](figures/tcp_run.png) + +## 注意事项 + +- 运行 TCP 通信示例之前,请确认当前 BSP **支持网络通信**且**可作为服务端**运行。 + +## 引用参考 + +- [FreeModbus 软件包主页]() diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/hw_com.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/hw_com.png new file mode 100644 index 0000000000000000000000000000000000000000..f1d5e78544d74dde16b8d3e4ec4a979963499d83 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/hw_com.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_menu.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..33af7eb60d189d7d465ea069d35b711069dfe1b7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_menu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_run1.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_run1.png new file mode 100644 index 0000000000000000000000000000000000000000..cba1c668c76c0cd848751af9f2b7a4878d2d536c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_run1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_run2.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_run2.png new file mode 100644 index 0000000000000000000000000000000000000000..95e1ffc05aab75eb16a6c7f7351361961126a3dd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/master_run2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/menu.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/menu.png new file mode 100644 index 0000000000000000000000000000000000000000..53b823fd9075d8758be8881f292f9b9b8f998fb3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/menu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_connection1.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_connection1.png new file mode 100644 index 0000000000000000000000000000000000000000..18fe7d17018c9f3b8b2117aa416a427851ad1666 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_connection1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_connection2.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_connection2.png new file mode 100644 index 0000000000000000000000000000000000000000..86c237447a81619047333be2ac0d6546ed26b6c6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_connection2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_main.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_main.png new file mode 100644 index 0000000000000000000000000000000000000000..b5dffdfba8c5e7db12ebe1deb2addba66559abb6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_main.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_run.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_run.png new file mode 100644 index 0000000000000000000000000000000000000000..f634625d2750e6f23e7a7b3e7a2df1e578e167d6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_run.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_setup.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_setup.png new file mode 100644 index 0000000000000000000000000000000000000000..7156fc75021570561c147219e82206f712615c96 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_setup.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_setup2.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_setup2.png new file mode 100644 index 0000000000000000000000000000000000000000..38cbeeb323ed000d2c543e77da641e92c5cd5185 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/poll_setup2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/run1.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/run1.png new file mode 100644 index 0000000000000000000000000000000000000000..c32015154ef36904e5421cac6ecbe0e5f54b0f44 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/run1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/scons_project.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/scons_project.png new file mode 100644 index 0000000000000000000000000000000000000000..1e02084178c704e4f18f9df318e2148bf25488af Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/scons_project.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_connection1.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_connection1.png new file mode 100644 index 0000000000000000000000000000000000000000..ba0fd7bce01ee4bd91768353c19467ad6fb4814f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_connection1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_menu.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..eae4f7dfb1fc6ef78bcf9ee65cdc7fde9aaa74c2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_menu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_setup.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_setup.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6a9335e1b4e09ac5c74911dff52d50915be19f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_setup.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_setup2.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_setup2.png new file mode 100644 index 0000000000000000000000000000000000000000..0f53c660fb0ee60b0348addfeb57879f6aa0f2cd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/slave_setup2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_menu.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..35a926b4fcb7296e0c57a7259137f5b5287a05a2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_menu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_poll.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_poll.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b7e42bea226c910f97dacd75f8470486ac23ad Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_poll.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_run.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_run.png new file mode 100644 index 0000000000000000000000000000000000000000..3b6d831a33b1c406f2f234dfe14203d13e02ace3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_run.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_update.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_update.png new file mode 100644 index 0000000000000000000000000000000000000000..8491461a5f09460e8d0a0ebf9fe206acabdd45aa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/tcp_update.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/uart2_menu.png b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/uart2_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..28258d0131912baf920428e8e933d7af7f7a984a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/freemodbus/figures/uart2_menu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/an0029-mbedtls_wireshark_sniffer.md b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/an0029-mbedtls_wireshark_sniffer.md new file mode 100644 index 0000000000000000000000000000000000000000..fb0c92d7a3b8325f97e8e6e956f0360b88b90f54 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/an0029-mbedtls_wireshark_sniffer.md @@ -0,0 +1,270 @@ +# 前言 +随着物联网的发展,连接到互联网的设备数量呈指数增长,物联网信息安全越来越重要。 + +因此,TLS逐渐成为物联网通讯的标配。但是TLS是加密传输,这给调试增加了一定的难度。 + +笔者最近工作中一直用到HTTPS,但是苦于wireshark只能抓取HTTP的明文数据包,无法抓取HTTPS的数据包,于是就有了这篇文章,使用wireshark抓取HTTPS的数据包. + +# 简单介绍TLS1.2握手和协商过程 + +![](./figures/handshake.png) + +## client hello +客户端向服务器发送客户端信息: + +- 支持的最高tls协议版本 +- 客户端支持的加密方式(cipher suites)列表 +- 客户端随机数(ramdom_c) +- 其他扩展字段 + +## server hello +- 服务器端返回 tls 版本, 加密方式(cipher suite), 服务器的随机数(random_s) +- 服务器发送证书,用于身份验证 +- 通知客户端 server hello 信息发送完成 + +## 证书校验 +客户端验证证书的合法性,如果验证通过才会进行后续通讯 + +## client key exchange +- 客户端计算产生随机数 pre-master,并使用服务器公钥(非对称加密的公钥)加密,发送给服务器 +- 客户端通过random_c,random_s,pre_master计算出密钥 +- 客户端发送change cipher spec通知服务器后续使用此密钥和加密算法进行通信 +- 发送握手数据. + +## server change cipher spec +- 服务器接收 pre_master,并使用服务器私钥(非对称加密的私钥)解密 +- 服务器通过random_c, random_s, pre_master计算出密钥 +- 服务器发送change cipher spec告诉客户端后续的通讯都采用协商的密钥和协商的加密算法通讯 + +## hangshake message finish +客户端接收服务器发送的握手消息,验证通过后,握手完成。 + +此后的通讯都采用协商密钥和加密算法通讯。 + +# 设备端解密https数据包 +查阅文档得知,wireshark 支持将 tls 会话中使用的密钥保存到外部文件中,供 wireshark 使用。 + +## 流程图 +![](./figures/flow.png) + +在没有抓包路由器的情况下,使用方案A, 电脑创建 wifi 热点,设备端连接电脑热点,并发起 https 请求,服务器接收到请求,向设备端发出响应,设备端根据响应的内容,计算出密钥, 并将设备端随机数和密钥通过 udp 发送到 pc,保存到 sslkey.log 文件,wireshark 根据设备端随机数和密钥即可将 tls 数据包解密。 + +## 配置wireshark +- 新建 sslkey.log 文件,并配置为 windows 系统变量。 + +![](./figures/system.png) + +- 配置 wireshark + +编辑->首选项->protocols->SSL(version 2.4.9),更高版本的 wireshark 操作步骤为:编辑->首选项->protocols->TLS + +![](./figures/wireshark_config.png) + +>配置好之后重启 wireshark + +按照下面的格式,向 sslkey.log 写入客户端随机数和密钥, 即可使 wireshark 解密 tls 数据包. + +``` +CLIENT_RANDOM 5a497axx 3756f69b4axxx +CLIENT_RANDOM 5dfb96xx b07a9da164xxx +CLIENT_RANDOM 5a497axx 12e14567b9xxx +CLIENT_RANDOM 55c00xxx b07a9da164xxx +CLIENT_RANDOM 5a497xxx b03ca0d5fcxxx +``` + +数据的含义如下: + +- CLIENT_RANDOM: 固定标签(支持 SSL 3.0, TLS 1.0, 1.1, 1.2) +- 第二个参数:客户端随机数(random_c)32个字节,编码为64个十六进制字符 +- 第三个参数: 48字节的协商密钥,编码为96个十六进制字符 + +接下来只要找到设备上的客户端随机数和密钥,保存到 syskey.log,即可通过 wireshark 解密 tls 数据包。 + +下面函数,保存了客户端随机数和密钥信息。 + +ssl_tls.c + +```c +int mbedtls_ssl_derive_keys( mbedtls_ssl_context *ssl ) +{ + ... + + MBEDTLS_SSL_DEBUG_MSG( 3, ( "ciphersuite = %s", + mbedtls_ssl_get_ciphersuite_name( session->ciphersuite ) ) ); + MBEDTLS_SSL_DEBUG_BUF( 3, "master secret", , 48 ); + MBEDTLS_SSL_DEBUG_BUF( 4, "random bytes", handshake->randbytes, 64 ); + MBEDTLS_SSL_DEBUG_BUF( 4, "key block", keyblk, 256 ); + ... +} + +``` +其中`session->master`保存的是密钥,`handshake->randbytes`保存的是客户端和服务器的随机数。 +也就是说,将这两个参数保存到 sslkey.log 文件中,那么 wireshark 就能解密设备上的https数据包。 + +编写 udp 客户端,将客户端随机数和密钥发送到 windows,windows 编写 udp server python 脚本,用于接收数据,并将数据写入 sslkey.log 文件 + +```c +#include +#include + +#include +#include "netdb.h" + +static int port = 5000; + +void udpcli_send(char* ip, char* random_c, int random_len, char* master, int master_len) +{ + int sock; + struct hostent *host; + struct sockaddr_in server_addr; + char random_ptr[100] = {0}; + char master_ptr[100] = {0}; + int i = 0; + + if(random_c == RT_NULL || master == RT_NULL) + { + rt_kprintf("random_c or master is null\n"); + return; + } + + host = (struct hostent *) gethostbyname(ip); + if(host == RT_NULL) + { + rt_kprintf("Get host by name failed!\n"); + return; + } + + //random server_random : 32bit + client_random : 32bit + for(i = 0; i < 32; i++) + { + sprintf(&random_ptr[i*2], "%02x", random_c[32+i]); + } + + for(i = 0; i < 48; i++) + { + sprintf(&master_ptr[i*2], "%02x", master[i]); + } + rt_kprintf("random : %s\n", random_ptr); + rt_kprintf("master : %s\n", master_ptr); + + if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + { + rt_kprintf("Create socket error"); + return; + } + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + // sendto(sock, send_data, rt_strlen(send_data), 0, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); + sendto(sock, random_ptr, 64, 0, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); + sendto(sock, master_ptr, 96, 0, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); + + if(sock >= 0) + { + closesocket(sock); + sock = -1; + } +} + +``` + +udpserver.py +```python +import socket + +BUFSIZ = 1024 +ip_port = ('0.0.0.0', 5000) +file = r'd:\work\tmp\sslkey.log' + +server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +server.bind(ip_port) + +while True: + random_c, client_addr = server.recvfrom(BUFSIZ) + master, client_addr = server.recvfrom(BUFSIZ) + print("open file" + " " + file) + write_data = 'CLIENT_RANDOM ' + str(random_c, encoding='utf-8') + ' ' + str(master, encoding='utf-8') + print(write_data) + + with open(file, 'a') as f: + f.write(write_data) + print("close file" + " " +file) +``` + +需要注意的是,设备使用上述方法解密 https 的数据包,加密算法目前只能是 RSA,所以还需要强制客户端发送的加密方式(cipher suites)只能是 RSA。 + +修改`packages\mbedtls-latest\ports\inc\tls_config.h`,注释掉如下宏定义: + +```c +// #define MBEDTLS_KEY_EXCHANGE_PSK_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_DHE_PSK_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_ECDHE_PSK_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_RSA_PSK_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_ECDH_RSA_ENABLED +``` +这样就可以确保客户端和服务器只使用 RSA 的加密方式进行通信, 但是部分服务器不支持 RSA 的方式,握手过程会失败。 + +将`udpcli_send`函数添加到`mbedtls_ssl_derive_keys`函数中,如下所示 +```c + MBEDTLS_SSL_DEBUG_MSG( 3, ( "ciphersuite = %s", + mbedtls_ssl_get_ciphersuite_name( session->ciphersuite ) ) ); + MBEDTLS_SSL_DEBUG_BUF( 3, "master secret", session->master, 48 ); + MBEDTLS_SSL_DEBUG_BUF( 4, "random bytes", handshake->randbytes, 64 ); + MBEDTLS_SSL_DEBUG_BUF( 4, "key block", keyblk, 256 ); + + //replace your ip address + udpcli_send("192.168.123.206", handshake->randbytes, 32, session->master, 48); + + mbedtls_zeroize( handshake->randbytes, sizeof( handshake->randbytes ) ); +``` + +windows 运行 python 脚本(注意修改sslkey.log的文件路径) + +``` +python udpserver.py +``` + +设备联网成功后,在 MSH 终端输入 + +``` + \ | / +- RT - Thread Operating System + / | \ 4.0.1 build Apr 2 2019 + 2006 - 2019 Copyright by rt-thread team +lwIP-2.0.2 initialized! +[I/SAL_SOC] Socket Abstraction Layer initialize success. + +........... +msh /mnt/sdcard> +msh /mnt/sdcard> +msh /mnt/sdcard> +msh /mnt/sdcard>wget https://www.rt-thread.com/service/rt-thread.txt 1.txt +``` +## wireshark抓包 + +加密的数据包 + +![](./figures/wireshark_encrypt.png) + +解密的数据包 + +![](./figures/wireshark_decode.png) + +## 分析tls数据包 + +### 查看客户端随机数 +![](./figures/client_hello.png) + +### 查看服务端随机数 +![](./figures/server_hello.png) + +### 查看http请求 +![](./figures/http.png) diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/1.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/1.png new file mode 100644 index 0000000000000000000000000000000000000000..0b39566a6849fa3ea2a74ee13c2105294897b7b2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/2.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/2.png new file mode 100644 index 0000000000000000000000000000000000000000..94fe8c138649030d47f2ce5a3c91f18f1907144c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/3.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/3.png new file mode 100644 index 0000000000000000000000000000000000000000..757ce80596b52972d943dcd144ee9c337a3a6a57 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/client_hello.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/client_hello.png new file mode 100644 index 0000000000000000000000000000000000000000..41018bebb279ea8610047b34601f813c71d9bb37 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/client_hello.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/flow.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/flow.png new file mode 100644 index 0000000000000000000000000000000000000000..5ad9b1c8a1be7268a408dd8185f57c48125361ff Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/flow.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/handshake.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/handshake.png new file mode 100644 index 0000000000000000000000000000000000000000..ab7487362e335956458bf60659dcd41a533f1bc1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/handshake.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/http.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/http.png new file mode 100644 index 0000000000000000000000000000000000000000..23cbbbeec9e8991cf76174a7198a37a445da7f92 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/http.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/server_hello.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/server_hello.png new file mode 100644 index 0000000000000000000000000000000000000000..d81faec3c0993851bf6dab6d02ab7a2594f8b63c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/server_hello.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/system.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/system.png new file mode 100644 index 0000000000000000000000000000000000000000..5185e36329c6f93a2d88fde72a51a054572312a7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/system.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_config.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_config.png new file mode 100644 index 0000000000000000000000000000000000000000..94fe8c138649030d47f2ce5a3c91f18f1907144c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_decode.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_decode.png new file mode 100644 index 0000000000000000000000000000000000000000..3c34c02928d70449879c99d723978e4a9d7a7656 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_decode.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_encrypt.png b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_encrypt.png new file mode 100644 index 0000000000000000000000000000000000000000..92bbe58a8e603831dca47b08a08ec80bb6800d8c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/mbedtls_wireshark_sniffer/figures/wireshark_encrypt.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/netutils/an0018-system-netutils.md b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/an0018-system-netutils.md new file mode 100644 index 0000000000000000000000000000000000000000..9abaac3c23b2af48708f222e27aea3c8b5aa2f8d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/an0018-system-netutils.md @@ -0,0 +1,359 @@ +# 网络工具集 (NetUtils) 使用指南 + +本文介绍 RT-Thread NetUtils 的使用方法,帮助开发者更好地使用 RT-Thread NetUtils 组件来解决网络开发过程中遇到的问题。 + +## 简介 + +在进行网络相关的产品开发和调试时,一些好用的小工具往往能取到事半功倍的效果。 RT-Thread NetUtils 组件基于此应用场景,开发和封装了一系列简洁好用的网络工具集合,为开发者提供便利。 + +为了方便用户开发网络应用,RT-Thread 将常用的网络工具制作 NetUtils 组件包,通过 Env 动态配置,即开即用, 有效降低资源的占用。 + +## NetUtils 组件简介 + +RT-Thread NetUtils 作为网络工具合集,既有用于测试调试的 Ping 命令, 同步时间的 NTP 工具, 性能和带宽测试的 Iperf 、 NetIO,还有在嵌入式系统中广泛使用的轻量级文件传输工具 TFTP,方便地通过网络完成两个设备间的文件互传。另外, RT-Thread 还针对开发中的实际问题,提供了一些高级的辅助工具,如可以远程登录到 RT-Thread Finsh/MSH Shell 的 Telnet 工具,以及基于 lwIP 的网络抓包工具 tcpdump。 + +下面是 RT-Thread NetUtils 的分类和简介: + +| **名称** | **分类** | **描述** | +|:--|:--:|:--| +| Ping | 调试测试 | 利用 “ping” 命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障 | +| NTP | 时间同步 | 网络时间协议 | +| TFTP | 文件传输 | TFTP 是一个传输文件的简单协议,比 FTP 还要轻量级 | +| Iperf | 性能测试 | 测试最大 TCP 和 UDP 带宽性能,可以报告带宽、延迟抖动和数据包丢失 | +| NetIO | 性能测试 | 测试网络的吞吐量的工具 | +| Telnet | 远程访问 | 可以远程登录到 RT-Thread 的 Finsh/MSH Shell| +| tcpdump | 网络调试 | tcpdump 是 RT-Thread 基于 lwIP 的网络抓包工具 | + +每个小工具可使用 menuconfig 独立控制启用 / 停用,并提供了 Finsh/MSH 的使用命令。 +首先打开 Env 工具,进入 BSP 目录,在 Env 命令行输入 menuconfig 进入配置界面配置工程,根据需求选择合适的 NetUtils 功能,如图所示 + +> [!NOTE] +> 注:Ping 和 TFTP 依赖于 lwIP,需要先开启 lwIP 的依赖后才能显示 + +```c +RT-Thread online packages + -> IoT - internet of things + -> netutils: Networking utilities for RT-Thread +``` + +![Env 配置](./figures/env_config.png) + +## Ping 工具 + +[Ping](https://baike.baidu.com/item/ping/6235) 是一种网络诊断工具,用来测试数据包能否通过 IP 协议到达特定主机。估算与主机间的丢失数据包率(丢包率)和数据包往返时间(网络时延,Round-trip delay time) + +Ping 工具依赖 lwIP,需要先在 Env 工具 开启 lwIP 的依赖才可见,步骤如下: + +```c +-> RT-Thread Components + -> Network stack + -> light weight TCP/IP stack + -> Enable lwIP stack +``` + +在 NetUtils 菜单栏使能 Ping 选项: + +```c +RT-Thread online packages + -> IoT - internet of things + -> netutils: Networking utilities for RT-Thread + [*] Enable Ping utility +``` + +Ping 支持访问 `IP 地址 ` 或 ` 域名 ` ,使用 Finsh/MSH 命令进行测试,大致使用效果如下: + +- Ping 域名 + +``` +msh />ping rt-thread.org +60 bytes from 116.62.244.242 icmp_seq=0 ttl=49 time=11 ticks +60 bytes from 116.62.244.242 icmp_seq=1 ttl=49 time=10 ticks +60 bytes from 116.62.244.242 icmp_seq=2 ttl=49 time=12 ticks +60 bytes from 116.62.244.242 icmp_seq=3 ttl=49 time=10 ticks +msh /> + +``` +- Ping IP + +``` +msh />ping 192.168.10.12 +60 bytes from 192.168.10.12 icmp_seq=0 ttl=64 time=5 ticks +60 bytes from 192.168.10.12 icmp_seq=1 ttl=64 time=1 ticks +60 bytes from 192.168.10.12 icmp_seq=2 ttl=64 time=2 ticks +60 bytes from 192.168.10.12 icmp_seq=3 ttl=64 time=3 ticks +msh /> + +``` + +## NTP 工具 + +[NTP](https://baike.baidu.com/item/NTP) 是网络时间协议 (Network Time Protocol),它是用来同步网络中各个计算机时间的协议。 +在 RT-Thread 上实现了 NTP 客户端,连接上网络后,可以获取当前 UTC 时间,并更新至 RTC 中。 + +在 NetUtils 菜单栏使能 NTP 选项: + +```c +RT-Thread online packages + -> IoT - internet of things + -> netutils: Networking utilities for RT-Thread + [*] Enable NTP(Network Time Protocol) client +``` + +### 获取 UTC 时间 + +[UTC 时间](https://baike.baidu.com/item/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6/787659?fromtitle=UTC&fromid=5899996) 又称世界统一时间、世界标准时间、国际协调时间。北京时间为 UTC+8 时间,比 UTC 时间多 8 小时,或者理解为早 8 小时。 + +获取 UTC 时间函数原型为:`time_t time_t ntp_get_time(void)`,返回值大于 0 则获取时间成功,等于 0 失败。 + +示例代码: + +```c +#include + +void main(void) +{ + time_t cur_time; + + cur_time = ntp_get_time(); + + if (cur_time) + { + rt_kprintf("NTP Server Time: %s", ctime((const time_t*) &cur_time)); + } +} +``` + +### 获取本地时间 + +本地时间比 UTC 时间多了时区的概念,例如:北京时间为东八区,比 UTC 时间多 8 个小时。在 `menuconfig` 中可以设置当前时区,默认为 `8`。 + +获取本地时间函数原型为: `time_t ntp_get_local_time(void)`,返回值大于 0 则获取时间成功,等于 0 失败。该 API 使用方法与 `ntp_get_time()` 类似。 + +### 同步本地时间至 RTC + +如果开启 RTC 设备,还可以使用下面的命令及 API 同步 NTP 的本地时间至 RTC 设备。 + +Finsh/MSH 命令效果如下: + +``` +msh />ntp_sync +Get local time from NTP server: Sat Feb 10 15:22:33 2018 +The system time is updated. Timezone is 8. +msh /> +``` + +同步本地时间至 RTC 函数原型为:`time_t ntp_sync_to_rtc(void)`,返回值大于 0 成功,等于 0 失败。 + +> [!NOTE] +> NTP API 方法执行时会占用较多的线程堆栈,使用时保证堆栈空间充足(≥1.5K)。 +> +> NTP API 方法 **不支持可重入**,并发使用时,请注意加锁。 + +## TFTP 工具 + +[TFTP](https://baike.baidu.com/item/TFTP) (Trivial File Transfer Protocol, 简单文件传输协议)是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务,端口号为**69**,比传统的 FTP 协议要轻量级很多,适用于小型的嵌入式产品上。 + +RT-Thread 目前支持的是 TFTP 服务器。 + +TFTP 工具依赖 lwIP,需要先在 Env 工具 开启 lwIP 的依赖才可见,步骤如下: +```c +-> RT-Thread Components + -> Network stack + -> light weight TCP/IP stack + -> Enable lwIP stack +``` +在 NetUtils 菜单栏使能 TFTP 选项: +```c +RT-Thread online packages + -> IoT - internet of things + -> netutils: Networking utilities for RT-Thread + [*] Enable TFTP(Trivial File Transfer Protocol) server +``` + +- 安装 TFTP 客户端 + +安装文件位于 `netutils/tools/Tftpd64-4.60-setup.exe` ,使用 TFTP 前,请先安装该软件。 + +- 启动 TFTP 服务器 + +在传输文件前,需要在 RT-Thread 上使用 Finsh/MSH 命令来启动 TFTP 服务器,大致效果如下: + +``` +msh />tftp_server +TFTP server start successfully. +msh /> +``` + +### 传输文件 + +打开刚安装的 `Tftpd64` 软件,按如下操作进行配置: + +1、选择 `Tftp Client` ; + +2、在 `Server interfaces` 下拉框中,务必选择好与 RT-Thread 处于同一网段的网卡; + +3、填写 TFTP 服务器的 IP 地址。可以在 RT-Thread 的 MSH 下使用 `ifconfig` 命令查看; + +4、填写 TFTP 服务器端口号,默认: `69` + +![tftpd config](./figures/tftpd_cfg.png) + +### 发送文件到 RT-Thread + +1、在 `Tftpd64` 软件中,选择好要发送文件; + +2、`Remote File` 是服务器端保存文件的路径(包括文件名),选项支持相对路径和绝对路径。由于 RT-Thread 默认开启 `DFS_USING_WORKDIR` 选项,此时相对路径是基于 Finsh/MSH 当前进入的目录。所以,使用相对路径时,务必提前切换好目录; + +3、点击 `Put` 按钮即可。 + +如下图所示,将文件发送至 Finsh/MSH 当前进入的目录下,这里使用的是**相对路径**: + +![tftpd get](./figures/tftpd_put.png) + +> [!NOTE] +> 注:如果 `DFS_USING_WORKDIR` 未开启,同时 `Remote File` 为空,文件会将保存至根路径下。 + +### 从 RT-Thread 接收文件 + +1、在 `Tftpd64` 软件中,填写好要接收保存的文件路径(包含文件名); + +2、`Remote File` 是服务器端待接收回来的文件路径(包括文件名),选项支持相对路径和绝对路径。由于 RT-Thread 默认开启 `DFS_USING_WORKDIR` 选项,此时相对路径是基于 Finsh/MSH 当前进入的目录。所以,使用相对路径时,务必提前切换好目录; + +3、点击 `Get` 按钮即可。 + + +如下所示,将 `/web_root/image.jpg` 保存到本地,这里使用的是**绝对路径**: + +``` +msh /web_root>ls ## 查看文件是否存在 +Directory /web_root: +image.jpg 10559 +msh /web_root> +``` +![tftpd put](./figures/tftpd_get.png) + +## Iperf 工具 + +[Iperf](https://baike.baidu.com/item/iperf) 是一个网络性能测试工具。Iperf 可以测试最大 TCP 和 UDP 带宽性能,具有多种参数和 UDP 特性,可以根据需要调整,可以报告带宽、延迟抖动和数据包丢失。 + +在 NetUtils 菜单栏使能 Iperf 选项: +```c +RT-Thread online packages + -> IoT - internet of things + -> netutils: Networking utilities for RT-Thread + [*] Enable iperf-liked network performance tool +``` + +Iperf 使用的是主从式架构,即一端是服务器,另一端是客户端,我们提供的 Iperf 软件包实现了 TCP 服务器模式和客户端模式,暂不支持 UDP 测试。下面将具体讲解 2 种模式的使用方法。 + +### Iperf 服务器模式 + +#### 获取 IP 地址 + +需要在 RT-Thread 上使用 Finsh/MSH 命令来获取 IP 地址,大致效果如下: + +``` +msh />ifconfig +network interface: e0 (Default) +MTU: 1500 +MAC: 00 04 9f 05 44 e5 +FLAGS: UP LINK_UP ETHARP +ip address: 192.168.12.71 +gw address: 192.168.10.1 +net mask : 255.255.0.0 +dns server #0: 192.168.10.1 +dns server #1: 223.5.5.5 +``` +记下获得的 IP 地址 192.168.12.71(按实际情况记录) + +#### 启动 Iperf 服务器 + +需要在 RT-Thread 上使用 Finsh/MSH 命令来启动 Iperf 服务器,大致效果如下: + +``` +msh />iperf -s -p 5001 +``` + +-s 表示作为服务器启动 +-p 表示监听 5001 端口 + +- 安装 JPerf 测试软件 + +安装文件位于 `netutils/tools/jperf.rar` ,这个是绿色软件,安装实际上是解压的过程,解压到新文件夹即可。 + +- 进行 jperf 测试 + +打开 `jperf.bat` 软件,按如下操作进行配置: + +1、 选择 `Client` 模式; + +2、 输入刚刚获得的 IP 地址 192.168.12.71(按实际地址填写); + +3、 修改端口号为 5001; + +4、 点击 `run Lperf!` 开始测试; + +5、 等待测试结束。测试时,测试数据会在 shell 界面和 JPerf 软件上显示。 + +![iperf server](./figures/iperfc.png) + +### Iperf 客户端模式 + +- 获取 PC 的 IP 地址 + +在 PC 的命令提示符窗口上使用 ipconfig 命令获取 PC 的 IP 地址,记下获得的 PC IP 地址为 192.168.12.45(按实际情况记录)。 + +- 安装 JPerf 测试软件 + +安装文件位于 `netutils/tools/jperf.rar` ,这个是绿色软件,安装实际上是解压的过程,解压到新文件夹即可。 + +- 开启 jperf 服务器 + +打开 `jperf.bat` 软件,按如下操作进行配置: + +1、 选择 `Server` 模式 + +2、 修改端口号为 5001 + +3、 点击 `run Lperf!` 开启服务器 + +- 启动 Iperf 客户端 + +需要在 RT-Thread 上使用 Finsh/MSH 命令来启动 Iperf 客户端,大致效果如下: + +``` +msh />iperf -c 192.168.12.45 -p 5001 +``` +-c 表示作为客户端启动,后面需要加运行服务器端的 pc 的 IP 地址 +-p 表示连接 5001 端口 +等待测试结束。测试时,测试数据会在 shell 界面和 JPerf 软件上显示。 + +![iperf client](./figures/iperfs.png) + +## 其他网络调试工具的介绍和使用 +除了上述常用的网络工具,RT-Thread 也提供一些开发调试中比较实用的网络工具,如 NetIO 工具、 Telnet 工具和 tcpdump 工具。 + +### NetIO 工具 +[NetIO](http://www.nwlab.net/art/netio/netio.html) 用于在 OS/2 2.x 、 Windows 、 Linux 和 Unix 上进行网络性能测试的工具。它会通过 TCP/UDP 方式,使用不同大小的数据包进行网络净吞吐量测试。 + +RT-Thread 目前支持的是 NetIO TCP 服务器。 + +NetIO 的使用请参考组件目录下的 README ,此处不再赘述。 + +### Telnet 工具 + +[Telnet](https://baike.baidu.com/item/Telnet) 协议是一种应用层协议,使用于互联网及局域网中,使用虚拟终端机的形式,提供双向、以文字字符串为主的交互功能。属于 TCP/IP 协议族的其中之一,是 Internet 远程登录服务的标准协议和主要方式,常用于网页服务器的远程控制,可供用户在本地主机运行远程主机上的工作。 + +RT-Thread 目前支持的是 Telnet 服务器, Telnet 客户端连接成功后,将会远程连接到设备的 Finsh/MSH ,实现设备的远程控制。 + +Telnet 的使用请参考组件目录下的 README ,此处不再赘述。 + +### tcpdump 工具 + +tcpdump 是一款基于 RT-Thread 的捕获 IP 报文的小工具, 抓包的数据可以通过文件系统保存,或者通过 rdb 工具导入 PC,利用 wireshark 软件解析。 + +tcpdump 的使用请参考组件目录下的 README ,此处不再赘述。 + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/env_config.png b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/env_config.png new file mode 100644 index 0000000000000000000000000000000000000000..40ca4458256b1d0232e1664417351958564b2b92 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/env_config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/iperfc.png b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/iperfc.png new file mode 100644 index 0000000000000000000000000000000000000000..99422edcb8966f95eed4bb6d35cc182f68b343b6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/iperfc.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/iperfs.png b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/iperfs.png new file mode 100644 index 0000000000000000000000000000000000000000..b082ac464b552e0e1c41051ff1dccb64aeba1633 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/iperfs.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_cfg.png b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_cfg.png new file mode 100644 index 0000000000000000000000000000000000000000..6a77b8dfd268294aaa24d7af8d76d9c9075b5832 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_cfg.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_get.png b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_get.png new file mode 100644 index 0000000000000000000000000000000000000000..79609822aeef762125beccad63e823af9c62147b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_get.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_put.png b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_put.png new file mode 100644 index 0000000000000000000000000000000000000000..4a14e0ef60afaf978ca690c05e1ac70aa66f5173 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/netutils/figures/tftpd_put.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/an0034-rw007-module-using.md b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/an0034-rw007-module-using.md new file mode 100644 index 0000000000000000000000000000000000000000..96f20f8b58cec2ab036d918965eeb12ec1e3f529 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/an0034-rw007-module-using.md @@ -0,0 +1,275 @@ +# 简介 + +本文使用 STM32 Nucleo 系列开发板连接 RW007 WiFi 模块,通过运行 RT-Thread 操作系统,让开发板轻松愉快联网。 + +## STM32F401 Nucleo-64 + +STM32 Nucleo-64 是 ST 官方推出的开发板,依据搭载的 STM32 芯片型号不同(皆为 LQFP64 封装),分为众多版本,本文所使用的是带 STM32F401RE 芯片的板子 —— `STM32F401 Nucleo-64`作为本文的示例,使用其他型号的板子也可以参考本文的方法进行操作,通过 RW007 WIFI 模块方便快速联网。 + +![STM32F401 Nucleo-64 开发板](figures/en.nucleo-F4.jpg) + +### 主要特性 + +* STM32F401RET6 64 脚 MCU +* ARM Cortex-M4 内核,84MHz 主频 +* 512KB Flash, 96KB SRAM 存储容量 +* Arduino Uno 和 ST morpho 两类扩展接口 +* 板载 ST-LINK/V2-1 调试编程器、USB 调试串口 + +Nucleo 上的 Arduino 接口能让开发板与 WiFi 模块「无缝衔接」,值得一提的是,这款开发板还自带了 ST-LINK 和 USB 串口,这就意味着:只需要一根 Mini-USB 线,就能完成开发和调试工作。 + +快速入门:[Getting started with STM32 Nucleo board software development tools](https://www.st.com/resource/en/user_manual/dm00105928.pdf) + +原理图下载:[STM32 Nucleo (64 pins) schematics](https://www.st.com/resource/en/schematic_pack/nucleo_64pins_sch.zip) + +*更多相关信息资料见 ST 官网详情页:[STM32 Nucleo-64 development board with STM32F401RE MCU](https://www.st.com/zh/evaluation-tools/nucleo-f401re.html)* + +## RW007 + +RW007 是由上海睿赛德电子科技有限公司开发的高速 WiFi 模块,模块基于 Realtek RTL8710BN(Ameba Z 系列) WIFI SOC,使用 SPI/UART 与主机通信 ,支持 IEEE 802.11b/g/n 网络、 WEP/WPA/WPA2 加密方式和 STA 和 AP 模式。 + +![RW007 WiFi 模块](figures/rtt-rw007.jpg) + +### 主要特性 + +* Cortex-M4 高性能 MCU +* 可自由选择的 AT SPI 双模式,工作模式可由主机配置 +* SPI 时钟高达 30Mbps,UART 波特率高达 6Mbps。 +* SPI 模式下有效以太网带宽高达上传 1MBytes/s,下载 1MBytes/s +* 内置 Bootloader,支持固件升级、安全固件功能。 +* 支持快速连接、airkiss 配网 +* 支持存储多达 5 条连接信息 + +*更多相关信息资料见 RW007 介绍页面:[睿赛德科技推出高速Wi-Fi模块RW007:内置RT-Thread物联网操作系统](https://mp.weixin.qq.com/s/HYHoMnOhzad2m6IBS2Z-Qg)* + +由睿赛德推出的 WiFi 模块,可以说是 RT-Thread 的「亲儿子」了,操作系统原生支持,相应的网络组件、WLAN 框架都能完美兼容,在跑 RTT 的板子上使用 RW007,几乎不需要过多配置,即插即用式的使用体验,大大减轻了嵌入式开发者的工作量。 + +# 准备工作 + +在把 RW007 畅快跑起来之前,以下准备工作必不可少,你将需要: + +1. STM32 Nucleo-64 开发板(或其他支持 RTT 的板子) +2. RW007 WiFi 模块 +3. Mini-USB 连接线(连接开发板与电脑) +4. ENV 编译构建环境([安装使用说明](https://www.rt-thread.org/document/site/programming-manual/env/env/)) +5. 开发常用软件(git、Keil5、串口调试等) +6. 一颗爱折腾的心 + +![硬件准备](figures/prepare-work.jpg) + + +# 开始上路 + +RT-Thread 包含了 RW007 的软件包,用户无需自己编写驱动程序,下面以 SPI 模式(断开模块上 UART 的电阻 R5 和 R7)为例,介绍如何在 STM32F401 Nucleo-64 上驱动 RW007 模块,并完成 AP 扫描、连接等基本 WiFi 功能。 + +## 硬件连接 + +得益于 Nucleo 上的 Arduino 接口,只需把 RW007 往开发板上一插,即可完成了两者的硬件连接。显然,其他带 Arduino 接口的开发板也能直接插,就是这么简单粗暴…… + +![开发板插接模块](figures/hardware-connect.jpg) + +电路连接示意图如下: + +![电路连接示意图](figures/line-connect.png) + +各 IO 接口与功能之间的对应关系表: + +| STM32 引脚名 | 封装管脚序号 | Arduino 接口序号 | 功能 | +|--------------|--------------|------------------|-----------| +| PA5 | 5 | D13 | BOOT0/CLK | +| PA6 | 6 | D12 | MISO | +| PA7 | 7 | D11 | MOSI | +| PB6 | 22 | D10 | BOOT1/CS | +| PC7 | 39 | D9 | INT/BUSY | +| PA9 | 9 | D8 | RESET | + +**特别注意!!!** + +**关于pin 序号规则,与旧 bsp 使用封装管脚序号不同,在新的 stm32 bsp 框架中,统一采用顺序编号的方式,对 GPIO 驱动进行管理,移植旧程序时要留意。** + +pin 序号与引脚名对应关系如下表: + +| STM32 引脚名 | 管脚序号 pin | +|--------------|--------------| +| PA0 - PA15 | 0 - 15 | +| PB0 - PB15 | 16 - 31 | +| PC0 - PC15 | 32 - 47 | +| PD0 - ... | 48 - ... | + +*在 [bsp/stm32/libraries/HAL_Drivers/drv_gpio.c](https://github.com/RT-Thread/rt-thread/blob/master/bsp/stm32/libraries/HAL_Drivers/drv_gpio.c) 的 `pins[]` 数组中,能清除看到 pinmap 关系。* + +## STM32 bsp 配置(Menuconfig) + +### 步骤一:下载 RT-Thread SDK + +* Github 链接:[RT-Thread/rt-thread: RT-Thread is an open source IoT operating system from China.](https://github.com/RT-Thread/rt-thread) +* Gitee 链接:[RT-Thread/rt-thread: RT-Thread is an open source IoT operating system from China.](https://gitee.com/rtthread/rt-thread) + +打开 `rt-thread\bsp\stm32` 目录,能看到 RT-Thread 所支持的开发板型号,把 RT-Thread 在 STM32 上跑起来并不是一件难事,但在编译内核组件之前,要先对 bsp 进行简单配置(别慌,通过 Menuconfig 图形化界面即可完成)。 + +本次实验所使用的 bsp 为 `stm32f401-st-nucleo`,Github 仓库链接:[rt-thread/bsp/stm32/stm32f401-st-nucleo at master · RT-Thread/rt-thread](https://github.com/RT-Thread/rt-thread/bsp/stm32/stm32f401-st-nucleo), Gitee 仓库链接:[rt-thread/bsp/stm32/stm32f401-st-nucleo at master · RT-Thread/rt-thread](https://gitee.com/rtthread/rt-thread/tree/gitee_master/bsp/stm32/stm32f401-st-nucleo). +从 RT-Thread SDK 中分离 stm32f401-st-nucleo 分离 BSP 出来。 +进入 `rt-thread\bsp\stm32\stm32f401-st-nucleo` 文件夹,右键打开 ENV 窗口(前提是已在 windows 下搭好 ENV 环境),输入 `scons --dist`命令。 + +![通过`scons --dist`分离 BSP](figures/stm32_f401_bsp_split.gif) + +到此可以把分离出来的 BSP 拷贝到任意的目录中,进行项目的开发。 + +### 步骤二 通过 CubeMX 配置 SPI 初始化程序 + +1. 查看对应的引脚: + +![电路连接示意图](figures/line-connect.png) + +2.引脚列表 + +| STM32 引脚名 | 封装管脚序号 | Arduino 接口序号 | 功能 | +|--------------|--------------|------------------|-----------| +| PA5 | 5 | D13 | BOOT0/CLK | +| PA6 | 6 | D12 | MISO | +| PA7 | 7 | D11 | MOSI | +| PB6 | 22 | D10 | BOOT1/CS | +| PC7 | 39 | D9 | INT/BUSY | +| PA9 | 9 | D8 | RESET | + +3. CubeMX 配置 SPI + + 一般 STM32 系列的引脚分配可以通过对应 BSP 中`board\CubeMX_Config`目录下的`CubeMX_Config.ioc`打开 CubeMX 工程,进行配置 SPI1,并生成代码,保存退出即可 。 + + ![cubemx 配置](figures/cubemx_setting.png) + + + + + + ![CubeMX 配置 SPI ](figures/stm32_f401_cubeMX_Setting.gif) + + + +### 步骤三 :通过`menuconfig`配置 RW007 软件包 + +进入 `rt-thread\bsp\stm32\stm32f401-st-nucleo` 文件夹,右键打开 ENV 窗口(前提是已在 Windows 下搭好 ENV 环境),输入 `pkgs --upgrade` 更新 ENV 和软件包,再输入 `menuconfig` 进行系统配置: + +![menuconfig 界面](figures/menuconfig.png) + +附 Menuconfig 常用操作按键: + +| 按键 | ↑↓ | ←→ | Enter | 空格 | Esc | +|------ |---------|---------|-------|----------|------| +| 功能 | 列表选择 | 菜单选择 | 确认 | 选中/取消 | 后退 | + +**1. 配置开启 SPI 外设** + +开发板与模块的通讯依赖 SPI 设备,在 bsp 中已经实现了 SPI 驱动,只需在设置中打开即可使用。 进入 `Hardware Drivers Config --->` 下的 `On-chip Peripheral Drivers`,勾选 `Enable SPI BUS --->` 选项,并按回车键进入,进一步选中 `Enable SPI1 BUS`,完成配置: + +![开启 SPI 外设](figures/enable-spi.png) + + + +如果在 bsp 中的 menuconfig 中没有对应 `spi`的配置,可以通过修改 `Kconfig`文件增加对应`spi`的配置。 `Kconfig` 的路径在`board/Kconfig` ,如下面是添加 `SPI1`的配置。 + +![添加 SPI 配置](figures/kconfig_setting.png) + + + +**2. 配置 RW007 软件包** + +RT-Thread 通过软件包的形式,对 RW007 模块提供配套驱动支持,系统默认选项不包含软件包,用户需手动开启:通过 `Esc` 键回到 Menuconfig 主界面,依次进入 `RT-Thread online packages ---> `、`IoT - internet of things --->`、`Wi-Fi --->`,勾选 `rw007: SPI WIFI rw007 driver --->` 选项: + +![使用 RW007 软件包](figures/use-rw007-pkg.png) + +*RW007 软件包 Github 仓库链接:[RT-Thread-packages/rw007: RW007 (SPI Wi-Fi module) driver for RT-Thread](https://github.com/RT-Thread-packages/rw007)* + +紧接着按下 `Enter` 键进一步设置软件包参数,完成 SPI 总线和 IO 的配置,更改总线设备名称 `RW007 BUS NAME` 为 `spi1`: + +![更改 SPI 总线名称](figures/change-spi-bus-name.png) + +然后配置 SPI 控制 IO,各管脚号依次按下表填入: + +| 引脚号 | 功能 | +|-------|---------------------------------------| +| 22 | CS pin index | +| 5 | BOOT0 pin index (same as spi clk pin) | +| 22 | BOOT1 pin index (same as spi cs pin) | +| 39 | INT/BUSY pin index | +| 9 | RESET pin index | + +![配置 SPI 引脚](figures/007-pkg-config.png) + +最高 SPI 速率配置:**从 v1.1.0 版本起**,用户可以根据实际使用情况提高或降低总线速率(默认为 30MHz),为满足对通讯稳定性和传输速度的需求,建议更新至最新版本,对应的 Menuconfig 配置项如下: + +![SPI 最高速率配置](figures/spi_max_hz_config.png) + +**3. 开启 WiFi 框架** + +RW007 驱动使用了 WLAN 相关的接口,按以下选项路径打开 WiFi 框架:`RT-Thread Components --->`、`Device Drivers --->`、`Using WiFi --->`,勾选 `Using Wi-Fi framework`: + +![开启 WiFi 框架](figures/using-wifi-framework.png) + +**4. 保存 Menuconfig 配置** + +完成了上面的 3 步,bsp 配置算大功告成了,但最最重要的一步不能漏 —— 保存 Menuconfig 配置:直接一路狂按 `Esc` 键退出,在保存提示窗口中选择 `Yes` 确认即可: + +![保存 Menuconfig 配置](figures/save-menuconfig.png) + +## 编译烧写固件 + +**1. 更新本地软件包** + +根据 RT-Thread 的软件包机制,在 Menucofnig 选中了软件包后,相关代码文件并未添加到工程中。在 ENV 终端输入 `pkgs --update` 命令,便能从服务器下载所选软件包,更新到本地目录: + +![下载更新本地软件包](figures/packages-update.png) + +**2. 生成 MDK5 项目文件** + +使用 Keil IDE 可以十分方便对 STM32 程序编译和烧录,在 ENV 终端输入 `scons --target=mdk5 -s`,生成 Keil5 工程文件: + +![生成 MDK5 项目文件](figures/generate-mdk5-project.png) + +**3. 编译、下载工程** + +使用工具栏的 `Build` 按钮编译工程,出现 `0 Error(s)` 表示编译成功,将开发板连接电脑,再点击 `Download` 按钮下载固件到开发板,完成上面所有步骤后,接下来就是见证奇迹的时刻了。 + +## 运行、测试模块功能 + +下载完程序便能自动复位运行,打开串口工具(推荐使用 XShell 等交互型终端),设置参数为 `115200 8-1-N`。若系统启动正常,且开发板与模块间的通讯也没有问题,会看到如下初始化打印信息: + +``` + \ | / +- RT - Thread Operating System + / | \ 4.0.1 build Mar 7 2019 + 2006 - 2019 Copyright by rt-thread team +lwIP-2.0.2 initialized! +[I/WLAN.dev] wlan init success +[I/WLAN.lwip] eth device init ok name:w0 +[I/WLAN.dev] wlan init success +[I/WLAN.lwip] eth device init ok name:w1 + +rw007 sn: [rw0072795b24400ac48] +rw007 ver: [1.2.3] + +msh > +``` + +使用 `wifi scan` 命令扫描周边热点、`wifi join ssid password` 命令连接路由、`ifconfig` 命令查看网络配置,验证模块功能: + +![测试模块功能](figures/wifi_test.gif) + +# 常见问题与解决方法 + +* **Menuconfig 中没看到 rw007 pkg?** + + ENV 和 包索引不是最新,执行 `pkgs --upgrade` 命令更新软件包源。 + +* **Keil 编译错误?** + + 是否已经把软件包替换为 RW007 适配版本,新老版本 stm32 bsp 中 spi 接口稍有差异。 + +* **运行后串口无输出?** + + 1. 检查开发板与电脑的连接是否正常 + 2. 检查串口工具参数配置,应为 `115200, 8, 1, None` + +* **运行出现 `wspi` device not found** + + 确认 RW007 软件包中总线的设备名为 `spi1`,否则会导致设备挂载失败。 diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/007-pkg-config.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/007-pkg-config.png new file mode 100644 index 0000000000000000000000000000000000000000..c31f6d4ab2ec0f9af0650e6108bf759ebf66fbf3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/007-pkg-config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/change-spi-bus-name.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/change-spi-bus-name.png new file mode 100644 index 0000000000000000000000000000000000000000..ab6fb6d18baa39dfc779e37f681fbe25ac6638ef Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/change-spi-bus-name.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/cubemx_setting.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/cubemx_setting.png new file mode 100644 index 0000000000000000000000000000000000000000..6c04f689d9fe8ca0623565ff680aae7803a4c986 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/cubemx_setting.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/en.nucleo-F4.jpg b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/en.nucleo-F4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ff1e19eb5e5219cee3baa18bed23549292a1ce03 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/en.nucleo-F4.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/enable-spi.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/enable-spi.png new file mode 100644 index 0000000000000000000000000000000000000000..307285a32a53409485a88a10b23ec928218d6345 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/enable-spi.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/generate-mdk5-project.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/generate-mdk5-project.png new file mode 100644 index 0000000000000000000000000000000000000000..ec5f311dc6ea13902e6c03509a7d8d54259ca03a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/generate-mdk5-project.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/hardware-connect.jpg b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/hardware-connect.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ea9cc9dfd6d0b64f8f912e63a3d5842a5382a01c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/hardware-connect.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/kconfig_setting.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/kconfig_setting.png new file mode 100644 index 0000000000000000000000000000000000000000..124a8b1624c3c33f9349208e11259e0db718e6d1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/kconfig_setting.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/line-connect.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/line-connect.png new file mode 100644 index 0000000000000000000000000000000000000000..03556eb90ad9cab1c6ed48a896dcf7503c30d24e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/line-connect.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..de1b502080f8d0a3769509a5c99aaebd12e48c95 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/packages-update.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/packages-update.png new file mode 100644 index 0000000000000000000000000000000000000000..b26f8ee0afbfd04ffe3f6d3cf6fb06e9090b38d9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/packages-update.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/prepare-work.jpg b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/prepare-work.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b73350026c12c8059390925c66cff9d11f768154 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/prepare-work.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/rtt-rw007.jpg b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/rtt-rw007.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7bd7c87092153bb929ed2c2a16c8d619dea75123 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/rtt-rw007.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/save-menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/save-menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..9732759de09b638fac64bee5d0ee1286cf337ea2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/save-menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/spi_max_hz_config.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/spi_max_hz_config.png new file mode 100644 index 0000000000000000000000000000000000000000..ac7f0ee2c917f07d831d3f08e4f6cf7c91b45255 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/spi_max_hz_config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/stm32_f401_bsp_split.gif b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/stm32_f401_bsp_split.gif new file mode 100644 index 0000000000000000000000000000000000000000..59d11cdf7236b781ae555b49cdf535fc5acb166b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/stm32_f401_bsp_split.gif differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/stm32_f401_cubeMX_Setting.gif b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/stm32_f401_cubeMX_Setting.gif new file mode 100644 index 0000000000000000000000000000000000000000..ccdcb05ba30caedf55f20c1faaaa7d30b4f4f90d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/stm32_f401_cubeMX_Setting.gif differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/use-rw007-pkg.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/use-rw007-pkg.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e86e8b09e47e9171634b8f02e4f1ffb4865dbd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/use-rw007-pkg.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/using-wifi-framework.png b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/using-wifi-framework.png new file mode 100644 index 0000000000000000000000000000000000000000..13f2936f0b22bcc0e231aacef94a40d6d1be8942 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/using-wifi-framework.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/wifi_test.gif b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/wifi_test.gif new file mode 100644 index 0000000000000000000000000000000000000000..216afd1c13cfd0075f8122af9d64521ba71d2161 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/packages/rw007_module_using/figures/wifi_test.gif differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/README.md b/rt-thread-version/rt-thread-standard/application-note/setup/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/git/an0015-rtthread-setup-git.md b/rt-thread-version/rt-thread-standard/application-note/setup/git/an0015-rtthread-setup-git.md new file mode 100644 index 0000000000000000000000000000000000000000..0356a89c57eb8e955f70e574529153f34ab0a8a6 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/setup/git/an0015-rtthread-setup-git.md @@ -0,0 +1,39 @@ +# Git 安装应用笔记 # + +本文将详细介绍如何安装 Git,并将 Git 添加到 Windows 系统环境变量。 + +## 本文的目的和结构 ## + +### 本文的目的和背景 ### + +Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目,任何程序员想要很好的管理自己的项目肯定离不开 Git 。RT-Thread提供的开发辅助工具 Env 的软件包管理功能也需要 Git 提供支持。 + +### 本文的结构 ### + +本文先解释 Git 安装步骤,然后介绍了怎么添加 Git 到系统环境变量中。 + +## Git 安装 ## + +从 Git 官方网站(https://git-scm.com/download/win)下载 Git 安装包,根据自己的系统配置选择对应的安装包。 + +![Git安装](figures/git-setup.png) + +下载完成后运行安装程序,会出现如下图所示的软件安装画面,这是git的安装说明。 + +![Git安装](figures/git-setup2.png) + +点击“Next >”进入组件选择界面,在这里可以选择自己需要安装的组件,默认选择就可以,默认选择直到安装成功。 + +![Git安装](figures/git-setup3.png) + +在 Windows 环境下 Git 有两个命令行输入窗口 Git Bash 和 Git CMD,以及一个图形界面窗口 Git Gui。Git Bash是基于Git CMD,在 Git CMD 的基础上增添一些新的命令与功能。所以建议在使用的时候,用 Git Bash 更加方便。 + +![Git安装](figures/git-3.png) + +安装好 Git 后启用 Windows 命令行工具或者 Git Bash ,输入Git 命令将会显示下图所示的提示信息,说明 Git 安装成功。 + +![运行Git命令](figures/path3.png) + + + + diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-3.png b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-3.png new file mode 100644 index 0000000000000000000000000000000000000000..e4cfa3d0bd1c2d5747c35c571f32ba045e930b1e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup.png b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..cede165c3e47de796e6f022dc1e098a7eb68740e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup2.png b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup2.png new file mode 100644 index 0000000000000000000000000000000000000000..f9bdfbb649a229d5d7d0e5f6d60b0e5d5a74b1c2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup3.png b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup3.png new file mode 100644 index 0000000000000000000000000000000000000000..68a349b8fa7b4fef93a566bacdc11e01436ac40a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/git-setup3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path1.png b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path1.png new file mode 100644 index 0000000000000000000000000000000000000000..fff61bf0a99dd79689a47d559938f11058766548 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path2.png b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path2.png new file mode 100644 index 0000000000000000000000000000000000000000..c0280bdaf1f8ddd0fad3f7eda3427827af5e972b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path3.png b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path3.png new file mode 100644 index 0000000000000000000000000000000000000000..b68bb80ebaeecad31088683f83fd5a550afdec45 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/git/figures/path3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/an0020-qemu-eclipse.md b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/an0020-qemu-eclipse.md new file mode 100644 index 0000000000000000000000000000000000000000..2080ee881bdb1db5ccf4ea4a984b4cc3e10fe30c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/an0020-qemu-eclipse.md @@ -0,0 +1,81 @@ +# 使用 Eclipse 开发 RT-Thread # + +本文描述了在 Windows 平台使用 Eclipse 开发 RT-Thread qemu-vexpress-a9 BSP 工程。 + +## 简介 + +Eclipse 是跨平台的自由集成开发环境(IDE)。最初主要用来 Java 语言开发,通过安装不同的插件 Eclipse 可以支持不同的计算机语言,比如 C++ 和 Python 等开发工具。Eclipse 的本身只是一个框架平台,但是众多插件的支持使得 Eclipse 拥有其他功能相对固定的 IDE 软件很难具有的灵活性。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +* [Eclipse](https://www.eclipse.org/downloads/) + +## 步骤一 使用 scons 命令编译工程 + +打开 Env 文件夹,双击 env.exe 文件打开 Env 控制台: + +![Env 文件夹](figures/env.png) + +在 Env 控制台下切换目录,输入命令 `cd D:\repository\rt-thread\bsp\qemu-vexpress-a9` 切换到 RT-Thread 源码文件夹下的 qemu-vexpress-a9 BSP 根目录,然后输入 `scons` 命令编译工程,如果编译正确无误,会在 BSP 目录下生成 QEMU 下运行的 rtthread.elf 目标文件,调试工程需要这个文件。 + +![编译工程](figures/scons.png) + +## 步骤二 安装调试插件 + +在 Eclipse Marketplace 里下载并安装支持 QEMU 的调试插件: + +![Eclipse Marketplace](figures/2.4-1-Eclipse-Marketplace.png) + +![调试工具安装](figures/2.4-2-gnu-mcu-eclipse.png) + +## 步骤三 新建 Eclipse 工程 + +按照如下图所示步骤添加 RT-Thread 源代码到 Eclipse: + +![新建 eclipse 工程](figures/eclipse-new-project.png) + +![添加 RT-Thread 源代码](figures/project-dir.png) + +## 步骤四 新建调试项目 + +新建调试项目并配置调试参数,如下图所示步骤配置: + +![eclipse 调试配置选项](figures/2.4-3-eclipse-debug-config.png) + +![创建新的调试项目](figures/2.4-4-eclipse-debug-config.png) + +![选择调试文件](figures/2.4-5-eclipse-debug-config.png) + +![选择调试工具](figures/2.4-6-eclipse-debug-config.png) + +![选择断点](figures/2.4-7-eclipse-debug-config.png) + +## 步骤五 调试工程 + +1、调试相关参数配置好后就可以开始调试了,回到 Env 命令行界面输入 `qemu-dbg.bat` 开启调试模式: + +![启动调试](figures/qemu-dbg.png) + +这时候启动的 QEMU 虚拟机处于暂停状态,等待连接调试: + +![虚拟屏调试初始界面](figures/2.4-9-lcd-init.png) + +2、点击 eclipse 调试配置界面的 “Debug” 按钮,或者点击调试项目名称,开启 eclipse 调试界面,这时就可以对工程进行调试了: + +![开始调试](figures/2.4-10-debug.png) + +3、Eclipse 主要调试选项简介如下所示: + +![eclipse 调试选项](figures/eclipse-debug.png) + +可以使用快捷键 `Ctrl+Shift+r` 查看其他源文件内容。 + +![eclipse 调试选项](figures/open-file.png) + +## 参考资料 + +* [《Env 用户手册》](../../../../programming-manual/env/env.md) diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-1-Eclipse-Marketplace.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-1-Eclipse-Marketplace.png new file mode 100644 index 0000000000000000000000000000000000000000..dd01fb3d41f277ad67e9a9bd824df24c59e329fe Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-1-Eclipse-Marketplace.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-10-debug.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-10-debug.png new file mode 100644 index 0000000000000000000000000000000000000000..a2a8b366e5d585b1470ea1484c3b49787afc33e6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-10-debug.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-11-debug.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-11-debug.png new file mode 100644 index 0000000000000000000000000000000000000000..c6aded792bd31a4adf0ac858795f04fe17395076 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-11-debug.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-12-eclipse-debug.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-12-eclipse-debug.png new file mode 100644 index 0000000000000000000000000000000000000000..7a3bb523b33e0b55c62fac8787ce078cda83e55c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-12-eclipse-debug.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-2-gnu-mcu-eclipse.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-2-gnu-mcu-eclipse.png new file mode 100644 index 0000000000000000000000000000000000000000..964a71c782318ebd1f9857ca3ef437633d77fbff Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-2-gnu-mcu-eclipse.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-3-eclipse-debug-config.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-3-eclipse-debug-config.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe2cf0e7750ca427b5a985730255c6eb46adb4d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-3-eclipse-debug-config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-4-eclipse-debug-config.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-4-eclipse-debug-config.png new file mode 100644 index 0000000000000000000000000000000000000000..cc9143e9e6541a0d59ddd52bee0c1fc446fc6263 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-4-eclipse-debug-config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-5-eclipse-debug-config.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-5-eclipse-debug-config.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2a6f76f8cbc818c7c49bb13fc70ea0c289d1f9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-5-eclipse-debug-config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-6-eclipse-debug-config.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-6-eclipse-debug-config.png new file mode 100644 index 0000000000000000000000000000000000000000..9401e286e848cb3e8ed7f54013ca12e6abf63d31 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-6-eclipse-debug-config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-7-eclipse-debug-config.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-7-eclipse-debug-config.png new file mode 100644 index 0000000000000000000000000000000000000000..773ad344d7daf59f437b923672fdd8f6e9096f70 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-7-eclipse-debug-config.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-9-lcd-init.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-9-lcd-init.png new file mode 100644 index 0000000000000000000000000000000000000000..59f9a8274634f7332550c9897a9c784ba15c0425 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/2.4-9-lcd-init.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/eclipse-debug.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/eclipse-debug.png new file mode 100644 index 0000000000000000000000000000000000000000..b4e61f24eb86f7647630d9bd3419ef20ed124cdb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/eclipse-debug.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/eclipse-new-project.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/eclipse-new-project.png new file mode 100644 index 0000000000000000000000000000000000000000..65866cff756a1d51eb95f82847136a6c498e13fb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/eclipse-new-project.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/env.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/env.png new file mode 100644 index 0000000000000000000000000000000000000000..4794a0b3ac9c35a5996b4e2ca54098e960d41694 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/env.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/open-file.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/open-file.png new file mode 100644 index 0000000000000000000000000000000000000000..d727d39196aba85d32aaf2bf1b2e5f7a26f3b9b8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/open-file.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/project-dir.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/project-dir.png new file mode 100644 index 0000000000000000000000000000000000000000..36e17204c32cb2a72748b1ada1cd1ab8f2d117e9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/project-dir.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/qemu-dbg.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/qemu-dbg.png new file mode 100644 index 0000000000000000000000000000000000000000..6e6aa328594c78df7df0c110e6595de5d6b17e9f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/qemu-dbg.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/scons.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/scons.png new file mode 100644 index 0000000000000000000000000000000000000000..3365797484214e3aa54ffc5562e005e90649d112 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/eclipse/figures/scons.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/an0005-qemu-ubuntu.md b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/an0005-qemu-ubuntu.md new file mode 100644 index 0000000000000000000000000000000000000000..827d17c3d87d7e0747f5355de5d14e347fb77266 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/an0005-qemu-ubuntu.md @@ -0,0 +1,118 @@ +# 在 Ubuntu 平台开发 RT-Thread + +本文描述了如何在 Ubuntu 平台使用 QEMU 运行 RT-Thread qemu-vexpress-a9 BSP 工程。 + +## 简介 + +嵌入式软件开发离不开开发板,在没有物理开发板的情况下,可以使用 QEMU 等类似的虚拟机来模拟开发板。QEMU 是一个支持跨平台虚拟化的虚拟机,它可以虚拟很多开发板。为了方便大家在没有开发板的情况下体验 RT-Thread,RT-Thread 提供了 QEMU 模拟的 ARM vexpress A9 开发板的板级支持包 (BSP)。 + +本文主要介绍在 Ubuntu 平台使用 QEMU 运行 RT-Thread qemu-vexpress-a9 BSP 工程。 + +## 准备工作 + +### 安装环境 + +* 下载 RT-Thread 源码,使用命令: + +``` +git clone https://github.com/RT-Thread/rt-thread.git +``` + +* 安装 QEMU,使用命令: + +``` +sudo apt-get install qemu +``` + +* 安装 Scons,使用命令: + +``` +sudo apt-get install scons +``` + +* 安装编译器。使用 apt-get 命令安装的编译器版本太旧会导致编译报错,可依次使用如下命令下载安装新版本,下载链接和解压文件夹名因下载版本而异: + +``` +tom@laptop:~$ cd /tmp/ +tom@laptop:/tmp$ wget https://armkeil.blob.core.windows.net/developer/Files/downloads/gnu-rm/6-2016q4/gcc-arm-none-eabi-6_2-2016q4-20161216-linux.tar.bz2 +tom@laptop:/tmp$ tar xf ./gcc-arm-none-eabi-6_2-2016q4-20161216-linux.tar.bz2 +tom@laptop:/tmp$ mv gcc-arm-none-eabi-6_2-2016q4/ /opt/ +tom@laptop:/tmp$ /opt/gcc-arm-none-eabi-6_2-2016q4/bin/arm-none-eabi-gcc --version +``` + +以上命令下载编译器,并安装到 `/opt/gcc-arm-none-eabi-6_2-2016q4/` 。如果最后输出结果如下,则安装完成: + +``` +arm-none-eabi-gcc (GNU Tools for ARM Embedded Processors) 6.2.1 20161205 (release) [ARM/embedded-6-branch revision 243739] +Copyright (C) 2016 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +``` + +* 安装 ncurses 库,使用命令: + +``` +sudo apt-get install libncurses5-dev +``` + +### 配置 rtconfig.py + +编译器安装好以后需要修改源码中的 `bsp/qemu-vexpress-a9/rtconfig.py` 文件,修改 gcc 链接的路径为刚才安装路径,如将 `EXEC_PATH = '/usr/bin'` 改为: + +``` +EXEC_PATH = '/opt/gcc-arm-none-eabi-6_2-2016q4/bin' +``` + +如下: + +![编译器路径修改](figures/3.1-1-rtconfig.py.png) + +## 使用 menuconfig 配置工程 + +1、在 qemu-vexpress-a9 BSP 根目录输入 + +``` +scons --menuconfig +``` + +此命令开启配置界面,配置操作和 Window 平台一样: + +![menuconfig 配置界面](figures/3.2-1-linux-menuconfig.png) + +2、使用 `scons --menuconfig` 命令后会安装及初始化 Env 工具,并在 home 目录下面生成 “.env” 文件夹,此文件夹为隐藏文件夹,切换到 home 目录,使用 `la` 命令可查看所有目录和文件。 + +``` +$ la ~/.env +env.sh local_pkgs packages tools +``` + +运行 env.sh 会配置好环境变量,让我们可以使用 `pkgs` 命令来更新软件包,执行 + +``` +$ source ~/.env/env.sh +``` + +若已经选择了在线软件包,就可以使用 `pkgs --update` 命令下载软件包到 BSP 目录下的 packages 文件夹里: + +``` +$ pkgs --update +``` + +## 编译和运行 RT-Thread + +1、在 qemu-vexpress-a9 BSP 目录下输入 `scons` 命令编译工程: + +![scons 命令编译工程](figures/3.3-1-linux-scons.png) + +2、输入 ls 命令查看 BSP 下面的文件明细,绿色显示的文件是有执行权限的文件,我们需要给 qemu.sh 文件新增执行权限,输入 `chmod +x qemu.sh` 命令: + +![qemu.sh 文件新增执行权限](figures/3.3-2-qemu.sh.png) + +3、输入 `./qemu.sh` 命令执行脚本文件,这时候虚拟机便运行起来,如下图所示,命令行显示了 RT-Thread 操作系统启动过程所打印的相关信息,弹出的窗口为虚拟的 LCD 屏。 + +![虚拟机运行界面](figures/3.3-3-qemu.png) + +## 参考资料 + +* [《Env 用户手册》](../../../../programming-manual/env/env.md) + diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.1-1-rtconfig.py.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.1-1-rtconfig.py.png new file mode 100644 index 0000000000000000000000000000000000000000..74b56845c796821f60f59b88e8f7bebbef5c0a91 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.1-1-rtconfig.py.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-1-linux-menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-1-linux-menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..32a0ec17250da4a6e17c2b5b0940e7055994ec3d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-1-linux-menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-2-linux-env.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-2-linux-env.png new file mode 100644 index 0000000000000000000000000000000000000000..c790fcbc38fdd4e48771c7ebb4b6f37024c7ec9c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-2-linux-env.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-3-pkgs--update.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-3-pkgs--update.png new file mode 100644 index 0000000000000000000000000000000000000000..714dcc6b473d13752200592847dd62e1867058d3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.2-3-pkgs--update.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-1-linux-scons.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-1-linux-scons.png new file mode 100644 index 0000000000000000000000000000000000000000..5beecb66da315509d69e924dc77b7f39cc1b0574 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-1-linux-scons.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-2-qemu.sh.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-2-qemu.sh.png new file mode 100644 index 0000000000000000000000000000000000000000..3648d7fec5796ecc87112f3ad9903d56a3c2b717 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-2-qemu.sh.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-3-qemu.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-3-qemu.png new file mode 100644 index 0000000000000000000000000000000000000000..b02740a4dbb9dcaa3c366695024b6ccb7a616005 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/ubuntu/figures/3.3-3-qemu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/an0021-qemu-vscode.md b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/an0021-qemu-vscode.md new file mode 100644 index 0000000000000000000000000000000000000000..34d06e21043d0373b87987783723fe0792deecd5 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/an0021-qemu-vscode.md @@ -0,0 +1,136 @@ +# 使用 VS Code 开发 RT-Thread # + +本文描述了在 Windows 平台使用 VS Code 开发 RT-Thread qemu-vexpress-a9 BSP 工程。 + +## 简介 + +VS Code(全称 Visual Studio Code)是一个轻量且强大的代码编辑器,支持 Windows,OS X 和 Linux。内置 JavaScript、TypeScript 和 Node.js 支持,而且拥有丰富的插件生态系统,可通过安装插件来支持 C++、C#、Python、PHP 等其他语言。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +* [VS Code](https://code.visualstudio.com/Download) + +## 步骤一 安装调试插件 + +在 VS Code Extensions 里下载并安装支持 C/C++ 的调试插件: + +![安装 c/c++ 插件](figures/installplugin.png) + +安装好后确认插件为以下状态,如果不是则点击重新加载: + +![c/c++ 插件状态](figures/plugin-status.png) + +## 步骤二 打开 VS Code 项目工程 + +在 Env 控制台进入 qemu-vexpress-a9 BSP 根目录,然后输入命令 `code .` (**注意:code 后面有一个点**)打开 VS Code,表示使用 VS Code 打开当前目录。 + +![打开 Env 控制台](figures/sconsvsc.png) + +VS Code 打开后会自动打开 qemu-vexpress-a9 BSP 文件夹,如下图所示。 + +![打开 VS Code](figures/open.png) + +## 步骤三 编译 RT-Thread + +点击 VS Code “查看 -> 终端” 打开 VS Code 内部终端,在终端里输入命令 `scons` 即可编译工程,终端会打印出编译信息。 + +![编译工程](figures/vsscons.png) + +编译完成后输入 `.\qemu.bat` 命令就可以运行工程。终端会输出 RT-Thread 启动 logo 信息,QEMU 也运行了起来。 + +![运行工程](figures/vscode-run.png) + +> [!NOTE] +> 注:1、调试 BSP 工程前需要先编译工程生成 rtthread.elf 文件。 + 2、可以使用 `scons --target=vsc -s` 命令更新 VS Code 需要用到的 C/C++ 头文件搜索路径信息。不是每次都需要更新,只有在使用了 menuconfig 重新配置了 RT-Thread 或更改了 rtconfig.h 头文件时才需要。 + +## 步骤四 修改 qemu-dbg.bat 文件 + +开始调试前需要编辑 `qemu-vexpress-a9` 目录下的 `qemu-dbg.bat` 文件,在 qemu-system-arm 前加入 start : + +```bash +@echo off +if exist sd.bin goto run +qemu-img create -f raw sd.bin 64M + +:run +start qemu-system-arm -M vexpress-a9 -kernel rtthread.elf -serial stdio -sd sd.bin -S -s + +``` + +## 步骤五 调试工程 + +如下图所示,在 VS Code 里点击调试菜单(小虫子图标),调试平台选择 Windows,然后按 F5 就可以开启 QEMU 调试模式,断点停留在 main 函数。VS Code 调试选项如下图所示: + +![Env 调试界面](figures/debug-set.png) + +QEMU 也运行了起来,如下图所示。 + +![qemu 调试界面](figures/dbg.png) + +在 VS Code 里可以使用 GDB 命令,需要在最前面加上 `-exec`。 例如 `-exec info registers` 命令可以查看寄存器的内容: + +![gdb 查看内存](figures/register.png) + +其他一些主要命令介绍如下所示: + +查看内存地址内容:`x/ `,各个参数说明如下所示: + +* n 是一个正整数,表示需要显示的内存单元的个数,也就是说从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的 u 定义 +* f 表示显示的格式,参见下面。如果地址所指的是字符串,那么格式可以是 s。其他格式如下表所示: + +| 参数 | 描述 | +| ------------- | --------------- | +| x | 按十六进制格式显示变量 | +| d | 按十进制格式显示变量 | +| u | 按十六进制格式显示无符号整型 | +| o | 按八进制格式显示变量 | +| t | 按二进制格式显示变量 | +| a | 按十六进制格式显示变量 | +| c | 按字符格式显示变量 | +| f | 按浮点数格式显示变量 | + +* u 表示从当前地址往后请求的字节数,如果不指定的话,GDB 默认是 4 个 bytes。u 参数可以用下面的字符来代替,b 表示单字节,h 表示双字节,w 表示四字 节,g 表示八字节。当我们指定了字节长度后,GDB 会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。 +* addr 表示一个内存地址。 + +> [!NOTE] +> 注:严格区分 n 和 u 的关系,n 表示单元个数,u 表示每个单元的大小。 + +示例: `x/3uh 0x54320` 表示从内存地址 0x54320 读取内容,h 表示以双字节为一个单位,3 表示输出三个单位,u 表示按十六进制显示。 + +查看当前程序栈的内容: x/10x $sp--> 打印 stack 的前 10 个元素 +查看当前程序栈的信息: info frame----list general info about the frame +查看当前程序栈的参数: info args---lists arguments to the function +查看当前程序栈的局部变量: info locals---list variables stored in the frame +查看当前寄存器的值:info registers(不包括浮点寄存器) info all-registers(包括浮点寄存器) +查看当前栈帧中的异常处理器:info catch(exception handlers) + +> 提示:输入命令时可以只输入每个命令的第一个字母。例如:`info registers` 可以只输入 `i r`。 + +> [!NOTE] +> 注:如果在 VS Code 目录中额外添加了文件夹,会导致调试不能够启动。 + 每次开始调试都需要使用 Env 工具在 BSP 根目录使用`code .`命令打开 VS Code 才能正常调试工程。 + +## 参考资料 + +* [《Env 用户手册》](../../../../programming-manual/env/env.md) + +## 常见问题 + +### Env 工具的相关问题请参考《Env 用户手册》常见问题小节。 + +### Q: 提示找不到 ‘qemu-system-arm’。 + +**A:** 直接打开 VS Code 调试工程会有这个错误,**每次调试请使用 Env 工具在 BSP 根目录使用`code .`命令打开 VS Code** 。 + +### Q: VS Code 调试选项没有出现 Debug@windows选项或者其他不能调试问题。 + +**A:** 请更新 RT-Thread 源代码到 v3.1.0 及以上版本。 + +### Q: VS Code 出现错误提示:Unable to start debugging.Unexpected GDB output from command "-interpreter-exec console" ........ + +**A:** 请修改 qemu-dbg.bat 文件,特别是有更新源代码的情况下。有问题请按照文档步骤检查一遍是否每一步都做了。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/dbg.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/dbg.png new file mode 100644 index 0000000000000000000000000000000000000000..0cbd4f19066bf72c7703bf0582ebb620446ea325 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/dbg.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/debug-set.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/debug-set.png new file mode 100644 index 0000000000000000000000000000000000000000..4e1fa42d83b9c89924655e703f9d0a917b5eddca Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/debug-set.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/installplugin.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/installplugin.png new file mode 100644 index 0000000000000000000000000000000000000000..61c38e048a89c47b500451c9dd7a2d2082663d8e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/installplugin.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/open.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/open.png new file mode 100644 index 0000000000000000000000000000000000000000..e9495739f649c590576ade0cb3f78502e5c74c90 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/open.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/plugin-status.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/plugin-status.png new file mode 100644 index 0000000000000000000000000000000000000000..d2050a4d86eac14ba58e58d14806e3b7d305cacd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/plugin-status.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/register.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/register.png new file mode 100644 index 0000000000000000000000000000000000000000..29dc8e82c5bb984df31281707a6b696ad66c211f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/register.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/sconsvsc.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/sconsvsc.png new file mode 100644 index 0000000000000000000000000000000000000000..3df2c87a92b0a0ba16928d9f99196100335d6b55 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/sconsvsc.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/vscode-run.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/vscode-run.png new file mode 100644 index 0000000000000000000000000000000000000000..2b5d4a8c54c6990742821cb23780f776e963e10a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/vscode-run.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/vsscons.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/vsscons.png new file mode 100644 index 0000000000000000000000000000000000000000..a3c272bc0bf1378b38e60178f497a790898c01b7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/vscode/figures/vsscons.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/an0006-qemu-windows.md b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/an0006-qemu-windows.md new file mode 100644 index 0000000000000000000000000000000000000000..0e236beb151c10fd2384820ad5958280dd7909b2 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/an0006-qemu-windows.md @@ -0,0 +1,207 @@ +# 在 Window 平台使用 QEMU 运行 RT-Thread + +本文描述了如何在 Window 平台使用 QEMU 运行 RT-Thread qemu-vexpress-a9 BSP 工程。 + +## 本文的目的和结构 + +### 本文的目的和背景 + +嵌入式软件开发离不开开发板,在没有物理开发板的情况下,可以使用 QEMU 等类似的虚拟机来模拟开发板。QEMU 是一个支持跨平台虚拟化的虚拟机,它可以虚拟很多开发板。为了方便大家在没有开发板的情况下体验 RT-Thread,RT-Thread 提供了 QEMU 模拟的 ARM vexpress A9 开发板的板级支持包 (BSP)。 + +本文主要介绍在 Window 平台使用 QEMU 运行 RT-Thread qemu-vexpress-a9 BSP 工程,并介绍了如何使用虚拟网卡连接 QEMU 到网络。 + +### 本文的结构 + +本文介绍了 qemu-vexpress-a9 BSP 在 Window 平台的使用明细。 + +## 准备工作 + +* [下载 RT-Thread 源码](https://www.rt-thread.org/page/download.html),推荐下载3.1.0及以上版本。 + +* 下载 RT-Thread Env 工具,推荐下载1.0.0及以上版本。参考编程指南-《Env 用户手册了解如何下载及使用。 + +RT-Thread 提供的 QEMU 模拟的 ARM vexpress A9 开发板的板级支持包 (BSP) 位于 RT-Thread 源码 BSP 目录下的 qemu-vexpress-a9 文件夹,此 BSP 实现了 LCD、键盘、鼠标、SD 卡、以太网卡、串口等相关驱动,文件夹内容如下图所示。 + +![qemu-vexpress-a9 文件夹](figures/qemubsp.png) + +qemu-vexpress-a9 BSP 主要文件及目录描述如下所示: + +| 文件 / 目录 | 描述 | +| ------------- | --------------- | +| .vscode | vscode 配置文件 | +| applications | 用户应用代码目录 | +| cpu | 芯片相关 | +| drivers | RT-Thread 提供的底层驱动 | +| qemu.bat | Windows 平台运行脚本文件 | +| qemu.sh | Linux 平台运行脚本文件 | +| qemu-dbg.bat | Windows 平台调试脚本文件 | +| qemu-dbg.sh | Linux 平台调试脚本文件 | +| README.md | BSP 说明文件 | +| rtconfig.h | BSP 配置头文件 | + +## 编译和运行工程 + +### 步骤一 使用 scons 命令编译工程 + +打开 Env 文件夹,双击 env.exe 文件打开 Env 控制台: + +![Env 文件夹](figures/env.png) + +在 Env 控制台下切换目录,输入命令 `cd D:\repository\rt-thread\bsp\qemu-vexpress-a9` 切换到 RT-Thread 源码文件夹下的 qemu-vexpress-a9 BSP 根目录,然后输入 `scons` 命令编译工程,如果编译正确无误,会在 BSP 目录下生成 QEMU 下运行的 rtthread.elf 目标文件。 + +![编译工程](figures/scons.png) + +### 步骤二 使用 qemu.bat 命令运行工程 + +编译完成后输入 `qemu.bat` 启动虚拟机及 BSP 工程,qemu.bat 是 Window 批处理文件,此文件位于 BSP 文件夹下,主要包括 QEMU 的执行指令,第一次运行工程会在 BSP 文件夹下创建一份空白的 sd.bin 文件,这是虚拟的 sd 卡,大小为 64M。Env 命令行界面显示 RT-Thread 系统启动过程中打印的初始化信息及版本号信息等,qemu 虚拟机也运行起来了。如下面图片所示: + +![运行工程](figures/qemu.bat.png) + +![虚拟机](figures/qemu.png) + +**注意事项:**若电脑安装有 360 安全卫士会有警告,请点击允许程序运行。 + +## 运行 Finsh 控制台 + +RT-Thread 支持 Finsh,用户可以在命令行模式使用命令操作。输入 `help` 或按 tab 键可以查看所有支持的命令。如下图所示,左边为命令,右边为命令描述。 + +![查看 Finsh 命令 ](figures/finsh-cmd.png) + +如下图所示,比如输入`list_thread`命令可以查看当前运行的线程,以及线程状态和堆栈大小等信息。输入`list_timer`可以查看定时器的状态。 + +![查看系统线程情况 ](figures/finsh-thread.png) + +## 运行文件系统 + +输入 `list_device` 可以查看注册到系统的所有设备。如下面图片所示可以看到虚拟的 sd 卡 “sd0” 设备,接下来我们可以使用 `mkfs sd0` 命令格式化 sd 卡,执行该命令会将 sd 卡格式化成 FatFS 文件系统。FatFs 是专为小型嵌入式设备开发的一个兼容微软 fat 的文件系统,采用 ANSI C 编写,采用抽象的硬件 I/O 层以及提供持续的维护,因此具有良好的硬件无关性以及可移植性。 + +了解 FatFS 详细信息请点击链接: + +![格式化 sd 卡](figures/mkfs-sd0.png) + +第一次格式化 sd 卡后文件系统不会马上装载上,第二次启动才会被正确装载。我们退出虚拟机,然后在 Env 命令行界面输入 `qemu.bat` 重新启动虚拟机及工程,输入 `ls` 命令可以看到新增了 Directory 目录,文件系统已经装载上,然后可以使用 RT-Thread 提供的其他命令体验文件系统 + +![文件系统其他命令](figures/echo-cat.png) + +## 运行网络功能 + +### 步骤一 安装和配置 TAP 网卡 + +1、下载 TAP 网卡 [tap-windows-9.21.2.exe](https://pan.baidu.com/s/1h2BmdL9myK6S0g8TlfSW0g)。下载好后双击安装程序,默认安装即可。 + +2、打开网络和共享中心更改适配器设置,将安装的虚拟网卡重命名为 tap,如下图所示: + + ![tap_rename](figures/tap_rename.png) + +**方法A:**右键当前能上网的网络连接(本文使用以太网),打开属性 -> 共享,选择家庭网络连接为 tap,点击确定完成设置,如下图所示: + + ![tap_share_internet](figures/tap_share_internet.png) + + **方法B:**也可以将一个能正常连接网络的物理网卡与 tap 进行桥接,桥接成功后会出现一个网桥。如下面图片所示: + +![tap_桥接](./figures/tap_bridge1.png) + +![tap_桥接成功](./figures/tap_bridge2.png) + +**注意事项:** tap 网卡和 VMware 的虚拟网卡可能会冲突,如果出现无法开启网络共享,或者 ping 不通网络的情况,请在删除 VMware 虚拟网卡之后再尝试一次。 + +### 步骤二 修改 qemu.bat 脚本文件 + +打开 qemu-vexpress-a9 BSP 目录下的 qemu.bat 文件,在下图所示位置添加以下配置内容: + +`-net nic -net tap,ifname=tap` + +其中 ifname=tap 的意思是网卡的名称是 tap。 + +![qemu_modify](figures/qemu_modify.png) + +### 步骤三 查看 IP 地址 + +输入 `qemu.bat` 命令运行工程,在 shell 中输入 `ifconfig`命令查看网络状态,正常获取到 IP 即表示网络驱动正常,配置工作完成,效果如下图所示: + +![ifconfig](figures/ifconfig.png) + +### 注意事项 + +* 当出现获取不到 IP 地址的情况时,先将以太网的共享关闭,然后再次打开即可。 + +* 如果获取到的 IP 是 10.0.x,x,是因为没有为 QEMU 添加启动参数 `-net nic -net tap,ifname=tap` 。 + +* 虚拟机刚开始运行的时候并不会立刻获取到 IP 地址,有时需要等待几秒钟才会获取到 IP。 + +* 关闭虚拟机可以按 Ctrl + 'C' 来结束程序运行。 + +## 运行 Ping 工具 + +### 步骤一 下载网络工具软件包 + +1、在路径`bsp\qemu-vexpress-a9`下打开 Env 工具,执行 menuconfig,如下图所示: + +![Env_menuconfig](figures/env_menuconfig.png) + +2、在 RT-Thread online packages->IoT - internet of things 页面打开 netutils: Networking utilities for RT-Thread 功能,如下图所示: + + ![online_packages](figures/online_packages.png) + +3、进入 netutils: Networking utilities for RT-Thread 页面,打开 Enable Ping utility 功能,如下图所示: + + ![enable_ping](figures/enable_ping.png) + +4、保存并退出配置界面。如果没有开启 Env 自动更新软件包功能的话,需要输入 `pkgs --update` 更新软件包配置。更新完成后使用 scons 命令重新编译工程,如下图所示: + +![scons](figures/scons-net.png) + +5、编译完成之后运行 qemu.bat 文件,如下图所示: + +![qemu_bat](figures/qemu_bat.png) + +### 步骤二 运行 ping 工具 + +在 shell 中输入 `ifconfig`命令查看网络状态,正常获取到 IP 即表示网络驱动正常: + +![ifconfig](figures/ifconfig.png) + +在 shell 中输入 ping www.rt-thread.com 可以看到 ping 通的返回结果, 表示网络配置成功,能够 ping 通,如下图所示: + +![ping](figures/ping.png) + +## 运行 GUI 引擎 + +### 步骤一 下载 GUI 引擎软件包 + +关掉 QEMU 虚拟机,回到 ENV 控制台,输入 `menuconfig` 命令进入配置界面: + +![menuconfig](figures/menuconfig.png) + +进入 “RT-Thread oneline packages” → “system packages” → “RT-Thread GUI Engine” 子菜单,选中 “Enable GUI Engine” 和“Enable the example of GUI Engine”: + +![选择 GUI 示例](figures/gui.png) + +按‘→’键选中 “save” 保存配置,并按 “Exit” 键退出配置界面,回到命令行界面,输入 `pkgs --update` 下载 GUI 软件包及示例代码: + +![下载 GUI 软件包](figures/pkgs--update.png) + +### 步骤二 运行 GUI 引擎 + +软件包下载完成后输入 `scons` 重新编译工程: + +![scons 命令编译工程](figures/scons-again.png) + +编译完成后输入 `qemu.bat` 命令启动 QEMU 虚拟机及工程,可以看到 QEMU 虚拟的显示屏上显示了示例代码展示的图片、文字和图形信息: + +![虚拟屏 GUI 示例展示](figures/2.3-5-gui.png) + +## 参考资料 + +* 《Env 用户手册》 + +* 《文件系统》 + +## 常见问题 + +* **Env 工具的相关问题请参考编程指南-《Env 用户手册》章节。** + +* **编译工程提示 fatal error: rtgui/driver.h: No such file or directory** + +> 提示:解决方法:使用 menuconfig 使能 “Enable GUI Engine”后需要使用命令`pkgs --update`下载 GUI 软件包。 + diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/2.3-5-gui.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/2.3-5-gui.png new file mode 100644 index 0000000000000000000000000000000000000000..4649339c3c14aed33d270e84cb2cd568920537e7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/2.3-5-gui.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/echo-cat.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/echo-cat.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d3e56bb3d067f69bdfad392bf4a4493aea7f12 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/echo-cat.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/enable_ping.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/enable_ping.png new file mode 100644 index 0000000000000000000000000000000000000000..68ad5cfd01d4a7f4429aeff096de4a1339765639 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/enable_ping.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/env.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/env.png new file mode 100644 index 0000000000000000000000000000000000000000..4794a0b3ac9c35a5996b4e2ca54098e960d41694 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/env.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/env_menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/env_menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..1cedd2650432e0153f5ff1a4e3f5fa8a1bd01424 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/env_menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/finsh-cmd.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/finsh-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..a5145c075c51d5da18c60f7b410a4d3d886ee293 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/finsh-cmd.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/finsh-thread.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/finsh-thread.png new file mode 100644 index 0000000000000000000000000000000000000000..5cd53699862825d53836c8ab5c9cd64325d88557 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/finsh-thread.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/gui.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/gui.png new file mode 100644 index 0000000000000000000000000000000000000000..e667396c8c6e9cfb6ef6c4325e9914f2ce1bed69 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/gui.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/ifconfig.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/ifconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..feadc74b86e43f74255f87f976f61077ef52b43e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/ifconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..213c964f831f712dfee338834cc2db4c4e765074 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/mkfs-sd0.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/mkfs-sd0.png new file mode 100644 index 0000000000000000000000000000000000000000..4bcea502b01bca5a8ebf0e1b9344097bc505fd27 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/mkfs-sd0.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/online_packages.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/online_packages.png new file mode 100644 index 0000000000000000000000000000000000000000..63391b83dab188595371b4123eac48e5e1f5a4e0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/online_packages.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/ping.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/ping.png new file mode 100644 index 0000000000000000000000000000000000000000..12ac99b24678f66e60dc57bfe827a819e2ce52c4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/ping.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/pkgs--update.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/pkgs--update.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc1116180244859166d974164a73c3c7d4bbe3c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/pkgs--update.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu-dbg.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu-dbg.png new file mode 100644 index 0000000000000000000000000000000000000000..6e6aa328594c78df7df0c110e6595de5d6b17e9f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu-dbg.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu.bat.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu.bat.png new file mode 100644 index 0000000000000000000000000000000000000000..8e6c4fc46921d8d08a31ee9f3bbe28bd0743b174 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu.bat.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu.png new file mode 100644 index 0000000000000000000000000000000000000000..bed1e4d3c75b28665c634b1e3993ede5107dbc72 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu_bat.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu_bat.png new file mode 100644 index 0000000000000000000000000000000000000000..f8306ddb134757cc7530fa67cd487b2dd8f3f1af Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu_bat.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu_modify.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu_modify.png new file mode 100644 index 0000000000000000000000000000000000000000..e014667302ccb6baf4718d7bd58f8a7d6ca9138f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemu_modify.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemubsp.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemubsp.png new file mode 100644 index 0000000000000000000000000000000000000000..2584eedc7b0eec38ab8cf522bd20a6fcdce6c4e9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/qemubsp.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons-again.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons-again.png new file mode 100644 index 0000000000000000000000000000000000000000..9d1ec2f49fdb792904c04f0d96cc08672962aa16 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons-again.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons-net.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons-net.png new file mode 100644 index 0000000000000000000000000000000000000000..6a23c03e815391be93e759ff342c3d2421e86dc4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons-net.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons.png new file mode 100644 index 0000000000000000000000000000000000000000..3365797484214e3aa54ffc5562e005e90649d112 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/scons.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_bridge1.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_bridge1.png new file mode 100644 index 0000000000000000000000000000000000000000..f025d2ba9701178b73f4acb862aa85559ad5ae48 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_bridge1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_bridge2.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_bridge2.png new file mode 100644 index 0000000000000000000000000000000000000000..a221e605aef38aa8c5121ee925fc0e1a9fefe373 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_bridge2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_rename.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_rename.png new file mode 100644 index 0000000000000000000000000000000000000000..927149b6611f0a15c185dd722545df207b7b8a86 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_rename.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_share_internet.png b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_share_internet.png new file mode 100644 index 0000000000000000000000000000000000000000..9f1c8f42605e5704ecba519e4d21a263da56700b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/qemu/windows/figures/tap_share_internet.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/rt-thread-version/an0030-rtthread-version.md b/rt-thread-version/rt-thread-standard/application-note/setup/rt-thread-version/an0030-rtthread-version.md new file mode 100644 index 0000000000000000000000000000000000000000..6be03578cea713dcaef6e4fabbfec88c673a89a6 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/setup/rt-thread-version/an0030-rtthread-version.md @@ -0,0 +1,70 @@ +# 如何选择合适的 RT-Thread 版本进行开发? + +RT-Thread 是以 Apache License v2 开源许可发布的物联网操作系统。RT-Thread 有十多年的历史,在开发过程中也放在 Github 上由大家协同开发,并发布一个个版本,导致了不同人群面对多样的版本无从下手,此文档将说明如何选择一个合适自己的版本进行开发。 + +RT-Thread 的版本 / 分支有以下几种可供选择: + +开发分支(master 主分支)、长期支持分支(lts-v3.1.x 分支)、发布版本(release),**推荐用户使用已发布的版本**。 + +## RT-Thread 分支与版本介绍 + +**分支情况**:迄今为止,RT-Thread 已经存在的分支有: + +- stable-v1.2.x(已不维护) +- stable-v2.0.x(已不维护) +- stable-v2.1.x(已不维护) +- stable-v3.0.x(已不维护) +- lts-v3.1.x(长期支持、维护) +- master(master 主分支是 RT-Thread 开发分支,一直活跃) + +当有较大的版本变化时(如 3.0.x 更新为 3.1.x,或者是主版本变化时),此时会在 master 分支上建立一个新分支对旧版本进行维护。 + +**版本发布**:RT-Thread 已发布版本众多,如 3.1.1、3.1.2、4.0.0 发布版等等。新版本是基于 master 主分支或者基于正在维护的分支进行发布的。 + +例如长期支持分支 lts-v3.1.x,最新发布版是 3.1.2,之后还可能会发布 3.1.3、3.1.4、... 等版本,但是该分支上不会发布 3.2.x 版本。 + +例如当前 master 分支的版本是 4.0.1,最新发布版本是 4.0.0,之后也可能会发布 4.0.1、4.0.2、... 等版本。若待到有较大版本变化时,比如发布 4.1.0 时,此时就会建立 4.0.x 分支,对 4.0.x 进行维护。 + +**分支图**: + +![1555142093799](figures/RT-Thread_tree.jpg) + + + +## 如何选择 + +### 发布版本(GitHub releases) + +发布版本位于 [GitHub releases](https://github.com/RT-Thread/rt-thread/releases)([百度网盘快速下载链接](https://pan.baidu.com/s/1mgIAyWo)),其中包含 RT-Thread 所有的发布版本。发布版本稳定性高,推荐使用最新发布版本。最新的发布版本有两个:3.1.2 版本与 4.0.0 版本,这两个发布版本可以根据自己需求进行选择。 + +**最新发布版本 3.1.2:** + +- 做产品 / 项目:适合公司做产品或者项目 + - 若产品已经使用的是较早的发布版本,那么在维护产品时,建议仍然在旧的版本上进行维护 + - 如果是新的产品,那么建议使用 3.1.x 最新发布版本,如 3.1.x 中最新的 3.1.2 发布版本 +- 学习 / 研究:适合新手入门学习 + +**最新发布版本 4.0.0:** + +- 做产品 / 项目:适合公司做产品或者项目 + - 4.0.0 支持 SMP,适合有多核需求的产品或项目 +- 学习 / 研究:适合新手入门学习、适合有入门经验的 RT-Thread 开发者 + +### 开发分支(GitHub master 主分支) + +开发分支是 RT-Thread 团队在开发中过程中提交的代码的分支,位于 [GitHub master 分支](https://github.com/RT-Thread/rt-thread/tree/master)。该分支会一直更新迭代、优化功能,并且更新频率非常高。 + +- 做产品 / 项目:开发中的分支不稳定,不适合做产品或者用于项目中 +- 学习 / 研究:由于更新速度快,适合有较多经验的 RT-Thread 开发者研究尝鲜 +- 代码贡献:可以提交代码或者修补 Bug,欢迎广大开发者 [为 RT-Thread 贡献代码](https://www.rt-thread.org/document/site/development-guide/github/github/),成为 RT-Thread 贡献者 + +### 长期支持分支(GitHub lts-v3.1.x 分支) + +长期支持分支位于 [GitHub lts-v3.1.x 分支](https://github.com/RT-Thread/rt-thread/tree/lts-v3.1.x),是 3.1.x 版本的维护分支,主要在于修复 Bug 以及 BSP 的更新。由于从 4.0.0 开始,增加了较多特性,如 SMP、lwp等,对于 3.1.x 来说有非常大的变化,所以对 3.1.x 会做长期的支持。 + +- 做产品 / 项目:开发中的分支不稳定,不适合做产品或者用于项目中 +- 学习 / 研究:适合有较多经验的 RT-Thread 开发者 + + + + diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/rt-thread-version/figures/RT-Thread_tree.jpg b/rt-thread-version/rt-thread-standard/application-note/setup/rt-thread-version/figures/RT-Thread_tree.jpg new file mode 100644 index 0000000000000000000000000000000000000000..331dc8fb4d47c0c0af384093a67b4ed4103675a8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/rt-thread-version/figures/RT-Thread_tree.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/an0016-source-code.md b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/an0016-source-code.md new file mode 100644 index 0000000000000000000000000000000000000000..2eb0383434c308f5d175a3b2596aa162b4d23a0b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/an0016-source-code.md @@ -0,0 +1,87 @@ +# RT-Thread 源码下载应用笔记 # + +本文将详细介绍如何获取 RT-Thread 源代码。 + +## 简介 + +RT-Thread 源代码托管在 GitHub,对于不熟悉 GitHub 的新手,初次接触 RT-Thread 不知道如何从 GitHub 获取 RT-Thread 源代码。本文档将一步步介绍如何从 GitHub 获取 RT-Thread 源代码。 + +## RT-Thread 源代码下载链接 ## + +**前提条件:安装好 Git ,git clone 命令需要用到 Git 工具。请参考文档[Git 安装应用笔记][Git 安装应用笔记]。** + +在 [RT-Thread 官网](https://www.rt-thread.org/)提供了3种 RT-Thread 源代码的下载链接。 + +![RT-Thead源代码官网下载链接](figures/rt-thread-link.png) + +1、GitHub 的下载链接:https://github.com/RT-Thread/rt-thread + +2、Gitee 的下载链接:https://gitee.com/rtthread/rt-thread + +3、百度网盘的下载链接:https://pan.baidu.com/s/1mgIAyWo#list/path=%2F + +百度网盘包含 RT-Thread 发布的各个版本源代码压缩包,压缩包不包括 RT-Thread 项目版本的历史记录。 + +Gitee 的源码是 GitHub 的镜像,会及时自动和 GitHub 保持同步,从这2个地方下载源代码都有2种方式,一种是复制 RT-Thread 项目仓库地址后使用 git clone 命令下载,另外一种是直接下载 RT-Thread 项目压缩包。两者的差别是后者不包括 RT-Thread 项目版本的历史记录,而前者会在本地创建一个本地仓库,并且包含 RT-Thread 项目所有版本的历史记录,并且可以直接使用 Git 工具对这个本地仓库进行管理,**强烈建议使用 git clone 命令下载 RT-Thread 源代码**。 + +Gitee 和 GitHub 2种下载方式如下面2张图所示,可以通过点击对应链接进入下载页面。 + +![RT-Thead项目GitHub下载链接](figures/git-hub.png) + +![RT-Thead项目Gitee下载链接](figures/gitee.png) + +## RT-Thread源代码下载 ## + +如上图所示复制 GitHub 上 RT-Thread 项目仓库地址后,打开 windows 命令行工具 或者Git Bash,切换到你想要放置源代码的目录,注意目录路径中不可以有中文字符或者空格,否则影响后续 Env 工具的使用。然后使用`git clone https://github.com/RT-Thread/rt-thread.git`命令克隆 RT-Thread 远程仓库到本地,如下图所示使用 git clone命令后将在 D盘的repository 目录下创建 rt-thread目录。 + +![git-clone-RT-Thead项目](figures/git-clone-github.png) + +由于国外服务器 GitHub 下载代码速度较慢,建议从 Gitee 下载,可以很快就下载好 RT-Thread 源代码,如上图所示复制 Gitee 上 RT-Thread 项目仓库地址后,同样使用git clone命令下载代码,只是仓库地址为 RT-Thread 项目在 Gitee 的仓库地址。使用`git clone https://gitee.com/rtthread/rt-thread.git`命令克隆 RT-Thread 远程仓库到本地。 + +![git-clone-RT-Thead项目](figures/git-clone-gitee.png) + +如果想要修改从 Gitee 克隆的本地 RT-Thread 仓库对应的远程仓库为 GitHub 上的仓库,可以按照下图所示修改,打开本地 RT-Thread 仓库 .git 目录里的config文件,修改 url 地址为 GitHub 上 RT-Thread 项目仓库地址。 + +![修改本地仓库对应的远程仓库地址](figures/change-url.png) + +说明:RT-Thread 源代码下载包较大,因其包含有将近 90 个 BSP(实际你只需要提取自己的板卡对应的 BSP 即可),而 Git 仓库不提供单独下载某个文件,请下载源码时多一些耐心。 + +## RT-Thread 源代码目录简介 ## + +RT-Thread 源代码目录结构如下图所示: + +![RT-Thread 源代码目录结构](figures/rt-thread-dir.png) + +| **目录名** | **描述** | +| -------- | -------- | +| BSP | Board Support Package(板级支持包)基于各种开发板的移植 | +| components | RT-Thread 的各个组件代码,例如 finsh,gui 等。 | +| documentation | 相关文档,如编码规范等 | +| examples | 相关示例代码 | +| include | RT-Thread 内核的头文件。 | +| libcpu | 各类芯片的移植代码。 | +| src | RT-Thread 内核的源文件。 | +| tools | RT-Thread 命令构建工具的脚本文件。 | + +## RT-Thread BSP 介绍 ## + +目前 RT-Thread 已经针对将近90种开发板做好了移植,大部分 BSP 都支持 MDK﹑IAR开发环境和GCC编译器,并且已经提供了默认的 MDK 和 IAR 工程,用户可以直接基于这个工程添加自己的应用代码。 每个 BSP 的目录结构都类似,大部分 BSP 都提供一个 README.md 文件,这是一个 markdown 格式的文件,包含了对这个 BSP 的基本介绍,以及怎么样简单上手使用这个BSP,相当于是这份BSP的使用说明。大家找到对应的 BSP 以后首先应该看一下这份使用说明,才能少走弯路,快速的把RT-Thread 操作系统跑起来。下图是 stm32f4xx-HAL BSP 的目录描述。 + +![stm32f4xx-HAL BSP的目录描述](figures/stm32-1.png) + +双击打开 stm32f4xx-HAL BSP 下的MDK 工程文件 project.uvprojx 。 + +![stm32f4xx-HAL 工程文件](figures/stm32f4-mdk.png) + +在工程主窗口的左侧 “Project” 栏里可以看到该工程的文件列表,这些文件被分别存放到如下几个组内,其他 BSP 提供的工程文件也是类似的分组方式。 + +| 目录组 | 描述 | +| ------------- | --------------- | +| Drivers | 对应的目录为 stm32f4xx-HAL/drivers,它用于存放根据RT-Thread 提供的驱动框架实现的底层驱动代码。 | +| CMSIS | 对应目录为 stm32f4xx-HAL/Libraries/CMSIS, stm32 CMSIS系统文件。 | +| STM32F4XX_HAL_Driver | 对应的目录为 stm32f4xx-HAL/Libraries/STM32F4xx_HAL_Driver,它用于存放 STM32 的固件库文件。 | +| Applications | 对应的目录为 stm32f4xx-HAL/applications,它用于存放用户自己的应用代码。 | +| Kernel | 对应的目录为 rt-thread/src,它用于存放 RT-Thread 内核核心代码。 | +| CORTEX-M4 | 对应的目录为 rt-thread/libcpu/arm,它用于存放 ARM Cortex-M4移植代码。 | +| DeviceDrivers | 对应的目录为 rt-thread/components/drivers,它用于存放 RT-Thread 驱动框架源代码。 | +| finsh | 对应的目录为 rt-thread/components/finsh,它用于存放 RT-Thread finsh 命令行组件。 | diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/change-url.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/change-url.png new file mode 100644 index 0000000000000000000000000000000000000000..80250c2f4920babc17c3dd7172fc6616840e9596 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/change-url.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-clone-gitee.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-clone-gitee.png new file mode 100644 index 0000000000000000000000000000000000000000..8a373bd1fed02010ebd0c455399de0d3282688a3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-clone-gitee.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-clone-github.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-clone-github.png new file mode 100644 index 0000000000000000000000000000000000000000..f3b949211e4ad316e3595c76b4b1d46d7c057635 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-clone-github.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-hub.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-hub.png new file mode 100644 index 0000000000000000000000000000000000000000..6f45b8a4936db7144a021bc2fd06f992189dd6b1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-hub.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-order.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-order.png new file mode 100644 index 0000000000000000000000000000000000000000..744c907ec8d2a4e382f2ca075dabef7fbbedcfbf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/git-order.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/gitee.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/gitee.png new file mode 100644 index 0000000000000000000000000000000000000000..4f25184ebe9b667f30ffe8e933a0716c2d445d9f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/gitee.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/rt-thread-dir.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/rt-thread-dir.png new file mode 100644 index 0000000000000000000000000000000000000000..ea31e6e2ac0217a550db18e587d2c2688cbd8ddf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/rt-thread-dir.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/rt-thread-link.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/rt-thread-link.png new file mode 100644 index 0000000000000000000000000000000000000000..e1fa3b8e0aed1761ee30c391a319efc9e5dcfa04 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/rt-thread-link.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/stm32-1.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/stm32-1.png new file mode 100644 index 0000000000000000000000000000000000000000..70e30e966ffd17de0c33f95679ed4334be1a7123 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/stm32-1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/stm32f4-mdk.png b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/stm32f4-mdk.png new file mode 100644 index 0000000000000000000000000000000000000000..089cac1b6c6d8de704d7eefdbf33c14880631cf2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/source-code/figures/stm32f4-mdk.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/an0017-standard-project.md b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/an0017-standard-project.md new file mode 100644 index 0000000000000000000000000000000000000000..78126c4efdbca76dc0d6a46f837e5a05602185e5 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/an0017-standard-project.md @@ -0,0 +1,265 @@ +# 使用 Env 创建 RT-Thread 项目工程 + +本文用来指导用户按照标准方式创建和管理 RT-Thread 工程。 + +## 简介 + +RT-Thread 完全开源开放,支持几十款 BSP,支持多种编译器,支持众多基础组件以及数量持续增长的软件包,然而对于工程项目开发来说,只需要支持一款或者有限几款 MCU,使用一种熟悉的 IDE 开发环境,使用有限的外设和组件,本文档旨在指导用户在全功能 RT-Thread 版本基础上,根据项目需求搭建 RT-Thread 工程框架。 + +本文准备资料如下: + +* [RT-Thread 源码](https://www.rt-thread.org/page/download.html) + +* [Env 工具](https://www.rt-thread.org/page/download.html) + +* 安装好 MDK 软件或者 IAR 软件。 + +## 标准工程管理 + +### 选择 BSP + +用户获取 RT-Thread 源代码后需要根据自己手上的开发板型号找到对应的 BSP,就可以运行 BSP 提供的默认工程。大部分 BSP 都支持 MDK﹑IAR 开发环境和 GCC 编译器,并且已经提供了默认的 MDK 和 IAR 工程。 + +本文后续章节将使用正点原子潘多拉开发板演示相关操作,该开发板对应的 BSP 为 stm32l475-atk-pandora,默认使用串口 1 作为 shell 控制台输出使用串口。 + +### 搭建项目框架 + +在 BSP 目录下打开 Env 工具,运行 `scons --dist` 命令。使用此命令会在该 BSP 目录下生成 dist 目录,这便是开发项目的目录结构,RT-Thread 源码位于项目文件夹内,可以随意拷贝 dist 文件夹的工程到任何目录下使用。 + +![scons dist命令](figures/scons-dist.png) + +进入dist目录下面的 stm32f4xx-HAL 工程目录,项目框架目录结构如下图所示: + +![项目框架目录结构](figures/dist-dir.png) + +项目框架主要目录及文件的说明如下表所示: + +| 文件 / 目录 | 描述 | +| ------------- | --------------- | +| applications | 用户应用代码目录 | +| drivers 或 board | RT-Thread 提供的底层驱动/板级相关的移植 | +| Libraries | 芯片官网下载的固件库 | +| rt-thread | RT-Thread 源代码 | +| Kconfig | menuconfig 使用的文件 | +| project.ewww | 用户使用的 IAR 工程文件 | +| project.uvprojx | 用户使用的 MDK 工程文件 | +| template. uvprojx | MDK 工程模板文件 | +| SConscript | SCons 配置工具使用的文件 | +| SConstruct | SCons 配置工具使用的文件 | +| README.md | BSP 说明文件 | +| rtconfig.h | BSP 配置头文件 | + +> [!NOTE] +> 注:此命令从 RT-Thread 3.1.0 正式版才开始支持。 + +### 修改工程模板 + +用户一般都需要根据自己的需求对工程做一些工程配置,比如配置 MCU 型号,设置调试选项等。建议大家直接修改工程模板,这样使用 Scons 相关命令生成的新工程也都会包含对模板的修改。MDK 的模板工程为 template.uvprojx。IAR 的模板工程为 template.eww。注意:直接双击打开 IAR 工程模板修改可能会导致生成的新工程低版本 IAR 软件用不了。 + +下图为修改 MDK 工程模板文件的芯片型号示例,选择相应 MCU 型号。 + +![在工程模板中选择芯片](figures/stm32-mcu.png) + +下图为根据自己使用的调试工具设置对应的调试选项,相关配置修改完成后就可以关闭模板工程。 + +![stm32f4xx-HAL 选择调试选项](figures/debug.png) + +> [!NOTE] +> 注:源代码和头文件路径的添加不建议直接修改工程模板,后面章节会介绍使用 Scons 工具往工程添加源代码及头文件路径。 + +### 配置和裁剪 RT-Thread + +每个 BSP 下的工程都有默认的配置,比如系统内核支持的最大线程优先级、系统时钟频率、使用的设备驱动、控制台使用的串口等。RT-Thread 操作系统具有高度的可裁剪性,用户可以根据自己的需求使用 Env 工具进行配置和裁剪。 + +在 BSP 目录下打开 Env,然后在使用 `menuconfig` 命令打开配置界面。 + +![Env 命令行界面](figures/env.png) + +menuconfig 常用快捷键如图所示: + +![menuconfig 配置界面](figures/menuconfig.png) + +### 使能在线软件包 + +下图使能了 mqtt 相关的软件包。 + +![使能 MQTT 软件包](figures/mqtt.png) + +### 生成工程 + +选择软件包后需要使用 `pkgs --update` 命令下载软件包,然后使用`scons --target=mdk5` 命令或者 `scons --target=iar` 命令生成 MDK 或者 IAR 工程。如果大家直接修改 MDK 工程文件 project.uvprojx 或者 IAR 的工程文件 project.ewww 添加了自己的代码,或者修改了工程的一些基本配置,生成的新工程会覆盖之前对工程文件 project 的修改。 + +![下载软件包并更新工程](figures/scons-mdk5.png) + +打开新生成的 MDK 工程 project.uvprojx ,可以看到我们选择的 paho mqtt 相关的软件包源文件已经被添加到了工程中。工程对应的芯片型号也是前文基于工程模板选择的芯片型号。 + +![新文件已经添加到工程](figures/new-mdk5.png) + +### 验证工程 + +编译工程,生成目标代码,然后就可以下载至开发板运行。本文使用终端软件 PuTTY 接收工程控制台对应串口 2 发送的数据,电脑右键→属性→设备管理器→端口(COM 和 LPT),即可查看串口对应的 COM 号,本文为 COM14。打开 PuTTY 按照下图配置,波特率一般配置为 115200。 + +![PuTTY 配置](figures/putty-setup.png) + +点击 open 打开,重启开发板后会看到 RT-Thread 的启动 logo 信息。 + +![RT-Thread 的启动 logo 信息](figures/rtt-logo.png) + +### 添加文件到工程 + +BSP 下的 applications 文件夹用于存放用户自己的应用代码,目前只有一个 main.c 文件。如果用户的应用代码不是很多,建议相关源文件都放在这个文件夹下面,本文在 applications 文件夹下新增了 2 个简单的文件 hello.c 和 hello.h。 + +```c +/* file: hello.h */ + +#ifndef _HELLO_H_ +#define _HELLO_H_ + +int hello_world(void); + +#endif /* _HELLO_H_ */ +``` + +```c +/* file: hello.c */ +#include +#include +#include + +int hello_world(void) +{ + rt_kprintf("Hello, world!\n"); + + return 0; +} + +MSH_CMD_EXPORT(hello_world, Hello world!) +``` + +那么用户如何加入自己的应用程序的初始化代码呢?RT-Thread 将 main 函数作为了用户代码入口,只需要在 main 函数里添加自己的代码即可。 + +```c +#include +#include +#include "hello.h" + +int main(void) +{ + /* user app entry */ + + hello_world(); + + return 0; +} +``` + +RT-Thread 还支持 finsh,提供一套供用户在命令行的操作接口,通过使用宏 `MSH_CMD_EXPORT()` 就可以新增一个自己命令,并可以在命令行端启动自己的应用代码。 + + +> [!NOTE] +> 注:applications 文件夹下新增的 2 个文件需要使用 `scons --target=xxx` 命令生成新的工程,才会添加到工程项目中。每次新增文件都要重新生成工程。 + +下图为应用程序 hello_world() 的两种调用结果。 + +![hello-world](figures/hello.png) + +applications 文件夹下新增的文件是怎么样添加到工程中的呢?每个 BSP 的 applications 文件夹里都有一个 SConscript 文件,这些文件的功能都相似。RT-Thread 使用 SCons 构建系统,SCons 使用 SConscript 和 SConstruct 文件来组织源码结构。通常来说一个项目只有一个 SConstruct,但是会有多个 SConscript 。一般情况下,每个存放有源代码的子目录下都会放置一个 SConscript。 + +SConscript 文件是使用 Python 语言编写,stm32f4xx-HAL BSP 里面的 applications 文件夹里的 SConscript 文件的内容如下所示,# 号后面内容为注释。 + +```c +Import('RTT_ROOT') +Import('rtconfig') +from building import * + +# str(Dir('#') 表示获取工程的 SConstruct 所在的目录, 也就是 D:\repository\rt-thread\bsp\stm32f4xx-HAL +# os.path.join() 表示将 `str(Dir('#')` 表示的目录和 applications 一起拼接成一个完整目录。 +# 也就是获得当前目录 `D:\repository\rt-thread\bsp\stm32f4xx-HAL\applications` +cwd = os.path.join(str(Dir('#')), 'applications') + +# 获取当前目录下所有的 C 文件 +src = Glob('*.c') + +# 将当前目录和 SConstruct 所在的目录保存到 list 变量 CPPPATH 中 +CPPPATH = [cwd, str(Dir('#'))] + +# 将 src 包含的源文件创建为 Applications 组。depend 为空表示该组不依赖 rtconfig.h 中的任何宏。 +# CPPPATH = CPPPATH 表示将右边 CPPPATH 变量包含的目录添加到系统的头文件路径中,左边的 CPPPATH 表示头文件路径。 +# 这个 Applications 组也就对应 MDK 或者 IAR 工程里面的 Applications 分组。 +group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH) + +Return('group') +``` + +通过这个 SConscript 文件,applications 目录下的所有源文件和相关头文件目录就添加到了工程中,而不需要用户手动添加。 + +### 添加模块到工程 + +前文提到在自己源代码文件不多的情况下,建议所有源代码文件都放在 applications 文件夹里面。如果用户源代码很多了,并且想创建自己的工程模块,或者需要使用自己获取的其他模块,怎么做会比较合适呢?前文对 applications 文件夹里的 SConscript 文件做了简单介绍,这一小节将简单演示怎么实现自己的 SConscript 文件来管理工程文件。 + +#### 添加源代码 + +同样以上文提到的 hello.c 和 hello.h 为例,需要把他们作为一个单独的模块管理,并且在 MDK 或者 IAR 的工程文件里有自己的分组,且可以通过 menuconfig 选择是否使用这个模块。在 BSP 下新增 hello 文件夹。 + +![新增 hello 模块](figures/hello-module.png) + +#### 编写 SConscript 文件 + +大家注意到文件夹里多了一个 SConscript 文件,如果想要将自己的一些源代码加入到 SCons 编译环境中,一般可以创建或修改已有 SConscript 文件。SConscript 文件可以控制源码文件的加入,并且可以指定文件的 Group(与 MDK/IAR 等 IDE 中的 Group 的概念类似)。对于这个新增的 hello 模块 SConscript 文件内容如下,# 号后面的内容为注释。 + +```c +# -*- coding: UTF-8 -*- # 指定文件的编码格式是 UTF-8,文件可以用中文 +Import('RTT_ROOT') +Import('rtconfig') +from building import * + +cwd = GetCurrentDir() # 将当前路径赋值给字符串 cwd +include_path = [cwd] # 将当前头文件路径保存到 list 变量 include_path 中 +src = [] # 定义一个 list 变量 src + +if GetDepend(['RT_USING_HELLO']): # hello.c 依赖宏 RT_USING_HELLO + src += ['hello.c'] # 保存 hello.c 字符串到 list 变量 src 中 + +# 将 src 包含的源文件创建为 hello 组。depend 为空表示该组不依赖 rtconfig.h 的任何宏。 +# CPPPATH = include_path 表示将当前目录添加到系统的头文件路径中 +group = DefineGroup('hello', src, depend = [''], CPPPATH = include_path) + +Return('group') +``` + +通过上面几行简单的 python 代码就创建了一个新的 hello 分组,并且可以通过宏定义控制要加入到组里面的源文件。关于 SCons 的更多使用可以参考 [Scons 官方文档](https://scons.org/documentation.html) 或者参考 RT-Thread 提供的 [Scons 构建工具手册][Scons 构建工具手册]。 + +#### 增加 menuconfig 菜单 + +那么自定义宏 RT_USING_HELLO 又是通过怎样的方式定义呢?这里要介绍一个新的文件 Kconfig 。Kconfig 用来配置内核,menuconfig 命令通过读取工程的各个 Kconfig 文件,生成配置界面供用户配置内核 ,最后所有配置相关的宏定义都会自动保存到 BSP 目录里的 rtconfig.h 文件中,每一个 BSP 都有一个 rtconfig.h 文件,也就是这个 BSP 的配置信息。 + +在潘多拉 stm32l475-atk-pandora BSP 目录下已经有了关于这个 BSP 的 Kconfig 文件,如果用户的 BSP 没有则可以自己新建一个。我们可以基于这个文件添加自己需要的配置选项。关于 hello 模块添加了如下配置选项,# 号后面为注释。 + +![新增 hello 模块配置信息](figures/kconfig-hello.png) + +使用 Env 工具进入 stm32l475-atk-pandora BSP 目录后,使用 menuconfig 命令在主页面最下面就可以看到新增的 hello 模块的配置菜单,进入菜单后如下图显示。 + +![新增 hello 模块配置菜单](figures/hello-menu.png) + +我们还可以修改 hello value 的值。 + +![新增 hello 模块配置菜单](figures/hello-value.png) + +保存配置后退出配置界面,打开 stm32l475-atk-pandora BSP 目录下的 rtconfig.h 文件可以看到 hello 模块的配置信息已经有了。 + +![新增 hello 模块配置信息](figures/hello-rtconfig.png) + +> [!NOTE] +> 注:每次 menuconfig 配置完成后要使用 `scons --target=XXX` 生成新工程。 + +![生成新工程](figures/hello-scons.png) + +打开新生成的 MDK5 工程文件 project.uvprojx 后可以看到新增一个 hello 分组,以及此分组下包含的 hello.c 文件。 + +![生成新工程](figures/hello-mdk.png) + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +* [《Scons 构建工具》](../../../programming-manual/scons/scons.md) diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/add-hello.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/add-hello.png new file mode 100644 index 0000000000000000000000000000000000000000..e0606276a255fc822d0a94b4f86c082c7447ba42 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/add-hello.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/debug.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/debug.png new file mode 100644 index 0000000000000000000000000000000000000000..6e3b16503f00dd6aac6d9af125cec313fe422b63 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/debug.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/dist-dir.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/dist-dir.png new file mode 100644 index 0000000000000000000000000000000000000000..439b71086e90d15bd32d2deb60ec0b497ab6766f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/dist-dir.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/env-setup.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/env-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..3f51eb83efd61bc2b04d7e61f0fab2ce2d4bd6b9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/env-setup.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/env.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/env.png new file mode 100644 index 0000000000000000000000000000000000000000..e165d44c64728c0d4e1be38f846643cdbddb1d35 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/env.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-mdk.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-mdk.png new file mode 100644 index 0000000000000000000000000000000000000000..4531d5011283035ca4832f7f0d04fbb46321c930 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-mdk.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-menu.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..e1238809314a1fa21edd5c75c0837ccf04625b1b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-menu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-module.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-module.png new file mode 100644 index 0000000000000000000000000000000000000000..79952ada81718f4599979a667884acc4c4dc0d3e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-module.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-rtconfig.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-rtconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..e67bf04fae39c391e083ca0ed16e992eecbbeee4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-rtconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-scons.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-scons.png new file mode 100644 index 0000000000000000000000000000000000000000..b768e346fc266a88166a875cd286da5320baff8b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-scons.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-value.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-value.png new file mode 100644 index 0000000000000000000000000000000000000000..40f592af35bc80eca619aec7c2f714319b813dff Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello-value.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello.png new file mode 100644 index 0000000000000000000000000000000000000000..f94bd4a8aad5a9b65d08aa010f9f3c2abf237175 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/hello.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/kconfig-hello.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/kconfig-hello.png new file mode 100644 index 0000000000000000000000000000000000000000..01862e13da3180e96dc14b415cd67a69c5738b43 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/kconfig-hello.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..f46239c73ba54e39ea4b4f83a082d6ca7232ee4c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/mqtt.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/mqtt.png new file mode 100644 index 0000000000000000000000000000000000000000..6c536291ee1b58a383f334fee3451dc094bd4596 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/mqtt.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/new-mdk5.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/new-mdk5.png new file mode 100644 index 0000000000000000000000000000000000000000..488f14c0f1d2eacefe4b4e6d7ad314fb57232b9d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/new-mdk5.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/putty-setup.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/putty-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..4206760e7fe6091efcccbc2da3e0abaf4e021f61 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/putty-setup.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/rtt-logo.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/rtt-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..98d48658337b528d319f7bdf2b7ac5b4f0b09183 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/rtt-logo.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-dist.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-dist.png new file mode 100644 index 0000000000000000000000000000000000000000..f56da4dbbe521660b4e01c251a019733ae2d0cff Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-dist.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-mdk5.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-mdk5.png new file mode 100644 index 0000000000000000000000000000000000000000..abfb5bb0179f84a1359cbf6014ba51ab33967e79 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-mdk5.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-target.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-target.png new file mode 100644 index 0000000000000000000000000000000000000000..0fb9edf1c3e6538b7e257f801dba086a527ed9d7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/scons-target.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/stm32-mcu.png b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/stm32-mcu.png new file mode 100644 index 0000000000000000000000000000000000000000..56a0ae59abdf230e0bf3e32ac22580e0cb5fb6fc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/setup/standard-project/figures/stm32-mcu.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/init/an0007-rtthread-system-component-init.md b/rt-thread-version/rt-thread-standard/application-note/system/init/an0007-rtthread-system-component-init.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/rt-thread-version/rt-thread-standard/application-note/system/libc/an0008-rtthread-system-libc.md b/rt-thread-version/rt-thread-standard/application-note/system/libc/an0008-rtthread-system-libc.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/an0049-optimize-code-size.md b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/an0049-optimize-code-size.md new file mode 100644 index 0000000000000000000000000000000000000000..2c3f1e04c1ce5126ad8a8fdcbecf42718b4f9de6 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/an0049-optimize-code-size.md @@ -0,0 +1,306 @@ +# 固件尺寸优化 + +## 前言 + +使用 RT-Thread-Studio 进行工程构建时,为了实现业务需求,我们常常会增加驱动文件、组件或者软件包等等,并且在调试代码时也可能需要使能调试相关的功能(例如打开 ulog 功能 )或者自行打印一些调试的信息。因此,我们会编译得到一个稍微冗余的固件。对于 MCU 的 Flash 比较紧张时,我们需要考虑代码体积的优化,使其尽量精简,这样的代码在之后的迭代开发中才可以实现小而美的目标。 + +下面是几个可以去考虑的优化的方向: + +* 裁剪 +* 选择合适的优化等级 +* 开启 newlib-nano 选项 +* 使用 Map File 分析工具 +* readelf 命令分析 ELF 文件 +* 更换 libc 库 +* 更换同类型 Flash 较大的硬件平台 + +需要注意的是,并不是所有的优化都是行之有效的,如果收效甚微的优化却造成了系统性能的大幅衰减,这是非常不可取的,所以优化的时候要认真分析,综合考虑,不可能一蹴而就。 + +## 1. 裁剪 + +裁剪是优先需要考虑的方向,这种方式操作简单,也最为见效。 + +以下是基于 stm32l475-atk-pandora BSP 进行裁剪的例子,该示例使用 RT-Thread 4.0.3 版本,优化等级 -O0。 + +MCU:STM32L475VET6,512KB FLASH ,128KB RAM + +在做了一系列配置之后(模拟项目工程),该 BSP 现已有资源为: + +- 内核:信号量、互斥量、事件集、邮箱、消息队列;main 线程、tshell 线程、idle 线程 +- 组件:文件系统(fatfs)、Finsh 组件、UART 框架、GPIO 框架、SFUD 组件、QSPI 框架 +- 外设驱动:片上 UART、GPIO、QSPI、板上 QSPI FLASH、NRF24L01、LCD、PWM、ADC等 + +其中关系为: + +1. main 中的 led 闪烁:PIN 驱动、PIN 框架 +2. FinSH 控制台:使用 UART 驱动、UART 框架、FinSH 组件 +3. 文件系统(板上 QSPI FLASH):使用 QSPI 驱动、QSPI 框架、文件系统组件 Fatfs、FAL 软件包 +4. Ulog:ulog 组件 +5. 其他硬件板载设备驱动:LCD、TIMER、PWM、ADC、RTC、Audio + +利用 RT-Thread Setting 的图形界面,我们可以比较直观的看到使能了哪些软件包、驱动和组件。 +![RT-Thread Setting_01](figures/RT-Thread Setting_01.png) + +当前系统体积大小如下所示: + +```information + text data bss dec hex filename + 260932 1648 5388 267968 416c0 rtthread.elf + Used Size(B) Used Size(KB) +Flash: 262580 B 256.43 KB +RAM: 7036 B 6.87 KB +``` + +可以看到**当前系统体积大小 256KB**,下面对该BSP进行裁剪,打开工程的 RT-Thread Settings 配置界面: + +### 裁剪 Ulog 组件(-3.8KB) + +![1563873491345](figures/disable-ulog-async.png) + +去除异步日志功能后 + +![1563873528253](figures/disable-ulog.png) + +```information + text data bss dec hex filename + 257000 1648 5120 263768 40658 rtthread.elf + Used Size(B) Used Size(KB) +Flash: 258648 B 252.59 KB +RAM: 6768 B 6.61 KB +``` + +### 裁剪文件系统及 Flash 设备(-83.2KB) + +由于系统使能了 FAL 软件包,如下图 + +![除能 FAL 软件包](figures/fal.png) + +关闭 QSPI Flash 设备,在Hardware选项中,将已经适配好的 QSPI FLASH相关设备除能。 + +![QspiFlash](figures/QspiFlash.png) + +**由于系统不再使用 QSPI 设备,那么相对应的 QSPI设备驱动框架,也是可以取消掉的。这点在裁剪系统时候很重要,因为我们开发中经常 使能/除能 一些总线上的设备,却常常忘记关 总线/设备驱动框架 造成系统体积上的损耗。** + +![关闭 QSPI Flash 设备框架](figures/disable_qspi_drivers.png) + +最后将虚拟文件系统 DFS 关闭。 + +![1563874891663](figures/disable-dfs.png) + +``` + text data bss dec hex filename + 172148 1308 3556 177012 2b374 rtthread.elf + Used Size(B) Used Size(KB) +Flash: 173456 B 169.39 KB +RAM: 4864 B 4.75 KB +``` + +### 裁剪外设驱动(-101.8KB) + +关闭 LCD、Audio 设备(**由于SPI无其他设备挂载,因为可以裁剪掉SPI BUS**)( -75KB) + +![关闭LCD和Audio设备驱动](figures/disable_hd_drivers.png) + +```information + text data bss dec hex filename + 95204 1260 2864 99328 18400 rtthread.elf + Used Size(B) Used Size(KB) +Flash: 96464 B 94.20 KB +RAM: 4124 B 4.03 KB +``` + +裁剪掉 TIMER、PWM、ADC、RTC 外设驱动 (-26.6KB) + +![裁剪掉TIMER、PWM、ADC、RTC外设驱动](figures/disable_hd_drivers2.png) + +```information + text data bss dec hex filename + 68856 384 2524 71764 11854 rtthread.elf + Used Size(B) Used Size(KB) +Flash: 69240 B 67.62 KB +RAM: 2908 B 2.84 KB +``` + +### 裁剪 FinSH(-13K) + +![1563876671738](figures/disable-finsh.png) + +```information + text data bss dec hex filename + 55500 384 2240 58124 e30c rtthread.elf + Used Size(B) Used Size(KB) +Flash: 55884 B 54.57 KB +RAM: 2624 B 2.56 KB +``` + +### 裁剪内核 IPC(体积几乎不变) + +关闭事件集、邮箱、消息队列 + +![1563878646561](figures/disable-kernel-ipc.png) + +```information + text data bss dec hex filename + 54888 336 2232 57456 e070 rtthread.elf + Used Size(B) Used Size(KB) +Flash: 55224 B 53.93 KB +RAM: 2568 B 2.51 KB +``` + +### 检查 RT-Thread Setting 和 rtconfig.h 配置文件 + +经过以上的裁剪步骤,差不多裁剪了十之八九了,接下来就要检查还有什么地方在裁剪的过程中被忽略了,然后再按照上面的步骤做深入的裁剪,在此不再一一演示,仅作展示说明为主。 + +打开 RT-Thread Setting 图形化界面如下图所示: + +![RT-Thread Setting 图形化界](figures/RT-Thread Setting_02.png) + +可以看到目前系统有使用到 libc 组件、Serial 驱动、Pin 设备驱动、Soft I2C 驱动。例如现在除能 libc 组件,直接单击 libc 按钮去除使能即可。 + +另外,我们也可以根据 rtconfig.h 文件查看各个宏定义信息,避免遗漏。下面是 rtcopnfig.h 的部分配置信息。 + +```c +#ifndef RT_CONFIG_H__ +#define RT_CONFIG_H__ + +/* Generated by Kconfiglib (https://github.com/ulfalizer/Kconfiglib) */ + +/* RT-Thread Kernel */ + +#define RT_NAME_MAX 8 +#define RT_ALIGN_SIZE 4 +#define RT_THREAD_PRIORITY_32 +#define RT_THREAD_PRIORITY_MAX 32 +#define RT_TICK_PER_SECOND 1000 +#define RT_USING_OVERFLOW_CHECK +#define RT_USING_HOOK +#define RT_USING_IDLE_HOOK +#define RT_IDLE_HOOK_LIST_SIZE 4 +#define IDLE_THREAD_STACK_SIZE 256 +#define RT_DEBUG //DEBUG相关还可以再优化掉 +#define RT_DEBUG_COLOR //DEBUG相关还可以再优化掉 + +/* Inter-Thread communication */ + +#define RT_USING_SEMAPHORE +#define RT_USING_MUTEX +/* end of Inter-Thread communication */ + +/* Memory Management */ + +#define RT_USING_MEMPOOL //内存池还可以再优化掉 +#define RT_USING_SMALL_MEM +#define RT_USING_HEAP +/* end of Memory Management */ + +/* Kernel Device Object */ + +#define RT_USING_DEVICE +#define RT_USING_CONSOLE +#define RT_CONSOLEBUF_SIZE 256 +#define RT_CONSOLE_DEVICE_NAME "uart1" +/* end of Kernel Device Object */ +#define RT_VER_NUM 0x40003 +/* end of RT-Thread Kernel */ +#define ARCH_ARM +#define RT_USING_CPU_FFS +#define ARCH_ARM_CORTEX_M +#define ARCH_ARM_CORTEX_M4 + +#endif +``` + +## 2.选择合适的优化等级 + +RT-Thread-Studio 使用的是 GCC 编译器,GCC 编译器对代码的编译优化有一系列的配置项,大体分为五个优化等级:-O0、-O1、-O2、-O3 和 -Os。 + +-O0:关闭所有优化选项,是 GCC 默认的等级,目的是让编译器减少编译时间并使调试产生预期的结果。在 RT-Thread-Studio 中,默认也是配置的该选项,如果编译的代码尺寸较大,我们建议更换优化等级(一般我们会选择 O2 等级)。 + +-O1:这是最基本的优化等级。编译器会在不花费太多编译时间的同时试图生成更快更小的代码。这些优化是非常基础的,但一般这些任务肯定能顺利完成。 + +-O2:O1 的进阶。这是推荐的优化等级,除非你有特殊的需求。O2 会比 O1 启用更多的优化选项。当设置了 O2 等级后,编译器会试图增加编译的时间和提升生成代码的性能(我们一般选用此优化等级完成编译任务)。 + +-O3:这是最高的优化等级,O3 开启了 O2 指定的所有优化,并启用了更多的优化选项。例如构建用于保存变量的伪寄存器网络(使得调试更加困难)、优化循环执行过程等。开启 O3 优化不一定会减少代码尺寸,有可能会为了减少代码执行时间反而增加代码体积。一般我们不使用此优化等级。 + +-Os:该这个等级用来优化代码尺寸。其中启用了 O2 中不增加目标文件大小的优化选项。这对于磁盘空间极其紧张或者 CPU 缓存较小的机器非常有用。一般使用 O2 等级之后发现生成的目标文件尺寸偏大,可以尝试使用 Os 等级进一步的优化。下表是[GCC 优化等级列表](https://www.rapidtables.com/code/linux/gcc/gcc-o.html#optimization)。 + +### gcc -O option flag + +Set the compiler's optimization level. + +| option | optimization level | execution time | code size | memory usage | compile time | +| :-------: | :------------------------------------------------: | :------------: | :-------: | :----------: | :----------: | +| -O0 | optimization for compilation time (default) | + | + | - | - | +| -O1 or -O | optimization for code size and execution time | - | - | + | + | +| -O2 | optimization more for code size and execution time | -- | | + | ++ | +| -O3 | optimization more for code size and execution time | --- | | + | +++ | +| -Os | optimization for code size | | -- | | ++ | +| -Ofast | O3 with fast none accurate math calculations | --- | | + | +++ | + ++increase ++increase more +++increase even more -reduce --reduce more ---reduce even more + +RT-Thread-Studio 默认选择的是 -O0(关闭所有优化)等级,按照上一章节,系统最后的裁剪的尺寸为 53.93KB,下面开启 O2 优化等级,代码尺寸缩小为 38.14 KB: + +![开启-O2优化等级](figures/Optimazation1.png) + +```information + text data bss dec hex filename + 38724 336 2232 41292 a14c rtthread.elf + Used Size(B) Used Size(KB) +Flash: 39060 B 38.14 KB +RAM: 2568 B 2.51 KB +``` + +下面开启 -Os 优化等级,代码尺寸缩小为 34.64 KB: + +![开启-Os化等级](figures/Optimazation2.png) + +```information + text data bss dec hex filename + 35140 336 2232 37708 934c rtthread.elf + Used Size(B) Used Size(KB) +Flash: 35476 B 34.64 KB +RAM: 2568 B 2.51 KB +``` + +## 3.开启 newlib-nano 选项 + +RT-Thread-Studio 默认使用的 libc,提供了 printf、scanf 等很多标准库函数,但是这些库函数相对都比较大(在嵌入式平台上),而且很可能一些复杂的功能,我们在项目中并没有使用到,这样会造成代码体积的增大。 + +因此 newlib 提供了一个精简功能的版本,将一些标准库函数进行简化,仅仅实现一些简单常用的功能,这样便可以使得编译的代码轻量化,更适合嵌入式平台使用。(但是如果我们使用了标准库的一些复杂的功能,而 newlib-nano 并没有完备的实现这些功能,那么可能会造成一些意外的运行结果,我们在使用时要注意这些。) + +如下图所示,我们在RT-Thread-Studio 中便可以开启该选项。 + +![newlib_nano](figures/newlib_nano.png) + +另外,开启 newlib-nano 时,对于 printf 和 scanf 等的使用是默认不带浮点运算的,如果使用浮点的话,则需要开启对应选项,如上图中 `Use float with nano printf` 和 `Use float with nano scanf` 选项框。 + +## 4.对 Map File 进行分析优化 + +在进行裁剪之后,我们还可以使用 Amap.exe 工具(( map 文件分析工具)[http://www.sikorskiy.net/prj/amap/]) + +使用该工具只是辅助性的分析函数调用所占字段大小,从而针对各个组件和函数进行优化裁剪等。 + +![Amap工具分析图示](figures/Amap.png) + +## 5.使用 readelf 命令分析 ELF文件 + +与 Amap 工具类似,我们也可以使用 readelf 命令分析系统生成的 elf 文件。详细命令介绍见 [readelf - Linux man page](https://linux.die.net/man/1/readelf),或者直接 `readelf --help`查看用法。 + +使用 `readelf -all rtthread.elf` 可以查看 elf 的所有信息。 + +![例举ELF的头信息](figures/readelf.png) + +依据生成的符号表 ( Symbol table ) ,可以看到生成的字段信息,例如类型为 `GLOBAL` 代表全局符号,`OBJECT` 代表数据对象,比如变量数组,`FUNC` 代表函数等等。我们可以利用这些这些信息,分析具体的段对应的大小。 + +![例举ELF的符号表信息](figures/readelf1.png) + +## 6.更换 libc 库 + +目前 RT-Thread-Studio 在使用 libc 时,默认使用的是 newlib,也有 minilibc 库支持,这个主要是提供给 gcc 编译器的,minilibc 可以不需要再链接 GCC 自带的 libc 库。newlib 则是用于链接到 GCC 自带的 libc 库。newlib 提供的底层c库接口相对 minilibc 库更全面,而 minilibc 库在实现上可以使得代码体积更小。 + +如果我们项目上需要用到 C 库时,可以按照具体需求选择更换 C 库,甚至有能力的开发者可以自行设计优化 C 库代码使得编译尺寸减小的同时,又不会造成性能上的损失。 + +## 7.更换同类型 Flash + +最后还要提一点的是,当系统经过优化后仍然无法满足需求,如果有必要的话,建议更换成同类型 Flash 较大的硬件平台,这样可以在软件和硬件完全不需要修改的情况下完成项目功能,达到预期目标。 diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Amap.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Amap.png new file mode 100644 index 0000000000000000000000000000000000000000..e1206747964176eac45f49106cf1d4546594804d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Amap.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Optimazation1.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Optimazation1.png new file mode 100644 index 0000000000000000000000000000000000000000..d413b2856c5e926904f27338e29956a317b57b4e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Optimazation1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Optimazation2.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Optimazation2.png new file mode 100644 index 0000000000000000000000000000000000000000..f0ac6c2bd7b4a3ed6e217f130380877a5c9a39ce Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/Optimazation2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/QspiFlash.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/QspiFlash.png new file mode 100644 index 0000000000000000000000000000000000000000..800e4ce8021f3a0bf1ea0cf601e02fbede70108e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/QspiFlash.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/RT-Thread Setting_01.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/RT-Thread Setting_01.png new file mode 100644 index 0000000000000000000000000000000000000000..d4c4530d7fe55d13295c2104f1deed9639fa1c6a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/RT-Thread Setting_01.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/RT-Thread Setting_02.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/RT-Thread Setting_02.png new file mode 100644 index 0000000000000000000000000000000000000000..12e01924697fc2125eb3228158f69f7f9455f3e0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/RT-Thread Setting_02.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-dfs.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-dfs.png new file mode 100644 index 0000000000000000000000000000000000000000..39da51232ae11b2507385bd3c011e94ea481b4c3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-dfs.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-finsh.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-finsh.png new file mode 100644 index 0000000000000000000000000000000000000000..a0ae3822f0d813886207a41cd3d4efcb98022edb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-finsh.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-kernel-ipc.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-kernel-ipc.png new file mode 100644 index 0000000000000000000000000000000000000000..4e98c4c10a4e53227bda109b28e0f09446f69cb5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-kernel-ipc.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-ulog-async.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-ulog-async.png new file mode 100644 index 0000000000000000000000000000000000000000..c339352398fd42d06d5defbf8a9b273225b7d389 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-ulog-async.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-ulog.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-ulog.png new file mode 100644 index 0000000000000000000000000000000000000000..986589d55e0a86517569dd5163a25aeef007a13e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable-ulog.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_hd_drivers.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_hd_drivers.png new file mode 100644 index 0000000000000000000000000000000000000000..d8f5c7acf831cad3fd73b9698e2a8e573599681a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_hd_drivers.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_hd_drivers2.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_hd_drivers2.png new file mode 100644 index 0000000000000000000000000000000000000000..49f1b91ca5d985ca68ce9aacd3a6230ca77687ad Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_hd_drivers2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_qspi_drivers.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_qspi_drivers.png new file mode 100644 index 0000000000000000000000000000000000000000..92398c72746437e489f36f3883f9237aed3481cb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/disable_qspi_drivers.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/fal.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/fal.png new file mode 100644 index 0000000000000000000000000000000000000000..1a38ab6f2edd998bb5e0af7d3981f579e9b10e39 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/fal.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/newlib_nano.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/newlib_nano.png new file mode 100644 index 0000000000000000000000000000000000000000..7d33573ae3b7d246e1d7f14b2b3a3fc210ea7ce6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/newlib_nano.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/readelf.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/readelf.png new file mode 100644 index 0000000000000000000000000000000000000000..79edb37fdcbd5724b233a2d06b214a531ebec0a1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/readelf.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/readelf1.png b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/readelf1.png new file mode 100644 index 0000000000000000000000000000000000000000..07a34e3b1590d6d7146c7b3814897be636abe00c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/optimization/Optimize-code-size/figures/readelf1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/an0025-pm.md b/rt-thread-version/rt-thread-standard/application-note/system/pm/an0025-pm.md new file mode 100644 index 0000000000000000000000000000000000000000..43955e57c7e05552a23e51a43dfcc7fa3abc719c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/system/pm/an0025-pm.md @@ -0,0 +1,469 @@ +# 在 RT-Thread 潘多拉开发板上实现电源管理 + +本文介绍了基于 RT-Thread潘多拉开发板电源管理组件的使用和移植过程。 + +## 简介 + +随着物联网(IoT)的兴起,产品对功耗的需求越来越强烈。作为数据采集的传感器节点通常需要在电池供电时长期工作,而作为联网的SOC也需要有快速的响应功能和较低的功耗。 + +在产品开发的起始阶段,首先考虑是尽快完成产品的功能开发。在产品功能逐步完善之后,就需要加入电源管理功能。为了适应IoT的这种需求,RT-Thread提供了电源管理框架。电源管理框架的理念是尽量透明,使得产品加入低功耗功能更加轻松。 + +本文的示例都是在潘多拉开发板下运行。潘多拉开发板是 RT-Thread 和正点原子联合推出的硬件平台,该平台上专门为 IoT 领域设计,并提供了丰富的例程和文档。 + +MCU通常提供了多种时钟源供用户选择。例如潘多拉开发板上板载的 STM32L475 就可以选择 LSI/MSI/HSI 等内部时钟,还可以选择 HSE/LSE 等外部时钟。MCU 内通常也集成了 PLL(Phase-locked loops),基于不同的时钟源,向 MCU 的其他模块提供更高频率的时钟。 + +为了支持低功耗功能,MCU 里也会提供不同的休眠模式。例如 STM32L475 里,可以分成 SLEEP模式、STOP模式、STANDBY模式。这些模式还可以有进一步的细分,以适应不同的场合。 + +本节主要展示了如何开启 PM 组件和相应的驱动,并通过例程来演示常见场景下,应用应该如何管理模式。 + +最后,本节将介绍 RT-Thread PM 组件在 STM32L476 上的移植和注意事项。 + +## 配置工程 + +在潘多拉开发板上运行电源管理组件,需要下载潘多拉开发板的相关资料、RT-Thread 源码和 ENV 工具。 + +* [RT-Thread 源码](https://github.com/RT-Thread/rt-thread) +* [ENV 工具](https://www.rt-thread.org/page/download.html) + +开启 Env 工具,进入潘多拉开发板的 BSP 目录(`rt-thread\bsp\stm32\stm32l475-atk-pandora`),在 Env 命令行里输入 `menuconfig` 进入配置界面配置工程。 + +- 配置 PM 组件:勾选 BSP 里面的`RT-Thread Components ---> Device Drivers ---> [*] Using Power Management device drivers`: + + ![配置组件](figures/menuocnfig0.jpg) + +- 配置内核选项:使用 PM 组件需要更大的 IDLE 线程的栈,这里使用了1024 字节。例程里还使用 Software timer,所以我们还需要开启相应的配置 + + ![配置内核选项](figures/menuconfig1.jpg) + +- 配置完成,保存并退出配置选项,输入命令`scons --target=mdk5`生成 mdk5 工程; + +打开mdk5 工程可以看到相应的源码以及被添加进来: + +![MDK工程](figures/project.png) + +## 使用 +### 定时应用 + +在定时应用里,我们创建了一个周期性的软件定时器,定时器任务里周期性输出当前的 OS Tick。如果创建软件定时器成功之后,使用`rt_pm_request(PM_SLEEP_MODE_DEEP)`请求深度睡眠模式。以下是示例核心代码(该代码可以直接复制 main.c 里运行): + +```c +#include +#include +#include + +#ifndef RT_USING_TIMER_SOFT + #error "Please enable soft timer feature!" +#endif + +#define TIMER_APP_DEFAULT_TICK (RT_TICK_PER_SECOND * 2) + +#ifdef RT_USING_PM + +static rt_timer_t timer1; + +static void _timeout_entry(void *parameter) +{ + rt_kprintf("current tick: %ld\n", rt_tick_get()); +} + +static int timer_app_init(void) +{ + rt_pm_request(PM_SLEEP_MODE_IDLE); + rt_pm_request(PM_SLEEP_MODE_LIGHT); + + timer1 = rt_timer_create("timer_app", + _timeout_entry, + RT_NULL, + TIMER_APP_DEFAULT_TICK, + RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER); + if (timer1 != RT_NULL) + { + rt_timer_start(timer1); + + /* keep in timer mode */ + rt_pm_request(PM_SLEEP_MODE_DEEP); + + return 0; + } + else + { + return -1; + } +} +INIT_APP_EXPORT(timer_app_init); + +#endif /* RT_USING_PM */ +``` + +按下复位按键重启开发板,打开终端软件,我们可以看到有定时输出日志: + +``` + \ | / +- RT - Thread Operating System + / | \ 4.0.1 build May 9 2019 + 2006 - 2019 Copyright by rt-thread team +msh >current tick: 2001 +current tick: 4002 +current tick: 6003 +current tick: 8004 +``` + +我们可以在msh里输入`pm_dump`命令观察PM组件的模式状态: + +```shell +pm_dump +| Power Management Mode | Counter | Timer | ++-----------------------+---------+-------+ +| None Mode | 0 | 0 | +| Idle Mode | 1 | 0 | +| LightSleep Mode | 1 | 0 | +| DeepSleep Mode | 1 | 1 | +| Standby Mode | 0 | 0 | +| Shutdown Mode | 0 | 0 | ++-----------------------+---------+-------+ +pm current sleep mode: Idle Mode +pm current run mode: Normal Speed +msh > +``` + +以上的输出说明,PM 组件里 Idle、Light Sleep、Deep Sleep 都被请求了一次,现在正处于空闲模式(Idle Mode)。 + +我们依次输入命令`pm_release 1`和`pm_release 2` 手动释放 Idle 和 Light Sleep 模式后,将进入`Deep Sleep Mode`。进入`Deep Sleep Mode`之后会定时唤醒,shell 还是一直在输出: + +```shell +msh />pm_release 1 +msh /> +msh />current tick: 8023 +current tick: 10024 +current tick: 12025 + +msh />pm_release 2 +msh /> +msh />current tick: 14026 +current tick: 16027 +current tick: 18028 +current tick: 20029 +current tick: 22030 +current tick: 24031 + +``` + +我们可以通过功耗仪器观察功耗的变化。下图是基于 Monsoon Solutions Inc 的 Power Monitor 的运行截图,可以看到随着模式变化,功耗明显变化: + +![功耗变化](figures/deep_sleep.png) + +休眠时显示2mA是仪器的误差。 + +### 按键唤醒应用 + +在按键唤醒应用里,我们使用 wakeup 按键来唤醒处于休眠模式的 MCU。一般情况下,在 MCU 处于比较深度的休眠模式,只能通过特定的方式唤醒。MCU 被唤醒之后,会触发相应的中断。以下例程是从 Deep Sleep 模式唤醒 MCU 并闪烁 LED 之后,再次进入休眠的例程。以下是核心代码(该代码可以直接复制 main.c 里运行): + +```c +#include +#include +#include + +#ifdef RT_USING_PM + +#define WAKEUP_EVENT_BUTTON (1 << 0) +#define PIN_LED_R GET_PIN(E, 7) +#define WAKEUP_PIN GET_PIN(C, 13) +#define WAKEUP_APP_THREAD_STACK_SIZE 1024 + +static rt_event_t wakeup_event; + +static void wakeup_callback(void *args) +{ + rt_event_send(wakeup_event, WAKEUP_EVENT_BUTTON); +} + +static void wakeup_init(void) +{ + rt_pin_mode(WAKEUP_PIN, PIN_MODE_INPUT_PULLUP); + rt_pin_attach_irq(WAKEUP_PIN, PIN_IRQ_MODE_FALLING, wakeup_callback, RT_NULL); + rt_pin_irq_enable(WAKEUP_PIN, 1); +} + +static void wakeup_app_entry(void *parameter) +{ + wakeup_init(); + rt_pm_request(PM_SLEEP_MODE_DEEP); + + while (1) + { + if (rt_event_recv(wakeup_event, + WAKEUP_EVENT_BUTTON, + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, RT_NULL) == RT_EOK) + { + rt_pm_request(PM_SLEEP_MODE_NONE); + + rt_pin_mode(PIN_LED_R, PIN_MODE_OUTPUT); + rt_pin_write(PIN_LED_R, 0); + rt_thread_delay(rt_tick_from_millisecond(500)); + rt_pin_write(PIN_LED_R, 1); + + rt_pm_release(PM_SLEEP_MODE_NONE); + } + } +} + +static int wakeup_app(void) +{ + rt_thread_t tid; + + wakeup_event = rt_event_create("wakup", RT_IPC_FLAG_FIFO); + RT_ASSERT(wakeup_event != RT_NULL); + + tid = rt_thread_create("wakeup_app", wakeup_app_entry, RT_NULL, + WAKEUP_APP_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20); + RT_ASSERT(tid != RT_NULL); + + rt_thread_startup(tid); + + return 0; +} +INIT_APP_EXPORT(wakeup_app); + +#endif +``` + +上面的代码里,我们创建一个线程,这个线程里注册了按键中断唤醒回调函数,接着请求深度睡眠模式,每当唤醒中断之后就会触发回调。回调函数里会发送事件`WAKEUP_EVENT_BUTTON`。这样我们的线程里接收到这个事件之后,首先请求在 None 模式,然后完成 LED 闪烁功能之后,再去释放 None 。 + +![功耗变化](figures/wakeup.png) + +上图是我们三次按下 wakeup 按键的运行截图。每次按下按键,MCU 都会被唤醒点亮 LED 2秒之后,再次进入休眠。 + +## STM32L4 移植 PM + +### STM32L4 的低功耗模式简介 + +STM32L476 是 ST 公司推出的一款超低功耗的 Crotex-M4 内核的 MCU,支持多个电源管理模式,其中最低功耗 Shutdown 模式下,待机电流仅 30 nA。ST 公司 把 L476 的电管管理分为很多种,但各个模式的并非功耗逐级递减的特点,下面是各个模式之间的状态转换图: + +![模式切换图](figures/stl476_mode.png) + +尽管 STM32L476 的低功耗模式很多,但本质上并不复杂,理解它的原理有助于我们移植驱动,同时更好的在产品中选择合适的模式。 + +最终决定 STM32L476 系统功耗的主要是三个因素:稳压器(voltage regulator)、CPU 工作频率、芯片自身低功耗的处理,下面分别对三个因素进行阐述。 + +- 稳压器 + +L4 使用两个嵌入式线性稳压器为所有数字电路、待机电路以及备份时钟域供电,分别是主稳压器(main regulator,下文简称 MR)和低功耗稳压器(low-power +regulator,下文简称 LPR)。稳压器在复位后处于使能状态,根据应用模式,选择不同的稳压器对 Vcore 域供电。其中,MR 的输出电压可以由软件配置为不同的范围(Range 1 和 Rnage 2)。 + +| 稳压器 | 应用场合 | +| -- | -- | +| MR(Range 1) | Vcore = 1.2V,用于运行模式、睡眠模式和停止模式0,MR 未 Vcore 域提供全功率 | +| MR(Range 2) | Vcore = 1.0V,使用的场景同上| +| LPR | 用于低功耗运行模式、低功耗休眠模式、停止模式 1、停止模式2 | +| OFF | Standby 和 Shutdown 模式下,MR 和 LPR 都被关闭 | + +- CPU 工作频率 + +通过降低 CPU 的主频达到降低功耗的目的: +MR 工作在 Range 1 正常模式时,SYSCLK 最高可以工作在 80M; +MR 工作在 Range 2 时,SYSCLK 最高不能超过 26 M; +低功耗运行模式和低功耗休眠模式,即 Vcore 域由 LPR 供电,SYSCLK 必须小于 2M。 + +- 芯片本身的低功耗处理 + +芯片本身定义了一系列的休眠模式,如 Sleeep、Stop、Standby 和 Shutdown,前面的四种模式功耗逐渐降低,实质是芯片内部通过关闭外设和时钟来实现。 + +### 移植的具体实现 + +上文简要说明 STM32 的低功耗模式和工作原理,下面介绍 RT-Thread PM 的功能和移植接口。 + +RT-Thread 低功耗管理系统从设计上分离运行模式和休眠模式,独立管理,运行模式用于变频和变电压,休眠调用芯片的休眠特性。对于多数芯片和开发来说,可能并不需要考虑变频和变电压,仅需关注休眠模式。 + +STM32 L4 系列的芯片有运行模式和低功耗运行模式的概念,同时 MR 还有 Range 2 模式,可用于变频场景。 + +PM 组件的底层功能都是通过struct rt_pm_ops结构体里的函数完成: + +```c +/** + * low power mode operations + */ +struct rt_pm_ops +{ + void (*sleep)(struct rt_pm *pm, uint8_t mode); + void (*run)(struct rt_pm *pm, uint8_t mode); + void (*timer_start)(struct rt_pm *pm, rt_uint32_t timeout); + void (*timer_stop)(struct rt_pm *pm); + rt_tick_t (*timer_get_tick)(struct rt_pm *pm); +}; +``` + +- 移植休眠模式 + +移植休眠模式仅需关注 sleep 接口,根据 PM 用户手册相关介绍,首先将 RT-Thread 的休眠模式和 STM32 的模式作一个转换: + +| RT-Thread | STM32 | 描述 | +| -- | -- | -- | +|PM_SLEEP_MODE_NONE | Run | 正常运行模式,不进行任何降功耗的措施 | +|PM_SLEEP_MODE_IDLE | Run | 正常运行模式,可选择 WFI(等待中断唤醒)和WFE(等待事件唤醒),此处暂不处理 | +|PM_SLEEP_MODE_LIGHT | Sleep | 轻度睡眠模式,执行 ST 的 Sleep 模式| +|PM_SLEEP_MODE_DEEP | Stop2 | 深度睡眠模式,执行 ST 的 Stop2 模式 | +|PM_SLEEP_MODE_STANDBY | Standby| 待机模式,执行 ST 的 Standby 模式 | +|PM_SLEEP_MODE_SHUTDOWN | Shutdown |停止模式,执行 ST 的 Shtudown 模式 | + +下面是具体的实现: + +```c + +#include +#include +#include + +static void sleep(struct rt_pm *pm, uint8_t mode) +{ + switch (mode) + { + case PM_SLEEP_MODE_NONE: + break; + + case PM_SLEEP_MODE_IDLE: + // __WFI(); + break; + + case PM_SLEEP_MODE_LIGHT: + /* Enter SLEEP Mode, Main regulator is ON */ + HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI); + break; + + case PM_SLEEP_MODE_DEEP: + /* Enter STOP 2 mode */ + HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI); + break; + + case PM_SLEEP_MODE_STANDBY: + /* Enter STANDBY mode */ + HAL_PWR_EnterSTANDBYMode(); + break; + + case PM_SLEEP_MODE_SHUTDOWN: + /* Enter SHUTDOWNN mode */ + HAL_PWREx_EnterSHUTDOWNMode(); + break; + + default: + RT_ASSERT(0); + break; + } +} + +int rt_hw_pm_init(void) +{ + static const struct rt_pm_ops _ops = + { + sleep, + RT_NULL, + RT_NULL, + RT_NULL, + RT_NULL + }; + + rt_uint8_t timer_mask = 0; + + /* Enable Power Clock */ + __HAL_RCC_PWR_CLK_ENABLE(); + + /* initialize system pm module */ + rt_system_pm_init(&_ops, timer_mask, RT_NULL); + + return 0; +} + +INIT_BOARD_EXPORT(rt_hw_pm_init); + +``` + +目前为止,ST 的休眠模式已经初步加入了,能够满足部分的应用场景。打开命令行,输入请求/释放休眠模式命令,可以观察到功耗显著降低。 + +- 移植时间补偿接口 + +某些情况下,我们可能需要系统在空闲时进入 Stop 模式,以达到更低的将功耗效果。L476 Stop 2 模式下的电流可以达到 1.6 uA 左右,ST 手册上对 Stop2 模式的描述如下: + +Stop 2 模式基于 Cortex-M4 深度睡眠模式与外设时钟门控。在 Stop 2 模式下, Vcore 域中的所有时钟都会停止, PLL、 MSI、 HSI16 和 HSE 振荡器也被禁止。一些带有唤醒功能(I2C3 和 LPUART)的外设可以开启 HSI16 以获取帧,如果该帧不是唤醒帧,也可以在接收到帧后关闭 HSI16。SRAM1、 SRAM2、 SRAM3 和寄存器内容将保留,所有 I/O 引脚的状态与运行模式下相同。 + +根据手册可知,Stop 2 模式会关闭系统时钟,当前的 OS Tick 基于内核的 Systick 定时器。那么在系统时钟停止后,OS Tick 也会停止,对于某些依赖 OS Tick 的应用,在进入 Stop 2 模式,又被中断唤醒后,就会出现问题,因此需要在系统唤醒后,对 OS Tick 进行补偿。Stop 2 模式下,绝大多数外设都停止工作,仅低功耗定时器 1(LP_TIM1)选择 LSI 作为时钟源后,仍然能正常运行,所以选择 LP_TIM1 作为 Stop 2 模式的时间补偿定时器。 + +休眠的时间补偿需要实现三个接口,分别用于启动低功耗定时器、停止定时器、唤醒后获取休眠的 Tick,下面是具体的实现: + +```c +static void pm_timer_start(struct rt_pm *pm, rt_uint32_t timeout) +{ + RT_ASSERT(pm != RT_NULL); + RT_ASSERT(timeout > 0); + + /** + * 当超时为 RT_TICK_MAX 时,表明系统此时没有依赖 OS Tick 的应用, + * 因此不启动低功耗定时器,避免超时唤醒而增加系统功耗 + */ + if (timeout != RT_TICK_MAX) + { + /* Convert OS Tick to pmtimer timeout value */ + timeout = stm32l4_pm_tick_from_os_tick(timeout); + if (timeout > stm32l4_lptim_get_tick_max()) + { + timeout = stm32l4_lptim_get_tick_max(); + } + + /* Enter PM_TIMER_MODE */ + stm32l4_lptim_start(timeout); + } +} + +static void pm_timer_stop(struct rt_pm *pm) +{ + RT_ASSERT(pm != RT_NULL); + + /* Reset pmtimer status */ + stm32l4_lptim_stop(); +} + +static rt_tick_t pm_timer_get_tick(struct rt_pm *pm) +{ + rt_uint32_t timer_tick; + + RT_ASSERT(pm != RT_NULL); + + timer_tick = stm32l4_lptim_get_current_tick(); + + return stm32l4_os_tick_from_pm_tick(timer_tick); +} + +int rt_hw_pm_init(void) +{ + static const struct rt_pm_ops _ops = + { + sleep, + RT_NULL, + pm_timer_start, + pm_timer_stop, + pm_timer_get_tick + }; + + rt_uint8_t timer_mask = 0; + + /* Enable Power Clock */ + __HAL_RCC_PWR_CLK_ENABLE(); + + /* initialize timer mask */ + timer_mask = 1UL << PM_SLEEP_MODE_DEEP; + + /* initialize system pm module */ + rt_system_pm_init(&_ops, timer_mask, RT_NULL); + + return 0; +} +``` + +休眠时间补偿的移植相对并不复杂,根据 Tick 配置低功耗定时器超时,唤醒后获取实际休眠时间并转换为OS Tick,告知 PM 组件即可。另外,从 Stop 2 模式唤醒后,默认会切换到内部的 MSI 时钟,通常需要重新配置时钟树。 + +- 移植运行模式 + +STM32L476 的运行模式移植主要是通过改变CPU 频率 和 稳压器,让其工作在 MR Range 2 或者 LP_RUN 模式,两个模式切换都会触发 CPU 频率改变的操作,这是一个比较危险的操作,我们在此处不作介绍。但是RT-Thread 仓库的 stm32l476-nucleo 有完整的实现,且已通过测试,如果项目中有此需求,可以参考该 bsp。 + +## 参考资料 + +* [《用户指南-电源管理》](../../../programming-manual/pm/pm.md) diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/.gitkeep b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..b09e6a4fdf05b586844b14353a9b145b9cbc4c08 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/.gitkeep @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/deep_sleep.png b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/deep_sleep.png new file mode 100644 index 0000000000000000000000000000000000000000..26d2f323b539490e797dde263e6bab9415b45d9e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/deep_sleep.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/menuconfig1.jpg b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/menuconfig1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5748c1423c1936834e92e7fd2e9a59e92c6d9e3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/menuconfig1.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/menuocnfig0.jpg b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/menuocnfig0.jpg new file mode 100644 index 0000000000000000000000000000000000000000..72c3766370714839a03e24156648def437f5a599 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/menuocnfig0.jpg differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm.png b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm.png new file mode 100644 index 0000000000000000000000000000000000000000..4a37b2b69ee10e49063c824eca5da63d3e72484f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_figure1.png b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_figure1.png new file mode 100644 index 0000000000000000000000000000000000000000..9c1b2c171fdafc816cb02e2630bfd7b874d82139 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_figure1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_figure2.png b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_figure2.png new file mode 100644 index 0000000000000000000000000000000000000000..7c9c64894c843c79918a678334b52a720b1e52ef Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_figure2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_process.png b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_process.png new file mode 100644 index 0000000000000000000000000000000000000000..97f016ab81e9eca7bb0251482bbebf2b5f897d2f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/pm_process.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/project.png b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/project.png new file mode 100644 index 0000000000000000000000000000000000000000..6aecd078848b287f270f62d08d2d5d4d910a8ca4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/project.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/stl476_mode.png b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/stl476_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..cfaa04f553a1d79b4944448fa6482863d827e18a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/stl476_mode.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/wakeup.png b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/wakeup.png new file mode 100644 index 0000000000000000000000000000000000000000..ff05a7a1621565ef5025ea9048b97248aae5cf73 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/pm/figures/wakeup.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/an0028-rtboot.md b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/an0028-rtboot.md new file mode 100644 index 0000000000000000000000000000000000000000..01eb28c85cb92db92f08708b4805efd9fa4257db --- /dev/null +++ b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/an0028-rtboot.md @@ -0,0 +1,342 @@ +# STM32 通用 Bootloader + +## 简介 + +为了能让开发者快速掌握 OTA 升级这把利器,RT-Thread 开发团队提供了通用的 Bootloader。开发者通过该 Bootloader 即可直接使用 RT-Thread OTA 功能,轻松实现对设备端固件的管理、升级与维护。 + +下图展示了 RT-Thread 通用 Bootloader 的软件框架: + +![1553826295876](figures/1553826295876.png) + +RT-Thread 通用 Bootloader 有如下特点: + +- 以 bin 文件的形式提供,无需修改即可使用 + +- 资源占用小,ROM 最小只需要 16KB,最大 32KB + +- 适用于多系列 STM32 芯片(目前支持 F1 和 F4 系列 ) + +- 支持各种 SPI Flash 存储固件 + +- 支持固件加解密功能 + +- 支持多种固件压缩方式 + +- 支持恢复出厂固件功能 + +- 以上功能均可自由配置 + +## 功能说明 + +Bootloader 的主要功能是更新 app 分区中的固件。 + +### 分区表介绍 + +通用 Bootloader 中的分区表包含如下三个分区: + +| 分区名 | 起始地址 | 分区大小 | 分区位置 | 介绍 | +| -------- | -------- | -------- | ----------------------------- | -------------- | +| app | 自定义 | 自定义 | 片内 Flash | 存储 app 固件 | +| download | 自定义 | 自定义 | 片内 Flash 或者片外 SPI Flash | 存储待升级固件 | +| factory | 自定义 | 自定义 | 片内 Flash 或者片外 SPI Flash | 存储出厂固件 | + +### 升级固件功能 + +当系统需要升级固件时,Bootloader 将从 `download` 分区将固件搬运到 `app` 分区,主要功能流程如下所示: + +1. Bootloader 启动时检查 `download` 分区和 `app` 分区中的固件版本。 +2. 如果两个固件版本相同,则跳转到 app 分区,Bootloader 运行结束。 +3. 固件版本不同则将 `download` 分区中的固件搬运到 `app` 分区。 +4. 在搬运的过程中 Bootloader 可以对固件进行校验、解密、解压缩等操作。 +5. 搬运完毕后,删除 `download` 分区中存储的固件。 +6. 重启系统跳转到 `app` 分区中的固件运行,Bootloader 运行结束。 + +Bootloader 工作过程如下图所示: + +![rt-boot 工作过程](figures/Bootloader_workprocess.png) + +### 恢复固件功能 + +当系统中的固件损坏,Bootloader 将从 `factory` 分区将固件搬运到 `app` 分区,主要功能流程如下所示: + +1. Bootloader 启动时检查触发固件恢复的引脚是否为有效电平。 +2. 如果有效电平持续超过 10S 则将 `factory` 分区中的固件搬运到 `app` 分区中。 +3. 如果有效电平没有持续超过 10S 则继续进行 2.2 小节中介绍的启动步骤。 +4. 在搬运的过程中 Bootloader 可以对固件进行校验、解密、解压缩等操作。 +5. 搬运完毕后,保持 `factory` 分区中的固件不变。 +6. 重启系统跳转到 `app` 分区中的固件运行,Bootloader 运行结束。 + +## 获取 Bootloader + +Bootloader 可以通过网页端在线生成的方式来获取。开发者根据自己使用的芯片,填写相关参数,然后点击生成按钮,即可在线生成 Bootloader。 + +Bootloader 在线获取地址: http://iot.rt-thread.com + +### 登陆账号 + +![1553247249515](figures/1553247249515.png) + +### 新建产品 + +![1553247299946](figures/1553247299946.png) + +![1553247372693](figures/1553247372693.png) + +### 进入设备管理 + +![1553247497496](figures/1553247497496.png) + +### 进入生成页面 + +进入 Bootloader 生成页面后,根据页面提示填写板卡参数,点击生成固件按钮即可在线自动生成 BootLoader,同时也会将生成的固件发送到用户邮箱中。 + +下面提供一个示例配置以供参考,开发者则需要根据自己手中板卡的实际情况,勾选和填写所需功能。 + +![1553675707296](figures/1553675707296.png) + +![1553673658407](figures/1553673658407.png) + +点击生成按钮后,等待大约一分钟即可通过自动下载或者邮件的方式获取定制的 Bootloader。 + +## 使用 Bootloader + +### 准备工作 + +1. 在线生成 Bootloader 时可以自定义使用某个引脚作为串口输出引脚,例如 PA9。波特率设置为 115200 ,通过 usb 转串口正确地连接到 PC 端。 + +2. 保证 jlink 正常连接到开发板。 + +### 烧录 Bootloader + +#### 方法 1: J-Flash 工具烧写 + +以 `STM32F407ZGT6` 芯片为例讲述如何使用 J-Flash 工具烧录 bootloader.bin 到开发板中,操作步骤如下: + +1. 打开 J-Flash 工具,配置烧录参数。 + +![J-Flash1](figures/J-Flash1.png) + +2. 选择打开固件功能。 + +![J-Flash1](figures/J-Flash2.png) + +3. 选择需要烧录的固件,这里选择在线生成后下载到本地的 Bootloader 固件。 + +![J-Flash1](figures/J-Flash3.png) + +4. 烧录 bootloader.bin 到指定地址 0x8000000。 + +![J-Flash1](figures/J-Flash4.png) + +5. 连接开发板。 + +![J-Flash1](figures/J-Flash5.png) + +6. 开始烧录固件。 + +![J-Flash1](figures/J-Flash6.png) + +固件烧录成功后会自动运行 Bootloader,打印出 RT-Thread 的 logo。 + +#### 方法 2: ST-LINK Utility 工具烧写 + +以下介绍如何使用 ST-LINK Utility 工具烧录 bootloader.bin 到开发板中,这需要配合 ST-LINK 进行烧写,操作步骤如下所示: + +1. 连接开发板。 + +![connet to the target](figures/utility1.png) + +2. 设置连接选项。 + +![setting connection](figures/utility2.png) + +3. 烧录:选择需要烧录的固件,这里选择在线生成后下载到本地的 Bootloader 固件。 + +![program and verify](figures/utility3.png) + +![select bin file and download](figures/utility4.png) + +## 制作 app 固件 + +本小节介绍如何使用 stm32 系列的 BSP 制作一个可以用于 OTA 升级的,包含 OTA 下载器功能 app 固件。 + +接下来的示例中所用的 BSP 路径为 `stm32/stm32f407-atk-explorer`。 + +固件中使用的分区表如下所示: + +| 分区名 | 起始地址 | 分区大小 | 分区位置 | +| -------- | --------- | -------- | ---------- | +| app | 0x8040000 | 128k | 片内 Flash | +| download | 0x8020000 | 128k | 片内 Flash | +| factory | 0x8060000 | 128k | 片内 Flash | + +制作该 app 固件有如下三个步骤: + +- 为 BSP 添加下载器功能,下载需要的软件包并修改 FAL 分区表 +- 修改 stm32 BSP 中断向量表跳转地址 +- 修改 BSP 链接脚本 + +在后面的章节中,将按照上述步骤来制作 app 固件。 + +### 添加下载器功能 + +本小节介绍如何将下载器功能添加到 app 固件中。 + +添加该功能需要使用 env 工具,本次下载的软件包在 iot 类别中,需要按照如下步骤操作: + + 1. 下载 ota_downloader 软件包,选中 Ymodem 功能。 + +![app1](figures/app1.png) + + 2. 添加 BSP Flash 驱动。 + +![app1](figures/app3.png) + +注:如果 BSP 没有该选项,则需要手动在 board 文件夹的 Kconfig 添加下面定义,保存,然后重新进入 menuconfig 即可。 + +``` + config BSP_USING_ON_CHIP_FLASH + bool "Enable on-chip FLASH" + default n +``` + +3. 配置完毕后,先使用 `pkgs --update` 命令将所需要的软件包下载下来,然后使用 `scons --target=mdk5` 命令重新生成 mdk 工程。 + +### 配置 FAL 分区 + +本小节将讲述如何初始化 FAL 组件,并修改 FAL 分区表。开发者需要对 FAL 进行简单入门,无需移植,只需要了解如何配置即可,详细内容可参考 [官方文档](https://github.com/RT-Thread-packages/fal)。 + +本次制作的 `app` 固件将附带下载器功能,下载器会将固件下载到 `download` 分区。根据第 4 章开始时的分区表可知,`download` 分区的地址为 0x8020000,而 `app` 分区的地址为 0x8040000。 + +#### 初始化 FAL + +由于 FAL 组件会被 ota_downloader 软件包自动选中,因此直接添加 FAL 组件的初始化代码即可。 + +![app1](figures/app5.png) + +2. 修改 `stm32f407-atk-explorer/board/ports/fal_cfg.h` 文件中的分区表,使分区表中 download 分区的起始地址和大小与 Bootloader 中的 download 分区一致。 + +![app1](figures/fal_partition.png) + +注意:如果 BSP 中没有该头文件,可以在该 BSP 目录下 `/packages/fal-latest/samples/porting` 中复制一份进行修改,其中分区地址和大小是根据实际 bootloader 中定义的大小进行设置。下图中标记处了可能需要修改的地方,请根据个人实际情况进行修改。 + +![modify the fal_cfg.h](figures/fal_cfg_modify.png) + +#### 修改 app 固件配置 + +由于 `app` 分区的起始地址为 `0x08040000`,`app` 固件如果想运行在该地址,就需要修改链接脚本和中断向量的跳转地址。 + +1. 修改固件的链接地址为 0x8040000。 + +![app1](figures/app6.png) + +2. 修改中断向量表的跳转基地址为 0x8040000。 + +首先在 main.c 文件中添加如下代码,这段代码的功能是重新设定中断向量跳转地址为 app 分区的地址。 + +```c +/** + * Function ota_app_vtor_reconfig + * Description Set Vector Table base location to the start addr of app(RT_APP_PART_ADDR). +*/ +static int ota_app_vtor_reconfig(void) +{ + #define NVIC_VTOR_MASK 0x3FFFFF80 + /* Set the Vector Table base location by user application firmware definition */ + SCB->VTOR = RT_APP_PART_ADDR & NVIC_VTOR_MASK; + + return 0; +} +INIT_BOARD_EXPORT(ota_app_vtor_reconfig); +``` + +然后在 main 函数中添加版本信息打印,如下图所示: + +![app1](figures/nvic_fun.png) + +3. 下载 app 程序 + +直接点击下载程序,固件就会被烧录到 app 分区。Bootloader 启动后将跳转到 app 分区运行,实验效果如下图所示: + +![app1](figures/exp_show.png) + +可以看到串口输出的信息 `The current version of APP firmware is 1.0.0`,即当前固件的版本为 1.0.0 。 + +## 打包 app 固件 + +本小节讲述如何使用 RT-Thread OTA 固件打包器对 app 固件进行打包,制作可以被下载到 download 分区的升级固件。固件打包工具可以在 ota_downloader 软件包下的 tools 文件夹内找到。 + +1. 在对固件进行打包操作前,先修改 `stm32f407-atk-explorer/applications/main.c` 中 APP_VERSION 宏的值为 2.0.0 作为参照,**然后重新编译一遍生成新的 rtthread.bin 文件**,修改内容如下图所示: + +![app1](figures/app_pack_1.png) + +2. 双击打开 `tools\ota_packager\rt_ota_packaging_tool.exe` 程序,使用 OTA 固件打包工具将上一步编译出的 `rtthread.bin` 文件打包成可被升级的 `rtthread.rbl` 文件,如下图所示: + +![app1](figures/app8.png) + +固件打包器提供三种固件压缩方式:fastlz、quicklz 和 gzip,一种固件加密方式 AES256。开发者可以根据实际需求选择合适的加密压缩方式。 + +## 执行 OTA + +### Ymodem 升级固件 + +使用 Ymodem 协议升级固件时,推荐使用 Xshell 终端。 + +在 msh 命令行中输入 `ymodem_ota` 命令后,点击鼠标右键,然后在菜单栏找到用 YMODEM 发送选项发送文件,如下图所示: + +1. 选择 Ymodem 方式发送升级固件。 + +![ymodem_ota](figures/ymodem_ota.png) + +2. 选中之前 OTA 固件打包工具生成的 rtthread.rbl 文件。 + +![ymodem_ota](figures/ymodem_ota_select.png) + +接下来升级固件就会通过 Ymodem 的方式被下载到 download 分区。 + +### 执行 OTA 升级 + +固件被下载到 download 分区后,系统会自动重启,执行 OTA 升级。 + +![1550821330959](figures/1550821330959.png) + +升级完毕后可以看到如下效果: + +![http_ota](figures/app_pack_2.png) + +串口输出信息为 `The current version of APP firmware is 2.0.0` ,说明固件已经被升级到 2.0.0 版本了。 + +### 更多固件下载方式 + +HTTP/HTTPS 固件升级是另外一种固件下载方式,制作下载器时如果开启了系统中的网络驱动,即可使用此种方式下载固件。具体步骤与 **Ymodem 升级固件** 小节大体一致。以下是配置截图: + +![http_ota](figures/http_menuconfig.png) + +![http_ota](figures/en_eth_drv.png) + +![http_ota](figures/app3.png) + +在终端输入 `http_ota http://xxx/xxx/rtthreadf.rbl` 命令,系统将会从链接 http://xxx/xxx/rtthreadf.rbl` 处下载固件到 download 分区,之后系统会自动重启,执行 OTA 升级程序。 + +![http_ota](figures/http_ota.png) + +### 恢复出厂固件 + +在生成页面中选中了 **恢复出厂固件引脚** 功能后,即开启了出厂固件恢复功能。开发者可以将制作好的 app 固件烧录至 `factory` 分区中,在系统启动前按下恢复出厂固件引脚(可选择一个或者两个引脚作为固件恢复触发引脚)并保持 10S,即可从 `factory` 分区中恢复出厂固件到 `app` 分区中。 + +恢复出厂按键引脚配置如下: + +![1553830878300](figures/1553830878300.png) + +`factory` 分区的配置如下所示: + +![1553832436431](figures/1553832436431.png) + +按照上图的配置,`factory` 分区被设置为从片内 Flash 首地址偏移 0x60000 的位置开始,大小为 128KB,想要使用固件恢复功能,则需要将可用的 app 固件烧录到该分区中,固件恢复过程如下图所示: + +![1553840590537](figures/1553840590537.png) + + + + + diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1550821330959.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1550821330959.png new file mode 100644 index 0000000000000000000000000000000000000000..300bb842dd0485be775859550a4934ac581c649a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1550821330959.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247249515.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247249515.png new file mode 100644 index 0000000000000000000000000000000000000000..90250e7f972b4a10dd65cd0cd0a6692b9094dfc0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247249515.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247299946.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247299946.png new file mode 100644 index 0000000000000000000000000000000000000000..033970a0a3c209432198b3328de78f28d532d297 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247299946.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247372693.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247372693.png new file mode 100644 index 0000000000000000000000000000000000000000..8e9fc19480430fd2e507aa8bcd19a3f193f31210 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247372693.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247497496.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247497496.png new file mode 100644 index 0000000000000000000000000000000000000000..21295522795da6f94c3cda53502c5d4059108038 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247497496.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247783487.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247783487.png new file mode 100644 index 0000000000000000000000000000000000000000..f54606983cff6b78eba096f284d574cff7d05b9a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553247783487.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553673658407.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553673658407.png new file mode 100644 index 0000000000000000000000000000000000000000..ad24cef431a1cd180ca074dd11b76b461ffcdc75 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553673658407.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553675707296.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553675707296.png new file mode 100644 index 0000000000000000000000000000000000000000..2936a1672a21925cb64bc80ad8486c24b069c918 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553675707296.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553761443975.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553761443975.png new file mode 100644 index 0000000000000000000000000000000000000000..e078f7ca34571f94222f2303efabadd1b45bd021 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553761443975.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553826295876.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553826295876.png new file mode 100644 index 0000000000000000000000000000000000000000..c7027a436adfa97b452956949160c912017f7ac5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553826295876.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553830878300.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553830878300.png new file mode 100644 index 0000000000000000000000000000000000000000..de9f125174fc477be3059e3d927998485c7b1948 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553830878300.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553832436431.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553832436431.png new file mode 100644 index 0000000000000000000000000000000000000000..9fdc94c4c30d8ea08c4cd4cab92168420bda427f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553832436431.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553840590537.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553840590537.png new file mode 100644 index 0000000000000000000000000000000000000000..e7231a034a995607f473164f751460a6617db849 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/1553840590537.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/Bootloader_workprocess.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/Bootloader_workprocess.png new file mode 100644 index 0000000000000000000000000000000000000000..9c4cfc2fb95bda29ad90a9e17626d9055ab8882f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/Bootloader_workprocess.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash1.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash1.png new file mode 100644 index 0000000000000000000000000000000000000000..a7481cddfe4621849d5e47779c2c31962db0f798 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash2.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash2.png new file mode 100644 index 0000000000000000000000000000000000000000..2816226a7b769feac02bf816a961ebd4d65ec858 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash3.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash3.png new file mode 100644 index 0000000000000000000000000000000000000000..35742ad739e0dc44cca8cef3dbc65b1075042c6b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash4.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash4.png new file mode 100644 index 0000000000000000000000000000000000000000..f29648000ed7a999843667068e2580b019a4d801 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash4.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash5.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash5.png new file mode 100644 index 0000000000000000000000000000000000000000..754bd78df37cda0a60dd1b65441aafa96597f997 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash5.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash6.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash6.png new file mode 100644 index 0000000000000000000000000000000000000000..35fef9ba36e99fdcf7b4eb6f1409dde871b4c23d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/J-Flash6.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app1.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app1.png new file mode 100644 index 0000000000000000000000000000000000000000..403e13920cc7684e68b683e49b207f1b4963ee7c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app2.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app2.png new file mode 100644 index 0000000000000000000000000000000000000000..e50bb56d35bd2981481f8517e417eeb14edd9a5b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app3.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app3.png new file mode 100644 index 0000000000000000000000000000000000000000..340587da44b7d036145e6cfac7bc2f2f62ac32b6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app4.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app4.png new file mode 100644 index 0000000000000000000000000000000000000000..160c5aa10d11ec9eb6f20c3849b975dca417e87f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app4.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app5.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app5.png new file mode 100644 index 0000000000000000000000000000000000000000..a5ae87f48f1d98674aee12f71f20128ff72fb408 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app5.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app5xxx.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app5xxx.png new file mode 100644 index 0000000000000000000000000000000000000000..3d43bcb752331e6fd129759e2c2f7994f38a2694 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app5xxx.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app6.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app6.png new file mode 100644 index 0000000000000000000000000000000000000000..ae6b91bc8776f749a74ee4ca6346b9e6e24c4079 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app6.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app7.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app7.png new file mode 100644 index 0000000000000000000000000000000000000000..bf61766ee37349c0fe79adeafabd9e07657ff2d2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app7.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app8.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app8.png new file mode 100644 index 0000000000000000000000000000000000000000..155e737ceeec5415bba2467a4096169f33992603 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app8.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app9.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app9.png new file mode 100644 index 0000000000000000000000000000000000000000..cabcaee1b947c13b0ddf75a1fd27737dfef5833d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app9.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app_pack_1.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app_pack_1.png new file mode 100644 index 0000000000000000000000000000000000000000..33717acc891506d583eaf176d4b4271275e04b49 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app_pack_1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app_pack_2.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app_pack_2.png new file mode 100644 index 0000000000000000000000000000000000000000..95cd7138e950d9d34c07719d0cf329b0342baf03 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/app_pack_2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/en_eth_drv.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/en_eth_drv.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b795535cfe9c61c2c0ebc3742cb2953fd6074b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/en_eth_drv.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/exp_show.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/exp_show.png new file mode 100644 index 0000000000000000000000000000000000000000..358130c807a80fb07ce5e636ffac38e240b37ba8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/exp_show.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/fal_cfg_modify.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/fal_cfg_modify.png new file mode 100644 index 0000000000000000000000000000000000000000..d49b64cbfc9c8fc5d3bef7baf44fcb60dfe2f9c5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/fal_cfg_modify.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/fal_partition.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/fal_partition.png new file mode 100644 index 0000000000000000000000000000000000000000..030f07ce1b4e77c8e0d0f9d09470c1c1d7615b11 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/fal_partition.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/http_menuconfig.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/http_menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..0d5f181f86a0cc239ef1f70acc05d1bd517d8bb6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/http_menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/http_ota.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/http_ota.png new file mode 100644 index 0000000000000000000000000000000000000000..477fe59641d093d601c60826b99e9207cfe90796 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/http_ota.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/nvic_fun.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/nvic_fun.png new file mode 100644 index 0000000000000000000000000000000000000000..67b759e988bb4ceb4458193da840da2d495a73f7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/nvic_fun.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility1.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility1.png new file mode 100644 index 0000000000000000000000000000000000000000..8eb358d50812df3d030e3f3aa4139b54cc10a55d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility1.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility2.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility2.png new file mode 100644 index 0000000000000000000000000000000000000000..4b64e40c0990beda38ad7812451a572d2ad84035 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility2.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility3.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility3.png new file mode 100644 index 0000000000000000000000000000000000000000..1ae20c5662b21c8461d17c008aef01a849ddfd51 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility3.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility4.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility4.png new file mode 100644 index 0000000000000000000000000000000000000000..4735f75952b1fc3a866c563f7265f3dfa4d6ba7b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/utility4.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/ymodem_ota.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/ymodem_ota.png new file mode 100644 index 0000000000000000000000000000000000000000..86f8080237157cc338c6ba2f0477ec98efb608af Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/ymodem_ota.png differ diff --git a/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/ymodem_ota_select.png b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/ymodem_ota_select.png new file mode 100644 index 0000000000000000000000000000000000000000..0d89e927c2f28469276d76d7e4ea1b3325d31c28 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/application-note/system/rtboot/figures/ymodem_ota_select.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/bug1.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/bug1.png new file mode 100644 index 0000000000000000000000000000000000000000..26419b508cc88b1b22883f05883556eb887aae07 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/bug1.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/bug2.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/bug2.png new file mode 100644 index 0000000000000000000000000000000000000000..491766d8ab6d764737d4ee794378276baf21eb74 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/bug2.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/checklist.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/checklist.png new file mode 100644 index 0000000000000000000000000000000000000000..db627bc5aad404b030c3204953530c5a91f0cab8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/checklist.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/checklistyes.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/checklistyes.png new file mode 100644 index 0000000000000000000000000000000000000000..30bb3bbfdd319672df36ba6c15ef41cc3b04e86e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/checklistyes.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/checkok.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/checkok.png new file mode 100644 index 0000000000000000000000000000000000000000..9be4ef0a5e6f0dcad48aa201e51768fcf292d1c4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/checkok.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/cla.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/cla.png new file mode 100644 index 0000000000000000000000000000000000000000..7e3f7021e21aa728a9ca95a92bf7187ac3e5582a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/cla.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit.png new file mode 100644 index 0000000000000000000000000000000000000000..c69427353228de389680229d922040ef42f8e836 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit2.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit2.png new file mode 100644 index 0000000000000000000000000000000000000000..193f44d4586e2de315c9bec5b41d953e12dbf8eb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit2.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit3.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit3.png new file mode 100644 index 0000000000000000000000000000000000000000..ef373c416a94774b6ab91a86fd18ca91985e10b8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/cloneformgit3.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/create_pull_request.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/create_pull_request.png new file mode 100644 index 0000000000000000000000000000000000000000..69fb7a0e253d2ff8207237b2f0ef1b6c2fa49fc6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/create_pull_request.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/fork.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/fork.png new file mode 100644 index 0000000000000000000000000000000000000000..4010586778c96f3981c88e446624637eddf250a9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/fork.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/pullrequest.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/pullrequest.png new file mode 100644 index 0000000000000000000000000000000000000000..912a0369b580cb5f055f279d8005603063a487ab Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/pullrequest.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/figures/push.png b/rt-thread-version/rt-thread-standard/development-guide/github/figures/push.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f13ca8d07006cf53bb504c7d108521ca38e8ce Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/github/figures/push.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/github/github.md b/rt-thread-version/rt-thread-standard/development-guide/github/github.md new file mode 100644 index 0000000000000000000000000000000000000000..17e0eba3873b7ad4f01caa85815b36b70b6d3157 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/development-guide/github/github.md @@ -0,0 +1,151 @@ +# 向 RT-Thread 贡献代码 + +我们真诚地感谢您的贡献,欢迎通过 GitHub 的 fork 和 Pull Request 流程来提交代码。 + +首先解释一下 Pull Request 这个词,pull request的意思是推送请求,开发者发起 Pull Request 的目的是请求仓库维护人采用开发者提交的代码。 + +下面是摘自[知乎](https://www.zhihu.com/question/21682976) 网友的一段解释: + +我尝试用类比的方法来解释一下pull reqeust。想想我们中学考试,老师改卷的场景吧。你做的试卷就像仓库,你的试卷肯定会有很多错误,就相当于程序里的bug。老师把你的试卷拿过来,相当于先fork。在你的卷子上做一些修改批注,相当于git commit。最后把改好的试卷给你,相当于发pull request,你拿到试卷重新改正错误,相当于merge。 + +当你想更正别人仓库里的错误时,要按照下面的流程进行: + +* 先 fork 别人的仓库,相当于拷贝一份别人的资料。因为不能保证你的修改一定是正确的,对项目有利的,所以你不能直接在别人的仓库里修改,而是要先fork到自己的git仓库中。 + +* clone 到自己的本地分支,做一些 bug fix,然后发起 pull request给原仓库,让原仓库的管理者看到你提交的修改。 + +* 原仓库的管理者 review 这个 bug,如果是正确的话,就会 merge 到他自己的项目中。merge 的意思就是合并,将你修改的这部分代码合并到原来的仓库中添加代码或者替换掉原来的代码。至此,整个 Pull Request 的过程就结束了。 + +## 编程风格 + +RT-Thread 代码编程风格请参考 rt_thread 项目 documentation 目录下的 coding_style_cn.txt文件。 + +## 准备工作 + +* 安装 git +* 安装 TortoiseGit 工具,这个工具是 git 的一种图形化界面 +* 注意安装 git 的时候记得勾选将 git 所在目录添加到系统环境变量 + +现在以rt-thread仓库为例说明贡献代码的流程: + +## fork + +将 rt-thread 仓库 fork 到自己的 git 仓库中。 + +![avatar](figures/fork.png) + +## 克隆(clone) + +将 rt-thread 仓库 clone 到自己的本地 PC。 + +![image](figures/cloneformgit.png) + +![image](figures/cloneformgit2.png) + +![image](figures/cloneformgit3.png) + +## 创建本地分支 + +建议从 master 分支创建自己的开发分支,可使用命令 `git checkout -b branchName`。 + +## 开发 + +发现了一个小 bug 并进行修改。 + +![image](figures/bug1.png) + +注意:开发时,代码需要符合 [RT-Thread 代码规范](https://github.com/RT-Thread/rt-thread/blob/master/documentation/coding_style_cn.md),请仔细检查。 + +## 提交(commit) + +向本地仓库提交 bug. + +![image](figures/bug2.png) + +> [!NOTE] +> 注:若本地分支多个 commit,为了保证 RT-Thread 仓库 commit 干净,请整理一下本地的 commit,不接受 Pull Request 有超过 5 个及以上个commit。 + +## Push 到远程仓库 + +push 到开发者自己的远程仓库中。 + +![image](figures/push.png) + +## 发起 Pull Request + +在 git 仓库中选择自己修改了的分支,点击 create Pull Request 按钮发起 pull request。 + +![image](figures/pullrequest.png) + +![image](figures/create_pull_request.png) + +## checklist 核对 + +在正式发起 Pull Request 之前,需要根据 Preview 里面默认的描述信息即 checklist 仔细核对代码,在没问题的 checklist 对应选项复选框填写[x]确认,**注意[x]两边没有空格**。比如若代码是成熟版本,请选择成熟版本,且可以添加相应的描述信息。checklist 核对完成才可发起 Pull Request。 + +![image](figures/checklistyes.png) + +![image](figures/checklist.png) + +## 签署 CLA + +第一次为 RT-Thread 贡献代码需要需要签署 Contributor License Agreement。 + +![image](figures/cla.png) + +请确认 CLA 显示签署成功及 CI 编译通过,如下图所示: + +![image](figures/checkok.png) + +> [!NOTE] +> 注:注意不要使用非 GitHub 账号提交 commmit,或者使用不同的账号提交 commit 后提 Pull Request,这会导致 CLA 签署失败。 + +## Pull Request 审核 + +发起请求成功后,RT-Thread 维护人就可以看到你提交的代码。并且会对代码进行审核,相关的评审意见会填写在 GitHub 上。请及时查看 PR 状态并根据评审意见更新代码。 + +## 合并成功 + +Pull Request 审核没有问题代码就会被合并到 RT-Thread 仓库中。这样一次 Pull Request 就成功了。 + +至此,我们就完成了一次代码贡献的过程。 + +## 和 RT-Thread 仓库保持更新 + +RT-Thread GitHub 仓库的内容是经常处于更新的状态,若要基于最新的 RT-Thread 代码开发,那么就需要更新本地仓库。 + +clone 后本地 master 分支内容默认是和 clone 的远程仓库的 master 分支内容保持一致。建议本地新建其他分支开发,master 则和 fork 的 RT-Thread 原仓库保持同步,master 分支不要有内容修改,可以根据以下步骤保持同步: + +* 查看现有的远程仓库,一般只有一个默认的 origin,也就是自己的远程仓库: + +```c +$ git remote -v +origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch) +origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push) +``` + +* 添加 RT-Thread 远程仓库并命名为 rtt,rtt 可以是自己自定义的名称: + +```c +$ git remote add rtt https://github.com/RT-Thread/rt-thread.git +``` + +* 查看本地跟踪的所有远程仓库: + +```c +$ git remote -v +origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (fetch) +origin https://github.com/YOUR_USERNAME/YOUR_FORK.git (push) +rtt https://github.com/RT-Thread/rt-thread.git (fetch) +rtt https://github.com/RT-Thread/rt-thread.git (push) +``` + +* 从 RT-Thread 远程仓库的 master 分支拉取代码并合并到本地的 master 分支: + +```c +git pull rtt master +``` + +## 参考资料 + +* 若对贡献代码还有不明白的地方,可以参考 Git 官方中文文档[《GitHub - 对项目做出贡献》](https://git-scm.com/book/zh/v2/GitHub-%E5%AF%B9%E9%A1%B9%E7%9B%AE%E5%81%9A%E5%87%BA%E8%B4%A1%E7%8C%AE)章节。 diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/figures/add_new_index.png b/rt-thread-version/rt-thread-standard/development-guide/package/figures/add_new_index.png new file mode 100644 index 0000000000000000000000000000000000000000..c40b188f20da3ccc53b8fda6967828b8b36c486b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/package/figures/add_new_index.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/figures/add_new_source.png b/rt-thread-version/rt-thread-standard/development-guide/package/figures/add_new_source.png new file mode 100644 index 0000000000000000000000000000000000000000..b8d34044de48bb8505abcdeb23d71c55778caf2c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/package/figures/add_new_source.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/figures/create_index_1.png b/rt-thread-version/rt-thread-standard/development-guide/package/figures/create_index_1.png new file mode 100644 index 0000000000000000000000000000000000000000..6a6c70731e6e91a537c78989fc534a6178c303c6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/package/figures/create_index_1.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/figures/create_index_2.png b/rt-thread-version/rt-thread-standard/development-guide/package/figures/create_index_2.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c69d78302efc688faaed8bb74e60df9c8b5833 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/package/figures/create_index_2.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/figures/kconfig_ex.png b/rt-thread-version/rt-thread-standard/development-guide/package/figures/kconfig_ex.png new file mode 100644 index 0000000000000000000000000000000000000000..9dee7916f380727f7ad0bfd439835913febb1bd0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/package/figures/kconfig_ex.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/figures/make_software_index.png b/rt-thread-version/rt-thread-standard/development-guide/package/figures/make_software_index.png new file mode 100644 index 0000000000000000000000000000000000000000..0385bfc75bcf00fd2c27c0c4fe5b5adfc47d8d55 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/package/figures/make_software_index.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/figures/release_process.png b/rt-thread-version/rt-thread-standard/development-guide/package/figures/release_process.png new file mode 100644 index 0000000000000000000000000000000000000000..85a6922795c01cdda9bd72e3fbcd2492806a2fb6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/package/figures/release_process.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/figures/software_index.png b/rt-thread-version/rt-thread-standard/development-guide/package/figures/software_index.png new file mode 100644 index 0000000000000000000000000000000000000000..32bbd54bb7a72c304eff724065ba2e2dd5da4dad Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/package/figures/software_index.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/package/package.md b/rt-thread-version/rt-thread-standard/development-guide/package/package.md new file mode 100644 index 0000000000000000000000000000000000000000..5419a6ba2a014f4d31af76083765cb3e208b8328 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/development-guide/package/package.md @@ -0,0 +1,184 @@ +# 软件包开发指南 + +**软件包定义**:运行于 RT-Thread 物联网操作系统平台上,面向不同应用领域的软件组件形成一个个软件包,由软件包描述信息,软件包源代码或库文件组成。 + +在制作软件包前,需要先对软件包的功能进行准确的定义,确保 **不要耦合** 与产品业务逻辑有关的代码,提高软件包的 **通用性** 。 + +软件包应该包含以下内容: + +* 软件包代码和说明文档。 +* 软件包索引。 + +例如作为示例的 hello 软件包,这两部分的地址为分别是: + +* [软件包代码和说明文档](https://github.com/RT-Thread-packages/hello) +* [软件包索引](https://github.com/RT-Thread/packages/tree/master/misc/hello) + +### 整理软件包代码和说明文档 + +制作软件包前需要仔细阅读[示例软件包说明文档](https://github.com/RT-Thread-packages/hello),参考示例软件包的文件夹结构。 + +软件包代码和说明文档应该由以下部分组成: + +* 软件包的功能代码; +* 根目录下的 SConscript 文件,用于和 RT-Thread 环境一起进行编译; +* 根目录下的 examples 文件夹,用于提供一份使用的例子; +* 如果需要加入 git submodule ,则可以直接在根目录下添加; +* 根目录下的 README.md 文件,说明这个软件包的功能; +* 根目录下的 docs 文件夹,放置除了 README 之外的其他文档; +* 如果需要额外的移植代码,可将其放在 port 文件夹下; + +### 创建软件包索引 + +**软件包索引** :指存放在 `env\packages` 文件夹下的软件包 **描述文件** 。以 `env\packages\packages\iot\pahomqtt` 文件夹为例,包含内容如图所示: + +![image](./figures/software_index.png) + +- **Kconfig 文件**:软件包的配置项,如软件包版本、功能选项等信息。 +- **package.json 文件**:存放软件包的名称、简介、各个版本对应的下载链接等信息。 + +#### 使用索引生成向导 + +我们可以使用 Env 的软件包索引生成向导功能来制作软件包索引,命令为 `pkgs --wizard`,大致流程如下如图所示: + +![image](./figures/make_software_index.png) + +生成内容如下图所示: + +![image](./figures/create_index_1.png) + +![image](./figures/create_index_2.png) + +> 提示:SConscript 文件在制作软件包代码时使用,将其移动到软件包源码文件夹中即可,无需保留在索引文件夹中。 + +#### 修改 package.json 文件 + +- **package.json 文件**介绍: + +```json +{ + "name" : "pahomqtt", + "description" : "a pahomqtt package for rt-thread", # 软件包描述信息 + "keywords" : [ + "pahomqtt" + ], + "site" : [ + { + "version" : "v1.0.0", + "URL" : "https://pahomqtt-1.0.0.zip", # 根据版本号修改软件包压缩包的下载地址 + "filename" : "pahomqtt-1.0.0.zip", + "VER_SHA" : "fill in the git version SHA value" # 压缩包形式无需填写 + }, + { + "version" : "latest", # latest 版本 + "URL" : "https://xxxxx.git", # 可以填入 Git 仓库地址 + "filename" : "Null for git package", + "VER_SHA" : "fill in latest version branch name,such as mater" # 填入 SHA 值或者分支名 + } + ] +} +``` + +关于文件中 `URL` 值,每个版本可以填入两种类型 : + +- **Git** :可以填入 Git 仓库地址和对应版本的 SHA 值。一般 `latest` 版本会在 **SHA** 填入`master`。 +- **压缩包** : 指定软件包压缩包的下载地址,此时无需填入 **SHA** 。 + +**package.json** 是软件包的描述信息文件,包括软件包名称,软件包描述,作者等信息,以及必须的 package 代码下载链接。另外,请务必包含许可证的说明,使用了哪种许可证( GPLv2,LGPLv2.1,MIT,Apache license v2.0,BSD 等)。 + +修改后的 package.json 大致如下: +```json +{ + "name" : "pahomqtt", + "description" : "Eclipse Paho MQTT C/C++ client for Embedded platforms", # 更新了描述信息 + "keywords" : [ + "pahomqtt" + ], + "site" : [ + { + "version" : "v1.0.0", # v1.0.0 版本 + "URL" : "https://github.com/RT-Thread-packages/paho-mqtt.git", # 更新了 git 仓库地址 + "filename" : "paho-mqtt-1.0.0.zip", + "VER_SHA" : "cff7e82e3a7b33e100106d34d1d6c82e7862e6ab" # 填入了指定版本的 SHA 值 + }, + { + "version" : "latest", # 最新版本 + "URL" : "https://github.com/RT-Thread-packages/paho-mqtt.git", + "filename" : "paho-mqtt.zip", + "VER_SHA" : "master" # 填入 master + } + ] +} +``` + +#### 修改 Kconfig 文件 + + **Kconfig** 文件内容大致如下: + +![image](./figures/kconfig_ex.png) + +软件包索引中的 Kconfig 文件主要由 menuconfig 命令使用,软件包的一些选项必须在 Kconfig 文件中定义出来,注意事项如下: + +* 1、索引向导自动生成的 **Kconfig** 文件中的内容大多是必须的,可以参考其他软件包修改选项的值,但是**不要删除选项**。 + +* 2、软件包必须包含一个以`PKG_USING_`开头的配置项,这样RT-Thread的包管理器才能将其正确识别。假设这个包的名称叫做SOFTA,那么软件包总选项应该是`PKG_USING_SOFTA`; + +* 3、和这个SOFTA软件包相关的其他选项,需要以`SOFTA_`开头的配置项进行定义,可以是`SOFTA_USING_A`或者`SOFTA_ENABLE_A`等方式。 + +* 4、支持 **latest** 版本的软件包也至少需要一个固定版本,以防止在某个时候找不到合适的版本。 + +* 5、软件包如果还需要更多的配置项,可以搜索 **Kconfig 语法** ,并参考已有的软件包来对 Kconfig 文件进行修改。 + +### 上传软件包 + +软件包可以上传到 git 上或者其他可供下载的地方,推荐使用 git 仓库的方式进行保存,这样方便更新软件包版本。 + +参考:[RT-Thread 软件包仓库]( https://github.com/RT-Thread-packages) + +### 测试软件包 + +- **软件包的下载**:将软件包索引拷贝到 `env\packages\packages` 下对应的位置,然后在 Env 中尝试在线下载软件包,测试下载是否成功。 +- **软件包的功能**:下载完成后,使用 scons 命令重新编译项目,在相应的环境下运行,测试软件包功能是否正确。 +- **软件包版本的切换**:尝试在 menuconfig 下切换软件包的版本,查看版本切换是否正常。 + +### 提交软件包索引 + +最后需要将软件包索引通过 PR 流程推送到:[https://github.com/RT-Thread/packages](https://github.com/RT-Thread/packages) + +[点击这里](../github/github.md)了解如何提交 PR 。 + +## 软件包索引源的管理 + +Env 可以从多个软件包源来下载软件包,每个源的软件包列表就存放在 `env\packages` 文件夹中,如 `env\packages\packages` 文件夹下就是 RT-Thread 官方的软件包列表。 + +### 添加软件包源 + +- 复制一份 RT-Thread 官方的 packages 文件夹,修改文件夹名称后,删除该文件夹内不需要的软件包索引,将需要的索引添加进去。 + +![image](./figures/add_new_index.png) + +- 更新 `env\packages` 文件夹下的 Kconfig 文件,在 Kconfig 文件中添加软件包源信息。 + +![image](./figures/add_new_source.png) + +### 删除软件包源 + +- 删除软件源文件夹; +- 将 Kconfig 文件中相应的源文件夹信息删除。 + +## 新版本发布流程 + +软件包发布新版本需要遵循以下流程: + +1、检查软件包,确保软件包功能使用正常。 + +2、在 github 上使用 release 功能发布新版本,如果没有权限可以通知管理员发布新版本。如果不知道该如何 release,可以参考 [paho-mqtt 软件包仓库](https://github.com/RT-Thread-packages/paho-mqtt/releases),发布新版本界面如下所示: + + ![release](./figures/release_process.png) + +3、修改本地软件包索引,在 kconfig 文件和 package.json 文件中添加新版本的信息。 + +4、在本地测试新版本软件包的下载和删除,以及安装是否正常,确保软件包可以被正常添加到工程中使用。 + +5、向[软件包索引](https://github.com/RT-Thread/packages)提交 PR,并通知管理员合并。 + diff --git a/rt-thread-version/rt-thread-standard/development-guide/sensor/figures/sensor.png b/rt-thread-version/rt-thread-standard/development-guide/sensor/figures/sensor.png new file mode 100644 index 0000000000000000000000000000000000000000..b52961814a2c6c89f0a54dc42d0b740ce1d00afc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/sensor/figures/sensor.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/sensor/figures/sensor1.png b/rt-thread-version/rt-thread-standard/development-guide/sensor/figures/sensor1.png new file mode 100644 index 0000000000000000000000000000000000000000..b0cd5a84ffadf8a5c8628b0529cb288fcf935bcc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/sensor/figures/sensor1.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/sensor/sensor_driver.md b/rt-thread-version/rt-thread-standard/development-guide/sensor/sensor_driver.md new file mode 100644 index 0000000000000000000000000000000000000000..fbc56a1a6a3fddcd0df8fdcf64593d17b50007fa --- /dev/null +++ b/rt-thread-version/rt-thread-standard/development-guide/sensor/sensor_driver.md @@ -0,0 +1,205 @@ +# 传感器驱动框架介绍 + +## 背景与概述 + +Sensor 是物联网重要的一部分,“Sensor 之于物联网”相当于“眼睛之于人类”。人没有眼睛就看不到这大千的花花世界,物联网没有了 Sensor 更是不能感知这变化万千的世界。 + +现在,为物联网开发的 Sensor 已经很多了,有加速度计(Accelerometer),磁力计(Magnetometer),陀螺仪(Gyroscope),光感计(Ambient light sensor),接近光(Proximity),气压计(Barometer/pressure),湿度计(Humidometer)等等。这些传感器,世界上的各大半导体厂商都有出产,虽然增加了市场的可选择性,同时也加大了应用程序开发的难度。因为不同的传感器厂商、不同的传感器都需要配套自己独有的驱动才能运转起来,这样在开发应用程序的时候就需要针对不同的传感器做适配,自然加大了开发难度。为了降低应用开发的难度,增加传感器驱动的可复用性,我们设计了 Sensor 驱动框架。 + +Sensor 驱动框架的作用是:为上层提供统一的操作接口,提高上层代码的可重用性;简化底层驱动开发的难度,只要实现简单的 ops(operations: 操作命令) 就可以将传感器注册到系统上。 + +## 整体框架 + +Sensor 驱动框架的整体架构图如下: + +![sensor](figures/sensor1.png) + +它为上层提供的是标准 device 接口`open/close/read/write/control` ,为底层驱动提供的是简单的 ops 接口:`fetch_data/control`。并且框架支持 module(模块),为底层存在耦合的传感器设备提供服务。 + +## 工作原理 + +Sensor 设备其实是对标准设备 `rt_device` 的一个丰富,是在原有标准设备的基础上增加了 Sensor 自己独有的一部分 `属性` 和 `控制命令` ,如下图所示: + +![sensor](figures/sensor.png) + +整个 Sensor 设备包括两个部分: + +- 继承自标准设备的一些特性,包括:标准的控制接口 、`回调函数`、`device_id` 等。 +- Sensor 设备独有的部分,包括:`Sensor 的类型`、`相关的信息`、`特有的控制命令`、`ops`、以及一些 `数据的结构`。 + +### sensor 的结构体 + +Sensor 设备的结构体如下所示: + +```c +struct rt_sensor_device +{ + struct rt_device parent; /* The standard device */ + + struct rt_sensor_info info; /* The sensor info data */ + struct rt_sensor_config config; /* The sensor config data */ + + void *data_buf; /* The buf of the data received */ + rt_size_t data_len; /* The size of the data received */ + + const struct rt_sensor_ops *ops; /* The sensor ops */ + + struct rt_sensor_module *module; /* The sensor module */ + + rt_err_t (*irq_handle)(rt_sensor_t sensor); /* Called when an interrupt is generated, registered by the driver */ +}; +typedef struct rt_sensor_device *rt_sensor_t; +``` + +### Sensor 的信息 + +struct rt_sensor_info info 里存储的是一些与 Sensor 自身相关的信息,在 Sensor 设备注册的时候提供,在使用的过程中不应修改其内容。具体成员如下所示。 + +```c +struct rt_sensor_info +{ + rt_uint8_t type; /* The sensor type */ + rt_uint8_t vendor; /* Vendor of sensors */ + const char *model; /* model name of sensor */ + rt_uint8_t unit; /* unit of measurement */ + rt_uint8_t intf_type; /* Communication interface type */ + rt_int32_t range_max; /* maximum range of this sensor's value. unit is 'unit' */ + rt_int32_t range_min; /* minimum range of this sensor's value. unit is 'unit' */ + rt_uint32_t period_min; /* Minimum measurement period,unit:ms. zero = not a constant rate */ + rt_uint8_t fifo_max; /* Maximum depth of fifo */ +}; +``` + +Sensor 的类型暂时只有以下几种,如果有新的传感器类型,可以提 PR 添加上。 + +```c +#define RT_SENSOR_CLASS_ACCE (1) /* Accelerometer */ +#define RT_SENSOR_CLASS_GYRO (2) /* Gyroscope */ +#define RT_SENSOR_CLASS_MAG (3) /* Magnetometer */ +#define RT_SENSOR_CLASS_TEMP (4) /* Temperature */ +#define RT_SENSOR_CLASS_HUMI (5) /* Relative Humidity */ +#define RT_SENSOR_CLASS_BARO (6) /* Barometer */ +#define RT_SENSOR_CLASS_LIGHT (7) /* Ambient light */ +#define RT_SENSOR_CLASS_PROXIMITY (8) /* Proximity */ +#define RT_SENSOR_CLASS_HR (9) /* Heart Rate */ +#define RT_SENSOR_CLASS_TVOC (10) /* TVOC Level */ +#define RT_SENSOR_CLASS_NOISE (11) /* Noise Loudness */ +#define RT_SENSOR_CLASS_STEP (12) /* Step sensor */ +#define RT_SENSOR_CLASS_FORCE (13) /* Force sensor */ +``` +其他的几个成员,分别是厂商、model(如:"mpu6050")、传感器数据的单位、通信接口类型、测量的最大范围、测量的最小范围、最小测量周期、硬件 FIFO 的最大深度。 + +### Sensor 的配置 + +Sensor 驱动框架抽象出了一些公共的配置选项,这些可配置的选项置于 `struct rt_sensor_config` 里, 成员如下: + +```c +struct rt_sensor_config +{ + struct rt_sensor_intf intf; /* sensor interface config */ + struct rt_device_pin_mode irq_pin; /* Interrupt pin, The purpose of this pin is to notification read data */ + rt_uint8_t mode; /* sensor work mode */ + rt_uint8_t power; /* sensor power mode */ + rt_uint16_t odr; /* sensor out data rate */ + rt_int32_t range; /* sensor range of measurement */ +}; +``` +这些配置项中的 intf 和 irq_pin 是为了将传感器和硬件解耦而抽象出来的,通过在底层初始化的时候传入 `struct rt_sensor_config` 这个参数,完成了通信接口的解耦。 + +```c +struct rt_sensor_intf +{ + char *dev_name; /* The name of the communication device */ + rt_uint8_t type; /* Communication interface type */ + void *user_data; /* Private data for the sensor. ex. i2c addr,spi cs,control I/O */ +}; +``` +其余的一些配置项是用 Sensor 特有控制命令控制的,如下所示: + +```c +#define RT_SENSOR_CTRL_GET_ID (0) /* 读设备ID */ +#define RT_SENSOR_CTRL_GET_INFO (1) /* 获取设备信息 */ +#define RT_SENSOR_CTRL_SET_RANGE (2) /* 设置传感器测量范围 */ +#define RT_SENSOR_CTRL_SET_ODR (3) /* 设置传感器数据输出速率,unit is HZ */ +#define RT_SENSOR_CTRL_SET_MODE (4) /* 设置工作模式 */ +#define RT_SENSOR_CTRL_SET_POWER (5) /* 设置电源模式 */ +#define RT_SENSOR_CTRL_SELF_TEST (6) /* 自检 */ +``` +结合 ops 中的 control 接口使用,就可以完成传感器的配置了。 + +### Sensor 数据的存储 + +为了方便数据的解析,规定每一个类型的 Sensor 都有自己独有的数据结构,这些成员之间使用`共用体`以减少代码量。 + +```c +/* 3-axis Data Type */ +struct sensor_3_axis +{ + rt_int32_t x; + rt_int32_t y; + rt_int32_t z; +}; +struct rt_sensor_data +{ + rt_uint32_t timestamp; /* The timestamp when the data was received */ + rt_uint8_t type; /* The sensor type of the data */ + union + { + struct sensor_3_axis acce; /* Accelerometer. unit: mG */ + struct sensor_3_axis gyro; /* Gyroscope. unit: mdps */ + struct sensor_3_axis mag; /* Magnetometer. unit: mGauss */ + rt_int32_t temp; /* Temperature. unit: dCelsius */ + rt_int32_t humi; /* Relative humidity. unit: permillage */ + rt_int32_t baro; /* Pressure. unit: pascal (Pa) */ + rt_int32_t light; /* Light. unit: lux */ + rt_int32_t proximity; /* Distance. unit: centimeters */ + rt_int32_t hr; /* Heat rate. unit: HZ */ + rt_int32_t tvoc; /* TVOC. unit: permillage */ + rt_int32_t noise; /* Noise Loudness. unit: HZ */ + rt_uint32_t step; /* Step sensor. unit: 1 */ + rt_int32_t force; /* Force sensor. unit: mN */ + } data; +}; +``` + +### 特有的 ops + +ops(操作函数)包含两个函数指针, 一个的作用是获取传感器数据(fetch_data),另一个的作用是通过控制命令控制传感器(control)。 + +```c +struct rt_sensor_ops +{ + rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len); + rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg); +}; +``` + +### 注册方式 + +传感器驱动框架提供了一个 Sensor 注册函数,通过传入 Sensor 的控制块,名称,标志位和私有数据,就可以完成传感器设备的注册。 + +```c +int rt_hw_sensor_register(rt_sensor_t sensor, + const char *name, + rt_uint32_t flag, + void *data); +``` + +这样看来 Sensor 驱动框架依托于标准的设备框架,只要将传感器驱动对接到 Sensor 的 ops 上,并通过调用 `rt_hw_sensor_register` 函数注册为 Sensor 设备就可以通过标准的设备接口控制传感器了。 + +### module支持 + +module 的定义是解决底层有耦合的两个传感器而出现的,有些传感器既有加速度计的功能又有陀螺仪的功能,并且他们的FIFO是共用的,在 FIFO 模式下,只能将两个类型的传感器的数据同时读出,这就说明他们的数据是耦合的。 + +为了解决这个问题,我们定义了 module 的类型 + +```c +struct rt_sensor_module +{ + rt_mutex_t lock; /* The module lock */ + + rt_sensor_t sen[RT_SENSOR_MODULE_MAX]; /* The module contains a list of sensors */ + rt_uint8_t sen_num; /* Number of sensors contained in the module */ +}; +``` +里面包含有耦合的传感器的设备控制块指针,通过这个功能就可以在读取陀螺仪的数据的时候,同时更新加速度计的值,解决了底层耦合的问题。 diff --git a/rt-thread-version/rt-thread-standard/development-guide/sensor/sensor_driver_development.md b/rt-thread-version/rt-thread-standard/development-guide/sensor/sensor_driver_development.md new file mode 100644 index 0000000000000000000000000000000000000000..5708673115337d1fca2eaa229d3724dcee46915b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/development-guide/sensor/sensor_driver_development.md @@ -0,0 +1,264 @@ +# 传感器驱动开发指南 + +## 概述 + +### 目的与概述 + +本文档为 RT-Thread Sensor 驱动框架下传感器驱动的开发指南文档,给开发团队提供开发标准和规范。 + +### 阅读对象 + +- 进行传感器驱动开发的工程人员 + +> [!NOTE] +> 注:在阅读本篇文档之前,请先查看 [传感器驱动框架介绍](sensor_driver.md)。 + +## 开发指南 + +开发一个传感器驱动一般需要下面的几个步骤:调研与准备、开发、测试、提交。 + +开发过程可以参考已经支持的传感器,点击查看[支持的传感器列表](../../programming-manual/device/sensor/sensor_list.md)。 + +### 调研与准备 + +根据 datasheet 或其他途径,了解传感器的特性,并记录下来,如下面几种: + +- 传感器类型 +- 通讯接口(i2c/spi/...) +- 测量范围 +- 最短测量周期 +- 支持几种工作模式、电源模式 + +### 开发 + +开发的主要任务就是对接 Sensor 驱动框架的 ops 接口,然后注册为 Sensor 设备,进而能够通过驱动框架控制传感器的相关行为。 + +#### ops 接口对接 + +sensor 框架共给出了两个接口(`fetch_data` / `control`),需要在驱动中实现这两个接口。 + +**fetchdata** + +作用: 获取传感器的数据。接口原型: + +```c +rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len); +``` + +Sensor 驱动框架当前默认支持 轮询(RT_DEVICE_FLAG_RDONLY)、中断(RT_DEVICE_FLAG_INT_RX)、FIFO(RT_DEVICE_FLAG_FIFO_RX) 这三种打开方式。需要在这里判断传感器的工作模式,然后再根据不同的模式返回传感器数据。 + +如下所示: + +```c +static rt_size_t xxx_acc_fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len) +{ + if (sensor->parent.open_flag & RT_DEVICE_FLAG_RDONLY) + { + return _xxx_acc_polling_get_data(sensor, buf, len); + } + else if (sensor->parent.open_flag & RT_DEVICE_FLAG_INT_RX) + { + return _xxx_acc_int_get_data(sensor, buf, len); + } + else if (sensor->parent.open_flag & RT_DEVICE_FLAG_FIFO_RX) + { + return _xxx_acc_fifo_get_data(sensor, buf, len); + } + else + return 0; +} +``` + +开发人员在返回数据时应先标识存储数据的数据类型,然后再填充数据域与时间戳,如下所示: + +```c +sensor_data->type = RT_SENSOR_CLASS_ACCE +sensor_data->data.acce.x = acceleration.x; +sensor_data->data.acce.y = acceleration.y; +sensor_data->data.acce.z = acceleration.z; +sensor_data->timestamp = rt_sensor_get_ts(); +``` + +> [!NOTE] +> 注:- 时间戳的获取函数请使用 Sensor 驱动框架提供的时间戳获取函数 `rt_sensor_get_ts`。 + - 在 FIFO 模式下底层数据可能会有耦合,需要使用 module,同时更新两个传感器的数据。 + - 要将数据的单位转换为 Sensor 驱动框架里规定的数据单位。 + +数据单位参考如下: + +| 传感器名称 | 类型 | 单位 | 说明 | +| ---------- | ------------------------- | ----------- | -------------------------------------- | +| 加速度计 | RT_SENSOR_CLASS_ACCE | mg | 1 g = 1000 mg, 1 g = 9.8 m/s2 | +| 陀螺仪 | RT_SENSOR_CLASS_GYRO | mdps | 1 dps = 1000 mdps, dps(度每秒) | +| 磁力计 | RT_SENSOR_CLASS_MAG | mGauss | 1 G = 1000 mGauss(毫高斯) | +| 环境光 | RT_SENSOR_CLASS_LIGHT | lux | 亮度流明值 | +| 接近光 | RT_SENSOR_CLASS_PROXIMITY | centimeters | 代表物体到传感器的距离大小,单位:厘米 | +| 气压计 | RT_SENSOR_CLASS_BARO | Pa | 100 Pa = 1 hPa(百帕) | +| 温度计 | RT_SENSOR_CLASS_TEMP | °c/10 | 0.1摄氏度 | +| 湿度计 | RT_SENSOR_CLASS_HUMI | ‰ | 相对湿度RH,一般用‰表示 | +| 心率计 | RT_SENSOR_CLASS_HR | bpm | 次数每分钟 | +| 噪声 | RT_SENSOR_CLASS_NOISE | HZ | 频率单位 | +| 计步计 | RT_SENSOR_CLASS_STEP | 1 | 步数:无量纲单位 1 | +| 力传感器 | RT_SENSOR_UNIT_MN | mN | 压力的大小:10^-3 N | + +*注:后期会迭代增加新的传感器数据单位。* + +**control** + +```c +rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg); +``` + +传感器的控制就是依靠这个接口函数实现的,通过判断传入的命令字的不同执行不同的操作,目前支持以下命令字: + +```c +#define RT_SENSOR_CTRL_GET_ID (0) /* 读设备ID */ +#define RT_SENSOR_CTRL_GET_INFO (1) /* 获取设备信息(由框架实现,在驱动中不需要实现)*/ +#define RT_SENSOR_CTRL_SET_RANGE (2) /* 设置传感器测量范围 */ +#define RT_SENSOR_CTRL_SET_ODR (3) /* 设置传感器数据输出速率,unit is HZ */ +#define RT_SENSOR_CTRL_SET_MODE (4) /* 设置工作模式 */ +#define RT_SENSOR_CTRL_SET_POWER (5) /* 设置电源模式 */ +#define RT_SENSOR_CTRL_SELF_TEST (6) /* 自检 */ +``` + +需要在驱动里实现这个函数,如下所示: + +```c +static rt_err_t xxx_acc_control(struct rt_sensor_device *sensor, int cmd, void *args) +{ + rt_err_t result = RT_EOK; + + switch (cmd) + { + case RT_SENSOR_CTRL_GET_ID: + result = _xxx_acc_get_id(sensor, args); + break; + case RT_SENSOR_CTRL_SET_RANGE: + result = _xxx_acc_set_range(sensor, (rt_int32_t)args); + break; + case RT_SENSOR_CTRL_SET_ODR: + result = _xxx_acc_set_odr(sensor, (rt_uint32_t)args & 0xffff); + break; + case RT_SENSOR_CTRL_SET_MODE: + result = _xxx_acc_set_mode(sensor, (rt_uint32_t)args & 0xff); + break; + case RT_SENSOR_CTRL_SET_POWER: + result = _xxx_acc_set_power(sensor, (rt_uint32_t)args & 0xff); + break; + case RT_SENSOR_CTRL_SELF_TEST: + break; + default: + return -RT_ERROR; + } + return result; +} +``` +> [!NOTE] +> 注:这里需要注意传来参数的数据类型是由 struct rt_sensor_config 这个结构体规定的,因此 `RT_SENSOR_CTRL_SET_RANGE` 这个命令传来的参数是 `rt_int32_t` 类型的,需要经过强转一次,才可以得到正确的参数。 + +然后 实现一个设备接口的结构体 ops 存储上面的接口函数, + +```c +static struct rt_sensor_ops xxx_ops = +{ + xxx_acc_fetch_data, + xxx_acc_control +}; +``` + +#### 设备注册 + +完成 Sensor 的 ops 的对接之后还要注册一个 sensor 设备,这样上层才能找到这个传感器设备,进而进行控制。 + +设备的注册一共需要下面几步:创建一个 `rt_sensor_t` 的结构体指针,然后为结构体分配内存,并完成相关初始化。 + +```c +int rt_hw_xxx_init(const char *name, struct rt_sensor_config *cfg) +{ + rt_int8_t result; + rt_sensor_t sensor = RT_NULL; + + sensor = rt_calloc(1, sizeof(struct rt_sensor_device)); + if (sensor == RT_NULL) + return -1; + + sensor->info.type = RT_SENSOR_CLASS_ACCE; + sensor->info.vendor = RT_SENSOR_VENDOR_STM; + sensor->info.model = "xxx_acce"; + sensor->info.unit = RT_SENSOR_UNIT_MG; + sensor->info.intf_type = RT_SENSOR_INTF_I2C; + sensor->info.range_max = SENSOR_ACC_RANGE_16G; + sensor->info.range_min = SENSOR_ACC_RANGE_2G; + sensor->info.period_min = 100; + + rt_memcpy(&sensor->config, cfg, sizeof(struct rt_sensor_config)); + sensor->ops = &sensor_ops; + + result = rt_hw_sensor_register(sensor, name, RT_DEVICE_FLAG_RDONLY | RT_DEVICE_FLAG_FIFO_RX, RT_NULL); + if (result != RT_EOK) + { + LOG_E("device register err code: %d", result); + rt_free(sensor); + return -RT_ERROR; + } + + LOG_I("acc sensor init success"); + return 0; +} +``` + +> [!NOTE] +> 注:- `rt_hw_sensor_register` 会为传入的 name 自动添加前缀,如`加速度计`类型的传感器会自动添加 `acce_` 的前缀。由于系统默认的设备名最长为 7 个字符,因此如果传入的名称超过3个字符的话会被裁掉。 + - 注册时如传感器支持 FIFO 的话,需要添加 RT_DEVICE_FLAG_FIFO_RX 的标志位。 + - 如果两个设备有耦合的话,需要利用 module 解耦。初始化一个 module,将两个传感器的设备控制块赋值到里面,并将 module 的地址赋值给两个传感器设备。框架会自动完成 module 锁的创建。 + +其中传入参数 `struct rt_sensor_config *cfg` 是用来解耦硬件的通讯接口的,通过在底层驱动初始化的时候传入这个参数,实现硬件接口的配置。里面包含 `struct rt_sensor_intf` 这个结构体, + +```c +struct rt_sensor_intf +{ + char *dev_name; /* The name of the communication device */ + rt_uint8_t type; /* Communication interface type */ + void *user_data; /* Private data for the sensor. ex. i2c addr,spi cs,control I/O */ +}; +``` + +其中,type 表示接口的类型,dev_name 表示设备的名字,例如"i2c1"。user_data 是此接口类型的一些私有数据,如果是 I2C 的话,这里就是传感器对应的 i2c 地址,传入方式为 `(void*)0x55`。 + +在底层驱动初始化时,需要先初始化此结构体,然后作为参数传入,以便完成通讯接口的解耦。类似这样: + +```c +#define irq_pin GET_PIN(B, 0) + +int lps22hb_port(void) +{ + struct rt_sensor_config cfg; + + cfg.intf.dev_name = "i2c1"; + cfg.intf.user_data = (void *)0x55; + cfg.irq_pin.pin = irq_pin; + cfg.irq_pin.mode = PIN_MODE_INPUT_PULLDOWN; + rt_hw_xxx_init("xxx", &cfg); + + return RT_EOK; +} +INIT_APP_EXPORT(lps22hb_port); +``` +### 测试 + +1. 通过 list_device 命令查看对应设备是否注册成功。 +2. 通过导出的测试函数 `sensor_polling/int/fifo `,判断能否成功读取数据。 +3. 测试其他的模式和控制接口 + +### 提交 + +Sensor 驱动需要按 `软件包` 的方式提交,具体的结构可以参考已有的 Sensor 软件包。具体的提交流程也可以参考文档中心:[软件包开发指南](https://www.rt-thread.org/document/site/development-guide/package/package/) + +### 注意事项 + +- 动态分配内存时使用 `rt_calloc`,该 API 会将申请的内存初始化为 0,无需再手动清零。 +- 静态定义的变量请赋初值,未使用的初始化为 0。 +- 如果可能的话,请尽量支持多实例。需要注意下面的问题: + - 不能出现全局变量 + - 所有的配置信息存储到 sensor 结构体里 + - sensor 结构体里没有的配置,利用 rt_device 的 user_data 来存储 diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/gt9147_reg.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/gt9147_reg.png new file mode 100644 index 0000000000000000000000000000000000000000..c7da71b4ca5fb95944850dfbf3d838ef2025fce1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/gt9147_reg.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/power_on.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/power_on.png new file mode 100644 index 0000000000000000000000000000000000000000..09448cebde57606b7043ddd2577098a590db6da9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/power_on.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/read_data.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/read_data.png new file mode 100644 index 0000000000000000000000000000000000000000..30dc4b0005b5332f71c78d903ced7bf0a7e6fb29 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/read_data.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch new file mode 100644 index 0000000000000000000000000000000000000000..cd9f6d48ab866d06a75619a2482f82169b7dca32 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch @@ -0,0 +1,31 @@ +@startuml + +participant Ӧó +participant IO豸 +participant Touch豸 +participant Touch豸 + +Touch豸->Touch豸: Touch豸 + +Touch豸->Touch豸: עTouch豸 rt_hw_touch_register() + +Touch豸->IO豸: עIO豸 rt_device_register() + +Ӧó->IO豸: Touch豸 rt_device_find() + +Ӧó->IO豸: Touch豸 rt_device_open() +IO豸->Touch豸:rt_touch_open() +Touch豸->Touch豸: rt_touch_irq_init() + +Ӧó->IO豸: ȡTouch豸 rt_device_read() +IO豸->Touch豸:rt_touch_read() +Touch豸->Touch豸: touch_readpoint() + +Ӧó->IO豸: Touch豸 rt_device_control() +IO豸->Touch豸:rt_touch_control() +Touch豸->Touch豸: touch_control() + +Ӧó->IO豸: رTouch豸 rt_device_close() +IO豸->Touch豸:rt_touch_close() +Touch豸->Touch豸: rt_touch_irq_disable() +@enduml \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch.vsd b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch.vsd new file mode 100644 index 0000000000000000000000000000000000000000..cc6a75d593e0a95a4a41c0299b61443cbd232160 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch.vsd differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_hardware.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_hardware.png new file mode 100644 index 0000000000000000000000000000000000000000..b51b6c7c7615de3f430a0cf437957f3287664382 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_hardware.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_irq.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_irq.png new file mode 100644 index 0000000000000000000000000000000000000000..b24d76fb5880e50ce46c94e7f4dce56b7cb02886 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_irq.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_pull.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_pull.png new file mode 100644 index 0000000000000000000000000000000000000000..5c646ef258e1620ae19aad35033de3ec77c380cb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_pull.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_reg.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_reg.png new file mode 100644 index 0000000000000000000000000000000000000000..b8670447aeefea9505a461555a79274ee4a58277 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_reg.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_uml.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_uml.png new file mode 100644 index 0000000000000000000000000000000000000000..9564428e4c446dfbf513f1feb238311e0ab34449 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/touch_uml.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/figures/write_data.png b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/write_data.png new file mode 100644 index 0000000000000000000000000000000000000000..84a3f8b034a0cbbf6494f01915c1e55005639982 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/development-guide/touch/figures/write_data.png differ diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/touch_device.md b/rt-thread-version/rt-thread-standard/development-guide/touch/touch_device.md new file mode 100644 index 0000000000000000000000000000000000000000..2c9341092c1793af79596024b899900e8b50c358 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/development-guide/touch/touch_device.md @@ -0,0 +1,227 @@ +# Touch 设备驱动框架详解 + +## 概述 + +### 特性 +RT-Thread 为方便管理和使用触摸设备和与 UI 的对接,抽象出了 Touch 设备驱动框架。应用程序可通过 I/O 设备管理接口来访问触摸设备硬件。Touch 设备驱动框架有以下特点: + + * 为上层提供统一的操作接口,提高上层代码的可重用性; + * 简化底层驱动开发的难度,只要实现简单的 ops(operations: 操作命令) 就可以将触摸设备注册到系统上。 + * 支持中断模式读取触摸点数据; + * Touch 框架支持读取多触摸点数据。 + +### 软件架构 + +Touch 设备驱动框架层级如下图所示: + +![Touch 设备驱动框架层级图](figures/touch_hardware.png) + +* I/O 设备管理器 `src\device.c`: + * 为应用程序提供通用的 I/O 设备管理接口,比如 rt_device_open(read/control) 等,用户使用这些接口访问 Touch 设备。 + * 为设备驱动提供了通用的 IO 设备的操作方法接口 struct rt_device_ops(init/open/close/read/write/control),设备驱动需要实现这些接口。 + +* Touch 设备驱动框架 `components\drivers\touch\touch.c`: + * Touch 设备驱动框架实现了 I/O 设备的操作方法接口: rt_touch_open(close/read/control) 。 + * 为 Touch 设备驱动提供了 Touch 设备的操作方法 `struct rt_touch_ops`。 + * 为 Touch 设备提供了注册接口 `rt_hw_touch_register`。 + +* Touch 设备驱动 + * 实现由 Touch 设备驱动框架提供的具体 Touch 设备的操作方法 `struct rt_touch_ops`。 + * 注册 Touch 设备到操作系统; + +### Touch 设备使用序列 + + * Touch 设备驱动创建 Touch 设备实例,并将该 Touch 设备通过 `rt_hw_touch_register()` 注册到 Touch 设备驱动框架中。Touch 设备驱动框架通过 `rt_device_register()` 将 Touch 设备注册到 I/O 设备管理器中。 + + * Touch 设备驱动通过 Touch 设备驱动框架提供的接口访问硬件 Touch 控制器。 + +Touch 设备注册和使用序列图如下图所示: + +![Touch 设备使用序列图](figures/touch_uml.png) + +## Touch 设备注册机制 + +### Touch 设备控制块 +Touch 设备驱动框架定义了 Touch 设备模型,它从设备对象派生而来,Touch 设备模型定义如下面代码所示: + +```c +struct rt_touch_device +{ + struct rt_device parent; /* 设备基类 */ + struct rt_touch_info info; /* Touch 设备基本信息*/ + struct rt_touch_config config; /* Touch 设备配置参数 */ + + const struct rt_touch_ops *ops; /* Touch 的操作方法 */ + rt_err_t (*irq_handle)(rt_touch_t touch); /* 中断回调函数 */ +}; +``` +* 设备基类(parent):Touch 设备继承至设备基类 `struct rt_device` ,这个类是所有设备的父类。 +* Touch 设备基本信息(info):Touch 设备的基本信息例如分辨率、支持的触点个数等; +* Touch 设备的配置参数(config):Touch 设备初始化的时候会根据配置参数初始化 Touch 芯片 +* Touch 设备的操作方法(ops):提供了 Touch 设备的操作方法,由 Touch 设备驱动实现。 + + ### Touch 的信息 +struct rt_touch_info info 里存储的是一些与 Touch 自身相关的信息,在使用的过程中不应修改其内容。具体成员如下所示: + +```c +struct rt_touch_info +{ + rt_uint8_t type; /* 触摸芯片类型:电阻屏/电容屏 */ + rt_uint8_t vendor; /* 厂商信息 */ + rt_uint8_t point_num; /* 支持的触控点数 */ + rt_int32_t range_x; /* X 轴分辨率 */ + rt_int32_t range_y; /* Y 轴分辨率 */ +}; +``` + +### Touch 的配置 +Touch 驱动框架抽象出了一些公共的配置选项,这些可配置的选项置于 `struct rt_touch_config` 里, 成员如下: +```c +struct rt_touch_config +{ + struct rt_device_pin_mode irq_pin; /* 中断引脚和模式 */ + char *dev_name; /* 接口总线名称 */ + void *user_data; /* 扩展数据 */ +}; +``` + +### Touch 设备操作方法 + +Touch 设备的操作方法是 Touch 设备驱动需要实现的主要功能, 结构体原型如下: + +```c +/* Touch 设备的操作方法 */ +struct rt_touch_ops +{ + rt_size_t (*touch_readpoint)(struct rt_touch_device *touch, void *buf, rt_size_t touch_num); + rt_err_t (*touch_control)(struct rt_touch_device *touch, int cmd, void *arg); +}; +``` + +操作方法的描述如下表所示: + +| 接口 | 描述 | +|:------------------|:------------------------------------| +|touch_readpoint | 读取 Touch 设备触摸点信息 | +|touch_control | 根据命令控制字 cmd 控制 Touch 设备 | + +#### 接口函数 `touch_readpoint` + +读取触点信息的接口函数 `touch_readpoint` 的描述如下表所示: + +| 参数 | 描述 | +|----------|------------------------------------| +| touch | Touch 设备句柄 | +| buf | 读到的数据指针(数据的类型如下触点信息组成所示) | +| touch_num | 需要读取的触点信息的个数 | +| 返回 | —— | +| rt_size_t | 实际读到的触点信息的个数 | + + +#### 接口函数 `touch_control` + +控制 Touch 设备的接口函数 `touch_control` 的描述如下表所示: + + +| 参数 | 描述 | +|----------|------------------------------------| +| touch | Touch 设备句柄 | +| cmd | 控制命令 | +| arg | 控制参数 | +| 返回 | —— | +| RT_EOK | 成功 | +| 其他错误码 | 失败 | + +该接口根据控制命令 cmd 和控制参数 arg 控制 Touch 设备。 +参数 cmd 可取以下宏定义值: +```c +#define RT_TOUCH_CTRL_GET_ID (0) +#define RT_TOUCH_CTRL_GET_INFO (1) +#define RT_TOUCH_CTRL_SET_MODE (2) +#define RT_TOUCH_CTRL_SET_X_RANGE (3) +#define RT_TOUCH_CTRL_SET_Y_RANGE (4) +#define RT_TOUCH_CTRL_SET_X_TO_Y (5) +#define RT_TOUCH_CTRL_DISABLE_INT (6) +#define RT_TOUCH_CTRL_ENABLE_INT (7) +``` + +### Touch 设备注册 + +Touch 设备的实例化、操作方法的实现和注册都是在 Touch 驱动中完成。Touch 设备注册函数的源代码如下所示: +```c +/* 注册 Touch 设备 */ +int rt_hw_touch_register(rt_touch_t touch, + const char *name, + rt_uint32_t flag, + void *data) +{ + rt_int8_t result; + rt_device_t device; + RT_ASSERT(touch != RT_NULL); + /* 获取设备基本对象 */ + device = &touch->parent; + /* 保存 Touch 设备的操作方法 */ +#ifdef RT_USING_DEVICE_OPS + device->ops = &rt_touch_ops; +#else + device->init = RT_NULL; + device->open = rt_touch_open; + device->close = rt_touch_close; + device->read = rt_touch_read; + device->write = RT_NULL; + device->control = rt_touch_control; +#endif + /* 设备类型为 Touch 设备 */ + device->type = RT_Device_Class_Touch; + device->rx_indicate = RT_NULL; + device->tx_complete = RT_NULL; + device->user_data = data; + /* 注册 Touch 设备 */ + result = rt_device_register(device, name, flag | RT_DEVICE_FLAG_STANDALONE); + ...... +} +``` + +注册 Touch 设备的时候 Touch 设备控制块会被初始化,操作方法也会保存,最终会调用 `rt_device_register()` 注册 Touch 到内核对象管理器中。RT-Thread 采用内核对象管理器来管理所有的内核对象,系统中所有的设备都属于设备类型对象。所有注册到系统的设备都被链接到了设备对象管理链表上。链接到对象管理器中的 Touch 设备如下图所示: + +![Touch 设备注册序列图](figures/touch_reg.png) + +## Touch 设备数据传输 + +### 触点信息组成 + +```c +struct rt_touch_data +{ + rt_uint8_t event; + rt_uint8_t track_id; + rt_uint8_t width; + rt_uint16_t x_coordinate; + rt_uint16_t y_coordinate; + rt_tick_t timestamp; +}; +``` + +* event:触摸事件,包括抬起事件、按下事件和移动事件。 +* track_id:每个触摸点都有自己的触摸轨迹,这个数据用来保存触摸轨迹 ID。 +* width:触摸点宽度。 +* x_coordinate:触摸点 X 轴坐标。 +* y_coordinate:触摸点 Y 轴坐标。 +* timestamp:触摸事件时间戳。 + +### 中断模式接收触点信息 + +中断模式下接收数据有很好的实时性,这种模式下对 CPU 的占用率大大降低,应用程序只用在底层中断发生时去读取数据,不需要一直占用 CPU。使用中断接收模式时,首先应用程序打开 Touch 设备会指定打开标志为 `RT_DEVICE_FLAG_INT_RX`,此时 Touch 设备驱动框架会按照初始化时指定的中断引脚和模式去使能中断。 + +中断接收数据的应用: + +一般情况下应用程序使用 Touch 接收数据会配合操作系统提供的通信机制一起使用。Touch 数据读取线程会等待一个信号量,获取信号量后才会去读取数据。Touch 接收数据回调函数则会释放信号量,通知应用程序有触摸事件发生。流程如下图所示: + +![Touch 中断读取流程](figures/touch_irq.png) + + +## 参考资料 + +* 《Touch 设备使用指南》 + +* 《Touch 设备驱动开发指南》 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/development-guide/touch/touch_gt9147_driver.md b/rt-thread-version/rt-thread-standard/development-guide/touch/touch_gt9147_driver.md new file mode 100644 index 0000000000000000000000000000000000000000..cc0203ab211154a76012b69974bc9f8530d12270 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/development-guide/touch/touch_gt9147_driver.md @@ -0,0 +1,437 @@ +# TOUCH 从设备 GT9147 驱动开发指南 + +## 开发步骤 + +本文档为 RT-Thread Touch 从设备驱动开发指南,给开发团队提供开发指导和规范。 + +> [!NOTE] +> 注:在阅读本篇文档之前,请先查看文档 [《Touch 设备驱动框架详解》](./touch_device.md)。 + +Touch 从设备驱动开发可以按照如下的步骤进行: + + 1. 创建 Touch 从设备。 + + 2. 实现 Touch 设备的操作方法。 + + 3. 注册 Touch 设备到操作系统。 + +本文档将会以 GT9147 为例讲解 Touch 从设备驱动的具体实现。 + +## GT9147 简介 + +### 概述 + +GT9147 是采用最新的电容检测技术,内置高性能微信号检测电路,可以很好地解决 LCD 干扰和共模干扰问题。软件方面,专门基于单层互容的电气环境设计,可支持 5 点触控。同时支持 HotKnot 功能。 + +GT9147 可同时识别 5 个触摸点位的实时准确位置,移动轨迹及触摸面积。并可根据主控需要,读取相应点数的触摸信息。功能框图如下图所示: + + ![GT9147 功能框图](figures/gt9147_reg.png) + +GT9147 采用 I2C 连接的方式读取转换数据。GT9147 相关使用引脚和 MCU 的连接方式如下表所示: + +| GT9147 引脚 | MCU 引脚 | +|----------------|----------------------------------------------------------------------------------------------------------------------------| +| RSTB(复位) | GPIO 输出,输出高、低来控制 GT9147 的 RESET 口为高或低。为保证可靠复位,建议 RESET 脚输出低 100μs 以上。 | +| INT(中断) | GPIO 输入, 主控的 INT 口线需具有上升沿或下降沿中断触发功能,并且当其在输入态时,主控端必须设为悬浮态,取消内部上下拉功能; | +| I2C | I2C 总线 | + +### 硬件配置 + +这里以配置 GT9147 从设备地址为 0xBA/0xBB 为例,配置的时序图如下图所示: + + ![GT9147 从设备地址配置](figures/power_on.png) + +* 首先配置 INT 引脚和 RST 引脚均为输出。 +* INT 引脚输出低电平。 +* 拉低 RST 引脚,为保证可靠复位,建议 RST 引脚输出低 100 us 以上。 +* 拉高 RST 引脚,为保证可靠复位,建议 RST 引脚输出高 100 us 以上。 +* 配置 INT 引脚为输入,用于接收中断。 + +GT9147 使用到了 RT-Thread 提供的 PIN 设备管理框架、I2C 设备管理框架和 Touch 设备管理框架。 + +## 创建 Touch 从设备 + +GT9147 的接口总线是基于 I2C 从设备来实现数据的传输,所以创建一个 Touch 从设备也就是创建一个 I2C 从设备。部分源代码如下所示: + +```c +...... +static struct rt_i2c_client *gt9147_client; +/* 创建 I2C 从设备 */ +gt9147_client = (struct rt_i2c_client *)rt_calloc(1, sizeof(struct rt_i2c_client)); +/* 查找I2C 从设备 */ +gt9147_client->bus = (struct rt_i2c_bus_device *)rt_device_find(cfg->dev_name); +if (gt9147_client->bus == RT_NULL) +{ + LOG_E("Can't find device\n"); + return RT_ERROR; +} +/* 打开 I2C 接口总线 */ +if (rt_device_open((rt_device_t)gt9147_client->bus, RT_DEVICE_FLAG_RDWR) != RT_EOK) +{ + LOG_E("open device failed\n"); + return RT_ERROR; +} + +gt9147_client->client_addr = GT9147_ADDRESS_HIGH; +/* 复位 I2C 从设备 */ +gt9147_soft_reset(gt9147_client); +``` + +用户创建 Touch 从设备时只需要传入 Touch 从设备使用的接口总线的名称,就可以创建对应 Touch 从设备。 + +I2C 从设备框架中定义了 I2C 的从设备控制块,如下面代码所示: + +```c +struct rt_i2c_client +{ + struct rt_device parent; /* 设备基类 */ + struct rt_i2c_bus_device *bus; /* I2C 总线控制块指针 */ + rt_uint16_t client_addr; /* 从设备地址 */ +}; +``` +## 实现 Touch 设备的操作方法 + +### 读 GT9147 寄存器 + +GT9147 是标准的 I2C 从设备,所以使用 I2C 总线读取数据,下图为使用 I2C 从 GT9147 读取数据的时序图: + + ![读取 GT9147 数据的时序图](figures/read_data.png) + + GT9147 读时序的描述如下: + 1. 发送 I2C 总线起始信号; + 2. 发送 I2C 器件地址(写标志); + 3. 发送寄存器地址的高八位并等待应答; + 4. 发送寄存器地址的低八位并等待应答; + 5. 重新发送 I2C 起始信号; + 6. 发送 I2C 地址(读标志); + 7. 读数据(data1、2...n); + 8. 数据读取完成发送停止 I2C 总线信号 + +读 GT9147 寄存器的源代码如下: + +```c +static rt_err_t gt9147_read_regs(struct rt_i2c_client *dev, + rt_uint8_t *cmd_buf, + rt_uint8_t cmd_len, + rt_uint8_t read_len, + rt_uint8_t *read_buf) +{ + rt_int8_t res = 0; + + struct rt_i2c_msg msgs[2]; + /* I2C 总线写数据 */ + msgs[0].addr = dev->client_addr; /* 从设备地址 */ + msgs[0].flags = RT_I2C_WR; /* 写标志 */ + msgs[0].buf = cmd_buf; /* 要写入的寄存器地址 */ + msgs[0].len = cmd_len; /* 写入数据长度 */ + + msgs[1].addr = dev->client_addr; /* 从设备地址 */ + msgs[1].flags = RT_I2C_RD; /* 读标志 */ + msgs[1].buf = read_buf; /* 读到的数据指针 */ + msgs[1].len = read_len; /* 要读的数据长度 */ + /* 传输数据 */ + if (rt_i2c_transfer(dev->bus, msgs, 2) == 2) + { + res = RT_EOK; + } + else + { + res = RT_ERROR; + } + + return res; +} +``` + +### 写 GT9147 寄存器 + +下图为使用 I2C 向 GT9147 写入数据的时序图: + +![写 GT9147 数据的时序图](figures/write_data.png) + + GT9147 写时序的描述如下: + 1. 发送 I2C 总线起始信号; + 2. 发送 I2C 器件地址(写标志); + 3. 发送寄存器地址的高八位并等待应答; + 4. 发送寄存器地址的低八位并等待应答; + 7. 发送要写入的数据(data1、2...n); + 8. 数据发送完成发送 I2C 总线停止信号 + +写 GT9147 寄存器的源代码如下: + +```c +static rt_err_t gt9147_write_reg(struct rt_i2c_client *dev, rt_uint8_t write_len, rt_uint8_t *write_data) +{ + rt_int8_t res = 0; + struct rt_i2c_msg msgs; + + msgs.addr = dev->client_addr; /* 从设备地址 */ + msgs.flags = RT_I2C_WR; /* 写标志 */ + msgs.buf = write_data; /* 需要写入的数据 */ + msgs.len = write_len; /* 写入数据的长度 */ + /* I2C 总线传输数据 */ + if (rt_i2c_transfer(dev->bus, &msgs, 1) == 1) + { + res = RT_EOK; + } + else + { + res = RT_ERROR; + } + + return res; +} +``` + +### 读 GT9147 触摸点信息 +GT9147 触摸设备检测到有点按下时会更新状态寄存器和存放触点信息的寄存器,读取触点信息的代码如下所示: +```c +static rt_size_t gt9147_read_point(struct rt_touch_device *touch, void *buf, rt_size_t read_num) +{ + /* 读触摸点状态寄存器 */ + if (gt9147_read_regs(gt9147_client, cmd, 2, 1, &point_status) != RT_EOK) + { + LOG_D("read point failed\n"); + read_num = 0; + goto exit_; + } + + /* 根据参数读取 read_num 个触摸点数据 */ + if (gt9147_read_regs(gt9147_client, cmd, 2, read_num * GT9147_POINT_INFO_NUM, read_buf) != RT_EOK) + { + LOG_D("read point failed\n"); + read_num = 0; + goto exit_; + } + + /*有触点抬起 */ + if (pre_touch > touch_num) + { + for (read_index = 0; read_index < pre_touch; read_index++) + { + rt_uint8_t j; + + for (j = 0; j < touch_num; j++) + { + read_id = read_buf[j * 8] & 0x0F; + + if (pre_id[read_index] == read_id) + break; + + if (j >= touch_num - 1) + { + rt_uint8_t up_id; + up_id = pre_id[read_index]; + /* 抬起事件 */ + gt9147_touch_up(buf, up_id); + } + } + } + } + + /* 有触点按下 */ + if (touch_num) + { + rt_uint8_t off_set; + + for (read_index = 0; read_index < touch_num; read_index++) + { + off_set = read_index * 8; + read_id = read_buf[off_set] & 0x0f; + pre_id[read_index] = read_id; + input_x = read_buf[off_set + 1] | (read_buf[off_set + 2] << 8); /* x */ + input_y = read_buf[off_set + 3] | (read_buf[off_set + 4] << 8); /* y */ + input_w = read_buf[off_set + 5] | (read_buf[off_set + 6] << 8); /* size */ + /* 按下事件 */ + gt9147_touch_down(buf, read_id, input_x, input_y, input_w); + } + } + else if (pre_touch) + { + for(read_index = 0; read_index < pre_touch; read_index++) + { + /* 抬起事件 */ + gt9147_touch_up(buf, pre_id[read_index]); + } + } + + pre_touch = touch_num; + +exit_: + /* 清除触摸点状态寄存器 */ + gt9147_write_reg(gt9147_client, 3, write_buf); + return read_num; +} + +/* 抬起事件处理函数 */ +static void gt9147_touch_up(void *buf, int8_t id) +{ + read_data = (struct rt_touch_data *)buf; + + if(s_tp_dowm[id] == 1) + { + s_tp_dowm[id] = 0; + read_data[id].event = RT_TOUCH_EVENT_UP; + } + else + { + read_data[id].event = RT_TOUCH_EVENT_NONE; + } + + read_data[id].timestamp = rt_touch_get_ts(); + read_data[id].width = pre_w[id]; + read_data[id].x_coordinate = pre_x[id]; + read_data[id].y_coordinate = pre_y[id]; + read_data[id].track_id = id; + + pre_x[id] = -1; /* last point is none */ + pre_y[id] = -1; + pre_w[id] = -1; +} + +/* 按下事件处理函数 */ +static void gt9147_touch_down(void *buf, int8_t id, int16_t x, int16_t y, int16_t w) +{ + read_data = (struct rt_touch_data *)buf; + + if (s_tp_dowm[id] == 1) + { + read_data[id].event = RT_TOUCH_EVENT_MOVE; + + } + else + { + read_data[id].event = RT_TOUCH_EVENT_DOWN; + s_tp_dowm[id] = 1; + } + + read_data[id].timestamp = rt_touch_get_ts(); + read_data[id].width = w; + read_data[id].x_coordinate = x; + read_data[id].y_coordinate = y; + read_data[id].track_id = id; + + pre_x[id] = x; /* save last point */ + pre_y[id] = y; + pre_w[id] = w; +} +``` + +### 控制 GT9147 +Touch 框架中定义了 Touch 控制设备的宏,宏定义如下所示: + +```c +#define RT_TOUCH_CTRL_GET_ID (0) +#define RT_TOUCH_CTRL_GET_INFO (1) +#define RT_TOUCH_CTRL_SET_MODE (2) +#define RT_TOUCH_CTRL_SET_X_RANGE (3) +#define RT_TOUCH_CTRL_SET_Y_RANGE (4) +#define RT_TOUCH_CTRL_SET_X_TO_Y (5) +#define RT_TOUCH_CTRL_DISABLE_INT (6) +#define RT_TOUCH_CTRL_ENABLE_INT (7) +``` + +其中 `RT_TOUCH_CTRL_DISABLE_INT` 和 `RT_TOUCH_CTRL_ENABLE_INT` 已经在框架层中实现,所以从设备驱动需要实现剩下的宏定义,对接到 Touch 从设备的代码如下: + +```c +static rt_err_t gt9147_control(struct rt_touch_device *device, int cmd, void *data) +{ + if (cmd == RT_TOUCH_CTRL_GET_ID) + { + return gt9147_get_product_id(gt9147_client, 6, data); + } + + if (cmd == RT_TOUCH_CTRL_GET_INFO) + { + return gt9147_get_info(gt9147_client, data); + } + + switch(cmd) + { + case RT_TOUCH_CTRL_SET_X_RANGE: + { + /* 设置 X 轴分辨率 */ + func_set_x_range(); + break; + } + case RT_TOUCH_CTRL_SET_Y_RANGE: + { + /* 设置 Y 轴分辨率 */ + func_set_y_range(); + break; + } + case RT_TOUCH_CTRL_SET_X_TO_Y: + { + /* 交换 X、Y 轴坐标 */ + func_set_x_to_y(); + break; + } + case RT_TOUCH_CTRL_SET_MODE: + { + /* 设置触摸芯片工作模式 */ + func_set_mode(); + break; + } + default: + { + break; + } + } + + return RT_EOK; +} +``` + +## 注册 Touch 设备 +Touch 设备的操作方法实现后,需要注册 Touch 设备到操作系统中,注册部分代码如下: + +```c +...... +/* 保存 Touch 设备的操作方法 */ +static struct rt_touch_ops touch_ops = +{ + .touch_readpoint = gt9147_read_point, + .touch_control = gt9147_control, +}; + +int rt_hw_gt9147_init(const char *name, struct rt_touch_config *cfg) +{ + rt_touch_t touch_device = RT_NULL; + + /* 创建 Touch 设备 */ + touch_device = (rt_touch_t)rt_calloc(1, sizeof(struct rt_touch_device)); + + /* 硬件初始化 */ + gt9147_hw_init(); + + /* 创建接口总线从设备 */ + gt9147_client = (struct rt_i2c_client *)rt_calloc(1, sizeof(struct rt_i2c_client)); + /* 查找接口总线设备 */ + gt9147_client->bus = (struct rt_i2c_bus_device *)rt_device_find(cfg->dev_name); + /* 打开接口总线 */ + if (rt_device_open((rt_device_t)gt9147_client->bus, RT_DEVICE_FLAG_RDWR) != RT_EOK) + { + LOG_E("open device failed\n"); + return RT_ERROR; + } + + gt9147_client->client_addr = GT9147_ADDRESS_HIGH; + gt9147_soft_reset(gt9147_client); + /* 触摸设备类型是电容屏 */ + touch_device->info.type = RT_TOUCH_TYPE_CAPACITANCE; + /* 厂商信息为 GT 系列 */ + touch_device->info.vendor = RT_TOUCH_VENDOR_GT; + rt_memcpy(&touch_device->config, cfg, sizeof(struct rt_touch_config)); + /* 保存操作方法 */ + touch_device->ops = &touch_ops; + + /* 注册 Touch 设备到操作系统 */ + rt_hw_touch_register(touch_device, name, RT_DEVICE_FLAG_INT_RX, RT_NULL); + + ...... +} +``` + +## 参考资料 +《Touch 设备驱动框架详解》 + diff --git a/rt-thread-version/rt-thread-standard/figures/02Software_framework_diagram.png b/rt-thread-version/rt-thread-standard/figures/02Software_framework_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..0ece529620db6b5cf75d243088c5ca72b0ea48cf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/figures/02Software_framework_diagram.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/08_direct_run_files.gif b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/08_direct_run_files.gif new file mode 100644 index 0000000000000000000000000000000000000000..823b59e71767cdef95494589984414f4f666d504 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/08_direct_run_files.gif differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/IoT_Board.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/IoT_Board.png new file mode 100644 index 0000000000000000000000000000000000000000..be664acdff4b0a522799e70021d3621ca7be4328 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/IoT_Board.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/W60x_HW_origin.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/W60x_HW_origin.png new file mode 100644 index 0000000000000000000000000000000000000000..7c92885ececf6d579d4de34530b6234f0132bff0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/W60x_HW_origin.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/add_main_stack.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/add_main_stack.png new file mode 100644 index 0000000000000000000000000000000000000000..e7967af829d16201b4f22c821f9ab401455b8e45 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/add_main_stack.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/c-gen.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/c-gen.png new file mode 100644 index 0000000000000000000000000000000000000000..918ccd4a3acf93966cb6da359b509474ff45e44f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/c-gen.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/check_memory.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/check_memory.png new file mode 100644 index 0000000000000000000000000000000000000000..2c2bc1483d4b1dda1b2e46dfcdc9b00cf82cfedf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/check_memory.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/check_pandora_examples.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/check_pandora_examples.png new file mode 100644 index 0000000000000000000000000000000000000000..6bdb9cf5abb442a412a57af6292d0edf59c32515 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/check_pandora_examples.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/config_runtime.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/config_runtime.png new file mode 100644 index 0000000000000000000000000000000000000000..a1f2611c160037d1e314c6c1a80893d85786a45c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/config_runtime.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/en_connect_board.gif b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/en_connect_board.gif new file mode 100644 index 0000000000000000000000000000000000000000..84c395b057e5001b9780b08ef0bafc238790aa71 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/en_connect_board.gif differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/mount_fs.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/mount_fs.png new file mode 100644 index 0000000000000000000000000000000000000000..52d3f05360bfc7bd54572d3f50411ab0ed14a77d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/mount_fs.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/select_micropython.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/select_micropython.png new file mode 100644 index 0000000000000000000000000000000000000000..54969586a1cab4a2f90914db8464331f2841f13a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/select_micropython.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/select_mpy_package.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/select_mpy_package.png new file mode 100644 index 0000000000000000000000000000000000000000..6a8792971d352d32d875a54056025cf587e91292 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/select_mpy_package.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/sparrow_example.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/sparrow_example.png new file mode 100644 index 0000000000000000000000000000000000000000..af065469f36222206e016caf249c311b49568ebf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/sparrow_example.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/sparrow_one_board.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/sparrow_one_board.png new file mode 100644 index 0000000000000000000000000000000000000000..70c08d950e980929edca1156dbd69cddd443e8b6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/sparrow_one_board.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/w601_examples.png b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/w601_examples.png new file mode 100644 index 0000000000000000000000000000000000000000..9909488a8da001a79c3ef578845a6e4a519be71e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/assets/w601_examples.png differ diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/external_c_modules.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/external_c_modules.md new file mode 100644 index 0000000000000000000000000000000000000000..3825ee20b783e7b603b3df6a080e5705094622de --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/external_c_modules.md @@ -0,0 +1,53 @@ +# 为 MicroPython 扩展 C 模块 + +当使用原生 MicroPython 进行开发时,你可能会遇到这样一些限制,比如官方没有实现自己想要的功能,或者你觉得这些实现不符合自己的工作需求。此时,添加自己的 C 模块到 MicroPython 中是一个不错的选择,你可以按照自己的想法,设计适合自己的 Python 函数调用。 + +为了帮助各位开发者快速添加 C 模块,RT-Thread 提供了相应的辅助工具 [C 绑定代码自动生成器](https://summerlife.github.io/RT-MicroPython-Generator/)。该工具可以帮助开发者自动生成 C 代码和 MicroPython 之间的接口层,开发者只需将 C 语言编写的功能代码添加到指定位置,MicroPython 即可直接调用该功能。 + +## Python 调用 C 函数的实现原理 + +C 语言和 Python 是两种完全不同的语言,如何使用 MicroPython 来调用 C 语言所实现的函数是许多小伙伴非常疑惑的地方。简单来说,这个问题的关键点在于,如何用 C 语言的形式在 MicroPython 源代码中**表述函数的入参和出参**。我举一个例子来讲解这个问题, 请观察如下 Python 函数: + +```python +def add(a, b): + return a + b +``` + +该函数有两个入参,返回一个参数。此时如果能用 C 语言表示该 **Python 函数的输入输出参数**,就可以将一个实现该功能的 C 函数对接到 MicroPython 中。 + +### 添加用户函数到 MicroPython + +假设上述函数的参数类型都为整形,通过自动生成器可以得到如下样板函数: + +```c +STATIC mp_obj_t add( + mp_obj_t arg_1_obj, + mp_obj_t arg_2_obj) { + mp_int_t arg_1 = mp_obj_get_int(arg_1_obj); /* 通过 Python 获取的第一个整形参数 arg_1 */ + mp_int_t arg_2 = mp_obj_get_int(arg_2_obj); /* 通过 Python 获取的第二个整形参数 arg_2 */ + mp_int_t ret_val; + + /* Your code start! */ + + ret_val = arg_1 + arg_2; /* 处理入参 arg_1 和 arg_2,并将结果赋给返回值 ret_val */ + + /* Your code end! */ + + return mp_obj_new_int(ret_val); /* 向 python 返回整形参数 ret_val */ +} +MP_DEFINE_CONST_FUN_OBJ_2(add_obj, add); +``` + +生成器会处理好需要导出到 MicroPython 的函数的入参和出参,而开发者只需要编写相应的代码来处理这些输入参数,并且把返回值赋给输出参数即可。 通过包含头文件的方式,可以调用先前编写的 C 函数来对输入参数进行处理,或者根据输入参数来执行相应的动作,添加控制硬件的驱动的原理也是一样的。 + +最终使用 Python 调用 C 函数的效果如下: + +```python +>>> import userfunc +>>> userfunc.add(666,777) +1443 +``` + +### 添加用户模块到 MicroPython + +添加用户模块到 MicroPython 中也不难,首先应当熟练掌握上述添加 C 函数的过程,然后参考 PR [add module userfunc to MicroPython](https://github.com/RT-Thread-packages/micropython/pull/144) 来添加属于自己的模块,该 PR 实现了添加 `userfunc` 模块到 MicroPython 的功能,你可以按照同样的方式将自己编写的模块注册到 MicroPython 中,要注意仔细查看这个 PR 中修改的 4 个文件,不要漏掉修改的细节。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/firmware-develop.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/firmware-develop.md new file mode 100644 index 0000000000000000000000000000000000000000..7fe121f47f7dbb0be2cf3d5116800d25c321cada --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/firmware-develop.md @@ -0,0 +1,103 @@ +# MicroPython 固件开发指南 + +如果手上没有官方支持固件的开发板,就需要自己来动手制作 MicroPython 固件了。由于 RT-Thread 官方提供了 [MicroPython 软件包](https://github.com/RT-Thread-packages/micropython),并且 MicroPython 底层和硬件绑定时对接了 RT-Thread 驱动框架,因此可以很方便地在运行了 RT-Thread 的板卡上将 MicroPython 跑起来。 + +**注意**:RT-Thread MicroPython 需要运行在 **RT-Thread 3.0** 版本以上。 + +## 选择合适的 BSP 平台 + +RT-Thread MicroPython mini 版本占用资源最大不超过: + +- ROM : 190KB +- RAM : 20KB + +只要系统资源满足上述要求,常见的许多开发板都可以运行 MicroPython,例如 STM32 系列 BSP。 + +接下来我们以 `rt-thread\bsp\stm32\stm32f407-atk-explorer` 上的 MDK 工程为例,讲解如何在 BSP 的基础上制作 MicroPython 固件。 + +## 获取 MicroPython 软件包 + +先使用 `pkgs --upgrade` 命令更新软件包列表,然后通过 env 工具选中 MicroPython 软件包,最后使用 `pkgs -update` 命令将软件包拉取到本地。 + +![select_mpy_package](assets/select_mpy_package.png) + +## 增大 main 线程栈 + +为了能后续在 main 线程中启动 MicroPython 运行时环境,需要增大 main 线程的栈大小,这里我们将栈大小增加到 8k。 + +![add_main_stack](assets/add_main_stack.png) + +## 配置 MicroPython 运行环境堆大小 + +接下来根据板卡内存实际剩余情况来给 MicroPython 运行环境分配内存,这里填写的数值越大,就能运行更大代码量的 Python 程序。但是如果这里填写的数值超过了实际可分配内存,就可能会出现无法分配内存而报错。因此在配置此项目之前,需要对系统 RAM 资源的分配情况有一定了解。 + +### 查看系统剩余内存 + +重新生成工程,编译下载后通过 `msh` 的 `free` 命令来查看内存使用情况。 + +![check_memory](assets/check_memory.png) + +### 配置系统 + +通过上一步查询的内存分配情况,对系统 RAM 资源有了一定的了解。在本次示例中,我们分配 20k 内存用于 MicroPython 运行时环境。后续如果想要运行更多 MicroPython 代码,可以将更多空余内存分配给 MicroPython 运行时环境,配置如下图所示: + +![config_runtime](assets/config_runtime.png) + +## 在根目录挂载文件系统 + +最后要确保系统中 `/` 目录挂载了文件系统。有了文件系统,后续才能使用 [**MicroPython 开发环境**](https://marketplace.visualstudio.com/items?itemName=RT-Thread.rt-thread-micropython) 将 Python 代码文件同步到板卡中来运行,本次示例中将使用 elm-fat 文件系统,需要对系统进行如下配置: + +![mount_fs](assets/mount_fs.png) + +配置完成后,记得要使用 `scons --target=mkd5` 重新生成工程,使配置在工程中生效。 + +## 在 main 线程中启动 MicroPython + +最后要在 main 线程中启动 MicroPython,代码修改如下所示: + +```c +#include +#include +#include +#include +#include + +/* 文件系统所在分区名称,根据实际情况填写 */ +#define FS_PARTITION_NAME "W25Q128" + +int main(void) +{ + /* 挂载 elm 文件系统到 / 目录,如果你所使用的开发板没有文件系统也可以跳过这一步 */ + if (dfs_mount(FS_PARTITION_NAME, "/", "elm", 0, 0) == 0) + { + rt_kprintf("Filesystem initialized!"); + } + else + { + /* 如果挂载失败,则尝试在文件系统分区重新创建文件系统 */ + dfs_mkfs("elm", FS_PARTITION_NAME); + /* 尝试重新挂载文件系统 */ + if (dfs_mount(FS_PARTITION_NAME, "/", "elm", 0, 0) == 0) + { + /* 仍然挂载文件系统失败,请自行检查失败原因 */ + rt_kprintf("Failed to initialize filesystem!"); + } + } + + rt_thread_mdelay(100); + + while(1) + { + /* 在这里让程序进入循环,通过这种方式实现 MicroPython 的软复位*/ + extern void mpy_main(const char *filename); + /* 启动 MicroPython */ + mpy_main(NULL); + } + + return RT_EOK; +} +``` + +重新编译工程并下载程序到板卡中,就会在 main 线程中自动启动 MicroPython,接下来就可以使用 [**RT-Thread MicroPython 开发环境**](https://marketplace.visualstudio.com/items?itemName=RT-Thread.rt-thread-micropython) 来进行应用开发了。 通过开发环境连接到开发板,即可看到 MicroPython 的交互环境 REPL,如下图所示: + +![en_connect_board](assets/en_connect_board.gif) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/introduction.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/introduction.md new file mode 100644 index 0000000000000000000000000000000000000000..1fedb66e156cec416c6219d1d9c9a10731035920 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/introduction.md @@ -0,0 +1,49 @@ +# MicroPython 入门必读 + +本文档将初步介绍 MicroPython 的基本概念,RT-Thread MicroPython 的特性与优势,以及可以被用在哪些领域。 + +## 主要特性 + +- MicroPython 是 Python 3 编程语言的一种精简而高效的实现,它包含 Python 标准库的一个子集,并被优化为在微控制器和受限环境中运行。 + +- RT-Thread MicroPython 可以运行在任何搭载了 RT-Thread 操作系统并且有一定资源的嵌入式平台上。 + +- MicroPython 可以运行在有一定资源的开发板上,给你一个低层次的 Python 操作系统,可以用来控制各种电子系统。 + +- MicroPython 富有各种高级特性,比如交互式提示、任意精度整数、闭包函数、列表解析、生成器、异常处理等等。 + +- MicroPython 的目标是尽可能与普通 Python 兼容,使开发者能够轻松地将代码从桌面端转移到微控制器或嵌入式系统。程序可移植性很强,因为不需要考虑底层驱动,所以程序移植变得轻松和容易。 + +## MicroPython 的优势 + +- Python 是一款容易上手的脚本语言,同时具有强大的功能,语法优雅简单。使用 MicroPython 编程可以降低嵌入式的开发门槛,让更多的人体验嵌入式的乐趣。 +- 通过 MicroPython 实现硬件底层的访问和控制,不需要了解底层寄存器、数据手册、厂家的库函数等,即可轻松控制硬件。 +- 外设与常用功能都有相应的模块,降低开发难度,使开发和移植变得容易和快速。 + +## MicroPython 的应用领域 + +- MicroPython 在嵌入式系统上完整实现了 Python3 的核心功能,可以在产品开发的各个阶段给开发者带来便利。 +- 通过 MicroPython 提供的库和函数,开发者可以快速控制 LED、液晶、舵机、多种传感器、SD、UART、I2C 等,实现各种功能,而不用再去研究底层硬件模块的使用方法,翻看寄存器手册。这样不但降低了开发难度,而且减少了重复开发工作,可以加快开发速度,提高开发效率。以前需要较高水平的嵌入式工程师花费数天甚至数周才能完成的功能,现在普通的嵌入式开发者用几个小时就能实现类似的功能。 +- 随着半导体技术的不断发展,芯片的功能、内部的存储器容量和资源不断增加,成本不断降低,可以使用 MicroPython 来进行开发设计的应用领域也会越来越多。 + +### 产品原型验证 + +- 众所周知,在开发新产品时,原型设计是一个非常重要的环节,这个环节需要以最快速的方式设计出产品的大致模型,并验证业务流程或者技术点。与传统开发方法相比,使用 MicroPython 对于原型验证非常有用,让原型验证过程变得轻松,加速原型验证过程。 +- 在进行一些物联网功能开发时,网络功能也是 MicroPython 的长处,可以利用现成的众多 MicroPython 网络模块,节省开发时间。而这些功能如果使用 C/C++ 来完成,会耗费几倍的时间。 + +### 硬件测试 + +- 嵌入式产品在开发时,一般会分为硬件开发及软件开发。硬件工程师并不一定都擅长软件开发,所以在测试新硬件时,经常需要软件工程师参与。这就导致软件工程师可能会耗费很多时间帮助硬件工程师查找设计或者焊接问题。有了 MicroPython 后,将 MicroPython 固件烧入待测试的新硬件,在检查焊接、连线等问题时,只需使用简单的 Python 命令即可测试。这样,硬件工程师一人即可搞定,再也不用麻烦别人了。 + +### 创客 DIY + +- MicroPython 无需复杂的设置,不需要安装特别的软件环境和额外的硬件,使用任何文本编辑器就可以进行编程。大部分硬件功能,使用一个命令就能驱动,不用了解硬件底层就能快速开发。这些特性使得 MicroPython 非常适合创客使用来开发一些有创意的项目。 +- 下面是使用 MicroPython 开发的一些 DIY 项目: + - [显示温湿度的 WIFI 时钟](https://www.bilibili.com/video/av15929152?from=search&seid=16285206333541196172) + - [OpenMV 智能摄像头](https://www.bilibili.com/video/av16418889?from=search&seid=16285206333541196172) + - [快速实现人脸识别](https://www.bilibili.com/video/av73853903?from=search&seid=9793819178982436353) + - [搭建 MQTT 服务器](http://www.360doc.com/content/17/1218/22/8473307_714341237.shtml) + +### 教育 + +- MicroPython 使用简单、方便,非常适合于编程入门。在校学生或者业余爱好者都可以通过 MicroPython 快速的开发一些好玩的项目,在开发的过程中学习编程思想,提高自己的动手能力。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython-ide.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython-ide.md new file mode 100644 index 0000000000000000000000000000000000000000..c4589a9334bd672e9674acd055111693071aeae3 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython-ide.md @@ -0,0 +1,24 @@ +# MicroPython IDE + +RT-Thread 为广大开发者提供了 VSCode 最好用的 MicroPython 插件 来帮助大家使用 MicroPython 来开发应用程序。该插件为 MicroPython 开发提供了功能强大的开发环境,主要特性如下: + +- 便捷的开发板连接方式(串口、网络、USB) +- 支持基于 MicroPython 的代码智能补全与语法检查 +- 支持 MicroPython REPL 交互环境 +- 提供丰富的代码示例与 demo 程序 +- 提供工程同步功能 +- 支持下载单个文件或文件夹至开发板 +- 支持在内存中快速运行代码文件功能 +- 支持运行代码片段功能 +- 支持多款主流 MicroPython 开发板 +- 支持 Windows、Ubuntu、Mac 操作系统 + +### 安装 IDE 开发环境 + +开发者可以通过 RT-Thread MicroPython IDE 来快速开发 MicroPython 应用,下图展示了 IDE 的快速调试功能: + +![08_direct_run_files](assets/08_direct_run_files.gif) + +可通过查看如下文档进一步了解并使用 RT-Thread MicroPython IDE: + +- [RT-Thread MicroPython Develop Environment](https://marketplace.visualstudio.com/items?itemName=RT-Thread.rt-thread-micropython) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython-librarys.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython-librarys.md new file mode 100644 index 0000000000000000000000000000000000000000..604a86755ab61d6b47cb8e0551354203df1f692a --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython-librarys.md @@ -0,0 +1,47 @@ +# MicroPython 库 + +## MicroPython 标准库 + +- [Builtin functions and exceptions](std-librarys/builtins.md) – 内置函数与异常 +- [cmath](std-librarys/cmath.md) – 复数运算函数功能 +- [gc](std-librarys/gc.md) – 控制垃圾收集器 +- [math](std-librarys/math.md) – 数学函数功能 +- [sys](std-librarys/sys.md) – 系统特定功能 +- [uarray](std-librarys/uarray.md) – 数组存储功能 +- [ubinascii](std-librarys/ubinascii.md) – 二进制与 ASCII 码转换功能 +- [ucollections](std-librarys/ucollections.md) – 集合与容器类型 +- [uerrno](std-librarys/uerrno.md) – 系统错误码 +- [uhashlib](std-librarys/uhashlib.md) – 哈希算法 +- [uheapq](std-librarys/uheapq.md) – 堆队列算法 +- [uio](std-librarys/uio.md) – 输入输出流 +- [ujson](std-librarys/ujson.md) – JSON 编解码 +- [uos](std-librarys/uos.md) – 基本的操作系统服务 +- [ure](std-librarys/ure.md) – 正则表达式 +- [uselect](std-librarys/uselect.md) – 在一组 streams 上等待事件 +- [usocket](std-librarys/usocket.md) – socket 模块 +- [ussl](std-librarys/ussl.md) – SSL/TLS 模块 +- [ustruct](std-librarys/ustruct.md) – 原生数据类型的打包和解包 +- [utime](std-librarys/utime.md) – 时间相关功能 +- [uzlib](std-librarys/uzlib.md) – zlib 解压 +- [_thread](std-librarys/_thread.md) – 多线程支持 + +## MicroPython 特定库 + +在 RT-Thread 移植的 MicroPython 版本中,实现了如下特定功能库: + +- [micropython](spec-librarys/micropython.md) – 实现 MicroPython 内部功能访问与控制 +- [rtthread](spec-librarys/rtthread.md) – RT-Thread 系统功能模块 +- [machine](spec-librarys/machine.md) – 硬件控制模块 + - [Pin](spec-librarys/machine/Pin.md) + - [I2C](spec-librarys/machine/I2C.md) + - [SPI](spec-librarys/machine/SPI.md) + - [UART](spec-librarys/machine/UART.md) + - [LCD](spec-librarys/machine/LCD.md) + - [RTC](spec-librarys/machine/RTC.md) + - [PWM](spec-librarys/machine/PWM.md) + - [ADC](spec-librarys/machine/ADC.md) + - [WDT](spec-librarys/machine/WDT.md) + - [TIMER](spec-librarys/machine/Timer.md) + +- [network](spec-librarys/network.md) – 网络功能配置模块 + - [wlan](spec-librarys/network/wlan.md) diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_pandora_iot_board.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_pandora_iot_board.md new file mode 100644 index 0000000000000000000000000000000000000000..83e4df985bff0118c10ad2b10dde88d23447a3cc --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_pandora_iot_board.md @@ -0,0 +1,76 @@ +# MicroPython for Pandora IoT Board + +![IoT_Board](assets/IoT_Board.png) + +[**IoT Board 潘多拉**](https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-18400369818.12.2ba47ea5PzJxZx&id=583843059625) 是 RT-Thread 推出的一款物联网开发板,它给开发者带来了物联网时代的无限可能。而现在,它已经不仅仅是一块简单的物联网开发板,因为它已经全面支持 **MicroPython** 。在 IoT Board 上,你将会体验到有别于传统的,前所未有的开发方式。 + +借助于 MicroPython,你将有能力使用 Python 语言控制所有硬件外设,体验高级语言带来的便利特性,与此同时你还可以利用高级软件库快速实现你的 IoT 构想。 + +## 硬件支持 + +Pandora MicroPython 固件硬件功能如下所示: + +| 外设名称 | 引脚号 | 简介 | +| -------- | ---------------------------------------------- | ----------------------------------------- | +| pin | PA4 PA8, PB8-9 PB10-15, PC2 PC4 PC6-7, PD12-15 | 开发板引出的可自由分配的 IO,支持引脚中断 | +| led | PE7 | 红色 led 灯 | +| rgb | R: PE7, G: PE8, B: PE9 | rgb 灯 | +| key | KEY0: PD10, KEY1: PD9, KEY2: PD8 | 输入按键 | +| uart1 | PA9, PA10 | 串口1 | +| i2c | | 软件 i2c 可选择任意 pin | +| spi | | 软件 spi 可选择任意引出 pin | +| adc | PC4 | adc1,通道 13 | +| pwm | PB0 | pwm3, 通道 3, 用于红外发射 | +| timer | | 硬件定时器 15 | +| wdt | | 看门狗 | +| rtc | | 实时时钟 | +| beeper | PB2 | 蜂鸣器 | +| lcd | | lcd 显示屏 | +| wifi | | wifi 网络连接 | +| aht10 | CLK: PD6, SDA: PC1 | 温湿度传感器 | +| ap3216c | CLK: PC0, SDA: PC1 | 接近与光强传感器 | +| icm20608 | CLK: PC0, SDA: PC1 | 六轴传感器 | + + +## 入门必读 + +如果您从来没有了解过 MicroPython, 可以阅读这篇简短的文章来 [带你入门 MicroPython](introduction.md)。 + +## 开启 MicroPython 之旅 + +推荐遵循如下步骤开始进行 MicroPython 开发: + +- 在您的开发板上烧录合适的固件 +- 在 PC 机上安装 RT-Thread MicroPython 开发环境并连接上开发板 + +接下来就可以尽情挥洒您的创意了,更详细的内容可以点击下文中的链接来进一步了解。 + +### 下载合适的固件 + +- [Pandora IoT Board firmware](https://www.rt-thread.org/qa/forum.php?mod=viewthread&tid=12305&extra=page%3D1%26filter%3Dtypeid%26typeid%3D20) + +### 安装 IDE 开发环境 + +- [RT-Thread MicroPython develop environment](https://marketplace.visualstudio.com/items?itemName=RT-Thread.rt-thread-micropython) + +## 开发资料 + +### 示例程序 + +以下示例程序可以在 RT-Thread MicroPython IDE 开发环境中直接添加到工程: + +![check_pandora_examples](assets/check_pandora_examples.png) + +### MicroPython 模块详解 + +- [MicroPython Librarys](micropython-librarys.md) + + +## 联系我们 + +如果在使用的过程中遇到问题,您可以用如下方式联系我们: + +- 在 github 上提交 issue +- [RT-Thread MicroPython 官方论坛](https://www.rt-thread.org/qa/forum.php?mod=forumdisplay&fid=2&filter=typeid&typeid=20) + +- RT-Thread MicroPython 交流 QQ 群:703840633 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_sparrow_one_board.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_sparrow_one_board.md new file mode 100644 index 0000000000000000000000000000000000000000..c3b815417f2371902fe2b3d2335dbdb5965d5299 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_sparrow_one_board.md @@ -0,0 +1,65 @@ +# MicroPython for sparrow one board + +![sparrow_one](assets/sparrow_one_board.png) + +[**麻雀一号开发板**](https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-5210898174.2.29401ae39JyGKY&id=606684373403) 是 RT-Thread 推出的一款物联网音视频开发板,现在它已经全面支持 **MicroPython** 。在麻雀一号开发板上,你将会体验到有别于传统的,前所未有的开发方式。 + +借助于 MicroPython,你将有能力使用 Python 语言控制所有硬件外设,体验高级语言带来的便利特性,与此同时你还可以利用高级软件库快速实现你的 IoT 构想。 + +## 硬件支持 + +麻雀一号开发板固件硬件功能如下所示: + +| 外设名称 | 简介 | +| --------- | ---------------------------------------------------------- | +| key | 输入按键 | +| uart1 | 串口1 | +| lcd | lcd 显示屏 | +| wifi | wifi 网络连接 | +| bluetooth | 蓝牙 | +| player | 扬声器,音频播放 | +| recorder | 麦克风,录音功能 | +| camera | 摄像头,可拍照并存入文件系统,开启 TCP Server 查看实时图像 | + +## 入门必读 + +如果您从来没有了解过 MicroPython, 可以阅读这篇简短的文章来 [带你入门 MicroPython](https://github.com/RT-Thread-packages/micropython/blob/master/docs/introduction.md)。 + +## 开启 MicroPython 之旅 + +推荐遵循如下步骤开始进行 MicroPython 开发: + +- 在您的开发板上烧录合适的固件 +- 在 PC 机上安装 RT-Thread MicroPython 开发环境并连接上开发板 + +接下来就可以尽情挥洒您的创意了,更详细的内容可以点击下文中的链接来进一步了解。 + +### 下载合适的固件 + +- [Sparrow One Board firmware](https://www.rt-thread.org/qa/forum.php?mod=viewthread&tid=12305&extra=page%3D1%26filter%3Dtypeid%26typeid%3D20) + +### 安装 IDE 开发环境 + +- [RT-Thread MicroPython develop environment](https://marketplace.visualstudio.com/items?itemName=RT-Thread.rt-thread-micropython) + +## 开发资料 + +### 示例程序 + +以下示例程序可以在 RT-Thread MicroPython IDE 开发环境中直接添加到工程: + +![sparrow_example](assets/sparrow_example.png) + +### MicroPython 模块详解 + +- [MicroPython Librarys](https://github.com/RT-Thread-packages/micropython/blob/master/docs/micropython-librarys.md) + + +## 联系我们 + +如果在使用的过程中遇到问题,您可以用如下方式联系我们: + +- 在 github 上提交 issue +- [RT-Thread MicroPython 官方论坛](https://www.rt-thread.org/qa/forum.php?mod=forumdisplay&fid=2&filter=typeid&typeid=20) + +- RT-Thread MicroPython 交流 QQ 群:703840633 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_w601_iot_board.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_w601_iot_board.md new file mode 100644 index 0000000000000000000000000000000000000000..ac7e19742b89a19f699a12ea9939b27a16f47c59 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/micropython_for_w601_iot_board.md @@ -0,0 +1,74 @@ +# MicroPython for W601 IoT Board + +![IoT_Board](assets/W60x_HW_origin.png) + +[**W601 IoT Board**](https://item.taobao.com/item.htm?spm=a230r.1.14.13.7c5b4a9bS2LYUD&id=602233847745&ns=1&abbucket=17#detail) 是 RT-Thread 推出的一款物联网开发板,它给开发者带来了物联网时代的无限可能。而现在,它已经不仅仅是一块简单的物联网开发板,因为它已经全面支持 **MicroPython** 。在 IoT Board 上,你将会体验到有别于传统的,前所未有的开发方式。 + +借助于 MicroPython,你将有能力使用 Python 语言控制所有硬件外设,体验高级语言带来的便利特性,与此同时你还可以利用高级软件库快速实现你的 IoT 构想。 + +## 硬件支持 + +W601 IoT Board MicroPython 固件硬件功能如下所示: + +| 外设名称 | 引脚号 | 简介 | +| -------- | -------------------------------------- | ----------------------------------------- | +| pin | PA11, PB4、10-14 、17-18、23-26、30-31 | 开发板引出的可自由分配的 IO,支持引脚中断 | +| led | PA13 | 红色 led 灯 | +| rgb | R: PA13, G: PA14, B: PA15 | rgb 灯 | +| key | KEY0: PA7, KEY1: PA6, | 输入按键 | +| uart1 | PA4, PA5 | 串口1 | +| i2c | | 软件 i2c 可选择任意 pin | +| spi | | 软件 spi 可选择任意引出 pin | +| adc | PB23 - 26 | adc,通道 5 - 8 | +| pwm | PB17、PB18 | pwm1, 通道 1、2 | +| timer | | 硬件定时器 1 | +| wdt | | 看门狗 | +| rtc | | 实时时钟 | +| beeper | PB15 | 蜂鸣器 | +| lcd | | lcd 显示屏 | +| wifi | | wifi 网络连接 | +| aht10 | CLK: PA0, SDA: PA1 | 温湿度传感器 | +| ap3216c | CLK: PA2, SDA: PA1 | 接近与光强传感器 | + +## 入门必读 + +如果您从来没有了解过 MicroPython, 可以阅读这篇简短的文章来 [带你入门 MicroPython](https://github.com/RT-Thread-packages/micropython/blob/master/docs/introduction.md)。 + +## 开启 MicroPython 之旅 + +推荐遵循如下步骤开始进行 MicroPython 开发: + +- 在您的开发板上烧录合适的固件 +- 在 PC 机上安装 RT-Thread MicroPython 开发环境并连接上开发板 + +接下来就可以尽情挥洒您的创意了,更详细的内容可以点击下文中的链接来进一步了解。 + +### 下载合适的固件 + +- [W601 IoT Board firmware](https://www.rt-thread.org/qa/forum.php?mod=viewthread&tid=12305&extra=page%3D1%26filter%3Dtypeid%26typeid%3D20) + +### 安装 IDE 开发环境 + +- [RT-Thread MicroPython develop environment](https://marketplace.visualstudio.com/items?itemName=RT-Thread.rt-thread-micropython) + +## 开发资料 + +### 示例程序 + +以下示例程序可以在 RT-Thread MicroPython IDE 开发环境中直接添加到工程: + +![w601_examples](assets/w601_examples.png) + +### MicroPython 模块详解 + +- [MicroPython Librarys](https://github.com/RT-Thread-packages/micropython/blob/master/docs/micropython-librarys.md) + + +## 联系我们 + +如果在使用的过程中遇到问题,您可以用如下方式联系我们: + +- 在 github 上提交 issue +- [RT-Thread MicroPython 官方论坛](https://www.rt-thread.org/qa/forum.php?mod=forumdisplay&fid=2&filter=typeid&typeid=20) + +- RT-Thread MicroPython 交流 QQ 群:703840633 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine.md new file mode 100644 index 0000000000000000000000000000000000000000..cea6db977b0a167b32b1cb4f59ea5db63ea55579 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine.md @@ -0,0 +1,60 @@ +## **machine** – 与硬件相关的功能 + +**machine** 模块包含与特定开发板上的硬件相关的特定函数。 在这个模块中的大多数功能允许实现直接和不受限制地访问和控制系统上的硬件块(如CPU,定时器,总线等)。如果使用不当,会导致故障,死机,崩溃,在极端的情况下,硬件会损坏。 + +需要注意的是,由于不同开发板的硬件资源不同,MicroPython 移植所能控制的硬件也是不一样的。因此对于控制硬件的例程来说,在使用前需要修改相关的配置参数来适配不同的开发板,或者直接运行已经对某一开发板适配好的 MicroPython 示例程序。本文档中的例程都是基于 RT-Thread IoT Board 潘多拉开发板而讲解的。 + +### 函数 + +#### 复位相关函数 + +##### **machine.info**() + 显示关于系统介绍和内存占用等信息。 + +##### **machine.rest**() + 重启设备,类似于按下复位按钮。 + +##### **machine.reset_cause**() + 获得复位的原因,查看可能的返回值的常量。 + +#### 中断相关函数 + +##### **machine.disable_irq**() + 禁用中断请求。返回先前的 `IRQ` 状态,该状态应该被认为是一个未知的值。这个返回值应该在 `disable_irq` 函数被调用之前被传给 `enable_irq` 函数来重置中断到初始状态。 + +##### **machine.enable_irq**(state) + 重新使能中断请求。状态参数应该是从最近一次禁用功能的调用中返回的值。 + +#### 功耗相关函数 + +##### **machine.freq**() + 返回 `CPU` 的运行频率。 + +##### **machine.idle**() + 阻断给 `CPU` 的时钟信号,在较短或者较长的周期里减少功耗。当中断发生时,外设将继续工作。 + +##### **machine.sleep**() + 停止 `CPU` 并禁止除了 `WLAN` 之外的所有外设。系统会从睡眠请求的地方重新恢复工作。为了确保唤醒一定会发生,应当首先配置中断源。 + +##### **machine.deepsleep**() + 停止 `CPU` 和所有外设(包括网络接口)。执行从主函数中恢复,就像被复位一样。复位的原因可以检查 `machine.DEEPSLEEP` 参数获得。为了确保唤醒一定会发生,应该首先配置中断源,比如一个引脚的变换或者 `RTC` 的超时。 + +### 常数 + +- `IRQ` 唤醒值 +#### **machine.IDLE** +#### **machine.SLEEP** +#### **machine.DEEPSLEEP** + +- 复位 +#### **machine.PWRON_RESET ** +#### **machine.HARD_RESET ** +#### **machine.WDT_RESET ** +#### **machine.DEEPSLEEP_RESET ** +#### **machine.SOFT_RESET** + +- 唤醒 +#### **machine.WLAN_WAKE** +#### **machine.PIN_WAKE** +#### **machine.RTC_WAKE** + diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/ADC.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/ADC.md new file mode 100644 index 0000000000000000000000000000000000000000..43639d2a656f8f03bdeae6fdf743298bda1bc499 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/ADC.md @@ -0,0 +1,44 @@ +## machine.ADC + +**machine.ADC** 类是 machine 模块下的一个硬件类,用于指定 ADC 设备的配置和控制,提供对 ADC 设备的操作方法。 + +- ADC(Analog-to-Digital Converter,模数转换器),用于将连续变化的模拟信号转化为离散的数字信号。 +- ADC 设备两个重要参数:采样值、分辨率; + - 采样值:当前时间由模拟信号转化的数值信号的数值; + - 分辨率:以二进制(或十进制)数的位数来表示,一般有 8 位、10 位、12 位、16 位等,它说明模数转换器对输入信号的分辨能力,位数越多,表示分辨率越高,采样值会更精确。 + +### 构造函数 + +在 RT-Thread MicroPython 中 `ADC` 对象的构造函数如下: + +#### **class machine.ADC**(id, channel) + +- **id**:使用的 ADC 设备编号,`id = 1` 表示编号为 1 的 ADC 设备,或者表示使用的 ADC 设备名,如 `id = "adc"` 表示设备名为 `adc` 的 ADC 设备; +- **channel**:使用的 ADC 设备通道号,每个 ADC 设备对应多个通道; + +例如:`ADC(1,4)` 表示当前使用编号为 1 的 ADC 设备的 4 通道。 + +### 方法 + +#### **ADC.init**(channel) + +根据输入的层参数初始化 ADC 对象,入参为使用的 ADC 对象通道号; + +#### **ADC.deinit**() + +用于关闭 ADC 对象,ADC 对象 deinit 之后需要重新 init 才能使用。 + +#### **ADC.read**() + +用于获取并返回当前 ADC 对象的采样值。例如当前采样值为 2048,对应设备的分辨率为 12位,当前设备参考电压为 3.3V ,则该 ADC 对象通道上实际电压值的计算公式为:**采样值 * 参考电压 / (1 << 分辨率位数)**,即 `vol = 2048 / 4096 * 3.3 V = 1.15V`。 + +### 示例 + +``` python +>>> from machine import ADC # 从 machine 导入 ADC 类 +>>> adc = ADC(1, 13) # 创建 ADC 对象,当前使用编号为 1 的 ADC 设备的 13 通道 +>>> adc.read() # 获取 ADC 对象采样值 +4095 +>>> adc.deinit() # 关闭 ADC 对象 +>>> adc.init(13) # 开启并重新配置 ADC 对象 +``` diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/I2C.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/I2C.md new file mode 100644 index 0000000000000000000000000000000000000000..a1fd7f89e35ee5e23321fbeb13b7582f7b8df4da --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/I2C.md @@ -0,0 +1,111 @@ +## machine.I2C + +**machine.I2C** 类是 `machine` 模块下面的一个硬件类,用于对 `I2C` 的配置和控制,提供对 `I2C` 设备的操作方法。 + +- `I2C` 是一种用于设备间通信的两线协议。在物理层上,它由两根线组成:`SCL` 和 `SDA` ,即时钟和数据线。 +- `I2C` 对象被创建到一个特定的总线上,它们可以在创建时被初始化,也可以之后再来初始化。 +- 打印 `I2C` 对象会打印出配置时的信息。 + +### 构造函数 + +在 RT-Thread MicroPython 中 `I2C` 对象的构造函数如下: + +#### **class machine.I2C**(id= -1, scl, sda, freq=400000) +使用下面的参数构造并返回一个新的 `I2C` 对象: + +- **id** :标识特定的 `I2C` 外设。如果填入 id = -1,即选择软件模拟的方式实现 `I2C`,这时可以使用任意引脚来模拟 `I2C` 总线 ,这样在初始化时就必须指定 `scl` 和 `sda` 。 +软件 I2C 的初始化方式可参考 [软件 I2C 示例](#i2c_2)。 +硬件 I2C 的初始化方式可参考 [硬件 I2C 示例](#i2c_3)。 + +- **scl** : 应该是一个 `Pin` 对象,指定为一个用于 `scl` 的 `Pin` 对象。 +- **sda** : 应该是一个 `Pin` 对象,指定为一个用于 `sda` 的 `Pin` 对象。 +- **freq** :应该是为 `scl` 设置的最大频率。 + +### 方法 + +#### **I2C.init**(scl, sda, freq=400000) +初始化 `I2C` 总线,参数介绍可以参考构造函数中的参数。 + +#### **I2C.deinit**() +关闭 `I2C` 总线。 + +#### **I2C.scan**() +扫描所有 0x08 和 0x77 之间的 `I2C` 地址,然后返回一个有响应地址的列表。如果一个设备在总线上收到了他的地址,就会通过拉低 `SDA` 的方式来响应。 + +### I2C 基础方法 +下面的方法实现了基本的 `I2C` 总线操作,可以组合成任何的 `I2C` 通信操作,如果需要对总线进行更多的控制,可以可以使用他们,否则可以使用后面介绍的标准使用方法。 + +#### **I2C.start**() +在总线上产生一个启动信号。(`SCL` 为高时,`SDA` 转换为低) + +#### **I2C.stop**() +在总线上产生一个停止信号。(`SCL` 为高时,`SDA` 转换为高) + +#### **I2C.readinto**(buf, nack=True) +从总线上读取字节并将他们存储到 `buf` 中,读取的字节数时 `buf` 的长度。在收到最后一个字节以外的所有内容后,将在总线上发送 `ACK`。在收到最后一个字节之后,如果 `NACK` 是正确的,那么就会发送一个 `NACK`,否则将会发送 `ACK`。 + +#### **I2C.write**(buf) +将 `buf` 中的数据接入到总线,检查每个字节之后是否收到 `ACK`,并在收到 `NACK` 时停止传输剩余的字节。这个函数返回接收到的 `ACK` 的数量。 + +### I2C 标准总线操作 +下面的方法实现了标准 `I2C` 主设备对一个给定从设备的读写操作。 + +#### **I2C.readfrom**(addr, nbytes, stop=True) +从 `addr` 指定的从设备中读取 n 个字节,如果 `stop = True`,那么在传输结束时会产生一个停止信号。函数会返回一个存储着读到数据的字节对象。 + +#### **I2C.readfrom_into**(addr, buf, stop=True) +从 `addr` 指定的从设备中读取数据存储到 `buf` 中,读取的字节数将是 `buf` 的长度,如果 `stop = True`,那么在传输结束时会产生一个停止信号。 +这个方法没有返回值。 + +#### **I2C.writeto**(addr, buf, stop=True) +将 `buf` 中的数据写入到 `addr` 指定的的从设备中,如果在写的过程中收到了 `NACK` 信号,那么就不会发送剩余的字节。如果 `stop = True`,那么在传输结束时会产生一个停止信号,即使收到一个 `NACK`。这个函数返回接收到的 `ACK` 的数量。 + +### 内存操作 + +一些 `I2C` 设备充当一个内存设备,可以读取和写入。在这种情况下,有两个与 `I2C` 相关的地址,从机地址和内存地址。下面的方法是与这些设备进行通信的便利函数。 + +#### **I2C.readfrom_mem**(addr, memaddr, nbytes, \*, addrsize=8) +从 `addr` 指定的从设备中 `memaddr` 地址开始读取 n 个字节。`addrsize` 参数指定地址的长度。返回一个存储读取数据的字节对象。 + +#### **I2C.readfrom_mem_into**(addr, memaddr, buf, \*, addrsize=8) +从 `addr` 指定的从设备中 `memaddr` 地址读取数据到 `buf` 中,,读取的字节数是 `buf` 的长度。 +这个方法没有返回值。 + +#### **I2C.writeto_mem**(addr, memaddr, buf, \*, addrsize=8) +将 `buf` 里的数据写入 `addr` 指定的从机的 `memaddr` 地址中。 +这个方法没有返回值。 + +### 示例 + +#### 软件模拟 I2C +```python +>>> from machine import Pin, I2C +>>> clk = Pin(("clk", 29), Pin.OUT_OD) # Select the 29 pin device as the clock +>>> sda = Pin(("sda", 30), Pin.OUT_OD) # Select the 30 pin device as the data line +>>> i2c = I2C(-1, clk, sda, freq=100000) # create I2C peripheral at frequency of 100kHz +>>> i2c.scan() # scan for slaves, returning a list of 7-bit addresses +[81] # Decimal representation +>>> i2c.writeto(0x51, b'123') # write 3 bytes to slave with 7-bit address 42 +3 +>>> i2c.readfrom(0x51, 4) # read 4 bytes from slave with 7-bit address 42 +b'\xf8\xc0\xc0\xc0' +>>> i2c.readfrom_mem(0x51, 0x02, 1) # read 1 bytes from memory of slave 0x51(7-bit), +b'\x12' # starting at memory-address 8 in the slave +>>> i2c.writeto_mem(0x51, 2, b'\x10') # write 1 byte to memory of slave 42, + # starting at address 2 in the slave +``` + +#### 硬件 I2C + +需要先开启 `I2C` 设备驱动,查找设备可以在 `msh` 中输入`list_device` 命令。 +在构造函数的第一个参数传入 `0`,系统就会搜索名为 `i2c0` 的设备,找到之后使用这个设备来构建 `I2C` 对象: + +```python +>>> from machine import Pin, I2C +>>> i2c = I2C(0) # create I2C peripheral at frequency of 100kHz +>>> i2c.scan() # scan for slaves, returning a list of 7-bit addresses +[81] # Decimal representation +``` + + 更多内容可参考 [machine.I2C](http://docs.micropython.org/en/latest/pyboard/library/machine.I2C.html) 。 + diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/LCD.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/LCD.md new file mode 100644 index 0000000000000000000000000000000000000000..a9ba9e0debb59d0fb95314a4e2d14374db6ee866 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/LCD.md @@ -0,0 +1,76 @@ +## machine.LCD + +**machine.LCD** 类是 machine 模块下面的一个硬件类,用于对 LCD 的配置和控制,提供对 LCD 设备的操作方法。 + +IoT board 板载一块 1.3 寸,分辨率为 `240*240` 的 LCD 显示屏,因此对该屏幕操作时,(x, y) 坐标的范围是 `0 - 239`。 + +### 构造函数 + +在 RT-Thread MicroPython 中 `LCD` 对象的构造函数如下: + +#### **class machine.LCD**() +在给定总线上构造一个 `LCD` 对象,无入参,初始化的对象取决于特定硬件,初始化方式可参考 [示例](#_3)。 + +### 方法 + +#### **LCD.light**(value) + +控制是否开启 LCD 背光,入参为 True 则打开 LCD 背光,入参为 False 则关闭 LCD 背光。 + +#### **LCD.fill**(color) + +根据给定的颜色填充整个屏幕,支持多种颜色,可以传入的参数有: + +``` +WHITE BLACK BLUE BRED GRED GBLUE RED MAGENTA GREEN CYAN YELLOW BROWN BRRED GRAY GRAY175 GRAY151 GRAY240 +``` + +详细的使用方法可参考[示例](#_3)。 + +#### **LCD.pixel**(x, y, color) + +向指定的位置(x, y)画点,点的颜色为 color 指定的颜色,可指定的颜色和上一个功能相同。 + +注意:(x, y) 坐标的范围是 0 - 239,使用下面的方法对坐标进行操作时同样需要遵循此限制。 + +#### **LCD.text**(str, x, y, size) + +在指定的位置(x,y)写入字符串,字符串由 str 指定,字体的大小由 size 指定,size 的大小可为 16,24,32。 + +#### **LCD.line**(x1, y1, x2, y2) + +在 LCD 上画一条直线,起始地址为 (x1, y1),终点为(x2, y2)。 + +#### **LCD.rectangle**(x1, y1, x2, y2) + +在 LCD 上画一个矩形,左上角的位置为(x1, y1),右下角的位置为(x2, y2)。 + +#### **LCD.circle**(x1, y1, r) + +在 LCD 上画一个圆形,圆心的位置为(x1, y1),半径长度为 r。 + +#### **LCD.show_bmp**( x, y, pathname) + +在 LCD 指定位置上显示 32-bit bmp 格式的图片信息,注意显示 bmp 图片时,(x, y) 坐标是图片的左下角。 + +### 示例 + +```python +from machine import LCD # 从 machine 导入 LCD 类 +lcd = LCD() # 创建一个 lcd 对象 +lcd.light(False) # 关闭背光 +lcd.light(True) # 打开背光 +lcd.fill(lcd.BLACK) # 将整个 LCD 填充为黑色 +lcd.fill(lcd.RED) # 将整个 LCD 填充为红色 +lcd.fill(lcd.GRAY) # 将整个 LCD 填充为灰色 +lcd.fill(lcd.WHITE) # 将整个 LCD 填充为白色 +lcd.pixel(50, 50, lcd.BLUE) # 将(50,50)位置的像素填充为蓝色 +lcd.text("hello RT-Thread", 0, 0, 16) # 在(0, 0) 位置以 16 字号打印字符串 +lcd.text("hello RT-Thread", 0, 16, 24) # 在(0, 16)位置以 24 字号打印字符串 +lcd.text("hello RT-Thread", 0, 48, 32) # 在(0, 48)位置以 32 字号打印字符串 +lcd.line(0, 50, 239, 50) # 以起点(0,50),终点(239,50)画一条线 +lcd.line(0, 50, 239, 50) # 以起点(0,50),终点(239,50)画一条线 +lcd.rectangle(100, 100, 200, 200) # 以左上角为(100,100),右下角(200,200)画矩形 +lcd.circle(150, 150, 80) # 以圆心位置(150,150),半径为 80 画圆 +lcd.show_bmp(180, 50, "sun.bmp") # 以位置(180,50)为图片左下角坐标显示文件系统中的 bmp 图片 "sun.bmp" +``` diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/PWM.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/PWM.md new file mode 100644 index 0000000000000000000000000000000000000000..7269984ec2ea3f625ad5faddebe91312205864ef --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/PWM.md @@ -0,0 +1,57 @@ +## machine.PWM + +**machine.PWM** 类是 machine 模块下的一个硬件类,用于指定 PWM 设备的配置和控制,提供对 PWM 设备的操作方法。 + +- PWM (Pulse Width Modulation,脉冲宽度调制) 是一种对模拟信号电平进行数字编码的方式; +- PWM 设备可以通过调节有效电平在一个周期信号中的比例时间来操作设备; +- PWM 设备两个重要的参数:频率(freq)和占空比(duty); + - 频率:从一个上升沿(下降沿)到下一个上升沿(下降沿)的时间周期,单位为 Hz; + - 占空比:有效电平(通常为电平)在一个周期内的时间比例; + +### 构造函数 + +在 RT-Thread MicroPython 中 `PWM` 对象的构造函数如下: + +#### **class machine.PWM**(id, channel, freq, duty) + +在给定的总线上构建一个 `PWM` 对象,参数介绍如下: + +- **id**:使用的 PWM 设备编号,如 `id = 1` 表示编号为 1 的 PWM 设备,或者表示使用的 PWM 设备名,如 `id = "pwm"` 表示设备名为 `pwm` 的 PWM 设备; +- **channel**:使用的 PWM 设备通道号,每个 PWM 设备包含多个通道,范围为 [0, 4]; +- **freq**:初始化频率,范围 [1, 156250]; +- **duty**:初始化占空比数值,范围 [0 255]; + +例如:`PWM(1,4,100,100)` 表示当前使用 编号为 1 的 PWM 设备的 4 通道,初始化频率为 1000 Hz,初始化占空比的数值为 100。 + +### 方法 + +#### **PWM.init**(channel, freq, duty) + +根据输入的参数初始化 PWM 对象,参数说明同上。 + +#### **PWM.deinit**() + +用于关闭 PWM 对象,对象 deinit 之后需要重新 init 才能使用。 + +#### **PWM.freq**(freq) + +用于获取或者设置 PWM 对象的频率,频率的范围为 [1, 156250]。如果参数为空,返回当前 PWM 对象的频率;如果参数非空,则使用该参数设置当前 PWM 对象的频率。 + +#### **PWM.duty**(duty) + +用于获取或者设置 PWM 对象的占空比数值,占空比数值的范围为 [0, 255],例如 `duty = 100`,表示当前设备占空比为 `100/255 = 39.22%` 。如果参数为空,返回当前 PWM 对象的占空比数值;如果参数非空,则使用该参数设置当前 PWM 对象的占空比数值。 + +### 示例 + +``` python +>>> from machine import PWM # 从 machine 导入 PWM 类 +>>> pwm = PWM(3, 3, 1000, 100) # 创建 PWM 对象,当前使用编号为 3 的 PWM 设备的 3 通道,初始化的频率为 1000Hz,占空比数值为 100(占空比为 100/255 = 39.22%) +>>> pwm.freq(2000) # 设置 PWM 对象频率 +>>> pwm.freq() # 获取 PWM 对象频率 +2000 +>>> pwm.duty(200) # 设置 PWM 对象占空比数值 +>>> pwm.duty() # 获取 PWM 对象占空比数值 +200 +>>> pwm.deinit() # 关闭 PWM 对象 +>>> pwm.init(3, 1000, 100) # 开启并重新配置 PWM 对象 +``` \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/Pin.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/Pin.md new file mode 100644 index 0000000000000000000000000000000000000000..8b1039d519bb2bc948555da9ff8dc4a2f0678e54 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/Pin.md @@ -0,0 +1,110 @@ +## machine.Pin + +**machine.Pin** 类是 machine 模块下面的一个硬件类,用于对引脚的配置和控制,提供对 `Pin` 设备的操作方法。 + +`Pin` 对象用于控制输入/输出引脚(也称为 `GPIO`)。`Pin` 对象通常与一个物理引脚相关联,他可以驱动输出电压和读取输入电压。Pin 类中有设置引脚模式(输入/输出)的方法,也有获取和设置数字逻辑(`0` 或 `1`)的方法。 + +一个 `Pin` 对象是通过一个标识符来构造的,它明确地指定了一个特定的输入输出。标识符的形式和物理引脚的映射是特定于一次移植的。标识符可以是整数,字符串或者是一个带有端口和引脚号码的元组。在 RT-Thread MicroPython 中,引脚标识符是一个由代号和引脚号组成的元组,如 `Pin(("PB15", 31), Pin.OUT_PP)` 中的` ("PB15", 31)`。 + +### 构造函数 + +在 RT-Thread MicroPython 中 `Pin` 对象的构造函数如下: + +#### **class machine.Pin**( id, mode = -1, pull = -1,value) +- **id** :由用户自定义的引脚名和 `Pin` 设备引脚号组成,如 ("PB15", 31),"PB15" 为用户自定义的引脚名,`31` 为 `RT-Thread Pin` 设备驱动在本次移植中的引脚号。 + +- **mode** : 指定引脚模式,可以是以下几种: + - **Pin.IN** :输入模式 + - **Pin.OUT** :输出模式 + - **Pin.OPEN_DRAIN** :开漏模式 + +- **pull** : 如果指定的引脚连接了上拉下拉电阻,那么可以配置成下面的状态: + - **None** :没有上拉或者下拉电阻。 + - **Pin.PULL_UP** :使能上拉电阻。 + - **Pin.PULL_DOWN** :使能下拉电阻。 + +- **value** : `value` 的值只对输出模式和开漏输出模式有效,用来设置初始输出值。 + +### 方法 + +#### **Pin.init**(mode= -1, pull= -1, \*, value, drive, alt) + +根据输入的参数重新初始化引脚。只有那些被指定的参数才会被设置,其余引脚的状态将保持不变,详细的参数可以参考上面的构造函数。 + +#### **Pin.value**([x]) +如果没有给定参数 `x` ,这个方法可以获得引脚的值。 +如果给定参数 `x` ,如 `0` 或 `1`,那么设置引脚的值为 逻辑 `0` 或 逻辑 `1`。 + +#### **Pin.name**() +返回引脚对象在构造时用户自定义的引脚名。 + +#### **Pin.irq**(handler=None, trigger=(Pin.IRQ_RISING)) + +配置在引脚的触发源处于活动状态时调用的中断处理程序。如果引脚模式是, `Pin.IN` 则触发源是引脚上的外部值。 如果引脚模式是, `Pin.OUT` 则触发源是引脚的输出缓冲器。 否则,如果引脚模式是, `Pin.OPEN_DRAIN` 那么触发源是状态'0'的输出缓冲器和状态'1'的外部引脚值。 + +参数: + +- `handler` 是一个可选的函数,在中断触发时调用 +- `trigger` 配置可以触发中断的事件。可能的值是: + - `Pin.IRQ_FALLING` 下降沿中断 + - `Pin.IRQ_RISING` 上升沿中断 + - `Pin.IRQ_RISING_FALLING` 上升沿或下降沿中断 + - `Pin.IRQ_LOW_LEVEL` 低电平中断 + - `Pin.IRQ_HIGH_LEVEL` 高电平中断 + +### 常量 + +下面的常量用来配置 `Pin` 对象。 + +#### 选择引脚模式: +##### **Pin.IN** +##### **Pin.OUT** +##### **Pin.OPEN_DRAIN** + +#### 选择上/下拉模式: +##### **Pin.PULL_UP** +##### **Pin.PULL_DOWN** +##### **None** +使用值 `None` 代表不进行上下拉。 + +#### 选择中断触发模式: +##### **Pin.IRQ_FALLING** +##### **Pin.IRQ_RISING** +##### **Pin.IRQ_RISING_FALLING** +##### **Pin.IRQ_LOW_LEVEL** +##### **Pin.IRQ_HIGH_LEVEL** + +### 示例一 + +控制引脚输出高低电平信号,并读取按键引脚电平信号。 + +``` +from machine import Pin + +PIN_OUT = 31 +PIN_IN = 58 + +p_out = Pin(("PB15", PIN_OUT), Pin.OUT_PP) +p_out.value(1) # set io high +p_out.value(0) # set io low + +p_in = Pin(("key_0", PIN_IN), Pin.IN, Pin.PULL_UP) +print(p_in.value() ) # get value, 0 or 1 +``` + +### 示例二 + +上升沿信号触发引脚中断后执行中断处理函数。 + +``` +from machine import Pin + +PIN_KEY0 = 58 # PD10 +key_0 = Pin(("key_0", PIN_KEY0), Pin.IN, Pin.PULL_UP) + +def func(v): + print("Hello rt-thread!") + +key_0.irq(trigger=Pin.IRQ_RISING, handler=func) +``` +更多内容可参考 [machine.Pin](http://docs.micropython.org/en/latest/pyboard/library/machine.Pin.html) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/RTC.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/RTC.md new file mode 100644 index 0000000000000000000000000000000000000000..be8e4b13a5e409513f52bc699961f9680fa54054 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/RTC.md @@ -0,0 +1,56 @@ +## machine.RTC + +**machine.RTC** 类是 machine 模块下面的一个硬件类,用于对指定 RTC 设备的配置和控制,提供对 RTC 设备的操作方法。 + +- RTC(Real-Time Clock )实时时钟可以提供精确的实时时间,它可以用于产生年、月、日、时、分、秒等信息。 + +### 构造函数 + +在 RT-Thread MicroPython 中 `RTC` 对象的构造函数如下: + +#### **class machine.RTC**() + +所以在给定的总线上构造一个 `RTC` 对象,无入参对象,使用方式可参考 [示例](#_3)。 + +### 方法 + +#### **RTC.init**(datetime) + +根据传入的参数初始化 RTC 设备起始时间。入参 `datetime` 为一个时间元组,格式如下: + +``` +(year, month, day, wday, hour, minute, second, yday) +``` +参数介绍如下所示: + +- **year**:年份; +- **month**:月份,范围 [1, 12]; +- **day**:日期,范围 [1, 31]; +- **wday**:星期,范围 [0, 6],0 表示星期一,以此类推; +- **hour**:小时,范围 [0, 23]; +- **minute**:分钟,范围[0, 59]; +- **second**:秒,范围[0, 59]; +- **yday**:从当前年份 1 月 1 日开始的天数,范围 [0, 365],一般置位 0 未实现。 + +使用的方式可参考 [示例](#_3)。 + +#### **RTC.deinit**() + +重置 RTC 设备时间到 2015 年 1 月 1日,重新运行 RTC 设备。 + +#### **RTC.now**() + +获取当前时间,返回值为上述 `datetime` 时间元组格式。 + +### 示例 + +```python +>>> from machine import RTC +>>> rtc = RTC() # 创建 RTC 设备对象 +>>> rtc.init((2019,6,5,2,10,22,30,0)) # 设置初始化时间 +>>> rtc.now() # 获取当前时间 +(2019, 6, 5, 2, 10, 22, 40, 0) +>>> rtc.deinit() # 重置时间到2015年1月1日 +>>> rtc.now() # 获取当前时间 +(2015, 1, 1, 3, 0, 0, 1, 0) +``` diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/SPI.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/SPI.md new file mode 100644 index 0000000000000000000000000000000000000000..93994d2a8e5d136e4fe07b84fbe5c5d6b803fa3b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/SPI.md @@ -0,0 +1,99 @@ +## machine.SPI + +**machine.SPI** 类是 machine 模块下面的一个硬件类,用于对 SPI 的配置和控制,提供对 SPI 设备的操作方法。 + +- `SPI` 是一个由主机驱动的同步串行协议。在物理层,总线有三根:`SCK`、`MOSI`、`MISO`。多个设备可以共享同一总线,每个设备都由一个单独的信号 `SS` 来选中,也称片选信号。 +- 主机通过片选信号选定一个设备进行通信。`SS` 信号的管理应该由用户代码负责。(通过 [machine.Pin](Pin.md)) + +### 构造函数 + +在 RT-Thread MicroPython 中 `SPI` 对象的构造函数如下: + +#### **class machine.SPI**(id, ...) +在给定总线上构造一个 `SPI` 对象,`id` 取决于特定的移植。 + +如果想要使用软件 `SPI` , 即使用引脚模拟 `SPI` 总线,那么初始化的第一个参数需要设置为 `-1` ,可参考 [软件 SPI 示例](#spi) 。 + +使用硬件 `SPI` 在初始化时只需传入 `SPI` 设备的编号即可,如 '50' 表示 `SPI5` 总线上的第 0 个设备。初始化方式可参考 [硬件 SPI 示例](#spi_1)。 + +如果没有额外的参数,`SPI` 对象会被创建,但是不会被初始化,如果给出额外的参数,那么总线将被初始化,初始化参数可以参考下面的 `SPI.init` 方法。 + +### 方法 + +#### **SPI.init**(baudrate=1000000, \*, polarity=0, phase=0, bits=8, firstbit=SPI.MSB, sck=None, mosi=None, miso=None) + +用给定的参数初始化`SPI`总线: + +- **baudrate** :`SCK` 时钟频率。 +- **polarity** :极性可以是 `0` 或 `1`,是时钟空闲时所处的电平。 +- **phase** :相位可以是 `0` 或 `1`,分别在第一个或者第二个时钟边缘采集数据。 +- **bits** :每次传输的数据长度,一般是 8 位。 +- **firstbit** :传输数据从高位开始还是从低位开始,可以是 `SPI.MSB` 或者 `SPI.LSB`。 +- **sck** :用于 `sck` 的 `machine.Pin` 对象。 +- **mosi** :用于 `mosi` 的 `machine.Pin` 对象。 +- **miso** :用于`miso` 的 `machine.Pin` 对象。 + +#### **SPI.deinit**() +关闭 `SPI` 总线。 + +#### **SPI.read**(nbytes, write=0x00) +读出 n 字节的同时不断的写入 `write` 给定的单字节。返回一个存放着读出数据的字节对象。 + +#### **SPI.readinto**(buf, write=0x00) +读出 n 字节到 `buf` 的同时不断地写入 `write` 给定的单字节。 +这个方法返回读入的字节数。 + +#### **SPI.write**(buf) +写入 `buf` 中包含的字节。返回`None`。 + +#### **SPI.write_readinto**(write_buf, read_buf) +在读出数据到 `readbuf` 时,从 `writebuf` 中写入数据。缓冲区可以是相同的或不同,但是两个缓冲区必须具有相同的长度。返回 `None`。 + +### 常量 + +#### **SPI.MASTER** +用于初始化 `SPI` 总线为主机。 + +#### **SPI.MSB** +设置从高位开始传输数据。 + +#### **SPI.LSB** +设置从低位开始传输数据。 + +### 示例 + +#### 软件模拟 SPI +``` +>>> from machine import Pin, SPI +>>> clk = Pin(("clk", 26), Pin.OUT_PP) +>>> mosi = Pin(("mosi", 27), Pin.OUT_PP) +>>> miso = Pin(("miso", 28), Pin.IN) +>>> spi = SPI(-1, 500000, polarity = 0, phase = 0, bits = 8, firstbit = 0, sck = clk, mosi = mosi, miso = miso) +>>> print(spi) +SoftSPI(baudrate=500000, polarity=0, phase=0, sck=clk, mosi=mosi, miso=miso) +>>> spi.write("hello rt-thread!") +>>> spi.read(10) +b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' +``` + +#### 硬件 SPI + +需要先开启 `SPI` 设备驱动,查找设备可以在 `msh` 中输入`list_device` 命令。 +在构造函数的第一个参数传入 `50`,系统就会搜索名为 `spi50` 的设备,找到之后使用这个设备来构建 `SPI` 对象: + +``` +>>> from machine import SPI +>>> spi = SPI(50) +>>> print(spi) +SPI(device port : spi50) +>>> spi.write(b'\x9f') +>>> spi.read(5) +b'\xff\xff\xff\xff\xff' +>>> buf = bytearray(1) +>>> spi.write_readinto(b"\x9f",buf) +>>> buf +bytearray(b'\xef') +>>> spi.init(100000,0,0,8,1) # Resetting SPI parameter +``` + + 更多内容可参考 [machine.SPI](http://docs.micropython.org/en/latest/pyboard/library/machine.SPI.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/Timer.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/Timer.md new file mode 100644 index 0000000000000000000000000000000000000000..54cfe59108e4471b060ab81b3fe57fd52b76b034 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/Timer.md @@ -0,0 +1,73 @@ +## machine.Timer + +**machine.Timer** 类是 machine 模块下的一个硬件类,用于 Timer 设备的配置和控制,提供对 Timer 设备的操作方法。 + +- Timer(硬件定时器),是一种用于处理周期性和定时性事件的设备。 +- Timer 硬件定时器主要通过内部计数器模块对脉冲信号进行计数,实现周期性设备控制的功能。 +- Timer 硬件定时器可以自定义**超时时间**和**超时回调函数**,并且提供两种**定时器模式**: + - `ONE_SHOT`:定时器只执行一次设置的回调函数; + - `PERIOD`:定时器会周期性执行设置的回调函数; +- 打印 Timer 对象会打印出配置的信息。 + +### 构造函数 + +在 RT-Thread MicroPython 中 `Timer` 对象的构造函数如下: + +#### **class machine.Timer**(id) + +- **id**:使用的 Timer 设备编号,`id = 1` 表示编号为 1 的 Timer 设备,或者表示使用的 timer 设备名,如 `id = "timer"` 表示设备名为 `timer` 的 Timer 设备; + +该函数主要用于通过设备编号创建 Timer 设备对象。 + +### 方法 + +#### **Timer.init**(mode = Timer.PERIODIC, period = 0, callback = None) + +- **mode**:设置 Timer 定时器模式,可以设置两种模式:`ONE_SHOT`(执行一次)、`PERIOD`(周期性执行),默认设置的模式为 `PERIOD` 模式; + +- **period**:设置 Timer 定时器定时周期,单位:毫秒(ms) + +- **callback**:设置 Timer 定义器超时回调函数,默认设置的函数为 None 空函数,设置的函数格式如下所示: + +```python +def callback_test(device): # 回调函数有且只有一个入参,为创建的 Timer 对象 + print("Timer callback test") + print(device) # 打印 Timer 对象配置信息 +``` + +该函数使用方式如下示例所示: + +```python +timer.init(wdt.PERIOD, 5000, callback_test) # 设置定时器模式为周期性执行,超时时间为 5 秒, 超时函数为 callback_test +``` +#### **Timer.deinit**() + +该函数用于停止并关闭 Timer 设备。 + +### 常量 + +下面的常量用来配置 `Timer` 对象。 + +#### 选择定时器模式: +##### **Timer.PERIODIC** +##### **Timer.ONE_SHOT** + +### 示例 + +```python +>>> from machine import Timer # 从 machine 导入 Timer 类 +>>> timer = Timer(15) # 创建 Timer 对象,当前设备编号为 11 +>>> # 进入粘贴模式 +paste mode; Ctrl-C to cancel, Ctrl-D to finish +=== def callback_test(device): # 定义超时回调函数 +=== print("Timer callback test") +>>> timer.init(timer.PERIODIC, 5000, callback_test) # 初始化 Timer 对象,设置定时器模式为循环执行,超时时间为 5 秒,超时回调函数 callback_test +>>> Timer callback test # 5 秒超时循环执行回调函数,打印日志 +>>> Timer callback test +>>> Timer callback test +>>> timer.init(timer.ONE_SHOT, 5000, callback_test) # 设置定时器模式为只执行一次,超时时间为 5 秒,超时回调函数为 callback_test +>>> Timer callback test # 5 秒超时后执行一次回调函数,打印日志 +>>> timer.deinit() # 停止并关闭 Timer 定时器 +``` + +更多内容可参考 [machine.Timer](http://docs.micropython.org/en/latest/library/machine.Timer.html)。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/UART.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/UART.md new file mode 100644 index 0000000000000000000000000000000000000000..e10ea9c9d6120000b1dcc2c93374d63fea58401a --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/UART.md @@ -0,0 +1,60 @@ +## machine.UART + +**machine.UART** 类是 machine 模块下面的一个硬件类,用于对 UART 的配置和控制,提供对 UART 设备的操作方法。 + +`UART` 实现了标准的 `uart/usart` 双工串行通信协议,在物理层上,他由两根数据线组成:`RX` 和 `TX`。通信单元是一个字符,它可以是 8 或 9 位宽。 + +### 构造函数 + +在 RT-Thread MicroPython 中 `UART` 对象的构造函数如下: + +#### **class machine.UART**(id, ...) +在给定总线上构造一个 `UART` 对象,`id` 取决于特定的移植。 +初始化参数可以参考下面的 `UART.init` 方法。 + +使用硬件 UART 在初始化时只需传入 `UART` 设备的编号即可,如传入 `1` 表示 `uart1` 设备。 +初始化方式可参考 [示例](#_3)。 + +### 方法 + +#### **UART.init**(baudrate = 9600, bits=8, parity=None, stop=1) +- **baudrate** :`SCK` 时钟频率。 +- **bits** :每次发送数据的长度。 +- **parity** :校验方式。 +- **stop** :停止位的长度。 + +#### **UART.deinit**() +关闭串口总线。 + +#### **UART.read**([nbytes]) +读取字符,如果指定读 n 个字节,那么最多读取 n 个字节,否则就会读取尽可能多的数据。 +返回值:一个包含读入数据的字节对象。如果如果超时则返回 `None`。 + +#### **UART.readinto**(buf[, nbytes]) +读取字符到 `buf` 中,如果指定读 n 个字节,那么最多读取 n 个字节,否则就读取尽可能多的数据。另外读取数据的长度不超过 `buf` 的长度。 +返回值:读取和存储到 `buf` 中的字节数。如果超时则返回 `None`。 + +#### **UART.readline**() +读一行数据,以换行符结尾。 +返回值:读入的行数,如果超时则返回 `None`。 + +#### **UART.write**(buf) +将 `buf` 中的数据写入总线。 +返回值:写入的字节数,如果超时则返回 `None`。 + +### 示例 + +在构造函数的第一个参数传入`1`,系统就会搜索名为 `uart1` 的设备,找到之后使用这个设备来构建 `UART` 对象: + +```python +from machine import UART +uart = UART(1, 115200) # init with given baudrate +uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters +uart.read(10) # read 10 characters, returns a bytes object +uart.read() # read all available characters +uart.readline() # read a line +uart.readinto(buf) # read and store into the given buffer +uart.write('abc') # write the 3 characters +``` + + 更多内容可参考 [machine.UART](http://docs.micropython.org/en/latest/pyboard/library/machine.UART.html) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/WDT.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/WDT.md new file mode 100644 index 0000000000000000000000000000000000000000..64af73f6b14b0e3398b4974faa5c37179037462e --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/machine/WDT.md @@ -0,0 +1,42 @@ +## machine.WDT + +**machine.WDT** 类是 machine 模块下的一个硬件类,用于 WDT 设备的配置和控制,提供对 WDT 设备的操作方法。 + +如下为 WDT 设备基本介绍: + +- WDT(WatchDog Timer,硬件看门狗),是一个定时器设备,用于系统程序结束或出错导致系统进入不可恢复状态时重启系统。 + +- WDT 启动之后,计数器开始计数,在计数器溢出前没有被复位,会对 CPU 产生一个复位信号使设备重启(简称 “被狗咬”); + +- 系统正常运行时,需要在 WDT 设备允许的时间间隔内对看门狗计数清零(简称“喂狗”),WDT 设备一旦启动,需要定时“喂狗”以确保设备正常运行。 + +### 构造函数 + +在 RT-Thread MicroPython 中 `WDT` 对象的构造函数如下: + +#### **class machine.WDT**(id = "wdt", timeout=5) + +- **id**: 使用的 WDT 设备编号,`id = 1` 表示编号为 1 的 WDT 设备,或者表示使用的 WDT 设备名,如 `id = "wdt"` 表示设备名为 `wdt` 的 WDT 设备; + +- **timeout**:设置看门狗超时时间,单位:秒(s); + +用于创建一个 WDT 对象并且启动 WDT 功能。一旦启动,设置的超时时间无法改动,WDT 功能无法停止。 + +如果该函数入参为空,则设置超时时间为 5 秒;如果入参非空,则使用该入参设置 WDT 超时时间,超时时间最小设置为 1 秒。 + +### 方法 + +#### **WDT.feed**() + +用于执行“喂狗”操作,清空看门狗设备计数。应用程序应该合理的周期性调用该函数,以防止系统重启。 + +### 示例 + +``` python +>>> from machine import WDT # 从 machine 导入 WDT 类 +>>> wdt = WDT() # 创建 WDT 对象,默认超时时间为 5 秒 +>>> wdt = WDT(10) # 创建 WDT 对象,设置超时时间为 10 秒 +>>> wdt.feed() # 在 10 秒超时时间内需要执行“喂狗”操作,清空看门狗设备计数,否则系统将重启 +``` + +更多内容可参考 [machine.WDT](http://docs.micropython.org/en/latest/library/machine.WDT.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/micropython.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/micropython.md new file mode 100644 index 0000000000000000000000000000000000000000..040e8e3ef93caae8710bd3a679cedbbc7ed1c127 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/micropython.md @@ -0,0 +1,72 @@ + +# micropython – 内部功能访问与控制模块 + +## Functions + +### micropython.const(expr) +Used to declare that the expression is a constant so that the compile can optimise it. The use of this function should be as follows: + + +```python +from micropython import const +CONST_X = const(123) +CONST_Y = const(2 * CONST_X + 1) +``` + +Constants declared this way are still accessible as global variables from outside the module they are declared in. On the other hand, if a constant begins with an underscore then it is hidden, it is not available as a global variable, and does not take up any memory during execution. + +This const function is recognised directly by the MicroPython parser and is provided as part of the micropython module mainly so that scripts can be written which run under both CPython and MicroPython, by following the above pattern. + +### micropython.opt_level([level]) + +If level is given then this function sets the optimisation level for subsequent compilation of scripts, and returns None. Otherwise it returns the current optimisation level. + +- The optimisation level controls the following compilation features: +- Assertions: at level 0 assertion statements are enabled and compiled into the bytecode; at levels 1 and higher assertions are not compiled. +- Built-in __debug__ variable: at level 0 this variable expands to True; at levels 1 and higher it expands to False. + Source-code line numbers: at levels 0, 1 and 2 source-code line number are stored along with the bytecode so that exceptions can report the line number they occurred at; at levels 3 and higher line numbers are not stored. +- The default optimisation level is usually level 0. + +### micropython.alloc_emergency_exception_buf(size) +Allocate size bytes of RAM for the emergency exception buffer (a good size is around 100 bytes). The buffer is used to create exceptions in cases when normal RAM allocation would fail (eg within an interrupt handler) and therefore give useful traceback information in these situations. + +A good way to use this function is to put it at the start of your main script (eg boot.py or main.py) and then the emergency exception buffer will be active for all the code following it. + +### micropython.mem_info([verbose]) + +Print information about currently used memory. If the verbose argument is given then extra information is printed. + +The information that is printed is implementation dependent, but currently includes the amount of stack and heap used. In verbose mode it prints out the entire heap indicating which blocks are used and which are free. + +### micropython.qstr_info([verbose]) +Print information about currently interned strings. If the verbose argument is given then extra information is printed. + +The information that is printed is implementation dependent, but currently includes the number of interned strings and the amount of RAM they use. In verbose mode it prints out the names of all RAM-interned strings. + +### micropython.stack_use() + Return an integer representing the current amount of stack that is being used. The absolute value of this is not particularly useful, rather it should be used to compute differences in stack usage at different points. + +### micropython.heap_lock() +### micropython.heap_unlock() + Lock or unlock the heap. When locked no memory allocation can occur and a MemoryError will be raised if any heap allocation is attempted. + +These functions can be nested, ie heap_lock() can be called multiple times in a row and the lock-depth will increase, and then heap_unlock() must be called the same number of times to make the heap available again. + +If the REPL becomes active with the heap locked then it will be forcefully unlocked. + +### micropython.kbd_intr(chr) +Set the character that will raise a KeyboardInterrupt exception. By default this is set to 3 during script execution, corresponding to Ctrl-C. Passing -1 to this function will disable capture of Ctrl-C, and passing 3 will restore it. + +This function can be used to prevent the capturing of Ctrl-C on the incoming stream of characters that is usually used for the REPL, in case that stream is used for other purposes. + +### micropython.schedule(func, arg) + Schedule the function func to be executed “very soon”. The function is passed the value arg as its single argument. “Very soon” means that the MicroPython runtime will do its best to execute the function at the earliest possible time, given that it is also trying to be efficient, and that the following conditions hold: + +- A scheduled function will never preempt another scheduled function. +- Scheduled functions are always executed “between opcodes” which means that all fundamental Python operations (such as appending to a list) are guaranteed to be atomic. +- A given port may define “critical regions” within which scheduled functions will never be executed. Functions may be scheduled within a critical region but they will not be executed until that region is exited. An example of a critical region is a preempting interrupt handler (an IRQ). + A use for this function is to schedule a callback from a preempting IRQ. Such an IRQ puts restrictions on the code that runs in the IRQ (for example the heap may be locked) and scheduling a function to call later will lift those restrictions. + +Note: If schedule() is called from a preempting IRQ, when memory allocation is not allowed and the callback to be passed to schedule() is a bound method, passing this directly will fail. This is because creating a reference to a bound method causes memory allocation. A solution is to create a reference to the method in the class constructor and to pass that reference to schedule(). This is discussed in detail here reference documentation under “Creation of Python objects”. + +There is a finite queue to hold the scheduled functions and schedule() will raise a RuntimeError if the queue is full. \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/network.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/network.md new file mode 100644 index 0000000000000000000000000000000000000000..3f145cbb8ae1acd2bd0d8b6b9158eb5a80ae01e2 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/network.md @@ -0,0 +1,10 @@ +## network – 网络配置 + +此模块提供网络驱动程序和路由配置。特定硬件的网络驱动程序在此模块中可用,用于配置硬件网络接口。然后,配置接口提供的网络服务可以通过 `usocket` 模块使用。 + +### 专用的网络类配置 + +下面具体的类实现了抽象网卡的接口,并提供了一种控制各种网络接口的方法。 + +- [class WLAN – control built-in WiFi interfaces](network/wlan.md) + diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/network/wlan.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/network/wlan.md new file mode 100644 index 0000000000000000000000000000000000000000..f71c0861fda95f67f557e4644eaedcb62a3f5fc1 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/network/wlan.md @@ -0,0 +1,109 @@ +## class WLAN – 控制内置的 WiFi 网络接口 + +该类为 WiFi 网络处理器提供一个驱动程序。使用示例: + +```python +import network +# enable station interface and connect to WiFi access point +nic = network.WLAN(network.STA_IF) +nic.active(True) +nic.connect('your-ssid', 'your-password') +# now use sockets as usual +``` + +### 构造函数 + +在 RT-Thread MicroPython 中 `WLAN` 对象的构造函数如下: + +#### class network.WLAN(interface_id) + +创建一个 WLAN 网络接口对象。支持的接口是 ` network.STA_IF`(STA 模式,可以连接到上游的 WiFi 热点上) 和 `network.AP_IF`(AP 模式,允许其他 WiFi 客户端连接到自身的热点)。下面方法的可用性取决于接口的类型。例如,只有STA 接口可以使用 `WLAN.connect()` 方法连接到 AP 热点上。 + +### 方法 + +#### **WLAN.active**([is_active]) + +如果向该方法传入布尔数值,传入 True 则使能卡,传入 False 则禁止网卡。否则,如果不传入参数,则查询当前网卡的状态。 + +#### **WLAN.connect**(ssid, password) +使用指定的账号和密码链接指定的无线热点。 + +#### **WLAN.disconnect**() +从当前链接的无线网络中断开。 + +#### **WLAN.scan**() + +扫描当前可以连接的无线网络。 + +只能在 STA 模式下进行扫描,使用元组列表的形式返回 WiFi 接入点的相关信息。 + +(ssid, bssid, channel, rssi, authmode, hidden) + +#### **WLAN.status**([param]) + +返回当前无线连接的状态。 + +当调用该方法时没有附带参数,就会返回值描述当前网络连接的状态。如果还没有从热点连接中获得 IP 地址,此时的状态为 `STATION_IDLE`。如果已经从连接的无线网络中获得 IP 地址,此时的状态为 `STAT_GOT_IP`。 + +当调用该函数使用的参数为 `rssi` 时,则返回 `rssi` 的值,该函数目前只支持这一个参数。 + +#### **WLAN.isconnected**() + +在 STA 模式时,如果已经连接到 WiFi 网络,并且获得了 IP 地址,则返回 True。如果处在 AP 模式,此时已经与客户端建立连接,则返回 True。其他情况下都返回 False。 + +#### WLAN.ifconfig([(ip, subnet, gateway, dns)]) + +获取或者设置网络接口的参数,IP 地址,子网掩码,网关,DNS 服务器。当调用该方法不附带参数时,该方法会返回一个包含四个元素的元组来描述上面的信息。想要设置上面的值,传入一个包含上述四个元素的元组,例如: + +```python +nic.ifconfig(('192.168.0.4', '255.255.255.0', '192.168.0.1', '8.8.8.8')) +``` + +#### **WLAN.config**('param') + +#### WLAN.config(param=value, ...) + +获取或者设置一般网络接口参数,这些方法允许处理标准的 ip 配置之外的其他参数,如 `WLAN. ifconfig()` 函数处理的参数。这些参数包括特定网络和特定硬件的参数。对于参数的设置,应该使用关键字的语法,可以一次性设置多个参数。 + +当查询参数时,参数名称的引用应该为字符串,且每次只能查询一个参数。 + +```python +# Set WiFi access point name (formally known as ESSID) and WiFi password +ap.config(essid='My_AP', password="88888888") +# Query params one by one +print(ap.config('essid')) +print(ap.config('channel')) +``` +下面是目前支持的参数: + +| Parameter | Description | +| --------- | --------------------------------- | +| mac | MAC address (bytes) | +| essid | WiFi access point name (string) | +| channel | WiFi channel (integer) | +| hidden | Whether ESSID is hidden (boolean) | +| password | Access password (string) | + + +### 示例 + +STA 模式下: + +```python +import network +wlan = network.WLAN(network.STA_IF) +wlan.scan() +wlan.connect("rtthread","02188888888") +wlan.isconnected() +``` + +AP 模式下: + +```python +import network +ap = network.WLAN(network.AP_IF) +ap.config(essid="hello_rt-thread", password="88888888") +ap.active(True) +ap.config("essid") +``` + diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/rtthread.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/rtthread.md new file mode 100644 index 0000000000000000000000000000000000000000..5fe265767f6ce8e77029a71999bba595f3e9a72f --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/spec-librarys/rtthread.md @@ -0,0 +1,23 @@ +## **rtthread** – 系统相关函数 + +**rtthread** 模块提供了与 RT-Thread 操作系统相关的功能,如查看栈使用情况等。 + +### 函数 + +#### rtthread.current_tid() +返回当前线程的 id 。 + +#### rtthread.is_preempt_thread() +返回是否是可抢占线程。 + +### 示例 + +``` +>>> import rtthread +>>> +>>> rtthread.is_preempt_thread() # determine if it's a preemptible thread +True +>>> rtthread.current_tid() # current thread id +268464956 +>>> +``` diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/_thread.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/_thread.md new file mode 100644 index 0000000000000000000000000000000000000000..29faf523684409748ee82dd0707d5e9ecdc718d9 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/_thread.md @@ -0,0 +1,28 @@ +## _thread – 多线程支持 + +`_thread` 模块提供了用于处理多线程的基本方法——多个控制线程共享它们的全局数据空间。为了实现同步,提供了简单的锁(也称为互斥锁或二进制信号量)。 + +### 示例 + +```python +import _thread +import time +def testThread(): + while True: + print("Hello from thread") + time.sleep(2) + +_thread.start_new_thread(testThread, ()) +while True: + pass +``` + +输出结果(启动新的线程,每隔两秒打印字符): + +Hello from thread +Hello from thread +Hello from thread +Hello from thread +Hello from thread + +更多内容可参考 [_thread](http://docs.micropython.org/en/latest/pyboard/library/_thread.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/builtins.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/builtins.md new file mode 100644 index 0000000000000000000000000000000000000000..3f92ff7c0e7252255de74b168fcae39e990edf82 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/builtins.md @@ -0,0 +1,104 @@ +# Builtin functions and exceptions + +所有的内饰函数和异常类型都在下面描述。 + +## 函数和类型 + +- abs() +- all() +- any() +- bin() +- **class** bool +- **class** bytearray +- **class** bytes +- callable() +- chr() +- classmethod() +- compile() +- **class** complex +- delattr(obj, name) + +- **class** dict +- dir() + +- divmod() +- enumerate() +- eval() +- exec() +- filter() +- **class** float +- **class** frozenset +- getattr() +- globals() +- hasattr() +- hash() +- hex() +- id() +- input() +- **class** int + - classmethod from_bytes(bytes, byteorder) + In MicroPython, byteorder parameter must be positional (this is compatible with CPython). + - to_bytes(size, byteorder) + In MicroPython, byteorder parameter must be positional (this is compatible with CPython). + +- isinstance() +- issubclass() +- iter() +- len() +- class list +- locals() +- map() +- max() +- **class** memoryview +- min() +- next() +- **class** object +- oct() +- open() +- ord() +- pow() +- print() +- property() +- range() +- repr() +- reversed() +- round() +- class set +- setattr() +- **class** slice + - The slice builtin is the type that slice objects have. +- sorted() +- staticmethod() +- **class** str +- sum() +- super() +- **class** tuple +- type() +- zip() + +## 异常类型 +- **exception** AssertionError +- **exception** AttributeError +- **exception** Exception +- **exception** ImportError +- **exception** IndexError +- **exception** KeyboardInterrupt +- **exception** KeyError +- **exception** MemoryError +- **exception** NameError +- **exception** NotImplementedError +- **exception** OSError + - See CPython documentation: OSError. MicroPython doesn’t implement errno attribute, instead use the standard way to access exception arguments: exc.args[0]. + +- **exception** RuntimeError +- **exception** StopIteration +- **exception** SyntaxError +- **exception** SystemExit + - See CPython documentation: SystemExit. + +- **exception** TypeError + - See CPython documentation: TypeError. + +- **exception** ValueError +- **exception** ZeroDivisionError + diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/cmath.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/cmath.md new file mode 100644 index 0000000000000000000000000000000000000000..1add57e4af11ef3ea9e476c4814b52050ec4fe25 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/cmath.md @@ -0,0 +1,42 @@ +## **cmath** – 复数运算函数功能 + +`cmath` 模块提供了一些用于复数运算的函数。这个模块中的函数接受整数、浮点数或复数作为参数。他们还将接受任何有复数或浮点方法的 Python 对象:这些方法分别用于将对象转换成复数或浮点数,然后将该函数应用到转换的结果中。 + +### 函数 + +#### **cmath.cos**(z) +返回``z``的余弦。 + +#### **cmath.exp**(z) +返回``z``的指数。 + +#### **cmath.log**(z) +返回``z``的对数。 + +#### **cmath.log10**(z) +返回``z``的常用对数。 + +#### **cmath.phase**(z) +返回``z``的相位, 范围是(-pi, +pi],以弧度表示。 + +#### **cmath.polar**(z) +返回``z``的极坐标。 + +#### **cmath.rect**(r, phi) +返回`模量r`和相位``phi``的复数。 + +#### **cmath.sin**(z) +返回``z``的正弦。 + +#### **cmath.sqrt**(z) +返回``z``的平方根。 + +### 常数 + +#### **cmath.e** +自然对数的指数。 + +#### **cmath.pi** +圆周率。 + +更多内容可参考 [cmath](http://docs.micropython.org/en/latest/pyboard/library/cmath.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/gc.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/gc.md new file mode 100644 index 0000000000000000000000000000000000000000..b5147cf7be25097cdb7078b86b9419a56a9c16c7 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/gc.md @@ -0,0 +1,22 @@ +## **gc** – 控制垃圾回收 + +**gc** 模块提供了垃圾收集器的控制接口。 + +### 函数 + +#### **gc.enable**() +允许自动回收内存碎片。 + +#### **gc.disable**() +禁止自动回收,但可以通过collect()函数进行手动回收内存碎片。 + +#### **gc.collect**() +运行一次垃圾回收。 + +#### **gc.mem_alloc**() +返回已分配的内存数量。 + +#### **gc.mem_free**() +返回剩余的内存数量。 + +更多内容可参考 [gc](http://docs.micropython.org/en/latest/pyboard/library/gc.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/math.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/math.md new file mode 100644 index 0000000000000000000000000000000000000000..7de3bd809966f08f99f20bf86829dd5db47551c1 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/math.md @@ -0,0 +1,283 @@ +## **math** – 数学函数 + +**math** 模块提供了对 C 标准定义的数学函数的访问。 + +> 注意:本模块需要带有硬件 FPU,精度是32位,这个模块需要浮点功能支持。 + +### 常数 + +#### **math.e** +自然对数的底数。 + +示例: +``` +>>>import math +>>>print(math.e) +2.718282 +``` +#### **math.pi** +圆周长与直径的比值。 + +示例: + +``` +>>> print(math.pi) +3.141593 +``` + +### 函数 + +#### **math.acos(x)** +传入弧度值,计算cos(x)的反三角函数。 + +#### **math.acosh(x)** + 返回 ``x`` 的逆双曲余弦。 + +#### **math.asin(x)** +传入弧度值,计算sin(x)的反三角函数。 +示例: + +``` +>>> x = math.asin(0.5) +>>> print(x) +0.5235988 +``` + +#### **math.asinh(x)** + 返回``x`` 的逆双曲正弦。 + +#### **math.atan(x)** + 返回 ``x`` 的逆切线。 + +#### **math.atan2(y, x)** + Return the principal value of the inverse tangent of y/x. + +#### **math.atanh(x)** + Return the inverse hyperbolic tangent of x. + +#### **math.ceil(x)** +向上取整。 +示例: + +``` +>>> x = math.ceil(5.6454) +>>> print(x) +6 +``` + +#### **math.copysign(x, y)** + Return x with the sign of y. + +#### **math.cos(x)** +传入弧度值,计算余弦。 +示例:计算cos60° + +``` +>>> math.cos(math.radians(60)) +0.5 +``` + +#### **math.cosh(x)** + Return the hyperbolic cosine of x. + +#### **math.degrees(x)** +弧度转化为角度。 +示例: + +``` +>>> x = math.degrees(1.047198) +>>> print(x) +60.00002 +``` + +#### **math.erf(x)** + Return the error function of x. + +#### **math.erfc(x)** + Return the complementary error function of x. + +#### **math.exp(x)** +计算e的x次方(幂)。 +示例: + +``` +>>> x = math.exp(2) +>>> print(x) +7.389056 +``` + +#### **math.expm1(x)** +计算 math.exp(x) - 1。 + +#### **math.fabs(x)** +计算绝对值。 +示例: + +``` +>>> x = math.fabs(-5) +>>> print(x) +5.0 +>>> y = math.fabs(5.0) +>>> print(y) +5.0 +``` + +#### **math.floor(x)** +向下取整。 +示例: + +``` +>>> x = math.floor(2.99) +>>> print(x) +2 +>>> y = math.floor(-2.34) +>>> print(y) +-3 +``` + +#### **math.fmod(x, y)** +取x除以y的模。 +示例: + +``` +>>> x = math.fmod(4, 5) +>>> print(x) +4.0 +``` + +#### **math.frexp(x)** + Decomposes a floating-point number into its mantissa and exponent. The returned value is the tuple (m, e) such that x == m * 2**e exactly. If x == 0 then the function returns (0.0, 0), otherwise the relation 0.5 <= abs(m) < 1 holds. + +#### **math.gamma(x)** +返回伽马函数。 +示例: + +``` +>>> x = math.gamma(5.21) +>>> print(x) +33.08715 +``` + +#### **math.isfinite(x)** + Return True if x is finite. + +#### **math.isinf(x)** + Return True if x is infinite. + +#### **math.isnan(x)** + Return True if x is not-a-number + +#### **math.ldexp(x, exp)** + Return x * (2**exp). + +#### **math.lgamma(x)** +返回伽马函数的自然对数。 +示例: + +``` +>>> x = math.lgamma(5.21) +>>> print(x) +3.499145 +``` + +#### **math.log(x)** +计算以e为底的x的对数。 +示例: + +``` +>>> x = math.log(10) +>>> print(x) +2.302585 +``` + +#### **math.log10(x)** +计算以10为底的x的对数。 +示例: + +``` +>>> x = math.log10(10) +>>> print(x) +1.0 +``` + +#### **math.log2(x)** + 计算以2为底的x的对数。 +示例: + +``` +>>> x = math.log2(8) +>>> print(x) +3.0 +``` + +#### **math.modf(x)** + Return a tuple of two floats, being the fractional and integral parts of x. Both return values have the same sign as x. + +#### **math.pow(x, y)** +计算 x 的 y 次方(幂)。 +示例: + +``` +>>> x = math.pow(2, 3) +>>> print(x) +8.0 +``` + +#### **math.radians(x)** +角度转化为弧度。 +示例: + +``` +>>> x = math.radians(60) +>>> print(x) +1.047198 +``` + +#### **math.sin(x)** +传入弧度值,计算正弦。 +示例:计算sin90° + +``` +>>> math.sin(math.radians(90)) +1.0 +``` + +#### **math.sinh(x)** + Return the hyperbolic sine of x. + +#### **math.sqrt(x)** +计算平方根。 +示例: + +``` +>>> x = math.sqrt(9) +>>> print(x) +3.0 +``` + +#### **math.tan(x)** +传入弧度值,计算正切。 +示例:计算tan60° + +``` +>>> math.tan(math.radians(60)) +1.732051 +``` + +#### **math.tanh(x)** + Return the hyperbolic tangent of x. + +#### **math.trunc(x)** +取整。 +示例: + +``` +>>> x = math.trunc(5.12) +>>> print(x) +5 +>>> y = math.trunc(-6.8) +>>> print(y) +-6 +``` + +更多内容可参考 [math](http://docs.micropython.org/en/latest/pyboard/library/math.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/rtthread.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/rtthread.md new file mode 100644 index 0000000000000000000000000000000000000000..4591032fc8ebfd8bd24efc414d566886210a1035 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/rtthread.md @@ -0,0 +1,34 @@ +## **rtthread** – 系统相关函数 + +**rtthread** 模块提供了与 RT-Thread 操作系统相关的功能,如查看栈使用情况等。 + +### 函数 + +#### rtthread.current_tid() +返回当前线程的 id 。 + +#### rtthread.is_preempt_thread() +返回是否是可抢占线程。 + +#### rtthread.stacks_analyze() +返回当前系统线程和栈使用信息。 + +### 示例 + +``` +>>> import rtthread +>>> +>>> rtthread.is_preempt_thread() # determine if code is running in a preemptible thread +True +>>> rtthread.current_tid() # current thread id +268464956 +>>> rtthread.stacks_analyze() # show thread information +thread pri status sp stack size max used left tick error +---------- --- ------- ---------- ---------- ------ ---------- --- +elog_async 31 suspend 0x000000a8 0x00000400 26% 0x00000003 000 +tshell 20 ready 0x00000260 0x00001000 39% 0x00000003 000 +tidle 31 ready 0x00000070 0x00000100 51% 0x0000000f 000 +SysMonitor 30 suspend 0x000000a4 0x00000200 32% 0x00000005 000 +timer 4 suspend 0x00000080 0x00000200 25% 0x00000009 000 +>>> +``` diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/sys.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/sys.md new file mode 100644 index 0000000000000000000000000000000000000000..e4eb9077715b6ba87469de75c7c633c919ebecf8 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/sys.md @@ -0,0 +1,69 @@ +## **sys** – 系统特有功能函数 + +**sys** 模块提供系统特有的功能。 + +### 函数 + +#### **sys.exit**(retval=0) + 终止当前程序给定的退出代码。 函数会抛出 `SystemExit` 异常。 +#### **sys.print_exception**(exc, file=sys.stdout) + 打印异常与追踪到一个类似文件的对象 file (或者缺省 `sys.stdout` ). + +> 提示:这是 CPython 中回溯模块的简化版本。不同于 `traceback.print_exception()`,这个函数用异常值代替了异常类型、异常参数和回溯对象。文件参数在对应位置,不支持更多参数。CPython 兼容回溯模块在 `micropython-lib`。 + +### 常数 + +#### **sys.argv** + 当前程序启动时参数的可变列表。 + +#### **sys.byteorder** + 系统字节顺序 (“little” or “big”). + +#### **sys.implementation** + 关于当前 Python 实现的信息,对于 MicroPython 来说,有以下属性: + - 名称 - ‘’micropython“ + - 版本 - 元组(主要,次要,小),比如(1,9,3) + +#### **sys.modules** + 已加载模块的字典。在一部分移植中,它可能不包含内置模块。 + +#### **sys.path** + 用来搜索导入模块地址的列表。 + +#### **sys.platform** + 返回当前平台的信息。 + +#### **sys.stderr** + 标准错误流。 + +#### **sys.stdin** + 标准输入流。 + +#### **sys.stdout** + 标准输出流。 + +#### **sys.version** + 符合的 Python 语言版本,如字符串。 + +#### **sys.version_info** + 本次实现使用的 Python 语言版本,用一个元组的方式表示。 + +### 示例 + +``` +>>> import sys +>>> sys.version +'3.4.0' +>>> sys.version_info +(3, 4, 0) +>>> sys.path +['', '/libs/mpy/'] +>>> sys.__name__ +'sys' +>>> sys.platform +'rt-thread' +>>> sys.byteorder +'little' +``` + +更多内容可参考 [sys](http://docs.micropython.org/en/latest/pyboard/library/sys.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uarray.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uarray.md new file mode 100644 index 0000000000000000000000000000000000000000..e352a082a79c194e676714665f0a96885be7d03d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uarray.md @@ -0,0 +1,56 @@ +## **array** – 数字数据数组 + +**array** 模块定义了一个对象类型,它可以简洁地表示基本值的数组:字符、整数、浮点数。支持代码格式: b, B, h, H, i, I, l, L, q, Q, f, d (最后2个需要支持浮点数)。 + +### 构造函数 + +#### **class array.array**(typecode[, iterable]) +用给定类型的元素创建数组。数组的初始内容由 iterable 提供,如果没有提供,则创建一个空数组。 + +``` +typecode:数组的类型 +iterable:数组初始内容 +``` + +示例: + +```python +>>> import array +>>> a = array.array('i', [2, 4, 1, 5]) +>>> b = array.array('f') +>>> print(a) +array('i', [2, 4, 1, 5]) +>>> print(b) +array('f') +``` + +### 方法 + +#### **array.append**(val) +将一个新元素追加到数组的末尾。 + +示例: + +```python +>>> a = array.array('f', [3, 6]) +>>> print(a) +array('f', [3.0, 6.0]) +>>> a.append(7.0) +>>> print(a) +array('f', [3.0, 6.0, 7.0]) +``` + +#### **array.extend**(iterable) +将一个新的数组追加到数组的末尾,注意追加的数组和原来数组的数据类型要保持一致。 + +示例: + +```python +>>> a = array.array('i', [1, 2, 3]) +>>> b = array.array('i', [4, 5]) +>>> a.extend(b) +>>> print(a) +array('i', [1, 2, 3, 4, 5]) +``` + +更多内容可参考 [array](http://docs.micropython.org/en/latest/pyboard/library/array.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ubinascii.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ubinascii.md new file mode 100644 index 0000000000000000000000000000000000000000..17ebf299a1fdf48dbd5b5f930f6bb53499eff1f4 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ubinascii.md @@ -0,0 +1,47 @@ +## **ubinascii** – 二进制/ ASCII转换 + +`ubinascii` 模块包含许多在二进制和各种 ascii 编码的二进制表示之间转换的方法。 + +### 函数 + +#### **ubinascii.hexlify**(data[, sep]) +将字符串转换为十六进制表示的字符串。 + +示例: + +``` +>>> ubinascii.hexlify('hello RT-Thread') +b'68656c6c6f2052542d546872656164' +>>> ubinascii.hexlify('summer') +b'73756d6d6572' +``` +如果指定了第二个参数sep,它将用于分隔两个十六进制数。 + +示例: + +``` +如果指定了第二个参数sep,它将用于分隔两个十六进制数。 +示例: +>>> ubinascii.hexlify('hello RT-Thread'," ") +b'68 65 6c 6c 6f 20 52 54 2d 54 68 72 65 61 64' +>>> ubinascii.hexlify('hello RT-Thread',",") +b'68,65,6c,6c,6f,20,52,54,2d,54,68,72,65,61,64' +``` + +#### **ubinascii.unhexlify**(data) +转换十六进制字符串为二进制字符串,功能和 hexlify 相反。 + +示例: + +``` +>>> ubinascii.unhexlify('73756d6d6572') +b'summer' +``` + +#### **ubinascii.a2b_base64**(data) +Base64编码的数据转换为二进制表示。返回字节串。 + +#### **ubinascii.b2a_base64**(data) +编码base64格式的二进制数据。返回的字符串。 + +更多内容可参考 [ubinascii](http://docs.micropython.org/en/latest/pyboard/library/ubinascii.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ucollections.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ucollections.md new file mode 100644 index 0000000000000000000000000000000000000000..7e68dbf6ff7b8a3f6103ef20839c788f652cb02b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ucollections.md @@ -0,0 +1,44 @@ +## **ucollections** – 集合与容器类型 + +**ucollections** 模块实现了专门的容器数据类型,它提供了 Python 的通用内置容器的替代方案,包括了字典、列表、集合和元组。 + +### 类 + +#### **ucollections.namedtuple**(name, fields) +这是工厂函数创建一个新的 `namedtuple` 型与一个特定的字段名称和集合。`namedtuple` 是元组允许子类要访问它的字段不仅是数字索引,而且还具有属性使用符号字段名访问语法。 字段是字符串序列指定字段名称。为了兼容的实现也可以用空间分隔的字符串命名的字段(但效率较低) 。 + +代码示例: +```python +from ucollections import namedtuple + +MyTuple = namedtuple("MyTuple", ("id", "name")) +t1 = MyTuple(1, "foo") +t2 = MyTuple(2, "bar") +print(t1.name) +assert t2.name == t2[1] +ucollections.OrderedDict(...) +``` + +#### **ucollections.OrderedDict**(...) +字典类型的子类,会记住并保留键/值的追加顺序。当有序的字典被迭代输出时,键/值 会按照他们被添加的顺序返回 : + +```python +from ucollections import OrderedDict + +# To make benefit of ordered keys, OrderedDict should be initialized +# from sequence of (key, value) pairs. +d = OrderedDict([("z", 1), ("a", 2)]) +# More items can be added as usual +d["w"] = 5 +d["b"] = 3 +for k, v in d.items(): + print(k, v) +``` +输出: + +z 1 +a 2 +w 5 +b 3 + +更多的内容可参考 [ucollections](http://docs.micropython.org/en/latest/pyboard/library/ucollections.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uctypes.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uctypes.md new file mode 100644 index 0000000000000000000000000000000000000000..810c0843e47fdbaf32b95ab06111134047626159 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uctypes.md @@ -0,0 +1,82 @@ +## **uctypes** – 以结构化的方式访问二进制数据 + +uctypes 模块用来访问二进制数据结构,它提供 C 兼容的数据类型。 + +### 常量 +- uctypes.LITTLE_ENDIAN — 小端压缩结构。 +- uctypes.BIG_ENDIAN — 大端压缩结构类型。 +- NATIVE — mricopython 本地的存储类型 + + +### 构造函数 + +#### class uctypes.struct(addr, descriptor, type) +将内存中以 c 形式打包的结构体或联合体转换为字典,并返回该字典。 +``` +addr:开始转换的地址 +descriptor:转换描述符 +格式:"field_name":offset|uctypes.UINT32 +offset:偏移量, +单位:字节、VOID、UINT8、INT8、UINT16、INT16、UINT32、INT32、UINT64、INT64、BFUINT8、BFINT8、BFUINT16、BFINT16、BFUINT32、BFINT32、BF_POS、BF_LEN、FLOAT32、FLOAT64、PTR、ARRAY +type:c 结构体或联合体存储类型,默认为本地存储类型 +``` + +示例: + +```python +>>> a = b"0123" +>>> s = uctypes.struct(uctypes.addressof(a), {"a": uctypes.UINT8 | 0, "b": uctypes.UINT16 | 1}, uctypes.LITTLE_ENDIAN) +>>> print(s) + +>>> print(s.a) +48 +>>> s.a = 49 +>>> print(a) +b'1123' +``` + +### 方法 + +#### **uctypes.sizeof**(struct) +按字节返回数据的大小。参数可以是类或者数据对象 (或集合)。 +示例: +```python +>>> a = b"0123" +>>>b = uctypes.struct(uctypes.addressof(a), {"a": uctypes.UINT8 | 0, "b": uctypes.UINT16 | 1}, uctypes.LITTLE_ENDIAN) +>>> b.a +48 +>>> print(uctypes.sizeof(b)) +3 +``` + +#### **uctypes.addressof**(obj) +返回对象地址。参数需要是 bytes, bytearray 。 +示例: + +```python +>>> a = b"0123" +>>> print(uctypes.addressof(a)) +1073504048 +``` + +#### **uctypes.bytes_at**(addr, size) +捕捉从 addr 开始到 size 个地址偏移量结束的内存数据为 bytearray 对象并返回。 +示例: + +```python +>>> a = b"0123" +>>>print( uctypes.bytes_at(uctypes.addressof(a), 4)) +b'0123' +``` + +#### **uctypes.bytearray_at**(addr, size) +捕捉给定大小和地址内存为 bytearray 对象。与 bytes_at() 函数不同的是,它可以被再次写入,可以访问给定地址的参数。 +示例: + +```python +>>> a = b"0123" +>>> print(uctypes.bytearray_at(uctypes.addressof(a), 2)) +bytearray(b'01') +``` + +更多内容可参考 [uctypes](http://docs.micropython.org/en/latest/pyboard/library/uctypes.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uerrno.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uerrno.md new file mode 100644 index 0000000000000000000000000000000000000000..07872df3fb13fcd75837b1dc3f22a6b739b84ad5 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uerrno.md @@ -0,0 +1,20 @@ +## **uerrno** – 系统错误码模块 + +`uerrno` 模块提供了标准的 errno 系统符号,每个符号都有对应的整数值。 + +### 示例 + +```python +try: + uos.mkdir("my_dir") +except OSError as exc: + if exc.args[0] == uerrno.EEXIST: + print("Directory already exists") +uerrno.errorcode +Dictionary mapping numeric error codes to strings with symbolic error code (see above): + +>>> print(uerrno.errorcode[uerrno.EEXIST]) +EEXIST +``` + +更多内容可参考 [uerrno](http://docs.micropython.org/en/latest/pyboard/library/uerrno.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uhashlib.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uhashlib.md new file mode 100644 index 0000000000000000000000000000000000000000..93f359f3210b8a28bf8ef0dbc3da7ab117f0729d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uhashlib.md @@ -0,0 +1,36 @@ +## **uhashlib** – 哈希算法 + +`uhashlib` 模块实现了二进制数据哈希算法。 + +### 算法功能 + +#### **SHA256** +当代的散列算法(SHA2系列),它适用于密码安全的目的。被包含在 MicroPython 内核中,除非有特定的代码大小限制,否则推荐任何开发板都支持这个功能。 + +#### **SHA1** +上一代的算法,不推荐新的应用使用这种算法,但是 SHA1 算法是互联网标准和现有应用程序的一部分,所以针对网络连接便利的开发板会提供这种功能。 + +#### **MD5** +一种遗留下来的算法,作为密码使用被认为是不安全的。只有特定的开发板,为了兼容老的应用才会提供这种算法。 + +### 函数 + +#### **class uhashlib.sha256**([data]) +创建一个SHA256哈希对象并提供 data 赋值。 + +#### **class uhashlib.sha1**([data]) +创建一个SHA1哈希对象并提供 data 赋值。 + +#### **class uhashlib.md5**([data]) +创建一个MD5哈希对象并提供 data 赋值。 + +#### **hash.update**(data) +将更多二进制数据放入哈希表中。 + +#### **hash.digest**() +返回字节对象哈希的所有数据。调用此方法后,将无法将更多数据送入哈希。 + +#### **hash.hexdigest**() +此方法没有实现, 使用 ubinascii.hexlify(hash.digest()) 达到类似效果。 + +更多内容可参考 [uhashlib](http://docs.micropython.org/en/latest/pyboard/library/uhashlib.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uheapq.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uheapq.md new file mode 100644 index 0000000000000000000000000000000000000000..be7dfdad9a1b64ff2a0d0fcf97110052ec955041 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uheapq.md @@ -0,0 +1,16 @@ +## **uheapq** – 堆排序算法 + +`uheapq` 模块提供了堆排序相关算法,堆队列是一个列表,它的元素以特定的方式存储。 + +### 函数 + +#### **uheapq.heappush**(heap, item) +将对象压入堆中。 + +#### **uheapq.heappop**(heap) +从 heap 弹出第一个元素并返回。 如果是堆时空的会抛出 IndexError。 + +#### **uheapq.heapify**(x) +将列表 x 转换成堆。 + +更多内容可参考 [uheapq](http://docs.micropython.org/en/latest/pyboard/library/uheapq.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uio.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uio.md new file mode 100644 index 0000000000000000000000000000000000000000..6f5476e208d44f8e147ada33bff8a6427cbb2e79 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uio.md @@ -0,0 +1,27 @@ +## **uio** – 输入/输出流 + +**uio** 模块包含流类型 (类似文件) 对象和帮助函数。 + +### 函数 + +#### **uio.open**(name, mode='r', \*\*kwargs) + +打开一个文件,关联到内建函数``open()``。所有端口 (用于访问文件系统) 需要支持模式参数,但支持其他参数不同的端口。 + +### 类 + +#### **class uio.FileIO**(...) + 这个文件类型用二进制方式打开文件,等于使用``open(name, “rb”)``。 不应直接使用这个实例。 + +#### **class uio.TextIOWrapper**(...) + 这个类型以文本方式打开文件,等同于使用``open(name, “rt”)``不应直接使用这个实例。 + +#### **class uio.StringIO**([string]) + +#### **class uio.BytesIO**([string]) + 内存文件对象。`StringIO` 用于文本模式 I/O (用 “t” 打开文件),`BytesIO` 用于二进制方式 (用 “b” 方式)。文件对象的初始内容可以用字符串参数指定(`stringio`用普通字符串,`bytesio`用`bytes`对象)。所有的文件方法,如 `read(), write(), seek(), flush(), close()` 都可以用在这些对象上,包括下面方法: + +#### **getvalue**() + 获取缓存区内容。 + +更多内容可参考 [uio](http://docs.micropython.org/en/latest/pyboard/library/uio.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ujson.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ujson.md new file mode 100644 index 0000000000000000000000000000000000000000..fd8ecb8f726e2dba7087a98e45b8a921e2b0889d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ujson.md @@ -0,0 +1,42 @@ +## **ujson** – JSON编码与解码 + +`ujson` 模块提供 Python 对象到 JSON(JavaScript Object Notation) 数据格式的转换。 + +### 函数 + +#### **ujson.dumps**(obj) + +将 dict 类型转换成 str。 + +``` +obj:要转换的对象 +``` + +示例: + +``` +>>> obj = {1:2, 3:4, "a":6} +>>> print(type(obj), obj) #原来为dict类型 + {3: 4, 1: 2, 'a': 6} +>>> jsObj = json.dumps(obj) #将dict类型转换成str +>>> print(type(jsObj), jsObj) + {3: 4, 1: 2, "a": 6} +``` + +#### **ujson.loads**(str) +解析 JSON 字符串并返回对象。如果字符串格式错误将引发 ValueError 异常。 +示例: + +``` +>>> obj = {1:2, 3:4, "a":6} +>>> jsDumps = json.dumps(obj) +>>> jsLoads = json.loads(jsDumps) +>>> print(type(obj), obj) + {3: 4, 1: 2, 'a': 6} +>>> print(type(jsDumps), jsDumps) + {3: 4, 1: 2, "a": 6} +>>> print(type(jsLoads), jsLoads) + {'a': 6, 1: 2, 3: 4} +``` + +更多内容可参考 [ujson](http://docs.micropython.org/en/latest/pyboard/library/ujson.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uos.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uos.md new file mode 100644 index 0000000000000000000000000000000000000000..73f270d5bc8195b0336d9e6ca6f70051b913286f --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uos.md @@ -0,0 +1,56 @@ +## **uos** – 基本的操作系统服务 + +`uos` 模块包含了对文件系统的访问操作,是对应 CPython 模块的一个子集。 + +### 函数 + +#### **uos.chdir**(path) +更改当前目录。 + +#### **uos.getcwd**() +获取当前目录。 + +#### **uos.listdir**([dir]) +没有参数就列出当前目录,否则列出给定目录。 + +#### **uos.mkdir**(path) +创建一个目录。 + +#### **uos.remove**(path) +删除文件。 + +#### **uos.rmdir**(path) +删除目录。 + +#### **uos.rename**(old_path, new_path) +重命名文件或者文件夹。 + +#### **uos.stat**(path) +获取文件或目录的状态。 + +#### **uos.sync**() +同步所有的文件系统。 + +### 示例 + +``` +>>> import uos +>>> uos. # Tab +__name__ uname chdir getcwd +listdir mkdir remove rmdir +stat unlink mount umount +>>> uos.mkdir("rtthread") +>>> uos.getcwd() +'/' +>>> uos.chdir("rtthread") +>>> uos.getcwd() +'/rtthread' +>>> uos.listdir() +['web_root', 'rtthread', '11'] +>>> uos.rmdir("11") +>>> uos.listdir() +['web_root', 'rtthread'] +>>> +``` + +更多内容可参考 [uos](http://docs.micropython.org/en/latest/pyboard/library/uos.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/urandom.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/urandom.md new file mode 100644 index 0000000000000000000000000000000000000000..794b56a36ab7701739402384e5068dd2e3faf13b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/urandom.md @@ -0,0 +1,174 @@ +## **urandom** - 随机数生成模块 + +`urandom` 模块实现了伪随机数生成器。 + +### 函数 + +#### **urandom.choice**(obj) + +随机生成对象 obj 中的元数。 + +``` +obj:元数列表 +``` + +示例: + +```python +>>> print(random.choice("DFRobot")) +R +>>> print(random.choice("DFRobot")) +D +>>> print(random.choice([0, 2, 4, 3])) +3 +>>> print(random.choice([0, 2, 4, 3])) +3 +>>> print(random.choice([0, 2, 4, 3])) +2 +``` + +#### **urandom.getrandbits**(size) + +随机生成 0 到 size 个位二进制数范围内的正整数。 比如 : + +- size = 4,那么便是从 0 到0b1111中随机一个正整数。 +- size = 8,那么便是从 0 到 0b11111111中随机一个正整数。 + +```python +size:位大小 +``` + +示例: + +```python +>>> print( random.getrandbits(1)) #1位二进制位,范围为0~1(十进制:0~1) +1 +>>> print(random.getrandbits(1)) +0 +>>> print(random.getrandbits(8)) #8位二进制位,范围为0000 0000~1111 11111(十进制:0~255) +224 +>>> print(random.getrandbits(8)) +155 +``` + +#### **urandom.randint**(start, end) + +随机生成一个 start 到 end 之间的整数。 + +``` +start:指定范围内的开始值,包含在范围内 +end:指定范围内的结束值,包含在范围内 +``` + +示例: + +```python +>>> import random +>>> print(random.randint(1, 4)) +4 +>>> print(random.randint(1, 4)) +2 +``` + +#### **urandom.random**() +随机生成一个 0 到 1 之间的浮点数。 +示例: + +```python +>>> print(random.random()) +0.7111824 +>>> print(random.random()) +0.3168149 +``` + +#### **urandom.randrange**(start, end, step) + +随机生成 start 到 end 并且递增为 step 的范围内的正整数。例如,randrange(0, 8, 2)中,随机生成的数为 0、2、4、6 中任一个。 + +``` +start:指定范围内的开始值,包含在范围内 +end:指定范围内的结束值,包含在范围内 +step:递增基数 +``` + +示例: + +```python +>>> print(random.randrange(2, 8, 2)) +4 +>>> print(random.randrange(2, 8, 2)) +6 +>>> print(random.randrange(2, 8, 2)) +2 +``` + +#### **urandom.seed**(sed) + +指定随机数种子,通常和其他随机数生成函数搭配使用。 +**注意:** + MicroPython 中的随机数其实是一个稳定算法得出的稳定结果序列,而不是一个随机序列。sed 就是这个算法开始计算的第一个值。所以就会出现只要 sed 是一样的,那么后续所有“随机”结果和顺序也都完全一致。 + +``` +sed:随机数种子 +``` + +示例: + +```python +import random + +for j in range(0, 2): + random.seed(13) #指定随机数种子 + for i in range(0, 10): #生成0到10范围内的随机序列 + print(random.randint(1, 10)) + print("end") +``` + +运行结果: + +``` +5 +2 +3 +2 +3 +4 +2 +5 +8 +2 +end +5 +2 +3 +2 +3 +4 +2 +5 +8 +2 +end +``` + + 从上面可以看到生成两个随机数列表是一样的,你也可以多生成几个随机数列表查看结果。 + +#### **urandom.uniform**(start, end) + +随机生成start到end之间的浮点数。 + +``` +start:指定范围内的开始值,包含在范围内 +stop:指定范围内的结束值,包含在范围内 +``` + +示例: + +```python +>>> print(random.uniform(2, 4)) +2.021441 +>>> print(random.uniform(2, 4)) +3.998012 +``` + +更多内容可参考 [urandom](https://docs.python.org/3/library/random.html?highlight=random#module-random) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ure.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ure.md new file mode 100644 index 0000000000000000000000000000000000000000..7c8805f6fbcf9ebf581903bb1ebe7ce1fe2d08b7 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ure.md @@ -0,0 +1,52 @@ +## **ure** – 正则表达式 + +`ure` 模块用于测试字符串的某个模式,执行正则表达式操作。 + +### 匹配字符集 + + +#### 匹配任意字符 + ``'.'`` + +#### 匹配字符集合,支持单个字符和一个范围 + ``'[]'`` + +#### 支持多种匹配元字符 + ``'^'`` + ``'$'`` + ``'?'`` + ``'*'`` + ``'+'`` + ``'??'`` + ``'*?'`` + ``'+?'`` + ``'{m,n}'`` + +### 函数 + +#### **ure.compile**(regex) +编译正则表达式,返回 regex 对象。 + +#### **ure.match**(regex, string) +用 string 匹配 regex,匹配总是从字符串的开始匹配。 + +#### **ure.search**(regex, string) +在 string 中搜索 regex。不同于匹配,它搜索第一个匹配位置的正则表达式字符串 (结果可能会是0)。 + +#### **ure.DEBUG** +标志值,显示表达式的调试信息。 + +### **正则表达式对象**: +编译正则表达式,使用 `ure.compile()` 创建实例。 + +#### **regex.match**(string) +#### **regex.search**(string) +#### **regex.split**(string, max_split=-1) + +### **匹配对象** : +匹配对象是 match() 和 search() 方法的返回值。 + +#### **match.group**([index]) +只支持数字组。 + +更多内容可参考 [ure](http://docs.micropython.org/en/latest/pyboard/library/ure.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uselect.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uselect.md new file mode 100644 index 0000000000000000000000000000000000000000..0743c5c5d5fba088cfcb471dd532dbafa20cd18e --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uselect.md @@ -0,0 +1,102 @@ +## **uselect** – 等待流事件 + +`uselect` 模块提供了等待数据流的事件功能。 + +### 常数 + +#### **select.POLLIN** - 读取可用数据 + +#### **select.POLLOUT** - 写入更多数据 + +#### **select.POLLERR** - 发生错误 + +#### **select.POLLHUP** - 流结束/连接终止检测 + +### 函数 + +#### **select.select**(rlist, wlist, xlist[, timeout]) +监控对象何时可读或可写,一旦监控的对象状态改变,返回结果(阻塞线程)。这个函数是为了兼容,效率不高,推荐用 `poll` 函数 。 + +``` +rlist:等待读就绪的文件描述符数组 +wlist:等待写就绪的文件描述符数组 +xlist:等待异常的数组 +timeout:等待时间(单位:秒) +``` +示例: + +```python +def selectTest(): + global s + rs, ws, es = select.select([s,], [], []) + #程序会在此等待直到对象s可读 + print(rs) + for i in rs: + if i == s: + print("s can read now") + data,addr=s.recvfrom(1024) + print('received:',data,'from',addr) +``` + +### Poll 类 + +#### **select.poll**() +创建 poll 实例。 + +示例: + +``` +>>>poller = select.poll() +>>>print(poller) + +``` + +#### **poll.register**(obj[, eventmask]) +注册一个用以监控的对象,并设置被监控对象的监控标志位 flag。 + +``` +obj:被监控的对象 +flag:被监控的标志 + select.POLLIN — 可读 + select.POLLHUP — 已挂断 + select.POLLERR — 出错 + select.POLLOUT — 可写 +``` + +#### **poll.unregister**(obj) +解除监控的对象的注册。 + +``` +obj:注册过的对象 +``` + +示例: + +``` +>>>READ_ONLY = select.POLLIN | select.POLLHUP | select.POLLERR +>>>READ_WRITE = select.POLLOUT | READ_ONLY +>>>poller.register(s, READ_WRITE) +>>>poller.unregister(s) +``` + +#### **poll.modify**(obj, eventmask) +修改已注册的对象监控标志。 + +``` +obj:已注册的被监控对象 +flag:修改为的监控标志 +``` + +示例: + +``` +>>>READ_ONLY = select.POLLIN | select.POLLHUP | select.POLLERR +>>>READ_WRITE = select.POLLOUT | READ_ONLY +>>>poller.register(s, READ_WRITE) +>>>poller.modify(s, READ_ONLY) +``` + +#### **poll.poll**([timeout]) +等待至少一个已注册的对象准备就绪。返回 (obj, event, ...) 元组, event 元素指定了一个流发生的事件,是上面所描述的 `select.POLL*`常量组合。 根据平台和版本的不同,在元组中可能有其他元素,所以不要假定元组的大小是 2 。如果超时,则返回空列表。 + +更多内容可参考 [uselect](http://docs.micropython.org/en/latest/pyboard/library/uselect.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/usocket.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/usocket.md new file mode 100644 index 0000000000000000000000000000000000000000..0413e449c4a8ccc937a44a6d4a7d9a1ff0f44ddc --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/usocket.md @@ -0,0 +1,195 @@ +## **usocket** – 套接字模块 + +`usocket` 模块提供对BSD套接字接口的访问。 + +### 常数 + +#### 地址簇 +- socket.AF_INET =2 — TCP/IP – IPv4 +- socket.AF_INET6 =10 — TCP/IP – IPv6 + +#### 套接字类型 +- socket.SOCK_STREAM =1 — TCP流 +- socket.SOCK_DGRAM =2 — UDP数据报 +- socket.SOCK_RAW =3 — 原始套接字 +- socket.SO_REUSEADDR =4 — socket可重用 + +#### IP协议号 +- socket.IPPROTO_TCP =16 +- socket.IPPROTO_UDP =17 + +#### 套接字选项级别 +- socket.SOL_SOCKET =4095 + +### 函数 + +#### **socket.socket** + +`socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)` + +创建新的套接字,使用指定的地址、类型和协议号。 + +#### **socket.getaddrinfo**(host, port) +将主机域名(host)和端口(port)转换为用于创建套接字的5元组序列。元组列表的结构如下: + +``` +(family, type, proto, canonname, sockaddr) +``` + +示例: + +``` +>>> info = socket.getaddrinfo("rt-thread.org", 10000) +>>> print(info) +[(2, 1, 0, '', ('118.31.15.152', 10000))] +``` + +#### **socket.close**() +关闭套接字。一旦关闭后,套接字所有的功能都将失效。远端将接收不到任何数据 (清理队列数据后)。 虽然在垃圾回收时套接字会自动关闭,但还是推荐在必要时用 close() 去关闭。 + +#### **socket.bind**(address) +将套接字绑定到地址,套接字不能是已经绑定的。 + +#### **socket.listen**([backlog]) +监听套接字,使服务器能够接收连接。 +``` +backlog:接受套接字的最大个数,至少为0,如果没有指定,则默认一个合理值。 +``` + +#### **socket.accept**() +接收连接请求。 +**注意:** + 只能在绑定地址端口号和监听后调用,返回 conn 和 address。 + +``` +conn:新的套接字对象,可以用来收发消息 +address:连接到服务器的客户端地址 +``` + +#### **socket.connect**(address) +连接服务器。 + +``` +address:服务器地址和端口号的元组或列表 +``` + +#### **socket.send**(bytes) +发送数据,并返回成功发送的字节数,返回字节数可能比发送的数据长度少。 + +``` +bytes:bytes类型数据 +``` + +#### **socket.recv**(bufsize) +接收数据,返回接收到的数据对象。 + +``` +bufsize:指定一次接收的最大数据量 +``` + +示例: + +``` +data = conn.recv(1024) +``` + +#### **socket.sendto**(bytes, address) +发送数据,目标由address决定,常用于UDP通信,返回发送的数据大小。 + +``` +bytes:bytes类型数据 +address:目标地址和端口号的元组 +``` + +示例: + +``` +data = sendto("hello RT-Thread", ("192.168.10.110", 100)) +``` + +#### **socket.recvfrom**(bufsize) +接收数据,常用于UDP通信,并返回接收到的数据对象和对象的地址。 + +``` +bufsize:指定一次接收的最大数据量 +``` + +示例: + +``` +data,addr=fd.recvfrom(1024) +``` + +#### **socket.setsockopt**(level, optname, value) +根据选项值设置套接字。 + +``` +level:套接字选项级别 +optname:套接字的选项 +value:可以是一个整数,也可以是一个表示缓冲区的bytes类对象。 +``` + +示例: + +``` +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +``` + +#### **socket.settimeout**(value) +设置超时时间,单位:秒。 +示例: + +``` +s.settimeout(2) +``` + +#### **socket.setblocking**(flag) +设置阻塞或非阻塞模式: 如果 flag 是 false,设置非阻塞模式。 + +#### **socket.read**([size]) +Read up to size bytes from the socket. Return a bytes object. If size is not given, it reads all data available from the socket until EOF; as such the method will not return until the socket is closed. This function tries to read as much data as requested (no “short reads”). This may be not possible with non-blocking socket though, and then less data will be returned. + +#### **socket.readinto**(buf[, nbytes]) +Read bytes into the buf. If nbytes is specified then read at most that many bytes. Otherwise, read at most len(buf) bytes. Just as read(), this method follows “no short reads” policy. +Return value: number of bytes read and stored into buf. + +#### **socket.readline**() +接收一行数据,遇换行符结束,并返回接收数据的对象 。 + +#### **socket.write**(buf) +将字节类型数据写入套接字,并返回写入成功的数据大小。 + +### 示例 + +#### TCP Server example + +``` +>>> import usocket +>>> s = usocket.socket(usocket.AF_INET,usocket.SOCK_STREAM) # Create STREAM TCP socket +>>> s.bind(('192.168.12.32', 6001)) +>>> s.listen(5) +>>> s.setblocking(True) +>>> sock,addr=s.accept() +>>> sock.recv(10) +b'rt-thread\r' +>>> s.close() +``` + +#### TCP Client example + +``` +>>> import usocket +>>> s = usocket.socket(usocket.AF_INET,usocket.SOCK_STREAM) +>>> s.connect(("192.168.10.110",6000)) +>>> s.send("micropython") +11 +>>> s.close() +``` + +`connect to a web site example`: +``` +s = socket.socket() +s.connect(socket.getaddrinfo('www.micropython.org', 80)[0][-1]) +``` + +更多的内容可参考 [usocket](http://docs.micropython.org/en/latest/pyboard/library/usocket.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ussl.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ussl.md new file mode 100644 index 0000000000000000000000000000000000000000..6351659e7e2c155bd5544ae79956a0d55310e16e --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ussl.md @@ -0,0 +1,28 @@ +# ussl – SSL/TLS 模块 + +This module implements a subset of the corresponding* [`CPython`](http://docs.micropython.org/en/latest/reference/glossary.html#term-cpython) *module, as described below. For more information, refer to the original CPython documentation: [`ssl`](https://docs.python.org/3.5/library/ssl.html#module-ssl). + +This module provides access to Transport Layer Security (previously and widely known as “Secure Sockets Layer”) encryption and peer authentication facilities for network sockets, both client-side and server-side. + +## 功能函数 + +- `ussl.wrap_socket`(sock, server_side=False, keyfile=None, certfile=None, cert_reqs=CERT_NONE, ca_certs=None) + +Takes a [`stream`](http://docs.micropython.org/en/latest/reference/glossary.html#term-stream) *sock* (usually usocket.socket instance of `SOCK_STREAM` type), and returns an instance of ssl.SSLSocket, which wraps the underlying stream in an SSL context. Returned object has the usual [`stream`](http://docs.micropython.org/en/latest/reference/glossary.html#term-stream) interface methods like `read()`, `write()`, etc. In MicroPython, the returned object does not expose socket interface and methods like `recv()`, `send()`. In particular, a server-side SSL socket should be created from a normal socket returned from[`accept()`](http://docs.micropython.org/en/latest/library/usocket.html#usocket.socket.accept) on a non-SSL listening server socket. Depending on the underlying module implementation in a particular [`MicroPython port`](http://docs.micropython.org/en/latest/reference/glossary.html#term-micropython-port), some or all keyword arguments above may be not supported. + +Warning: Some implementations of `ussl` module do NOT validate server certificates, which makes an SSL connection established prone to man-in-the-middle attacks. + +## 异常类型 + +- `ssl.SSLError` + + This exception does NOT exist. Instead its base class, OSError, is used. + +## 常量 + +- `ussl.CERT_NONE` + +- `ussl.CERT_OPTIONAL` + +- `ussl.CERT_REQUIRED` +- Supported values for **cert_reqs** parameter. \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ustruct.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ustruct.md new file mode 100644 index 0000000000000000000000000000000000000000..18cff7209c9592f7670dbe02697767fcb539cebb --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/ustruct.md @@ -0,0 +1,82 @@ +## **ustruct** – 打包和解包原始数据类型 + +**ustruct** 模块在 Python 值和以 Python 字节对象表示的 C 结构之间执行转换。 + +- 支持 size/byte 的前缀: @, <, >, !. +- 支持的格式代码: b, B, h, H, i, I, l, L, q, Q, s, P, f, d (最后2个需要支持浮点数). + +### 函数 + +#### **ustruct.calcsize**(fmt) +返回存放某一类型数据 fmt 需要的字节数。 + +``` +fmt:数据类型 + b — 字节型 + B — 无符号字节型 + h — 短整型 + H — 无符号短整型 + i — 整型 + I — 无符号整型 + l — 整型 + L — 无符号整型 + q — 长整型 + Q — 无符号长整型 + f — 浮点型 + d — 双精度浮点型 + P — 无符号型 +``` + +示例: + +```python +>>> print(struct.calcsize("i")) +4 +>>> print(struct.calcsize("B")) +1 +``` + +#### **ustruct.pack**(fmt, v1, v2, ...) +按照格式字符串 fmt 打包参数 v1, v2, ... 。返回值是参数打包后的字节对象。 + +``` +fmt:同上 +``` + +示例: + +```python +>>> struct.pack("ii", 3, 2) +b'\x03\x00\x00\x00\x02\x00\x00\x00' +``` + +#### **ustruct.unpack**(fmt, data) +从 fmt 中解包数据。返回值是解包后参数的元组。 + +``` +data:要解压的字节对象 +``` + +示例: + +```python +>>> buf = struct.pack("bb", 1, 2) +>>> print(buf) +b'\x01\x02' +>>> print(struct.unpack("bb", buf)) +(1, 2) +``` + +#### **ustruct.pack_into**(fmt, buffer, offset, v1, v2, ...) +按照格式字符串 fmt 压缩参数 v1, v2, ... 到缓冲区 buffer,开始位置是 offset。当offset 为负数时,从缓冲区末尾开始计数。 + +#### **ustruct.unpack_from**(fmt, data, offset=0) +以 fmt 作为规则从 data 的 offset 位置开始解包数据,如果 offset 是负数就是从缓冲区末尾开始计算。返回值是解包后的参数元组。 +```python +>>> buf = struct.pack("bb", 1, 2) +>>> print(struct.unpack("bb", buf)) +(1, 2) +>>> print(struct.unpack_from("b", buf, 1)) +(2,) +``` +更多的内容可参考 [ustruct](http://docs.micropython.org/en/latest/pyboard/library/ustruct.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/utime.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/utime.md new file mode 100644 index 0000000000000000000000000000000000000000..38ec6c5182b5b642d17467aa585e54f0ef23d5d7 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/utime.md @@ -0,0 +1,121 @@ +## utime – 时间相关函数 + +utime 模块提供获取当前时间和日期、测量时间间隔和延迟的功能。 + +初始时刻: `Unix` 使用 `POSIX` 系统标准,从 1970-01-01 00:00:00 `UTC` 开始。 +嵌入式程序从 2000-01-01 00:00:00 `UTC` 开始。 + +保持实际日历日期/时间:需要一个实时时钟 `(RTC)`。在底层系统 (包括一些 `RTOS` 中),`RTC` 已经包含在其中。设置时间是通过 `OS/RTOS` 而不是 MicroPython 完成,查询日期/时间也需要通过系统 `API`。对于裸板系统时钟依赖于 ``machine.RTC()`` 对象。设置时间通过 ``machine.RTC().datetime(tuple)`` 函数,并通过下面方式维持: + +* 后备电池 (可能是选件、扩展板等)。 +* 使用网络时间协议 (需要用户设置)。 +* 每次上电时手工设置 (大部分只是在硬复位时需要设置,少部分每次复位都需要设置)。 + +如果实际时间不是通过系统 / MicroPython RTC 维持,那么下面函数结果可能不是和预期的相同。 + +### 函数 + +#### utime.localtime([secs]) + + 从初始时间的秒转换为元组: (年, 月, 日, 时, 分, 秒, 星期, `yearday`) 。如果 `secs` 是空或者 `None`,那么使用当前时间。 + +* `year` 年份包括世纪(例如2014) +* `month` 范围 1-12 +* `day` 范围 1-31 +* `hour` 范围 0-23 +* `minute` 范围 0-59 +* `second` 范围 0-59 +* `weekday` 范围 0-6 对应周一到周日 +* `yearday` 范围 1-366 + +#### utime.mktime() + + 时间的反函数,它的参数是完整8参数的元组,返回值一个整数自2000年1月1日以来的秒数。 + +#### utime.sleep(seconds) + + 休眠指定的时间(秒),``Seconds`` 可以是浮点数。注意有些版本的 MicroPython不支持浮点数,为了兼容可以使用 ``sleep_ms()`` 和 ``sleep_us()``函数。 + +#### utime.sleep_ms(ms) + + 延时指定毫秒,参数不能小于0。 + +#### utime.sleep_us(us) + + 延时指定微秒,参数不能小于0。 + +#### utime.ticks_ms() + + 返回不断递增的毫秒计数器,在某些值后会重新计数(未指定)。计数值本身无特定意义,只适合用在``ticks_diff()``。 + 注: 直接在这些值上执行标准数学运算(+,-)或关系运算符(<,>,>,> =)会导致无效结果。执行数学运算然后传递结果作为参数给`ticks_diff()` 或 ` ticks_add() ` 也将导致函数产生无效结果。 + +#### utime.ticks_us() + + 和上面 `ticks_ms()` 类似,只是返回微秒。 + +#### utime.ticks_cpu() + + 与 ``ticks_ms()`` 和 ``ticks_us()`` 类似,具有更高精度 (使用 CPU 时钟),并非每个端口都实现此功能。 + +#### utime.ticks_add(ticks, delta) + + 给定一个数字作为节拍的偏移值 `delta`,这个数字的值是正数或者负数都可以。 + 给定一个 `ticks` 节拍值,本函数允许根据节拍值的模算数定义来计算给定节拍值之前或者之后 `delta` 个节拍的节拍值 。 + `ticks` 参数必须是 `ticks_ms()`, `ticks_us()`, or `ticks_cpu()` 函数的直接返回值。然而,`delta` 可以是一个任意整数或者是数字表达式。`ticks_add` 函数对计算事件/任务的截至时间很有用。(注意:必须使用 `ticksdiff()` 函数来处理 +最后期限)。 + +代码示例: + +```python +## 查找 100ms 之前的节拍值 +print(utime.ticks_add(utime.ticks_ms(), -100)) + +## 计算操作的截止时间然后进行测试 +deadline = utime.ticks_add(utime.ticks_ms(), 200) +while utime.ticks_diff(deadline, utime.ticks_ms()) > 0: + do_a_little_of_something() + +## 找出本次移植节拍值的最大值 +print(utime.ticks_add(0, -1)) +``` + +#### utime.ticks_diff(ticks1, ticks2) + + 计算两次调用 `ticksms()`, `ticks_us()`, 或 `ticks_cpu()`之间的时间。因为这些函数的计数值可能会回绕,所以不能直接相减,需要使用 `ticks_diff()` 函数。“旧” 时间需要在 “新” 时间之前,否则结果无法确定。这个函数不要用在计算很长的时间 (因为 `ticks*()` 函数会回绕,通常周期不是很长)。通常用法是在带超时的轮询事件中调用: + +代码示例: + +```python +## 等待 GPIO 引脚有效,但是最多等待500微秒 +start = time.ticks_us() +while pin.value() == 0: + if time.ticks_diff(time.ticks_us(), start) > 500: + raise TimeoutError +``` + +#### utime.time() + + 返回从开始时间的秒数(整数),假设 `RTC` 已经按照前面方法设置好。如果 `RTC` 没有设置,函数将返回参考点开始计算的秒数 (对于 `RTC` 没有后备电池的板子,上电或复位后的情况)。如果你开发便携版的 MicroPython 应用程序,你不要依赖函数来提供超过秒级的精度。如果需要高精度,使用 `ticks_ms()` 和 `ticks_us()` 函数。如果需要日历时间,使用不带参数的 `localtime()` 是更好选择。 + +> [!NOTE] +> 与 `CPython` 的区别:
+> 在 `CPython` 中,这个函数用浮点数返回从 `Unix` 开始时间(1970-01-01 00:00 `UTC`)的秒数,通常是毫秒级的精度。在 MicroPython 中,只有 `Unix` 版才使用相同开始时间,如果允许浮点精度,将返回亚秒精度。嵌入式硬件通常没有用浮点数表示长时间访问和亚秒精度,所以返回值是整数。一些嵌入式系统硬件不支持 `RTC` 电池供电方式,所以返回的秒数是从最后上电、或相对某个时间、以及特定硬件时间 (如复位)。 + +### 示例 + +``` msh +>>> import utime +>>> utime.sleep(1) # sleep for 1 second +>>> utime.sleep_ms(500) # sleep for 500 milliseconds +>>> utime.sleep_us(10) # sleep for 10 microseconds +>>> start = utime.ticks_ms() # get value of millisecond counter +>>> delta = utime.ticks_diff(utime.ticks_ms(), start) # compute time difference +>>> delta +6928 +>>> print(utime.ticks_add(utime.ticks_ms(), -100)) +1140718 +>>> print(utime.ticks_add(0, -1)) +1073741823 +``` + +更多内容可参考 [utime](http://docs.micropython.org/en/latest/pyboard/library/utime.html#module-utime) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uzlib.md b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uzlib.md new file mode 100644 index 0000000000000000000000000000000000000000..4be9fe0af853294e14ae77e333795cc0ff4b5688 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/micropython-docs/std-librarys/uzlib.md @@ -0,0 +1,10 @@ +## **uzlib** – zlib 解压缩 + +`uzlib` 模块实现了使用 DEFLATE 算法解压缩二进制数据 (常用的 zlib 库和 gzip 文档)。目前不支持压缩。 + +### 函数 + +#### **uzlib.decompress**(data) +返回解压后的 bytes 数据。 + +更多内容可参考 [uzlib](http://docs.micropython.org/en/latest/pyboard/library/uzlib.html) 。 diff --git a/rt-thread-version/rt-thread-standard/packages-manual/more.md b/rt-thread-version/rt-thread-standard/packages-manual/more.md new file mode 100644 index 0000000000000000000000000000000000000000..4c39b90d3e794c9ca8c797c2f0fa7732dbc0c1b1 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/packages-manual/more.md @@ -0,0 +1,121 @@ +# 软件包分类 + +以下列出一些常用的软件包,更多软件包还请访问 [RT-Thread 软件包中心](http://packages.rt-thread.org/) 进行查看和使用。 + +## IoT + +与物联网相关的软件包,包括网络想干软件包,云接入软件包等。 + +| 软件包名称 | 备注 | +| ------------------------------------------------------------ | ----------------------------------------------------- | +| [ota_downloader](http://packages.rt-thread.org/detail.html?package=ota_downloader) | 基于RT-Thread OTA 组件的 固件下载器 | +| [onenet](http://packages.rt-thread.org/detail.html?package=onenet) | 连接中国移动 OneNet 云的软件包 | +| [webclient](http://packages.rt-thread.org/detail.html?package=webclient) | RT-Thread 官方开源的 http/https 协议客户端 | +| [webnet](http://packages.rt-thread.org/detail.html?package=webnet) | RT-Thread 官方开源的、轻量级、可定制嵌入式 Web 服务器 | +| [webTerminal](http://packages.rt-thread.org/detail.html?package=WebTerminal) | 可以在浏览器上运行的终端 | +| [rw007](http://packages.rt-thread.org/detail.html?package=rw007) | RT-Thread 的 RW007 驱动 (SPI Wi-Fi 模式) | +| [pahomqtt](http://packages.rt-thread.org/detail.html?package=pahomqtt) | Eclipse 开源的 MQTT C/C++ 客户端 | +| [netutils](http://packages.rt-thread.org/detail.html?package=netutils) | RT-Thread 网络小工具集 | +| [at_device](http://packages.rt-thread.org/detail.html?package=at_device) | AT 组件在不同设备上的移植或示例 | + +[更多IOT相关软件包...](http://packages.rt-thread.org/search.html?classify=iot) + +## 外设 + +与底层外设硬件相关的软件包,sensor软件包等 + +| 软件包名称 | 备注 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [aht10](http://packages.rt-thread.org/detail.html?package=aht10) | 数字温湿度传感器 aht10 的驱动库 | +| [dht11](http://packages.rt-thread.org/detail.html?package=dht11) | DHT11 单总线数字温湿度传感器 | +| [gt9147](http://packages.rt-thread.org/detail.html?package=gt9147) | GT9147 触摸芯片的驱动包 | +| [ft6236](http://packages.rt-thread.org/detail.html?package=ft6236) | FT6236 触摸芯片的驱动包 | +| [mpu6xxx](http://packages.rt-thread.org/detail.html?package=mpu6xxx) | 兼容 mpu6000, mpu6050, mpu6500, mpu9250 等等型号的驱动库 | +| [pcf8574](http://packages.rt-thread.org/detail.html?package=pcf8574) | 针对 I2C 并行口扩展 8 位 I/O 软件包 | +| [easyblink](http://packages.rt-thread.org/detail.html?package=easyblink) | 小巧轻便的 LED 控制软件包, 可以容易地控制 LED 开 、关、反转和各种间隔闪烁 | +| [max17048](http://packages.rt-thread.org/detail.html?package=max17048) | 电池监测芯片 | +| [ds18b20](http://packages.rt-thread.org/detail.html?package=ds18b20) | 单总线数字温湿度传感器 ds18b20 的软件包 | + +[更多外设相关软件包...](http://packages.rt-thread.org/search.html?classify=peripherals) + +## 系统 + +系统级软件包,监控系统行为、其他文件系统等 + +| 软件包名称 | 备注 | +| ------------------------------------------------------------ | ---------------------------------------------------- | +| [CMSIS](http://packages.rt-thread.org/detail.html?package=CMSIS) | CMSIS 软件包在 RT-Thread 上的移植 | +| [FlashDB](http://packages.rt-thread.org/detail.html?package=FlashDB) | 一款支持 KV 数据和时序数据的轻量级数据库 | +| [fal](http://packages.rt-thread.org/detail.html?package=fal) | Flash 抽象层的实现,负责管理 Flash 设备和 Flash 分区 | +| [littlefs](http://packages.rt-thread.org/detail.html?package=littlefs) | 为微控制器设计的一个小型的且掉电安全的文件系统 | +| [syswatch](http://packages.rt-thread.org/detail.html?package=syswatch) | 系统看守:一个用于保障系统长期正常运行的组件 | +| [rt_printf](http://packages.rt-thread.org/detail.html?package=rt_printf) | 线程安全版本的rt_kprintf | +| [LittlevGL2RTT](http://packages.rt-thread.org/detail.html?package=LittlevGL2RTT) | LittlevGL2RTT 是基于 RT-Thread 的图形库软件包 | + +[更多系统相关软件包...](http://packages.rt-thread.org/search.html?classify=system) + +## 编程语言 + +可运行在终端板卡上的各种编程语言,脚本或解释器 + +| 软件包名称 | 备注 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [jerryscript](http://packages.rt-thread.org/detail.html?package=jerryscript) | 针对 RT-Thread 的JerryScript 移植 | +| [Lua](http://packages.rt-thread.org/detail.html?package=Lua) | Lua 库适配 RT-Thread 3.0 (基于 lua 5.1.4版本 和 lua 5.3.4版本) | +| [micropython](http://packages.rt-thread.org/detail.html?package=micropython) | MicroPython 在 RT-Thread 上的移植 | + +[更多编程语言相关软件包...](http://packages.rt-thread.org/search.html?classify=language) + +## 工具 + +辅助使用的一些工具软件包 + +| 软件包名称 | 备注 | +| ------------------------------------------------------------ | -------------------------------------------------------- | +| [adbd](http://packages.rt-thread.org/detail.html?package=adbd) | 在 RT-Thread 上实现的 Android ADB daemon | +| [CmBacktrace](http://packages.rt-thread.org/detail.html?package=CmBacktrace) | ARM Cortex-M 系列 MCU 错误追踪库 | +| [EasyFlash](http://packages.rt-thread.org/detail.html?package=EasyFlash) | 轻量级嵌入式 Flash 存储器库,让 Flash 成为小型 KV 数据库 | +| [EasyLogger](http://packages.rt-thread.org/detail.html?package=EasyLogger) | 一款超轻量级(ROM<1.6K, RAM<0.3k)、高性能的 C/C++ 日志库 | +| [SystemView](http://packages.rt-thread.org/detail.html?package=SystemView) | SEGGER 的 SystemView 移植 | +| [ulog_easyflash](http://packages.rt-thread.org/detail.html?package=ulog_easyflash) | 基于 EasyFlash 的 ulog 插件 | + +[更多工具相关软件包...](http://packages.rt-thread.org/search.html?classify=tools) + +## 杂类 + +一些未归类的软件包,demo,示例等 + +| 软件包名称 | 备注 | +| ------------------------------------------------------------ | ------------------------------------------ | +| [fastlz](http://packages.rt-thread.org/detail.html?package=fastlz) | 一款极速的压缩库 | +| [filesystem_samples](http://packages.rt-thread.org/detail.html?package=filesystem_samples) | RT-Thread 文件系统示例 | +| [network_samples](http://packages.rt-thread.org/detail.html?package=network_samples) | RT-Thread 网络示例 | +| [peripheral_samples](http://packages.rt-thread.org/detail.html?package=peripheral_samples) | RT-Thread 外设示例 | +| [crclib](http://packages.rt-thread.org/detail.html?package=crclib) | 一个包含8位、16位、32位CRC校验计算的函数库 | + +[更多杂类相关软件包...](http://packages.rt-thread.org/search.html?classify=misc) + +## 多媒体 + +RT-Thread上的音视频软件包 + +| 软件包名称 | 备注 | +| ------------------------------------------------------------ | --------------------------------------------- | +| [TJpgDec](http://packages.rt-thread.org/detail.html?package=TJpgDec) | JPEG 解码库 | +| [touchgfx2rtt](http://packages.rt-thread.org/detail.html?package=touchgfx2rtt) | touchgfx在RT-Thread上的移植。 | +| [wavplayer](http://packages.rt-thread.org/detail.html?package=wavplayer) | 简洁的wav格式的音乐播放器,提供播放和录音功能 | +| [STemWin](http://packages.rt-thread.org/detail.html?package=STemWin) | STemWin在RT-Thread上的移植 | + +[更多多媒体相关软件包...](http://packages.rt-thread.org/search.html?classify=multimedia) + +## 安全 + +加解密算法及安全传输层 + +| 软件包名称 | 备注 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| [mbedtls](http://packages.rt-thread.org/detail.html?package=mbedtls) | 一个开源的、可移植的、易于使用的、可读的且灵活的 SSL 库 | +| [tinycrypt](http://packages.rt-thread.org/detail.html?package=tinycrypt) | 一个简小并且可配置的加解密软件包 | +| [yd_crypto](http://packages.rt-thread.org/detail.html?package=yd_crypto) | 用于微控制器的加解密算法库,平台无关、算法独立、易移植、易使用。 | + +[更多安全相关软件包...](http://packages.rt-thread.org/search.html?classify=security) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/at/at.md b/rt-thread-version/rt-thread-standard/programming-manual/at/at.md new file mode 100644 index 0000000000000000000000000000000000000000..64ea5b19fc3164a7b20084f219e54d768f4f1e89 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/at/at.md @@ -0,0 +1,885 @@ +# AT 组件 # + +## AT 命令简介 + +AT 命令(AT Commands)最早是由发明拨号调制解调器(MODEM)的贺氏公司(Hayes)为了控制 MODEM 而发明的控制协议。后来随着网络带宽的升级,速度很低的拨号 MODEM 基本退出一般使用市场,但是 AT 命令保留下来。当时主要的移动电话生产厂家共同为 GSM 研制了一整套 AT 命令,用于控制手机的 GSM 模块。AT 命令在此基础上演化并加入 GSM 07.05 标准以及后来的 GSM 07.07 标准,实现比较健全的标准化。 + +在随后的 GPRS 控制、3G 模块等方面,均采用的 AT 命令来控制,AT 命令逐渐在产品开发中成为实际的标准。如今,AT 命令也广泛的应用于嵌入式开发领域,AT 命令作为主芯片和通讯模块的协议接口,硬件接口一般为串口,这样主控设备可以通过简单的命令和硬件设计完成多种操作。 + +**AT 命令集是一种应用于 AT 服务器(AT Server)与 AT 客户端(AT Client)间的设备连接与数据通信的方式**。 其基本结构如下图所示: + +![AT 命令框架](figures/at_framework.jpg) + +1. 一般 AT 命令由三个部分组成,分别是:前缀、主体和结束符。其中前缀由字符 AT 构成;主体由命令、参数和可能用到的数据组成;结束符一般为 `` (`"\r\n"`)。 + +2. AT 功能的实现需要 AT Server 和 AT Client 两个部分共同完成。 + +3. AT Server 主要用于接收 AT Client 发送的命令,判断接收的命令及参数格式,并下发对应的响应数据,或者主动下发数据。 + +4. AT Client 主要用于发送命令、等待 AT Server 响应,并对 AT Server 响应数据或主动发送的数据进行解析处理,获取相关信息。 + +5. AT Server 和 AT Client 之间支持多种数据通讯的方式(UART、SPI 等),目前最常用的是串口 UART 通讯方式。 + +6. AT Server 向 AT Client 发送的数据分成两种:响应数据和 URC 数据。 + +- 响应数据: AT Client 发送命令之后收到的 AT Server 响应状态和信息。 + +- URC 数据: AT Server 主动发送给 AT Client 的数据,一般出现在一些特殊的情况,比如 WIFI 连接断开、TCP 接收数据等,这些情况往往需要用户做出相应操作。 + +随着 AT 命令的逐渐普及,越来越多的嵌入式产品上使用了 AT 命令,AT 命令作为主芯片和通讯模块的协议接口,硬件接口一般为串口,这样主控设备可以通过简单的命令和硬件设计完成多种操作。 + +虽然 AT 命令已经形成了一定的标准化,但是不同的芯片支持的 AT 命令并没有完全统一,这直接提高了用户使用的复杂性。对于 AT 命令的发送和接收以及数据的解析没有统一的处理方式。并且在使用 AT 设备连接网络时,只能通过命令完成简单的设备连接和数据收发功能,很难做到对上层网络应用接口的适配,不利于产品设备的开发。 + +为了方便用户使用 AT 命令,简单的适配不同的 AT 模块, RT-Thread 提供了 AT 组件用于 AT 设备的连接和数据通讯。AT 组件的实现包括客户端的和服务器两部分。 + +## AT 组件简介 + +AT 组件是基于 RT-Thread 系统的 `AT Server` 和 `AT Client` 的实现,组件完成 AT 命令的发送、命令格式及参数判断、命令的响应、响应数据的接收、响应数据的解析、URC 数据处理等整个 AT 命令数据交互流程。 + +通过 AT 组件,设备可以作为 AT Client 使用串口连接其他设备发送并接收解析数据,可以作为 AT Server 让其他设备甚至电脑端连接完成发送数据的响应,也可以在本地 shell 启动 CLI 模式使设备同时支持 AT Server 和 AT Client 功能,该模式多用于设备开发调试。 + +**AT 组件资源占用:** + +- AT Client 功能:4.6K ROM 和 2.0K RAM; + +- AT Server 功能:4.0K ROM 和 2.5K RAM; + +- AT CLI 功能: 1.5K ROM ,几乎没有使用 RAM。 + +整体看来,AT 组件资源占用极小,因此非常适用应用于资源有限的嵌入式设备中。AT 组件代码主要位于 `rt-thread/components/net/at/` 目录中。主要的功能包括如下, + +**AT Server 主要功能特点:** + +- 基础命令: 实现多种通用基础命令(ATE、ATZ 等); +- 命令兼容: 命令支持忽略大小写,提高命令兼容性; +- 命令检测: 命令支持自定义参数表达式,并实现对接收的命令参数自检测功能; +- 命令注册: 提供简单的用户自定义命令添加方式,类似于 `finsh/msh` 命令添加方式; +- 调试模式: 提供 AT Server CLI 命令行交互模式,主要用于设备调试。 + +**AT Client 主要功能特点:** + +- URC 数据处理: 完备的 URC 数据的处理方式; +- 数据解析: 支持自定义响应数据的解析方式,方便获取响应数据中相关信息; +- 调试模式: 提供 AT Client CLI 命令行交互模式,主要用于设备调试。 +- AT Socket:作为 AT Client 功能的延伸,使用 AT 命令收发作为基础,实现标准的 BSD Socket API,完成数据的收发功能,使用户通过 AT 命令完成设备连网和数据通讯。 +- 多客户端支持: AT 组件目前支持多客户端同时运行。 + +## AT Server ## + +### AT Server 配置 ### + +当我们使用 AT 组件中的 AT Server 功能时需要在 rtconfig.h 中定义如下配置: + +| **宏定义** | **描述** | +| ---- | ---- | +|RT_USING_AT| 开启 AT 组件 | +|AT_USING_SERVER |开启 AT Server 功能| +|AT_SERVER_DEVICE |定义设备上 AT Server 功能使用的串口通讯设备名称,确保未被使用且设备名称唯一,例如 `uart3` 设备| +|AT_SERVER_RECV_BUFF_LEN|AT Server 设备最大接收数据的长度| +|AT_CMD_END_MARK_CRLF|判断接收命令的行结束符 | +|AT_USING_CLI | 开启服务器命令行交互模式| +|AT_DEBUG|开启 AT 组件 DEBUG 模式,可以显示更多调试日志信息 | +|AT_PRINT_RAW_CMD | 开启实时显示 AT 命令通信数据模式,方便调试| + +对于不同的 AT 设备,发送命令的行结束符的格式有几种: `"\r\n"`、`"\r"`、`"\n"`,用户需要根据 AT Server 连接的设备类型选用对应的行结束符,进而判断发送命令行的结束, 定义的方式如下: + +| **宏定义** | **结束符** | +| ---- | ---- | +| AT_CMD_END_MARK_CRLF | `"\r\n"` | +| AT_CMD_END_MARK_CR | `"\r"` | +| AT_CMD_END_MARK_LF | `"\n"` | + +上面配置选项可以直接在 `rtconfig.h` 文件中添加使用,也可以通过组件包管理工具 Env 配置选项加入,ENV 中具体路径如下: + +```c +RT-Thread Components ---> + Network ---> + AT commands ---> + [*] Enable AT commands + [*] Enable debug log output + [*] Enable AT commands server + (uart3) Server device name + (256) The maximum length of server data accepted + The commands new line sign (\r\n) ---> + [ ] Enable AT commands client + [*] Enable command-line interface for AT commands + [ ] Enable print RAW format AT command communication data +``` + +添加配置完成之后可以使用命令行重新生成工程,或使用 scons 来进行编译生成。 + +### AT Server 初始化 ### + +配置开启 AT Server 配置之后,需要在启动时对它进行初始化,开启 AT Server 功能,如果程序中已经使用了组件自动初始化,则不再需要额外进行单独的初始化,否则需要在初始化任务中调用如下函数: + +```c +int at_server_init(void); +``` +AT Server 初始化函数,属于应用层函数,需要在使用 AT Server 功能或者使用 AT Server CLI 功能前调用。`at_server_init()` 函数完成对 AT 命令存放数据段初始化、AT Server 设备初始化以及 AT Server 使用的信号量等资源的初始化,并创建 `at_server` 线程用于 AT Server 中数据的接收的解析。 + +AT Server 初始化成功之后,设备就可以作为 AT 服务器与 AT 客户端的串口设备连接并进行数据通讯,或者使用串口转化工具连接 PC,使 PC 端串口调试助手作为 AT 客户端与其进行数据通讯。 + +### 自定义 AT 命令添加方式 ### + +目前,不同厂家的 AT 设备使用的 AT 命令集的格式没有完全的统一的标准,所以 AT 组件中的 AT Server 只支持了部分基础通用 AT 命令,例如:ATE、AT+RST 等,这些命令只能满足设备基本操作,用户想使用更多功能需要针对不同 AT 设备完成自定义 AT Server 命令,AT 组件提供类似于 `finsh/msh` 命令添加方式的 AT 命令添加方式,方便用户实现需要的命令。 + +AT Server 目前默认支持的基础命令如下: + +- AT:AT 测试命令; +- ATZ:设备恢复出厂设置; +- AT+RST:设备重启; +- ATE:ATE1 开启回显,ATE0 关闭回显; +- AT&L:列出全部命令列表; +- AT+UART:设置串口设置信息。 + +AT 命令根据传入的参数格式不同可以实现不同的功能,对于每个 AT 命令最多包含四种功能,如下所述: + +- 测试功能:`AT+=?` 用于查询命令参数格式及取值范围; +- 查询功能:`AT+?` 用于返回命令参数当前值; +- 设置功能:`AT+=...` 用于用户自定义参数值; +- 执行功能:`AT+` 用于执行相关操作。 + +每个命令的四种功能并不需要全部实现,用户自定义添加 AT Server 命令时,可根据自己需求实现一种或几种上述功能函数,未实现的功能可以使用 `NULL` 表示,再通过自定义命令添加函数添加到基础命令列表,添加方式类似于 `finsh/msh` 命令添加方式,添加函数如下: + +```c +AT_CMD_EXPORT(_name_, _args_expr_, _test_, _query_, _setup_, _exec_); +``` + +|**参数** |**描述** | +| ---------- | ------------------------------- | +| `_name_ ` | AT 命令名称 | +| `_args_expr_` | AT 命令参数表达式;(无参数为 NULL,`<>` 中为必选参数,`[]` 中为可选参数) | +| `_test_` | AT 测试功能函数名;(无实现为 NULL) | +| `_query_` | AT 查询功能函数名;(同上) | +| `_setup_` | AT 设置功能函数名;(同上) | +| `_exec_` | AT 执行功能函数名;(同上) | + +如下为 AT 命令注册示例,`AT+TEST` 命令存在两个参数,第一个参数为必选参数,第二个参数为可选参数,命令实现查询功能和执行功能: + +```c +static at_result_t at_test_exec(void) +{ + at_server_printfln("AT test commands execute!"); + + return 0; +} +static at_result_t at_test_query(void) +{ + at_server_printfln("AT+TEST=1,2"); + + return 0; +} + +AT_CMD_EXPORT("AT+TEST", =[,], NULL, at_test_query, NULL, at_test_exec); +``` + +### AT Server API 接口 + +#### 发送数据至客户端(不换行) + +```c +void at_server_printf(const char *format, ...); +``` + +该函数用于 AT Server 通过串口设备发送固定格式的数据到对应的 AT Client 串口设备上,数据结尾不带换行符。用于自定义 AT Server 中 AT 命令的功能函数中。 + +| **参数** | **描述** | +|------|-------------------------| +| format | 自定义输入数据的表达式 | +| ... | 输入数据列表,为可变参数 | + +#### 发送数据至客户端(换行) + +```c +void at_server_printfln(const char *format, ...); +``` + + 该函数用于 AT Server 通过串口设备发送固定格式的数据到对应的 AT Client 串口设备上,数据结尾带换行符。用于自定义 AT Server 中 AT 命令的功能函数中。 + +| **参数** | **描述** | +|------|-------------------------| +| format | 自定义输入数据的表达式 | +| ... | 输入数据列表,为可变参数 | + +#### 发送命令执行结果至客户端 + +```c +void at_server_print_result(at_result_t result); +``` + +该函数用于 AT Server 通过串口设备发送命令执行结果到对应的 AT Client 串口设备上。AT 组件提供多种固定的命令执行结果类型,自定义命令时可以直接使用函数返回结果; + +| **参数** | **描述** | +|------|-----------------| +| result | 命令执行结果类型 | + +AT 组件中命令执行结果类型以枚举类型给出,如下表所示: + +| 命令执行结果类型 | 解释 | +|------------------------|------------------| +| AT_RESULT_OK | 命令执行成功 | +| AT_RESULT_FAILE | 命令执行失败 | +| AT_RESULT_NULL | 命令无返回结果 | +| AT_RESULT_CMD_ERR | 输入命令错误 | +| AT_RESULT_CHECK_FAILE | 参数表达式匹配错误 | +| AT_RESULT_PARSE_FAILE | 参数解析错误 | + +可参考以下代码了解如何使用 at_server_print_result 函数: + +```c +static at_result_t at_test_setup(const char *args) +{ + if(!args) + { + /* 如果传入的命令之后的参数错误,返回表达式匹配错误结果 */ + at_server_print_result(AT_RESULT_CHECK_FAILE); + } + + /* 正常情况下返回执行成功结果 */ + at_server_print_result(AT_RESULT_OK); + return 0; +} +static at_result_t at_test_exec(void) +{ + // execute some functions of the AT command. + + /* 该命令不需要返回结果 */ + at_server_print_result(AT_RESULT_NULL); + return 0; +} +AT_CMD_EXPORT("AT+TEST", =,, NULL, NULL, at_test_setup, at_test_exec); +``` + +#### 解析输入命令参数 + +```c +int at_req_parse_args(const char *req_args, const char *req_expr, ...); +``` + +一个 AT 命令的四种功能函数中,只有设置函数有入参,该入参为去除 AT 命令剩余部分,例如一个命令输入为 `"AT+TEST=1,2,3,4"`,则设置函数的入参为参数字符串 `"=1,2,3,4"` 部分。 + +该命令解析函数主要用于 AT 命令的设置函数中,用于解析传入字符串参数,得到对应的多个输入参数,用于执行后面操作,这里的解析语法使用的标准 `sscanf` 解析语法,后面 AT Client 参数解析函数中会详细介绍。 + +| **参数** | **描述** | +|---------|-----------------------------------------------| +| req_args | 请求命令的传入参数字符串 | +| req_expr | 自定义参数解析表达式,用于解析上述传入参数数据 | +| ... | 输出的解析参数列表,为可变参数 | +| **返回** | -- | +| >0 | 成功,返回匹配参数表达式的可变参数个数 | +| =0 | 失败,无匹配参数表达式的参数 | +| -1 | 失败,参数解析错误 | + +可参考以下代码了解如何使用 at_server_print_result 函数: + +```c +static at_result_t at_test_setup(const char *args) +{ + int value1,value2; + + /* args 的输入标准格式应为 "=1,2","=%d,%d" 为自定义参数解析表达式,解析得到结果存入 value1 和 value2 变量 */ + if (at_req_parse_args(args, "=%d,%d", &value1, &value2) > 0) + { + /* 数据解析成功,回显数据到 AT Server 串口设备 */ + at_server_printfln("value1 : %d, value2 : %d", value1, value2); + + /* 数据解析成功,解析参数的个数大于零,返回执行成功 */ + at_server_print_result(AT_RESULT_OK); + } + else + { + /* 数据解析失败,解析参数的个数不大于零,返回解析失败结果类型 */ + at_server_print_result(AT_RESULT_PARSE_FAILE); + } + return 0; +} +/* 添加 "AT+TEST" 命令到 AT 命令列表,命令参数格式为两个必选参数 */ +AT_CMD_EXPORT("AT+TEST", =,, NULL, NULL, at_test_setup, NULL); +``` + +**移植相关接口** + +AT Server 默认已支持多种基础命令(ATE、ATZ 等),其中部分命令的函数实现与硬件或平台相关,需要用户自定义实现。AT 组件源码 `src/at_server.c` 文件中给出了移植文件的弱函数定义,用户可在项目中新建移植文件实现如下函数完成移植接口,也可以直接在文件中修改弱函数完成移植接口。 + +1. 设备重启函数:`void at_port_reset(void);`。该函数完成设备软重启功能,用于 AT Server 中基础命令 AT+RST 的实现。 + +2. 设备恢复出厂设置函数:`void at_port_factory_reset(void);`。该函数完成设备恢复出厂设置功能,用于 AT Server 中基础命令 ATZ 的实现。 + +3. 链接脚本中添加命令表(gcc 添加,keil、iar 跳过) + +工程中若使用 gcc 工具链,需在链接脚本中添加 AT 服务器命令表对应的 section ,参考如下链接脚本: + +```c +/* Constant data goes into FLASH */ +.rodata : +{ + ... + + /* section information for RT-thread AT package */ + . = ALIGN(4); + __rtatcmdtab_start = .; + KEEP(*(RtAtCmdTab)) + __rtatcmdtab_end = .; + . = ALIGN(4); +} > CODE +``` + +## AT Client + +### AT Client 配置 + +当我们使用 AT 组件中的 AT Client 功能是需要在 rtconfig.h 中定义如下配置: + +```c +#define RT_USING_AT +#define AT_USING_CLIENT +#define AT_CLIENT_NUM_MAX 1 +#define AT_USING_SOCKET +#define AT_USING_CLI +#define AT_PRINT_RAW_CMD +``` + +- `RT_USING_AT`: 用于开启或关闭 AT 组件; + +- `AT_USING_CLIENT`: 用于开启 AT Client 功能; + +- `AT_CLIENT_NUM_MAX`: 最大同时支持的 AT 客户端数量。 + +- `AT_USING_SOCKET`:用于 AT 客户端支持标准 BSD Socket API,开启 AT Socket 功能。 + +- `AT_USING_CLI`: 用于开启或关闭客户端命令行交互模式。 + +- `AT_PRINT_RAW_CMD`:用于开启 AT 命令通信数据的实时显示模式,方便调试 + +上面配置选项可以直接在 `rtconfig.h` 文件中添加使用,也可以通过组件包管理工具 Env 配置选项加入,ENV 中具体路径如下: + +```c +RT-Thread Components ---> + Network ---> + AT commands ---> + [*] Enable AT commands + [ ] Enable debug log output + [ ] Enable AT commands server + [*] Enable AT commands client + (1) The maximum number of supported clients + [*] Enable BSD Socket API support by AT commnads + [*] Enable command-line interface for AT commands + [ ] Enable print RAW format AT command communication data +``` + +添加配置完成之后可以使用命令行重新生成工程,或使用 scons 来进行编译生成。 + +### AT Client 初始化 ### + +配置开启 AT Client 配置之后,需要在启动时对它进行初始化,开启 AT client 功能,如果程序中已经使用了组件自动初始化,则不再需要额外进行单独的初始化,否则需要在初始化任务中调用如下函数: + +```c +int at_client_init(const char *dev_name, rt_size_t recv_bufsz); +``` + +AT Client 初始化函数,属于应用层函数,需要在使用 AT Client 功能或者使用 AT Client CLI 功能前调用。`at_client_init()` 函数完成对 AT Client 设备初始化、AT Client 移植函数的初始化、AT Client 使用的信号量、互斥锁等资源初始化,并创建 `at_client` 线程用于 AT Client 中数据的接收的解析以及对 URC 数据的处理。 + +### AT Client 数据收发方式 ### + +AT Client 主要功能是发送 AT 命令、接收数据并解析数据。下面是对 AT Client 数据接收和发送相关流程与函数介绍。相关结构体定义: + +```c +struct at_response +{ + /* response buffer */ + char *buf; + /* the maximum response buffer size */ + rt_size_t buf_size; + /* the number of setting response lines + * == 0: the response data will auto return when received 'OK' or 'ERROR' + * != 0: the response data will return when received setting lines number data */ + rt_size_t line_num; + /* the count of received response lines */ + rt_size_t line_counts; + /* the maximum response time */ + rt_int32_t timeout; +}; +typedef struct at_response *at_response_t; +``` + +AT 组件中,该结构体用于定义一个 AT 命令响应数据的控制块,用于存放或者限制 AT 命令响应数据的数据格式。其中 `buf` 用于存放接收到的响应数据,注意的是 buf 中存放的数据并不是原始响应数据,而是原始响应数据去除结束符(`"\r\n"`)的数据,**buf 中每行数据以 '\0' 分割,方便按行获取数据**。`buf_size` 为用户自定义本次响应最大支持的接收数据的长度,由用户根据自己命令返回值长度定义。`line_num` 为用户自定义的本次响应数据需要接收的行数,**如果没有响应行数限定需求,可以置为 0**。 `line_counts` 用于记录本次响应数据总行数。`timeout` 为用户自定义的本次响应数据最大响应时间。该结构体中 `buf_size`、`line_num`、`timeout` 三个参数为限制条件,在结构体创建时设置,其他参数为存放数据参数,用于后面数据解析。 + +相关 API 接口介绍: + +#### 创建响应结构体 + +```c +at_response_t at_create_resp(rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout); +``` + +| **参数** | **描述** | +|---------|-----------------------------------------| +| buf_size | 本次响应最大支持的接收数据的长度 | +| line_num | 本次响应需要返回数据的行数,行数是以标准结束符(如 `"\r\n"`)划分。若为 0 ,则接收到 "OK" 或 "ERROR" 数据后结束本次响应接收;若大于 0,接收完当前设置行号的数据后返回成功 | +| timeout | 本次响应数据最大响应时间,数据接收超时返回错误 | +| **返回** | -- | +| != NULL | 成功,返回指向响应结构体的指针 | +| = NULL | 失败,内存不足 | + +该函数用于创建自定义的响应数据接收结构,用于后面接收并解析发送命令响应数据。 + +#### 删除响应结构体 + +```c +void at_delete_resp(at_response_t resp); +``` + +| **参数** | **描述** | +|----|-------------------------| +| resp | 准备删除的响应结构体指针 | + +该函数用于删除创建的响应结构体对象,一般与**at_create_resp**创建函数成对出现。 + +#### 设置响应结构体参数 + +```c +at_response_t at_resp_set_info(at_response_t resp, rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout); +``` + +| **参数** | **描述** | +|---------|----------------------------------| +| resp | 已经创建的响应结构体指针 | +| buf_size | 本次响应最大支持的接收数据的长度 | +| line_num | 本次响应需要返回数据的行数,行数是以标准结束符划分 若为 0 ,则接收到 "OK" 或 "ERROR" 数据后结束本次响应接收 若大于 0,接收完当前设置行号的数据后返回成功 | +| timeout | 本次响应数据最大响应时间,数据接收超时返回错误 | +| **返回** | -- | +| != NULL | 成功,返回指向响应结构体的指针 | +| = NULL | 失败,内存不足 | + +该函数用于设置已经创建的响应结构体信息,主要设置对响应数据的限制信息,一般用于创建结构体之后,发送 AT 命令之前。该函数主要用于设备初始化时命令的发送,可以减少响应结构体创建次数,降低代码资源占用。 + +#### 发送命令并接收响应 + +```c +rt_err_t at_exec_cmd(at_response_t resp, const char *cmd_expr, ...); +``` + +| **参数** | **描述** | +|---------|-----------------------------| +| resp | 创建的响应结构体指针 | +| cmd_expr | 自定义输入命令的表达式 | +| ... | 输入命令数据列表,为可变参数 | +| **返回** | -- | +| >=0 | 成功 | +| -1 | 失败 | +| -2 | 失败,接收响应超时 | + +该函数用于 AT Client 发送命令到 AT Server,并等待接收响应,其中 `resp` 是已经创建好的响应结构体的指针,AT 命令的使用匹配表达式的可变参输入,**输入命令的结尾不需要添加命令结束符**。 + +可参考以下代码了解如何使用以上几个 AT 命令收发相关函数使用方式: + +```c +/* + * 程序清单:AT Client 发送命令并接收响应例程 + */ + +#include +#include /* AT 组件头文件 */ + +int at_client_send(int argc, char**argv) +{ + at_response_t resp = RT_NULL; + + if (argc != 2) + { + LOG_E("at_cli_send [command] - AT client send commands to AT server."); + return -RT_ERROR; + } + + /* 创建响应结构体,设置最大支持响应数据长度为 512 字节,响应数据行数无限制,超时时间为 5 秒 */ + resp = at_create_resp(512, 0, rt_tick_from_millisecond(5000)); + if (!resp) + { + LOG_E("No memory for response structure!"); + return -RT_ENOMEM; + } + + /* 发送 AT 命令并接收 AT Server 响应数据,数据及信息存放在 resp 结构体中 */ + if (at_exec_cmd(resp, argv[1]) != RT_EOK) + { + LOG_E("AT client send commands failed, response error or timeout !"); + return -ET_ERROR; + } + + /* 命令发送成功 */ + LOG_D("AT Client send commands to AT Server success!"); + + /* 删除响应结构体 */ + at_delete_resp(resp); + + return RT_EOK; +} +#ifdef FINSH_USING_MSH +#include +/* 输出 at_Client_send 函数到 msh 中 */ +MSH_CMD_EXPORT(at_Client_send, AT Client send commands to AT Server and get response data); +#endif +``` + +发送和接收数据的实现原理比较简单,主要是对 AT Client 绑定的串口设备的读写操作,并设置相关行数和超时来限制响应数据,值得注意的是,正常情况下需要先创建 resp 响应结构体传入 at_exec_cmd 函数用于数据的接收,当 at_exec_cmd 函数传入 resp 为 NULL 时说明本次发送数据**不考虑处理响应数据直接返回结果**。 + +### AT Client 数据解析方式 ### + +数据正常获取之后,需要对响应的数据进行解析处理,这也是 AT Client 重要的功能之一。 AT Client 中数据的解析提供自定义解析表达式的解析形式,其解析语法使用标准的 `sscanf` 解析语法。开发者可以通过自定义数据解析表达式回去响应数据中有用信息,前提是开发者需要提前查看相关手册了解 AT Client 连接的 AT Server 设备响应数据的基本格式。下面通过几个函数和例程简单 AT Client 数据解析方式。 + +#### 获取指定行号的响应数据 + +```c +const char *at_resp_get_line(at_response_t resp, rt_size_t resp_line); +``` + +| **参数** | **描述** | +|----------|-----------------------------| +| resp |响应结构体指针 | +| resp_line | 需要获取数据的行号 | +| **返回** | -- | +| != NULL | 成功,返回对应行号数据的指针 | +| = NULL | 失败,输入行号错误 | + +该函数用于在 AT Server 响应数据中获取指定行号的一行数据。行号是以标准数据结束符来判断的,上述发送和接收函数 at_exec_cmd 已经对响应数据的数据和行号进行记录处理存放于 resp 响应结构体中,这里可以直接获取对应行号的数据信息。 + +#### 获取指定关键字的响应数据 + +```c +const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword); +``` + +| **参数** | **描述** | +|-------|-----------------------------| +| resp |响应结构体指针 | +| keyword | 关键字信息 | +| **返回** | -- | +| != NULL | 成功,返回对应行号数据的指针 | +| = NULL | 失败,未找到关键字信息 | + +该函数用于在 AT Server 响应数据中通过关键字获取对应的一行数据。 + +#### 解析指定行号的响应数据 + +```c +int at_resp_parse_line_args(at_response_t resp, rt_size_t resp_line, const char *resp_expr, ...); +``` + +| **参数** | **描述** | +|----------|---------------------------------| +| resp |响应结构体指针 | +| resp_line | 需要解析数据的行号,**行号从 1 开始计数** | +| resp_expr | 自定义的参数解析表达式 | +| ... | 解析参数列表,为可变参数 | +| **返回** | -- | +| >0 | 成功,返回解析成功的参数个数 | +| =0 | 失败,无匹参配数解析表达式的参数 | +| -1 | 失败,参数解析错误 | + +该函数用于在 AT Server 响应数据中获取指定行号的一行数据, 并解析该行数据中的参数。 + +#### 解析指定关键字行的响应数据 + +```c +int at_resp_parse_line_args_by_kw(at_response_t resp, const char *keyword, const char *resp_expr, ...); +``` + +| **参数** | **描述** | +|----------|---------------------------------| +| resp |响应结构体指针 | +| keyword | 关键字信息 | +| resp_expr | 自定义的参数解析表达式 | +| ... | 解析参数列表,为可变参数 | +| **返回** | -- | +| >0 | 成功,返回解析成功的参数个数 | +| =0 | 失败,无匹参配数解析表达式的参数 | +| -1 | 失败,参数解析错误 | + +该函数用于在 AT Server 响应数据中获取包含关键字的一行数据, 并解析该行数据中的参数。 + +数据解析语法使用标准 `sscanf` 解析语法,语法的内容比较多,开发者可以自行搜索其解析语法,这里使用两个例程介绍简单使用方法。 + +#### 串口配置信息解析示例 + +客户端发送的数据: + +```c +AT+UART? +``` + +客户端获取的响应数据: + +```c +UART=115200,8,1,0,0\r\n +OK\r\n +``` + +解析伪代码如下: + +```c +/* 创建服务器响应结构体,64 为用户自定义接收数据最大长度 */ +resp = at_create_resp(64, 0, rt_tick_from_millisecond(5000)); + +/* 发送数据到服务器,并接收响应数据存放在 resp 结构体中 */ +at_exec_cmd(resp, "AT+UART?"); + +/* 解析获取串口配置信息,1 表示解析响应数据第一行,'%*[^=]'表示忽略等号之前的数据 */ +at_resp_parse_line_args(resp, 1,"%*[^=]=%d,%d,%d,%d,%d", &baudrate, &databits, &stopbits, &parity, &control); +printf("baudrate=%d, databits=%d, stopbits=%d, parity=%d, control=%d\n", + baudrate, databits, stopbits, parity, control); + +/* 删除服务器响应结构体 */ +at_delete_resp(resp); +``` + +#### IP 和 MAC 地址解析示例 #### + +客户端发送的数据: + +```c +AT+IPMAC? +``` + +服务器获取的响应数据: + +```c +IP=192.168.1.10\r\n +MAC=12:34:56:78:9a:bc\r\n +OK\r\n +``` + +解析伪代码如下: + +```c +/* 创建服务器响应结构体,128 为用户自定义接收数据最大长度 */ +resp = at_create_resp(128, 0, rt_tick_from_millisecond(5000)); + +at_exec_cmd(resp, "AT+IPMAC?"); + +/* 自定义解析表达式,解析当前行号数据中的信息 */ +at_resp_parse_line_args(resp, 1,"IP=%s", ip); +at_resp_parse_line_args(resp, 2,"MAC=%s", mac); +printf("IP=%s, MAC=%s\n", ip, mac); + +at_delete_resp(resp); +``` + +解析数据的关键在于解析表达式的正确定义,因为对于 AT 设备的响应数据,不同设备厂家不同命令的响应数据格式不唯一,所以只能提供自定义解析表达式的形式获取需要信息,at_resp_parse_line_args 解析参数函数的设计基于 `sscanf` 数据解析方式,开发者使用之前需要先了解基本的解析语法,再结合响应数据设计合适的解析语法。如果开发者不需要解析具体参数,可以直接使用 at_resp_get_line 函数获取一行的具体数据。 + +### AT Client URC 数据处理 ### + +URC 数据的处理是 AT Client 另一个重要功能,URC 数据为服务器主动下发的数据,不能通过上述数据发送接收函数接收,并且对于不同设备 URC 数据格式和功能不一样,所以 URC 数据处理的方式也是需要用户自定义实现的。AT 组件中对 URC 数据的处理提供列表管理方式,用户可自定义添加 URC 数据和其执行函数到管理列表中,所以 URC 数据的处理也是 AT Client 的主要移植工作。 + +相关结构体: + +```c +struct at_urc +{ + const char *cmd_prefix; // URC 数据前缀 + const char *cmd_suffix; // URC 数据后缀 + void (*func)(const char *data, rt_size_t size); // URC 数据执行函数 +}; +typedef struct at_urc *at_urc_t; +``` + +每种 URC 数据都有一个结构体控制块,用于定义判断 URC 数据的前缀和后缀,以及 URC 数据的执行函数。一段数据只有完全匹配 URC 的前缀和后缀才能定义为 URC 数据,获取到匹配的 URC 数据后会立刻执行 URC 数据执行函数。所以开发者添加一个 URC 数据需要自定义匹配的前缀、后缀和执行函数。 + + +#### URC 数据列表初始化 + +```c +void at_set_urc_table(const struct at_urc *table, rt_size_t size); +``` + +| **参数** | **描述** | +|-----|-----------------------| +| table | URC 数据结构体数组指针 | +| size | URC 数据的个数 | + +该函数用于初始化开发者自定义的 URC 数据列表,主要在 AT Client 移植函数中使用。 + +下面给出 AT Client 移植具体示例,该示例主要展示 `at_client_port_init()` 移植函数中 URC 数据的具体处理方式,开发者可直接应用到自己的移植文件中,或者自定义修改实现功能,完成 AT Client 的移植。 + +```c +static void urc_conn_func(const char *data, rt_size_t size) +{ + /* WIFI 连接成功信息 */ + LOG_D("AT Server device WIFI connect success!"); +} + +static void urc_recv_func(const char *data, rt_size_t size) +{ + /* 接收到服务器发送数据 */ + LOG_D("AT Client receive AT Server data!"); +} + +static void urc_func(const char *data, rt_size_t size) +{ + /* 设备启动信息 */ + LOG_D("AT Server device startup!"); +} + +static struct at_urc urc_table[] = { + {"WIFI CONNECTED", "\r\n", urc_conn_func}, + {"+RECV", ":", urc_recv_func}, + {"RDY", "\r\n", urc_func}, +}; + +int at_client_port_init(void) +{ + /* 添加多种 URC 数据至 URC 列表中,当接收到同时匹配 URC 前缀和后缀的数据,执行 URC 函数 */ + at_set_urc_table(urc_table, sizeof(urc_table) / sizeof(urc_table[0])); + return RT_EOK; +} +``` + +### AT Client 其他 API 接口介绍 + +#### 发送指定长度数据 + +```c +rt_size_t at_client_send(const char *buf, rt_size_t size); +``` + +| **参数** | **描述** | +|----|-----------------------------| +| buf | 发送数据的指针 | +| size | 发送数据的长度 | +| **返回** | -- | +| >0 | 成功,返回发送成功的数据长度 | +| <=0 | 失败 | + +该函数用于通过 AT Client 设备发送指定长度数据到 AT Server 设备,多用于 AT Socket 功能。 + +#### 接收指定长度数据 + +```c +rt_size_t at_client_recv(char *buf, rt_size_t size,rt_int32_t timeout); +``` + +| **参数** | **描述** | +|----|-----------------------------| +| buf | 接收数据的指针 | +| size | 最大支持接收数据的长度 | +| timeout | 接收数据超时时间,单位为 tick | +| **返回** | -- | +| >0 | 成功,返回接收成功的数据长度 | +| <=0 | 失败,接收数据错误或超时 | + +该函数用于通过 AT Client 设备接收指定长度的数据,多用于 AT Socket 功能。**该函数只能在 URC 回调处理函数中使用**。 + +#### 设置接收数据的行结束符 #### + +```c +void at_set_end_sign(char ch); +``` + +| 参数 | 描述 | +| ----- | ----- | +|ch | 行结束符 | +| **返回** | **描述** | +|无 | 无 | + +该函数用于设置行结束符,用于判断客户端接收一行数据的结束, 多用于 AT Socket 功能。 + +#### 等待模块初始化完成 #### + +```c +int at_client_wait_connect(rt_uint32_t timeout); +``` + +| 参数 | 描述 | +| ----- | ----- | +|timeout | 等待超时时间 | +| **返回** | **描述** | +|0 | 成功 | +|<0 | 失败,超时时间内无数据返回 | + +该函数用于 AT 模块启动时循环发送 AT 命令,直到模块响应数据,说明模块启动成功。 + +### AT Client 多客户端支持 ### + +一般情况下,设备作为 AT Client 只连接一个 AT 模块(AT 模块作为 AT Server)可直接使用上述数据收发和命令解析的函数。少数情况,设备作为 AT Client 需要连接多个 AT 模块,这种情况下需要设备的多客户端支持功能。 + +AT 组件提供对多客户端连接的支持,并且提供两套不同的函数接口:**单客户端模式函数** 和 **多客户端模式函数**。 + +- 单客户端模式函数:该类函数接口主要用于设备只连接一个 AT 模块情况,或者在设备连接多个 AT 模块时,用于**第一个初始化**的 AT 客户端中。 + +- 多客户端模式函数:该类函数接口主要用设备连接多个 AT 模块情况。 + +两种不同模式函数和在不同应用场景下的优缺点如下图: + +![at client 模式对比图](figures/at_multiple_client.jpg) + +单客户端模式函数定义与单连接模式函数相比,主要是对传入的客户端对象的定义不同,单客户端模式函数默认使用第一个初始化的 AT 客户端对象,多客户端模式函数可以传入用户自定义获取的客户端对象, 获取客户端对象的函数如下: + +```c +at_client_t at_client_get(const char *dev_name); +``` + +该函数通过传入的设备名称获取该设备创建的 AT 客户端对象,用于多客户端连接时区分不同的客户端。 + +单客户端模式和多客户端模式函数接口定义区别如下几个函数: + +| 单客户端模式函数 | 多客户端模式函数 | +| ----------------------------| ---------------------------------------| +| at_exec_cmd(...) | at_obj_exec_cmd(client, ...) | +| at_set_end_sign(...) | at_obj_set_end_sign(client, ...) | +| at_set_urc_table(...) | at_obj_set_urc_table(client, ...) | +| at_client_wait_connect(...) | at_client_obj_wait_connect(client, ...) | +| at_client_send(...) | at_client_obj_send(client, ...) | +| at_client_recv(...) | at_client_obj_recv(client, ...) | + +两种模式客户端数据收发和解析的方式基本相同,在函数使用流程上有所不同,如下所示: + +```c +/* 单客户端模式函数使用方式 */ + +at_response_t resp = RT_NULL; + +at_client_init("uart2", 512); + +resp = at_create_resp(256, 0, 5000); + +/* 使用单客户端模式函数发送命令 */ +at_exec_cmd(resp, "AT+CIFSR"); + +at_delete_resp(resp); +``` + +```c +/* 多客户端模式函数使用方式 */ + +at_response_t resp = RT_NULL; +at_client_t client = RT_NULL; + +/* 初始化两个 AT 客户端 */ +at_client_init("uart2", 512); +at_client_init("uart3", 512); + +/* 通过名称获取对应的 AT 客户端对象 */ +client = at_client_get("uart3"); + +resp = at_create_resp(256, 0, 5000); + +/* 使用多客户端模式函数发送命令 */ +at_obj_exec_cmd(client, resp, "AT+CIFSR"); + +at_delete_resp(resp); +``` + +其他函数使用的流程区别类似于上述 `at_obj_exec_cmd()` 函数,主要是先通过 `at_client_get()` 函数获取客户端对象,再通过传入的对象判断是哪个客户端,实现多客户端的支持。 + +## 常见问题 + +### Q: 开启 AT 命令收发数据实时打印功能,shell 上日志显示错误怎么办? + +**A:** 提高 shell 对应串口设备波特率为 921600,提高串口打印速度,防止数据量过大时打印显示错误。 + +### Q: AT Socket 功能启动时,编译提示 “The AT socket device is not selected, please select it through the env menuconfig”? + +**A:** 该错误因为开启 AT Socket 功能之后,默认开启 at device 软件包中为配置对应的设备型号,进入 at device 软件包,配置设备为 ESP8266 设备,配置 WIFI 信息,重新 scons 生成工程,编译下载。 + +### Q: AT Socket 功能数据接收超时或者数据接收不全? + +**A:** 该错误可能是 AT 使用的串口设备中接收数据缓冲区过小(RT_SERIAL_RB_BUFSZ 默认为 64 bytes),数据未及时接收完就被覆盖导致的,适当增加串口接收数据的缓冲区大小(如 256 bytes)。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/at/figures/at_framework.jpg b/rt-thread-version/rt-thread-standard/programming-manual/at/figures/at_framework.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f5c868dc6c26a7715b9946817c68ca24d9be68cd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/at/figures/at_framework.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/at/figures/at_multiple_client.jpg b/rt-thread-version/rt-thread-standard/programming-manual/at/figures/at_multiple_client.jpg new file mode 100644 index 0000000000000000000000000000000000000000..58d01f6bf6adf0d8f506d81d99a531705b79e7fd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/at/figures/at_multiple_client.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/basic.md b/rt-thread-version/rt-thread-standard/programming-manual/basic/basic.md new file mode 100644 index 0000000000000000000000000000000000000000..dc8f618a6236ec0deb6d34e12d493069206cc0b2 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/basic/basic.md @@ -0,0 +1,743 @@ +# 内核基础 + +本章介绍 RT-Thread 内核基础,包括:内核简介、系统的启动流程及内核配置的部分内容,为后面的章节奠定基础。 + +RT-Thread 内核的简单介绍,从软件架构入手讲解实时内核的组成与实现,这部分给初学者引入一些 RT-Thread 内核相关的概念与基础知识,让初学者对内核有初步的了解。学完本章,读者将会对 RT-Thread 内核有基本的了解,知道内核的组成部分、系统如何启动、内存分布情况以及内核配置方法。 + +RT-Thread 内核介绍 +----------------- + +内核是操作系统最基础也是最重要的部分。下图为 RT-Thread 内核架构图,内核处于硬件层之上,内核部分包括内核库、实时内核实现。 + +![RT-Thread 内核及底层结构](figures/03kernel_Framework.png) + +内核库是为了保证内核能够独立运行的一套小型的类似 C 库的函数实现子集。这部分根据编译器的不同自带 C 库的情况也会有些不同,当使用 GNU GCC 编译器时,会携带更多的标准 C 库实现。 + +> 提示:C 库:也叫 C 运行库(C Runtime Library),它提供了类似 “strcpy”、“memcpy” 等函数,有些也会包括 “printf”、“scanf” 函数的实现。RT-Thread Kernel Service Library 仅提供内核用到的一小部分 C 库函数实现,为了避免与标准 C 库重名,在这些函数前都会添加上 rt_前缀。 + +实时内核的实现包括:对象管理、线程管理及调度器、线程间通信管理、时钟管理及内存管理等等,内核最小的资源占用情况是 3KB ROM,1.2KB RAM。 + +### 线程调度 + +线程是 RT-Thread 操作系统中最小的调度单位,线程调度算法是基于优先级的全抢占式多线程调度算法,即在系统中除了中断处理函数、调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的,包括线程调度器自身。支持 256 个线程优先级(也可通过配置文件更改为最大支持 32 个或 8 个线程优先级,针对 STM32 默认配置是 32 个线程优先级),0 优先级代表最高优先级,最低优先级留给空闲线程使用;同时它也支持创建多个具有相同优先级的线程,相同优先级的线程间采用时间片的轮转调度算法进行调度,使每个线程运行相应时间;另外调度器在寻找那些处于就绪状态的具有最高优先级的线程时,所经历的时间是恒定的,系统也不限制线程数量的多少,线程数目只和硬件平台的具体内存相关。 + +线程管理将在[《线程管理》](../thread/thread.md)章节详细介绍。 + +### 时钟管理 + +RT-Thread 的时钟管理以时钟节拍为基础,时钟节拍是 RT-Thread 操作系统中最小的时钟单位。RT-Thread 的定时器提供两类定时器机制:第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止定时器否则将永远持续执行下去。 + +另外,根据超时函数执行时所处的上下文环境,RT-Thread 的定时器可以设置为 HARD_TIMER 模式或者 SOFT_TIMER 模式。 + +通常使用定时器定时回调函数(即超时函数),完成定时服务。用户根据自己对定时处理的实时性要求选择合适类型的定时器。 + +定时器将在[《时钟管理》](../timer/timer.md)章节展开讲解。 + +### 线程间同步 + +RT-Thread 采用信号量、互斥量与事件集实现线程间同步。线程通过对信号量、互斥量的获取与释放进行同步;互斥量采用优先级继承的方式解决了实时系统常见的优先级翻转问题。线程同步机制支持线程按优先级等待或按先进先出方式获取信号量或互斥量。线程通过对事件的发送与接收进行同步;事件集支持多事件的 “或触发” 和“与触发”,适合于线程等待多个事件的情况。 + +信号量、互斥量与事件集的概念将在[《线程间同步》](../ipc1/ipc1.md)章节详细介绍。 + +### 线程间通信 + +RT-Thread 支持邮箱和消息队列等通信机制。邮箱中一封邮件的长度固定为 4 字节大小;消息队列能够接收不固定长度的消息,并把消息缓存在自己的内存空间中。邮箱效率较消息队列更为高效。邮箱和消息队列的发送动作可安全用于中断服务例程中。通信机制支持线程按优先级等待或按先进先出方式获取。 + +邮箱和消息队列的概念将在[《线程间通信》](../ipc2/ipc2.md)章节详细介绍。 + +### 内存管理 + +RT-Thread 支持静态内存池管理及动态内存堆管理。当静态内存池具有可用内存时,系统对内存块分配的时间将是恒定的;当静态内存池为空时,系统将申请内存块的线程挂起或阻塞掉 (即线程等待一段时间后仍未获得内存块就放弃申请并返回,或者立刻返回。等待的时间取决于申请内存块时设置的等待时间参数),当其他线程释放内存块到内存池时,如果有挂起的待分配内存块的线程存在的话,则系统会将这个线程唤醒。 + +动态内存堆管理模块在系统资源不同的情况下,分别提供了面向小内存系统的内存管理算法及面向大内存系统的 +SLAB 内存管理算法。 + +还有一种动态内存堆管理叫做 memheap,适用于系统含有多个地址可不连续的内存堆。使用 memheap 可以将多个内存堆 “粘贴” 在一起,让用户操作起来像是在操作一个内存堆。 + +内存管理的概念将在[《内存管理》](../memory/memory.md)章节展开讲解。 + +### I/O 设备管理 + +RT-Thread 将 PIN、I2C、SPI、USB、UART 等作为外设设备,统一通过设备注册完成。实现了按名称访问的设备管理子系统,可按照统一的 API 界面访问硬件设备。在设备驱动接口上,根据嵌入式系统的特点,对不同的设备可以挂接相应的事件。当设备事件触发时,由驱动程序通知给上层的应用程序。 + +I/O 设备管理的概念将在[《设备模型》](../device/device.md)及[《通用设备》](../device/adc/adc.md)章节展开讲解。 + +RT-Thread 启动流程 +------------------ + +一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。RT-Thread 支持多种平台和多种编译器,而 rtthread_startup() 函数是 RT-Thread 规定的统一启动入口。一般执行顺序是:系统先从启动文件开始运行,然后进入 RT-Thread 的启动 rtthread_startup() ,最后进入用户入口 main(),如下图所示: + +![启动流程](figures/rtt_startup.png) + +以 MDK-ARM 为例,用户程序入口为 main() 函数,位于 main.c 文件中。系统启动后先从汇编代码 startup_stm32f103xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统启动,最后进入用户程序入口 main()。 + +为了在进入 main() 之前完成 RT-Thread 系统功能初始化,我们使用了 MDK 的扩展功能 `$Sub$$` 和 `$Super$$`。可以给 main 添加 `$Sub$$` 的前缀符号作为一个新功能函数 `$Sub$$main`,这个 `$Sub$$main` 可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统启动,进行系统一系列初始化),再调用 `$Super$$main` 转到 main() 函数执行,这样可以让用户不用去管 main() 之前的系统初始化操作。 + +关于 `$Sub$$` 和 `$Super$$` 扩展功能的使用,详见 [ARM® Compiler v5.06 for µVision®armlink User Guide](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0377g/pge1362065967698.html)。 + +下面我们来看看在 components.c 中定义的这段代码: + +```c +/* $Sub$$main 函数 */ +int $Sub$$main(void) +{ +  rtthread_startup(); +  return 0; +} +``` + + +在这里 `$Sub$$main` 函数调用了 rtthread_startup() 函数,其中 rtthread_startup() 函数的代码如下所示: + +```c +int rtthread_startup(void) +{ + rt_hw_interrupt_disable(); + + /* 板级初始化:需在该函数内部进行系统堆的初始化 */ + rt_hw_board_init(); + + /* 打印 RT-Thread 版本信息 */ + rt_show_version(); + + /* 定时器初始化 */ + rt_system_timer_init(); + + /* 调度器初始化 */ + rt_system_scheduler_init(); + +#ifdef RT_USING_SIGNALS + /* 信号初始化 */ + rt_system_signal_init(); +#endif + + /* 由此创建一个用户 main 线程 */ + rt_application_init(); + + /* 定时器线程初始化 */ + rt_system_timer_thread_init(); + + /* 空闲线程初始化 */ + rt_thread_idle_init(); + + /* 启动调度器 */ + rt_system_scheduler_start(); + + /* 不会执行至此 */ + return 0; +} +``` + + +这部分启动代码,大致可以分为四个部分: + +(1)初始化与系统相关的硬件; + +(2)初始化系统内核对象,例如定时器、调度器、信号; + +(3)创建 main 线程,在 main 线程中对各类模块依次进行初始化; + +(4)初始化定时器线程、空闲线程,并启动调度器。 + +启动调度器之前,系统所创建的线程在执行 rt_thread_startup() 后并不会立马运行,它们会处于就绪状态等待系统调度;待启动调度器之后,系统才转入第一个线程开始运行,根据调度规则,选择的是就绪队列中优先级最高的线程。 + +rt_hw_board_init() 中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出终端绑定到这个串口,后续系统运行信息就会从串口打印出来。 + +main() 函数是 RT-Thread 的用户代码入口,用户可以在 main() 函数里添加自己的应用。 + +```c +int main(void) +{ + /* user app entry */ + return 0; +} +``` + + +RT-Thread 程序内存分布 +--------------------- + +一般 MCU 包含的存储空间有:片内 Flash 与片内 RAM,RAM 相当于内存,Flash 相当于硬盘。编译器会将一个程序分类为好几个部分,分别存储在 MCU 不同的存储区。 + +Keil 工程在编译完之后,会有相应的程序所占用的空间提示信息,如下所示: + +``` +linking... +Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124 +After Build - User command \#1: fromelf --bin.\\build\\rtthread-stm32.axf--output rtthread.bin +".\\build\\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:07 +``` + +上面提到的 Program Size 包含以下几个部分: + +1)Code:代码段,存放程序的代码部分; + +2)RO-data:只读数据段,存放程序中定义的常量; + +3)RW-data:读写数据段,存放初始化为非 0 值的全局变量; + +4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量; + +编译完工程会生成一个. map 的文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行也说明了上面几个字段的关系: + +``` +Total RO Size (Code + RO Data) 53668 ( 52.41kB) +Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB) +Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB) +``` + +1)RO Size 包含了 Code 及 RO-data,表示程序占用 Flash 空间的大小; + +2)RW Size 包含了 RW-data 及 ZI-data,表示运行时占用的 RAM 的大小; + +3)ROM Size 包含了 Code、RO Data 以及 RW Data,表示烧写程序所占用的 Flash 空间的大小; + +程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如图 3-3 中左图所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。 + +STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。 + +![RT-Thread 内存分布](figures/03Memory_distribution.png) + +其中动态内存堆为未使用的 RAM 空间,应用程序申请和释放的内存块都来自该空间。 + +如下面的例子: + +```c +rt_uint8_t* msg_ptr; +msg_ptr = (rt_uint8_t*) rt_malloc (128); +rt_memset(msg_ptr, 0, 128); +``` + +代码中的 msg_ptr 指针指向的 128 字节内存空间位于动态内存堆空间中。 + +而一些全局变量则是存放于 RW 段和 ZI 段中,RW 段存放的是具有初始值的全局变量(而常量形式的全局变量则放置在 RO 段中,是只读属性的),ZI 段存放的系统未初始化的全局变量,如下面的例子: + +```c +#include + +const static rt_uint32_t sensor_enable = 0x000000FE; +rt_uint32_t sensor_value; +rt_bool_t sensor_inited = RT_FALSE; + +void sensor_init() +{ + /* ... */ +} +``` + +sensor_value 存放在 ZI 段中,系统启动后会自动初始化成零(由用户程序或编译器提供的一些库函数初始化成零)。sensor_inited 变量则存放在 RW 段中,而 sensor_enable 存放在 RO 段中。 + +RT-Thread 自动初始化机制 +----------------------- + +自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。 + +例如在串口驱动中调用一个宏定义告知系统初始化需要调用的函数,代码如下: + +```c +int rt_hw_usart_init(void) /* 串口初始化函数 */ +{ + ... ... + /* 注册串口 1 设备 */ + rt_hw_serial_register(&serial1, "uart1", + RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, + uart); + return 0; +} +INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使用组件自动初始化机制 */ +``` + +示例代码最后的 INIT_BOARD_EXPORT(rt_hw_usart_init) 表示使用自动初始化功能,按照这种方式,rt_hw_usart_init() 函数就会被系统自动调用,那么它是在哪里被调用的呢? + +在系统启动流程图中,有两个函数:rt_components_board_init() 与 rt_components_init(),其后的带底色方框内部的函数表示被自动初始化的函数,其中: + +1. “board init functions” 为所有通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数。 + +2. “pre-initialization functions” 为所有通过 INIT_PREV_EXPORT(fn)申明的初始化函数。 + +3. “device init functions” 为所有通过 INIT_DEVICE_EXPORT(fn) 申明的初始化函数。 + +4. “components init functions” 为所有通过 INIT_COMPONENT_EXPORT(fn)申明的初始化函数。 + +5. “enviroment init functions” 为所有通过 INIT_ENV_EXPORT(fn) 申明的初始化函数。 + +6. “application init functions” 为所有通过 INIT_APP_EXPORT(fn)申明的初始化函数。 + +rt_components_board_init() 函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数表,并调用各个函数。 + +rt_components_init() 函数会在操作系统运行起来之后创建的 main 线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。rt_components_init() 函数会遍历通过剩下的其他几个宏申明的初始化函数表。 + +RT-Thread 的自动初始化机制使用了自定义 RTI 符号段,将需要在启动时进行初始化的函数指针放到了该段中,形成一张初始化函数表,在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。 + +用来实现自动初始化功能的宏接口定义详细描述如下表所示: + +|**初始化顺序**|**宏接口** |**描述** | +|----------------|------------------------------------|----------------------------------------------| +| 1 | INIT_BOARD_EXPORT(fn) | 非常早期的初始化,此时调度器还未启动 | +| 2 | INIT_PREV_EXPORT(fn) | 主要是用于纯软件的初始化、没有太多依赖的函数 | +| 3 | INIT_DEVICE_EXPORT(fn) | 外设驱动初始化相关,比如网卡设备 | +| 4 | INIT_COMPONENT_EXPORT(fn) | 组件初始化,比如文件系统或者 LWIP | +| 5 | INIT_ENV_EXPORT(fn) | 系统环境初始化,比如挂载文件系统 | +| 6 | INIT_APP_EXPORT(fn) | 应用初始化,比如 GUI 应用 | + +初始化函数主动通过这些宏接口进行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),链接器会自动收集所有被申明的初始化函数,放到 RTI 符号段中,该符号段位于内存分布的 RO 段中,该 RTI 符号段中的所有函数在系统初始化时会被自动调用。 + +RT-Thread 内核对象模型 +--------------------- + +### 静态对象和动态对象 + +RT-Thread 内核采用面向对象的设计思想进行设计,系统级的基础设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。内核对象分为两类:静态内核对象和动态内核对象,静态内核对象通常放在 RW 段和 ZI 段中,在系统启动后在程序中初始化;动态内核对象则是从内存堆中创建的,而后手工做初始化。 + +以下代码是一个关于静态线程和动态线程的例子: + + +```c +/* 线程 1 的对象和运行时用到的栈 */ +static struct rt_thread thread1; +static rt_uint8_t thread1_stack[512]; + +/* 线程 1 入口 */ +void thread1_entry(void* parameter) +{ + int i; + + while (1) + { + for (i = 0; i < 10; i ++) + { + rt_kprintf("%d\n", i); + + /* 延时 100ms */ + rt_thread_mdelay(100); + } + } +} + +/* 线程 2 入口 */ +void thread2_entry(void* parameter) +{ + int count = 0; + while (1) + { + rt_kprintf("Thread2 count:%d\n", ++count); + + /* 延时 50ms */ + rt_thread_mdelay(50); + } +} + +/* 线程例程初始化 */ +int thread_sample_init() +{ + rt_thread_t thread2_ptr; + rt_err_t result; + + /* 初始化线程 1 */ + /* 线程的入口是 thread1_entry,参数是 RT_NULL + * 线程栈是 thread1_stack + * 优先级是 200,时间片是 10 个 OS Tick + */ + result = rt_thread_init(&thread1, + "thread1", + thread1_entry, RT_NULL, + &thread1_stack[0], sizeof(thread1_stack), + 200, 10); + + /* 启动线程 */ + if (result == RT_EOK) rt_thread_startup(&thread1); + + /* 创建线程 2 */ + /* 线程的入口是 thread2_entry, 参数是 RT_NULL + * 栈空间是 512,优先级是 250,时间片是 25 个 OS Tick + */ + thread2_ptr = rt_thread_create("thread2", + thread2_entry, RT_NULL, + 512, 250, 25); + + /* 启动线程 */ + if (thread2_ptr != RT_NULL) rt_thread_startup(thread2_ptr); + + return 0; +} +``` + +在这个例子中,thread1 是一个静态线程对象,而 thread2 是一个动态线程对象。thread1 对象的内存空间,包括线程控制块 thread1 与栈空间 thread1_stack 都是编译时决定的,因为代码中都不存在初始值,都统一放在未初始化数据段中。thread2 运行中用到的空间都是动态分配的,包括线程控制块(thread2_ptr 指向的内容)和栈空间。 + +静态对象会占用 RAM 空间,不依赖于内存堆管理器,内存分配时间确定。动态对象则依赖于内存堆管理器,运行时申请 RAM 空间,当对象被删除后,占用的 RAM 空间被释放。这两种方式各有利弊,可以根据实际环境需求选择具体使用方式。 + +### 内核对象管理架构 + +RT-Thread 采用内核对象管理系统来访问 / 管理所有内核对象,内核对象包含了内核中绝大部分设施,这些内核对象可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象。 + +通过这种内核对象的设计方式,RT-Thread 做到了不依赖于具体的内存分配方式,系统的灵活性得到极大的提高。 + +RT-Thread 内核对象包括:线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。对象容器中包含了每类内核对象的信息,包括对象类型,大小等。对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上,如图 RT-Thread 的内核对象容器及链表如下图所示: + +![RT-Thread 的内核对象容器及链表](figures/03kernel_object.png) + +下图则显示了 RT-Thread 中各类内核对象的派生和继承关系。对于每一种具体内核对象和对象控制块,除了基本结构外,还有自己的扩展属性(私有属性),例如,对于线程控制块,在基类对象基础上进行扩展,增加了线程状态、优先级等属性。这些属性在基类对象的操作中不会用到,只有在与具体线程相关的操作中才会使用。因此从面向对象的观点,可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。 + +![RT-Thread 内核对象继承关系](figures/03kernel_object2.png) + +在对象管理模块中,定义了通用的数据结构,用来保存各种对象的共同属性,各种具体对象只需要在此基础上加上自己的某些特别的属性,就可以清楚的表示自己的特征。 + +这种设计方法的优点有: + +(1)提高了系统的可重用性和扩展性,增加新的对象类别很容易,只需要继承通用对象的属性再加少量扩展即可。 + +(2)提供统一的对象操作方式,简化了各种具体对象的操作,提高了系统的可靠性。 + +上图中由对象控制块 rt_object 派生出来的有:线程对象、内存池对象、定时器对象、设备对象和 IPC 对象(IPC:Inter-Process Communication,进程间通信。在 RT-Thread 实时操作系统中,IPC 对象的作用是进行线程间同步与通信);由 IPC 对象派生出信号量、互斥量、事件、邮箱与消息队列、信号等对象。 + +### 对象控制块 + +内核对象控制块的数据结构: + +```c +struct rt_object +{ + /* 内核对象名称 */ + char name[RT_NAME_MAX]; + /* 内核对象类型 */ + rt_uint8_t type; + /* 内核对象的参数 */ + rt_uint8_t flag; + /* 内核对象管理链表 */ + rt_list_t list; +}; +``` + +目前内核对象支持的类型如下: + +```c +enum rt_object_class_type +{ + RT_Object_Class_Thread = 0, /* 对象为线程类型 */ +#ifdef RT_USING_SEMAPHORE + RT_Object_Class_Semaphore, /* 对象为信号量类型 */ +#endif +#ifdef RT_USING_MUTEX + RT_Object_Class_Mutex, /* 对象为互斥量类型 */ +#endif +#ifdef RT_USING_EVENT + RT_Object_Class_Event, /* 对象为事件类型 */ +#endif +#ifdef RT_USING_MAILBOX + RT_Object_Class_MailBox, /* 对象为邮箱类型 */ +#endif +#ifdef RT_USING_MESSAGEQUEUE + RT_Object_Class_MessageQueue, /* 对象为消息队列类型 */ +#endif +#ifdef RT_USING_MEMPOOL + RT_Object_Class_MemPool, /* 对象为内存池类型 */ +#endif +#ifdef RT_USING_DEVICE + RT_Object_Class_Device, /* 对象为设备类型 */ +#endif + RT_Object_Class_Timer, /* 对象为定时器类型 */ +#ifdef RT_USING_MODULE + RT_Object_Class_Module, /* 对象为模块 */ +#endif + RT_Object_Class_Unknown, /* 对象类型未知 */ + RT_Object_Class_Static = 0x80 /* 对象为静态对象 */ +}; +``` + +从上面的类型说明,我们可以看出,如果是静态对象,那么对象类型的最高位将是 1(是 RT_Object_Class_Static 与其他对象类型的与操作),否则就是动态对象,系统最多能够容纳的对象类别数目是 127 个。 + +### 内核对象管理方式 + +内核对象容器的数据结构: + +```c +struct rt_object_information +{ + /* 对象类型 */ + enum rt_object_class_type type; + /* 对象链表 */ + rt_list_t object_list; + /* 对象大小 */ + rt_size_t object_size; +}; +``` + +一类对象由一个 rt_object_information 结构体来管理,每一个这类对象的具体实例都通过链表的形式挂接在 object_list 上。而这一类对象的内存块尺寸由 object_size 标识出来(每一类对象的具体实例,他们占有的内存块大小都是相同的)。 + +#### 初始化对象 + +在使用一个未初始化的静态对象前必须先对其进行初始化。初始化对象使用以下接口: + +```c +void rt_object_init(struct rt_object* object , + enum rt_object_class_type type , + const char* name) +``` + +当调用这个函数进行对象初始化时,系统会把这个对象放置到对象容器中进行管理,即初始化对象的一些参数,然后把这个对象节点插入到对象容器的对象链表中,对该函数的输入参数的描述如下表: + + +|**参数**|**描述** | +| -------- | ------------------------------------------------------------ | +| object | 需要初始化的对象指针,它必须指向具体的对象内存块,而不能是空指针或野指针 | +| type | 对象的类型,必须是 rt_object_class_type 枚举类型中列出的除 RT_Object_Class_Static 以外的类型(对于静态对象,或使用 rt_object_init 接口进行初始化的对象,系统会把它标识成 RT_Object_Class_Static 类型) | +| name | 对象的名字。每个对象可以设置一个名字,这个名字的最大长度由 RT_NAME_MAX 指定,并且系统不关心它是否是由’`\0`’做为终结符 | + +#### 脱离对象 + +从内核对象管理器中脱离一个对象。脱离对象使用以下接口: + +```c +void rt_object_detach(rt_object_t object); +``` + +调用该接口,可使得一个静态内核对象从内核对象容器中脱离出来,即从内核对象容器链表上删除相应的对象节点。对象脱离后,对象占用的内存并不会被释放。 + +#### 分配对象 + +上述描述的都是对象初始化、脱离的接口,都是面向对象内存块已经有的情况下,而动态的对象则可以在需要时申请,不需要时释放出内存空间给其他应用使用。申请分配新的对象可以使用以下接口: + +```c +rt_object_t rt_object_allocate(enum rt_object_class_type type , + const char* name) +``` + +在调用以上接口时,系统首先需要根据对象类型来获取对象信息(特别是对象类型的大小信息以用于系统能够分配正确大小的内存数据块),而后从内存堆中分配对象所对应大小的内存空间,然后再对该对象进行必要的初始化,最后将其插入到它所在的对象容器链表中。对该函数的输入参数的描述如下表: + + +|**参数** |**描述** | +| ------------------ | ------------------------------------------------------------ | +| type | 分配对象的类型,只能是 rt_object_class_type 中除 RT_Object_Class_Static 以外的类型。并且经过这个接口分配出来的对象类型是动态的,而不是静态的 | +| name | 对象的名字。每个对象可以设置一个名字,这个名字的最大长度由 RT_NAME_MAX 指定,并且系统不关心它是否是由’`\0`’做为终结符 | +|**返回** | —— | +| 分配成功的对象句柄 | 分配成功 | +| RT_NULL | 分配失败 | + +#### 删除对象 + +对于一个动态对象,当不再使用时,可以调用如下接口删除对象,并释放相应的系统资源: + +```c +void rt_object_delete(rt_object_t object); +``` + +当调用以上接口时,首先从对象容器链表中脱离对象,然后释放对象所占用的内存。对该函数的输入参数的描述下表: + + +|**参数**|**描述** | +|----------|------------| +| object | 对象的句柄 | + +#### 辨别对象 + +判断指定对象是否是系统对象(静态内核对象)。辨别对象使用以下接口: + +```c +rt_err_t rt_object_is_systemobject(rt_object_t object); +``` + +调用 rt_object_is_systemobject 接口可判断一个对象是否是系统对象,在 RT-Thread 操作系统中,一个系统对象也就是一个静态对象,对象类型标识上 RT_Object_Class_Static 位置位。通常使用 rt_object_init() 方式初始化的对象都是系统对象。对该函数的输入参数的描述如下表: + +rt_object_is_systemobject() 的输入参数 + +|**参数**|**描述** | +|----------|------------| +| object | 对象的句柄 | + +RT-Thread 内核配置示例 +---------------------- + +RT-Thread 的一个重要特性是高度可裁剪性,支持对内核进行精细调整,对组件进行灵活拆卸。 + +配置主要是通过修改工程目录下的 rtconfig.h 文件来进行,用户可以通过打开 / 关闭该文件中的宏定义来对代码进行条件编译,最终达到系统配置和裁剪的目的,如下: + +(1)RT-Thread 内核部分 + +```c +/* 表示内核对象的名称的最大长度,若代码中对象名称的最大长度大于宏定义的长度, + * 多余的部分将被截掉。*/ +#define RT_NAME_MAX 8 + +/* 字节对齐时设定对齐的字节个数。常使用 ALIGN(RT_ALIGN_SIZE) 进行字节对齐。*/ +#define RT_ALIGN_SIZE 4 + +/* 定义系统线程优先级数;通常用 RT_THREAD_PRIORITY_MAX-1 定义空闲线程的优先级 */ +#define RT_THREAD_PRIORITY_MAX 32 + +/* 定义时钟节拍,为 100 时表示 100 个 tick 每秒,一个 tick 为 10ms */ +#define RT_TICK_PER_SECOND 100 + +/* 检查栈是否溢出,未定义则关闭 */ +#define RT_USING_OVERFLOW_CHECK + +/* 定义该宏开启 debug 模式,未定义则关闭 */ +#define RT_DEBUG +/* 开启 debug 模式时:该宏定义为 0 时表示关闭打印组件初始化信息,定义为 1 时表示启用 */ +#define RT_DEBUG_INIT 0 +/* 开启 debug 模式时:该宏定义为 0 时表示关闭打印线程切换信息,定义为 1 时表示启用 */ +#define RT_DEBUG_THREAD 0 + +/* 定义该宏表示开启钩子函数的使用,未定义则关闭 */ +#define RT_USING_HOOK + +/* 定义了空闲线程的栈大小 */ +#define IDLE_THREAD_STACK_SIZE 256 +``` + +(2)线程间同步与通信部分,该部分会使用到的对象有信号量、互斥量、事件、邮箱、消息队列、信号等。 + +```c +/* 定义该宏可开启信号量的使用,未定义则关闭 */ +#define RT_USING_SEMAPHORE + +/* 定义该宏可开启互斥量的使用,未定义则关闭 */ +#define RT_USING_MUTEX + +/* 定义该宏可开启事件集的使用,未定义则关闭 */ +#define RT_USING_EVENT + +/* 定义该宏可开启邮箱的使用,未定义则关闭 */ +#define RT_USING_MAILBOX + +/* 定义该宏可开启消息队列的使用,未定义则关闭 */ +#define RT_USING_MESSAGEQUEUE + +/* 定义该宏可开启信号的使用,未定义则关闭 */ +#define RT_USING_SIGNALS +``` + +(3)内存管理部分 + +```c +/* 开启静态内存池的使用 */ +#define RT_USING_MEMPOOL + +/* 定义该宏可开启两个或以上内存堆拼接的使用,未定义则关闭 */ +#define RT_USING_MEMHEAP + +/* 开启小内存管理算法 */ +#define RT_USING_SMALL_MEM + +/* 关闭 SLAB 内存管理算法 */ +/* #define RT_USING_SLAB */ + +/* 开启堆的使用 */ +#define RT_USING_HEAP +``` + +(4)内核设备对象 + +```c +/* 表示开启了系统设备的使用 */ +#define RT_USING_DEVICE + +/* 定义该宏可开启系统控制台设备的使用,未定义则关闭 */ +#define RT_USING_CONSOLE +/* 定义控制台设备的缓冲区大小 */ +#define RT_CONSOLEBUF_SIZE 128 +/* 控制台设备的名称 */ +#define RT_CONSOLE_DEVICE_NAME "uart1" +``` + +(5)自动初始化方式 + +```c +/* 定义该宏开启自动初始化机制,未定义则关闭 */ +#define RT_USING_COMPONENTS_INIT + +/* 定义该宏开启设置应用入口为 main 函数 */ +#define RT_USING_USER_MAIN +/* 定义 main 线程的栈大小 */ +#define RT_MAIN_THREAD_STACK_SIZE 2048 +``` + +(6)FinSH + +```c +/* 定义该宏可开启系统 FinSH 调试工具的使用,未定义则关闭 */ +#define RT_USING_FINSH + +/* 开启系统 FinSH 时:将该线程名称定义为 tshell */ +#define FINSH_THREAD_NAME "tshell" + +/* 开启系统 FinSH 时:使用历史命令 */ +#define FINSH_USING_HISTORY +/* 开启系统 FinSH 时:对历史命令行数的定义 */ +#define FINSH_HISTORY_LINES 5 + +/* 开启系统 FinSH 时:定义该宏开启使用 Tab 键,未定义则关闭 */ +#define FINSH_USING_SYMTAB + +/* 开启系统 FinSH 时:定义该线程的优先级 */ +#define FINSH_THREAD_PRIORITY 20 +/* 开启系统 FinSH 时:定义该线程的栈大小 */ +#define FINSH_THREAD_STACK_SIZE 4096 +/* 开启系统 FinSH 时:定义命令字符长度 */ +#define FINSH_CMD_SIZE 80 + +/* 开启系统 FinSH 时:定义该宏开启 MSH 功能 */ +#define FINSH_USING_MSH +/* 开启系统 FinSH 时:开启 MSH 功能时,定义该宏默认使用 MSH 功能 */ +#define FINSH_USING_MSH_DEFAULT +/* 开启系统 FinSH 时:定义该宏,仅使用 MSH 功能 */ +#define FINSH_USING_MSH_ONLY +``` + +(7)关于 MCU + +```c +/* 定义该工程使用的 MCU 为 STM32F103ZE;系统通过对芯片类型的定义,来定义芯片的管脚 */ +#define STM32F103ZE + +/* 定义时钟源频率 */ +#define RT_HSE_VALUE 8000000 + +/* 定义该宏开启 UART1 的使用 */ +#define RT_USING_UART1 +``` + +> [!NOTE] +> 注:在实际应用中,系统配置文件 rtconfig.h 是由配置工具自动生成的,无需手动更改。 + +常见宏定义说明 +-------------- + +RT-Thread 中经常使用一些宏定义,举例 Keil 编译环境下一些常见的宏定义: + +1)rt_inline,定义如下,static 关键字的作用是令函数只能在当前的文件中使用;inline 表示内联,用 static 修饰后在调用函数时会建议编译器进行内联展开。 + +```c +#define rt_inline static __inline +``` + +2)RT_USED,定义如下,该宏的作用是向编译器说明这段代码有用,即使函数中没有调用也要保留编译。例如 RT-Thread 自动初始化功能使用了自定义的段,使用 RT_USED 会将自定义的代码段保留。 + +```c +#define RT_USED __attribute__((used)) +``` + +3)RT_UNUSED,定义如下,表示函数或变量可能不使用,这个属性可以避免编译器产生警告信息。 + +```c +#define RT_UNUSED __attribute__((unused)) +``` + +4)RT_WEAK,定义如下,常用于定义函数,编译器在链接函数时会优先链接没有该关键字前缀的函数,如果找不到则再链接由 weak 修饰的函数。 + +```c +#define RT_WEAK __weak +``` + +5)ALIGN(n),定义如下,作用是在给某对象分配地址空间时,将其存放的地址按照 n 字节对齐,这里 n 可取 2 的幂次方。字节对齐的作用不仅是便于 CPU 快速访问,同时合理的利用字节对齐可以有效地节省存储空间。 + +```c +#define ALIGN(n) __attribute__((aligned(n))) +``` + +6)RT_ALIGN(size,align),定义如下,作用是将 size 提升为 align 定义的整数的倍数,例如,RT_ALIGN(13,4) 将返回 16。 + +```c +#define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1)) +``` + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03Memory_distribution.png b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03Memory_distribution.png new file mode 100644 index 0000000000000000000000000000000000000000..8664ec89f9aaf53a2fc7afbadeb1dcf47e5a3a0d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03Memory_distribution.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03Startup_process.png b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03Startup_process.png new file mode 100644 index 0000000000000000000000000000000000000000..4128df8d949bc2e7dd062a176beb280cff75d3cf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03Startup_process.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_Framework.png b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_Framework.png new file mode 100644 index 0000000000000000000000000000000000000000..a68f0e0afb3c323aa5754b0f8359b299fe791eaa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_Framework.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_object.png b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_object.png new file mode 100644 index 0000000000000000000000000000000000000000..8a5cbd9236b2729b8955d708af49c1cebbcd2c62 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_object.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_object2.png b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_object2.png new file mode 100644 index 0000000000000000000000000000000000000000..b1601b48686cb94abb817b7213c52e85f80cf6c4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/03kernel_object2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/rtt_startup.png b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/rtt_startup.png new file mode 100644 index 0000000000000000000000000000000000000000..04dbb79fed05b7463c5ceea179f5dd75c6553724 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/rtt_startup.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/startup-rtt.png b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/startup-rtt.png new file mode 100644 index 0000000000000000000000000000000000000000..db72d70a1c03a7946c586cafbe8e527befb150f0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/startup-rtt.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/startup.png b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/startup.png new file mode 100644 index 0000000000000000000000000000000000000000..7fe7c643dbd896ea9e1708b700a150612e87cf11 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/basic/figures/startup.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/adc/adc.md b/rt-thread-version/rt-thread-standard/programming-manual/device/adc/adc.md new file mode 100644 index 0000000000000000000000000000000000000000..40c907ddc5da4088affbec20a82f6e0681d8aa8b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/adc/adc.md @@ -0,0 +1,271 @@ +# ADC 设备 + +## ADC 简介 + +ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。模数转换器可以实现这个功能,在各种不同的产品中都可以找到它的身影。与之相对应的 DAC(Digital-to-Analog Converter),它是 ADC 模数转换的逆向过程。ADC 最早用于对无线信号向数字信号转换。如电视信号,长短播电台发接收等。 + +### 转换过程 + +如下图所示模数转换一般要经过采样、保持和量化、编码这几个步骤。在实际电路中,有些过程是合并进行的,如采样和保持,量化和编码在转换过程中是同时实现的。 + +![ADC 转换过程](figures/adc-p.png) + +采样是将时间上连续变化的模拟信号转换为时间上离散的模拟信号。采样取得的模拟信号转换为数字信号都需要一定时间,为了给后续的量化编码过程提供一个稳定的值,在采样电路后要求将所采样的模拟信号保持一段时间。 + +将数值连续的模拟量转换为数字量的过程称为量化。数字信号在数值上是离散的。采样保持电路的输出电压还需要按照某种近似方式归化到与之相应的离散电平上,任何数字量只能是某个最小数量单位的整数倍。量化后的数值最后还需要编码过程,也就是 A/D 转换器输出的数字量。 + +### 分辨率 + +分辨率以二进制(或十进制)数的位数来表示,一般有8位、10位、12位、16位等,它说明模数转换器对输入信号的分辨能力,位数越多,表示分辨率越高,恢复模拟信号时会更精确。 + +### 精度 + +精度表示 ADC 器件在所有的数值点上对应的模拟值和真实值之间的最大误差值,也就是输出数值偏离线性最大的距离。 + +> [!NOTE] +> 注:精度与分辨率是两个不一样的概念,请注意区分。 + +### 转换速率 + +转换速率是指 A/D 转换器完成一次从模拟到数字的 AD 转换所需时间的倒数。例如,某 A/D 转换器的转换速率为 1MHz,则表示完成一次 AD 转换时间为 1 微秒。 + +## 访问 ADC 设备 + +应用程序通过 RT-Thread 提供的 ADC 设备管理接口来访问 ADC 硬件,相关接口如下所示: + +| **函数** | **描述** | +| --------------- | ------------------ | +| rt_device_find() | 根据 ADC 设备名称查找设备获取设备句柄 | +| rt_adc_enable() | 使能 ADC 设备 | +| rt_adc_read() | 读取 ADC 设备数据 | +| rt_adc_disable() | 关闭 ADC 设备 | + +### 查找 ADC 设备 + +应用程序根据 ADC 设备名称获取设备句柄,进而可以操作 ADC 设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | ADC 设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到设备 | + +一般情况下,注册到系统的 ADC 设备名称为 adc0,adc1等,使用示例如下所示: + +```c +#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */ +rt_adc_device_t adc_dev; /* ADC 设备句柄 */ +/* 查找设备 */ +adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME); +``` + +### 使能 ADC 通道 + +在读取 ADC 设备数据前需要先使能设备,通过如下函数使能设备: + +```c +rt_err_t rt_adc_enable(rt_adc_device_t dev, rt_uint32_t channel); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | ADC 设备句柄 | +| channel | ADC 通道 | +| **返回** | —— | +| RT_EOK | 成功 | +| -RT_ENOSYS | 失败,设备操作方法为空 | +| 其他错误码 | 失败 | + +使用示例如下所示: + +```c +#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */ +#define ADC_DEV_CHANNEL 5 /* ADC 通道 */ +rt_adc_device_t adc_dev; /* ADC 设备句柄 */ +/* 查找设备 */ +adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME); +/* 使能设备 */ +rt_adc_enable(adc_dev, ADC_DEV_CHANNEL); +``` + +### 读取 ADC 通道采样值 + +读取 ADC 通道采样值可通过如下函数完成: + +```c +rt_uint32_t rt_adc_read(rt_adc_device_t dev, rt_uint32_t channel); +``` + +| **参数** | **描述** | +| ---------- | ----------------- | +| dev | ADC 设备句柄 | +| channel | ADC 通道 | +| **返回** | —— | +| 读取的数值 | | + +使用 ADC 采样电压值的使用示例如下所示: + +```c +#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */ +#define ADC_DEV_CHANNEL 5 /* ADC 通道 */ +#define REFER_VOLTAGE 330 /* 参考电压 3.3V,数据精度乘以100保留2位小数*/ +#define CONVERT_BITS (1 << 12) /* 转换位数为12位 */ + +rt_adc_device_t adc_dev; /* ADC 设备句柄 */ +rt_uint32_t value; +/* 查找设备 */ +adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME); +/* 使能设备 */ +rt_adc_enable(adc_dev, ADC_DEV_CHANNEL); +/* 读取采样值 */ +value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL); +/* 转换为对应电压值 */ +vol = value * REFER_VOLTAGE / CONVERT_BITS; +rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100); +``` + +实际电压值的计算公式为:采样值 * 参考电压 / (1 << 分辨率位数),上面示例代码乘以 100 将数据放大,最后通过 vol / 100 获得电压的整数位值,通过 vol % 100 获得电压的小数位值。 + +### 关闭 ADC 通道 + +关闭 ADC 通道可通过如下函数完成: + +```c +rt_err_t rt_adc_disable(rt_adc_device_t dev, rt_uint32_t channel); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | ADC 设备句柄 | +| channel | ADC 通道 | +| **返回** | —— | +| RT_EOK | 成功 | +| -RT_ENOSYS | 失败,设备操作方法为空 | +| 其他错误码 | 失败 | + +使用示例如下所示: + +```c +#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */ +#define ADC_DEV_CHANNEL 5 /* ADC 通道 */ +rt_adc_device_t adc_dev; /* ADC 设备句柄 */ +rt_uint32_t value; +/* 查找设备 */ +adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME); +/* 使能设备 */ +rt_adc_enable(adc_dev, ADC_DEV_CHANNEL); +/* 读取采样值 */ +value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL); +/* 转换为对应电压值 */ +vol = value * REFER_VOLTAGE / CONVERT_BITS; +rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100); +/* 关闭通道 */ +rt_adc_disable(adc_dev, ADC_DEV_CHANNEL); +``` + +### FinSH 命令 + +在使用设备前,需要先查找设备是否存在,可以使用命令 `adc probe` 后面跟注册的 ADC 设备的名称。如下所示: + +```c +msh >adc probe adc1 +probe adc1 success +``` + +使能设备的某个通道可以使用命令 `adc enable` 后面跟通道号。 + +```c +msh >adc enable 5 +adc1 channel 5 enables success +``` + +读取 ADC 设备某个通道的数据可以使用命令 `adc read` 后面跟通道号。 + +```c +msh >adc read 5 +adc1 channel 5 read value is 0x00000FFF +msh > +``` + +关闭设备的某个通道可以使用命令 `adc disable` 后面跟通道号。 + +```c +msh >adc disable 5 +adc1 channel 5 disable success +msh > +``` + +## ADC 设备使用示例 + +ADC 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 首先根据 ADC 设备名称 “adc1” 查找设备获取设备句柄。 + +2. 使能设备后读取 adc1 设备对应的通道 5 的采样值,然后根据分辨率为 12 位,参考电压为 3.3V 计算实际的电压值。 + +3. 最后关闭 ADC 设备对应通道。 + +运行结果:打印实际读取到的转换的原始数据和经过计算后的实际电压值。 + +```c +/* + * 程序清单: ADC 设备使用例程 + * 例程导出了 adc_sample 命令到控制终端 + * 命令调用格式:adc_sample + * 程序功能:通过 ADC 设备采样电压值并转换为数值。 + * 示例代码参考电压为3.3V,转换位数为12位。 +*/ + +#include +#include + +#define ADC_DEV_NAME "adc1" /* ADC 设备名称 */ +#define ADC_DEV_CHANNEL 5 /* ADC 通道 */ +#define REFER_VOLTAGE 330 /* 参考电压 3.3V,数据精度乘以100保留2位小数*/ +#define CONVERT_BITS (1 << 12) /* 转换位数为12位 */ + +static int adc_vol_sample(int argc, char *argv[]) +{ + rt_adc_device_t adc_dev; + rt_uint32_t value, vol; + rt_err_t ret = RT_EOK; + + /* 查找设备 */ + adc_dev = (rt_adc_device_t)rt_device_find(ADC_DEV_NAME); + if (adc_dev == RT_NULL) + { + rt_kprintf("adc sample run failed! can't find %s device!\n", ADC_DEV_NAME); + return RT_ERROR; + } + + /* 使能设备 */ + ret = rt_adc_enable(adc_dev, ADC_DEV_CHANNEL); + + /* 读取采样值 */ + value = rt_adc_read(adc_dev, ADC_DEV_CHANNEL); + rt_kprintf("the value is :%d \n", value); + + /* 转换为对应电压值 */ + vol = value * REFER_VOLTAGE / CONVERT_BITS; + rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100); + + /* 关闭通道 */ + ret = rt_adc_disable(adc_dev, ADC_DEV_CHANNEL); + + return ret; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(adc_vol_sample, adc voltage convert sample); +``` + +## 常见问题 + +### Q: menuconfig 找不到 ADC 设备的配置选项? + + **A:** 使用的源代码还不支持 ADC 设备驱动框架。建议更新源代码。 + \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/adc-p.png b/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/adc-p.png new file mode 100644 index 0000000000000000000000000000000000000000..8a36ca066a8c590cd05214f8bfe3a8a6acc080c5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/adc-p.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/adc.vsdx b/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/adc.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..e7b912f110cd5fb4238d0af3aa805cc0a4550aeb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/adc.vsdx differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/open_other.png b/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/open_other.png new file mode 100644 index 0000000000000000000000000000000000000000..018fc722205ed8ef01785abb29c6535ffaaa39b5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/adc/figures/open_other.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/audio/audio.md b/rt-thread-version/rt-thread-standard/programming-manual/device/audio/audio.md new file mode 100644 index 0000000000000000000000000000000000000000..b574891fc1f80bf4a1362e7ac89cef44dd9347d3 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/audio/audio.md @@ -0,0 +1,544 @@ +# AUDIO 设备 + +## Audio 简介 + +Audio (音频)设备是嵌入式系统中非常重要的一个组成部分,负责音频数据的采样和输出。Audio 设备通常由数据总线接口、控制总线接口、音频编解码器(Codec)、扬声器和麦克风等组成,如下图所示: + +![嵌入式音频系统组成](figures/audio_system.png) + +### Audio 设备特性 + +RT-Thread Audio 设备驱动框架是 Audio 框架的底层部分,主要负责原生音频数据的采集和输出、音频流的控制、音频设备的管理、音量调节以及不同硬件和 Codec 的抽象等。 + +- 接口:标准 device 接口(open/close/read/control)。 +- 同步模式访问。 +- 支持播放和录音。 +- 支持音频参数管理。 +- 支持音量调节。 + +## 访问 Audio 设备 + +### 查找 Audio 设备 + +应用程序根据 Audio 设备名称获取设备句柄,进而可以操作 Audio 设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | Audio 设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +使用示例如下所示: +```c +#define SOUND_DEVICE_NAME "sound0" /* Audio 设备名称 */ + +static rt_device_t snd_dev; /* Audio 设备句柄 */ + +/* 根据设备名称查找 Audio 设备,获取设备句柄 */ +snd_dev = rt_device_find(SOUND_DEVICE_NAME); +``` + +### 打开 Audio 设备 + +通过设备句柄,应用程序可以打开和关闭设备,通过如下函数打开设备: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | 设备句柄 | +| oflags | 设备模式标志 | +| **返回** | —— | +| RT_EOK | 设备打开成功 | +| -RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开 | +| -RT_EINVAL | 不支持的打开参数 | +| 其他错误码 | 设备打开失败 | + +oflags 参数支持下列参数: + +```c +#define RT_DEVICE_OFLAG_WRONLY 0x002 /* 标准设备的只写模式,对应 Audio 播放设备 */ +#define RT_DEVICE_FLAG_RDONLY 0x001 /* 标准设备的只读模式,对应 Audio 录音设备 */ +``` + +Audio 设备分为播放和录音 2 种类型,播放设备输出音频数据到 Codec 编解码器,录音设备则读取数据。在使用的时候,播放设备通过只写标志进行标识,录音设备通过只读标志进行标识。 + +打开 Audio 播放设备使用示例如下所示: +```c +rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY) +``` + +打开 Audio 录音设备使用示例如下所示: +```c +rt_device_open(mic_dev, RT_DEVICE_FLAG_RDONLY) +``` + +### 控制 Audio 设备 + +通过命令控制字,应用程序可以对 Audio 设备进行配置,通过如下函数完成: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| cmd | 命令控制字,详细介绍见下面 | +| arg | 控制的参数, 详细介绍见下面 | +| **返回** | —— | +| RT_EOK | 函数执行成功 | +| -RT_ENOSYS | 执行失败,dev 为空 | +| 其他错误码 | 执行失败 | + +其中的 cmd 目前支持以下几种命令控制字 + +```c +/* AUDIO command */ +#define _AUDIO_CTL(a) (0x10 + a) + +#define AUDIO_CTL_GETCAPS _AUDIO_CTL(1) /* 获取设备功能属性 */ +#define AUDIO_CTL_CONFIGURE _AUDIO_CTL(2) /* 配置设备功能属性 */ + +``` + +- 设备功能属性结构体的定义如下 + +```c +struct rt_audio_caps +{ + int main_type; /* 命令主类型 */ + int sub_type; /* 命令子类型 */ + + union + { + rt_uint32_t mask; + int value; /* 参数值 */ + struct rt_audio_configure config; /* 音频参数信息 */ + } udata; +}; +``` + +#### 设置播放的音频参数信息 + +设置播放的采样率、采样通道、以及采样位数。 + +```c +struct rt_audio_caps caps; + +caps.main_type = AUDIO_TYPE_OUTPUT; /* 输出类型(播放设备 )*/ +caps.sub_type = AUDIO_DSP_PARAM; /* 设置所有音频参数信息 */ +caps.udata.config.samplerate = 44100; /* 采样率 */ +caps.udata.config.channels = 2; /* 采样通道 */ +caps.udata.config.samplebits = 16; /* 采样位数 */ +rt_device_control(device, AUDIO_CTL_CONFIGURE, &caps); + +``` + +#### 设置播放的主音量 + +设置播放的主音量。 + +```c +struct rt_audio_caps caps; + +caps.main_type = AUDIO_TYPE_MIXER; /* 音量管理类型 */ +caps.sub_type = AUDIO_MIXER_VOLUME; /* 设置播放的主音量 */ +caps.udata.value = volume; /* 范围 0 ~ 100 */ +rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps); + +``` + +#### 设置录音的音频参数信息 + +设置录音的采样率、采样通道、以及采样位数。 + +```c +struct rt_audio_caps caps; + +caps.main_type = AUDIO_TYPE_INPUT; /* 输入类型(录音设备 )*/ +caps.sub_type = AUDIO_DSP_PARAM; /* 设置所有音频参数信息 */ +caps.udata.config.samplerate = 44100; /* 采样率 */ +caps.udata.config.channels = 2; /* 采样通道 */ +caps.udata.config.samplebits = 16; /* 采样位数 */ +rt_device_control(device, AUDIO_CTL_CONFIGURE, &caps); + +``` + +#### 设置录音的主音量 + +设置录音的主音量。 + +```c +struct rt_audio_caps caps; + +caps.main_type = AUDIO_TYPE_MIXER; /* 音量管理类型 */ +caps.sub_type = AUDIO_MIXER_MIC; /* 设置录音的主音量 */ +caps.udata.value = volume; /* 范围 0 ~ 100 */ +rt_device_control(player->device, AUDIO_CTL_CONFIGURE, &caps); + +``` + +### 写入音频数据 + +向音频播放设备中写入数据,可以通过如下函数完成: + +```c +rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| pos | 写入数据偏移量,此参数音频设备未使用 | +| buffer | 内存缓冲区指针,放置要写入的数据 | +| size | 写入数据的大小 | +| **返回** | —— | +| 写入数据的实际大小 | 以字节为单位; | + +调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的大小是 size。该函数为同步接口,驱动框架内部会将数据先保存到音频设备的缓冲区,当缓冲区满时,函数被阻塞。 + +### 读取音频数据 + +可调用如下函数读取音频录音设备接收到的数据: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ------------------ | ---------------------------------------------- | +| dev | 设备句柄 | +| pos | 读取数据偏移量,此参数串口设备未使用 | +| buffer | 缓冲区指针,读取的数据将会被保存在缓冲区中 | +| size | 读取数据的大小 | +| **返回** | —— | +| 读到数据的实际大小 | 如果是字符设备,返回大小以字节为单位 | +| 0 | 需要读取当前线程的 errno 来判断错误状态 | + +调用这个函数,从音频录音设备读取 size 大小的数据到 buffer 中。该函数为同步接口,当驱动框架内部 Pipe缓存的数据小于 size 时,函数被阻塞。 + +### 关闭音频设备 + +当应用程序完成串口操作后,可以关闭音频设备,通过如下函数完成: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------- | +| dev | 设备句柄 | +| **返回** | —— | +| RT_EOK | 关闭设备成功 | +| -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + +关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + +## 音频设备使用示例 + +音频设备用于播放和录音,通常伴随着音频文件的编解码使用。下面介绍播放和录制 wav 文件的示例,完整的代码可以通过 [RT-Thread wavplayer 软件包](https://github.com/RT-Thread-packages/wavplayer) 获取。 + +### 播放 + +播放一段音频数据的主要步骤如下: + +1. 首先查找 Audio 设备获取设备句柄。 + +2. 以只写方式打开 Audio 设备。 + +3. 设置音频参数信息(采样率、通道等)。 + +4. 解码音频文件的数据。 + +5. 写入音频文件数据。 + +6. 播放完成,关闭设备。 + +```c +#include +#include +#include + +#define BUFSZ 1024 +#define SOUND_DEVICE_NAME "sound0" /* Audio 设备名称 */ +static rt_device_t snd_dev; /* Audio 设备句柄 */ + +struct RIFF_HEADER_DEF +{ + char riff_id[4]; // 'R','I','F','F' + uint32_t riff_size; + char riff_format[4]; // 'W','A','V','E' +}; + +struct WAVE_FORMAT_DEF +{ + uint16_t FormatTag; + uint16_t Channels; + uint32_t SamplesPerSec; + uint32_t AvgBytesPerSec; + uint16_t BlockAlign; + uint16_t BitsPerSample; +}; + +struct FMT_BLOCK_DEF +{ + char fmt_id[4]; // 'f','m','t',' ' + uint32_t fmt_size; + struct WAVE_FORMAT_DEF wav_format; +}; + +struct DATA_BLOCK_DEF +{ + char data_id[4]; // 'R','I','F','F' + uint32_t data_size; +}; + +struct wav_info +{ + struct RIFF_HEADER_DEF header; + struct FMT_BLOCK_DEF fmt_block; + struct DATA_BLOCK_DEF data_block; +}; + +int wavplay_sample(int argc, char **argv) +{ + int fd = -1; + uint8_t *buffer = NULL; + struct wav_info *info = NULL; + struct rt_audio_caps caps = {0}; + + if (argc != 2) + { + rt_kprintf("Usage:\n"); + rt_kprintf("wavplay_sample song.wav\n"); + return 0; + } + + fd = open(argv[1], O_WRONLY); + if (fd < 0) + { + rt_kprintf("open file failed!\n"); + goto __exit; + } + + buffer = rt_malloc(BUFSZ); + if (buffer == RT_NULL) + goto __exit; + + info = (struct wav_info *) rt_malloc(sizeof * info); + if (info == RT_NULL) + goto __exit; + + if (read(fd, &(info->header), sizeof(struct RIFF_HEADER_DEF)) <= 0) + goto __exit; + if (read(fd, &(info->fmt_block), sizeof(struct FMT_BLOCK_DEF)) <= 0) + goto __exit; + if (read(fd, &(info->data_block), sizeof(struct DATA_BLOCK_DEF)) <= 0) + goto __exit; + + rt_kprintf("wav information:\n"); + rt_kprintf("samplerate %d\n", info->fmt_block.wav_format.SamplesPerSec); + rt_kprintf("channel %d\n", info->fmt_block.wav_format.Channels); + + /* 根据设备名称查找 Audio 设备,获取设备句柄 */ + snd_dev = rt_device_find(SOUND_DEVICE_NAME); + + /* 以只写方式打开 Audio 播放设备 */ + rt_device_open(snd_dev, RT_DEVICE_OFLAG_WRONLY); + + /* 设置采样率、通道、采样位数等音频参数信息 */ + caps.main_type = AUDIO_TYPE_OUTPUT; /* 输出类型(播放设备 )*/ + caps.sub_type = AUDIO_DSP_PARAM; /* 设置所有音频参数信息 */ + caps.udata.config.samplerate = info->fmt_block.wav_format.SamplesPerSec; /* 采样率 */ + caps.udata.config.channels = info->fmt_block.wav_format.Channels; /* 采样通道 */ + caps.udata.config.samplebits = 16; /* 采样位数 */ + rt_device_control(snd_dev, AUDIO_CTL_CONFIGURE, &caps); + + while (1) + { + int length; + + /* 从文件系统读取 wav 文件的音频数据 */ + length = read(fd, buffer, BUFSZ); + + if (length <= 0) + break; + + /* 向 Audio 设备写入音频数据 */ + rt_device_write(snd_dev, 0, buffer, length); + } + + /* 关闭 Audio 设备 */ + rt_device_close(snd_dev); + +__exit: + + if (fd >= 0) + close(fd); + + if (buffer) + rt_free(buffer); + + if (info) + rt_free(info); + + return 0; +} + +MSH_CMD_EXPORT(wavplay_sample, play wav file); + +``` + + + +### 录音 + +录制一段音频数据的主要步骤如下: + +1. 首先查找 Audio 设备获取设备句柄。 + +2. 以只读方式打开 Audio 设备。 + +3. 设置音频参数信息(采样率、通道等)。 + +4. 从音频设备读取数据。 + +5. 处理读取的数据等。 + +6. 录音完成,关闭设备。 + +```c +#include +#include +#include + +#define RECORD_TIME_MS 5000 +#define RECORD_SAMPLERATE 16000 +#define RECORD_CHANNEL 2 +#define RECORD_CHUNK_SZ ((RECORD_SAMPLERATE * RECORD_CHANNEL * 2) * 20 / 1000) + +#define SOUND_DEVICE_NAME "mic0" /* Audio 设备名称 */ +static rt_device_t mic_dev; /* Audio 设备句柄 */ + +struct wav_header +{ + char riff_id[4]; /* "RIFF" */ + int riff_datasize; /* RIFF chunk data size,exclude riff_id[4] and riff_datasize,total - 8 */ + char riff_type[4]; /* "WAVE" */ + char fmt_id[4]; /* "fmt " */ + int fmt_datasize; /* fmt chunk data size,16 for pcm */ + short fmt_compression_code; /* 1 for PCM */ + short fmt_channels; /* 1(mono) or 2(stereo) */ + int fmt_sample_rate; /* samples per second */ + int fmt_avg_bytes_per_sec; /* sample_rate * channels * bit_per_sample / 8 */ + short fmt_block_align; /* number bytes per sample, bit_per_sample * channels / 8 */ + short fmt_bit_per_sample; /* bits of each sample(8,16,32). */ + char data_id[4]; /* "data" */ + int data_datasize; /* data chunk size,pcm_size - 44 */ +}; + +static void wavheader_init(struct wav_header *header, int sample_rate, int channels, int datasize) +{ + memcpy(header->riff_id, "RIFF", 4); + header->riff_datasize = datasize + 44 - 8; + memcpy(header->riff_type, "WAVE", 4); + memcpy(header->fmt_id, "fmt ", 4); + header->fmt_datasize = 16; + header->fmt_compression_code = 1; + header->fmt_channels = channels; + header->fmt_sample_rate = sample_rate; + header->fmt_bit_per_sample = 16; + header->fmt_avg_bytes_per_sec = header->fmt_sample_rate * header->fmt_channels * header->fmt_bit_per_sample / 8; + header->fmt_block_align = header->fmt_bit_per_sample * header->fmt_channels / 8; + memcpy(header->data_id, "data", 4); + header->data_datasize = datasize; +} + +int wavrecord_sample(int argc, char **argv) +{ + int fd = -1; + uint8_t *buffer = NULL; + struct wav_header header; + struct rt_audio_caps caps = {0}; + int length, total_length = 0; + + if (argc != 2) + { + rt_kprintf("Usage:\n"); + rt_kprintf("wavrecord_sample file.wav\n"); + return -1; + } + + fd = open(argv[1], O_WRONLY | O_CREAT); + if (fd < 0) + { + rt_kprintf("open file for recording failed!\n"); + return -1; + } + write(fd, &header, sizeof(struct wav_header)); + + buffer = rt_malloc(RECORD_CHUNK_SZ); + if (buffer == RT_NULL) + goto __exit; + + /* 根据设备名称查找 Audio 设备,获取设备句柄 */ + mic_dev = rt_device_find(SOUND_DEVICE_NAME); + if (mic_dev == RT_NULL) + goto __exit; + + /* 以只读方式打开 Audio 录音设备 */ + rt_device_open(mic_dev, RT_DEVICE_OFLAG_RDONLY); + + /* 设置采样率、通道、采样位数等音频参数信息 */ + caps.main_type = AUDIO_TYPE_INPUT; /* 输入类型(录音设备 )*/ + caps.sub_type = AUDIO_DSP_PARAM; /* 设置所有音频参数信息 */ + caps.udata.config.samplerate = RECORD_SAMPLERATE; /* 采样率 */ + caps.udata.config.channels = RECORD_CHANNEL; /* 采样通道 */ + caps.udata.config.samplebits = 16; /* 采样位数 */ + rt_device_control(mic_dev, AUDIO_CTL_CONFIGURE, &caps); + + while (1) + { + /* 从 Audio 设备中,读取 20ms 的音频数据 */ + length = rt_device_read(mic_dev, 0, buffer, RECORD_CHUNK_SZ); + + if (length) + { + /* 写入音频数据到到文件系统 */ + write(fd, buffer, length); + total_length += length; + } + + if ((total_length / RECORD_CHUNK_SZ) > (RECORD_TIME_MS / 20)) + break; + } + + /* 重新写入 wav 文件的头 */ + wavheader_init(&header, RECORD_SAMPLERATE, RECORD_CHANNEL, total_length); + lseek(fd, 0, SEEK_SET); + write(fd, &header, sizeof(struct wav_header)); + close(fd); + + /* 关闭 Audio 设备 */ + rt_device_close(mic_dev); + +__exit: + if (fd >= 0) + close(fd); + + if (buffer) + rt_free(buffer); + + return 0; +} +MSH_CMD_EXPORT(wavrecord_sample, record voice to a wav file); +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/audio/figures/audio_system.png b/rt-thread-version/rt-thread-standard/programming-manual/device/audio/figures/audio_system.png new file mode 100644 index 0000000000000000000000000000000000000000..d58bd3a1980b6104e4442c9cd9dc911db590c165 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/audio/figures/audio_system.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/can/can.md b/rt-thread-version/rt-thread-standard/programming-manual/device/can/can.md new file mode 100644 index 0000000000000000000000000000000000000000..0db30bee5cbe82af085df0bcfdf9b9cdbe2506da --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/can/can.md @@ -0,0 +1,615 @@ +# CAN 设备 + +## CAN 简介 + +CAN 是控制器局域网络 (Controller Area Network, CAN) 的简称,是由以研发和生产汽车电子产品著称的德国 BOSCH 公司开发的,并最终成为国际标准(ISO 11898),是国际上应用最广泛的现场总线之一。 + +CAN 控制器根据两根线上的电位差来判断总线电平。总线电平分为显性电平和隐性电平,二者必居其一。发送方通过使总线电平发生变化,将消息发送给接收方。 CAN 的连接示意图如下图所示: + +![CAN 连接图](figures/can-link.png) + +CAN 总线有如下特点: + + * CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。 + * 多主控制。在总线空闲时,所有的单元都可开始发送消息(多主控制)。多个单元同时开始发送时,发送高优先级 ID 消息的单元可获得发送权。 + * 消息的发送。在 CAN 协议中,所有的消息都以固定的格式发送。总线空闲时,所有与总线相连的单元都可以开始发送新消息。两个以上的单元同时开始发送消息时,根据标识符 ID 决定优先级。ID 表示访问总线的消息的优先级。两个以上的单元同时开始发送消息时,对各消息 ID 的每个位进行逐个仲裁比较。仲裁获胜(被判定为优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。 + * 根据整个网络的规模,可设定适合的通信速度。在同一网络中,所有单元必须设定成统一的通信速度。即使有一个单元的通信速度与其它的不一样,此单元也会输出错误信号,妨碍整个网络的通信。不同网络间则可以有不同的通信速度。 + + CAN 协议包括 5 种类型的帧: + + * 数据帧 + * 遥控帧 + * 错误帧 + * 过载帧 + * 帧间隔 + +数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有 11 个位的 ID,扩展格式有 29 个位的 ID。 + +各种帧的用途如下表所示: + +| 帧 | 帧用途 | +| --------------------------- | -------------------------- | +| 数据帧 |用于发送单元向接收单元传送数据的帧 | +| 遥控帧 | 用于接收单元向具有相同 ID 的发送单元请求数据的帧 | +| 错误帧 | 用于当检测出错误时向其它单元通知错误的帧 | +| 过载帧 |用于接收单元通知其尚未做好接收准备的帧 | +| 帧间隔 |用于将数据帧及遥控帧与前面的帧分离开来的帧 | + +## 访问 CAN 设备 + +应用程序通过 RT-Thread 提供的 I/O 设备管理接口来访问 CAN 硬件控制器,相关接口如下所示: + +| 函数 | 描述 | +| --------------------------- | -------------------------- | +| rt_device_find | 查找设备 | +| rt_device_open | 打开设备 | +| rt_device_read | 读取数据 | +| rt_device_write | 写入数据 | +| rt_device_control | 控制设备 | +| rt_device_set_rx_indicate | 设置接收回调函数 | +| rt_device_close | 关闭设备 | + +### 查找 CAN 设备 + +应用程序根据 CAN 设备名称查找设备获取设备句柄,进而可以操作 CAN 设备,查找设备函数如下所示, + +```c +rt_device_t rt_device_find(const char* name); +``` + +| 参数 | 描述 | +| -------- | ---------------------------------- | +| name | 设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +一般情况下,注册到系统的 CAN 设备名称为 can1,can2 等,使用示例如下所示: + +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static rt_device_t can_dev; /* CAN 设备句柄 */ +/* 查找 CAN 设备 */ +can_dev = rt_device_find(CAN_DEV_NAME); +``` + +### 打开 CAN 设备 + +通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +| 参数 | 描述 | +| ---------- | ------------------------------- | +| dev | 设备句柄 | +| oflags | 打开设备模式标志 | +| **返回** | —— | +| RT_EOK | 设备打开成功 | +| -RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开 | +| 其他错误码 | 设备打开失败 | + +目前 RT-Thread CAN 设备驱动框架支持中断接收和中断发送模式。oflags 参数支持下列取值 (可以采用或的方式支持多种取值): + +```c +#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收模式 */ +#define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送模式 */ +``` + +以中断接收及发送模式打开 CAN 设备的示例如下所示: + +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static rt_device_t can_dev; /* CAN 设备句柄 */ +/* 查找 CAN 设备 */ +can_dev = rt_device_find(CAN_DEV_NAME); +/* 以中断接收及发送模式打开 CAN 设备 */ +rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); +``` + +### 控制 CAN 设备 + +通过命令控制字,应用程序可以对 CAN 设备进行配置,通过如下函数完成: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| 参数 | 描述 | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| cmd | 控制命令 | +| arg | 控制参数 | +| **返回** | —— | +| RT_EOK | 函数执行成功 | +| 其他错误码 | 执行失败 | + +arg(控制参数)根据命令不同而不同,cmd(控制命令)可取以下值: + +```c +#define RT_DEVICE_CTRL_RESUME 0x01 /* 恢复设备 */ +#define RT_DEVICE_CTRL_SUSPEND 0x02 /* 挂起设备 */ +#define RT_DEVICE_CTRL_CONFIG 0x03 /* 配置设备 */ + +#define RT_CAN_CMD_SET_FILTER 0x13 /* 设置硬件过滤表 */ +#define RT_CAN_CMD_SET_BAUD 0x14 /* 设置波特率 */ +#define RT_CAN_CMD_SET_MODE 0x15 /* 设置 CAN 工作模式 */ +#define RT_CAN_CMD_SET_PRIV 0x16 /* 设置发送优先级 */ +#define RT_CAN_CMD_GET_STATUS 0x17 /* 获取 CAN 设备状态 */ +#define RT_CAN_CMD_SET_STATUS_IND 0x18 /* 设置状态回调函数 */ +#define RT_CAN_CMD_SET_BUS_HOOK 0x19 /* 设置 CAN 总线钩子函数 */ +``` +#### 设置波特率 +设置波特率的示例代码如下所示: + +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static rt_device_t can_dev; /* CAN 设备句柄 */ + +/* 查找 CAN 设备 */ +can_dev = rt_device_find(CAN_DEV_NAME); +/* 以中断接收及发送方式打开 CAN 设备 */ +res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); +/* 设置 CAN 通信的波特率为 500kbit/s*/ +res = rt_device_control(can_dev, RT_CAN_CMD_SET_BAUD, (void *)CAN500kBaud); + +``` + +#### 设置工作模式 + +设置工作模式的示例代码如下所示: +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static rt_device_t can_dev; /* CAN 设备句柄 */ + +/* 查找 CAN 设备 */ +can_dev = rt_device_find(CAN_DEV_NAME); +/* 以中断接收及发送方式打开 CAN 设备 */ +res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); +/* 设置 CAN 的工作模式为正常工作模式 */ +res = rt_device_control(can_dev, RT_CAN_CMD_SET_MODE, (void *)RT_CAN_MODE_NORMAL); +``` +#### 获取 CAN 设备状态 + +获取 CAN 设备状态的示例代码如下所示: +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static rt_device_t can_dev; /* CAN 设备句柄 */ +static struct rt_can_status status; /* 获取到的 CAN 总线状态 */ + +/* 查找 CAN 设备 */ +can_dev = rt_device_find(CAN_DEV_NAME); +/* 以中断接收及发送方式打开 CAN 设备 */ +res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); +/* 获取 CAN 总线设备的状态 */ +res = rt_device_control(can_dev, RT_CAN_CMD_GET_STATUS, &status); +``` + +#### 设置硬件过滤表 + +过滤表控制块各成员描述如下所示: +```c +struct rt_can_filter_item +{ + rt_uint32_t id : 29; /* 报文 ID */ + rt_uint32_t ide : 1; /* 扩展帧标识位 */ + rt_uint32_t rtr : 1; /* 远程帧标识位 */ + rt_uint32_t mode : 1; /* 过滤表模式 */ + rt_uint32_t mask; /* ID 掩码,0 表示对应的位不关心,1 表示对应的位必须匹配 */ + rt_int32_t hdr; /* -1 表示不指定过滤表号,对应的过滤表控制块也不会被初始化,正数为过滤表号,对应的过滤表控制块会被初始化 */ +#ifdef RT_CAN_USING_HDR + /* 过滤表回调函数 */ + rt_err_t (*ind)(rt_device_t dev, void *args , rt_int32_t hdr, rt_size_t size); + /* 回调函数参数 */ + void *args; +#endif /*RT_CAN_USING_HDR*/ +}; +``` + +如果需要过滤的报文 ID 为 0x01 的标准数据帧,使用默认过滤表,则过滤表各个成员设置如下: +```c +struct rt_can_filter_item filter; +/* 报文 ID */ +filter.id = 0x01; +/* 标准格式 */ +filter.ide = 0x00; +/* 数据帧 */ +filter.rtr = 0x00; +/* 过滤表模式 */ +filter.mode = 0x01; +/* 匹配 ID */ +filter.mask = 0x01; +/* 使用默认过滤表 */ +filter.hdr = -1; +``` + +为了方便表示过滤表的各个成员变量的值, RT-Thread 系统提供了匹配过滤表的宏 + +```c +#define RT_CAN_FILTER_ITEM_INIT(id,ide,rtr,mode,mask,ind,args) \ + {(id), (ide), (rtr), (mode), (mask), -1, (ind), (args)} +``` +过滤表宏中各个位分别和过滤表结构体成员变量一一对应,只是使用的过滤表是默认的过滤表。 + +则上述过滤信息使用过滤表的宏可以表示为 + +```c +RT_CAN_FILTER_ITEM_INIT(0x01, 0, 0, 1, 0x01, RT_NULL, RT_NULL); +``` + +当需要使用过滤表时还需要指定过滤表配置控制块的成员变量,过滤表的配置控制块成员变量的组成如下所示: + +```c +struct rt_can_filter_config +{ + rt_uint32_t count; /* 过滤表数量 */ + rt_uint32_t actived; /* 过滤表激活选项,1 表示初始化过滤表控制块,0 表示去初始化过滤表控制块 */ + struct rt_can_filter_item *items; /* 过滤表指针,可指向一个过滤表数组 */ +}; +``` + +设置硬件过滤表示例代码如下所示: + +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static rt_device_t can_dev; /* CAN 设备句柄 */ + +can_dev = rt_device_find(CAN_DEV_NAME); + +/* 以中断接收及发送模式打开 CAN 设备 */ +rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); + +struct rt_can_filter_item items[1] = +{ + RT_CAN_FILTER_ITEM_INIT(0x01, 0, 0, 1, 0x01, RT_NULL, RT_NULL), + /* 过滤 ID 为 0x01,match ID:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */ +}; +struct rt_can_filter_config cfg = {1, 1, items}; /* 一共有 1 个过滤表 */ +/* 设置硬件过滤表 */ +res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg); +``` + + + +### 发送数据 + +使用 CAN 设备发送数据,可以通过如下函数完成: + +```c +rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size); +``` + +| 参数 | 描述 | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| pos | 写入数据偏移量,此参数 CAN 设备未使用 | +| buffer | CAN 消息指针 | +| size | CAN 消息大小 | +| **返回** | —— | +| 不为 0 | 实际发送的 CAN 消息大小 | +| 0 | 发送失败 | + +CAN 消息原型如下所示: + +```c +struct rt_can_msg +{ + rt_uint32_t id : 29; /* CAN ID, 标志格式 11 位,扩展格式 29 位 */ + rt_uint32_t ide : 1; /* 扩展帧标识位 */ + rt_uint32_t rtr : 1; /* 远程帧标识位 */ + rt_uint32_t rsv : 1; /* 保留位 */ + rt_uint32_t len : 8; /* 数据段长度 */ + rt_uint32_t priv : 8; /* 报文发送优先级 */ + rt_uint32_t hdr : 8; /* 硬件过滤表号 */ + rt_uint32_t reserved : 8; + rt_uint8_t data[8]; /* 数据段 */ +}; +``` + +使用 CAN 设备发送数据示例程序如下所示: + +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static rt_device_t can_dev; /* CAN 设备句柄 */ +struct rt_can_msg msg = {0}; /* CAN 消息 */ + +can_dev = rt_device_find(CAN_DEV_NAME); + +/* 以中断接收及发送模式打开 CAN 设备 */ +rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); + +msg.id = 0x78; /* ID 为 0x78 */ +msg.ide = RT_CAN_STDID; /* 标准格式 */ +msg.rtr = RT_CAN_DTR; /* 数据帧 */ +msg.len = 8; /* 数据长度为 8 */ +/* 待发送的 8 字节数据 */ +msg.data[0] = 0x00; +msg.data[1] = 0x11; +msg.data[2] = 0x22; +msg.data[3] = 0x33; +msg.data[4] = 0x44; +msg.data[5] = 0x55; +msg.data[6] = 0x66; +msg.data[7] = 0x77; +/* 发送一帧 CAN 数据 */ +size = rt_device_write(can_dev, 0, &msg, sizeof(msg)); +``` + +### 设置接收回调函数 + +可以通过如下函数来设置数据接收指示,当 CAN 收到数据时,通知上层应用线程有数据到达 : + +```c +rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size)); +``` + +| 参数 | 描述 | +| -------- | ------------ | +| dev | 设备句柄 | +| rx_ind | 回调函数指针 | +| dev | 设备句柄(回调函数参数)| +| size | 缓冲区数据大小(回调函数参数)| +| **返回** | —— | +| RT_EOK | 设置成功 | + +该函数的回调函数由调用者提供。CAN 设备在中断接收模式下,当 CAN 接收到一帧数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在 size 参数里,把 CAN 设备句柄放在 dev 参数里供调用者获取。 + +一般情况下接收回调函数可以发送一个信号量或者事件通知 CAN 数据处理线程有数据到达。使用示例如下所示: + +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ +static rt_device_t can_dev; /* CAN 设备句柄 */ +struct rt_can_msg msg = {0}; /* CAN 消息 */ + +/* 接收数据回调函数 */ +static rt_err_t can_rx_call(rt_device_t dev, rt_size_t size) +{ + /* CAN 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ + rt_sem_release(&rx_sem); + + return RT_EOK; +} + +/* 设置接收回调函数 */ +rt_device_set_rx_indicate(can_dev, can_rx_call); +``` + +### 接收数据 + +可调用如下函数读取 CAN 设备接收到的数据: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +``` + +| 参数 | 描述 | +| ------------------ | ---------------------------------------------- | +| dev | 设备句柄 | +| pos | 读取数据偏移量,此参数 CAN 设备未使用 | +| buffer | CAN 消息指针,读取的数据将会被保存在缓冲区中 | +| size | CAN 消息大小 | +| **返回** | —— | +| 不为 0 | CAN 消息大小 | +| 0 | 失败 | + +> [!NOTE] +> 注:接收数据时 CAN 消息的 hdr 参数必须要指定值,默认指定为 -1 就可以,表示从接收数据的 uselist 链表读取数据。也可以指定为硬件过滤表号的值,表示此次读取数据从哪一个硬件过滤表对应的消息链接读取数据,此时需要设置硬件过滤表的时候 hdr 有指定正确的过滤表号。如果设置硬件过滤表的时候 hdr 都为 -1,则读取数据的时候也要赋值为-1。 + +CAN 使用中断接收模式并配合接收回调函数的使用示例如下所示: + +```c +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static rt_device_t can_dev; /* CAN 设备句柄 */ +struct rt_can_msg rxmsg = {0}; /* CAN 接收消息缓冲区 */ + +/* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */ +rxmsg.hdr = -1; + +/* 阻塞等待接收信号量 */ +rt_sem_take(&rx_sem, RT_WAITING_FOREVER); +/* 从 CAN 读取一帧数据 */ +rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg)); +``` + +### 关闭 CAN 设备 + +当应用程序完成 CAN 操作后,可以关闭 CAN 设备,通过如下函数完成: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| 参数 | 描述 | +| ---------- | ---------------------------------- | +| dev | 设备句柄 | +| **返回** | —— | +| RT_EOK | 关闭设备成功 | +| -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + +关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + +## CAN 设备使用示例 + +示例代码的主要步骤如下所示: + +1. 首先查找 CAN 设备获取设备句柄。 + +2. 初始化信号量,然后以中断接收及中断发送方式打开 CAN 设备。 + +3. 创建读取数据线程。 + +4. 发送一帧 CAN 数据。 + +* 读取数据线程首先会设置接收回调函数,然后设置硬件过滤表,之后会等待信号量。当 CAN 设备接收到一帧数据时会触发中断并调用接收回调函数,此函数会发送信号量唤醒线程,此时线程会马上读取接收到的数据。 + +* 此示例代码不局限于特定的 BSP,根据 BSP 注册的 CAN 设备,修改示例代码宏定义 CAN_DEV_NAME 对应的 CAN 设备名称即可运行。 + +运行序列图如下图所示: + +![CAN 中断接收及发送序列图](figures/can-init.png) + +程序运行起来后在命令行输入 `can_sample` 即可运行示例代码,后面数据为 CAN 设备接收到的数据: + +```c + \ | / +- RT - Thread Operating System + / | \ 4.0.1 build Jun 24 2019 + 2006 - 2019 Copyright by rt-thread team +msh >can_sample +ID:486 0 11 22 33 0 23 4 86 +ID:111 0 11 22 33 0 23 1 11 +ID:555 0 11 22 33 0 23 5 55 +ID:211 0 11 22 33 0 23 2 11 +ID:344 0 11 22 33 0 23 3 44 +``` + +可以使用 CAN 分析工具连接对应 CAN 设备收发数据,第一帧数据为 CAN 示例代码发送的 ID 为 0X78的数据, 效果如下图所示: + +![CAN 分析工具数据收发过程](figures/can-test.png) + +```c +/* + * 程序清单:这是一个 CAN 设备使用例程 + * 例程导出了 can_sample 命令到控制终端 + * 命令调用格式:can_sample can1 + * 命令解释:命令第二个参数是要使用的 CAN 设备名称,为空则使用默认的 CAN 设备 + * 程序功能:通过 CAN 设备发送一帧,并创建一个线程接收数据然后打印输出。 +*/ + +#include +#include "rtdevice.h" + +#define CAN_DEV_NAME "can1" /* CAN 设备名称 */ + +static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */ +static rt_device_t can_dev; /* CAN 设备句柄 */ + +/* 接收数据回调函数 */ +static rt_err_t can_rx_call(rt_device_t dev, rt_size_t size) +{ + /* CAN 接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ + rt_sem_release(&rx_sem); + + return RT_EOK; +} + +static void can_rx_thread(void *parameter) +{ + int i; + rt_err_t res; + struct rt_can_msg rxmsg = {0}; + + /* 设置接收回调函数 */ + rt_device_set_rx_indicate(can_dev, can_rx_call); + +#ifdef RT_CAN_USING_HDR + struct rt_can_filter_item items[5] = + { + RT_CAN_FILTER_ITEM_INIT(0x100, 0, 0, 1, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x100~0x1ff,hdr 为 - 1,设置默认过滤表 */ + RT_CAN_FILTER_ITEM_INIT(0x300, 0, 0, 1, 0x700, RT_NULL, RT_NULL), /* std,match ID:0x300~0x3ff,hdr 为 - 1 */ + RT_CAN_FILTER_ITEM_INIT(0x211, 0, 0, 1, 0x7ff, RT_NULL, RT_NULL), /* std,match ID:0x211,hdr 为 - 1 */ + RT_CAN_FILTER_STD_INIT(0x486, RT_NULL, RT_NULL), /* std,match ID:0x486,hdr 为 - 1 */ + {0x555, 0, 0, 1, 0x7ff, 7,} /* std,match ID:0x555,hdr 为 7,指定设置 7 号过滤表 */ + }; + struct rt_can_filter_config cfg = {5, 1, items}; /* 一共有 5 个过滤表 */ + /* 设置硬件过滤表 */ + res = rt_device_control(can_dev, RT_CAN_CMD_SET_FILTER, &cfg); + RT_ASSERT(res == RT_EOK); +#endif + + while (1) + { + /* hdr 值为 - 1,表示直接从 uselist 链表读取数据 */ + rxmsg.hdr = -1; + /* 阻塞等待接收信号量 */ + rt_sem_take(&rx_sem, RT_WAITING_FOREVER); + /* 从 CAN 读取一帧数据 */ + rt_device_read(can_dev, 0, &rxmsg, sizeof(rxmsg)); + /* 打印数据 ID 及内容 */ + rt_kprintf("ID:%x", rxmsg.id); + for (i = 0; i < 8; i++) + { + rt_kprintf("%2x", rxmsg.data[i]); + } + + rt_kprintf("\n"); + } +} + +int can_sample(int argc, char *argv[]) +{ + struct rt_can_msg msg = {0}; + rt_err_t res; + rt_size_t size; + rt_thread_t thread; + char can_name[RT_NAME_MAX]; + + if (argc == 2) + { + rt_strncpy(can_name, argv[1], RT_NAME_MAX); + } + else + { + rt_strncpy(can_name, CAN_DEV_NAME, RT_NAME_MAX); + } + /* 查找 CAN 设备 */ + can_dev = rt_device_find(can_name); + if (!can_dev) + { + rt_kprintf("find %s failed!\n", can_name); + return RT_ERROR; + } + + /* 初始化 CAN 接收信号量 */ + rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); + + /* 以中断接收及发送方式打开 CAN 设备 */ + res = rt_device_open(can_dev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); + RT_ASSERT(res == RT_EOK); + /* 创建数据接收线程 */ + thread = rt_thread_create("can_rx", can_rx_thread, RT_NULL, 1024, 25, 10); + if (thread != RT_NULL) + { + rt_thread_startup(thread); + } + else + { + rt_kprintf("create can_rx thread failed!\n"); + } + + msg.id = 0x78; /* ID 为 0x78 */ + msg.ide = RT_CAN_STDID; /* 标准格式 */ + msg.rtr = RT_CAN_DTR; /* 数据帧 */ + msg.len = 8; /* 数据长度为 8 */ + /* 待发送的 8 字节数据 */ + msg.data[0] = 0x00; + msg.data[1] = 0x11; + msg.data[2] = 0x22; + msg.data[3] = 0x33; + msg.data[4] = 0x44; + msg.data[5] = 0x55; + msg.data[6] = 0x66; + msg.data[7] = 0x77; + /* 发送一帧 CAN 数据 */ + size = rt_device_write(can_dev, 0, &msg, sizeof(msg)); + if (size == 0) + { + rt_kprintf("can dev write data failed!\n"); + } + + return res; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(can_sample, can device sample); +``` + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-data-frame.png b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-data-frame.png new file mode 100644 index 0000000000000000000000000000000000000000..09e2efd55eaef1b5ec199ef9d331980b9544c75d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-data-frame.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-init.png b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-init.png new file mode 100644 index 0000000000000000000000000000000000000000..927801fc55dccd894d207157363b22b5934eea44 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-init.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-int b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-int new file mode 100644 index 0000000000000000000000000000000000000000..a7ff6cfc634004df451b71753fad0e8f5babc11d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-int @@ -0,0 +1,19 @@ +@startuml + +participant Ӧó +participant ISR +participant CANӲ + +Ӧó->Ӧó: ʼź +Ӧó->Ӧó: ݴ߳ +Ӧó->Ӧó: ýջص +Ӧó->Ӧó: һ֡ CAN +Ӧó->Ӧó: ݴ߳ȴź + +CANӲ->ISR: յһ֡ݲCAN ж + +ISR->Ӧó: ISRݷ뻺,ڽջصзź߳ + +Ӧó->Ӧó: ݴ̻߳ȡźӻȡһ֡ + +@enduml \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-link.png b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-link.png new file mode 100644 index 0000000000000000000000000000000000000000..d027c50a8cb90380dc6f9304142448d41c0acb2e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-link.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-test.png b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-test.png new file mode 100644 index 0000000000000000000000000000000000000000..4f1f8e3a06aa8757609314436fa119f94392322f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can-test.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can.vsdx b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..622f618bac9e129d1e0afec15409a720e77dd9a8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/can/figures/can.vsdx differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/crypto/crypto.md b/rt-thread-version/rt-thread-standard/programming-manual/device/crypto/crypto.md new file mode 100644 index 0000000000000000000000000000000000000000..2252444e94c4b5d3a1046673b062a33726ba321f --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/crypto/crypto.md @@ -0,0 +1,1336 @@ +# CRYPTO 设备 + +加解密(Encryption/Decryption)是一种文件、消息加密、解密技术。这种技术随着物联网快速发展,用户的正常工作中对数据的加解密需求有着强烈的需求。 + +为保证物联网设备的信息安全,软件层面引入了 TLS 安全传输层协议,同时硬件芯片上也逐渐添加安全相关的加解密模块,甚至出现了专为安全设计的安全芯片。芯片上的硬件安全模块相比纯软件实现的安全算法,拥有更快的运算速度,更小的资源占用。但大多数物联网设备上仍在使用纯软件的安全算法。其中最重要的一个原因,就是硬件接口不一,种类繁杂,软件对接起来比较困难。 + +因此 RT-Thread 推出了 hwcrypto 硬件加解密驱动框架,并对接了常见的安全传输套件。只要硬件支持加解密模块,就能直接使用基于硬件加解密的安全传输套件,传输速度提升数倍。 + +## CRYPTO 简介 + +### 框架简介 + +hwcrypto 是一个硬件加解密设备驱动框架。主要由硬件加解密驱动抽象层以及各种加解密 API 接口两部分构成。对于上层应用,可对接安全套件或直接使用,使用方式十分灵活。对于驱动,需要对接的接口少,功能单一,驱动开发简单快捷。下图是 Crypto 框架层次图: + +![Crypto](figures/framework.png) + +主要**特性**如下: + +- **设计轻薄,运行高效** + +硬件加解密驱动的最重要的一个功能就是接口转换,实现接口统一,方便上层应用使用硬件加解密。所以它被设计的十分轻薄。有着极低资源占用,ROM < 0.8K / RAM < 0.2K。 + +加解密在运行速度上,也有着很高的要求。所以频繁调用的代码,细致考虑,把运行过程的步骤降到最少,让性能损失降到最小,如同直接操作硬件寄存器一般迅速。 + +- **考虑周全,使用简单** + +在 API 设计上,从简单易用,功能齐全两个维度出发。首先是用户直接使用硬件加解密API,要求上手简单, 使用容易。为此前期针对众多软件接口进行了评估。亲身使用测试,最终定义出一套功能齐全,接口简单的 API。 + +满足用户的使用需求的同时,在安全传输套件的对接上,也做了许多考虑。专门为安全传输套件增加了API。最终这套API用户使用或是对接安全传输套件,都能游刃有余。 + +- **完全兼容,通用性强** + +驱动对接的接口设计上,也精心设计。前期对多家硬件厂商的加解密外设根据功能进行分类,进行分析整理,提取出一套功能单一,参数全面的驱动接口。该驱动接口可完全适配常规 MCU 加解密外设。现目前已在多个平台上做验证,例如联盛德 W60X 系列,STM32 系列等。 + +### 功能简介 + +硬件加解密框架目前已经支持 AES/DES/3DES/RC4/SHA1/SHA2/MD5/CRC/RNG/BIGNUM 等加解密相关的接口。 + +将上述加解密算法按照不同的类型分成如下几个大类,每一类都有丰富的 API 可供使用。目前已经支持的类型如下: + +- hash: 散列算法 +- symmetric: 对称加解密算法 +- gcm: GMAC 消息认证码 +- crc: CRC 冗余校验 +- rng: 随机数发生器 +- bignum: 大数运算 + +## hash 算法 + +hash 算法是把任意长度的输入通过散列算法变换成固定长度的输出,是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。 + +### 访问 hash 算法设备 + +应用程序通过 RT-Thread 提供的 hash 算法设备管理接口来访问 hash 算法设备硬件,相关接口如下所示: + +| 函数 | 描述 | +|-----------------------------|--------------------| +| rt_hwcrypto_hash_create() | 创建 hash 上下文 | +| rt_hwcrypto_hash_destroy() | 释放上下文 | +| rt_hwcrypto_hash_finish() | 计算最终 hash 值 | +| rt_hwcrypto_hash_update() | 处理一包数据 | +| rt_hwcrypto_hash_cpy() | 复制上下文 | +| rt_hwcrypto_hash_reset() | 重置上下文 | +| rt_hwcrypto_hash_set_type() | 设置 hash 算法类型 | + +### 创建 hash 上下文 + +应用程序根据 hash 设备的句柄,创建 hash 上下文,如下所示: + +```c +struct rt_hwcrypto_ctx *rt_hwcrypto_hash_create(struct rt_hwcrypto_device *device, + hwcrypto_type type); +``` + +| **参数** | **描述** | +|----------|----------------| +| device | 加解密设备句柄 | +| type | hash 算法类型 | +| **返回** | —— | +| NULL | 失败 | +| 其他 | 设备对象 | + +hash 算法常用类型及子类型如下 + +```c + /* HASH Type */ + HWCRYPTO_TYPE_MD5 = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< MD5 */ + HWCRYPTO_TYPE_SHA1 = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< SHA1 */ + HWCRYPTO_TYPE_SHA2 = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< SHA2 */ + + /* SHA2 Subtype */ + HWCRYPTO_TYPE_SHA224 = HWCRYPTO_TYPE_SHA2 | (0x01 << 8), + HWCRYPTO_TYPE_SHA256 = HWCRYPTO_TYPE_SHA2 | (0x02 << 8), + HWCRYPTO_TYPE_SHA384 = HWCRYPTO_TYPE_SHA2 | (0x03 << 8), + HWCRYPTO_TYPE_SHA512 = HWCRYPTO_TYPE_SHA2 | (0x04 << 8), + +``` + +如果创建 MD5 类型的 hash 算法,使用示例如下 + +```c + struct rt_hwcrypto_ctx *ctx; + + ctx = rt_hwcrypto_hash_create(rt_hwcrypto_dev_default(), HWCRYPTO_TYPE_MD5); +``` + +- rt_hwcrypto_dev_default()函数原型为 `struct rt_hwcrypto_device *rt_hwcrypto_dev_default(void)`,返回默认的硬件加解密设备句柄。 + +### 释放上下文 + +应用程序根据 hash 的上下文,删除该上下文,并且释放资源,如下所示: + +```c +void rt_hwcrypto_hash_destroy(struct rt_hwcrypto_ctx *ctx); +``` + +| **参数** | **描述** | +|----------|----------| +| ctx | 上下文 | +| **返回** | —— | + +### 计算最终 hash 值 + +应用程序根据 hash 的上下文,可以输出最后的 hash 值,如下所示: + +```c +rt_err_t rt_hwcrypto_hash_finish(struct rt_hwcrypto_ctx *ctx, rt_uint8_t *output, rt_size_t length); +``` + +| **参数** | **描述** | +|----------|----------| +| ctx | 上下文 | +| output | 输出数据 | +| length | 数据长度 | +| **返回** | —— | +| RT_EOK | 计算成功 | +| 其他 | 失败 | + +### 计算一包数据 + +应用程序根据 hash 的上下文,输入一包数据,计算 hash 值,如下所示: + +```c +rt_err_t rt_hwcrypto_hash_update(struct rt_hwcrypto_ctx *ctx, const rt_uint8_t *input, rt_size_t length); +``` + +| **参数** | **描述** | +|----------|--------------| +| ctx | 上下文 | +| input | 输入数据 | +| length | 输入数据长度 | +| **返回** | —— | +| RT_EOK | 计算成功 | +| 其他 | 失败 | + +使用实例如下: + +```c +int main(void) +{ + rt_uint8_t buf_in[32]; + int i; + struct rt_hwcrypto_ctx *ctx; + + /* 填充测试数据 */ + for (i = 0; i < sizeof(buf_in); i++) + { + buf_in[i] = (rt_uint8_t)i; + } + /* 创建一个 SHA1/MD5 类型的上下文 */ + ctx = rt_hwcrypto_hash_create(rt_hwcrypto_dev_default(), type); + + /* 将输入数据进行 hash 运算 */ + rt_hwcrypto_hash_update(ctx, in, 32); + /* 获得运算结果 */ + rt_hwcrypto_hash_finish(ctx, out, 32); + /* 删除上下文,释放资源 */ + rt_hwcrypto_hash_destroy(ctx); +} +``` + +### 复制上下文 + +应用程序根据源 hash 的上下文,复制到目标 hash 的上下文中,如下所示: + +```c +rt_err_t rt_hwcrypto_hash_cpy(struct rt_hwcrypto_ctx *des, const struct rt_hwcrypto_ctx *src); +``` + +| **参数** | **描述** | +|----------|------------| +| des | 目标上下文 | +| src | 源上下文 | +| **返回** | —— | +| RT_EOK | 计算成功 | +| 其他 | 失败 | + +### 重置上下文 + +应用程序根据 hash 的上下文,重置 hash 上下文文本,如下所示: + +```c +void rt_hwcrypto_hash_reset(struct rt_hwcrypto_ctx *ctx); +``` + +| **参数** | **描述** | +|----------|----------| +| ctx | 上下文 | +| **返回** | —— | + +- 没有释放上下文之前,只有重置后才可以计算下一包数据 + +### 设置 hash 算法类型 + +应用程序根据 hash 的上下文,设置 hash 算法类型,如下所示: + +```c +rt_err_t rt_hwcrypto_hash_set_type(struct rt_hwcrypto_ctx *ctx, hwcrypto_type type); +``` + +| **参数** | **描述** | +|----------|---------------| +| ctx | 上下文 | +| type | hash 算法类型 | +| **返回** | —— | +| RT_EOK | 计算成功 | +| 其他 | 失败 | + +- 常用 hash 算法类型,参考创建 hash 上下文中类型介绍 +- 设置类型仅在数据运算之前有效,运算中途更改类型,将导致运算结果错误 + +## 对称加解密算法 + +对称加解密指加密和解密使用相同密钥的加密算法,所以也称这种加密算法为秘密密钥算法或单密钥算法。对称算法的安全性依赖于密钥,泄漏密钥就意味着任何人都可以对他们发送或接收的消息解密,所以密钥的保密性对通信的安全性至关重要。 + +### 访问对称加解密设备 + +应用程序通过 RT-Thread 提供的 symmetric 设备管理接口来访问 symmetric 算法设备硬件,相关接口如下所示: + +| 函数 | 描述 | +|-----------------------------------|--------------------------------| +| rt_hwcrypto_symmetric_create() | 创建对称加解密上下文 | +| rt_hwcrypto_symmetric_destroy() | 释放上下文 | +| rt_hwcrypto_symmetric_crypt() | 加解密操作 | +| rt_hwcrypto_symmetric_setkey() | 设置加解密密钥 | +| rt_hwcrypto_symmetric_getkey() | 获取加解密密钥 | +| rt_hwcrypto_symmetric_setiv() | 设置对称加解密初始化向量 | +| rt_hwcrypto_symmetric_getiv() | 获取对称加解密初始化向量 | +| rt_hwcrypto_symmetric_set_ivoff() | 设置对称加解密初始化向量偏移值 | +| rt_hwcrypto_symmetric_get_ivoff() | 获取对称加解密初始化向量偏移值 | +| rt_hwcrypto_symmetric_cpy() | 复制上下文 | +| rt_hwcrypto_symmetric_reset() | 重置上下文 | +| rt_hwcrypto_symmetric_set_type() | 设置加解密类型 | + +### 创建对称加解密上下文 + +应用程序根据对称加解密设备的句柄,创建对称加解密上下文,如下所示: + +```c +struct rt_hwcrypto_ctx *rt_hwcrypto_symmetric_create(struct rt_hwcrypto_device *device, hwcrypto_type type); +``` + +| **参数** | **描述** | +|----------|--------------------| +| device | 加解密设备句柄 | +| type | 对称加解密算法类型 | +| **返回** | —— | +| NULL | 失败 | +| 其他 | 设备对象 | + +对称加解密算法常用类型及子类型如下 + +```c + /* symmetric Type */ + HWCRYPTO_TYPE_AES = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< AES */ + HWCRYPTO_TYPE_DES = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< DES */ + HWCRYPTO_TYPE_3DES = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< 3DES */ + HWCRYPTO_TYPE_RC4 = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< RC4 */ + HWCRYPTO_TYPE_GCM = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< GCM */ + + /* AES Subtype */ + HWCRYPTO_TYPE_AES_ECB = HWCRYPTO_TYPE_AES | (0x01 << 8), + HWCRYPTO_TYPE_AES_CBC = HWCRYPTO_TYPE_AES | (0x02 << 8), + HWCRYPTO_TYPE_AES_CFB = HWCRYPTO_TYPE_AES | (0x03 << 8), + HWCRYPTO_TYPE_AES_CTR = HWCRYPTO_TYPE_AES | (0x04 << 8), + HWCRYPTO_TYPE_AES_OFB = HWCRYPTO_TYPE_AES | (0x05 << 8), + + /* DES Subtype */ + HWCRYPTO_TYPE_DES_ECB = HWCRYPTO_TYPE_DES | (0x01 << 8), + HWCRYPTO_TYPE_DES_CBC = HWCRYPTO_TYPE_DES | (0x02 << 8), + + /* 3DES Subtype */ + HWCRYPTO_TYPE_3DES_ECB = HWCRYPTO_TYPE_3DES | (0x01 << 8), + HWCRYPTO_TYPE_3DES_CBC = HWCRYPTO_TYPE_3DES | (0x02 << 8), +``` + +创建 AES-CBC 加密,如下所示 + +```c + struct rt_hwcrypto_ctx *ctx; + + ctx = rt_hwcrypto_symmetric_create(rt_hwcrypto_dev_default(), HWCRYPTO_TYPE_AES_CBC); +``` + +### 释放上下文 + +应用程序根据对称加解密的上下文,删除该上下文,并且释放资源,如下所示: + +```c +void rt_hwcrypto_symmetric_destroy(struct rt_hwcrypto_ctx *ctx); +``` + +| **参数** | **描述** | +|----------|------------| +| ctx | 上下文句柄 | +| **返回** | —— | + +### 加解密数据 + +应用程序根据对称加解密的上下文、加解密模式、输入数据及长度,可以输出最后计算后的值,如下所示: + +```c +rt_err_t rt_hwcrypto_symmetric_crypt(struct rt_hwcrypto_ctx *ctx, hwcrypto_mode mode, rt_size_t length, const rt_uint8_t *in, rt_uint8_t *out); + +``` + +| **参数** | **描述** | +|----------|--------------| +| ctx | 上下文句柄 | +| mode | 加密或者解密 | +| length | 数据长度 | +| in | 输入数据 | +| out | 输出数据 | +| **返回** | —— | +| RT_EOK | 计算成功 | +| 其他 | 失败 | + +加密或者解密模式如下 + +```c +typedef enum +{ + HWCRYPTO_MODE_ENCRYPT = 0x1, /**< Encryption operations */ + HWCRYPTO_MODE_DECRYPT = 0x2, /**< Decryption operations */ + HWCRYPTO_MODE_UNKNOWN = 0x7fffffff, /**< Unknown */ +} hwcrypto_mode; +``` + +### 设置加解密密钥 + +应用程序根据对称加解密的上下文、密钥与密钥及长度,设置加解密密钥,如下所示: + +```c +rt_err_t rt_hwcrypto_symmetric_setkey(struct rt_hwcrypto_ctx *ctx, const rt_uint8_t *key, rt_uint32_t bitlen); +``` + +| **参数** | **描述** | +|----------|--------------| +| ctx | 上下文句柄 | +| key | 输入密钥 | +| bitlen | 密钥长度 | +| **返回** | —— | +| RT_EOK | 设置密钥成功 | +| 其他 | 失败 | + +### 获取密钥 + +应用程序根据对称加解密的上下文、密钥、长度,获取密钥长度,如下所示: + +```c +int rt_hwcrypto_symmetric_getkey(struct rt_hwcrypto_ctx *ctx, rt_uint8_t *key, rt_uint32_t bitlen); +``` + +| **参数** | **描述** | +|----------|--------------| +| ctx | 目标上下文 | +| key | 密钥 | +| bitlen | 密钥长度 | +| **返回** | —— | +| int | 复制密钥长度 | +| 其他 | 失败 | + +### 设置对称加解密初始化向量 + +应用程序根据对称加解密的上下文、初始化向量与向量长度,设置对称加解密初始化向量,如下所示: + +```c +rt_err_t rt_hwcrypto_symmetric_setiv(struct rt_hwcrypto_ctx *ctx, const rt_uint8_t *iv, rt_size_t len); +``` + +| **参数** | **描述** | +|----------|------------------| +| ctx | 上下文 | +| iv | 初始化向量 | +| len | 初始化向量的长度 | +| **返回** | —— | +| RT_EOK | 设置成功 | +| 其他 | 失败 | + +### 获取对称加解密初始化向量 + +应用程序根据对称加解密的上下文,获取对称加解密初始化向量,如下所示: + +```c +int rt_hwcrypto_symmetric_getiv(struct rt_hwcrypto_ctx *ctx, rt_uint8_t *iv, rt_size_t len); +``` + +| **参数** | **描述** | +|----------|------------------| +| ctx | 上下文 | +| iv | 将获取初始化向量 | +| len | 初始化向量的长度 | +| **返回** | —— | +| int | 获取成功的长度 | +| 其他 | 失败 | + +### 设置对称加解密初始化向量偏移 + +应用程序根据对称加解密的上下文、初始化向量偏移值,设置对称加解密初始化向量的偏移值,如下所示: + +```c +void rt_hwcrypto_symmetric_set_ivoff(struct rt_hwcrypto_ctx *ctx, rt_int32_t iv_off); +``` + +| **参数** | **描述** | +|----------|--------------------| +| ctx | 上下文 | +| iv_off | 初始化向量的偏移值 | +| **返回** | —— | + +### 获取对称加解密初始化向量偏移值 + +应用程序根据对称加解密的上下文,获取对称加解密初始化向量的偏移值,如下所示: + +```c +void rt_hwcrypto_symmetric_get_ivoff(struct rt_hwcrypto_ctx *ctx, rt_int32_t *iv_off); +``` + +| **参数** | **描述** | +|----------|----------------------| +| ctx | 上下文 | +| iv_off | 初始化向量的偏移指针 | +| **返回** | —— | + +### 复制对称加解密上下文 + +应用程序根据源对称加解密的上下文,复制到目的上下文中,如下所示: + +```c +rt_err_t rt_hwcrypto_symmetric_cpy(struct rt_hwcrypto_ctx *des, const struct rt_hwcrypto_ctx *src); +``` + +| **参数** | **描述** | +|----------|------------| +| des | 目标上下文 | +| src | 源上下文 | +| **返回** | —— | +| RT_EOK | 设置成功 | +| 其他 | 失败 | + +### 重置对称加解密上下文 + +应用程序根据对称加解密的上下文,重置上下文,如下所示: + +```c +void rt_hwcrypto_symmetric_reset(struct rt_hwcrypto_ctx *ctx); +``` + +| **参数** | **描述** | +|----------|----------| +| des | 上下文 | +| **返回** | —— | + +### 设置加解密类型 + +应用程序根据对称加解密的上下文,设置加密或者解密,如下所示: + +```c +rt_err_t rt_hwcrypto_symmetric_set_type(struct rt_hwcrypto_ctx *ctx, hwcrypto_type type); +``` + +| **参数** | **描述** | +|----------|------------| +| ctx | 上下文 | +| type | 加解密类型 | +| **返回** | —— | +| RT_EOK | 设置成功 | +| 其他 | 失败 | + +- 常用加解密类型,参考创建对称加解密上下文中类型介绍。 + +采用应用 AES-CBC 进行加密,应用举例如下: + +```c +/* 加密密钥 */ +static const rt_uint8_t key[16] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}; + +static void hw_aes_cbc(const rt_uint8_t in[32], rt_uint8_t out[32], hwcrypto_mode mode); + +int main(void) +{ + rt_uint8_t buf_in[32]; + rt_uint8_t buf_out[32]; + int i; + + /* 填充测试数据 */ + for (i = 0; i < sizeof(buf_in); i++) + { + buf_in[i] = (rt_uint8_t)i; + } + + memset(buf_out, 0, sizeof(buf_out)); + /* 对测试数据进行加密 */ + hw_aes_cbc(buf_in, buf_out, HWCRYPTO_MODE_ENCRYPT); +} + +static void hw_aes_cbc(const rt_uint8_t in[32], rt_uint8_t out[32], hwcrypto_mode mode) +{ + struct rt_hwcrypto_ctx *ctx; + + /* 创建一个 AES-CBC 模式的上下文 */ + ctx = rt_hwcrypto_symmetric_create(rt_hwcrypto_dev_default(), HWCRYPTO_TYPE_AES_CBC); + if (ctx == RT_NULL) + { + LOG_E("create AES-CBC context err!"); + return; + } + /* 设置 AES-CBC 加密密钥 */ + rt_hwcrypto_symmetric_setkey(ctx, key, 128); + /* 执行 AES-CBC 加密/解密 */ + rt_hwcrypto_symmetric_crypt(ctx, mode, 32, in, out); + /* 删除上下文,释放资源 */ + rt_hwcrypto_symmetric_destroy(ctx); +} + +``` + +## gcm 消息认证 + +gcm 消息认证可以提供对消息的加密和完整性校验,也能够对其他附加消息的真实性进行检验。 + +### 访问 gcm 设备 + +应用程序通过 RT-Thread 提供的 gcm 设备管理接口来访问 gcm 算法设备硬件,相关接口如下所示: + +| 函数 | 描述 | +|-----------------------------|--------------------| +| rt_hwcrypto_gcm_create() | 创建上下文 | +| rt_hwcrypto_gcm_destroy() | 释放上下文 | +| rt_hwcrypto_gcm_start() | 传入附加值 | +| rt_hwcrypto_gcm_finish() | 生成消息认证码 | +| rt_hwcrypto_gcm_crypt() | 进行加解密 | +| rt_hwcrypto_gcm_setkey() | 设置密钥 | +| rt_hwcrypto_gcm_getkey() | 获取密钥 | +| rt_hwcrypto_gcm_setiv() | 设置初始化向量 | +| rt_hwcrypto_gcm_getiv() | 获取初始化向量 | +| rt_hwcrypto_gcm_set_ivoff() | 设置初始化向量偏移 | +| rt_hwcrypto_gcm_get_ivoff() | 获取初始化向量偏移 | +| rt_hwcrypto_gcm_cpy() | 复制上下文 | +| rt_hwcrypto_gcm_reset() | 重置上下文 | + +### 创建 gcm 上下文 + +应用程序根据 gcm 消息认证的句柄,创建对称加解密上下文,如下所示: + +```c +struct rt_hwcrypto_ctx *rt_hwcrypto_gcm_create(struct rt_hwcrypto_device *device, + hwcrypto_type crypt_type); +``` + +| **参数** | **描述** | +|------------|----------------| +| device | 加解密设备句柄 | +| crypt_type | 加解密算法类型 | +| **返回** | —— | +| NULL | 失败 | +| 其他 | 设备对象 | + +gcm 常用类型如下 + +```c + /* symmetric Type */ + HWCRYPTO_TYPE_AES = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< AES */ + HWCRYPTO_TYPE_DES = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< DES */ + HWCRYPTO_TYPE_3DES = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< 3DES */ + HWCRYPTO_TYPE_RC4 = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< RC4 */ + HWCRYPTO_TYPE_GCM = ((__LINE__ - HWCRYPTO_TYPE_HEAD) & 0xffff) << 16, /**< GCM */ + + /* AES Subtype */ + HWCRYPTO_TYPE_AES_ECB = HWCRYPTO_TYPE_AES | (0x01 << 8), + HWCRYPTO_TYPE_AES_CBC = HWCRYPTO_TYPE_AES | (0x02 << 8), + HWCRYPTO_TYPE_AES_CFB = HWCRYPTO_TYPE_AES | (0x03 << 8), + HWCRYPTO_TYPE_AES_CTR = HWCRYPTO_TYPE_AES | (0x04 << 8), + HWCRYPTO_TYPE_AES_OFB = HWCRYPTO_TYPE_AES | (0x05 << 8), + + /* DES Subtype */ + HWCRYPTO_TYPE_DES_ECB = HWCRYPTO_TYPE_DES | (0x01 << 8), + HWCRYPTO_TYPE_DES_CBC = HWCRYPTO_TYPE_DES | (0x02 << 8), + + /* 3DES Subtype */ + HWCRYPTO_TYPE_3DES_ECB = HWCRYPTO_TYPE_3DES | (0x01 << 8), + HWCRYPTO_TYPE_3DES_CBC = HWCRYPTO_TYPE_3DES | (0x02 << 8), +``` + +### 释放上下文 + +应用程序根据 gcm 的上下文,删除该上下文,并且释放资源,如下所示: + +```c +void rt_hwcrypto_gcm_destroy(struct rt_hwcrypto_ctx *ctx); +``` + +| **参数** | **描述** | +|----------|----------| +| ctx | 上下文 | +| **返回** | —— | + +### 传入附加值 + +应用程序根据 gcm 的上下文,传入附加值初值进行加密,如下所示: + +```c +rt_err_t rt_hwcrypto_gcm_start(struct rt_hwcrypto_ctx *ctx, const rt_uint8_t *add, + rt_size_t add_len); +``` + +| **参数** | **描述** | +|----------|--------------| +| ctx | 上下文 | +| add | 附加消息 | +| add_len | 附加消息长度 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 生成消息认证码 + +应用程序根据 gcm 的上下文,对数据进行加密之后,输出 tag 值与长度,作为完整性与真实性检验认证,如下所示: + +```c +rt_err_t rt_hwcrypto_gcm_finish(struct rt_hwcrypto_ctx *ctx, const rt_uint8_t *tag, rt_size_t tag_len); +``` + +| **参数** | **描述** | +|----------|----------------| +| ctx | 上下文 | +| tag | 消息认证码 | +| tag_len | 消息认证码长度 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 进行加解密 + +应用程序根据 gcm 的上下文、加解密模式、数据长度以及输入数据后,对数据进行加密之后,输出数据缓存区,如下所示: + +```c +rt_err_t rt_hwcrypto_gcm_crypt(struct rt_hwcrypto_ctx *ctx, hwcrypto_mode mode, + rt_size_t length, const rt_uint8_t *in, rt_uint8_t *out); +``` + +| **参数** | **描述** | +|----------|--------------| +| ctx | 上下文 | +| mode | 加解密模式 | +| length | 输入数据长度 | +| in | 输入数据 | +| out | 输出数据 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 设置密钥 + +应用程序根据 gcm 的上下文、加解密密钥与长度,设置密钥,如下所示: + +```c +rt_err_t rt_hwcrypto_gcm_setkey(struct rt_hwcrypto_ctx *ctx, + const rt_uint8_t *key, rt_uint32_t bitlen); +``` + +| **参数** | **描述** | +|----------|----------| +| ctx | 上下文 | +| key | 密钥 | +| bitlen | 密钥长度 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 获取密钥 + +应用程序根据 gcm 的上下文、加解密密钥与长度,获取密钥,如下所示: + +```c +rt_err_t rt_hwcrypto_gcm_getkey(struct rt_hwcrypto_ctx *ctx, + rt_uint8_t *key, rt_uint32_t bitlen); +``` + +| **参数** | **描述** | +|----------|----------| +| ctx | 上下文 | +| key | 密钥 | +| bitlen | 密钥长度 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 设置初始化向量 + +应用程序根据 gcm 的上下文、初始化向量与长度,设置初始化向量,如下所示: + +```c +rt_err_t rt_hwcrypto_gcm_setiv(struct rt_hwcrypto_ctx *ctx, + const rt_uint8_t *iv, rt_size_t len); +``` + +| **参数** | **描述** | +|----------|----------------| +| ctx | 上下文 | +| iv | 初始化向量 | +| len | 初始化向量长度 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 获取初始化向量 + +应用程序根据 gcm 的上下文、初始化向量与长度,获取初始化向量,如下所示: + +```c +rt_err_t rt_hwcrypto_gcm_getiv(struct rt_hwcrypto_ctx *ctx, + rt_uint8_t *iv, rt_size_t len); +``` + +| **参数** | **描述** | +|----------|----------------| +| ctx | 上下文 | +| iv | 初始化向量 | +| len | 初始化向量长度 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 设置初始化向量偏移 + +应用程序根据 gcm 的上下文、初始化向量偏移,设置初始化向量偏移,如下所示: + +```c +void rt_hwcrypto_gcm_set_ivoff(struct rt_hwcrypto_ctx *ctx, rt_int32_t iv_off); +``` + +| **参数** | **描述** | +|----------|----------------| +| ctx | 上下文 | +| iv_off | 初始化向量偏移 | +| **返回** | —— | + +### 获取初始化向量偏移 + +应用程序根据 gcm 的上下文、初始化向量偏移,获取初始化向量偏移,如下所示: + +```c +void rt_hwcrypto_gcm_get_ivoff(struct rt_hwcrypto_ctx *ctx, rt_int32_t iv_off); +``` + +| **参数** | **描述** | +|----------|----------------| +| ctx | 上下文 | +| iv_off | 初始化向量偏移 | +| **返回** | —— | + +### 复制上下文 + +应用程序根据 gcm 的上下文,复制到目标上下文中,如下所示: + +```c +rt_err_t rt_hwcrypto_gcm_cpy(struct rt_hwcrypto_ctx *des, + const struct rt_hwcrypto_ctx *src); +``` + +| **参数** | **描述** | +|----------|------------| +| des | 目标上下文 | +| src | 源上下文 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +## CRC 冗余校验 + +循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。 + +### 访问 CRC 设备 + +应用程序通过 RT-Thread 提供的 CRC 设备管理接口来访问 CRC 算法设备硬件,相关接口如下所示: + +| 函数 | 描述 | +|---------------------------|--------------------| +| rt_hwcrypto_crc_create() | 创建 CRC 上下文 | +| rt_hwcrypto_crc_destroy() | 释放上下文 | +| rt_hwcrypto_crc_update() | 计算一包数据 | +| rt_hwcrypto_crc_cfg() | 设置上下文计算数据 | + +### 创建 CRC 上下文 + +应用程序根据 CRC 设备的句柄,创建 CRC 上下文,如下所示: + +```c +struct rt_hwcrypto_ctx *rt_hwcrypto_crc_create(struct rt_hwcrypto_device *device, + hwcrypto_crc_mode mode); +``` + +| **参数** | **描述** | +|----------|----------------| +| device | 将要设置的设备 | +| mode | CRC 计算模式 | +| **返回** | —— | +| NULL | 失败 | +| 其他 | 设备对象 | + +常用的 CRC 计算模式如下: + +```c +typedef enum +{ + HWCRYPTO_CRC_CUSTOM, /**< Custom CRC mode */ + HWCRYPTO_CRC_CRC8, /**< poly : 0x07 */ + HWCRYPTO_CRC_CRC16, /**< poly : 0x8005 */ + HWCRYPTO_CRC_CRC32, /**< poly : 0x04C11DB7 */ + HWCRYPTO_CRC_CCITT, /**< poly : 0x1021 */ + HWCRYPTO_CRC_DNP, /**< poly : 0x3D65 */ +} hwcrypto_crc_mode; +``` + +这些模式分别对应不同的计算多项式,如备注所示。 + +### 释放上下文 + +应用程序根据 CRC 的上下文,删除该上下文,并且释放资源,如下所示: + +```c +void rt_hwcrypto_crc_destroy(struct rt_hwcrypto_ctx *ctx); +``` + +| **参数** | **描述** | +|----------|----------| +| ctx | 上下文 | +| **返回** | —— | + +### 计算数据 + +应用程序根据 CRC 的上下文、输入数据及其长度,计算 CRC 计算结果,如下所示: + +```c +rt_uint32_t rt_hwcrypto_crc_update(struct rt_hwcrypto_ctx *ctx, + const rt_uint8_t *input, rt_size_t length); +``` + +| **参数** | **描述** | +|----------|--------------| +| ctx | 上下文 | +| input | 输入数据 | +| length | 输入数据长度 | +| **返回** | —— | +| uint32_t | 计算结果 | +| 0 | 失败 | + +### 设置配置 + +应用程序根据 CRC 的上下文与配置信息,并且释放资源,如下所示: + +```c +void rt_hwcrypto_crc_cfg(struct rt_hwcrypto_ctx *ctx, + struct hwcrypto_crc_cfg *cfg); +``` + +| **参数** | **描述** | +|----------|----------------| +| ctx | 上下文 | +| cfg | 加解密配置参数 | +| **返回** | —— | + +CRC 配置结构体如下 + +```c +struct hwcrypto_crc_cfg +{ + rt_uint32_t last_val; /**< Last CRC value cache */ + rt_uint32_t poly; /**< CRC polynomial */ + rt_uint16_t width; /**< CRC value width */ + rt_uint32_t xorout; /**< Result XOR Value */ + rt_uint16_t flags; /**< Input or output data reverse. CRC_FLAG_REFIN or CRC_FLAG_REFOUT */ +}; +``` + +计算 0 - 7 的数据校验,并且实现如下配置 + +```c + struct hwcrypto_crc_cfg cfg = + { + .last_val = 0xFFFFFFFF, + .poly = 0x04C11DB7, + .width = 32, + .xorout = 0x00000000, + .flags = 0, + }; +``` + +求取校验值,具体程序如下 + +```c +int main(void) +{ + rt_uint8_t temp[] = {0,1,2,3,4,5,6,7}; + struct rt_hwcrypto_ctx *ctx; + rt_uint32_t result = 0; + struct hwcrypto_crc_cfg cfg = + { + .last_val = 0xFFFFFFFF, + .poly = 0x04C11DB7, + .width = 32, + .xorout = 0x00000000, + .flags = 0, + }; + + /* 创建设备的上下文 */ + ctx = rt_hwcrypto_crc_create(rt_hwcrypto_dev_default(), HWCRYPTO_CRC_CRC32);/* 设置 CRC 配置 */ + rt_hwcrypto_crc_cfg(ctx, &cfg); + /* 输入数据,获取其 CRC 计算值 */ + result = rt_hwcrypto_crc_update(ctx, temp, sizeof(temp)); + /* 打印结果 */ + rt_kprintf("result: %x \n", result); + /* 释放 ctx */ + rt_hwcrypto_crc_destroy(ctx); +} +``` + +## 随机数发生器 + +随机数发生(Random Numeral Generator,RNG)器所产生的数据,后面的数与前面的数毫无关系。 + +### 访问 RNG 设备 + +应用程序通过 RT-Thread 提供的 RNG 设备管理接口来访问 RNG 算法设备硬件,相关接口如下所示: + +| 函数 | 描述 | +|--------------------------|--------------------| +| rt_hwcrypto_rng_update() | 获取默认设备随机数 | + +### 获取默认设备随机数 + +应用程序根据 RNG 默认设备的上下文,产生随机数,如下所示: + +```c +rt_uint32_t rt_hwcrypto_rng_update(void); +``` + +| **参数** | **描述** | +|-------------|--------------| +| **返回** | —— | +| rt_uint32_t | 产生的随机数 | +| 其他 | 失败 | + +例如检测随机数产生 1000,000 次,统计产生奇数与偶数的数量,代码如下: + +```c +void hw_rng(void) +{ + rt_uint32_t result=0; + int i, num0=0, num1 =0; + const int max_test = 1000 * 1000; + + + for (i = 0; i < max_test; i++) + { + result = rt_hwcrypto_rng_update(); + result%2 ? num1++ : num0++; + } + LOG_I(" num1: %d, num0: %d ",num1, num0); +} +``` + +## 大数运算 + +由于编程语言提供的基本数值数据类型表示的数值范围有限,不能满足较大规模的高精度数值计算,因此需要利用其他方法实现高精度数值的计算。 + +### 访问大数设备 + +应用程序通过 RT-Thread 提供的大数设备管理接口来访问大数算法设备硬件,相关接口如下所示: + +| 函数 | 描述 | +|---------------------------------|----------------------| +| rt_hwcrypto_bignum_default() | 获取默认上下文 | +| rt_hwcrypto_bignum_init() | 初始化大数对象 | +| rt_hwcrypto_bignum_free() | 释放大数 | +| rt_hwcrypto_bignum_get_len() | 获取大数长度 | +| rt_hwcrypto_bignum_export_bin() | 以大端模式输出二进制 | +| rt_hwcrypto_bignum_import_bin() | 以大端模式输入二进制 | +| rt_hwcrypto_bignum_add() | 大数相加 | +| rt_hwcrypto_bignum_sub() | 大数相减 | +| rt_hwcrypto_bignum_mul() | 大数相乘 | +| rt_hwcrypto_bignum_mulmod | 大数乘积取模 | +| rt_hwcrypto_bignum_exptmod | 大数幂运算取模 | + +### 获取默认上下文 + +应用程序根据大数的设备句柄,获取默认上下文,如下所示: + +```c +rt_err_t rt_hwcrypto_bignum_default(struct rt_hwcrypto_device *device); +``` + +| **参数** | **描述** | +|----------|----------| +| device | 设备句柄 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 初始化大数对象 + +应用程序根据大数的上下文,初始化大数对象,如下所示: + +```c +void rt_hwcrypto_bignum_init(struct hw_bignum_mpi *n); +``` + +| **参数** | **描述** | +|----------|----------------| +| n | 初始化大数对象 | +| **返回** | —— | + +大数对象的结构体如下: + +```c +struct hw_bignum_mpi +{ + int sign; /**< integer sign */ + rt_size_t total; /**< total of limbs */ + rt_uint8_t *p; /**< pointer to limbs */ +}; +``` + +### 释放大数 + +应用程序释放大数对象,如下所示: + +```c +void rt_hwcrypto_bignum_free(struct hw_bignum_mpi *n); +``` + +| **参数** | **描述** | +|----------|----------| +| n | 大数对象 | +| **返回** | —— | + +### 获取大数长度 + +应用程序获取大数的长度,如下所示: + +```c +int rt_hwcrypto_bignum_get_len(const struct hw_bignum_mpi *n); +``` + +| **参数** | **描述** | +|----------|------------------------| +| n | 将要获取长度的大数对象 | +| **返回** | —— | +| int | 返回大数的长度 | +| 其他 | 失败 | + +### 以大端模式输出二进制数 + +应用程序根据大数的对象,以大端的模式输出二进制数,返回复制的长度,如下所示: + +```c +int rt_hwcrypto_bignum_export_bin(struct hw_bignum_mpi *n, rt_uint8_t *buf, int len); +``` + +| **参数** | **描述** | +|----------|--------------| +| n | 大数对象 | +| buf | 将要输出数据 | +| len | 数据长度 | +| **返回** | —— | +| int | 返回复制长度 | +| 其他 | 失败 | + +### 以大端模式输入二进制数 + +应用程序根据大数的句柄,以大端的模式输入二进制数,如下所示: + +```c +rt_err_t rt_hwcrypto_bignum_import_bin(struct hw_bignum_mpi *n, rt_uint8_t *buf, int len); +``` + +| **参数** | **描述** | +|----------|----------| +| n | 大数对象 | +| buf | 输入数据 | +| len | 数据长度 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 大数相加 + +应用程序将两个大数对象进行相加,结果赋值给 `x`,如下所示 + +```c +rt_err_t rt_hwcrypto_bignum_add(struct hw_bignum_mpi *x, + const struct hw_bignum_mpi *a, + const struct hw_bignum_mpi *b); +``` + +| **参数** | **描述** | +|----------|----------| +| x | 输出结果 | +| a | 输入数据 | +| b | 输入数据 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 大数相减 + +应用程序将两个大数对象进行相减,结果赋值给 `x`,如下所示 + +```c +rt_err_t rt_hwcrypto_bignum_sub(struct hw_bignum_mpi *x, + const struct hw_bignum_mpi *a, + const struct hw_bignum_mpi *b); +``` + +| **参数** | **描述** | +|----------|----------| +| x | 输出结果 | +| a | 输入数据 | +| b | 输入数据 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 大数相乘 + +应用程序将两个大数对象进行相乘,结果赋值给 `x`,如下所示 + +```c +rt_err_t rt_hwcrypto_bignum_mul(struct hw_bignum_mpi *x, + const struct hw_bignum_mpi *a, + const struct hw_bignum_mpi *b); +``` + +| **参数** | **描述** | +|----------|----------| +| x | 输出结果 | +| a | 输入数据 | +| b | 输入数据 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 乘积取模 + +应用程序将大数对象进行乘积取模,结果赋值给 `x = a * b (mod c)`,如下所示 + +```c +rt_err_t rt_hwcrypto_bignum_mulmod(struct hw_bignum_mpi *x, + const struct hw_bignum_mpi *a, + const struct hw_bignum_mpi *b, + const struct hw_bignum_mpi *c); +``` + +| **参数** | **描述** | +|----------|----------| +| x | 输出结果 | +| a | 输入数据 | +| b | 输入数据 | +| c | 输入数据 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +### 幂运算取模 + +应用程序将大数对象进行指数取模,结果赋值给 `x = a ^ b (mod c)`,如下所示 + +```c +rt_err_t rt_hwcrypto_bignum_exptmod(struct hw_bignum_mpi *x, + const struct hw_bignum_mpi *a, + const struct hw_bignum_mpi *b, + const struct hw_bignum_mpi *c); +``` + +| **参数** | **描述** | +|----------|----------| +| x | 输出结果 | +| a | 输入数据 | +| b | 输入数据 | +| c | 输入数据 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他 | 失败 | + +## crypto 设备使用示例 + +通常硬件加解密使用流程大致分为 4 个步骤:第一步创建具体加解密类型的上下文;第二步对上下文进行配置,如设置密钥等操作;第三步执行相应的功能,获得处理后的结果;第四步删除上下文,释放资源。 + +### 示例代码 + +下面代码先使用 AES-CBC 将数据进行加密,然后对加密后的数据进行解密。解密完成后,使用 MD5 和 SHA1 两种散列算法生成信息摘要,同时输出一些日志信息(硬件加解密具体实现代码在 main() 函数下方)。 + +```c + +/* 加密密钥 */ +static const rt_uint8_t key[16] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF}; + +int main(void) +{ + rt_uint8_t buf_in[32]; + rt_uint8_t buf_out[32]; + int i; + + /* 填充测试数据 */ + for (i = 0; i < sizeof(buf_in); i++) + { + buf_in[i] = (rt_uint8_t)i; + } + /* 打印填充的数据 */ + LOG_HEX("Data ", 8, buf_in, sizeof(buf_in)); + + memset(buf_out, 0, sizeof(buf_out)); + /* 对测试数据进行加密 */ + hw_aes_cbc(buf_in, buf_out, HWCRYPTO_MODE_ENCRYPT); + + /* 打印加密后的数据 */ + LOG_HEX("AES-enc", 8, buf_out, sizeof(buf_out)); + + memset(buf_in, 0, sizeof(buf_in)); + /* 对加密数据进行解密 */ + hw_aes_cbc(buf_out, buf_in, HWCRYPTO_MODE_DECRYPT); + + /* 打印解密后的数据 */ + LOG_HEX("AES-dec", 8, buf_in, sizeof(buf_in)); + + memset(buf_out, 0, sizeof(buf_out)); + /* 对测试数据进行 MD5 运算 */ + hw_hash(buf_in, buf_out, HWCRYPTO_TYPE_MD5); + + /* 打印 16 字节长度的 MD5 结果 */ + LOG_HEX("MD5 ", 8, buf_out, 16); + + memset(buf_out, 0, sizeof(buf_out)); + /* 对测试数据进行 SHA1 运算 */ + hw_hash(buf_in, buf_out, HWCRYPTO_TYPE_SHA1); + + /* 打印 20 字节长度的 SHA1 结果 */ + LOG_HEX("SHA1 ", 8, buf_out, 20); + + return 0; +} +``` +**1. AES-CBC 加解密** + +```c +static void hw_aes_cbc(const rt_uint8_t in[32], rt_uint8_t out[32], hwcrypto_mode mode) +{ + struct rt_hwcrypto_ctx *ctx; + + /* 创建一个 AES-CBC 模式的上下文 */ + ctx = rt_hwcrypto_symmetric_create(rt_hwcrypto_dev_default(), HWCRYPTO_TYPE_AES_CBC); + if (ctx == RT_NULL) + { + LOG_E("create AES-CBC context err!"); + return; + } + /* 设置 AES-CBC 加密密钥 */ + rt_hwcrypto_symmetric_setkey(ctx, key, 128); + /* 执行 AES-CBC 加密/解密 */ + rt_hwcrypto_symmetric_crypt(ctx, mode, 32, in, out); + /* 删除上下文,释放资源 */ + rt_hwcrypto_symmetric_destroy(ctx); +} +``` + +**2. HASH 信息摘要** + +```c +static void hw_hash(const rt_uint8_t in[32], rt_uint8_t out[32], hwcrypto_type type) +{ + struct rt_hwcrypto_ctx *ctx; + + /* 创建一个 SHA1/MD5 类型的上下文 */ + ctx = rt_hwcrypto_hash_create(rt_hwcrypto_dev_default(), type); + if (ctx == RT_NULL) + { + LOG_E("create hash[%08x] context err!", type); + return; + } + /* 将输入数据进行 hash 运算 */ + rt_hwcrypto_hash_update(ctx, in, 32); + /* 获得运算结果 */ + rt_hwcrypto_hash_finish(ctx, out, 32); + /* 删除上下文,释放资源 */ + rt_hwcrypto_hash_destroy(ctx); +} +``` + +### 运行效果 + +在正常运行后,终端输出信息如下: + +```shell + \ | / +- RT - Thread Operating System + / | \ 4.0.1 build Jun 3 2019 + 2006 - 2019 Copyright by rt-thread team +D/HEX Data : 0000-0008: 00 01 02 03 04 05 06 07 ........ + 0008-0010: 08 09 0A 0B 0C 0D 0E 0F ........ + 0010-0018: 10 11 12 13 14 15 16 17 ........ + 0018-0020: 18 19 1A 1B 1C 1D 1E 1F ........ +D/HEX AES-enc: 0000-0008: 0A 94 0B B5 41 6E F0 45 ....An.E + 0008-0010: F1 C3 94 58 C6 53 EA 5A ...X.S.Z + 0010-0018: 3C F4 56 B4 CA 48 8A A3 <.V..H.. + 0018-0020: 83 C7 9C 98 B3 47 97 CB .....G.. +D/HEX AES-dec: 0000-0008: 00 01 02 03 04 05 06 07 ........ + 0008-0010: 08 09 0A 0B 0C 0D 0E 0F ........ + 0010-0018: 10 11 12 13 14 15 16 17 ........ + 0018-0020: 18 19 1A 1B 1C 1D 1E 1F ........ +D/HEX MD5 : 0000-0008: B4 FF CB 23 73 7C EC 31 ...#s|.1 + 0008-0010: 5A 4A 4D 1A A2 A6 20 CE ZJM... . +D/HEX SHA1 : 0000-0008: AE 5B D8 EF EA 53 22 C4 .[...S". + 0008-0010: D9 98 6D 06 68 0A 78 13 ..m.h.x. + 0010-0018: 92 F9 A6 42 ...B +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/crypto/figures/framework.png b/rt-thread-version/rt-thread-standard/programming-manual/device/crypto/figures/framework.png new file mode 100644 index 0000000000000000000000000000000000000000..ab8df1607289c1c3322feb99d64173e5531fedb3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/crypto/figures/framework.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/dac/dac.md b/rt-thread-version/rt-thread-standard/programming-manual/device/dac/dac.md new file mode 100644 index 0000000000000000000000000000000000000000..dcd1839139daab350c3b50d39f95e1658342ed4c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/dac/dac.md @@ -0,0 +1,266 @@ +# DAC 设备 + +## DAC 简介 + +DAC(Digital-to-Analogl Converter) 指数模转换器。是指把二进制数字量形式的离散数字信号转换为连续变化的模拟信号的器件。在数字世界中,要处理不稳定和动态的模拟信号并不容易,基于 DAC 的特性,在各种不同的产品中都可以找到它的身影。与之相对应的 ADC(Analog-to-Digital Converter)),它是 DAC 数模转换的逆向过程。DAC 主要应用于音频放大,视频编码,电机控制,数字电位计等。 + +### 转换过程 + +DAC 主要由数字寄存器、模拟电子开关、位权网络、求和运算放大器和基准电压源(或恒流源)组成。用存于数字寄存器的数字量的各位数码,分别控制对应位的模拟电子开关,使数码为 1 的位在位权网络上产生与其位权成正比的电流值,再由运算放大器对各电流值求和,并转换成电压值。DAC 的转换过程如下图所示: + +![dac-p](figures/dac-p.png) + +数模转换器是将数字信号转换为模拟信号的系统,一般用低通滤波即可以实现。数字信号先进行解码,即把数字码转换成与之对应的电平,形成阶梯状信号,然后进行低通滤波。 + +### 分辨率 + +分辨率是指 D/A 转换器能够转换的二进制位数,位数越多分辨率越高。 + +### 转换时间 + +建立时间是将一个数字量转换为稳定模拟信号所需的时间,也可以认为是转换时间。D/A 中常用建立时间来描述其速度,而不是 A/D 中常用的转换速率。一般地,电流输出 D/A 建立时间较短,电压输出 D/A 则较长。 + +### 转换精度 + +精度是指输入端加有最大数值量时,DAC 的实际输出值和理论计算值之差,它主要包括非线性误差、比例系统误差、失调误差。 + +### 线性度 + +理想的 D/A 转换器是线性的,实际上是有误差的。线性度是指数字量化时,D/A 转换器输出的模拟量按比例关系变化程度。 + +## 访问 DAC 设备 + +应用程序通过 RT-Thread 提供的 DAC 设备管理接口来访问 DAC 硬件,相关接口如下所示: + +| **函数** | **描述** | +| ---------------- | ------------------------------------- | +| rt_device_find() | 根据 DAC 设备名称查找设备获取设备句柄 | +| rt_dac_enable() | 使能 DAC 设备 | +| rt_dac_write() | 设置 DAC 设备输出值 | +| rt_dac_disable() | 关闭 DAC 设备 | + +### 查找 DAC 设备 + +应用程序根据 DAC 设备名称获取设备句柄,进而可以操作 DAC 设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | DAC 设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到设备 | + +一般情况下,注册到系统的 DAC 设备名称为 dac1,dac2 等,使用示例如下所示: + +``` +#define DAC_DEV_NAME "dac1" /* DAC 设备名称 */ +rt_dac_device_t dac_dev; /* DAC 设备句柄 */ +/* 查找设备 */ +dac_dev = (rt_dac_device_t)rt_device_find(DAC_DEV_NAME); +``` + +### 使能 DAC 通道 + +在设置 DAC 设备数据前需要先使能设备,通过如下函数使能设备: + +```c +rt_err_t rt_dac_enable(rt_dac_device_t dev, rt_uint32_t channel); +``` + +| **参数** | **描述** | +| ---------- | ---------------------- | +| dev | DAC 设备句柄 | +| channel | DAC 通道 | +| **返回** | —— | +| RT_EOK | 成功 | +| -RT_ENOSYS | 失败,设备操作方法为空 | +| 其他错误码 | 失败 | + +使用示例如下所示: + +```c +#define DAC_DEV_NAME "dac1" /* DAC 设备名称 */ +#define DAC_DEV_CHANNEL 1 /* DAC 通道 */ +rt_dac_device_t dac_dev; /* DAC 设备句柄 */ +/* 查找设备 */ +dac_dev = (rt_dac_device_t)rt_device_find(DAC_DEV_NAME); +/* 使能设备 */ +rt_dac_enable(dac_dev, DAC_DEV_CHANNEL); +``` + +### 设置 DAC 通道输出值 + +设置 DAC 通道输出值可通过如下函数完成: + +```c +rt_uint32_t rt_dac_write(rt_dac_device_t dev, rt_uint32_t channel, rt_uint32_t value); +``` + +| | | +| ---------- | ------------ | +| **参数** | **描述** | +| dev | DAC 设备句柄 | +| channel | DAC 通道 | +| value | DAC 输出值 | +| **返回** | —— | +| RT_EOK | 成功 | +| -RT_ENOSYS | 失败 | + +使用 DAC 输出电压值的使用示例如下所示: + +```c +#define DAC_DEV_NAME "dac1" /* DAC 设备名称 */ +#define DAC_DEV_CHANNEL 1 /* DAC 通道 */ +rt_dac_device_t dac_dev; /* DAC 设备句柄 */ +rt_uint32_t value = 1000; /* DAC 数据保持寄存器值 */ +/* 查找设备 */ +dac_dev = (rt_dac_device_t)rt_device_find(DAC_DEV_NAME); +/* 使能设备 */ +rt_dac_enable(dac_dev, DAC_DEV_CHANNEL); +/* 输出电压值 */ +rt_dac_write(dac_dev, DAC_DEV_CHANNEL, &value); +/* 转换为对应电压值 */ +vol = value * REFER_VOLTAGE / CONVERT_BITS; +rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100); +``` + +### 关闭 DAC 通道 + +关闭 DAC 通道可通过如下函数完成: + +```c +rt_err_t rt_dac_disable(rt_dac_device_t dev, rt_uint32_t channel); +``` + +| **参数** | **描述** | +| ---------- | ---------------------- | +| dev | DAC 设备句柄 | +| channel | DAC 通道 | +| **返回** | —— | +| RT_EOK | 成功 | +| -RT_ENOSYS | 失败,设备操作方法为空 | +| 其他错误码 | 失败 | + +使用示例如下所示: + +``` +#define DAC_DEV_NAME "dac1" /* DAC 设备名称 */ +#define DAC_DEV_CHANNEL 1 /* DAC 通道 */ +rt_dac_device_t dac_dev; /* DAC 设备句柄 */ +rt_uint32_t value = 1000; /* DAC 数据保持寄存器值 */ +/* 查找设备 */ +dac_dev = (rt_dac_device_t)rt_device_find(DAC_DEV_NAME); +/* 使能设备 */ +rt_dac_enable(dac_dev, DAC_DEV_CHANNEL); +/* 设置输出值 */ +rt_dac_write(dac_dev, DAC_DEV_CHANNEL, &value); +/* 转换为对应电压值 */ +vol = value * REFER_VOLTAGE / CONVERT_BITS; +rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100); +/* 关闭通道 */ +rt_dac_disable(dac_dev, DAC_DEV_CHANNEL); +``` + +### FinSH 命令 + +在使用设备前,需要先查找设备是否存在,可以使用命令 `dac probe` 后面跟注册的 DAC 设备的名称。如下所示: + +```c +msh >dac probe dac1 +probe dac1 success +``` + +使能设备的某个通道可以使用命令 `dac enable` 后面跟通道号。 + +```c +msh >dac enable 1 +dac1 channel 1 enables success +``` + +设置 DAC 设备某个通道的输出值可以使用命令 `dac write` 后面跟通道号和输出值。 + +```c +msh >dac write 1 1000 +dac1 channel 1 write value is 1000 +msh > +``` + +关闭设备的某个通道可以使用命令 `dac disable` 后面跟通道号。 + +``` +msh >dac disable 1 +dac1 channel 1 disable success +msh > +``` + +## DAC 设备使用示例 + +DAC 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 首先根据 DAC 设备名称 “dac1” 查找设备获取设备句柄。 +2. 使能设备后设置 dac1 设备对应的通道 1 的输出值,然后根据分辨率为 12 位,参考电压为 3.3V 计算实际的电压值。 +3. 若不使用 DAC 设备对应通道,则需要关闭该通道。 + +运行结果:打印实际读取到的转换的原始数据和经过计算后的实际电压值。 + +``` +/* + * 程序清单: DAC 设备使用例程 + * 例程导出了 dac_sample 命令到控制终端 + * 命令调用格式:dac_sample + * 程序功能:通过 DAC 设备将数字值转换为模拟量,并输出电压值。 + * 示例代码参考电压为3.3V,转换位数为12位。 +*/ + +#include +#include + +#define DAC_DEV_NAME "dac1" /* DAC 设备名称 */ +#define DAC_DEV_CHANNEL 1 /* DAC 通道 */ +#define REFER_VOLTAGE 330 /* 参考电压 3.3V,数据精度乘以100保留2位小数*/ +#define CONVERT_BITS (1 << 12) /* 转换位数为12位 */ + +static int dac_vol_sample(int argc, char *argv[]) +{ + rt_dac_device_t dac_dev; + rt_uint32_t value, vol; + rt_err_t ret = RT_EOK; + + /* 查找设备 */ + dac_dev = (rt_dac_device_t)rt_device_find(DAC_DEV_NAME); + if (dac_dev == RT_NULL) + { + rt_kprintf("dac sample run failed! can't find %s device!\n", DAC_DEV_NAME); + return RT_ERROR; + } + + /* 使能设备 */ + ret = rt_dac_enable(dac_dev, DAC_DEV_NAME); + + /* 设置输出值 */ + value = atoi(argv[1]); + rt_dac_write(dac_dev, DAC_DEV_NAME, DAC_DEV_CHANNEL, &value); + rt_kprintf("the value is :%d \n", value); + + /* 转换为对应电压值 */ + vol = value * REFER_VOLTAGE / CONVERT_BITS; + rt_kprintf("the voltage is :%d.%02d \n", vol / 100, vol % 100); + + /* 关闭通道 */ + ret = rt_dac_disable(dac_dev, DAC_DEV_CHANNEL); + + return ret; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(dac_vol_sample, dac voltage convert sample); +``` + +## 常见问题 + +### Q: menuconfig 找不到 DAC 设备的配置选项? + +**A:** 使用的源代码还不支持 DAC 设备驱动框架。建议更新源代码。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/dac/figures/dac-p.png b/rt-thread-version/rt-thread-standard/programming-manual/device/dac/figures/dac-p.png new file mode 100644 index 0000000000000000000000000000000000000000..4d90608f6d964d62a1d5a4431aaaeb4bc1b392c9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/dac/figures/dac-p.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/device.md b/rt-thread-version/rt-thread-standard/programming-manual/device/device.md new file mode 100644 index 0000000000000000000000000000000000000000..477982ac3067544c60123394bed6bd1a02679855 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/device.md @@ -0,0 +1,495 @@ +# I/O 设备模型 + +绝大部分的嵌入式系统都包括一些 I/O(Input/Output,输入 / 输出)设备,例如仪器上的数据显示屏、工业设备上的串口通信、数据采集设备上用于保存数据的 Flash 或 SD 卡,以及网络设备的以太网接口等,都是嵌入式系统中容易找到的 I/O 设备例子。 + +本章主要介绍 RT-Thread 如何对不同的 I/O 设备进行管理,读完本章,我们会了解 RT-Thread 的 I/O 设备模型,并熟悉 I/O 设备管理接口的不同功能。 + +## I/O 设备介绍 + +### I/O 设备模型框架 + +RT-Thread 提供了一套简单的 I/O 设备模型框架,如下图所示,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。 + +![I/O 设备模型框架](figures/io-dev.png) + +应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互。 + +I/O 设备管理层实现了对设备驱动程序的封装。应用程序通过 I/O 设备层提供的标准接口访问底层设备,设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。 + +设备驱动框架层是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。 + +设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备,对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中,使用序列图如下图所示,主要有以下 2 点: + +* 设备驱动根据设备模型定义,创建出具备硬件访问能力的设备实例,将该设备通过 `rt_device_register()` 接口注册到 I/O 设备管理器中。 + +* 应用程序通过 `rt_device_find()` 接口查找到设备,然后使用 I/O 设备管理接口来访问硬件。 + +![简单 I/O 设备使用序列图](figures/io-call.png) + +对于另一些设备,如看门狗等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 I/O 设备管理器进行注册,主要有以下几点: + +* 看门狗设备驱动程序根据看门狗设备模型定义,创建出具备硬件访问能力的看门狗设备实例,并将该看门狗设备通过 `rt_hw_watchdog_register()` 接口注册到看门狗设备驱动框架中。 + +* 看门狗设备驱动框架通过 `rt_device_register()` 接口将看门狗设备注册到 I/O 设备管理器中。 + +* 应用程序通过 I/O 设备管理接口来访问看门狗设备硬件。 + +看门狗设备使用序列图: + +![看门狗设备使用序列图](figures/wtd-uml.png) + +### I/O 设备模型 + +RT-Thread 的设备模型是建立在内核对象模型基础之上的,设备被认为是一类对象,被纳入对象管理器的范畴。每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出其私有属性,下图是设备对象的继承和派生关系示意图。 + +![设备继承关系图](figures/io-parent.png) + +设备对象具体定义如下所示: + +```c +struct rt_device +{ + struct rt_object parent; /* 内核对象基类 */ + enum rt_device_class_type type; /* 设备类型 */ + rt_uint16_t flag; /* 设备参数 */ + rt_uint16_t open_flag; /* 设备打开标志 */ + rt_uint8_t ref_count; /* 设备被引用次数 */ + rt_uint8_t device_id; /* 设备 ID,0 - 255 */ + + /* 数据收发回调函数 */ + rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); + rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); + + const struct rt_device_ops *ops; /* 设备操作方法 */ + + /* 设备的私有数据 */ + void *user_data; +}; +typedef struct rt_device *rt_device_t; + +``` + +### I/O 设备类型 + +RT-Thread 支持多种 I/O 设备类型,主要设备类型如下所示: + +```c +RT_Device_Class_Char /* 字符设备 */ +RT_Device_Class_Block /* 块设备 */ +RT_Device_Class_NetIf /* 网络接口设备 */ +RT_Device_Class_MTD /* 内存设备 */ +RT_Device_Class_RTC /* RTC 设备 */ +RT_Device_Class_Sound /* 声音设备 */ +RT_Device_Class_Graphic /* 图形设备 */ +RT_Device_Class_I2CBUS /* I2C 总线设备 */ +RT_Device_Class_USBDevice /* USB device 设备 */ +RT_Device_Class_USBHost /* USB host 设备 */ +RT_Device_Class_SPIBUS /* SPI 总线设备 */ +RT_Device_Class_SPIDevice /* SPI 设备 */ +RT_Device_Class_SDIO /* SDIO 设备 */ +RT_Device_Class_Miscellaneous /* 杂类设备 */ +``` + +其中字符设备、块设备是常用的设备类型,它们的分类依据是设备数据与系统之间的传输处理方式。字符模式设备允许非结构的数据传输,即通常数据传输采用串行的形式,每次一个字节。字符设备通常是一些简单设备,如串口、按键。 + +块设备每次传输一个数据块,例如每次传输 512 个字节数据。这个数据块是硬件强制性的,数据块可能使用某类数据接口或某些强制性的传输协议,否则就可能发生错误。因此,有时块设备驱动程序对读或写操作必须执行附加的工作,如下图所示: + +![块设备](figures/block-dev.png) + +当系统服务于一个具有大量数据的写操作时,设备驱动程序必须首先将数据划分为多个包,每个包采用设备指定的数据尺寸。而在实际过程中,最后一部分数据尺寸有可能小于正常的设备块尺寸。如上图中每个块使用单独的写请求写入到设备中,头 3 个直接进行写操作。但最后一个数据块尺寸小于设备块尺寸,设备驱动程序必须使用不同于前 3 个块的方式处理最后的数据块。通常情况下,设备驱动程序需要首先执行相对应的设备块的读操作,然后把写入数据覆盖到读出数据上,然后再把这个 “合成” 的数据块作为一整个块写回到设备中。例如上图中的块 4,驱动程序需要先把块 4 所对应的设备块读出来,然后将需要写入的数据覆盖至从设备块读出的数据上,使其合并成一个新的块,最后再写回到块设备中。 + +## 创建和注册 I/O 设备 + +驱动层负责创建设备实例,并注册到 I/O 设备管理器中,可以通过静态申明的方式创建设备实例,也可以用下面的接口进行动态创建: + +```c +rt_device_t rt_device_create(int type, int attach_size); +``` + +|**参数** |**描述** | +|-------------|-------------------------------------| +| type | 设备类型,可取前面小节列出的设备类型值 | +| attach_size | 用户数据大小 | +|**返回** | —— | +| 设备句柄 | 创建成功 | +| RT_NULL | 创建失败,动态内存分配失败 | + +调用该接口时,系统会从动态堆内存中分配一个设备控制块,大小为 struct rt_device 和 attach_size 的和,设备的类型由参数 type 设定。设备被创建后,需要实现它访问硬件的操作方法。 + +```c +struct rt_device_ops +{ + /* common device interface */ + rt_err_t (*init) (rt_device_t dev); + rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); + rt_err_t (*close) (rt_device_t dev); + rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); + rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); + rt_err_t (*control)(rt_device_t dev, int cmd, void *args); +}; + +``` + +各个操作方法的描述如下表所示: + +|**方法名称**|**方法描述** | +|----|-----------------------| +| init | 初始化设备。设备初始化完成后,设备控制块的 flag 会被置成已激活状态 (RT_DEVICE_FLAG_ACTIVATED)。如果设备控制块中的 flag 标志已经设置成激活状态,那么再运行初始化接口时会立刻返回,而不会重新进行初始化。 | +| open | 打开设备。有些设备并不是系统一启动就已经打开开始运行,或者设备需要进行数据收发,但如果上层应用还未准备好,设备也不应默认已经使能并开始接收数据。所以建议在写底层驱动程序时,在调用 open 接口时才使能设备。 | +| close | 关闭设备。在打开设备时,设备控制块会维护一个打开计数,在打开设备时进行 + 1 操作,在关闭设备时进行 - 1 操作,当计数器变为 0 时,才会进行真正的关闭操作。| +| read | 从设备读取数据。参数 pos 是读取数据的偏移量,但是有些设备并不一定需要指定偏移量,例如串口设备,设备驱动应忽略这个参数。而对于块设备来说,pos 以及 size 都是以块设备的数据块大小为单位的。例如块设备的数据块大小是 512,而参数中 pos = 10, size = 2,那么驱动应该返回设备中第 10 个块 (从第 0 个块做为起始),共计 2 个块的数据。这个接口返回的类型是 rt_size_t,即读到的字节数或块数目。正常情况下应该会返回参数中 size 的数值,如果返回零请设置对应的 errno 值。 | +| write | 向设备写入数据。参数 pos 是写入数据的偏移量。与读操作类似,对于块设备来说,pos 以及 size 都是以块设备的数据块大小为单位的。这个接口返回的类型是 rt_size_t,即真实写入数据的字节数或块数目。正常情况下应该会返回参数中 size 的数值,如果返回零请设置对应的 errno 值。 | +| control | 根据 cmd 命令控制设备。命令往往是由底层各类设备驱动自定义实现。例如参数 RT_DEVICE_CTRL_BLK_GETGEOME,意思是获取块设备的大小信息。 | + +当一个动态创建的设备不再需要使用时可以通过如下函数来销毁: + +```c +void rt_device_destroy(rt_device_t device); +``` + +|**参数**|**描述**| +|----------|----------| +| device | 设备句柄 | +|**返回**| 无 | + +设备被创建后,需要注册到 I/O 设备管理器中,应用程序才能够访问,注册设备的函数如下所示: + +```c +rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags); +``` + +|**参数** |**描述** | +|------------|-----------------------| +| dev | 设备句柄 | +| name | 设备名称,设备名称的最大长度由 rtconfig.h 中定义的宏 RT_NAME_MAX 指定,多余部分会被自动截掉 | +| flags | 设备模式标志 | +|**返回** | —— | +| RT_EOK | 注册成功 | +| -RT_ERROR | 注册失败,dev 为空或者 name 已经存在 | + +> [!NOTE] +> 注:应当避免重复注册已经注册的设备,以及注册相同名字的设备。 + +flags 参数支持下列参数 (可以采用或的方式支持多种参数): + +```c +#define RT_DEVICE_FLAG_RDONLY 0x001 /* 只读 */ +#define RT_DEVICE_FLAG_WRONLY 0x002 /* 只写 */ +#define RT_DEVICE_FLAG_RDWR 0x003 /* 读写 */ +#define RT_DEVICE_FLAG_REMOVABLE 0x004 /* 可移除 */ +#define RT_DEVICE_FLAG_STANDALONE 0x008 /* 独立 */ +#define RT_DEVICE_FLAG_SUSPENDED 0x020 /* 挂起 */ +#define RT_DEVICE_FLAG_STREAM 0x040 /* 流模式 */ +#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收 */ +#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收 */ +#define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送 */ +#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送 */ +``` + +设备流模式 RT_DEVICE_FLAG_STREAM 参数用于向串口终端输出字符串:当输出的字符是 `“\n”` 时,自动在前面补一个 `“\r”` 做分行。 + +注册成功的设备可以在 FinSH 命令行使用 `list_device` 命令查看系统中所有的设备信息,包括设备名称、设备类型和设备被打开次数: + +```c +msh />list_device +device type ref count +-------- -------------------- ---------- +e0 Network Interface 0 +sd0 Block Device 1 +rtc RTC 0 +uart1 Character Device 0 +uart0 Character Device 2 +msh /> +``` + +当设备注销后的,设备将从设备管理器中移除,也就不能再通过设备查找搜索到该设备。注销设备不会释放设备控制块占用的内存。注销设备的函数如下所示: + +```c +rt_err_t rt_device_unregister(rt_device_t dev); +``` + +|**参数**|**描述**| +|----------|----------| +| dev | 设备句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +下面代码为看门狗设备的注册示例,调用 `rt_hw_watchdog_register()` 接口后,设备通过 `rt_device_register()` 接口被注册到 I/O 设备管理器中。 + +```c +const static struct rt_device_ops wdt_ops = +{ + rt_watchdog_init, + rt_watchdog_open, + rt_watchdog_close, + RT_NULL, + RT_NULL, + rt_watchdog_control, +}; + +rt_err_t rt_hw_watchdog_register(struct rt_watchdog_device *wtd, + const char *name, + rt_uint32_t flag, + void *data) +{ + struct rt_device *device; + RT_ASSERT(wtd != RT_NULL); + + device = &(wtd->parent); + + device->type = RT_Device_Class_Miscellaneous; + device->rx_indicate = RT_NULL; + device->tx_complete = RT_NULL; + + device->ops = &wdt_ops; + device->user_data = data; + + /* register a character device */ + return rt_device_register(device, name, flag); +} + +``` + +## 访问 I/O 设备 + +应用程序通过 I/O 设备管理接口来访问硬件设备,当设备驱动实现后,应用程序就可以访问该硬件。I/O 设备管理接口与 I/O 设备的操作方法的映射关系下图所示: + +![I/O 设备管理接口与 I/O 设备的操作方法的映射关系](figures/io-fun-call.png) + +### 查找设备 + +应用程序根据设备名称获取设备句柄,进而可以操作设备。查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +|**参数**|**描述** | +|----------|------------------------------------| +| name | 设备名称 | +|**返回**| —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +### 初始化设备 + +获得设备句柄后,应用程序可使用如下函数对设备进行初始化操作: + +```c +rt_err_t rt_device_init(rt_device_t dev); +``` + +|**参数**|**描述** | +|----------|----------------| +| dev | 设备句柄 | +|**返回**| —— | +| RT_EOK | 设备初始化成功 | +| 错误码 | 设备初始化失败 | + +> [!NOTE] +> 注:当一个设备已经初始化成功后,调用这个接口将不再重复做初始化 0。 + +### 打开和关闭设备 + +通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +|**参数** |**描述** | +|------------|-----------------------------| +| dev | 设备句柄 | +| oflags | 设备打开模式标志 | +|**返回** | —— | +| RT_EOK | 设备打开成功 | +|-RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开 | +| 其他错误码 | 设备打开失败 | + +oflags 支持以下的参数: + +```c +#define RT_DEVICE_OFLAG_CLOSE 0x000 /* 设备已经关闭(内部使用)*/ +#define RT_DEVICE_OFLAG_RDONLY 0x001 /* 以只读方式打开设备 */ +#define RT_DEVICE_OFLAG_WRONLY 0x002 /* 以只写方式打开设备 */ +#define RT_DEVICE_OFLAG_RDWR 0x003 /* 以读写方式打开设备 */ +#define RT_DEVICE_OFLAG_OPEN 0x008 /* 设备已经打开(内部使用)*/ +#define RT_DEVICE_FLAG_STREAM 0x040 /* 设备以流模式打开 */ +#define RT_DEVICE_FLAG_INT_RX 0x100 /* 设备以中断接收模式打开 */ +#define RT_DEVICE_FLAG_DMA_RX 0x200 /* 设备以 DMA 接收模式打开 */ +#define RT_DEVICE_FLAG_INT_TX 0x400 /* 设备以中断发送模式打开 */ +#define RT_DEVICE_FLAG_DMA_TX 0x800 /* 设备以 DMA 发送模式打开 */ +``` + +> [!NOTE] +> 注:如果上层应用程序需要设置设备的接收回调函数,则必须以 RT_DEVICE_FLAG_INT_RX 或者 RT_DEVICE_FLAG_DMA_RX 的方式打开设备,否则不会回调函数。 + +应用程序打开设备完成读写等操作后,如果不需要再对设备进行操作则可以关闭设备,通过如下函数完成: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +|**参数** |**描述** | +|------------|------------------------------------| +| dev | 设备句柄 | +|**返回** | —— | +| RT_EOK | 关闭设备成功 | +| \-RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + +> [!NOTE] +> 注:关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + +### 控制设备 + +通过命令控制字,应用程序也可以对设备进行控制,通过如下函数完成: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +|**参数** |**描述** | +|-------------|--------------------------------------------| +| dev | 设备句柄 | +| cmd | 命令控制字,这个参数通常与设备驱动程序相关 | +| arg | 控制的参数 | +|**返回** | —— | +| RT_EOK | 函数执行成功 | +| -RT_ENOSYS | 执行失败,dev 为空 | +| 其他错误码 | 执行失败 | + +参数 cmd 的通用设备命令可取如下宏定义: + +```c +#define RT_DEVICE_CTRL_RESUME 0x01 /* 恢复设备 */ +#define RT_DEVICE_CTRL_SUSPEND 0x02 /* 挂起设备 */ +#define RT_DEVICE_CTRL_CONFIG 0x03 /* 配置设备 */ +#define RT_DEVICE_CTRL_SET_INT 0x10 /* 设置中断 */ +#define RT_DEVICE_CTRL_CLR_INT 0x11 /* 清中断 */ +#define RT_DEVICE_CTRL_GET_INT 0x12 /* 获取中断状态 */ +``` + +### 读写设备 + +应用程序从设备中读取数据可以通过如下函数完成: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size); +``` + +|**参数** |**描述** | +|--------------------|--------------------------------| +| dev | 设备句柄 | +| pos | 读取数据偏移量 | +| buffer | 内存缓冲区指针,读取的数据将会被保存在缓冲区中 | +| size | 读取数据的大小 | +|**返回** | —— | +| 读到数据的实际大小 | 如果是字符设备,返回大小以字节为单位,如果是块设备,返回的大小以块为单位 | +| 0 | 需要读取当前线程的 errno 来判断错误状态 | + +调用这个函数,会从 dev 设备中读取数据,并存放在 buffer 缓冲区中,这个缓冲区的最大长度是 size,pos 根据不同的设备类别有不同的意义。 + +向设备中写入数据,可以通过如下函数完成: + +```c +rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size); +``` + +|**参数** |**描述** | +|--------------------|--------------------------------| +| dev | 设备句柄 | +| pos | 写入数据偏移量 | +| buffer | 内存缓冲区指针,放置要写入的数据 | +| size | 写入数据的大小 | +|**返回** | —— | +| 写入数据的实际大小 | 如果是字符设备,返回大小以字节为单位;如果是块设备,返回的大小以块为单位 | +| 0 | 需要读取当前线程的 errno 来判断错误状态 | + +调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的最大长度是 size,pos 根据不同的设备类别存在不同的意义。 + +### 数据收发回调 + +当硬件设备收到数据时,可以通过如下函数回调另一个函数来设置数据接收指示,通知上层应用线程有数据到达: + +```c +rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size)); + +``` + +|**参数**|**描述** | +|----------|--------------| +| dev | 设备句柄 | +| rx_ind | 回调函数指针 | +|**返回**| —— | +| RT_EOK | 设置成功 | + +该函数的回调函数由调用者提供。当硬件设备接收到数据时,会回调这个函数并把收到的数据长度放在 size 参数中传递给上层应用。上层应用线程应在收到指示后,立刻从设备中读取数据。 + +在应用程序调用 `rt_device_write()` 入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。可以通过如下函数设置设备发送完成指示,函数参数及返回值见: + +```c +rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer)); +``` + +|**参数**|**描述** | +|----------|--------------| +| dev | 设备句柄 | +| tx_done | 回调函数指针 | +|**返回**| —— | +| RT_EOK | 设置成功 | + +调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由驱动程序回调这个函数并把发送完成的数据块地址 buffer 作为参数传递给上层应用。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。 + +### 设备访问示例 + +下面代码为用程序访问设备的示例,首先通过 `rt_device_find()` 口查找到看门狗设备,获得设备句柄,然后通过 `rt_device_init()` 口初始化设备,通过 `rt_device_control()` 口设置看门狗设备溢出时间。 + +```c +#include +#include + +#define IWDG_DEVICE_NAME "iwg" + +static rt_device_t wdg_dev; + +static void idle_hook(void) +{ + /* 在空闲线程的回调函数里喂狗 */ + rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL); + rt_kprintf("feed the dog!\n "); +} + +int main(void) +{ + rt_err_t res = RT_EOK; + rt_uint32_t timeout = 1000; /* 溢出时间 */ + + /* 根据设备名称查找看门狗设备,获取设备句柄 */ + wdg_dev = rt_device_find(IWDG_DEVICE_NAME); + if (!wdg_dev) + { + rt_kprintf("find %s failed!\n", IWDG_DEVICE_NAME); + return RT_ERROR; + } + /* 初始化设备 */ + res = rt_device_init(wdg_dev); + if (res != RT_EOK) + { + rt_kprintf("initialize %s failed!\n", IWDG_DEVICE_NAME); + return res; + } + /* 设置看门狗溢出时间 */ + res = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout); + if (res != RT_EOK) + { + rt_kprintf("set %s timeout failed!\n", IWDG_DEVICE_NAME); + return res; + } + /* 设置空闲线程回调函数 */ + rt_thread_idle_sethook(idle_hook); + + return res; +} +``` + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/figures/block-dev.png b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/block-dev.png new file mode 100644 index 0000000000000000000000000000000000000000..88e2c962f997fdf304b53b71f9fb2ed45d5d86e7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/block-dev.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-call.png b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-call.png new file mode 100644 index 0000000000000000000000000000000000000000..d63fb26fec64c374258b0c2603662b1d4c49a463 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-call.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-dev.png b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-dev.png new file mode 100644 index 0000000000000000000000000000000000000000..901577c64d7fcbbddaf85c39fbc888dc1afe4e14 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-dev.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-fun-call.png b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-fun-call.png new file mode 100644 index 0000000000000000000000000000000000000000..3ad89dba68c0ca1a71064f1bcc4ce490b8ac92da Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-fun-call.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-parent.png b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-parent.png new file mode 100644 index 0000000000000000000000000000000000000000..2800f9ebb582498dd08b206360affdc7d4606fc0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/io-parent.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/figures/wtd-uml.png b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/wtd-uml.png new file mode 100644 index 0000000000000000000000000000000000000000..a25df0c5ffa7085bdbf2cf72f628ae3a989c2059 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/figures/wtd-uml.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/hwtimer/hwtimer.md b/rt-thread-version/rt-thread-standard/programming-manual/device/hwtimer/hwtimer.md new file mode 100644 index 0000000000000000000000000000000000000000..ee42ecada9578fefa9a1e9a813423fbfc435f67d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/hwtimer/hwtimer.md @@ -0,0 +1,409 @@ +# HWTIMER 设备 + + +## 定时器简介 + +硬件定时器一般有 2 种工作模式,定时器模式和计数器模式。不管是工作在哪一种模式,实质都是通过内部计数器模块对脉冲信号进行计数。下面是定时器的一些重要概念。 + +**计数器模式:**对外部输入引脚的外部脉冲信号计数。 + +**定时器模式:**对内部脉冲信号计数。定时器常用作定时时钟,以实现定时检测,定时响应、定时控制。 + +**计数器:**计数器可以递增计数或者递减计数。16位计数器的最大计数值为65535,32位的最大值为4294967295。 + +**计数频率:**定时器模式时,计数器单位时间内的计数次数,由于系统时钟频率是定值,所以可以根据计数器的计数值计算出定时时间,定时时间 = 计数值 / 计数频率。例如计数频率为 1MHz,计数器计数一次的时间则为 1 / 1000000, 也就是每经过 1 微秒计数器加一(或减一),此时 16 位计数器的最大定时能力为 65535 微秒,即 65.535 毫秒。 + +## 访问硬件定时器设备 + +应用程序通过 RT-Thread 提供的 I/O 设备管理接口来访问硬件定时器设备,相关接口如下所示: + +| **函数** | **描述** | +| -------------------- | ---------------------------------- | +| rt_device_find() | 查找定时器设备 | +| rt_device_open() | 以读写方式打开定时器设备 | +| rt_device_set_rx_indicate() | 设置超时回调函数 | +| rt_device_control() | 控制定时器设备,可以设置定时模式(单次/周期)/计数频率,或者停止定时器 | +| rt_device_write() | 设置定时器超时值,定时器随即启动 | +| rt_device_read() | 获取定时器当前值 | +| rt_device_close() | 关闭定时器设备 | + +### 查找定时器设备 + +应用程序根据硬件定时器设备名称获取设备句柄,进而可以操作硬件定时器设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | 硬件定时器设备名称 | +| **返回** | —— | +| 定时器设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到设备 | + +一般情况下,注册到系统的硬件定时器设备名称为 timer0,timer1等,使用示例如下所示: + +```c +#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */ +rt_device_t hw_dev; /* 定时器设备句柄 */ +/* 查找定时器设备 */ +hw_dev = rt_device_find(HWTIMER_DEV_NAME); +``` + +### 打开定时器设备 + +通过设备句柄,应用程序可以打开设备。打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | 硬件定时器设备句柄 | +| oflags | 设备打开模式,一般以读写方式打开,即取值:RT_DEVICE_OFLAG_RDWR | +| **返回** | —— | +| RT_EOK | 设备打开成功 | +| 其他错误码 | 设备打开失败 | + +使用示例如下所示: + +```c +#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */ +rt_device_t hw_dev; /* 定时器设备句柄 */ +/* 查找定时器设备 */ +hw_dev = rt_device_find(HWTIMER_DEV_NAME); +/* 以读写方式打开设备 */ +rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); +``` + +### 设置超时回调函数 + +通过如下函数设置定时器超时回调函数,当定时器超时将会调用此回调函数: + +```c +rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size)) +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | 设备句柄 | +| rx_ind | 超时回调函数,由调用者提供 | +| **返回** | —— | +| RT_EOK | 成功 | + +使用示例如下所示: + +```c +#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */ +rt_device_t hw_dev; /* 定时器设备句柄 */ + +/* 定时器超时回调函数 */ +static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("this is hwtimer timeout callback fucntion!\n"); + rt_kprintf("tick is :%d !\n", rt_tick_get()); + + return 0; +} + +static int hwtimer_sample(int argc, char *argv[]) +{ + /* 查找定时器设备 */ + hw_dev = rt_device_find(HWTIMER_DEV_NAME); + /* 以读写方式打开设备 */ + rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); + /* 设置超时回调函数 */ + rt_device_set_rx_indicate(hw_dev, timeout_cb); +} +``` + +### 控制定时器设备 + +通过命令控制字,应用程序可以对硬件定时器设备进行配置,通过如下函数完成: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| cmd | 命令控制字 | +| arg | 控制的参数 | +| **返回** | —— | +| RT_EOK | 函数执行成功 | +| -RT_ENOSYS | 执行失败,dev 为空 | +| 其他错误码 | 执行失败 | + +硬件定时器设备支持的命令控制字如下所示: + +| **控制字** | **描述** | +| ---------------------- | ------------------------ | +| HWTIMER_CTRL_FREQ_SET | 设置计数频率 | +| HWTIMER_CTRL_STOP | 停止定时器 | +| HWTIMER_CTRL_INFO_GET | 获取定时器特征信息 | +| HWTIMER_CTRL_MODE_SET | 设置定时器模式 | + +获取定时器特征信息参数 arg 为指向结构体 struct rt_hwtimer_info 的指针,作为一个输出参数保存获取的信息。 + +> [!NOTE] +> 注:定时器硬件及驱动支持设置计数频率的情况下设置频率才有效,一般使用驱动设置的默认频率即可。 + +设置定时器模式时,参数 arg 可取如下值: + +```c +HWTIMER_MODE_ONESHOT 单次定时 +HWTIMER_MODE_PERIOD 周期性定时 +``` + +设置定时器计数频率和定时模式的使用示例如下所示: + +```c +#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */ +rt_device_t hw_dev; /* 定时器设备句柄 */ +rt_hwtimer_mode_t mode; /* 定时器模式 */ +rt_uint32_t freq = 10000; /* 计数频率 */ + +/* 定时器超时回调函数 */ +static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("this is hwtimer timeout callback fucntion!\n"); + rt_kprintf("tick is :%d !\n", rt_tick_get()); + + return 0; +} + +static int hwtimer_sample(int argc, char *argv[]) +{ + /* 查找定时器设备 */ + hw_dev = rt_device_find(HWTIMER_DEV_NAME); + /* 以读写方式打开设备 */ + rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); + /* 设置超时回调函数 */ + rt_device_set_rx_indicate(hw_dev, timeout_cb); + + /* 设置计数频率(默认1Mhz或支持的最小计数频率) */ + rt_device_control(hw_dev, HWTIMER_CTRL_FREQ_SET, &freq); + /* 设置模式为周期性定时器 */ + mode = HWTIMER_MODE_PERIOD; + rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode); +} +``` + +### 设置定时器超时值 + +通过如下函数可以设置定时器的超时值: + +```c +rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| pos | 写入数据偏移量,未使用,可取 0 值 | +| buffer | 指向定时器超时时间结构体的指针 | +| size | 超时时间结构体的大小 | +| **返回** | —— | +| 写入数据的实际大小 | | +| 0 | 失败 | + +超时时间结构体原型如下所示: + +```c +typedef struct rt_hwtimerval +{ + rt_int32_t sec; /* 秒 s */ + rt_int32_t usec; /* 微秒 us */ +} rt_hwtimerval_t; +``` + +设置定时器超时值的使用示例如下所示: + +```c +#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */ +rt_device_t hw_dev; /* 定时器设备句柄 */ +rt_hwtimer_mode_t mode; /* 定时器模式 */ +rt_hwtimerval_t timeout_s; /* 定时器超时值 */ + +/* 定时器超时回调函数 */ +static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("this is hwtimer timeout callback fucntion!\n"); + rt_kprintf("tick is :%d !\n", rt_tick_get()); + + return 0; +} + +static int hwtimer_sample(int argc, char *argv[]) +{ + /* 查找定时器设备 */ + hw_dev = rt_device_find(HWTIMER_DEV_NAME); + /* 以读写方式打开设备 */ + rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); + /* 设置超时回调函数 */ + rt_device_set_rx_indicate(hw_dev, timeout_cb); + /* 设置模式为周期性定时器 */ + mode = HWTIMER_MODE_PERIOD; + rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode); + + /* 设置定时器超时值为5s并启动定时器 */ + timeout_s.sec = 5; /* 秒 */ + timeout_s.usec = 0; /* 微秒 */ + rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)); +} +``` + +### 获取定时器当前值 + +通过如下函数可以获取定时器当前值: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 定时器设备句柄 | +| pos | 写入数据偏移量,未使用,可取 0 值 | +| buffer | 输出参数,指向定时器超时时间结构体的指针 | +| size | 超时时间结构体的大小 | +| **返回** | —— | +| 超时时间结构体的大小 | 成功 | +| 0 | 失败 | + +使用示例如下所示: + +```c +rt_hwtimerval_t timeout_s; /* 用于保存定时器经过时间 */ +/* 读取定时器经过时间 */ +rt_device_read(hw_dev, 0, &timeout_s, sizeof(timeout_s)); +``` + +### 关闭定时器设备 + +通过如下函数可以关闭定时器设备: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------- | +| dev | 定时器设备句柄 | +| **返回** | —— | +| RT_EOK | 关闭设备成功 | +| -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + + 关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + + 使用示例如下所示: + +```c +#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */ +rt_device_t hw_dev; /* 定时器设备句柄 */ +/* 查找定时器设备 */ +hw_dev = rt_device_find(HWTIMER_DEV_NAME); +... ... +rt_device_close(hw_dev); +``` + +> [!NOTE] +> 注:可能出现定时误差。假设计数器最大值 0xFFFF,计数频率 1Mhz,定时时间 1 秒又 1 微秒。由于定时器一次最多只能计时到 65535us,对于 1000001us 的定时要求。可以 50000us 定时 20 次完成,此时将会出现计算误差 1us。 + +## 硬件定时器设备使用示例 + +硬件定时器设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 首先根据定时器设备名称 “timer0” 查找设备获取设备句柄。 + +2. 以读写方式打开设备 “timer0” 。 + +3. 设置定时器超时回调函数。 + +4. 设置定时器模式为周期性定时器,并设置超时时间为 5 秒,此时定时器启动。 + +5. 延时 3500ms 后读取定时器时间,读取到的值会以秒和微秒的形式显示。 + +```c +/* + * 程序清单:这是一个 hwtimer 设备使用例程 + * 例程导出了 hwtimer_sample 命令到控制终端 + * 命令调用格式:hwtimer_sample + * 程序功能:硬件定时器超时回调函数周期性的打印当前tick值,2次tick值之差换算为时间等同于定时时间值。 +*/ + +#include +#include + +#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */ + +/* 定时器超时回调函数 */ +static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("this is hwtimer timeout callback fucntion!\n"); + rt_kprintf("tick is :%d !\n", rt_tick_get()); + + return 0; +} + +static int hwtimer_sample(int argc, char *argv[]) +{ + rt_err_t ret = RT_EOK; + rt_hwtimerval_t timeout_s; /* 定时器超时值 */ + rt_device_t hw_dev = RT_NULL; /* 定时器设备句柄 */ + rt_hwtimer_mode_t mode; /* 定时器模式 */ + + /* 查找定时器设备 */ + hw_dev = rt_device_find(HWTIMER_DEV_NAME); + if (hw_dev == RT_NULL) + { + rt_kprintf("hwtimer sample run failed! can't find %s device!\n", HWTIMER_DEV_NAME); + return RT_ERROR; + } + + /* 以读写方式打开设备 */ + ret = rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); + if (ret != RT_EOK) + { + rt_kprintf("open %s device failed!\n", HWTIMER_DEV_NAME); + return ret; + } + + /* 设置超时回调函数 */ + rt_device_set_rx_indicate(hw_dev, timeout_cb); + + /* 设置模式为周期性定时器 */ + mode = HWTIMER_MODE_PERIOD; + ret = rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode); + if (ret != RT_EOK) + { + rt_kprintf("set mode failed! ret is :%d\n", ret); + return ret; + } + + /* 设置定时器超时值为5s并启动定时器 */ + timeout_s.sec = 5; /* 秒 */ + timeout_s.usec = 0; /* 微秒 */ + + if (rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s)) + { + rt_kprintf("set timeout value failed\n"); + return RT_ERROR; + } + + /* 延时3500ms */ + rt_thread_mdelay(3500); + + /* 读取定时器当前值 */ + rt_device_read(hw_dev, 0, &timeout_s, sizeof(timeout_s)); + rt_kprintf("Read: Sec = %d, Usec = %d\n", timeout_s.sec, timeout_s.usec); + + return ret; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(hwtimer_sample, hwtimer sample); +``` \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c.vsdx b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..f3a5e74a3fd8b0a7a97e9bb8feaf7cb0681d33c4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c.vsdx differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c1.png b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c1.png new file mode 100644 index 0000000000000000000000000000000000000000..7b3b2d204d3a3a7a2e10c7328f5ee24f53bb8634 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c2.png b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c2.png new file mode 100644 index 0000000000000000000000000000000000000000..efcaff5e735e5cc7d2b9032efab33d98c454927e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c3.png b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c3.png new file mode 100644 index 0000000000000000000000000000000000000000..a8ad1a1c9311ae814d35192a92bee230d2e0082d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/figures/i2c3.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/i2c.md b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/i2c.md new file mode 100644 index 0000000000000000000000000000000000000000..50f5842fcf877ef28656997f2a9f49fbe4005676 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/i2c/i2c.md @@ -0,0 +1,307 @@ +# I2C 总线设备 + +## I2C 简介 + +I2C(Inter Integrated Circuit)总线是 PHILIPS 公司开发的一种半双工、双向二线制同步串行总线。I2C 总线传输数据时只需两根信号线,一根是双向数据线 SDA(serial data),另一根是双向时钟线 SCL(serial clock)。SPI 总线有两根线分别用于主从设备之间接收数据和发送数据,而 I2C 总线只使用一根线进行数据收发。 + +I2C 和 SPI 一样以主从的方式工作,不同于 SPI 一主多从的结构,它允许同时有多个主设备存在,每个连接到总线上的器件都有唯一的地址,主设备启动数据传输并产生时钟信号,从设备被主设备寻址,同一时刻只允许有一个主设备。如下图所示: + +![I2C 总线主从设备连接方式](figures/i2c1.png) + +如下图所示为 I2C 总线主要的数据传输格式: + +![I2C 总线数据传输格式](figures/i2c2.png) + +当总线空闲时,SDA 和 SCL 都处于高电平状态,当主机要和某个从机通讯时,会先发送一个开始条件,然后发送从机地址和读写控制位,接下来传输数据(主机发送或者接收数据),数据传输结束时主机会发送停止条件。传输的每个字节为8位,高位在前,低位在后。数据传输过程中的不同名词详解如下所示: + +* **开始条件:** SCL 为高电平时,主机将 SDA 拉低,表示数据传输即将开始。 + +* **从机地址:** 主机发送的第一个字节为从机地址,高 7 位为地址,最低位为 R/W 读写控制位,1 表示读操作,0 表示写操作。一般从机地址有 7 位地址模式和 10 位地址模式两种,如果是 10 位地址模式,第一个字节的头 7 位 是 11110XX 的组合,其中最后两位(XX)是 10 位地址的两个最高位,第二个字节为 10 位从机地址的剩下8位,如下图所示: + +![7 位地址和 10 位地址格式](figures/i2c3.png) + +* **应答信号:** 每传输完成一个字节的数据,接收方就需要回复一个 ACK(acknowledge)。写数据时由从机发送 ACK,读数据时由主机发送 ACK。当主机读到最后一个字节数据时,可发送 NACK(Not acknowledge)然后跟停止条件。 + +* **数据:** 从机地址发送完后可能会发送一些指令,依从机而定,然后开始传输数据,由主机或者从机发送,每个数据为 8 位,数据的字节数没有限制。 + +* **重复开始条件:** 在一次通信过程中,主机可能需要和不同的从机传输数据或者需要切换读写操作时,主机可以再发送一个开始条件。 + +* **停止条件:** 在 SDA 为低电平时,主机将 SCL 拉高并保持高电平,然后在将 SDA 拉高,表示传输结束。 + +## 访问 I2C 总线设备 + +一般情况下 MCU 的 I2C 器件都是作为主机和从机通讯,在 RT-Thread 中将 I2C 主机虚拟为 I2C总线设备,I2C 从机通过 I2C 设备接口和 I2C 总线通讯,相关接口如下所示: + +| **函数** | **描述** | +| --------------- | ---------------------------------- | +| rt_device_find() | 根据 I2C 总线设备名称查找设备获取设备句柄 | +| rt_i2c_transfer() | 传输数据 | + +### 查找 I2C 总线设备 + +在使用 I2C 总线设备前需要根据 I2C 总线设备名称获取设备句柄,进而才可以操作 I2C 总线设备,查找设备函数如下所示, + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | I2C 总线设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +一般情况下,注册到系统的 I2C 设备名称为 i2c0 ,i2c1等,使用示例如下所示: + +```c +#define AHT10_I2C_BUS_NAME "i2c1" /* 传感器连接的I2C总线设备名称 */ +struct rt_i2c_bus_device *i2c_bus; /* I2C总线设备句柄 */ + +/* 查找I2C总线设备,获取I2C总线设备句柄 */ +i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name); +``` + +### 数据传输 + +获取到 I2C 总线设备句柄就可以使用 `rt_i2c_transfer()` 进行数据传输。函数原型如下所示: + +```c +rt_size_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, + struct rt_i2c_msg msgs[], + rt_uint32_t num); +``` + +| **参数** | **描述** | +|--------------------|----------------------| +| bus | I2C 总线设备句柄 | +| msgs[] | 待传输的消息数组指针 | +| num | 消息数组的元素个数 | +| **返回** | —— | +| 消息数组的元素个数 | 成功 | +| 错误码 | 失败 | + +和 SPI 总线的自定义传输接口一样,I2C 总线的自定义传输接口传输的数据也是以一个消息为单位。参数 msgs[] 指向待传输的消息数组,用户可以自定义每条消息的内容,实现 I2C 总线所支持的 2 种不同的数据传输模式。如果主设备需要发送重复开始条件,则需要发送 2 个消息。 + +> [!NOTE] +> 注:此函数会调用 rt_mutex_take(), 不能在中断服务程序里面调用,会导致 assertion 报错。 + +I2C 消息数据结构原型如下: + +```c +struct rt_i2c_msg +{ + rt_uint16_t addr; /* 从机地址 */ + rt_uint16_t flags; /* 读、写标志等 */ + rt_uint16_t len; /* 读写数据字节数 */ + rt_uint8_t *buf; /* 读写数据缓冲区指针 */ +} +``` + +从机地址 addr:支持 7 位和 10 位二进制地址,需查看不同设备的数据手册 。 + +> [!NOTE] +> 注:RT-Thread I2C 设备接口使用的从机地址均不包含读写位,读写位控制需修改标志 flags。 + +标志 flags 可取值为以下宏定义,根据需要可以与其他宏使用位运算 “|” 组合起来使用。 + +```c +#define RT_I2C_WR 0x0000 /* 写标志 */ +#define RT_I2C_RD (1u << 0) /* 读标志 */ +#define RT_I2C_ADDR_10BIT (1u << 2) /* 10 位地址模式 */ +#define RT_I2C_NO_START (1u << 4) /* 无开始条件 */ +#define RT_I2C_IGNORE_NACK (1u << 5) /* 忽视 NACK */ +#define RT_I2C_NO_READ_ACK (1u << 6) /* 读的时候不发送 ACK */ +``` + +使用示例如下所示: + +```c +#define AHT10_I2C_BUS_NAME "i2c1" /* 传感器连接的I2C总线设备名称 */ +#define AHT10_ADDR 0x38 /* 从机地址 */ +struct rt_i2c_bus_device *i2c_bus; /* I2C总线设备句柄 */ + +/* 查找I2C总线设备,获取I2C总线设备句柄 */ +i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name); + +/* 读传感器寄存器数据 */ +static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf) +{ + struct rt_i2c_msg msgs; + + msgs.addr = AHT10_ADDR; /* 从机地址 */ + msgs.flags = RT_I2C_RD; /* 读标志 */ + msgs.buf = buf; /* 读写数据缓冲区指针 */ + msgs.len = len; /* 读写数据字节数 */ + + /* 调用I2C设备接口传输数据 */ + if (rt_i2c_transfer(bus, &msgs, 1) == 1) + { + return RT_EOK; + } + else + { + return -RT_ERROR; + } +} +``` + +## I2C 总线设备使用示例 + +I2C 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 首先根据 I2C 设备名称查找 I2C 名称,获取设备句柄,然后初始化 aht10 传感器。 + +2. 控制传感器的 2 的函数为写传感器寄存器 write_reg() 和读传感器寄存器 read_regs(),这两个函数分别调用了 rt_i2c_transfer() 传输数据。读取温湿度信息的函数 read_temp_humi() 则是调用这两个函数完成功能。 + +```c +/* + * 程序清单:这是一个 I2C 设备使用例程 + * 例程导出了 i2c_aht10_sample 命令到控制终端 + * 命令调用格式:i2c_aht10_sample i2c1 + * 命令解释:命令第二个参数是要使用的I2C总线设备名称,为空则使用默认的I2C总线设备 + * 程序功能:通过 I2C 设备读取温湿度传感器 aht10 的温湿度数据并打印 +*/ + +#include +#include + +#define AHT10_I2C_BUS_NAME "i2c1" /* 传感器连接的I2C总线设备名称 */ +#define AHT10_ADDR 0x38 /* 从机地址 */ +#define AHT10_CALIBRATION_CMD 0xE1 /* 校准命令 */ +#define AHT10_NORMAL_CMD 0xA8 /* 一般命令 */ +#define AHT10_GET_DATA 0xAC /* 获取数据命令 */ + +static struct rt_i2c_bus_device *i2c_bus = RT_NULL; /* I2C总线设备句柄 */ +static rt_bool_t initialized = RT_FALSE; /* 传感器初始化状态 */ + +/* 写传感器寄存器 */ +static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t *data) +{ + rt_uint8_t buf[3]; + struct rt_i2c_msg msgs; + + buf[0] = reg; //cmd + buf[1] = data[0]; + buf[2] = data[1]; + + msgs.addr = AHT10_ADDR; + msgs.flags = RT_I2C_WR; + msgs.buf = buf; + msgs.len = 3; + + /* 调用I2C设备接口传输数据 */ + if (rt_i2c_transfer(bus, &msgs, 1) == 1) + { + return RT_EOK; + } + else + { + return -RT_ERROR; + } +} + +/* 读传感器寄存器数据 */ +static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf) +{ + struct rt_i2c_msg msgs; + + msgs.addr = AHT10_ADDR; + msgs.flags = RT_I2C_RD; + msgs.buf = buf; + msgs.len = len; + + /* 调用I2C设备接口传输数据 */ + if (rt_i2c_transfer(bus, &msgs, 1) == 1) + { + return RT_EOK; + } + else + { + return -RT_ERROR; + } +} + +static void read_temp_humi(float *cur_temp, float *cur_humi) +{ + rt_uint8_t temp[6]; + + write_reg(i2c_bus, AHT10_GET_DATA, 0); /* 发送命令 */ + rt_thread_mdelay(400); + read_regs(i2c_bus, 6, temp); /* 获取传感器数据 */ + + /* 湿度数据转换 */ + *cur_humi = (temp[1] << 12 | temp[2] << 4 | (temp[3] & 0xf0) >> 4) * 100.0 / (1 << 20); + /* 温度数据转换 */ + *cur_temp = ((temp[3] & 0xf) << 16 | temp[4] << 8 | temp[5]) * 200.0 / (1 << 20) - 50; +} + +static void aht10_init(const char *name) +{ + rt_uint8_t temp[2] = {0, 0}; + + /* 查找I2C总线设备,获取I2C总线设备句柄 */ + i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name); + + if (i2c_bus == RT_NULL) + { + rt_kprintf("can't find %s device!\n", name); + } + else + { + write_reg(i2c_bus, AHT10_NORMAL_CMD, temp); + rt_thread_mdelay(400); + + temp[0] = 0x08; + temp[1] = 0x00; + write_reg(i2c_bus, AHT10_CALIBRATION_CMD, temp); + rt_thread_mdelay(400); + initialized = RT_TRUE; + } +} + +static void i2c_aht10_sample(int argc, char *argv[]) +{ + float humidity, temperature; + char name[RT_NAME_MAX]; + + humidity = 0.0; + temperature = 0.0; + + if (argc == 2) + { + rt_strncpy(name, argv[1], RT_NAME_MAX); + } + else + { + rt_strncpy(name, AHT10_I2C_BUS_NAME, RT_NAME_MAX); + } + + if (!initialized) + { + /* 传感器初始化 */ + aht10_init(name); + } + if (initialized) + { + /* 读取温湿度数据 */ + read_temp_humi(&temperature, &humidity); + + rt_kprintf("read aht10 sensor humidity : %d.%d %%\n", (int)humidity, (int)(humidity * 10) % 10); + if( temperature >= 0 ) + { + rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature, (int)(temperature * 10) % 10); + } + else + { + rt_kprintf("read aht10 sensor temperature: %d.%d°C\n", (int)temperature, (int)(-temperature * 10) % 10); + } + } + else + { + rt_kprintf("initialize sensor failed!\n"); + } +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(i2c_aht10_sample, i2c aht10 sample); +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/pin/figures/pin.vsdx b/rt-thread-version/rt-thread-standard/programming-manual/device/pin/figures/pin.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..9f05478fadbfd3be77e6a409c1dddbd9a36c298f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/pin/figures/pin.vsdx differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/pin/figures/pin2.png b/rt-thread-version/rt-thread-standard/programming-manual/device/pin/figures/pin2.png new file mode 100644 index 0000000000000000000000000000000000000000..79d7dfc94c42e763184e9b8edce8f9bf937cd85f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/pin/figures/pin2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin.md b/rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin.md new file mode 100644 index 0000000000000000000000000000000000000000..a7d764335f86599e40b98addde1bd69efc2a9a5c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin.md @@ -0,0 +1,353 @@ +# PIN 设备 + +## 引脚简介 + +芯片上的引脚一般分为 4 类:电源、时钟、控制与 I/O,I/O 口在使用模式上又分为 General Purpose Input Output(通用输入 / 输出),简称 GPIO,与功能复用 I/O(如 SPI/I2C/UART 等)。 + +大多数 MCU 的引脚都不止一个功能。不同引脚内部结构不一样,拥有的功能也不一样。可以通过不同的配置,切换引脚的实际功能。通用 I/O 口主要特性如下: + +* 可编程控制中断:中断触发模式可配置,一般有下图所示 5 种中断触发模式: + +![5 种中断触发模式](figures/pin2.png) + +* 输入输出模式可控制。 + + * 输出模式一般包括:推挽、开漏、上拉、下拉。引脚为输出模式时,可以通过配置引脚输出的电平状态为高电平或低电平来控制连接的外围设备。 + + * 输入模式一般包括:浮空、上拉、下拉、模拟。引脚为输入模式时,可以读取引脚的电平状态,即高电平或低电平。 + +## 访问 PIN 设备 + +应用程序通过 RT-Thread 提供的 PIN 设备管理接口来访问 GPIO,相关接口如下所示: + +| **函数** | **描述** | +| ---------------- | ---------------------------------- | +| rt_pin_mode() | 设置引脚模式 | +| rt_pin_write() | 设置引脚电平 | +| rt_pin_read() | 读取引脚电平 | +| rt_pin_attach_irq() | 绑定引脚中断回调函数 | +| rt_pin_irq_enable() | 使能引脚中断 | +| rt_pin_detach_irq() | 脱离引脚中断回调函数 | + +### 获取引脚编号 + +RT-Thread 提供的引脚编号需要和芯片的引脚号区分开来,它们并不是同一个概念,引脚编号由 PIN 设备驱动程序定义,和具体的芯片相关。有2种方式可以获取引脚编号:使用宏定义或者查看PIN 驱动文件。 + +#### 使用宏定义 + +如果使用 `rt-thread/bsp/stm32` 目录下的 BSP 则可以使用下面的宏获取引脚编号: + +```c +GET_PIN(port, pin) +``` + +获取引脚号为 PF9 的 LED0 对应的引脚编号的示例代码如下所示: + +```c +#define LED0_PIN GET_PIN(F, 9) +``` + +#### 查看驱动文件 + +如果使用其他 BSP 则需要查看 PIN 驱动代码 drv_gpio.c 文件确认引脚编号。此文件里有一个数组存放了每个 PIN 脚对应的编号信息,如下所示: + +```c +static const rt_uint16_t pins[] = +{ + __STM32_PIN_DEFAULT, + __STM32_PIN_DEFAULT, + __STM32_PIN(2, A, 15), + __STM32_PIN(3, B, 5), + __STM32_PIN(4, B, 8), + __STM32_PIN_DEFAULT, + __STM32_PIN_DEFAULT, + __STM32_PIN_DEFAULT, + __STM32_PIN(8, A, 14), + __STM32_PIN(9, B, 6), + ... ... +} +``` + +以`__STM32_PIN(2, A, 15)`为例,2 为 RT-Thread 使用的引脚编号,A 为端口号,15 为引脚号,所以 PA15 对应的引脚编号为 2。 + +### 设置引脚模式 + +引脚在使用前需要先设置好输入或者输出模式,通过如下函数完成: + +```c +void rt_pin_mode(rt_base_t pin, rt_base_t mode); +``` + +| **参数** | **描述** | +|----------|--------------| +| pin | 引脚编号 | +| mode | 引脚工作模式 | + +目前 RT-Thread 支持的引脚工作模式可取如所示的 5 种宏定义值之一,每种模式对应的芯片实际支持的模式需参考 PIN 设备驱动程序的具体实现: + +```c +#define PIN_MODE_OUTPUT 0x00 /* 输出 */ +#define PIN_MODE_INPUT 0x01 /* 输入 */ +#define PIN_MODE_INPUT_PULLUP 0x02 /* 上拉输入 */ +#define PIN_MODE_INPUT_PULLDOWN 0x03 /* 下拉输入 */ +#define PIN_MODE_OUTPUT_OD 0x04 /* 开漏输出 */ +``` + +使用示例如下所示: + +```c +#define BEEP_PIN_NUM 35 /* PB0 */ + +/* 蜂鸣器引脚为输出模式 */ +rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); +``` + +### 设置引脚电平 + +设置引脚输出电平的函数如下所示: + +```c +void rt_pin_write(rt_base_t pin, rt_base_t value); +``` + +| **参数** | **描述** | +|----------|-------------------------| +| pin | 引脚编号 | +| value | 电平逻辑值,可取 2 种宏定义值之一:PIN_LOW 低电平,PIN_HIGH 高电平 | + +使用示例如下所示: + +```c +#define BEEP_PIN_NUM 35 /* PB0 */ + +/* 蜂鸣器引脚为输出模式 */ +rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); +/* 设置低电平 */ +rt_pin_write(BEEP_PIN_NUM, PIN_LOW); +``` + +### 读取引脚电平 + +读取引脚电平的函数如下所示: + +```c +int rt_pin_read(rt_base_t pin); +``` + +| **参数** | **描述** | +|----------|----------| +| pin | 引脚编号 | +| **返回** | —— | +| PIN_LOW | 低电平 | +| PIN_HIGH | 高电平 | + +使用示例如下所示: + +```c +#define BEEP_PIN_NUM 35 /* PB0 */ +int status; + +/* 蜂鸣器引脚为输出模式 */ +rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); +/* 设置低电平 */ +rt_pin_write(BEEP_PIN_NUM, PIN_LOW); + +status = rt_pin_read(BEEP_PIN_NUM); +``` + +### 绑定引脚中断回调函数 + +若要使用到引脚的中断功能,可以使用如下函数将某个引脚配置为某种中断触发模式并绑定一个中断回调函数到对应引脚,当引脚中断发生时,就会执行回调函数: + +```c +rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, + void (*hdr)(void *args), void *args); +``` + +| **参数** | **描述** | +|----------|--------------------------------------| +| pin | 引脚编号 | +| mode | 中断触发模式 | +| hdr | 中断回调函数,用户需要自行定义这个函数 | +| args | 中断回调函数的参数,不需要时设置为 RT_NULL | +| **返回** | —— | +| RT_EOK | 绑定成功 | +| 错误码 | 绑定失败 | + +中断触发模式 mode 可取如下 5 种宏定义值之一: + +```c +#define PIN_IRQ_MODE_RISING 0x00 /* 上升沿触发 */ +#define PIN_IRQ_MODE_FALLING 0x01 /* 下降沿触发 */ +#define PIN_IRQ_MODE_RISING_FALLING 0x02 /* 边沿触发(上升沿和下降沿都触发)*/ +#define PIN_IRQ_MODE_HIGH_LEVEL 0x03 /* 高电平触发 */ +#define PIN_IRQ_MODE_LOW_LEVEL 0x04 /* 低电平触发 */ +``` + +使用示例如下所示: + +```c +#define KEY0_PIN_NUM 55 /* PD8 */ +/* 中断回调函数 */ +void beep_on(void *args) +{ + rt_kprintf("turn on beep!\n"); + + rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); +} +static void pin_beep_sample(void) +{ + /* 按键0引脚为输入模式 */ + rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP); + /* 绑定中断,上升沿模式,回调函数名为beep_on */ + rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL); +} +``` + +### 使能引脚中断 + +绑定好引脚中断回调函数后使用下面的函数使能引脚中断: + +```c +rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled); +``` + +| **参数** | **描述** | +|----------|----------------| +| pin | 引脚编号 | +| enabled | 状态,可取 2 种值之一:PIN_IRQ_ENABLE(开启),PIN_IRQ_DISABLE(关闭) | +| **返回** | —— | +| RT_EOK | 使能成功 | +| 错误码 | 使能失败 | + +使用示例如下所示: + +```c +#define KEY0_PIN_NUM 55 /* PD8 */ +/* 中断回调函数 */ +void beep_on(void *args) +{ + rt_kprintf("turn on beep!\n"); + + rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); +} +static void pin_beep_sample(void) +{ + /* 按键0引脚为输入模式 */ + rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP); + /* 绑定中断,上升沿模式,回调函数名为beep_on */ + rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL); + /* 使能中断 */ + rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE); +} +``` + +### 脱离引脚中断回调函数 + +可以使用如下函数脱离引脚中断回调函数: + +```c +rt_err_t rt_pin_detach_irq(rt_int32_t pin); +``` + +| **参数** | **描述** | +|----------|----------| +| pin | 引脚编号 | +| **返回** | —— | +| RT_EOK | 脱离成功 | +| 错误码 | 脱离失败 | + +引脚脱离了中断回调函数以后,中断并没有关闭,还可以调用绑定中断回调函数再次绑定其他回调函数。 + +```c +#define KEY0_PIN_NUM 55 /* PD8 */ +/* 中断回调函数 */ +void beep_on(void *args) +{ + rt_kprintf("turn on beep!\n"); + + rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); +} +static void pin_beep_sample(void) +{ + /* 按键0引脚为输入模式 */ + rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP); + /* 绑定中断,上升沿模式,回调函数名为beep_on */ + rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL); + /* 使能中断 */ + rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE); + /* 脱离中断回调函数 */ + rt_pin_detach_irq(KEY0_PIN_NUM); +} +``` + +## PIN 设备使用示例 + +PIN 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 设置蜂鸣器对应引脚为输出模式,并给一个默认的低电平状态。 + +2. 设置按键 0 和 按键1 对应引脚为输入模式,然后绑定中断回调函数并使能中断。 + +3. 按下按键 0 蜂鸣器开始响,按下按键 1 蜂鸣器停止响。 + +```c +/* + * 程序清单:这是一个 PIN 设备使用例程 + * 例程导出了 pin_beep_sample 命令到控制终端 + * 命令调用格式:pin_beep_sample + * 程序功能:通过按键控制蜂鸣器对应引脚的电平状态控制蜂鸣器 +*/ + +#include +#include + +/* 引脚编号,通过查看设备驱动文件drv_gpio.c确定 */ +#ifndef BEEP_PIN_NUM + #define BEEP_PIN_NUM 35 /* PB0 */ +#endif +#ifndef KEY0_PIN_NUM + #define KEY0_PIN_NUM 55 /* PD8 */ +#endif +#ifndef KEY1_PIN_NUM + #define KEY1_PIN_NUM 56 /* PD9 */ +#endif + +void beep_on(void *args) +{ + rt_kprintf("turn on beep!\n"); + + rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); +} + +void beep_off(void *args) +{ + rt_kprintf("turn off beep!\n"); + + rt_pin_write(BEEP_PIN_NUM, PIN_LOW); +} + +static void pin_beep_sample(void) +{ + /* 蜂鸣器引脚为输出模式 */ + rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); + /* 默认低电平 */ + rt_pin_write(BEEP_PIN_NUM, PIN_LOW); + + /* 按键0引脚为输入模式 */ + rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP); + /* 绑定中断,下降沿模式,回调函数名为beep_on */ + rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL); + /* 使能中断 */ + rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE); + + /* 按键1引脚为输入模式 */ + rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP); + /* 绑定中断,下降沿模式,回调函数名为beep_off */ + rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_off, RT_NULL); + /* 使能中断 */ + rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE); +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(pin_beep_sample, pin beep sample); +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/pulse_encoder/pulse_encoder.md b/rt-thread-version/rt-thread-standard/programming-manual/device/pulse_encoder/pulse_encoder.md new file mode 100644 index 0000000000000000000000000000000000000000..286f2a5c08a581047ae7500adeb5ab64eb8559aa --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/pulse_encoder/pulse_encoder.md @@ -0,0 +1,222 @@ +# Pulse Encoder 设备 + +## 脉冲编码器简介 + +脉冲编码器是利用光学、磁性或机械接点的方式感测位置,并将位置信息转换为电子信号后输出的传感器。其输出的电子信号一般被用作控制位置时的回授信号。 + +脉冲编码器按照工作原理可分为增量式和绝对式两类。增量式编码器是将位移转换成周期性的电信号,再把这个电信号转变成计数脉冲,用脉冲的个数表示位移的大小。绝对式编码器的每一个位置对应一个确定的数字码,因此它的示值只与测量的起始和终止位置有关,而与测量的中间过程无关。当代大多数的微控制器都提供了编码器外设用于接收存储脉冲编码器的信号。 + +## 访问脉冲编码器设备 + +应用程序通过 RT-Thread 提供的 I/O 设备管理接口来访问脉冲编码器设备,相关接口如下所示: + +| **函数** | **描述** | +| -------------------- | ---------------------------------- | +| rt_device_find() | 查找脉冲编码器设备 | +| rt_device_open() | 打开脉冲编码器设备(仅支持只读的方式) | +| rt_device_control() | 控制脉冲编码器设备,可以清空计数值、获取类型、使能设备。 | +| rt_device_read() | 获取脉冲编码器当前值 | +| rt_device_close() | 关闭脉冲编码器设备 | + +### 查找脉冲编码器设备 + +应用程序根据脉冲编码器的设备名称获取设备句柄,进而可以操作脉冲编码器设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | 脉冲编码器的设备名称 | +| **返回** | —— | +| 脉冲编码器设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到设备 | + +一般情况下,注册到系统的脉冲编码器的设备名称为 pulse1,pulse2等,使用示例如下所示: + +```c +#define PULSE_ENCODER_DEV_NAME "pulse1" /* 脉冲编码器名称 */ +rt_device_t pulse_encoder_dev; /* 脉冲编码器设备句柄 */ +/* 查找脉冲编码器设备 */ +pulse_encoder_dev = rt_device_find(PULSE_ENCODER_DEV_NAME); +``` + +### 打开脉冲编码器设备 + +通过设备句柄,应用程序可以打开设备。打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | 脉冲编码器设备句柄 | +| oflags | 设备打开模式,一般以只读方式打开,即取值:RT_DEVICE_OFLAG_RDONLY | +| **返回** | —— | +| RT_EOK | 设备打开成功 | +| 其他错误码 | 设备打开失败 | + +使用示例如下所示: + +```c +#define PULSE_ENCODER_DEV_NAME "pulse1" /* 脉冲编码器名称 */ +rt_device_t pulse_encoder_dev; /* 脉冲编码器设备句柄 */ +/* 查找脉冲编码器设备 */ +pulse_encoder_dev = rt_device_find(PULSE_ENCODER_DEV_NAME); +/* 以只读方式打开设备 */ +rt_device_open(pulse_encoder_dev, RT_DEVICE_OFLAG_RDONLY); +``` + +### 控制脉冲编码器设备 + +通过命令控制字,应用程序可以对脉冲编码器设备进行设置,通过如下函数完成: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| cmd | 命令控制字 | +| arg | 控制的参数 | +| **返回** | —— | +| RT_EOK | 函数执行成功 | +| -RT_ENOSYS | 执行失败,dev 为空 | +| 其他错误码 | 执行失败 | + +脉冲编码器设备支持的命令控制字如下所示: + +| **控制字** | **描述** | +| ---------------------- | ------------------------ | +| PULSE_ENCODER_CMD_GET_TYPE | 获取脉冲编码器类型 | +| PULSE_ENCODER_CMD_ENABLE | 使能脉冲编码器 | +| PULSE_ENCODER_CMD_DISABLE | 失能脉冲编码器 | +| PULSE_ENCODER_CMD_CLEAR_COUNT | 清空编码器计数值 | + +### 读取脉冲编码器计数值 + +通过如下函数可以读取脉冲编码器计数值: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| pos | 固定值为 0 | +| buffer | rt_int32_t 类型变量的地址,用于存放脉冲编码器的值。 | +| size | 固定值为 1 | +| **返回** | —— | +| 固定返回值 | 返回 1 | + +使用示例如下所示: + +```c +#define PULSE_ENCODER_DEV_NAME "pulse1" /* 脉冲编码器名称 */ +rt_device_t pulse_encoder_dev; /* 脉冲编码器设备句柄 */ +rt_int32_t count; +/* 查找脉冲编码器设备 */ +pulse_encoder_dev = rt_device_find(PULSE_ENCODER_DEV_NAME); +/* 以只读方式打开设备 */ +rt_device_open(pulse_encoder_dev, RT_DEVICE_OFLAG_RDONLY); +/* 读取脉冲编码器计数值 */ +rt_device_read(pulse_encoder_dev, 0, &count, 1); +``` + +### 关闭脉冲编码器设备 + +通过如下函数可以关闭脉冲编码器设备: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------- | +| dev | 脉冲编码器设备句柄 | +| **返回** | —— | +| RT_EOK | 关闭设备成功 | +| -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + + 关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + + 使用示例如下所示: + +```c +#define PULSE_ENCODER_DEV_NAME "pulse1" /* 脉冲编码器名称 */ +rt_device_t pulse_encoder_dev; /* 脉冲编码器设备句柄 */ +/* 查找定时器设备 */ +pulse_encoder_dev = rt_device_find(PULSE_ENCODER_DEV_NAME); +... ... +rt_device_close(pulse_encoder_dev); +``` + +## 脉冲编码器设备使用示例 + +脉冲编码器设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 首先根据脉冲编码器的设备名称 “pulse1” 查找设备获取设备句柄。 + +2. 以只读方式打开设备 “pulse1” 。 + +3. 读取脉冲编码器设备的计数值。 + +4. 清空脉冲编码器的计数值。(可选步骤) + +```c +/* + * 程序清单:这是一个脉冲编码器设备使用例程 + * 例程导出了 pulse_encoder_sample 命令到控制终端 + * 命令调用格式:pulse_encoder_sample + * 程序功能:每隔 500 ms 读取一次脉冲编码器外设的计数值,然后清空计数值,将读取到的计数值打印出来。 +*/ + +#include +#include + +#define PULSE_ENCODER_DEV_NAME "pulse1" /* 脉冲编码器名称 */ + +static int pulse_encoder_sample(int argc, char *argv[]) +{ + rt_err_t ret = RT_EOK; + rt_device_t pulse_encoder_dev = RT_NULL; /* 脉冲编码器设备句柄 */ + + rt_int32_t count; + + /* 查找脉冲编码器设备 */ + pulse_encoder_dev = rt_device_find(PULSE_ENCODER_DEV_NAME); + if (pulse_encoder_dev == RT_NULL) + { + rt_kprintf("pulse encoder sample run failed! can't find %s device!\n", PULSE_ENCODER_DEV_NAME); + return RT_ERROR; + } + + /* 以只读方式打开设备 */ + ret = rt_device_open(pulse_encoder_dev, RT_DEVICE_OFLAG_RDONLY); + if (ret != RT_EOK) + { + rt_kprintf("open %s device failed!\n", PULSE_ENCODER_DEV_NAME); + return ret; + } + + for(rt_uint32 i; i <= 10; i++) + { + rt_thread_mdelay(500); + /* 读取脉冲编码器计数值 */ + rt_device_read(pulse_encoder_dev, 0, &count, 1); + /* 清空脉冲编码器计数值 */ + rt_device_control(pulse_encoder_dev, PULSE_ENCODER_CMD_CLEAR_COUNT, RT_NULL); + rt_kprintf("get count %d\n",count); + } + + rt_device_close(pulse_encoder_dev); + return ret; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(pulse_encoder_sample, pulse encoder sample); +``` \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm-f.png b/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm-f.png new file mode 100644 index 0000000000000000000000000000000000000000..a107af8c7dadb726ec4e0031d2ea20996095c451 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm-f.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm-l.png b/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm-l.png new file mode 100644 index 0000000000000000000000000000000000000000..e1927459bb51ebf9d35dad5a1da5a8b9dad4dd39 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm-l.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm.vsdx b/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..a3f03017f5b2d40c5256e7274bb999af8ba95649 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/figures/pwm.vsdx differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/pwm.md b/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/pwm.md new file mode 100644 index 0000000000000000000000000000000000000000..5b6c4e47e52245cc95ed12a809c10fb061a7bccb --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/pwm/pwm.md @@ -0,0 +1,265 @@ +# PWM 设备 + +## PWM 简介 + +PWM(Pulse Width Modulation , 脉冲宽度调制) 是一种对模拟信号电平进行数字编码的方法,通过不同频率的脉冲使用方波的占空比用来对一个具体模拟信号的电平进行编码,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替所需要波形的设备。 + +![PWM 原理图](figures/pwm-f.png) + +上图是一个简单的 PWM 原理示意图,假定定时器工作模式为向上计数,当计数值小于阈值时,则输出一种电平状态,比如高电平,当计数值大于阈值时则输出相反的电平状态,比如低电平。当计数值达到最大值是,计数器从0开始重新计数,又回到最初的电平状态。高电平持续时间(脉冲宽度)和周期时间的比值就是占空比,范围为0~100%。上图高电平的持续时间刚好是周期时间的一半,所以占空比为50%。 + +一个比较常用的pwm控制情景就是用来调节灯或者屏幕的亮度,根据占空比的不同,就可以完成亮度的调节。PWM调节亮度并不是持续发光的,而是在不停地点亮、熄灭屏幕。当亮、灭交替够快时,肉眼就会认为一直在亮。在亮、灭的过程中,灭的状态持续时间越长,屏幕给肉眼的观感就是亮度越低。亮的时间越长,灭的时间就相应减少,屏幕就会变亮。 + +![PWM 调节亮度](figures/pwm-l.png) + +## 访问 PWM 设备 + +应用程序通过 RT-Thread 提供的 PWM 设备管理接口来访问 PWM 设备硬件,相关接口如下所示: + +| **函数** | **描述** | +| ----------------- | ---------------------------------- | +| rt_device_find() | 根据 PWM 设备名称查找设备获取设备句柄 | +| rt_pwm_set() | 设置 PWM 周期和脉冲宽度 | +| rt_pwm_enable() | 使能 PWM 设备 | +| rt_pwm_disable() | 关闭 PWM 设备 | + +### 查找 PWM 设备 + +应用程序根据 PWM 设备名称获取设备句柄,进而可以操作 PWM 设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | 设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到设备 | + +一般情况下,注册到系统的 PWM 设备名称为 pwm0,pwm1等,使用示例如下所示: + +```c +#define PWM_DEV_NAME "pwm3" /* PWM 设备名称 */ +struct rt_device_pwm *pwm_dev; /* PWM 设备句柄 */ +/* 查找设备 */ +pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); +``` + +### 设置 PWM 周期和脉冲宽度 + +通过如下函数设置 PWM 周期和占空比: + +```c +rt_err_t rt_pwm_set(struct rt_device_pwm *device, + int channel, + rt_uint32_t period, + rt_uint32_t pulse); +``` + +| **参数** | **描述** | +| ---------- | ----------------- | +| device | PWM 设备句柄 | +| channel | PWM 通道 | +| period | PWM 周期时间 (单位纳秒 ns) | +| pulse | PWM 脉冲宽度时间 (单位纳秒 ns) | +| **返回** | —— | +| RT_EOK | 成功 | +| -RT_EIO | device 为空 | +| -RT_ENOSYS | 设备操作方法为空 | +| 其他错误码 | 执行失败 | + +PWM 的输出频率由周期时间 period 决定,例如周期时间为 0.5ms (毫秒),则 period 值为 500000ns(纳秒),输出频率为 2KHz,占空比为 pulse / period,pulse 值不能超过 period。 + +使用示例如下所示: + +```c +#define PWM_DEV_NAME "pwm3" /* PWM设备名称 */ +#define PWM_DEV_CHANNEL 4 /* PWM通道 */ +struct rt_device_pwm *pwm_dev; /* PWM设备句柄 */ +rt_uint32_t period, pulse; + +period = 500000; /* 周期为0.5ms,单位为纳秒ns */ +pulse = 0; /* PWM脉冲宽度值,单位为纳秒ns */ +/* 查找设备 */ +pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); +/* 设置PWM周期和脉冲宽度 */ +rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); +``` + +### 使能 PWM 设备 + +设置好 PWM 周期和脉冲宽度后就可以通过如下函数使能 PWM 设备: + +```c +rt_err_t rt_pwm_enable(struct rt_device_pwm *device, int channel); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| device | PWM 设备句柄 | +| channel | PWM 通道 | +| **返回** | —— | +| RT_EOK | 设备使能成功 | +| -RT_ENOSYS | 设备操作方法为空 | +| 其他错误码 | 设备使能失败 | + +使用示例如下所示: + +```c +#define PWM_DEV_NAME "pwm3" /* PWM设备名称 */ +#define PWM_DEV_CHANNEL 4 /* PWM通道 */ +struct rt_device_pwm *pwm_dev; /* PWM设备句柄 */ +rt_uint32_t period, pulse; + +period = 500000; /* 周期为0.5ms,单位为纳秒ns */ +pulse = 0; /* PWM脉冲宽度值,单位为纳秒ns */ +/* 查找设备 */ +pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); +/* 设置PWM周期和脉冲宽度 */ +rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); +/* 使能设备 */ +rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL); +``` + +### 关闭 PWM 设备通道 + +通过如下函数关闭 PWM 设备对应通道。 + +```c +rt_err_t rt_pwm_disable(struct rt_device_pwm *device, int channel); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| device | PWM 设备句柄 | +| channel | PWM 通道 | +| **返回** | —— | +| RT_EOK | 设备关闭成功 | +| -RT_EIO | 设备句柄为空 | +| 其他错误码 | 设备关闭失败 | + +使用示例如下所示: + +```c +#define PWM_DEV_NAME "pwm3" /* PWM设备名称 */ +#define PWM_DEV_CHANNEL 4 /* PWM通道 */ +struct rt_device_pwm *pwm_dev; /* PWM设备句柄 */ +rt_uint32_t period, pulse; + +period = 500000; /* 周期为0.5ms,单位为纳秒ns */ +pulse = 0; /* PWM脉冲宽度值,单位为纳秒ns */ +/* 查找设备 */ +pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); +/* 设置PWM周期和脉冲宽度 */ +rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); +/* 使能设备 */ +rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL); +/* 关闭设备通道 */ +rt_pwm_disable(pwm_dev,PWM_DEV_CHANNEL); +``` + + + +## FinSH 命令 + +设置 PWM 设备的某个通道的周期和占空比可使用命令`pwm_set pwm1 1 500000 5000`,第一个参数为命令,第二个参数为 PWM 设备名称,第 3 个参数为 PWM 通道,第 4 个参数为周期(单位纳秒),第 5 个参数为脉冲宽度(单位纳秒)。 + +```c +msh />pwm_set pwm1 1 500000 5000 +msh /> +``` + +使能 PWM 设备的某个通道可使用命令`pwm_enable pwm1 1`,第一个参数为命令,第二个参数为 PWM 设备名称,第 3 个参数为 PWM 通道。 + +```c +msh />pwm_enable pwm1 1 +msh /> +``` + +关闭 PWM 设备的某个通道可使用命令`pwm_disable pwm1 1`,第一个参数为命令,第二个参数为 PWM 设备名称,第 3 个参数为 PWM 通道。 + +```c +msh />pwm_disable pwm1 1 +msh /> +``` + +## PWM 设备使用示例 + +PWM 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 查找 PWM 设备获取设备句柄。 + +2. 设置 PWM 周期和脉冲宽度。 + +3. 使能 PWM 设备。 + +4. while 循环里每 50 毫秒修改一次脉冲宽度。 + +* 将 PWM通道对应引脚和 LED 对应引脚相连,可以看到 LED 不停的由暗变到亮,然后又从亮变到暗。 + +```c +/* + * 程序清单:这是一个 PWM 设备使用例程 + * 例程导出了 pwm_led_sample 命令到控制终端 + * 命令调用格式:pwm_led_sample + * 程序功能:通过 PWM 设备控制 LED 灯的亮度,可以看到LED不停的由暗变到亮,然后又从亮变到暗。 +*/ + +#include +#include + +#define PWM_DEV_NAME "pwm3" /* PWM设备名称 */ +#define PWM_DEV_CHANNEL 4 /* PWM通道 */ + +struct rt_device_pwm *pwm_dev; /* PWM设备句柄 */ + +static int pwm_led_sample(int argc, char *argv[]) +{ + rt_uint32_t period, pulse, dir; + + period = 500000; /* 周期为0.5ms,单位为纳秒ns */ + dir = 1; /* PWM脉冲宽度值的增减方向 */ + pulse = 0; /* PWM脉冲宽度值,单位为纳秒ns */ + + /* 查找设备 */ + pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); + if (pwm_dev == RT_NULL) + { + rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM_DEV_NAME); + return RT_ERROR; + } + + /* 设置PWM周期和脉冲宽度默认值 */ + rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); + /* 使能设备 */ + rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL); + + while (1) + { + rt_thread_mdelay(50); + if (dir) + { + pulse += 5000; /* 从0值开始每次增加5000ns */ + } + else + { + pulse -= 5000; /* 从最大值开始每次减少5000ns */ + } + if (pulse >= period) + { + dir = 0; + } + if (0 == pulse) + { + dir = 1; + } + + /* 设置PWM周期和脉冲宽度 */ + rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse); + } +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(pwm_led_sample, pwm sample); +``` \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/rtc/rtc.md b/rt-thread-version/rt-thread-standard/programming-manual/device/rtc/rtc.md new file mode 100644 index 0000000000000000000000000000000000000000..fabb7ec675369e25b8dddf0c4b5a9ceb2dce3712 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/rtc/rtc.md @@ -0,0 +1,199 @@ +# RTC 设备 + +## RTC 简介 + +RTC (Real-Time Clock)实时时钟可以提供精确的实时时间,它可以用于产生年、月、日、时、分、秒等信息。目前实时时钟芯片大多采用精度较高的晶体振荡器作为时钟源。有些时钟芯片为了在主电源掉电时还可以工作,会外加电池供电,使时间信息一直保持有效。 + +RT-Thread 的 RTC设备为操作系统的时间系统提供了基础服务。面对越来越多的 IoT 场景,RTC 已经成为产品的标配,甚至在诸如 SSL 的安全传输过程中,RTC 已经成为不可或缺的部分。 + + +## 访问 RTC 设备 + +应用程序通过 RTC 设备管理接口来访问 RTC 硬件,相关接口如下所示: + +| **函数** | **描述** | +| ------------- | ---------------------------------- | +| set_date() | 设置日期,年、月、日 | +| set_time() | 设置时间,时、分、秒 | +| time() | 获取当前时间 | + +### 设置日期 + +通过如下函数设置 RTC 设备当前日期值: + +```c +rt_err_t set_date(rt_uint32_t year, rt_uint32_t month, rt_uint32_t day) +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +|year |待设置生效的年份| +|month |待设置生效的月份| +|day |待设置生效的日| +| **返回** | —— | +| RT_EOK | 设置成功 | +| -RT_ERROR | 失败,没有找到 rtc 设备 | +| 其他错误码 | 失败 | + +使用示例如下所示: + +```c +/* 设置日期为2018年12月3号 */ +set_date(2018, 12, 3); +``` + +### 设置时间 + +通过如下函数设置 RTC 设备当前时间值: + +```c +rt_err_t set_time(rt_uint32_t hour, rt_uint32_t minute, rt_uint32_t second) +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +|hour |待设置生效的时| +|minute |待设置生效的分| +|second |待设置生效的秒| +| **返回** | —— | +| RT_EOK | 设置成功 | +| -RT_ERROR | 失败,没有找到 rtc 设备 | +| 其他错误码 | 失败 | + +使用示例如下所示: + +```c +/* 设置时间为11点15分50秒 */ +set_time(11, 15, 50); +``` + +### 获取当前时间 + +使用到 C 标准库中的时间 API 获取时间: + +```c +time_t time(time_t *t) +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +|t |时间数据指针 | +| **返回** | —— | +| 当前时间值 | | + +使用示例如下所示: + +```c +time_t now; /* 保存获取的当前时间值 */ +/* 获取时间 */ +now = time(RT_NULL); +/* 打印输出时间信息 */ +rt_kprintf("%s\n", ctime(&now)); +``` + +> [!NOTE] +> 注:目前系统内只允许存在一个 RTC 设备,且名称为 `"rtc"` 。 + +## 功能配置 + +### 启用 Soft RTC (软件模拟 RTC) + +在 menuconfig 中可以启用使用软件模拟 RTC 的功能,这个模式非常适用于对时间精度要求不高,没有硬件 RTC 的产品。配置选项如下所示: + +```c +RT-Thread Components → + Device Drivers: + -*- Using RTC device drivers /* 使用 RTC 设备驱动 */ + [ ] Using software simulation RTC device /* 使用软件模拟 RTC */ +``` + +### 启用 NTP 时间自动同步 + +如果 RT-Thread 已接入互联网,可启用 NTP 时间自动同步功能,定期同步本地时间。 + +首先在 menuconfig 中按照如下选项开启 NTP 功能: + +```c +RT-Thread online packages → + IoT - internet of things → + netutils: Networking utilities for RT-Thread: + [*] Enable NTP(Network Time Protocol) client +``` + +开启 NTP 后 RTC 的自动同步功能将会自动开启,还可以设置同步周期和首次同步的延时时间: + +```c +RT-Thread Components → + Device Drivers: + -*- Using RTC device drivers /* 使用 RTC 设备驱动 */ + [ ] Using software simulation RTC device /* 使用软件模拟 RTC */ + [*] Using NTP auto sync RTC time /* 使用 NTP 自动同步 RTC 时间 */ + (30) NTP first sync delay time(second) for network connect /* 首次执行 NTP 时间同步的延时。延时的目的在于,给网络连接预留一定的时间,尽量提高第一次执行 NTP 时间同步时的成功率。默认时间为 30S; */ + (3600) NTP auto sync period(second) /* NTP 自动同步周期,单位为秒,默认一小时(即 3600S)同步一次。 */ +``` + +## FinSH 命令 + +输入 `date` 即可查看当前时间,大致效果如下: + +```c +msh />date +Fri Feb 16 01:11:56 2018 +msh /> +``` + +同样使用 `date` 命令,在命令后面再依次输入 ` 年 ` ` 月 ` ` 日 ` ` 时 ` ` 分 ` ` 秒 ` (中间空格隔开, 24H 制),设置当前时间为 2018-02-16 01:15:30,大致效果如下: + +```c +msh />date 2018 02 16 01 15 30 +msh /> +``` + +## RTC 设备使用示例 + +RTC 设备的具体使用方式可以参考如下示例代码,首先设置了年月日时分秒信息,然后延时 3 秒后获取当前时间信息。 + +```c +/* + * 程序清单:这是一个 RTC 设备使用例程 + * 例程导出了 rtc_sample 命令到控制终端 + * 命令调用格式:rtc_sample + * 程序功能:设置RTC设备的日期和时间,延时一段时间后获取当前时间并打印显示。 +*/ + +#include +#include + +static int rtc_sample(int argc, char *argv[]) +{ + rt_err_t ret = RT_EOK; + time_t now; + + /* 设置日期 */ + ret = set_date(2018, 12, 3); + if (ret != RT_EOK) + { + rt_kprintf("set RTC date failed\n"); + return ret; + } + + /* 设置时间 */ + ret = set_time(11, 15, 50); + if (ret != RT_EOK) + { + rt_kprintf("set RTC time failed\n"); + return ret; + } + + /* 延时3秒 */ + rt_thread_mdelay(3000); + + /* 获取时间 */ + now = time(RT_NULL); + rt_kprintf("%s\n", ctime(&now)); + + return ret; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(rtc_sample, rtc sample); +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/sensor/sensor.md b/rt-thread-version/rt-thread-standard/programming-manual/device/sensor/sensor.md new file mode 100644 index 0000000000000000000000000000000000000000..f6a4a0ddd7651512d0f8cbc7b73cba8c1dc44fdd --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/sensor/sensor.md @@ -0,0 +1,453 @@ +# SENSOR 设备 + +## SENSOR 简介 + +Sensor(传感器)是物联网重要的一部分,“Sensor 之于物联网”就相当于“眼睛之于人类”。人类如果没有了眼睛就看不到这大千的花花世界,对于物联网来说也是一样。 + +如今随着物联网的发展,已经有大量的 Sensor 被开发出来供开发者选择了,如:加速度计(Accelerometer)、磁力计(Magnetometer)、陀螺仪(Gyroscope)、气压计(Barometer/pressure)、湿度计(Humidometer)等。这些传感器,世界上的各大半导体厂商都有生产,虽然增加了市场的可选择性,同时也加大了应用程序开发的难度。因为不同的传感器厂商、不同的传感器都需要配套自己独有的驱动才能运转起来,这样在开发应用程序的时候就需要针对不同的传感器做适配,自然加大了开发难度。为了降低应用开发的难度,增加传感器驱动的可复用性,我们设计了 Sensor 设备。 + +Sensor 设备的作用是:为上层提供统一的操作接口,提高上层代码的可重用性。 + +### 传感器设备特性 + +- 接口:标准 device 接口(open/close/read/control) +- 工作模式:支持 轮询、中断、FIFO 三种模式 +- 电源模式:支持 掉电、普通、低功耗、高功耗 四种模式 + +点击[传感器列表](sensor_list.md),查看当前支持的传感器 + +## 访问传感器设备 + +应用程序通过 RT-Thread 提供的 I/O 设备管理接口来访问传感器设备,相关接口如下所示: + +| **函数** | **描述** | +| --------------------------- | ------------------------------------------ | +| rt_device_find() | 根据传感器设备设备名称查找设备获取设备句柄 | +| rt_device_open() | 打开传感器设备 | +| rt_device_read() | 读取数据 | +| rt_device_control() | 控制传感器设备 | +| rt_device_set_rx_indicate() | 设置接收回调函数 | +| rt_device_close() | 关闭传感器设备 | + +### 查找传感器 + +应用程序根据传感器设备名称获取设备句柄,进而可以操作传感器设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | 传感器设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +使用示例如下所示: +```c +#define SENSOR_DEVICE_NAME "acce_st" /* 传感器设备名称 */ + +static rt_device_t sensor_dev; /* 传感器设备句柄 */ +/* 根据设备名称查找传感器设备,获取设备句柄 */ +sensor_dev = rt_device_find(SENSOR_DEVICE_NAME); +``` + +### 打开传感器设备 + +通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | 设备句柄 | +| oflags | 设备模式标志 | +| **返回** | —— | +| RT_EOK | 设备打开成功 | +| -RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开 | +| -RT_EINVAL | 不支持的打开参数 | +| 其他错误码 | 设备打开失败 | + +oflags 参数支持下列参数: + +```c +#define RT_DEVICE_FLAG_RDONLY 0x001 /* 标准设备的只读模式,对应传感器的轮询模式 */ +#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收模式 */ +#define RT_DEVICE_FLAG_FIFO_RX 0x200 /* FIFO 接收模式 */ +``` + +传感器数据接收和发送数据的模式分为 3 种:中断模式、轮询模式、FIFO 模式。在使用的时候,这 3 种模式只能**选其一**,若传感器的打开参数 oflags 没有指定使用中断模式或者 FIFO 模式,则默认使用轮询模式。 + +FIFO(First Input First Output)即先进先出。 FIFO 传输方式需要传感器硬件支持,数据存储在硬件 FIFO 中,一次读取可以读取多个数据,这就节省了 CPU 的资源来做其他操作。在低功耗模式时非常有用。 + +若传感器要使用 FIFO 接收模式,oflags 取值 RT_DEVICE_FLAG_FIFO_RX。 + +以轮询模式打开传感器设备使用示例如下所示: + +```c +#define SAMPLE_SENSOR_NAME "acce_st" /* 传感器设备名称 */ +int main(void) +{ + rt_device_t dev; + struct rt_sensor_data data; + + /* 查找传感器设备 */ + dev = rt_device_find(SAMPLE_SENSOR_NAME); + /* 以只读及轮询模式打开传感器设备 */ + rt_device_open(dev, RT_DEVICE_FLAG_RDONLY); + + if (rt_device_read(dev, 0, &data, 1) == 1) + { + rt_kprintf("acce: x:%5d, y:%5d, z:%5d, timestamp:%5d\n", data.data.acce.x, data.data.acce.y, data.data.acce.z, data.timestamp); + } + rt_device_close(dev); + + return RT_EOK; +} +``` +### 控制传感器设备 + +通过命令控制字,应用程序可以对传感器设备进行配置,通过如下函数完成: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| cmd | 命令控制字,详细介绍见下面 | +| arg | 控制的参数, 详细介绍见下面 | +| **返回** | —— | +| RT_EOK | 函数执行成功 | +| -RT_ENOSYS | 执行失败,dev 为空 | +| 其他错误码 | 执行失败 | + +其中的 cmd 目前支持以下几种命令控制字 + +```c +#define RT_SENSOR_CTRL_GET_ID /* 读设备ID */ +#define RT_SENSOR_CTRL_GET_INFO /* 获取设备信息 */ +#define RT_SENSOR_CTRL_SET_RANGE /* 设置传感器测量范围 */ +#define RT_SENSOR_CTRL_SET_ODR /* 设置传感器数据输出速率,unit is HZ */ +#define RT_SENSOR_CTRL_SET_POWER /* 设置电源模式 */ +#define RT_SENSOR_CTRL_SELF_TEST /* 自检 */ +``` +#### 获取设备信息 + +```c +struct rt_sensor_info info; +rt_device_control(dev, RT_SENSOR_CTRL_GET_INFO, &info); +LOG_I("vendor :%d", info.vendor); +LOG_I("model :%s", info.model); +LOG_I("unit :%d", info.unit); +LOG_I("intf_type :%d", info.intf_type); +LOG_I("period_min:%d", info.period_min); +``` + +#### 读设备ID + +```c +rt_uint8_t reg = 0xFF; +rt_device_control(dev, RT_SENSOR_CTRL_GET_ID, ®); +LOG_I("device id: 0x%x!", reg); +``` + +#### 设置测量范围 + +设置传感器测量范围时的单位为设备注册时提供的单位。 + +```c +rt_device_control(dev, RT_SENSOR_CTRL_SET_RANGE, (void *)1000); +``` + +#### 设置数据输出速率 + +设置输出速率为 100HZ,调用下面的接口。 + +```c +rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)100); +``` + +#### 设置电源模式 + +```c +/* 设置电源模式为掉电模式 */ +rt_device_control(dev, RT_SENSOR_CTRL_SET_POWER, (void *)RT_SEN_POWER_DOWN); +/* 设置电源模式为普通模式 */ +rt_device_control(dev, RT_SENSOR_CTRL_SET_POWER, (void *)RT_SEN_POWER_NORMAL); +/* 设置电源模式为低功耗模式 */ +rt_device_control(dev, RT_SENSOR_CTRL_SET_POWER, (void *)RT_SEN_POWER_LOW); +/* 设置电源模式为高性能模式 */ +rt_device_control(dev, RT_SENSOR_CTRL_SET_POWER, (void *)RT_SEN_POWER_HIGH); +``` + +#### 设备自检 + +```c +int test_res; +/* 控制设备自检 并把结果返回回来,RT_EOK 自检成功, 其他自检失败 */ +rt_device_control(dev, RT_SENSOR_CTRL_SELF_TEST, &test_res); +``` + +### 设置接收回调函数 + +可以通过如下函数来设置数据接收指示,当传感器收到数据时,通知上层应用线程有数据到达 : + +```c +rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size)); +``` + +| **参数** | **描述** | +| -------- | ------------ | +| dev | 设备句柄 | +| rx_ind | 回调函数指针 | +| dev | 设备句柄(回调函数参数)| +| size | 缓冲区数据大小(回调函数参数)| +| **返回** | —— | +| RT_EOK | 设置成功 | + +该函数的回调函数由调用者提供。若传感器以中断接收模式打开,当传感器接收到数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在 size 参数里,把传感器设备句柄放在 dev 参数里供调用者获取。 + +一般情况下接收回调函数可以发送一个信号量或者事件通知传感器数据处理线程有数据到达。使用示例如下所示: + +```c +#define SAMPLE_SENSOR_NAME "acce_st" /* 传感器设备名称 */ +static rt_device_t dev; /* 传感器设备句柄 */ +static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */ + +/* 接收数据回调函数 */ +static rt_err_t sensor_input(rt_device_t dev, rt_size_t size) +{ + /* 传感器接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ + rt_sem_release(&rx_sem); + + return RT_EOK; +} + +static int sensor_sample(int argc, char *argv[]) +{ + dev = rt_device_find(SAMPLE_SENSOR_NAME); + + /* 以中断接收及轮询发送模式打开传感器设备 */ + rt_device_open(dev, RT_DEVICE_FLAG_INT_RX); + /* 初始化信号量 */ + rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); + + /* 设置接收回调函数 */ + rt_device_set_rx_indicate(dev, sensor_input); +} + +``` + +### 读取数据 + +可调用如下函数读取传感器接收到的数据: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ------------------ | ---------------------------------------------- | +| dev | 设备句柄 | +| pos | 读取数据偏移量,此参数传感器未使用 | +| buffer | 缓冲区指针,读取的数据将会被保存在缓冲区中 | +| size | 读取数据的大小 | +| **返回** | —— | +| 读到数据的实际大小 | 返回读取到数据的个数 | +| 0 | 需要读取当前线程的 errno 来判断错误状态 | + +传感器使用中断接收模式并配合接收回调函数的使用示例如下所示: + +```c +static rt_device_t dev; /* 传感器设备句柄 */ +static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */ + +/* 接收数据的线程 */ +static void sensor_irq_rx_entry(void *parameter) +{ + rt_device_t dev = parameter; + struct rt_sensor_data data; + rt_size_t res; + + while (1) + { + rt_sem_take(rx_sem, RT_WAITING_FOREVER); + + res = rt_device_read(dev, 0, &data, 1); + if (res == 1) + { + sensor_show_data(dev, &data); + } + } +} + +``` + +传感器使用 FIFO 接收模式并配合接收回调函数的使用示例如下所示: + +```c +static rt_sem_t sensor_rx_sem = RT_NULL; +rt_err_t rx_cb(rt_device_t dev, rt_size_t size) +{ + rt_sem_release(sensor_rx_sem); + return 0; +} +static void sensor_fifo_rx_entry(void *parameter) +{ + rt_device_t dev = parameter; + struct rt_sensor_data data; + rt_size_t res, i; + + data = rt_malloc(sizeof(struct rt_sensor_data) * 32); + + while (1) + { + rt_sem_take(sensor_rx_sem, RT_WAITING_FOREVER); + + res = rt_device_read(dev, 0, data, 32); + for (i = 0; i < res; i++) + { + sensor_show_data(dev, &data[i]); + } + } +} +int main(void) +{ + static rt_thread_t tid1 = RT_NULL; + rt_device_t dev; + struct rt_sensor_data data; + + sensor_rx_sem = rt_sem_create("sen_rx_sem", 0, RT_IPC_FLAG_FIFO); + tid1 = rt_thread_create("sen_rx_thread", + sensor_fifo_rx_entry, dev, + 1024, + 15, 5); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + dev = rt_device_find("acce_st"); + rt_device_set_rx_indicate(dev, rx_cb); + rt_device_open(dev, RT_SEN_FLAG_FIFO); + return RT_EOK; +} +``` + +### 关闭传感器设备 + +当应用程序完成传感器操作后,可以关闭传感器设备,通过如下函数完成: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------- | +| dev | 设备句柄 | +| **返回** | —— | +| RT_EOK | 关闭设备成功 | +| -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + +关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + +## 传感器设备使用示例 + +传感器设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 首先查找传感器设置获取设备句柄。 + +2. 以轮询的方式打开传感器。 + +3. 连续读取 5 次数据并打印出来。 + +4. 关闭传感器。 + +* 此示例代码不局限于特定的 BSP,根据 BSP 注册的传感器设备,输入不同的 dev_name 即可运行。 + +```c +/* + * 程序清单:这是一个 传感器 设备使用例程 + * 例程导出了 sensor_sample 命令到控制终端 + * 命令调用格式:sensor_sample dev_name + * 命令解释:命令第二个参数是要使用的传感器设备名称 + * 程序功能:打开对应的传感器,然后连续读取 5 次数据并打印出来。 +*/ + +#include "sensor.h" + +static void sensor_show_data(rt_size_t num, rt_sensor_t sensor, struct rt_sensor_data *sensor_data) +{ + switch (sensor->info.type) + { + case RT_SENSOR_CLASS_ACCE: + rt_kprintf("num:%3d, x:%5d, y:%5d, z:%5d, timestamp:%5d\n", num, sensor_data->data.acce.x, sensor_data->data.acce.y, sensor_data->data.acce.z, sensor_data->timestamp); + break; + case RT_SENSOR_CLASS_GYRO: + rt_kprintf("num:%3d, x:%8d, y:%8d, z:%8d, timestamp:%5d\n", num, sensor_data->data.gyro.x, sensor_data->data.gyro.y, sensor_data->data.gyro.z, sensor_data->timestamp); + break; + case RT_SENSOR_CLASS_MAG: + rt_kprintf("num:%3d, x:%5d, y:%5d, z:%5d, timestamp:%5d\n", num, sensor_data->data.mag.x, sensor_data->data.mag.y, sensor_data->data.mag.z, sensor_data->timestamp); + break; + case RT_SENSOR_CLASS_HUMI: + rt_kprintf("num:%3d, humi:%3d.%d%%, timestamp:%5d\n", num, sensor_data->data.humi / 10, sensor_data->data.humi % 10, sensor_data->timestamp); + break; + case RT_SENSOR_CLASS_TEMP: + rt_kprintf("num:%3d, temp:%3d.%dC, timestamp:%5d\n", num, sensor_data->data.temp / 10, sensor_data->data.temp % 10, sensor_data->timestamp); + break; + case RT_SENSOR_CLASS_BARO: + rt_kprintf("num:%3d, press:%5d, timestamp:%5d\n", num, sensor_data->data.baro, sensor_data->timestamp); + break; + case RT_SENSOR_CLASS_STEP: + rt_kprintf("num:%3d, step:%5d, timestamp:%5d\n", num, sensor_data->data.step, sensor_data->timestamp); + break; + default: + break; + } +} + +static void sensor_sample(int argc, char **argv) +{ + rt_device_t dev = RT_NULL; + struct rt_sensor_data data; + rt_size_t res, i; + + /* 查找系统中的传感器设备 */ + dev = rt_device_find(argv[1]); + if (dev == RT_NULL) + { + rt_kprintf("Can't find device:%s\n", argv[1]); + return; + } + + /* 以轮询模式打开传感器设备 */ + if (rt_device_open(dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK) + { + rt_kprintf("open device failed!"); + return; + } + + for (i = 0; i < 5; i++) + { + /* 从传感器读取一个数据 */ + res = rt_device_read(dev, 0, &data, 1); + if (res != 1) + { + rt_kprintf("read data failed!size is %d", res); + } + else + { + sensor_show_data(i, (rt_sensor_t)dev, &data); + } + rt_thread_mdelay(100); + } + /* 关闭传感器设备 */ + rt_device_close(dev); +} +MSH_CMD_EXPORT(sensor_sample, sensor device sample); +``` + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/sensor/sensor_list.md b/rt-thread-version/rt-thread-standard/programming-manual/device/sensor/sensor_list.md new file mode 100644 index 0000000000000000000000000000000000000000..8448d36be17876e6f1f4f2562d70f7510abb5d04 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/sensor/sensor_list.md @@ -0,0 +1,34 @@ +# 支持的传感器列表 + +下面是一份已经对接到 RT-Therad Sensor 框架上的传感器的列表,点击传感器名称即可跳转到相应软件包主页。(本文档不定时更新,如要查看所有支持的传感器的列表,可以查看最新的[软件包索引](https://github.com/RT-Thread/packages/tree/master/peripherals/sensors)。 + +| 厂商 | 传感器 | 备注 | +| -------------- | ------------------------------------------------------------ | ------------------------ | +| **BOSCH** | | | +| | **[bma400](https://github.com/RT-Thread-packages/bma400)** | 加速度计、计步计 | +| | **[bmi160](https://github.com/RT-Thread-packages/bmi160_bmx160)** | 加速计、陀螺仪 | +| | **[bmx160](https://github.com/RT-Thread-packages/bmi160_bmx160)** | 加速计、陀螺仪、磁力计 | +| | **[bme280](https://github.com/RT-Thread-packages/bme280)** | 气压计、湿度计、温度计 | +| **Goertek** | | | +| | **[spl0601](https://github.com/RT-Thread-packages/spl0601)** | 气压计、温度计 | +| **ST** | | | +| | **[lsm6dsl](https://github.com/RT-Thread-packages/lsm6dsl)** | 加速度计、陀螺仪、计步计 | +| | **[lsm303agr](https://github.com/RT-Thread-packages/lsm303agr)** | 加速度计、磁力计 | +| | **[hts221](https://github.com/RT-Thread-packages/hts221)** | 气压计、气温计 | +| | **[lps22hb](https://github.com/RT-Thread-packages/lps22hb)** | 气压计、气温计 | +| **MiraMEMS** | | | +| | **[da270]()** | 加速度计 | +| | **[df220]()** | 压力计 | +| **ALPSALPINE** | | | +| | **[hshcal001]()** | 湿度计、温度计 | +| **MEAS** | | | +| | **[MS5611]()** | 气压计、温度计 | +| **invensense** | | | +| | **[mpu6xxx(mpu6050/mpu6000/icm20608)]()** | 加速度计、陀螺仪 | +| **ASAIR** | | | +| | **[aht10]()** | 温度计、湿度计 | +| **ROHM** | | | +| | **[BH1750FVI]()** | 环境光照强度 | +| **Richtek** | | | +| | **[RT3020](http://packages.rt-thread.org/itemDetail.html?package=rt3020)** | 加速度计 | + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi.vsdx b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..42657aab19fc401a6226f149216183a5acda5eb0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi.vsdx differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi1.png b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi1.png new file mode 100644 index 0000000000000000000000000000000000000000..2096701713f23a386b6827ec3740f86b206da5df Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi2.png b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi2.png new file mode 100644 index 0000000000000000000000000000000000000000..975196d0797036a4b791ad319f8b5bb1dc3cbe92 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi5.png b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi5.png new file mode 100644 index 0000000000000000000000000000000000000000..2e71536451ce4c025d2370d67e07a46ccb140c01 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/figures/spi5.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi.md b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi.md new file mode 100644 index 0000000000000000000000000000000000000000..41b14869564dd49a1958d5f5ba2988fe89154389 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi.md @@ -0,0 +1,741 @@ +# SPI 设备 + +## SPI 简介 + +SPI(Serial Peripheral Interface,串行外设接口)是一种高速、全双工、同步通信总线,常用于短距离通讯,主要应用于 EEPROM、FLASH、实时时钟、AD 转换器、还有数字信号处理器和数字信号解码器之间。SPI 一般使用 4 根线通信,如下图所示: + +![SPI 主设备和从设备的连接方式](figures/spi1.png) + +* MOSI –主机输出 / 从机输入数据线(SPI Bus Master Output/Slave Input)。 + +* MISO –主机输入 / 从机输出数据线(SPI Bus Master Input/Slave Output)。 + +* SCLK –串行时钟线(Serial Clock),主设备输出时钟信号至从设备。 + +* CS –从设备选择线 (Chip select)。也叫 SS、CSB、CSN、EN 等,主设备输出片选信号至从设备。 + +SPI 以主从方式工作,通常有一个主设备和一个或多个从设备。通信由主设备发起,主设备通过 CS 选择要通信的从设备,然后通过 SCLK 给从设备提供时钟信号,数据通过 MOSI 输出给从设备,同时通过 MISO 接收从设备发送的数据。 + +如下图所示芯片有 2 个 SPI 控制器,SPI 控制器对应 SPI 主设备,每个 SPI 控制器可以连接多个 SPI 从设备。挂载在同一个 SPI 控制器上的从设备共享 3 个信号引脚:SCK、MISO、MOSI,但每个从设备的 CS 引脚是独立的。 + +![一个 SPI 主设备与多个从设备连接](figures/spi2.png) + +主设备通过控制 CS 引脚对从设备进行片选,一般为低电平有效。任何时刻,一个 SPI 主设备上只有一个 CS 引脚处于有效状态,与该有效 CS 引脚连接的从设备此时可以与主设备通信。 + +从设备的时钟由主设备通过 SCLK 提供,MOSI、MISO 则基于此脉冲完成数据传输。SPI 的工作时序模式由 CPOL(Clock Polarity,时钟极性)和 CPHA(Clock Phase,时钟相位)之间的相位关系决定,CPOL 表示时钟信号的初始电平的状态,CPOL 为 0 表示时钟信号初始状态为低电平,为 1 表示时钟信号的初始电平是高电平。CPHA 表示在哪个时钟沿采样数据,CPHA 为 0 表示在首个时钟变化沿采样数据,而 CPHA 为 1 则表示在第二个时钟变化沿采样数据。根据 CPOL 和 CPHA 的不同组合共有 4 种工作时序模式:①CPOL=0,CPHA=0、②CPOL=0,CPHA=1、③CPOL=1,CPHA=0、④CPOL=1,CPHA=1。如下图所示: + +![SPI 4 种工作模式时序图](figures/spi5.png) + +**QSPI:** QSPI 是 Queued SPI 的简写,是 Motorola 公司推出的 SPI 接口的扩展,比 SPI 应用更加广泛。在 SPI 协议的基础上,Motorola 公司对其功能进行了增强,增加了队列传输机制,推出了队列串行外围接口协议(即 QSPI 协议)。使用该接口,用户可以一次性传输包含多达 16 个 8 位或 16 位数据的传输队列。一旦传输启动,直到传输结束,都不需要 CPU 干预,极大的提高了传输效率。与 SPI 相比,QSPI 的最大结构特点是以 80 字节的 RAM 代替了 SPI 的发送和接收数据寄存器。 + +**Dual SPI Flash:** 对于 SPI Flash 而言全双工并不常用,可以发送一个命令字节进入 Dual 模式,让它工作在半双工模式,用以加倍数据传输。这样 MOSI 变成 SIO0(serial io 0),MISO 变成 SIO1(serial io 1),这样一个时钟周期内就能传输 2 个 bit 数据,加倍了数据传输。 + +**Quad SPI Flash:** 与 Dual SPI 类似,Quad SPI Flash增加了两根 I/O 线(SIO2,SIO3),目的是一个时钟内传输 4 个 bit 数据。 + +所以对于 SPI Flash,有标准 SPI Flash,Dual SPI Flash, Quad SPI Flash 三种类型。在相同时钟下,线数越多传输速率越高。 + +## 挂载 SPI 设备 + +SPI 驱动会注册 SPI 总线,SPI 设备需要挂载到已经注册好的 SPI 总线上。 + +```C +rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device, + const char *name, + const char *bus_name, + void *user_data) +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| device | SPI 设备句柄 | +| name | SPI 设备名称 | +| bus_name | SPI 总线名称 | +| user_data | 用户数据指针 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他错误码 | 失败 | + +此函数用于挂载一个 SPI 设备到指定的 SPI 总线,并向内核注册 SPI 设备,并将 user_data 保存到 SPI 设备的控制块里。 + +一般 SPI 总线命名原则为 spix, SPI 设备命名原则为 spixy ,如 spi10 表示挂载在 spi1 总线上的 0 号设备。user_data 一般为 SPI 设备的 CS 引脚指针,进行数据传输时 SPI 控制器会操作此引脚进行片选。 + +若使用 rt-thread/bsp/stm32 目录下的 BSP 则可以使用下面的函数挂载 SPI 设备到总线: + +```c +rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_TypeDef* cs_gpiox, uint16_t cs_gpio_pin); +``` + +下面的示例代码挂载 SPI FLASH W25Q128 到 SPI 总线: + +```c +static int rt_hw_spi_flash_init(void) +{ + __HAL_RCC_GPIOB_CLK_ENABLE(); + rt_hw_spi_device_attach("spi1", "spi10", GPIOB, GPIO_PIN_14); + + if (RT_NULL == rt_sfud_flash_probe("W25Q128", "spi10")) + { + return -RT_ERROR; + }; + + return RT_EOK; +} +/* 导出到自动初始化 */ +INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init); +``` + +## 配置 SPI 设备 + +挂载 SPI 设备到 SPI 总线后需要配置 SPI 设备的传输参数。 + +```c +rt_err_t rt_spi_configure(struct rt_spi_device *device, + struct rt_spi_configuration *cfg) +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| device | SPI 设备句柄 | +| cfg | SPI 配置参数指针 | +| **返回** | —— | +| RT_EOK | 成功 | + +此函数会保存 cfg 指向的配置参数到 SPI 设备 device 的控制块里,当传输数据时会使用此配置参数。 + +struct rt_spi_configuration 原型如下: + +```c +struct rt_spi_configuration +{ + rt_uint8_t mode; /* 模式 */ + rt_uint8_t data_width; /* 数据宽度,可取8位、16位、32位 */ + rt_uint16_t reserved; /* 保留 */ + rt_uint32_t max_hz; /* 最大频率 */ +}; +``` + +**模式:** 包含 MSB/LSB、主从模式、 时序模式等,可取宏组合如下: + +```c +/* 设置数据传输顺序是MSB位在前还是LSB位在前 */ +#define RT_SPI_LSB (0<<2) /* bit[2]: 0-LSB */ +#define RT_SPI_MSB (1<<2) /* bit[2]: 1-MSB */ + +/* 设置SPI的主从模式 */ +#define RT_SPI_MASTER (0<<3) /* SPI master device */ +#define RT_SPI_SLAVE (1<<3) /* SPI slave device */ + +/* 设置时钟极性和时钟相位 */ +#define RT_SPI_MODE_0 (0 | 0) /* CPOL = 0, CPHA = 0 */ +#define RT_SPI_MODE_1 (0 | RT_SPI_CPHA) /* CPOL = 0, CPHA = 1 */ +#define RT_SPI_MODE_2 (RT_SPI_CPOL | 0) /* CPOL = 1, CPHA = 0 */ +#define RT_SPI_MODE_3 (RT_SPI_CPOL | RT_SPI_CPHA) /* CPOL = 1, CPHA = 1 */ + +#define RT_SPI_CS_HIGH (1<<4) /* Chipselect active high */ +#define RT_SPI_NO_CS (1<<5) /* No chipselect */ +#define RT_SPI_3WIRE (1<<6) /* SI/SO pin shared */ +#define RT_SPI_READY (1<<7) /* Slave pulls low to pause */ +``` + +**数据宽度:** 根据 SPI 主设备及 SPI 从设备可发送及接收的数据宽度格式设置为8位、16位或者32位。 + +**最大频率:** 设置数据传输的波特率,同样根据 SPI 主设备及 SPI 从设备工作的波特率范围设置。 + +配置示例如下所示: + +```c + struct rt_spi_configuration cfg; + cfg.data_width = 8; + cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; + cfg.max_hz = 20 * 1000 *1000; /* 20M */ + + rt_spi_configure(spi_dev, &cfg); +``` + +## 配置 QSPI 设备 + +配置 QSPI 设备的传输参数可使用如下函数: + +```c +rt_err_t rt_qspi_configure(struct rt_qspi_device *device, struct rt_qspi_configuration *cfg); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| device | QSPI 设备句柄 | +| cfg | QSPI 配置参数指针 | +| **返回** | —— | +| RT_EOK | 成功 | + +此函数会保存 cfg 指向的配置参数到 QSPI 设备 device 的控制块里,当传输数据时会使用此配置参数。 + +struct rt_qspi_configuration 原型如下: + +```c +struct rt_qspi_configuration +{ + struct rt_spi_configuration parent; /* 继承自 SPI 设备配置参数 */ + rt_uint32_t medium_size; /* 介质大小 */ + rt_uint8_t ddr_mode; /* 双倍速率模式 */ + rt_uint8_t qspi_dl_width ; /* QSPI 总线位宽,单线模式 1 位、双线模式 2 位,4 线模式 4 位 */ +}; +``` + +## 访问 SPI 设备 + +一般情况下 MCU 的 SPI 器件都是作为主机和从机通讯,在 RT-Thread 中将 SPI 主机虚拟为 SPI 总线设备,应用程序使用 SPI 设备管理接口来访问 SPI 从机器件,主要接口如下所示: + +| **函数** | **描述** | +| -------------------- | ---------------------------------- | +| rt_device_find() | 根据 SPI 设备名称查找设备获取设备句柄 | +| rt_spi_transfer_message() | 自定义传输数据 | +| rt_spi_transfer() | 传输一次数据 | +| rt_spi_send() | 发送一次数据 | +| rt_spi_recv() | 接受一次数据 | +| rt_spi_send_then_send() | 连续两次发送 | +| rt_spi_send_then_recv() | 先发送后接收 | + +> [!NOTE] +> 注:SPI 数据传输相关接口会调用 rt_mutex_take(), 此函数不能在中断服务程序里面调用,会导致 assertion 报错。 + +### 查找 SPI 设备 + +在使用 SPI 设备前需要根据 SPI 设备名称获取设备句柄,进而才可以操作 SPI 设备,查找设备函数如下所示, + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | 设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +一般情况下,注册到系统的 SPI 设备名称为 spi10, qspi10等,使用示例如下所示: + +```c +#define W25Q_SPI_DEVICE_NAME "qspi10" /* SPI 设备名称 */ +struct rt_spi_device *spi_dev_w25q; /* SPI 设备句柄 */ + +/* 查找 spi 设备获取设备句柄 */ +spi_dev_w25q = (struct rt_spi_device *)rt_device_find(W25Q_SPI_DEVICE_NAME); +``` + +### 自定义传输数据 + +获取到 SPI 设备句柄就可以使用 SPI 设备管理接口访问 SPI 设备器件,进行数据收发。可以通过如下函数传输消息: + +```c +struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device,struct rt_spi_message *message); +``` + +| **参数** | **描述** | +|----------|--------------------------------------------| +| device | SPI 设备句柄 | +| message | 消息指针 | +| **返回** | —— | +| RT_NULL | 成功发送 | +| 非空指针 | 发送失败,返回指向剩余未发送的 message 的指针 | + +此函数可以传输一连串消息,用户可以自定义每个待传输的 message 结构体各参数的数值,从而可以很方便的控制数据传输方式。struct rt_spi_message 原型如下: + +```c +struct rt_spi_message +{ + const void *send_buf; /* 发送缓冲区指针 */ + void *recv_buf; /* 接收缓冲区指针 */ + rt_size_t length; /* 发送 / 接收 数据字节数 */ + struct rt_spi_message *next; /* 指向继续发送的下一条消息的指针 */ + unsigned cs_take : 1; /* 片选选中 */ + unsigned cs_release : 1; /* 释放片选 */ +}; +``` +sendbuf 为发送缓冲区指针,其值为 RT_NULL 时,表示本次传输为只接收状态,不需要发送数据。 + +recvbuf 为接收缓冲区指针,其值为 RT_NULL 时,表示本次传输为只发送状态,不需要保存接收到的数据,所以收到的数据直接丢弃。 + +length 的单位为 word,即数据长度为 8 位时,每个 length 占用 1 个字节;当数据长度为 16 位时,每个 length 占用 2 个字节。 + +参数 next 是指向继续发送的下一条消息的指针,若只发送一条消息,则此指针值为 RT_NULL。多个待传输的消息通过 next 指针以单向链表的形式连接在一起。 + +cs_take 值为 1 时,表示在传输数据前,设置对应的 CS 为有效状态。cs_release 值为 1 时,表示在数据传输结束后,释放对应的 CS。 + +> [!NOTE] +> 注:* 当 send_buf 或 recv_buf 不为空时,两者的可用空间都不得小于 length。 + * 若使用此函数传输消息,传输的第一条消息 cs_take 需置为 1,设置片选为有效,最后一条消息的 cs_release 需置 1,释放片选。 + +使用示例如下所示: + +```c +#define W25Q_SPI_DEVICE_NAME "qspi10" /* SPI 设备名称 */ +struct rt_spi_device *spi_dev_w25q; /* SPI 设备句柄 */ +struct rt_spi_message msg1, msg2; +rt_uint8_t w25x_read_id = 0x90; /* 命令 */ +rt_uint8_t id[5] = {0}; + +/* 查找 spi 设备获取设备句柄 */ +spi_dev_w25q = (struct rt_spi_device *)rt_device_find(W25Q_SPI_DEVICE_NAME); +/* 发送命令读取ID */ +struct rt_spi_message msg1, msg2; + +msg1.send_buf = &w25x_read_id; +msg1.recv_buf = RT_NULL; +msg1.length = 1; +msg1.cs_take = 1; +msg1.cs_release = 0; +msg1.next = &msg2; + +msg2.send_buf = RT_NULL; +msg2.recv_buf = id; +msg2.length = 5; +msg2.cs_take = 0; +msg2.cs_release = 1; +msg2.next = RT_NULL; + +rt_spi_transfer_message(spi_dev_w25q, &msg1); +rt_kprintf("use rt_spi_transfer_message() read w25q ID is:%x%x\n", id[3], id[4]); +``` + +### 传输一次数据 + +如果只传输一次数据可以通过如下函数: + +```c +rt_size_t rt_spi_transfer(struct rt_spi_device *device, + const void *send_buf, + void *recv_buf, + rt_size_t length); +``` + +| **参数** | **描述** | +|----------|----------------------| +| device | SPI 设备句柄 | +| send_buf | 发送数据缓冲区指针 | +| recv_buf | 接收数据缓冲区指针 | +| length | 发送/接收 数据字节数 | +| **返回** | —— | +| 0 | 传输失败 | +| 非 0 值 | 成功传输的字节数 | + +此函数等同于调用`rt_spi_transfer_message()` 传输一条消息,开始发送数据时片选选中,函数返回时释放片选,message 参数配置如下: + +```c +struct rt_spi_message msg; + +msg.send_buf = send_buf; +msg.recv_buf = recv_buf; +msg.length = length; +msg.cs_take = 1; +msg.cs_release = 1; +msg.next = RT_NULL; +``` + +### 发送一次数据 + +如果只发送一次数据,而忽略接收到的数据可以通过如下函数: + +```c +rt_size_t rt_spi_send(struct rt_spi_device *device, + const void *send_buf, + rt_size_t length) +``` + +| **参数** | **描述** | +|----------|--------------------| +| device | SPI 设备句柄 | +| send_buf | 发送数据缓冲区指针 | +| length | 发送数据字节数 | +| **返回** | —— | +| 0 | 发送失败 | +| 非 0 值 | 成功发送的字节数 | + +调用此函数发送 send_buf 指向的缓冲区的数据,忽略接收到的数据,此函数是对 `rt_spi_transfer()` 函数的封装。 + +此函数等同于调用 `rt_spi_transfer_message()` 传输一条消息,开始发送数据时片选选中,函数返回时释放片选,message 参数配置如下: + +```c +struct rt_spi_message msg; + +msg.send_buf = send_buf; +msg.recv_buf = RT_NULL; +msg.length = length; +msg.cs_take = 1; +msg.cs_release = 1; +msg.next = RT_NULL; +``` + +### 接收一次数据 + +如果只接收一次数据可以通过如下函数: + +```c +rt_size_t rt_spi_recv(struct rt_spi_device *device, + void *recv_buf, + rt_size_t length); +``` + +| **参数** | **描述** | +|----------|--------------------| +| device | SPI 设备句柄 | +| recv_buf | 接收数据缓冲区指针 | +| length | 接收数据字节数 | +| **返回** | —— | +| 0 | 接收失败 | +| 非 0 值 | 成功接收的字节数 | + +调用此函数接收数据并保存到 recv_buf 指向的缓冲区。此函数是对 `rt_spi_transfer()` 函数的封装。SPI 总线协议规定只能由主设备产生时钟,因此在接收数据时,主设备会发送数据 0XFF。 + +此函数等同于调用 `rt_spi_transfer_message()` 传输一条消息,开始接收数据时片选选中,函数返回时释放片选,message 参数配置如下: + +```c +struct rt_spi_message msg; + +msg.send_buf = RT_NULL; +msg.recv_buf = recv_buf; +msg.length = length; +msg.cs_take = 1; +msg.cs_release = 1; +msg.next = RT_NULL; +``` + +### 连续两次发送数据 + +如果需要先后连续发送 2 个缓冲区的数据,并且中间片选不释放,可以调用如下函数: + +```c +rt_err_t rt_spi_send_then_send(struct rt_spi_device *device, + const void *send_buf1, + rt_size_t send_length1, + const void *send_buf2, + rt_size_t send_length2); +``` + +| **参数** | **描述** | +|--------------|---------------------------| +| device | SPI 设备句柄 | +| send_buf1 | 发送数据缓冲区 1 指针 | +| send_length1 | 发送数据缓冲区 1 数据字节数 | +| send_buf2 | 发送数据缓冲区 2 指针 | +| send_length2 | 发送数据缓冲区 2 数据字节数 | +| **返回** | —— | +| RT_EOK | 发送成功 | +| -RT_EIO | 发送失败 | + +此函数可以连续发送 2 个缓冲区的数据,忽略接收到的数据,发送 send_buf1 时片选选中,发送完 send_buf2 后释放片选。 + +本函数适合向 SPI 设备中写入一块数据,第一次先发送命令和地址等数据,第二次再发送指定长度的数据。之所以分两次发送而不是合并成一个数据块发送,或调用两次 `rt_spi_send()`,是因为在大部分的数据写操作中,都需要先发命令和地址,长度一般只有几个字节。如果与后面的数据合并在一起发送,将需要进行内存空间申请和大量的数据搬运。而如果调用两次 `rt_spi_send()`,那么在发送完命令和地址后,片选会被释放,大部分 SPI 设备都依靠设置片选一次有效为命令的起始,所以片选在发送完命令或地址数据后被释放,则此次操作被丢弃。 + +此函数等同于调用 `rt_spi_transfer_message()` 传输 2 条消息,message 参数配置如下: + +```c +struct rt_spi_message msg1,msg2; + +msg1.send_buf = send_buf1; +msg1.recv_buf = RT_NULL; +msg1.length = send_length1; +msg1.cs_take = 1; +msg1.cs_release = 0; +msg1.next = &msg2; + +msg2.send_buf = send_buf2; +msg2.recv_buf = RT_NULL; +msg2.length = send_length2; +msg2.cs_take = 0; +msg2.cs_release = 1; +msg2.next = RT_NULL; +``` + +### 先发送后接收数据 + +如果需要向从设备先发送数据,然后接收从设备发送的数据,并且中间片选不释放,可以调用如下函数: + +```c +rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device, + const void *send_buf, + rt_size_t send_length, + void *recv_buf, + rt_size_t recv_length); +``` + +| **参数** | **描述** | +|-------------|--------------------------| +| device | SPI 从设备句柄 | +| send_buf | 发送数据缓冲区指针 | +| send_length | 发送数据缓冲区数据字节数 | +| recv_buf | 接收数据缓冲区指针 | +| recv_length | 接收数据字节数 | +| **返回** | —— | +| RT_EOK | 成功 | +| -RT_EIO | 失败 | + +此函数发送第一条数据 send_buf 时开始片选,此时忽略接收到的数据,然后发送第二条数据,此时主设备会发送数据 0XFF,接收到的数据保存在 recv_buf 里,函数返回时释放片选。 + +本函数适合从 SPI 从设备中读取一块数据,第一次会先发送一些命令和地址数据,然后再接收指定长度的数据。 + +此函数等同于调用 `rt_spi_transfer_message()` 传输 2 条消息,message 参数配置如下: + +```c +struct rt_spi_message msg1,msg2; + +msg1.send_buf = send_buf; +msg1.recv_buf = RT_NULL; +msg1.length = send_length; +msg1.cs_take = 1; +msg1.cs_release = 0; +msg1.next = &msg2; + +msg2.send_buf = RT_NULL; +msg2.recv_buf = recv_buf; +msg2.length = recv_length; +msg2.cs_take = 0; +msg2.cs_release = 1; +msg2.next = RT_NULL; +``` + +SPI 设备管理模块还提供 `rt_spi_sendrecv8()` 和 `rt_spi_sendrecv16()` 函数,这两个函数都是对此函数的封装,`rt_spi_sendrecv8()` 发送一个字节数据同时收到一个字节数据,`rt_spi_sendrecv16()` 发送 2 个字节数据同时收到 2 个字节数据。 + +## 访问 QSPI 设备 + +QSPI 的数据传输接口如下所示: + +| **函数** | **描述** | +| -------------------- | ----------------------------| +| rt_qspi_transfer_message() | 传输数据 | +| rt_qspi_send_then_recv() | 先发送后接收 | +| rt_qspi_send() | 发送一次数据 | + +> [!NOTE] +> 注:QSPI 数据传输相关接口会调用 rt_mutex_take(), 此函数不能在中断服务程序里面调用,会导致 assertion 报错。 + +### 传输数据 + +可以通过如下函数传输消息: + +```c +rt_size_t rt_qspi_transfer_message(struct rt_qspi_device *device, struct rt_qspi_message *message); +``` + +| **参数** | **描述** | +|----------|--------------------------------------------| +| device | QSPI 设备句柄 | +| message | 消息指针 | +| **返回** | —— | +| 实际传输的消息大小 | | + +消息结构体 struct rt_qspi_message 原型如下: + +```c +struct rt_qspi_message +{ + struct rt_spi_message parent; /* 继承自struct rt_spi_message */ + + struct + { + rt_uint8_t content; /* 指令内容 */ + rt_uint8_t qspi_lines; /* 指令模式,单线模式 1 位、双线模式 2 位,4 线模式 4 位 */ + } instruction; /* 指令阶段 */ + + struct + { + rt_uint32_t content; /* 地址/交替字节 内容 */ + rt_uint8_t size; /* 地址/交替字节 长度 */ + rt_uint8_t qspi_lines; /* 地址/交替字节 模式,单线模式 1 位、双线模式 2 位,4 线模式 4 位 */ + } address, alternate_bytes; /* 地址/交替字节 阶段 */ + + rt_uint32_t dummy_cycles; /* 空指令周期阶段 */ + rt_uint8_t qspi_data_lines; /* QSPI 总线位宽 */ +}; +``` + +### 接收数据 + +可以调用如下函数: + +```c +rt_err_t rt_qspi_send_then_recv(struct rt_qspi_device *device, + const void *send_buf, + rt_size_t send_length, + void *recv_buf, + rt_size_t recv_length); +``` + +| **参数** | **描述** | +|-------------|--------------------------| +| device | QSPI 设备句柄 | +| send_buf | 发送数据缓冲区指针 | +| send_length | 发送数据字节数 | +| recv_buf | 接收数据缓冲区指针 | +| recv_length | 接收数据字节数 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他错误码 | 失败 | + +send_buf 参数包含了将要发送的命令序列。 + +### 发送数据 + +```c +rt_err_t rt_qspi_send(struct rt_qspi_device *device, const void *send_buf, rt_size_t length) +``` + +| **参数** | **描述** | +|-------------|--------------------------| +| device | QSPI 设备句柄 | +| send_buf | 发送数据缓冲区指针 | +| length | 发送数据字节数 | +| **返回** | —— | +| RT_EOK | 成功 | +| 其他错误码 | 失败 | + +send_buf 参数包含了将要发送的命令序列和数据。 + +## 特殊使用场景 + +在一些特殊的使用场景,某个设备希望独占总线一段时间,且期间要保持片选一直有效,期间数据传输可能是间断的,则可以按照如所示步骤使用相关接口。传输数据函数必须使用 `rt_spi_transfer_message()`,并且此函数每个待传输消息的片选控制域 cs_take 和 cs_release 都要设置为 0 值,因为片选已经使用了其他接口控制,不需要在数据传输的时候控制。 + +### 获取总线 + +在多线程的情况下,同一个 SPI 总线可能会在不同的线程中使用,为了防止 SPI 总线正在传输的数据丢失,从设备在开始传输数据前需要先获取 SPI 总线的使用权,获取成功才能够使用总线传输数据,可使用如下函数获取 SPI 总线: + +```c +rt_err_t rt_spi_take_bus(struct rt_spi_device *device); +``` + +| **参数** | **描述** | +|----------|---------------| +| device | SPI 设备句柄 | +| **返回** | —— | +| RT_EOK | 成功 | +| 错误码 | 失败 | + +### 选中片选 + +从设备获取总线的使用权后,需要设置自己对应的片选信号为有效,可使用如下函数选中片选: + +```c +rt_err_t rt_spi_take(struct rt_spi_device *device); +``` + +| **参数** | **描述** | +|----------|---------------| +| device | SPI 设备句柄 | +| **返回** | —— | +| 0 | 成功 | +| 错误码 | 失败 | + +### 增加一条消息 + +使用 `rt_spi_transfer_message()` 传输消息时,所有待传输的消息都是以单向链表的形式连接起来的,可使用如下函数往消息链表里增加一条新的待传输消息: + +```c +void rt_spi_message_append(struct rt_spi_message *list, + struct rt_spi_message *message); +``` + +| **参数** | **描述** | +|----------|----------------------| +| list | 待传输的消息链表节点 | +| message | 新增消息指针 | + +### 释放片选 + +从设备数据传输完成后,需要释放片选,可使用如下函数释放片选: + +```c +rt_err_t rt_spi_release(struct rt_spi_device *device); +``` + +| **参数** | **描述** | +|----------|---------------| +| device | SPI 设备句柄 | +| 返回 | —— | +| 0 | 成功 | +| 错误码 | 失败 | + +### 释放总线 + +从设备不在使用 SPI 总线传输数据,必须尽快释放总线,这样其他从设备才能使用 SPI 总线传输数据,可使用如下函数释放总线: + +```c +rt_err_t rt_spi_release_bus(struct rt_spi_device *device); +``` + +| **参数** | **描述** | +|----------|---------------| +| device | SPI 设备句柄 | +| **返回** | —— | +| RT_EOK | 成功 | + +## SPI 设备使用示例 + +SPI 设备的具体使用方式可以参考如下的示例代码,示例代码首先查找 SPI 设备获取设备句柄,然后使用 rt_spi_transfer_message() 发送命令读取 ID信息。 + +```c +/* + * 程序清单:这是一个 SPI 设备使用例程 + * 例程导出了 spi_w25q_sample 命令到控制终端 + * 命令调用格式:spi_w25q_sample spi10 + * 命令解释:命令第二个参数是要使用的SPI设备名称,为空则使用默认的SPI设备 + * 程序功能:通过SPI设备读取 w25q 的 ID 数据 +*/ + +#include +#include + +#define W25Q_SPI_DEVICE_NAME "qspi10" + +static void spi_w25q_sample(int argc, char *argv[]) +{ + struct rt_spi_device *spi_dev_w25q; + char name[RT_NAME_MAX]; + rt_uint8_t w25x_read_id = 0x90; + rt_uint8_t id[5] = {0}; + + if (argc == 2) + { + rt_strncpy(name, argv[1], RT_NAME_MAX); + } + else + { + rt_strncpy(name, W25Q_SPI_DEVICE_NAME, RT_NAME_MAX); + } + + /* 查找 spi 设备获取设备句柄 */ + spi_dev_w25q = (struct rt_spi_device *)rt_device_find(name); + if (!spi_dev_w25q) + { + rt_kprintf("spi sample run failed! can't find %s device!\n", name); + } + else + { + /* 方式1:使用 rt_spi_send_then_recv()发送命令读取ID */ + rt_spi_send_then_recv(spi_dev_w25q, &w25x_read_id, 1, id, 5); + rt_kprintf("use rt_spi_send_then_recv() read w25q ID is:%x%x\n", id[3], id[4]); + + /* 方式2:使用 rt_spi_transfer_message()发送命令读取ID */ + struct rt_spi_message msg1, msg2; + + msg1.send_buf = &w25x_read_id; + msg1.recv_buf = RT_NULL; + msg1.length = 1; + msg1.cs_take = 1; + msg1.cs_release = 0; + msg1.next = &msg2; + + msg2.send_buf = RT_NULL; + msg2.recv_buf = id; + msg2.length = 5; + msg2.cs_take = 0; + msg2.cs_release = 1; + msg2.next = RT_NULL; + + rt_spi_transfer_message(spi_dev_w25q, &msg1); + rt_kprintf("use rt_spi_transfer_message() read w25q ID is:%x%x\n", id[3], id[4]); + + } +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(spi_w25q_sample, spi w25q sample); +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/touch/figures/touch.vsd b/rt-thread-version/rt-thread-standard/programming-manual/device/touch/figures/touch.vsd new file mode 100644 index 0000000000000000000000000000000000000000..321871365c8c014fdfbc1bf74e301a3728f994eb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/touch/figures/touch.vsd differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/touch/figures/touch_pro.png b/rt-thread-version/rt-thread-standard/programming-manual/device/touch/figures/touch_pro.png new file mode 100644 index 0000000000000000000000000000000000000000..522b6f103c48c0863b270e3f67d92d3a4767bc02 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/touch/figures/touch_pro.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/touch/touch.md b/rt-thread-version/rt-thread-standard/programming-manual/device/touch/touch.md new file mode 100644 index 0000000000000000000000000000000000000000..b0afb06aeb798c867fdb01f39fdabbb2c5e7be74 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/touch/touch.md @@ -0,0 +1,408 @@ +# TOUCH 设备 + +## Touch 简介 + +Touch(触摸芯片)是 UI 设计中进行人机交互重要的一部分,一个完整的 UI 设计应该包括输入信息和输出信息,LCD 等屏幕设备负责显示输出,那么 Touch 设备就负责触点信息采集作为信息输入。 + +Touch 设备与主机通讯一般都是采用 I2C 总线协议来进行数据交互,所以一个 Touch 设备,就是一个标准的 I2C 从设备,而且为了提高接收 Touch 数据的实时性,触摸芯片都会提供中断支持,当有触摸事件(抬起,按下,移动)发生时,会触发中断通知 MCU 有触摸事件。主机可以通过中断回调函数去读取触摸点信息。 + +Touch 设备与主机通讯连接如下图所示: + + ![touch设备读取数据流程](figures/touch_pro.png) + +RT-Thread 为了方便使用 Touch 设备,抽象出了 Touch 设备驱动框架,并且向上层提供统一的操作接口,提高上层代码的可重用性。 + +### Touch 设备特性 + +- 接口:标准 device 接口(open/close/read/control)。 +- 工作模式:支持和轮询两种模式。 +- 支持读取多点数据 + +点击 [Touch 列表](touch_list.md),查看当前支持的 Touch 类型。 + + +## 访问 Touch 设备 + +应用程序通过 RT-Thread 提供的 I/O 设备管理接口来访问 Touch 设备,相关接口如下所示: + +| **函数** | **描述** | +| --------------------------- | ------------------------------------------ | +| rt_device_find() | 根据 Touch 设备设备名称查找设备获取设备句柄 | +| rt_device_open() | 打开 Touch 设备 | +| rt_device_read() | 读取触点数据 | +| rt_device_control() | 控制 Touch 设备 | +| rt_device_set_rx_indicate() | 设置接收回调函数 | +| rt_device_close() | 关闭 Touch 设备 | + +### 查找 Touch 设备 + +应用程序根据 Touch 设备名称获取设备句柄,进而可以操作 Touch 设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | Touch 设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +使用示例如下所示: +```c +#define TOUCH_DEVICE_NAME "touch_gt" /* Touch 设备名称 */ + +static rt_device_t touch_dev; /* Touch 设备句柄 */ +/* 根据设备名称查找 Touch 设备,获取设备句柄 */ +touch_dev = rt_device_find(TOUCH_DEVICE_NAME); +``` + +### 打开 Touch 设备 + +通过设备句柄,应用程序可以打开和关闭设备,通过如下函数打开设备: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | 设备句柄 | +| oflags | 设备模式标志 | +| **返回** | —— | +| RT_EOK | 设备打开成功 | +| -RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开 | +| -RT_EINVAL | 不支持的打开参数 | +| 其他错误码 | 设备打开失败 | + +oflags 参数支持下列参数: + +```c +#define RT_DEVICE_FLAG_RDONLY 0x001 /* 标准设备的只读模式,对应 Touch 的轮询模式 */ +#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收模式 */ +``` + +Touch 设备数据接收和发送数据的模式分为 2 种:中断模式和轮询模式。在使用的时候,这 2 种模式只能**选其一**,若 Touch 的打开参数 oflags 没有指定使用中断模式,则默认使用轮询模式。 + +以轮询模式打开 Touch 设备使用示例如下所示: + +```c +rt_device_open(touch_dev, RT_DEVICE_FLAG_RDONLY) +``` + +以中断模式打开 Touch 设备使用示例如下所示: +```c +rt_device_open(touch_dev, RT_DEVICE_FLAG_INT_RX) +``` + +### 控制 Touch 设备 + +通过命令控制字,应用程序可以对 Touch 设备进行配置,通过如下函数完成: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| cmd | 命令控制字,详细介绍见下面 | +| arg | 控制的参数, 详细介绍见下面 | +| **返回** | —— | +| RT_EOK | 函数执行成功 | +| -RT_ENOSYS | 执行失败,dev 为空 | +| 其他错误码 | 执行失败 | + +其中的 cmd 目前支持以下几种命令控制字 + +```c +#define RT_TOUCH_CTRL_GET_ID (0) /* 读设备ID */ +#define RT_TOUCH_CTRL_GET_INFO (1) /* 获取设备信息 */ +#define RT_TOUCH_CTRL_SET_MODE (2) /* 设置工作模式 */ +#define RT_TOUCH_CTRL_SET_X_RANGE (3) /* 设置 X 轴分辨率 */ +#define RT_TOUCH_CTRL_SET_Y_RANGE (4) /* 设置 Y 轴分辨率 */ +#define RT_TOUCH_CTRL_SET_X_TO_Y (5) /* 交换 X、Y 轴坐标 */ +#define RT_TOUCH_CTRL_DISABLE_INT (6) /* 使能中断 */ +#define RT_TOUCH_CTRL_ENABLE_INT (7) /* 失能中断 */ +``` + + +#### 读设备ID + +```c +rt_uint8_t read_id[4]; +rt_device_control(touch_dev, RT_TOUCH_CTRL_GET_ID, read_id); +LOG_I("id = %d %d %d %d \n", read_id[0] - '0', read_id[1] - '0', read_id[2] - '0', read_id[3] - '0'); +``` + +#### 获取设备信息 + +```c +struct rt_touch_info info; +rt_device_control(touch_dev, RT_TOUCH_CTRL_GET_INFO, &info); +LOG_I("type :%d", info.type); /* 类型:电容型/电阻型*/ +LOG_I("vendor :%s", info.vendor); /* 厂商 */ +LOG_I("point_num :%d", info.point_num); /* 支持的触点个数 */ +LOG_I("range_x :%d", info.range_x); /* X 轴分辨率 */ +LOG_I("range_y :%d", info.range_y); /* Y 轴分辨率*/ +``` +#### 设置工作模式 + +```c +/* 设置工作模式为中断模式 */ +rt_device_control(touch_dev, RT_TOUCH_CTRL_SET_MODE, (void *)RT_DEVICE_FLAG_INT_RX); +/* 设置工作模式为轮询模式 */ +rt_device_control(touch_dev, RT_TOUCH_CTRL_SET_MODE, (void *)RT_DEVICE_FLAG_RDONLY); +``` + +#### 设置 X 轴范围 + +设置 Touch X 轴坐标的分辨率。 + +```c +rt_uint16_t x = 400; +rt_device_control(touch_dev, RT_TOUCH_CTRL_SET_X_RANGE, &x); +``` + +#### 设置 Y 轴范围 + +设置 Touch Y 轴坐标的分辨率。 +```c +rt_uint16_t y = 400; +rt_device_control(touch_dev, RT_TOUCH_CTRL_SET_Y_RANGE, &y); +``` + +#### 交换 X、Y 轴坐标 + +```c +rt_device_control(touch_dev, RT_TOUCH_CTRL_SET_X_TO_Y, RT_NULL); +``` + +#### 关闭 Touch 关闭中断 + +```c +rt_device_control(touch_dev, RT_TOUCH_CTRL_DISABLE_INT, RT_NULL); +``` + +#### 开启 Touch 设备中断 + +```c +rt_device_control(touch_dev, RT_TOUCH_CTRL_ENABLE_INT, RT_NULL); +``` + +当使用中断模式读取触点数据时,底层有触摸事件发生时会触发中断,由于中断触发的速度会大于 Touch 设备读取的速速(I2C 读取数据一般较慢),所以,在接收回调函数中需要关闭中断,然后再释放信号量,在触点信息读取线程中,请求到信号量之后会去读取数据,数据读取完成再去打开中断。注意打开中断接口和关闭中断接口需配对使用,打开一次中断对应要关闭一次中断,这样设备才能以正常的中断模式去读取数据。 + + +### 设置接收回调函数 + +可以通过如下函数来设置数据接收指示,当 Touch 收到数据时,通知上层应用线程有数据到达 : + +```c +rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size)); +``` + +| **参数** | **描述** | +| -------- | ------------ | +| dev | 设备句柄 | +| rx_ind | 回调函数指针 | +| dev | 设备句柄(回调函数参数)| +| size | 缓冲区数据大小(回调函数参数)| +| **返回** | —— | +| RT_EOK | 设置成功 | + +该函数的回调函数由调用者提供。若 Touch 设备以中断接收模式打开,当 Touch 接收到数据产生中断时,就会调用回调函数,把 Touch 设备句柄放在 dev 参数里供调用者获取。 + +### 读 Touch 设备触摸点信息 + +#### 触点信息组成 +```c +struct rt_touch_data +{ + rt_uint8_t event; + rt_uint8_t track_id; + rt_uint8_t width; + rt_uint16_t x_coordinate; + rt_uint16_t y_coordinate; + rt_tick_t timestamp; +}; +``` +* event:触摸事件,包括抬起事件、按下事件和移动事件。 +* track_id:每个触摸点都有自己的触摸轨迹,这个数据用来保存触摸轨迹 ID。 +* width:触摸点宽度。 +* x_coordinate:触摸点 X 轴坐标。 +* y_coordinate:触摸点 Y 轴坐标。 +* timestamp:触摸事件时间戳。 + +#### 读取触摸点信息接口 + +可调用如下接口读取触摸点信息: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ------------------ | ---------------------------------------------- | +| dev | 设备句柄 | +| pos | 读取数据偏移量,此参数 Touch 未使用 | +| buffer | 缓冲区指针,读取的数据将会被保存在缓冲区中 | +| size | Touch 设备驱动框架根据这个参数来确定需要读取的触点个数 | +| **返回** | —— | +| 读到数据的实际大小 | 返回读取到的触点信息的个数 | +| 0 | 需要读取当前线程的 errno 来判断错误状态 | + +读取一个触摸点信息的代码片段如下: +```c +struct rt_touch_data *read_data; +read_data = (struct rt_touch_data *)rt_malloc(sizeof(struct rt_touch_data)); + +if (rt_device_read(touch_dev, 0, read_data, 1) == 1) +{ + rt_kprintf("%d %d %d %d %d\n", read_data.track_id, read_data.x_coordinate, read_data.y_coordinate, read_data.timestamp, read_data.width); +} +``` + +读取五个触摸点信息的代码片段如下: +```c +struct rt_touch_data *read_data; +read_data = (struct rt_touch_data *)rt_malloc(sizeof(struct rt_touch_data) * 5); + +if (rt_device_read(dev, 0, read_data, 5) == 5) +{ + for (rt_uint8_t i = 0; i < 5; i++) + { + if (read_data[i].event == RT_TOUCH_EVENT_DOWN || read_data[i].event == RT_TOUCH_EVENT_MOVE) + { + rt_kprintf("%d %d %d %d %d\n", + read_data[i].track_id, + read_data[i].x_coordinate, + read_data[i].y_coordinate, + read_data[i].timestamp, + read_data[i].width); + } + } +} +``` + +### 关闭 Touch 设备 + +当应用程序完成 Touch 操作后,可以关闭 Touch 设备,通过如下函数完成: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------- | +| dev | 设备句柄 | +| **返回** | —— | +| RT_EOK | 关闭设备成功 | +| -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + +关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + +## Touch 设备使用示例 + +### 中断方式读取五个点的触摸信息 + +Touch 设备中断方式读取五个点的触摸信息的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 首先查找 Touch 设置获取设备句柄。 + +2. 以中断接收数据的方式打开 Touch 设备。 + +3. 设置中断接收回调。 + +4. 创建读取 Touch 数据线程和信号量,在中断接收回调中释放该信号量,在数据接收线程中一直请求信号量,一旦请求到信号量就去读取 Touch 的数据。 + +```c +static rt_thread_t gt9147_thread = RT_NULL; +static rt_sem_t gt9147_sem = RT_NULL; +static rt_device_t dev = RT_NULL; +static struct rt_touch_data *read_data; + +/* 读取数据线程入口函数 */ +static void gt9147_entry(void *parameter) +{ + struct rt_touch_data *read_data; + read_data = (struct rt_touch_data *)rt_malloc(sizeof(struct rt_touch_data) * 5); + + while (1) + { + /* 请求信号量 */ + rt_sem_take(gt9147_sem, RT_WAITING_FOREVER); + /* 读取五个点的触摸信息 */ + if (rt_device_read(dev, 0, read_data, 5) == 5) + { + for (rt_uint8_t i = 0; i < 5; i++) + { + if (read_data[i].event == RT_TOUCH_EVENT_DOWN || read_data[i].event == RT_TOUCH_EVENT_MOVE) + { + rt_kprintf("%d %d %d %d %d\n", + read_data[i].track_id, + read_data[i].x_coordinate, + read_data[i].y_coordinate, + read_data[i].timestamp, + read_data[i].width); + } + } + } + /* 打开中断 */ + rt_device_control(dev, RT_TOUCH_CTRL_ENABLE_INT, RT_NULL); + } +} + +/* 接收回调函数 */ +static rt_err_t rx_callback(rt_device_t dev, rt_size_t size) +{ + /* 关闭中断 */ + rt_device_control(dev, RT_TOUCH_CTRL_DISABLE_INT, RT_NULL); + /* 释放信号量 */ + rt_sem_release(gt9147_sem); + return 0; +} + +static int gt9147_sample(void) +{ + /* 查找 Touch 设备 */ + dev = rt_device_find("touch"); + + if (dev == RT_NULL) + { + rt_kprintf("can't find device:%s\n", "touch"); + return -1; + } + /* 以中断的方式打开设备 */ + if (rt_device_open(dev, RT_DEVICE_FLAG_INT_RX) != RT_EOK) + { + rt_kprintf("open device failed!"); + return -1; + } + /* 设置接收回调 */ + rt_device_set_rx_indicate(dev, rx_callback); + /* 创建信号量 */ + gt9147_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO); + + if (gt9147_sem == RT_NULL) + { + rt_kprintf("create dynamic semaphore failed.\n"); + return -1; + } + /* 创建读取数据线程 */ + gt9147_thread = rt_thread_create("thread1", + gt9147_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, + THREAD_TIMESLICE); + /* 启动线程 */ + if (gt9147_thread != RT_NULL) + rt_thread_startup(gt9147_thread); + + return 0; +} +MSH_CMD_EXPORT(gt9147_sample, gt9147 sample); + +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/touch/touch_list.md b/rt-thread-version/rt-thread-standard/programming-manual/device/touch/touch_list.md new file mode 100644 index 0000000000000000000000000000000000000000..e872b24ab30653417e65458b2012b0de6ebdf406 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/touch/touch_list.md @@ -0,0 +1,8 @@ +# 支持的 Touch 列表 + +下面是一份已经对接到 RT-Therad Touch 框架上的 Touch 列表,点击 Touch 名称即可跳转到相应软件包主页。(本文档不定时更新,如要查看所有支持的 Touch 的列表,可以查看最新的[软件包索引](https://github.com/RT-Thread/packages/tree/master/peripherals/touch)。 + +| 厂商 | 传感器 | 备注 | +| -------------- | ------------------------------------------------------------ | ------------------------ | +| **GT** | | | +| | **[gt9147](https://github.com/RT-Thread-packages/gt9147)** | 支持 5 点触控 diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-dma b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-dma new file mode 100644 index 0000000000000000000000000000000000000000..56b46e9e688c7cd7272297b4384a31fa37b5a53c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-dma @@ -0,0 +1,24 @@ +@startuml + +actor û + +participant Ӧó +participant ISR +participant DMA +participant û + +Ӧó->Ӧó: ʼϢ +Ӧó->Ӧó: ýջص +Ӧó->Ӧó: ݴ߳ + +Ӧó->Ӧó: ݴ߳ȴϢ + +û->DMA: ûһַ + +DMA->ISR: DMAһַ뻺ж + +ISR->Ӧó: ýջصϢм߳ + +Ӧó->Ӧó: ݴ̻߳ȡϢбӻȡһַ + +@enduml \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-dma.png b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-dma.png new file mode 100644 index 0000000000000000000000000000000000000000..dcc008ef9285c57d6e2f6b2017dc44b884b60ea1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-dma.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-int b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-int new file mode 100644 index 0000000000000000000000000000000000000000..a6f4c7c2ca7b9395a944b4796deea7521c0b16b9 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-int @@ -0,0 +1,24 @@ +@startuml + +actor û + +participant Ӧó +participant ISR +participant +participant û + +Ӧó->Ӧó: ʼź +Ӧó->Ӧó: ýջص +Ӧó->Ӧó: ݴ߳ + +Ӧó->Ӧó: ݴ߳ȴź + +û->: ûһַ + +->ISR: յַڽж + +ISR->Ӧó: ISRݷ뻺,ڽջصзź߳ + +Ӧó->Ӧó: ݴ̻߳ȡźӻȡһַ + +@enduml \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-int.png b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-int.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ff56ad7c3174a3f3799e6be05d0485e9f76065 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart-int.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart.vsdx b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..e98d48e2c0773249bca850e55ef40231500996c5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart.vsdx differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart1.png b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart1.png new file mode 100644 index 0000000000000000000000000000000000000000..8f1404b7522001a78fe2c051712daa7896707496 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/figures/uart1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart.md b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart.md new file mode 100644 index 0000000000000000000000000000000000000000..2def8b4bf57eaf117eed501e2cca7bd7ac2aa433 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/uart/uart.md @@ -0,0 +1,789 @@ +# UART 设备 + +## UART 简介 + +UART(Universal Asynchronous Receiver/Transmitter)通用异步收发传输器,UART 作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。是在应用程序开发过程中使用频率最高的数据总线。 + +UART 串口的特点是将数据一位一位地顺序传送,只要 2 根传输线就可以实现双向通信,一根线发送数据的同时用另一根线接收数据。UART 串口通信有几个重要的参数,分别是波特率、起始位、数据位、停止位和奇偶检验位,对于两个使用 UART 串口通信的端口,这些参数必须匹配,否则通信将无法正常完成。UART 串口传输的数据格式如下图所示: + +![串口传输数据格式](figures/uart1.png) + +* 起始位:表示数据传输的开始,电平逻辑为 “0” 。 + +* 数据位:可能值有 5、6、7、8、9,表示传输这几个 bit 位数据。一般取值为 8,因为一个 ASCII 字符值为 8 位。 + +* 奇偶校验位:用于接收方对接收到的数据进行校验,校验 “1” 的位数为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性,使用时不需要此位也可以。 + +* 停止位: 表示一帧数据的结束。电平逻辑为 “1”。 + +* 波特率:串口通信时的速率,它用单位时间内传输的二进制代码的有效位(bit)数来表示,其单位为每秒比特数 bit/s(bps)。常见的波特率值有 4800、9600、14400、38400、115200等,数值越大数据传输的越快,波特率为 115200 表示每秒钟传输 115200 位数据。 + +## 访问串口设备 + +应用程序通过 RT-Thread提供的 I/O 设备管理接口来访问串口硬件,相关接口如下所示: + +| **函数** | **描述** | +| --------------------------- | -------------------------- | +| rt_device_find() | 查找设备 | +| rt_device_open() | 打开设备 | +| rt_device_read() | 读取数据 | +| rt_device_write() |写入数据| +| rt_device_control() | 控制设备 | +| rt_device_set_rx_indicate() | 设置接收回调函数 | +| rt_device_set_tx_complete() | 设置发送完成回调函数 | +| rt_device_close() | 关闭设备| + +### 查找串口设备 + +应用程序根据串口设备名称获取设备句柄,进而可以操作串口设备,查找设备函数如下所示, + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | 设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +一般情况下,注册到系统的串口设备名称为 uart0,uart1等,使用示例如下所示: + +```c +#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */ +static rt_device_t serial; /* 串口设备句柄 */ +/* 查找串口设备 */ +serial = rt_device_find(SAMPLE_UART_NAME); +``` + +### 打开串口设备 + +通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | 设备句柄 | +| oflags | 设备模式标志 | +| **返回** | —— | +| RT_EOK | 设备打开成功 | +| -RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开 | +| 其他错误码 | 设备打开失败 | + +oflags 参数支持下列取值 (可以采用或的方式支持多种取值): + +```c +#define RT_DEVICE_FLAG_STREAM 0x040 /* 流模式 */ +/* 接收模式参数 */ +#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收模式 */ +#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收模式 */ +/* 发送模式参数 */ +#define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送模式 */ +#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送模式 */ +``` + +串口数据接收和发送数据的模式分为 3 种:中断模式、轮询模式、DMA 模式。在使用的时候,这 3 种模式只能**选其一**,若串口的打开参数 oflags 没有指定使用中断模式或者 DMA 模式,则默认使用轮询模式。 + +DMA(Direct Memory Access)即直接存储器访问。 DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过 DMA 控制器为 RAM 与 I/O 设备开辟一条直接传送数据的通路,这就节省了 CPU 的资源来做其他操作。使用 DMA 传输可以连续获取或发送一段信息而不占用中断或延时,在通信频繁或有大段信息要传输时非常有用。 + +> [!NOTE] +> 注:* RT_DEVICE_FLAG_STREAM:流模式用于向串口终端输出字符串:当输出的字符是 `"\n"` (对应 16 进制值为 0x0A)时,自动在前面输出一个 `"\r"`(对应 16 进制值为 0x0D) 做分行。 + +流模式 RT_DEVICE_FLAG_STREAM 可以和接收发送模式参数使用或 “|” 运算符一起使用。 + +以**中断接收及轮询发送模式**使用串口设备的示例如下所示: + +```c +#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */ +static rt_device_t serial; /* 串口设备句柄 */ +/* 查找串口设备 */ +serial = rt_device_find(SAMPLE_UART_NAME); + +/* 以中断接收及轮询发送模式打开串口设备 */ +rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); +``` + +若串口要使用 DMA 接收模式,oflags 取值 RT_DEVICE_FLAG_DMA_RX。以**DMA 接收及轮询发送模式**使用串口设备的示例如下所示: + +```c +#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */ +static rt_device_t serial; /* 串口设备句柄 */ +/* 查找串口设备 */ +serial = rt_device_find(SAMPLE_UART_NAME); + +/* 以 DMA 接收及轮询发送模式打开串口设备 */ +rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX); +``` + +### 控制串口设备 + +通过控制接口,应用程序可以对串口设备进行配置,如波特率、数据位、校验位、接收缓冲区大小、停止位等参数的修改。控制函数如下所示: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------------------- | +| dev | 设备句柄 | +| cmd | 命令控制字,可取值:RT_DEVICE_CTRL_CONFIG | +| arg | 控制的参数,可取类型: struct serial_configure | +| **返回** | —— | +| RT_EOK | 函数执行成功 | +| -RT_ENOSYS | 执行失败,dev 为空 | +| 其他错误码 | 执行失败 | + +控制参数结构体 struct serial_configure 原型如下: + +```c +struct serial_configure +{ + rt_uint32_t baud_rate; /* 波特率 */ + rt_uint32_t data_bits :4; /* 数据位 */ + rt_uint32_t stop_bits :2; /* 停止位 */ + rt_uint32_t parity :2; /* 奇偶校验位 */ + rt_uint32_t bit_order :1; /* 高位在前或者低位在前 */ + rt_uint32_t invert :1; /* 模式 */ + rt_uint32_t bufsz :16; /* 接收数据缓冲区大小 */ + rt_uint32_t reserved :4; /* 保留位 */ +}; +``` + +RT-Thread 提供的配置参数可取值为如下宏定义: + +```c +/* 波特率可取值 */ +#define BAUD_RATE_2400 2400 +#define BAUD_RATE_4800 4800 +#define BAUD_RATE_9600 9600 +#define BAUD_RATE_19200 19200 +#define BAUD_RATE_38400 38400 +#define BAUD_RATE_57600 57600 +#define BAUD_RATE_115200 115200 +#define BAUD_RATE_230400 230400 +#define BAUD_RATE_460800 460800 +#define BAUD_RATE_921600 921600 +#define BAUD_RATE_2000000 2000000 +#define BAUD_RATE_3000000 3000000 +/* 数据位可取值 */ +#define DATA_BITS_5 5 +#define DATA_BITS_6 6 +#define DATA_BITS_7 7 +#define DATA_BITS_8 8 +#define DATA_BITS_9 9 +/* 停止位可取值 */ +#define STOP_BITS_1 0 +#define STOP_BITS_2 1 +#define STOP_BITS_3 2 +#define STOP_BITS_4 3 +/* 极性位可取值 */ +#define PARITY_NONE 0 +#define PARITY_ODD 1 +#define PARITY_EVEN 2 +/* 高低位顺序可取值 */ +#define BIT_ORDER_LSB 0 +#define BIT_ORDER_MSB 1 +/* 模式可取值 */ +#define NRZ_NORMAL 0 /* normal mode */ +#define NRZ_INVERTED 1 /* inverted mode */ +/* 接收数据缓冲区默认大小 */ +#define RT_SERIAL_RB_BUFSZ 64 +``` + +接收缓冲区:当串口使用中断接收模式打开时,串口驱动框架会根据 RT_SERIAL_RB_BUFSZ 大小开辟一块缓冲区用于保存接收到的数据,底层驱动接收到一个数据,都会在中断服务程序里面将数据放入缓冲区。 + +RT-Thread 提供的默认串口配置如下,即 RT-Thread 系统中默认每个串口设备都使用如下配置: + +```c +#define RT_SERIAL_CONFIG_DEFAULT \ +{ \ + BAUD_RATE_115200, /* 115200 bits/s */ \ + DATA_BITS_8, /* 8 databits */ \ + STOP_BITS_1, /* 1 stopbit */ \ + PARITY_NONE, /* No parity */ \ + BIT_ORDER_LSB, /* LSB first sent */ \ + NRZ_NORMAL, /* Normal mode */ \ + RT_SERIAL_RB_BUFSZ, /* Buffer size */ \ + 0 \ +} +``` + +> [!NOTE] +> 注:默认串口配置接收数据缓冲区大小为 RT_SERIAL_RB_BUFSZ,即 64 字节。若一次性数据接收字节数很多,没有及时读取数据,那么缓冲区的数据将会被新接收到的数据覆盖,造成数据丢失,建议调大缓冲区,即通过 control 接口修改。在修改缓冲区大小时请注意,缓冲区大小无法动态改变,只有在 open 设备之前可以配置。open 设备之后,缓冲区大小不可再进行更改。但除过缓冲区之外的其他参数,在 open 设备前 / 后,均可进行更改。 + +若实际使用串口的配置参数与默认配置参数不符,则用户可以通过应用代码进行修改。修改串口配置参数,如波特率、数据位、校验位、缓冲区接收 buffsize、停止位等的示例程序如下: + +```c +#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */ +static rt_device_t serial; /* 串口设备句柄 */ +struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */ + +/* step1:查找串口设备 */ +serial = rt_device_find(SAMPLE_UART_NAME); + +/* step2:修改串口配置参数 */ +config.baud_rate = BAUD_RATE_9600; //修改波特率为 9600 +config.data_bits = DATA_BITS_8; //数据位 8 +config.stop_bits = STOP_BITS_1; //停止位 1 +config.bufsz = 128; //修改缓冲区 buff size 为 128 +config.parity = PARITY_NONE; //无奇偶校验位 + +/* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */ +rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config); + +/* step4:打开串口设备。以中断接收及轮询发送模式打开串口设备 */ +rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); +``` + +### 发送数据 + +向串口中写入数据,可以通过如下函数完成: + +```c +rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 设备句柄 | +| pos | 写入数据偏移量,此参数串口设备未使用 | +| buffer | 内存缓冲区指针,放置要写入的数据 | +| size | 写入数据的大小 | +| **返回** | —— | +| 写入数据的实际大小 | 如果是字符设备,返回大小以字节为单位; | +| 0 | 需要读取当前线程的 errno 来判断错误状态 | + +调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的大小是 size。 + +向串口写入数据示例程序如下所示: + +```c +#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */ +static rt_device_t serial; /* 串口设备句柄 */ +char str[] = "hello RT-Thread!\r\n"; +struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 配置参数 */ +/* 查找串口设备 */ +serial = rt_device_find(SAMPLE_UART_NAME); + +/* 以中断接收及轮询发送模式打开串口设备 */ +rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); +/* 发送字符串 */ +rt_device_write(serial, 0, str, (sizeof(str) - 1)); +``` + +### 设置发送完成回调函数 + +在应用程序调用 `rt_device_write()` 写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。可以通过如下函数设置设备发送完成指示 : + +```c +rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer)); +``` + +| **参数** | **描述** | +| -------- | ------------ | +| dev | 设备句柄 | +| tx_done | 回调函数指针 | +| **返回** | —— | +| RT_EOK | 设置成功 | + +调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由设备驱动程序回调这个函数并把发送完成的数据块地址 buffer 作为参数传递给上层应用。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。 + +### 设置接收回调函数 + +可以通过如下函数来设置数据接收指示,当串口收到数据时,通知上层应用线程有数据到达 : + +```c +rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size)); +``` + +| **参数** | **描述** | +| -------- | ------------ | +| dev | 设备句柄 | +| rx_ind | 回调函数指针 | +| dev | 设备句柄(回调函数参数)| +| size | 缓冲区数据大小(回调函数参数)| +| **返回** | —— | +| RT_EOK | 设置成功 | + +该函数的回调函数由调用者提供。若串口以中断接收模式打开,当串口接收到一个数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在 size 参数里,把串口设备句柄放在 dev 参数里供调用者获取。 + +若串口以 DMA 接收模式打开,当 DMA 完成一批数据的接收后会调用此回调函数。 + +一般情况下接收回调函数可以发送一个信号量或者事件通知串口数据处理线程有数据到达。使用示例如下所示: + +```c +#define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */ +static rt_device_t serial; /* 串口设备句柄 */ +static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */ + +/* 接收数据回调函数 */ +static rt_err_t uart_input(rt_device_t dev, rt_size_t size) +{ + /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ + rt_sem_release(&rx_sem); + + return RT_EOK; +} + +static int uart_sample(int argc, char *argv[]) +{ + serial = rt_device_find(SAMPLE_UART_NAME); + + /* 以中断接收及轮询发送模式打开串口设备 */ + rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); + + /* 初始化信号量 */ + rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); + + /* 设置接收回调函数 */ + rt_device_set_rx_indicate(serial, uart_input); +} + +``` + +### 接收数据 + +可调用如下函数读取串口接收到的数据: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +``` + +| **参数** | **描述** | +| ------------------ | ---------------------------------------------- | +| dev | 设备句柄 | +| pos | 读取数据偏移量,此参数串口设备未使用 | +| buffer | 缓冲区指针,读取的数据将会被保存在缓冲区中 | +| size | 读取数据的大小 | +| **返回** | —— | +| 读到数据的实际大小 | 如果是字符设备,返回大小以字节为单位 | +| 0 | 需要读取当前线程的 errno 来判断错误状态 | + +读取数据偏移量 pos 针对字符设备无效,此参数主要用于块设备中。 + +串口使用中断接收模式并配合接收回调函数的使用示例如下所示: + +```c +static rt_device_t serial; /* 串口设备句柄 */ +static struct rt_semaphore rx_sem; /* 用于接收消息的信号量 */ + +/* 接收数据的线程 */ +static void serial_thread_entry(void *parameter) +{ + char ch; + + while (1) + { + /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */ + while (rt_device_read(serial, -1, &ch, 1) != 1) + { + /* 阻塞等待接收信号量,等到信号量后再次读取数据 */ + rt_sem_take(&rx_sem, RT_WAITING_FOREVER); + } + /* 读取到的数据通过串口错位输出 */ + ch = ch + 1; + rt_device_write(serial, 0, &ch, 1); + } +} +``` + +### 关闭串口设备 + +当应用程序完成串口操作后,可以关闭串口设备,通过如下函数完成: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------- | +| dev | 设备句柄 | +| **返回** | —— | +| RT_EOK | 关闭设备成功 | +| -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + +关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + +## 串口设备使用示例 + +### 中断接收及轮询发送 + +示例代码的主要步骤如下所示: + +1. 首先查找串口设备获取设备句柄。 + +2. 初始化回调函数发送使用的信号量,然后以读写及中断接收方式打开串口设备。 + +3. 设置串口设备的接收回调函数,之后发送字符串,并创建读取数据线程。 + +* 读取数据线程会尝试读取一个字符数据,如果没有数据则会挂起并等待信号量,当串口设备接收到一个数据时会触发中断并调用接收回调函数,此函数会发送信号量唤醒线程,此时线程会马上读取接收到的数据。 + +* 此示例代码不局限于特定的 BSP,根据 BSP 注册的串口设备,修改示例代码宏定义 SAMPLE_UART_NAME 对应的串口设备名称即可运行。 + +运行序列图如下图所示: + +![串口中断接收及轮询发送序列图](figures/uart-int.png) + + +```c +/* + * 程序清单:这是一个 串口 设备使用例程 + * 例程导出了 uart_sample 命令到控制终端 + * 命令调用格式:uart_sample uart2 + * 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备 + * 程序功能:通过串口输出字符串"hello RT-Thread!",然后错位输出输入的字符 +*/ + +#include + +#define SAMPLE_UART_NAME "uart2" + +/* 用于接收消息的信号量 */ +static struct rt_semaphore rx_sem; +static rt_device_t serial; + +/* 接收数据回调函数 */ +static rt_err_t uart_input(rt_device_t dev, rt_size_t size) +{ + /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ + rt_sem_release(&rx_sem); + + return RT_EOK; +} + +static void serial_thread_entry(void *parameter) +{ + char ch; + + while (1) + { + /* 从串口读取一个字节的数据,没有读取到则等待接收信号量 */ + while (rt_device_read(serial, -1, &ch, 1) != 1) + { + /* 阻塞等待接收信号量,等到信号量后再次读取数据 */ + rt_sem_take(&rx_sem, RT_WAITING_FOREVER); + } + /* 读取到的数据通过串口错位输出 */ + ch = ch + 1; + rt_device_write(serial, 0, &ch, 1); + } +} + +static int uart_sample(int argc, char *argv[]) +{ + rt_err_t ret = RT_EOK; + char uart_name[RT_NAME_MAX]; + char str[] = "hello RT-Thread!\r\n"; + + if (argc == 2) + { + rt_strncpy(uart_name, argv[1], RT_NAME_MAX); + } + else + { + rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX); + } + + /* 查找系统中的串口设备 */ + serial = rt_device_find(uart_name); + if (!serial) + { + rt_kprintf("find %s failed!\n", uart_name); + return RT_ERROR; + } + + /* 初始化信号量 */ + rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); + /* 以中断接收及轮询发送模式打开串口设备 */ + rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); + /* 设置接收回调函数 */ + rt_device_set_rx_indicate(serial, uart_input); + /* 发送字符串 */ + rt_device_write(serial, 0, str, (sizeof(str) - 1)); + + /* 创建 serial 线程 */ + rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10); + /* 创建成功则启动线程 */ + if (thread != RT_NULL) + { + rt_thread_startup(thread); + } + else + { + ret = RT_ERROR; + } + + return ret; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(uart_sample, uart device sample); +``` + +### DMA 接收及轮询发送 + +当串口接收到一批数据后会调用接收回调函数,接收回调函数会把此时缓冲区的数据大小通过消息队列发送给等待的数据处理线程。线程获取到消息后被激活,并读取数据。一般情况下 DMA 接收模式会结合 DMA 接收完成中断和串口空闲中断完成数据接收。 + +* 此示例代码不局限于特定的 BSP,根据 BSP 注册的串口设备,修改示例代码宏定义 SAMPLE_UART_NAME 对应的串口设备名称即可运行。 + +运行序列图如下图所示: + +![串口DMA接收及轮询发送序列图](figures/uart-dma.png) + +```c +/* + * 程序清单:这是一个串口设备 DMA 接收使用例程 + * 例程导出了 uart_dma_sample 命令到控制终端 + * 命令调用格式:uart_dma_sample uart3 + * 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备 + * 程序功能:通过串口输出字符串"hello RT-Thread!",并通过串口输出接收到的数据,然后打印接收到的数据。 +*/ + +#include + +#define SAMPLE_UART_NAME "uart3" /* 串口设备名称 */ + +/* 串口接收消息结构*/ +struct rx_msg +{ + rt_device_t dev; + rt_size_t size; +}; +/* 串口设备句柄 */ +static rt_device_t serial; +/* 消息队列控制块 */ +static struct rt_messagequeue rx_mq; + +/* 接收数据回调函数 */ +static rt_err_t uart_input(rt_device_t dev, rt_size_t size) +{ + struct rx_msg msg; + rt_err_t result; + msg.dev = dev; + msg.size = size; + + result = rt_mq_send(&rx_mq, &msg, sizeof(msg)); + if ( result == -RT_EFULL) + { + /* 消息队列满 */ + rt_kprintf("message queue full!\n"); + } + return result; +} + +static void serial_thread_entry(void *parameter) +{ + struct rx_msg msg; + rt_err_t result; + rt_uint32_t rx_length; + static char rx_buffer[RT_SERIAL_RB_BUFSZ + 1]; + + while (1) + { + rt_memset(&msg, 0, sizeof(msg)); + /* 从消息队列中读取消息*/ + result = rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER); + if (result == RT_EOK) + { + /* 从串口读取数据*/ + rx_length = rt_device_read(msg.dev, 0, rx_buffer, msg.size); + rx_buffer[rx_length] = '\0'; + /* 通过串口设备 serial 输出读取到的消息 */ + rt_device_write(serial, 0, rx_buffer, rx_length); + /* 打印数据 */ + rt_kprintf("%s\n",rx_buffer); + } + } +} + +static int uart_dma_sample(int argc, char *argv[]) +{ + rt_err_t ret = RT_EOK; + char uart_name[RT_NAME_MAX]; + static char msg_pool[256]; + char str[] = "hello RT-Thread!\r\n"; + + if (argc == 2) + { + rt_strncpy(uart_name, argv[1], RT_NAME_MAX); + } + else + { + rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX); + } + + /* 查找串口设备 */ + serial = rt_device_find(uart_name); + if (!serial) + { + rt_kprintf("find %s failed!\n", uart_name); + return RT_ERROR; + } + + /* 初始化消息队列 */ + rt_mq_init(&rx_mq, "rx_mq", + msg_pool, /* 存放消息的缓冲区 */ + sizeof(struct rx_msg), /* 一条消息的最大长度 */ + sizeof(msg_pool), /* 存放消息的缓冲区大小 */ + RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */ + + /* 以 DMA 接收及轮询发送方式打开串口设备 */ + rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX); + /* 设置接收回调函数 */ + rt_device_set_rx_indicate(serial, uart_input); + /* 发送字符串 */ + rt_device_write(serial, 0, str, (sizeof(str) - 1)); + + /* 创建 serial 线程 */ + rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10); + /* 创建成功则启动线程 */ + if (thread != RT_NULL) + { + rt_thread_startup(thread); + } + else + { + ret = RT_ERROR; + } + + return ret; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(uart_dma_sample, uart device dma sample); +``` + +### 串口接收不定长数据 + +串口接收不定长数据需要用户在应用层进行处理,一般会有特定的协议,比如一帧数据可能会有起始标记位、数据长度位、数据、终止标记位等,发送数据帧时按照约定的协议进行发送,接收数据时再按照协议进行解析。 + +以下是一个简单的串口接收不定长数据示例代码,仅做了数据的结束标志位 DATA_CMD_END,如果遇到结束标志,则表示一帧数据结束。示例代码的主要步骤如下所示: + +1. 首先查找串口设备获取设备句柄。 +2. 初始化回调函数发送使用的信号量,然后以读写及中断接收方式打开串口设备。 +3. 设置串口设备的接收回调函数,之后发送字符串,并创建解析数据线程。 + +- 解析数据线程会尝试读取一个字符数据,如果没有数据则会挂起并等待信号量,当串口设备接收到一个数据时会触发中断并调用接收回调函数,此函数会发送信号量唤醒线程,此时线程会马上读取接收到的数据。在解析数据时,判断结束符,如果结束,则打印数据。 +- 此示例代码不局限于特定的 BSP,根据 BSP 注册的串口设备,修改示例代码宏定义 SAMPLE_UART_NAME 对应的串口设备名称即可运行。 +- 当一帧数据长度超过最大长度时,这将是一帧不合格的数据,因为后面接收到的字符将覆盖最后一个字符。 + +```c +/* + * 程序清单:这是一个串口设备接收不定长数据的示例代码 + * 例程导出了 uart_dma_sample 命令到控制终端 + * 命令调用格式:uart_dma_sample uart2 + * 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备 + * 程序功能:通过串口 uart2 输出字符串"hello RT-Thread!",并通过串口 uart2 输入一串字符(不定长),再通过数据解析后,使用控制台显示有效数据。 +*/ + +#include + +#define SAMPLE_UART_NAME "uart2" +#define DATA_CMD_END '\r' /* 结束位设置为 \r,即回车符 */ +#define ONE_DATA_MAXLEN 20 /* 不定长数据的最大长度 */ + +/* 用于接收消息的信号量 */ +static struct rt_semaphore rx_sem; +static rt_device_t serial; + +/* 接收数据回调函数 */ +static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size) +{ + /* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */ + if (size > 0) + { + rt_sem_release(&rx_sem); + } + return RT_EOK; +} + +static char uart_sample_get_char(void) +{ + char ch; + + while (rt_device_read(serial, 0, &ch, 1) == 0) + { + rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL); + rt_sem_take(&rx_sem, RT_WAITING_FOREVER); + } + return ch; +} + +/* 数据解析线程 */ +static void data_parsing(void) +{ + char ch; + char data[ONE_DATA_MAXLEN]; + static char i = 0; + + while (1) + { + ch = uart_sample_get_char(); + rt_device_write(serial, 0, &ch, 1); + if(ch == DATA_CMD_END) + { + data[i++] = '\0'; + rt_kprintf("data=%s\r\n",data); + i = 0; + continue; + } + i = (i >= ONE_DATA_MAXLEN-1) ? ONE_DATA_MAXLEN-1 : i; + data[i++] = ch; + } +} + +static int uart_data_sample(int argc, char *argv[]) +{ + rt_err_t ret = RT_EOK; + char uart_name[RT_NAME_MAX]; + char str[] = "hello RT-Thread!\r\n"; + + if (argc == 2) + { + rt_strncpy(uart_name, argv[1], RT_NAME_MAX); + } + else + { + rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX); + } + + /* 查找系统中的串口设备 */ + serial = rt_device_find(uart_name); + if (!serial) + { + rt_kprintf("find %s failed!\n", uart_name); + return RT_ERROR; + } + + /* 初始化信号量 */ + rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO); + /* 以中断接收及轮询发送模式打开串口设备 */ + rt_device_open(serial, RT_DEVICE_FLAG_INT_RX); + /* 设置接收回调函数 */ + rt_device_set_rx_indicate(serial, uart_rx_ind); + /* 发送字符串 */ + rt_device_write(serial, 0, str, (sizeof(str) - 1)); + + /* 创建 serial 线程 */ + rt_thread_t thread = rt_thread_create("serial", (void (*)(void *parameter))data_parsing, RT_NULL, 1024, 25, 10); + /* 创建成功则启动线程 */ + if (thread != RT_NULL) + { + rt_thread_startup(thread); + } + else + { + ret = RT_ERROR; + } + + return ret; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(uart_data_sample, uart device sample); + +``` + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/watchdog/watchdog.md b/rt-thread-version/rt-thread-standard/programming-manual/device/watchdog/watchdog.md new file mode 100644 index 0000000000000000000000000000000000000000..eec93f2e5bbfa31e16019559075560cb6628136e --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/watchdog/watchdog.md @@ -0,0 +1,229 @@ +# WATCHDOG 设备 + +## WATCHDOG 简介 + +硬件看门狗(watchdog timer)是一个定时器,其定时输出连接到电路的复位端。在产品化的嵌入式系统中,为了使系统在异常情况下能自动复位,一般都需要引入看门狗。 + +当看门狗启动后,计数器开始自动计数,在计数器溢出前如果没有被复位,计数器溢出就会对 CPU 产生一个复位信号使系统重启(俗称 “被狗咬”)。系统正常运行时,需要在看门狗允许的时间间隔内对看门狗计数器清零(俗称“喂狗“),不让复位信号产生。如果系统不出问题,程序能够按时“喂狗”。一旦程序跑飞,没有“喂狗”,系统“被咬” 复位。 + +一般情况下可以在 RT-Thread 的 idle 回调函数和关键任务里喂狗。 + +## 访问看门狗设备 + +应用程序通过 RT-Thread 提供的 I/O 设备管理接口来访问看门狗硬件,相关接口如下所示: + +| **函数** | **描述** | +| ---------------- | ---------------------------------- | +| rt_device_find() | 根据看门狗设备设备名称查找设备获取设备句柄 | +| rt_device_init() | 初始化看门狗设备 | +| rt_device_control() |控制看门狗设备 | +| rt_device_close() | 关闭看门狗设备| + +### 查找看门狗 + +应用程序根据看门狗设备名称获取设备句柄,进而可以操作看门狗设备,查找设备函数如下所示: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| **参数** | **描述** | +| -------- | ---------------------------------- | +| name | 看门狗设备名称 | +| **返回** | —— | +| 设备句柄 | 查找到对应设备将返回相应的设备句柄 | +| RT_NULL | 没有找到相应的设备对象 | + +使用示例如下所示: + +```c +#define WDT_DEVICE_NAME "wdt" /* 看门狗设备名称 */ + +static rt_device_t wdg_dev; /* 看门狗设备句柄 */ +/* 根据设备名称查找看门狗设备,获取设备句柄 */ +wdg_dev = rt_device_find(WDT_DEVICE_NAME); +``` + +### 初始化看门狗 + +使用看门狗设备前需要先初始化,通过如下函数初始化看门狗设备: + +```c +rt_err_t rt_device_init(rt_device_t dev); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------- | +| dev | 看门狗设备句柄 | +| **返回** | —— | +| RT_EOK | 设备初始化成功 | +| -RT_ENOSYS | 初始化失败,看门狗设备驱动初始化函数为空 | +| 其他错误码 | 设备打开失败 | + +使用示例如下所示: + +```c +#define WDT_DEVICE_NAME "wdt" /* 看门狗设备名称 */ + +static rt_device_t wdg_dev; /* 看门狗设备句柄 */ +/* 根据设备名称查找看门狗设备,获取设备句柄 */ +wdg_dev = rt_device_find(WDT_DEVICE_NAME); + +/* 初始化设备 */ +rt_device_init(wdg_dev); +``` + +### 控制看门狗 + +通过命令控制字,应用程序可以对看门狗设备进行配置,通过如下函数完成: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| **参数** | **描述** | +| ---------- | ------------------------------------------ | +| dev | 看门狗设备句柄 | +| cmd | 命令控制字 | +| arg | 控制的参数 | +| **返回** | —— | +| RT_EOK | 函数执行成功 | +| -RT_ENOSYS | 执行失败,dev 为空 | +| 其他错误码 | 执行失败 | + +命令控制字可取如下宏定义值: + +```c +#define RT_DEVICE_CTRL_WDT_GET_TIMEOUT (1) /* 获取溢出时间 */ +#define RT_DEVICE_CTRL_WDT_SET_TIMEOUT (2) /* 设置溢出时间 */ +#define RT_DEVICE_CTRL_WDT_GET_TIMELEFT (3) /* 获取剩余时间 */ +#define RT_DEVICE_CTRL_WDT_KEEPALIVE (4) /* 喂狗 */ +#define RT_DEVICE_CTRL_WDT_START (5) /* 启动看门狗 */ +#define RT_DEVICE_CTRL_WDT_STOP (6) /* 停止看门狗 */ +``` + +设置看门狗溢出时间使用示例如下所示: + +```c +#define WDT_DEVICE_NAME "wdt" /* 看门狗设备名称 */ + +rt_uint32_t timeout = 1; /* 溢出时间,单位:秒*/ +static rt_device_t wdg_dev; /* 看门狗设备句柄 */ +/* 根据设备名称查找看门狗设备,获取设备句柄 */ +wdg_dev = rt_device_find(WDT_DEVICE_NAME); +/* 初始化设备 */ +rt_device_init(wdg_dev); + +/* 设置看门狗溢出时间 */ +rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, (void *)timeout); +/* 设置空闲线程回调函数 */ +rt_thread_idle_sethook(idle_hook); +``` + +在空闲线程钩子函数里喂狗使用示例如下所示: + +```c +static void idle_hook(void) +{ + /* 在空闲线程的回调函数里喂狗 */ + rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL); +} +``` + +### 关闭看门狗 + +当应用程序完成看门狗操作后,可以关闭看门狗设备,通过如下函数完成: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------- | +| dev | 看门狗设备句柄 | +| **返回** | —— | +| RT_EOK | 关闭设备成功 | +| -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | +| 其他错误码 | 关闭设备失败 | + +关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 + +## 看门狗设备使用示例 + +看门狗设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下: + +1. 根据设备名称 “wdt” 查找设备获取设备句柄。 +2. 初始化设备后设置看门狗溢出时间。 +3. 启动看门狗。 +4. 喂狗:设置空闲线程回调函数,在空闲线程回调函数中喂狗。 + +```c +/* + * 程序清单:这是一个独立看门狗设备使用例程 + * 例程导出了 wdt_sample 命令到控制终端 + * 命令调用格式:wdt_sample wdt + * 命令解释:命令第二个参数是要使用的看门狗设备名称,为空则使用例程默认的看门狗设备。 + * 程序功能:程序通过设备名称查找看门狗设备,然后初始化设备并设置看门狗设备溢出时间。 + * 然后设置空闲线程回调函数,在回调函数里会喂狗。 +*/ + +#include +#include + +#define WDT_DEVICE_NAME "wdt" /* 看门狗设备名称 */ + +static rt_device_t wdg_dev; /* 看门狗设备句柄 */ + +static void idle_hook(void) +{ + /* 在空闲线程的回调函数里喂狗 */ + rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL); + rt_kprintf("feed the dog!\n "); +} + +static int wdt_sample(int argc, char *argv[]) +{ + rt_err_t ret = RT_EOK; + rt_uint32_t timeout = 1; /* 溢出时间,单位:秒 */ + char device_name[RT_NAME_MAX]; + + /* 判断命令行参数是否给定了设备名称 */ + if (argc == 2) + { + rt_strncpy(device_name, argv[1], RT_NAME_MAX); + } + else + { + rt_strncpy(device_name, WDT_DEVICE_NAME, RT_NAME_MAX); + } + /* 根据设备名称查找看门狗设备,获取设备句柄 */ + wdg_dev = rt_device_find(device_name); + if (!wdg_dev) + { + rt_kprintf("find %s failed!\n", device_name); + return RT_ERROR; + } + + /* 设置看门狗溢出时间 */ + ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout); + if (ret != RT_EOK) + { + rt_kprintf("set %s timeout failed!\n", device_name); + return RT_ERROR; + } + /* 启动看门狗 */ + ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_START, RT_NULL); + if (ret != RT_EOK) + { + rt_kprintf("start %s failed!\n", device_name); + return -RT_ERROR; + } + /* 设置空闲线程回调函数 */ + rt_thread_idle_sethook(idle_hook); + + return ret; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(wdt_sample, wdt sample); +``` + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_1.png b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_1.png new file mode 100644 index 0000000000000000000000000000000000000000..6686efbcfe1e423b479e1e0a47166981f99d1422 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_3.png b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_3.png new file mode 100644 index 0000000000000000000000000000000000000000..42c276770c0d4e862150468b57b3493f108935f5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_3.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_4.png b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_4.png new file mode 100644 index 0000000000000000000000000000000000000000..ed59def6122d3b7fed77b7e5372060b80b95a166 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_4.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_5.png b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_5.png new file mode 100644 index 0000000000000000000000000000000000000000..660f77c0015ba8b4af3299fc01fc92b7e11077f7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/figures/an0026_5.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/wlan.md b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/wlan.md new file mode 100644 index 0000000000000000000000000000000000000000..7292c19b887c48ac65a78ce32dbf288a2449c54e --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device/wlan/wlan.md @@ -0,0 +1,963 @@ +# WLAN 设备 + +随着物联网快速发展,越来越多的嵌入式设备上搭载了 WIFI 无线网络设备。为了能够管理 WIFI 网络设备,RT-Thread 引入了 WLAN 设备管理框架。这套框架具备控制和管理 WIFI 的众多功能,为开发者使用 WIFI 设备提供许多便利。 + +## WLAN 框架简介 + +WLAN 框架是 RT-Thread 开发的一套用于管理 WIFI 的中间件。对下连接具体的 WIFI 驱动,控制 WIFI 的连接断开,扫描等操作。对上承载不同的应用,为应用提供 WIFI 控制,事件,数据导流等操作,为上层应用提供统一的 WIFI 控制接口。WLAN 框架主要由三个部分组成。DEV 驱动接口层,为 WLAN 框架提供统一的调用接口。Manage 管理层为用户提供 WIFI 扫描,连接,断线重连等具体功能。Protocol 协议负责处理 WIFI 上产生的数据流,可根据不同的使用场景挂载不同通讯协议,如 LWIP 等。具有使用简单,功能齐全,对接方便,兼容性强等特点。 + +下图是 WIFI 框架层次图: + +![WIFI 框架](figures/an0026_1.png) + +第一部分 app 为应用层。是基于 WLAN 框架的具体应用,如 WiFi 相关的 Shell 命令。 + +第二部分 airkiss、voice 为配网层。提供无线配网和声波配网等功能。 + +第三部分 WLAN manager 为 WLAN 管理层。能够对 WLAN 设备进行控制和管理。具备设置模式、连接热点、断开热点、启动热点、扫描热点等 WLAN 控制相关的功能。还提供断线重连,自动切换热点等管理功能。 + +第四部分 WLAN protocol 为协议层。将数据流递交给具体协议进行解析,用户可以指定使用不同的协议进行通信。 + +第五部分 WLAN config 为参数管理层。管理连接成功的热点信息及密码,并写入非易失的存储介质中。 + +第六部分 WLAN dev 为驱动接口层。对接具体 WLAN 硬件,为管理层提供统一的调用接口。 + +### 功能简介 + +* 自动连接:打开自动连接功能后,只要 WIFI 处在断线状态,就会自动读取之前连接成功的热点信息,连接热点。如果一个热点连接失败,则切换下一个热点信息进行连接,直到连接成功为止。自动连接使用的热点信息,按连接成功的时间顺序,依次尝试,优先使用最近连接成功的热点信息。连接成功后,将热点信息缓存在最前面,下次断线优先使用。 + +* 参数存储:存储连接成功的 WIFI 参数,WIFI 参数会在内存中缓存一份,如果配置外部非易失存储接口,则会在外部存储介质中存储一份。用户可根据自己的实际情况,实现 `struct rt_wlan_cfg_ops` 这个结构体,将参数保存任何地方。缓存的参数主要给自动连接提供热点信息,wifi 处在未连接状态时,会读取缓存的参数,尝试连接。 + +* WIFI 控制:提供完备的 WIFI 控制接口,扫描,连接,热点等。提供 WIFI 相关状态回调事件,断开,连接,连接失败等。为用户提供简单易用的 WIFI 管理接口。 + +* Shell 命令:可在 Msh 中输入命令控制 WIFI 执行扫描,连接,断开等动作。打印 WIFI 状态等调试信息。 + +### 配置选项 + +在 ENV工具中使用 `menuconfig`命令按照以下菜单进入 WLAN 配置界面: + +```c +RT-Thread Components -> Device Drivers -> Using WiFi -> +``` + +各个配置选项详细描述如下: + +```shell +(wlan0) The device name for station /* Station 设备默认名字 */ +(wlan1) The device name for ap /* AP 设备默认名字 */ +(32) SSID maximum length /* SSID 最大长度 */ +(64) Password maximum length /* 密码最大长度 */ +(2) Driver events maxcount /* 事件最大注册数 */ +[*] Connection management Enable /* 连接管理功能使能 */ +(10000) Set scan timeout time(ms) /* 扫描超时时间 */ +(10000) Set connect timeout time(ms) /* 连接超时时间 */ +[*] Automatic sorting of scan results /* 扫描结果自动排序 */ +[*] MSH command Enable /* MSH 命令功能使能 */ +[*] Auto connect Enable /* 自动连接功能使能 */ +(2000) Auto connect period(ms) /* 断线检查周期 */ +-*- WiFi information automatically saved Enable /* 连接参数自动存储使能 */ +(3) Maximum number of WiFi information automatically saved /* 连接参数最大存储数量 */ +[*] Transport protocol manage Enable /* 传输协议管理功能使能 */ +(8) Transport protocol name length /* 传输协议名字最大长度 */ +(2) Transport protocol maxcount /* 传输协议类型数量 */ +(lwip) Default transport protocol /* 默认传输协议名字 */ +[*] LWIP transport protocol Enable /* LWIP 传输协议使能 */ +(lwip) LWIP transport protocol name /* LWIP 传输协议名字 */ +[ ] Forced use of PBUF transmission /* 强制使用 PBUF 交换数据 */ +-*- WLAN work queue thread Enable /* WLAN 线程使能 */ +(wlan) WLAN work queue thread name /* WLAN 线程名字 */ +(2048) WLAN work queue thread size /* WLAN 线程栈大小 */ +(15) WLAN work queue thread priority /* WLAN 线程优先级 */ +[ ] Enable WLAN Debugging Options ---> /* 打开调试日志 */ +``` + +应用程序通过 WLAN 连接管理层相关 API 来访问硬件设备,相关接口如下所示: + +## WLAN 初始化 + +| **函数** | **描述** | +| --------------------- | ---------------------- | +| rt_wlan_init() | 初始化连接管理器 | +| rt_wlan_set_mode() | 设置工作模式 | +| rt_wlan_get_mode() | 获取设备工作模式 | + +### 连接管理初始化 + +`int rt_wlan_init(void)` + +初始化连接管理器需要的静态资源,如全局变量,线程,互斥锁等。支持自动初始化,无需用户调用。如果没用使能自动初始化,在使用 WLAN 相关 API 之前,需要手动调用进行初始化。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +|无 | | +| **返回值** | **描述** | +| 0 | 执行成功 | + +### 设置设备模式 + +`rt_err_t rt_wlan_set_mode(const char *dev_name, rt_wlan_mode_t mode)` + +设置 WLAN 设备的工作模。同一个设备,切换相同的模式无效,一种模式,只能存在一个设备,不能两个设备设置同一个模式。一般的,一个设备只支持一种模式。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +|`dev_name` | 设备名字 | +|`mode` | 工作模式 | +| **返回值** | **描述** | +| RT_EOK | 设置成功 | +| -RT_ERROR | 设置失败 | + +WLAN 设备工作模式如下: + +```c +typedef enum +{ + RT_WLAN_NONE, /* 停止工作模式 */ + RT_WLAN_STATION, /* 无线终端模式 */ + RT_WLAN_AP, /* 无线接入服务模式 */ + RT_WLAN_MODE_MAX /* 无效 */ +} rt_wlan_mode_t; +``` + +系统默认会提供一个默认的 STA 设备名和 AP 设备名,下面示例将展示默认 STA 设备工作在无线终端模式: + +```c +rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION); +``` + +### 获取设备模式 + +`rt_wlan_mode_t rt_wlan_get_mode(const char *dev_name)` + +获得设备的工作模式。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +|`dev_name` | 设备名字 | +| **返回值** | **描述** | +| RT_WLAN_NONE | 设备停止工作 | +| RT_WLAN_STATION | 无线终端模式 | +| RT_WLAN_AP | 无线接入服务模式 | + +## WLAN 连接 + +| **函数** | **描述** | +| ------------------------ | ---------------------------- | +| rt_wlan_connect() | 连接热点 | +| rt_wlan_connect_adv() | 无阻塞连接热点 | +| rt_wlan_disconnect() | 断开热点 | +| rt_wlan_is_connected() | 获取连接标志 | +| rt_wlan_is_ready() | 获取就绪标志 | +| rt_wlan_get_info() | 获取连接信息 | +| rt_wlan_get_rssi() | 获取信号强度 | + +### 连接热点 + +`rt_err_t rt_wlan_connect(const char *ssid, const char *password)` + +阻塞式连接热点。此 API调用的时间会比较长,连接成功或失败后才会返回。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +|`ssid` | 热点的名字 | +|`password` | 热点密码,无密码传空 | +| **返回** | **描述** | +| RT_EOK | 连接成功 | +| -RT_ERROR | 连接失败 | + +WLAN 连接成功,还不能进行数据通讯,需要等待连接就绪才能通讯。 + +### 无阻塞连接 + +`rt_err_t rt_wlan_connect_adv(struct rt_wlan_info *info, const char *password)` + +非阻塞连接热点,连接参数可通过扫描获得或手动指定。一般用于连接特定热点或隐藏热点,返回值仅表示连接动作是否开始执行,是否连接成功需要主动查询或设置回调通知。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +|`info` | 连接信息 | +|`password` | 热点密码,无密码传空 | +| **返回** | **描述** | +| RT_EOK | 执行成功 | +| -RT_ERROR | 执行失败 | + +连接信息必须配置的项有`security`、`ssid`。完整配置如下: + +```c +struct rt_wlan_info +{ + rt_wlan_security_t security; /* 安全类型 */ + rt_802_11_band_t band; /* 2.4G / 5G */ + rt_uint32_t datarate; /* 连接速率 */ + rt_int16_t channel; /* 通道 */ + rt_int16_t rssi; /* 信号强度 */ + rt_wlan_ssid_t ssid; /* 热点名称 */ + rt_uint8_t bssid[RT_WLAN_BSSID_MAX_LENGTH]; /* 热点物理地址 */ + rt_uint8_t hidden; /* 热点隐藏标志 */ +}; +``` + +安全模式如下所示: + +```c +typedef enum +{ + SECURITY_OPEN = 0, /* Open security */ + SECURITY_WEP_PSK = WEP_ENABLED, /* WEP Security with open authentication */ + SECURITY_WEP_SHARED = (WEP_ENABLED | SHARED_ENABLED), /* WEP Security with shared authentication */ + SECURITY_WPA_TKIP_PSK = (WPA_SECURITY | TKIP_ENABLED), /* WPA Security with TKIP */ + SECURITY_WPA_AES_PSK = (WPA_SECURITY | AES_ENABLED), /* WPA Security with AES */ + SECURITY_WPA2_AES_PSK = (WPA2_SECURITY | AES_ENABLED), /* WPA2 Security with AES */ + SECURITY_WPA2_TKIP_PSK = (WPA2_SECURITY | TKIP_ENABLED), /* WPA2 Security with TKIP */ + SECURITY_WPA2_MIXED_PSK = (WPA2_SECURITY | AES_ENABLED | TKIP_ENABLED), /* WPA2 Security with AES & TKIP */ + SECURITY_WPS_OPEN = WPS_ENABLED, /* WPS with open security */ + SECURITY_WPS_SECURE = (WPS_ENABLED | AES_ENABLED), /* WPS with AES security */ + SECURITY_UNKNOWN = -1, /* security is unknown. */ +} rt_wlan_security_t; +``` + +下面将展示使用指定连接信息执行连接。 + +```c +struct rt_wlan_info info; + +INVALID_INFO(&info); /* 初始化 info */ +SSID_SET(&info, "test_ap"); /* 设置热点名字 */ +info.security = SECURITY_WPA2_AES_PSK; /* 指定安全类型 */ +rt_wlan_connect_adv(&info, "12345678"); /* 执行连接动作 */ +while (rt_wlan_is_connected() == RT_FALSE); /* 等待连接成功 */ +``` + +### 断开热点 + +`rt_err_t rt_wlan_disconnect(void)` + +阻塞式断开连接,返回值表示是否成功断开。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| RT_EOK | 断开成功 | +| -RT_ERROR | 断开失败 | + +执行断开之前,建议先查询是否已经连接,如果已经连接,再执行断开。示例代码如下: + +```c +if (rt_wlan_is_connected()) /* 判断是否已经连接 */ +{ + rt_wlan_disconnect(); /* 断开连接 */ +} +``` + +### 获取连接标志 + +`rt_bool_t rt_wlan_is_connected(void)` + +查询是否连接到热点。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| RT_TRUE | 已经连接 | +| RT_FALSE | 没有连接 | + +### 获取就绪标志 + +`rt_bool_t rt_wlan_is_ready(void)` + +查询连接是否就绪。一般的,获取到 IP 表示已经准备就绪,可以传输数据。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| RT_TRUE | 已经就绪 | +| RT_FALSE | 没有就绪 | + +### 获取连接信息 + +`rt_err_t rt_wlan_get_info(struct rt_wlan_info *info)` + +获取详细的连接信息,可获取热点名字、通道、信号强度、安全类型等。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `info` | info 对象 | +| **返回** | **描述** | +| RT_EOK | 获取成功 | +| -RT_ERROR | 获取失败 | + +### 获取信号强度 + +`int rt_wlan_get_rssi(void);` + +> 获得信号强度。信号强度为负值,值越大信号越强。例如信号强度 -25 比 信号强度 -55 要好。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| 负数 | 信号强度 | +| 0 | 未连接 | + +## WLAN 扫描 + +| **函数** | **描述** | +| ----------------------------- | ----------------------- | +| rt_wlan_scan() | 异步扫描 | +| rt_wlan_scan_sync() | 同步扫描 | +| rt_wlan_scan_with_info() | 条件扫描 | +| rt_wlan_scan_get_info_num() | 获取热点个数 | +| rt_wlan_scan_get_info() | 拷贝热点信息 | +| rt_wlan_scan_get_result() | 获取扫描缓存 | +| rt_wlan_scan_result_clean() | 清理扫描缓存 | +| rt_wlan_find_best_by_cache() | 查找最佳热点 | + +### 异步扫描 + +`rt_err_t rt_wlan_scan(void)` + +异步扫描函数,扫描完成需要通过回调进行通知。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| RT_EOK | 启动扫描成功 | +| -RT_ERROR | 启动扫描失败 | + +### 同步扫描 + +`struct rt_wlan_scan_result *rt_wlan_scan_sync(void)` + +> 同步扫描函数,扫描全部热点信息,完成过直接返回扫描结果。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| 扫描结果 | 热点信息和数量 | +| RT_NULL | 扫描失败 | + +该接口执行成功,会返回 `struct rt_wlan_scan_result` 类型的指针,包含了热点的详细信息和数量。结构体定义如下: + +```c +struct rt_wlan_scan_result +{ + rt_int32_t num; /* 热点个数 */ + struct rt_wlan_info *info; /* 热点信息 */ +}; +``` + +结构体中的 info 是连续的内存块,可通过类似数组的形式进行访问。示例如下: + +```c +result = rt_wlan_scan_sync(void); /* 获取扫描结果 */ +for (i = 0; i < result->num; i++) /* 根据扫描到的结果,进行遍历 */ +{ + printf("SSID:%s\n", result->info[i].ssid.val); /* 使用数组的形式进行访问,打印扫描到的 SSID 信息 */ +} +``` + +### 条件扫描 + +`struct rt_wlan_scan_result *rt_wlan_scan_with_info(struct rt_wlan_info *info)` + +同步条件扫描。根据参入的条件进行过滤,可用于扫描指定 SSID。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `info` | 通过 info 指定限定条件 | +| **返回** | **描述** | +| 扫描结果 | 热点信息和数量 | +| RT_NULL | 扫描失败 | + +下面示例将展示扫描指定 SSID 的热点信息。 + +```c +struct rt_wlan_info info; + +INVALID_INFO(&info); /* 初始化 info */ +SSID_SET(&info, "test_ap"); /* 指定 SSID */ +result = rt_wlan_scan_with_info(&info); /* 开始同步扫描 */ +``` + +### 获取热点个数 + +`int rt_wlan_scan_get_result_num(void)` + +返回扫描到的热点数量。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| 数量 | 热点数量 | + +### 拷贝热点信息 + +`int rt_wlan_scan_get_info(struct rt_wlan_info *info, int num)` + +拷贝热点信息。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `info` | info 缓存,用于保存拷贝结果 | +| `num` | info 个数 | +| **返回** | **描述** | +| 数量 | 实际拷贝的个数 | + +下面代码片段将展示如何拷贝热点信息 + +```c +num = rt_wlan_scan_get_result_num(); /* 查询热点数量 */ +info = rt_malloc(sizeof(struct rt_wlan_info) * num); /* 分配内存 */ +rt_wlan_scan_get_info(info, num); /* 拷贝 */ +``` + +### 获取扫描缓存 + +`struct rt_wlan_scan_result *rt_wlan_scan_get_result(void)` + +返回扫描缓存。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| 扫描缓存指针 | 该指针不安全,仅作临时访问 | + +### 清理扫描缓存 + +`void rt_wlan_scan_result_clean(void)` + +清理扫描缓存。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| 无 | | + +### 查找最佳热点 + +`rt_bool_t rt_wlan_find_best_by_cache(const char *ssid, struct rt_wlan_info *info)` + +指定 SSID ,在扫描缓存中查找信号最好的热点信息。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `ssid` | 指定需要查询的 ssid | +| `info` | 存查询到的热点信息 | +| **返回** | **描述** | +| RT_FALSE | 没有查到 | +| RT_TRUE | 查到 | + +## WLAN 热点 + +| **函数** | **描述** | +| ----------------------------- | ----------------------- | +| rt_wlan_start_ap() | 启动热点 | +| rt_wlan_start_ap_adv() | 无阻塞启动热点 | +| rt_wlan_ap_is_active() | 获取启动标志 | +| rt_wlan_ap_stop() | 停止热点 | +| rt_wlan_ap_get_info() | 获取热点信息 | + +### 启动热点 + +`rt_err_t rt_wlan_start_ap(const char *ssid, const char *password)` + +阻塞式启动热点,返回值表示是否启动成功。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `ssid` | 热点名字 | +| `password` | 热点密码。 | +| **返回** | **描述** | +| RT_EOK | 启动成功 | +| -RT_ERROR | 启动失败 | + +### 非阻塞启动热点 + +`rt_err_t rt_wlan_start_ap_adv(struct rt_wlan_info *info, const char *password)` + +非阻塞启动热点,可以指加密类型,通道等。热点是否启动需要手动查询或回调通知。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `info` | 热点信息 | +| `password` | 热点密码,开放热点传空 | +| **返回** | **描述** | +| RT_EOK | 执行成功 | +| -RT_ERROR | 执行失败 | + +### 获取启动标志 + +`rt_bool_t rt_wlan_ap_is_active(void)` + +> 查询热点是否处于活动状态。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| RT_TRUE | 热点启动 | +| -RT_FALSE | 热点未启动 | + +### 停止热点 + +`rt_err_t rt_wlan_ap_stop(void)` + +> 阻塞式停止热点。停止之前先查询是否已经启动,已经启动后在停止。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| RT_EOK | 停止成功 | +| -RT_ERROR | 停止失败 | + +### 获取热点信息 + +`rt_err_t rt_wlan_ap_get_info(struct rt_wlan_info *info)` + +> 获取热点相关信息,如热点名字,通道等。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| info | 热点信息 | +| **返回** | **描述** | +| RT_EOK | 获取成功 | +| -RT_ERROR | 获取失败 | + +## WLAN 自动重连 + +| **函数** | **描述** | +| ---------------------------------- | --------------------- | +| rt_wlan_config_autoreconnect() | 启动/停止自动重连 | +| rt_wlan_get_autoreconnect_mode() | 获取自动重连模式 | + +### 启动/停止自动重连 + +`void rt_wlan_config_autoreconnect(rt_bool_t enable)` + +开启或关闭自动重连模式,当没有网络时,会自动进行重连。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `enable` | 开启或关闭 | +| **返回** | **描述** | +| 无 | 执行成功 | + +### 获取自动重连模式 + +`rt_bool_t rt_wlan_get_autoreconnect_mode(void)` + +查询自动重连是否启动。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| RT_TRUE | 启动 | +| RT_FALSE | 未启动 | + +## WLAN 事件回调 + +| **函数** | **描述** | +| ------------------------------------ | ----------------- | +| rt_wlan_register_event_handler() | 事件注册 | +| rt_wlan_unregister_event_handler() | 解除注册 | + +### 事件注册 + +`rt_err_t rt_wlan_register_event_handler(rt_wlan_event_t event, rt_wlan_event_handler handler, void *parameter)` + +> 注册事件回调函数。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `event` | 事件类型 | +| `handler` | 事件处理函数 | +| `parameter` | 用户参数 | +| **返回** | **描述** | +| RT_EOK | 注册成功 | +| -RT_ERROR | 注册失败 | + +WLAN 产生如下事件时,触发回调: + +```c +typedef enum +{ + RT_WLAN_EVT_READY = 0, /* 网络就绪 */ + RT_WLAN_EVT_SCAN_DONE, /* 扫描完成 */ + RT_WLAN_EVT_SCAN_REPORT, /* 扫描到一个热点 */ + RT_WLAN_EVT_STA_CONNECTED, /* 连接成功 */ + RT_WLAN_EVT_STA_CONNECTED_FAIL, /* 连接失败 */ + RT_WLAN_EVT_STA_DISCONNECTED, /* 断开连接 */ + RT_WLAN_EVT_AP_START, /* 热点启动 */ + RT_WLAN_EVT_AP_STOP, /* 热点停止 */ + RT_WLAN_EVT_AP_ASSOCIATED, /* STA 接入 */ + RT_WLAN_EVT_AP_DISASSOCIATED, /* STA 断开 */ +} rt_wlan_event_t; +``` + +WLAN 回调函数定义为: `void (*rt_wlan_event_handler)(int event, struct rt_wlan_buff *buff, void *parameter);`,其中 `buff` 根据事件的不同有不同的涵义。详细信息参考下表。 + +| 事件 | 类型 | 描述 | +| :----------------------------- | :--------------------------- | :---------------------- | +| RT_WLAN_EVT_READY | ip_addr_t * | IP 地址 | +| RT_WLAN_EVT_SCAN_DONE | struct rt_wlan_scan_result * | 扫描的结果 | +| RT_WLAN_EVT_SCAN_REPORT | struct rt_wlan_info * | 扫描到的热点信息 | +| RT_WLAN_EVT_STA_CONNECTED | struct rt_wlan_info * | 连接成功的 Station 信息 | +| RT_WLAN_EVT_STA_CONNECTED_FAIL | struct rt_wlan_info * | 连接失败的 Station 信息 | +| RT_WLAN_EVT_STA_DISCONNECTED | struct rt_wlan_info * | 断开连接的 Station 信息 | +| RT_WLAN_EVT_AP_START | struct rt_wlan_info * | 启动成功的 AP 信息 | +| RT_WLAN_EVT_AP_STOP | struct rt_wlan_info * | 启动失败的 AP 信息 | +| RT_WLAN_EVT_AP_ASSOCIATED | struct rt_wlan_info * | 连入的 Station 信息 | +| RT_WLAN_EVT_AP_DISASSOCIATED | struct rt_wlan_info * | 断开的 Station 信息 | + +### 解除注册 + +`rt_err_t rt_wlan_unregister_event_handler(rt_wlan_event_t event)` + +事件解除注册。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `event` | 事件类型 | +| **返回** | **描述** | +| RT_EOK | 解除成功 | + +## WLAN 功耗管理 + +| **函数** | **描述** | +| --------------------------------- | --------------------- | +| rt_wlan_set_powersave() | 设置功耗等级 | +| rt_wlan_get_powersave() | 获取功耗等级 | + +### 设置功耗等级 + +设置功耗等级,用于 station 模式。 + +`rt_err_t rt_wlan_set_powersave(int level)` + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| `level` | 功耗等级 | +| **返回** | **描述** | +| RT_EOK | 设置成功 | +| -RT_ERROR | 设置失败 | + +### 获取功耗等级 + +`int rt_wlan_get_powersave(void)` + +获取当前工作功耗等级。 + +| 参数 | 描述 | +|:------------------|:------------------------------------| +| 无 | | +| **返回** | **描述** | +| 功耗级别 | | + +## FinSH 命令 + +使用 shell 命令,可以帮助我们快速调试 WiFi 相关功能。wifi 相关的 shell 命令如下: + +```shell +wifi /* 打印帮助 */ +wifi help /* 查看帮助 */ +wifi join SSID [PASSWORD] /* 连接 wifi,SSDI 为空,使用配置自动连接 */ +wifi ap SSID [PASSWORD] /* 建立热点 */ +wifi scan /* 扫描全部热点 */ +wifi disc /* 断开连接 */ +wifi ap_stop /* 停止热点 */ +wifi status /* 打印 wifi 状态 sta + ap */ +wifi smartconfig /* 启动配网功能 */ +``` + +### WiFi 扫描 + +wifi 扫描命令为 `wifi scan`,执行 wifi 扫描命令后,会将周围的热点信息打印在终端上。通过打印的热点信息,可以看到 SSID,MAC 地址等多项属性。 + +在 msh 中输入该命令,扫描结果如下所示: + +```shell +wifi scan +SSID MAC security rssi chn Mbps +------------------------------- ----------------- -------------- ---- --- ---- +rtt_test_ssid_1 c0:3d:46:00:3e:aa OPEN -14 8 300 +test_ssid 3c:f5:91:8e:4c:79 WPA2_AES_PSK -18 6 72 +rtt_test_ssid_2 ec:88:8f:88:aa:9a WPA2_MIXED_PSK -47 6 144 +rtt_test_ssid_3 c0:3d:46:00:41:ca WPA2_MIXED_PSK -48 3 300 +``` + +### WiFi 连接 + +wifi 扫描命令为 `wifi join`,命令后面需要跟热点名称和热点密码,没有密码可不输入这一项。执行 WiFi 连接命令后,如果热点存在,且密码正确,开发板会连接上热点,并获得 IP 地址。网络连接成功后,可使用 socket 套接字进行网络通讯。 + +wifi 连接命令使用示例如下所示,连接成功后,将在终端上打印获得的 IP 地址,如下所示: + +```shell +wifi join ssid_test 12345678 +[I/WLAN.mgnt] wifi connect success ssid:ssid_test +[I/WLAN.lwip] Got IP address : 192.168.1.110 +``` + +### WiFi 断开 + +wifi 断开的命令为 `wifi disc`,执行 WiFi 断开命令后,开发板将断开与热点的连接。 + +WiFi 断开命令使用示例如下所示,断开成功后,将在终端上打印如下信息,如下所示 + +```shell +wifi disc +[I/WLAN.mgnt] disconnect success! +``` + +## WLAN 设备使用示例 + +### 扫描示例 + +下面这段代码将展示 WiFi 同步扫描,然后我们将结果打印在终端上。先需要执行 WIFI 初始化,然后执行 WIFI 扫描函数 `rt_wlan_scan_sync`, 这个函数是同步的,函数返回的扫描的数量和结果。在这个示例中,会将扫描的热点名字打印出来。 + +```c +#include +#include + +#include +#include +#include + +void wifi_scan(void) +{ + struct rt_wlan_scan_result *result; + int i = 0; + + /* Configuring WLAN device working mode */ + rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION); + /* WiFi scan */ + result = rt_wlan_scan_sync(); + /* Print scan results */ + rt_kprintf("scan num:%d\n", result->num); + for (i = 0; i < result->num; i++) + { + rt_kprintf("ssid:%s\n", result->info[i].ssid.val); + } +} + +int scan(int argc, char *argv[]) +{ + wifi_scan(); + return 0; +} +MSH_CMD_EXPORT(scan, scan test.); +``` + +运行结果如下: + +![扫描](figures/an0026_3.png) + +### 连接与断开示例 + +下面这段代码将展示 WiFi 同步连接。需要先执行 WIFI 初始化,然后创建一个用于等待 `RT_WLAN_EVT_READY` 事件的信号量。注册需要关注的事件的回调函数,执行 `rt_wlan_connect` wifi 连接函数,函数返回表示是否已经连接成功。但是连接成功还不能进行通信,还需要等待网络获取 IP。使用事先创建的信号量等待网络准备好,网络准备好后,就能正常通信了。 + +连接上 WIFI 后,等待一段时间后,执行 `rt_wlan_disconnect` 函数断开连接。断开操作是阻塞的,返回值表示是否断开成功。 + +```c +#include +#include + +#include +#include +#include + +#define WLAN_SSID "SSID-A" +#define WLAN_PASSWORD "12345678" +#define NET_READY_TIME_OUT (rt_tick_from_millisecond(15 * 1000)) + +static rt_sem_t net_ready = RT_NULL; + +static void +wifi_ready_callback(int event, struct rt_wlan_buff *buff, void *parameter) +{ + rt_kprintf("%s\n", __FUNCTION__); + rt_sem_release(net_ready); +} + +static void +wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter) +{ + rt_kprintf("%s\n", __FUNCTION__); + if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) + { + rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val); + } +} + +static void +wifi_disconnect_callback(int event, struct rt_wlan_buff *buff, void *parameter) +{ + rt_kprintf("%s\n", __FUNCTION__); + if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) + { + rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val); + } +} + +static void +wifi_connect_fail_callback(int event, struct rt_wlan_buff *buff, void *parameter) +{ + rt_kprintf("%s\n", __FUNCTION__); + if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) + { + rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val); + } +} + +rt_err_t wifi_connect(void) +{ + rt_err_t result = RT_EOK; + + /* Configuring WLAN device working mode */ + rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION); + /* station connect */ + rt_kprintf("start to connect ap ...\n"); + net_ready = rt_sem_create("net_ready", 0, RT_IPC_FLAG_FIFO); + rt_wlan_register_event_handler(RT_WLAN_EVT_READY, + wifi_ready_callback, RT_NULL); + rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED, + wifi_connect_callback, RT_NULL); + rt_wlan_register_event_handler(RT_WLAN_EVT_STA_DISCONNECTED, + wifi_disconnect_callback, RT_NULL); + rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED_FAIL, + wifi_connect_fail_callback, RT_NULL); + + /* connect wifi */ + result = rt_wlan_connect(WLAN_SSID, WLAN_PASSWORD); + + if (result == RT_EOK) + { + /* waiting for IP to be got successfully */ + result = rt_sem_take(net_ready, NET_READY_TIME_OUT); + if (result == RT_EOK) + { + rt_kprintf("networking ready!\n"); + } + else + { + rt_kprintf("wait ip got timeout!\n"); + } + rt_wlan_unregister_event_handler(RT_WLAN_EVT_READY); + rt_sem_delete(net_ready); + + rt_thread_delay(rt_tick_from_millisecond(5 * 1000)); + rt_kprintf("wifi disconnect test!\n"); + /* disconnect */ + result = rt_wlan_disconnect(); + if (result != RT_EOK) + { + rt_kprintf("disconnect failed\n"); + return result; + } + rt_kprintf("disconnect success\n"); + } + else + { + rt_kprintf("connect failed!\n"); + } + return result; +} + +int connect(int argc, char *argv[]) +{ + wifi_connect(); + return 0; +} +MSH_CMD_EXPORT(connect, connect test.); +``` + +运行结果如下 + +![连接断开](figures/an0026_4.png) + +### 自动连接示例 + +先开启自动重连功能,使用命令行连接上一个热点 A 后,在连接上另一个热点 B。等待几秒后,将热点 B 断电,系统会自动重试连接 B 热点,此时 B 热点连接不上,系统自动切换热点 A 进行连接。连接成功 A 后,系统停止连接。 + +```c +#include +#include + +#include +#include +#include + +static void +wifi_ready_callback(int event, struct rt_wlan_buff *buff, void *parameter) +{ + rt_kprintf("%s\n", __FUNCTION__); +} + +static void +wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter) +{ + rt_kprintf("%s\n", __FUNCTION__); + if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) + { + rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val); + } +} + +static void +wifi_disconnect_callback(int event, struct rt_wlan_buff *buff, void *parameter) +{ + rt_kprintf("%s\n", __FUNCTION__); + if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) + { + rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val); + } +} + +static void +wifi_connect_fail_callback(int event, struct rt_wlan_buff *buff, void *parameter) +{ + rt_kprintf("%s\n", __FUNCTION__); + if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) + { + rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val); + } +} + +int wifi_autoconnect(void) +{ + /* Configuring WLAN device working mode */ + rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION); + /* Start automatic connection */ + rt_wlan_config_autoreconnect(RT_TRUE); + /* register event */ + rt_wlan_register_event_handler(RT_WLAN_EVT_READY, + wifi_ready_callback, RT_NULL); + rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED, + wifi_connect_callback, RT_NULL); + rt_wlan_register_event_handler(RT_WLAN_EVT_STA_DISCONNECTED, + wifi_disconnect_callback, RT_NULL); + rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED_FAIL, + wifi_connect_fail_callback, RT_NULL); + return 0; +} + +int auto_connect(int argc, char *argv[]) +{ + wifi_autoconnect(); + return 0; +} +MSH_CMD_EXPORT(auto_connect, auto connect test.); +``` + +运行结果如下: + +![自动连接](figures/an0026_5.png) diff --git a/rt-thread-version/rt-thread-standard/programming-manual/dlmodule/dlmodule.md b/rt-thread-version/rt-thread-standard/programming-manual/dlmodule/dlmodule.md new file mode 100644 index 0000000000000000000000000000000000000000..9d1bdcb500bfed2d65e3b1f7e5268fe661a2fe40 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/dlmodule/dlmodule.md @@ -0,0 +1,311 @@ +# 动态模块 # + +在传统桌面操作系统中,用户空间和内核空间是分开的,应用程序运行在用户空间,内核以及内核模块则运行于内核空间,其中内核模块可以动态加载与删除以扩展内核功能。`dlmodule` 则是 RT-Thread 下,在内核空间对外提供的动态模块加载机制的软件组件。在 RT-Thread v3.1.0 以前的版本中,这也称之为应用模块(Application Module),在 RT-Thread v3.1.0 及之后,则回归传统,以动态模块命名。 + +`dlmodule` 组件更多的是一个 ELF 格式加载器,把单独编译的一个 elf 文件的代码段,数据段加载到内存中,并对其中的符号进行解析,绑定到内核导出的 API 地址上。动态模块 elf 文件主要放置于 RT-Thread 下的文件系统上。 + +## 功能简介 ## + +动态模块为 RT-Thread 提供了动态加载程序模块的机制,因为也独立于内核编译,所以使用方式比较灵活。从实现上讲,这是一种将内核和动态模块分开的机制,通过这种机制,内核和动态模块可以分开编译,并在运行时通过内核中的模块加载器将编译好的动态模块加载到内核中运行。 + +在 RT-Thread 的动态模块中,目前支持两种格式: + +* `.mo` 则是编译出来时以 `.mo` 做为后缀名的可执行动态模块;它可以被加载,并且系统中会自动创建一个主线程执行这个动态模块中的 `main` 函数;同时这个 `main(int argc, char**argv)` 函数也可以接受命令行上的参数。 +* `.so` 则是编译出来时以 `.so` 做为后缀名的动态库;它可以被加载,并驻留在内存中,并提供一些函数集由其他程序(内核里的代码或动态模块)来使用。 + +当前 RT-Thread 支持动态模块的架构主要包括 ARM 类架构和 x86 架构,未来会扩展到 MIPS,以及 RISC-V 等架构上。RT-Thread 内核固件部分可使用多种编译器工具链,如 GCC, ARMCC、IAR 等工具链;但动态模块部分编译当前只支持 GNU GCC 工具链编译。因此编译 RT-Thread 模块需下载 GCC 工具,例如 CodeSourcery 的 arm-none-eabi 工具链。一般的,最好内核和动态模块使用一样的工具链进行编译(这样不会在 libc 上产生不一致的行为)。另外,动态模块一般只能加载到 RAM 中使用,并进行符号解析绑定到内核导出的 API 地址上,而不能基于 Flash 直接以 XIP 方式运行(因为 Flash 上也不能够再行修改其中的代码段)。 + +## 使用动态模块 ## + +当要在系统中测试使用动态模块,需要编译一份支持动态模块的固件,以及需要运行的动态模块。下面将固件和动态模块的编译方式分为两部分进行介绍。 + +### 编译固件 ### + +当要使用动态模块时,需要在固件的配置中打开对应的选项,使用 menuconfig 打开如下配置: + +```c + RT-Thread Components ---> + POSIX layer and C standard library ---> + [*] Enable dynamic module with dlopen/dlsym/dlclose feature +``` + +也要打开文件系统的配置选项: + +```c + RT-Thread Components ---> + Device virtual file system ---> + [*] Using device virtual file system +``` + +bsp 对应的 rtconfig.py 中设置动态模块编译时需要用到的配置参数: + +```Python +M_CFLAGS = CFLAGS + ' -mlong-calls -fPIC ' +M_CXXFLAGS = CXXFLAGS + ' -mlong-calls -fPIC' +M_LFLAGS = DEVICE + CXXFLAGS + ' -Wl,--gc-sections,-z,max-page-size=0x4' +\ + ' -shared -fPIC -nostartfiles -nostdlib -static-libgcc' +M_POST_ACTION = STRIP + ' -R .hash $TARGET\n' + SIZE + ' $TARGET \n' +M_BIN_PATH = r'E:\qemu-dev310\fatdisk\root' +``` + +相关的解释如下: + +* M_CFLAGS - 动态模块编译时用到的 C 代码编译参数,一般此处以 PIC 方式进行编译(即代码地址支持浮动方式执行); +* M_CXXFLAGS - 动态模块编译时用到的 C++ 代码编译参数,参数和上面的 `M_CFLAGS` 类似; +* M_LFLAGS - 动态模块进行链接时的参数。同样是 PIC 方式,并且是按照共享库方式链接(部分链接); +* M_POST_ACTIOn - 动态模块编译完成后要进行的动作,这里会对 elf 文件进行 strip 下,以减少 elf 文件的大小; +* M_BIN_PATH - 当动态模块编译成功时,对应的动态模块文件是否需要复制到统一的地方; + +基本上来说,ARM9、Cortex-A、Cortex-M 系列的这些编译配置参数是一样的。 + +内核固件也会通过 `RTM(function)` 的方式导出一些函数 API 给动态模块使用,这些导出符号可以在 msh 下通过命令: + +`list_symbols` + +列出固件中所有导出的符号信息。`dlmodule` 加载器也是把动态模块中需要解析的符号按照这里导出的符号表来进行解析,完成最终的绑定动作。 + +这段符号表会放在一个专门的,名字是 RTMSymTab 的 section 中,所以对应的固件链接脚本也需要保留这块区域,而不会被链接器优化移除掉。可以在链接脚本中添加对应的信息: + +```text +/* section information for modules */ +. = ALIGN(4); +__rtmsymtab_start = .; +KEEP(*(RTMSymTab)) +__rtmsymtab_end = .; +``` + +然后在 BSP 工程目录下执行 `scons` 正确无误地生成固件后,在 BSP 工程目录下执行一下命令: + +`scons --target=ua -s` + +来生成编译动态模块时需要包括的内核头文件搜索路径及全局宏定义。 + +### 编译动态模块 ### + +在 github 上有一份独立仓库: [rtthread-apps](https://github.com/RT-Thread/rtthread-apps) ,这份仓库中放置了一些和动态模块,动态库相关的示例。 + +其目录结构如下: + +| **目录名** | **说明** | +| --- | ---------------- | +| cxx | 演示了如何在动态模块中使用 C++ 进行编程 | +| hello | 最简单的 `hello world` 示例 | +| lib | 动态库的示例 | +| md5 | 为一个文件产生 md5 码 | +| tools | 动态模块编译时需要使用到的 Python/SConscript 脚本 | +| ymodem | 通过串口以 YModem 协议下载一个文件到文件系统上 | + +可以把这份 git clone 到本地,然后在命令行下以 scons 工具进行编译,如果是 Windows 平台,推荐使用 RT-Thread/ENV 工具。 + +进入控制台命令行后,进入到这个 rtthread-apps repo 所在的目录(同样的,请保证这个目录所在全路径不包含空格,中文字符等字符),并设置好两个变量: + +* RTT_ROOT - 指向到 RT-Thread 代码的根目录; +* BSP_ROOT - 指向到 BSP 的工程目录; + +Windows 下可以使用 (假设使用的 BSP 是 qemu-vexpress-a9): + +```c +set RTT_ROOT=d:\your_rtthread +set BSP_ROOT=d:\your_rtthread\bsp\qemu-vexpress-a9 +``` + +来设置对应的环境变量。然后使用如下命令来编译动态模块,例如 hello 的例子: + +`scons --app=hello` + +编译成功后,它会在 rtthread-apps/hello 目录下生成 hello.mo 文件。 + +也可以编译动态库,例如 lib 的例子: + +`scons --lib=lib` + +编译成功后,它会在 rtthread-apps/lib 目录下生成 lib.so 文件。 + +我们可以把这些 mo、so 文件放到 RT-Thread 文件系统下。在 msh 下,可以简单的以 `hello` 命令方式执行 `hello.mo` 动态模块: + +```c +msh />ls +Directory /: +hello.mo 1368 +lib.so 1376 +msh />hello +msh />Hello, world +``` + +调用 hello 后,会执行 hello.mo 里的 main 函数,执行完毕后退出对应的动态模块。其中 `hello/main.c` 的代码如下: + +```c +#include + +int main(int argc, char *argv[]) +{ + printf("Hello, world\n"); + + return 0; +} +``` + +## RT-Thread 动态模块 API + +除了可以通过 msh 直接加载并执行动态模块外,也可以在主程序中使用 RT-Thread 提供的动态模块 API 来加载或卸载动态模块。 + +### 加载动态模块 + +```c +struct rt_dlmodule *dlmodule_load(const char* pgname); +``` + +|**参数**|**描述**| +| ---- | ---- | +| pgname | 动态模块的路径 | +|**返回**| —— | +| 动态模块指针 | 成功加载 | +| RT_NULL | 失败 | + +这个函数从文件系统中加载动态模块到内存中,若正确加载返回该模块的指针。这个函数并不会创建一个线程去执行这个动态模块,仅仅把模块加载到内存中,并解析其中的符号地址。 + +### 执行动态模块 + +```c +struct rt_dlmodule *dlmodule_exec(const char* pgname, const char* cmd, int cmd_size); +``` + +|**参数**|**描述**| +| ---- | ---- | +| pgname | 动态模块的路径 | +| cmd | 包括动态模块命令自身的命令行字符串 | +| cmd_size | 命令行字符串大小 | +|**返回**| —— | +| 动态模块指针 | 成功运行 | +| RT_NULL | 失败 | + +这个函数根据 `pgname` 路径加载动态模块,并启动一个线程来执行这个动态模块的 `main` 函数,同时 `cmd` 会作为命令行参数传递给动态模块的 `main` 函数入口。 + +### 退出动态模块 + +```c +void dlmodule_exit(int ret_code); +``` + +|**参数**|**描述**| +| ---- | ---- | +| ret_code | 模块的返回参数 | + +这个函数由模块运行时调用,它可以设置模块退出的返回值 `ret_code`,然后从模块退出。 + +### 查找动态模块 + +```c +struct rt_dlmodule *dlmodule_find(const char *name); +``` + +|**参数**|**描述**| +| ---- | ---- | +| name | 模块名称 | +|**返回**| —— | +| 动态模块指针 | 成功 | +| RT_NULL | 失败 | + +这个函数以 `name` 查找系统中是否已经有加载的动态模块。 + +### 返回动态模块 + +```c +struct rt_dlmodule *dlmodule_self(void); +``` + +|**参数**|**描述**| +| ---- | ---- | +|**返回**| —— | +| 动态模块指针 | 成功 | +| RT_NULL | 失败 | + +这个函数返回调用上下文环境下动态模块的指针。 + +### 查找符号 + +```c +rt_uint32_t dlmodule_symbol_find(const char *sym_str); +``` + +|**参数**|**描述**| +| ---- | ---- | +| sym_str | 符号名称 | +|**返回**| —— | +| 符号地址 | 成功 | +| 0 | 失败 | + +这个函数根据符号名称返回符号地址。 + +## 标准 POSIX 动态库 libdl API ## + +在 RT-Thread dlmodule 中也支持 POSIX 标准的 libdl API,类似于把一个动态库加载到内存中(并解析其中的一些符号信息),由这份动态库提供对应的函数操作集。libdl API 需要包含的头文件: + +`#include ` + +### 打开动态库 + +```c +void * dlopen (const char * pathname, int mode); +``` + +|**参数**|**描述**| +| ---- | ---- | +| pathname | 动态库路径名称 | +| mode | 打开动态库时的模式,在 RT-Thread 中并未使用 | +|**返回**| —— | +| 动态库的句柄 (`struct dlmodule` 结构体指针) | 成功 | +| NULL | 失败 | + +这个函数类似 `dlmodule_load` 的功能,会从文件系统上加载动态库,并返回动态库的句柄指针。 + +### 查找符号 + +```c +void* dlsym(void *handle, const char *symbol); +``` + +|**参数**|**描述**| +| ---- | ---- | +| handle | 动态库句柄,`dlopen` 的返回值 | +| symbol | 要返回的符号地址 | +|**返回**| —— | +| 符号地址 | 成功 | +| NULL | 失败 | + +这个函数在动态库 `handle` 中查找是否存在 `symbol` 的符号,如果存在返回它的地址。 + +### 关闭动态库 + +``` +int dlclose (void *handle); +``` + +|**参数**|**描述**| +| ---- | ---- | +| handle | 动态库句柄 | +|**返回**| —— | +| 0 | 成功 | +| 负数 | 失败 | + +这个函数会关闭 `handle` 指向的动态库,从内存中卸载掉。需要注意的是,当动态库关闭后,原来通过 `dlsym` 返回的符号地址将不再可用。如果依然尝试去访问,可能会引起 fault 错误。 + +## 常见问题 + +### Env 工具的相关问题请参考 [《Env 用户手册》](../env/env.md)。 + +### Q: 根据文档不能成功运行动态模块。 + +**A:** 请更新 RT-Thread 源代码到 3.1.0 及以上版本。 + +### Q: 使用 scons 命令编译工程,提示 undefined reference to __rtmsymtab_start。 + +**A:** 请参考 qemu-vexpress-a9 BSP 的 GCC 链接脚本文件 link.lds,在工程的 GCC 链接脚本的 TEXT 段增加以下内容。 + +``` + /* section information for modules */ + . = ALIGN(4); + __rtmsymtab_start = .; + KEEP(*(RTMSymTab)) + __rtmsymtab_end = .; +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/dfs2.txt b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/dfs2.txt new file mode 100644 index 0000000000000000000000000000000000000000..c88135ce95ba97421d0f71b2ebfac64016eb241a --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/dfs2.txt @@ -0,0 +1,21 @@ +@startuml + +participant 初始化模块 + +participant SPIFlash驱动 + +participant SFUD模块 + +participant IO设备管理器 + +初始化模块->SPIFlash驱动: 初始化SPIFlash spiflash_init() + +SPIFlash驱动->IO设备管理器: 创建并注册SPI从设备 rt_device_register() + +初始化模块->SFUD模块: 探测设备rt_sfud_flash_probe() + +SFUD模块->IO设备管理器: 查找SPIFlash从设备rt_device_find() + +SFUD模块->IO设备管理器: 创建并注册块设备rt_device_register() + +@enduml \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/dfs4.txt b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/dfs4.txt new file mode 100644 index 0000000000000000000000000000000000000000..d314c7e0bed9323c8d741f691b227a5f8bfd5097 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/dfs4.txt @@ -0,0 +1,22 @@ +@startuml + +participant Application +participant DFS +participant Device + +Application->DFS: dfs_mkfs() +activate Application + +DFS-->Application +deactivate Application +activate DFS + +DFS->Device: rt_device_find() +activate Device +deactivate DFS + +Device-->DFS +deactivate Device +activate DFS + +@enduml \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-mkfs b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-mkfs new file mode 100644 index 0000000000000000000000000000000000000000..5ebda076abef4b9e2433290068a713f347706790 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-mkfs @@ -0,0 +1,18 @@ +@startuml + +participant Ӧó +participant DFS +participant elmFATļϵͳ +participant IO豸 +participant SFUDģ +participant SPIFlash + +Ӧó->DFS: ʽ dfs_mkfs() + +DFS->IO豸: 豸 rt_device_find() +DFS->elmFATļϵͳ: FATʽ dfs_elm_mkfs() +elmFATļϵͳ->IO豸: rt_device_write() +IO豸->SFUDģ: rt_sfud_write() +SFUDģ->SPIFlash: spiflash_write() + +@enduml \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-mkfs.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-mkfs.png new file mode 100644 index 0000000000000000000000000000000000000000..15113fee426344f3ccd8f7a030aa10430978630a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-mkfs.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-reg b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-reg new file mode 100644 index 0000000000000000000000000000000000000000..10653ac734d717ff98bb54f0b655f8a15a0b7583 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-reg @@ -0,0 +1,15 @@ +@startuml + +participant ʼģ + +participant DFS + +participant elmFATļϵͳ + +ʼģ->DFS: ʼDFS DFS_init() + +ʼģ->elmFATļϵͳ: ʼFATļϵͳ elm_init() + +elmFATļϵͳ->DFS: עFATļϵͳ dfs_register() + +@enduml \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-reg.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-reg.png new file mode 100644 index 0000000000000000000000000000000000000000..71b7878403d6230da98ffeafa1c6d06efea6ab12 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/elm-fat-reg.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-dir-mg.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-dir-mg.png new file mode 100644 index 0000000000000000000000000000000000000000..9df97048841e864bb5c878e3f459055126f7540f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-dir-mg.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-dir.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-dir.png new file mode 100644 index 0000000000000000000000000000000000000000..14202280171c5a78742e2b3cc5790398a4740c2d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-dir.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-layer.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-layer.png new file mode 100644 index 0000000000000000000000000000000000000000..c0e0772cad4ea14dfb52d0b6274c2c21f1ddf5c7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-layer.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mdk.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mdk.png new file mode 100644 index 0000000000000000000000000000000000000000..2496dee3e5570f8a3074d01eaabc964f8bae16e0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mdk.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mg.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mg.png new file mode 100644 index 0000000000000000000000000000000000000000..aa8505119fd2c8b0aeb5a9c1c8d57094a3da97fe Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mg.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mkfs.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mkfs.png new file mode 100644 index 0000000000000000000000000000000000000000..71b7878403d6230da98ffeafa1c6d06efea6ab12 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-mkfs.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-reg-block.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-reg-block.png new file mode 100644 index 0000000000000000000000000000000000000000..77869c78c4e61e07337ab5a0d2697f3a4ff6378e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-reg-block.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-reg.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-reg.png new file mode 100644 index 0000000000000000000000000000000000000000..71b7878403d6230da98ffeafa1c6d06efea6ab12 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-reg.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-spi-flash.png b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-spi-flash.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf06f8bdf9204e831b025a12825868a2f738e25 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/figures/fs-spi-flash.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/filesystem/filesystem.md b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/filesystem.md new file mode 100644 index 0000000000000000000000000000000000000000..bae47a3cda019e1d6cdcf870fd7a7925e4c8da27 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/filesystem/filesystem.md @@ -0,0 +1,1049 @@ +# 虚拟文件系统 + +在早期的嵌入式系统中,需要存储的数据比较少,数据类型也比较单一,往往使用直接在存储设备中的指定地址写入数据的方法来存储数据。然而随着嵌入式设备功能的发展,需要存储的数据越来越多,也越来越复杂,这时仍使用旧方法来存储并管理数据就变得非常繁琐困难。因此我们需要新的数据管理方式来简化存储数据的组织形式,这种方式就是我们接下来要介绍的文件系统。 + +文件系统是一套实现了数据的存储、分级组织、访问和获取等操作的抽象数据类型 (Abstract data type),是一种用于向用户提供底层数据访问的机制。文件系统通常存储的基本单位是文件,即数据是按照一个个文件的方式进行组织。当文件比较多时,将导致文件繁多,不易分类、重名的问题。而文件夹作为一个容纳多个文件的容器而存在。 + +本章讲解 RT-Thread 文件系统相关内容,带你了解 RT-Thread 虚拟文件系统的架构、功能特点和使用方式。 + +## DFS 简介 + +DFS 是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System,即设备虚拟文件系统,文件系统的名称使用类似 UNIX 文件、文件夹的风格,目录结构如下图所示: + +![目录结构图](figures/fs-dir.png) + +在 RT-Thread DFS 中,文件系统有统一的根目录,使用 `/` 来表示。而在根目录下的 f1.bin 文件则使用 `/f1.bin` 来表示,2018 目录下的 `f1.bin` 目录则使用 `/data/2018/f1.bin` 来表示。即目录的分割符号是 `/`,这与 UNIX/Linux 完全相同,与 Windows 则不相同(Windows 操作系统上使用 `\` 来作为目录的分割符)。 + +### DFS 架构 + +RT-Thread DFS 组件的主要功能特点有: + +* 为应用程序提供统一的 POSIX 文件和目录操作接口:read、write、poll/select 等。 + +* 支持多种类型的文件系统,如 FatFS、RomFS、DevFS 等,并提供普通文件、设备文件、网络文件描述符的管理。 + +* 支持多种类型的存储设备,如 SD Card、SPI Flash、Nand Flash 等。 + +DFS 的层次架构如下图所示,主要分为 POSIX 接口层、虚拟文件系统层和设备抽象层。 + +![DFS 层次架构图](figures/fs-layer.png) + +### POSIX 接口层 + +POSIX 表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写 POSIX),POSIX 标准定义了操作系统应该为应用程序提供的接口标准,是 IEEE 为要在各种 UNIX 操作系统上运行的软件而定义的一系列 API 标准的总称。 + +POSIX 标准意在期望获得源代码级别的软件可移植性。换句话说,为一个 POSIX 兼容的操作系统编写的程序,应该可以在任何其它 POSIX 操作系统(即使是来自另一个厂商)上编译执行。RT-Thread 支持 POSIX 标准接口,因此可以很方便的将 Linux/Unix 的程序移植到 RT-Thread 操作系统上。 + +在类 Unix 系统中,普通文件、设备文件、网络文件描述符是同一种文件描述符。而在 RT-Thread 操作系统中,使用 DFS 来实现这种统一性。有了这种文件描述符的统一性,我们就可以使用 poll/select 接口来对这几种描述符进行统一轮询,为实现程序功能带来方便。 + +使用 poll/select 接口可以阻塞地同时探测一组支持非阻塞的 I/O 设备是否有事件发生(如可读,可写,有高优先级的错误输出,出现错误等等),直至某一个设备触发了事件或者超过了指定的等待时间。这种机制可以帮助调用者寻找当前就绪的设备,降低编程的复杂度。 + +### 虚拟文件系统层 + +用户可以将具体的文件系统注册到 DFS 中,如 FatFS、RomFS、DevFS 等,下面介绍几种常用的文件系统类型: + +* FatFS 是专为小型嵌入式设备开发的一个兼容微软 FAT 格式的文件系统,采用 ANSI C 编写,具有良好的硬件无关性以及可移植性,是 RT-Thread 中最常用的文件系统类型。 + +* 传统型的 RomFS 文件系统是一种简单的、紧凑的、只读的文件系统,不支持动态擦写保存,按顺序存放数据,因而支持应用程序以 XIP(execute In Place,片内运行) 方式运行,在系统运行时, 节省 RAM 空间。 + +* Jffs2 文件系统是一种日志闪存文件系统。主要用于 NOR 型闪存,基于 MTD 驱动层,特点是:可读写的、支持数据压缩的、基于哈希表的日志型文件系统,并提供了崩溃 / 掉电安全保护,提供写平衡支持等。 + +* DevFS 即设备文件系统,在 RT-Thread 操作系统中开启该功能后,可以将系统中的设备在 /dev 文件夹下虚拟成文件,使得设备可以按照文件的操作方式使用 read、write 等接口进行操作。 + +* NFS 网络文件系统(Network File System)是一项在不同机器、不同操作系统之间通过网络共享文件的技术。在操作系统的开发调试阶段,可以利用该技术在主机上建立基于 NFS 的根文件系统,挂载到嵌入式设备上,可以很方便地修改根文件系统的内容。 + +* UFFS 是 Ultra-low-cost Flash File System(超低功耗的闪存文件系统)的简称。它是国人开发的、专为嵌入式设备等小内存环境中使用 Nand Flash 的开源文件系统。与嵌入式中常使用的 Yaffs 文件系统相比具有资源占用少、启动速度快、免费等优势。 + +### 设备抽象层 + +设备抽象层将物理设备如 SD Card、SPI Flash、Nand Flash,抽象成符合文件系统能够访问的设备,例如 FAT 文件系统要求存储设备必须是块设备类型。 + +不同文件系统类型是独立于存储设备驱动而实现的,因此把底层存储设备的驱动接口和文件系统对接起来之后,才可以正确地使用文件系统功能。 + +## 挂载管理 + +文件系统的初始化过程一般分为以下几个步骤: + +1. 初始化 DFS 组件。 +2. 初始化具体类型的文件系统。 +3. 在存储器上创建块设备。 +4. 格式化块设备。 +5. 挂载块设备到 DFS 目录中。 +6. 当文件系统不再使用,可以将它卸载。 + +### 初始化 DFS 组件 + +DFS 组件的的初始化是由 dfs_init() 函数完成。dfs_init() 函数会初始化 DFS 所需的相关资源,创建一些关键的数据结构, 有了这些数据结构,DFS 便能在系统中找到特定的文件系统,并获得对特定存储设备内文件的操作方法。如果开启了自动初始化(默认开启),该函数将被自动调用。 + +### 注册文件系统 + +在 DFS 组件初始化之后,还需要初始化使用的具体类型的文件系统,也就是将具体类型的文件系统注册到 DFS 中。注册文件系统的接口如下所示: + +```c +int dfs_register(const struct dfs_filesystem_ops *ops); +``` + +|**参数**|**描述** | +|----------|------------------------------------| +| ops | 文件系统的操作函数的集合 | +|**返回**|**——** | +| 0 | 文件注册成功 | +| -1 | 文件注册失败 | + +该函数不需要用户调用,他会被不同文件系统的初始化函数调用,如 elm-FAT 文件系统的初始化函数`elm_init()`。开启对应的文件系统后,如果开启了自动初始化(默认开启),文件系统初始化函数也将被自动调用。 + +`elm_init()` 函数会初始化 elm-FAT 文件系统,此函数会调用 `dfs_register(`) 函数将 elm-FAT 文件系统注册到 DFS 中,文件系统注册过程如下图所示: + +![注册文件系统](figures/fs-reg.png) + +### 将存储设备注册为块设备 + +因为只有块设备才可以挂载到文件系统上,因此需要在存储设备上创建所需的块设备。如果存储设备是 SPI Flash,则可以使用 “串行 Flash 通用驱动库 SFUD” 组件,它提供了各种 SPI Flash 的驱动,并将 SPI Flash 抽象成块设备用于挂载,注册块设备过程如下图所示: + +![注册块设备时序图](figures/fs-reg-block.png) + +### 格式化文件系统 + +注册了块设备之后,还需要在块设备上创建指定类型的文件系统,也就是格式化文件系统。可以使用 `dfs_mkfs()` 函数对指定的存储设备进行格式化,创建文件系统,格式化文件系统的接口如下所示: + +```c +int dfs_mkfs(const char * fs_name, const char * device_name); +``` + +|**参数** |**描述** | +|-------------|----------------------------| +| fs_name | 文件系统类型 | +| device_name | 块设备名称 | +|**返回** |**——** | +| 0 | 文件系统格式化成功 | +| -1 | 文件系统格式化失败 | + +文件系统类型(fs_name)可取值及对应的文件系统如下表所示: + +|**取值** |**文件系统类型** | +|-------------|----------------------------| +| elm | elm-FAT 文件系统 | +| jffs2 | jffs2 日志闪存文件系统 | +| nfs | NFS 网络文件系统 | +| ram | RamFS 文件系统 | +| rom | RomFS 只读文件系统 | +| uffs | uffs 文件系统 | + +以 elm-FAT 文件系统格式化块设备为例,格式化过程如下图所示: + +![格式化文件系统](figures/elm-fat-mkfs.png) + +还可以使用 `mkfs` 命令格式化文件系统,格式化块设备 sd0 的运行结果如下所示: + +```c +msh />mkfs sd0 # sd0 为块设备名称,该命令会默认格式化 sd0 为 elm-FAT 文件系统 +msh /> +msh />mkfs -t elm sd0 # 使用 -t 参数指定文件系统类型为 elm-FAT 文件系统 +``` + +### 挂载文件系统 + +在 RT-Thread 中,挂载是指将一个存储设备挂接到一个已存在的路径上。我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的路径上,然后通过这个路径来访问存储设备。挂载文件系统的接口如下所示: + +```c +int dfs_mount(const char *device_name, + const char *path, + const char *filesystemtype, + unsigned long rwflag, + const void *data); +``` + +|**参数** |**描述** | +|----------------|------------------------------| +| device_name | 已经格式化的块设备名称 | +| path | 挂载路径,即挂载点 | +| filesystemtype | 挂载的文件系统类型,可取值见 dfs_mkfs() 函数描述 | +| rwflag | 读写标志位 | +| data | 特定文件系统的私有数据 | +|**返回** | **——** | +| 0 | 文件系统挂载成功 | +| -1 | 文件系统挂载失败 | + +如果只有一个存储设备,则可以直接挂载到根目录 `/` 上。 + +### 卸载文件系统 + +当某个文件系统不需要再使用了,那么可以将它卸载掉。卸载文件系统的接口如下所示: + +```c +int dfs_unmount(const char *specialfile); +``` + +|**参数** |**描述** | +|-------------|--------------------------| +| specialfile | 挂载路径 | +|**返回** |**——** | +| 0 | 卸载文件系统成功 | +| -1 | 卸载文件系统失败 | + +## 文件管理 + +本节介绍对文件进行操作的相关函数,对文件的操作一般都要基于文件描述符 fd,如下图所示: + +![文件管理常用函数](figures/fs-mg.png) + +### 打开和关闭文件 + +打开或创建一个文件可以调用下面的 open() 函数: + +```c +int open(const char *file, int flags, ...); +``` + +|**参数** |**描述** | +|------------|--------------------------------------| +| file | 打开或创建的文件名 | +| flags | 指定打开文件的方式,取值可参考下表 | +|**返回** |**——** | +| 文件描述符 | 文件打开成功 | +| -1 | 文件打开失败 | + +一个文件可以以多种方式打开,并且可以同时指定多种打开方式。例如,一个文件以 O_RDONLY 和 O_CREAT 的方式打开,那么当指定打开的文件不存在时,就会先创建这个文件,然后再以只读的方式打开。文件打开方式如下表所示: + +|**参数**|**描述** | +|----------|-----------------------| +| O_RDONLY | 只读方式打开文件 | +| O_WRONLY | 只写方式打开文件 | +| O_RDWR | 以读写方式打开文件 | +| O_CREAT | 如果要打开的文件不存在,则建立该文件 | +| O_APPEND | 当读写文件时会从文件尾开始移动,也就是所写入的数据会以附加的方式添加到文件的尾部 | +| O_TRUNC | 如果文件已经存在,则清空文件中的内容 | + +当使用完文件后若不再需要使用则可使用 `close()` 函数关闭该文件,而 `close()` 会让数据写回磁盘,并释放该文件所占用的资源。 + +``` +int close(int fd); +``` + +|**参数**|**描述** | +|----------|--------------| +| fd | 文件描述符 | +|**返回**|**——** | +| 0 | 文件关闭成功 | +| -1 | 文件关闭失败 | + +### 读写数据 + +读取文件内容可使用 `read()` 函数: + +``` +int read(int fd, void *buf, size_t len); +``` + +|**参数**|**描述** | +|----------|------------------------------------------| +| fd | 文件描述符 | +| buf | 缓冲区指针 | +| len | 读取文件的字节数 | +|**返回**|**——** | +| int | 实际读取到的字节数 | +| 0 | 读取数据已到达文件结尾或者无可读取的数据 | +| -1 | 读取出错,错误代码查看当前线程的 errno | + +该函数会把参数 fd 所指的文件的 len 个字节读取到 buf 指针所指的内存中。此外文件的读写位置指针会随读取到的字节移动。 + +向文件中写入数据可使用 `write()` 函数: + +``` +int write(int fd, const void *buf, size_t len); +``` + +|**参数**|**描述** | +|----------|---------------------------------------| +| Fd | 文件描述符 | +| buf | 缓冲区指针 | +| len | 写入文件的字节数 | +|**返回**|**——** | +| int | 实际写入的字节数 | +| -1 | 写入出错,错误代码查看当前线程的 errno | + +该函数会把 buf 指针所指向的内存中 len 个字节写入到参数 fd 所指的文件内。此外文件的读写位置指针会随着写入的字节移动。 + +### 重命名 + +重命名文件可使用 `rename()` 函数: + +``` +int rename(const char *old, const char *new); +``` + +|**参数**|**描述** | +|----------|--------------| +| old | 旧文件名 | +| new | 新文件名 | +|**返回**|**——** | +| 0 | 更改名称成功 | +| -1 | 更改名称失败 | + +该函数会将参数 old 所指定的文件名称改为参数 new 所指的文件名称。若 new 所指定的文件已经存在,则该文件将会被覆盖。 + +### 取得状态 + +获取文件状态可使用下面的 `stat()` 函数: + +``` +int stat(const char *file, struct stat *buf); +``` + +|**参数**|**描述** | +|----------|--------------------------------------------| +| file | 文件名 | +| buf | 结构指针,指向一个存放文件状态信息的结构体 | +|**返回**|**——** | +| 0 | 获取状态成功 | +| -1 | 获取状态失败 | + +### 删除文件 + +删除指定目录下的文件可使用 `unlink()` 函数: + +``` +int unlink(const char *pathname); +``` + +|**参数**|**描述** | +|----------|------------------------| +| pathname | 指定删除文件的绝对路径 | +|**返回**|**——** | +| 0 | 删除文件成功 | +| -1 | 删除文件失败 | + +### 同步文件数据到存储设备 + +同步内存中所有已修改的文件数据到储存设备可使用 `fsync()` 函数: + +```c +int fsync(int fildes); +``` + +|**参数**|**描述** | +|----------|--------------| +| fildes | 文件描述符 | +|**返回**|**——** | +| 0 | 同步文件成功 | +| -1 | 同步文件失败 | + +### 查询文件系统相关信息 + +查询文件系统相关信息可使用 `statfs()` 函数: + +```c +int statfs(const char *path, struct statfs *buf); +``` + +|**参数**|**描述** | +|----------|----------------------------------| +| path | 文件系统的挂载路径 | +| buf | 用于储存文件系统信息的结构体指针 | +|**返回**|**——** | +| 0 | 查询文件系统信息成功 | +| -1 | 查询文件系统信息失败 | + +### 监视 I/O 设备状态 + +监视 I/O 设备是否有事件发生可使用 `select()` 函数: + +```c +int select( int nfds, + fd_set *readfds, + fd_set *writefds, + fd_set *exceptfds, + struct timeval *timeout); +``` + +|**参数** |**描述** | +|-----------|---------------------------------------------------------| +| nfds | 集合中所有文件描述符的范围,即所有文件描述符的最大值加 1 | +| readfds | 需要监视读变化的文件描述符集合 | +| writefds | 需要监视写变化的文件描述符集合 | +| exceptfds | 需要监视出现异常的文件描述符集合 | +| timeout | select 的超时时间 | +|**返回** |**——** | +| 正值 | 监视的文件集合出现可读写事件或出错 | +| 0 | 等待超时,没有可读写或错误的文件 | +| 负值 | 出错 | + +使用 `select()` 接口可以阻塞地同时探测一组支持非阻塞的 I/O 设备是否有事件发生(如可读,可写,有高优先级的错误输出,出现错误等等),直至某一个设备触发了事件或者超过了指定的等待时间。 + +## 目录管理 + +本节介绍目录管理经常使用的函数,对目录的操作一般都基于目录地址,如下图所示: + +![目录管理常用函数](figures/fs-dir-mg.png) + +### 创建和删除目录 + +创建目录可使用 `mkdir()` 函数: + +```c +int mkdir(const char *path, mode_t mode); +``` + +|**参数**|**描述** | +|----------|----------------| +| path | 目录的绝对地址 | +| mode | 创建模式 | +|**返回**|**——** | +| 0 | 创建目录成功 | +| -1 | 创建目录失败 | + +该函数用来创建一个目录即文件夹,参数 path 为目录的绝对路径,参数 mode 在当前版本未启用,所以填入默认参数 0x777 即可。 + +删除目录可使用 `rmdir()` 函数: + +```c +int rmdir(const char *pathname); +``` + +|**参数**|**描述** | +|----------|------------------------| +| pathname | 需要删除目录的绝对路径 | +|**返回**|**——** | +| 0 | 目录删除成功 | +| -1 | 目录删除错误 | + +### 打开和关闭目录 + +打开目录可使用 `opendir()` 函数: + +```c +DIR* opendir(const char* name); +``` + +|**参数**|**描述** | +|----------|-----------------------------------------| +| name | 目录的绝对地址 | +|**返回**|**——** | +| DIR | 打开目录成功,返回指向目录流的指针 | +| NULL | 打开失败 | + +关闭目录可使用 `closedir()` 函数: + +```c +int closedir(DIR* d); +``` + +|**参数**|**描述** | +|----------|--------------| +| d | 目录流指针 | +|**返回**|**——** | +| 0 | 目录关闭成功 | +| -1 | 目录关闭错误 | + +该函数用来关闭一个目录,必须和 `opendir()` 函数配合使用。 + +### 读取目录 + +读取目录可使用 `readdir()` 函数: + +```c +struct dirent* readdir(DIR *d); +``` + +|**参数**|**描述** | +|----------|---------------------------------------| +| d | 目录流指针 | +|**返回**|**——** | +| dirent | 读取成功返回指向目录条目的结构体指针 | +| NULL | 已读到目录尾 | + +该函数用来读取目录,参数 d 为目录流指针。此外,每读取一次目录,目录流的指针位置将自动往后递推 1 个位置。 + +### 取得目录流的读取位置 + +获取目录流的读取位置可使用 `telldir()` 函数: + +``` +long telldir(DIR *d); +``` + +|**参数**|**描述** | +|----------|------------------| +| d | 目录流指针 | +|**返回**|**——** | +| long | 读取位置的偏移量 | + +该函数的返回值记录着一个目录流的当前位置,此返回值代表距离目录文件开头的 [偏移量](https://baike.baidu.com/item/%E5%81%8F%E7%A7%BB%E9%87%8F)。你可以在随后的 `seekdir()`[函数调用](https://baike.baidu.com/item/%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8) 中利用这个值来重置目录到当前位置。也就是说 `telldir()` 函数可以和 `seekdir()` 函数配合使用,重新设置目录流的读取位置到指定的偏移量。 + +### 设置下次读取目录的位置 + +设置下次读取目录的位置可使用 `seekdir()` 函数: + +``` +void seekdir(DIR *d, off_t offset); +``` + +|**参数**|**描述** | +|----------|----------------------------| +| d | 目录流指针 | +| offset | 偏移值,距离本次目录的位移 | + +该用来设置参数 d 目录流的读取位置,在调用 readdir() 时便从此新位置开始读取。 + +### 重设读取目录的位置为开头位置 + +重设目录流的读取位置为开头可使用 `rewinddir()` 函数: + +``` +void rewinddir(DIR *d); +``` + +|**参数**|**描述** | +|----------|------------| +| d | 目录流指针 | + +该函数可以用来设置 d 目录流目前的读取位置为目录流的初始位置。 + +## DFS 配置选项 + +文件系统在 menuconfig 中具体配置路径如下: + +```c +RT-Thread Components ---> + Device virtual file system ---> +``` + +配置菜单描述及对应的宏定义如下表所示: + +|**配置选项** |**对应宏定义**|**描述** | +|-------------------------------|-------------------------------|----------------------| +|[*] Using device virtual file system |RT_USING_DFS |开启DFS虚拟文件系统 | +|[*] Using working directory |DFS_USING_WORKDIR |开启相对路径 | +|(2) The maximal number of mounted file system |DFS_FILESYSTEMS_MAX |最大挂载文件系统的数量 | +|(2) The maximal number of file system type |DFS_FILESYSTEM_TYPES_MAX |最大支持文件系统的数量 | +|(4) The maximal number of opened files | DFS_FD_MAX|打开文件的最大数量 | +|[ ] Using mount table for file system|RT_USING_DFS_MNTTABLE |开启自动挂载表 | +|[*] Enable elm-chan fatfs |RT_USING_DFS_ELMFAT |开启 elm-FatFs 文件系统 | +|[*] Using devfs for device objects |RT_USING_DFS_DEVFS | 开启 DevFS 设备文件系统| +|[ ] Enable ReadOnly file system on flash |RT_USING_DFS_ROMFS |开启 RomFS 文件系统 | +|[ ] Enable RAM file system |RT_USING_DFS_RAMFS |开启 RamFS 文件系统 | +|[ ] Enable UFFS file system: Ultra-low-cost Flash File System |RT_USING_DFS_UFFS |开启 UFFS 文件系统 | +|[ ] Enable JFFS2 file system |RT_USING_DFS_JFFS2 |开启 JFFS2 文件系统 | +|[ ] Using NFS v3 client file system |RT_USING_DFS_NFS |开启 NFS 文件系统 | + +默认情况下,RT-Thread 操作系统为了获得较小的内存占用,并不会开启相对路径功能。当支持相对路径选项没有打开时,在使用文件、目录接口进行操作时应该使用绝对目录进行(因为此时系统中不存在当前工作的目录)。如果需要使用当前工作目录以及相对目录,可在文件系统的配置项中开启相对路径功能。 + +选项 `[*] Using mount table for file system` 被选中之后,会使能相应的宏 `RT_USING_DFS_MNTTABLE`,开启自动挂载表功能。自动挂载表 `mount_table[]` 由用户在应用代码中提供,用户需在表中指定设备名称、挂载路径、文件系统类型、读写标志及私有数据等,之后系统便会遍历该挂载表执行挂载,需要注意的是挂载表必须以 {0} 结尾,用于判断表格结束。 + +自动挂载表 `mount_table[]` 的示例如下所示,其中 `mount_table[0]` 的 5 个成员即函数 `dfs_mount()` 的 5 个参数,意思是将 elm 文件系统挂载到 flash0 设备的 / 路径下,rwflag 为 0 ,data 为 0 ; `mount_table[1]` 为 {0} 作为结尾,用于判断表格结束。 + +```c +const struct dfs_mount_tbl mount_table[] = +{ + {"flash0", "/", "elm", 0, 0}, + {0} +}; +``` + +### elm-FatFs 文件系统配置选项 + +在 menuconfig 中开启 elm-FatFs 文件系统后可对 elm-FatFs 做进一步配置,配置菜单描述及对应的宏定义如下表所示: + +|**配置选项** |**对应宏定义**|**描述** | +|---------------------------------|-----------------------------------|-------------------| +|(437) OEM code page |RT_DFS_ELM_CODE_PAGE |编码方式 | +|[*] Using RT_DFS_ELM_WORD_ACCESS |RT_DFS_ELM_WORD_ACCESS | | +|Support long file name (0: LFN disable) ---> |RT_DFS_ELM_USE_LFN |开启长文件名子菜单 | +|(255) Maximal size of file name length |RT_DFS_ELM_MAX_LFN |文件名最大长度 | +|(2) Number of volumes (logical drives) to be used. |RT_DFS_ELM_DRIVES |挂载 FatFs 的设备数量 | +(4096) Maximum sector size to be handled. |RT_DFS_ELM_MAX_SECTOR_SIZE |文件系统扇区大小 | +|[ ] Enable sector erase feature |RT_DFS_ELM_USE_ERASE | | +|[*] Enable the reentrancy (thread safe) of the FatFs module |RT_DFS_ELM_REENTRANT |开启可重入性 | + +#### 长文件名 + +默认情况下,FatFs 的文件命名有如下缺点: + +* 文件名(不含后缀)最长不超过8个字符,后缀最长不超过3个字符,文件名和后缀超过限制后将会被截断。 + +* 文件名不支持大小写(显示为大写) + +如果需要支持长文件名,则需要打开支持长文件名选项。长文件名子菜单描述如下所示: + +|**配置选项** |**对应宏定义**|**描述** | +|----------------------------------|-------------------------|---------------------| +|( ) 0: LFN disable |RT_DFS_ELM_USE_LFN_0 |关闭长文件名 | +|( ) 1: LFN with static LFN working buffer|RT_DFS_ELM_USE_LFN_1 |采用静态缓冲区支持长文件名,多线程操作文件名时将会带来重入问题 | +|( ) 2: LFN with dynamic LFN working buffer on the stack |RT_DFS_ELM_USE_LFN_2 |采用栈内临时缓冲区支持长文件名。对栈空间需求较大 | +|(X) 3: LFN with dynamic LFN working buffer on the heap |RT_DFS_ELM_USE_LFN_3 |使用heap(malloc申请)缓冲区存放长文件名,最安全(默认方式) | + +#### 编码方式 + +当打开长文件名支持时,可以设置文件名的编码方式,RT-Thread/FatFs 默认使用 437 编码(美国英语)。如果需要存储中文文件名,可以使用 936 编码(GBK编码)。936 编码需要一个大约 180KB 的字库。如果仅使用英文字符作为文件,建议使用 437 编码(美国 +英语),这样就可以节省这 180KB 的 Flash 空间。 + +FatFs所支持的文件编码如下所示: + +```c +/* This option specifies the OEM code page to be used on the target system. +/ Incorrect setting of the code page can cause a file open failure. +/ +/ 1 - ASCII (No extended character. Non-LFN cfg. only) +/ 437 - U.S. +/ 720 - Arabic +/ 737 - Greek +/ 771 - KBL +/ 775 - Baltic +/ 850 - Latin 1 +/ 852 - Latin 2 +/ 855 - Cyrillic +/ 857 - Turkish +/ 860 - Portuguese +/ 861 - Icelandic +/ 862 - Hebrew +/ 863 - Canadian French +/ 864 - Arabic +/ 865 - Nordic +/ 866 - Russian +/ 869 - Greek 2 +/ 932 - Japanese (DBCS) +/ 936 - Simplified Chinese (DBCS) +/ 949 - Korean (DBCS) +/ 950 - Traditional Chinese (DBCS) +*/ +``` + +#### 文件系统扇区大小 + +指定 FatFs 的内部扇区大小,需要大于等于实际硬件驱动的扇区大小。例如,某 spi flash 芯片扇区为 4096 字节,则上述宏需要修改为 4096,否则 FatFs 从驱动读入数据时就会发生数组越界而导致系统崩溃(新版本在系统执行时给出警告信息)。 + +一般 Flash 设备可以设置为 4096,常见的 TF 卡和 SD 卡的扇区大小设置为 512。 + +#### 可重入性 + +FatFs 充分考虑了多线程安全读写安全的情况,当在多线程中读写 FafFs 时,为了避免重入带来的问题,需要打开上述宏。如果系统仅有一个线程操作文件系统,不会出现重入问题,则可以关闭此功能节省资源。 + +#### 更多配置 + +FatFs 本身支持非常多的配置选项,配置非常灵活。下面文件为 FatFs 的配置文件,可以修改这个文件来定制 FatFs。 + +```c +components/dfs/filesystems/elmfat/ffconf.h +``` + +## DFS 应用示例 + +### FinSH 命令 + +文件系统挂载成功后就可以进行文件和目录的操作了,文件系统操作常用的 FinSH 命令如下表所示: + +|**FinSH 命令** |**描述** | +|--------|----------------------------------| +| ls | 显示文件和目录的信息 | +| cd | 进入指定目录 | +| cp | 复制文件 | +| rm | 删除文件或目录 | +| mv | 将文件移动位置或改名 | +| echo | 将指定内容写入指定文件,当文件存在时,就写入该文件,当文件不存在时就新创建一个文件并写入 | +| cat | 展示文件的内容 | +| pwd | 打印出当前目录地址 | +| mkdir | 创建文件夹 | +| mkfs | 格式化文件系统 | + +使用 `ls` 命令查看当前目录信息,运行结果如下所示: + +```c +msh />ls # 使用 ls 命令查看文件系统目录信息 +Directory /: # 可以看到已经存在根目录 / +``` + +使用 `mkdir` 命令来创建文件夹,运行结果如下所示: + +```c +msh />mkdir rt-thread # 创建 rt-thread 文件夹 +msh />ls # 查看目录信息如下 +Directory /: +rt-thread +``` + +使用 `echo` 命令将输入的字符串输出到指定输出位置,运行结果如下所示: + +```c +msh />echo "hello rt-thread!!!" # 将字符串输出到标准输出 +hello rt-thread!!! +msh />echo "hello rt-thread!!!" hello.txt # 将字符串出输出到 hello.txt 文件 +msh />ls +Directory /: +rt-thread +hello.txt 18 +msh /> +``` + +使用 `cat` 命令查看文件内容,运行结果如下所示: + +```c +msh />cat hello.txt # 查看 hello.txt 文件的内容并输出 +hello rt-thread!!! +``` + +使用 `rm` 命令删除文件夹或文件,运行结果如下所示: + +```c +msh />ls # 查看当前目录信息 +Directory /: +rt-thread +hello.txt 18 +msh />rm rt-thread # 删除 rt-thread 文件夹 +msh />ls +Directory /: +hello.txt 18 +msh />rm hello.txt # 删除 hello.txt 文件 +msh />ls +Directory /: +msh /> +``` + +### 读写文件示例 + +文件系统正常工作后,就可以运行应用示例,在该示例代码中,首先会使用 `open()` 函数创建一个文件 text.txt,并使用 write() 函数在文件中写入字符串 `“RT-Thread Programmer!\n”`,然后关闭文件。再次使用 `open()` 函数打开 text.txt 文件,读出其中的内容并打印出来,最后关闭该文件。 + +示例代码如下所示: + +```c +#include +#include /* 当需要使用文件操作时,需要包含这个头文件 */ + +static void readwrite_sample(void) +{ + int fd, size; + char s[] = "RT-Thread Programmer!", buffer[80]; + + rt_kprintf("Write string %s to test.txt.\n", s); + + /* 以创建和读写模式打开 /text.txt 文件,如果该文件不存在则创建该文件 */ + fd = open("/text.txt", O_WRONLY | O_CREAT); + if (fd>= 0) + { + write(fd, s, sizeof(s)); + close(fd); + rt_kprintf("Write done.\n"); + } + + /* 以只读模式打开 /text.txt 文件 */ + fd = open("/text.txt", O_RDONLY); + if (fd>= 0) + { + size = read(fd, buffer, sizeof(buffer)); + close(fd); + rt_kprintf("Read from file test.txt : %s \n", buffer); + if (size < 0) + return ; + } + } +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(readwrite_sample, readwrite sample); + +``` + +### 更改文件名称示例 + +本小节的示例代码展示如何修改文件名称,程序会创建一个操作文件的函数 `rename_sample()` 并导出到 msh 命令列表。该函数会调用 `rename()` 函数, 将名为 text.txt 的文件改名为 text1.txt。示例代码如下所示: + +```c +#include +#include /* 当需要使用文件操作时,需要包含这个头文件 */ + +static void rename_sample(void) +{ + rt_kprintf("%s => %s", "/text.txt", "/text1.txt"); + + if (rename("/text.txt", "/text1.txt") < 0) + rt_kprintf("[error!]\n"); + else + rt_kprintf("[ok!]\n"); +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(rename_sample, rename sample); +``` + +在 FinSH 控制台运行该示例,运行结果如下: + +```c +msh />echo "hello" text.txt +msh />ls +Directory /: +text.txt 5 +msh />rename_sample +/text.txt => /text1.txt [ok!] +msh />ls +Directory /: +text1.txt 5 +``` + +在示例展示过程中,我们先使用 echo 命令创建一个名为 text.txt 文件,然后运行示例代码将文件 text.txt 的文件名修改为 text1.txt。 + +### 获取文件状态示例 + +本小节的示例代码展示如何获取文件状态,程序会创建一个操作文件的函数 `stat_sample()` 并导出到 msh 命令列表。该函数会调用 `stat()` 函数获取 text.txt 文件的文件大小信息。示例代码如下所示: + +```c +#include +#include /* 当需要使用文件操作时,需要包含这个头文件 */ + +static void stat_sample(void) +{ + int ret; + struct stat buf; + ret = stat("/text.txt", &buf); + if(ret == 0) + rt_kprintf("text.txt file size = %d\n", buf.st_size); + else + rt_kprintf("text.txt file not fonud\n"); +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(stat_sample, show text.txt stat sample); +``` + +在 FinSH 控制台运行该示例,运行结果如下: + +```c +msh />echo "hello" text.txt +msh />stat_sample +text.txt file size = 5 +``` + +在示例运行过程中,首先会使用 `echo` 命令创建文件 text.txt,然后运行示例代码,将文件 text.txt 的文件大小信息打印出来。 + +### 创建目录示例 + +本小节的示例代码展示如何创建目录,程序会创建一个操作文件的函数 `mkdir_sample()` 并导出到 msh 命令列表,该函数会调用 `mkdir()` 函数创建一个名为 dir_test 的文件夹。示例代码如下所示: + +```c +#include +#include /* 当需要使用文件操作时,需要包含这个头文件 */ + +static void mkdir_sample(void) +{ + int ret; + + /* 创建目录 */ + ret = mkdir("/dir_test", 0x777); + if (ret < 0) + { + /* 创建目录失败 */ + rt_kprintf("dir error!\n"); + } + else + { + /* 创建目录成功 */ + rt_kprintf("mkdir ok!\n"); + } +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mkdir_sample, mkdir sample); +``` + +在 FinSH 控制台运行该示例,运行结果如下: + +```c +msh />mkdir_sample +mkdir ok! +msh />ls +Directory /: +dir_test # 表示该目录的类型是文件夹 +``` + +本例程演示了在根目录下创建名为 dir_test 的文件夹。 + +### 读取目录示例 + +本小节的示例代码展示如何读取目录,程序会创建一个操作文件的函数 `readdir_sample()` 并导出到 msh 命令列表,该函数会调用 `readdir()` 函数获取 dir_test 文件夹的内容信息并打印出来。示例代码如下所示: + +```c +#include +#include /* 当需要使用文件操作时,需要包含这个头文件 */ + +static void readdir_sample(void) +{ + DIR *dirp; + struct dirent *d; + + /* 打开 / dir_test 目录 */ + dirp = opendir("/dir_test"); + if (dirp == RT_NULL) + { + rt_kprintf("open directory error!\n"); + } + else + { + /* 读取目录 */ + while ((d = readdir(dirp)) != RT_NULL) + { + rt_kprintf("found %s\n", d->d_name); + } + + /* 关闭目录 */ + closedir(dirp); + } +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(readdir_sample, readdir sample); +``` + +在 FinSH 控制台运行该示例,运行结果如下: + +```c +msh />ls +Directory /: +dir_test +msh />cd dir_test +msh /dir_test>echo "hello" hello.txt # 创建一个 hello.txt 文件 +msh /dir_test>cd .. # 切换到上级文件夹 +msh />readdir_sample +found hello.txt +``` + +本示例中,首先进入到 dir_test 文件夹下创建 hello.txt 文件,然后退出 dir_test 文件夹。此时运行示例程序将 dir_test 文件夹中的内容打印出来。 + +### 设置读取目录位置示例 + +本小节的示例代码展示如何设置下次读取目录的位置,程序会创建一个操作文件的函数 `telldir_sample()` 并导出到 msh 命令列表。该函数会首先打开根目录,然后读取根目录下所有目录信息,并将这些目录信息打印出来。同时使用 `telldir()` 函数记录第三个目录项的位置信息。在第二次读取根目录下的目录信息前,使用 `seekdir()` 函数设置读取位置为之前记录的第三个目录项的地址,此时再次读取根目录下的信息,并将目录信息打印出来。示例代码如下所示: + +```c +#include +#include /* 当需要使用文件操作时,需要包含这个头文件 */ + +/* 假设文件操作是在一个线程中完成 */ +static void telldir_sample(void) +{ + DIR *dirp; + int save3 = 0; + int cur; + int i = 0; + struct dirent *dp; + + /* 打开根目录 */ + rt_kprintf("the directory is:\n"); + dirp = opendir("/"); + + for (dp = readdir(dirp); dp != RT_NULL; dp = readdir(dirp)) + { + /* 保存第三个目录项的目录指针 */ + i++; + if (i == 3) + save3 = telldir(dirp); + + rt_kprintf("%s\n", dp->d_name); + } + + /* 回到刚才保存的第三个目录项的目录指针 */ + seekdir(dirp, save3); + + /* 检查当前目录指针是否等于保存过的第三个目录项的指针. */ + cur = telldir(dirp); + if (cur != save3) + { + rt_kprintf("seekdir (d, %ld); telldir (d) == %ld\n", save3, cur); + } + + /* 从第三个目录项开始打印 */ + rt_kprintf("the result of tell_seek_dir is:\n"); + for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) + { + rt_kprintf("%s\n", dp->d_name); + } + + /* 关闭目录 */ + closedir(dirp); +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(telldir_sample, telldir sample); +``` + +本次演示示例中,需要手动在根目录下用 `mkdir` 命令依次创建从 hello_1 到 hello_5 这五个文件夹,确保根目录下有运行示例所需的文件夹目录。 + +在 FinSH 控制台运行该示例,运行结果如下: + +```c +msh />ls +Directory /: +hello_1 +hello_2 +hello_3 +hello_4 +hello_5 +msh />telldir_sample +the directory is: +hello_1 +hello_2 +hello_3 +hello_4 +hello_5 +the result of tell_seek_dir is: +hello_3 +hello_4 +hello_5 +``` + +运行示例程序后,可以看到第一次读取根目录信息时是从第一个文件夹开始读取,打印出了根目录下所有的目录信息。第二次打印目录信息时,由于使用了 `seekdir()` 函数设置读取的起始位置为第三个文件夹的位置,因此第二次读取根目录时,是从第三个文件夹开始读取直到最后一个文件夹,只打印出了从 hello_3 到 hello_5 的目录信息。 + +## 常见问题 + +### Q: 发现文件名或者文件夹名称显示不正常怎么办? + + **A:** 检查是否开启了长文件名支持,DFS 功能配置小节。 + +### Q: 文件系统初始化失败怎么办? + + **A:** 检查文件系统配置项目中的允许挂载的文件系统类型和数量是否充足。 + +### Q: 创建文件系统 mkfs 命令失败怎么办? + + **A:** 检查存储设备是否存在,如果存在检查设备驱动是否可以通过功能测试,如果不能通过,则检查驱动错误。 检查 libc 功能是否开启。 + +### Q: 文件系统挂载失败怎么办? + + **A:** + + * 检查指定的挂载路径是否存在。文件系统可以直接挂载到根目录(“/”),但是如果想要挂载到其他路径上,如 (“/sdcard”)。需要确保(“/sdcard”)路径是存在的,否则需要先在根目录创建 `sdcard` 文件夹才能挂载成功。 + + * 检查是否在存储设备上创建了文件系统,如果存储设备上没有文件系统,需要使用 `mkfs` 命令在存储器上创建文件系统。 + +### Q: SFUD 探测不到 Flash 所使用的具体型号怎么办? + + **A:** + +* 检查硬件引脚设置是否错误。 + +* SPI 设备是否已经注册。 + +* SPI 设备是否已经挂载到总线。 + +* 检查在 `RT-Thread Components → Device Drivers -> Using SPI Bus/Device device drivers -> Using Serial Flash Universal Driver` 菜单下的 `Using auto probe flash JEDEC SFDP parameter` 和 `Using defined supported flash chip information table` 配置项是否选中,如果没有选中那么需要开启这两个选项。 + +* 如果开启了上面的选项仍然无法识别存储设备,那么可以在 [SFUD](https://github.com/armink/SFUD) 项目中提出 issues。 + +### Q: 存储设备的 benchmark 测试耗时过长是怎么回事? + + **A:** + +* 可对比 `system tick` 为 1000 时的 [benchmark 测试数据](https://github.com/armink/SFUD/blob/master/docs/zh/benchmark.txt) 和本次测试所需的时长,如果耗时差距过大,则可以认为测试工作运行不正常。 + +* 检查系统 tick 的设置,因为一些延时操作会根据 tick 时间来决定,所以需要根据系统情况来设置合适的 `system tick` 值。如果系统的 `system tick` 值不低于 1000,则需要使用逻辑分析仪检查波形确定通信速率正常。 + +### Q: SPI Flash 实现 elmfat 文件系统,如何保留部分扇区不被文件系统使用? + + **A:** 可以使用 RT-Thread 提供的 [partition](https://github.com/RT-Thread-packages/partition) 工具软件包为整个存储设备创建多个块设备,为创建的多个块设备分配不同的功能即可。 + +### Q: 测试文件系统过程中程序卡住了怎么办? + + **A:** 尝试使用调试器或者打印一些必要的调试信息,确定程序卡住的位置再提出问题。 + +### Q: 如何一步步检查文件系统出现的问题? + + **A:** + +* 可以采用从底层到上层的方法来逐步排查问题。 + +* 首先检查存储设备是否注册成功,功能是否正常。 + +* 检查存储设备中是否创建了文件系统。 + +* 检查指定文件系统类型是否注册到 DFS 框架,经常要检查允许的文件系统类型和数量是否足够。 + +* 检查 DFS 是否初始化成功,这一步的初始化操作是纯软件的,因此出错的可能性不高。需要注意的是如果开启了组件自动初始化,就无需再次手动初始化。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-hd.png b/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-hd.png new file mode 100644 index 0000000000000000000000000000000000000000..c48840ff58b147534adcf961debb10b4bfea404c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-hd.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-mdk.png b/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-mdk.png new file mode 100644 index 0000000000000000000000000000000000000000..1f7bbb8941cd85558063f97e64cf16b47a2dc282 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-mdk.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-run.png b/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-run.png new file mode 100644 index 0000000000000000000000000000000000000000..6939b0872b7c8fc7bc9717c8cadbcf005193b6fc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/finsh/figures/finsh-run.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/finsh/finsh.md b/rt-thread-version/rt-thread-standard/programming-manual/finsh/finsh.md new file mode 100644 index 0000000000000000000000000000000000000000..b70b6d2b5abb04703296172e6829eab3cd47c85d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/finsh/finsh.md @@ -0,0 +1,552 @@ +# FinSH 控制台 + +在计算机发展的早期,图形系统出现之前,没有鼠标,甚至没有键盘。那时候人们如何与计算机交互呢?最早期的计算机使用打孔的纸条向计算机输入命令,编写程序。后来随着计算机的不断发展,显示器、键盘成为计算机的标准配置,但此时的操作系统还不支持图形界面,计算机先驱们开发了一种软件,它接受用户输入的命令,解释之后,传递给操作系统,并将操作系统执行的结果返回给用户。这个程序像一层外壳包裹在操作系统的外面,所以它被称为 shell。 + +嵌入式设备通常需要将开发板与 PC 机连接起来通讯,常见连接方式包括:串口、USB、以太网、Wi-Fi 等。一个灵活的 shell 也应该支持在多种连接方式上工作。有了 shell,就像在开发者和计算机之间架起了一座沟通的桥梁,开发者能很方便的获取系统的运行情况,并通过命令控制系统的运行。特别是在调试阶段,有了 shell,开发者除了能更快的定位到问题之外,也能利用 shell 调用测试函数,改变测试函数的参数,减少代码的烧录次数,缩短项目的开发时间。 + +FinSH 是 RT-Thread 的命令行组件(shell),正是基于上面这些考虑而诞生的,FinSH 的发音为 [ˈfɪnʃ]。读完本章,我们会对 FinSH 的工作方式以及如何导出自己的命令到 FinSH 有更加深入的了解。 + +## FinSH 简介 + +FinSH 是 RT-Thread 的命令行组件,提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。它可以使用串口 / 以太网 / USB 等与 PC 机进行通信,硬件拓扑结构如下图所示: + +![FinSH 硬件连接图](figures/finsh-hd.png) + +用户在控制终端输入命令,控制终端通过串口、USB、网络等方式将命令传给设备里的 FinSH,FinSH 会读取设备输入命令,解析并自动扫描内部函数表,寻找对应函数名,执行函数后输出回应,回应通过原路返回,将结果显示在控制终端上。 + +当使用串口连接设备与控制终端时,FinSH 命令的执行流程,如下图所示: + +![FinSH 命令执行流程图](figures/finsh-run.png) + +FinSH 支持权限验证功能,系统在启动后会进行权限验证,只有权限验证通过,才会开启 FinSH 功能,提升系统输入的安全性。 + +FinSH 支持自动补全、查看历史命令等功能,通过键盘上的按键可以很方便的使用这些功能,FinSH 支持的按键如下表所示: + +|**按键**|**功能描述** | +|----------|--------------| +| Tab 键 | 当没有输入任何字符时按下 Tab 键将会打印当前系统支持的所有命令。若已经输入部分字符时按下 Tab 键,将会查找匹配的命令,也会按照文件系统的当前目录下的文件名进行补全,并可以继续输入,多次补全 | +| ↑↓键 | 上下翻阅最近输入的历史命令 | +| 退格键 | 删除符 | +| ←→键 | 向左或向右移动标 | + +FinSH 支持两种输入模式,分别是传统命令行模式和 C 语言解释器模式。 + +### 传统命令行模式 + +此模式又称为 msh(module shell),msh 模式下,FinSH 与传统 shell(dos/bash)执行方式一致,例如,可以通过 `cd /` 命令将目录切换至根目录。 + +msh 通过解析,将输入字符分解成以空格区分开的命令和参数。其命令执行格式如下所示: + +`command [arg1] [arg2] [...]` + +其中 command 既可以是 RT-Thread 内置的命令,也可以是可执行的文件。 + +### C 语言解释器模式 + +此模式又称为 C-Style 模式。C 语言解释器模式下,FinSH 能够解析执行大部分 C 语言的表达式,并使用类似 C 语言的函数调用方式访问系统中的函数及全局变量,此外它也能够通过命令行方式创建变量。在该模式下,输入的命令必须类似 C 语言中的函数调用方式,即必须携带 () 符号,例如,要输出系统当前所有线程及其状态,在 FinSH 中输入 `list_thread()` 即可打印出需要的信息。FinSH 命令的输出为此函数的返回值。对于一些不存在返回值的函数(void 返回值),这个打印输出没有意义。 + +最初 FinSH 仅支持 C-Style 模式,后来随着 RT-Thread 的不断发展,C-Style 模式在运行脚本或者程序时不太方便,而使用传统的 shell 方式则比较方便。另外,C-Style 模式下,FinSH 占用体积比较大。出于这些考虑,在 RT-Thread 中增加了 msh 模式,msh 模式体积小,使用方便,推荐大家使用 msh 模式。 + +如果在 RT-Thread 中同时使能了这两种模式,那它们可以动态切换,在 msh 模式下输入 exit 后回车,即可切换到 C-Style 模式。在 C-Style 模式输入 msh() 后回车,即可进入 msh 模式。两种模式的命令不通用,msh 命令无法在 C-Style 模式下使用,反之同理。 + +## FinSH 内置命令 + +在 RT-Thread 中默认内置了一些 FinSH 命令,在 FinSH 中输入 help 后回车或者直接按下 Tab 键,就可以打印当前系统支持的所有命令。C-Style 和 msh 模式下的内置命令基本一致,这里就以 msh 为例。 + +msh 模式下,按下 Tab 键后可以列出当前支持的所有命令。默认命令的数量不是固定的,RT-Thread 的各个组件会向 FinSH 输出一些命令。例如,当打开 DFS 组件时,就会把 ls,cp,cd 等命令加到 FinSH 中,方便开发者调试。 + +以下为按下 Tab 键后打印出来的当前支持的所有显示 RT-Thread 内核状态信息的命令,左边是命令名称,右边是关于命令的描述: + +```c +RT-Thread shell commands: +version - show RT-Thread version information +list_thread - list thread +list_sem - list semaphore in system +list_event - list event in system +list_mutex - list mutex in system +list_mailbox - list mail box in system +list_msgqueue - list message queue in system +list_timer - list timer in system +list_device - list device in system +exit - return to RT-Thread shell mode. +help - RT-Thread shell help. +ps - List threads in the system. +time - Execute command with time. +free - Show the memory usage in the system. +``` + +这里列出输入常用命令后返回的字段信息,方便开发者理解返回的信息内容。 + +### 显示线程状态 + +使用 ps 或者 list_thread 命令来列出系统中的所有线程信息,包括线程优先级、状态、栈的最大使用量等。 + +```c +msh />list_thread +thread pri status sp stack size max used left tick error +-------- --- ------- ---------- ---------- ------ ---------- --- +tshell 20 ready 0x00000118 0x00001000 29% 0x00000009 000 +tidle 31 ready 0x0000005c 0x00000200 28% 0x00000005 000 +timer 4 suspend 0x00000078 0x00000400 11% 0x00000009 000 +``` +list_thread 返回字段的描述: + +|**字段** |**描述** | +|------------|----------------------------| +| thread | 线程的名称 | +| pri | 线程的优先级 | +| status | 线程当前的状态 | +| sp | 线程当前的栈位置 | +| stack size | 线程的栈大小 | +| max used | 线程历史中使用的最大栈位置 | +| left tick | 线程剩余的运行节拍数 | +| error | 线程的错误码 | + +### 显示信号量状态 + +使用 list_sem 命令来显示系统中所有信号量信息,包括信号量的名称、信号量的值和等待这个信号量的线程数目。 + +```c +msh />list_sem +semaphore v suspend thread +-------- --- -------------- +shrx 000 0 +e0 000 0 +``` + +list_sem 返回字段的描述: + +|**字段** |**描述** | +|----------------|--------------------------| +| semaphore | 信号量的名称 | +| v | 信号量当前的值 | +| suspend thread | 等待这个信号量的线程数目 | + +### 显示事件状态 + +使用 list_event 命令来显示系统中所有的事件信息,包括事件名称、事件的值和等待这个事件的线程数目。 + +```c +msh />list_event +event set suspend thread +----- ---------- -------------- +``` + +list_event 返回字段的描述: + +|**字段** |**描述** | +|----------------|----------------------------------| +| event | 事件集的名称 | +| set | 事件集中当前发生的事件 | +| suspend thread | 在这个事件集中等待事件的线程数目 | + +### 显示互斥量状态 + +使用 list_mutex 命令来显示系统中所有的互斥量信息,包括互斥量名称、互斥量的所有者和所有者在互斥量上持有的嵌套次数等。 + +```c +msh />list_mutex +mutex owner hold suspend thread +-------- -------- ---- -------------- +fat0 (NULL) 0000 0 +sal_lock (NULL) 0000 0 +``` + +list_mutex 返回字段的描述: + +|**字段** |**描述** | +|----------------|------------------------------------| +| mutxe | 互斥量的名称 | +| owner | 当前持有互斥量的线程 | +| hold | 持有者在这个互斥量上嵌套持有的次数 | +| suspend thread | 等待这个互斥量的线程数目 | + +### 显示邮箱状态 + +使用 list_mailbox 命令显示系统中所有的邮箱信息,包括邮箱名称、邮箱中邮件的数目和邮箱能容纳邮件的最大数目等。 + +```c +msh />list_mailbox +mailbox entry size suspend thread +-------- ---- ---- -------------- +etxmb 0000 0008 1:etx +erxmb 0000 0008 1:erx +``` + +list_mailbox 返回字段的描述: + +|**字段** |**描述** | +|----------------|----------------------------| +| mailbox | 邮箱的名称 | +| entry | 邮箱中包含的邮件数目 | +| size | 邮箱能够容纳的最大邮件数目 | +| suspend thread | 等待这个邮箱的线程数目 | + +### 显示消息队列状态 + +使用 list_msgqueue 命令来显示系统中所有的消息队列信息,包括消息队列的名称、包含的消息数目和等待这个消息队列的线程数目。 + +```c +msh />list_msgqueue +msgqueue entry suspend thread +-------- ---- -------------- +``` + +list_msgqueue 返回字段的描述: + +|**字段** |**描述** | +|----------------|----------------------------| +| msgqueue | 消息队列的名称 | +| entry | 消息队列当前包含的消息数目 | +| suspend thread | 等待这个消息队列的线程数目 | + +### 显示内存池状态 + +使用 list_mempool 命令来显示系统中所有的内存池信息,包括内存池的名称、内存池的大小和最大使用的内存大小等。 + +```c +msh />list_mempool +mempool block total free suspend thread +------- ---- ---- ---- -------------- +signal 0012 0032 0032 0 +``` + +list_mempool 返回字段的描述: + +|**字段** |**描述** | +|----------------|--------------------| +| mempool | 内存池名称 | +| block | 内存块大小 | +| total | 总内存块 | +| free | 空闲内存块 | +| suspend thread | 等待这个内存池的线程数目 | + +### 显示定时器状态 + +使用 list_timer 命令来显示系统中所有的定时器信息,包括定时器的名称、是否是周期性定时器和定时器超时的节拍数等。 + +```c +msh />list_timer +timer periodic timeout flag +-------- ---------- ---------- ----------- +tshell 0x00000000 0x00000000 deactivated +tidle 0x00000000 0x00000000 deactivated +timer 0x00000000 0x00000000 deactivated +``` + +list_timer 返回字段的描述: + +|**字段**|**描述** | +|----------|--------------------------------| +| timer | 定时器的名称 | +| periodic | 定时器是否是周期性的 | +| timeout | 定时器超时时的节拍数 | +| flag | 定时器的状态,activated 表示活动的,deactivated 表示不活动的 | + +### 显示设备状态 + +使用 list_device 命令来显示系统中所有的设备信息,包括设备名称、设备类型和设备被打开次数。 + +```c +msh />list_device +device type ref count +------ ----------------- ---------- +e0 Network Interface 0 +uart0 Character Device 2 +``` + +list_device 返回字段的描述: + +|**字段** |**描述** | +|-----------|----------------| +| device | 设备的名称 | +| type | 设备的类型 | +| ref count | 设备被打开次数 | + +### 显示动态内存状态 + +使用 free 命令来显示系统中所有的内存信息。 + +```c +msh />free +total memory: 7669836 +used memory : 15240 +maximum allocated memory: 18520 +``` + +free 返回字段的描述: + +|**字段** |**描述** | +|--------------------------|------------------| +| total memory | 内存总大小 | +| used memory | 已使用的内存大小 | +| maximum allocated memory | 最大分配内存 | + +## 自定义 FinSH 命令 + +除了 FinSH 自带的命令,FinSH 还也提供了多个宏接口来导出自定义命令,导出的命令可以直接在 FinSH 中执行。 + +### 自定义 msh 命令 + +自定义的 msh 命令,可以在 msh 模式下被运行,将一个命令导出到 msh 模式可以使用如下宏接口: + +`MSH_CMD_EXPORT(name, desc);` + +|**参数**|**描述** | +|----------|----------------| +| name | 要导出的命令 | +| desc | 导出命令的描述 | + +这个命令可以导出有参数的命令,也可以导出无参数的命令。导出无参数命令时,函数的入参为 void,示例如下: + +```c +void hello(void) +{ + rt_kprintf("hello RT-Thread!\n"); +} + +MSH_CMD_EXPORT(hello , say hello to RT-Thread); +``` + +导出有参数的命令时,函数的入参为 `int argc` 和 `char**argv`。argc 表示参数的个数,argv 表示命令行参数字符串指针数组指针。导出有参数命令示例如下: + +```c +static void atcmd(int argc, char**argv) +{ + …… +} + +MSH_CMD_EXPORT(atcmd, atcmd sample: atcmd ); +``` + +### 自定义 C-Style 命令和变量 + +将自定义命令导出到 C-Style 模式可以使用如下接口: + +`FINSH_FUNCTION_EXPORT(name, desc);` + +|**参数**|**描述** | +|----------|----------------| +| name | 要导出的命令 | +| desc | 导出命令的描述 | + +以下示例定义了一个 hello 函数,并将它导出成 C-Style 模式下的命令: + +```c +void hello(void) +{ + rt_kprintf("hello RT-Thread!\n"); +} + +FINSH_FUNCTION_EXPORT(hello , say hello to RT-Thread); +``` + +按照类似的方式,也可以导出一个变量,可以通过如下接口: + +`FINSH_VAR_EXPORT(name, type, desc);` + +|**参数**|**描述** | +|----------|----------------| +| name | 要导出的变量 | +| type | 变量的类型 | +| desc | 导出变量的描述 | + +以下示例定义了一个 dummy 变量,并将它导出成 C-Style 模式下的变量命令: + +```c +static int dummy = 0; +FINSH_VAR_EXPORT(dummy, finsh_type_int, dummy variable for finsh) +``` +### 自定义命令重命名 + +FinSH 的函数名字长度有一定限制,它由 finsh.h 中的宏定义 FINSH_NAME_MAX 控制,默认是 16 字节,这意味着 FinSH 命令长度不会超过 16 字节。这里有个潜在的问题:当一个函数名长度超过 FINSH_NAME_MAX 时,使用 FINSH_FUNCTION_EXPORT 导出这个函数到命令表中后,在 FinSH 符号表中看到完整的函数名,但是完整输入执行会出现 null node 错误。这是因为虽然显示了完整的函数名,但是实际上 FinSH 中却保存了前 16 字节作为命令,过多的输入会导致无法正确找到命令,这时就可以使用 FINSH_FUNCTION_EXPORT_ALIAS 来对导出的命令进行重命名。 + +`FINSH_FUNCTION_EXPORT_ALIAS(name, alias, desc);` + +|**参数**|**描述** | +|----------|-------------------------| +| name | 要导出的命令 | +| alias | 导出到 FinSH 时显示的名字 | +| desc | 导出命令的描述 | + +在重命名的命令名字前加 `__cmd_` 就可以将命令导出到 msh 模式,否则,命令会被导出到 C-Style 模式。以下示例定义了一个 hello 函数,并将它重命名为 ho 后导出成 C-Style 模式下的命令。 + +```c +void hello(void) +{ + rt_kprintf("hello RT-Thread!\n"); +} + +FINSH_FUNCTION_EXPORT_ALIAS(hello , ho, say hello to RT-Thread); +``` +## FinSH 功能配置 + +FinSH 功能可以裁剪,宏配置选项在 rtconfig.h 文件中定义,具体配置项如下表所示。 + +|**宏定义** |**取值类型**|**描述** |**默认值**| +|------------------------|----|------------|-------| +| #define RT_USING_FINSH | 无 | 使能 FinSH | 开启 | +| #define FINSH_THREAD_NAME | 字符串 | FinSH 线程的名字 | "tshell" | +| #define FINSH_USING_HISTORY | 无 | 打开历史回溯功能 | 开启 | +| #define FINSH_HISTORY_LINES | 整数型 | 能回溯的历史命令行数 | 5| +| #define FINSH_USING_SYMTAB | 无 | 可以在 FinSH 中使用符号表 | 开启 | +|#define FINSH_USING_DESCRIPTION | 无 | 给每个 FinSH 的符号添加一段描述 | 开启 | +| #define FINSH_USING_MSH| 无 | 使能 msh 模式 | 开启 | +| #define FINSH_USING_MSH_ONLY | 无 | 只使用 msh 模式 | 开启 | +| #define FINSH_ARG_MAX | 整数型 | 最大输入参数数量 | 10 | +| #define FINSH_USING_AUTH | 无 | 使能权限验证 | 关闭 | +| #define FINSH_DEFAULT_PASSWORD | 字符串 | 权限验证密码 | 关闭 | + +rtconfig.h 中的参考配置示例如下所示,可以根据实际功能需求情况进行配置。 + +```c +/* 开启 FinSH */ +#define RT_USING_FINSH + +/* 将线程名称定义为 tshell */ +#define FINSH_THREAD_NAME "tshell" + +/* 开启历史命令 */ +#define FINSH_USING_HISTORY +/* 记录 5 行历史命令 */ +#define FINSH_HISTORY_LINES 5 + +/* 开启使用 Tab 键 */ +#define FINSH_USING_SYMTAB +/* 开启描述功能 */ +#define FINSH_USING_DESCRIPTION + +/* 定义 FinSH 线程优先级为 20 */ +#define FINSH_THREAD_PRIORITY 20 +/* 定义 FinSH 线程的栈大小为 4KB */ +#define FINSH_THREAD_STACK_SIZE 4096 +/* 定义命令字符长度为 80 字节 */ +#define FINSH_CMD_SIZE 80 + +/* 开启 msh 功能 */ +#define FINSH_USING_MSH +/* 默认使用 msh 功能 */ +#define FINSH_USING_MSH_DEFAULT +/* 最大输入参数数量为 10 个 */ +#define FINSH_ARG_MAX 10 +``` + +## FinSH 应用示例 + +### 不带参数的 msh 命令示例 + +本小节将演示如何将一个自定义的命令导出到 msh 中,示例代码如下所示,代码中创建了 hello 函数,然后通过 MSH_CMD_EXPORT 命令即可将 hello 函数导出到 FinSH 命令列表中。 + +```c +#include + +void hello(void) +{ + rt_kprintf("hello RT-Thread!\n"); +} + +MSH_CMD_EXPORT(hello , say hello to RT-Thread); +``` + +系统运行起来后,在 FinSH 控制台按 tab 键可以看到导出的命令: + +```c +msh /> +RT-Thread shell commands: +hello - say hello to RT-Thread +version - show RT-Thread version information +list_thread - list thread +…… +``` + +运行 hello 命令,运行结果如下所示: + +```c +msh />hello +hello RT_Thread! +msh /> +``` + +### 带参数的 msh 命令示例 + +本小节将演示如何将一个带参数的自定义的命令导出到 FinSH 中, 示例代码如下所示,代码中创建了 `atcmd()` 函数,然后通过 MSH_CMD_EXPORT 命令即可将 `atcmd()` 函数导出到 msh 命令列表中。 + +```c +#include + +static void atcmd(int argc, char**argv) +{ + if (argc < 2) + { + rt_kprintf("Please input'atcmd '\n"); + return; + } + + if (!rt_strcmp(argv[1], "server")) + { + rt_kprintf("AT server!\n"); + } + else if (!rt_strcmp(argv[1], "client")) + { + rt_kprintf("AT client!\n"); + } + else + { + rt_kprintf("Please input'atcmd '\n"); + } +} + +MSH_CMD_EXPORT(atcmd, atcmd sample: atcmd ); +``` + +系统运行起来后,在 FinSH 控制台按 tab 键可以看到导出的命令: + +```c +msh /> +RT-Thread shell commands: +hello - say hello to RT-Thread +atcmd - atcmd sample: atcmd +version - show RT-Thread version information +list_thread - list thread +…… +``` + +运行 atcmd 命令,运行结果如下所示: + +```c +msh />atcmd +Please input 'atcmd ' +msh /> +``` + +运行 atcmd server 命令,运行结果如下所示: + +```c +msh />atcmd server +AT server! +msh /> +``` + +运行 atcmd client 命令,运行结果如下所示: + +```c +msh />atcmd client +AT client! +msh /> +``` + +## FinSH 移植 + +FinSH 完全采用 ANSI C 编写,具备极好的移植性;内存占用少,如果不使用前面章节中介绍的函数方式动态地向 FinSH 添加符号,FinSH 将不会动态申请内存。FinSH 源码位于 `components/finsh` 目录下。移植 FinSH 需要注意以下几个方面: + +* FinSH 线程: + +每次的命令执行都是在 FinSH 线程(即 tshell 线程)的上下文中完成的。当定义 RT_USING_FINSH 宏时,就可以在初始化线程中调用 finsh_system_init() 初始化 FinSH 线程。RT-Thread 1.2.0 之后的版本中可以不使用 `finsh_set_device(const char* device_name)` 函数去显式指定使用的设备,而是会自动调用 `rt_console_get_device()` 函数去使用 console 设备(RT-Thread 1.1.x 及以下版本中必须使用 `finsh_set_device(const char* device_name)` 指定 FinSH 使用的设备)。FinSH 线程在函数 `finsh_system_init()` 函数中被创建,它将一直等待 rx_sem 信号量。 + +* FinSH 的输出: + +FinSH 的输出依赖于系统的输出,在 RT-Thread 中依赖 `rt_kprintf()` 输出。在启动函数 `rt_hw_board_init()` 中, `rt_console_set_device(const char* name)` 函数设置了 FinSH 的打印输出设备。 + +* FinSH 的输入: + +FinSH 线程在获得了 rx_sem 信号量后,调用 `rt_device_read()` 函数从设备 (选用串口设备) 中获得一个字符然后处理。所以 FinSH 的移植需要 `rt_device_read()` 函数的实现。而 rx_sem 信号量的释放通过调用 `rx_indicate()` 函数以完成对 FinSH 线程的输入通知。通常的过程是,当串口接收中断发生时(即串口有输入),接受中断服务例程调用 `rx_indicate()` 函数通知 FinSH 线程有输入,而后 FinSH 线程获取串口输入最后做相应的命令处理。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09fun1.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09fun1.png new file mode 100644 index 0000000000000000000000000000000000000000..d854b3c5c11aaa076e1c8fe591e703389a391620 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09fun1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09fun2.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09fun2.png new file mode 100644 index 0000000000000000000000000000000000000000..290601f4e52508523cedfd7fab63ec13c0a5ec99 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09fun2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_handle.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_handle.png new file mode 100644 index 0000000000000000000000000000000000000000..fb4195d2b106411f3f6902507a6d6963cf88713f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_handle.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..d1fa3ac3e5e7cbd43cace95613879ee9864748a7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_reque.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_reque.png new file mode 100644 index 0000000000000000000000000000000000000000..040ef96cb8301c3657ea9b29b31ba7199ab066bd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_reque.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_table.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_table.png new file mode 100644 index 0000000000000000000000000000000000000000..213140a7e6b5c4e1c6d476f67f99141525df3e3f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_table.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work.png new file mode 100644 index 0000000000000000000000000000000000000000..b91978c151132675843821170ffa99534238ed0f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work_process.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work_process.png new file mode 100644 index 0000000000000000000000000000000000000000..8d15d327b20ce710ee59abe010584b0a27c74f25 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work_process.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work_sta.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work_sta.png new file mode 100644 index 0000000000000000000000000000000000000000..ceca462c6ef65eb2633d0768e3b1d96286eaeba3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09interrupt_work_sta.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09relation.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09relation.png new file mode 100644 index 0000000000000000000000000000000000000000..3938dce4826e180358bc2abc7f151235361741b6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09relation.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09ths_switch.png b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09ths_switch.png new file mode 100644 index 0000000000000000000000000000000000000000..f13fce62def649186d7b222f2dd64d6126b5c9e4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/figures/09ths_switch.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/interrupt/interrupt.md b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/interrupt.md new file mode 100644 index 0000000000000000000000000000000000000000..13dddbcf2f75479465c2666bea3c19a8deca9b32 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/interrupt/interrupt.md @@ -0,0 +1,576 @@ +# 中断管理 + +什么是中断?简单的解释就是系统正在处理某一个正常事件,忽然被另一个需要马上处理的紧急事件打断,系统转而处理这个紧急事件,待处理完毕,再恢复运行刚才被打断的事件。生活中,我们经常会遇到这样的场景: + +当你正在专心看书的时候,忽然来了一个电话,于是记下书的页码,去接电话,接完电话后接着刚才的页码继续看书,这是一个典型的中断的过程。 + +电话是老师打过来的,让你赶快交作业,你判断交作业的优先级比看书高,于是电话挂断后先做作业,等交完作业后再接着刚才的页码继续看书,这是一个典型的在中断中进行任务调度的过程。 + +这些场景在嵌入式系统中也很常见,当 CPU 正在处理内部数据时,外界发生了紧急情况,要求 CPU 暂停当前的工作转去处理这个 [异步事件](https://baike.baidu.com/item/%E7%B4%A7%E6%80%A5%E4%BA%8B%E4%BB%B6)。处理完毕后,再回到原来被中断的地址,继续原来的工作,这样的过程称为中断。实现这一功能的系统称为 [中断系统](https://baike.baidu.com/item/%E4%B8%AD%E6%96%AD%E7%B3%BB%E7%BB%9F),申请 CPU 中断的请求源称为 [中断源](https://baike.baidu.com/item/%E4%B8%AD%E6%96%AD%E6%BA%90)。中断是一种异常,异常是导致处理器脱离正常运行转向执行特殊代码的任何事件,如果不及时进行处理,轻则系统出错,重则会导致系统毁灭性地瘫痪。所以正确地处理异常,避免错误的发生是提高软件鲁棒性(稳定性)非常重要的一环。如下图是一个简单的中断示意图。 + +![中断示意图](figures/09interrupt_work.png) + +中断处理与 CPU 架构密切相关,所以,本章会先介绍 ARM Cortex-M 的 CPU 架构,然后结合 Cortex-M CPU 架构来介绍 RT-Thread 的中断管理机制,读完本章,大家将深入了解 RT-Thread 的中断处理过程,如何添加中断服务程序(ISR)以及相关的注意事项。 + +## Cortex-M CPU 架构基础 + +不同于老的经典 ARM 处理器(例如:ARM7, ARM9),ARM Cortex-M 处理器有一个非常不同的架构,Cortex-M 是一个家族系列,其中包括 Cortex M0/M3/M4/M7 多个不同型号,每个型号之间会有些区别,例如 Cortex-M4 比 Cortex-M3 多了浮点计算功能等,但它们的编程模型基本是一致的,因此本书中介绍中断管理和移植的部分都不会对 Cortex M0/M3/M4/M7 做太精细的区分。本节主要介绍和 RT-Thread 中断管理相关的架构部分。 + +### 寄存器简介 + +Cortex-M 系列 CPU 的寄存器组里有 R0\~R15 共 16 个通用寄存器组和若干特殊功能寄存器,如下图所示。 + +通用寄存器组里的 R13 作为堆栈指针寄存器 (Stack Pointer,SP);R14 作为连接寄存器 (Link Register,LR),用于在调用子程序时,存储返回地址;R15 作为程序计数器 (Program Counter,PC),其中堆栈指针寄存器可以是主堆栈指针(MSP),也可以是进程堆栈指针(PSP)。 + +![Cortex-M 寄存器示意图](figures/09interrupt_table.png) + +特殊功能寄存器包括程序状态字寄存器组(PSRs)、中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)、控制寄存器(CONTROL),可以通过 MSR/MRS 指令来访问特殊功能寄存器,例如: + +``` +MRS R0, CONTROL ; 读取 CONTROL 到 R0 中 +MSR CONTROL, R0 ; 写入 R0 到 CONTROL 寄存器中 +``` + +程序状态字寄存器里保存算术与逻辑标志,例如负数标志,零结果标志,溢出标志等等。中断屏蔽寄存器组控制 Cortex-M 的中断除能。控制寄存器用来定义特权级别和当前使用哪个堆栈指针。 + +如果是具有浮点单元的 Cortex-M4 或者 Cortex-M7,控制寄存器也用来指示浮点单元当前是否在使用,浮点单元包含了 32 个浮点通用寄存器 S0\~S31 和特殊 FPSCR 寄存器(Floating point status and control register)。 + +### 操作模式和特权级别 + +Cortex-M 引入了操作模式和特权级别的概念,分别为线程模式和处理模式,如果进入异常或中断处理则进入处理模式,其他情况则为线程模式。 + +![Cortex-M 工作模式状态图](figures/09interrupt_work_sta.png) + +Cortex-M 有两个运行级别,分别为特权级和用户级,线程模式可以工作在特权级或者用户级,而处理模式总工作在特权级,可通过 CONTROL 特殊寄存器控制。工作模式状态切换情况如上图所示。 + +Cortex-M 的堆栈寄存器 SP 对应两个物理寄存器 MSP 和 PSP,MSP 为主堆栈,PSP 为进程堆栈,处理模式总是使用 MSP 作为堆栈,线程模式可以选择使用 MSP 或 PSP 作为堆栈,同样通过 CONTROL 特殊寄存器控制。复位后,Cortex-M 默认进入线程模式、特权级、使用 MSP 堆栈。 + +### 嵌套向量中断控制器 + +Cortex-M 中断控制器名为 NVIC(嵌套向量中断控制器),支持中断嵌套功能。当一个中断触发并且系统进行响应时,处理器硬件会将当前运行位置的上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR、PC、LR、R12、R3-R0 寄存器。 + +![Cortex-M 内核和 NVIC 关系示意图](figures/09relation.png) + +当系统正在服务一个中断时,如果有一个更高优先级的中断触发,那么处理器同样会打断当前运行的中断服务程序,然后把这个中断服务程序上下文的 PSR、PC、LR、R12、R3-R0 寄存器自动保存到中断栈中。 + +### PendSV 系统调用 + +PendSV 也称为可悬起的系统调用,它是一种异常,可以像普通的中断一样被挂起,它是专门用来辅助操作系统进行上下文切换的。PendSV 异常会被初始化为最低优先级的异常。每次需要进行上下文切换的时候,会手动触发 PendSV 异常,在 PendSV 异常处理函数中进行上下文切换。在下一章《内核移植》中会详细介绍利用 PendSV 机制进行操作系统上下文切换的详细流程。 + +## RT-Thread 中断工作机制 + +### 中断向量表 + +中断向量表是所有中断处理程序的入口,如下图所示是 Cortex-M 系列的中断处理过程:把一个函数(用户中断服务程序)同一个虚拟中断向量表中的中断向量联系在一起。当中断向量对应中断发生的时候,被挂接的用户中断服务程序就会被调用执行。 + +![中断处理过程](figures/09interrupt_handle.png) + +在 Cortex-M 内核上,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器将直接判定是哪个中断源,然后直接跳转到相应的固定位置进行处理,每个中断服务程序必须排列在一起放在统一的地址上(这个地址必须要设置到 NVIC 的中断向量偏移寄存器中)。中断向量表一般由一个数组定义或在起始代码中给出,默认采用起始代码给出: + +```c + __Vectors DCD __initial_sp ; Top of Stack + DCD Reset_Handler ; Reset 处理函数 + DCD NMI_Handler ; NMI 处理函数 + DCD HardFault_Handler ; Hard Fault 处理函数 + DCD MemManage_Handler ; MPU Fault 处理函数 + DCD BusFault_Handler ; Bus Fault 处理函数 + DCD UsageFault_Handler ; Usage Fault 处理函数 + DCD 0 ; 保留 + DCD 0 ; 保留 + DCD 0 ; 保留 + DCD 0 ; 保留 + DCD SVC_Handler ; SVCall 处理函数 + DCD DebugMon_Handler ; Debug Monitor 处理函数 + DCD 0 ; 保留 + DCD PendSV_Handler ; PendSV 处理函数 + DCD SysTick_Handler ; SysTick 处理函数 + +… … + +NMI_Handler PROC + EXPORT NMI_Handler [WEAK] + B . + ENDP +HardFault_Handler PROC + EXPORT HardFault_Handler [WEAK] + B . + ENDP +… … +``` + +请注意代码后面的 [WEAK] 标识,它是符号弱化标识,在 [WEAK] 前面的符号(如 NMI_Handler、HardFault_Handler)将被执行弱化处理,如果整个代码在链接时遇到了名称相同的符号(例如与 NMI_Handler 相同名称的函数),那么代码将使用未被弱化定义的符号(与 NMI_Handler 相同名称的函数),而与弱化符号相关的代码将被自动丢弃。 + +以 SysTick 中断为例,在系统启动代码中,需要填上 SysTick_Handler 中断入口函数,然后实现该函数即可对 SysTick 中断进行响应,中断处理函数示例程序如下所示: + +```c +void SysTick_Handler(void) +{ + /* enter interrupt */ + rt_interrupt_enter(); + + rt_tick_increase(); + + /* leave interrupt */ + rt_interrupt_leave(); +} +``` + +### 中断处理过程 + +RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分,如下图: + +![中断处理程序的 3 部分](figures/09interrupt_work_process.png) + +#### 中断前导程序 + +中断前导程序主要工作如下: + +1)保存 CPU 中断现场,这部分跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。 + +对于 Cortex-M 来说,该工作由硬件自动完成。当一个中断触发并且系统进行响应时,处理器硬件会将当前运行部分的上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR、PC、LR、R12、R3-R0 寄存器。 + +2)通知内核进入中断状态,调用 rt_interrupt_enter() 函数,作用是把全局变量 rt_interrupt_nest 加 1,用它来记录中断嵌套的层数,代码如下所示。 + +```c +void rt_interrupt_enter(void) +{ + rt_base_t level; + + level = rt_hw_interrupt_disable(); + rt_interrupt_nest ++; + rt_hw_interrupt_enable(level); +} +``` + +#### 用户中断服务程序 + +在用户中断服务程序(ISR)中,分为两种情况,第一种情况是不进行线程切换,这种情况下用户中断服务程序和中断后续程序运行完毕后退出中断模式,返回被中断的线程。 + +另一种情况是,在中断处理过程中需要进行线程切换,这种情况会调用 rt_hw_context_switch_interrupt() 函数进行上下文切换,该函数跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。 + +在 Cortex-M 架构中,rt_hw_context_switch_interrupt() 的函数实现流程如下图所示,它将设置需要切换的线程 rt_interrupt_to_thread 变量,然后触发 PendSV 异常(PendSV 异常是专门用来辅助上下文切换的,且被初始化为最低优先级的异常)。PendSV 异常被触发后,不会立即进行 PendSV 异常中断处理程序,因为此时还在中断处理中,只有当中断后续程序运行完毕,真正退出中断处理后,才进入 PendSV 异常中断处理程序。 + +![rt_hw_context_switch_interrupt() 函数实现流程](figures/09fun1.png) + +#### 中断后续程序 + +中断后续程序主要完成的工作是: + +1 通知内核离开中断状态,通过调用 rt_interrupt_leave() 函数,将全局变量 rt_interrupt_nest 减 1,代码如下所示。 + +```c +void rt_interrupt_leave(void) +{ + rt_base_t level; + + level = rt_hw_interrupt_disable(); + rt_interrupt_nest --; + rt_hw_interrupt_enable(level); +} +``` + +2 恢复中断前的 CPU 上下文,如果在中断处理过程中未进行线程切换,那么恢复 from 线程的 CPU 上下文,如果在中断中进行了线程切换,那么恢复 to 线程的 CPU 上下文。这部分实现跟 CPU 架构相关,不同 CPU 架构的实现方式有差异,在 Cortex-M 架构中实现流程如下图所示。 + +![rt_hw_context_switch_interrupt() 函数实现流程](figures/09fun2.png) + +### 中断嵌套 + +在允许中断嵌套的情况下,在执行中断服务程序的过程中,如果出现高优先级的中断,当前中断服务程序的执行将被打断,以执行高优先级中断的中断服务程序,当高优先级中断的处理完成后,被打断的中断服务程序才又得到继续执行,如果需要进行线程调度,线程的上下文切换将在所有中断处理程序都运行结束时才发生,如下图所示。 + +![中断中的线程切换](figures/09ths_switch.png) + +### 中断栈 + +在中断处理过程中,在系统响应中断前,软件代码(或处理器)需要把当前线程的上下文保存下来(通常保存在当前线程的线程栈中),再调用中断服务程序进行中断响应、处理。在进行中断处理时(实质是调用用户的中断服务程序函数),中断处理函数中很可能会有自己的局部变量,这些都需要相应的栈空间来保存,所以中断响应依然需要一个栈空间来做为上下文,运行中断处理函数。中断栈可以保存在打断线程的栈中,当从中断中退出时,返回相应的线程继续执行。 + +中断栈也可以与线程栈完全分离开来,即每次进入中断时,在保存完打断线程上下文后,切换到新的中断栈中独立运行。在中断退出时,再做相应的上下文恢复。使用独立中断栈相对来说更容易实现,并且对于线程栈使用情况也比较容易了解和掌握(否则必须要为中断栈预留空间,如果系统支持中断嵌套,还需要考虑应该为嵌套中断预留多大的空间)。 + +RT-Thread 采用的方式是提供独立的中断栈,即中断发生时,中断的前期处理程序会将用户的栈指针更换到系统事先留出的中断栈空间中,等中断退出时再恢复用户的栈指针。这样中断就不会占用线程的栈空间,从而提高了内存空间的利用率,且随着线程的增加,这种减少内存占用的效果也越明显。 + +在 Cortex-M 处理器内核里有两个堆栈指针,一个是主堆栈指针(MSP),是默认的堆栈指针,在运行第一个线程之前和在中断和异常服务程序里使用;另一个是线程堆栈指针(PSP),在线程里使用。在中断和异常服务程序退出时,修改 LR 寄存器的第 2 位的值为 1,线程的 SP 就由 MSP 切换到 PSP。 + +### 中断的底半处理 + +RT-Thread 不对中断服务程序所需要的处理时间做任何假设、限制,但如同其他实时操作系统或非实时操作系统一样,用户需要保证所有的中断服务程序在尽可能短的时间内完成(中断服务程序在系统中相当于拥有最高的优先级,会抢占所有线程优先执行)。这样在发生中断嵌套,或屏蔽了相应中断源的过程中,不会耽误嵌套的其他中断处理过程,或自身中断源的下一次中断信号。 + +当一个中断发生时,中断服务程序需要取得相应的硬件状态或者数据。如果中断服务程序接下来要对状态或者数据进行简单处理,比如 CPU 时钟中断,中断服务程序只需对一个系统时钟变量进行加一操作,然后就结束中断服务程序。这类中断需要的运行时间往往都比较短。但对于另外一些中断,中断服务程序在取得硬件状态或数据以后,还需要进行一系列更耗时的处理过程,通常需要将该中断分割为两部分,即**上半部分**(Top Half)和**底半部分**(Bottom Half)。在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知(可以是 RT-Thread 所提供的信号量、事件、邮箱或消息队列等方式),然后结束中断服务程序;而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称之为**底半处理**。 + +为了详细描述底半处理在 RT-Thread 中的实现,我们以一个虚拟的网络设备接收网络数据包作为范例,如下代码,并假设接收到数据报文后,系统对报文的分析、处理是一个相对耗时的,比外部中断源信号重要性小许多的,而且在不屏蔽中断源信号情况下也能处理的过程。 + +这个例子的程序创建了一个 nwt 线程,这个线程在启动运行后,将阻塞在 nw_bh_sem 信号上,一旦这个信号量被释放,将执行接下来的 nw_packet_parser 过程,开始 Bottom Half 的事件处理。 + +```c +/* + * 程序清单:中断底半处理例子 + */ + +/* 用于唤醒线程的信号量 */ +rt_sem_t nw_bh_sem; + +/* 数据读取、分析的线程 */ +void demo_nw_thread(void *param) +{ + /* 首先对设备进行必要的初始化工作 */ + device_init_setting(); + + /*.. 其他的一些操作..*/ + + /* 创建一个 semaphore 来响应 Bottom Half 的事件 */ + nw_bh_sem = rt_sem_create("bh_sem", 0, RT_IPC_FLAG_FIFO); + + while(1) + { + /* 最后,让 demo_nw_thread 等待在 nw_bh_sem 上 */ + rt_sem_take(nw_bh_sem, RT_WAITING_FOREVER); + + /* 接收到 semaphore 信号后,开始真正的 Bottom Half 处理过程 */ + nw_packet_parser (packet_buffer); + nw_packet_process(packet_buffer); + } +} + +int main(void) +{ + rt_thread_t thread; + + /* 创建处理线程 */ + thread = rt_thread_create("nwt",demo_nw_thread, RT_NULL, 1024, 20, 5); + + if (thread != RT_NULL) + rt_thread_startup(thread); +} +``` + +接下来让我们来看一下 demo_nw_isr 中是如何处理 Top Half,并开启 Bottom Half 的,如下例。 + +```c +void demo_nw_isr(int vector, void *param) +{ + /* 当 network 设备接收到数据后,陷入中断异常,开始执行此 ISR */ + /* 开始 Top Half 部分的处理,如读取硬件设备的状态以判断发生了何种中断 */ + nw_device_status_read(); + + /*.. 其他一些数据操作等..*/ + + /* 释放 nw_bh_sem,发送信号给 demo_nw_thread,准备开始 Bottom Half */ + rt_sem_release(nw_bh_sem); + + /* 然后退出中断的 Top Half 部分,结束 device 的 ISR */ +} +``` + +从上面例子的两个代码片段可以看出,中断服务程序通过对一个信号量对象的等待和释放,来完成中断 Bottom Half 的起始和终结。由于将中断处理划分为 Top 和 Bottom 两个部分后,使得中断处理过程变为异步过程。这部分系统开销需要用户在使用 RT-Thread 时,必须认真考虑中断服务的处理时间是否大于给 Bottom Half 发送通知并处理的时间。 + +RT-Thread 中断管理接口 +--------------------- + +为了把操作系统和系统底层的异常、中断硬件隔离开来,RT-Thread 把中断和异常封装为一组抽象接口,如下图所示: + +![中断相关接口](figures/09interrupt_ops.png) + +### 中断服务程序挂接 + +系统把用户的中断服务程序 (handler) 和指定的中断号关联起来,可调用如下的接口挂载一个新的中断服务程序: + +```c +rt_isr_handler_t rt_hw_interrupt_install(int vector, + rt_isr_handler_t handler, + void *param, + char *name); +``` + +调用 rt_hw_interrupt_install() 后,当这个中断源产生中断时,系统将自动调用装载的中断服务程序。下表描述了此函数的输入参数和返回值: + + rt_hw_interrupt_install() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|--------------------------------------------------| +| vector | vector 是挂载的中断号 | +| handler | 新挂载的中断服务程序 | +| param | param 会作为参数传递给中断服务程序 | +| name | 中断的名称 | +|**返回**| —— | +| return | 挂载这个中断服务程序之前挂载的中断服务程序的句柄 | + +> [!NOTE] +> 注:这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。 + +中断服务程序是一种需要特别注意的运行环境,它运行在非线程的执行环境下(一般为芯片的一种特殊运行模式(特权模式)),在这个运行环境中不能使用挂起当前线程的操作,因为当前线程并不存在,执行相关的操作会有类似打印提示信息,“Function [abc_func] shall not used in ISR”,含义是不应该在中断服务程序中调用的函数)。 + +### 中断源管理 + +通常在 ISR 准备处理某个中断信号之前,我们需要先屏蔽该中断源,在 ISR 处理完状态或数据以后,及时的打开之前被屏蔽的中断源。 + +屏蔽中断源可以保证在接下来的处理过程中硬件状态或者数据不会受到干扰,可调用下面这个函数接口: + +```c +void rt_hw_interrupt_mask(int vector); +``` + +调用 rt_hw_interrupt_mask 函数接口后,相应的中断将会被屏蔽(通常当这个中断触发时,中断状态寄存器会有相应的变化,但并不送达到处理器进行处理)。下表描述了此函数的输入参数: + + rt_hw_interrupt_mask() 的输入参数 + +|**参数**|**描述** | +|----------|----------------| +| vector | 要屏蔽的中断号 | + +> [!NOTE] +> 注:这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。 + +为了尽可能的不丢失硬件中断信号,可调用下面的函数接口打开被屏蔽的中断源: + +```c +void rt_hw_interrupt_umask(int vector); +``` + +调用 rt_hw_interrupt_umask 函数接口后,如果中断(及对应外设)被配置正确时,中断触发后,将送到到处理器进行处理。下表描述了此函数的输入参数: + + rt_hw_interrupt_umask() 的输入参数 + +|**参数**|**描述** | +|----------|--------------------| +| vector | 要打开屏蔽的中断号 | + +> [!NOTE] +> 注:这个 API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这个 API。 + +### 全局中断开关 + +**全局中断开关也称为**中断锁,是禁止多线程访问临界区最简单的一种方式,即通过关闭中断的方式,来保证当前线程不会被其他事件打断(因为整个系统已经不再响应那些可以触发线程重新调度的外部事件),也就是当前线程不会被抢占,除非这个线程主动放弃了处理器控制权。当需要关闭整个系统的中断时,可调用下面的函数接口: + +```c +rt_base_t rt_hw_interrupt_disable(void); +``` + +下表描述了此函数的返回值: + + rt_hw_interrupt_disable() 的返回值 + +|**返回**|**描述** | +|----------|---------------------------------------------| +| 中断状态 | rt_hw_interrupt_disable 函数运行前的中断状态 | + +恢复中断也称开中断。rt_hw_interrupt_enable()这个函数用于 “使能” 中断,它恢复了调用 rt_hw_interrupt_disable()函数前的中断状态。如果调用 rt_hw_interrupt_disable()函数前是关中断状态,那么调用此函数后依然是关中断状态。恢复中断往往是和关闭中断成对使用的,调用的函数接口如下: + +```c +void rt_hw_interrupt_enable(rt_base_t level); +``` + +下表描述了此函数的输入参数: + + rt_hw_interrupt_enable() 的输入参数 + +|**参数**|**描述** | +|----------|---------------------------------------------| +| level | 前一次 rt_hw_interrupt_disable 返回的中断状态 | + +1)使用中断锁来操作临界区的方法可以应用于任何场合,且其他几类同步方式都是依赖于中断锁而实现的,可以说中断锁是最强大的和最高效的同步方法。只是使用中断锁最主要的问题在于,在中断关闭期间系统将不再响应任何中断,也就不能响应外部的事件。所以中断锁对系统的实时性影响非常巨大,当使用不当的时候会导致系统完全无实时性可言(可能导致系统完全偏离要求的时间需求);而使用得当,则会变成一种快速、高效的同步方式。 + +例如,为了保证一行代码(例如赋值)的互斥运行,最快速的方法是使用中断锁而不是信号量或互斥量: + +```c + /* 关闭中断 */ + level = rt_hw_interrupt_disable(); + a = a + value; + /* 恢复中断 */ + rt_hw_interrupt_enable(level); +``` + +在使用中断锁时,需要确保关闭中断的时间非常短,例如上面代码中的 a = a + value; 也可换成另外一种方式,例如使用信号量: + +```c + /* 获得信号量锁 */ + rt_sem_take(sem_lock, RT_WAITING_FOREVER); + a = a + value; + /* 释放信号量锁 */ + rt_sem_release(sem_lock); +``` + +这段代码在 rt_sem_take 、rt_sem_release 的实现中,已经存在使用中断锁保护信号量内部变量的行为,所以对于简单如 a = a + value; 的操作,使用中断锁将更为简洁快速。 + +2)函数 rt_base_t rt_hw_interrupt_disable(void) 和函数 void rt_hw_interrupt_enable(rt_base_t level) 一般需要配对使用,从而保证正确的中断状态。 + +在 RT-Thread 中,开关全局中断的 API 支持多级嵌套使用,简单嵌套中断的代码如下代码所示: + + 简单嵌套中断使用 + +```c +#include + +void global_interrupt_demo(void) +{ + rt_base_t level0; + rt_base_t level1; + + /* 第一次关闭全局中断,关闭之前的全局中断状态可能是打开的,也可能是关闭的 */ + level0 = rt_hw_interrupt_disable(); + /* 第二次关闭全局中断,关闭之前的全局中断是关闭的,关闭之后全局中断还是关闭的 */ + level1 = rt_hw_interrupt_disable(); + + do_something(); + + /* 恢复全局中断到第二次关闭之前的状态,所以本次 enable 之后全局中断还是关闭的 */ + rt_hw_interrupt_enable(level1); + /* 恢复全局中断到第一次关闭之前的状态,这时候的全局中断状态可能是打开的,也可能是关闭的 */ + rt_hw_interrupt_enable(level0); +} +``` + +这个特性可以给代码的开发带来很大的便利。例如在某个函数里关闭了中断,然后调用某些子函数,再打开中断。这些子函数里面也可能存在开关中断的代码。由于全局中断的 API 支持嵌套使用,用户无需为这些代码做特殊处理。 + +### 中断通知 + +当整个系统被中断打断,进入中断处理函数时,需要通知内核当前已经进入到中断状态。针对这种情况,可通过以下接口: + +```c +void rt_interrupt_enter(void); +void rt_interrupt_leave(void); +``` + +这两个接口分别用在中断前导程序和中断后续程序中,均会对 rt_interrupt_nest(中断嵌套深度)的值进行修改: + +每当进入中断时,可以调用 rt_interrupt_enter() 函数,用于通知内核,当前已经进入了中断状态,并增加中断嵌套深度(执行 rt_interrupt_nest++); + +每当退出中断时,可以调用 rt_interrupt_leave() 函数,用于通知内核,当前已经离开了中断状态,并减少中断嵌套深度(执行 rt_interrupt_nest +--)。注意不要在应用程序中调用这两个接口函数。 + +使用 rt_interrupt_enter/leave() 的作用是,在中断服务程序中,如果调用了内核相关的函数(如释放信号量等操作),则可以通过判断当前中断状态,让内核及时调整相应的行为。例如:在中断中释放了一个信号量,唤醒了某线程,但通过判断发现当前系统处于中断上下文环境中,那么在进行线程切换时应该采取中断中线程切换的策略,而不是立即进行切换。 + +但如果中断服务程序不会调用内核相关的函数(释放信号量等操作),这个时候,也可以不调用 rt_interrupt_enter/leave() 函数。 + +在上层应用中,在内核需要知道当前已经进入到中断状态或当前嵌套的中断深度时,可调用 rt_interrupt_get_nest() 接口,它会返回 rt_interrupt_nest。如下: + +```c +rt_uint8_t rt_interrupt_get_nest(void); +``` + +下表描述了 rt_interrupt_get_nest() 的返回值 + +|**返回**|**描述** | +|----------|--------------------------------| +| 0 | 当前系统不处于中断上下文环境中 | +| 1 | 当前系统处于中断上下文环境中 | +| 大于 1 | 当前中断嵌套层次 | + +中断与轮询 +---------- + +当驱动外设工作时,其编程模式到底采用中断模式触发还是轮询模式触发往往是驱动开发人员首先要考虑的问题,并且这个问题在实时操作系统与分时操作系统中差异还非常大。因为轮询模式本身采用顺序执行的方式:查询到相应的事件然后进行对应的处理。所以轮询模式从实现上来说,相对简单清晰。例如往串口中写入数据,仅当串口控制器写完一个数据时,程序代码才写入下一个数据(否则这个数据丢弃掉)。相应的代码可以是这样的: + +```c +/* 轮询模式向串口写入数据 */ + while (size) + { + /* 判断 UART 外设中数据是否发送完毕 */ + while (!(uart->uart_device->SR & USART_FLAG_TXE)); + /* 当所有数据发送完毕后,才发送下一个数据 */ + uart->uart_device->DR = (*ptr & 0x1FF); + + ++ptr; --size; + } +``` + +在实时系统中轮询模式可能会出现非常大问题,因为在实时操作系统中,当一个程序持续地执行时(轮询时),它所在的线程会一直运行,比它优先级低的线程都不会得到运行。而分时系统中,这点恰恰相反,几乎没有优先级之分,可以在一个时间片运行这个程序,然后在另外一段时间片上运行另外一段程序。 + +所以通常情况下,实时系统中更多采用的是中断模式来驱动外设。当数据达到时,由中断唤醒相关的处理线程,再继续进行后续的动作。例如一些携带 FIFO(包含一定数据量的先进先出队列)的串口外设,其写入过程可以是这样的,如下图所示: + +![中断模式驱动外设](figures/09interrupt_reque.png) + +线程先向串口的 FIFO 中写入数据,当 FIFO 满时,线程主动挂起。串口控制器持续地从 FIFO 中取出数据并以配置的波特率(例如 115200bps)发送出去。当 FIFO 中所有数据都发送完成时,将向处理器触发一个中断;当中断服务程序得到执行时,可以唤醒这个线程。这里举例的是 FIFO 类型的设备,在现实中也有 DMA 类型的设备,原理类似。 + +对于低速设备来说,运用这种模式非常好,因为在串口外设把 FIFO 中的数据发送出去前,处理器可以运行其他的线程,这样就提高了系统的整体运行效率(甚至对于分时系统来说,这样的设计也是非常必要)。但是对于一些高速设备,例如传输速度达到 10Mbps 的时候,假设一次发送的数据量是 32 字节,我们可以计算出发送这样一段数据量需要的时间是:(32 X 8) X 1/10Mbps = 25us。当数据需要持续传输时,系统将在 25us 后触发一个中断以唤醒上层线程继续下次传递。假设系统的线程切换时间是 8us(通常实时操作系统的线程上下文切换时间只有几个 us),那么当整个系统运行时,对于数据带宽利用率将只有 25/(25+8) =75.8%。但是采用轮询模式,数据带宽的利用率则可能达到 100%。这个也是大家普遍认为实时系统中数据吞吐量不足的缘故,系统开销消耗在了线程切换上(有些实时系统甚至会如本章前面说的,采用底半处理,分级的中断处理方式,相当于又拉长中断到发送线程的时间开销,效率会更进一步下降)。 + +通过上述的计算过程,我们可以看出其中的一些关键因素:发送数据量越小,发送速度越快,对于数据吞吐量的影响也将越大。归根结底,取决于系统中产生中断的频度如何。当一个实时系统想要提升数据吞吐量时,可以考虑的几种方式: + +1)增加每次数据量发送的长度,每次尽量让外设尽量多地发送数据; + +2)必要情况下更改中断模式为轮询模式。同时为了解决轮询方式一直抢占处理机,其他低优先级线程得不到运行的情况,可以把轮询线程的优先级适当降低。 + +全局中断开关使用示例 +-------------------- + +这是一个中断的应用例程:在多线程访问同一个变量时,使用开关全局中断对该变量进行保护,如下代码所示: + + 使用开关中断进行全局变量的访问 + +```c +#include +#include + +#define THREAD_PRIORITY 20 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* 同时访问的全局变量 */ +static rt_uint32_t cnt; +void thread_entry(void *parameter) +{ + rt_uint32_t no; + rt_uint32_t level; + + no = (rt_uint32_t) parameter; + while (1) + { + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + cnt += no; + /* 恢复全局中断 */ + rt_hw_interrupt_enable(level); + + rt_kprintf("protect thread[%d]'s counter is %d\n", no, cnt); + rt_thread_mdelay(no * 10); + } +} + +/* 用户应用程序入口 */ +int interrupt_sample(void) +{ + rt_thread_t thread; + + /* 创建 t1 线程 */ + thread = rt_thread_create("thread1", thread_entry, (void *)10, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (thread != RT_NULL) + rt_thread_startup(thread); + + + /* 创建 t2 线程 */ + thread = rt_thread_create("thread2", thread_entry, (void *)20, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (thread != RT_NULL) + rt_thread_startup(thread); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(interrupt_sample, interrupt sample); +``` + +仿真运行结果如下: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >interrupt_sample +msh >protect thread[10]'s counter is 10 +protect thread[20]'s counter is 30 +protect thread[10]'s counter is 40 +protect thread[20]'s counter is 60 +protect thread[10]'s counter is 70 +protect thread[10]'s counter is 80 +protect thread[20]'s counter is 100 +protect thread[10]'s counter is 110 +protect thread[10]'s counter is 120 +protect thread[20]'s counter is 140 +… +``` + +> [!NOTE] +> 注:由于关闭全局中断会导致整个系统不能响应中断,所以在使用关闭全局中断做为互斥访问临界区的手段时,必须需要保证关闭全局中断的时间非常短,例如运行数条机器指令的时间。 + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..a2abb7717fc2b97563d344d81dd3bd0b6d9a58a9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_use.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_use.png new file mode 100644 index 0000000000000000000000000000000000000000..887b1823bdaae318238b15ad48bbf2171d2968e0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_use.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_work.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_work.png new file mode 100644 index 0000000000000000000000000000000000000000..a3969cf7cb8769b4093ffa7a51d48c7a4f5beb89 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06event_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06inter_ths_commu1.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06inter_ths_commu1.png new file mode 100644 index 0000000000000000000000000000000000000000..59c29c255262ca76dd2ddf528963e0c415a5a928 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06inter_ths_commu1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06inter_ths_commu2.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06inter_ths_commu2.png new file mode 100644 index 0000000000000000000000000000000000000000..ad1a9bf722b7d04e73d802d07a553aeedc801ec6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06inter_ths_commu2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06mutex_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06mutex_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..cbf7e6e3fbceef4041b69ed35e32b87594fa2089 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06mutex_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06mutex_work.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06mutex_work.png new file mode 100644 index 0000000000000000000000000000000000000000..b396b2da99d2d8201e46d923d3ed9d883c835626 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06mutex_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06priority_inherit.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06priority_inherit.png new file mode 100644 index 0000000000000000000000000000000000000000..6822fc3572d4763cbd1820be606f828111923696 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06priority_inherit.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06priority_inversion.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06priority_inversion.png new file mode 100644 index 0000000000000000000000000000000000000000..764052bbfcbe044970bf28483c6045ae6276eee9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06priority_inversion.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_lock.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_lock.png new file mode 100644 index 0000000000000000000000000000000000000000..924265f5361c62877ea2a5fb5f00d1a6cbde9755 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_lock.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..671bc468b211fe4503fa8326747ce6be0b30594d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_work.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_work.png new file mode 100644 index 0000000000000000000000000000000000000000..d09ffc99ae8bbf2bfd859307db03191867f33e04 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/figures/06sem_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1.md b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1.md new file mode 100644 index 0000000000000000000000000000000000000000..799771f7ed7b981b65f2ab9abb6cac6b326e399a --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/ipc1/ipc1.md @@ -0,0 +1,1325 @@ +# 线程间同步 + +在多线程实时系统中,一项工作的完成往往可以通过多个线程协调的方式共同来完成,那么多个线程之间如何 “默契” 协作才能使这项工作无差错执行?下面举个例子说明。 + +例如一项工作中的两个线程:一个线程从传感器中接收数据并且将数据写到共享内存中,同时另一个线程周期性的从共享内存中读取数据并发送去显示,下图描述了两个线程间的数据传递: + +![线程间数据传递示意图](figures/06inter_ths_commu1.png) + +如果对共享内存的访问不是排他性的,那么各个线程间可能同时访问它,这将引起数据一致性的问题。例如,在显示线程试图显示数据之前,接收线程还未完成数据的写入,那么显示将包含不同时间采样的数据,造成显示数据的错乱。 + +将传感器数据写入到共享内存块的接收线程 \#1 和将传感器数据从共享内存块中读出的线程 \#2 都会访问同一块内存。为了防止出现数据的差错,两个线程访问的动作必须是互斥进行的,应该是在一个线程对共享内存块操作完成后,才允许另一个线程去操作,这样,接收线程 \#1 与显示线程 \#2 才能正常配合,使此项工作正确地执行。 + +同步是指按预定的先后次序进行运行,线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序,也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间将是无序的。 + +多个线程操作 / 访问同一块区域(代码),这块代码就称为临界区,上述例子中的共享内存块就是临界区。线程互斥是指对于临界区资源访问的排它性。当多个线程都要使用临界区资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。 + +线程的同步方式有很多种,其核心思想都是:**在访问临界区的时候只允许一个 (或一类) 线程运行。**进入 / 退出临界区的方式有很多种: + +1)调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区;详见《中断管理》的全局中断开关内容。 + +2)调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。 + +本章将介绍多种同步方式:**信号量**(semaphore)、**互斥量**(mutex)、和**事件集**(event)。学习完本章,大家将学会如何使用信号量、互斥量、事件集这些对象进行线程间的同步。 + +## 信号量 + +以生活中的停车场为例来理解信号量的概念: + +①当停车场空的时候,停车场的管理员发现有很多空车位,此时会让外面的车陆续进入停车场获得停车位; + +②当停车场的车位满的时候,管理员发现已经没有空车位,将禁止外面的车进入停车场,车辆在外排队等候; + +③当停车场内有车离开时,管理员发现有空的车位让出,允许外面的车进入停车场;待空车位填满后,又禁止外部车辆进入。 + +在此例子中,管理员就相当于信号量,管理员手中空车位的个数就是信号量的值(非负数,动态变化);停车位相当于公共资源(临界区),车辆相当于线程。车辆通过获得管理员的允许取得停车位,就类似于线程通过获得信号量访问公共资源。 + +### 信号量工作机制 + +信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。 + +信号量工作示意图如下图所示,每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。 + +![信号量工作示意图](figures/06sem_work.png) + +### 信号量控制块 + +在 RT-Thread 中,信号量控制块是操作系统用于管理信号量的一个数据结构,由结构体 struct rt_semaphore 表示。另外一种 C 表达方式 rt_sem_t,表示的是信号量的句柄,在 C 语言中的实现是指向信号量控制块的指针。信号量控制块结构的详细定义如下: + +```c +struct rt_semaphore +{ + struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ + rt_uint16_t value; /* 信号量的值 */ +}; +/* rt_sem_t 是指向 semaphore 结构体的指针类型 */ +typedef struct rt_semaphore* rt_sem_t; +``` + +rt_semaphore 对象从 rt_ipc_object 中派生,由 IPC 容器所管理,信号量的最大值是 65535。 + +### 信号量的管理方式 + +信号量控制块中含有信号量相关的重要参数,在信号量各种状态间起到纽带的作用。信号量相关接口如下图所示,对一个信号量的操作包含:创建 / 初始化信号量、获取信号量、释放信号量、删除 / 脱离信号量。 + +![信号量相关接口](figures/06sem_ops.png) + +#### 创建和删除信号量 + +当创建一个信号量时,内核首先创建一个信号量控制块,然后对该控制块进行基本的初始化工作,创建信号量使用下面的函数接口: + +```c + rt_sem_t rt_sem_create(const char *name, + rt_uint32_t value, + rt_uint8_t flag); +``` + +当调用这个函数时,系统将先从对象管理器中分配一个 semaphore 对象,并初始化这个对象,然后初始化父类 IPC 对象以及与 semaphore 相关的部分。在创建信号量指定的参数中,信号量标志参数决定了当信号量不可用时,多个线程等待的排队方式。当选择 RT_IPC_FLAG_FIFO(先进先出)方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;当选择 RT_IPC_FLAG_PRIO(优先级等待)方式时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的信号量。下表描述了该函数的输入参数与返回值: + +rt_sem_create() 的输入参数和返回值 + +|**参数** |**描述** | +|--------------------|-------------------------------------------------------------------| +| name | 信号量名称 | +| value | 信号量初始值 | +| flag | 信号量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回** | —— | +| RT_NULL | 创建失败 | +| 信号量的控制块指针 | 创建成功 | + +系统不再使用信号量时,可通过删除信号量以释放系统资源,适用于动态创建的信号量。删除信号量使用下面的函数接口: + +```c +rt_err_t rt_sem_delete(rt_sem_t sem); +``` + +调用这个函数时,系统将删除这个信号量。如果删除该信号量时,有线程正在等待该信号量,那么删除操作会先唤醒等待在该信号量上的线程(等待线程的返回值是 - +RT_ERROR),然后再释放信号量的内存资源。下表描述了该函数的输入参数与返回值: + + rt_sem_delete() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|----------------------------------| +| sem | rt_sem_create() 创建的信号量对象 | +|**返回**| —— | +| RT_EOK | 删除成功 | + +#### 初始化和脱离信号量 + +对于静态信号量对象,它的内存空间在编译时期就被编译器分配出来,放在读写数据段或未初始化数据段上,此时使用信号量就不再需要使用 rt_sem_create 接口来创建它,而只需在使用前对它进行初始化即可。初始化信号量对象可使用下面的函数接口: + +```c +rt_err_t rt_sem_init(rt_sem_t sem, + const char *name, + rt_uint32_t value, + rt_uint8_t flag) +``` + +当调用这个函数时,系统将对这个 semaphore 对象进行初始化,然后初始化 IPC 对象以及与 +semaphore 相关的部分。信号量标志可用上面创建信号量函数里提到的标志。下表描述了该函数的输入参数与返回值: + + rt_sem_init() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|-------------------------------------------------------------------| +| sem | 信号量对象的句柄 | +| name | 信号量名称 | +| value | 信号量初始值 | +| flag | 信号量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回**| —— | +| RT_EOK | 初始化成功 | + +脱离信号量就是让信号量对象从内核对象管理器中脱离,适用于静态初始化的信号量。脱离信号量使用下面的函数接口: + +```c +rt_err_t rt_sem_detach(rt_sem_t sem); +``` + +使用该函数后,内核先唤醒所有挂在该信号量等待队列上的线程,然后将该信号量从内核对象管理器中脱离。原来挂起在信号量上的等待线程将获得 - RT_ERROR 的返回值。下表描述了该函数的输入参数与返回值: + + rt_sem_detach() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------------| +| sem | 信号量对象的句柄 | +|**返回**| —— | +| RT_EOK | 脱离成功 | + +#### 获取信号量 + +线程通过获取信号量来获得信号量资源实例,当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1,获取信号量使用下面的函数接口: + +```c +rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time); +``` + +在调用这个函数时,如果信号量的值等于零,那么说明当前信号量资源实例不可用,申请该信号量的线程将根据 time 参数的情况选择直接返回、或挂起等待一段时间、或永久等待,直到其他线程或中断释放该信号量。如果在参数 time 指定的时间内依然得不到信号量,线程将超时返回,返回值是 - RT_ETIMEOUT。下表描述了该函数的输入参数与返回值: + + rt_sem_take() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------|---------------------------------------------------| +| sem | 信号量对象的句柄 | +| time | 指定的等待时间,单位是操作系统时钟节拍(OS Tick) | +|**返回** | —— | +| RT_EOK | 成功获得信号量 | +| \-RT_ETIMEOUT | 超时依然未获得信号量 | +| \-RT_ERROR | 其他错误 | + +#### 无等待获取信号量 + +当用户不想在申请的信号量上挂起线程进行等待时,可以使用无等待方式获取信号量,无等待获取信号量使用下面的函数接口: + +```c +rt_err_t rt_sem_trytake(rt_sem_t sem); +``` + +这个函数与 rt_sem_take(sem, 0) 的作用相同,即当线程申请的信号量资源实例不可用的时候,它不会等待在该信号量上,而是直接返回 - RT_ETIMEOUT。下表描述了该函数的输入参数与返回值: + + rt_sem_trytake() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------|------------------| +| sem | 信号量对象的句柄 | +|**返回** | —— | +| RT_EOK | 成功获得信号量 | +| \-RT_ETIMEOUT | 获取失败 | + +#### 释放信号量 + +释放信号量可以唤醒挂起在该信号量上的线程。释放信号量使用下面的函数接口: + +```c +rt_err_t rt_sem_release(rt_sem_t sem); +``` + +例如当信号量的值等于零时,并且有线程等待这个信号量时,释放信号量将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量;否则将把信号量的值加 1。下表描述了该函数的输入参数与返回值: + + rt_sem_release() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------------| +| sem | 信号量对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功释放信号量 | + +### 信号量应用示例 + +这是一个信号量使用例程,该例程创建了一个动态信号量,初始化两个线程,一个线程发送信号量,一个线程接收到信号量后,执行相应的操作。如下代码所示: + +信号量的使用: + +```c +#include + +#define THREAD_PRIORITY 25 +#define THREAD_TIMESLICE 5 + +/* 指向信号量的指针 */ +static rt_sem_t dynamic_sem = RT_NULL; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; +static void rt_thread1_entry(void *parameter) +{ + static rt_uint8_t count = 0; + + while(1) + { + if(count <= 100) + { + count++; + } + else + return; + + /* count 每计数 10 次,就释放一次信号量 */ + if(0 == (count % 10)) + { + rt_kprintf("t1 release a dynamic semaphore.\n"); + rt_sem_release(dynamic_sem); + } + } +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; +static void rt_thread2_entry(void *parameter) +{ + static rt_err_t result; + static rt_uint8_t number = 0; + while(1) + { + /* 永久方式等待信号量,获取到信号量,则执行 number 自加的操作 */ + result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER); + if (result != RT_EOK) + { + rt_kprintf("t2 take a dynamic semaphore, failed.\n"); + rt_sem_delete(dynamic_sem); + return; + } + else + { + number++; + rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number); + } + } +} + +/* 信号量示例的初始化 */ +int semaphore_sample(void) +{ + /* 创建一个动态信号量,初始值是 0 */ + dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_FIFO); + if (dynamic_sem == RT_NULL) + { + rt_kprintf("create dynamic semaphore failed.\n"); + return -1; + } + else + { + rt_kprintf("create done. dynamic semaphore value = 0.\n"); + } + + rt_thread_init(&thread1, + "thread1", + rt_thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + rt_thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY-1, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(semaphore_sample, semaphore sample); +``` + +仿真运行结果: + +```c + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >semaphore_sample +create done. dynamic semaphore value = 0. +msh >t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 1 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 2 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 3 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 4 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 5 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 6 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 7 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 8 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 9 +t1 release a dynamic semaphore. +t2 take a dynamic semaphore. number = 10 +``` + +如上面运行结果:线程 1 在 count 计数为 10 的倍数时(count 计数为 100 之后线程退出),发送一个信号量,线程 2 在接收信号量后,对 number 进行加 1 操作。 + +信号量的另一个应用例程如下所示,本例程将使用 2 个线程、3 个信号量实现生产者与消费者的例子。其中: + +3 个信号量分别为:①lock:信号量锁的作用,因为 2 个线程都会对同一个数组 array 进行操作,所以该数组是一个共享资源,锁用来保护这个共享资源。②empty:空位个数,初始化为 5 个空位。③full:满位个数,初始化为 0 个满位。 + +2 个线程分别为:①生产者线程:获取到空位后,产生一个数字,循环放入数组中,然后释放一个满位。②消费者线程:获取到满位后,读取数组内容并相加,然后释放一个空位。 + +生产者消费者例程 + +```c +#include + +#define THREAD_PRIORITY 6 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* 定义最大 5 个元素能够被产生 */ +#define MAXSEM 5 + +/* 用于放置生产的整数数组 */ +rt_uint32_t array[MAXSEM]; + +/* 指向生产者、消费者在 array 数组中的读写位置 */ +static rt_uint32_t set, get; + +/* 指向线程控制块的指针 */ +static rt_thread_t producer_tid = RT_NULL; +static rt_thread_t consumer_tid = RT_NULL; + +struct rt_semaphore sem_lock; +struct rt_semaphore sem_empty, sem_full; + +/* 生产者线程入口 */ +void producer_thread_entry(void *parameter) +{ + int cnt = 0; + + /* 运行 10 次 */ + while (cnt < 10) + { + /* 获取一个空位 */ + rt_sem_take(&sem_empty, RT_WAITING_FOREVER); + + /* 修改 array 内容,上锁 */ + rt_sem_take(&sem_lock, RT_WAITING_FOREVER); + array[set % MAXSEM] = cnt + 1; + rt_kprintf("the producer generates a number: %d\n", array[set % MAXSEM]); + set++; + rt_sem_release(&sem_lock); + + /* 发布一个满位 */ + rt_sem_release(&sem_full); + cnt++; + + /* 暂停一段时间 */ + rt_thread_mdelay(20); + } + + rt_kprintf("the producer exit!\n"); +} + +/* 消费者线程入口 */ +void consumer_thread_entry(void *parameter) +{ + rt_uint32_t sum = 0; + + while (1) + { + /* 获取一个满位 */ + rt_sem_take(&sem_full, RT_WAITING_FOREVER); + + /* 临界区,上锁进行操作 */ + rt_sem_take(&sem_lock, RT_WAITING_FOREVER); + sum += array[get % MAXSEM]; + rt_kprintf("the consumer[%d] get a number: %d\n", (get % MAXSEM), array[get % MAXSEM]); + get++; + rt_sem_release(&sem_lock); + + /* 释放一个空位 */ + rt_sem_release(&sem_empty); + + /* 生产者生产到 10 个数目,停止,消费者线程相应停止 */ + if (get == 10) break; + + /* 暂停一小会时间 */ + rt_thread_mdelay(50); + } + + rt_kprintf("the consumer sum is: %d\n", sum); + rt_kprintf("the consumer exit!\n"); +} + +int producer_consumer(void) +{ + set = 0; + get = 0; + + /* 初始化 3 个信号量 */ + rt_sem_init(&sem_lock, "lock", 1, RT_IPC_FLAG_FIFO); + rt_sem_init(&sem_empty, "empty", MAXSEM, RT_IPC_FLAG_FIFO); + rt_sem_init(&sem_full, "full", 0, RT_IPC_FLAG_FIFO); + + /* 创建生产者线程 */ + producer_tid = rt_thread_create("producer", + producer_thread_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, + THREAD_TIMESLICE); + if (producer_tid != RT_NULL) + { + rt_thread_startup(producer_tid); + } + else + { + rt_kprintf("create thread producer failed"); + return -1; + } + + /* 创建消费者线程 */ + consumer_tid = rt_thread_create("consumer", + consumer_thread_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, + THREAD_TIMESLICE); + if (consumer_tid != RT_NULL) + { + rt_thread_startup(consumer_tid); + } + else + { + rt_kprintf("create thread consumer failed"); + return -1; + } + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(producer_consumer, producer_consumer sample); +``` + +该例程的仿真结果如下: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >producer_consumer +the producer generates a number: 1 +the consumer[0] get a number: 1 +msh >the producer generates a number: 2 +the producer generates a number: 3 +the consumer[1] get a number: 2 +the producer generates a number: 4 +the producer generates a number: 5 +the producer generates a number: 6 +the consumer[2] get a number: 3 +the producer generates a number: 7 +the producer generates a number: 8 +the consumer[3] get a number: 4 +the producer generates a number: 9 +the consumer[4] get a number: 5 +the producer generates a number: 10 +the producer exit! +the consumer[0] get a number: 6 +the consumer[1] get a number: 7 +the consumer[2] get a number: 8 +the consumer[3] get a number: 9 +the consumer[4] get a number: 10 +the consumer sum is: 55 +the consumer exit! +``` + +本例程可以理解为生产者生产产品放入仓库,消费者从仓库中取走产品。 + +(1)生产者线程: + +1)获取 1 个空位(放产品 number),此时空位减 1; + +2)上锁保护;本次的产生的 number 值为 cnt+1,把值循环存入数组 array 中;再开锁; + +3)释放 1 个满位(给仓库中放置一个产品,仓库就多一个满位),满位加 1; + +(2)消费者线程: + +1)获取 1 个满位(取产品 number),此时满位减 1; + +2)上锁保护;将本次生产者生产的 number 值从 array 中读出来,并与上次的 number 值相加;再开锁; + +3)释放 1 个空位(从仓库上取走一个产品,仓库就多一个空位),空位加 1。 + +生产者依次产生 10 个 number,消费者依次取走,并将 10 个 number 的值求和。信号量锁 lock 保护 array 临界区资源:保证了消费者每次取 number 值的排他性,实现了线程间同步。 + +### 信号量的使用场合 + +信号量是一种非常灵活的同步方式,可以运用在多种场合中。形成锁、同步、资源计数等关系,也能方便的用于线程与线程、中断与线程间的同步中。 + +#### 线程同步 + +线程同步是信号量最简单的一类应用。例如,使用信号量进行两个线程之间的同步,信号量的值初始化成 0,表示具备 0 个信号量资源实例;而尝试获得该信号量的线程,将直接在这个信号量上进行等待。 + +当持有信号量的线程完成它处理的工作时,释放这个信号量,可以把等待在这个信号量上的线程唤醒,让它执行下一部分工作。这类场合也可以看成把信号量用于工作完成标志:持有信号量的线程完成它自己的工作,然后通知等待该信号量的线程继续下一部分工作。 + +#### 锁 + +锁,单一的锁常应用于多个线程间对同一共享资源(即临界区)的访问。信号量在作为锁来使用时,通常应将信号量资源实例初始化成 1,代表系统默认有一个资源可用,因为信号量的值始终在 1 和 0 之间变动,所以这类锁也叫做二值信号量。如下图所示,当线程需要访问共享资源时,它需要先获得这个资源锁。当这个线程成功获得资源锁时,其他打算访问共享资源的线程会由于获取不到资源而挂起,这是因为其他线程在试图获取这个锁时,这个锁已经被锁上(信号量值是 0)。当获得信号量的线程处理完毕,退出临界区时,它将会释放信号量并把锁解开,而挂起在锁上的第一个等待线程将被唤醒从而获得临界区的访问权。 + +![锁](figures/06sem_lock.png) + +#### 中断与线程的同步 + +信号量也能够方便地应用于中断与线程间的同步,例如一个中断触发,中断服务例程需要通知线程进行相应的数据处理。这个时候可以设置信号量的初始值是 0,线程在试图持有这个信号量时,由于信号量的初始值是 0,线程直接在这个信号量上挂起直到信号量被释放。当中断触发时,先进行与硬件相关的动作,例如从硬件的 I/O 口中读取相应的数据,并确认中断以清除中断源,而后释放一个信号量来唤醒相应的线程以做后续的数据处理。例如 FinSH 线程的处理方式,如下图所示。 + +![FinSH 的中断、线程间同步示意图](figures/06inter_ths_commu2.png) + +信号量的值初始为 0,当 FinSH 线程试图取得信号量时,因为信号量值是 0,所以它会被挂起。当 console 设备有数据输入时,产生中断,从而进入中断服务例程。在中断服务例程中,它会读取 console 设备的数据,并把读得的数据放入 UART buffer 中进行缓冲,而后释放信号量,释放信号量的操作将唤醒 shell 线程。在中断服务例程运行完毕后,如果系统中没有比 shell 线程优先级更高的就绪线程存在时,shell 线程将持有信号量并运行,从 UART buffer 缓冲区中获取输入的数据。 + +> [!NOTE] +> 注:中断与线程间的互斥不能采用信号量(锁)的方式,而应采用开关中断的方式。 + +#### 资源计数 + +信号量也可以认为是一个递增或递减的计数器,需要注意的是信号量的值非负。例如:初始化一个信号量的值为 5,则这个信号量可最大连续减少 5 次,直到计数器减为 0。资源计数适合于线程间工作处理速度不匹配的场合,这个时候信号量可以做为前一线程工作完成个数的计数,而当调度到后一线程时,它也可以以一种连续的方式一次处理多个事件。例如,生产者与消费者问题中,生产者可以对信号量进行多次释放,而后消费者被调度到时能够一次处理多个信号量资源。 + +> [!NOTE] +> 注:一般资源计数类型多是混合方式的线程间同步,因为对于单个的资源处理依然存在线程的多重访问,这就需要对一个单独的资源进行访问、处理,并进行锁方式的互斥操作。 + +互斥量 +------ + +互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。互斥量类似于只有一个车位的停车场:当有一辆车进入的时候,将停车场大门锁住,其他车辆在外面等候。当里面的车出来时,将停车场大门打开,下一辆车才可以进入。 + +### 互斥量工作机制 + +互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。 + +互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起,如下图时所示。这个特性与一般的二值信号量有很大的不同:在信号量中,因为已经不存在实例,线程递归持有会发生主动挂起(最终形成死锁)。 + +![互斥量工作示意图](figures/06mutex_work.png) + +使用信号量会导致的另一个潜在问题是线程优先级翻转问题。所谓优先级翻转,即当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。如下图所示:有优先级为 A、B 和 C 的三个线程,优先级 A> B > C。线程 A,B 处于挂起状态,等待某一事件触发,线程 C 正在运行,此时线程 C 开始使用某一共享资源 M。在使用过程中,线程 A 等待的事件到来,线程 A 转为就绪态,因为它比线程 C 优先级高,所以立即执行。但是当线程 A 要使用共享资源 M 时,由于其正在被线程 C 使用,因此线程 A 被挂起切换到线程 C 运行。如果此时线程 B 等待的事件到来,则线程 B 转为就绪态。由于线程 B 的优先级比线程 C 高,因此线程 B 开始运行,直到其运行完毕,线程 C 才开始运行。只有当线程 C 释放共享资源 M 后,线程 A 才得以执行。在这种情况下,优先级发生了翻转:线程 B 先于线程 A 运行。这样便不能保证高优先级线程的响应时间。 + +![优先级反转 (M 为信号量)](figures/06priority_inversion.png) + +在 RT-Thread 操作系统中,互斥量可以解决优先级翻转问题,实现的是优先级继承算法。优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。这样能够防止 C(间接地防止 A)被 B 抢占,如下图所示。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。 + +![优先级继承 (M 为互斥量)](figures/06priority_inherit.png) + +> [!NOTE] +> 注:在获得互斥量后,请尽快释放互斥量,并且在持有互斥量的过程中,不得再行更改持有互斥量线程的优先级。 + +### 互斥量控制块 + +在 RT-Thread 中,互斥量控制块是操作系统用于管理互斥量的一个数据结构,由结构体 struct rt_mutex 表示。另外一种 C 表达方式 rt_mutex_t,表示的是互斥量的句柄,在 C 语言中的实现是指互斥量控制块的指针。互斥量控制块结构的详细定义请见以下代码: + +```c +struct rt_mutex + { + struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ + + rt_uint16_t value; /* 互斥量的值 */ + rt_uint8_t original_priority; /* 持有线程的原始优先级 */ + rt_uint8_t hold; /* 持有线程的持有次数 */ + struct rt_thread *owner; /* 当前拥有互斥量的线程 */ + }; + /* rt_mutext_t 为指向互斥量结构体的指针类型 */ + typedef struct rt_mutex* rt_mutex_t; +``` + +rt_mutex 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。 + +### 互斥量的管理方式 + +互斥量控制块中含有互斥相关的重要参数,在互斥量功能的实现中起到重要的作用。互斥量相关接口如下图所示,对一个互斥量的操作包含:创建 / 初始化互斥量、获取互斥量、释放互斥量、删除 / 脱离互斥量。 + +![互斥量相关接口](figures/06mutex_ops.png) + +#### 创建和删除互斥量 + +创建一个互斥量时,内核首先创建一个互斥量控制块,然后完成对该控制块的初始化工作。创建互斥量使用下面的函数接口: + +```c +rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag); +``` + +可以调用 rt_mutex_create 函数创建一个互斥量,它的名字由 name 所指定。当调用这个函数时,系统将先从对象管理器中分配一个 mutex 对象,并初始化这个对象,然后初始化父类 IPC 对象以及与 mutex 相关的部分。互斥量的 flag 标志设置为 RT_IPC_FLAG_PRIO,表示在多个线程等待资源时,将由优先级高的线程优先获得资源。flag 设置为 RT_IPC_FLAG_FIFO,表示在多个线程等待资源时,将按照先来先得的顺序获得资源。下表描述了该函数的输入参数与返回值: + + rt_mutex_create() 的输入参数和返回值 + +|**参数** |**描述** | +|------------|-------------------------------------------------------------------| +| name | 互斥量的名称 | +| flag | 互斥量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回** | —— | +| 互斥量句柄 | 创建成功 | +| RT_NULL | 创建失败 | + +当不再使用互斥量时,通过删除互斥量以释放系统资源,适用于动态创建的互斥量。删除互斥量使用下面的函数接口: + +```c +rt_err_t rt_mutex_delete (rt_mutex_t mutex); +``` + +当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是 - +RT_ERROR。然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间。下表描述了该函数的输入参数与返回值: + + rt_mutex_delete() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------------| +| mutex | 互斥量对象的句柄 | +|**返回**| —— | +| RT_EOK | 删除成功 | + +#### 初始化和脱离互斥量 + +静态互斥量对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中。在使用这类静态互斥量对象前,需要先进行初始化。初始化互斥量使用下面的函数接口: + +```c +rt_err_t rt_mutex_init (rt_mutex_t mutex, const char* name, rt_uint8_t flag); +``` + +使用该函数接口时,需指定互斥量对象的句柄(即指向互斥量控制块的指针),互斥量名称以及互斥量标志。互斥量标志可用上面创建互斥量函数里提到的标志。下表描述了该函数的输入参数与返回值: + + rt_mutex_init() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|-------------------------------------------------------------------| +| mutex | 互斥量对象的句柄,它由用户提供,并指向互斥量对象的内存块 | +| name | 互斥量的名称 | +| flag | 互斥量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回**| —— | +| RT_EOK | 初始化成功 | + +脱离互斥量将把互斥量对象从内核对象管理器中脱离,适用于静态初始化的互斥量。脱离互斥量使用下面的函数接口: + +```c +rt_err_t rt_mutex_detach (rt_mutex_t mutex); +``` + +使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是 -RT_ERROR),然后系统将该互斥量从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值: + + rt_mutex_detach() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------------| +| mutex | 互斥量对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 获取互斥量 + +线程获取了互斥量,那么线程就有了对该互斥量的所有权,即某一个时刻一个互斥量只能被一个线程持有。获取互斥量使用下面的函数接口: + +```c +rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time); +``` + +如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 +1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。下表描述了该函数的输入参数与返回值: + +rt_mutex_take() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------|------------------| +| mutex | 互斥量对象的句柄 | +| time | 指定等待的时间 | +|**返回** | —— | +| RT_EOK | 成功获得互斥量 | +| \-RT_ETIMEOUT | 超时 | +| \-RT_ERROR | 获取失败 | + +#### 释放互斥量 + +当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量。释放互斥量使用下面的函数接口: + +```c +rt_err_t rt_mutex_release(rt_mutex_t mutex); +``` + +使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减 +1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。下表描述了该函数的输入参数与返回值: + +rt_mutex_release() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------------| +| mutex | 互斥量对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +### 互斥量应用示例 + +这是一个互斥量的应用例程,互斥锁是一种保护共享资源的方法。当一个线程拥有互斥锁的时候,可以保护共享资源不被其他线程破坏。下面用一个例子来说明,有两个线程:线程 1 和线程 2,线程 1 对 2 个 number 分别进行加 1 操作;线程 2 也对 2 个 number 分别进行加 1 操作,使用互斥量保证线程改变 2 个 number 值的操作不被打断。如下代码所示: + +互斥量例程 + +```c +#include + +#define THREAD_PRIORITY 8 +#define THREAD_TIMESLICE 5 + +/* 指向互斥量的指针 */ +static rt_mutex_t dynamic_mutex = RT_NULL; +static rt_uint8_t number1,number2 = 0; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; +static void rt_thread_entry1(void *parameter) +{ + while(1) + { + /* 线程 1 获取到互斥量后,先后对 number1、number2 进行加 1 操作,然后释放互斥量 */ + rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); + number1++; + rt_thread_mdelay(10); + number2++; + rt_mutex_release(dynamic_mutex); + } +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; +static void rt_thread_entry2(void *parameter) +{ + while(1) + { + /* 线程 2 获取到互斥量后,检查 number1、number2 的值是否相同,相同则表示 mutex 起到了锁的作用 */ + rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); + if(number1 != number2) + { + rt_kprintf("not protect.number1 = %d, mumber2 = %d \n",number1 ,number2); + } + else + { + rt_kprintf("mutex protect ,number1 = mumber2 is %d\n",number1); + } + + number1++; + number2++; + rt_mutex_release(dynamic_mutex); + + if(number1>=50) + return; + } +} + +/* 互斥量示例的初始化 */ +int mutex_sample(void) +{ + /* 创建一个动态互斥量 */ + dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_FIFO); + if (dynamic_mutex == RT_NULL) + { + rt_kprintf("create dynamic mutex failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + rt_thread_entry1, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + rt_thread_entry2, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY-1, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + return 0; +} + +/* 导出到 MSH 命令列表中 */ +MSH_CMD_EXPORT(mutex_sample, mutex sample); +``` + +线程 1 与线程 2 中均使用互斥量保护对 2 个 number 的操作(倘若将线程 1 中的获取、释放互斥量语句注释掉,线程 1 将对 number 不再做保护),仿真运行结果如下: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >mutex_sample +msh >mutex protect ,number1 = mumber2 is 1 +mutex protect ,number1 = mumber2 is 2 +mutex protect ,number1 = mumber2 is 3 +mutex protect ,number1 = mumber2 is 4 +… +mutex protect ,number1 = mumber2 is 48 +mutex protect ,number1 = mumber2 is 49 +``` + +线程使用互斥量保护对两个 number 的操作,使 number 值保持一致。 + +互斥量的另一个例子见下面的代码,这个例子将创建 3 个动态线程以检查持有互斥量时,持有的线程优先级是否被调整到等待线程优先级中的最高优先级。 + +防止优先级翻转特性例程 + +```c +#include + +/* 指向线程控制块的指针 */ +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; +static rt_thread_t tid3 = RT_NULL; +static rt_mutex_t mutex = RT_NULL; + + +#define THREAD_PRIORITY 10 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* 线程 1 入口 */ +static void thread1_entry(void *parameter) +{ + /* 先让低优先级线程运行 */ + rt_thread_mdelay(100); + + /* 此时 thread3 持有 mutex,并且 thread2 等待持有 mutex */ + + /* 检查 thread2 与 thread3 的优先级情况 */ + if (tid2->current_priority != tid3->current_priority) + { + /* 优先级不相同,测试失败 */ + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + rt_kprintf("test failed.\n"); + return; + } + else + { + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + rt_kprintf("test OK.\n"); + } +} + +/* 线程 2 入口 */ +static void thread2_entry(void *parameter) +{ + rt_err_t result; + + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + + /* 先让低优先级线程运行 */ + rt_thread_mdelay(50); + + /* + * 试图持有互斥锁,此时 thread3 持有,应把 thread3 的优先级提升 + * 到 thread2 相同的优先级 + */ + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + + if (result == RT_EOK) + { + /* 释放互斥锁 */ + rt_mutex_release(mutex); + } +} + +/* 线程 3 入口 */ +static void thread3_entry(void *parameter) +{ + rt_tick_t tick; + rt_err_t result; + + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + if (result != RT_EOK) + { + rt_kprintf("thread3 take a mutex, failed.\n"); + } + + /* 做一个长时间的循环,500ms */ + tick = rt_tick_get(); + while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ; + + rt_mutex_release(mutex); +} + +int pri_inversion(void) +{ + /* 创建互斥锁 */ + mutex = rt_mutex_create("mutex", RT_IPC_FLAG_FIFO); + if (mutex == RT_NULL) + { + rt_kprintf("create dynamic mutex failed.\n"); + return -1; + } + + /* 创建线程 1 */ + tid1 = rt_thread_create("thread1", + thread1_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* 创建线程 2 */ + tid2 = rt_thread_create("thread2", + thread2_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + + /* 创建线程 3 */ + tid3 = rt_thread_create("thread3", + thread3_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, THREAD_TIMESLICE); + if (tid3 != RT_NULL) + rt_thread_startup(tid3); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(pri_inversion, prio_inversion sample); +``` + +仿真运行结果如下: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >pri_inversion +the priority of thread2 is: 10 +the priority of thread3 is: 11 +the priority of thread2 is: 10 +the priority of thread3 is: 10 +test OK. +``` + +例程演示了互斥量的使用方法。线程 3 先持有互斥量,而后线程 2 试图持有互斥量,此时线程 3 的优先级被提升为和线程 2 的优先级相同。 + +> [!NOTE] +> 注:需要切记的是互斥量不能在中断服务例程中使用。 + +### 互斥量的使用场合 + +互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。互斥量更适合于: + +(1)线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题。 + +(2)可能会由于多线程同步而造成优先级翻转的情况。 + +事件集 +------ + +事件集也是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。下面以坐公交为例说明事件,在公交站等公交时可能有以下几种情况: + +①P1 坐公交去某地,只有一种公交可以到达目的地,等到此公交即可出发。 + +②P1 坐公交去某地,有 3 种公交都可以到达目的地,等到其中任意一辆即可出发。 + +③P1 约另一人 P2 一起去某地,则 P1 必须要等到 “同伴 P2 到达公交站” 与“公交到达公交站”两个条件都满足后,才能出发。 + +这里,可以将 P1 去某地视为线程,将 “公交到达公交站”、“同伴 P2 到达公交站” 视为事件的发生,情况①是特定事件唤醒线程;情况②是任意单个事件唤醒线程;情况③是多个事件同时发生才唤醒线程。 + +### 事件集工作机制 + +事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。 + +RT-Thread 定义的事件集有以下特点: + +1)事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件; + +2)事件仅用于同步,不提供数据传输功能; + +3)事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。 + +在 RT-Thread 中,每个线程都拥有一个事件信息标记,它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。当线程等待事件同步时,可以通过 32 个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。 + +![事件集工作示意图](figures/06event_work.png) + +如上图所示,线程 \#1 的事件标志中第 1 位和第 30 位被置位,如果事件信息标记位设为逻辑与,则表示线程 \#1 只有在事件 1 和事件 30 都发生以后才会被触发唤醒,如果事件信息标记位设为逻辑或,则事件 1 或事件 30 中的任意一个发生都会触发唤醒线程 \#1。如果信息标记同时设置了清除标记位,则当线程 \#1 唤醒后将主动把事件 1 和事件 30 清为零,否则事件标志将依然存在(即置 1)。 + +### 事件集控制块 + +在 RT-Thread 中,事件集控制块是操作系统用于管理事件的一个数据结构,由结构体 struct rt_event 表示。另外一种 C 表达方式 rt_event_t,表示的是事件集的句柄,在 C 语言中的实现是事件集控制块的指针。事件集控制块结构的详细定义请见以下代码: + +```c +struct rt_event +{ + struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ + + /* 事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生 */ + rt_uint32_t set; +}; +/* rt_event_t 是指向事件结构体的指针类型 */ +typedef struct rt_event* rt_event_t; +``` + +rt_event 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。 + +### 事件集的管理方式 + +事件集控制块中含有与事件集相关的重要参数,在事件集功能的实现中起重要的作用。事件集相关接口如下图所示,对一个事件集的操作包含:创建 / 初始化事件集、发送事件、接收事件、删除 / 脱离事件集。 + +![事件相关接口](figures/06event_ops.png) + +#### 创建和删除事件集 + +当创建一个事件集时,内核首先创建一个事件集控制块,然后对该事件集控制块进行基本的初始化,创建事件集使用下面的函数接口: + +```c +rt_event_t rt_event_create(const char* name, rt_uint8_t flag); +``` + +调用该函数接口时,系统会从对象管理器中分配事件集对象,并初始化这个对象,然后初始化父类 IPC 对象。下表描述了该函数的输入参数与返回值: + + rt_event_create() 的输入参数和返回值 + +|**参数** |**描述** | +|----------------|---------------------------------------------------------------------| +| name | 事件集的名称 | +| flag | 事件集的标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回** | —— | +| RT_NULL | 创建失败 | +| 事件对象的句柄 | 创建成功 | + +系统不再使用 rt_event_create() 创建的事件集对象时,通过删除事件集对象控制块来释放系统资源。删除事件集可以使用下面的函数接口: + +```c +rt_err_t rt_event_delete(rt_event_t event); +``` + +在调用 rt_event_delete 函数删除一个事件集对象时,应该确保该事件集不再被使用。在删除前会唤醒所有挂起在该事件集上的线程(线程的返回值是 - RT_ERROR),然后释放事件集对象占用的内存块。下表描述了该函数的输入参数与返回值: + +rt_event_delete() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------------| +| event | 事件集对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 初始化和脱离事件集 + +静态事件集对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中。在使用静态事件集对象前,需要先行对它进行初始化操作。初始化事件集使用下面的函数接口: + +```c +rt_err_t rt_event_init(rt_event_t event, const char* name, rt_uint8_t flag); +``` + +调用该接口时,需指定静态事件集对象的句柄(即指向事件集控制块的指针),然后系统会初始化事件集对象,并加入到系统对象容器中进行管理。下表描述了该函数的输入参数与返回值: + + rt_event_init() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|---------------------------------------------------------------------| +| event | 事件集对象的句柄 | +| name | 事件集的名称 | +| flag | 事件集的标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回**| —— | +| RT_EOK | 成功 | + +系统不再使用 rt_event_init() 初始化的事件集对象时,通过脱离事件集对象控制块来释放系统资源。脱离事件集是将事件集对象从内核对象管理器中脱离。脱离事件集使用下面的函数接口: + +```c +rt_err_t rt_event_detach(rt_event_t event); +``` + +用户调用这个函数时,系统首先唤醒所有挂在该事件集等待队列上的线程(线程的返回值是 - RT_ERROR),然后将该事件集从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值: + +rt_event_detach() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------------| +| event | 事件集对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 发送事件 + +发送事件函数可以发送事件集中的一个或多个事件,如下: + +```c +rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set); +``` + +使用该函数接口时,通过参数 set 指定的事件标志来设定 event 事件集对象的事件标志值,然后遍历等待在 event 事件集对象上的等待线程链表,判断是否有线程的事件激活要求与当前 event 对象事件标志值匹配,如果有,则唤醒该线程。下表描述了该函数的输入参数与返回值: + +rt_event_send() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------------------------| +| event | 事件集对象的句柄 | +| set | 发送的一个或多个事件的标志值 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 接收事件 + +内核使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件,内核可以通过指定选择参数 “逻辑与” 或“逻辑或”来选择如何激活线程,使用 “逻辑与” 参数表示只有当所有等待的事件都发生时才激活线程,而使用 “逻辑或” 参数则表示只要有一个等待的事件发生就激活线程。接收事件使用下面的函数接口: + +```c +rt_err_t rt_event_recv(rt_event_t event, + rt_uint32_t set, + rt_uint8_t option, + rt_int32_t timeout, + rt_uint32_t* recved); +``` + +当用户调用这个接口时,系统首先根据 set 参数和接收选项 option 来判断它要接收的事件是否发生,如果已经发生,则根据参数 option 上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应标志位,然后返回(其中 recved 参数返回接收到的事件);如果没有发生,则把等待的 set 和 option 参数填入线程本身的结构中,然后把线程挂起在此事件上,直到其等待的事件满足条件或等待时间超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不等待,而直接返回 - RT_ETIMEOUT。下表描述了该函数的输入参数与返回值: + + rt_event_recv() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------|----------------------| +| event | 事件集对象的句柄 | +| set | 接收线程感兴趣的事件 | +| option | 接收选项 | +| timeout | 指定超时时间 | +| recved | 指向接收到的事件 | +|**返回** | —— | +| RT_EOK | 成功 | +| \-RT_ETIMEOUT | 超时 | +| \-RT_ERROR | 错误 | + +option 的值可取: + +```c +/* 选择 逻辑与 或 逻辑或 的方式接收事件 */ +RT_EVENT_FLAG_OR +RT_EVENT_FLAG_AND + +/* 选择清除重置事件标志位 */ +RT_EVENT_FLAG_CLEAR +``` + +### 事件集应用示例 + +这是事件集的应用例程,例子中初始化了一个事件集,两个线程。一个线程等待自己关心的事件发生,另外一个线程发送事件,如代码清单 6-5 例所示: + +事件集的使用例程 + +```c +#include + +#define THREAD_PRIORITY 9 +#define THREAD_TIMESLICE 5 + +#define EVENT_FLAG3 (1 << 3) +#define EVENT_FLAG5 (1 << 5) + +/* 事件控制块 */ +static struct rt_event event; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程 1 入口函数 */ +static void thread1_recv_event(void *param) +{ + rt_uint32_t e; + + /* 第一次接收事件,事件 3 或事件 5 任意一个可以触发线程 1,接收完后清除事件标志 */ + if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &e) == RT_EOK) + { + rt_kprintf("thread1: OR recv event 0x%x\n", e); + } + + rt_kprintf("thread1: delay 1s to prepare the second event\n"); + rt_thread_mdelay(1000); + + /* 第二次接收事件,事件 3 和事件 5 均发生时才可以触发线程 1,接收完后清除事件标志 */ + if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &e) == RT_EOK) + { + rt_kprintf("thread1: AND recv event 0x%x\n", e); + } + rt_kprintf("thread1 leave.\n"); +} + + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程 2 入口 */ +static void thread2_send_event(void *param) +{ + rt_kprintf("thread2: send event3\n"); + rt_event_send(&event, EVENT_FLAG3); + rt_thread_mdelay(200); + + rt_kprintf("thread2: send event5\n"); + rt_event_send(&event, EVENT_FLAG5); + rt_thread_mdelay(200); + + rt_kprintf("thread2: send event3\n"); + rt_event_send(&event, EVENT_FLAG3); + rt_kprintf("thread2 leave.\n"); +} + +int event_sample(void) +{ + rt_err_t result; + + /* 初始化事件对象 */ + result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_kprintf("init event failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_recv_event, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_send_event, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(event_sample, event sample); +``` + +仿真运行结果如下: + +```c + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >event_sample +thread2: send event3 +thread1: OR recv event 0x8 +thread1: delay 1s to prepare the second event +msh >thread2: send event5 +thread2: send event3 +thread2 leave. +thread1: AND recv event 0x28 +thread1 leave. +``` + +例程演示了事件集的使用方法。线程 1 前后两次接收事件,分别使用了 “逻辑或” 与“逻辑与”的方法。 + +### 事件集的使用场合 + +事件集可使用于多种场合,它能够在一定程度上替代信号量,用于线程间同步。一个线程或中断服务例程发送一个事件给事件集对象,而后等待的线程被唤醒并对相应的事件进行处理。但是它与信号量不同的是,事件的发送操作在事件未清除前,是不可累计的,而信号量的释放动作是累计的。事件的另一个特性是,接收线程可等待多种事件,即多个事件对应一个线程或多个线程。同时按照线程等待的参数,可选择是 “逻辑或” 触发还是 “逻辑与” 触发。这个特性也是信号量等所不具备的,信号量只能识别单一的释放动作,而不能同时等待多种类型的释放。如下图所示为多事件接收示意图: + +![多事件接收示意图](figures/06event_use.png) + +一个事件集中包含 32 个事件,特定线程只等待、接收它关注的事件。可以是一个线程等待多个事件的到来(线程 1、2 均等待多个事件,事件间可以使用 “与” 或者 “或” 逻辑触发线程),也可以是多个线程等待一个事件的到来(事件 25)。当有它们关注的事件发生时,线程将被唤醒并进行后续的处理动作。 + + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07mb_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07mb_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..26291ede7b0e01750f4d518986821019f208130c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07mb_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07mb_work.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07mb_work.png new file mode 100644 index 0000000000000000000000000000000000000000..150ac857635c21abce2c268a57fc44ee4292b92e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07mb_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_ops.jpg b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_ops.jpg new file mode 100644 index 0000000000000000000000000000000000000000..06988e38f7f2970e974040a9a531d60dc136255d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_ops.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..ac5969ad91d3fbdc7433c915c4e62e6fbf874dca Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_syn.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_syn.png new file mode 100644 index 0000000000000000000000000000000000000000..6aa3e511aceb3e30029b48fbc73acf9420c699e6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_syn.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_work.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_work.png new file mode 100644 index 0000000000000000000000000000000000000000..cd3ef44310c7a248777d192f24517a5c7a499f69 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07msg_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07signal_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07signal_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..f8f466cf9b7cbd4db6174b5b46807cee1643c920 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07signal_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07signal_work.png b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07signal_work.png new file mode 100644 index 0000000000000000000000000000000000000000..49be0f6fe845806dac5ef297330ff4b220aca57b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/figures/07signal_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2.md b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2.md new file mode 100644 index 0000000000000000000000000000000000000000..824f4a942273677fd67b10a8b41a2742169d5f36 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/ipc2/ipc2.md @@ -0,0 +1,1068 @@ +# 线程间通信 + +前面一章讲了线程间同步,提到了信号量、互斥量、事件集等概念;本章接着上一章的内容,讲解线程间通信。在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取,根据读取到的全局变量值执行相应的动作,达到通信协作的目的。RT-Thread 中则提供了更多的工具帮助在不同的线程中间传递信息,本章会详细介绍这些工具。学习完本章,大家将学会如何将邮箱、消息队列、信号用于线程间的通信。 + +## 邮箱 + +邮箱服务是实时操作系统中一种典型的线程间通信方法。举一个简单的例子,有两个线程,线程 1 检测按键状态并发送,线程 2 读取按键状态并根据按键的状态相应地改变 LED 的亮灭。这里就可以使用邮箱的方式进行通信,线程 1 将按键的状态作为邮件发送到邮箱,线程 2 在邮箱中读取邮件获得按键状态并对 LED 执行亮灭操作。 + +这里的线程 1 也可以扩展为多个线程。例如,共有三个线程,线程 1 检测并发送按键状态,线程 2 检测并发送 ADC 采样信息,线程 3 则根据接收的信息类型不同,执行不同的操作。 + +### 邮箱的工作机制 + +RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。典型的邮箱也称作交换消息,如下图所示,线程或中断服务例程把一封 4 字节长度的邮件发送到邮箱中,而一个或多个线程可以从邮箱中接收这些邮件并进行处理。 + +![邮箱工作示意图](figures/07mb_work.png) + +非阻塞方式的邮件发送过程能够安全的应用于中断服务中,是线程、中断服务、定时器向线程发送消息的有效手段。通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。当邮箱中不存在邮件且超时时间不为 0 时,邮件收取过程将变成阻塞方式。在这类情况下,只能由线程进行邮件的收取。 + +当一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,选择等待挂起或直接返回 - RT_EFULL。如果发送线程选择挂起等待,那么当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送。 + +当一个线程从邮箱中接收邮件时,如果邮箱是空的,接收线程可以选择是否等待挂起直到收到新的邮件而唤醒,或可以设置超时时间。当达到设置的超时时间,邮箱依然未收到邮件时,这个选择超时等待的线程将被唤醒并返回 - RT_ETIMEOUT。如果邮箱中存在邮件,那么接收线程将复制邮箱中的 4 个字节邮件到接收缓存中。 + +### 邮箱控制块 + +在 RT-Thread 中,邮箱控制块是操作系统用于管理邮箱的一个数据结构,由结构体 struct rt_mailbox 表示。另外一种 C 表达方式 rt_mailbox_t,表示的是邮箱的句柄,在 C 语言中的实现是邮箱控制块的指针。邮箱控制块结构的详细定义请见以下代码: + +```c +struct rt_mailbox +{ + struct rt_ipc_object parent; + + rt_uint32_t* msg_pool; /* 邮箱缓冲区的开始地址 */ + rt_uint16_t size; /* 邮箱缓冲区的大小 */ + + rt_uint16_t entry; /* 邮箱中邮件的数目 */ + rt_uint16_t in_offset, out_offset; /* 邮箱缓冲的进出指针 */ + rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ +}; +typedef struct rt_mailbox* rt_mailbox_t; +``` + +rt_mailbox 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。 + +### 邮箱的管理方式 + +邮箱控制块是一个结构体,其中含有事件相关的重要参数,在邮箱的功能实现中起重要的作用。邮箱的相关接口如下图所示,对一个邮箱的操作包含:创建 / 初始化邮箱、发送邮件、接收邮件、删除 / 脱离邮箱。 + +![邮箱相关接口](figures/07mb_ops.png) + +#### 创建和删除邮箱 + +动态创建一个邮箱对象可以调用如下的函数接口: + +```c +rt_mailbox_t rt_mb_create (const char* name, rt_size_t size, rt_uint8_t flag); +``` + +创建邮箱对象时会先从对象管理器中分配一个邮箱对象,然后给邮箱动态分配一块内存空间用来存放邮件,这块内存的大小等于邮件大小(4 字节)与邮箱容量的乘积,接着初始化接收邮件数目和发送邮件在邮箱中的偏移量。下表描述了该函数的输入参数与返回值: + + rt_mb_create() 的输入参数和返回值 + +|**参数** |**描述** | +|----------------|------------------------------------------------------------------| +| name | 邮箱名称 | +| size | 邮箱容量 | +| flag | 邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回** | —— | +| RT_NULL | 创建失败 | +| 邮箱对象的句柄 | 创建成功 | + +当用 rt_mb_create() 创建的邮箱不再被使用时,应该删除它来释放相应的系统资源,一旦操作完成,邮箱将被永久性的删除。删除邮箱的函数接口如下: + +```c +rt_err_t rt_mb_delete (rt_mailbox_t mb); +``` + +删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 - +RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。下表描述了该函数的输入参数与返回值: + + rt_mb_delete() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|----------------| +| mb | 邮箱对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 初始化和脱离邮箱 + +初始化邮箱跟创建邮箱类似,只是初始化邮箱用于静态邮箱对象的初始化。与创建邮箱不同的是,静态邮箱对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中,其余的初始化工作与创建邮箱时相同。函数接口如下: + +```c + rt_err_t rt_mb_init(rt_mailbox_t mb, + const char* name, + void* msgpool, + rt_size_t size, + rt_uint8_t flag) +``` + +初始化邮箱时,该函数接口需要获得用户已经申请获得的邮箱对象控制块,缓冲区的指针,以及邮箱名称和邮箱容量(能够存储的邮件数)。下表描述了该函数的输入参数与返回值: + + rt_mb_init() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|-----------------------------------------------------------------| +| mb | 邮箱对象的句柄 | +| name | 邮箱名称 | +| msgpool | 缓冲区指针 | +| size | 邮箱容量 | +| flag | 邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回**| —— | +| RT_EOK | 成功 | + +这里的 size 参数指定的是邮箱的容量,即如果 msgpool 指向的缓冲区的字节数是 N,那么邮箱容量应该是 N/4。 + +脱离邮箱将把静态初始化的邮箱对象从内核对象管理器中脱离。脱离邮箱使用下面的接口: + +```c +rt_err_t rt_mb_detach(rt_mailbox_t mb); +``` + +使用该函数接口后,内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是 - RT_ERROR),然后将该邮箱对象从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值: + + rt_mb_detach() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|----------------| +| mb | 邮箱对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 发送邮件 + +线程或者中断服务程序可以通过邮箱给其他线程发送邮件,发送邮件函数接口如下: + +```c +rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value); +``` + +发送的邮件可以是 32 位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到 -RT_EFULL 的返回值。下表描述了该函数的输入参数与返回值: + + rt_mb_send() 的输入参数和返回值 + +|**参数** |**描述** | +|------------|----------------| +| mb | 邮箱对象的句柄 | +| value | 邮件内容 | +|**返回** | —— | +| RT_EOK | 发送成功 | +| \-RT_EFULL | 邮箱已经满了 | + +#### 等待方式发送邮件 + +用户也可以通过如下的函数接口向指定邮箱发送邮件: + +```c +rt_err_t rt_mb_send_wait (rt_mailbox_t mb, + rt_uint32_t value, + rt_int32_t timeout); +``` + +rt_mb_send_wait() 与 rt_mb_send() 的区别在于有等待时间,如果邮箱已经满了,那么发送线程将根据设定的 timeout 参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。下表描述了该函数的输入参数与返回值: + + rt_mb_send_wait() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------|----------------| +| mb | 邮箱对象的句柄 | +| value | 邮件内容 | +| timeout | 超时时间 | +|**返回** | —— | +| RT_EOK | 发送成功 | +| \-RT_ETIMEOUT | 超时 | +| \-RT_ERROR | 失败,返回错误 | + +#### 接收邮件 + +只有当接收者接收的邮箱中有邮件时,接收者才能立即取到邮件并返回 RT_EOK 的返回值,否则接收线程会根据超时时间设置,或挂起在邮箱的等待线程队列上,或直接返回。接收邮件函数接口如下: + +```c +rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t* value, rt_int32_t timeout); +``` + +接收邮件时,接收者需指定接收邮件的邮箱句柄,并指定接收到的邮件存放位置以及最多能够等待的超时时间。如果接收时设定了超时,当指定的时间内依然未收到邮件时,将返回 - RT_ETIMEOUT。下表描述了该函数的输入参数与返回值: + + rt_mb_recv() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------|----------------| +| mb | 邮箱对象的句柄 | +| value | 邮件内容 | +| timeout | 超时时间 | +|**返回** | —— | +| RT_EOK | 发送成功 | +| \-RT_ETIMEOUT | 超时 | +| \-RT_ERROR | 失败,返回错误 | + +### 邮箱使用示例 + +这是一个邮箱的应用例程,初始化 2 个静态线程,一个静态的邮箱对象,其中一个线程往邮箱中发送邮件,一个线程往邮箱中收取邮件。如下代码所示: + + 邮箱的使用例程 + +```c +#include + +#define THREAD_PRIORITY 10 +#define THREAD_TIMESLICE 5 + +/* 邮箱控制块 */ +static struct rt_mailbox mb; +/* 用于放邮件的内存池 */ +static char mb_pool[128]; + +static char mb_str1[] = "I'm a mail!"; +static char mb_str2[] = "this is another mail!"; +static char mb_str3[] = "over"; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程 1 入口 */ +static void thread1_entry(void *parameter) +{ + char *str; + + while (1) + { + rt_kprintf("thread1: try to recv a mail\n"); + + /* 从邮箱中收取邮件 */ + if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str); + if (str == mb_str3) + break; + + /* 延时 100ms */ + rt_thread_mdelay(100); + } + } + /* 执行邮箱对象脱离 */ + rt_mb_detach(&mb); +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程 2 入口 */ +static void thread2_entry(void *parameter) +{ + rt_uint8_t count; + + count = 0; + while (count < 10) + { + count ++; + if (count & 0x1) + { + /* 发送 mb_str1 地址到邮箱中 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str1); + } + else + { + /* 发送 mb_str2 地址到邮箱中 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str2); + } + + /* 延时 200ms */ + rt_thread_mdelay(200); + } + + /* 发送邮件告诉线程 1,线程 2 已经运行结束 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str3); +} + +int mailbox_sample(void) +{ + rt_err_t result; + + /* 初始化一个 mailbox */ + result = rt_mb_init(&mb, + "mbt", /* 名称是 mbt */ + &mb_pool[0], /* 邮箱用到的内存池是 mb_pool */ + sizeof(mb_pool) / 4, /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */ + RT_IPC_FLAG_FIFO); /* 采用 FIFO 方式进行线程等待 */ + if (result != RT_EOK) + { + rt_kprintf("init mailbox failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mailbox_sample, mailbox sample); +``` + +仿真运行结果如下: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >mailbox_sample +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:I'm a mail! +msh >thread1: try to recv a mail +thread1: get a mail from mailbox, the content:this is another mail! +… +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:this is another mail! +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:over +``` + +例程演示了邮箱的使用方法。线程 2 发送邮件,共发送 11 次;线程 1 接收邮件,共接收到 11 封邮件,将邮件内容打印出来,并判断结束。 + +### 邮箱的使用场合 + +邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。在 RT-Thread 操作系统的实现中能够一次传递一个 4 字节大小的邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数 (邮件数由创建、初始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是 4 字节,所以邮箱能够用于不超过 4 字节的消息传递。由于在 32 系统上 4 字节的内容恰好可以放置一个指针,因此当需要在线程间传递比较大的消息时,可以把指向一个缓冲区的指针作为邮件发送到邮箱中,即邮箱也可以传递指针,例如: + +```c +struct msg +{ + rt_uint8_t *data_ptr; + rt_uint32_t data_size; +}; +``` + +对于这样一个消息结构体,其中包含了指向数据的指针 data_ptr 和数据块长度的变量 data_size。当一个线程需要把这个消息发送给另外一个线程时,可以采用如下的操作: + +```c +struct msg* msg_ptr; + +msg_ptr = (struct msg*)rt_malloc(sizeof(struct msg)); +msg_ptr->data_ptr = ...; /* 指向相应的数据块地址 */ +msg_ptr->data_size = len; /* 数据块的长度 */ +/* 发送这个消息指针给 mb 邮箱 */ +rt_mb_send(mb, (rt_uint32_t)msg_ptr); +``` + +而在接收线程中,因为收取过来的是指针,而 msg_ptr 是一个新分配出来的内存块,所以在接收线程处理完毕后,需要释放相应的内存块: + +```c +struct msg* msg_ptr; +if (rt_mb_recv(mb, (rt_uint32_t*)&msg_ptr) == RT_EOK) +{ + /* 在接收线程处理完毕后,需要释放相应的内存块 */ + rt_free(msg_ptr); +} +``` + +消息队列 +-------- + +消息队列是另一种常用的线程间通讯方式,是邮箱的扩展。可以应用在多种场合:线程间的消息交换、使用串口接收不定长数据等。 + +### 消息队列的工作机制 + +消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。 + +如下图所示,线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则 (FIFO)。 + +![消息队列工作示意图](figures/07msg_work.png) + +RT-Thread 操作系统的消息队列对象由多个元素组成,当消息队列被创建时,它就被分配了消息队列控制块:消息队列名称、内存缓冲区、消息大小以及队列长度等。同时每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息;消息队列中的第一个和最后一个消息框被分别称为消息链表头和消息链表尾,对应于消息队列控制块中的 msg_queue_head 和 msg_queue_tail;有些消息框可能是空的,它们通过 msg_queue_free 形成一个空闲消息框链表。所有消息队列中的消息框总数即是消息队列的长度,这个长度可在消息队列创建时指定。 + +### 消息队列控制块 + +在 RT-Thread 中,消息队列控制块是操作系统用于管理消息队列的一个数据结构,由结构体 struct rt_messagequeue 表示。另外一种 C 表达方式 rt_mq_t,表示的是消息队列的句柄,在 C 语言中的实现是消息队列控制块的指针。消息队列控制块结构的详细定义请见以下代码: + +```c +struct rt_messagequeue +{ + struct rt_ipc_object parent; + + void* msg_pool; /* 指向存放消息的缓冲区的指针 */ + + rt_uint16_t msg_size; /* 每个消息的长度 */ + rt_uint16_t max_msgs; /* 最大能够容纳的消息数 */ + + rt_uint16_t entry; /* 队列中已有的消息数 */ + + void* msg_queue_head; /* 消息链表头 */ + void* msg_queue_tail; /* 消息链表尾 */ + void* msg_queue_free; /* 空闲消息链表 */ + + rt_list_t suspend_sender_thread; /* 发送线程的挂起等待队列 */ +}; +typedef struct rt_messagequeue* rt_mq_t; +``` + +rt_messagequeue 对象从 rt_ipc_object 中派生,由 IPC 容器所管理。 + +### 消息队列的管理方式 + +消息队列控制块是一个结构体,其中含有消息队列相关的重要参数,在消息队列的功能实现中起重要的作用。消息队列的相关接口如下图所示,对一个消息队列的操作包含:创建消息队列 - 发送消息 - 接收消息 - 删除消息队列。 + +![消息队列相关接口](figures/07msg_ops.png) + +#### 创建和删除消息队列 + +消息队列在使用前,应该被创建出来,或对已有的静态消息队列对象进行初始化,创建消息队列的函数接口如下所示: + +```c +rt_mq_t rt_mq_create(const char* name, rt_size_t msg_size, + rt_size_t max_msgs, rt_uint8_t flag); +``` + +创建消息队列时先从对象管理器中分配一个消息队列对象,然后给消息队列对象分配一块内存空间,组织成空闲消息链表,这块内存的大小 =[消息大小 + 消息头(用于链表连接)的大小]X 消息队列最大个数,接着再初始化消息队列,此时消息队列为空。下表描述了该函数的输入参数与返回值: + + rt_mq_create() 的输入参数和返回值 + +|**参数** |**描述** | +|--------------------|-------------------------------------------------------------------------------| +| name | 消息队列的名称 | +| msg_size | 消息队列中一条消息的最大长度,单位字节 | +| max_msgs | 消息队列的最大个数 | +| flag | 消息队列采用的等待方式,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回** | —— | +| RT_EOK | 发送成功 | +| 消息队列对象的句柄 | 成功 | +| RT_NULL | 失败 | + +当消息队列不再被使用时,应该删除它以释放系统资源,一旦操作完成,消息队列将被永久性地删除。删除消息队列的函数接口如下: + +```c +rt_err_t rt_mq_delete(rt_mq_t mq); +``` + +删除消息队列时,如果有线程被挂起在该消息队列等待队列上,则内核先唤醒挂起在该消息等待队列上的所有线程(线程返回值是 - RT_ERROR),然后再释放消息队列使用的内存,最后删除消息队列对象。下表描述了该函数的输入参数与返回值: + + rt_mq_delete() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|--------------------| +| mq | 消息队列对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 初始化和脱离消息队列 + +初始化静态消息队列对象跟创建消息队列对象类似,只是静态消息队列对象的内存是在系统编译时由编译器分配的,一般放于读数据段或未初始化数据段中。在使用这类静态消息队列对象前,需要进行初始化。初始化消息队列对象的函数接口如下: + +```c +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_mq_init() 的输入参数和返回值 + +|**参数** |**描述** | +|-----------|-------------------------------------------------------------------------------| +| mq | 消息队列对象的句柄 | +| name | 消息队列的名称 | +| msgpool | 指向存放消息的缓冲区的指针 | +| msg_size | 消息队列中一条消息的最大长度,单位字节 | +| pool_size | 存放消息的缓冲区大小 | +| flag | 消息队列采用的等待方式,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO | +|**返回** | —— | +| RT_EOK | 成功 | + +脱离消息队列将使消息队列对象被从内核对象管理器中脱离。脱离消息队列使用下面的接口: + +```c +rt_err_t rt_mq_detach(rt_mq_t mq); +``` + +使用该函数接口后,内核先唤醒所有挂在该消息等待队列对象上的线程(线程返回值是 -RT_ERROR),然后将该消息队列对象从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值: + + rt_mq_detach() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|--------------------| +| mq | 消息队列对象的句柄 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 发送消息 + +线程或者中断服务程序都可以给消息队列发送消息。当发送消息时,消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。发送消息的函数接口如下: + +```c +rt_err_t rt_mq_send (rt_mq_t mq, void* buffer, rt_size_t size); +``` + +发送消息时,发送者需指定发送的消息队列的对象句柄(即指向消息队列控制块的指针),并且指定发送的消息内容以及消息大小。如下图所示,在发送一个普通消息之后,空闲消息链表上的队首消息被转移到了消息队列尾。下表描述了该函数的输入参数与返回值: + + rt_mq_send() 的输入参数和返回值 + +|**参数** |**描述** | +|------------|------------------------------------------------------| +| mq | 消息队列对象的句柄 | +| buffer | 消息内容 | +| size | 消息大小 | +|**返回** | —— | +| RT_EOK | 成功 | +| \-RT_EFULL | 消息队列已满 | +| \-RT_ERROR | 失败,表示发送的消息长度大于消息队列中消息的最大长度 | + +#### 等待方式发送消息 + +用户也可以通过如下的函数接口向指定的消息队列中发送消息: + +```c +rt_err_t rt_mq_send_wait(rt_mq_t mq, + const void *buffer, + rt_size_t size, + rt_int32_t timeout); +``` + +rt_mq_send_wait() 与 rt_mq_send() 的区别在于有等待时间,如果消息队列已经满了,那么发送线程将根据设定的 timeout 参数进行等待。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。下表描述了该函数的输入参数与返回值: + + rt_mq_send_wait() 的输入参数和返回值 + +|**参数** |**描述** | +|------------|------------------------------------------------------| +| mq | 消息队列对象的句柄 | +| buffer | 消息内容 | +| size | 消息大小 | +| timeout | 超时时间 | +|**返回** | —— | +| RT_EOK | 成功 | +| \-RT_EFULL | 消息队列已满 | +| \-RT_ERROR | 失败,表示发送的消息长度大于消息队列中消息的最大长度 | + +#### 发送紧急消息 + +发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。发送紧急消息的函数接口如下: + +```c +rt_err_t rt_mq_urgent(rt_mq_t mq, void* buffer, rt_size_t size); +``` + +下表描述了该函数的输入参数与返回值: + + rt_mq_urgent() 的输入参数和返回值 + +|**参数** |**描述** | +|------------|--------------------| +| mq | 消息队列对象的句柄 | +| buffer | 消息内容 | +| size | 消息大小 | +|**返回** | —— | +| RT_EOK | 成功 | +| \-RT_EFULL | 消息队列已满 | +| \-RT_ERROR | 失败 | + +#### 接收消息 + +当消息队列中有消息时,接收者才能接收消息,否则接收者会根据超时时间设置,或挂起在消息队列的等待线程队列上,或直接返回。接收消息函数接口如下: + +```c +rt_err_t rt_mq_recv (rt_mq_t mq, void* buffer, + rt_size_t size, rt_int32_t timeout); +``` + +接收消息时,接收者需指定存储消息的消息队列对象句柄,并且指定一个内存缓冲区,接收到的消息内容将被复制到该缓冲区里。此外,还需指定未能及时取到消息时的超时时间。如下图所示,接收一个消息后消息队列上的队首消息被转移到了空闲消息链表的尾部。下表描述了该函数的输入参数与返回值: + + rt_mq_recv() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------|--------------------| +| mq | 消息队列对象的句柄 | +| buffer | 消息内容 | +| size | 消息大小 | +| timeout | 指定的超时时间 | +|**返回** | —— | +| RT_EOK | 成功收到 | +| \-RT_ETIMEOUT | 超时 | +| \-RT_ERROR | 失败,返回错误 | + +### 消息队列应用示例 + +这是一个消息队列的应用例程,例程中初始化了 2 个静态线程,一个线程会从消息队列中收取消息;另一个线程会定时给消息队列发送普通消息和紧急消息,如下代码所示: + + 消息队列的使用例程 + +```c +#include + +/* 消息队列控制块 */ +static struct rt_messagequeue mq; +/* 消息队列中用到的放置消息的内存池 */ +static rt_uint8_t msg_pool[2048]; + +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; +/* 线程 1 入口函数 */ +static void thread1_entry(void *parameter) +{ + char buf = 0; + rt_uint8_t cnt = 0; + + while (1) + { + /* 从消息队列中接收消息 */ + if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf); + if (cnt == 19) + { + break; + } + } + /* 延时 50ms */ + cnt++; + rt_thread_mdelay(50); + } + rt_kprintf("thread1: detach mq \n"); + rt_mq_detach(&mq); +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; +/* 线程 2 入口 */ +static void thread2_entry(void *parameter) +{ + int result; + char buf = 'A'; + rt_uint8_t cnt = 0; + + while (1) + { + if (cnt == 8) + { + /* 发送紧急消息到消息队列中 */ + result = rt_mq_urgent(&mq, &buf, 1); + if (result != RT_EOK) + { + rt_kprintf("rt_mq_urgent ERR\n"); + } + else + { + rt_kprintf("thread2: send urgent message - %c\n", buf); + } + } + else if (cnt>= 20)/* 发送 20 次消息之后退出 */ + { + rt_kprintf("message queue stop send, thread2 quit\n"); + break; + } + else + { + /* 发送消息到消息队列中 */ + result = rt_mq_send(&mq, &buf, 1); + if (result != RT_EOK) + { + rt_kprintf("rt_mq_send ERR\n"); + } + + rt_kprintf("thread2: send message - %c\n", buf); + } + buf++; + cnt++; + /* 延时 5ms */ + rt_thread_mdelay(5); + } +} + +/* 消息队列示例的初始化 */ +int msgq_sample(void) +{ + rt_err_t result; + + /* 初始化消息队列 */ + result = rt_mq_init(&mq, + "mqt", + &msg_pool[0], /* 内存池指向 msg_pool */ + 1, /* 每个消息的大小是 1 字节 */ + sizeof(msg_pool), /* 内存池的大小是 msg_pool 的大小 */ + RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */ + + if (result != RT_EOK) + { + rt_kprintf("init message queue failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), 25, 5); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), 25, 5); + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(msgq_sample, msgq sample); +``` + +仿真运行结果如下: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh > msgq_sample +msh >thread2: send message - A +thread1: recv msg from msg queue, the content:A +thread2: send message - B +thread2: send message - C +thread2: send message - D +thread2: send message - E +thread1: recv msg from msg queue, the content:B +thread2: send message - F +thread2: send message - G +thread2: send message - H +thread2: send urgent message - I +thread2: send message - J +thread1: recv msg from msg queue, the content:I +thread2: send message - K +thread2: send message - L +thread2: send message - M +thread2: send message - N +thread2: send message - O +thread1: recv msg from msg queue, the content:C +thread2: send message - P +thread2: send message - Q +thread2: send message - R +thread2: send message - S +thread2: send message - T +thread1: recv msg from msg queue, the content:D +message queue stop send, thread2 quit +thread1: recv msg from msg queue, the content:E +thread1: recv msg from msg queue, the content:F +thread1: recv msg from msg queue, the content:G +… +thread1: recv msg from msg queue, the content:T +thread1: detach mq +``` + +例程演示了消息队列的使用方法。线程 1 会从消息队列中收取消息;线程 2 定时给消息队列发送普通消息和紧急消息。由于线程 2 发送消息 “I” 是紧急消息,会直接插入消息队列的队首,所以线程 1 在接收到消息 “B” 后,接收的是该紧急消息,之后才接收消息“C”。 + +### 消息队列的使用场合 + +消息队列可以应用于发送不定长消息的场合,包括线程与线程间的消息交换,以及中断服务例程中给线程发送消息(中断服务例程不能接收消息)。下面分发送消息和同步消息两部分来介绍消息队列的使用。 + +#### 发送消息 + +消息队列和邮箱的明显不同是消息的长度并不限定在 4 个字节以内;另外,消息队列也包括了一个发送紧急消息的函数接口。但是当创建的是一个所有消息的最大长度是 4 字节的消息队列时,消息队列对象将蜕化成邮箱。这个不限定长度的消息,也及时的反应到了代码编写的场合上,同样是类似邮箱的代码: + +```c +struct msg +{ + rt_uint8_t *data_ptr; /* 数据块首地址 */ + rt_uint32_t data_size; /* 数据块大小 */ +}; +``` + +和邮箱例子相同的消息结构定义,假设依然需要发送这样一个消息给接收线程。在邮箱例子中,这个结构只能够发送指向这个结构的指针(在函数指针被发送过去后,接收线程能够正确的访问指向这个地址的内容,通常这块数据需要留给接收线程来释放)。而使用消息队列的方式则大不相同: + +```c +void send_op(void *data, rt_size_t length) +{ + struct msg msg_ptr; + + msg_ptr.data_ptr = data; /* 指向相应的数据块地址 */ + msg_ptr.data_size = length; /* 数据块的长度 */ + + /* 发送这个消息指针给 mq 消息队列 */ + rt_mq_send(mq, (void*)&msg_ptr, sizeof(struct msg)); +} +``` + +注意,上面的代码中,是把一个局部变量的数据内容发送到了消息队列中。在接收线程中,同样也采用局部变量进行消息接收的结构体: + +```c +void message_handler() +{ + struct msg msg_ptr; /* 用于放置消息的局部变量 */ + + /* 从消息队列中接收消息到 msg_ptr 中 */ + if (rt_mq_recv(mq, (void*)&msg_ptr, sizeof(struct msg), RT_WAITING_FOREVER) == RT_EOK) + { + /* 成功接收到消息,进行相应的数据处理 */ + } +} +``` + +因为消息队列是直接的数据内容复制,所以在上面的例子中,都采用了局部变量的方式保存消息结构体,这样也就免去动态内存分配的烦恼了(也就不用担心,接收线程在接收到消息时,消息内存空间已经被释放)。 + +#### 同步消息 + +在一般的系统设计中会经常遇到要发送同步消息的问题,这个时候就可以根据当时状态的不同选择相应的实现:两个线程间可以采用**[消息队列 + 信号量或邮箱]**的形式实现。发送线程通过消息发送的形式发送相应的消息给消息队列,发送完毕后希望获得接收线程的收到确认,工作示意图如下图所示: + +![同步消息示意图](figures/07msg_syn.png) + +根据消息确认的不同,可以把消息结构体定义成: + +```c +struct msg +{ + /* 消息结构其他成员 */ + struct rt_mailbox ack; +}; +/* 或者 */ +struct msg +{ + /* 消息结构其他成员 */ + struct rt_semaphore ack; +}; +``` + +第一种类型的消息使用了邮箱来作为确认标志,而第二种类型的消息采用了信号量来作为确认标志。邮箱作为确认标志,代表着接收线程能够通知一些状态值给发送线程;而信号量作为确认标志只能够单一的通知发送线程,消息已经确认接收。 + +信号 +---- + +信号(又称为软中断信号),在软件层次上是对中断机制的一种模拟,在原理上,一个线程收到一个信号与处理器收到一个中断请求可以说是类似的。 + +### 信号的工作机制 + +信号在 RT-Thread 中用作异步通信,POSIX 标准定义了 sigset_t 类型来定义一个信号集,然而 sigset_t 类型在不同的系统可能有不同的定义方式,在 RT-Thread 中,将 sigset_t 定义成了 unsigned long 型,并命名为 rt_sigset_t,应用程序能够使用的信号为 SIGUSR1(10)和 SIGUSR2(12)。 + +信号本质是软中断,用来通知线程发生了异步事件,用做线程之间的异常通知、应急处理。一个线程不必通过任何操作来等待信号的到达,事实上,线程也不知道信号到底什么时候到达,线程之间可以互相通过调用 rt_thread_kill() 发送软中断信号。 + +收到信号的线程对各种信号有不同的处理方法,处理方法可以分为三类: + +第一种是类似中断的处理程序,对于需要处理的信号,线程可以指定处理函数,由该函数来处理。 + +第二种方法是,忽略某个信号,对该信号不做任何处理,就像未发生过一样。 + +第三种方法是,对该信号的处理保留系统的默认值。 + +如下图所示,假设线程 1 需要对信号进行处理,首先线程 1 安装一个信号并解除阻塞,并在安装的同时设定了对信号的异常处理方式;然后其他线程可以给线程 1 发送信号,触发线程 1 对该信号的处理。 + +![信号工作机制](figures/07signal_work.png) + +当信号被传递给线程 1 时,如果它正处于挂起状态,那会把状态改为就绪状态去处理对应的信号。如果它正处于运行状态,那么会在它当前的线程栈基础上建立新栈帧空间去处理对应的信号,需要注意的是使用的线程栈大小也会相应增加。 + +### 信号的管理方式 + +对于信号的操作,有以下几种:安装信号、阻塞信号、阻塞解除、信号发送、信号等待。信号的接口详见下图: + +![信号相关接口](figures/07signal_ops.png) + +#### 安装信号 + +如果线程要处理某一信号,那么就要在线程中安装该信号。安装信号主要用来确定信号值及线程针对该信号值的动作之间的映射关系,即线程将要处理哪个信号,该信号被传递给线程时,将执行何种操作。详细定义请见以下代码: + +```c +rt_sighandler_t rt_signal_install(int signo, rt_sighandler_t[] handler); +``` + +其中 rt_sighandler_t 是定义信号处理函数的函数指针类型。下表描述了该函数的输入参数与返回值: + + rt_signal_install() 的输入参数和返回值 + +|**参数** |**描述** | +|-----------------------|--------------------------------------------------------| +| signo | 信号值(只有 SIGUSR1 和 SIGUSR2 是开放给用户使用的,下同) | +| handler | 设置对信号值的处理方式 | +|**返回** | —— | +| SIG_ERR | 错误的信号 | +| 安装信号前的 handler 值 | 成功 | + +在信号安装时设定 handler 参数,决定了该信号的不同的处理方法。处理方法可以分为三种: + +1)类似中断的处理方式,参数指向当信号发生时用户自定义的处理函数,由该函数来处理。 + +2)参数设为 SIG_IGN,忽略某个信号,对该信号不做任何处理,就像未发生过一样。 + +3)参数设为 SIG_DFL,系统会调用默认的处理函数_signal_default_handler()。 + +#### 阻塞信号 + +信号阻塞,也可以理解为屏蔽信号。如果该信号被阻塞,则该信号将不会递达给安装此信号的线程,也不会引发软中断处理。调 rt_signal_mask() 可以使信号阻塞: + +```c +void rt_signal_mask(int signo); +``` + +下表描述了该函数的输入参数: + + rt_signal_mask() 函数参数 + +|**参数**|**描述**| +|----------|----------| +| signo | 信号值 | + +#### 解除信号阻塞 + +线程中可以安装好几个信号,使用此函数可以对其中一些信号给予 “关注”,那么发送这些信号都会引发该线程的软中断。调用 rt_signal_unmask() 可以用来解除信号阻塞: + +```c +void rt_signal_unmask(int signo); +``` + +下表描述了该函数的输入参数: + + rt_signal_unmask() 函数参数 + +|**参数**|**描述**| +|----------|----------| +| signo | 信号值 | + +#### 发送信号 + +当需要进行异常处理时,可以给设定了处理异常的线程发送信号,调用 rt_thread_kill() 可以用来向任何线程发送信号: + +```c +int rt_thread_kill(rt_thread_t tid, int sig); +``` + +下表描述了该函数的输入参数与返回值: + + rt_thread_kill() 的输入参数和返回值 + +|**参数** |**描述** | +|-------------|----------------| +| tid | 接收信号的线程 | +| sig | 信号值 | +|**返回** | —— | +| RT_EOK | 发送成功 | +| \-RT_EINVAL | 参数错误 | + +#### 等待信号 + +等待 set 信号的到来,如果没有等到这个信号,则将线程挂起,直到等到这个信号或者等待时间超过指定的超时时间 timeout。如果等到了该信号,则将指向该信号体的指针存入 si,如下是等待信号的函数。 + +```c +int rt_signal_wait(const rt_sigset_t *set, + rt_siginfo_t[] *si, rt_int32_t timeout); +``` + +其中 rt_siginfo_t 是定义信号信息的数据类型,下表描述了该函数的输入参数与返回值: + + rt_signal_wait() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------|----------------------------| +| set | 指定等待的信号 | +| si | 指向存储等到信号信息的指针 | +| timeout | 指定的等待时间 | +|**返回** | —— | +| RT_EOK | 等到信号 | +| \-RT_ETIMEOUT | 超时 | +| \-RT_EINVAL | 参数错误 | + +### 信号应用示例 + +这是一个信号的应用例程,如下代码所示。此例程创建了 1 个线程,在安装信号时,信号处理方式设为自定义处理,定义的信号的处理函数为 thread1_signal_handler()。待此线程运行起来安装好信号之后,给此线程发送信号。此线程将接收到信号,并打印信息。 + + 信号使用例程 + +```c +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static rt_thread_t tid1 = RT_NULL; + +/* 线程 1 的信号处理函数 */ +void thread1_signal_handler(int sig) +{ + rt_kprintf("thread1 received signal %d\n", sig); +} + +/* 线程 1 的入口函数 */ +static void thread1_entry(void *parameter) +{ + int cnt = 0; + + /* 安装信号 */ + rt_signal_install(SIGUSR1, thread1_signal_handler); + rt_signal_unmask(SIGUSR1); + + /* 运行 10 次 */ + while (cnt < 10) + { + /* 线程 1 采用低优先级运行,一直打印计数值 */ + rt_kprintf("thread1 count : %d\n", cnt); + + cnt++; + rt_thread_mdelay(100); + } +} + +/* 信号示例的初始化 */ +int signal_sample(void) +{ + /* 创建线程 1 */ + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + rt_thread_mdelay(300); + + /* 发送信号 SIGUSR1 给线程 1 */ + rt_thread_kill(tid1, SIGUSR1); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(signal_sample, signal sample); +``` + +仿真运行结果如下: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >signal_sample +thread1 count : 0 +thread1 count : 1 +thread1 count : 2 +msh >thread1 received signal 10 +thread1 count : 3 +thread1 count : 4 +thread1 count : 5 +thread1 count : 6 +thread1 count : 7 +thread1 count : 8 +thread1 count : 9 +``` + +例程中,首先线程安装信号并解除阻塞,然后发送信号给线程。线程接收到信号并打印出了接收到的信号:SIGUSR1(10)。 + + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08Memory_distribution.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08Memory_distribution.png new file mode 100644 index 0000000000000000000000000000000000000000..434c04c9bc144cfa0ad766b03f0e3d1fe4f93422 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08Memory_distribution.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08heap_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08heap_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..e85cac976385bf6fb9f4f6f15dd004b37b163c7b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08heap_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08memheap.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08memheap.png new file mode 100644 index 0000000000000000000000000000000000000000..fc93ffe436bcbad46cf9083633b0aa34b3ef1725 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08memheap.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool.png new file mode 100644 index 0000000000000000000000000000000000000000..f0abb093150948914c0d2a7c2691ca730e8fe71b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..b49ed6586670edaafb80732da35b9b85810bf1aa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool_work.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool_work.png new file mode 100644 index 0000000000000000000000000000000000000000..db14bcd0f8c9e57a2866c9c906053c40e9ed04b0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08mempool_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08slab.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08slab.png new file mode 100644 index 0000000000000000000000000000000000000000..a16da39406715bdd2464f23f5d62a3e621d50545 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08slab.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work.png new file mode 100644 index 0000000000000000000000000000000000000000..1c7485fd45d27b2b078b609a43213e7510f35c1b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work2.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work2.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc17d6d0b58df2813c8f2f0421d764c06be5971 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work3.png b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work3.png new file mode 100644 index 0000000000000000000000000000000000000000..e2e6752fa05176da5059c6860ee3396a59a89c07 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/memory/figures/08smem_work3.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/memory/memory.md b/rt-thread-version/rt-thread-standard/programming-manual/memory/memory.md new file mode 100644 index 0000000000000000000000000000000000000000..81ca0637ec6fd564845574982270e1262dcfba59 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/memory/memory.md @@ -0,0 +1,665 @@ +# 内存管理 + +在计算系统中,通常存储空间可以分为两种:内部存储空间和外部存储空间。内部存储空间通常访问速度比较快,能够按照变量地址随机地访问,也就是我们通常所说的 RAM(随机存储器),可以把它理解为电脑的内存;而外部存储空间内所保存的内容相对来说比较固定,即使掉电后数据也不会丢失,这就是通常所讲的 ROM(只读存储器),可以把它理解为电脑的硬盘。 + +计算机系统中,变量、中间数据一般存放在 RAM 中,只有在实际使用时才将它们从 RAM 调入到 CPU 中进行运算。一些数据需要的内存大小需要在程序运行过程中根据实际情况确定,这就要求系统具有对内存空间进行动态管理的能力,在用户需要一段内存空间时,向系统申请,系统选择一段合适的内存空间分配给用户,用户使用完毕后,再释放回系统,以便系统将该段内存空间回收再利用。 + +这章主要介绍 RT-Thread 中的两种内存管理方式,分别是动态内存堆管理和静态内存池管理,学完本章,读者会了解 RT-Thread 的内存管理原理及使用方式。 + +## 内存管理的功能特点 + +由于实时系统中对时间的要求非常严格,内存管理往往要比通用操作系统要求苛刻得多: + +1)分配内存的时间必须是确定的。一般内存管理算法是根据需要存储的数据的长度在内存中去寻找一个与这段数据相适应的空闲内存块,然后将数据存储在里面。而寻找这样一个空闲内存块所耗费的时间是不确定的,因此对于实时系统来说,这就是不可接受的,实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。 + +2)随着内存不断被分配和释放,整个内存区域会产生越来越多的碎片(因为在使用过程中,申请了一些内存,其中一些释放了,导致内存空间中存在一些小的内存块,它们地址不连续,不能够作为一整块的大内存分配出去),系统中还有足够的空闲内存,但因为它们地址并非连续,不能组成一块连续的完整内存块,会使得程序不能申请到大的内存。对于通用系统而言,这种不恰当的内存分配算法可以通过重新启动系统来解决 +(每个月或者数个月进行一次),但是对于那些需要常年不间断地工作于野外的嵌入式系统来说,就变得让人无法接受了。 + +3)嵌入式系统的资源环境也是不尽相同,有些系统的资源比较紧张,只有数十 KB 的内存可供分配,而有些系统则存在数 MB 的内存,如何为这些不同的系统,选择适合它们的高效率的内存分配算法,就将变得复杂化。 + +RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况: + +第一种是针对小内存块的分配管理(小内存管理算法); + +第二种是针对大内存块的分配管理(slab 管理算法); + +第三种是针对多内存堆的分配情况(memheap 管理算法) + +## 内存堆管理 + +内存堆管理用于管理一段连续的内存空间,在第三章中介绍过 RT-Thread 的内存分布情况,如下图所示,RT-Thread 将 “ZI 段结尾处” 到内存尾部的空间用作内存堆。 + +![RT-Thread 内存分布](figures/08Memory_distribution.png) + +内存堆可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。而当用户不需要再使用这些内存块时,又可以释放回堆中供其他应用分配使用。RT-Thread +系统为了满足不同的需求,提供了不同的内存管理算法,分别是小内存管理算法、slab 管理算法和 memheap 管理算法。 + +小内存管理算法主要针对系统资源比较少,一般用于小于 2MB 内存空间的系统;而 slab 内存管理算法则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。除上述之外,RT-Thread 还有一种针对多内存堆的管理算法,即 memheap 管理算法。memheap 方法适用于系统存在多个内存堆的情况,它可以将多个内存 “粘贴” 在一起,形成一个大的内存堆,用户使用起来会非常方便。 + +这几类内存堆管理算法在系统运行时只能选择其中之一或者完全不使用内存堆管理器,他们提供给应用程序的 API 接口完全相同。 + +> [!NOTE] +> 注:因为内存堆管理器要满足多线程情况下的安全分配,会考虑多线程间的互斥问题,所以请不要在中断服务例程中分配或释放动态内存块。因为它可能会引起当前上下文被挂起等待。 + +### 小内存管理算法 + +小内存管理算法是一个简单的内存分配算法。初始时,它是一块大的内存。当需要分配内存块时,将从这个大的内存块上分割出相匹配的内存块,然后把分割出来的空闲内存块还回给堆管理系统中。每个内存块都包含一个管理用的数据头,通过这个头把使用块与空闲块用双向链表的方式链接起来,如下图所示: + +![小内存管理工作机制图](figures/08smem_work.png) + +每个内存块(不管是已分配的内存块还是空闲的内存块)都包含一个数据头,其中包括: + +**1)magic**:变数(或称为幻数),它会被初始化成 +0x1ea0(即英文单词 heap),用于标记这个内存块是一个内存管理用的内存数据块;变数不仅仅用于标识这个数据块是一个内存管理用的内存数据块,实质也是一个内存保护字:如果这个区域被改写,那么也就意味着这块内存块被非法改写(正常情况下只有内存管理器才会去碰这块内存)。 + +**2)used**:指示出当前内存块是否已经分配。 + +内存管理的表现主要体现在内存的分配与释放上,小型内存管理算法可以用以下例子体现出来。 + +如下图所示的内存分配情况,空闲链表指针 lfree 初始指向 32 字节的内存块。当用户线程要再分配一个 64 字节的内存块时,但此 lfree 指针指向的内存块只有 32 字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128 字节时,它满足分配的要求。因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52 字节)继续留在 lfree 链表中,如下图分配 64 字节后的链表结构所示。 + +![小内存管理算法链表结构示意图 1](figures/08smem_work2.png) + +![小内存管理算法链表结构示意图 2](figures/08smem_work3.png) + +另外,在每次分配内存块前,都会留出 12 字节数据头用于 magic、used 信息及链表节点使用。返回给应用的地址实际上是这块内存块 12 字节以后的地址,前面的 12 字节数据头是用户永远不应该碰的部分(注:12 字节数据头长度会与系统对齐差异而有所不同)。 + +释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。 + +### slab 管理算法 + +RT-Thread 的 slab 分配器是在 DragonFly BSD 创始人 Matthew Dillon 实现的 slab 分配器基础上,针对嵌入式系统优化的内存分配算法。最原始的 slab 算法是 Jeff Bonwick 为 Solaris 操作系统而引入的一种高效内核内存分配算法。 + +RT-Thread 的 slab 分配器实现主要是去掉了其中的对象构造及析构过程,只保留了纯粹的缓冲型的内存池算法。slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示: + +![slab 内存分配结构图](figures/08slab.png) + +一个 zone 的大小在 32K 到 128K 字节之间,分配器会在堆初始化时根据堆的大小自动调整。系统中的 zone 最多包括 72 种对象,一次最大能够分配 16K 的内存空间,如果超出了 16K 那么直接从页分配器中分配。每个 zone 上分配的内存块大小是固定的,能够分配相同大小内存块的 zone 会链接在一个链表中,而 72 种对象的 zone 链表则放在一个数组(zone_array[])中统一管理。 + +下面是内存分配器主要的两种操作: + +**(1)内存分配** + +假设分配一个 32 字节的内存,slab 内存分配器会先按照 32 字节的值,从 zone array 链表表头数组中找到相应的 zone 链表。如果这个链表是空的,则向页分配器分配一个新的 zone,然后从 zone 中返回第一个空闲内存块。如果链表非空,则这个 zone 链表中的第一个 zone 节点必然有空闲块存在(否则它就不应该放在这个链表中),那么就取相应的空闲块。如果分配完成后,zone 中所有空闲内存块都使用完毕,那么分配器需要把这个 zone 节点从链表中删除。 + +**(2)内存释放** + +分配器需要找到内存块所在的 zone 节点,然后把内存块链接到 zone 的空闲内存块链表中。如果此时 zone 的空闲链表指示出 zone 的所有内存块都已经释放,即 zone 是完全空闲的,那么当 zone 链表中全空闲 zone 达到一定数目后,系统就会把这个全空闲的 zone 释放到页面分配器中去。 + +### memheap 管理算法 + +memheap 管理算法适用于系统含有多个地址可不连续的内存堆。使用 memheap 内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的 memheap 初始化,并开启 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。 + +> [!NOTE] +> 注:在开启 memheap 之后原来的 heap 功能将被关闭,两者只可以通过打开或关闭 RT_USING_MEMHEAP_AS_HEAP 来选择其一 + +memheap 工作机制如下图所示,首先将多块内存加入 memheap_item 链表进行粘合。当分配内存块时,会先从默认内存堆去分配内存,当分配不到时会查找 memheap_item 链表,尝试从其他的内存堆上分配内存块。应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆。 + +![memheap 处理多内存堆](figures/08memheap.png) + +### 内存堆配置和初始化 + +在使用内存堆时,必须要在系统初始化的时候进行堆的初始化,可以通过下面的函数接口完成: + +```c +void rt_system_heap_init(void* begin_addr, void* end_addr); +``` + +这个函数会把参数 begin_addr,end_addr 区域的内存空间作为内存堆来使用。下表描述了该函数的输入参数: + + rt_system_heap_init() 的输入参数 + +|**参数** |**描述** | +|------------|--------------------| +| begin_addr | 堆内存区域起始地址 | +| end_addr | 堆内存区域结束地址 | + +在使用 memheap 堆内存时,必须要在系统初始化的时候进行堆内存的初始化,可以通过下面的函数接口完成: + +```c +rt_err_t rt_memheap_init(struct rt_memheap *memheap, + const char *name, + void *start_addr, + rt_uint32_t size) +``` + +如果有多个不连续的 memheap 可以多次调用该函数将其初始化并加入 memheap_item 链表。下表描述了该函数的输入参数与返回值: + + rt_memheap_init() 的输入参数与返回值 + +|**参数** |**描述** | +|------------|--------------------| +| memheap | memheap 控制块 | +| name | 内存堆的名称 | +| start_addr | 堆内存区域起始地址 | +| size | 堆内存大小 | +|**返回** | —— | +| RT_EOK | 成功 | + +### 内存堆的管理方式 + +对内存堆的操作如下图所示,包含:初始化、申请内存块、释放内存,所有使用完成后的动态内存都应该被释放,以供其他程序的申请使用。 + +![内存堆的操作](figures/08heap_ops.png) + +#### 分配和释放内存块 + +从内存堆上分配用户指定大小的内存块,函数接口如下: + +```c +void *rt_malloc(rt_size_t nbytes); +``` + +rt_malloc 函数会从系统堆空间中找到合适大小的内存块,然后把内存块可用地址返回给用户。下表描述了该函数的输入参数与返回值: + + rt_malloc() 的输入参数和返回值 + +|**参数** |**描述** | +|------------------|------------------------------------| +| nbytes | 需要分配的内存块的大小,单位为字节 | +|**返回** | —— | +| 分配的内存块地址 | 成功 | +| RT_NULL | 失败 | + +应用程序使用完从内存分配器中申请的内存后,必须及时释放,否则会造成内存泄漏,释放内存块的函数接口如下: + +```c +void rt_free (void *ptr); +``` + +rt_free 函数会把待释放的内存还回给堆管理器中。在调用这个函数时用户需传递待释放的内存块指针,如果是空指针直接返回。下表描述了该函数的输入参数: + + rt_free() 的输入参数 + +|**参数**|**描述** | +|----------|--------------------| +| ptr | 待释放的内存块指针 | + +#### 重分配内存块 + +在已分配内存块的基础上重新分配内存块的大小(增加或缩小),可以通过下面的函数接口完成: + +```c +void *rt_realloc(void *rmem, rt_size_t newsize); +``` + +在进行重新分配内存块时,原来的内存块数据保持不变(缩小的情况下,后面的数据被自动截断)。下表描述了该函数的输入参数和返回值: + + rt_realloc() 的输入参数和返回值 + +|**参数** |**描述** | +|----------------------|--------------------| +| rmem | 指向已分配的内存块 | +| newsize | 重新分配的内存大小 | +|**返回** | —— | +| 重新分配的内存块地址 | 成功 | + +#### 分配多内存块 + +从内存堆中分配连续内存地址的多个内存块,可以通过下面的函数接口完成: + +```c + void *rt_calloc(rt_size_t count, rt_size_t size); +``` + +下表描述了该函数的输入参数与返回值: + + rt_calloc() 的输入参数和返回值 + +|**参数** |**描述** | +|----------------------------|---------------------------------------------| +| count | 内存块数量 | +| size | 内存块容量 | +|**返回** | —— | +| 指向第一个内存块地址的指针 | 成功 ,并且所有分配的内存块都被初始化成零。 | +| RT_NULL | 分配失败 | + +#### 设置内存钩子函数 + +在分配内存块过程中,用户可设置一个钩子函数,调用的函数接口如下: + +```c +void rt_malloc_sethook(void (*hook)(void *ptr, rt_size_t size)); +``` + +设置的钩子函数会在内存分配完成后进行回调。回调时,会把分配到的内存块地址和大小做为入口参数传递进去。下表描述了该函数的输入参数: + + rt_malloc_sethook() 的输入参数 + +|**参数**|**描述** | +|----------|--------------| +| hook | 钩子函数指针 | + +其中 hook 函数接口如下: + +```c +void hook(void *ptr, rt_size_t size); +``` + +下表描述了 hook 函数的输入参数: + + 分配钩子 hook 函数接口参数 + +|**参数**|**描述** | +|----------|----------------------| +| ptr | 分配到的内存块指针 | +| size | 分配到的内存块的大小 | + +在释放内存时,用户可设置一个钩子函数,调用的函数接口如下: + +```c +void rt_free_sethook(void (*hook)(void *ptr)); +``` + +设置的钩子函数会在调用内存释放完成前进行回调。回调时,释放的内存块地址会做为入口参数传递进去(此时内存块并没有被释放)。下表描述了该函数的输入参数: + + rt_free_sethook() 的输入参数 + +|**参数**|**描述** | +|----------|--------------| +| hook | 钩子函数指针 | + +其中 hook 函数接口如下: + +```c +void hook(void *ptr); +``` + +下表描述了 hook 函数的输入参数: + + 钩子函数 hook 的输入参数 + +|**参数**|**描述** | +|----------|--------------------| +| ptr | 待释放的内存块指针 | + +### 内存堆管理应用示例 + +这是一个内存堆的应用示例,这个程序会创建一个动态的线程,这个线程会动态申请内存并释放,每次申请更大的内存,当申请不到的时候就结束,如下代码所示: + + 内存堆管理 + +```c +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* 线程入口 */ +void thread1_entry(void *parameter) +{ + int i; + char *ptr = RT_NULL; /* 内存块的指针 */ + + for (i = 0; ; i++) + { + /* 每次分配 (1 << i) 大小字节数的内存空间 */ + ptr = rt_malloc(1 << i); + + /* 如果分配成功 */ + if (ptr != RT_NULL) + { + rt_kprintf("get memory :%d byte\n", (1 << i)); + /* 释放内存块 */ + rt_free(ptr); + rt_kprintf("free memory :%d byte\n", (1 << i)); + ptr = RT_NULL; + } + else + { + rt_kprintf("try to get %d byte memory failed!\n", (1 << i)); + return; + } + } +} + +int dynmem_sample(void) +{ + rt_thread_t tid = RT_NULL; + + /* 创建线程 1 */ + tid = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, + THREAD_TIMESLICE); + if (tid != RT_NULL) + rt_thread_startup(tid); + + return 0; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(dynmem_sample, dynmem sample); +``` + +仿真运行结果如下: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >dynmem_sample +msh >get memory :1 byte +free memory :1 byte +get memory :2 byte +free memory :2 byte +… +get memory :16384 byte +free memory :16384 byte +get memory :32768 byte +free memory :32768 byte +try to get 65536 byte memory failed! +``` + +例程中分配内存成功并打印信息;当试图申请 65536 byte 即 64KB 内存时,由于 RAM 总大小只有 64K,而可用 RAM 小于 64K,所以分配失败。 + +内存池 +------ + +内存堆管理器可以分配任意大小的内存块,非常灵活和方便。但其也存在明显的缺点:一是分配效率不高,在每次分配时,都要空闲内存块查找;二是容易产生内存碎片。为了提高内存分配的效率,并且避免内存碎片,RT-Thread 提供了另外一种内存管理方法:内存池(Memory Pool)。 + +内存池是一种内存分配方式,用于分配大量大小相同的小内存块,它可以极大地加快内存分配与释放的速度,且能尽量避免内存碎片化。此外,RT-Thread 的内存池支持线程挂起功能,当内存池中无空闲内存块时,申请线程会被挂起,直到内存池中有新的可用内存块,再将挂起的申请线程唤醒。 + +内存池的线程挂起功能非常适合需要通过内存资源进行同步的场景,例如播放音乐时,播放器线程会对音乐文件进行解码,然后发送到声卡驱动,从而驱动硬件播放音乐。 + +![播放器线程与声卡驱动关系](figures/08mempool.png) + +如上图所示,当播放器线程需要解码数据时,就会向内存池请求内存块,如果内存块已经用完,线程将被挂起,否则它将获得内存块以放置解码的数据; + +而后播放器线程把包含解码数据的内存块写入到声卡抽象设备中 (线程会立刻返回,继续解码出更多的数据); + +当声卡设备写入完成后,将调用播放器线程设置的回调函数,释放写入的内存块,如果在此之前,播放器线程因为把内存池里的内存块都用完而被挂起的话,那么这时它将被将唤醒,并继续进行解码。 + +### 内存池工作机制 + +#### 内存池控制块 + +内存池控制块是操作系统用于管理内存池的一个数据结构,它会存放内存池的一些信息,例如内存池数据区域开始地址,内存块大小和内存块列表等,也包含内存块与内存块之间连接用的链表结构,因内存块不可用而挂起的线程等待事件集合等。 + +在 RT-Thread 实时操作系统中,内存池控制块由结构体 struct rt_mempool 表示。另外一种 C 表达方式 rt_mp_t,表示的是内存块句柄,在 C 语言中的实现是指向内存池控制块的指针,详细定义情况见以下代码: + +```c +struct rt_mempool +{ + struct rt_object parent; + + void *start_address; /* 内存池数据区域开始地址 */ + rt_size_t size; /* 内存池数据区域大小 */ + + rt_size_t block_size; /* 内存块大小 */ + rt_uint8_t *block_list; /* 内存块列表 */ + + /* 内存池数据区域中能够容纳的最大内存块数 */ + rt_size_t block_total_count; + /* 内存池中空闲的内存块数 */ + rt_size_t block_free_count; + /* 因为内存块不可用而挂起的线程列表 */ + rt_list_t suspend_thread; + /* 因为内存块不可用而挂起的线程数 */ + rt_size_t suspend_thread_count; +}; +typedef struct rt_mempool* rt_mp_t; +``` + +#### 内存块分配机制 + +内存池在创建时先向系统申请一大块内存,然后分成同样大小的多个小内存块,小内存块直接通过链表连接起来(此链表也称为空闲链表)。每次分配的时候,从空闲链表中取出链头上第一个内存块,提供给申请者。从下图中可以看到,物理内存中允许存在多个大小不同的内存池,每一个内存池又由多个空闲内存块组成,内核用它们来进行内存管理。当一个内存池对象被创建时,内存池对象就被分配给了一个内存池控制块,内存控制块的参数包括内存池名,内存缓冲区,内存块大小,块数以及一个等待线程队列。 + +![内存池工作机制图](figures/08mempool_work.png) + +内核负责给内存池分配内存池控制块,它同时也接收用户线程的分配内存块申请,当获得这些信息后,内核就可以从内存池中为内存池分配内存。内存池一旦初始化完成,内部的内存块大小将不能再做调整。 + +每一个内存池对象由上述结构组成,其中 suspend_thread 形成了一个申请线程等待列表,即当内存池中无可用内存块,并且申请线程允许等待时,申请线程将挂起在 suspend_thread 链表上。 + +### 内存池的管理方式 + +内存池控制块是一个结构体,其中含有内存池相关的重要参数,在内存池各种状态间起到纽带的作用。内存池的相关接口如下图所示,对内存池的操作包含:创建 / 初始化内存池、申请内存块、释放内存块、删除 / 脱离内存池,但不是所有的内存池都会被删除,这与设计者的需求相关,但是使用完的内存块都应该被释放。 + +![内存池相关接口](figures/08mempool_ops.png) + +#### 创建和删除内存池 + +创建内存池操作将会创建一个内存池对象并从堆上分配一个内存池。创建内存池是从对应内存池中分配和释放内存块的先决条件,创建内存池后,线程便可以从内存池中执行申请、释放等操作。创建内存池使用下面的函数接口,该函数返回一个已创建的内存池对象。 + +```c +rt_mp_t rt_mp_create(const char* name, + rt_size_t block_count, + rt_size_t block_size); +``` + +使用该函数接口可以创建一个与需求的内存块大小、数目相匹配的内存池,前提当然是在系统资源允许的情况下(最主要的是内存堆内存资源)才能创建成功。创建内存池时,需要给内存池指定一个名称。然后内核从系统中申请一个内存池对象,然后从内存堆中分配一块由块数目和块大小计算得来的内存缓冲区,接着初始化内存池对象,并将申请成功的内存缓冲区组织成可用于分配的空闲块链表。下表描述了该函数的输入参数与返回值: + + rt_mp_create() 的输入参数和返回值 + +|**参数** |**描述** | +|--------------|--------------------| +| name | 内存池名 | +| block_count | 内存块数量 | +| block_size | 内存块容量 | +|**返回** | —— | +| 内存池的句柄 | 创建内存池对象成功 | +| RT_NULL | 创建失败 | + +删除内存池将删除内存池对象并释放申请的内存。使用下面的函数接口: + +```c +rt_err_t rt_mp_delete(rt_mp_t mp); +``` + +删除内存池时,会首先唤醒等待在该内存池对象上的所有线程(返回 -RT_ERROR),然后再释放已从内存堆上分配的内存池数据存放区域,然后删除内存池对象。下表描述了该函数的输入参数与返回值: + + rt_mp_delete() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|-----------------------------------| +| mp | rt_mp_create 返回的内存池对象句柄 | +|**返回**| —— | +| RT_EOK | 删除成功 | + +#### 初始化和脱离内存池 + +初始化内存池跟创建内存池类似,只是初始化内存池用于静态内存管理模式,内存池控制块来源于用户在系统中申请的静态对象。另外与创建内存池不同的是,此处内存池对象所使用的内存空间是由用户指定的一个缓冲区空间,用户把缓冲区的指针传递给内存池控制块,其余的初始化工作与创建内存池相同。函数接口如下: + +```c +rt_err_t rt_mp_init(rt_mp_t mp, + const char* name, + void *start, rt_size_t size, + rt_size_t block size); +``` + +初始化内存池时,把需要进行初始化的内存池对象传递给内核,同时需要传递的还有内存池用到的内存空间,以及内存池管理的内存块数目和块大小,并且给内存池指定一个名称。这样,内核就可以对该内存池进行初始化,将内存池用到的内存空间组织成可用于分配的空闲块链表。下表描述了该函数的输入参数与返回值: + + rt_mp_init() 的输入参数和返回值 + +|**参数** |**描述** | +|-------------|--------------------| +| mp | 内存池对象 | +| name | 内存池名 | +| start | 内存池的起始位置 | +| size | 内存池数据区域大小 | +| block_size | 内存块容量 | +|**返回** | —— | +| RT_EOK | 初始化成功 | +| \- RT_ERROR | 失败 | + +内存池块个数 = size / (block_size + 4 链表指针大小),计算结果取整数。 + +例如:内存池数据区总大小 size 设为 4096 字节,内存块大小 block_size 设为 80 字节;则申请的内存块个数为 4096/ (80+4)= 48 个。 + +脱离内存池将把内存池对象从内核对象管理器中脱离。脱离内存池使用下面的函数接口: + +```c +rt_err_t rt_mp_detach(rt_mp_t mp); +``` + +使用该函数接口后,内核先唤醒所有等待在该内存池对象上的线程,然后将内存池对象从内核对象管理器中脱离。下表描述了该函数的输入参数与返回值: + + rt_mp_detach() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|------------| +| mp | 内存池对象 | +|**返回**| —— | +| RT_EOK | 成功 | + +#### 分配和释放内存块 + +从指定的内存池中分配一个内存块,使用如下接口: + +```c +void *rt_mp_alloc (rt_mp_t mp, rt_int32_t time); +``` + +其中 time 参数的含义是申请分配内存块的超时时间。如果内存池中有可用的内存块,则从内存池的空闲块链表上取下一个内存块,减少空闲块数目并返回这个内存块;如果内存池中已经没有空闲内存块,则判断超时时间设置:若超时时间设置为零,则立刻返回空内存块;若等待时间大于零,则把当前线程挂起在该内存池对象上,直到内存池中有可用的自由内存块,或等待时间到达。下表描述了该函数的输入参数与返回值: + + rt_mp_alloc() 的输入参数和返回值 + +|**参数** |**描述** | +|------------------|------------| +| mp | 内存池对象 | +| time | 超时时间 | +|**返回** | —— | +| 分配的内存块地址 | 成功 | +| RT_NULL | 失败 | + +任何内存块使用完后都必须被释放,否则会造成内存泄露,释放内存块使用如下接口: + +```c +void rt_mp_free (void *block); +``` + +使用该函数接口时,首先通过需要被释放的内存块指针计算出该内存块所在的(或所属于的)内存池对象,然后增加内存池对象的可用内存块数目,并把该被释放的内存块加入空闲内存块链表上。接着判断该内存池对象上是否有挂起的线程,如果有,则唤醒挂起线程链表上的首线程。下表描述了该函数的输入参数: + + rt_mp_free() 的输入参数 + +|**参数**|**描述** | +|----------|------------| +| block | 内存块指针 | + +### 内存池应用示例 + +这是一个静态内内存池的应用例程,这个例程会创建一个静态的内存池对象,2 个动态线程。一个线程会试图从内存池中获得内存块,另一个线程释放内存块内存块,如下代码所示: + + 内存池使用示例 + +```c +#include + +static rt_uint8_t *ptr[50]; +static rt_uint8_t mempool[4096]; +static struct rt_mempool mp; + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* 指向线程控制块的指针 */ +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; + +/* 线程 1 入口 */ +static void thread1_mp_alloc(void *parameter) +{ + int i; + for (i = 0 ; i < 50 ; i++) + { + if (ptr[i] == RT_NULL) + { + /* 试图申请内存块 50 次,当申请不到内存块时, + 线程 1 挂起,转至线程 2 运行 */ + ptr[i] = rt_mp_alloc(&mp, RT_WAITING_FOREVER); + if (ptr[i] != RT_NULL) + rt_kprintf("allocate No.%d\n", i); + } + } +} + +/* 线程 2 入口,线程 2 的优先级比线程 1 低,应该线程 1 先获得执行。*/ +static void thread2_mp_release(void *parameter) +{ + int i; + + rt_kprintf("thread2 try to release block\n"); + for (i = 0; i < 50 ; i++) + { + /* 释放所有分配成功的内存块 */ + if (ptr[i] != RT_NULL) + { + rt_kprintf("release block %d\n", i); + rt_mp_free(ptr[i]); + ptr[i] = RT_NULL; + } + } +} + +int mempool_sample(void) +{ + int i; + for (i = 0; i < 50; i ++) ptr[i] = RT_NULL; + + /* 初始化内存池对象 */ + rt_mp_init(&mp, "mp1", &mempool[0], sizeof(mempool), 80); + + /* 创建线程 1:申请内存池 */ + tid1 = rt_thread_create("thread1", thread1_mp_alloc, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + + /* 创建线程 2:释放内存池 */ + tid2 = rt_thread_create("thread2", thread2_mp_release, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, THREAD_TIMESLICE); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mempool_sample, mempool sample); +``` + +仿真运行结果如下: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >mempool_sample +msh >allocate No.0 +allocate No.1 +allocate No.2 +allocate No.3 +allocate No.4 +… +allocate No.46 +allocate No.47 +thread2 try to release block +release block 0 +allocate No.48 +release block 1 +allocate No.49 +release block 2 +release block 3 +release block 4 +release block 5 +… +release block 47 +release block 48 +release block 49 +``` + +本例程在初始化内存池对象时,初始化了 4096 /(80+4) = 48 个内存块。 + +①线程 1 申请了 48 个内存块之后,此时内存块已经被用完,需要其他地方释放才能再次申请;但此时,线程 1 以一直等待的方式又申请了 1 个,由于无法分配,所以线程 1 挂起; + +②线程 2 开始执行释放内存的操作;当线程 2 释放一个内存块的时候,就有一个内存块空闲出来,唤醒线程 1 申请内存,申请成功后再申请,线程 1 又挂起,再循环一次②; + +③线程 2 继续释放剩余的内存块,释放完毕。 + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/netdev/figures/netdev.jpg b/rt-thread-version/rt-thread-standard/programming-manual/netdev/figures/netdev.jpg new file mode 100644 index 0000000000000000000000000000000000000000..73559dda3b128b8a167d15163c58a04e6b638c60 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/netdev/figures/netdev.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/netdev/netdev.md b/rt-thread-version/rt-thread-standard/programming-manual/netdev/netdev.md new file mode 100644 index 0000000000000000000000000000000000000000..2cd28faa7dd3677e831cc96acd9564c55cdb57b5 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/netdev/netdev.md @@ -0,0 +1,607 @@ +# netdev 网卡 + +## 介绍 + +netdev(network interface device),即网络接口设备,又称网卡。每一个用于网络连接的设备都可以注册成网卡,为了适配更多的种类的网卡,避免系统中对单一网卡的依赖,RT-Thread 系统提供了 netdev 组件用于网卡管理和控制。 + +netdev 组件主要作用是**解决设备多网卡连接时网络连接问题,用于统一管理各个网卡信息与网络连接状态,并且提供统一的网卡调试命令接口**。 其主要功能特点如下所示: + +- 抽象网卡概念,每个网络连接设备可注册唯一网卡。 +- 提供多种网络连接信息查询,方便用户实时获取当前网卡网络状态; +- 建立网卡列表和默认网卡,可用于网络连接的切换; +- 提供多种网卡操作接口(设置 IP、DNS 服务器地址,设置网卡状态等); +- 统一管理网卡调试命令(ping、ifconfig、netstat、dns 等命令); + +## 工作原理 + +### 网卡概念 + +网卡概念介绍之前先了解协议栈相关概念,协议栈是指网络中各层协议的总和,每种协议栈反映了不同的网络数据交互方式,RT-Thread 系统中目前支持三种协议栈类型: lwIP 协议栈、AT Socket 协议栈、WIZnet TCP/IP硬件协议栈。每种协议栈对应一种协议簇类型(family),上述协议栈分别对应的协议簇类型为:AF_INET、AF_AT、AF_WIZ。 + +网卡的初始化和注册建立在协议簇类型上,所以每种网卡对应唯一的协议簇类型。 Socket 套接字描述符的创建建立在 netdev 网卡基础上,所以每个创建的 Socket 对应唯一的网卡。协议簇、网卡和 socket 之间关系如下图所示: + +![netdev 网络关系图](figures/netdev.jpg) + +每个网卡对应唯一的网卡结构体对象,其中包含该网卡的主要信息和实时状态,用于后面网卡信息的获取和设置,如下为网卡结构体对象参数介绍: + +```c +/* 网卡结构体对象 */ +struct netdev +{ + rt_slist_t list; /* 网卡列表 */ + + char name[RT_NAME_MAX]; /* 网卡名称 */ + ip_addr_t ip_addr; /* IP 地址 */ + ip_addr_t netmask; /* 子网掩码地址 */ + ip_addr_t gw; /* 网关地址 */ + ip_addr_t dns_servers[NETDEV_DNS_SERVERS_NUM]; /* DNS 服务器地址 */ + uint8_t hwaddr_len; /* 硬件地址长度 */ + uint8_t hwaddr[NETDEV_HWADDR_MAX_LEN]; /* 硬件地址 */ + + uint16_t flags; /* 网卡状态位 */ + uint16_t mtu; /* 网卡最大传输单元 */ + const struct netdev_ops *ops; /* 网卡操作回调函数 */ + + netdev_callback_fn status_callback; /* 网卡状态改变回调 */ + netdev_callback_fn addr_callback; /* 网卡地址改变回调 */ + +#ifdef RT_USING_SAL + void *sal_user_data; /* 网卡中协议簇相关参数数据 */ +#endif /* RT_USING_SAL */ + void *user_data; /* 预留用户数据 */ +}; + +``` + +### 网卡状态 + +netdev 组件提供对网卡网络状态的管理和控制,其类型主要包括下面四种:up/down、link_up/link_down、internet_up/internet_down、dhcp_enable/dhcp_disable。 + +- **up/down:** 底层网卡初始化完成之后置为 up 状态,用于判断网卡开启还是禁用。 +- **link_up/link_down:** 用于判断网卡设备是否具有有效的链路连接,连接后可以与其他网络设备进行通信。该状态一般由网卡底层驱动设置。 +- **internet_up/internet_down:** 用于判断设备是否连接到因特网,接入后可以与外网设备进行通信。 +- **dhcp_enable/dhcp_disable:** 用于判断当前网卡设备是否开启 DHCP 功能支持。 + +其中`up/down` 状态以及 `dhcp_enable/dhcp_disable` 状态可以通过 netdev 组件提供的接口设置,可以在应用层控制。其他状态是由网卡底层驱动或者 netdev 组件根据当前网卡网络连接情况自动设置。 + +### 默认网卡和网卡列表 + +为了方便网卡的管理和控制,netdev 组件中提供网卡列表用于统一管理各个网卡设备,系统中每个网卡在初始化是会创建和注册网卡对象到 netdev 组件网卡列表中。 + +网卡列表中有且只有一个**默认网卡**,一般为系统中第一个注册的网卡,可以通过 `netdev_set_default()` 函数设置默认网卡,默认网卡的主要作用是确定优先使用的进行网络通讯的网卡类型,方便网卡的切换和网卡信息的获取。 + +## 配置选项 + +当我们使用 netdev 组件时需要在 `rtconfig.h` 中定义如下宏定义: + +| **宏定义** | **描述** | +| ------------------------- | ---------------------------- | +| RT_USING_NETDEV | 开启 netdev 功能 | +| NETDEV_USING_IFCONFIG | 开启 ifconfig 命令支持 | +| NETDEV_USING_PING | 开启 ping 命令支持 | +| NETDEV_USING_NETSTAT | 开启 netstat 命令支持 | +| NETDEV_USING_AUTO_DEFAULT | 开启默认网卡自动切换功能支持 | + +上面配置选项可以直接在 `rtconfig.h` 文件中添加使用,也可以通过组件包管理工具 ENV 配置选项加入,ENV 工具中具体配置路径如下: + +```c +RT-Thread Components ---> + Network ---> + Network interface device ---> + [*] Enable network interface device + [*] Enable ifconfig features + [*] Enable ping features + [*] Enable netstat features + [*] Enable default netdev automatic change features +``` + +配置完成可以通过 scons 命令重新生成功能,完成 netdev 组件的添加。 + +## 使用方式 + +### 头文件定义 + +使用下面 netdev 网卡功能相关操作函数,需要包含如下头文件: + +```c +#include /* 包含 ip_addr_t 等地址相关的头文件 */ +#include /* 包含全部的 netdev 相关操作接口函数 */ +``` + +### 网卡注册和获取 + +**注册网卡** + +每一个网卡在初始化完成之后,需要调用网卡注册函数注册网卡到网卡列表中,注册网卡的接口如下所示: + +```c +int netdev_register(struct netdev *netdev, const char *name, void *user_data); +``` + +| **参数** | **描述** | +| --------- | ------------ | +| netdev | 网卡对象 | +| name | 网卡名称 | +| user_data | 用户使用数据 | +| **返回** | **——** | +| 0 | 网卡注册成功 | +| -1 | 网卡注册失败 | + +该函数不需要在用户层调用,一般为网卡驱动初始化完成之后自动调用,如 esp8266 网卡的注册在 esp8266 设备网络初始化之后自动完成。 + +**注销网卡** + +该函数可以在网卡使用时,注销网卡的注册,即从网卡列表中删除对应网卡,注销网卡的接口如下所示: + +```c +int netdev_unregister(struct netdev *netdev); +``` + +| **参数** | **描述** | +| -------- | ------------ | +| netdev | 网卡对象 | +| **返回** | **——** | +| 0 | 网卡注销成功 | +| -1 | 网卡注销失败 | + +**通过状态获取第一个匹配的网卡对象** + +如果想要通过指定传入状态匹配默认的网卡,可以调用如下函数: + +```c +struct netdev *netdev_get_first_by_flags(uint16_t flags); +``` + +| **参数** | **描述** | +| -------- | ---------------- | +| flags | 指定匹配的状态 | +| **返回** | **——** | +| != NULL | 获取网卡对象成功 | +| NULL | 获取网卡对象失败 | + +可以用于匹配网卡的状态如下所示: + +| 状态 | 描述 | +| ----------------------- | ---------------------- | +| NETDEV_FLAG_UP | 网卡 up 状态 | +| NETDEV_FLAG_LINK_UP | 网卡 link_up 状态 | +| NETDEV_FLAG_INTERNET_UP | 网卡外网连接状态 | +| NETDEV_FLAG_DHCP | 网卡 DHCP 功能开启状态 | + +**获取第一个指定协议簇类型的网卡对象** + +每个网卡对应唯一的协议簇类型(family),如下函数可以通过指定的协议簇类型获取网卡列表中第一个网卡对象: + +```c +struct netdev *netdev_get_by_family(int family); +``` + +| **参数** | **描述** | +| -------- | ---------------- | +| family | 协议簇类型 | +| **返回** | **——** | +| != NULL | 获取网卡对象成功 | +| NULL | 获取网卡对象失败 | + +目前 RT-Thread 系统中支持的协议簇类型有如下几种: + +| 协议簇类型 | 介绍 | +| ---------- | --------------------------------------- | +| AF_INET | 用于使用 lwIP 协议栈的网卡 | +| AF_AT | 用于使用 AT Socket 协议栈的网卡 | +| AF_WIZ | 用于使用 WIZnet TCP/IP 硬件协议栈的网卡 | + +该函数主要用于指定协议簇网卡操作,以及多网卡环境下,同协议簇网卡之间的切换的情况。 + +**通过 IP 地址获取网卡对象** + +每个网卡中都包含该网卡的基本信息如 :IP 地址,网关和子网掩码等,下面函数可以通过 IP 地址获取网卡对象: + +```c +struct netdev *netdev_get_by_ipaddr(ip_addr_t *ip_addr); +``` + +| **参数** | **描述** | +| -------- | ---------------- | +| ip_addr | IP 地址对象 | +| **返回** | **——** | +| != NULL | 获取网卡对象成功 | +| NULL | 获取网卡对象失败 | + +该函数主要用于 bind 函数绑定指定 IP 地址时获取网卡状态信息的情况。 + +> 可以通过 inet_aton () 函数将 IP 地址从字符串格式转化为 ip_addr_t 类型。 + +**通过名称获取网卡对象** + +每个网卡中有唯一的网卡名称,可以通过网卡名称获取网卡对象: + +```c +struct netdev *netdev_get_by_name(const char *name); +``` + +| **参数** | **描述** | +| -------- | ---------------- | +| name | 网卡名称 | +| **返回** | **——** | +| != NULL | 获取网卡对象成功 | +| NULL | 获取网卡对象失败 | + +该函数为应用层常用获取网卡对象接口,当前网卡列表中名称可通过 `ifconfig` 命令查看。 + +### 设置网卡信息 + +**设置默认网卡** + +系统中注册的网卡通过统一网卡列表管理,并且提供唯一的默认网卡,下面函数可以用于切换默认网卡: + +```c +void netdev_set_default(struct netdev *netdev); +``` + +**设置网卡 up/down 状态** + +网卡 up/down 状态用于控制网卡状态是否能使用。 + +如下函数用于启用网卡: + +```c +int netdev_set_up(struct netdev *netdev); +``` + + 如下函数用于禁用网卡: + +```c +int netdev_set_down(struct netdev *netdev); +``` + +禁用网卡之后,该网卡上对应的 Socket 套接字将无法进行数据通讯,并且将无法在该网卡上创建和绑定 Socket 套接字,直到网卡状态设置为 up 状态。 + +**设置网卡 DHCP 功能状态** + +DHCP 即动态主机配置协议,如果开启该网卡 DHCP 功能将无法设置该网卡 IP 、网关和子网掩码地址等信息,如果关闭该功能则可以设置上述信息。下面函数可以用于控制开启或关闭网卡 DHCP 功能: + +```c +int netdev_dhcp_enabled(struct netdev *netdev, rt_bool_t is_enabled); +``` + +| **参数** | **描述** | +| ---------- | ---------------------------------- | +| netdev | 网卡对象 | +| is_enabled | 是否开启功能(RT_TRUE / RT_FALSE) | +| **返回** | **——** | +| 0 | 设置 DHCP 功能状态成功 | +| < 0 | 设置 DHCP 功能状态失败 | + +部分网卡不支持设置 DHCP 状态功能,如 M26、EC20 等 GRPS 模块,在调用该函数时会报错提示。 + +**设置网卡地址信息** + +下面函数可以用于设置指定网卡地址 IP 、网关和子网掩码地址,需要在网卡关闭 DHCP 功能状态使用。 + +```c +/* 设置网卡 IP 地址 */ +int netdev_set_ipaddr(struct netdev *netdev, const ip_addr_t *ipaddr); +``` + +```c +/* 设置网卡网关地址 */ +int netdev_set_gw(struct netdev *netdev, const ip_addr_t *gw); +``` + +```c +/* 设置网卡子网掩码地址 */ +int netdev_set_netmask(struct netdev *netdev, const ip_addr_t *netmask); +``` + +| **参数** | **描述** | +| ----------------- | ------------------------------- | +| netdev | 网卡对象 | +| ipaddr/gw/netmask | 需要设置的 IP/网关/子网掩码地址 | +| **返回** | **——** | +| 0 | 设置地址信息成功 | +| < 0 | 设置地址信息失败 | + +下面函数可以用于设置网卡 DNS 服务器地址,主要用于网卡域名解析功能。 + +```c +int netdev_set_dns_server(struct netdev *netdev, uint8_t dns_num, const ip_addr_t *dns_server); +``` +| **参数** | **描述** | +| ---------- | ------------------------- | +| netdev | 网卡对象 | +| dns_num | 需要设置的 DNS 服务器 | +| dns_server | 需要设置的 DNS 服务器地址 | +| **返回** | **——** | +| 0 | 设置地址信息成功 | +| < 0 | 设置地址信息失败 | + +**设置网卡回调函数** + +下面函数可以用于设备网卡状态改变时调用的回调函数,状态的改变包括:`up/down`、 `link_up/link_down`、`internet_up/internet_down`、`dhcp_enable/dhcp_disable` 等。 + +```c +typedef void (*netdev_callback_fn )(struct netdev *netdev, enum netdev_cb_type type); +void netdev_set_status_callback(struct netdev *netdev, netdev_callback_fn status_callback); +``` + +| **参数** | **描述** | +| --------------- | -------------------- | +| netdev | 网卡对象 | +| status_callback | 状态改变回调函数指针 | +| **返回** | **——** | +| 无 | 无 | + +下面函数可以用于设置网卡地址信息改变时调用的回调函数,地址的改变包括:IP、子网掩码、网关、DNS 服务器等地址。 + +```c +void netdev_set_addr_callback(struct netdev *netdev, netdev_callback_fn addr_callback) +``` + +| **参数** | **描述** | +| ------------- | -------------------- | +| netdev | 网卡对象 | +| addr_callback | 地址改变回调函数指针 | +| **返回** | **——** | +| 无 | 无 | + +### 获取网卡信息 + +**判断网卡是否为 up 状态** + +```c +#define netdev_is_up(netdev) +``` + +**判断网卡是否为 link_up 状态** + +```c +#define netdev_is_link_up(netdev) +``` + +**判断网卡是否为 internet_up 状态** + +```c +#define netdev_is_internet_up(netdev) +``` + +**判断网卡 DHCP 功能是否开启** + +```c +#define netdev_is_dhcp_enable(netdev) +``` + +### 默认网卡自动切换 + +menuconfig 中配置开启如下选项,可使能默认网卡自动切换功能: + +```c +[*] Enable default netdev automatic change features +``` + + 单网卡模式下,开启和关闭默认网卡自动切换功能无明显效果。 + +多网卡模式下,如果开启默认网卡自动切换功能,当前默认网卡状态改变为 down 或 link_down 时,默认网卡会切换到网卡列表中第一个状态为 up 和 link_up 的网卡。这样可以使一个网卡断开后快速切换到另一个可用网卡,简化用户应用层网卡切换操作。如果未开启该功能,则不会自动切换默认网卡。 + +## 应用示例 + +### FinSH 命令 + +为了方便用户多网卡环境下网卡的管理和控制,netdev 组件提供多种网络调试命令,常用的 FinSH 命令如下表所示: + +| FinSH 命令 | 描述 | +| ---------- | ------------------------------------------------------ | +| ping | 用于检查网络是否连通 | +| ifconfig | 用于显示和配置网卡信息,如设置 IP 、网关和子网掩码地址 | +| dns | 用于显示和配置网卡 DNS 服务器地址 | +| netstat | 用于查看各网卡网络连接信息和端口使用情况 | + +使用 `ping` 命令测试网络连接情况,运行结果如下所示: + +```c +msh />ping rt-thread.org +60 bytes from 118.31.15.152 icmp_seq=0 ttl=52 time=13 ms +60 bytes from 118.31.15.152 icmp_seq=1 ttl=52 time=13 ms +60 bytes from 118.31.15.152 icmp_seq=2 ttl=52 time=14 ms +60 bytes from 118.31.15.152 icmp_seq=3 ttl=52 time=14 ms +``` + +- 该命令默认使用网卡列表中默认网卡发送 ping 协议数据,如果默认网卡状态为 down 或者 link_down,则使用网卡列表中第一个 up 和 link_up 状态的网卡发送 ping 协议请求数据。 + +使用 `ifconfig ` 命令查询和设置网卡信息,运行结果如下所示: + +```c +msh />ifconfig /* 显示全部网卡信息 */ +network interface device: w0 (Default) +MTU: 1500 +MAC: 98 3b 16 55 9a 87 +FLAGS: UP LINK_UP INTERNET_UP DHCP_DISABLE ETHARP BROADCAST IGMP +ip address: 192.168.12.92 +gw address: 192.168.10.1 +net mask : 255.255.0.0 +dns server #0: 192.168.10.1 +dns server #1: 223.5.5.5 +msh /> +msh />ifconfig w0 192.168.12.93 192.168.10.1 255.255.0.0 /* 设置指定网卡 IP 地址*/ +config : w0 +IP addr: 192.168.12.93 +Gateway: 192.168.10.1 +netmask: 255.255.0.0 +``` + +- `ifconfig` 命令会顺序显示网卡列表中全部网卡的信息; +- 网卡列表中默认网卡命令后会添加 `default` 标识; +- `ifconfig` 命令设置网卡信息功能,只能在网卡 DHCP 功能关闭时调用,否则会报错。 + +使用 `dns` 命令查询和设置网卡 DNS 服务器地址,运行结果如下所示: + +```c +msh />dns /* 显示全部网卡 DNS 服务器地址 */ +network interface device: w0 (Default) +dns server #0: 192.168.10.1 +dns server #1: 223.5.5.5 +msh /> +msh />dns w0 192.168.12.1 /* 设置指定网卡 DNS 服务器地址 */ +set network interface device(w0) dns server #0: 192.168.12.1 +``` + +- `dns` 命令会顺序显示网卡列表中全部网卡的 DNS 服务器地址信息。 +- 目前每个网卡只同时支持 2 个 DNS 服务器地址。 + +使用 `netstat` 命令可以网卡下网络连接状态和端口使用,运行效果如下所示: + +```c +msh />netstat +Active PCB states: +#0 192.168.12.93:49153 <==> 183.230.40.39:6002 +Listen PCB states: +TIME-WAIT PCB states: +Active UDP PCB states: +#0 4 0.0.0.0:68 <==> 0.0.0.0:67 +``` + +- 该命令可以查看当前网卡上使用的 TCP/UDP 连接的地址和端口信息。 + +### 切换默认网卡示例 + +在多网卡连接的情况下,可以通过设置默认网卡切换网卡。创建 Socket 连接时,使用 gethostbyname 等域名解析相关的函数时,以及在完成 ping 网络功能时,都会优先选择默认网卡。 + +如下示例,导出命令用于切换默认网卡: + +```c +#include +#include /* 当需要网卡操作是,需要包含这两个头文件 */ + +static int netdev_set_default_test(int argc, char **argv) +{ + struct netdev *netdev = RT_NULL; + + if (argc != 2) + { + rt_kprintf("netdev_set_default [netdev_name] --set default network interface device.\n"); + return -1; + } + + /* 通过网卡名称获取网卡对象,名称可以通过 ifconfig 命令查看 */ + netdev = netdev_get_by_name(argv[1]); + if (netdev == RT_NULL) + { + rt_kprintf("not find network interface device name(%s).\n", argv[1]); + return -1; + } + + /* 设置默认网卡对象 */ + netdev_set_default(netdev); + + rt_kprintf("set default network interface device(%s) success.\n", argv[1]); + return 0; +} +#ifdef FINSH_USING_MSH +#include +/* 导出命令到 FinSH 控制台 */ +MSH_CMD_EXPORT_ALIAS(netdev_set_default_test, netdev_set_default, set default network interface device); +#endif /* FINSH_USING_MSH */ +``` + +### 设置网卡 up/down 状态示例 + +网卡 up/down 状态用于设置网卡是否被禁用,下面提供导出设置网卡状态命令: + +```c +#include +#include /* 当需要网卡操作是,需要包含这两个头文件 */ + +int netdev_set_status_test(int argc, char **argv) +{ + struct netdev *netdev = RT_NULL; + + if (argc != 3) + { + rt_kprintf("netdev_set_status [netdev_name] [up/down] --set network interface device status.\n"); + return -1; + } + + /* 通过名称获取网卡对象 */ + netdev = netdev_get_by_name(argv[1]); + if (netdev == RT_NULL) + { + rt_kprintf("input network interface name(%s) error.\n", argv[1]); + return -1; + } + + /* 设置网卡状态为 up 或 down */ + if (strcmp(argv[2], "up") == 0) + { + netdev_set_up(netdev); + rt_kprintf("set network interface device(%s) up status.\n", argv[1]); + } + else if (strcmp(argv[2], "down") == 0) + { + netdev_set_down(netdev); + rt_kprintf("set network interface device(%s) down status.\n", argv[1]); + } + else + { + rt_kprintf("netdev_set_status [netdev_name] [up/down].\n"); + return -1; + } + + return 0; +} +#ifdef FINSH_USING_MSH +#include +/* 导出命令到 FinSH 控制台 */ +MSH_CMD_EXPORT_ALIAS(netdev_set_status_test, netdev_set_status, set network interface device status); +#endif /* FINSH_USING_MSH */ +``` + +### 设置网卡 IP 地址示例 + +每个网卡对应唯一的 IP 地址,可以通过 netdev 提供接口设置网卡 IP 地址,如下所示: + +```c +#include +#include /* 当需要网卡操作是,需要包含这两个头文件 */ + +int netdev_set_ipaddr_test(int argc, char **argv) +{ + struct netdev *netdev = RT_NULL; + ip_addr_t addr; + + if (argc != 3) + { + rt_kprintf("netdev_set_status [netdev_name] [ip_addr] --set network interface device IP address.\n"); + return -1; + } + + /* 通过名称获取网卡对象 */ + netdev = netdev_get_by_name(argv[1]); + if (netdev == RT_NULL) + { + rt_kprintf("input network interface name(%s) error.\n", argv[1]); + return -1; + } + + /* 设置网卡 IP 地址*/ + inet_aton(argv[2], &addr); + netdev_set_ipaddr(netdev, &addr); + + return 0; +} +#ifdef FINSH_USING_MSH +#include +/* 导出命令到 FinSH 控制台 */ +MSH_CMD_EXPORT_ALIAS(netdev_set_ipaddr_test, netdev_set_ipaddr, set network interface device IP + address); +#endif /* FINSH_USING_MSH */ +``` + +## 常见问题 + +### Q: 设置 IP地址、网关和子网掩码时报错无法设置? + + **A:** 设置 IP地址、网关和子网掩码时,需要确定当前网卡 DHCP 状态,只有当网卡 DHCP 状态关闭时才可以设置,可以调用 `netdev_dhcp_enabled ()` 函数设置网卡 DHCP 状态。 + +### Q: 多网卡情况下,ping 功能或者创建 socket 时调用的网卡不是想要使用的网卡怎么办? + + **A:** 首先使用 `ifconfig` 命令查看当前网卡列表中网卡,确实想要使用的网卡名称,可是使用 `netdev_set_default()` 设置默认网卡为该网卡,然后确定该网卡状态为 UP 和 LINK_UP 状态。 diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-hello.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-hello.png new file mode 100644 index 0000000000000000000000000000000000000000..aec33b859c789b99083d059002550651894d4d7e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-hello.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-layer.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-layer.png new file mode 100644 index 0000000000000000000000000000000000000000..597e7ef386d8f2212b8656d0a22d837d4d2f934a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-layer.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-mdk.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-mdk.png new file mode 100644 index 0000000000000000000000000000000000000000..d1b19c4af769cc2f03cc79f67bcc0827100a8d0e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-mdk.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-osi.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-osi.png new file mode 100644 index 0000000000000000000000000000000000000000..a2659a07fe2138b2f454748fc9d7b23c25cde069 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-osi.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-recv.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-recv.png new file mode 100644 index 0000000000000000000000000000000000000000..c36eb6d4f3c34304e17ce501c29e032f470d907d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-recv.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-send.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-send.png new file mode 100644 index 0000000000000000000000000000000000000000..7805a39b8f4b5fe00f14270a3be60a1f0974c89a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-send.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-tcp-s.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-tcp-s.png new file mode 100644 index 0000000000000000000000000000000000000000..aca0900d4acc9862d00022c8dc25c61d1b04d94d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-tcp-s.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-tcp.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-tcp.png new file mode 100644 index 0000000000000000000000000000000000000000..1abf4d284c2a2aa95a2e3347d3550bd446dcd2fd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-tcp.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp-client.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp-client.png new file mode 100644 index 0000000000000000000000000000000000000000..c0e916b4217de1204589257d85349cf8e2e0bcbc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp-client.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp-server.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp-server.png new file mode 100644 index 0000000000000000000000000000000000000000..093e160d05cd5269704fed8a72dda61c334413f4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp-server.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp.png new file mode 100644 index 0000000000000000000000000000000000000000..c2cdc6da8213c30778a13990e7e340ca5b64429d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-udp.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-wireless.png b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-wireless.png new file mode 100644 index 0000000000000000000000000000000000000000..76e26e0b52a583afb71cc491524b03d21599b664 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/network/figures/net-wireless.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/network/network.md b/rt-thread-version/rt-thread-standard/programming-manual/network/network.md new file mode 100644 index 0000000000000000000000000000000000000000..0092084a3ad9def5d2042fb82b3a8e8f5ce0f2d6 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/network/network.md @@ -0,0 +1,782 @@ +# RT-Thread 网络框架 + +随着网络的普及,人们的生活越来越依赖于网络的应用,越来越多的产品需要连接互联网,设备联网已经成为一种趋势。要实现设备和网络的连接,需要遵循 TCP/IP 协议,可以在设备运行网络协议栈来联网,也可以使用设备配合自带硬件网络协议栈的接口芯片来联网。 + +当设备连接上网络,就犹如插上了翅膀,可以利用网络实时的上传数据,用户在十万八千里之外就可以看到设备现在的运行状态和采集到的数据,并远程控制设备完成特定的任务。也可以通过设备播放网络音乐、拨打网络电话、充当局域网存储服务器等。 + +本章将讲解 RT-Thread 网络框架的相关内容,带你了解网络框架的概念、功能特点和使用方法,读完本章,大家将熟悉 RT-Thread 网络框架的概念和实现原理、熟悉使用 Socket API 进行网络编程。 + +## TCP/IP 网络协议简介 + +TCP/IP(Transmission Control Protocol/Internet Protocol)是传输控制协议和网络协议的简称,它不是单个协议,而是一个协议族的统称,里面包括了 IP 协议、ICMP 协议、TCP 协议、以及 http、ftp、pop3、https 协议等,它定义了电子设备如何连入因特网,以及数据在它们之间传输的标准。 + +### OSI 参考模型 + +OSI(Open System Interconnect),即开放式系统互联。一般都称为 OSI 参考模型,是 ISO(国际标准化组织)组织在 1985 年研究的网络互联模型。该体系结构标准定义了网络互连的七层框架(物理层、数据链路层、网络层、传输层、会话层、表示层和应用层),即 ISO 开放系统互连参考模型。第一层到第三层属于 [OSI 参考模型](http://baike.baidu.com/view/38361.htm) 的低三层,负责创建网络通信连接的链路;第四层到第七层为 OSI 参考模型的高四层,具体负责端到端的数据通信。在这一框架下进一步详细规定了每一层的功能,以实现开放系统环境中的互连性、互操作性和应用的可移植性。 + +### TCP/IP 参考模型 + +TCP/IP 通讯协议采用了 4 层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。这 4 层分别为: + +* 应用层:不同类型的网络应用有不同的通信规则,因此应用层的协议是多种多样的,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。 + +* 传输层:在此层中,它提供了节点间的数据传送服务,如传输控制协议(TCP)、用户数据报协议(UDP)等,TCP 和 UDP 给数据包加入传输数据并把它传输到下一层中,这一层负责传送数据,并且确定数据已被送达并接收。 + +* 网络层:负责提供基本的数据封包传送功能,让每一块数据包都能够到达目的主机(但不检查是否被正确接收),如网际协议(IP)。 + +* 网络接口层:对实际的网络媒体的管理,定义如何使用实际网络(如 Ethernet、Serial Line 等)来传送数据。 + +### TCP/IP 参考模型和 OSI 参考模型的区别 + +下图为 TCP/IP 参考模型与 OSI 参考模型图: + +![TCP/IP 参考模型与 OSI 参考模型](figures/net-osi.png) + +OSI 参考模型与 TCP/IP 参考模型都采用了分层结构,都是基于独立的协议栈的概念。OSI 参考模型有 7 层,而 TCP/IP 参考模型只有 4 层,即 TCP/IP 参考模型没有了表示层和会话层,并且把数据链路层和物理层合并为网络接口层。不过,二者的分层之间有一定的对应关系。OSI 由于体系比较复杂,而且设计先于实现,有许多设计过于理想,不太方便软件实现,因而完全实现 OSI 参考模型的系统并不多,应用的范围有限。而 TCP/IP 参考模型最早在计算机系统中实现,在 UNIX、Windows 平台中都有稳定的实现,并且提供了简单方便的编程接口(API),可以在其上开发出丰富的应用程序,因此得到了广泛的应用。TCP/IP 参考模型已成为现在网际互联的国际标准和工业标准。 + +### IP 地址 + +IP 地址是指互联网协议地址(Internet Protocol Address,又译为网际协议地址),是 [IP 协议](https://baike.baidu.com/item/IP%E5%8D%8F%E8%AE%AE) 提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。常见的局域网 IP 地址为 192.168.X.X。 + +### 子网掩码 + +子网掩码 (subnet mask) 又叫网络掩码、地址掩码、子网络遮罩,它是一种用来指明一个 IP 地址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它必须结合 IP 地址一起使用。子网掩码只有一个作用,就是将某个 IP 地址划分成网络地址和主机地址两部分。子网掩码为 1 的位,对应的 IP 地址为网络地址,子网掩码为 0 的位,对应的 IP 地址为主机地址。以 IP 地址 192.168.1.10 和子网掩码 255.255.255.0 为例,子网掩码前 24 位(将 10 进制转换成 2 进制)为 1,所以 IP 地址的前 24 位 192.168.1 表示网络地址,剩下的 0 为主机地址。 + +### MAC 地址 + +MAC(figures Access Control 或者 Medium Access Control)地址,意译为媒体访问控制,或称为物理地址、硬件地址,用来定义 [网络设备](https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E8%AE%BE%E5%A4%87/7667828) 的位置。在 [OSI 模型](https://baike.baidu.com/item/OSI%E6%A8%A1%E5%9E%8B) 中,第三层 [网络层](https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E5%B1%82/4329439) 负责[IP 地址](https://baike.baidu.com/item/IP%E5%9C%B0%E5%9D%80),第二层数据链路层则负责 MAC 地址。一个主机至少会有一个 MAC 地址。 + +## RT-Thread 网络框架简介 + +RT-Thread 为了能够支持各种网络协议栈,开发了 SAL 组件,全称 Socket abstraction layer,即套接字抽象层,RT-Thread 通过它可以无缝接入各类协议栈,包括几种常用的 TCP/IP 协议栈,例如嵌入式开发中常用的 LwIP 协议栈以及 RT-Thread 开发的 AT Socket 协议栈组件等,这些协议栈完成数据从网络层到传输层的转化。 + +RT-Thread 网络框架主要功能特点如下所示: + +* 支持标准网络套接字 BSD Socket API,支持使用 poll/select + +* 抽象、统一多种网络协议栈接口 + +* 支持各类物理网卡、网络通讯模块硬件 + +* 资源占用小,SAL 套接字抽象层组件资源占用为 ROM 2.8K 和 RAM 0.6K + +RT-Thread 的网络框架采用了分层设计,共四层,每层都有不同的职责,下图为 RT-Thread 网络框架结构图: + +![RT-Thread 网络框架结构图](figures/net-layer.png) + +网络框架向用户应用程序提供标准 BSD Socket 接口,开发者使用 BSD Socket 接口进行操作,无需关心网络底层如何实现,也无需关心网络数据是通过哪个网络协议栈,套接字抽象层为上层应用层提供的接口有:accept、connect、send、recv 等。 + +SAL 层之下是协议栈层,当前网络框架中支持的几个主要协议栈如下: + +* LwIP 是一个开源的 TCP/IP 协议栈实现,它在保持 TCP/IP 协议主要功能的基础上减少了对 RAM 的占用,这使得 LwIP 协议栈很适合在嵌入式系统中使用。 + +* AT Socket 是给支持 AT 指令的模块使用的组件。AT 命令采用标准串口进行数据收发,将复杂的设备通讯方式转换成简单的串口编程,大大简化了产品的硬件设计和软件开发成本,这使得几乎所有的网络模组如 GPRS、3G/4G、NB-IoT、蓝牙、WiFi、GPS 等模组都很方便的接入 RT-Thread 网络框架,通过标准的 BSD Socket 方式开发网络应用,极大程度地简化上层应用的开发难度。 + +* Socket CAN 是 CAN 编程的一种方式,它简单易用,编程顺手。通过接入 SAL 层,开发者就可以在 RT-Thread 上实现 Socket CAN 编程了。 + +协议栈层下面是抽象设备层,通过将硬件设备抽象成以太网设备或者 AT 设备,从而将硬件设备接入到各类网络协议栈中。 + +最底层是各式各样的网络芯片或模块(例如:W5500/CH395 这类自带协议栈的以太网芯片,带 AT 指令的 WiFi 模块、GPRS 模块、NB-IoT 模块等等),这些硬件模块是真正进行网络通信功能的承载者,负责跟各类物理网络进行通信。 + +总体来说,RT-Thread 网络框架使开发者只需要关心和使用标准 BSD Socket 网络接口进行网络应用开发,而无需关心底层具体网络协议栈类型和实现,极大的提高了系统的兼容性,方便开发者完成网络相关应用的开发,也极大地提升了 RT-Thread 在物联网领域对于不同网络硬件的兼容性。 + +此外,基于网络框架,RT-Thread 提供了数量丰富的网络软件包,他们是基于 SAL 层的各种网络应用,例如 Paho MQTT、WebClient、cJSON、netutils 等等,可以从在线软件包管理中心获得。这些软件包都是网络应用利器,使用它们可以大大简化网络应用的开发难度,缩短网络应用开发周期。目前网络软件包数量达十几个,下表列出了目前 RT-Thread 支持的部分网络软件包,软件包的数量还在不断的增加中。 + +|**软件包名称**|**描述** | +|----------------|------------------------| +| Paho MQTT | 基于 Eclipse 开源的 Paho MQTT,做了很多功能及性能优化,比如:增加了断线自动重连功能,采用 pipe 模型,支持非阻塞 API,支持 TLS 加密传输等等 | +| WebClient | 简单易用的 HTTP 客户端,支持 HTTP GET/POST 等常见请求功能,支持 HTTPS,断点续传等功能 | +| mongoose | 嵌入式 Web 服务器网络库,类似嵌入式世界里的 Nginx。授权许可不够友好,商业需要收费 | +| WebTerminal | 可以在浏览器或手机端访问 Finsh/MSHShell 的软件包 | +| cJSON | 超轻量级的 JSON 解析库 | +| ljson | json 到 struct 的解析,输出库 | +| ezXML | XML 文件解析库,目前还不支持解析 XML 数据 | +| nanopb | Protocol Buffers 格式数据解析库,Protocol Buffers 格式比 JSON、XML 格式资源占用更少 | +| GAgent | 接入机智云的软件包 | +| Marvell WiFi | Marvell WiFi 驱动 | +| Wiced WiFi | Wiced 接口的 WiFi 驱动 | +| CoAP | 移植 libcoap 的 CoAP 通信软件包 | +| nopoll | 移植的开源 WebSocket 通信软件包 | +| netutils | 实用的网络调试小工具集合,包括:ping、TFTP、iperf、NetIO、NTP、Telnet 等 | +| OneNet | 与中国移动 OneNet 云对接的软件包 | + +## 网络框架工作流程 + +使用 RT-Thread 网络框架,首先需要初始化 SAL,然后注册各类网络协议簇,确保应用程序能够使用 socket 网络套接字接口进行通信,本节主要以 LwIP 作为示例进行讲解。 + +### 网络协议簇注册 + +首先使用 `sal_init()` 接口对组件中使用的互斥锁等资源进行初始化,接口如下所示: + +```c +int sal_init(void); +``` + +SAL 初始化后,通过 `sal_proto_family_register()` 接口来注册网络协议簇,将 LwIP 网络协议簇注册到 SAL 中,示例代码如下: + +```c +static const struct proto_family LwIP_inet_family_ops = { + "LwIP", + AF_INET, + AF_INET, + inet_create, + LwIP_gethostbyname, + LwIP_gethostbyname_r, + LwIP_freeaddrinfo, + LwIP_getaddrinfo, +}; + +int LwIP_inet_init(void) +{ + sal_proto_family_register(&LwIP_inet_family_ops); + + return 0; +} +``` + +AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF 是 “Address Family” 的简写,INET 是 “Internet” 的简写。 + +其中 `sal_proto_family_register()` 接口定义如下所示: + +``` +int sal_proto_family_register(const struct proto_family *pf); +``` + +|**参数**|**描述** | +|----------|------------------| +| pf | 协议簇结构体指针 | +|**返回**|**——** | +| 0 | 注册成功 | +| -1 | 注册失败 | + +### 网络数据接收流程 + +LwIP 注册到 SAL 之后,应用程序可通过网络套接字接口进行网络数据收发。在 LwIP 中,创建了几个主要线程,分布是 tcpip 线程、erx 接收线程和 etx 发送线程,网络数据接收流程如下面图片所示,应用程序通过调用标准套接字接口 recv() 接收数据,以阻塞方式进行。当以太网硬件设备收到网络数据报文,将报文存放到接收缓冲区,然后通过以太网中断程序,发送邮件通知 erx 线程有数据到达,erx 线程会按照接收到的数据长度来申请 pbuf 内存块,并将数据放入 pbuf 的 payload 数据中,然后将 pbuf 内存块通过邮件发送给 tcpip 线程,tcpip 线程将数据返回给正在阻塞接收数据的应用程序。 + +![数据接收函数调用流程图](figures/net-recv.png) + +### 网络数据发送流程 + +网络数据发送流程如下图所示。当有数据需要发送时,应用程序调用标准网络套接字接口 send() 将数据交给 tcpip 线程,tcpip 线程会发送一个邮件来唤醒 etx 线程,etx 线程先判断以太网是否正在发送数据,如果没有,那么将待发送的数据放入发送缓冲区,然后通过以太网设备将数据发送出去。如果正在发送数据,etx 线程会将自己挂起,直到以太网设备空闲后再发送数据出去。 + +![数据发送函数调用流程图](figures/net-send.png) + +## 网络套接字编程 + +应用程序使用 Socket(网络套接字) 接口编程来实现网络通信功能,Socket 是一组应用程序接口(API),屏蔽了各个协议的通信细节,使得应用程序无需关注协议本身,直接使用 socket 提供的接口来进行互联的不同主机间的通信。 + +### TCP socket 通信流程 + +TCP 是 Tranfer Control Protocol 的简称,是一种面向连接的保证数据可靠传输的协议。通过 TCP 协议传输,得到的是一个顺序的无差错的数据流。基于 TCP 的 socket 编程流程图见下图,发送方和接收方的两个 socket 之间必须建立连接,以便在 TCP 协议的基础上进行通信,当一个 socket(通常都是 server socket)等待建立连接时,另一个 socket 可以要求进行连接,一旦这两个 socket 连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。TCP 连接是可靠的连接,它能保证数据包按顺序到达,如果出现丢包,则会自动重发数据包。 + +举个例子,TCP 相当于生活中打电话,当你打电话给对方时,必须要等待对方接听,只有对方接听了你的电话,和你建立了连接,双方才可以通话,互相传递信息。当然,这时候传递的信息是可靠地,因为对方听不清你说的内容可以要求你重新将内容复述一遍。当打电话的双方中的任何一方要结束本次通话时,会主动和对方告别,等到对方也和自己告别后,才会挂断电话,结束本次通讯。 + +![基于 TCP 的 socket 编程流程图](figures/net-tcp.png) + +### UDP socket 通信流程 + +UDP 是 User Datagram Protocol 的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址和目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的,基于 UDP 的 socket 编程流程如下图所示。 + +![基于 UDP 的 socket 编程流程](figures/net-udp.png) + +举个例子,UDP 就相当于生活中的对讲机通讯,你设定好频道后就可以直接说你要表达的信息了,数据被对讲机发送了出去,但是你不知道你的消息有没有被别人接收到,除非别人也用对讲机回复你,因此这种方式是不可靠的。 + +### 创建套接字 + +在进行通信前,通信双方首先使用 `socket()` 接口创建套接字,根据指定的地址族、数据类型和协议来分配一个套接字描述符及其所用的资源。接口如下所示: + +```c +int socket(int domain, int type, int protocol); +``` + +|**参数**|**描述** | +|----------|--------------------------------------------------| +| domain | 协议族 | +| type | 指定通信类型,取值包括 SOCK_STREAM 和 SOCK_DGRAM。 | +| protocol | protocol 允许为套接字指定一种协议,默认设为 0 | +|**返回**|**——** | +| >=0 | 成功,返回一个代表套接字描述符的整数 | +| -1 | 失败 | + +**通信类型包括**SOCK_STREAM 和 SOCK_DGRAM 两种方式,**SOCK_STREAM**表示面向连接的 TCP 数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。 + +**SOCK_DGRAM**表示无连接的 UDP 数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。 + +创建一个 TCP 类型的套接字的示例代码如下: + +```c + /* 创建一个 socket,类型是 SOCKET_STREAM,TCP 类型 */ + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + /* 创建 socket 失败 */ + rt_kprintf("Socket error\n"); + + return; + } +``` + +### 绑定套接字 + +绑定套接字用于将端口号和 IP 地址绑定到指定套接字上。当使用 socket() 创造一个套接字时, 只是给定了协议族,并没有分配地址,在套接字接收来自其他主机的连接前,必须用 bind() 给它绑定一个地址和端口号。接口如下所示: + +```c +int bind(int s, const struct sockaddr *name, socklen_t namelen); +``` + +|**参数**|**描述** | +|----------|--------------------------------------------| +| S | 套接字描述符 | +| name | 指向 sockaddr 结构体的指针,代表要绑定的地址 | +| namelen | sockaddr 结构体的长度 | +|**返回**|**——** | +| 0 | 成功 | +| -1 | 失败 | + +### 建立 TCP 连接 + +对于服务器端程序,使用 `bind()` 绑定套接字后,还需要使用 `listen()` 函数让套接字进入被动监听状态,再调用 `accept()` 函数,就可以随时响应客户端的请求了。 + +#### 监听套接字 + +监听套接字用于 TCP 服务器监听指定套接字连接。接口如下所示: + +```c +int listen(int s, int backlog); +``` + +|**参数**|**描述** | +|----------|--------------------------------| +| s | 套接字描述符 | +| backlog | 表示一次能够等待的最大连接数目 | +|**返回**|**——** | +| 0 | 成功 | +| -1 | 失败 | + +#### 接受连接 + +当应用程序监听来自其他客户端的连接时,要使用 `accept()` 函数初始化连接,它为每个连接创立新的套接字并从监听队列中移除这个连接。接口如下所示: + +```c +int accept(int s, struct sockaddr *addr, socklen_t *addrlen); +``` + +|**参数**|**描述** | +|----------|--------------------------------| +| s | 套接字描述符 | +| addr | 客户端设备地址信息 | +| addrlen | 客户端设备地址结构体的长度 | +|**返回**|**——** | +| >=0 | 成功,返回新创建的套接字描述符 | +| -1 | 失败 | + +#### 建立连接 + +用于客户端与指定服务器建立连接。接口如下所示: + +``` +int connect(int s, const struct sockaddr *name, socklen_t namelen); +``` + +|**参数**|**描述** | +|----------|------------------------| +| s | 套接字描述符 | +| name | 服务器地址信息 | +| namelen | 服务器地址结构体的长度 | +|**返回**|**描述** | +| 0 | 成功 | +| -1 | 失败 | + +客户端与服务端连接时,首先设置服务端地址,然后使用 `connect()` 函数进行连接,示例代码如下所示: + +```c +struct sockaddr_in server_addr; +/* 初始化预连接的服务端地址 */ +server_addr.sin_family = AF_INET; +server_addr.sin_port = htons(port); +server_addr.sin_addr = *((struct in_addr *)host->h_addr); +rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + +/* 连接到服务端 */ +if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) +{ + /* 连接失败 */ + closesocket(sock); + + return; +} +``` + +### 数据传输 + +TCP 和 UDP 的数据传输方式不同,TCP 需要建立连接后才能进行数据传输,使用 `send()` 函数进行数据发送,使用 `recv()` 函数进行数据接收,而 UDP 则不需要建立连接,使用 `sendto()` 函数进行数据发送,使用 `recvfrom()` 函数接收数据。 + +#### TCP 数据发送 + +TCP 连接建立以后,使用 `send()` 函数进行数据发送,接口如下所示: + +```c +int send(int s, const void *dataptr, size_t size, int flags); +``` + +|**参数**|**描述** | +|----------|----------------------------| +| s | 套接字描述符 | +| dataptr | 要发送的数据指针 | +| size | 发送的数据长度 | +| flags | 标志,一般为 0 | +|**返回**|**——** | +| >0 | 成功,返回发送的数据的长度 | +| <=0 | 失败 | + +#### TCP 数据接收 + +TCP 连接建立以后,使用 `recv()` 接收数据,接口如下所示: + +```c +int recv(int s, void *mem, size_t len, int flags); +``` + +|**参数**|**描述** | +|----------|----------------------------| +| s | 套接字描述符 | +| mem | 接收的数据指针 | +| len | 接收的数据长度 | +| flags | 标志,一般为 0 | +|**返回**|**描述** | +| >0 | 成功,返回接收的数据的长度 | +| =0 | 目标地址已传输完并关闭连接 | +| <0 | 失败 | + +#### UDP 数据发送 + +在未建立连接的情况下,可以使用 `sendto()` 函数向指定的目标地址发送 UDP 数据,接口如下所示: + +```c +int sendto(int s, const void *dataptr, size_t size, int flags, + const struct sockaddr *to, socklen_t tolen); +``` + +|**参数**|**描述** | +|----------|----------------------------| +| s | 套接字描述符 | +| dataptr | 发送的数据指针 | +| size | 发送的数据长度 | +| flags | 标志,一般为 0 | +| to | 目标地址结构体指针 | +| tolen | 目标地址结构体长度 | +|**返回**|**——** | +| >0 | 成功,返回发送的数据的长度 | +| <=0 | 失败 | + +#### UDP 数据接收 + +接收 UDP 数据则使用 `recvfrom()` 函数,接口如下所示: + +```c +int recvfrom(int s, void *mem, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen); +``` + +|**参数**|**描述** | +|----------|----------------------------| +| s | 套接字描述符 | +| mem | 接收的数据指针 | +| len | 接收的数据长度 | +| flags | 标志,一般为 0 | +| from | 接收地址结构体指针 | +| fromlen | 接收地址结构体长度 | +|**返回**|**——** | +| >0 | 成功,返回接收的数据的长度 | +| 0 | 接收地址已传输完并关闭连接 | +| <0 | 失败 | + +### 关闭网络连接 + +网络通信结束后,需要关闭网络连接,有两种方式,分别是使用 `closesocket()` 和 `shutdown()`。 + +`closesocket()` 接口用来关闭已经存在的 socket 连接,释放 socket 资源,将套接字描述符从内存清除,之后再也不能使用该套接字,与该套接字相关的连接和缓存也失去了意义,TCP 协议会自动关闭连接。接口如下所示: + +```c +int closesocket(int s); +``` + +|**参数**|**描述** | +|----------|--------------| +| s | 套接字描述符 | +|**返回**|**——** | +| 0 | 成功 | +| -1 | 失败 | + +使用 `shutdown()` 函数也可以关闭网络连接。TCP 连接是全双工的,使用 `shutdown()` 函数可以实现半关闭,它可以关闭连接的读或者写操作,也可以两端都关闭,但它不释放 socket 资源,接口如下所示: + +```c +int shutdown(int s, int how); +``` + +|**参数**|**描述** | +|----------|-----------------------------| +| s | 套接字描述符 | +| how | SHUT_RD 关闭连接的接收端,不再接收数据 SHUT_WR 关闭连接的发送端,不再发送数据 SHUT_RDWR 两端都关闭 | +|**返回**|**——** | +| 0 | 成功 | +| -1 | 失败 | + +## 网络功能配置 + +网络框架的主要功能配置选项如下表所示,可以根据不同的功能需求进行配置: + +SAL 组件配置选项: + +|**宏定义** |**取值类型**|**描述** | +|------------------------|--------------|--------------------| +| RT_USING_SAL | 布尔 | 开启 SAL | +| SAL_USING_LWIP | 布尔 | 开启 LwIP 组件 | +| SAL_USING_AT | 布尔 | 开启 AT 组件 | +| SAL_USING_POSIX | 布尔 | 开启 POSIX 接口支持 | +| SAL_PROTO_FAMILIES_NUM | 整数 | 支持最大协议族数量 | + +LwIP 配置选项: + +|**宏定义** |**取值类型**|**描述** | +|-----------------------------|--------------|----------------------| +| RT_USING_LWIP | 布尔 | 开启 LwIP 组件 | +| RT_USING_LWIP_IPV6 | 布尔 | 开启 IPV6 功能 | +| RT_LWIP_IGMP | 布尔 | 开启 IGMP 协议 | +| RT_LWIP_ICMP | 布尔 | 开启 ICMP 协议 | +| RT_LWIP_SNMP | 布尔 | 开启 SNMP 协议 | +| RT_LWIP_DNS | 布尔 | 开启 DNS 功能 | +| RT_LWIP_DHCP | 布尔 | 开启 DHCP 功能 | +| IP_SOF_BROADCAST | 整数 | IP 发送广播包过滤 | +| IP_SOF_BROADCAST_RECV | 整数 | IP 接收广播包过滤 | +| RT_LWIP_IPADDR | 字符串 | IP 地址 | +| RT_LWIP_GWADDR | 字符串 | 网关地址 | +| RT_LWIP_MSKADDR | 字符串 | 子网掩码 | +| RT_LWIP_UDP | 布尔 | 启用 UDP 协议 | +| RT_LWIP_TCP | 布尔 | 启用 TCP 协议 | +| RT_LWIP_RAW | 布尔 | 启用 RAW API | +| RT_MEMP_NUM_NETCONN | 整数 | 支持网络连接数 | +| RT_LWIP_PBUF_NUM | 整数 | pbuf 内存块数量 | +| RT_LWIP_RAW_PCB_NUM | 整数 | RAW 最大连接数量 | +| RT_LWIP_UDP_PCB_NUM | 整数 | UDP 最大连接数量 | +| RT_LWIP_TCP_PCB_NUM | 整数 | TCP 最大连接数量 | +| RT_LWIP_TCP_SND_BUF | 整数 | TCP 发送缓冲区大小 | +| RT_LWIP_TCP_WND | 整数 | TCP 滑动窗口大小 | +| RT_LWIP_TCPTHREAD_PRIORITY | 整数 | TCP 线程的优先级 | +| RT_LWIP_TCPTHREAD_MBOX_SIZE | 整数 | TCP 线程邮箱大小 | +| RT_LWIP_TCPTHREAD_STACKSIZE | 整数 | TCP 线程栈大小 | +| RT_LWIP_ETHTHREAD_PRIORITY | 整数 | 接收发送线程的优先级 | +| RT_LWIP_ETHTHREAD_STACKSIZE | 整数 | 接收发送线程栈大小 | +| RT_LwIP_ETHTHREAD_MBOX_SIZE | 整数 | 接收发送线程邮箱大小 | + +## 网络应用示例 + +### 查看 IP 地址 + +在控制台可使用 ifconfig 命令查看网络情况,可知 IP 地址 192.168.12.26,并且 FLAGS 状态是 LINK_UP,表示网络已经配置好: + +```c +msh >ifconfig +network interface: e0 (Default) +MTU: 1500 +MAC: 00 04 a3 12 34 56 +FLAGS: UP LINK_UP ETHARP BROADCAST IGMP +ip address: 192.168.12.26 +gw address: 192.168.10.1 +net mask : 255.255.0.0· +dns server #0: 192.168.10.1 +dns server #1: 223.5.5.5 +``` + +### Ping 网络测试 + +使用 ping 命令进行网络测试: + +```c +msh />ping rt-thread.org +60 bytes from 116.62.244.242 icmp_seq=0 ttl=49 time=11 ticks +60 bytes from 116.62.244.242 icmp_seq=1 ttl=49 time=10 ticks +60 bytes from 116.62.244.242 icmp_seq=2 ttl=49 time=12 ticks +60 bytes from 116.62.244.242 icmp_seq=3 ttl=49 time=10 ticks +msh />ping 192.168.10.12 +60 bytes from 192.168.10.12 icmp_seq=0 ttl=64 time=5 ticks +60 bytes from 192.168.10.12 icmp_seq=1 ttl=64 time=1 ticks +60 bytes from 192.168.10.12 icmp_seq=2 ttl=64 time=2 ticks +60 bytes from 192.168.10.12 icmp_seq=3 ttl=64 time=3 ticks +msh /> +``` + +得到以上的输出结果,表示连接网络成功! + +### TCP 客户端示例 + +网络连接成功后就可以运行网络示例,先运行 TCP 客户端的示例。本示例将在 PC 上开启一个 TCP 服务器,在 IoT Board 板上开启一个 TCP 客户端,双方进行网络通信。 + +在示例工程中已经有 TCP 客户端程序 tcpclient_sample.c,功能是实现一个 TCP 客户端,能够接收并显示从服务端发送过来的信息,如果接收到开头是'q' 或'Q'的信息,那么直接退出程序,关闭 TCP 客户端。该程序导出了 tcpclient 命令到 FinSH 控制台,命令调用格式是 tcpclient URL PORT,其中 URL 是服务器地址,PORT 是端口号。示例代码如下所示: + +```c + +/* + * 程序清单:tcp 客户端 + * + * 这是一个 tcp 客户端的例程 + * 导出 tcpclient 命令到控制终端 + * 命令调用格式:tcpclient URL PORT + * URL:服务器地址 PORT::端口号 + * 程序功能:接收并显示从服务端发送过来的信息,接收到开头是 'q' 或 'Q' 的信息退出程序 +*/ +#include +#include /* 使用BSD socket,需要包含socket.h头文件 */ +#include +#include +#include + +#define BUFSZ 1024 + +static const char send_data[] = "This is TCP Client from RT-Thread."; /* 发送用到的数据 */ +void tcpclient(int argc, char**argv) +{ + int ret; + char *recv_data; + struct hostent *host; + int sock, bytes_received; + struct sockaddr_in server_addr; + const char *url; + int port; + + /* 接收到的参数小于 3 个 */ + if (argc < 3) + { + rt_kprintf("Usage: tcpclient URL PORT\n"); + rt_kprintf("Like: tcpclient 192.168.12.44 5000\n"); + return ; + } + + url = argv[1]; + port = strtoul(argv[2], 0, 10); + + /* 通过函数入口参数 url 获得 host 地址(如果是域名,会做域名解析) */ + host = gethostbyname(url); + + /* 分配用于存放接收数据的缓冲 */ + recv_data = rt_malloc(BUFSZ); + if (recv_data == RT_NULL) + { + rt_kprintf("No memory\n"); + return; + } + + /* 创建一个 socket,类型是 SOCKET_STREAM,TCP 类型 */ + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + /* 创建 socket 失败 */ + rt_kprintf("Socket error\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + return; + } + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 连接到服务端 */ + if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) + { + /* 连接失败 */ + rt_kprintf("Connect fail!\n"); + closesocket(sock); + + /* 释放接收缓冲 */ + rt_free(recv_data); + return; + } + + while (1) + { + /* 从 sock 连接中接收最大 BUFSZ - 1 字节数据 */ + bytes_received = recv(sock, recv_data, BUFSZ - 1, 0); + if (bytes_received < 0) + { + /* 接收失败,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nreceived error,close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + else if (bytes_received == 0) + { + /* 打印 recv 函数返回值为 0 的警告信息 */ + rt_kprintf("\nReceived warning,recv function return 0.\r\n"); + + continue; + } + + /* 有接收到数据,把末端清零 */ + recv_data[bytes_received] = '\0'; + + if (strncmp(recv_data, "q", 1) == 0 || strncmp(recv_data, "Q", 1) == 0) + { + /* 如果是首字母是 q 或 Q,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\n got a'q'or'Q',close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + else + { + /* 在控制终端显示收到的数据 */ + rt_kprintf("\nReceived data = %s", recv_data); + } + + /* 发送数据到 sock 连接 */ + ret = send(sock, send_data, strlen(send_data), 0); + if (ret < 0) + { + /* 接收失败,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nsend error,close the socket.\r\n"); + + rt_free(recv_data); + break; + } + else if (ret == 0) + { + /* 打印 send 函数返回值为 0 的警告信息 */ + rt_kprintf("\n Send warning,send function return 0.\r\n"); + } + } + return; +} +MSH_CMD_EXPORT(tcpclient, a tcp client sample); +``` + +运行该示例时,首先,在电脑上打开网络调试助手,开启一个 TCP 服务器。选择协议类型为 TCP +Server, 填入本机 IP 地址和端口 5000,如下图所示。 + +![网络调试工具界面](figures/net-tcp-s.png) + +然后就在 FinSH 控制台输入以下命令启动 TCP 客户端来连接 TCP 服务器: + +```c +msh />tcpclient 192.168.12.45 5000 // 按照实际情况输入 +Connect successful +``` + +当控制台输出 “Connect successful” 的日志信息,表示 TCP 连接被成功建立。接下来就可以进行数据通信了,在网络调试工具窗口,发送 Hello RT-Thread!,表示从 TCP 服务器发送一条数据给 TCP 客户端,如下图所示: + +![网络调试工具界面](figures/net-hello.png) + +FinSH 控制台上接收到数据后会输出相应的日志信息,可以看到: + +```c +msh >tcpclient 192.168.12.130 5000 +Connect successful +Received data = hello world +Received data = hello world +Received data = hello world +Received data = hello world +Received data = hello world + got a 'q' or 'Q',close the socket. +msh > +``` + +上面的信息表示 TCP 客户端接收到了从服务器发送的 5 条 “hello world” 数据,最后,从 TCP 服务器接收到退出指令’q’,TCP 客户端程序退出运行, 返回到 FinSH 控制台。 + +### UDP 客户端示例 + +这是一个 UDP 客户端的示例,本示例将在 PC 上开启一个 UDP 服务器,在 IoT Board 板上开启一个 UDP 客户端,双方进行网络通信。在示例工程中已经实现了一个 UDP 客户端程序,功能是发送数据到服务器端,示例代码如下所示: + +```c +/* + * 程序清单:udp 客户端 + * + * 这是一个 udp 客户端的例程 + * 导出 udpclient 命令到控制终端 + * 命令调用格式:udpclient URL PORT [COUNT = 10] + * URL:服务器地址 PORT:端口号 COUNT:可选参数 默认为 10 + * 程序功能:发送 COUNT 条数据到服务远端 +*/ +#include +#include /* 使用BSD socket,需要包含sockets.h头文件 */ +#include +#include +#include + +const char send_data[] = "This is UDP Client from RT-Thread.\n"; /* 发送用到的数据 */ + +void udpclient(int argc, char**argv) +{ + int sock, port, count; + struct hostent *host; + struct sockaddr_in server_addr; + const char *url; + + /* 接收到的参数小于 3 个 */ + if (argc < 3) + { + rt_kprintf("Usage: udpclient URL PORT [COUNT = 10]\n"); + rt_kprintf("Like: tcpclient 192.168.12.44 5000\n"); + return ; + } + + url = argv[1]; + port = strtoul(argv[2], 0, 10); + + if (argc> 3) + count = strtoul(argv[3], 0, 10); + else + count = 10; + + /* 通过函数入口参数 url 获得 host 地址(如果是域名,会做域名解析) */ + host = (struct hostent *) gethostbyname(url); + + /* 创建一个 socket,类型是 SOCK_DGRAM,UDP 类型 */ + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + { + rt_kprintf("Socket error\n"); + return; + } + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 总计发送 count 次数据 */ + while (count) + { + /* 发送数据到服务远端 */ + sendto(sock, send_data, strlen(send_data), 0, + (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); + + /* 线程休眠一段时间 */ + rt_thread_delay(50); + + /* 计数值减一 */ + count --; + } + + /* 关闭这个 socket */ + closesocket(sock); +} +``` + +运行该示例时,首先,在电脑上打开网络调试助手,开启一个 UDP 服务器。选择协议类型为 UDP,填入本机 IP 地址和端口 5000,如下图所示。 + +![网络调试工具界面](figures/net-udp-server.png) + +然后就可以在 FinSH 控制台输入以下命令来给 UDP 服务器发送数据, + +`msh />udpclient 192.168.12.45 1001 // 需按照真实情况输入 ` + +服务器会收到 10 条 This is UDP Client from RT-Thread. 的消息,如下图所示: + +![网络调试工具界面](figures/net-udp-client.png) diff --git a/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/mode.png b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/mode.png new file mode 100644 index 0000000000000000000000000000000000000000..ea224b7ac89f54dac2adc845c4c86e62d150bd60 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/mode.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_architecture.png b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..45d7c8dc757408dbfeb397a18a563ee0daa87d2e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_architecture.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_description.png b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_description.png new file mode 100644 index 0000000000000000000000000000000000000000..f686f5e2e3c2904fd5837708396bd0e3ca8e07cc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_description.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_sequence.png b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_sequence.png new file mode 100644 index 0000000000000000000000000000000000000000..57cd0d2bf6e41b54b4230df70195bccd9f88dc29 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_sequence.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_system.png b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_system.png new file mode 100644 index 0000000000000000000000000000000000000000..2f87e731bc3252830afda0b05c1e0d2c42313d8d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/pm/figures/pm_system.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/pm/pm.md b/rt-thread-version/rt-thread-standard/programming-manual/pm/pm.md new file mode 100644 index 0000000000000000000000000000000000000000..32a23a1cd6eaabd29253efd28e88d321c557ee9f --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/pm/pm.md @@ -0,0 +1,377 @@ +# 电源管理组件 + +嵌入式系统低功耗管理的目的在于满足用户对性能需求的前提下,尽可能降低系统能耗以延长设备待机时间。高性能与有限的电池能量在嵌入式系统中矛盾最为突出,硬件低功耗设计与软件低功耗管理的联合应用成为解决矛盾的有效手段。现在的各种 MCU 都或多或少的在低功耗方面提供了管理接口。比如对主控时钟频率的调整、工作电压的改变、总线频率的调整甚至关闭、外围设备工作时钟的关闭等。有了硬件上的支持,合理的软件设计就成为节能的关键,一般可以把低功耗管理分为三个类别: + +- 处理器电源管理 +主要实现方式:对 CPU 频率的动态管理,以及系统空闲时对工作模式的调整。 + +- 设备电源管理 +主要实现方式:关闭个别闲置设备 + +- 系统平台电源管理 +主要实现方式:针对特定系统平台的非常见设备具体定制。 + +随着物联网 (IoT) 的兴起,产品对功耗的需求越来越强烈。作为数据采集的传感器节点通常需要在电池供电时长期工作,而作为联网的 SOC 也需要有快速的响应功能和较低的功耗。 + +在产品开发的起始阶段,首先考虑是尽快完成产品的功能开发。在产品功能逐步完善之后,就需要加入电源管理 (Power Management,以下简称 PM) 功能。为了适应 IoT 的这种需求,RT-Thread 提供了电源管理组件。电源管理组件的理念是尽量透明,使得产品加入低功耗功能更加轻松。 + +## PM 组件介绍 + +RT-Thread 的 PM 组件采用分层设计思想,分离架构和芯片相关的部分,提取公共部分作为核心。在对上层提供通用的接口同时,也让底层驱动对组件的适配变得更加简单。 + +![PM组件概略图](figures/pm_system.png) + +### 主要特点 + +RT-Thread PM 组件主要特点如下所示: + +- 基于模式来管理功耗,空闲时动态调整工作模式,支持多个等级的休眠。 +- 对应用透明,组件在底层自动完成电源管理。 +- 支持运行模式下动态变频,根据模式自动更新设备的频率配置,确保在不同的运行模式都可以正常工作。 +- 支持设备电源管理,根据模式自动管理设备的挂起和恢复,确保在不同的休眠模式下可以正确的挂起和恢复。 +- 支持可选的休眠时间补偿,让依赖 OS Tick 的应用可以透明使用。 +- 向上层提供设备接口,如果打开了 devfs 组件,那么也可以通过文件系统接口访问。 + +### 工作原理 + +低功耗的本质是系统空闲时 CPU 停止工作,中断或事件唤醒后继续工作。在 RTOS 中,通常包含一个 IDLE 任务,该任务的优先级最低且一直保持就绪状态,当高优先级任务未就绪时,OS 执行 IDLE 任务。一般地,未进行低功耗处理时,CPU 在 IDLE 任务中循环执行空指令。RT-Thread 的电源管理组件在 IDLE 任务中,通过对 CPU 、时钟和设备等进行管理,从而有效降低系统的功耗。 + +![PM工作原理](figures/pm_description.png) + +在上图所示,当高优先级任务运行结束或被挂起时,系统将进入 IDLE 任务中。在 IDLE 任务执行后,它将判断系统是否可以进入到休眠状态(以节省功耗)。如果可以进入休眠, +将根据芯片情况关闭部分硬件模块,OS Tick 也非常有可能进入暂停状态。此时电源管理框架会根据系统定时器情况,计算出下一个超时时间点,并设置低功耗定时器,让设备能够在这个时刻点唤醒,并进行后续的工作。当系统被(低功耗定时器中断或其他唤醒中断源)唤醒后,系统也需要知道睡眠时间长度是多少,并对OS Tick 进行补偿,让系统的OS tick值调整为一个正确的值。 + +## 设计架构 + +在 RT-Thread PM 组件中,外设或应用通过投票机制对所需的功耗模式进行投票,当系统空闲时,根据投票数决策出合适的功耗模式,调用抽象接口,控制芯片进入低功耗状态,从而降低系统功耗。当未进行进行任何投票时,会以默认模式进入(通常为空闲模式)。与应用不同,某些外设可能在进入低功耗状态时执行特定操作,退出低功耗时采取措施恢复,此时可以通过注册PM设备来实现。通过注册 PM 设备,在进入低功耗状态之前,会触发注册设备的 suspend 回调,开发者可在回调里执行自己的操作;类似地,从低功耗状态退出时,也会触发 resume 回调。 + +![PM设计架构](figures/pm_architecture.png) + +## 低功耗状态和模式 + +RT-Thread PM 组件将系统划分为两种状态:运行状态(RUN)和休眠状态(Sleep)。 +运行状态控制 CPU 的频率,适用于变频场景;休眠状态根据 SOC 特性实现休眠 CPU,以降低功耗。两种状态分别使用不同的 API 接口,独立控制。 + +- 休眠状态 + +休眠状态也就是通常意义上的低功耗状态,通过关闭外设、执行 SOC 电源管理接口,降低系统功耗。 +休眠状态又分为六个模式,呈现为金字塔的形式。随着模式增加,功耗逐级递减的特点。下面是休眠状态下模式的定义,开发者可根据具体的 SOC 实现相应的模式,但需要遵循功耗逐级降低的特点。 + +| 模式 | 级别 | 描述 | +| -- | -- | -- | +| PM_SLEEP_MODE_NONE | 0 | 系统处于活跃状态,未采取任何的降低功耗状态 | +| PM_SLEEP_MODE_IDLE | 1 | 空闲模式,该模式在系统空闲时停止 CPU 和部分时钟,任意事件或中断均可以唤醒 | +| PM_SLEEP_MODE_LIGHT | 2 | 轻度睡眠模式,CPU 停止,多数时钟和外设停止,唤醒后需要进行时间补偿 | +| PM_SLEEP_MODE_DEEP | 3 | 深度睡眠模式,CPU 停止,仅少数低功耗外设工作,可被特殊中断唤醒 | +| PM_SLEEP_MODE_STANDBY | 4 | 待机模式,CPU 停止,设备上下文丢失(可保存至特殊外设),唤醒后通常复位 | +| PM_SLEEP_MODE_SHUTDOWN | 5 | 关断模式,比 Standby 模式功耗更低, 上下文通常不可恢复, 唤醒后复位 | + +**注意:**因各家芯片差异,功耗管理的实现也不尽相同,上述的描述仅给出一些推荐的场景,并非一定需要实现所有。开发者可根据自身情况选择其中的几种进行实现,但是需要遵循级别越高,功耗越低的原则! + +- 运行状态 + +运行状态通常用于改变 CPU 的运行频率,独立于休眠模式。当前运行状态划分了四个等级:高速、正常、中速、低速,如下: + +| 模式 | 描述 | +| -- | -- | +| PM_RUN_MODE_HIGH_SPEED | 高速模式,适用于一些超频的场景 | +| PM_RUN_MODE_NORMAL_SPEED | 正常模式,该模式作为默认的运行状态 | +| PM_RUN_MODE_MEDIUM_SPEED | 中速模式,降低 CPU 运行速度,从而降低运行功耗 | +| PM_RUN_MODE_LOW_SPEED | 低速模式,CPU 频率进一步降低 | + +### 模式的请求和释放 + +在 PM 组件里,上层应用可以通过请求和释放休眠模式主动参与功耗管理。应用可以根据场景请求不同的休眠模式,并在处理完毕后释放,只要有任意一个应用或设备请求高等级的功耗模式,就不会切换到比它更低的模式。因此,休眠模式的请求和释放的操作通常成对出现,可用于对某个阶段进行保护,如外设的 DMA 传输过程。 + +### 对模式变化敏感的设备 + +在 PM 组件里,切换到新的运行模式可能会导致 CPU 频率发生变化,如果外设和 CPU 共用一部分时钟,那外设的时钟就会受到影响;在进入新的休眠模式,大部分时钟源会被停止,如果外设不支持休眠的冻结功能,那么从休眠唤醒的时候,外设的时钟就需要重新配置外设。所以 PM 组件里支持了 PM 模式敏感的 PM 设备。使得设备在切换到新的运行模式或者新的休眠模式都能正常的工作。该功能需要底层驱动实现相关的接口并注册为对模式变化敏感的设备。 + +## 调用流程 + +![PM时序图](figures/pm_sequence.png) + +首先应用设置进出休眠状态的回调函数,然后调用 rt_pm_request 请求休眠模式,触发休眠操作;PM 组件在系统空闲时检查休眠模式计数,根据投票数给出推荐的模式;接着 PM 组件调用 notfiy 通知应用,告知即将进入休眠模式;然后对注册的 PM 设备执行挂起操作,返回 OK 后执行 SOC 实现的的休眠模式,系统进入休眠状态(如果使能时间补偿,休眠之前会先启动低功耗定时器)。此时 CPU 停止工作,等待事件或者中断唤醒。当系统被唤醒后,由于全局中断为关闭状态,系统继续从该处执行,获取睡眠时间补偿系统的心跳,依次唤醒设备,通知应用从休眠模式退出。如此一个周期执行完毕,退出,等待系统下次空闲。 + +## API 介绍 + +- 请求休眠模式 +```c +void rt_pm_request(uint8_t sleep_mode); +``` +| 参数 | 模式 | +| -- | -- | +| sleep_mode | 请求的休眠模式等级 | + +sleep_mode 取以下枚举值: +```c +enum +{ + /* sleep modes */ + PM_SLEEP_MODE_NONE = 0, /* 活跃状态 */ + PM_SLEEP_MODE_IDLE, /* 空闲模式(默认) */ + PM_SLEEP_MODE_LIGHT, /* 轻度睡眠模式 */ + PM_SLEEP_MODE_DEEP, /* 深度睡眠模式 */ + PM_SLEEP_MODE_STANDBY, /* 待机模式 */ + PM_SLEEP_MODE_SHUTDOWN, /* 关断模式 */ + PM_SLEEP_MODE_MAX, +}; +``` +调用该函数会将对应的模式计数加1,并锁住该模式。此时如果请求更低级别的功耗模式,将无法进入,只有释放(解锁)先前请求的模式后,系统才能进入更低的模式;向更高的功耗模式请求则不受此影响。该函数需要和 rt_pm_release 配合使用,用于对某一阶段或过程进行保护。 + +- 释放休眠模式 +```c +void rt_pm_release(uint8_t sleep_mode); +``` +| 参数 | 模式 | +| -- | -- | +| sleep_mode | 释放的休眠模式等级 | + +调用该函数会将对应的模式计数减1,配合 rt_pm_request 使用,释放先前请求的模式。 + +- 设置运行模式 +```c +int rt_pm_run_enter(uint8_t run_mode); +``` +| 参数 | 模式 | +| -- | -- | +| run_mode | 设置的运行模式等级 | + +run_mode 可以取以下枚举值: +```c +enum +{ + /* run modes*/ + PM_RUN_MODE_HIGH_SPEED = 0, /* 高速 */ + PM_RUN_MODE_NORMAL_SPEED, /* 正常(默认) */ + PM_RUN_MODE_MEDIUM_SPEED, /* 中速 */ + PM_RUN_MODE_LOW_SPEED, /* 低速 */ + PM_RUN_MODE_MAX, +}; +``` +调用该函数改变 CPU 的运行频率,从而降低运行时的功耗。此函数只提供级别,具体的 CPU 频率应在移植阶段视实际情况而定。 + +- 设置进入/退出休眠模式的回调通知 +```c +void rt_pm_notify_set(void (*notify)(uint8_t event, uint8_t mode, void *data), void *data); +``` +| 参数 | 模式 | +| -- | -- | +| notify | 应用的回调函数 | +| data | 私有数据 | + +event 为以下两个枚举值,分别标识进入/退出休眠模式。 +```c +enum +{ + RT_PM_ENTER_SLEEP = 0, /* 进入休眠模式 */ + RT_PM_EXIT_SLEEP, /* 退出休眠模式 */ +}; + +``` + +## 使用说明 + +- 设置低功耗等级 + +如果系统需要进入指定指定等级的低功耗,可通过调用 rt_pm_request 实现。如进入深度睡眠模式: +```c +/* 请求进入深度睡眠模式 */ +rt_pm_request(PM_SLEEP_MODE_DEEP); +``` +**注意:**如果程序的其他地方请求了更高的功耗模式,如 Light Mode 或者 Idle Mode,则需要释放相应的模式后,深度睡眠模式才能被进入。 + +- 保护某个阶段或者过程 + +特殊情况下,比如某个阶段并不允许系统进入更低的功耗模式,此时可以通过 rt_pm_request 和 rt_pm_release 对该过程进行保护。如 I2C 读取数据期间,不允许进入深度睡眠模式(可能会导致外设停止工作),因此可以做如下处理: +```c +/* 请求轻度睡眠模式(I2C外设该模式下正常工作) */ +rt_pm_request(PM_SLEEP_MODE_LIGHT); + +/* 读取数据过程 */ + +/* 释放该模式 */ +rt_pm_release(PM_SLEEP_MODE_LIGHT); + +``` + +- 改变 CPU 运行频率 + +降低运行频率能有效减少系统的功耗,通过 rt_pm_run_enter 接口改变 CPU 的运行频率。一般地,降频率意味着 CPU 性能降低、处理速度减慢,可能会导致任务的执行时间增长,需要合理进行权衡。 + +```c +/* 进入中等速度运行模式 */ +rt_pm_run_enter(PM_RUN_MODE_MEDIUM_SPEED); +``` + +## 移植说明 + +低功耗管理是一项十分细致的任务,开发者在移植时,不仅需要充分了解芯片本身的功耗管理,还需熟悉板卡的外围电路,进入低功耗状态时逐一处理,避免出现外围电路漏电拉升整体功耗的情况。RT-Thread PM 组件对各部分进行抽象,提供不同的 ops 接口供开发者适配。移植时需要关注的部分如下: + +```c +/** + * low power mode operations + */ +struct rt_pm_ops +{ + /* sleep 接口用于适配芯片相关的低功耗特性 */ + void (*sleep)(struct rt_pm *pm, uint8_t mode); + /* run 接口用于运行模式的变频和变电压 */ + void (*run)(struct rt_pm *pm, uint8_t mode); + /* 以下三个接口用于心跳停止后启动低功耗定时器以补偿心跳 */ + void (*timer_start)(struct rt_pm *pm, rt_uint32_t timeout); + void (*timer_stop)(struct rt_pm *pm); + rt_tick_t (*timer_get_tick)(struct rt_pm *pm); +}; + +/* 该 ops 用于管理外设的功耗 */ +struct rt_device_pm_ops +{ + /* 进入休眠模式之前挂起外设,返回非 0 表示外设未就绪,不能进入 */ + int (*suspend)(const struct rt_device *device, uint8_t mode); + /* 从休眠模式退出后恢复外设 */ + void (*resume)(const struct rt_device *device, uint8_t mode); + /* 运行状态下模式改变通知外设处理 */ + int (*frequency_change)(const struct rt_device *device, uint8_t mode); +}; + +/* 注册一个 PM 设备 */ +void rt_pm_device_register(struct rt_device *device, const struct rt_device_pm_ops *ops); +``` + +- 芯片自身的功耗特性 +```c +void (*sleep)(struct rt_pm *pm, uint8_t mode); +``` +各个芯片对低功耗模式的定义和管理不同,PM 组件将芯片相关的特性抽象为 sleep 接口。该接口适配芯片相关的低功耗管理,当要进入不同的休眠模式时,一些硬件相关的配置,保存等相关处理。 + +- 休眠的时间补偿 + +```c +void (*timer_start)(struct rt_pm *pm, rt_uint32_t timeout); +void (*timer_stop)(struct rt_pm *pm); +rt_tick_t (*timer_get_tick)(struct rt_pm *pm); +``` + +在某些休眠模式下(Light Sleep 或 Deep Sleep),内核心跳定时器可能会被停止,此时需要对启动一个定时器对休眠的时间进行计量,唤醒后对心跳补偿。时间补偿的定时器必须在该模式下仍能够正常工作,并唤醒系统,否则没有意义! + +`timer_start` :启动低功耗定时器,入参为最近的下次任务就绪时间; +`timer_get_tick` :获取系统被唤醒的睡眠时间; +`timer_stop` :用于系统唤醒后停止低功耗定时器。 + +**注意**:休眠模式的时间补偿需要在初始化阶段通过设置 timer_mask 的对应模式的 bit 控制开启。例如需要开启 Deep Sleep 模式下的时间补偿,在实现 timer 相关的 ops 接口后,初始化时设置相应的bit: + +```c +rt_uint8_t timer_mask = 0; + +/* 设置 Deep Sleep 模式对应的 bit,使能休眠时间补偿 */ +timer_mask = 1UL << PM_SLEEP_MODE_DEEP; + +/* initialize system pm module */ +rt_system_pm_init(&_ops, timer_mask, RT_NULL); +``` + +- 运行模式变频 + +``` +void (*run)(struct rt_pm *pm, uint8_t mode); +``` +运行模式的变频通过适配 rt_pm_ops 中的 run 接口实现,根据使用场景选择合适的频率。 + +- 外设的功耗管理 + +外设的功耗处理是低功耗管理系统的一个重要部分,在进入某些级别的休眠模式时,通常需要对一些外设进行处理,如清空 DMA,关闭时钟或是设置 IO 为复位状态;并在退出休眠后进行恢复。 +此类情况可以通过 `rt_pm_device_register` 接口注册 PM 设备,在进入/退出休眠模式时,会执行注册设备的 `suspend` 和 `resume` 回调;运行模式下的频率改变同样会触发设备的 `frequency_change` 回调。 + +更详细的移植案例可以参考 RT-Thread 仓库中的 stm32l476-nucleo bsp。 + +## MSH 命令 + +### 请求休眠模式 + +可以使用 `pm_request` 命令请求模式,使用示例如下所示: + +```c +msh />pm_request 0 +msh /> +``` + +参数取值为 0-5,分别对应以下枚举值: + +```c +enum +{ + /* sleep modes */ + PM_SLEEP_MODE_NONE = 0, /* 活跃状态 */ + PM_SLEEP_MODE_IDLE, /* 空闲模式(默认) */ + PM_SLEEP_MODE_LIGHT, /* 轻度睡眠模式 */ + PM_SLEEP_MODE_DEEP, /* 深度睡眠模式 */ + PM_SLEEP_MODE_STANDBY, /* 待机模式 */ + PM_SLEEP_MODE_SHUTDOWN, /* 关断模式 */ + PM_SLEEP_MODE_MAX, +}; +``` + +### 释放休眠模式 + +可以使用 `pm_release` 命令释放模式,参数取值为 0-5,使用示例如下所示: + +```c +msh />pm_release 0 +msh /> +``` + +### 设置运行模式 + +可以使用 `pm_run` 命令切换运行模式,参数取值 0-3,使用示例如下所示 +```c +msh />pm_run 2 +msh /> +``` +参数取值 0-3 +``` +enum +{ + /* run modes*/ + PM_RUN_MODE_HIGH_SPEED = 0, + PM_RUN_MODE_NORMAL_SPEED, + PM_RUN_MODE_MEDIUM_SPEED, + PM_RUN_MODE_LOW_SPEED, + PM_RUN_MODE_MAX, +}; +``` +### 查看模式状态 + +可以使用 `pm_dump` 命令查看 PM 组件的模式状态,使用示例如下所示: + +```c +msh > +msh >pm_dump +| Power Management Mode | Counter | Timer | ++-----------------------+---------+-------+ +| None Mode | 0 | 0 | +| Idle Mode | 0 | 0 | +| LightSleep Mode | 1 | 0 | +| DeepSleep Mode | 0 | 1 | +| Standby Mode | 0 | 0 | +| Shutdown Mode | 0 | 0 | ++-----------------------+---------+-------+ +pm current sleep mode: LightSleep Mode +pm current run mode: Normal Speed +msh > +``` + +在 `pm_dump` 的模式列表里,休眠模式的优先级是从高到低排列,`Counter` 一栏标识请求的计数值,图中表明 LightSleep 模式被请求一次,因此当前工作在轻度休眠状态;`Timer` + 一栏标识是否开启睡眠时间补偿,图中仅深度睡眠(DeepSleep)模式进行时间补偿。 +最下面分别标识当前所处的休眠模式及运行模式等级。 + +## 常见问题及调试方法 + +- 系统进入低功耗模式后功耗偏高 + +根据外围电路,检查设备是否处于合理状态,避免出现外设漏电的情况; +根据产品自身情况,关闭相应休眠模式期间不使用的外设和时钟。 + +- 无法进入更低等级的功耗 + +检查是否未释放高等级的功耗模式,RT-Thread 的 PM 组件使用 `rt_pm_request` 请求休眠模式,当请求高功耗模式后,未进行释放,系统将无法切换至更低等级的功耗。例如,在请求 Light Sleep 模式后,接着请求 Deep Sleep 模式,此时系统仍然处于 Light Sleep 模式。通过调用接口 `rt_pm_request` 释放 Light Sleep 模式,系统会自动切换到 Deep Sleep 模式。 diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10IoTboard.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10IoTboard.png new file mode 100644 index 0000000000000000000000000000000000000000..3771fedd8c7371c9adb32f0b750102cfc7b61400 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10IoTboard.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10IoTboard1.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10IoTboard1.png new file mode 100644 index 0000000000000000000000000000000000000000..4c685019ad49a68b9133125cf655ec7e07f16969 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10IoTboard1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10board2.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10board2.png new file mode 100644 index 0000000000000000000000000000000000000000..c0273858b5a0435feeb3ac86f06f5ccbc47e16fa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10board2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10path.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10path.png new file mode 100644 index 0000000000000000000000000000000000000000..e60cc1da2d2215897e22d760cc9b906b83dd7b4b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10path.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10pendsv.jpg b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10pendsv.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c97cc11879585fa2549e696699f474d30f266c68 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10pendsv.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10project1.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10project1.png new file mode 100644 index 0000000000000000000000000000000000000000..7258f0b4dadfe71fe92ec74efcc7c8108e178a99 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10project1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10project2.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10project2.png new file mode 100644 index 0000000000000000000000000000000000000000..057ee5129d9feed81c3b1d356bd1990f97eba3bc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10project2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty1.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty1.png new file mode 100644 index 0000000000000000000000000000000000000000..26557242eaa380ea104cc257cf5400bcc2cafef9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty2.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty2.png new file mode 100644 index 0000000000000000000000000000000000000000..e53a66f542de0a8acce316019ba4eb675d42ba1e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty3.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty3.png new file mode 100644 index 0000000000000000000000000000000000000000..a5518ac3e9ccbf478a480d0a8d68b08adc7586e8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty3.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty4.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty4.png new file mode 100644 index 0000000000000000000000000000000000000000..6a3422b894e74ac714a4c8ec713b371060d199a5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10putty4.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10stack.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10stack.png new file mode 100644 index 0000000000000000000000000000000000000000..029cb880ee58cb2828dbe209cb366688f4da75de Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10stack.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10switch.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10switch.png new file mode 100644 index 0000000000000000000000000000000000000000..20a19c94cf35c31d2a6bb4a1e5ced90fa57549c5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10switch.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10switch2.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10switch2.png new file mode 100644 index 0000000000000000000000000000000000000000..7422766b58572026f44c9391eef06cc81b66c2c0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10switch2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10ths_env1.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10ths_env1.png new file mode 100644 index 0000000000000000000000000000000000000000..68caec49baef4a6fca2528590bd7c82cd84420e1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10ths_env1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10ths_env2.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10ths_env2.png new file mode 100644 index 0000000000000000000000000000000000000000..fed5ee1d880456b2d48cd4755a2d499dac52fb8c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/10ths_env2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/252bfe8648541f2036d0a2aa040776cf.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/252bfe8648541f2036d0a2aa040776cf.png new file mode 100644 index 0000000000000000000000000000000000000000..057ee5129d9feed81c3b1d356bd1990f97eba3bc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/252bfe8648541f2036d0a2aa040776cf.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/681450fc51f107be479c393c956c313a.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/681450fc51f107be479c393c956c313a.png new file mode 100644 index 0000000000000000000000000000000000000000..e60cc1da2d2215897e22d760cc9b906b83dd7b4b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/681450fc51f107be479c393c956c313a.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/6c08b8b3928e21053abb9b09db333e25.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/6c08b8b3928e21053abb9b09db333e25.png new file mode 100644 index 0000000000000000000000000000000000000000..29bf2a505d6ec58c2e9d68c54e78d4fc057b2439 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/6c08b8b3928e21053abb9b09db333e25.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/83ea9f351b86425e32343d28a8e2179b.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/83ea9f351b86425e32343d28a8e2179b.png new file mode 100644 index 0000000000000000000000000000000000000000..5b1fcf784ce284cd655cfcdb37707bdc3680ebe6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/83ea9f351b86425e32343d28a8e2179b.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/c0fde6c0e89981fefdff6cf0d523ec21.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/c0fde6c0e89981fefdff6cf0d523ec21.png new file mode 100644 index 0000000000000000000000000000000000000000..f297af7bc0ea305b5103a595836f12ea3855f9e3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/c0fde6c0e89981fefdff6cf0d523ec21.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/c70cb10dd23808162907cf993e3b53f2.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/c70cb10dd23808162907cf993e3b53f2.png new file mode 100644 index 0000000000000000000000000000000000000000..fab107038e4d1ea06b20706922cb06d881cda32c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/c70cb10dd23808162907cf993e3b53f2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/df52bd3e28126182d945bd487af0debf.png b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/df52bd3e28126182d945bd487af0debf.png new file mode 100644 index 0000000000000000000000000000000000000000..e53a66f542de0a8acce316019ba4eb675d42ba1e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/porting/figures/df52bd3e28126182d945bd487af0debf.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/porting/porting.md b/rt-thread-version/rt-thread-standard/programming-manual/porting/porting.md new file mode 100644 index 0000000000000000000000000000000000000000..d84266532e0d25c426507d50c030ede6e7d1d554 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/porting/porting.md @@ -0,0 +1,379 @@ +# 内核移植 + +经过前面内核章节的学习,大家对 RT-Thread 也有了不少的了解,但是如何将 RT-Thread 内核移植到不同的硬件平台上,很多人还不一定熟悉。内核移植就是指将 RT-Thread 内核在不同的芯片架构、不同的板卡上运行起来,能够具备线程管理和调度,内存管理,线程间同步和通信、定时器管理等功能。移植可分为 CPU 架构移植和 BSP(Board support package,板级支持包)移植两部分。 + +本章将展开介绍 CPU 架构移植和 BSP 移植,CPU 架构移植部分会结合 Cortex-M CPU 架构进行介绍,因此有必要回顾下上一章[《中断管理》](../interrupt/interrupt.md)介绍的 “Cortex-M CPU 架构基础” 的内容,本章最后以实际移植到一个开发板的示例展示 RT-Thread 内核移植的完整过程,读完本章,我们将了解如何完成 RT-Thread 的内核移植。 + +## CPU 架构移植 + +在嵌入式领域有多种不同 CPU 架构,例如 Cortex-M、ARM920T、MIPS32、RISC-V 等等。为了使 RT-Thread 能够在不同 CPU 架构的芯片上运行,RT-Thread 提供了一个 libcpu 抽象层来适配不同的 CPU 架构。libcpu 层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等。 + +RT-Thread 的 libcpu 抽象层向下提供了一套统一的 CPU 架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容。下表是 CPU 架构移植需要实现的接口和变量。 + + libcpu 移植相关 API + +|**函数和变量** |**描述** | +|--------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------| +| rt_base_t rt_hw_interrupt_disable(void); | 关闭全局中断 | +| void rt_hw_interrupt_enable(rt_base_t level); | 打开全局中断 | +| rt_uint8_t \*rt_hw_stack_init(void \*tentry, void \*parameter, rt_uint8_t \*stack_addr, void \*texit); | 线程栈的初始化,内核在线程创建和线程初始化里面会调用这个函数 | +| void rt_hw_context_switch_to(rt_uint32 to); | 没有来源线程的上下文切换,在调度器启动第一个线程的时候调用,以及在 signal 里面会调用 | +| void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); | 从 from 线程切换到 to 线程,用于线程和线程之间的切换 | +| void rt_hw_context_switch_interrupt(rt_uint32 from, rt_uint32 to); | 从 from 线程切换到 to 线程,用于中断里面进行切换的时候使用 | +| rt_uint32_t rt_thread_switch_interrupt_flag; | 表示需要在中断里进行切换的标志 | +| rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; | 在线程进行上下文切换时候,用来保存 from 和 to 线程 | + +### 实现全局中断开关 + +无论内核代码还是用户的代码,都可能存在一些变量,需要在多个线程或者中断里面使用,如果没有相应的保护机制,那就可能导致临界区问题。RT-Thread 里为了解决这个问题,提供了一系列的线程间同步和通信机制来解决。但是这些机制都需要用到 libcpu 里提供的全局中断开关函数。它们分别是: + +```c +/* 关闭全局中断 */ +rt_base_t rt_hw_interrupt_disable(void); + +/* 打开全局中断 */ +void rt_hw_interrupt_enable(rt_base_t level); +``` + +下面介绍在 Cortex-M 架构上如何实现这两个函数,前文中曾提到过,Cortex-M 为了快速开关中断,实现了 CPS 指令,可以用在此处。 + +```c +CPSID I ;PRIMASK=1, ; 关中断 +CPSIE I ;PRIMASK=0, ; 开中断 +``` + +#### 关闭全局中断 + +在 rt_hw_interrupt_disable() 函数里面需要依序完成的功能是: + +1). 保存当前的全局中断状态,并把状态作为函数的返回值。 + +2). 关闭全局中断。 + +基于 MDK,在 Cortex-M 内核上实现关闭全局中断,如下代码所示: + + 关闭全局中断 + +```c +;/* +; * rt_base_t rt_hw_interrupt_disable(void); +; */ +rt_hw_interrupt_disable PROC ;PROC 伪指令定义函数 + EXPORT rt_hw_interrupt_disable ;EXPORT 输出定义的函数,类似于 C 语言 extern + MRS r0, PRIMASK ; 读取 PRIMASK 寄存器的值到 r0 寄存器 + CPSID I ; 关闭全局中断 + BX LR ; 函数返回 + ENDP ;ENDP 函数结束 +``` + +上面的代码首先是使用 MRS 指令将 PRIMASK 寄存器的值保存到 r0 寄存器里,然后使用 “CPSID I” 指令关闭全局中断,最后使用 BX 指令返回。r0 存储的数据就是函数的返回值。中断可以发生在 “MRS r0, PRIMASK” 指令和 “CPSID I” 之间,这并不会导致全局中断状态的错乱。 + +关于寄存器在函数调用的时候和在中断处理程序里是如何管理的,不同的 CPU 架构有不同的约定。在 ARM 官方手册《Procedure Call Standard for the ARM ® Architecture》里可以找到关于 Cortex-M 的更详细的介绍寄存器使用的约定。 + +#### 打开全局中断 + +在 rt_hw_interrupt_enable(rt_base_t level) 里,将变量 level 作为需要恢复的状态,覆盖芯片的全局中断状态。 + +基于 MDK,在 Cortex-M 内核上的实现打开全局中断,如下代码所示: + + 打开全局中断 + +```c +;/* +; * void rt_hw_interrupt_enable(rt_base_t level); +; */ +rt_hw_interrupt_enable PROC ; PROC 伪指令定义函数 + EXPORT rt_hw_interrupt_enable ; EXPORT 输出定义的函数,类似于 C 语言 extern + MSR PRIMASK, r0 ; 将 r0 寄存器的值写入到 PRIMASK 寄存器 + BX LR ; 函数返回 + ENDP ; ENDP 函数结束 +``` + +上面的代码首先是使用 MSR 指令将 r0 的值寄存器写入到 PRIMASK 寄存器,从而恢复之前的中断状态。 + +### 实现线程栈初始化 + +在动态创建线程和初始化线程的时候,会使用到内部的线程初始化函数_rt_thread_init(),_rt_thread_init() 函数会调用栈初始化函数 rt_hw_stack_init(),在栈初始化函数里会手动构造一个上下文内容,这个上下文内容将被作为每个线程第一次执行的初始值。上下文在栈里的排布如下图所示: + +![栈里的上下文信息](figures/10stack.png) + +下代码是栈初始化的代码: + + 在栈里构建上下文 + +```c +rt_uint8_t *rt_hw_stack_init(void *tentry, + void *parameter, + rt_uint8_t *stack_addr, + void *texit) +{ + struct stack_frame *stack_frame; + rt_uint8_t *stk; + unsigned long i; + + /* 对传入的栈指针做对齐处理 */ + stk = stack_addr + sizeof(rt_uint32_t); + stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8); + stk -= sizeof(struct stack_frame); + + /* 得到上下文的栈帧的指针 */ + stack_frame = (struct stack_frame *)stk; + + /* 把所有寄存器的默认值设置为 0xdeadbeef */ + for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++) + { + ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef; + } + + /* 根据 ARM APCS 调用标准,将第一个参数保存在 r0 寄存器 */ + stack_frame->exception_stack_frame.r0 = (unsigned long)parameter; + /* 将剩下的参数寄存器都设置为 0 */ + stack_frame->exception_stack_frame.r1 = 0; /* r1 寄存器 */ + stack_frame->exception_stack_frame.r2 = 0; /* r2 寄存器 */ + stack_frame->exception_stack_frame.r3 = 0; /* r3 寄存器 */ + /* 将 IP(Intra-Procedure-call scratch register.) 设置为 0 */ + stack_frame->exception_stack_frame.r12 = 0; /* r12 寄存器 */ + /* 将线程退出函数的地址保存在 lr 寄存器 */ + stack_frame->exception_stack_frame.lr = (unsigned long)texit; + /* 将线程入口函数的地址保存在 pc 寄存器 */ + stack_frame->exception_stack_frame.pc = (unsigned long)tentry; + /* 设置 psr 的值为 0x01000000L,表示默认切换过去是 Thumb 模式 */ + stack_frame->exception_stack_frame.psr = 0x01000000L; + + /* 返回当前线程的栈地址 */ + return stk; +} +``` + +### 实现上下文切换 + +在不同的 CPU 架构里,线程之间的上下文切换和中断到线程的上下文切换,上下文的寄存器部分可能是有差异的,也可能是一样的。在 Cortex-M 里面上下文切换都是统一使用 PendSV 异常来完成,切换部分并没有差异。但是为了能适应不同的 CPU 架构,RT-Thread 的 libcpu 抽象层还是需要实现三个线程切换相关的函数: + +1) rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。 + +2) rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。 + +3) rt_hw_context_switch_interrupt ():在中断环境下,从当前线程切换到目标线程。 + +在线程环境下进行切换和在中断环境进行切换是存在差异的。线程环境下,如果调用 rt_hw_context_switch() 函数,那么可以马上进行上下文切换;而在中断环境下,需要等待中断处理函数完成之后才能进行切换。 + +由于这种差异,在 ARM9 等平台,rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 的实现并不一样。在中断处理程序里如果触发了线程的调度,调度函数里会调用 rt_hw_context_switch_interrupt() 触发上下文切换。中断处理程序里处理完中断事务之后,中断退出之前,检查 rt_thread_switch_interrupt_flag 变量,如果该变量的值为 1,就根据 rt_interrupt_from_thread 变量和 rt_interrupt_to_thread 变量,完成线程的上下文切换。 + +在 Cortex-M 处理器架构里,基于自动部分压栈和 PendSV 的特性,上下文切换可以实现地更加简洁。 + +线程之间的上下文切换,如下图表示: + +![线程之间的上下文切换](figures/10ths_env1.png) + +硬件在进入 PendSV 中断之前自动保存了 from 线程的 PSR、PC、LR、R12、R3-R0 寄存器,然后 PendSV 里保存 from 线程的 R11\~R4 寄存器,以及恢复 to 线程的 R4\~R11 寄存器,最后硬件在退出 PendSV 中断之后,自动恢复 to 线程的 R0\~R3、R12、LR、PC、PSR 寄存器。 + +中断到线程的上下文切换可以用下图表示: + +![中断到线程的切换](figures/10ths_env2.png) + +硬件在进入中断之前自动保存了 from 线程的 PSR、PC、LR、R12、R3-R0 寄存器,然后触发了 PendSV 异常。在 PendSV 异常处理函数里保存 from 线程的 R11\~R4 寄存器,以及恢复 to 线程的 R4\~R11 寄存器,最后硬件在退出 PendSV 中断之后,自动恢复 to 线程的 R0\~R3、R12、PSR、PC、LR 寄存器。 + +显然,在 Cortex-M 内核里 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 功能一致,都是在 PendSV 里完成剩余上下文的保存和回复。所以我们仅仅需要实现一份代码,简化移植的工作。 + +#### 实现 rt_hw_context_switch_to() + +rt_hw_context_switch_to() 只有目标线程,没有来源线程。这个函数里实现切换到指定线程的功能,下图是流程图: + +![rt_hw_context_switch_to() 流程图](figures/10switch.png) + +在 Cortex-M3 内核上的 rt_hw_context_switch_to() 实现(基于 MDK),如下代码所示: + + MDK 版 rt_hw_context_switch_to() 实现 + +```c +;/* +; * void rt_hw_context_switch_to(rt_uint32 to); +; * r0 --> to +; * this fucntion is used to perform the first thread switch +; */ +rt_hw_context_switch_to PROC + EXPORT rt_hw_context_switch_to + ; r0 的值是一个指针,该指针指向 to 线程的线程控制块的 SP 成员 + ; 将 r0 寄存器的值保存到 rt_interrupt_to_thread 变量里 + LDR r1, =rt_interrupt_to_thread + STR r0, [r1] + + ; 设置 from 线程为空,表示不需要从保存 from 的上下文 + LDR r1, =rt_interrupt_from_thread + MOV r0, #0x0 + STR r0, [r1] + + ; 设置标志为 1,表示需要切换,这个变量将在 PendSV 异常处理函数里切换的时被清零 + LDR r1, =rt_thread_switch_interrupt_flag + MOV r0, #1 + STR r0, [r1] + + ; 设置 PendSV 异常优先级为最低优先级 + LDR r0, =NVIC_SYSPRI2 + LDR r1, =NVIC_PENDSV_PRI + LDR.W r2, [r0,#0x00] ; read + ORR r1,r1,r2 ; modify + STR r1, [r0] ; write-back + + ; 触发 PendSV 异常 (将执行 PendSV 异常处理程序) + LDR r0, =NVIC_INT_CTRL + LDR r1, =NVIC_PENDSVSET + STR r1, [r0] + + ; 放弃芯片启动到第一次上下文切换之前的栈内容,将 MSP 设置启动时的值 + LDR r0, =SCB_VTOR + LDR r0, [r0] + LDR r0, [r0] + MSR msp, r0 + + ; 使能全局中断和全局异常,使能之后将进入 PendSV 异常处理函数 + CPSIE F + CPSIE I + + ; 不会执行到这里 + ENDP +``` + +#### 实现 rt_hw_context_switch()/ rt_hw_context_switch_interrupt() + +函数 rt_hw_context_switch() 和函数 rt_hw_context_switch_interrupt() 都有两个参数,分别是 from 线程和 to 线程。它们实现从 from 线程切换到 to 线程的功能。下图是具体的流程图: + +![rt_hw_context_switch()/ rt_hw_context_switch_interrupt() 流程图](figures/10switch2.png) + +在 Cortex-M3 内核上的 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 实现(基于 MDK),如下代码的所示: + + rt_hw_context_switch()/rt_hw_context_switch_interrupt() 实现 + +```c +;/* +; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); +; * r0 --> from +; * r1 --> to +; */ +rt_hw_context_switch_interrupt + EXPORT rt_hw_context_switch_interrupt +rt_hw_context_switch PROC + EXPORT rt_hw_context_switch + + ; 检查 rt_thread_switch_interrupt_flag 变量是否为 1 + ; 如果变量为 1 就跳过更新 from 线程的内容 + LDR r2, =rt_thread_switch_interrupt_flag + LDR r3, [r2] + CMP r3, #1 + BEQ _reswitch + ; 设置 rt_thread_switch_interrupt_flag 变量为 1 + MOV r3, #1 + STR r3, [r2] + + ; 从参数 r0 里更新 rt_interrupt_from_thread 变量 + LDR r2, =rt_interrupt_from_thread + STR r0, [r2] + +_reswitch + ; 从参数 r1 里更新 rt_interrupt_to_thread 变量 + LDR r2, =rt_interrupt_to_thread + STR r1, [r2] + + ; 触发 PendSV 异常,将进入 PendSV 异常处理函数里完成上下文切换 + LDR r0, =NVIC_INT_CTRL + LDR r1, =NVIC_PENDSVSET + STR r1, [r0] + BX LR +``` + +#### 实现 PendSV 中断 + +在 Cortex-M3 里,PendSV 中断处理函数是 PendSV_Handler()。在 PendSV_Handler() 里完成线程切换的实际工作,下图是具体的流程图: + +![PendSV 中断处理](figures/10pendsv.jpg) + +如下代码是 PendSV_Handler 实现: + +```c +; r0 --> switch from thread stack +; r1 --> switch to thread stack +; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack +PendSV_Handler PROC + EXPORT PendSV_Handler + + ; 关闭全局中断 + MRS r2, PRIMASK + CPSID I + + ; 检查 rt_thread_switch_interrupt_flag 变量是否为 0 + ; 如果为零就跳转到 pendsv_exit + LDR r0, =rt_thread_switch_interrupt_flag + LDR r1, [r0] + CBZ r1, pendsv_exit ; pendsv already handled + + ; 清零 rt_thread_switch_interrupt_flag 变量 + MOV r1, #0x00 + STR r1, [r0] + + ; 检查 rt_thread_switch_interrupt_flag 变量 + ; 如果为 0,就不进行 from 线程的上下文保存 + LDR r0, =rt_interrupt_from_thread + LDR r1, [r0] + CBZ r1, switch_to_thread + + ; 保存 from 线程的上下文 + MRS r1, psp ; 获取 from 线程的栈指针 + STMFD r1!, {r4 - r11} ; 将 r4~r11 保存到线程的栈里 + LDR r0, [r0] + STR r1, [r0] ; 更新线程的控制块的 SP 指针 + +switch_to_thread + LDR r1, =rt_interrupt_to_thread + LDR r1, [r1] + LDR r1, [r1] ; 获取 to 线程的栈指针 + + LDMFD r1!, {r4 - r11} ; 从 to 线程的栈里恢复 to 线程的寄存器值 + MSR psp, r1 ; 更新 r1 的值到 psp + +pendsv_exit + ; 恢复全局中断状态 + MSR PRIMASK, r2 + + ; 修改 lr 寄存器的 bit2,确保进程使用 PSP 堆栈指针 + ORR lr, lr, #0x04 + ; 退出中断函数 + BX lr + ENDP +``` + +### 实现时钟节拍 + +有了开关全局中断和上下文切换功能的基础,RTOS 就可以进行线程的创建、运行、调度等功能了。有了时钟节拍支持,RT-Thread 可以实现对相同优先级的线程采用时间片轮转的方式来调度,实现定时器功能,实现 rt_thread_delay() 延时函数等等。 + +libcpu 的移植需要完成的工作,就是确保 rt_tick_increase() 函数会在时钟节拍的中断里被周期性的调用,调用周期取决于 rtconfig.h 的宏 RT_TICK_PER_SECOND 的值。 + +在 Cortex M 中,实现 SysTick 的中断处理函数即可实现时钟节拍功能。 + +```c +void SysTick_Handler(void) +{ + /* enter interrupt */ + rt_interrupt_enter(); + + rt_tick_increase(); + + /* leave interrupt */ + rt_interrupt_leave(); +} +``` + +BSP 移植 +------- + +相同的 CPU 架构在实际项目中,不同的板卡上可能使用相同的 CPU 架构,搭载不同的外设资源,完成不同的产品,所以我们也需要针对板卡做适配工作。RT-Thread 提供了 BSP 抽象层来适配常见的板卡。如果希望在一个板卡上使用 RT-Thread 内核,除了需要有相应的芯片架构的移植,还需要有针对板卡的移植,也就是实现一个基本的 BSP。主要任务是建立让操作系统运行的基本环境,需要完成的主要工作是: + +1)初始化 CPU 内部寄存器,设定 RAM 工作时序。 + +2)实现时钟驱动及中断控制器驱动,完善中断管理。 + +3)实现串口和 GPIO 驱动。 + +4)初始化动态内存堆,实现动态堆内存管理。 + + + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/posix/posix.md b/rt-thread-version/rt-thread-standard/programming-manual/posix/posix.md new file mode 100644 index 0000000000000000000000000000000000000000..4ca47f6ede6acf422c1377a15cc07d60ae782274 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/posix/posix.md @@ -0,0 +1,2632 @@ +# POSIX 接口 + +## Pthreads 简介 + +POSIX Threads 简称 Pthreads,POSIX 是 “Portable Operating System +Interface”(可移植操作系统接口) 的缩写,POSIX 是 IEEE Computer +Society 为了提高不同操作系统的兼容性和应用程序的可移植性而制定的一套标准。Pthreads 是线程的 POSIX 标准,被定义在 POSIX.1c, +Threads extensions (IEEE +Std1003.1c-1995)标准里,该标准定义了一套 C 程序语言的类型、函数和常量。定义在 pthread.h 头文件和一个线程库里,大约有 100 个 API,所有 API 都带有 “pthread_” 前缀,可以分为 4 大类: + +- 线程管理(Thread + management):包括线程创建(creating)、分离(detaching)、连接(joining)及设置和查询线程属性的函数等。 + +- 互斥锁(Mutex):“mutual + exclusion” 的缩写,用了限制线程对共享数据的访问,保护共享数据的完整性。包括创建、销毁、锁定和解锁互斥锁及一些用于设置或修改互斥量属性等函数。 + +- 条件变量(Condition + variable):用于共享一个互斥量的线程间的通信。包括条件变量的创建、销毁、等待和发送信号(signal)等函数。 + +- 读写锁(read/write + lock)和屏障(barrier):包括读写锁和屏障的创建、销毁、等待及相关属性设置等函数。 + +- POSIX 信号量(semaphore)和 Pthreads 一起使用,但不是 Pthreads 标准定义的一部分,被定义在 POSIX.1b, + Real-time extensions (IEEE + Std1003.1b-1993)标准里。因此信号量相关函数的前缀是 “sem_” 而不是“pthread_”。 + +- 消息队列(Message + queue)和信号量一样,和 Pthreads 一起使用,也不是 Pthreads 标准定义的一部分,被定义在 IEEE + Std 1003.1-2001 标准里。消息队列相关函数的前缀是 “mq_”。 + +| 函数前缀 | 函数组 | +|----------------------|----------------------| +| `pthread_ ` | 线程本身和各种相关函数 | +| `pthread_attr_` | 线程属性对象 | +| `Pthread_mutex_` | 互斥锁 | +| `pthread_mutexattr_` | 互斥锁属性对象 | +| `pthread_cond_ ` | 条件变量 | +| `pthread_condattr_` | 条件变量属性对象 | +| `pthread_rwlock_` | 读写锁 | +| `pthread_rwlockattr_` | 读写锁属性对象 | +| `pthread_spin_` | 自旋锁 | +| `pthread_barrier_ ` | 屏障 | +| `pthread_barrierattr_` | 屏障属性对象 | +| `sem_` | 信号量 | +| `mq_ ` | 消息队列 | + +绝大部分 Pthreads 的函数执行成功则返回 0 值,不成功则返回一个包含在 `errno.h` 头文件中的错误代码。很多操作系统都支持 Pthreads,比如 Linux、MacOSX、 Android +和 Solaris,因此使用 Pthreads 的函数编写的应用程序有很好的可移植性,可以在很多支持 Pthreads 的平台上直接编译运行。 + +### 在 RT-Thread 中使用 POSIX + +在 RT-Thread 中使用 POSIX API 接口包括几个部分:libc(例如 newlib),filesystem,pthread 等。需要在 rtconfig.h 中打开相关的选项: + +``` c +#define RT_USING_LIBC +#define RT_USING_DFS +#define RT_USING_DFS_DEVFS +#define RT_USING_PTHREADS +``` + +RT-Thread 实现了 Pthreads 的大部分函数和常量,按照 POSIX 标准定义在 pthread.h、mqueue.h、semaphore.h 和 sched.h 头文件里。Pthreads 是 libc 的一个子库,RT-Thread 中的 Pthreads 是基于 RT-Thread 内核函数的封装,使其符合 POSIX 标准。后续章节会详细介绍 RT-Thread 中实现的 Pthreads 函数及相关功能。 + +## 线程 + +### 线程句柄 + +``` c +typedef rt_thread_t pthread_t; +``` + +pthread_t 是 rt_thread_t 类型的重定义,定义在 pthread.h 头文件里。rt_thread_t 是 RT-Thread 的线程句柄(或线程标识符),是指向线程控制块的指针。在创建线程前需要先定义一个 pthread_t 类型的变量。每个线程都对应了自己的线程控制块,线程控制块是操作系统用于控制线程的一个数据结构,它存放了线程的一些信息,例如优先级,线程名称和线程堆栈地址等。线程控制块及线程具体信息在 RT-Thread 编程手册的线程调度与管理一章有详细的介绍。 + +### 创建线程 + +``` c +int pthread_create (pthread_t *tid, + const pthread_attr_t *attr, + void *(*start) (void *), void *arg); +``` + +| **参数** | **描述** | +|----------|------------------------------------------------------| +| tid | 指向线程句柄 (线程标识符) 的指针,不能为 NULL | +| attr | 指向线程属性的指针,如果使用 NULL,则使用默认的线程属性 | +| start | 线程入口函数地址 | +| arg | 传递给线程入口函数的参数 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| ENOMEM | 动态分配内存失败 | + +此函数创建一个 pthread 线程。此函数会动态分配 POSIX 线程数据块和 RT-Thread 线程控制块,并把线程控制块的起始地址(线程 ID)保存在参数 tid 指向的内存里,此线程标识符可用于在其他线程中操作此线程;并把 attr 指向的线程属性、start 指向的线程入口函数及入口函数参数 arg 保存在线程数据块和线程控制块里。如果线程创建成功,线程立刻进入就绪态,参与系统的调度,如果线程创建失败,则会释放之前线程占有的资源。 + +关于线程属性及相关函数会在线程高级编程一章里有详细介绍,一般情况下采用默认属性就可以。 + +> [!NOTE] +> 注:创建出 pthread 线程后,如果线程需要重复创建使用,需要设置 pthread 线程为 detach 模式,或者使用 pthread_join 等待创建后的 pthread 线程结束。 + +#### 创建线程示例代码 + +以下程序会初始化 2 个线程,它们拥有共同的入口函数,但是它们的入口参数不相同。其他的,它们具备相同的优先级,并以时间片进行轮转调度。 + +``` c +#include +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; + +/* 函数返回值检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} + +/* 线程入口函数 */ +static void* thread_entry(void* parameter) +{ + int count = 0; + int no = (int) parameter; /* 获得线程的入口参数 */ + + while (1) + { + /* 打印输出线程计数值 */ + printf("thread%d count: %d\n", no, count ++); + + sleep(2); /* 休眠 2 秒 */ + } +} + +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + + /* 创建线程 1, 属性为默认值,入口函数是 thread_entry,入口函数参数是 1 */ + result = pthread_create(&tid1,NULL,thread_entry,(void*)1); + check_result("thread1 created", result); + + /* 创建线程 2, 属性为默认值,入口函数是 thread_entry,入口函数参数是 2 */ + result = pthread_create(&tid2,NULL,thread_entry,(void*)2); + check_result("thread2 created", result); + + return 0; +} +``` + +### 脱离线程 + +``` c +int pthread_detach (pthread_t thread); +``` + +| **参数** | **描述** | +|------|----------------------| +| thread | 线程句柄(线程标识符) | +|**返回**| —— | +| 0 | 成功 | + +调用此函数,如果 pthread 线程没有结束,则将 thread 线程属性的分离状态设置为 detached;当 thread 线程已经结束时,系统将回收 pthread 线程占用的资源。 + +使用方法:子线程调用 pthread_detach(pthread_self())(pthread_self() 返回当前调用线程的线程句柄),或者其他线程调用 pthread_detach(thread_id)。关于线程属性的分离状态会在后面详细介绍。 + +> [!NOTE] +> 注:一旦线程属性的分离状态设置为 detached,该线程不能被 pthread_join() 函数等待或者重新被设置为 detached。 + +#### 脱离线程示例代码 + +以下程序会初始化 2 个线程,它们拥有相同的优先级,并按照时间片轮转调度。2 个线程都会被设置为脱离状态,2 个线程循环打印 3 次信息后自动退出,退出后系统将会自动回收其资源。 + +``` c +#include +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; + +/* 函数返回值检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} + +/* 线程 1 入口函数 */ +static void* thread1_entry(void* parameter) +{ + int i; + + printf("i'm thread1 and i will detach myself!\n"); + pthread_detach(pthread_self()); /* 线程 1 脱离自己 */ + + for (i = 0;i < 3;i++) /* 循环打印 3 次信息 */ + { + printf("thread1 run count: %d\n",i); + sleep(2); /* 休眠 2 秒 */ + } + + printf("thread1 exited!\n"); + return NULL; +} + +/* 线程 2 入口函数 */ +static void* thread2_entry(void* parameter) +{ + int i; + + for (i = 0;i < 3;i++) /* 循环打印 3 次信息 */ + { + printf("thread2 run count: %d\n",i); + sleep(2); /* 休眠 2 秒 */ + } + + printf("thread2 exited!\n"); + return NULL; +} +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + + /* 创建线程 1, 属性为默认值,分离状态为默认值 joinable, + * 入口函数是 thread1_entry,入口函数参数为 NULL */ + result = pthread_create(&tid1,NULL,thread1_entry,NULL); + check_result("thread1 created",result); + + /* 创建线程 2, 属性为默认值,分离状态为默认值 joinable, + * 入口函数是 thread2_entry,入口函数参数为 NULL */ + result = pthread_create(&tid2,NULL,thread2_entry,NULL); + check_result("thread2 created",result); + + pthread_detach(tid2); /* 脱离线程 2 */ + + return 0; +} +``` + +### 等待线程结束 + +``` c +int pthread_join (pthread_t thread, void**value_ptr); +``` + +| **参数** | **描述** | +|----------|----------------------| +| thread | 线程句柄(线程标识符) | +| value_ptr | 用户定义的指针,用来存储被等待线程的返回值地址,可由函数 pthread_join() 获取 | +|**返回**| —— | +| 0 | 成功 | +| EDEADLK | 线程 join 自己 | +| EINVAL | join 一个分离状态为 detached 的线程 | +| ESRCH | 找不到 thread 线程 | + +此函数会使调用该函数的线程以阻塞的方式等待线程分离属性为 joinable 的 thread 线程运行结束,并获得 thread 线程的返回值,返回值的地址保存在 value_ptr 里,并释放 thread 线程占用的资源。 + +pthread_join() 和 pthread_detach() 函数功能类似,都是在线程结束后用来回收线程占用的资源。线程不能等待自己结束,thread 线程的分离状态必须是 joinable,一个线程只对应一次 `pthread_join()` 调用。分离状态为 joinable 的线程仅当有其他线程对其执行了 `pthread_join()` 后,它所占用的资源才会释放。因此为了避免内存泄漏,所有会结束运行的线程,分离状态要么已设为 detached,要么使用 pthread_join() 来回收其占用的资源。 + +#### 等待线程结束示例代码 + +以下程序代码会初始化 2 个线程,它们拥有相同的优先级,相同优先级的线程是按照时间片轮转调度。2 个线程属性的分离状态为默认值 joinable,线程 1 先开始运行,循环打印 3 次信息后结束。线程 2 调用 pthread_join() 阻塞等待线程 1 结束,并回收线程 1 占用的资源,然后线程 2 每隔 2 秒钟会打印一次信息。 + +``` c +#include +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; + +/* 函数返回值检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} + +/* 线程 1 入口函数 */ +static void* thread1_entry(void* parameter) +{ + int i; + + for (int i = 0;i < 3;i++) /* 循环打印 3 次信息 */ + { + printf("thread1 run count: %d\n",i); + sleep(2); /* 休眠 2 秒 */ + } + + printf("thread1 exited!\n"); + return NULL; +} + +/* 线程 2 入口函数 */ +static void* thread2_entry(void* parameter) +{ + int count = 0; + void* thread1_return_value; + + /* 阻塞等待线程 1 运行结束 */ + pthread_join(tid1, NULL); + + /* 线程 2 打印信息开始输出 */ + while(1) + { + /* 打印线程计数值输出 */ + printf("thread2 run count: %d\n",count ++); + sleep(2); /* 休眠 2 秒 */ + } + + return NULL; +} + +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + /* 创建线程 1, 属性为默认值,分离状态为默认值 joinable, + * 入口函数是 thread1_entry,入口函数参数为 NULL */ + result = pthread_create(&tid1,NULL,thread1_entry,NULL); + check_result("thread1 created",result); + + /* 创建线程 2, 属性为默认值,分离状态为默认值 joinable, + * 入口函数是 thread2_entry,入口函数参数为 NULL */ + result = pthread_create(&tid2,NULL,thread2_entry,NULL); + check_result("thread2 created",result); + + return 0; +} +``` + +### 退出线程 + +``` c +void pthread_exit(void *value_ptr); +``` + +| **参数** | **描述** | +|----------|---------------------------| +| value_ptr | 用户定义的指针,用来存储被等待线程的返回值地址,可由函数 pthread_join() 获取 | + +pthread 线程调用此函数会终止执行,如同进程调用 exit() 函数一样,并返回一个指向线程返回值的指针。线程退出由线程自身发起。 + +> [!NOTE] +> 注:若线程的分离状态为 joinable,线程退出后该线程占用的资源并不会被释放,必须调用 pthread_join() 函数释放线程占用的资源。 + +#### 退出线程示例代码 + +这个程序会初始化 2 个线程,它们拥有相同的优先级,相同优先级的线程是按照时间片轮转调度。2 个线程属性的分离状态为默认值 joinable,线程 1 先开始运行,打印一次信息后休眠 2 秒,之后打印退出信息然后结束运行。线程 2 调用 pthread_join() 阻塞等待线程 1 结束,并回收线程 1 占用的资源,然后线程 2 每隔 2 秒钟会打印一次信息。 + +``` c +#include +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; + +/* 函数返回值核对函数 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} + +/* 线程 1 入口函数 */ +static void* thread1_entry(void* parameter) +{ + int count = 0; + while(1) + { + /* 打印线程计数值输出 */ + printf("thread1 run count: %d\n",count ++); + sleep(2); /* 休眠 2 秒 */ + printf("thread1 will exit!\n"); + + pthread_exit(0); /* 线程 1 主动退出 */ + } +} + +/* 线程 2 入口函数 */ +static void* thread2_entry(void* parameter) +{ + int count = 0; + + /* 阻塞等待线程 1 运行结束 */ + pthread_join(tid1,NULL); + /* 线程 2 开始输出打印信息 */ + while(1) + { + /* 打印线程计数值输出 */ + printf("thread2 run count: %d\n",count ++); + sleep(2); /* 休眠 2 秒 */ + } +} + +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + + /* 创建线程 1, 属性为默认值,分离状态为默认值 joinable, + * 入口函数是 thread1_entry,入口函数参数为 NULL */ + result = pthread_create(&tid1,NULL,thread1_entry,NULL); + check_result("thread1 created",result); + + /* 创建线程 2, 属性为默认值,分离状态为默认值 joinable, + * 入口函数是 thread2_entry,入口函数参数为 NULL */ + result = pthread_create(&tid2,NULL,thread2_entry,NULL); + check_result("thread2 created",result); + + return 0; +} +``` + +## 互斥锁 + +互斥锁又叫相互排斥的信号量,是一种特殊的二值信号量。互斥锁用来保证共享资源的完整性,保证在任一时刻,只能有一个线程访问该共享资源,线程要访问共享资源,必须先拿到互斥锁,访问完成后需要释放互斥锁。嵌入式的共享资源包括内存、IO、SCI、SPI 等,如果两个线程同时访问共享资源可能会出现问题,因为一个线程可能在另一个线程修改共享资源的过程中使用了该资源,并认为共享资源没有变化。 + +互斥锁的操作只有两种上锁或解锁,同一时刻只会有一个线程持有某个互斥锁。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行解锁或持有它。 + +对互斥锁的主要操作包括:调用 pthread_mutex_init() 初始化一个互斥锁,调用 pthread_mutex_destroy() 销毁互斥锁,调用 pthread_mutex_lock() 对互斥锁上锁,调 pthread_mutex_unlock() 对互斥锁解锁。 + +使用互斥锁会导致一个潜在问题是线程优先级翻转。在 RT-Thread 操作系统中实现的是优先级继承算法。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。 + +有关优先级反转的详细介绍请参考《线程间同步》互斥量小节。 + +### 互斥锁控制块 + +每个互斥锁对应一个互斥锁控制块,包含对互斥锁进行的控制的一些信息。创建互斥锁前必须先定义一个 pthread_mutex_t 类型的变量,pthread_mutex_t 是 pthread_mutex 的重定义,pthread_mutex 数据结构定义在 pthread.h 头文件里,数据结构如下: + +``` c +struct pthread_mutex +{ + pthread_mutexattr_t attr; /* 互斥锁属性 */ + struct rt_mutex lock; /* RT-Thread 互斥锁控制块 */ +}; +typedef struct pthread_mutex pthread_mutex_t; + +rt_mutex 是 RT-Thread 内核里定义的一个数据结构,定义在 rtdef.h 头文件里,数据结构如下: + +struct rt_mutex +{ + struct rt_ipc_object parent; /* 继承自 ipc_object 类 */ + rt_uint16_t value; /* 互斥锁的值 */ + rt_uint8_t original_priority; /* 持有线程的原始优先级 */ + rt_uint8_t hold; /* 互斥锁持有计数 */ + struct rt_thread *owner; /* 当前拥有互斥锁的线程 */ +}; +typedef struct rt_mutex* rt_mutex_t; /* rt_mutext_t 为指向互斥锁结构体的指针 */ +``` + +### 初始化互斥锁 + +``` c +int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); +``` + +| **参数** | **描述** | +|----------|------------------------------------------------------| +| mutex | 互斥锁句柄,不能为 NULL | +| attr | 指向互斥锁属性的指针,若该指针 NULL,则使用默认的属性。 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +此函数会初始化 mutex 互斥锁,并根据 attr 指向的互斥锁属性对象设置 mutex 属性,成功初始化后互斥锁处于未上锁状态,线程可以获取,此函数是对 rt_mutex_init() 函数的封装。 + +除了调用 pthread_mutex_init() 函数创建一个互斥锁,还可以用宏 PTHREAD_MUTEX_INITIALIZER 来静态初始化互斥锁,方法:`pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER`(结构体常量),等同于调用 pthread_mutex_init() 时 attr 指定为 NULL。 + +关于互斥锁属性及相关函数会在线程高级编程一章里有详细介绍,一般情况下采用默认属性就可以。 + +### 销毁互斥锁 + +``` c +int pthread_mutex_destroy(pthread_mutex_t *mutex); +``` + +| **参数** | **描述** | +|----------|----------------------| +| mutex | 互斥锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | mutex 为空或者 mutex 已经被销毁过 | +| EBUSY | 互斥锁正在被使用 | + +此函数会销毁 mutex 互斥锁。销毁后互斥锁 mutex 处于未初始化状态。销毁以后互斥锁的属性和控制块参数将不在有效,但可以调用 pthread_mutex_init() 对销毁后的互斥锁重新初始化。但不需要销毁使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化的互斥锁。 + +当确定互斥锁没有被锁住,且没有线程阻塞在该互斥锁上,才可以销毁该互斥锁。 + +### 阻塞方式对互斥锁上锁 + +``` c +int pthread_mutex_lock(pthread_mutex_t *mutex); +``` + +| **参数** | **描述** | +|----------|----------------------| +| mutex | 互斥锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EDEADLK | 互斥锁 mutex 不为嵌套锁的情况下线程重复调用此函数 | + +此函数对 mutex 互斥锁上锁,此函数是对 rt_mutex_take() 函数的封装。如果互斥锁 mutex 还没有被上锁,那么申请该互斥锁的线程将成功对该互斥锁上锁。如果互斥锁 mutex 已经被当前线程上锁,且互斥锁类型为嵌套锁,则该互斥锁的持有计数加 1,当前线程也不会挂起等待(死锁),但线程必须对应相同次数的解锁。如果互斥锁 mutex 被其他线程上锁持有,则当前线程将被阻塞,一直到其他线程对该互斥锁解锁后,等待该互斥锁的线程将按照先进先出的原则获取互斥锁。 + +### 非阻塞方式对互斥锁上锁 + +``` c +int pthread_mutex_trylock(pthread_mutex_t *mutex); +``` + +| **参数** | **描述** | +|----------|----------------------| +| mutex | 互斥锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EDEADLK | 互斥锁 mutex 不为嵌套锁的情况下线程重复调用此函数 | +| EBUSY | 互斥锁 mutex 已经被其他线程上锁 | + +此函数是 pthread_mutex_lock() 函数的非阻塞版本。区别在于如果互斥锁 mutex 已经被上锁,线程不会被阻塞,而是马上返回错误码。 + +### 解锁互斥锁 + +``` c +int pthread_mutex_unlock(pthread_mutex_t *mutex); +``` + +| **参数** | **描述** | +|----------|----------------------| +| mutex | 互斥锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EPERM | 互斥锁 mutex 不为嵌套锁的情况下线程重复调用此函数 | +| EBUSY | 解锁其他线程持有的类型为检错锁的互斥锁 | + +调用此函数给 mutex 互斥锁解锁,是对 rt_mutex_release() 函数的封装。当线程完成共享资源的访问后,应尽快释放占有的互斥锁,使得其他线程能及时获取该互斥锁。只有已经拥有互斥锁的线程才能释放它,每释放一次该互斥锁,它的持有计数就减 1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),互斥锁才变为可用,等待在该互斥锁上的线程将按先进先出方式被唤醒。如果线程的运行优先级被互斥锁提升,那么当互斥锁被释放后,线程恢复为持有互斥锁前的优先级。 + +### 互斥锁示例代码 + +这个程序会初始化 2 个线程,它们拥有相同的优先级,2 个线程都会调用同一个 printer() 函数输出自己的字符串,printer() 函数每次只输出一个字符,之后休眠 1 秒,调用 printer() 函数的线程同样也休眠。如果不使用互斥锁,线程 1 打印了一个字符,休眠后执行线程 2,线程 2 打印一个字符,这样就不能完整的打印线程 1 和线程 2 的字符串,打印出的字符串是混乱的。如果使用了互斥锁保护 2 个线程共享的打印函数 printer(),线程 1 拿到互斥锁后执行 printer() 打印函数打印一个字符,之后休眠 1 秒,这是切换到线程 2,因为互斥锁已经被线程 1 上锁,线程 2 将阻塞,直到线程 1 的字符串打印完整后主动释放互斥锁后线程 2 才会被唤醒。 + +``` c +#include +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; +/* 互斥锁控制块 */ +static pthread_mutex_t mutex; +/* 线程共享的打印函数 */ +static void printer(char* str) +{ + while(*str != 0) + { + putchar(*str); /* 输出一个字符 */ + str++; + sleep(1); /* 休眠 1 秒 */ + } + printf("\n"); +} +/* 函数返回值检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} +/* 线程入口 */ +static void* thread1_entry(void* parameter) +{ + char* str = "thread1 hello RT-Thread"; + while (1) + { + pthread_mutex_lock(&mutex); /* 互斥锁上锁 */ + + printer(str); /* 访问共享打印函数 */ + + pthread_mutex_unlock(&mutex); /* 访问完成后解锁 */ + + sleep(2); /* 休眠 2 秒 */ + } +} +static void* thread2_entry(void* parameter) +{ + char* str = "thread2 hi world"; + while (1) + { + pthread_mutex_lock(&mutex); /* 互斥锁上锁 */ + + printer(str); /* 访问共享打印函数 */ + + pthread_mutex_unlock(&mutex); /* 访问完成后解锁 */ + + sleep(2); /* 休眠 2 秒 */ + } +} +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + /* 初始化一个互斥锁 */ + pthread_mutex_init(&mutex,NULL); + + /* 创建线程 1, 线程入口是 thread1_entry, 属性参数为 NULL 选择默认值,入口参数是 NULL*/ + result = pthread_create(&tid1,NULL,thread1_entry,NULL); + check_result("thread1 created",result); + + /* 创建线程 2, 线程入口是 thread2_entry, 属性参数为 NULL 选择默认值,入口参数是 NULL*/ + result = pthread_create(&tid2,NULL,thread2_entry,NULL); + check_result("thread2 created",result); + + return 0; +} +``` + +## 条件变量 + +条件变量其实就是一个信号量,用于线程间同步。条件变量用来阻塞一个线程,当条件满足时向阻塞的线程发送一个条件,阻塞线程就被唤醒,条件变量需要和互斥锁配合使用,互斥锁用来保护共享数据。 + +条件变量可以用来通知共享数据状态。比如一个处理共享资源队列的线程发现队列为空,则此线程只能等待,直到有一个节点被添加到队列中,添加后在发一个条件变量信号激活等待线程。 + +条件变量的主要操作包括:调用 pthread_cond_init() 对条件变量初始化,调用 +pthread_cond_destroy() 销毁一个条件变量,调用 pthread_cond_wait() 等待一个条件变量,调用 pthread_cond_signal() 发送一个条件变量。 + +### 条件变量控制块 + +每个条件变量对应一个条件变量控制块,包括对条件变量进行操作的一些信息。初始化一个条件变量前需要先定义一个 pthread_cond_t 条件变量控制块。pthread_cond_t 是 pthread_cond 结构体类型的重定义,定义在 pthread.h 头文件里。 + +``` c +struct pthread_cond +{ + pthread_condattr_t attr; /* 条件变量属性 */ + struct rt_semaphore sem; /* RT-Thread 信号量控制块 */ +}; +typedef struct pthread_cond pthread_cond_t; + +rt_semaphore 是 RT-Thread 内核里定义的一个数据结构,是信号量控制块, +定义在 rtdef.h 头文件里 + +struct rt_semaphore +{ + struct rt_ipc_object parent;/* 继承自 ipc_object 类 */ + rt_uint16_t value; /* 信号量的值 */ +}; +``` + +### 初始化条件变量 + +``` c +int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); +``` + +| **参数** | **描述** | +|----|------------------------------------------------| +| cond | 条件变量句柄,不能为 NULL | +| attr | 指向条件变量属性的指针,若为 NULL 则使用默认属性值 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +此函数会初始化 cond 条件变量,并根据 attr 指向的条件变量属性设置其属性,此函数是对 rt_sem_init() 函数的一个封装,基于信号量实现。初始化成功后条件变量处于不可用状态。 + +还可以用宏 PTHREAD_COND_INITIALIZER 静态初始化一个条件变量,方法: +`pthread_cond_t cond = PTHREAD_COND_INITIALIZER`(结构体常量),等同于调用 +pthread_cond_init() 时 attr 指定为 NULL。 + +attr 一般设置 NULL 使用默认值即可,具体会在线程高级编程一章介绍。 + +### 销毁条件变量 + +``` c +int pthread_cond_destroy(pthread_cond_t *cond); +``` + +| **参数** | **描述** | +|----|------------------------| +| cond | 条件变量句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EPERM | 互斥锁 mutex 不为嵌套锁的情况下线程重复调用此函数 | +| EBUSY | 条件变量正在被使用 | + +此函数会销毁 cond 条件变量,销毁后 cond 处于未初始化状态。销毁之后条件变量的属性及控制块参数将不在有效,但可以调用 pthread_cond_init() 或者静态方式重新初始化。 + +销毁条件变量前需要确定没有线程被阻塞在该条件变量上,也不会等待获取、发信号或者广播。 + +### 阻塞方式获取条件变量 + +``` c +int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); +``` + +| **参数** | **描述** | +|----------|----------------------------------| +| cond | 条件变量句柄,不能为 NULL | +| mutex | 指向互斥锁控制块的指针,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +此函数会以阻塞方式获取 cond 条件变量。线程等待条件变量前需要先将 mutex 互斥锁锁住,此函数首先判断条件变量是否可用,如果不可用则初始化一个条件变量,之后解锁 mutex 互斥锁,然后尝试获取一个信号量,当信号量值大于零时,表明信号量可用,线程将获得信号量,也就获得该条件变量,相应的信号量值会减 1。如果信号量的值等于零,表明信号量不可用,线程将阻塞直到信号量可用,之后将对 mutex 互斥锁再次上锁。 + +### 指定阻塞时间获取条件变量 + +``` c +int pthread_cond_timedwait(pthread_cond_t *cond, + pthread_mutex_t *mutex, + const struct timespec *abstime); +``` + +| **参数** | **描述** | +|-------|-------------------------------------------------| +| cond | 条件变量句柄,不能为 NULL | +| mutex | 指向互斥锁控制块的指针,不能为 NULL | +| abstime | 指定的等待时间,单位是操作系统时钟节拍(OS Tick) | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EPERM | 互斥锁 mutex 不为嵌套锁的情况下线程重复调用此函数 | +| ETIMEDOUT | 超时 | + +此函数和 pthread_cond_wait() 函数唯一的差别在于,如果条件变量不可用,线程将被阻塞 abstime 时长,超时后函数将直接返回 ETIMEDOUT 错误码,线程将会被唤醒进入就绪态。 + +### 发送满足条件信号量 + +``` c +int pthread_cond_signal(pthread_cond_t *cond); +``` + +| **参数** | **描述** | +|----|------------------------| +| cond | 条件变量句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | + +此函数会发送一个信号且只唤醒一个等待 cond 条件变量的线程,是对 rt_sem_release() 函数的封装,也就是发送一个信号量。当信号量的值等于零,并且有线程等待这个信号量时,将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量。否则将把信号量的值加 1。 + +### 广播 + +``` c +int pthread_cond_broadcast(pthread_cond_t *cond); +``` + +| **参数** | **描述** | +|----|------------------------| +| cond | 条件变量句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +调用此函数将唤醒所有等待 cond 条件变量的线程。 + +### 条件变量示例代码 + +这个程序是一个生产者消费者模型,有一个生产者线程,一个消费者线程,它们拥有相同的优先级。生产者每隔 2 秒会生产一个数字,放到 head 指向的链表里面,之后调用 pthread_cond_signal() 给消费者线程发信号,通知消费者线程链表里面有数据。消费者线程会调用 pthread_cond_wait() 等待生产者线程发送信号。 + +``` c +#include +#include +#include +#include + +/* 静态方式初始化一个互斥锁和一个条件变量 */ +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + +/* 指向线程控制块的指针 */ +static pthread_t tid1; +static pthread_t tid2; + +/* 函数返回值检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} + +/* 生产者生产的结构体数据,存放在链表里 */ +struct node +{ + int n_number; + struct node* n_next; +}; +struct node* head = NULL; /* 链表头, 是共享资源 */ + +/* 消费者线程入口函数 */ +static void* consumer(void* parameter) +{ + struct node* p_node = NULL; + + pthread_mutex_lock(&mutex); /* 对互斥锁上锁 */ + + while (1) + { + while (head == NULL) /* 判断链表里是否有元素 */ + { + pthread_cond_wait(&cond,&mutex); /* 尝试获取条件变量 */ + } + /* + pthread_cond_wait() 会先对 mutex 解锁, + 然后阻塞在等待队列,直到获取条件变量被唤醒, + 被唤醒后,该线程会再次对 mutex 上锁,成功进入临界区。 + */ + + p_node = head; /* 拿到资源 */ + head = head->n_next; /* 头指针指向下一个资源 */ + /* 打印输出 */ + printf("consume %d\n",p_node->n_number); + + free(p_node); /* 拿到资源后释放节点占用的内存 */ + } + pthread_mutex_unlock(&mutex); /* 释放互斥锁 */ + return 0; +} +/* 生产者线程入口函数 */ +static void* product(void* patameter) +{ + int count = 0; + struct node *p_node; + + while(1) + { + /* 动态分配一块结构体内存 */ + p_node = (struct node*)malloc(sizeof(struct node)); + if (p_node != NULL) + { + p_node->n_number = count++; + pthread_mutex_lock(&mutex); /* 需要操作 head 这个临界资源,先加锁 */ + + p_node->n_next = head; + head = p_node; /* 往链表头插入数据 */ + + pthread_mutex_unlock(&mutex); /* 解锁 */ + printf("produce %d\n",p_node->n_number); + + pthread_cond_signal(&cond); /* 发信号唤醒一个线程 */ + + sleep(2); /* 休眠 2 秒 */ + } + else + { + printf("product malloc node failed!\n"); + break; + } + } +} + +int rt_application_init() +{ + int result; + + /* 创建生产者线程, 属性为默认值,入口函数是 product,入口函数参数为 NULL*/ + result = pthread_create(&tid1,NULL,product,NULL); + check_result("product thread created",result); + + /* 创建消费者线程, 属性为默认值,入口函数是 consumer,入口函数参数是 NULL */ + result = pthread_create(&tid2,NULL,consumer,NULL); + check_result("consumer thread created",result); + + return 0; +} +``` + +## 读写锁 + +读写锁也称为多读者单写者锁。读写锁把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。同一时间只能有一个线程可以占有写模式的读写锁, +但是可以有多个线程同时占有读模式的读写锁。读写锁适合于对数据结构的读次数比写次数多得多的情况,因为读模式锁定时可以共享, +写模式锁定时意味着独占。 + +读写锁通常是基于互斥锁和条件变量实现的。一个线程可以对一个读写锁进行多次读写锁定,同样必须有对应次数的解锁。 + +读写锁的主要操作包括:调用 pthread_rwlock_init() 初始化一个读写锁,写线程调用 pthread_rwlock_wrlock() 对读写锁写锁定,读线程调用 pthread_rwlock_rdlock() 对读写锁读锁定,当不需要使用此读写锁时调用 pthread_rwlock_destroy() 销毁读写锁。 + +### 读写锁控制块 + +每个读写锁对应一个读写锁控制块,包括对读写锁进行操作的一些信息。pthread_rwlock_t 是 pthread_rwlock 数据结构的重定义,定义在 pthread.h 头文件里。在创建一个读写锁之前需要先定义一个 pthread_rwlock_t 类型的数据结构。 + +``` c +struct pthread_rwlock +{ + pthread_rwlockattr_t attr; /* 读写锁属性 */ + pthread_mutex_t rw_mutex; /* 互斥锁 */ + pthread_cond_t rw_condreaders; /* 条件变量,供读者线程使用 */ + pthread_cond_t rw_condwriters; /* 条件变量,供写者线程使用 */ + int rw_nwaitreaders; /* 读者线程等待计数 */ + int rw_nwaitwriters; /* 写者线程等待计数 */ + /* 读写锁值,值为 0:未上锁, 值为 - 1:被写者线程锁定, 大于 0 值:被读者线程锁定数量 */ + int rw_refcount; +}; +typedef struct pthread_rwlock pthread_rwlock_t; /* 类型重定义 */ +``` + +### 初始化读写锁初始化 + +``` c +int pthread_rwlock_init (pthread_rwlock_t *rwlock, + const pthread_rwlockattr_t *attr); +``` + +| **参数** | **描述** | +|------|-------------------------------------------| +| rwlock | 读写锁句柄,不能为 NULL | +| attr | 指向读写锁属性的指针,RT-Thread 不使用此变量 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +此函数会初始化一个 rwlock 读写锁。此函数使用默认值初始化读写锁控制块的信号量和条件变量,相关计数参数初始为 0 值。初始化后的读写锁处于未上锁状态。 + +还可以使用宏 PTHREAD_RWLOCK_INITIALIZER 来静态初始化读写锁,方法: +pthread_rwlock_t mutex = PTHREAD_RWLOCK_INITIALIZER(结构体常量),等同于调用pthread_rwlock_init() 时 attr 指定为 NULL。 + +attr 一般设置 NULL 使用默认值即可,具体会在线程高级编程一章介绍。 + +### 销毁读写锁 + +``` c +int pthread_rwlock_destroy (pthread_rwlock_t *rwlock); +``` + +| **参数** | **描述** | +|------|----------------------| +| rwlock | 读写锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EBUSY | 读写锁目前正在被使用或者有线程等待该读写锁 | +| EDEADLK | 死锁 | + +此函数会销毁一个 rwlock 读写锁,对应的会销毁读写锁里的互斥锁和条件变量。销毁之后读写锁的属性及控制块参数将不在有效,但可以调用 pthread_rwlock_init() 或者静态方式重新初始化读写锁。 + +### 读写锁读锁定 + +#### 阻塞方式对读写锁读锁定 + +``` c +int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock); +``` + +| **参数** | **描述** | +|------|----------------------| +| rwlock | 读写锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EDEADLK | 死锁 | + +读者线程可以调用此函数来对 rwlock 读写锁进行读锁定。如果读写锁没有被写锁定并且没有写者线程阻塞在该读写锁上,读写线程将成功获取该读写锁。如果读写锁已经被写锁定,读者线程将会阻塞,直到写锁定该读写锁的线程解锁。 + +#### 非阻塞方式对读写锁读锁定 + +``` c +int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock); +``` + +| **参数** | **描述** | +|------|----------------------| +| rwlock | 读写锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EBUSY | 读写锁目前正在被使用或者有线程等待该读写锁 | +| EDEADLK | 死锁 | + +此函数和 pthread_rwlock_rdlock() 函数的不同在于,如果读写锁已经被写锁定,读者线程不会被阻塞,而是返回一个错误码 EBUSY。 + +#### 指定阻塞时间对读写锁读锁定 + +``` c +int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock, + const struct timespec *abstime); +``` + +| **参数** | **描述** | +|-------|-------------------------------------------------| +| rwlock | 读写锁句柄,不能为 NULL | +| abstime | 指定的等待时间,单位是操作系统时钟节拍(OS Tick) | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| ETIMEDOUT | 超时 | +| EDEADLK | 死锁 | + +此函数和 pthread_rwlock_rdlock() 函数的不同在于,如果读写锁已经被写锁定,读者线程将会阻塞指定的 abstime 时长,超时后函数将返回错误码 ETIMEDOUT,线程将会被唤醒进入就绪态。 + +### 读写锁写锁定 + +#### 阻塞方式对读写锁写锁定 + +``` c +int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock); +``` + +| **参数** | **描述** | +|------|----------------------| +| rwlock | 读写锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EDEADLK | 死锁 | + +写者线程调用此函数对 rwlock 读写锁进行写锁定。写锁定读写锁类似互斥量,同一时刻只能有一个线程写锁定读写锁。如果没有线程锁定该读写锁,即读写锁值为 0,调用此函数的写者线程将会写锁定读写锁,其他线程此时都不能获取读写锁,如果已经有线程锁定该读写锁,即读写锁值不为 0,则写线程将被阻塞,直到读写锁解锁。 + +#### 非阻塞方式写锁定读写锁 + +``` c +int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock); +``` + +| **参数** | **描述** | +|------|----------------------| +| rwlock | 读写锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EBUSY | 读写锁目前被写锁定或者有写着线程阻塞在该读写锁上 | +| EDEADLK | 死锁 | + +此函数和 pthread_rwlock_wrlock() 函数唯一的不同在于,如果已经有线程锁定该读写锁,即读写锁值不为 0,则调用该函数的写者线程会直接返回一个错误代码,线程不会被阻塞。 + +#### 指定阻塞时长写锁定读写锁 + +``` c +int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock, + const struct timespec *abstime); +``` + +| **参数** | **描述** | +|--------------|---------------------| +| rwlock abstime | 读写锁句柄,不能为 NULL 指定的等待时间,单位是操作系统时钟节拍(OS Tick) | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| ETIMEDOUT | 超时 | +| EDEADLK | 死锁 | + +此函数和 pthread_rwlock_wrlock() 函数唯一的不同在于,如果已经有线程锁定该读写锁,即读写锁值不为 0,调用线程阻塞指定的 abstime 时长,超时后函数将返回错误码 ETIMEDOUT,线程将会被唤醒进入就绪态。 + +### 读写锁解锁 + +``` c +int pthread_rwlock_unlock (pthread_rwlock_t *rwlock); +``` + +| **参数** | **描述** | +|------|----------------------| +| rwlock | 读写锁句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | +| EDEADLK | 死锁 | + +此函数可以对 rwlock 读写锁解锁。线程对同一个读写锁加锁多次,必须有同样次数的解锁,若解锁后有多个线程等待对读写锁进行锁定,系统将按照先进先出的规则激活等待的线程。 + +### 读写锁示例代码 + +这个程序有 2 个读者线程,一个写着线程。2 个读者线程先对读写锁读锁定,之后休眠 2 秒,这是其他的读者线程还是可以对该读写锁读锁定,然后读取共享数据。 + +``` c +#include +#include +#include + +/* 线程控制块 */ +static pthread_t reader1; +static pthread_t reader2; +static pthread_t writer1; +/* 共享数据 book */ +static int book = 0; +/* 读写锁 */ +static pthread_rwlock_t rwlock; +/* 函数结果检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} +/* 线程入口 */ +static void* reader1_entry(void* parameter) +{ + while (1) + { + + pthread_rwlock_rdlock(&rwlock); /* 尝试读锁定该读写锁 */ + + printf("reader1 read book value is %d\n",book); + sleep(2); /* 线程休眠 2 秒,切换到其他线程运行 */ + + pthread_rwlock_unlock(&rwlock); /* 线程运行后对读写锁解锁 */ + } +} +static void* reader2_entry(void* parameter) +{ + while (1) + { + pthread_rwlock_rdlock(&rwlock); /* 尝试读锁定该读写锁 */ + + printf("reader2 read book value is %d\n",book); + sleep(2); /* 线程休眠 2 秒,切换到其他线程运行 */ + + pthread_rwlock_unlock(&rwlock); /* 线程运行后对读写锁解锁 */ + } +} +static void* writer1_entry(void* parameter) +{ + while (1) + { + pthread_rwlock_wrlock(&rwlock); /* 尝试写锁定该读写锁 */ + + book++; + printf("writer1 write book value is %d\n",book); + + pthread_rwlock_unlock(&rwlock); /* 对读写锁解锁 */ + + sleep(2); /* 线程休眠 2 秒,切换到其他线程运行 */ + } +} +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + /* 默认属性初始化读写锁 */ + pthread_rwlock_init(&rwlock,NULL); + + /* 创建 reader1 线程, 线程入口是 reader1_entry, 线程属性为默认值,入口参数为 NULL*/ + result = pthread_create(&reader1,NULL,reader1_entry,NULL); + check_result("reader1 created",result); + + /* 创建 reader2 线程, 线程入口是 reader2_entry, 线程属性为默认值,入口参数为 NULL*/ + result = pthread_create(&reader2,NULL,reader2_entry,NULL); + check_result("reader2 created",result); + + /* 创建 writer1 线程, 线程入口是 writer1_entry, 线程属性为,入口参数为 NULL*/ + result = pthread_create(&writer1,NULL,writer1_entry,NULL); + check_result("writer1 created",result); + + return 0; +} +``` + +## 屏障 + +屏障是多线程同步的一种方法。barrier 意为屏障或者栏杆,把先后到达的多个线程挡在同一栏杆前,直到所有线程到齐,然后撤下栏杆同时放行。先到达的线程将会阻塞,等到所有调用 pthread_barrier_wait() 函数的线程(数量等于屏障初始化时指定的 count)都到达后,这些线程才会由阻塞状态进入就绪状态再次参与系统调度。 + +屏障是基于条件变量和互斥锁实现的。主要操作包括:调用 pthread_barrier_init() 初始化一个屏障,其他线程调用 pthread_barrier_wait(),所有线程到期后线程唤醒进入准备状态,屏障不在使用调用 pthread_barrier_destroy() 销毁一个屏障。 + +### 屏障控制块 + +创建一个屏障前需要先定义一个 pthread_barrier_t 屏障控制块。pthread_barrier_t 是 pthread_barrier 结构体类型的重定义,定义在 pthread.h 头文件里。 + +``` c +struct pthread_barrier +{ + int count; /* 指定的等待线程个数 */ + pthread_cond_t cond; /* 条件变量 */ + pthread_mutex_t mutex; /* 互斥锁 */ +}; +typedef struct pthread_barrier pthread_barrier_t; +``` + +### 创建屏障 + +``` c +int pthread_barrier_init(pthread_barrier_t *barrier, + const pthread_barrierattr_t *attr, + unsigned count); +``` + +| **参数** | **描述** | +|-------|-------------------------------| +| attr | 指向屏障属性的指针,传入 NULL,则使用默认值,非 NULL 必须使用 PTHREAD_PROCESS_PRIVATE | +| barrier | 屏障句柄 | +| count | 指定的等待线程个数 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +此函数会创建一个 barrier 屏障,并根据默认的参数对屏障控制块的条件变量和互斥锁初始化,初始化后指定的等待线程个数为 count 个,必须对应 count 个线程调用 pthread_barrier_wait()。 + +attr 一般设置 NULL 使用默认值即可,具体会在线程高级编程一章介绍。 + +### 销毁屏障 + +``` c +int pthread_barrier_destroy(pthread_barrier_t *barrier); +``` + +| **参数** | **描述** | +|-------|--------| +| barrier | 屏障句柄 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +此函数会销毁一个 barrier 屏障。销毁之后屏障的属性及控制块参数将不在有效,但可以调用 pthread_barrier_init() 重新初始化。 + +### 等待屏障 + +``` c +int pthread_barrier_wait(pthread_barrier_t *barrier); +``` + +| **参数** | **描述** | +|-------|--------| +| barrier | 屏障句柄 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +此函数同步等待在 barrier 前的线程,由每个线程主动调用,若屏障等待线程个数 count 不为 0,count 将减 1,若减 1 后 count 为 0,表明所有线程都已经到达栏杆前,所有到达的线程将被唤醒重新进入就绪状态,参与系统调度。若减一后 count 不为 0,表明还有线程没有到达屏障,调用的线程将阻塞直到所有线程到达屏障。 + +### 屏障示例代码 + +此程序会创建 3 个线程,初始化一个屏障,屏障等待线程数初始化为 3。3 个线程都会调用 pthread_barrier_wait() 等待在屏障前,当 3 个线程都到齐后,3 个线程进入就绪态,之后会每隔 2 秒打印输出计数信息。 + +``` c +#include +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; +static pthread_t tid3; +/* 屏障控制块 */ +static pthread_barrier_t barrier; +/* 函数返回值检查函数 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} +/* 线程 1 入口函数 */ +static void* thread1_entry(void* parameter) +{ + int count = 0; + + printf("thread1 have arrived the barrier!\n"); + pthread_barrier_wait(&barrier); /* 到达屏障,并等待其他线程到达 */ + + while (1) + { + /* 打印线程计数值输出 */ + printf("thread1 count: %d\n",count ++); + + /* 休眠 2 秒 */ + sleep(2); + } +} +/* 线程 2 入口函数 */ +static void* thread2_entry(void* parameter) +{ + int count = 0; + + printf("thread2 have arrived the barrier!\n"); + pthread_barrier_wait(&barrier); + + while (1) + { + /* 打印线程计数值输出 */ + printf("thread2 count: %d\n",count ++); + + /* 休眠 2 秒 */ + sleep(2); + } +} +/* 线程 3 入口函数 */ +static void* thread3_entry(void* parameter) +{ + int count = 0; + + printf("thread3 have arrived the barrier!\n"); + pthread_barrier_wait(&barrier); + + while (1) + { + /* 打印线程计数值输出 */ + printf("thread3 count: %d\n",count ++); + + /* 休眠 2 秒 */ + sleep(2); + } +} +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + pthread_barrier_init(&barrier,NULL,3); + + /* 创建线程 1, 线程入口是 thread1_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/ + result = pthread_create(&tid1,NULL,thread1_entry,NULL); + check_result("thread1 created",result); + + /* 创建线程 2, 线程入口是 thread2_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/ + result = pthread_create(&tid2,NULL,thread2_entry,NULL); + check_result("thread2 created",result); + + /* 创建线程 3, 线程入口是 thread3_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/ + result = pthread_create(&tid3,NULL,thread3_entry,NULL); + check_result("thread3 created",result); + +} +``` + +## 信号量 + +信号量可以用于进程与进程之间,或者进程内线程之间的通信。每个信号量都有一个不会小于 0 的信号量值,对应信号量的可用数量。调用 sem_init() 或者 sem_open() 给信号量值赋初值,调用 sem_post() 函数可以让信号量值加 1,调用 sem_wait() 可以让信号量值减 1,如果当前信号量为 0,调用 sem_wait() 的线程被挂起在该信号量的等待队列上,直到信号量值大于 0,处于可用状态。 + +根据信号量的值(代表可用资源的数目)的不同,POSIX 信号量可以分为: + +- 二值信号量:信号量的值只有 0 和 1,初始值指定为 1。这和互斥锁一样,若资源被锁住,信号量的值为 0,若资源可用,则信号量的值为 1。相当于只有一把钥匙,线程拿到钥匙后,完成了对共享资源的访问后需要解锁,把钥匙再放回去,给其他需要此钥匙的线程使用。使用方法和互斥锁一样,等待信号量函数必须和发送信号量函数成对使用,不能单独使用,必须先等待后发送。 + +- 计数信号量:信号量的值在 0 到一个大于 1 的限制值(POSIX 指出系统的最大限制值至少要为 32767)。该计数表示可用信号量个数。此时,发送信号量函数可以被单独调用发送信号量,相当于有多把钥匙,线程拿到一把钥匙就消耗了一把,使用过的钥匙不必在放回去。 + +POSIX 信号量又分为有名信号量和无名信号量: + +- 有名信号量:其值保存在文件中,一般用于进程间同步或互斥。 + +- 无名信号量:其值保存在内存中,一般用于线程间同步或互斥。 + +RT-Thread 操作系统的 POSIX 信号量主要是基于 RT-Thread 内核信号量的一个封装,主要还是用于系统内线程间的通讯。使用方式和 RT-Thread 内核的信号量差不多。 + +### 信号量控制块 + +每个信号量对应一个信号量控制块,创建一个信号量前需要先定义一个 sem_t 信号量控制块。sem_t 是 posix_sem 结构体类型的重定义,定义在 semaphore.h 头文件里。 + +``` c +struct posix_sem +{ + rt_uint16_t refcount; + rt_uint8_t unlinked; + rt_uint8_t unamed; + rt_sem_t sem; /* RT-Thread 信号量 */ + struct posix_sem* next; /* 指向下一个信号量控制块 */ +}; +typedef struct posix_sem sem_t; + +rt_sem_t 是 RT-Thread 信号量控制块,定义在 rtdef.h 头文件里。 + +struct rt_semaphore +{ + struct rt_ipc_object parent;/* 继承自 ipc_object 类 */ + rt_uint16_t value; /* 信号量的值 */ +}; +/* rt_sem_t 是指向 semaphore 结构体的指针类型 */ +typedef struct rt_semaphore* rt_sem_t; + +``` + +### 无名信号量 + +无名信号量的值保存在内存中,一般用于线程间同步或互斥。在使用之前,必须先调用 sem_init() 初始化。 + +#### 初始化无名信号量 + +``` c +int sem_init(sem_t *sem, int pshared, unsigned int value); +``` + +| **参数** | **描述** | +|-------|--------------------------------------| +| sem | 信号量句柄 | +| value | 信号量初始值,表示信号量资源的可用数量 | +| pshared | RT-Thread 未实现参数 | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +此函数初始化一个无名信号量 sem,根据给定的或默认的参数对信号量相关数据结构进行初始化,并把信号量放入信号量链表里。初始化后信号量值为给定的初始值 value。此函数是对 rt_sem_create() 函数的封装。 + +#### 销毁无名信号量 + +``` c +int sem_destroy(sem_t *sem); +``` + +| **参数** | **描述** | +|----|----------| +| sem | 信号量句柄 | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +此函数会销毁一个无名信号量 sem,并释放信号量占用的资源。 + +### 有名信号量 + +有名信号量,其值保存在文件中,一般用于进程间同步或互斥。两个进程可以操作相同名称的有名信号量。RT-Thread 操作系统中的有名信号量实现和无名信号量差不多,都是设计用于线程间的通信,使用方法也类似。 + +#### 创建或打开有名信号量 + +``` c +sem_t *sem_open(const char *name, int oflag, ...); +``` + +| **参数** | **描述** | +|----------|----------------| +| name | 信号量名称 | +| oflag | 信号量的打开方式 | +|**返回**| —— | +| 信号量句柄 | 成功 | +| NULL | 失败 | + +此函数会根据信号量名字 name 创建一个新的信号量或者打开一个已经存在的信号量。Oflag 的可选值有 0、O_CREAT 或 O_CREAT\|O_EXCL。如果 Oflag 设置为 O_CREAT 则会创建一个新的信号量。如果 Oflag 设置 O_CREAT\|O_EXCL,如果信号量已经存在则会返回 NULL,如果不存在则会创建一个新的信号量。如果 Oflag 设置为 0,信号量不存在则会返回 NULL。 + +#### 分离有名信号量 + +``` c +int sem_unlink(const char *name); +``` + +| **参数** | **描述** | +|----|----------| +| name | 信号量名称 | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败,信号量不存在 | + +此函数会根据信号量名称 name 查找该信号量,若信号量存在,则将该信号量标记为分离状态。之后检查引用计数,若值为 0,则立即删除信号量,若值不为 0,则等到所有持有该信号量的线程关闭信号量之后才会删除。 + +#### 关闭有名信号量 + +``` c +int sem_close(sem_t *sem); +``` + +| **参数** | **描述** | +|----|----------| +| sem | 信号量句柄 | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +当一个线程终止时,会对其占用的信号量执行此关闭操作。不论线程是自愿终止还是非自愿终止都会执行这个关闭操作,相当于是信号量的持有计数减 1。若减 1 后持有计数为 0 且信号量已经处于分离状态,则会删除 sem 信号量并释放其占有的资源。 + +### 获取信号量值 + +``` c +int sem_getvalue(sem_t *sem, int *sval); +``` + +| **参数** | **描述** | +|----|---------------------------------| +| sem | 信号量句柄,不能为 NULL | +| sval | 保存获取的信号量值地址, 不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +此函数可以获取 sem 信号量的值,并保存在 sval 指向的内存里,可以知道信号量的资源数量。 + +### 阻塞方式等待信号量 + +``` c +int sem_wait(sem_t *sem); +``` + +| **参数** | **描述** | +|----|----------------------| +| sem | 信号量句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +线程调用此函数获取信号量,是 rt_sem_take(sem,RT_WAITING_FOREVER) 函数的封装。若信号量值大于零,表明信号量可用,线程获得信号量,信号量值减 1。若信号量值等于 0,表明信号量不可用,线程阻塞进入挂起状态,并按照先进先出的方式排队等待,直到信号量可用。 + +### 非阻塞方式获取信号量 + +``` c +int sem_trywait(sem_t *sem); +``` + +| **参数** | **描述** | +|----|----------------------| +| sem | 信号量句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +此函数是 sem_wait() 函数的非阻塞版,是 rt_sem_take(sem,0) 函数的封装。当信号量不可用时,线程不会阻塞,而是直接返回。 + +### 指定阻塞时间等待信号量 + +``` c +int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); +``` + +| **参数** | **描述** | +|------------|-------------------------------------------------| +| sem | 信号量句柄,不能为 NULL | +| abs_timeout | 指定的等待时间,单位是操作系统时钟节拍(OS Tick) | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +此函数和 sem_wait() 函数的区别在于,若信号量不可用,线程将阻塞 abs_timeout 时长,超时后函数返回 - 1,线程将被唤醒由阻塞态进入就绪态。 + +### 发送信号量 + +``` c +int sem_post(sem_t *sem); +``` + +| **参数** | **描述** | +|----|----------------------| +| sem | 信号量句柄,不能为 NULL | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +此函数将释放一个 sem 信号量,是 rt_sem_release() 函数的封装。若等待该信号量的线程队列不为空,表明有线程在等待该信号量,第一个等待该信号量的线程将由挂起状态切换到就绪状态,等待系统调度。若没有线程等待该信号量,该信号量值将加 1。 + +### 无名信号量使用示例代码 + +信号量使用的典型案例是生产者消费者模型。一个生产者线程和一个消费者线程对同一块内存进行操作,生产者往共享内存填充数据,消费者从共享内存读取数据。 + +此程序会创建 2 个线程,2 个信号量,一个信号量表示共享数据为空状态,一个信号量表示共享数据不为空状态,一个互斥锁用于保护共享资源。生产者线程生产好数据后会给消费者发送一个 full_sem 信号量,通知消费者线程有数据可用,休眠 2 秒后会等待消费者线程发送的 empty_sem 信号量。消费者线程等到生产者发送的 full_sem 后会处理共享数据,处理完后会给生产者线程发送 empty_sem 信号量。程序会这样一直循环。 + +``` c +#include +#include +#include +#include +#include + +/* 静态方式初始化一个互斥锁用于保护共享资源 */ +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +/* 2 个信号量控制块,一个表示资源空信号,一个表示资源满信号 */ +static sem_t empty_sem,full_sem; + +/* 指向线程控制块的指针 */ +static pthread_t tid1; +static pthread_t tid2; + +/* 函数返回值检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} + +/* 生产者生产的结构体数据,存放在链表里 */ +struct node +{ + int n_number; + struct node* n_next; +}; +struct node* head = NULL; /* 链表头, 是共享资源 */ + +/* 消费者线程入口函数 */ +static void* consumer(void* parameter) +{ + struct node* p_node = NULL; + + while (1) + { + sem_wait(&full_sem); + pthread_mutex_lock(&mutex); /* 对互斥锁上锁, */ + + while (head != NULL) /* 判断链表里是否有元素 */ + { + p_node = head; /* 拿到资源 */ + head = head->n_next; /* 头指针指向下一个资源 */ + /* 打印输出 */ + printf("consume %d\n",p_node->n_number); + + free(p_node); /* 拿到资源后释放节点占用的内存 */ + } + + pthread_mutex_unlock(&mutex); /* 临界区数据操作完毕,释放互斥锁 */ + + sem_post(&empty_sem); /* 发送一个空信号量给生产者 */ + } +} +/* 生产者线程入口函数 */ +static void* product(void* patameter) +{ + int count = 0; + struct node *p_node; + + while(1) + { + /* 动态分配一块结构体内存 */ + p_node = (struct node*)malloc(sizeof(struct node)); + if (p_node != NULL) + { + p_node->n_number = count++; + pthread_mutex_lock(&mutex); /* 需要操作 head 这个临界资源,先加锁 */ + + p_node->n_next = head; + head = p_node; /* 往链表头插入数据 */ + + pthread_mutex_unlock(&mutex); /* 解锁 */ + printf("produce %d\n",p_node->n_number); + + sem_post(&full_sem); /* 发送一个满信号量给消费者 */ + } + else + { + printf("product malloc node failed!\n"); + break; + } + sleep(2); /* 休眠 2 秒 */ + sem_wait(&empty_sem); /* 等待消费者发送空信号量 */ + } +} + +int rt_application_init() +{ + int result; + + sem_init(&empty_sem,NULL,0); + sem_init(&full_sem,NULL,0); + /* 创建生产者线程, 属性为默认值,入口函数是 product,入口函数参数为 NULL*/ + result = pthread_create(&tid1,NULL,product,NULL); + check_result("product thread created",result); + + /* 创建消费者线程, 属性为默认值,入口函数是 consumer,入口函数参数是 NULL */ + result = pthread_create(&tid2,NULL,consumer,NULL); + check_result("consumer thread created",result); + + return 0; +} +``` + +## 消息队列 + +消息队列是另一种常用的线程间通讯方式,它能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。 + +消息队列主要操作包括:通过函数 mq_open() 创建或者打开,调用 mq_send() 发送一条消息到消息队列,调用 mq_receive() 从消息队列获取一条消息,当消息队列不在使用时,可以调用 mq_unlink() 删除消息队列。 + +POSIX 消息队列主要用于进程间通信,RT-Thread 操作系统的 POSIX 消息队列主要是基于 RT-Thread 内核消息队列的一个封装,主要还是用于系统内线程间的通讯。使用方式和 RT-Thread 内核的消息队列差不多。 + +### 消息队列控制块 + +每个消息队列对应一个消息队列控制块,创建消息队列前需要先定义一个消息队列控制块。消息队列控制块定义在 mqueue.h 头文件里。 + +``` c +struct mqdes +{ + rt_uint16_t refcount; /* 引用计数 */ + rt_uint16_t unlinked; /* 消息队列的分离状态,值为 1 表示消息队列已经分离 */ + rt_mq_t mq; /* RT-Thread 消息队列控制块 */ + struct mqdes* next; /* 指向下一个消息队列控制块 */ +}; +typedef struct mqdes* mqd_t; /* 消息队列控制块指针类型重定义 */ +``` + +### 创建或打开消息队列 + +``` c +mqd_t mq_open(const char *name, int oflag, ...); +``` + +| **参数** | **描述** | +|----------|----------------| +| name | 消息队列名称 | +| oflag | 消息队列打开方式 | +|**返回**| —— | +| 消息队列句柄 | 成功 | +| NULL | 失败 | + +此函数会根据消息队列的名字 name 创建一个新的消息队列或者打开一个已经存在的消息队列。Oflag 的可选值有 0、O_CREAT 或 O_CREAT\|O_EXCL。如果 Oflag 设置为 O_CREAT 则会创建一个新的消息队列。如果 Oflag 设置 O_CREAT\|O_EXCL,如果消息队列已经存在则会返回 NULL,如果不存在则会创建一个新的消息队列。如果 Oflag 设置为 0,消息队列不存在则会返回 NULL。 + +### 分离消息队列 + +``` c +int mq_unlink(const char *name); +``` + +| **参数** | **描述** | +|----|------------| +| name | 消息队列名称 | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +此函数会根据消息队列名称 name 查找消息队列,若找到,则将消息队列置为分离状态,之后若持有计数为 0,则删除消息队列,并释放消息队列占有的资源。 + +### 关闭消息队列 + +``` c +int mq_close(mqd_t mqdes); +``` + +| **参数** | **描述** | +|----------|------------| +| mqdes | 消息队列句柄 | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +当一个线程终止时,会对其占用的消息队列执行此关闭操作。不论线程是自愿终止还是非自愿终止都会执行这个关闭操作,相当于是消息队列的持有计数减 1,若减 1 后持有计数为 0,且消息队列处于分离状态,则会删除 mqdes 消息队列并释放其占有的资源。 + +### 阻塞方式发送消息 + +``` c +int mq_send(mqd_t mqdes, + const char *msg_ptr, + size_t msg_len, + unsigned msg_prio); +``` + +| **参数** | **描述** | +|---------|----------------------------------| +| mqdes | 消息队列句柄, 不能为 NULL | +| sg_ptr | 指向要发送的消息的指针,不能为 NULL | +| msg_len | 发送的消息的长度 | +| msg_prio | RT-Thread 未实现参数 | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +此函数用来向 mqdes 消息队列发送一条消息,是 rt_mq_send() 函数的封装。此函数把 msg_ptr 指向的消息添加到 mqdes 消息队列中,发送的消息长度 msg_len 必须小于或者等于创建消息队列时设置的最大消息长度。 + +如果消息队列已经满,即消息队列中的消息数量等于最大消息数,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。 + +### 指定阻塞时间发送消息 + +``` c +int mq_timedsend(mqd_t mqdes, + const char *msg_ptr, + size_t msg_len, + unsigned msg_prio, + const struct timespec *abs_timeout); +``` + +| **参数** | **描述** | +|------------|-------------------------------------------------| +| mqdes | 消息队列句柄, 不能为 NULL | +| msg_ptr | 指向要发送的消息的指针,不能为 NULL | +| msg_len | 发送的消息的长度 | +| msg_prio | RT-Thread 未实现参数 | +| abs_timeout | 指定的等待时间,单位是操作系统时钟节拍(OS Tick) | +|**返回**| —— | +| 0 | 成功 | +| -1 | 失败 | + +目前 RT-Thread 不支持指定阻塞时间发送消息,但是函数接口已经实现,相当于调用 mq_send()。 + +### 阻塞方式接受消息 + +``` c +ssize_t mq_receive(mqd_t mqdes, + char *msg_ptr, + size_t msg_len, + unsigned *msg_prio); +``` + +| **参数** | **描述** | +|---------|----------------------------------| +| mqdes | 消息队列句柄, 不能为 NULL | +| msg_ptr | 指向要发送的消息的指针,不能为 NULL | +| msg_len | 发送的消息的长度 | +| msg_prio | RT-Thread 未实现参数 | +|**返回**| —— | +| 消息长度 | 成功 | +| -1 | 失败 | + +此函数会把 mqdes 消息队列里面最老的消息移除消息队列,并把消息放到 msg_ptr 指向的内存里。如果消息队列为空,调用 mq_receive() 函数的线程将会阻塞,直到消息队列中消息可用。 + +### 指定阻塞时间接受消息 + +``` c +ssize_t mq_timedreceive(mqd_t mqdes, + char *msg_ptr, + size_t msg_len, + unsigned *msg_prio, + const struct timespec *abs_timeout); +``` + +| **参数** | **描述** | +|------------|-------------------------------------------------| +| mqdes | 消息队列句柄, 不能为 NULL | +| msg_ptr | 指向要发送的消息的指针,不能为 NULL | +| msg_len | 发送的消息的长度 | +| msg_prio | RT-Thread 未实现参数 | +| abs_timeout | 指定的等待时间,单位是操作系统时钟节拍(OS Tick) | +|**返回**| —— | +| 消息长度 | 成功 | +| -1 | 失败 | + +此函数和 mq_receive() 函数的区别在于,若消息队列为空,线程将阻塞 abs_timeout 时长,超时后函数直接返回 - 1,线程将被唤醒由阻塞态进入就绪态。 + +### 消息队列示例代码 + +这个程序会创建 3 个线程,线程 2 从消息队列接受消息,线程 2 和线程 3 往消息队列发送消息。 + +``` c +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; +static pthread_t tid3; +/* 消息队列句柄 */ +static mqd_t mqueue; + +/* 函数返回值检查函数 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} +/* 线程 1 入口函数 */ +static void* thread1_entry(void* parameter) +{ + char buf[128]; + int result; + + while (1) + { + /* 从消息队列中接收消息 */ + result = mq_receive(mqueue, &buf[0], sizeof(buf), 0); + if (result != -1) + { + /* 输出内容 */ + printf("thread1 recv [%s]\n", buf); + } + + /* 休眠 1 秒 */ + // sleep(1); + } +} +/* 线程 2 入口函数 */ +static void* thread2_entry(void* parameter) +{ + int i, result; + char buf[] = "message2 No.x"; + + while (1) + { + for (i = 0; i < 10; i++) + { + buf[sizeof(buf) - 2] = '0' + i; + + printf("thread2 send [%s]\n", buf); + /* 发送消息到消息队列中 */ + result = mq_send(mqueue, &buf[0], sizeof(buf), 0); + if (result == -1) + { + /* 消息队列满, 延迟 1s 时间 */ + printf("thread2:message queue is full, delay 1s\n"); + sleep(1); + } + } + + /* 休眠 2 秒 */ + sleep(2); + } +} +/* 线程 3 入口函数 */ +static void* thread3_entry(void* parameter) +{ + int i, result; + char buf[] = "message3 No.x"; + + while (1) + { + for (i = 0; i < 10; i++) + { + buf[sizeof(buf) - 2] = '0' + i; + + printf("thread3 send [%s]\n", buf); + /* 发送消息到消息队列中 */ + result = mq_send(mqueue, &buf[0], sizeof(buf), 0); + if (result == -1) + { + /* 消息队列满, 延迟 1s 时间 */ + printf("thread3:message queue is full, delay 1s\n"); + sleep(1); + } + } + + /* 休眠 2 秒 */ + sleep(2); + } +} +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + struct mq_attr mqstat; + int oflag = O_CREAT|O_RDWR; +#define MSG_SIZE 128 +#define MAX_MSG 128 + memset(&mqstat, 0, sizeof(mqstat)); + mqstat.mq_maxmsg = MAX_MSG; + mqstat.mq_msgsize = MSG_SIZE; + mqstat.mq_flags = 0; + mqueue = mq_open("mqueue1",O_CREAT,0777,&mqstat); + + /* 创建线程 1, 线程入口是 thread1_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/ + result = pthread_create(&tid1,NULL,thread1_entry,NULL); + check_result("thread1 created",result); + + /* 创建线程 2, 线程入口是 thread2_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/ + result = pthread_create(&tid2,NULL,thread2_entry,NULL); + check_result("thread2 created",result); + + /* 创建线程 3, 线程入口是 thread3_entry, 属性参数设为 NULL 选择默认值,入口参数为 NULL*/ + result = pthread_create(&tid3,NULL,thread3_entry,NULL); + check_result("thread3 created",result); + + + return 0; +} +``` + +## 线程高级编程 + +本章节会对一些很少使用的属性对象及相关函数做详细介绍。 + +RT-Thread 实现的线程属性包括线程栈大小、线程优先级、线程分离状态、线程调度策略。pthread_create() 使用属性对象前必须先对属性对象进行初始化。设置线程属性之类的 API 函数应在创建线程之前就调用。线程属性的变更不会影响到已创建的线程。 + +线程属性结构 pthread_attr_t 定义在 pthread.h 头文件里。线程属性结构如下: + +``` c +/* pthread_attr_t 类型重定义 */ +typedef struct pthread_attr pthread_attr_t; +/* 线程属性结构体 */ +struct pthread_attr +{ + void* stack_base; /* 线程栈的地址 */ + rt_uint32_t stack_size; /* 线程栈大小 */ + rt_uint8_t priority; /* 线程优先级 */ + rt_uint8_t detachstate; /* 线程的分离状态 */ + rt_uint8_t policy; /* 线程调度策略 */ + rt_uint8_t inheritsched; /* 线程的继承性 */ +}; +``` + +#### 线程属性初始化及去初始化 + +线程属性初始化及去初始化函数如下所示: + +``` c +int pthread_attr_init(pthread_attr_t *attr); +int pthread_attr_destroy(pthread_attr_t *attr); +``` + +| **参数** | **描述** | +|----|------------------| +| attr | 指向线程属性的指针 | +|**返回**| —— | +| 0 | 成功 | + +使用 pthread_attr_init() 函数会使用默认值初始化线程属性结构体 attr,等同于调用线程初始化函数时将此参数设置为 NULL,使用前需要定义一个 pthread_attr_t 属性对象,此函数必须在 pthread_create() 函数之前调用。 + +pthread_attr_destroy() 函数对 attr 指向的属性去初始化,之后可以再次调用 pthread_attr_init() 函数对此属性对象重新初始化。 + +#### 线程的分离状态 + +设置线程的分离状态 / 获取线程的分离状态如下所示,默认情况下线程是非分离状态。 + +``` c +int pthread_attr_setdetachstate(pthread_attr_t *attr, int state); +int pthread_attr_getdetachstate(pthread_attr_t const *attr, int *state); +``` + +| **参数** | **描述** | +|----------|-------------------| +| attr | 指向线程属性的指针 | +| state | 线程分离状态 | +|**返回**| —— | +| 0 | 成功 | + +线程分离状态属性值 state 可以是 PTHREAD_CREATE_JOINABL(非分离)和 +PTHREAD_CREATE_DETACHED(分离)。 + +线程的分离状态决定一个线程以什么样的方式来回收自己运行结束后占用的资源。线程的分离状态有 2 种:joinable 或者 detached。当线程创建后,应该调用 pthread_join() 或者 pthread_detach() 回收线程结束运行后占用的资源。如果线程的分离状态为 joinable 其他线程可以调用 pthread_join() 函数等待该线程结束并获取线程返回值,然后回收线程占用的资源。分离状态为 detached 的线程不能被其他的线程所 join,自己运行结束后,马上释放系统资源。 + +#### 线程的调度策略 + +设置 \ 获取线程调度策略函数如下所示: + +``` c +int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy); +int pthread_attr_getschedpolicy(pthread_attr_t const *attr, int *policy); +``` + +只实现了函数接口,默认不同优先级基于优先级调度,同一优先级时间片轮询调度 + +#### 线程的调度参数 + +设置线程的优先级 / 获取线程的优先级函数如下所示: + +``` c +int pthread_attr_setschedparam(pthread_attr_t *attr, + struct sched_param const *param); +int pthread_attr_getschedparam(pthread_attr_t const *attr, + struct sched_param *param); +``` + +| **参数** | **描述** | +|----------|------------------| +| attr | 指向线程属性的指针 | +| param | 指向调度参数的指针 | +|**返回**| —— | +| 0 | 成功 | + +pthread_attr_setschedparam() 函数设置线程的优先级。使用 param 对线程属性优先级赋值。 + +**参数** struct sched_param 定义在 sched.h 里,结构如下: + +``` c +struct sched_param +{ + int sched_priority; /* 线程优先级 */ +}; +``` + +结构体 sched_param 的成员 sched_priority 控制线程的优先级值。 + +#### 线程的堆栈大小 + +设置 / 获取 线程的堆栈大小的函数如下所示: + +``` c +int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stack_size); +int pthread_attr_getstacksize(pthread_attr_t const *attr, size_t *stack_size); +``` + +| **参数** | **描述** | +|-----------|------------------| +| attr | 指向线程属性的指针 | +| stack_size | 线程堆栈大小 | +|**返回**| —— | +| 0 | 成功 | + +pthread_attr_setstacksize() 函数可以设置堆栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐)。 + +#### 线程堆栈大小和地址 + +设置 / 获取 线程的堆栈地址和堆栈大小的函数如下所示: + +``` c +int pthread_attr_setstack(pthread_attr_t *attr, + void *stack_base, + size_t stack_size); +int pthread_attr_getstack(pthread_attr_t const *attr, + void**stack_base, + size_t *stack_size); +``` + +| **参数** | **描述** | +|-----------|------------------| +| attr | 指向线程属性的指针 | +| stack_size | 线程堆栈大小 | +| stack_base | 线程堆栈地址 | +|**返回**| —— | +| 0 | 成功 | + +#### 线程属性相关函数 + +设置 / 获取线程的作用域的函数如下所示: + +``` c +int pthread_attr_setscope(pthread_attr_t *attr, int scope); +int pthread_attr_getscope(pthread_attr_t const *attr); +``` + +| **参数** | **描述** | +|-----------|------------------| +| attr | 指向线程属性的指针 | +| scope | 线程作用域 | +|**返回**| —— | +| 0 | scope 为 PTHREAD_SCOPE_SYSTEM | +| EOPNOTSUPP | scope 为 PTHREAD_SCOPE_PROCESS | +| EINVAL | scope 为 PTHREAD_SCOPE_SYSTEM | + +#### 线程属性示例代码 + +这个程序会初始化 2 个线程,它们拥有共同的入口函数,但是它们的入口参数不相同。最先创建的线程会使用提供的 attr 线程属性,另外一个线程使用系统默认的属性。线程的优先级是很重要的一个参数,因此这个程序会修改第一个创建的线程的优先级为 8,而系统默认的优先级为 24。 + +``` c +#include +#include +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; + +/* 函数返回值检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} +/* 线程入口函数 */ +static void* thread_entry(void* parameter) +{ + int count = 0; + int no = (int) parameter; /* 获得线程的入口参数 */ + + while (1) + { + /* 打印输出线程计数值 */ + printf("thread%d count: %d\n", no, count ++); + + sleep(2); /* 休眠 2 秒 */ + } +} + +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + pthread_attr_t attr; /* 线程属性 */ + struct sched_param prio; /* 线程优先级 */ + + prio.sched_priority = 8; /* 优先级设置为 8 */ + pthread_attr_init(&attr); /* 先使用默认值初始化属性 */ + pthread_attr_setschedparam(&attr,&prio); /* 修改属性对应的优先级 */ + + /* 创建线程 1, 属性为 attr,入口函数是 thread_entry,入口函数参数是 1 */ + result = pthread_create(&tid1,&attr,thread_entry,(void*)1); + check_result("thread1 created",result); + + /* 创建线程 2, 属性为默认值,入口函数是 thread_entry,入口函数参数是 2 */ + result = pthread_create(&tid2,NULL,thread_entry,(void*)2); + check_result("thread2 created",result); + + return 0; +} +``` + +### 线程取消 + +取消是一种让一个线程可以结束其它线程运行的机制。一个线程可以对另一个线程发送一个取消请求。依据设置的不同,目标线程可能会置之不理,可能会立即结束也可能会将它推迟到下一个取消点才结束。 + +#### 发送取消请求 + +可使用如下函数发送取消请求: + +``` c +int pthread_cancel(pthread_t thread); +``` + +| **参数** | **描述** | +|------|--------| +| thread | 线程句柄 | +|**返回**| —— | +| 0 | 成功 | + +此函数发送取消请求给 thread 线程。Thread 线程是否会对取消请求做出回应以及什么时候做出回应依赖于线程取消的状态及类型。 + +#### 设置取消状态 + +可使用如下函数设置取消请求: + +``` c +int pthread_setcancelstate(int state, int *oldstate); +``` + +| **参数** | **描述** | +|--------|-------------------------------| +| state | 有两种值:PTHREAD_CANCEL_ENABLE:取消使能 PTHREAD_CANCEL_DISABLE:取消不使能(线程创建时的默认值) | +| oldstate | 保存原来的取消状态 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | state 非 PTHREAD_CANCEL_ENABLE 或者 PTHREAD_CANCEL_DISABLE | + +此函数设置取消状态,由线程自己调用。取消使能的线程将会对取消请求做出反应,而取消没有使能的线程不会对取消请求做出反应。 + +#### 设置取消类型 + +可使用如下函数设置取消类型,由线程自己调用: + +``` c +int pthread_setcanceltype(int type, int *oldtype); +``` + +| **参数** | **描述** | +|-------|---------------------------------| +| type | 有 2 种值:PTHREAD_CANCEL_DEFFERED:线程收到取消请求后继续运行至下一个取消点再结束。(线程创建时的默认值)PTHREAD_CANCEL_ASYNCHRONOUS:线程立即结束。 | +| oldtype | 保存原来的取消类型 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | state 非 PTHREAD_CANCEL_DEFFERED 或者 PTHREAD_CANCEL_ASYNCHRONOUS | + +#### 设置取消点 + +可使用如下函数设置取消点: + +``` c +void pthread_testcancel(void); +``` + +此函数在线程调用的地方创建一个取消点。主要由不包含取消点的线程调用,可以回应取消请求。如果在取消状态处于禁用状态下调用 pthread_testcancel(),则该函数不起作用。 + +#### 取消点 + +取消点也就是线程接受取消请求后会结束运行的地方,根据 POSIX 标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait() 等会引起阻塞的系统调用都是取消点。 + +RT-Thread 包含的所有取消点如下: + +- mq_receive() + +- mq_send() + +- mq_timedreceive() + +- mq_timedsend() + +- msgrcv() + +- msgsnd() + +- msync() + +- pthread_cond_timedwait() + +- pthread_cond_wait() + +- pthread_join() + +- pthread_testcancel() + +- sem_timedwait() + +- sem_wait() + +- pthread_rwlock_rdlock() + +- pthread_rwlock_timedrdlock() + +- pthread_rwlock_timedwrlock() + +- pthread_rwlock_wrlock() + +#### 线程取消示例代码 + +此程序会创建 2 个线程,线程 2 开始运行后马上休眠 8 秒,线程 1 设置了自己的取消状态和类型,之后在一个无限循环里打印运行计数信息。线程 2 唤醒后向线程 1 发送取消请求,线程 1 收到取消请求后马上结束运行。 + +``` c +#include +#include +#include + +/* 线程控制块 */ +static pthread_t tid1; +static pthread_t tid2; + +/* 函数返回值检查 */ +static void check_result(char* str,int result) +{ + if (0 == result) + { + printf("%s successfully!\n",str); + } + else + { + printf("%s failed! error code is %d\n",str,result); + } +} +/* 线程 1 入口函数 */ +static void* thread1_entry(void* parameter) +{ + int count = 0; + /* 设置线程 1 的取消状态使能,取消类型为线程收到取消点后马上结束 */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + while(1) + { + /* 打印线程计数值输出 */ + printf("thread1 run count: %d\n",count ++); + sleep(2); /* 休眠 2 秒 */ + } +} +/* 线程 2 入口函数 */ +static void* thread2_entry(void* parameter) +{ + int count = 0; + sleep(8); + /* 向线程 1 发送取消请求 */ + pthread_cancel(tid1); + /* 阻塞等待线程 1 运行结束 */ + pthread_join(tid1,NULL); + printf("thread1 exited!\n"); + /* 线程 2 打印信息开始输出 */ + while(1) + { + /* 打印线程计数值输出 */ + printf("thread2 run count: %d\n",count ++); + sleep(2); /* 休眠 2 秒 */ + } +} +/* 用户应用入口 */ +int rt_application_init() +{ + int result; + /* 创建线程 1, 属性为默认值,分离状态为默认值 joinable, + 入口函数是 thread1_entry,入口函数参数为 NULL */ + result = pthread_create(&tid1,NULL,thread1_entry,NULL); + check_result("thread1 created",result); + + /* 创建线程 2, 属性为默认值,分离状态为默认值 joinable, + 入口函数是 thread2_entry,入口函数参数为 NULL */ + result = pthread_create(&tid2,NULL,thread2_entry,NULL); + check_result("thread2 created",result); + + return 0; +} +``` + +### 一次性初始化 + +可使用如下函数一次性初始化: + +``` c +int pthread_once(pthread_once_t * once_control, void (*init_routine) (void)); +``` + +| **参数** | **描述** | +|-------------|--------| +| once_control | 控制变量 | +| init_routine | 执行函数 | +|**返回**| —— | +| 0 | 成功 | + +有时候我们需要对一些变量只进行一次初始化。如果我们进行多次初始化程序就会出现错误。在传统的顺序编程中,一次性初始化经常通过使用布尔变量来管理。控制变量被静态初始化为 0,而任何依赖于初始化的代码都能测试该变量。如果变量值仍然为 0,则它能实行初始化,然后将变量置为 1。以后检查的代码将跳过初始化。 + +### 线程结束后清理 + +线程清理函数接口如下所示: + +``` c +void pthread_cleanup_pop(int execute); +void pthread_cleanup_push(void (*routine)(void*), void *arg); +``` + +| **参数** | **描述** | +|-------|-----------------------------| +| execute | 0 或 1,决定是否执行 cleanup 函数 | +| routine | 指向清理函数的指针 | +| arg | 传递给清理函数的参数 | + +pthread_cleanup_push() 把指定的清理函数 routine 放到线程的清理函数链表里, pthread_cleanup_pop() 从清理函数链表头部取出第一项函数,若 execute 为非 0 值,则执行此函数。 + +### 其他线程相关函数 + +#### 判断 2 个线程是否相等 + +``` c +int pthread_equal (pthread_t t1, pthread_t t2); +``` + +| **参数** | **描述** | +|----------|--------| +| pthread_t | 线程句柄 | +|**返回**| —— | +| 0 | 不相等 | +| 1 | 相等 | + +#### 获取线程句柄 + +``` c +pthread_t pthread_self (void); +``` +pthread_self() 返回调用线程的句柄。 + +#### 获取最大最小优先级 + +``` c +int sched_get_priority_min(int policy); +int sched_get_priority_max(int policy); +``` + +| **参数** | **描述** | +|------|---------------------------------| +| policy | 2 个值可选:SCHED_FIFO,SCHED_RR | + +sched_get_priority_min() 返回值为 0,RT-Thread 里为最大优先级, sched_get_priority_max() 返回值最小优先级。 + +### 互斥锁属性 + +RT-Thread 实现的互斥锁属性包括互斥锁类型和互斥锁作用域。 + +#### 互斥锁属性初始化及去初始化 + +``` c +int pthread_mutexattr_init(pthread_mutexattr_t *attr); +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); +``` + +| **参数** | **描述** | +|----|------------------------| +| attr | 指向互斥锁属性对象的指针 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +pthread_mutexattr_init() 函数将使用默认值初始化 attr 指向的属性对象,等同于调用 pthread_mutex_init() 函数时将属性参数设置为 NULL。 + +pthread_mutexattr_destroy() 函数将会对 attr 指向的属性对象去初始化,之后可以调用 pthread_mutexattr_init() 函数重新初始化。 + +#### 互斥锁作用域 + +``` c +int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); +int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared); +``` + +| **参数** | **描述** | +|-------|--------------------| +| type | 互斥锁类型 | +| pshared | 有 2 个可选值: PTHREAD_PROCESS_PRIVATE:默认值,用于仅同步该进程中的线程。PTHREAD_PROCESS_SHARED:用于同步该进程和其他进程中的线程。 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +#### 互斥锁类型 + +``` c +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); +int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type); +``` + +| **参数** | **描述** | +|----|------------------------| +| type | 互斥锁类型 | +| attr | 指向互斥锁属性对象的指针 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +互斥锁的类型决定了一个线程在获取一个互斥锁时的表现方式,RT-Thread 实现了 3 种互斥锁类型: + +- PTHREAD_MUTEX_NORMAL:普通锁,当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按先进先出方式获得锁。如果一个线程在不首先解除互斥锁的情况下尝试重新获得该互斥锁,不会产生死锁,而是返回错误码,和检错锁一样。 + +- PTHREAD_MUTEX_RECURSIVE:嵌套锁,允许一个线程对同一个锁成功获得多次,需要相同次数的解锁释放该互斥锁。 + +- PTHREAD_MUTEX_ERRORCHECK:检错锁,如果一个线程在不首先解除互斥锁的情况下尝试重新获得该互斥锁,则返回错误。这样就保证当不允许多次加锁时不会出现死锁。 + +### 条件变量属性 + +使用默认值 PTHREAD_PROCESS_PRIVATE 初始化条件变量属性 attr 可使用如下函数: + +``` c +int pthread_condattr_init(pthread_condattr_t *attr); +``` + +| **参数** | **描述** | +|----|--------------------------| +| attr | 指向条件变量属性对象的指针 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +#### 获取条件变量作用域 + +``` c +int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared); +``` + +| **参数** | **描述** | +|----|--------------------------| +| attr | 指向条件变量属性对象的指针 | +|**返回**| —— | +| 0 | 成功 | +| EINVAL | 参数无效 | + +### 读写锁属性 + +#### 初始化属性 + +``` c +int pthread_rwlockattr_init (pthread_rwlockattr_t *attr); +``` + +| **参数** | **描述** | +|----|--------------------| +| attr | 指向读写锁属性的指针 | +|**返回**| —— | +| 0 | 成功 | +|-1 | 参数无效 | + +该函数会使用默认值 PTHREAD_PROCESS_PRIVATE 初始化读写锁属性 attr。 + +#### 获取作用域 + +``` c +int pthread_rwlockattr_getpshared (const pthread_rwlockattr_t *attr, int *pshared); +``` + +| **参数** | **描述** | +|-------|--------------------------| +| attr | 指向读写锁属性的指针 | +| pshared | 指向保存读写锁作用域的指针 | +|**返回**| —— | +| 0 | 成功 | +|-1 | 参数无效 | + +pshared 指向的内存保存的值为 PTHREAD_PROCESS_PRIVATE。 + +### 屏障属性 + +#### 初始化属性 + +``` c +int pthread_barrierattr_init(pthread_barrierattr_t *attr); +``` + +| **参数** | **描述** | +|----|------------------| +| attr | 指向屏障属性的指针 | +|**返回**| —— | +| 0 | 成功 | +|-1 | 参数无效 | + +改函数会使用默认值 PTHREAD_PROCESS_PRIVATE 初始化屏障属性 attr。 + +#### 获取作用域 + +``` c +int pthread_barrierattr_getpshared(const pthread_barrierattr_t *attr, int *pshared); +``` + +| **参数** | **描述** | +|-------|-----------------------------| +| attr | 指向屏障属性的指针 | +| pshared | 指向保存屏障作用域数据的指针 | +|**返回**| —— | +| 0 | 成功 | +|-1 | 参数无效 | + +### 消息队列属性 + +消息队列属性控制块如下: + +``` c +struct mq_attr +{ + long mq_flags; /* 消息队列的标志,用来表示是否阻塞 */ + long mq_maxmsg; /* 消息队列最大消息数 */ + long mq_msgsize; /* 消息队列每个消息的最大字节数 */ + long mq_curmsgs; /* 消息队列当前消息数 */ +}; +``` +#### 获取属性 +``` c +int mq_getattr(mqd_t mqdes, struct mq_attr *mqstat); +``` + +| **参数** | **描述** | +|------|------------------------| +| mqdes | 指向消息队列控制块的指针 | +| mqstat | 指向保存获取数据的指针 | +|**返回**| —— | +| 0 | 成功 | +|-1 | 参数无效 | diff --git a/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00ENC28J60.png b/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00ENC28J60.png new file mode 100644 index 0000000000000000000000000000000000000000..6b07ea7282719e45136567287afff9e0959173fc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00ENC28J60.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00IoTboard.png b/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00IoTboard.png new file mode 100644 index 0000000000000000000000000000000000000000..6be7fce77b2f5dda9b9a1834bad801d79900ae8a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00IoTboard.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00QRcode.png b/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00QRcode.png new file mode 100644 index 0000000000000000000000000000000000000000..dad965b618232d5a0880b1b4c736646ca8598553 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00QRcode.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00board.png b/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00board.png new file mode 100644 index 0000000000000000000000000000000000000000..caa3532a44dc06aca0a2740de4da9b862a5c33fe Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/preface/figures/00board.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/preface/preface.md b/rt-thread-version/rt-thread-standard/programming-manual/preface/preface.md new file mode 100644 index 0000000000000000000000000000000000000000..277814489aa435d7c557045ce3d193a7434492fd --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/preface/preface.md @@ -0,0 +1,64 @@ +前 言 +===== + +为什么要写这本书 +---------------- + +自2006年发布V0.01版起,到今年正式发布V4.0版,RT-Thread历经12年的累积发展,凭借良好的口碑和开源免费的策略,已经拥有了一个国内最大的嵌入式开源社区,积聚了数十万的软件爱好者。RT-Thread被广泛应用于能源、车载、医疗、消费电子等众多行业,成为国人自主开发、最成熟稳定和装机量最大的开源嵌入式操作系统。 + +深处于行业中,我们深刻地感受到近年来国内芯片产业和物联网产业快速崛起的趋势,行业发展迫切需要更多人才,尤其是需要掌握嵌入式操作系统等底层技术人才,我们希望通过本书让RT-Thread触达更多人群,让更多人了解集聚国人智慧的RT-Thread这一小而美的操作系统,从而让RT-Thread赋能更多行业,真正做到“积识成睿,慧泽百川”。 + +另外,高校大学生是RT-Thread非常重视的群体,从2018年起,RT-Thread启动一系列大学生计划,包括送书计划、培训计划、合作开课、赞助竞赛等,帮助学生了解、学习RT-Thread,在编写本书过程中,尽可能做到简单、易懂,能够让大学生轻松上手RT-Thread,希望通过本书能够加速RT-Thread在高校普及。 + +总之,本书初衷在于降低RT-Thread的学习门槛,让更多人能轻松学习RT-Thread,掌握RT-Thread,从而能够一起参与开发RT-Thread,共同打造开源开放、小而美物联网操作系统。 + +读者对象 +-------- + +- 所有使用C/C++进行编程的开发人员 + +- 高校计算机/电子/自动化/通信类专业学生、老师 + +- 嵌入式软硬件工程师、电子工程师、物联网开发工程师 + +- 其他对嵌入式操作系统感兴趣人员 + +如何阅读本书 +------------ + +为了能够阅读本书,建议先学习C语言和STM32编程知识,如果有数据结构和面向对象编程基础则更佳。学习本书时,大多数章节都有配套示例代码,这些代码都可以实际运行,建议按照边阅读边实战的方式进行学习,读完一章的同时完成该章示例实验。 + +本书分为两大部分,共16章,第1-10章为内核篇;第11-16章为组件篇。 + +第1-9章介绍RT-Thread内核,首先对RT-Thread进行总体介绍,在随后各个章节中分别介绍RT-Thread的线程管理、时钟管理、线程间同步、线程间通信、内存管理、中断管理,每章都有配套的示例代码,这部分示例可运行在Keil MDK模拟器环境下,不需要任何硬件。 + +第10章介绍RT-Thread内核移植,读完本章,可以将RT-Thread移植到实际的硬件板上运行。 + +第11-16章介绍RT-Thread组件部分,分别介绍Env开发环境、FinSH控制台、设备管理、文件系统和网络框架,这部分配套示例可以运行在硬件板上,分别完成外设访问,文件系统读写,网络通信功能。 + +本书配套资料包括实验源码及相关工具软件,硬件资料,可以通过关注微信公众号“RTThread物联网操作系统”获得。 + +![官方微信](figures/00QRcode.png) + +配套硬件 +-------- + +本书配套硬件为RT-Thread与正点原子联合开发的IoT +Board开发板,基于STM32L475主芯片,本书组件篇配套的示例代码都基于IoT Board。 + +![IoT Board开发板](figures/00IoTboard.png) + +本书第16章需要使用到如下图所示的ENC28J60模块实现网络示例功能。 + +![ENC28J60网络模块](figures/00ENC28J60.png) + +如果已经购买其他开发板,如下图所示的野火和正点原子开发板,也可以配合本书籍进行学习,前提是根据第10章介绍完成RT-Thread内核在开发板上的移植,然后实现相关的外设驱动。 + +![1544513848694](figures/00board.png) + +勘误和支持 +---------- + +由于作者水平有限,编写时间仓促,书中难免会出现一些错误或者不准确的地方,恳请读者到论坛发帖指正,RT-Thread官方论坛地址:https://www.rt-thread.org/qa/。在学习过程中遇到任何问题,也可以发帖交流,期待能够得到你们的真挚反馈,在技术之路上互勉共进。 + + diff --git a/rt-thread-version/rt-thread-standard/programming-manual/readme.md b/rt-thread-version/rt-thread-standard/programming-manual/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..c8c29f0fbce49ef4e5fe54c385cbe6aa45b425ce --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/readme.md @@ -0,0 +1,34 @@ +# RT-Thread 编程指南 + +* [RT-Thread简介](../tutorial/quick-start/introduction/introduction.md) +* [快速入门](../tutorial/quick-start/stm32f103-simulator/stm32f103-simulator.md) +* [内核基础](basic/basic.md) +* [线程管理](thread/thread.md) +* [时钟管理](timer/timer.md) +* [线程间同步](ipc1/ipc1.md) +* [线程间通信](ipc2/ipc2.md) +* [内存管理](memory/memory.md) +* [中断管理](interrupt/interrupt.md) +* [内核移植](porting/porting.md) +* [Env用户手册](env/env.md) +* [设备模型](device/device.md) +* [UART设备](device/uart/uart.md) +* [PIN设备](device/pin/pin.md) +* [ADC设备](device/adc/adc.md) +* [HWTimer设备](device/hwtimer/hwtimer.md) +* [I2C设备](device/i2c/i2c.md) +* [PWM设备](device/pwm/pwm.md) +* [RTC设备](device/rtc/rtc.md) +* [SPI设备](device/spi/spi.md) +* [WATCHDOG设备](device/watchdog/watchdog.md) +* [WLAN设备](device/wlan/wlan.md) +* [FinSH控制台](finsh/finsh.md) +* [ulog日志](ulog/ulog.md) +* [文件系统](filesystem/filesystem.md) +* [动态模块](dlmodule/dlmodule.md) +* [netdev网卡](netdev/netdev.md) +* [AT组件](at/at.md) +* [网络框架](sal/sal.md) +* [电源管理](pm/pm.md) +* [POSIX接口](posix/posix.md) +* [SCons构建工具](scons/scons.md) diff --git a/rt-thread-version/rt-thread-standard/programming-manual/sal/figures/network_frame.jpg b/rt-thread-version/rt-thread-standard/programming-manual/sal/figures/network_frame.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5980ac6d929c4aa3387a02c6305c876a328de658 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/sal/figures/network_frame.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/sal/sal.md b/rt-thread-version/rt-thread-standard/programming-manual/sal/sal.md new file mode 100644 index 0000000000000000000000000000000000000000..64282c95a10aa9ac63c619649f887084932f248b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/sal/sal.md @@ -0,0 +1,824 @@ +# SAL 套接字抽象层 + +## SAL 简介 + +为了适配更多的网络协议栈类型,避免系统对单一网络协议栈的依赖,RT-Thread 系统提供了一套 SAL(套接字抽象层)组件,该组件完成对不同网络协议栈或网络实现接口的抽象并对上层提供一组标准的 BSD Socket API,这样开发者只需要关心和使用网络应用层提供的网络接口,而无需关心底层具体网络协议栈类型和实现,极大的提高了系统的兼容性,方便开发者完成协议栈的适配和网络相关的开发。SAL 组件主要功能特点: + +- 抽象、统一多种网络协议栈接口; +- 提供 Socket 层面的 TLS 加密传输特性; +- 支持标准 BSD Socket API; +- 统一的 FD 管理,便于使用 read/write poll/select 来操作网络功能; + +### SAL 网络框架 + +RT-Thread 的 网络框架结构如下所示: + +![网络框架图](figures/network_frame.jpg) + +最顶层是网络应用层,提供一套标准 BSD Socket API ,如 socket、connect 等函数,用于系统中大部分网络开发应用。 + +往下第二部分为 SAL 套接字抽象层,通过它 RT-Thread 系统能够适配下层不同的网络协议栈,并提供给上层统一的网络编程接口,方便不同协议栈的接入。套接字抽象层为上层应用层提供接口有:accept、connect、send、recv 等。 + +第三部分为 netdev 网卡层,主要作用是解决多网卡情况设备网络连接和网络管理相关问题,通过 netdev 网卡层用户可以统一管理各个网卡信息和网络连接状态,并且可以使用统一的网卡调试命令接口。 + +第四部分为协议栈层,该层包括几种常用的 TCP/IP 协议栈,例如嵌入式开发中常用的轻型 TCP/IP 协议栈 lwIP 以及 RT-Thread 自主研发的 AT Socket 网络功能实现等。这些协议栈或网络功能实现直接和硬件接触,完成数据从网络层到传输层的转化。 + +RT-Thread 的网络应用层提供的接口主要以标准 BSD Socket API 为主,这样能确保程序可以在 PC 上编写、调试,然后再移植到 RT-Thread 操作系统上。 + +### 工作原理 + +SAL 组件工作原理的介绍主要分为如下三部分: + +- 多协议栈接入与接口函数统一抽象功能; +- SAL TLS 加密传输功能; + +#### 多协议栈接入与接口函数统一抽象功能 + +对于不同的协议栈或网络功能实现,网络接口的名称可能各不相同,以 connect 连接函数为例,lwIP 协议栈中接口名称为 lwip_connect ,而 AT Socket 网络实现中接口名称为 at_connect。SAL 组件提供对不同协议栈或网络实现接口的抽象和统一,组件在 socket 创建时通过**判断传入的协议簇(domain)类型来判断使用的协议栈或网络功能**,完成 RT-Thread 系统中多协议的接入与使用。 + +目前 SAL 组件支持的协议栈或网络实现类型有:**lwIP 协议栈**、**AT Socket 协议栈**、**WIZnet 硬件 TCP/IP 协议栈**。 + +```c +int socket(int domain, int type, int protocol); +``` + +上述为标准 BSD Socket API 中 socket 创建函数的定义,`domain` 表示协议域又称为协议簇(family),用于判断使用哪种协议栈或网络实现,AT Socket 协议栈使用的簇类型为 **AF_AT**,lwIP 协议栈使用协议簇类型有 **AF_INET**等,WIZnet 协议栈使用的协议簇类型为 **AF_WIZ**。 + +对于不同的软件包,socket 传入的协议簇类型可能是固定的,不会随着 SAL 组件接入方式的不同而改变。**为了动态适配不同协议栈或网络实现的接入**,SAL 组件中对于每个协议栈或者网络实现提供两种协议簇类型匹配方式:**主协议簇类型和次协议簇类型**。socket 创建时先判断传入协议簇类型是否存在已经支持的主协议类型,如果是则使用对应协议栈或网络实现,如果不是判断次协议簇类型是否支持。目前系统支持协议簇类型如下: + +``` +lwIP 协议栈: family = AF_INET、sec_family = AF_INET +AT Socket 协议栈: family = AF_AT、sec_family = AF_INET +WIZnet 硬件 TCP/IP 协议栈: family = AF_WIZ、sec_family = AF_INET +``` + +SAL 组件主要作用是统一抽象底层 BSD Socket API 接口,下面以 connect 函数调用流程为例说明 SAL 组件函数调用方式: + +- connect:SAL 组件对外提供的抽象的 BSD Socket API,用于统一 fd 管理; +- sal_connect:SAL 组件中 connect 实现函数,用于调用底层协议栈注册的 operation 函数。 +- lwip_connect:底层协议栈提供的层 connect 连接函数,在网卡初始化完成时注册到 SAL 组件中,最终调用的操作函数。 + +```c +/* SAL 组件为应用层提供的标准 BSD Socket API */ +int connect(int s, const struct sockaddr *name, socklen_t namelen) +{ + /* 获取 SAL 套接字描述符 */ + int socket = dfs_net_getsocket(s); + + /* 通过 SAL 套接字描述符执行 sal_connect 函数 */ + return sal_connect(socket, name, namelen); +} + +/* SAL 组件抽象函数接口实现 */ +int sal_connect(int socket, const struct sockaddr *name, socklen_t namelen) +{ + struct sal_socket *sock; + struct sal_proto_family *pf; + int ret; + + /* 检查 SAL socket 结构体是否正常 */ + SAL_SOCKET_OBJ_GET(sock, socket); + + /* 检查当前 socket 网络连接状态是否正常 */ + SAL_NETDEV_IS_COMMONICABLE(sock->netdev); + /* 检查当前 socket 对应的底层 operation 函数是否正常 */ + SAL_NETDEV_SOCKETOPS_VALID(sock->netdev, pf, connect); + + /* 执行底层注册的 connect operation 函数 */ + ret = pf->skt_ops->connect((int) sock->user_data, name, namelen); +#ifdef SAL_USING_TLS + if (ret >= 0 && SAL_SOCKOPS_PROTO_TLS_VALID(sock, connect)) + { + if (proto_tls->ops->connect(sock->user_data_tls) < 0) + { + return -1; + } + return ret; + } +#endif + return ret; +} + +/* lwIP 协议栈函数底层 connect 函数实现 */ +int lwip_connect(int socket, const struct sockaddr *name, socklen_t namelen) +{ + ... +} +``` + +#### SAL TLS 加密传输功能 + +**1. SAL TLS 功能介绍** + +在 TCP、UDP等协议数据传输时,由于数据包是明文的,所以很可能被其他人拦截并解析出信息,这给信息的安全传输带来很大的影响。为了解决此类问题,一般需要用户在应用层和传输层之间添加 SSL/TLS 协议。 + +TLS(Transport Layer Security,传输层安全协议) 是建立在传输层 TCP 协议之上的协议,其前身是 SSL(Secure Socket Layer,安全套接字层 ),主要作用是将应用层的报文进行非对称加密后再由 TCP 协议进行传输,实现了数据的加密安全交互。 + +目前常用的 TLS 方式:**MbedTLS、OpenSSL、s2n** 等,但是对于不同的加密方式,需要使用其指定的加密接口和流程进行加密,对于部分应用层协议的移植较为复杂。因此 SAL TLS 功能产生,主要作用是**提供 Socket 层面的 TLS 加密传输特性,抽象多种 TLS 处理方式,提供统一的接口用于完成 TLS 数据交互**。 + +**2. SAL TLS 功能使用方式** + +使用流程如下: + +- 配置开启任意网络协议栈支持(如 lwIP 协议栈); + +- 配置开启 MbedTLS 软件包(目前只支持 MbedTLS 类型加密方式); + +- 配置开启 SAL_TLS 功能支持(如下配置选项章节所示); + +配置完成之后,只要在 socket 创建时传入的 `protocol` 类型使用 **PROTOCOL_TLS** 或 **PROTOCOL_DTLS **,即可使用标准 BSD Socket API 接口,完成 TLS 连接的建立和数据的收发。示例代码如下所示: + +```c +#include +#include + +#include +#include +#include + +/* RT-Thread 官网,支持 TLS 功能 */ +#define SAL_TLS_HOST "www.rt-thread.org" +#define SAL_TLS_PORT 443 +#define SAL_TLS_BUFSZ 1024 + +static const char *send_data = "GET /download/rt-thread.txt HTTP/1.1\r\n" + "Host: www.rt-thread.org\r\n" + "User-Agent: rtthread/4.0.1 rtt\r\n\r\n"; + +void sal_tls_test(void) +{ + int ret, i; + char *recv_data; + struct hostent *host; + int sock = -1, bytes_received; + struct sockaddr_in server_addr; + + /* 通过函数入口参数url获得host地址(如果是域名,会做域名解析) */ + host = gethostbyname(SAL_TLS_HOST); + + recv_data = rt_calloc(1, SAL_TLS_BUFSZ); + if (recv_data == RT_NULL) + { + rt_kprintf("No memory\n"); + return; + } + + /* 创建一个socket,类型是SOCKET_STREAM,TCP 协议, TLS 类型 */ + if ((sock = socket(AF_INET, SOCK_STREAM, PROTOCOL_TLS)) < 0) + { + rt_kprintf("Socket error\n"); + goto __exit; + } + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(SAL_TLS_PORT); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0) + { + rt_kprintf("Connect fail!\n"); + goto __exit; + } + + /* 发送数据到 socket 连接 */ + ret = send(sock, send_data, strlen(send_data), 0); + if (ret <= 0) + { + rt_kprintf("send error,close the socket.\n"); + goto __exit; + } + + /* 接收并打印响应的数据,使用加密数据传输 */ + bytes_received = recv(sock, recv_data, SAL_TLS_BUFSZ - 1, 0); + if (bytes_received <= 0) + { + rt_kprintf("received error,close the socket.\n"); + goto __exit; + } + + rt_kprintf("recv data:\n"); + for (i = 0; i < bytes_received; i++) + { + rt_kprintf("%c", recv_data[i]); + } + +__exit: + if (recv_data) + rt_free(recv_data); + + if (sock >= 0) + closesocket(sock); +} + +#ifdef FINSH_USING_MSH +#include +MSH_CMD_EXPORT(sal_tls_test, SAL TLS function test); +#endif /* FINSH_USING_MSH */ +``` + +### 配置选项 + +当我们使用 SAL 组件时需要在 rtconfig.h 中定义如下宏定义: + +| **宏定义** | **描述** | +|--------------------------------|--------------------------------| +| RT_USING_SAL | 开启 SAL 功能 | +| SAL_USING_LWIP | 开启 lwIP 协议栈支持 | +| SAL_USING_AT | 开启 AT Socket 协议栈支持 | +| SAL_USING_TLS | 开启 SAL TLS 功能支持 | +| SAL_USING_POSIX | 开启 POSIX 文件系统相关函数支持,如 read、write、select/poll 等 | + +目前 SAL 抽象层支持 lwIP 协 议栈、 AT Socket 协议栈和 WIZnet 硬件 TCP/IP 协议栈,系统中开启 SAL 需要至少开启一种协议栈支持。 + +上面配置选项可以直接在 `rtconfig.h` 文件中添加使用,也可以通过组件包管理工具 ENV 配置选项加入,ENV 工具中具体配置路径如下: + +```c +RT-Thread Components ---> + Network ---> + Socket abstraction layer ---> + [*] Enable socket abstraction layer + protocol stack implement ---> + [ ] Support lwIP stack + [ ] Support AT Commands stack + [ ] Support MbedTLS protocol + [*] Enable BSD socket operated by file system API +``` + +配置完成可以通过 scons 命令重新生成功能,完成 SAL 组件的添加。 + +## 初始化 ## + +配置开启 SAL 选项之后,需要在启动时对它进行初始化,开启 SAL 功能,如果程序中已经使用了组件自动初始化,则不再需要额外进行单独的初始化,否则需要在初始化任务中调用如下函数: + +```c +int sal_init(void); +``` + +该初始化函数主要是对 SAL 组件进行初始,支持组件重复初始化判断,完成对组件中使用的互斥锁等资源的初始化。 SAL 组件中没有创建新的线程,这也意味着 SAL 组件资源占用极小,目前**SAL 组件资源占用为 ROM 2.8K 和 RAM 0.6K**。 + + +## BSD Socket API 介绍 ## + +SAL 组件抽象出标准 BSD Socket API 接口,如下是对常用网络接口的介绍: + +### 创建套接字(socket) + +``` c +int socket(int domain, int type, int protocol); +``` + +| **参数** | **描述** | +|--------|-------------------------------------| +| domain | 协议族类型 | +| type | 协议类型 | +| protocol | 实际使用的运输层协议 | +| **返回** | -- | +| >=0 | 成功,返回一个代表套接字描述符的整数 | +| -1 | 失败 | + +该函数用于根据指定的地址族、数据类型和协议来分配一个套接字描述符及其所用的资源。 + +**domain / 协议族类型:** + +- AF_INET: IPv4 +- AF_INET6: IPv6 + +**type / 协议类型:** + +- SOCK_STREAM:流套接字 +- SOCK_DGRAM: 数据报套接字 +- SOCK_RAW: 原始套接字 + +### 绑定套接字(bind) + +```c +int bind(int s, const struct sockaddr *name, socklen_t namelen); +``` + +| **参数** | **描述** | +|---------|---------------------------------------------| +| s | 套接字描述符 | +| name | 指向 sockaddr 结构体的指针,代表要绑定的地址 | +| namelen | sockaddr 结构体的长度 | +| **返回** | -- | +| 0 | 成功 | +| -1 | 失败 | + +该函数用于将端口号和 IP 地址绑定带指定套接字上。 + +SAL 组件依赖 netdev 组件,当使用 `bind()` 函数时,可以通过 netdev 网卡名称获取网卡对象中 IP 地址信息,用于将创建的 Socket 套接字绑定到指定的网卡对象。下面示例完成通过传入的网卡名称绑定该网卡 IP 地址并和服务器进行连接的过程: + +```c +#include +#include +#include + +#define SERVER_HOST "192.168.1.123" +#define SERVER_PORT 1234 + +static int bing_test(int argc, char **argv) +{ + struct sockaddr_in client_addr; + struct sockaddr_in server_addr; + struct netdev *netdev = RT_NULL; + int sockfd = -1; + + if (argc != 2) + { + rt_kprintf("bind_test [netdev_name] --bind network interface device by name.\n"); + return -RT_ERROR; + } + + /* 通过名称获取 netdev 网卡对象 */ + netdev = netdev_get_by_name(argv[1]); + if (netdev == RT_NULL) + { + rt_kprintf("get network interface device(%s) failed.\n", argv[1]); + return -RT_ERROR; + } + + if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + rt_kprintf("Socket create failed.\n"); + return -RT_ERROR; + } + + /* 初始化需要绑定的客户端地址 */ + client_addr.sin_family = AF_INET; + client_addr.sin_port = htons(8080); + /* 获取网卡对象中 IP 地址信息 */ + client_addr.sin_addr.s_addr = netdev->ip_addr.addr; + rt_memset(&(client_addr.sin_zero), 0, sizeof(client_addr.sin_zero)); + + if (bind(sockfd, (struct sockaddr *)&client_addr, sizeof(struct sockaddr)) < 0) + { + rt_kprintf("socket bind failed.\n"); + closesocket(sockfd); + return -RT_ERROR; + } + rt_kprintf("socket bind network interface device(%s) success!\n", netdev->name); + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(SERVER_PORT); + server_addr.sin_addr.s_addr = inet_addr(SERVER_HOST); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 连接到服务端 */ + if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0) + { + rt_kprintf("socket connect failed!\n"); + closesocket(sockfd); + return -RT_ERROR; + } + else + { + rt_kprintf("socket connect success!\n"); + } + + /* 关闭连接 */ + closesocket(sockfd); + return RT_EOK; +} + +#ifdef FINSH_USING_MSH +#include +MSH_CMD_EXPORT(bing_test, bind network interface device test); +#endif /* FINSH_USING_MSH */ +``` + +### 监听套接字(listen) + +```c +int listen(int s, int backlog); +``` + +| **参数** | **描述** | +|-------|-------------------------------| +| s | 套接字描述符 | +| backlog | 表示一次能够等待的最大连接数目 | +| **返回** | -- | +| 0 | 成功 | +| -1 | 失败 | + +该函数用于 TCP 服务器监听指定套接字连接。 + +### 接收连接(accept) + +```c +int accept(int s, struct sockaddr *addr, socklen_t *addrlen); +``` + +| **参数** | **描述** | +|-------|-------------------------------| +| s | 套接字描述符 | +| addr | 表示一次能够等待的最大连接数目 | +| addrlen | 客户端设备地址结构体的长度 | +| **返回** | -- | +| 0 | 成功,返回新创建的套接字描述符 | +| -1 | 失败 | + +当应用程序监听来自其他主机的连接时,使用 `accept()` 函数初始化连接,`accept()` 为每个连接创立新的套接字并从监听队列中移除这个连接。 + +### 建立连接(connect) + +```c +int connect(int s, const struct sockaddr *name, socklen_t namelen); +``` + +| **参数** | **描述** | +|-------|-------------------------------| +| s | 套接字描述符 | +| name | 服务器地址信息 | +| namelen | 服务器地址结构体的长度 | +| **返回** | -- | +| 0 | 成功,返回新创建的套接字描述符 | +| -1 | 失败 | + +该函数用于建立与指定 socket 的连接。 + +### TCP 数据发送(send) + +```c +int send(int s, const void *dataptr, size_t size, int flags); +``` + +| **参数** | **描述** | +|-------|---------------------------| +| s | 套接字描述符 | +| dataptr | 发送的数据指针 | +| size | 发送的数据长度 | +| flags | 标志,一般为 0 | +| **返回** | -- | +| >0 | 成功,返回发送的数据的长度 | +| <=0 | 失败 | + +该函数常用于 TCP 连接发送数据。 + +### TCP 数据接收(recv) + +```c +int recv(int s, void *mem, size_t len, int flags); +``` + +| **参数** | **描述** | +|-----|---------------------------| +| s | 套接字描述符 | +| mem | 接收的数据指针 | +| len | 接收的数据长度 | +| flags | 标志,一般为 0 | +| **返回** | -- | +| >0 | 成功,返回接收的数据的长度 | +| =0 | 目标地址已传输完并关闭连接 | +| <0 | 失败 | + +该函数用于 TCP 连接接收数据。 + +### UDP 数据发送(sendto) + +```c +int sendto(int s, const void *dataptr, size_t size, int flags, const struct sockaddr *to, socklen_t tolen); +``` + +| **参数** | **描述** | +|-------|---------------------------| +| s | 套接字描述符 | +| dataptr | 发送的数据指针 | +| size | 发送的数据长度 | +| flags | 标志,一般为 0 | +| to | 目标地址结构体指针 | +| tolen | 目标地址结构体长度 | +| **返回** | -- | +| >0 | 成功,返回发送的数据的长度 | +| <=0 | 失败 | + +该函数用于 UDP 连接发送数据。 + +### UDP 数据接收(recvfrom) + +```c +int recvfrom(int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); +``` + +| **参数** | **描述** | +|-------|---------------------------| +| s | 套接字描述符 | +| mem | 接收的数据指针 | +| len | 接收的数据长度 | +| flags | 标志,一般为 0 | +| from | 接收地址结构体指针 | +| fromlen | 接收地址结构体长度 | +| **返回** | -- | +| >0 | 成功,返回接收的数据的长度 | +| =0 | 接收地址已传输完并关闭连接 | +| <0 | 失败 | + +该函数用于 UDP 连接接收数据。 + +### 关闭套接字(closesocket) + +```c +int closesocket(int s); +``` + +| **参数** | **描述** | +|----|-------------| +| s | 套接字描述符 | +| **返回** | -- | +| 0 | 成功 | +| -1 | 失败 | + +该函数用于关闭连接,释放资源。 + +### 按设置关闭套接字(shutdown) + +```c +int shutdown(int s, int how); +``` + +| **参数** | **描述** | +|----|-----------------| +| s | 套接字描述符 | +| how | 套接字控制的方式 | +| **返回** | -- | +| 0 | 成功 | +| -1 | 失败 | + +该函数提供更多的权限控制套接字的关闭过程。 + +**how / 套接字控制的方式:** + +- 0: 停止接收当前数据,并拒绝以后的数据接收; +- 1: 停止发送数据,并丢弃未发送的数据; +- 2: 停止接收和发送数据。 + +### 设置套接字选项(setsockopt) + +```c +int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen); +``` + +| **参数** | **描述** | +|-------|-----------------------| +| s | 套接字描述符 | +| level | 协议栈配置选项 | +| optname | 需要设置的选项名 | +| optval | 设置选项值的缓冲区地址 | +| optlen | 设置选项值的缓冲区长度 | +| **返回** | -- | +| =0 | 成功 | +| <0 | 失败 | + +该函数用于设置套接字模式,修改套接字配置选项。 + +**level / 协议栈配置选项:** + +- SOL_SOCKET:套接字层 +- IPPROTO_TCP:TCP 层 +- IPPROTO_IP:IP 层 + +**optname / 需要设置的选项名 :** + +- SO_KEEPALIVE:设置保持连接选项 +- SO_RCVTIMEO:设置套接字数据接收超时 +- SO_SNDTIMEO:设置套接数据发送超时 + +### 获取套接字选项(getsockopt) + +```c +int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen); +``` + +| **参数** | **描述** | +|-------|---------------------------| +| s | 套接字描述符 | +| level | 协议栈配置选项 | +| optname | 需要设置的选项名 | +| optval | 获取选项值的缓冲区地址 | +| optlen | 获取选项值的缓冲区长度地址 | +| **返回** | -- | +| =0 | 成功 | +| <0 | 失败 | + +该函数用于获取套接字配置选项。 + +### 获取远端地址信息(getpeername) + +```c +int getpeername(int s, struct sockaddr *name, socklen_t *namelen); +``` + +| **参数** | **描述** | +|-------|-------------------------| +| s | 套接字描述符 | +| name | 接收信息的地址结构体指针 | +| namelen | 接收信息的地址结构体长度 | +| **返回** | -- | +| =0 | 成功 | +| <0 | 失败 | + +该函数用于获取与套接字相连的远端地址信息。 + +### 获取本地地址信息(getsockname) + +```c +int getsockname(int s, struct sockaddr *name, socklen_t *namelen); +``` + +| **参数** | **描述** | +|-------|-------------------------| +| s | 套接字描述符 | +| name | 接收信息的地址结构体指针 | +| namelen | 接收信息的地址结构体长度 | +| **返回** | -- | +| =0 | 成功 | +| <0 | 失败 | + +该函数用于获取本地套接字地址信息。 + +### 配置套接字参数(ioctlsocket) + +```c +int ioctlsocket(int s, long cmd, void *arg); +``` + +| **参数** | **描述** | +|-----|-----------------| +| s | 套接字描述符 | +| cmd | 套接字操作命令 | +| arg | 操作命令所带参数 | +| **返回** | -- | +| =0 | 成功 | +| <0 | 失败 | + +该函数用于设置套接字控制模式。 + +**cmd 支持下列命令** + +- FIONBIO:开启或关闭套接字的非阻塞模式,arg 参数 1 为开启非阻塞,0 + 为关闭非阻塞。 + +## 网络协议栈接入方式 + +网络协议栈或网络功能实现的接入,主要是对协议簇结构体的初始化和注册处理, 并且添加到 SAL 组件中协议簇列表中,协议簇结构体定义如下: + +```c +/* network interface socket opreations */ +struct sal_socket_ops +{ + int (*socket) (int domain, int type, int protocol); + int (*closesocket)(int s); + int (*bind) (int s, const struct sockaddr *name, socklen_t namelen); + int (*listen) (int s, int backlog); + int (*connect) (int s, const struct sockaddr *name, socklen_t namelen); + int (*accept) (int s, struct sockaddr *addr, socklen_t *addrlen); + int (*sendto) (int s, const void *data, size_t size, int flags, const struct sockaddr *to, socklen_t tolen); + int (*recvfrom) (int s, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); + int (*getsockopt) (int s, int level, int optname, void *optval, socklen_t *optlen); + int (*setsockopt) (int s, int level, int optname, const void *optval, socklen_t optlen); + int (*shutdown) (int s, int how); + int (*getpeername)(int s, struct sockaddr *name, socklen_t *namelen); + int (*getsockname)(int s, struct sockaddr *name, socklen_t *namelen); + int (*ioctlsocket)(int s, long cmd, void *arg); +#ifdef SAL_USING_POSIX + int (*poll) (struct dfs_fd *file, struct rt_pollreq *req); +#endif +}; + +/* sal network database name resolving */ +struct sal_netdb_ops +{ + struct hostent* (*gethostbyname) (const char *name); + int (*gethostbyname_r)(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop); + int (*getaddrinfo) (const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res); + void (*freeaddrinfo) (struct addrinfo *ai); +}; + +/* 协议簇结构体定义 */ +struct sal_proto_family +{ + int family; /* primary protocol families type */ + int sec_family; /* secondary protocol families type */ + const struct sal_socket_ops *skt_ops; /* socket opreations */ + const struct sal_netdb_ops *netdb_ops; /* network database opreations */ +}; + +``` + +- `family`: 每个协议栈支持的主协议簇类型,例如 lwIP 的为 AF_INET ,AT Socket 为 AF_AT, WIZnet 为 AF_WIZ。 +- `sec_family`:每个协议栈支持的次协议簇类型,用于支持单个协议栈或网络实现时,匹配软件包中其他类型的协议簇类型。 +- `skt_ops`: 定义 socket 相关执行函数,如connect、send、recv 等,每种协议簇都有一组不同的实现方式。 +- `netdb_ops`:定义非 socket 相关执行函数,如 gethostbyname、getaddrinfo、 freeaddrinfo等,每种协议簇都有一组不同的实现方式。 + +以下为 AT Socket 网络实现的接入注册流程,开发者可参考实现其他的协议栈或网络实现的接入: + +```c +#include +#include +#include /* SAL 组件结构体存放头文件 */ +#include /* AT Socket 相关头文件 */ +#include + +#include /* 网卡功能相关头文件 */ + +#ifdef SAL_USING_POSIX +#include /* poll 函数实现相关头文件 */ +#endif + +#ifdef SAL_USING_AT + +/* 自定义的 poll 执行函数,用于 poll 中处理接收的事件 */ +static int at_poll(struct dfs_fd *file, struct rt_pollreq *req) +{ + int mask = 0; + struct at_socket *sock; + struct socket *sal_sock; + + sal_sock = sal_get_socket((int) file->data); + if(!sal_sock) + { + return -1; + } + + sock = at_get_socket((int)sal_sock->user_data); + if (sock != NULL) + { + rt_base_t level; + + rt_poll_add(&sock->wait_head, req); + + level = rt_hw_interrupt_disable(); + if (sock->rcvevent) + { + mask |= POLLIN; + } + if (sock->sendevent) + { + mask |= POLLOUT; + } + if (sock->errevent) + { + mask |= POLLERR; + } + rt_hw_interrupt_enable(level); + } + + return mask; +} +#endif + +/* 定义和赋值 Socket 执行函数,SAL 组件执行相关函数时调用该注册的底层函数 */ +static const struct proto_ops at_inet_stream_ops = +{ + at_socket, + at_closesocket, + at_bind, + NULL, + at_connect, + NULL, + at_sendto, + at_recvfrom, + at_getsockopt, + at_setsockopt, + at_shutdown, + NULL, + NULL, + NULL, + +#ifdef SAL_USING_POSIX + at_poll, +#else + NULL, +#endif /* SAL_USING_POSIX */ +}; + +static const struct sal_netdb_ops at_netdb_ops = +{ + at_gethostbyname, + NULL, + at_getaddrinfo, + at_freeaddrinfo, +}; + +/* 定义和赋值 AT Socket 协议簇结构体 */ +static const struct sal_proto_family at_inet_family = +{ + AF_AT, + AF_INET, + &at_socket_ops, + &at_netdb_ops, +}; + +/* 用于设置网卡设备中协议簇相关信息 */ +int sal_at_netdev_set_pf_info(struct netdev *netdev) +{ + RT_ASSERT(netdev); + + netdev->sal_user_data = (void *) &at_inet_family; + return 0; +} + +#endif /* SAL_USING_AT */ +``` diff --git a/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04Object_container.png b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04Object_container.png new file mode 100644 index 0000000000000000000000000000000000000000..904221de12e93fa0346f0d4b3499c341f23b4b10 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04Object_container.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04Task_switching.png b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04Task_switching.png new file mode 100644 index 0000000000000000000000000000000000000000..b520db2cb8f049e3486c2547f33555fb93a0c11e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04Task_switching.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04main_thread.png b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04main_thread.png new file mode 100644 index 0000000000000000000000000000000000000000..7e86b22aa676c0ff2ba3bf8498cb6034eb26d878 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04main_thread.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..dd552117c1fd516249a1597dc4b03cf85b5c570c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_sta.png b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_sta.png new file mode 100644 index 0000000000000000000000000000000000000000..fe2cc8141fd5c220db0fca600c75bd9b8ea66a92 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_sta.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_stack.png b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_stack.png new file mode 100644 index 0000000000000000000000000000000000000000..74ad90b59b9197e0420671e13470aab70b512934 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04thread_stack.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04time_slience.png b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04time_slience.png new file mode 100644 index 0000000000000000000000000000000000000000..67974876bc850f4a37e283a80825662aefc42c96 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/thread/figures/04time_slience.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/thread/thread.md b/rt-thread-version/rt-thread-standard/programming-manual/thread/thread.md new file mode 100644 index 0000000000000000000000000000000000000000..7fc4a549fe88f642f4013d6b3414be1f9bd88dea --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/thread/thread.md @@ -0,0 +1,770 @@ +# 线程管理 + +在日常生活中,我们要完成一个大任务,一般会将它分解成多个简单、容易解决的小问题,小问题逐个被解决,大问题也就随之解决了。在多线程操作系统中,也同样需要开发人员把一个复杂的应用分解成多个小的、可调度的、序列化的程序单元,当合理地划分任务并正确地执行时,这种设计能够让系统满足实时系统的性能及时间的要求,例如让嵌入式系统执行这样的任务,系统通过传感器采集数据,并通过显示屏将数据显示出来,在多线程实时系统中,可以将这个任务分解成两个子任务,如下图所示,一个子任务不间断地读取传感器数据,并将数据写到共享内存中,另外一个子任务周期性的从共享内存中读取数据,并将传感器数据输出到显示屏上。 + +![传感器数据接收任务与显示任务的切换执行](figures/04Task_switching.png) + +在 RT-Thread 中,与上述子任务对应的程序实体就是线程,线程是实现任务的载体,它是 RT-Thread 中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级,重要的任务可设置相对较高的优先级,非重要的任务可以设置较低的优先级,不同的任务还可以设置相同的优先级,轮流运行。 + +当线程运行时,它会认为自己是以独占 CPU 的方式在运行,线程执行时的运行环境称为上下文,具体来说就是各个变量和数据,包括所有的寄存器变量、堆栈、内存信息等。 + +本章将分成 5 节内容对 RT-Thread 线程管理进行介绍,读完本章,读者会对 RT-Thread 的线程管理机制有比较深入的了解,如:线程有哪些状态、如何创建一个线程、为什么会存在空闲线程等问题,心中也会有一个明确的答案了。 + +## 线程管理的功能特点 + +RT-Thread 线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由 RT-Thread 内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除,如图 4-2 所示,每个线程都有重要的属性,如线程控制块、线程栈、入口函数等。 + +![对象容器与线程对象](figures/04Object_container.png) + +RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权。 + +当一个运行着的线程使一个比它优先级高的线程满足运行条件,当前线程的 CPU 使用权就被剥夺了,或者说被让出了,高优先级的线程立刻得到了 CPU 的使用权。 + +如果是中断服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行。 + +当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。 + +## 线程的工作机制 + +### 线程控制块 + +在 RT-Thread 中,线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等,详细定义如下: + +```c +/* 线程控制块 */ +struct rt_thread +{ + /* rt 对象 */ + char name[RT_NAME_MAX]; /* 线程名称 */ + rt_uint8_t type; /* 对象类型 */ + rt_uint8_t flags; /* 标志位 */ + + rt_list_t list; /* 对象列表 */ + rt_list_t tlist; /* 线程列表 */ + + /* 栈指针与入口指针 */ + void *sp; /* 栈指针 */ + void *entry; /* 入口函数指针 */ + void *parameter; /* 参数 */ + void *stack_addr; /* 栈地址指针 */ + rt_uint32_t stack_size; /* 栈大小 */ + + /* 错误代码 */ + rt_err_t error; /* 线程错误代码 */ + rt_uint8_t stat; /* 线程状态 */ + + /* 优先级 */ + rt_uint8_t current_priority; /* 当前优先级 */ + rt_uint8_t init_priority; /* 初始优先级 */ + rt_uint32_t number_mask; + + ...... + + rt_ubase_t init_tick; /* 线程初始化计数值 */ + rt_ubase_t remaining_tick; /* 线程剩余计数值 */ + + struct rt_timer thread_timer; /* 内置线程定时器 */ + + void (*cleanup)(struct rt_thread *tid); /* 线程退出清除函数 */ + rt_uint32_t user_data; /* 用户数据 */ +}; +``` + +其中 init_priority 是线程创建时指定的线程优先级,在线程运行过程当中是不会被改变的(除非用户执行线程控制函数进行手动调整线程优先级)。cleanup 会在线程退出时,被空闲线程回调一次以执行用户设置的清理现场等工作。最后的一个成员 user_data 可由用户挂接一些数据信息到线程控制块中,以提供类似线程私有数据的实现。 + +### 线程重要属性 + +#### 线程栈 + +RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。 + +线程栈还用来存放函数中的局部变量:函数中的局部变量从线程栈空间中申请;函数中局部变量初始时从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。 + +对于线程第一次运行,可以以手工的方式构造这个上下文来设置一些初始的环境:入口函数(PC 寄存器)、入口参数(R0 寄存器)、返回位置(LR 寄存器)、当前机器运行状态(CPSR 寄存器)。 + +线程栈的增长方向是芯片构架密切相关的,RT-Thread 3.1.0 以前的版本,均只支持栈由高地址向低地址增长的方式,对于 ARM Cortex-M 架构,线程栈可构造如下图所示。 + +![线程栈 (ARM)](figures/04thread_stack.png) + +线程栈大小可以这样设定,对于资源相对较大的 MCU,可以适当设计较大的线程栈;也可以在初始时设置较大的栈,例如指定大小为 1K 或 2K 字节,然后在 FinSH 中用 list_thread 命令查看线程运行的过程中线程所使用的栈的大小,通过此命令,能够看到从线程启动运行时,到当前时刻点,线程使用的最大栈深度,而后加上适当的余量形成最终的线程栈大小,最后对栈空间大小加以修改。 + +#### 线程状态 + +线程运行的过程中,同一时间内只允许一个线程在处理器中运行,从运行的过程上划分,线程有多种不同的运行状态,如初始状态、挂起状态、就绪状态等。在 RT-Thread 中,线程包含五种状态,操作系统会自动根据它运行的情况来动态调整它的状态。 +RT-Thread 中线程的五种状态,如下表所示: + +|**状态**|**描述** | +|--------|---------------------------------------------------------------------------------| +| 初始状态 | 当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT | +| 就绪状态 | 在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY | +| 运行状态 | 线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING | +| 挂起状态 | 也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND | +| 关闭状态 | 当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE | + +#### 线程优先级 + +RT-Thread 线程的优先级是表示线程被调度的优先程度。每个线程都具有优先级,线程越重要,赋予的优先级就应越高,- 线程被调度的可能才会越大。 + +RT-Thread 最大支持 256 个线程优先级 (0\~255),数值越小的优先级越高,0 为最高优先级。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置;对于 ARM Cortex-M 系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行。 + +#### 时间片 + +每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick),详见第五章。假设有 2 个优先级相同的就绪态线程 A 与 B,A 线程的时间片设置为 10,B 线程的时间片设置为 5,那么当系统中不存在比 A 优先级高的就绪态线程时,系统会在 A、B 线程间来回切换执行,并且每次对 A 线程执行 10 个节拍的时长,对 B 线程执行 5 个节拍的时长,如下图。 + +![相同优先级时间片轮转](figures/04time_slience.png) + +#### 线程的入口函数 + +线程控制块中的 entry 是线程的入口函数,它是线程实现预期功能的函数。线程的入口函数由用户设计实现,一般有以下两种代码形式: + +-**无限循环模式:** + +在实时系统中,线程通常是被动式的:这个是由实时系统的特性所决定的,实时系统通常总是等待外界事件的发生,而后进行相应的服务: + +```c +void thread_entry(void* paramenter) +{ + while (1) + { + /* 等待事件的发生 */ + + /* 对事件进行服务、进行处理 */ + } +} +``` + +线程看似没有什么限制程序执行的因素,似乎所有的操作都可以执行。但是作为一个实时系统,一个优先级明确的实时系统,如果一个线程中的程序陷入了死循环操作,那么比它优先级低的线程都将不能够得到执行。所以在实时操作系统中必须注意的一点就是:线程中不能陷入死循环操作,必须要有让出 CPU 使用权的动作,如循环中调用延时函数或者主动挂起。用户设计这种无线循环的线程的目的,就是为了让这个线程一直被系统循环调度运行,永不删除。 + +-**顺序执行或有限次循环模式:** + +如简单的顺序语句、do whlie() 或 for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。 + +```c +static void thread_entry(void* parameter) +{ + /* 处理事务 #1 */ + … + /* 处理事务 #2 */ + … + /* 处理事务 #3 */ +} +``` + +#### 线程错误码 + +一个线程就是一个执行场景,错误码是与执行环境密切相关的,所以每个线程配备了一个变量用于保存错误码,线程的错误码有以下几种: + +```c +#define RT_EOK 0 /* 无错误 */ +#define RT_ERROR 1 /* 普通错误 */ +#define RT_ETIMEOUT 2 /* 超时错误 */ +#define RT_EFULL 3 /* 资源已满 */ +#define RT_EEMPTY 4 /* 无资源 */ +#define RT_ENOMEM 5 /* 无内存 */ +#define RT_ENOSYS 6 /* 系统不支持 */ +#define RT_EBUSY 7 /* 系统忙 */ +#define RT_EIO 8 /* IO 错误 */ +#define RT_EINTR 9 /* 中断系统调用 */ +#define RT_EINVAL 10 /* 非法参数 */ +``` + +### 线程状态切换 + +RT-Thread 提供一系列的操作系统调用接口,使得线程的状态在这五个状态之间来回切换。几种状态间的转换关系如下图所示: + +![线程状态转换图](figures/04thread_sta.png) + +线程通过调用函数 rt_thread_create/init() 进入到初始状态(RT_THREAD_INIT);初始状态的线程通过调用函数 rt_thread_startup() 进入到就绪状态(RT_THREAD_READY);就绪状态的线程被调度器调度后进入运行状态(RT_THREAD_RUNNING);当处于运行状态的线程调用 rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv() 等函数或者获取不到资源时,将进入到挂起状态(RT_THREAD_SUSPEND);处于挂起状态的线程,如果等待超时依然未能获得资源或由于其他线程释放了资源,那么它将返回到就绪状态。挂起状态的线程,如果调用 rt_thread_delete/detach() 函数,将更改为关闭状态(RT_THREAD_CLOSE);而运行状态的线程,如果运行结束,就会在线程的最后部分执行 rt_thread_exit() 函数,将状态更改为关闭状态。 + +> [!NOTE] +> 注:RT-Thread 中,实际上线程并不存在运行状态,就绪状态和运行状态是等同的。 + +### 系统线程 + +前文中已提到,系统线程是指由系统创建的线程,用户线程是由用户程序调用线程管理接口创建的线程,在 RT-Thread 内核中的系统线程有空闲线程和主线程。 + +#### 空闲线程 + +空闲线程是系统创建的最低优先级的线程,线程状态永远为就绪态。当系统中无其他就绪线程存在时,调度器将调度到空闲线程,它通常是一个死循环,且永远不能被挂起。另外,空闲线程在 RT-Thread 也有着它的特殊用途: + +若某线程运行完毕,系统将自动删除线程:自动执行 rt_thread_exit() 函数,先将该线程从系统就绪队列中删除,再将该线程的状态更改为关闭状态,不再参与系统调度,然后挂入 rt_thread_defunct 僵尸队列(资源未回收、处于关闭状态的线程队列)中,最后空闲线程会回收被删除线程的资源。 + +空闲线程也提供了接口来运行用户设置的钩子函数,在空闲线程运行时会调用该钩子函数,适合钩入功耗管理、看门狗喂狗等工作。 + +#### 主线程 + +在系统启动时,系统会创建 main 线程,它的入口函数为 main_thread_entry(),用户的应用入口函数 main() 就是从这里真正开始的,系统调度器启动后,main 线程就开始运行,过程如下图,用户可以在 main() 函数里添加自己的应用程序初始化代码。 + +![主线程调用过程](figures/04main_thread.png) + +## 线程的管理方式 + +本章前面 2 节对线程的功能与工作机制进行了概念上的讲解,相信大家对线程已经不再陌生。本节将深入到 RT-Thread 线程的各个接口,并给出部分源码,帮助读者在代码层次上理解线程。 + +下图描述了线程的相关操作,包含:创建 / 初始化线程、启动线程、运行线程、删除 / 脱离线程。可以使用 rt_thread_create() 创建一个动态线程,使用 rt_thread_init() 初始化一个静态线程,动态线程与静态线程的区别是:动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄。 + +![线程相关操作](figures/04thread_ops.png) + +### 创建和删除线程 + +一个线程要成为可执行的对象,就必须由操作系统的内核来为它创建一个线程。可以通过如下的接口创建一个动态线程: + +```c +rt_thread_t rt_thread_create(const char* name, + void (*entry)(void* parameter), + void* parameter, + rt_uint32_t stack_size, + rt_uint8_t priority, + rt_uint32_t tick); +``` + +调用这个函数时,系统会从动态堆内存中分配一个线程句柄以及按照参数中指定的栈大小从动态堆内存中分配相应的空间。分配出来的栈空间是按照 rtconfig.h 中配置的 RT_ALIGN_SIZE 方式对齐。线程创建 rt_thread_create() 的参数和返回值见下表: + +|**参数** |**描述** | +|------------|----------------------------------------------------------------------------------------| +| name | 线程的名称;线程名称的最大长度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,多余部分会被自动截掉 | +| entry | 线程入口函数 | +| parameter | 线程入口函数参数 | +| stack_size | 线程栈大小,单位是字节 | +| priority | 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0\~255,数值越小优先级越高,0 代表最高优先级 | +| tick | 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 | +|**返回** | —— | +| thread | 线程创建成功,返回线程句柄 | +| RT_NULL | 线程创建失败 | + +对于一些使用 rt_thread_create() 创建出来的线程,当不需要使用,或者运行出错时,我们可以使用下面的函数接口来从系统中把线程完全删除掉: + +```c +rt_err_t rt_thread_delete(rt_thread_t thread); +``` + +调用该函数后,线程对象将会被移出线程队列并且从内核对象管理器中删除,线程占用的堆栈空间也会被释放,收回的空间将重新用于其他的内存分配。实际上,用 rt_thread_delete() 函数删除线程接口,仅仅是把相应的线程状态更改为 RT_THREAD_CLOSE 状态,然后放入到 rt_thread_defunct 队列中;而真正的删除动作(释放线程控制块和释放线程栈)需要到下一次执行空闲线程时,由空闲线程完成最后的线程删除动作。线程删除 rt_thread_delete() 接口的参数和返回值见下表: + +|**参数** |**描述** | +|------------|------------------| +| thread | 要删除的线程句柄 | +|**返回** | —— | +| RT_EOK | 删除线程成功 | +| \-RT_ERROR | 删除线程失败 | + +这个函数仅在使能了系统动态堆时才有效(即 RT_USING_HEAP 宏定义已经定义了)。 + +### 初始化和脱离线程 + +线程的初始化可以使用下面的函数接口完成,来初始化静态线程对象: + +```c +rt_err_t rt_thread_init(struct rt_thread* thread, + const char* name, + void (*entry)(void* parameter), void* parameter, + void* stack_start, rt_uint32_t stack_size, + rt_uint8_t priority, rt_uint32_t tick); +``` + +静态线程的线程句柄(或者说线程控制块指针)、线程栈由用户提供。静态线程是指线程控制块、线程运行栈一般都设置为全局变量,在编译时就被确定、被分配处理,内核不负责动态分配内存空间。需要注意的是,用户提供的栈首地址需做系统对齐(例如 ARM 上需要做 4 字节对齐)。线程初始化接口 rt_thread_init() 的参数和返回值见下表: + +|**参数** |**描述** | +|-----------------|---------------------------------------------------------------------------| +| thread | 线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址 | +| name | 线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉 | +| entry | 线程入口函数 | +| parameter | 线程入口函数参数 | +| stack_start | 线程栈起始地址 | +| stack_size | 线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐) | +| priority | 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0 ~ 255,数值越小优先级越高,0 代表最高优先级 | +| tick | 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 | +|**返回** | —— | +| RT_EOK | 线程创建成功 | +| \-RT_ERROR | 线程创建失败 | + +对于用 rt_thread_init() 初始化的线程,使用 rt_thread_detach() 将使线程对象在线程队列和内核对象管理器中被脱离。线程脱离函数如下: + +```c +rt_err_t rt_thread_detach (rt_thread_t thread); +``` + +线程脱离接口 rt_thread_detach() 的参数和返回值见下表: + +|**参数** |**描述** | +|------------|------------------------------------------------------------| +| thread | 线程句柄,它应该是由 rt_thread_init 进行初始化的线程句柄。 | +|**返回** | —— | +| RT_EOK | 线程脱离成功 | +| \-RT_ERROR | 线程脱离失败 | + +这个函数接口是和 rt_thread_delete() 函数相对应的, rt_thread_delete() 函数操作的对象是 rt_thread_create() 创建的句柄,而 rt_thread_detach() 函数操作的对象是使用 rt_thread_init() 函数初始化的线程控制块。同样,线程本身不应调用这个接口脱离线程本身。 + +### 启动线程 + +创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,我们可以在线程初始化 / 创建成功后调用下面的函数接口让该线程进入就绪态: + +```c +rt_err_t rt_thread_startup(rt_thread_t thread); +``` + +当调用这个函数时,将把线程的状态更改为就绪状态,并放到相应优先级队列中等待调度。如果新启动的线程优先级比当前线程优先级高,将立刻切换到这个线程。线程启动接口 rt_thread_startup() 的参数和返回值见下表: + +|**参数** |**描述** | +|------------|--------------| +| thread | 线程句柄 | +|**返回** | —— | +| RT_EOK | 线程启动成功 | +| \-RT_ERROR | 线程起动失败 | + +### 获得当前线程 + +在程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过下面的函数接口获得当前执行的线程句柄: + +```c +rt_thread_t rt_thread_self(void); +``` + +该接口的返回值见下表: + +|**返回**|**描述** | +|----------|----------------------| +| thread | 当前运行的线程句柄 | +| RT_NULL | 失败,调度器还未启动 | + +### 使线程让出处理器资源 + +当前线程的时间片用完或者该线程主动要求让出处理器资源时,它将不再占有处理器,调度器会选择相同优先级的下一个线程执行。线程调用这个接口后,这个线程仍然在就绪队列中。线程让出处理器使用下面的函数接口: + +```c +rt_err_t rt_thread_yield(void); +``` + +调用该函数后,当前线程首先把自己从它所在的就绪优先级线程队列中删除,然后把自己挂到这个优先级队列链表的尾部,然后激活调度器进行线程上下文切换(如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。 + +rt_thread_yield() 函数和 rt_schedule() 函数比较相像,但在有相同优先级的其他就绪态线程存在时,系统的行为却完全不一样。执行 rt_thread_yield() 函数后,当前线程被换出,相同优先级的下一个就绪线程将被执行。而执行 rt_schedule() 函数后,当前线程并不一定被换出,即使被换出,也不会被放到就绪线程链表的尾部,而是在系统中选取就绪的优先级最高的线程执行(如果系统中没有比当前线程优先级更高的线程存在,那么执行完 rt_schedule() 函数后,系统将继续执行当前线程)。 + +### 使线程睡眠 + +在实际应用中,我们有时需要让运行的当前线程延迟一段时间,在指定的时间到达后重新运行,这就叫做 “线程睡眠”。线程睡眠可使用以下三个函数接口: + +```c +rt_err_t rt_thread_sleep(rt_tick_t tick); +rt_err_t rt_thread_delay(rt_tick_t tick); +rt_err_t rt_thread_mdelay(rt_int32_t ms); +``` + +这三个函数接口的作用相同,调用它们可以使当前线程挂起一段指定的时间,当这个时间过后,线程会被唤醒并再次进入就绪状态。这个函数接受一个参数,该参数指定了线程的休眠时间。线程睡眠接口 rt_thread_sleep/delay/mdelay() 的参数和返回值见下表: + +|**参数**|**描述** | +| -------- | ------------------------------------------------------------ | +| tick/ms | 线程睡眠的时间:
sleep/delay 的传入参数 tick 以 1 个 OS Tick 为单位 ;
mdelay 的传入参数 ms 以 1ms 为单位; | +|**返回**| —— | +| RT_EOK | 操作成功 | + +### 挂起和恢复线程 + +当线程调用 rt_thread_delay() 时,线程将主动挂起;当调用 rt_sem_take(),rt_mb_recv() 等函数时,资源不可使用也将导致线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其他线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。 + +线程挂起使用下面的函数接口: + +```c +rt_err_t rt_thread_suspend (rt_thread_t thread); +``` + +线程挂起接口 rt_thread_suspend() 的参数和返回值见下表: + +|**参数** |**描述** | +|------------|----------------------------------------------| +| thread | 线程句柄 | +|**返回** | —— | +| RT_EOK | 线程挂起成功 | +| \-RT_ERROR | 线程挂起失败,因为该线程的状态并不是就绪状态 | + +> [!NOTE] +> 注:通常不应该使用这个函数来挂起线程本身,如果确实需要采用 rt_thread_suspend() + 函数挂起当前任务,需要在调用 rt_thread_suspend() 函数后立刻调用 rt_schedule() + 函数进行手动的线程上下文切换。用户只需要了解该接口的作用,不推荐使用该接口。 + +恢复线程就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中;如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。线程恢复使用下面的函数接口: + +```c +rt_err_t rt_thread_resume (rt_thread_t thread); +``` + +线程恢复接口 rt_thread_resume() 的参数和返回值见下表: + +|**参数** |**描述** | +|------------|---------------------------------------------------------------| +| thread | 线程句柄 | +|**返回** | —— | +| RT_EOK | 线程恢复成功 | +| \-RT_ERROR | 线程恢复失败,因为该个线程的状态并不是 RT_THREAD_SUSPEND 状态 | + +### 控制线程 + +当需要对线程进行一些其他控制时,例如动态更改线程的优先级,可以调用如下函数接口: + +```c +rt_err_t rt_thread_control(rt_thread_t thread, rt_uint8_t cmd, void* arg); +``` + +线程控制接口 rt_thread_control() 的参数和返回值见下表: + +|**函数参数**|**描述** | +|--------------|--------------| +| thread | 线程句柄 | +| cmd | 指示控制命令 | +| arg | 控制参数 | +|**返回** | —— | +| RT_EOK | 控制执行正确 | +| \-RT_ERROR | 失败 | + +指示控制命令 cmd 当前支持的命令包括: + +•RT_THREAD_CTRL_CHANGE_PRIORITY:动态更改线程的优先级; + +•RT_THREAD_CTRL_STARTUP:开始运行一个线程,等同于 rt_thread_startup() 函数调用; + +•RT_THREAD_CTRL_CLOSE:关闭一个线程,等同于 rt_thread_delete() 函数调用。 + +### 设置和删除空闲钩子 + +空闲钩子函数是空闲线程的钩子函数,如果设置了空闲钩子函数,就可以在系统执行空闲线程时,自动执行空闲钩子函数来做一些其他事情,比如系统指示灯。设置 / 删除空闲钩子的接口如下: + +```c +rt_err_t rt_thread_idle_sethook(void (*hook)(void)); +rt_err_t rt_thread_idle_delhook(void (*hook)(void)); +``` + +设置空闲钩子函数 rt_thread_idle_sethook() 的输入参数和返回值如下表所示: + +|**函数参数**|**描述** | +|--------------|----------------| +| hook | 设置的钩子函数 | +|**返回** | —— | +| RT_EOK | 设置成功 | +| \-RT_EFULL | 设置失败 | + +删除空闲钩子函数 rt_thread_idle_delhook() 的输入参数和返回值如下表所示: + +|**函数参数**|**描述** | +|--------------|----------------| +| hook | 删除的钩子函数 | +|**返回** | —— | +| RT_EOK | 删除成功 | +| \-RT_ENOSYS | 删除失败 | + +> [!NOTE] +> 注:空闲线程是一个线程状态永远为就绪态的线程,因此设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态,例如 rt_thread_delay(),rt_sem_take() 等可能会导致线程挂起的函数都不能使用。 + +### 设置调度器钩子 + +在整个系统的运行时,系统都处于线程运行、中断触发 - 响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事件。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用: + +```c +void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to)); +``` + +设置调度器钩子函数的输入参数如下表所示: + +|**函数参数**|**描述** | +|--------------|----------------------------| +| hook | 表示用户定义的钩子函数指针 | + +钩子函数 hook() 的声明如下: + +```c +void hook(struct rt_thread* from, struct rt_thread* to); +``` + +调度器钩子函数 hook() 的输入参数如下表所示: + +|**函数参数**|**描述** | +|--------------|------------------------------------| +| from | 表示系统所要切换出的线程控制块指针 | +| to | 表示系统所要切换到的线程控制块指针 | + +> [!NOTE] +> 注:请仔细编写你的钩子函数,稍有不慎将很可能导致整个系统运行不正常(在这个钩子函数中,基本上不允许调用系统 API,更不应该导致当前运行的上下文挂起)。 + +## 线程应用示例 + +下面给出在 Keil 模拟器环境下的应用示例。 + +### 创建线程示例 + +这个例子创建一个动态线程初始化一个静态线程,一个线程在运行完毕后自动被系统删除,另一个线程一直打印计数,如下代码: + +```c +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static rt_thread_t tid1 = RT_NULL; + +/* 线程 1 的入口函数 */ +static void thread1_entry(void *parameter) +{ + rt_uint32_t count = 0; + + while (1) + { + /* 线程 1 采用低优先级运行,一直打印计数值 */ + rt_kprintf("thread1 count: %d\n", count ++); + rt_thread_mdelay(500); + } +} + +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; +/* 线程 2 入口 */ +static void thread2_entry(void *param) +{ + rt_uint32_t count = 0; + + /* 线程 2 拥有较高的优先级,以抢占线程 1 而获得执行 */ + for (count = 0; count < 10 ; count++) + { + /* 线程 2 打印计数值 */ + rt_kprintf("thread2 count: %d\n", count); + } + rt_kprintf("thread2 exit\n"); + /* 线程 2 运行结束后也将自动被系统脱离 */ +} + +/* 线程示例 */ +int thread_sample(void) +{ + /* 创建线程 1,名称是 thread1,入口是 thread1_entry*/ + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + + /* 如果获得线程控制块,启动这个线程 */ + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* 初始化线程 2,名称是 thread2,入口是 thread2_entry */ + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(thread_sample, thread sample); +``` + +仿真运行结果如下: + +```c +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >thread_sample +msh >thread2 count: 0 +thread2 count: 1 +thread2 count: 2 +thread2 count: 3 +thread2 count: 4 +thread2 count: 5 +thread2 count: 6 +thread2 count: 7 +thread2 count: 8 +thread2 count: 9 +thread2 exit +thread1 count: 0 +thread1 count: 1 +thread1 count: 2 +thread1 count: 3 +… +``` + +线程 2 计数到一定值会执行完毕,线程 2 被系统自动删除,计数停止。线程 1 一直打印计数。 + +> [!NOTE] +> 注:关于删除线程:大多数线程是循环执行的,无需删除;而能运行完毕的线程,RT-Thread 在线程运行完毕后,自动删除线程,在 rt_thread_exit() 里完成删除动作。用户只需要了解该接口的作用,不推荐使用该接口(可以由其他线程调用此接口或在定时器超时函数中调用此接口删除一个线程,但是这种使用非常少)。 + +### 线程时间片轮转调度示例 + +这个例子创建两个线程,在执行时会一直打印计数,如下代码: + +```c +#include + +#define THREAD_STACK_SIZE 1024 +#define THREAD_PRIORITY 20 +#define THREAD_TIMESLICE 10 + +/* 线程入口 */ +static void thread_entry(void* parameter) +{ + rt_uint32_t value; + rt_uint32_t count = 0; + + value = (rt_uint32_t)parameter; + while (1) + { + if(0 == (count % 5)) + { + rt_kprintf("thread %d is running ,thread %d count = %d\n", value , value , count); + + if(count> 200) + return; + } + count++; + } +} + +int timeslice_sample(void) +{ + rt_thread_t tid = RT_NULL; + /* 创建线程 1 */ + tid = rt_thread_create("thread1", + thread_entry, (void*)1, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid != RT_NULL) + rt_thread_startup(tid); + + + /* 创建线程 2 */ + tid = rt_thread_create("thread2", + thread_entry, (void*)2, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE-5); + if (tid != RT_NULL) + rt_thread_startup(tid); + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(timeslice_sample, timeslice sample); +``` + +仿真运行结果如下: + +```c + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh >timeslice_sample +msh >thread 1 is running ,thread 1 count = 0 +thread 1 is running ,thread 1 count = 5 +thread 1 is running ,thread 1 count = 10 +thread 1 is running ,thread 1 count = 15 +… +thread 1 is running ,thread 1 count = 125 +thread 1 is rthread 2 is running ,thread 2 count = 0 +thread 2 is running ,thread 2 count = 5 +thread 2 is running ,thread 2 count = 10 +thread 2 is running ,thread 2 count = 15 +thread 2 is running ,thread 2 count = 20 +thread 2 is running ,thread 2 count = 25 +thread 2 is running ,thread 2 count = 30 +thread 2 is running ,thread 2 count = 35 +thread 2 is running ,thread 2 count = 40 +thread 2 is running ,thread 2 count = 45 +thread 2 is running ,thread 2 count = 50 +thread 2 is running ,thread 2 count = 55 +thread 2 is running ,thread 2 count = 60 +thread 2 is running ,thread 2 cunning ,thread 2 count = 65 +thread 1 is running ,thread 1 count = 135 +… +thread 2 is running ,thread 2 count = 205 +``` + +由运行的计数结果可以看出,线程 2 的运行时间是线程 1 的一半。 + +### 线程调度器钩子示例 + +在线程进行调度切换时,会执行调度,我们可以设置一个调度器钩子,这样可以在线程切换时,做一些额外的事情,这个例子是在调度器钩子函数中打印线程间的切换信息,如下代码: + +```c +#include + +#define THREAD_STACK_SIZE 1024 +#define THREAD_PRIORITY 20 +#define THREAD_TIMESLICE 10 + +/* 针对每个线程的计数器 */ +volatile rt_uint32_t count[2]; + +/* 线程 1、2 共用一个入口,但入口参数不同 */ +static void thread_entry(void* parameter) +{ + rt_uint32_t value; + + value = (rt_uint32_t)parameter; + while (1) + { + rt_kprintf("thread %d is running\n", value); + rt_thread_mdelay(1000); // 延时一段时间 + } +} + +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; + +static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to) +{ + rt_kprintf("from: %s --> to: %s \n", from->name , to->name); +} + +int scheduler_hook(void) +{ + /* 设置调度器钩子 */ + rt_scheduler_sethook(hook_of_scheduler); + + /* 创建线程 1 */ + tid1 = rt_thread_create("thread1", + thread_entry, (void*)1, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* 创建线程 2 */ + tid2 = rt_thread_create("thread2", + thread_entry, (void*)2, + THREAD_STACK_SIZE, + THREAD_PRIORITY,THREAD_TIMESLICE - 5); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(scheduler_hook, scheduler_hook sample); +``` + +仿真运行结果如下: + +```c + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 27 2018 + 2006 - 2018 Copyright by rt-thread team +msh > scheduler_hook +msh >from: tshell --> to: thread1 +thread 1 is running +from: thread1 --> to: thread2 +thread 2 is running +from: thread2 --> to: tidle +from: tidle --> to: thread1 +thread 1 is running +from: thread1 --> to: tidle +from: tidle --> to: thread2 +thread 2 is running +from: thread2 --> to: tidle +… +``` + +由仿真的结果可以看出,对线程进行切换时,设置的调度器钩子函数是在正常工作的,一直在打印线程切换的信息,包含切换到空闲线程。 diff --git a/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_env.png b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_env.png new file mode 100644 index 0000000000000000000000000000000000000000..2b2dd7a64b868a46027f7acb86747bcbc071d1ad Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_env.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_linked_list.png b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_linked_list.png new file mode 100644 index 0000000000000000000000000000000000000000..16e6eb7fdd8de7c04646562ca5a8948c2fffda80 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_linked_list.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_linked_list2.png b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_linked_list2.png new file mode 100644 index 0000000000000000000000000000000000000000..6f17cd3de0555e44315948b26c5224762be924b9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_linked_list2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_ops.png b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_ops.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb2f7b2b1fdb6deab5e80bc66c8bcd1060c0765 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_ops.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list.png b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list.png new file mode 100644 index 0000000000000000000000000000000000000000..eb0b54bfd3b748636567de6cc8bbc84e37a55b4a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list2.png b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list2.png new file mode 100644 index 0000000000000000000000000000000000000000..b16117c97d6f22a50fd6182f44e2752a98b0bd81 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list2.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list3.png b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list3.png new file mode 100644 index 0000000000000000000000000000000000000000..d5550cc3e51c0686fd38129146051deb659bae3e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/timer/figures/05timer_skip_list3.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/timer/timer.md b/rt-thread-version/rt-thread-standard/programming-manual/timer/timer.md new file mode 100644 index 0000000000000000000000000000000000000000..1bed97fccf80af7999f3de3c5a78861991847ac6 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/timer/timer.md @@ -0,0 +1,526 @@ +# 时钟管理 + +时间是非常重要的概念,和朋友出去游玩需要约定时间,完成任务也需要花费时间,生活离不开时间。操作系统也一样,需要通过时间来规范其任务的执行,操作系统中最小的时间单位是时钟节拍 (OS Tick)。本章主要介绍时钟节拍和基于时钟节拍的定时器,读完本章,我们将了解时钟节拍如何产生,并学会如何使用 RT-Thread 的定时器。 + +## 时钟节拍 + +任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。时钟节拍是特定的周期性中断,这个中断可以看做是系统心跳,中断之间的时间间隔取决于不同的应用,一般是 1ms–100ms,时钟节拍率越快,系统的额外开销就越大,从系统启动开始计数的时钟节拍数称为系统时间。 + +RT-Thread 中,时钟节拍的长度可以根据 RT_TICK_PER_SECOND 的定义来调整,等于 1/RT_TICK_PER_SECOND 秒。 + +### 时钟节拍的实现方式 + +时钟节拍由配置为中断触发模式的硬件定时器产生,当中断到来时,将调用一次:void rt_tick_increase(void),通知操作系统已经过去一个系统时钟;不同硬件定时器中断实现都不同,下面的中断函数以 STM32 定时器作为示例。 + +```c +void SysTick_Handler(void) +{ + /* 进入中断 */ + rt_interrupt_enter(); + …… + rt_tick_increase(); + /* 退出中断 */ + rt_interrupt_leave(); +} +``` + +在中断函数中调用 rt_tick_increase() 对全局变量 rt_tick 进行自加,代码如下所示: + +```c +void rt_tick_increase(void) +{ + struct rt_thread *thread; + + /* 全局变量 rt_tick 自加 */ + ++ rt_tick; + + /* 检查时间片 */ + thread = rt_thread_self(); + + -- thread->remaining_tick; + if (thread->remaining_tick == 0) + { + /* 重新赋初值 */ + thread->remaining_tick = thread->init_tick; + + /* 线程挂起 */ + rt_thread_yield(); + } + + /* 检查定时器 */ + rt_timer_check(); +} +``` + +可以看到全局变量 rt_tick 在每经过一个时钟节拍时,值就会加 1,rt_tick 的值表示了系统从启动开始总共经过的时钟节拍数,即系统时间。此外,每经过一个时钟节拍时,都会检查当前线程的时间片是否用完,以及是否有定时器超时。 + +> [!NOTE] +> 注:中断中的 rt_timer_check() 用于检查系统硬件定时器链表,如果有定时器超时,将调用相应的超时函数。且所有定时器在定时超时后都会从定时器链表中被移除,而周期性定时器会在它再次启动时被加入定时器链表。 + +### 获取时钟节拍 + +由于全局变量 rt_tick 在每经过一个时钟节拍时,值就会加 1,通过调用 rt_tick_get 会返回当前 rt_tick 的值,即可以获取到当前的时钟节拍值。此接口可用于记录系统的运行时间长短,或者测量某任务运行的时间。接口函数如下: + +```c +rt_tick_t rt_tick_get(void); +``` + +下表描述了 rt_tick_get() 函数的返回值: + +|**返回**|**描述** | +|----------|----------------| +| rt_tick | 当前时钟节拍值 | + +## 定时器管理 + +定时器,是指从指定的时刻开始,经过一定的指定时间后触发一个事件,例如定个时间提醒第二天能够按时起床。定时器有硬件定时器和软件定时器之分: + +1)**硬件定时器**是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。 + +2)**软件定时器**是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。 + +RT-Thread 操作系统提供软件实现的定时器,以时钟节拍(OS Tick)的时间长度为单位,即定时数值必须是 OS Tick 的整数倍,例如一个 OS Tick 是 10ms,那么上层软件定时器只能是 10ms,20ms,100ms 等,而不能定时为 15ms。RT-Thread 的定时器也基于系统的节拍,提供了基于节拍整数倍的定时能力。 + +### RT-Thread 定时器介绍 + +RT-Thread 的定时器提供两类定时器机制:第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止,否则将永远持续执行下去。 + +另外,根据超时函数执行时所处的上下文环境,RT-Thread 的定时器可以分为 HARD_TIMER 模式与 SOFT_TIMER 模式,如下图。 + +![定时器上下文环境](figures/05timer_env.png) + +#### HARD_TIMER 模式 + +HARD_TIMER 模式的定时器超时函数在中断上下文环境中执行,可以在初始化 / 创建定时器时使用参数 RT_TIMER_FLAG_HARD_TIMER 来指定。 + +在中断上下文环境中执行时,对于超时函数的要求与中断服务例程的要求相同:执行时间应该尽量短,执行时不应导致当前上下文挂起、等待。例如在中断上下文中执行的超时函数它不应该试图去申请动态内存、释放动态内存等。 + +RT-Thread 定时器默认的方式是 HARD_TIMER 模式,即定时器超时后,超时函数是在系统时钟中断的上下文环境中运行的。在中断上下文中的执行方式决定了定时器的超时函数不应该调用任何会让当前上下文挂起的系统函数;也不能够执行非常长的时间,否则会导致其他中断的响应时间加长或抢占了其他线程执行的时间。 + +#### SOFT_TIMER 模式 + +SOFT_TIMER 模式可配置,通过宏定义 RT_USING_TIMER_SOFT 来决定是否启用该模式。该模式被启用后,系统会在初始化时创建一个 timer 线程,然后 SOFT_TIMER 模式的定时器超时函数在都会在 timer 线程的上下文环境中执行。可以在初始化 / 创建定时器时使用参数 RT_TIMER_FLAG_SOFT_TIMER 来指定设置 SOFT_TIMER 模式。 + +### 定时器工作机制 + +下面以一个例子来说明 RT-Thread 定时器的工作机制。在 RT-Thread 定时器模块中维护着两个重要的全局变量: + +(1)当前系统经过的 tick 时间 rt_tick(当硬件定时器中断来临时,它将加 1); + +(2)定时器链表 rt_timer_list。系统新创建并激活的定时器都会按照以超时时间排序的方式插入到 rt_timer_list 链表中。 + +如下图所示,系统当前 tick 值为 20,在当前系统中已经创建并启动了三个定时器,分别是定时时间为 50 个 tick 的 Timer1、100 个 tick 的 Timer2 和 500 个 tick 的 Timer3,这三个定时器分别加上系统当前时间 rt_tick=20,从小到大排序链接在 rt_timer_list 链表中,形成如图所示的定时器链表结构。 + +![定时器链表示意图](figures/05timer_linked_list.png) + +而 rt_tick 随着硬件定时器的触发一直在增长(每一次硬件定时器中断来临,rt_tick 变量会加 1),50 个 tick 以后,rt_tick 从 20 增长到 70,与 Timer1 的 timeout 值相等,这时会触发与 Timer1 定时器相关联的超时函数,同时将 Timer1 从 rt_timer_list 链表上删除。同理,100 个 tick 和 500 个 tick 过去后,与 Timer2 和 Timer3 定时器相关联的超时函数会被触发,接着将 Time2 和 Timer3 定时器从 rt_timer_list 链表中删除。 + +如果系统当前定时器状态在 10 个 tick 以后(rt_tick=30)有一个任务新创建了一个 tick 值为 300 的 Timer4 定时器,由于 Timer4 定时器的 timeout=rt_tick+300=330, 因此它将被插入到 Timer2 和 Timer3 定时器中间,形成如下图所示链表结构: + +![定时器链表插入示意图](figures/05timer_linked_list2.png) + +#### 定时器控制块 + +在 RT-Thread 操作系统中,定时器控制块由结构体 struct rt_timer 定义并形成定时器内核对象,再链接到内核对象容器中进行管理。它是操作系统用于管理定时器的一个数据结构,会存储定时器的一些信息,例如初始节拍数,超时时的节拍数,也包含定时器与定时器之间连接用的链表结构,超时回调函数等。 + +```c +struct rt_timer +{ + struct rt_object parent; + rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; /* 定时器链表节点 */ + + void (*timeout_func)(void *parameter); /* 定时器超时调用的函数 */ + void *parameter; /* 超时函数的参数 */ + rt_tick_t init_tick; /* 定时器初始超时节拍数 */ + rt_tick_t timeout_tick; /* 定时器实际超时时的节拍数 */ +}; +typedef struct rt_timer *rt_timer_t; +``` + +定时器控制块由 struct rt_timer 结构体定义并形成定时器内核对象,再链接到内核对象容器中进行管理,list 成员则用于把一个激活的(已经启动的)定时器链接到 rt_timer_list 链表中。 + +#### 定时器跳表 (Skip List) 算法 + +在前面介绍定时器的工作方式的时候说过,系统新创建并激活的定时器都会按照以超时时间排序的方式插入到 rt_timer_list 链表中,也就是说 t_timer_list 链表是一个有序链表,RT-Thread 中使用了跳表算法来加快搜索链表元素的速度。 + +跳表是一种基于并联链表的数据结构,实现简单,插入、删除、查找的时间复杂度均为 O(log n)。跳表是链表的一种,但它在链表的基础上增加了 “跳跃” 功能,正是这个功能,使得在查找元素时,跳表能够提供 O(log n)的时间复杂度,举例如下: + +一个有序的链表,如下图所示,从该有序链表中搜索元素 {13, 39},需要比较的次数分别为 {3, 5},总共比较的次数为 3 + 5 = 8 次。 + +![有序链表示意图](figures/05timer_skip_list.png) + +使用跳表算法后可以采用类似二叉搜索树的方法,把一些节点提取出来作为索引,得到如下图所示的结构: + +![有序链表索引示意图](figures/05timer_skip_list2.png) + +在这个结构里把 {3, 18,77} 提取出来作为一级索引,这样搜索的时候就可以减少比较次数了, 例如在搜索 39 时仅比较了 3 次(通过比较 3,18,39)。当然我们还可以再从一级索引提取一些元素出来,作为二级索引,这样更能加快元素搜索。 + +![三层跳表示意图](figures/05timer_skip_list3.png) + +所以,定时器跳表可以通过上层的索引,在搜索的时候就减少比较次数,提升查找的效率,这是一种通过 “空间来换取时间” 的算法,在 RT-Thread 中通过宏定义 RT_TIMER_SKIP_LIST_LEVEL 来配置跳表的层数,默认为 1,表示采用一级有序链表图的有序链表算法,每增加一,表示在原链表基础上增加一级索引。 + +### 定时器的管理方式 + +前面介绍了 RT-Thread 定时器并对定时器的工作机制进行了概念上的讲解,本节将深入到定时器的各个接口,帮助读者在代码层次上理解 RT-Thread 定时器。 + +在系统启动时需要初始化定时器管理系统。可以通过下面的函数接口完成: + +```c +void rt_system_timer_init(void); +``` + +如果需要使用 SOFT_TIMER,则系统初始化时,应该调用下面这个函数接口: + +```c +void rt_system_timer_thread_init(void); +``` + +定时器控制块中含有定时器相关的重要参数,在定时器各种状态间起到纽带的作用。定时器的相关操作如下图所示,对定时器的操作包含:创建 / 初始化定时器、启动定时器、运行定时器、删除 / 脱离定时器,所有定时器在定时超时后都会从定时器链表中被移除,而周期性定时器会在它再次启动时被加入定时器链表,这与定时器参数设置相关。在每次的操作系统时钟中断发生时,都会对已经超时的定时器状态参数做改变。 + +![定时器相关操作](figures/05timer_ops.png) + +#### 创建和删除定时器 + +当动态创建一个定时器时,可使用下面的函数接口: + +```c +rt_timer_t rt_timer_create(const char* name, + void (*timeout)(void* parameter), + void* parameter, + rt_tick_t time, + rt_uint8_t flag); +``` + +调用该函数接口后,内核首先从动态内存堆中分配一个定时器控制块,然后对该控制块进行基本的初始化。其中的各参数和返回值说明详见下表: + + rt_timer_create() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------------------------|--------------------------------------------------------------------------| +| name | 定时器的名称 | +| void (timeout) (void parameter) | 定时器超时函数指针(当定时器超时时,系统会调用这个函数) | +| parameter | 定时器超时函数的入口参数(当定时器超时时,调用超时回调函数会把这个参数做为入口参数传递给超时函数) | +| time | 定时器的超时时间,单位是时钟节拍 | +| flag | 定时器创建时的参数,支持的值包括单次定时、周期定时、硬件定时器、软件定时器等(可以用 “或” 关系取多个值) | +|**返回** | —— | +| RT_NULL | 创建失败(通常会由于系统内存不够用而返回 RT_NULL) | +| 定时器的句柄 | 定时器创建成功 | + +include/rtdef.h 中定义了一些定时器相关的宏,如下: + +```c +#define RT_TIMER_FLAG_ONE_SHOT     0x0     /* 单次定时     */ +#define RT_TIMER_FLAG_PERIODIC     0x2     /* 周期定时     */ + +#define RT_TIMER_FLAG_HARD_TIMER   0x0     /* 硬件定时器   */ +#define RT_TIMER_FLAG_SOFT_TIMER   0x4     /* 软件定时器   */ +``` + +上面 2 组值可以以 “或” 逻辑的方式赋给 flag。当指定的 flag 为 RT_TIMER_FLAG_HARD_TIMER 时,如果定时器超时,定时器的回调函数将在时钟中断的服务例程上下文中被调用;当指定的 flag 为 RT_TIMER_FLAG_SOFT_TIMER 时,如果定时器超时,定时器的回调函数将在系统时钟 timer 线程的上下文中被调用。 + +系统不再使用动态定时器时,可使用下面的函数接口: + +```c +rt_err_t rt_timer_delete(rt_timer_t timer); +``` + +调用这个函数接口后,系统会把这个定时器从 rt_timer_list 链表中删除,然后释放相应的定时器控制块占有的内存,其中的各参数和返回值说明详见下表: + +rt_timer_delete() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|-------------------------------------------------------------------------| +| timer | 定时器句柄,指向要删除的定时器 | +|**返回**| —— | +| RT_EOK | 删除成功(如果参数 timer 句柄是一个 RT_NULL,将会导致一个 ASSERT 断言) | + +#### 初始化和脱离定时器 + +当选择静态创建定时器时,可利用 rt_timer_init 接口来初始化该定时器,函数接口如下: + +```c +void rt_timer_init(rt_timer_t timer, + const char* name, + void (*timeout)(void* parameter), + void* parameter, + rt_tick_t time, rt_uint8_t flag); +``` + +使用该函数接口时会初始化相应的定时器控制块,初始化相应的定时器名称,定时器超时函数等等,其中的各参数和返回值说明详见下表: + +rt_timer_init() 的输入参数和返回值 + +|**参数** |**描述** | +|---------------------------------|-------------------------------------------------------------------------------------| +| timer | 定时器句柄,指向要初始化的定时器控制块 | +| name | 定时器的名称 | +| void (timeout) (void parameter) | 定时器超时函数指针(当定时器超时时,系统会调用这个函数) | +| parameter | 定时器超时函数的入口参数(当定时器超时时,调用超时回调函数会把这个参数做为入口参数传递给超时函数) | +| time | 定时器的超时时间,单位是时钟节拍 | +| flag | 定时器创建时的参数,支持的值包括单次定时、周期定时、硬件定时器、软件定时器(可以用 “或” 关系取多个值),详见创建定时器小节 | + +当一个静态定时器不需要再使用时,可以使用下面的函数接口: + +```c +rt_err_t rt_timer_detach(rt_timer_t timer); +``` + +脱离定时器时,系统会把定时器对象从内核对象容器中脱离,但是定时器对象所占有的内存不会被释放,其中的各参数和返回值说明详见表下表: + + rt_timer_detach() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|--------------------------------------| +| timer | 定时器句柄,指向要脱离的定时器控制块 | +|**返回**| —— | +| RT_EOK | 脱离成功 | + +#### 启动和停止定时器 + +当定时器被创建或者初始化以后,并不会被立即启动,必须在调用启动定时器函数接口后,才开始工作,启动定时器函数接口如下: + +```c +rt_err_t rt_timer_start(rt_timer_t timer); +``` + +调用定时器启动函数接口后,定时器的状态将更改为激活状态(RT_TIMER_FLAG_ACTIVATED),并按照超时顺序插入到 +rt_timer_list 队列链表中,其中的各参数和返回值说明详见下表: + + rt_timer_start() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|--------------------------------------| +| timer | 定时器句柄,指向要启动的定时器控制块 | +|**返回**| —— | +| RT_EOK | 启动成功 | + +启动定时器的例子请参考后面的示例代码。 + +启动定时器以后,若想使它停止,可以使用下面的函数接口: + +```c +rt_err_t rt_timer_stop(rt_timer_t timer); +``` + +调用定时器停止函数接口后,定时器状态将更改为停止状态,并从 rt_timer_list 链表中脱离出来不参与定时器超时检查。当一个(周期性)定时器超时时,也可以调用这个函数接口停止这个(周期性)定时器本身,其中的各参数和返回值说明详见下表: + + rt_timer_stop() 的输入参数和返回值 + +|**参数** |**描述** | +|-------------|--------------------------------------| +| timer | 定时器句柄,指向要停止的定时器控制块 | +|**返回** | —— | +| RT_EOK | 成功停止定时器 | +| \- RT_ERROR | timer 已经处于停止状态 | + +#### 控制定时器 + +除了上述提供的一些编程接口,RT-Thread 也额外提供了定时器控制函数接口,以获取或设置更多定时器的信息。控制定时器函数接口如下: + +```c +rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg); +``` + +控制定时器函数接口可根据命令类型参数,来查看或改变定时器的设置,其中的各参数和返回值说明详见下表: + + rt_timer_control() 的输入参数和返回值 + +|**参数**|**描述** | +|----------|----------------------------------------------------------------------------------------------------------| +| timer | 定时器句柄,指向要停止的定时器控制块 | +| cmd | 用于控制定时器的命令,当前支持四个命令,分别是设置定时时间,查看定时时间,设置单次触发,设置周期触发 | +| arg | 与 cmd 相对应的控制命令参数 比如,cmd 为设定超时时间时,就可以将超时时间参数通过 arg 进行设定 | +|**返回**| —— | +| RT_EOK | 成功 | + +函数参数 cmd 支持的命令: + +```c +#define RT_TIMER_CTRL_SET_TIME     0x0     /* 设置定时器超时时间       */ +#define RT_TIMER_CTRL_GET_TIME     0x1     /* 获得定时器超时时间       */ +#define RT_TIMER_CTRL_SET_ONESHOT   0x2     /* 设置定时器为单次定时器   */ +#define RT_TIMER_CTRL_SET_PERIODIC 0x3     /* 设置定时器为周期型定时器 */ +``` + +使用定时器控制接口的代码请见动态定时器例程。 + +定时器应用示例 +-------------- + +这是一个创建定时器的例子,这个例程会创建两个动态定时器,一个是单次定时,一个是周期性定时并让周期定时器运行一段时间后停止运行,如下所示: + +```c +#include + +/* 定时器的控制块 */ +static rt_timer_t timer1; +static rt_timer_t timer2; +static int cnt = 0; + +/* 定时器 1 超时函数 */ +static void timeout1(void *parameter) +{ + rt_kprintf("periodic timer is timeout %d\n", cnt); + + /* 运行第 10 次,停止周期定时器 */ + if (cnt++>= 9) + { + rt_timer_stop(timer1); + rt_kprintf("periodic timer was stopped! \n"); + } +} + +/* 定时器 2 超时函数 */ +static void timeout2(void *parameter) +{ + rt_kprintf("one shot timer is timeout\n"); +} + +int timer_sample(void) +{ + /* 创建定时器 1 周期定时器 */ + timer1 = rt_timer_create("timer1", timeout1, + RT_NULL, 10, + RT_TIMER_FLAG_PERIODIC); + + /* 启动定时器 1 */ + if (timer1 != RT_NULL) rt_timer_start(timer1); + + /* 创建定时器 2 单次定时器 */ + timer2 = rt_timer_create("timer2", timeout2, + RT_NULL, 30, + RT_TIMER_FLAG_ONE_SHOT); + + /* 启动定时器 2 */ + if (timer2 != RT_NULL) rt_timer_start(timer2); + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(timer_sample, timer sample); +``` + +仿真运行结果如下: + +``` + \ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >timer_sample +msh >periodic timer is timeout 0 +periodic timer is timeout 1 +one shot timer is timeout +periodic timer is timeout 2 +periodic timer is timeout 3 +periodic timer is timeout 4 +periodic timer is timeout 5 +periodic timer is timeout 6 +periodic timer is timeout 7 +periodic timer is timeout 8 +periodic timer is timeout 9 +periodic timer was stopped! +``` + +周期性定时器 1 的超时函数,每 10 个 OS Tick 运行 1 次,共运行 10 次(10 次后调用 rt_timer_stop 使定时器 1 停止运行);单次定时器 2 的超时函数在第 30 个 OS Tick 时运行一次。 + +初始化定时器的例子与创建定时器的例子类似,这个程序会初始化 2 个静态定时器,一个是单次定时,一个是周期性的定时,如下代码所示: + + 初始化静态定时器例程 + +```c +#include + +/* 定时器的控制块 */ +static struct rt_timer timer1; +static struct rt_timer timer2; +static int cnt = 0; + +/* 定时器 1 超时函数 */ +static void timeout1(void* parameter) +{ + rt_kprintf("periodic timer is timeout\n"); + /* 运行 10 次 */ + if (cnt++>= 9) + { + rt_timer_stop(&timer1); + } +} + +/* 定时器 2 超时函数 */ +static void timeout2(void* parameter) +{ + rt_kprintf("one shot timer is timeout\n"); +} + +int timer_static_sample(void) +{ + /* 初始化定时器 */ + rt_timer_init(&timer1, "timer1", /* 定时器名字是 timer1 */ + timeout1, /* 超时时回调的处理函数 */ + RT_NULL, /* 超时函数的入口参数 */ + 10, /* 定时长度,以 OS Tick 为单位,即 10 个 OS Tick */ + RT_TIMER_FLAG_PERIODIC); /* 周期性定时器 */ + rt_timer_init(&timer2, "timer2", /* 定时器名字是 timer2 */ + timeout2, /* 超时时回调的处理函数 */ + RT_NULL, /* 超时函数的入口参数 */ + 30, /* 定时长度为 30 个 OS Tick */ + RT_TIMER_FLAG_ONE_SHOT); /* 单次定时器 */ + + /* 启动定时器 */ + rt_timer_start(&timer1); + rt_timer_start(&timer2); + return 0; +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(timer_static_sample, timer_static sample); +``` + +仿真运行结果如下: + +``` +\ | / +- RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >timer_static_sample +msh >periodic timer is timeout +periodic timer is timeout +one shot timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +periodic timer is timeout +``` + +周期性定时器 1 的超时函数,每 10 个 OS Tick 运行 1 次,共运行 10 次(10 次后调用 rt_timer_stop 使定时器 1 停止运行);单次定时器 2 的超时函数在第 30 个 OS Tick 时运行一次。 + +高精度延时 +---------- + +RT-Thread 定时器的最小精度是由系统时钟节拍所决定的(1 OS Tick = 1/RT_TICK_PER_SECOND 秒,RT_TICK_PER_SECOND 值在 rtconfig.h 文件中定义),定时器设定的时间必须是 OS Tick 的整数倍。当需要实现更短时间长度的系统定时时,例如 OS Tick 是 10ms,而程序需要实现 1ms 的定时或延时,这种时候操作系统定时器将不能够满足要求,只能通过读取系统某个硬件定时器的计数器或直接使用硬件定时器的方式。 + +在 Cortex-M 系列中,SysTick 已经被 RT-Thread 用于作为 OS Tick 使用,它被配置成 1/RT_TICK_PER_SECOND 秒后触发一次中断的方式,中断处理函数使用 Cortex-M3 默认的 SysTick_Handler 名字。在 Cortex-M3 的 CMSIS(Cortex Microcontroller Software Interface Standard)规范中规定了 SystemCoreClock 代表芯片的主频,所以基于 SysTick 以及 SystemCoreClock,我们能够使用 SysTick 获得一个精确的延时函数,如下例所示,Cortex-M3 上的基于 SysTick 的精确延时(需要系统在使能 SysTick 后使用): + +高精度延时的例程如下所示: + +```c +#include +void rt_hw_us_delay(rt_uint32_t us) +{ + rt_uint32_t delta; + /* 获得延时经过的 tick 数 */ + us = us * (SysTick->LOAD/(1000000/RT_TICK_PER_SECOND)); + /* 获得当前时间 */ + delta = SysTick->VAL; + /* 循环获得当前时间,直到达到指定的时间后退出循环 */ + while (delta - SysTick->VAL< us); +} + +``` + +其中入口参数 us 指示出需要延时的微秒数目,这个函数只能支持低于 1 OS Tick 的延时,否则 SysTick 会出现溢出而不能够获得指定的延时时间。 diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_async_vs_sync.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_async_vs_sync.png new file mode 100644 index 0000000000000000000000000000000000000000..b8e557dcb91caaa634e1f221243c2e89ec9edb6a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_async_vs_sync.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example.png new file mode 100644 index 0000000000000000000000000000000000000000..7d4d5b945f813135fdb304a7618553da4fa507c3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_all_format.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_all_format.png new file mode 100644 index 0000000000000000000000000000000000000000..70dbb240edddf52d52da84978c81ed55a887e87f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_all_format.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_async.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_async.png new file mode 100644 index 0000000000000000000000000000000000000000..43fd13ecdd860e1ea00782a9b6e4253348188055 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_async.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter20.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter20.png new file mode 100644 index 0000000000000000000000000000000000000000..a16f2e93a93a227e03e38fbdb65a9bb7358ea71d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter20.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter30.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter30.png new file mode 100644 index 0000000000000000000000000000000000000000..3ec6e528f4e01a2ae5ba00c6c1a7a345e1e62d69 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter30.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter40.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter40.png new file mode 100644 index 0000000000000000000000000000000000000000..2a9f8f946dc9c7ac2f205dd67569ee34b065f8be Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_filter40.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_hexdump.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_hexdump.png new file mode 100644 index 0000000000000000000000000000000000000000..1d14f050f36e7f96881c090e11fdb6de0ea611de Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_hexdump.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_syslog.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_syslog.png new file mode 100644 index 0000000000000000000000000000000000000000..996cde3a1784e3ccb3fdbe17d2e9b238d6543c6f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_example_syslog.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_framework.jpg b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_framework.jpg new file mode 100644 index 0000000000000000000000000000000000000000..213c770ab6b8307eee58f9699384f100ba14bc7b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_framework.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_framework_backend.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_framework_backend.png new file mode 100644 index 0000000000000000000000000000000000000000..f132d9df4db7404e35a5ccfaa4c76f6210d3f525 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_framework_backend.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_menuconfig_async.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_menuconfig_async.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5f591829e5410b311e390ad4ec53de5fa356af Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_menuconfig_async.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_menuconfig_format.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_menuconfig_format.png new file mode 100644 index 0000000000000000000000000000000000000000..63bf46019ed3e3d3e85677f02d6194c6e2cdccf3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_menuconfig_format.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_syslog_format.png b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_syslog_format.png new file mode 100644 index 0000000000000000000000000000000000000000..e5cf305add8a60413e3ff6c71f3610c41049502c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/ulog/figures/ulog_syslog_format.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/ulog/ulog.md b/rt-thread-version/rt-thread-standard/programming-manual/ulog/ulog.md new file mode 100644 index 0000000000000000000000000000000000000000..7b302457940813d1b83ac8ead0eddbfa566a9dac --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/ulog/ulog.md @@ -0,0 +1,798 @@ +# ulog 日志 + +## ulog 简介 + +**日志的定义**:日志是将软件运行的状态、过程等信息,输出到不同的介质中(例如:文件、控制台、显示屏等),并进行显示和保存。为软件调试、维护过程中的问题追溯、性能分析、系统监控、故障预警等功能,提供参考依据。可以说,日志的使用,几乎占用的软件生命周期的至少 80% 的时间。 + +**日志的重要性**:对于操作系统而言,由于其软件的复杂度非常大,单步调试在一些场景下并不适合,所以日志组件在操作系统上几乎都是标配。完善的日志系统也能让操作系统的调试事半功倍。 + +**ulog 的起源**: RT-Thread 一直缺少小巧、实用的日志组件,而 ulog 的诞生补全了这块的短板。它将作为 RT-Thread 的基础组件被开源出来,让我们的开发者也能用上简洁易用的日志系统,提高开发效率。 + +ulog 是一个非常简洁、易用的 C/C++ 日志组件,第一个字母 u 代表 μ,即微型的意思。它能做到最低**ROM<1K, RAM<0.2K**的资源占用。ulog 不仅有小巧体积,同样也有非常全面的功能,其设计理念参考的是另外一款 C/C++ 开源日志库:EasyLogger(简称 elog),并在功能和性能等方面做了非常多的改进。主要特性如下: + +* 日志输出的后端多样化,可支持例如:串口、网络,文件、闪存等后端形式。 + +* 日志输出被设计为线程安全的方式,并支持异步输出模式。 + +* 日志系统高可靠,在中断 ISR 、Hardfault 等复杂环境下依旧可用。 + +* 日志支持运行期 / 编译期设置输出级别。 + +* 日志内容支持按关键词及标签方式进行全局过滤。 + +* API 和日志格式可兼容 linux syslog。 + +* 支持以 hex 格式 dump 调试数据到日志中。 + +* 兼容 rtdbg (RTT 早期的日志头文件)及 EasyLogger 的日志输出 API。 + +### ulog 架构 + +下图为 ulog 日志组件架构图: + +![ulog 框架](figures/ulog_framework.jpg) + +* **前端**:该层作为离应用最近的一层,给用户提供了 syslog 及 LOG_X 两类 API 接口,方便用户在不同的场景中使用。 + +* **核心**:中间核心层的主要工作是将上层传递过来的日志,按照不同的配置要求进行格式化与过滤然后生成日志帧,最终通过不同的输出模块,输出到最底层的后端设备上。 + +* **后端**:接收到核心层发来的日志帧后,将日志输出到已经注册的日志后端设备上,例如:文件、控制台、日志服务器等等。 + +### 配置选项 ### + +ENV 工具中使用 menuconfig 配置 ulog 的路径如下所示: + +```c + RT-Thread Components → Utilities → Enable ulog +``` + + ulog 配置选项说明如下所示,一般情况下使用默认配置即可: + +```c +[*] Enable ulog /* 使能 ulog */ + The static output log level./* 选择静态的日志输出级别。选择完成后,比设定级别低的日志(这里特指使用 LOG_X API 的日志)将不会被编译到 ROM 中 */ +[ ] Enable ISR log. /* 使能中断 ISR 日志,即在 ISR 中也可以使用日志输出 API */ +[*] Enable assert check. /* 使能断言检查。关闭后,断言的日志将不会被编译到 ROM 中 */ +(128) The log's max width. /* 日志的最大长度。由于 ulog 的日志 API 按行作为单位,所以这个长度也代表一行日志的最大长度 */ +[ ] Enable async output mode. /* 使能异步日志输出模式。开启这个模式后,日志不会立刻输出到后端,而是先缓存起来,然后交给日志输出线程(例如:idle 线程)去输出 */ + log format ---> /* 配置日志的格式,例如:时间信息,颜色信息,线程信息,是否支持浮点等等 */ +[*] Enable console backend. /* 使能控制台作为后端。使能后日志可以输出到控制台串口上。建议保持开启。 */ +[ ] Enable runtime log filter. /* 使能运行时的日志过滤器,即动态过滤。使能后,日志将支持按标签、关键词等方式,在系统运行时进行动态过滤。 */ +``` + +**配置日志的格式(log format)选项描述如下所示:** + +```c +[ ] Enable float number support. It will using more thread stack. /* 浮点型数字的支持(传统的 rtdbg/rt_kprintf 均不支持浮点数日志) */ + [*] Enable color log. /* 带颜色的日志 */ + [*] Enable time information. /* 时间信息 */ + [ ] Enable timestamp format for time. /* 包括时间戳 */ + [*] Enable level information. /* 级别信息 */ + [*] Enable tag information. /* 标签信息 */ + [ ] Enable thread information. /* 线程信息 */ +``` + +### 日志级别 + +日志级别代表了日志的重要性,在 ulog 中由高到低,有如下几个日志级别: + +| **级别** | **名称** | **描述** | +| ------------ | ---- | ----------------------- | +| LOG_LVL_ASSERT | 断言 | 发生无法处理、致命性的的错误,以至于系统无法继续运行的断言日志 | +| LOG_LVL_ERROR | 错误 | 发生严重的、**不可修复**的错误时输出的日志属于错误级别日志 | +| LOG_LVL_WARNING | 警告 | 出现一些不太重要的、具有**可修复性**的错误时,会输出这些警告日志 | +| LOG_LVL_INFO | 信息 | 给本模块上层使用人员查看的重要提示信息日志,例如:初始化成功,当前工作状态等。该级别日志一般在量产时依旧**保留**| +| LOG_LVL_DBG | 调试 | 给本模块开发人员查看的调试日志,该级别日志一般在量产时**关闭**| + +在 ulog 中日志级别还有如下分类: + +* **静态级别与动态级别**:按照日志是否可以在运行阶段修改进行分类。可在运行阶段修改的称之为动态级别,只能在**编译阶段**修改的称之为静态级别。比静态级别低的日志(这里特指使用 LOG_X API 的日志)将不会被编译到 ROM 中,最终也不会输出、显示出来。而动态级别可以管控的是高于或等于静态级别的日志。在 ulog 运行时,比动态级别低的日志会被过滤掉。 + +* **全局级别与模块级别**:按照作用域进行的分类。在 ulog 中每个文件(模块)也可以设定独立的日志级别。全局级别作用域大于模块级别,也就是模块级别只能管控那些高于或等于全局级别的模块日志。 + +综合上面分类可以看出,在 ulog 可以通过以下 4 个方面来设定日志的输出级别: + +* **全局静态**日志级别:在 menuconfig 中配置,对应 `ULOG_OUTPUT_LVL` 宏。 + +* **全局动态**日志级别:使用 `void ulog_global_filter_lvl_set(rt_uint32_t level)` 函数来设定。 + +* **模块静态**日志级别:在模块(文件)内定义 `LOG_LVL` 宏,与日志标签宏 `LOG_TAG` 定义方式类似。 + +* **模块动态**日志级别:使用 `int ulog_tag_lvl_filter_set(const char *tag, rt_uint32_t level)` 函数来设定。 + +它们的作用范围关系为:**全局静态**>**全局动态**>**模块静态**>**模块动态**。 + +### 日志标签 + +由于日志输出量的不断增大,为了避免日志被杂乱无章的输出出来,就需要使用标签(tag)给每条日志进行分类。标签的定义是按照**模块化**的方式,例如:Wi-Fi 组件包括设备驱动(wifi_driver)、设备管理(wifi_mgnt)等模块,则 Wi-Fi 组件内部模块可以使用 `wifi.driver`、`wifi.mgnt` 等作为标签,进行日志的分类输出。 + +每条日志的标签属性也可以被输出并显示出来,同时 ulog 还可以设置每个标签(模块)对应日志的输出级别,当前不重要模块的日志可以选择性关闭,不仅降低 ROM 资源,还能帮助开发者过滤无关日志。 + +参见 `rt-thread\examples\ulog_example.c` ulog 例程文件,在文件顶部有定义 `LOG_TAG` 宏: + +```c +#define LOG_TAG "example" // 该模块对应的标签。不定义时,默认:NO_TAG +#define LOG_LVL LOG_LVL_DBG // 该模块对应的日志输出级别。不定义时,默认:调试级别 +#include // 必须在 LOG_TAG 与 LOG_LVL 下面 +``` + +需要注意的,定义日志标签必须位于 `#include ` 的上方,否则会使用默认的 `NO_TAG`(不推荐定义在头文件中定义这些宏)。 + +日志标签的作用域是当前源码文件,项目源代码通常也会按照模块进行文件分类。所以在定义标签时,可以指定模块名、子模块名作为标签名称,这样不仅在日志输出显示时清晰直观,也能方便后续按标签方式动态调整级别或过滤。 + +## 日志初始化 + +### 初始化 + +```c +int ulog_init(void) +``` + +| **返回** | **描述** | +| :----- | :----- | +|>=0 | 成功 | +|-5 | 失败,内存不足 | + +在使用 ulog 前必须调用该函数完成 ulog 初始化。如果开启了组件自动初始化,该函数也将被自动调用。 + +### 去初始化 + +```c +void ulog_deinit(void) +``` + +当 ulog 不再使用时,可以执行该 deinit 释放资源。 + +## 日志输出 API + +ulog 主要有两种日志输出宏 API,源代码中定义如下所示: + +```c +#define LOG_E(...) ulog_e(LOG_TAG, __VA_ARGS__) +#define LOG_W(...) ulog_w(LOG_TAG, __VA_ARGS__) +#define LOG_I(...) ulog_i(LOG_TAG, __VA_ARGS__) +#define LOG_D(...) ulog_d(LOG_TAG, __VA_ARGS__) +#define LOG_RAW(...) ulog_raw(__VA_ARGS__) +#define LOG_HEX(name, width, buf, size) ulog_hex(name, width, buf, size) +``` + +* 宏 `LOG_X(...)`:`X` 对应的是不同级别的第一个字母大写。参数 `...` 为日志内容,格式与 printf 一致。这种方式是首选,一方面因为其 API 格式简单,入参只有一个即日志信息,再者还支持按模块静态日志级别过滤。 + +* 宏 `ulog_x(LOG_TAG, __VA_ARGS__):`x` 对应的是不同级别的简写。参数 `LOG_TAG` 为日志标签,参数 `...` 为日志内容,格式与 printf 一致。这个 API 适用于在一个文件中使用不同 tag 输出日志的情况。 + +| **API** |**描述** | +|-------------------------|--------------------------| +| LOG_E(...)| 错误级别日志 | +| LOG_W(...) | 错误级别日志 | +| LOG_I(...) | 提示级别日志 | +| LOG_D(...)| 调试级别日志 | +| LOG_RAW(...) | 输出 raw 日志 | +| LOG_HEX(name, width, buf, size)| 输出 16 进制格式数据到日志 | + +`LOG_X` 及 `ulog_x` 这类 API 输出都是带格式日志,有些时候需要输出不带任何格式的日志时,可以使用 `LOG_RAW` 或 `ulog_raw()` 。例如: + +```c +LOG_RAW("\r"); +ulog_raw("\033[2A"); +``` + +以 16 进制 hex 格式 dump 数据到日志中可使用可以使用 `LOG_HEX()` 或 `ulog_hex` 。函数参数及描述如下所示: + +| **参数** | **描述** | +| ---- | -------------------------- | +| tag | 日志标签 | +| width | 一行 hex 内容的宽度(数量) | +| buf | 待输出的数据内容 | +| size | 数据大小 | + +hexdump 日志为 DEBUG 级别,支持运行期的级别过滤,hexdump 日志对应的 tag ,支持运行期的标签过滤。 + +ulog 也提供里断言 API :`ASSERT(表达式)` ,当断言触发时,系统会停止运行,内部也会执行 `ulog_flush()` ,所有日志后端将执行 flush 。如果开启了异步模式,缓冲区中所有的日志也将被 flush 。断言的使用示例如下: + +``` +void show_string(const char *str) +{ + ASSERT(str); + ... +} +``` + +## 日志使用示例 + +### 使用示例 + +下面将以 ulog 例程进行介绍,打开 `rt-thread\examples\ulog_example.c` 可以看到,顶部有定义该文件的标签及静态优先级。 + +```c +#define LOG_TAG "example" +#define LOG_LVL LOG_LVL_DBG +#include +``` + +在 `void ulog_example(void)` 函数中有使用 `LOG_X` API ,大致如下: + +```c +/* output different level log by LOG_X API */ +LOG_D("LOG_D(%d): RT-Thread is an open source IoT operating system from China.", count); +LOG_I("LOG_I(%d): RT-Thread is an open source IoT operating system from China.", count); +LOG_W("LOG_W(%d): RT-Thread is an open source IoT operating system from China.", count); +LOG_E("LOG_E(%d): RT-Thread is an open source IoT operating system from China.", count); +``` + +这些日志输出 API 均支持 printf 格式,并且会在日志末尾自动换行。 + +下面将在 qemu 上展示下 ulog 例程的运行效果: + +- 将 `rt-thread\examples\ulog_example.c` 拷贝至 `rt-thread\bsp\qemu-vexpress-a9\applications` 文件夹下 +- 在 Env 中进入 `rt-thread\bsp\qemu-vexpress-a9` 目录 +- 确定之前已执行过 ulog 的配置后,执行 `scons` 命令并等待编译完成 +- 运行 `qemu.bat` 来打开 RT-Thread 的 qemu 模拟器 +- 输入 `ulog_example` 命令,即可看到 ulog 例程运行结果,大致效果如下图 + +![ulog 例程](figures/ulog_example.png) + +可以看到每条日志都是按行显示,不同级别日志也有着不同的颜色。在日志最前面有当前系统的 tick ,中间有显示日志级别及标签,最后面是具体的日志内容。在本文后面也会重点介绍这些日志格式及配置说明。 + +### 在中断 ISR 中使用 + +很多时候需要在中断 ISR 中输出日志,但是中断 ISR 可能会打断正在进行日志输出的线程。要保证中断日志与线程日志互不干涉,就得针对于中断情况进行特殊处理。 + +ulog 已集成中断日志的功能,但是默认没有开启,使用时打开 `Enable ISR log` 选项即可,日志的 API 与线程中使用的方式一致,例如: + +```c +#define LOG_TAG "driver.timer" +#define LOG_LVL LOG_LVL_DBG +#include + +void Timer2_Handler(void) +{ + /* enter interrupt */ + rt_interrupt_enter(); + + LOG_D("I'm in timer2 ISR"); + + /* leave interrupt */ + rt_interrupt_leave(); +} + +``` + +这里说明下中断日志在 ulog 处于同步模式与异步模式下的不同策略: + +-**同步模式下**:如果线程此时正在输出日志时来了中断,此时如果中断里也有日志要输出,会直接输出到控制台上,不支持输出到其他后端; +-**异步模式下**:如果发生上面的情况,中断里的日志会先放入缓冲区中,最终和线程日志一起交给日志输出线程来处理。 + +### 设置日志格式 + +ulog 支持的日志格式可以在 menuconfig 中配置,位于 `RT-Thread Components` → `Utilities` → `ulog` → `log format`,具体配置如下: + +![ulog 格式配置](figures/ulog_menuconfig_format.png) + +分别可以配置:浮点型数字的支持(传统的 rtdbg/rt_kprintf 均不支持浮点数日志)、带颜色的日志、时间信息(包括时间戳)、级别信息、标签信息、线程信息。下面我们将这些选项**全部选中**,保存后重新编译并在 qemu 中再次运行 ulog 例程,看下实际的效果: + +![ulog 例程 (全部格式)](figures/ulog_example_all_format.png) + +可以看出,相比第一次运行例程,时间信息已经由系统的 tick 数值变为时间戳信息,并且线程信息也已被输出出来。 + +### hexdump 输出使用 + +hexdump 也是日志输出时较为常用的功能,通过 hexdump 可以将一段数据以 hex 格式输出出来,对应的 API 为:`void ulog_hexdump(const char *tag, rt_size_t width, rt_uint8_t *buf, rt_size_t size)` ,下面看下具体的使用方法及运行效果: + +```c +/* 定义一个 128 个字节长度的数组 */ +uint8_t i, buf[128]; +/* 在数组内填充上数字 */ +for (i = 0; i < sizeof(buf); i++) +{ + buf[i] = i; +} +/* 以 hex 格式 dump 数组内的数据,宽度为 16 */ +ulog_hexdump("buf_dump_test", 16, buf, sizeof(buf)); +``` + +可以将上面的代码拷贝到 ulog 例程中运行,然后再看下实际运行结果: + +![ulog 例程 (hexdump)](figures/ulog_example_hexdump.png) + +可以看出,中部为 buf 数据的 16 进制 hex 信息,最右侧为各个数据对应的字符信息。 + +## 日志高级功能 + +在了解了前面小节对日志的 介绍,ulog 的基本功能都可以掌握了。为了让大家更好的玩转 ulog ,这篇应用笔记会重点跟大家介绍 ulog 的高级功能及一些日志调试的经验和技巧。学会这些高级用法以后,开发者也能很大程度上提升日志调试的效率。 + +同时还会介绍 ulog 的高级模式: syslog 模式,这个模式能做到从前端 API 到日志格式对于 Linux syslog 的完全兼容,极大的方便从 Linux 上的迁移过来的软件。 + +### 日志后端 + +![ulog 框架](figures/ulog_framework_backend.png) + +讲到后端,我们来回顾下 ulog 的框架图。通过上图可以看出, ulog 是采用前后端分离的设计,前后端无依赖。并且支持的后端多样化,无论什么样后端,只要实现出来,都可以注册上去。 + +目前 ulog 已集成控制台后端,即传统的输出 `rt_kprintf` 打印日志的设备。ulog 还支持 Flash 后端,与 EasyFlash 无缝结合,详见其软件包([点击查看](https://github.com/armink-rtt-pkgs/ulog_easyflash_be))。后期 ulog 还会增加文件后端、网络后端等后端的实现。当然,如果有特殊需求,用户也可以自己来实现后端。 + +#### 注册后端设备 + +```c +rt_err_t ulog_backend_register(ulog_backend_t backend, const char *name, rt_bool_t support_color) +``` + +| **参数** | **描述** | +| :----- | :----- | +|backend | 要注册的后端设备句柄 | +|name| 后端设备名称 | +|support_color| 是否支持彩色日志 | +|**返回**|-- | +|>=0 | 成功 | + +该函数用于将后端设备注册到 ulog 中,注册前确保后端设备结构体中的函数成员已设置。 + +#### 注销后端设备 + +```c +rt_err_t ulog_backend_unregister(ulog_backend_t backend); +``` + +| **参数** | **描述** | +| :----- | :----- | +|backend | 要注销的后端设备句柄 | +|**返回**|-- | +|>=0 | 成功 | + +该函数用于注销已经注册的后端设备。 + +#### 后端实现及注册示例 + +下面以控制台后端为例,简单介绍后端的实现方法及注册方法。 + +打开 `rt-thread/components/utilities/ulog/backend/console_be.c` 文件,可以看到大致有如下内容: + +```c +#include +#include + +/* 定义控制台后端设备 */ +static struct ulog_backend console; +/* 控制台后端输出函数 */ +void ulog_console_backend_output(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, size_t len) +{ + ... + /* 输出日志到控制台 */ + ... +} +/* 控制台后端初始化 */ +int ulog_console_backend_init(void) +{ + /* 设定输出函数 */ + console.output = ulog_console_backend_output; + /* 注册后端 */ + ulog_backend_register(&console, "console", RT_TRUE); + + return 0; +} +INIT_COMPONENT_EXPORT(ulog_console_backend_init); +``` + +通过上面的代码可以看出控制台后端的实现非常简单,这里实现了后端设备的 `output` 函数,并将该后端注册到 ulog 里,之后 ulog 的日志都会输出到控制台上。 + +如果要实现一个比较复杂的后端设备,此时就需要了解后端设备结构体,具体如下: + +```c +struct ulog_backend +{ + char name[RT_NAME_MAX]; + rt_bool_t support_color; + void (*init) (struct ulog_backend *backend); + void (*output)(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw, const char *log, size_t len); + void (*flush) (struct ulog_backend *backend); + void (*deinit)(struct ulog_backend *backend); + rt_slist_t list; +}; +``` + +从这个结构体的角度可以看出,实现后端设备的要求如下: + +* `name` 以及 `support_color` 属性可以通过 `ulog_backend_register` 函数在注册时传入。 + +* `output` 为后端具体的输出函数,所有后端都必须实现接口。 + +* `init`/`deinit` 可选择性实现,init 会在 register 时调用,deinit 会在 ulog_deinit 时调用。 + +* `flush` 也是可选择性实现,一些内部输出带缓存的后端需要必须实现该接口 。比如一些带RAM 缓存的文件系统。后端的 flush 一般会在断言、hardfault 等异常情况下由 `ulog_flush` 完成调用。 + +### 异步日志 + +在 ulog 中,默认的输出模式是同步模式,在很多场景下用户可能还需要异步模式。用户在调用日志输出 API 时,会将日志缓存到缓冲区中,会有专门负责日志输出的线程取出日志,然后输出到后端。 + +异步模式和同步模式针对用户而言,在日志 API 使用上是没有差异的,因为 ulog 在底层处理上会有区分。两者的工作原理区别大致如下图所示: + +![ulog 异步 VS 同步](figures/ulog_async_vs_sync.png) + +异步模式的优缺点如下: + +**优点**: + +* 首先日志输出时不会阻塞住当前线程,再加上有些后端输出速率低,所以使用同步输出模式可能影响当前线程的时序,异步模式不存在该问题。 + +* 其次,由于每个使用日志的线程省略了后端输出的动作,所以这些线程的堆栈开销可能也会减少,从这个角度也可以降低整个系统的资源占用。 + +* 同步模式下的中断日志只能输出到控制台后端,而异步模式下中断日志可以输出到所有后端去。 + +**缺点**:首先异步模式需要日志缓冲区。再者异步日志的输出还需要有专门线程来完成,比如:idle 线程或者用户自定义的线程,用法上略显复杂。整体感觉异步模式资源占用会比同步模式要高。 + +#### 配置选项 + +在 ENV 工具中使用 menuconfig 进入 ulog 配置选项: + +```c + RT-Thread Components → Utilities → Enable ulog +``` + +异步模式相关配置选项描述如下所示: + +```c +[*] Enable async output mode. /* 使能异步模式 */ +(2048) The async output buffer size. /* 异步缓冲区大小,默认为 2048*/ +[*] Enable async output by thread. /* 是否启用 ulog 里异步日志输出线程,该线程运行时将会等待日志通知,然后输出日志到所有的后端。该选项默认开启,如果想要修改为其他线程,例如:idle 线程,可关闭此选项。 */ +(1024) The async output thread stack size. /* 异步输出线程的堆栈大小,默认为 1024 */ +(30) The async output thread stack priority./* 异步输出线程的优先级,默认为 30*/ +``` + +使用 idle 线程输出时,实现虽然很简单,只需在应用层调用 `rt_thread_idle_sethook(ulog_async_output)` 即可,但也会存在一些限制。 + +* idle 线程堆栈大小需要根据实际的后端使用情况进行调整。 + +* 由于在 idle 线程内部不允许执行线程挂起操作,所以 Flash 、网络等后端可能无法基于 idle 线程使用。 + +#### 使用示例 + +保存异步输出选项配置,将 `rt-thread\examples\ulog_example.c` 拷贝至 `rt-thread\bsp\qemu-vexpress-a9\applications` 文件夹下。 + +执行 `scons` 命令并等待编译完成。运行 `qemu.bat` 来打开 RT-Thread 的 qemu 模拟器。 +输入 `ulog_example` 命令,即可看到 ulog 例程运行结果,大致效果如下图: + +![ulog 异步例程](figures/ulog_example_async.png) + +大家如果细心观察可以发现,开启异步模式后,这一些在代码上离得非常近的日志的时间信息几乎是相同的。但在同步模式下,日志使用用户线程来输出,由于日志输出要花一定时间,所以每条日志的时间会有一定的间隔。这里也充分说明了异步日志的输出效率很高,几乎不占用调用者的时间。 + +### 日志动态过滤器 + +前面小节有介绍过一些日志的静态过滤功能,静态过滤有其优点比如:节省资源,但很多时候,用户需要在软件运行时动态调整日志的过滤方式,这就可以使用到 ulog 的动态过滤器功能。使用动态过滤器功能需在 menuconfig 中开启 `Enable runtime log filter.` 选项,该选项**默认关闭**。 + +ulog 支持的动态过滤方式有以下 4 种,并且都有对应的 API 函数及 Finsh/MSH 命令,下面将会逐一介绍。 + +#### 按模块的级别过滤 + +```c +int ulog_tag_lvl_filter_set(const char *tag, rt_uint32_t level) +``` + +| **参数** | **描述** | +| ------- | ------------------------------ | +| tag | 日志的标签 | +| level | 设定的日志级别 | +|**返回**|-- | +| >=0 | 成功 | +| -5 | 失败,没有足够的内存 | + +* 命令格式: `ulog_tag_lvl ` + +这里指的**模块**代表一类具有相同标签属性的日志代码。有些时候需要在运行时动态的修改某一个模块的日志输出级别。 + +参数 level 日志级别可取如下值: + +|**级别** |**名称** | +| --------------------- | ---------------- | +| LOG_LVL_ASSERT | 断言 | +| LOG_LVL_ERROR | 错误 | +| LOG_LVL_WARNING | 警告 | +| LOG_LVL_INFO | 信息 | +| LOG_LVL_DBG | 调试 | +| LOG_FILTER_LVL_SILENT | 静默(停止输出) | +| LOG_FILTER_LVL_ALL | 全部 | + +函数调用与命令示例如下所示: + +| 功能 | 函数调用 | 执行命令 | +| ---------------- | ------------------------------ | ------------------ | +| 关闭 `wifi` 模块全部日志 | `ulog_tag_lvl_filter_set("wifi", LOG_FILTER_LVL_SILENT);` | `ulog_tag_lvl wifi 0` | +| 开启 `wifi` 模块全部日志 | `ulog_tag_lvl_filter_set("wifi", LOG_FILTER_LVL_ALL);` | `ulog_tag_lvl wifi 7` | +| 设置 `wifi` 模块日志级别为警告 | `ulog_tag_lvl_filter_set("wifi", LOG_LVL_WARNING);` | `ulog_tag_lvl wifi 4` | + +#### 按标签全局过滤 + +```c +void ulog_global_filter_tag_set(const char *tag) +``` + +| **参数** | **描述** | +| :--- | :------------- | +| tag | 设定的过滤标签 | + +* 命令格式: `ulog_tag [tag]` ,tag 为空时,则取消标签过滤。 + +该过滤方式可以对所有日志执行按标签过滤,只有**包含标签信息**的日志才允许输出。 + +例如:有 `wifi.driver` 、 `wifi.mgnt` 、`audio.driver` 3 种标签的日志,当设定过滤标签为 `wifi` 时,只有标签为 `wifi.driver` 及 `wifi.mgnt` 的日志会输出。同理,当设置过滤标签为 `driver` 时,只有标签为 `wifi.driver` 及 `audio.driver` 的日志会输出。常见功能对应的函数调用与命令示例如下: + +| 功能 | 函数调用 | 执行命令 | +| -------------| -------------------- | ---------- | +| 设置过滤标签为 `wifi` | `ulog_global_filter_tag_set("wifi");` | `ulog_tag wifi` | +| 设置过滤标签为 `driver` | `ulog_global_filter_tag_set("driver");` | `ulog_tag driver` | +| 取消标签过滤 | `ulog_global_filter_tag_set("");` | `ulog_tag` | + +#### 按级别全局过滤 + +```c +void ulog_global_filter_lvl_set(rt_uint32_t level) +``` + +| **参数** | **描述** | +| ---- | -------------------| +| level | 设定的日志级别 | + +* 命令格式: `ulog_lvl ` ,level 取值参照下表: + +| **取值** | **描述** | +| :------------ | :--------------- | +| 0 | 断言 | +| 3 | 错误 | +| 4 | 警告 | +| 6 | 信息 | +| 7 | 调试 | + +通过函数或者命令设定好全局的过滤级别以后,**低于设定级别**的日志都将停止输出。常见功能对应的函数调用与命令示例如下: + +| 功能 | 函数调用 | 执行命令 | +| ----------| ------------------------------ | ------- | +| 关闭全部日志 | `ulog_global_filter_lvl_set(LOG_FILTER_LVL_SILENT);` | `ulog_lvl 0` | +| 开启全部日志 | `ulog_global_filter_lvl_set(LOG_FILTER_LVL_ALL);` | `ulog_lvl 7` | +| 设置日志级别为警告 | `ulog_global_filter_lvl_set(LOG_LVL_WARNING);` | `ulog_lvl 4` | + +#### 按关键词全局过滤 + +```c +void ulog_global_filter_kw_set(const char *keyword) +``` + +| **参数** | **描述** | +| :------ | :--------------- | +| keyword | 设定的过滤关键词 | + +* 命令格式: `ulog_kw [keyword]` ,keyword 为空时,则取消关键词过滤。 + +该过滤方式可以对所有日志执行按关键词过滤,**包含关键词信息**的日志才允许输出。常见功能对应的函数调用与命令示例如下: + +| 功能 | 函数调用 | 执行命令 | +| -------------- | ------------------- | --------- | +| 设置过滤关键词为 `wifi` | `ulog_global_filter_kw_set("wifi");` | `ulog_kw wifi` | +| 清空过滤关键词 | `ulog_global_filter_kw_set("");` | `ulog_kw` | + +#### 查看过滤器信息 + +在设定完过滤器参数后,如果想要查看当前过滤器信息,可以输入 `ulog_filter` 命令,大致效果如下: + +```c +msh />ulog_filter +-------------------------------------- +ulog global filter: +level : Debug +tag : NULL +keyword : NULL +-------------------------------------- +ulog tag's level filter: +wifi : Warning +audio.driver : Error +msh /> +``` + +> 提示:过滤参数也支持保存在 Flash 中,也支持开机自动装载配置。如果需要该功能,请查看**ulog_easyflash**软件包的使用说明。([点击查看](https://github.com/armink-rtt-pkgs/ulog_easyflash_be)) + +#### 使用示例 + +依然是在 qemu BSP 中执行,首先在 menuconfig 开启动态过滤,然后保存配置并编译、运行例程,在日志输出约**20**次后,会执行 ulog_example.c 里对应的如下过滤代码: + +```c +if (count == 20) +{ + /* Set the global filer level is INFO. All of DEBUG log will stop output */ + ulog_global_filter_lvl_set(LOG_LVL_INFO); + /* Set the test tag's level filter's level is ERROR. The DEBUG, INFO, WARNING log will stop output. */ + ulog_tag_lvl_filter_set("test", LOG_LVL_ERROR); +} +... +``` + +此时全局的过滤级别由于被设定到了 INFO 级别,所以无法再看到比 INFO 级别低的日志。同时,又将 `test` 标签的日志输出级别设定为 ERROR ,此时 `test` 标签里比 ERROR 低的日志也都停止输出了。在每条日志里都有当前日志输出次数的计数值,对比的效果如下: + +![ulog 过滤器例程 20](figures/ulog_example_filter20.png) + +在日志输出约**30**次后,会执行 ulog_example.c 里对应的如下过滤代码: + +```c +... +else if (count == 30) +{ + /* Set the example tag's level filter's level is LOG_FILTER_LVL_SILENT, the log enter silent mode. */ + ulog_tag_lvl_filter_set("example", LOG_FILTER_LVL_SILENT); + /* Set the test tag's level filter's level is WARNING. The DEBUG, INFO log will stop output. */ + ulog_tag_lvl_filter_set("test", LOG_LVL_WARNING); +} +... +``` + +此时又新增了 `example` 模块的过滤器,并且是将这个模块的所有日志都停止输出,所以接下来将看不到该模块日志。同时,又将 `test` 标签的日志输出级别降低为 WARING ,此时就只能看到 `test` 标签的 WARING 与 ERROR 级别日志 。效果如下: + +![ulog 过滤器例程 30](figures/ulog_example_filter30.png) + +在日志输出约**40**次后,会执行 ulog_example.c 里对应的如下过滤代码: + +```c +... +else if (count == 40) +{ + /* Set the test tag's level filter's level is LOG_FILTER_LVL_ALL. All level log will resume output. */ + ulog_tag_lvl_filter_set("test", LOG_FILTER_LVL_ALL); + /* Set the global filer level is LOG_FILTER_LVL_ALL. All level log will resume output */ + ulog_global_filter_lvl_set(LOG_FILTER_LVL_ALL); +} +``` + +此时将 `test` 模块的日志输出级别调整为 `LOG_FILTER_LVL_ALL` ,即不再过滤该模块任何级别的日志。同时,又将全局过滤级别设定为 `LOG_FILTER_LVL_ALL` ,所以接下来 `test` 模块的全部日志将恢复输出。效果如下: + +![ulog 过滤器例程 40](figures/ulog_example_filter40.png) + +### 系统异常时的使用 + +由于 ulog 的异步模式具有缓存机制,注册进来的后端内部也可能具有缓存。如果系统出现了 hardfault 、断言等错误情况,但缓存中还有日志没有输出出来,这可能会导致日志丢失的问题,对于查找异常的原因会无从入手。 + +针对这种场景,ulog 提供了统一的日志 flush 函数: `void ulog_flush(void)` ,当出现异常时,输出异常信息日志时,同时再调用该函数,即可保证缓存中剩余的日志也能够输出到后端中去。 + +下面以 RT-Thread 的断言及 CmBacktrace 进行举例: + +#### 断言 + +RT-Thread 的断言支持断言回调(hook),我们定义一个类似如下的断言 hook 函数,然后通过 `rt_assert_set_hook(rtt_user_assert_hook);` 函数将其设置到系统中即可。 + +```c +static void rtt_user_assert_hook(const char* ex, const char* func, rt_size_t line) +{ + rt_enter_critical(); + + ulog_output(LOG_LVL_ASSERT, "rtt", RT_TRUE, "(%s) has assert failed at %s:%ld.", ex, func, line); + /* flush all log */ + ulog_flush(); + while(1); +} +``` + +#### CmBacktrace + +CmBacktrace 是一个 ARM Cortex-M 系列 MCU 的错误诊断库,它也有对应 RT-Thread 软件包,并且最新版的软件包已经做好了针对于 ulog 的适配。里面适配代码位于 `cmb_cfg.h` : + +```c +... +/* print line, must config by user */ +#include +#ifndef RT_USING_ULOG +#define cmb_println(...) rt_kprintf(__VA_ARGS__);rt_kprintf("\r\n") +#else +#include +#define cmb_println(...) ulog_e("cmb", __VA_ARGS__);ulog_flush() +#endif /* RT_USING_ULOG */ +... +``` + +由此可以看出,当启用了 ulog 以后,CmBacktrace 的每一条日志输出时都会使用错误级别,并且会同时执行 `ulog_flush` ,用户无需再做任何修改。 + +### syslog 模式 + +在 Unix 类操作系统上,syslog 广泛应用于系统日志。syslog 常见的后端有文件和网络,syslog 日志可以记录在本地文件中,也可以通过网络发送到接收 syslog 的服务器 。 + +ulog 提供了 syslog 模式的支持,不仅仅前端 API 与 syslog API 完全一致,日志的格式也符合 RFC 标准。但需要注意的是,在开启 syslog 模式后,不管使用哪一种日志输出 API ,整个 ulog 的日志输出格式都会采用 syslog 格式。 + +使用 syslog 配置需要开启 `Enable syslog format log and API.` 选项。 + +#### 日志格式 + +![ulog syslog 格式](figures/ulog_syslog_format.png) + +如上图所示,ulog syslog 日志格式分为下面 4 个部分: + +| 格式 | **描述** | +| ---- | --------------------- | +| PRI | PRI 部分由尖括号包含的一个数字构成,这个数字包含了程序模块(Facility)、严重性(Severity)信息,是由 Facility 乘以 8,然后加上 Severity 得来。 Facility 和 Severity 由 syslog 函数的入参传入,具体数值详见 syslog.h | +| Header | Header 部分主要是时间戳,指示当前日志的时间; | +| TAG | 当前日志的标签,可以通过 openlog 函数入参传入,如果不指定将会使用 rtt 作为默认标签 | +| Content | 日志的具体内容 | + +#### 使用方法 + +使用前需要在 menuconfig 中开启 syslog 选项,主要常用的 API 有: + +* 打开 syslog:void openlog(const char *ident, int option, int facility) + +* 输出 syslog 日志:void syslog(int priority, const char *format, ...) + +> 提示:提示:调用 openlog 是可选择的。如果不调用 openlog ,则在第一次调用 syslog 时,自动调用 openlog + +syslog() 函数的使用方法也非常简单,其入参格式与 printf 函数一致。在 ulog_example.c 中也有 syslog 的例程,在 qemu 中的运行效果大致如下: + +![ulog syslog 例程](figures/ulog_example_syslog.png) + +### 从 rt_dbg.h 或 elog 迁移到 ulog + +如果项目中以前使用的是这两类日志组件,当要使用 ulog 时,就会牵扯到如何让以前代码也支持 ulog ,下面将会重点介绍迁移过程。 + +#### 从 rt_dbg.h 迁移 + +当前 rtdbg 已完成**无缝对接** ulog ,开启 ulog 后,旧项目中使用 rtdbg 的代码无需做任何修改,即可使用 ulog 完成日志输出。 + +#### 从 elog(EasyLogger)迁移 + +如果无法确认某个源代码文件运行的目标平台上一定会使用 ulog ,那么还是建议在该文件中增加下面的改动: + +```c +#ifdef RT_USING_ULOG +#include +#else +#include +#endif /* RT_USING_ULOG */ +``` + +如果明确只会使用 ulog 组件后,那么只需将头文件引用从 `elog.h` 更换为 `ulog .h`,其他任何代码都无需改动。 + +### 日志使用技巧 + +有了日志工具后,如果使用不当,也会造成日志被滥用、日志信息无法突出重点等问题。这里重点与大家分享下日志组件在使用时的一些技巧,让日志信息更加直观。主要关注点有: + +#### 合理利用标签分类 + +合理利用标签功能,每个模块代码在使用日志前,先明确好模块、子模块名称。这样也能让日志在最开始阶段就做好分类,为后期日志过滤也做好了准备。 + +#### 合理利用日志级别 + +刚开始使用日志库时,大家会经常遇到 警告与错误 日志无法区分,信息与调试 日志无法区分,导致日志级别选择不合适。一些重要日志可能看不到,不重要的日志满天飞等问题。所以,在使用前务必仔细阅读日志级别小节,针对各个级别划分,里面有明确的标准。 + +#### 避免重复性冗余日志 + +在一些情况下会出现代码的重复调用或者循环执行,多次输出相同、相似的日志问题。这样的日志不仅会占用很大的系统资源,还会影响开发人员对于问题的定位。所以,在遇到这种情况时,建议增加对于重复性日志特殊处理,比如:让上层来输出一些业务有关的日志,底层只返回具体结果状态;同一个时间点下相同的日志,是否可以增加去重处理,在错误状态没有变化时,只输出一次等等。 + +#### 开启更多的日志格式 + +ulog 默认的日志格式中没有开启时间戳及线程信息。这两个日志信息,在 RTOS 上挺实用。它们能帮助开发者直观的了解各个日志的运行时间点、时间差,还能清晰的看到是在哪个线程执行当前代码。所以如果条件允许,还是建议开启。 + +#### 关闭不重要的日志 + +ulog 提供了多种维度的日志开关、过滤的功能,完全能够做到精细化控制,所以如果在调试某个功能模块时,可以适当关闭其他无关模块的日志输出,这样就可以聚焦在当前调试的模块上。 + +## 常见问题 + +### Q: 日志代码已执行,但是无输出。 + + **A:** 参考日志级别小节,了解日志级别分类,并检查日志过滤参数。还有种可能是不小心将控制台后端给关闭了,重新开启 `Enable console backend` 即可。 + +### Q: 开启 ulog 后,系统运行崩溃,例如:线程堆栈溢出。 + + **A:** ulog 比起以前用的 rtdbg 或者 `rt_kprintf` 打印输出函数会多占一部分线程堆栈空间,如果是开启了浮点数打印支持,由于其内部使用了 libc 里资源占用加大的 `vsnprintf`,所以堆栈建议多预留 250 字节。如果开启了时间戳功能,堆栈建议多预留 100 字节。 + +### Q: 日志内容的末尾缺失。 + + **A:** 这是由于日志内容超出设定的日志的最大宽度。检查 `The log's max width` 选项,并增大其至合适的大小。 + +### Q: 开启时间戳以后,为什么看不到毫秒级时间。 + + **A:** 这是因为 ulog 目前只支持在开启软件模拟 RTC 状态下,显示毫秒级时间戳。如需显示,只要开启 RT-Thread 软件模拟 RTC 功能即可。 + +### Q: 每次 include ulog 头文件前,都要定义 LOG_TAG 及 LOG_LVL ,可否简化。 + +**A:** `LOG_TAG` 如果不定义,默认会使用 `NO_TAG` 标签,这样输出的日志会容易产生误解,所以标签的宏不建议省略。 + + `LOG_LVL` 如果不定义,默认会使用调试级别,如果该模块处于开发阶段这个过程可以省略,但是模块代码如果已经稳定,建议定义该宏,并修改级别为信息级别。 + +### Q: 运行出现警告提示:Warning: There is no enough buffer for saving async log, please increase the ULOG_ASYNC_OUTPUT_BUF_SIZE option。 + +**A:** 当遇到该提示时,说明了在异步模式下的缓冲区出现了溢出的情况,这会导致一部分日志丢失,增大 ULOG_ASYNC_OUTPUT_BUF_SIZE 选项可以解决该问题。 + +### Q: 编译时提示:The idle thread stack size must more than 384 when using async output by idle (ULOG_ASYNC_OUTPUT_BY_IDLE)。 + +**A:** 在使用 idle 线程作为输出线程时,idle 线程的堆栈大小需要提高,这也取决于具体的后端设备,例如:控制台后端时,idle 线程至少得 384 字节。 diff --git a/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/UtestAppStruct-1.png b/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/UtestAppStruct-1.png new file mode 100644 index 0000000000000000000000000000000000000000..9f60fbb81ccc7c9407f0286316ee9711ffb22596 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/UtestAppStruct-1.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/UtestRunLogShow.png b/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/UtestRunLogShow.png new file mode 100644 index 0000000000000000000000000000000000000000..595d01dc5dfee57d16937c7436a541b5216e3385 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/UtestRunLogShow.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/testcase-runflowchart.jpg b/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/testcase-runflowchart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e8c9375c047b5bbf9052a81d5762f93814d0a318 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/testcase-runflowchart.jpg differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/testcase-runflowchart.vsdx b/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/testcase-runflowchart.vsdx new file mode 100644 index 0000000000000000000000000000000000000000..3010961d20456037b2d13d091f5689f7e4731a8c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/utest/figures/testcase-runflowchart.vsdx differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/utest/utest.md b/rt-thread-version/rt-thread-standard/programming-manual/utest/utest.md new file mode 100644 index 0000000000000000000000000000000000000000..92924905b2465ec23ef45b032d38e6287a7ba592 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/utest/utest.md @@ -0,0 +1,276 @@ +# utest 测试框架 + +## utest 简介 + +utest(unit test)是 RT-Thread 开发的单元测试框架。设计 utest 的初衷是方便 RT-Thread 开发者使用统一的框架接口编写测试程序,实现单元测试、覆盖测试以及集成测试的目的。 + +### 测试用例定义 + +测试用例(testcase,简称 tc)是为实现特定测试目标而执行的单个测试,是包括测试输入、执行条件、测试过程和预期结果的规范,是一个有明确的结束条件和明确的测试结果的有限循环。 + +utest(unit test)测试框架定义用户编写的测试程序为**测试用例**,一个*测试用例*仅包含一个 *testcase* 函数(类似 main 函数),可包含多个*测试单元*函数。 + +具体地通过 utest 测试框架提供的 API 完成的针对某一功能的测试代码就是一个测试用例。 + +### 测试单元定义 + +测试单元(test unit)是被测功能细分后的测试点,每个测试点可以任务是被测功能的最小可测单位。当然,不同的分类方式会细分出不同的测试单元。 + +### utest 应用框图 + +![utest 应用框图](./figures/UtestAppStruct-1.png) + +如上图所示,测试用例基于测试框架 utest 测试框架提供的服务接口进行程序设计,支持将多个测试用例编译到一起进行测试。另外从图中可以看到,一个测试用例对应唯一的 *testcase* 函数,在 *testcase* 中包含多个测试单元(test unit)。 + +## utest API + +为了能够实现格式统一的测试用例代码,测试框架 utest 为测试用例编写提供了一套通用的 API 接口。 + +### assert 宏 + +``` +注意: +这里的 assert 仅记录通过和失败的数量,不会产生断言并终止程序运行。其功能不等同于 RT_ASSERT。 +``` + +| assert 宏 | 说明 | +| :------ | :------ | +| uassert_true(value) | value 为 true 则测试通过,否则测试失败 | +| uassert_false(value) | value 为 false 则测试通过,否则测试失败 | +| uassert_null(value) | value 为 null 则测试通过,否则测试失败 | +| uassert_not_null(value)| value 为非 null 值则测试通过,否则测试失败 | +| uassert_int_equal(a, b)| a 和 b 值相等则测试通过,否则测试失败 | +| uassert_int_not_equal(a, b)| a 和 b 值不相等则测试通过,否则测试失败 | +| uassert_str_equal(a, b) | 字符串 a 和字符串 b 相同则测试通过,否则测试失败 | +| uassert_str_not_equal(a, b)| 字符串 a 和字符串 b 不相同则测试通过,否则测试失败 | +| uassert_in_range(value, min, max) | value 在 min 和 max 的范围内则测试通过,否则测试失败 | +| uassert_not_in_range(value, min, max)| value 不在 min 和 max 的范围内则测试通过,否则测试失败 | + +### 测试单元运行宏 + +```c +UTEST_UNIT_RUN(test_unit_func) +``` + +测试用例中,使用 `UTEST_UNIT_RUN` 宏执行指定的测试单元函数 `test_unit_func`。测试单元(test unit)必须使用 `UTEST_UNIT_RUN` 宏执行。 + +### 测试用例导出宏 + +```c +UTEST_TC_EXPORT(testcase, name, init, cleanup, timeout) +``` + +| 参数 | 描述 | +| :----- | :------ | +| testcase | 测试用例主承载函数(**规定**使用名为 static void testcase(void) 的函数 | +| name | 测试用例名称(唯一性)。规定使用测试用例相对 `testcases 目录`的相对路径以 `.` 进行连接的命名格式 | +| init | 测试用例启动前的初始化函数 | +| cleanup | 测试用例结束后的清理函数 | +| timeout | 测试用例预计需要的测试时间(单位是秒) | + +**测试用例命名要求:** + +测试用例需要按照规定的格式命名。规定使用当前测试用例相对 `testcases 目录`的相对路径以 `.` 进行连接的命名格式,名字中包含当前测试用例文件的文件名(除去后缀名的文件名)。 + +**测试用例命名示例:** + +假设在测试用例 testcases 目录下,有 `testcases\components\filesystem\dfs\dfs_api_tc.c` 测试用例文件,那么该 dfs_api_tc.c 中的测试用例的名称命名为 `components.filesystem.dfs.dfs_api_tc`。 + +### 测试用例 LOG 输出接口 + +utest 测试框架依赖 *ulog 日志模块*进行日志输出,并且 utest 测试框架中已经日志输出级别。因此只要在测试用例里加入 `#include "utest.h"` 即可使用 ulog 日志模块的所有级别接口(LOG_D/LOG_I/LOG_E)。 + +另外,utest 测试框架增加了额外的日志控制接口,如下: + +```c +#define UTEST_LOG_ALL (1u) +#define UTEST_LOG_ASSERT (2u) + +void utest_log_lv_set(rt_uint8_t lv); +``` + +用户可以在测试用例中使用 `utest_log_lv_set` 接口控制日志输出级别。`UTEST_LOG_ALL` 配置输出所有日志,`UTEST_LOG_ASSERT` 配置仅输出 uassert 失败后的日志。 + +## 配置使能 + +使用 utest 测试框架需要在 ENV 工具中使用 menuconfig 进行如下配置: + +```c +RT-Thread Kernel ---> + Kernel Device Object ---> + (256) the buffer size for console log printf /* utest 日志需要的最小 buffer */ +RT-Thread Components ---> + Utilities ---> + -*- Enable utest (RT-Thread test framework) /* 使能 utest 测试框架 */ + (4096) The utest thread stack size /* 设置 utest 线程堆栈(-thread 模式需要) */ + (20) The utest thread priority /* 设置 utest 线程优先级(-thread 模式需要) */ +``` + +## 应用范式 + +前面介绍了 utest 测试框架和相关 API,这里介绍基本的测试用例代码结构。 + +测试用例文件必须的代码块如下所示: + +```c +/* + * Copyright (c) 2006-2019, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2019-01-16 MurphyZhao the first version + */ + +#include +#include "utest.h" + +static void test_xxx(void) +{ + uassert_true(1); +} + +static rt_err_t utest_tc_init(void) +{ + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + return RT_EOK; +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(test_xxx); +} +UTEST_TC_EXPORT(testcase, "components.utilities.utest.sample.sample_tc", utest_tc_init, utest_tc_cleanup, 10); +``` + +一个基本的测试用例必须包含以下内容: + +- 文件注释头(Copyright) + + 测试用例文件必须包含文件注释头,包含 `Copyright`、时间、作者、描述信息。 + +- utest_tc_init(void) + + 测试运行前的初始化函数,一般用来初始化测试需要的环境。 + +- utest_tc_cleanup(void) + + 测试结束后的清理函数,用来清理测试过程中申请的资源(如内存,线程,信号量等)。 + +- testcase(void) + + 测试主体函数,一个测试用例实现仅能包含一个 testcase 函数(类似 main 函数)。通常该函数里只用来运行测试单元执行函数 `UTEST_UNIT_RUN` 。 + + 一个 testcase 中可以包含多个测试单元,每个测试单元由 `UTEST_UNIT_RUN` 执行。 + +- UTEST_UNIT_RUN + + 测试单元执行函数。 + +- test_xxx(void) + + 每个功能单元的测试实现。用户根据需求确定函数名和函数实现。 + +- uassert_true + + 用于判断测试结果的断言宏(该断言宏并不会终止程序运行)。测试用例必须使用 `uassert_xxx` 宏来判断测试结果,否则测试框架不知道测试是否通过。 + + 所有的 `uassert_xxx` 宏都通过后,整个测试用例才算测试通过。 + +- UTEST_TC_EXPORT + + 将测试用例 testcase 函数导出到测试框架。 + +## 测试用例运行要求 + +测试框架 utest 将所有的测试用例导出到了 `UtestTcTab` 代码段,在 IAR 和 MDK 编译器中不需要在链接脚本中定义 `UtestTcTab` 段,但是在 GCC 编译时,需要在链接脚本中显式地设置 `UtestTcTab` 段。 + +因此,测试用例要在 GCC 下能够编译运行,必须要先在 GCC 的*链接脚本*中定义 `UtestTcTab` 代码段。 + +在 GCC 链接脚本的 `.text` 中,增加 `UtestTcTab` 段的定义,格式如下所示: + +```c +/* section information for utest */ +. = ALIGN(4); +__rt_utest_tc_tab_start = .; +KEEP(*(UtestTcTab)) +__rt_utest_tc_tab_end = .; +``` + +## 运行测试用例 + +测试框架提供了以下命令,便于用户在 RT-Thread MSH 命令行中运行测试用例,命令如下: + +**utest_list 命令** + +列出当前系统支持的测试用例,包括测试用例的名称和测试需要的时间。该命令无参数。 + +**utest_run 命令** + +测试用例执行命令,该命令格式如下: + +```c +utest_run [-thread or -help] [testcase name] [loop num] +``` + +| utest_run 命令参数| 描述 | +| :---- | :----- | +| -thread | 使用线程模式运行测试框架 | +| -help | 打印帮助信息 | +| testcase name | 指定测试用例名称。支持使用通配符`*`,支持指定测试用例名称前部分字节 | +| loop num | 指定测试用例循环测试次数 | + +**测试命令使用示例:** + +```c +msh />utest_list +[14875] I/utest: Commands list : +[14879] I/utest: [testcase name]:components.filesystem.dfs.dfs_api_tc; [run timeout]:30 +[14889] I/utest: [testcase name]:components.filesystem.posix.posix_api_tc; [run timeout]:30 +[14899] I/utest: [testcase name]:packages.iot.netutils.iperf.iperf_tc; [run timeout]:30 +msh /> +msh />utest_run components.filesystem.dfs.dfs_api_tc +[83706] I/utest: [==========] [ utest ] started +[83712] I/utest: [----------] [ testcase ] (components.filesystem.dfs.dfs_api_tc) started +[83721] I/testcase: in testcase func... +[84615] D/utest: [ OK ] [ unit ] (test_mkfs:26) is passed +[84624] D/testcase: dfs mount rst: 0 +[84628] D/utest: [ OK ] [ unit ] (test_dfs_mount:35) is passed +[84639] D/utest: [ OK ] [ unit ] (test_dfs_open:40) is passed +[84762] D/utest: [ OK ] [ unit ] (test_dfs_write:74) is passed +[84770] D/utest: [ OK ] [ unit ] (test_dfs_read:113) is passed +[85116] D/utest: [ OK ] [ unit ] (test_dfs_close:118) is passed +[85123] I/utest: [ PASSED ] [ result ] testcase (components.filesystem.dfs.dfs_api_tc) +[85133] I/utest: [----------] [ testcase ] (components.filesystem.dfs.dfs_api_tc) finished +[85143] I/utest: [==========] [ utest ] finished +msh /> +``` + +### 测试结果分析 + +![utest 日志展示](./figures/UtestRunLogShow.png) + +如上图所示,测试用例运行的日志从左到右被分成了四列,分别是 log 日志头信息、结果栏、属性栏、详细信息展示栏。日志中使用 `result` 属性标识该测试用例测试结果(PASSED or FAILED)。 + +## 测试用例运行流程 + +![测试用例运行流程](./figures/testcase-runflowchart.jpg) + +从上面的流程图中可以得到以下内容: + +* utest 测试框架是顺序执行 *testcase* 函数中的所有**测试单元** +* 上一个 UTEST_UNIT_RUN 宏执出现了 assert,后面的所有 UTEST_UNIT_RUN 会跳过执行 + +## 注意事项 + +> [!NOTE] +> 注:- 使用 GCC 编译前,确定链接脚本是否增加了 `UtestTcTab` 段 + - 编译前确保 `RT-Thread Kernel -> Kernel Device Object -> (256) the buffer size for console log printf` 至少为 256 字节 + - 测试用例中创建的资源(线程、信号量、定时器、内存等)需要在测试结束前释放 + - 一个测试用例实现仅能使用 `UTEST_TC_EXPORT` 导出一个测试主体函数(testcase 函数) + - 为编写的测试用例程序编写 README.md 文档,指导用户配置测试环境 diff --git a/rt-thread-version/rt-thread-standard/programming-manual/version.md b/rt-thread-version/rt-thread-standard/programming-manual/version.md new file mode 100644 index 0000000000000000000000000000000000000000..74d62e5da6af53454005825a68b58bfa058e8815 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/version.md @@ -0,0 +1,8 @@ +# 版本和修订 + +| Date | Version | Author | Note | +| ----- | :-----: | :---- | :----------- | +| 2013-05-14 | v1.0.0 | bernard | 初始版本 | +| 2018-12-29 | v2.0.0 | yangjie | 内核内容修订 | +| 2018-12.29 | v2.0.0 | misonyo | 组件及设备内容修订 | + diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/README.md b/rt-thread-version/rt-thread-standard/tutorial/beep-player/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ae6a5c631a8b25b4990ccaa06184133909ec253b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/beep-player/README.md @@ -0,0 +1,39 @@ +# 应用 RT-Thread 实现蜂鸣器播放器 +## 简介 + +这是一个应用 RT-Thread 实现蜂鸣器播放器的教程,共包含 6 节的内容,介绍了由浅入深,一步步实现一个蜂鸣器播放器的过程。学完这个课程可以对 RT-Thread 的设备框架有一个更深入的了解,也能增加自己的动手实践能力。 + +此播放器支持 `歌曲列表`、`上一曲`、`下一曲`、`暂停/播放`、`增减音量`。 + +歌单通过串口打印出来,效果如下: + +``` +*********** Beep Player *********** +01. 两只老虎 +02. 挥着翅膀的女孩 +03. 同一首歌 +04. 两只蝴蝶 +<--- 正在播放:同一首歌---> +播放进度:00% 音量大小:03% +*********************************** +``` + +## 教程目录 + +[第 1 节:使用 PIN 设备控制 LED](pin.md) + +[第 2 节:使用 PIN 设备实现按键控制](button.md) + +[第 3 节:使用 PWM 设备驱动蜂鸣器](pwm.md) + +[第 4 节:音乐数据的编码与解码](decode.md) + +[第 5 节:播放器的实现](player.md) + +[第 6 节:为播放器添加按键控制](key.md) + +> 提示:此教程只涉及 PIN/PWM 这两个外设的使用,如果想要学习更多的外设,像 I2C/SPI/CAN/ADC 等可以参考[设备和驱动](../../programming-manual/device/i2c/i2c.md)。 + +## 继续学习 + +学习完这个外设实践的课程,你已经可以利用这些常用外设进行项目的开发了。如果想要继续学习 `文件系统` 或 `网络` 相关的课程可以前往 [QEMU网络视频教程](../qemu-network/README.md) 学习。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/button.md b/rt-thread-version/rt-thread-standard/tutorial/beep-player/button.md new file mode 100644 index 0000000000000000000000000000000000000000..30e2bc42a96380bba222a6aee4d3fe27f9809561 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/beep-player/button.md @@ -0,0 +1,250 @@ +# 第 2 节:使用 pin 设备实现按键控制 + +之前我们学习了利用 pin 设备控制 led 的亮灭,这一节我们学习利用 pin 设备进行按键的控制。 + +## 基础知识 + +按键控制的实现有很多的方式,在裸机编程的时候最常用的就是延时消抖以及抬手检测了,可以很简单的就实现按键的输入。其实还有更加简单、灵活的按键处理方式,就是每隔一定的时间间隔扫描一次按键的状态,如果连续多次按键的状态都是按下的状态,我们就认为按键是按下的。 + +在裸机编程的时候,按键处理一般是轮询的方式获取按键的状态,然后再根据按键的不同状态进行对应的操作。但是在 RT-Thread 上可以有更优雅的实现方式,那就是使用回调函数的方式。我们可以为每一个按键的事件注册按键回调事件,这样就可以将键值的读取,按键状态的判断都交给驱动程序来实现。在使用按键的时候,只要注册上对应的处理函数,待按键发生对应的事件之后,就会自动执行相应的逻辑代码。使按键处理的代码更及时,更独立。 + +现在我们就用 RT-Thread 的 pin 设备来实现一个简单的按键驱动库,为了满足播放器控制的需求,需要能够区分 `短按` 和 `长按` 事件。 + +## 硬件连接 + +以正点原子探索者 STM32F4 开发板为例。开发板上有四个供用户使用的按键 KEY_UP/KEY0/1/2,通过原理图可以看出,KEY0/1/2 三个按键对应的 PIN 分别是 1/2/3。 + +![原理图](figures/hw_key.png) + +## 软件实现 + +为了更好的判断按键的事件,我们可以定义一个枚举类型 `my_button_event`。其中 `BUTTON_EVENT_HOLD_CYC` 的意义就是:在长按的时候可以每隔一定的时间间隔,就触发一次,方便进行音量的调节。 + +```c +enum my_button_event +{ + BUTTON_EVENT_CLICK_DOWN, /* 按键单击按下事件 */ + BUTTON_EVENT_CLICK_UP, /* 按键单击结束事件 */ + BUTTON_EVENT_HOLD, /* 按键长按开始事件 */ + BUTTON_EVENT_HOLD_CYC, /* 按键长按周期性触发事件 */ + BUTTON_EVENT_HOLD_UP, /* 按键长按结束事件 */ + BUTTON_EVENT_NONE /* 无按键事件 */ +}; +``` + +然后写一个结构体,将按键相关的信息都放到一起,方便统一管理。 + +```c +struct my_button +{ + rt_uint8_t press_logic_level; /* 按键按下时的电平 */ + rt_uint16_t cnt; /* 连续扫描到按下状态的次数 */ + rt_uint16_t hold_cyc_period; /* 长按周期回调的周期 */ + rt_uint16_t pin; /* 按键对应的 pin 编号 */ + + enum my_button_event event; /* 按键的触发的事件 */ + + my_button_callback cb; /* 按键触发事件回调函数 */ +}; +``` + +这只是一个按键的抽象,我们使用按键不可能只使用一个,因此需要定义一个存储按键的结构体,以及一个注册按键的 API。 + +```c +int my_button_register(struct my_button *button); +``` + +```c +struct my_button_manage +{ + rt_uint8_t num; /* 已注册的按键的数目 */ + rt_timer_t timer; /* 按键扫描用到的定时器 */ + struct my_button *button_list[MY_BUTTON_LIST_MAX]; /* 存储按键指针的数组 */ +}; +static struct my_button_manage button_manage; +``` + +这样只要在调用 `my_button_register` API 时将传进来的按键结构体添加到 button_manage 这个结构体里即可 + +```c +int my_button_register(struct my_button *button) +{ + /* 初始化按键对应的 pin 模式 */ + if (button->press_logic_level == 0) + { + rt_pin_mode(button->pin, PIN_MODE_INPUT_PULLUP); + } + else + { + rt_pin_mode(button->pin, PIN_MODE_INPUT_PULLDOWN); + } + + /* 初始化按键结构体 */ + button->cnt = 0; + button->event = BUTTON_EVENT_NONE; + + /* 添加按键到管理列表 */ + button_manage.button_list[button_manage.num++] = button; + + return 0; +} +``` + +然后创建一个定时器,定时扫描一遍按键的状态,下面是扫描函数的实现,逻辑还是很简单的,只要按下的时间达到相应的触发条件,就执行相应的回调函数 + +```c +#define MY_BUTTON_DOWN_MS 50 +#define MY_BUTTON_HOLD_MS 700 + +#define MY_BUTTON_CALL(func, argv) \ + do { if ((func) != RT_NULL) func argv; } while (0) + +static void my_button_scan(void *param) +{ + rt_uint8_t i; + rt_uint16_t cnt_old; + + for (i = 0; i < button_manage.num; i++) + { + cnt_old = button_manage.button_list[i]->cnt; + + /* 检测按键的电平状态为按下状态 */ + if (rt_pin_read(button_manage.button_list[i]->pin) == button_manage.button_list[i]->press_logic_level) + { + /* 按键扫描的计数值加一 */ + button_manage.button_list[i]->cnt ++; + + /* 连续按下的时间达到单击按下事件触发的阈值 */ + if (button_manage.button_list[i]->cnt == MY_BUTTON_DOWN_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_DOWN */ + { + LOG_D("BUTTON_DOWN"); + button_manage.button_list[i]->event = BUTTON_EVENT_CLICK_DOWN; + MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i])); + } + /* 连续按下的时间达到长按开始事件触发的阈值 */ + else if (button_manage.button_list[i]->cnt == MY_BUTTON_HOLD_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_HOLD */ + { + LOG_D("BUTTON_HOLD"); + button_manage.button_list[i]->event = BUTTON_EVENT_HOLD; + MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i])); + } + /* 连续按下的时间达到长按周期回调事件触发的阈值 */ + else if (button_manage.button_list[i]->cnt > MY_BUTTON_HOLD_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_HOLD_CYC */ + { + LOG_D("BUTTON_HOLD_CYC"); + button_manage.button_list[i]->event = BUTTON_EVENT_HOLD_CYC; + if (button_manage.button_list[i]->hold_cyc_period && button_manage.button_list[i]->cnt % (button_manage.button_list[i]->hold_cyc_period / MY_BUTTON_SCAN_SPACE_MS) == 0) + MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i])); + } + } + /* 检测按键的电平状态为抬起状态 */ + else + { + /* 清除按键的计数值 */ + button_manage.button_list[i]->cnt = 0; + /* 连续按下的时间达到单击结束事件触发的阈值 */ + if (cnt_old >= MY_BUTTON_DOWN_MS / MY_BUTTON_SCAN_SPACE_MS && cnt_old < MY_BUTTON_HOLD_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_CLICK_UP */ + { + LOG_D("BUTTON_CLICK_UP"); + button_manage.button_list[i]->event = BUTTON_EVENT_CLICK_UP; + MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i])); + } + /* 连续按下的时间达到长按结束事件触发的阈值 */ + else if (cnt_old >= MY_BUTTON_HOLD_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_HOLD_UP */ + { + LOG_D("BUTTON_HOLD_UP"); + button_manage.button_list[i]->event = BUTTON_EVENT_HOLD_UP; + MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i])); + } + } + } +} +``` +然后再在 `int my_button_start(void);` API 里执行定时器的初始化,定时执行这个按键扫描的函数。 + +```c +int my_button_start() +{ + if (button_manage.timer != RT_NULL) + return -1; + + /* 创建定时器1 */ + button_manage.timer = rt_timer_create("timer1", /* 定时器名字是 timer1 */ + my_button_scan, /* 超时时回调的处理函数 */ + RT_NULL, /* 超时函数的入口参数 */ + RT_TICK_PER_SECOND * MY_BUTTON_SCAN_SPACE_MS / 1000, /* 定时长度,以OS Tick为单位,即10个OS Tick */ + RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER); /* 周期性定时器 */ + /* 启动定时器 */ + if (button_manage.timer != RT_NULL) + rt_timer_start(button_manage.timer); + + return 0; +} +``` + +## 示例程序 + + 这一节的示例程序实现的是利用按键 KEY2 控制 led 的亮灭,每按一次按键就会翻转一次 led 的状态。实现的方式是: + + 1. 包含 `button.h` 的头文件 + 2. 实现按键回调函数 + 3. 初始化按键的结构体 + 4. 注册按键 + 5. 启动按键扫描 + +详细的代码如下所示: + +```c +#include +#include "led.h" +#include "button.h" + +#define KEY_PIN 1 +#define KEY_PRESS_VALUE 0 + +void key_cb(struct my_button *button) +{ + switch (button->event) + { + case BUTTON_EVENT_CLICK_UP: + led_toggle(); + rt_kprintf("This is click up callback!\n"); + break; + case BUTTON_EVENT_HOLD_CYC: + rt_kprintf("This is hold cyc callback!\n"); + break; + default: + ; + } +} + +int main(void) +{ + /* user app entry */ + static struct my_button key = {0}; + + led_init(); + + key.press_logic_level = KEY_PRESS_VALUE; + key.hold_cyc_period = 100; + key.cb = (my_button_callback)key_cb; + key.pin = KEY_PIN; + + my_button_register(&key); + my_button_start(); + + return RT_EOK; +} +``` + +![运行结果](figures/run6.jpg) + +## 参考资料 + +* [《PIN 设备》](rt-thread-version/rt-thread-standard/programming-manual/device/pin/pin.md) + +## 程序源码 + +[button.c/.h](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/tree/master/code/button) + +[示例程序](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/blob/master/samples/main_2.c) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/decode.md b/rt-thread-version/rt-thread-standard/tutorial/beep-player/decode.md new file mode 100644 index 0000000000000000000000000000000000000000..807a4111d046d7907711ca6d7f3c449b92e7ff9d --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/beep-player/decode.md @@ -0,0 +1,361 @@ +# 第 4 节:音乐数据的编码与解码 + +## 基础知识 + +一般说来,蜂鸣器演奏音乐只能是单音频率,它不包含相应幅度的谐波频率,也就是说不能象电子琴那样能奏出多种音色的声音。因此蜂鸣器奏乐只需弄清楚两个概念即可,也就是“音调”和“节拍”。音调表示一个音符唱多高的频率,节拍表示一个音符唱多长的时间。十二平均律就规定了每一个音符的标准频率。 + +十二平均律,是一种音乐定律方法,将一个纯[八度](https://baike.baidu.com/item/%E5%85%AB%E5%BA%A6)平均分成十二等份,每等分称为[半音](https://baike.baidu.com/item/%E5%8D%8A%E9%9F%B3),是最主要的调音法。十二平均律中各音的频率: C: 262 Hz、#C: 277 Hz、D: 294 Hz、\#D: 311 Hz、E: 330 Hz、F: 349 Hz、#F: 370 Hz、G: 392 Hz、#G: 415 Hz、A: 440 Hz、#A: 466 Hz、B: 494 Hz + +![钢琴按键](figures/tone1.jpg) + +下面是两只老虎的简谱 + +![两只老虎](figures/timg.jpg) + +其中1=E 表示乐谱的曲调,就是说,“这歌曲唱E调”。那么 1(do) 的频率就为 330 HZ,同样 2 = F,3 = G依次类推。后面 4/4 是用来表示节拍的,它表示乐谱中以四分音符为节拍,每一小结有四拍。比如第二行的第二小节,其中5、6 为一拍,5、4为一拍,3为一拍,1为一拍共四拍。5、6的时值为四分音符的一半,即为八分音符长。 + +> 提示:简谱里音符下面加一道横线表示该音减少一半时值,即该音符为半拍;加两道横线表示该音在减少一半时值基础上再减一半时值,即该音符为四分之一拍。附点音符,时值是原音符的长加上这个音长的一半。 + +那么一拍到底该唱多长呢?一般说来,如果乐曲没有特殊说明,一拍的时长大约为400—500ms 。我们以一拍的时长为400ms为例,则当以四分音符为节拍时,四分音符的时长就为400ms,八分音符的时长就为200ms,十六分音符的时长就为100ms。 + +## 软件实现 + +### 编码的实现 + +有关编码解码的方式,在网上检索到了一个对简谱进行编码的软件 MusicEncode ,然后看了一下他的示例程序,感觉他的编码解码的方式还是可取的,编码的方式如下所示: + +```c + 音高由三位数字组成: + 个位是表示 1~7 这七个音符 + 十位是表示音符所在的音区:1-低音,2-中音,3-高音; + 百位表示这个音符是否要升半音: 0-不升,1-升半音。 + + 音长最多由三位数字组成: + 个位表示音符的时值,其对应关系是: + |数值(n): |0 |1 |2 |3 | 4 | 5 | 6 + |几分音符: |1 |2 |4 |8 |16 |32 |64 音符=2^n + 十位表示音符的演奏效果(0-2): 0-普通,1-连音,2-顿音 + 百位是符点位: 0-无符点,1-有符点 +``` + +将上面两只老虎的简谱编码成16进制的数据如下: + +```c + 0x15, 0x02, 0x16, 0x02, 0x17, 0x02, 0x15, 0x02, 0x15, 0x02, + // 1 2 3 1 1 + 0x16, 0x02, 0x17, 0x02, 0x15, 0x02, 0x17, 0x02, 0x18, 0x02, + // 2 3 1 3 4 + 0x19, 0x01, 0x17, 0x02, 0x18, 0x02, 0x19, 0x01, 0x19, 0x03, + // 5 - 3 4 5 - 5_ + 0x1A, 0x03, 0x19, 0x03, 0x18, 0x03, 0x17, 0x02, 0x15, 0x16, + // 6_ 5_ 4_ 3 1 + 0x19, 0x03, 0x1A, 0x03, 0x19, 0x03, 0x18, 0x03, 0x17, 0x02, + // 5_ 6_ 5_ 4_ 3 + 0x15, 0x16, 0x15, 0x02, 0x0F, 0x02, 0x15, 0x01, 0x15, 0x02, + // 1 1 5. 1 - 1 + 0x0F, 0x02, 0x15, 0x01, 0x00, 0x00 + // 5. 1 - +``` + +确定了编码的方式之后,还要考虑如何将歌曲名和它的编码之后的数据对应起来。可以定义一个 song 的结构体,里面包含 name 和 data 两个成员,这样就可以将歌曲名和对应的数据联系起来了。 + +我们新建一个 `decode.h` 文件在里面定义如下的结构体,当然还要在文件的头部添加宏的判断,以及包含 `rtthread.h` 头文件。 + +```c +struct beep_song +{ + const uint8_t name[SONG_NAME_LENGTH_MAX]; + const uint8_t data[SONG_DATA_LENGTH_MAX]; +}; +``` + +这里的 歌曲名字的长度和数据的大小 用两个宏来表示,定义如下: + +```c +#define SONG_NAME_LENGTH_MAX 30 +#define SONG_DATA_LENGTH_MAX 500 +``` + +### 解码的实现 + +#### 获取歌曲长度 + +首先要实现一个解析歌曲长度的函数,供其他模块使用(我们用音符的个数表示为歌曲的长度)。新建一个 `decode.c` 文件,添加 `decode.h` 头文件,然后添加如下代码。并在 `decode.h` 头文件里放一个这个函数的函数声明,供其他文件调用。 + +```c +uint16_t beep_song_get_len(const struct beep_song *song) +{ + uint16_t cnt = 0; + + /* 歌曲以0x00 0x00结尾 检测结束标志*/ + while (song->data[cnt]) + { + cnt += 2; + } + return cnt / 2; +} +``` + +#### 获取歌曲名 + +一个获取歌曲名的 API 也是必不可少的,实现如下函数,返回歌曲的名称。当然,同样需要将这个函数在.h文件里声明,供其他文件调用。 + +``` +int beep_song_get_name(const struct beep_song *song, char *name) +{ + int i=0; + while(song->name[i]) + { + name[i] = song->name[i]; + i++; + } + name[i] = '\0'; + return 0; +} +``` + +#### 解码前的准备 + +解码就是将编码的数据提取出来,然后转变成频率和音长的过程。解码的思路是,先根据音符在频率表里确定它的频率,然后再根据是几分音符以及演奏效果计算出音符的时值。 + +**频率** + +首先要定义出音高的频率表,定义如下: + +```c +static const uint16_t freq_tab[12] = {262, 277, 294, 311, 330, 349, 369, 392, 415, 440, 466, 494}; //频率表 CDEFGAB +``` + +在获取频率时会有一个问题,那就是音符的 1234567 对应的频率并不是连续的。他们之间有的只差一个半音,有的差一个全音,因此可以定义一个音符的表,里面标识各个音符在频率表中的位置,定义如下: + +```c +static const uint8_t sign_tab[7] = {0, 2, 4, 5, 7, 9, 11}; //1~7在频率表中的位置 +``` + +这样就可以根据音符获取到对应的频率信息了,比如获取音符 3(mi) 的频率,就可以这么获取`freq_tab[sign_tab[3-1]]`,如果要升半 #3 就是 `freq_tab[sign_tab[3-1]+1]` 。 + +**时值** + +还有就是时值的获取,可以先设定四分音符的时值为 400ms,则二分音符的时值时 800ms,全音符的时值 1600ms,可以依次计算出八分音符的时值 200ms,16分音符的时值 100ms。这样可以将全音符用宏来定义出来,然后其他音符的时值,再除以相应的倍数即可。定义全音符的长度为 `#define SEMIBREVE_LEN 1600` 则,四分音符为 `SEMIBREVE_LEN/4` 。 + +接着根据效果处理时值数据,如连音就演奏整个时值,顿音就只演奏原时值的 1/2,普通音符也不能演奏整个时值,需要乘以一个长度分率,这里是 `#define SOUND_SPACE 4/5 `。这样不同音符的时值就计算出来了。 + +**调号的调节** + +演奏音乐还要能够根据需要调节乐谱的调号。因为蜂鸣器的音域较窄,而且只有在高音的区域播放的音乐才比较好听,所以需要对乐谱的播放进行调号的调节。可以通过调整音符对应的频率表来调整乐谱的调号,根据新的调号生成一个新的频率表,然后根据新的频率表来播放,就可以达到调节调号的要求了。以 1(do) 的频率为例: 升1个半音的话,就由 262HZ 变为了 277HZ;升一个八度,就变成了 262 * 2 = 524 HZ。新的频率表定义如下: + +```c +static rt_uint16_t freq_tab_new[12]; //新的频率表 +``` + +写成具体的函数如下所示: + +```c +//signature|调号(0-11) : 是指乐曲升多少个半音演奏; +//octachord|升降八度(-2到+2) : < 0 降几个八度; > 0 升几个八度 +static int beep_song_decode_new_freq(rt_uint8_t signature, rt_int8_t octachord) +{ + uint8_t i, j; + for (i = 0; i < 12; i++) // 根据调号及升降八度来生成新的频率表 + { + j = i + signature; + + if (j > 11) //升完之后超出本组基本音级,就跳到下一组音级 + { + j = j - 12; + freq_tab_new[i] = freq_tab[j] * 2; + } + else + { + freq_tab_new[i] = freq_tab[j]; + } + + /* 升降八度 */ + if (octachord < 0) + { + freq_tab_new[i] >>= (- octachord); + } + else if (octachord > 0) + { + freq_tab_new[i] <<= octachord; //每升一个八度 频率就翻一倍 + } + } + return 0; +} +``` + +#### 解码函数 + +解码的过程也可以抽象成一个函数,传入的参数是编码后的数据(音高、音长数据),返回的结果是解码出来的音符对应的频率,以及音符的时值。为了方便蜂鸣器播放,我们把时值分成两个数据表示,`发声的时间`和`不发声的时间`。我们创建如下一个解码函数。 + +```c +int beep_song_decode(rt_uint16_t tone, rt_uint16_t length, rt_uint16_t *freq, rt_uint16_t *sound_len, rt_uint16_t *nosound_len); //解码音乐数据 +``` + +具体的实现如下: + +```c +int beep_song_decode(rt_uint16_t tone, rt_uint16_t length, rt_uint16_t *freq, rt_uint16_t *sound_len, rt_uint16_t *nosound_len) +{ + static const rt_uint16_t div0_len = SEMIBREVE_LEN; // 全音符的长度(ms) + rt_uint16_t note_len, note_sound_len, current_freq; + rt_uint8_t note, sharp, range, note_div, effect, dotted; + + note = tone % 10; //计算出音符 + range = tone / 10 % 10; //计算出高低音 + sharp = tone / 100; //计算出是否升半 + current_freq = freq_tab_new[sign_tab[note - 1] + sharp]; //查出对应音符的频率 + + if (note != 0) + { + if (range == 1) current_freq >>= 1; //低音 降八度 + if (range == 3) current_freq <<= 1; //高音 升八度 + *freq = current_freq; + } + else + { + *freq = 0; + } + note_div = length_tab[length % 10]; //算出是几分音符 + effect = length / 10 % 10; //算出音符类型(0普通1连音2顿音) + dotted = length / 100; //算出是否附点 + note_len = div0_len / note_div; //算出音符的时长 + + if (dotted == 1) + note_len = note_len + note_len / 2; + + if (effect != 1) + { + if (effect == 0) //算出普通音符的演奏长度 + { + note_sound_len = note_len * SOUND_SPACE; + } + else //算出顿音的演奏长度 + { + note_sound_len = note_len / 2; + } + } + else //算出连音的演奏长度 + { + note_sound_len = note_len; + } + if (note == 0) + { + note_sound_len = 0; + } + *sound_len = note_sound_len; + *nosound_len = note_len - note_sound_len; //算出不发音的长度 + + return 0; +} +``` + +但是这样组织数据的方式太散了,这么多参数,也不方便其他函数调用。可以定义一个蜂鸣器能够直接用来播放的数据结构。如下,将频率以及发声的时长和不发声的时长封装了起来。 + +``` +struct beep_song_data +{ + rt_uint16_t freq; + rt_uint16_t sound_len; + rt_uint16_t nosound_len; +}; +``` + +同样,可以将这个解码函数再封装一层,屏蔽掉具体的解码细节。如下所示,传入歌曲的数据以及要获得的音符的序号,可直接返回供蜂鸣器播放的数据,方便了其他模块的使用。 + +``` +uint16_t beep_song_get_data(const struct beep_song *song, uint16_t index, struct beep_song_data *data) +``` + +具体实现如下 + +``` +uint16_t beep_song_get_data(const struct beep_song *song, uint16_t index, struct beep_song_data *data) +{ + beep_song_decode(song->data[index * 2],song->data[index * 2+1],&data->freq,&data->sound_len,&data->nosound_len); + + return 2; +} +``` + +#### 解码器初始化 + +在解码器的初始化函数里完成调节调号的过程。定义两个宏作为基础的调号 。 + +```c +#define SOUND_SIGNATURE 0 //调号:升0个半音 +#define SOUND_OCTACHORD 1 //升降八度:升一个八度 +``` + +然后才 `decode.c` 里实现下面的函数,并把函数声明放到 `.h` 文件里,供外部文件调用。 + +```c +int beep_song_decode_init(void) +{ + beep_song_decode_new_freq(SOUND_SIGNATURE, SOUND_OCTACHORD); + return 0; +} +``` + +## 示例程序 + +我们可以在main.c里包含 `beep.h` 以及 `decode.h` 两个头文件,然后在循环函数里解码并播放一首歌曲。详细代码如下。 + +```c +#include +#include "beep.h" +#include "decode.h" + +const struct beep_song song1 = +{ + .name = "两只老虎", + .data = { + 0x15, 0x02, 0x16, 0x02, 0x17, 0x02, 0x15, 0x02, 0x15, 0x02, + 0x16, 0x02, 0x17, 0x02, 0x15, 0x02, 0x17, 0x02, 0x18, 0x02, + 0x19, 0x01, 0x17, 0x02, 0x18, 0x02, 0x19, 0x01, 0x19, 0x03, + 0x1A, 0x03, 0x19, 0x03, 0x18, 0x03, 0x17, 0x02, 0x15, 0x16, + 0x19, 0x03, 0x1A, 0x03, 0x19, 0x03, 0x18, 0x03, 0x17, 0x02, + 0x15, 0x16, 0x15, 0x02, 0x0F, 0x02, 0x15, 0x01, 0x15, 0x02, + 0x0F, 0x02, 0x15, 0x01, 0x00, 0x00 + } +}; + +int main(void) +{ + /* user app entry */ + struct beep_song_data data; + int len, i; + char name[20]; + + beep_init(); + beep_song_decode_init(); + beep_song_get_name(&song1, name); + rt_kprintf("正在播放:%s\n",name); + len = beep_song_get_len(&song1); + while (i < len) + { + /* 解码音乐数据 */ + beep_song_get_data(&song1, i, &data); + beep_set(data.freq, 3); + beep_on(); + + rt_thread_mdelay(data.sound_len); + + beep_off(); + rt_thread_mdelay(data.nosound_len); + i++; + } + + return 0; +} +``` + +连接好蜂鸣器,下载并运行程序就可以听到通过蜂鸣器播放的两只老虎了! + +![运行结果](figures/run3.png) + +## 程序源码 + +[decode.c/.h](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/tree/master/code/decode) + +[示例程序](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/blob/master/samples/main_4.c) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/Kconfig.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/Kconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..6393d8319ecc09bbc36347b9ac280e50a8df1ad8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/Kconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/Kconfig2.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/Kconfig2.png new file mode 100644 index 0000000000000000000000000000000000000000..e8507020b1a3d60ae0c91d21c8f6bf138970419d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/Kconfig2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/com.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/com.png new file mode 100644 index 0000000000000000000000000000000000000000..cf3a9b0a9360afaf18658bdc4705562e2b26a481 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/com.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/github.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/github.png new file mode 100644 index 0000000000000000000000000000000000000000..e445fc13cb1a18d1485aef14770868762b4d20c1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/github.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_key.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_key.png new file mode 100644 index 0000000000000000000000000000000000000000..a751f754cef1ca8c4a927e6f21b952dcb0611d26 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_key.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_led.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_led.png new file mode 100644 index 0000000000000000000000000000000000000000..46280d88e4d98fc8061cc912e3f220fec9e9fc0b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_led.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_png1.jpg b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_png1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..63fb13a9659ce82e94d7873930df88a4b47ee3ec Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_png1.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_pwm.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_pwm.png new file mode 100644 index 0000000000000000000000000000000000000000..09f3c366d438643ce06b549ab4b2a4c3b4d39209 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/hw_pwm.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/led_run.jpg b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/led_run.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7f8b1c4c0606e2366cb365571a08ea2b8d6a0fd3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/led_run.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/multi_button1.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/multi_button1.png new file mode 100644 index 0000000000000000000000000000000000000000..f1c0fbef88fe5a6b333512fc3d3292a3e63a02f9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/multi_button1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/palyer-thread.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/palyer-thread.png new file mode 100644 index 0000000000000000000000000000000000000000..5669b8bee9b757ca4c61d39b2296d0e219808136 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/palyer-thread.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/pwm_ch1.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/pwm_ch1.png new file mode 100644 index 0000000000000000000000000000000000000000..013dae9dad39e2c31e736c5d155b0219bc4f503c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/pwm_ch1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/pwm_env.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/pwm_env.png new file mode 100644 index 0000000000000000000000000000000000000000..bf5d747c43cc5189ab4c8ee19fd9281eb2d86e0f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/pwm_env.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run3.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run3.png new file mode 100644 index 0000000000000000000000000000000000000000..19118cf66085cb57ad99bf0db047389efe9d1c6e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run4.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run4.png new file mode 100644 index 0000000000000000000000000000000000000000..918f11aa94bf746b11cd6983836485dae9570c13 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run5.jpg b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4e918c03fdf4457d3b73c28fca9b15ff82b4ff4d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run5.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run6.jpg b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..40a642f433392d5cc1d9d19f8382266d1665ddac Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/run6.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/sha.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/sha.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8ab16b21581890de8768507d9c7385acc8c179 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/sha.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/timg.jpg b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/timg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0e8f67ab447216427e6fa402d14f3981ee7cfc63 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/timg.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/tone1.jpg b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/tone1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4cabe1325668206b0a7959a7f81ca3ddadcb033b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/tone1.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/tuopu.png b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/tuopu.png new file mode 100644 index 0000000000000000000000000000000000000000..34b53cf8e96c2a8eb4a0c051f72d2b470722eaf0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/beep-player/figures/tuopu.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/key.md b/rt-thread-version/rt-thread-standard/tutorial/beep-player/key.md new file mode 100644 index 0000000000000000000000000000000000000000..14f6295e28556714d219f58243443e71e0b1b596 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/beep-player/key.md @@ -0,0 +1,290 @@ +# 第 6 节:为播放器添加按键控制 + +上一节的示例程序,演示了播放器内核的基础使用,但是只能循环播放,不能控制的播放器叫什么播放器。因此,这一节我们使用之前写的 button 按键库驱动按键,并实现按键控制播放器的行为:上一曲、下一曲、暂停/播放,音量调节。 + +## 硬件连接 + +以正点原子探索者 STM32F4 开发板为例。开发板上有四个供用户使用的按键 KEY_UP/KEY0/1/2,通过原理图可以看出,KEY0/1/2 三个按键对应的 PIN 分别是 1/2/3。 + +![原理图](figures/hw_key.png) + +## 软件实现 + +要做一个音乐播放器,需要有什么按键的功能呢?暂停/播放,上一曲,下一曲,音量加/减。要实现这几个按键的功能,需要借助 button 驱动库的单击事件和长按事件。 + +根据按键在开发板上的排布,我们使用左边的 KEY2 作为`上一曲`的按键,长按为`音量减`;右边的按键 KEY0 作为`下一曲`的按键,长按为`音量加`;中间的按键 KEY1 作为 `暂停/播放` 的按钮。 + +为了使按键处理的代码更清晰,使用按键回调的方式处理按键事件,当有按键事件发生时,直接调用对应的事件回调函数处理。这样所有的业务代码都将在模块内部实现,只需要一个初始化函数供外部调用即可。 + +```c +int key_init(void); //按键初始化 +``` + +同样我们新建一个 `key.h` 的文件,将按键初始化函数声明放到 `key.h` 文件里,方便其他文件的调用。 + +然后再新建一个 `key.c` 文件,在文件里添加下面的头文件 + +```c +#include //RT-Thread 标准头文件 +#include //使用 RT-Thread 的设备需要包含此头文件 +#include "button.h" //之前实现的按键驱动库 +#include "key.h" +``` + +为了程序更好的可移植性,把 key 使用的 PIN 设备用宏定义的方式来表示,如下所示: + +```c +#define KEY_PLAY_PIN 2 +#define KEY_LAST_PIN 1 +#define KEY_NEXT_PIN 3 +#define KEY_PRESS_LEVEL 0 +``` + +先为三个按键申请三个按键结构体 + +```c +static struct my_button btn_last, btn_next, btn_play; +``` + +然后需要写这三个函数的按键事件回调函数。 + +由于回调函数会返回按键的结构体,可以获取到按键的 pin 编号,所以可以把三个按键的回调函数统一成一个,然后在具体的处理函数里根据 pin 的不同分别处理。根据按键事件的不同,可以将按键回调处理函数分为短按处理函数和长按处理函数。 + +```c +void btn_cb(struct my_button *button) +{ + switch (button->event) + { + case BUTTON_EVENT_CLICK_UP: + beep_key_press_short(button->pin); + break; + case BUTTON_EVENT_HOLD_CYC: + beep_key_press_long(button->pin); + break; + default: + ; + } +} +``` + + +### 短按处理函数 + +在短按处理函数里判断不同的按键,然后处理不同的事件,如果是上一曲的短按事件,就切换为上一曲,如果是下一曲的按键就切换为下一曲等。由于使用的 player 结构体定义在 mian.c 里,因此我们要定义一个外部变量,`extern struct player player;` 然后才能使用 player 的 API 控制播放器的行为。实现代码如下: + +```c +extern struct player player; + +static void beep_key_press_short(rt_uint32_t pin) +{ + switch (pin) + { + case KEY_PLAY_PIN: + /* 根据当前播放状态切换播放状态 */ + if (player.status == PLAYER_RUNNING) + { + player_control(&player, PLAYER_CMD_STOP, RT_NULL); + } + else + { + player_control(&player, PLAYER_CMD_PLAY, RT_NULL); + } + + /*打印一次播放状态*/ + player_show(&player); + break; + case KEY_LAST_PIN: + player_control(&player, PLAYER_CMD_LAST, RT_NULL); + + /*打印一次播放状态*/ + player_show(&player); + break; + case KEY_NEXT_PIN: + player_control(&player, PLAYER_CMD_NEXT, RT_NULL); + + /*打印一次播放状态*/ + player_show(&player); + break; + } +} +``` + +### 长按回调函数 + +在长按处理函数里同样判断不同的按键,然后处理不同的事件,如果是上一曲的长按周期回调事件,就减小音量,如果是下一曲的长按周期回调事件就加到音量等。 + +```c +static void btn_next_cb(void *btn) +{ + uint32_t btn_event_val; + uint8_t volume; + + btn_event_val = get_button_event((struct my_button *)btn); + + switch (btn_event_val) + { + case SINGLE_CLICK: + player_control(&player, PLAYER_CMD_NEXT, RT_NULL); + + /*打印一次播放状态*/ + player_show(&player); + break; + case LONG_PRESS_HOLD: + player_control(&player, PLAYER_CMD_GET_VOL, &volume); + if (volume < 99) + { + volume++; + player_control(&player, PLAYER_CMD_SET_VOL, &volume); + } + break; + } +} +``` + +写完回调函数函数之后就要进行按键的初始化和绑定回调事件了。 + +### 初始化按键 + +我们在初始化函数里初始化按键结构体,并注册按键的回调函数,并启动按键。 + +启动完按键之后还需要周期性调用按键扫描的函数,这里时通过创建了一个定时器的形式,进行循环调用的。 + +```c +int key_init(void) +{ + btn_last.press_logic_level = KEY_PRESS_LEVEL; + btn_last.hold_cyc_period = 100; + btn_last.cb = (my_button_callback)btn_cb; + + btn_next = btn_play = btn_last; + + btn_last.pin = KEY_PLAY_PIN; + btn_play.pin = KEY_LAST_PIN; + btn_next.pin = KEY_NEXT_PIN; + + my_button_register(&btn_last); + my_button_register(&btn_play); + my_button_register(&btn_next); + my_button_start(); + + return 0; +} +``` + +## 示例程序 + + 这一节的示例程序和上一节的基本一样,只是加一个 `key.h` 的头文件,并在main 函数里调用一下按键的初始化函数即可。为了实现一个完整的播放器,我们再为这个播放器添加上一个状态指示灯。目的是在播放器播放时,灯会一直闪烁;暂停播放时,灯就熄灭了。要完成这个功能,就需要改变一下音频设备的接口,将led 看作音频设备的一部分,音频设备开的时候,led 也打开 ,音频设备关的时候 led 也关闭 ,音频设备在播放的时候,led 开始闪烁等。示例代码如下: + +```c +#include + +#include "player.h" +#include "song_data.h" +#include "beep.h" +#include "decode.h" +#include "key.h" +#include "led.h" + +struct player player; +struct audio_ops audio; +struct decode_ops decode; + +uint8_t beep_volume = 3; + +/* 解码器的读操作接口 */ +int decode_read(void *song, int index, void *buffer, int size) +{ + beep_song_get_data(song, index, buffer); + /* 返回歌曲进度的增量 */ + return 1; +} +/* 解码器的控制操作接口 */ +int decode_control(void *song, int cmd, void *arg) +{ + if (cmd == DECODE_OPS_CMD_GET_NAME) + beep_song_get_name(song, arg); + else if (cmd == DECODE_OPS_CMD_GET_LEN) + *(uint16_t *)arg = beep_song_get_len(song); + return 0; +} +int audio_init(void) +{ + beep_init(); + led_init(); + return 0; +} +int audio_open(void) +{ + beep_on(); + led_on(); + return 0; +} +int audio_close(void) +{ + beep_off(); + led_off(); + return 0; +} +int audio_control(int cmd, void *arg) +{ + if (cmd == AUDIO_OPS_CMD_SET_VOL) + beep_volume = *(uint8_t *)arg; + return beep_volume; +} +int audio_write(void *buffer, int size) +{ + struct beep_song_data *data = buffer; + + led_toggle(); + + beep_on(); + beep_set(data->freq, beep_volume); + rt_thread_mdelay(data->sound_len); + beep_off(); + rt_thread_mdelay(data->nosound_len); + + return size; +} + +int player_init(void) +{ + decode.init = beep_song_decode_init; + decode.control = decode_control; + decode.read = decode_read; + + audio.init = audio_init; + audio.open = audio_open; + audio.close = audio_close; + audio.control = audio_control; + audio.write = audio_write; + + player.decode = &decode; + player.audio = &audio; + + player_add_song(&player, (void *)&song1); + player_add_song(&player, (void *)&song2); + player_add_song(&player, (void *)&song3); + player_add_song(&player, (void *)&song4); + player_start(&player); + + player_control(&player, PLAYER_CMD_PLAY, RT_NULL); + player_show(&player); + + return 0; +} +int main(void) +{ + /* user app entry */ + player_init(); + key_init(); + return 0; +} +``` + +![运行结果](figures/run5.jpg) + +## 程序源码 + +[key.c/.h](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/tree/master/code/key) + +[示例程序](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/blob/master/samples/main_6.c) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/pin.md b/rt-thread-version/rt-thread-standard/tutorial/beep-player/pin.md new file mode 100644 index 0000000000000000000000000000000000000000..4d9108831b3bb3d34169f445c97adc4570ec5d69 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/beep-player/pin.md @@ -0,0 +1,174 @@ +# 第 1 节:利用 PIN 设备控制 LED + +## 基础知识 + +为了给用户提供操作 GPIO 的通用 API,方便应用程序开发,RT-Thread 中引入了通用 GPIO设备驱动。并提供类似 Arduino 风格的 API 用于操作 GPIO,如设置 GPIO 模式和输出电平、读取 GPIO 输入电平、配置 GPIO 外部中断等。 + +常用的 PIN 设备的 API 有以下几个: + +| API | 描述 | +| ----------------- | -------------------------- | +| rt_pin_mode | 设置 GPIO 模式 | +| rt_pin_write | 输出电平 | +| rt_pin_read | 读取 GPIO 输入电平 | +| rt_pin_attach_irq | 挂载 GPIO 外部中断回调函数 | +| rt_pin_detach_irq | 脱离 GPIO 外部中断回调函数 | +| rt_pin_irq_enable | 配置 GPIO 外部中断开关 | + +如果只是控制 LED 的亮灭只需要使用 `rt_pin_mode/write/read` 这三个函数即可。 + +- 使用 rt_pin_mode 函数将驱动 LED 的 IO 口初始化为推挽输出模式, +- 使用 rt_pin_write 函数来控制 IO 口的电平高低, +- 使用 rt_pin_read 函数来读取 IO 口的电平高低。 + +## 硬件连接 + +本教程使用 正点原子 STM32F4 探索者 作为演示平台, 在开发板上有一个红色的 LED 灯,我们使用这颗 LED 作为播放器的状态指示灯,硬件的连接如下图所示,其对应的 PIN 脚为 21: + +> 提示:注意:一般情况下如果芯片的选择正确的话,这里的 PIN 的编号 21 和芯片上对应的 PIN 脚是一样的。但是有时可能会不一样,需要去 drv_pin/drv_gpio.c 里检查一下,GPIOF9 对应的 PIN 编号是不是 21。 + +![硬件连接](figures/hw_led.png) + +下面详细介绍一下软件的实现。 + +## 软件实现 + +LED 模块是最简单的一个模块,它只需要有几个API 即可,像初始化、开/关、翻转即可。 + +```c +int led_init(void); //LED 灯初始化 +int led_on(void); //LED 灯亮 +int led_off(void); //LED 灯灭 +int led_toggle(void); //LED 灯亮灭状态翻转 +``` + +然后就只要使用这几个 API 就可以控制 LED 的全部行为。 + +我们新建一个 `led.h` 的文件,将上面的这些函数声明放到 `led.h` 文件里,方便其他文件的调用。 + +为了防止头文件被重复包含,一般需要在文件的头部添加宏的判断,如下所示: + +``` +#ifndef LED_H +#define LED_H +...头文件的具体内容... +#endif +``` + +然后再新建一个 `led.c` 文件,在文件里添加下面的头文件 + +``` +#include //使用 RT-Thread pin 设备需要包含此头文件 +#include "led.h" //对应的头文件 +``` + +为了程序更好的可移植性,把 LED 使用的管脚用宏定义的方式来表示,如下所示: + +``` +#define LED_PIN 21 +``` + +然后在文件 `led.c` 里分别实现上面的几个 API。 + +### LED 初始化 + +```c +int led_init(void); //LED 灯初始化 +``` + +在 LED 初始化函数里,需要完成 PIN 设备的配置,将 PIN 设备设定成输出模式。代码如下所示: + +```c +int led_init(void) +{ + /* 设定 LED 引脚为推挽输出模式 */ + rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); + return 0; +} +``` + +### LED 亮 + +```c +int led_on(void); //LED 灯亮 +``` + +假设 LED 低电平亮,调用 API 输出低电平,驱动 LED。 + +```c +int led_on(void) +{ + /* 调用 API 输出低电平 */ + rt_pin_write(LED_PIN, PIN_LOW); + return 0; +} +``` + +### LED 灭 + +```c +int led_off(void); //LED 灯灭 +``` + +假设 LED 高电平灭,调用 API 输出低电平,使 LED 熄灭。 + +```c +int led_off(void) +{ + /* 调用 API 输出高电平 */ + rt_pin_write(LED_PIN, PIN_HIGH); + return 0; +} +``` + +### LED 状态翻转 + +```c +int led_toggle(void); //LED 灯亮灭状态翻转 +``` + +要使 LED 状态翻转应先利用API `rt_pin_read` 读出当前电平,然后输出相反电平 + +```c +int led_toggle(void) +{ + /* 调用 API 读出当前电平 然后输出相反电平 */ + rt_pin_write(LED_PIN, !rt_pin_read(LED_PIN)); + return 0; +} +``` + +## 示例程序 + +这样就可以在 `main.c` 里包含 `led.h` 头文件,然后使用这些 API 来控制 LED 灯的亮灭了。 + +下面是一段控制 LED 灯每隔 500 ms 闪烁一次的函数,运行的结果如下图所示。 + +```c +#include +#include "led.h" + +int main(void) +{ + /* user app entry */ + led_init(); + led_on(); + while (1) + { + led_toggle(); + rt_thread_mdelay(500); + } +} +``` + +![运行结果](figures/led_run.jpg) + +## 参考资料 + +* [《PIN 设备》](../../programming-manual/device/pin/pin.md) + +## 程序源码 + +[led.c/.h](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/tree/master/code/led) + +[示例程序](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/blob/master/samples/main_1.c) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/player.md b/rt-thread-version/rt-thread-standard/tutorial/beep-player/player.md new file mode 100644 index 0000000000000000000000000000000000000000..3b6caa0aa62320e144ac3aa13ae051dc3b6a791c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/beep-player/player.md @@ -0,0 +1,738 @@ +# 第 5 节:播放器的实现 + +这一节的任务是实现一个播放器的内核,主要就是实现:播放列表,暂停/播放、上一曲、下一曲,音量调节等功能。 + +## 播放器设计 + +为了实现这些功能,我们可以先创建一个结构体来存储播放器的信息及其当前的状态,例如:播放列表、当前的播放状态、音量大小等,如下所示:播放状态只有两种状态,正在播放和播放停止;`PLAYER_SONG_NUM_MAX` 默认定义为 10 首。 + +```c +enum PLAYER_STATUS +{ + PLAYER_RUNNING, //正在播放 + PLAYER_STOP //播放停止 +}; +struct player +{ + enum PLAYER_STATUS status; //当前播放状态 + uint8_t volume; //声音大小 + uint8_t song_current; //正在播放的歌曲 + uint8_t song_num; //歌曲总数 + uint16_t song_time_pass; //已经播放的时间 + uint16_t song_time_all; //总播放时间 + void *song_sheet[PLAYER_SONG_NUM_MAX]; //歌曲列表 +}; +``` + +我们设计好了存储播放器当前状态信息的结构体之后,只要控制这个结构体就可以控制播放器的行为了。例如:改变播放器的状态 `status`,就可以控制播放的暂停和播放;改变正在播放的歌曲 `song_current`,就可以完成上一曲和下一曲的切换等。 + +要想播放音乐还要有解码器和音频设备,没有这两部分,仅有播放器是不能播放歌曲的。由于播放器是不知道如何解码,以及播放音乐的,因此只能留出一些操作接口,供解码器和音频设备实现。可以定义如下的操作接口, + +```c +struct audio_ops +{ + int (*init)(void); + int (*open)(void); + int (*close)(void); + int (*control)(int cmd, void *arg); + int (*write)(void *buffer, int size); +}; +struct decode_ops +{ + int (*init)(void); + int (*control)(void *song, int cmd, void *arg); + int (*read)(void *song, int index, void *buffer, int size); +}; +``` + +然后定义一些命令字,可以操作解码设备和音频设备。暂时先定义这些命令字,有需要时可以再添加。 + +```c +enum AUDIO_OPS_CMD +{ + AUDIO_OPS_CMD_SET_VOL +}; +enum DECODE_OPS_CMD +{ + DECODE_OPS_CMD_GET_NAME, + DECODE_OPS_CMD_GET_LEN +}; +``` + +然后播放器直接使用这些接口来控制解码和播放音频。 + +要想播放音乐,还需要有一个线程来播放音乐。这个播放线程的主要任务就是:从播放列表里获取音乐的数据,然后送到解码器解码,接着根据解码之后的数据来控制蜂鸣器的行为,播放音乐。 + +这个播放线程应该满足下面的条件,它要能时刻获取播放器的状态,当检测到播放器的播放状态变为停止状态时,能立即停止播放,然后阻塞住自己,等待播放信号的到来。当播放信号到来之后,可以重新进行播放。 + +怎么才能实现让播放线程挂起,然后在接收到一个信号之后再继续运行呢,这就可以利用信号量来实现。也就是当播放线程检测到播放器的状态改变之后就通过获取信号量挂起自身,然后等待开始播放的信号量,获取到信号量之后,播放线程才重新开始运行。 + +我们将播放器需要的线程、信号量、解码和音频操作接口都放到 player 的结构体里,player 结构体就变成了下面的结构: + +```c +struct player +{ + enum PLAYER_STATUS status; //当前播放状态 + uint8_t volume; //声音大小 + uint8_t song_current; //正在播放的歌曲 + uint8_t song_num; //歌曲总数 + uint16_t song_time_pass; //已经播放的时间 + uint16_t song_time_all; //总播放时间 + void *song_sheet[PLAYER_SONG_NUM_MAX]; //歌曲列表 + + rt_sem_t sem_play; //用于播放状态控制的信号量 + rt_thread_t play_thread; //播放的线程 + + struct audio_ops *audio; + struct decode_ops *decode; +}; +typedef struct player *player_t; +``` + +这个播放线程大体的流程如下所示: + +![播放线程](figures/palyer-thread.png) + + +## 软件实现 + +### 播放线程 + +播放线程具体的代码如下,其中 PLAYER_BUFFER_SIZE 默认定义为 20。 + +```c +static void player_entry(void *parameter) +{ + player_t player = (player_t)parameter; + uint8_t buffer[PLAYER_BUFFER_SIZE], size; + + while (1) + { + if (player->status == PLAYER_RUNNING) + { + size = player->song_time_all - player->song_time_pass; + if (size > PLAYER_BUFFER_SIZE) size = PLAYER_BUFFER_SIZE; + size = player->decode->read(player->song_sheet[player->song_current - 1], player->song_time_pass, buffer, size); + if (size > 0) + { + player->audio->write(buffer, size); + player->song_time_pass += size; + } + /* 如果播放时间到了,切换到下一首 */ + if (player->song_time_pass >= player->song_time_all) + { + player_next(player); + player_show(player); + } + } + else + { + /* 暂停播放时关闭音频设备*/ + player->audio->close(); + + /* 等待播放的信号量 */ + rt_sem_take(player->sem_play, RT_WAITING_FOREVER); + + /* 开始播放时打开音频设备*/ + player->audio->open(); + } + } +} +``` + +当然一个播放器,肯定需要一些控制播放的 API 如下: + +```c +int player_play(player_t player); //开始播放 +int player_stop(player_t player); //停止播放 +int player_last(player_t player); //上一曲 +int player_next(player_t player); //下一曲 +``` + +### 开始播放 + +在开始播放的函数实现里,要将播放器的播放状态改为正在播放。然后释放一个信号量通知播放线程,开始播放。 + +注意:当我们要改变一个全局变量的值的时候,一定要注意互斥访问,就是不能让两个函数同时设定一个全局变量的值这样的事情发生。互斥访问的实现方法有很多,信号量,互斥锁,调度器锁,关中断等方法都可以保证资源的互斥访问,如果只是保护像**设定一个变量的值**这样一条语句或简单的几条语句这样的情况,可以直接使用关中断的方式。具体实现如下: + +```c +int player_play(player_t player) +{ + rt_uint32_t level; + + if (player->status != PLAYER_RUNNING) + { + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + + /* 设定播放状态为播放状态 */ + player->status = PLAYER_RUNNING; + + /* 打开全局中断 */ + rt_hw_interrupt_enable(level); + + /*释放信号量通知播放线程开始播放*/ + rt_sem_release(player->sem_play); + } + + return 0; +} + +``` + +### 停止播放 + +在停止播放的函数实现里,只需要将播放器的播放状态改为停止播放即可。具体实现如下: + +```c +int player_stop(player_t player) +{ + rt_uint32_t level; + + if (player->status == PLAYER_RUNNING) + { + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + + /* 设定播放状态为播放状态 */ + player->status = PLAYER_STOP; + + /* 打开全局中断 */ + rt_hw_interrupt_enable(level); + } + + return 0; +} +``` + +### 上一曲 + +在切换上一曲的函数里,将当前播放歌曲的序号减一,然后更新一下结构体里的相关信息。如果当前播放器的状态不是正在播放的话,就调用一次开始播放函数。 + +```c +int player_last(player_t player) +{ + uint16_t len; + rt_uint32_t level; + + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + + /* 将当前播放歌曲的序号 减一 */ + if (player->song_current > 1) + { + player->song_current --; + } + else + { + player->song_current = player->song_num; + } + + /* 更新播放器的当前歌曲的播放时间以及总时间 */ + player->song_time_pass = 0; + + /* 打开全局中断 */ + rt_hw_interrupt_enable(level); + + player->decode->control(player->song_sheet[player->song_current - 1], DECODE_OPS_CMD_GET_LEN, &len); + + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + + player->song_time_all = len; + + /* 打开全局中断 */ + rt_hw_interrupt_enable(level); + + if (player->status != PLAYER_RUNNING) + { + player_play(player); + } + + return 0; +} +``` + +### 下一曲 + +在切换下一曲的函数里,和上一曲的操作相同,只不过是将当前播放歌曲的序号加一。 + +```c +int player_next(player_t player) +{ + uint16_t len; + rt_uint32_t level; + + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + + /* 将当前播放歌曲的序号 加一 */ + if (player->song_current < player->song_num) + { + player->song_current ++; + } + else + { + player->song_current = 1; + } + + /* 更新播放器的当前歌曲的播放时间以及总时间 */ + player->song_time_pass = 0; + + /* 打开全局中断 */ + rt_hw_interrupt_enable(level); + + player->decode->control(player->song_sheet[player->song_current - 1], DECODE_OPS_CMD_GET_LEN, &len); + + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + + player->song_time_all = len; + + /* 打开全局中断 */ + rt_hw_interrupt_enable(level); + + if (player->status != PLAYER_RUNNING) + { + player_play(player); + } + + return 0; +} +``` + +### 控制函数 + +为了更好的操作播放器,我们可以实现一个操作函数,用这个函数来控制播放器。 + +```c +int player_control(player_t player, int cmd, void *arg); +``` + +设定的一些控制播放器的控制字,这样就可以减少API 的个数方便其他模块的使用。 + +```c +enum PLAYER_CMD +{ + PLAYER_CMD_PLAY, + PLAYER_CMD_STOP, + PLAYER_CMD_LAST, + PLAYER_CMD_NEXT, + PLAYER_CMD_SET_VOL, + PLAYER_CMD_GET_VOL, + PLAYER_CMD_GET_STATUS +}; +``` + +具体的实现如下所示: + +```c +int player_control(player_t player, int cmd, void *arg) +{ + rt_uint32_t level; + + switch (cmd) + { + case PLAYER_CMD_PLAY: + player_play(player); + break; + case PLAYER_CMD_STOP: + player_stop(player); + break; + case PLAYER_CMD_LAST: + player_last(player); + break; + case PLAYER_CMD_NEXT: + player_next(player); + break; + case PLAYER_CMD_SET_VOL: + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + player->volume = *(uint8_t *)arg; + /* 打开全局中断 */ + rt_hw_interrupt_enable(level); + player->audio->control(AUDIO_OPS_CMD_SET_VOL, &player->volume); + break; + case PLAYER_CMD_GET_VOL: + *(uint8_t *)arg = player->volume; + break; + case PLAYER_CMD_GET_STATUS: + *(uint8_t *)arg = player->status; + break; + } + return 0; +} +``` + +### 显示信息 + +然后我们可以实现一个打印播放器的歌单,当前的播放状态,正在播放的歌曲,以及进度和音量大小等信息,我们只要简单的写几个 `rt_kprintf` 函数将这些信息打印出来就好了。然后我们可以把这个函数放到歌曲的切换的位置,这样每改变一次播放器的状态就会打印一次播放器当前的状态。实际的代码如下所示,其中 `PLAYER_SONG_NAME_LEN_MAX` 默认定义为 20 + +```c +int player_show(player_t player) +{ + char name[PLAYER_SONG_NAME_LEN_MAX + 1]; + uint8_t i; + uint16_t percent; + + rt_kprintf("*********** Beep Player ***********\n"); + + /* 打印歌单 */ + for (i = 0; i < player->song_num; i++) + { + player->decode->control(player->song_sheet[i], DECODE_OPS_CMD_GET_NAME, name); + rt_kprintf("%02d. %s\n", i + 1, name); + } + + /* 打印当前播放状态 */ + if (PLAYER_RUNNING == player->status) + { + rt_kprintf("<--- 正在播放:"); + } + else + { + rt_kprintf("<--- 暂停播放:"); + } + + /* 打印当前歌曲 */ + player->decode->control(player->song_sheet[player->song_current - 1], DECODE_OPS_CMD_GET_NAME, name); + rt_kprintf("%s", name); + rt_kprintf("--->\n"); + + /* 打印播放进度 */ + percent = player->song_time_pass * 100 / player->song_time_all; + rt_kprintf("播放进度:%02d%% 音量大小:%02d%%\n", percent, player->volume); + + rt_kprintf("***********************************\n"); + + return 0; +} +``` + +### 添加歌曲 + +添加歌曲到播放列表,要实现这个功能,只要将歌曲信息的首地址添加到播放列表的数组里,然后将歌曲的个数加一即可。实现如下: + +```c +int player_add_song(player_t player, void *song) +{ + rt_uint32_t level; + + if (player->song_num == PLAYER_SONG_NUM_MAX) + { + return -1; + } + /* 关闭全局中断 */ + level = rt_hw_interrupt_disable(); + + player->song_sheet[player->song_num] = song; + player->song_num++; + + /* 打开全局中断 */ + rt_hw_interrupt_enable(level); + + return 0; +} +``` + +### 启动播放器 + +在启动播放器的函数里,需要完成 player 这个结构体对象的初始化,为结构体内部的各个变量赋初值。然后调用解码和音频的操作函数,初始化解码器和音频设备。然后初始化信号量并创建播放线程。不过为了保证播放器能正常的工作,应该先添加歌曲,再启动播放器。其中 PLAYER_SOUND_SIZE_DEFAULT 默认定义为 3。 + +```c +int player_start(player_t player) +{ + uint16_t len; + static rt_uint8_t inited = 0; + + /* 检测播放器是否已经在运行 */ + if (inited == 1) + { + return -RT_ERROR; + } + + if (player->song_num == 0) + { + return -1; + } + /* 调用接口初始化解码器 */ + player->decode->init(); + + player->status = PLAYER_STOP; + player->volume = PLAYER_SOUND_SIZE_DEFAULT; + player->song_current = 1; + player->song_time_pass = 0; + player->decode->control(player->song_sheet[player->song_current - 1], DECODE_OPS_CMD_GET_LEN, &len); + player->song_time_all = len; + + /* 调用接口初始化音频设备 */ + player->audio->init(); + player->audio->control(AUDIO_OPS_CMD_SET_VOL, &player->volume); + + /* 初始化动态信号量 */ + player->sem_play = rt_sem_create("sem_play", 0, RT_IPC_FLAG_FIFO); + if (player->sem_play == RT_NULL) + { + return -RT_ERROR; + } + + /* 创建动态线程 */ + player->play_thread = rt_thread_create("player", + player_entry, player, + 512, 20, 20); + if (player->play_thread != RT_NULL) + { + rt_thread_startup(player->play_thread); + } + else + { + rt_sem_delete(player->sem_play); + return -RT_ERROR; + } + inited = 1; + + return 0; +} +``` + +## 示例程序 + +这个播放器的内核应该如何使用呢?首先需要包含头文件 `#include "player.h"` ,然后需要定义播放器的结构体 `struct player player` ,还要对接上播放器的解码和音频播放的接口。对接上接口之后,就添加歌曲,然后启动播放器,控制播放器改为播放状态,并打印一次播放器状态。为了更好的展示播放器的列表循环播放,需要多准备几首歌,我们新建一个 `song_data.h` 然后将下面的内容复制进去,在这里面存储了4首歌的数据,可以包含在示例程序里使用。 + +```c +#ifndef SONG_DATA_H +#define SONG_DATA_H + +#include "decode.h" + +const struct beep_song song1 = +{ + .name = "两只老虎", + .data = { + 0x15, 0x02, 0x16, 0x02, 0x17, 0x02, 0x15, 0x02, 0x15, 0x02, + // 1 2 3 1 1 + 0x16, 0x02, 0x17, 0x02, 0x15, 0x02, 0x17, 0x02, 0x18, 0x02, + // 2 3 1 3 4 + 0x19, 0x01, 0x17, 0x02, 0x18, 0x02, 0x19, 0x01, 0x19, 0x03, + // 5 - 3 4 5 - 5_ + 0x1A, 0x03, 0x19, 0x03, 0x18, 0x03, 0x17, 0x02, 0x15, 0x16, + // 6_ 5_ 4_ 3 1 + 0x19, 0x03, 0x1A, 0x03, 0x19, 0x03, 0x18, 0x03, 0x17, 0x02, + // 5_ 6_ 5_ 4_ 3 + 0x15, 0x16, 0x15, 0x02, 0x0F, 0x02, 0x15, 0x01, 0x15, 0x02, + // 1 1 5. 1 - 1 + 0x0F, 0x02, 0x15, 0x01, 0x00, 0x00 + // 5. 1 - + } +}; + +const struct beep_song song2 = +{ + .name = "挥着翅膀的女孩", + .data = { + 0x17, 0x02, 0x17, 0x03, 0x18, 0x03, 0x19, 0x02, 0x15, 0x03, + 0x16, 0x03, 0x17, 0x03, 0x17, 0x03, 0x17, 0x03, 0x18, 0x03, + 0x19, 0x02, 0x16, 0x03, 0x17, 0x03, 0x18, 0x02, 0x18, 0x03, + 0x17, 0x03, 0x15, 0x02, 0x18, 0x03, 0x17, 0x03, 0x18, 0x02, + 0x10, 0x03, 0x15, 0x03, 0x16, 0x02, 0x15, 0x03, 0x16, 0x03, + 0x17, 0x02, 0x17, 0x03, 0x18, 0x03, 0x19, 0x02, 0x1A, 0x03, + 0x1B, 0x03, 0x1F, 0x03, 0x1F, 0x03, 0x17, 0x03, 0x18, 0x03, + 0x19, 0x02, 0x16, 0x03, 0x17, 0x03, 0x18, 0x03, 0x17, 0x03, + 0x18, 0x03, 0x1F, 0x03, 0x1F, 0x02, 0x16, 0x03, 0x17, 0x03, + 0x18, 0x03, 0x17, 0x03, 0x18, 0x03, 0x20, 0x03, 0x20, 0x02, + 0x1F, 0x03, 0x1B, 0x03, 0x1F, 0x66, 0x20, 0x03, 0x21, 0x03, + 0x20, 0x03, 0x1F, 0x03, 0x1B, 0x03, 0x1F, 0x66, 0x1F, 0x03, + 0x1B, 0x03, 0x19, 0x03, 0x19, 0x03, 0x15, 0x03, 0x1A, 0x66, + 0x1A, 0x03, 0x19, 0x03, 0x15, 0x03, 0x15, 0x03, 0x17, 0x03, + 0x16, 0x66, 0x17, 0x04, 0x18, 0x04, 0x18, 0x03, 0x19, 0x03, + 0x1F, 0x03, 0x1B, 0x03, 0x1F, 0x66, 0x20, 0x03, 0x21, 0x03, + 0x20, 0x03, 0x1F, 0x03, 0x1B, 0x03, 0x1F, 0x66, 0x1F, 0x03, + 0x1B, 0x03, 0x19, 0x03, 0x19, 0x03, 0x15, 0x03, 0x1A, 0x66, + 0x1A, 0x03, 0x19, 0x03, 0x19, 0x03, 0x1F, 0x03, 0x1B, 0x03, + 0x1F, 0x00, 0x1A, 0x03, 0x1A, 0x03, 0x1A, 0x03, 0x1B, 0x03, + 0x1B, 0x03, 0x1A, 0x03, 0x19, 0x03, 0x19, 0x02, 0x17, 0x03, + 0x15, 0x17, 0x15, 0x03, 0x16, 0x03, 0x17, 0x03, 0x18, 0x03, + 0x17, 0x04, 0x18, 0x0E, 0x18, 0x03, 0x17, 0x04, 0x18, 0x0E, + 0x18, 0x66, 0x17, 0x03, 0x18, 0x03, 0x17, 0x03, 0x18, 0x03, + 0x20, 0x03, 0x20, 0x02, 0x1F, 0x03, 0x1B, 0x03, 0x1F, 0x66, + 0x20, 0x03, 0x21, 0x03, 0x20, 0x03, 0x1F, 0x03, 0x1B, 0x03, + 0x1F, 0x66, 0x1F, 0x04, 0x1B, 0x0E, 0x1B, 0x03, 0x19, 0x03, + 0x19, 0x03, 0x15, 0x03, 0x1A, 0x66, 0x1A, 0x03, 0x19, 0x03, + 0x15, 0x03, 0x15, 0x03, 0x17, 0x03, 0x16, 0x66, 0x17, 0x04, + 0x18, 0x04, 0x18, 0x03, 0x19, 0x03, 0x1F, 0x03, 0x1B, 0x03, + 0x1F, 0x66, 0x20, 0x03, 0x21, 0x03, 0x20, 0x03, 0x1F, 0x03, + 0x1B, 0x03, 0x1F, 0x66, 0x1F, 0x03, 0x1B, 0x03, 0x19, 0x03, + 0x19, 0x03, 0x15, 0x03, 0x1A, 0x66, 0x1A, 0x03, 0x19, 0x03, + 0x19, 0x03, 0x1F, 0x03, 0x1B, 0x03, 0x1F, 0x00, 0x18, 0x02, + 0x18, 0x03, 0x1A, 0x03, 0x19, 0x0D, 0x15, 0x03, 0x15, 0x02, + 0x18, 0x66, 0x16, 0x02, 0x17, 0x02, 0x15, 0x00, 0x00, 0x00 + } +}; + +const struct beep_song song3 = +{ + .name = "同一首歌", + .data = { + 0x0F, 0x01, 0x15, 0x02, 0x16, 0x02, 0x17, 0x66, 0x18, 0x03, + 0x17, 0x02, 0x15, 0x02, 0x16, 0x01, 0x15, 0x02, 0x10, 0x02, + 0x15, 0x00, 0x0F, 0x01, 0x15, 0x02, 0x16, 0x02, 0x17, 0x02, + 0x17, 0x03, 0x18, 0x03, 0x19, 0x02, 0x15, 0x02, 0x18, 0x66, + 0x17, 0x03, 0x19, 0x02, 0x16, 0x03, 0x17, 0x03, 0x16, 0x00, + 0x17, 0x01, 0x19, 0x02, 0x1B, 0x02, 0x1B, 0x70, 0x1A, 0x03, + 0x1A, 0x01, 0x19, 0x02, 0x19, 0x03, 0x1A, 0x03, 0x1B, 0x02, + 0x1A, 0x0D, 0x19, 0x03, 0x17, 0x00, 0x18, 0x66, 0x18, 0x03, + 0x19, 0x02, 0x1A, 0x02, 0x19, 0x0C, 0x18, 0x0D, 0x17, 0x03, + 0x16, 0x01, 0x11, 0x02, 0x11, 0x03, 0x10, 0x03, 0x0F, 0x0C, + 0x10, 0x02, 0x15, 0x00, 0x1F, 0x01, 0x1A, 0x01, 0x18, 0x66, + 0x19, 0x03, 0x1A, 0x01, 0x1B, 0x02, 0x1B, 0x03, 0x1B, 0x03, + 0x1B, 0x0C, 0x1A, 0x0D, 0x19, 0x03, 0x17, 0x00, 0x1F, 0x01, + 0x1A, 0x01, 0x18, 0x66, 0x19, 0x03, 0x1A, 0x01, 0x10, 0x02, + 0x10, 0x03, 0x10, 0x03, 0x1A, 0x0C, 0x18, 0x0D, 0x17, 0x03, + 0x16, 0x00, 0x0F, 0x01, 0x15, 0x02, 0x16, 0x02, 0x17, 0x70, + 0x18, 0x03, 0x17, 0x02, 0x15, 0x03, 0x15, 0x03, 0x16, 0x66, + 0x16, 0x03, 0x16, 0x02, 0x16, 0x03, 0x15, 0x03, 0x10, 0x02, + 0x10, 0x01, 0x11, 0x01, 0x11, 0x66, 0x10, 0x03, 0x0F, 0x0C, + 0x1A, 0x02, 0x19, 0x02, 0x16, 0x03, 0x16, 0x03, 0x18, 0x66, + 0x18, 0x03, 0x18, 0x02, 0x17, 0x03, 0x16, 0x03, 0x19, 0x00, + 0x00, 0x00 + } +}; + +const struct beep_song song4 = +{ + .name = "两只蝴蝶", + .data = { + 0x17, 0x03, 0x16, 0x03, 0x17, 0x01, 0x16, 0x03, 0x17, 0x03, + 0x16, 0x03, 0x15, 0x01, 0x10, 0x03, 0x15, 0x03, 0x16, 0x02, + 0x16, 0x0D, 0x17, 0x03, 0x16, 0x03, 0x15, 0x03, 0x10, 0x03, + 0x10, 0x0E, 0x15, 0x04, 0x0F, 0x01, 0x17, 0x03, 0x16, 0x03, + 0x17, 0x01, 0x16, 0x03, 0x17, 0x03, 0x16, 0x03, 0x15, 0x01, + 0x10, 0x03, 0x15, 0x03, 0x16, 0x02, 0x16, 0x0D, 0x17, 0x03, + 0x16, 0x03, 0x15, 0x03, 0x10, 0x03, 0x15, 0x03, 0x16, 0x01, + 0x17, 0x03, 0x16, 0x03, 0x17, 0x01, 0x16, 0x03, 0x17, 0x03, + 0x16, 0x03, 0x15, 0x01, 0x10, 0x03, 0x15, 0x03, 0x16, 0x02, + 0x16, 0x0D, 0x17, 0x03, 0x16, 0x03, 0x15, 0x03, 0x10, 0x03, + 0x10, 0x0E, 0x15, 0x04, 0x0F, 0x01, 0x17, 0x03, 0x19, 0x03, + 0x19, 0x01, 0x19, 0x03, 0x1A, 0x03, 0x19, 0x03, 0x17, 0x01, + 0x16, 0x03, 0x16, 0x03, 0x16, 0x02, 0x16, 0x0D, 0x17, 0x03, + 0x16, 0x03, 0x15, 0x03, 0x10, 0x03, 0x10, 0x0D, 0x15, 0x00, + 0x19, 0x03, 0x19, 0x03, 0x1A, 0x03, 0x1F, 0x03, 0x1B, 0x03, + 0x1B, 0x03, 0x1A, 0x03, 0x17, 0x0D, 0x16, 0x03, 0x16, 0x03, + 0x16, 0x0D, 0x17, 0x01, 0x17, 0x03, 0x17, 0x03, 0x19, 0x03, + 0x1A, 0x02, 0x1A, 0x02, 0x10, 0x03, 0x17, 0x0D, 0x16, 0x03, + 0x16, 0x01, 0x17, 0x03, 0x19, 0x03, 0x19, 0x03, 0x17, 0x03, + 0x19, 0x02, 0x1F, 0x02, 0x1B, 0x03, 0x1A, 0x03, 0x1A, 0x0E, + 0x1B, 0x04, 0x17, 0x02, 0x1A, 0x03, 0x1A, 0x03, 0x1A, 0x0E, + 0x1B, 0x04, 0x1A, 0x03, 0x19, 0x03, 0x17, 0x03, 0x16, 0x03, + 0x17, 0x0D, 0x16, 0x03, 0x17, 0x03, 0x19, 0x01, 0x19, 0x03, + 0x19, 0x03, 0x1A, 0x03, 0x1F, 0x03, 0x1B, 0x03, 0x1B, 0x03, + 0x1A, 0x03, 0x17, 0x0D, 0x16, 0x03, 0x16, 0x03, 0x16, 0x03, + 0x17, 0x01, 0x17, 0x03, 0x17, 0x03, 0x19, 0x03, 0x1A, 0x02, + 0x1A, 0x02, 0x10, 0x03, 0x17, 0x0D, 0x16, 0x03, 0x16, 0x01, + 0x17, 0x03, 0x19, 0x03, 0x19, 0x03, 0x17, 0x03, 0x19, 0x03, + 0x1F, 0x02, 0x1B, 0x03, 0x1A, 0x03, 0x1A, 0x0E, 0x1B, 0x04, + 0x17, 0x02, 0x1A, 0x03, 0x1A, 0x03, 0x1A, 0x0E, 0x1B, 0x04, + 0x17, 0x16, 0x1A, 0x03, 0x1A, 0x03, 0x1A, 0x0E, 0x1B, 0x04, + 0x1A, 0x03, 0x19, 0x03, 0x17, 0x03, 0x16, 0x03, 0x0F, 0x02, + 0x10, 0x03, 0x15, 0x00, 0x00, 0x00 + } +}; + +#endif +``` + +在对接接口时,需要注意,音频设备写的音频数据就是从解码器那里读来的,所以只要自己的知道音频数据怎么播放,并在音频设备写的接口里完成播放即可。还有就是解码函数读接口返回的长度表示:当次读数据歌曲进度增加的量,因为只返回了一个音符的数据,因此歌曲的进度相应的也只加1。示例代码如下: + +```c +#include + +#include "player.h" +#include "song_data.h" +#include "beep.h" +#include "decode.h" + +struct player player; +struct audio_ops audio; +struct decode_ops decode; + +uint8_t beep_volume = 3; + +int decode_read(void *song, int index, void *buffer, int size) +{ + beep_song_get_data(song, index, buffer); + + return 1; +} + +int audio_write(void *buffer, int size) +{ + struct beep_song_data *data = buffer; + + beep_on(); + beep_set(data->freq, beep_volume); + rt_thread_mdelay(data->sound_len); + beep_off(); + rt_thread_mdelay(data->nosound_len); + + return size; +} +int decode_control(void *song, int cmd, void *arg) +{ + if (cmd == DECODE_OPS_CMD_GET_NAME) + beep_song_get_name(song, arg); + else if (cmd == DECODE_OPS_CMD_GET_LEN) + *(uint16_t *)arg = beep_song_get_len(song); + return 0; +} +int audio_control(int cmd, void *arg) +{ + if (cmd == AUDIO_OPS_CMD_SET_VOL) + beep_volume = *(uint8_t *)arg; + return beep_volume; +} + +int player_init(void) +{ + decode.init = beep_song_decode_init; + decode.control = decode_control; + decode.read = decode_read; + + audio.init = beep_init; + audio.open = beep_on; + audio.close = beep_off; + audio.control = audio_control; + audio.write = audio_write; + + player.decode = &decode; + player.audio = &audio; + + player_add_song(&player, (void *)&song1); + player_add_song(&player, (void *)&song2); + player_add_song(&player, (void *)&song3); + player_add_song(&player, (void *)&song4); + player_start(&player); + + player_control(&player, PLAYER_CMD_PLAY, RT_NULL); + player_show(&player); + + return 0; +} + +int main(void) +{ + /* user app entry */ + player_init(); + return 0; +} +``` + +![运行结果](figures/run4.png) + +## 程序源码 + +[player.c/.h](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/tree/master/code/player) + +[song_data.h](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/blob/master/samples/song_data.h) + +[示例程序](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/blob/master/samples/main_5.c) + diff --git a/rt-thread-version/rt-thread-standard/tutorial/beep-player/pwm.md b/rt-thread-version/rt-thread-standard/tutorial/beep-player/pwm.md new file mode 100644 index 0000000000000000000000000000000000000000..cc655bf4af7053b6247a866da6f0f9a8a203c7c9 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/beep-player/pwm.md @@ -0,0 +1,211 @@ +# 第 3 节:使用 PWM 设备驱动蜂鸣器 + +## 基础知识 + +只有无源蜂鸣器才能够用来播放音乐。因为无源蜂鸣器振动的频率是可调的,而有源蜂鸣器的振动频率是固定的。只有频率(也就是音高)可调,才能够播放简单的音乐。由于无源蜂鸣器需要有震荡信号才能发出声音,所以需要使用 PWM 设备来控制蜂鸣器的播放。 + +为了给用户提供产生 PWM 的通用 API,方便应用程序开发,RT-Thread 中引入了 PWM 设备驱动。并提供 API 用于操作 PWM 设备,如设置 PWM 的周期及高电平的脉宽、开启 PWM 通道的输出、关闭 PWM 通道的输出。 + +常用的 PWM 设备的 API 有以下几个: + +| API | 描述 | +| -------------- | ----------------------- | +| rt_pwm_set | 设置 PWM 某一通道的参数 | +| rt_pwm_enable | 开启 PWM 通道的输出 | +| rt_pwm_disable | 关闭 PWM 通道的输出 | + +### 开启 PWM 功能 + +要使用 PWM 设备需要在 Env 中开启 PWM 设备,如下所示。然后回车,在具体的配置里开启通道一,保存并重新生成工程。 + +![开启 PWM1](figures/pwm_env.png) + +![开启通道一](figures/pwm_ch1.png) + +## 硬件连接 + +由于 PWM 设备是基于定时器 Timer 实现的,所以只有与定时器相应通道连接的管脚才能输出 PWM。查看已经实现的 PWM 设备的驱动文件 `drv_pwm.c`,从中可以看到当前 PWM 设备支持的管脚,我们选择与 TIM1 通道1对应的管脚 PA8 作为驱动蜂鸣器的管脚。 + +![原理图](figures/hw_pwm.png) + +使用杜邦线将无源蜂鸣器和开发板连接起来,其中 VCC 接 3.3V,GND 接 GND, I/O 引脚连接开发板的 PA8。 + +![硬件连接](figures/hw_png1.jpg) + +## 软件实现 + +利用 RT-Thread 的 PWM 设备可以很轻松的控制 IO 口输出的脉冲的频率和占空比,这就为控制蜂鸣器提供了方便。我们可以将 PWM 设备的 API 封装成函数,来作为蜂鸣器的控制接口,例如我们可以封装成下面的四个函数,一个初始化函数,一个开、一个关,再加一个设定频率和响度的函数,就能很好的控制蜂鸣器了。 + +```c +static int beep_init(void); //蜂鸣器初始化 +static int beep_on(void); //蜂鸣器开 +static int beep_off(void); //蜂鸣器关 +static int beep_set(uint16_t freq, uint8_t volume); //蜂鸣器设定 +``` + +同样我们新建一个 `beep.h` 的文件,将上面的这些函数声明放到 `beep.h` 文件里,方便其他文件的调用。直接在头文件里使用 `uint16_t/uint8_t` 的数据类型会报错,因此在头文件里包含 `rtthread.h` 头文件。 + +为了程序更好的可移植性,把 beep 使用的 PWM 设备用宏定义的方式来表示,如下所示: + +```c +#define BEEP_PWM_DEVICE "pwm1" +#define BEEP_PWM_CH 1 +``` + +最后 `beep.h` 文件中内容如下所示: + +```c +#ifndef BEEP_H +#define BEEP_H + +#include + +#define BEEP_PWM_DEVICE "pwm1" +#define BEEP_PWM_CH 1 + +int beep_init(void); //蜂鸣器初始化 +int beep_on(void); //蜂鸣器开 +int beep_off(void); //蜂鸣器关 +int beep_set(uint16_t freq, uint8_t volume); //蜂鸣器设定 + +#endif +``` + +然后再新建一个 `beep.c` 文件,在文件里添加下面的头文件 + +```c +#include //使用 RT-Thread 的设备需要包含此头文件 +``` + + + +然后在文件 `beep.c` 里分别实现上面的几个 API。 + +### 蜂鸣器初始化 + +```c +static int beep_init(void); //蜂鸣器初始化 +``` + +要想使用某一个 PWM 设备(例如 PWM1),需要先依据名字获取到设备的控制块,然后在调用 PWM 的 API 时作为参数传入。 + +为了能够使用 API 控制 PWM 设备,在蜂鸣器初始化函数里,需要获取 PWM 设备的设备控制块。 + +```c +struct rt_device_pwm *pwm_device = RT_NULL; //定义 pwm 设备指针 + +int beep_init(void) +{ + /* 查找PWM设备 */ + pwm_device = (struct rt_device_pwm *)rt_device_find(BEEP_PWM_DEVICE); + if (pwm_device == RT_NULL) + { + rt_kprintf("pwm device %s not found!\n", BEEP_PWM_DEVICE); + return -RT_ERROR; + } + return 0; +} +``` + +### 打开蜂鸣器 + +```c +int beep_on(void); //蜂鸣器开 +``` + +在打开蜂鸣器的函数里,通过调用 `rt_pwm_enable` API 开启 PWM 通道的输出功能。 + +```c +static int beep_on(void) +{ + rt_pwm_enable(pwm_device, BEEP_PWM_CH); //使能蜂鸣器对应的 PWM 通道 + return 0; +} +``` + +### 关闭蜂鸣器 + +```c +int beep_off(void); //蜂鸣器关 +``` + +在关闭蜂鸣器的函数里,通过调用 `rt_pwm_disable` API 关闭 PWM 通道的输出功能。 + +```c +int beep_off(void) +{ + rt_pwm_disable(pwm_device, BEEP_PWM_CH); //失能蜂鸣器对应的 PWM 通道 + return 0; +} +``` + +### 蜂鸣器设定 + +```c +static int beep_set(uint16_t freq, uint8_t volume); //蜂鸣器设定 +``` + +在蜂鸣器设定函数里需要设定 蜂鸣器的频率和音量。PWM 设备设定的 API 是设定 **周期** 和 **脉宽** 的,因此我们需要转换一下,将频率装换成周期,将音量大小(占空比)转换成脉宽。这里的周期的单位是 **ns**,因此转换成的 **秒** 之后还要乘以 10^9 。由于无源蜂鸣器一般是低电平触发的,因此音量越大对应的脉宽就要越小。然后,将转换后的数据传入 `rt_pwm_set` 来设置 PWM 设备的参数。 + +```c +static int beep_set(uint16_t freq, uint8_t volume) +{ + rt_uint32_t period, pulse; + + /* 将频率转化为周期 周期单位:ns 频率单位:HZ */ + period = 1000000000 / freq; //unit:ns 1/HZ*10^9 = ns + + /* 根据声音大小计算占空比 蜂鸣器低电平触发 */ + pulse = period - period / 100 * volume; + + /* 利用 PWM API 设定 周期和占空比 */ + rt_pwm_set(pwm_device, BEEP_PWM_CH, period, pulse);//channel,period,pulse + + return 0; +} +``` + +## 示例程序 + + 这样就可以在 `main.c` 里包含 `beep.h` 头文件,然后使用这些 API 来控制蜂鸣器了。 + +下面是一段控制无源蜂鸣器依次播放音符 CDEFGAB 的一段程序。 + +``` +#include +#include "beep.h" + +uint16_t freq_tab[12] = {262, 277, 294, 311, 330, 349, 369, 392, 415, 440, 466, 494}; //原始频率表 CDEFGAB +uint8_t beep_volume = 3; + +int main(void) +{ + /* user app entry */ + int i; + + beep_init(); + + for (i = 0; i < 12; i++) + { + beep_set(freq_tab[i], beep_volume); + beep_on(); + + rt_thread_mdelay(500); + + beep_off(); + rt_thread_mdelay(500); + } + + return 0; +} +``` + +## 参考资料 + +* [《PWM 设备》](../../programming-manual/device/pwm/pwm.md) + +## 程序源码 + +[beep.c/.h](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/tree/master/code/beep) + +[示例程序](https://github.com/Guozhanxin/RTT-BeepPlayer-pkg/blob/master/samples/main_3.c) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/README.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/dynmem_sample/dynmem_sample.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/dynmem_sample/dynmem_sample.md new file mode 100644 index 0000000000000000000000000000000000000000..2876aef6ed5b9956fb992659d2868020b69dba67 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/dynmem_sample/dynmem_sample.md @@ -0,0 +1,120 @@ +# 实验:动态内存堆的使用 + +## 实验目的 + +- 理解动态内存的基本原理 + +- 在 RT-Thread 中熟练使用动态内存 + +## 实验原理及程序结构 + +动态堆管理根据具体内存设备划分为以下三种情况,本实验针对于第一种情况,前提是要开启系统 heap 功能。 + +第一种是针对小内存块的分配管理(小堆内存管理算法),小内存管理算法主要针对系统资源比较少,一般用于小于 2MB 内存空间的系统。 + +第二种是针对大内存块的分配管理(slab 管理算法),slab 内存管理算法则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。 + +第三种是针对多内存块的分配情况(memheap 管理算法),memheap 方法适用于系统存在多个内存堆的情况,它可以将多个内存 “粘贴” 在一起,形成一个大的内存堆,用户使用起来会感到格外便捷。 + +### 实验设计 + +本实验使用的例程为:[dynmem_sample.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/dynmem_sample.c) + +实验设计一个动态的线程,这个线程会动态申请内存并释放,每次申请更大的内存,当申请不到的时候就结束。 + +## 源程序说明 + +### 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +### 示例源码 + +以下定义了线程所用的优先级、栈大小以及时间片的宏。 + +```c +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 +``` + +线程入口函数,一直申请内存,申请到之后就释放内存,每次会申请更大的内存,申请不到时,将结束,申请的内存大小信息也会打印出来。 + +```c +/* 线程入口 */ +void thread1_entry(void *parameter) +{ + int i; + char *ptr = RT_NULL; /* 内存块的指针 */ + + for (i = 0; ; i++) + { + /* 每次分配 (1 << i) 大小字节数的内存空间 */ + ptr = rt_malloc(1 << i); + + /* 如果分配成功 */ + if (ptr != RT_NULL) + { + rt_kprintf("get memory :%d byte\n", (1 << i)); + /* 释放内存块 */ + rt_free(ptr); + rt_kprintf("free memory :%d byte\n", (1 << i)); + ptr = RT_NULL; + } + else + { + rt_kprintf("try to get %d byte memory failed!\n", (1 << i)); + return; + } + } +} +``` + +动态内存管理的示例代码,创建 thread1 并启动。并将函数使用 MSH_CMD_EXPORT 导出命令。 + +```c +int dynmem_sample(void) +{ + rt_thread_t tid = RT_NULL; + + /* 创建线程 1 */ + tid = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, + THREAD_TIMESLICE); + if (tid != RT_NULL) + rt_thread_startup(tid); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(dynmem_sample, dynmem sample); +``` + +## 编译、运行和观察示例应用输出 + +编译工程,然后开始仿真。使用控制台 UART#1 做为 msh 终端,可以看到系统的启动日志,输入 dynmem_sample 命令启动示例应用,示例输出结果如下: + +```c +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Aug 24 2018 +2006 - 2018 Copyright by rt-thread team +msh >dynmem_sample +msh >get memory :1 byte +free memory :1 byte +get memory :2 byte +free memory :2 byte +… +get memory :16384 byte +free memory :16384 byte +get memory :32768 byte +free memory :32768 byte +try to get 65536 byte memory failed! +``` + +例程中分配内存成功并打印信息;当试图申请 65536 byte 即 64KB 内存时,由于 RAM 总大小只有 64K,而可用 RAM 小于 64K,所以分配失败。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/event_sample.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/event_sample.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..02f9d8859dae36e7f84a349cd6f0b904abefd6c1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/event_sample.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/event_sample.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/event_sample.md new file mode 100644 index 0000000000000000000000000000000000000000..74a7982558ad0526a3be906c5aa8b52e4cd761ba --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/event_sample.md @@ -0,0 +1,216 @@ +实验:事件集的使用 +================== + +实验目的 +-------- + +- 理解事件集的基本原理; + +- 使用事件集来达到多条件情况下线程间同步; + +- 在 RT-Thread 中熟练使用事件集来完成需求。 + +实验原理及程序结构 +------------------ + +事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。 + +### 实验设计 + +本实验使用的例程为:[event_sample.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/event_sample.c) + +为了体现使用事件集来达到线程间的同步,本实验设计了 thread1、thread2 两个线程,优先级分别为 8、9,设计了一个事件集 event。 + +线程 thread1 进入后接收事件组合 “事件 3 或事件 5”,接收到事件时候进行 100ms 延时,然后接收事件组合 “事件 3 与事件 5”,接收完成后结束线程。 + +线程 thread2 进入后发送事件 3,延时 200ms;发送事件 5,延时 200ms;发送事件 3,完成后结束线程。 + +整体情况:thread1 首先等待 “事件 3 或事件 5” 的到来,thread2 发送事件 3,唤醒 thread1 接收事件,之后 thread1 等待“事件 3 与事件 5”;thread2 再发送事件 5,进行延时,thread2 发送事件 3,等 thread1 延时结束就能接收事件组合“事件 3 与事件 5”。 + +通过本实验,用户可以清晰地了解到,线程在同时接收多个事件和接收多个事件中的一个时的运行情况。 + +整个实验运行过程如下图所示,过程描述如下: + +![实验运行过程](figures/process60.png) + +(1)在 tshell 线程中初始化一个事件集 event,初始化为先进先出型;并分别初始化、启动线程 thread1、thread2,优先级分别为 8、9; + +(2)在操作系统的调度下,thread1 优先级高,首先被投入运行;thread1 开始运行后接收事件集 3 或 5,由于未接收到事件集 3 或 5,线程 thread1 挂起; + +(3)随后操作系统调度 thread2 投入运行,thread2 发送事件 3,然后执行延时将自己挂起 200ms; + +(4)thread1 接收到事件 3,打印相关信息,并开始等待 “事件 3 与事件 5”; + +(5)thread2 的延时时间到,发送事件 5;延时,发送事件 3; + +(6)等待 thread1 的延时结束后,可以马上接收到 “事件 3 与事件 5”,打印信息,结束运行; + +### 源程序说明 + +#### RT-Thread 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +#### 示例源码 + +例子中初始化一个事件集,初始化两个静态线程。一个线程等待自己关心的事件的发生,另外一个线程发生事件。以下定义了待创建线程需要用到的优先级,栈空间,时间片的宏,事件控制块。 + +```c +# include +# define THREAD_PRIORITY 9 +# define THREAD_TIMESLICE 5 +# define EVENT_FLAG3 (1 << 3) +# define EVENT_FLAG5 (1 << 5) + +/* 事件控制块 */ +static struct rt_event event; +``` + +线程 thread1 的栈空间、线程控制块以及线程 thread1 的入口函数,共接收两次事件,第一次永久等待 “事件 3 或事件 5”,第二次永久等待 “事件 3 与事件 5” + +```c +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程 1 入口函数 */ +static void thread1_recv_event(void *param) +{ + rt_uint32_t e; + + /* 第一次接收事件,事件 3 或事件 5 任意一个可以触发线程 1,接收完后清除事件标志 */ + if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &e) == RT_EOK) + { + rt_kprintf("thread1: OR recv event 0x%x\n", e); + } + + rt_kprintf("thread1: delay 1s to prepare the second event\n"); + rt_thread_mdelay(1000); + + /* 第二次接收事件,事件 3 和事件 5 均发生时才可以触发线程 1,接收完后清除事件标志 */ + if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), + RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, + RT_WAITING_FOREVER, &e) == RT_EOK) + { + rt_kprintf("thread1: AND recv event 0x%x\n", e); + } + rt_kprintf("thread1 leave.\n"); +} +``` + +线程 thread2 的栈空间、线程控制块以及线程 thread1 的入口函数,发送 3 次事件,发送事件 3,延时 200ms;发送事件 5,延时 200ms;发送事件 3,结束。 + +```c +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程 2 入口 */ +static void thread2_send_event(void *param) +{ + rt_kprintf("thread2: send event3\n"); + rt_event_send(&event, EVENT_FLAG3); + rt_thread_mdelay(200); + + rt_kprintf("thread2: send event5\n"); + rt_event_send(&event, EVENT_FLAG5); + rt_thread_mdelay(200); + + rt_kprintf("thread2: send event3\n"); + rt_event_send(&event, EVENT_FLAG3); + rt_kprintf("thread2 leave.\n"); +} +``` + +事件的示例代码,初始化一个事件对象,初始化并启动线程 thread1、thread2,并将函数使用 MSH_CMD_EXPORT 导出命令。 + +```c +int event_sample(void) +{ + rt_err_t result; + + /* 初始化事件对象 */ + result = rt_event_init(&event, "event", RT_IPC_FLAG_FIFO); + if (result != RT_EOK) + { + rt_kprintf("init event failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_recv_event, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_send_event, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(event_sample, event sample); +``` + +编译、仿真运行和观察示例应用输出 +-------------------------------- + +编译工程,然后开始仿真。使用控制台 UART\#1 做为 msh 终端,可以看到系统的启动日志,输入 event_sample 命令启动示例应用,示例输出结果如下: + +``` +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Aug 24 2018 +2006 - 2018 Copyright by rt-thread team +msh >event_sample +thread2: send eventhread3 +thread1: OR recv event 0x8 +thread1: delay 1s to prepare the second event +msh >thread2: send event5 +thread2: send eventhread3 +thread2 leave. +thread1: AND recv event 0x28 +thread1 leave. +``` + +例程演示了事件集的使用方法。thread1 前后两次接收事件,分别使用了 “逻辑或” 与“逻辑与”的方法。 + +使用 SystemView 工具可以监测示例实际运行过程,示例开始之后现象与实验设计相同。如下图所示。 + +![运行整体图](figures/process61.png) + +第一部分:初始化事件与线程,详见下图;第二部分 thread2 发送事件 5;第三部分 thread2 发送事件 3,第四部分 thread1 延时结束接收到 “事件 3 与事件 5”。 + +![第一部分细节](figures/process62.png) + +图中各名称对应描述如下表: + +| 名称 | 描述 | +|-----------|-----------------| +| Unified | CPU 当前运行状态 | +| UART ISR | 串口中断 | +| SysTick | 系统时钟 | +| Scheduler | 调度器 | +| Timer | 定时器 | +| thread2 | 线程 thread2 | +| thread1 | 线程 thread1 | +| tshell | 线程 tshell | +| Idle | 空闲线程 | + +附件 +---- + +整个示例运行流程可以使用工具 SystemView 工具打开附件文件 [event_sample.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/event_sample/event_sample.SVDat) 查看具体细节。注意打开附件时,不要有中文路径。 + diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process60.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process60.png new file mode 100644 index 0000000000000000000000000000000000000000..8eff1d213ef27b8ee7cf2956075ec321236ba105 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process60.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process61.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process61.png new file mode 100644 index 0000000000000000000000000000000000000000..c1af6a2e3ec369f1300dc79e2edac82c4ecf6c4d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process61.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process62.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process62.png new file mode 100644 index 0000000000000000000000000000000000000000..87351c619dd718e862a4cbf43db8a4aac39e510c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/event_sample/figures/process62.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/experimental-manual.pdf b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/experimental-manual.pdf new file mode 100644 index 0000000000000000000000000000000000000000..179a84de3ae05680cac69bffceed3ff9b124f7eb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/experimental-manual.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/1_install.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/1_install.png new file mode 100644 index 0000000000000000000000000000000000000000..f2df5fc551b98cc43dc73b076a4373907c922f54 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/1_install.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/2_install.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/2_install.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e54f46d5b69f35e43eb3c95c14283a8c266c20 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/2_install.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/3_install.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/3_install.png new file mode 100644 index 0000000000000000000000000000000000000000..c3d131e7153cbdda1fa559e461090d2590ff3c82 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/3_install.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/4_install.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/4_install.png new file mode 100644 index 0000000000000000000000000000000000000000..9eb7156e295627cdf3590606c965bb16a755b90d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/4_install.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/5_install.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/5_install.png new file mode 100644 index 0000000000000000000000000000000000000000..d56139166a06240800c481e59f8b3b51983a565d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/5_install.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/6_install.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/6_install.png new file mode 100644 index 0000000000000000000000000000000000000000..17aaf3f9d466cc7102ebde798a33dd847993dcbf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/6_install.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/button.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/button.png new file mode 100644 index 0000000000000000000000000000000000000000..f7ac9b4fef3702274b744e0060e9489547f997f3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/button.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/code_structure.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/code_structure.png new file mode 100644 index 0000000000000000000000000000000000000000..8408d025ffa0d96cd90130f6802d29dfdeb27a86 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/code_structure.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/code_structure2.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/code_structure2.png new file mode 100644 index 0000000000000000000000000000000000000000..13c9a14543c112169f91762c2bc65f16b29c0465 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/code_structure2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/compile.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/compile.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f7707f1f13ce8a4d6a090e9aa2fbdb7915bca4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/compile.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/compile_result.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/compile_result.png new file mode 100644 index 0000000000000000000000000000000000000000..5283a2a0442f6a9e6ec6a838105c4f4baa1fa559 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/compile_result.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/debug.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/debug.png new file mode 100644 index 0000000000000000000000000000000000000000..7bfccb444e46458e9a099421550934c998c3565e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/debug.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/reset.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/reset.png new file mode 100644 index 0000000000000000000000000000000000000000..7cef5288628f581c3695d17b04c2b2f48e19cc4d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/reset.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/run.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/run.png new file mode 100644 index 0000000000000000000000000000000000000000..38c075468358d8da1a619cd740ef3edc82a02251 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/run.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/serial_window.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/serial_window.png new file mode 100644 index 0000000000000000000000000000000000000000..81b2c26c48c8acbae223f567f4bbe9ee2f0637f3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/serial_window.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/simulation.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/simulation.png new file mode 100644 index 0000000000000000000000000000000000000000..bb21dc12e14965df3bde1dcb64679575f981e117 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/figures/simulation.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process70.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process70.png new file mode 100644 index 0000000000000000000000000000000000000000..15ea7a9d3c2a61f7e0bdc89ee0ae3fb5a137c716 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process70.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process71.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process71.png new file mode 100644 index 0000000000000000000000000000000000000000..74dd2a577b5d40f69b9142df7ec4b5f9edfe1f38 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process71.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process72.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process72.png new file mode 100644 index 0000000000000000000000000000000000000000..f9dd3ab9776e705fd21379fa2bf43b9e62b2cb60 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process72.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process73.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process73.png new file mode 100644 index 0000000000000000000000000000000000000000..2bcd3a9f046f4f13eefb624be09549025e0d0008 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/figures/process73.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/mailbox_sample.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/mailbox_sample.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..21a56704a616392421d512dc451bf0bb9b3a7df4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/mailbox_sample.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/mailbox_sample.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/mailbox_sample.md new file mode 100644 index 0000000000000000000000000000000000000000..9ec5aa0abeb03be82a37b12f3a20982a6717dede --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/mailbox_sample/mailbox_sample.md @@ -0,0 +1,240 @@ +实验:邮箱的使用 +================ + +实验目的 +-------- + +- 理解邮箱的基本原理; + +- 使用邮箱进行线程间通信; + +- 在 RT-Thread 中熟练使用邮箱来完成需求。 + +实验原理及程序结构 +------------------ + +邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。在 RT-Thread 操作系统的实现中能够一次传递一个 4 字节大小的邮件,并且邮箱具备一定的存储功能,能够缓存一定数量的邮件数 +(邮件数由创建、初始化邮箱时指定的容量决定)。邮箱中一封邮件的最大长度是 4 字节,所以邮箱能够用于不超过 4 字节的消息传递。 + +### 实验设计 + +本实验使用的例程为:[mailbox_sample.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/mailbox_sample.c) + +为了体现使用邮箱来达到线程间的通信,本实验设计了 thread1、thread2 两个线程,优先级同为 10,设计了一个邮箱 mbt。 + +线程 thread1 每 100ms 尝试接收一次邮件,如果接收到邮件就将邮件内容打印出来。在接收到结束邮件时,打印邮件信息,线程结束。 + +线程 thread2 每 200ms 发送一次邮件,发送 10 次之后,发送结束邮件(线程 2 共发送 11 封邮件),线程运行结束。 + +通过本实验,用户可以清晰地了解到,线程在使用邮箱时候的线程调度。 + +整个实验运行过程如下图所示,下面以 thread2 开始运行时为开始时间,过程描述如下: + +![实验运行过程](figures/process70.png) + +(1)在 tshell 线程中初始化一个邮箱 mbt,采用 FIFO 方式进行线程等待;初始化并启动线程 thread1、thread2,优先级同为 10; + +(2)在操作系统的调度下,thread1 首先被投入运行; + +(3)thread1 开始运行,首先打印一段信息,然后尝试获取邮件,邮箱暂时没有邮件,thread1 挂起; + +(4)随后操作系统调度 thread2 投入运行,发送一封邮件,随后进入 200ms 延时; + +(5)此时线程 thread1 被唤醒,接收到邮件,继续打印一段信息,然后进入 100ms 延时; + +(6)thread2 在发送 10 次邮件后,发送一封结束内容的邮件,线程结束。 + +(7)thread1 一直接收邮件,当接收到来自 thread2 的结束邮件后,脱离邮箱,线程结束。 + +### 源程序说明 + +#### RT-Thread 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +#### 示例源码 + +以下定义了线程需要用到的优先级,栈空间,时间片的宏,邮箱控制块,存放邮件的内存池、3 份邮件内容。 + +```c +#include + +#define THREAD_PRIORITY 10 +#define THREAD_TIMESLICE 5 + +/* 邮箱控制块 */ +static struct rt_mailbox mb; + +/* 用于放邮件的内存池 */ +static char mb_pool[128]; + +static char mb_str1[] = "I'm a mail!"; +static char mb_str2[] = "this is another mail!"; +static char mb_str3[] = "over"; +``` + + +线程 thread1 使用的栈空间、线程控制块,以及线程 thread1 的入口函数,每 100ms 收取一次邮件并打印邮件内容,当收取到结束邮件的时候,脱离邮箱,结束运行。 + +```c +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程 1 入口 */ +static void thread1_entry(void *parameter) +{ + char *str; + + while (1) + { + rt_kprintf("thread1: try to recv a mail\n"); + + /* 从邮箱中收取邮件 */ + if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("thread1: get a mail from mailbox, the content:%s\n", str); + if (str == mb_str3) + break; + + /* 延时 100ms */ + rt_thread_mdelay(100); + } + } + /* 执行邮箱对象脱离 */ + rt_mb_detach(&mb); +} +``` + +线程 thread2 使用的栈空间、线程控制块,以及线程 thread2 的入口函数,每 200ms 发送一封邮件,10 次后发送结束邮件,结束运行 + +```c +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程 2 入口 */ +static void thread2_entry(void *parameter) +{ + rt_uint8_t count; + + count = 0; + while (count < 10) + { + count ++; + if (count & 0x1) + { + /* 发送 mb_str1 地址到邮箱中 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str1); + } + else + { + /* 发送 mb_str2 地址到邮箱中 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str2); + } + + /* 延时 200ms */ + rt_thread_mdelay(200); + } + + /* 发送邮件告诉线程 1,线程 2 已经运行结束 */ + rt_mb_send(&mb, (rt_uint32_t)&mb_str3); +} +``` + +邮箱的示例代码,初始化了邮箱,初始化并启动了线程 thread1 与 thread2。并将函数使用 MSH_CMD_EXPORT 导出命令 + +```c +int mailbox_sample(void) +{ + rt_err_t result; + + /* 初始化一个 mailbox */ + result = rt_mb_init(&mb, + "mbt", /* 名称是 mbt */ + &mb_pool[0], /* 邮箱用到的内存池是 mb_pool */ + sizeof(mb_pool) / 4, /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */ + RT_IPC_FLAG_FIFO); /* 采用 FIFO 方式进行线程等待 */ + if (result != RT_EOK) + { + rt_kprintf("init mailbox failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(mailbox_sample, mailbox sample); +``` + +编译、仿真运行和观察示例应用输出 +-------------------------------- + +编译工程,然后开始仿真。使用控制台 UART\#1 做为 msh 终端,可以看到系统的启动日志,输入 mailbox_sample 命令启动示例应用,示例输出结果如下: + +```c +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Aug 27 2018 +2006 - 2018 Copyright by rt-thread team +msh >mailbox_sample +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:I'm a mail! +msh >thread1: try to recv a mail +thread1: get a mail from mailbox, the content:this is another mail! +… +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:this is another mail! +thread1: try to recv a mail +thread1: get a mail from mailbox, the content:over +``` + +使用 SystemView 工具可以监测示例实际运行过程,示例开始之后现象与实验设计相同。整体流程如下两张图所示,“接收” 表示线程 t1 接收然后挂起,“收取” 表示线程 t1 接收到邮件恢复运行,“发送” 表示 t2 发送邮件。 + +![运行整体图](figures/process71.png) + +起始阶段详细调度如下图所示: + +![运行过程](figures/process72.png) + +放大线程初始化部分: + +![线程初始化过程](figures/process73.png) + +图中各名称对应描述如下表: + +| 名称 | 描述 | +|-----------|-----------------| +| Unified | CPU 当前运行状态 | +| UART ISR | 串口中断 | +| SysTick | 系统时钟 | +| Scheduler | 调度器 | +| Timer | 定时器 | +| thread1 | 线程 thread1 | +| Thread2 | 线程 thread2 | +| tshell | 线程 tshell | +| Idle | 空闲线程 | + +附件 +---- + +整个示例运行流程可以使用工具 SystemView 工具打开附件文件 [mailbox_sample.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/mailbox_sample/mailbox_sample.SVDat) 查看具体细节。注意打开附件时,不要有中文路径。 + diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process80.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process80.png new file mode 100644 index 0000000000000000000000000000000000000000..74d2ddb8f41c1563931831ff07e8271a93e3a9df Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process80.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process81.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process81.png new file mode 100644 index 0000000000000000000000000000000000000000..b02df615a01df61af80e95ea39e60a675cabe3c6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process81.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process82.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process82.png new file mode 100644 index 0000000000000000000000000000000000000000..3577e60d2ce80f7a7b22590859125c9074ad8ba6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/figures/process82.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/msgq_sample.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/msgq_sample.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..20ad671cbbde8a3bc7e6c3403901cceb8c215814 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/msgq_sample.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/msgq_sample.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/msgq_sample.md new file mode 100644 index 0000000000000000000000000000000000000000..07490d017a1135c80c91809ccd95110907059f89 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/msgq_sample/msgq_sample.md @@ -0,0 +1,274 @@ +实验:消息队列的使用 +==================== + +实验目的 +-------- + +- 理解消息队列的基本原理 + +- 使用消息队列进行线程间通信 + +- 在 RT-Thread 中熟练使用消息队列来完成需求 + +实验原理及程序结构 +------------------ + +消息队列能够接收来自线程或中断服务例程中不固定长度的消息,并把消息缓存在自己的内存空间中。其他线程也能够从消息队列中读取相应的消息,而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。消息队列是一种异步的通信方式。 + +### 实验设计 + +本实验使用的例程为:[msgq_sample.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/msgq_sample.c) + +为了体现使用消息队列来达到线程间的通信,本实验设计了 thread1、thread2 两个线程,优先级同为 25,设计了一个消息队列 mqt。 + +线程 thread1 每 50ms 从消息队列接收一次消息,并打印接收到的消息内容,在接收 20 次消息之后,将消息队列脱离、结束线程。 + +线程 thread2 每 10ms 向 mqt 消息队列依次发送 20 次消息,分别是消息 “A”-“T”,第 9 次发送的是一个紧急消息 “I”,发送 20 次后线程运行结束。(注,虽然设置的是 5ms,但是该工程设置的一个 OS +Tock 是 10ms,是最小精度)。 + +通过本实验,用户可以清晰地了解到,线程在使用消息队列时候的线程调度。 + +整个实验运行过程如下图所示,OS Tick 为系统滴答时钟,下面以实验开始后第一个到来的 OS Tick 为第 1 个 OS Tick,过程描述如下: + +![实验运行过程](figures/process80.png) + +(1)在 tshell 线程中初始化一个消息队列 mqt,采用 FIFO 方式进行线程等待;初始化并启动线程 thread1、thread2,优先级同为 25; + +(2)在操作系统的调度下,thread1 首先被投入运行,尝试从消息队列获取消息,消息队列暂时没有消息,线程挂起; + +(3)随后操作系统调度 thread2 投入运行,thread2 发送一个消息 “A”,并打印发送消息内容,随后每 10ms 发送一条消息; + +(4)此时线程 thread1 接收到消息,打印消息内容 “A”,然后每 50ms 接收一次消息; + +(5)在第 100ms 时,thread1 本应接收消息 “C”,但由于队列中有紧急消息,所以 thread1 先接收紧急消息 “I”,之后再顺序接收其他消息。 + +(6)thread2 发送 20 条消息后,结束线程。 + +(7)thread1 接收 20 条消息后,结束线程。 + +### 源程序说明 + +#### 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +#### 示例源码 + +以下定义了待创建线程需要用到的优先级、时间片的宏,消息队列控制块以及存放消息用到的内存池。 + +```C +#include + +#define THREAD_PRIORITY 25 +#define THREAD_TIMESLICE 5 + +/* 消息队列控制块 */ +static struct rt_messagequeue mq; +/* 消息队列中用到的放置消息的内存池 */ +static rt_uint8_t msg_pool[2048]; +``` + +线程 thread1 使用的栈空间、线程控制块,以及线程 thread1 的入口函数,每 50ms 从消息队列中收取消息,并打印消息内容,20 次后结束。 + +```c +ALIGN(RT_ALIGN_SIZE) +static char thread1_stack[1024]; +static struct rt_thread thread1; + +/* 线程 1 入口函数 */ +static void thread1_entry(void *parameter) +{ + char buf = 0; + rt_uint8_t cnt = 0; + + while (1) + { + /* 从消息队列中接收消息 */ + if (rt_mq_recv(&mq, &buf, sizeof(buf), RT_WAITING_FOREVER) == RT_EOK) + { + rt_kprintf("thread1: recv msg from msg queue, the content:%c\n", buf); + if (cnt == 19) + { + break; + } + } + /* 延时 50ms */ + cnt++; + rt_thread_mdelay(50); + } + rt_kprintf("thread1: detach mq \n"); + rt_mq_detach(&mq); +} +``` + +线程 thread2 使用的栈空间、线程控制块,以及线程 thread2 的入口函数,每 5ms 向消息队列中发送消息,并打印消息内容,20 次后结束 + +```c +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程 2 入口 */ +static void thread2_entry(void *parameter) +{ + int result; + char buf = 'A'; + rt_uint8_t cnt = 0; + + while (1) + { + if (cnt == 8) + { + /* 发送紧急消息到消息队列中 */ + result = rt_mq_urgent(&mq, &buf, 1); + if (result != RT_EOK) + { + rt_kprintf("rt_mq_urgent ERR\n"); + } + else + { + rt_kprintf("thread2: send urgent message - %c\n", buf); + } + } + else if (cnt>= 20)/* 发送 20 次消息之后退出 */ + { + rt_kprintf("message queue stop send, thread2 quit\n"); + break; + } + else + { + /* 发送消息到消息队列中 */ + result = rt_mq_send(&mq, &buf, 1); + if (result != RT_EOK) + { + rt_kprintf("rt_mq_send ERR\n"); + } + + rt_kprintf("thread2: send message - %c\n", buf); + } + buf++; + cnt++; + /* 延时 5ms */ + rt_thread_mdelay(5); + } +} +``` + +消息队列的示例代码,初始化了一个消息队列,初始化并启动了 thread1 与 thread2. 并将函数使用 MSH_CMD_EXPORT 导出命令。 + +```c +/* 消息队列示例的初始化 */ +int msgq_sample(void) +{ + rt_err_t result; + + /* 初始化消息队列 */ + result = rt_mq_init(&mq, + "mqt", + &msg_pool[0], /* 内存池指向 msg_pool */ + 1, /* 每个消息的大小是 1 字节 */ + sizeof(msg_pool), /* 内存池的大小是 msg_pool 的大小 */ + RT_IPC_FLAG_FIFO); /* 如果有多个线程等待,按照先来先得到的方法分配消息 */ + + if (result != RT_EOK) + { + rt_kprintf("init message queue failed.\n"); + return -1; + } + + rt_thread_init(&thread1, + "thread1", + thread1_entry, + RT_NULL, + &thread1_stack[0], + sizeof(thread1_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread1); + + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(msgq_sample, msgq sample); +``` + +编译、仿真运行和观察示例应用输出 +-------------------------------- + +编译工程,然后开始仿真。使用控制台 UART\#1 做为 msh 终端,可以看到系统的启动日志,输入 msgq_sample 命令启动示例应用,示例输出结果如下: + +```c +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Aug 24 2018 +2006 - 2018 Copyright by rt-thread team +msh > msgq_sample +msh >thread2: send message - A +thread1: recv msg from msg queue, the content:A +thread2: send message - B +thread2: send message - C +thread2: send message - D +thread2: send message - E +thread1: recv msg from msg queue, the content:B +thread2: send message - F +thread2: send message - G +thread2: send message - H +thread2: send urgent message - I +thread2: send message - J +thread1: recv msg from msg queue, the content:I +thread2: send message - K +thread2: send message - L +thread2: send message - M +thread2: send message - N +thread2: send message - O +thread1: recv msg from msg queue, the content:C +thread2: send message - P +thread2: send message - Q +thread2: send message - R +thread2: send message - S +thread2: send message - T +thread1: recv msg from msg queue, the content:D +message queue stop send, thread2 quit +thread1: recv msg from msg queue, the content:E +thread1: recv msg from msg queue, the content:F +thread1: recv msg from msg queue, the content:G +… +thread1: recv msg from msg queue, the content:T +thread1: detach mq +``` + +使用 SystemView 工具可以监测示例实际运行过程,示例开始之后现象与实验设计相同。整体流程如下图所示,初始化部分细节见第二张图。 + +![运行整体图](figures/process81.png) + +初始化部分细节: + +![初始化部分细节](figures/process82.png) + +图中各名称对应描述如下表: + +| 名称 | 描述 | +|-----------|-----------------| +| Unified | CPU 当前运行状态 | +| UART ISR | 串口中断 | +| SysTick | 系统时钟 | +| Scheduler | 调度器 | +| Timer | 定时器 | +| thread1 | 线程 thread1 | +| thread2 | 线程 thread2 | +| tshell | 线程 tshell | +| Idle | 空闲线程 | + +附件 +---- + +整个示例运行流程可以使用工具 SystemView 工具打开附件文件 [msgq_sample.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/msgq_sample/msgq_sample.SVDat) 查看具体细节。注意打开附件时,不要有中文路径。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/preparations.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/preparations.md new file mode 100644 index 0000000000000000000000000000000000000000..94d0500671cb8c7d017db14f87dc6425ca71f6ed --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/preparations.md @@ -0,0 +1,138 @@ +# RT-Thread 实验环境搭建 + +## 目的 + +- 本章的目的是让初学者了解 RT-Thread 运行环境,将以 MDK5 为例,搭建 RT-Thread 运行环境。 + +## MDK5 安装 + +已经安装 MDK5 的可以直接略过此步骤。 + +在运行 RT-Thread 操作系统前,我们需要安装 MDK-ARM 5.24(正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。这里采用了 16K 编译代码限制的评估版 5.24 版本,如果要解除 16K 编译代码限制,请购买 MDK-ARM 正式版。先从 www.keil.com 官方网站下载 MDK-ARM 评估版:。 + +在下载时,需要填一些个人基本信息,请填写相应的完整信息,然后开始下载。下载完成后,鼠标双击运行,会出现如下图所示的软件安装画面: + +![MDK 安装图 1](figures/1_install.png) + +**步骤 1** 这是 MDK-ARM 的安装说明,点击 “Next>>” 进入下一画面,如下图所示: + +![MDK 安装图 2](figures/2_install.png) + +**步骤 2** 在 “I agree to all the terms of the preceding License Agreement” 前的选择框中点击选择 “√”,并点击 “Next >>” 进入下一步安装,如下图所示: + +![MDK 安装图 3](figures/3_install.png) + +**步骤 3** 点击 “Browse…” 选择 MDK-ARM 的安装目录或者直接在 “Destination Folder” 下的文本框中输入安装路径,这里我们默认 “C:/Keil” 即可,然后点击 “Next>>” 进入下一步安装,如下图所示: + +![MDK 安装图 4](figures/4_install.png) + +**步骤 4** 在 “First Name” 后输入您的名字,“Last Name” 后输入您的姓,“Company Name” 后输入您的公司名称,“E-mail” 后输入您的邮箱地址,然后点击 “Next>>” 进行安装,等待一段时间后,安装结束,出现如下图所示画面: + +![MDK 安装图 5](figures/5_install.png) + +**步骤 5** 图中的默认选择不需改动,直接点击 “Next” 进入如下图所示画面: + +![MDK 安装图 6](figures/6_install.png) + +**步骤 6** 在这里可以点击 “Finish” 完成整个 MDK-ARM 软件的安装。 + +有了 MDK-ARM 利器,就可以轻松开始 RT-Thread 操作系统之旅,一起探索实时操作系统的奥秘。 + +注:MDK-ARM 正式版是收费的,如果您希望能够编译出更大体积的二进制文件,请购买 MDK-ARM 正式版。RT-Thread 操作系统也支持自由软件基金会的 GNU GCC 编译器,这是一款开源的编译器,想要了解如何使用 GNU 的相关工具请参考 RT-Thread 网站上的相关文档。 + +## 运行仿真 + +打开配合本实验的代码工程 [RT-Thread Simulator 例程](https://www.rt-thread.org/document/site/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z),例程源码中包含:RT-Thread 内核、FinSH 控制台、串口驱动、GPIO 驱动这些内容,支持 STM32F10X 系列 MCU,源码的目录结构如下图所示: + +![源码的目录结构](figures/code_structure.png) + +在目录下,有一个 project.uvprojx 文件,它是本文内容所引述的例程中的一个 MDK5 工程文件,双击 “project.uvprojx” 图标,打开此工程文件,如下图所示: + +![工程文件](figures/code_structure2.png) + +现在我们点击一下窗口上方工具栏中的按钮![编译](figures/compile.png),对该工程进行编译,如下图所示: + +![编译结果](figures/compile_result.png) + +编译的结果显示在窗口下方的 “Build” 栏中,没什么意外的话,最后一行会显示 “0 Error(s), * Warning(s).”,即无任何错误和警告。 + +在编译完 RT-Thread/STM32 后,我们可以通过 MDK-ARM 的模拟器来仿真运行 RT-Thread: + +- 点击下图中的按钮 1 或直接按 “Ctrl+F5” 进入仿真界面。 +- 点击下图中的按钮 2 或直接按 “F5” 开始仿真。 +- 点击下图中的按钮 3 或者选择菜单栏中的 “View → Serial Windows →UART\#1”,打开串口 1 窗口。 + +![button](figures/button.png) + +可以看到串口输出了 RT-Thread 的 LOGO,其模拟运行的结果如下图所示: + +![模拟运行的结果图](figures/simulation.png) + +## FinSH 命令行中启动线程 + +RT-Thread 提供 FinSH 功能,用于调试或查看系统信息,msh 表示 FinSH 处于一种传统命令行模式,此模式下可以使用类似于 dos/bash 等传统的 shell 命令。 + +比如,我们可以通过输入 “help + 回车” 或者直接按下 Tab 键,输出当前系统所支持的所有命令,如下: + +```c +msh >help +RT-Thread shell commands: +thread_sample - thread sample +timer_sample - timer sample +semaphore_sample - semaphore sample +mutex_sample - mutex sample +event_sample - event sample +mailbox_sample - mailbox sample +msgq_sample - msgq sample +signal_sample - signal sample +mempool_sample - mempool sample +dynmem_sample - dynmem sample +interrupt_sample - interrupt sample +idle_hook_sample - idle hook sample +producer_consumer - producer_consumer sample +timeslice_sample - timeslice sample +scheduler_hook - scheduler_hook sample +pri_inversion - prio_inversion sample +version - show RT-Thread version information +list_thread - list thread +list_sem - list semaphore in system +list_event - list event in system +list_mutex - list mutex in system +list_mailbox - list mail box in system +list_msgqueue - list message queue in system +list_memheap - list memory heap in system +list_mempool - list memory pool in system +list_timer - list timer in system +list_device - list device in system +help - RT-Thread shell help. +ps - List threads in the system. +time - Execute command with time. +free - Show the memory usage in the system. + +msh > +``` + +此时可以输入列表中的命令,如输入 list_thread 命令显示系统当前正在运行的线程,结果显示为 tshell(shell 线程)线程与 tidle(空闲线程)线程: + +```c +msh >list_thread +thread pri status sp stack size max used left tick error +------ --- ------- ---------- ---------- ------ ---------- --- +tshell 20 ready 0x00000080 0x00001000 07% 0x0000000a 000 +tidle 31 ready 0x00000054 0x00000100 32% 0x00000016 000 +msh > +``` + +FinSH 具有命令自动补全功能,输入命令的部分字符(前几个字母,注意区分大小写),按下 Tab 键,则系统会根据当前已输入的字符,从系统中查找已经注册好的相关命令,如果找到与输入相关的命令,则会将完整的命令显示在终端上。 + +如:要使用 version 命令,可以先输入 “v”,再按下 Tab 键,可以发现系统会在下方补全了有关 “v” 开头的命令:version,此时只需要回车,即可查看该命令的执行结果。 + +每一个实验都会导出一个命令,做某个实验时,键入该实验对应的命令并回车,就会对该实验开始仿真。复位程序可以点击 `"RST"` 按钮,退出仿真需要再次点击仿真按钮。 + +## SystemView 工具介绍 + +SystemView 是一个可以在线调试嵌入式系统的工具,它可以分析有哪些中断、任务执行了,以及这些中断、任务执行的先后关系。还可以查看一些内核对象持有和释放的时间点,比如信号量、互斥量、事件、消息队列等,这在开发和处理具有多个线程和事件的复杂系统时尤其有效,能帮助用户进行系统调试和分析、显著缩短开发和调试时间,提高开发效率。 + +本实验采用 SystemView 对系统执行的线程及其状态进行可视化监控分析,通过该工具将实验的运行状态与时间的关系保存下来,大家可以下载安装该工具,打开实验附带的附件,了解实验过程及细节。 + +下载 SystemView 分析工具:https://www.segger.com/products/development-tools/systemview/ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process50.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process50.png new file mode 100644 index 0000000000000000000000000000000000000000..3c20bdc9945462d4e3f9d162c3d5a6ebf64c733b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process50.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process51.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process51.png new file mode 100644 index 0000000000000000000000000000000000000000..434b2a4898a3fabbce06a1604f71ffa63210c5ac Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process51.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process52.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process52.png new file mode 100644 index 0000000000000000000000000000000000000000..4c0d5c27160cd088a62c67f6777598e97e10c493 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process52.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process53.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process53.png new file mode 100644 index 0000000000000000000000000000000000000000..44313965f34c02e38d7f228127bb7a3ac34c605f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process53.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process54.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process54.png new file mode 100644 index 0000000000000000000000000000000000000000..fc506a6b3cdf45d2eccefd8e45c667e6f5b38d94 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process54.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process55.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process55.png new file mode 100644 index 0000000000000000000000000000000000000000..86870d488115118f9a7f653f64f4bd37c32cbdb1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process55.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process56.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process56.png new file mode 100644 index 0000000000000000000000000000000000000000..5caea10046f8b311f326f633f42ab8c5742608e8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process56.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process57.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process57.png new file mode 100644 index 0000000000000000000000000000000000000000..258657068ac95278aa2ddfb81613e04d4d183200 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/figures/process57.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/pri_inversion.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/pri_inversion.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..24978979c574ead34e33bc7078e7fc03e5286828 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/pri_inversion.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/pri_inversion.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/pri_inversion.md new file mode 100644 index 0000000000000000000000000000000000000000..cf9a8f7fb8c0f925be89705c6b7f004254f1184a --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/pri_inversion/pri_inversion.md @@ -0,0 +1,278 @@ +实验:互斥量——优先级继承 +======================== + +实验目的 +-------- + +- 理解互斥量的基本原理; + +- 使用互斥量来达到线程间同步并探索其中的优先级继承问题; + +- 在 RT-Thread 中熟练使用互斥量来完成需求。 + +实验原理及程序结构 +------------------ + +互斥量是一种特殊的二值信号量。它和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。 + +互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。 + +注意:需要切记的是互斥量不能在中断服务例程中使用。 + +### 实验设计 + +本实验使用的例程为:[priority_inversion.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/priority_inversion.c) + +为了体现使用互斥量来达到线程间的同步,并体现优先级继承的现象,本实验设计了 thread1、thread2、thread3 三个线程,优先级分别为 9、10、11,设计了一个互斥量 mutex。 + +线程 thread1 优先级最高,先执行 100ms 延时,之后再打印线程 2 与线程 3 的优先级信息——用于检查线程 +thread3 的优先级是否被提升为 thread2 的优先级。 + +线程 thread2 进入后先打印自己的优先级,然后进入 50ms 延时,延时结束后获取互斥量 mutex,获取到互斥量之后再释放互斥量 mutex。 + +线程 thread3 进入后先打印自己的优先级,然后获取互斥量 mutex,获取到互斥量之后进入 500ms 的循环,循环结束后将互斥量释放。 + +整体情况就是:线程 3 先持有互斥量,而后线程 2 试图持有互斥量,此时线程 3 +的优先级应该被提升为和线程 2 +的优先级相同,然后线程 1 打印线程 2 与线程 3 的优先级信息。 + +通过本实验,用户可以清晰地了解到,互斥量在线程间同步的作用、互斥量的优先级继承性以及互斥量连续获取不会造成死锁。 + +整个实验运行过程如下图所示,过程描述如下: + +![实验运行过程](figures/process50.png) + +(1)在 tshell 线程中创建一个互斥量 mutex,初始化为先进先出型;并分别创建、启动线程 thread1、thread2、thread3,优先级分别为 9、10、11; + +(2)thread1 开始执行,延时 100ms 将自己挂起; + +(3)thread2 开始执行,打印自己的优先级信息,开始延时 50ms 将自己挂起; + +(4)thread3 获取互斥量,然后使用循环 500ms 来模拟 thread3 运行 500ms,之后释放互斥量。 + +(5)在 thread2 延时 50ms 结束时,试图获取互斥量,由于互斥量被 thread3 持有,所以获取失败,自身挂起。(此时,thread3 的优先级应该是被提升为和 thread2 的优先级相同)。 + +(6)在 thread1 延时 100ms 结束时,打印 thread2 与 thread3 的优先级信息,检查两者优先级是否相同。如果相同,那么说明互斥量确实解决了优先级翻转的问题,进行了优先级继承。 + +### 源程序说明 + +#### RT-Thread 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +#### 示例源码 + +定义了待创建线程需要用到的优先级,栈空间,时间片的宏,以及线程控制块句柄和互斥量控制块句柄 + +```c +#include + +/* 指向线程控制块的指针 */ +static rt_thread_t tid1 = RT_NULL; +static rt_thread_t tid2 = RT_NULL; +static rt_thread_t tid3 = RT_NULL; +static rt_mutex_t mutex = RT_NULL; + +#define THREAD_PRIORITY 10 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 +``` + +线程 thread1 入口函数,首先让低优先级先运行,之后打印 thread2 与 thread3 的优先级,验证互斥量优先级继承。 + +```c +/* 线程 1 入口 */ +static void thread1_entry(void *parameter) +{ + /* 先让低优先级线程运行 */ + rt_thread_mdelay(100); + + /* 此时 thread3 持有 mutex,并且 thread2 等待持有 mutex */ + + /* 检查天 thread2 与 thread3 的优先级情况 */ + if (tid2->current_priority != tid3->current_priority) + { + /* 优先级不相同,测试失败 */ + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + rt_kprintf("test failed.\n"); + return; + } + else + { + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + rt_kprintf("test OK.\n"); + } +} +``` + +线程 thread2 的入口函数,打印优先级信息之后,先让低优先级的 thread3 先运行,然后尝试获取互斥量,获取到后释放互斥量。 + +```c +/* 线程 2 入口 */ +static void thread2_entry(void *parameter) +{ + rt_err_t result; + + rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority); + + /* 先让低优先级线程运行 */ + rt_thread_mdelay(50); + + /* + * 试图持有互斥锁,此时 thread3 持有,应把 thread3 的优先级提升 + * 到 thread2 相同的优先级 + */ + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + + if (result == RT_EOK) + { + /* 释放互斥锁 */ + rt_mutex_release(mutex); + } +} +``` + +线程 thread3 的入口函数,先打印自身优先级信息,然后获取互斥量,获取到互斥量之后进行 500ms 的长时间循环,使 thread3 运行 500ms 左右,之后释放互斥量。 + +```c +/* 线程 3 入口 */ +static void thread3_entry(void *parameter) +{ + rt_tick_t tick; + rt_err_t result; + + rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority); + + result = rt_mutex_take(mutex, RT_WAITING_FOREVER); + if (result != RT_EOK) + { + rt_kprintf("thread3 take a mutex, failed.\n"); + } + + /* 做一个长时间的循环,500ms */ + tick = rt_tick_get(); + while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ; + + rt_mutex_release(mutex); +} +``` + +互斥量优先级继承的例子,解决优先级翻转问题。示例函数首先创建互斥量,再创建、启动了线程 thread1、thread2、thread3。并将函数使用 MSH_CMD_EXPORT 导出命令。 + +```c +int pri_inversion(void) +{ + /* 创建互斥锁 */ + mutex = rt_mutex_create("mutex", RT_IPC_FLAG_FIFO); + if (mutex == RT_NULL) + { + rt_kprintf("create dynamic mutex failed.\n"); + return -1; + } + + /* 创建线程 1 */ + tid1 = rt_thread_create("thread1", + thread1_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* 创建线程 2 */ + tid2 = rt_thread_create("thread2", + thread2_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid2 != RT_NULL) + rt_thread_startup(tid2); + + /* 创建线程 3 */ + tid3 = rt_thread_create("thread3", + thread3_entry, + RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, THREAD_TIMESLICE); + if (tid3 != RT_NULL) + rt_thread_startup(tid3); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(pri_inversion, pri_inversion sample); +``` + +编译、仿真运行和观察示例应用输出 +-------------------------------- + +编译工程,然后开始仿真。使用控制台 UART#1 做为 msh 终端,可以看到系统的启动日志,输入 mutex_simple_init 命令启动示例应用,示例输出结果如下: + +```c +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Aug 27 2018 +2006 - 2018 Copyright by rt-thread team +msh >pri_inversion +the priority of thread2 is: 10 +the priority of thread3 is: 11 +the priority of thread2 is: 10 +the priority of thread3 is: 10 +test OK. +``` + +例程演示了互斥量的使用方法。线程 3 先持有互斥量,而后线程 2 试图持有互斥量,此时线程 3 的优先级被提升为和线程 2 的优先级相同。 + +使用 SystemView 工具可以监测示例实际运行过程,示例开始之后现象与实验设计相同,如下几张图所示。 + +几个阶段详细调度信息如下: + +![运行整体图](figures/process51.png) + +第一、二部分放大图如下: + +![第一二部分放大图](figures/process52.png) + +第一部分细节图如下: + +![第一部分细节图 - 创建线程 1、2](figures/process53.png) + +第二部分细节图如下: + +![第二部分细节图](figures/process54.png) + +第三部分细节图如下,thread2 获取互斥量详细过程: + +![第三部分细节图](figures/process55.png) + +第四部分细节图如下: + +![第四部分细节图](figures/process56.png) + +第五部分细节图如下,结束阶段详细调度过程: + +![第五部分细节图](figures/process57.png) + +图中各名称对应描述如下表: + +| 名称 | 描述 | +|-----------|-----------------| +| Unified | CPU 当前运行状态 | +| UART ISR | 串口中断 | +| SysTick | 系统时钟 | +| Scheduler | 调度器 | +| Timer | 定时器 | +| thread3 | 线程 thread3 | +| thread2 | 线程 thread2 | +| thread1 | 线程 thread1 | +| tshell | 线程 tshell | +| Idle | 空闲线程 | + +附件 +---- + +整个示例运行流程可以使用工具 SystemView 工具打开附件文件 [pri_inversion.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/pri_inversion/pri_inversion.SVDat) 查看具体细节。注意打开附件时,不要有中文路径。 + diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process40.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process40.png new file mode 100644 index 0000000000000000000000000000000000000000..d3265275b4b65287d7229be51b275d56101dc239 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process40.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process41.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process41.png new file mode 100644 index 0000000000000000000000000000000000000000..e9e35fdd5b105be4ac078235773ba324d8d21457 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process41.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process42.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process42.png new file mode 100644 index 0000000000000000000000000000000000000000..54d8474391a0f08637014efc95fe4982daa1778f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process42.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process43.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process43.png new file mode 100644 index 0000000000000000000000000000000000000000..100b52eeee2cb42251d64c5e51e3989ec412a0a6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process43.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process44.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process44.png new file mode 100644 index 0000000000000000000000000000000000000000..1b7a3bdaba89a5cc31e43e54e62fc5bdfc1513f3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process44.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process45.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process45.png new file mode 100644 index 0000000000000000000000000000000000000000..cf5308aee33789fe136ac9220d968d9f04a693cc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/figures/process45.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/producer_consumer.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/producer_consumer.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..c6867016b9963342d9c3030ff565c592733e9ac9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/producer_consumer.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/producer_consumer.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/producer_consumer.md new file mode 100644 index 0000000000000000000000000000000000000000..6fac2ed4eebf052471248c5ca2947c069e14aac5 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/producer_consumer/producer_consumer.md @@ -0,0 +1,253 @@ +实验:信号量—生产者消费者问题 +============================= + +实验目的 +-------- + +- 理解信号量的基本原理; + +- 使用信号量来达到线程间同步; + +- 理解资源计数适合于线程间工作处理速度不匹配的场合; + +- 在 RT-Thread 中熟练使用信号量来完成需求。 + +实验原理及程序结构 +------------------ + +信号量在大于 0 时才能获取,在中断、线程中均可释放信号量。 + +### 实验设计 + +本实验使用的例程为:[producer_consumer.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/producer_consumer.c) + +为了体现使用信号量来达到线程间的同步,本实验设计了 producer、consumer 两个线程,producer 优先级为 24,consumer 优先级为 26。线程 producer 每生产一个数据进入 20ms 延时,生产 10 个数据后结束。线程 consumer 每消费一个数据进入 5 +0ms 延时,消费 10 个数据后结束。通过本实验,用户可以清晰地了解到,信号量在线程同步以及资源计数时起到的作用。 + +整个实验运行过程如下图所示,OS Tick 为系统滴答时钟,下面以实验开始后第一个到来的 OS Tick 为第 1 个 OS Tick,过程描述如下: + +![实验运行过程](figures/process40.png) + +(1)在 tshell 线程中初始化 3 个信号量,lock 初始化为 1(用作保护临界区,保护数组), +empty 初始化为 5, full 初始化为 0;信号量情况: + +![信号量情况 1](figures/process41.png) + +(2)创建并启动线程 producer,优先级为 24;创建并启动线程和 consumer,优先级为 26; + +(3)在操作系统的调度下,producer 优先级高,首先被投入运行; + +(4)producer 获取一个 empty 信号量,产生一个数据放入数组,再释放一个 full 信号量,然后进入 2 OS Tick 延时;之后的信号量情况: + +![信号量情况 2](figures/process42.png) + +(5)随后 consumer 投入运行,获取一个 full 信号量,消费一个数据用于累加,再释放一个 empty 信号量,然后进入 5 OS Tick 延时;之后的信号量情况: + +![信号量情况 3](figures/process43.png) + +(6)由于生产速度 > 消费速度,所以在某一时刻会存在 full = 5 / empty = 0 的情况,如下: + +![信号量情况 4](figures/process44.png) + +比如第 18 个 OS Tick 时,producer 延时结束,操作系统调度 producer 投入运行,获取一个 empty 信号量,由于此时 empty 信号量为 0,producer 由于获取不到信号量挂起;等待有 empty 信号时,才可以继续生产。 + +(7)直到 producer 产生 10 个 num 后,producer 线程结束,被系统删除。 + +(8)直到 consumer 消费 10 个 num 后,consumer 线程结束,被系统删除。 + +### 源程序说明 + +#### RT-Thread 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +#### 示例源码 + +定义了待创建线程需要用到的优先级,栈空间,时间片的宏,以及生产消费过程中用于存放产生数据的数字和相关变量、线程句柄、信号量控制块。 + +``` +#include + +#define THREAD_PRIORITY 6 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +/* 定义最大 5 个元素能够被产生 */ +#define MAXSEM 5 + +/* 用于放置生产的整数数组 */ +rt_uint32_t array[MAXSEM]; + +/* 指向生产者、消费者在 array 数组中的读写位置 */ +static rt_uint32_t set, get; + +/* 指向线程控制块的指针 */ +static rt_thread_t producer_tid = RT_NULL; +static rt_thread_t consumer_tid = RT_NULL; + +struct rt_semaphore sem_lock; +struct rt_semaphore sem_empty, sem_full; +``` + +生产者 producer 线程的入口函数,每 20ms 就获取一个空位(获取不到时挂起),上锁 , 产生一个数字写入数组 , 解锁,释放一个满位,10 次后结束。 + +```c +/* 生产者线程入口 */ +void producer_thread_entry(void *parameter) +{ + int cnt = 0; + + /* 运行 10 次 */ + while (cnt < 10) + { + /* 获取一个空位 */ + rt_sem_take(&sem_empty, RT_WAITING_FOREVER); + + /* 修改 array 内容,上锁 */ + rt_sem_take(&sem_lock, RT_WAITING_FOREVER); + array[set % MAXSEM] = cnt + 1; + rt_kprintf("the producer generates a number: %d\n", array[set % MAXSEM]); + set++; + rt_sem_release(&sem_lock); + + /* 发布一个满位 */ + rt_sem_release(&sem_full); + cnt++; + + /* 暂停一段时间 */ + rt_thread_mdelay(20); + } + + rt_kprintf("the producer exit!\n"); +} +``` + +消费者 consumer 线程的入口函数,每 50ms 获取一个满位(获取不到时挂起),上锁, 将数组中的内容相加, 解锁,释放一个空位,10 次后结束。 + +```c +/* 消费者线程入口 */ +void consumer_thread_entry(void *parameter) +{ + rt_uint32_t sum = 0; + + while (1) + { + /* 获取一个满位 */ + rt_sem_take(&sem_full, RT_WAITING_FOREVER); + + /* 临界区,上锁进行操作 */ + rt_sem_take(&sem_lock, RT_WAITING_FOREVER); + sum += array[get % MAXSEM]; + rt_kprintf("the consumer[%d] get a number: %d\n", (get % MAXSEM), array[get % MAXSEM]); + get++; + rt_sem_release(&sem_lock); + + /* 释放一个空位 */ + rt_sem_release(&sem_empty); + + /* 生产者生产到 10 个数目,停止,消费者线程相应停止 */ + if (get == 10) break; + + /* 暂停一小会时间 */ + rt_thread_mdelay(50); + } + + rt_kprintf("the consumer sum is: %d\n", sum); + rt_kprintf("the consumer exit!\n"); +} +``` + +生产者与消费者问题的示例函数,示例函数首先初始化了 3 个信号量,创建并启动生产者线程 producer,然后创建、启动消费者线程 consumer。并将函数使用 MSH_CMD_EXPORT 导出命令。 + +```c +int producer_consumer(void) +{ + set = 0; + get = 0; + + /* 初始化 3 个信号量 */ + rt_sem_init(&sem_lock, "lock", 1, RT_IPC_FLAG_FIFO); + rt_sem_init(&sem_empty, "empty", MAXSEM, RT_IPC_FLAG_FIFO); + rt_sem_init(&sem_full, "full", 0, RT_IPC_FLAG_FIFO); + + /* 创建生产者线程 */ + producer_tid = rt_thread_create("producer", + producer_thread_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + if (producer_tid != RT_NULL) + rt_thread_startup(producer_tid); + + /* 创建消费者线程 */ + consumer_tid = rt_thread_create("consumer", + consumer_thread_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY + 1, THREAD_TIMESLICE); + if (consumer_tid != RT_NULL) + rt_thread_startup(consumer_tid); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(producer_consumer, producer_consumer sample); +``` + +编译、仿真运行和观察示例应用输出 +-------------------------------- + +编译工程,然后开始仿真。使用控制台 UART#1 做为 msh 终端,可以看到系统的启动日志,输入 producer_consumer 命令启动示例应用,示例输出结果如下: + +```c +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Aug 27 2018 +2006 - 2018 Copyright by rt-thread team +msh >producer_consumer +the producer generates a number: 1 +the consumer[0] get a number: 1 +msh >the producer generates a number: 2 +the producer generates a number: 3 +the consumer[1] get a number: 2 +the producer generates a number: 4 +the producer generates a number: 5 +the producer generates a number: 6 +the consumer[2] get a number: 3 +the producer generates a number: 7 +the producer generates a number: 8 +the consumer[3] get a number: 4 +the producer generates a number: 9 +the consumer[4] get a number: 5 +the producer generates a number: 10 +the producer exit! +the consumer[0] get a number: 6 +the consumer[1] get a number: 7 +the consumer[2] get a number: 8 +the consumer[3] get a number: 9 +the consumer[4] get a number: 10 +the consumer sum is: 55 +the consumer exit! +``` + +使用 SystemView 工具可以监测示例实际运行过程,示例开始之后现象与实验设计相同,生产者每 20ms 生产一个数据,生产 10 个数据后结束,且最多存在 5 个未被消费的数据。消费者每 50ms 消费一个数据。如下图所示,图中红色数字表示当前线程执行之后 empty 信号量的值。 + +![示例运行流程图](figures/process45.png) + +图中各名称对应描述如下表: + +| 名称 | 描述 | +|-----------|-----------------| +| Unified | CPU 当前运行状态 | +| UART ISR | 串口中断 | +| SysTick | 系统时钟 | +| Scheduler | 调度器 | +| producer | 线程 producer | +| consumer | 线程 consumer | +| Timer | 定时器 | +| tshell | 线程 tshell | +| Idle | 空闲线程 | + +附件 +---- + +整个示例运行流程可以使用工具 SystemView 工具打开附件文件 [producer_consumer.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/producer_consumer/producer_consumer.SVDat) 查看具体细节。注意打开附件时,不要有中文路径。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/signal_sample/signal_sample.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/signal_sample/signal_sample.md new file mode 100644 index 0000000000000000000000000000000000000000..c87da4104a62c5df80fe117c7ca2fac04c231753 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/signal_sample/signal_sample.md @@ -0,0 +1,125 @@ +实验:信号的使用 +================ + +实验目的 +-------- + +- 理解信号的概念 + +- 使用信号进行线程间通信 + +- 在 RT-Thread 中熟练使用信号来完成需求 + +实验原理及程序结构 +------------------ + +信号(又称为软中断信号),在软件层次上是对中断机制的一种模拟,在原理上,一个线程收到一个信号与处理器收到一个中断请求是类似的。 + +### 实验设计 + +本实验使用的例程为:[signal_sample.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/signal_sample.c) + +实验设计一个线程 thread1,并安装信号解除阻塞,通过给该线程发送信号,信号会触发线程执行一个软中断函数。 + +## 源程序说明 + +### 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +### 示例源码 + +定义了线程用到的优先级、线程栈、时间片等参数: + +```c +#include + +#define THREAD_PRIORITY 25 +#define THREAD_STACK_SIZE 512 +#define THREAD_TIMESLICE 5 + +static rt_thread_t tid1 = RT_NULL; +``` + +线程 1 的入口函数,在线程 thread1 中安装信号并取消阻塞,运行一个打印计数的循环: + +```c +/* 线程 1 的信号处理函数 */ +void thread1_signal_handler(int sig) +{ + rt_kprintf("thread1 received signal %d\n", sig); +} + +/* 线程 1 的入口函数 */ +static void thread1_entry(void *parameter) +{ + int cnt = 0; + + /* 安装信号 */ + rt_signal_install(SIGUSR1, thread1_signal_handler); + rt_signal_unmask(SIGUSR1); + + /* 运行 10 次 */ + while (cnt < 10) + { + /* 线程 1 采用低优先级运行,一直打印计数值 */ + rt_kprintf("thread1 count : %d\n", cnt); + + cnt++; + rt_thread_mdelay(100); + } +} +``` + +信号示例函数,创建线程 tread1,300ms 后发送信号,并将函数使用 MSH_CMD_EXPORT 导出命令。 + +```c +/* 信号示例的初始化 */ +int signal_sample(void) +{ + /* 创建线程 1 */ + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + rt_thread_mdelay(300); + + /* 发送信号 SIGUSR1 给线程 1 */ + rt_thread_kill(tid1, SIGUSR1); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(signal_sample, signal sample); +``` + +编译、运行和观察示例应用输出 +---------------------------- + +编译工程,然后开始仿真。使用控制台 UART\#1 做为 msh 终端,可以看到系统的启动日志,输入 dynmem_sample 命令启动示例应用,示例输出结果如下: + +```c +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Aug 24 2018 +2006 - 2018 Copyright by rt-thread team +msh >signal_sample +thread1 count : 0 +thread1 count : 1 +thread1 count : 2 +msh >thread1 received signal 10 +thread1 count : 3 +thread1 count : 4 +thread1 count : 5 +thread1 count : 6 +thread1 count : 7 +thread1 count : 8 +thread1 count : 9 +``` + +例程中,SIGUSR1 定义为 10,首先线程安装信号并解除阻塞;然后发送信号给线程,信号会触发线程运行一个软中断函数。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process.png new file mode 100644 index 0000000000000000000000000000000000000000..a50ddf6509a4a5c2813e592d5dbbd4d554029b5f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process1.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process1.png new file mode 100644 index 0000000000000000000000000000000000000000..d0aa5f2a06db7b986f16a1e72cc779d7298206ac Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process2.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process2.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d485ad0be74a87ce15b79af70425334af2193c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process3.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process3.png new file mode 100644 index 0000000000000000000000000000000000000000..38955c9b697c0af742c9cc8118e750d3059ca08c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/figures/process3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/thread_sample.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/thread_sample.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..0d7dd8bfa5d23524fd7890872231fc73da5bd6eb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/thread_sample.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/thread_sample.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/thread_sample.md new file mode 100644 index 0000000000000000000000000000000000000000..77b7bf20de78a7af12bddf2eb2adb5ece8e949f6 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/thread_sample/thread_sample.md @@ -0,0 +1,200 @@ +实验:线程的使用 +================ + +实验目的 +-------- + +- 理解线程创建、初始化与自动脱离的基本原理; + +- 理解高优先级线程抢占低优先级线程运行; + +- 掌握 RT-Thread 中线程的动态创建、静态初始化; + +- 在 RT-Thread 中熟练使用线程来完成需求。 + +实验原理及程序结构 +------------------ + +线程,即任务的载体。一般被设计成 while(1) 的循环模式,但在循环中一定要有让出 CPU 使用权的动作。如果是可以执行完毕的线程,则系统会自动将执行完毕的线程进行删除 / 脱离。 + +### 实验设计 + +本实验使用的例程为:[thread_sample.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/thread_sample.c) + +为了体现线程的创建、初始化与脱离,本实验设计了 thread1、thread2 两个线程。thread1 是创建的动态线程,优先级为 25;Thread2 初始化的静态线程,优先级为 24。 + +优先级较高的 Thread2 抢占低优先级的 thread1,执行完毕一段程序后自动被系统脱离。 + +优先级较低的 thread1 被设计成死循环,循环中有让出 CPU 使用权的动作 -- 使用了 delay 函数。该线程在 thread2 退出运行之后开始运行,每隔一段时间运行一次,并一直循环运行下去。 + +通过本实验,用户可以清晰地了解到线程在本实验中的状态变迁情况。 + +整个实验运行过程如下图所示,描述如下: + +![实验运行过程](figures/process.png) + +(1)在 tshell 线程 (优先级 20) 中创建线程 thread1 和初始化 thread2,thread1 优先级为 25,thread2 优先级为 24; + +(2) 启动线程 thread1 和 thread2,使 thread1 和 thread2 处于就绪状态; + +(3)随后 tshell 线程挂起,在操作系统的调度下,优先级较高的 thread2 首先被投入运行; + +(4) thread2 是可执行完毕线程,运行完毕打印之后,系统自动删除 thread2; + +(5) thread1 得以运行,打印信息之后执行延时将自己挂起; + +(6) 系统中没有优先级更高的就绪队列,开始执行空闲线程; + +(7) 延时时间到,执行 thread1; + +(8) 循环(5)~(7)。 + +### 源程序说明 + +#### 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +#### 示例源码 + +定义了待创建线程需要用到的优先级,栈空间,时间片的宏,定义线程 thread1 的线程句柄: + +```c +# include +# define THREAD_PRIORITY 25 +# define THREAD_STACK_SIZE 512 +# define THREAD_TIMESLICE 5 +static rt_thread_t tid1 = RT_NULL; +``` + +线程 thread1 入口函数,每 500ms 打印一次计数值 + +```c +/* 线程 1 的入口函数 */ +static void thread1_entry(void *parameter) +{ + rt_uint32_t count = 0; + + while (1) + { + /* 线程 1 采用低优先级运行,一直打印计数值 */ + rt_kprintf("thread1 count: %d\n", count ++); + rt_thread_mdelay(500); + } +} +``` + +线程 thread2 线程栈、控制块以及线程 2 入口函数的定义,线程 2 打印计数,10 次后退出。 + +```c +ALIGN(RT_ALIGN_SIZE) +static char thread2_stack[1024]; +static struct rt_thread thread2; + +/* 线程 2 入口 */ +static void thread2_entry(void *param) +{ + rt_uint32_t count = 0; + + /* 线程 2 拥有较高的优先级,以抢占线程 1 而获得执行 */ + for (count = 0; count < 10 ; count++) + { + /* 线程 2 打印计数值 */ + rt_kprintf("thread2 count: %d\n", count); + } + rt_kprintf("thread2 exit\n"); + + /* 线程 2 运行结束后也将自动被系统脱离 */ +} +``` + +例程代码,其中创建了线程 thread1,初始化了线程 thread2,并将函数使用 MSH_CMD_EXPORT 导出命令。 + +```c +/* 线程示例 */ +int thread_sample(void) +{ + /* 创建线程 1,名称是 thread1,入口是 thread1_entry*/ + tid1 = rt_thread_create("thread1", + thread1_entry, RT_NULL, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + + /* 如果获得线程控制块,启动这个线程 */ + if (tid1 != RT_NULL) + rt_thread_startup(tid1); + + /* 初始化线程 2,名称是 thread2,入口是 thread2_entry */ + rt_thread_init(&thread2, + "thread2", + thread2_entry, + RT_NULL, + &thread2_stack[0], + sizeof(thread2_stack), + THREAD_PRIORITY - 1, THREAD_TIMESLICE); + rt_thread_startup(&thread2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(thread_sample, thread sample); +``` + +编译、仿真运行和观察示例应用输出 +-------------------------------- + +编译工程,然后开始仿真。使用控制台 UART#1 做为 msh 终端,可以看到系统的启动日志,输入 thread_sample 命令启动示例应用,示例输出结果如下: + +```c + \ | / + - RT - Thread Operating System + / | \ 3.1.0 build Aug 24 2018 + 2006 - 2018 Copyright by rt-thread team +msh >thread_sample +msh >thread2 count: 0 +thread2 count: 1 +thread2 count: 2 +thread2 count: 3 +thread2 count: 4 +thread2 count: 5 +thread2 count: 6 +thread2 count: 7 +thread2 count: 8 +thread2 count: 9 +thread2 exit +thread1 count: 0 +thread1 count: 1 +thread1 count: 2 +thread1 count: 3 +... +``` + +使用 SystemView 工具可以监测示例实际运行过程,如下三图所示,可以看到实验的实际运行流程与实验设计的流程一致,thread2 运行一段时间结束,thread1 每隔一段时间运行一次,并一直循环运行下去。 + +![实验总过程](figures/process1.png) + +将创建、初始化的部分放大看,如下两张图: + +![线程创建、初始化细节](figures/process2.png) + +![线程间切换](figures/process3.png) + +图中各名称对应描述如下表: + +| **名称** | **描述 ** | +|-----------|-----------------| +| Unified | CPU 当前运行状态 | +| UART ISR | 串口中断 | +| SysTick | 系统时钟 | +| Scheduler | 调度器 | +| thread1 | 线程 thread1 | +| thread2 | 线程 thread2 | +| Timer | 定时器 | +| tshell | 线程 tshell | +| Idle | 空闲线程 | + +附件 +---- + +整个示例运行流程可以使用工具 SystemView 工具打开附件文件 [thread_sample.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/thread_sample/thread_sample.SVDat) 查看具体细节。注意打开附件时,不要有中文路径。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/figures/process30.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/figures/process30.png new file mode 100644 index 0000000000000000000000000000000000000000..ff91594ed9e8a47cec08b7ab8a314ab281f8edae Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/figures/process30.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/figures/process31.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/figures/process31.png new file mode 100644 index 0000000000000000000000000000000000000000..51af05c5c9d554556563e87d52c171edb321b97c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/figures/process31.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/timer_sample.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/timer_sample.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..a8f91f262ac1c990f21684be6acd2e461edcd497 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/timer_sample.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/timer_sample.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/timer_sample.md new file mode 100644 index 0000000000000000000000000000000000000000..96738414baed8a8b5f5ca7cab65e53da48ebbe05 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timer_sample/timer_sample.md @@ -0,0 +1,161 @@ +实验:定时器的使用 +================== + +实验目的 +-------- + +- 理解动态定时器的基本原理; + +- 掌握 RT-Thread 中动态定时器的创建与使用; + +- 在 RT-Thread 中熟练使用动态定时器来完成需求。 + +实验原理及程序结构 +------------------ + +RT-Thread 定时器由操作系统提供的一类系统接口(函数),它构建在芯片的硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。 + +RT-Thread 定时器分为 HARD_TIMER 与 SOFT_TIMER,可以设置为单次定时与周期定时,这些属性均可在创建 / 初始化定时器时设置;而如果没有设置 HARD_TIMER 或 SOFT_TIMER,则默认使用 HARD_TIMER。 + +### 实验设计 + +本实验使用的例程为:[timer_sample.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/timer_sample.c) + +为了体现动态定时器的单次定时与周期性定时,本实验设计了 timer1、timer2 两定时器。 + +周期性定时器 1 的超时函数,每 5 个 OS Tick 运行 1 次,共运行 5 次(5 次后调用 rt_timer_stop 使定时器 1 停止运行);单次定时器 2 的超时函数在第 15 个 OS Tick 时运行一次。 + +通过本实验,用户可以清晰地了解到定时器的工作过程,以及使用定时器相关 API 动态更改定时器属性。 + +整个实验运行过程如下图所示,描述如下: + + ![实验运行过程](figures/process30.png) + +(1)在 tshell 线程中创建定时器 timer1 和 timer2,timer1 周期定时 5 OS Tick,timer2 单次定时 15 OS Tick;启动定时器 timer1、timer2; + +(2)定时器的定时时间均为到,在操作系统的调度下,Idle 投入运行; + +(3)每 5 个 OS Tick 到来时,定时器 timer1 定时时间到,调用超时函数打印一段信息,timer1 定时器重置; + +(4)在第 15 个 OS Tick 到来时,timer1 第 3 次超时,调用超时函数打印一段信息;timer2 第一次超时,调用超时函数打印一段信息且超时函数运行完删除; + +(5)在第 25 个 OS Tick 到来时,定时器 timer1 第 5 次超时,调用超时函数打印一段信息,并使用 rt_timer_stop() 接口将定时器停止,超时函数运行完后自行删除; + +### 源程序说明 + +#### RT-Thread 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +#### 示例源码 + +头文件以及定义了待创建定时器控制块以及实验需要用到的变量 + +``` +#include + +/* 定时器的控制块 */ +static rt_timer_t timer1; +static rt_timer_t timer2; +static int cnt = 0; +``` + +周期定时器 timer1 的超时函数,timer1 定时时间到会执行次函数,10 次之后停止定时器 timer1。 +```c +/* 定时器 1 超时函数 */ +static void timeout1(void *parameter) +{ + rt_kprintf("periodic timer is timeout %d\n", cnt); + + /* 运行第 10 次,停止周期定时器 */ + if (cnt++>= 9) + { + rt_timer_stop(timer1); + rt_kprintf("periodic timer was stopped! \n"); + } +} +``` + +单次定时器 timer2 的超时函数,timer2 定时时间到会执行次函数 + +```c +/* 定时器 2 超时函数 */ +static void timeout2(void *parameter) +{ + rt_kprintf("one shot timer is timeout\n"); +} +``` + +定时器的示例代码,示例函数首先创建并启动了线程 timer1,然后创建并启动了线程 timer2。并将函数使用 MSH_CMD_EXPORT 导出命令. + +```c +int timer_sample(void) +{ + /* 创建定时器 1 周期定时器 */ + timer1 = rt_timer_create("timer1", timeout1, + RT_NULL, 10, + RT_TIMER_FLAG_PERIODIC); + + /* 启动定时器 1 */ + if (timer1 != RT_NULL) + rt_timer_start(timer1); + + /* 创建定时器 2 单次定时器 */ + timer2 = rt_timer_create("timer2", timeout2, + RT_NULL, 30, + RT_TIMER_FLAG_ONE_SHOT); + + /* 启动定时器 2 */ + if (timer2 != RT_NULL) + rt_timer_start(timer2); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(timer_sample, timer sample); +``` + +以上为示例函数,可以看到将函数使用 MSH_CMD_EXPORT 导出命令,示例函数首先创建并启动了定时器 timer1,然后创建并启动了定时器 timer2。 + +编译、仿真运行和观察示例应用输出 +-------------------------------- + +编译工程,然后开始仿真。使用控制台 UART#1 做为 msh 终端,可以看到系统的启动日志,输入 timer_sample 命令启动示例应用,示例输出结果如下: + +```c +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Aug 24 2018 +2006 - 2018 Copyright by rt-thread team +msh >timer_sample +msh >periodic timer is timeout 0 +periodic timer is timeout 1 +one shot timer is timeout +periodic timer is timeout 2 +periodic timer is timeout 3 +periodic timer is timeout 4 +periodic timer was stopped! +``` + +使用 SystemView 工具可以监测示例实际运行过程,如下图所示。Systemview 没有区分定时器名称,详细时间信息可以看到定时器标号。 + +![示例运行流程图](figures/process31.png) + +图中各名称对应描述如下表: + +| 名称 | 描述 | +|-----------|-----------------| +| Unified | CPU 当前运行状态 | +| UART ISR | 串口中断 | +| SysTick | 系统时钟 | +| Scheduler | 调度器 | +| Timer | 定时器 | +| tshell | 线程 tshell | +| Idle | 空闲线程 | + +附件 +---- + +整个示例运行流程可以使用工具 SystemView 工具打开附件文件 [timer_sample.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/timer_sample/timer_sample.SVDat) 查看具体细节。注意打开附件时,不要有中文路径。 + diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process20.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process20.png new file mode 100644 index 0000000000000000000000000000000000000000..64f500e0e1114d5c44ea76ea2ec1b95dd9943bcb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process20.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process21.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process21.png new file mode 100644 index 0000000000000000000000000000000000000000..f41a48e7687c22e3560d36f117ee18d1fac40785 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process21.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process22.png b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process22.png new file mode 100644 index 0000000000000000000000000000000000000000..1f022d85dc0f10d5bbe0c7721e279b66821e4a13 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/figures/process22.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..b2d68aef8431a1ba2017ada6d4ddc0332b506380 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample.md new file mode 100644 index 0000000000000000000000000000000000000000..faba44884300ea25cb0b885c23c6fb0eb07f7912 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample.md @@ -0,0 +1,177 @@ +# 实验:线程的时间片轮转调度 + +## 实验目的 + +- 理解多线程时间片轮转的基本原理; + +- 理解同优先级线程间的时间片轮转机制; + +- 在 RT-Thread 中熟练使用时间片轮转来完成需求。 + +实验原理及程序结构 +------------------ + +对优先级相同的线程采用时间片轮转的方式进行调度。 + +### 实验设计 + +本实验使用的例程为:[timeslice_sample.c](https://github.com/RT-Thread-packages/kernel-sample/blob/v0.2.0/timeslice_sample.c) + +为了体现时间片轮转,本实验设计了 thread1、thread2 两个相同优先级的线程,thread1 时间片为 10,thread2 时间片为 5,如果就绪列表中该优先级最高,则这两个线程会按照时间片长短被轮番调度。两个线程采用同一个入口函数,分别打印一条带有累加计数的信息(每个线程进入一次入口函数会将计数 count++,count>200 时线程退出)遵循时间片轮转调度机制。 + +通过本实验,用户可以清晰地了解到,同优先级线程在时间片轮转调度时刻的状态变迁。 + +整个实验运行过程如下图所示,OS Tick 为系统滴答时钟(精度 10ms),下面以实验开始后第一个到来的 OS Tick 为第 1 个 OS Tick,过程描述如下: + +![实验运行过程](figures/process20.png) + +(1)在 tshell 线程中创建线程 thread1 和 thread2,优先级相同为 20,thread1 时间片为 10,thread2 时间片为 5; + +(2)启动线程 thread1 和 thread2,使 thread1 和 thread2 处于就绪状态; + +(3)在操作系统的调度下,thread1 首先被投入运行; + +(4)thread1 循环打印带有累计计数的信息,当 thread1 运行到第 10 个时间片时,操作系统调度 thread2 投入运行,thread1 进入就绪状态; + +(5)thread2 开始运行后,循环打印带有累计计数的信息,直到第 15 个 OS Tick 到来,thread2 已经运行了 5 个时间片,操作系统调度 thread1 投入运行,thread2 进入就绪状态; + +(6)thread1 运行直到计数值 count>200,线程 thread1 退出,接着调度 thread2 运行直到计数值 count>200,thread2 线程退出;之后操作统调度空闲线程投入运行; + +注意:时间片轮转机制,在 OS Tick 到来时,正在运行的线程时间片减 1。 + +### 源程序说明 + +#### RT-Thread 示例代码框架 + +RT-Thread 示例代码都通过 MSH_CMD_EXPORT 将示例初始函数导出到 msh 命令,可以在系统运行过程中,通过在控制台输入命令来启动。 + +#### 示例源码 + +定义了待创建线程需要用到的优先级,栈空间,时间片的宏: + +```c +#include + +#define THREAD_STACK_SIZE 1024 +#define THREAD_PRIORITY 20 +#define THREAD_TIMESLICE 10 +``` + +两个线程公共的入口函数,线程 thread1 和 thread2 采用同一个入口函数,但是变量分别存在不同的堆空间 + +```c +/* 线程入口 */ +static void thread_entry(void* parameter) +{ + rt_uint32_t value; + rt_uint32_t count = 0; + + value = (rt_uint32_t)parameter; + while (1) + { + if(0 == (count % 5)) + { + rt_kprintf("thread %d is running ,thread %d count = %d\n", value , value , count); + + if(count> 200) + return; + } + count++; + } +} +``` + +线程时间片的示例函数,示例函数首先创建并启动了线程 thread1,然后创建并启动了线程 thread2。并将函数使用 MSH_CMD_EXPORT 导出命令。 + +```c +int timeslice_sample(void) +{ + rt_thread_t tid = RT_NULL; + /* 创建线程 1 */ + tid = rt_thread_create("thread1", + thread_entry, (void*)1, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE); + if (tid != RT_NULL) + rt_thread_startup(tid); + + /* 创建线程 2 */ + tid = rt_thread_create("thread2", + thread_entry, (void*)2, + THREAD_STACK_SIZE, + THREAD_PRIORITY, THREAD_TIMESLICE-5); + if (tid != RT_NULL) + rt_thread_startup(tid); + + return 0; +} + +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(timeslice_sample, timeslice sample); +``` + +编译、仿真运行和观察示例应用输出 +-------------------------------- + +编译工程,然后开始仿真。使用控制台 UART#1 作为 msh 终端,可以看到系统的启动日志,输入 timeslice_sample 命令启动示例应用,示例输出结果如下: + +``` +\ | / +- RT - Thread Operating System +/ | \ 3.1.0 build Jun 14 2018 +2006 - 2018 Copyright by rt-thread team +msh >timeslice_sample +msh >thread 1 is running ,thread 1 count = 0 +thread 1 is running ,thread 1 count = 5 +thread 1 is running ,thread 1 count = 10 +thread 1 is running ,thread 1 count = 15 +thread 1 is running ,thread 1 count = 20 +… +thread 1 is running ,thread 1 count = 125 +thread 1 is running ,thread 1 count = 1thread 2 is running ,thread 2 count = 0 +thread 2 is running ,thread 2 count = 5 +thread 2 is running ,thread 2 count = 10 +… +thread 2 is running ,thread 2 count = 60 +thread 2 is running ,thread 2 co30 +thread 1 is running ,thread 1 count = 135 +thread 1 is running ,thread 1 count = 140 +thread 1 is running ,thread 1 count = 145 +… +thread 1 is running ,thread 1 count = 205 +unt = 205thread 2 is running ,thread 2 count = 70 +thread 2 is running ,thread 2 count = 75 +thread 2 is running ,thread 2 count = 80 +… +thread 2 is running ,thread 2 count = 200 +thread 2 is running ,thread 2 count = 205 +``` + +线程 thread1 在 10 个 OS Tick 中,可计数约 125 左右,计数 > 200 会退出,所以下一次执行不了 10 个 OS Tick 就会退出了。由于 “计数> 200 会退出”,Thread1 与 thread2 只会轮番调度一次就会先后退出了。 + +使用 SystemView 工具可以监测示例实际运行过程,示例开始之后现象如下图,与实验设计相同,轮流切换打印的同时(只是由于 OS Tick 的精度 10ms,导致在计数 200 内,thread1 与 thread2 时间片只轮番调度了一次),遵循时间片轮转规则。 + +![示例运行流程图](figures/process21.png) + +图中各名称对应描述如下表: + +| 名称 | 描述 | +|-----------|-----------------| +| Unified | CPU 当前运行状态 | +| UART ISR | 串口中断 | +| SysTick | 系统时钟 | +| Scheduler | 调度器 | +| thread1 | 线程 thread1 | +| thread2 | 线程 thread2 | +| tshell | 线程 tshell | +| Idle | 空闲线程 | + +附件 +---- + +整个示例运行流程可以使用工具 SystemView 工具打开附件文件 [timeslice_sample.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/timeslice_sample/timeslice_sample.SVDat) 查看具体细节。注意打开附件时,不要有中文路径。 + +注:如果将 OS Tick 设置 1ms,又或者将示例代码中 if(count> 200 ) 中的 200 改成更大的数值(如 2000),那么实验效果就很明显了。OS Tick 设置为 1ms 效果如下图所示,附件详见 [timeslice_sample1.SVDat](https://www.rt-thread.org/document/site/tutorial/experimental-manual/timeslice_sample/timeslice_sample1.SVDat) + +![OS Tick 设置为 1ms 时的结果](figures/process22.png) + diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample1.SVDat b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample1.SVDat new file mode 100644 index 0000000000000000000000000000000000000000..70eae8dc61dbc3e034de476c04aaa54eb5001559 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/timeslice_sample/timeslice_sample1.SVDat differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/version.md b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/version.md new file mode 100644 index 0000000000000000000000000000000000000000..77a93e7911f0750f12ef00706725ecd3479f878f --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/experimental-manual/version.md @@ -0,0 +1,6 @@ +# 版本和修订 + +| Date | Version | Author | Note | +| -------- | :-----: | :---- | :---- | +| 2018-12-29 | v1.0.0 | yangjie | 初始版本 | + diff --git a/rt-thread-version/rt-thread-standard/tutorial/figures/407.jpg b/rt-thread-version/rt-thread-standard/tutorial/figures/407.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7967eba69b263691e6b4db006eb5ffe9902aedf4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/figures/407.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/figures/examples.jpg b/rt-thread-version/rt-thread-standard/tutorial/figures/examples.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f938d501af3e1e25ee4ab2ee07afaae33e1c18cf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/figures/examples.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add.png b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add.png new file mode 100644 index 0000000000000000000000000000000000000000..857112f061e1e165940ccfca735c8cbb1426fefd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_goup.png b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_goup.png new file mode 100644 index 0000000000000000000000000000000000000000..2d32c4a78c8e01998765d2eb7fe164db489c8857 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_goup.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_samples.png b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_samples.png new file mode 100644 index 0000000000000000000000000000000000000000..92202487252531895eeaf6c485cfea3976caae29 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_samples.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_samples2.png b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_samples2.png new file mode 100644 index 0000000000000000000000000000000000000000..3da3dca099c3136e5a0ad31527816b64c19ee2bc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/add_samples2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/kernel_samples.png b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/kernel_samples.png new file mode 100644 index 0000000000000000000000000000000000000000..e20ae499cf7e57717dbfc5faee6bdb7e0feee161 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/kernel/figures/kernel_samples.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/kernel/kernel-video.md b/rt-thread-version/rt-thread-standard/tutorial/kernel/kernel-video.md new file mode 100644 index 0000000000000000000000000000000000000000..cec1f24d4b51dd370538dfda1945c87eeda1ec21 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/kernel/kernel-video.md @@ -0,0 +1,140 @@ +# 内核视频教程 + +内核是操作系统最基础也是最重要的部分,包含线程调度、时钟管理、线程间同步与通信、内存管理等内容,可阅读《内核基础》进行了解。 + +内核视频教程基于内核示例讲解,观看视频之前请做好[内核示例代码准备](../kernel/preparations.md) 。 + +内核视频教程百度网盘链接:[https://pan.baidu.com/s/19jvxfQlyfyp_PH9dt-6OAQ](https://pan.baidu.com/s/19jvxfQlyfyp_PH9dt-6OAQ) + +提取码:i3xq + +## 初识 RT-Thread + + + +
+ +## 动态内存堆的使用 + + + +参考资料:《内存管理》 + +
+ +## 线程的创建 + + + +参考资料:《线程管理》 + +
+ +## 简单的线程实例-跑马灯及栈空间分配技巧 + + + +参考资料:《线程管理》 + +
+ +## 线程的时间片轮转调度 + + + +参考资料:《线程管理》 + +
+ +## 空闲线程及两个常用的钩子函数 + + + +参考资料: 《线程管理》 + +
+ +## 临界区保护 + + + +参考资料: 《线程间同步》 + +
+ +## 信号量的使用 + + + +参考资料:《线程间同步》 + +
+ +## 生产者消费者问题 + + + +参考资料:《 线程间同步》 + +
+ +## 互斥量的使用 + + + +参考资料: 《线程间同步》 + +
+ +## 线程的优先级翻转 + + + +参考资料:《 线程间同步》 + +
+ +## 事件集的使用 + + + +参考资料:《线程间同步》 + +
+ +## 邮箱的使用 + + + +参考资料:《线程间通信》 + +
+ +## 消息队列的使用 + + + +参考资料: 《线程间通信》 + +
+ +## 软件定时器 + + + +参考资料:《时钟管理》 + +
+ +## 内存池的使用 + + + +参考资料:《内存管理》 + +
+ + +## 继续学习 + +内核部分学习完成,本应学习外设和组件,但是这些功能需要 Env 工具进行打开或关闭(比如要使用SPI,就需要使用 Env 打开 SPI),所以在学习外设与组件之前,先[学习使用 Env 工具](../env-video.md)。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/kernel/preparations.md b/rt-thread-version/rt-thread-standard/tutorial/kernel/preparations.md new file mode 100644 index 0000000000000000000000000000000000000000..3bc372d918d9fc2c14a1b46aac7fc3b3143e34b8 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/kernel/preparations.md @@ -0,0 +1,22 @@ +## 内核示例代码准备 + +使用开发板进行内核学习,需要添加内核例程到工程中。 + +> [!NOTE] +> 注:使用 keil MDK 模拟则无需此步骤,可以直接开始内核部分的学习。 + +### 下载内核示例代码 + +点击此处链接下载 [内核示例代码](https://github.com/RT-Thread-packages/kernel-sample.git),下载方法1是使用git方式 clone 下载;下载方法2是直接下载zip包。将下载的示例代码放在使用的 BSP 工程目录下。 + +![内核示例包下载](figures/kernel_samples.png) + +如下,当前使用的是stm32f407-atk-explorer,那就将下载的内核例程包(kernel-samples,当前版本是 0.2.0)放在当前文件夹中,如下图所示: + +![添加内核示例](figures/add.png) + +### 添加内核示例代码到工程 + +打开工程,新建`kernel_samples`分组,然后将下载下来的内核例程添加进这个分组(暂不添加signal_sample.c),编译、下载。下载开发板之后,通过串口助手键入相关命令,进行内核部分的学习。 + +![添加内核示例到工程](figures/add_samples2.png) diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/add_groups.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/add_groups.png new file mode 100644 index 0000000000000000000000000000000000000000..f739ba4809ae154538218e5d02e8b5e118c006c7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/add_groups.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/compile.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/compile.png new file mode 100644 index 0000000000000000000000000000000000000000..59599898a71baae1ff81692d82451207ee0b7a80 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/compile.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/compile_no_error.jpg b/rt-thread-version/rt-thread-standard/tutorial/project/figures/compile_no_error.jpg new file mode 100644 index 0000000000000000000000000000000000000000..50e50bcbbc16288238a0a098b7c44c39746237f7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/compile_no_error.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy1.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy1.png new file mode 100644 index 0000000000000000000000000000000000000000..b76afefda9727faf37153de9b0d5ce64a6c892f7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy2.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy2.png new file mode 100644 index 0000000000000000000000000000000000000000..d85fdcaaa80239e2a16175cae12a15c1ce8a26cb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy3.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy3.png new file mode 100644 index 0000000000000000000000000000000000000000..c7aac0ff9366a00000a34b2bf5994a39d3583e02 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy4.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy4.png new file mode 100644 index 0000000000000000000000000000000000000000..914920cb2f7414bd98c1443a43710070ec36927c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy_main.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy_main.png new file mode 100644 index 0000000000000000000000000000000000000000..0197f0ffca528eddcabc9b1cced53c9203de9a6e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/copy_main.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/create_new_project.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/create_new_project.png new file mode 100644 index 0000000000000000000000000000000000000000..b9aeae0449bc0747ac8e14cad8f7b0473af4e9de Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/create_new_project.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_error.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_error.png new file mode 100644 index 0000000000000000000000000000000000000000..34cf02a1351108523e653023984ad86fa9783c93 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_error.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_ok.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_ok.png new file mode 100644 index 0000000000000000000000000000000000000000..283e541b8283c40506daed8c29888d8923fbd9c5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_ok.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_setting.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_setting.png new file mode 100644 index 0000000000000000000000000000000000000000..dc26c88c48849a125fd508d998dad1dfff0f5311 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/debug_setting.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/environment.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/environment.png new file mode 100644 index 0000000000000000000000000000000000000000..820ded2558d11563574775d569624751384e36d0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/environment.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/include_path.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/include_path.png new file mode 100644 index 0000000000000000000000000000000000000000..b991fa292158756e291830a94f7c40a2810b64db Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/include_path.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/misc_controls.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/misc_controls.png new file mode 100644 index 0000000000000000000000000000000000000000..d61a632f87d2aef4d06b6de9446a5410aa7c7cab Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/misc_controls.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/preprocessor_symbols.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/preprocessor_symbols.png new file mode 100644 index 0000000000000000000000000000000000000000..b488185d2a69ae3f9703905ab94052ca62768dc3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/preprocessor_symbols.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/project.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/project.png new file mode 100644 index 0000000000000000000000000000000000000000..4925ffdde2862790a051d5cd82adc0b053e515cc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/project.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/rt-thread_folders.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/rt-thread_folders.png new file mode 100644 index 0000000000000000000000000000000000000000..97e2f743ed08ef3c15e849766b07de7739838357 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/rt-thread_folders.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/figures/select_device.png b/rt-thread-version/rt-thread-standard/tutorial/project/figures/select_device.png new file mode 100644 index 0000000000000000000000000000000000000000..45eb12806a7fb1d78b99bf8f9903dd93f7682a82 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/project/figures/select_device.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/project/project.md b/rt-thread-version/rt-thread-standard/tutorial/project/project.md new file mode 100644 index 0000000000000000000000000000000000000000..11a641d542738ee6ab265de773c25c18d4d534f3 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/project/project.md @@ -0,0 +1,146 @@ +# Keil 下搭建 RT-Thread 最小系统工程 + +对于初次接触 RT-Thread 的朋友来说,要想自己建立一个 Keil 下的工程,可能会觉得不知所措,本文将一 STM32F103 为例介绍最小系统的建立过程。 + +本文所使用的方法是非常传统的 ,只适用于非常简单的工程建立,用于初学者了解 RT-Thread 最小系统工程的建立,不建议在实际开发中使用该方式。 + +实际开发中推荐使用 Env 和 scons` 辅助工具来裁剪功能和组织文件 `,这部分将不在本部分说明,建议读者在熟悉 RT-Thread 内核后再去学习 scons 使用。 + +## 准备工作 + +### 下载固件库 + +ST 的 STM32 系列芯片,官方网站上为开发者提供了非常方便的开发库。到目前为止,有标准外设库 (STD 库)、HAL 库、LL 库 三种。前两者都是常用的库,后面的 LL 库是 ST 最近才添加,随 HAL 源码包一起提供,目前支持的芯片也偏少。其中,STD 库和 HAL 库两者相互独立,互不兼容。LL 库和 HAL 库两者相互独立,只不过 LL 库更底层。而且,部分 HAL 库会调用 LL 库(例如:USB 驱动)。同样,LL 库也会调用 HAL 库 。 +这里,笔者将使用 STM32F1xx 系列的 HAL 固件库建立最小工程。我们从官网下载 [STM32CubeF1](http://www.st.com/content/st_com/en/products/embedded-software/mcus-embedded-software/stm32-embedded-software/stm32cube-mcu-packages/stm32cubef1.html),这个软件包里就包含了我们需要的 HAL 库。 + +### 下载 RT-Thread 最新版本的源码 + +RT-Thread 是完全开源、开放的物联网操作系统,可以从官网下载相关的源代码和辅助开发工具。RT-Thread 项目源代码都保存在 Git 仓库中; 您可以将源文件下载为 tar.gz 文件,或者使用 Git 克隆和检出命令: + +- 国外 github:https://github.com/RT-Thread/rt-thread; +- 国内 gitee:https://gitee.com/rtthread/rt-thread ; + +由于国外服务器 github 速度较慢,建议从 gitee 上下载,也可以选择从备份的网盘中下载 RT-Thread 发布的各个版本源代码: +- 百度网盘:https://pan.baidu.com/s/1mgIAyWo#list/path=%2F + +> 提示:说明:RT-Thread 源代码下载包较大,因其包含有将近 90 个 BSP(实际你只需要提取自己的板卡对应的 BSP 即可),而 Git 仓库不提供单独下载某个文件,请下载源码时多一些耐心。 +> + +注:请下载最新版本的源码,否则可能导致搭建的工程不能仿真。 + +### 拷贝需要的文件 + +我们先建立 4 个文件夹并命名如下,将之前下载到的固件库和 RT-Thread 源码提取部分需要的文件,拷贝并按照下面的文件夹结构重新组织文件。 + +![最小系统目录](./figures/rt-thread_folders.png) + +在动手之前,先对上面的文件夹结构做个简短的说明: + +| 文件夹 | 说明 | +| --- | --- | +| 根目录 | 存放 MDK 的工程文件及 rtconfig.h 文件 | +| applications | 存放一些用户的应用代码文件 | +| drivers | 用于存放设备驱动的底层驱动实现 | +| Libraries | 用于存放我们从芯片官网下载的固件库 | +| rt-thread | 用于存放 RT-Thread 的源码包 | + +具体的拷贝内容如下: + - 根目录:从 RT-Thread 源码的目录里 rt-thread\bsp\stm32f10x-HAL\ 里复制 `rtconfig.h` 文件。`rtconfig.h`:RT-Thread 的配置文件,通过宏定义打开 / 关闭各功能组件。 + + ![根目录](./figures/copy1.png) + + - drivers:从下载的 RT-Thread 源码的目录里 rt-thread\bsp\stm32f10x-HAL\drivers 里复制文件: + 1) `drv_usart.c`,`drv_usart.h`:串口的底层实现,因为我们需要通过串口输入 finsh/msh 命令和打印系统信息; + 2) `board.h`,`board.c`:开发板的初始化配置文件; + 3) `stm32f1xx_it.h`, `stm32f1xx_it.c`: 中断配置文件; + 4) `stm32f1xx_hal_conf.h`:官方固件库的配置文件,可以配置需要使用到的固件库。 + + ![根目录](./figures/copy2.png) + + - Libraries:把下载的固件库里的 `CMSIS`、`STM32F1xx_HAL_Driver` 内容放到我们建立的 `Libraries` 文件夹下。 + + ![根目录](./figures/copy4.png) + + - rt-thread:把下载的 RT-Thread 源码目录下的 `components`,`libcpu`,`include`,`src` 四个文件夹拷贝到这个目录下。`components` 里包含了协议驱动的上层实现,`libcpu` 包含了对各种厂家内核芯片的支持文件,`include` 和 `src` 包含了系统的各种内核组件的源码和头文件。 + + ![根目录](./figures/copy3.png) + + - applications:从 rt-thread\bsp\stm32f10x-HAL\applications 文件中复制 `main.c` 文件到我们的建立的文件夹 applications 中。 + + ![空的用户程序](./figures/copy_main.png) + +## 搭建最小系统工程 + +### MDK 中新建工程 + +1) 从 MDK 中新建工程项目,并选择保存在我们建立的根目录下: + +![新建工程](./figures/create_new_project.png) + +2) 选择所使用的具体芯片型号后点击 “OK” 确定选择: + +![选择器件型号](./figures/select_device.png) + +3) Manage Run-Time Environment 配置页选择 cancel。 + +![管理运行环境](./figures/environment.png) + +4) 修改工程属性,并建立分组: + +![添加分组](./figures/add_groups.png) + +### 工程中添加文件 + +在上一小节中新建的分组里添加文件,详见下表: + +| groups | 需要添加的文件 | +| ---------------- | ---------------------------- | +| Applications | `applications`文件夹下的`main.c` | +| Drivers | `drivers`文件夹下的`board.c`、`drv_usart.c`、`stm32f1xx_it.c`文件。 | +| STM32_HAL | 官方固件库下的:`Libraries\STM32F1xx_HAL_Driver\src`文件夹中除了`stm32f1xx_hal_msp_template.c`、`stm32f1xx_hal_timebase_tim_template.c`、`stm32f1xx_hal_timebase_rtc_alarm_template.c`这三个文件外的所有.c文件;
`Libraries\CMSIS\Device\ST\STM32F1xx\Source\Templates`目录下的`system_stm32f1xx.c`文件;
`Libraries\CMSIS\Device\ST\STM32F1xx\Source\Templates\arm`目录下的`startup_stm32f103xe.s`文件。 | +| Kernel | `rt-thread\src` 目录下的所有.C文件 | +| CORTEX-M3 | `rt-thread\libcpu\arm\cortex-m3`目录下的`context_rvds.S`和`cpuport.c`文件。 | +| DeviceDrivers | `rt-thread\components\drivers`目录下`src`和`serial`两个文件夹中的所有.C文件 | +| finsh | `rt-thread\components\finsh`目录下的所有.C文件。 | + +工程如图: + +![工程界面图](figures/project.png) + +### 添加包含文件路径 + +添加完文件,接下来在项目设置里的 `C/C++` 页里的 `Include Path` 添加这些源文件包含的头文件的路径。 + +![Include Path](./figures/include_path.png) + +### 工程中的其他设置 + +在下图所示区域设置器件类型和制定使用 stm32 固件库:`USE_HAL_DRIVER, STM32F103xE`。 + +![预处理器符号](./figures/preprocessor_symbols.png) + +设置好之后编译无错误。 + +![编译无错误](./figures/compile_no_error.jpg) + +如下图,选择软件仿真,将Dialog DLL设置为`DARMSTM.DLL`,Parameter设置为`-pSTM32F103C8`(由相应的芯片型号而定),之后点 `OK` 确定: + +![仿真设置](./figures/debug_setting.png) + +仿真运行,命令窗口有错误输出: + +![仿真错误](./figures/debug_error.png) + +这个错误是由 `rtconfig.h` 里设置的芯片和刚开始我们建立工程时所选器件型号不一致,导致的存储器地址访问超范围。将定义改为: + +```c + #define STM32F103C8 +``` + +再次编译运行无错误: + +![仿真错误](./figures/debug_ok.png) + +再次重申本文所使用的方法仅用于初学者了解 RT-Thread 最小系统工程的建立,不建议在实际开发中使用该方式。 + +实际开发中推荐使用 Env 和 scons` 辅助工具来裁剪功能和组织文件 `,这部分将不在本部分说明,建议读者在熟悉 RT-Thread 内核后再去学习 scons 使用。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/README.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b8cf73972f65afb7d272816e44170d00e811dbc0 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/README.md @@ -0,0 +1,13 @@ +# 教程简介 + +> 摘要:RT-Thread 网络入门教程,指导读者学会了解和使用 RT-Thread 网络框架,能够进行网络编程。 + +这是一个基于 QEMU 平台的系列教程,旨在为已学习完[内核教程](../kernel/kernel-video.md)及[ENV 工具](../env-video.md)的人提供进步的阶梯。 + +QEMU 是一个支持跨平台的虚拟机,它可以虚拟很多开发板。RT-Thread 使用 QEMU 模拟了 ARM vexpress A9 开发板,这样使得用户在电脑上就可以进行 RT-Thread 的程序开发,大大提高了工作效率,降低了入门难度,也提高了系统的可玩性。 + +为了让大家进一步提高利用 RT-Thread 进行实际开发的能力,我们推出了这篇包括文件系统和网络编程的进阶教程。 + +本教程配套视频: + +交流 QQ 群:807319828 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/arp_principle.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/arp_principle.md new file mode 100644 index 0000000000000000000000000000000000000000..8e3330d131138408e107b156b2ebe0a7e3623c79 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/arp_principle.md @@ -0,0 +1,72 @@ +# ARP : 网络世界到物理世界的桥梁 + +## ARP 协议 + +ARP(Address Resolution Protocol)地址解析协议,是根据 IP 地址获取物理 MAC 地址的一个 TCP/IP 协议。 + +现在局域网中主机的 IP 一般都是动态分配的,这样做的好处是提高了 IP 的利用率;缺点是,当数据到来时只根据 IP 地址就不能确定到底哪一台主机了。因此需要弄一个缓存表,用来记录 IP 和 主机 MAC 地址的对应关系,这个缓存表就是 ARP 高速缓冲表 。 + +ARP协议的基本功能就是通过目标设备的 IP 地址,查询目标设备的 MAC 地址,同时,维护 ARP 高速缓冲表,以保证通信的顺利进行。 + +ARP 的分组格式如下图所示: + +![ARP 的分组格式](figures/ARP2.png) + +**以太网的源地址**和**目的地址**。目的地址为全1的特殊地址是广播地址。电缆上的所有以太网接口都要接收广播的数据帧。 + +以太网**帧类型**表示后面数据的类型。对于 ARP 请求或应答来说,该字段的值为 0x0806。 + +**硬件类型**字段表示硬件地址的类型。它的值为1即表示以太网地址。 + +**协议类型**字段表示要映射的协议地址类型。它的值为 0x0800 即表示 IPv4 协议。 + +**硬件地址长度**和**协议地址长度**分别指出硬件地址和协议地址的长度,以字节为单位。对于以太网上 IP 地址的 ARP 请求或应答来说,它们的值分别为 4 和 6。 + +**操作字段**指出四种操作类型,它们是 ARP 请求(值为1)、ARP 应答(值为2)、RARP 请求(值为3)和 RARP 应答(值为 4)。 + +**发送端的硬件地址**(在本例中是以太网地址)、**发送端的协议地址**( IP 地址)、 + +**目的端的硬件地址**和**目的端的协议地址**。 + +**注意:**这里有一些重复信息:在以太网的数据帧报头中和 ARP 请求数据帧中都有发送端的硬件地址。 + +## ARP 过程分析 + +例如: + +主机 A :IP:192.168.0.2;MAC:00-00-C0-15-AD-18. + +主机 B :IP:192.168.0.4;MAC:08-00-2B-00-EE-AA. + +当局域网的主机 A 接收到发给 IP:192.168.0.4 的数据,他就需要转发数据给主机 B,首先主机 A先查询自己的 ARP 缓存看是否有主机 B 对应的 MAC 地址,如果没有的话,主机 A 就会运行 ARP。 + +1. 主机 A 在本局域网广播一个 ARP 请求分组 。ARP请求分组的主要内容是表明:我的 IP 地址是 192.168.0.2,我的 MAC 地址是 00-00-C0-15-AD-18 .我想知道 IP 地址为 192.168.0.4 的主机的 MAC 地址。 +2. 在本局域网上的所有主机都收到此 ARP 请求分组。 +3. 主机 B 在 ARP 请求分组中见到自己的 IP 地址,就向主机 A 发送 ARP 响应分组,并写入自己的 MAC 地址。其余的所有主机都不理睬这个 ARP 请求分组。ARP 响应分组的主要内容是表明:“我的 IP 地址是192.168.0.4,我的硬件地址是 08-00-2B-00-EE-AA ”,请注意:虽然 ARP 请求分组是广播发送的,但 ARP 响应分组是普通的单播,即从一个源地址发送到一个目的地址。 +4. 主机 A 收到主机 B 的 ARP 响应分组后,就在其 ARP 高速缓冲表中写入主机 B 的 IP 地址到 MAC 地址的映射。 + +然后,现在主机 A 就可以给主机 B 发送数据了。 + +整个 ARP 的过程如下图所示: + +![ARP 过程](figures/ARP.png) + +## 抓包分析 + +其实 QEMU 在刚开始运行的时候就会自动运行 ARP,只要在开始运行 QEMU 之前开启抓包就能抓到 ARP的包 + +1. 打开 wireshark 软件 开启抓包,设定过滤条件为 arp,只显示 ARP 协议的包。 +2. 运行 QEMU + +查看 wireshark ,发现已经抓到了 ARP 协议 的数据包 + +![1533113857061](figures/ARP1.png) + +我们也可以点开封包详细信息然后和上面的 ARP 的分组格式做对比 + +![1533116943401](figures/ARP3.png) + +当然还有回复 ARP 请求分组的数据包,如下图所示: + +![1533117092963](figures/ARP4.png) + diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/arp_principle.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/arp_principle.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0f4286bc9bbabc61739ada5480ffcde16776ef1d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/arp_principle.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP.png new file mode 100644 index 0000000000000000000000000000000000000000..931e700b2c6777ae0c00e2706cd4ed6754778bc1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP1.png new file mode 100644 index 0000000000000000000000000000000000000000..ab8d47ee096d9bf3125d39b689c8840e413922f5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP2.png new file mode 100644 index 0000000000000000000000000000000000000000..a9a564b1bf4ec5b3372f4788f73526a2289c4599 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP3.png new file mode 100644 index 0000000000000000000000000000000000000000..88d62df294777de95d900a5167c8b572cd407a3e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP4.png new file mode 100644 index 0000000000000000000000000000000000000000..865f6206835e838d108e6d0aa6d23e8b2de368aa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/arp_principle/figures/ARP4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/dhcp_principle.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/dhcp_principle.md new file mode 100644 index 0000000000000000000000000000000000000000..be9359a9a94c3c7729345953a4db4ea7e10bc097 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/dhcp_principle.md @@ -0,0 +1,89 @@ +# DHCP : 网络世界身份的获取 + +## DHCP 协议 + +DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)前身是 BOOTP 协议,是一个局域网的网络协议。它是一种 **服务器- 客户端** 的工作模式,使用 UDP 协议工作,常用的 2 个端口:67(DHCP server), 68(DHCP client)。 + +DHCP 通常被用于局域网环境,主要作用是集中的管理、分配 IP 地址,使 client 动态的获得 IP 地址、Gateway 地址、DNS 服务器地址等信息,并能够提升地址的使用率。 + +## DHCP 报文种类 + +DHCP 一共有 8 种报文,分别为 DHCP Discover、DHCP Offer、DHCP Request、DHCP ACK、DHCP NAK、DHCP Release、DHCP Decline、DHCP Inform。各种类型报文的基本功能如下: + +| DHCP报文类型 | 说明 | +| ------------- | ------------------------------------------------------------ | +| DHCP Discover | DHCP 客户端在请求 IP 地址时并不知道 DHCP 服务器的位置,因此 DHCP 客户端会在本地网络内以广播方式发送 Discover 请求报文,以发现网络中的 DHCP 服务器。所有收到 Discover 报文的 DHCP 服务器都会发送应答报文,DHCP 客户端据此可以知道网络中存在的 DHCP 服务器的位置。 | +| DHCP Offer | DHCP 服务器收到 Discover 报文后,就会在所配置的地址池中查找一个合适的IP地址,加上相应的租约期限和其他配置信息(如网关、DNS服务器等),构造一个 Offer 报文,发送给 DHCP 客户端,告知用户本服务器可以为其提供 IP 地址。但这个报文只是告诉 DHCP 客户端可以提供 IP 地址,最终还需要客户端通过 ARP 来检测该 IP 地址是否重复。 | +| DHCP Request | DHCP 客户端可能会收到很多 Offer 请求报文,所以必须在这些应答中选择一个。通常是选择第一个 Offer 应答报文的服务器作为自己的目标服务器,并向该服务器发送一个广播的 Request 请求报文,通告选择的服务器,希望获得所分配的IP地址。另外,DHCP 客户端在成功获取 IP 地址后,在地址使用租期达到 50% 时,会向 DHCP 服务器发送单播 Request 请求报文请求续延租约,如果没有收到 ACK 报文,在租期达到 87.5% 时,会再次发送广播的 Request 请求报文以请求续延租约。 | +| DHCP ACK | DHCP 服务器收到 Request 请求报文后,根据 Request 报文中携带的用户 MAC 来查找有没有相应的租约记录,如果有则发送 ACK 应答报文,通知用户可以使用分配的 IP 地址。 | +| DHCP NAK | 如果 DHCP 服务器收到 Request 请求报文后,没有发现有相应的租约记录或者由于某些原因无法正常分配 IP 地址,则向 DHCP 客户端发送 NAK 应答报文,通知用户无法分配合适的 IP 地址。 | +| DHCP Release | 当 DHCP 客户端不再需要使用分配 IP 地址时,就会主动向 DHCP 服务器发送 RELEASE 请求报文,告知服务器用户不再需要分配 IP 地址,请求 DHCP 服务器释放对应的 IP 地址。 | +| DHCP Decline | 当客户端发现服务器分配的IP地址无法使用(如IP地址冲突时),则会向 DHCP 服务器发送 Decline 请求报文,通知服务器所分配的 IP 地址不可用,以期获得新的 IP 地址。 | +| DHCP Inform | DHCP 客户端如果需要从 DHCP 服务器端获取更为详细的配置信息,则向 DHCP 服务器发送Inform 请求报文;目前基本上已经弃用了。 | + +正常的工作流程如下: + +![DHCP 工作过程](figures/DHCP2.png) + +## DHCP 报文格式 + +DHCP 服务的 8 种报文的格式是相同的,不同类型的报文只是报文中的某些字段取值不同。DHCP 报文格式基于 BOOTP 的报文格式,如下图所示: + +![DHCP 报文格式](figures/DHCP1.png) + +下面是各字段的说明。 + +- **OP** : 报文的操作类型。若是 client 送给 server 的封包,设为 1 ,反向为 2。 +- **htype** : 客户端的MAC地址类型 ,Ethernet 为 1。 +- **hlen** : 客户端的 MAC 地址长度 , Ethernet 为 6。 +- **hops** : DHCP 报文经过的 DHCP 中继的数目,默认为 0。DHCP 请求报文每经过一个 DHCP 中继,该字段就会增加 1。没有经过 DHCP 中继时值为 0。 +- **xid **: 随机生成的一段字符串,两个数据包拥有相同的 xid 说明他们属于同一次会话 +- **secs** : DHCP 客户端从获取到 IP 地址或者续约过程开始到现在所消耗的时间,以秒为单位。 +- **flags** : 标志位,只使用第 0 比特位,是广播应答标识位,用来标识 DHCP 服务器应答报文是采用单播还是广播发送,0 表示采用单播发送方式,1 表示采用广播发送方式。其余位尚未使用。 +- **ciaddr **: 客户端的 IP 地址。仅在 DHCP 服务器发送的ACK报文中显示,在其他报文中均显示0 +- **yiaddr **: DHCP 服务器分配给客户端的 IP 地址。仅在 DHCP 服务器发送的 Offer 和 ACK 报文中显示,其他报文中显示为 0。 +- **siaddr** : 若 client 需要透过网络开机,从 server 送出之 DHCP OFFER、DHCPACK、DHCPNACK封包中,此栏填写开机程序代码所在 server 之地址。 +- **giaddr **: 若需跨网域进行 DHCP 发放,此栏为 relay agent 的地址,否则为 0。 +- **chaddr** : DHCP 客户端的 MAC 地址 +- **sname** : DHCP 服务器的名称(DNS域名格式)。在 Offer 和 ACK 报文中显示发送报文的 DHCP 服务器名称,其他报文显示为0。 +- **file** : 若 client 需要透过网络开机,此栏将指出开机程序名称,稍后以 TFTP 传送。 +- **options** : 允许厂商定义选项(Vendor-Specific Area),以提供更多的设定信息(如:Netmask、Gateway、DNS、等等)。长度可变,格式为"代码+长度+数据"。 + +列出 options 部分可选的选项: + +| 代码 | 长度(字节) | 说明 | +| ---- | --------------------------------- | ------------------------------------------------------------ | +| 1 | 4 | 子网掩码 | +| 3 | 长度可变,必须是4个字节的倍数。 | 默认网关(可以是一个路由器IP地址列表) | +| 6 | 长度可变,必须是4个字节的整数倍。 | DNS服务器(可以是一个DNS服务器IP地址列表) | +| 15 | 长度可变 | 域名称(主DNS服务器名称) | +| 44 | 长度可变,必须是4个字节的整数倍。 | WINS服务器(可以是一个WINS服务器IP列表) | +| 51 | 4 | 有效租约期(以秒为单位) | +| 53 | 1 | 报文类型1: DHCP Discover2: DHCP Offer3: DHCP Request4: DHCP Decline5: DHCP ACK6: DHCP NAK7: DHCP Release8: DHCP Inform | +| 58 | 4 | 续约时间 | + +## 抓包分析 + +QEMU 在刚开始运行的时候就会自动运行 DHCP,只要在开始运行 QEMU 之前开启抓包就能抓到 DHCP 的包 + +1. 打开 wireshark 软件 开启抓包,设定过滤条件为 bootp,只显示 DHCP 相关的包。 +2. 运行 QEMU + +查看 wireshark ,发现已经抓到了 DHCP 协议 的数据包 + +![抓包详情](figures/DHCP3.png) + +其中的 1-6 就对应着上面的工作流程的那 6 个过程。 + +我们也可以点开封包详细信息然后和上面的 DHCP 的报文格式做对比 + +点开 DHCP Discover 数据包, 从下图可以看出,DHCP 属于应用层协议,它在传输层使用 UDP 协议,目的端口是 67。 + +![DHCP Discover 数据包](figures/DHCP4.png) + +当然还有回复 DHCP 服务器响应请求的数据包,如下图所示: + +![DHCP 服务器响应请求的数据包](figures/DHCP5.png) + +其他的一些选项对照着上面的报文格式做对比,就可以很容易的了解 DHCP 协议的工作过程了。 + diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP1.png new file mode 100644 index 0000000000000000000000000000000000000000..79c41622ec2eb00c525ca0e83d333c2a01832a7f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP2.png new file mode 100644 index 0000000000000000000000000000000000000000..9edea676ce5009de1400c1e56745a46bc25e5a85 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP3.png new file mode 100644 index 0000000000000000000000000000000000000000..088d4897a6a73cc4bbef87cf81cdf0b8da529556 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP4.png new file mode 100644 index 0000000000000000000000000000000000000000..6d1f0f4bbee1553a3de2ecd422c7dfde790b12f6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP5.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP5.png new file mode 100644 index 0000000000000000000000000000000000000000..b3f1e036d0743156b488d1802c3d7e5e7a8da92f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/dhcp_principle/figures/DHCP5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/faq.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/faq.md new file mode 100644 index 0000000000000000000000000000000000000000..97b8644244bb89447d04c81cb33fbd015dfd792a --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/faq.md @@ -0,0 +1,198 @@ +# 常见问题及解决方法 + +不太会使用 Env 工具的请先看一遍 [《Env 用户手册》](../../../programming-manual/env/env.md)(不长的,看完费不了几分钟) + +> 提示:1. Env 工具和 源码 所处的目录都不能有中文或空格请先检查!! + 2. code 是一个命令 点 ‘.’ 是一个参数表示当前目录,中间有一个空格。 + 3. romfs ramfs 文件系统中的文件名和c的变量的命名一样,只能由英文字母开头且仅包含数字和下划线。 + 4. 修改 qemu.bat 里面的参数时,要注意那是一行参数中间没有空格(复制粘贴完要注意)。其中ifname=tap 的意思就是虚拟TAP网卡的名字重命名为了 tap,如下图所示 + +![修改 qemu 启动参数](figures/faq1.png) + +## 基础知识 + +### Q: 如何添加环境变量? + +> [!NOTE] +> 注:首先请确认下面红框里的环境变量是否都有,修改完环境变量之后,要注销或重启电脑才能使环境变量生效 + +![添加环境变量方法](figures/faq2.png) + +### Q: 终端为什么会显示乱码? + +![终端显示乱码](figures/faq20.jpg) + +**A:** 图中的 `?[m?[` 是终端字体颜色的代码,显示出来是因为终端不支持颜色显示。 + +### Q: 为什么获取不到 IP 地址? + +**A:** 获取不到 IP 地址的可能有很多,一般有如下几种情况: + +1. 一般启动 qemu 之后需要等个几秒才能获取到 IP 地址。 + +2. 第一次开机可能获取不到 IP 地址,只要在 qemu 运行着的情况下,关闭网络共享,然后再开一次即可。 + +3. 获取到的 IP 地址时 `10.0.x.x` 说明没有添加启动参数,请参照上面基础知识第 4 条添加启动参数。 + +4. 如果运行 QEMU 时,tap 网卡的显示的不是`未识别的状态` ,那可能是启动参数填错了,请参照上面基础知识第 4 条修改为正确的启动参数。 + +5. 如果都设置对了,运行时 tap 网卡显示 `未识别的状态`,右键 tap 网卡点击 `状态`,如果是 `无 Internet 连接` ,请进行网络诊断试试。 +6. 有 VMvare 网卡的可以先禁用 再试一次。 + +### Q: 为什么我的 menuconfig 和教程的不一样? + +![menuconfig 找不到要配置的项](figures/faq22.jpg) + +**A:** 应该是 Env 的软件包索引需要更新了,或者需要先按空格开启这一项功能,然后再按回车进入配置项。 + +**解决方法:** 在 Env 输入命令 `pkgs --upgrade` 更新 Env 的运行脚本和软件包索引。 具体方法参见:[《Env 用户手册》](../../../programming-manual/env/env.md) + +## 环境搭建 + +### Q: 为什么我的 scons 编译结果不一样,很少? + +![scons 编译结果很短](figures/faq15.png) + +**A:** 这不是问题,编译器只编译被改变过的文件,没有改变的不会重复编译,而且,显示已经编译完成了。另外,图中的 `?[m?[` 是终端字体颜色的代码,显示出来是因为终端不支持颜色显示。 + +### Q: 为什么 pkgs --update 更新失败? + +![pkgs --update 更新失败](figures/faq3.png) + +**A:** 这可能是没有安装Git,也可能是没有添加环境变量 + +**解决方法:** 安装Git,添加环境变量,并需要注销或者重启系统添加环境变量方法 + +### Q: 为什么会创建网桥失败? + +![创建网桥失败](figures/faq4.png) + +**A:** 共享和网桥只能使用一种方式,不要同时使用。 + +### Q: 为什么会网络共享失败? + +![网络共享失败](figures/faq8.png) + +**A:** 可能是杀毒软件将防火墙服务关了,需要手动开启防火墙功能 + +具体的解决办法,可以参考: + +### Q: 我的 Tap 网卡为什么显示未识别的网络? + +![Tap 网卡显示未识别的网络](figures/faq5.png) + +**A:** 这是==正常==的状态,如果获取不到ip 只要在qemu运行的情况(即tap网卡显示未识别的网络)下关闭网络共享,再重新打开即可。 + +### Q: Scons 为什么不是可运行的程序? + +![Scons 不能用](figures/faq7.png) + +**A:** 这是因为 RT-Thread 的代码需要更新了,更新源码到最新 或 按视频操作下载最新的源码。 + +### Q: 为什么运行时有红色 SDIO 错误? + +![运行有 SDIO 红色错误](figures/faq13.png) + +**A:** 这个是因为模拟的 SD 卡不支持高速模式,不用管,没有影响。 + +### Q: 为什么提示找不到 ping 命令? + +![ping 命令找不到](figures/faq21.png) + +**A:** 只是由于虽然打开了 RT-Thread online packages ---> 里面的某些功能,但是由于一些原因却没有成功更新到本地上造成的。有以下几种情况: + +1. 没有在 online packages 里开启 ping 的功能 +2. 没有更新软件包 +3. 网络不好更新失败 +4. 更新成功了却没有 scons 重新编译 + +**解决方法:** + +更新软件包的方法: + +- 手动更新软件包 输入命令 pkgs --update +- 开启自动更新软件包功能 + +网络不好更新失败的: +去官网下载最新版 env,然后在 Env 的配置 menuconfig -s 里面开启镜像源下载 pkgs download using mirror server + +## Vscode 调试 + +### Q: 为什么输入 code 命令打不开 VS Code? + +![输入 code 命令出错](figures/faq6.png) + +> [!NOTE] +> 注:code 是命令 ‘.’ 是参数,中间有空格 + +**A:** 添加 vscode 所在路径的环境变量,添加环境变量,并需要注销或者重启系统 + +### Q: 为什么调试时提示找不到 ‘qemu-system-arm’? + +**A:** 直接打开 VS Code 调试工程会有这个错误,**每次调试请使用 Env 工具在 BSP 根目录使用`code .`命令打开 VS Code** 。 + +### Q: 为什么 VS Code 调试选项没有 Debug@windows 选项? + +**A:** 请更新 RT-Thread 源代码到 v3.1.0 及以上版本。 + +### Q: 为什么提示找不到 qemu debug? + +![找不到 qemu debug](figures/faq9.png) + + **A:** 请看下图中的操作步骤 + +![调试工程注意事项](figures/faq10.png) + +### Q: 为什么会出现 cppdbg 错误? + +![出现 cppdbg 错误](figures/faq11.png) + + **A:** 只是没有安装插件的原因,请按教程文档上描述安装上 c/c++ 插件 + +### Q: 为什么调试报错,出现一长串数字? + +![调试出现一长串数字](figures/faq17.jpg) + +**A:** 这是因为没有在文件 qemu-dbg.bat 的参数前加 start 或者 没关闭之前运行的 qemu,要先关闭之前运行的 qemu,才能开始调试。 + +**解决方法:** 按教程中操作,在文件 qemu-dbg.bat 的参数前加 start 或着先关闭之前运行的 qemu,再按 F5。 + +### Q: 为什么按 F5 不能调试? + +**A:** 笔记本电脑的 F5 一般和功能键复用了,如果按下 F5 是调节屏幕亮度或其他功能,可以尝试按 `Fn + F5`。 + +## 文件系统 + +### Q: 为什么 romfs 编译会报错? + +**A:** romfs ramfs 文件系统中的**文件名**和c的变量的命名一样,只能由英文字母开头且仅包含数字和下划线,不能有中横线。 + +### Q: 为什么 ramfs 运行会出现断言错误? + +![ramfs 运行出现断言错误](figures/faq19.png) + +**A:** 这是一个已经解决的 bug ,需要更新源码,拉取最新源码的方法见第一节环境搭建的视频。 + +### Q: 为什么中文名的文件会显示乱码? + +![中文名称文件显示乱码](figures/faq16.jpg) + +**A:** 因为显示的编码不同,VScode 是 UTF8, Env 暂时还不支持 UTF-8。 + +### Q: 为什么 Env 中找不到示例代码的选项? + +![在 Env 中找不到示例代码](figures/faq18.png) + +**A:** Env 工具需要更新了,输入命令 `pkgs --upgrade` 升级 env,然后请仔细看一遍 《Env 用户手册》(链接地址在本文档头部)。 + +### Q: 为什么输入 ls 命令提示 No such directory? + +**A:** 这是文件系统初始化失败了,查看文件系统初始化的代码,检查问题。 + +## 网络抓包 + +检测不到网卡的话,请自行百度。 + +## socket 编程 + +注意:socket 不支持多线程操作 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq1.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac3c7b0cf065da967b7e22c3ae642130f14239a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq10.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq10.png new file mode 100644 index 0000000000000000000000000000000000000000..408054cd906bf1e7969c871f9ed55be70a9d3f3a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq10.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq11.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq11.png new file mode 100644 index 0000000000000000000000000000000000000000..67046357b12a9aab6b24c2966f1c8d0b6eaf52a3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq11.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq12.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq12.png new file mode 100644 index 0000000000000000000000000000000000000000..2ec365b469769c47a529bfd92979f91ad4ac9d1e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq12.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq13.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq13.png new file mode 100644 index 0000000000000000000000000000000000000000..03b582473e5f4ad2315258cf7ca10d36b63e8e10 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq13.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq15.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq15.png new file mode 100644 index 0000000000000000000000000000000000000000..eeeecf9e42bedf1ee4a72c6b399d7f99fbaec797 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq15.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq16.jpg b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq16.jpg new file mode 100644 index 0000000000000000000000000000000000000000..db507dbacc8e6dc91a06bcd3d7ca675e76a93b0b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq16.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq17.jpg b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq17.jpg new file mode 100644 index 0000000000000000000000000000000000000000..81e726edd152b07fcd1f9230b8c9d3f2a91d2f1f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq17.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq18.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq18.png new file mode 100644 index 0000000000000000000000000000000000000000..cb32d23496222d441c8b70c885d13fb61c4b7a9d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq18.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq19.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq19.png new file mode 100644 index 0000000000000000000000000000000000000000..56e0641ae405b96278290b5249901a3a089d8d5d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq19.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq2.png new file mode 100644 index 0000000000000000000000000000000000000000..7e8326375a759c7a14d928ab2d02eaa21fc98af7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq20.jpg b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq20.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9a80fd84be9f0eec59ccaa72ded40b7b6b1c6517 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq20.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq21.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq21.png new file mode 100644 index 0000000000000000000000000000000000000000..188b6daf20e4583278698a10ed74f152b71da24b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq21.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq22.jpg b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq22.jpg new file mode 100644 index 0000000000000000000000000000000000000000..06531d8a8fd166a6662e113186d5a0a2e56f40ed Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq22.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq3.png new file mode 100644 index 0000000000000000000000000000000000000000..e6bd71c5baa66fbd554d9b15e15bf6d658508ff8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq4.png new file mode 100644 index 0000000000000000000000000000000000000000..85f52c628fdcc8a739271db9cf55456811df2790 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq5.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq5.png new file mode 100644 index 0000000000000000000000000000000000000000..a9ddb0b8582edde0b16f80cb5b8b185759e9763a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq6.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq6.png new file mode 100644 index 0000000000000000000000000000000000000000..d60de7256a9af4a002b1242f9aa79697920412d6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq6.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq7.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq7.png new file mode 100644 index 0000000000000000000000000000000000000000..02b3b66679d561774e45470b6ff753f711dbb174 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq7.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq8.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq8.png new file mode 100644 index 0000000000000000000000000000000000000000..87ed0a3154e7d645126cfa97ab8c150ee31eaaa4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq8.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq9.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq9.png new file mode 100644 index 0000000000000000000000000000000000000000..441d2286c90ebc7946fe4ecab6cc488567b8704d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/faq/figures/faq9.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/filesystems1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/filesystems1.png new file mode 100644 index 0000000000000000000000000000000000000000..c2211c3db0af8c2c3bd3d3dff72a959a803d4e1f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/filesystems1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/filesystems2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/filesystems2.png new file mode 100644 index 0000000000000000000000000000000000000000..bb84dc61c4bda0b07fcaec0d2a99a0031c5266a8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/filesystems2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/ramfs1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/ramfs1.png new file mode 100644 index 0000000000000000000000000000000000000000..ee131b1907c492dc4eeb8baa3e92f142130d4099 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/ramfs1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/ramfs2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/ramfs2.png new file mode 100644 index 0000000000000000000000000000000000000000..580a1dbe91b28adc68efe75f689c41d6168860aa Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/ramfs2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs1.png new file mode 100644 index 0000000000000000000000000000000000000000..a364410868321d7fd482f81c39ba57714308a3a5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs2.png new file mode 100644 index 0000000000000000000000000000000000000000..fceb4d6394f3eeedf22158c63d467db0d205c521 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs3.png new file mode 100644 index 0000000000000000000000000000000000000000..bb257919cad24325ea23a53f6c62ed523ad1cf49 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs4.png new file mode 100644 index 0000000000000000000000000000000000000000..4a0e73ec90f4f9305a4f5e648514cd8510f6902e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs5.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs5.png new file mode 100644 index 0000000000000000000000000000000000000000..84ee953f6f557efb3b302470333d2600b08d2a8a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs6.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs6.png new file mode 100644 index 0000000000000000000000000000000000000000..84fef516aaa3608b5bc587d28f08a891597acf82 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/romfs6.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd1.png new file mode 100644 index 0000000000000000000000000000000000000000..74ab90e41d6bf5a7df25f7d1a8a79f3dec02fc9a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd2.png new file mode 100644 index 0000000000000000000000000000000000000000..c37530c64710380de593a23e7f2cfe1c5a32b2e1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd3.png new file mode 100644 index 0000000000000000000000000000000000000000..bea1b7666506f54bab6d980da1c67c1c69ed6db8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd4.png new file mode 100644 index 0000000000000000000000000000000000000000..bc959ad657a81a59188bf11caa1a309f2880b4f2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd5.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd5.png new file mode 100644 index 0000000000000000000000000000000000000000..69cca39ae0b60947a5a8bce42e12e637a8efedd4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd6.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd6.png new file mode 100644 index 0000000000000000000000000000000000000000..f484b1b43f82f367ccd1dedd7073d95dc08bb2f9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd6.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd7.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd7.png new file mode 100644 index 0000000000000000000000000000000000000000..ffa176ad184a5740c0ad9c2802ccca533199f5d4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/figures/sd7.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/filesystems.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/filesystems.md new file mode 100644 index 0000000000000000000000000000000000000000..6d32b293d1ef0d5f0174aac0136d0c7ea23b9652 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/filesystems.md @@ -0,0 +1,325 @@ +# 使用RT-Thread文件系统 + + + +> 提示:视频 PPT 下载 + +## SD 卡挂载操作代码 + +挂载文件系统的源代码位于 `qemu-vexpress-a9\applications\mnt.c` 中。在实际代码中会将块设备 `sd0` 中的文件系统挂载到根目录 `/` 上。 + +``` +#include + +#ifdef RT_USING_DFS +#include + +int mnt_init(void) +{ + rt_thread_delay(RT_TICK_PER_SECOND); + + if (dfs_mount("sd0", "/", "elm", 0, 0) == 0) + { + rt_kprintf("file system initialization done!\n"); + } + + return 0; +} +INIT_ENV_EXPORT(mnt_init); +#endif +``` + +## 常用命令展示 + +在挂载文件系统成功之后,就可以在 msh 中使用一些常用命令体验文件系统了。 + +### ls: 查看当前目录信息 + +```shell +msh />ls # 使用 ls 命令查看文件系统目录信息 +Directory /: # 可以看到已经存在根目录 / +``` + +### mkdir: 创建文件夹 + +```shell +msh />mkdir rt-thread # 创建 rt-thread 文件夹 +msh />ls # 查看目录信息如下 +Directory /: +rt-thread +``` + +### echo: 将输入的字符串输出到指定输出位置 + +```shell +msh />echo "hello rt-thread!!!" # 将字符串输出到标准输出 +hello rt-thread!!! +msh />echo "hello rt-thread!!!" hello.txt # 将字符串输出到 hello.txt 文件 +msh />ls +Directory /: +rt-thread +hello.txt 18 +msh /> +``` + +### cat: 查看文件内容 + +```shell +msh />cat hello.txt # 查看 hello.txt 文件的内容并输出 +hello rt-thread!!! +``` + +### rm: 删除文件夹或文件 + +```shell +msh />ls # 查看当前目录信息 +Directory /: +rt-thread +hello.txt 18 +msh />rm rt-thread # 删除 rt-thread 文件夹 +msh />ls +Directory /: +hello.txt 18 +msh />rm hello.txt # 删除 hello.txt 文件 +msh />ls +Directory /: +msh /> +``` + +## 运行文件系统示例程序 + +了解了文件系统的一些常用命令之后,下面带领大家通过运行文件系统的一些示例程序,来熟悉文件系统的基本操作。示例程序通过使用一些 DFS 的 API 接口来实现,并将示例导出到 msh 命令,通过运行示例程序并对照示例程序源码,有利于我们尽快上手操作文件系统。 + +### 获取示例代码 + +文件系统的示例代码包含在 RT-Thread samples 软件包中,可以通过 Env 配置将示例代码加入到项目中,路径如下所示。 + +``` + RT-Thread online packages ---> + miscellaneous packages ---> + samples: RT-Thread kernel and components samples ---> + [*] a filesystem_samples package for rt-thread ---> +``` + +将示例代码全部选中,然后退出**保存**并**更新软件包**即可将示例代码加入到工程里。 + +![获取示例程序](figures/filesystems1.png) + +### 运行示例代码 + +在运行示例代码之前需要先输入 `scons` 编译一遍工程。 + +然后输入 `.\qemu.bat` 运行工程 + +RT-Thread 启动完成之后,按 TAB 键查看 msh 命令,文件系统 samples 命令已经导出到 msh : + +![查看所有例程](figures/filesystems2.png) + +然后就可以输入命令运行相应的示例代码了。 + +例如:执行命令 mkdir_sample 的运行结果是 + +``` +msh />mkdir_sample +mkdir ok! +msh />ls +Directory /: +dir_test +``` + +然后我们就可以对照这几个示例代码的源码来详细的了解文件系统 API 的用法了。 + +## QEMU SD卡的读写 + +QEMU 运行起来之后会在 `bsp\qemu-vexpress-a9` 目录下创建一个 sd.bin 文件。这是一个虚拟的 SD 卡,RT-Thread 默认的文件系统就是搭建在这个里面的。 + +![sd.bin 位置](figures/sd6.png) + +### 读取 QEMU SD 卡的内容 + +因 sd.bin 本质上就是一个 FAT 文件系统的镜像文件,所以我们利用支持提取 FAT 格式的解压软件 7-Zip 就可以将 sd.bin 里的文件读取出来。 + +![读取 QEMU SD 卡的内容](figures/sd1.png) + +### 制作 QEMU SD 卡 + +在 Env 工具的 `tools/fatdisk` 目录下有一个打包 FAT 格式文件的工具 fatdisk.exe,我们可以利用这个工具将我们要存储到QEMU SD卡里的文件打包成 sd.bin 文件。然后用新生成的 sd.bin 替换掉 `bsp\qemu-vexpress-a9` 目录下原来的 sd.bin 文件即可。 + +打开路径 `env/tools/fatdisk` 并在该目录下新建文件夹 sd + +![新建文件夹 sd](figures/sd2.png) + +打开上一步我们创建好的文件夹,放入我们需要存储到QEMU SD卡里的文件或文件夹 + +![在 sd 文件夹里放入文件](figures/sd3.png) + +修改 `env/tools/fatdisk` 目录下 fatdisk.xml 文件为下面的内容(视频中内容有误) + +```xml + + + 65536 + 512 + sd + sd.bin + 0 + +``` + +在 `env/tools/fatdisk` 目录下右键打开 Env 工具,输入命令 `fatdisk` 运行,就会在当前目录下生成 sd.bin 文件了。 + +![运行 fatdisk](figures/sd5.png) + +然后用新生成的 sd.bin 替换掉 `bsp\qemu-vexpress-a9` 目录下原来的 sd.bin 文件 + +运行 qemu 输入 ls 可以看到我们存储到QEMU SD卡里的文件和文件夹了。 + +![查看文件及文件夹](figures/sd7.png) + +## 使用 RomFS + +RomFS 是在嵌入式设备上常用的一种文件系统,具备体积小,可靠性高,读取速度快等优点,常用来作为系统初始文件系统。但也具有其局限性,RomFS 是一种只读文件系统。 + +不同的数据存储方式对文件系统占用空间,读写效率,查找速度等主要性能影响极大。RomFS 使用顺序存储方式,所有数据都是顺序存放的。因此 RomFS 中的数据一旦确定就无法修改,这是 RomFS 是一种只读文件系统的原因。也由于这种顺序存放策略,RomFS 中每个文件的数据都能连续存放,读取过程中只需要一次寻址操作,就可以读入整块数据,因此 RomFS 中读取数据效率很高。 + +### 配置使能 RomFS + +开启 RT-Thread 对 RomFS 的支持,并调整最大支持的文件系统类型数目。 + +打开 menuconfig 菜单,保证 “RT-Thread Components” → “Device virtual file system” → “Enable ReadOnly file system on flash” 为开启状态: + +![开启 romfs](figures/romfs1.png) + +打开子菜单 "RT-Thread Components" → "Device virtual file system" 调整最大支持文件系统系统类型数: + +![调整文件系统参数](figures/romfs2.png) + +### 生成 romfs.c 文件 + +由于 RomFS 是只读文件系统,所以需要放入到 RomFS 的文件都需要在系统运行前加入。我们可以将要存入 RomFS 中的文件数据放在 romfs.c 文件中,RT-Thread提供了制作 romfs.c 的 Python 脚本文件 mkromfs.py,根据用户需要加入到 RomFS 的文件和目录生成对应的数据结构。 + +打开 mkromfs.py 脚本文件所在路径 `rt-thread\tools` 并在该目录下新建文件夹 romfs + +![新建文件夹 romfs](figures/romfs3.png) + +打开上一步我们创建好的文件夹,放入我们需要在 RomFS 中放置的文件或文件夹。 + +![在文件夹romfs中放入文件](figures/romfs4.png) + +回到上一级目录 `rt-thread\tools`,在该目录下打开 Env 工具,并运行命令: + +``` +python mkromfs.py romfs romfs.c +``` + +可以看到目录下成功生成 romfs.c 文件: + +![romfs.c 所在路径](figures/romfs5.png) + +最后还需要生成的 romfs.c 文件拷贝在与 mnt.c 文件相同的路径 `qemu-vexpress-a9\applications` 内。 + +### 挂载 RomFS + +在系统任务调度开始之后,通过 dfs_mount() 函数挂载 RomFS ,在添加挂载函数的文件中需添加头文件 `#include "dfs_romfs.h"` + +我们将 `qemu-vexpress-a9\applications\mnt.c` 文件中的内容替换成下面的代码,即可将 RomFS 挂载到根目录。 + +```c +#include + +#ifdef RT_USING_DFS +#include +#include "dfs_romfs.h" + +int mnt_init(void) +{ + if (dfs_mount(RT_NULL, "/", "rom", 0, &(romfs_root)) == 0) + { + rt_kprintf("ROM file system initializated!\n"); + } + else + { + rt_kprintf("ROM file system initializate failed!\n"); + } + + return 0; +} +INIT_ENV_EXPORT(mnt_init); +#endif +``` + +### 预期结果 + +编译并运行工程之后,可以看到 RomFS 文件系统挂载成功,使用 ls 命令可以看到 RomFS 文件系统里面的文件夹和文件: + +![预期结果](figures/romfs6.png) + +## 使用 RamFS + +RamFS 顾名思义是内存文件系统,它不能格式化,可以同时创建多个,在创建时可以指定其最大能使用的内存大小。其优点是读写速度很快,但存在掉电丢失的风险。如果一个进程的性能瓶颈是硬盘的读写,那么可以考虑在 RamFS 或 tmpfs 上进行大文件的读写操作。 + +RamFS 使用链式存储方式,并且数据存储在内存空间,因此 RamFS 具备了可读写文件系统的特征,同时也拥有较快的读写速度。 + +### 配置使能 RamFS + +打开 menuconfig 菜单,保证 “RT-Thread Components” → “Device virtual file system” → “Enable RAM file system” 为开启状态: + +![开启 ramfs](figures/ramfs1.png) + +### 挂载 RamFS + +由于 RamFS 是在系统运行过程中动态创建的,所以在挂载之前我们应该先创建 RamFS ,RT-Thread 提供了创建 RamFS 的 API 接口: + +```c +struct dfs_ramfs* dfs_ramfs_create(rt_uint8_t *pool, rt_size_t size) +``` + +| 参数 | 描述 | +| -------- | ---------------------------- | +| pool | 文件系统内存池地址 | +| size | 文件系统大小 | +| **返回** | **——** | +| > 0 | 文件系统根目录对应的数据结构 | +| < = 0 | 失败 | + +在系统任务调度开始之后,通过 dfs_mount() 函数挂载 RamFS + +我们将 `qemu-vexpress-a9\applications\mnt.c` 文件中的内容替换成下面的代码,即可将 RamFS 挂载到根目录。 + +```c +#include + +#ifdef RT_USING_DFS +#include + +int mnt_init(void) +{ + if (dfs_mount(RT_NULL, "/", "ram", 0, dfs_ramfs_create(rt_malloc(1024),1024)) == 0) + { + rt_kprintf("RAM file system initializated!\n"); + } + else + { + rt_kprintf("RAM file system initializate failed!\n"); + } + + return 0; +} +INIT_ENV_EXPORT(mnt_init); +#endif +``` + +### 预期结果 + +编译并运行工程之后, 可以看到 RamFS 文件系统挂载成功了。然后我们使用 echo 命令创建一个文件,可以看到创建成功了。 + +![运行结果](figures/ramfs2.png) + +## 参考资料 + +* [文件系统 samples](https://github.com/RT-Thread-packages/filesystem-sample) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/filesystems.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/filesystems.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0fa3d99ae373c8883a9503eb9d99fab00c460926 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/filesystems.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/figures/cjson.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/figures/cjson.png new file mode 100644 index 0000000000000000000000000000000000000000..8f7474010a912f6ddbaab360c9f22ffb484b1c1b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/figures/cjson.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/figures/webclient1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/figures/webclient1.png new file mode 100644 index 0000000000000000000000000000000000000000..15bad15328adf873f88b232a1b6d1a6f262ae425 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/figures/webclient1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/httpclient.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/httpclient.md new file mode 100644 index 0000000000000000000000000000000000000000..3a068f910db730d8763418a2364f7771639b09f8 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/httpclient.md @@ -0,0 +1,252 @@ +# 使用HTTP获取网络天气预报 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +HTTP 协议是互联网上应用最为广泛的一种网络协议,越来越多的应用程序需要直接通过 HTTP 协议来访问网络资源。webclient 是 RT-Thread 上实现的一个 HTTP 客户端,用来提供高效且功能丰富的 HTTP 客户端编程工具包。 + +这个教程展示了如何利用 HTTP 协议获取天气,我们是使用 webclient 这个工具包实现的。 + +## 准备工作 + +### 开启 WebClient 软件包 + +[webclient](https://github.com/RT-Thread-packages/webclient) 是一个 **HTTP** 协议的客户端工具 ,利用这个工具可以完成与 HTTP 服务器的通信。 + +打开 Env 工具输入 menuconfig 按照下面的路径开启 WebClient 软件包 + +``` +RT-Thread online packages + IoT - internet of things ---> + [*] WebClient: A webclient package for rt-thread ---> +``` + +WebClient 软件包的配置如下所示 + +![webclient 配置](figures/webclient1.png) + + +- Eable support tls protocol: 开启加密传输 +- Enable webclient GET/POST samples:开启 webclient 的 GET/POST 示例程序 +- version (latest) --->: 选择版本 + +### 开启 cJSON 软件包 + +因为服务器返回的天气信息是 JSON 格式的,所以我们用了一个用 C 语言实现的 JSON 解析库。 + +打开 Env 工具输入 menuconfig 按照下面的路径开启 [cJSON](https://github.com/RT-Thread-packages/cJSON) 软件包 + +``` +RT-Thread online packages + IoT - internet of things ---> + [*] cJSON: Ultralightweight JSON parser in ANSI C ---> +``` + +cJSON 软件包的配置保持默认即可 + +![开启 cJSON](figures/cjson.png) + +- version (v1.0.2) --->: 选择版本 + +### 获取示例代码 + +RT-Thread samples 软件包中中已有一份该示例代码 [httpclient.c](https://github.com/RT-Thread-packages/network-sample/blob/master/httpclient_sample.c),可以通过 Env配置将示例代码加入到项目中。 + +``` + RT-Thread online packages ---> + miscellaneous packages ---> + samples: RT-Thread kernel and components samples ---> + a network_samples package for rt-thread ---> + [*] [network] http client +``` + +### 示例代码文件 + +```c +/* +* 程序清单:利用 http client 获取天气 + * + * 这是一个利用 http client 获取天气的例程 + * 导出 weather 命令到控制终端 + * 命令调用格式:weather + * 无参数 + * 程序功能:作为一个 http 客户端,通过 GET 方法与远方服务器通信,获取到服务器传来的天气信息 +*/ + +#include /* 使用 HTTP 协议与服务器通信需要包含此头文件 */ +#include /* 使用BSD socket,需要包含socket.h头文件 */ +#include +#include +#include + +#define GET_HEADER_BUFSZ 1024 //头部大小 +#define GET_RESP_BUFSZ 1024 //响应缓冲区大小 +#define GET_URL_LEN_MAX 256 //网址最大长度 +#define GET_URI "http://mobile.weather.com.cn/data/sk/%s.html" //获取天气的 API +#define AREA_ID "101021300" //上海浦东地区 ID + +/* 天气数据解析 */ +void weather_data_parse(rt_uint8_t *data) +{ + cJSON *root = RT_NULL, *object = RT_NULL, *item = RT_NULL; + + root = cJSON_Parse((const char *)data); + if (!root) + { + rt_kprintf("No memory for cJSON root!\n"); + return; + } + object = cJSON_GetObjectItem(root, "sk_info"); + + item = cJSON_GetObjectItem(object, "cityName"); + rt_kprintf("\ncityName:%s ", item->valuestring); + + item = cJSON_GetObjectItem(object, "temp"); + rt_kprintf("\ntemp :%s ", item->valuestring); + + item = cJSON_GetObjectItem(object, "wd"); + rt_kprintf("\nwd :%s ", item->valuestring); + + item = cJSON_GetObjectItem(object, "ws"); + rt_kprintf("\nws :%s ", item->valuestring); + + item = cJSON_GetObjectItem(object, "sd"); + rt_kprintf("\nsd :%s ", item->valuestring); + + item = cJSON_GetObjectItem(object, "date"); + rt_kprintf("\ndate :%s", item->valuestring); + + item = cJSON_GetObjectItem(object, "time"); + rt_kprintf("\ntime :%s \n", item->valuestring); + + if (root != RT_NULL) + cJSON_Delete(root); +} +void weather(int argc, char **argv) +{ + rt_uint8_t *buffer = RT_NULL; + int resp_status; + struct webclient_session *session = RT_NULL; + char *weather_url = RT_NULL; + int content_length = -1, bytes_read = 0; + int content_pos = 0; + + /* 为 weather_url 分配空间 */ + weather_url = rt_calloc(1, GET_URL_LEN_MAX); + if (weather_url == RT_NULL) + { + rt_kprintf("No memory for weather_url!\n"); + goto __exit; + } + /* 拼接 GET 网址 */ + rt_snprintf(weather_url, GET_URL_LEN_MAX, GET_URI, AREA_ID); + + /* 创建会话并且设置响应的大小 */ + session = webclient_session_create(GET_HEADER_BUFSZ); + if (session == RT_NULL) + { + rt_kprintf("No memory for get header!\n"); + goto __exit; + } + + /* 发送 GET 请求使用默认的头部 */ + if ((resp_status = webclient_get(session, weather_url)) != 200) + { + rt_kprintf("webclient GET request failed, response(%d) error.\n", resp_status); + goto __exit; + } + + /* 分配用于存放接收数据的缓冲 */ + buffer = rt_calloc(1, GET_RESP_BUFSZ); + if(buffer == RT_NULL) + { + rt_kprintf("No memory for data receive buffer!\n"); + goto __exit; + } + + content_length = webclient_content_length_get(session); + if (content_length < 0) + { + /* 返回的数据是分块传输的. */ + do + { + bytes_read = webclient_read(session, buffer, GET_RESP_BUFSZ); + if (bytes_read <= 0) + { + break; + } + } while (1); + } + else + { + do + { + bytes_read = webclient_read(session, buffer, + content_length - content_pos > GET_RESP_BUFSZ ? + GET_RESP_BUFSZ : content_length - content_pos); + if (bytes_read <= 0) + { + break; + } + content_pos += bytes_read; + } while (content_pos < content_length); + } + + /* 天气数据解析 */ + weather_data_parse(buffer); + +__exit: + /* 释放网址空间 */ + if (weather_url != RT_NULL) + rt_free(weather_url); + /* 关闭会话 */ + if (session != RT_NULL) + webclient_close(session); + /* 释放缓冲区空间 */ + if (buffer != RT_NULL) + rt_free(buffer); +} + +MSH_CMD_EXPORT(weather, Get weather by webclient); + +``` + +示例代码中的 `AREA_ID` 是城市的[代码](https://blog.csdn.net/wanghao940101/article/details/72416518) ,更换城市代码可以获取不同城市的天气。 + +## 在 msh shell 中运行示例代码 + +示例代码中已经将 weather 命令导出到了 msh 命令列表中,因此系统运行起来后,在 msh 命令行下输入 weather 命令即可让示例代码运行。 + +``` +msh> weather +``` + +## 预期结果 ## + +终端会打印出一些天气信息 + +``` +cityName:浦东 +temp :25℃ +wd :东风 +ws :2级 +sd :49% +date :20131012 +time :15:00 +``` + +> [!NOTE] +> 注:* QEMU 模拟器平台由于 Env 编码格式不支持 UTF-8,会出现中文显示乱码的问题。 + * 此程序仅为 http client 的示例程序,其中获取天气的 API 已经被弃用,实际使用时更换为其他的 API 才可获取最新的天气。 + +## 参考资料 + +* 源码 [httpclient.c](https://github.com/RT-Thread-packages/network-sample/blob/master/httpclient_sample.c) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/httpclient.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/httpclient.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0fca01b873339b2af4a0490f5f288e7a9d6f4e20 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/httpclient/httpclient.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt1.png new file mode 100644 index 0000000000000000000000000000000000000000..0be80464720d596f0e0b8839d02727a2524d02f0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt2.png new file mode 100644 index 0000000000000000000000000000000000000000..5a334578d98b55d12f0ed8bc5c428501a81905d1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt3.png new file mode 100644 index 0000000000000000000000000000000000000000..60eaf2c7a02f7df107fa94d78504427bca02cc0c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/figures/mqtt3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/mqtt.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/mqtt.md new file mode 100644 index 0000000000000000000000000000000000000000..7b842f5b7a6e37268f1420041868d76a3e2ccc17 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/mqtt.md @@ -0,0 +1,272 @@ +# 使用MQTT进行物联网即时通讯 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通讯协议,该协议构建于 TCP/IP 协议上,由 IBM 在 1999 年发布。 + +MQTT 最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。 + +MQTT 是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT 协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。 + +本教程就是介绍如何利用 RT-Thread 开发的 [Paho MQTT](https://github.com/RT-Thread-packages/paho-mqtt) 软件包与 MQTT 服务器进行通信的。 + +## 准备工作 + +### 开启 Paho MQTT 软件包 + +打开 Env 工具输入 menuconfig 按照下面的路径开启 Paho MQTT 软件包 + +``` +RT-Thread online packages + IoT - internet of things ---> + [*] Paho MQTT: Eclipse Paho MQTT C/C++ client for Embedded platforms ---> +``` + +进入 Paho MQTT 软件包的配置菜单按下图所示配置 + +![开启 mqtt](figures/mqtt1.png) + +- Enable MQTT example:开启 MQTT 示例 + +- Enable MQTT test:开启测试例程 + +- Enable support tls protocol:开启 TLS 安全传输选项 + +- Set MQTT thread stack size:配置 MQTT 线程堆栈大小 + +- Max pahomqtt subscribe topic handlers:配置 MQTT 能订阅的最大 topic 主题数量 + +- Enable debug log output:开启调试日志 + +- latest_version:配置包版本选为最新版 + +### 示例代码文件 + +测试服务器使用 Eclipse 的测试服务器,地址 `iot.eclipse.org` ,端口 `1883`,MQTT 功能示例代码如下: + +```c +#include +#include +#include + +#include + +#define DBG_ENABLE +#define DBG_SECTION_NAME "MQTT" +#define DBG_LEVEL DBG_LOG +#define DBG_COLOR +#include + +#include "paho_mqtt.h" + +/** + * MQTT URI farmat: + * domain mode + * tcp://iot.eclipse.org:1883 + * + * ipv4 mode + * tcp://192.168.10.1:1883 + * ssl://192.168.10.1:1884 + * + * ipv6 mode + * tcp://[fe80::20c:29ff:fe9a:a07e]:1883 + * ssl://[fe80::20c:29ff:fe9a:a07e]:1884 + */ +#define MQTT_URI "tcp://iot.eclipse.org:1883"// 配置测试服务器地址 +#define MQTT_USERNAME "admin" +#define MQTT_PASSWORD "admin" +#define MQTT_SUBTOPIC "/mqtt/test" // 设置订阅主题 +#define MQTT_PUBTOPIC "/mqtt/test" // 设置推送主题 +#define MQTT_WILLMSG "Goodbye!" // 设置遗言消息 + +/* 定义 MQTT 客户端环境结构体 */ +static MQTTClient client; + +static void mqtt_sub_callback(MQTTClient *c, MessageData *msg_data) +{ + *((char *)msg_data->message->payload + msg_data->message->payloadlen) = '\0'; + LOG_D("mqtt sub callback: %.*s %.*s", + msg_data->topicName->lenstring.len, + msg_data->topicName->lenstring.data, + msg_data->message->payloadlen, + (char *)msg_data->message->payload); + + return; +} +/* MQTT 订阅事件默认回调函数 */ +static void mqtt_sub_default_callback(MQTTClient *c, MessageData *msg_data) +{ + *((char *)msg_data->message->payload + msg_data->message->payloadlen) = '\0'; + LOG_D("mqtt sub default callback: %.*s %.*s", + msg_data->topicName->lenstring.len, + msg_data->topicName->lenstring.data, + msg_data->message->payloadlen, + (char *)msg_data->message->payload); + return; +} +/* MQTT 连接事件回调函数 */ +static void mqtt_connect_callback(MQTTClient *c) +{ + LOG_D("inter mqtt_connect_callback!"); +} +/* MQTT 上线事件回调函数 */ +static void mqtt_online_callback(MQTTClient *c) +{ + LOG_D("inter mqtt_online_callback!"); +} +/* MQTT 下线事件回调函数 */ +static void mqtt_offline_callback(MQTTClient *c) +{ + LOG_D("inter mqtt_offline_callback!"); +} + +/** + * 这个函数创建并配置 MQTT 客户端。 + * + * @param void + * + * @return none + */ +static void mq_start(void) +{ + /* 使用 MQTTPacket_connectData_initializer 初始化 condata 参数 */ + MQTTPacket_connectData condata = MQTTPacket_connectData_initializer; + static char cid[20] = { 0 }; + + static int is_started = 0; + if (is_started) + { + return; + } + /* 配置 MQTT 结构体内容参数 */ + { + client.isconnected = 0; + client.uri = MQTT_URI; + + /* 产生随机的客户端 ID */ + rt_snprintf(cid, sizeof(cid), "rtthread%d", rt_tick_get()); + /* 配置连接参数 */ + memcpy(&client.condata, &condata, sizeof(condata)); + client.condata.clientID.cstring = cid; + client.condata.keepAliveInterval = 60; + client.condata.cleansession = 1; + client.condata.username.cstring = MQTT_USERNAME; + client.condata.password.cstring = MQTT_PASSWORD; + + /* 配置 MQTT 遗言参数 */ + client.condata.willFlag = 1; + client.condata.will.qos = 1; + client.condata.will.retained = 0; + client.condata.will.topicName.cstring = MQTT_PUBTOPIC; + client.condata.will.message.cstring = MQTT_WILLMSG; + + /* 分配缓冲区 */ + client.buf_size = client.readbuf_size = 1024; + client.buf = malloc(client.buf_size); + client.readbuf = malloc(client.readbuf_size); + if (!(client.buf && client.readbuf)) + { + LOG_E("no memory for MQTT client buffer!"); + goto _exit; + } + + /* 设置事件回调函数 */ + client.connect_callback = mqtt_connect_callback; + client.online_callback = mqtt_online_callback; + client.offline_callback = mqtt_offline_callback; + + /* 设置订阅表和事件回调函数*/ + client.messageHandlers[0].topicFilter = MQTT_SUBTOPIC; + client.messageHandlers[0].callback = mqtt_sub_callback; + client.messageHandlers[0].qos = QOS1; + + /* 设置默认的订阅主题*/ + client.defaultMessageHandler = mqtt_sub_default_callback; + } + + /* 运行 MQTT 客户端 */ + paho_mqtt_start(&client); + is_started = 1; + +_exit: + return; +} + +/** + * 这个函数推送消息给特定的 MQTT 主题。 + * + * @param send_str publish message + * + * @return none + */ +static void mq_publish(const char *send_str) +{ + MQTTMessage message; + const char *msg_str = send_str; + const char *topic = MQTT_PUBTOPIC; + message.qos = QOS1; //消息等级 + message.retained = 0; + message.payload = (void *)msg_str; + message.payloadlen = strlen(message.payload); + + MQTTPublish(&client, topic, &message); + + return; +} + +#ifdef RT_USING_FINSH +#include +FINSH_FUNCTION_EXPORT(mq_start, startup mqtt client); +FINSH_FUNCTION_EXPORT(mq_publish, publish mqtt msg); +#ifdef FINSH_USING_MSH +MSH_CMD_EXPORT(mq_start, startup mqtt client); + +int mq_pub(int argc, char **argv) +{ + if (argc != 2) + { + rt_kprintf("More than two input parameters err!!\n"); + return 0; + } + mq_publish(argv[1]); + + return 0; +} +MSH_CMD_EXPORT(mq_pub, publish mqtt msg); +#endif /* FINSH_USING_MSH */ +#endif /* RT_USING_FINSH */ +``` + +## 在 msh shell 中运行示例代码 + +系统运行起来后,在 msh 命令行下输入 mq_start 命令即可让示例代码运行。 + +``` +msh> mq_start +``` + +## 预期结果 + +![mq_start 运行结果](figures/mqtt2.png) + +发布消息用命令 `mq_pub` ,用于向固定的 MQTT Topic 发送数据,同时 MQTT 服务器会立刻向该 Topic 发送同样数据,MQTT 示例测试完成,如下图所示: + +![mq_pub 运行结果](figures/mqtt3.png) + +> [!NOTE] +> 注:* 如用 QEMU 模拟器平台需要连接外网,如出现连接失败的情况,可以尝试关闭防火墙 + * 如果使用 MQTT TLS 加密连接,MQTT 线程栈至少需要 6144 字节。 + +## 参考资料 + +* 源码 [Paho MQTT](https://github.com/RT-Thread-packages/paho-mqtt) + +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/mqtt.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/mqtt.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a83b93330c0a8ac18dea729896f05c3920eb8c2f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/mqtt/mqtt.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ntp/ntp.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ntp/ntp.md new file mode 100644 index 0000000000000000000000000000000000000000..3e05f8024f7b5676eb61a69e4e4f98be0bf4bcf2 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ntp/ntp.md @@ -0,0 +1,52 @@ +# 使用NTP轻松获取网络时间 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +[NTP](https://baike.baidu.com/item/NTP) 网络时间协议(Network Time Protocol),是用来同步网络中各个计算机时间的协议。 + +本教程介绍了如何开启 NTP 软件包(一个运行在 RT_Thread 上的 NTP 客户端),并且当连接上网络后,如何利用这个软件包,获取当前的 UTC 时间,并更新至 RTC(实时时钟)中。 + +## 获取示例代码 ## + +打开 Env 工具输入 menuconfig 按照下面的路径即可打开 ntp 客户端 + +``` + RT-Thread online packages ---> + IoT - internet of things ---> + netutils: Networking utilities for RT-Thread ---> + [*] Enable NTP(Network Time Protocol) client +``` + +## 在 msh shell 中运行代码 + +系统运行起来后,在 msh 命令行下输入 ntp_sync 命令即可通过 NTP 协议获取当前的 UTC 时间,并更新至 RTC(实时时钟)中。 + +``` +msh> ntp_sync +``` + +## 预期结果 ## + +如果程序能够正常运行,就会获取到正确的网络时间,运行结果如下 + +``` +Get local time from NTP server: Wed Jul 11 17:19:02 2018 +The system time is updated. Timezone is 8. +``` + +> [!NOTE] +> 注:* 如出现连接失败的情况,请尝试关闭防火墙 + +## 参考资料 + +* 源码 [ntp](https://github.com/RT-Thread-packages/netutils/tree/master/ntp) +* [RT-Thread 网络小工具集](https://github.com/RT-Thread-packages/netutils) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ntp/ntp.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ntp/ntp.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ea921407642f3ded7cdbadf658f2df2f627c20b6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ntp/ntp.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet1.png new file mode 100644 index 0000000000000000000000000000000000000000..c43f3c7f9382801ab536cb8203dcbfd9cf3b2e9e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet10.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet10.png new file mode 100644 index 0000000000000000000000000000000000000000..3f848d3fc121e2a4fe218de514b1840fc8c64fb8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet10.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet11.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet11.png new file mode 100644 index 0000000000000000000000000000000000000000..edba81b539221a97e53e035edf8a0678752e58ae Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet11.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet12.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet12.png new file mode 100644 index 0000000000000000000000000000000000000000..6d8bba3d874f5cf4de2d9b6b0b45fc5b5218b6f0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet12.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet13.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet13.png new file mode 100644 index 0000000000000000000000000000000000000000..f33383c16bf60febbb5c0b9f3f1e63c5aeaa0e58 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet13.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet14.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet14.png new file mode 100644 index 0000000000000000000000000000000000000000..fc67a1e9038f25b4722ec1d54c79d047767d05d3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet14.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet15.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet15.png new file mode 100644 index 0000000000000000000000000000000000000000..ca87ecbfadd5a4c8db063e7de6b8678b3ff7fa54 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet15.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet16.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet16.png new file mode 100644 index 0000000000000000000000000000000000000000..f0538789c7fc70df4d4617ab4931d0e1f54b1417 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet16.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet17.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet17.png new file mode 100644 index 0000000000000000000000000000000000000000..870b21fd6d8d73a7fa7254c1e10f55c29b36192b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet17.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet2.png new file mode 100644 index 0000000000000000000000000000000000000000..fa3a1ae04e11dc97b2dcf00360cfd13511294e20 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet3.png new file mode 100644 index 0000000000000000000000000000000000000000..ac99e79b0f30f72a22ab116cb1443929e8d30a30 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet4.png new file mode 100644 index 0000000000000000000000000000000000000000..9bcf95fefac7c7249ccb43012b8e8c6b524ba650 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet5.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet5.png new file mode 100644 index 0000000000000000000000000000000000000000..1af3a496e4f67eec46ca80090c6517eb09a780fc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet6.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet6.png new file mode 100644 index 0000000000000000000000000000000000000000..6ecb0ff3afc10ac58ffc9f782d35b583a10c5993 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet6.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet7.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet7.png new file mode 100644 index 0000000000000000000000000000000000000000..88b36a8710e6e9f48155490d87bceebc0631bfa1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet7.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet8.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet8.png new file mode 100644 index 0000000000000000000000000000000000000000..8815fc91b39650b5c8182f04b725019e5ce6e337 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet8.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet9.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet9.png new file mode 100644 index 0000000000000000000000000000000000000000..4643e2aa5e344dc415d9b92a605a6d629f89185d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/figures/onenet9.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/onenet.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/onenet.md new file mode 100644 index 0000000000000000000000000000000000000000..aac8364409a62c4a3c3c9a64a6a6aa7e988b5a0c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/onenet.md @@ -0,0 +1,155 @@ +# 轻松连接中移OneNet云 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +[OneNET](https://open.iot.10086.cn/) 平台是中国移动基于物联网产业打造的生态平台,可以适配多种网络环境和协议类型,例如MQTT、HTTP、LWM2M等,方便用户数据的管理和设备控制。 + +我们的 [onenet](https://github.com/RT-Thread-packages/onenet) 组件包是 RT-Thread 系统针对 OneNET 平台连接的适配,通过这个组件包可以让设备在 RT-Thread 上使用 MQTT 协议连接 OneNet 平台,完成数据的接受和发送、以及设备的控制等功能,更多 OneNET 平台信息可查看 [OneNET 文档中心](https://open.iot.10086.cn/doc)。 + +## 准备工作 + +设备接入 OneNET 云之前,需要在平台注册用户账号,OneNET 云平台地址: + +### 创建产品 + +账号注册登录成功后,点击**开发者中心**进入开发者中心界面; + +点击 **创建产品**,输入产品基本参数,页面最下方设备接入协议选择 **MQTT** 协议,如下图所示: + +![开发者中心](figures/onenet1.png) + +![创建产品](figures/onenet2.png) + +产品创建成功之后,可以在开发者中心左侧**产品概况**中查看产品基础信息(如产品ID,接入协议,创建时间,产品 APIkey 等,后面有用)。 + +![产品中心](figures/onenet12.png) + +### 接入设备 + +在开发者中心左侧 **设备管理** 中点击 **添加设备** 按钮添加设备 + +![添加设备](figures/onenet4.png) + +![填写设备信息](figures/onenet3.png) + +鉴权信息是为了区分每一个不同的设备,(这里仅为了测试就填写了当前时间作为鉴权信息)填完之后点击接入设备 + +### 添加 APIkey + +接入设备之后,可以看到设备管理的界面多了一个设备,设备的右边有一些操作设备的按钮,点击查看详情按钮 + +![产看设备详情](figures/onenet5.png) + +![设备详情](figures/onenet6.png) + +此设备的相关信息就都显示出来了,比如:设备 ID、鉴权信息、设备 APIkey,这些信息需要记下,在ENV配置时会用到。 + +点击按钮`添加APIkey`,APIKey 的名称一般和设备相关联,我们这里填入`test_APIKey`,关联设备写入我们刚刚创建的设备`test1`。 + +![添加 APIKey](figures/onenet7.png) + +### 开启 onenet 软件包 + +打开 Env 工具输入 menuconfig 按照下面的路径开启 onenet 软件包 + +``` +RT-Thread online packages + IoT - internet of things ---> + [*] OneNET: China Mobile OneNet cloud SDK for RT-Thread +``` + +进入 onenet 软件包的配置菜单按下图所示配置,里面的信息依据自己的产品和设备的**实际情况**填写 + +![配置 OneNet 软件包](figures/onenet8.png) + +- **Enable OneNET sample**:开启 OneNET 示例代码 + +- **Enable support MQTT protocol**:开启 MQTT 协议连接 OneNET 支持 + +- **Enable OneNET automatic register device**:开启 OneNET 自动注册设备功能 + +- **device id**:配置云端创建设备时获取的 `设备ID` + +- **auth info**:配置云端创建产品时 `用户自定义的鉴权信息` (每个产品的每个设备唯一) + +- **api key**:配置云端创建设备时获取的 `APIkey` + +- **product id**:配置云端创建产品时获取的 `产品ID` + +- **master/product apikey**:配置云端创建产品时获取的 `产品APIKey` + + +### 示例文件介绍 + +利用 ENV 生成工程后,我们可以在工程的 onenet 目录下看到`onenet_sample.c`文件,该文件是 OneNET 软件包的示例展示,主要是向用户展示如何使用 OneNET 软件包**上传数据**和**接收命令**。 + +## 在 msh shell 中运行示例代码 + +系统运行起来后,首先在 msh 命令行下输入 `onenet_mqtt_init` 命令初始化 mqtt 客户端,然后输入`onenet_upload_cycle` 命令运行 OneNET 示例代码 。 + +``` +msh> onenet_mqtt_init +[D/ONENET] (mqtt_connect_callback:85) Enter mqtt_connect_callback! +[D/MQTT] ipv4 address port: 6002 +[D/MQTT] HOST = '183.230.40.39' +[I/ONENET] RT-Thread OneNET package(V0.2.0) initialize success. +[I/MQTT] MQTT server connect success +[D/ONENET] (mqtt_online_callback:90) Enter mqtt_online_callback! +msh> onenet_upload_cycle +msh />[D/ONENET] (onenet_upload_data:106) buffer : {"temperature":0} +[D/ONENET] (onenet_upload_data:106) buffer : {"temperature":56} +[D/ONENET] (onenet_upload_data:106) buffer : {"temperature":56} +``` + +## 预期结果 + +点击网页上 **设备管理** 里面设备的 **数据流管理** 操作 + +![打开数据流管理](figures/onenet9.png) + + + +刷新网页,能看到云端能接收到开发板发来的数据。 + +![查看开发板发来的数据](figures/onenet11.png) + +## 创建应用 + +点击左侧 **应用管理** 然后点击 **创建应用** + +![创建应用](figures/onenet13.png) + + 然后输入应用名称并点击 **创建** + +![点击创建](figures/onenet14.png) + +然后在点击 **编辑** 按钮编辑应用 + +![编辑应用](figures/onenet15.png) + +在弹出的窗口中编辑应用,可以拖动左侧元件库中的元件到中间的显示区域 + +![编辑应用的界面](figures/onenet16.png) + +点击要设置的元件可以在右侧的 **属性和样式** 窗口改变元件的属性 + +![设置元件的属性和样式](figures/onenet17.png) + +> [!NOTE] +> 注:* 如用 QEMU 模拟器平台需要连接外网,如出现连接失败的情况,可以尝试关闭防火墙 + * 文件系统最大打开文件个数不能太小,建议不小于16 + +## 参考资料 + +* 源码 [OneNET 软件包](https://github.com/RT-Thread-packages/onenet) +* [OneNET 软件包用户手册](https://github.com/RT-Thread-packages/onenet/tree/master/docs) +* [OneNET 官方文档](https://open.iot.10086.cn/doc/art398.html#97) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/onenet.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/onenet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a1cafef5fc3ca877c91d3223c16096898b83e782 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/onenet/onenet.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP1.png new file mode 100644 index 0000000000000000000000000000000000000000..64e44240434b707f66931121e6a0596de1e6f3c4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP3.png new file mode 100644 index 0000000000000000000000000000000000000000..40a51197ffe519f8b0e2a319370606d4115e8cf7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP4.png new file mode 100644 index 0000000000000000000000000000000000000000..af25eba893af9a53ebea47e254bc6893063b3bb6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP_ping.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP_ping.png new file mode 100644 index 0000000000000000000000000000000000000000..8fd5cd5a4beacee95f523d268c74339b9afe34dd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/figures/ICMP_ping.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/ping_principle.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/ping_principle.md new file mode 100644 index 0000000000000000000000000000000000000000..c4f33cc56369cd33acca5d90e2d7f3723a648323 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/ping_principle.md @@ -0,0 +1,79 @@ +# 一次完整的网络Ping过程 + + + +> 提示:视频 PPT 下载 + +**Ping **程序是用来探测主机到主机之间是否可通信,如果不能 Ping 到某台主机,表明不能和这台主机建立连接。 + +当我们要检查网络状况的时候,就总喜欢 Ping 一下百度,检测网络到底通不通。但是这一个看似简单的命令 Ping,到底涉及了什么协议,数据又经历了什么样的路程,我们今天就来看一看。一次完整的 Ping 过程其实涉及很多协议,如 DNS,UDP,ARP,ICMP 以及路由协议等。 + +Ping 使用的是 ICMP 协议,它发送 ICMP 回送请求消息给目的主机。ICMP 协议规定:目的主机必须返回 ICMP 回送应答消息给源主机。如果源主机在一定时间内收到应答,则认为主机可达。 + +## DNS 协议 + +DNS(Domain Name System,域名系统),万维网上作为域名(网址)和 IP 地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记那一串无意义的数字组成的 IP 地址。 + +通过域名得到该域名对应的 IP 地址的过程叫做域名解析(或主机名解析)。DNS 协议运行在 UDP 协议之上,使用端口号 53。 + +如果我们要 Ping www.baidu.com 首先就要先进行 DNS 域名解析获得 IP 地址。 + +## ARP 协议 + +ARP(Address Resolution Protocol)地址解析协议,是根据 IP 地址获取物理 MAC 地址的一个 TCP/IP 协议。 + +现在局域网中主机的 IP 一般都是动态分配的,这样做的好处是提高了 IP 的利用率;缺点是,当数据到来时只根据 IP 地址就不能确定到底哪一台主机了。因此需要弄一个缓存表,用来记录 IP 和 主机 MAC 地址的对应关系,这个缓存表就是 ARP 高速缓冲表 。 + +ARP协议的基本功能就是通过目标设备的 IP 地址,查询目标设备的 MAC 地址,同时,维护 ARP 高速缓冲表,以保证通信的顺利进行。 + +ARP 的分组格式如下图所示: + +![ARP 的分组格式](../arp_principle/figures/ARP2.png) + +> 提示:ARP 协议参考 PPT 下载 + +## ICMP 协议 + +ICMP 是 “Internet Control Message Protocol”(网络控制报文协议)的缩写。 + +它是 [TCP/IP协议](https://baike.baidu.com/item/TCP%2FIP%E5%8D%8F%E8%AE%AE) 族的一个子协议,用于在 IP 主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。 + +ICMP 层区分不是很明显,一般划分在 IP 层中 通过 IP 包来封装ICMP数据 ,在实际传输中数据包的格式一般都是 IP 包 + ICMP包的格式,具体格式如下: + +**IP 首部(20字节) + 8 位类型 + 8 位代码 + 16 位校验和 + ICMP 首部其他部分( 7 个字节) + 数据。** + +如果用图表的形式展现出来就是下面的这张图了 + +![ICMP 格式](figures/ICMP1.png) + +## Ping 的过程 + +假设我们发起一个从开发板到百度 www.baidu.com 的 Ping 请求。(这里由路由1作为局域网的默认网关) + +![Ping 的过程](figures/ICMP_ping.png) + +1. 首先开发板要解析百度的域名,获取到百度主机的 IP 地址,涉及到 DNS 协议,传输层用的是 UDP 协议 +2. DNS 主机利用 UDP 协议,回复百度的 IP 地址给开发板(这里也涉及了 ARP 协议暂时不讲) 。 +3. 现在开发板要发送 Ping 请求包给百度主机,但是发现百度主机 IP 地址与自己不在同一网段,因此要发送 Ping 请求包给默认网关。 +4. 要发送给默认网关的时候,忽然发现并没有默认网关对应的 MAC 地址,因此发送一个 ARP 广播包,如果交换机存储了默认网关的 MAC 地址,就直接告诉开发板默认网关的 MAC 地址,否则就会向所有端口发送ARP广播,直到路由1收到了报文后,立即响应,单播自己的 MAC 地址给开发板。 +5. 这样开发板就可以把 Ping 包发送给默认网关(路由1)了。 +6. 然后路由1 经过路由协议,经过一个个路由的转发,最后发送到了百度的主机上。百度主机检测到 IP 是自己的 IP,接收并处理 Ping 请求,接着百度主机发送一个 Ping 回应报文给开发板。 + +我们可以在用 wireshark 抓包的时候,用电脑 Ping 一下开发板 ,通过抓到的包和上面的图对照着看就可以弄清 Ping 的过程了。 + +## 抓包分析 + +1. 打开 wireshark 软件 开启抓包,设定好过滤条件,只显示和开发板 IP 相关的包。 +2. 当 QEMU 连接上网络后,输入 `ping www.baidu.com` + +查看 wireshark ,发现已经抓到了 Ping 的数据包了。 + +![Ping 的数据包](figures/ICMP3.png) + +因为默认 Ping 四次所以带着两个 DNS 的数据包,最少可以抓到 10 个包,我们只看前四个就够了。第一个是域名解析的 DNS 请求包,第二个是 DNS 服务器回复的响应数据包,第三条是开发板发送给百度主机的请求包( request )。第四条数据包是百度主机发送给开发板的一个回应( reply )的包。 + +我们也可以点开封包详细信息然后和上面的 ICMP 协议的报文格式做对比,对 Ping 的机制的理解会更加充分。 + +![封包详细信息](figures/ICMP4.png) + +这样就知道了 Ping 的时候开发板和电脑到底都干了什么。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/ping_principle.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/ping_principle.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9a686d0acd312cf795b86225a112fffb25d1f97c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/ping_principle/ping_principle.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/echo-cat.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/echo-cat.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d3e56bb3d067f69bdfad392bf4a4493aea7f12 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/echo-cat.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/enable_ping.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/enable_ping.png new file mode 100644 index 0000000000000000000000000000000000000000..68ad5cfd01d4a7f4429aeff096de4a1339765639 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/enable_ping.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/env.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/env.png new file mode 100644 index 0000000000000000000000000000000000000000..4794a0b3ac9c35a5996b4e2ca54098e960d41694 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/env.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/env_menuconfig.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/env_menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..bac6ffd51021a20723f32e5561f3b678229c36a9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/env_menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/finsh-cmd.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/finsh-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..a5145c075c51d5da18c60f7b410a4d3d886ee293 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/finsh-cmd.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/finsh-thread.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/finsh-thread.png new file mode 100644 index 0000000000000000000000000000000000000000..5cd53699862825d53836c8ab5c9cd64325d88557 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/finsh-thread.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/ifconfig.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/ifconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..55ee60d8e1ceb884f589b92ae0da829cd2fe7092 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/ifconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/menuconfig.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..213c964f831f712dfee338834cc2db4c4e765074 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/mkfs-sd0.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/mkfs-sd0.png new file mode 100644 index 0000000000000000000000000000000000000000..4bcea502b01bca5a8ebf0e1b9344097bc505fd27 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/mkfs-sd0.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/online_packages.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/online_packages.png new file mode 100644 index 0000000000000000000000000000000000000000..63391b83dab188595371b4123eac48e5e1f5a4e0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/online_packages.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/ping.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/ping.png new file mode 100644 index 0000000000000000000000000000000000000000..12ac99b24678f66e60dc57bfe827a819e2ce52c4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/ping.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/pkgs--update.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/pkgs--update.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc1116180244859166d974164a73c3c7d4bbe3c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/pkgs--update.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu.bat.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu.bat.png new file mode 100644 index 0000000000000000000000000000000000000000..8e6c4fc46921d8d08a31ee9f3bbe28bd0743b174 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu.bat.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu.png new file mode 100644 index 0000000000000000000000000000000000000000..bed1e4d3c75b28665c634b1e3993ede5107dbc72 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu_bat.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu_bat.png new file mode 100644 index 0000000000000000000000000000000000000000..f8306ddb134757cc7530fa67cd487b2dd8f3f1af Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu_bat.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu_modify.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu_modify.png new file mode 100644 index 0000000000000000000000000000000000000000..e014667302ccb6baf4718d7bd58f8a7d6ca9138f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemu_modify.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemubsp.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemubsp.png new file mode 100644 index 0000000000000000000000000000000000000000..2584eedc7b0eec38ab8cf522bd20a6fcdce6c4e9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/qemubsp.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons-again.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons-again.png new file mode 100644 index 0000000000000000000000000000000000000000..9d1ec2f49fdb792904c04f0d96cc08672962aa16 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons-again.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons-net.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons-net.png new file mode 100644 index 0000000000000000000000000000000000000000..6a23c03e815391be93e759ff342c3d2421e86dc4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons-net.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons.png new file mode 100644 index 0000000000000000000000000000000000000000..3365797484214e3aa54ffc5562e005e90649d112 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/scons.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/tap_rename.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/tap_rename.png new file mode 100644 index 0000000000000000000000000000000000000000..927149b6611f0a15c185dd722545df207b7b8a86 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/tap_rename.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/tap_share_internet.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/tap_share_internet.png new file mode 100644 index 0000000000000000000000000000000000000000..9f1c8f42605e5704ecba519e4d21a263da56700b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/figures/tap_share_internet.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/qemu_setup.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/qemu_setup.md new file mode 100644 index 0000000000000000000000000000000000000000..3004385757ef0f88e999a6a9fa2830e960495d04 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/qemu_setup.md @@ -0,0 +1,157 @@ +# 使用 QEMU 运行 RT-Thread + + + +> 提示:视频 PPT 下载 + +嵌入式软件开发离不开开发板,在没有物理开发板的情况下,可以使用 QEMU 等类似的虚拟机来模拟开发板。QEMU 是一个支持跨平台虚拟化的虚拟机,它可以虚拟很多开发板。为了方便大家在没有开发板的情况下体验 RT-Thread,RT-Thread 提供了 QEMU 模拟的 ARM vexpress A9 开发板的板级支持包 (BSP)。 +本文主要介绍在 Window 平台上使用 QEMU 运行 RT-Thread qemu-vexpress-a9 BSP 工程,并介绍了如何使用虚拟网卡连接 QEMU 到网络。 + +## 准备工作 + +- Git 工具:[下载地址](https://www.git-scm.com/download/) +- tap 网卡:[下载地址](https://pan.baidu.com/s/1h2BmdL9myK6S0g8TlfSW0g) + +git 和 tap 网卡一路默认安装即可。 + +RT-Thread 提供的 QEMU 模拟的 ARM vexpress A9 开发板的板级支持包 (BSP) 位于 RT-Thread 源码 BSP 目录下的 qemu-vexpress-a9 文件夹,此 BSP 实现了 LCD、键盘、鼠标、SD 卡、以太网卡、串口等相关驱动,文件夹内容如下图所示。 + +![qemu-vexpress-a9 文件夹](figures/qemubsp.png) + +qemu-vexpress-a9 BSP 主要文件及目录描述如下所示: + +| 文件 / 目录 | 描述 | +| ------------- | --------------- | +| .vscode | vscode 配置文件 | +| applications | 用户应用代码目录 | +| cpu | 芯片相关 | +| drivers | RT-Thread 提供的底层驱动 | +| qemu.bat | Windows 平台运行脚本文件 | +| qemu.sh | Linux 平台运行脚本文件 | +| qemu-dbg.bat | Windows 平台调试脚本文件 | +| qemu-dbg.sh | Linux 平台调试脚本文件 | +| README.md | BSP 说明文件 | +| rtconfig.h | BSP 配置头文件 | + +## 编译和运行 RT-Thread + +### 步骤一 使用 scons 命令编译工程 + +打开 Env 文件夹,双击 env.exe 文件打开 Env 控制台: + +![Env 文件夹](figures/env.png) + +在 Env 控制台下切换目录,输入命令 `cd D:\repository\rt-thread\bsp\qemu-vexpress-a9` 切换到 RT-Thread 源码文件夹下的 qemu-vexpress-a9 BSP 根目录,然后输入 `scons` 命令编译工程,如果编译正确无误,会在 BSP 目录下生成 QEMU 下运行的 rtthread.elf 目标文件。 + +![编译工程](figures/scons.png) + +### 步骤二 使用 qemu.bat 命令运行工程 + +编译完成后输入 `qemu.bat` 启动虚拟机及 BSP 工程,qemu.bat 是 Window 批处理文件,此文件位于 BSP 文件夹下,主要包括 QEMU 的执行指令,第一次运行工程会在 BSP 文件夹下创建一份空白的 sd.bin 文件,这是虚拟的 sd 卡,大小为 64M。Env 命令行界面显示 RT-Thread 系统启动过程中打印的初始化信息及版本号信息等,qemu 虚拟机也运行起来了。如下面图片所示: + +![运行工程](figures/qemu.bat.png) + +![虚拟机](figures/qemu.png) + +> [!NOTE] +> 注:若电脑安装有 360 安全卫士会有警告,请点击允许程序运行。 + +## 运行 RT-Thread Finsh 控制台 + +RT-Thread 支持 Finsh,用户可以在命令行模式使用命令操作。输入 `help` 或按 tab 键可以查看所有支持的命令。如下图所示,左边为命令,右边为命令描述。 + +![查看 Finsh 命令 ](figures/finsh-cmd.png) + +如下图所示,比如输入`list_thread`命令可以查看当前运行的线程,以及线程状态和堆栈大小等信息。输入`list_timer`可以查看定时器的状态。 + +![查看系统线程情况 ](figures/finsh-thread.png) + +## 运行 RT-Thread 文件系统 + +输入 `list_device` 可以查看注册到系统的所有设备。如下面图片所示可以看到虚拟的 sd 卡 “sd0” 设备,接下来我们可以使用 `mkfs sd0` 命令格式化 sd 卡,执行该命令会将 sd 卡格式化成 FatFS 文件系统。FatFs 是专为小型嵌入式设备开发的一个兼容微软 fat 的文件系统,采用 ANSI C 编写,采用抽象的硬件 I/O 层以及提供持续的维护,因此具有良好的硬件无关性以及可移植性。 + +了解 FatFS 详细信息请点击链接: + +![格式化 sd 卡](figures/mkfs-sd0.png) + +第一次格式化 sd 卡后文件系统不会马上装载上,第二次启动才会被正确装载。我们退出虚拟机,然后在 Env 命令行界面输入 `qemu.bat` 重新启动虚拟机及工程,输入 `ls` 命令可以看到新增了 Directory 目录,文件系统已经装载上,然后可以使用 RT-Thread 提供的其他命令体验文件系统 + +![文件系统其他命令](figures/echo-cat.png) + +## 运行 RT-Thread 网络 + +### 步骤一 配置 TAP 网卡 + +打开网络和共享中心更改适配器设置,将安装的虚拟网卡重命名为 tap,如下图所示: + + ![重命名 Tap 网卡](figures/tap_rename.png) + +右键当前能上网的网络连接(本文使用以太网),打开属性 -> 共享,选择家庭网络连接为 tap,点击确定完成设置,如下图所示:(如果只有一个网卡的话,就不用下拉选择网卡了,只要勾选允许共享即可) + + ![设置网络共享](figures/tap_share_internet.png) + +> [!NOTE] +> 注:tap 网卡和 VMware 的虚拟网卡可能会冲突,如果出现无法开启网络共享,或者 ping 不通网络的情况,请禁用 VMware 虚拟网卡之后再尝试一次。 + +### 步骤二 修改 qemu.bat 脚本文件 + +1. 打开 qemu-vexpress-a9 BSP 目录下的 qemu.bat 文件。 + +2. 在下图所示位置添加 `-net nic -net tap,ifname=tap` 配置。其中 ifname=tap 的意思是网卡的名称是 tap。 + +![修改 qemu 启动参数](figures/qemu_modify.png) + +### 步骤三 查看 IP 地址 + +输入 `qemu.bat` 命令运行工程,在 shell 中输入 `ifconfig`命令查看网络状态,正常获取到 IP 即表示网络驱动正常,配置工作完成,效果如下图所示: + +![查看 IP 地址](figures/ifconfig.png) + +> [!NOTE] +> 注:* 当出现获取不到 IP 地址的情况时,在 qemu 运行的情况下,先将以太网的共享关闭,然后再次打开即可。 + * 如果获取到的 IP 是 10.0.x,x,是因为没有为 QEMU 添加启动参数 `-net nic -net tap,ifname=tap` 。 + * 虚拟机刚开始运行的时候并不会立刻获取到 IP 地址,有时需要等待几秒钟才会获取到 IP。 + * 关闭虚拟机可以按 Ctrl + 'C' 来结束程序运行。 + +## 运行 RT-Thread Ping 工具 + +### 步骤一 下载网络工具软件包 + +1、在路径`bsp\qemu-vexpress-a9`下打开 Env 工具,执行 menuconfig,如下图所示: + +![执行 menuconfig](figures/env_menuconfig.png) + +2、在 RT-Thread online packages->IoT - internet of things 页面打开 netutils: Networking utilities for RT-Thread 功能,如下图所示: + + ![开启网络工具包](figures/online_packages.png) + +3、进入 netutils: Networking utilities for RT-Thread 页面,打开 Enable Ping utility 功能,如下图所示: + + ![开启 Ping 工具包](figures/enable_ping.png) + +4、保存并退出配置界面。如果没有开启 Env 自动更新软件包功能的话,需要输入 `pkgs --update` 更新软件包配置。更新完成后使用 scons 命令重新编译工程,如下图所示: + +![scons 编译工程](figures/scons-net.png) + +5、编译完成之后运行 qemu.bat 文件,如下图所示: + +![运行工程](figures/qemu_bat.png) + +### 步骤二 运行 ping 工具 + +在 shell 中输入 `ifconfig`命令查看网络状态,正常获取到 IP 即表示网络驱动正常: + +![运行结果](figures/ifconfig.png) + +在 shell 中输入 ping www.rt-thread.com 可以看到 ping 通的返回结果, 表示网络配置成功,能够 ping 通,如下图所示: + +![ping 的结果](figures/ping.png) + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) +* [文件系统应用笔记](https://www.rt-thread.org/document/site/application-note/components/dfs/an0012-dfs/) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/qemu_setup.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/qemu_setup.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bb7bbaf5c40f419c4e8b1b4f72c06f2cf23965e7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_setup/qemu_setup.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/dbg.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/dbg.png new file mode 100644 index 0000000000000000000000000000000000000000..0cbd4f19066bf72c7703bf0582ebb620446ea325 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/dbg.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/debug-set.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/debug-set.png new file mode 100644 index 0000000000000000000000000000000000000000..4e1fa42d83b9c89924655e703f9d0a917b5eddca Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/debug-set.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/installplugin.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/installplugin.png new file mode 100644 index 0000000000000000000000000000000000000000..61c38e048a89c47b500451c9dd7a2d2082663d8e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/installplugin.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/open.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/open.png new file mode 100644 index 0000000000000000000000000000000000000000..e9495739f649c590576ade0cb3f78502e5c74c90 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/open.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/plugin-status.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/plugin-status.png new file mode 100644 index 0000000000000000000000000000000000000000..d2050a4d86eac14ba58e58d14806e3b7d305cacd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/plugin-status.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/register.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/register.png new file mode 100644 index 0000000000000000000000000000000000000000..29dc8e82c5bb984df31281707a6b696ad66c211f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/register.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/sconsvsc.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/sconsvsc.png new file mode 100644 index 0000000000000000000000000000000000000000..4b6f85a19daf5ab483cb2e392abecf20a151225a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/sconsvsc.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/vscode-run.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/vscode-run.png new file mode 100644 index 0000000000000000000000000000000000000000..2b5d4a8c54c6990742821cb23780f776e963e10a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/vscode-run.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/vsscons.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/vsscons.png new file mode 100644 index 0000000000000000000000000000000000000000..a3c272bc0bf1378b38e60178f497a790898c01b7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/figures/vsscons.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/qemu_vscode.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/qemu_vscode.md new file mode 100644 index 0000000000000000000000000000000000000000..2dbe2e895cce5636c0f9d535abaa3ffd9e159767 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/qemu_vscode.md @@ -0,0 +1,130 @@ +# 使用VS Code调试RT-Thread # + + + +> 提示:视频 PPT 下载 + +## 本文的目的和背景 ## + +VS Code(全称 Visual Studio Code)是一个轻量且强大的代码编辑器,支持 Windows,OS X 和 Linux。内置 JavaScript、TypeScript 和 Node.js 支持,而且拥有丰富的插件生态系统,可通过安装插件来支持 C++、C#、Python、PHP 等其他语言。 + +本文主要介绍在 Windows 平台使用 VS Code 调试 qemu-vexpress-a9 BSP 工程。 + +## 准备工作 + +* 下载、安装 VS Code:[官网下载](https://code.visualstudio.com/Download) + +## 运行和调试 RT-Thread + + +### 步骤一 打开 VS Code 项目工程 + +在 Env 控制台进入 qemu-vexpress-a9 BSP 根目录,然后输入命令 `code .` (**注意:code 后面有一个点**)打开 VS Code,表示使用 VS Code 打开当前目录。 + +> [!NOTE] +> 注:code 是命令,点 '.' 是参数表示当前目录,中间由空格隔开 + +![打开 Env 控制台](figures/sconsvsc.png) + +VS Code 打开后会自动打开 qemu-vexpress-a9 BSP 文件夹,如下图所示。 + +![打开 VS Code](figures/open.png) + +### 步骤二 安装调试插件 + +在 VS Code Extensions 里下载并安装支持 C/C++ 的调试插件: + +![安装 c/c++ 插件](figures/installplugin.png) + +安装好后确认插件为以下状态,如果不是则点击重新加载: + +![c/c++ 插件状态](figures/plugin-status.png) + +### 步骤三 编译 RT-Thread + +点击 VS Code “查看 -> 终端” 打开 VS Code 内部终端,在终端里输入命令 `scons` 即可编译工程,终端会打印出编译信息。 + +![编译工程](figures/vsscons.png) + +编译完成后输入 `.\qemu.bat` 命令就可以运行工程。终端会输出 RT-Thread 启动 logo 信息,QEMU 也运行了起来。 + +![运行工程](figures/vscode-run.png) + +> [!NOTE] +> 注:1. 调试 BSP 工程前需要先编译工程生成 rtthread.elf 文件。 + 2. 可以使用 `scons --target=vsc -s` 命令更新 VS Code 需要用到的 C/C++ 头文件搜索路径信息。不是每次都需要更新,只有在使用了 menuconfig 重新配置了 RT-Thread 或更改了 rtconfig.h 头文件时才需要。 + +### 步骤四 修改 qemu-dbg.bat 文件 + +开始调试前需要编辑 `qemu-vexpress-a9` 目录下的 `qemu-dbg.bat` 文件,在 qemu-system-arm 前加入 `start :` + +```bash +@echo off +if exist sd.bin goto run +qemu-img create -f raw sd.bin 64M + +:run +start qemu-system-arm -M vexpress-a9 -kernel rtthread.elf -serial stdio -sd sd.bin -S -s + +``` + +### 步骤五 调试工程 + +如下图所示,在 VS Code 里点击调试菜单(小虫子图标),调试平台选择 Windows,然后按 F5 就可以开启 QEMU 调试模式,断点停留在 main 函数。VS Code 调试选项如下图所示: + +![Env 调试界面](figures/debug-set.png) + +QEMU 也运行了起来,如下图所示。 + +![qemu 调试界面](figures/dbg.png) + +在 VS Code 里可以使用 GDB 命令,需要在最前面加上 `-exec`。 例如 `-exec info registers` 命令可以查看寄存器的内容: + +![gdb 查看内存](figures/register.png) + +其他一些主要命令介绍如下所示: + +查看内存地址内容:`x/ `,各个参数说明如下所示: + +* n 是一个正整数,表示需要显示的内存单元的个数,也就是说从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的 u 定义 +* f 表示显示的格式,参见下面。如果地址所指的是字符串,那么格式可以是 s。其他格式如下表所示: + +| 参数 | 描述 | +| ------------- | --------------- | +| x | 按十六进制格式显示变量 | +| d | 按十进制格式显示变量 | +| u | 按十六进制格式显示无符号整型 | +| o | 按八进制格式显示变量 | +| t | 按二进制格式显示变量 | +| a | 按十六进制格式显示变量 | +| c | 按字符格式显示变量 | +| f | 按浮点数格式显示变量 | + +* u 表示从当前地址往后请求的字节数,如果不指定的话,GDB 默认是 4 个 bytes。u 参数可以用下面的字符来代替,b 表示单字节,h 表示双字节,w 表示四字 节,g 表示八字节。当我们指定了字节长度后,GDB 会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。 +* addr 表示一个内存地址。 + +> [!NOTE] +> 注:严格区分 n 和 u 的关系,n 表示单元个数,u 表示每个单元的大小。 + +示例: `x/3uh 0x54320` 表示从内存地址 0x54320 读取内容,h 表示以双字节为一个单位,3 表示输出三个单位,u 表示按十六进制显示。 + +查看当前程序栈的内容: x/10x $sp--> 打印 stack 的前 10 个元素 +查看当前程序栈的信息: info frame----list general info about the frame +查看当前程序栈的参数: info args---lists arguments to the function +查看当前程序栈的局部变量: info locals---list variables stored in the frame +查看当前寄存器的值:info registers(不包括浮点寄存器) info all-registers(包括浮点寄存器) +查看当前栈帧中的异常处理器:info catch(exception handlers) + +> 提示:输入命令时可以只输入每个命令的第一个字母。例如:`info registers` 可以只输入 `i r`。 + +> [!NOTE] +> 注:* 如果在 VS Code 目录中额外添加了文件夹,会导致调试不能够启动。 + * 每次开始调试都需要使用 Env 工具在 BSP 根目录使用`code .`命令打开 VS Code 才能正常调试工程。 + +## 参考资料 + +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/qemu_vscode.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/qemu_vscode.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cfb6380933f4c5b575605dbe9a32aae1b03f5cac Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/qemu_vscode/qemu_vscode.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/tcp.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/tcp.png new file mode 100644 index 0000000000000000000000000000000000000000..5e72c8a606227c62fee944e12e5af6a533f2a47e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/tcp.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/tcp1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/tcp1.png new file mode 100644 index 0000000000000000000000000000000000000000..cb1e6d4317e0b131929e471f76159319d5c186cd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/tcp1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/udp1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/udp1.png new file mode 100644 index 0000000000000000000000000000000000000000..116dae327e2b1b746b5092e7d3e3d8765b20379d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/figures/udp1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/socket.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/socket.md new file mode 100644 index 0000000000000000000000000000000000000000..7b3a884d1f495bf54cbbd5603a04defd0e5b8616 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/socket.md @@ -0,0 +1,474 @@ +# 基础知识之 BSD Socket + +Socket 通常也称作"套接字",是支持 TCP/IP 协议的网络通信应用的基本操作单元,可以用来实现网间不同虚拟机或不同计算机之间的通信。使用TCP/IP协议的应用程序通过在客户端和服务器各自创建一个 Socket ,然后通过操作各自的 Socket 就可以完成客户端和服务器的连接以及数据传输的任务了。 + +Socket 的本质是编程接口( API ),是对 TCP/IP 的封装。使开发者不需要面对复杂的 TCP/IP 协议族,只需要调用几个较简单的 Socket API 就可以完成网络通信了。 + +RT-Thread 中的 **SAL 抽象层** 提供完整的 BSD Socket 相关 API。 + + +## BSD Socket 相关 API ## + +| 名称 | 作用 | +| ------------- | -------------------------- | +| socket | 创建一个 socket 套接字 | +| bind | 将端口号和 IP 地址绑定带指定套接字上 | +| listen | 开始监听 | +| accept | 接受连接请求 | +| connect | 建立连接 | +| send | 面向连接的发送数据(tcp) | +| recv | 面向连接的接收数据(tcp) | +| sendto | 无连接的发送数据(udp) | +| recvfrom | 无连接的接收数据(udp) | +| closesocket | 关闭 socket | +| shutdown | 按设置关闭套接字 | +| gethostbyname | 通过域名获取主机的 IP 地址等信息 | +| getsockname | 获取本地主机的信息 | +| getpeername | 获取连接的远程主机的信息 | +| ioctlsocket | 设置套接字控制模式 | + + +## TCP/UDP ## + +要学用套接字编程,一定要了解 TCP/UDP 协议。TCP/UDP 协议工作在 TPC/IP 协议栈的传输层,如下图所示: + +![tcp/ip协议](figures/tcp.png) + +TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的协议,使用该协议时,可以保证客户端和服务端的连接是可靠和安全的。使用 TCP 协议进行通信之前,通信双方必须先建立连接,然后再进行数据传输,通信结束后终止连接。 + +优点:能保证可靠性、稳定性。 + +适用场景:TCP适合用于端到端的通信,适用于对可靠性要求较高的服务。 + +基于 TCP 的 socket 编程流程如下图所示: + +![基于 TCP 的 socket 编程流程](figures/tcp1.png) + +UDP(User Datagram Protocol 用户数据报协议)是一种非面向连接的协议,它不能保证网络连接的可靠性。 客户端发送数据之前并不会去服务器建立连接,而是直接将数据打包发送出去。当服务器接收数据时它也不向发送方提供确认信息,如果出现丢失包或重份包的情况,也不会向发送方发出差错报文。 + +优点:控制选项少,无须建立连接,从而使得数据传输过程中的延迟小、数据传输效率高。 + +适用场景:UDP适合对可靠性不高,或网络质量有保障,或对实时性要求较高的应用程序。 + +基于 UDP 的 socket 编程流程如下图所示: + +![基于 UDP 的 socket 编程流程](figures/udp1.png) + +## API 详解 + +### socket ### + +使用 socket 通信之前,通信双方都需要各自建立一个 socket。我们通过调用 socket 函数来创建一个 socket 套接字: + +```c +int socket(int domain, int type, int protocol) +``` + +**函数参数** + + +| 参数 | 描述 | +| -------- | ------------------------------------ | +| domain | 协议域 | +| type | 类型 | +| protocol | 传输协议 | +| **返回** | **——** | +| > = 0 | 成功,返回一个代表套接字描述符的整数 | +| < 0 | 失败 | + +domain 参数支持下列参数: + +```c +AF_INET Ipv4 +AF_INET6 Ipv6 +AF_UNIX UNIX 域 +AF_UNSPEC 未指定 +``` + +type 参数支持下列参数: + +```c +SOCK_DGRAM 长度固定的、无连接的不可靠的报文传递(UDP) +SOCK_RAM IP 协议的数据报接口 +SOCK_STREAM 有序、可靠、双向的面向连接字节流(TCP) +``` + +protocol 参数: + +通常是 0 ,表示按给定的 domain 和 type 选择默认传输协议。在 AF_INET 通信域中套接字类型 SOCK_STREAM 的默认传输协议是 TCP。在 AF_INET 通信域中套接字类型 SOCK_DGRAM 的默认传输协议是 UDP。 + +当对同一 domian 和 type 支持多个协议时,可以使用 protocol 参数选择一个特定协议。 + +**函数返回** + +返回一个 socket 描述符,它唯一标识一个 socket。这个 socket 描述符 跟文件描述符 一样,后续的操作都有用到它,比如,把它作为参数,通过它来进行一些读写操作等。 + +### bind ### + +bind 函数用来将套接字与计算机上的一个端口号相绑定,进而在该端口监听服务请求,该函数的一般形式如下: + +```c +int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen) +``` + +**函数参数** + +| 参数 | 描述 | +| ------- | ------------------------------------------------------------ | +| sockfd | 要绑定的 socket描述符 | +| my_addr | 一个指向含有本机 IP 地址和端口号等信息的 sockaddr 结构的指针 | +| addrlen | sockaddr 结构的长度 | +| **返回** | **——** | +| 0 | 成功 | +| < 0 | 失败 | + +sockaddr 结构体定义如下: + +```c +struct sockaddr { + u8_t sa_len; + u8_t sa_family; + char sa_data[14]; +}; +``` + +在 IPv4 因特网域(AF_INET)中,我们使用 sockaddr_in 结构体来代替 sockaddr 结构体: + +```c +struct sockaddr_in { + u8_t sin_len; + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; + char sin_zero[SIN_ZERO_LEN]; +}; +``` + +其中, + +- sin_family 一般固定写 AF_INET; + +- sin_port 为套接字的端口号; + +- sin_addr 为套接字的 IP 地址, + +- sin_zero 通常全为 0,主要功能是为了与 sockaddr 结构在长度上保持一致。这样指向 sockaddr_in 的指针和指向 sockaddr 的指针可以互相转换。 + + +一般情况下,可以将 sin_port 设为 0,这样系统会随机选择一个未被占用的端口号。同样,sin_addr 设为 INADDR_ANY,系统会自动填入本机的 IP 地址。 + +> [!NOTE] +> 注:当调用 bind 函数时,不要将端口号设为小于 1024 的值,因为 1-1024 为系统的保留端口号,我们可以选择大于 1024 的任何一个未被占用的端口号。 + +### listen ### + +listen 函数用来将套接字设为监听模式,并在套接字指定的端口上开始监听,以便对到达的服务请求进行处理。listen 函数的一般形式如下: + +```c +int listen(int sockfd, int backlog) +``` + +**函数参数** + +| 参数 | 描述 | +| ------- | ------------------------------ | +| sockfd | 绑定后的 socket描述符 | +| backlog | 连接请求队列可以容纳的最大数目 | +| **返回** | **——** | +| 0 | 成功 | +| < 0 | 失败 | + +### accept ### + +accept 函数用来从完全建立的连接的队列中接受一个连接,它的一般形式如下: + +```c +int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) +``` + +**函数参数** + +| 参数 | 描述 | +| ------- | ------------------------------------------------------------ | +| sockfd | 被监听的 socket 描述符 | +| addr | 一个指向 scokaddr_in 结构的指针,存放提出连接请求的主机 IP 地址和端口号等信息 | +| addrlen | 一个指向 socklen_t 的指针,用来存放 sockaddr_in 结构的长度 | +| **返回** | **——** | +| > = 0 | 成功,返回新创建的套接字描述符| +| < 0 | 失败 | + +服务端接受连接后,accept 函数会返回一个新的 socket 描述符,线程可以使用这个新的描述符同客户端传输数据。 + + +### connect ### + +connect 函数用来与服务器建立一个 TCP 连接,它的一般形式如下: + +```c +int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen) +``` + +**函数参数** + +| 参数 | 描述 | +| ------- | ------------------------------------------------------------ | +| sockfd | socket 描述符 | +| addr | 指向 sockaddr 结构的指针,存放要连接的服务器的 IP 地址和端口号等信息 | +| addrlen | sockaddr 结构体的长度 | +| **返回** | **——** | +| > = 0 | 成功,返回新创建的套接字描述符| +| < 0 | 失败 | + + +### send ### + +send 函数用来在面向连接的数据流 socket 模式下发送数据,send 函数的一般形式如下: + +```c +int send(int sockfd, const void *msg, size_t len, int flags) +``` + +**函数参数** + +| 参数 | 描述 | +| ------ | -------------------------- | +| sockfd | socket 描述符 | +| msg | 指向所要发送的数据区的指针 | +| len | 要发送的字节数 | +| flags | 控制选项,通常为 0 | +| **返回** | **——** | +| >0 |成功,返回发送的数据的长度 | +| <=0 |失败 | + + +如果返回值小于 len 的话,你需要再次发送剩下的数据。 + +### recv ### + +recv 函数用来在面向连接的数据流 socket 模式下接收数据,recv 函数的一般形式如下: + +```c +int recv(int sockfd, void *buf, size_t len, int flags) +``` + +**函数参数** + +| 参数 | 描述 | +| ------ | ------------------------------ | +| sockfd | socket 描述符 | +| msg | 指向存储数据的内存缓存区的指针 | +| len | 缓冲区的长度 | +| flags | 控制选项,通常为 0 | +| **返回** | **——** | +| > 0 | 成功,返回接收的数据的长度 | +| = 0 | 目标地址已传输完并关闭连接 | +| < 0 | 失败 | + +### sendto ### + +sendto 函数用来在无连接的数据报 socket 模式下发送数据,sendto 函数的一般形式如下: + +```c +int sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) +``` + +**函数参数** + +| 参数 | 描述 | +| ------ | ------------------------------------------------------ | +| sockfd | socket 描述符 | +| msg | 指向所要发送的数据区的指针 | +| len | 要发送的字节数 | +| flags | 控制选项,通常为 0 | +| to | 指向 sockaddr 结构体的指针,存放目的主机的 IP 和端口号 | +| tolen | sockaddr 结构体的长度 | +| **返回** | **——** | +| > 0 | 成功,返回发送的数据的长度 | +| < = 0 | 失败 | + +### recvfrom ### + +recvfrom函数用来在无连接的数据报 socket 模式下接收数据,recvfrom 函数的一般形式如下: + +```c +int recvfrom(int sockfd, void*buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) +``` + +**函数参数** + +| 参数 | 描述 | +| ------- | ---------------------------------------------------- | +| sockfd | socket 描述符 | +| msg | 指向存储数据的内存缓存区的指针 | +| len | 缓冲区的长度 | +| flags | 控制选项,通常为 0 | +| from | 指向 sockaddr 结构体的指针,存放源主机的 IP 和端口号 | +| fromlen | 指向 sockaddr 结构体的长度的指针 | +| **返回** | **——** | +| > 0 | 成功,返回接收的数据的长度 | +| = 0 | 目标地址已传输完并关闭连接 | +| < 0 | 失败 | + +### closesocket ### + +closesocket 在传输完数据之后关闭 socket 并释放资源的函数,closesocket 函数的一般形式如下: + +```c +int closesocket(int sockfd) +``` + +**函数参数** + +| 参数 | 描述 | +| ------ | ------------- | +| sockfd | socket 描述符 | +| **返回** | **——** | +| 0 | 成功 | +| < 0 | 失败 | + +### shutdown ### + +shutdown 允许进行单向的关闭操作,或是全部禁止掉,shutdown 函数的一般形式如下: + +```c +int shutdown(int sockfd, int how) +``` + +**函数参数** + +| 参数 | 描述 | +| ------ | ------------- | +| sockfd | socket 描述符 | +| how | 控制选项 | +| **返回** | **——** | +| 0 | 成功 | +| < 0 | 失败 | + +how 参数支持下列参数: + +```c +SHUT_RD 关闭接收信道 +SHUT_WR 关闭发送信道 +SHUT_RDWR 将发送和接收信道全部关闭 +``` + +**函数返回** + +返回 0 表示成功 + +### gethostbyname ### + +此函数可以通过域名来获取主机的 IP 地址等信息,它的一般形式如下: + +```c +struct hostent* gethostbyname(const char*name) +``` + +**函数参数** + +| 参数 | 描述 | +| ---- | -------- | +| name | 主机域名 | +| **返回** | **——** | +| > 0 | 成功,返回一个 hostent 结构体指针 | +| < 0 | 失败 | + +name 可以是具体域名,如:“www.rt-thread.org”,也可以是 IP 地址,如:“192.168.2.56” + +hostent 结构体定义如下: + +```c +struct hostent { + char *h_name; /* 主机正式域名 */ + char **h_aliases; /* 主机的别名数组 */ + int h_addrtype; /* 协议类型,对于 TCP/IP 为 AF_INET */ + int h_length; /* 协议的字节长度,对于 IPv4 为 4 个字节 */ + char **h_addr_list; /* 地址的列表*/ +#define h_addr h_addr_list[0] /* 保持向后兼容 */ +}; +``` + +### getsockname ### + +此函数可以获取本地主机的信息,它的一般形式如下: + +```c +int getsockname(int sockfd, struct sockaddr *name, socklen_t *namelen) +``` + +**函数参数** + +| 参数 | 描述 | +| ------- | ------------------------------------------- | +| sockfd | socket 描述符 | +| name | sockaddr 结构体指针,用来存储得到的主机信息 | +| namelen | 指向 sockaddr 结构体的长度的指针 | +| **返回** | **——** | +| 0 | 成功 | +| < 0 | 失败 | + +### getpeername ### + +此函数可以得到与本地主机连接的远程主机的信息,它的一般形式如下: + +```c +int getpeername(int socket, struct sockaddr *name, socklen_t *namelen) +``` + +**函数参数** + +| 参数 | 描述 | +| ------- | ------------------------------------------- | +| sockfd | socket 描述符 | +| name | sockaddr 结构体指针,用来存储得到的主机信息 | +| namelen | 指向 sockaddr 结构体的长度的指针 | +| **返回** | **——** | +| 0 | 成功 | +| < 0 | 失败 | + +### ioctlsocket + +设置套接字控制模式,它的一般形式如下: + +```c +int ioctlsocket(int sockfd, long cmd, void *arg) +``` + +**函数参数** + +| 参数 | 描述 | +| ------ | ---------------- | +| sockfd | socket 描述符 | +| cmd | 套接字操作命令 | +| arg | 操作命令所带参数 | +| **返回** | **——** | +| 0 | 成功 | +| < 0 | 失败 | + +cmd 参数支持下列参数: + +```c +FIONBIO 开启或关闭套接字的非阻塞模式,arg 参数为 1 开启非阻塞,为 0 关闭非阻塞。 +``` + +> [!NOTE] +> 注:在网络中都采用大端字节序,但是不同的嵌入式系统,其字节序不一定都是大端格式,相反小端字节序倒是很常见,比如 STM32。我们在设置 IP 和端口号时,要根据自己的平台特点进行必要的字节序转换。 + +下面给出套接字字节转换函数的列表: + +```c +htons() —— "Host to Network Short" 主机字节顺序转换为网络字节顺序 +htonl() —— "Host to Network Long" 主机字节顺序转换为网络字节顺序 +ntohs() —— "Network to Host Short" 网络字节顺序转换为主机字节顺序 +ntohl() —— "Network to Host Long" 网络字节顺序转换为主机字节顺序 +``` + +对于一个“192.168.2.1”这种字符串形式的 IP 地址,我们如何将其正确的转换为网络字节序呢? + +可以使用 inet_addr(“192.168.2.1”),结果直接就是网络字节序了; + +我们也可以使用 inet_ntoa()(“ntoa”代表“Network to ASCII”)函数将一个长整形的 IP 地址转换为一个字符串。 + +## 参考资料 + +* [网络开发应用笔记](https://www.rt-thread.org/document/site/application-note/components/network/an0011-rtthread-system-network-started/) diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/socket.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/socket.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e7bafa4d08f877ed21fc4e5711feb3b9d7c83c16 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/socket/socket.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/figures/tcpip.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/figures/tcpip.png new file mode 100644 index 0000000000000000000000000000000000000000..59318b1ac5f5f8d4a78d4981fa884efe7933fbea Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/figures/tcpip.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/tcp_ip.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/tcp_ip.md new file mode 100644 index 0000000000000000000000000000000000000000..766fb5f907807a30f473cfe4f63f19f493ef1819 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/tcp_ip.md @@ -0,0 +1,65 @@ +# 基础知识之 TCP/IP 协议栈 + +在这个互联网蓬勃发展的时代,网络正在迅速的改变着我们周围的一切。小到智能电灯、智能手表,大到航天飞机、宇宙空间站,这所有的一切都被互联网连接到了一起。他们之间能够相互识别,相互通信,这所有的一切都依赖于 TCP/IP 协议。TCP/IP 不是一个协议,而是一个协议族的统称。里面包括了 IP 协议,IMCP 协议,TCP 协议,以及我们更加熟悉的 http、ftp、pop3 协议等等。有了 TCP/IP 协议,这些设备就有了统一的语言,他们之间就能够自由的交流了。 + +## TCP/IP 参考模型 + +提到协议分层,我们很容易联想到 OSI 的七层协议经典架构,但是 TCP/IP 协议族的结构则稍有不同。如下图所示 : + +![TCP/IP 参考模型](figures/tcpip.png) + +TCP/IP 协议族按照层次由上到下,层层包装。 + +最上面的一层是应用层,这里面有 http,ftp, 等等我们熟悉的协议。 + +第二层是传输层,著名的 TCP 和 UDP 协议就在这个层次。 + +第三层是网络层,IP 协议就在这里,它负责对数据加上 IP 地址和其他的数据以确定传输的目标。 + +第四层是网络接口层,对实际的网络媒体的管理,定义如何使用实际网络(如Ethernet、Serial Line等)来传送数据。 + +发送协议的主机从上自下将数据按照协议封装,而接收数据的主机则按照协议将得到的数据包解开,最后拿到需要的数据。这种结构非常有栈的味道,所以 tcp/ip 协议族也被称为 tcp/ip 协议栈。 + +## 基本概念 + +**IP 地址** + +网络上每一个节点都必须有一个独立的 IP 地址,通常使用的 IP 地址是一个 32bit 的数字,被分成 4 组,例如,255.255.255.255 就是一个 IP 地址。IP 地址就是计算机网络组成的最小单位。 + +在 Linux 系统中,可以用 ifconfig -a 命令查看自己的 IP 地址,windows的 DOS 中可以用 ipconfig 查看,在 RT-Thread 系统中可以输入 ifconfig 命令查看自己 IP 地址。 + + **域名** + +一连串无意义的数字组成的 IP 对人类来说是极不友好的,人们很难记住某一个服务器的 IP。因此由点和字符组成的域名系统应运而生。域名系统其实就是一个分布的数据库,它提供将主机名(也就是网址)转换成 IP 地址的服务。 例如,我们都能记住 www.baidu.com 却没有一个人知道 111.13.100.91。 + + **MAC 地址** + +MAC(Media Access Control)地址,或称为物理地址、硬件地址,用来定义互联网中设备的位置。在 TCP/IP 层次模型中,网络层管理 IP 地址,链路层则负责 MAC 地址。因此每个网络位置会有一个专属于它的 IP 地址,而每个主机会有一个专属于它 MAC 地址。 + +## 应用层 + +不同类型的网络应用有不同的通信规则,因此应用层的协议是多种多样的,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)、域名解析服务(DNS)、超文本传输协议(HTTP)等。动态主机配置协议 (DHCP)也工作在应用层。 + +## 传输层 + +在此层中,它提供了节点间的数据传送服务,如传输控制协议(TCP)、用户数据报协议(UDP)等,TCP 和 UDP 给数据包加入传输数据并把它传输到下一层中,这一层实现端到端的数据传输。 TCP 协议是面向连接的,这就意味着传输层能保持对分段的跟踪,并且重传那些失败的分段。 + +## 网络层 + +用来处理网络上流动的数据包(网络传输中最小的数据单元),规定了怎样的路径把数据包传输到目标计算机,并把数据包传送给对方。相关的有 IP 协议、ARP 协议、RARP 协议、ICMP 协议等 + +**IP 协议**:将多个包交换网络连接起来,它在源地址和目的地址之间传送一种称之为数据包的东西,它还提供对数据大小的重新组装功能,以适应不同网络对包大小的要求。 + +**ARP 协议**: 是根据 IP 地址获取 MAC 地址的一种协议。 用于将 32 位 IP 地址映射到网卡的 48 位MAC地址。 + +**RARP 协议**:与 ARP 协议的工作相反,主要用于将网卡的 48 位 MAC 地址转换为 32 位 IP 地址。 + +**ICMP 协议**:用于在 IP 主机、路由器之间传递控制消息——指网络通不通、主机是否可达、路由是否可用等网络本身的消息。 + +## 网络接口层 + +用来处理连接网络的硬件部分,包括控制操作系统、硬件的设备驱动和网络适配器,及光纤等物理可见的部分。硬件上的范畴均属于网络接口层的作用范围之内。 + +## 参考资料 + +* [网络开发应用笔记](../../../application-note/components/network/an0011-network-started.md) diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/tcp_ip.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/tcp_ip.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a5b3d27ce2a09f7dfc3df94173afba4af81ca39d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_ip/tcp_ip.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP.png new file mode 100644 index 0000000000000000000000000000000000000000..2da23452c69f9d03b42faa95814e72326824fdde Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP1.png new file mode 100644 index 0000000000000000000000000000000000000000..5991ee85d2bee6530535d37d8b5df1247a203223 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP2.png new file mode 100644 index 0000000000000000000000000000000000000000..e593f6245ec96e3ed7ac348261f3689594eb58e6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP_shake.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP_shake.png new file mode 100644 index 0000000000000000000000000000000000000000..370d80dda36dfaf95dc86f11de96972facf2d1e4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP_shake.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP_wave.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP_wave.png new file mode 100644 index 0000000000000000000000000000000000000000..7cba68fc6d2d82e57f1e90e167b408da2cf82730 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/TCP_wave.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/UDP1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/UDP1.png new file mode 100644 index 0000000000000000000000000000000000000000..d782bcafa1c290576c648efd4d3bfdc32fa3e99a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/UDP1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/UDP2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/UDP2.png new file mode 100644 index 0000000000000000000000000000000000000000..5030a4d505e4c241e800fae7c32f3c9bcc27866e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/figures/UDP2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/tcp_udp_principle.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/tcp_udp_principle.md new file mode 100644 index 0000000000000000000000000000000000000000..d2a7c3ab2f76dd5ae28e8deee785770ceec11e10 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/tcp_udp_principle.md @@ -0,0 +1,112 @@ +# 深入理解TCP/UDP通信原理 + + + +> 提示:视频 PPT 下载 + +## TCP 通信原理 + +TCP 把连接作为最基本的对象,每一条 TCP 连接都有两个端点,这种端点我们叫作套接字(socket),它的定义为端口号拼接到 IP 地址即构成了套接字,例如,若 IP 地址为 192.3.4.16 而端口号为 80,那么得到的套接字为`192.3.4.16:80`。IP 协议虽然能把数据报文送到目的主机,但是并没有交付给主机的具体应用进程。而端到端的通信才是应用进程之间的通信。 + +TCP 报文是 TCP 层传输的数据单元,也叫报文段。 TCP 报文的格式如下图所示 + +![TCP 报文格式](figures/TCP1.png) + +我们从 TCP 的报文格式中能看到 6 个**标志位**:URG ACK PSH RST SYN FIN,每一个标志位表示一个控制功能。 + +- URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。 +- ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。 +- PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。 +- RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。 +- SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。 +- FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。 + +TCP 的连接和断开的过程和这几个标志位有关。 + +### 建立连接 + +TCP 的三次握手,意思就是建立连接的时候客户端与服务器之间需要三次数据包的交流。 + +1. 客户端发送给服务器一个请求连接数据包,即发送了一个指向服务器目标端口的一个 SYN 位为 1 的TCP 报文。 +2. 服务器接收到客户端的连接请求之后,会回应一个 SYN 位为 1 的TCP 报文,表示同意连接。并且,会把 ACK 位也置 1 表示确认收到上次消息。 +3. 客户端接收到服务器的同意连接的数据包之后,还要回复一个 ACK 为 1 的 TCP 报文,表示确认收到。 + +如下图所示,这就是 TCP 的三次握手。 + +![TCP 的三次握手](figures/TCP_shake.png) + +### 数据传输 + +**TCP 以段为单位发送数据** + +在建立 TCP 连接的同时,也可以确定发送数据包的单位,我们也可以称其为“最大消息长度”(MSS:Maximum Segment Size)。最理想的情况是,最大消息长度正好是 IP 中不会被分片处理的最大数据长度。 + +TCP 在传输大量数据时,是以 MSS 的大小将数据进行分割发送的。进行重发时也是以 MSS 为单位。 + +MSS 是在三次握手的时候,在两端主机之间被计算得出。两端的主机在发出建立连接的请求时,会在 TCP 首部中写入 MSS 选项,告诉对方自己的接口能够适应的 MSS 的大小(为附加 MSS 选项,TCP首部将不再是20字节,而是 4 字节的整数倍)。然后会在两者之间选择一个较小的值投入使用(在建立连接时,如果某一方的 MSS 选项被省略,可以选为 IP 包的长度不超过 576 字节的值(IP 首部 20 字节,TCP 首部 20 字节,MSS 536 字节))。 + +例如客户端发送一段 3500 长度的数据到服务端,假设确定的 MSS 长度为1000,数据传输的过程如下所示: + +![TCP 数据传输的过程](figures/TCP2.png) + + + +### 断开连接 + +TCP 的四次挥手,意思就是释放连接的时候客户端与服务器之间需要四次数据包的交流。 + +1. 客户端发送给服务器一个请求释放连接的数据包,即发送了一个指向服务器目标端口的一个 FIN 位为 1 的TCP 报文,表示客户端没有数据要发送了,但是仍然可以接收数据;并且 ACK 位也为 1,表示对上次传输数据结果的确认。并且之后处去等待状态,等待服务器的两次回应。 +2. 服务器接收到客户端的释放连接请求之后,会先回应一个 ACK 位为 1 的报文,表示确认收到。但是,这时服务器可能还有数据没有发送完成,继续发送数据。 +3. 服务器发送完数据之后,发送一个 FIN 为 1 的 TCP 报文,表示我也没有要发送的数据了,你可以释放连接了。当然 ACK 位仍然为 1 。 +4. 客户端接收到服务器的同意释放连接的数据包之后,回复一个 ACK 为 1 的 TCP 报文,表示确认收到。 + +如下图所示,这就是 TCP 的四次挥手。 + +![TCP 的四次挥手](figures/TCP_wave.png) + +### 抓包分析 + +利用 wireshark 工具抓包分析可以很清晰的看到这两个流程。 + +1. 打开 wireshark 软件 开启抓包,过滤规则设置为 tcp 。 + +2. 开发板连接上网络后,运行 samples 里面的 tcpclient 例程, + +这样就可以很方便的看到 TCP 的三次握手与四次挥手的全过程,如下图所示: + +![TCP 协议抓包结果](figures/TCP.png) + +数据传输的过程需要在以后用到 http client 传输较大数据的时候才能更好的看出来。 + +## UDP 通信原理 + +UDP 是 User Datagram Protocol 的简称, 中文名用户数据报协议,是[OSI](https://baike.baidu.com/item/OSI)(Open System Interconnection,开放式系统互联)参考模型中一种无连接的[传输层](https://baike.baidu.com/item/%E4%BC%A0%E8%BE%93%E5%B1%82)协议,提供面向事务的简单不可靠信息传送服务,UDP 在 IP 报文的协议号是 17。 + +与 TCP(传输控制协议)协议一样,UDP 协议直接位于 IP(网际协议)协议的顶层。根据 TCP/IP 参考模型,UDP 和TCP 都属于传输层协议。UDP 协议的主要作用是将数据压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前 8 个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。 + +UDP 报文的具体格式如下: + +**源端口( 2 字节) + 目的端口( 2 字节) + 长度( 2 字节) + 检验和( 2 字节) + 数据** + +用图表的形式展现出来如下图所示: + +![UDP 报文格式](figures/UDP1.png) + +### UDP 通信过程 + +UDP 协议的通信较 TCP 简单了很多,减少了 TCP 的握手、确认、窗口、重传、拥塞控制等机制,UDP 是一个无状态的传输协议。 + +UDP 客户端在发送数据时并不判断主机是否可达,服务器是否开启等问题,同样它不能确定数据是否成功送达服务器。它只是将数据简单的封了一个包,之后就丢出去了。 + +我们可以在开发板上运行 samples 里面的 udp client 示例程序。 + +### 抓包分析 + +1. 打开 wireshark 软件 开启抓包,设定过滤条件为 udp,只显示和 udp 协议相关的包。 +2. 开发板连接上网络后,在终端上输入 `udpclient 192.168.12.44 5000 5` + +查看 wireshark ,发现已经抓到了 udpclient 发来的五个包了。 + +我们点开封包详细信息然后和上面的 UDP 协议的报文格式对照一下,就弄清楚 UDP 协议的工作机制了。 + +![UDP 协议抓包结果](figures/UDP2.png) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/tcp_udp_principle.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/tcp_udp_principle.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2e6502e7f430d5a7b67b8529bf50aa3df020ae33 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcp_udp_principle/tcp_udp_principle.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/ip.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/ip.png new file mode 100644 index 0000000000000000000000000000000000000000..8c4ac8cbbedfb98c1a0ce84be8f91b52ed1f28ee Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/ip.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient1.png new file mode 100644 index 0000000000000000000000000000000000000000..f1344f975142079a0ee4f892fae2b2d749a887c2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient2.png new file mode 100644 index 0000000000000000000000000000000000000000..b11018812e4a00fa2d155ea45a3c343c00a3caaf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient3.png new file mode 100644 index 0000000000000000000000000000000000000000..d825c806c1ac255031bffffa9dc9e0f2f1c5df45 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient4.png new file mode 100644 index 0000000000000000000000000000000000000000..f00fac8e33d35bc80d622abe808cdf1188222c6c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient5.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient5.png new file mode 100644 index 0000000000000000000000000000000000000000..55d8653d74a57dfa4b933524eabf9fa4be83bd7a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient_flow.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..8c0d32aa1974ce17f2daf489e58d64864e1f777c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/figures/tcpclient_flow.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/tcpclient.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/tcpclient.md new file mode 100644 index 0000000000000000000000000000000000000000..cb4c6faf2e4dfc250be85576a28160f56809c5ca --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/tcpclient.md @@ -0,0 +1,227 @@ +# 使用Socket实现TCP客户端 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +socket 编程一般采用客户端-服务器模式,即由客户进程向服务器进程发出请求,服务器进程执行请求的任务并将执行结果返回给客户进程的模式。 + +本教程介绍了如何编写一个基于 socket 编程实现的 TCP 客户端。我们先将 socket 编程的流程列出来,然后给出具体的实例。 + +TCP 客户端的 socket 编程流程 +1. 创建 socket +2. 建立连接 +3. 通信 +4. 关闭 socket + +如下图所示: + +![TCP 客户端的 socket 编程流程](figures/tcpclient_flow.png) + +## 准备工作 + +### 获取示例代码 + +RT-Thread samples 软件包中已有一份该示例代码 [tcpclient.c](https://github.com/RT-Thread-packages/network-sample/blob/master/tcpclient_sample.c),可以通过 Env 配置将示例代码加入到项目中。 + +``` + RT-Thread online packages ---> + miscellaneous packages ---> + samples: RT-Thread kernel and components samples ---> + [*] a network_samples package for rt-thread ---> + [*] [network] tcp client +``` + +### 示例代码文件 + +```c +/* + * 程序清单:tcp 客户端 + * + * 这是一个 tcp 客户端的例程 + * 导出 tcpclient 命令到控制终端 + * 命令调用格式:tcpclient URL PORT + * URL:服务器地址 PORT::端口号 + * 程序功能:接收并显示从服务端发送过来的信息,接收到开头是 'q' 或 'Q' 的信息退出程序 +*/ +#include +#include /* 使用BSD socket,需要包含socket.h头文件 */ +#include "netdb.h" + +#define BUFSZ 1024 + +static const char send_data[] = "This is TCP Client from RT-Thread."; /* 发送用到的数据 */ +void tcpclient(int argc, char **argv) +{ + int ret; + char *recv_data; + struct hostent *host; + int sock, bytes_received; + struct sockaddr_in server_addr; + const char *url; + int port; + + if (argc < 3) + { + rt_kprintf("Usage: tcpclient URL PORT\n"); + rt_kprintf("Like: tcpclient 192.168.12.44 5000\n"); + return ; + } + + url = argv[1]; + port = strtoul(argv[2], 0, 10); + + /* 通过函数入口参数url获得host地址(如果是域名,会做域名解析) */ + host = gethostbyname(url); + + /* 分配用于存放接收数据的缓冲 */ + recv_data = rt_malloc(BUFSZ); + if (recv_data == RT_NULL) + { + rt_kprintf("No memory\n"); + return; + } + + /* 创建一个socket,类型是SOCKET_STREAM,TCP类型 */ + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + /* 创建socket失败 */ + rt_kprintf("Socket error\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + return; + } + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 连接到服务端 */ + if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) + { + /* 连接失败 */ + rt_kprintf("Connect fail!\n"); + closesocket(sock); + + /*释放接收缓冲 */ + rt_free(recv_data); + return; + } + + while (1) + { + /* 从sock连接中接收最大BUFSZ - 1字节数据 */ + bytes_received = recv(sock, recv_data, BUFSZ - 1, 0); + if (bytes_received < 0) + { + /* 接收失败,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nreceived error,close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + else if (bytes_received == 0) + { + /* 默认 recv 为阻塞模式,此时收到0认为连接出错,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nreceived error,close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + + /* 有接收到数据,把末端清零 */ + recv_data[bytes_received] = '\0'; + + if (strncmp(recv_data, "q", 1) == 0 || strncmp(recv_data, "Q", 1) == 0) + { + /* 如果是首字母是q或Q,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\n got a 'q' or 'Q',close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + else + { + /* 在控制终端显示收到的数据 */ + rt_kprintf("\nReceived data = %s ", recv_data); + } + + /* 发送数据到sock连接 */ + ret = send(sock, send_data, strlen(send_data), 0); + if (ret < 0) + { + /* 接收失败,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nsend error,close the socket.\r\n"); + + rt_free(recv_data); + break; + } + else if (ret == 0) + { + /* 打印send函数返回值为0的警告信息 */ + rt_kprintf("\n Send warning,send function return 0.\r\n"); + } + } + return; +} +MSH_CMD_EXPORT(tcpclient, a tcp client sample); +``` + +## 在 msh shell 中运行示例代码 + +在运行示例代码之前需要先在电脑上开启一个 TCP 服务器,这里以网络调试助手 IPOP 为例。 + +![搭建服务器](figures/tcpclient1.png) + +然后,查看本机 ip 地址 + +在 windows 系统中打开命令提示符,输入 ipconfig 即可查看本机 ip + +![查看本机 IP 地址](figures/ip.png) + +然后,在系统运行起来后,在 msh 命令行下输入下面的命令即可让示例代码运行。 + +```c +msh> tcpclient 192.168.12.44 5000 +``` + +tcpclient 有两个参数 URL PORT,其中: + +* URL 是目标服务器的网址或 IP 地址 +* PORT 是目标服务器的端口号 + +## 预期结果 + +![连接到服务器](figures/tcpclient5.png) + +可以看出客户端已经连接到服务器了。然后,从服务端向客户端发送数据 + +![从服务端向客户端发送数据](figures/tcpclient2.png) + +从客户端能接收到服务端发来的数据,发送字符 'q' 即可断开连接 + +![预期结果](figures/tcpclient3.png) + +> [!NOTE] +> 注:请关闭防火墙之后,再运行此例程。 + +## 参考资料 + +* 源码 [tcpclient.c](https://github.com/RT-Thread-packages/network-sample/blob/master/tcpclient_sample.c) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/tcpclient.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/tcpclient.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c76807210abe2517739f8f0d841bd2e52e2983d0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient/tcpclient.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/ip.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/ip.png new file mode 100644 index 0000000000000000000000000000000000000000..8c4ac8cbbedfb98c1a0ce84be8f91b52ed1f28ee Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/ip.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient1.png new file mode 100644 index 0000000000000000000000000000000000000000..f1344f975142079a0ee4f892fae2b2d749a887c2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient2.png new file mode 100644 index 0000000000000000000000000000000000000000..b11018812e4a00fa2d155ea45a3c343c00a3caaf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient3.png new file mode 100644 index 0000000000000000000000000000000000000000..d825c806c1ac255031bffffa9dc9e0f2f1c5df45 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient5.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient5.png new file mode 100644 index 0000000000000000000000000000000000000000..d0fd7af376d5e2a280ef8b65ae6e35343d0c37d6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/figures/tcpclient5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/tcpclient_select.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/tcpclient_select.md new file mode 100644 index 0000000000000000000000000000000000000000..25d00f14c07b267bbe25810a0353af79d3fb45ed --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/tcpclient_select.md @@ -0,0 +1,258 @@ +# 使用Select实现非阻塞网络编程 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +在 RT-Thread 使用 socket 网络编程时,由于 socket 的 recv 和 send 的实现是阻塞式的,因此当一个任务调用 recv() 函数接收数据时,如果 socket 上并没有接收到数据,这个任务将阻塞在 recv() 函数里。这个时候,这个任务想要处理一些其他事情,就变得不可能了。 + +在要求网络传输的同时,还能处理其他的数据的场景下,就需要用到 select 了,select 能够同时监视多个非阻塞 socket 的多个事件,这对于以上问题的解决有着重要的意义。 + +这里我们先用一个简单的 *利用 select 实现的 tcp 客户端* ,给大家展示 select 函数的基本用法。在掌握 select 的基本用法之后,就可以从下面的应用笔记中找到以上问题的解决方法了。 + +应用笔记:[基于多线程的非阻塞 socket 编程](https://www.rt-thread.org/document/site/application-note/components/network/an0019-rtthread-system-tcpclient-socket/) + +## 准备工作 + +### 获取示例代码 + +RT-Thread samples 软件包中已有一份该示例代码 [tcpclient_select_sample.c](https://github.com/RT-Thread-packages/network-sample/blob/master/tcpclient_select_sample.c),可以通过 Env 配置将示例代码加入到项目中。 + +按照下面的路径即可开启 select 的示例代码 + +``` + RT-Thread online packages ---> + miscellaneous packages ---> + samples: RT-Thread kernel and components samples ---> + [*] a network_samples package for rt-thread ---> + [*] [network] tcp client by select api +``` + +### 示例代码文件 + +```c +/* + * Copyright (c) 2006-2018, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * + */ +/* 程序清单:利用 select 实现的 tcp 客户端 + * + * 这是一个 利用 select 实现 tcp 客户端的例程 + * 导出 tcpclient_select 命令到控制终端 + * 命令调用格式:tcpclient_select URL PORT + * URL:服务器地址 PORT::端口号 + * 程序功能:利用 select 监听 socket 是否有数据到达, + * 有数据到达的话再接收并显示从服务端发送过来的信息, + * 接收到开头是 'q' 或 'Q' 的信息退出程序 +*/ +#include +#include /* 使用BSD socket,需要包含socket.h头文件 */ +#include +#include /* 使用 dfs select 功能 */ +#include +#include + +#define BUFSZ 1024 + +static const char send_data[] = "This is TCP Client from RT-Thread."; /* 发送用到的数据 */ +void tcpclient_select(int argc, char **argv) +{ + int ret; + char *recv_data; + struct hostent *host; + int sock, bytes_received; + struct sockaddr_in server_addr; + const char *url; + int port; + fd_set readset; + int i, maxfdp1; + + if (argc < 3) + { + rt_kprintf("Usage: tcpclient_select URL PORT\n"); + rt_kprintf("Like: tcpclient_select 192.168.12.44 5000\n"); + return ; + } + + url = argv[1]; + port = strtoul(argv[2], 0, 10); + + /* 通过函数入口参数url获得host地址(如果是域名,会做域名解析) */ + host = gethostbyname(url); + + /* 分配用于存放接收数据的缓冲 */ + recv_data = rt_malloc(BUFSZ); + if (recv_data == RT_NULL) + { + rt_kprintf("No memory\n"); + return; + } + + /* 创建一个socket,类型是SOCKET_STREAM,TCP类型 */ + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + /* 创建socket失败 */ + rt_kprintf("Socket error\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + return; + } + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 连接到服务端 */ + if (connect(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) + { + /* 连接失败 */ + rt_kprintf("Connect fail!\n"); + closesocket(sock); + + /*释放接收缓冲 */ + rt_free(recv_data); + return; + } + + /* 获取需要监听的描述符号最大值 */ + maxfdp1 = sock + 1; + + while (1) + { + /* 清空可读事件描述符列表 */ + FD_ZERO(&readset); + + /* 将需要监听可读事件的描述符加入列表 */ + FD_SET(sock, &readset); + + /* 等待设定的网络描述符有事件发生 */ + i = select(maxfdp1, &readset, RT_NULL, RT_NULL, RT_NULL); + + /* 至少有一个文件描述符有指定事件发生再向后运行 */ + if (i == 0) continue; + + /* 查看 sock 描述符上有没有发生可读事件 */ + if (FD_ISSET(sock, &readset)) + { + /* 从sock连接中接收最大BUFSZ - 1字节数据 */ + bytes_received = recv(sock, recv_data, BUFSZ - 1, 0); + if (bytes_received < 0) + { + /* 接收失败,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nreceived error,close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + else if (bytes_received == 0) + { + /* 默认 recv 为阻塞模式,此时收到0认为连接出错,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nreceived error,close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + + /* 有接收到数据,把末端清零 */ + recv_data[bytes_received] = '\0'; + + if (strncmp(recv_data, "q", 1) == 0 || strncmp(recv_data, "Q", 1) == 0) + { + /* 如果是首字母是q或Q,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\n got a 'q' or 'Q',close the socket.\r\n"); + + /* 释放接收缓冲 */ + rt_free(recv_data); + break; + } + else + { + /* 在控制终端显示收到的数据 */ + rt_kprintf("\nReceived data = %s ", recv_data); + } + + /* 发送数据到sock连接 */ + ret = send(sock, send_data, strlen(send_data), 0); + if (ret < 0) + { + /* 接收失败,关闭这个连接 */ + closesocket(sock); + rt_kprintf("\nsend error,close the socket.\r\n"); + + rt_free(recv_data); + break; + } + else if (ret == 0) + { + /* 打印send函数返回值为0的警告信息 */ + rt_kprintf("\n Send warning,send function return 0.\r\n"); + } + } + } + return; +} + +MSH_CMD_EXPORT(tcpclient_select, a tcp client sample by select api); +``` + +## 在 msh shell 中运行示例代码 + +在运行示例代码之前需要先在电脑上开启一个 TCP 服务器,这里以网络调试助手 IPOP 为例。 + +![搭建服务器](figures/tcpclient1.png) + +然后,查看本机 ip 地址 + +在 windows 系统中打开命令提示符,输入 ipconfig 即可查看本机 ip + +![查看 IP 地址](figures/ip.png) + +然后,在系统运行起来后,在 msh 命令行下输入下面的命令即可让示例代码运行。 + +```c +msh> tcpclient_select 192.168.12.44 5000 +``` + +tcpclient_select 有两个参数 URL PORT,其中: + +* URL 是目标服务器的网址或 IP 地址 +* PORT 是目标服务器的端口号 + +![连接到服务器](figures/tcpclient5.png) + +从服务端向客户端发送数据 + +![发数据到客户端](figures/tcpclient2.png) + +## 预期结果 + +从客户端能接收到服务端发来的数据,发送字符 'q' 即可断开连接 + +![预期结果](figures/tcpclient3.png) + +> [!NOTE] +> 注:请关闭防火墙之后,再运行此例程。 + +## 参考资料 + +* 源码 [tcpclient_select_sample.c](https://github.com/RT-Thread-packages/network-sample/blob/master/tcpclient_select_sample.c) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/tcpclient_select.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/tcpclient_select.pdf new file mode 100644 index 0000000000000000000000000000000000000000..13db3bafa9c0c4f32f0cbe7b1349b5033bcdf7b8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpclient_select/tcpclient_select.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpserver_flow.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpserver_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..4ebc350b874bd8105df70458b31e47c479cb8639 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpserver_flow.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever1.png new file mode 100644 index 0000000000000000000000000000000000000000..5b282e40c79d7e65e4c8ed55b86207d341639d71 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever2.png new file mode 100644 index 0000000000000000000000000000000000000000..015817e267aff9a83def3775d92581dd4ad5eaac Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever3.png new file mode 100644 index 0000000000000000000000000000000000000000..30e74a81cae094effd615449b231a4a38d0251c1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever4.png new file mode 100644 index 0000000000000000000000000000000000000000..80a4b9c4d36fae8ea2f9e18be2d40292a492012c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/figures/tcpsever4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/tcpserver.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/tcpserver.md new file mode 100644 index 0000000000000000000000000000000000000000..5ce225b62670f60b0b7db37a2f3f2eb6e60edac6 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/tcpserver.md @@ -0,0 +1,236 @@ +# 使用Socket实现TCP服务器 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +socket 编程一般采用客户端-服务器模式,即由客户进程向服务器进程发出请求,服务器进程执行请求的任务并将执行结果返回给客户进程的模式。 + +本教程介绍了如何编写一个基于 socket 编程实现的 TCP 服务器。我们先将 socket 编程的流程列出来,然后给出具体的实例。 + +TCP 服务器的 socket 编程流程 +1. 创建 socket +2. 将创建的 socket 绑定到一个 IP 地址和端口号上 +3. 设置 socket 为监听模式 +4. 接收请求并返回 socket +5. 与客户端进行通信 +6. 关闭 socket + +如下图所示: + +![TCP 服务器的 socket 编程流程](figures/tcpserver_flow.png) + +## 准备工作 + +### 获取示例代码 + +RT-Thread samples 软件包中已有一份该示例代码 [tcpserver.c](https://github.com/RT-Thread-packages/network-sample/blob/master/tcpserver_sample.c),可以通过 Env 配置将示例代码加入到项目中。 + +``` + RT-Thread online packages ---> + miscellaneous packages ---> + samples: RT-Thread kernel and components samples ---> + network sample options ---> + [*] [network] tcp server +``` + +### 示例代码文件 + +```c +/* +* 程序清单:tcp 服务端 + * + * 这是一个 tcp 服务端的例程 + * 导出 tcpserv 命令到控制终端 + * 命令调用格式:tcpserv + * 无参数 + * 程序功能:作为一个服务端,接收并显示客户端发来的数据 ,接收到 exit 退出程序 +*/ +#include +#include /* 使用BSD socket,需要包含socket.h头文件 */ +#include "netdb.h" + +#define BUFSZ (1024) + +static const char send_data[] = "This is TCP Server from RT-Thread."; /* 发送用到的数据 */ +static void tcpserv(int argc, char **argv) +{ + char *recv_data; /* 用于接收的指针,后面会做一次动态分配以请求可用内存 */ + socklen_t sin_size; + int sock, connected, bytes_received; + struct sockaddr_in server_addr, client_addr; + rt_bool_t stop = RT_FALSE; /* 停止标志 */ + int ret; + + recv_data = rt_malloc(BUFSZ + 1); /* 分配接收用的数据缓冲 */ + if (recv_data == RT_NULL) + { + rt_kprintf("No memory\n"); + return; + } + + /* 一个socket在使用前,需要预先创建出来,指定SOCK_STREAM为TCP的socket */ + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + /* 创建失败的错误处理 */ + rt_kprintf("Socket error\n"); + + /* 释放已分配的接收缓冲 */ + rt_free(recv_data); + return; + } + + /* 初始化服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(5000); /* 服务端工作的端口 */ + server_addr.sin_addr.s_addr = INADDR_ANY; + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 绑定socket到服务端地址 */ + if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) + { + /* 绑定失败 */ + rt_kprintf("Unable to bind\n"); + + /* 释放已分配的接收缓冲 */ + rt_free(recv_data); + return; + } + + /* 在socket上进行监听 */ + if (listen(sock, 5) == -1) + { + rt_kprintf("Listen error\n"); + + /* release recv buffer */ + rt_free(recv_data); + return; + } + + rt_kprintf("\nTCPServer Waiting for client on port 5000...\n"); + while (stop != RT_TRUE) + { + sin_size = sizeof(struct sockaddr_in); + + /* 接受一个客户端连接socket的请求,这个函数调用是阻塞式的 */ + connected = accept(sock, (struct sockaddr *)&client_addr, &sin_size); + /* 返回的是连接成功的socket */ + if (connected < 0) + { + rt_kprintf("accept connection failed! errno = %d\n", errno); + continue; + } + + /* 接受返回的client_addr指向了客户端的地址信息 */ + rt_kprintf("I got a connection from (%s , %d)\n", + inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); + + /* 客户端连接的处理 */ + while (1) + { + /* 发送数据到connected socket */ + ret = send(connected, send_data, strlen(send_data), 0); + if (ret < 0) + { + /* 发送失败,关闭这个连接 */ + closesocket(connected); + rt_kprintf("\nsend error,close the socket.\r\n"); + break; + } + else if (ret == 0) + { + /* 打印send函数返回值为0的警告信息 */ + rt_kprintf("\n Send warning,send function return 0.\r\n"); + } + + /* 从connected socket中接收数据,接收buffer是1024大小,但并不一定能够收到1024大小的数据 */ + bytes_received = recv(connected, recv_data, BUFSZ, 0); + if (bytes_received < 0) + { + /* 接收失败,关闭这个connected socket */ + closesocket(connected); + break; + } + else if (bytes_received == 0) + { + /* 打印recv函数返回值为0的警告信息 */ + rt_kprintf("\nReceived warning,recv function return 0.\r\n"); + closesocket(connected); + break; + } + + /* 有接收到数据,把末端清零 */ + recv_data[bytes_received] = '\0'; + if (strcmp(recv_data, "q") == 0 || strcmp(recv_data, "Q") == 0) + { + /* 如果是首字母是q或Q,关闭这个连接 */ + closesocket(connected); + break; + } + else if (strcmp(recv_data, "exit") == 0) + { + /* 如果接收的是exit,则关闭整个服务端 */ + closesocket(connected); + stop = RT_TRUE; + break; + } + else + { + /* 在控制终端显示收到的数据 */ + rt_kprintf("RECEIVED DATA = %s \n", recv_data); + } + } + } + + /* 退出服务 */ + closesocket(sock); + + /* 释放接收缓冲 */ + rt_free(recv_data); + + return ; +} +MSH_CMD_EXPORT(tcpserv, a tcp server sample); +``` + +## 在 msh shell 中运行示例代码 + +在系统运行起来后,在 msh 命令行下输入命令 `tcpserv` 即可让示例代码运行。 + +```c +msh /> tcpserv +TCPServer Waiting for client on port 5000... +``` + +然后在电脑上开启一个 TCP 客户端,这里以网络调试助手为例。 + + 1. 如果 是 QEMU 平台的话要先绑定网卡和 IP 地址。 + + ![绑定 Tap 网卡](figures/tcpsever1.png) + + 2. 连接服务器 + + ![连接服务器](figures/tcpsever2.png) + + 3. 从客户端向服务器发送数据 + + ![从客户端向服务器发送数据](figures/tcpsever3.png) + +## 预期结果 + +从服务器能接收到客户端发来的数据 + +![预期结果](figures/tcpsever4.png) + +发送 exit 到服务器,即可停止服务器的运行。 + +## 参考资料 + +* 源码 [tcpserver.c](https://github.com/RT-Thread-packages/network-sample/blob/master/tcpserver_sample.c) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/tcpserver.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/tcpserver.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8556b7abc1ec01102295816621b609427ff03812 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tcpserver/tcpserver.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/figures/telnet1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/figures/telnet1.png new file mode 100644 index 0000000000000000000000000000000000000000..99db712a6f2e2268d855633710be4807e24df7b3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/figures/telnet1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/figures/telnet2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/figures/telnet2.png new file mode 100644 index 0000000000000000000000000000000000000000..43924bf3d10a5c590081a279c37892d80188bf6b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/figures/telnet2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/telnet.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/telnet.md new file mode 100644 index 0000000000000000000000000000000000000000..fc2bd3b1e2a9b59129713a35adb0168e0ca53737 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/telnet.md @@ -0,0 +1,79 @@ +# 使用Telnet进行远程设备控制 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +[Telnet](https://baike.baidu.com/item/Telnet) 协议是一种应用层协议,使用于互联网及局域网中,使用虚拟终端机的形式,提供双向、以文字字符串为主的交互功能。属于 TCP/IP 协议族的其中之一,是 Internet 远程登录服务的标准协议和主要方式,常用于网页服务器的远程控制,可供用户在本地主机运行远程主机上的工作。 + +RT-Thread 目前支持的是 Telnet 服务器, Telnet 客户端连接成功后,将会远程连接到设备的 Finsh/MSH ,实现设备的远程控制。 + +## 准备工作 + +### 开启 telnet server + +打开 Env 工具输入 menuconfig 按照下面的路径打开 telnet server + +``` + RT-Thread online packages ---> + IoT - internet of things ---> + netutils: Networking utilities for RT-Thread ---> + [*] Enable Telnet server +``` + +然后,打开工程,编译并运行程序 + +### 查看系统的 ip 地址 + +系统运行起来后,在终端输入 ifconfig,查看系统的 ip 地址。 + +``` +msh />ifconfig +network interface: e0 (Default) +MTU: 1500 +MAC: 52 54 00 11 22 33 +FLAGS: UP LINK_UP ETHARP BROADCAST +ip address: 192.168.137.91 +gw address: 192.168.137.1 +net mask : 255.255.255.0 +dns server #0: 192.168.137.1 +dns server #1: 0.0.0.0 +``` + +其中 ip address: 192.168.137.91就是系统的 ip 地址 + +## 在 msh shell 中运行示例代码 + +系统运行起来后,在 msh 命令行下输入 telnet_server 命令即可让示例代码运行。 + +``` +msh /> telnet_server +Telnet server start successfully +telnet: waiting for connection +``` + +然后,用 putty 远程登陆系统 + +![putty 设置步骤](figures/telnet1.png) + +## 预期结果 ## + +这样就已经远程登陆到系统了,打印出了系统的信息。 + +![预期结果](figures/telnet2.png) + +> [!NOTE] +> 注:* 与 Telnet 服务器连接成功后,设备本地的 Finsh/MSH 将无法使用。如果需要使用,断开已连接的 Telnet 客户端即可; + * Telnet 不支持 `TAB` 自动补全快捷键、`Up`/`Down` 查阅历史等快捷键; + * 目前 Telnet 服务器只支持连接到 **1** 个客户端。 + +## 参考资料 + +* [RT-Thread 网络小工具集](https://github.com/RT-Thread-packages/netutils) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/telnet.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/telnet.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e714c9565982a115a3f16cc749598a48c5280ba9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/telnet/telnet.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp1.png new file mode 100644 index 0000000000000000000000000000000000000000..bc40ee08d5f9cc9a3ed706cfa94e53c489d3df14 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp2.png new file mode 100644 index 0000000000000000000000000000000000000000..6ac6ca207ad7ef5be9f7d76a0f5e0062842c0180 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp3.png new file mode 100644 index 0000000000000000000000000000000000000000..59d133de58898b01814a18ba5099d3a31f046877 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp4.png new file mode 100644 index 0000000000000000000000000000000000000000..1f67ee1b12d3a8fb3f699d68b412f36a9adbb6bb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp5.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp5.png new file mode 100644 index 0000000000000000000000000000000000000000..b00f25f0c1a4919e8b69b4e0d3211f99b30498c2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp6.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp6.png new file mode 100644 index 0000000000000000000000000000000000000000..b23f0db4c83583c3ac557a24bd40d8dd46fcb509 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/figures/tftp6.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/tftp.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/tftp.md new file mode 100644 index 0000000000000000000000000000000000000000..a3a37279caffaeb432d79dffffb08a4b17c91f38 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/tftp.md @@ -0,0 +1,118 @@ +# 使用TFTP实现网络文件传输 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +[TFTP](https://baike.baidu.com/item/TFTP) (Trivial File Transfer Protocol, 简单文件传输协议)是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务,端口号为 **69** ,比传统的 FTP 协议要轻量级很多,适用于小型的嵌入式产品上。 + +RT-Thread 目前支持的是 TFTP 服务器。 + +## 准备工作 + +### 安装 TFTP 客户端 ### + +下载安装 [Tftpd64-4.60-setup.exe](https://github.com/RT-Thread-packages/netutils/blob/master/tools/Tftpd64-4.60-setup.exe),使用 TFTP 前,请先安装该软件。 + +### 开启 TFTP server ### + +打开 Env 工具输入 menuconfig 按照下面的路径打开 tftp server + +``` + RT-Thread online packages ---> + IoT - internet of things ---> + netutils: Networking utilities for RT-Thread ---> + [*] Enable TFTP(Trivial File Transfer Protocol) server +``` + +### 调大 lwIP 线程的堆栈大小 ### + +按下面路径找到 lwIP 的 tcpip 默认配置,将原来默认的 1024 改为 2048 + +``` +RT-Thread Components ---> + Network ---> + light weight TCP/IP stack ---> + (2048) the stack size of lwIP thread +``` + +编译运行 + +### 查看系统的 ip 地址 + +在系统运行起来后,在 msh 命令行下输入 ifconfig,运行结果如下 + +``` +msh />ifconfig +network interface: e0 (Default) +MTU: 1500 +MAC: 52 54 00 11 22 33 +FLAGS: UP LINK_UP ETHARP BROADCAST +ip address: 192.168.137.140 +gw address: 192.168.137.1 +net mask : 255.255.255.0 +dns server #0: 192.168.137.1 +dns server #1: 0.0.0.0 +``` + +其中 ip address: 192.168.137.140 就是系统的 ip 地址 + +## 在 msh shell 中运行 TFTP 服务器 + +在 msh 命令行下输入下面的命令即可运行 tftp 服务器,运行结果如下,等待客户端连接。 + +```c +msh> tftp_server +TFTP server start successfully. +``` + +## 运行 TFTP 客户端 ## + +打开 TFTP 客户端,按照下图所示方法配置 + +![配置 TFTP 客户端](figures/tftp1.png) + +### 发送文件到 RT-Thread ### + +我们先传输一个文件到 RT-Thread 上,操作如下图所示 + +![发送文件到 RT-Thread](figures/tftp2.png) + +提示发送完成 + +![提示发送完成](figures/tftp3.png) + +在终端输入命令 ls,发现文件已经传输到当前目录下了 + +``` +msh />ls +Directory /: +aa.png 67288 +msh /> +``` + +### 从 RT-Thread 获取文件 ### + +我们再将刚才传输到 RT-Thread 中的文件获取到本地 + +![从 RT-Thread 获取文件](figures/tftp4.png) + +同样会提示传输完成,打开本地文件路径,发现文件已经从 RT-Thread 获取到本地了。 + +![预期结果](figures/tftp6.png) + +> [!NOTE] +> 注:* 如出现连接失败的情况,可以尝试关闭防火墙 + * 相对路径是基于 Finsh/MSH 当前进入的目录。使用相对路径时,务必提前切换好目录 + +## 参考资料 + +* 源码 [tftp](https://github.com/RT-Thread-packages/netutils/tree/master/tftp) +* [RT-Thread 网络小工具集](https://github.com/RT-Thread-packages/netutils) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/tftp.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/tftp.pdf new file mode 100644 index 0000000000000000000000000000000000000000..28dd0f025ce78c78518c5d90eff5cb4fbd654c4e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/tftp/tftp.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/ip.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/ip.png new file mode 100644 index 0000000000000000000000000000000000000000..8c4ac8cbbedfb98c1a0ce84be8f91b52ed1f28ee Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/ip.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient1.png new file mode 100644 index 0000000000000000000000000000000000000000..ffd42b805810adbf08ddd68a0b94ce170693918c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient2.png new file mode 100644 index 0000000000000000000000000000000000000000..8afa8934be352a884da2c5a8a2b0ec25643655ba Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient_flow.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..e7de809c01afe0d42135cdf02999b4efdf5333cf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/figures/udpclient_flow.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/udpclient.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/udpclient.md new file mode 100644 index 0000000000000000000000000000000000000000..46ccd415b8a015095eb325b307ce41aa00acd669 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/udpclient.md @@ -0,0 +1,156 @@ +# 使用Socket实现UDP客户端 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +UDP 协议是用于客户端-服务器模式的一种传输协议,如今的很多通信软件都是利用这个协议实现的,如腾讯 QQ 发送消息用的就是 UDP 协议。 + +本教程介绍了如何利用 socket 编程来实现一个 UDP 客户端,与服务器进行通信。与开发 TCP 客户端一样,我们先将 socket 编程的流程列出来,然后给出具体的实例。 + +UDP 与 TCP 的不同之处是,他的通信不需要建立连接的过程。 + +UDP 客户端的 socket 编程流程 +1. 创建 socket +2. 通信 +3. 关闭 socket + +如下图所示: + +![UDP 客户端的 socket 编程流程](figures/udpclient_flow.png) + +## 准备工作 + +### 获取示例代码 + +RT-Thread samples 软件包中中已有一份该示例代码 [udpclient.c](https://github.com/RT-Thread-packages/network-sample/blob/master/udpclient_sample.c),可以通过 Env 配置将示例代码加入到项目中。 + +``` + RT-Thread online packages ---> + miscellaneous packages ---> + samples: RT-Thread kernel and components samples ---> + [*] a network_samples package for rt-thread ---> + [*] [network] udp client +``` + +### 示例代码文件 + +```c +/* + * 程序清单:udp 客户端 + * + * 这是一个 udp 客户端的例程 + * 导出 udpclient 命令到控制终端 + * 命令调用格式:udpclient URL PORT [COUNT = 10] + * URL:服务器地址 PORT:端口号 COUNT:可选参数 默认为 10 + * 程序功能:发送 COUNT 条数据到服务远端 +*/ +#include +#include /* 使用BSD socket,需要包含sockets.h头文件 */ +#include "netdb.h" + +const char send_data[] = "This is UDP Client from RT-Thread.\n"; /* 发送用到的数据 */ +void udpclient(int argc, char **argv) +{ + int sock, port, count; + struct hostent *host; + struct sockaddr_in server_addr; + const char *url; + + if (argc < 3) + { + rt_kprintf("Usage: udpclient URL PORT [COUNT = 10]\n"); + rt_kprintf("Like: tcpclient 192.168.12.44 5000\n"); + return ; + } + + url = argv[1]; + port = strtoul(argv[2], 0, 10); + + if (argc > 3) + count = strtoul(argv[3], 0, 10); + else + count = 10; + + /* 通过函数入口参数url获得host地址(如果是域名,会做域名解析) */ + host = (struct hostent *) gethostbyname(url); + + /* 创建一个socket,类型是SOCK_DGRAM,UDP类型 */ + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + { + rt_kprintf("Socket error\n"); + return; + } + + /* 初始化预连接的服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + server_addr.sin_addr = *((struct in_addr *)host->h_addr); + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 总计发送count次数据 */ + while (count) + { + /* 发送数据到服务远端 */ + sendto(sock, send_data, strlen(send_data), 0, + (struct sockaddr *)&server_addr, sizeof(struct sockaddr)); + + /* 线程休眠一段时间 */ + rt_thread_delay(50); + + /* 计数值减一 */ + count --; + } + + /* 关闭这个socket */ + closesocket(sock); +} +MSH_CMD_EXPORT(udpclient, a udp client sample); +``` + +## 在 msh shell 中运行示例代码 + +在运行示例代码之前需要先在电脑上开启一个 UDP 服务器,这里以网络调试助手 IPOP 为例。 + +![搭建 UDP 服务器](figures/udpclient1.png) + +查看本机 ip 地址 + +在 windows 系统中打开命令提示符,输入 ipconfig 即可查看本机 ip + +![查看 IP 地址](figures/ip.png) + +在系统运行起来后,在 msh 命令行下输入下面的命令即可让示例代码运行。 + +``` +msh> udpclient 192.168.12.44 5000 +``` + +这个示例代码的功能是向输入的 IP 地址发送 10 条消息,发送完毕即退出,具体的参数如下。 + +udpclient 有两个固定参数和一个可选参数 URL PORT [COUNT = 10],其中: + +- URL 是目标服务器的网址或 IP 地址,这个对应刚才搭建的服务器中的本机地址 +- PORT 是目标服务器的端口号,这个对应刚才输出的端口号 +- [COUNT = 10] 是向服务器发送数据的条数 默认是10 + + +## 预期结果 ## + +从服务端能接收到客户端发来的数据 + +![预期结果](figures/udpclient2.png) + +> [!NOTE] +> 注:请关闭防火墙之后再运行这个例程。 + +## 参考资料 + +* 源码 [udpclient.c](https://github.com/RT-Thread-packages/network-sample/blob/master/udpclient_sample.c) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/udpclient.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/udpclient.pdf new file mode 100644 index 0000000000000000000000000000000000000000..93b5cef9690329564978da9c149a55d83b32909e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpclient/udpclient.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udp_flow.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udp_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..7ebaceabb6aebd892004189aae5177d9a36356da Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udp_flow.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever1.png new file mode 100644 index 0000000000000000000000000000000000000000..5b282e40c79d7e65e4c8ed55b86207d341639d71 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever2.png new file mode 100644 index 0000000000000000000000000000000000000000..439b0eafe184dc55c166bf27ca6259d91e4be25a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever3.png new file mode 100644 index 0000000000000000000000000000000000000000..87cdd2c257dd899639ce1c5fab697c31a89e41ba Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/figures/udpsever3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/udpserver.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/udpserver.md new file mode 100644 index 0000000000000000000000000000000000000000..b9b71787a510e4c9431d7bc0e252d52a6f9aeb0e --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/udpserver.md @@ -0,0 +1,170 @@ +# 使用Socket实现UDP服务器 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +UDP 协议是用于客户端-服务器模式的一种传输协议,如今的很多通信软件都是利用这个协议实现的,如腾讯 QQ 发送消息用的就是 UDP 协议。 + +本教程介绍了如何利用 socket 编程来实现一个 UDP 服务器,与客户端进行通信。 + +UDP 服务器的 socket 编程流程 +1. 创建 socket +2. 将创建的 socket 绑定到一个 IP 地址和端口号上 +3. 等待接收数据报,处理完成后将结果返回到客户端 +4. 关闭 socket + +如下图所示: + +![UDP 服务器的 socket 编程流程](figures/udp_flow.png) + +## 准备工作 + +### 获取示例代码 + +RT-Thread samples 软件包中已有一份该示例代码 [udpserver.c](https://github.com/RT-Thread-packages/network-sample/blob/master/udpserver_sample.c),可以通过 Env 配置将示例代码加入到项目中。 + +``` + RT-Thread online packages ---> + miscellaneous packages ---> + samples: RT-Thread kernel and components samples ---> + network sample options ---> + [*] [network] udp server +``` + +### 示例代码文件 + +```c +/* +* 程序清单:udp 服务端 + * + * 这是一个 udp 服务端的例程 + * 导出 udpserv 命令到控制终端 + * 命令调用格式:udpserv + * 无参数 + * 程序功能:作为一个服务端,接收并显示客户端发来的数据 ,接收到 exit 退出程序 +*/ +#include +#include /* 使用BSD socket,需要包含socket.h头文件 */ +#include "netdb.h" + +#define BUFSZ 1024 + +static void udpserv(int argc, char **argv) +{ + int sock; + int bytes_read; + char *recv_data; + socklen_t addr_len; + struct sockaddr_in server_addr, client_addr; + + /* 分配接收用的数据缓冲 */ + recv_data = rt_malloc(BUFSZ); + if (recv_data == RT_NULL) + { + /* 分配内存失败,返回 */ + rt_kprintf("No memory\n"); + return; + } + + /* 创建一个socket,类型是SOCK_DGRAM,UDP类型 */ + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + { + rt_kprintf("Socket error\n"); + + /* 释放接收用的数据缓冲 */ + rt_free(recv_data); + return; + } + + /* 初始化服务端地址 */ + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(5000); + server_addr.sin_addr.s_addr = INADDR_ANY; + rt_memset(&(server_addr.sin_zero), 0, sizeof(server_addr.sin_zero)); + + /* 绑定socket到服务端地址 */ + if (bind(sock, (struct sockaddr *)&server_addr, + sizeof(struct sockaddr)) == -1) + { + /* 绑定地址失败 */ + rt_kprintf("Bind error\n"); + + /* 释放接收用的数据缓冲 */ + rt_free(recv_data); + return; + } + + addr_len = sizeof(struct sockaddr); + rt_kprintf("UDPServer Waiting for client on port 5000...\n"); + + while (1) + { + /* 从sock中收取最大BUFSZ - 1字节数据 */ + bytes_read = recvfrom(sock, recv_data, BUFSZ - 1, 0, + (struct sockaddr *)&client_addr, &addr_len); + /* UDP不同于TCP,它基本不会出现收取的数据失败的情况,除非设置了超时等待 */ + + recv_data[bytes_read] = '\0'; /* 把末端清零 */ + + /* 输出接收的数据 */ + rt_kprintf("\n(%s , %d) said : ", inet_ntoa(client_addr.sin_addr), + ntohs(client_addr.sin_port)); + rt_kprintf("%s", recv_data); + + /* 如果接收数据是exit,退出 */ + if (strcmp(recv_data, "exit") == 0) + { + closesocket(sock); + + /* 释放接收用的数据缓冲 */ + rt_free(recv_data); + break; + } + } + + return; +} +MSH_CMD_EXPORT(udpserv, a udp server sample); +``` + +## 在 msh shell 中运行示例代码 + +在系统运行起来后,在 msh 命令行下输入命令 `udpserv` 即可让示例代码运行。 + +```c +msh />udpserv +UDPServer Waiting for client on port 5000... +``` + +然后在电脑上开启一个 UDP 客户端,这里以网络调试助手为例。 + + 1. 如果 是 QEMU 平台的话要先绑定网卡和 IP 地址。 + + ![绑定 TAP 网卡](figures/udpsever1.png) + + 2. 给服务器发送消息 + + ![给服务器发送消息](figures/udpsever2.png) + +## 预期结果 + +从服务器能接收到客户端发来的数据 + +![预期结果](figures/udpsever3.png) + +发送 exit 到服务器,即可停止服务器的运行。 + +> [!NOTE] +> 注:请关闭防火墙之后,再运行此例程。 + +## 参考资料 + +* 源码 [udpserver.c](https://github.com/RT-Thread-packages/network-sample/blob/master/udpserver_sample.c) +* [《Env 用户手册》](../../../programming-manual/env/env.md) + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/udpserver.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/udpserver.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6f6cbfefe778b03302f4e540d5b2408a96fee69d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/udpserver/udpserver.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/version.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/version.md new file mode 100644 index 0000000000000000000000000000000000000000..dc367229ba075d41a14814e31d275bbef9c7f118 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/version.md @@ -0,0 +1,6 @@ +# 版本和修订 + +| Date | Version | Author | Note | +| -------- | :-----: | :---- | :---- | +| 2018-10-26 | v1.0.0 | flybreak | 初始版本 | + diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark1.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark1.png new file mode 100644 index 0000000000000000000000000000000000000000..ec0d3fb815e30705acdd88eaf6bcc8a5fa1781bb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark2.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark2.png new file mode 100644 index 0000000000000000000000000000000000000000..67d0c62c51f2b407a800efe8f56c05e2a2064e72 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark3.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark3.png new file mode 100644 index 0000000000000000000000000000000000000000..fb6a03d534a0e8f0b3d38cb15de37e98214a5ad5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark4.png b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark4.png new file mode 100644 index 0000000000000000000000000000000000000000..994d16ce636f44e10ea5b3b9a0acc9e3ad20e4c2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/figures/wireshark4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/wireshark.md b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/wireshark.md new file mode 100644 index 0000000000000000000000000000000000000000..1b7cc740065e626e19cb689016c6ec43ee3b2fa0 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/wireshark.md @@ -0,0 +1,56 @@ +# 使用WireShark进行网络抓包 + + + +> 提示:视频 PPT 下载 + +## 背景介绍 + +在网络编程的过程中,经常需要利用抓包工具对开发板发出或接收到的数据包进行抓包分析。wireshark 是一个非常好用的抓包工具,使用 wireshark 工具抓包分析,是学习网络编程必不可少的一项技能。 + +## 准备工作 + +### 安装 wireshark + +安装 [wireshark](https://www.wireshark.org/),一路默认安装就行。程序安装完之后,打开 wireshark 软件。 + +### 选择与开发板相对应的网卡 + +打开 wireshark 之后,会给出你的网卡信息,让你选择一个要抓包的网卡如下图所示,选择自己开发板用来上网的网卡,双击就开始抓包了。 + +![选择网卡](figures/wireshark1.png) + +## wireshark 主界面介绍 + +wireshark 的主界面和一般的 Windows 软件很像,也有菜单栏,工具栏,地址栏等。还有一些它自己的像,显示过滤器、封包列表、封包详细信息、十六进制数据显示区等。如下图所示,选择完网卡之后其实就已经开始抓包了。 + +![wireshark 主界面](figures/wireshark2.png) + +## 显示过滤器的使用 + +合理的使用显示过滤器,有助于我们从下面的封包列表里快速找到我们要关注网络包。假如我们的开发板获取到的 IP 地址为 192.168.137.135 ,这样在显示过滤器里输入 `ip.src == 192.168.137.135 or ip.dst == 192.168.137.135` 然后点击右边的那个小箭头就会执行这个过滤条件。 + +过滤之后剩下的都是和我们限定的 IP 相关的包。 + +当然也有一些其他的过滤条件,比如: +- 按 mac 地址过滤 `eth.addr == 52:54:00:11:22:33` +- 只显示 TCP 协议的数据包 `tcp` +- 只显示 UDP 协议的数据包 `udp` + +## 封包列表介绍 + +从封包列表里我们也可以看到一些跟封包相关的信息,例如:来源的 ip,还有用到的通信协议等。 + +![封包列表介绍](figures/wireshark3.png) + +## 封包详细信息介绍 + +下面介绍一下封包详细信息那一栏和底下的十六进制数据是相通的。封包的详细信息就是从底下的实际数据解包显示出来的。下面这张图展示了封包的一些详细信息。 + +![封包详细信息](figures/wireshark4.png) + +只要学会了使用 wireshark 抓包工具,在进行网络编程的时候就会事半功倍了。 + +## 常见问题 + +* [常见问题及解决方法](../faq/faq.md)。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/wireshark.pdf b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/wireshark.pdf new file mode 100644 index 0000000000000000000000000000000000000000..17800a9738b490b3232562efc2fe2c492853835b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/qemu-network/wireshark/wireshark.pdf differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/board.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/board.png new file mode 100644 index 0000000000000000000000000000000000000000..c6ea9070479b8e1c4a02dd7b117950f049c7b5a8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/board.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/dir.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/dir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e5e6116025eefb489d9d254c331e62482ac447c5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/dir.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/project.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7d00b56cc820f76ab1369c8bf0a527b45dd769c6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/usb_pc.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/usb_pc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d01965316c5950314cdea3aae80ede7465a6c843 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/figures/usb_pc.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..719c6c73def36c9b50a6cc83e3ea6bd122fabd0a --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-atk-commander/quick-start.md @@ -0,0 +1,81 @@ +# 正点原子 I.MX RT1052 号令者上手指南 + +## 简介 + +I.MX RT1052 号令者是正点原子推出的一款高性能开发板,主控芯片是 NXP 推出的基于 ARM Cortex-M7 内核的跨界处理器,最高主频为 600Mhz,该开发板具有丰富的板载资源,可以充分发挥 I.MX RT1052 的芯片性能。 + +开发板外观如下图所示: + +![board](figures/board.png) + +该开发板常用 **板载资源** 如下: + +- MCU:I.MX RT1052CVL5B,主频 600MHz,512KB SRAM +- 外部 FLASH:W25Q64(SPI,8MB,存代码)、W25Q256(SPI,32MB,存数据)、EEPROM(24c02) +- 常用外设 + * LED:8个,(红色,PC0-PC7) + * 按键:4个,KEY_UP(兼具唤醒功能,PA0),KEY0(PC8),KEY1(PC9),KEY2(PD2) +- 常用接口:USB 转串口、DS18B20/DHT11 接口 、USB SLAVE、USB HOST +- 调试接口,标准 JTAG 接口 + +开发板更多详细信息请参考 [正点原子官方品牌店宝贝介绍](https://eboard.taobao.com/index.htm)。 + +## 准备工作 + +号令者 I.MX RT1052 板级支持包提供 MDK5 和 IAR 工程,并且支持 GCC 开发环境,下面以 MDK5 开发环境为例,介绍如何将示例程序运行起来。运行示例程序前需要做如下准备工作: + + 1. MDK 开发环境 + + 需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK安装](../keil/keil.md)。 + + 2. 拷贝下载算法 + + 正点原子的号令者 有自己的下载算法,点击[这里](http://www.openedv.com/thread-270579-1-1.html)获取。下载下来之后,按教程操作将 `下载算法` 拷贝到 `Keil_v5/ARM/Flash` 目录下。 + + 3. 源码获取 + + + + [源码目录说明](../src_code_introduction/rtthread_dir.md) + + 4. 使用 USB 线连接开发板的 USB 转串口 到 PC 机。 + + ![连接到 PC](figures/usb_pc.jpg) + +## 运行第一个示例程序 + +### 配置工程 + +进入到 `rt-thread\bsp\imxrt1052-evk` 文件夹中,然后按照下面的步骤配置工程 + +- 在bsp下打开env工具 +- 输入 `menuconfig` 命令,RT1052 Board select (***)-->选择 `RT1050_ATK` 开发板。 +- 输入 `scons --target=mdk5 -s` 生成需要的工程 + +### 编译下载 + +双击 project.uvprojx 文件,打开 MDK5 工程。 + +![工程目录](figures/dir.jpg) + +执行编译,编译完成后,点击下载按钮将固件下载至开发板,下载完成后,程序会自动开始运行,观察程序运行状况。 + +> 提示:工程默认配置使用 DAP 下载程序,在通过 DAP 连接开发板的基础上,点击下载按钮即可下载程序到开发板 + +![编译下载方法](figures/project.jpg) + +### 运行 + +使用串口工具(如:PuTTY)打开板子对应的串口(115200-8-1-N) ,复位设备后,可以看到 RT-Thread 的输出信息: + +```bash + \ | / +- RT - Thread Operating System + / | \ 3.1.1 build Nov 19 2018 + 2006 - 2018 Copyright by rt-thread team +msh > +``` + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/board.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/board.png new file mode 100644 index 0000000000000000000000000000000000000000..e8ff1b0a45f40de46317e922f725c51d6cea8230 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/board.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/dir.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/dir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e5e6116025eefb489d9d254c331e62482ac447c5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/dir.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/project.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7d00b56cc820f76ab1369c8bf0a527b45dd769c6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/usb_pc.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/usb_pc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..010da7f34d3c9d381bf36c4a39f830b02de4d76a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/figures/usb_pc.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..f43140dd6c0e428603ec44d3c94f1f904d8432ea --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/imxrt1052-fire-mini/quick-start.md @@ -0,0 +1,80 @@ +# 野火 I.MX RT1052 上手指南 + +## 简介 + +I.MX RT1052 是野火推出的一款高性能开发板,主控芯片是 NXP 推出的基于 ARM Cortex-M7 内核的跨界处理器,最高主频为 600Mhz,该开发板具有丰富的板载资源,可以充分发挥 I.MX RT1052 的芯片性能。 + +开发板外观如下图所示: + +![board](figures/board.png) + +该开发板常用 **板载资源** 如下: + +- MCU:I.MX RT1025DVL6A,主频 600MHz,512KB SRAM +- 外部 RAM:W9825G6KH(SDRAM,32MB) +- 外部 FLASH:W25Q256(SPI,32MB,存代码)、W29N01GVSIAA(NAND,128MB)、EEPROM(24c02) +- 常用外设: 1 个 RGB、2 个按键 +- 常用接口:USB 转串口、DHT11 接口、LCD 接口、USB SLAVE、USB HOST +- 调试接口,SWD 接口 + +开发板更多详细信息请参考 [野火官方淘宝店铺宝贝介绍](https://fire-stm32.taobao.com/)。 + +## 准备工作 + +野火 I.MX RT1052 板级支持包提供 MDK5 和 IAR 工程,并且支持 GCC 开发环境,下面以 MDK5 开发环境为例,介绍如何将示例程序运行起来。运行示例程序前需要做如下准备工作: + + 1. MDK 开发环境 + + 需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK安装](../keil/keil.md)。 + + 2. 拷贝下载算法 + + 野火的 I.MX RT1052 有自己的下载算法,点击[这里](http://www.firebbs.cn/thread-22513-1-4.html)获取。下载下来之后解压,将 `下载算法` 拷贝到 `Keil_v5/ARM/Flash` 目录下。 + + 3. 源码获取 + + + + [源码目录说明](../src_code_introduction/rtthread_dir.md) + + 4. 使用 USB 线连接开发板的 USB 转串口 到 PC 机。 + + ![连接到 PC](figures/usb_pc.jpg) + +## 运行第一个示例程序 + +### 配置工程 + +进入到 `rt-thread\bsp\imxrt1052-evk` 文件夹中,然后按照下面的步骤配置工程 + +- 在bsp下打开env工具 +- 输入 `menuconfig` 命令,RT1052 Board select (***)-->选择 `RT1050_FIRE` 开发板。 +- 输入 `scons --target=mdk5 -s` 生成需要的工程 + +### 编译下载 + +双击 project.uvprojx 文件,打开 MDK5 工程。 + +![工程目录](figures/dir.jpg) + +执行编译,编译完成后,点击下载按钮将固件下载至开发板,下载完成后,程序会自动开始运行,观察程序运行状况。 + +> 提示:工程默认配置使用 DAP 下载程序,在通过 DAP 仿真器连接开发板的基础上,点击下载按钮即可下载程序到开发板 + +![编译下载方法](figures/project.jpg) + +### 运行 + +使用串口工具(如:PuTTY)打开板子对应的串口(115200-8-1-N) ,复位设备后,可以看到 RT-Thread 的输出信息: + +```bash + \ | / +- RT - Thread Operating System + / | \ 3.1.1 build Nov 19 2018 + 2006 - 2018 Copyright by rt-thread team +msh > +``` + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/board_connect_pc.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/board_connect_pc.png new file mode 100644 index 0000000000000000000000000000000000000000..c207ce89fc30237cacf2de42b25d4fe09af1112c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/board_connect_pc.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/compile_first_example.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/compile_first_example.png new file mode 100644 index 0000000000000000000000000000000000000000..8e525de98cc5732cac248a09ab2a54cd28275c9f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/compile_first_example.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/iot.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/iot.png new file mode 100644 index 0000000000000000000000000000000000000000..9cbe0eb4e9c0277bd4e9a46a9ca6c5deb19843a5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/iot.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/red.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/red.jpg new file mode 100644 index 0000000000000000000000000000000000000000..59b977ee541f936f38ace4887198fa2e30d0b343 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/figures/red.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..44e69927d25c1a8d9eab2a1d60c632e6cf35bc8c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/iot_board/quick-start.md @@ -0,0 +1,68 @@ +# RT-Thread 潘多拉 STM32L475 上手指南 + +本文档将介绍潘多拉(即 IoT Board)开发板和 IoT Board SDK 的基本情况。使开发者熟悉 IoT Board SDK 的目录结构,并且可以将 SDK 提供的示例程序运行起来。 + +## IoT Board 开发板简介 + +IoT Board 是正点原子、 RT-Thread 联合推出的一款基于 ARM Cortex-M4 内核的开发板,最高主频为 80Mhz,该开发板具有丰富的板载资源,可以充分发挥 STM32L475 的芯片性能。 + +开发板外观及资源如下图: + +![IoT Board 开发板](figures/iot.png) + +该开发板常用 **板载资源** 如下: + +- MCU:STM32L475,主频 80MHz,512KB FLASH ,128KB SRAM +- 外部 FLASH:W25Q128(SPI,128Mbit) +- 常用外设 + * RGBLED:1个,(R接PE7 , G接PE8 , B接PE9) + * 按键:4个,WK_UP(兼具唤醒功能,PC13),KEY0(PD8),KEY1(PD9),KEY2(PD10) +- 常用接口:WIRELESS 模块接口 、TF 卡接口 、USB 串口 、USB OTG 接口 、耳机接口 +- 调试接口,板载的 ST-LINK 下载 + +开发板更多详细信息请参考 [正点原子官方品牌店宝贝介绍](https://eboard.taobao.com/index.htm)。 + +## IoT Board SDK 说明 + +IoT Board SDK 获取方式:[从 Github 获取](https://github.com/RT-Thread/IoT_Board) 或 [从 Gitee 获取](https://gitee.com/Armink/IoT_Board)。 SDK 的目录结构如下所示: + +| 名称 | 说明 | +| ---- | ---- | +| docs | 说明文档 | +| drivers | 开发板驱动文件 | +| examples | 示例程序 | +| libraries | 库文件 | +| rt-thread | rt-thread 源代码 | +| tools | 工具目录 | + +为开发板提供的示例程序存放在 examples 文件夹中,后面将以第一个示例程序为例,介绍如何将 SDK 提供的示例程序运行起来。 + +## 准备工作 + +IoT Board SDK 中的示例程序均提供 MDK 和 IAR 工程,并且支持 GCC 开发环境,下面以 MDK 开发环境为例,介绍如何将示例程序运行起来。运行示例程序前需要做如下准备工作: + +1. MDK 开发环境 + + 我们需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK安装](../keil/keil.md)。 + +2. 连接开发板的 ST-Link USB 口到 PC 机 + +![开发板连接PC机](figures/board_connect_pc.png) + +## 运行第一个示例程序 + +进入到 `examples\01_basic_led_blink` 文件夹中,双击 project.uvprojx 文件,打开 MDK5 工程,执行编译。编译完成后,点击下载按钮将固件下载至开发板,观察程序运行状况。 + +![编译第一个示例程序](figures/compile_first_example.png) + +按下复位按键重启开发板,观察开发板上 RBG-LED 的实际效果。正常运行后,红色 LED 会周期性闪烁,如下图所示: + +![RGB 红灯周期性闪烁](figures/red.jpg) + +IoT Board SDK 中其余例程的使用方法也是相同的,了解了运行例程的方法之后,就可以进行后面例程的运行和学习了。 + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 + +内核学习完成后,继续学习 IoT Board SDK 中的例程,教程参考 [《IoT Board 开发手册》](../../iot_board_tutorial.pdf) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/1.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/1.png new file mode 100644 index 0000000000000000000000000000000000000000..f2df5fc551b98cc43dc73b076a4373907c922f54 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/12.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/12.png new file mode 100644 index 0000000000000000000000000000000000000000..d56139166a06240800c481e59f8b3b51983a565d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/12.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/13.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/13.png new file mode 100644 index 0000000000000000000000000000000000000000..17aaf3f9d466cc7102ebde798a33dd847993dcbf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/13.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/2.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/2.png new file mode 100644 index 0000000000000000000000000000000000000000..b1e54f46d5b69f35e43eb3c95c14283a8c266c20 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/3.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/3.png new file mode 100644 index 0000000000000000000000000000000000000000..c3d131e7153cbdda1fa559e461090d2590ff3c82 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/4.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/4.png new file mode 100644 index 0000000000000000000000000000000000000000..9eb7156e295627cdf3590606c965bb16a755b90d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/figures/4.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/keil.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/keil.md new file mode 100644 index 0000000000000000000000000000000000000000..9fbdffb21e607daabb9d7ba2a4111b93c9b3eec8 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/keil/keil.md @@ -0,0 +1,36 @@ +# Keil MDK 安装 + +在运行 RT-Thread 操作系统前,我们需要安装 MDK-ARM 5.24(正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。这里采用了 16K 编译代码限制的评估版 5.24 版本,如果要解除 16K 编译代码限制,请购买 MDK-ARM 正式版。 + +先从 www.keil.com 官方网站下载 MDK-ARM 评估版: +[http://www.keil.com/download/](http://www.keil.com/download/) + +在下载时,需要填一些个人基本信息,请填写相应的完整信息,然后开始下载。下载完成后,鼠标双击运行,会出现如图所示的软件安装画面: + +![第一步](./figures/1.png) + +这是 MDK-ARM 的安装说明,点击 “Next>>” 进入下一画面,如图所示。 + +![第二步](./figures/2.png) + +在 “I agree to all the terms of the preceding License Agreement” 前的选择框中点击选择 “√”,并点击”Next >>” 进入下一步安装,如图所示: + +![第三步](./figures/3.png) + +点击 “Browse…” 选择 MDK-ARM 的安装目录或者直接在 “Destination Folder” 下的文本框中输入安装路径,这里我们这里我们默认 “C:/Keil” 即可,然后点击”“Next>>”进入下一步安装,如图所示: + +![第四步](./figures/4.png) + +在 “First Name” 后输入您的名字,“Last Name”后输入您的姓,“Company Name”后输入您的公司名称,“E-mail”后输入您的邮箱地址,然后点击 “Next>>” 进行安装,等待一段时间后,安装结束,出现如图所示画面: + +![第五步](./figures/12.png) + +图中的默认选择不需改动,直接点击 “Next” 进入如图所示画面。 + +![完成 MDK-ARM 安装](./figures/13.png) + +在这里可以点击 “Finish” 完成整个 MDK-ARM 软件的安装。 + +有了 MDK-ARM 利器,就可以轻松开始 RT-Thread 操作系统之旅,一起探索实时操作系统的奥秘。 + +> 提示:注:MDK-ARM 正式版是收费的,如果您希望能够编译出更大体积的二进制文件,请购买 MDK-ARM 正式版。RT-Thread 操作系统也支持自由软件基金会的 GNU GCC 编译器,这是一款开源的编译器,想要了解如何使用 GNU 的相关工具请参考 RT-Thread 网站上的相关文档。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/more.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/more.md new file mode 100644 index 0000000000000000000000000000000000000000..b157daceca60e373a65f5f4cc202489fd56b236b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/more.md @@ -0,0 +1,36 @@ +# 其他开发板 上手指南 + +## 新手推荐开发板列表及其 BSP + +推荐没有 RTOS 基础的新手选择以下一款开发板学习,按照文档中心的快速上手文档进行快速上手: + +| 厂商 | 开发板 | +| -------- | ------------------------------------------------------------ | +| 正点原子 | 【强烈推荐】[潘多拉(IoT Board)STM32L475](../quick-start/iot_board/quick-start.md) | +| | 【推荐】[nano STM32F103](../quick-start/stm32f103-atk-nano/quick-start.md) | +| | 【推荐】[探索者 STM32F407](../quick-start/stm32f407-atk-explorer/quick-start.md) | +| | 【推荐】[阿波罗 STM32F429](../quick-start/stm32f429-atk-apolo/quick-start.md) | +| 野火 | 【推荐】[霸道 STM32F103](../quick-start/stm32f103-fire-arbitrary/quick-start.md) | +| | 【推荐】[挑战者 STM32F429](../quick-start/stm32f429-fire-challenger/quick-start.md) | + +## 其他开发板及其 BSP + +RT-Thread 支持的全部 BSP 均在 github 的 `rt-thread` 源码中,位于 `bsp` 文件夹下,可以下载 [RT-Thread 源码](https://github.com/RT-Thread/rt-thread),按照相应 BSP 下的 README 文档进行快速上手。下列为部分开发板: + +| 厂商 | 开发板 | +| -------- | ---------------- | +| 正点原子 | [号令者 I.MX RT1052](https://github.com/RT-Thread/rt-thread/tree/master/bsp/imxrt1052-evk) | +| | [阿波罗 STM32F767](https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32f767-atk-apollo) | +| 野火 | [I.MX RT1052](https://github.com/RT-Thread/rt-thread/tree/master/bsp/imxrt1052-evk) | +| | [挑战者 STM32F767](https://github.com/RT-Thread/rt-thread/tree/master/bsp/stm32/stm32f767-fire-challenger) | +| NXP | [I.MX RT1050 EVK](https://github.com/RT-Thread/rt-thread/tree/master/bsp/imxrt/imxrt1050-evk) | +| | [NXP LPC54110 Board](https://github.com/RT-Thread/rt-thread/tree/master/bsp/lpc54114-lite) | +| | [LPC54608 系列评估板](https://github.com/RT-Thread/rt-thread/tree/master/bsp/lpc54608-LPCXpresso) | +| 兆易创新 | [GD32303E-EVAL](https://github.com/RT-Thread/rt-thread/tree/master/bsp/gd32303e-eval) | +| | [GD32450Z-EVAL](https://github.com/RT-Thread/rt-thread/tree/master/bsp/gd32450z-eval) | +| RISC-V | [SiFive HIFIVE1](https://github.com/RT-Thread/rt-thread/tree/master/bsp/hifive1) | +| 华芯微特 | [SWXT-LQ100-32102](https://github.com/RT-Thread/rt-thread/tree/master/bsp/swm320-lq100) | + +## STM32 系列 BSP 制作 + +RT-Thread 团队提供 [STM32 系列 BSP 制作教程](https://github.com/RT-Thread/rt-thread/blob/master/bsp/stm32/docs/STM32%E7%B3%BB%E5%88%97BSP%E5%88%B6%E4%BD%9C%E6%95%99%E7%A8%8B.md),若目标开发板是 STM32 系列,但在 RT-Thread 源码中没有找到相应的 BSP,那么可以根据这里提供的教程制作一个新的 BSP。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/echo-cat.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/echo-cat.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d3e56bb3d067f69bdfad392bf4a4493aea7f12 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/echo-cat.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/finsh-cmd.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/finsh-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..a5145c075c51d5da18c60f7b410a4d3d886ee293 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/finsh-cmd.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/finsh-thread.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/finsh-thread.png new file mode 100644 index 0000000000000000000000000000000000000000..5cd53699862825d53836c8ab5c9cd64325d88557 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/finsh-thread.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/mkfs-sd0.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/mkfs-sd0.png new file mode 100644 index 0000000000000000000000000000000000000000..4bcea502b01bca5a8ebf0e1b9344097bc505fd27 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/mkfs-sd0.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/qemu.bat.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/qemu.bat.png new file mode 100644 index 0000000000000000000000000000000000000000..8e6c4fc46921d8d08a31ee9f3bbe28bd0743b174 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/qemu.bat.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/qemu.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/qemu.png new file mode 100644 index 0000000000000000000000000000000000000000..bed1e4d3c75b28665c634b1e3993ede5107dbc72 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/qemu.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/scons.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/scons.jpg new file mode 100644 index 0000000000000000000000000000000000000000..74f650b51284f147859862e7cf46a71fdfaca116 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/figures/scons.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..e0999afd508faae5c326c72a2db87ded55ca58ae --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/qemu-vexpress-a9/quick-start.md @@ -0,0 +1,63 @@ +# 使用 QEMU 运行 RT-Thread + +## 简介 + +嵌入式软件开发离不开开发板,在没有物理开发板的情况下,可以使用 QEMU 等类似的虚拟机来模拟开发板。QEMU 是一个支持跨平台虚拟化的虚拟机,它可以虚拟很多开发板。为了方便大家在没有开发板的情况下体验 RT-Thread,RT-Thread 提供了 QEMU 模拟的 ARM vexpress A9 开发板的板级支持包 (BSP)。本文主要介绍在 Window 平台上使用 QEMU 运行 RT-Thread 工程 + +## 准备工作 + + 1. 获取 ENV 工具 + + [官网下载](https://www.rt-thread.org/page/download.html),下载完成后,按照 Add_Env_To_Right-click_Menu 图片中的步骤将 Env 添加到右键启动菜单 + + 2. 源码获取 + + + + [源码目录说明](../src_code_introduction/rtthread_dir.md) + +> 提示:注意:ENV 工具和源码都不能放在中文或者有空格的路径下,并且 win7 第一次打开 ENV 工具推荐使用管理员权限打开。 + +## 运行第一个示例程序 + +### 编译运行 + +进入到 `rt-thread\bsp\qemu-vexpress-a9` 文件夹中,然后右键打开 ENV 工具,输入 `scons` 命令执行编译 + +![编译工程](figures/scons.jpg) + +编译完成后,输入 `qemu.bat` 运行程序。 + +![运行工程](figures/qemu.bat.png) + +![虚拟机](figures/qemu.png) + +### 体验 RT-Thread Finsh 控制台 + +RT-Thread 支持 Finsh,用户可以在命令行模式使用命令操作。输入 `help` 或按 `Tab` 键可以查看所有支持的命令。如下图所示,左边为命令,右边为命令描述。 + +![查看 Finsh 命令 ](figures/finsh-cmd.png) + +如下图所示,输入 `list_thread` 命令可以查看当前运行的线程,以及线程状态和堆栈大小等信息。输入`list_timer`可以查看定时器的状态。 + +![查看系统线程情况 ](figures/finsh-thread.png) + +### 体验 RT-Thread 文件系统 + +输入 `list_device` 可以查看注册到系统的所有设备。如下面图片所示可以看到虚拟的 sd 卡 “sd0” 设备,接下来我们可以使用 `mkfs sd0` 命令格式化 sd 卡,执行该命令会将 sd 卡格式化成 [FatFS 文件系统](http://elm-chan.org/fsw/ff/00index_e.html)。 + +![格式化 sd 卡](figures/mkfs-sd0.png) + +第一次格式化 sd 卡后文件系统不会马上装载上,第二次启动才会被正确装载。我们关闭 qemu 窗口退出虚拟机,然后在 Env 命令行界面输入 `qemu.bat` 重新启动虚拟机及工程,输入 `ls` 命令可以看到新增了 Directory 目录,文件系统已经装载上,然后可以使用 RT-Thread 提供的其他命令体验文件系统。 + +![文件系统其他命令](figures/echo-cat.png) + + +## 参考资料 + +* [《Env 用户手册》](https://www.rt-thread.org/document/site/development-guide/rtthread-tool-manual/env/env-user-manual/) + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 + diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/bsp_pic00.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/bsp_pic00.png new file mode 100644 index 0000000000000000000000000000000000000000..2d7959b51c7d27c1caf72989ed8d4d447cd39257 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/bsp_pic00.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/explorer.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/explorer.png new file mode 100644 index 0000000000000000000000000000000000000000..ad2e204a17f77ce94e7845bf202b82fce8ba4781 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/explorer.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/menu_pic.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/menu_pic.png new file mode 100644 index 0000000000000000000000000000000000000000..fe85b457303d1aef2669ee98a9b7b1ba700c1450 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/menu_pic.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/stm32.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/stm32.png new file mode 100644 index 0000000000000000000000000000000000000000..c09b85bb05b7eb1e7377b8e1b188a54bd60b81d0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/stm32.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/stm32_0.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/stm32_0.png new file mode 100644 index 0000000000000000000000000000000000000000..99f3a109a7ba1a4e00c9cb293bdcb4645bba87cc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/figures/stm32_0.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/rtthread_dir.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/rtthread_dir.md new file mode 100644 index 0000000000000000000000000000000000000000..2cae576cdd80c3ba9ffea0d5bbc60e3c62d4953b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/src_code_introduction/rtthread_dir.md @@ -0,0 +1,37 @@ +## rt-thread 源码的目录文件说明 + +### rt-thread 目录 + +下图是打开 rt-thread 源码的目录,下表是该目录的简单说明。 + +![源码目录](figures/menu_pic.png) + +| 目录名 | 描述 | +|--- | --- | +|bsp| Board support package,RT-Thread 板级支持包
(IAR/MDK 工程在 BSP 目录下的具体的 BSP 中) | +|components| RT-Thread 的各个组件目录 | +| documentation | 一些说明文件,如代码风格说明 | +|include | RT-Thread 内核的头文件 | +|libcpu | 各类芯片的移植代码,此处包含了 STM32 的移植文件 | +|src | RT-Thread 内核的源文件 | +|tools | RT-Thread 命令构建工具的脚本文件 | + +### BSP 目录 + +打开 BSP 目录,里面包含 RT-Thread 已经支持的所有 bsp + +![bsp目录](figures/bsp_pic00.png) + + +### 打开一个 bsp + +如打开bsp stm32f407-atk-explorer,下表是该目录的简单说明。 + +![bsp-探索者](figures/explorer.png) + +| 目录名 | 描述 | +|--- | --- | +|applications| RT-Thread 应用程序 | +| board | 与开发板相关的配置文件 | +| project.eww
project.uvproj
project.uvprojx |iar 的工程文件
keil4 工程文件
keil5 工程文件 | + diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/board.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/board.png new file mode 100644 index 0000000000000000000000000000000000000000..470751381ed3e311060eb0dbecf882153b1df9b6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/board.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/dir.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/dir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..eb410b80c168ba11243bedfaf6c3cf0b543c1a52 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/dir.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/project.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..21bd5331599d00c30b41da90c0643f4ba99a25a4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/run.gif b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/run.gif new file mode 100644 index 0000000000000000000000000000000000000000..a683d55d7318f014556d54f356681e250484b3ed Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/run.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/usb_pc.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/usb_pc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7f50cb1ece45d0b628ac0b25c547f218846bdda6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/figures/usb_pc.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..ba77f60299984594c98c9476656fe0c9ea8d0f64 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-atk-nano/quick-start.md @@ -0,0 +1,71 @@ +# 正点原子 nano STM32F103 上手指南 + +## 简介 + +正点原子nano STM32F103 是正点原子推出的一款基于 ARM Cortex-M3 内核的开发板,最高主频为 72Mhz,该开发板具有丰富的板载资源,可以充分发挥 STM32F103 的芯片性能。 + +开发板外观如下图所示: + +![board](figures/board.png) + +该开发板常用 **板载资源** 如下: + +- MCU:STM32F103RBT6,主频 72MHz,128KB FLASH ,20KB RAM +- 外部 FLASH:W25Q16(SPI,2MB)、EEPROM(24c02) +- 常用外设 + * LED:8个,(红色,PC0-PC7) + * 按键:4个,KEY_UP(兼具唤醒功能,PA0),KEY0(PC8),KEY1(PC9),KEY2(PD2) +- 常用接口:USB 转串口、DS18B20/DHT11 接口 、USB SLAVE +- 调试接口,板载的 ST-LINK SWD 下载 + +开发板更多详细信息请参考 [正点原子官方品牌店宝贝介绍](https://eboard.taobao.com/index.htm)。 + +## 准备工作 + +ATK-NANO STM32F103 板级支持包提供 MDK4、MDK5 和 IAR 工程,并且支持 GCC 开发环境,下面以 MDK5 开发环境为例,介绍如何将示例程序运行起来。运行示例程序前需要做如下准备工作: + + 1. MDK 开发环境 + + 需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK安装](../keil/keil.md)。 + + 2. 源码获取 + + + + [源码目录说明](../src_code_introduction/rtthread_dir.md) + + 3. 使用 USB 线连接开发板的 USB 转串口 到 PC 机。 + + ![连接到 PC](figures/usb_pc.jpg) + +## 运行第一个示例程序 + +### 编译下载 + +进入到 `rt-thread\bsp\stm32\stm32F429-atk-explorer` 文件夹中,双击 project.uvprojx 文件,打开 MDK5 工程。 + +![工程目录](figures/dir.jpg) + +执行编译,编译完成后,点击下载按钮将固件下载至开发板,下载完成后,程序会自动开始运行,观察程序运行状况。 + +![编译下载方法](figures/project.jpg) + +### 运行 + +如没有自动运行,按下复位按键重启开发板,观察开发板上 LED 的实际效果。正常运行后,LED 灯会周期性闪烁,如下图所示: + +![run](figures/run.gif) + +使用串口工具(如:PuTTY)打开板子对应的串口(115200-8-1-N) ,复位设备后,可以看到 RT-Thread 的输出信息: + +```bash + \ | / +- RT - Thread Operating System + / | \ 3.1.1 build Nov 19 2018 + 2006 - 2018 Copyright by rt-thread team +msh > +``` + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/board.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/board.png new file mode 100644 index 0000000000000000000000000000000000000000..5d5140579a98474945e8fb31af058c45400a92d7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/board.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/dir.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/dir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..986b479ac36659ed426ee42b08cda1d760014d4e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/dir.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/project.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e5ec99abad5a9d1252bb547b2bc2cb7b2c5c969 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/run.gif b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/run.gif new file mode 100644 index 0000000000000000000000000000000000000000..c8dd7eecec59ca51a54da4f5930ab637d42ffcd9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/run.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/usb_pc.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/usb_pc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..53aa90c5f8439dfc3f131def59c906bbc8769db7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/figures/usb_pc.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..dc14cf818cf6efe6bfa099e23044fad12f0dbea4 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-fire-arbitrary/quick-start.md @@ -0,0 +1,78 @@ +# 野火霸道 STM32F103 上手指南 + +## 简介 + +霸道 STM32F103 是野火推出的一款基于 ARM Cortex-M3 内核的开发板,最高主频为 72Mhz,该开发板具有丰富的板载资源,可以充分发挥 STM32F103 的芯片性能。 + +开发板外观如下图所示: + +![board](figures/board.png) + + + +该开发板常用 **板载资源** 如下: + +- MCU:STM32F103ZET6,主频 72MHz,512KB FLASH ,64KB RAM +- 外部 RAM:IS62WV51216BLL(SRAM,1MB) +- 外部 FLASH:W25Q64(SPI,8MB) +- 常用外设 + * LED:1个RGB灯;2个普通LED,D4(蓝色,PF7),D5(蓝色,PF8) + * 按键:2个,K1(兼具唤醒功能,PA0),K2(PC13) +- 常用接口:USB 转串口、SD 卡接口、以太网接口、LCD 接口 +- 调试接口,标准 JTAG/SWD + +开发板更多详细信息请参考 [野火官方淘宝店铺宝贝介绍](https://fire-stm32.taobao.com/index.htm)。 + +## 准备工作 + +野火霸道 STM32F103 板级支持包提供 MDK4、MDK5 和 IAR 工程,并且支持 GCC 开发环境,下面以 MDK5 开发环境为例,介绍如何将示例程序运行起来。运行示例程序前需要做如下准备工作: + + 1. MDK 开发环境 + + 需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK安装](../keil/keil.md)。 + + 2. 源码获取 + + + + [源码目录说明](../src_code_introduction/rtthread_dir.md) + + 3. 使用 USB 线连接开发板的 USB 转串口 到 PC 机。 + + ![连接到 PC](figures/usb_pc.jpg) + +## 运行第一个示例程序 + +### 编译下载 + +进入到 `rt-thread\bsp\stm32\stm32f103-fire-arbitrary` 文件夹中,双击 project.uvprojx 文件,打开 MDK5 工程。 + +![工程目录](figures/dir.jpg) + +执行编译,编译完成后,点击下载按钮将固件下载至开发板,下载完成后,程序会自动开始运行,观察程序运行状况。 + +> 提示:工程默认配置使用 JLink 下载程序,在通过 JLink 连接开发板的基础上,点击下载按钮即可下载程序到开发板 + +![编译下载方法](figures/project.jpg) + +### 运行 + +如没有自动运行,按下复位按键重启开发板,观察开发板上 LED 的实际效果。正常运行后,LED 灯会周期性闪烁,如下图所示: + +![run](figures/run.gif) + +连接开发板对应串口到 PC , 在串口工具里打开相应的串口(115200-8-1-N) ,复位设备后,可以看到 RT-Thread 的输出信息: + +> 提示:注:野火一键下载电路和终端工具冲突,在使用终端工具如:PuTTy、XShell 时,会出现系统不能启动的问题,推荐使用串口调试助手如:sscom + +```bash + \ | / +- RT - Thread Operating System + / | \ 3.1.1 build Nov 19 2018 + 2006 - 2018 Copyright by rt-thread team +msh > +``` + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/10.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/10.png new file mode 100644 index 0000000000000000000000000000000000000000..92c771a9b971a5eb1912fe38c6061b419e5c026f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/10.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/11.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/11.png new file mode 100644 index 0000000000000000000000000000000000000000..d7e995732c7f0bec96e1546581bdc47d8708d41e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/11.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/14.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/14.png new file mode 100644 index 0000000000000000000000000000000000000000..6a20af1021f34baae91a695e1afb2e5c48feb6b9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/14.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/5.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/5.png new file mode 100644 index 0000000000000000000000000000000000000000..90794700ca0f42d6951bbc9dce0af0493f2b991f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/6.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/6.png new file mode 100644 index 0000000000000000000000000000000000000000..d53031df23425e74d552913fb80dafcee796a916 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/6.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/7.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/7.png new file mode 100644 index 0000000000000000000000000000000000000000..de0207c0c956fda512d29bb6389bb14ca1755acf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/7.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/8.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/8.png new file mode 100644 index 0000000000000000000000000000000000000000..ceed8014077d817f1e936028d52abd70092691ab Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/8.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/9.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/9.png new file mode 100644 index 0000000000000000000000000000000000000000..73d821e705dc4a11d6891ff3389ce2fcb588f15a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/9.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/compile.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/compile.jpg new file mode 100644 index 0000000000000000000000000000000000000000..962323d256b04b00d372b1df187c6686e0d4602f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/compile.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/debug.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/debug.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0a0cfc63f3ef8a969b5d4199fedb2f35f9214be9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/debug.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/project1.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/project1.png new file mode 100644 index 0000000000000000000000000000000000000000..1301a7aa849da77ae7228b8ff28969eb34fd648a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/figures/project1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z new file mode 100644 index 0000000000000000000000000000000000000000..c36f62e88ca0f278731bd167405c02dc670d7a70 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/stm32f103-simulator.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/stm32f103-simulator.md new file mode 100644 index 0000000000000000000000000000000000000000..40ab70803ddcb4f684f1019984eac2d26c273ec6 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/stm32f103-simulator.md @@ -0,0 +1,222 @@ +# Keil 模拟器 STM32F103 上手指南 + +一般嵌入式操作系统因为它的特殊性,往往和硬件平台密切相关连,具体的嵌入式操作系统往往只能在特定的硬件上运行。对于刚接触 RT-Thread 操作系统的读者并不容易马上就获得一个和 RT-Thread 操作系统相配套的硬件模块,但随着计算机技术的发展,我们可以采用软件方式来模拟一个能够运行 RT-Thread 操作系统的硬件模块,这就是 ARM 公司的 MDK-ARM 仿真模拟环境。 + +MDK-ARM(MDK-ARM Microcontroller Development Kit)软件是一套完整的集成开发环境(IDE),它出自 ARM 公司,包括了针对 ARM 芯片(ARM7,ARM9,Cortex-M 系列,Cortex-R 系列等)的高效 C/C++ 编译器;针对各类 ARM 设备、评估板的工程向导,工程管理;用于软件模拟运行硬件平台的模拟器;以及与市面上常见的如 ST-Link,JLink 等在线仿真器相连接以配合调试目标板的调试器。MDK-ARM 软件中的软件仿真模拟器,采用完全软件模拟方式解释执行 ARM 的机器指令,并实现外围的一些外设逻辑,从而构成一套完整的虚拟硬件环境,使得用户能够不借助真实的硬件平台就能够在电脑上执行相应的目标程序。 + +MDK-ARM 集成开发环境因为其完全的 STM32F103 软件仿真环境,也让我们有机会在不使用真实硬件环境的情况下直接在电脑上运行目标代码。这套软件仿真模拟器能够完整地虚拟出 ARM Cortex-M3 的各种运行模式、外设,如中断异常,时钟定时器,串口等,这几乎和真实的硬件环境完全一致。实践也证明,本文使用到的这份 RT-Thread 入门例程,在编译成二进制代码后,不仅能够在模拟器上软件模拟运行,也能够不需要修改地在真实硬件平台上正常运行。 + +下面我们将选择 MDK-ARM 集成开发环境作为目标硬件平台来观察 RT-Thread 操作系统是如何运行的。 + +## 准备工作 + +MDK 开发环境:需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK安装](../keil/keil.md)。 + +使用 STM32F103 软件仿真 ,还需要下载安装 STM32F103 pack 文件,如果在 MDK 中下载较慢,也可以[点击此处下载](https://pan.baidu.com/s/1MKzo24RTxiJGcjTJBHxcqA),下载后双击安装即可。 + +## 初识 RT-Thread + +作为一个操作系统,RT-Thread 的代码规模怎么样呢?在弄清楚这些之前,我们先要做的就是获得与本文相对应的 RT-Thread 的例子,这份例子可以从以下链接获得: + +[RT-Thread Simulator 例程](https://gitee.com/rtthread/docs-online/raw/master/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f103-simulator/rtthread_simulator_v0.1.0.7z) + +这个例子是一个压缩包文件,将它解压,我们这里解压到 D:/。解压完成后的目录结构如下图所示: + +![rtthread_simulator_v0.1.0 代码目录](./figures/7.png) + +各个目录所包含的文件类型的描述如下表所示: + +| 目录名 | 描述 | +| --- | --- | +| applications| RT-Thread 应用程序。| +| rt-thread | RT-Thread 的源文件。 | +| - components| RT-Thread 的各个组件目录。| +| - include | RT-Thread 内核的头文件。| +| - libcpu | 各类芯片的移植代码,此处包含了 STM32 的移植文件。| +| - src | RT-Thread 内核的源文件。| +| - tools | RT-Thread 命令构建工具的脚本文件。| +| drivers | RT-Thread 的驱动,不同平台的底层驱动具体实现。| +| Libraries | ST 的 STM32 固件库文件。| +| kernel-sample-0.1.0 | RT-Thread 的内核例程。| + +在目录下,有一个 project.uvprojx 文件,它是本文内容所引述的例程中的一个 MDK5 工程文件,双击 “project.uvprojx” 图标,打开此工程文件: + +![打开第一个 RT-Thread 工程](./figures/5.png) + +在工程主窗口的左侧 `Project` 栏里可以看到该工程的文件列表,这些文件被分别存放到如下几个组内,分别是: + +| 目录组 | 描述 | +| ------------- | ------------------------------------------------------------ | +| Applications | 对应的目录为 rtthread_simulator_v0.1.0/applications,它用于存放用户应用代码。 | +| Drivers | 对应的目录为 rtthread_simulator_v0.1.0/drivers,它用于存放 RT-Thread 底层的驱动代码。 | +| STM32_HAL | 对应的目录为 rtthread_simulator_v0.1.0/Libraries/CMSIS/Device/ST/STM32F1xx,它用于存放 STM32 的固件库文件。 | +| kernel-sample | 对应的目录为 rtthread_simulator_v0.1.0/kernel-sample-0.1.0,它用于存放 RT-Thread 的内核例程。 | +| Kernel | 对应的目录为 rtthread_simulator_v0.1.0/src,它用于存放 RT-Thread 内核核心代码。 | +| CORTEX-M3 | 对应的目录为 rtthread_simulator_v0.1.0/rt-thread/libcpu,它用于存放 ARM Cortex-M3 移植代码。 | +| DeviceDrivers | 对应的目录为 rtthread_simulator_v0.1.0/rt-thread/components/drivers,它用于存放 RT-Thread 驱动框架源码。 | +| finsh | 对应的目录为 rtthread_simulator_v0.1.0/rt-thread/components/finsh,它用于存放 RT-Thread 命令行 finsh 命令行组件。 | + +现在我们点击一下窗口上方工具栏中的按钮![img](./figures/compile.jpg),对该工程进行编译,如图所示: + +![编译工程](./figures/project1.png) + +编译的结果显示在窗口下方的 “Build” 栏中,没什么意外的话,最后一行会显示“0 Error(s), * Warning(s).”,即无任何错误和警告。 + +注:由于工程中包含的内核例程代码较多,若使用的是 MDK 试用版本,则会有 16KB 限制,此时可以只保留某个目标例程的代码(例如内核例程只保留一个 thread_sample.c 参与编译),将其他不用的例程先从工程中移除,然后编译。 + +在编译完 RT-Thread/STM32 后,我们可以通过 MDK-ARM 的模拟器来仿真运行 RT-Thread。点击窗口右上方的按钮![img](./figures/debug.jpg)或直接按 “Ctrl+F5” 进入仿真界面,再按 F5 开始运行,然后点击该图工具栏中的按钮或者选择菜单栏中的 “View→Serial Windows→UART#1”,打开串口 1 窗口,可以看到串口的输出只显示了 RT-Thread 的 LOGO,这是因为用户代码是空的,其模拟运行的结果如图所示: + +![模拟运行 RT-Thread](./figures/10.png) + +> 提示:我们可以通过输入Tab键或者 `help + 回车 ` 输出当前系统所支持的所有命令,如下图所示。 + +![模拟运行 RT-Thread](./figures/6.png) + +## 系统启动代码 + +一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。以 MDK-ARM 为例,MDK-ARM 的用户程序入口为 main() 函数,位于 main.c 文件中。系统启动后先从汇编代码 startup_stm32f103xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统功能初始化,最后进入用户程序入口 main()。 + +下面我们来看看在 components.c 中定义的这段代码: + +```c +//components.c 中定义 +/* re-define main function */ +int $Sub$$main(void) +{ + rt_hw_interrupt_disable(); + rtthread_startup(); + return 0; +} +``` + +在这里 `$Sub$$main` 函数仅仅调用了 `rtthread_startup()` 函数。RT-Thread 支持多种平台和多种编译器,而 `rtthread_startup()` 函数是 RT-Thread 规定的统一入口点,所以 `$Sub$$main` 函数只需调用 `rtthread_startup()` 函数即可。例如采用 `GNU` `GCC` 编译器编译的 `RT-Thread`,就是直接从汇编启动代码部分跳转到 `rtthread_startup()` 函数中,并开始第一个 C 代码的执行的。在 `components.c` 的代码中找到 `rtthread_startup()` 函数,我们将可以看到 RT-Thread 的启动流程: + +```c +int rtthread_startup(void) +{ + rt_hw_interrupt_disable(); + + /* board level initalization + * NOTE: please initialize heap inside board initialization. + */ + rt_hw_board_init(); + + /* show RT-Thread version */ + rt_show_version(); + + /* timer system initialization */ + rt_system_timer_init(); + + /* scheduler system initialization */ + rt_system_scheduler_init(); + +#ifdef RT_USING_SIGNALS + /* signal system initialization */ + rt_system_signal_init(); +#endif + + /* create init_thread */ + rt_application_init(); + + /* timer thread initialization */ + rt_system_timer_thread_init(); + + /* idle thread initialization */ + rt_thread_idle_init(); + + /* start scheduler */ + rt_system_scheduler_start(); + + /* never reach here */ + return 0; +} +``` + +**这部分启动代码,大致可以分为四个部分**: + +- 初始化与系统相关的硬件; + +- 初始化系统内核对象,例如定时器,调度器; + +- 初始化系统设备,这个主要是为 RT-Thread 的设备框架做的初始化; + +- 初始化各个应用线程,并启动调度器。 + +## 用户入口代码 + +上面的启动代码基本上可以说都是和 RT-Thread 系统相关的,那么用户如何加入自己的应用程序的初始化代码呢?RT-Thread 将 main 函数作为了用户代码入口,只需要在 main 函数里添加自己的代码即可。 + +```c +int main(void) +{ + /* user app entry */ + return 0; +} +``` + +提示:
+ 为了在进入 main 程序之前,完成系统功能初始化,可以使用 `$sub$$` 和 `$super$$` 函数标识符在进入主程序之前调用另外一个例程,这样可以让用户不用去管 main() 之前的系统初始化操作。详见[ARM® Compiler v5.06 for µVision® armlink User Guide](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0377g/pge1362065967698.html) +
+ +## 跑马灯的例子 + +对于从事电子方面开发的技术工程师来说,跑马灯大概是最简单的例子,就类似于每种编程语言中程序员接触的第一个程序 Hello World 一样,所以这个例子就从跑马灯开始。让它定时地对 LED 进行更新(关或灭)。 + +我们 UART#1 中输入 msh 命令:led 然后回车就可以运行起来了,如图所示: + +![模拟运行跑马灯](./figures/11.png) + +**跑马灯例子** + +```c +/* + * 程序清单:跑马灯例程 + * + * 跑马灯大概是最简单的例子,就类似于每种编程语言中程序员接触的第一个程序 + * Hello World 一样,所以这个例子就从跑马灯开始。创建一个线程,让它定时地对 + * LED 进行更新(关或灭) + */ + +int led(void) +{ + rt_uint8_t count; + + rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); + + for(count = 0 ; count < 10 ;count++) + { + rt_pin_write(LED_PIN, PIN_HIGH); + rt_kprintf("led on, count : %d\r\n", count); + rt_thread_mdelay(500); + + rt_pin_write(LED_PIN, PIN_LOW); + rt_kprintf("led off\r\n"); + rt_thread_mdelay(500); + } + return 0; +} +MSH_CMD_EXPORT(led, RT-Thread first led sample); +``` + +## 其他例子 + +其他更多的内核示例可以从 kernel-sample-0.1.0 目录下找到。 + +![更多内核示例](./figures/14.png) + +## 常见问题 + +* 出现如下编译错误 + +``` +rt-thread\src\kservice.c(823): error: #929: incorrect use of vaarg fieldwidth = aarg(args, int); +rt-thread\src\kservice.c(842): error: #929: incorrect use of vaarg precision = aarg(args, int); +……… +``` + +原因:这类问题基本上都是因为安装了 ADS 导致,ADS 与 keil共存,va_start 所在的头文件指向了 ADS 的文件夹。 + +解决办法: + +* 删除 ADS 环境变量 +* 卸载 ADS 和 keil,重启电脑,重装keil diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/board.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/board.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8562ae6604128b239de0a6aeea03edc2bcb63e54 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/board.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/dir.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/dir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f109b92ebec3ec81116738e58bb3176c7b8e2e98 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/dir.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/project.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4f0bd604d67bf8c8987cb1452a3fff88b3fa4149 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/run.gif b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/run.gif new file mode 100644 index 0000000000000000000000000000000000000000..d5c9eeded10ff2141f830e060df5737b5e236613 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/run.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/usb_pc.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/usb_pc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3aee473f1fac09ad105b2af34d4276377f4a90ca Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/figures/usb_pc.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..c6ea00dbe7d8cfd19c9210d39a7a841761ff0258 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f407-atk-explorer/quick-start.md @@ -0,0 +1,76 @@ +# 正点原子探索者 STM32F407 上手指南 + +## 简介 + +探索者 STM32F407 是正点原子推出的一款基于 ARM Cortex-M4 内核的开发板,最高主频为 168Mhz,该开发板具有丰富的板载资源,可以充分发挥 STM32F407 的芯片性能。 + +开发板外观如下图所示: + +![board](figures/board.jpg) + +该开发板常用 **板载资源** 如下: + + - MCU:STM32F407ZGT6,主频 168MHz,1024KB FLASH ,192KB RAM +- 外部 RAM:IS62WV51216(1MB) +- 外部 FLASH:W25Q128(SPI,16MB) +- 常用外设 + * LED:2个,DS0(红色,PB1),DS1(绿色,PB0) + * 按键,4个,KEY_UP(兼具唤醒功能,PIN:0),K0(PIN:68),K1(PIN:67),K2(PIN:66) +- 常用接口:USB 转串口、SD 卡接口、以太网接口、LCD 接口、USB SLAVE、USB HOST +- 调试接口,标准 JTAG/SWD + +开发板更多详细信息请参考 [正点原子官方品牌店宝贝介绍](https://eboard.taobao.com/index.htm)。 + +## 准备工作 + +正点原子探索者 STM32F407 板级支持包提供 MDK4、MDK5 和 IAR 工程,并且支持 GCC 开发环境,下面以 MDK5 开发环境为例,介绍如何将示例程序运行起来。运行示例程序前需要做如下准备工作: + + 1. MDK 开发环境 + + 需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK 安装](https://www.rt-thread.org/document/site/tutorial/quick-start/quick-start/)。 + + 2. 源码获取 + + + + [源码目录说明](../src_code_introduction/rtthread_dir.md) + + 3. 使用 Mini USB 线连接开发板的 USB 转串口 到 PC 机。 + + ![连接到 PC](figures/usb_pc.jpg) + +## 运行第一个示例程序 + +### 编译下载 + +进入到 `rt-thread\bsp\stm32\stm32f407-atk-explorer` 文件夹中,双击 project.uvprojx 文件,打开 MDK5 工程。 + +![工程目录](figures/dir.jpg) + +执行编译,编译完成后,点击下载按钮将固件下载至开发板,下载完成后,程序会自动开始运行,观察程序运行状况。 + +> 提示:工程默认配置使用 JLink 下载程序,在通过 JLink 连接开发板的基础上,点击下载按钮即可下载程序到开发板 + +![编译下载方法](figures/project.jpg) + +### 运行 + +如没有自动运行,按下复位按键重启开发板,观察开发板上 LED 的实际效果。正常运行后,LED 灯会周期性闪烁,如下图所示: + +![run](figures/run.gif) + +连接开发板对应串口到 PC , 在串口工具里打开相应的串口(115200-8-1-N) ,复位设备后,可以看到 RT-Thread 的输出信息: + +> 提示:注:正点原子一键下载电路和终端工具冲突,在使用终端工具如:PuTTy、XShell 时,会出现系统不能启动的问题,推荐使用串口调试助手如:sscom + +```bash + \ | / +- RT - Thread Operating System + / | \ 3.1.1 build Nov 19 2018 + 2006 - 2018 Copyright by rt-thread team +msh > +``` + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/board.png b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/board.png new file mode 100644 index 0000000000000000000000000000000000000000..f0f7522efb094cd4d001b8b69382e7d859de9d0a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/board.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/dir.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/dir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3cd61b3996926a10a6de1da7f46e6347c74bba35 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/dir.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/project.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e5ec99abad5a9d1252bb547b2bc2cb7b2c5c969 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/run.gif b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/run.gif new file mode 100644 index 0000000000000000000000000000000000000000..f8be85bca1a2a7dead5d92cf971a14facf7be144 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/run.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/usb_pc.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/usb_pc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..39233d6cf0e11be574a704bf9c0f80d5ab6a1d78 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/figures/usb_pc.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..5f54f55c1f4aac1bcc6b0fca6dc518a987b65c5f --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-atk-apolo/quick-start.md @@ -0,0 +1,78 @@ +# 正点原子阿波罗 STM32F429 上手指南 + +## 简介 + +阿波罗 STM32F429 是正点原子推出的一款基于 ARM Cortex-M4 内核的开发板,最高主频为 180Mhz,该开发板具有丰富的板载资源,可以充分发挥 STM32F429 的芯片性能。 + +开发板外观如下图所示: + +![board](figures/board.png) + +该开发板常用 **板载资源** 如下: + + - MCU:STM32F429IGT6,主频 180MHz,1024KB FLASH ,256KB RAM +- 外部 RAM:W9825G6KH(SDRAM,32MB) +- 外部 FLASH:W25Q256(SPI,32MB)、MT29F4G08(NAND,512MB) +- 常用外设 + * LED:2个,DS0(红色,PB1),DS1(绿色,PB0) + * 按键:4个,KEY_UP(兼具唤醒功能,PA0),K0(PH3),K1(PH2),K2(PC13) +- 常用接口:USB 转串口、SD 卡接口、以太网接口、LCD 接口 +- 调试接口,标准 JTAG/SWD + +开发板更多详细信息请参考 [正点原子官方品牌店宝贝介绍](https://eboard.taobao.com/index.htm)。 + +## 准备工作 + +正点原子阿波罗 STM32F429 板级支持包提供 MDK4、MDK5 和 IAR 工程,并且支持 GCC 开发环境,下面以 MDK5 开发环境为例,介绍如何将示例程序运行起来。运行示例程序前需要做如下准备工作: + + 1. MDK 开发环境 + + 需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK安装](../keil/keil.md)。 + + + 2. 源码获取 + + + + [源码目录说明](../src_code_introduction/rtthread_dir.md) + + + 3. 使用 USB 线连接开发板的 USB 转串口 到 PC 机。 + + ![连接到 PC](figures/usb_pc.jpg) + +## 运行第一个示例程序 + +### 编译下载 + +进入到 `rt-thread\bsp\stm32\stm32F429-atk-explorer` 文件夹中,双击 project.uvprojx 文件,打开 MDK5 工程。 + +![工程目录](figures/dir.jpg) + +执行编译,编译完成后,点击下载按钮将固件下载至开发板,下载完成后,程序会自动开始运行,观察程序运行状况。 + +> 提示:工程默认配置使用 JLink 下载程序,在通过 JLink 连接开发板的基础上,点击下载按钮即可下载程序到开发板 + +![编译下载方法](figures/project.jpg) + +### 运行 + +如没有自动运行,按下复位按键重启开发板,观察开发板上 LED 的实际效果。正常运行后,LED 灯会周期性闪烁,如下图所示: + +![run](figures/run.gif) + +连接开发板对应串口到 PC , 在串口工具里打开相应的串口(115200-8-1-N) ,复位设备后,可以看到 RT-Thread 的输出信息: + +> 提示:注:正点原子一键下载电路与终端工具冲突,在使用终端工具如:PuTTy、XShell 时,会出现系统不能启动的问题,推荐使用串口调试助手如:sscom + +```bash + \ | / +- RT - Thread Operating System + / | \ 3.1.1 build Nov 19 2018 + 2006 - 2018 Copyright by rt-thread team +msh > +``` + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/board.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/board.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8dbe504ba5b55415ca613c42248f98db9f7bb8b2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/board.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/dir.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/dir.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0246545abc11d15de58fb71528fa3c0a916a961f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/dir.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/project.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e5ec99abad5a9d1252bb547b2bc2cb7b2c5c969 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/run.gif b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/run.gif new file mode 100644 index 0000000000000000000000000000000000000000..cf8ea091377c649528cc6b3d8dc3728f295fed45 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/run.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/usb_pc.jpg b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/usb_pc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e7ff2e373e6a0722ead5909707583c82f1d49640 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/figures/usb_pc.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/quick-start.md b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/quick-start.md new file mode 100644 index 0000000000000000000000000000000000000000..bfc2e520a60ff8e20eaa50cdb4daec716b36b101 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/quick-start/stm32f429-fire-challenger/quick-start.md @@ -0,0 +1,76 @@ +# 野火挑战者 STM32F429 上手指南 + +## 简介 + +挑战者 STM32F429 是野火推出的一款基于 ARM Cortex-M4 内核的开发板,最高主频为 180Mhz,该开发板具有丰富的板载资源,可以充分发挥 STM32F429 的芯片性能。 + +开发板外观如下图所示: + +![board](figures/board.jpg) + +该开发板常用 **板载资源** 如下: + +- MCU:STM32F429IGT6,主频 180MHz,1024KB FLASH ,256KB RAM +- 外部 RAM:IS42S16400J(SDRAM,8MB) +- 外部 FLASH:W25Q128(SPI,16MB) +- 常用外设 + * LED:RGB灯 + * 按键:2个,K1(兼具唤醒功能,PA0),K2(PC13) +- 常用接口:USB 转串口、SD 卡接口、以太网接口、LCD 接口 +- 调试接口,标准 JTAG/SWD + +开发板更多详细信息请参考 [野火官方淘宝店铺宝贝介绍](https://fire-stm32.taobao.com/index.htm)。 + +## 准备工作 + +野火挑战者 STM32F429 板级支持包提供 MDK4、MDK5 和 IAR 工程,并且支持 GCC 开发环境,下面以 MDK5 开发环境为例,介绍如何将示例程序运行起来。运行示例程序前需要做如下准备工作: + + 1. MDK 开发环境 + + 需要安装 MDK-ARM 5.24 (正式版或评估版,5.14 版本及以上版本均可),这个版本也是当前比较新的版本,它能够提供相对比较完善的调试功能。安装方法可以参考 [Keil MDK安装](../keil/keil.md)。 + + 2. 源码获取 + + + + [源码目录说明](../src_code_introduction/rtthread_dir.md) + + 3. 使用 USB 线连接开发板的 USB 转串口 到 PC 机。 + + ![连接到 PC](figures/usb_pc.jpg) + +## 运行第一个示例程序 + +### 编译下载 + +进入到 `rt-thread\bsp\stm32\stm32f429-fire-challenger` 文件夹中,双击 project.uvprojx 文件,打开 MDK5 工程。 + +![工程目录](figures/dir.jpg) + +执行编译,编译完成后,点击下载按钮将固件下载至开发板,下载完成后,程序会自动开始运行,观察程序运行状况。 + +> 提示:工程默认配置使用 JLink 下载程序,在通过 JLink 连接开发板的基础上,点击下载按钮即可下载程序到开发板 + +![编译下载方法](figures/project.jpg) + +### 运行 + +如没有自动运行,按下复位按键重启开发板,观察开发板上 LED 的实际效果。正常运行后,LED 灯会周期性闪烁,如下图所示: + +![run](figures/run.gif) + +连接开发板对应串口到 PC , 在串口工具里打开相应的串口(115200-8-1-N) ,复位设备后,可以看到 RT-Thread 的输出信息: + +> 提示:注:野火一键下载电路和终端工具冲突,在使用终端工具如:PuTTy、XShell 时,会出现系统不能启动的问题,推荐使用串口调试助手如:sscom + +```bash + \ | / +- RT - Thread Operating System + / | \ 3.1.1 build Nov 19 2018 + 2006 - 2018 Copyright by rt-thread team +msh > +``` + +## 继续学习 + +已完成 RT-Thread 快速上手!点击这里进行 [内核学习](../../kernel/kernel-video.md) 。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/README.md b/rt-thread-version/rt-thread-standard/tutorial/smart-car/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d1696bf3543bbeb74bfc50741328c0f33b903ea9 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/smart-car/README.md @@ -0,0 +1,37 @@ +# 智能车连载教程简介 + +本连载教程一辆能够用 ROS 控制的带摄像头的小车,用 ROS 发布图像数据,对获取到的图像进行处理,例如目标检测。 + +智能车系统框图如下: + +![img](ros-camera-car/figures/04-structure.png) + +实物图: + +![img](ros-camera-car/figures/04-car.jpg) + +## 教程目录 + +1、[RT-Thread 卷积神经网络(CNN) 手写体识别 (MNIST)](cnn-mnist/cnn-mnist.md) + +​ 神经网络理论:神经网络图像相关理论 + +​ 训练卷积神经网络模型:用 Keras 训练手写体识别模型 + +​ 运行卷积神经网络模型:RT-Thread 解析并加载 onnx 模型(protobuf格式) + +2、 [Darknet 训练目标检测模型](darknet-yolov2/darknet-yolov2.md) :Darknet 训练一个小黄人检测模型 + +3、 [RT-Thread 连接 ROS](ros-connect/ros-connect.md) :RT-Thread 的 rosserial 软件包和 ROS 建立连接 + +4、 [RT-Thread 连接 ROS 控制摄像头小车](ros-camera-car/ros-camera-car.md) :ROS 发布图像信息 + +5、 [RT-Thread 连接 RPLidar A1 激光雷达](rplidar-connect/rplidar-connect.md) :使用 RT-Thread 的 RPLidar 软件包 + +6、[RT-Thread 搭配 ROS 实现目标检测小车](object-detection/object-detection.md) :Darknet ROS 利用 ROS 小车发布的图像信息运行 Yolo 神经网络做目标检测 + +## 效果 + +下面这张图有两个视频流,左边的是没有处理的实时图像,右边是运行了目标检测的结果: + +![img](object-detection/figures/06-03.png) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/cnn-mnist.md b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/cnn-mnist.md new file mode 100644 index 0000000000000000000000000000000000000000..2986ab5ed9d0abb70ceeced76af3d7449878c294 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/cnn-mnist.md @@ -0,0 +1,1018 @@ +# RT-Thread 卷积神经网络(CNN) 手写体识别 (MNIST) + +## 引言 + +这一部分会说明这个文档会包含哪些内容,以及不会包含哪些内容,因为人工智能,机器学习,监督学习,神经网络,无论哪一个都是非常大的话题都,覆盖到可能就成一本书了,所以这篇文档只会包含与 RT-Thread 上面加载 MNIST 手写体识别模型相关的部分。 + +当然,在每一部分的最后我也会给出参考文献,参考文献是个非常重要的部分,一方面它可以补充我没有介绍到的部分,另一方面也可以提供一些支撑,因为现在网上文档太多了,但是并不是每一篇文档都没有任何错误,比如大家觉得我列出的一些公式结论来的有些突兀的话,可以在参考文献里找到更详细的推导证明。 + +这篇文档可能还是会非常长,因为机器学习并不是纯软件开发,简单地调用库函数 API,需要有一定的理论支撑,如果完全不介绍理论部分,可能就不知道为什么模型要这样设计,模型出了问题应该怎样改善。不过文档如果写太长大家可能很难有耐心看完,特别是理论部分会有很多公式,但是机器学习确实又对 **理论基础** 和 **编程能力** 都有一些要求,相信坚持看下去还是会有很多收获的,我也尽可能把理论和应用都介绍清楚。 + +之后一篇文档就基本是纯实际应用了,不会有太多理论内容了:**用 Darknet 机器学习框架训练一个目标检测模型**。 + +- 如果对机器学习理论比较清楚,可以直接看第二部分 Keras 训练模型 +- 如果对 Keras 机器学习框架也比较熟悉了,可以直接跳转到第三部分 RT-Thread 加载 onnx 模型 +- 如果对 RT-Thread 和 onnx 模型都很熟悉了,那我们可以一起交流下如何在嵌入式设备上高效实现机器学习算法 + +这篇文章假定大家都已经会用 RT-Thread 的 env 工具下载软件包,并且生成项目上传固件到 stm32 上,毕竟这篇文章重点在于加载 onnx 通用机器学习模型,关于 RT-Thread 的教程大家可以在官网上找一找。 + +------ + +首先,简单介绍一下上面提到的各个话题的范围 (Domain),人工智能 (Artifitial Intelligence) 是最大的话题,如果用一张图来说明的话: + +![Domain of AI](figures/01-a98ee523a803cb33.png) + +然后机器学习 (Machine Learning) 就是这篇文档的主题了,但是 **机器学习** 依旧是一个非常大的话题: + +![Domain of Machine Learning](figures/01-3e7e7a6e0ecc6b5f.png) + +这里简单介绍一下上面提到的三种类型: + +**监督学习 (Supervised Learning)**: 这应当是应用最多的领域了,例如人脸识别,我提前先给你大量的图片,然后告诉你当中哪些包含了人脸,哪些不包含,你从我给的照片中总结出人脸的特征,这就是训练过程。最后我再提供一些从来没有见过的图片,如果算法训练得好的话,就能很好的区分一张图片中是否包含人脸。所以监督学习最大的特点就是有训练集,告诉模型什么是对的,什么是错的。 + +**非监督学习 (Unsupervised Learning)**: 例如网上购物的推荐系统,模型会对我的浏览记录进行分类,然后自动向我推荐相关的商品。非监督学习最大的特点就是没有一个标准答案,比如水杯既可以分类为日用品,也可以分类为礼品,都没有问题。 + +**强化学习 (Reinforcement Learnong)**: 强化学习应当是机器学习当中最吸引人的一个部分了,例如 [Gym](https://gym.openai.com/) 上就有很多训练电脑自己玩游戏最后拿高分的例子。强化学习主要就是通过试错 (Action),找到能让自己收益最大的方法,这也是为什么很多都例子都是电脑玩游戏。 + +所以文档后面介绍的都是关于 **监督学习**,因为手写体识别需要有一些训练集告诉我这些图像实际上应该是什么数字,不过监督学习的方法也有很多,主要有分类和回归两大类: + +![Supervised Learning](figures/01-b0977a236e95f427.png) + +**分类 (Classification):** 例如手写体识别,这类问题的特点在于最后的结果是离散的,最后分类的数字只能是 0, 1, 2, 3 而不会是 1.414, 1.732 这样的小数。 + +**回归 (Regression):** 例如经典的房价预测,这类问题得到的结果是连续的,例如房价是会连续变化的,有无限多种可能,不像手写体识别那样只有 0-9 这 10 种类别。 + +这样看来,接下来介绍的手写体识别是一个 **分类问题**。但是做分类算法也非常多,这篇文章要介绍的是应用非常多也相对成熟的 **神经网络** (Neural Network)。 + +![Neural Network](figures/01-6c42a7145053244b.png) + + + +**人工神经网络 (Artifitial Neural Network)**:这是个比较通用的方法,可以应用在各个领域做数据拟合,但是像图像和语音也有各自更适合的算法。 + +**卷积神经网络 (Convolutional Neural Network)**:主要应用在图像领域,后面也会详细介绍。 + +**循环神经网络 (Recurrent Neural Network)**:比较适用于像声音这样的序列输入,因此在语言识别领域应用比较多。 + +最后总结一下,这篇文档介绍的是**人工智能**下面发展比较快的**机器学习**分支,然后解决的是机器学习**监督学习**下面的**分类问题**,用的是**神经网络**里的**卷积神经网络** (CNN) 方法。 + +------ + +## 1 神经网络相关理论 + +这一部分主要介绍神经网络的整个运行流程,怎么准备训练集,什么是训练,为什么要训练,怎么进行训练,以及训练之后得到了什么。 + +### 1.1 线性回归 (Linear Regression) + +#### 1.1.1 回归模型 + +要做机器学习训练预测,我们首先得知道自己训练的模型是什么样的,还是以最经典的线性回归模型为例,后面的人工神经网络 (ANN) 其实可以看做多个线性回归组合。那么什么是线性回归模型呢? + +比如下面图上这些散点,希望能找到一条直线进行拟合,线性回归拟合的模型就是: + +![1575440223406](figures/01-1575440223406.png) + +这样如果以后有一个点 x = 3,不在图上这些点覆盖的区域,我们也可以通过训练好的线性回归模型预测出对应的 y。 + +不过上面的公式通常使用另外一种表示方法,最终的预测值也就是 y 通常用 hθ (hypothesis) 表示,而它的下标 θ 代表不同训练参数也就是 k, b。这样模型就成了: + +![1575440247208](figures/01-1575440247208.png) + +所以 θ0 对应着 b,θ1 对应着k。但是这样表示模型还不够通用,比如 x 可能不是一个一维向量,例如经典的房价预测,我们要知道房价,可能需要房子大小,房间数等很多因素,因此把上面的用更通用的方法表示: + +![1575440279312](figures/01-1575440279312.png) + +这就是线性回归的模型了,只要会向量乘法,上面的公式计算起来还是挺轻松的。 + +顺便一提,θ 需要一个转置 θT,是因为我们通常都习惯使用列向量。上面这个公式和 y=kx+b 其实是一样的,只是换了一种表示方法而已,不过这种表示方法就更加通用,而且也更加简洁优美了: + +![1575440310847](figures/01-1575440310847.png) + +#### 1.1.2 评价指标 + +为了让上面的模型能够很好的拟合这些散点,我们的目标就是改变模型参数 θ0 和 θ1,也就是这条直线的斜率和截距,让它能很好的反应散点的趋势,下面的动画就很直观的反应了训练过程。 + +![img](figures/01-regressionfit.gif) + +可以看到,一开始是一条几乎水平的直线,但是慢慢地它的斜率和截距就移动到一个比较好的位置,那么问题来了,我们要怎么评价这条直线当前的位置满不满足我们的需求呢? + +一个很直接的想法就是求出所有散点实际值 y 和我们模型的测试值 hθ 相差的绝对值,这个评价指标我们就称为损失函数 J(θ) (cost function): + +![1575440353458](figures/01-1575440353458.png) + +函数右边之所以除以了2是为了求倒数的时候更加方便,因为如果右边的公式求导,上面的平方就会得到一个2,刚好和分母里的2抵消了。 + +这样我们就有了评价指标了,损失函数计算出来的值越小越好,这样就知道当前的模型是不时能很好地满足需求,下一步就是告诉模型该如何往更好的方向优化了,这就是训练 (Training) 过程。 + +#### 1.1.3 模型训练 + +为了让模型的参数 θ 能够往更好的方向运动,也就是很自然的想法就是向下坡的方向走,比如上面的损失函数其实是个双曲线,我们只要沿着下坡的方向走总能走到函数的最低点: + +![Gradiant Descent](figures/01-e307909709475176.gif) + +那么什么是"下坡"的方向呢?其实就是导数的方向,从上面的动画也可以看出来,黑点一直是沿着切线方向逐渐走到最低点的,如果我们对损失函数求导,也就是对 J(θ) 求导: + +![1575440402057](figures/01-1575440402057.png) + +我们现在知道 θ 应该往哪个方向走了,那每一次应该走多远呢?就像上面的动画那样,黑点就算知道了运动方向,每一次运动多少也是需要确定的。这个每次运动的多少称之为学习速率 α (learning rate),这样我们就知道参数每次应该向哪个方向运动多少了: + +![1575440423002](figures/01-1575440423002.png) + +这种训练方法就是很有名的 **梯度下降法**(Gradient Descent),当然现在也有很多改进的训练方法例如 Adam,其实原理都差不多,这里就不做过多的介绍了。 + +#### 1.1.4 总结 + +机器学习的流程总结出来就是,我们先要设计一个模型,然后定义一个评价指标称之为损失函数,这样我们就知道怎么去判断模型的好坏,接下来就是用一种训练方法,让模型参数能朝着能让损失函数减少的方向运动,当损失函数几乎不再减少的时候,我们就可以认为训练结束了。最终训练得到的就是模型的参数,使用训练好的模型我们就可以对其他的数据进行预测了。 + +顺便一提,上面的线性回归其实是有标准理论解的,也就是说不需要通过训练过程,一步得到最优权值,我们称之为 **Normal Equation**: + +![1575440443614](figures/01-1575440443614.png) + +那么,明明有一步到位的理论解,我们为什么还需要一步一步的训练呢?因为上面的公式里有矩阵的逆运算,当矩阵规模比较小时,对矩阵求逆运算量并不大,但是一旦矩阵的规模提升上去,用现有的计算能力求逆是几乎不可能了,所以这个时候就需要用梯度下降这样的训练方法一步一步的逼近最优解。 + +### 1.2 非线性回归 (Logistic Regression) + +我们回到手写体识别的例子,上面介绍的线性回归最后得到的是一个连续的数值,但是手写体识别最后的目标是得到一个离散的数值,也就是 0-9,那么这要怎么做到呢? + +![1575440465033](figures/01-1575440465033.png) + +这个就是上一部分的模型,其实很简单,只需要在最后的结果再加一个 sigmoid 函数,把最终得到的结果限制在 0-1 就可以了。 + +![img](figures/01-15468.png) + +就像上面图中的公式那样,sigmoid 函数就是: + +![1575440567328](figures/01-1575440567328.png) + +如果把它应用到线性回归的模型,我们就得到了一个非线性回归模型,也就是 Logistic Regression: + +![1575440583616](figures/01-1575440583616.png) + +这样就可以确保我们最后得到的结果肯定是在 0-1 之间了,然后我们可以定义如果最后的结果大于 0.5 就是 1,小于 0.5 就是 0,这样一个连续的输出就被离散了。 + +### 1.3 人工神经网络 (ANN) + +现在我们介绍了连续的线性回归模型 Linear Regression,和离散的非线性回归模型 Logistic Regression,模型都非常简单,写在纸上也就不过几厘米的长度。那么这么简单的模型到底是怎么组合成非常好用的神经网络的呢? + +其实上面的模型可以看做是只有一层的神经网络,我们输入 x 经过一次计算就得到输出 hθ 了: + +![1575440607155](figures/01-1575440607155.png) + +如果我们不那么快得到计算结果,而是在中间再插入一层呢?就得到了有一层隐藏层的神经网络了。 + +![img](figures/01-3449d9b4852fb4ec.png) + +上面这张图里,我们用 a 代表 **激活函数** (activation function) 的输出,激活函数也就是上一部分提到的 sigmoid 函数,为了将输出限制在 0-1,如果不这么做,很有可能经过几层神经网络的计算,输出值就爆炸到一个很大很大的数了。当然除了 **sigmoid** 函数外,激活函数还有很多,例如下一部分在卷积神经网络里非常常用的 **Relu**。 + +另外,我们用带括号的上标代表神经网络的层数。例如 a(1) 代表第一层神经网络输出。当然,第一层就是输入层,并不需要经过任何计算,所以可以看到图上的 a(1)=x,第一层的激活函数输出直接就是我们的输入 x。但是,θ(1) 不是代表第一层的参数,而是第一层与第二层之间的参数,毕竟参数存在于两层网络之间的计算过程。 + +于是,我们可以总结一下上面的神经网络结构: + +![1575440668825](figures/01-1575440668825.png) + +如果我们设置最后的输出层节点是 10 个,那就刚好可以用来表示 0-9 这 10 个数字了。 + +如果我们再多增加几个隐藏层,是不是看起来就有点像是互相连接的神经元了? + +![img](figures/01-234567.png) + +如果我们再深入一点 Go Deeper (论文里作者提到,他做深度学习的灵感其实源自于盗梦空间) + +![img](figures/01-389246109.png) + +这样我们就得到一个深度神经网络了: + +![img](figures/01-624x172.png) + +如果你想知道,具体应当选多少层隐藏层,每个隐藏层应该选几个节点,这就跟你从哪里来,要到哪里去一样,是神经网络的终极问题了。 + +最后,神经网络的训练方法是用的 **反向传播** (Back Propagation),如果感兴趣可以在[这里](https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/)找到更加详细的介绍。 + +### 1.4 卷积神经网络 (CNN) + +终于到了后面会用到的卷积神经网络了,从前面的介绍可以看到,其实神经网络的模型非常简单,用到的数学知识也不多,只需要知道矩阵乘法,函数求导就可以了,而深度神经网络只不过是反复地进行矩阵乘法和激活函数的运算: + +![1575440789862](figures/01-1575440789862.png) + +这样重复相同的运算显得有些单调了,下面要介绍的卷积神经网络就引入了更多更有意思的操作,主要有: + +- Cov2D +- Maxpooling +- Relu +- Dropout +- Flatten +- Dense +- Softmax + +接下来就对这些算子逐一介绍。 + +#### 1.4.1 Conv2D + +首先图像领域的神经网络最大的特点就是引入了卷积操作,虽然这个名字看起来有点神秘,其实卷积运算非常简单。 + +这里说明一下为什么要引入卷积运算,尽管前面的矩阵乘法其实已经可以解决很多问题了,但是一旦到了图像领域,对一个 1920*1080 的图像做乘法,就是一个 [1, 2,073,600] 的矩阵了,这个运算量已经不算小了,而用卷积操作,计算量就会大大缩减;另一方面,如果把一个二维的图像压缩成一个一维的向量,其实就丢失了像素点在上下左右方向相互关联的信息,例如一个像素点和周围的颜色通常比较相近,这些信息很多时候是很重要的图像信息。 + +介绍完了卷积操作的优势,那么到底什么是卷积运算呢?其实卷积就是简单的加减乘除,我们需要一幅图像,然后是一个卷积核 (Kernel): + +![img](figures/01-14257755.png) + +上面这张图像经过一个 3x3 的卷积核操作,就很好地把图像的边缘提取出来了,下面这个动画就很清晰地介绍了矩阵运算: + +![img](figures/01-123.gif) + +上面动画用到的卷积核是一个 3x3 的矩阵: + +![1575440982783](figures/01-1575440982783.png) + +如果我们把动画暂停一下: + +![conv](figures/01-acc75794b641d2c3.png) + +可以看到卷积操作实际上就是把卷积核在图像上按照行列扫描一遍,把对应位置的数字相乘,然后求和,例如上面的左上角的卷积结果 4 是这么计算得到的 (这里用 ∗ 代表卷积): + +![1575441028184](figures/01-1575441028184.png) + +当然上面的计算过程用等号连接是不严谨的,不过可以方便地说明卷积的计算过程。可以看到,卷积的计算量相比全连接的神经网络是非常小的,而且保留了图像在二维空间的关联性,所以在图像领域应用地非常多。 + +卷积操作非常好用,但是卷积后图像大小变小了,例如上面的 5x5 矩阵经过一个 3x3 的卷积核运算最后得到的是一个 3x3 的矩阵,所以有的时候为了保持图像大小不变,会在图像周围一圈用 0 填充,这个操作称之为 **padding**。 + +但是 padding 也没有办法完全保证图像大小不变,因为上面动画的卷积核每次都只向一个方向运动一格,如果每次运动 2 格,那么 5x5 的图像经过 3x3 的卷积就成了 2x2 的矩阵了,卷积核每次移动的步数我们称之为 **stride**。 + +下面是一幅图像经过卷积运算后得到的图像大小计算公式: + +![1575441047302](figures/01-1575441047302.png) + +比如上面图像宽度 W = 5,卷积核大小 F = 3,没有使用 padding 所以 P = 0,每次移动步数 S = 1: + +![1575441067421](figures/01-1575441067421.png) + +这里说明一下,上面的计算都是针对一个卷积核而言的,实际上一层卷积层可能有多个卷积核,而且实际上很多 CNN 模型也是卷积核随着层数往后,越来越多的。 + +#### 1.4.2 Maxpooling + +上面提到卷积可以通过 padding 保持图像大小不变,但是很多时候我们希望能随着模型的推进,逐渐减小图像大小,因为最后的输出例如手写体识别,实际上只有 0-9 这 10 个数字,但是图像的输入却是 1920x1080,所以 maxpooling 就是为了减少图像尺寸的。 + +其实这个计算比卷积要简单多了: + +![img](figures/01-456789.png) + +比如左边 4x4 的输入,经过 2x2 的 maxpooling,其实就是把左上角 2x2 的方块取最大值: + +![1575441107835](figures/01-1575441107835.png) + +所以这样一个 4x4 的矩阵经过 2x2 的 maxpooling 一下尺寸就缩小了一半,这也就是 maxpooling 的目的了 + +#### 1.4.3 Relu + +之前介绍 sigmoid 函数的时候,提到过它是激活函数的一种,而 Relu 就是另一种在图像领域更为常用的激活函数, Relu 相比 sigmoid 就非常简单了: + +![img](figures/01-relu.png) + +其实就是当数字小于0的时候取0,大于0的时候保持不变。就这么简单。 + +#### 1.4.4 Dropout + +到这里一共介绍了3个算子,conv2d, maxpooling,relu,每一个的运算都非常简单,但是 Dropout 甚至更简单,连计算都没有,于是在这个部分一个公式都没有。 + +之前没有提到模型过拟合的问题,因为神经网络模型在训练过程中,很有可能出现模型对自己提供的训练集拟合非常好,但是一旦碰到没有见过的数据,就完全预测不出正确的结果了,这种时候就是出现了过拟合。 + +那么,怎么解决过拟合问题呢?Dropout 就是一种非常简单粗暴的方法,从已经训练好的参数当中,随机挑一些出来丢弃掉重置为 0,这也是为什么它的名字叫 Dropout,就是随机丢掉一些参数。 + +这是个简单到不可思议的方法,但是却意外地好用,例如仅仅是在 maxpooling 后随机丢弃掉 60% 训练好的参数,就可以很好地解决过拟合问题。 + +#### 1.4.5 Flatten + +依旧是卷积神经网络的简单风格,这里也不会有公式。 + +Flatten 就是像字面意思那样,把一个2维的矩阵压平,比如这样一个矩阵: + +![1575441163190](figures/01-1575441163190.png) + +就是这么简单 。 + +#### 1.4.6 Dense + +Dense 其实前面已经介绍过了,就是矩阵的乘法,然后加法: + +![1575441184688](figures/01-1575441184688.png) + +所以卷积部分其实确实不需要知道太多的数学运算。 + +#### 1.4.7 Softmax + +这一个就是最后一个算子了,比如我们要做手写体识别,那么最后的输出就会是 0-9,这将是一个 1x10 的矩阵,例如下面的预测结果 (实际上是一行,为了方便显示写成两行了): + +![1575441415846](figures/01-1575441415846.png) + +上面的 1x10 的矩阵可以看到第 7 个数 0.753 远远大于其他几个数 (下标我们从 0 开始),所以我们可以知道当前预测结果是 7。 **所以 softmax 会作为模型的输出层输出 10 个数字,每个数字分别代表图片是 0-9 的概率,我们取最大的一个概率就是预测结果了**。 + +另一方面,上面 10 个数相加刚好是 1,所以其实每个数就代表一个概率,模型认为这个数是1个概率是 0.000498,是 2 的概率是 0.000027,以此类推,这么直观方便的结果就是用 softmax 计算得到的。 + +![1575441452485](figures/01-1575441452485.png) + +比如有两个数 [1, 2] 经过 softmax 运算: + +![1575441474390](figures/01-1575441474390.png) + +最后得到的两个数字就是 [0.269, 0.731]。 + +到这里第一部分卷积神经网络相关的算子就终于介绍完了,第二部分部分就会介绍实际如何用 Keras (Tensorflow) 机器学框架训练一个手写体识别模型,最后第三部分就是介绍如何利用把生成的模型导入到 stm32 上面运行。 + +### 1.5 参考文献 + +- [斯坦福经典机器学习入门视频](https://www.coursera.org/learn/machine-learning) +- [线性回归](https://towardsdatascience.com/introduction-to-linear-regression-and-polynomial-regression-f8adc96f31cb) +- [反向传播](https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/) +- [卷积运算](https://ujjwalkarn.me/2016/08/11/intuitive-explanation-convnets/) + +------ + +## 2 训练卷积神经网络模型 + +这里会介绍如何训练图像领域应用非常广的卷积神经网络 (Convolutional Neural Network) + +这一部分应当不会涉及到很多理论了,其实用 Keras 训练模型写起代码来非常简单,如果发现不太清楚代码为什么要这么写,可以看看上一部分对应的算子。 + +### 2.1 MNIST 手写体训练集 + +首先我们需要介绍一下训练集,毕竟在训练之前我们得先看看训练集长什么样子。 + +这就是手写体识别数据库的[官网](http://yann.lecun.com/exdb/mnist/)了,风格比较跨世纪: + +![img](figures/01-059f548df4ba65fe.png) + +这个图上就是全球各地大家用不同方法做手写体识别得到的准确率汇总,可以看到我用红圈画出来的部分,用前面介绍的 Logistic Regression (Linear Classifier) 做手写体识别效果是最差的,所以我们之后要使用的是卷积神经网络 CNN (之后我就都用 CNN 简写了)。 + +在网站的下面给出了训练集的二进制格式定义: + +![img](figures/01-9168acb2a010f32f.png) + +当然,这是指自己从网站下载原始训练集,从当中提取图片才需要了解的,**我们使用 tensorflow 不需要自己解析数据集**。 + +### 2.2 开发环境搭建 + +首先介绍一下机器学习的开发环境,现在主流开发环境都是 **Python**,但是我们也不是一个裸 Python 打开记事本就直接开始写代码了,实际上数据科学家用的最多的开发环境是 **Anaconda**,里面集成了 **Python** 和 **R** 开发环境。 + +我们从官网下载 Anaconda 安装包 根据自己的操作系统选择就可以了,因为安装过程基本就是单纯地下一步、下一步,所以这里就不介绍了。 + +安装好之后,我们打开 **Anaconda Prompt**: + +Anaconda 其实是有图形界面的,叫 **Anaconda Navigator**,但是这里以控制台为主,因为图形界面其实用起来反而比较麻烦,因为控制台一行命令就解决了更加快速方便。 + +![img](figures/01-a9911df75880c8bb.png) + +然后我们输入: + +``` +# 如果你是用的 CPU +conda create -n tensorflow-cpu tensorflow + +# 如果你是用的 GPU (NVIDIA 显卡会自动安装显卡驱动,CUDA,cudnn,简直方便) +conda create -n tensorflow-gpu tensorflow-gpu +``` + +这样开发环境就搭好了,我们激活一下当前的开发环境: + +``` +# 如果你是用的 CPU +conda activate tensorflow-cpu + +# 如果你是用的 GPU +conda activate tensorflow-gpu +``` + +这里 **激活** 开发环境是指,在 Anaconda 下我们可以有多个开发环境,比如如果你想对比一下 CPU 和 GPU 计算速度的差距,可以同时安装 2 个开发环境,然后根据需要切换到 CPU 开发环境,或者 GPU 开发环境,非常方便。如果不用 Anaconda 而是一个 Python 裸奔的话,要么使用 **VirtualEnv**,要么就只能反复安装卸载不同的开发环境了。 + +接下来就可以启动我们写代码的位置了: + +``` +# 这里的软件包 anaconda 可能已经都装好了,以防万一再确认一遍 +pip install numpy scipy sklearn pandas pillow matplotlib keras onnx jupyter -i https://pypi.tuna.tsinghua.edu.cn/simple + +# 启动编辑器 +jupyter notebook +``` + +这样就会自动打开浏览器,看到我们的开发环境了,在这里新建一个 notebook: + +![notebook](figures/01-9a9a68dc53c1e1a9.png) + +可以把它重命名为 mnist-keras: + +![img](figures/01-b016dceca991ef5d.png) + +接下来就可以开始训练模型了。 + +### 2.3 Keras 训练模型 + +#### 2.3.1 导入库函数 + +我们首先导入需要的库函数,在 In[1] 后面的方框内写入代码: + +``` +#coding:utf-8 +from tensorflow.examples.tutorials.mnist import input_data + +import numpy as np +np.set_printoptions(suppress=True) + +import matplotlib.pyplot as plt +%matplotlib inline +``` + +这些代码写进去之后就是这个样子,后面我就不一一截图了。 + +![img](figures/01-d36a1a7e53d2f9ae.png) + +如果你对上面 **导入库** 这一个注释比较感兴趣,可以在一个把光标移到到一个输入框,按下 Esc 再按下 m,这个输入框就从 **代码段** 变成 **注释段** 了,Anaconda 也是代码、注释、输出可以同时保存所以用起来体验非常好。更多的快捷键可以在菜单栏的 Help --> Keyboard Shortcuts 找到。 + +把光标移动到刚刚输入的代码块,按下键盘的 Shift + Enter 就自动执行了,并且会自动在下面增加一行代码输入框,导入库根据电脑的配置可能需要一些时间,耐心等待一下。 + +#### 2.3.2 下载 MNIST 训练集 + +在代码块输入一行代码: + +``` +mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) #MNIST数据输入 +``` + +这样就会自动下载数据集了,国内可能下载速度比较慢,可以从这个地址下载 [MNIST数据集](https://wuhanshare-1252843818.cos.ap-guangzhou.myqcloud.com/MNIST_data.zip) 然后解压到 Anaconda Prompt 启动 Jupyter Notebook 的位置就不用等它慢慢下载了,默认是 C:/Users/你的用户名/ + +![img](figures/01-3cd670c5ceb2433e.png) + +#### 2.3.3 看一看 MNIST 数据 + +我们把下载下来的数据集分成训练集和测试集,训练集用来训练模型,测试集用来检测最后模型预测的正确率: + +``` +X_train = mnist.train.images +y_train = mnist.train.labels +X_test = mnist.test.images +y_test = mnist.test.labels + +# 输入图像大小是 28x28 大小 +X_train = X_train.reshape([-1, 28, 28, 1]) +X_test = X_test.reshape([-1, 28, 28, 1]) +``` + +如果比较好奇这是一个怎样的图片,可以看看它长什么样子,比如我们看看训练集的第一张图片 + +``` +plt.imshow(X_train[0].reshape((28, 28)), cmap='gray') +``` + +![img](figures/01-40694b28faaf6777.png) + +也可以看看第二张图片: + +``` +plt.imshow(X_train[1].reshape((28, 28)), cmap='gray') +``` + +![img](figures/01-63edc86a94db9ecd.png) + +下面就正式开始建立训练模型了。 + +#### 2.3.4 构建模型 + +同样先导入一下 Keras 库: + +``` +# Importing the Keras libraries and packages +# Importing the Keras libraries and packages +from keras.models import Sequential +from keras.layers import Dense +from keras.layers import Conv2D +from keras.layers import MaxPooling2D +from keras.layers import Dropout +from keras.layers import Flatten +``` + +接下来就可以建模了,可以看到这里的模型和上一部分介绍的 CNN 算子是一模一样的,熟悉的 conv2d, maxpooling, dropout, flatten, dense, softmax, adam, 如果忘了它们是什么意思,随时可以切换到上一个部分回忆一下。 + +``` +def build_classifier(): + + classifier = Sequential() + + # 第一层 Conv2D,激活函数 Relu + classifier.add(Conv2D(filters = 2, kernel_size = 3, strides = 1, padding = "SAME", activation = "relu", input_shape = (28, 28, 1))) + + # 第二层 Maxpooling, 使用保持图像大小的 padding + classifier.add(MaxPooling2D(pool_size=(2, 2), padding='SAME')) + + # 第三层 Dropout + classifier.add(Dropout(0.5)) + + # 第四层 Conv2D,激活函数 Relu + classifier.add(Conv2D(filters = 2, kernel_size = 3, strides = 1, padding = "SAME", activation = "relu")) + + # 第五层 Maxpoling,使用保持图像大小的 padding + classifier.add(MaxPooling2D(pool_size=(2, 2), padding='SAME')) + + # 第六层 Dropout + classifier.add(Dropout(0.5)) + + # 第七层 Flatten + classifier.add(Flatten()) + + # 第八层 Dense + classifier.add(Dense(kernel_initializer="uniform", units = 4)) + + # 第九层 softmax 输出 + classifier.add(Dense(kernel_initializer="uniform", units = 10, activation="softmax")) + + # 使用 adam 训练 + classifier.compile(optimizer = 'adam', loss = 'categorical_crossentropy', metrics=['accuracy']) + + return classifier +``` + +这样模型就建完了。 + +代码真的就是一层只要一行,但是一定要知道自己的模型为什么要这么建,比如为什么 maxpooling 要放在 con2d 之后,为什么要加 dropout,最后的 softmax 到底是在干什么,可不可以不要? + +我们可以看看自己建立的模型长什么样: + +``` +classifier = build_classifier() +classifier.summary() +``` + +可以看到确实是和上一部分的理论是一一对应的。 + +``` +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +conv2d_1 (Conv2D) (None, 28, 28, 2) 20 +_________________________________________________________________ +max_pooling2d_1 (MaxPooling2 (None, 14, 14, 2) 0 +_________________________________________________________________ +dropout_1 (Dropout) (None, 14, 14, 2) 0 +_________________________________________________________________ +conv2d_2 (Conv2D) (None, 14, 14, 2) 38 +_________________________________________________________________ +max_pooling2d_2 (MaxPooling2 (None, 7, 7, 2) 0 +_________________________________________________________________ +dropout_2 (Dropout) (None, 7, 7, 2) 0 +_________________________________________________________________ +flatten_1 (Flatten) (None, 98) 0 +_________________________________________________________________ +dense_1 (Dense) (None, 4) 396 +_________________________________________________________________ +dense_2 (Dense) (None, 10) 50 +================================================================= +Total params: 504 +Trainable params: 504 +Non-trainable params: 0 +_________________________________________________________________ +``` + +#### 2.3.5 训练模型 + +接下来我们就可以开始训练模型了: + +``` +from keras.callbacks import ModelCheckpoint +checkpointer = ModelCheckpoint(filepath='minions.hdf5', verbose=1, save_best_only=True, monitor='val_loss',mode='min') + +history = classifier.fit(X_train, y_train, epochs = 50, batch_size = 50, validation_data=(X_test, y_test), callbacks=[checkpointer]) +``` + +这个模型非常小,不过我用 CPU 训练才迭代 50 步,也差不多花了 10 分钟,所以 **能用 GPU 我们是坚决不用 CPU 的**。 + +我们可以看看刚刚的训练过程: + +``` +def plot_history(history) : + SMALL_SIZE = 20 + MEDIUM_SIZE = 22 + BIGGER_SIZE = 24 + + plt.rc('font', size=SMALL_SIZE) # controls default text sizes + plt.rc('axes', titlesize=SMALL_SIZE) # fontsize of the axes title + plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels + plt.rc('xtick', labelsize=SMALL_SIZE) # fontsize of the tick labels + plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels + plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize + plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title + + fig = plt.figure() + fig.set_size_inches(15,10) + plt.plot(history['loss']) + plt.plot(history['val_loss']) + plt.title('Model Loss') + plt.xlabel('epoch') + plt.ylabel('loss') + plt.legend(['train', 'test'],loc='upper left') + plt.show() +``` + +用图片显示一下训练过程: + +``` +plot_history(history.history) +``` + +![img](figures/01-f965b4f47be63747.png) + +可以看到,模型在训练集和测试集的 cost function 计算出来的 loss 都在减小,很神奇的是模型在测试集上的表现竟然比训练集还要好,不过模型精度不算太高才 60% 多一点的准确率,大家可以试着优化一下,在尝试改进模型的过程中,就会加深对模型的理解,如果我在这里直接就给出一个表现非常好的模型,可能对大家帮助反而不是那么大。 + +### 2.4 保存模型为 onnx + +我们可以把模型保存为原生的 Keras 模型: + +``` +classifier.save("mnist.h5") +``` + +当然,为了在 stm32 上面加载,我们更想保存为通用机器学习模型 onnx 的格式: + +``` +import onnx +import keras2onnx + +onnx_model = keras2onnx.convert_keras(classifier, 'mnist') +onnx.save_model(onnx_model, 'mnist.onnx') +``` + +这样在 Anaconda Prompt 默认目录下 **C:/Users/你的用户名** 可以看到 mnist.h5 和 mnist.onnx 两个文件,这就是训练好的模型。 + +这样我们模型也训练好了,也保存好了,下一步就是怎么使用训练好的模型了。 + +(大家可以试试把 Dropout 的概率从 0.5 改为 0.3,训练集准确度就会从 60% 提升到 80%,测试集则有 90% 以上,为什么呢?) + +### 2.5 参考文献 + +- [MNIST 训练集](http://yann.lecun.com/exdb/mnist/) +- [Anaconda GPU 环境](https://www.anaconda.com/tensorflow-in-anaconda/) + +------ + +## 3 运行卷积神经网络模型 + +这一部分会介绍模型训练好之后要如何使用,也就是模型的推断过程 (Inference) + +### 3.1 python 导入模型并运行 + +我们先用 python 加载模型,看看用刚刚训练好的模型能不能进行很好的预测,下面的代码就是导入了刚刚训练完保存的 mnist.onnx 模型。 + +``` +import onnxruntime as rt +sess = rt.InferenceSession("mnist.onnx") +``` + +为了运行模型,我们需要先得到模型的输出和输入层,输出层上一部分提到了,应当是 softmax: + +``` +input_name = sess.get_inputs()[0].name +output_name = sess.get_outputs()[0].name +``` + +接下来就是用测试集对模型进行预测了: + +``` +res = np.array(sess.run([output_name], {input_name: X_test})) +``` + +我们可以看看模型的测试集是个什么数字,然后看看模型计算得到了什么结果: + +``` +plt.imshow(X_test[0].reshape((28, 28)), cmap='gray') +print(res[0][0]) +``` + +![img](figures/01-cf4b652a9da7bd1d.png) + +可以看到模型最后一层 softmax 输出了 10 个数字,其中第 7 个数字 0.99688894 (下标从 0 开始) 明显远大于其他的数,这说明这张图片里的数字是 7 的概率是 99% 以上,这张图片也确实就是 7。 + +这样看来,刚刚训练得到的模型还是可以正常预测的,当然,并不能保证有 100% 的正确率,如果大家感兴趣也可以改变一下上面代码里 X_test[0] 的序号,看看其他的测试集预测效果怎么样 + +到这里为止,我们就不需要在 Anaconda 的 Jupyter Notebook 里写任何 Python 代码了,完整的代码可以在这里看到。 + + + +### 3.2 Google Protobuf + +#### 3.2.1 Protobuf 简介 + +从这里开始,我们的目的就是在 stm32 上面加载训练好的 onnx 模型了,那么为什么这里突然提到 Google Protobuf 呢?因为 onnx 的模型结构是用 Google Protobuf 的格式保存的。 + +之前我们提到,模型训练的目的就是为了得到变量的权值,只不过是纯数字罢了,但是我们也不能就这样把这些数字一个一个地写入文件,因为在要保存的模型文件里,不光要保存权值,也要告诉之后用这个模型的人,模型结构是怎么样的,所以需要合理地设计保存文件的格式。不同的机器学习框架都有自己的模型保存格式,例如 Keras 的模型格式是 h5,而 Tensorflow 和 onnx 的保存格式就是 protobuf。 + +那么到底什么是 protobuf? 为什么 protobuf 这么受欢迎? + +其实 protobuf 使用起来非常简单方便,就是自己先定义一个数据保存格式,然后用 protoc 自动生成各个语言的解析代码,现在支持 C, C++, C#, Java, Javascript, Objective-C, PHP, Python, Ruby。 + +举个例子,我们创建这么个文件 amessage.proto + +``` +syntax = "proto3"; + +message AMessage { + int32 a=1; + int32 b=2; +} +``` + +那么我们就定义了一种二进制的数据存储格式,里面包含 2 个数字,其中 a = 1,代表 id 为 1 的数据是个 int32 类型,它的名字是 a,**并不是代表 a 这个变量数值是 1**,同样的道理,id 为 2 的数据是个 int32 类型,它的名字是 b。这里 **id 是不能重复的**。 + +所以使用 protobuf 需要先定义一个数据格式,然后自动生成 **编码** 和 **解码** 的代码,供不同语言使用,因为能自动生成代码,所以 protobuf 简单好用,非常受欢迎,**建议大家使用 protov3**。 + +#### 3.2.2 RT-Thread 使用 Protobuf + +在 RT-Thread 也是有 protobuf 的库的,可以帮助我们用 C 语言解析和保存 protobuf 文件,毕竟我们之后要解析的 onnx 模型就是 protobuf 协议保存的。 + +protobuf 软件包地址: + +虽然在文章的开头已经假定大家熟练使用 RT-Thread 软件包了,还是提醒一下 **menuconfig** 之前记得 **pkgs --upgrade** 一下才能看到最新的软件包。 + +可以看到这个软件包下有 2 个例程,一个直接创建一个 protobuf 格式的数据,然后直接解码;另一个例程则是先把数据编码保存到文件,再从二进制文件读取数据并解码。 + +![img](figures/01-5c31eb7f7175b860.png) + +关于 protobuf 这里就不做更多的介绍了,因为 onnx 的模型格式已经定义好了,我们只需要直接拿来用就可以了。 + +### 3.3 onnx 模型解析 + +现在我们已经有了 RT-Thread 支持的 protobuf 软件包,下一步就是弄清楚 onnx 模型的格式到底是怎么定义的了,关于 onnx 数据格式的完整定义可以在这里看到。 + +onnx 数据格式定义: + +为了帮助我们更加直观的看到模型结构,这里推荐一个工具 [protobuf editor](https://sourceforge.net/projects/protobufeditor/),可以很方便地解析 protobuf 文件。 + +软件下载下来后,按照下面的流程就可以解析之前我们生产的 mnist.onnx 文件了,图上面提到的 onnx.proto3 文件之前提到过可以在 [这里](https://github.com/onnx/onnx/blob/master/onnx/onnx.proto3) 下载。 + +![img](figures/01-afae4925c7aba622.png) + +然后我们就可以在弹出来的界面看到之前训练生成的模型里到底有哪些数据了。 + +![img](figures/01-9c0bf4c387eaa6a6.png) + +可以看到里面有模型的版本信息、模型结构、模型权值、模型输入和模型输出,这也就是我们需要的信息了。 + +这里大家看到的权值不一定和我完全一样,因为每个人训练得到的模型都是略有差异的。 + +### 3.4 RT-Thread 导入模型并运行 + +在介绍了神经网络的基本理论,如何用 Python 训练 MNIST 手写体识别模型,以及 onnx 模型的 protobuf 文件格式后,终于到了最后一步,从 stm32 上加载这个模型并运行了。 + +到这里你应该准备好了: + +- 训练好的模型 mnist.onnx +- 一个带 SD 卡的 stm32 开发板,毕竟我们要把模型保存进去才能加载 + +项目源码: + +- RT-Thread 加载模型: +- 直接在上电脑上体验: + +首先,我们需要在 env 里通过 menuconfig 选中软件包: + +``` +RT-Thread online packages → IoT - internet of things → onnx-backend +``` + +这里忍不住再提醒大家一下,记得先在 env 里: + +``` +pkgs --upgrade +``` + +![img](figures/01-cd07bcbca37710c5.png) + +可以看到这里一共有三个例程,下面就对这三个例程分别介绍,在看下面的源码解析之前也可以直接下载代码到板子上体验一下,不过**记得打开文件系统,并且复制模型到 SD 卡里面,如果希望得到相同的输出,请使用 examples/mnist-sm.onnx 这个模型**。 + +#### 3.4.1 纯手动构建模型和参数 + +第一个例程是纯手动构建模型和参数,这样可以帮助我们理解模型结构和参数的位置,后面自动加载权值和模型结构就显得很自然简单了。 + +既然是纯手动构建模型,我们肯定得先知道模型长什么样子,这里再推荐另外一个 onnx 模型可视化根据 [netron](https://github.com/lutzroeder/Netron),下面的图就是 **netron** 根据我们之前训练生成的 mnist.onnx 模型生成的,非常漂亮: + +![img](figures/01-35655d9a89a1b8e2.png) + +可以看到我们的模型大致是这么个流程,中间重复的层我就没有写 2 次了,但是我们手动建模的时候自然是要加进去的。 + +``` +Conv2D -> Relu -> Maxpool -> Dense ->Softmax +``` + +这里解释一下为什么训练时候用到的 Dropout 这里看不到,因为 Dropout 只是为了防止过拟合,在训练的时候随机将训练好的参数丢弃置 0,所以一旦模型训练好,我们就不再需要 Dropout 操作了。 + +那么,接下来就要手动构建上面这个模型了。 + +模型的权值可以在 mnist.h 这个头文件里看到,其实这里面的权值就是我从 Protocol Buffer Editor 里面复制过来的,大家训练好的模型权值不一定和我完全一样。 + +``` +static const float W3[] = {-0.3233681, -0.4261553, -0.6519891, 0.79061985, -0.2210753, 0.037107922, 0.3984157, 0.22128074, 0.7975414, 0.2549885, 0.3076058, 0.62500215, -0.58958095, 0.20375429, -0.06477713, -1.566038, -0.37670124, -0.6443057}; +static const float B3[] = {-0.829373, -0.14096421}; + +static const float W2[] = {0.0070440695, 0.23192555, 0.036849476, -0.14687373, -0.15593372, 0.0044246824, 0.27322513, -0.027562773, 0.23404223, -0.6354651, -0.55645454, -0.77057034, 0.15603222, 0.71015775, 0.23954256, 1.8201442, -0.018377468, 1.5745461, 1.7230825, -0.59662616, 1.3997843, 0.33511618, 0.56846994, 0.3797911, 0.035079807, -0.18287429, -0.032232445, 0.006910181, -0.0026898328, -0.0057844054, 0.29354542, 0.13796881, 0.3558416, 0.0022847173, 0.0025906325, -0.022641085}; +static const float B2[] = {-0.11655525, -0.0036503011}; + +static const float W1[] = {0.15791991, -0.22649878, 0.021204736, 0.025593571, 0.008755621, -0.775102, -0.41594088, -0.12580238, -0.3963741, 0.33545518, -0.631953, -0.028754484, -0.50668705, -0.3574023, -3.7807872, -0.8261617, 0.102246165, 0.571127, -0.6256297, 0.06698781, 0.55969477, 0.25374785, -3.075965, -0.6959133, 0.2531965, 0.31739804, -0.8664238, 0.12750633, 0.83136076, 0.2666574, -2.5865922, -0.572031, 0.29743987, 0.16238026, -0.99154145, 0.077973805, 0.8913329, 0.16854058, -2.5247803, -0.5639109, 0.41671264, -0.10801031, -1.0229865, 0.2062031, 0.39889312, -0.16026731, -1.9185526, -0.48375717, 0.057339806, -1.2573057, -0.23117211, 1.051854, -0.7981992, -1.6263007, -0.26003376, -0.07649365, -0.4646075, 0.755821, 0.13187818, 0.24743222, -1.5276812, 0.1636555, -0.075465426, -0.058517877, -0.33852127, 1.3052516, 0.14443535, 0.44080895, -0.31031442, 0.15416017, 0.0053661224, -0.03175326, -0.15991405, 0.66121936, 0.0832211, 0.2651985, -0.038445678, 0.18054117, -0.0073251156, 0.054193687, -0.014296916, 0.30657783, 0.006181963, 0.22319937, 0.030315898, 0.12695274, -0.028179673, 0.11189027, 0.035358384, 0.046855893, -0.026528472, 0.26450494, 0.069981076, 0.107152134, -0.030371506, 0.09524366, 0.24802336, -0.36496836, -0.102762334, 0.49609017, 0.04002767, 0.020934932, -0.054773595, 0.05412083, -0.071876526, -1.5381132, -0.2356421, 1.5890793, -0.023087852, -0.24933836, 0.018771818, 0.08040064, 0.051946845, 0.6141782, 0.15780787, 0.12887044, -0.8691056, 1.3761537, 0.43058, 0.13476849, -0.14973496, 0.4542634, 0.13077497, 0.23117822, 0.003657386, 0.42742714, 0.23396699, 0.09209521, -0.060258932, 0.4642852, 0.10395402, 0.25047097, -0.05326261, 0.21466804, 0.11694269, 0.22402634, 0.12639907, 0.23495848, 0.12770525, 0.3324459, 0.0140223345, 0.106348366, 0.10877733, 0.30522102, 0.31412345, -0.07164018, 0.13483422, 0.45414954, 0.054698735, 0.07451815, 0.097312905, 0.27480683, 0.4866108, -0.43636885, -0.13586079, 0.5724732, 0.13595985, -0.0074526076, 0.11859829, 0.24481037, -0.37537888, -0.46877658, -0.5648533, 0.86578417, 0.3407381, -0.17214134, 0.040683553, 0.3630519, 0.089548275, -0.4989473, 0.47688767, 0.021731026, 0.2856471, 0.6174715, 0.7059148, -0.30635756, -0.5705427, -0.20692639, 0.041900065, 0.23040071, -0.1790487, -0.023751246, 0.14114629, 0.02345284, -0.64177734, -0.069909826, -0.08587972, 0.16460821, -0.53466517, -0.10163383, -0.13119817, 0.14908728, -0.63503706, -0.098961875, -0.23248474, 0.15406314, -0.48586813, -0.1904713, -0.20466608, 0.10629631, -0.5291871, -0.17358926, -0.36273107, 0.12225631, -0.38659447, -0.24787207, -0.25225234, 0.102635615, -0.14507034, -0.10110793, 0.043757595, -0.17158166, -0.031343404, -0.30139172, -0.09401665, 0.06986169, -0.54915506, 0.66843456, 0.14574362, -0.737502, 0.7700305, -0.4125441, 0.10115133, 0.05281194, 0.25467375, 0.22757779, -0.030224197, -0.0832025, -0.66385627, 0.51225215, -0.121023245, -0.3340579, -0.07505331, -0.09820366, -0.016041134, -0.03187605, -0.43589246, 0.094394326, -0.04983066, -0.0777906, -0.12822862, -0.089667186, -0.07014707, -0.010794195, -0.29095307, -0.01319235, -0.039757702, -0.023403417, -0.15530063, -0.052093383, -0.1477549, -0.07557954, -0.2686017, -0.035220042, -0.095615104, -0.015471024, -0.03906604, 0.024237331, -0.19604297, -0.19998372, -0.20302829, -0.04267139, -0.18774728, -0.045169186, -0.010131819, 0.14829905, -0.117015064, -0.4180649, -0.20680964, -0.024034742, -0.15787442, -0.055698488, -0.09037726, 0.40253848, -0.35745984, -0.786149, -0.0799551, 0.16205557, -0.14461482, -0.2749642, 0.2683253, 0.6881363, -0.064145364, 0.11361358, 0.59981894, 1.2947721, -1.2500908, 0.6082035, 0.12344158, 0.15808935, -0.17505693, 0.03425684, 0.39107767, 0.23190938, -0.7568858, 0.20042256, 0.079169095, 0.014275463, -0.12135842, 0.008516737, 0.26897284, 0.05706199, -0.52615446, 0.12489152, 0.08065737, -0.038548164, -0.08894516, 7.250979E-4, 0.28635752, -0.010820533, -0.39301336, 0.11144395, 0.06563818, -0.033744805, -0.07450528, -0.027328406, 0.3002447, 0.0029921278, -0.47954947, -0.04527057, -0.010289918, 0.039380465, -0.09236952, -0.1924659, 0.15401903, 0.21237805, -0.38984418, -0.37384143, -0.20648403, 0.29201767, -0.1299253, -0.36048025, -0.5544466, 0.45723814, -0.35266167, -0.94797707, -1.2481197, 0.88701195, 0.33620682, 0.0035414647, -0.22769359, 1.4563162, 0.54950374, 0.38396382, -0.41196275, 0.3758704, 0.17687413, 0.038129736, 0.16358295, 0.70515764, 0.055063568, 0.6445265, -0.2072113, 0.14618243, 0.10311305, 0.1971523, 0.174206, 0.36578146, -0.09782787, 0.5229244, -0.18459272, -0.0013945608, 0.08863555, 0.24184574, 0.15541393, 0.1722381, -0.10531331, 0.38215113, -0.30659106, -0.16298945, 0.11549875, 0.30750987, 0.1586183, -0.017728966, -0.050216004, 0.26232007, -1.2994286, -0.22700997, 0.108534105, 0.7447398, -0.39803517, 0.016863048, 0.10067235, -0.16355589, -0.64953077, -0.5674107, 0.017935256, 0.98968256, -1.395801, 0.44127485, 0.16644385, -0.19195901}; +static const float B1[] = {1.2019119, -1.1770505, 2.1698284, -1.9615222}; + +static const float W[] = {0.55808353, 0.78707385, -0.040990848, -0.122510895, -0.41261443, -0.036044, 0.1691557, -0.14711425, -0.016407091, -0.28058195, 0.018765535, 0.062936015, 0.49562064, 0.33931744, -0.47547337, -0.1405672, -0.88271654, 0.18359914, 0.020887045, -0.13782434, -0.052250575, 0.67922074, -0.28022966, -0.31278887, 0.44416663, -0.26106882, -0.32219923, 1.0321393, -0.1444394, 0.5221766, 0.057590708, -0.96547794, -0.3051688, 0.16859075, -0.5320585, 0.42684716, -0.5434046, 0.014693736, 0.26795483, 0.15921915}; +static const float B[] = {0.041442648, 1.461427, 0.07154641, -1.2774754, 0.80927604, -1.6933714, -0.29740578, -0.11774022, 0.3292682, 0.6596958}; +``` + +接下来就是利用这些权值进行计算了,也就是把这些权值带入到理论部分介绍的各个运算里面,其中各个算子都可以在源代码的目录下看到,一个算子对应一个 c 文件: + +``` +conv2d.c maxpool.c softmax.c transpose.c +matmul.c add.c dense.c relu.c +``` + +这些算子的代码如果对应理论部分的公式,就很好理解了,这里就不再重复介绍每个算子对应的含义了,在 mnist.c 里也可以看到,其实就只是输入图像,经过各个算子的运算,加上一些内存的释放操作,最后就得到了 softmax 的输出,**如果我把内存部分的操作隐藏掉**: + +``` + // 1. Conv2D + float* W3_t = transpose(W3, shapeW3, dimW3, permW3_t); + conv2D(img[img_index], 28, 28, 1, W3, 2, 3, 3, 1, 1, 1, 1, B3, conv1, 28, 28); + + // 2. Relu + relu(conv1, 28*28*2, relu1); + + // 3. Maxpool + maxpool(relu1, 28, 28, 2, 2, 2, 0, 0, 2, 2, 14, 14, maxpool1); + + // 4. Conv2D + float* W2_t = transpose(W2, shapeW2, dimW2, perm_t); + conv2D(maxpool1, 14, 14, 2, W2_t, 2, 3, 3, 1, 1, 1, 1, B2, conv2, 14, 14); + + // 5. Relu + relu(conv2, 14*14*2, relu2); + + // 6. Maxpool + maxpool(relu2, 14, 14, 2, 2, 2, 0, 0, 2, 2, 7, 7, maxpool2); + + // Flatten NOT REQUIRED + + // 7. Dense + float* W1_t = transpose(W1, shapeW1, dimW1, permW1_t); + dense(maxpool2, W1_t, 98, 4, B1, dense1); + + // 8. Dense + float* W_t = transpose(W, shapeW, dimW, permW_t); + dense(dense1, W_t, 4, 10, B, dense2); + + // 9. Softmax + softmax(dense2, 10, output); +``` + +可以看到,这些操作和前面图片里的模型是一一对应的,所以理解了理论部分模型为什么这么建立之后,再看代码就有一种恍然大悟的感觉,只不过相比 Python 而言, C 需要手动把权值和输入保存的数组里,并合理地管理内存的分配和释放。 + +如果我们把 mnist.c 编译上传到板子里,就可以看到成功地输出了预测结果: + +``` +msh />onnx_mnist 1 +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@ @@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@ @@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@ @@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@ @@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@ @@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + +Predictions: +0.007383 0.000000 0.057510 0.570970 0.000000 0.105505 0.000000 0.000039 0.257576 0.001016 + +The number is 3 +``` + +由于这个模型是完全手动构建的,所以内存消耗非常少,大约在 16KB 左右,下面的例子由于需要从文件系统加载模型,所以内存消耗会增大许多。 + +这里需要说明的是,大家可能听说过机器学习的模型在 MCU 上运行需要做量化 (Quantization),而这里为了说明方便是没有做量化的,所以当前是以浮点数进行计算,速度会比做了量化之后要慢,不过因为这个模型比较小,几乎还是瞬间就能看到结果的。 + +这里可以看到跟多关于 **模型量化** 的介绍。 + +#### 3.4.2 手动构建模型自动加载参数 + +之前我们是手动构建模型,并且从 Protocol Buffer Editor 里手动复制权值到 mnist.h 里面,这样非常辛苦,所以这个例子就是会根据当前计算的模型的名称,自动加载权值。 + +比如我们在 Protocol Buffer Editor 软件里可以看到: + +![img](figures/01-84f1a72c21405b26.png) + +如果我们想计算 “dense_5” 这一层模型,那么我们需要权值 W1,接下来就会根据 “W1” 这个名字取寻找对应的权值: + +![img](figures/01-2add98f611a2a528.png) + +所以这个例程只是多了自动寻找权值这个功能,因此我们传入的参数只需要是模型各层的名字就可以了,如果去掉内存释放相关的代码,每一层的计算还是非常清晰的。 + +``` + // 2. Conv2D + float* conv1 = conv2D_layer(model->graph, img[MNIST_TEST_IMAGE], shapeInput, shapeOutput, "conv2d_5"); + + // 3. Relu + float* relu1 = relu_layer(model->graph, conv1, shapeInput, shapeOutput, "Relu1"); + + // 4. Maxpool + float* maxpool1 = maxpool_layer(model->graph, relu1, shapeInput, shapeOutput, "max_pooling2d_5"); + + // 5. Conv2D + float* conv2 = conv2D_layer(model->graph, maxpool1, shapeInput, shapeOutput, "conv2d_6"); + + // 6. Relu + float* relu2 = relu_layer(model->graph, conv2, shapeInput, shapeOutput, "Relu"); + + // 7. Maxpool + float* maxpool2 = maxpool_layer(model->graph, relu2, shapeInput, shapeOutput, "max_pooling2d_6"); + + // 8. Transpose + + // 9. Flatten + + // 10. Dense + float* matmul1 = matmul_layer(model->graph, maxpool2, shapeInput, shapeOutput, "dense_5"); + + // 11. Add + float* dense1 = add_layer(model->graph, matmul1, shapeInput, shapeOutput, "Add1"); + + // 12. Dense + float* matmul2 = matmul_layer(model->graph, dense1, shapeInput, shapeOutput, "dense_6"); + + // 13. Add + float* dense2 = add_layer(model->graph, matmul2, shapeInput, shapeOutput, "Add"); + + // 14. Softmax + float* output = softmax_layer(model->graph, dense2, shapeInput, shapeOutput, "Softmax"); +``` + +如果大家对上面这些算子的名字有些陌生了,可以再回忆一下第一部分理论介绍。 + +#### 3.4.3 自动构建模型并加载参数 + +这三个例程越往下是越简单的,可以看到最后一个例程几乎就这两行代码了,加载模型,然后运行模型。 + +只需要指定模型的输入就可以了,毕竟后面模型各层的输入输出是完全可以自动计算出来的。 + +``` +// 加载模型 +Onnx__ModelProto* model = onnx_load_model(ONNX_MODEL_NAME); + +// 计算模型 +float* output = onnx_model_run(model, input, shapeInput); +``` + +这个例子使用 valgrind 测试发现大约需要 64KB 内存,所以大家记得检查一下自己的开发板内存够不够。 + +这里还有最后一点前面没有提到,对于图像而言数据顺序是非常重要的,比如 NWHC 和 NCWH 这两就略有不同,其中 N 代表输入图像数量, W 代表图像宽度, H 代表图像高度,C 代表图像的通道数,比如彩色图像就有 RGB 三个通道。所以 NWHC 和 NCWH 的区别就在于到底应该把通道 C 放在前面还是放在后面呢? + +关于这个问题,有一篇论文做了一些研究,在 CPU 和 GPU 上通常选择 NCWH 效率更高,这也是为什么大部分机器学习框架都是默认 NCWH 的格式,但是在 MCU 上例如 Cortex-M 系列使用 NWHC 计算效率就更高了。 + +论文地址: + + + +### 3.5 参考文献 + +- [完整 Jupyter Notebook 源码](https://github.com/wuhanstudio/onnx-backend/blob/master/examples/model/mnist-keras.ipynb) +- [protobuf editor 解析工具](https://sourceforge.net/projects/protobufeditor/) +- [onnx 数据格式定义](https://github.com/onnx/onnx/blob/master/onnx/onnx.proto3) +- [onnx 模型可视化](https://github.com/lutzroeder/Netron) +- [模型量化](https://nervanasystems.github.io/distiller/quantization.html) +- [NWHC 还是 NCWH](https://arxiv.org/abs/1801.06601) +- [RT-Thread Protobuf 软件包](https://github.com/wuhanstudio/protobuf-c) +- [RT-Thread Protobuf 软件包](https://github.com/wuhanstudio/rt-onnx-parser) +- [RT-Thread Protobuf 软件包](https://github.com/wuhanstudio/onnx-backend) + +------ + +## 4 总结 + +不知不觉这个文档已经写得这么长了,不知道大家有没有耐心看到最后,相信静下心来看得话还是有很多收获的,这里最后总结一下这篇文档介绍了哪些内容。 + +- 机器学习算法分类 +- Linear Regression (损失函数,梯度下降) +- Logistic Regression (sigmoid 函数) +- ANN (反向传播) +- CNN (conv2d, maxpooling, relu, dropout, flatten, dense, softmax) +- Protobuf (**RT-Thread 软件包 protobuf-c**) +- onnx 模型结构 (**RT-Thread 软件包 onnx-parser**) +- RTT 加载 onnx 模型并运行 (**RT-Thread 软件包 onnx-backend**) + +理论部分这里基本介绍完了,主要就是 darknet 框架的使用,甚至都不怎么需要写代码。最后,如果大家对在 MCU 上运行机器学习模型感兴趣,希望这篇文档还是能有所帮助。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-059f548df4ba65fe.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-059f548df4ba65fe.png new file mode 100644 index 0000000000000000000000000000000000000000..c72339d82b7646aa3d7e3b96654ac6589df96889 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-059f548df4ba65fe.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-123.gif b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-123.gif new file mode 100644 index 0000000000000000000000000000000000000000..6567b5edfcc8d5f86a6c0f6113547ac1cd32573a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-123.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-14257755.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-14257755.png new file mode 100644 index 0000000000000000000000000000000000000000..490690e4fa06a2d7a5f5451416d5b7560eda9fc9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-14257755.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-15468.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-15468.png new file mode 100644 index 0000000000000000000000000000000000000000..fe1ab5bdfce0e47684febe87ea980ba91026ec76 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-15468.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440223406.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440223406.png new file mode 100644 index 0000000000000000000000000000000000000000..3e21862d68f5f26cb2b468deee670a4f2e095424 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440223406.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440247208.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440247208.png new file mode 100644 index 0000000000000000000000000000000000000000..2fa51bfb83cef32d725c38b20dd6ef20a9fa3bd0 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440247208.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440279312.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440279312.png new file mode 100644 index 0000000000000000000000000000000000000000..4e487020ace89f72c5925f3e1b210ec793e02d27 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440279312.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440310847.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440310847.png new file mode 100644 index 0000000000000000000000000000000000000000..200ff66f3f8d5b85ebd96e69fc84e1a0d94868c9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440310847.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440353458.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440353458.png new file mode 100644 index 0000000000000000000000000000000000000000..88259ac6f17d23aa6b052c532fedccf375a1c80f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440353458.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440402057.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440402057.png new file mode 100644 index 0000000000000000000000000000000000000000..2cb7fd589d61af10dfce428a4e57fef059d23e4d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440402057.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440423002.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440423002.png new file mode 100644 index 0000000000000000000000000000000000000000..803b3cab7bb597b900b31708825bd16be314d424 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440423002.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440443614.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440443614.png new file mode 100644 index 0000000000000000000000000000000000000000..037d1c5134a994db3457471cc62f6fbee1b7a312 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440443614.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440465033.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440465033.png new file mode 100644 index 0000000000000000000000000000000000000000..5bcc47a67631b0eeb3b51a8cdcdbcebe95255509 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440465033.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440567328.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440567328.png new file mode 100644 index 0000000000000000000000000000000000000000..c2db6973a47c0bb95662f8b1e56ddbd3df79b259 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440567328.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440583616.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440583616.png new file mode 100644 index 0000000000000000000000000000000000000000..312e32ae4161d192913fb18a31217c7856497fa2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440583616.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440607155.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440607155.png new file mode 100644 index 0000000000000000000000000000000000000000..7b3216c890e1d0841ea986e6a99c19f4144e6a19 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440607155.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440668825.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440668825.png new file mode 100644 index 0000000000000000000000000000000000000000..1aa709504de9033a822c106fc58752ef2e89ed4d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440668825.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440789862.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440789862.png new file mode 100644 index 0000000000000000000000000000000000000000..a275c45e9c72e6a3b07adeaaa567ea0b97d57195 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440789862.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440982783.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440982783.png new file mode 100644 index 0000000000000000000000000000000000000000..a0f0da9542d18088386994475ae42dbf1a47dc04 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575440982783.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441028184.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441028184.png new file mode 100644 index 0000000000000000000000000000000000000000..82cba29d019c976fce7b0df417385e6bad389832 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441028184.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441047302.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441047302.png new file mode 100644 index 0000000000000000000000000000000000000000..e26356ab66537b553427d181038b6ca641015a25 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441047302.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441067421.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441067421.png new file mode 100644 index 0000000000000000000000000000000000000000..bb2780c28204661f9cc17a7e9cc3c75bd9cd03cf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441067421.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441107835.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441107835.png new file mode 100644 index 0000000000000000000000000000000000000000..8e07c3bb496e9a7fc3db6be3201842ab10a9b276 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441107835.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441163190.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441163190.png new file mode 100644 index 0000000000000000000000000000000000000000..26e8001c8ab5486d77b0bf0fc0f6fca72d582d31 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441163190.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441184688.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441184688.png new file mode 100644 index 0000000000000000000000000000000000000000..c1e2db8fedebf819dd5c96dc324040971fdd7e4e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441184688.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441415846.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441415846.png new file mode 100644 index 0000000000000000000000000000000000000000..477d9c5a06dd3583213445608eaf418281ba0e15 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441415846.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441452485.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441452485.png new file mode 100644 index 0000000000000000000000000000000000000000..66e221a686f97db7386816a46fd70ed1f0264796 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441452485.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441474390.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441474390.png new file mode 100644 index 0000000000000000000000000000000000000000..5205b574e88eac6c84cb33008344762e8f871f9c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-1575441474390.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-234567.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-234567.png new file mode 100644 index 0000000000000000000000000000000000000000..eb954c7a4b858905a579b74dbda63dacdb108b65 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-234567.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-2add98f611a2a528.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-2add98f611a2a528.png new file mode 100644 index 0000000000000000000000000000000000000000..fada8a022478662062e200771f176d51e4ee4859 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-2add98f611a2a528.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3449d9b4852fb4ec.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3449d9b4852fb4ec.png new file mode 100644 index 0000000000000000000000000000000000000000..d60e2332d1d708216926f3f45b6cd6bee3f5b289 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3449d9b4852fb4ec.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-35655d9a89a1b8e2.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-35655d9a89a1b8e2.png new file mode 100644 index 0000000000000000000000000000000000000000..ab105e270bfd3604a959a4b7269bd460b0d0b5cf Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-35655d9a89a1b8e2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-389246109.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-389246109.png new file mode 100644 index 0000000000000000000000000000000000000000..01b624fc9dc83f1a061a28a5357dda8c3a051ec3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-389246109.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3cd670c5ceb2433e.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3cd670c5ceb2433e.png new file mode 100644 index 0000000000000000000000000000000000000000..e672fad3604f7defbea0a131c150e3f8ad2a358c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3cd670c5ceb2433e.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3e7e7a6e0ecc6b5f.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3e7e7a6e0ecc6b5f.png new file mode 100644 index 0000000000000000000000000000000000000000..8b62c2e2a28132f02d4ac4634db26d18b3f55673 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-3e7e7a6e0ecc6b5f.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-40694b28faaf6777.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-40694b28faaf6777.png new file mode 100644 index 0000000000000000000000000000000000000000..3f602be94e0f875ad6b450bf069324e816a6b30e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-40694b28faaf6777.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-456789.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-456789.png new file mode 100644 index 0000000000000000000000000000000000000000..c4d4fb59a5bb5b3c87454e9d60e2a3913abe6d99 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-456789.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-51452196.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-51452196.png new file mode 100644 index 0000000000000000000000000000000000000000..6567b5edfcc8d5f86a6c0f6113547ac1cd32573a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-51452196.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-5c31eb7f7175b860.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-5c31eb7f7175b860.png new file mode 100644 index 0000000000000000000000000000000000000000..3b4c9e56ed0c554e3b2676027743ba11b550bf4d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-5c31eb7f7175b860.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-624x172.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-624x172.png new file mode 100644 index 0000000000000000000000000000000000000000..7201901d07c09e330995bbac55007fe6eedb5c70 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-624x172.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-63edc86a94db9ecd.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-63edc86a94db9ecd.png new file mode 100644 index 0000000000000000000000000000000000000000..199679bbe57003e93fab74d5abd2f4661d6a06c4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-63edc86a94db9ecd.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-6c42a7145053244b.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-6c42a7145053244b.png new file mode 100644 index 0000000000000000000000000000000000000000..eb5bf240d816990f2a0be178de0c7082eaf37e92 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-6c42a7145053244b.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-84f1a72c21405b26.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-84f1a72c21405b26.png new file mode 100644 index 0000000000000000000000000000000000000000..388e7a206a579cf63371daae5d57aa95a9211e19 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-84f1a72c21405b26.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9168acb2a010f32f.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9168acb2a010f32f.png new file mode 100644 index 0000000000000000000000000000000000000000..e58cfb9e59c6ede40cff219ab6dbd263fccddb48 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9168acb2a010f32f.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9a9a68dc53c1e1a9.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9a9a68dc53c1e1a9.png new file mode 100644 index 0000000000000000000000000000000000000000..8f12dc3aa3e9c5a64ecfb5428788e195867958b1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9a9a68dc53c1e1a9.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9c0bf4c387eaa6a6.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9c0bf4c387eaa6a6.png new file mode 100644 index 0000000000000000000000000000000000000000..add3dcbbceeff8bb9c179bf4c6c23a136e7ed9a6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-9c0bf4c387eaa6a6.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-a98ee523a803cb33.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-a98ee523a803cb33.png new file mode 100644 index 0000000000000000000000000000000000000000..6a2e0998a50a3f0b177a868823878ca25eb82204 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-a98ee523a803cb33.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-a9911df75880c8bb.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-a9911df75880c8bb.png new file mode 100644 index 0000000000000000000000000000000000000000..53bdd4eb69c939f84e8170e2bf8cdc20d66d8ea8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-a9911df75880c8bb.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-acc75794b641d2c3.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-acc75794b641d2c3.png new file mode 100644 index 0000000000000000000000000000000000000000..971988e6dc4a43f5424a9b477d4b68e905874e34 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-acc75794b641d2c3.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-afae4925c7aba622.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-afae4925c7aba622.png new file mode 100644 index 0000000000000000000000000000000000000000..1f54f2d52deda20965935eb5e00c17992cd85964 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-afae4925c7aba622.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-b016dceca991ef5d.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-b016dceca991ef5d.png new file mode 100644 index 0000000000000000000000000000000000000000..3a8dd01fc05d6473b7d53bd2d7ad2b43fd25a170 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-b016dceca991ef5d.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-b0977a236e95f427.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-b0977a236e95f427.png new file mode 100644 index 0000000000000000000000000000000000000000..dd4b9f7da0eb736099e33eaf7742a8cf46100844 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-b0977a236e95f427.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-cd07bcbca37710c5.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-cd07bcbca37710c5.png new file mode 100644 index 0000000000000000000000000000000000000000..837ddc7fb208d1eec92274504dd62cfd5da09f94 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-cd07bcbca37710c5.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-cf4b652a9da7bd1d.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-cf4b652a9da7bd1d.png new file mode 100644 index 0000000000000000000000000000000000000000..afce16099487e0fc001261befa98f2f2f061f2c7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-cf4b652a9da7bd1d.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-d36a1a7e53d2f9ae.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-d36a1a7e53d2f9ae.png new file mode 100644 index 0000000000000000000000000000000000000000..8ccc92754a0b6eb5cdd049ca61688477e5317583 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-d36a1a7e53d2f9ae.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-e307909709475176.gif b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-e307909709475176.gif new file mode 100644 index 0000000000000000000000000000000000000000..64786efd66d59fbb4aad1a3e65737ade582d9185 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-e307909709475176.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-f965b4f47be63747.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-f965b4f47be63747.png new file mode 100644 index 0000000000000000000000000000000000000000..6fb5b7f5afa22aa7caf27797bacd52f7fb90bcff Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-f965b4f47be63747.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-regressionfit.gif b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-regressionfit.gif new file mode 100644 index 0000000000000000000000000000000000000000..d6de7deb1030495fab154c5f94275f0caa8cabbc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-regressionfit.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-relu.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-relu.png new file mode 100644 index 0000000000000000000000000000000000000000..f3052e27093ad80908be8a3eac6251039d712bdb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/cnn-mnist/figures/01-relu.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/darknet-yolov2.md b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/darknet-yolov2.md new file mode 100644 index 0000000000000000000000000000000000000000..98c7e306ade9db532c5696c4e3068002890d52ea --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/darknet-yolov2.md @@ -0,0 +1,445 @@ +# Darknet 训练目标检测模型 + +## 引言 + +这篇文档会介绍如何用 darknet 训练一个 YOLOv2 目标检测模型,看完这篇文档会发现:模型训练和预测都非常简单,最花时间的精力的往往是训练集的数据预处理。 + +这里先简单介绍一下 **目标分类** (Classification) 和 **目标检测** (Detection) 的区别?什么是 **YOLO**?以及什么是 **darknet**? + +下面这张图很清晰地说明了目标分类: + +![img](figures/02-cat-dog-classifier.jpg) + +一张图片作为输入,然后模型就会告诉我这张图片里有猫的概率是多少,有狗的概率是多少,但是并不会告诉我有几只猫,几只狗,也不会告诉我猫和狗在图片里的具体位置。 + +目标检测则不一样: + +![img](figures/02-cat-dog-detection.jpg) + +目标检测的模型输出会告诉我图片中猫和狗分别在什么位置,以及是猫的概率是多少,所以目标检测模型提供的信息更加丰富,但是因此对模型的要求也更高了。 + +这里非常推荐这篇文章,对目标检测解释地非常直观详细: + + + +现在目标检测算法也有很多,这篇文章用到的就是非常有名的 YOLO (You only look once),就和字面意思一样, YOLO 可以一步 (one-stage) 得到结果所以非常快 (R-CNN 就需要两步 two-stage),而 darknet 就是论文作者对自己算法的开源实现,既然是作者本人的实现,自然会有很多人愿意看一看。 + +darknet 是用 C 语言实现的,在 github 上也能找到开源的 [**源码**](https://github.com/pjreddie/darknet),用 darknet 既可以训练新的模型,也可以用现有的模型做预测。 + +接下来就会介绍如何用 darknet 训练一个小黄人目标检测模型。 + +![img](figures/02-minions.gif) + +### 参考文献 + +- [darknet](https://pjreddie.com/darknet/) +- [目标检测]() + +## 1 准备训练集 + +### 1.1 拍摄照片 + +首先当然是要拍照了,照片用什么摄像头都是可以的,不过如果采集数据用的摄像头和最后实时预测用的是一样的,效果当然会更好了,不过我这里拍照用的是 K210 上面的摄像头,上面的动画里预测是用的 USB 摄像头,效果也还可以 。 + +![img](figures/02-dan_dock_1.png) + +其实拍照就是拍一些里面包含你想要识别的物体的照片就好了,从各个角度、各个距离拍,这很重要!!!比如如果你想识别只有一半的小黄人,那拍的照片里就得有这样的例子才行,下面就是我拍的 300 张照片: + +![img](figures/02-minions.png) + +通常做机器学习的图像识别,训练集很多时候都是 10GB+,但是我是用的 K210 摄像头拍的,每张照片就 320x240 差不多 3KB 大小,最后 300 张照片加在一起还没有 3MB,不过最后的训练结果却还挺不错的 。 + +如果碰巧你也有 K210 可以参照下面的 micropython 代码每隔 1-2s 自动拍一张照片保存到 SD 卡,**用手机摄像头当然也是完全没有问题的**: + +``` +# Untitled - By: RT-Thread - 周五 7月 19 2019 + +import sensor, image, time, lcd +from fpioa_manager import * +from Maix import GPIO +import os + +def getMax(): + maxnum = 0; + files = os.listdir('/sd') + for file in files: + name = file.split(".") + if(len(name)>1 and name[1] == "jpg"): + if(int(name[0])>maxnum): + maxnum = int(name[0]) + return maxnum + +fm.register(board_info.LED_R, fm.fpioa.GPIO0) +led_r=GPIO(GPIO.GPIO0,GPIO.OUT) + +lcd.init(freq=15000000) + +sensor.reset() +sensor.set_pixformat(sensor.RGB565) # Set pixel format to RGB565 (or GRAYSCALE) +sensor.set_framesize(sensor.QVGA) # Set frame size to QVGA (320x240) +sensor.skip_frames(time = 2000) # Wait for settings take effect. +sensor.set_hmirror(0) +sensor.set_windowing((224,224)) +clock = time.clock() # Create a clock object to track the FPS. + +i = getMax() + 1 + +def capture(): + global i + img = sensor.snapshot() + filename = '/sd/' + str(i) + '.jpg' + print(filename) + img.save(filename) + img.draw_string(2,2, ("%2.1f" %(i)), color=(0,128,0), scale=2) + lcd.display(img) + i = i + 1; + +print("Start from %d" % i) + +while True: + led_r.value(0) + time.sleep(1) + led_r.value(1) + capture() +``` + +这里千万不要把图片保存为中文名,建议像图中那样用数字按顺序标注,到这里我们应该有几百张包含我们要检测的物体的图片了。 + +### 1.2 标注图片 + +这一步应当是最累的了,上面只是拍好了 300 张照片,这一步就要为这些图片一一的做标注了,看起来就是这样: + +![img](figures/02-label.png) + +这里推荐用软件 [LabelImg](https://github.com/tzutalin/labelImg) 来标注: + +![img](figures/02-label.gif) + +软件用起来还是非常简单的,打开拍摄的图片文件夹,选择要保存的文件夹,然后一张一张地标注就可以了,最后可以看到每一张图片都生成了对应的 txt 文件: + +![img](figures/02-label_res.png) + +如果我们打开一个 txt 文件就可以看到类似下面的内容: + +``` +0 0.671875 0.575893 0.352679 0.848214 +``` + +其中每个数字代表的含义是: + +``` +物体类别 物体中心位置 x 物体中心位置 y 物体宽度 x 物体高度y +``` + +因为我们只有一个类别,所以第一个数字始终是 0,代表是第一个类别。 + +到这里我们应该每一张拍摄的图片都有一个对应的 txt 文件,里面包含上面提到的内容。 + +### 1.3 划分训练集和测试集 + +在开始训练之前,我们还需要把上面排到的图片分为训练集和测试集,这样我们才知道模型训练效果怎么样。 + +这里有个 python 脚本可以自动生成训练集和测试集,其实最终的目的就是生成两个文件,train.txt 和 test.txt,里面分别包含了训练图像,和测试图像的名字。 + +``` +import glob, os + +# Current directory +current_dir = os.path.dirname(os.path.abspath(__file__)) + +# Directory where the data will reside, relative to 'darknet.exe' +# 修改为你自己的目录 +path_data = './train/' + +# Percentage of images to be used for the test set +percentage_test = 10; + +# Create and/or truncate train.txt and test.txt +file_train = open('train.txt', 'w') +file_test = open('test.txt', 'w') + +# Populate train.txt and test.txt +counter = 1 +index_test = round(100 / percentage_test) +for pathAndFilename in glob.iglob(os.path.join(current_dir, "*.jpg")): + title, ext = os.path.splitext(os.path.basename(pathAndFilename)) + + if counter == index_test: + counter = 1 + file_test.write(path_data + title + '.jpg' + "\n") + else: + file_train.write(path_data + title + '.jpg' + "\n") + counter = counter + 1 +``` + +最后 train.txt 和 test.txt 看起来是这样的: + +![img](figures/02-train.png) + +到这里应该有拍好的照片,每张照片对应一个 txt 标明了小黄人的位置,大小,另外还有一个 train.txt 和 test.txt 汇总了当前训练集和测试集的文件名。 + +### 1.4 参考文献 + +- [荔枝丹 Dan Dock (K210)](https://wiki.sipeed.com/en/maix/board/dock.html) +- [训练自己的 Darknet Yolov2 模型](https://timebutt.github.io/static/how-to-train-yolov2-to-detect-custom-objects/) + +## 2 训练模型 + +### 2.1 开发环境 + +训练的过程就非常简单了,**建议使用 GPU,请务必使用 GPU**,而且请使用 Linux 开发环境,**Ubuntu**自然是科研领域用的最多的 Linux 发行版了,而且在开始训练之前请安装好**显卡驱动**、**CUDA 工具链**、**cuDNN 库**。 + +我用 i5 训练了整整 2天,才迭代了 200 步,用 NVIDIA GP104 训练一上午就迭代了 20000 步 + +都安装好之后,输入 nvidia-smi 应当是可以看到 CUDA 版本的: + +![nvidia-smi](figures/02-nvidia.png) + +### 2.2 编译 darknet + +如果到这里开发环境安装没有问题,我们就可以开始训练了,其实训练过程非常简单,首先下载源码: + +``` +git clone https://github.com/pjreddie/darknet +``` + +为了使用 GPU 训练,我们需要修改一下 Makefile: + +``` +GPU=1 +CUDNN=1 +OPENCV=0 +OPENMP=0 +DEBUG=0 +``` + +然后编译: + +``` +make +``` + +编译好 darknet 后就可以在源码根目录看到一个 darknet 可执行文件了。 + +### 2.3 配置文件 + +下面我们在 cfg 目录下添加一个 obj.names 文件,里面定义了我们的物体类别: + +``` +minions +``` + +当然,我们只有一个类别,接下来还需要在 cfg 目录下添加一个 obj.data 文件,里面第一行定义了我们的物体类别数,我们只有一个类别,已经我们前面生成的 train.txt,test.txt,obj.names 这 3 个文件在哪里,最后的 backup 是指训练的模型保存的位置。 + +``` +classes= 1 +train = /home/wuhan/darknet/data/train.txt +valid = /home/wuhan/darknet/data/test.txt +names = /home/wuhan/darknet/cfg/obj.names +backup = backup/ +``` + +最后因为我们训练的模型只有 1 类,但是 yolo 模型默认并不是只有 1 类,所以我们需要修改 cfg/yolov2.cfg,需要修改的部分我都标注出来了,我把这个配置文件保存成了 minionsv2.cfg: + +``` +[net] +# Testing +batch=16 +subdivisions=1 +# Training +# batch=64 +# subdivisions=2 +width=214 # 修改图像宽度 +height=214 # 修改图像高度 +channels=3 +momentum=0.9 +decay=0.0005 +angle=0 +saturation = 1.5 +exposure = 1.5 +hue=.1 + +learning_rate=0.001 +burn_in=1000 +max_batches = 500200 +policy=steps +steps=400000,450000 +scales=.1,.1 + +[convolutional] +batch_normalize=1 +filters=16 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=32 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=64 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=128 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=256 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=2 + +[convolutional] +batch_normalize=1 +filters=512 +size=3 +stride=1 +pad=1 +activation=leaky + +[maxpool] +size=2 +stride=1 + +[convolutional] +batch_normalize=1 +filters=1024 +size=3 +stride=1 +pad=1 +activation=leaky + +########### + +[convolutional] +batch_normalize=1 +size=3 +stride=1 +pad=1 +filters=512 +activation=leaky + +[convolutional] +size=1 +stride=1 +pad=1 +filters=30 # filters = (classes + 5) * 5 +activation=linear + +[region] +anchors = 0.57273, 0.677385, 1.87446, 2.06253, 3.33843, 5.47434, 7.88282, 3.52778, 9.77052, 9.16828 +bias_match=1 +classes=1 # 只有 1 类 +coords=4 +num=5 +softmax=1 +jitter=.2 +rescore=0 + +object_scale=5 +noobject_scale=1 +class_scale=1 +coord_scale=1 + +absolute=1 +thresh = .6 +random=1 +``` + +### 2.4 开始训练 + +终于可以开始训练了: + +``` +./darknet detector train cfg/obj.data cfg/minionsv2.cfg darknet19_448.conv.23 +``` + +记得把上面的配置文件改成自己的配置文件,上面命令里提前训练好的模型可以从这里下载 [darknet19_448.conv.23 (78MB)](https://wuhanshare-1252843818.cos.ap-guangzhou.myqcloud.com/darknet19_448.conv.23),可以加快训练速度。 + +前面提到,训练好的模型可以在 backup 目录找到,这是我训练了 20000 步之后自动保存的模型文件: + +``` +minionsv2_10000.weights +minionsv2_100.weights +minionsv2_20000.weights +minionsv2_200.weights +minionsv2_300.weights +minionsv2_400.weights +minionsv2_500.weights +minionsv2_600.weights +minionsv2_700.weights +minionsv2_800.weights +minionsv2_900.weights +minionsv2.backup +``` + +## 3 实时预测 + +如果我们想从摄像头实时看到检测结果,需要先重新编译 darknet,因为访问摄像头需要 OpenCV 库支持,修改 Makefile: + +``` +GPU=1 +CUDNN=1 +OPENCV=1 +OPENMP=0 +DEBUG=0 +``` + +重新 make 得到 darknet 可执行文件,输入下面的命令就可以看到试试检测结果了。 + +``` +./darknet detector demo cfg/obj.data cfg/minionsv2.cfg /home/wuhan/darknet/backup/minionsv2_10000.weights +``` + +记得把上面的文件路径改为自己的实际文件路径。 + +![img](figures/02-minions.gif) + +## 4 总结 + +这里总结一下,用 darknet 训练目标检测模型其实就这几步: + +1. 拍摄照片 +2. 用 labelimg 在照片上标注出自己想识别的物体的位置 +3. 把拍摄的照片划分为测试集和训练集,生成 train.txt 和 test.txt +4. 编译 darknet,配置好 CUDA,cuDNN +5. 添加配置文件 cfg/obj.names 和 cfg/obj.data +6. 修改 yolov2 模型为单类别 cfg/yolov2.cfg,并下载 darknet19_448.conv.23 +7. 用 GPU 开始训练,模型会自动保存 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-cat-dog-classifier.jpg b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-cat-dog-classifier.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e8f62c5afce6185aef524db996484babbd08e8f7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-cat-dog-classifier.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-cat-dog-detection.jpg b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-cat-dog-detection.jpg new file mode 100644 index 0000000000000000000000000000000000000000..46128489034385d1a13b3aadf38b6c46e54e44b3 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-cat-dog-detection.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-dan_dock_1.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-dan_dock_1.png new file mode 100644 index 0000000000000000000000000000000000000000..a30ba1575b18db3d5d08c3712690915177d89794 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-dan_dock_1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label.gif b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label.gif new file mode 100644 index 0000000000000000000000000000000000000000..b1fa13bf00d1696f67ee5b84294422f8772b0b1d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label.png new file mode 100644 index 0000000000000000000000000000000000000000..f9fe13809c94decf379daaab94327f143c6de0bb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label_res.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label_res.png new file mode 100644 index 0000000000000000000000000000000000000000..19a05dc004a2e36ebcb665560902041d66a407a9 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-label_res.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-minions.gif b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-minions.gif new file mode 100644 index 0000000000000000000000000000000000000000..5dacc96c9c53ede3146a82c5a26559d2fc1525f2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-minions.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-minions.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-minions.png new file mode 100644 index 0000000000000000000000000000000000000000..3ed74ca522b74f46c9db9bccf8030ba324146e71 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-minions.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-nvidia.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-nvidia.png new file mode 100644 index 0000000000000000000000000000000000000000..f44a4f45ed02cd86dcadba2ae0c301615f9a4d34 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-nvidia.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-train.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-train.png new file mode 100644 index 0000000000000000000000000000000000000000..84e34c0144318c4215ee2026ae59305ab72ada44 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/02-train.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/dan_dock_1-1575442842646.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/dan_dock_1-1575442842646.png new file mode 100644 index 0000000000000000000000000000000000000000..a30ba1575b18db3d5d08c3712690915177d89794 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/darknet-yolov2/figures/dan_dock_1-1575442842646.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-01.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-01.png new file mode 100644 index 0000000000000000000000000000000000000000..81891000148d6180162004e1a76ecb04a6a21424 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-01.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-02.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-02.png new file mode 100644 index 0000000000000000000000000000000000000000..88c4e974319ea2b610d31d1f5f4d7436d896259c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-02.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-03.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-03.png new file mode 100644 index 0000000000000000000000000000000000000000..81891000148d6180162004e1a76ecb04a6a21424 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/figures/06-03.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/object-detection.md b/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/object-detection.md new file mode 100644 index 0000000000000000000000000000000000000000..95c758cd2d0b7ecd0426288b3e66d0f18dc5de3c --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/smart-car/object-detection/object-detection.md @@ -0,0 +1,112 @@ +# RT-Thread 搭配 ROS 实现目标检测小车 + +## 引言 + +这应当是 RT-Thread 搭配 ROS 做摄像头小车的最后一篇文档了,这篇文档会把之前的内容汇聚起来,实现一个能目标检测的小车。 + +![img](figures/06-01.png) + +这篇文档几乎涉及了之前所有文档的内容,所以建议在看这篇文档之前先熟悉一下之前提到的内容。如果对之前的内容比较熟悉了,就会发现这篇文档很简短,但是却是建立在之前已有的基础上。 + +现在应当已经熟悉的内容: + +- 了解 CNN 的工作原理; +- 能使用 Darknet 训练自己的目标检测模型 +- 能用 rosserial 建立 RT-Thread 和 ROS 的连接 +- 能用 ROS 发布图像信息 + +下面就会介绍如何用 ROS 发布的图像信息和 Darknet 连接做目标检测。 + +## 1.Darknet ROS + +### 1.1 获取源码 + +其实下面要用到的是一个 ROS 软件包,这个软件包现在也是开源的: + +``` +# 初始化工作环境 +$ mkdir catkin_workspace +$ cd catkin_workspace/src +$ catkin_init_workspace + +## 下载源码 +$ git clone --recursive http://github.com/leggedrobotics/darknet_ros.git +``` + +除了源码,我们还要下载一些训练好的神经网络权值,放在下面这个目录: + +``` +$ catkin_workspace/src/darknet_ros/darknet_ros/yolo_network_config/weights/ +``` + +如果觉得国外下载速度太慢的话,这里我有个国内的 CDN 加速镜像: + +- yolov2-tiny.weights:https://wuhanshare-1252843818.cos.ap-guangzhou.myqcloud.com/yolov2-tiny.weights +- yolov2.weights:https://wuhanshare-1252843818.cos.ap-guangzhou.myqcloud.com/yolov2.weights +- yolov3.weights:https://wuhanshare-1252843818.cos.ap-guangzhou.myqcloud.com/yolov3.weights + +如果源码和权值都下载好了,我们就可以准备编译了。 + +### 1.2 编译源码 + +为了保证 Darknet 能够获取到摄像头的数据,我们需要先告诉它摄像头的信息发布在哪里,修改这个文件: + +``` +$ catkin_workspace/src/darknet_ros/darknet_ros/config/ros.yaml +``` + +把下面的 topic 修改为自己图像发布的位置,例如我这里发布在 /usb_cam/image_raw + +``` +camera_reading: + topic: /usb_cam/image_raw + queue_size: 1 +``` + +然后就可以编译软件包了,在 catkin_workspace 目录下: + +``` +$ catkin_make +``` + +一切正常的话,编译就完成了,其实不需要做太多的工作,编译完记得更新一下环境变量,这样后面才能正常启动这个软件包。 + +``` +$ sorce devel/setup.bash +``` + +### 1.3 目标检测 + +在进行目标检测前,我们先启动 ROS 节点: + +``` +$ roscore +``` + +然后启动一个摄像头节点: + +``` +roslaunch usb_cam usb_cam-test.launch +``` + +这样就可以实时看到摄像头的数据了,摄像头在哪里其实不重要,既可以在小车上,也可以在电脑上,这也是 ROS 的优美之处,只要节点发布了摄像头消息,不管摄像头在哪, ROS 都能拿到处理: + +![img](figures/06-02.png) + +接下来我们启动 Darknet 的节点: + +``` +$ roslaunch darknet_ros darknet_ros.launch +``` + +下面这张图就可以看到有两个视频流,左边的是没有处理的实时图像,右边是运行了目标检测的结果: + +![img](figures/06-03.png) + +## 2.总结 + +RT-Thread 作为实时操作系统负责控制,Linux 则负责提供丰富的软件包运行算法,两者相结合,互相取长补短还是配合地挺好的。 + +## 3.参考文献 + +Darknet ROS:https://github.com/leggedrobotics/darknet_ros \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-camera.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-camera.png new file mode 100644 index 0000000000000000000000000000000000000000..f463c1eebb46033a8919af34fb0533916deadb60 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-camera.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-car.jpg b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-car.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2cc886a75ccbcf544e4e2a48ca09f2ff624a4620 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-car.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-ros.gif b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-ros.gif new file mode 100644 index 0000000000000000000000000000000000000000..717b8dd00d5ccb8b0b01f8c8682d3fcda505f20a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-ros.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-structure.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-structure.png new file mode 100644 index 0000000000000000000000000000000000000000..7163e25ab42f0d523a9313d42a945e735355b1fb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/figures/04-structure.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/ros-camera-car.md b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/ros-camera-car.md new file mode 100644 index 0000000000000000000000000000000000000000..4ec305ea93ce45f7906eca86d0235e757c0542c7 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-camera-car/ros-camera-car.md @@ -0,0 +1,355 @@ +# RT-Thread 连接 ROS 控制小车 + +## 1 引言 + +这篇文档会介绍如何用 RT-Thread 和 ROS 连接实现一个带摄像头的远程控制小车。 + +![img](figures/04-ros.gif) + +不过其实 RT-Thread 部分的代码已经在这篇文档里面介绍了:[RT-Thread 连接 ROS 控制小车](http://123.207.116.104/ros),在这个基础上,我们只需要修改 ROS 的代码就可以了。 + +这里先把整个系统框图画出来,这样如果想要自己做一辆这样的小车也可以动手试一试: + +![img](figures/04-structure.png) + +实物图看起来就是这样: + +![img](figures/04-car.jpg) + +## 2 ROS 平滑运动 + +### 2.1 ROS 工作环境 + +下面的代码都是在安装了 ROS 的电脑上操作的 + +ROS 的安装之前已经介绍过了,这里就不重复了,我们先新建一个工作区间: + +``` +$ mkdir telebot_ws && cd telebot_ws +$ catkin_init_workspace +``` + +我们再新建一个 ROS 软件包: + +``` +$ cd src +$ catkin_create_pkg telebot rospy +``` + +这样我们就可以开始 ROS 的开发了,在 telebot_ws 目录下: + +``` +$ catkin_make +$ source devel/setup.bash +``` + +### 2.2 按键触发 + +我们先建立一个节点用来监听键盘的按键,并且将收到的按键发布到 /keys 这个话题 (ROS 节点间的通信就是靠发布和订阅话题实现的),我们在 telebot_ws/src/telebot/src 目录下新建一个文件 key_publisher.py + +``` +#!/usr/bin/python + +# 导入软件包 +import sys, select, tty, termios +import rospy +from std_msgs.msg import String + +if __name__ == '__main__': + # 初始化节点 + key_pub = rospy.Publisher('keys', String, queue_size=1) + rospy.init_node("keyboard_driver") + rate = rospy.Rate(100) + + # 设置终端输入模式 + old_attr = termios.tcgetattr(sys.stdin) + tty.setcbreak(sys.stdin.fileno()) + print("Publishing keystrokes. Press Ctrl-C to exit ...") + + # 循环监听键盘事件 + while not rospy.is_shutdown(): + if select.select([sys.stdin], [], [], 0)[0] == [sys.stdin]: + # 发布监听到的按键 + key_pub.publish(sys.stdin.read(1)) + rate.sleep() + + # 恢复终端设置 + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_attr) +``` + +上面的代码不到 20 行,我也添加了一些注释,就不详细介绍了,我们为这个文件添加可执行权限: + +``` +$ chmod u+x key_publisher.py +``` + +就可以启动节点了: + +``` +$ rosrun telebot key_publisher.py +``` + +这样我们就可以看到有一个 /keys 的话题会不断输出键盘按键: + +``` +$ rostopic echo /keys +data: "w" +--- +data: "a" +--- +data: "s" +--- +``` + +### 2.3 按键解析 + +现在已经有一个节点在发布我们的按键消息了,接下来就是把按键消息转换为小车的运动指令了,也就是发布到 /cmd_vel,我们在 telebot_ws/src/telebot/src 目录下新建一个文件 keys_to_twist_with_ramps.py: + +``` +#!/usr/bin/python + +# 导入软件包 +import rospy +import math +from std_msgs.msg import String +from geometry_msgs.msg import Twist + +# 键盘和速度映设 w a s d +key_mapping = { 'w': [ 0, 1], 'x': [ 0, -1], + 'a': [ -1, 0], 'd': [1, 0], + 's': [ 0, 0] } +g_twist_pub = None +g_target_twist = None +g_last_twist = None +g_last_send_time = None +g_vel_scales = [0.1, 0.1] # default to very slow +g_vel_ramps = [1, 1] # units: meters per second^2 + +# 防止速度突变 +def ramped_vel(v_prev, v_target, t_prev, t_now, ramp_rate): + # compute maximum velocity step + step = ramp_rate * (t_now - t_prev).to_sec() + sign = 1.0 if (v_target > v_prev) else -1.0 + error = math.fabs(v_target - v_prev) + if error < step: # we can get there within this timestep. we're done. + return v_target + else: + return v_prev + sign * step # take a step towards the target + +def ramped_twist(prev, target, t_prev, t_now, ramps): + tw = Twist() + tw.angular.z = ramped_vel(prev.angular.z, target.angular.z, t_prev, + t_now, ramps[0]) + tw.linear.x = ramped_vel(prev.linear.x, target.linear.x, t_prev, + t_now, ramps[1]) + return tw + +# 发布控制指令到 /cmd_vel +def send_twist(): + global g_last_twist_send_time, g_target_twist, g_last_twist,\ + g_vel_scales, g_vel_ramps, g_twist_pub + t_now = rospy.Time.now() + g_last_twist = ramped_twist(g_last_twist, g_target_twist, + g_last_twist_send_time, t_now, g_vel_ramps) + g_last_twist_send_time = t_now + g_twist_pub.publish(g_last_twist) + +# 订阅 /keys 的回调函数 +def keys_cb(msg): + global g_target_twist, g_last_twist, g_vel_scales + if len(msg.data) == 0 or not key_mapping.has_key(msg.data[0]): + return # unknown key. + vels = key_mapping[msg.data[0]] + g_target_twist.angular.z = vels[0] * g_vel_scales[0] + g_target_twist.linear.x = vels[1] * g_vel_scales[1] + +# 获取传递进来的速度加速度比例 +def fetch_param(name, default): + if rospy.has_param(name): + return rospy.get_param(name) + else: + print "parameter [%s] not defined. Defaulting to %.3f" % (name, default) + return default + +if __name__ == '__main__': + rospy.init_node('keys_to_twist') + g_last_twist_send_time = rospy.Time.now() + g_twist_pub = rospy.Publisher('cmd_vel', Twist, queue_size=1) + rospy.Subscriber('keys', String, keys_cb) + g_target_twist = Twist() # initializes to zero + g_last_twist = Twist() + g_vel_scales[0] = fetch_param('~angular_scale', 0.1) + g_vel_scales[1] = fetch_param('~linear_scale', 0.1) + g_vel_ramps[0] = fetch_param('~angular_accel', 1.0) + g_vel_ramps[1] = fetch_param('~linear_accel', 1.0) + + rate = rospy.Rate(20) + while not rospy.is_shutdown(): + send_twist() + rate.sleep() +``` + +同样的,我们为这个文件添加可执行权限: + +``` +$ chmod u+x keys_to_twist_with_ramps.py +``` + +就可以启动节点了: + +``` +$ rosrun telebot keys_to_twist_with_ramps.py _linear_scale:=1.0 _angular_scale:=0.8 _linear_accel:=1.0 _angular_accel:=0.8 +``` + +上面传入的参数是我们希望的小车运动速度和加速度比例,这样我们就可以看到有一个 /cmd_vel 的话题会输出期望的小车速度: + +``` +$ rostopic echo /cmd_vel +linear: + x: 1.0 + y: 0.0 + z: 0.0 +angular: + x: 0.0 + y: 0.0 + z: 0.0 +--- +``` + +现在小车已经可以按照我们的指令慢慢启动,停下,转弯了,下一步就是给它加上远程摄像头。 + +## 3 ROS 摄像头 + +在和小车的摄像头连接之前,有一点非常重要,之前的操作都是在电脑上执行的,接下来我们要把自己的 ARM 开发板作为 ROS 主节点,所以需要设置环境变量,需要**把下面的 IP 地址替换为小车上 ARM 板的实际 ip 地址**: + +``` +$ export ROS_MASTER_URI=http://your.armbian_ros.ip.address:11311 +``` + +下面的代码都是在安装了 ROS 的小车上 ARM 开发板上操作的 + +我们先新建一个工作区间: + +``` +$ mkdir telebot_ws && cd telebot_ws +$ catkin_init_workspace +``` + +我们再新建一个 ROS 软件包: + +``` +$ cd src +$ catkin_create_pkg telebot_image roscpp +``` + +这样我们就可以开始 ROS 的开发了,在 telebot_ws 目录下: + +``` +$ catkin_make +$ source devel/setup.bash +``` + +### 3.1 发布摄像头消息 + +其实发布摄像头消息的代码也就 30 行左右,我们在 telebot_ws/src/telebot_image/src 目录下新建 my_publisher.cpp + +``` +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + ros::init(argc, argv, "video_transp"); + ros::NodeHandle nh; + image_transport::ImageTransport it(nh); + image_transport::Publisher pub = it.advertise("camera/image", 1); + + cv::VideoCapture cap(0); + cv::Mat frame; + sensor_msgs::ImagePtr frame_msg; + + ros::Rate rate(10); + + while(ros::ok()) + { + cap >> frame; + if (!frame.empty()) + { + frame_msg = cv_bridge::CvImage(std_msgs::Header(), "bgr8", frame).toImageMsg(); + pub.publish(frame_msg); + cv::waitKey(1); + } + ros::spinOnce(); + rate.sleep(); + } + return 0; +} +``` + +在编译之前**需要先安装好 OpenCV 的开发环境**,因为我们是用 OpenCV 的库函数获取到摄像头数据,然后用 ROS 的库函数发布出去的,这是 telebot_ws/src/telebot_image 目录下的 CMakeLists.txt + +``` +cmake_minimum_required(VERSION 2.8.3) +project(telebot_image) + +find_package(catkin REQUIRED COMPONENTS + cv_bridge + image_transport +) + +catkin_package( +# INCLUDE_DIRS include +# LIBRARIES my_image_transport +# CATKIN_DEPENDS cv_bridge image_transport +# DEPENDS system_lib +) + +include_directories( + ${catkin_INCLUDE_DIRS} +) + +find_package(OpenCV) +include_directories(include ${OpenCV_INCLUDE_DIRS}) +#build my_publisher and my_subscriber +add_executable(my_publisher src/my_publisher.cpp) +target_link_libraries(my_publisher ${catkin_LIBRARIES} ${OpenCV_LIBS}) +``` + +我们在 telebot_ws 目录下编译项目: + +``` +$ catkin_make +``` + +这样就可以发布摄像头消息了,图像消息发布在 camera/image: + +``` +$rosrun telebot_image my_publisher +``` + +### 3.2 订阅摄像头消息 + +下面的代码都是在安装了 ROS 的电脑上操作的 + +要订阅并看到摄像头消息其实非常简单: + +``` +$ rosrun image_view image_view image:=/camera/image +``` + +就可以再电脑上看到小车摄像头的图片了。 + +![img](figures/04-camera.png) + +## 4 总结 + +如果已经有一辆能够用 ROS 控制的小车,其实只需要写第 3 部分图像发布 30 行左右的代码,就可以用 OpenCV 库获取摄像头信息,然后用 ROS 库发布出去了。 + +当然,如果只是为了在电脑上看到小车的摄像头,其实还有其他很多甚至不用写代码的办法,用 ROS 发布图像数据的好处在于我们可以对获取到的图像进行处理,例如目标检测,这会在之后的文档里进一步介绍。 + +## 参考文献 + +- [ROS 机器人编程实践](https://github.com/osrf/rosbook) +- [rosserial 软件包](https://github.com/wuhanstudio/rt-rosserial) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-car.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-car.png new file mode 100644 index 0000000000000000000000000000000000000000..bb58c7caa3feb8cf52977b1f496fc53c8ee0de77 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-car.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-cubemx.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-cubemx.png new file mode 100644 index 0000000000000000000000000000000000000000..af992c40581b0123432066d146863f2c1eb33462 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-cubemx.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-main.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-main.png new file mode 100644 index 0000000000000000000000000000000000000000..eb24e94b22f131bd873fa0729837a2301a66d487 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-main.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-ros.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-ros.png new file mode 100644 index 0000000000000000000000000000000000000000..d4b9f787a79e7494ce3af8f8b3083b0e95dbf7a1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-ros.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-rosserial.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-rosserial.png new file mode 100644 index 0000000000000000000000000000000000000000..911fea4463100c60ae1d61cb5f0c03b5da6256c2 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-rosserial.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-tcp.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-tcp.png new file mode 100644 index 0000000000000000000000000000000000000000..57c5b8d552b3c0a2aa4b75136831c8b65eb2d5e7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-tcp.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-usart.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-usart.png new file mode 100644 index 0000000000000000000000000000000000000000..17d77fe2fe4ca20c0368e7603aa91e0c2477e842 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-usart.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-usart2.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-usart2.png new file mode 100644 index 0000000000000000000000000000000000000000..a55d0c5e0c5ff584eed3b56fb0894ff708ec8e67 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/figures/03-usart2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/ros-connect.md b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/ros-connect.md new file mode 100644 index 0000000000000000000000000000000000000000..23ac5abbe843f02b2d5f7af2483aae1eee11a2a4 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/smart-car/ros-connect/ros-connect.md @@ -0,0 +1,619 @@ +# RT-Thread 连接 ROS + +## 引言 + +这篇文档主要介绍 RT-Thread 如何使用串口或者无线和 ROS 连接,会包含这么些内容: + +- 第一部分:ROS 环境搭建 +- 第二部分:RT-Thread rosserial 软件包 +- 第二部分:RT-Thread 添加 USART2 和 PWM +- 第三部分:RT-Thread 使用 ESP8266 AT 固件联网 + +这里先介绍一下什么是 ROS?为什么要和 ROS 连接? + +**机器人操作系统 ROS** (Robots Operating System) 最早是斯坦福大学的一个软件框架,现在不管是工业机器人,还是娱乐用的机器人都运行着 ROS。 + +![img](figures/03-ros.png) + +一个机器人通常有很多个部件、传感器,为了保证机器人不会因为某一个传感器故障,导致整个系统瘫痪,所以采用了分布式的节点,利用不同节点之间的通讯收集传感器数据和控制指令,这篇文档后面会使用到的通讯协议就是 **rosserial**。 + +和 ROS 连接的好处在于,一方面由 ROS 管理各个机器人节点更稳定,另一方面 ROS 现在已经有了非常多成熟的软件包,使用 ROS 就可以非常方便的为自己的机器人添加摄像头图像识别、激光雷达建图导航等高级功能。 + +不过这篇文档只会涉及 RT-Thread 和 ROS 建立基本的连接,实现小车的运动控制,之后可能会有后续文档介绍如何连接激光雷达建图,并进行全局路径规划。 + +这篇文章假定大家都已经会用 RT-Thread 的 env 工具下载软件包,生成项目上传固件到 stm32 上,并且熟悉 Ubuntu 的基本使用。 + +## 1 ROS 简介 + +### 1.1 ROS 环境搭建 + +这里的开发环境搭建其实是需要搭建 2 份,一份是小车上的 ARM 开发板 (树莓派,NanoPi 什么的),另一个则是自己的电脑,因为我们希望把电脑作为 ROS 从节点,连接到小车上的 ROS 主节点,不过开发板和电脑的 ROS 安装是一模一样的。 + +既然要和 ROS 连接,那么首先就得要有一个正常运行的 ROS。安装 ROS 其实非常简单,这里推荐使用 Ubuntu 18 (开发板推荐系统用 Armbian),因为官方对 Ubuntu 的支持优先级是最高的,安装教程也可以参照 [官网](http://wiki.ros.org/melodic/Installation/Ubuntu)。 + +只需要输入下面的 4 行命令,就在 Ubuntu 上装好了 ROS。 + +``` +sudo sh -c 'echo "deb https://mirror.tuna.tsinghua.edu.cn/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' + +sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 + +sudo apt update + +sudo apt install ros-melodic-ros-base +``` + +上面我使用了清华大学的镜像源,这样从国内下载 ROS 会快很多,而且我只安装了 ROS 的基本软件包,没有安装图形化软件包 gviz,gazebo 什么的,因为后面也没有用到。 + +### 1.2 ROS 环境初始化 + +ROS 安装好之后还需要进行初始化,不过也是只有短短几行命令: + +``` +sudo rosdep init +rosdep update + +echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc +source ~/.bashrc +``` + +### 1.3 启动 ROS + +启动 ROS 的话我们需要确保它是常驻后台运行的,所以我们可以使用 tmux: + +``` +roscore +``` + +在 tmux 里启动了 ROS 主节点后,我们就可以 Ctrl + B D 退出了,而 ROS 主节点依旧在后台运行。 + +### 1.4 参考文献 + +- [Armbian](https://www.armbian.com/) +- [ROS Melodic 安装](http://wiki.ros.org/melodic/Installation/Ubuntu) + +## 2 RT-Thread 串口连接 ROS + +这一部分会介绍如何使用串口将运行着 RT-Thread 的 STM32 开发板和运行着 ROS 的 ARM 开发板连接,看起来差不多就是这样。 + +![img](figures/03-car.png) + +这里说明一下不同开发板的分工,STM32 运行着 RT-Thread 负责控制电机,接收传感器信息;ARM 运行着 ROS 负责进行全局控制,例如给小车发出前进的指令。 + +### 2.1 RT-Thread 配置 + +首先我们需要打开 usart2,因为 usart1 被 msh 使用了,保留作为调试还是挺方便的。 + +![img](figures/03-cubemx.png) + +在 CubeMX 里我打开了 USART2,另外还打开了 4 路 PWM,因为我后面使用了 2 个电机,每个电机需要 2 路 PWM 分别控制前进和后退。 + +接下来还需要在 menuconfig 里面打开对应的选项,考虑到有的开发板默认的 bsp 可能没有这些选项,可以修改 board/Kconfig 添加下面的内容。 + +串口的配置: + +``` +menuconfig BSP_USING_UART + bool "Enable UART" + default y + select RT_USING_SERIAL + if BSP_USING_UART + config BSP_USING_UART1 + bool "Enable UART1" + default y + + config BSP_UART1_RX_USING_DMA + bool "Enable UART1 RX DMA" + depends on BSP_USING_UART1 && RT_SERIAL_USING_DMA + default n + + config BSP_USING_UART2 + bool "Enable UART2" + default y + + config BSP_UART2_RX_USING_DMA + bool "Enable UART2 RX DMA" + depends on BSP_USING_UART2 && RT_SERIAL_USING_DMA + default n + endif +``` + +PWM 的配置: + +``` +menuconfig BSP_USING_PWM + bool "Enable pwm" + default n + select RT_USING_PWM + if BSP_USING_PWM + menuconfig BSP_USING_PWM3 + bool "Enable timer3 output pwm" + default n + if BSP_USING_PWM3 + config BSP_USING_PWM3_CH1 + bool "Enable PWM3 channel1" + default n + config BSP_USING_PWM3_CH2 + bool "Enable PWM3 channel2" + default n + config BSP_USING_PWM3_CH3 + bool "Enable PWM3 channel3" + default n + config BSP_USING_PWM3_CH4 + bool "Enable PWM3 channel4" + default n + endif + endif +``` + +这样我们在 env 下就可以看到有对应的配置了 + +![img](figures/03-usart.png) + +![img](figures/03-usart2.png) + +除此之外,我们还需要选择 rosserial 软件包: + +![img](figures/03-rosserial.png) + +可以看到上面默认的串口就是 USART2,这样我们就可以生成对应的工程了: + +``` +pkgs --update +scons --target=mdk5 -s +``` + +如果我们打开 Keil 项目,首先需要把 main.c 修改为 main.cpp,因为 rosserial 很多数据格式的定义都是用 C++ 写的,所以如果要使用 rosserial 库,我们先得把后缀改为 cpp,这样 Keil 就会用 C++ 编译器编译。 + +![img](figures/03-main.png) + +下面是 main.cpp 的内容,其实就是初始化了电机,然后发布了 2 个话题 (topic),一个是 /vel_x 告诉 ROS 当前小车的速度,一个是 /turn_bias 告诉 ROS 当前小车的旋转速度。同时又订阅了一个话题 /cmd_vel,用来接收从 ROS 发出的控制指令。 + +代码不是特别长,我也添加了一些注释,所以这里就不一行行分析了。 + +```c +#include +#include +#include + +#include +#include +#include +#include "motors.h" + +ros::NodeHandle nh; +MotorControl mtr(1, 2, 3, 4); //Motor + +bool msgRecieved = false; +float velX = 0, turnBias = 0; +char stat_log[200]; + +// 接收到命令时的回调函数 +void velCB( const geometry_msgs::Twist& twist_msg) +{ + velX = twist_msg.linear.x; + turnBias = twist_msg.angular.z; + msgRecieved = true; +} + +//Subscriber +ros::Subscriber sub("cmd_vel", velCB ); + +//Publisher +std_msgs::Float64 velX_tmp; +std_msgs::Float64 turnBias_tmp; +ros::Publisher xv("vel_x", &velX_tmp); +ros::Publisher xt("turn_bias", &turnBias_tmp); + +static void rosserial_thread_entry(void *parameter) +{ + //Init motors, specif>y the respective motor pins + mtr.initMotors(); + + //Init node> + nh.initNode(); + + // 订阅了一个话题 /cmd_vel 接收控制指令 + nh.subscribe(sub); + + // 发布了一个话题 /vel_x 告诉 ROS 小车速度 + nh.advertise(xv); + + // 发布了一个话题 /turn_bias 告诉 ROS 小车的旋转角速度 + nh.advertise(xt); + + mtr.stopMotors(); + + while (1) + { + // 如果接收到了控制指令 + if (msgRecieved) + { + velX *= mtr.maxSpd; + mtr.moveBot(velX, turnBias); + msgRecieved = false; + } + + velX_tmp.data = velX; + turnBias_tmp.data = turnBias/mtr.turnFactor; + + // 更新话题内容 + xv.publish( &velX_tmp ); + xt.publish( &turnBias_tmp ); + + nh.spinOnce(); + } +} + +int main(void) +{ + // 启动一个线程用来和 ROS 通信 + rt_thread_t thread = rt_thread_create("rosserial", rosserial_thread_entry, RT_NULL, 2048, 8, 10); + if(thread != RT_NULL) + { + rt_thread_startup(thread); + rt_kprintf("[rosserial] New thread rosserial\n"); + } + else + { + rt_kprintf("[rosserial] Failed to create thread rosserial\n"); + } + return RT_EOK; +} +``` + +另外还有对应的电机控制的代码,不过这个大家的小车不同,驱动应当也不一样,我这里由于小车电机上没有编码器,所以全部是开环控制的。 + +motors.h + +```c +#include + +class MotorControl { + public: + //Var + rt_uint32_t maxSpd; + float moveFactor; + float turnFactor; + + MotorControl(int fl_for, int fl_back, + int fr_for, int fr_back); + void initMotors(); + void rotateBot(int dir, float spd); + void moveBot(float spd, float bias); + void stopMotors(); + private: + struct rt_device_pwm *pwm_dev; + //The pins + int fl_for; + int fl_back; + int fr_for; + int fr_back; + int bl_for; + int bl_back; + int br_for; + int br_back; +}; +``` + +motors.c + +```c +#include +#include +#include "motors.h" + +#define PWM_DEV_NAME "pwm3" + +MotorControl::MotorControl(int fl_for, int fl_back, + int fr_for, int fr_back) +{ + this->maxSpd = 500000; + this->moveFactor = 1.0; + this->turnFactor = 3.0; + + this->fl_for = fl_for; + this->fl_back = fl_back; + + this->fr_for = fr_for; + this->fr_back = fr_back; +} + +void MotorControl::initMotors() { + /* 查找设备 */ + this->pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME); + if (pwm_dev == RT_NULL) + { + rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM_DEV_NAME); + } + rt_kprintf("pwm found %s device!\n", PWM_DEV_NAME); + rt_pwm_set(pwm_dev, fl_for, maxSpd, 0); + rt_pwm_enable(pwm_dev, fl_for); + + rt_pwm_set(pwm_dev, fl_back, maxSpd, 0); + rt_pwm_enable(pwm_dev, fl_back); + + rt_pwm_set(pwm_dev, fr_for, maxSpd, 0); + rt_pwm_enable(pwm_dev, fr_for); + + rt_pwm_set(pwm_dev, fr_back, maxSpd, 0); + rt_pwm_enable(pwm_dev, fr_back); +} + +// 小车运动 +void MotorControl::moveBot(float spd, float bias) { + float sL = spd * maxSpd; + float sR = spd * maxSpd; + int dir = (spd > 0) ? 1 : 0; + + if(bias != 0) + { + rotateBot((bias > 0) ? 1 : 0, bias); + return; + } + + if( sL < -moveFactor * maxSpd) + { + sL = -moveFactor * maxSpd; + } + if( sL > moveFactor * maxSpd) + { + sL = moveFactor * maxSpd; + } + + if( sR < -moveFactor * maxSpd) + { + sR = -moveFactor * maxSpd; + } + if( sR > moveFactor * maxSpd) + { + sR = moveFactor * maxSpd; + } + + if (sL < 0) + { + sL *= -1; + } + + if (sR < 0) + { + sR *= -1; + } + + rt_kprintf("Speed Left: %ld\n", (rt_int32_t)sL); + rt_kprintf("Speed Right: %ld\n", (rt_int32_t)sR); + + if(dir) + { + rt_pwm_set(pwm_dev, fl_for, maxSpd, (rt_int32_t)sL); + rt_pwm_set(pwm_dev, fl_back, maxSpd, 0); + rt_pwm_set(pwm_dev, fr_for, maxSpd, (rt_int32_t)sR); + rt_pwm_set(pwm_dev, fr_back, maxSpd, 0); + } + else + { + rt_pwm_set(pwm_dev, fl_for, maxSpd, 0); + rt_pwm_set(pwm_dev, fl_back, maxSpd, (rt_int32_t)sL); + rt_pwm_set(pwm_dev, fr_for, maxSpd, 0); + rt_pwm_set(pwm_dev, fr_back, maxSpd, (rt_int32_t)sR); + } + + rt_thread_mdelay(1); +} + + +// 小车旋转 +void MotorControl::rotateBot(int dir, float spd) { + float s = spd * maxSpd; + if (dir < 0) + { + s *= -1; + } + if(dir) + { + // Clockwise + rt_pwm_set(pwm_dev, fl_for, maxSpd, (rt_int32_t)s); + rt_pwm_set(pwm_dev, fl_back, maxSpd, 0); + rt_pwm_set(pwm_dev, fr_for, maxSpd, 0); + rt_pwm_set(pwm_dev, fr_back, maxSpd, (rt_int32_t)s); + } + else + { + // Counter Clockwise + rt_pwm_set(pwm_dev, fl_for, maxSpd, 0); + rt_pwm_set(pwm_dev, fl_back, maxSpd, (rt_int32_t)s); + rt_pwm_set(pwm_dev, fr_for, maxSpd, (rt_int32_t)s); + rt_pwm_set(pwm_dev, fr_back, maxSpd, 0); + } + rt_thread_mdelay(1); +} + +//Turn off both motors +void MotorControl::stopMotors() +{ + rt_pwm_set(pwm_dev, fl_for, maxSpd, 0); + rt_pwm_set(pwm_dev, fl_back, maxSpd, 0); + rt_pwm_set(pwm_dev, fr_for, maxSpd, 0); + rt_pwm_set(pwm_dev, fr_back, maxSpd, 0); +} +``` + +一共只需要这么一点代码就可以实现和 ROS 的连接了,所以其实 ROS 也不是那么神秘,它就是因为简单好用所以才这么受欢迎的。 + +既然 RT-Thread 已经配置好了,下一步就是 ROS 的配置了。 + +### 2.2 ROS 配置 + +我们把上面 RT-Thread 的固件传到板子上以后,可以用一个 USB-TTL 一边和 STM32 控制板的 USART2 连接,另一边插到 ARM 控制板的 USB 口,接下来就可以建立连接了,在 ARM 板上输入命令: + +``` +$ rosrun rosserial_python serial_node.py /dev/ttyUSB0 +``` + +如果看到下面的输出,那就成功建立连接了: + +``` +tpl@nanopineoplus2:~$ rosrun rosserial_python serial_node.py /dev/ttyUSB0 +[INFO] [1567239474.258919]: ROS Serial Python Node +[INFO] [1567239474.288435]: Connecting to /dev/ttyUSB0 at 57600 baud +[INFO] [1567239476.425646]: Requesting topics... +[INFO] [1567239476.464336]: Note: publish buffer size is 512 bytes +[INFO] [1567239476.471349]: Setup publisher on vel_x [std_msgs/Float64] +[INFO] [1567239476.489881]: Setup publisher on turn_bias [std_msgs/Float64] +[INFO] [1567239476.777573]: Note: subscribe buffer size is 512 bytes +[INFO] [1567239476.785032]: Setup subscriber on cmd_vel [geometry_msgs/Twist] +``` + +### 2.3 ROS 控制小车 + +既然已经成功建立连接了,下一步就是写小车控制的代码了。 + +我们先初始化一个工作区间: + +``` +$ mkdir catkin_workspace && cd catkin_workspace +$ catkin_init_workspace +``` + +接下来创建一个软件包: + +``` +$ cd src +$ catkin_create_pkg my_first_pkg rospy +``` + +这样就会自动在 src 目录创建一个 ROS 软件包了。 + +我们在 catkin_workspace/src/my_first_pkg/src 目录下新建一个文件 ros_cmd_vel_pub.py: + +``` +#!/usr/bin/python + +import rospy +from geometry_msgs.msg import Twist +from pynput.keyboard import Key, Listener + +vel = Twist() +vel.linear.x = 0 + +def on_press(key): + + try: + if(key.char == 'w'): + print("Forward") + vel.linear.x = 0.8 + vel.angular.z = 0 + + if(key.char == 's'): + print("Backward") + vel.linear.x = -0.8 + vel.angular.z = 0 + + if(key.char == 'a'): + print("Counter Clockwise") + vel.linear.x = 0 + vel.angular.z = -0.8 + + if(key.char == 'd'): + print("Clockwise") + vel.linear.x = 0 + vel.angular.z = 0.8 + + return False + + except AttributeError: + print('special key {0} pressed'.format(key)) + return False + +def on_release(key): + vel.linear.x = 0 + vel.angular.z = 0 + + return False + +# Init Node +rospy.init_node('my_cmd_vel_publisher') +pub = rospy.Publisher('cmd_vel', Twist, queue_size=10) + +# Set rate +rate = rospy.Rate(10) + +listener = Listener(on_release=on_release, on_press = on_press) + +while not rospy.is_shutdown(): + print(vel.linear.x) + pub.publish(vel) + vel.linear.x = 0 + vel.angular.z = 0 + rate.sleep() + + if not listener.running: + listener = Listener(on_release=on_release, on_press = on_press) + listener.start() +``` + +这就是我们的 python 控制程序了,可以使用键盘的 wasd 控制小车前进后退,顺时针、逆时针旋转。我们需要给它添加可执行权限: + +``` +$ chmod u+x ./ros_cmd_vel_pub.py +``` + +这样就可以编译软件包了,在 catkin_worspace 目录下。 + +``` +$ catkin_make +$ source devel/setup.bash +``` + +我们终于就可以启动程序从电脑上控制小车运动了: + +``` +rosrun my_first_pkg ros_cmd_vel_pub.py +``` + +可以看到用 ROS 实现小车控制其实代码量并不算多,只需要在自己小车原有的代码上发布一些话题,告诉 ROS 小车当前的状态,并且订阅一个话题接收 ROS 的控制指令就可以了。 + +### 2.4 参考文献 + +- [ros-pibot](https://github.com/wuhanstudio/ros-pibot) + +## 3 RT-Thread 无线连接 ROS + +### 3.1 rosserial 配置 + +其实无线连接和有线连接几乎是一模一样的,只不过是先用 ESP8266 使自己的控制板能连上网,然后用 tcp 连接和 ROS 通信,关于 RT-Thread 使用 ESP8266 上网的教程可以参照 [官网](https://www.rt-thread.org/document/site/application-note/components/at/an0014-at-client/),非常详细了,我这里就不重复了。 + +确保开发板有网络连接后,我们就可以在 rosserial 里面配置为使用 tcp 连接: + +![img](figures/03-tcp.png) + +我们只需要在上一部分的 main.cpp 里添加一行代码: + +``` +// 设置 ROS 的 IP 端口号 +nh.getHardware()->setConnection("192.168.1.210", 11411); + +// 添加在节点初始化之前 +nh.initNode(); +``` + +开发板就能通过 tcp 连接和 ROS 通信了,非常方便。 + +### 3.2 ROS 配置 + +由于我们使用了 tcp 连接,所以 ROS 上自然也要开启一个服务器了,之前是使用的串口建立连接,现在就是使用 tcp 了: + +``` +$ rosrun rosserial_python serial_node.py tcp +``` + +其他的代码完全不需要改变,这样我们就实现了一个 ROS 无线控制的小车了。 + +### 3.3 参考文献 + +- [RT-Thread 使用 ESP8266 上网](https://www.rt-thread.org/document/site/application-note/components/at/an0014-at-client/) + +## 4 总结 + +这里再总结一下,其实 RT-Thread 使用 rosserial 软件包和 ROS 建立连接非常简单,只需要在自己小车原有代码的基础上发布一些消息,告诉 ROS 小车当前的状态,以及订阅来自 ROS 的控制指令就可以了。 + diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-1123587.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-1123587.png new file mode 100644 index 0000000000000000000000000000000000000000..acffcd4f47c4e9461d7297d538c7fc226631dfad Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-1123587.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-8k.gif b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-8k.gif new file mode 100644 index 0000000000000000000000000000000000000000..194f54675922d5419c247d0801b7312764cd1472 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-8k.gif differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-arm.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-arm.png new file mode 100644 index 0000000000000000000000000000000000000000..40b5b05fe91683aee7b10dfe09469daa61c97248 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-arm.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-cubemx.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-cubemx.png new file mode 100644 index 0000000000000000000000000000000000000000..f3e2587b11eee812bb0914d936fd4401db7028c4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-cubemx.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-data.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-data.png new file mode 100644 index 0000000000000000000000000000000000000000..2f7359e33ac8ec9e04a469b0e90a09a4afbe416d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-data.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-env.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-env.png new file mode 100644 index 0000000000000000000000000000000000000000..863633b76aa751d5059eee845266d2e5411dcdca Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-env.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-menuconfig.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-menuconfig.png new file mode 100644 index 0000000000000000000000000000000000000000..45770a157bd2f29609cb27c125eba062e8c42f72 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-menuconfig.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-pkg.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-pkg.png new file mode 100644 index 0000000000000000000000000000000000000000..b90359c6358d4e24e9682d9042b9a0a6a5cb271b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-pkg.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-putty.png b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-putty.png new file mode 100644 index 0000000000000000000000000000000000000000..46df9a009295d84f034245441ee05bfbfbe6dcd1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-rplidar-putty.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-summary-section3.jpg b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-summary-section3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d89b5f12a838fba4f28b15ffbde57279309aab2d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/figures/05-summary-section3.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/rplidar-connect.md b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/rplidar-connect.md new file mode 100644 index 0000000000000000000000000000000000000000..1d9d74cb213631de5649d156dd27e6bc41c4b3b4 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/smart-car/rplidar-connect/rplidar-connect.md @@ -0,0 +1,207 @@ +# RT-Thread 连接 RPlidar A1 激光雷达 + +## 引言 + +最早一些智能小车可能会有一些超声波模块,用来判断前方的障碍物,并配合舵机的旋转,从而知道各个方向的障碍物分布。但是如果使用激光雷达的话,可以一次性得到360度范围内的障碍物分布。 + +![Lidar Scan](figures/05-8k.gif) + +激光雷达基本上已经成为无人驾驶的必备传感器了,当然,它的成本也并不低,这篇文档将会介绍如何利用 RT-Thread 获取思岚 RPLidar A1 的扫描数据。 + +![RPLidar A1](figures/05-summary-section3.jpg) + +其实思岚的激光雷达就是串口发送控制命令,然后激光雷达响应从串口发送扫描结果,根据思岚的[通信协议](https://download.slamtec.com/api/download/rplidar-protocol/2.1.1?lang=zh-cn)进行解析就可以了,后面的文档就不会具体介绍通信协议了,毕竟官方文档已经介绍的很详细了。 + +在开始介绍之前,需要准备好这些东西: + +- 搭建好 RT-Thread 的环境 ([Git](https://git-scm.com/), [Env 工具](https://www.rt-thread.org/page/download.html), [RT-Thread 源码](https://www.rt-thread.org/page/download.html)) +- RPLidar A1 (或者其他A系列也可以) +- STM32 (或者其他可以运行 RT-Thread 的开发板,需要有2个串口) + +## 1. 硬件接线 + +在介绍接线之前,先给出[官方资料](http://www.slamtec.com/cn/Support#rplidar-a-series)链接,在这里可以找到[用户手册](https://download.slamtec.com/api/download/rplidarkit-a1m8-usermaunal/1.0?lang=zh-cn),[ROS软件包](https://github.com/slamtec/rplidar_ros),[SDK说明](https://download.slamtec.com/api/download/rplidar-sdk-manual/1.0?lang=zh-cn),[通信协议](https://download.slamtec.com/api/download/rplidar-protocol/2.1.1?lang=zh-cn)等很多重要的资料。 + +我使用的是 STM32-L746RG Nucleo 开发板,使用其他可以运行 RT-Thread 并有 **2个串口** 的板子当然也是可以的。 + +![img](figures/05-arm.png) + +STM32 和激光雷达的接线可以参照[这里](https://wiki.slamtec.com/pages/viewpage.action?pageId=13959187),主要也就是给电机的供电,和串口 RX TX。 + +![硬件接线](figures/05-1123587.png) + +这里总结一下接线: + +| 激光雷达 | STM32 | 备注 | +| -------- | ----- | --------------------------------- | +| GND | GND | | +| RX | TX | | +| TX | RX | | +| V5.0 | VCC | | +| GND | GND | | +| VMOTOCTL | VCC | 也可以接一个 PWM 手动控制电机转速 | +| VMOTO | VCC | 电机电源 | + +接好线上电后,应当就可以看到激光雷达开始旋转。 + +## 2. RPlidar 软件包 + +### 2.1 串口配置 + +在下载 RPlidar 软件包之前,需要先配置好开发板的2个串口,一个用于 msh 调试,一个用来连接激光雷达。 + +以 STM32 为例,下载好 RT-Thread 源码后,根据我手上的板子型号,进入**rt-thread/bsp/stm32/stm32l476-st-nucleo** 目录,在 **board/CubeMX_Config** 下可以找到 CubeMX_Config.ioc 打开。 + +![img](figures/05-rplidar-cubemx.png) + +这里我配置了两个串口 UART2 (MSH) 和 UART3 (激光雷达),可以根据自己的板子调整,然后 CubeMX 生成代码,把生成的项目里的 main.c 下面的 **SystemClock_Config** 函数,复制到 board.c 里面替换。 + +接下来我们需要编辑 **board/Kconfig** 在里面添加串口的配置,当然,如果你的 bsp 已经有相关配置就不需要了。 + +``` +menuconfig BSP_USING_UART + bool "Enable UART" + default y + select RT_USING_SERIAL + if BSP_USING_UART + config BSP_USING_UART2 + bool "Enable UART2" + default n + + config BSP_UART2_RX_USING_DMA + bool "Enable UART2 RX DMA" + depends on BSP_USING_UART2 && RT_SERIAL_USING_DMA + default n + endif +``` + +### 2.2 下载软件包 + +接下来我们就可以下载软件包了,右键打开 env 工具。 + +![env](figures/05-rplidar-env.png) + +输入 menuconfig 进入 **Hardware Drivers** 打开两个串口: + +![img](figures/05-rplidar-menuconfig.png) + +再进入 RT-Thread Online Package 选中 RPLidar 软件包: + +![img](figures/05-rplidar-pkg.png) + +软件包选中以后,我们保存 KConfig 的配置,就可以下载软件包了,在 env 里面输入: + +``` +pkgs --update +``` + +这样我们就下好了 RPLidar 软件包,接下来就可以开始编译获取激光雷达的数据了。 + +如果在 **Peripheral libraries and drivers** 找不到 RPLidar 这个软件包的话,需要先在 env 下 pkgs --upgrade 一下更新软件包仓库。 + +### 2.3 编译上传 + +接线完成,串口配置完成,软件包也下载完了,最后我们就可以编译上传了。 + +编译的话我们可以用 Keil 也可以用 env 自带的 arm-none-gcc,如果使用 Keil 的话,先生成项目: + +``` +scons --target=mdk5 -s +``` + +然后就可以打开 Keil 项目编译了,如果是 arm-none-gcc,直接在 env 下编译就可以了。 + +``` +scons +``` + +这里简单介绍一下例程代码,以激光雷达复位为例: + +```c +#include +#include "rplidar.h" + +#define RPLIDAR_DEVICE_NAME "rplidar" /* 设备名称 */ + +static int rplidar_stop_example(int argc, char *argv[]) +{ + rt_err_t ret; + + // 获取激光雷达设备 + rt_device_t lidar = rp_lidar_create(RPLIDAR_DEVICE_NAME); + if(lidar == RT_NULL) + { + rt_kprintf("Failed to find device %s\n", RPLIDAR_DEVICE_NAME); + return -1; + } + + // 雷达初始化 + ret = rp_lidar_init(lidar); + if(ret != RT_EOK) + { + rt_kprintf("Failed to init lidar device\n"); + return -1; + } + + // 发送停止命令 + ret = rp_lidar_stop(lidar); + if(ret == RT_EOK) + { + rt_kprintf("Lidar stopped scanning\n"); + } + else + { + rt_kprintf("Failed to communicate with lidar device\n"); + } + + return RT_EOK; +} +MSH_CMD_EXPORT(rplidar_stop_example, rplidar stop example); +``` + +代码非常短我也增加了一些注释就不重复介绍了,主要流程就是: + +``` +1. 获取雷达设备 rp_lidar_create +2. 雷达初始化 rp_lidar_init +3. 发送控制命令 rp_lidar_scan +4. 获取雷达数据 rp_lidar_get_scan_data +``` + +### 2.4 获取激光雷达数据 + +固件编译好上传之后,就可以用串口调试助手打开 msh 了,这里我使用 **putty** 或者 **kitty**,输入 rplidar 然后 Tab 自动补全,可以看到有很多命令: + +![img](figures/05-rplidar-putty.png) + +如果输入 **rplidar_scan_and_recv_example** 就可以看到一圈扫描的结果了: + +![扫描结果](figures/05-rplidar-data.png) + +## 3. 总结 + +思岚激光雷达可以通过串口和单片机通信,然后单片机发送控制指令,例如扫描(0xA5 0x20),激光雷达就会从串口不停地发送扫描数据,接下来要做的就是数据的解析了,而这些在 RT-Thread 的 rplidar 软件包里已经完成了,更多的 API 可以参考[这里](https://github.com/wuhanstudio/rplidar/blob/master/src/rplidar.h)。 + +```c +rt_device_t rp_lidar_create(const char* lidar_name); +rt_err_t rp_lidar_init(rt_device_t lidar); + +rt_err_t rp_lidar_scan(rt_device_t lidar, _u32 timeout); +rt_err_t rp_lidar_stop(rt_device_t lidar); +rt_err_t rp_lidar_reset(rt_device_t lidar); + +rt_err_t rp_lidar_get_health(rt_device_t lidar, rplidar_response_device_health_t* health, _u32 timeout); +rt_err_t rp_lidar_get_device_info(rt_device_t lidar, rplidar_response_device_info_t* info, _u32 timeout); +rt_err_t rp_lidar_get_scan_data(rt_device_t lidar, rplidar_response_measurement_node_t* node, _u32 timeout); +u_result rp_lidar_wait_scan_data(rt_device_t lidar, rplidar_response_measurement_node_t * node, _u32 timeout); + +u_result rp_lidar_wait_resp_header(rt_device_t lidar, rplidar_ans_header_t * header, _u32 timeout); +u_result rp_lidar_recev_data(rt_device_t lidar, _u8* buffer, size_t len, _u32 timeout); +``` + +顺便一提,因为我手头只有思岚激光雷达 A1,所以 TCP 连接和一些我的设备不支持的命令还没有实现,欢迎大家有更多高级雷达的小伙伴提 PR 完善这个软件包。 + +## 4. 参考资料 + +- [思岚科技官方资料](http://www.slamtec.com/cn/Support#rplidar-a-series) +- [RPlidar 软件包](https://github.com/wuhanstudio/rplidar) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/README.md b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c8a77f00c060bd659b11baa61576710ce0190d82 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/README.md @@ -0,0 +1,67 @@ +# 分布式温度监控系统 + +## 简介 + +这是一篇应用 RT-Thread 实现分布式温度监控系统的教程,共包含 4 节的内容,介绍了由浅入深,一步步实现一个分布式温度监控系统的过程。学完这个课程可以对 RT-Thread 的 Sensor 框架、线程间的同步、线程间的通信、虚拟文件系统、物联网应用有一个更深入的了解,也能增加自己的动手实践能力。 + +分布式温度监控系统基于 STM32 系类芯片开发,支持采集多达六个分节点的温度数据,网关节点收集分节点的数据并通过 WIFI 上传云端远程实时监视,也可本地连接串口与 PC 端通讯,上位机实时显示分节点数据。该系统适用于家庭、办公室、教室等小面积场所的多点温度监控,无线传输距离可达 100m ~ 500m,具有功耗低,丢包率低,传输距离远等特点,是一个相当实用的 DIY 设计。 + +![系统](figures/system.jpg) + +## 物料清单 + +- STM32 开发板三块或以上,开发板最好有板载 SPI Flash 或 SD 卡卡槽。 + +- DS18B20 数字温度传感器两个或以上。 + +- NRF24L01 无线射频模块三个或以上。 + +- ESP8266 WIFI 模块一个。 + +## 系统实物 + +系统主控是 STM32 系类芯片,本教程基于 `stm32l475-atk-pandora`、`stm32f407-atk-explorer`、`stm32f103-dofly-M3S` 这三款 BSP 制作。教程中的代码具有良好的硬件无关性和可移植性,大家可以使用自己的开发板制作。 + +![系统实物](figures/temp-system.jpg) + +## 实际效果 + +### PC 端远程监视温度 + +网关节点连接中国移动的 OneNet 云,PC 上网页登录 OneNet 即可实现远程监视分节点的温度数据,如下图所示。 + +![PC端](figures/onenetapp1.png) + +### 移动端远程监视温度 + +移动端实现远程监视需要下载 OneNet 的官方 APP,登录账号即可查看设备数据。 + +![移动端](figures/onenetapp2.png) + +### PC 端本地监视节点温度 + +网关节点通过串口连接 PC,并在上位机中打开串口即可实现本地监视节点数据。 + +![上位机](figures/myapp.jpg) + + +## 教程目录 + +- [第 1 节:Sensor 设备框架对接实战](sensor.md)([此章节对应的视频教程](https://www.bilibili.com/video/av63644514)) + +- [第 2 节:邮箱与消息队列实战](ipc.md)([此章节对应的视频教程](https://www.bilibili.com/video/av64282535)) + +- [第 3 节:文件系统实战](dfs.md)([此章节对应的视频教程](https://www.bilibili.com/video/av64296999)) + +- [第 4 节:OneNet 连云实战](onenet.md)([此章节对应的视频教程](https://www.bilibili.com/video/av64441866)) + +## 开源代码 + +- 这个系统的全部代码都是开源到 GitHub 上的,[点击此处跳转](https://github.com/willianchanlovegithub/DIY_projects_base_on_RT-Thread)。也欢迎大家往这个仓库里面提交自己的优秀代码或者是修复 BUG。 +- 上位机代码也是开源的,[点击此处跳转](https://github.com/willianchanlovegithub/Upper_computer_of_Multi-point_temperature_monitoring_system)。欢迎大家使用。 + +> 提示:往这个项目仓库里面提交代码可以参考这篇教程——[向 RT-Thread 贡献代码](../../development-guide/github/github.md)。 + +## 继续学习 + +如果想要继续学习 `文件系统` 或 `网络` 相关的课程可以前往 [QEMU网络视频教程](../qemu-network/README.md) 学习。 diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/dfs.md b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/dfs.md new file mode 100644 index 0000000000000000000000000000000000000000..4e4d77dcc6962f1a35abf58e80deac9d1ffc6fdb --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/dfs.md @@ -0,0 +1,326 @@ +# 智能家居 DIY 教程连载(3) + +本文将从 SPI Flash 和 SD Card 两方面给大家讲解如何使用文件系统,以及针对本次 DIY 做出的一些优化,会大大增强系统性能。 + +## 本次任务 + +- 了解 RT-Thread 文件系统,在接收节点中使用文件系统,存放来自发送节点发送过来的数据。 + +上述任务比较单一,只是文件系统而已。不过,能巧妙灵活的把文件系统用好用对,可不是一件轻松的事情。 + +## RT-Thread 文件系统简要介绍 + +DFS 是 RT-Thread 提供的虚拟文件系统组件,全称为 Device File System,即设备虚拟文件系统,文件系统的名称使用类似 UNIX 文件、文件夹的风格。 + +RT-Thread DFS 组件的主要功能特点有: + +- 为应用程序提供统一的 POSIX 文件和目录操作接口:read、write、poll/select 等。 + +- 支持多种类型的文件系统,如 FatFS、RomFS、DevFS 等,并提供普通文件、设备文件、网络文件描述符的管理。 + +- 支持多种类型的存储设备,如 SD Card、SPI Flash、Nand Flash 等。 + +DFS 的层次架构如下图所示,主要分为 POSIX 接口层、虚拟文件系统层和设备抽象层。如下图: + +![board](figures/fs-layer.png) + +DFS 的更多内容,这里不过多赘述,[点击此处跳转](https://www.rt-thread.org/document/site/programming-manual/filesystem/filesystem/)。 + +## 在 SPI Flash 上使用文件系统 + +### 准备工作 + +以正点原子的潘多拉开发板 (Iot Board) 为例,教大家在 SPI Flash 上使用文件系统。 + +值得一提的是,RT-Thread 已经将 libc 那套文件系统接口对接到 DSF 上了,在 env 工具中开启 libc 和 DFS 即可,本次教程使用 libc 的那套接口进行文件的打开/关闭、读取/写入。 + +在 menuconfig 中开启 libc: + +``` +RT-Thread Components ---> + POSIX layer and C standard library ---> + [*] Enable libc APIs from toolchain +``` + +在 meunconfig 中开启 DFS,本教程使用 elmfatfs 文件系统,需要将 elmfatfs 挂载到 RT-Thread 的 DFS 上,所以 elmfatfs 也要开启: + +``` +RT-Thread Components ---> + Device virtual file system ---> + [*] Using device virtual file system + [*] Enable elm-chan fatfs +``` + +当然,不要忘记在 meunconfig 中开启 SPI Flash: + +``` +Hardware Drivers Config ---> + Onboard Peripheral Drivers ---> + [*] Enable QSPI FLASH (W25Q128 qspi1) +``` + +潘多拉开发板上的 SPI Flash 使用的是 QSPI 接口,还需要在 meunconfig 中把 QSPI 接口开启: + +``` +Hardware Drivers Config ---> + On-chip Peripheral Drivers ---> + [*] Enable QSPI BUS +``` + +退出 menuconfig 后需要输入 “scons --target=mdk5” 更新工程。 + +### 文件系统的挂载 + +本次 DIY 使用的文件系统是 elmfatfs,elmfatfs 需要在块设备上才能进行文件操作。潘多拉板子上的 SPI Flash 是 W25Q128,我们需要将 W25Q128 注册成块设备,才能使用 elmfatfs 进行文件操作。如下示例代码: + +```c +static int rt_hw_qspi_flash_with_sfud_init(void) +{ + stm32_qspi_bus_attach_device("qspi1", "qspi10", RT_NULL, 4, w25qxx_enter_qspi_mode, RT_NULL); + if (RT_NULL == rt_sfud_flash_probe("W25Q128", "qspi10")) + return -RT_ERROR; + return RT_EOK; +} +INIT_COMPONENT_EXPORT(rt_hw_qspi_flash_with_sfud_init); +``` + +在 FinSH 中输入 “list_decive”,即可看到 W25Q128 注册成了块设备了,并挂载在 QSPI 上: + +![board](figures/W25Q128.png) + +W25Q128 注册成了块设备后,就能将 elmfatfs 这个文件系统挂载到 RT-Thread 的 DFS 上了,如下示例代码: + +```c +dfs_mount("W25Q128", "/", "elm", 0, 0) +``` + +### 文件操作 + +到此为止,我们就可以使用 libc 的接口进行文件操作了,将接收到的数据以文件方式存放到 W25Q128 里面去,举个简单的例子,如下示例代码: + +```c +FILE *recvdata_p0; +recvdata_p0 = fopen("recvdata_p0.csv", "a+"); +if (recvdata_p0 != RT_NULL) +{ + fputs((char *)RxBuf_P0, recvdata_p0); + fputs("\n", recvdata_p0); + fclose(recvdata_p0); +} +``` + +在 Finsh 中输入 “ls” 可以查看当前文件系统中的文件目录,如下图: + +![board](figures/ls.png) + +输入 “cat XXX” 可以查看文件内容,如下图: + +![board](figures/cat.png) + +简单的几步就可以进行文件操作了,RT-Thread 的文件系统还是相当易用的。 + +## 在 SD Card 上使用文件系统 + +### 准备工作 + +以正点原子的潘多拉开发板 (Iot Board) 为例,教大家在 SD Card 上使用文件系统。 + +和上面的 SPI Flash 一样,在 menuconfig 中开启相关选项:SD Card,SPI(潘多拉板子的 SD 卡是用 SPI 驱动的而不是 SDIO),libc,DFS,elmfatfs。 + +### 文件系统的挂载 + +与 SPI Flash 一样,需要将 SD Card 注册成块设备,才能挂载文件系统。如下示例代码: + +```c +static int rt_hw_spi1_tfcard(void) +{ + __HAL_RCC_GPIOC_CLK_ENABLE(); + rt_hw_spi_device_attach("spi1", "spi10", GPIOC, GPIO_PIN_3); + return msd_init("sd0", "spi10"); +} +INIT_DEVICE_EXPORT(rt_hw_spi1_tfcard); +``` + +在 FinSH 中输入 “list_decive”,即可看到 SD Card 注册成了块设备了,并挂载在 SPI 上: + +![board](figures/sd0.png) + +SD Card 注册成了块设备后,就能将 elmfatfs 这个文件系统挂载到 RT-Thread 的 DFS 上了,如下示例代码: + +```c +dfs_mount("sd0", "/", "elm", 0, 0) +``` + +需要注意的是,如果大家手头的板子是使用 SDIO 接口来驱动 SD Card 的,那么将 SD Card 注册成块设备将不用我们操心,RT-Thread 源码中的 “...rt-thread\components\drivers\sdio\block_dev.c” 文件中,会将 SD Card 注册成块设备的。当然,文件系统的挂载还是需要我们手动敲代码去实现的。 + +### 文件操作 + +与 SPI Flash 一样,可以直接使用 libc 的接口进行文件操作,如下示例代码: + +```c +FILE *recvdata_p0; +recvdata_p0 = fopen("recvdata_p0.csv", "a+"); +if (recvdata_p0 != RT_NULL) +{ + fputs((char *)RxBuf_P0, recvdata_p0); + fputs("\n", recvdata_p0); + fclose(recvdata_p0); +} +``` + +## 针对本次 DIY 的一些优化 + +### 优化1 + +将文件系统用起来,进行文件操作,是一件相对比较容易的事情。不过当将文件系统运用到实际项目中的时候,往往会因为一些需求或者说是其他因素,导致事情不那么好办。就拿这个 DIY 来说,如果就像上面的示例代码这么用文件系统,虽然系统能正常工作,但是会带来一些问题: + +- 众所周知,文件的操作是需要占用大量时间和资源的,通俗来说就是慢,像文件的读,写,打开,创建等,都是比较慢的。如果发送节点发数据过来,接收节点每收到一条数据,就用文件系统记录这个数据,这样会导致系统性能下降。如何保证减少文件操作次数,提高系统性能,又能保证每条数据都不丢失呢? + +这里使用 ringbuffer 来避免这个问题。 + +ringbuffer 是一种先进先出的 FIFO 环形缓冲区,DIY 的接收节点工程中,我们创建了两个线程去工作,一个是 `nrf24l01_thread` 线程,用于接收来自发送节点的数据,另一个是 `DFS_thread` 线程,用于利用文件系统保存数据的。并且创建一个 4KB 大小的一个 ringbuffer: + +```c +static struct rt_ringbuffer *recvdatabuf; +recvdatabuf = rt_ringbuffer_create(4069); /* ringbuffer的大小是4KB */ +``` + +每当 `nrf24l01_thread` 线程接收到一条数据,就存放到 ringbuffer 中去: + +```c +rt_ringbuffer_put(recvdatabuf, (rt_uint8_t *)str_data, strlen(str_data)); +``` +在`DFS_thread` 线程中,我们设置一个 ringbuffer 的阈值,这里我将阈值设置成了 ringbuffer 大小的一半,当写入的数据达到了 ringbuffer 的阈值之后,就将 ringbuffer 中所有的数据统统写入文件中去: + +```c +/* 判断写入的数据大小到没到所设置的ringbuffer的阈值 */ +if (rt_ringbuffer_data_len(recvdatabuf) > (4096 / 2)) +{ + /* 到阈值就直接写数据 */ + recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+"); + if (recvdatafile_p0 != RT_NULL) + { + while(rt_ringbuffer_data_len(recvdatabuf)) + { + size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, (4096 / 2)); + fwrite(writebuffer, 1, size, recvdatafile_p0); + } + fclose(recvdatafile_p0); + } +} +``` + +这么做,就可以尽可能的减少了文件的操作,提高了系统的性能,同时又保证每一条数据都不会丢失。 + +### 优化2 + +结合上述的优化1,再仔细想想的话,发现还有另一个问题: + +- 如果发送节点很久很久才发数据过来,或者说是接收节点很久很久才收到数据,那么 ringbuffer 要很久很久才能到阈值。如果这时候,已经写了整整一天的数据进 ringbuffer 中了,只差一点点就要到阈值了,很快就可以将数据写入到文件中去了,这时候偏偏断电了!整整一天的数据白白丢失了,心痛吗? + +当然,掉电丢数据这种情况是不可以避免的,但是我们可以通过一些算法优化(姑且叫它算法吧),尽可能的减少丢失数据的可能。 + +解决思路是:定个固定时间,计时,如果时间一到,此时数据还没写满 ringbuffer 的阈值,这时候就不管数据到没到阈值了,直接将 ringbuffer 里的数据全部写入文件中去。要实现这个思路需要搭配事件集 (event) 使用。 + +在 `nrf24l01_thread` 线程中,每收到一个数据,就发送一个事件: + +```c +while (1) +{ + if (!rx_pipe_num_choose()) + { + /* 通过sscnaf解析收到的数据 */ + if(sscanf((char *)RxBuf_P0, "%d,+%f", &buf.timestamp, &buf.temperature) != 2) + { + /* 通过sscnaf解析收到的数据 */ + if(sscanf((char *)RxBuf_P0, "%d,-%f", &buf.timestamp, &buf.temperature) != 2) + { + continue; + } + buf.temperature = -buf.temperature; + } + sprintf(str_data, "%d,%f\n", buf.timestamp, buf.temperature); + /* 将数据存放到ringbuffer里 */ + rt_ringbuffer_put(recvdatabuf, (rt_uint8_t *)str_data, strlen(str_data)); + /* 收到数据,并将数据存放到ringbuffer里后,才发送事件 */ + rt_event_send(recvdata_event, WRITE_EVENT); + } + rt_thread_mdelay(30); +} +``` + +在`DFS_thread` 线程中,通过接收两次事件,并设置接收事件的超时时间,达到计时的目的: + +```c +while (1) +{ + /* 接收感兴趣的事件WRITE_EVENT,以永久等待方式去接收 */ + if (rt_event_recv(recvdata_event, WRITE_EVENT, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &set) != RT_EOK) + continue; + do + { + /* 接收感兴趣的事件WRITE_EVENT,以1000ms超时方式接收 */ + if (rt_event_recv(recvdata_event, WRITE_EVENT, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(1000), &set) == RT_EOK) + { + /* 判断写入的数据大小到没到所设置的ringbuffer的阈值 */ + if (rt_ringbuffer_data_len(recvdatabuf) > THRESHOLD) + { + /* 到阈值就直接写数据 */ + recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+"); + if (recvdatafile_p0 != RT_NULL) + { + while(rt_ringbuffer_data_len(recvdatabuf)) + { + size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, THRESHOLD); + fwrite(writebuffer, 1, size, recvdatafile_p0); + } + fclose(recvdatafile_p0); + } + } + /* 阈值没到就继续接收感兴趣的事件WRITE_EVENT,以1000ms超时方式接收 */ + continue; + } + /* 1000ms到了,还没有收到感兴趣的事件,这时候不管到没到阈值,直接写 */ + recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+"); + if (recvdatafile_p0 != RT_NULL) + { + while(rt_ringbuffer_data_len(recvdatabuf)) + { + size = rt_ringbuffer_get(recvdatabuf, (rt_uint8_t *)writebuffer, THRESHOLD); + fwrite(writebuffer, 1, size, recvdatafile_p0); + } + fclose(recvdatafile_p0); + } + } while(0); +} +``` + +这样就尽最大力度的解决了掉电丢失数据的可能了。当然,第二次接收事件的超时时间可以根据自己需求设定长短。 + +## 开源代码 + +为了更进一步便于大家学习,本次任务的代码已经开源啦~ [请点击这里查看](https://github.com/willianchanlovegithub/DIY_projects_base_on_RT-Thread)请给这个项目点个小星星 (Star) ^_^ + +获得更多官方技术支持,请添加 RT-Thread 小师妹为好友,备注`智能家居 DIY`,可拉进技术交流群。微信扫下方二维码添加好友: + +![board](figures/xiaoshimei.png) + +## 注意事项 + +- 源码中,只上传了两个 demo 工程,均是本次 DIY 中接收节点的代码。RECEIVE(stm32l475-atk-pandora)(SD_Card) 是在 SD Card 上使用文件系统的 demo,RECEIVE(stm32l475-atk-pandora)(SPI_Flash) 是在 SPI Flash 上使用文件系统的 demo。 + +- 发送节点的代码,在上一次任务的 demo 工程中有,这里不再重复上传相同 demo 工程 + +- SPI Flash 的 sector 大小为 4096 字节,需要在 menuconfig 中修改: + + ![board](figures/spiflashblocksize.png) + +- SD Card 的 sector 大小为 512 字节,需要在 menuconfig 中修改: + + ![board](figures/sdcardblocksize.png) + +- 本次任务基于 `...rt-thread/bsp/stm32/stm32l475-atk-pandora` 这个 bsp 完成的。 + +## 维护人 + +- [WillianChan](https://github.com/willianchanlovegithub)。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/DIY.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/DIY.jpg new file mode 100644 index 0000000000000000000000000000000000000000..760073e346b13e250cd1703fe3620136c5eaca53 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/DIY.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/W25Q128.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/W25Q128.png new file mode 100644 index 0000000000000000000000000000000000000000..61ef66d95de27341814e4c0a15123a6de9e886d4 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/W25Q128.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/cat.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/cat.png new file mode 100644 index 0000000000000000000000000000000000000000..c92371002b61e0bcfee5a2f2ced633546c23df4e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/cat.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/env_download1.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/env_download1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1117c8f37fdff8462ac792a3f2191474b7273312 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/env_download1.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/fs-layer.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/fs-layer.png new file mode 100644 index 0000000000000000000000000000000000000000..c0e0772cad4ea14dfb52d0b6274c2c21f1ddf5c7 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/fs-layer.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/kuaidixiang.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/kuaidixiang.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2bef1f53b3069af761a9a86e421332084ec200cb Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/kuaidixiang.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/list_device.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/list_device.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1c30ae8798c4fcf07f944716a9ce6ca07db35e2e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/list_device.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/ls.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/ls.png new file mode 100644 index 0000000000000000000000000000000000000000..46fcbaf0016f676faad64ecd7cc2fc1f5deece6c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/ls.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/makeapp.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/makeapp.png new file mode 100644 index 0000000000000000000000000000000000000000..6ff86d17f592d15df43549236bc93affdafd5e9b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/makeapp.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/mdk_project.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/mdk_project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc861550613e65381ce4cd60efb4b097574b498e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/mdk_project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/msg_work.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/msg_work.png new file mode 100644 index 0000000000000000000000000000000000000000..cd3ef44310c7a248777d192f24517a5c7a499f69 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/msg_work.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/myapp.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/myapp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..11949cb204ac3f5c6ae5ea5ae541708c56f16c4c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/myapp.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenet_send_cmd.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenet_send_cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..151ab08552edc599059da1a5c60c9f15a97d7f05 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenet_send_cmd.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenet_upload.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenet_upload.png new file mode 100644 index 0000000000000000000000000000000000000000..a80a49447aba22e9c3c3b7ddbf26e68266db9826 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenet_upload.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetapp1.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetapp1.png new file mode 100644 index 0000000000000000000000000000000000000000..65ea72da9ad39a1870b508fe0f9adc76d997078a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetapp1.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetapp2.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetapp2.png new file mode 100644 index 0000000000000000000000000000000000000000..3fa791bd2a7f9f352abc0beb8438f9b7c0e2cc0f Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetapp2.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetdata.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetdata.png new file mode 100644 index 0000000000000000000000000000000000000000..029b4c4b227dd7bad9b4f3d8110b12522c9ca8f8 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetdata.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetdependon.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetdependon.png new file mode 100644 index 0000000000000000000000000000000000000000..3afc583f6dd023eede8dfab8548b425f1a3df2ac Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetdependon.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetuploadok.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetuploadok.png new file mode 100644 index 0000000000000000000000000000000000000000..f9ebc96a3f1106d6381ae5ebe6c8e1f46c95a7da Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/onenetuploadok.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/packages.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/packages.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8bd813b413e0d6fe88164bf0b8c070d24cd3d2e1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/packages.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/result-nrf24l01.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/result-nrf24l01.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4fb252f3afec1ce8cefa5fe96b573af104ea4b87 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/result-nrf24l01.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/result-sensor.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/result-sensor.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7fffe23620108edb58ee5f198ccc4df303e7d87b Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/result-sensor.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sd0.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sd0.png new file mode 100644 index 0000000000000000000000000000000000000000..b7611845d6bcc01aae67cd6b5585bf69f83107cd Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sd0.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sdcardblocksize.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sdcardblocksize.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5f79c58c2774b9ea624636441ff1991a192ac6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sdcardblocksize.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sensor.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sensor.png new file mode 100644 index 0000000000000000000000000000000000000000..b0cd5a84ffadf8a5c8628b0529cb288fcf935bcc Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sensor.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sensor_in_project.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sensor_in_project.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5db5c58e9fb7d8c47d86247440cf6a6e3506f6b6 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/sensor_in_project.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/shebeiyun.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/shebeiyun.png new file mode 100644 index 0000000000000000000000000000000000000000..a1a90f614ee5cec7dae2dc2f68d109332576f3ab Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/shebeiyun.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/spiflashblocksize.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/spiflashblocksize.png new file mode 100644 index 0000000000000000000000000000000000000000..e49bc32c7ee791faf798e176163cf5fe9a3f714c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/spiflashblocksize.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/system.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/system.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a775882a94336d065c38a126c0a2b45a4065d95d Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/system.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/temp-system.jpg b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/temp-system.jpg new file mode 100644 index 0000000000000000000000000000000000000000..de668344c7b3dc29cd389537624a16fa3166d53e Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/temp-system.jpg differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/xiaoshimei.png b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/xiaoshimei.png new file mode 100644 index 0000000000000000000000000000000000000000..b70a97a0d91f3a07d6add960517ebde1a0b9dd39 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/figures/xiaoshimei.png differ diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/ipc.md b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/ipc.md new file mode 100644 index 0000000000000000000000000000000000000000..64573236e0c074629ecf044011733c71d5de746b --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/ipc.md @@ -0,0 +1,383 @@ +# 智能家居 DIY 教程连载(2) + +本文重点给大家介绍如何将邮箱和消息队列运用到实际项目中去。 + +## 本次任务 + +- 通过 ENV 工具获取 nrf24l01 软件包,并加载到 MDK 工程里面。 + +- 了解多线程间的通信,了解 IPC 中邮箱和消息队列的特性,并能灵活使用,实现 ds18b20 线程与 nrf24l01 线程之间的数据通信。 + +- 修改 nrf24l01 软件包,实现多点通信功能。 + +上述任务的重点,是要学习去灵活运用邮箱和消息队列。 + +## 软件包的获取 + +软件包可以通过 env 工具十分方便的获取到,并且加载到工程里面去,env 工具的下载链接可以在官网找到,[点击此处跳转](https://www.rt-thread.org/page/download.html)。env 的使用方法可以查看其教程进行学习,[点击此处跳转](https://www.rt-thread.org/document/site/tutorial/env-video/)。 + +![board](figures/env_download1.jpg) + +值得注意的是,在 env 中获取软件包是需要依赖于 git 的,可以去 git 官网获得下载,[点击此处跳转](https://git-scm.com/)。 + +本次任务中,我们需要用到 nrf24l01 的软件包,只需要在 menuconfig 中选中 nrf24l01 即可: + +``` +RT-Thread online packages ---> + peripheral libraries and drivers ---> + [*] nRF24L01: Single-chip 2.4GHz wireless transceiver. ---> +``` + +选中之后需要将该软件包获取到本地来,在 env 中输入 `pkgs --update` 命令回车即可。我们在工程目录的 `packages` 目录下,可以看到,nrf24l01 软件包被获取到本地来了,如下图所示: + +![board](figures/packages.jpg) + +不过该软件包现在仅仅只是获取到本地,尚未加载到 MDK 工程当中来。我们在 env 中输入 `scons --target=mdk5` 命令回车即可,执行完该命令之后打开 MDK5 工程,发现 nrf24l01 软件包成功加载到工程里面去了,如下图所示: + +![board](figures/mdk_project.jpg) + +## IPC 之邮箱实战指南 + +### 为什么要使用邮箱 + +我们需要通过 nrf24l01 无线模块进行数据发送与接收,定义:通过 nrf24l01 发送数据的是发送节点,通过 nrf24l01 接收数据的是接收节点。(本 DIY 整个项目需要至少用到两个发送节点。) + +在发送节点创建一个线程,用于无线发送数据。具体的,nrf24l01 的软件包提供了哪些 API,是如何通过这些 API 实现发送功能的,可以参考该软件包的 samples,路径为:...\packages\nrf24l01-latest\examples。 + +还记得上一次的任务吗?在 main 函数中创建了一个线程,用于获取 ds18b20 温度数据的。同理的,我们在 main 函数中再创建一个线程,该线程是用来通过 nrf24l01 发送数据的,线程入口函数是 `nrf24l01_send_entry`: + +```c +int main(void) +{ + rt_thread_t ds18b20_thread, nrf24l01_thread; + + ds18b20_thread = rt_thread_create("18b20tem", read_temp_entry, "temp_ds18b20", + 640, RT_THREAD_PRIORITY_MAX / 2, 20); + if (ds18b20_thread != RT_NULL) + { + rt_thread_startup(ds18b20_thread); + } + + nrf24l01_thread = rt_thread_create("nrfsend", nrf24l01_send_entry, RT_NULL, + 1024, RT_THREAD_PRIORITY_MAX / 2, 20); + if (nrf24l01_thread != RT_NULL) + { + rt_thread_startup(nrf24l01_thread); + } + + return RT_EOK; +} +``` + +这时候,我们的程序当中就存在了两个线程了,`ds18b20_thread` 线程用来获取温度数据,`nrf24l01_thread` 线程用来向外无线发送温度数据,那么问题来了: + +- `ds18b20_thread` 线程如何将温度数据给 `nrf24l01_thread` 线程? + +- 如果 `ds18b20_thread` 线程采集温度数据过快,`nrf24l01_thread` 线程来不及发送,怎么办? + +- 如果 `nrf24l01_thread` 线程发送数据过快,`ds18b20_thread` 线程来不及采集温度数据,怎么办? + +这时候,IPC 中的邮箱(mailbox)可以很好的解决以上问题。不过这里,我们需要将邮箱与内存池(mempool)搭配一起使用。往往而言,在实际项目中,邮箱和内存池这两个 IPC 是经常需要配套着一起使用的。为什么,且慢慢看来。 + +### 邮箱工作原理举例介绍 + +RT-Thread 的文档中心已经有对邮箱和内存池原理上的详细讲解,[点击此处跳转](https://www.rt-thread.org/document/site/programming-manual/ipc2/ipc2/)至邮箱,[点击此处跳转](https://www.rt-thread.org/document/site/programming-manual/memory/memory/)至内存池。这里不在赘述。这里通过举一个生活中的例子,去帮助大家理解邮箱和内存池。 + +如今,很多人购物都是通过电商平台购买,那避免不了是要收快递的。 + +![board](figures/kuaidixiang.jpg) + +我们拟定一个生活场景。小区内放置有快递柜,快递柜里面有很多快递箱,快递箱里面可以存放快递,快递员把快递存放到快递箱之后,会发短信通知你过来取快递,还会告诉你编号是多少,通过编号你可以找到你的快递存放在快递柜的哪个快递箱里面。 + +上面这个模型中有几个名词,我们抽取出来:快递、快递柜、快递箱、快递员、你自己、短信、编号。 + +我们将上面这个生活场景和 IPC 中的邮箱和内存池一一对应起来: + +- 快递:采集到的温度数据 + +- 快递柜:内存池 + +- 快递箱:内存池里面的内存块 + +- 快递员:`ds18b20_thread` 线程 + +- 你自己:`nrf24l01_thread` 线程 + +- 短信:邮箱中的一封邮件 + +- 编号:内存块地址指针 + +邮箱和内存池的使用,其实和上面那个收快递的生活场景是一样的: + +- 在程度的一开始,即 main 函数中,我们创建一个邮箱和一个内存池 + +- 在 `ds18b20_thread` 线程里: + + - 首先,每当该线程采集到一个温度数据(有快递来了),就在内存池里面申请一个内存块(快递员找一个空快递箱) + + - 然后,把本次采集到的这个温度数据放到内存块里(快递放入快递箱),再把内存块的地址放在邮箱里(快递编号) + + - 最后,邮件发送出去(发短信告知用户) + +- 在 `nrf24l01_thread` 线程里: + + - 首先,接收 `ds18b20_thread` 线程发送过来的邮件(用户收到短信) + + - 然后,根据邮箱中的存放的地址,知道了当前温度数据存放在哪个内存块里面,也就是说,`nrf24l01_thread` 线程找到了(收到了)当前温度数据。(根据短信里的编号知道快递放在哪里了) + + - 最后,用完了这个内存块要及时释放掉(快递取出来了,快递箱空了) + +### 在项目中运用邮箱 + +通过代码解读一下。 + +main 函数中创建一个邮箱和一个内存池是这么做的: + +```c +tmp_msg_mb = rt_mb_create("temp_mb0", MB_LEN, RT_IPC_FLAG_FIFO); /* 创建邮箱 */ +tmp_msg_mp = rt_mp_create("temp_mp0", MP_LEN, MP_BLOCK_SIZE); /* 创建内存池 */ +``` + +`ds18b20_thread` 线程的入口函数是 `read_temp_entry`,如下: + +```c +static void read_temp_entry(void *parameter) +{ + struct tmp_msg *msg; + rt_device_t dev = RT_NULL; + rt_size_t res; + + dev = rt_device_find(parameter); + if (dev == RT_NULL) + { + rt_kprintf("Can't find device:%s\n", parameter); + return; + } + + if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK) + { + rt_kprintf("open device failed!\n"); + return; + } + rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)100); + + while (1) + { + res = rt_device_read(dev, 0, &sensor_data, 1); + if (res != 1) + { + rt_kprintf("read data failed!size is %d\n", res); + rt_device_close(dev); + return; + } + else + { + /* 申请一块内存 要是内存池满了 就挂起等待 */ + msg = rt_mp_alloc(tmp_msg_mp, RT_WAITING_FOREVER); + msg->timestamp = sensor_data.timestamp; + msg->int_value = sensor_data.data.temp; + rt_mb_send(tmp_msg_mb, (rt_ubase_t)msg); + msg = NULL; + } + rt_thread_mdelay(100); + } +} +``` + +在上述代码中,该线程采集到一个温度数据之后,就会在内存池中申请内存块: + +```c +msg = rt_mp_alloc(tmp_msg_mp, RT_WAITING_FOREVER); +``` + +将温度数据存放到刚刚申请到的内存块里面: + +```c +msg->int_value = sensor_data.data.temp; +``` + +将这个存放着温度数据的内存块的地址给邮箱,然后发送邮件: + +```c +rt_mb_send(tmp_msg_mb, (rt_ubase_t)msg); +``` + +`nrf24l01_thread` 线程的入口函数是 `nrf24l01_send_entry`,如下: + +```c +static void nrf24l01_send_entry(void *parameter) +{ + struct tmp_msg *msg; + struct hal_nrf24l01_port_cfg halcfg; + nrf24_cfg_t cfg; + uint8_t rbuf[32 + 1] = {0}; + uint8_t tbuf[32] = {0}; + + nrf24_default_param(&cfg); + halcfg.ce_pin = NRF24L01_CE_PIN; + halcfg.spi_device_name = NRF24L01_SPI_DEVICE; + cfg.role = ROLE_PTX; + cfg.ud = &halcfg; + cfg.use_irq = 0; + nrf24_init(&cfg); + + while (1) + { + rt_thread_mdelay(100); + + if (rt_mb_recv(tmp_msg_mb, (rt_ubase_t*)&msg, RT_WAITING_FOREVER) == RT_EOK) + { + if (msg->int_value >= 0) + { + rt_sprintf((char *)tbuf, "temp:+%3d.%dC, ts:%d", + msg->int_value / 10, msg->int_value % 10, msg->timestamp); + } + else + { + rt_sprintf((char *)tbuf, "temp:-%2d.%dC, ts:%d", + msg->int_value / 10, msg->int_value % 10, msg->timestamp); + } + rt_kputs((char *)tbuf); + rt_kputs("\n"); + rt_mp_free(msg); /* 释放内存块 */ + msg = RT_NULL; /* 请务必要做 */ + } + if (nrf24_ptx_run(rbuf, tbuf, rt_strlen((char *)tbuf)) < 0) + { + rt_kputs("Send failed! >>> "); + } + } +} +``` + +在上述代码中,nrf24l01 软件包提供了发送数据的 API `nrf24_ptx_run`。 + +该线程接收`ds18b20_thread` 线程发送过来的邮件,并收到了温度数据: + +```c +rt_mb_recv(tmp_msg_mb, (rt_ubase_t*)&msg, RT_WAITING_FOREVER) +``` + +将温度数据发送出去: + +```c +nrf24_ptx_run(rbuf, tbuf, rt_strlen((char *)tbuf)) +``` + +用完的内存块释放掉: + +```c +rt_mp_free(msg); +msg = RT_NULL; +``` + +还有两个问题没有解答: + +- 如果`ds18b20_thread` 线程采集温度数据过快,`nrf24l01_thread` 线程来不及发送,怎么办? + +- 如果`nrf24l01_thread` 线程发送数据过快,`ds18b20_thread` 线程来不及采集温度数据,怎么办? + +这两个问题其实就是解决供过于求和供不应求的问题。 + +有没有留意到,申请内存块的代码上有一个 `RT_WAITING_FOREVER`,接收邮件的代码上也有一个 `RT_WAITING_FOREVER`。 + +这两个 `RT_WAITING_FOREVER` 就是用来解决上面两个问题的。 + +当内存池满了的时候,再也申请不到内存块了,这时候申请内存块里面的 `RT_WAITING_FOREVER` 会使得 `ds18b20_thread` 线程阻塞,并挂起,然后 MCU 就会去干别的事情去了,不断的在 `nrf24l01_thread` 线程中发送存放在内存池中的温度数据,并释放掉内存块。等一有内存块可以申请了,`ds18b20_thread` 线程被唤醒,又会往里面塞数据了。 + +同理的,如果内存池是空的,里面没有数据,接收邮件里面的 `RT_WAITING_FOREVER` 会使得`nrf24l01_thread` 线程阻塞,并挂起,然后 MCU 就会去干别的事情去了,在 `ds18b20_thread` 线程中采集温度,并申请内存块塞数据进去,内存块一旦有数据,就会发邮箱,另外一边一有邮箱收到了,就又开始工作了。 + +## IPC 之消息队列实战指南 + +### 为什么要使用消息队列 + +在本次 DIY 中,消息队列其实也是用来解决以下问题的: + +- `ds18b20_thread` 线程如何将温度数据给 `nrf24l01_thread` 线程? + +- 如果`ds18b20_thread` 线程采集温度数据过快,`nrf24l01_thread` 线程来不及发送,怎么办? + +### 消息队列工作原理举例介绍 + +![board](figures/msg_work.png) + +消息队列一般来说,不需要搭配内存池一起使用,因为消息队列创建的时候会申请一段固定大小的内存出来,作用其实和邮箱+内存池是一样的。 + +每个消息队列对象中包含着多个消息框,每个消息框可以存放一条消息,每个消息框的大小是一样的,存放的消息大小不能超过消息框的大小,即可以相同,可以小于。 + +类比生活中的例子,存钱罐,发工资了(纸币),把一张张的钱放到存钱罐里去,你自己或者你对象需要花钱了,就从存钱罐里面取钱出来用。 + +更多对消息队列的讲解,请查看 RT-Thread 文档中心,[点击此处跳转](https://www.rt-thread.org/document/site/programming-manual/ipc2/ipc2/#_13)。 + +### 在项目中运用消息队列 + +因为文章篇幅原因,这里不把代码放出来了,可打开工程查看。工程源码是已经开源了的,链接会在下面给出。 + +在 main 函数中创建消息队列: + +```c +tmp_msg_mq = rt_mq_create("temp_mq", MQ_BLOCK_SIZE, MQ_LEN, RT_IPC_FLAG_FIFO); +``` + +在 `ds18b20_thread` 线程中转载数据并发送消息队列: + +```c +msg.int_value = sensor_data.data.temp; +rt_mq_send(tmp_msg_mq, &msg, sizeof msg); +``` + +在 `nrf24l01_thread` 线程中接收消息队列: + +```c +rt_mq_recv(tmp_msg_mq, &msg, sizeof msg, RT_WAITING_FOREVER) +``` + +上面这个 `RT_WAITING_FOREVER` 作用是为了解决下面这个问题,原理和邮箱中的介绍一样: +- 如果`ds18b20_thread` 线程采集温度数据过快,`nrf24l01_thread` 线程来不及发送,怎么办? + +即而当消息队列是空的时候,可以挂起读取线程。当有新的消息到达时,挂起的线程将被唤醒以接收并处理消息。 + +nrf24l01 的多通道数据接收 + +nrf24l01 的多通道数据接收与其底层驱动相关,与 RT-Thread 的应用开发没啥关系了,可参考这篇教程学习,[点击此处跳转](https://www.rt-thread.org/qa/forum.php?mod=viewthread&tid=421259&highlight=24l01)。 + +## 本次任务结果 + +![board](figures/result-nrf24l01.jpg) + +这里只是展示三个发送节点的情况,接收节点这边代码上已经把六个节点全部支持了。手头板子够多的话,把六个发送节点全部弄出来也是OK的。 + +## 开源代码 + +为了更进一步便于大家学习,本次任务的代码已经开源啦~ [请点击这里查看](https://github.com/willianchanlovegithub/DIY_projects_base_on_RT-Thread)请给这个项目点个小星星 (Star) ^_^ + +获得更多官方技术支持,请添加 RT-Thread 小师妹为好友,备注`智能家居 DIY`,可拉进技术交流群。微信扫下方二维码添加好友: + +![board](figures/xiaoshimei.png) + +## 注意事项 + +![board](figures/DIY.jpg) + +- RECEIVE(stm32l475-atk-pandora) 是接收节点的工程,支持6个数据通道接收数据 + +- SEND1(stm32f407-atk-explorer) 是发送节点1的工程,使用 nrf24l01 的通道0传输,消息队列 demo 工程 + +- SEND2(stm32f407-atk-explorer) 是发送节点2的工程,使用 nrf24l01 的通道1传输,消息队列 demo 工程 + +- SEND3(stm32f103-dofly-M3S) 是发送节点3的工程,使用 nrf24l01 的通道2传输,消息队列 demo 工程 + +- SEND4(stm32f103-dofly-M3S) 是发送节点4的工程,使用 nrf24l01 的通道3传输,消息队列 demo 工程 + +- SEND5(stm32f103-dofly-M3S) 是发送节点5的工程,使用 nrf24l01 的通道4传输,消息队列 demo 工程 + +- SEND6(stm32f103-dofly-M3S) 是发送节点6的工程,使用 nrf24l01 的通道5传输,消息队列 demo 工程 + +- SEND1(stm32f407-atk-explorer) (mailbox+mempool)是发送节点1的工程,使用 nrf24l01 的通道0传输,邮箱+内存池 demo 工程 + +- 本次任务基于 `...rt-thread/bsp/stm32/stm32f407-atk-explorer` 和 `...rt-thread/bsp/stm32/stm32f103-dofly-M3S` 这两个 bsp 完成的。 + +## 维护人 + +- [WillianChan](https://github.com/willianchanlovegithub)。 + diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/onenet.md b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/onenet.md new file mode 100644 index 0000000000000000000000000000000000000000..7a70dcca07453f0726efe4bfb833f010b9c26456 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/onenet.md @@ -0,0 +1,274 @@ +# 智能家居 DIY 教程连载(4) + +DIY 活动已经来到了尾声,本次的任务是整个项目中最有趣也是最重要的部分——物联网。 + +## 本次任务 + +- 接收节点根据上位机数据帧格式,通过串口发送温度数据给上位机,上位机能正确显示温度曲线。 + +- 了解 RT-Thread 的 OneNet 云软件包、AT 组件,并使用它们实现将接收节点的数据通过 esp8266 wifi 模块上传至 OneNet 云端,云端要求能简单制作小应用实现实时温度远程监控。 + +本次的任务重点是对接云,云是物联网中必不可少的一个重要部分,与物联网有着密切的关联。本篇教程将给大家讲解一下如何通过 esp8266 wifi 模块将温度数据上传到 OneNet 云,实现远程监控温度。 + +本篇文章取消了对上位机使用的讲解,因为上位机的使用过于简单,大家私底下自行尝试即可。上位机的使用方法及其源码:[点击此处跳转](https://github.com/willianchanlovegithub/Upper_computer_of_Multi-point_temperature_monitoring_system)。 + +## 准备工作 + +请务必先学习以下内容,再继续看本篇文章: + +- OneNet 的产品创建与设备接入视频教程:[点击此处跳转](https://www.rt-thread.org/document/site/tutorial/qemu-network/onenet/onenet/) + +- OneNet 软件包简介与使用说明:[点击此处跳转](https://github.com/RT-Thread-packages/onenet) + +- AT Device 软件包简介与使用说明:[点击此处跳转](https://github.com/RT-Thread-packages/at_device) + +- 《RT-Thread 编程指南》中 AT 组件的章节 :[点击此处跳转](https://www.rt-thread.org/document/site/programming-manual/at/at/) + +- Paho MQTT 软件包简介与使用说明:[点击此处跳转](https://github.com/RT-Thread-packages/paho-mqtt) + +以上内容是十分重要的预备知识,不要偷懒略过上述内容的学习噢。 + +## 配置工程 + +首先,要想清楚本次任务的整个工程需要依赖什么工具才能正常工作,简单来说,我们需要将 ESP8266 对接到 OneNet 云,而 ESP8266 通过 AT Device 控制的,所以现在目标是明确的:开启 AT 和 ESP8266,并配置 OneNet 软件包中的相关参数。 + +开启 AT 和 ESP8266,ESP8266 的 WIFI 账号和密码需要写对,不然连不上网,自然就对接不上 OneNet 了: + +``` +RT-Thread online packages ---> + IoT - internet of things ---> + [*] AT DEVICE: RT-Thread AT component porting or samples for different device ---> + [*] Espressif ESP8266 ---> + (testwifi) WIFI ssid + (12345678) WIFI password + (uart2) AT client device name + (512) The maximum length of receive line buffer +``` + +开启 OneNet,完成相关参数配置,其中 `device id`、`auth info`、`api key`、`product id`、`master/product apikey` 这些参数如何配置请查看 OneNet 的产品创建与设备接入视频教程:[点此处跳转](https://www.rt-thread.org/document/site/tutorial/qemu-network/onenet/onenet/) + +``` +RT-Thread online packages ---> + IoT - internet of things ---> + IoT Cloud ---> + [*] OneNET: China Mobile OneNet cloud SDK for RT-Thread ---> + [ ] Enable OneNET sample + [*] Enable support MQTT protocol + [ ] Enable OneNET automatic register device + (534342011) device id + (201907091115) auth info + (IFzqRGOjq530YSyFY6EMda45Xdw=) api key + (258302) product id + (FV=ssEvYvHcKqN0=ZdTjM6NrGv0=) master/product apikey +``` + +值得一提的是,OneNet 软件包是需要依赖 paho-mqtt、webclient、cJSON 的,选中 OneNet 后,以上三个选项也会被自动选中,如下图: + +![board](figures/onenetdependon.png) + +注意自己手上的 ESP8266 接板子的哪个串口,需要将这个串口打开: + +``` +Hardware Drivers Config ---> + On-chip Peripheral Drivers ---> + [*] Enable UART ---> + [*] Enable UARTx +``` + +在 menuconfig 中完成以上配置之后,先 `pkgs --update` 将软件包获取到本地,再 `scons --target=mdk5` 更新工程,将相关软件包加载到工程中来。 + +## 开发思路 + +### OneNet 软件包工作原理 + +首先,我们需要大致了解一下 OneNet 软件包的工作原理。 + +OneNet 软件包数据的上传和命令的接收是基于 MQTT 实现的,OneNet 的初始化其实就是 MQTT 客户端的初始化,初始化完成后,MQTT 客户端会自动连接 OneNet 平台。数据的上传其实就是往特定的 topic 发布消息。当服务器有命令或者响应需要下发时,会将消息推送给设备。 + +获取数据流、数据点,发布命令则是基于 HTTP Client 实现的,通过 POST 或 GET 将相应的请求发送给 OneNet 平台,OneNet 将对应的数据返回,这样,我们 就能在网页上或者手机 APP 上看到设备上传的数据了。 + +下图是应用显示设备上传数据的流程图: + +![board](figures/onenet_upload.png) + +下图是应用下发命令给设备的流程图: + +![board](figures/onenet_send_cmd.png) + +### MQTT 初始化 + +了解了 OneNet 软件包的工作原理后,我们知道,OneNet 软件包数据的上传和命令的接收是基于 MQTT 实现的,OneNet 的初始化其实就是 MQTT 客户端的初始化。OneNet 软件包提供了一个接口 `onenet_mqtt_init`,供用户去初始化 MQTT,只有当 MQTT 初始化成功之后,才能做后续的操作,如上传数据到 OneNet 服务器。 + +我们创建两个线程去工作,`onenet_mqtt_init_thread` 线程用于初始化 MQTT 客户端,`onenet_upload_data_thread` 线程去上传数据给云。那么问题来了,`onenet_upload_data_thread` 线程怎么知道 MQTT 初始化成功了呢?这里使用信号量去通知。 + +要用信号量之前,第一步当然是创建一个信号量: + +```c +mqttinit_sem = rt_sem_create("mqtt_sem", RT_NULL, RT_IPC_FLAG_FIFO); +``` + +在 `onenet_mqtt_init_thread` 线程的线程入口函数中使用 OneNet 软件包提供的接口 `onenet_mqtt_init` 去初始化 MQTT,初始化成功后,释放信号量: + +```c +static void onenet_mqtt_init_entry(void *parameter) +{ + uint8_t onenet_mqtt_init_failed_times; + + /* mqtt初始化 */ + while (1) + { + if (!onenet_mqtt_init()) + { + /* mqtt初始化成功之后,释放信号量告知onenet_upload_data_thread线程可以上传数据了 */ + rt_sem_release(mqttinit_sem); + return; + } + rt_thread_mdelay(100); + LOG_E("onenet mqtt init failed %d times", onenet_mqtt_init_failed_times++); + } +} +``` + +`onenet_upload_data_thread` 线程要一直等待信号量的到来,才能上传数据,看看它的线程入口函数的伪代码: + +```c +static void onenet_upload_data_entry(void *parameter) +{ + /* 永久等待方式接收信号量,若收不到,该线程会一直挂起 */ + rt_sem_take(mqttinit_sem, RT_WAITING_FOREVER); + /* 后面用不到这个信号量了,把它删除了,回收资源 */ + rt_sem_delete(mqttinit_sem); + + while (1) + { + /* 这里是上传数据到OneNet的代码*/ + /* 这里要怎么写,后面会有教程说明 */ + } +} +``` + +在上面的代码中,其实不用信号量也能完成上述工作。这里创建两个线程,并且使用信号量去做通知工作,旨在能让大家多学习些 IPC 的使用。 + +### 上传数据至云端 + +在 ESP8266 已经正常连上 WIFI 的前提下,MQTT 初始化成功后,就可以放心大胆的上传数据给 OneNet 了。OneNet 软件包给用户提供了几个上传数据的接口,详情请查看 OneNet 软件包的 API 说明文档:[点击此处跳转](https://github.com/RT-Thread-packages/onenet/blob/master/docs/api.md)。这个 DIY 项目中需要上传的数据是温度数据,属于数字数据,可以使用下面这个 API: + +```c +rt_err_t onenet_mqtt_upload_digit(const char *ds_name, const double digit); +``` + +利用 mqtt 向 OneNET 平台发送字符串数据。 + +| **参数** | **描述** | +| -------- | -------------- | +| ds_name | 数据流名称 | +| digit | 要上传的数字 | +| **返回** | **--** | +| 0 | 上传成功 | +| -5 | 内存不足 | + +根据前两次任务的教程,我们知道 `nrf24l01_thread` 线程是用来通过 nrf24l01 射频模块接收发送节点采集到的温度数据的。那么如何将 `nrf24l01_thread` 线程收到的温度数据给 `onenet_upload_data_thread` 线程上传云呢?这里使用邮箱和内存池来实现。邮箱和内存池的使用,可以回顾一下 IPC 实战的教程:[点击此处跳转](https://github.com/willianchanlovegithub/DIY_projects_base_on_RT-Thread/tree/master/DIY1/Second_week_mission)。 + +使用邮箱和内存池是为了解决一下问题: + +- `nrf24l01_thread` 线程接收数据过快,`onenet_upload_data_thread` 线程来不及上传。 + +- `onenet_upload_data_thread` 线程上传过快,`nrf24l01_thread` 线程来不及接收数据。 + +其实就是典型的流控问题,或者说是供求关系问题。 + +在 `onenet_upload_data_thread` 线程入口函数中利用 `onenet_mqtt_upload_digit` 上传数据给 OneNet: + +```c +static void onenet_upload_data_entry(void *parameter) +{ + struct recvdata *buf_mp; + + /* 永久等待方式接收信号量,若收不到,该线程会一直挂起 */ + rt_sem_take(mqttinit_sem, RT_WAITING_FOREVER); + /* 后面用不到这个信号量了,把它删除了,回收资源 */ + rt_sem_delete(mqttinit_sem); + + while (1) + { + if (rt_mb_recv(tmp_msg_mb, (rt_ubase_t*)&buf_mp, RT_WAITING_FOREVER) == RT_EOK) + { + /* 500ms上传一次数据 */ + rt_thread_delay(rt_tick_from_millisecond(500)); + /* 上传发送节点1的数据到OneNet服务器,数据流名字是temperature_p0 */ + if (onenet_mqtt_upload_digit("temperature_p0", buf_mp->temperature_p0) != RT_EOK) + rt_kprintf("upload temperature_p0 has an error, try again\n"); + else + printf("onenet upload OK >>> temp_p0:%f\n", buf_mp->temperature_p0); + + rt_mp_free(buf_mp); /* 释放内存块 */ + buf_mp = RT_NULL; /* 请务必要做 */ + } + } +} +``` + +就是这么简单。 + +如果说,我们底下有多个发送节点采集温度,接收节点会收到多个发送节点的温度数据,而只有 一个 ESP8266,怎么在上传数据给 OneNet 的时候区分这些不同节点的数据?这里其实只需要建立不同的数据流就好了,每一个节点的数据为一个数据流,`rt_err_t onenet_mqtt_upload_digit(const char *ds_name, const double digit)` 函数的第一个参数就是数据流的名称,起不同名字就是不用的数据流了,如: + +```c +/* 上传发送节点1的数据到OneNet服务器,数据流名字是temperature_p0 */ +onenet_mqtt_upload_digit("temperature_p0", buf_mp->temperature_p0); +/* 上传发送节点2的数据到OneNet服务器,数据流名字是temperature_p1 */ +onenet_mqtt_upload_digit("temperature_p1", buf_mp->temperature_p1); +``` + +## OneNet 应用开发 + +我们关把数据上传给 OneNet 后,如何才能看到这些数据呢?OneNet 给开发者提供了一些简单应用开发的工具,我们利用这些工具在 OneNet 上创建一些简单的小应用,即可实现对数据的远程监控了。 + +![board](figures/makeapp.png) + +由于文章篇幅有限,这里就不详细展示如何制作应用了,更多内容,请查看 OneNet 的产品创建与设备接入视频教程:[点击此处跳转](https://www.rt-thread.org/document/site/tutorial/qemu-network/onenet/onenet/)。 + +## 6. 开源代码 + +为了更进一步便于大家学习,本次任务的代码已经开源啦~ [点击这里查看](https://github.com/willianchanlovegithub/DIY_projects_base_on_RT-Thread)。请给这个项目点个小星星(Star)^_^ + +获得更多官方技术支持,请添加 RT-Thread 小师妹为好友,备注`智能家居 DIY`,可拉进技术交流群。微信扫下方二维码添加好友: + +![board](figures/xiaoshimei.png) + +## 本次任务结果 + +- 在网页上查看 OneNet 云端数据,能正常收到来自每个发送节点数据流了: + +![board](figures/onenetdata.png) + +- OneNet PC端应用,可在电脑实现远程监控。(如:人坐在办公室,查看家里房间客厅的温度,看看家里着火没有): + +![board](figures/onenetapp1.png) + +- OneNet 移动端应用,可在手机平板等设备实现远程监控。(如:外出旅游,打开手机 APP,查看办公室的温度,看看办公室着火没) + +![board](figures/onenetapp2.png) + +- 本地 FinSH 信息输出,提示 mqtt 初始化成功、ESP8266 连接 WIFI 成功、数据上传 OneNet 服务器成功: + +![board](figures/onenetuploadok.png) + +## 注意事项 + +- 本次的 demo 工程只接收两个发送节点的数据,需要更多发送节点的可以自行添加。 + +- 本次的 demo 工程中的 `RECEIVE(stm32l475-atk-pandora)(SD_Card)(ESP8266)` 是利用 ESP8266 对接 OneNet 的,使用 SD Card 挂载文件系统。 + +- 本次的 demo 工程中的 `RECEIVE(stm32l475-atk-pandora)(SPI Flash)(RW007)` 是利用 RW007 对接 OneNet 的,使用 SPI Flash 挂载文件系统,RW007 的配置与 ESP8266 一样,请参考上面教程。 + +- 发送节点的程序下载 `IPC 实战` 中的 demo 就好了,GitHub 中的 `Fourth week mission` 文件夹不再包含发送节点工程。 + +- 移动端要远程监控温度需要在 OneNet 官网下载一个叫设备云的 APP,如下图,[点击此处跳转](https://open.iot.10086.cn/doc/art656.html#118): + +![board](figures/shebeiyun.png) + +- 本次任务基于 `...rt-thread/bsp/stm32/stm32l475-atk-pandora` 这个 bsp 完成的。 + +## 维护人 + +- [WillianChan](https://github.com/willianchanlovegithub)。 \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/tutorial/temperature-system/sensor.md b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/sensor.md new file mode 100644 index 0000000000000000000000000000000000000000..ca7ec0efa270117dfb950060941ae03e87f5dce1 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/tutorial/temperature-system/sensor.md @@ -0,0 +1,323 @@ +# 智能家居 DIY 教程连载(1) + +本文以 ds18b20 数字温度传感器为例教大家将手头的传感器对接上 RT-Thread 的 Sensor 框架。 + +## 本次任务: + +- 正确读取 ds18b20 温度数据。 + +- 了解 RT-Thread 的 Sensor 框架,并将 ds18b20 对接到 Sensor 框架上。 + +- 了解线程的使用,创建一个线程,在线程中读取温度数据,并通过FinSH控制台实时打印出来。 + +上述任务的重点,其实不在于把温度读取到就好了的,重点在于如何将 ds18b20 对接到 RT-Thread 的 Sensor 框架上去。 + +Sensor 是物联网重要的一部分,“Sensor 之于物联网”相当于“眼睛之于人类”。人没有眼睛就看不到这大千的花花世界,物联网没有了 Sensor 更是不能感知这变化万千的世界。 + +现在,为物联网开发的 Sensor 已经很多了,不同的传感器厂商、不同的传感器都需要配套自己独有的驱动才能运转起来,这样在开发应用程序的时候就需要针对不同的传感器做适配,自然加大了开发难度。为了降低应用开发的难度,增加传感器驱动的可复用性,我们设计了 Sensor 驱动框架。 + +Sensor 驱动框架的作用是:为上层提供统一的操作接口,提高上层代码的可重用性;简化底层驱动开发的难度,只要实现简单的 ops(operations: 操作命令) 就可以将传感器注册到系统上。 + +本次 DIY 活动,以 ds18b20 温度传感器为例子,教大家如何正确使用 Sensor 框架。 + +## Sensor 框架介绍 + +![board](figures/sensor.png) + +Sensor 框架的整体框架图如上。它为上层提供的是标准 device 接口 `open/close/read/write/control`,这些接口与上层用户程序对接,为底层驱动提供的是简单的 ops(operations: 操作命令)接口:`fetch_data/control`,这两个接口对接具体硬件的底层驱动。除此之外,Sensor 框架还支持 module(模块),为底层存在耦合的传感器设备提供服务,如果,ds18b20 的底层不存在耦合,此处不需要用到 module。 + +Sensor 框架更多的介绍在 RT-Thread 的文档中心已有详细说明,这里不过多赘述,[点击此处跳转](https://www.rt-thread.org/document/site/development-guide/sensor/sensor_driver/)。 + +## Sensor 框架的使用 + +看完文档中心的 Sensor 介绍后,相信大伙儿已经对这个框架有了一定的了解。有的小伙伴是不是早就按耐不住想要跃跃欲试将传感器对接到 Sensor 框架上?这里以 ds18b20 温度传感器为例子。 + +Sensor 框架的使用分两个步骤:ops 接口对接、传感器设备注册。 + +### ops 接口对接 + +我们知道,Sensor 框架的接口分为上层接口和底层接口两种。将 ds18b20 的底层驱动对接到框架上,其实对接就是 Sensor 框架的底层接口,具体的,是底层的 ops 接口。 + +我们在 RT-Thread 源码中可以找到 Sensor 框架的源码,源码路径为:rt-thread\components\drivers\sensors,在 `sensor.h` 文件中,我们可以找到对 ops 接口的定义,有两个函数指针,`fetch_data` 和 `contorl`。 + +```c +struct rt_sensor_ops +{ + rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len); + rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg); +}; +``` + +`fetch_data` 作用是获取传感器数据,`control` 作用是通过控制命令控制传感器,ds18b20 并不支持 `control`,我们只需要实现 `fetch_data` 就好了。 + +Sensor 框架当前默认支持轮询 (RT_DEVICE_FLAG_RDONLY)、中断 (RT_DEVICE_FLAG_INT_RX)、FIFO (RT_DEVICE_FLAG_FIFO_RX) 这三种打开方式。需要在这里判断传感器的工作模式,然后再根据不同的模式返回传感器数据。我们以轮询的方式读取 ds18b20 的温度数据,那么 `fetch_data` 的实现如下: + +```c +static rt_size_t ds18b20_fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len) +{ + RT_ASSERT(buf); + + if (sensor->config.mode == RT_SENSOR_MODE_POLLING) + { + return _ds18b20_polling_get_data(sensor, buf); + } + else + return 0; +} +``` + +具体的,`_ds18b20_polling_get_data(sensor, buf)` 的实现如下,其中,`ds18b20_get_temperature` 函数就是 ds18b20 温度传感器底层驱动的获取温度的函数。 + +```c +static rt_size_t _ds18b20_polling_get_data(rt_sensor_t sensor, struct rt_sensor_data *data) +{ + rt_int32_t temperature_x10; + if (sensor->info.type == RT_SENSOR_CLASS_TEMP) + { + temperature_x10 = ds18b20_get_temperature((rt_base_t)sensor->config.intf.user_data); + data->data.temp = temperature_x10; + data->timestamp = rt_sensor_get_ts(); + } + return 1; +} +``` + +因为不需要 `control`,我们直接让 `control` 返回 `RT_EOK` 即可 + +```c +static rt_err_t ds18b20_control(struct rt_sensor_device *sensor, int cmd, void *args) +{ + rt_err_t result = RT_EOK; + + return result; +} +``` + +这样,我们的 ops 函数就写好了。然后,需要实现一个设备接口的结构体 ops 存储上面的接口函数: + +```c +static struct rt_sensor_ops sensor_ops = +{ + ds18b20_fetch_data, + ds18b20_control +}; +``` + +这样一来, ops 接口就对接成功了。 + +### 传感器设备注册 + +完成 Sensor 的 ops 的对接之后还要注册一个 Sensor 设备,这样上层才能找到这个传感器设备,进而进行控制。 + +设备的注册一共需要下面几步: + +- 创建一个 rt_sensor_t 的结构体指针 +- 为结构体分配内存 +- 完成相关初始化 + +具体的,放到 ds18b20 上面来,具体实现如下: + +```c +int rt_hw_ds18b20_init(const char *name, struct rt_sensor_config *cfg) +{ + rt_int8_t result; + rt_sensor_t sensor_temp = RT_NULL; + + if (!ds18b20_init((rt_base_t)cfg->intf.user_data)) + { + /* temperature sensor register */ + sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device)); + if (sensor_temp == RT_NULL) + return -1; + + sensor_temp->info.type = RT_SENSOR_CLASS_TEMP; + sensor_temp->info.vendor = RT_SENSOR_VENDOR_DALLAS; + sensor_temp->info.model = "ds18b20"; + sensor_temp->info.unit = RT_SENSOR_UNIT_DCELSIUS; + sensor_temp->info.intf_type = RT_SENSOR_INTF_ONEWIRE; + sensor_temp->info.range_max = SENSOR_TEMP_RANGE_MAX; + sensor_temp->info.range_min = SENSOR_TEMP_RANGE_MIN; + sensor_temp->info.period_min = 5; + + rt_memcpy(&sensor_temp->config, cfg, + sizeof(struct rt_sensor_config)); + sensor_temp->ops = &sensor_ops; + + result = rt_hw_sensor_register(sensor_temp, + name, + RT_DEVICE_FLAG_RDONLY, + RT_NULL); + if (result != RT_EOK) + { + LOG_E("device register err code: %d", result); + goto __exit; + } + + } + return RT_EOK; + +__exit: + if (sensor_temp) + rt_free(sensor_temp); + return -RT_ERROR; +} +``` + +我们来解读一下。 + +传感器设备注册的第一步:创建一个 rt_sensor_t 的结构体指针,上述代码中是这么实现的: + +```c +rt_sensor_t sensor_temp = RT_NULL; +``` + +传感器设备注册的第二步:为结构体分配内存,上述代码中是这么实现的: + +```c +sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device)); +if (sensor_temp == RT_NULL) + return -1; +``` + +传感器设备注册的第三步:完成相关初始化,上述代码中是这么实现的: + +```c +sensor_temp->info.type = RT_SENSOR_CLASS_TEMP; +sensor_temp->info.vendor = RT_SENSOR_VENDOR_DALLAS; +sensor_temp->info.model = "ds18b20"; +sensor_temp->info.unit = RT_SENSOR_UNIT_DCELSIUS; +sensor_temp->info.intf_type = RT_SENSOR_INTF_ONEWIRE; +sensor_temp->info.range_max = SENSOR_TEMP_RANGE_MAX; +sensor_temp->info.range_min = SENSOR_TEMP_RANGE_MIN; +sensor_temp->info.period_min = 5; + +rt_memcpy(&sensor_temp->config, cfg, sizeof(struct rt_sensor_config)); +sensor_temp->ops = &sensor_ops; + +``` + +传感器设备注册的三个步骤完成之后,就可以放心大胆地注册传感器设备了,上述代码中是这么实现的: + +```c +rt_hw_sensor_register(sensor_temp, name, RT_DEVICE_FLAG_RDONLY, RT_NULL); + +``` + +上述的“ops 接口对接”和“传感器设备注册”两个工作完成后,就可以通过 Sensor 框架中的上层接口 `open/close/read/write/control`,对 ds18b20 进行操作了。 + +先不着急,我们在 FinSH 中输入 `list_device` 命令查看 ds18b20 温度传感器是否真的已经被注册上去了: + +![board](figures/list_device.jpg) + +哇!成功将 ds18b20 注册成传感器设备了,可喜可贺!!! + +传感器驱动对接 Sensor 框架的操作中的更多细节,请在 RT-Thread 的文档中心中查看,[点击此处跳转](https://www.rt-thread.org/document/site/development-guide/sensor/sensor_driver_development/)。 + +## 在线程中读取温度数据 + +我们通过一个线程,去实时获取 ds18b20 的温度数据。 + +线程的基本操作有:创建/初始化 ( rt_thread_create/rt_thread_init)、启动 (rt_thread_startup)、运行 (rt_thread_delay/rt_thread_control)、删除/脱离 (rt_thread_delete/rt_thread_detach)。 + +之前我们已经将 ds18b20 对接到 ops 接口并成功注册成传感器设备了,接下来就可以利用 Sensor 框架的上层接口 `open/close/read/write/control` 对 ds18b20 进行操作了。 + +在 `main` 函数中创建一个读取 ds18b20 温度数据的线程并启动它,线程入口函数是 `read_temp_entry`: + +```c +rt_thread_t ds18b20_thread, led_thread; + +ds18b20_thread = rt_thread_create("18b20tem", + read_temp_entry, + "temp_ds18b20", + 512, + RT_THREAD_PRIORITY_MAX / 2, + 20); +if (ds18b20_thread != RT_NULL) +{ + rt_thread_startup(ds18b20_thread); +} + +``` + +在线程入口函数 `read_temp_entry` 中,我们通过几个步骤,就可以读取 ds18b20 的温度数据了: + +- 创建一个 rt_sensor_data 的数据结构体 +- 查找传感器设备驱动 +- 打开对应的传感器设备 +- 读取传感器设备数据 + +上述步骤具体实现如下: + +```c +static void read_temp_entry(void *parameter) +{ + rt_device_t dev = RT_NULL; + struct rt_sensor_data sensor_data; + rt_size_t res; + + dev = rt_device_find(parameter); + if (dev == RT_NULL) + { + rt_kprintf("Can't find device:%s\n", parameter); + return; + } + + if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK) + { + rt_kprintf("open device failed!\n"); + return; + } + rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)100); + + while (1) + { + res = rt_device_read(dev, 0, &sensor_data, 1); + if (res != 1) + { + rt_kprintf("read data failed!size is %d\n", res); + rt_device_close(dev); + return; + } + else + { + rt_kprintf("temp:%3d.%dC, timestamp:%5d\n", sensor_data.data.temp / 10, sensor_data.data.temp % 10, sensor_data.timestamp); + } + rt_thread_mdelay(100); + } +} + +``` + +通过 FinSH 控制台,查看该线程源源不断输出的数据: + +![board](figures/result-sensor.jpg) + +温度数据在线程中被正确读取出来了,到此为止,本次任务就算是成功完成了的,因吹斯听 ~ ^_^ + +## 开源代码 + +为了更进一步便于大家学习,本次任务的代码已经开源啦~ [请点击这里查看](https://github.com/willianchanlovegithub/DIY_projects_base_on_RT-Thread)请给这个项目点个小星星 (Star) ^_^ + +获得更多官方技术支持,请添加 RT-Thread 小师妹为好友,备注`智能家居 DIY`,可拉进技术交流群。微信扫下方二维码添加好友: + +![board](figures/xiaoshimei.png) + +## 注意事项 + +- 欲使用 Sensor 框架,当然是要在 menuconfig 中将它开启啦: + +``` +RT-Thread Components ---> + Device Drivers ---> + [*] Using Sensor device drivers +``` + +- 打开之后,需要使用 `scons --target=mdk5` 更新工程。看,Sensor 框架加入到工程当中了: + +![board](figures/sensor_in_project.jpg) + +- 本次任务基于 `...rt-thread/bsp/stm32/stm32f407-atk-explorer` 这个 bsp 完成的。 + +## 维护人 + +- [WillianChan](https://github.com/willianchanlovegithub)。 \ No newline at end of file