Jekyll2022-02-28T23:32:35+01:00https://jonathanklimt.de/feed.xmlStackenbloggenPersonal BlogJonathan KlimtSimple remote backup probe2022-02-28T01:00:00+01:002022-02-28T23:32:29+01:00https://jonathanklimt.de/backup_probe<p>Backups.</p>
<p>Nobody likes them, few people do them, and you really only start to appreciate them once you need them but don’t have them.
I’ve set up a kind of dual-stage backup system for my computers and my home-server which is working fine so far.
There surely is room for improvement and more redundancy but nevertheless, I thought it is might be worth sharing.</p>
<h2 id="general-idea">General Idea</h2>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/overview.svg">
<img src="https://jonathanklimt.de/assets/images/backup/overview.svg" />
</a>
</div>
<figcaption>
Overview of the backup setup
</figcaption>
</figure>
<p>I already run daily backups to my home-server via <a href="https://github.com/bit-team/backintime">Back in Time</a>. Back in Time is using <a href="https://en.wikipedia.org/wiki/Rsync">rsync</a> under the hood, provides a quite usable GUI and has the cool features, that the backups are real files and folders on the remote. But the fancy thing is, that you have separate folders for each backup, but they don’t multiply the memory need by using <a href="https://en.wikipedia.org/wiki/Hard_link">Hard-links</a> internally.
What does that mean? Let’s imagine you have a file <code class="language-plaintext highlighter-rouge">foo.txt</code> which hasn’t changed between the backups. Back In Time would then create a folder for each backup and places the file inside. You’ll end up with something like <code class="language-plaintext highlighter-rouge">2022-01-20/foo.txt</code> and <code class="language-plaintext highlighter-rouge">2022-01-21/foo.txt</code>.
The clever thing is, both file tree entries actually point to the same bytes on your hard drive and only once consume the memory.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>backups
├── 2022-01-20
│ ├── foo1.txt <------+
│ └── foo2.txt |
└── 2022-01-21 |
├── foo1.txt <------+----- no changes - same file on disk
└── foo2.txt <---- had changes - new file on disk
</code></pre></div></div>
<p>I like doing it this way in my LAN, because file restoration is really simple: You just mount the remote file system, navigate to the file and copy-paste it back where you want it. The drawback is missing encryption (even though Back In Time supports this) and no compression.</p>
<p>But this setup was missing an offsite component so far to protect the data from any kind of “misfortune” like fire, lightning or a wild horde of rhinos (better be prepared for this!). Many people seem to use cloud storage for such an application, but I didn’t like the idea of giving my most sensible data into some company’s hand (even though encrypted).
For this, I decided to use a different tool: <a href="https://restic.net/">Restic</a>.
It is a very advanced command line based backup tool which support numerous storage back-ends. One of the common applications is to back up your data to cloud storage.
In comparison to the first tool, restic has its own data storage format, and you also need restic to restore the backups again.
(Of course, both are open-source tools).</p>
<h2 id="the-probe">The Probe</h2>
<p>What I wanted was a device which I can give to a friend and tell him: “Can you please plug this to your router? Thanks!” and everything run’s automatically.
I had an Odroid-XU4 lying around for some time collecting dust, and it seems that this project is a good opportunity to put it to real use.
With it’s USB3 port it was a better choice for a storage application than e.g., a Raspberry < 4.</p>
<h3 id="hardware">Hardware</h3>
<p>Besides the Odroid, I of course needed a disk, and the largest 2.5” one I could find back then was the “Seagate Barracuda ST5000LM000” with 5 TB. I also picked the shortest SATA-USB3 adapter cable I could find for a reasonable price.</p>
<p>It took me a few attempts to design the enclosure. I first tried something more compact, but then I’d end up with the HDD directly below the CPU, which was suboptimal. Also, the USB-SATA cable caused a lot of headache. But the final design is ok and I only need this once, so…</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/cad1.png">
<img src="https://jonathanklimt.de/assets/images/backup/cad1.png" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/cad2.png">
<img src="https://jonathanklimt.de/assets/images/backup/cad2.png" />
</a>
</div>
<figcaption>
CAD drawing of the enclosure with the Odroid (gray) and the HDD (blue/violet)
</figcaption>
</figure>
<p>The HDD is located in a kind of pan, which I filled with silicone I had lying around. The small bracket you can see above the HDD keep it secured in the custom bed.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/backup/probe1_large.jpg">
<img src="https://jonathanklimt.de/assets/images/backup/probe1_small.jpg" />
</a>
</div>
<figcaption>
The printed probe
</figcaption>
</figure>
<p>My original idea was to use some push rod mechanism for the Odroid’s button, but this didn’t work out (and I accidentally destroyed the button on the PCB), so instead I soldered the red button with some wires to the board, which worked fine.</p>
<h3 id="software">Software</h3>
<p class="notice--danger"><strong>Disclaimer:</strong> It’s been a while since I set up the whole thing, so I couldn’t check all these commands upon writing again</p>
<p>So besides WireGuard, the probe isn’t really running any special software. The starting point is an <a href="https://www.armbian.com/odroid-xu4/#kernels-archive-all">Armbian-Focal</a> (Ubuntu based) installation on that machine with ssh access and pubkey authentication. I assume that you are familiar with setting up such a system.</p>
<h4 id="external-disk">External Disk:</h4>
<p>The external disk is configured with <a href="https://en.wikipedia.org/wiki/Btrfs">Btrfs</a>. Of course, you can use different file systems, but I felt like this is the way to go.</p>
<p>To create the Btrfs partition on the disk (obviously, replace the aaaaa-bbb-… id with the one for your drive):</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># create the mountpoint</span>
<span class="o">></span> <span class="nb">mkdir</span> /data
<span class="c"># create the filesystem on the external disk</span>
<span class="o">></span> mkfs.btrfs /dev/disk/by-uuid/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
</code></pre></div></div>
<p>To automatically mount the disk, the disk has to be added to to <code class="language-plaintext highlighter-rouge">/etc/fstab</code> as follows:</p>
<pre><code class="language-fstab">UUID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee /data btrfs defaults,compress,autodefrag,inode_cache 0 1
</code></pre>
<p>Now might be a good time for a reboot.</p>
<div class="notice--success">
<p>You can verify that everything worked with the following command:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> mount <span class="nt">-l</span> | <span class="nb">grep</span> /data
<span class="c"># should be like:</span>
/dev/sda1 on /data <span class="nb">type </span>btrfs <span class="o">(</span>rw,relatime,compress<span class="o">=</span>zlib:3,...<span class="o">)</span>
</code></pre></div></div>
</div>
<h4 id="unattended-upgrades">Unattended upgrades</h4>
<p>As the probe won’t be maintained actively, it might be a good idea to enable <em>unattended-upgrades</em>.
Unattended upgrades is already installed, we just have to enable it:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> <span class="nb">sudo </span>dpkg-reconfigure <span class="nt">--priority</span><span class="o">=</span>low unattended-upgrades
<span class="c"># ...</span>
<span class="c"># Check if it worked:</span>
<span class="o">></span> apt-config dump APT::Periodic::Unattended-Upgrade
APT::Periodic::Unattended-Upgrade <span class="s2">"1"</span><span class="p">;</span>
</code></pre></div></div>
<p class="notice--warning"><strong>Warning:</strong> beware that a power-outage during such an upgrade might render your installation useless. However, the backups *shouldn’t* be affected by this.</p>
<h4 id="power-tweaking-of-the-odroid-xu4">Power tweaking of the Odroid-XU4</h4>
<p>The idle power use of a device running 24/7 is actually relevant. I measured the Odroid and it draws ~3.5W. A single watt you can save sums up to ~9 kWh over a year. It’s not that much but 2.5€ is at least something, especially if you give it away and let your friends pay the bill.</p>
<p>You can play around with the <code class="language-plaintext highlighter-rouge">/sys</code> filesystem and tweak the frequencies and governors of the system:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Limit the frequency of the first 4 cores to 1GHz</span>
<span class="o">></span> <span class="nb">echo </span>1000000 <span class="o">></span> /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
<span class="c"># Disable CPUs 4-7</span>
<span class="o">></span> <span class="nb">echo </span>0 <span class="o">></span> /sys/devices/system/cpu/cpu4/online
</code></pre></div></div>
<p>I found, that the minimum frequency is set to 600 MHz, which can be reduced to 200 MHz with <code class="language-plaintext highlighter-rouge">scaling_min_freq</code> (check <code class="language-plaintext highlighter-rouge">scaling_available_frequencies</code>).</p>
<p>The changes made with the commands above are not persistent.
Multiple ways exist to make them persistent and I used the <code class="language-plaintext highlighter-rouge">sysfsutils</code> package and modified <code class="language-plaintext highlighter-rouge">/etc/sysfs.conf</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>devices/system/cpu/cpu0/cpufreq/scaling_governor = ondemand
devices/system/cpu/cpu0/cpufreq/scaling_min_freq = 200000
devices/system/cpu/cpu4/cpufreq/scaling_governor = ondemand
devices/system/cpu/cpu4/cpufreq/scaling_min_freq = 200000
</code></pre></div></div>
<p>You can also check the <code class="language-plaintext highlighter-rouge">/boot/boot.ini</code> to do more tweaks like clocking down the RAM.</p>
<h2 id="wireguard-server-setup">WireGuard Server Setup</h2>
<p>Ok, the Odroid is running for now and now we have to go back to the home-server and set up the WireGuard server.
Not that I’m a Docker evangelist, but I found it actually pretty handy to use containers for the services on the home-server.
So I also utilized it for the VPN and the backup routine, even though you surely can set it up without docker as well.</p>
<p><strong>Please note:</strong> It is important that this machine is reachable from outside your network. Otherwise you’d need another server which is and acts as a VPN master.</p>
<p>So I’m using the <a href="https://github.com/linuxserver/docker-wireguard">linuxserver.io Dockerfile</a> for this.
Basically I’m mostly copying their <a href="https://github.com/linuxserver/docker-wireguard#docker-compose-recommended-click-here-for-more-info"><code class="language-plaintext highlighter-rouge">docker_compose.yaml</code></a>.
I only adapted the <code class="language-plaintext highlighter-rouge">SERVERURL</code> to the one my home-server is using and of course made sure that the selected <code class="language-plaintext highlighter-rouge">SERVERPORT</code> port is reachable.</p>
<p>Running <code class="language-plaintext highlighter-rouge">docker-compose up -d</code> should be sufficient to start the server now!</p>
<p>If you take a look at the folder selected for the WireGuard config, you’ll see a number of folders <code class="language-plaintext highlighter-rouge">peer1</code>, <code class="language-plaintext highlighter-rouge">peer2</code> … with predefined configs for the clients of the vpn - convenient, isn’t it?</p>
<h2 id="wireguard-client-setup">WireGuard Client Setup</h2>
<p>We now go back to the backup probe and install WireGuard:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> apt <span class="nb">install </span>wireguard
</code></pre></div></div>
<p>Easy! Now copy the content of one of the <code class="language-plaintext highlighter-rouge">peerX.conf</code> files mentioned before into <code class="language-plaintext highlighter-rouge">/etc/wireguard/wg0.conf</code>:</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Interface]</span>
<span class="c"># The new wireguard IP of the probe</span>
<span class="py">Address</span> <span class="p">=</span> <span class="mf">10.13.13.2</span>
<span class="py">PrivateKey</span> <span class="p">=</span> <span class="err">XXXXXXXXX</span>
<span class="py">ListenPort</span> <span class="p">=</span> <span class="mi">51820</span>
<span class="py">DNS</span> <span class="p">=</span> <span class="mf">10.13.13.1</span>
<span class="nn">[Peer]</span>
<span class="py">PublicKey</span> <span class="p">=</span> <span class="err">YYYYYYYYY</span>
<span class="c"># My server's url</span>
<span class="py">Endpoint</span> <span class="p">=</span> <span class="err">my.very-own.url.org:</span><span class="mi">51820</span>
<span class="py">AllowedIPs</span> <span class="p">=</span> <span class="mf">10.13.13.0</span><span class="err">/</span><span class="mi">24</span>
<span class="py">PersistentKeepalive</span> <span class="p">=</span> <span class="mi">25</span>
</code></pre></div></div>
<p class="notice--warning"><strong>Important:</strong> The <code class="language-plaintext highlighter-rouge">PersistentKeepalive</code> is required, as the Probe is most likely behind a NAT and thus not reachable from outside my friend’s home network.
With that parameter set, the probe sends a keepalive package to the server every 25 seconds, which keeps the connection open and enables us to ssh and backup anytime.</p>
<p>Verify that it works with:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> wg-quick up wg0
<span class="c"># ...</span>
</code></pre></div></div>
<p>Now we check on the home-server or another machine in the same WireGuard network if this works:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> ping 10.13.13.2 <span class="c"># the IP set in the peerX.conf</span>
PING 10.13.13.2 <span class="o">(</span>10.13.13.2<span class="o">)</span> 56<span class="o">(</span>84<span class="o">)</span> bytes of data.
64 bytes from 10.13.13.2: <span class="nv">icmp_seq</span><span class="o">=</span>1 <span class="nv">ttl</span><span class="o">=</span>63 <span class="nb">time</span><span class="o">=</span>59.1 ms
64 bytes from 10.13.13.2: <span class="nv">icmp_seq</span><span class="o">=</span>2 <span class="nv">ttl</span><span class="o">=</span>63 <span class="nb">time</span><span class="o">=</span>44.3 ms
<span class="c"># nice!</span>
<span class="o">></span> ssh jonathan@10.13.13.2
<span class="c"># ...</span>
</code></pre></div></div>
<p>At the moment, VPN connection is lost after a reboot. But this can be changed with systemd. We just have to enable the corresponding service:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> systemctl <span class="nb">enable </span>wg-quick@wg0
<span class="o">></span> systemctl start wg-quick@wg0 <span class="c"># (Otherwise a reboot is required)</span>
</code></pre></div></div>
<p>Now is a good time to reboot the probe and check if the setup works so far.</p>
<div class="notice--info">
<p><strong>DNS Issue Fix:</strong> If you address your host directly via an IP address, you can skip this step. But if you have set a DNS record for the server, you’ll probably run into an error here.
The problem is, that the WireGuard service now tries to establish a connection to the home-server before the DNS resolver is fully running. Therefore, the initial connection attempt fails, and the normal service configuration doesn’t include reconnects in such a case.
The <em>dirty hack</em> I did and which worked surprisingly well so far is, to wait for the domain to be reachable before the connection.
To do so, modify the systemd service file at <code class="language-plaintext highlighter-rouge">/lib/systemd/system/wg-quick@.service</code> and add the following line to the <code class="language-plaintext highlighter-rouge">[Service]</code> section:</p>
<div class="language-systemd highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">ExecStartPre</span><span class="p">=</span>/bin/bash -c 'until host my.very-own.url.org; do sleep 1; done'
</code></pre></div></div>
</div>
<h3 id="restic-setup">Restic Setup</h3>
<p>You could now install <a href="https://github.com/restic/restic#backends">various cloud storage systems</a> as back-ends on the probe, but the most simple one at this point and for this use case is probably <a href="https://en.wikipedia.org/wiki/SSHFS">SFTP/SSHFS</a>.
We use the Dockerfile <a href="https://github.com/lobaro/restic-backup-docker">provided by lobaro</a>.
However, with the plain Dockerfile, we would have to store the password for the probe in plaintext in the compose file, and it would also appear in the logs. So we extend the Dockerfile a bit and utilize <a href="https://docs.docker.com/engine/swarm/secrets/">Docker secrets</a> to mount an ssh key in the container, so we can use passwordless authentication.</p>
<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> lobaro/restic-backup-docker:latest</span>
<span class="c"># secrets are mounted at /run/secrets. Here we link the secret into the container's ssh config:</span>
<span class="k">RUN </span><span class="nb">mkdir</span> <span class="nt">-p</span> /root/.ssh <span class="o">&&</span> <span class="nb">ln</span> <span class="nt">-s</span> /run/secrets/user_ssh_key /root/.ssh/backup_probe_key
<span class="c"># Ensure correct permissions. SSH won't work if these are incorrect.</span>
<span class="k">RUN </span><span class="nb">chown</span> <span class="nt">-R</span> root:root /root/.ssh
<span class="c"># Disable the "trust host message" in the container.</span>
<span class="k">RUN </span><span class="nb">printf</span> <span class="s2">"Host 10.13.13.2</span><span class="se">\n\t</span><span class="s2">StrictHostKeyChecking no</span><span class="se">\n</span><span class="s2">"</span> <span class="o">>></span> /root/.ssh/config
</code></pre></div></div>
<p>If you haven’t done before, at the latest now we need an ssh key to log into the probe:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># (Optional:) Generate a new key:</span>
<span class="o">></span> ssh-keygen <span class="nt">-t</span> ed25519 <span class="nt">-C</span> <span class="s2">"your_email@example.com"</span>
<span class="c"># Copy the key (here: backup_probe_key) to the probe:</span>
<span class="o">></span> ssh-copy-id <span class="nt">-i</span> ~/.ssh/backup_probe_key <span class="nt">-o</span> <span class="nv">PreferredAuthentications</span><span class="o">=</span>password <span class="nt">-o</span> <span class="nv">PubkeyAuthentication</span><span class="o">=</span>no jonathan@10.13.13.2
</code></pre></div></div>
<p>Ok, now we are ready to tackle the restic configuration:
First we want to create a <code class="language-plaintext highlighter-rouge">.env</code> file to hold our backup’s password (the <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> is really not the best place for these).</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RESTIC_PASSWORD=asdfasdfasdf123412341234
</code></pre></div></div>
<p class="notice--info">Note: A handy way to generate passwords is <code class="language-plaintext highlighter-rouge">pwgen</code>: <code class="language-plaintext highlighter-rouge">pwgen -n 30 1</code> generates a 30 character alphanumeric password.</p>
<p>Ok, now so here’s the section of the <code class="language-plaintext highlighter-rouge">docker-compose.yml</code> with the actual backup configuration:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.3"</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">restic-backup</span><span class="pi">:</span>
<span class="na">depends_on</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">wireguard</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">restic-backup</span>
<span class="na">build</span><span class="pi">:</span>
<span class="c1"># use the local Dockerfile</span>
<span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
<span class="na">dockerfile</span><span class="pi">:</span> <span class="s">Dockerfile</span>
<span class="na">secrets</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">user_ssh_key</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="c1"># repository location: /data/backup/server on the probe</span>
<span class="pi">-</span> <span class="s">RESTIC_REPOSITORY=sftp:jonathan@10.13.13.2:/data/backup/server</span>
<span class="c1"># The name of the ssh key inside the container (not really necessary)</span>
<span class="pi">-</span> <span class="s">RESTIC_KEY_HINT=backup_probe_key</span>
<span class="c1"># Every second day at 12:00</span>
<span class="pi">-</span> <span class="s">BACKUP_CRON= 0 12 2-30/2 * *</span>
<span class="c1"># keep the last 2 backups plus one for the last 3 months.</span>
<span class="pi">-</span> <span class="s">RESTIC_FORGET_ARGS=--keep-last 2 --keep-monthly </span><span class="m">3</span>
<span class="na">env_file</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">.env</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="c1"># Backup the folders /zstorage/web and /home/jonathan</span>
<span class="pi">-</span> <span class="s">/zstorage/git:/data/git:ro</span>
<span class="pi">-</span> <span class="s">/home/jonathan/:/data/home:ro</span>
<span class="c1"># ...</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
<span class="c1"># Same network as the wireguard container so we can access the probe</span>
<span class="na">network_mode</span><span class="pi">:</span> <span class="s">service:wireguard</span>
<span class="na">secrets</span><span class="pi">:</span>
<span class="na">user_ssh_key</span><span class="pi">:</span>
<span class="c1"># create the secret for the ssh key (here: .ssh/backup_probe_key)</span>
<span class="na">file</span><span class="pi">:</span> <span class="s">/home/jonathan/.ssh/backup_probe_key</span>
</code></pre></div></div>
<p class="notice--info">Note the addition of <code class="language-plaintext highlighter-rouge">:ro</code> in the <code class="language-plaintext highlighter-rouge">volumes</code> section to mount the folders as read-only. This ensures that anything wonky on your remote, backup configuration or in the wireguard network at least can’t change your files.</p>
<h3 id="final-compose-file">Final compose file</h3>
<p>All that is left is to combine the two sections of the compose file and run <code class="language-plaintext highlighter-rouge">docker-compose up -d</code>.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.3"</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">wireguard</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">ghcr.io/linuxserver/wireguard</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">wireguard</span>
<span class="na">cap_add</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">NET_ADMIN</span>
<span class="pi">-</span> <span class="s">SYS_MODULE</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">PUID=1000</span>
<span class="pi">-</span> <span class="s">PGID=1000</span>
<span class="pi">-</span> <span class="s">TZ=Europe/Berlin</span>
<span class="pi">-</span> <span class="s">SERVERURL=my.very-own.url.org</span>
<span class="pi">-</span> <span class="s">SERVERPORT=51820</span>
<span class="pi">-</span> <span class="s">PEERS=5</span>
<span class="pi">-</span> <span class="s">PEERDNS=auto</span>
<span class="pi">-</span> <span class="s">INTERNAL_SUBNET=10.13.13.0</span>
<span class="pi">-</span> <span class="s">ALLOWEDIPS=10.13.13.0/24</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/home/jonathan/wireguard/config:/config</span>
<span class="pi">-</span> <span class="s">/lib/modules:/lib/modules</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">51820:51820/udp</span>
<span class="na">sysctls</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">net.ipv4.conf.all.src_valid_mark=1</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
<span class="na">restic-backup</span><span class="pi">:</span>
<span class="na">depends_on</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">wireguard</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">restic-backup</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
<span class="na">dockerfile</span><span class="pi">:</span> <span class="s">Dockerfile</span>
<span class="na">secrets</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">user_ssh_key</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">RESTIC_REPOSITORY=sftp:jonathan@10.13.13.2:/data/backup/server</span>
<span class="pi">-</span> <span class="s">RESTIC_KEY_HINT=backup_probe_key</span>
<span class="pi">-</span> <span class="s">BACKUP_CRON= 0 12 2-30/2 * *</span>
<span class="pi">-</span> <span class="s">RESTIC_FORGET_ARGS=--keep-last 2 --keep-monthly </span><span class="m">3</span>
<span class="na">env_file</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">.env</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/zstorage/git:/data/git:ro</span>
<span class="pi">-</span> <span class="s">/home/jonathan/:/data/home:ro</span>
<span class="c1"># ...</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
<span class="na">network_mode</span><span class="pi">:</span> <span class="s">service:wireguard</span>
<span class="na">secrets</span><span class="pi">:</span>
<span class="na">user_ssh_key</span><span class="pi">:</span>
<span class="na">file</span><span class="pi">:</span> <span class="s">/home/jonathan/.ssh/backup_probe_key</span>
</code></pre></div></div>
<h2 id="checking-the-backup">Checking the Backup</h2>
<p>Almost as important as creating a backup: Checking that you can restore from it.
One way is, to mount the backup, so you can navigate it with a normal file manager:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> <span class="nb">mkdir</span> /tmp/restic
<span class="o">></span> restic <span class="nt">-r</span> sftp:jonathan@10.13.13.2:/data/backup/server mount /tmp/restic
</code></pre></div></div>
<p>After entering your <code class="language-plaintext highlighter-rouge">RESTIC_PASSWORD</code>, you should find your backup at <code class="language-plaintext highlighter-rouge">/tmp/restic</code>.</p>
<p><strong>Happy Backuping!</strong></p>Jonathan KlimtFor offsite backups of my home-server, I've created a small probe based on a single-board computerUpgrade of my Artillery Genius Heat Bed2021-12-03T01:00:00+01:002022-01-23T10:31:34+01:00https://jonathanklimt.de/3d-printing/genius-heatbed<h2 id="artillery-genius-heatbed-upgrade">Artillery Genius Heatbed Upgrade</h2>
<p>I own an <a href="https://artillery3d.com/products/artillery-genius-3d-printer-kit-220220250mm-print-size-with-ultra-quiet-stepper-motor-tft-touch-screen">Artillery Genius</a> 3D printer since now more than 1.5 years, and now the temperature sensor’s cable broke. This is unfortunate, because I’ve read multiple times that a strain relief for this cable is an upgrade you should definitely print. But I’ve constantly ignored it.
Ok, lesson learned. (BTW: The ribbon cable of this printer also needs a strain relief!)</p>
<p>As a hotfix, I replaced the broken part of the cable, which surprisingly worked for a few prints but is certainly something from the <em>do-not-try-this-at-home</em> category. Be reminded that the heating bed of this printer operates at 230V!
Looking at the picture, you will surely come to the same conclusion I did: This won’t last very long.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/repair_2_large.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/repair_2_small.jpg" />
</a>
</div>
<figcaption>
A quick and dirty repair of the broken thermistor cable
</figcaption>
</figure>
<h2 id="the-upgrade">The Upgrade</h2>
<p>You can just buy a new heat mat for ~35€, but since I’m on it anyway, I decided to directly go for an upgrade of the complete heat bed.</p>
<p>The stock glass surface is just fine, but the heat-matte is not doing a good job in heating the plate evenly. You can see from the following picture that the corners, above the mounting screws, the bed is significantly cooler than in the center.
No problem for small prints, but this has ruined a few larger prints already.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/heated_before.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/heated_before.jpg" />
</a>
</div>
<figcaption>
The stock bed of the Genius after a few minutes
</figcaption>
</figure>
<p>It is rather difficult to find a different heat mat, at least if you don’t want to import something from the USA or China.
The only thing I found was the <a href="https://princore.de/products/artillery-genius-silikonheizmatte-220v-300watt">Princore Genius Heat Mat</a> from a German shop.
There is more choice for aluminum beds, so I chose the following for no particular reason: <a href="https://www.abs-3d.de/druckbetten/druckbetten-nicht-magnetisch/artillery/626/artillery-genius-aluminium-druckbett?number=ABS-Genuis-Set2">ABS Maschinenschutz Heat Bed</a></p>
<p>The assembly was mostly straightforward. You need to shorten the cables and have to pay attention not to damage anything. Some remarks on this:</p>
<ul>
<li>It is advisable to use <a href="https://en.wikipedia.org/wiki/Electrical_connector#Ring_and_spade_connectors">ring terminals</a> to attach the new wires to the printer.</li>
<li>⚠️ <strong>Only do this if you know what you are doing! 230V are dangerous!</strong> ⚠️</li>
<li>⚠️ <strong>The bed MUST be connected to ground</strong> ⚠️</li>
</ul>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/printer_after_blank_large.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/printer_after_blank_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/printer_after_large.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/printer_after_small.jpg" />
</a>
</div>
<figcaption>
The Genius with the new heat bed. Right: Without and Left: with the GFK sheet
</figcaption>
</figure>
<p>Ok, so does this improve the situation? I’m not doing a scientific level analysis here, but I do have some results:</p>
<p>First, I wanted to know if the mat heats more evenly than the stock one which has the cross shape heating characteristic.
So I’ve taken a thermal camera picture of the bottom of the heat bed without any isolation:</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/new_bottom.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/new_bottom.jpg" />
</a>
</div>
<figcaption>
The bottom of the aluminum bed with the Princore heat mat and without any isolation
</figcaption>
</figure>
<p>It’s a bit hard to judge if the mat heats up more evenly, or we do mostly see the effects of the metal plate dissipating the heat more evenly, but it looks better than before!</p>
<p>But what about the obviously more important surface temperature?</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/heatup_after.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/heatup_after.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/heatup_after_3.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/heatup_after_3.jpg" />
</a>
</div>
<figcaption>
The top of the new bed with the GFK sheet during heat up and after a few minutes
</figcaption>
</figure>
<p>I’d say, this looks quite nice. Definitely better than before! The plate also seems to be sticky enough, but I only did one short test-print so far.</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/first_print_top_large.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/first_print_top_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/genius-heatbed/first_print_bot_large.jpg">
<img src="https://jonathanklimt.de/assets/images/genius-heatbed/first_print_bot_small.jpg" />
</a>
</div>
<figcaption>
First simple test print. In the printer (left) and the bottom of it (right).
</figcaption>
</figure>
<p>The bottom is <em>very</em> smooth. You see small gaps, which I’ll tackle later on.
So for now, in all I’m rather pleased. Is it worth the 100€? Hmm, time will tell. Let’s see how long this will last. But if you don’t want to spend the money, be sure to print and attach a strain relief, so your bed will last longer than mine!</p>
<ul>
<li><a href="https://www.thingiverse.com/thing:4389200">This one for the bed</a>. You have to attach it to the metal plate which is holding the bed screws.</li>
<li>I have printed <a href="https://www.thingiverse.com/thing:4100016">this relief for the X-axis ribbon cable</a>, but it needed some manual adaption since it is made for the Sidewinder X1 and not for the Genius.</li>
</ul>
<p>Happy Printing 🖨</p>Jonathan KlimtI've upgraded the heat bed of my Artillery Genius with a metal one and checked if the temperature is distributed more evenly.Rust on STM32: Getting started2020-07-22T02:00:00+02:002020-07-24T11:25:44+02:00https://jonathanklimt.de/electronics/programming/embedded-rust/rust-on-stm32-2<p><em>(Thumbnail: <a href="https://github.com/rust-embedded/wg/tree/master/assets">Rust Embedded WG Logo</a> - <a href="https://creativecommons.org/licenses/by/4.0/">CC-BY</a>)</em></p>
<h2 id="preface">Preface</h2>
<p>I didn’t found any time and motivation to write something here, but I guess it’s time for an updated version of <a href="/electronics/programming/embedded-rust/rust-STM32F103-blink/">my previous post on Rust on the STM32F1</a>
I plan to make more shorter and more modular posts, but to get started, here is a short tutorial on how to get started:</p>
<p>🎉 Blink an LED in Rust on a cheap microcontroller. 🎉</p>
<h2 id="what-you-need">What you need</h2>
<ul>
<li>A PC with an installed <a href="https://www.rust-lang.org/learn/get-started">Rust Toolchain</a> (which also should involve <code class="language-plaintext highlighter-rouge">cargo</code>). I only run Linux, but the following is likely to run on MacOS as well, and maybe even on Windows.</li>
<li>A Board with an STM32F103 microcontroller.
The most common dev-boards with this controllers are probably the “BluePills” which you usually can get for less than 2€ (e.g. on <a href="https://www.aliexpress.com/item/STM32F103C8T6-ARM-STM32-Minimum-System-Development-Board-Module-For-Arduino/32278016818.html">Aliexpress</a>).
We use a BluePill, but other Boards should work very similar.
<em>Attention:</em> Especially on Aliexpress there exists an incredible amount of counterfeit chips.
If it does not work, this might be the reason.</li>
<li>An Programmer.
Among the cheapest programmers you can get for the STM chips is a ST-Link V2 Clone (<a href="https://www.aliexpress.com/item/1PCS-ST-LINK-Stlink-ST-Link-V2-Mini-STM8-STM32-Simulator-Download-Programmer-Programming-With-Cover/32920031233.html">AliExpress</a>).
The Programmer itself is a clone and often contains an “CS32” chip instead of an STM32. Again: Might be a source of trouble.</li>
</ul>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005342_edit.jpg">
<img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005342_edit_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005543_edit.jpg">
<img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005543_edit_small.jpg" />
</a>
</div>
<figcaption>
BluePill, Blackpill and a ST-Link clone connected to the BluePill
</figcaption>
</figure>
<h2 id="software-preparation">Software Preparation</h2>
<p>At first, let’s make sure we have an up-to-date compiler and cargo:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> rustup update
</code></pre></div></div>
<p>Then we’ll install the cross-toolchain for the STM32F1, which runs a <code class="language-plaintext highlighter-rouge">thumbv7m-none-eabi</code> ARM core:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> rustup target <span class="nb">install </span>thumbv7m-none-eabi
</code></pre></div></div>
<p>For now, we use <a href="https://github.com/probe-rs/cargo-flash"><code class="language-plaintext highlighter-rouge">cargo flash</code></a> to program the microcontroller.
We can comfortably install this tool via cargo:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> cargo <span class="nb">install </span>cargo-flash
</code></pre></div></div>
<p>And finally we create a new Rust project in a directory of our choice with</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> cargo init rusty-blink
</code></pre></div></div>
<p>Before we write any code, we configure the project, so that it compiles for our <code class="language-plaintext highlighter-rouge">thumbv7m-none-eabi</code> target by default and uses the correct linker script (more on that in a second). We do this by creating a file <code class="language-plaintext highlighter-rouge">.cargo/config</code> in our project root.</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># .cargo/config</span>
<span class="nn">[build]</span>
<span class="c"># Always compile for the instruction set of the STM32F1</span>
<span class="py">target</span> <span class="p">=</span> <span class="s">"thumbv7m-none-eabi"</span>
<span class="c"># use the Tlink.x scrip from the cortex-m-rt crate</span>
<span class="py">rustflags</span> <span class="p">=</span> <span class="p">[</span> <span class="s">"-C"</span><span class="p">,</span> <span class="s">"link-arg=-Tlink.x"</span><span class="p">]</span>
</code></pre></div></div>
<p>The next we need is the linker script which tells the linker about the memory layout of the chip. It must lay in the project root and is called <code class="language-plaintext highlighter-rouge">memory.x</code>. For the STM32F103C8 this looks as follows:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* memory.x - Linker script for the STM32F103C8T6 */</span>
<span class="n">MEMORY</span>
<span class="p">{</span>
<span class="cm">/* Flash memory begins at 0x80000000 and has a size of 64kB*/</span>
<span class="n">FLASH</span> <span class="o">:</span> <span class="n">ORIGIN</span> <span class="o">=</span> <span class="mh">0x08000000</span><span class="p">,</span> <span class="n">LENGTH</span> <span class="o">=</span> <span class="mi">64</span><span class="n">K</span>
<span class="cm">/* RAM begins at 0x20000000 and has a size of 20kB*/</span>
<span class="n">RAM</span> <span class="o">:</span> <span class="n">ORIGIN</span> <span class="o">=</span> <span class="mh">0x20000000</span><span class="p">,</span> <span class="n">LENGTH</span> <span class="o">=</span> <span class="mi">20</span><span class="n">K</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Ok, enough preparation, lets get to Rust/Cargo!</p>
<h2 id="the-blinky-program">The Blinky Program</h2>
<p>Of course, you need a <code class="language-plaintext highlighter-rouge">Cargo.toml</code> file to configure your project. There is already one present in your project folder, but we need to add some dependencies and configurations:
(These are current versions as of mid July 2020).</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cargo.toml</span>
<span class="nn">[package]</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2018"</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"blinky-rust"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="nn">[profile.release]</span>
<span class="py">opt-level</span> <span class="p">=</span> <span class="s">'z'</span> <span class="c"># turn on maximum optimizations. We only have 64kB</span>
<span class="py">lto</span> <span class="p">=</span> <span class="kc">true</span> <span class="c"># Link-time-optimizations for further size reduction</span>
<span class="nn">[dependencies]</span>
<span class="py">cortex-m</span> <span class="p">=</span> <span class="s">"^0.6.3"</span> <span class="c"># Access to the generic ARM peripherals</span>
<span class="py">cortex-m-rt</span> <span class="p">=</span> <span class="s">"^0.6.12"</span> <span class="c"># Startup code for the ARM Core</span>
<span class="py">embedded-hal</span> <span class="p">=</span> <span class="s">"^0.2.4"</span> <span class="c"># Access to generic embedded functions (`set_high`)</span>
<span class="py">panic-halt</span> <span class="p">=</span> <span class="s">"^0.2.0"</span> <span class="c"># Panic handler</span>
<span class="c"># Access to the stm32f103 HAL.</span>
<span class="nn">[dependencies.stm32f1xx-hal]</span>
<span class="c"># Bluepill contains a 64kB flash variant which is called "medium density"</span>
<span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"stm32f103"</span><span class="p">,</span> <span class="s">"rt"</span><span class="p">,</span> <span class="s">"medium"</span><span class="p">]</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"^0.6.1"</span>
</code></pre></div></div>
<p>And finally: Here is a simple blink program! Don’t be afraid, it only looks that long because I added the explanatory comments. Without it’s only ~30 lines 😉.</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// src/main.rs</span>
<span class="c">// std and main are not available for bare metal software</span>
<span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>
<span class="k">use</span> <span class="nn">cortex_m_rt</span><span class="p">::</span><span class="n">entry</span><span class="p">;</span> <span class="c">// The runtime</span>
<span class="k">use</span> <span class="nn">embedded_hal</span><span class="p">::</span><span class="nn">digital</span><span class="p">::</span><span class="nn">v2</span><span class="p">::</span><span class="n">OutputPin</span><span class="p">;</span> <span class="c">// the `set_high/low`function</span>
<span class="k">use</span> <span class="nn">stm32f1xx_hal</span><span class="p">::{</span><span class="nn">delay</span><span class="p">::</span><span class="n">Delay</span><span class="p">,</span> <span class="n">pac</span><span class="p">,</span> <span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">};</span> <span class="c">// STM32F1 specific functions</span>
<span class="nd">#[allow(unused_imports)]</span>
<span class="k">use</span> <span class="n">panic_halt</span><span class="p">;</span> <span class="c">// When a panic occurs, stop the microcontroller</span>
<span class="c">// This marks the entrypoint of our application. The cortex_m_rt creates some</span>
<span class="c">// startup code before this, but we don't need to worry about this</span>
<span class="nd">#[entry]</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-></span> <span class="o">!</span> <span class="p">{</span>
<span class="c">// Get handles to the hardware objects. These functions can only be called</span>
<span class="c">// once, so that the borrowchecker can ensure you don't reconfigure</span>
<span class="c">// something by accident.</span>
<span class="k">let</span> <span class="n">dp</span> <span class="o">=</span> <span class="nn">pac</span><span class="p">::</span><span class="nn">Peripherals</span><span class="p">::</span><span class="nf">take</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">let</span> <span class="n">cp</span> <span class="o">=</span> <span class="nn">cortex_m</span><span class="p">::</span><span class="nn">Peripherals</span><span class="p">::</span><span class="nf">take</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="c">// GPIO pins on the STM32F1 must be driven by the APB2 peripheral clock.</span>
<span class="c">// This must be enabled first. The HAL provides some abstractions for</span>
<span class="c">// us: First get a handle to the RCC peripheral:</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">rcc</span> <span class="o">=</span> <span class="n">dp</span><span class="py">.RCC</span><span class="nf">.constrain</span><span class="p">();</span>
<span class="c">// Now we have access to the RCC's registers. The GPIOC can be enabled in</span>
<span class="c">// RCC_APB2ENR (Prog. Ref. Manual 8.3.7), therefore we must pass this</span>
<span class="c">// register to the `split` function.</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">gpioc</span> <span class="o">=</span> <span class="n">dp</span><span class="py">.GPIOC</span><span class="nf">.split</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="n">rcc</span><span class="py">.apb2</span><span class="p">);</span>
<span class="c">// This gives us an exclusive handle to the GPIOC peripheral. To get the</span>
<span class="c">// handle to a single pin, we need to configure the pin first. Pin C13</span>
<span class="c">// is usually connected to the Bluepills onboard LED.</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">led</span> <span class="o">=</span> <span class="n">gpioc</span><span class="py">.pc13</span><span class="nf">.into_push_pull_output</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="n">gpioc</span><span class="py">.crh</span><span class="p">);</span>
<span class="c">// Now we need a delay object. The delay is of course depending on the clock</span>
<span class="c">// frequency of the microcontroller, so we need to fix the frequency</span>
<span class="c">// first. The system frequency is set via the FLASH_ACR register, so we</span>
<span class="c">// need to get a handle to the FLASH peripheral first:</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">flash</span> <span class="o">=</span> <span class="n">dp</span><span class="py">.FLASH</span><span class="nf">.constrain</span><span class="p">();</span>
<span class="c">// Now we can set the controllers frequency to 8 MHz:</span>
<span class="k">let</span> <span class="n">clocks</span> <span class="o">=</span> <span class="n">rcc</span><span class="py">.cfgr</span><span class="nf">.sysclk</span><span class="p">(</span><span class="mi">8</span><span class="nf">.mhz</span><span class="p">())</span><span class="nf">.freeze</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="n">flash</span><span class="py">.acr</span><span class="p">);</span>
<span class="c">// The `clocks` handle ensures that the clocks are now configured and gives</span>
<span class="c">// the `Delay::new` function access to the configured frequency. With</span>
<span class="c">// this information it can later calculate how many cycles it has to</span>
<span class="c">// wait. The function also consumes the System Timer peripheral, so that no</span>
<span class="c">// other function can access it. Otherwise the timer could be reset during a</span>
<span class="c">// delay.</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">delay</span> <span class="o">=</span> <span class="nn">Delay</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">cp</span><span class="py">.SYST</span><span class="p">,</span> <span class="n">clocks</span><span class="p">);</span>
<span class="c">// Now, enjoy the lightshow!</span>
<span class="k">loop</span> <span class="p">{</span>
<span class="n">led</span><span class="nf">.set_high</span><span class="p">()</span><span class="nf">.ok</span><span class="p">();</span>
<span class="n">delay</span><span class="nf">.delay_ms</span><span class="p">(</span><span class="mi">1_000_u16</span><span class="p">);</span>
<span class="n">led</span><span class="nf">.set_low</span><span class="p">()</span><span class="nf">.ok</span><span class="p">();</span>
<span class="n">delay</span><span class="nf">.delay_ms</span><span class="p">(</span><span class="mi">1_000_u16</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Let’s build it!</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> cargo build <span class="nt">--release</span>
Compiling semver-parser v0.7.0
Compiling typenum v1.12.0
<span class="o">[</span>...]
Compiling cortex-m-rt-macros v0.1.8
Compiling stm32f1xx-hal v0.6.1
Compiling blinky-rust v0.1.0 <span class="o">(</span>/home/xxx/xxx/Rust/embedded/rusty-blink<span class="o">)</span>
Finished release <span class="o">[</span>optimized] target<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>34.63s
</code></pre></div></div>
<p>For the blink example you can leave out the <code class="language-plaintext highlighter-rouge">--release</code>, but when you compile a more complicated program you will run out of device memory fast without optimization enabled.</p>
<p>My resulting program takes 864 bytes of memory (2,8kB without LTO). To check this, you have to convert the compiler output (an <em>elf</em> file) to hex/binary.
You can for example use <code class="language-plaintext highlighter-rouge">arm-none-eabi-objcopy -O binary target/thumbv7m-none-eabi/release/blinky-rust blinky-rust.bin</code> and then <code class="language-plaintext highlighter-rouge">ls</code>.</p>
<p>Now you are ready to flash the controller with <code class="language-plaintext highlighter-rouge">cargo flash</code>.
It should auto-detect your ST-Link and use it to program the microcontroller.
You only have to specify the target chip (<code class="language-plaintext highlighter-rouge">stm32f103C8</code>).</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> cargo flash <span class="nt">--chip</span> stm32f103C8 <span class="nt">--release</span>
Finished release <span class="o">[</span>optimized] target<span class="o">(</span>s<span class="o">)</span> <span class="k">in </span>0.02s
Flashing /home/xxx/xxx/Rust/embedded/rusty-blink/target/thumbv7m-none-eabi/release/blinky-rust
WARN probe_rs::architecture::arm::core::m4 <span class="o">></span> Reason <span class="k">for </span>halt has changed, old reason was Halted<span class="o">(</span>Request<span class="o">)</span>, new reason is Exception
WARN probe_rs::architecture::arm::core::m4 <span class="o">></span> Reason <span class="k">for </span>halt has changed, old reason was Halted<span class="o">(</span>Breakpoint<span class="o">)</span>, new reason is Request
WARN probe_rs::architecture::arm::core::m4 <span class="o">></span> Reason <span class="k">for </span>halt has changed, old reason was Halted<span class="o">(</span>Request<span class="o">)</span>, new reason is Exception
Erasing sectors ✔ <span class="o">[</span>00:00:00] <span class="o">[</span><span class="c">#############################] 1.00KB/ 1.00KB @ 1.49KB/s (eta 0s )</span>
Programming pages ✔ <span class="o">[</span>00:00:01] <span class="o">[</span><span class="c">#############################] 1.00KB/ 1.00KB @ 583B/s (eta 0s )</span>
</code></pre></div></div>
<p>(The warnings can be ignored, <code class="language-plaintext highlighter-rouge">cargo flash</code> is still a young project and has some faults.)</p>
<p>You should now see the blinking onboard LED!</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005642_edit.jpg">
<img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005642_edit_small.jpg" />
</a>
</div>
<figcaption>
Blinking LED on BluePill
</figcaption>
</figure>
<h2 id="im-hyped-whats-next">I’m hyped! What’s next?</h2>
<p>Good you asked! I prepared some link’s for you:</p>
<ul>
<li><a href="https://docs.rs/stm32f1xx-hal/latest/stm32f1xx_hal/index.html">The <code class="language-plaintext highlighter-rouge">stm32f1xx_hal</code>s documentation</a></li>
<li><a href="https://github.com/stm32-rs/stm32f1xx-hal/tree/master/examples">Plenty of examples for the <code class="language-plaintext highlighter-rouge">stm32f1xx_hal</code></a></li>
<li>You can find this example’s code on <a href="https://gitlab.com/jounathaen-projects/embedded-rust/blinky-rust">Gitlab</a></li>
</ul>
<p>I also plan to do some more posts on this, but you know… time and motivation.
If you have any questions or want to remind me to write some more posts, feel free to contact me! 🙂👍</p>Jonathan KlimtGetting started with embedded Rust. Blinking LED on Bluepill. (Thumbnail: Rust Embedded WG - [CC-BY](https://creativecommons.org/licenses/by/4.0/))Rust on STM32: Blinking an LED2019-02-06T01:00:00+01:002020-07-22T21:40:47+02:00https://jonathanklimt.de/electronics/programming/embedded-rust/rust-STM32F103-blink<p><br /></p>
<hr />
<hr />
<p><strong>DEPRECATED: I have created a more up-to-date version of this article. You can find it <a href="/electronics/programming/embedded-rust/rust-on-stm32-2/">here</a> !</strong></p>
<hr />
<hr />
<p><br /></p>
<p><strong>GOAL:</strong> Blink the onboard LED on a BluePill board using Rust on a Linux Mint (Ubuntu based) machine.</p>
<p><strong>Update/Information:</strong> The rustc version I used is <code class="language-plaintext highlighter-rouge">rustc 1.31.1</code> (stable) and you need to install the ARM target with <code class="language-plaintext highlighter-rouge">rustup target add thumbv7m-none-eabi</code> beforehand.</p>
<p>I also put the code on <a href="https://gitlab.com/jounathaen/stm32_blink">Gitlab</a>.</p>
<h2 id="preface">Preface</h2>
<p>I have mainly used Atmel microcontrollers so far, but they are rather expensive, slow and have a totally confusing pinout. (And I think, they have no real future).
So I wanted to use a better controller and setup the flow to program it on my Linux Mint machine (should work on Ubuntu as well).
<strong>I assume, that the reader already has at least a little knowledge in Rust, Linux, and Microcontrollers</strong></p>
<p>One of the possibly cheapest and general purpose boards is the STM32F103 board aka “BluePill”.
These sell for less than 2€ on <a href="https://www.aliexpress.com/item/STM32F103C8T6-ARM-STM32-Minimum-System-Development-Board-Module-For-Arduino/32278016818.html">AliExpress</a>,
but I’d recommend you the more up-to-date version, the “BlackPill” (<a href="https://www.aliexpress.com/item/STM32F103C8T6-ARM-STM32-Minimum-Development-Board-Module-MCU-Core-Board-MicroUSB-for-Arduino-Diy-Kit/32969629826.html">AliExpress</a>), because the BluePill usually has a wrong resistor which causes trouble when using the USB port.
Besides a slightly different pinout and the BlackPill being one row larger than the BluePill, they are basically the same.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005342_edit.jpg">
<img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005342_edit_small.jpg" />
</a>
</div>
<figcaption>
BluePill and BlackPill
</figcaption>
</figure>
<h2 id="setting-up-the-cargo-project">Setting up the Cargo Project</h2>
<p>First, we initialize a new Cargo project</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> cargo init stm32_blink
<span class="o">></span> <span class="nb">cd </span>stm32_blink
</code></pre></div></div>
<p>We modify our <code class="language-plaintext highlighter-rouge">Cargo.toml</code> file as follows to include the required libraries:</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"stm32_blink"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2018"</span>
<span class="nn">[profile.release]</span>
<span class="c"># optimize for size ('z' would optimize even more)</span>
<span class="py">opt-level</span> <span class="p">=</span> <span class="s">'s'</span>
<span class="c"># link with link time optimization (lto).</span>
<span class="py">lto</span> <span class="p">=</span> <span class="kc">true</span>
<span class="c"># enable debugging in release mode.</span>
<span class="py">debug</span> <span class="p">=</span> <span class="kc">true</span>
<span class="nn">[dependencies]</span>
<span class="c"># Gives us access to the STM32F1 registers</span>
<span class="nn">stm32f1</span> <span class="o">=</span> <span class="p">{</span><span class="py">version</span> <span class="p">=</span> <span class="s">"0.6.0"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"stm32f103"</span><span class="p">,</span> <span class="s">"rt"</span><span class="p">]}</span>
<span class="c"># provides startup code for the ARM CPU</span>
<span class="py">cortex-m-rt</span> <span class="p">=</span> <span class="s">"0.6.7"</span>
<span class="c"># provides access to low level ARM CPU registers (used for delay)</span>
<span class="py">cortex-m</span> <span class="p">=</span> <span class="s">"0.5.8"</span>
<span class="c"># provies a panic-handler (halting cpu)</span>
<span class="c"># (required when not using stdlib)</span>
<span class="py">panic-halt</span> <span class="p">=</span> <span class="s">"0.2.0"</span>
</code></pre></div></div>
<p>I the <code class="language-plaintext highlighter-rouge">cortex-m-rt</code> crate also requires a <code class="language-plaintext highlighter-rouge">memory.x</code> file, which specifies the memory layout of the board (See the <a href="https://docs.rs/cortex-m-rt/0.6.7/cortex_m_rt/#memoryx">documentation</a>).
The documentation already provides the file for the BluePill as an example, so we only have to create the file <code class="language-plaintext highlighter-rouge">memory.x</code> with the following content:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/* Linker script for the STM32F103C8T6 */
MEMORY
{
FLASH : ORIGIN = 0x08000000, LENGTH = 64K
RAM : ORIGIN = 0x20000000, LENGTH = 20K
}
</code></pre></div></div>
<p>We can now compile the program using <code class="language-plaintext highlighter-rouge">cargo rustc --target thumbv7m-none-eabi -- -C link-arg=-Tlink.x</code>, but that would be a bit inconvenient.
Therefore, we create the file <code class="language-plaintext highlighter-rouge">.cargo/config</code> (and the folder <code class="language-plaintext highlighter-rouge">.cargo</code>) with the following content (based on <a href="https://github.com/rust-embedded/cortex-m-quickstart/blob/master/.cargo/config">this file</a>):</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[build]</span>
<span class="c"># Instruction set of Cortex-M3 (used in BluePill)</span>
<span class="py">target</span> <span class="p">=</span> <span class="s">"thumbv7m-none-eabi"</span>
<span class="py">rustflags</span> <span class="p">=</span> <span class="p">[</span>
<span class="c"># use the Tlink.x scrip from the cortex-m-rt crate</span>
<span class="s">"-C"</span><span class="p">,</span> <span class="s">"link-arg=-Tlink.x"</span><span class="p">,</span>
<span class="p">]</span>
</code></pre></div></div>
<p>Hoooray, now we can compile Rust code into an ARM <em>elf</em> file!
(However, the code we have does not compile yet)</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> cargo build <span class="nt">--release</span>
<span class="o">></span> file target/thumbv7m-none-eabi/release/stm32_blink
target/thumbv7m-none-eabi/release/stm32_blink:
ELF 32-bit LSB executable, ARM, EABI5 version 1 <span class="o">(</span>SYSV<span class="o">)</span>,
statically linked, with debug_info, not stripped
</code></pre></div></div>
<h2 id="the-blink-program">The Blink Program</h2>
<p>This is the content of the <code class="language-plaintext highlighter-rouge">src/main.rs</code> file:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// std and main are not available for bare metal software</span>
<span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>
<span class="k">extern</span> <span class="n">crate</span> <span class="n">stm32f1</span><span class="p">;</span>
<span class="k">extern</span> <span class="n">crate</span> <span class="n">panic_halt</span><span class="p">;</span>
<span class="k">extern</span> <span class="n">crate</span> <span class="n">cortex_m_rt</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">cortex_m_rt</span><span class="p">::</span><span class="n">entry</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">stm32f1</span><span class="p">::</span><span class="n">stm32f103</span><span class="p">;</span>
<span class="c">// use `main` as the entry point of this application</span>
<span class="nd">#[entry]</span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="k">-></span> <span class="o">!</span> <span class="p">{</span>
<span class="c">// get handles to the hardware</span>
<span class="k">let</span> <span class="n">peripherals</span> <span class="o">=</span> <span class="nn">stm32f103</span><span class="p">::</span><span class="nn">Peripherals</span><span class="p">::</span><span class="nf">take</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">let</span> <span class="n">gpioc</span> <span class="o">=</span> <span class="o">&</span><span class="n">peripherals</span><span class="py">.GPIOC</span><span class="p">;</span>
<span class="k">let</span> <span class="n">rcc</span> <span class="o">=</span> <span class="o">&</span><span class="n">peripherals</span><span class="py">.RCC</span><span class="p">;</span>
<span class="c">// enable the GPIO clock for IO port C</span>
<span class="n">rcc</span><span class="py">.apb2enr</span><span class="nf">.write</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="n">w</span><span class="nf">.iopcen</span><span class="p">()</span><span class="nf">.set_bit</span><span class="p">());</span>
<span class="n">gpioc</span><span class="py">.crh</span><span class="nf">.write</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="k">unsafe</span><span class="p">{</span>
<span class="n">w</span><span class="nf">.mode13</span><span class="p">()</span><span class="nf">.bits</span><span class="p">(</span><span class="mi">0b11</span><span class="p">);</span>
<span class="n">w</span><span class="nf">.cnf13</span><span class="p">()</span><span class="nf">.bits</span><span class="p">(</span><span class="mi">0b00</span><span class="p">)</span>
<span class="p">});</span>
<span class="k">loop</span><span class="p">{</span>
<span class="n">gpioc</span><span class="py">.bsrr</span><span class="nf">.write</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="n">w</span><span class="nf">.bs13</span><span class="p">()</span><span class="nf">.set_bit</span><span class="p">());</span>
<span class="nn">cortex_m</span><span class="p">::</span><span class="nn">asm</span><span class="p">::</span><span class="nf">delay</span><span class="p">(</span><span class="mi">2000000</span><span class="p">);</span>
<span class="n">gpioc</span><span class="py">.brr</span><span class="nf">.write</span><span class="p">(|</span><span class="n">w</span><span class="p">|</span> <span class="n">w</span><span class="nf">.br13</span><span class="p">()</span><span class="nf">.set_bit</span><span class="p">());</span>
<span class="nn">cortex_m</span><span class="p">::</span><span class="nn">asm</span><span class="p">::</span><span class="nf">delay</span><span class="p">(</span><span class="mi">2000000</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p><strong>Explanation:</strong>
<code class="language-plaintext highlighter-rouge">#![no_std]</code> is required, because we build a bare-metal application but the standard library requires an operating system.
<code class="language-plaintext highlighter-rouge">#![no_main]</code> tells the compiler that we don’t use the default <code class="language-plaintext highlighter-rouge">main</code> function with the argument vector and return type.
This wouldn’t make sense, since we don’t have an OS or other kind of runtime which would call the function and handle the return value.
Instead, the <code class="language-plaintext highlighter-rouge">cortex_m_rt</code> crate contains a minimal runtime and the <code class="language-plaintext highlighter-rouge">#[entry]</code> macro, which specifies our custom entry function (<code class="language-plaintext highlighter-rouge">fn main () -> ! {}</code>), which we call <code class="language-plaintext highlighter-rouge">main</code> as well (don’t be confused).
More information about bare metal software in Rust can be found for example in the <a href="https://os.phil-opp.com/">“Writing an OS in Rust”</a> Blog.</p>
<p>Inside the <code class="language-plaintext highlighter-rouge">main</code> function we create a handle to the peripheral object, which “owns” all the peripherals and registers with <code class="language-plaintext highlighter-rouge">stm32f103::Peripherals::take()</code>.
<code class="language-plaintext highlighter-rouge">rcc</code> and <code class="language-plaintext highlighter-rouge">gpioc</code> are handles to the “reset and clock control” and GPIO register bank C (the onboard LED on my BluePill is connected to pin C13 but this might differ).</p>
<p>To use a pin as output we have to enable the clock for that specific GPIO pin bank by setting the <em>IOPCEN</em> bit in the <em>APB2EN</em> register.
Writing a byte in a peripheral register using the <code class="language-plaintext highlighter-rouge">stm32f1</code> crate is done by calling the <code class="language-plaintext highlighter-rouge">write</code> function on that register struct (<code class="language-plaintext highlighter-rouge">rcc.apb2enr.write()</code>).
This function takes a <a href="https://doc.rust-lang.org/std/ops/trait.FnOnce.html"><code class="language-plaintext highlighter-rouge">FnOnce</code></a> function as an argument.
The parameter which is passed to this function is in the first case of type <code class="language-plaintext highlighter-rouge">gpioc::crh::W</code> which implements functions to access the single bit fields in that very register (here: <code class="language-plaintext highlighter-rouge">w.iopcen()</code>).
This in turn has a function <code class="language-plaintext highlighter-rouge">set_bit</code>, which is used to set the bit in that hardware register and thus enabling the clock for the GPIO C bank.</p>
<p>The same applies for the <em>CRH</em> register, which is used to configure the pin as an output pin at highest speed (<em>MODE</em>=0b11 and <em>CNF</em>=00).
The <code class="language-plaintext highlighter-rouge">bits</code> function of the <code class="language-plaintext highlighter-rouge">mode13</code> and <code class="language-plaintext highlighter-rouge">cnf13</code> bit field is unsafe, but I can’t tell you why.
Therefore, we have to mark the functions inside the closure as unsafe as well.</p>
<p><strong>Hint:</strong>
To get a documentation of the stm32f1 crate, generate it yourself with <code class="language-plaintext highlighter-rouge">cargo doc --open</code></p>
<p>In the infinite loop, we write to the <em>Port bit set/reset register (BSRR)</em> and the <em>Port bit reset register (BRR)</em> to set pin C13 to high respectively low.</p>
<p>The <a href="https://docs.rs/cortex-m/0.5.8/cortex_m/asm/fn.delay.html"><code class="language-plaintext highlighter-rouge">delay</code> function</a> <em>busy waits</em> for at least the given number of cycles. The number here was just randomly taken and represents around 250ms.
The better approach would be to utilize the onboard timers, but for this simple example, <code class="language-plaintext highlighter-rouge">delay</code> is sufficient.</p>
<h2 id="creating-the-binary">Creating the Binary</h2>
<p>For the next steps, we need an ARM toolchain installed on our system.
On Linux Mint (Ubuntu), this can be done with:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> <span class="nb">sudo </span>apt <span class="nb">install </span>binutils-arm-none-eabi
</code></pre></div></div>
<p><em>Update:</em> I use the toolchain from the Ubuntu package manager. The toolchain which is installed with the thumbv7m target can also be <a href="https://github.com/rust-embedded/cargo-binutils">invoked using Cargo</a> (I’ll try this next time).</p>
<h4 id="optional-disassembly"><em>Optional:</em> Disassembly</h4>
<p>We can now view the disassembly of the <em>elf</em> file:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> arm-none-eabi-objdump <span class="nt">--disassemble</span> <span class="se">\</span>
target/thumbv7m-none-eabi/release/stm32_blink | less
</code></pre></div></div>
<p><br /></p>
<p>So far, we only operated with the <em>elf</em> file.
This format does not only contain the binary code but also some headers and stuff (see <a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format">Wikipedia</a>).
This is useful, when another software like an operating system or bootloader launches the file.
However, to run the program bare-metal we don’t need an <em>elf</em> file, but rather a <em>bin</em> file.
This is an image of the software which can be written byte-by-byte into the memory of the microcontroller.</p>
<p>An <em>elf</em> file can be converted into a <em>bin</em> file with <code class="language-plaintext highlighter-rouge">objcopy</code>.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> arm-none-eabi-objcopy <span class="nt">-O</span> binary <span class="se">\</span>
target/thumbv7m-none-eabi/release/stm32_blink stm32_blink.bin
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">stm32_blink.bin</code> is now ready to flash.</p>
<h2 id="flashing-the-binary">Flashing the Binary</h2>
<p>There are multiple ways to program the board, for example using the UART, using a USB bootloader (like the Arduino has), or using a programmer like an ST-Link.
I’m using a ST-Link-V2 clone, which are almost as cheap as the Pill itself (<a href="https://www.aliexpress.com/item/1PCS-ST-LINK-Stlink-ST-Link-V2-Mini-STM8-STM32-Simulator-Download-Programmer-Programming-With-Cover/32920031233.html">AliExpress</a>).
You can even <a href="https://medium.com/@paramaggarwal/converting-an-stm32f103-board-to-a-black-magic-probe-c013cf2cc38c">flash one yourself</a> out of a BluePill/BlackPill.</p>
<p>Connect the 4 pins <em>SWDIO</em>, <em>GND</em>, <em>SWCLK</em>, and <em>3.3V</em> on the programmer with the corresponding pins on the BluePill and plug it into your PC.
<strong>Important:</strong> Disconnect any other power supply from the board, otherwise you can damage the board or in the worst case your PC.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005543_edit.jpg">
<img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005543_edit_small.jpg" />
</a>
</div>
<figcaption>
Connection between the ST-Link and the BluePill
</figcaption>
</figure>
<p>There are other ways to program the board with the ST-Link, such as <a href="https://github.com/rogerclarkmelbourne/Arduino_STM32/wiki/Programming-an-STM32F103XXX-with-a-generic-%22ST-Link-V2%22-programmer-from-Linux">openocd</a>,
but I will use the <a href="https://github.com/texane/stlink">open-source stlink</a> tools.
Unfortunately, this software is not available via the apt package manager, therefore we have to <a href="https://github.com/texane/stlink/blob/master/doc/compiling.md">compile it from source</a>.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># in a directory of your choice:</span>
<span class="o">></span> <span class="nb">sudo </span>apt <span class="nb">install </span>libusb-1.0 libusb-1.0-0-dev
<span class="o">></span> git clone https://github.com/texane/stlink
<span class="o">></span> <span class="nb">cd </span>stlink
<span class="o">></span> make all
</code></pre></div></div>
<p>TODO UPDATE:
cmake ..
make -j4
make install DESTDIR=/home/jonathan/.local</p>
<p>This creates the executables <code class="language-plaintext highlighter-rouge">st-flash</code> and <code class="language-plaintext highlighter-rouge">st-info</code>, which reside now in <code class="language-plaintext highlighter-rouge">build/Release/</code>.
There are different approaches to “install” these executables.
I will now copy them into a system folder.
However, it may be desirable to only copy them into a folder in your home directory which is added to <code class="language-plaintext highlighter-rouge">$PATH</code> or simply prefix all commands with the full path to the executable instead.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> <span class="nb">sudo cp </span>build/Release/st-<span class="o">{</span>flash,info<span class="o">}</span> <span class="se">\</span>
build/Release/src/gdbserver/st-util /usr/local/bin
<span class="o">></span> which st-info
/usr/local/bin/st-info
</code></pre></div></div>
<h4 id="optional-udev-rules"><em>Optional:</em> Udev Rules</h4>
<p>To access the ST-Link without root permissions, copy the udev rules from the source code to <code class="language-plaintext highlighter-rouge">/etc/udev/rules.d/</code></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> <span class="nb">sudo cp </span>etc/udev/rules.d/<span class="k">*</span>.rules /etc/udev/rules.d
<span class="o">></span> <span class="nb">sudo </span>udevadm control <span class="nt">--reload-rules</span> <span class="o">&&</span> udevadm trigger
</code></pre></div></div>
<p><br />
We can verify the connection:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> st-info <span class="nt">--descr</span>
F1 Medium-density device
</code></pre></div></div>
<h4 id="optional-erase-existing-softwarebootloaders"><em>Optional:</em> Erase existing software/bootloaders:</h4>
<p>I had a stm32duino bootloader running on my BluePill, which lead to errors like <code class="language-plaintext highlighter-rouge">WARN common.c: unknown chip id! 0x5fa0004</code> upon flashing.
To fix that, I had to erase the chip. This is done by executing the following command <em>right after pressing the reset button</em> on the board:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> st-flash erase
</code></pre></div></div>
<p><br /></p>
<p>To flash the <em>bin</em> file, we execute:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> st-flash write stm32_blink.bin 0x8000000
st-flash 1.5.1-12-g30de1b3
2019-02-07T00:23:15 INFO common.c: Loading device parameters....
<span class="c">#[...]</span>
2019-02-07T00:23:15 INFO common.c: Flash written and verified!
jolly good!
</code></pre></div></div>
<p><strong>Success!</strong></p>
<h3 id="summary-of-commands-to-build-and-flash-the-software">Summary of Commands to Build and Flash the Software:</h3>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">></span> cargo build <span class="nt">--release</span>
<span class="o">></span> arm-none-eabi-objcopy <span class="nt">-O</span> binary <span class="se">\</span>
target/thumbv7m-none-eabi/release/stm32_blink stm32_blink.bin
<span class="o">></span> st-flash write stm32_blink.bin 0x8000000
</code></pre></div></div>
<p><strong>That’s it!</strong> Now the LED on the board is blinking!</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005642_edit.jpg">
<img src="https://jonathanklimt.de/assets/images/stm32_flash/IMG_20190207_005642_edit_small.jpg" />
</a>
</div>
<figcaption>
Blinking LED on BluePill
</figcaption>
</figure>
<p><em>TODO: Create a single Cargo Command to Flash the Board</em></p>Jonathan KlimtUsing Rust to blink a LED on a STM32F103 aka "BluePill" on Linux Mint/UbuntuHow to (not) Change the Keyboard of a MSI-GS302019-01-11T01:00:00+01:002020-07-22T21:40:47+02:00https://jonathanklimt.de/electronics/msi-keyboard<p>I use the US-international keyboard layout since a few years now, as the German layout sucks when it comes to programming.
But my laptop (MSI-GS30) still has a German layout.
Most of the time, I use the laptop inside a docking station, but as I was traveling the last few weeks I decided to replace the keyboard with a US-version one.</p>
<p>On AliExpress I found <a href="https://www.aliexpress.com/item/New-notebook-keyboard-for-MSI-GS40-GS32-GS30-GS43VR-6QE-233RU-6RE-007RU-RUSSIAN-SPANISH-Thailand/32912704270.html">a replacement part</a> for around 50€.
It arrived soon (about 1 week) and after I payed additional 10€ as import VAT, so the total costs were about 65€.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:46:43_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:46:43_small_edit.jpg" />
</a>
</div>
<figcaption>
The new keyboard
</figcaption>
</figure>
<h3 id="step-1---open-the-backside-of-the-laptop">Step 1 - Open the Backside of the Laptop</h3>
<p>The first step was to open the laptop.</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:46:34_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:46:34_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:48:21_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:48:21_small.jpg" />
</a>
</div>
<figcaption>
Opening the laptop
</figcaption>
</figure>
<h3 id="step-2---disassembly-of-the-laptop">Step 2 - Disassembly of the Laptop</h3>
<p>Then I removed the battery and the fan to remove the mainboard.
Unfortunately, two screws of the mainboard are located below the CPU’s heatpipes.
So I had to remove the cooler as well.</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:58:00_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:58:00_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:59:51_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T13:59:51_small.jpg" />
</a>
</div>
<figcaption>
Disassembly of the laptop
</figcaption>
</figure>
<h3 id="step-3---realize-that-you-fucked-up-and-revert-everything">Step 3 - Realize that you fucked up and revert everything</h3>
<p>At this point I found out, that you can’t remove the keyboard because it is riveted to the case.
Great! So I had do reassemble the Laptop.
Removing the CPU-cooler was totally unnecessary. I applied some more heat-conducting paste to the cooler, but I only had the cheap one at home.
This works, but I ordered some better one to ensure my CPU stays cool (which is a major issue with this laptop).</p>
<p>However, I can still change the layout by removing the key-caps and replacing them with the ones from the US-keyboard.</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T14:18:09_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T14:18:09_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T14:41:43_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T14:41:43_small.jpg" />
</a>
</div>
<figcaption>
Removing the key caps from the laptop
</figcaption>
</figure>
<p>When you remove the caps you have to be very careful, the “scissors”-mechanism break extremely easy.
In the whole project I broke two of them. (But I’ve still got a whole keyboard full of replacement parts 🤷)</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T15:14:22_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T15:14:22_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T15:18:54_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/msi-keyboard-replacement/2019-01-11T15:18:54_small.jpg" />
</a>
</div>
<figcaption>
Putting on the US layout keys and final product
</figcaption>
</figure>
<p>Summary: I now have a nice US layout on my laptop, it has cost a lot of money for a rare use case and I have to apply new heat-conducting paste to my cooler 👍.</p>Jonathan KlimtChanging the physical keyboard of a MSI-GS30 from german to USGallery Safety Barrier2018-11-15T01:00:00+01:002020-07-22T21:40:03+02:00https://jonathanklimt.de/woodwork/saftey-barrier<p>A friend of mine, Laura, lives in a flat with a kind of gallery.
The landlord build in a kitchen and a bath in the formerly one large room and now there is some space above that, where the bed is located.
Unfortunately there is no ceiling or something similar and sleeping next to a 2.5m abyss is apparently a bit uncomfortable.
So Laura wanted to build some kind of safety barrier.
Laura designed a X shaped barrier with three “pillars” and so I made some rough sketches in Inkscape.
I also tried some other shapes, but I couldn’t convice Laura, so we agreed on a refinement of her design.
(It is simmilar to the first item in the sketches.)</p>
<figure class="custom">
<div class="single">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/LauraGeländerFachwerk_Skizzen.svg" />
</div>
<figcaption>
Sketches for the Barrier
</figcaption>
</figure>
<p>Dirk came around and joined the handicraft group.
So we went out and bought the wood for it.</p>
<p>One long post (8cm * 8 cm I think), two boards for the cross-bars, and two for the upper and lower mount.</p>
<p>First, we cut the long post into the three pieces and laid everything out to draw the cut lines for the cross-bars.
(It was a bit of a challenge, that the floor of the gallery was not parallel to the ceiling).</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_172925_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_172925_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_174043_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_174043_small.jpg" />
</a>
</div>
<figcaption>
Laying out the pieces to the floor to draw the cut lines
</figcaption>
</figure>
<p>We decided to cut slots into the posts and stick the cross bars into it.
This avoids the need for brackets and is probably more stable.
I cut the slots with my router, Dirk cleaned up the slots with a chisel and Laura sanded the posts.
I wanted to create a router jig, but I forgot the wood to do it, so we used scrap wood and clamps to create some guides for the router.
It was not super precise, but as the cross bars overlap the cuts a little bit, this was OK.</p>
<figure class="custom">
<div class="third">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_152151_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_152151_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_133500_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_133500_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_133504_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_133504_small.jpg" />
</a>
</div>
<figcaption>
Cutting the Slots for the Cross-Bars
</figcaption>
</figure>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_185806_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_185806_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_185820_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181013_185820_small.jpg" />
</a>
</div>
<figcaption>
Cleaning and Testing the Slots for the Cross-Bars
</figcaption>
</figure>
<p>Then we could assemble it and put it in place to validate it.</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_170842_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_170842_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_171027_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181014_171027_small.jpg" />
</a>
</div>
<figcaption>
Testing the whole thing
</figcaption>
</figure>
<p>Laura doesn’t like the optics of light wood, so she painted it dark.
Once the paint was dry, we drilled the holes into the ceiling and the floor (which was super loud and one drill broke) and screwed it in.</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181018_213302_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181018_213302_small.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181018_203830_medium.jpg">
<img src="https://jonathanklimt.de/assets/images/safety-barrier/IMG_20181018_203830_small.jpg" />
</a>
</div>
<figcaption>
Final Product
</figcaption>
</figure>Jonathan KlimtBuilding a safety barrier for the gallery in a friends student apartmentLaundry Basket2018-10-03T02:00:00+02:002020-07-22T21:40:03+02:00https://jonathanklimt.de/woodwork/laundry-basket<p>This is about a project from March 2018, which I thought was a nice topic for the blog here.
Unfortunately I can’t remember all details in sizes of the pieces and I don’t have photos of all steps.</p>
<p>For my first appartment, I bought this cheap <a href="https://www.amazon.de/gp/product/B00576BK74/ref=oh_aui_search_detailpage?ie=UTF8&psc=1">laundry basket</a> from amazon.
It did the job for a few years, but the metal rods from the frame deformed, so it was time for a new one.</p>
<p>I looked online, but I found nothing I liked.
Stuff was crappy and/or too expensive.</p>
<p>So, I decided to build my own:</p>
<p>I bought</p>
<ul>
<li>4x 2.5m * 6cm * 1cm oak wood lath for the side faces.</li>
<li>1x 2.5m * 4cm * 3cm oak post for the frame.</li>
<li>1x 35cm * 35cm * 1.5cm plywood from the scrap basket at the hardware shop, for the bottom.</li>
<li>2x 1m *4mm diameter round beech rod</li>
</ul>
<p>I let the hardware store cut the oak lath into 50cm pieces, what was a mistake.
Not because the lenght was wrong, but because they carged 0.50€ for each cut, which was 7€ for 1 minute of work for them.
Conclusion: <strong>FUCK YOU Bauhaus</strong>, I’ll try to never buy anything at these stores anymore.</p>
<p>All in all it costed me about 70€-80€, which is relatively expensive.
But laundry baskets made of oak are even more expensive, if you buy them ready-made.
And I like thinkering, so ¯\<em>(ツ)</em>/¯ .</p>
<p>First I cut the lath into pieces of 16cm and cleaned my desk from all the scrap that accumulates over time there.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_201414_smaller.jpg">
<img src="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_201414_smaller.jpg" />
</a>
</div>
</figure>
<p>Then I drilled two 4mm holes into each piece, to put them together with a wood plug later on.</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_204210_smaller.jpg">
<img src="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_204210_smaller.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_210306_smaller_cropped.jpg">
<img src="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_210306_smaller_cropped.jpg" />
</a>
</div>
<figcaption>
Drilling the holes
</figcaption>
</figure>
<p>I don’t have a drill press in my student appartment, so a hand drill with a drill jig had to do the job.
It was not super precise, but it worked in the end.</p>
<p>I also drilled the holes in the oak posts, so that all pieces could be put together with wood plugs.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_223402_smaller.jpg">
<img src="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_223402_smaller.jpg" />
</a>
</div>
<figcaption>
Drilling the posts
</figcaption>
</figure>
<p>This is how the assembly of the frame looked like:</p>
<figure class="custom">
<div class="half">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_222754_smaller.jpg">
<img src="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180309_222754_smaller.jpg" />
</a>
<a class="image-popup" href="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180310_140628_smaller.jpg">
<img src="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180310_140628_smaller.jpg" />
</a>
</div>
</figure>
<p>The remaining oak late was cut with 45° miter cuts into 4 pieces, which are glued together to form the top opening.
I used the plastic fabric inlay from the old laundry basket.
My first idea was to fix it with screws to the inside of the basket, but it is stiff enough, so that this is not necessary.
simply screwed the frame onto the bottom frame, the sides are not glued at all.
I used some small pieces of wood, to add some stands, these are glued as well.</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180310_150524_smaller.jpg">
<img src="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20180310_150524_smaller.jpg" />
</a>
</div>
</figure>
<p>This is the final laundry basket.
I’m quite happy with the result.
It took al in all 1.5 days to finish and it fits my taste way better than anything affordable I’ve seen.</p>
<p>When I was oiling my dining table, I decided to oil the laundry basket as well, but this was a bit of a mistake.
It became a lot darker and I quite liked the pale look of the unthreated wood.
But it is still ok…</p>
<figure class="custom">
<div class="single">
<a class="image-popup" href="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20181003_150802_smaller_edited.jpg">
<img src="https://jonathanklimt.de/assets/images/waeschekorb/IMG_20181003_150802_smaller_edited.jpg" />
</a>
</div>
<figcaption>
Final basket
</figcaption>
</figure>Jonathan KlimtBuilding an oak wood laundry basket at my student roomFirst Hello World Post2018-08-12T00:09:57+02:002020-07-22T21:29:42+02:00https://jonathanklimt.de/blog/programming/hello_world<p>This is the first post and it has to start with an <em>hello world</em> example</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="cp">#include <stdio>
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(){</span>
<span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o"><<</span> <span class="s">"Hello World"</span> <span class="o"><<</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>Jonathan KlimtThis is the first post and it has to start with an hello world example