2709 lines
139 KiB
HTML
2709 lines
139 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="sidebar-visible no-js">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>MicroRust</title>
|
||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||
<meta name="description" content="">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="theme-color" content="#ffffff" />
|
||
|
||
<link rel="shortcut icon" href="favicon.png">
|
||
<link rel="stylesheet" href="css/variables.css">
|
||
<link rel="stylesheet" href="css/general.css">
|
||
<link rel="stylesheet" href="css/chrome.css">
|
||
<link rel="stylesheet" href="css/print.css" media="print">
|
||
|
||
<!-- Fonts -->
|
||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
|
||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
|
||
|
||
<!-- Highlight.js Stylesheets -->
|
||
<link rel="stylesheet" href="highlight.css">
|
||
<link rel="stylesheet" href="tomorrow-night.css">
|
||
<link rel="stylesheet" href="ayu-highlight.css">
|
||
|
||
<!-- Custom theme stylesheets -->
|
||
|
||
|
||
|
||
</head>
|
||
<body class="light">
|
||
<!-- Provide site root to javascript -->
|
||
<script type="text/javascript">var path_to_root = "";</script>
|
||
|
||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||
<script type="text/javascript">
|
||
try {
|
||
var theme = localStorage.getItem('mdbook-theme');
|
||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||
|
||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||
}
|
||
|
||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||
}
|
||
} catch (e) { }
|
||
</script>
|
||
|
||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
<script type="text/javascript">
|
||
var theme;
|
||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||
if (theme === null || theme === undefined) { theme = 'light'; }
|
||
document.body.className = theme;
|
||
document.querySelector('html').className = theme + ' js';
|
||
</script>
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script type="text/javascript">
|
||
var html = document.querySelector('html');
|
||
var sidebar = 'hidden';
|
||
if (document.body.clientWidth >= 1080) {
|
||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||
sidebar = sidebar || 'visible';
|
||
}
|
||
html.classList.remove('sidebar-visible');
|
||
html.classList.add("sidebar-" + sidebar);
|
||
</script>
|
||
|
||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||
<ol class="chapter"><li class="affix"><a href="index.html">Introduction</a></li><li class="affix"><a href="background/index.html">Background</a></li><li class="affix"><a href="requirements/index.html">Requirements</a></li><li><a href="hardware/index.html"><strong aria-hidden="true">1.</strong> Meet your hardware</a></li><li><a href="setup/index.html"><strong aria-hidden="true">2.</strong> Development environment setup</a></li><li><ol class="section"><li><a href="setup/LINUX.html"><strong aria-hidden="true">2.1.</strong> Linux</a></li><li><a href="setup/WINDOWS.html"><strong aria-hidden="true">2.2.</strong> Windows</a></li><li><a href="setup/MACOS.html"><strong aria-hidden="true">2.3.</strong> macOS</a></li><li><a href="setup/VERIFY.html"><strong aria-hidden="true">2.4.</strong> Verify the installation</a></li></ol></li><li><a href="getting-started/00.00.README.html"><strong aria-hidden="true">3.</strong> Getting started</a></li><li><ol class="section"><li><a href="getting-started/01.00.BUILD.html"><strong aria-hidden="true">3.1.</strong> Building</a></li><li><a href="getting-started/02.00.FLASH.html"><strong aria-hidden="true">3.2.</strong> Flashing</a></li><li><a href="getting-started/03.00.DEBUG.html"><strong aria-hidden="true">3.3.</strong> Debugging</a></li><li><a href="getting-started/04.00.SOLUTION.html"><strong aria-hidden="true">3.4.</strong> Solution</a></li></ol></li><li><a href="hello-world/00.00.README.html"><strong aria-hidden="true">4.</strong> Hello world</a></li><li><ol class="section"><li><a href="hello-world/01.00.SEMIHOSTING.html"><strong aria-hidden="true">4.1.</strong> Semihosting</a></li><li><a href="hello-world/02.00.UART.html"><strong aria-hidden="true">4.2.</strong> Serial communication</a></li><li><ol class="section"><li><a href="hello-world/02.01.NIX.html"><strong aria-hidden="true">4.2.1.</strong> *nix</a></li><li><a href="hello-world/02.02.WINDOWS.html"><strong aria-hidden="true">4.2.2.</strong> Windows</a></li></ol></li><li><a href="hello-world/03.00.LED.html"><strong aria-hidden="true">4.3.</strong> GPIO and LEDs</a></li><li><ol class="section"><li><a href="hello-world/03.01.SOLUTION.html"><strong aria-hidden="true">4.3.1.</strong> Solution</a></li></ol></li></ol></li><li><a href="choice/00.00.README.html"><strong aria-hidden="true">5.</strong> Choose Your Own Adventure</a></li><li><a href="microbit/00.00.README.html"><strong aria-hidden="true">6.</strong> micro:bit HAL basics</a></li><li><ol class="section"><li><a href="microbit/01.00.BUTTONS.html"><strong aria-hidden="true">6.1.</strong> Buttons</a></li><li><a href="microbit/02.00.DELAY.html"><strong aria-hidden="true">6.2.</strong> Delays</a></li><li><a href="microbit/03.00.DISPLAY.html"><strong aria-hidden="true">6.3.</strong> Display</a></li></ol></li><li><a href="serial/00.00.README.html"><strong aria-hidden="true">7.</strong> Serial UART - Blocking</a></li><li><ol class="section"><li><a href="serial/01.00.ECHO.html"><strong aria-hidden="true">7.1.</strong> Echo Server</a></li><li><ol class="section"><li><a href="serial/01.01.THEORY.html"><strong aria-hidden="true">7.1.1.</strong> Theory</a></li><li><a href="serial/01.02.ECHO.html"><strong aria-hidden="true">7.1.2.</strong> Solution</a></li></ol></li><li><a href="serial/02.00.html"><strong aria-hidden="true">7.2.</strong> Exercises</a></li><li><ol class="section"><li><a href="serial/02.01.html"><strong aria-hidden="true">7.2.1.</strong> Reverse echo</a></li><li><ol class="section"><li><a href="serial/02.01.SOLUTION.html"><strong aria-hidden="true">7.2.1.1.</strong> Solution</a></li></ol></li><li><a href="serial/02.02.html"><strong aria-hidden="true">7.2.2.</strong> Countdown</a></li><li><ol class="section"><li><a href="serial/02.02.SOLUTION.html"><strong aria-hidden="true">7.2.2.1.</strong> Solution</a></li></ol></li><li><a href="serial/02.04.html"><strong aria-hidden="true">7.2.3.</strong> Quiz</a></li><li><ol class="section"><li><a href="serial/02.04.SOLUTION.html"><strong aria-hidden="true">7.2.3.1.</strong> Solution</a></li></ol></li></ol></li></ol></li><li><a href="display/00.00.README.html"><strong aria-hidden="true">8.</strong> LED display</a></li><li><ol class="section"><li><a href="display/01.00.THEORY.html"><strong aria-hidden="true">8.1.</strong> Theory</a></li><li><a href="display/02.00.PROBLEM.html"><strong aria-hidden="true">8.2.</strong> Problem</a></li><li><ol class="section"><li><a href="display/02.01.LAYOUT.html"><strong aria-hidden="true">8.2.1.</strong> Layout</a></li><li><a href="display/02.02.DELAY.html"><strong aria-hidden="true">8.2.2.</strong> Delays</a></li><li><a href="display/02.03.MULT.html"><strong aria-hidden="true">8.2.3.</strong> Multiplexing</a></li></ol></li><li><a href="display/03.00.SOLUTION.html"><strong aria-hidden="true">8.3.</strong> Solution</a></li><li><ol class="section"><li><a href="display/03.01.LAYOUT.html"><strong aria-hidden="true">8.3.1.</strong> Layout</a></li><li><a href="display/03.02.MULT.html"><strong aria-hidden="true">8.3.2.</strong> Multiplexing</a></li><li><a href="display/03.03.FULL.html"><strong aria-hidden="true">8.3.3.</strong> Full Solution</a></li></ol></li></ol></li><li><a href="sensors/00.00.README.html"><strong aria-hidden="true">9.</strong> WIP - Sensors and I²C</a></li><li><a href="nb/00.00.README.html"><strong aria-hidden="true">10.</strong> WIP - Non-blocking</a></li><li><a href="nb/00.00.README.html"><strong aria-hidden="true">11.</strong> WIP - Interrupts</a></li><li><a href="rtfm/00.00.README.html"><strong aria-hidden="true">12.</strong> WIP - Real time</a></li><li><a href="hal/00.00.README.html"><strong aria-hidden="true">13.</strong> WIP - Creating a HAL</a></li><li class="affix"><a href="appendix/explore.html">Explore</a></li><li class="affix"><a href="appendix/gdb.html">GDB cheatsheet</a></li><li class="affix"><a href="appendix/troubleshooting.html">General troubleshooting</a></li></ol>
|
||
</nav>
|
||
|
||
<div id="page-wrapper" class="page-wrapper">
|
||
|
||
<div class="page">
|
||
|
||
<div id="menu-bar" class="menu-bar">
|
||
<div id="menu-bar-sticky-container">
|
||
<div class="left-buttons">
|
||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||
<i class="fa fa-bars"></i>
|
||
</button>
|
||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||
<i class="fa fa-paint-brush"></i>
|
||
</button>
|
||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||
<li role="none"><button role="menuitem" class="theme" id="light">Light <span class="default">(default)</span></button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||
</ul>
|
||
|
||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||
<i class="fa fa-search"></i>
|
||
</button>
|
||
|
||
</div>
|
||
|
||
<h1 class="menu-title">MicroRust</h1>
|
||
|
||
<div class="right-buttons">
|
||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||
<i id="print-button" class="fa fa-print"></i>
|
||
</a>
|
||
<a href="https://github.com/droogmic/microrust" class="icon-button" title="Go to GitHub repo" aria-label="Link to GitHub repo">
|
||
<i id="github-button" class="fa fa-github"></i>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<div id="search-wrapper" class="hidden">
|
||
<form id="searchbar-outer" class="searchbar-outer">
|
||
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||
</form>
|
||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||
<div id="searchresults-header" class="searchresults-header"></div>
|
||
<ul id="searchresults">
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||
<script type="text/javascript">
|
||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||
});
|
||
</script>
|
||
|
||
<div id="content" class="content">
|
||
<main>
|
||
<a class="header" href="#microrust" id="microrust"><h1>MicroRust</h1></a>
|
||
<blockquote>
|
||
<p>Discover the world of microcontrollers through <a href="https://www.rust-lang.org/">Rust</a> on the <a href="https://microbit.org/">BBC micro:bit</a>!</p>
|
||
</blockquote>
|
||
<p>This book is an introductory course on microcontroller-based embedded systems
|
||
that uses Rust as the teaching language (rather than the usual C/C++),
|
||
and the micro:bit as the target system.</p>
|
||
<a class="header" href="#approach" id="approach"><h2>Approach</h2></a>
|
||
<ul>
|
||
<li>
|
||
<p>Beginner friendly.
|
||
No previous experience with microcontrollers or embedded systems is required.</p>
|
||
</li>
|
||
<li>
|
||
<p>Hands on.
|
||
<em>You</em> will be doing most of the work here.
|
||
When possible, pages will end on a problem for you to solve, with the solution on the next page.
|
||
There are plenty of exercises to put the theory into practice.</p>
|
||
</li>
|
||
<li>
|
||
<p>Standard.
|
||
We'll make plenty use of standard tooling and processes to ease development
|
||
so you can apply the skills learnt to any Rust embedded project.
|
||
Fixing compiler errors, debugging with GDB, and logging will be introduced early on.
|
||
Using LEDs as a debugging mechanism has no place here.</p>
|
||
</li>
|
||
</ul>
|
||
<a class="header" href="#scope" id="scope"><h2>Scope</h2></a>
|
||
<p>The following topics are covered in the core chapters:</p>
|
||
<ul>
|
||
<li>How to write, build, flash and debug an embedded program.</li>
|
||
<li>Basic operation of a GPIO, ubiquitous in microcontrollers.</li>
|
||
</ul>
|
||
<p>The rest of the chapters are independent, only requiring the core knowledge:</p>
|
||
<ul>
|
||
<li>Functionality ("peripherals") commonly found in microcontrollers:
|
||
<ul>
|
||
<li>Digital input and output, including buttons and LEDs</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<!-- - Functionality ("peripherals") commonly found in microcontrollers: Digital input and output, Pulse
|
||
Width Modulation (PWM), Analog to Digital Converters (ADC), common communication protocols like
|
||
Serial, I2C and SPI, etc. -->
|
||
<!-- - Multitasking concepts: cooperative vs preemptive multitasking, interrupts, schedulers, etc. -->
|
||
<!-- - Control systems concepts: sensors, calibration, digital filters, actuators, open loop control,
|
||
closed loop control, etc. -->
|
||
<a class="header" href="#non-goals" id="non-goals"><h2>Non-goals</h2></a>
|
||
<p>What's out of scope for this book:</p>
|
||
<ul>
|
||
<li>
|
||
<p>Teaching Rust.
|
||
There's plenty of material on that topic already.
|
||
We'll focus on microcontrollers and embedded systems.</p>
|
||
</li>
|
||
<li>
|
||
<p>Teaching electric circuit theory or electronics.
|
||
We'll cover the minimum required to understand how some devices work along the way.</p>
|
||
</li>
|
||
<li>
|
||
<p>Covering Rustic, low level details.
|
||
We won't be talking about linker scripts, the boot process,
|
||
or how to glue those two into a minimally working Rust program.</p>
|
||
</li>
|
||
</ul>
|
||
<a class="header" href="#reporting-problems" id="reporting-problems"><h2>Reporting problems</h2></a>
|
||
<p>The source of this book is in <a href="https://github.com/droogmic/microrust">this repository</a>.
|
||
If you encounter any typo or problem please report it on the <a href="https://github.com/droogmic/microrust/issues">issue tracker</a>,
|
||
or even submit a <a href="https://github.com/droogmic/microrust/pulls">pull request</a>.</p>
|
||
<a class="header" href="#background" id="background"><h1>Background</h1></a>
|
||
<a class="header" href="#what-is-a-microcontroller" id="what-is-a-microcontroller"><h2>What is a microcontroller?</h2></a>
|
||
<p>A microcontroller is a <em>system</em> on a chip. Whereas your laptop is made up of several discrete
|
||
components: a processor, RAM sticks, a hard drive, an ethernet port, etc.; a microcontroller has all
|
||
those components built into a single "chip" or package. This makes it possible to build systems with
|
||
minimal part count.</p>
|
||
<a class="header" href="#what-can-you-do-with-a-microcontroller" id="what-can-you-do-with-a-microcontroller"><h2>What can you do with a microcontroller?</h2></a>
|
||
<p>Lots of things! Microcontrollers are the central part of systems known as <em>embedded</em> systems. These
|
||
systems are everywhere but you don't usually notice them. These systems control the brakes of your
|
||
car, wash your clothes, print your documents, keep you warm, keep you cool, optimize the fuel
|
||
consumption of your car, etc.</p>
|
||
<p>The main trait of these systems is that they operate without user intervention even if they expose a
|
||
user interface like a washing machine does; most of their operation is done on their own.</p>
|
||
<p>The other common trait of these systems is that they <em>control</em> a process. And for that these systems
|
||
usually have one or more sensors and one or more actuators. For example, an HVAC system has several
|
||
sensors, thermometers and humidy sensors spread across some area, and several actuators as well,
|
||
heating elements and fans connected to ducts.</p>
|
||
<a class="header" href="#when-should-i-use-a-microcontroller" id="when-should-i-use-a-microcontroller"><h2>When should I use a microcontroller?</h2></a>
|
||
<p>All these application I've mentioned, you can probably implement with a Raspberry Pi, a computer
|
||
that runs Linux. Why should I bother with a microcontroller that operates without an OS? Sounds like
|
||
it would be harder to develop a program.</p>
|
||
<p>One main reason is cost. A microcontroller is much cheaper than a general purpose computer. Not only
|
||
the microcontroller is cheaper; it also requires much less external electrical components to
|
||
operate. This makes Printed Circuit Boards (PCB) smaller and cheaper to design and manufacture.</p>
|
||
<p>Another big reason is power consumption. A microcontroller consumes orders of magnitude less power
|
||
than a full blown processor. If your application will run on batteries that makes a huge difference.</p>
|
||
<p>And last but not least: (hard) <em>real time</em> constraints. Some processes require their controllers to
|
||
respond to some events within some time interval (e.g. a quadcopter/drone hit by a wind gust). If
|
||
this <em>deadline</em> is not met, the process could end in catastrophic failure (e.g. the drone crashes to
|
||
the ground). A general purpose computer running a general purpose OS has many services running in
|
||
the background. This makes it hard to guarantee execution of a program within tight time constraints.</p>
|
||
<a class="header" href="#when-should-i-not-use-a-microcontroller" id="when-should-i-not-use-a-microcontroller"><h2>When should I <em>not</em> use a microcontroller?</h2></a>
|
||
<p>Where heavy computations are involved. To keep their power consumption low, microcontrollers have
|
||
very limited computational resources available to them. For example, some microcontrollers don't
|
||
even have hardware support for floating point operations. On those devices, performing a simple
|
||
addition of single precision numbers can take hundreds of CPU cycles.</p>
|
||
<a class="header" href="#development-on-the-microbit" id="development-on-the-microbit"><h2>Development on the micro:bit</h2></a>
|
||
<p>The <a href="https://microbit.org/code/">micro:bit website</a> offers several very simple ways of programming a
|
||
microbit, aimed at teaching school children how to program. This is a very good introduction to the
|
||
world of microcontollers <strong>and</strong> programming, but falls short of teaching true embedded development.
|
||
From there you would usually move to C to develop skills useful in industry, developing performant
|
||
embedded software.</p>
|
||
<a class="header" href="#why-use-rust-and-not-c" id="why-use-rust-and-not-c"><h2>Why use Rust and not C?</h2></a>
|
||
<p>Hopefully I don't need to convince you here as you are probably familiar with the language
|
||
differences between Rust and C. One point I do want to bring up is package management. C lacks an
|
||
official, widely accepted package management solution whereas Rust has Cargo. This makes development
|
||
<em>much</em> easier. And, IMO, easy package management encourages code reuse because libraries can be
|
||
easily integrated into an application which is also a good thing as libraries get more "battle
|
||
testing".</p>
|
||
<a class="header" href="#why-should-i-not-use-rust" id="why-should-i-not-use-rust"><h2>Why should I not use Rust?</h2></a>
|
||
<p>Or why should I prefer C over Rust?</p>
|
||
<p>The C ecosystem is way more mature. Off the shelf solution for several problems already exist. If
|
||
you need to control a time sensitive process, you can grab one of the existing commercial Real Time
|
||
Operating Systems (RTOS) out there and solve your problem. There are no commercial, production-grade
|
||
RTOSes in Rust yet so you would have to either create one yourself or try one of the ones that are
|
||
in development. If you are looking to develop your skills to find a job, it is currently unlikely
|
||
that a company doing embedded software development will be using Rust, and so your time would be
|
||
better spent learning normal embedded development using C.</p>
|
||
<a class="header" href="#requirements" id="requirements"><h1>Requirements</h1></a>
|
||
<a class="header" href="#knowledge" id="knowledge"><h2>Knowledge</h2></a>
|
||
<p>The only knowledge requirement to read this book is to know <em>some</em> Rust. It's hard to
|
||
quantify <em>some</em> but a good benchmark is having read and understood the first 14 chapters of <a href="https://doc.rust-lang.org/nightly/book/">the Rust book</a>.</p>
|
||
<a class="header" href="#hardware" id="hardware"><h2>Hardware</h2></a>
|
||
<p>To follow this material you'll only need a <a href="http://tech.microbit.org/hardware/">micro:bit</a>.</p>
|
||
<p>You can purchase the BBC micro:bit from <a href="https://microbit.org/resellers/">a large list of international resellers</a>.</p>
|
||
<p align="center">
|
||
<img title="microbit" src="https://microbit.org/images/microbit-front.png" width="45%">
|
||
<img title="microbit" src="https://microbit.org/images/microbit-back.png" width="45%">
|
||
</p>
|
||
<blockquote>
|
||
<p><strong>FAQ</strong>: Wait, why do I need this specific device?</p>
|
||
</blockquote>
|
||
<p>It makes my life and yours much easier.</p>
|
||
<p>The material is much, much more approachable if we don't have to worry about hardware differences.
|
||
Trust me on this one.</p>
|
||
<blockquote>
|
||
<p><strong>FAQ</strong>: Can I follow this material with a different development board?</p>
|
||
</blockquote>
|
||
<p>Maybe? It depends mainly on two things: your previous experience with microcontrollers and/or
|
||
whether there already exists a high level crate. A list of boards with high level crates available can be found <a href="https://github.com/rust-embedded/awesome-embedded-rust#board-support-crates">here</a>.</p>
|
||
<p>With other development boards, this text would lose most if not all its beginner friendliness
|
||
and "easy to follow"-ness, IMO.</p>
|
||
<p>There are other similar guides for different hardware. For a full list see <a href="https://github.com/rust-embedded/awesome-embedded-rust/#books-blogs-and-training-materials">this list</a>.</p>
|
||
<p>The following are worth a special mention:</p>
|
||
<ul>
|
||
<li><a href="https://japaric.github.io/discovery/">Discovery</a> by @japaric: The genesis guide which this is based on. Uses the STM32F3DISCOVERY.</li>
|
||
</ul>
|
||
<p>If you have a different cortex-m development board and you don't consider yourself a total beginner, you are
|
||
better off starting with the <a href="https://docs.rs/crate/cortex-m-quickstart">cortex-m-quickstart</a> project template.</p>
|
||
<a class="header" href="#meet-your-hardware" id="meet-your-hardware"><h1>Meet your hardware</h1></a>
|
||
<p>Let's get familiar with the hardware we'll be working with.</p>
|
||
<a class="header" href="#bbc-microbit-the-microbit" id="bbc-microbit-the-microbit"><h2>BBC micro:bit (the "microbit")</h2></a>
|
||
<p align="center">
|
||
<img title="microbit" src="http://tech.microbit.org/docs/hardware/assets/microbit-overview.png">
|
||
</p>
|
||
<p>What does this board contain? For full details see the <a href="http://tech.microbit.org/hardware">microbit hardware page</a>.</p>
|
||
<ul>
|
||
<li>
|
||
<p>A Nordic nRF51822 microcontroller. This microcontroller has</p>
|
||
<ul>
|
||
<li>
|
||
<p>A single core ARM Cortex-M0 processor with a maximum clock frequency of 16 MHz.</p>
|
||
</li>
|
||
<li>
|
||
<p>256 KB of flash memory. (1 KB = 10<strong>24</strong> bytes)</p>
|
||
</li>
|
||
<li>
|
||
<p>16 KB of static RAM.</p>
|
||
</li>
|
||
<li>
|
||
<p>many "peripherals": timers, GPIO, I2C, SPI, UART, etc.</p>
|
||
</li>
|
||
<li>
|
||
<p>This microcontroller operates at (around) 3.3V.</p>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<p>2 user buttons on the front and 1 reset button on the back.</p>
|
||
</li>
|
||
<li>
|
||
<p>A 5x5 array of user LEDs.</p>
|
||
</li>
|
||
<li>
|
||
<p>A configureable 23-pin edge connector</p>
|
||
</li>
|
||
<li>
|
||
<p>A 2.4GHz radio transciever with support for <a href="https://en.wikipedia.org/wiki/Bluetooth_Low_Energy">bluetooth low energy</a> (BLE).</p>
|
||
</li>
|
||
</ul>
|
||
<ul>
|
||
<li>
|
||
<p>An on-core nRF51 temperature sensor.</p>
|
||
</li>
|
||
<li>
|
||
<p>An NXP/Freescale MMA8652 3-axis <a href="https://en.wikipedia.org/wiki/Accelerometer">accelerometer</a>.</p>
|
||
</li>
|
||
</ul>
|
||
<ul>
|
||
<li>An NXP/Freescale MAG3110 3-axis <a href="https://en.wikipedia.org/wiki/Magnetometer">magnetometer</a>.</li>
|
||
</ul>
|
||
<ul>
|
||
<li>A second microcontroller: NXP/Freescale KL26Z. This microcontroller handles the USB interface,
|
||
communication between your computer and the main microcontroller,
|
||
and converting the USB's input voltage from 5V to 3.3V.</li>
|
||
</ul>
|
||
<a class="header" href="#micro-usb-cable" id="micro-usb-cable"><h2>Micro-USB Cable</h2></a>
|
||
<p>This comes with your microbit but can be any generic cable,
|
||
and is used to connect the microbit to your computer.</p>
|
||
<a class="header" href="#external-battery-pack" id="external-battery-pack"><h2>External battery pack</h2></a>
|
||
<p>The external battery pack that comes with the microbit will not be used explicitly as part of this guide,
|
||
but feel free to use it to test your software without being tethered to a computer.</p>
|
||
<a class="header" href="#plugging-it-in" id="plugging-it-in"><h2>Plugging it in</h2></a>
|
||
<p>You can use the micro-USB cable to power the micro:bit, and to transfer data.
|
||
When you power up a new micro:bit you will see the display light up as the factory-installed program is executed.
|
||
Otherwise, the last program will automatically be executed.
|
||
The black reset button next to the USB input will restart the program being run.</p>
|
||
<a class="header" href="#development-environment-setup" id="development-environment-setup"><h1>Development environment setup</h1></a>
|
||
<p>Dealing with microcontrollers involves several tools,
|
||
as we'll be dealing with an architecture different than your laptop's,
|
||
and we'll have to run and debug programs on a "remote" device.</p>
|
||
<a class="header" href="#documentation" id="documentation"><h2>Documentation</h2></a>
|
||
<p>Without documentation it is pretty much impossible to work with microcontrollers.</p>
|
||
<p>We'll be referring to the <a href="http://tech.microbit.org/hardware">micro:bit hardware page</a> and the links found within.</p>
|
||
<p><em>HEADS UP</em> Some of the links point to large PDF files several MBs in size.</p>
|
||
<a class="header" href="#tools" id="tools"><h2>Tools</h2></a>
|
||
<p>We'll use all the tools listed below. Where a minimum version is not specified,
|
||
any recent version should work but we have listed the version we have tested.</p>
|
||
<ul>
|
||
<li>
|
||
<p>Cargo & <code>rustc</code>.</p>
|
||
</li>
|
||
<li>
|
||
<p>OpenOCD. version >=0.8</p>
|
||
</li>
|
||
<li>
|
||
<p><code>arm-none-eabi</code> toolchain. Tested version: gcc 8.1.0, binutils 2.30.</p>
|
||
</li>
|
||
<li>
|
||
<p><code>arm-none-eabi-gdb</code>.</p>
|
||
</li>
|
||
<li>
|
||
<p><code>minicom</code> on Linux and macOS. Tested version: 2.7.
|
||
Readers report that <code>picocom</code> also works but we'll use <code>minicom</code> in this book.</p>
|
||
</li>
|
||
<li>
|
||
<p><code>PuTTY</code> on Windows.</p>
|
||
</li>
|
||
</ul>
|
||
<p>Next, follow OS-agnostic installation instructions for a few of the tools:</p>
|
||
<a class="header" href="#rustc--cargo" id="rustc--cargo"><h3><code>rustc</code> & Cargo</h3></a>
|
||
<p>Install rustup by following the instructions at <a href="https://rustup.rs">https://rustup.rs</a>.</p>
|
||
<p>Then, install or switch to the nightly channel.</p>
|
||
<pre><code class="language-shell">$ rustup default nightly
|
||
</code></pre>
|
||
<p><strong>NOTE</strong> Make sure you have a nightly newer than <code>nightly-2018-10-12</code>.
|
||
<code>rustc -V</code> should return a date newer than the one shown below:</p>
|
||
<pre><code class="language-shell">$ rustc -V
|
||
rustc 1.31.0-nightly (2c2e2c57d 2018-10-12)
|
||
</code></pre>
|
||
<a class="header" href="#os-specific-instructions" id="os-specific-instructions"><h3>OS specific instructions</h3></a>
|
||
<p>Now follow the instructions specific to the OS you are using:</p>
|
||
<ul>
|
||
<li><a href="../setup/LINUX.html">Linux</a></li>
|
||
<li><a href="../setup/WINDOWS.html">Windows</a></li>
|
||
<li><a href="../setup/MACOS.html">macOS</a></li>
|
||
</ul>
|
||
<a class="header" href="#linux" id="linux"><h1>Linux</h1></a>
|
||
<p>Here are the installation commands for a few Linux distributions.</p>
|
||
<a class="header" href="#required-packages" id="required-packages"><h2>REQUIRED packages</h2></a>
|
||
<ul>
|
||
<li>Ubuntu 16.04 or newer / Debian Jessie or newer</li>
|
||
</ul>
|
||
<pre><code class="language-shell">$ sudo apt-get install \
|
||
gcc-arm-none-eabi \
|
||
gdb-arm-none-eabi \
|
||
minicom \
|
||
openocd
|
||
</code></pre>
|
||
<ul>
|
||
<li>Fedora 23 or newer</li>
|
||
</ul>
|
||
<pre><code class="language-shell">$ sudo dnf install \
|
||
arm-none-eabi-gcc-cs \
|
||
arm-none-eabi-gdb \
|
||
minicom \
|
||
openocd
|
||
</code></pre>
|
||
<ul>
|
||
<li>Arch Linux</li>
|
||
</ul>
|
||
<pre><code class="language-shell">$ sudo pacman -S \
|
||
arm-none-eabi-gcc \
|
||
arm-none-eabi-gdb \
|
||
minicom \
|
||
openocd
|
||
</code></pre>
|
||
<ul>
|
||
<li>Other distros</li>
|
||
</ul>
|
||
<p>For distros that don't have packages for <a href="https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads">ARM's pre-built toolchain</a>, download the "Linux 64-bit" file and put its <code>bin</code> directory on your path. Here's one way to do it:</p>
|
||
<pre><code class="language-shell">$ mkdir -p ~/local && cd ~/local
|
||
$ tar xjf /path/to/downloaded/file/gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2.tbz
|
||
</code></pre>
|
||
<p>Then, use your editor of choice to append to your <code>PATH</code> in the appropriate shell init file (e.g. <code>~/.zshrc</code> or <code>~/.bashrc</code>):</p>
|
||
<pre><code>PATH=$PATH:$HOME/local/gcc-arm-none-eabi-7-2017-q4-major/bin
|
||
</code></pre>
|
||
<a class="header" href="#udev-rules" id="udev-rules"><h2>udev rules</h2></a>
|
||
<p>These rules let you use USB devices like the F3 and the Serial module without root privilege, i.e.
|
||
<code>sudo</code>.</p>
|
||
<p>Create this file in <code>/etc/udev/rules.d</code> with the contents shown below.</p>
|
||
<pre><code class="language-shell">$ cat /etc/udev/rules.d/99-openocd.rules
|
||
</code></pre>
|
||
<pre><code class="language-text"># microbit - CMSIS-DAP
|
||
ATTRS{idVendor}=="0d28", ATTRS{idProduct}=="0204", GROUP="uucp"
|
||
</code></pre>
|
||
<p>Then reload the udev rules with:</p>
|
||
<pre><code class="language-shell">$ sudo udevadm control --reload-rules
|
||
</code></pre>
|
||
<p>If you had any board plugged to your laptop, unplug them and then plug them in again.</p>
|
||
<p>Finally, check if you are in the <code>uucp</code> group.</p>
|
||
<pre><code class="language-shell">$ groups $(id -nu)
|
||
(..) uucp (..)
|
||
$ # ^^^^
|
||
</code></pre>
|
||
<p>(<code>$(id -nu)</code> returns your user name.)</p>
|
||
<p>If <code>uucp</code> appears in the output. You are all set! Go to the <a href="../setup/VERIFY.html">next section</a>. Otherwise, keep reading:</p>
|
||
<ul>
|
||
<li>Add yourself to the <code>uucp</code> group.</li>
|
||
</ul>
|
||
<pre><code class="language-shell">$ sudo usermod -a -G uucp $(id -u -n)
|
||
</code></pre>
|
||
<ul>
|
||
<li>Check again the output of <code>groups</code>. <code>uucp</code> should be there this time!</li>
|
||
</ul>
|
||
<pre><code class="language-shell">$ groups $(id -nu)
|
||
(..) uucp (..)
|
||
$ # ^^^^
|
||
</code></pre>
|
||
<p>You'll have to re-log for these changes to take effect. You have two options:</p>
|
||
<p>You can reboot or log out from your current session and then log in; this will close all the
|
||
programs you have open right now.</p>
|
||
<p>The other option is to use the command below:</p>
|
||
<pre><code class="language-shell">$ su - $(id -nu)
|
||
</code></pre>
|
||
<p>to re-log <em>only in the current shell</em> and get access to <code>uucp</code> devices <em>only on that shell</em>. Other
|
||
shells <em>won't</em> have access to <code>uucp</code> devices unless you manually re-log on them with the same <code>su</code>
|
||
command.</p>
|
||
<p>Now, go to the <a href="../setup/VERIFY.html">next section</a>.</p>
|
||
<a class="header" href="#windows" id="windows"><h1>Windows</h1></a>
|
||
<a class="header" href="#arm-none-eabi-" id="arm-none-eabi-"><h2><code>arm-none-eabi-*</code></h2></a>
|
||
<p>ARM provides <code>.exe</code> installers for Windows. Grab one from <a href="https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads">here</a>, and follow the instructions.
|
||
Just before the installation process finishes tick/select the "Add path to environment variable"
|
||
option. Then verify that the tools are in your <code>%PATH%</code>:</p>
|
||
<pre><code class="language-shell">$ arm-none-eabi-gcc -v
|
||
(..)
|
||
gcc version 5.4.1 20160919 (release) (..)
|
||
</code></pre>
|
||
<a class="header" href="#openocd" id="openocd"><h2>OpenOCD</h2></a>
|
||
<p>There's no official binary release of OpenOCD for Windows but there are unofficial releases
|
||
available <a href="https://github.com/gnu-mcu-eclipse/openocd/releases">here</a>. Grab the 0.10.x zipfile and extract it somewhere in your drive (I
|
||
recommend <code>C:\OpenOCD</code> but with the drive letter that makes sense to you) then update your <code>%PATH%</code>
|
||
environment variable to include the following path: <code>C:\OpenOCD\bin</code> (or the path that you used
|
||
before).</p>
|
||
<p>Verify that OpenOCD is in yout <code>%PATH%</code> with:</p>
|
||
<pre><code class="language-shell">$ openocd -v
|
||
Open On-Chip Debugger 0.10.0
|
||
(..)
|
||
</code></pre>
|
||
<a class="header" href="#putty" id="putty"><h2>PuTTY</h2></a>
|
||
<p>Download the latest <code>putty.exe</code> from <a href="http://www.chiark.greenend.org.uk/%7Esgtatham/putty/download.html">this site</a> and place it somewhere in your <code>%PATH%</code>.</p>
|
||
<p>That's all! Go to the <a href="../setup/VERIFY.html">next section</a>.</p>
|
||
<a class="header" href="#macos" id="macos"><h1>macOS</h1></a>
|
||
<blockquote>
|
||
<p>UNTESTED: please submit an issue if you can confirm this works.</p>
|
||
</blockquote>
|
||
<p>All the tools can be install using <a href="http://brew.sh/">Homebrew</a>:</p>
|
||
<pre><code class="language-shell">$ brew cask install gcc-arm-embedded
|
||
$ brew install minicom openocd
|
||
</code></pre>
|
||
<p>If the <code>brew cask</code> command doesn't work (<code>Error: Unknown command: cask</code>), then run <code>brew tap Caskroom/tap</code> first and try again.</p>
|
||
<p>That's all! Go to the <a href="../setup/VERIFY.html">next section</a>.</p>
|
||
<a class="header" href="#verify-the-installation" id="verify-the-installation"><h1>Verify the installation</h1></a>
|
||
<p>Let's verify that all the tools were installed correctly.</p>
|
||
<a class="header" href="#linux-only" id="linux-only"><h2>Linux only</h2></a>
|
||
<a class="header" href="#verify-permissions" id="verify-permissions"><h3>Verify permissions</h3></a>
|
||
<p>Connect the micro:bit to your laptop using an USB cable.</p>
|
||
<p>The micro:bit should now appear as a USB device (file) in <code>/dev/bus/usb</code>.
|
||
Let's find out how it got enumerated:</p>
|
||
<pre><code class="language-shell">$ lsusb | grep -i NXP
|
||
Bus 002 Device 033: ID 0d28:0204 NXP ARM mbed
|
||
^^^ ^^^
|
||
</code></pre>
|
||
<p>In my case, the micro:bit got connected to the bus #2 and got enumerated as the device #33.
|
||
This means the file <code>/dev/bus/usb/002/033</code> <em>is</em> the Fmicro:bit3.
|
||
Let's check its permissions:</p>
|
||
<pre><code class="language-shell">$ ls -l /dev/bus/usb/002/033
|
||
crw-rw---- 1 root uucp 189, 160 Jul 8 14:06 /dev/bus/usb/002/033
|
||
^^^^
|
||
</code></pre>
|
||
<p>The group should be <code>uucp</code>.
|
||
If it's not ... then check your <a href="../setup/LINUX.html#udev%20rules">udev rules</a> and try re-loading them with:</p>
|
||
<pre><code class="language-shell">$ sudo udevadm control --reload-rules
|
||
</code></pre>
|
||
<a class="header" href="#all" id="all"><h2>All</h2></a>
|
||
<a class="header" href="#first-openocd-connection" id="first-openocd-connection"><h3>First OpenOCD connection</h3></a>
|
||
<p>First, connect the micro:bit to your computer using the micro-USB cable.
|
||
The <em>yellow</em> LED next to the USB input should turn on right after connecting the USB cable to the board.</p>
|
||
<p>Next, run this command:</p>
|
||
<pre><code class="language-shell">$ # *nix
|
||
$ openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg
|
||
|
||
$ # Windows
|
||
$ # NOTE cygwin users have reported problems with the -s flag. If you run into
|
||
$ # that you can call openocd from the `C:\OpenOCD\share\scripts` directory
|
||
$ openocd -s C:\OpenOCD\share\scripts -f interface/cmsis-dap.cfg -f target/nrf51.cfg
|
||
</code></pre>
|
||
<blockquote>
|
||
<p><strong>NOTE</strong> Windows users: <code>C:\OpenOCD</code> is the directory where you installed OpenOCD to.</p>
|
||
</blockquote>
|
||
<p>You should see output like this:</p>
|
||
<pre><code class="language-shell">Open On-Chip Debugger 0.10.0
|
||
Licensed under GNU GPL v2
|
||
For bug reports, read
|
||
http://openocd.org/doc/doxygen/bugs.html
|
||
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
|
||
cortex_m reset_config sysresetreq
|
||
adapter speed: 1000 kHz
|
||
Info : CMSIS-DAP: SWD Supported
|
||
Info : CMSIS-DAP: Interface Initialised (SWD)
|
||
Info : CMSIS-DAP: FW Version = 1.0
|
||
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
|
||
Info : CMSIS-DAP: Interface ready
|
||
Info : clock speed 1000 kHz
|
||
Info : SWD DPIDR 0x0bb11477
|
||
Info : nrf51.cpu: hardware has 4 breakpoints, 2 watchpoints
|
||
</code></pre>
|
||
<p>(If you don't ... then check the <a href="../appendix/troubleshooting.html">general troubleshooting</a> instructions.)</p>
|
||
<p><code>openocd</code> will block the terminal. That's fine.</p>
|
||
<p>Also, the <code>yellow</code> LED should start blinking very fast.
|
||
It may seem concerning, but it is a good sign.</p>
|
||
<p>That's it! It works. You can now close/kill <code>openocd</code>.</p>
|
||
<a class="header" href="#getting-started" id="getting-started"><h1>Getting started</h1></a>
|
||
<p>Alright, let's start as you usually would with Rust.</p>
|
||
<pre><code class="language-console">$ rustup update
|
||
</code></pre>
|
||
<p>It is always good to keep your toolchain up to date.</p>
|
||
<p>Now let's make a new binary project.
|
||
You might not do this often, so it is understandeable to forget.
|
||
If you run <code>$ cargo</code>, you will be given a hint.</p>
|
||
<a class="header" href="#new-project" id="new-project"><h1>New Project</h1></a>
|
||
<pre><code class="language-shell">$ cargo new microrust-start
|
||
Created binary (application) `microrust-start` project
|
||
$ cd microrust-start
|
||
Cargo.toml src
|
||
</code></pre>
|
||
<p>This has created a binary crate.</p>
|
||
<p>Now we could <code>$ cargo build</code> this, and even <code>$ cargo run</code> it,
|
||
but everything is being compiled for, and run on, your computer.</p>
|
||
<a class="header" href="#targets" id="targets"><h2>Targets</h2></a>
|
||
<p>The micro:bit has a different architecture than your computer,
|
||
so the first step will be to cross compile for the micro:bit's architecture.
|
||
If you were to do an internet search, you would find a <a href="https://forge.rust-lang.org/platform-support.html">platform support list for Rust</a>.
|
||
Looking into this page, you will find the micro:bit's nRF51822 Cortex-M0 microprocessor:</p>
|
||
<blockquote>
|
||
<p><code>thumbv6m-none-eabi [*] [ ] [ ] Bare Cortex-M0, M0+, M1</code></p>
|
||
</blockquote>
|
||
<p>"thumbv6m-none-eabi" is known a a target triple. Note what the star represents:</p>
|
||
<blockquote>
|
||
<p>These are bare-metal microcontroller targets that only have access to the core library, not std.</p>
|
||
</blockquote>
|
||
<p>To install this target:</p>
|
||
<pre><code class="language-console">$ rustup target add thumbv6m-none-eabi
|
||
</code></pre>
|
||
<a class="header" href="#build-1" id="build-1"><h2>Build 1</h2></a>
|
||
<p>Now how should we use this? Well, if you were to take a look at <code>$ cargo build -h</code>, you would try:</p>
|
||
<pre><code class="language-shell">$ cargo build --target thumbv6m-none-eabi
|
||
</code></pre>
|
||
<pre><code class="language-shell">error[E0463]: can't find crate for `std`
|
||
|
|
||
= note: the `thumbv6m-none-eabi` target may not be installed
|
||
|
||
error: aborting due to previous error
|
||
|
||
For more information about this error, try `rustc --explain E0463`.
|
||
error: Could not compile `microrust-start`.
|
||
|
||
To learn more, run the command again with --verbose.
|
||
</code></pre>
|
||
<p>The help note is rather unhelpful because we just installed that target.
|
||
We also just noted that the thumbv6m-none-eabi target does not include std,
|
||
only the core crate, which is has a platform independent subset of the std features.
|
||
Why is it still looking for the std crate when we build?</p>
|
||
<a class="header" href="#no_std" id="no_std"><h3><code>no_std</code></h3></a>
|
||
<p>It turns out, rust will always look for the std crate unless explicitly disabled,
|
||
so we will add the no_std attribute</p>
|
||
<p><code>src/main.rs</code></p>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
|
||
fn main() {
|
||
println!("Hello, world!");
|
||
}
|
||
</code></pre></pre>
|
||
<a class="header" href="#build-2" id="build-2"><h2>Build 2</h2></a>
|
||
<pre><code class="language-shell">$ cargo build --target thumbv6m-none-eabi
|
||
error: cannot find macro `println!` in this scope
|
||
--> src/main.rs:4:5
|
||
|
|
||
4 | println!("Hello, world!");
|
||
| ^^^^^^^
|
||
</code></pre>
|
||
<p><code>println</code> is a macro found in the std crate.
|
||
We don't need it at the moment, so we can remove it and try to build again.</p>
|
||
<a class="header" href="#build-3" id="build-3"><h2>Build 3</h2></a>
|
||
<pre><code class="language-shell">$ cargo build --target thumbv6m-none-eabi
|
||
error: `#[panic_handler]` function required, but not found
|
||
</code></pre>
|
||
<p>This error, is because rustc required a panic handler to be implemented.</p>
|
||
<a class="header" href="#panic_impl" id="panic_impl"><h3><code>panic_impl</code></h3></a>
|
||
<p>We could try and implement the panic macro ourselves,
|
||
but it's easier and more portable to use a crate that does it for us.</p>
|
||
<p>If we look on <a href="https://crates.io/keywords/panic-impl">crates.io for the panic-impl keyword</a> we will find some examples.
|
||
Let us pic the simplest one, and add it to our Cargo.toml.
|
||
If you have forgotten how to do this, try looking at <a href="https://doc.rust-lang.org/stable/cargo/">the cargo book</a>.</p>
|
||
<p><code>Cargo.toml</code></p>
|
||
<pre><code class="language-toml">[dependencies]
|
||
panic-halt = "~0.2"
|
||
</code></pre>
|
||
<p><code>src/main.rs</code></p>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
|
||
extern crate panic_halt;
|
||
|
||
fn main() {
|
||
}
|
||
</code></pre></pre>
|
||
<a class="header" href="#build-4" id="build-4"><h2>Build 4</h2></a>
|
||
<pre><code class="language-shell">$ cargo build --target thumbv6m-none-eabi
|
||
error: requires `start` lang_item
|
||
</code></pre>
|
||
<a class="header" href="#no_main" id="no_main"><h3><code>no_main</code></h3></a>
|
||
<p>In the normal command line rust binaries you would be used to making,
|
||
executing the binary usually has the operating system start by executing the C runtime library (crt0).
|
||
This in turn invokes the Rust runtime, as marked by the <code>start</code> language item,
|
||
which in turn invokes the main function.</p>
|
||
<p>Having enabled <code>no_std</code>, as we are targeting on a microcontroller,
|
||
neither the crt0 nor the rust runtime are available,
|
||
so even implementing <code>start</code> would not help us.
|
||
We need to replace the operating system entry point.</p>
|
||
<p>You could for example name a function after the default entry point,
|
||
which for linux is <code>_start</code>, and start that way.
|
||
Note, you would also need to disable <a href="https://en.wikipedia.org/wiki/Name_mangling">name mangling</a>:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#![no_std]
|
||
#![no_main]
|
||
|
||
#fn main() {
|
||
#[no_mangle]
|
||
pub extern "C" fn _start() -> ! {
|
||
loop {}
|
||
}
|
||
#}</code></pre></pre>
|
||
<p>This is the end of the road for trying to get this to work on our own.
|
||
At this point we need the help of a board-specific support crate and a few cargo tweaks to get this working.</p>
|
||
<a class="header" href="#microbit-crate" id="microbit-crate"><h2>microbit crate</h2></a>
|
||
<p>Let us add a dependency on the board crate for the micro:bit.</p>
|
||
<pre><code class="language-toml">[dependencies]
|
||
panic-halt = "~0.2"
|
||
microbit="~0.7"
|
||
</code></pre>
|
||
<p>The microbit crate has 2 notable dependencies:</p>
|
||
<a class="header" href="#embedded-hal" id="embedded-hal"><h3><code>embedded-hal</code></h3></a>
|
||
<p>This crate is a HAL implementation crate, where HAL stands for <em>hardware abstraction layer</em>.
|
||
As rust becomes more and more popular in embedded development,
|
||
it is desireable to have as little hardware specific implementation as possible.</p>
|
||
<p>For this reason, the <code>embedded-hal</code> crate contains a range of hardware abstraction traits which can
|
||
be implemented by board specific crates.</p>
|
||
<a class="header" href="#cortex-m-rt" id="cortex-m-rt"><h3><code>cortex-m-rt</code></h3></a>
|
||
<p>This crate implements the minimal startup / runtime for Cortex-M microcontrollers.
|
||
Among other things this crate provides:</p>
|
||
<ul>
|
||
<li>the <code>#[entry]</code> attribute, to define the entry point of the program.</li>
|
||
<li>a definition of the hard fault handler</li>
|
||
<li>a definition of the default exception handler</li>
|
||
</ul>
|
||
<p>This crate requires:</p>
|
||
<ul>
|
||
<li>a definition of the specific microcontroller's memory layout as a memory.x file.
|
||
fortunately this is usually provided by the board support crates</li>
|
||
</ul>
|
||
<p>To use the <code>#[entry]</code> attribute, we will need to add this as a dependency.</p>
|
||
<p>For more detailed information,
|
||
you can use the helpful <a href="https://docs.rs/crate/cortex-m-quickstart">cortex-m-quickstart crate</a> and <a href="https://docs.rs/cortex-m-quickstart">its documentation</a>.</p>
|
||
<a class="header" href="#cargo-config" id="cargo-config"><h2>cargo config</h2></a>
|
||
<p>Before we go any further,
|
||
we are going to tweak the cargo's configuration by editing <code>microrust-start/.cargo/config</code>.
|
||
For more information, you can read <a href="https://doc.rust-lang.org/cargo/reference/config.html">the documentation here</a>.</p>
|
||
<a class="header" href="#cargoconfig" id="cargoconfig"><h3><code>.cargo/config</code></h3></a>
|
||
<pre><code class="language-toml"># Configure builds for our target, the micro:bit's architecture
|
||
[target.thumbv6m-none-eabi]
|
||
# Execute binary using gdb when calling cargo run
|
||
runner = "arm-none-eabi-gdb"
|
||
# Tweak to the linking process required by the cortex-m-rt crate
|
||
rustflags = [
|
||
"-C", "link-arg=-Tlink.x",
|
||
# The LLD linker is selected by default
|
||
#"-C", "linker=arm-none-eabi-ld",
|
||
]
|
||
|
||
# Automatically select this target when cargo building this project
|
||
[build]
|
||
target = "thumbv6m-none-eabi"
|
||
|
||
</code></pre>
|
||
<a class="header" href="#arm-none-eabi-gdb" id="arm-none-eabi-gdb"><h3>arm-none-eabi-gdb</h3></a>
|
||
<p>This is a version of gdb (the GNU debugger) for the ARM EABI (embedded application binary interface).
|
||
It will allow us to debug the code running on our micro:bit, from your computer.</p>
|
||
<a class="header" href="#build-target" id="build-target"><h3>Build target</h3></a>
|
||
<p>Now, all you need to do is run <code>$ cargo build</code>,
|
||
and cargo will automatically add <code>--target thumbv6m-none-eabi</code>.</p>
|
||
<a class="header" href="#build-5" id="build-5"><h2>Build 5</h2></a>
|
||
<a class="header" href="#cargotoml" id="cargotoml"><h3><code>Cargo.toml</code></h3></a>
|
||
<pre><code class="language-toml">[dependencies]
|
||
panic-halt = "~0.2"
|
||
microbit="~0.7"
|
||
cortex-m-rt="~0.6"
|
||
</code></pre>
|
||
<a class="header" href="#srcmainrs" id="srcmainrs"><h3><code>src/main.rs</code></h3></a>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate panic_halt;
|
||
|
||
use cortex_m_rt::entry;
|
||
|
||
#[entry]
|
||
fn main() {
|
||
}
|
||
</code></pre></pre>
|
||
<pre><code class="language-shell">$ cargo build
|
||
</code></pre>
|
||
<pre><code class="language-shell">error: custom attribute panicked
|
||
--> src/main.rs:7:1
|
||
|
|
||
7 | #[entry]
|
||
| ^^^^^^^^
|
||
|
|
||
= help: message: `#[entry]` function must have signature `[unsafe] fn() -> !`
|
||
</code></pre>
|
||
<a class="header" href="#a-return-type" id="a-return-type"><h2><code>!</code> return type</h2></a>
|
||
<p>A little known rust feature, so I will forgive you if you do not know what this means.
|
||
A return type of <code>!</code> means that the function cannot return.
|
||
An easy way to implement this is to use an infinite loop.</p>
|
||
<a class="header" href="#srcmainrs-1" id="srcmainrs-1"><h3><code>src/main.rs</code></h3></a>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate panic_halt;
|
||
|
||
use cortex_m_rt::entry;
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
loop {}
|
||
}
|
||
</code></pre></pre>
|
||
<a class="header" href="#build-6" id="build-6"><h2>Build 6</h2></a>
|
||
<p>If you try building now, you should finally be greeted with <code>Finished</code>!</p>
|
||
<pre><code class="language-shell">$ cargo build
|
||
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
|
||
</code></pre>
|
||
<a class="header" href="#build-complete" id="build-complete"><h2>Build Complete</h2></a>
|
||
<p>As a sanity check, let's verify that the produced executable is actually an ARM binary:</p>
|
||
<pre><code class="language-shell">$ file target/thumbv6m-none-eabi/debug/microrust-start
|
||
target/thumbv6m-none-eabi/debug/microrust-start: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
|
||
^^^ ^^^^^
|
||
</code></pre>
|
||
<a class="header" href="#flashing" id="flashing"><h1>Flashing</h1></a>
|
||
<p>Flashing is the process of moving our program into the microcontroller's (persistent) memory. Once flashed, the microcontroller will execute the flashed program every time it is powered on.</p>
|
||
<p>In this case, our <code>rustled</code> program will be the only program in the microcontroller memory. By this I mean that there's nothing else running on the microcontroller: no OS, no daemon, nothing. <code>rustled</code> has full control over the device. This is what is meant by <em>bare-metal</em> programming.</p>
|
||
<dl>
|
||
<dt>OS</dt>
|
||
<dd>operating system</dd>
|
||
<dt>Daemon</dt>
|
||
<dd>program running in the background</dd>
|
||
</dl>
|
||
<p>Connect the micro:bit to your computer and run the following commands on a new terminal.</p>
|
||
<p>We need to give OCD the name of the interfaces we are using:</p>
|
||
<pre><code class="language-console">$ # All
|
||
$ # Windows: remember that you need an extra `-s %PATH_TO_OPENOCD%\<version>\scripts`
|
||
$ openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg
|
||
</code></pre>
|
||
<p>The program will block; leave that terminal open.</p>
|
||
<p>Now it's a good time to explain what this command is actually doing.</p>
|
||
<p>I mentioned that the micro:bit actually has two microcontrollers.
|
||
One of them is used as a USB interface and programmer/debugger.
|
||
This microcontroller is connected to the target microcontroller using a Serial Wire Debug (SWD) interface
|
||
(this interface is an ARM standard so you'll run into it when dealing with other Cortex-M based microcontrollers).
|
||
This SWD interface can be used to flash and debug a microcontroller.
|
||
It uses the CMSIS-DAP protocol for host debugging of application programs.
|
||
It will appear as a USB device when you connect the micro:bit to your laptop.</p>
|
||
<p>As for OpenOCD,
|
||
it's software that provides some services like a <em>GDB server</em>
|
||
on top of USB devices that expose a debugging protocol like SWD or JTAG.</p>
|
||
<blockquote>
|
||
<p>GDB: The <strong>G</strong>NU <strong>d</strong>e<strong>b</strong>ugger will allow us to debug our software
|
||
by controlling the execution of our program.
|
||
We will learn more about this a little bit later.</p>
|
||
</blockquote>
|
||
<p>Onto the actual command: those <code>.cfg</code> files we are using instruct OpenOCD to look for</p>
|
||
<ul>
|
||
<li>a CMSIS-DAP USB interface device (<code>interface/cmsis-dap.cfg</code>)</li>
|
||
<li>a nRF51XXX microcontroller target (<code>target/nrf51.cfg</code>) to be connected to the USB interface.</li>
|
||
</ul>
|
||
<p>The OpenOCD output looks like this:</p>
|
||
<pre><code class="language-console">Open On-Chip Debugger 0.10.0
|
||
Licensed under GNU GPL v2
|
||
For bug reports, read
|
||
http://openocd.org/doc/doxygen/bugs.html
|
||
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
|
||
cortex_m reset_config sysresetreq
|
||
adapter speed: 1000 kHz
|
||
Info : CMSIS-DAP: SWD Supported
|
||
Info : CMSIS-DAP: Interface Initialised (SWD)
|
||
Info : CMSIS-DAP: FW Version = 1.0
|
||
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
|
||
Info : CMSIS-DAP: Interface ready
|
||
Info : clock speed 1000 kHz
|
||
Info : SWD DPIDR 0x0bb11477
|
||
Info : nrf51.cpu: hardware has 4 breakpoints, 2 watchpoints
|
||
</code></pre>
|
||
<p>The "4 breakpoints, 2 watchpoints" part indicates the debugging features the processor has
|
||
available.</p>
|
||
<p>I mentioned that OpenOCD provides a GDB server so let's connect to that right now:</p>
|
||
<pre><code class="language-console">$ arm-none-eabi-gdb -q target/thumbv6m-none-eabi/debug/rustled
|
||
Reading symbols from target/thumbv6m-none-eabi/debug/rustled...done.
|
||
(gdb)
|
||
</code></pre>
|
||
<p>This only opens a GDB shell. To actually connect to the OpenOCD GDB server, use the following
|
||
command within the GDB shell:</p>
|
||
<pre><code class="language-gdb">(gdb) target remote :3333
|
||
Remote debugging using :3333
|
||
0x00000000 in ?? ()
|
||
</code></pre>
|
||
<p>By default OpenOCD's GDB server listens on TCP port 3333 (localhost). This command is connecting to
|
||
that port.</p>
|
||
<p>After entering this command, you'll see new output in the OpenOCD terminal:</p>
|
||
<pre><code class="language-diff"> Info : stm32f3x.cpu: hardware has 4 breakpoints, 2 watchpoints
|
||
+Info : accepting 'gdb' connection on tcp/3333
|
||
+Info : nRF51822-QFAA(build code: H0) 256kB Flash
|
||
</code></pre>
|
||
<p>Almost there. To flash the device, we'll use the <code>load</code> command inside the GDB shell:</p>
|
||
<pre><code class="language-gdb">(gdb) load
|
||
Loading section .vector_table, size 0x188 lma 0x8000000
|
||
Loading section .text, size 0x38a lma 0x8000188
|
||
Loading section .rodata, size 0x8 lma 0x8000514
|
||
Start address 0x8000188, load size 1306
|
||
Transfer rate: 6 KB/sec, 435 bytes/write.
|
||
</code></pre>
|
||
<p>And that's it. You'll also see new output in the OpenOCD terminal.</p>
|
||
<pre><code class="language-diff"> Info : flash size = 256kbytes
|
||
+Info : Unable to match requested speed 1000 kHz, using 950 kHz
|
||
+Info : Unable to match requested speed 1000 kHz, using 950 kHz
|
||
+adapter speed: 950 kHz
|
||
+target state: halted
|
||
+target halted due to debug-request, current mode: Thread
|
||
+xPSR: 0x01000000 pc: 0x08000194 msp: 0x2000a000
|
||
+Info : Unable to match requested speed 8000 kHz, using 4000 kHz
|
||
+Info : Unable to match requested speed 8000 kHz, using 4000 kHz
|
||
+adapter speed: 4000 kHz
|
||
+target state: halted
|
||
+target halted due to breakpoint, current mode: Thread
|
||
+xPSR: 0x61000000 pc: 0x2000003a msp: 0x2000a000
|
||
+Info : Unable to match requested speed 1000 kHz, using 950 kHz
|
||
+Info : Unable to match requested speed 1000 kHz, using 950 kHz
|
||
+adapter speed: 950 kHz
|
||
+target state: halted
|
||
+target halted due to debug-request, current mode: Thread
|
||
+xPSR: 0x01000000 pc: 0x08000194 msp: 0x2000a000
|
||
</code></pre>
|
||
<p>Our program is loaded, we can now run it!</p>
|
||
<pre><code class="language-gdb">(gdb) continue
|
||
Continuing.
|
||
</code></pre>
|
||
<p>Continue runs the program until the next breakpoint.
|
||
This time it blocks, nothing happens.
|
||
This is because all we have in our code is a loop!</p>
|
||
<a class="header" href="#gdbinit" id="gdbinit"><h2><code>.gdbinit</code></h2></a>
|
||
<p>Before we move on though, we are going to add one more file to our project.
|
||
This will automate the last few steps so we don't need to repeatedly do the same actions in gdb:</p>
|
||
<p><code>.gdbinit</code></p>
|
||
<pre><code class="language-gdbinit"># Connects GDB to OpenOCD server port
|
||
target remote :3333
|
||
# (optional) Unmangle function names when debugging
|
||
set print asm-demangle on
|
||
# Load your program, breaks at entry
|
||
load
|
||
# (optional) Add breakpoint at function
|
||
break rustled::main
|
||
# Continue with execution
|
||
continue
|
||
</code></pre>
|
||
<p>Now we can learn how to debug code on the micro:bit.</p>
|
||
<a class="header" href="#debugging" id="debugging"><h1>Debugging</h1></a>
|
||
<a class="header" href="#setup" id="setup"><h2>Setup</h2></a>
|
||
<p>Before we start, let's add some code to debug:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">// -- snip --
|
||
entry!(main);
|
||
fn main() -> ! {
|
||
let _y;
|
||
let x = 42;
|
||
_y = x;
|
||
loop {}
|
||
}
|
||
</code></pre></pre>
|
||
<a class="header" href="#gdb-session" id="gdb-session"><h2>GDB session</h2></a>
|
||
<p>We are already inside a debugging session so let's debug our program.</p>
|
||
<p>After the <code>load</code> command, our program is stopped at its <em>entry point</em>. This is indicated by the
|
||
"Start address 0x8000XXX" part of GDB's output. The entry point is the part of a program that a
|
||
processor / CPU will execute first.</p>
|
||
<p>The starter project I've provided to you has some extra code that runs <em>before</em> the <code>main</code> function.
|
||
At this time, we are not interested in that "pre-main" part so let's skip right to the beginning of
|
||
the <code>main</code> function. We'll do that using a breakpoint:</p>
|
||
<pre><code>(gdb) break rustled::main
|
||
Breakpoint 1 at 0x8000218: file src/main.rs, line 8.
|
||
|
||
(gdb) continue
|
||
Continuing.
|
||
Note: automatically using hardware breakpoints for read-only addresses.
|
||
|
||
Breakpoint 1, rustled::main () at src/rustled/src/main.rs:13
|
||
13 let x = 42;
|
||
</code></pre>
|
||
<p>Breakpoints can be used to stop the normal flow of a program.
|
||
The <code>continue</code> command will let the program run freely <em>until</em> it reaches a breakpoint.
|
||
In this case, until it reaches the <code>main</code> function because there's a breakpoint there.</p>
|
||
<p>Note that GDB output says "Breakpoint 1".
|
||
Remember that our processor can only use four of these
|
||
breakpoints so it's a good idea to pay attention to these messages.</p>
|
||
<p>For a nicer debugging experience, we'll be using GDB's Text User Interface (TUI).
|
||
To enter into that mode, on the GDB shell enter the following command:</p>
|
||
<pre><code>(gdb) layout src
|
||
</code></pre>
|
||
<blockquote>
|
||
<p><strong>NOTE</strong> Apologies Windows users.
|
||
The GDB shipped with the GNU ARM Embedded Toolchain doesn't support this TUI mode <code>:(</code>.</p>
|
||
</blockquote>
|
||
<p>At any point you can leave the TUI mode using the following command:</p>
|
||
<pre><code>(gdb) tui disable
|
||
</code></pre>
|
||
<p>OK. We are now at the beginning of <code>main</code>.
|
||
We can advance the program statement by statement using the <code>step</code> command.
|
||
So let's use that twice to reach the <code>y = x</code> statement.
|
||
Once you've typed <code>step</code> once you can just hit enter to run it again.</p>
|
||
<pre><code>(gdb) step
|
||
14 _y = x;
|
||
</code></pre>
|
||
<p>If you are not using the TUI mode,
|
||
on each <code>step</code> call GDB will print back the current statement along with its line number.</p>
|
||
<p>We are now "on" the <code>y = x</code> statement; that statement hasn't been executed yet. This means that <code>x</code>
|
||
is initialized but <code>y</code> is not. Let's inspect those stack/local variables using the <code>print</code> command:</p>
|
||
<pre><code>(gdb) print x
|
||
$1 = 42
|
||
|
||
(gdb) print &x
|
||
$2 = (i32 *) 0x10001fdc
|
||
|
||
(gdb) print _y
|
||
$3 = 134219052
|
||
|
||
(gdb) print &_y
|
||
$4 = (i32 *) 0x10001fd8
|
||
</code></pre>
|
||
<p>As expected, <code>x</code> contains the value <code>42</code>.
|
||
<code>_y</code> however, contains the value <code>134219052</code> (?).
|
||
Because <code>_y</code> has not been initialized yet, it contains some garbage value.</p>
|
||
<p>The command <code>print &x</code> prints the address of the variable <code>x</code>.
|
||
The interesting bit here is that GDB output shows the type of the reference:
|
||
<code>i32*</code>, a pointer to an <code>i32</code> value.
|
||
Another interesting thing is that the addresses of <code>x</code> and <code>_y</code> are very close to each other:
|
||
their addresses are just <code>4</code> bytes apart.</p>
|
||
<p>Instead of printing the local variables one by one, you can also use the <code>info locals</code> command:</p>
|
||
<pre><code>(gdb) info locals
|
||
x = 42
|
||
_y = 134219052
|
||
</code></pre>
|
||
<p>OK. With another <code>step</code>, we'll be on top of the <code>loop {}</code> statement:</p>
|
||
<pre><code>(gdb) step
|
||
17 loop {}
|
||
</code></pre>
|
||
<p>And <code>_y</code> should now be initialized.</p>
|
||
<pre><code>(gdb) print _y
|
||
$5 = 42
|
||
</code></pre>
|
||
<p>If we use <code>step</code> again on top of the <code>loop {}</code> statement, we'll get stuck because the program will
|
||
never pass that statement. Instead, we'll switch to the disassemble view with the <code>layout asm</code>
|
||
command and advance one instruction at a time using <code>stepi</code>.</p>
|
||
<blockquote>
|
||
<p><strong>NOTE</strong> If you used the <code>step</code> command by mistake and GDB got stuck, you can get unstuck by hitting <code>Ctrl+C</code>.</p>
|
||
</blockquote>
|
||
<pre><code>(gdb) layout asm
|
||
</code></pre>
|
||
<p>If you are not using the TUI mode,
|
||
you can use the <code>disassemble /m</code> command to disassemble the program around the line you are currently at.</p>
|
||
<pre><code>(gdb) disassemble /m
|
||
Dump of assembler code for function led_roulette::main:
|
||
11 fn main() -> ! {
|
||
0x08000188 <+0>: sub sp, #8
|
||
|
||
12 let _y;
|
||
13 let x = 42;
|
||
0x0800018a <+2>: movs r0, #42 ; 0x2a
|
||
0x0800018c <+4>: str r0, [sp, #4]
|
||
|
||
14 _y = x;
|
||
0x0800018e <+6>: ldr r0, [sp, #4]
|
||
0x08000190 <+8>: str r0, [sp, #0]
|
||
|
||
15
|
||
16 // infinite loop; just so we don't leave this stack frame
|
||
17 loop {}
|
||
=> 0x08000192 <+10>: b.n 0x8000194 <led_roulette::main+12>
|
||
0x08000194 <+12>: b.n 0x8000194 <led_roulette::main+12>
|
||
|
||
End of assembler dump.
|
||
</code></pre>
|
||
<p>See the fat arrow <code>=></code> on the left side? It shows the instruction the processor will execute next.</p>
|
||
<p>If not inside the TUI mode on each <code>stepi</code> command GDB will print the statement,
|
||
the line number <em>and</em> the address of the instruction the processor will execute next.</p>
|
||
<pre><code>(gdb) stepi
|
||
0x08000194 17 loop {}
|
||
|
||
(gdb) stepi
|
||
0x08000194 17 loop {}
|
||
</code></pre>
|
||
<p>One last trick before we move to something more interesting.
|
||
Enter the following commands into GDB:</p>
|
||
<pre><code>(gdb) monitor reset halt
|
||
Unable to match requested speed 1000 kHz, using 950 kHz
|
||
Unable to match requested speed 1000 kHz, using 950 kHz
|
||
adapter speed: 950 kHz
|
||
target halted due to debug-request, current mode: Thread
|
||
xPSR: 0x01000000 pc: 0x08000188 msp: 0x10002000
|
||
|
||
(gdb) continue
|
||
Continuing.
|
||
|
||
Breakpoint 1, led_roulette::main () at src/main.rs:8
|
||
8 let x = 42;
|
||
</code></pre>
|
||
<p>We are now back at the beginning of <code>main</code>!</p>
|
||
<p><code>monitor reset halt</code> will reset the microcontroller and stop it right at the program entry point.
|
||
The following <code>continue</code> command will let the program run freely until it reaches the <code>main</code> function that has a breakpoint on it.</p>
|
||
<p>This combo is handy when you, by mistake,
|
||
skipped over a part of the program that you were interested in inspecting.
|
||
You can easily roll back the state of your program back to its very beginning.</p>
|
||
<blockquote>
|
||
<p><strong>The fine print</strong>: This <code>reset</code> command doesn't clear or touch RAM.
|
||
That memory will retain its values from the previous run.
|
||
That shouldn't be a problem though, unless your program behavior depends of the value of <em>uninitialized</em> variables,
|
||
but that's the definition of <em>undefined behavior</em> (UB).</p>
|
||
</blockquote>
|
||
<p>We are done with this debug session. You can end it with the <code>quit</code> command.</p>
|
||
<pre><code>(gdb) quit
|
||
A debugging session is active.
|
||
|
||
Inferior 1 [Remote target] will be detached.
|
||
|
||
Quit anyway? (y or n) y
|
||
Detaching from program: $PWD/target/thumbv7em-none-eabihf/debug/led-roulette, Remote target
|
||
Ending remote debugging.
|
||
</code></pre>
|
||
<blockquote>
|
||
<p><strong>NOTE</strong> If the default GDB CLI is not to your liking check out <a href="https://github.com/cyrus-and/gdb-dashboard#gdb-dashboard">gdb-dashboard</a>.
|
||
It uses Python to turn the default GDB CLI into a dashboard that shows registers,
|
||
the source view, the assembly view and other things.</p>
|
||
</blockquote>
|
||
<p>Don't close OpenOCD though! We'll use it again and again later on. It's better
|
||
just to leave it running.</p>
|
||
<a class="header" href="#what-next" id="what-next"><h2>What next?</h2></a>
|
||
<p>In the next chapter we will learn
|
||
how to send messages from the micro:bit to your computer,
|
||
as well as howt to control the HAL GPIO.</p>
|
||
<a class="header" href="#solution" id="solution"><h1>Solution</h1></a>
|
||
<p>This is a recap of what we have done so far.</p>
|
||
<a class="header" href="#cargotoml-1" id="cargotoml-1"><h2>Cargo.toml</h2></a>
|
||
<pre><code class="language-toml">[package]
|
||
name = "start"
|
||
version = "0.2.0"
|
||
|
||
[dependencies]
|
||
panic-halt = "~0.2"
|
||
microbit="~0.7"
|
||
cortex-m-rt="~0.6"
|
||
|
||
</code></pre>
|
||
<a class="header" href="#rust" id="rust"><h2>Rust</h2></a>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate cortex_m_rt;
|
||
extern crate microbit;
|
||
extern crate panic_halt;
|
||
|
||
use cortex_m_rt::entry;
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
let _y;
|
||
let x = 42;
|
||
_y = x;
|
||
loop {}
|
||
}
|
||
|
||
</code></pre></pre>
|
||
<a class="header" href="#cargoconfig-1" id="cargoconfig-1"><h2><code>.cargo/config</code></h2></a>
|
||
<pre><code class="language-toml"># Configure builds for our target, the micro:bit's architecture
|
||
[target.thumbv6m-none-eabi]
|
||
# Execute binary using gdb when calling cargo run
|
||
runner = "arm-none-eabi-gdb"
|
||
# Tweak to the linking process required by the cortex-m-rt crate
|
||
rustflags = [
|
||
"-C", "link-arg=-Tlink.x",
|
||
# The LLD linker is selected by default
|
||
#"-C", "linker=arm-none-eabi-ld",
|
||
]
|
||
|
||
# Automatically select this target when cargo building this project
|
||
[build]
|
||
target = "thumbv6m-none-eabi"
|
||
|
||
</code></pre>
|
||
<a class="header" href="#gdbinit-1" id="gdbinit-1"><h2><code>.gdbinit</code></h2></a>
|
||
<pre><code class="language-gdb"># Connects GDB to OpenOCD server port
|
||
target remote :3333
|
||
# (optional) Unmangle function names when debugging
|
||
set print asm-demangle on
|
||
# Load your program, breaks at entry
|
||
load
|
||
# (optional) Add breakpoint at function
|
||
break main
|
||
# Continue with execution
|
||
continue
|
||
</code></pre>
|
||
<a class="header" href="#hello-world" id="hello-world"><h1>Hello world</h1></a>
|
||
<p>In this chapter, we will discuss the basic I/O of embedded development in rust.</p>
|
||
<p>After this chapter,you should have all the neccesary basic knowledge to do embedded development in Rust,
|
||
with anything remaining being solution specific.</p>
|
||
<a class="header" href="#semihosting" id="semihosting"><h1>Semihosting</h1></a>
|
||
<p>Semihosting is a feature which allows targets without I/O support to use the I/O of the host.
|
||
When the special <code>BKPT</code> instruction is reached, the host reads the characters directly from the micro:bit's memory.</p>
|
||
<a class="header" href="#semihosting-is-slow" id="semihosting-is-slow"><h2>Semihosting is slow</h2></a>
|
||
<p>The most important thing to remember about semihosting is that it is slow.
|
||
The processor halts entirely for each operation, making each operation take 107 milliseconds.
|
||
This means that if you are doing any time sensitive work, you should not use it for logging.
|
||
<a href="http://blog.japaric.io/itm/">Check out this blog post for more information.</a></p>
|
||
<a class="header" href="#gdb" id="gdb"><h2>GDB</h2></a>
|
||
<p>The first thing to do is to enable semihosting in GDB.
|
||
As before, we will add this to <code>.gdbinit</code> to avoid typing it every time.</p>
|
||
<p><code>.gdbinit</code></p>
|
||
<pre><code class="language-gdb">target remote :3333
|
||
monitor arm semihosting enable
|
||
load
|
||
</code></pre>
|
||
<a class="header" href="#openocd-1" id="openocd-1"><h2>OpenOCD</h2></a>
|
||
<p>You may have incorrectly assumed at this point that the outpust would appear in GDB.
|
||
Remember that GDB simply connects to OpenOCD to interface with the micro:bit.
|
||
OpenOCD is very loud currently,
|
||
so it will be quite hard to see the output of our micro:bit in the noise.
|
||
Fix this by stopping and restarting it with logging dumped to a file.</p>
|
||
<pre><code class="language-console">openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -l /tmp/openocd.log
|
||
</code></pre>
|
||
<a class="header" href="#panic" id="panic"><h2>Panic</h2></a>
|
||
<p>The easiest way to use semihosting is to use it for the <code>panic!</code> macro.</p>
|
||
<p><code>Cargo.toml</code></p>
|
||
<pre><code class="language-toml">panic-semihosting = ""
|
||
</code></pre>
|
||
<p>You can then see what happens if you add a <code>panic!</code> to your code:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">fn main() -> ! {
|
||
panic!("test-panic");
|
||
}
|
||
</code></pre></pre>
|
||
<pre><code>Open On-Chip Debugger 0.10.0
|
||
Licensed under GNU GPL v2
|
||
For bug reports, read
|
||
http://openocd.org/doc/doxygen/bugs.html
|
||
panicked at 'test-panic', src/hello-world/src/main.rs:27:5
|
||
</code></pre>
|
||
<a class="header" href="#stdout" id="stdout"><h2>stdout</h2></a>
|
||
<p>Finally, this is how to write to stdout.</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
extern crate cortex_m_semihosting as sh;
|
||
use core::fmt::Write;
|
||
use sh::hio;
|
||
// -- snip --
|
||
let mut stdout = hio::hstdout().unwrap();
|
||
stdout.write_str("semitest\n\r").unwrap();
|
||
// or
|
||
writeln!(hio::hstdout().unwrap(), "Init").unwrap();
|
||
#}</code></pre></pre>
|
||
<p>Writing to stderr is just as easy.</p>
|
||
<a class="header" href="#serial-communication" id="serial-communication"><h1>Serial communication</h1></a>
|
||
<p>The micro:bit has a perihperal called UART,
|
||
a Universal Asynchronous Receiver/Transmitter.
|
||
This is a form of serial communication, data is transferred serially,
|
||
i.e. one bit at a a time.
|
||
It is asynchronous communication, and there is no clock signal to dictate the bitrate,
|
||
intead this is agreed upon beforehand.
|
||
The protocol has frames consisting of a start bit, data bits, parity bits, and stop bits.
|
||
We will be using 8 bits per frame: 1 start, 6 data and 1 stop.
|
||
The data rate is called the <em>baud rate</em>, and we will use 115200bps.</p>
|
||
<a class="header" href="#usb" id="usb"><h2>USB</h2></a>
|
||
<p>The micro:bit allows us to transmit and receive this serial communication over USB with no additional hardware.</p>
|
||
<a class="header" href="#tooling" id="tooling"><h2>Tooling</h2></a>
|
||
<p>To read and write to the serial bus from your computer, you will need to configure your tooling:</p>
|
||
<ul>
|
||
<li><a href="../hello-world/02.01.NIX.html">*nix</a></li>
|
||
<li><a href="../hello-world/02.02.WINDOWS.html">Windows</a></li>
|
||
</ul>
|
||
<a class="header" href="#code" id="code"><h2>Code</h2></a>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
use microbit::hal::prelude::*;
|
||
use microbit::hal::serial;
|
||
use microbit::hal::serial::BAUD115200;
|
||
// -- snip --
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
let mut gpio = p.GPIO.split();
|
||
// Configure RX and TX pins accordingly
|
||
let tx = gpio.pin24.into_push_pull_output().downgrade();
|
||
let rx = gpio.pin25.into_floating_input().downgrade();
|
||
// Configure serial communication
|
||
let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
|
||
// Write string with newline and carriage return
|
||
// This could also be a format string
|
||
let _ = write!(tx, "serial test\n\r");
|
||
}
|
||
#}</code></pre></pre>
|
||
<p>In minicom/PuTTY you should see:</p>
|
||
<pre><code>serial test
|
||
</code></pre>
|
||
<p>This is a very simple introduction to using the UART as one way serial logging.
|
||
The chapter on UART serial communication goes into much more detail.</p>
|
||
<a class="header" href="#nix-tooling" id="nix-tooling"><h1>*nix tooling</h1></a>
|
||
<p>Connect the serial module to your laptop and let's find out what name the OS assigned to it.</p>
|
||
<pre><code class="language-console">$ dmesg | grep -i tty
|
||
(..)
|
||
[ +0.000155] usb 3-2: FTDI USB Serial Device converter now attached to ttyUSB0
|
||
</code></pre>
|
||
<blockquote>
|
||
<p><strong>NOTE</strong> On macs, the USB device will named like this: <code>cu.usbserial-*</code>. Adjust the following
|
||
commands accordingly!</p>
|
||
</blockquote>
|
||
<p>But what's this <code>ttyUSB0</code> thing? It's a file of course! Everything is a file in *nix:</p>
|
||
<pre><code class="language-console">$ ls -l /dev/ttyUSB0
|
||
crw-rw---- 1 root uucp 188, 0 Oct 27 00:00 /dev/ttyUSB0
|
||
</code></pre>
|
||
<p>You can send out data by simply writing to this file:</p>
|
||
<pre><code class="language-console">$ echo 'Hello, world!' > /dev/ttyUSB0
|
||
</code></pre>
|
||
<p>You should see the TX (red) LED on the serial module blink, just once and very fast!</p>
|
||
<a class="header" href="#minicom" id="minicom"><h2>minicom</h2></a>
|
||
<p>Dealing with serial devices using <code>echo</code> is far from ergonomic. So, we'll use the program <code>minicom</code>
|
||
to interact with the serial device using the keyboard.</p>
|
||
<p>We must configure <code>minicom</code> before we use it. There are quite a few ways to do that but we'll use a
|
||
<code>.minirc.dfl</code> file in the home directory. Create a file in <code>~/.minirc.dfl</code> with the following
|
||
contents:</p>
|
||
<pre><code class="language-console">$ cat ~/.minirc.dfl
|
||
pu baudrate 115200
|
||
pu bits 8
|
||
pu parity N
|
||
pu stopbits 1
|
||
pu rtscts No
|
||
pu xonxoff No
|
||
</code></pre>
|
||
<blockquote>
|
||
<p><strong>NOTE</strong> Make sure this file ends in a newline! Otherwise, <code>minicom</code> will fail to read it.</p>
|
||
</blockquote>
|
||
<p>That file should be straightforward to read (except for the last two lines), but nonetheless let's
|
||
go over it line by line:</p>
|
||
<ul>
|
||
<li><code>pu baudrate 115200</code>. Sets baud rate to 115200 bps.</li>
|
||
<li><code>pu bits 8</code>. 8 bits per frame.</li>
|
||
<li><code>pu parity N</code>. No parity check.</li>
|
||
<li><code>pu stopbits 1</code>. 1 stop bit.</li>
|
||
<li><code>pu rtscts No</code>. No hardware control flow.</li>
|
||
<li><code>pu xonxoff No</code>. No software control flow.</li>
|
||
</ul>
|
||
<p>Once that's in place. We can launch <code>minicom</code></p>
|
||
<pre><code class="language-console">$ minicom -D /dev/ttyUSB0 -b 115200
|
||
</code></pre>
|
||
<p>This tells <code>minicom</code> to open the serial device at <code>/dev/ttyUSB0</code> and set its baud rate to 115200.
|
||
A text-based user interface (TUI) will pop out.</p>
|
||
<pre><code>Welcome to minicom 2.7.1
|
||
|
||
OPTIONS: I18n
|
||
Compiled on Jun 5 2018, 10:54:41.
|
||
Port /dev/ttyACM0, 19:50:57
|
||
|
||
Press CTRL-A Z for help on special keys
|
||
|
||
</code></pre>
|
||
<p>You can now send data using the keyboard! Go ahead and type something.
|
||
Note that the TUI <em>won't</em> echo back what you type (nothing will happen when you type)
|
||
but you'll see TX (red) LED on the serial module blink with each keystroke.</p>
|
||
<a class="header" href="#minicom-commands" id="minicom-commands"><h2><code>minicom</code> commands</h2></a>
|
||
<p><code>minicom</code> exposes commands via keyboard shortcuts. On Linux, the shortcuts start with <code>Ctrl+A</code>. On
|
||
mac, the shortcuts start with the <code>Meta</code> key. Some useful commands below:</p>
|
||
<ul>
|
||
<li><code>Ctrl+A</code> + <code>Z</code>. Minicom Command Summary</li>
|
||
<li><code>Ctrl+A</code> + <code>C</code>. Clear the screen</li>
|
||
<li><code>Ctrl+A</code> + <code>X</code>. Exit and reset</li>
|
||
<li><code>Ctrl+A</code> + <code>Q</code>. Quit with no reset</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p><strong>NOTE</strong> mac users: In the above commands, replace <code>Ctrl+A</code> with <code>Meta</code>.</p>
|
||
</blockquote>
|
||
<a class="header" href="#windows-tooling" id="windows-tooling"><h1>Windows tooling</h1></a>
|
||
<p>Before plugging the Serial module, run the following command on the terminal:</p>
|
||
<pre><code class="language-console">$ mode
|
||
</code></pre>
|
||
<p>It will print a list of devices that are connected to your laptop. The ones that start with <code>COM</code> in
|
||
their names are serial devices. This is the kind of device we'll be working with. Take note of all
|
||
the <code>COM</code> <em>ports</em> <code>mode</code> outputs <em>before</em> plugging the serial module.</p>
|
||
<p>Now, plug the Serial module and run the <code>mode</code> command again. You should see a new <code>COM</code> port appear
|
||
on the list. That's the COM port assigned to the serial module.</p>
|
||
<p>Now launch <code>putty</code>. A GUI will pop out.</p>
|
||
<p>On the starter screen, which should have the "Session" category open, pick "Serial" as the
|
||
"Connection type". On the "Serial line" field enter the <code>COM</code> device you got on the previous step,
|
||
for example <code>COM3</code>.</p>
|
||
<p>Next, pick the "Connection/Serial" category from the menu on the left. On this new view, make sure
|
||
that the serial port is configured as follows:</p>
|
||
<ul>
|
||
<li>"Speed (baud)": 115200</li>
|
||
<li>"Data bits": 8</li>
|
||
<li>"Stop bits": 1</li>
|
||
<li>"Parity": None</li>
|
||
<li>"Flow control": None</li>
|
||
</ul>
|
||
<p>Finally, click the Open button. A console will show up now.</p>
|
||
<p>If you type on this console, the TX (red) LED on the Serial module should blink. Each key stroke
|
||
should make the LED blink once. Note that the console won't echo back what you type so the screen
|
||
will remain blank.</p>
|
||
<a class="header" href="#gpio-and-leds" id="gpio-and-leds"><h1>GPIO and LEDs</h1></a>
|
||
<a class="header" href="#gpio" id="gpio"><h2>GPIO</h2></a>
|
||
<blockquote>
|
||
<p>GPIO: General purpose input-output</p>
|
||
</blockquote>
|
||
<p>The GPIO is a block of pins found on nearly all microcontrollers.
|
||
As the name implies, they are general-purpose, configureable, analog or digital, input or output, electrical pins.
|
||
Exactly what features each pin has on a given microcontroller will require looking at a datasheet.</p>
|
||
<blockquote>
|
||
<p>Analog vs Digital: Analog signals carry data in their amplitude as they continuously vary over time,
|
||
whereas digital signals have a fixed rate and fixed amplitudes. Digital signals are usually just 0 or 1, i.e. 0V or 3.3V</p>
|
||
</blockquote>
|
||
<a class="header" href="#led" id="led"><h2>LED</h2></a>
|
||
<blockquote>
|
||
<p>LED: Light emitting diode</p>
|
||
</blockquote>
|
||
<p>Let us now turn on an LED! But how?</p>
|
||
<p>Many integrated periperals like LEDs and buttons are already connected to certain GPIO pins,
|
||
so lighting up an LED can be as simple as configuring a GPIO pin to be a digital output.</p>
|
||
<p>First we should look at the <a href="https://docs.rs/microbit/0.5.1/microbit/">documentation of our crate</a>,
|
||
and you should be able to figure out how to get access to the gpio,
|
||
and set individual pins high and low:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
// This takes singleton ownership of the micro:bit's peripherals
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
// Take the micro:bit's GPIO
|
||
let mut gpio = p.GPIO.split();
|
||
// Take pin 1 of the GPIO, and configure it as a digital output
|
||
let mut pin1 = gpio.pin1.into_push_pull_output();
|
||
// Set pin 1 high
|
||
pin1.set_high();
|
||
}
|
||
#}</code></pre></pre>
|
||
<p>Next we need to see how these pins are hooked up,
|
||
for that we need <a href="https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf">the micro:bit schematics</a> linked to at the bottom of <a href="http://tech.microbit.org/hardware/">the hardware overview</a>.
|
||
On the first sheet you should find a diagram with a grid of numbered LEDs.</p>
|
||
<blockquote>
|
||
<p>If you do not know much about electronics:
|
||
Each row and column (labelled ROW and COL) represent a GPIO output pin.
|
||
The components labelled are LEDs.
|
||
LEDs only let current flow one way, and only emit light when current is flowing.
|
||
If a row is set high, high voltage, and a column is set low, low voltage,
|
||
the LED at the point that they cross will have a potential difference across it;
|
||
current will flow and it will light up.</p>
|
||
</blockquote>
|
||
<p>As you can see, the micro:bit's display LEDs are a bit more complicated than being connected to a single pin.
|
||
Each LED is connected to 2 pins, where one needs to be high, and the other low for the LED to light up.</p>
|
||
<p>The 5x5 array of LEDs are actually wired up as a 3x9 array (3 rows by 9 columns), with 2 missing.
|
||
This is usually done to make the circuit design easier.</p>
|
||
<p>The fifth sheet shows how each row and column correspond to each GPIO pin.</p>
|
||
<p>You should now have enough information to try and turn on an LED.</p>
|
||
<a class="header" href="#solution-1" id="solution-1"><h1>Solution</h1></a>
|
||
<p>This is my solution:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate panic_semihosting;
|
||
extern crate cortex_m_rt as rt;
|
||
extern crate cortex_m_semihosting as sh;
|
||
extern crate microbit;
|
||
|
||
use core::fmt::Write;
|
||
use rt::entry;
|
||
use sh::hio;
|
||
|
||
use microbit::hal::prelude::*;
|
||
use microbit::hal::serial;
|
||
use microbit::hal::serial::BAUD115200;
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
let mut stdout = hio::hstdout().unwrap();
|
||
writeln!(stdout, "Start").unwrap();
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
// Split GPIO
|
||
let mut gpio = p.GPIO.split();
|
||
// Configure RX and TX pins accordingly
|
||
let tx = gpio.pin24.into_push_pull_output().downgrade();
|
||
let rx = gpio.pin25.into_floating_input().downgrade();
|
||
// Configure serial communication
|
||
let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
|
||
write!(tx, "serial - start\r\n");
|
||
// Get row and column for display
|
||
let mut led = gpio.pin13.into_push_pull_output();
|
||
let _ = gpio.pin4.into_push_pull_output();
|
||
// Set row high (column starts low)
|
||
led.set_high();
|
||
// Write string with newline and carriage return
|
||
write!(tx, "serial - LED on\r\n");
|
||
}
|
||
panic!("End");
|
||
}
|
||
|
||
</code></pre></pre>
|
||
<p>It is worth noting that pin4 starts low, so does not need to be explicitly set low.</p>
|
||
<p>You now know enough to start playing around with the micro:bit's LED display and GPIO,
|
||
as well as logging data back to the host.</p>
|
||
<p>You should know that the microbit crate already includes an abstraction for the LED display for you to use.
|
||
How to implemented a simple blocking display driver is demonstrated in the <a href="../display/00.00.README.html">LED display chapter</a>.</p>
|
||
<a class="header" href="#choose-your-own-adventure" id="choose-your-own-adventure"><h1>Choose your own adventure</h1></a>
|
||
<p>At this point of the book,
|
||
you know the basics to get started with embedded development with Rust.</p>
|
||
<p>The following chapters are more independent of each other, and can be done in any order;
|
||
the only required knowledge is found in the chapters before this.
|
||
If attempting the exerises, it is best to follow the book in order to pace the difficulty.</p>
|
||
<blockquote>
|
||
<p>A large portion of this book is still unfinished and I would love your support.
|
||
Please submit an issue to request a new section, and a pull request to add a section.</p>
|
||
</blockquote>
|
||
<a class="header" href="#microbit-hal" id="microbit-hal"><h1>micro:bit HAL</h1></a>
|
||
<p>This chapter will demosntrate the common uses of the micro:bit crate,
|
||
and its specific hardware abstraction layer (HAL) features.
|
||
The content in this chapter should be deduceable from the HAL crate,
|
||
but are given here as a reference.</p>
|
||
<p>More examples can be found in the <a href="https://github.com/therealprof/microbit/tree/master/examples">micro:bit crate's examples</a>.</p>
|
||
<a class="header" href="#buttons" id="buttons"><h1>Buttons</h1></a>
|
||
<p>The micro:bit as 3 hardware buttons, 2 user buttons and the reset button.</p>
|
||
<a class="header" href="#user-buttons" id="user-buttons"><h2>User Buttons</h2></a>
|
||
<p>The user buttons are wired up to be high when unpressed and low when pressed.</p>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate panic_abort;
|
||
extern crate cortex_m_rt as rt;
|
||
extern crate microbit;
|
||
|
||
use core::fmt::Write;
|
||
use rt::entry;
|
||
|
||
use microbit::hal::prelude::*;
|
||
use microbit::hal::serial;
|
||
use microbit::hal::serial::BAUD115200;
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
// Split GPIO
|
||
let mut gpio = p.GPIO.split();
|
||
// Configure RX and TX pins accordingly
|
||
let tx = gpio.pin24.into_push_pull_output().downgrade();
|
||
let rx = gpio.pin25.into_floating_input().downgrade();
|
||
// Configure serial communication
|
||
let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
|
||
// Configure button GPIOs as inputs
|
||
let button_a = gpio.pin17.into_floating_input();
|
||
let button_b = gpio.pin26.into_floating_input();
|
||
// loop variables
|
||
let mut state_a_low = false;
|
||
let mut state_b_low = false;
|
||
loop {
|
||
// Get button states
|
||
let button_a_low = button_a.is_low();
|
||
let button_b_low = button_b.is_low();
|
||
if button_a_low && !state_a_low {
|
||
writeln!(tx, "Button A down").unwrap();
|
||
}
|
||
if button_b_low && !state_b_low {
|
||
writeln!(tx, "Button B down").unwrap();
|
||
}
|
||
if !button_a_low && state_a_low {
|
||
writeln!(tx, "Button A up").unwrap();
|
||
}
|
||
if !button_b_low && state_b_low {
|
||
writeln!(tx, "Button B up").unwrap();
|
||
}
|
||
// Store buttons states
|
||
// This should not read the GPIO pins again, as the state
|
||
// may have changed and the change will not be recorded
|
||
state_a_low = button_a_low;
|
||
state_b_low = button_b_low;
|
||
}
|
||
}
|
||
panic!("End");
|
||
}
|
||
|
||
</code></pre></pre>
|
||
<a class="header" href="#delays" id="delays"><h1>Delays</h1></a>
|
||
<p>The microbit has 3 timers, the micro:bit crate currently only supports using TIMER0.</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
let mut delay = Delay::new(p.TIMER0);
|
||
delay.delay_ms(1000_u32);
|
||
}
|
||
#}</code></pre></pre>
|
||
<a class="header" href="#display" id="display"><h1>Display</h1></a>
|
||
<p>The micro:bit display is not trivial to control, so a driver is needed;
|
||
see <a href="../display/00.00.README.html">the display chapter</a> for more details.</p>
|
||
<p>Display calls for now are only blocking, and can either be for binary images (0 for off, 1 for on),
|
||
or for monochrome images with differing brightness levels</p>
|
||
<a class="header" href="#blocking-binary-image-display" id="blocking-binary-image-display"><h2>Blocking binary image display</h2></a>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
#[macro_use(entry, exception)]
|
||
extern crate microbit;
|
||
extern crate cortex_m_rt as rt;
|
||
extern crate cortex_m_semihosting as sh;
|
||
extern crate panic_abort;
|
||
|
||
use core::fmt::Write;
|
||
use rt::entry;
|
||
|
||
use microbit::hal::delay::Delay;
|
||
use microbit::hal::prelude::*;
|
||
use microbit::hal::serial;
|
||
use microbit::hal::serial::BAUD115200;
|
||
use microbit::led;
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
let mut gpio = p.GPIO.split();
|
||
let mut delay = Delay::new(p.TIMER0);
|
||
|
||
// Configure display pins
|
||
let row1 = gpio.pin13.into_push_pull_output().downgrade();
|
||
let row2 = gpio.pin14.into_push_pull_output().downgrade();
|
||
let row3 = gpio.pin15.into_push_pull_output().downgrade();
|
||
let col1 = gpio.pin4.into_push_pull_output().downgrade();
|
||
let col2 = gpio.pin5.into_push_pull_output().downgrade();
|
||
let col3 = gpio.pin6.into_push_pull_output().downgrade();
|
||
let col4 = gpio.pin7.into_push_pull_output().downgrade();
|
||
let col5 = gpio.pin8.into_push_pull_output().downgrade();
|
||
let col6 = gpio.pin9.into_push_pull_output().downgrade();
|
||
let col7 = gpio.pin10.into_push_pull_output().downgrade();
|
||
let col8 = gpio.pin11.into_push_pull_output().downgrade();
|
||
let col9 = gpio.pin12.into_push_pull_output().downgrade();
|
||
|
||
// Configure RX and TX pins accordingly
|
||
let tx = gpio.pin24.into_push_pull_output().downgrade();
|
||
let rx = gpio.pin25.into_floating_input().downgrade();
|
||
|
||
let mut leds = led::Display::new(
|
||
row1, row2, row3, col1, col2, col3, col4, col5, col6, col7, col8, col9,
|
||
);
|
||
|
||
let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
|
||
|
||
let _ = write!(tx, "\n\rStarting!\n\r");
|
||
|
||
#[allow(non_snake_case)]
|
||
let letter_I = [
|
||
[0, 1, 1, 1, 0],
|
||
[0, 0, 1, 0, 0],
|
||
[0, 0, 1, 0, 0],
|
||
[0, 0, 1, 0, 0],
|
||
[0, 1, 1, 1, 0],
|
||
];
|
||
|
||
let heart = [
|
||
[0, 1, 0, 1, 0],
|
||
[1, 0, 1, 0, 1],
|
||
[1, 0, 0, 0, 1],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 0, 1, 0, 0],
|
||
];
|
||
|
||
#[allow(non_snake_case)]
|
||
let letter_U = [
|
||
[0, 1, 0, 1, 0],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 1, 1, 1, 0],
|
||
];
|
||
|
||
loop {
|
||
let _ = write!(tx, "I <3 Rust!\n\r");
|
||
leds.display(&mut delay, letter_I, 1000);
|
||
leds.display(&mut delay, heart, 1000);
|
||
leds.display(&mut delay, letter_U, 1000);
|
||
leds.clear();
|
||
delay.delay_ms(250_u32);
|
||
}
|
||
}
|
||
panic!("End");
|
||
}
|
||
</code></pre></pre>
|
||
<a class="header" href="#wip---uart-serial-server" id="wip---uart-serial-server"><h1>WIP - UART serial server</h1></a>
|
||
<p>In the first section of this book we saw how to do a simple debug print using serial.
|
||
This is useful for logging and debugging, but does not cover the full potential of the UART peripheral.</p>
|
||
<a class="header" href="#input-and-output" id="input-and-output"><h2>Input and output</h2></a>
|
||
<p>The simultaneous input and output capabilities of the UART allow for both your computer and the micro:bit to act as a server.
|
||
They can receive a transmission, process it, perform some action, and send a response.</p>
|
||
<a class="header" href="#echo-server" id="echo-server"><h1>Echo Server</h1></a>
|
||
<p>An echo server, is probably the simplest server we could make.
|
||
It should receive a message, and echo it back to the sender.</p>
|
||
<p>We earlier said that minicom/PuTTY would transmit any keystrokes we send,
|
||
and display and data received,
|
||
so the end result should be the familiar experience of typing and seeing the letters typed appear as expected.</p>
|
||
<a class="header" href="#flow" id="flow"><h2>Flow</h2></a>
|
||
<ol>
|
||
<li>The character <code>a</code> is typed</li>
|
||
<li>minicom/PuTTY encodes the <code>a</code> character's unicode code point (<code>097</code> in decimal) as a word in a serial frame</li>
|
||
<li>The frame is transmitted to the micro:bit over USB</li>
|
||
<li>The micro:bit software decodes the frame to get the word</li>
|
||
<li>The microbit software re-encodes the word to get a (new but identical) frame</li>
|
||
<li>The frame is transmitted to the computer over USB</li>
|
||
<li>minicom/PuTTY decodes the frame's word as a unicode code point</li>
|
||
<li>The letter <code>a</code> is displayed</li>
|
||
</ol>
|
||
<a class="header" href="#serial-theory" id="serial-theory"><h1>Serial Theory</h1></a>
|
||
<p>The micro:bit core crate implements the embedded_hal::serial::Write and embedded_hal::serial::Read traits for the tx and rx pins respectively.</p>
|
||
<a class="header" href="#writeln-and-carriage-return" id="writeln-and-carriage-return"><h2><code>writeln!</code> and Carriage Return</h2></a>
|
||
<p>In the introduction page on serial communication, I brushed over this:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
// Write string with newline and carriage return
|
||
let _ = write!(tx, "serial test\r\n");
|
||
#}</code></pre></pre>
|
||
<p>A naïve assumption would be to try the seemingly more correct <code>writeln!</code> macro:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
// Write string with newline and carriage return
|
||
let _ = writeln!(tx, "serial test");
|
||
#}</code></pre></pre>
|
||
<p>This will usually fail to do what is intended,
|
||
as multiple writes will only print one line in PuTTY,
|
||
and produce the following in minicom:</p>
|
||
<pre><code>serial test
|
||
serial test
|
||
serial test
|
||
serial test
|
||
</code></pre>
|
||
<p>Your choices are to either configure minicom and PuTTY appropriately or use <code>write!</code> with <code>\r\n</code>.</p>
|
||
<a class="header" href="#control-characters" id="control-characters"><h3>Control Characters</h3></a>
|
||
<p>The control characters operate based on a print head, as used in <a href="https://en.wikipedia.org/wiki/Teleprinter">teleprinters</a>.</p>
|
||
<p><code>\r</code> - Carriage Return - The print head is moves left to the start of the line.
|
||
<code>\n</code> - Line Feed - The print head moves down once to a new line.</p>
|
||
<a class="header" href="#writeln-macro" id="writeln-macro"><h3><code>writeln!</code> macro</h3></a>
|
||
<p>The <code>writeln!</code> macro should append a new line,
|
||
but he <a href="https://doc.rust-lang.org/core/macro.writeln.html">documentation for core::writeln</a> says:</p>
|
||
<blockquote>
|
||
<p>On all platforms, the newline is the LINE FEED character (\n/U+000A) alone (no additional CARRIAGE RETURN (\r/U+000D).</p>
|
||
</blockquote>
|
||
<a class="header" href="#minicom-1" id="minicom-1"><h3>minicom</h3></a>
|
||
<p>CTRL-A + Z will tell you that CTRL-A + U will add a carriage return.
|
||
This will add a carriage return to a received <code>\n</code></p>
|
||
<a class="header" href="#putty-1" id="putty-1"><h3>PuTTY</h3></a>
|
||
<p>In PuTTY, you can enable enable <code>Implicit LF in every CR</code> under Terminal options.</p>
|
||
<a class="header" href="#blocking" id="blocking"><h2>Blocking</h2></a>
|
||
<p>Behind the scenes, <code>embedded_hal::serial</code> uses the nb crate to allow for blocking and non-blocking operation.
|
||
This is implemented in embedded_hal crates by returning nb::Error::WouldBlock
|
||
when a read or write action cannot be performed immediately.
|
||
In this chapter, we will only be using read and write as simple blocking calls.</p>
|
||
<a class="header" href="#block" id="block"><h3><code>block!</code></h3></a>
|
||
<p>The <code>block!</code> macro provided by the crate continuously calls the expression
|
||
contained until it no longer returns Error::WouldBlock.</p>
|
||
<a class="header" href="#tx---embedded_halserialwrite-or-corefmtwrite" id="tx---embedded_halserialwrite-or-corefmtwrite"><h2>Tx - <code>embedded_hal::serial::Write</code> or <code>core::fmt::Write</code></h2></a>
|
||
<p>The <code>write!</code> and <code>writeln!</code> macros call <code>write_str</code> of the <code>core::fmt::Write</code> trait which is implemented for Tx.
|
||
<code>write_str</code> is implemented as a blocking call to <code>write</code> of the <code>embedded_hal::serial::Write</code> trait.</p>
|
||
<p>This means <code>write!(tx, "a")</code> is equivalent to <code>block!(tx.write(b'a'))</code>.</p>
|
||
<a class="header" href="#echo-solution" id="echo-solution"><h1>Echo Solution</h1></a>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate panic_semihosting;
|
||
extern crate cortex_m_rt as rt;
|
||
extern crate cortex_m_semihosting as sh;
|
||
extern crate microbit;
|
||
|
||
use core::fmt::Write;
|
||
use rt::entry;
|
||
use sh::hio;
|
||
|
||
use microbit::hal::prelude::*;
|
||
use microbit::hal::serial;
|
||
use microbit::hal::serial::BAUD115200;
|
||
use microbit::nb::block;
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
let mut stdout = hio::hstdout().unwrap();
|
||
writeln!(stdout, "Start").unwrap();
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
// Split GPIO
|
||
let mut gpio = p.GPIO.split();
|
||
// Configure RX and TX pins accordingly
|
||
let tx = gpio.pin24.into_push_pull_output().downgrade();
|
||
let rx = gpio.pin25.into_floating_input().downgrade();
|
||
// Configure serial communication
|
||
let (mut tx, mut rx) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
|
||
writeln!(tx, "Start");
|
||
loop {
|
||
let val = block!(rx.read()).unwrap();
|
||
block!(tx.write(val));
|
||
}
|
||
}
|
||
panic!("End");
|
||
}
|
||
|
||
</code></pre></pre>
|
||
<a class="header" href="#exercises" id="exercises"><h1>Exercises</h1></a>
|
||
<ul>
|
||
<li>Reverse echo a line of input</li>
|
||
<li>Numerical countdown</li>
|
||
<li>Display echo</li>
|
||
<li>Quiz game</li>
|
||
</ul>
|
||
<a class="header" href="#reverse-echo" id="reverse-echo"><h1>Reverse Echo</h1></a>
|
||
<p>The micro:bit should buffer characters it receives until <code>\n</code> or <code>\r</code> is received
|
||
(the enter key is pressed on the host computer).
|
||
The characters should then be printed back in reverse order to the host computer.
|
||
The characters may also be echoed like earlier to see what is being typed.</p>
|
||
<a class="header" href="#flow-1" id="flow-1"><h2>Flow</h2></a>
|
||
<ol>
|
||
<li>Letter <code>a</code> is typed and transmitted to the micro:bit</li>
|
||
<li>(optional) The micro:bit retransmits the letter <code>a</code> (echo)</li>
|
||
<li>Letter <code>b</code> is typed and transmitted to the micro:bit</li>
|
||
<li>(optional) The micro:bit retransmits the letter <code>b</code> (echo)</li>
|
||
<li>Enter key is pressed and <code>\r</code> is transmitted to the micro:bit</li>
|
||
<li>Letters <code>ba</code> are transmitted from the micro:bit</li>
|
||
</ol>
|
||
<a class="header" href="#useful-crates" id="useful-crates"><h2>Useful crates</h2></a>
|
||
<ul>
|
||
<li><a href="https://docs.rs/heapless">Heapless</a></li>
|
||
</ul>
|
||
<a class="header" href="#solution-2" id="solution-2"><h1>Solution</h1></a>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate panic_semihosting;
|
||
extern crate cortex_m_rt as rt;
|
||
extern crate cortex_m_semihosting as sh;
|
||
extern crate heapless;
|
||
extern crate microbit;
|
||
|
||
use core::fmt::Write;
|
||
use rt::entry;
|
||
use sh::hio;
|
||
use heapless::{consts, Vec};
|
||
|
||
use microbit::hal::prelude::*;
|
||
use microbit::hal::delay::Delay;
|
||
use microbit::hal::serial;
|
||
use microbit::hal::serial::BAUD115200;
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
let mut stdout = hio::hstdout().unwrap();
|
||
writeln!(stdout, "Start").unwrap();
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
// Split GPIO
|
||
let mut gpio = p.GPIO.split();
|
||
// Create delay provider
|
||
let mut delay = Delay::new(p.TIMER0);
|
||
// Configure RX and TX pins accordingly
|
||
let tx = gpio.pin24.into_push_pull_output().downgrade();
|
||
let rx = gpio.pin25.into_floating_input().downgrade();
|
||
// Configure serial communication
|
||
let (mut tx, mut rx) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
|
||
// A buffer with 32 bytes of capacity
|
||
let mut buffer: Vec<u8, consts::U32> = Vec::new();
|
||
writeln!(tx, "Start");
|
||
loop {
|
||
loop {
|
||
// Read
|
||
let byte = block!(rx.read()).unwrap();
|
||
// Echo
|
||
block!(tx.write(byte));
|
||
// Carriage return
|
||
if byte == b'\r' {
|
||
break;
|
||
}
|
||
// Push to buffer
|
||
if buffer.push(byte).is_err() {
|
||
// Buffer full
|
||
writeln!(tx, "\r\nWarning: buffer full, dumping buffer");
|
||
break;
|
||
}
|
||
}
|
||
// Uncomment to not overwrite input string
|
||
//writeln!(tx, "");
|
||
// Respond
|
||
for b in buffer.iter().rev() {
|
||
block!(tx.write(*b));
|
||
}
|
||
writeln!(tx, "");
|
||
buffer.clear();
|
||
}
|
||
}
|
||
panic!("End");
|
||
}
|
||
|
||
</code></pre></pre>
|
||
<p>I have used an implementation of a vector on the stack, provided by the heapless crate.
|
||
After 32 characters (a char is a u8 byte) the heapless vector is full, and an error is shown.</p>
|
||
<a class="header" href="#countdown" id="countdown"><h1>Countdown</h1></a>
|
||
<p>You should be able to type a number greater than 0, press enter,
|
||
and the micro:bit will return a countdown.</p>
|
||
<pre><code>5
|
||
4
|
||
3
|
||
2
|
||
1
|
||
</code></pre>
|
||
<p>Feel free to add your own surprise at the end of the countdown</p>
|
||
<a class="header" href="#useful-crates-1" id="useful-crates-1"><h2>Useful crates</h2></a>
|
||
<ul>
|
||
<li><a href="https://docs.rs/heapless">Heapless</a></li>
|
||
</ul>
|
||
<a class="header" href="#solution-3" id="solution-3"><h1>Solution</h1></a>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate panic_semihosting;
|
||
extern crate cortex_m_rt as rt;
|
||
extern crate cortex_m_semihosting as sh;
|
||
extern crate heapless;
|
||
extern crate microbit;
|
||
|
||
use core::fmt::Write;
|
||
use rt::entry;
|
||
use sh::hio;
|
||
use heapless::{consts, Vec, String};
|
||
|
||
use microbit::hal::prelude::*;
|
||
use microbit::hal::delay::Delay;
|
||
use microbit::hal::serial;
|
||
use microbit::hal::serial::BAUD115200;
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
let mut stdout = hio::hstdout().unwrap();
|
||
writeln!(stdout, "Start").unwrap();
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
// Split GPIO
|
||
let mut gpio = p.GPIO.split();
|
||
// Create delay provider
|
||
let mut delay = Delay::new(p.TIMER0);
|
||
// Configure RX and TX pins accordingly
|
||
let tx = gpio.pin24.into_push_pull_output().downgrade();
|
||
let rx = gpio.pin25.into_floating_input().downgrade();
|
||
// Configure serial communication
|
||
let (mut tx, mut rx) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
|
||
writeln!(tx, "Start");
|
||
loop {
|
||
// A buffer with 32 bytes of capacity
|
||
let mut buffer: Vec<u8, consts::U32> = Vec::new();
|
||
loop {
|
||
// Read
|
||
let byte = block!(rx.read()).unwrap();
|
||
// Echo
|
||
block!(tx.write(byte));
|
||
// Carriage return
|
||
if byte == b'\r' {
|
||
break;
|
||
}
|
||
// Push to buffer
|
||
if buffer.push(byte).is_err() {
|
||
// Buffer full
|
||
writeln!(tx, "\r\nWarning: buffer full, dumping buffer");
|
||
break;
|
||
}
|
||
}
|
||
// Buffer to string
|
||
let buf_str = String::from_utf8(buffer).unwrap();
|
||
writeln!(tx, "");
|
||
match buf_str.parse() {
|
||
// Transmit countdown
|
||
Ok(buf_int) => {
|
||
for i in (1..buf_int).rev() {
|
||
delay.delay_ms(1000_u32);
|
||
writeln!(tx, "{}", i);
|
||
}
|
||
// Add post countdown effects here
|
||
},
|
||
// Transmit parse error
|
||
Err(e) => writeln!(tx, "{:?}", e).unwrap(),
|
||
}
|
||
}
|
||
}
|
||
panic!("End");
|
||
}
|
||
|
||
</code></pre></pre>
|
||
<a class="header" href="#quiz" id="quiz"><h1>Quiz</h1></a>
|
||
<a class="header" href="#solution-4" id="solution-4"><h1>Solution</h1></a>
|
||
<a class="header" href="#wip---led-display" id="wip---led-display"><h1>WIP - LED display</h1></a>
|
||
<p>This chapters follows on from the basic LED example from getting started.
|
||
The LEDs are wired up in a matrix, as described in <a href="https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf">these schematics</a>.
|
||
Only a small proportion of images can be displayed at once with this layout;
|
||
it is impossible for example to turn on both LED1 and LED11 without also having LED2 and LED10 turning on.</p>
|
||
<p>In this chapter we will be talking through how to achieve the impossible,
|
||
displaying any chosen image, and even adjusting the brightness!</p>
|
||
<a class="header" href="#theory" id="theory"><h1>Theory</h1></a>
|
||
<a class="header" href="#led-dot-matrix-display" id="led-dot-matrix-display"><h2>LED dot matrix display</h2></a>
|
||
<p>A dot matrix display is a display containing a two dimensional array of points,
|
||
used to represent characters, symbols, or images.
|
||
All displays today are dot matrix displays, but vector displays also used to exist,
|
||
examples include radar displays and 1980s arcade games.</p>
|
||
<a class="header" href="#persistence-of-vision" id="persistence-of-vision"><h2>Persistence of vision</h2></a>
|
||
<p>In order to achieve full control of all LEDs we need to use an optical illusion called the <a href="https://en.wikipedia.org/wiki/Persistence_of_vision">persistence of vision</a>.
|
||
This effect causes light which has ceased entering the eye to still be seen for a little while after it has disappeared.</p>
|
||
<a class="header" href="#multiplexing" id="multiplexing"><h2>Multiplexing</h2></a>
|
||
<p>Multiplexing is the combination of multiple signals over shared medium.
|
||
In this case, we will be using human vision to multiplex time and space divided signals</p>
|
||
<a class="header" href="#method" id="method"><h2>Method</h2></a>
|
||
<p>We are able to fully control one (circuit) row of LEDs at a time,
|
||
so by quickly looping through the rows lof LEDs,
|
||
we can let the brain blur them together into a complete image.</p>
|
||
<a class="header" href="#problem-statement" id="problem-statement"><h1>Problem statement</h1></a>
|
||
<blockquote>
|
||
<p>Display any given image on the micro:bit display.
|
||
The image will be a 5x5 array, where 0 will represent off and 1 will represent on.</p>
|
||
</blockquote>
|
||
<p>The following, for example, should display a heart:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
let heart = [
|
||
[0, 1, 0, 1, 0],
|
||
[1, 0, 1, 0, 1],
|
||
[1, 0, 0, 0, 1],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 0, 1, 0, 0],
|
||
];
|
||
#}</code></pre></pre>
|
||
<p>At this point, you may know enough to solve the problem yourself.
|
||
If you want to jump straight to the solution, you can go to the end of the next section in this chapter.</p>
|
||
<p>This section will continue formally breaking this problem down into smaller and more tractable pieces.</p>
|
||
<a class="header" href="#led-layout" id="led-layout"><h2>LED layout</h2></a>
|
||
<blockquote>
|
||
<p>Convert a 5x5 array into a 3x9 array to match the display's circuitry.</p>
|
||
</blockquote>
|
||
<a class="header" href="#schematics" id="schematics"><h3>Schematics</h3></a>
|
||
<p>The <a href="https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf">schem1</a> discussed earlier describe the electrical layout of the LEDs,
|
||
but they do not describe how it relates to the visual layout.
|
||
It would be a mistake to assume that the numbers 1 to 25 have any correlation
|
||
to the visual layout of the LEDs on the micro:bit as they do <em>NOT</em>.
|
||
It just happened to be that ROW0 and COL0 intersect at an LED in the top left corner.</p>
|
||
<a class="header" href="#reference-design" id="reference-design"><h3>Reference design</h3></a>
|
||
<p>To find the relationship between the electrical array and visual array,
|
||
we need to look at the reference design for the micro:bit.
|
||
This can be found through a link at the bottom of the <a href="http://tech.microbit.org/hardware/#links">micro:bit hardware page</a></p>
|
||
<p>By navigating to the github page > PDF > Schematic Print,
|
||
you can find a <a href="https://github.com/microbit-foundation/microbit-reference-design/blob/master/PDF/Schematic%20Print/Schematic%20Prints.PDF">detailed electrical schematic for the micro:bit</a>.</p>
|
||
<p>In the top right, you will see an array which can be defined in Rust as follows:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
const LED_LAYOUT: [[(usize, usize); 5]; 5] = [
|
||
[(0, 0), (1, 3), (0, 1), (1, 4), (0, 2)],
|
||
[(2, 3), (2, 4), (2, 5), (2, 6), (2, 7)],
|
||
[(1, 1), (0, 8), (1, 2), (2, 8), (1, 0)],
|
||
[(0, 7), (0, 6), (0, 5), (0, 4), (0, 3)],
|
||
[(2, 2), (1, 6), (2, 0), (1, 5), (2, 1)],
|
||
];
|
||
#}</code></pre></pre>
|
||
<a class="header" href="#delays-1" id="delays-1"><h2>Delays</h2></a>
|
||
<blockquote>
|
||
<p>Create a time delay.</p>
|
||
</blockquote>
|
||
<p>Another piece of information you will need is how to create a time delay before moving to the next row.
|
||
we want the time spent switching LED lines on and off to be much shorter than the time spent waiting with LEDs on.</p>
|
||
<a class="header" href="#for-loop" id="for-loop"><h3>For loop</h3></a>
|
||
<p>A first attempt to implement the <code>delay</code> function
|
||
without using any peripherals is to implement it as a <code>for</code> loop delay:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
fn delay(ms: u16) {
|
||
const K: u16 = 16_000; // 16MHz microprocessor, needs to be tweaked
|
||
for _ in 0..(K*ms) {}
|
||
}
|
||
#}</code></pre></pre>
|
||
<p>When compiled in release mode however, this is optimized away.
|
||
To solve this we could explicitly add an operation inside the loop.
|
||
The perfect candidate is the <a href="https://en.wikipedia.org/wiki/NOP">NOP</a>.</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
fn delay(ms: u16) {
|
||
const K: u16 = 16_000; // 16MHz microprocessor, needs to be tweaked
|
||
for _ in 0..(K*ms) {
|
||
cortex_m::asm::nop();
|
||
}
|
||
}
|
||
#}</code></pre></pre>
|
||
<a class="header" href="#timers" id="timers"><h3>Timers</h3></a>
|
||
<p>A better way of implementing delays is by using timers.
|
||
A one-shot timer (also called one pulse mode) works like an alarm clock.
|
||
You set it once with the amount of time you want, and then wait until it goes off.
|
||
Fortuinately for us, HAL crates usually have already solved this for us.</p>
|
||
<a class="header" href="#microbit" id="microbit"><h3>Microbit</h3></a>
|
||
<p>The microbit has 3 timers, we will use the first: TIMER0.
|
||
To use it, do the following:</p>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
let mut delay = Delay::new(p.TIMER0);
|
||
delay.delay_ms(1000_u32);
|
||
}
|
||
#}</code></pre></pre>
|
||
<a class="header" href="#multiplexing-1" id="multiplexing-1"><h2>Multiplexing</h2></a>
|
||
<blockquote>
|
||
<p>Multiplex the rows of the matrix</p>
|
||
</blockquote>
|
||
<p>The final task is to multiplex the rows of electrical matrix into a full image.
|
||
We will be doing this by scanning through the rows in the display.</p>
|
||
<a class="header" href="#pseudocode" id="pseudocode"><h3>Pseudocode</h3></a>
|
||
<p>In order to light up an LED,
|
||
the row needs to be set high and the column needs to be set low.
|
||
We will assume that at the start of a refresh cycle,
|
||
that all the rows are set low and all the columns are set high.
|
||
The order of operations during a refresh cycle is then, for each row:</p>
|
||
<ol>
|
||
<li>set the row high</li>
|
||
<li>for each column
|
||
<ul>
|
||
<li>set low if the LED associated with that row-column pair should be on</li>
|
||
</ul>
|
||
</li>
|
||
<li>sleep for a known duration, you should find 2ms is sufficient</li>
|
||
<li>for each column
|
||
<ul>
|
||
<li>set high</li>
|
||
</ul>
|
||
</li>
|
||
<li>set the row low</li>
|
||
</ol>
|
||
<a class="header" href="#solution-5" id="solution-5"><h1>Solution</h1></a>
|
||
<p>This solution describes a blocking display driver.</p>
|
||
<a class="header" href="#layout" id="layout"><h1>Layout</h1></a>
|
||
<blockquote>
|
||
<p>Convert a 5x5 array into a 3x9 array to match the display's circuitry.</p>
|
||
</blockquote>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
const LED_LAYOUT: [[(usize, usize); 5]; 5] = [
|
||
[(0, 0), (1, 3), (0, 1), (1, 4), (0, 2)],
|
||
[(2, 3), (2, 4), (2, 5), (2, 6), (2, 7)],
|
||
[(1, 1), (0, 8), (1, 2), (2, 8), (1, 0)],
|
||
[(0, 7), (0, 6), (0, 5), (0, 4), (0, 3)],
|
||
[(2, 2), (1, 6), (2, 0), (1, 5), (2, 1)],
|
||
];
|
||
|
||
/// Convert 5x5 display image to 3x9 matrix image
|
||
pub fn display2matrix(led_display: [[u8; 5]; 5]) -> [[u8; 9]; 3] {
|
||
// Create 3x9 array
|
||
let mut led_matrix: [[u8; 9]; 3] = [[0; 9]; 3];
|
||
// Iterate through zip of input array and layout array
|
||
for (led_display_row, layout_row) in led_display.iter().zip(LED_LAYOUT.iter()) {
|
||
// Continue iterating through rows
|
||
for (led_display_val, layout_loc) in led_display_row.iter().zip(layout_row) {
|
||
// Assign dereferenced val to array
|
||
led_matrix[layout_loc.0][layout_loc.1] = *led_display_val;
|
||
}
|
||
}
|
||
return led_matrix;
|
||
}
|
||
#}</code></pre></pre>
|
||
<a class="header" href="#multiplexing-2" id="multiplexing-2"><h1>Multiplexing</h1></a>
|
||
<blockquote>
|
||
<p>Multiplex the rows of the matrix</p>
|
||
</blockquote>
|
||
<pre><pre class="playpen"><code class="language-rust">
|
||
# #![allow(unused_variables)]
|
||
#fn main() {
|
||
/// Display 3x9 matrix image for a given duration.
|
||
pub fn display_pre(&mut self, delay: &mut Delay, led_matrix: [[u8; 9]; 3], duration_ms: u32) {
|
||
// TODO
|
||
// These need to be populated with PINs, e.g.:
|
||
let rows = [PIN; 3];
|
||
let cols = [PIN; 9];
|
||
// Set refresh rate.
|
||
let delay_ms = 2;
|
||
// Calculate number of loops.
|
||
let loops = duration_ms / (rows.len() as u32 * delay_ms);
|
||
for _ in 0..loops {
|
||
for (row_line, led_matrix_row) in rows.iter_mut().zip(led_matrix.iter()) {
|
||
// Set the row high.
|
||
row_line.set_high();
|
||
// Set the correct pins low (on)
|
||
// This could lead to very small differences in execution time,
|
||
// but this is not worth correcting for, as it is << 2ms.
|
||
for (col_line, led_matrix_val) in cols.iter_mut().zip(led_matrix_row.iter()) {
|
||
// We ignore any brightness setting, just use 0 and 1.
|
||
if *led_matrix_val > 0 {
|
||
col_line.set_low();
|
||
}
|
||
}
|
||
delay.delay_ms(delay_ms);
|
||
// It is not worth the logic to check which pins need resetting,
|
||
// so set all the pins back high.
|
||
for col_line in &mut cols {
|
||
col_line.set_high();
|
||
}
|
||
// Set the row back low.
|
||
row_line.set_low();
|
||
}
|
||
}
|
||
}
|
||
#}</code></pre></pre>
|
||
<a class="header" href="#full-solution" id="full-solution"><h1>Full Solution</h1></a>
|
||
<p>For the most modern implementations,
|
||
please look at the code in the <a href="https://github.com/therealprof/microbit">micro:bit crate</a>.</p>
|
||
<pre><pre class="playpen"><code class="language-rust">#![no_std]
|
||
#![no_main]
|
||
|
||
extern crate panic_semihosting;
|
||
extern crate cortex_m_rt as rt;
|
||
extern crate cortex_m_semihosting as sh;
|
||
extern crate microbit;
|
||
|
||
use core::fmt::Write;
|
||
use rt::entry;
|
||
use sh::hio;
|
||
|
||
use microbit::hal::delay::Delay;
|
||
use microbit::hal::gpio::gpio::PIN;
|
||
use microbit::hal::gpio::gpio::{PIN4, PIN5, PIN6, PIN7, PIN8, PIN9, PIN10, PIN11, PIN12, PIN13, PIN14, PIN15};
|
||
use microbit::hal::gpio::{Output, PushPull};
|
||
use microbit::hal::serial;
|
||
use microbit::hal::serial::BAUD115200;
|
||
use microbit::hal::prelude::*;
|
||
|
||
type LED = PIN<Output<PushPull>>;
|
||
|
||
const DEFAULT_DELAY_MS: u32 = 2;
|
||
const LED_LAYOUT: [[(usize, usize); 5]; 5] = [
|
||
[(0, 0), (1, 3), (0, 1), (1, 4), (0, 2)],
|
||
[(2, 3), (2, 4), (2, 5), (2, 6), (2, 7)],
|
||
[(1, 1), (0, 8), (1, 2), (2, 8), (1, 0)],
|
||
[(0, 7), (0, 6), (0, 5), (0, 4), (0, 3)],
|
||
[(2, 2), (1, 6), (2, 0), (1, 5), (2, 1)],
|
||
];
|
||
|
||
/// Array of all the LEDs in the 5x5 display on the board
|
||
pub struct Display {
|
||
delay_ms: u32,
|
||
rows: [LED; 3],
|
||
cols: [LED; 9],
|
||
}
|
||
|
||
impl Display {
|
||
/// Initializes all the user LEDs
|
||
pub fn new(
|
||
col1: PIN4<Output<PushPull>>,
|
||
col2: PIN5<Output<PushPull>>,
|
||
col3: PIN6<Output<PushPull>>,
|
||
col4: PIN7<Output<PushPull>>,
|
||
col5: PIN8<Output<PushPull>>,
|
||
col6: PIN9<Output<PushPull>>,
|
||
col7: PIN10<Output<PushPull>>,
|
||
col8: PIN11<Output<PushPull>>,
|
||
col9: PIN12<Output<PushPull>>,
|
||
row1: PIN13<Output<PushPull>>,
|
||
row2: PIN14<Output<PushPull>>,
|
||
row3: PIN15<Output<PushPull>>,
|
||
) -> Self {
|
||
let mut retval = Display {
|
||
delay_ms: DEFAULT_DELAY_MS,
|
||
rows: [row1.downgrade(), row2.downgrade(), row3.downgrade()],
|
||
cols: [
|
||
col1.downgrade(), col2.downgrade(), col3.downgrade(),
|
||
col4.downgrade(), col5.downgrade(), col6.downgrade(),
|
||
col7.downgrade(), col8.downgrade(), col9.downgrade()
|
||
],
|
||
};
|
||
// This is needed to reduce flickering on reset
|
||
retval.clear();
|
||
retval
|
||
}
|
||
|
||
/// Clear display
|
||
pub fn clear(&mut self) {
|
||
for row in &mut self.rows {
|
||
row.set_low();
|
||
}
|
||
for col in &mut self.cols {
|
||
col.set_high();
|
||
}
|
||
}
|
||
|
||
/// Convert 5x5 display image to 3x9 matrix image
|
||
pub fn display2matrix(led_display: [[u8; 5]; 5]) -> [[u8; 9]; 3] {
|
||
let mut led_matrix: [[u8; 9]; 3] = [[0; 9]; 3];
|
||
for (led_display_row, layout_row) in led_display.iter().zip(LED_LAYOUT.iter()) {
|
||
for (led_display_val, layout_loc) in led_display_row.iter().zip(layout_row) {
|
||
led_matrix[layout_loc.0][layout_loc.1] = *led_display_val;
|
||
}
|
||
}
|
||
led_matrix
|
||
}
|
||
|
||
/// Display 5x5 display image for a given duration
|
||
pub fn display(&mut self, delay: &mut Delay, led_display: [[u8; 5]; 5], duration_ms: u32) {
|
||
let led_matrix = Display::display2matrix(led_display);
|
||
// Calculates how long to block for
|
||
// e.g. If the duration_ms is 500ms (half a second)
|
||
// and self.delay_ms is 2ms (about 2ms per scan row),
|
||
// each refresh takes 3rows×2ms, so we need 500ms / (3×2ms) loops.
|
||
let loops = duration_ms / (self.rows.len() as u32 * self.delay_ms);
|
||
for _ in 0..loops {
|
||
for (row_line, led_matrix_row) in self.rows.iter_mut().zip(led_matrix.iter()) {
|
||
row_line.set_high();
|
||
for (col_line, led_matrix_val) in self.cols.iter_mut().zip(led_matrix_row.iter()) {
|
||
// We are keeping it simple, and not adding brightness
|
||
if *led_matrix_val > 0 {
|
||
col_line.set_low();
|
||
}
|
||
}
|
||
delay.delay_ms(self.delay_ms);
|
||
for col_line in &mut self.cols {
|
||
col_line.set_high();
|
||
}
|
||
row_line.set_low();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#[entry]
|
||
fn main() -> ! {
|
||
let mut stdout = hio::hstdout().unwrap();
|
||
writeln!(stdout, "Start").unwrap();
|
||
if let Some(p) = microbit::Peripherals::take() {
|
||
// Split GPIO
|
||
let mut gpio = p.GPIO.split();
|
||
|
||
// Configure RX and TX pins accordingly
|
||
let tx = gpio.pin24.into_push_pull_output().downgrade();
|
||
let rx = gpio.pin25.into_floating_input().downgrade();
|
||
// Configure serial communication
|
||
let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
|
||
writeln!(tx, "");
|
||
writeln!(tx, "Init");
|
||
|
||
// Create delay provider
|
||
let mut delay = Delay::new(p.TIMER0);
|
||
|
||
// Display pins
|
||
let row1 = gpio.pin13.into_push_pull_output();
|
||
let row2 = gpio.pin14.into_push_pull_output();
|
||
let row3 = gpio.pin15.into_push_pull_output();
|
||
let col1 = gpio.pin4.into_push_pull_output();
|
||
let col2 = gpio.pin5.into_push_pull_output();
|
||
let col3 = gpio.pin6.into_push_pull_output();
|
||
let col4 = gpio.pin7.into_push_pull_output();
|
||
let col5 = gpio.pin8.into_push_pull_output();
|
||
let col6 = gpio.pin9.into_push_pull_output();
|
||
let col7 = gpio.pin10.into_push_pull_output();
|
||
let col8 = gpio.pin11.into_push_pull_output();
|
||
let col9 = gpio.pin12.into_push_pull_output();
|
||
let mut leds = Display::new(
|
||
col1, col2, col3,
|
||
col4, col5, col6,
|
||
col7, col8, col9,
|
||
row1, row2, row3,
|
||
);
|
||
|
||
#[allow(non_snake_case)]
|
||
let letter_I = [
|
||
[0, 1, 1, 1, 0],
|
||
[0, 0, 1, 0, 0],
|
||
[0, 0, 1, 0, 0],
|
||
[0, 0, 1, 0, 0],
|
||
[0, 1, 1, 1, 0],
|
||
];
|
||
|
||
let heart = [
|
||
[0, 1, 0, 1, 0],
|
||
[1, 0, 1, 0, 1],
|
||
[1, 0, 0, 0, 1],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 0, 1, 0, 0],
|
||
];
|
||
|
||
#[allow(non_snake_case)]
|
||
let letter_U = [
|
||
[0, 1, 0, 1, 0],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 1, 0, 1, 0],
|
||
[0, 1, 1, 1, 0],
|
||
];
|
||
|
||
writeln!(tx, "Starting!");
|
||
|
||
loop {
|
||
writeln!(tx, "I <3 Rust on the micro:bit!");
|
||
leds.display(&mut delay, letter_I, 1000);
|
||
leds.display(&mut delay, heart, 1000);
|
||
leds.display(&mut delay, letter_U, 1000);
|
||
leds.clear();
|
||
delay.delay_ms(250_u32);
|
||
}
|
||
|
||
}
|
||
panic!("End");
|
||
|
||
}
|
||
|
||
</code></pre></pre>
|
||
<a class="header" href="#wip---sensors-and-i²c" id="wip---sensors-and-i²c"><h1>WIP - Sensors and I²C</h1></a>
|
||
<a class="header" href="#wip---interrupts" id="wip---interrupts"><h1>WIP - Interrupts</h1></a>
|
||
<a class="header" href="#wip---interrupts-1" id="wip---interrupts-1"><h1>WIP - Interrupts</h1></a>
|
||
<a class="header" href="#wip---real-time" id="wip---real-time"><h1>WIP - Real time</h1></a>
|
||
<a class="header" href="#wip---creating-a-hal" id="wip---creating-a-hal"><h1>WIP - Creating a HAL</h1></a>
|
||
<a class="header" href="#whats-left-for-you-to-explore" id="whats-left-for-you-to-explore"><h1>What's left for you to explore</h1></a>
|
||
<p>We have barely scratched the surface! There's lots of stuff left for you to explore:</p>
|
||
<a class="header" href="#multitasking" id="multitasking"><h2>Multitasking</h2></a>
|
||
<p>All our programs executed a single task. How could we achieve multitasking in a system with no OS,
|
||
and thus no threads. There are two main approaches to multitasking: preemptive multitasking and
|
||
cooperative multitasking.</p>
|
||
<p>In preemptive multitasking a task that's currently being executed can, at any point in time, be
|
||
<em>preempted</em> (interrupted) by another task. On preemption, the first task will be suspended and the
|
||
processor will instead execute the second task. At some point the first task will be resumed.
|
||
Microcontrollers provide hardware support for preemption in the form of <em>interrupts</em>.</p>
|
||
<p>In cooperative multitasking a task that's being executed will run until it reaches a <em>suspension
|
||
point</em>. When the processor reaches that suspension point it will stop executing the current task and
|
||
instead go and execute a different task. At some point the first task will be resumed. The main
|
||
difference between these two approaches to multitasking is that in cooperative multitasking <em>yields</em>
|
||
execution control at <em>known</em> suspension points instead of being forcefully preempted at any point of
|
||
its execution.</p>
|
||
<a class="header" href="#direct-memory-access-dma" id="direct-memory-access-dma"><h2>Direct Memory Access (DMA).</h2></a>
|
||
<p>This peripheral is a kind of <em>asynchronous</em> <code>memcpy</code>. So far our programs have
|
||
been pumping data, byte by byte, into peripherals like UART and I2C. This DMA
|
||
peripheral can be used to perform bulk transfers of data. Either from RAM to
|
||
RAM, from a peripheral, like a UART, to RAM or from RAM to a peripheral. You can
|
||
schedule a DMA transfer, like read 256 bytes from USART1 into this buffer, leave
|
||
it running in the background and then poll some register to see if it has
|
||
completed so you can do other stuff while the transfer is ongoing.</p>
|
||
<a class="header" href="#sleeping" id="sleeping"><h2>Sleeping</h2></a>
|
||
<p>All our programs have been continuously polling peripherals to see if there's
|
||
anything that needs to be done. However, some times there's nothing to be done!
|
||
At those times, the microcontroller should "sleep".</p>
|
||
<p>When the processor sleeps, it stops executing instructions and this saves power.
|
||
It's almost always a good idea to save power so your microcontroller should be
|
||
sleeping as much as possible. But, how does it know when it has to wake up to
|
||
perform some action? "Interrupts" are one of the events that wake up the
|
||
microcontroller but there are others and the <code>wfi</code> and <code>wfe</code> are the
|
||
instructions that make the processor "sleep".</p>
|
||
<a class="header" href="#pulse-width-modulation-pwm" id="pulse-width-modulation-pwm"><h2>Pulse Width Modulation (PWM)</h2></a>
|
||
<p>In a nutshell, PWM is turning on something and then turning it off periodically
|
||
while keeping some proportion ("duty cycle") between the "on time" and the "off
|
||
time". When used on a LED with a sufficiently high frequency, this can be used
|
||
to dim the LED. A low duty cycle, say 10% on time and 90% off time, will make
|
||
the LED very dim wheres a high duty cycle, say 90% on time and 10% off time,
|
||
will make the LED much brighter (almost as if it were fully powered).</p>
|
||
<p>In general, PWM can be used to control how much <em>power</em> is given to some
|
||
electric device. With proper (power) electronics between a microcontroller and
|
||
an electrical motor, PWM can be used to control how much power is given to the
|
||
motor thus it can be used to control its torque and speed. Then you can add an
|
||
angular position sensor and you got yourself a closed loop controller that can
|
||
control the position of the motor at different loads.</p>
|
||
<a class="header" href="#digital-input" id="digital-input"><h2>Digital input</h2></a>
|
||
<p>We have used the microcontroller pins as digital outputs, to drive LEDs. But
|
||
these pins can also be configured as digital inputs. As digital inputs, these
|
||
pins can read the binary state of switches (on/off) or buttons (pressed/not
|
||
pressed).</p>
|
||
<p>(<em>spoilers</em> reading the binary state of switches / buttons is not as
|
||
straightforward as it sounds ;-)</p>
|
||
<a class="header" href="#sensor-fusion" id="sensor-fusion"><h2>Sensor fusion</h2></a>
|
||
<p>The STM32F3DISCOVERY contains three motion sensors: an accelerometer, a
|
||
gyroscope and a magnetometer. On their own these measure: (proper) acceleration,
|
||
angular speed and (the Earth's) magnetic field. But these magnitudes can be
|
||
"fused" into something more useful: a "robust" measurement of the orientation of
|
||
the board. Where robust means with less measurement error than a single sensor
|
||
would be capable of.</p>
|
||
<p>This idea of deriving more reliable data from different sources is known as
|
||
sensor fusion.</p>
|
||
<a class="header" href="#analog-to-digital-converters-adc" id="analog-to-digital-converters-adc"><h2>Analog-to-Digital Converters (ADC)</h2></a>
|
||
<p>There are a lots of digital sensors out there. You can use a protocol like I2C
|
||
and SPI to read them. But analog sensors also exist! These sensors just output a
|
||
voltage level that's proportional to the magnitude they are sensing.</p>
|
||
<p>The ADC peripheral can be use to convert that "analog" voltage level, say <code>1.25</code>
|
||
Volts,into a "digital" number, say in the <code>[0, 65535]</code> range, that the processor
|
||
can use in its calculations.</p>
|
||
<a class="header" href="#digital-to-analog-converters-dac" id="digital-to-analog-converters-dac"><h2>Digital-to-Analog Converters (DAC)</h2></a>
|
||
<p>As you might expect a DAC is exactly the opposite of ADC. You can write some
|
||
digital value into a register to produce a voltage in the <code>[0, 3.3V]</code> range
|
||
(assuming a <code>3.3V</code> power supply) on some "analog" pin. When this analog pin is
|
||
connected to some appropriate electronics and the register is written to at some
|
||
constant, fast rate (frequency) with the right values you can produce sounds or
|
||
even music!</p>
|
||
<a class="header" href="#real-time-clock-rtc" id="real-time-clock-rtc"><h2>Real Time Clock (RTC)</h2></a>
|
||
<p>This peripheral can be used to track time in "human format". Seconds, minutes,
|
||
hours, days, months and years. This peripheral handles the translation from
|
||
"ticks" to these human friendly units of time. It even handles leap years and
|
||
Daylight Save Time for you!</p>
|
||
<a class="header" href="#other-communication-protocols" id="other-communication-protocols"><h2>Other communication protocols</h2></a>
|
||
<p>SPI, I2S, SMBUS, CAN, IrDA, Ethernet, USB, Bluetooth, etc.</p>
|
||
<p>Different applications use different communication protocols. User facing
|
||
applications usually have an USB connector because USB is an ubiquitous
|
||
protocol in PCs and smartphones. Whereas inside cars you'll find plenty of CAN
|
||
"buses". Some digital sensors use SPI, others use I2C and others, SMBUS.</p>
|
||
<hr />
|
||
<p>So where to next? There are several options:</p>
|
||
<ul>
|
||
<li>You could check out the examples in the <a href="https://github.com/therealprof/microbit"><code>microbit</code></a> board support crate repository. All those examples work for
|
||
the micro:bit board you have.</li>
|
||
</ul>
|
||
<ul>
|
||
<li>You could try out <a href="https://mobile.twitter.com/japaricious/status/962770003325005824">this motion sensors demo</a>. Details about the implementation and
|
||
source code are available in <a href="http://blog.japaric.io/wd-1-2-l3gd20-lsm303dlhc-madgwick/">this blog post</a>.</li>
|
||
</ul>
|
||
<ul>
|
||
<li>You could check out <a href="https://docs.rs/cortex-m-rtfm">Real Time for The Masses</a>. A very efficient preemptive multitasking framework
|
||
that supports task prioritization and dead lock free execution.</li>
|
||
</ul>
|
||
<ul>
|
||
<li>You could try running Rust on a different development board. The easiest way to get started is to
|
||
use the <a href="https://docs.rs/cortex-m-quickstart/0.2.4/cortex_m_quickstart"><code>cortex-m-quickstart</code></a> Cargo project template.</li>
|
||
</ul>
|
||
<ul>
|
||
<li>You could check out <a href="http://blog.japaric.io/brave-new-io/">this blog post</a> which describes how Rust type system can
|
||
prevent bugs in I/O configuration.</li>
|
||
</ul>
|
||
<ul>
|
||
<li>You could check out my <a href="http://blog.japaric.io">blog</a> for miscellaneous topics about embedded development with Rust.</li>
|
||
</ul>
|
||
<ul>
|
||
<li>You could check out the <a href="https://github.com/japaric/embedded-hal"><code>embedded-hal</code></a> project which aims to build abstractions (traits) for all
|
||
the embedded I/O functionality commonly found on microcontrollers.</li>
|
||
</ul>
|
||
<ul>
|
||
<li>You could join the <a href="https://github.com/rust-lang-nursery/embedded-wg/issues/39">Weekly driver initiative</a> and help us write generic drivers on top of the
|
||
<code>embedded-hal</code> traits and that work for all sorts of platforms (ARM Cortex-M, AVR, MSP430, RISCV,
|
||
etc.)</li>
|
||
</ul>
|
||
<a class="header" href="#gdb-cheatsheet" id="gdb-cheatsheet"><h1>GDB cheatsheet</h1></a>
|
||
<table><thead><tr><th align="left"> Short </th><th align="left"> Command </th><th align="left"> Action </th></tr></thead><tbody>
|
||
<tr><td align="left"> b </td><td align="left"> break [location] </td><td align="left"> Set breakpoint at specified location. </td></tr>
|
||
<tr><td align="left"> c </td><td align="left"> continue </td><td align="left"> Continue program being debugged. </td></tr>
|
||
<tr><td align="left"> s </td><td align="left"> step </td><td align="left"> Step program until it reaches a different source line. </td></tr>
|
||
<tr><td align="left"> si </td><td align="left"> stepi </td><td align="left"> Step one instruction exactly. </td></tr>
|
||
<tr><td align="left"> p </td><td align="left"> print </td><td align="left"> Print value of expression EXP. </td></tr>
|
||
<tr><td align="left"> i lo </td><td align="left"> info locals </td><td align="left"> Local variables of current stack frame. </td></tr>
|
||
<tr><td align="left"> la s </td><td align="left"> layout src </td><td align="left"> Displays source and command windows. </td></tr>
|
||
<tr><td align="left"> la a </td><td align="left"> layout asm </td><td align="left"> Displays disassembly and command windows. </td></tr>
|
||
<tr><td align="left"> tu d </td><td align="left"> tui disable </td><td align="left"> Disable TUI display mode. </td></tr>
|
||
<tr><td align="left"> </td><td align="left"> dissasmble /m </td><td align="left"> Disassemble a specified section of memory. </td></tr>
|
||
<tr><td align="left"> </td><td align="left"> monitor reset halt </td><td align="left"> Send a command to the remote monitor. </td></tr>
|
||
</tbody></table>
|
||
<a class="header" href="#general-troubleshooting" id="general-troubleshooting"><h1>General troubleshooting</h1></a>
|
||
<a class="header" href="#openocd-problems" id="openocd-problems"><h2>OpenOCD problems</h2></a>
|
||
<a class="header" href="#cant-connect-to-openocd---error-open-failed" id="cant-connect-to-openocd---error-open-failed"><h3>can't connect to OpenOCD - "Error: open failed"</h3></a>
|
||
<a class="header" href="#symptoms" id="symptoms"><h4>Symptoms</h4></a>
|
||
<p>Upon trying to establish a <em>new connection</em> with the device you get an error
|
||
that looks like this:</p>
|
||
<pre><code>$ openocd -f (..)
|
||
(..)
|
||
Error: open failed
|
||
in procedure 'init'
|
||
in procedure 'ocd_bouncer'
|
||
</code></pre>
|
||
<a class="header" href="#cause--fix" id="cause--fix"><h4>Cause + Fix</h4></a>
|
||
<ul>
|
||
<li>All: The device is not (properly) connected. Check the USB connection using
|
||
<code>lsusb</code> or the Device Manager.</li>
|
||
<li>Linux: You may not have enough permission to open the device. Try again with
|
||
<code>sudo</code>. If that works, you can use <a href="../setup/LINUX.html#udev%20rules">these instructions</a> to make OpenOCD work
|
||
without root privilege.</li>
|
||
<li>Windows: You are probably missing the USB drivers.</li>
|
||
</ul>
|
||
<a class="header" href="#cant-connect-to-openocd---polling-again-in-x00ms" id="cant-connect-to-openocd---polling-again-in-x00ms"><h3>can't connect to OpenOCD - "Polling again in X00ms"</h3></a>
|
||
<a class="header" href="#symptoms-1" id="symptoms-1"><h4>Symptoms</h4></a>
|
||
<p>Upon trying to establish a <em>new connection</em> with the device you get an error
|
||
that looks like this:</p>
|
||
<pre><code>$ openocd -f (..)
|
||
(..)
|
||
Error: jtag status contains invalid mode value - communication failure
|
||
Polling target stm32f3x.cpu failed, trying to reexamine
|
||
Examination failed, GDB will be halted. Polling again in 100ms
|
||
Info : Previous state query failed, trying to reconnect
|
||
Error: jtag status contains invalid mode value - communication failure
|
||
Polling target stm32f3x.cpu failed, trying to reexamine
|
||
Examination failed, GDB will be halted. Polling again in 300ms
|
||
Info : Previous state query failed, trying to reconnect
|
||
</code></pre>
|
||
<a class="header" href="#cause" id="cause"><h4>Cause</h4></a>
|
||
<p>The microcontroller may have get stuck in some tight infinite loop or it may be
|
||
continuously raising an exception, e.g. the exception handler is raising an
|
||
exception.</p>
|
||
<a class="header" href="#fix" id="fix"><h4>Fix</h4></a>
|
||
<ul>
|
||
<li>Close OpenOCD, if running</li>
|
||
<li>Press and hold the reset (black) button</li>
|
||
<li>Launch the OpenOCD command</li>
|
||
<li>Now, release the reset button</li>
|
||
</ul>
|
||
<a class="header" href="#openocd-connection-lost---polling-again-in-x00ms" id="openocd-connection-lost---polling-again-in-x00ms"><h3>OpenOCD connection lost - "Polling again in X00ms"</h3></a>
|
||
<a class="header" href="#symptoms-2" id="symptoms-2"><h4>Symptoms</h4></a>
|
||
<p>A <em>running</em> OpenOCD session suddenly errors with:</p>
|
||
<pre><code># openocd -f (..)
|
||
Error: jtag status contains invalid mode value - communication failure
|
||
Polling target stm32f3x.cpu failed, trying to reexamine
|
||
Examination failed, GDB will be halted. Polling again in 100ms
|
||
Info : Previous state query failed, trying to reconnect
|
||
Error: jtag status contains invalid mode value - communication failure
|
||
Polling target stm32f3x.cpu failed, trying to reexamine
|
||
Examination failed, GDB will be halted. Polling again in 300ms
|
||
Info : Previous state query failed, trying to reconnect
|
||
</code></pre>
|
||
<a class="header" href="#cause-1" id="cause-1"><h4>Cause</h4></a>
|
||
<p>The USB connection was lost.</p>
|
||
<a class="header" href="#fix-1" id="fix-1"><h4>Fix</h4></a>
|
||
<ul>
|
||
<li>Close OpenOCD</li>
|
||
<li>Disconnect and re-connect the USB cable.</li>
|
||
<li>Re-launch OpenOCD</li>
|
||
</ul>
|
||
<a class="header" href="#cargo-problems" id="cargo-problems"><h2>Cargo problems</h2></a>
|
||
<a class="header" href="#cant-find-crate-for-core" id="cant-find-crate-for-core"><h3>"can't find crate for <code>core</code>"</h3></a>
|
||
<a class="header" href="#symptoms-3" id="symptoms-3"><h4>Symptoms</h4></a>
|
||
<pre><code> Compiling volatile-register v0.1.2
|
||
Compiling rlibc v1.0.0
|
||
Compiling r0 v0.1.0
|
||
error[E0463]: can't find crate for `core`
|
||
|
||
error: aborting due to previous error
|
||
|
||
error[E0463]: can't find crate for `core`
|
||
|
||
error: aborting due to previous error
|
||
|
||
error[E0463]: can't find crate for `core`
|
||
|
||
error: aborting due to previous error
|
||
|
||
Build failed, waiting for other jobs to finish...
|
||
Build failed, waiting for other jobs to finish...
|
||
error: Could not compile `r0`.
|
||
|
||
To learn more, run the command again with --verbose.
|
||
</code></pre>
|
||
<a class="header" href="#cause-2" id="cause-2"><h4>Cause</h4></a>
|
||
<p>You are using a toolchain older than <code>nightly-2018-04-08</code> and forgot to call <code>rustup target add thumbv7m-none-eabi</code>.</p>
|
||
<a class="header" href="#fix-2" id="fix-2"><h4>Fix</h4></a>
|
||
<p>Update your nightly and install the <code>thumbv7em-none-eabihf</code> target.</p>
|
||
<pre><code class="language-console">$ rustup update nightly
|
||
|
||
$ rustup target add thumbv7em-none-eabihf
|
||
</code></pre>
|
||
<a class="header" href="#build-problems" id="build-problems"><h2>Build problems</h2></a>
|
||
<a class="header" href="#error-language-item-required-but-not-found-eh_personality" id="error-language-item-required-but-not-found-eh_personality"><h3><code>error: language item required, but not found: 'eh_personality'</code></h3></a>
|
||
<a class="header" href="#cause-3" id="cause-3"><h4>Cause</h4></a>
|
||
<p>The <code>eh_personality</code> language item is used to implement stack unwinding in case a panic occurs.</p>
|
||
<a class="header" href="#fix-3" id="fix-3"><h4>Fix</h4></a>
|
||
<p>You need to use the correct target
|
||
by using <code>--target thumbv6m-none-eabi</code>
|
||
or modifying <code>.cargo/config</code></p>
|
||
<a class="header" href="#error-ld-cannot-open-linker-script-file-memoryx-no-such-file-or-directory" id="error-ld-cannot-open-linker-script-file-memoryx-no-such-file-or-directory"><h3><code>error: ld: cannot open linker script file memory.x: No such file or directory</code></h3></a>
|
||
<a class="header" href="#cause-4" id="cause-4"><h4>Cause</h4></a>
|
||
<p>A memory.x file is needed, this specifies memory layout.</p>
|
||
<a class="header" href="#fix-4" id="fix-4"><h4>Fix</h4></a>
|
||
<p>Either ask the board support crate maintainer to add memory.x to their crate,
|
||
or add a memory.x file to your project.</p>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
|
||
|
||
|
||
<div style="clear: both"></div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
|
||
|
||
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
||
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
|
||
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
|
||
|
||
|
||
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
||
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
|
||
<script src="book.js" type="text/javascript" charset="utf-8"></script>
|
||
|
||
<!-- Custom JS scripts -->
|
||
|
||
|
||
|
||
|
||
<script type="text/javascript">
|
||
window.addEventListener('load', function() {
|
||
window.setTimeout(window.print, 100);
|
||
});
|
||
</script>
|
||
|
||
|
||
|
||
</body>
|
||
</html>
|