Posts Tagged ‘raspbian’
Erlang on the Raspberry Pi
Introduction
Erlang is a rather interesting programming language with a number of rather unique properties. If you are used to procedural languages like C or its many variants, then Erlang will appear very different. Erlang was originally developed by Ericsson to program telephone switches. The name is either based on Ericsson Language or is named after Agner Krarup Erlang depending on who you ask. In Erlang there are no loops, all loops are accomplished via recursion. Similarly once a variable is assigned, it can never be changed (is immutable). A lot of the function execution is controlled via pattern matching. Beyond the basic language the Erlang system is used to create highly available, scalable fault tolerant systems. You can even swap code on the fly without stopping the system. Erlang is still used in many telephony type applications, but it is also used to create highly reliable and scalable web sites. The best known being WhatsApp. Plus Facebook, Yahoo and many others have services implemented in Erlang serving massive numbers of users.
In this article we’ll begin to look at the basic language and consider an implementation of our flashing LED program for the Raspberry Pi implemented in pure Erlang. ERLang runs on most operating systems and is open source now. It is easy to add to the Raspberry Pi and is a very good system if you want to make use of a cluster of Raspberry Pis.
How to Run the Program
Erlang doesn’t come pre-installed with Raspbian, but it’s easy to add with the command:
sudo apt-get install erlang
After this completes you are good to go.
Erlang includes a terminal based command system where you can compile modules and call functions. To run the programs included here, you need to save the first file as lights.erl and the second as gpio.erl. Then you can compile and execute the program as indicated in the screenshot of my terminal window below:
Some things to note:
- Erlang is case sensitive and case has meaning. For instance variables start with a capital letter and functions with a lowercase letter.
- Punctuation is very important. The periods end a statement and in the erl shell will cause it to execute. If you miss the period, nothing will happen until you enter one (it assumes you have more text to enter). Similarly inside functions , and ; have a specific meaning that affects how things run.
- You do everything by calling functions both in the shell and in the Erlang language. So the c() function compiles a module (produces a .beam file). The q() function exits the shell after terminating all programs. lights:flashinglights() is our exported entry point function to run the program with. You can also call things like ls() to get a directory listing or cd() to change directories or pwd() to find out where you are. Remember to enter any lines with a period to terminate the line.
- To access the gpio /sys files, erl must be run from sudo. You could fix the file system permissions, but this seems easy enough.
Flashing LED Program
Below is my main module to flash the lights. Unlike the C or Fortran version of this program, there is no loop, since loops don’t exist in Erlang. Instead it uses recursion to accomplish the same things. (Actually in the Erlang tutorials there are design patterns on how to accomplish while or for loops with recursion). Notice that once a variable is assigned it can never be changed. But you accomplish the same thing with recursion by passing a modified version of the variable into a function. Similarly you can preserve variables using function closures, but I don’t do that here. I included edoc comments which are Erlang version of JavaDoc. Otherwise this is just intended to give a flavour for the language without going into too much detail.
%% @author Stephen Smith %% @copyright 2018 Stephen Smith %% @version 1.0.0 %% @doc %% A erlang implementation of my flashing lights program %% for the Raspberry Pi. %% @end -module(lights). -export([flashlights/0]). -author('Stephen Smith'). flashlights() -> Leds = init(), flash(Leds, 10). init() -> L0 = gpio:init(17, out), L1 = gpio:init(27, out), L2 = gpio:init(22, out), {L0, L1, L2}. flash(Leds, Times) when Times > 0 -> gpio:write(element(1, Leds), 1), timer:sleep(200), gpio:write(element(1, Leds), 0), gpio:write(element(2, Leds), 1), timer:sleep(200), gpio:write(element(2, Leds), 0), gpio:write(element(3, Leds), 1), timer:sleep(200), gpio:write(element(3, Leds), 0), flash(Leds, Times-1); flash(Leds, Times) when Times =< 0 -> true.
Erlang GPIO Library
Rather than write the file access library for the GPIO drivers myself, doing a quick Google search revealed several existing ones including this one from Paolo Oliveira.
%% @author Paolo Oliveira <paolo@fisica.ufc.br> %% @copyright 2015-2016 Paolo Oliveira (license MIT) %% @version 1.0.0 %% @doc %% A simple, pure erlang implementation of a module for %% <b>Raspberry Pi's General Purpose %% Input/Output</b> (GPIO), using the standard Linux kernel %% interface for user-space, sysfs, %% available at <b>/sys/class/gpio/</b>. %% @end -module(gpio). -export([init/1, init/2, handler/2, read/1, write/2, stop/1]). -author('Paolo Oliveira <paolo@fisica.ufc.br>'). %% API % @doc: Initialize a Pin as input or output. init(Pin, Direction) -> Ref = configure(Pin, Direction), Pid = spawn(?MODULE, handler, [Ref, Pin]), Pid. % @doc: A shortcut to initialize a Pin as output. init(Pin) -> init(Pin, out). % @doc: Stop using and release the Pin referenced as file descriptor Ref. stop(Ref) -> Ref ! stop, ok. % @doc: Read from an initialized Pin referenced as the file descriptor Ref. read(Ref) -> Ref ! {recv, self()}, receive Msg -> Msg end. % @doc: Write value Val to an initialized Pin referenced as the file descriptor Ref. write(Ref, Val) -> Ref ! {send, Val}, ok. %% Internals configure(Pin, Direction) -> DirectionFile = "/sys/class/gpio/gpio" ++ integer_to_list(Pin) ++ "/direction", % Export the GPIO pin {ok, RefExport} = file:open("/sys/class/gpio/export", [write]), file:write(RefExport, integer_to_list(Pin)), file:close(RefExport), % It can take a moment for the GPIO pin file to be created. case filelib:is_file(DirectionFile) of true -> ok; false -> receive after 1000 -> ok end end, {ok, RefDirection} = file:open(DirectionFile, [write]), case Direction of in -> file:write(RefDirection, "in"); out -> file:write(RefDirection, "out") end, file:close(RefDirection), {ok, RefVal} = file:open("/sys/class/gpio/gpio" ++ integer_to_list(Pin) ++ "/value", [read, write]), RefVal. release(Pin) -> {ok, RefUnexport} = file:open("/sys/class/gpio/unexport", [write]), file:write(RefUnexport, integer_to_list(Pin)), file:close(RefUnexport). % @doc: Message passing interface, should not be used directly, it is present for debugging purpose. handler(Ref, Pin) -> receive {send, Val} -> file:position(Ref, 0), file:write(Ref, integer_to_list(Val)), handler(Ref, Pin); {recv, From} -> file:position(Ref, 0), {ok, Data} = file:read(Ref, 1), From ! Data, handler(Ref, Pin); stop -> file:close(Ref), release(Pin), ok end. %% End of Module.
Summary
Erlang is a very interesting language. If you are interested in functional programming or how to create highly scalable reliable web servers, then Erlang is definitely worth checking out. We only looked at a quick introduction to the language, really by example. There are lots of good documentation, tutorials and samples just a Google search away. Perhaps in a future article we’ll look at processes and messages and how to build such a highly scalable server.
Flashing LEDs in Assembler
Introduction
Previously I wrote an article on an introduction to Assembler programming on the Raspberry Pi. This was quite a long article without much of a coding example, so I wanted to produce an Assembler language version of the little program I did in Python, Scratch, Fortran and C to flash three LEDs attached to the Raspberry Pi’s GPIO port on a breadboard. So in this article I’ll introduce that program.
This program is fairly minimal. It doesn’t do any error checking, but it does work. I don’t use any external libraries, and only make calls to Linux (Raspbian) via software interrupts (SVC 0). I implemented a minimal GPIO library using Assembler Macros along with the necessary file I/O and sleep Linux system calls. There probably aren’t enough comments in the code, but at this point it is fairly small and the macros help to modularize and explain things.
Main Program
Here is the main program, that probably doesn’t look structurally that different than the C code, since the macro names roughly match up to those in the GPIO library the C function called. The main bit of Assembler code here is to do the loop through flashing the lights 10 times. This is pretty straight forward, just load 10 into register r6 and then decrement it until it hits zero.
@ @ Assembler program to flash three LEDs connected to the @ Raspberry Pi GPIO port. @ @ r6 - loop variable to flash lights 10 times @ .include "gpiomacros.s" .global _start @ Provide program starting address to linker _start: GPIOExport pin17 GPIOExport pin27 GPIOExport pin22 nanoSleep GPIODirectionOut pin17 GPIODirectionOut pin27 GPIODirectionOut pin22 @ setup a loop counter for 10 iterations mov r6, #10 loop: GPIOWrite pin17, high nanoSleep GPIOWrite pin17, low GPIOWrite pin27, high nanoSleep GPIOWrite pin27, low GPIOWrite pin22, high nanoSleep GPIOWrite pin22, low @decrement loop counter and see if we loop subs r6, #1 @ Subtract 1 from loop register setting status register bne loop @ If we haven't counted down to 0 then loop _end: mov R0, #0 @ Use 0 return code lsl R0, #2 @ Shift R0 left by 2 bits (ie multiply by 4) mov R7, #1 @ Service command code 1 terminates this program svc 0 @ Linus command to terminate program pin17: .asciz "17" pin27: .asciz "27" pin22: .asciz "22" low: .asciz "0" high: .asciz "1"
GPIO and Linux Macros
Now the real guts of the program are in the Assembler macros. Again it isn’t too bad. We use the Linux service calls to open, write, flush and close the GPIO device files in /sys/class/gpio. Similarly nanosleep is also a Linux service call for a high resolution timer. Note that ARM doesn’t have memory to memory or operations on memory type instructions, so to do anything we need to load it into a register, process it and write it back out. Hence to copy the pin number to the file name we load the two pin characters and store them to the file name memory area. Hard coding the offset for this as 20 isn’t great, we could have used a .equ directive, or better yet implemented a string scan, but for quick and dirty this is fine. Similarly we only implemented the parameters we really needed and ignored anything else. We’ll leave it as an exercise to the reader to flush these out more. Note that when we copy the first byte of the pin number, we include a #1 on the end of the ldrb and strb instructions, this will do a post increment by one on the index register that holds the memory location. This means the ARM is really very efficient in accessing arrays (even without using Neon) we combine the array read/write with the index increment all in one instruction.
If you are wondering how you find the Linux service calls, you look in /usr/include/arm-linux-gnueabihf/asm/unistd.h. This C include file has all the function numbers for the Linux system calls. Then you Google the call for its parameters and they go in order in registers r0, r1, …, r6, with the return code coming back in r0.
@ Various macros to access the GPIO pins @ on the Raspberry Pi. @ R5 is used for the file descriptor .macro openFile fileName ldr r0, =\fileName mov r1, #01 @ O_WRONLY mov r7, #5 @ 5 is system call number for open svc 0 .endm .macro writeFile buffer, length mov r0, r5 @ file descriptor ldr r1, =\buffer mov r2, #\length mov r7, #4 @ 4 is write svc 0 .endm .macro flushClose @fsync syscall mov r0, r5 mov r7, #118 @ 118 is flush svc 0 @close syscall mov r0, r5 mov r7, #6 @ 6 is close svc 0 .endm @ Macro nanoSleep to sleep .1 second @ Calls Linux nanosleep entry point which is function 162. @ Pass a reference to a timespec in both r0 and r1 @ First is input time to sleep in seconds and nanoseconds. @ Second is time left to sleep if interrupted (which we ignore) .macro nanoSleep ldr r0, =timespecsec ldr r1, =timespecsec mov r7, #162 @ 162 is nanosleep svc 0 .endm .macro GPIOExport pin openFile gpioexp mov r5, r0 @ save the file descriptor writeFile \pin, 2 flushClose .endm .macro GPIODirectionOut pin @ copy pin into filename pattern ldr r1, =\pin ldr r2, =gpiopinfile add r2, #20 ldrb r3, [r1], #1 @ load pin and post increment strb r3, [r2], #1 @ store to filename and post increment ldrb r3, [r1] strb r3, [r2] openFile gpiopinfile writeFile outstr, 3 flushClose .endm .macro GPIOWrite pin, value @ copy pin into filename pattern ldr r1, =\pin ldr r2, =gpiovaluefile add r2, #20 ldrb r3, [r1], #1 @ load pin and post increment strb r3, [r2], #1 @ store to filename and post increment ldrb r3, [r1] strb r3, [r2] openFile gpiovaluefile writeFile \value, 1 flushClose .endm .data timespecsec: .word 0 timespecnano: .word 100000000 gpioexp: .asciz "/sys/class/gpio/export" gpiopinfile: .asciz "/sys/class/gpio/gpioxx/direction" gpiovaluefile: .asciz "/sys/class/gpio/gpioxx/value" outstr: .asciz "out" .align 2 @ save users of this file having to do this. .text
Makefile
Here is a simple makefile for the project if you name the files as indicated. Again note that WordPress and Google Docs may mess up white space and quote characters so these might need to be fixed if you copy/paste.
model: model.o ld -o model model.o model.o: model.s gpiomacros.s as -ggdb3 -o model.o model.s clean: rm model model.o
IDE or Not to IDE
People often do Assembler language development in an IDE like Code::Blocks. Code::Blocks doesn’t support Assembler language projects, but you can add Assembler language files to C projects. This is a pretty common way to do development since you want to do more programming in a higher level language like C. This way you also get full use of the C runtime. I didn’t do this, I just used a text editor, make and gdb (command line). This way the above program has no extra overhead the executable is quite small since there is no C runtime or any other library linked to it. The debug version of the executable is only 2904 bytes long and non debug is 2376 bytes. Of course if I really wanted to reduce executable size, I could have used function calls rather than Assembler macros as the macros duplicate the code everywhere they are used.
Summary
Assembler language programming is kind of fun. But I don’t think I would want to do too large a project this way. Hats off to the early personal computer programmers who wrote spreadsheet programs, word processors and games entirely in Assembler. Certainly writing a few Assembler programs gives you a really good understanding of how the underlying computer hardware works and what sort of things your computer can do really efficiently. You could even consider adding compiler optimizations for your processor to GCC, after all compiler code generation has a huge effect on your computer’s performance.
Raspberry Pi Assembly Programming
Introduction
Most University Computer Science programs now do most of their instruction in quite high level languages like Java which don’t require you to need to know much about the underlying computer architecture. I think this tends to create quite large gaps in understanding which can lead to quite bad program design mistakes. Learning to program C puts you closer to the metal since you need to know more about memory and low level storage, but nothing really teaches you how the underlying computer works than doing some Assembler Language Programming, The Raspbian operating system comes with the GNU Assembler pre-installed, so you have everything you need to try Assembler programming right out of the gate. Learning a bit of Assembler teaches you how the Raspberry Pi’s ARM processor works, how a modern RISC architecture processes instructions and how work is divided by the ARM CPU and the various co-processors which are included on the same chip.
A Bit of History
The ARM processor was originally developed to be a low cost processor for the British educational computer, the Acorn. The developers of the ARM felt they had something and negotiated a split into a separate company selling CPU designs to hardware manufacturers. Their first big sale was to Apple to provide a CPU for Apple’s first PDA the Newton. Their first big success was the inclusion of their chip design in Apple’s iPods. Along the way many chip makers like TI which had given up competing on CPUs built single chip computers around the ARM. These ended up being included in about every cell phone including those from Nokia and Apple. Nowadays pretty up every Android phone is also built around ARM designs.
ARM Assembler Instructions
There have been a lot of ARM designs from early simple 16 bit processors up through 32 bit processors to the current 64bit designs. In this article I’m just going to consider the ARM processor in the Raspberry Pi, this processor is a 64 bit processor, but Raspbian is still a 32 bit operating system, so I’ll just be talking about the 32 bit processing used here (and I’ll ignore the 16 bit “thumb” instructions for now).
ARM is a RISC processor which means the idea is that it executes very simple instructions very quickly. The idea is to keep the main processor simple to reduce complexity and power usage. Each instruction is 32 bits long, so the processor doesn’t need to think about how much to increment the program counter for each instruction. Interestingly nearly all the instructions are in the same format, where you can control whether it sets the status bits, can test the status bits on whether to do anything and can have four register parameters (or 2 registers and an immediate constant). One of the registers can also have a shift operation applied. So how is all this packed into on 32 bit instruction? There are 16 registers in the ARM CPU (these include the program counter, link return register and the stack pointer. There is also the status register that can’t be used as a general purpose register. This means it takes 4 bits to specify a register, so specifying 4 registers takes 16 bits out of the instruction.
Below are the formats for the main types of ARM instructions.
Now we break out the data processing instructions in more detail since these comprise quite a large set of instructions and are the ones we use the most.
Although RISC means a small set of simple instructions, we see that by cleverly using every bit in those 32 bits for an instruction that we can pack quite a bit of information.
Since the ARM is a 32-Bit processor meaning among other things that it can address a 32-Bit address space this does lead to some interesting questions:
- The processor is 32-Bits but immediate constants are 16-Bits. How do we load arbitrary 32-Bit quantities? Actually the immediate instruction is 12-Bits of data and 4 Bits of a shift amount. So we can load 12 Bits and then shift it into position. If this works great. If not we have to be tricky by loading and adding two quantities.
- Since the total instruction is 32-Bits how do we load a 32-Bit memory address? The answer is that we can’t. However we can use the same tricks indicated in number 1. Plus you can use the program counter. You can add an immediate constant to the program counter. The assembler often does this when assembling load instructions. Note that since the ARM is pipelined the program counter tends to be a couple of instructions ahead of the current one, so this has to properly be taken into account.
- Why is there the ability to conditionally execute all the data processing instructions? Since the ARM processor is pipelines, doing a branch instruction is quite expensive since the pipeline needs to be discarded and then reloaded at the new execution point. Having individual instructions conditionally execute can save quite a few branch instructions leading to faster execution. (As long as your compiler is good at generating RISC type code or you are hand coding in Assembler).
- The ARM processor has a multiply instruction, but no divide instruction? This seems strange and unbalanced. Multiply (and multiply and add) are recent additions to the ARM processor. Divide is still considered too complicated and slow, plus is it really used that much? You can do divisions on either the floating point coprocessor or the Neon SIMD coprocessor.
A Very Small Example
Assembler listings tend to be quite long, so as a minimal set let’s start with a program that just exits when run returning 17 to the command line (you can see this if you type echo $@ after running it). Basically we have an assembler directive to define the entry point as a global. We move the immediate value 17 to register R0. Shift it left by 2 bits (Linux expects this). Move 1 to register R7 which is the Linux command code to exit the program and then call service zero which is the Linux operating system. All calls to Linux use this pattern. Set some parameters in registers and then call “svc 0” to do the call. You can open files, read user input, write to the console, all sorts of things this way.
.global _start @ Provide program starting address to linker _start: mov R0, #17 @ Use 17 for test example lsl R0, #2 @ Shift R0 left by 2 bits (ie multiply by 4) mov R7, #1 @ Service command code 1 terminates this program svc 0 @ Linux command to terminate program
Here is a simple makefile to compile and link the preceding program. If you save the above as model.s and then the makefile as makefile then you can compile the program by typing “make” and run it by typing “./model”. as is the GNU-Assembler and ld is the linker/loader.
model: model.o ld -o model model.o model.o: model.s as -g -o model.o model.s clean: rm model model.o
Note that make is very particular about whitespace. It must be a true “tab” character before the commands to execute. If Google Docs or WordPress changes this, then you will need to change it back. Unfortunately word processors have a bad habit of introducing syntax errors to program listings by changing whitespace, quotes and underlines to typographic characters that compilers (and assemblers) don’t recognize.
Summary
Although these days you can do most things with C or a higher level language, Assembler still has its place in certain applications that require performance or detailed hardware interaction. For instance perhaps tuning the numpy Python library to use the Neon coprocessor for faster operation of vector type operations. I do still think that every programmer should spend some time playing with Assembler language so they understand better how the underlying processor and architecture they use day to day really works. The Raspberry Pi offers and excellent environment to do this with the good GNU Macro Assembler, the modern ARM RISC architecture and the various on-chip co-processors.
Just the Assembler introductory material has gotten fairly long, so we won’t get to an assembler version of our flashing LED program. But perhaps in a future article.
Playing with my Raspberry Pi
Introduction
I do most of my work (like writing this blog posting) on my MacBook Air laptop. I used to have a good desktop computer for running various longer running processes or playing games. Last year the desktop packed it in (it was getting old anyway), so since then I’ve just been using my laptop. I wondered if I should get another desktop and run Ubuntu on it, since that is good for machine learning, but I wondered if it was worth price. Meanwhile I was intrigued with everything I see people doing with Raspberry Pi’s. So I figured why not just get a Raspberry Pi and see if I can do the same things with it as I did with my desktop. Plus I thought it would be fun to learn about the Pi and that it would be a good toy to play with.
Setup
Since I’m new to the Raspberry Pi, I figured the best way to get started was to order one of the starter kits. This way I’d be able to get up and running quicker and get everything I needed in one shot. I had a credit with Amazon, so I ordered one of the Canakits from there. It included the Raspberry Pi 3, a microSD card with Raspbian Linux, a case, a power supply, an electronics breadboard, some leds and resistors, heat sinks and an HDMI cable. Then I needed to supply a monitor, a USB keyboard and a USB mouse (which I had lying around).
Setting up was quite easy, though the quick setup instructions were missing a few steps like what to do with the heatsinks (which was obvious) or how to connect the breadboard. Setup was really just install the Raspberry Pi motherboard in the case, add the heat sinks, insert the microSD card and then connect the various cables.
As soon as I powered it on, it displayed an operating system selection and installation menu (with only one choice), so clicked install and 10 minutes later I was logged in and running Raspbian.
The quick setup guide then recommends you set your locale and change the default password, but they don’t tell you the existing password, which a quick Google reveals as “Raspberry”. Then I connected to our Wifi network and I was up and running. I could browse the Internet using Chromium, I could run Mathematica (a free Raspberry version comes pre-installed), run a Linux terminal session. All rather painless and fairly straight forward.
I was quite impressed how quickly it went and how powerful a computer I had up and running costing less than $100 (for everything) and how easy the installation and setup process was.
Software
I was extremely pleased with how much software the Raspberry Pi came with pre-installed. This was all on the provided 32Gig card, which with a few extra things installed, I still have 28Gig free. Amazingly compact. Some of the pre-installed software includes:
- Mathematica. Great for Math students and to promote Mathematica. Runs from the Wolfram Language which is interesting in itself.
- Python 2 and 3 (more on the pain of having Python 2 later).
- LibreOffice. A full MS Office like suite of programs.
- Lots of accessories like file manager, calculator, image viewer, etc.
- Chromium web browser.
- Two Java IDEs.
- Sonic Pi music synthesizer.
- Terminal command prompt.
- Minecraft and some Python games.
- Scratch programming environment.
Plus there is an add/remove software program where you can easily add many more open source Pi programs. You can also use the Linux apt-get command to get many other pre-compiled packages.
Generally I would say this is a very complete set of software for any student, hobbyist or even office worker.
Python
I use Python as my main goto programming language these days and generally I use a number of scientific and machine learning libraries. So I tried installing these. Usually I just use pip3 and away things go (at least on my Mac). However doing this caused pip3 to download the C++/Fortran source code and to try to compile it, which failed. I then Googled around on how to best install these packages.
Unfortunately most of the Google results were how to do this for Python 2, which I didn’t want. It will be so nice when Python 2 finally is discontinued and stops confusing everything. I wanted these for Python 3. Before you start you should update apt-get’s list of available software and upgrade all the packages on your machine. You can do this with:
sudo apt-get update # Fetches the list of available updates
sudo apt-get upgrade # Strictly upgrades the current packages
What I found is I could get most of what I wanted using apt-get. I got most of what I wanted with:
sudo apt-get install python3-numpy
sudo apt-get install python3-scipy
sudo apt-get install python3-matplotlib
sudo apt-get install python3-pandas
However I couldn’t find and apt-get module for SciKit Learn the machine learning library. So I tried pip3 and it did work even though it downloaded the source code and compiled it.
pip3 install sklearn –upgrade
Now I had all the scientific programming power of the standard Python libraries. Note that since the Raspberry Pi only has 1Gig RAM and the SD Card only has twenty something Gig free, you can’t really run large machine learning tasks. However if they do fit within the Pi then it is a very inexpensive way to do these computations. What a lot of people do is build clusters of Raspberry Pi’s that work together. I’ve seen articles on how University labs have built supercomputers out of hundreds or Pi’s all put together in a cluster. Further they run quite sophisticated software like Hadoop, Docker and Kubernetes to orchestrate the whole thing.
Summary
I now have the Raspberry Pi up and running and I’m enjoying playing with Mathematica and Sonic Pi. I’m doing a bit of Python programming and browsing the Internet. Quite an amazing little device. I’m also impressed with how much it can do for such a low cost. As other vendors like Apple, Microsoft, HP and Dell try to push people into more and more expensive desktops and laptops, it will be interesting to see how many people revolt and switch to the far more inexpensive DIY type solutions. Note that there are vendors that make things like Raspberry Pi complete desktop computers at quite a low cost as well.