{"title":"Georg's Log - misc","link":[{"@attributes":{"href":"https:\/\/gms.tf\/","rel":"alternate"}},{"@attributes":{"href":"https:\/\/gms.tf\/feeds\/misc.atom.xml","rel":"self"}}],"id":"https:\/\/gms.tf\/","updated":"2024-06-17T01:00:00+02:00","entry":[{"title":"Blocking Audio Ads","link":{"@attributes":{"href":"https:\/\/gms.tf\/blocking-audio-ads.html","rel":"alternate"}},"published":"2024-06-17T01:00:00+02:00","updated":"2024-06-17T01:00:00+02:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2024-06-17:\/blocking-audio-ads.html","summary":"<p>This article describes an approach to blocking ads in audio\nfiles and introduces software that implements it.<\/p>\n<h2 id=\"motivation\">Motivation<a class=\"headerlink\" href=\"#motivation\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The modern <a href=\"https:\/\/en.wikipedia.org\/wiki\/World_Wide_Web\">web<\/a> is unbearable without a good ad-blocker such as\n<a href=\"https:\/\/github.com\/gorhill\/uBlock\">uBlock Origin<\/a>. Unfortunately, the online marketing\nindustry, while destroying the web, providing creative\ndistribution networks for <a href=\"https:\/\/en.wikipedia.org\/wiki\/Malvertising\">malware<\/a> and <a href=\"https:\/\/www.jstor.org\/stable\/26508116\">disinformation<\/a>\ncampaigns there \u2026<\/p>","content":"<p>This article describes an approach to blocking ads in audio\nfiles and introduces software that implements it.<\/p>\n<h2 id=\"motivation\">Motivation<a class=\"headerlink\" href=\"#motivation\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The modern <a href=\"https:\/\/en.wikipedia.org\/wiki\/World_Wide_Web\">web<\/a> is unbearable without a good ad-blocker such as\n<a href=\"https:\/\/github.com\/gorhill\/uBlock\">uBlock Origin<\/a>. Unfortunately, the online marketing\nindustry, while destroying the web, providing creative\ndistribution networks for <a href=\"https:\/\/en.wikipedia.org\/wiki\/Malvertising\">malware<\/a> and <a href=\"https:\/\/www.jstor.org\/stable\/26508116\">disinformation<\/a>\ncampaigns there, has also discovered <a href=\"https:\/\/en.wikipedia.org\/wiki\/Podcast\">podcasts<\/a> as a\nmedium for spreading obnoxious advertisements.<\/p>\n<h2 id=\"the-challenge\">The Challenge<a class=\"headerlink\" href=\"#the-challenge\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Since <a href=\"https:\/\/en.wikipedia.org\/wiki\/Podcast\">podcasts<\/a> (also known as audiocasts) usually\nintegrate advertisement clips inline as part of the audio stream\nin the audio file such that they can't simply be filtered by\nblocking certain URLs or manipulating scripts, as it's done with\nads on websites or even Youtube.  (NB: as of 2024, <a href=\"https:\/\/www.bleepingcomputer.com\/news\/google\/youtube-tests-harder-to-block-server-side-ad-injection-in-videos\/\">Youtube is\nexperimenting<\/a> rendering ads server-side into the\nvideo-stream)<\/p>\n<p>However, many podcasts, especially German ones, clearly mark the\nbeginning and the end of an advertisement by some sound bite or\njingle.\nSuch marking isn't entirely voluntarily, because a few\nlegal norms regarding <a href=\"https:\/\/de.wikipedia.org\/wiki\/Schleichwerbung#Situation_in_Deutschland\">broadcasting<\/a> and unfair business practices\ndo <a href=\"https:\/\/de.wikipedia.org\/wiki\/Telemediengesetz\">apply<\/a>.<\/p>\n<h2 id=\"a-solution\">A Solution<a class=\"headerlink\" href=\"#a-solution\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>One approach to blocking audio ads in podcasts is thus to\nautomatically search for the characteristic samples (templates)\nand cut everything in between out.<\/p>\n<p>I searched for software that implements this approach, but couldn't\nfind any.<\/p>\n<p>Thus, I created <a href=\"https:\/\/github.com\/gsauthof\/cutbynoise\">cutbynoise<\/a> - a small Python program that cuts\naudio files by characteristic templates. \nSee also its README for a detailed technical description of how it works.<\/p>\n<p>Of course, nobody wants to manually call <code>cutbynoise<\/code> on each new\nepisode of a podcast. I thus also created <a href=\"https:\/\/github.com\/gsauthof\/feed-util#castproxypy\">castproxy<\/a> - a\nsmall Python program that aggregates podcast feeds, caches\nembedded audio files, and is able to invoke audio filters on\nthem, such as <code>cutbynoise<\/code>.<\/p>\n<h2 id=\"results\">Results<a class=\"headerlink\" href=\"#results\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Using <a href=\"https:\/\/en.wikipedia.org\/wiki\/Cross-correlation\">cross-correlation<\/a> to search for ad start\/stop markers works\nsurprisingly well on real world audio data.\nEspecially given that podcasts are usually heavily post-processed\nand of course lossily compressed.<\/p>\n<p>I'm running castproxy and cutbynoise for two months now on a\ndozen or so podcasts.<\/p>\n<p>So far this setup successfully filtered out 5301 seconds of super\nannoying ads! Almost one and a half hours!<\/p>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"adblocking"}},{"@attributes":{"term":"podcast"}},{"@attributes":{"term":"audiocast"}},{"@attributes":{"term":"audio"}},{"@attributes":{"term":"signal-processing"}}]},{"title":"Shift Typing","link":{"@attributes":{"href":"https:\/\/gms.tf\/shift-typing.html","rel":"alternate"}},"published":"2023-10-07T18:00:00+02:00","updated":"2023-10-07T18:00:00+02:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2023-10-07:\/shift-typing.html","summary":"<p>This article introduces <a href=\"https:\/\/github.com\/gsauthof\/shift-tutor\">shift-tutor<\/a>, a Linux program that filters\nshift key events to improve or verify touch typing skills.<\/p>\n<h2 id=\"rationale\">Rationale<a class=\"headerlink\" href=\"#rationale\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Most keyboards contain two shift keys, i.e. one located on the\nleft bottom and the other on the opposite side:<\/p>\n<p><img src=\"https:\/\/gms.tf\/image\/ansi-querty-ergo-wp-commons.svg\" alt=\" Keyboard with split+rotated layout, similar to the layout of the Microsoft Natural keyboard\" width=\"100%\"\/>\n<small>Source: <a href=\"https:\/\/commons.wikimedia.org\/wiki\/User:Mliu92\">Mliu92<\/a>,\n<a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:ANSI_US_QWERTY_(Windows,_split_+_rotated).svg\">ANSI\nUS QWERTY (Windows, split + rotated)<\/a>, <a href=\"https:\/\/creativecommons.org\/licenses\/by-sa\/4.0\/legalcode\" rel=\"license\">CC \u2026<\/a><\/small><\/p>","content":"<p>This article introduces <a href=\"https:\/\/github.com\/gsauthof\/shift-tutor\">shift-tutor<\/a>, a Linux program that filters\nshift key events to improve or verify touch typing skills.<\/p>\n<h2 id=\"rationale\">Rationale<a class=\"headerlink\" href=\"#rationale\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Most keyboards contain two shift keys, i.e. one located on the\nleft bottom and the other on the opposite side:<\/p>\n<p><img src=\"https:\/\/gms.tf\/image\/ansi-querty-ergo-wp-commons.svg\" alt=\" Keyboard with split+rotated layout, similar to the layout of the Microsoft Natural keyboard\" width=\"100%\"\/>\n<small>Source: <a href=\"https:\/\/commons.wikimedia.org\/wiki\/User:Mliu92\">Mliu92<\/a>,\n<a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:ANSI_US_QWERTY_(Windows,_split_+_rotated).svg\">ANSI\nUS QWERTY (Windows, split + rotated)<\/a>, <a href=\"https:\/\/creativecommons.org\/licenses\/by-sa\/4.0\/legalcode\" rel=\"license\">CC BY-SA 4.0<\/a><\/small><\/p>\n<p>The purpose of these arguably redundant keys is to simplify\nshifting while <a href=\"https:\/\/en.wikipedia.org\/wiki\/Touch_typing\">touch typing<\/a>. For example, when typing an upper\ncase 'A' the left hand should press the 'A' key with the little finger, i.e.\nwhich doesn't leave the home row, while the right hand is free\nand thus its little finger can easily leave the home row and press\nand hold the right shift key, before.<\/p>\n<p>A typist unskilled in touch typing may be tempted to enter upper\ncase characters with one hand, like a short cut that involves a\nsingular meta key. In our example, this may lead to upper case\n'A' being entered by pressing left shift with the left little \nfinger together with 'A' being pressed by the left ring finger.<\/p>\n<h2 id=\"the-problem\">The Problem<a class=\"headerlink\" href=\"#the-problem\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>When touch typing, underutilizing both available shift keys may\nlead to the following problems:<\/p>\n<ol>\n<li>suboptimal touch typing performance in terms of speed\n   and effective use of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Muscle_memory\">muscle memory<\/a><\/li>\n<li>health issues such as <a href=\"https:\/\/en.wikipedia.org\/wiki\/Repetitive_strain_injury\">repetitive strain injury<\/a><\/li>\n<\/ol>\n<h2 id=\"a-solution\">A Solution<a class=\"headerlink\" href=\"#a-solution\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>When attending an on-site course in a touch typing school, a good\nteacher may be able to observe suboptimal usage combinations of\nthe shift keys and give immediate feedback such that this bad habit\nis eliminated.<\/p>\n<p>This is similar to a presentation coach who watches out for filler words,\nperhaps rings a bell when a filler is uttered and\/or counts them such that\nthe student can pay into a filler word piggy bank.<\/p>\n<p>In other words, in these cases, a teacher supervises a student\nand gives immediate feedback to reinforce optimal behaviour in\norder to improve the learning outcome.<\/p>\n<h2 id=\"shift-tutor\">Shift-Tutor<a class=\"headerlink\" href=\"#shift-tutor\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p><a href=\"https:\/\/github.com\/gsauthof\/shift-tutor\">Shift-tutor<\/a> is a technical aid that can be used while\nlearning touch typing or for verifying touch typing skills. It\nsimply grabs all events of a keyboard device on Linux and filters\n(i.e. discards) key presses whenever the <em>wrong<\/em> shift key is\npressed. All other events are simply forwarded.<\/p>\n<p>In that way, the typist gets immediate feedback on shift key\nusage, i.e. since something like <code>left_shift + a<\/code> is discarded,\nthe better usage of <code>right_shift + a<\/code> is reinforced.<\/p>\n<p>Shift-tutor is written in Python, doesn't require much code and\nis arguably easy to extend. Thus, triggering some annoying bell\nwhenever a suboptimal key combination is pressed (in addition to\ndiscarding it), like in a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Operant_conditioning\">positive punishment operant\nconditioning<\/a> scheme is left as an exercise to the reader.<\/p>\n<h2 id=\"what-about-latency\">What about Latency?<a class=\"headerlink\" href=\"#what-about-latency\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Since keyboard events are filtered in user-space, such filtering\nas implemented by shift-tutor increases input latency somewhat.\nThat means in the best case shift-tutor requires 2\nkernel-\/user-space transitions and a few cycles in user space\nwhich adds a few microseconds or so.<\/p>\n<p>Depending on the load and available cores, waking up\nshift-tutor might even require a more expensive rescheduling or\ncontext-switching that would increase the latency more.<\/p>\n<p>However, over the years local input-to-display latency\nincreased to the <a href=\"https:\/\/danluu.com\/input-lag\/\">three digit millisecond range<\/a> with\nmodern computers and modern keyboards being in the <a href=\"https:\/\/danluu.com\/keyboard-latency\/\">two digit\nmillisecond range<\/a> already (see also <a href=\"https:\/\/hci.ur.de\/publications\/on_the_latency_of_usb-connected_input_devices_2019\">R. Wimmer, A. Schmid,\nand F. Bockes: On the Latency of USB-Connected Input Devices. ACM CHI\n2019<\/a> and <a href=\"https:\/\/web.cs.wpi.edu\/~claypool\/papers\/evlag\/\">S. Liu, and M. Claypool: EvLag - A tool for\nMonitoring and Lagging Linux Input Devices. ACM MMSys\n2021<\/a>).<br\/>\nMeaning, when measuring end-to-end input-to-display latency under\nactive shift-tutor, the added latency would likely be\nindistinguishable from measurement noise, as <a href=\"https:\/\/danluu.com\/term-latency\/\">with - say - tmux<\/a>.<br\/>\nNot speaking of additional relatively high network latency when working remotely\nvia ssh, without local echo, which nevertheless usually still\nallows for a quite fine touch typing experience.<\/p>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"keyboard"}},{"@attributes":{"term":"typing"}}]},{"title":"Fosi Audio TDA7498E Amplifier Review","link":{"@attributes":{"href":"https:\/\/gms.tf\/fosi-review.html","rel":"alternate"}},"published":"2022-12-31T20:00:00+01:00","updated":"2022-12-31T20:00:00+01:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2022-12-31:\/fosi-review.html","summary":"<p>Recently, I was in the market for a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Power_amplifier_classes#Class_D\">Class-D<\/a> amplifier\nto replace an aging model. This article reviews the very compact\n<a href=\"https:\/\/www.amazon.de\/Fosi-Audio-2-Kanal-Stereo-Audioverst%C3%A4rkerempf%C3%A4nger-Mini-HiFi-Klasse-D-Integrierter-Heimlautsprecher\/dp\/B077GWVHHN\">Fosi Audio TSA7498E 2 channel amplifier<\/a> I bought from a China\nbased business, via Amazon. tl;dr: Can't recommend, use something\nbetter.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#motivation\">Motivation<\/a><\/li>\n<li><a href=\"#selection\">Selection<\/a><\/li>\n<li><a href=\"#review\">Review<\/a><ul>\n<li><a href=\"#the-mostly-good\">The (mostly) Good<\/a><\/li>\n<li><a href=\"#the-bad\">The \u2026<\/a><\/li><\/ul><\/li><\/ul><\/div>","content":"<p>Recently, I was in the market for a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Power_amplifier_classes#Class_D\">Class-D<\/a> amplifier\nto replace an aging model. This article reviews the very compact\n<a href=\"https:\/\/www.amazon.de\/Fosi-Audio-2-Kanal-Stereo-Audioverst%C3%A4rkerempf%C3%A4nger-Mini-HiFi-Klasse-D-Integrierter-Heimlautsprecher\/dp\/B077GWVHHN\">Fosi Audio TSA7498E 2 channel amplifier<\/a> I bought from a China\nbased business, via Amazon. tl;dr: Can't recommend, use something\nbetter.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#motivation\">Motivation<\/a><\/li>\n<li><a href=\"#selection\">Selection<\/a><\/li>\n<li><a href=\"#review\">Review<\/a><ul>\n<li><a href=\"#the-mostly-good\">The (mostly) Good<\/a><\/li>\n<li><a href=\"#the-bad\">The Bad<\/a><\/li>\n<li><a href=\"#the-ugly\">The Ugly<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#lessons-learned\">Lessons Learned<\/a><\/li>\n<li><a href=\"#amazon-reviews\">Amazon Reviews<\/a><\/li>\n<li><a href=\"#alternatives\">Alternatives<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"motivation\">Motivation<a class=\"headerlink\" href=\"#motivation\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>I needed a small and efficient amplifier for connecting a pair\nof old 4 Ohm monitor boxes (JBL Control One) to a computer.\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Power_amplifier_classes#Class_D\">Class-D<\/a> amplifiers are known to be much more power\nefficient than traditional Class-AB ones. For example, I measured\nan old Class-AB Pioneer A-109 stereo amplifier at 33 W, during\nidle. That means that energy is wasted in the form of heat, which\nmight be especially annoying in the summer.\nWhereas a comparable Class-D amplifier just consumes a few watts, and\nlikely completely shuts down during idle operation.<\/p>\n<h2 id=\"selection\">Selection<a class=\"headerlink\" href=\"#selection\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Unfortunately, Class-D amplifiers often aren't clearly marketed\nas such and thus kind of hard to select for when browsing Hi-Fi\nvendor's product pages. Also, some Hi-Fi vendors just use Class-D\nfor very large amplifiers, where Class-AB simply gets impractical\nand only have Class-AB for living room amplifiers.<\/p>\n<p>The best bet is to look at the power consumption ratings,\nif available.<\/p>\n<p>When searching for Class-D amplifiers on Amazon the results are\nflooded with Chinese products, i.e. distributed by some no-name\nsmall companies, which probably are renamed often, whose wares\nare shipped from Amazon warehouses, without much quality control.\nJudging from the product images, many products from different\nvendors look quite similar to each other, i.e. it seems different\nvendors are just reselling wares from the same manufacturer.<\/p>\n<p>I bought the Fosi Audio TDA7498E 2 channel amplifier because its\ndesign is minimal and compact and it isn't too expensive.\nAlso its reviews are quite positive, however nowadays Amazon is\nalso flooded with paid and low-quality reviews, so this has to be\ntaken with a grain of salt.<\/p>\n<h2 id=\"review\">Review<a class=\"headerlink\" href=\"#review\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The Fosi Audio TDA7498E indeed is very compact and has a low\npower consumption. On idle I measured 0 W or so and during real\nuse I never measured above 1 W power.<\/p>\n<h3 id=\"the-mostly-good\">The (mostly) Good<a class=\"headerlink\" href=\"#the-mostly-good\" title=\"Permanent link\">&para;<\/a><\/h3>\n<p>Except for the volume knob, the design is nice, i.e. it comes in\na full metal case and with dual-use speaker connectors,\ni.e. one can connect speakers with banana plugs or raw cables.\nHowever, the volume knob isn't marked and way to easy to turn,\ni.e. it's very easy to turn it by accident to some high level.\nThis is especially problematic because the useful range for\nliving room volume levels is tiny, i.e. a few millimeter turn\nalready leads to a high volume. Last, it comes with a real power\nswitch.<\/p>\n<h3 id=\"the-bad\">The Bad<a class=\"headerlink\" href=\"#the-bad\" title=\"Permanent link\">&para;<\/a><\/h3>\n<p>At least when connecting 4 Ohm speakers, the amplifier creates a\ntoo high amount of background noise.\nProbably all amplifier speaker combinations produce some\nbackground noise, but usually it's only audible if you place one\near just a few centimeters from the speaker (while playing\nsilence and turning the volume knob up). With the Fosi Audio\nTDA7498E amplifier the background noise is still audible in a few\nmeters distance, which makes it unsuitable for office usage.<\/p>\n<h3 id=\"the-ugly\">The Ugly<a class=\"headerlink\" href=\"#the-ugly\" title=\"Permanent link\">&para;<\/a><\/h3>\n<p>There is a channel imbalance at living room volumes, i.e. below\n30 dB or so, of plus 6 dB on the left, when using audio sources\nwith standard line-out levels.\nThat means one either has to compensate that amount by adjusting\nthe line-out balance settings of the connected audio source (if even\npossible). Alternatively, one might reduce the audio source's\nline-out level to 72 % or so and turn up Fosi Audio TDA7498E's volume accordingly.<\/p>\n<p>Both workarounds are ridiculous, i.e. one simply doesn't\nartificially decrease the quality of the input signal to work\naround channel imbalance in the amplifier.<\/p>\n<p>Last, the speaker polarity on both channels is reversed, i.e.\nblack is positive and red is negative. Granted, it would be worse\nif just one channel was reversed (cf. phase shift), but\nstill, this speaks volumes regarding the quality of the product.<\/p>\n<h2 id=\"lessons-learned\">Lessons Learned<a class=\"headerlink\" href=\"#lessons-learned\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>My hearing is better than I thought. That means I immediately\nnoticed the channel imbalance and background noise. Furthermore,\nthe compensation levels and range (where the imbalance occurs) I\nsubjectively determined by changing mixer knobs until I didn't\nnotice any imbalance anymore, first while listening to normal\nmusic and then to pink noise, matched a later verification with a\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Sound_level_meter\">sound-level meter<\/a> pretty well.<\/p>\n<p>The alsa-utils package contains the <code>speaker-test<\/code> command which\nis quite convenient for testing speakers. Example:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>speaker-test<span class=\"w\"> <\/span>-c2\n<\/code><\/pre><\/div>\n\n<p>FWIW, I also was able to detect a defect speaker with speaker-test,\nwhere its deficiencies were more obvious.<\/p>\n<p>In case no dedicated sound-level meter is available, depending on\nyour smartphone, a sound-level measuring app might be good enough\nfor speaker balance testing (<a href=\"https:\/\/digscholarship.unco.edu\/cgi\/viewcontent.cgi?article=1130&amp;context=capstones\">see<\/a> <a href=\"https:\/\/www.constructionjunkie.com\/blog\/2017\/2\/24\/niosh-releases-sound-level-meter-app-for-smartphone\">also<\/a>). FWIW, I\ntested the Google Pixel 6a with the 'Noise Meter' Android app and\nobserved reasonable performance.  However, as of the end of 2022,\nthe 'Noise Meter' app seems to have vanished from the Google play\nstore, as well as the company behind it (JINASYS) seems to have\nvanished from the internet.<\/p>\n<h2 id=\"amazon-reviews\">Amazon Reviews<a class=\"headerlink\" href=\"#amazon-reviews\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>As mentioned previously, the Amazon Reviews for the <a href=\"https:\/\/www.amazon.de\/Fosi-Audio-2-Kanal-Stereo-Audioverst%C3%A4rkerempf%C3%A4nger-Mini-HiFi-Klasse-D-Integrierter-Heimlautsprecher\/dp\/B077GWVHHN\">Fosi Audio\nTDA7498E<\/a> amplifier are quite positive, i.e. as of 2022-12-31 it has\nan average rating of 4.3 on a five star scale with 63 % 5 star\nand 19 % 4 star ratings.<\/p>\n<p>Especially in the last years, Amazon is plagued more and more \nwith fake reviews and reportedly doesn't police enough against\nthem. Thus, some of those positive reviews might be fake, either\ngenerated by some 'AI' bots or by some incentivised persons.<\/p>\n<p>However, many of the positive Amazon reviews may very well still\nbe genuine. This can be explained by various factors such as:<\/p>\n<ol>\n<li>The seller sells various batches and revisions, also perhaps\n   sourced quite heterogeneously, via the same product page.<\/li>\n<li>Some reviewers simply have kind of bad hearing.<\/li>\n<li>Some reviewers are simply used to listening at a frankly too\n   high volume where the channels aren't imbalanced. Either some\n   of this group already have bad hearing or they are on a good\n   way to damage their hearing capabilities.<\/li>\n<li>Some reviewers are biased, e.g. in order to reduce their\n   <a href=\"https:\/\/en.wikipedia.org\/wiki\/Cognitive_dissonance\">cognitive dissonance<\/a>, i.e. to avoid recognizing having\n   made a bad purchase (which would require some effort to get a\n   refund and get a replacement). Or due to some kind of\n   <a href=\"https:\/\/en.wikipedia.org\/wiki\/Groupthink\">groupthink<\/a>, where a reviewer already bought into the\n   narrative of\n   some enthusiastic reviews (because of a good story such as: simple\n   underdog product is as good or better than one from an\n   established Hi-Fi company and you have discovered and saved\n   a good penny on it).<\/li>\n<\/ol>\n<h2 id=\"alternatives\">Alternatives<a class=\"headerlink\" href=\"#alternatives\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Mini or micro stereo systems are an alternative to a dedicated\namplifier, because they are widely available and tested by\nreputable independent entities. For example, in Germany there is\nthe independent <a href=\"https:\/\/en.wikipedia.org\/wiki\/Stiftung_Warentest\">Stiftung Warentest<\/a> consumer organisation\nwhich tests this product group periodically. Their last tests\ninclude power ratings, besides the usual feature comparisons,\nsuch that selecting systems with Class-D amplifiers (and other\nrequired features) is much simpler than having to research all\nsuch details on ones own.<\/p>\n<p>I settled on an ultra compact stereo system from an established\nvendor: the Panasonic SA-PMX94 (a.k.a. SC-PMX94 or SC-PMX94EG-S\nor SC-PMX94EG-K). It comes with a pair of 3 Ohm speakers which\nare quite decent.<\/p>\n<p>It might be even possible to connect third-party speakers to that\nsystem, possibly even 4 Ohm ones, as the conventional wisdom is that\nspeakers that exceed the amplifiers impedance rating (within reason)\nshould work while connecting lower Ohm speakers\nis considered dangerous, as they might draw too much power such\nthat the amplifier operates outside of its safe operating area\nand might get damaged. However, I'm not an electronics engineer\nand haven't tested it, so YMMV.<\/p>\n<p>That stereo system comes with some features I don't really need,\nbut are still kind of nice to have, e.g. a DAB+ receiver and\nintegrated CD player.<\/p>\n<p>The best thing about it, besides good sound quality (i.e.\nno excessive background noise and balanced channels!) and\na reasonable volume knob, is its connectivity. That means\nbesides Bluetooth, optical S\/PDIF (TOSLINK), standard analog-in\n(Cinch\/RCA) it also integrates a standard USB sound device!<\/p>\n<p>In that way it is able to also replace an existing USB sound card and\npossibly a separate external DAC device. That means one can simply\nconnect the stereo system via USB to a computer and it should be\ndetected like an external USB sound card. I'm using it under\nLinux where it worked out of the box without any extra drivers.<\/p>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"review"}},{"@attributes":{"term":"audio"}}]},{"title":"Scanning I2C addresses","link":{"@attributes":{"href":"https:\/\/gms.tf\/i2c-scanner.html","rel":"alternate"}},"published":"2022-09-21T16:00:00+02:00","updated":"2022-09-21T16:00:00+02:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2022-09-21:\/i2c-scanner.html","summary":"<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/I%C2%B2C\">I2C<\/a> is a simple open-drain bus that is popular for\nconnecting devices to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Microcontroller\">microcontrollers (MCUs)<\/a>, such as\nsensors, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Real-time_clock\">RTCs<\/a>, displays etc. Devices on the I2C bus are\naddressed by 7 bits and the bus data and clock lines are\npulled-up by a set of pull-up resistors. A good first test \u2026<\/p>","content":"<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/I%C2%B2C\">I2C<\/a> is a simple open-drain bus that is popular for\nconnecting devices to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Microcontroller\">microcontrollers (MCUs)<\/a>, such as\nsensors, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Real-time_clock\">RTCs<\/a>, displays etc. Devices on the I2C bus are\naddressed by 7 bits and the bus data and clock lines are\npulled-up by a set of pull-up resistors. A good first test for\nchecking that I2C communication works fine is to scan for the\ndevices' addresses. This article presents two I2C address\nscanners for ATmega328p AVR (e.g. Arduino Pro Mini 3.3V 8 MHz)\nand GD32VF103 RISC-V (e.g.  Longan Nano) MCUs.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#i2c-high-level-overview\">I2C High-Level Overview<\/a><\/li>\n<li><a href=\"#i2c-address-scanning\">I2C Address Scanning<\/a><\/li>\n<li><a href=\"#scanning-on-the-atmega328p\">Scanning on the ATmega328p<\/a><ul>\n<li><a href=\"#see-also\">See Also<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#scanning-on-the-gd32vf103\">Scanning on the GD32VF103<\/a><ul>\n<li><a href=\"#see-also_1\">See Also<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#other-mcus\">Other MCUs<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"i2c-high-level-overview\">I2C High-Level Overview<a class=\"headerlink\" href=\"#i2c-high-level-overview\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>On an I2C bus, every communication is initiated by a master\n(controller) that addresses a slave (target) device. There can be\nmultiple controllers and multiple targets on a bus. With MCUs,\nit's common to have one controller (the MCU) and one or more\ntarget devices connected to a bus.<\/p>\n<p>An address of a target is static, i.e. there is no address\nautoconfiguration protocol. Usually, the address of a target is\nspecified in its datasheet.<\/p>\n<p>The following sequence diagram describes the structure of I2C\ncommunication:<\/p>\n<p><img alt=\"I2C sequence diagram\" src=\"https:\/\/gms.tf\/image\/i2c-sequence.svg\"\/><\/p>\n<h2 id=\"i2c-address-scanning\">I2C Address Scanning<a class=\"headerlink\" href=\"#i2c-address-scanning\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The basic idea with I2C address scanning is to simply send\nSTART conditions for all possible addresses (there are just up to\n<code>2^7<\/code> of them!) and detect ACKnowledgement conditions on the bus, in a loop.<\/p>\n<p>Of course, in any case, after sending the address and detecting either an ACK\nor NACK the controller immediately sends a STOP condition to avoid confusing\nany target device and being able to continue with the next one.<\/p>\n<h2 id=\"scanning-on-the-atmega328p\">Scanning on the ATmega328p<a class=\"headerlink\" href=\"#scanning-on-the-atmega328p\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>There are <a href=\"https:\/\/wolles-elektronikkiste.de\/en\/i2c-scanner\">several<\/a> I2C scanners <a href=\"http:\/\/www.gammon.com.au\/i2c#reply6\">around<\/a> that use the Arduino API.\nArguably, that API hides too much of the I2C fundamentals, such\nthat it's not immediately obvious how the scanning really works.<\/p>\n<p>I thus wrote a bare bones I2C scanner that directly interfaces\nwith the I2C unit of an 8 bit ATmega MCU using its registers:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"c1\">\/\/ at 8 Mhz<\/span>\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">i2c_set_100kHz<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">TWBR<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">32<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">i2c_set_400kHz<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">TWBR<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ NB: clearing TWINT starts next transmission<\/span>\n<span class=\"c1\">\/\/     TWI == I2C<\/span>\n<span class=\"c1\">\/\/     TWCR = TWI Control Register<\/span>\n<span class=\"c1\">\/\/     TWDR = TWI Data Register<\/span>\n\n<span class=\"c1\">\/\/ start master transmission<\/span>\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">i2c_start<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ clear TWINT flag, send START, enable TWI unit<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">TWCR<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">_BV<\/span><span class=\"p\">(<\/span><span class=\"n\">TWINT<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">_BV<\/span><span class=\"p\">(<\/span><span class=\"n\">TWSTA<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">_BV<\/span><span class=\"p\">(<\/span><span class=\"n\">TWEN<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">loop_until_bit_is_set<\/span><span class=\"p\">(<\/span><span class=\"n\">TWCR<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">TWINT<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ NB: TWSTA must be cleared explicitly in the next operation<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">i2c_stop<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ clear TWINT flag, send STOP, enable TWI unit<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">TWCR<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">_BV<\/span><span class=\"p\">(<\/span><span class=\"n\">TWINT<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">_BV<\/span><span class=\"p\">(<\/span><span class=\"n\">TWSTO<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">_BV<\/span><span class=\"p\">(<\/span><span class=\"n\">TWEN<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">loop_until_bit_is_clear<\/span><span class=\"p\">(<\/span><span class=\"n\">TWCR<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">TWSTO<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ NB: TWSTO is cleared automatically<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ NB: TWINT is NOT set after STOP transmission ...<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"c1\">\/\/ rw: TW_READ (1) or TW_WRITE (0)<\/span>\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">i2c_set_address<\/span><span class=\"p\">(<\/span><span class=\"kt\">uint8_t<\/span><span class=\"w\"> <\/span><span class=\"n\">addr<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"kt\">uint8_t<\/span><span class=\"w\"> <\/span><span class=\"n\">rw<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">TWDR<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">addr<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">rw<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ clear TWINT flag, send STOP, enable TWI unit<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">TWCR<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">_BV<\/span><span class=\"p\">(<\/span><span class=\"n\">TWINT<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">_BV<\/span><span class=\"p\">(<\/span><span class=\"n\">TWEN<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">loop_until_bit_is_set<\/span><span class=\"p\">(<\/span><span class=\"n\">TWCR<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">TWINT<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">setup<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">setup_uart<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">stdout<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"n\">uart_stdout<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_set_100kHz<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ uncomment if your I2C modules don&#39;t come with pull-ups ...<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ DDRC &amp;= ~ _BV(DDC4); PORTC |= _BV(PORTC4);<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ DDRC &amp;= ~ _BV(DDC5); PORTC |= _BV(PORTC5);<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">probe_address<\/span><span class=\"p\">(<\/span><span class=\"kt\">uint8_t<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_start<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_set_address<\/span><span class=\"p\">(<\/span><span class=\"n\">i<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">TW_WRITE<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">((<\/span><span class=\"n\">TWSR<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"w\"> <\/span><span class=\"n\">TW_STATUS_MASK<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"n\">TW_MT_SLA_ACK<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">         <\/span><span class=\"n\">printf<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Found device on address: 0x%&quot;<\/span><span class=\"w\"> <\/span><span class=\"n\">PRIx8<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot; (%&quot;<\/span><span class=\"w\"> <\/span><span class=\"n\">PRIu8<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;)<\/span><span class=\"se\">\\n<\/span><span class=\"s\">&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_stop<\/span><span class=\"p\">();<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">scan<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ skipping reserved addresses<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ cf. https:\/\/en.wikipedia.org\/wiki\/I%C2%B2C#Reserved_addresses_in_7-bit_address_space<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">8<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"o\">&lt;<\/span><span class=\"mi\">128<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">probe_address<\/span><span class=\"p\">(<\/span><span class=\"n\">i<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"kt\">int<\/span><span class=\"w\"> <\/span><span class=\"nf\">main<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">setup<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(;;)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">printf<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Scanning I2C bus at 100 kHz ...<\/span><span class=\"se\">\\n<\/span><span class=\"s\">&quot;<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">scan<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">printf<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Scanning I2C bus at 400 kHz ...<\/span><span class=\"se\">\\n<\/span><span class=\"s\">&quot;<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">i2c_set_400kHz<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">scan<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">printf<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;done<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">&quot;<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">i2c_set_100kHz<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">        <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">uint16_t<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;<\/span><span class=\"w\"> <\/span><span class=\"mi\">30<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"mi\">30<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">_delay_ms<\/span><span class=\"p\">(<\/span><span class=\"mi\">32<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>The is also a pretty direct translation of the basic idea. It's clear from the\ncode that the scanner check whether the address is acknowledged or not.<\/p>\n<h3 id=\"see-also\">See Also<a class=\"headerlink\" href=\"#see-also\" title=\"Permanent link\">&para;<\/a><\/h3>\n<ul>\n<li><a href=\"https:\/\/ww1.microchip.com\/downloads\/en\/DeviceDoc\/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061B.pdf\">ATmega328p Datasheet<\/a> Section 22 for the I2C (a.k.a. TWI) register specification<\/li>\n<li><a href=\"https:\/\/www.nongnu.org\/avr-libc\/user-manual\/modules.html\">avr-libc<\/a> documentation of the used headers<\/li>\n<li><a href=\"https:\/\/github.com\/gsauthof\/i2c-scanner\">My github repository<\/a> for the complete code<\/li>\n<\/ul>\n<h2 id=\"scanning-on-the-gd32vf103\">Scanning on the GD32VF103<a class=\"headerlink\" href=\"#scanning-on-the-gd32vf103\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Scanning for I2C addresses on the the GD32VF103 32 Bit RISC-V MCU\n(e.g. the Longan Nano) isn't too different:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">probe_address<\/span><span class=\"p\">(<\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">while<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">i2c_flag_get<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_FLAG_I2CBSY<\/span><span class=\"p\">))<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_start_on_bus<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">while<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"o\">!<\/span><span class=\"n\">i2c_flag_get<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_FLAG_SBSEND<\/span><span class=\"p\">))<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ it&#39;s cleared by getting the flag, ie. reading I2C_STAT0<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_master_addressing<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_TRANSMITTER<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">k<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">while<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"o\">!<\/span><span class=\"n\">i2c_flag_get<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_FLAG_ADDSEND<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">i2c_flag_get<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_FLAG_AERR<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">i2c_flag_clear<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_FLAG_AERR<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">i2c_stop_on_bus<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">return<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ in case no bus is connected<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">k<\/span><span class=\"o\">++<\/span><span class=\"w\"> <\/span><span class=\"o\">&gt;<\/span><span class=\"w\"> <\/span><span class=\"mi\">1000<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"mi\">1000<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">i2c_stop_on_bus<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">return<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">printf<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Found device on address: 0x%&quot;<\/span><span class=\"w\"> <\/span><span class=\"n\">PRIx32<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot; (%&quot;<\/span><span class=\"w\"> <\/span><span class=\"n\">PRIu32<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;)<\/span><span class=\"se\">\\n<\/span><span class=\"s\">&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ NB: it&#39;s cleared by reading I2C_STAT0 _and_ I2C_STAT1<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_flag_clear<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_FLAG_ADDSEND<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">    <\/span><span class=\"n\">i2c_stop_on_bus<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ wait for stop being sent<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">while<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">I2C_CTL0<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_CTL0_STOP<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">scan<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ skipping reserved addresses<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ cf. https:\/\/en.wikipedia.org\/wiki\/I%C2%B2C#Reserved_addresses_in_7-bit_address_space<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">8<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"o\">&lt;<\/span><span class=\"mi\">128<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">probe_address<\/span><span class=\"p\">(<\/span><span class=\"n\">i<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"kt\">int<\/span><span class=\"w\"> <\/span><span class=\"nf\">main<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rcu_periph_clock_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_GPIOB<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rcu_periph_clock_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_I2C0<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ yes, I2C is open-drain<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">gpio_init<\/span><span class=\"p\">(<\/span><span class=\"n\">GPIOB<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_MODE_AF_OD<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_OSPEED_50MHZ<\/span><span class=\"p\">,<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">GPIO_PIN_6<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_PIN_7<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_deinit<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_clock_config<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">100000<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_DTCY_2<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ NB: we don&#39;t call i2c_mode_addr_config()<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/     because we are only in master transmit mode<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">i2c_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(;;)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">printf<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Scanning I2C bus at 100 kHz ...<\/span><span class=\"se\">\\n<\/span><span class=\"s\">&quot;<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">scan<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">printf<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;Scanning I2C bus at 400 kHz ...<\/span><span class=\"se\">\\n<\/span><span class=\"s\">&quot;<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">i2c_clock_config<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">400000<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_DTCY_2<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">scan<\/span><span class=\"p\">();<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">printf<\/span><span class=\"p\">(<\/span><span class=\"s\">&quot;done<\/span><span class=\"se\">\\n\\n<\/span><span class=\"s\">&quot;<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">i2c_clock_config<\/span><span class=\"p\">(<\/span><span class=\"n\">I2C0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">100000<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">I2C_DTCY_2<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">delay_1ms<\/span><span class=\"p\">(<\/span><span class=\"mi\">30<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"mi\">1000<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>Since the Nuclei SDK provides an I2C API that isn't too\nhigh-level (and arguably doesn't obfuscate too much) it's used\ninstead of wrapping the register accesses in a similar way.<\/p>\n<p>On that MCU, detecting the ACK of an address is a little bit\ntricky and it isn't explicitly documented in the manual.<\/p>\n<p>Also, the setup code for the I2C unit is more complex than\non the ATmega328p and arguably not immediately obvious after\nconsulting the manual.<\/p>\n<h3 id=\"see-also_1\">See Also<a class=\"headerlink\" href=\"#see-also_1\" title=\"Permanent link\">&para;<\/a><\/h3>\n<ul>\n<li><a href=\"http:\/\/www.gd32mcu.com\/download\/down\/document_id\/222\/path_type\/1\">GD32VF103 User Manual<\/a> Section 17 for the I2C (a.k.a. TWI) register specification<\/li>\n<li>Nuclei SDK source code for mapping the registers to API calls<\/li>\n<li><a href=\"https:\/\/github.com\/gsauthof\/i2c-scanner\">My github repository<\/a> for the complete code<\/li>\n<\/ul>\n<h2 id=\"other-mcus\">Other MCUs<a class=\"headerlink\" href=\"#other-mcus\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Looking at the general idea and the above example it shouldn't be hard\nto implement an analogous I2C address scanner on a completely different MCU,\ngiven that it has some sort of acceptable documentation of its I2C unit.<\/p>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"MCU"}},{"@attributes":{"term":"I2C"}},{"@attributes":{"term":"riscv"}}]},{"title":"Measuring PWM Signals with a Pokit Meter","link":{"@attributes":{"href":"https:\/\/gms.tf\/pwm-fft.html","rel":"alternate"}},"published":"2022-07-03T15:00:00+02:00","updated":"2022-07-03T15:00:00+02:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2022-07-03:\/pwm-fft.html","summary":"<p>This article describes how to measure a pulse-width-modulated\n(<a href=\"https:\/\/en.wikipedia.org\/wiki\/Pulse-width_modulation\">PWM<\/a>) signal using the Pokit Meter device.<\/p>\n<p>The Pokit Meter is a very small digital multimeter (DMM) like device\nthat can be interfaced over Bluetooth Low Energy (BLE). Due to\nits design and small form factor it comes with many limitations,\nbut \u2026<\/p>","content":"<p>This article describes how to measure a pulse-width-modulated\n(<a href=\"https:\/\/en.wikipedia.org\/wiki\/Pulse-width_modulation\">PWM<\/a>) signal using the Pokit Meter device.<\/p>\n<p>The Pokit Meter is a very small digital multimeter (DMM) like device\nthat can be interfaced over Bluetooth Low Energy (BLE). Due to\nits design and small form factor it comes with many limitations,\nbut it's good enough for some measurements, is relatively\ninexpensive (90 \u20ac or so) and is very portable.<\/p>\n<p>The <a href=\"https:\/\/www.pokitinnovations.com\/pokit-meter\/\">vendor<\/a> also provides an <a href=\"https:\/\/play.google.com\/store\/apps\/details?id=com.ingenuity.pokit.dev\">Android application<\/a> which (as of\n2022-06) is good enough for occasional measurements, but contains\ntoo many paper cuts, sadly.<\/p>\n<p>Besides the usual functions that are expected from an entry level DMM\n(limited to a lower range) and a logger mode, it supports an\noscilloscope (DSO) mode.<\/p>\n<p>Of course, the DSO mode is also very limited, most notably it doesn't\nsupport continuous data acquisition. However, its bandwidth is\ngood enough for sampling 1000 data points at a rate of 1 Mhz from\na low-level logic signal (i.e. over a period of 1 ms). Which is\ngood enough for e.g. measuring a 40 kHz PWM signal as used in IR\nconsumer remote protocols.<\/p>\n<p>Besides plotting the sampled signal, the Pokit Android software\nalso supports computing its frequency and duty cycle.\nUnfortunately, these functions are buggy (as of 2022-06), such\nthat every few times the wrong frequency is displayed (i.e. it's\ndoubled), such that one basically has to repeat a measurement several times\nand take a majority vote ... Example (correct frequency is 40\nkHz):<\/p>\n<p><img alt=\"Pokit Android App Screenshot that shows buggy frequency display\" src=\"https:\/\/gms.tf\/image\/pokit-freq-bug.png\" width=\"100%\"\/><\/p>\n<p>However, the graph still\nlooks fine. So it seems that the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Fast_Fourier_transform\">FFT<\/a> and\/or the peak detection\nthe software uses or has implemented contains a bug.<\/p>\n<p>I thus exported the raw data of a few measurements (including\nseries where the bug wasn't triggered) and analyzed them with\nScipy, Numpy, Pandas and Seaborn. See also the <a href=\"#notebook\">last\nsection<\/a> for details (i.e. it's my exported Jupyter Notebook).<\/p>\n<p>Summary: The signal looks good in all cases and there really\naren't any unexpected issues with the results of the FFT such as\nthe spectrogram. Thus, even when using a simple peak detection\n'algorithm' there is aren't any challenges to reliably detect the signal's\nmain frequency, programmatically.<\/p>\n<p>Which increases the suspicion that the wrong frequency display is\nentirely caused by a bug in Polkit's Android software.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#pokit-meter-specifications\">Pokit Meter Specifications<\/a><\/li>\n<li><a href=\"#burden-voltage\">Burden Voltage<\/a><\/li>\n<li><a href=\"#paper-cuts\">Paper Cuts<\/a><\/li>\n<li><a href=\"#alternatives\">Alternatives<\/a><\/li>\n<li><a href=\"#notebook\">Notebook<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"pokit-meter-specifications\">Pokit Meter Specifications<a class=\"headerlink\" href=\"#pokit-meter-specifications\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Since the <a href=\"https:\/\/www.pokitinnovations.com\/specifications\/\">Pokit specifications<\/a>\nrequire some scrolling on the product page, I also include them\nhere:<\/p>\n<p>DMM:<\/p>\n<ul>\n<li>Voltage: 10 mV to 42 V AC (True RMS) 10 mV to 60 VDC +- 1%<\/li>\n<li>Current: 1 mA to 2 A AC (True RMS)\/DC +- 1%<\/li>\n<li>Resistance: 100 m\u03a9 to 1 M\u03a9 +- 5%<\/li>\n<li>Temp: 0 to 60 C +- 1 C<\/li>\n<li>Diode: 0 V - 1.0 V (Check Only)<\/li>\n<li>Input Impedance(DC): 1M\u03a9<\/li>\n<li>Continuity Check<\/li>\n<li>Continuity Check Delay (200 ms max)<\/li>\n<li>Continuity Buzzer (via phone)<\/li>\n<li>True RMS<\/li>\n<\/ul>\n<p>DSO:<\/p>\n<ul>\n<li>Voltage: 10 mV to 42 V AC 10 mV to 60 VDC +- 1%<\/li>\n<li>Current: 1 mA to 2 A AC\/DC +- 1%<\/li>\n<li>Input Impedance (DC): 1 M\u03a9<\/li>\n<li>Sampling: 12bit, 1 M samples\/s<\/li>\n<\/ul>\n<p>Logger:<\/p>\n<ul>\n<li>Voltage: 10 mV to 42V AC (True RMS) 10 mV to 60 VDC +- 1%<\/li>\n<li>Current: 1 mA to 2A AC (True RMS)\/DC +- 1%<\/li>\n<li>Temp: 0 to 60 C +- 1 C<\/li>\n<li>Memory: 128 KB, i.e. 1 sample\/45 minutes (-&gt; up to 6 Months that way)<\/li>\n<li>Sample Rate: 1 s to 18 h<\/li>\n<li>Input Impedance(DC): 1 M\u03a9<\/li>\n<li>True RMS<\/li>\n<\/ul>\n<p>Of course, other 'real' DMMs have better specs and also specify them more detailed.\nEspecially with respect to the accuracy and resolution of the different ranges.<\/p>\n<hr>\n<p>These specifications leave it open up to which frequency True RMS AC measurement is supported.\nI did a quick test, and the results for a 40 kHz signal were way off.\nFor comparison, the datasheet of the EEVBlog 121GW DMM specifies AC coupled RMS mode up to 5 kHz and DC coupled RMS mode up to 1 kHz.<\/p>\n<p>Notably, the Pokit Meter doesn't support DC coupled RMS measurments.<\/p>\n<h2 id=\"burden-voltage\">Burden Voltage<a class=\"headerlink\" href=\"#burden-voltage\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Also, Pokit doesn't specify any burden voltages for current measurements (unlike other DMMs).\nI measured an internal resistance of 22.35 Ohm at the 10 mA current range which results in a burden voltage of 22 mV\/mA.<\/p>\n<p>Of course, this isn't particularly impressive, as might be expected from the other specs.\nFor example, even a 25 year old ultra cheap DMM of mine has half of that burden voltage.<\/p>\n<h2 id=\"paper-cuts\">Paper Cuts<a class=\"headerlink\" href=\"#paper-cuts\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>As mentioned in the introduction, the Pokit Android software comes with too many annoyances, as of 2022.\nEven after playing with it for only a few minutes, one easily notices several of them.<\/p>\n<p>Most notable issues:<\/p>\n<ul>\n<li>DSO mode is immediately terminated (and all settings are lost) when quickly switching to another app, e.g. even when just sharing a measurement from history or a screenshot<\/li>\n<li>many settings aren't retained when switching between DSO and DMM mode, or when exiting the DSO mode, such as position of the x-axis, selected functions, etc.<\/li>\n<li>there is no way to lock the time\/unit division (div) such that it's easily changed by accident to nonsensical decimal divisions<\/li>\n<li>some settings can only be set by tedious zooming gestures, there is no direct discrete entry possible<\/li>\n<li>graph isn't automatically vertically shifted when there aren't negative values<\/li>\n<li>there is no snap-to-grid feature<\/li>\n<li>there are no labels on the axes<\/li>\n<li>functions are positioned suboptimally such that they are often unreadable<\/li>\n<li>DFT just shows histogram, no spectrogram<\/li>\n<li>placing measurement tips doesn't work, as a side effect the\n  graph isn't displayed anymore, even on follow up measurements.\n  Workaround: switch between modes to reset and thus lose again all the\n  settings ...<\/li>\n<\/ul>\n<p>Arguably somewhat less notable:<\/p>\n<ul>\n<li>the application doesn't support Dark Mode<\/li>\n<\/ul>\n<p>The device itself has to make some compromises because of its form factor.\nHowever, the quality of the J-probes could be better. For example, with my device, the black one gets stuck when being retracted.<\/p>\n<p>On the positive side, some pros:<\/p>\n<ul>\n<li>device is ultra portable and relatively inexpensive<\/li>\n<li>the device's BLE protocol is <a href=\"https:\/\/www.pokitinnovations.com\/wp-content\/uploads\/D0005250-PokitMeter-Bluetooth-API-Documentation-0_02.pdf\">openly documented<\/a><\/li>\n<li>if nothing else is available, it's definitely good enough for reverse-engineering or verifying some PWM signals<\/li>\n<\/ul>\n<h2 id=\"alternatives\">Alternatives<a class=\"headerlink\" href=\"#alternatives\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Obvious alternatives are to use a 'real' DMM with better specs\nfor the usual measurements, and a real\noscilloscope\/logic-analyzer for looking at PWM signals,\nespecially if longer periods need to be observed. See for example\nthe <a href=\"https:\/\/sigrok.org\/wiki\/Supported_hardware\">Sigrok device support\npage<\/a> to get an idea\nof typical devices in different device classes that also can be\ninterfaced by an computer (and support continuous data\nacquisition).<\/p>\n<p>Some mid-level DMMs also support a frequency measurement mode, where\nthe frequency and even the duty cycle can be measured. However, a look at the\ndatasheet is mandatory, since the DMM likely isn't able to sample the signal\nfast enough, it isn't sensitive enough for common logic-levels, only supports\nfrequency measurements of AC voltages etc.<\/p>\n<p>For a first approximation, a better DMM might be used to verify a regular PWM signal\neven without a dedicated frequency mode, at least for doing some plausibility checks.\nFor example, one can calculate the expected average DC voltage, AC coupled and\nDC couples RMS voltages and see if the DMM measured values match the\nexpectation.\nAlso, the True RMS AC mode might be sensitive\/fast enough to even display the logic-level PWM frequency, correctly.\nHowever, even with better DMMs the True RMS sampling might not be high enough for some PWM signals (check the datasheet).<\/p>\n<p>If the DMM specs are sparse and details regarding various measurement modes are lacking, one should look for a better device.<\/p>\n<h2 id=\"notebook\">Notebook<a class=\"headerlink\" href=\"#notebook\" title=\"Permanent link\">&para;<\/a><\/h2>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"o\">%<\/span><span class=\"n\">matplotlib<\/span> <span class=\"n\">inline<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"kn\">import<\/span><span class=\"w\"> <\/span><span class=\"nn\">pandas<\/span><span class=\"w\"> <\/span><span class=\"k\">as<\/span><span class=\"w\"> <\/span><span class=\"nn\">pd<\/span>\n<span class=\"kn\">import<\/span><span class=\"w\"> <\/span><span class=\"nn\">numpy<\/span><span class=\"w\"> <\/span><span class=\"k\">as<\/span><span class=\"w\"> <\/span><span class=\"nn\">np<\/span>\n<span class=\"kn\">import<\/span><span class=\"w\"> <\/span><span class=\"nn\">seaborn<\/span><span class=\"w\"> <\/span><span class=\"k\">as<\/span><span class=\"w\"> <\/span><span class=\"nn\">sns<\/span>\n<span class=\"kn\">import<\/span><span class=\"w\"> <\/span><span class=\"nn\">matplotlib.pyplot<\/span><span class=\"w\"> <\/span><span class=\"k\">as<\/span><span class=\"w\"> <\/span><span class=\"nn\">plt<\/span>\n<span class=\"kn\">import<\/span><span class=\"w\"> <\/span><span class=\"nn\">scipy.signal<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">rcParams<\/span><span class=\"p\">[<\/span><span class=\"s1\">&#39;figure.figsize&#39;<\/span><span class=\"p\">]<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>[6.4, 4.8]\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">set<\/span><span class=\"p\">(<\/span><span class=\"n\">rc<\/span><span class=\"o\">=<\/span><span class=\"p\">{<\/span><span class=\"s1\">&#39;figure.figsize&#39;<\/span><span class=\"p\">:(<\/span><span class=\"mi\">8<\/span><span class=\"p\">,<\/span> <span class=\"mi\">4<\/span><span class=\"p\">)})<\/span>\n<span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">set_style<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;darkgrid&quot;<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"p\">{<\/span> <span class=\"n\">k<\/span><span class=\"p\">:<\/span> <span class=\"n\">v<\/span> <span class=\"k\">for<\/span> <span class=\"n\">k<\/span><span class=\"p\">,<\/span> <span class=\"n\">v<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">axes_style<\/span><span class=\"p\">()<\/span><span class=\"o\">.<\/span><span class=\"n\">items<\/span><span class=\"p\">()<\/span> <span class=\"k\">if<\/span> <span class=\"s2\">&quot;color&quot;<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">k<\/span> <span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>{&#39;axes.facecolor&#39;: &#39;#EAEAF2&#39;,\n &#39;axes.edgecolor&#39;: &#39;white&#39;,\n &#39;axes.labelcolor&#39;: &#39;.15&#39;,\n &#39;figure.facecolor&#39;: &#39;white&#39;,\n &#39;grid.color&#39;: &#39;white&#39;,\n &#39;text.color&#39;: &#39;.15&#39;,\n &#39;xtick.color&#39;: &#39;.15&#39;,\n &#39;ytick.color&#39;: &#39;.15&#39;,\n &#39;patch.edgecolor&#39;: &#39;w&#39;,\n &#39;patch.force_edgecolor&#39;: True}\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">custom_style<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"s1\">&#39;axes.labelcolor&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;white&#39;<\/span><span class=\"p\">,<\/span>\n    <span class=\"s1\">&#39;xtick.color&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;white&#39;<\/span><span class=\"p\">,<\/span>\n    <span class=\"s1\">&#39;ytick.color&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;white&#39;<\/span><span class=\"p\">,<\/span>\n    <span class=\"s1\">&#39;figure.facecolor&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;#282e36&#39;<\/span><span class=\"p\">,<\/span>\n    <span class=\"s1\">&#39;axes.facecolor&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;#282e36&#39;<\/span><span class=\"p\">,<\/span>\n    <span class=\"s1\">&#39;grid.color&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;#2f363f&#39;<\/span><span class=\"p\">,<\/span>\n    <span class=\"s1\">&#39;axes.edgecolor&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;#2f363f&#39;<\/span><span class=\"p\">,<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">set_style<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;darkgrid&quot;<\/span><span class=\"p\">,<\/span> <span class=\"n\">rc<\/span><span class=\"o\">=<\/span><span class=\"n\">custom_style<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">set_palette<\/span><span class=\"p\">(<\/span><span class=\"s1\">&#39;pastel&#39;<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">color_palette<\/span><span class=\"p\">(<\/span><span class=\"s1\">&#39;pastel&#39;<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<p><svg  width=\"550\" height=\"55\"><rect x=\"0\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#a1c9f4;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"55\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#ffb482;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"110\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#8de5a1;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"165\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#ff9f9b;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"220\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#d0bbff;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"275\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#debb9b;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"330\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#fab0e4;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"385\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#cfcfcf;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"440\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#fffea3;stroke-width:2;stroke:rgb(255,255,255)\"\/><rect x=\"495\" y=\"0\" width=\"55\" height=\"55\" style=\"fill:#b9f2f0;stroke-width:2;stroke:rgb(255,255,255)\"\/><\/svg><\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">def<\/span><span class=\"w\"> <\/span><span class=\"nf\">read_pokit_dso<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">):<\/span>\n    <span class=\"n\">df<\/span> <span class=\"o\">=<\/span> <span class=\"n\">pd<\/span><span class=\"o\">.<\/span><span class=\"n\">read_csv<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">,<\/span> <span class=\"n\">skiprows<\/span><span class=\"o\">=<\/span><span class=\"mi\">8<\/span><span class=\"p\">,<\/span> <span class=\"n\">usecols<\/span><span class=\"o\">=<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">,<\/span><span class=\"mi\">1<\/span><span class=\"p\">])<\/span>\n    <span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">rename<\/span><span class=\"p\">({<\/span><span class=\"s1\">&#39;Time (s)&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;timestamp_ms&#39;<\/span><span class=\"p\">,<\/span> <span class=\"s1\">&#39;CH 1 - Pokit (V DC)&#39;<\/span><span class=\"p\">:<\/span> <span class=\"s1\">&#39;V&#39;<\/span><span class=\"p\">},<\/span>  <span class=\"n\">axis<\/span><span class=\"o\">=<\/span><span class=\"mi\">1<\/span><span class=\"p\">,<\/span> <span class=\"n\">inplace<\/span><span class=\"o\">=<\/span><span class=\"kc\">True<\/span><span class=\"p\">)<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">df<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">def<\/span><span class=\"w\"> <\/span><span class=\"nf\">read_sample_freq<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">):<\/span>\n    <span class=\"n\">df<\/span> <span class=\"o\">=<\/span> <span class=\"n\">pd<\/span><span class=\"o\">.<\/span><span class=\"n\">read_csv<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">,<\/span> <span class=\"n\">skiprows<\/span><span class=\"o\">=<\/span><span class=\"mi\">2<\/span><span class=\"p\">,<\/span> <span class=\"n\">nrows<\/span><span class=\"o\">=<\/span><span class=\"mi\">1<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">d<\/span> <span class=\"o\">=<\/span> <span class=\"n\">df<\/span><span class=\"p\">[<\/span><span class=\"s1\">&#39;Duration&#39;<\/span><span class=\"p\">][<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span>\n    <span class=\"k\">if<\/span> <span class=\"ow\">not<\/span> <span class=\"n\">d<\/span><span class=\"o\">.<\/span><span class=\"n\">endswith<\/span><span class=\"p\">(<\/span><span class=\"s1\">&#39;ms&#39;<\/span><span class=\"p\">):<\/span>\n        <span class=\"k\">raise<\/span> <span class=\"ne\">RuntimeError<\/span><span class=\"p\">(<\/span><span class=\"sa\">f<\/span><span class=\"s1\">&#39;Unknown unit in: <\/span><span class=\"si\">{<\/span><span class=\"n\">d<\/span><span class=\"si\">}<\/span><span class=\"s1\">&#39;<\/span><span class=\"p\">)<\/span>\n    <span class=\"n\">d<\/span> <span class=\"o\">=<\/span> <span class=\"nb\">float<\/span><span class=\"p\">(<\/span><span class=\"n\">d<\/span><span class=\"o\">.<\/span><span class=\"n\">replace<\/span><span class=\"p\">(<\/span><span class=\"s1\">&#39;,&#39;<\/span><span class=\"p\">,<\/span> <span class=\"s1\">&#39;.&#39;<\/span><span class=\"p\">)[:<\/span><span class=\"o\">-<\/span><span class=\"mi\">2<\/span><span class=\"p\">])<\/span>\n    <span class=\"n\">n<\/span> <span class=\"o\">=<\/span> <span class=\"n\">df<\/span><span class=\"p\">[<\/span><span class=\"s1\">&#39;# Samples&#39;<\/span><span class=\"p\">][<\/span><span class=\"mi\">0<\/span><span class=\"p\">]<\/span>\n    <span class=\"n\">f<\/span> <span class=\"o\">=<\/span> <span class=\"mf\">1000.0<\/span> <span class=\"o\">\/<\/span> <span class=\"n\">d<\/span> <span class=\"o\">*<\/span> <span class=\"n\">n<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">f<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"c1\">#filename = &#39;Pokit DSO Export 2022-06-16 19-05-04.csv&#39;<\/span>\n<span class=\"c1\">#filename = &#39;Pokit DSO Export 2022-06-16 19-05-13.csv&#39;<\/span>\n<span class=\"c1\">#filename = &#39;Pokit DSO Export 2022-06-18 20-42-40.csv&#39;<\/span>\n<span class=\"n\">filename<\/span> <span class=\"o\">=<\/span> <span class=\"s1\">&#39;Pokit DSO Export 2022-06-19 15-16-33.csv&#39;<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">sample_freq<\/span> <span class=\"o\">=<\/span> <span class=\"n\">read_sample_freq<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">sample_freq<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"mf\">1000000.0<\/span>\n<\/code><\/pre><\/div>\n\n<p>The example CSV originate from measuring a GPIO pin of an Arduino Mini Pro 3.3V (8 MHz) that is configured for fast PWM (i.e. its Timer2 is set to mode 7) at ~ 40 kHz with a duty cycle of ~ 33 %.<\/p>\n<p>IOW, it's configured with:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>OCR2A   = 200;\nOCR2B   = 66;\nTCCR2A  = _BV(WGM21) | _BV(WGM20);\nTCCR2B  = _BV(WGM22);\nTCCR2B |= _BV(CS20);\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">df<\/span> <span class=\"o\">=<\/span> <span class=\"n\">read_pokit_dso<\/span><span class=\"p\">(<\/span><span class=\"n\">filename<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">df<\/span>\n<\/code><\/pre><\/div>\n\n<div>\n<style scoped>\n    .dataframe tbody tr th:only-of-type {\n        vertical-align: middle;\n    }\n\n    .dataframe tbody tr th {\n        vertical-align: top;\n    }\n\n    .dataframe thead th {\n        text-align: right;\n    }\n<\/style>\n<table border=\"1\" class=\"m-table\">\n  <thead>\n    <tr style=\"text-align: right;\">\n      <th><\/th>\n      <th>timestamp_ms<\/th>\n      <th>V<\/th>\n    <\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <th>0<\/th>\n      <td>0.000<\/td>\n      <td>3.409125<\/td>\n    <\/tr>\n    <tr>\n      <th>1<\/th>\n      <td>0.001<\/td>\n      <td>2.087047<\/td>\n    <\/tr>\n    <tr>\n      <th>2<\/th>\n      <td>0.002<\/td>\n      <td>0.096353<\/td>\n    <\/tr>\n    <tr>\n      <th>3<\/th>\n      <td>0.003<\/td>\n      <td>0.013272<\/td>\n    <\/tr>\n    <tr>\n      <th>4<\/th>\n      <td>0.004<\/td>\n      <td>0.005748<\/td>\n    <\/tr>\n    <tr>\n      <th>...<\/th>\n      <td>...<\/td>\n      <td>...<\/td>\n    <\/tr>\n    <tr>\n      <th>995<\/th>\n      <td>0.995<\/td>\n      <td>0.020796<\/td>\n    <\/tr>\n    <tr>\n      <th>996<\/th>\n      <td>0.996<\/td>\n      <td>0.005748<\/td>\n    <\/tr>\n    <tr>\n      <th>997<\/th>\n      <td>0.997<\/td>\n      <td>-0.001777<\/td>\n    <\/tr>\n    <tr>\n      <th>998<\/th>\n      <td>0.998<\/td>\n      <td>-0.009301<\/td>\n    <\/tr>\n    <tr>\n      <th>999<\/th>\n      <td>0.999<\/td>\n      <td>-0.013063<\/td>\n    <\/tr>\n  <\/tbody>\n<\/table>\n<p>1000 rows \u00d7 2 columns<\/p>\n<\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">lineplot<\/span><span class=\"p\">(<\/span><span class=\"n\">data<\/span><span class=\"o\">=<\/span><span class=\"n\">df<\/span><span class=\"p\">,<\/span> <span class=\"n\">x<\/span><span class=\"o\">=<\/span><span class=\"s1\">&#39;timestamp_ms&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">y<\/span><span class=\"o\">=<\/span><span class=\"s1\">&#39;V&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">palette<\/span><span class=\"o\">=<\/span><span class=\"s1\">&#39;bright&#39;<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>&lt;AxesSubplot:xlabel=&#39;timestamp_ms&#39;, ylabel=&#39;V&#39;&gt;\n<\/code><\/pre><\/div>\n\n<p><img alt=\"png\" src=\"https:\/\/gms.tf\/image\/pokit-output_13_1.png\"><\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">V_max<\/span> <span class=\"o\">=<\/span> <span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">V<\/span><span class=\"o\">.<\/span><span class=\"n\">max<\/span><span class=\"p\">()<\/span>\n<span class=\"n\">V_min<\/span> <span class=\"o\">=<\/span> <span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">V<\/span><span class=\"o\">.<\/span><span class=\"n\">min<\/span><span class=\"p\">()<\/span>\n<span class=\"n\">V_pp<\/span> <span class=\"o\">=<\/span> <span class=\"n\">V_max<\/span> <span class=\"o\">-<\/span> <span class=\"n\">V_min<\/span>\n<span class=\"p\">(<\/span><span class=\"n\">V_min<\/span><span class=\"p\">,<\/span> <span class=\"n\">V_max<\/span><span class=\"p\">,<\/span> <span class=\"n\">V_pp<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>(-0.0168251227587461, 3.424278497695923, 3.441103620454669)\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">duty_cycle<\/span> <span class=\"o\">=<\/span> <span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">V<\/span><span class=\"o\">.<\/span><span class=\"n\">mean<\/span><span class=\"p\">()<\/span><span class=\"o\">\/<\/span><span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">V<\/span><span class=\"o\">.<\/span><span class=\"n\">max<\/span><span class=\"p\">()<\/span> <span class=\"o\">*<\/span> <span class=\"mf\">100.0<\/span>\n<span class=\"n\">duty_cycle<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"mf\">33.010387910119036<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">histplot<\/span><span class=\"p\">(<\/span><span class=\"n\">data<\/span><span class=\"o\">=<\/span><span class=\"n\">df<\/span><span class=\"p\">,<\/span> <span class=\"n\">x<\/span><span class=\"o\">=<\/span><span class=\"s1\">&#39;V&#39;<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>&lt;AxesSubplot:xlabel=&#39;V&#39;, ylabel=&#39;Count&#39;&gt;\n<\/code><\/pre><\/div>\n\n<p><img alt=\"png\" src=\"https:\/\/gms.tf\/image\/pokit-output_16_1.png\"><\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">vs<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">histogram<\/span><span class=\"p\">(<\/span><span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">V<\/span><span class=\"p\">,<\/span> <span class=\"n\">bins<\/span><span class=\"o\">=<\/span><span class=\"mi\">2<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">duty_cycleP<\/span> <span class=\"o\">=<\/span> <span class=\"n\">vs<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">][<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span> <span class=\"o\">\/<\/span> <span class=\"nb\">sum<\/span><span class=\"p\">(<\/span><span class=\"n\">vs<\/span><span class=\"p\">[<\/span><span class=\"mi\">0<\/span><span class=\"p\">])<\/span> <span class=\"o\">*<\/span> <span class=\"mf\">100.0<\/span>\n<span class=\"n\">duty_cycleP<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"mf\">33.0<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">f<\/span><span class=\"p\">,<\/span> <span class=\"n\">t<\/span><span class=\"p\">,<\/span> <span class=\"n\">xs<\/span> <span class=\"o\">=<\/span> <span class=\"n\">scipy<\/span><span class=\"o\">.<\/span><span class=\"n\">signal<\/span><span class=\"o\">.<\/span><span class=\"n\">spectrogram<\/span><span class=\"p\">(<\/span><span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">V<\/span><span class=\"p\">,<\/span> <span class=\"n\">fs<\/span><span class=\"o\">=<\/span><span class=\"n\">sample_freq<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<p>That means <code>scipy.signal.spectogram()<\/code> returns an array of frequencies (y-axis), an array of time segments (x-axis) and an 2D array of amplitudes (the color).<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">t<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>array([0.000128, 0.000352, 0.000576, 0.0008  ])\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"nb\">len<\/span><span class=\"p\">(<\/span><span class=\"n\">f<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"mf\">129<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">shape<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>(129, 4)\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">xs<\/span><span class=\"p\">[:<\/span><span class=\"mi\">3<\/span><span class=\"p\">]<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>array([[3.83836353e-08, 8.26500290e-08, 2.77362553e-07, 3.39608196e-07],\n       [7.44573266e-09, 1.56151258e-08, 7.45418394e-09, 3.61549100e-09],\n       [2.69642610e-08, 1.45100991e-07, 2.17451029e-07, 9.25996058e-08]])\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">flatten<\/span><span class=\"p\">()<\/span><span class=\"o\">.<\/span><span class=\"n\">min<\/span><span class=\"p\">(),<\/span> <span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">flatten<\/span><span class=\"p\">()<\/span><span class=\"o\">.<\/span><span class=\"n\">max<\/span><span class=\"p\">()<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>(2.9019512920648874e-11, 0.0003120145689393602)\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">vs<\/span> <span class=\"o\">=<\/span> <span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">sum<\/span><span class=\"p\">(<\/span><span class=\"n\">axis<\/span><span class=\"o\">=<\/span><span class=\"mi\">1<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">lineplot<\/span><span class=\"p\">(<\/span><span class=\"n\">x<\/span><span class=\"o\">=<\/span><span class=\"n\">f<\/span><span class=\"p\">,<\/span> <span class=\"n\">y<\/span><span class=\"o\">=<\/span><span class=\"n\">vs<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>&lt;AxesSubplot:&gt;\n<\/code><\/pre><\/div>\n\n<p><img alt=\"png\" src=\"https:\/\/gms.tf\/image\/pokit-output_25_1.png\"><\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">freq<\/span> <span class=\"o\">=<\/span> <span class=\"n\">f<\/span><span class=\"p\">[<\/span><span class=\"n\">vs<\/span><span class=\"o\">.<\/span><span class=\"n\">argmax<\/span><span class=\"p\">()]<\/span>\n<span class=\"n\">freq<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"mf\">39062.5<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">argmax<\/span><span class=\"p\">(<\/span><span class=\"n\">axis<\/span><span class=\"o\">=<\/span><span class=\"mi\">0<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>array([10, 10, 10, 10])\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">freq<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">mean<\/span><span class=\"p\">([<\/span><span class=\"n\">f<\/span><span class=\"p\">[<\/span><span class=\"n\">i<\/span><span class=\"p\">]<\/span> <span class=\"k\">for<\/span> <span class=\"n\">i<\/span> <span class=\"ow\">in<\/span> <span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">argmax<\/span><span class=\"p\">(<\/span><span class=\"n\">axis<\/span><span class=\"o\">=<\/span><span class=\"mi\">0<\/span><span class=\"p\">)])<\/span>\n<span class=\"n\">freq<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"mf\">39062.5<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">def<\/span><span class=\"w\"> <\/span><span class=\"nf\">normalize<\/span><span class=\"p\">(<\/span><span class=\"n\">xs<\/span><span class=\"p\">):<\/span>\n    <span class=\"n\">xs<\/span> <span class=\"o\">=<\/span> <span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">copy<\/span><span class=\"p\">()<\/span>\n    <span class=\"n\">xs<\/span> <span class=\"o\">-=<\/span> <span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">min<\/span><span class=\"p\">()<\/span>\n    <span class=\"n\">xs<\/span> <span class=\"o\">\/=<\/span> <span class=\"n\">xs<\/span><span class=\"o\">.<\/span><span class=\"n\">max<\/span><span class=\"p\">()<\/span>\n    <span class=\"n\">xs<\/span> <span class=\"o\">*=<\/span> <span class=\"mf\">100.0<\/span>\n    <span class=\"k\">return<\/span> <span class=\"n\">xs<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">cmap<\/span> <span class=\"o\">=<\/span> <span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">colormaps<\/span><span class=\"p\">[<\/span><span class=\"s1\">&#39;inferno&#39;<\/span><span class=\"p\">]<\/span>\n<span class=\"n\">fig<\/span> <span class=\"o\">=<\/span> <span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">pcolormesh<\/span><span class=\"p\">(<\/span><span class=\"n\">t<\/span><span class=\"p\">,<\/span> <span class=\"n\">f<\/span><span class=\"p\">,<\/span> <span class=\"n\">normalize<\/span><span class=\"p\">(<\/span><span class=\"n\">xs<\/span><span class=\"p\">),<\/span> <span class=\"n\">shading<\/span><span class=\"o\">=<\/span><span class=\"s1\">&#39;gouraud&#39;<\/span><span class=\"p\">,<\/span> <span class=\"n\">cmap<\/span><span class=\"o\">=<\/span><span class=\"n\">cmap<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">colorbar<\/span><span class=\"p\">(<\/span><span class=\"n\">fig<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">ylim<\/span><span class=\"p\">(<\/span><span class=\"mi\">0<\/span><span class=\"p\">,<\/span> <span class=\"n\">freq<\/span> <span class=\"o\">*<\/span> <span class=\"mf\">3.0<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">ylabel<\/span><span class=\"p\">(<\/span><span class=\"s1\">&#39;Frequency [Hz]&#39;<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">xlabel<\/span><span class=\"p\">(<\/span><span class=\"s1\">&#39;Time [sec]&#39;<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">show<\/span><span class=\"p\">()<\/span>\n<\/code><\/pre><\/div>\n\n<p><img alt=\"png\" src=\"https:\/\/gms.tf\/image\/pokit-output_30_0.png\"><\/p>\n<p>As expected, such a spectogram also contains (faint) multiples of the main frequency. Here we can see one at 80 kHz or so.<\/p>\n<p>Perhaps the Pokit Android app has a bug in its FFT code such that it sometimes misses the main frequency and settles for the next multiple.<\/p>\n<p>Looing into the FFT in more detail:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">sp<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">fft<\/span><span class=\"o\">.<\/span><span class=\"n\">fft<\/span><span class=\"p\">(<\/span><span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">V<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">x_freqs<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">fft<\/span><span class=\"o\">.<\/span><span class=\"n\">fftfreq<\/span><span class=\"p\">(<\/span><span class=\"n\">df<\/span><span class=\"o\">.<\/span><span class=\"n\">V<\/span><span class=\"o\">.<\/span><span class=\"n\">shape<\/span><span class=\"p\">[<\/span><span class=\"o\">-<\/span><span class=\"mi\">1<\/span><span class=\"p\">],<\/span> <span class=\"mi\">1<\/span><span class=\"o\">\/<\/span><span class=\"n\">sample_freq<\/span><span class=\"p\">)<\/span>\n<span class=\"c1\">#plt.xlim(-freq * 3.0, freq * 3.0)<\/span>\n<span class=\"c1\">#plt.xlim(38000, freq * 3.0)<\/span>\n<span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">plot<\/span><span class=\"p\">(<\/span><span class=\"n\">x_freqs<\/span><span class=\"p\">,<\/span> <span class=\"n\">sp<\/span><span class=\"o\">.<\/span><span class=\"n\">real<\/span><span class=\"p\">,<\/span> <span class=\"n\">x_freqs<\/span><span class=\"p\">,<\/span> <span class=\"n\">sp<\/span><span class=\"o\">.<\/span><span class=\"n\">imag<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>[&lt;matplotlib.lines.Line2D at 0x7f8322ab33a0&gt;,\n &lt;matplotlib.lines.Line2D at 0x7f8322ab33d0&gt;]\n<\/code><\/pre><\/div>\n\n<p><img alt=\"png\" src=\"https:\/\/gms.tf\/image\/pokit-output_33_1.png\"><\/p>\n<p>Compute magnitudes:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">ms<\/span> <span class=\"o\">=<\/span> <span class=\"n\">np<\/span><span class=\"o\">.<\/span><span class=\"n\">sqrt<\/span><span class=\"p\">(<\/span><span class=\"n\">sp<\/span><span class=\"o\">.<\/span><span class=\"n\">real<\/span><span class=\"o\">**<\/span><span class=\"mi\">2<\/span> <span class=\"o\">+<\/span> <span class=\"n\">sp<\/span><span class=\"o\">.<\/span><span class=\"n\">imag<\/span><span class=\"o\">**<\/span><span class=\"mi\">2<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">plt<\/span><span class=\"o\">.<\/span><span class=\"n\">xlim<\/span><span class=\"p\">(<\/span><span class=\"mi\">1000<\/span><span class=\"p\">,<\/span> <span class=\"n\">freq<\/span> <span class=\"o\">*<\/span> <span class=\"mf\">3.0<\/span><span class=\"p\">)<\/span>\n<span class=\"n\">sns<\/span><span class=\"o\">.<\/span><span class=\"n\">lineplot<\/span><span class=\"p\">(<\/span><span class=\"n\">x<\/span><span class=\"o\">=<\/span><span class=\"n\">x_freqs<\/span><span class=\"p\">,<\/span> <span class=\"n\">y<\/span><span class=\"o\">=<\/span><span class=\"n\">ms<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code>&lt;AxesSubplot:&gt;\n<\/code><\/pre><\/div>\n\n<p><img alt=\"png\" src=\"https:\/\/gms.tf\/image\/pokit-output_35_1.png\"><\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">x_freqs<\/span><span class=\"p\">[<\/span><span class=\"n\">ms<\/span><span class=\"p\">[<\/span><span class=\"mi\">1<\/span><span class=\"p\">:]<\/span><span class=\"o\">.<\/span><span class=\"n\">argmax<\/span><span class=\"p\">()<\/span><span class=\"o\">+<\/span><span class=\"mi\">1<\/span><span class=\"p\">]<\/span>\n<\/code><\/pre><\/div>\n\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"mf\">40000.0<\/span>\n<\/code><\/pre><\/div>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"MCU"}},{"@attributes":{"term":"PWM"}},{"@attributes":{"term":"measurement"}}]},{"title":"Getting started with the GD32VF103 RISC-V Microcontroller","link":{"@attributes":{"href":"https:\/\/gms.tf\/riscv-gd32vf103.html","rel":"alternate"}},"published":"2022-05-08T14:00:00+02:00","updated":"2022-05-08T14:00:00+02:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2022-05-08:\/riscv-gd32vf103.html","summary":"<p>The <a href=\"https:\/\/www.gigadevice.com\/products\/microcontrollers\/gd32\/risc-v\/\">GD32VF103<\/a> is an inexpensive 32 bit <a href=\"https:\/\/en.wikipedia.org\/wiki\/RISC-V\">RISC-V<\/a> CPU that integrates\na bunch of peripherals. In other words, it's a cheap RISC-V\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Microcontroller\">microcontroller<\/a> (MCU). This article describes how to hook it up, to\nset up an development environment and do some first steps with\nits peripherals.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#characteristics\">Characteristics<\/a><\/li>\n<li><a href=\"#development-boards\">Development Boards<\/a><\/li>\n<li><a href=\"#assembling\">Assembling \u2026<\/a><\/li><\/ul><\/div>","content":"<p>The <a href=\"https:\/\/www.gigadevice.com\/products\/microcontrollers\/gd32\/risc-v\/\">GD32VF103<\/a> is an inexpensive 32 bit <a href=\"https:\/\/en.wikipedia.org\/wiki\/RISC-V\">RISC-V<\/a> CPU that integrates\na bunch of peripherals. In other words, it's a cheap RISC-V\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Microcontroller\">microcontroller<\/a> (MCU). This article describes how to hook it up, to\nset up an development environment and do some first steps with\nits peripherals.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#characteristics\">Characteristics<\/a><\/li>\n<li><a href=\"#development-boards\">Development Boards<\/a><\/li>\n<li><a href=\"#assembling\">Assembling<\/a><\/li>\n<li><a href=\"#connecting\">Connecting<\/a><\/li>\n<li><a href=\"#powering\">Powering<\/a><\/li>\n<li><a href=\"#development-environment\">Development Environment<\/a><\/li>\n<li><a href=\"#c\">C++<\/a><\/li>\n<li><a href=\"#interrupts\">Interrupts<\/a><\/li>\n<li><a href=\"#realtime-clock-rtc\">Realtime Clock (RTC)<\/a><\/li>\n<li><a href=\"#lcd\">LCD<\/a><\/li>\n<li><a href=\"#uart\">UART<\/a><\/li>\n<li><a href=\"#where-to-go-from-here\">Where to go from here<\/a><\/li>\n<li><a href=\"#parts\">Parts<\/a><\/li>\n<li><a href=\"#related-mcus\">Related MCUs<\/a><\/li>\n<li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"characteristics\">Characteristics<a class=\"headerlink\" href=\"#characteristics\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The GD32VF103 exists in several variants, e.g. the Seeed\ndevelopment boards are built around the GD32VF103CBT6. Some of\nits characteristics:<\/p>\n<ul>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/RISC-V\">RISC-V<\/a> 32 bit instruction set: RV32IMAC (i.e. no floating point extension)<\/li>\n<li>108 MHz CPU frequency (can be clocked lower)<\/li>\n<li>32 KiB RAM<\/li>\n<li>128 KiB flash<\/li>\n<li>3.3 V supply voltage<\/li>\n<li>2 stage instruction pipeline<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Real-time_clock\">Realtime clock<\/a> (with periodic and alarm interrupt, can be calibrated)<\/li>\n<li>6 16-bit timers<\/li>\n<li>2 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Watchdog_timer\">watchdogs<\/a><\/li>\n<li>many external interrupts<\/li>\n<li>several <a href=\"https:\/\/en.wikipedia.org\/wiki\/General-purpose_input\/output\">GPIO<\/a> and ADC pins<\/li>\n<li>a bunch of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Universal_asynchronous_receiver-transmitter\">UART<\/a>\/<a href=\"https:\/\/en.wikipedia.org\/wiki\/Universal_synchronous_and_asynchronous_receiver-transmitter\">USART<\/a>\/<a href=\"https:\/\/en.wikipedia.org\/wiki\/Serial_Peripheral_Interface\">SPI<\/a> units (e.g. 5 U(S)ART and 3 SPI)<\/li>\n<\/ul>\n<p>Looking at the peripherals, i.e. their registers,\nGigaDevice's GD32VF103 RISC-V MCU arguably is quite similar to\nGigaDevice's <a href=\"https:\/\/www.gigadevice.com\/products\/microcontrollers\/gd32\/arm-cortex-m3\/\">GD32F103<\/a> Arm MCU which in turn is similar to the ST\n<a href=\"https:\/\/www.st.com\/en\/microcontrollers-microprocessors\/stm32f103.html\">STM32F103<\/a> Arm MCU (i.e. an Arm Cortex-M3 MCU, commonly used on the\ncompact '<a href=\"https:\/\/stm32-base.org\/boards\/STM32F103C8T6-Blue-Pill.html\">Blue Pill<\/a>' development boards).<\/p>\n<p>Despite being 32 bit CPUs all these MCUs have one <a href=\"https:\/\/stackoverflow.com\/q\/71989193\/427158\">curious\nproperty<\/a> in common: almost all of their memory-mapped\n(special purpose) registers are only 16 bit wide. That means they\nare 32 bit registers, but only the least significant 16 bits are\nused. Thus, many registers used to configure and interface with\nperipherals that require more than 16 bits are split over two\nconcrete ones (i.e. a high and low part), such as the RTC counter\nvalue (32 bits mapped via 2 times 16 bit) or the RTC alarm\nregister (20 bits mapped via 16 bit and 4 bit halves).<\/p>\n<p>Of course, such splitting is tedious and error-prone. Especially\nwhen reading such registers, one has to think about atomic\naccess, since there might be a race where the low part\nwraps around just after you have read it and thus the high part\nmight get incremented just before you are reading it. (analogous issue\nwhen you start with the high part)<\/p>\n<h2 id=\"development-boards\">Development Boards<a class=\"headerlink\" href=\"#development-boards\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p><a href=\"https:\/\/www.seeedstudio.com\/catalogsearch\/result\/?q=risc%20v\">Seeed Studio sells<\/a>\nseveral development boards built around the GD32VF103.\nThe most compact one is the <a href=\"https:\/\/www.seeedstudio.com\/Sipeed-Longan-Nano-V1-1-p-5118.html\">Sipeed Longan Nano v1.1<\/a>\nwhich is\navailable for 10 dollars or euros or so. The rest of this article\nconcentrates on this board.<\/p>\n<p>The Longan Nano has similar dimensions like an Arm <a href=\"https:\/\/stm32-base.org\/boards\/STM32F103C8T6-Blue-Pill.html\">Blue Pill<\/a> board\nor some of the compact Arduino ones, such as the Arduino Micro or\n<a href=\"https:\/\/www.sparkfun.com\/products\/11114\">Mini Pro<\/a>.<\/p>\n<p>Some notable features of the Longan Nano v1.1 board:<\/p>\n<ul>\n<li>it comes with a small 160x80 pixel TFT display attached to an\n  SPI port (detachable)<\/li>\n<li>low-speed external 32.768 kHz quartz oscillator for use with the RTC\n  on GPIO pins C14 and C15 (LXTAL)<\/li>\n<li>high-speed external 8 MHz quartz oscillator (HXTAL) from which\n  the 108 CPU clock is synthesized<\/li>\n<li>RGB LED on GPIO pins C13, A1 and A2<\/li>\n<li>on-board push-button on GPIO pin A8 (boot0 button with external pull-down)<\/li>\n<li>micro-SD card slot<\/li>\n<li>3.3 V voltage regulator (max: 6 V) at 5V pin, non-regulated\n  supply pin available (pin 3V3)<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/JTAG\">JTAG<\/a> header<\/li>\n<li>power LED<\/li>\n<li>USB interface<\/li>\n<\/ul>\n<h2 id=\"assembling\">Assembling<a class=\"headerlink\" href=\"#assembling\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>As it's common with these boards, it comes with the side-headers\nnot soldered yet.<\/p>\n<p>When the pins C14 and C15 aren't needed for IO and instead the\nRTC should be driven by the external quartz connected to these\npins it's recommended to <em>not<\/em> solder any header to those pins.\nThat means simply break the supplied header two times to connect\njust the VBAT pin and the pins RESET until C13. This should\nimprove the performance of the external quartz somewhat.<\/p>\n<p>However, even if C14\/C15 are soldered and connected to a\nbread-board it isn't really a catastrophe for RTC usage, since\nthe RTC should be calibrated anyways and such calibration should\ncompensate some of the issues with that. Besides slight RF issues\nperhaps. Purists might even want to cut the traces to the C14\/C15\npins as early as possible.<\/p>\n<p><img src=\"https:\/\/gms.tf\/image\/longan-nano-pinout.svg\" alt=\"Longan Nano Pinout\" width=\"100%\"\/><\/p>\n<h2 id=\"connecting\">Connecting<a class=\"headerlink\" href=\"#connecting\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>In theory, the Nano supports programming it over the onboard USB\ninterface via the <a href=\"https:\/\/en.wikipedia.org\/wiki\/USB#Device_Firmware_Upgrade_mechanism\">DFU<\/a> mode. However, there are 2 issue\nwith that:<\/p>\n<ol>\n<li>It doesn't support auto-reset and thus requires a very tedious\nand error-prone manual procedure, i.e. pressing the boot0 booting before\npushing briefly reset and then releasing again boot0 sequence\nbefore the actual flashing<\/li>\n<li>It's only supported by the legacy gd32vf103-sdk framework\nwhich is incomplete, buggy and unmaintained.<\/li>\n<\/ol>\n<p>Thus, one really needs a <a href=\"https:\/\/en.wikipedia.org\/wiki\/JTAG\">JTAG<\/a> adapter for programming the Longan\nNano, such as the <a href=\"https:\/\/www.seeedstudio.com\/Sipeed-USB-JTAG-TTL-RISC-V-Debugger-ST-Link-V2-STM8-STM32-Simulator-p-2910.html\">Sipeed USB-JTAG\/TTL RISC-V Debugger<\/a> which also costs around 10\nDollars\/Euros.<\/p>\n<p>NB: The Vendor Seeed Studio apparently sells (or had sold)\ndifferent variants of that JTAG adapter under the same SKU. The\nitem I ordered at the end of 2021 through Antratek (who sourced\nit from Seeed Studio through eVatmaster Consulting GmbH, production date 2021\/05\/15) comes with\n3.3 V and 5 V supply pins (which is quite convenient) whereas the\nSeeed Studio stock photo has that pin marked as NC (not\nconnected) and GND (as of May 2022). Perhaps that's just an outdated photo and all\ncurrently sold devices have the supply pins. But one can't be\nsure. I've notified the vendor via a Disqus comment, but the\nmoderators didn't approve it.<\/p>\n<p><img src=\"https:\/\/gms.tf\/image\/sipeed-jtag.jpg\" alt=\"Sipeed JTAG device\" width=\"50%\"\/><\/p>\n<p>Note that both the Nano and the Sipeed JTAG device have a double\nrow header, however, the pins don't match 1:1, back to back. Especially the\nreset pin isn't part of the Nano JTAG header.<\/p>\n<p>When orienting the Nano with USB-C port to the left and the\nSipeed JTAG device to the right (USB-connector to the right),\nmarkings on top, the pins read top-down:<\/p>\n<table class=\"m-table\">\n<thead>\n<tr>\n<th>Location<\/th>\n<th>Pins<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>JTAG back-row<\/td>\n<td>GND, RXD, TXD, 5V, 3V3<\/td>\n<\/tr>\n<tr>\n<td>JTAG front-row<\/td>\n<td>TDI, RST, TMS, TDO, TCK<\/td>\n<\/tr>\n<tr>\n<td>Nano back-row<\/td>\n<td>JTDO, JTDI, JTCK, JTMS<\/td>\n<\/tr>\n<tr>\n<td>Nano front-row<\/td>\n<td>GND, TX0, RX0, 3.3V<\/td>\n<\/tr>\n<tr>\n<td>Nano top header<\/td>\n<td>RESET<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Thus, the Nano\/JTAG should be connected like this:<\/p>\n<table class=\"m-table\">\n<thead>\n<tr>\n<th>Nano<\/th>\n<th>JTAG\/TTL device<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>JTDO<\/td>\n<td>TDO<\/td>\n<\/tr>\n<tr>\n<td>JTDI<\/td>\n<td>TDI<\/td>\n<\/tr>\n<tr>\n<td>JTCK<\/td>\n<td>TCK<\/td>\n<\/tr>\n<tr>\n<td>JTMS<\/td>\n<td>TMS<\/td>\n<\/tr>\n<tr>\n<td>RESET<\/td>\n<td>RST<\/td>\n<\/tr>\n<tr>\n<td>GND<\/td>\n<td>GND<\/td>\n<\/tr>\n<tr>\n<td>TX0<\/td>\n<td>RXD<\/td>\n<\/tr>\n<tr>\n<td>RX0<\/td>\n<td>TXD<\/td>\n<\/tr>\n<tr>\n<td>3.3V<\/td>\n<td>3V3<\/td>\n<\/tr>\n<tr>\n<td>nothing<\/td>\n<td>5V<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"powering\">Powering<a class=\"headerlink\" href=\"#powering\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>When not powering the Nano from a JTAG adapter,\nit can be powered in several alternative ways:<\/p>\n<ol>\n<li>connecting a USB power-bank to the USB-C port<\/li>\n<li>connecting a battery (pack) with voltage between 3.3 V and 6 V\nto the voltage regulator (i.e. the 5V pin) and ground (GND pin).<\/li>\n<li>connecting a 3.3 V power source directly to the 3.3V pin\n   (i.e. left of the 5V pin), i.e. without using the onboard\n   voltage regulator<\/li>\n<\/ol>\n<p>The VBAT pin is for connecting a 2.6 to 3.6 V battery to power\nthe RTC (in the battery backup domain) when the main voltage\nsupply is turned off. When no extra RTC battery is required, the\nmanual <a href=\"https:\/\/electronics.stackexchange.com\/q\/616993\/56807\">recommends to connect VBAT to VDD<\/a> (i.e. to the\n3.3V pin) and place a 0.1 \u00b5F decoupling capacitor between VBAT\nand ground.  According to the schematics, there should be an\nunpopulated R8 somewhere on the PCB which could be closed in\norder to follow the recommendation.<\/p>\n<p>Similarly to the <a href=\"https:\/\/www.sparkfun.com\/products\/11114\">Arduino Mini Pro<\/a>, the Nano board isn't really\noptimized for low-power. First of all, it has a power-indicator\nLED. Also, the onboard voltage regulator (<a href=\"https:\/\/datasheet.lcsc.com\/szlcsc\/Nanjing-Micro-One-Elec-ME6211C33M5G-N_C82942.pdf\">MicrOne ME6211C33<\/a>)\nisn't the most efficient one (I_q=60 \u00b5A or so, whereas - say -\nthe <a href=\"https:\/\/ww1.microchip.com\/downloads\/aemDocuments\/documents\/APID\/ProductDocuments\/DataSheets\/MCP1700-Data-Sheet-20001826F.pdf\">MCP1700<\/a> has I_q=1.6 \u00b5A and a lower voltage drop).<\/p>\n<p>One could try the following modifications in order to decrease\nthe power usage:<\/p>\n<ol>\n<li>Desolder the power-on LED (DS1) or its series resistor\n   (R6=2kOhm).<\/li>\n<li>Desolder\/remove the voltage regulator and use an more\n   efficient one, externally, if required.<\/li>\n<\/ol>\n<p>Especially the first modification looks more complicated with the\nNano than with the Arduino Mini Pro, because the SMD parts are\ngrouped together more tightly. Unfortunately, unlike with the\noriginal Sparkfun Arduino Mini Pro there is no\n<a href=\"https:\/\/cdn.sparkfun.com\/datasheets\/Dev\/Arduino\/Boards\/Arduino-Pro-Mini-v14.pdf\">Jumper<\/a> that could\nsimply be disconnected (desoldered) to disconnect the onboard\nvoltage regulator and power-on LED.<\/p>\n<h2 id=\"development-environment\">Development Environment<a class=\"headerlink\" href=\"#development-environment\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>First of all, the JTAG device likely needs some additional udev\nrule such that its device files can be accessed by a normal user. For\nexample, for the Sipeed device one could drop a config snippet\nlike the following into <code>\/etc\/udev\/rules.d<\/code>, e.g. in a file named\n<code>99-platformio-udev.rule<\/code>:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>ATTRS{idVendor}==&quot;0403&quot;, ATTRS{idProduct}==&quot;6010&quot;, MODE=&quot;0660&quot;, GROUP=&quot;dialout&quot;, ENV{ID_MM_DEVICE_IGNORE}=&quot;1&quot;, ENV{ID_MM_PORT_IGNORE}=&quot;1&quot;\n<\/code><\/pre><\/div>\n\n<p>Make then sure that your user is part of the <code>dialout<\/code> group.<\/p>\n<hr>\n<p>The simplest way to start development is to use\n<a href=\"https:\/\/platformio.org\/\">PlatformIO<\/a> which supports the\nGD32VF103\/Longan Nano. PlatformIO can be used exclusively from\nthe <a href=\"https:\/\/github.com\/platformio\/platformio-core\/\">command line<\/a>, but there\nis also a PlatformIO VSCode plugin, which VSCode fans might\nprefer.<\/p>\n<p>In case PlatformIO isn't packaged yet for your favourite Linux\ndistribution you can install it like this:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>python3 -m venv ~\/local\/platformio\nsource ~\/local\/platformio\/bin\/activate\npip install platformio\ncd ~\/local\/bin\nln -s ..\/platformio\/bin\/pio\n<\/code><\/pre><\/div>\n\n<p>First test is to query the Longan Nano support:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>$<span class=\"w\"> <\/span>pio<span class=\"w\"> <\/span>boards<span class=\"w\"> <\/span>longan\n\nPlatform:<span class=\"w\"> <\/span><span class=\"nv\">gd32v<\/span>\n<span class=\"o\">============================================================================================<\/span>\nID<span class=\"w\">                       <\/span>MCU<span class=\"w\">            <\/span>Frequency<span class=\"w\">    <\/span>Flash<span class=\"w\">    <\/span>RAM<span class=\"w\">    <\/span>Name\n-----------------------<span class=\"w\">  <\/span>-------------<span class=\"w\">  <\/span>-----------<span class=\"w\">  <\/span>-------<span class=\"w\">  <\/span>-----<span class=\"w\">  <\/span>-----------------------\nsipeed-longan-nano<span class=\"w\">       <\/span>GD32VF103CBT6<span class=\"w\">  <\/span>108MHz<span class=\"w\">       <\/span>128KB<span class=\"w\">    <\/span>32KB<span class=\"w\">   <\/span>Sipeed<span class=\"w\"> <\/span>Longan<span class=\"w\"> <\/span>Nano\nsipeed-longan-nano-lite<span class=\"w\">  <\/span>GD32VF103C8T6<span class=\"w\">  <\/span>108MHz<span class=\"w\">       <\/span>64KB<span class=\"w\">     <\/span>20KB<span class=\"w\">   <\/span>Sipeed<span class=\"w\"> <\/span>Longan<span class=\"w\"> <\/span>Nano<span class=\"w\"> <\/span>Lite\n\nPlatform:<span class=\"w\"> <\/span><span class=\"nv\">nuclei<\/span>\n<span class=\"o\">======================================================================================<\/span>\nID<span class=\"w\">                      <\/span>MCU<span class=\"w\">            <\/span>Frequency<span class=\"w\">    <\/span>Flash<span class=\"w\">    <\/span>RAM<span class=\"w\">    <\/span>Name\n----------------------<span class=\"w\">  <\/span>-------------<span class=\"w\">  <\/span>-----------<span class=\"w\">  <\/span>-------<span class=\"w\">  <\/span>-----<span class=\"w\">  <\/span>------------------\ngd32vf103c_longan_nano<span class=\"w\">  <\/span>GD32VF103CBT6<span class=\"w\">  <\/span>108MHz<span class=\"w\">       <\/span>128KB<span class=\"w\">    <\/span>32KB<span class=\"w\">   <\/span>Sipeed<span class=\"w\"> <\/span>Longan<span class=\"w\"> <\/span>Nano\n<\/code><\/pre><\/div>\n\n<p>There is a pitfall right there! The first platform gd32v uses the outdated and\nunmaintained legacy gd32vf103-sdk framework <sup>(cf. https:\/\/github.com\/riscv-mcu\/GD32VF103_Firmware_Library)<\/sup>. Unfortunately, many Longan Nano\nexamples found online are a few years old and thus do use this framework.\nHence, one might be tempted to start with that. However, it's incomplete and buggy.\nThose examples that are using the gd32v platform are still instructive, though, because much\nof the API is also present in the <a href=\"https:\/\/github.com\/Nuclei-Software\/nuclei-sdk\">Nuclei SDK<\/a>. But some APIs changed so be prepared\nto adjust old examples when looking at them.<\/p>\n<p>So the <a href=\"https:\/\/github.com\/Nuclei-Software\/nuclei-sdk\">Nuclei platform<\/a> it is. To create a new project:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>mkdir first-longan-test\ncd first-longan-test\npio project init --board gd32vf103c_longan_nano --ide vim\n<\/code><\/pre><\/div>\n\n<p>This creates a <code>platform.ini<\/code> file that reads like this:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">[env:gd32vf103c_longan_nano]<\/span>\n<span class=\"na\">platform<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"s\">nuclei<\/span>\n<span class=\"na\">board<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"s\">gd32vf103c_longan_nano<\/span>\n<span class=\"na\">framework<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"s\">nuclei-sdk<\/span>\n<\/code><\/pre><\/div>\n\n<p>NB: With the nuclei platform the default <code>upload_protocol<\/code> is <code>rv-link<\/code> which works fine with the Sipeed JTAG device.<\/p>\n<p>For integration with <a href=\"http:\/\/vimdoc.sourceforge.net\/htmldoc\/quickfix.html\">Vim quickfix<\/a> mode you can create a simple makefile like the following:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"o\">.<\/span><span class=\"n\">PHONY<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">all<\/span>\n<span class=\"n\">all<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">build<\/span>\n\n\n<span class=\"o\">.<\/span><span class=\"n\">PHONY<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">build<\/span>\n<span class=\"n\">build<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">pio<\/span><span class=\"w\"> <\/span><span class=\"n\">run<\/span>\n\n<span class=\"o\">.<\/span><span class=\"n\">PHONY<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"n\">upload<\/span>\n<span class=\"n\">upload<\/span><span class=\"p\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">pio<\/span><span class=\"w\"> <\/span><span class=\"n\">run<\/span><span class=\"w\"> <\/span><span class=\"o\">--<\/span><span class=\"n\">target<\/span><span class=\"w\"> <\/span><span class=\"n\">upload<\/span>\n<\/code><\/pre><\/div>\n\n<p>For a first test run,  to check whether the edit\/compile\/flash cycle works, you can\nblink the on-board LED:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"cp\">#include<\/span><span class=\"w\"> <\/span><span class=\"cpf\">&lt;gd32vf103.h&gt;<\/span>\n<span class=\"cp\">#include<\/span><span class=\"w\"> <\/span><span class=\"cpf\">&lt;gd32vf103_gpio.h&gt;<\/span>\n<span class=\"cp\">#include<\/span><span class=\"w\"> <\/span><span class=\"cpf\">&lt;gd32vf103_rcu.h&gt;<\/span><span class=\"c1\"> \/\/ reset and clock unit<\/span>\n\n<span class=\"cp\">#include<\/span><span class=\"w\"> <\/span><span class=\"cpf\">&lt;stdbool.h&gt;<\/span>\n\n\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">switch_led<\/span><span class=\"p\">(<\/span><span class=\"kt\">bool<\/span><span class=\"w\"> <\/span><span class=\"n\">b<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ inverse logic with the onboard red LED<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">b<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">GPIO_BC<\/span><span class=\"p\">(<\/span><span class=\"n\">GPIOC<\/span><span class=\"p\">)<\/span><span class=\"w\">  <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_PIN_13<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"c1\">\/\/ bit-clear<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">else<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">GPIO_BOP<\/span><span class=\"p\">(<\/span><span class=\"n\">GPIOC<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_PIN_13<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"c1\">\/\/ bit-operate<\/span>\n\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ alternatively:<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ gpio_bit_write(GPIOC, GPIO_PIN_13, !b);<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"nf\">blink3<\/span><span class=\"p\">(<\/span><span class=\"kt\">unsigned<\/span><span class=\"w\"> <\/span><span class=\"n\">x<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"kt\">unsigned<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">switch_led<\/span><span class=\"p\">(<\/span><span class=\"nb\">true<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">delay_1ms<\/span><span class=\"p\">(<\/span><span class=\"n\">x<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">switch_led<\/span><span class=\"p\">(<\/span><span class=\"nb\">false<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">delay_1ms<\/span><span class=\"p\">(<\/span><span class=\"mi\">223<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">switch_led<\/span><span class=\"p\">(<\/span><span class=\"nb\">true<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">delay_1ms<\/span><span class=\"p\">(<\/span><span class=\"n\">x<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">switch_led<\/span><span class=\"p\">(<\/span><span class=\"nb\">false<\/span><span class=\"p\">);<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"kt\">int<\/span><span class=\"w\"> <\/span><span class=\"nf\">main<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rcu_periph_clock_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_GPIOC<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">gpio_init<\/span><span class=\"p\">(<\/span><span class=\"n\">GPIOC<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_MODE_OUT_PP<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_OSPEED_50MHZ<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_PIN_13<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">switch_led<\/span><span class=\"p\">(<\/span><span class=\"nb\">false<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(;;)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">blink3<\/span><span class=\"p\">(<\/span><span class=\"mi\">223<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">delay_1ms<\/span><span class=\"p\">(<\/span><span class=\"mi\">3<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"mi\">223<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">blink3<\/span><span class=\"p\">(<\/span><span class=\"mi\">3<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"mi\">223<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">delay_1ms<\/span><span class=\"p\">(<\/span><span class=\"mi\">3<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"mi\">223<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">blink3<\/span><span class=\"p\">(<\/span><span class=\"mi\">223<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">delay_1ms<\/span><span class=\"p\">(<\/span><span class=\"mi\">5<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"mi\">223<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>Just drop this example into the <code>src<\/code> directory as - say - <code>main.c<\/code> and call <code>make<\/code>.<\/p>\n<h2 id=\"c\">C++<a class=\"headerlink\" href=\"#c\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>One can also use C++ (or a subset of C++) with this MCU. However, not all of the SDK headers are C++ friendly, i.e. not all of them wrap themselves with:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"cp\">#ifdef  __cplusplus<\/span>\n<span class=\"k\">extern<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;C&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"cp\">#endif<\/span>\n\n<span class=\"c1\">\/\/ [all the declarations ...]<\/span>\n\n<span class=\"cp\">#ifdef  __cplusplus<\/span>\n<span class=\"p\">}<\/span>\n<span class=\"cp\">#endif<\/span>\n<\/code><\/pre><\/div>\n\n<p>Thus, from C++ one has to include the SDK headers like so:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">extern<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;C&quot;<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"cp\">#include<\/span><span class=\"w\"> <\/span><span class=\"cpf\">&lt;gd32vf103_rcu.h&gt;<\/span>\n<span class=\"cp\">#include<\/span><span class=\"w\"> <\/span><span class=\"cpf\">&lt;gd32vf103_gpio.h&gt;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>NB: The next Nuclei SDK release will have all such headers in a\nC++ friendly state (cf. my merged\n<a href=\"https:\/\/github.com\/Nuclei-Software\/nuclei-sdk\/pull\/38\">pull-request<\/a>).<\/p>\n<p>Also, interrupt handlers must be wrapped in a similar way.<\/p>\n<p>NB: Although the Nuclei SDK doesn't compile with\n<code>-fno-exceptions<\/code> (unlike e.g. the GCC\/AVR SDK that targets 8 bit\nMCUs), C++ exceptions simply don't work, out of the box.<\/p>\n<p>Code that throws and catches exceptions compiles, but the linker\nthen warns about <code>_getpid<\/code> and <code>_kill<\/code> always failing (due to not\nbeing implemented). Also, even a small example with a single\nthrow\/catch increases precious flash space usage by 40 KiB.<\/p>\n<p>FWIW, when actually disabling exceptions (by setting\n<code>build_flags = -fno-exceptions<\/code> in <code>platformio.ini<\/code>) compiling a\na small C++ example leads to 192 bytes of flash space being saved.<\/p>\n<h2 id=\"interrupts\">Interrupts<a class=\"headerlink\" href=\"#interrupts\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Only letting LEDs blinks in a busy-loop gets boring, fast.<\/p>\n<p>Configuring interrupts on the GD32VF103 is a bit more involved than with a -\nsay - ATmega328p, because the interrupt system is more advanced.<\/p>\n<p>For example, to configure an external interrupt for pin A0:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">rcu_periph_clock_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_AF<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">rcu_periph_clock_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_GPIOA<\/span><span class=\"p\">);<\/span>\n\n<span class=\"c1\">\/\/ configure internal pull-up<\/span>\n<span class=\"n\">gpio_init<\/span><span class=\"p\">(<\/span><span class=\"n\">GPIOB<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_MODE_IPU<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_OSPEED_50MHZ<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_PIN_0<\/span><span class=\"p\">);<\/span>\n\n<span class=\"c1\">\/\/ alternatively, with external pull-up\/pull-down<\/span>\n<span class=\"c1\">\/\/ gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_0);<\/span>\n\n<span class=\"n\">gpio_exti_source_select<\/span><span class=\"p\">(<\/span><span class=\"n\">GPIO_PORT_SOURCE_GPIOA<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_PIN_SOURCE_0<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">exti_init<\/span><span class=\"p\">(<\/span><span class=\"n\">EXTI_0<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">EXTI_INTERRUPT<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">EXTI_TRIG_BOTH<\/span><span class=\"p\">);<\/span>\n\n<span class=\"n\">exti_interrupt_flag_clear<\/span><span class=\"p\">(<\/span><span class=\"n\">EXTI_0<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">exti_interrupt_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">EXTI_0<\/span><span class=\"p\">);<\/span>\n\n<span class=\"c1\">\/\/ higher preempts lower, IRQ levels start at 1<\/span>\n<span class=\"n\">ECLIC_SetLevelIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">EXTI0_IRQn<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">);<\/span>\n<span class=\"c1\">\/\/ doesn&#39;t preempt, but is a tie breaker for multiple pending ones<\/span>\n<span class=\"n\">ECLIC_SetPriorityIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">EXTI0_IRQn<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">ECLIC_EnableIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">EXTI0_IRQn<\/span><span class=\"p\">);<\/span>\n\n<span class=\"c1\">\/\/ interrupts aren&#39;t enabled, by default<\/span>\n<span class=\"n\">__enable_irq<\/span><span class=\"p\">();<\/span>\n<\/code><\/pre><\/div>\n\n<p>There are several external interrupts and each can wake-up the CPU from sleep.<\/p>\n<p>Sidenote: The CPU can be also be woken by configured events which should be faster.<\/p>\n<p>The available interrupts can be looked up via:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>grep<span class=\"w\"> <\/span>IRQn<span class=\"w\"> <\/span>~\/.platformio\/packages\/framework-nuclei-sdk\/SoC\/gd32vf103\/Common\/Include\/gd32vf103.h\n<\/code><\/pre><\/div>\n\n<p>NB: Not every GPIO pin configured as input can have its own interrupt. For example, <code>EXTI0_IRQn<\/code> would be shared between pin 0 of each port group (e.g. B0 and C0). In addition, some pins are grouped into one interrupt such as <code>EXTI10_15_IRQn<\/code> for pins 10 to 15.<\/p>\n<hr>\n<p>With the above setup handling an interrupt is as simple as overwriting a weak symbol, e.g.:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">volatile<\/span><span class=\"w\"> <\/span><span class=\"kt\">bool<\/span><span class=\"w\"> <\/span><span class=\"n\">some_value<\/span><span class=\"p\">;<\/span>\n\n<span class=\"cp\">#ifdef  __cplusplus<\/span>\n<span class=\"k\">extern<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;C&quot;<\/span>\n<span class=\"cp\">#endif<\/span>\n<span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"n\">EXTI0_IRQHandler<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">exti_interrupt_flag_get<\/span><span class=\"p\">(<\/span><span class=\"n\">EXTI_0<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"n\">RESET<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ if it&#39;s an edge-triggered vectored interrupt then<\/span>\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ the flag should be auto-cleared<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">exti_interrupt_flag_clear<\/span><span class=\"p\">(<\/span><span class=\"n\">EXTI_0<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ do as little work as necessary in the handler, e.g.<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">some_value<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">gpio_input_bit_get<\/span><span class=\"p\">(<\/span><span class=\"n\">GPIOA<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">GPIO_PIN_0<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>To get a list of available interrupt handler symbol names:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"nx\">grep<\/span><span class=\"w\"> <\/span><span class=\"err\">&#39;<\/span><span class=\"nx\">weak<\/span><span class=\"p\">.<\/span><span class=\"o\">*<\/span><span class=\"nx\">Handler<\/span><span class=\"err\">&#39;<\/span><span class=\"w\"> <\/span><span class=\"o\">~\/<\/span><span class=\"p\">.<\/span><span class=\"nx\">platformio<\/span><span class=\"o\">\/<\/span><span class=\"nx\">packages<\/span><span class=\"o\">\/<\/span><span class=\"nx\">framework<\/span><span class=\"o\">-<\/span><span class=\"nx\">nuclei<\/span><span class=\"o\">-<\/span><span class=\"nx\">sdk<\/span><span class=\"o\">\/<\/span><span class=\"nx\">SoC<\/span><span class=\"o\">\/<\/span><span class=\"nx\">gd32vf103<\/span><span class=\"o\">\/<\/span><span class=\"nx\">Common<\/span><span class=\"o\">\/<\/span><span class=\"nx\">Source<\/span><span class=\"o\">\/<\/span><span class=\"nx\">GCC<\/span><span class=\"o\">\/<\/span><span class=\"nx\">startup_gd32vf103<\/span><span class=\"p\">.<\/span><span class=\"nx\">S<\/span>\n<\/code><\/pre><\/div>\n\n<hr>\n<p>Enabling an interrupt thus basically consists of the following steps:<\/p>\n<ol>\n<li>Enable the corresponding interface<\/li>\n<li>Configure the device<\/li>\n<li>Enable an interrupt at the device level<\/li>\n<li>Configure the interrupt controller (ECLIC)<\/li>\n<li>Enable interrupts globally<\/li>\n<li>Add an interrupt handler<\/li>\n<\/ol>\n<p>For comparison, to configure the RTC period 'second' interrupt (not to be confused with the RTC alarm interrupt):<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"c1\">\/\/ [.. enable\/configure RTC ..]<\/span>\n<span class=\"c1\">\/\/ prescale to 1 second (-1 because divider starts at 0 ...)<\/span>\n<span class=\"n\">rtc_prescaler_set<\/span><span class=\"p\">(<\/span><span class=\"mi\">32768<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">rtc_lwoff_wait<\/span><span class=\"p\">();<\/span>\n<span class=\"n\">rtc_interrupt_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_INT_SECOND<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">rtc_lwoff_wait<\/span><span class=\"p\">();<\/span>\n\n<span class=\"n\">ECLIC_SetLevelIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_IRQn<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">ECLIC_SetPriorityIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_IRQn<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">ECLIC_EnableIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_IRQn<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div>\n\n<p>The corresponding interrupt handler:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">volatile<\/span><span class=\"w\"> <\/span><span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">rtc_ticks<\/span><span class=\"p\">;<\/span>\n\n<span class=\"cp\">#ifdef  __cplusplus<\/span>\n<span class=\"k\">extern<\/span><span class=\"w\"> <\/span><span class=\"s\">&quot;C&quot;<\/span>\n<span class=\"cp\">#endif<\/span>\n<span class=\"kt\">void<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_IRQHandler<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">rtc_interrupt_flag_get<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_INT_FLAG_SECOND<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"n\">RESET<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">rtc_flag_clear<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_INT_FLAG_SECOND<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">        <\/span><span class=\"o\">++<\/span><span class=\"n\">rtc_ticks<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<hr>\n<p>A common use-case for an MCU is to let it sleep\/idle most of the time (to conserve energy) and wait on an interrupt. With the GD32VF103 (and the Nuclei SDK) this can be achieved like this:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"cp\">#include<\/span><span class=\"w\"> <\/span><span class=\"cpf\">&lt;gd32vf103_pmu.h&gt;<\/span>\n\n\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(;;)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">pmu_to_sleepmode<\/span><span class=\"p\">(<\/span><span class=\"n\">WFI_CMD<\/span><span class=\"p\">);<\/span><span class=\"w\"> <\/span><span class=\"c1\">\/\/ wait for interrupt<\/span>\n\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ go to work!<\/span>\n\n<span class=\"w\">        <\/span><span class=\"c1\">\/\/ [..]<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<h2 id=\"realtime-clock-rtc\">Realtime Clock (RTC)<a class=\"headerlink\" href=\"#realtime-clock-rtc\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Like the STM32F103 and unlike the - say - ATmega328p, the\nGD32VF103 integrates a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Real-time_clock\">realtime clock (RTC) unit<\/a>. The purpose of\nthe RTC to keep track of time even when the MCU is in deep sleep,\npossibly wake it up via an alarm at some point in time and\noptionally generate a timer interrupt. When the MCU doesn't provide\nan RTC an alternative is to use an external one such as a PCF8523\nor DS3231.<\/p>\n<p>A common RTC design is that an additional external low-speed\n32.768 kHz oscillator is used. This is also possible with the\nGD32VF103 and the Longan Nano board even connects the dedicated\npins to such an external quartz, like Blue Pill boards do.\nSee also the <a href=\"#assembling\">Assembling Section<\/a> for how these pins can be used\notherwise.<\/p>\n<p>Configuring the RTC is a little bit involved since it is located\nin the 'battery backup domain' (such that the rest of the MCU can\nbe powered down), it has several options for a clock source and\nmany other aspects can be customized. Being in the backup domain\nleads to shadow registers that have to synced over an <a href=\"https:\/\/en.wikipedia.org\/wiki\/Advanced_Microcontroller_Bus_Architecture\">Advanced\nPeripheral Bus (APB)<\/a>.<\/p>\n<p>For example, to set up the RTC for a periodic interrupt that\ntriggers each second:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"c1\">\/\/ enable power managemenet unit - perhaps enabled by default<\/span>\n<span class=\"n\">rcu_periph_clock_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_PMU<\/span><span class=\"p\">);<\/span>\n<span class=\"c1\">\/\/ enable write access to the registers in the backup domain<\/span>\n<span class=\"n\">pmu_backup_write_enable<\/span><span class=\"p\">();<\/span>\n<span class=\"c1\">\/\/ enable backup domain<\/span>\n<span class=\"n\">rcu_periph_clock_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_BKPI<\/span><span class=\"p\">);<\/span>\n<span class=\"c1\">\/\/ reset backup domain registers<\/span>\n<span class=\"n\">bkp_deinit<\/span><span class=\"p\">();<\/span>\n<span class=\"c1\">\/\/ set the results of a previous calibration procedure<\/span>\n<span class=\"c1\">\/\/ bkp_rtc_calibration_value_set(x);<\/span>\n\n<span class=\"c1\">\/\/ setup RTC<\/span>\n<span class=\"c1\">\/\/ enable external low speed XO<\/span>\n<span class=\"n\">rcu_osci_on<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_LXTAL<\/span><span class=\"p\">);<\/span>\n<span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">rcu_osci_stab_wait<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_LXTAL<\/span><span class=\"p\">))<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ use external low speed oscillaotr, i.e. 32.768 kHz<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rcu_rtc_clock_config<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_RTCSRC_LXTAL<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rcu_periph_clock_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RCU_RTC<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ wait until shadow registers are synced from the backup domain<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ over the APB bus<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rtc_register_sync_wait<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ wait until shadow register changes are synced over APB<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ to the backup doamin<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rtc_lwoff_wait<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"c1\">\/\/ prescale to 1 second<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rtc_prescaler_set<\/span><span class=\"p\">(<\/span><span class=\"mi\">32768<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rtc_lwoff_wait<\/span><span class=\"p\">();<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rtc_interrupt_enable<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_INT_SECOND<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">rtc_lwoff_wait<\/span><span class=\"p\">();<\/span>\n<span class=\"p\">}<\/span>\n\n<span class=\"c1\">\/\/ enable interrupt for RTC<\/span>\n<span class=\"n\">ECLIC_SetLevelIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_IRQn<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">);<\/span>\n<span class=\"c1\">\/\/ doesn&#39;t preempt, but is a tie breaker for multiple pending ones<\/span>\n<span class=\"n\">ECLIC_SetPriorityIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_IRQn<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"p\">);<\/span>\n<span class=\"n\">ECLIC_EnableIRQ<\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_IRQn<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div>\n\n<p>Since the external low speed quartz has a frequency of 32.768 kHz, we have\nto let the RTC count exactly 32768 times in order to generate the second\ninterrupt. As the counter starts at the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Prescaler\">prescaler<\/a> value, wraps around at 0 and\nthe interrupt is emitted at the top value we thus have to set the prescaler to\nthe frequency minus one.<\/p>\n<p>The different features of the RTC can be described in <a href=\"https:\/\/en.wikipedia.org\/wiki\/Pseudocode\">pseudocode<\/a> like this:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">RTC_unit<\/span><span class=\"o\">:<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">while<\/span><span class=\"w\"> <\/span><span class=\"n\">unit_is_enabled<\/span><span class=\"o\">:<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">second_irq_enabled<\/span><span class=\"o\">:<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">emit_second_interrupt<\/span><span class=\"o\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">alarm_irq_enabled<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_CNT<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_ALRM<\/span><span class=\"o\">:<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">emit_alarm_interrupt<\/span><span class=\"o\">()<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">overflow_irq_enabled<\/span><span class=\"w\"> <\/span><span class=\"o\">&amp;&amp;<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_CNT<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">32<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"o\">:<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">emit_overflow_interrupt<\/span><span class=\"o\">()<\/span>\n\n<span class=\"w\">        <\/span><span class=\"n\">RTC_CNT<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"n\">RTC_CNT<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"o\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">%<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">32<\/span><span class=\"w\"> <\/span><span class=\"c1\">\/\/ observable after next tick!<\/span>\n\n<span class=\"w\">        <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_DIV<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_PSC<\/span><span class=\"o\">;<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_DIV<\/span><span class=\"w\"> <\/span><span class=\"o\">&gt;=<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"o\">;<\/span><span class=\"w\"> <\/span><span class=\"o\">--<\/span><span class=\"n\">i<\/span><span class=\"o\">:<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">wait_for_next_tick<\/span><span class=\"o\">()<\/span>\n\n<span class=\"w\">            <\/span><span class=\"n\">pulses<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"o\">(<\/span><span class=\"n\">pulses<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"mi\">1<\/span><span class=\"o\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">%<\/span><span class=\"w\"> <\/span><span class=\"mi\">2<\/span><span class=\"o\">**<\/span><span class=\"mi\">20<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"n\">pulses<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"o\">:<\/span>\n<span class=\"w\">                <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mi\">0<\/span><span class=\"o\">;<\/span><span class=\"w\"> <\/span><span class=\"n\">i<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;<\/span><span class=\"w\"> <\/span><span class=\"n\">RCCV<\/span><span class=\"o\">;<\/span><span class=\"w\"> <\/span><span class=\"o\">++<\/span><span class=\"n\">i<\/span><span class=\"o\">:<\/span>\n<span class=\"w\">                    <\/span><span class=\"n\">wait_for_next_tick<\/span><span class=\"o\">()<\/span>\n<\/code><\/pre><\/div>\n\n<p>The upper-case variables are actual register (field) names that are also\nused in the <a href=\"http:\/\/www.gd32mcu.com\/download\/down\/document_id\/222\/path_type\/1\">GD32VF103 manual<\/a>. They are:<\/p>\n<table class=\"m-table\">\n<thead>\n<tr>\n<th>Field<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>RTC_CNT<\/code><\/td>\n<td>RTC counter (32 bits), incremented after <code>RTC_PSC + 1<\/code> ticks<\/td>\n<\/tr>\n<tr>\n<td><code>RTC_PSC<\/code><\/td>\n<td>RTC prescaler (20 bits)<\/td>\n<\/tr>\n<tr>\n<td><code>RTC_DIV<\/code><\/td>\n<td>RTC divider (20 bits), current prescaler iteration,<\/td>\n<\/tr>\n<tr>\n<td><code>RTC_ALRM<\/code><\/td>\n<td>RTC alarm (32 bits), emit alarm after <code>RTC_CNT<\/code> equals <code>RTC_ALRM + 1<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>RCCV<\/code><\/td>\n<td>Realtime clock calibration value (7 bits)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<hr>\n<p>More concretely, like almost all mapped peripheral\nregisters, in reality, those registers are split between a low a high part\n(e.g. <code>RTC_CNTL<\/code> and <code>RTC_CNTH<\/code>), as\nmentioned in the <a href=\"#characteristics\">Characteristics Section<\/a>. <\/p>\n<p>Thus, reading the RTC_CNT counter accurately is tricky. See for\nexample the utility function the Nuclei SDK contains (as of May\n2022):<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"nf\">rtc_counter_get<\/span><span class=\"p\">(<\/span><span class=\"kt\">void<\/span><span class=\"p\">)<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">temp<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"mh\">0x0U<\/span><span class=\"p\">;<\/span>\n\n<span class=\"w\">    <\/span><span class=\"n\">temp<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_CNTL<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"n\">temp<\/span><span class=\"w\"> <\/span><span class=\"o\">|=<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">RTC_CNTH<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_HIGH_BITS_OFFSET<\/span><span class=\"p\">);<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"n\">temp<\/span><span class=\"p\">;<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>The order of the reads is guaranteed because <code>RTC_CNTL<\/code> and\n<code>RTC_CNTH<\/code> are macros that expand to dereferenced pointers to\n<code>volatile<\/code> declared memory locations. However, the issue here is that\n<code>RTC_CNTL<\/code> might overflow and yield an increment of <code>RTC_CNTH<\/code>\n<em>after<\/em> it's loaded into temp but <em>before<\/em> <code>RTC_CNTH<\/code> is loaded.\nThus, a more robust approach is to code it like this:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"k\">static<\/span><span class=\"w\"> <\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"nf\">get_rtc_cnt<\/span><span class=\"p\">()<\/span>\n<span class=\"p\">{<\/span>\n<span class=\"w\">    <\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">old_h<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_CNTH<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(;;)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">l<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_CNTL<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">h<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">RTC_CNTH<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">h<\/span><span class=\"w\"> <\/span><span class=\"o\">==<\/span><span class=\"w\"> <\/span><span class=\"n\">old_h<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">            <\/span><span class=\"kt\">uint32_t<\/span><span class=\"w\"> <\/span><span class=\"n\">r<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"n\">h<\/span><span class=\"w\"> <\/span><span class=\"o\">&lt;&lt;<\/span><span class=\"w\"> <\/span><span class=\"mi\">16<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"o\">|<\/span><span class=\"w\"> <\/span><span class=\"n\">l<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"n\">r<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span>\n<span class=\"w\">        <\/span><span class=\"n\">old_h<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">h<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>This race condition isn't just a theoretical issue, one can\neasily verify that the probability of occurence is greater than\nzero by setting the prescaler to a low value (or zero), calling\nthat function in a loop and adding some diagnostics code at the\nend of the loop (or setting a breakpoint there).<\/p>\n<hr>\n<p>Since the prescaler register is 20 bit wide, the so called 'second'\ninterrupt can be set up to trigger from every 1\/32768 second up\nto half a minute, when using the 32.768 kHz oscillator.<\/p>\n<hr>\n<p>The <code>RCCV<\/code> register field can be set in order to compensate for an\noscillator that runs too fast. Without any\ncompensation, the clock likely drifts by a few seconds per day\ndue to production variability, temperature variation and aging.\nSee also a follow-up article where I describe a concrete\ncalibration procedure.<\/p>\n<p>NB: This is similar to the STM32F103 which also allows to set a 7\nbit wide calibration value in the RTC clock calibration register\nBKP_RTCCR.<\/p>\n<p>NB: The GD32F130's RTC also has a calibration register\n(RTC_HRFC). In contrast to the other devices, its calibration\nvalue is 9 bit wide and the register even contains a sign bit\n(FREQI).\nThat means it's also possible to compensate for a clock that goes\ntoo slow that way (i.e. by 'injecting' additional ticks after <code>2**20<\/code>\nreal ticks happened).<\/p>\n<p>Interestingly, the <a href=\"http:\/\/www.gd32mcu.com\/download\/down\/document_id\/222\/path_type\/1\">GD32VF103 manual<\/a> explicitly states\n(Section 4.4.2 RTC signal output control register BKP_OCTL, page\n59):<\/p>\n<blockquote>\n<p>The value indicates how many clock pulses are ignored <em>or added<\/em> every 2^20 RTC clock pulses.<\/p>\n<\/blockquote>\n<p>(emphasis mine)<\/p>\n<p>But there isn't any sign bit documented. And the RCCV field isn't\ninterpreted as two's complement value, either. Hence, it looks\nlike this statement is wrong and that RTC ticks can only be\nskipped when setting a calibration value, as with the STM32F103.<\/p>\n<p>Thus, to compensate a GD32VF103 (or STM32F103) RTC that goes too slow one has to\ndecrement the prescaler, as well, and work from there.<\/p>\n<h2 id=\"lcd\">LCD<a class=\"headerlink\" href=\"#lcd\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The Longan Nano comes with a small 160x80 pixel color LCD display\nattached which is good for 20 column x 5 row text or even some\ngraphics. It's connected to a ST7735S controller which in turn is\nattached to an SPI port. The controller implements the MIPI DCS\nstandard which seems to be popular with such devices.<\/p>\n<p>For quickly putting some text on the LCD, M\u00fcller's <a href=\"https:\/\/github.com\/MuellerA\/LonganNanoTest\/tree\/master\/Lcd\/src\">LCD and SPI\nclasses<\/a>\nare well suited and quick to start with.<\/p>\n<p>For real graphics the <a href=\"https:\/\/github.com\/slmisc\/gd32v-lcd\">gd32v-lcd library<\/a> looks like a good\nstarting point. (I haven't tested it, yet.)<\/p>\n<p>To make some sense of the command byte sequences used in the\nlibraries (and possibly modify\/extend them) a look into the\n<a href=\"https:\/\/dl.sipeed.com\/fileList\/LONGAN\/Nano\/HDK\/driver%20chip%20ST7735S_V1.5_20150303.pdf\">ST7735S\ndatasheet<\/a>\nis helpful.<\/p>\n<p>It's even possible to directly <code>printf()<\/code> to the LCD by overwrite\nthe weakly defined write stub. See also\n<code>~\/.platformio\/packages\/framework-nuclei-sdk\/SoC\/gd32vf103\/Common\/Source\/Stubs\/write.c<\/code>\nfor the default implementation. That means if you define the\nfunction yourself then it overwrites the weak default one.\nExample:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"n\">extern<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;C&quot;<\/span>\n<span class=\"n\">ssize_t<\/span><span class=\"w\"> <\/span><span class=\"n\">_write<\/span><span class=\"p\">(<\/span><span class=\"nb nb-Type\">int<\/span><span class=\"w\"> <\/span><span class=\"n\">fd<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"k\">const<\/span><span class=\"w\"> <\/span><span class=\"nb nb-Type\">void<\/span><span class=\"o\">*<\/span><span class=\"w\"> <\/span><span class=\"n\">ptr<\/span><span class=\"p\">,<\/span><span class=\"w\"> <\/span><span class=\"n\">size_t<\/span><span class=\"w\"> <\/span><span class=\"n\">len<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">{<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">if<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"o\">!<\/span><span class=\"n\">isatty<\/span><span class=\"p\">(<\/span><span class=\"n\">fd<\/span><span class=\"p\">))<\/span>\n<span class=\"w\">            <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"mi\">1<\/span><span class=\"p\">;<\/span>\n\n<span class=\"w\">        <\/span><span class=\"k\">const<\/span><span class=\"w\"> <\/span><span class=\"nb\">char<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"n\">p<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"p\">(<\/span><span class=\"k\">const<\/span><span class=\"w\"> <\/span><span class=\"nb\">char<\/span><span class=\"o\">*<\/span><span class=\"p\">)<\/span><span class=\"w\"> <\/span><span class=\"n\">ptr<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">const<\/span><span class=\"w\"> <\/span><span class=\"nb\">char<\/span><span class=\"w\"> <\/span><span class=\"o\">*<\/span><span class=\"n\">end<\/span><span class=\"w\"> <\/span><span class=\"o\">=<\/span><span class=\"w\"> <\/span><span class=\"n\">p<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"n\">len<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">        <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span><span class=\"p\">(;<\/span><span class=\"w\"> <\/span><span class=\"n\">p<\/span><span class=\"w\"> <\/span><span class=\"o\">!=<\/span><span class=\"w\"> <\/span><span class=\"n\">end<\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"o\">++<\/span><span class=\"n\">p<\/span><span class=\"p\">)<\/span>\n<span class=\"w\">            <\/span><span class=\"n\">lcd<\/span><span class=\"o\">.<\/span><span class=\"n\">putChar<\/span><span class=\"p\">(<\/span><span class=\"o\">*<\/span><span class=\"n\">p<\/span><span class=\"p\">);<\/span>\n\n<span class=\"w\">        <\/span><span class=\"k\">return<\/span><span class=\"w\"> <\/span><span class=\"n\">len<\/span><span class=\"p\">;<\/span>\n<span class=\"w\">    <\/span><span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>NB: The <code>printf()<\/code> implementation of the Nuclei SDK isn't\ncomplete, e.g. some specifiers such as <code>%f<\/code> or <code>%lu<\/code> are simply\nineffective.<\/p>\n<p>NB: The LCD and controller not just occupy the pins of the first\nSPI device, but also pins B0 (RS, i.e. command\/data selection),\nB1 (reset), B2 (chip select, not connected to an external pin)\nwhich aren't included in <a href=\"https:\/\/github.com\/kprasadvnsi\/longan_nano_pinout\/issues\/1\">kprasadvnsi's pinout\ndiagram<\/a>.<\/p>\n<h2 id=\"uart\">UART<a class=\"headerlink\" href=\"#uart\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The Nuclei SDK sets up the USART0 for 115200 baud UART during system start (cf. <code>gd_com_init()<\/code>), by default, and it\nprovides a stub such that <code>printf()<\/code> just works (cf. previous\nsection).<\/p>\n<p>When using the Sipeed JTAG device one can connect to the UART\nlike this:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>picocom --baud 115200 --echo --imap lfcrlf \/dev\/ttyUSB1\n<\/code><\/pre><\/div>\n\n<p>NB: Since the Nano is flashed over JTAG the serial console can be\nkept open during flashing. This is convenient in comparison with\nthe Arduino Mini Pro which are usually flashed also over the\nUART.<\/p>\n<p>For details on how to use and configure the UART (or USART) see\nalso <code>gd_com_init()<\/code> in\n<code>~\/.platformio\/packages\/framework-nuclei-sdk\/SoC\/gd32vf103\/Board\/gd32vf103c_longan_nano\/Source\/gd32vf103c_longan_nano.c<\/code>\nand the API in\n<code>~\/.platformio\/packages\/framework-nuclei-sdk\/SoC\/gd32vf103\/Common\/Source\/Drivers\/gd32vf103_usart.c<\/code>.<\/p>\n<hr>\n<p>M\u00fcller's <a href=\"https:\/\/github.com\/MuellerA\/LonganNanoTest\/tree\/master\/Usart\/src\">USART\nclass<\/a>\nis also instructive. If the default Nuclei's default serial\nparameters are sufficient you don't need to call its 'setup()`\nmethod.<\/p>\n<h2 id=\"where-to-go-from-here\">Where to go from here<a class=\"headerlink\" href=\"#where-to-go-from-here\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>There is definitely less information available on the net about\nthe GD32VF103 than about other microcontrollers. Also, the manual\narguably is more minimal than - say - the ATmega328p one.\nStarting points:<\/p>\n<ul>\n<li><a href=\"http:\/\/www.gd32mcu.com\/download\/down\/document_id\/287\/path_type\/1\">GD32VF103 Datasheet<\/a> - i.e. for looking up electrical\n  characteristics etc.<\/li>\n<li><a href=\"http:\/\/www.gd32mcu.com\/download\/down\/document_id\/222\/path_type\/1\">GD32VF103 Manual<\/a> - description of peripherals,\n  register reference etc.<\/li>\n<li><a href=\"https:\/\/dl.sipeed.com\/fileList\/LONGAN\/Nano\/HDK\/Longan%20Nano%203302\/2_Schematic\/Longan_nano_3302_Schematic.pdf\">Longan Nano Schematic<\/a><\/li>\n<li>Longan Nano <a href=\"https:\/\/github.com\/kprasadvnsi\/longan_nano_pinout\">pinout diagram<\/a><\/li>\n<\/ul>\n<p>However, a good strategy is to look up peripherals and other\nfeatures in the manual, check out the register description there\nand <code>grep<\/code> for the register names in the SDK. How the registers\nare accessed there in the implementation of API functions often\npaints a good picture how a feature is actually supposed to be\nused. Example:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code><span class=\"nx\">grep<\/span><span class=\"w\"> <\/span><span class=\"err\">&#39;<\/span><span class=\"nx\">GPIO<\/span><span class=\"p\">.<\/span><span class=\"o\">*<\/span><span class=\"nx\">BOP<\/span><span class=\"err\">&#39;<\/span><span class=\"w\"> <\/span><span class=\"o\">~\/<\/span><span class=\"p\">.<\/span><span class=\"nx\">platformio<\/span><span class=\"o\">\/<\/span><span class=\"nx\">packages<\/span><span class=\"o\">\/<\/span><span class=\"nx\">framework<\/span><span class=\"o\">-<\/span><span class=\"nx\">nuclei<\/span><span class=\"o\">-<\/span><span class=\"nx\">sdk<\/span><span class=\"o\">\/<\/span><span class=\"w\"> <\/span><span class=\"o\">-<\/span><span class=\"nx\">r<\/span>\n<\/code><\/pre><\/div>\n\n<hr>\n<p>As always, it doesn't hurt to search the web. There aren't many\narticles and examples available, but there are some. For example:<\/p>\n<p>Andreas M\u00fcller's <a href=\"https:\/\/github.com\/MuellerA\/LonganNanoTest\">collection of Longan Nano C++ test\nprograms<\/a> - the test\nprograms are well-structured and low-level access is often\nencapsulated in methods and classes in a meaningful way. The test\nprojects cover a bunch of features such as SPI, LCD, GPIO,\nUSART, I2C etc. The author has another repository\nthat contains some more Longan Nano test programs and\n<a href=\"https:\/\/github.com\/smuehlst\/longan-nano-experiments\">experiments<\/a>.\nAt least some of those seem to be based on examples distributed\nby GigaDevice.<\/p>\n<p>As mentioned before, some examples use the legacy framework and\nneed to adjusted, but often they are still quite helpful.<\/p>\n<hr>\n<p>Another angle is to also search in the context of the STM32F103\n(or just a keyword such as STM32 or STM32F1) since many features work in a similar way and\nthere are even similarities in the API of the SDKs. For example,\nafter reading a good tutorial on the STM32F103 one should be able\nto transfer much of the knowledge on how to access and configure\nGPIO pins to the GD32VF103.<\/p>\n<h2 id=\"parts\">Parts<a class=\"headerlink\" href=\"#parts\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>As of May 2022, Seeed Studio has all RISC-V articles listed as\nout-of-stock (as well as all of their ARM and AVR boards).\nAlso, shipping to Europe involves extra hassles due to import\nduties and taxes (either just extra charges or even dealings\nwith customs).<\/p>\n<p>Thus, it makes sense to look for these parts at a local\ndistributor, such as Antratek (NB: as of May 2022, GD32VF103\ndevices are out of stock there, as well).<\/p>\n<p>Another good alternative is to look at a global distributor such\nas <a href=\"https:\/\/www.digikey.de\/en\/\">Digi-Key<\/a>. Using them from Europe is\nno problem as they take care of duty and customs. For\nconvenience, direct links of the main parts referenced in this article:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.digikey.de\/en\/products\/detail\/seeed-technology-co-ltd\/114991786\/10060366\">Sipeed USB-JTAG\/TTL RISC-V Debugger<\/a> (8.28 \u20ac)<\/li>\n<li><a href=\"https:\/\/www.digikey.de\/en\/products\/detail\/seeed-technology-co-ltd\/102991574\/15277447\">Longan Nano v1.1<\/a> (7.84 \u20ac)<\/li>\n<\/ul>\n<p>And as of May 2022, they even have some of those still in stock.<\/p>\n<h2 id=\"related-mcus\">Related MCUs<a class=\"headerlink\" href=\"#related-mcus\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>For a microcontroller, the GD32VF103 is relatively powerful. As\nmentioned before, it's similar to an Arm STM32F103. However, a\ntypical STM32F103 Blue Pill board runs at 'just' 72 MHz, whereas\nthe Longan Nano runs at 108 MHz.<\/p>\n<p>For many tasks an 8 bit MCU such as the ATmega328p is sufficient,\ne.g. in the form of an <a href=\"https:\/\/www.sparkfun.com\/products\/11114\">Arduino Mini Pro<\/a> which is clocked at 8\nMHz (when using the 3.3V variant). Clocking an MCU lower saves some\nenergy, but when running at a higher frequency the MCU might\nmanage to finish a task faster such that it is able to go back to\na low-energy sleep state earlier. FWIW, the GD32VF103 can be\nclocked at lower frequencies such as 8 MHz, as well.<\/p>\n<p>Frequency and register size is one thing, but in terms of\nperipherals, in comparison with the ATmega328p, the GD32VF103\n(and STM32F103) has more of everything. For example, more 16 bit\ntimers, more fine grained timer dividers, a backup domain, an\nRTC, more external interrupts, etc.<\/p>\n<hr>\n<p>The GD32VF103 isn't the only RISC-V microcontroller available.\nThere is also the somewhat more powerful <a href=\"https:\/\/sifive.cdn.prismic.io\/sifive\/b56b304f-cd2d-421b-9c14-6b35c33f172e_fe310-g002-manual-v1p4.pdf\">SiFive E310<\/a> (<a href=\"https:\/\/sifive.cdn.prismic.io\/sifive\/4999db8a-432f-45e4-bab2-57007eed0a43_fe310-g002-datasheet-v1p2.pdf\">see also<\/a>)  which is a little bit more expensive, i.e. development boards are in the 30 \u20ac range. In contrast to the GD32VF103 it requires a 16 Mhz (and not 8 MHz) external oscillator.<\/p>\n<p>Interestingly, the 32 bit RISC-V E310 does make use of the whole\n32 bits in its memory-mapped peripheral registers, i.e. there is\nno splitting of fields at 16 bit boundaries as with the\nGD32VF103\/GD32F103\/STM32! The RTC counter is split, but only\nafter 32 bits (i.e. the RTC counter is 48 bit wide). Speaking of\nthe RTC, it's more limited than the previously mentioned ones.\nMost notably, it doesn't allow for any calibration!<\/p>\n<p>Available (as of May 2022) are:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/mwelling\/lofive\/wiki\">LoFive R1<\/a>\n  (<a href=\"https:\/\/www.digikey.de\/en\/products\/detail\/groupgets-llc\/LOFIVE-R1\/10186935\">Digi-Key<\/a>) - created via a crowd-funding camgaign in 2019; the board isn't\n    listed as supported in the PlatformIO sifive platform, i.e.\n    its project wiki describes a custom makefile based SDK; it\n    comes with 128 MBit flash and without an external low-frequency\n    oscillator<\/li>\n<li><a href=\"https:\/\/www.sparkfun.com\/products\/15799\">SparkFun RED-V Thing\n  Plus<\/a>\n  (<a href=\"https:\/\/www.digikey.de\/en\/products\/detail\/sparkfun-electronics\/DEV-15799\/10715591\">Digi-Key<\/a>) - it's supported by PlatformIO; it comes with an additional low-frequency oscillator (32.768 kHz),\n  on board LiPO charger, on-board USB JTAG device (which actually\n  is an Arm Cortex-M4 MCU) and 32 MBit flash<\/li>\n<\/ul>\n<h2 id=\"conclusion\">Conclusion<a class=\"headerlink\" href=\"#conclusion\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The GD32VF103 in the form of the Longan Nano is great for quickly\nhooking up external peripherals for experiments. For example,\nprinting some sensor data on the on-board LCD and\/or\ncollecting them over an UART\/USB link to a host computer.<\/p>\n<p>Per se, the GD32VF103 doesn't really have a unique selling point.\nSimilar things can be archieved with other more popular\nmicrocontrollers. However, a big advantage over - say - Arm\nSTM32F1 is that the GD32CVF103 has a RISC-V instruction set.\nArguably, dealing with RISC-V assembly is a much more pleasant\nexperience than working with - say - ARM assembly. Of course,\ngood C\/C++ compilers are available for many MCU architectures\n(including RISC-V, ARM, AVR), but a hand optimized function\nwritten in assembly may be required once in a while and verifying\ngood code generation by the compiler in the hot path involves\nlooking at an assembly dump.<\/p>\n<p>It's perhaps not the best idea to start with the GD32VF103\nif you are completely new to embedded programming and\nmicrocontrollers. An easier path probably is to start with a\nmuch more popular (and thus better documented) platform such as\nATmega328p\/Arduino or STM32. For example, the ATmega328p manual\nalready is relatively accessible and literally for every\nfeature there are at least 5 or so extensive and well-researched\nblog articles available. Also, with popular platforms there are\nlikely multiple strong communities available that are\napproachable for discussion and help (such as dedidicated\nStackexchange sites, Stackoverflow tags, dedicated forums, etc).<\/p>\n<p><img src=\"https:\/\/gms.tf\/image\/nano-dcf77.jpg\" alt=\"Longan Nano with a DCF77 module attached\" width=\"100%\"\/><\/p>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"riscv"}}]},{"title":"Announcing MANPATH.be","link":{"@attributes":{"href":"https:\/\/gms.tf\/announcing-manpathbe.html","rel":"alternate"}},"published":"2019-07-07T14:00:00+02:00","updated":"2019-07-07T14:00:00+02:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2019-07-07:\/announcing-manpathbe.html","summary":"<p>Recently, I launched <a href=\"https:\/\/manpath.be\/\">MANPATH.be<\/a> - a site for convenient <a href=\"https:\/\/en.wikipedia.org\/wiki\/Man_page\">man\npage<\/a> browsing. It provides access to the man pages of\ndifferent distributions, including <a href=\"https:\/\/en.wikipedia.org\/wiki\/Fedora_(operating_system)\">Fedora<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/CentOS\">CentOS<\/a> and\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/OpenSolaris\">OpenSolaris<\/a>. The <a href=\"https:\/\/manpath.be\/about\">about page<\/a> concisely describes some\nof the sites features, e.g.  human readable links like\n<a href=\"https:\/\/manpath.be\/f30\/3\/memcpy\">f30\/3\/memcpy<\/a>, permalinks and various kinds \u2026<\/p>","content":"<p>Recently, I launched <a href=\"https:\/\/manpath.be\/\">MANPATH.be<\/a> - a site for convenient <a href=\"https:\/\/en.wikipedia.org\/wiki\/Man_page\">man\npage<\/a> browsing. It provides access to the man pages of\ndifferent distributions, including <a href=\"https:\/\/en.wikipedia.org\/wiki\/Fedora_(operating_system)\">Fedora<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/CentOS\">CentOS<\/a> and\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/OpenSolaris\">OpenSolaris<\/a>. The <a href=\"https:\/\/manpath.be\/about\">about page<\/a> concisely describes some\nof the sites features, e.g.  human readable links like\n<a href=\"https:\/\/manpath.be\/f30\/3\/memcpy\">f30\/3\/memcpy<\/a>, permalinks and various kinds of inter-page\nlinks. The following sections give some detail on the motivation\nbehind this project and the technical decisions in its design and\nimplementation.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#motivation\">Motivation<\/a><\/li>\n<li><a href=\"#go\">Go<\/a><\/li>\n<li><a href=\"#python\">Python<\/a><\/li>\n<li><a href=\"#postgresql\">PostgreSQL<\/a><\/li>\n<li><a href=\"#no-javascript\">No JavaScript<\/a><\/li>\n<li><a href=\"#css\">CSS<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"motivation\">Motivation<a class=\"headerlink\" href=\"#motivation\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The motivation behind this project is to solve use cases I'm\ninterested in. Mainly to look up man pages of distributions I\ndon't always have access to, quickly jump between man pages of\ndifferent distributions and versions, conveniently navigate man\npages and create exact and stable references to man pages.<\/p>\n<p>These use cases come up when developing portable software,\nworking on source code in a restricted environment and when\nwriting documents that require man page references like - say -\ntechnical Wikipedia articles, blog posts and Stackoverflow\nquestions and answers.<\/p>\n<h2 id=\"go\">Go<a class=\"headerlink\" href=\"#go\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>One reason behind implementing the HTTP manpage backend daemon in\nthe <a href=\"https:\/\/en.wikipedia.org\/wiki\/Go_(programming_language)\">Go programming language<\/a> is to use this opportunity to\nget more familiar with Go in general and with the <a href=\"https:\/\/golang.org\/pkg\/net\/http\/\">HTTP<\/a>,\n<a href=\"https:\/\/golang.org\/pkg\/html\/template\/\">template<\/a> and <a href=\"https:\/\/golang.org\/pkg\/database\/sql\/\">SQL<\/a> packages (of the Go standard\nlibrary) in particular.<\/p>\n<p>Also, some alternatives aren't necessarily that attractive. For\nexample, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Python_(programming_language)\">Python<\/a> - while being a great choice for many use\ncases - using it for a potentially heavily loaded <a href=\"https:\/\/en.wikipedia.org\/wiki\/Hypertext_Transfer_Protocol\">HTTP<\/a>\nserver is probably not the best fit.<\/p>\n<p>Although there is the fine <a href=\"http:\/\/flask.pocoo.org\/\">Flask package<\/a> for Python that\nprovides a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Microframework\">microframework<\/a> for implementing HTTP daemons, it\narguably contains too much magic which complicates some tasks.\nFlask has <a href=\"http:\/\/flask.pocoo.org\/docs\/1.0\/deploying\/\">several deployment options<\/a>, some which involve\nmiddleware components which may lead to <a href=\"https:\/\/en.wikipedia.org\/wiki\/No_Silver_Bullet\">accidental\ncomplexity<\/a>.<\/p>\n<p>On the other hand, Go has some nice features that are well suited\nfor such a daemon. For example, Go routines (and channels) allow\nfor concurrent programming beyond relatively low-level <a href=\"https:\/\/eli.thegreenplace.net\/2018\/go-hits-the-concurrency-nail-right-on-the-head\/\">threading\nand asynchronous constructs<\/a> which are also hard to combine.\nIt also helps for performance that Go slices are lightweight\nreferences (instead of objects that create copies like immutable\nPython slices), for example when using them inside a templating\npackage.  Another win for performance is that, although Go uses\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Garbage_collection_(computer_science)\">garbage collection<\/a>, Go programs are compiled into machine code,\nwhereas Python uses a simple <a href=\"https:\/\/opensource.com\/article\/18\/4\/introduction-python-bytecode\">virtual machine<\/a>.<\/p>\n<p>However, Go isn't a perfect language. Some languages corners are\narguably too low-level like - say - error handling. And there\nisn't really a culture of <a href=\"https:\/\/semver.org\/\">semantic versioning<\/a>.<\/p>\n<p>As expected, the fact that Go doesn't provide exceptions leads to\nmost functions being interspersed with some verbose and redundant\nerror handling code. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Rust_(programming_language)\">Rust<\/a>, a language of similar age, also\ndoesn't have exceptions but at least <a href=\"https:\/\/doc.rust-lang.org\/stable\/book\/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator\">provides some means<\/a> to\neliminate redundant error checking code.<\/p>\n<p>Go also doesn't have C++ style <a href=\"https:\/\/en.wikipedia.org\/wiki\/Resource_acquisition_is_initialization\">RAII<\/a> which really simplifies\ncreating resource leaks. In contrast, although Python implements\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Reference_counting\">reference counting<\/a> it also has <a href=\"https:\/\/docs.python.org\/3.6\/reference\/compound_stmts.html#with\">with-statement context\nmanagers<\/a> which are arguably more convenient to use than the\n<a href=\"https:\/\/gobyexample.com\/defer\">Go defer mechanism<\/a>.<\/p>\n<p>In conclusion, for this concrete use case, the advantages of Go\noutweigh its disadvantages and its standard library already\ncovers many needs.<\/p>\n<h2 id=\"python\">Python<a class=\"headerlink\" href=\"#python\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The man page loader that pre-renders man pages for the\nbackend and is able to bulk import man pages directly from a\npackage repository is written in <a href=\"https:\/\/en.wikipedia.org\/wiki\/Python_(programming_language)\">Python<\/a>.<\/p>\n<p>The execution speed of Python is good enough for this task,\nespecially since the imports are batch jobs that happen at a low\nfrequency.<\/p>\n<p>On the other hand, the high-level Python allows to get the job in\nless code, e.g. when mangling strings and calling external\nprocesses. Also, the excellent <a href=\"https:\/\/docs.sqlalchemy.org\/en\/13\/core\/tutorial.html\">SQLAlchemy Core<\/a> package with\nits expressions leads to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Boilerplate_code\">boilerplate-free<\/a>, portable and\ncompact code and thus simplifies interacting with relational\ndatabases, a lot.<\/p>\n<h2 id=\"postgresql\">PostgreSQL<a class=\"headerlink\" href=\"#postgresql\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p><a href=\"https:\/\/www.postgresql.org\/\">PostgreSQL (or Postgres)<\/a> is a well performing and stable\nopen-source relational database with many useful features. Its\nsupport for relatively recent additions to the <a href=\"https:\/\/modern-sql.com\/\">SQL standard is\ngenerally good and better<\/a> than what MySQL\/MariaDB or even\nOracle offers.<\/p>\n<p>For example, Postgres supports transactions for DDL statements\nwhereas Oracle just implicitly commits the current transaction on\nthe next <a href=\"https:\/\/en.wikipedia.org\/wiki\/Data_definition_language\">DDL<\/a> statement. A string of length zero is just\nthat string and not implicitly converted to NULL, like Oracle\ndoes it. In contrast to Oracle, it also supports a boolean\ndata-type. Postgres' <a href=\"https:\/\/www.postgresql.org\/docs\/11\/sql-insert.html#SQL-ON-CONFLICT\">upsert support<\/a> is <a href=\"https:\/\/speakerdeck.com\/peterg\/upsert-use-cases\">more\nversatile<\/a>, robust and useful than what Oracle and MySQL\noffer.  In contrast to MySQL, Postgres' support of aggregate\nfunctions <a href=\"https:\/\/modern-sql.com\/\">is superior<\/a>.<\/p>\n<p>Postgres also offers many useful string, regex and array functions\nlike <code>regexp_replace<\/code>, <code>regexp_split_to_array<\/code> and <code>array_remove<\/code>.<\/p>\n<p>In addition to that, Oracle isn't Open-Source, comes with\nhorrible licensing conditions and is a pain in the neck to\ninstall and maintain.<\/p>\n<p>Postgres is included in the package repositories of most Linux\ndistributions. And the Postgres project even <a href=\"https:\/\/www.postgresql.org\/download\/linux\/redhat\/\">maintains own\nrepositories<\/a> for several distributions. For example, using the\nupstream Postgres repository it's very simple to get the\ncurrently stable Postgres 11 on CentOS 7 (which provides version\n9 in its base repository).<\/p>\n<h2 id=\"no-javascript\">No JavaScript<a class=\"headerlink\" href=\"#no-javascript\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Since <a href=\"https:\/\/en.wikipedia.org\/wiki\/JavaScript\">JavaScript<\/a> is a <a href=\"https:\/\/web.archive.org\/web\/20151010041903\/http:\/\/bdcampbell.net:80\/javascript\/book\/javascript_the_good_parts.pdf\">badly designed language<\/a>, it's\nnatural to strive for using it as little as possible. Also, with the\ncurrent (and even not so current) state of web standards,\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Cascading_Style_Sheets\">CSS<\/a> is often sufficient even for realizing <a href=\"https:\/\/en.wikipedia.org\/wiki\/Responsive_web_design\">adaptive\n('responsive') layouts<\/a>.<\/p>\n<p>Some established frontend frameworks (e.g. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Bootstrap_(front-end_framework)\">Bootstrap<\/a>) come with\ngood design defaults and pre-defined components but also with\nmetric tons of JavaScript. Also for supporting legacy browsers.<\/p>\n<p>Besides being a security risk to include tons of third-party\nobfuscated ('minified') JavaScript (that also increase page load\ntimes) I'm simply not interested in supporting very old legacy\nbrowsers.<\/p>\n<p>Thus, the <a href=\"https:\/\/manpath.be\/\">MANPATH.be<\/a> site is completely JavaScript free.<\/p>\n<h2 id=\"css\">CSS<a class=\"headerlink\" href=\"#css\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>It turns out that for the purpose of presenting man pages in a\nweb-browser (including a responsive layout) actually very little\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Cascading_Style_Sheets\">CSS<\/a> is required (75 lines or so).<\/p>\n<p>Looking into CSS for this project, I've learned some lessons. For\nexample, that the traditional simple three-column layout\n(navigation\/main\/other) has a name and is called <a href=\"https:\/\/en.wikipedia.org\/wiki\/Holy_grail_(web_design)#Current_solutions\">The Holy Grail\nLayout<\/a>.<\/p>\n<p>Another one is that CSS not just has one mechanism but two for\nrealizing adaptive <a href=\"https:\/\/en.wikipedia.org\/wiki\/Responsive_web_design\">responsive<\/a> layouts: The <a href=\"https:\/\/en.wikipedia.org\/wiki\/CSS_grid_layout\">CSS Grid Layout\nModule<\/a> and the <a href=\"https:\/\/en.wikipedia.org\/wiki\/CSS_Flexible_Box_Layout\">CSS Flexible Box Layout Module<\/a> - also\nknown as <a href=\"https:\/\/en.wikipedia.org\/wiki\/CSS_grid_layout\">CSS Grid<\/a> and <a href=\"https:\/\/en.wikipedia.org\/wiki\/CSS_Flexible_Box_Layout\">Flexbox<\/a>. Searching the web for CSS Grid\nis sometimes complicated by the fact that also legacy CSS solutions\nare included that implement a grid without using the CSS Grid\nLayout Module. Also, a search may turn up framework specific\nsolutions that might or might not use CSS Grid, internally.<\/p>\n<p>The main difference between CSS Grid and Flexbox is often\nstated as Grid being developed for flowing elements in (up to)\ntwo dimensions while Flexbox being developed for flowing element\nalong one axis (i.e. in a row\/column). Thus, CSS Grid sounds like\nthe natural solution to the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Holy_grail_(web_design)#Current_solutions\">Holy Grail Layout<\/a> problem.<\/p>\n<p>But this isn't really accurate as it's possible to construct a\nFlexbox column layout that downgrades into rows if the screen\nis too small. And that without even using <a href=\"https:\/\/en.wikipedia.org\/wiki\/Media_queries\">CSS media queries<\/a>. In\nthat sense Flexbox is well suited for two dimensional layouts,\ntoo.<\/p>\n<p>It seems that the same responsive layout can't be implemented\nwith CSS Grid alone. Instead one has to work with CSS media\nqueries to switch between different grids or even switch between\na Grid layout and Flexbox one. At least I didn't come up with a\nGrid-only solution and all the CSS Grid examples I found online\nused CSS media queries.<\/p>\n<p>I thus implemented the responsive three column layout for\n<a href=\"https:\/\/manpath.be\/\">MANPATH.be<\/a> using <a href=\"https:\/\/en.wikipedia.org\/wiki\/CSS_Flexible_Box_Layout\">CSS Flexbox<\/a> constructs. The complexity of\nFlexbox looks well balanced and being able to avoid <a href=\"https:\/\/en.wikipedia.org\/wiki\/Media_queries\">CSS media\nqueries<\/a> reduces the complexity of the overall solution.<\/p>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"go"}},{"@attributes":{"term":"http"}},{"@attributes":{"term":"manpage"}}]},{"title":"Touch Typing","link":{"@attributes":{"href":"https:\/\/gms.tf\/touch-typing.html","rel":"alternate"}},"published":"2018-12-15T18:00:00+01:00","updated":"2018-12-15T18:00:00+01:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2018-12-15:\/touch-typing.html","summary":"<p>Getting along without <a href=\"https:\/\/en.wikipedia.org\/wiki\/Touch_typing\">touch typing<\/a> is possible. But\nlearning touch typing is usually a big productivity boost,\nreduces stress and may prevent typical keyboard induced <a href=\"https:\/\/en.wikipedia.org\/wiki\/Repetitive_strain_injury\">health\nrisks<\/a>. This article describes some learning approaches\nand optimization opportunities.<\/p>\n<h2 id=\"getting-started\">Getting Started<a class=\"headerlink\" href=\"#getting-started\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The first thing to learn is to place the fingers in a \u2026<\/p>","content":"<p>Getting along without <a href=\"https:\/\/en.wikipedia.org\/wiki\/Touch_typing\">touch typing<\/a> is possible. But\nlearning touch typing is usually a big productivity boost,\nreduces stress and may prevent typical keyboard induced <a href=\"https:\/\/en.wikipedia.org\/wiki\/Repetitive_strain_injury\">health\nrisks<\/a>. This article describes some learning approaches\nand optimization opportunities.<\/p>\n<h2 id=\"getting-started\">Getting Started<a class=\"headerlink\" href=\"#getting-started\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The first thing to learn is to place the fingers in a basic\nposition on the keyboard, i.e. thumbs over the space bar and the\nother fingers on the main keys of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Touch_typing#Home_row\">home row<\/a> (i.e.\n<code>A, S, D, F<\/code> and <code>J, K, L, ;<\/code> keys with most US-like keyboards).<\/p>\n<p>There are free and open source training programs for learning the\nset of keys each finger is <a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:FingerHandPosUSA.gif\">supposed to hit<\/a>. A good one is\n<a href=\"https:\/\/www.kde.org\/applications\/education\/ktouch\/\">KTouch<\/a>. Old versions even used to include lessons from\nthe commercial <em>Duden Tipptrainer<\/em>. (This should be legal,\nbecause touch typing patterns likely don't reach the threshold of\noriginality. Ask a lawyer if in doubt.)<\/p>\n<p>The purpose of a training program is to properly build up some\ninitial <a href=\"https:\/\/en.wikipedia.org\/wiki\/Muscle_memory\">muscle memory<\/a>. After that, practice in the normal\nenvironment (e.g. coding, email writing, etc.) automatically\nimproves the muscle memory.<\/p>\n<p>Besides reading the Wikipedia article on <a href=\"https:\/\/en.wikipedia.org\/wiki\/Touch_typing\">touch typing<\/a> that\ncontains some helpful descriptions and figures, there are even\nshort books\/booklets on that topic. Nowadays probably hard to get\nin book stores, but you may find them as an accessory of an\nold mechanical typewriter (cf. your grandparents' attic or a\ngarage sale). They can be surprisingly entertaining, see for\nexample the <a href=\"https:\/\/gms.tf\/file\/erika-maschinenschreiben.pdf\">short manual by the Naumann Erika mechanical\ntypewriter<\/a> company (published 1930 or so, in German).<\/p>\n<h2 id=\"advanced-training\">Advanced Training<a class=\"headerlink\" href=\"#advanced-training\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>When touch typing upper case characters, it makes sense to hit\nthe shift modifier key with the other hand to avoid weird\ngestures, which thus increases speed and reduces strain. This can\nbe trained with tutor software that ignores shift key events for\nkeys from the 'wrong' keyboard half.<\/p>\n<p>Even more effective would be a modified keyboard driver that\nimplements this behaviour, all the time.<\/p>\n<p>I'm not aware if anyone has implemented something like the above.<\/p>\n<p>Touch typing is all about <a href=\"https:\/\/commons.wikimedia.org\/wiki\/File:Unterricht_im_Blindschreiben.jpg\">blindly typing<\/a>. If you have problems\nwith that you can remove all the key labeling with a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Die_grinder\">rotary\ntool<\/a> or something like that. There are even keyboard\nmodels without any labels.<\/p>\n<h2 id=\"optimal-layout\">Optimal Layout<a class=\"headerlink\" href=\"#optimal-layout\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>When you are <a href=\"https:\/\/en.wikipedia.org\/wiki\/Touch_typing\">touch typing<\/a> you should not\/don't have to look\nat the keyboard. Thus, you are free to choose any <a href=\"https:\/\/en.wikipedia.org\/wiki\/Keyboard_layout\">keyboard\nlayout<\/a> you want. It doesn't matter what is printed on\nthe keys.<\/p>\n<p>One main design goal of the standard layouts was to prevent jams\nin mechanical type writers. Computer keyboards of course don't\nhave to care about this, thus, modern layouts more optimized for\nfaster typing (and physiology) are an alternative. For example\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Dvorak_Simplified_Keyboard\">Dvorak<\/a> or <a href=\"https:\/\/en.wikipedia.org\/wiki\/Keyboard_layout#Neo\">Neo<\/a>.<\/p>\n<p>Obviously, one big disadvantage with those alternative layouts\nis how radically different they are. Imagine building up a lot of\nmuscle memory and then having to type an email in an internet\ncafe. This is nothing compared to having to type on a German\nlayout if you are used to - say - US layout.<\/p>\n<p>Perhaps Dvorak\/Neo only make a significant difference (if any)\nwhen you really have to type a lot of prose text very\nfast. Like transcribing tapes or something like that.<\/p>\n<p>For coding, the <a href=\"https:\/\/en.wikipedia.org\/wiki\/QWERTY\">US Layout (QWERTY)<\/a> is the number one\nchoice. The syntax of many programming languages is optimized for\nthe US layout. That means special characters you need a lot like\n<code>[]{};\/\\:<\/code> are easy to reach with the US layout.<\/p>\n<p>Also, if you use a real editor like <a href=\"https:\/\/en.wikipedia.org\/wiki\/Vim_(text_editor)\">Vim<\/a>, all the command\nsequences and shortcuts are optimized for the US layout. With Vim\nand a US layout you don't even have to care if your keyboard has\nan escape key or not. You just hit <code>Ctrl+[<\/code> which is conveniently\nto type on that layout. No need to remap Caps Lock to Escape.<\/p>\n<h2 id=\"compose-key\">Compose Key<a class=\"headerlink\" href=\"#compose-key\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>For non-US characters like <a href=\"https:\/\/en.wikipedia.org\/wiki\/Germanic_umlaut\">Umlauts<\/a>, the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Compose_key\">Compose\nmechanism<\/a> (natively available on Linux\/UNIX) is a\nconvenient solution when using the US layout. For example, to get\n<code>\u00fc<\/code> one just types <code>Compose<\/code> then <code>Shift+\"<\/code> followed by <code>U<\/code>. Most\nkeyboards don't come with a dedicated Compose key but Linux\/UNIX\nallows to configure another key as compose - e.g. the right-alt\nkey.  One-liner for Gnome-Shell:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>$<span class=\"w\"> <\/span>gsettings<span class=\"w\"> <\/span><span class=\"nb\">set<\/span><span class=\"w\"> <\/span>org.gnome.desktop.input-sources<span class=\"w\"> <\/span>xkb-options<span class=\"w\"> <\/span><span class=\"s2\">&quot;[&#39;compose:ralt&#39;]&quot;<\/span>\n<\/code><\/pre><\/div>\n\n<p>(alternatively, you can use the <code>gnome-tweaks<\/code> GUI)<\/p>\n<p>There are even tools that implement Compose behaviour on\n<a href=\"http:\/\/superuser.com\/questions\/64178\/compose-key-on-windows\">Windows<\/a>. The opensource <a href=\"https:\/\/github.com\/samhocevar\/wincompose\">WinCompose<\/a>\nworks great.<\/p>\n<h2 id=\"ergonomic-keyboards\">Ergonomic Keyboards<a class=\"headerlink\" href=\"#ergonomic-keyboards\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The common problem with standard keyboards is that one is forced\nto turn the wrist joints and\/or shoulder joints into a non-optimal\npossibly injury inducing position.<\/p>\n<p>The solution: a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ergonomic_keyboard\">keyboard that is divided<\/a> into two halves\nthat are placed with some space and an angle to each other such\nthat the wrist\/shoulder joints can be in a natural position.<\/p>\n<p>One example of this design is the <a href=\"https:\/\/www.kinesis-ergo.com\/shop\/freestyle2-for-pc-us\/\">Kinesis Freestyle 2<\/a>\nwhich has the big advantage that one is very free to adjust the\nangle and the space between the 2 halves. Also, it isn't too\nexpensive. Nowadays, they also have a similar model with\nmechanical switches (Freestyle Pro), although the switches don't\nnecessarily make much of a difference, ergonomically wise.<\/p>\n<p>Note that some ergonomic keyboards (and even non-ergonomic ones)\ncome with a wrist rest. This feature is easy to misuse, i.e. it\nis intended to be used only when taking a break from typing.\nAlways putting the wrist onto the rest during typing may induce\ninjuries.<\/p>\n<h2 id=\"concluding-remarks\">Concluding Remarks<a class=\"headerlink\" href=\"#concluding-remarks\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>If you don't already touch type, consider learning it because of\nthe advantages. I can't recommend it enough. If you want to learn\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/Touch_typing\">touch typing<\/a>, consider learning it with the right layout\nfrom the start. It' the same effort. If you program I strongly\nrecommend to use the US layout.<\/p>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"keyboard"}},{"@attributes":{"term":"typing"}}]},{"title":"Work-related Movies","link":{"@attributes":{"href":"https:\/\/gms.tf\/work-related-movies.html","rel":"alternate"}},"published":"2018-04-18T21:10:00+02:00","updated":"2018-04-18T21:10:00+02:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2018-04-18:\/work-related-movies.html","summary":"<p>This article features a watch list of work related\nmovies. A list for getting some peace of mind in the evening\nafter working overtime.<\/p>\n<p>Each movie has some relation to a pathological work related\nsetting. Not always on the surface, but more on a deeper\nlayer of interpretation.<\/p>\n<p>Some of \u2026<\/p>","content":"<p>This article features a watch list of work related\nmovies. A list for getting some peace of mind in the evening\nafter working overtime.<\/p>\n<p>Each movie has some relation to a pathological work related\nsetting. Not always on the surface, but more on a deeper\nlayer of interpretation.<\/p>\n<p>Some of the movies are about war, perhaps this isn't by chance,\nbecause sometimes work is war.<\/p>\n<ul>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/A_Bridge_Too_Far_(film)\">A Bridge too far (1977)<\/a>- Watching this movie one might quickly\n  shout: 'Just one bridge too far?!? What about two or three bridges too\n  far!' So to say a symbol for a big project that isn't\n  completely ill-conceived but it's just too big and too\n  ambitious for doing a successful big-bang release. Like a\n  <a href=\"https:\/\/en.wikipedia.org\/wiki\/Second-system_effect\">second-system<\/a> project <a href=\"https:\/\/en.wikipedia.org\/wiki\/Waterfall_model\">waterfall-planned<\/a> by an\n  army of wannabe software architects and project managers.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Apocalypse_Now\">Apocalypse Now (1979)<\/a> - <em>Apocalypse Now<\/em> is directed by Francis Ford\n  Coppola who adapts Joseph Conrad's <a href=\"https:\/\/en.wikipedia.org\/wiki\/Heart_of_Darkness\">Heart of Darkness<\/a> into the\n  Vietnam War setting. Special forces army Captain Willard, who\n  has trouble establishing some work-life balance, gets the\n  top-secret mission to terminate the vigilante Colonel Kurtz. Of course,\n  the mission requires to rally through the Vietnam war theater\n  to a remote part of Cambodia. Kurtz\n  is a curious figure who was top of every class, but at some\n  point of time\n  deviated from the expected way, starting with going through a\n  special forces training in late years.<\/li>\n<li><a href=\"http:\/\/knowyourmeme.com\/memes\/rage-guy-fffffuuuuuuuu\">Bad Lieutenant (2009)<\/a> - Directed by Werner Herzog (!), it\n  features several work-related\n  <a href=\"http:\/\/knowyourmeme.com\/memes\/everything-went-better-than-expected\">went-better-than-expected<\/a> and <a href=\"http:\/\/knowyourmeme.com\/memes\/rage-guy-fffffuuuuuuuu\">rage-guy<\/a> moments. The\n  lieutenant (Nicolas Cage!) is highly dedicated to his work and perhaps\n  he is too idealistic, when he goes above-and-beyond to be the\n  good cop who rescues a prisoner during an evacuation. Of\n  course this doesn't play out well for him, and thus he's forced\n  to be more creative and apply unconventional methods in his work.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Downfall_(2004_film)\">Der Untergang (2004)<\/a> - The movie <em>Der Untergang<\/em> is about the last\n  days in the <a href=\"https:\/\/en.wikipedia.org\/wiki\/F%C3%BChrerbunker\">F\u00fchrerbunker<\/a>. There are some surreal scenes where\n  the leadership, i.e. the Wehrmacht generals and Hitler keep on\n  having meetings about some grand strategies. Not to relativize\n  anything, but the levels of cognitive dissonance aren't\n  necessary that different to a - say - upper management call about a\n  utmost important but ultimately doomed IT project.\n  Last but not least, the movie is the source of the <a href=\"http:\/\/knowyourmeme.com\/memes\/downfall-hitler-reacts\">Hitler-Reacts meme<\/a>.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Falling_Down\">Falling Down (1993)<\/a> - William Foster (played by Michael\n  Douglas) has a breakdown when he is stuck in rush hour traffic.\n  Instead of simply burning out, he is done keeping\n  calm and the situation escalates. In a sort of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Michael_Kohlhaas\">Michael Kohlhass<\/a>\n  style. The source of some of his anger is that he was recently\n  reduced-in-force from his defense contractor engineering job.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Fight_Club\">Fightclub (1999)<\/a> - the protagonist (played by Edward\n  Norton) works a dead-end and at the same time ethical\n  questionable job where he has to apply shareholder-value logic\n  to the aftermath of fatal car crashes. Thus, he develops\n  insomnia and feels more and more depersonalized. As a way out\n  he discovers self-help group tourism. All this escalates\n  quickly in unexpected ways.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Full_Metal_Jacket\">Full Metal Jacket (1987)<\/a> - Stanley Kubrick's <em>Full Metal Jacket<\/em>\n  starts with a traditional bootcamp training with the ultra\n  authoritarian drill instructor Sergeant Hartman. Later on, a\n  military journalist who is in a relatively safe location\n  develops the meme of wanting to get into 'the shit', i.e. to get\n  some war experience basically out of boredom.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/In_the_Loop\">In the Loop (2009)<\/a> - This political satire is about how a bunch of\n  assistants, communications and PR experts try to manage their\n  incompetent bosses and deal with their inconsiderate statements. Of course,\n  each meeting is extremely chaotic and the minister is\n  absolutely replaceable. All this can be read as a parable on\n  clueless C-level\/upper-management types who are easily confused with the\n  latest hype such as 'Blockchain', 'IOT', 'AI' and 'Datalakes', and who are\n  thus happy to rashly engage with shady consultants to come up\n  with the ultimate strategy that really will pivot the company to mega-growth,\n  big-time. The people who do the real work then have to work\n  around that mess, limit the damage and try to catch some of the\n  most problematic stray messages.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Killing_Them_Softly\">Killing them Softly (2012)<\/a> - A free-lancer (Brad Pitt) in a 2008\n  USA bad economy setting has to deal with some mob issues. He well\n  knows how to proceed, but the mob isn't how it used to be. The\n  overhead has increased and like in a mediocre corporation,\n  there are some useless processes to follow. To make things even\n  more unpleasant, a colleague who used to be good at his work\n  now is a liability and the employer makes a fuzz about the\n  payment. This neo-noir movie features shots of depressing\n  city development and parts of election speeches are cut in. It\n  ends with this epic line: 'This guy wants to tell me we're living in\n  a community? Don't make me laugh. I'm living in America, and in\n  America, you're on your own. America is not a country; it's\n  just a business. Now fucking pay me.'<\/li>\n<li><a href=\"https:\/\/www.reddit.com\/r\/funny\/comments\/3bwb16\/someone_didnt_get_the_memo\/\">Office Space (1999)<\/a> - <em>Office space<\/em> is a seemingly light 90ies comedy that\n  involves some cubicles. It even features Jennifer Aniston. It\n  turns out that it is a <a href=\"https:\/\/www.youtube.com\/watch?v=m4OvQIGDg4I\">treasure trove of internet memes<\/a>.\n  Perhaps the most popular is the <a href=\"https:\/\/www.reddit.com\/r\/funny\/comments\/3bwb16\/someone_didnt_get_the_memo\/\">Didn't you get the memo?<\/a>\n  one.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/The_Thin_Red_Line_(1998_film)\">The Thin Red Line (1998)<\/a> - As a war movie, The Thin Red\n  Line has everything: an ambitious colonel who\n  wants to over-deliver on taking an pacific island from the Japanese\n  as fast as possible since he sees it as last chance\n  after feeling been left out again and again for a long time. An overly cautious\n  captain who's actions effectively make things worse. An almost\n  gay hero who cares about his peers more than himself, and\n  an endless stream character development. Basically, it's 170\n  minutes of epic.  Directed by Terrence Malick, it's one of his\n  better movies and features and impressive cast, as well.<\/li>\n<li><a href=\"https:\/\/en.wikipedia.org\/wiki\/Tinker_Tailor_Soldier_Spy_(film)\">Tinker Tailor Soldier Spy (2011)<\/a> - On the surface a cold war\n  espionage movie (directed by Alfredson <a href=\"https:\/\/en.wikipedia.org\/wiki\/The_Snowman_(2017_film)\">before he lost his mojo<\/a>) it's\n  really about colleagueship and paranoia at the workplace. Like\n  for example when\n  reminiscing about a past office party where everybody seemed to\n  get along, while in the meantime most conversations are filled\n  with distrust.<\/li>\n<li><a href=\"https:\/\/de.wikipedia.org\/wiki\/Work_Hard_\u2013_Play_Hard\">Work Hard - Play Hard (2011)<\/a> - This documentary features\n  some epic shots of modern office settings while interviewing\n  assessment center job candidates, architects and managers that\n  follow the latest trends (e.g. 'lean'). There is no narrator, the\n  interviews stand for themself. Often, they are self-revealing\n  the absurdness and are unwittingly funny. The atmosphere ranges\n  from an unclear creepiness (think: <a href=\"https:\/\/en.wikipedia.org\/wiki\/The_Invitation_(2015_film)\">The Invitation<\/a>\n  build-up) that has elements of a\n  <a href=\"https:\/\/en.wikipedia.org\/wiki\/Brave_New_World\">brave-new-world<\/a> environment mixed with some <a href=\"https:\/\/en.wikipedia.org\/wiki\/Newspeak\">newspeak<\/a> to\n  general alienation.<\/li>\n<\/ul>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"movies"}},{"@attributes":{"term":"work"}}]},{"title":"Updating Samsung SSD Firmware in the 21st Century","link":{"@attributes":{"href":"https:\/\/gms.tf\/updating-samsung-ssd-firmware-in-the-21st-century.html","rel":"alternate"}},"published":"2017-02-17T21:10:00+01:00","updated":"2017-02-17T21:10:00+01:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2017-02-17:\/updating-samsung-ssd-firmware-in-the-21st-century.html","summary":"<p>The firmware updates for the Samsung 840 EVO SSDs come as ISO\nimages which isn't very helpful if your system doesn't have a\nCDROM drive. It turns out that the ISO images just contain DOS\nbinaries, though. This article describes how to use a FreeDOS USB\nstick for flashing, instead \u2026<\/p>","content":"<p>The firmware updates for the Samsung 840 EVO SSDs come as ISO\nimages which isn't very helpful if your system doesn't have a\nCDROM drive. It turns out that the ISO images just contain DOS\nbinaries, though. This article describes how to use a FreeDOS USB\nstick for flashing, instead.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#background\">Background<\/a><\/li>\n<li><a href=\"#before-we-start\">Before we start<\/a><\/li>\n<li><a href=\"#firmware\">Firmware<\/a><\/li>\n<li><a href=\"#create-usb-stick\">Create USB Stick<\/a><\/li>\n<li><a href=\"#booting\">Booting<\/a><\/li>\n<li><a href=\"#flashing\">Flashing<\/a><\/li>\n<li><a href=\"#see-also\">See also<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"background\">Background<a class=\"headerlink\" href=\"#background\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The Samsung SSD 840 EVO drives, <a href=\"https:\/\/bugs.launchpad.net\/ubuntu\/+source\/fstrim\/+bug\/1449005\/comments\/52\">like other Samsung SSDs<\/a>, are\n<a href=\"http:\/\/www.anandtech.com\/show\/8997\/samsung-releases-statement-on-840-evo-performance-another-fix-is-in-the-works\">infamous for their broken design and firmware bugs<\/a>. The most\nnotable symptom being the one where read speed deteriorates with\nthe age of the written data. Samsung attempted to fix this\nvia firmware updates two times, i.e. they pushed several firmware\nupdates that try to work around limitations in the used SSD\ncomponents.<\/p>\n<h2 id=\"before-we-start\">Before we start<a class=\"headerlink\" href=\"#before-we-start\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>As of 2017-01 the most <a href=\"https:\/\/www.samsung.com\/semiconductor\/minisite\/ssd\/download\/tools.html\">current Samsung 840 EVO versions<\/a> are:<\/p>\n<table class=\"m-table\">\n<thead>\n<tr>\n<th>Model<\/th>\n<th>Firmware Version<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>840 EVO 2.5\"<\/td>\n<td>EXT0DB6Q<\/td>\n<\/tr>\n<tr>\n<td>840 EVO mSATA<\/td>\n<td>EXT43B6Q<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Apparently those files were released mid 2015.<\/p>\n<p>Under Linux, the version of the currently flashed firmware can be\ndisplayed with <code>hdparm<\/code>, e.g.:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>$<span class=\"w\"> <\/span><span class=\"k\">for<\/span><span class=\"w\"> <\/span>i<span class=\"w\"> <\/span><span class=\"k\">in<\/span><span class=\"w\"> <\/span>a<span class=\"w\"> <\/span>b<span class=\"w\"> <\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"k\">do<\/span><span class=\"w\"> <\/span>hdparm<span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>\/dev\/sd<span class=\"nv\">$i<\/span><span class=\"w\">  <\/span><span class=\"p\">|<\/span><span class=\"w\"> <\/span>grep<span class=\"w\"> <\/span>Model<span class=\"w\"> <\/span><span class=\"p\">;<\/span><span class=\"w\"> <\/span><span class=\"k\">done<\/span>\n<span class=\"nv\">Model<\/span><span class=\"o\">=<\/span>Samsung<span class=\"w\"> <\/span>SSD<span class=\"w\"> <\/span><span class=\"m\">840<\/span><span class=\"w\"> <\/span>EVO<span class=\"w\"> <\/span>250GB,<span class=\"w\"> <\/span><span class=\"nv\">FwRev<\/span><span class=\"o\">=<\/span>EXT0CB6Q,<span class=\"w\"> <\/span><span class=\"nv\">SerialNo<\/span><span class=\"o\">=<\/span>S1ABC1234567890\n<span class=\"nv\">Model<\/span><span class=\"o\">=<\/span>Samsung<span class=\"w\"> <\/span>SSD<span class=\"w\"> <\/span><span class=\"m\">840<\/span><span class=\"w\"> <\/span>EVO<span class=\"w\"> <\/span>250GB<span class=\"w\"> <\/span>mSATA,<span class=\"w\"> <\/span><span class=\"nv\">FwRev<\/span><span class=\"o\">=<\/span>EXT42B6Q,<span class=\"w\"> <\/span><span class=\"nv\">SerialNo<\/span><span class=\"o\">=<\/span>S1ABC1234567890\n<\/code><\/pre><\/div>\n\n<h2 id=\"firmware\">Firmware<a class=\"headerlink\" href=\"#firmware\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Since Samsung can't be bothered to publish some checksums for the\nfirmware updates in 2017, here are some SHA-256 ones:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>b11658abe3194c933db22bd6734f932e3275679a247f3566ee70518e24acff2e  Samsung_SSD_840_EVO_EXT0DB6Q.iso\n0bc484643f54880a141c680bace16333040600e720908608da548aaf2bd9a656  Samsung_SSD_840_EVO_mSATA_EXT43B6Q_Win_Mac.iso\n<\/code><\/pre><\/div>\n\n<p>At least, the files are also <a href=\"https:\/\/www.samsung.com\/semiconductor\/minisite\/ssd\/download\/tools.html\">available via https<\/a>.<\/p>\n<p>Samsung continues to shine via failing to provide any changelog\nor instructions with the firmware download.<\/p>\n<h2 id=\"create-usb-stick\">Create USB Stick<a class=\"headerlink\" href=\"#create-usb-stick\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>A natural method to inspect an ISO image under Linux is to\nloopback-mount it. But for our use case it is perhaps even\nsimpler to just extract each image with <a href=\"http:\/\/p7zip.sourceforge.net\/\"><code>7z<\/code> (if available)<\/a>:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>$<span class=\"w\"> <\/span>mkdir<span class=\"w\"> <\/span>ext0db6q<span class=\"w\"> <\/span>ext43b6q\n$<span class=\"w\"> <\/span><span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>ext0d6q\n$<span class=\"w\"> <\/span>7z<span class=\"w\"> <\/span>l<span class=\"w\"> <\/span>..\/Samsung_SSD_840_EVO_EXT0DB6Q.iso\n$<span class=\"w\"> <\/span>7z<span class=\"w\"> <\/span>x<span class=\"w\"> <\/span>..\/Samsung_SSD_840_EVO_EXT0DB6Q.iso\n$<span class=\"w\"> <\/span><span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>..\/ext43b6q\n$<span class=\"w\"> <\/span>7z<span class=\"w\"> <\/span>x<span class=\"w\"> <\/span>..\/Samsung_SSD_840_EVO_mSATA_EXT43B6Q_Win_Mac.iso\n<\/code><\/pre><\/div>\n\n<p>Both extracts look similar, they show how the <a href=\"http:\/\/www.syslinux.org\/wiki\/index.php?title=ISOLINUX\">ISOLINUX boot\nloader<\/a> is used. The interesting bit is the <code>ISOLINUX\/BTDSK.IMG<\/code>\nfile with contains the actual payload - i.e. a FAT image.<\/p>\n<p>Again, this image can be loopback-mounted but using the <a href=\"https:\/\/www.gnu.org\/software\/mtools\/\">Mtools<\/a>\nuser space utilites is sufficient, e.g.:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>$<span class=\"w\"> <\/span><span class=\"nb\">cd<\/span><span class=\"w\"> <\/span>ext0d6q\/ISOLINUX\n$<span class=\"w\"> <\/span>mdir<span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>BTDSK.IMG\n$<span class=\"w\"> <\/span>mkdir<span class=\"w\"> <\/span>t\n$<span class=\"w\"> <\/span>mcopy<span class=\"w\"> <\/span>-s<span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>BTDSK.IMG<span class=\"w\"> <\/span>::autoexec.bat<span class=\"w\"> <\/span>t\n$<span class=\"w\"> <\/span>mcopy<span class=\"w\"> <\/span>-s<span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>BTDSK.IMG<span class=\"w\"> <\/span>::license.txt<span class=\"w\"> <\/span>t\n$<span class=\"w\"> <\/span>mcopy<span class=\"w\"> <\/span>-s<span class=\"w\"> <\/span>-i<span class=\"w\"> <\/span>BTDSK.IMG<span class=\"w\"> <\/span>::samsung<span class=\"w\"> <\/span>t\n<\/code><\/pre><\/div>\n\n<p><a href=\"https:\/\/gms.tf\/updating-the-bios-on-a-dell-latitude-e7270.html#dos-boot\">Creating a FreeDOS USB stick can be unnecessarily tricky<\/a>, but\nwhen it is ready transferring the firmware is simple as:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>$<span class=\"w\"> <\/span>cp<span class=\"w\"> <\/span>ext0db6q\/ISOLINUX\/t<span class=\"w\"> <\/span>\/run\/media\/juser\/FREEDOS\/samsung\/ext0db6q<span class=\"w\"> <\/span>-r\n$<span class=\"w\"> <\/span>cp<span class=\"w\"> <\/span>ext43b6q\/ISOLINUX\/t<span class=\"w\"> <\/span>\/run\/media\/juser\/FREEDOS\/samsung\/ext43b6q<span class=\"w\"> <\/span>-r\n$<span class=\"w\"> <\/span>umount<span class=\"w\"> <\/span>\/run\/media\/juser\/FREEDOS\n<\/code><\/pre><\/div>\n\n<h2 id=\"booting\">Booting<a class=\"headerlink\" href=\"#booting\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Booting from the FreeDOS stick requires some BIOS support which\nshould be quite common nowadays. Ideally, one doesn't have to\nchange the boot order inside the BIOS configuration but can use a\nboot menu instead. For example, on a Thinkpad x220 the temporary\nboot device menu is invoked after power-on via pressing the\n'ThinkVantage' button and then F12.  Other popular shortcuts for\nthis menu are just F12, F7 (Intel) or even ESC.<\/p>\n<h2 id=\"flashing\">Flashing<a class=\"headerlink\" href=\"#flashing\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Arrived at the DOS prompt one has just to execute the autoexec.bat, e.g.:<\/p>\n<div class=\"m-code\"><pre><span><\/span><code>cd samsung\/ext0db6q\nautoexec.bat\n<\/code><\/pre><\/div>\n\n<h2 id=\"see-also\">See also<a class=\"headerlink\" href=\"#see-also\" title=\"Permanent link\">&para;<\/a><\/h2>\n<ul>\n<li><a href=\"https:\/\/blog.kylemanna.com\/hardware\/samsung-840-evo-ssd-linux-firwmare-update\/\">Kyle's article<\/a> that describes an alternative approach for\nbooting the Samsung image (i.e. loading the <code>BTDSK.IMG<\/code> file via the\nSyslinux <a href=\"http:\/\/www.syslinux.org\/wiki\/index.php?title=MEMDISK\">memdisk loader<\/a> via grub2). He also mentions queued trim issues\nwith the new firmware and in a <a href=\"https:\/\/blog.kylemanna.com\/hardware\/samsung-840-evo-trim-support-blacklisted-after-firmware-upgrade\/\">followup<\/a> he links how Linux kernel\n4.0.5 and later fix this via blacklisting queued TRIM in all\nSamsung 800-series drives.<\/li>\n<li>German article about Samsung's first attempt at a fix (incl. slow restoration): <a href=\"https:\/\/m.heise.de\/newsticker\/meldung\/Firmware-Update-fuer-Samsung-SSD-840-Evo-verfuegbar-2424150.html\">Firmware-Update f\u00fcr Samsung-SSD 840 Evo verf\u00fcgbar<\/a>, heise.de, 2014-10-15<\/li>\n<li>German article about Samsung's last attempt: <a href=\"https:\/\/m.heise.de\/newsticker\/meldung\/Samsungs-neue-840-Evo-Firmware-nun-frei-verfuegbar-2628990.html\">Samsungs neue 840-Evo-Firmware nun frei verf\u00fcgbar<\/a>, heise.de, 2015-04-29<\/li>\n<\/ul>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"samsung"}},{"@attributes":{"term":"ssd"}},{"@attributes":{"term":"firmware"}}]},{"title":"Updating the BIOS on a Dell Latitude E7270","link":{"@attributes":{"href":"https:\/\/gms.tf\/updating-the-bios-on-a-dell-latitude-e7270.html","rel":"alternate"}},"published":"2017-02-06T10:10:00+01:00","updated":"2017-02-06T10:10:00+01:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2017-02-06:\/updating-the-bios-on-a-dell-latitude-e7270.html","summary":"<p>Dell distributes BIOS updates for the Latitude E7270 as PE32\nWindows GUI executables. It turns out that the BIOS itself is\nable to extract the firmware images from such executables and to\napply them.<\/p>\n<h2 id=\"howto\">HOWTO<a class=\"headerlink\" href=\"#howto\" title=\"Permanent link\">&para;<\/a><\/h2>\n<ol>\n<li>Get the latest BIOS update executable from e.g.\n   <code>https:\/\/www.dell.com\/support\/home \u2026<\/code><\/li><\/ol>","content":"<p>Dell distributes BIOS updates for the Latitude E7270 as PE32\nWindows GUI executables. It turns out that the BIOS itself is\nable to extract the firmware images from such executables and to\napply them.<\/p>\n<h2 id=\"howto\">HOWTO<a class=\"headerlink\" href=\"#howto\" title=\"Permanent link\">&para;<\/a><\/h2>\n<ol>\n<li>Get the latest BIOS update executable from e.g.\n   <code>https:\/\/www.dell.com\/support\/home\/de\/de\/debsdt1\/product-support\/servicetag\/$YOURTAG\/drivers<\/code>. For example, as of 2017-02-06 this\n   would be <code>Latitude_E7x70_1.12.3.exe<\/code><\/li>\n<li>Verify the cryptographic file checksum (yes, Dell publishes\n   them over https)<\/li>\n<li>Put the executable on a FAT formatted USB stick<\/li>\n<li>Reboot the machine and press F12 when the Dell logo is\n   displayed<\/li>\n<li>Select the firmware upgrade menu entry<\/li>\n<li>Select the BIOS update executable in the file selection dialog\n   and continue<\/li>\n<li>Voila<\/li>\n<\/ol>\n<h2 id=\"usb-stick-partitioning\">USB Stick Partitioning<a class=\"headerlink\" href=\"#usb-stick-partitioning\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>What does work:<\/p>\n<ul>\n<li>DOS-style partition table<\/li>\n<li>1 primary partition<\/li>\n<li>bootable partition flag<\/li>\n<li>0xe partition type (W95 FAT16 LBA)<\/li>\n<li>vfat filesystem<\/li>\n<\/ul>\n<h2 id=\"dell-instructions\">Dell Instructions<a class=\"headerlink\" href=\"#dell-instructions\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>As always, Dell's documentation isn't very helpful. The\nBIOS install instructions just describe how to run the executable\nin a Windows or DOS environment. Which is scary if you are\nrunning Linux or something like that. They explicitly instruct\n'Non-Windows users' to boot into DOS using a USB stick and to start\nthe update executable from the DOS prompt. The more convenient\nalternative described above isn't mentioned in the Dell install\ninstructions, at all.<\/p>\n<h2 id=\"dos-boot\">DOS Boot<a class=\"headerlink\" href=\"#dos-boot\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The problem with USB-booting into DOS is that it is surprisingly\ntedious to get a proper ready-to-use USB FreeDOS image.\nFreedos.org only provides installer images. There is the\nseemingly popular Unetbootin GUI that supports creating a FreeDOS\nUSB stick but it <a href=\"https:\/\/github.com\/unetbootin\/unetbootin\/issues\/48\">downloads the images as root<\/a> and <a href=\"https:\/\/github.com\/unetbootin\/unetbootin\/issues\/58\">doesn't\ndo any verification of the downloaded files<\/a>, which both is\nquite irresponsible and dangerous.  There are even some\nready-to-use FreeDOS images available from private sites, but\neven if you trust the individuals, usually they don't provide any\nmeans for verification of the downloaded images (e.g. checksums\nover https, GPG generated file signatures).<\/p>\n<p>As-is, using <a href=\"http:\/\/web.archive.org\/web\/20160331083204\/http:\/\/www.chtaube.eu\/computers\/freedos\/bootable-usb\/image-generation-howto\/\">Christian Taube's FreeDOS image generation\nHOWTO<\/a> is the best option one has to create a FreeDOS bootable USB\nstick.<\/p>\n<h2 id=\"update-content\">Update Content<a class=\"headerlink\" href=\"#update-content\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>The BIOS update executable actually contains multiple firmware\nimages. That means that besides the traditional BIOS also the\nfirmware of the embedded controller (EC) and of the Intel Management Engine\n(ME) is updated.<\/p>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"dell"}},{"@attributes":{"term":"bios"}},{"@attributes":{"term":"firmware"}}]},{"title":"Shipping Books (in Germany)","link":{"@attributes":{"href":"https:\/\/gms.tf\/shipping-books-in-germany.html","rel":"alternate"}},"published":"2016-07-19T10:10:00+02:00","updated":"2016-07-19T10:10:00+02:00","author":{"name":"Georg Sauthoff"},"id":"tag:gms.tf,2016-07-19:\/shipping-books-in-germany.html","summary":"<p>There are several products for shipping books in Germany.\nThe 'Warensendung' product may be cheaper for certain\nbooks, but they are slowly transported and there is no tracking.\nThe following table compares different attributes and constraints\nof the products available in the market (last update: 2026-03-22).<\/p>\n<table class=\"m-table\">\n<thead>\n<tr>\n<th>Company<\/th>\n<th>Product<\/th>\n<th>\u20ac<\/th>\n<th>&lt; g<\/th>\n<th>w \u2026<\/th><\/tr><\/thead><\/table>","content":"<p>There are several products for shipping books in Germany.\nThe 'Warensendung' product may be cheaper for certain\nbooks, but they are slowly transported and there is no tracking.\nThe following table compares different attributes and constraints\nof the products available in the market (last update: 2026-03-22).<\/p>\n<table class=\"m-table\">\n<thead>\n<tr>\n<th>Company<\/th>\n<th>Product<\/th>\n<th>\u20ac<\/th>\n<th>&lt; g<\/th>\n<th>w &lt; cm<\/th>\n<th>h &lt; cm<\/th>\n<th>d &lt; cm<\/th>\n<th>w &gt; cm<\/th>\n<th>h &gt; cm<\/th>\n<th>constr.<\/th>\n<th>track.<\/th>\n<th>ETA<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/w\/buecherundwarensendung.html\">Warensendung<\/a><\/td>\n<td>2.70<\/td>\n<td>1000<\/td>\n<td>35.3<\/td>\n<td>25<\/td>\n<td>5<\/td>\n<td>10<\/td>\n<td>7<\/td>\n<td>'Warensendung' caption<\/td>\n<td>no<\/td>\n<td>4<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/w\/buecherundwarensendung.html\">Warensendung heavy<\/a><\/td>\n<td>3.55<\/td>\n<td>2000<\/td>\n<td>35.3<\/td>\n<td>25<\/td>\n<td>5<\/td>\n<td>10<\/td>\n<td>7<\/td>\n<td>'Warensendung' caption<\/td>\n<td>no<\/td>\n<td>4<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/b\/brief_postkarte.html\">Standard Brief<\/a><\/td>\n<td>0.95<\/td>\n<td>20<\/td>\n<td>23.5<\/td>\n<td>12.5<\/td>\n<td>0.5<\/td>\n<td>14<\/td>\n<td>9<\/td>\n<td>rectangular<\/td>\n<td>no<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/e\/einschreiben.html\">Standard Brief Einsch.<\/a><\/td>\n<td>3.30<\/td>\n<td>20<\/td>\n<td>23.5<\/td>\n<td>12.5<\/td>\n<td>0.5<\/td>\n<td>14<\/td>\n<td>9<\/td>\n<td>rectangular<\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/sendung\/simpleQuery.html\">yes<\/a><\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/b\/brief_postkarte.html\">Kompakt Brief<\/a><\/td>\n<td>1.10<\/td>\n<td>50<\/td>\n<td>23.5<\/td>\n<td>12.5<\/td>\n<td>1<\/td>\n<td>10<\/td>\n<td>7<\/td>\n<td>rectangular<\/td>\n<td>no<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/e\/einschreiben.html\">Kompakt Brief Einsch.<\/a><\/td>\n<td>3.45<\/td>\n<td>50<\/td>\n<td>23.5<\/td>\n<td>12.5<\/td>\n<td>1<\/td>\n<td>10<\/td>\n<td>7<\/td>\n<td>rectangular<\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/sendung\/simpleQuery.html\">yes<\/a><\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/b\/brief_postkarte.html\">Gro\u00df Brief<\/a><\/td>\n<td>1.80<\/td>\n<td>500<\/td>\n<td>35.3<\/td>\n<td>25<\/td>\n<td>2<\/td>\n<td>10<\/td>\n<td>7<\/td>\n<td><\/td>\n<td>no<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/e\/einschreiben.html\">Gro\u00df Brief Einsch.<\/a><\/td>\n<td>4.15<\/td>\n<td>500<\/td>\n<td>35.3<\/td>\n<td>25<\/td>\n<td>2<\/td>\n<td>10<\/td>\n<td>7<\/td>\n<td><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/sendung\/simpleQuery.html\">yes<\/a><\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/b\/brief_postkarte.html\">Maxi Brief<\/a><\/td>\n<td>2.90<\/td>\n<td>1000<\/td>\n<td>35.3<\/td>\n<td>25<\/td>\n<td>5<\/td>\n<td>10<\/td>\n<td>7<\/td>\n<td><\/td>\n<td>no<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/shop.deutschepost.de\/\">DP<\/a><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/de\/e\/einschreiben.html\">Maxi Brief Einsch.<\/a><\/td>\n<td>5.25<\/td>\n<td>1000<\/td>\n<td>35.3<\/td>\n<td>25<\/td>\n<td>5<\/td>\n<td>10<\/td>\n<td>7<\/td>\n<td><\/td>\n<td><a href=\"https:\/\/www.deutschepost.de\/sendung\/simpleQuery.html\">yes<\/a><\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">Hermes<\/a><\/td>\n<td><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">P\u00e4ckchen<\/a><\/td>\n<td>5.19<\/td>\n<td>25000<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>long + short &lt; 37<\/td>\n<td><a href=\"https:\/\/www.myhermes.de\/wps\/portal\/paket\/Home\/privatkunden\/sendungsverfolgung\">yes<\/a><\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">Hermes<\/a><\/td>\n<td><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">S-Paket<\/a><\/td>\n<td>5.79<\/td>\n<td>25000<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>long + short &lt; 50<\/td>\n<td><a href=\"https:\/\/www.myhermes.de\/wps\/portal\/paket\/Home\/privatkunden\/sendungsverfolgung\">yes<\/a><\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">Hermes<\/a><\/td>\n<td><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">M-Paket<\/a><\/td>\n<td>6.99<\/td>\n<td>25000<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>long + short &lt; 80<\/td>\n<td><a href=\"https:\/\/www.myhermes.de\/wps\/portal\/paket\/Home\/privatkunden\/sendungsverfolgung\">yes<\/a><\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">Hermes<\/a><\/td>\n<td><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">L-Paket<\/a><\/td>\n<td>10.99<\/td>\n<td>25000<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>long + short &lt; 120<\/td>\n<td><a href=\"https:\/\/www.myhermes.de\/wps\/portal\/paket\/Home\/privatkunden\/sendungsverfolgung\">yes<\/a><\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">DHL<\/a><\/td>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">P\u00e4ckchen S<\/a><\/td>\n<td>4.19<\/td>\n<td>2000<\/td>\n<td>35<\/td>\n<td>25<\/td>\n<td>10<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>no<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">DHL<\/a><\/td>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">P\u00e4ckchen M<\/a><\/td>\n<td>5.19<\/td>\n<td>2000<\/td>\n<td>60<\/td>\n<td>30<\/td>\n<td>15<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td>no<\/td>\n<td>2<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">DHL<\/a><\/td>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">Paket 2<\/a><\/td>\n<td>6.19<\/td>\n<td>2000<\/td>\n<td>60<\/td>\n<td>30<\/td>\n<td>15<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><a href=\"https:\/\/www.dhl.de\/en\/privatkunden\/pakete-empfangen\/verfolgen.html\">yes<\/a><\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">DHL<\/a><\/td>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">Paket 5<\/a><\/td>\n<td>7.69<\/td>\n<td>5000<\/td>\n<td>120<\/td>\n<td>60<\/td>\n<td>60<\/td>\n<td><\/td>\n<td><\/td>\n<td>long + 2 * (short + d) &lt; 300<\/td>\n<td><a href=\"https:\/\/www.dhl.de\/en\/privatkunden\/pakete-empfangen\/verfolgen.html\">yes<\/a><\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">DHL<\/a><\/td>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">Paket 10<\/a><\/td>\n<td>10.49<\/td>\n<td>10000<\/td>\n<td>120<\/td>\n<td>60<\/td>\n<td>60<\/td>\n<td><\/td>\n<td><\/td>\n<td>long + 2 * (short + d) &lt; 300<\/td>\n<td><a href=\"https:\/\/www.dhl.de\/en\/privatkunden\/pakete-empfangen\/verfolgen.html\">yes<\/a><\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">DHL<\/a><\/td>\n<td><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">Paket 31,5<\/a><\/td>\n<td>18.99<\/td>\n<td>31500<\/td>\n<td>120<\/td>\n<td>60<\/td>\n<td>60<\/td>\n<td><\/td>\n<td><\/td>\n<td><\/td>\n<td><a href=\"https:\/\/www.dhl.de\/en\/privatkunden\/pakete-empfangen\/verfolgen.html\">yes<\/a><\/td>\n<td>1<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Note that you have to print the qualifier 'Warensendung' on the\npackage when using the first two products. In contrast to the previously available\n'B\u00fcchersendung' product those packages don't need to be\nresealable, anymore.\nHowever, Deutsche Post still restricts the content, i.e.\ncontractually you are only allowed to ship books and wares as\n'Warensendung'.<\/p>\n<p>An address that is prefixed with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Packstation\">'Packstation'<\/a> can only be delivered by Deutsche Post (DP) or DHL because it is their DHL parcel collection service.<\/p>\n<h2 id=\"online-purchase\">Online Purchase<a class=\"headerlink\" href=\"#online-purchase\" title=\"Permanent link\">&para;<\/a><\/h2>\n<p>Buying the postage online is recommended since it is way more convenient and often also cheaper:<\/p>\n<ul>\n<li><a href=\"https:\/\/shop.deutschepost.de\/\">eFiliale - DHL\/DP Products<\/a><\/li>\n<li><a href=\"https:\/\/www.dhl.de\/de\/privatkunden\/pakete-versenden\/deutschlandweit-versenden\/preise-national.html\">DHL Online<\/a><\/li>\n<li><a href=\"https:\/\/www.myhermes.de\/preise\/paeckchen-paket\/\">Hermes Online<\/a><\/li>\n<\/ul>\n<p>The prices in the above table are all 'online' prices in case\nthere is a difference.<\/p>\n<p><strong>Update (2016-09-28):<\/strong> The Deutsche Post also provides a\nweb-service for buying online postage. After a separate\nregistration it can be used to buy postage online really fast. No\ntedious clicking in some web GUI anymore. I've created the\n<a href=\"https:\/\/codeberg.org\/gms\/python-inema.git\">open-source program frank<\/a> for easily buying one or multiple\nstamps (including addressing) from the command line.<\/p>\n<p>However, since 2025, the Deutsche Post postage order API is only\navailable for business customers, anymore.\nMeaning you have to register as 'Selbstst\u00e4ndiger' or other\nbusiness entity.<\/p>\n<h2 id=\"tracking\">Tracking<a class=\"headerlink\" href=\"#tracking\" title=\"Permanent link\">&para;<\/a><\/h2>\n<ul>\n<li><a href=\"https:\/\/www.dhl.de\/en\/privatkunden\/pakete-empfangen\/verfolgen.html\">DHL Tracking<\/a><\/li>\n<li><a href=\"https:\/\/www.myhermes.de\/wps\/portal\/paket\/Home\/privatkunden\/sendungsverfolgung\">Hermes Tracking<\/a><\/li>\n<li><a href=\"https:\/\/www.deutschepost.de\/sendung\/simpleQuery.html\">Deutsche Post Tracking<\/a><\/li>\n<\/ul>","category":[{"@attributes":{"term":"misc"}},{"@attributes":{"term":"books"}}]}]}