mirror of
https://github.com/ferrous-systems/embedded-trainings-2020.git
synced 2024-05-16 23:43:01 +00:00
Compare commits
73 commits
474ea1cdb2
...
5e6fb4ab06
Author | SHA1 | Date | |
---|---|---|---|
5e6fb4ab06 | |||
a4ee38540d | |||
04fc983107 | |||
33778655e4 | |||
3a4acd3543 | |||
55b40e8b47 | |||
15302d1e38 | |||
c097fcfab5 | |||
70ce745930 | |||
ee0f7b1145 | |||
b09dd739f8 | |||
5e5ccb510b | |||
7701ae8696 | |||
debd7658b7 | |||
f981189222 | |||
ee29b74c7f | |||
08654a53da | |||
1db5935bbb | |||
8de826d0bc | |||
4fb7415294 | |||
81b6657703 | |||
dc9232f25e | |||
d6470363d4 | |||
274767bc51 | |||
89ee72eb49 | |||
a3f653de23 | |||
b5c3480d25 | |||
5fc64c53a2 | |||
f0bf2ec6c3 | |||
e7eef7aff9 | |||
2505e00e32 | |||
5f079cdd88 | |||
ab2d9081bd | |||
bfcb572bc1 | |||
30fe350443 | |||
9a354f2c29 | |||
3b72b1b530 | |||
38c963e8b0 | |||
5b11a4de97 | |||
37930c3913 | |||
59a08c6854 | |||
71ad8b0b5a | |||
3465441108 | |||
b2adf4a8d1 | |||
a1e990b1e9 | |||
373e791693 | |||
cd345aad95 | |||
166550667b | |||
16fbd1c39c | |||
a142d1579d | |||
453eb95928 | |||
dffa5d483b | |||
bf87279d4a | |||
c1825d1185 | |||
3afc29ca72 | |||
14d76fb10e | |||
25cd0faf70 | |||
1891f17253 | |||
ad7f9db944 | |||
fd5179420f | |||
8b31518a6f | |||
f21124a304 | |||
5f8e4b1223 | |||
6529d5019c | |||
aa907e9f20 | |||
c4cc515666 | |||
9e46b5732e | |||
923b850102 | |||
a9293e68a6 | |||
3f56cd66d8 | |||
028a850596 | |||
e98ddd23e5 | |||
eaab43590e |
1
down-the-stack-book/.gitignore
vendored
Normal file
1
down-the-stack-book/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
book
|
11
down-the-stack-book/README.md
Normal file
11
down-the-stack-book/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Down the Stack Slides
|
||||
|
||||
To read as an mdbook:
|
||||
|
||||
1. `cargo install mdbook`
|
||||
2. `mdbook serve`
|
||||
|
||||
To read as a slide-show:
|
||||
|
||||
1. Download [marp-cli](https://github.com/marp-team/marp-cli/releases) and unpack into your `$PATH`
|
||||
2. `marp -c marp.config.js -I . -s`
|
16
down-the-stack-book/book.toml
Normal file
16
down-the-stack-book/book.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[book]
|
||||
authors = ["Jonathan Pallant (Ferrous Systems)"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
title = "Down the Stack"
|
||||
|
||||
[preprocessor]
|
||||
|
||||
[preprocessor.mermaid]
|
||||
command = "mdbook-mermaid"
|
||||
|
||||
[output]
|
||||
|
||||
[output.html]
|
||||
additional-js = ["mermaid.min.js", "mermaid-init.js"]
|
6
down-the-stack-book/marp.config.js
Normal file
6
down-the-stack-book/marp.config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
inputDir: './slides',
|
||||
engine: ({ marp }) => marp.use(require('@kazumatu981/markdown-it-kroki'), {
|
||||
entrypoint: "https://kroki.io",
|
||||
})
|
||||
}
|
1
down-the-stack-book/mermaid-init.js
Normal file
1
down-the-stack-book/mermaid-init.js
Normal file
|
@ -0,0 +1 @@
|
|||
mermaid.initialize({startOnLoad:true});
|
1282
down-the-stack-book/mermaid.min.js
vendored
Normal file
1282
down-the-stack-book/mermaid.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
13
down-the-stack-book/node_modules/.package-lock.json
generated
vendored
Normal file
13
down-the-stack-book/node_modules/.package-lock.json
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "down-the-stack-book",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@kazumatu981/markdown-it-kroki": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@kazumatu981/markdown-it-kroki/-/markdown-it-kroki-1.1.1.tgz",
|
||||
"integrity": "sha512-LDYl+mV2WogLQ5r4olxovm+gphL/MNGfWZ1M1woBO/YhFnfwdn5EAUu9zF/KoVZzytJPq0RNfyeDtMkv+GJihg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
17
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/CHANGELOG.md
generated
vendored
Normal file
17
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/CHANGELOG.md
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
# CHANGELOG
|
||||
|
||||
## v1.1.1
|
||||
|
||||
* fix readme, see [this issue](https://github.com/kazumatu981/markdown-it-kroki/issues/1)
|
||||
|
||||
## v1.1.0
|
||||
|
||||
* Obsolated Option `marpAutoScaling` and detect automatically wether it is nessesury or not.
|
||||
|
||||
## v1.0.1
|
||||
|
||||
release on npm
|
||||
|
||||
## v1.0.0
|
||||
|
||||
create new
|
21
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/LICENSE
generated
vendored
Normal file
21
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Kazuyoshi Matsumoto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
157
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/README.md
generated
vendored
Normal file
157
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/README.md
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
# markdown-it-kroki
|
||||
|
||||
> This library was designed to embed [Kori.io](https://kroki.io/) diagram into [Marp](https://marp.app/) Slide-deks!!
|
||||
|
||||
|
||||
This library is a pugin for markdown-it to embed figure is created by textual syntax.
|
||||
To use this package, You can embed **Software Diagram** (like uml) is written by **code** in Marp Slides-deck.
|
||||
|
||||
See marp sample code.
|
||||
|
||||
## Sample
|
||||
|
||||
---
|
||||
marp: true
|
||||
---
|
||||
|
||||
|
||||
## plantuml
|
||||
|
||||
```plantuml[platuml image]
|
||||
@startuml
|
||||
left to right direction
|
||||
actor Guest as g
|
||||
package Professional {
|
||||
actor Chef as c
|
||||
actor "Food Critic" as fc
|
||||
}
|
||||
package Restaurant {
|
||||
usecase "Eat Food" as UC1
|
||||
usecase "Pay for Food" as UC2
|
||||
usecase "Drink" as UC3
|
||||
usecase "Review" as UC4
|
||||
}
|
||||
fc --> UC4
|
||||
g --> UC1
|
||||
g --> UC2
|
||||
g --> UC3
|
||||
@enduml
|
||||
```
|
||||
|
||||
![plantuml-sample](img/plantuml-sample.png)
|
||||
|
||||
If you want to write daigram, you write Diagram Language (like [plantuml](https://plantuml.com/), [mermaid.js](https://mermaid-js.github.io/mermaid/#/)) with in fenced code block.
|
||||
|
||||
## How to install
|
||||
|
||||
You can install `npm install` command like bellow.
|
||||
|
||||
```bash
|
||||
npm install -D @kazumatu981/markdown-it-kroki
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
Here is the how to use `markdow-it-kroki`.
|
||||
This section introduce how to create Marp slides-deck project,
|
||||
and introduce how to create Marp slides-deck server.
|
||||
|
||||
You can find deltail info in [here](https://marp.app/),
|
||||
and you can learn about marp plugin eco-system, [here](https://marpit.marp.app/usage?id=extend-marpit-by-plugins).
|
||||
|
||||
### **[1st step]** Create Slides-deck project
|
||||
|
||||
First, for create slides-deck, you have to prepair to **Marp Project** directory.
|
||||
So First, Create slides-deck project, and init npm package.
|
||||
|
||||
```bash
|
||||
mkdir myslides
|
||||
cd myslides
|
||||
|
||||
npm init -y
|
||||
```
|
||||
|
||||
Secondary, Build Marp Environment.
|
||||
Install [@marp-team/marp-cli](https://github.com/marp-team/marp-cli).
|
||||
|
||||
```bash
|
||||
npm install -D @marp-team/marp-cli
|
||||
```
|
||||
|
||||
> Off-course you can install as **global package** (like `npm install -g @marp-team/marp-cli`), or **run at-once** (like `npx`).
|
||||
|
||||
### **[2nd step]** Download this project and install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/kazumatu981/markdown-it-kroki.git
|
||||
|
||||
cd myslides
|
||||
npm install -D path/to/markdown-it-kroki
|
||||
```
|
||||
|
||||
|
||||
### **[3rd step]** Create `marp.config.js`.
|
||||
|
||||
Here is the configuration file for **Marp**.
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
inputDir: './slides',
|
||||
engine: ({ marp }) => marp.use(require('@kazumatu981/markdown-it-kroki'), {
|
||||
entrypoint: "https://kroki.io",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### **[4th step]** Create your slides
|
||||
|
||||
On `slies` directory. you create slides-deck. like this.
|
||||
|
||||
---
|
||||
marp: true
|
||||
---
|
||||
|
||||
|
||||
## mermaid
|
||||
|
||||
```mermaid[mermaid image]
|
||||
flowchart TD
|
||||
Start --> Stop
|
||||
```
|
||||
|
||||
### **[5th step]** run server
|
||||
|
||||
Run marp server.
|
||||
|
||||
```bash
|
||||
marp -s -c marp.config.js
|
||||
```
|
||||
|
||||
## Detail
|
||||
|
||||
### Syntax of Markdown
|
||||
|
||||
#### Diagram Language
|
||||
|
||||
You have to write diagram language by [fenced code block](https://spec.commonmark.org/0.30/#fenced-code-blocks) syntax, start with **triple back quot** and after that the language.
|
||||
|
||||
```plantuml
|
||||
|
||||
This package depends on kroki.io.
|
||||
If you want to know which is **supported diagram language**,
|
||||
you will see in [Kroki.io official web site (https://kroki.io/)](https://kroki.io/).
|
||||
|
||||
#### Alt Text
|
||||
|
||||
You can write Alt-text attribute to IMG tag in HTML.
|
||||
Write in `square blacket` after **Diagram Language**.
|
||||
|
||||
```mermaid [check your network config..]
|
||||
|
||||
### Options of `constructor`
|
||||
|
||||
| property-name | type | mean | defaul value |
|
||||
| ---------------- | -------- | ------------------------------------------------------ | ------------------------- |
|
||||
| `entrypoint` | `string` | The entry point for Kroki server. | `'https://kroki.io'` |
|
||||
| `containerClass` | `string` | class name of container (`DIV`-tag `class` attribute). | `'kroki-image-container'` |
|
||||
| `imageFormat` | `string` | image format of diagram. see [here](https://kroki.io/) | `'svg'` |
|
8
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/demo/marp.config.js
generated
vendored
Normal file
8
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/demo/marp.config.js
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
module.exports = {
|
||||
inputDir: './',
|
||||
engine: ({ marp }) => marp.use(require('../index'), {
|
||||
entrypoint: "https://kroki.io",
|
||||
marpAutoScaling: true
|
||||
})
|
||||
}
|
59
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/demo/test.md
generated
vendored
Normal file
59
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/demo/test.md
generated
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
marp: true
|
||||
---
|
||||
|
||||
# @kazumatu981/markdown-it-kroki
|
||||
|
||||
## Marp Sample
|
||||
|
||||
---
|
||||
|
||||
## plantuml
|
||||
|
||||
```plantuml[platuml image]
|
||||
@startuml
|
||||
left to right direction
|
||||
actor Guest as g
|
||||
package Professional {
|
||||
actor Chef as c
|
||||
actor "Food Critic" as fc
|
||||
}
|
||||
package Restaurant {
|
||||
usecase "Eat Food" as UC1
|
||||
usecase "Pay for Food" as UC2
|
||||
usecase "Drink" as UC3
|
||||
usecase "Review" as UC4
|
||||
}
|
||||
fc --> UC4
|
||||
g --> UC1
|
||||
g --> UC2
|
||||
g --> UC3
|
||||
@enduml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## mermaid
|
||||
|
||||
```mermaid[mermaid image]
|
||||
graph TD
|
||||
A[ Anyone ] -->|Can help | B( Go to github.com/yuzutech/kroki )
|
||||
B --> C{ How to contribute? }
|
||||
C --> D[ Reporting bugs ]
|
||||
C --> E[ Sharing ideas ]
|
||||
C --> F[ Advocating ]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## normal code
|
||||
|
||||
```JavaScript
|
||||
function testFunc(test) {
|
||||
let sum = 0;
|
||||
for(let x = 1; x<=test; x++) {
|
||||
sum += x;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
```
|
BIN
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/img/plantuml-sample.png
generated
vendored
Normal file
BIN
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/img/plantuml-sample.png
generated
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
6
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/index.js
generated
vendored
Normal file
6
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/index.js
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
const { MarkdownItKrokiCore } = require('./lib/plugin-core');
|
||||
|
||||
module.exports = (md, opt) => {
|
||||
const plugin = new MarkdownItKrokiCore(md);
|
||||
plugin.setOptions(opt).use();
|
||||
};
|
37
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/contract.js
generated
vendored
Normal file
37
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/contract.js
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* contract `test` to be non-empty string.
|
||||
* @param {string} test test string
|
||||
* @param {string} msg message on exception
|
||||
*/
|
||||
toNonEmptyString: function (test, msg) {
|
||||
if (typeof test !== 'string') throw new Error(msg);
|
||||
if (test === ''
|
||||
|| test === null
|
||||
|| test === undefined) throw new Error(msg);
|
||||
},
|
||||
/**
|
||||
* contract `test` to be true.
|
||||
* @param {boolean} test test boolean.
|
||||
* @param {sting} msg massage on excetion.
|
||||
*/
|
||||
toTrue: function (test, msg) {
|
||||
if (typeof test !== 'boolean') throw new Error(msg);
|
||||
if (!test) throw new Error(msg);
|
||||
},
|
||||
toBeUrlString: function (test, msg) {
|
||||
this.toNonEmptyString(test, msg);
|
||||
try {
|
||||
require('url').parse(test);
|
||||
} catch {
|
||||
throw new Error(msg);
|
||||
}
|
||||
},
|
||||
toBeClassName: function (test, msg) {
|
||||
if (!/^[A-Za-z0-9]+(-?[A-Za-z0-9]+)*$/.exec(test)) {
|
||||
throw new Error(msg);
|
||||
}
|
||||
}
|
||||
};
|
27
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/diagram-encoder.js
generated
vendored
Normal file
27
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/diagram-encoder.js
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
'use strict';
|
||||
|
||||
const { deflateSync } = require('zlib');
|
||||
const contract = require('./contract');
|
||||
const support = require('./support');
|
||||
|
||||
function encode(diagram) {
|
||||
return deflateSync(diagram, { level: 9 }).toString('base64url');
|
||||
}
|
||||
function generateUrl(entrypoint, lang, imgType, diagram) {
|
||||
|
||||
contract.toNonEmptyString(entrypoint, '\'entrypoint\' must be non-empty string.');
|
||||
contract.toNonEmptyString(lang, '\'lang\' must be non-empty string.');
|
||||
contract.toNonEmptyString(imgType, '\'imgType\' must be non-empty string.');
|
||||
contract.toNonEmptyString(diagram, '\'diagram\' must be non-empty string.');
|
||||
contract.toTrue(support.languageSupports(lang), 'Not Supported Diagram Language.');
|
||||
contract.toTrue(support.imageFormatSupports(imgType), 'Not Supported Image Type.');
|
||||
|
||||
const api = `${lang}/${imgType}/${encode(diagram)}`;
|
||||
|
||||
return entrypoint.endsWith('/') ?
|
||||
`${entrypoint}${api}` : `${entrypoint}/${api}`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
encode, generateUrl
|
||||
};
|
52
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/obs/marp-it-kroki.js
generated
vendored
Normal file
52
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/obs/marp-it-kroki.js
generated
vendored
Normal file
|
@ -0,0 +1,52 @@
|
|||
const { deflateSync } = require('zlib')
|
||||
|
||||
const krokiLangs = [
|
||||
'actdiag',
|
||||
'blockdiag',
|
||||
'bpmn',
|
||||
'bytefield',
|
||||
'c4plantuml',
|
||||
'ditaa',
|
||||
'dot',
|
||||
'erd',
|
||||
'excalidraw',
|
||||
'graphviz',
|
||||
'mermaid',
|
||||
'nomnoml',
|
||||
'nwdiag',
|
||||
'packetdiag',
|
||||
'pikchr',
|
||||
'plantuml',
|
||||
'rackdiag',
|
||||
'seqdiag',
|
||||
'svgbob',
|
||||
'umlet',
|
||||
'vega',
|
||||
'vegalite',
|
||||
'wavedrom',
|
||||
]
|
||||
|
||||
const entrypoint = 'https://kroki.io/'
|
||||
|
||||
const marpKrokiPlugin = (md) => {
|
||||
const { fence } = md.renderer.rules
|
||||
|
||||
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
||||
const info = md.utils.unescapeAll(tokens[idx].info).trim()
|
||||
|
||||
if (info) {
|
||||
const [lang] = info.split(/(\s+)/g)
|
||||
|
||||
if (krokiLangs.includes(lang)) {
|
||||
const data = deflateSync(tokens[idx].content).toString('base64url')
|
||||
|
||||
// <marp-auto-scaling> is working only with Marp Core v3
|
||||
return `<p><marp-auto-scaling data-downscale-only><img src="${entrypoint}${lang}/svg/${data}"/></marp-auto-scaling></p>`
|
||||
}
|
||||
}
|
||||
|
||||
return fence.call(self, tokens, idx, options, env, self)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = marpKrokiPlugin
|
74
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/plugin-core.js
generated
vendored
Normal file
74
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/plugin-core.js
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
const support = require('./support');
|
||||
const contract = require('./contract');
|
||||
const { safeProperty, safeChoice } = require('./safe-property');
|
||||
const diagramEncoder = require('./diagram-encoder');
|
||||
|
||||
class MarkdownItKrokiCore {
|
||||
constructor(md) {
|
||||
this._md = md;
|
||||
}
|
||||
setOptions(opt) {
|
||||
this._entrypoint = safeProperty(opt, "entrypoint", "string", 'https://kroki.io');
|
||||
this._containerClass = safeProperty(opt, "containerClass", "string", "kroki-image-container");
|
||||
this._imageFormat = safeProperty(opt, "imageFormat", "string", "svg");
|
||||
|
||||
this._imageFormat = safeChoice(this._imageFormat, support.imageFormats, "svg");
|
||||
this._entrypoint = require('url').parse(this._entrypoint).href;
|
||||
|
||||
contract.toBeClassName(this._containerClass, "containerClass must be className.");
|
||||
|
||||
return this;
|
||||
}
|
||||
use() {
|
||||
// if _md has `marpit` property then use <marp-auto-scaling> tag
|
||||
this._marpAutoScaling = this._md['marpit'] !== undefined;
|
||||
|
||||
this._defaultFence = this._md.renderer.rules.fence;
|
||||
this._md.renderer.rules.fence
|
||||
= (tokens, idx, options, env, self) => this.krokiFencePlugin(tokens, idx, options, env, self);
|
||||
}
|
||||
static readLanguageAndAltText(info) {
|
||||
if (!info) return { language: '', alt: '' };
|
||||
|
||||
const trimed = info.trim();
|
||||
const langFound = /[\s|\[]/.exec(trimed);
|
||||
const altFound = /\[.*?\]/.exec(trimed);
|
||||
|
||||
return {
|
||||
language: langFound ?
|
||||
trimed.substring(0, langFound.index) : trimed,
|
||||
alt: altFound ?
|
||||
altFound[0].replace('[', '').replace(']', '') : ''
|
||||
};
|
||||
}
|
||||
buildEmbedHTML(langAndAlt, diagramCode) {
|
||||
// alt build url
|
||||
const url = diagramEncoder.generateUrl(
|
||||
this._entrypoint, langAndAlt.language, this._imageFormat, diagramCode);
|
||||
|
||||
// sanitize alt
|
||||
const alt = langAndAlt.alt ?
|
||||
this._md.utils.escapeHtml(langAndAlt.alt) : undefined;
|
||||
// build img tag
|
||||
const imgTag = langAndAlt.alt ?
|
||||
`<img alt="${alt}" src="${url}" />` : `<img src="${url}" />`;
|
||||
// build container contents
|
||||
const containerContents = this._marpAutoScaling ?
|
||||
`<marp-auto-scaling data-downscale-only>${imgTag}</marp-auto-scaling>` : imgTag;
|
||||
// build embed HTML
|
||||
return `<p class="${this._containerClass}">${containerContents}</p>`;
|
||||
}
|
||||
krokiFencePlugin(tokens, idx, options, env, self) {
|
||||
const info = this._md.utils.unescapeAll(tokens[idx].info)
|
||||
const langAndAlt = MarkdownItKrokiCore.readLanguageAndAltText(info);
|
||||
|
||||
return support.languageSupports(langAndAlt.language) ?
|
||||
this.buildEmbedHTML(langAndAlt, tokens[idx].content) :
|
||||
this._defaultFence.call(self, tokens, idx, options, env, self);
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
MarkdownItKrokiCore
|
||||
}
|
18
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/safe-property.js
generated
vendored
Normal file
18
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/safe-property.js
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
'use strict';
|
||||
|
||||
function safeProperty(test, name, type, defaultValue) {
|
||||
if (test == null || test == undefined) return defaultValue;
|
||||
if (typeof test[name] !== type) return defaultValue;
|
||||
if (typeof test[name] === "string" && test[name] === '') return defaultValue;
|
||||
return test[name];
|
||||
}
|
||||
|
||||
function safeChoice(test, candidates, defaultValue) {
|
||||
return candidates.includes(test) ?
|
||||
test : defaultValue;
|
||||
}
|
||||
function safeUrl(test) {
|
||||
|
||||
}
|
||||
|
||||
module.exports = { safeProperty, safeChoice };
|
54
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/support.js
generated
vendored
Normal file
54
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/lib/support.js
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Diagram Languages are supported by kroki.io
|
||||
*/
|
||||
const LANGUAGES = [
|
||||
'actdiag',
|
||||
'blockdiag',
|
||||
'bpmn',
|
||||
'bytefield',
|
||||
'c4plantuml',
|
||||
'ditaa',
|
||||
'dot',
|
||||
'erd',
|
||||
'excalidraw',
|
||||
'graphviz',
|
||||
'mermaid',
|
||||
'nomnoml',
|
||||
'nwdiag',
|
||||
'packetdiag',
|
||||
'pikchr',
|
||||
'plantuml',
|
||||
'rackdiag',
|
||||
'seqdiag',
|
||||
'svgbob',
|
||||
'umlet',
|
||||
'vega',
|
||||
'vegalite',
|
||||
'wavedrom',
|
||||
];
|
||||
|
||||
/**
|
||||
* Image formats are supported by kroki.io
|
||||
*/
|
||||
const IMG_FORMATS = [
|
||||
'png', 'svg', 'jpeg', 'pdf', 'base64'
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
lnaguages: LANGUAGES,
|
||||
imageFormats: IMG_FORMATS,
|
||||
/**
|
||||
* test whether `lang` is supported diagram language by kroki.io
|
||||
* @param {string} lang target language
|
||||
* @returns is supported
|
||||
*/
|
||||
languageSupports: (lang) => LANGUAGES.includes(lang),
|
||||
/**
|
||||
* test whether `format` is supported image format by kroki.io
|
||||
* @param {string} format name of image format like 'png', 'svg', ... etc
|
||||
* @returns is supported
|
||||
*/
|
||||
imageFormatSupports: (format) => IMG_FORMATS.includes(format)
|
||||
};
|
35
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/package.json
generated
vendored
Normal file
35
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/package.json
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "@kazumatu981/markdown-it-kroki",
|
||||
"version": "1.1.1",
|
||||
"description": "markdown-it kroki plugin.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha tests/**/*.test.js",
|
||||
"demo": "marp -s -c demo/marp.config.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/kazumatu981/markdown-it-kroki.git"
|
||||
},
|
||||
"keywords": [
|
||||
"markdown-it",
|
||||
"markdown-it-plugin",
|
||||
"kroki",
|
||||
"marp",
|
||||
"markdown"
|
||||
],
|
||||
"author": "kazumatu981",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/kazumatu981/markdown-it-kroki/issues"
|
||||
},
|
||||
"homepage": "https://github.com/kazumatu981/markdown-it-kroki#readme",
|
||||
"devDependencies": {
|
||||
"@marp-team/marp-cli": "^2.2.0",
|
||||
"chai": "^4.3.6",
|
||||
"jsdom": "^20.0.1",
|
||||
"markdown-it": "^13.0.1",
|
||||
"mocha": "^10.1.0",
|
||||
"nyc": "^15.1.0"
|
||||
}
|
||||
}
|
94
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/securitytest/security.test.js
generated
vendored
Normal file
94
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/securitytest/security.test.js
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
const MarkdownIt = require('markdown-it');
|
||||
const { expect } = require('chai');
|
||||
const { JSDOM } = require('jsdom');
|
||||
const MarkdownItKroki = require('../../index');
|
||||
|
||||
describe('# [Security-test] anti-injecttion for syntax.', () => {
|
||||
describe("## for alt", () => {
|
||||
it('* escape double quote', () => {
|
||||
const expected = 'this is a "test comment" test';
|
||||
|
||||
const md = new MarkdownIt();
|
||||
md.use(MarkdownItKroki, {
|
||||
entrypoint: "https://kroki.io",
|
||||
marpAutoScaling: true,
|
||||
containerClass: "the-container"
|
||||
});
|
||||
|
||||
const result = md.render(
|
||||
'```graphviz [this is a "test comment" test]\r\n' +
|
||||
'digraph G {Hello->World}\r\n' +
|
||||
'```\r\n'
|
||||
);
|
||||
const dom = new JSDOM(result);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
const actual = imgTag.getAttribute('alt');
|
||||
|
||||
expect(actual).to.be.equal(expected);
|
||||
})
|
||||
})
|
||||
});
|
||||
describe('# [Security-test] anti-injecttion for option.', () => {
|
||||
describe("## for entrypoint", () => {
|
||||
it('* deny invalid URL', () => {
|
||||
const md = new MarkdownIt();
|
||||
md.use(MarkdownItKroki, {
|
||||
entrypoint: "https://kroki.io\"> <script src=\"xxxx.js",
|
||||
marpAutoScaling: true,
|
||||
containerClass: "the-container"
|
||||
});
|
||||
|
||||
const html = md.render(
|
||||
'```graphviz [this is a test]\r\n' +
|
||||
'digraph G {Hello->World}\r\n' +
|
||||
'```\r\n'
|
||||
);
|
||||
const dom = new JSDOM(html);
|
||||
const scriptTag = dom.window.document.getElementsByTagName("script");
|
||||
expect(scriptTag.length).to.equal(0);
|
||||
})
|
||||
})
|
||||
describe("## for containerClass", () => {
|
||||
|
||||
it('* throw when containerClass are not alpha, digit, or \"-\"', () => {
|
||||
const testFunction = () => {
|
||||
const md = new MarkdownIt();
|
||||
md.use(MarkdownItKroki, {
|
||||
entrypoint: "https://kroki.io",
|
||||
marpAutoScaling: true,
|
||||
containerClass: "<container>"
|
||||
});
|
||||
|
||||
const result = md.render(
|
||||
'```graphviz [this is a test]\r\n' +
|
||||
'digraph G {Hello->World}\r\n' +
|
||||
'```\r\n'
|
||||
);
|
||||
};
|
||||
expect(testFunction).to.throw();
|
||||
|
||||
})
|
||||
})
|
||||
describe("## for imageFormat", () => {
|
||||
it('* ignore invalid imageFormat', () => {
|
||||
const md = new MarkdownIt();
|
||||
md.use(MarkdownItKroki, {
|
||||
entrypoint: "https://kroki.io",
|
||||
marpAutoScaling: true,
|
||||
containerClass: "the-container",
|
||||
imageFormat: "<injected>"
|
||||
});
|
||||
|
||||
const html = md.render(
|
||||
'```graphviz [this is a test]\r\n' +
|
||||
'digraph G {Hello->World}\r\n' +
|
||||
'```\r\n'
|
||||
);
|
||||
const dom = new JSDOM(html);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
const actual = imgTag.getAttribute('src');
|
||||
|
||||
expect(actual).to.includes('/svg/');
|
||||
})
|
||||
})
|
||||
});
|
96
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/totaltest/canrender.test.js
generated
vendored
Normal file
96
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/totaltest/canrender.test.js
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
const { expect } = require('chai');
|
||||
const MarkdownIt = require('markdown-it');
|
||||
const MarkdownItKroki = require('../../index');
|
||||
const { JSDOM } = require('jsdom');
|
||||
|
||||
const testData = [
|
||||
// graphviz
|
||||
{
|
||||
langname: 'graphviz',
|
||||
data: '```graphviz svg\r\n' +
|
||||
'digraph G {Hello->World}\r\n' +
|
||||
'```\r\n'
|
||||
}
|
||||
];
|
||||
|
||||
describe('# [total test] Test pulugin can Render DOM', () => {
|
||||
for (const test of testData) {
|
||||
describe(`## Test for ${test.langname}`, () => {
|
||||
describe('### no option call test', () => {
|
||||
it('* Not to Throw on no options', () => {
|
||||
const testFunction = () => {
|
||||
// render !
|
||||
const md = new MarkdownIt();
|
||||
md.use(MarkdownItKroki);
|
||||
const _ = md.render(test.data);
|
||||
};
|
||||
expect(testFunction).to.not.throw();
|
||||
});
|
||||
it('* root DOM item is \'p\' on no option', () => {
|
||||
// render !
|
||||
const md = new MarkdownIt();
|
||||
md.use(MarkdownItKroki);
|
||||
const result = md.render(test.data);
|
||||
|
||||
// find p-tag
|
||||
const dom = new JSDOM(result);
|
||||
const document = dom.window.document;
|
||||
const ptags = document.getElementsByTagName("p");
|
||||
|
||||
// test p-tag is only one
|
||||
expect(ptags.length).to.be.equal(1);
|
||||
|
||||
// test root item is p
|
||||
const thePtag = ptags[0];
|
||||
expect(thePtag.isSameNode(document.body.firstChild)).to.true;
|
||||
|
||||
// test embeded default container class
|
||||
expect(thePtag.getAttribute('class')).to.be.equal('kroki-image-container');
|
||||
});
|
||||
it('* has img tag and source is created by this library.', () => {
|
||||
// render !
|
||||
const md = new MarkdownIt();
|
||||
md.use(MarkdownItKroki);
|
||||
const result = md.render(test.data);
|
||||
|
||||
// find p-tag
|
||||
const dom = new JSDOM(result);
|
||||
const document = dom.window.document;
|
||||
const imgTags = document.getElementsByTagName("img");
|
||||
|
||||
// test img-tag is only one
|
||||
expect(imgTags.length).to.be.equal(1);
|
||||
|
||||
const imgTag = imgTags[0];
|
||||
expect(imgTag.getAttribute('src')).not.to.empty;
|
||||
})
|
||||
});
|
||||
})
|
||||
}
|
||||
})
|
||||
describe('[total test] Can Render', () => {
|
||||
it('* option constainerClass is embeded.', () => {
|
||||
const md = new MarkdownIt();
|
||||
|
||||
md.use(MarkdownItKroki, {
|
||||
entrypoint: "https://kroki.io",
|
||||
marpAutoScaling: true,
|
||||
containerClass: "the-container"
|
||||
});
|
||||
|
||||
var result = md.render(
|
||||
'```graphviz [praphviz-image]\r\n' +
|
||||
'digraph G {Hello->World}\r\n' +
|
||||
'```\r\n'
|
||||
);
|
||||
const dom = new JSDOM(result);
|
||||
const document = dom.window.document;
|
||||
const ptags = document.getElementsByTagName("p");
|
||||
|
||||
expect(ptags.length).to.be.equal(1);
|
||||
const thePtag = ptags[0];
|
||||
|
||||
expect(thePtag.isSameNode(document.body.firstChild)).to.true;
|
||||
expect(thePtag.getAttribute('class')).to.be.equal('the-container');
|
||||
});
|
||||
});
|
35
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/conract.test.js
generated
vendored
Normal file
35
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/conract.test.js
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
const expect = require('chai').expect;
|
||||
const contract = require('../../lib/contract');
|
||||
|
||||
describe('# [unit test]: contract.js', () => {
|
||||
describe('## toNonEmptyString()', () => {
|
||||
['abc', ' ', ' abc '].forEach((test) => {
|
||||
it(`* normal cases. { testcase: ${test} }`, () => {
|
||||
expect(() => {
|
||||
contract.toNonEmptyString(test);
|
||||
}).not.to.throw();
|
||||
})
|
||||
});
|
||||
['', null, undefined, 123, 0.123, true, { test: 123 }].forEach((test) => {
|
||||
it(`* abnormal cases. { testcase: ${test} }`, () => {
|
||||
expect(() => {
|
||||
contract.toNonEmptyString(test);
|
||||
}).to.throw();
|
||||
})
|
||||
});
|
||||
});
|
||||
describe('## toTrue', () => {
|
||||
it('* normal case. {test case: true}', () => {
|
||||
expect(() => {
|
||||
contract.toTrue(true);
|
||||
}).not.to.throw();
|
||||
});
|
||||
['', null, undefined, 123, 0.123, false, { test: 123 }].forEach((test) => {
|
||||
it(`* abnormal cases. { testcase: ${test} }`, () => {
|
||||
expect(() => {
|
||||
contract.toTrue(test);
|
||||
}).to.throw();
|
||||
})
|
||||
});
|
||||
});
|
||||
})
|
75
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/diagram-encoder.test.js
generated
vendored
Normal file
75
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/diagram-encoder.test.js
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
const expect = require('chai').expect;
|
||||
const { inflateSync } = require('zlib');
|
||||
|
||||
const { encode, generateUrl } = require('../../lib/diagram-encoder');
|
||||
|
||||
describe('# [unit-test]: diagram-encoder.js', () => {
|
||||
describe('## [function]: encode()', () => {
|
||||
it('* encoded data is able to decode.', () => {
|
||||
const testFunc = () => {
|
||||
const expected = '@startuml\nBob -> Alice : hello\n@enduml';
|
||||
|
||||
const encoded = encode(expected);
|
||||
const deflated = Buffer.from(encoded, "base64url");
|
||||
const actual = inflateSync(deflated).toString();
|
||||
|
||||
expect(actual).to.be.equal(expected);
|
||||
}
|
||||
expect(testFunc).not.to.Throw();
|
||||
});
|
||||
});
|
||||
describe('## [function]: generateUrl()', () => {
|
||||
it('* must start format like <entry-point>/<lang>/<format>/', () => {
|
||||
const actual = generateUrl('https://kroki.io', 'graphviz', 'svg', 'digraph G {Hello->World}');
|
||||
const expected = 'https://kroki.io/graphviz/svg/';
|
||||
|
||||
expect(actual).to.be.a('string');
|
||||
expect(actual.startsWith(expected)).to.be.true;
|
||||
});
|
||||
it('* must endwith <encoded>', () => {
|
||||
const actual = generateUrl('https://kroki.io', 'graphviz', 'svg', 'digraph G {Hello->World}');
|
||||
const expected = encode('digraph G {Hello->World}');
|
||||
|
||||
expect(actual).to.be.a('string');
|
||||
expect(actual.endsWith(expected)).to.be.true;
|
||||
});
|
||||
[1, '', null, undefined].forEach(test => {
|
||||
it(`* [exception] throws when entry-point is, non-string object, empty string, null or undefined. Test: ${test}`, () => {
|
||||
const testFunction = () => {
|
||||
let _ = generateUrl(test, 'graphviz', 'svg', 'digraph G {Hello->World}');
|
||||
}
|
||||
expect(testFunction).throw();
|
||||
});
|
||||
it(`* [exception] throws when lang is, non-string object, empty string, null or undefined. Test: ${test}`, () => {
|
||||
const testFunction = () => {
|
||||
let _ = generateUrl('https://kroki.io', test, 'svg', 'digraph G {Hello->World}');
|
||||
}
|
||||
expect(testFunction).throw();
|
||||
});
|
||||
it(`* [exception] throws when imgType is, non-string object, empty string, null or undefined. Test: ${test}`, () => {
|
||||
const testFunction = () => {
|
||||
let _ = generateUrl('https://kroki.io', 'graphviz', test, 'digraph G {Hello->World}');
|
||||
}
|
||||
expect(testFunction).throw();
|
||||
});
|
||||
it(`* [exception] throws when diagram is, non-string object, empty string, null or undefined. Test: ${test}`, () => {
|
||||
const testFunction = () => {
|
||||
let _ = generateUrl('https://kroki.io', 'graphviz', 'svg', diagram);
|
||||
}
|
||||
expect(testFunction).throw();
|
||||
});
|
||||
});
|
||||
it('* [exception] throws when lang is unsupported lang', () => {
|
||||
const testFunction = () => {
|
||||
let _ = generateUrl('https://kroki.io', 'graphviz123', 'svg', 'digraph G {Hello->World}');
|
||||
}
|
||||
expect(testFunction).throw();
|
||||
});
|
||||
it('* [exception] throws when imgType is unsupported imgType', () => {
|
||||
const testFunction = () => {
|
||||
let _ = generateUrl('https://kroki.io', 'graphviz', 'svg123', 'digraph G {Hello->World}');
|
||||
}
|
||||
expect(testFunction).throw();
|
||||
});
|
||||
});
|
||||
});
|
146
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/plugin-core.buuildEmbedHTML.test.js
generated
vendored
Normal file
146
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/plugin-core.buuildEmbedHTML.test.js
generated
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
const md = require('markdown-it');
|
||||
const expect = require('chai').expect;
|
||||
const { JSDOM } = require('jsdom');
|
||||
const { MarkdownItKrokiCore } = require('../../lib/plugin-core');
|
||||
const { encode } = require('../../lib/diagram-encoder');
|
||||
|
||||
describe('# [unit-test] plugin-core.js', () => {
|
||||
describe('## method: buuildEmbedHTML', () => {
|
||||
describe('### langAndAlt.language', () => {
|
||||
[null, undefined, ''].forEach((test) => {
|
||||
it(`* when langAndAlt.language is null or empty, throws error. testcase:${test}`, () => {
|
||||
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
|
||||
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
|
||||
const testFunc = () => {
|
||||
plugin.use();
|
||||
const _ = plugin.buildEmbedHTML(
|
||||
{ language: test, alt: '' }, diagramCode);
|
||||
};
|
||||
expect(testFunc).to.throw();
|
||||
});
|
||||
});
|
||||
it('* language embeded in to url', () => {
|
||||
const test = 'plantuml';
|
||||
|
||||
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
|
||||
|
||||
// build embed HTML
|
||||
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
|
||||
plugin.use();
|
||||
const html = plugin.buildEmbedHTML({ language: test, alt: '' }, diagramCode);
|
||||
|
||||
// parse dom
|
||||
const dom = new JSDOM(html);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
|
||||
// get url attribute
|
||||
const url = imgTag.getAttribute('src');
|
||||
|
||||
expect(/\/plantuml\//.test(url)).to.true;
|
||||
});
|
||||
});
|
||||
describe('### langAndAlt.alt', () => {
|
||||
[null, undefined, ''].forEach((test) => {
|
||||
it(`* when langAndAlt.alt is null or empty, no alt attribute. testcase:${test}`, () => {
|
||||
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
|
||||
|
||||
// prepair
|
||||
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
|
||||
plugin.use();
|
||||
|
||||
// render
|
||||
const html = plugin.buildEmbedHTML(
|
||||
{ language: 'plantuml', alt: test }, diagramCode);
|
||||
// parse dom
|
||||
const dom = new JSDOM(html);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
|
||||
expect(imgTag.hasAttribute('alt')).to.false;
|
||||
});
|
||||
});
|
||||
it('* embeded altText', () => {
|
||||
const expected = "this is test Text";
|
||||
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
|
||||
|
||||
// prepair
|
||||
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
|
||||
plugin.use();
|
||||
|
||||
// render
|
||||
const html = plugin.buildEmbedHTML(
|
||||
{ language: 'plantuml', alt: expected }, diagramCode);
|
||||
// parse dom
|
||||
const dom = new JSDOM(html);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
|
||||
expect(imgTag.getAttribute('alt')).to.equal(expected);
|
||||
|
||||
});
|
||||
});
|
||||
describe('### diagramCode', () => {
|
||||
[null, undefined, ''].forEach((test) => {
|
||||
it(`* when diagramCode is null or empty, throws error. testcase:${test}`, () => {
|
||||
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
|
||||
const testFunc = () => {
|
||||
plugin.use();
|
||||
const _ = plugin.buildEmbedHTML(
|
||||
{ language: 'plantuml', alt: '' }, test);
|
||||
};
|
||||
expect(testFunc).to.throw();
|
||||
});
|
||||
});
|
||||
it('* encoded diagram must be embed to url on <img src=\'....\' ', () => {
|
||||
const test = '@startuml\nBob -> Alice : hello\n @enduml';
|
||||
|
||||
const expected = encode(test);
|
||||
|
||||
// build embed HTML
|
||||
const plugin = new MarkdownItKrokiCore(new md()).setOptions();
|
||||
plugin.use();
|
||||
const html = plugin.buildEmbedHTML({ language: 'plantuml', alt: '' }, test);
|
||||
|
||||
// parse dom
|
||||
const dom = new JSDOM(html);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
|
||||
// get url attribute
|
||||
const url = imgTag.getAttribute('src');
|
||||
|
||||
expect(url.endsWith(expected)).to.true;
|
||||
});
|
||||
it('* <img> is surounded by <marp-auto-scaling> on to be used form marp-it', () => {
|
||||
const test = '@startuml\nBob -> Alice : hello\n @enduml';
|
||||
|
||||
const markdownIt = new md()
|
||||
markdownIt['marpit'] = { someObject: 'is implemented' };
|
||||
// build embed HTML
|
||||
const plugin = new MarkdownItKrokiCore(markdownIt).setOptions();
|
||||
plugin.use();
|
||||
const html = plugin.buildEmbedHTML({ language: 'plantuml', alt: '' }, test);
|
||||
|
||||
// parse dom
|
||||
const dom = new JSDOM(html);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
const marpAutoScaling = dom.window.document.getElementsByTagName("marp-auto-scaling")[0];
|
||||
|
||||
expect(imgTag.isSameNode(marpAutoScaling.firstChild)).to.be.true;
|
||||
});
|
||||
it('* <img> is surounded by <marp-auto-scaling> on not to be used form marp-it', () => {
|
||||
const test = '@startuml\nBob -> Alice : hello\n @enduml';
|
||||
|
||||
const markdownIt = new md()
|
||||
// build embed HTML
|
||||
const plugin = new MarkdownItKrokiCore(markdownIt).setOptions();
|
||||
plugin.use();
|
||||
const html = plugin.buildEmbedHTML({ language: 'plantuml', alt: '' }, test);
|
||||
|
||||
// parse dom
|
||||
const dom = new JSDOM(html);
|
||||
const marpAutoScaling = dom.window.document.getElementsByTagName("marp-auto-scaling");
|
||||
|
||||
expect(marpAutoScaling.length).to.equal(0);
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
});
|
178
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/plugin-core.setOptions.test.js
generated
vendored
Normal file
178
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/plugin-core.setOptions.test.js
generated
vendored
Normal file
|
@ -0,0 +1,178 @@
|
|||
const md = require('markdown-it');
|
||||
const expect = require('chai').expect;
|
||||
const { JSDOM } = require('jsdom');
|
||||
const { MarkdownItKrokiCore } = require('../../lib/plugin-core');
|
||||
|
||||
describe('# [unit-test] plugin-core.js', () => {
|
||||
describe('## method: setOptions() must be work', () => {
|
||||
function buildHtmlForTest(options) {
|
||||
const test = 'plantuml';
|
||||
|
||||
const diagramCode = '@startuml\nBob -> Alice : hello\n @enduml';
|
||||
|
||||
// build embed HTML
|
||||
const plugin = new MarkdownItKrokiCore(new md()).setOptions(options);
|
||||
plugin.use();
|
||||
return plugin.buildEmbedHTML({ language: test }, diagramCode);
|
||||
}
|
||||
describe('### entrypoint', () => {
|
||||
function expectEntryPointToEmbed(htmlString, expected) {
|
||||
if (!expected) expected = 'https://kroki.io';
|
||||
// parse dom
|
||||
const dom = new JSDOM(htmlString);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
|
||||
// get url attribute
|
||||
const url = imgTag.getAttribute('src');
|
||||
|
||||
expect(url.startsWith(expected)).to.true;
|
||||
}
|
||||
it('* no options', () => {
|
||||
const html = buildHtmlForTest();
|
||||
expectEntryPointToEmbed(html);
|
||||
});
|
||||
it('* option is null', () => {
|
||||
const html = buildHtmlForTest({ entrypoint: null });
|
||||
expectEntryPointToEmbed(html);
|
||||
});
|
||||
it('* option is undefined', () => {
|
||||
const html = buildHtmlForTest({ entrypoint: undefined });
|
||||
expectEntryPointToEmbed(html);
|
||||
});
|
||||
it('* option is \'\'', () => {
|
||||
const html = buildHtmlForTest({ entrypoint: '' });
|
||||
expectEntryPointToEmbed(html);
|
||||
});
|
||||
it('* option is 1', () => {
|
||||
const html = buildHtmlForTest({ entrypoint: 1 });
|
||||
expectEntryPointToEmbed(html);
|
||||
});
|
||||
it('* option is true', () => {
|
||||
const html = buildHtmlForTest({ entrypoint: true });
|
||||
expectEntryPointToEmbed(html);
|
||||
});
|
||||
it('* option is \'https://localhost:8080\'', () => {
|
||||
const html = buildHtmlForTest({
|
||||
entrypoint: 'https://localhost:8080'
|
||||
});
|
||||
expectEntryPointToEmbed(html, 'https://localhost:8080');
|
||||
});
|
||||
});
|
||||
describe.skip('### marpAutoScaling', () => {
|
||||
function expectMarpAutoScalingToEmbed(htmlString, expected) {
|
||||
// parse dom
|
||||
const dom = new JSDOM(htmlString);
|
||||
const tags = dom.window.document.getElementsByTagName("marp-auto-scaling");
|
||||
if (expected) {
|
||||
expect(tags).not.to.empty;
|
||||
} else {
|
||||
expect(tags).to.empty;
|
||||
}
|
||||
|
||||
}
|
||||
it('* no options', () => {
|
||||
const html = buildHtmlForTest();
|
||||
expectMarpAutoScalingToEmbed(html, true);
|
||||
});
|
||||
it('* option is null', () => {
|
||||
const html = buildHtmlForTest({ marpAutoScaling: null });
|
||||
expectMarpAutoScalingToEmbed(html, true);
|
||||
});
|
||||
it('* option is undefined', () => {
|
||||
const html = buildHtmlForTest({ marpAutoScaling: undefined });
|
||||
expectMarpAutoScalingToEmbed(html, true);
|
||||
});
|
||||
it('* option is \'\'', () => {
|
||||
const html = buildHtmlForTest({ marpAutoScaling: '' });
|
||||
expectMarpAutoScalingToEmbed(html, true);
|
||||
});
|
||||
it('* option is 1', () => {
|
||||
const html = buildHtmlForTest({ marpAutoScaling: 1 });
|
||||
expectMarpAutoScalingToEmbed(html, true);
|
||||
});
|
||||
it('* option is \'test\'', () => {
|
||||
const html = buildHtmlForTest({ marpAutoScaling: 'test' });
|
||||
expectMarpAutoScalingToEmbed(html, true);
|
||||
});
|
||||
it('* option is false', () => {
|
||||
const html = buildHtmlForTest({ marpAutoScaling: false });
|
||||
expectMarpAutoScalingToEmbed(html, false);
|
||||
});
|
||||
})
|
||||
describe('### containerClass', () => {
|
||||
function expectContainerClassToEmbed(htmlString, className) {
|
||||
// parse dom
|
||||
const dom = new JSDOM(htmlString);
|
||||
const pTag = dom.window.document.getElementsByTagName("p")[0];
|
||||
|
||||
const actualClassName = pTag.getAttribute('class');
|
||||
expect(actualClassName).to.equal(className);
|
||||
}
|
||||
it('* no options', () => {
|
||||
const html = buildHtmlForTest();
|
||||
expectContainerClassToEmbed(html, 'kroki-image-container');
|
||||
});
|
||||
it('* option is null', () => {
|
||||
const html = buildHtmlForTest({ containerClass: null });
|
||||
expectContainerClassToEmbed(html, 'kroki-image-container');
|
||||
});
|
||||
it('* option is undefined', () => {
|
||||
const html = buildHtmlForTest({ containerClass: undefined });
|
||||
expectContainerClassToEmbed(html, 'kroki-image-container');
|
||||
});
|
||||
it('* option is \'\'', () => {
|
||||
const html = buildHtmlForTest({ containerClass: '' });
|
||||
expectContainerClassToEmbed(html, 'kroki-image-container');
|
||||
});
|
||||
it('* option is 1', () => {
|
||||
const html = buildHtmlForTest({ containerClass: 1 });
|
||||
expectContainerClassToEmbed(html, 'kroki-image-container');
|
||||
});
|
||||
it('* option is \'containerClass\'', () => {
|
||||
const html = buildHtmlForTest({ containerClass: 'containerClass' });
|
||||
expectContainerClassToEmbed(html, 'containerClass');
|
||||
});
|
||||
});
|
||||
describe('### imageFormat', () => {
|
||||
function expectImageFormatToEmbed(htmlString, expected) {
|
||||
// parse dom
|
||||
const dom = new JSDOM(htmlString);
|
||||
const imgTag = dom.window.document.getElementsByTagName("img")[0];
|
||||
|
||||
// get url attribute
|
||||
const url = imgTag.getAttribute('src');
|
||||
|
||||
expect(url).to.includes('/' + expected + '/');
|
||||
}
|
||||
it('* no options', () => {
|
||||
const html = buildHtmlForTest();
|
||||
expectImageFormatToEmbed(html, 'svg');
|
||||
});
|
||||
it('* option is null', () => {
|
||||
const html = buildHtmlForTest({ imageFormat: null });
|
||||
expectImageFormatToEmbed(html, 'svg');
|
||||
});
|
||||
it('* option is undefined', () => {
|
||||
const html = buildHtmlForTest({ imageFormat: undefined });
|
||||
expectImageFormatToEmbed(html, 'svg');
|
||||
});
|
||||
it('* option is \'\'', () => {
|
||||
const html = buildHtmlForTest({ imageFormat: '' });
|
||||
expectImageFormatToEmbed(html, 'svg');
|
||||
});
|
||||
it('* option is 1', () => {
|
||||
const html = buildHtmlForTest({ imageFormat: 1 });
|
||||
expectImageFormatToEmbed(html, 'svg');
|
||||
|
||||
});
|
||||
it('* option is \'test\'', () => {
|
||||
const html = buildHtmlForTest({ imageFormat: 'test' });
|
||||
expectImageFormatToEmbed(html, 'svg');
|
||||
});
|
||||
it('* option is \'png\'', () => {
|
||||
const html = buildHtmlForTest({ imageFormat: 'png' });
|
||||
expectImageFormatToEmbed(html, 'png');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
49
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/plugin-core.static-methods.test.js
generated
vendored
Normal file
49
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/plugin-core.static-methods.test.js
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
const expect = require('chai').expect;
|
||||
const { MarkdownItKrokiCore } = require('../../lib/plugin-core');
|
||||
|
||||
describe('# [unit-test] plugin-core.js', () => {
|
||||
describe('## static method: readLanguageAndAltText() - language', () => {
|
||||
[
|
||||
{ test: null, expected: '' },
|
||||
{ test: undefined, expected: '' },
|
||||
{ test: '', expected: '' },
|
||||
{ test: ' ', expected: '' },
|
||||
{ test: 'plantuml', expected: 'plantuml' },
|
||||
{ test: ' plantuml', expected: 'plantuml' },
|
||||
{ test: 'plantuml ', expected: 'plantuml' },
|
||||
{ test: 'plantuml +++', expected: 'plantuml' },
|
||||
{ test: 'html+md', expected: 'html+md' },
|
||||
{ test: 'graphviz[]', expected: 'graphviz' },
|
||||
{ test: 'graphviz[test]', expected: 'graphviz' },
|
||||
{ test: 'graphviz [test test]', expected: 'graphviz' },
|
||||
].forEach(testCase => {
|
||||
it(`### Can read diagramLanguage. in case \'${testCase.test}\'`, () => {
|
||||
const actual = MarkdownItKrokiCore.readLanguageAndAltText(testCase.test);
|
||||
const expected = testCase.expected;
|
||||
expect(actual.language).to.be.equal(expected);
|
||||
})
|
||||
});
|
||||
});
|
||||
describe('## static method: readLanguageAndAltText() - alt', () => {
|
||||
[
|
||||
{ test: null, expected: '' },
|
||||
{ test: undefined, expected: '' },
|
||||
{ test: '', expected: '' },
|
||||
{ test: ' ', expected: '' },
|
||||
{ test: 'plantuml', expected: '' },
|
||||
{ test: ' plantuml', expected: '' },
|
||||
{ test: 'plantuml ', expected: '' },
|
||||
{ test: 'plantuml +++', expected: '' },
|
||||
{ test: 'html+md', expected: '' },
|
||||
{ test: 'graphviz[]', expected: '' },
|
||||
{ test: 'graphviz[test]', expected: 'test' },
|
||||
{ test: 'graphviz [test test]', expected: 'test test' },
|
||||
].forEach(testCase => {
|
||||
it(`### Can read diagramLanguage. in case \'${testCase.test}\'`, () => {
|
||||
const actual = MarkdownItKrokiCore.readLanguageAndAltText(testCase.test);
|
||||
const expected = testCase.expected;
|
||||
expect(actual.alt).to.be.equal(expected);
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
120
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/safe-property.test.js
generated
vendored
Normal file
120
down-the-stack-book/node_modules/@kazumatu981/markdown-it-kroki/tests/unittest/safe-property.test.js
generated
vendored
Normal file
|
@ -0,0 +1,120 @@
|
|||
const { expect } = require('chai');
|
||||
const { safeProperty } = require('../../lib/safe-property');
|
||||
|
||||
describe('# [unit-test]: safe-property.js', () => {
|
||||
[
|
||||
{
|
||||
testCaseDescription: "standard test - string",
|
||||
testCase: {
|
||||
test: {
|
||||
property1: "hello"
|
||||
},
|
||||
name: "property1",
|
||||
type: "string",
|
||||
defaultValue: undefined
|
||||
},
|
||||
expected: "hello"
|
||||
},
|
||||
{
|
||||
testCaseDescription: "standard test - boolean",
|
||||
testCase: {
|
||||
test: {
|
||||
property1: true
|
||||
},
|
||||
name: "property1",
|
||||
type: "boolean",
|
||||
defaultValue: undefined
|
||||
},
|
||||
expected: true
|
||||
},
|
||||
{
|
||||
testCaseDescription: "standard test - boolean on null",
|
||||
testCase: {
|
||||
test: {
|
||||
property1: null
|
||||
},
|
||||
name: "property1",
|
||||
type: "boolean",
|
||||
defaultValue: false
|
||||
},
|
||||
expected: false
|
||||
},
|
||||
{
|
||||
testCaseDescription: "on null",
|
||||
testCase: {
|
||||
test: {
|
||||
property1: null
|
||||
},
|
||||
name: "property1",
|
||||
type: "string",
|
||||
defaultValue: "hello"
|
||||
},
|
||||
expected: "hello"
|
||||
},
|
||||
{
|
||||
testCaseDescription: "on empty string",
|
||||
testCase: {
|
||||
test: {
|
||||
property1: ''
|
||||
},
|
||||
name: "property1",
|
||||
type: "string",
|
||||
defaultValue: "hello"
|
||||
},
|
||||
expected: "hello"
|
||||
},
|
||||
{
|
||||
testCaseDescription: "on undefined",
|
||||
testCase: {
|
||||
test: {
|
||||
property1: undefined
|
||||
},
|
||||
name: "property1",
|
||||
type: "string",
|
||||
defaultValue: "hello"
|
||||
},
|
||||
expected: "hello"
|
||||
},
|
||||
{
|
||||
testCaseDescription: "on not mutch type",
|
||||
testCase: {
|
||||
test: {
|
||||
property1: 1
|
||||
},
|
||||
name: "property1",
|
||||
type: "string",
|
||||
defaultValue: "hello"
|
||||
},
|
||||
expected: "hello"
|
||||
},
|
||||
{
|
||||
testCaseDescription: "on object is null",
|
||||
testCase: {
|
||||
test: null,
|
||||
name: "property1",
|
||||
type: "string",
|
||||
defaultValue: "hello"
|
||||
},
|
||||
expected: "hello"
|
||||
},
|
||||
{
|
||||
testCaseDescription: "on object is undefined",
|
||||
testCase: {
|
||||
test: undefined,
|
||||
name: "property1",
|
||||
type: "string",
|
||||
defaultValue: "hello"
|
||||
},
|
||||
expected: "hello"
|
||||
},
|
||||
].forEach((testItem) => {
|
||||
it(`* ${testItem.testCaseDescription}`, () => {
|
||||
const actual = safeProperty(
|
||||
testItem.testCase.test,
|
||||
testItem.testCase.name,
|
||||
testItem.testCase.type,
|
||||
testItem.testCase.defaultValue);
|
||||
expect(actual).to.equal(testItem.expected);
|
||||
});
|
||||
})
|
||||
})
|
18
down-the-stack-book/package-lock.json
generated
Normal file
18
down-the-stack-book/package-lock.json
generated
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "down-the-stack-book",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@kazumatu981/markdown-it-kroki": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@kazumatu981/markdown-it-kroki": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@kazumatu981/markdown-it-kroki/-/markdown-it-kroki-1.1.1.tgz",
|
||||
"integrity": "sha512-LDYl+mV2WogLQ5r4olxovm+gphL/MNGfWZ1M1woBO/YhFnfwdn5EAUu9zF/KoVZzytJPq0RNfyeDtMkv+GJihg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
5
down-the-stack-book/package.json
Normal file
5
down-the-stack-book/package.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"@kazumatu981/markdown-it-kroki": "^1.1.1"
|
||||
}
|
||||
}
|
10
down-the-stack-book/src/SUMMARY.md
Normal file
10
down-the-stack-book/src/SUMMARY.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Summary
|
||||
|
||||
- [Introduction](./introduction.md)
|
||||
- [The Peripheral Access Crate](./the_pac.md)
|
||||
- [The Hardware Abstraction Layer](./the_hal.md)
|
||||
- [Building Common Abstractions](./building_common_abstractions.md)
|
||||
- [Creating Portable Drivers](./creating_portable_drivers.md)
|
||||
- [Supporting your particular board](./supporting_your_particular_board.md)
|
||||
- [Writing an Application](./writing_an_application.md)
|
||||
|
1
down-the-stack-book/src/building_common_abstractions.md
Normal file
1
down-the-stack-book/src/building_common_abstractions.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Building Common Abstractions
|
1
down-the-stack-book/src/creating_portable_drivers.md
Normal file
1
down-the-stack-book/src/creating_portable_drivers.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Creating Portable Drivers
|
84
down-the-stack-book/src/introduction.md
Normal file
84
down-the-stack-book/src/introduction.md
Normal file
|
@ -0,0 +1,84 @@
|
|||
# Introduction
|
||||
|
||||
---
|
||||
|
||||
## A Layered Approach
|
||||
|
||||
When building Embedded Systems in Rust, we use Rust crates to help us build a modular system.
|
||||
|
||||
The elements are:
|
||||
|
||||
* The program you are writing
|
||||
* The MCU are running on
|
||||
* The PCB (or Board) your MCU is on
|
||||
* The external devices connected to your MCU
|
||||
|
||||
---
|
||||
|
||||
## The Layers
|
||||
|
||||
To support these elements, we (usually) have these layers.
|
||||
|
||||
* Application
|
||||
* Board Support
|
||||
* External Drivers (e.g. SPI LCD Driver)
|
||||
* Hardware Abstraction Layer Traits
|
||||
* MCU Hardware Abstraction Layer Implementation
|
||||
* MCU Peripheral Access Crate
|
||||
* Core Peripherals
|
||||
* Core Runtime
|
||||
|
||||
---
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
app(Application<br/><tt>my_application</tt>)
|
||||
bsc[Board Support<br/><tt>nrf52840_dk</tt>]
|
||||
hal[MCU HAL Implementation<br/><tt>nrf52480_hal</tt>]
|
||||
lcd_driver[SPI LCD Driver<br/><tt>ssd1306</tt>]
|
||||
hal_traits[[HAL Traits<br/><tt>embedded_hal</tt>]]
|
||||
pac[MCU PAC<br/><tt>nrf52840</tt>]
|
||||
rt[Core Runtime<br/><tt>cortex_m_rt</tt>]
|
||||
cp[Core Peripherals<br/><tt>cortex_m</tt>]
|
||||
|
||||
subgraph Key
|
||||
note1[Embedded Working Group]
|
||||
note2[nrf-rs]
|
||||
note3[You]
|
||||
note4[Others]
|
||||
end
|
||||
|
||||
direction TB
|
||||
app --> bsc
|
||||
app & bsc --> hal
|
||||
app --> lcd_driver
|
||||
app & lcd_driver --> hal_traits
|
||||
hal -- Implements --o hal_traits
|
||||
app & hal --> pac
|
||||
app & pac --> rt
|
||||
app & pac & rt --> cp
|
||||
|
||||
class app binary;
|
||||
class bsc library;
|
||||
class lcd_driver library;
|
||||
class hal mcu_library;
|
||||
class pac mcu_library;
|
||||
class hal_traits ewg_library;
|
||||
class rt ewg_library;
|
||||
class cp ewg_library;
|
||||
|
||||
class note1 ewg_library;
|
||||
class note2 mcu_library;
|
||||
class note3 binary;
|
||||
class note4 library;
|
||||
|
||||
classDef binary fill:#fb8,stroke:#333,stroke-width:4px;
|
||||
classDef library fill:#cf9,stroke:#333,stroke-width:2px;
|
||||
classDef ewg_library fill:#f9c,stroke:#333,stroke-width:2px;
|
||||
classDef mcu_library fill:#9cf,stroke:#333,stroke-width:2px;
|
||||
```
|
||||
---
|
||||
|
||||
## Don't worry!
|
||||
|
||||
There's a lot here. We're going to take it step by step, starting at the bottom.
|
|
@ -0,0 +1 @@
|
|||
# Supporting your particular board
|
1
down-the-stack-book/src/the_hal.md
Normal file
1
down-the-stack-book/src/the_hal.md
Normal file
|
@ -0,0 +1 @@
|
|||
# The Hardware Abstraction Layer
|
206
down-the-stack-book/src/the_pac.md
Normal file
206
down-the-stack-book/src/the_pac.md
Normal file
|
@ -0,0 +1,206 @@
|
|||
# The Peripheral Access Crate
|
||||
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
This crate sits at the bottom of the 'stack'. It provides access to the memory-mapped peripherals in your MCU.
|
||||
|
||||
---
|
||||
|
||||
## Memory Mapped Peripherals
|
||||
|
||||
* e.g. a UART peripheral
|
||||
* Has registers, represented by a memory address
|
||||
* Registers are usually consecutive in memory (not always)
|
||||
* Peripherals can have instances (same layout of registers, different start address)
|
||||
* UART0, UART1, etc
|
||||
|
||||
---
|
||||
|
||||
## Registers
|
||||
|
||||
* *Registers* are comprised of one or more *fields*.
|
||||
* Each field is at least 1 bit in length.
|
||||
* Sometimes fields can only take from a limited set of values
|
||||
* This is all in your datasheet!
|
||||
|
||||
---
|
||||
|
||||
## C Code!
|
||||
|
||||
Embedded Code in C often uses shifts and bitwise-AND to make up registers from fields.
|
||||
|
||||
```c
|
||||
#define UARTE_INTEN_CTS_SHIFT (0)
|
||||
#define UARTE_INTEN_CTS_MASK (0x00000001)
|
||||
#define UARTE_INTEN_RXRDY_SHIFT (2)
|
||||
#define UARTE_INTEN_RXRDY_MASK (0x00000001)
|
||||
|
||||
// The other nine fields are skipped for brevity
|
||||
uint32_t cts = 0;
|
||||
uint32_t rxrdy = 1;
|
||||
|
||||
uint32_t inten_value = ((cts & UARTE_INTEN_CTS_MASK) << UARTE_INTEN_CTS_SHIFT)
|
||||
| ((rxrdy & UARTE_INTEN_RXRDY_MASK) << UARTE_INTEN_RXRDY_SHIFT);
|
||||
|
||||
*((volatile uint32_t*) 0x40002300) = inten_value;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rust Code
|
||||
|
||||
You *could* do this in Rust if you wanted...
|
||||
|
||||
```rust
|
||||
const UARTE0_INTEN: *mut u32 = 0x4000_2300 as *mut u32;
|
||||
unsafe { UARTE0_INTEN.write_volatile(0x0000_0003); }
|
||||
```
|
||||
|
||||
But this still seems very error-prone. Nothing stops you putting the wrong value at the wrong address.
|
||||
|
||||
---
|
||||
|
||||
## Adding structure
|
||||
|
||||
In C, the various registers for a peripheral can also be grouped into a `struct`
|
||||
|
||||
```c
|
||||
typedef volatile struct uart0_reg_t {
|
||||
uint32_t tasks_startrx; // @ 0x000
|
||||
uint32_t tasks_stoprx; // @ 0x004
|
||||
// ...
|
||||
uint32_t inten; // @ 0x300
|
||||
uint32_t _padding[79];
|
||||
uint32_t baudrate; // @ 0x500
|
||||
} uart0_reg_t
|
||||
|
||||
uart0_reg_t* const p_uart = (uart0_reg_t*) 0x40002000;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structures in Rust
|
||||
|
||||
We can do that too (and this is how our PAC works under the hood).
|
||||
|
||||
```rust
|
||||
pub struct RegisterBlock {
|
||||
pub tasks_startrx: VolatileCell<u32>, // @ 0x000
|
||||
pub tasks_stoprx: VolatileCell<u32>, // @ 0x004
|
||||
// ...
|
||||
pub inten: VolatileCell<u32>, // @ 0x300
|
||||
_reserved12: [u32; 79],
|
||||
pub baudrate: VolatileCell<u32>, // @ 0x500
|
||||
}
|
||||
|
||||
let p_uart: &RegisterBlock = unsafe { &*(0x40002000 as *const RegisterBlock) };
|
||||
```
|
||||
|
||||
We use the [`VolatileCell`](https://docs.rs/vcell/0.1.3/vcell/struct.VolatileCell.html) to ensure reads/writes on the structure fields are always performed with volatile pointer read/writes.
|
||||
|
||||
---
|
||||
|
||||
## CMSIS-SVD Files
|
||||
|
||||
A CMSIS-SVD (or just SVD) file is an XML description of all the peripherals, registers and fields on an MCU.
|
||||
|
||||
We can use `svd2rust` to turn this into a Peripheral Access Crate.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
svd[(SVD XML)] --> svd2rust[<tt>svd2rust</tt>] --> rust[(Rust Source)]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## The `svd2rust` generated API
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Peripherals --> uarte1[.UARTE1: <b>UARTE1</b>]
|
||||
uarte1 --> uart1_baudrate[.baudrate: <b>BAUDRATE</b>]
|
||||
uarte1 --> uart1_inten[.inten: <b>INTEN</b>]
|
||||
Peripherals --> uarte2[.UARTE2: <b>UARTE2</b>]
|
||||
uarte2 --> uart2_baudrate[.baudrate: <b>BAUDRATE</b>]
|
||||
uarte2 --> uart2_inten[.inten: <b>INTEN</b>]
|
||||
```
|
||||
|
||||
* The crate has a top-level `struct Peripherals` with members for each *Peripheral*
|
||||
* Each *Peripheral* gets a `struct`, like `UARTE0`, `SPI1`, etc.
|
||||
* Each *Peripheral* `struct` has members for each *Register*
|
||||
* Each *Register* gets a `struct`, like `BAUDRATE`, `INTEN`, etc.
|
||||
* Each *Register* `struct` has `read()`, `write()` and `modify()` methods
|
||||
|
||||
---
|
||||
|
||||
## The `svd2rust` generated API (2)
|
||||
|
||||
* The `read()` method returns a special proxy object, with methods for each *Field*
|
||||
* The `write()` method takes a closure, which is given a special 'proxy' object, with methods for each *Field*
|
||||
* All the *Field* changes are batched together and written in one go
|
||||
* Any un-written *Fields* are set to a default value
|
||||
* The `modify()` method gives you both
|
||||
* Any un-written *Fields* are left alone
|
||||
|
||||
---
|
||||
|
||||
## Using a PAC
|
||||
|
||||
```rust
|
||||
// nrf52840 is the PAC
|
||||
let p = nrf52840::Peripherals::take().unwrap();
|
||||
// Reading the 'baudrate' field
|
||||
let current_baud_rate = p.UARTE1.baudrate.read().baudrate();
|
||||
// Modifying multiple fields in one go
|
||||
p.UARTE1.inten.modify(|_r, w| {
|
||||
w.cts().enabled();
|
||||
w.ncts().enabled();
|
||||
w.rxrdy().enabled();
|
||||
w
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wait, what's a closure?
|
||||
|
||||
* It's an anonymous function, declared in-line with your other code
|
||||
* It can 'capture' local variables (although we don't use that feature here)
|
||||
* It enables a very powerful Rust idiom, that you can't easily do in C...
|
||||
|
||||
---
|
||||
|
||||
## Let's take it in turns
|
||||
|
||||
- I, the callee, need to set some stuff up
|
||||
- You, the caller, need to do a bit of work
|
||||
- I, the callee, need to clean everything up
|
||||
|
||||
We can use a closure to insert the caller-provided code in the middle of our function. We see this used [all (1)](https://doc.rust-lang.org/core/iter/trait.Iterator.html#method.map) [over (2)](https://doc.rust-lang.org/core/primitive.str.html#method.matches) [the (3)](https://doc.rust-lang.org/std/thread/fn.spawn.html) Rust standard library!
|
||||
|
||||
---
|
||||
|
||||
## You tell me...
|
||||
|
||||
What are the three steps here?
|
||||
|
||||
```rust
|
||||
p.UARTE1.inten.modify(|_r, w| {
|
||||
w.cts().enabled();
|
||||
w.ncts().enabled();
|
||||
w.rxrdy().enabled();
|
||||
w
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
Docs can be generated from the source code.
|
||||
|
||||
See <https://docs.rs/nrf52840>
|
||||
|
||||
Note that `uarte0` is a *module* and `UARTE0` could mean either a `struct` type, or a field on the `Peripherals` struct.
|
1
down-the-stack-book/src/writing_an_application.md
Normal file
1
down-the-stack-book/src/writing_an_application.md
Normal file
|
@ -0,0 +1 @@
|
|||
# Writing an Application
|
19
down-the-stack/apps/.cargo/config.toml
Normal file
19
down-the-stack/apps/.cargo/config.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||
# (..)
|
||||
rustflags = [
|
||||
"-C", "linker=flip-link", # adds stack overflow protection
|
||||
"-C", "link-arg=-Tdefmt.x", # defmt support
|
||||
# (..)
|
||||
]
|
||||
|
||||
[target.thumbv7em-none-eabihf]
|
||||
# set custom cargo runner to flash & run on embedded target when we call `cargo run`
|
||||
# for more information, check out https://github.com/knurling-rs/probe-run
|
||||
runner = "probe-run --chip nRF52840_xxAA"
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
]
|
||||
|
||||
[build]
|
||||
# cross-compile to this target
|
||||
target = "thumbv7em-none-eabihf" # = ARM Cortex-M4
|
48
down-the-stack/apps/Cargo.toml
Normal file
48
down-the-stack/apps/Cargo.toml
Normal file
|
@ -0,0 +1,48 @@
|
|||
[package]
|
||||
authors = ["Tanks Transfeld <tanks.transfeld@ferrous-systems.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "apps"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
|
||||
cortex-m-rt = "0.7.2"
|
||||
dk_bsc = { path = "../dk_bsc" }
|
||||
heapless = "0.7.16"
|
||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
||||
defmt = "0.3.2"
|
||||
defmt-rtt = "0.3.2"
|
||||
|
||||
# optimize code in both profiles
|
||||
[profile.dev]
|
||||
codegen-units = 1
|
||||
debug = 2
|
||||
debug-assertions = true # !
|
||||
incremental = false
|
||||
lto = "fat"
|
||||
opt-level = 'z' # !
|
||||
overflow-checks = false
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
debug = 1
|
||||
debug-assertions = false
|
||||
incremental = false
|
||||
lto = "fat"
|
||||
opt-level = 3
|
||||
overflow-checks = false
|
||||
|
||||
[features]
|
||||
|
||||
default = [
|
||||
"other-feature"
|
||||
]
|
||||
other-feature = []
|
||||
# do NOT modify these features
|
||||
defmt-default = []
|
||||
defmt-trace = []
|
||||
defmt-debug = []
|
||||
defmt-info = []
|
||||
defmt-warn = []
|
||||
defmt-error = []
|
31
down-the-stack/apps/src/bin/button.rs
Normal file
31
down-the-stack/apps/src/bin/button.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
|
||||
use cortex_m_rt::entry;
|
||||
|
||||
// this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||
use apps as _;
|
||||
|
||||
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
// to enable more verbose logs, go to your `Cargo.toml` and set defmt logging levels
|
||||
// to `defmt-trace` by changing the `default = []` entry in `[features]`
|
||||
|
||||
let board = dk_bsc::init().unwrap();
|
||||
|
||||
let mut led = board.leds;
|
||||
let button_1 = board.buttons.b_1;
|
||||
|
||||
loop {
|
||||
if button_1.is_pushed() {
|
||||
led.led_1.on();
|
||||
} else {
|
||||
led.led_1.off();
|
||||
}
|
||||
}
|
||||
|
||||
// this program does not `exit`; use Ctrl+C to terminate it
|
||||
}
|
28
down-the-stack/apps/src/bin/hello.rs
Normal file
28
down-the-stack/apps/src/bin/hello.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
// this program does not use the standard library to avoid heap allocations.
|
||||
// only the `core` library functions are available.
|
||||
#![no_std]
|
||||
// this program uses a custom entry point instead of `fn main()`
|
||||
#![no_main]
|
||||
|
||||
use cortex_m::asm;
|
||||
use cortex_m_rt::entry;
|
||||
// this imports `beginner/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||
use apps as _;
|
||||
|
||||
// the custom entry point
|
||||
// vvvvv
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
// ˆˆˆ
|
||||
// ! is the 'never' type: this function never returns
|
||||
|
||||
// initializes the peripherals
|
||||
dk_bsc::init().unwrap();
|
||||
|
||||
defmt::println!("Hello, world!"); // :wave:
|
||||
|
||||
loop {
|
||||
// breakpoint: halts the program's execution
|
||||
asm::bkpt();
|
||||
}
|
||||
}
|
28
down-the-stack/apps/src/bin/uarte_print.rs
Normal file
28
down-the-stack/apps/src/bin/uarte_print.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use cortex_m::asm;
|
||||
use cortex_m_rt::entry;
|
||||
use core::fmt::Write;
|
||||
|
||||
// this imports `down-the-stack/apps/lib.rs` to retrieve our global logger + panicking-behavior
|
||||
use apps as _;
|
||||
|
||||
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
// to enable more verbose logs, go to your `Cargo.toml` and set defmt logging levels
|
||||
// to `defmt-trace` by changing the `default = []` entry in `[features]`
|
||||
|
||||
let board = dk_bsc::init().unwrap();
|
||||
let mut uarte = board.uarte;
|
||||
|
||||
let tx_buffer = "Hello, World!\n";
|
||||
uarte.write_str(tx_buffer).unwrap();
|
||||
|
||||
// this program does not `exit`; use Ctrl+C to terminate it
|
||||
loop {
|
||||
asm::nop();
|
||||
}
|
||||
}
|
10
down-the-stack/apps/src/lib.rs
Normal file
10
down-the-stack/apps/src/lib.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
#![no_std]
|
||||
// ⚠️ ⚠️ ⚠️ Don't change this file! ⚠️ ⚠️ ⚠️
|
||||
use panic_probe as _;
|
||||
|
||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||
#[defmt::panic_handler]
|
||||
fn panic() -> ! {
|
||||
cortex_m::asm::udf()
|
||||
}
|
24
down-the-stack/dk_bsc/Cargo.toml
Normal file
24
down-the-stack/dk_bsc/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
authors = ["Jorge Aparicio <jorge.aparicio@ferrous-systems.com>", "Tanks Transfeld <tanks.transfeld@ferrous-systems.com"]
|
||||
edition = "2018"
|
||||
license = "MIT OR Apache-2.0"
|
||||
name = "dk_bsc"
|
||||
version = "0.0.0"
|
||||
|
||||
[dependencies]
|
||||
cortex-m = {version = "0.7.6", features = ["critical-section-single-core"]}
|
||||
cortex-m-rt = "0.7.2"
|
||||
embedded-hal = "0.2.7"
|
||||
nrf52840-hal = "0.14.0"
|
||||
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
||||
defmt = "0.3.2"
|
||||
defmt-rtt = "0.3.2"
|
||||
|
||||
[features]
|
||||
advanced = []
|
||||
beginner = []
|
||||
|
||||
default = [
|
||||
"other-feature"
|
||||
]
|
||||
other-feature = []
|
7
down-the-stack/dk_bsc/README.md
Normal file
7
down-the-stack/dk_bsc/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# `dk`
|
||||
|
||||
Board Support Crate for the nRF52840 Development Kit (DK)
|
||||
|
||||
## Getting familiar with the hardware
|
||||
|
||||
TODO
|
12
down-the-stack/dk_bsc/build.rs
Normal file
12
down-the-stack/dk_bsc/build.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use std::{env, error::Error, fs, path::PathBuf};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
||||
|
||||
// put memory layout (linker script) in the linker search path
|
||||
fs::copy("memory.x", out_dir.join("memory.x"))?;
|
||||
|
||||
println!("cargo:rustc-link-search={}", out_dir.display());
|
||||
|
||||
Ok(())
|
||||
}
|
5
down-the-stack/dk_bsc/memory.x
Normal file
5
down-the-stack/dk_bsc/memory.x
Normal file
|
@ -0,0 +1,5 @@
|
|||
MEMORY
|
||||
{
|
||||
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 256K
|
||||
}
|
259
down-the-stack/dk_bsc/src/lib.rs
Normal file
259
down-the-stack/dk_bsc/src/lib.rs
Normal file
|
@ -0,0 +1,259 @@
|
|||
//! Board Support Crate (BSC) for the nRF52840 Development Kit
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![no_std]
|
||||
|
||||
use core::{
|
||||
ops,
|
||||
sync::atomic::{self, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use cortex_m::asm;
|
||||
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
|
||||
|
||||
use nrf52840_hal as hal;
|
||||
use hal::{
|
||||
gpio::{p0, Level, Output, Pin, Port, PushPull},
|
||||
timer::OneShot,
|
||||
};
|
||||
|
||||
use defmt;
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
/// Components on the boarde
|
||||
// Add structs for the peripheral you want to implement. First for the buttons, later UARTE
|
||||
pub struct Board {
|
||||
/// LEDs
|
||||
pub leds: Leds,
|
||||
|
||||
/// Timer
|
||||
pub timer: Timer,
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// All LEDs on the board
|
||||
pub struct Leds {
|
||||
/// LED1: pin P0.13, green LED
|
||||
pub led_1: Led,
|
||||
/// LED2: pin P0.14, green LED
|
||||
pub led_2: Led,
|
||||
/// LED3: pin P0.15, green LED
|
||||
pub led_3: Led,
|
||||
/// LED4: pin P0.16, green LED
|
||||
pub led_4: Led,
|
||||
}
|
||||
|
||||
/// A single LED
|
||||
pub struct Led {
|
||||
inner: Pin<Output<PushPull>>,
|
||||
}
|
||||
|
||||
impl Led {
|
||||
/// Turns on the LED
|
||||
pub fn on(&mut self) {
|
||||
defmt::trace!(
|
||||
"setting P{}.{} low (LED on)",
|
||||
port_as_char(&self.inner.port()),
|
||||
self.inner.pin()
|
||||
);
|
||||
|
||||
// NOTE this operations returns a `Result` but never returns the `Err` variant
|
||||
let _ = self.inner.set_low();
|
||||
}
|
||||
|
||||
/// Turns off the LED
|
||||
pub fn off(&mut self) {
|
||||
defmt::trace!(
|
||||
"setting P{}.{} high (LED off)",
|
||||
port_as_char(&self.inner.port()),
|
||||
self.inner.pin()
|
||||
);
|
||||
|
||||
// NOTE this operations returns a `Result` but never returns the `Err` variant
|
||||
let _ = self.inner.set_high();
|
||||
}
|
||||
|
||||
/// Returns `true` if the LED is in the OFF state
|
||||
pub fn is_off(&self) -> bool {
|
||||
self.inner.is_set_high() == Ok(true)
|
||||
}
|
||||
|
||||
/// Returns `true` if the LED is in the ON state
|
||||
pub fn is_on(&self) -> bool {
|
||||
!self.is_off()
|
||||
}
|
||||
|
||||
/// Toggles the state (on/off) of the LED
|
||||
pub fn toggle(&mut self) {
|
||||
if self.is_off() {
|
||||
self.on();
|
||||
} else {
|
||||
self.off()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All buttons on the board
|
||||
// todo! Add a struct that represents all buttons of the board.
|
||||
// ...
|
||||
|
||||
/// A single button
|
||||
// todo! Add a struct that represents a single button.
|
||||
// ...
|
||||
|
||||
|
||||
// Add an impl block for the Button struct
|
||||
// todo! Add a method that returns true, if the button is pushed.
|
||||
// ...
|
||||
|
||||
|
||||
|
||||
/// A timer for creating blocking delays
|
||||
pub struct Timer {
|
||||
inner: hal::Timer<hal::pac::TIMER0, OneShot>,
|
||||
}
|
||||
|
||||
|
||||
impl Timer {
|
||||
/// Blocks program execution for at least the specified `duration`
|
||||
pub fn wait(&mut self, duration: Duration) {
|
||||
defmt::trace!("blocking for {:?} ...", duration);
|
||||
|
||||
// 1 cycle = 1 microsecond because the underlying HAL driver
|
||||
// always sets the timer to 1 MHz.
|
||||
const NANOS_IN_ONE_MICRO: u32 = 1_000;
|
||||
let subsec_micros = duration.subsec_nanos() / NANOS_IN_ONE_MICRO;
|
||||
if subsec_micros != 0 {
|
||||
self.inner.delay(subsec_micros);
|
||||
}
|
||||
|
||||
const MICROS_IN_ONE_SEC: u32 = 1_000_000;
|
||||
// maximum number of seconds that fit in a single `delay` call without overflowing the `u32`
|
||||
// argument
|
||||
const MAX_SECS: u32 = u32::MAX / MICROS_IN_ONE_SEC;
|
||||
let mut secs = duration.as_secs();
|
||||
while secs != 0 {
|
||||
let cycles = if secs > MAX_SECS as u64 {
|
||||
secs -= MAX_SECS as u64;
|
||||
MAX_SECS * MICROS_IN_ONE_SEC
|
||||
} else {
|
||||
let cycles = secs as u32 * MICROS_IN_ONE_SEC;
|
||||
secs = 0;
|
||||
cycles
|
||||
};
|
||||
|
||||
self.inner.delay(cycles)
|
||||
}
|
||||
|
||||
defmt::trace!("... DONE");
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Timer {
|
||||
type Target = hal::Timer<hal::pac::TIMER0, OneShot>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for Timer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// Uarte peripheral
|
||||
// todo! Add a struct that represents the Uarte
|
||||
// ...
|
||||
|
||||
|
||||
// todo! Implement the fmt::Write Trait for Uarte
|
||||
// ...
|
||||
|
||||
/// Initializes the board
|
||||
///
|
||||
/// This return an `Err`or if called more than once
|
||||
pub fn init() -> Result<Board, ()> {
|
||||
if let Some(periph) = hal::pac::Peripherals::take() {
|
||||
|
||||
let pins = p0::Parts::new(periph.P0);
|
||||
|
||||
// NOTE LEDs turn on when the pin output level is low
|
||||
let led_1 = pins.p0_13.degrade().into_push_pull_output(Level::High);
|
||||
let led_2 = pins.p0_14.degrade().into_push_pull_output(Level::High);
|
||||
let led_3 = pins.p0_15.degrade().into_push_pull_output(Level::High);
|
||||
let led_4 = pins.p0_16.degrade().into_push_pull_output(Level::High);
|
||||
|
||||
|
||||
// Buttons
|
||||
// todo! Assign the pins of the buttons
|
||||
// ...
|
||||
|
||||
|
||||
|
||||
defmt::debug!("I/O pins have been configured for digital output");
|
||||
|
||||
let timer = hal::Timer::new(periph.TIMER0);
|
||||
|
||||
|
||||
// Uarte
|
||||
// todo! Assign the pins of the UARTE peripheral
|
||||
// ...
|
||||
|
||||
// todo! Instantiate the UARTE peripheral
|
||||
// ...
|
||||
|
||||
|
||||
|
||||
Ok(Board {
|
||||
leds: Leds {
|
||||
led_1: Led { inner: led_1 },
|
||||
led_2: Led { inner: led_2 },
|
||||
led_3: Led { inner: led_3 },
|
||||
led_4: Led { inner: led_4 },
|
||||
},
|
||||
|
||||
// todo! Create an instance of the struct that contains all the single buttons.
|
||||
// ...
|
||||
|
||||
timer: Timer { inner: timer },
|
||||
|
||||
// todo! Create an instance of the UARTE struct
|
||||
// ...
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Exits the application when the program is executed through the `probe-run` Cargo runner
|
||||
pub fn exit() -> ! {
|
||||
unsafe {
|
||||
// turn off the USB D+ pull-up before pausing the device with a breakpoint
|
||||
// this disconnects the nRF device from the USB host so the USB host won't attempt further
|
||||
// USB communication (and see an unresponsive device). probe-run will also reset the nRF's
|
||||
// USBD peripheral when it sees the device in a halted state which has the same effect as
|
||||
// this line but that can take a while and the USB host may issue a power cycle of the USB
|
||||
// port / hub / root in the meantime, which can bring down the probe and break probe-run
|
||||
const USBD_USBPULLUP: *mut u32 = 0x4002_7504 as *mut u32;
|
||||
USBD_USBPULLUP.write_volatile(0)
|
||||
}
|
||||
defmt::println!("`dk::exit()` called; exiting ...");
|
||||
// force any pending memory operation to complete before the BKPT instruction that follows
|
||||
atomic::compiler_fence(Ordering::SeqCst);
|
||||
loop {
|
||||
asm::bkpt()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn port_as_char(port: &Port) -> char {
|
||||
match port {
|
||||
Port::Port0 => '0',
|
||||
Port::Port1 => '1',
|
||||
}
|
||||
}
|
303
down-the-stack/dk_bsc/src/lib_solution.rs
Normal file
303
down-the-stack/dk_bsc/src/lib_solution.rs
Normal file
|
@ -0,0 +1,303 @@
|
|||
//! Board Support Crate (BSC) for the nRF52840 Development Kit
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![no_std]
|
||||
|
||||
use core::{
|
||||
ops,
|
||||
fmt,
|
||||
sync::atomic::{self, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use cortex_m::asm;
|
||||
use embedded_hal::digital::v2::{OutputPin as _, StatefulOutputPin};
|
||||
use nrf52840_hal as hal;
|
||||
pub use hal::pac::uarte0::{
|
||||
baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
|
||||
|
||||
|
||||
use hal::{
|
||||
gpio::{p0, Level, Output, Input, PullUp, Pin, Port, PushPull},
|
||||
timer::OneShot, prelude::InputPin,
|
||||
uarte,
|
||||
};
|
||||
|
||||
use defmt;
|
||||
use defmt_rtt as _; // global logger
|
||||
|
||||
/// Components on the board
|
||||
pub struct Board {
|
||||
/// LEDs
|
||||
pub leds: Leds,
|
||||
// 🔽 --- Button Exercise --- 🔽
|
||||
/// Buttons
|
||||
pub buttons: Buttons,
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
/// Timer
|
||||
pub timer: Timer,
|
||||
// 🔽 --- UARTE Exercise --- 🔽
|
||||
/// uarte interface
|
||||
pub uarte: Uarte,
|
||||
// 🔼 --- UARTE Exercise --- 🔼
|
||||
}
|
||||
|
||||
/// All LEDs on the board
|
||||
pub struct Leds {
|
||||
/// LED1: pin P0.13, green LED
|
||||
pub led_1: Led,
|
||||
/// LED2: pin P0.14, green LED
|
||||
pub led_2: Led,
|
||||
/// LED3: pin P0.15, green LED
|
||||
pub led_3: Led,
|
||||
/// LED4: pin P0.16, green LED
|
||||
pub led_4: Led,
|
||||
}
|
||||
|
||||
/// A single LED
|
||||
pub struct Led {
|
||||
inner: Pin<Output<PushPull>>,
|
||||
}
|
||||
|
||||
impl Led {
|
||||
/// Turns on the LED
|
||||
pub fn on(&mut self) {
|
||||
defmt::trace!(
|
||||
"setting P{}.{} low (LED on)",
|
||||
port_as_char(&self.inner.port()),
|
||||
self.inner.pin()
|
||||
);
|
||||
|
||||
// NOTE this operations returns a `Result` but never returns the `Err` variant
|
||||
let _ = self.inner.set_low();
|
||||
}
|
||||
|
||||
/// Turns off the LED
|
||||
pub fn off(&mut self) {
|
||||
defmt::trace!(
|
||||
"setting P{}.{} high (LED off)",
|
||||
port_as_char(&self.inner.port()),
|
||||
self.inner.pin()
|
||||
);
|
||||
|
||||
// NOTE this operations returns a `Result` but never returns the `Err` variant
|
||||
let _ = self.inner.set_high();
|
||||
}
|
||||
|
||||
/// Returns `true` if the LED is in the OFF state
|
||||
pub fn is_off(&self) -> bool {
|
||||
self.inner.is_set_high() == Ok(true)
|
||||
}
|
||||
|
||||
/// Returns `true` if the LED is in the ON state
|
||||
pub fn is_on(&self) -> bool {
|
||||
!self.is_off()
|
||||
}
|
||||
|
||||
/// Toggles the state (on/off) of the LED
|
||||
pub fn toggle(&mut self) {
|
||||
if self.is_off() {
|
||||
self.on();
|
||||
} else {
|
||||
self.off()
|
||||
}
|
||||
}
|
||||
}
|
||||
// 🔽 --- Button Exercise --- 🔽
|
||||
/// All buttons on the board
|
||||
pub struct Buttons {
|
||||
/// BUTTON1: pin P0.11
|
||||
pub b_1: Button,
|
||||
/// BUTTON2: pin P0.12
|
||||
pub b_2: Button,
|
||||
/// BUTTON3: pin P0.24
|
||||
pub b_3: Button,
|
||||
/// BUTTON4: pin P0.25
|
||||
pub b_4: Button,
|
||||
}
|
||||
|
||||
/// A single button
|
||||
pub struct Button {
|
||||
inner: Pin<Input<PullUp>>,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
/// returns true if button is pushed
|
||||
pub fn is_pushed(&self) -> bool {
|
||||
self.inner.is_low() == Ok(true)
|
||||
}
|
||||
}
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
|
||||
/// A timer for creating blocking delays
|
||||
pub struct Timer {
|
||||
inner: hal::Timer<hal::pac::TIMER0, OneShot>,
|
||||
}
|
||||
|
||||
|
||||
impl Timer {
|
||||
/// Blocks program execution for at least the specified `duration`
|
||||
pub fn wait(&mut self, duration: Duration) {
|
||||
defmt::trace!("blocking for {:?} ...", duration);
|
||||
|
||||
// 1 cycle = 1 microsecond because the underlying HAL driver
|
||||
// always sets the timer to 1 MHz.
|
||||
const NANOS_IN_ONE_MICRO: u32 = 1_000;
|
||||
let subsec_micros = duration.subsec_nanos() / NANOS_IN_ONE_MICRO;
|
||||
if subsec_micros != 0 {
|
||||
self.inner.delay(subsec_micros);
|
||||
}
|
||||
|
||||
const MICROS_IN_ONE_SEC: u32 = 1_000_000;
|
||||
// maximum number of seconds that fit in a single `delay` call without overflowing the `u32`
|
||||
// argument
|
||||
const MAX_SECS: u32 = u32::MAX / MICROS_IN_ONE_SEC;
|
||||
let mut secs = duration.as_secs();
|
||||
while secs != 0 {
|
||||
let cycles = if secs > MAX_SECS as u64 {
|
||||
secs -= MAX_SECS as u64;
|
||||
MAX_SECS * MICROS_IN_ONE_SEC
|
||||
} else {
|
||||
let cycles = secs as u32 * MICROS_IN_ONE_SEC;
|
||||
secs = 0;
|
||||
cycles
|
||||
};
|
||||
|
||||
self.inner.delay(cycles)
|
||||
}
|
||||
|
||||
defmt::trace!("... DONE");
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Timer {
|
||||
type Target = hal::Timer<hal::pac::TIMER0, OneShot>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::DerefMut for Timer {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
// 🔽 --- UARTE Exercise --- 🔽
|
||||
/// Uarte peripheral
|
||||
pub struct Uarte {
|
||||
inner: hal::Uarte<hal::pac::UARTE1>,
|
||||
}
|
||||
|
||||
impl fmt::Write for Uarte {
|
||||
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
// Copy all data into an on-stack buffer so we never try to EasyDMA from
|
||||
// flash.
|
||||
let mut buf: [u8; 16] = [0; 16];
|
||||
for block in s.as_bytes().chunks(16) {
|
||||
buf[..block.len()].copy_from_slice(block);
|
||||
self.inner.write(&buf[..block.len()]).map_err(|_| fmt::Error)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// 🔼 --- UARTE Exercise --- 🔼
|
||||
|
||||
/// Initializes the board
|
||||
///
|
||||
/// This return an `Err`or if called more than once
|
||||
pub fn init() -> Result<Board, ()> {
|
||||
if let Some(periph) = hal::pac::Peripherals::take() {
|
||||
|
||||
let pins = p0::Parts::new(periph.P0);
|
||||
|
||||
// NOTE LEDs turn on when the pin output level is low
|
||||
let led_1 = pins.p0_13.degrade().into_push_pull_output(Level::High);
|
||||
let led_2 = pins.p0_14.degrade().into_push_pull_output(Level::High);
|
||||
let led_3 = pins.p0_15.degrade().into_push_pull_output(Level::High);
|
||||
let led_4 = pins.p0_16.degrade().into_push_pull_output(Level::High);
|
||||
|
||||
// 🔽 --- Button Exercise --- 🔽
|
||||
// Buttons
|
||||
let b_1 = pins.p0_11.degrade().into_pullup_input();
|
||||
let b_2 = pins.p0_12.degrade().into_pullup_input();
|
||||
let b_3 = pins.p0_24.degrade().into_pullup_input();
|
||||
let b_4 = pins.p0_25.degrade().into_pullup_input();
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
|
||||
defmt::debug!("I/O pins have been configured for digital output");
|
||||
|
||||
let timer = hal::Timer::new(periph.TIMER0);
|
||||
|
||||
// 🔽 --- UARTE Exercise --- 🔽
|
||||
// Uarte
|
||||
let pins = hal::uarte::Pins {
|
||||
rxd: pins.p0_08.degrade().into_floating_input(),
|
||||
txd: pins.p0_06.degrade().into_push_pull_output(Level::High),
|
||||
cts: None,
|
||||
rts: None,
|
||||
};
|
||||
|
||||
|
||||
let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::EXCLUDED, Baudrate::BAUD115200);
|
||||
// 🔼 --- UARTE Exercise --- 🔼
|
||||
|
||||
Ok(Board {
|
||||
leds: Leds {
|
||||
led_1: Led { inner: led_1 },
|
||||
led_2: Led { inner: led_2 },
|
||||
led_3: Led { inner: led_3 },
|
||||
led_4: Led { inner: led_4 },
|
||||
},
|
||||
|
||||
// 🔽 --- Button Exercise --- 🔽
|
||||
buttons: Buttons {
|
||||
b_1: Button { inner: b_1},
|
||||
b_2: Button { inner: b_2},
|
||||
b_3: Button { inner: b_3},
|
||||
b_4: Button { inner: b_4},
|
||||
},
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
|
||||
timer: Timer { inner: timer },
|
||||
|
||||
// 🔽 --- UARTE Exercise --- 🔽
|
||||
uarte: Uarte { inner: uarte },
|
||||
// 🔼 --- UARTE Exercise --- 🔼
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Exits the application when the program is executed through the `probe-run` Cargo runner
|
||||
pub fn exit() -> ! {
|
||||
unsafe {
|
||||
// turn off the USB D+ pull-up before pausing the device with a breakpoint
|
||||
// this disconnects the nRF device from the USB host so the USB host won't attempt further
|
||||
// USB communication (and see an unresponsive device). probe-run will also reset the nRF's
|
||||
// USBD peripheral when it sees the device in a halted state which has the same effect as
|
||||
// this line but that can take a while and the USB host may issue a power cycle of the USB
|
||||
// port / hub / root in the meantime, which can bring down the probe and break probe-run
|
||||
const USBD_USBPULLUP: *mut u32 = 0x4002_7504 as *mut u32;
|
||||
USBD_USBPULLUP.write_volatile(0)
|
||||
}
|
||||
defmt::println!("`dk::exit()` called; exiting ...");
|
||||
// force any pending memory operation to complete before the BKPT instruction that follows
|
||||
atomic::compiler_fence(Ordering::SeqCst);
|
||||
loop {
|
||||
asm::bkpt()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn port_as_char(port: &Port) -> char {
|
||||
match port {
|
||||
Port::Port0 => '0',
|
||||
Port::Port1 => '1',
|
||||
}
|
||||
}
|
|
@ -24,6 +24,10 @@
|
|||
- [Collision avoidance](./collision-avoidance.md)
|
||||
- [Interrupt handling](./interrupt-handling.md)
|
||||
- [Starting a Project from Scratch](./from-scratch.md)
|
||||
- [Down the Stack Workbook](./down-the-stack.md)
|
||||
- [BSC Exercise](./bsc-exercise.md)
|
||||
- [Button Implementation](./button-implementation.md)
|
||||
- [UARTE Implementation](./uarte-implementation.md)
|
||||
- [Advanced Workbook](./advanced-workbook.md)
|
||||
- [Code Organization](./code-organisation.md)
|
||||
- [Listing USB Devices](./listing-usb-devices.md)
|
||||
|
|
67
embedded-workshop-book/src/bsc-exercise.md
Normal file
67
embedded-workshop-book/src/bsc-exercise.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# BSC Exercise
|
||||
|
||||
In this exercise you will learn how to write a *Board Support Crate* (or BSC, also known as a *Board Support Package*) by implementing support for handling button presses, and support for using the UARTE peripheral.
|
||||
|
||||
The template `down-the-stack/dk_bsc/src/lib.rs` already contains the LED and Timer implementations. Add your code to the designated lines. You'll find a `//todo!` there.
|
||||
|
||||
You can test after each step by running the following command out of `down-the-stack/apps`
|
||||
```
|
||||
cargo run --bin hello
|
||||
```
|
||||
This program will not call any of the functions you are implementing, so it does not matter if they are incomplete. It will refuse to build if there are errors present in the `lib.rs`!
|
||||
|
||||
`down-the-stack/dk_bsc/src/lib_solution.rs` contains the full solution code.
|
||||
|
||||
## You will learn how to
|
||||
* modify the `init()` function that brings up the board's peripherals
|
||||
* how to configure pins
|
||||
* how to write a function that checks the state of a pin
|
||||
* implement functionality on a type
|
||||
* implement a Trait
|
||||
* to document and generate docs for your own library!
|
||||
|
||||
## Prerequisites
|
||||
* installation of `pyserial` or other serial terminal that you are familiar with.
|
||||
* `impl` keyword
|
||||
* methods and associated functions
|
||||
* `pub` keyword
|
||||
* usage of structs to represent registers
|
||||
* Trait
|
||||
|
||||
## Tasks
|
||||
### Write a button implementation. This entails the following steps:
|
||||
* Add `struct Buttons` with 4 fields, that represents each of the four buttons.
|
||||
* Add `struct Button` that is a wrapper for the pin that a single button is connected to.
|
||||
* Write a method `is_pushed` that checks if a single button is pushed.
|
||||
* Initialize the pins in `fn init()`.
|
||||
* Add the `struct Button` to the definition and instantiation of `struct Board`.
|
||||
* Run `apps/buttons.rs` to test.
|
||||
* Run `cargo doc` out of the apps folder to find all your doc comments!
|
||||
### Write a UARTE implementation. This entails the following steps:
|
||||
* Check the `uarte` module of the `nrf-hal` for requirements of the instantiating method.
|
||||
* Add `struct Uarte` that serves as wrapper for the `UARTE1` instance.
|
||||
* Initialize the UARTE1 peripheral in `fn init()` using the following settings:
|
||||
* parity: included
|
||||
* baudrate: 115200 baud
|
||||
* Add `struct Uarte` to the definition and instantiation of `struct Board`.
|
||||
* Implement the `fmt::Write` trait for `struct Uarte`.
|
||||
* Connect your computer to the virtual UART port with `screen`.
|
||||
* Run `apps/uarte_print.rs` to test.
|
||||
## Knowledge
|
||||
|
||||
### Comments
|
||||
The `lib.rs` has an attribute `#![deny(missing_docs)]`. This means, that missing doc comments for structs are returned as compiler errors, to remind you to document your work properly.
|
||||
|
||||
```rust
|
||||
/// This is a doc comment
|
||||
// This is a normal comment
|
||||
```
|
||||
### Structs represent Registers
|
||||
|
||||
[todo!] insert refresher from rust fundamentals
|
||||
## Hardware documentation for pin configuration
|
||||
|
||||
Go to [Nordic Infocenter](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/intro.html) to download the User Guide. You can find all the information that is relevant to this exercise in there.
|
||||
|
||||
|
||||
|
146
embedded-workshop-book/src/button-implementation.md
Normal file
146
embedded-workshop-book/src/button-implementation.md
Normal file
|
@ -0,0 +1,146 @@
|
|||
# Write the Button Implementation
|
||||
## Step-by-Step Solution
|
||||
|
||||
### Step 1: Read the docs!
|
||||
|
||||
✅ Read the [User Guide section 8.7](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/hw_buttons_leds.html?cp=5_0_4_7_6) for info about pins and pin configuration related to the buttons. Note down the pins that the buttons are connected to.
|
||||
|
||||
The pins need to be configured as input pins with an internal pull-up. The pins as well as the configurations are defined as types in the `nrf-hal` in the `gpio` peripheral. Add the following imports: `Input` and `PullUp`.
|
||||
|
||||
### Step 2: Add the structs that represent the buttons as a group and a generic single button.
|
||||
|
||||
✅ Add the struct that represents the single button. It has only one field, `inner`. The type of this button is the pin configuration: `Pin<Input<PullUp>>`
|
||||
|
||||
✅ Add the `struct` that represents the group of buttons has four fields, one for each button. The field name contains the number that corresponds to the button numeration on the board. The type of each field is the struct that represents the generic single button.
|
||||
|
||||
✅ Add doc comments for every struct and field!
|
||||
|
||||
Building this code should return a warning: field `inner` is never read.
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
|
||||
```rust
|
||||
/// All buttons on the board
|
||||
pub struct Buttons {
|
||||
/// BUTTON1: pin P0.11
|
||||
pub b_1: Button,
|
||||
/// BUTTON2: pin P0.12
|
||||
pub b_2: Button,
|
||||
/// BUTTON3: pin P0.24
|
||||
pub b_3: Button,
|
||||
/// BUTTON4: pin P0.25
|
||||
pub b_4: Button,
|
||||
}
|
||||
|
||||
/// A single button
|
||||
pub struct Button {
|
||||
inner: Pin<Input<PullUp>>,
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Step 3: Implement the button function.
|
||||
|
||||
✅ Add an `impl` block for the `struct Button`. Add a method `is_pushed` that takes in the struct as `&self` and returns a bool, if the button is pushed.
|
||||
|
||||
✅ Now remember, the pins the buttons are connected to are configured as active low. For buttons this means, that the pin is pulled low, when the button is pushed.
|
||||
|
||||
✅ In the `nrf-hal` you can find a method to check if a single pin is low. To use it, you have to add the following line to your `nrf52840_hal` imports: `prelude::InputPin`.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
|
||||
```rust
|
||||
impl Button {
|
||||
/// returns true if button is pushed
|
||||
pub fn is_pushed(&self) -> bool {
|
||||
self.inner.is_low() == Ok(true)
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Step 4: Bring up the pins!
|
||||
|
||||
✅ Go to `pub fn init()`, the function that initializes the board's peripherals.
|
||||
|
||||
✅ Configure each pin as degraded, pull-up input pin and bind it to a variable that makes it clear what button number it is connected to.
|
||||
|
||||
Building this code brings up warnings about unused variables.
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
|
||||
```rust
|
||||
// Buttons
|
||||
let b_1 = pins.p0_11.degrade().into_pullup_input();
|
||||
let b_2 = pins.p0_12.degrade().into_pullup_input();
|
||||
let b_3 = pins.p0_24.degrade().into_pullup_input();
|
||||
let b_4 = pins.p0_25.degrade().into_pullup_input();
|
||||
```
|
||||
</details>
|
||||
|
||||
### Step 5: Add everything to the board struct.
|
||||
|
||||
✅ In the definition of the `struct Board` add a field for the `struct Buttons`.
|
||||
|
||||
✅ In the pub `fn init()` function, where `Board` is instantiated, add the button field, assigning the pins you defined earlier to the respective buttons.
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
|
||||
```rust
|
||||
/// Components on the board
|
||||
pub struct Board {
|
||||
/// LEDs
|
||||
pub leds: Leds,
|
||||
/// Buttons
|
||||
pub buttons: Buttons,
|
||||
/// Timer
|
||||
pub timer: Timer,
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
pub fn init() -> Result<Board, ()> {
|
||||
// ...
|
||||
Ok(Board {
|
||||
leds: Leds {
|
||||
// ...
|
||||
},
|
||||
buttons: Buttons {
|
||||
b_1: Button { inner: b_1},
|
||||
b_2: Button { inner: b_2},
|
||||
b_3: Button { inner: b_3},
|
||||
b_4: Button { inner: b_4},
|
||||
},
|
||||
timer: Timer { inner: timer },
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
</details>
|
||||
|
||||
### Step 6: Run the example!
|
||||
|
||||
✅ Go to `/down-the-stack/apps`.
|
||||
|
||||
✅ Run the following command:
|
||||
|
||||
```shell
|
||||
cargo run --bin button
|
||||
```
|
||||
|
||||
### Step 7: Generate the docs!
|
||||
|
||||
✅ Out of the apps folder run the following command to build the docs for this crate and to view your written documentation!
|
||||
|
||||
```shell
|
||||
cargo doc
|
||||
```
|
10
embedded-workshop-book/src/down-the-stack.md
Normal file
10
embedded-workshop-book/src/down-the-stack.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# Down the Stack
|
||||
|
||||
In this Session you will learn more about Rust's split crate model that includes
|
||||
* the Board Support Crate
|
||||
* an implementation of the Hardware Abstraction Layer
|
||||
* the Peripheral Access Crate
|
||||
|
||||
This session consists of lectures and two blocks of exercises:
|
||||
* BSC Exercise: Implementing further features in a basic BSC
|
||||
* PAC Exercise: Generating a PAC from an SVD file and writing into registers using the the generated PAC.
|
|
@ -157,3 +157,6 @@ $ cargo install nrfdfu
|
|||
(..)
|
||||
Installed package `nrfdfu v0.1.3` (..)
|
||||
```
|
||||
## pySerial for Down The Stack
|
||||
|
||||
If you don't have a serial terminal you're familiar with, we recommend using `pySerial`. Follow the installation instructions [here](https://pythonhosted.org/pyserial/pyserial.html#installation). This may require an update of your python installation. This is needed for the second day of the 3 day embedded training.
|
204
embedded-workshop-book/src/uarte-implementation.md
Normal file
204
embedded-workshop-book/src/uarte-implementation.md
Normal file
|
@ -0,0 +1,204 @@
|
|||
# Write the Uarte implementation
|
||||
## Step-by-Step Solution
|
||||
|
||||
### Step 1: Check Documentation.
|
||||
|
||||
The UART protocol requires up to four pins, two are mandatory, additional two are optional. they are usually labelled:
|
||||
* RXD
|
||||
* TXD
|
||||
* CTS (optional)
|
||||
* RTS (optional)
|
||||
|
||||
We will only use the mandatory ones.
|
||||
|
||||
✅ Check the [User Guide in section 7.2](https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/dk/vir_com_port.html) to find to find out which pins are reserved for these and what their configuration needs to be.
|
||||
|
||||
### Step 2: Explore the `nrf-hal` to find out what needs to be done.
|
||||
|
||||
|
||||
The `nrf52840-hal` is a crate that exports all the `52840` flagged features from the `nrf-hal-common`. Let's take a look at the `nrf52840-hal`'s [Uarte module](https://github.com/nrf-rs/nrf-hal/blob/v0.14.1/nrf-hal-common/src/uarte.rs).
|
||||
|
||||
In line 16 we see, that the nRF52840 uses the `hal::pac::UARTE1` peripheral.
|
||||
In line 44 you find the `struct Uarte<T>(T)`, the interface to a UARTE instance `T`. Besides the instance `T`, the instantiating method takes variables of the following types as arguments: `Pins`, `Parity` and `Baudrate`.
|
||||
|
||||
A quick search of the document reveals where to find all of them:
|
||||
* `Pins`: Line 463
|
||||
* `Parity` and `Baudrate`: Re-export on line 34
|
||||
|
||||
✅ Add the following lines as import:
|
||||
|
||||
```
|
||||
use hal::pac::uarte0::{
|
||||
baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity};
|
||||
use hal::uarte;
|
||||
```
|
||||
|
||||
### Step 3: Add `struct Uarte`
|
||||
|
||||
✅ Add `struct Uarte` that serves as a wrapper for underlying HAL UART driver.
|
||||
|
||||
The struct has one field labelled `inner` which is of type `hal::Uarte` but with the type parameter `T` being set to `hal::pac::UARTE1`.
|
||||
|
||||
The main difference between using our `Uarte` object and the underlying HAL `Uarte` object is that *ours* will be pre-configured for the correct pins and baud rate, according to the layout of our nRF52840-DK board and the Virtual COM Port interface chip on the other end of the UART link.
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
|
||||
```rust
|
||||
pub struct Uarte {
|
||||
inner: hal::Uarte<hal::pac::UARTE1>,
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Step 4: Bring up the peripheral in the `fn init()`
|
||||
|
||||
✅ Take a closer look at the definition of the `uarte::Pins` struct in the `nrf-hal`. Compare the pin type configurations with the ones you have already imported in `lib.rs`. Add the ones you're missing.
|
||||
|
||||
✅ Create an instance of this struct in `fn init()` with the appropriate pins and configurations. Set the output pin's level to `Level::High`.
|
||||
Note, that the third and fourth pin are each wrapped in an `Option`: Their type is `None` in this case.
|
||||
|
||||
✅ Create a Uarte driver with `hal::uarte::Uarte::new(...)` and bind it to a variable called `uarte` - we will stash this in our own `Uarte` struct later.
|
||||
|
||||
Creating the Uarte driver requires four arguments:
|
||||
* The `UARTE1` instance can be found in the `periph` variable.
|
||||
* Your instance of the `uarte::Pins` struct.
|
||||
* Set parity to `Parity::EXCLUDED`
|
||||
* set the baud rate to `Baudrate::BAUD115200`.
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
|
||||
```rust
|
||||
let pins = hal::uarte::Pins {
|
||||
rxd: pins.p0_08.degrade().into_floating_input(),
|
||||
txd: pins.p0_06.degrade().into_push_pull_output(Level::High),
|
||||
cts: None,
|
||||
rts: None,
|
||||
};
|
||||
|
||||
|
||||
let uarte = hal::uarte::Uarte::new(periph.UARTE1, pins, Parity::EXCLUDED, Baudrate::BAUD115200);
|
||||
```
|
||||
</details>
|
||||
|
||||
### Step 5: Board struct
|
||||
|
||||
✅ Add a field for the `Uarte` struct in the `Board` struct.
|
||||
add the field to the instance of the `Board` struct in `fn init()`.
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
|
||||
```rust
|
||||
|
||||
pub struct Board {
|
||||
/// LEDs
|
||||
pub leds: Leds,
|
||||
/// Buttons
|
||||
pub buttons: Buttons,
|
||||
/// Timer
|
||||
pub timer: Timer,
|
||||
/// uarte interface
|
||||
pub uarte: Uarte,
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
pub fn init() -> Result<Board, ()> {
|
||||
|
||||
// ...
|
||||
|
||||
Ok(Board {
|
||||
leds: Leds {
|
||||
// ...
|
||||
},
|
||||
|
||||
buttons: Buttons {
|
||||
// ...
|
||||
},
|
||||
// 🔼 --- Button Exercise --- 🔼
|
||||
|
||||
timer: Timer { inner: timer },
|
||||
|
||||
uarte: Uarte { inner: uarte },
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Step 6: Implementing the `fmt::Write` trait
|
||||
|
||||
We want to implement the `fmt::Write` trait so that users can call `write!` on our Uarte object
|
||||
|
||||
When implementing this, we can't just write to the `Uarte` instance because a simple write of a string literal would try and read the string literal from flash memory. This does not work because the EasyDMA peripheral in the nRF52 series can only access RAM, not flash.
|
||||
|
||||
Instead our implementation must ensure all the strings are copied to a stack allocated buffer and that buffer is passed to the Uarte's `write` method.
|
||||
|
||||
✅ Add `use::core::fmt;` to your imports.
|
||||
|
||||
✅ Create a public method `write_str`. It takes a mutable reference to self and a `&str` as argument. It returns an `fmt::Result`
|
||||
|
||||
✅ Create a buffer. The type is an `array` of 16 u8, set to all 0.
|
||||
|
||||
✅ To copy all data into an on-stack buffer, *iterate* over *chunks* of the string and copy them into the buffer (noting that the chunk length may be less than the requested size if you are at the end of the input string).
|
||||
|
||||
<details>
|
||||
<summary>Solution</summary>
|
||||
|
||||
```rust
|
||||
impl fmt::Write for Uarte {
|
||||
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
// Copy all data into an on-stack buffer so we never try to EasyDMA from
|
||||
// flash.
|
||||
let mut buf: [u8; 16] = [0; 16];
|
||||
for block in s.as_bytes().chunks(16) {
|
||||
buf[..block.len()].copy_from_slice(block);
|
||||
self.inner.write(&buf[..block.len()]).map_err(|_| fmt::Error)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Step 7: Connect your computer to the virtual UART
|
||||
|
||||
✅ Use the following command to find the address of the nRF52840-DK on your computer.
|
||||
|
||||
Linux:
|
||||
```
|
||||
$ ls /dev/ttyACM*
|
||||
```
|
||||
|
||||
MacOS:
|
||||
|
||||
```
|
||||
ls /dev/tty.usbmodem*
|
||||
```
|
||||
|
||||
Windows:
|
||||
a USB Serial Device (COM port) in the Device Manager under the Ports section.
|
||||
|
||||
✅ Run the following command to run `miniterm` with the nRF52840-DK with 115200 baud. This includes the default settings of no parity, 8 data bits, 1 stop bit
|
||||
|
||||
```
|
||||
python3 -m serial.tools.miniterm <address> 115200
|
||||
|
||||
```
|
||||
|
||||
### Step 8: Run the example.
|
||||
|
||||
✅ In another terminal window go into the folder `down-the-stack/apps` and use the following command.
|
||||
|
||||
```
|
||||
cargo run --bin uarte_print
|
||||
```
|
||||
|
||||
On your terminal window where `miniterm` runs, "Hello, World" should appear.
|
||||
|
Loading…
Reference in a new issue