GPS disciplined oscillators (GPSDO) can be used to create synchronized (coherent) clocks at a distance without drastically affecting other parameters of the local oscillator such as phase noise while simultaneously reducing long time drift.
This article serves as an introduction into the underlying concepts to provide the background on designing and manufactering an actual GPSDO in later articles.
Transmitters and receivers typically use crystal oscillators to generate clock signals. These oscillators have a temperatuur coefficient this results in a drift in frequency if the oscillators gets colder or warmer. Manufacters are aware of these implications often creating temperature compensated oscillators called Temperature Compensated Crystal Oscillator (TCXO) or even Oven Controlled Crystal Oscillators (OCXO).
These Characteristics in terms of phase and frequency stability (phase noise & frequency drift) play a major role in generating extremely high frequencies. Due to the high multiplication factor to obtain these high frequency any imperfections will also get multiplied. This can results in a large variety of problems such as frequency stability or modulation. To be able to minimize these problems we need to understand the characteristics starting with the definition of time and what a second is. Probably by no suprise frequency is based on the number of oscillations in one second. This second, our absolute unit of time, is based on the number of oscillations of a cesium-133 atom (SI) between its two so called hyperfine transition ground states. The exact details about these cesium oscillations and the so called zeeman lines are beyond the scope of this introduction.
Given that SI units define time based on the cesium-133 atom its no suprise that we can use a cesium clock to provide an absolute time reference. Using such a clock we can count the number of oscillations until it matches what should match exactly one second and measure how much oscillations the device under test (DuT) deviates from this. Usually the DuT provides a 10 MHz signal and our cesium clock signal is divided down to this common reference frequency. Even further, parts of an oscillations or the phase between oscillation periods can also be measured. Lastly, this difference can be sampled multiple times per second as opposed to just once. The result is the drift of an oscillator over time. Using statistical methods this drift can then be used to quantify the stability of the oscillator as will be described later.
These cesium clocks are prohibitively expensive however, so a cheaper solution is needed. Luckily, each GPS satellite contains one such a cesium clock. The signals of these GPS satellites and the underlying protocol thus allows us to reconstruct a time signal based on data from all currently received GPS satellites.
This reconstructed time signal is provided as square wave oscillating once per second (1 Hz), this signal is called Pulse Per Second (PPS). The short term stability behavior in terms of phase noise and harmonics of this signal is very poor, much worse than even the worst crystal oscillator. However, the long term behavior of this signal, over tens of minutes is better than even an Oven Controlled Crystal Oscillator (OCXO). If only there is a way to combine the short term behavior of an OCXO with the long term behavior of a GPS PPS square wave!
This is precisely the task of a GPS disciplined oscillator, these devices integrate two different clock signals adjusting the output clock of one using the other as reference clock to modulate these adjustments. The result is a self-stabilizing feedback loop for which the characteristics of one clock signal can be made dominant after a given period of time. This integration time, after which one of the two clock signals becomes dominant can be controlled. This time is chosen to match the precise transition point where the characteristics of one clock become superior over the other.
This works because all OCXOs there reference voltage can be adjusted. This adjustment voltage kan be derived from a phase comparator, specifically a phase locked loop (PLL). With a long intergration time this adjustment voltage is adjusted slowly ensuring it takes a long time before the GPS PPS signal becomes dominant.
The critical setting within this system becomes the integration time. This needs to be set as such that it occurs just on the transition where the characteristics of one clock surpass the other. This requires us to understand these characteristics in detail, primarily these are phase noise and frequency stability. This phase noise can be measured easily directly at the output of the GPSDO. This will be the output of the OCXO and can be done on a spectrum analyzer using a bode plot. In such a bode plot the power density of a 1 Hertz bandwidth of the spectrum is measured relative to the power at the carrier. We repeat these measurements for specific distances from the carrier and express it in terms of dBc/Hz with lower values being better.
The other characteristic, frequency stability, is measured using repeated phase differences between two clock signals. This measurement is a lot less intuitive and requires the use of statistics. This statististical analysis differentiates if individual measurement points influence the short term or long term frequency stability.
Luckily these statistical methods have been published long ago and are well standardized. One of these methods is called Allan Deviation (ADEV). This mathematical method modulates a selection factor $m$ with reference to the measurement interval $t$ (the sample interval in seconds used during collection the measurements). By increasing the factor $m$ we can select measurement points that are multiples of this measurement interval $t$. In the base case $m=t$ and each sample will be accounted in the calculation. Using $m = t*2$ the sample selection will switch between selected and unselected between each sample. Given an one second sampling interval and $m = 10$ would result in a sample selection of sample $10, 20, 30$ etc from our sample set. The remainder of the formula is based on variance and standard deviation, as a reminder to those bright college days, this is the summation of each sample of the sample set squared en multiplied by $1\div{(N-1)}$ where $N$ denotes the number of selected samples.
In reality this basic formula for ADEV is rarely used and Overlapping Allan Deviation (OADEV) is used or even Theo1 instead. This formula has the advantage of reusing measurement samples more often for different values of factor $m$. The entire formula for OADEV is shown below.
$$\sigma^2_y=\frac{1}{2(N-2m)\tau^2} \sum _{i=1}^{N-2m} [x _{i+2m} -2x _{i+m} + x _{i}]^2$$
To be able to reuse measurement samples OADEV selects not one but three samples per point in the summation as denoted by $[ x _{i+2m} -2x _{i+m} + x _{i} ]$. The subscription of $x$ denotes the index of the sample being selected from the sample set where $x _{1}$ denotes the first element.
The results of an OADEV computation as performed by the TimeLab [2] program are shown below. With these results lower values represent better results (higher stability).
Collecting measurement data to perform these frequency stability measurements requires access to a reference clock that is more stable then the clock you are trying to measure. The results is that while it is realitively cheap to build a GPSDO using off-the-shelf hardware, validating it and adjusting the cross over point for integrating the clocks signals is difficult.
Potentially the least commercially demanding solution that can provide adequate results is the use of refurbished rubidium clock oscillators from ebay.com or aliexpres.com.
These rubidium clock oscillators can be used as a reference clock on the NanoVNA H4 [3]. This NanoVNA H4 is a handheld Vector Network Analyzer (VNA) for which the TinyFPA [3] firmware was written by the community. This NanoVNA H4 can be readily bought online but through platforms like aliexpress or ebay defects can occur due to cheap reproductions so official resellers are advised.
With the TinyPFA firmware the NanoVNA H4 becomes a phase frequency analyzer that is able to provide measurement data that can be analyzed by TimeLab directly using serial over USB.
This setup will be used to analyze and iteratively improve the design of a GPSDO using the ADF4001 phase locked loop. The design as well as the measurement setup will hopefully be described soon in subsequent articles.
73,
Corne Lukken (PD3SU)
The GR874 RF connector was conceptualized between 1947 and 1948 by General Radio being popular throughout 1950 up until 1970 for many different pieces of RF equipment. The hermafrodite design as well as its low reflection coefficients and relatively operating frequency (up to 10 GHz) allowed for its popularization. However, its large size and high cost inevetially caused its decline once SMA become popular.
These connectors were available in both locking and non locking variants as well as adapters to a large variety of different other RF connectors. Notably, the GR874 system was often used in academic setups with stub filters or air lines. The 1973 brochure of these adapters is readily available online [1].
Given their conception in the early 50s and their popularity up until the late 70s how would these adapters hold up in 2024?
In this article the LiteVNA is utilized to characterise reflection & attentuation performance of half a dozen adapters and one AC coupler. For this AC coupler the high pass -3 dB cross over point will also be identified.
All these adapters are expected to have excellent SWR (reflection coefficient) and minimal attentuation till far beyond the maximum measurement frequency of 6.3 GHz of the LiteVNA. From the brochure we expect the SWR to be less then 1/1.10 typically although variance and error in the measurement setup will likely prohibit measuring down to such low SWR.
The LiteVNA is calibrated from 150 KHz to 6.3 GHz using 1024 measurement points and 20 averages per point. Additionally two adapters from SMA to BNC and BNC to SMA are used respectively. These adapters will fit most GR874 adapters allowing to largely isolate imperfections from the measurement setup. The measurements affected by discrepancies in the setup will be marked explicitely.
Because we have to convert SMA to GR874 and back to SMA for the termination we will effectively always measure two adapters in a single measurement.
The results are separated into two section one for reflection (S11) measurements and one for the attentuation (S21). Lastly, the results for the ac coupler are shown separately.
In total two BNC female, Two N male, one BNC male and one SMA male adapter are measured as well as the ac coupler.
Interestingly enough the second BNC male adapter seemed to have some kind of oxide coating that made the results seem AC coupled. After some brief cleaning this oxide coating and its effects on the results went away. The results without cleaning are shown first followed by when the BNC male adapter was cleaned.
Across the board the highest SWR did not go beyond 1/1.6. From the original manufactering specification these adapters do not exceed 1/1.10 all the way up to the 8.5 GHz they are rated for. Given room for measurement error and that each measurement constitutes two adapters back to back it is fair to assume these adapters still perform very close to the original rated reflection coefficients.
Due to shortcomings in the measurement setup, primarily, the absence of the second BNC to SMA adapter the following results will show amplifcation for some frequencies. This amplification is not real as these adapters are passive but rather a result of having lower attentuation then the second BNC to SMA adapter would have introduced.
These adapters and coupler hold up extremely well for their age. Not only is the -3 dB already reached at just 280 KHz for the AC coupler it performs nearly without any attentuation up till 6.3 GHz.
The higest attentuation was found in the BNC adapters but is close to within measurement error. Similarly reflections are almost always below 1/1.5 except for the AC coupler which reaches up to 1/1.9 at around 5.4 GHz.
With this there seems to be no reason to replace these connectors on old RF equipment especially given how well these adapters still perform.
The Purism Librem5 one of the first phones specifically designed to run Linux based operating systems without Android (along with the Pinephone).
As with any early concepts, designs and platforms this device has numerous small grievances which might complicate its daily use. This guides provides an overview of all my personal grievances and resolutions grouped by several topics.
Let’s start with describing several of the problems that might hinder daily use. First and foremost is battery life, expect no more than 8 hours on a single charge. Second, is connectivity you will find that both cellular and Wi-Fi become inactive frequently even though the phone suggests it is still connected. Moreover, out of the box GPS and Bluetooth do not even work at all at the moment (2023-09-06). Third is the compatability of apps, primarily social media apps are not supported or very difficult to use. And last there are no screen protectors or cases made specifically for the Librem5.
While it relatively simply to list all problems with a particular device we have to recognize several outstanding features. Firstly is the dedicated hardware buttons to disable functionality such as camera, Wi-Fi or cellular. Speaking about cellular, this device uses a separate cellular modem with its own dedicated memory, contrarily to most phones which have one memory region being both accessed by the CPU and cellular modem. From a security perspective both the modem having dedicated memory and being able to physically unpower it is a tremendous improvement.
While for most mobile applications you can find a decent replacement there are a few categories that proof problematic. Primary social media applications like snapchat or whatsapp are cumbersome to get working. Nevertheless, through Waydroid most android applications can be run on the Librem5.
Apart from using Waydroid several applications available from the store are noteworthy and work natively on mobile.
Some apps do not work natively on mobile displays but can be made to work by
forcing them to be scaled down. This is done in the mobile settings
app under
the compositor
tab.
Broken apps that do not start, are unusable due to scaling or otherwise are difficult to use on the phone currently
Several write ups have been made by Sebastian Krzyszkowiak (dos) about how to install waydroid on the Librem5. Unfortunately these excellent guides are very difficult to find on the internet. Instead searches are covered in unhelpful forum posts complaining about how this (supposedly) is not possible.
Dos has made several guides the most recent one being available here https://source.puri.sm/-/snippets/1198
The instructions boil down to the following commands
sudo apt update
sudo apt full-upgrade
wget https://source.puri.sm/Librem5/debs/waydroid/-/jobs/401129/artifacts/file/debian/output/waydroid_1.3.3-0pureos0+librem5ci79377.0353512_all.deb
sudo apt install ./waydroid_1.3.3-0pureos0+librem5ci79377.0353512_all.deb
That is all, you will now have waydroid launcher on the screen. Booting the first time will take a while and requires an internet connection. Be sure to minimize the keyboard when you launch the app otherwise waydroid will only occupy part of the screen.
The guide by dos contains additional information about how to access logs, use adb to debug or install apks and much more. However, there are a few limitations mostly related to sensors, WiFi, GPS and cellular. Additionally, the clipboard is not copied between waydroid and the host so instead something like the Nextcloud client can be used to copy data back and forth.
Given that the waydroid image does not contain Google play services an alternative app store like fdroid can be installed. For applications only available from the play store apkpure or aptoide can be used (with caution). Personally, I recommend uploading each apk to virustotal.com prior to installing.
The Nextcloud client allows to synchronise files for offline use, without an active internet connection. This is ideal for files such as keepass databases especially because any local changes to this file will also be uploaded back to Nextcloud.
The Nextcloud client can be found in the regular app store but the option
to show incompatible apps has to be enabled in order to see it. Simply press
the burger menu in the top right followed by preferences -> Show Incompatible
Applications
.
Once installed, launch the Nextcloud client and press the downward arrow to open the settings menu. These windows will be scaled down so the text might not look very crisp. In the legacy settings menu you will find it is much easier to configure which folders to sync between Nextcloud and the device.
If you close the Nextcloud window the only way to reopen it again is by first
typing killall nextcloud
in a terminal prior to relaunching.
Alternatively the Nextcloud files, contacts and calendar can be synced through
gnome settings. When using this for files it will appear in the file explorer
as a network mounted fileystem. To add an Nextcloud account in settings simply
go to gnome settings -> Online Accounts -> Nextcloud
. When added the option
to sync calendar, contact and files can be individually enabled or disabled.
When using the Nextcloud file synchronisation through gnome settings be sure to unmount the network filesystem whenever it is not used to prevent excessive battery drain. Alternatively the files synchronisation can be enabled and disabled on the fly in gnome settings whenever it is needed. The network filesystem will automatically remount during boot or when a new network connection becomes available.
The use of Nextcloud file synchronisation also allows to easily export and
import Firefox bookmarks. You can find this menu under the hamburger icon
in the top right Bookmarks -> Manage Bookmarks -> Import and Backup
.
Phone Contacts can be automatically synced through the Nextcloud online account in gnome settings allowing to access the same set of contacts anywhere.
However, in the absence of a Nextcloud installation altnernative methods can be used to keep contacts synchronised between devices or Waydroid. First the native contacts app has the ability to import contacts from Vcard files as well as export them.
The import button can be found under the right top burger menu labelled import
while exporting is done by selecting one or more contacts using press and hold
and selecting export in the lower left corner.
Inside waydroid the Android contacts app will have the same ability to import
and export contacts. On android the options are found under the left top
burger menu followed by settings
and labelled import
and export
respectively.
Unfortunately you will find that the Purism Librem5 regularly loses both WiFi and cellular connectivity among other issues. Other issues such as that Bluetooth does not work at all out of the box. For most of these I have developed tips and guidelines that can elleviate most problems.
First you will find that WiFi will refuse to connect or that it says it is connected but you can’t actually transfer any data. The solution is to simply toggle the physical hardware button disabling Bluetooth and WiFi temporarally.
In some rare occurances this will not fix the issue at which point you will need to reboot the phone.
For cellular the behavior is mostly identical to WiFi but instead you often
manually have to register to the APN in order to get network service. It is
easily visible when this happens by the exclamation mark in the top left corner
of the device. For this navigate to gnome settings and go to
Mobile -> Access Point Names
and take note of the name of the selected access
point. Next open a terminal and use it to toggle the APN network:
nmcli connection down "APN Name"; nmcli connection up "APN Name"
This should have the exclamation mark dissapear. It is best to routinely check
if you actually still have a working network service by pinging a server of
choice ping meter.met
in a terminal. In particular when travelling at high
speed having to switch cell towers regularly.
GPS can be enabled from gnome settings -> privacy -> location services
this
will allow phosh to provide geoclues which are used by both gnome-maps
and
pure-maps
. However, other maps apps such as foxtrotgps
require the use of
gpsd
which requires more configuration. In addition you might notice that the
location in gnome-maps
never updates because it silently crashes after
receiving the first geoclue.
The apparmor definition of gpsd
will have to be modified given the gps
serial device will be on /dev/gnss0
for which apparmor is not configured.
For this, use a text editor with elevated permissions to open
/etc/apparmor/usb.sbin.gpsd
and navigate to the line starting with /dev/tty{
and add the following as shown below:
/dev/gnss[0-9]* rw,
After this restart apparmor and gpsd.
sudo systemctl restart apparmor gpsd
You can check how well your gps is working by using the cpgs
tool in a
terminal. You might need to make the font a bit smaller for it to fit the
Librem5 screen.
Bluetooth is currently unsupported unless the Bluetooth module firmware is manually downloaded and installed. Simply execute the following:
wget https://source.puri.sm/angus.ainslie/firmware-rs9116-nonfree/-/raw/debian/master/Firmware/RS9116_NLINK_WLAN_BT_IMAGE.rps
sudo mv ./RS9116_NLINK_WLAN_BT_IMAGE.rps /lib/firmware RS9116_NLINK_WLAN_BT_IMAGE.rps
Afterwards modify /etc/modprobe.d/librem5-devkit.conf
and change
dev_oper_mode=_
to dev_oper_mode=13
. Now reboot.
Unfortunately, whenever the device now boots you will be discoverable on Bluetooth. Turning off Bluetooth will only engage rfkill and thus you will still be discoverable on Bluetooth!
The only way to prevent Bluetooth discoverability is by executing the following anytime the phone reboots or the WiFI / Bluetooth hardware button is toggled:
bluetoothctl discoverable off
bluetoothctl pairable off
bluetoothctl advertise off
These commands must be executed before turning off Bluetooth in gnome settings!
Some wireless audio systems or car systems will refuse to pair with the device.
One solution is to use hciconfig
to change the device class such that it
better reflects being a phone: sudo hciconfig hci0 class 0x5a020c
One of the nice things about low level Bluetooth access is that you can now configure the device however you like. This allows to either source audio from the phone to a connected device like Bluetooth speaker but also allows to sink audio from a device and play that through the phone speakers or headphones.
I recommend to install both pavucontrol
and blueman
to make configuring
Bluetooth easier. From the blueman-manager
you can select to configure any
device as source
or sink
under audio-profiles
. Similarly this can be
in pavucontrol
under the control plane.
For sourcing audio using either pavucontrol
or blueman
is sufficient but
for sinking you require to use pactl
. Simply use pactl list
to show the
audio sink when connected. Next create a loopback device to play the sinked
audio out of the speakers or headphones:
pactl load-module module-loopback source=bluez_source.xx_xx_xx_xx_xx_xx.a2dp_source
Be sure to replace the xx_xx_...
with the bluetooth address as listed by
pactl
before. With this the audio can now be played.
Without the loopback device the sinking can still be done but the sinked audio is only available as microphone input in that case.
In order to be able to launch blueman-manager
you might need to start the
bluetooth service systemctl start bluetooth.target
The phone can operate about 8 hours on a single charge which is pretty low for a phone. Now an experimental suspend feature is available that significantly extends this operation time. However, networking is not available while the phone is suspended, and it will only wake up for phone calls and text messages at the moment (2023-09-07). Now you could keep your phone connected to the charger for most of the day or charge it multiple times. However, both those conditions have downsides as I’ll explain.
Constantly keeping your battery at 100% with trickle charging is pretty bad for Lithium batteries their health and expected lifetime. The same type of degradation is experienced from rapid charge and discharge cycles, particularly when the battery gets hot / warm.
I have found this to be the case with the Librem5. Through sensors
we can see
that the battery is being supplied with roughly 20/30 mA of current continuously
when the battery is 100% charge. This means that keeping the phone plugged in
with the battery connected will accelerate the degradation of the battery.
More so, charging and discharging the battery multiple times a day does not only ad to the limited number of charge cycles of a Lithium battery but the battery also gets relatively hot (around 45 degrees Celsius) which further accelerates degradation.
Especially in Europe this is problematic as Purism refuses to ship batteries due to export surcharges. So unless you bought multiple batteries with the phone, which was not even an option with the crowdfunding campaign, you are stuck with this single battery for the entire phones life (yikes). In my opinion this situation is unacceptable export charges or not, especially for a 1000$ + phone.
To prevent rapid battery degradation I recommend removing the battery and powering it directly from a 18W capable USB-PD power bank or charger. This works, but it decreases ease of use and mobility. Keep in mind, depending on the power bank the phone might have problems booting, so it might be best to retain the battery until its fully booted only removing it after.
If you keep the back cover removed you will find it is quick and reliable to insert and remove the battery while the phone is operating (hence the bareback method). Should you need to run of with your phone simple connect the battery and start walking.
The only downside of this approach is that the phone will get confused about the
total capacity of the battery after the first insertion / removal. You can check
the actual state of charge through a command line utility called inxi
by
executing inxi -B
.
Using a short USB-C capable with two right angle connectors you might be able to construct a power bank and Librem5 sandwich that increases mobility drastically.
There are no purpose made screen protectors or phone cases available for the Librem5. However, several screen protectors should fit fairly well including
I personally use a ZTE Blade V9 screen protector and am satisfied with the fit although its a bit short around the edges and does not fully cover the screen.
For phone cases several 3D STl files are available online that can be 3D printed. However, this must be done in a material like TPU otherwise it will be impossible to stretch the case over the phone. 3D printing TPU requires a direct drive extruder and full metal hotend without PTFE feed tube. In the absence of the necessary 3D printing equipment an online 3D printing service capable of TPU printing can be used. Expect the cost to be around 50 euro.
You can find up to date 3D models for cases on the gitlab repository of purism.
I have not been able to fully test fit any of the online available phone case models. But have matched the location of buttons and phones by printing several cases in PLA instead of TPU.
According to the (terms and conditions) policy page of the purism store a 1 year limited warranty is offered including on the Librem5 phone. This a violation of European legislation that requires at least a 2 year legal guarantee or even longer in select countries. The warranty of Purism also explicitly does not apply to select goods such batteries while offering warranty to defective products even consumbles is also required. Purism also charges the shipping for defective products to the customer which is not allowed.
I would highly encourage Purism to follow the free training courses on European consumer law through the Consumer Law Ready portal.
Following the simple deductions from the policy and consumer law page we can conclude that Purism is in non conformance with European legislation.
For highly detailed and in depth information see the European Derictives:
There is a lot to be said about this phone, it serves a niche market with many of the features that the community values but clearly is not a very polished experience yet. This guide provides some of the steps to make the device more useable but it still a lot more cumbersome to use than a flagship Android or IOS phone. The focus of Purism on the US market and neglect towards European customers is worrysome and should be taken into consideration before purchase.
Years ago I received an Hameg HM1005 analog oscilloscope. These scopes, much like any Hameg really, is easily recognizable by its easy to understand user interface. Unique to this Hameg scope is its combination of analog and digital features. Most prominently its three 100MHz bandwidth inputs, x times y mode and 10x horizontal zoom. Its one and only feature is a digital adjustment between 0 and 999 steps for the delayed timebase.
During its acquisition I was already told that several problems plagued the scope from operating correctly. This must have been roughly 5 years ago already and the scope has been in working order for some time. I have documented this and other reparations on electronics in case I need to revisit them. But how do you take on such a repair in a careful and systematic manner? This is the process I’d like to share in detail.
Honestly, there are dozens of ways to approach an electronics repair and this is not necessarily the best one. However, I will write as if this is the only approach to keep the rest of the story as brief as possible. In this approach we will cover 6 individual steps that should be executed in order. Normally, there would also be a re-calibration step but this turned out to be unnecessary for this repair.
Very brief, these steps are in order fault analysis, disassembly, repair, verification and assembly. Normally re-calibration would be performed after verification.
In this first step we determine the problems the device is having and every condition in which this problem appears. Take care to note down these conditions and the problems either using a table on a piece of paper or in a program like excel. Twiddle and pull knobs and buttons as loose contact is a very common problem. Lastly, be patient several problems can take a long time to appear. Sometimes intermittent problems are caused by thermal stress or overheating, a cheap infrared thermal camera can help with this greatly.
Secondly, during disassembly we carefully remove the exterior of the device but ensure it remains in a functional state. Sometimes cables might be of insufficient length in which case they should be extended. While extending cable that carry high frequency signals extra care is needed. Drawings should be made as an overlay of the front, back and other panels such that screws and parts can be marked on these drawings. These drawings will serve as a map during re-assembly. Loose parts can be taped down using scotch tape. More involved or intricate steps of the disassembly should be documented in a step by step guide. Lastly, reference the user and service manuals if possible.
In the third step we finally start to use our fault analysis to identify the root cause of our problems. It is best to start this process with common fault analysis just because these are so predominant in electronics repair. These checks should include, checking voltage rails, refitting cables and connectors and checking fuses. If these checks proof insufficient a deeper analysis is needed. It is best to have schematics of boards available as reference. If these are entirely unavailable it is best to reconstruct suspected parts of the circuit in a reverse engineering process. This reverse engineering process can be performed by measuring resistance between points on the circuit. When it becomes difficult to reason about signal and current flow in small parts of the circuit a tool such as circuitjs can be used to visualize them. More advanced simulation is also available through spice programs. More recently kicad has also gained support for circuit simulation
Finally, once the problem has been found the necessary adjustments can be made or parts replaced. When changing adjustable resistors or capacitors it is best to always first measure there initial value and document it. Similarly, if a component is to be replaced because it is highly out of spec note down the measured value.
Fifth, with the problem supposedly solved we can begin the process of verifying the repair. Several actions are key to this step. These include, power cycling the device several times, letting it warm up and cool down and lifting the device and shaking it. Take into account the conditions that triggered the fault before especially executing these a few times. Use your intuition during this step, given the conditions in which you encountered faults, what could be potential further triggers? It is best to be thorough in this step and dissuade any self assurances of the current repair. Depending on the fault and the repair it might be necessary to switch between step three and four several times.
Finally, in the last step, the device is re-assembled using the documentation created earlier and it can be put back in working order. It is recommended to document the entire repair process even including pictures in case the fault returns again. The likelihood of this depends highly on the type of fault.
With these clear steps in mind we can perform the actual repair on the Hameg oscilloscope. This diagnosis starts by turning it on making it clear that no signal is shown on the display. However, the underscan indicator lights up suggesting the signal is below the display line. When the vertical position is adjusted it needs to be turned almost the entire way for the signal to even appear on screen. Turning it the entire way leaves it roughly half way across the screen. This is a strong indicator towards the cause of the problem. Immediately a thought process starts as to what parts of the device relate to drawing the trace on the screen. For me, this indicates a potential problem with the power supply, specifically the section feeding the vertical power amplifier. Or perhaps the vertical power amplifier itself. The reason the final power amplifier is a stronger suspect then any intermediate stage is because the final power amplifier will dissipate the most power. This makes it undergo the most mechanical and thermal stress which contributes highly to failure rates.
But why would the failure of such a vertical amplifier lead to the signal only going up half way on the screen? This type of reasoning relies on intuition but a lot can be determined from analyzing schematics as well. Most if not all vertical amplifiers in devices such as oscilloscope utilize differential amplifiers. These types of amplifiers are built by having two identical (symmetric) amplifier chains. If either the top or bottom half of this chain is not working we would expect the signal to only go halfway (or only start halfway). I also suspected that it might be that the amplifier was not getting the appropriate voltage. We should also employ steps to validate our intuition, cross referencing schematics. In this case we can use multiple input channels to verify the problem persists across all of them. When analyzing the schematic it was clear that the oscilloscope was indeed using differential signaling and amplifiers as observed from the single ended to differential conversion in the analog frontend.
Differential signaling has a number of advantages such a noise immunity through common mode rejection. Especially, when this conversion is performed as early as possible. As a result the Hameg HM 1005 is relatively quiet even at 1 mv per division. Even though the high frequency analog signal has to move across a very cheap sliding switch (oef). Given the partial functionality we can continue with further investigating any other issues. Among these are several potentiometers that are very tight or with bad contacts. Especially the contacts for Volts per division behave particularly poorly between the 20mV and the 50mV range.
In short the fault analysis detected 5 individual problems with the first and foremost being the most substantial.
The five problems conclude our fault analysis we can now continue with the disassembly. Luckily, this is relatively straight forward for this Hameg oscilloscope. 2 screws hold the case at the back afterwards it can glide of. Everything internally is secured to a ridged internal frame. This frame also holds the front panel using 5 screws. The indicators have to be removed to remove the front panel. This results in having to re-calibrate the indicator dials which we will deal with later. It is best to store dials on a drawn layout that represents the front panel.
With every panel easily removable and the entire boards connected to a rigid internal frame. In this case there is no need to extend any cables between board to board connections to improve access. Normally, extending cables or cards so they can be accessed while the device is operating is a common part in my repair procedure. Having the device operating and accessible aids in the process as it simplifies measuring signals tremendously.
During these initial repair steps I’d recommend to perform checks for common faults these include:
The monitoring of supply voltages is ideally done under load this helps identify potential shorts or supplies that can not supply enough current. Typically labels can be found on the board marked with test points and voltages. If not on the board themselves potential test points are often easily found from the schematics.
Leakage current is best measured out of circuit. A capacitor can easily be taken out of
circuits by removing on of the two legs from the board temporarily. The primary suspects
are large electrolytic or very old paper capacitors. The leakage current can be measured
by supplying a voltage through a current limiting resistor. The leakage current is then
measured from the voltage drop across the resistor. For this it is best to use resistors
that are multiple of 10s. A resistance of 1k would give a drop of 1 volt per ma (10k would
give 1 volt per 100 uA). Take into account that the voltage drop is also the burden
voltage and it will influence the measurement. As a rule of thumb try to use a resistor
value that keeps the voltage drop below 1 volt. The leakage current for electrolytic
capacitors should be less the 0.01*C*V
. Where C is in uF en V is in volts. The leakage
of most other capacitor types should be on the order of 10 to 1 uA. For example a 10.000
uF 40 volt electrolytic capacitor should have a leakage current smaller then 4 mA.
Lastly connectors should be firmly wiggled and re seated by removing and putting them back. Also check pins for crust or other degradation. The pins can be softly scratched to remove these contaminants if desired. Optionally, a resistance measurement or continuity tester can be used to identify if connections are intermittent and make solid connection.
By utilizing these checks I determined that at least some of the capacitors from the main power supply section needed to be replaced. To ensure prolonged live I replaced the entire capacitor section. Given that this is only 6 capacitors in total the cost is relatively minor. For good measure I bought low Equivalent Series Resistance (ESR) capacitors from a name brand like Rubycon.
These capacitors replacement did not resolve the vertical deflection issues hinting us further at problems with the vertical amplifier. For this I identified 6 stages of final amplification from the schematic. The reason I start diagnosing the final amplifier board first is because it dissipates the most power making it undergo the most thermal stress which aggravates failure. Using the symmetric design of such a differential amplifier I can verify the output at each stage by verifying the drive level is identical at both sides. To clarify, I turn the vertical position all the way down for the bottom half and all the way up for the top half. The drive level will likely be negative for the bottom half but the voltage should still roughly match.
I followed the drive level for each stage and was met with a lack of output at the final stage. When I tried to use the schematic for referencing this time it seemed none of the transistors in the amplifier chain matched. It is common for these types of device to undergo multiple revisions during their production. Unfortunately only one revision is typical available as online download and it is not easy to identify which version is made available. I aim to improve this over at our museum https://museum.dantalion.nl/documents
Given that the schematic did not match the vertical amplifier board a little tracing was in order to reconstruct the essential parts. This helps determining the connections at the underside of the board and how they attached to several components, including the transistor without a drive level. Looking at it now years later it makes little sense to me but I remember that it aided me in finding the connection.
When using this schematic to trace the bottom of the PCB board to the top it was not immediately clear why no drive signal appeared on the output. It took several minutes of closely looking before I noticed the trace had completely burned out. A continuity test proved there was indeed no connection between the top left leg and center pad as marked in red in the next image.
The length between the traces was short enough that solder could be used directly to bridge it. The final repair is shown in the image below. We can verify that this resolved the vertical deflection issue because we have ensured the device can be used while disassembled for repair.
With the electrical defects dealt with the focus remains on the knobs and buttons that are misbehaving. I repaired most of them, especially smaller ones by repeatedly moving them from left to right. This worked for the trigger level and screen intensity. For bigger potentiometers, especially those with safety pins, I recommend to use PTFE spray. To do this simply remove the safety pin and spray PTFE spray into the hole. Afterwards let it soak for a minute and then start moving it from left to right. This can not only repair intermittent behavior but also unstuck ceased potentiometers. The PTFE lubricant is soft enough where it won’t damage the axle or the carbon trace inside.
With the PTFE spray the two potentiometers controlling the volts / division on both channels came free quite quickly. With this the functionality has been restored.
The final step is to re-align the indicators of the front panel for this two separate approaches are needed. Most indicators should be aligned such that the arrows travels equal amount to the left and right. Depending on the age and built techniques of the device aiming the indicator straight up might be sufficient to achieve this. Other indicators should be aligned based on their impact on operation. For instance, horizontal and vertical position. These controls are best aligned such that the centering on the indicator achieves centering on the screen of the device.
It should be noted that it might not always be possible to achieve centering for any control. With the Hameg the horizontal axis gets a substantial offset when x times y mode is enabled. The decision has to be made to center the indicator for regular use or for use in x times y. Depending on the device and calibration procedure it might be possible to reduce or eliminate these offsets with internal trimmers.
We got lucky with this repair having the primary failure in section that is symmetrical. Even more so, the repair did not require a single part to be replaced. Even calibration was not really required. For now the Hameg HM1005 is fully functional again with smooth potentiometers and solidly switching knobs and buttons. A happy device with a happy owner.
]]>Our framework OpenCSD and filesystem FluffleFS are designed using existing technologies such that concurrent regular and offloaded access can be achieved! In addition the entire software suite is written in userspace drastically improving easy of use and reducing the barrier to entree. Together our solution is the first to support CSx offloading with filesystem integration while concurrently supporting regular access even to the same file!
Get started immediately by visiting our Github repository: https://github.com/Dantali0n/OpenCSD
The success of this design is achieved through a large set of pre-existing technologies that come together to create a cohesive whole.
The first technology is eBPF, a highly integrated technology in the Linux kernel. With eBPF users can write code using a familiar C programming language that is compiled to bytecode executed by a Virtual Machine (VM). Through header files and a call instruction that traps the VM, completely vendor and host architecture agnostic programs are achieved. This allows end users to compile eBPF programs (kernels) once and reuse them on any system.
Since the VM used by Linux is highly integrated and customized for this operating system (OS) our solution uses uBPF instead. This eBPF VM supports a memory access verifier as well as Just in Time (JiT) compilation for X86 code. The use of uBPF can be extended such that any misbehavior of user submitted kernels is terminated at runtime or the changes are aborted after the execution.
Using these technologies a stable interface with vendor and host architecture agnosticity can be offered for offloading in our computational storage system.
Secondly, The use of userspace storage and filesystem libraries namely FUSE and SPDK prevents end users from having to install kernel modules or make any other kernel modifications.
Meanwhile the flexibility of SPDK still allows to use modern storage technologies and their releatively new APIs. In addition, the configuration flexibility of FUSE allows to support different use cases of end users.
Many filesystems and operating systems support filesystem extended attributes. These attributes, effectively key-value pairs, can be stored on arbitrary files and directories in the filesystem. By reserving specific keys to trigger specific behavior we can create computational storage filesystems that can separate regular and offloaded access.
Our solution goes one step further and also maintains the process identifier (PID) for any process setting these extended attributes. This allows to further separate regular and offloaded access across individual users on the same system.
Our third techology is the use of a log-structured filesytem (LFS), these filesystem do not support in-place updates instead all writes must go to the tail of a log. Most LFSs employ multiple logs, however. By updating the metadata of changes to files by changing the locations of specific blocks we can ensure that this file metadata representation can be immutable for the intended livetime.
As a result a LFS allows to implement a snapshot consistency model for files on the system. Precisely these snapshots can be safely shared across the host filesystem and computational storage device allowing both to operate concurrently.
However, this sharing can be difficult due to internal translations performed by flash storage devices known as flash translation layer (FTL). By utilizing zoned namespaces (ZNS) we can avoid these translations and have the device more transparently expose its behavior.
Within ZNS this is achieved by separating the device in zones and requiring that each zone is linearly written. Moreover, adhering to this linear write requirement is trivial thanks to the use of a LFS. Lastly, ZNS devices that entire zones are erased as a single unit.
The combination of technologies allows for high ease of use having minimized barrier to entree. Mainly, the ability to reuse kernels across systems and vendors as well as all technologies being run in userspace aid greatly in these regards.
All the while the solution is still capable of separating users and their intent with concurrent regular and offloaded access even to the same file.
Finally, the use of existing operating system APIs means this solution can be implemented on any operating system be it Windows, MacOs, Linux or FreeBSD.
]]>G – General (I + M + A + F + D)
These extensions are the instruction extensions as defined for the user-level ISA specification 2.2 [2]. In addition to this specification there are also the unprivileged ISA specification [3], privileged ISA Specification [4] and a debug specification [5]. All these extensions and specifications might seem excessive but they allow for great flexibility. This flexibility will allow to create low power tiny microprocessors with only integer arithmetic. As well as full desktop processors with vector extensions. Furthermore, even special designed processors such as secure processors with separate program and data memory are possible.
In a nutshell, assembly instructions perform read, write or arithmeti operations on either registers or memory addresses. These registers are locations inside the processors of a specific length to which can be read from or written to. Typically the available registers depends on the extensions implemented by the processor. One can think of registers as short term memory. Places to store temporary information so it can be written down (to memory) later. For the RV32I extensions there will be 32 individual 32bit long registers labeled x0 to x31. For Convenience many of these registers have specific names which indicates their purpose. In reality this is just for semantics and one is free to use any register for their desired purpose. Compilers, however, operate under the assumption that they can use certain registers consistently. To correctly identify return types for example.
The first instruction to consider is called addi this is short for add immediate. Just like functions these instructions take arguments where in the case of addi three arguments are expected. First is the register to store the immediate into, second is the first integer and third is the second integer. The addition of the first and second integer will be stored in the specified register. Consider the example shown below where the addition of 0 + 5 is stored in the a0 (x11) register.
addi a0, zero, 5
Since an integer is a 32bit value it can only represent a limited range of discrete numbers. When adding to sufficiently large integers the addition can no longer represent the entire discrete number and it overflows. In other ISAs such as X86 such an overflow will set a flag in specific register. Such flags allow the program to detect that an overflow has occurred. However, in RISC-V it is up to the individual implementation how to handle such overflows, divide by zero, carries and other arithmetic events that might occur when executing instructions. Of course just adding, subtracting or multiplying integers is only going to get us so far. Most programs rely on conditions where a certain set of instructions is only executed if the condition holds. With subsequent assembly instructions the equivalent pseudocode as it could be interrupted in a higher level language such as Java or C is shown beside it.
blt a0, a1, .conditional # if(a0 < a1) goto conditional;
Above is the branch less than instruction which jumps to the specified label
if the first parameter is less than the second parameter. The third argument is
the label to jump to. Labels are declared in assembly programs with specific
names so they can be jumped back to later. Efficient assembly code uses small
general purpose snippets of labeled code which can be reused many times. This
effectively reduces the size of the program although it can sometimes be at the
cost of performance. In addition to conditional jump instructions or branch
instructions are they are called in RISC-V. There is also the unconditional
jump. This instruction will always jump to the specified label unconditionally
and in RISC-V it is denoted simply as j
.
j .exit # goto exit;
Why labels are more comparable to goto than a function call is because of the assembly instruction call which performs the actual call to functions. Calls are more involved than jumps requiring the correct setup of stack frames and dealing with return data. For now, it is important to know that it exists but basic loops are covered first before dealing with proper functions and their corresponding stack frames.
The final instructions to consider before the first program can be analyzed are the load and store instructions. Known as lw and sw in RISC-V these mean load word and store word respectivelu. Read the following examples below but do not worry if it is not immediately clear how they operate.
sw a0, 0(sp) # store the contents of a0 in the address of sp
lw a0, 0(sp) # load the contents of the address sp into register a0
An important property of a register is that it can be used to store a address in memory. This is known as an reference, subsequently with such a reference we can point to specific addresses in memory. The lw and sw allow to store and load information from registers into these memory references. The example below will show the importance of reading and writing to memory although arguably the most complicated instruction shown yet.
addi sp, zero, 0xff # set the sp register to 0xff in hexadecimal
sw a0, 0(sp) # use the value 0xff from sp as an memory address to store a0
addi, a0, zero, zero # set register a0 to 0 losing its previous value
lw a0, 0(sp) # use the value 0xff from sp as memory address to restore a0
This covers all assembly instructions that will subsequently be used in the Ripes simulator. When using this simulator the execution pipeline and results on registers or memory locations can be viewed graphically.
There are multiple methods to install Ripes for most operating systems. By far the simplest method is to download the release from Github. The information in this post will be based on Ripes version 1.0.3 [6].
All the three executables the .exe, app and AppImage can be executed directly. As a result, the download location is up for the end user to decide and afterwards the file can be removed if desired. OSX users are entrusted to be familiar with allowing third party executables to be allowed to run. Similarly, Linux users are expected to know how to set file permissions to make files executable. Now start Ripes and lets get started.
![Ripes simulator processor view](/assets/images/risc-v-assembly/ripes-thumbnail-3.png) |
Ripes simulator processor view |
The first example will entail a basic for loop that will start at 5 and decrement until smaller than 0. At first a minimal assembly representation of this will be covered. Slowly other important aspects that also need to be handled in assembly will be covered.
#pseudo code
for(int i = 5; i > 0; i--) {}
# assembly equivalent
addi a0, zero, 5
addi a1, zero, 1
j .loop
.loop:
blt a0, a1, .exit_loop
addi a0, a0, -1
j .loop
.exit_loop:
...
Before starting to announce improvements lets analyze the assembly shown above.
Initially the value 5 is put into the a0
register following 1 being put into
a1
. Now a unconditional jump is performed to enter .loop
. Inside the loop
(a0 < a1)
is evaluated and if true a jump to .exit_loop
is performed.
Obviously 5 is not less than 1 so the execution continuous normally. Next a0
is decremented by one and the subsequent jump goes back to the start of the
loop. Now 4 is still not less than 1 but the looping pattern has become
apparent. When the value in a0
has become 0 it is less than 1 which is in the
a1
register and a jump to .exit_loop
will be performed. Below the operation
of this simple loop is visually demonstrated step-by-step, of course it is
encouraged to perform this stepping in Ripes yourself.
Inevitably a program at some point becomes so significantly complex that it
would be impossible to keep an overview without functions. Even in assembly the
concept of functions is well know, however, in assembly a programmer needs to
perform various tasks to ensure a call executes correctly. Before the next two
assembly instructions call
and ret
can be properly introduced, some
registers their special purpose has to be described. Lets take a look at the
registers sp(x2)
, ra(x1)
, s0(x8)
. Here the labels are no longer of
significant value so they will be omitted in the future. It no longer matters
that a0
is label x11 for example. Here sp
stand for stack pointer, ra
stands for return address and s0
or otherwise known as fp
stands for
frame pointer. The basic purposes of these registers will be explained.
However, for a more thorough guide on register conventions one is advised to
read Harry H. Porter III his guide
[7]
starting on page 146.
The return address (ra) is used to return from a call back to where the program was executing instructions before the call. In other architectures such as X86 the return address is stored in main memory. Using registers instead allows the executions of calls to be performed much faster and with lower overhead However, the storing or the return address in main memory can only be avoided if the function makes no subsequent calls to other functions.
The stack pointer (sp) reserves space for calls to store variables in main
memory. It must always be modified by multiples of 16 bytes and reserving space
must be done by decrementing it. This is very typical for most architectures
where variables on the stack and heap are directly opposite from one another.
This allows to effectively use the entirety of available memory without
variables on the stack overwriting variables on the heap. After the reserving
of space by decrementing sp
it must be subsequently incremented again by the
same amount before returning from the call.
The frame pointer (fp) is not always needed with every call, furthermore, it is likely that most calls won’t need to use the frame pointer. The frame pointer is used to determine offsets for calls that create variables with dynamic sizes. Additionally, a frame pointer is needed when using a large amount local variables due to a limitation in RISC-V on the maximum size of relative offsets. The dynamic use of the frame pointer is outside the scope of this tutorial, however, the value will correctly be incremented and stored in examples.
Stack frames are sized in 16 bytes chunks, naturally, this makes the smallest
possible stack frame 16 bytes large. A stack frame is constructed by
decrementing the current value of sp
and subsequently storing ra
and fp
in
main memory. The storing of ra
and fp
is done with a relative offset to
sp
. Finally fp
incremented by the size of the stack frame. When a stack
frame is destructed the original value of fp
is restored from main memory.
Followed by restoring the value of ra
. Finally, the value of sp
is
incremented back up to its original value. Below is an example of the
construction and destruction of the most basic stack frame.
# construction
addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
addi s0, sp, 16
# destruction
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
Before putting this all together and introducing the call
and ret
instructions. Consider the following stack frame that takes one argument and
returns this argument after performing an addition with itself. The argument
taken is a regular 32bit integer and all operations with this argument use the
a0
register. Upon careful inspection of the assembly instructions it can be
observed that many of the sw
and lw
instructions could be optimized away.
Partly this is the job of the compiler but also of the programmer. Writing
return num + num;
would have removed almost all of the sw
and lw
instructions shown below for example.
#pseudo code
int sum(int num) {
num += num;
return num;
}
# construction
addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
addi s0, sp, 16
# store first argument
sw a0, -12(s0)
# addition of a0 + a0
lw a0, -12(s0)
add a0, a0, a0
sw a0, -12(s0)
# destruction
# load first argument
lw a0, -12(s0)
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
The fundamental understanding of critical registers as well as the stack frames
allows to define complete functions which can be called in assembly. The call
instruction is similar to the jump in that it will jump to a label, however, it
expects the subsequent instructions to properly construct and destruct a stack
frame. When the destruction of the stack frame is completed the ret
instruction should be called. This instruction will ensure that execution
continues from where the call
was made. To demonstrate consider the same
example as previously but now with appropriate function calls.
int sum(int num) {
num += num;
return num;
}
int main(int argc, char** argv) {
return sum(5);
}
From main the sum function is called with the integer value 5 as it’s first
argument. For simplicity, let’s show the resulting assembly without the
construction and destruction of the main stack frame. Instead focus on how
call
and ret
are used to jump to parts of the assembly code.
sum(int):
addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
addi s0, sp, 16
sw a0, -12(s0)
lw a0, -12(s0)
add a0, a0, a0
sw a0, -12(s0)
lw a0, -12(s0)
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret
main:
...
addi a0, zero, 5
call sum(int)
...
ret
Before calling the sum function the literal 5 is stored in register a0
. This
is because a0
is the first argument by convention. Now call will jump to the
sum(int)
: label and start executing instructions from there. In sum it can be
seen that a0
is assumed to contain the value for the first argument because of
the sw a0, -12(s0)
instruction. Naturally, the following lw a0, -12(s0)
instruction is redundant and only placed by the compiler if the -O
flag is set
to zero. The keen eyed might notice the compiler has still managed to sneak one
optimization into the assembly as the return value after the call to sum is
assumed to still be in a0
when the main calls ret
.
When observing the resulting assembly in Ripes the call
and ret
instructions
no longer exist. This is because these instructions are converted by the
assembler depending on the addressing mode
[8].
The most common mode is called PC-relative and this uses relative offsets from
the program counter pc current address. This special register is only accessible
through specific instructions as is shown later.
With the first call
to sum the instruction is translated into two separate
instructions. These instructions are auipc x6, 0
and jalr x1, x6, 24
.
Firstly let’s translate the registers to the names we have used throughout this
post, these now become auipc t1, 0
and jalr ra, t1, 24
. Here auipc
stores
the pc
register in t1
with an additional offset of 0. The jalr
instruction
jumps to t1
+ 24 and stores this address + 4 in ra
. The +4 is needed so that
upon returning the jalr
instruction is not executed again. While the 24 is the
relative distance between the auipc
instruction and the start of .sum:
. The
special auipc
register allows to store the current value of the program
counter pc
in a general purpose register such as ra
.
The ret
calls are translated to jalr x0, x1, 0
since this will jump back to
the address stored in x1
/ ra
. The return value of jalr
is stored in the
unchangeable x0
register since there is no desire to modify any registers upon
returning.
Although, it is important to understand that the call
and ret
instructions
are actually translated into separate instructions that differ per memory model.
The underlying instructions used to facilitate these instructions are typically
not shown in a disassembly or while assembling.
As a final note, an example of the incredible optimizations compilers are able
to generate is shown. The same c code where the sum of 5 is computed in a
function is used but now the compiler -O0
flag is changed to -O3
.
sum(int):
slli a0, a0, 1
ret
main:
addi a0, zero, 10
ret
The compiler has managed to infer the computation the sum function is performing and optimize them away by moving 10 into a0 and returning. Meaning, it has completely optimized away any function calls, construction and destruction of any stack frames. However, it still left the label to the sum function and the operations it needs to perform. This is because the function could be referenced from other libraries and files that might want to call it. It should be noted that this optimization can only be performed because the value 5 is known at compile time. Should the value be retrieved as program argument this optimization would not have been possible.
Keep a look out for subsequent blog posts on RISC-V where working with the Maix bit and accompanying JTAG debugger will be covered. And maybe consider donating so I can buy one of those SiFive processors as well.
Now, lets say you are an computer scientists, developer or embedded systems engineer; how can you benefit from this new ISA and start using it? Do you need to buy dedicated hardware and what kind of options are there at which price? The answer is that no matter the field anyone can get started with RISC-V today. Using either simulators without investing any money or for less than 100$ with dedicated hardware. The RISC-V Foundation maintains a list of tools that are already supported including simulators [1{}}. Anyone could easily start using one of these simulators. This list includes 1) QEMU [2] 2) Renode [3] 3) Spike [4] 4) Ripes ]5], a variety of simulators for different purposes. These simulators are ordered from higher level simulators to lower. Here Ripes is so low level it allows to graphically show the instruction pipeline. Contrarily, QEMU allows emulating a Linux operating systems with the RISC-V architecture.
Debugging on a Kendryte K210 RISC-V dual core microprocessor using OpenOCD and GDB.
Before considering which simulator would best fit with any expectations and desires let´s first show some affordable hardware considerations. Once again the RISC-V foundation maintains a list of ready to buy products based on their ISA [6]. The list is extensive but should be filtered on soc’s only as FPGA soft-cores are likely to be outside the scope of interest. Still the price range of the soc’s offered is large ,therefor the following three options are likely best candidates:
![](/assets/images/kendryte-k210/k210.jpg) | ![](/assets/images/gapuino/gapuino.png) | ![](/assets/images/hifive1/hifive1.jpg) |
Maix Bit – 13$ | GAPuino – 100$ | HiFive1 – 59$ |
All of these solutions offer a USB interface for serial communication and uploading applications. However, not all of them contain an onboard debugger. So for the Maix Bit a JTAG debugger is needed to perform step-by-step debugging with GDB. By far the best solution of these three boards is still the Maix bit which can be bought on SeedStudio for just 13$. This is the only option of the three offering a 64bit dual-core processor with an additional AI core. When making the decision to buy the Maix Bit an accompanying debugger should also be purchased. Unfortunately, a debugger that officially works for all three devices does not exist. However, the debugger offered with the Maix Bit is based on the FTDI FT2232D which should allow it to work with both the GAPuino and HiFive1 as well.
In order to use these simulators or microprocessors tools are needed. The installation of all tools required is covered regardless of the subsequently chosen platform. Because of this both 32 and 64 bit compilers for both native ELF and Linux ELF binaries are covered. To keep the installation simple and explain the tools later were necessary a simple step by step list of commands is provided. Upon the successful completion of all these commands all required tools for any subsequent platform will be installed. An attempt is made to cover the prerequisites for the most common Linux distributions. Windows will not be supported and on OSX homebrew [7] will be required. Install the prerequisites below based on your operating systems before continuing with building the RISC-V toolchain.
Ubuntu | Fedora / CentOS / RHEL | OSX |
sudo apt-get install autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-de |
sudo yum install autoconf automake libmpc-devel mpfr-devel gmp-devel gawk bison flex texinfo patchutils gcc gcc-c++ zlib-devel expat-devel |
brew install gawk gnu-sed gmp mpfr libmpc isl zlib expat |
The riscv-gnu-toolchain will download around 250 megabytes of data and build two independent complete toolchains in the /opt/riscv directory. One of these toolchains can be used to compile ELF binaries that can be executed directly on simulators or microprocessors. The other toolchain is used to compile ELF binaries that can be executed by a running Linux kernel. The total install size will be around 2 gigabytes.
git clone --recursive https://github.com/riscv/riscv-gnu-toolchain
cd riscv-gnu-toolchain
sudo mkdir /opt/riscv
sudo chown $USER /opt/riscv
./configure --prefix=/opt/riscv
make -j $(nproc)
unset LD_LIBRARY_PATH
./configure --prefix=/opt/riscv --enable-multilib
make linux -j $(nproc)
In subsequent blog posts using several of the simulators as well as working with the Maix Bit and accompanied debugger will be covered. Some of the simulators will be used to look at assembly instructions and how they are executed. Others will look at simple hello world examples written in C++. The post about the Maix Bit will cover the use of the Kendryte standalone SDK. In preparation for these posts one can buy both the Maix Bit and debugger from SeedStudio using the following links: Maix Bit, RV Debugger
References
]]>__init__.py
has been omitted):
The problem from this structure arises as the same instance of
declarative_base
needs to be imported by both example1.py
and example2.py
.
While at the same time the database_manager.py
would create the database with
all its associated tables & relationships. The simplest solution is for the
database_manager.py
to have the instance declared and all the models to import
it. However, this is not possible since all the models need to be imported as
well. this is necessary as the database would otherwise be created without the
tables & relationships defined by these models. Further complication this
construction is that blind imports of the models without calling any method or
accessing an attribute would trigger a pep8 unused import.
To resolve this two additional files are defined. The first file allows the
definition of a declarative_base
in a separate file. While the second file
allows for the dynamic importing of models in the models directory. The
resulting directory structure looks as follows:
In the declarative_base.py the shared instance of declarative_base
is
instantiated. Additionally this file can be used to define a base class for all
models to inherit automatically.
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative import declared_attr
class Base(object):
"""Base object all sqlalchemy models will extend"""
@declared_attr
def __tablename__(cls):
"""Generate tablename based on class name
Setting the model its __tablename__ attribute will override this
generated name.
"""
return cls.__name__.lower()
base = declarative_base(cls=Base)
Let’s cover create_database.py
first as this is the most extensive file, it
covers the automatic detection of files and classes inside the models directory.
It could be adapted to perform the same or similar automatic detection in
various other scenario’s easily. The file contains four methods starting with
list_model_names
. This method will return a collection of filenames inside the
models directory.
import os
import pkgutil
# relative directory for models from this file
models_rel_directory = '/models'
def _list_model_names():
"""Gather collection of model names from models modules
:return: A collection of module names iterated from the models directory
"""
model_names = []
# import everything from models directory relative to this file
package_path = os.path.dirname(
os.path.abspath(__file__)) + models_rel_directory
# iterate over all module files and append their names to the list
for __, modname, ispkg in pkgutil.iter_modules(path=[package_path]):
model_names.append(modname)
return model_names
In list_model_names
the path relative to the current file is used to discover
the models directory. This directory is subsequently used by pkgutil
to find
all the modules in this directory. Here every file that is not __init__.py
will be added to the collection. Note that at this point the collection contains
elements of type str and the actual modules are not yet imported.
The next method is import_models
this method will actually import the modules.
As argument it takes the collection from list_model_names
. In addition it uses
the importlib
library and works under the assumption the model file names are
similar to their class names.
import importlib
model_absolute_path = 'database.models.'
def _import_models(module_names):
"""Gather collection of tuples with modules and associated classes
:return: A collection of tuples with the module and its associated class
"""
imported_modules = []
# for every module names import it and check that it has a class similar
# to the name of the module. Example: if the file is named account it will
# look for the Account class. If the file is called account_types it will
# look for the AccountTypes class.
for modname in module_names:
# Capitalize first letters and subsequently remove all underscores.
class_name = modname.title().replace('_', '')
mod = importlib.import_module(model_absolute_path + modname)
if not hasattr(mod, class_name):
msg = "The module '" + model_absolute_path + ".%s' should have a" \
"'%s' class similar to the module name." % \
(modname, class_name)
raise AttributeError(msg)
else:
imported_modules.append((mod, class_name))
return imported_modules
For every str from the passed collection it will try to import it using the
specified model_absolute_path
. It will automatically determine the class name
as long as it follows the pattern as clearly defined in the comment. If the model
file is named account_types
than the class must be named AccountTypes
and so
on. Only if the associated class for the module could be found will it be added
to the collection. The collection itself consists of tuples with the loaded
module and the name of the class.
Now for the method that brings most of these methods together called
list_tables
. This method will create a collection of all models their
__table__
objects. These objects can be passed during the creation of a
database when using a declarative_base
instance. By accessing these properties
and using it in the creation of a database having unused imports is prevented.
def _list_tables():
"""Collection of all the sqlalchemy model __table__ objects
:return: A Collection of ``__table__`` objects from sqlalchemy models.
"""
tables = list()
model_names = _list_model_names()
imported_modules = _import_models(model_names)
for module in imported_modules:
# Access the modules class and subsequent __table__
tables.append(getattr(module[0], module[1]).__table__)
return tables
The list_tables
method uses getattr
to access the module its class by string
subsequently accessing the class its __table__
object. This is shown in:
tables.append(getattr(module[0], module[1]).__table__)
.
The final method create_database_tables
performs the actual creation of all
tables & relationships by using the shared declarative_base. Any type of
sqlalchemy
engine can be passed to this method and in this example the
database_manager.py
is expected to call it.
def create_database_tables(engine):
"""Creates the database table using the specified engine"""
tables = _list_tables()
base.metadata.create_all(bind=engine, tables=tables)
Finally let’s put these four methods together into one large file.
# -*- encoding: utf-8 -*-
# Copyright (c) 2019 Dantali0n
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import importlib
import os
import pkgutil
from database.declarative_base import base
# relative directory for models from this file
models_rel_directory = '/models'
# absolute path to models for imports
model_absolute_path = 'database.models.'
def create_database_tables(engine):
"""Creates the database table using the specified engine"""
tables = _list_tables()
base.metadata.create_all(bind=engine, tables=tables)
def _list_tables():
"""Collection of all the sqlalchemy model __table__ objects
:return: A Collection of ``__table__`` objects from sqlalchemy models.
"""
tables = list()
model_names = _list_model_names()
imported_modules = _import_models(model_names)
for module in imported_modules:
# Access the modules class and subsequent __table__
tables.append(getattr(module[0], module[1]).__table__)
return tables
def _list_model_names():
"""Gather collection of model names from models modules
:return: A collection of module names iterated from the models directory
"""
model_names = []
# import everything from models directory relative to this file
package_path = os.path.dirname(
os.path.abspath(__file__)) + models_rel_directory
# iterate over all module files and append their names to the list
for __, modname, ispkg in pkgutil.iter_modules(path=[package_path]):
model_names.append(modname)
return model_names
def _import_models(module_names):
"""Gather collection of tuples with modules and associated classes
:return: A collection of tuples with the module and its associated class
"""
imported_modules = []
# for every module names import it and check that it has a class similar
# to the name of the module. Example: if the file is named account it will
# look for the Account class. If the file is called account_types it will
# look for the AccountTypes class.
for modname in module_names:
# Capitalize first letters and subsequently remove all underscores.
class_name = modname.title().replace('_', '')
mod = importlib.import_module(model_absolute_path + modname)
if not hasattr(mod, class_name):
msg = "The module '" + model_absolute_path + ".%s' should have a" \
"'%s' class similar to the module name." % \
(modname, class_name)
raise AttributeError(msg)
else:
imported_modules.append((mod, class_name))
return imported_modules
sudo -s
ubiquity -b
sudo fdisk -l
. The partitions
are assumed to be on mmcblk0 but they could also be on mmcblk1.sudo mount /dev/mmcblk0p2 /mnt
were_ mmcblk0p2_ is the root
partition.sudo mkdir /mnt/boot/efi
sudo mount /dev/mmcblk0p1 /mnt/boot/efi
mmcblk0p1 is the efi
partitionfor i in /dev /dev/pts /proc /sys; do sudo mount -B $i /mnt$i; done
modprobe efivars
efibootmgr --verbose
apt-get install --reinstall grub-efi-amd64
grub-install —no-nvram —root-directory=/mnt
chroot /mnt
update-grub
`sudo -s
apt update && apt upgrade
update-grub
apt install grub-efi-amd64
ps aux | grep apt
and using the PID to call kill PIDdpkg --purge --force-all grub-efi-amd64
update-grub
An alternative to these vendor technologies is Xorg TwinView. Unfortunately TwinView requires a convoluted configuration procedure by writing Xorg config files. These configuration files are read during the start up of the graphical environment making it impossible to changes to the monitor setup once the system is running.
None of these clunky solutions are necessary anymore with recent versions of Xrandr. When using Xrandr multiple monitors can be combined into a single so called virtual display. Subsequently these virtual displays can be named and have many of their properties adjusted.
Consider a system having two HDMI devices labeled HDMI-A-0 and HDMI-A-1. These can be combined into one virtual display with the command:
xrandr --setmonitor NameOfDisplay auto HDMI-A-0,HDMI-A-1
The auto parameter will ensure that Xrandr will attempt to automatically combine
the monitors into the most logical virtual display. Alternative the geometry
can be supplied by using the formula <w>/<mmw>x<h>/<mmh>+<x>+<y>
were w and h
are the width and height of the virtual display. x and y are the initial
positions of the virtual display (displays start in the left top corner) and
mmw and mmh are the physical width and height of the monitor in millimeters.
All parameters are required and none are optional.
As an additional example consider a multi-monitor setup with 3 displays called
HDMI-A-0, DVI-D-0 and DisplayPort-0. Now on HDMI-A-0 a new 4k tv configured for
3840×2160 resolution is connected while on DisplayPort-0 and DVI-D-0 are 1080p
displays. Say the two 1080p displays need to display the upper area of the 4k
tv side-by-side, how would this be accomplished with xrandr –setmonitor
?
xrandr --setmonitor OverlayDisplay 3840/476x2160/268+0+0 HDMI-A-0,DisplayPort-0,DVI-D-0