Electronics - BrixIT Bloghttps://blog.brixit.nl/tag/electronics/page/1Sun, 07 Jul 2024 19:00:46 -000060Making a Linux-managed network switchhttps://blog.brixit.nl/making-a-linux-managed-network-switch/102LinuxMartijn BraamWed, 03 Jul 2024 14:10:04 -0000<p>Network switches are simple devices, packets go in, packets go out. Luckily people have figured out how to make it complicated instead and invented managed switches.</p> <p>Usually this is done by adding a web-interface for configuring the settings and see things like port status. If you have more expensive switches then you'd even get access to some alternate interfaces like telnet and serial console ports.</p> <p>There is a whole second category of managed switches though that people don't initially think of. These are the network switches that are inside consumer routers. These routers are little Linux devices that have a switch chip inside of them, one or more ports are internally connected to the CPU and the rest are on the outside as physical ports.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1719959978/RB2011UiAS-160620170256_160656.png" class="kg-image"><figcaption>Mikrotik RB2011 block diagram from mikrotik.com</figcaption></figure> <p>Here is an example of such a device that actually has this documented. I always thought that the configuration of these switch connected ports was just a nice abstraction by the webinterface but I was suprised to learn that with the DSA and switchdev subsystem in Linux these ports are actually fully functioning "local" network ports. Due to this practically only being available inside integrated routers It's pretty hard to play around with unfortunately.</p> <p>What is shown as a single line on this diagram is actually the connection of the SoC of the router and the switch over the SGMII bus (or maybe RGMII in this case) and a management bus that's either SMI or MDIO. Network switches have a lot of these fun acronyms that even with the full name written out make little sense unless you know how all of this fits together.</p> <p>Controlling your standard off-the-shelf switch using this system simply isn't possible because the required connections of the switch chip aren't exposed for this. So there's only one option left...</p> <h2>Making my own gigabit network switch</h2> <p>Making my own network switch can't be <i>that</i> hard right? Those things are available for the price of a cup of coffee and are most likely highly integrated to reach that price point. Since I don't see any homemade switches around on the internet I guess the chips for those must be pretty hard to get...</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1719960715/image.png" class="kg-image"></figure> <p>Nope, very easy to get. There's even a datasheet available for these. So I created a new KiCad project and started creating some footprints and symbols.</p> <p>I'm glad there's any amount of datasheet available for this chip since that's not usually the case for Realtek devices, but it's still pretty minimal. I resorted to finding any devices that has schematics available for similar Realtek chips to find out how to integrate it and looking at a lot of documentation for how to implement ethernet in a design at all.</p> <p>The implementation for the chip initially looked very complicated, there's about 7 different power nets it requires and there are several pretty badly documented communication interfaces. After going through other implementations it seem like the easiest way to power it is just connect all the nets with overlapping voltage ranges together and you're left with only needing a 3.3V and 1.1V regulator.</p> <p>The extra communication busses are for all the extra ports I don't seem to need. The switch chip I selected is the RTL8367S which is a very widely used 5-port gigabit switch chip, but it's actually not a 5-port chip. It's a 7 port switch chip where 5 ports have an integrated PHY and two are for CPU connections.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1719961532/image.png" class="kg-image"><figcaption>CPU connection block diagram from the RTL8367S datasheet</figcaption></figure> <p>My plan is different though, while there are these CPU ports available there is actually nothing in the Linux switchdev subsystem that requires the CPU connection to be to those ports. Instead I'll be connecting to port 0 on the switch with a network cable and as far as the switchdev driver knows there's no ethernet PHY in between.</p> <p>The next hurdle is the configuration of the switch chip, there's several configuration systems available and the datasheet does not really describe what is the minimum required setup to actually get it to function as a regular dumb switch. To sum up the configuration options of the chip:</p> <ul><li>There&#x27;s 8 pins on the chip that are read when it&#x27;s starting up. These pins are shared with the led pins for the ports so that makes designing pretty annoying. Switching the setting from pull-up to pull-down also requires the led to be connected in the different orientation.</li> <li>There&#x27;s an i2c bus that can be connected to an eeprom chip. The pins for this are shared with the SMI bus that I require to make this chip talk to Linux though. There is pin configuration to select from one of two eeprom size ranges but does not further specify what this setting actually changes.</li> <li>There&#x27;s a SPI bus that supports connecting a NOR flash chip to it. This can store either configuration registers or firmware for the embedded 8051 core depending on the configuration of the bootup pins. The SPI bus pins are also shared with one of the CPU network ports.</li> <li>There is a serial port available but from what I guess it probably does nothing at all unless there&#x27;s firmware loaded in the 8051.</li> </ul> <p>My solution to figuring out is to just order a board and solder connections differently until it works. I've added a footprint for a flash chip that I ended up not needing and for all the configuration pins I added solder jumpers. I left out all the leds since making that configurable would be pretty hard.</p> <p>The next step is figuring out how to do ethernet properly. There has been a lot of documentation written about this and they all make it sound like gigabit ethernet requires perfect precision engineering, impedance managed boards and a blessing from the ethernet gods themselves to work. This does not seem to match up with the reality that these switches are very very cheaply constructed and seem to work just fine. So I decided to open up a switch to check how many of these coupling capacitors and impedance matching planes are actually used in a real design. The answer seems to be that it doesn't matter that much.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1719962591/image.png" class="kg-image"></figure> <p>This is the design I have ended up with now but it is not what is on my test PCB. I got it almost right the first time though :D</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1719962813/image.png" class="kg-image"></figure> <p>The important parts seem to be matching the pair skew but matching the length of the 4 network pairs is completely useless, this is mainly because network cables don't have the same twisting rate for the 4 pairs and so the length of these are already significantly different inside the cable.</p> <p>The pairs between the transformer and the RJ45 jack has it's own ground plane that's coupled to the main ground through a capacitor. The pairs after the transformer are just on the main board ground fill.</p> <p>What I did wrong on my initial board revision was forgetting the capacitor that connects the center taps of the transformer on the switch side to ground making the signals on that side referenced to board ground. This makes ethernet very much not work anymore so I had to manually cut tiny traces on the board to disconnect that short to ground. In my test setup the capacitor just doesn't exist and all the center taps float. This seems to work just fine but the final design does have that capacitor added.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1720003020/fixed.JPG" class="kg-image"><figcaption>Cut ground traces on the ethernet transformer</figcaption></figure> <p>The end result is this slightly weird gigabit switch. It has 4 ports facing one direction and one facing backwards and it is powered over a 2.54mm pinheader. I have also added a footprint for a USB Type-C connector to have an easy way to power it without bringing out the DuPont wires.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1720007603/IMG_20240626_221246.jpg" class="kg-image"></figure> <h2>Connecting it to Linux</h2> <p>For my test setup I've picked the PINE64 A64-lts board since it has the connectors roughly in the spots where I want them. It not being an x86 platform is also pretty important because configuration requires a device tree change, can't do that on a platform that doesn't use device trees.</p> <p>The first required thing was rebuilding the kernel for the board since most kernels simply don't have these kernel modules enabled. For this I enabled these options:</p> <ul><li><code>CONFIG_NET_DSA</code> for the Distributed Switch Architecture system</li> <li><code>CONFIG_NET_DSA_TAG_RTL8_4</code> for having port tagging for this Realtek switch chip</li> <li><code>CONFIG_NET_SWITCHDEV</code> the driver system for network switches</li> <li><code>CONFIG_NET_DSA_REALTEK</code>, <code>CONFIG_NET_DSA_REALTEK_SMI</code>, <code>CONFIG_NET_DSA_REALTEK_RTL8365MB</code> for the actual switch chip driver</li> </ul> <p>Then the more complicated part was figuring out how to actually get this all loaded. In theory it is possible to create a device tree overlay for this and get it loaded by U-Boot. I decided to not do that and patch the device tree for the A64-lts board instead since I'm rebuilding the kernel anyway. The device tree change I ended up with is this:</p> <pre><code>diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-lts.dts b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-lts.dts index 596a25907..10c1a5187 100644 --- a/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-lts.dts +++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pine64-lts.dts @@ -18,8 +18,78 @@ led { gpios = &lt;&amp;r_pio 0 7 GPIO_ACTIVE_LOW&gt;; /* PL7 */ }; }; + +switch { + compatible = &quot;realtek,rtl8365rb&quot;; + mdc-gpios = &lt;&amp;pio 2 5 GPIO_ACTIVE_HIGH&gt;; // PC5 + mdio-gpios = &lt;&amp;pio 2 7 GPIO_ACTIVE_HIGH&gt;; // PC7 + reset-gpios = &lt;&amp;pio 8 5 GPIO_ACTIVE_LOW&gt;; // PH5 + realtek,disable-leds; + + mdio { + compatible = &quot;realtek,smi-mdio&quot;; + #address-cells = &lt;1&gt;; + #size-cells = &lt;0&gt;; + + ethphy0: ethernet-phy@0 { + reg = &lt;0&gt;; + }; + + ethphy1: ethernet-phy@1 { + reg = &lt;1&gt;; + }; + + ethphy2: ethernet-phy@2 { + reg = &lt;2&gt;; + }; + + ethphy3: ethernet-phy@3 { + reg = &lt;3&gt;; + }; + + ethphy4: ethernet-phy@4 { + reg = &lt;4&gt;; + }; + }; + + ports { + #address-cells = &lt;1&gt;; + #size-cells = &lt;0&gt;; + + port@0 { + reg = &lt;0&gt;; + label = &quot;cpu&quot;; + ethernet = &lt;&amp;emac&gt;; + }; + + port@1 { + reg = &lt;1&gt;; + label = &quot;lan1&quot;; + phy-handle = &lt;&amp;ethphy1&gt;; + }; + + port@2 { + reg = &lt;2&gt;; + label = &quot;lan2&quot;; + phy-handle = &lt;&amp;ethphy2&gt;; + }; + + port@3 { + reg = &lt;3&gt;; + label = &quot;lan3&quot;; + phy-handle = &lt;&amp;ethphy3&gt;; + }; + + port@4 { + reg = &lt;4&gt;; + label = &quot;lan4&quot;; + phy-handle = &lt;&amp;ethphy4&gt;; + }; + }; +}; }; </code></pre> <p>It loads the driver for the switch with the <code>realtek,rtl8365rb</code>, this driver supports a whole range of Realtek switch chips including the RTL8367S I've used in this design. I've removed the CPU ports from the documentation example and just added the definitions of the 5 regular switch ports.</p> <p>The important part is in <code>port@0</code>, this is the port that is facing backwards on my switch and is connected to the A64-lts, I've linked it up to <code>&emac</code> which is a reference to the ethernet port of the computer. The rest of the ports are linked up to their respective PHYs in the switch chip. </p> <p>In the top of the code there's also 3 GPIOs defined, these link up to SDA/SCL and Reset on the switch PCB to make the communication work. After booting up the system the result is this:</p> <pre><code>1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: &lt;BROADCAST,MULTICAST&gt; mtu 1508 qdisc noop state DOWN qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff 3 lan1@eth0: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff 4 lan2@eth0: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff 5 lan3@eth0: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff 6 lan4@eth0: &lt;BROADCAST,MULTICAST,M-DOWN&gt; mtu 1500 qdisc noop state DOWN qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff</code></pre> <p>I have the <code>eth0</code> device here like normal and then I have the 4 interfaces for the ports on the switch I defined in the device tree. To make it actually do something the interfaces actually need to be brought online first:</p> <pre><code>$ ip link set eth0 up $ ip link set lan1 up $ ip link set lan2 up $ ip link set lan3 up $ ip link set lan4 up $ ip link 1: lo: &lt;LOOPBACK&gt; mtu 65536 qdisc noop state DOWN qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1508 qdisc mq state UP qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff 3: lan1@eth0: &lt;NO-CARRIER,BROADCAST,MULTICAST,UP&gt; mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff 4: lan2@eth0: &lt;NO-CARRIER,BROADCAST,MULTICAST,UP&gt; mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff 5: lan3@eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff 6: lan4@eth0: &lt;NO-CARRIER,BROADCAST,MULTICAST,UP&gt; mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000 link/ether 02:ba:6f:0c:21:c4 brd ff:ff:ff:ff:ff:ff</code></pre> <p>Now the switch is up you can see I have a cable plugged into the third port. This system hooks into a lot of the Linux networking so it Just Works(tm) with a lot of tooling. Some examples:</p> <ul><li>Add a few of the lan ports into a standard Linux bridge and the switchdev system will bridge those ports together in the switch chip so Linux doesn&#x27;t have to forward that traffic.</li> <li>Thinks like <code>ethtool lan3</code> just work to get information about the link. and with <code>ethtool -S lan3</code> all the standard status return info which includes packets that have been fully handled by the switch.</li> </ul> <h2>Limitations</h2> <p>There's a few things that makes this not very nice to work with. First of all the requirement of either building a custom network switch or tearing open an existing one and finding the right connections. </p> <p>It's not really possible to use this system on regular computers/servers since you need device trees to configure the kernel for this and most computers don't have kernel-controlled GPIO pins available to hook up a switch.</p> <p>As far as I can find there's also no way to use this with a network port on the computer side that's not fixed, USB network interfaces don't have a device tree node handle to refer to to set the conduit port.</p> <p>There is a chance some of these limitations are possible to work around, maybe there's some weird USB device that exposes pins on the GPIO subsystem, maybe there's a way to load switchdev without being on an ARM device but that would certainly take a bit more documentation...</p> Building a DSMR reading boardhttps://blog.brixit.nl/building-a-dsmr-reading-board/101ElectronicsMartijn BraamMon, 27 May 2024 10:08:00 -0000<p>Quite a while back I designed a small PCB for hooking up sensors to an ESP8266 module to gather data and have a nice Grafana dashboard with temperature readings. While building this setup I grabbed one of my spare ESP8266 dev boards and soldered that to the P1 port on my smart energy meter to log the power usage of the whole house.</p> <p>For those unfamiliar with P1 and DSMR since it's quite regional: DSMR is the Dutch Smart Meter Requirements specification. It's a document that describes the connectivity of various ports on energy meters and it's used in the Netherlands and a few countries around it. One of the specifications within DSMR is P1 which is the document for the RJ12 connector for plugging third party monitoring tools.</p> <h2>Electrical design</h2> <p>So the first part in the project is figuring out how to make the hardware itself work. I've decided to slightly modernize my design so this is the first module I build that uses an ESP32 chip instead of the older ESP8266 I have in use everywhere.</p> <p>I specifically designed the board around the ESP32-S3-WROOM-1 module. This makes things significantly simpler than my older designs since there's no programming circuitry required.</p> <p>The design contraints I've used for this board are:</p> <ul><li>Make the design handsolderable instead of using JLCPCB assembly service for the boards. I enjoy designing boards and I probably should get some more actual soldering experience with SMD parts.</li> <li>Connect to the smart energy meter using off the shelf RJ12 cables instead of soldering wires on the board. Also include a passive P1 splitter on the board since it&#x27;s becoming more and more popular to have charging points for electric cars which can also be hooked up to the P1 port.</li> <li>USB-C for programming and power. I have a back-up programming header footprint on the board but it shouldn&#x27;t be necessary . Just like putting USB-B Micro ports on devices shouldn&#x27;t be allowed anymore in 2024.</li> </ul> <p>The schematic is basically the absolute minimum required to get the ESP32 module up and running. Since the ESP32-S3 has native USB support it means that I can drop a whole bunch of parts from the schematic that is normally required for programming.</p> <p>Another neat feature is that this chip has a fully connected I/O mux inside which means I can basically pick any random GPIO pin and later configure in software which hardware block in the chip it hooks up to. This feature is also available in other chips like the RP2040 but there it's way more limited. There is only a few valid choices for pins for UART1 for example and there's no way to swap RX and TX on a board without using a soldering iron.</p> <p>In the DSMR design I'll only be using a single GPIO pin configured to be the RX of one of the hardware UARTS so it can receive the data.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716744001/image.png" class="kg-image"><figcaption>The full schematic for the DSMR module</figcaption></figure> <p>The board has a jumper on it to pick how the data requests are handled. In one position the line is connected straight to 5V so the energy meter will just continuously send the data over the P1 port, which should be correct in most situations. The other mode connects the data request line to the pass-through port so the device connected after the DSMR module can select when the data should be sent, in this case the module will just be passively sniffing the traffic whenever it's sent.</p> <p>There is also a solder jumper on the board to select whether it's powered from the USB connection or from the P1 port itself. According to the P1 specifications the power supplied by most energy meters won't be enough to reliably run the ESP32 module so there is always to option to power it from the USB-C port. In most cases there will be random device like a router nearby that has a powered USB connection to run the module.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716744605/image.png" class="kg-image"><figcaption>The PCB for the DSMR module</figcaption></figure> <p>I managed to fit everything on a small 40x40mm PCB by having the the two RJ12 connectors hang off the board. This also makes those neatly line up with the edges of the case for this board. The ESP32 module also hangs off the edge of the board, pictured at the top here. This is because the antenna part of the ESP module has a keepout area where there shouldn't be any copper on the board anyway to not disturb the WiFi signal.</p> <p>To make it fit I've also moved the power regulator and a few passives to the bottom side of the board. Normally it would be pretty expensive to do this but since I'm hand soldering these boards using both sides of the board is free.</p> <p>Another upside with handsoldering the boards is that I'm not limited to the JLCPCB parts for once and I can just whatever random parts I can source. I've decided to get these boards made by Aisler this time so the boards are made in Germany instead of China and the boards just look great. I normally use the KiCad Aisler Push plugin in my workflow anyway just to run some sanity checks on the board design in addition to the checks ran in KiCad.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716745405/image.png" class="kg-image"><figcaption>Some PCB design notes generated by Aisler</figcaption></figure> <p>In this screenshot it's just warning me about the holes for the plastic pins in the footprint of the USB-C connector. It doesn't matter whether those are copper plated or not so I just ignore this warning. After receiving the boards I've also checked this and the holes did not get copper plated at all.</p> <p>I also noticed there was a <a href="https://community.aisler.net/t/introducing-design-in-europe-by-aisler-and-wurth-elektronik/3886">discount for using components from Würth Elektronik</a> on the board so naturally I took that as a challenge to see how for I can push that. It turns out that in this design the answer is pretty far: The easy thing is to use WE part numbers for the passives on the board.</p> <p>The second thing I replaced is the connectors on the board. I was already pretty happy I did not have to make custom footprints for once because that's just a time consuming job. After comparing a lot of footprints it turns out that WE makes an RJ12 connectors with the exact footprint I had already made. The USB-C connector is a bit more difficult since I'm used to picking the one available at JLCPCB that also happens to have a footprint already in KiCad. It turns out that the Würth Elektronik USB-C connector fits on the <code>USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal</code> footprint in KiCad. I guess there's simply not too many different ways to make spec compliant USB-C connectors.</p> <h2>The soldering</h2> <p>So the board is relatively straightforward to solder, it doesn't have that many parts and I decided to not pick the tiniest SMT parts I could find. If you're not familiar with SMT parts, they get delivered on reels and if you order small quantities you get a smaller cut-off portion of a reel. Here's some capacitors for example:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716749877/20240526_0008.jpg" class="kg-image"></figure> <p>These are the 22uF 0805 capacitors I've ordered for the ESP32 power rail. The rest of the passives are the slightly smaller 0603 packages. On the right of the board you can see the pads for the 0805 capacitor and the 0603 capacitor side by side. In this case the Würth capacitors came in a neat transparent piece of tube. If you order a full reel of cheap resistors they usually come on paper tape which looks like this:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716750066/IMG_20240526_205530.jpg" class="kg-image"><figcaption>This is 5000 resistors in 0603 size</figcaption></figure> <p>This is probably one of few times I've ever ordered 5000 of something. Even the bigger parts like the ESP32 modules come on a reel:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716750232/IMG_20240526_210232.jpg" class="kg-image"><figcaption>This strip holds 20 cores of pure computing power :D</figcaption></figure> <p>This soldering itself was quite time consuming but not that hard. The secret is to just use a lot of flux and not use plastic tweezers that melt while soldering. </p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716750417/20240526_0020.jpg" class="kg-image"></figure> <p>Maybe after some experience I can even get them on straight. Supposedly it's a lot easier and neater to use solder paste and stencils to do this so I also ordered the stencils for this PCB. I don't have the soldering paste and extra equipment for it though so that experiment will have to wait a bit longer.</p> <h2>The case</h2> <p>To have this look somewhat neat when it's finished I also need a case for the board. The need for cases has annoyed me several times already when designing PCBs. The options are either grabbing an off the shelf project box which is usually expensive and never fits exactly with the design or just 3D printing a case which involves me angrily staring at CAD software to figure out how to make things fit and match up to my PCB.</p> <p>I've already written a blog post about my solution here which is <a href="https://blog.brixit.nl/automatic-case-design-for-kicad/">TurboCase</a>. It's a tool that automatically generates a case based on the PCB file from KiCad. I wrote the software specifically because I needed it for this PCB so naturally the case generated by it works perfectly for this project :)</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716746584/image.png" class="kg-image"><figcaption>TurboCase generated case for this board</figcaption></figure> <p>Since writing the blog post about the details on how TurboCase works I've added support for TurboCase-specific footprints. I now have a global KiCad library that holds footprints that are designed to go on the <code>User.6</code> layer to add prefab features to the final OpenSCAD file like screw holes in the case and the hole for by USB-C connectors.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716746734/image.png" class="kg-image"><figcaption>The contents of the User.6 layer of my board design</figcaption></figure> <p>Here you can see the drawing on the <code>User.6</code> layer of the DSMR module. It contains several features:</p> <ul><li>The outline of the case drawn using the graphic elements in KiCad. This functions the same way as the board outline normally works on the <code>Edge.Cuts</code> layer except it describes the outline of the inside of the case.</li> <li>Three mounting holes that fit countersunk M3 sized screws to mount the case on the wall.</li> <li>A M3-sized keyhole screw-mounting thingy so the board can be mounted in a less permanent way.</li> <li>The cube with the line in it marks a spot where turbocase makes an USB-C shaped hole matched up perfectly to the USB-C connector on the board.</li> <li>The RJ12 connectors already cross the border of the case in KiCad so there will be holes generated for them automatically in the case based on the connector outline on the fabrication layer.</li> <li>The 3 MountingHole footprints on the PCB will be used to generate mounting posts for the PCB inside the case, the main reason why I started making this tool to make sure those things align perfectly every time.</li> </ul> <p>I did a few iterations on the footprint library and did a series of test prints of the case to make sure everything is correct and I can happily report that everything just matches up perfectly.</p> <p>The few days I spend building and testing this tool now narrows down the whole process from kicad to physical product down to:</p> <ul><li>Draw outline in KiCad</li> <li>Run TurboCase on the <code>.kicad_pcb</code> file to get my OpenSCAD file.</li> <li>Export the OpenSCAD file to .STL and 3D print it.</li> </ul> <p>I hope this utility is useful for more people and I would love to see how it performs with other board designs!</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716747340/darktable.ZQFGO2.jpg" class="kg-image"></figure> <h2>The software side</h2> <p>The more annoying part of the whole process was dealing with the software. For the ESP8266 I've always used the Arduino IDE. This is mainly because I did not want to deal with the xtensa toolchain required to build software for these chips. I don't do complicated things with these modules and with the Arduino IDE they simply always just worked.</p> <p>Switching over to the ESP32 did mean that stuff stopped working automatically for me sadly, the base software worked great but I had some troubly getting the MQTT library to work. The dependency resolution in the Arduino IDE is very simple and it does not account for having different dependencies with the same include name so I tried PlatformIO for this project.</p> <p>The whole setup process for PlatformIO was pretty smooth until I got to platform selection. There's a few different variations of the ESP32-S3 module available and even a few more specific variations of the WROOM-1 module I put on my board. I dit however commit the sin of picking the 4MB flash version of the board. This is the only one that doesn't have a specific config in PlatformIO and the merge request for it was denied based on that it would be easy to override the platform config with a json file. I did not find how to do that easily so I picked one of the random dev boards that used the wroom-1 module with 4MB flash instead to use whatever changes they have made to the JSON file for the board to make this work.</p> <p>Now for actually implementing the P1 protocol: This is deceptively hard. The way the protocol works is that you pull the data request line high on the connector and then the energy meter will start spewing out datagrams over the serial port on either a 1 second or 10 second interval depending on the protocol version. These datagrams look something like this:</p> <pre><code>/XMX5LGBBFG10 1-3:0.2.8(42) 0-0:1.0.0(170108161107W) 0-0:96.1.1(4530303331303033303031363939353135) 1-0:1.8.1(002074.842*kWh) 1-0:1.8.2(000881.383*kWh) 1-0:2.8.1(000010.981*kWh) 1-0:2.8.2(000028.031*kWh) 0-0:96.14.0(0001) 1-0:1.7.0(00.494*kW) 1-0:2.7.0(00.000*kW) 0-0:96.7.21(00004) 0-0:96.7.9(00003) 1-0:32.32.0(00000) 1-0:32.36.0(00000) 0-0:96.13.1() 0-0:96.13.0() 1-0:31.7.0(003*A) 1-0:21.7.0(00.494*kW) 1-0:22.7.0(00.000*kW) 0-1:24.1.0(003) 0-1:96.1.0(4730303139333430323231313938343135) 0-1:24.2.1(170108160000W)(01234.000*m3) !D3B0</code></pre> <p>This looks to be a relatively simple line based protocol. The fields are identified by a numeric code called the "OBIS" code. This stands for OBject Identification System. Then there's values added in parenthesis after it. The difficulty in parsing this is that there can be multiple values in parentheses added after each OBIS field. This would not be terrifically hard to parse if the DSMR spec had nailed down the encoding a bit more but it merly specifies that the field ends in a newline.</p> <p>In my case some of the values themselves also contain newlines so this breaks the assumption you can parse this based simply based on lines and that combined by the variable (but unspecified) amount of values means that the parser code for this becomes quite nasty.</p> <p>Luckily I got to use one of the features I gained by switching to PlatformIO: Unit testing. The whole DSMR parser is moved to it's own class in the codebase and only gets a <code>Stream</code> reference to parse the DSMR datagrams from. This means I don't have to sit with my laptop in the hallway debugging the parser and I can check if the parser works with dumps from various smart energy meters.</p> <p>This all glued together means that the DSMR module will get the data from the P1 port and then sent it out over WiFi to my MQTT server with a separate topic for every field. And the result:</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1716749436/image.png" class="kg-image"><figcaption>Grafana showing the power consumption and production as measured by the smart energy meter</figcaption></figure> <p>The source for the hardware and firmware is available at <a href="https://git.sr.ht/~martijnbraam/dsmr-module">https://git.sr.ht/~martijnbraam/dsmr-module</a> and the board itself is also available on <a href="https://aisler.net/p/IWCEGPWL">https://aisler.net/p/IWCEGPWL</a></p> Automatic case design for KiCadhttps://blog.brixit.nl/automatic-case-design-for-kicad/100ElectronicsMartijn BraamWed, 15 May 2024 19:48:27 -0000<p>I don't generally get along great with CAD software with the exception of KiCad. I guess the UX for designing things is just a lot simpler when you only have 2 dimensions to worry about. After enjoying making a PCB in KiCad the annoying for me is always getting a case designed to fit the board.</p> <p>If I'm lucky I don't need many external holes to fit buttons or connectors and if I'm really lucky the mounting holes for the board are even in sensible locations. I wondered if there was a quick way to get the positions of the mounting holes into some 3D CAD software to make the mounting posts in the right position without doing math or measuring.</p> <p>But what's even better than importing mounting hole locations? Not having to build the case at all!</p> <h2>Turbocase</h2> <p>So the solution is just several hundred lines of Python code. I've evaluated a few ways of getting data out of KiCad to mess with it and initially the <code>kicad-cli</code> tool looked really promising since it allows exporting the PCB design to several vector formats without launching KiCad. After exporting a few formats and seeing how easy it would be to get the data into Python I remembered that the PCB design files are just s-expressions, so the easiest way is just reading the .kicad_pcb file directly.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1715799912/image.png" class="kg-image"><figcaption>A snippet of the .kicad_pcb source file</figcaption></figure> <p>So with this there's several pieces of data the tool extracts for the case design:</p> <ul><li>The footprints with <code>MountingHole</code> in the name to place posts for threaded metal inserts in my 3d prints</li> <li>A case outline from a user layer. This has the same semantics as the Edge.Cuts layer except that it defines the shape of the inner edge of the case.</li> <li>Information about connectors to make holes in the case and to have placeholders in the final OpenSCAD file for easier modifications.</li> </ul> <p>The locations of the mounting holes is pretty easy to get up and running. By iterating over the PCB file the tool saves all the footprints that are mounting holes and for each of those footprints it locates the pad with the largest hole in it and saves that to make a mounting post in the case.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1715800268/image.png" class="kg-image"></figure> <p>In my test PCB I already had some nice edge-cases to deal with since the specific footprint I used has vias in it which also counts as pads. The outer side of the pad is used as the diameter of the mounting post that will be generated and inside that a hole will be created that fits the bag of threaded metal inserts I happen to have here.</p> <h2>Case outlines</h2> <p>To make the actual case I initially planned to just grab the PCB outline and slightly enlarge it as a template. This turns out to have a few obvious flaws, for example the ESP32 module that I have hanging over the edge of the PCB. Grabbing the bounding box of the entire design would also be a quick fix but that would mean the case would be way too large again due to the keepout area of the ESP32 footprint. The solution is manually defining the shape of the case since this gives the most flexibility.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1715800592/image.png" class="kg-image"></figure> <p>I picked the <code>User.6</code> layer as the case outline layer since it has a neat blue color in this KiCad theme. The semantics for the case outline is the same as what you'd normally do on the <code>Edge.Cuts</code> layer except that this defines the inner wall of the case. The turbocase utility will then add a wall thickness around it to make the 3D model of the case.</p> <h2>Connector holes</h2> <p>What use is a case without any holes for connections? This turned out to be a more difficult issue. For the connectors I would actually need some height information and KiCAD is still very much 2D. It is possible to link a 3D model to the footprint which is great to see how the final board will look like:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1715801016/image.png" class="kg-image"></figure> <p>Sadly using this 3D model data would be quite difficult for turbocase since it would require an importer for all the various supported 3D model formats and then a way to grab the outline of a slice of the model.</p> <p>So I picked the uglier but simpler solution. Just give the connector a height in some metadata and treat them as cubes. For the footprint I use the bounding box of the <code>F.Fab</code> layer which should correspond relatively closely to the size of the connector. To store the 3rd dimension I simply added a property to the connectors I wanted to be relevant to the case design:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1715801208/image.png" class="kg-image"></figure> <h2>Exporting the case</h2> <p>I decided to export the cases as OpenSCAD files. This is mostly because these are simple text files I can generate and I already have some experience with OpenSCAD design.</p> <p>A large part of the generated file is boilerplate code for doing the basic case components. After that it will export the case outline as a polygon and do the regular OpenSCAD things to it to make a 3D object.</p> <div class="highlight"><pre><span></span><span class="n">standoff_height</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">5</span><span class="p">;</span><span class="w"></span> <span class="n">floor_height</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1.2</span><span class="p">;</span><span class="w"></span> <span class="n">pcb_thickness</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">1.6</span><span class="p">;</span><span class="w"></span> <span class="n">inner_height</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">standoff_height</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">pcb_thickness</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mf">11.5</span><span class="p">;</span><span class="w"></span> <span class="n">pcb_top</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">floor_height</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">standoff_height</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">pcb_thickness</span><span class="p">;</span><span class="w"></span> <span class="n">box</span><span class="p">(</span><span class="mf">1.2</span><span class="p">,</span><span class="w"> </span><span class="mf">1.2</span><span class="p">,</span><span class="w"> </span><span class="n">inner_height</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">polygon</span><span class="p">(</span><span class="n">points</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[[</span><span class="mi">102</span><span class="p">,</span><span class="mi">145</span><span class="p">],</span><span class="w"> </span><span class="p">[</span><span class="mi">140</span><span class="p">,</span><span class="mi">145</span><span class="p">],</span><span class="w"> </span><span class="p">....</span><span class="w"> </span><span class="p">]);</span><span class="w"></span> <span class="p">}</span><span class="w"></span> </pre></div> <p>The pcb_thickness is also one of the variables exported from the KiCad PCB file and the <code>box(wall, bottom, height)</code> module creates the actual basic case.</p> <p>For the connector a series of cubes is generated and those are substracted from the generated box:</p> <div class="highlight"><pre><span></span><span class="c1">// J1 Connector_USB:USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_Horizontal USB 2.0-only 16P Type-C Receptacle connector</span> <span class="n">translate</span><span class="p">([</span><span class="mf">104.15</span><span class="p">,</span><span class="w"> </span><span class="mi">119</span><span class="p">,</span><span class="w"> </span><span class="n">pcb_top</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="n">rotate</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">90</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="cp">#connector(-4.47,-3.675,4.47,3.675,3.5100000000000002);</span> <span class="c1">// J2 Connector_RJ:RJ12_Amphenol_54601-x06_Horizontal </span> <span class="n">translate</span><span class="p">([</span><span class="mi">115</span><span class="p">,</span><span class="w"> </span><span class="mf">131.9</span><span class="p">,</span><span class="w"> </span><span class="n">pcb_top</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="n">rotate</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">90</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="cp">#connector(-3.42,-1.23,9.78,16.77,11.7);</span> <span class="c1">// J3 Connector_RJ:RJ12_Amphenol_54601-x06_Horizontal </span> <span class="n">translate</span><span class="p">([</span><span class="mf">127.11</span><span class="p">,</span><span class="w"> </span><span class="mf">138.26</span><span class="p">,</span><span class="w"> </span><span class="n">pcb_top</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="n">rotate</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">270</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="cp">#connector(-3.42,-1.23,9.78,16.77,11.7);</span> </pre></div> <p>This also has some extraced metadata from the PCB to figure out what's what when editing the .scad file. The <code>connector</code> module is a simple helper for generating a cube from a bounding box that accounts for the origin of the connector not being in the center.</p> <p>Finally the mounting posts are added to the case as simple cylinders:</p> <div class="highlight"><pre><span></span><span class="c1">// H1</span> <span class="n">translate</span><span class="p">([</span><span class="mi">105</span><span class="p">,</span><span class="w"> </span><span class="mi">108</span><span class="p">,</span><span class="w"> </span><span class="mf">1.2</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="n">mount</span><span class="p">(</span><span class="mf">3.4000000000000004</span><span class="p">,</span><span class="w"> </span><span class="mf">6.4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">);</span><span class="w"></span> <span class="c1">// H3</span> <span class="n">translate</span><span class="p">([</span><span class="mi">121</span><span class="p">,</span><span class="w"> </span><span class="mi">140</span><span class="p">,</span><span class="w"> </span><span class="mf">1.2</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="n">mount</span><span class="p">(</span><span class="mf">3.4000000000000004</span><span class="p">,</span><span class="w"> </span><span class="mf">6.4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">);</span><span class="w"></span> <span class="c1">// H2</span> <span class="n">translate</span><span class="p">([</span><span class="mi">137</span><span class="p">,</span><span class="w"> </span><span class="mi">108</span><span class="p">,</span><span class="w"> </span><span class="mf">1.2</span><span class="p">])</span><span class="w"></span> <span class="w"> </span><span class="n">mount</span><span class="p">(</span><span class="mf">3.4000000000000004</span><span class="p">,</span><span class="w"> </span><span class="mf">6.4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">);</span><span class="w"></span> </pre></div> <p>The extracted information from the mounting hole is the 3.2mm drill diameter and the 6.2mm pad diameter. The inner hole is expanded by 0.2mm in this case to make the holes work with the metal inserts.</p> <h2>Further improvements</h2> <p>There's a lot of neat things that could be added to this. The major one being a lid for the case. This also would need a bunch more configurability to deal with mounting mechanisms for the lid like screw holes or some clips.</p> <p>The system could also be extended by producing a footprint library specifically for turbocase to signify where to add specific features to the case. This could be things like cooling holes, led holes. Maybe some fancier connector integration.</p> <p>The output from turbocase also suffers from the same issue a lot of OpenSCAD designs suffer from: it's very hard to add chamfers and fillets to make a less rectangular case. That would require someone with more OpenSCAD knowledge to improve the generated output.</p> <p>The source code for the turbocase tool is available at <a href="https://sr.ht/~martijnbraam/turbocase/">https://sr.ht/~martijnbraam/turbocase/</a> and the utility is available on pypi under the <code>turbocase</code> name.</p> Moving to a RTOS on the RP2040https://blog.brixit.nl/moving-to-a-rtos-on-the-rp2040/96ElectronicsMartijn BraamMon, 06 May 2024 15:58:55 -0000<p>I've been working on a bunch of small projects involving microcontrollers. Currently a lot of them are based around the Raspberry Pi Pico boards because I like the development experience of those a lot. They have a decent SDK and cheap hardware to get started and the debugger works with gdb/openocd so it just integrates in all IDEs that support that.</p> <p>One of my current projects is making a fancy hardware controller for a bunch of video equipment I use. The main things that will be controlled are two PTZ cameras (those are cameras that have motors to move them). One stationary camera and the video switching equipment that that's hooked up to.</p> <p>Currently the control of the control of the PTZ cameras is done with an unbranded panel that looks suspiciously like the Marshall VS-PTC-200:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1715003106/VS-PTC-200-Keyboard-PTZ-Compact-Controller.jpg" class="kg-image"><figcaption>(Image from marshall-usa.com)</figcaption></figure> <p>The performance of this controller is simply not very great, especially for the price. It was a €650 device several years ago and for that money it has very annoying squishy buttons and the cheapest analog joystick you could find. Most of the buttons are also not functional with the cameras in use since this seems to be optimized for security cameras. This connects to the cameras over an RS-485 bus.</p> <p>The second thing I want my panel to do is very basic ATEM video switcher control. Currently that's fully done using the software panel on the computer because the panels from Blackmagic Design are very expensive.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1715003565/image.png" class="kg-image"><figcaption>There&#x27;s a tiny cheaper one now though. (from blackmagicdesign.com)</figcaption></figure> <p>After a bit of designing I figured the most minimal design I can get away with is 9 buttons, the joystick and a display for the user interface. The hardware design has gone through several iterations over the last year but I now have some PCBs with the 9 RGB buttons on it, the $10 joystick that was also in the Marshall-clone panel and to interface with the outside world it has the TP8485E to communicate with the cameras over RS-485 and a Wiznet W5500 module to communicate with the video switcher over ethernet.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1715003933/IMG_20240429_184436.jpg" class="kg-image"><figcaption>This includes a bunch of &quot;oops-the-wrong-pinout&quot; fixes...</figcaption></figure> <p>After a lot of fixing of the board I had made I now have all the hardware parts functional, but the difficult part of this project is the software.</p> <h2>Initial software</h2> <p>I first started creating the software like I do all the RP2040 based projects. A cmake project that pulls in the pico-sdk. To make anything work at all I dedicated the second core of the pico to dealing with the Wiznet module and the first core then handles all the user interface I/O. This worked fine to blink some leds and I did implement a DHCP client that ran on the second core. It did make implementing the rest of the system a lot more complicated. There's simply a lot of things that need to happen at once:</p> <ul><li>Draw an user interface on the display that&#x27;s somewhat smooth</li> <li>Send out VISCA commands over the RS-485 interface</li> <li>Respond to button presses</li> <li>Keep the entire network stack alive with multiple connections</li> </ul> <p>There's a bunch of things that need to happen on the network, the first of which is some actually standards complicant DHCP support. This would require keeping track of the expire times and occasionally talk to the DHCP server to keep the lease active. The second background task is making mDNS work. The ATEM video switcher IP can be autodiscovered using DNS-SD and it would be great to also announce the existence of the control panel.</p> <p>The ATEM protocol itself is also one of the harder parts to get right, the protocol itself is pretty simple but it does involve sometimes receiving a lot of data that exceeds the buffer size of the Wiznet module and the protocol has a very low timeout for disconnection for when you stop sending UDP datagrams to the ATEM.</p> <p>This all made me decide that it's probably better to switch to an RTOS for this project.</p> <h2>FreeRTOS</h2> <p>The first project I've looked into is FreeRTOS. This is technically already bundled inside the pico-sdk but all tutorials I've found for this download a fresh copy anyway so that's what I did. FreeRTOS seems to be the simplest RTOS I've looked at from this list, the main thing it provides is the RTOS scheduler and some communication between tasks. The simplest way I can show it is with some code:</p> <div class="highlight"><pre><span></span><span class="cp">#include</span><span class="w"> </span><span class="cpf">&quot;FreeRTOS.h&quot;</span><span class="cp"></span> <span class="n">TaskHandle_t</span><span class="w"> </span><span class="n">button_task</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">;</span><span class="w"></span> <span class="n">TaskHandle_t</span><span class="w"> </span><span class="n">led_task</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">;</span><span class="w"></span> <span class="n">QueueHandle_t</span><span class="w"> </span><span class="n">led_queue</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">;</span><span class="w"></span> <span class="kt">void</span><span class="w"> </span><span class="nf">buttonTask</span><span class="p">(</span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">param</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">get_button_pressed</span><span class="p">();</span><span class="w"></span> <span class="w"> </span><span class="n">xQueueSend</span><span class="p">(</span><span class="n">led_queue</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span> <span class="w"> </span><span class="p">}</span><span class="w"></span> <span class="p">}</span><span class="w"></span> <span class="kt">void</span><span class="w"> </span><span class="nf">ledTask</span><span class="p">(</span><span class="kt">void</span><span class="w"> </span><span class="o">*</span><span class="n">param</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="k">while</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="n">state</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="n">xQueueReceive</span><span class="p">(</span><span class="n">led_queue</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span><span class="w"> </span><span class="n">portMAX_DELAY</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">gpio_put</span><span class="p">(</span><span class="n">LED_PIN</span><span class="p">,</span><span class="w"> </span><span class="n">state</span><span class="p">);</span><span class="w"></span> <span class="w"> </span><span class="p">}</span><span class="w"></span> <span class="w"> </span><span class="p">}</span><span class="w"></span> <span class="p">}</span><span class="w"></span> <span class="kt">int</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="n">xTaskCreate</span><span class="p">(</span><span class="n">buttonTask</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;Button&quot;</span><span class="p">,</span><span class="w"> </span><span class="mi">128</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">button_task</span><span class="p">);</span><span class="w"></span> <span class="w"> </span><span class="n">xTaskCreate</span><span class="p">(</span><span class="n">ledTask</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;Led&quot;</span><span class="p">,</span><span class="w"> </span><span class="mi">128</span><span class="p">,</span><span class="w"> </span><span class="nb">NULL</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">led_task</span><span class="p">);</span><span class="w"></span> <span class="w"> </span><span class="n">vTaskStartScheduler</span><span class="p">();</span><span class="w"></span> <span class="w"> </span><span class="c1">// Code will never reach here</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span> <span class="p">}</span><span class="w"></span> </pre></div> <p>Both the buttonTask and the ledTask function will seem to run in parallel and there's a few IPC systems to move data between the various tasks. The code above is not functional but I stripped it down to get the general usage across.</p> <p>I've used this for a few days to make an enormous mess of my codebase. I have created several tasks in my test project:</p> <ul><li>The buttonsTask that polls the i2c gpio expander to check if buttons have been pressed and then put a message on the button queue.</li> <li>The ledTask that sets the right RGB color on the right button by putting a message on the ledQueue;</li> <li>The mainTask that runs the main loop of the project that updates the state based on the button presses.</li> <li>The networkTask that communicates with the Wiznet module.</li> <li>The dhcpTask that is spawned by the networkTask when a network cable is plugged in.</li> <li>The mdnsTask that is spawned by the dhcpTask once an ip address is aquired.</li> <li>the atemTask that is spawned by the mdnsTask when it gets a response from an ATEM device.</li> <li>the viscaTask that does nothing but should send data out the RS-485 port.</li> </ul> <p>This is a lot of tasks and the hardware doesn't even do anything yet except appear on the network.</p> <p>I ran into a few issues with FreeRTOS. The main annoying one is that printf simply caused things to hang every single time which makes debugging very hard. Sure the gdb debugger works but it's not neat for dumping out DHCP traffic for example.</p> <p>The FreeRTOS also doesn't seem to provide any hardware abstraction at all which means all the code I wrote to communicate with the various chips is not easily re-used.</p> <p>After a few days I created a new clean FreeRTOS project and started porting the various functionalities from the previous version over to try to get a cleaner and more manageable codebase but ended up giving up because blind debugging because there's no serial output is quite annoying. I decided to look what the alternatives have to offer.</p> <h2>Apache NuttX</h2> <p>Another seemingly popular RTOS is NuttX. This project seems a lot closer to what you'd expect from a regular operating system. It makes your microcontroller look like an unix system.</p> <p>First thing the tutorial tells me to do is fetching the pico-sdk and set the environment variable. No problem, I already have the sdk in /usr/share and that environment variable already exists on my system. Suprisingly this made the build fail because NuttX decides that it really needs to overwrite the version.h file in my pico-sdk for which it doesn't have permissions... why...</p> <p>After doing the initial setup of building a minimal NuttX firmware for my board I connected to the serial port and was greeted by an actual shell.</p> <pre><code>nsh&gt; uptime 00:01:34 up 0:01, load average: 0.00, 0.00, 0.00 nsh&gt; uname NuttX nsh&gt; uname -a NuttX 12.5.1 9d6e2b97fb May 6 2024 15:18:54 arm raspberrypi-pico</code></pre> <p>It looks like I'd just be able to write an app for this operating system and have it auto-launch on boot. Since this tries to do the Unix thing it also has a filesystem of course so the hardware has FS abstractions like <code>/dev/i2c0</code> and <code>/dev/adc0</code>. </p> <p>One thing I liked a lot was that it's build around menuconfig/Kconfig which I'm already used to for Linux development. This also means there's an actual hardware driver system and the GPIO expander chip I've used for the buttons already had a driver. The menuconfig system also allows me to configure the pin muxing of the rp2040 chip so I don't have to keep constants around with pin numbers and do a bunch of hardware setup to make my i2c bus work. I can just go into the menuconfig and tell it that i2c0 of the pico is used and that it's on two specific pins. I've also enabled the i2c testing utility as one of the apps that will be build into the firmware.</p> <pre><code>nsh&gt; i2c dev 0 79 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- -- -- nsh&gt; </code></pre> <p>Well uuuuh... yup the basics aren't working. I've spend a bit of time going through the rp2040 setup code and the various i2c related menuconfig options but it seems like this just doesn't really work...</p> <p><b>Update: This actually works fine but I managed to make a config mistake that broke the i2c bus, I have gotten the board to work more now on NuttX and will write a more detailed update post in the future for this. Consider the rest of this section as most likely invalid.</b></p> <p>I also have not figured out yet how I can tell NuttX that my gpio buttons are behind the gpio extender, or how to actually link the gpio extender to my non-functional i2c bus.</p> <p>Another thing that annoyed me is that I had to re-clone the nuttx repository multiple times simply because sometimes one of the configure.sh commands would fail which would leave the repository in an inconsistent state and the distclean command wouldn't work because the repository was in an inconsistent state. Really the classic "configure.sh: you are already configured; distclean: you are not configured yet"</p> <p>Unix-like seems great at first glance, but I don't really want to deal with filesystem paths on a microcontroller for a pretend filesystem. I also don't need a shell in my production system, it should just run my code.</p> <h2>Zephyr</h2> <p>So next on the list is Zephyr. This provides a python utility to set up a project which should make things a bit easier, or it's a sign something is terribly overcomplicated.</p> <p>The very first thing this project does is pull in 5GB of git repositories which includes the entire HAL library for every chip under the sun. The second thing it does is for some reason mess with my user-wide cmake stuff on my system.</p> <p>After that the tutorial told me to install the Zephyr SDK:</p> <blockquote>The <a href="https://docs.zephyrproject.org/latest/develop/toolchains/zephyr_sdk.html#toolchain-zephyr-sdk">Zephyr Software Development Kit (SDK)</a> contains toolchains for each of Zephyr’s supported architectures, which include a compiler, assembler, linker and other programs required to build Zephyr applications.<br><br>It also contains additional host tools, such as custom QEMU and OpenOCD builds that are used to emulate, flash and debug Zephyr applications.</blockquote> <p>Yeah no thanks, I have already several perfectly fine ARM toolchains and I don't really want to either build or fetch precompiled compilers for every architecture Zephyr supports, lets see if I can get away with not installing this.</p> <p>After some messing around I figured out how to get away with it. There need to be two command line options set for cross compiling:</p> <div class="highlight"><pre><span></span><span class="gp">$ </span><span class="nb">export</span> <span class="nv">ZEPHYR_TOOLCHAIN_VARIANT</span><span class="o">=</span>cross-compile <span class="gp">$ </span><span class="nb">export</span> <span class="nv">CROSS_COMPILE</span><span class="o">=</span>/usr/bin/arm-none-eabi- <span class="gp">$ </span>west build -p always -b sparkfun_pro_micro_rp2040 samples/basic/blinky </pre></div> <p>One thing I also found out is that the Raspberry Pi Pico is not actually supported, only other boards that have the same SoC. No worries, these boards are practically the same. The very second issue I hit is that the blinky demo doesn't build because it requires <code>led0</code> to be defined to have something to blink.</p> <p>It turns out the Sparkfun pro Micro RP2040 does not actually have a simple gpio led to blink but a ws2812B adressable led. </p> <p>So I started following the custom board manual which told me to copy a random other board because that's how it always goes. Maybe if you already have a meta tool to set-up a project make it create this scaffolding.</p> <p>In the end I did not manage to build for my board because it simply wouldn't start to exist after fixing all the errors and warnings in the build.</p> <h2>Conclusion</h2> <p>Well at least with FreeRTOS I managed to building some of my own application. I guess I have to follow the online instructions of replacing printf with another printf implementation and make sure to call the different function everywhere.</p> <p>I'll probably continue on trying to get FreeRTOS to do the things I want since it's the only one that can be simply integrated in your own environment instead of the other way around.</p> Digital audio mixer pt.2https://blog.brixit.nl/digital-audio-mixer-pt-2/97ElectronicsMartijn BraamSat, 09 Mar 2024 10:08:03 -0000<p>Since writing my <a href="https://blog.brixit.nl/building-a-digital-audio-mixer/">previous post</a> about the digital audio mixing I've made some significant progress. Initially my code was running on an off-the-shelf Teensy 4.1 and using only the digital input and output I could use directly with an external ADC/DAC. Shortly after writing that post I received the Teensy Audio Shield which makes the test setup a bit easier to deal with.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709912877/20240308_0054.jpg" class="kg-image"><figcaption>Teensy Audio Board Rev. D2</figcaption></figure> <p>This is a simple board that connects an NXP SGTL5000 codec chip to the Teensy. It provides a stereo in and output on the header at the top in this picture. It also has a fairly decent built-in headphone amplifier which is exposed with the 3.5mm jack at the bottom of the picture.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709913077/image.png" class="kg-image"><figcaption>SGTL5000 block diagram from the datasheet</figcaption></figure> <p>With this board I replaced the S/PDIF input and output in the code with an i2s interface which completed this into a self-contained 2-input and 2-output audio mixer. But how to expand from here? That's more in the realm of custom hardware since the Teensy Audio Shield is practically the only board you can order for Teensy Audio.</p> <p>So a custom board... There's a few other supported codecs by the audio library. The issue is that most of the codecs in the list are EOL or not recommended for new designs. They are also mostly out of stock so I postponed this idea for a bit.</p> <h2>FOSDEM 2024</h2> <p>So roughly at this point in the process <a href="https://fosdem.org/2024/">FOSDEM</a> happened. Not only is this a very interesting open source software event but it also manages to run ~30 concurrent live streams and in-room audio mixes with an ad-hoc setup with only about a tenth of the amount of personnel you'd expect to be needed to pull this off.</p> <p>To pull this off FOSDEM uses custom build "<a href="https://archive.fosdem.org/2020/schedule/event/videobox/">video boxes</a>" that contain half the equipment needed to run all the multimedia in every room. Two of these (identical) boxes are put in each room for the complete setup. This setup has evolved a lot over the years.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709914101/fosdem-2020-video-box.JPG" class="kg-image"><figcaption>FOSDEM video box from 2020</figcaption></figure> <p>This is one of the examples: the nice laser-cut video boxes used from ~2015 to 2023. These have the job of capturing from the HDMI video inputs and send off the video from the connected camera and speaker laptop to be encoded for the live-stream. This is a very nice and compact solution for deploying a room for FOSDEM and during the event these are controlled remotely from the central operations center allowing only a few people to monitor and manage all the video streams.</p> <p>But what about the audio? There's a microphone for the speaker and one or two microphones for audience questions but these don't hook up to the custom boxes. The room audio in most rooms is handled by a Yamaha MG10 audio desk. This is easily enough for mixing together the audio from 4 sources but has one major downside: you have to be physically present to turn the knobs to adjust anything.</p> <p>While video is usually pretty great while watching back the talks I've noticed there's sometimes a few audio issues like microphones that are clipping. The perfect solution for this all is a digital audio mixer that can be controlled remotely of course, but those are way larger and more expensive.</p> <p>It turns out FOSDEM is that perfect target for a 4-in 4-out audio mixer that is controlled over USB instead of physical controls. I'm very glad I managed to meet up with the FOSDEM video team, which lead to...</p> <h2>FOSDEM Audio Board</h2> <p>So the FOSDEM setup has a few very interesting constraints for an audio mixer:</p> <ul><li>There&#x27;s two audio mixes, one for in-room audio and one for the live-stream</li> <li>All mixes are mono, there&#x27;s not much sense in stereo for running a few microphones.</li> <li>All sources are line-level. The microphones at FOSDEM are all wireless and the receivers can output line-level signals so no need for microphone pre-amps. This massively simplifies the design of the analog inputs.</li> <li>Since no condenser microphones or phantom-powered DI boxes are used no +48V phantom power supply is required.</li> <li>The current iteration of video boxes are inside 19&quot; 1U rack cases which constrains the size of the audio mixer a lot.</li> </ul> <p>So I have practically no experience with designing audio gear. Luckily the additional constraints massively simplify the design which is great for cost optimization as well. So I did the most dangerous thing a software developer can do: I launched Kicad.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709916669/image.png" class="kg-image"><figcaption>PCB of the FOSDEM Audio Interface rev.A</figcaption></figure> <p>For the design I decided to put two of the SGTL5000 codecs on the board. It's one of the few supported codecs that are still available and they already deal nicely with line-level signals. Another great feature of these chips is that they include analog gain control which saves me from having to implement a digital-controlled analog gain circuit which sounds difficult and expensive. Having a built-in headphone amp is also great for adding a headphone connection for monitoring in the room.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709917075/image.png" class="kg-image"><figcaption>This is not only my block diagram but also the schematic itself thanks to Kicad sub-schematics :D</figcaption></figure> <p>This is how the hardware is connected internally. There are 3 analog XLR inputs for connecting the microphone receivers and the fourth input is a 3.5mm jack that is hooked up for some simple analog mono-summing of the incoming signal. This 3.5mm jack will connect to the audio output of the HDMI capture card connected to the laptop of the presenter.</p> <p>One of the codecs also provides the 2 XLR outputs. One connects to the existing audio speakers in the room and the other connects to the audio input of the camera. The headphone connector is connected to the outputs of the second codec so that audio mix can be controlled separately in software.</p> <p>The only thing that needs to happen in addition to the schematic of the original Teensy Audio Shield is dealing with balanced signals. Sadly there isn't a "getting started with designing audio interfaces" book but I found the brilliant website from <a href="https://sound-au.com/articles/balanced-io.htm">Elliot Sound Products</a> that has a lot of information on these circuits. The inputs and outputs have a pair of opamps to convert the signals. This is implemented with TL072 opamps because they are cheap, available and have 2 opamps in a single chip. This means the whole input circuit is a single chip and a few passives.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709917710/image.png" class="kg-image"><figcaption>A single balanced output channel on the audio board</figcaption></figure> <p>The other part that needed figuring out is the power part. This surprisingly was a lot more work than the actual analog audio handling. The whole design is powered from 5V from the USB port but there is a lot of separate voltages needed to run all the audio hardware.</p> <p>The codec chips need two 3.3v rails and one 1.8v rail to function. One of the 3.3v rails is for the digital part and one for the analog part. The opamp circuits are even more complicated because they need a positive and negative voltage rail to function. </p> <p>The exact voltage for the opamps does not matter much but it has to be high enough that they are always above the analog audio input levels and because these are cheap opamps it needs a few volts extra because the TL072 cannot process signals close to the supply voltage which results in distortion. On the other hand the output voltage needs to be below 40 volts because I'm building an audio interface and not a smoke machine. In this design the supplies are +9V and -9V which brings the total voltage on the opamp to 18V.</p> <p>To generate the positive and negative 9V rails I first generate +12V and -12V with a switching regulator and then feed those into an LDO to filter out the switching noise from the switching regulator. After dealing with all this I now finally understand why so much of the audio gear has old-school transformers to power them: it makes it very easy to make a dual-rail supply.</p> <p>It was not easy to figure out how to get the dual rails from 5V at all. To start I decided to open up one of the USB powered audio interfaces I already had and see what the designers of that device did to fix this. In this case it was a Tascam US-2x2. </p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709918861/P1470213.JPG" class="kg-image"></figure> <p>This is a picture of the power supply section of that audio interface. It contains a lot of different voltage regulators to make the various rails. It has to deal with a few extra voltages compared to my design since this also has +48V phantom power. After measuring it the main negative rail of this board was generated by the 34063 chip at the top of that picture. This is used as an inverting switching regulator in this case. The positive rail for opamps in this design is generated by the tiny chip labelled U26 all the way on the bottom of the picture, I've not been able to identify this chip.</p> <p>This all together lead to my initial design:</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709919148/image.png" class="kg-image"></figure> <p>The top left part of the board generates the voltages for the opamp circuits and the top right has the regulators for the codecs (and the PC input jack). This version of the power supply was not complete yet since it was lacking a few capacitors and I found out that the inductor I selected was way too small to function correctly.</p> <p>This revision of the power supply was scrapped because with the correct inductor the power supply simply became too large for the board and I didn't want to make the board any larger since the inside of the FOSDEM box is very space constrained.</p> <p>The MCP34063 is also decades old technology by now. It's a switching regulator that runs at 100Khz max. In this design the switching frequency would be ~60Khz but this results in needing a large capacitor and inductor on the board.</p> <p>In the current revision of the board this has been replaced with the TPS65130 regulator. This is a way more modern switching regulator running at 1.3Mhz instead. This chip is a bit more expensive but it generates both the positive rail and negative rail with a single chip and due to the order of magnitude larger switching frequency the inductor and capacitor can be way smaller. The end result is a more compact and cheaper power supply.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709919624/image.png" class="kg-image"></figure> <p>This is the board that was ordered as prototype. It's mostly the same as the board shown above but it has an extra header exposing a few I/O pins of the teensy for prototyping and the PC input connector has moved so the jack will be above the board to waste less space in the case.</p> <h2>The actual hardware</h2> <p> After waiting some days I received this partially assembled board:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709919939/PXL_20240223_094706542.jpg" class="kg-image"></figure> <p>After soldering on the connectors I powered it on and checked the voltage rails, it all seemed fine. Then after powering it on a second time it started making a screeching coil-whine sound and within a second the 12V generator made the bottom of the PCB too hot to touch. After initially working for a bit I didn't get it to generate the correct voltages again, even after restarting the board. Sometimes when powering it on it heated up again, sometimes it didn't but for some reason the output of the +/- 12V supply was +6V/-4.5V instead.</p> <p>This issue plagued me for some days until one day I had plugged in my headphones in the board while measuring things with the multimeter and suddenly the weird noise from the inputs disappeared. This happened when I had the probe of the multimeter on one of the pads of the diode for the negative supply. </p> <p>It turns out that the solder connection on that diode was not reliable and after heating up that pad with a soldering iron for a second I managed to get the board running at the right voltage again... but only sometimes. At least the board was now reliable enough that I could work a bit on the firmware and all the functionality that didn't depend on the opamps was working great.</p> <p>This is one of the moments where it's a massive help when other people double-check your schematics. It turns out I copy-pasted a few capacitors and forgot to adjust the values. Specifically I had a capacitor in the feedback path for the switching regulator that was two orders of magnitude too large. It turns out those capacitors were optional anyway according to the datasheet so after removing those from the power supply it already became a lot more reliable. Still it sometimes failed to start and unreliable equipment in a live environment is a non-starter.</p> <p>After verifying everything it turned out that there were more capacitors with wrong values and sadly these capacitors were actually required. So I took the boards to someone with actual electronics experience and also the correct gear to debug the boards. A few capacitors have been removed, a few have been added and the result is beautiful soldering work like this:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709921261/20240308_0052.jpg" class="kg-image"></figure> <p>Which is a 100nF capacitor soldered on top of an 10uF capacitor to further reduce the ripple of the power supply. The board has since been adjusted to actually have these capacitors included. After an evening of messing with the board to minimize ripple the switching regulator section has turned into quite a battlefield:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709921381/20240308_0046.jpg" class="kg-image"></figure> <p>A few stacked capacitors, a pad I've accidentally ripped when removing capacitors. The power-save pins on the regulator connected to GND instead of VCC and in the bottom left corner a beautiful stack of 3 capacitors and a 1k resistor on the output of the regulator.</p> <p>Luckily after all this the regulator started working reliably. To make it a bit nicer on my desk I also 3d printed a simple front panel for the mixer. I also got a random oled panel from my parts box and connected that to the GPIO pins so I can have a display to show real-time debugging information while testing the software.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709921615/20240308_0036.jpg" class="kg-image"></figure> <p>The source Kicad files for the audio board are available at <a href="https://git.sr.ht/~martijnbraam/mixolydian-4x4">https://git.sr.ht/~martijnbraam/mixolydian-4x4</a></p> <h2>The software side</h2> <p>For the software I started off from the Arduino IDE project I had for my previous blog post. The design for the audio at FOSDEM would be simpler though, no networking is needed at the mixer but instead the USB connection would be used for control. Adding a network port for the audio mixer would mean it needs more switch ports in the box and since the network switch ports are on the outside it would need an ugly cable coming out to the front of the box to connect it up.</p> <p>The control of the mixer would happen through the infrastructure FOSDEM already has where the volunteers in the rooms can control the boxes with a webpage from a phone and some software on the SBC inside the box will communicate with the mixer over a serial port.</p> <p>There were also a few small software issues to deal with due to the hardware. There are two SGTL5000 codecs on the board. There are two variants of this chip, the 32 and the 20 pin version where the major difference with the 20 pin version is that it doesn't have any address pins for the I²C bus. Sadly the 32 pin variant wasn't really available so the codecs are now using the same address but on different I²C busses of the Teensy. The audio library is hard-coded to have the codec on I²C0 of the Teensy so this requires a bit of patching.</p> <p>The issue of patching the Teensy audio library is that libraries in the Arduino build system are a mess and the audio library is also part of the Teensy core in the IDE instead of a separate library. After messing with it to try to make it a bit more sane I decided to convert the Arduino IDE project to a plain cmake project that pulls in the various parts of the Teensy core as git submodules and has the whole audio library vendored in. This also means it's now possible to build the firmware for the FOSDEM audio mixer without first downloading a pre-compiled ARM compiler so it can run quickly in Alpine in CI.</p> <p>The code for the cmake-ified project is available at <a href="https://git.sr.ht/~martijnbraam/mixolydian-4x4-fw">https://git.sr.ht/~martijnbraam/mixolydian-4x4-fw</a></p> <p>I had also added an OLED panel to the board for testing so I added a bit of code that displays the audio levels of all the inputs and outputs of the board on that screen.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709922412/20240308_0040.jpg" class="kg-image"></figure> <p>This part will definitely be different on the FOSDEM boxes since these oleds are very small and the PCB behind it are large enough that it barely fits in the height of an 1U rack case. It is very cool to have audio level bars in realtime though and if there is a color display it could even be usable enough to see if the levels are correct.</p> <p>There's also an initial implementation of the serial control protocol. The exact protocol has not been fully thought out yet but at least it does the one thing all serial protocols should do: print something useful when sending a newline.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709922665/image.png" class="kg-image"></figure> <p>When sending a newline to the interface it will print the state of the mixing matrix as percentages.</p> <p>The source code is of course available at </p> <h2>Further work</h2> <p>The audio interface has been tested with one of the wireless sets from FOSDEM, which is a Sennheiser AVX ME2 set. The audio seems to work great with this and for running FOSDEM there can simply be 3 receivers plugged into the box. </p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1709923660/avx-receiver.jpg" class="kg-image"><figcaption>Sennheiser AVX receiver plugged into the mixer</figcaption></figure> <p>But can this be simpler? The AVX system works with DECT chips so maybe it would be possible to make the worlds first digital audio mixer with built-in DECT base-station :D</p> <h2>Conclusion</h2> <p>This is certainly a lot of progress for the audio hardware part of the mixer project. While a lot of the hardware is specified for the exact requirements of a FOSDEM room it does provide a neat base to work off for designing digital audio mixers. The parts of the hardware design are modular enough to reconfigure them for other audio needs and now the line-level inputs are working it will be neat to figure out a digitally controlled microphone preamp. Or have hi-z input for instruments. Or implement a phantom power supply.</p> <p>It's always a ton of fun to figure out new systems and instead of learning a random new programming language it's hardware design stuff for once. I absolutely couldn't've done it without the expertise of the electrical engineers that helped me design this, especially Thea who actually knows how switching regulators work :)</p> <p>Hopefully the revision B design with the improvements from all the testing that happened with this board will be in the FOSDEM boxes in FOSDEM 2025 but I'll save that for a third part in this series of posts.</p> The MNT keyboard reviewedhttps://blog.brixit.nl/the-mnt-keyboard-reviewed/92LinuxMartijn BraamTue, 19 Dec 2023 23:59:01 -0000<p>MNT Research is one of those few companies that actually releases open source hardware. Instead of just getting a <a href="https://mntre.com/documentation/reform-keyboard-v3-manual.pdf">schematic</a> with your hardware (which is great even by itself) there's the full <a href="https://source.mnt.re/reform/reform/-/tree/master/reform2-keyboard3-pcb">sources for that schematic</a>, the Kicad parts libraries, the sources for the firmware and even documentation how to use that code.</p> <p>I received my <a href="https://shop.mntre.com/products/mnt-reform-keyboard-30">MNT Standalone Keyboard V3</a> a few days ago so I've been typing on it now for a bit. This is all happening while I'm recovering from covid so I hope if I read back this post in a few days it is actually somewhat coherent :)</p> <p>This being a more niche product sadly does make it a bit on the expensive side. But I must say this is by far the most solid keyboard I've owned. My main keyboard on my desktop is an Das Keyboard 4 ultimate. It's a nice keyboard but it doesn't compare to the full machined aluminium frame on the MNT keyboard.</p> <p>The whole keyboard is mounted on what's basically a 4mm slab of aluminium which has a nice MNT logo machined on it on the bottom</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703022648/20231219_0011.jpg" class="kg-image"></figure> <p>This makes the keyboard feel incredibly solid, even with the rest of the frame taken off it's practically impossible to even bend the keyboard. The second half of the frame is the top edge that screws on the base plate with 8 screws.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703022719/20231219_0012.jpg" class="kg-image"></figure> <p>This is another very carefully designed aluminium part In the close-up above you can see the opening for the USB-C connection for the keyboard and the internal cutouts for the display daughterboard with the screw mounting.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703027123/20231219_0021.jpg" class="kg-image"></figure> <h2>The electronics</h2> <p>This keyboard is based around an Atmega32U4 microcontroller. This is the same keyboard PCB as what's shipped in the MNT Reform laptop so there are two connectors on this board. The USB-C connector is what's exposed on the standalone keyboard and the laptop presumably uses the USB header that's beside it.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703023510/20231219_0015.jpg" class="kg-image"></figure> <p>Beside the USB header is one of the dip switches. SW36 is labeled "STANDALONE" here. This switches the board to use USB power instead of the 3.3V supplied by the laptop mainboard. The ribbon connector is the connection to the OLED display board.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703026316/20231219_0016.jpg" class="kg-image"></figure> <p>On the left side of the display board there is an empty footprint for the standard Atmega programming header and a serial port that's used to connect to the laptop mainboard. Additionally there's a reset button and SW84 which has the confusing label "RG".</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703026421/image.png" class="kg-image"></figure> <p>Thanks to the schematics being available in the manual it's easy to find that this is the switch to enable programming. The rest of the interesting parts is hidden somewhere below the display board or on the bottom side of the PCB possibly. I have not taken the keyboard further apart for this review since all the information I'd ever want is already available in the schematics. The keyboard matrix itself is read out by the Atmega directly which provides the full keyboard functionality and the OLED display is on a small daughterboard to slightly rise it towards the front bezel.</p> <h2>Firmware</h2> <p>Since this is one of the 8-bit Atmel parts it's very easy to build firmware using the gcc-avr compiler packaged in various distributions. All the source files are stored in the <a href="https://source.mnt.re/reform/reform/-/tree/master/reform2-keyboard-fw">firmware repository</a> for the various MNT products.</p> <p>Checking the version of the firmware is pretty easy. With the circle key on the top-right corner of the keyboard the menu on the display opens. You can use the arrow keys to browse to the "System Status" option or just press the "s" key on the keyboard.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703027504/20231220_0008.jpg" class="kg-image"></figure> <p>Which shows the hardware revision this firmware was build for and the version that was specified when building:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703027858/20231220_0018.jpg" class="kg-image"></figure> <p>It seems like the "g" at the start of the commit has was accidental here and it refers to commit <code>7e73483</code> in the firmware repository. This seems to be the newest tag when the keyboard was shipped so that makes sense.</p> <p>So lets change something! The key in the bottom left corner of the keyboard is the Hyper key instead of Ctrl as you'd expect from most keyboards. The Ctrl key is moved in place of the Caps Lock button on normal keyboard layouts which is great for a lot of uses. I never use Hyper though so I want to change that key to be my second Ctrl key.</p> <p>The readme specifies that the keyboard layout is defined in the various <code>matrix_*</code> files so after reading around a bit it seems like I have to edit <code>matrix_3.h</code> for my keyboard.</p> <p>Reading the manual again I realized that doing this makes me lose access to the media keys since those are defined as "Hyper+F*" for the various media actions. To fix that I changed the right control button into the Hyper key, this is the button with the three dots on it. My resulting code change:</p> <pre><code>diff --git a/reform2-keyboard-fw/matrix_3.h b/reform2-keyboard-fw/matrix_3.h index bb72f6d..f9db133 100644 --- a/reform2-keyboard-fw/matrix_3.h +++ b/reform2-keyboard-fw/matrix_3.h @@ -25,7 +25,7 @@ // Sixth row #define MATRIX3_DEFAULT_ROW_6 \ - HID_KEYBOARD_SC_EXECUTE,\ + HID_KEYBOARD_SC_LEFT_CONTROL,\ HID_KEYBOARD_SC_LEFT_GUI,\ HID_KEYBOARD_SC_LEFT_ALT,\ KEY_SPACE,\ @@ -33,7 +33,7 @@ KEY_SPACE,\ KEY_SPACE,\ HID_KEYBOARD_SC_RIGHT_ALT,\ - HID_KEYBOARD_SC_RIGHT_CONTROL,\ + HID_KEYBOARD_SC_EXECUTE,\ HID_KEYBOARD_SC_LEFT_ARROW,\ HID_KEYBOARD_SC_DOWN_ARROW,\ HID_KEYBOARD_SC_RIGHT_ARROW </code></pre> <p>Now to build this there's a simple Makefile. Since I've already programmed Atmega parts on this machine I already have the compiler installed making this very quick and easy.</p> <p>I ended up compiling with the following command:</p> <div class="highlight"><pre><span></span><span class="gp">$ </span>make <span class="nv">REFORM_KBD_OPTIONS</span><span class="o">=</span><span class="s2">&quot;-DKBD_VARIANT_3 -DKBD_MODE_STANDALONE -DKBD_FW_VERSION=\\\&quot;Martijn\\\&quot;&quot;</span> </pre></div> <p>This is straight from the readme with an additional define to set the firmware version to "Martijn". After building this I got the <code>keyboard.hex</code> file that can be flashed.</p> <p>The flashing is as simple as running the <code>flash.sh</code> script. This will instruct you to press "Circle + X" to enter flashing mode and then run the neccesary commands to flash the keyboard. After running this I noticed that the delete key on the keyboard was no longer a delete key. It turns out I don't have <code>VARIANT_3</code> but instead <code>VARIANT_3_US</code>. A quick rebuild and reflash also fixes that.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1703029671/20231220_0021.jpg" class="kg-image"><figcaption>The brightness differences on the display are a camera artifact</figcaption></figure> <p>Tadaa! My own name in the firmware version field. It's super easy to mess with this firmware.</p> <h2>The keyboard itself</h2> <p>Well the keyboard works just fine as a keyboard. Typing on this keyboard takes a few minutes to get used to compared to my normal keyboard since all the keys are slightly closer together. The split spacebar is also annoying me a bit. It turns out that the left split in the spacebar is <i>exactly</i> the spot where I normally hit the spacebar with my thumb.</p> <p>The switches are nice and clicky (but silent, I have the version with brown switches in them). Overall the keyboard just does what it needs to. The standard layout is quite unusual but everything can be changed with open firmware so I'm confident I can get to a layout I'm 100% happy with.</p> <h2>Conclusion</h2> <p>This is an extremely solid and very compact keyboard I can easily throw in my backpack. It being an USB-C keyboard makes it fit neatly with all my other random cables I usually take with me.</p> <p>It might be slightly more expensive than similar keyboards, but I don't know of similar keyboards with a case this rugged and the display functionality (I forgot to mention you can use HID reports from the host to write custom content to the display from your computer). The openness of this product makes the extra cost certainly worth it for me.</p> <p>I'll probably be messing with the firmware for this keyboard a bit more while I use it. There's some small things to fix like the device reporting the name "LUFA Keyboard Demo Application" in Linux instead of a neater "MNT Keyboard" or something.</p> Making an USB Ethernet adapter work [SR9700]https://blog.brixit.nl/making-a-usb-ethernet-adapter-work-sr9700/86LinuxMartijn BraamSat, 28 Oct 2023 17:41:51 -0000<p>I just needed a simple USB to Ethernet adapter for testing. It does not need to be fast, it does not need to reach USB 3.0 speeds or gigabit speeds. I own two other USB ethernet adapters that have various reliability issues so I got a random cheap one from ebay.</p> <p>The adapter I ended up getting was the SR-QF9700 that does not have an actual brand on it. When plugging it into my laptop there was a slight issue though. It shows up as a CD drive...</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1698514364/image.png" class="kg-image"></figure> <p>This is one of those annoying features where USB dongles will present a CD drive with drivers to windows to make them work, ignoring that this is just an rndis device that has worked without special drivers <i><i>for decades</i></i>. This wouldn't be problematic if it exposed both the ethernet interface and the driver CD at the same time so I could just ignore it.</p> <p>In theory this can be made working by hacks such as <code>usb_modeswitch</code> but I've seen several forum posts online that mention it not working on this hardware. Even if that did solve the issue, it still makes this device useless for me because I don't want to mess with special software on every device I plug it into.</p> <p>I was ready to add this adapter to the e-waste problem but then I thought about how this would be handled from the manufacturing side. The chip is generic but those driver installers are usually branded. There needs to be somewhere to actually store the drivers on the device...</p> <h2>Taking it apart</h2> <p>This thing is very easy to open. There are no screws or clips in it at all, it's all held together by the sticker with the model number.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1698514385/20231028_0014.jpg" class="kg-image"></figure> <p>Inside the adapter is a simple PCB that has a crystal and the USB ethernet module. This particular adapter contains a CoreChips SR9700 single-chip ethernet controller. Doing anything interesting with this would be hard, there's not really public documentation for this chip except for the pin descriptions. The SR9900 datasheet mentions some internal one-time programmable memory, but it being one-time programmable does not really help me.</p> <p>So lets look at the back of the PCB</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1698514403/20231028_0006.jpg" class="kg-image"></figure> <p>Another chip! This is an 4MBit SPI Nor flash chip. This must be the chip actually storing the windows drivers.</p> <p>So what is the behavior of the ethernet controller chip if this chip is absent...</p> <p>SPI flash pinouts are pretty standardized, but I looked up the datasheet for this specific chip to be sure:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1698514414/image.png" class="kg-image"></figure> <p>There's many ways to make a SPI flash chip temporarily not function. I decided to jam a screwdriver between the <code>CS</code> and <code>SO</code> pin to make the chip unable to respond to SPI communication. With the screwdriver in place I plugged in the USB cable and behold:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1698514424/image.png" class="kg-image"></figure> <p>Ethernet! Well that was easy. Now to make this permanent I shorted together the same two pins with my soldering iron since removing the whole chip is way harder.</p> <p>So instead of messing with any device/computer I want to plug this ethernet adapter into I now have a normal USB ethernet adapter with just one extra solder connection :)</p> <p><br></p> Building a digital audio mixerhttps://blog.brixit.nl/building-a-digital-audio-mixer/83ElectronicsMartijn BraamWed, 04 Oct 2023 17:41:04 -0000<p>Digital audio mixers have always been the magical tech from the future when I started messing with audio mixers. Back then the cost of those mixers was extremely far out of reach for home use, into the thousands of euros.</p> <p>Prices for digital mixing has come down a lot, it's now possible to use tablets as control interface instead of having physical controls which drives down the price a lot. I've been using a borrowed Behringer X-air 18 for a while which is priced around €650. That mixer had to go back into production so now I'm mixer-less again.</p> <h2>Looking at the options</h2> <p>There's an annoying issue with audio mixers. To get a mixer that has a lot of features you also have to get a lot of channels. I used a lot of the routing and processing features of the X-air 18 but I only used 4 of the channels. If I switch to a smaller/cheaper version in the X-air series like the X-air 12 I still have way too many channels but I lose the multichannel usb audio interface part of it. Even the smallest version still takes up 2 rack units with features I don't need. </p> <p>Going to the competition of the X-air series doesn't help either. There's only significantly more expensive and larger options or more limited hardware. This leaves me once again with the last option: build it myself.</p> <h2>Teensy Audio Library</h2> <p>While looking at options for hardware with audio I/O I came across the <a href="https://www.pjrc.com/store/teensy41.html">Teensy 4.1</a>. This is a microcontroller board that has built-in 10/100 ethernet and has several digital audio interfaces. More importantly it already has a very nice library called the <a href="https://www.pjrc.com/teensy/td_libs_Audio.html">Teensy Audio Library</a> for creating digital audio pipelines with it.</p> <p>The hardware has a native USB interface that already has the libraries available to make the Teensy act like an USB audio interface. It also has two i2s/tdm interfaces for hooking up DAC/ADC chips. It is probably possible to create a 16-in 16-out mixer with this chip.</p> <p>So I ordered the Teensy and started creating the software. The audio pipeline is easily designed using the web editor for the audio library to generate the pipeline code:</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1696435005/image.png" class="kg-image"></figure> <p>This creates a basic audio mixer with 6 inputs and 4 outputs. Since I don't have any extra hardware yet the development is done purely using the stereo USB input and output.</p> <p>Every input channel has a <code>biquad</code> block which implements a parametric 4-band EQ. Then an <code>amp</code> block to do dynamics together with the detected signal level from the <code>rms</code> blocks. This creates a very simple channel strips with a compressor and EQ. </p> <p>The second part of the pipeline is a fully connected 6x4 matrix of mixers. This allows routing any of the inputs to any of the outputs with signal levels controlled by the faders on the mixer.</p> <p>After the mixer matrix there's <code>amp</code> blocks again for the output volume faders and <code>rms</code> blocks for the output VU meters.</p> <h2>Network control</h2> <p>So suprisingly the full DSP audio pipeline part was the easy part of the project. Just loading the generated code into the Teensy was enough to get audio running through the hardware after setting some default values in the blocks and hardcoding some values for the routing matrix. But a mixer you can't control or monitor is not a very useful mixer.</p> <p>I want to control the mixer over a network connection because I got used to being able to open the mixer interface on any PC here to control my audio routing, in my case the audio output of multiple computers is routed to the mixer to get a mix to my headphones.</p> <p>I also don't want to hardcode the control application I'd have to write for this mixer for this specific hardware so I have designed a network protocol that on connection describes the functionality and routing matrix to the control application and the control application dynamically creates a visual interface for it.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1696436026/image_6.png" class="kg-image"><figcaption>Prototype control application</figcaption></figure> <p>The control application is a Python GTK3 application and the Teensy side has a c++ class that implements this network protocol on top of the Teensy native ethernet controller. The network control code is fully separated from the audio code and with a few <code>#ifdef</code>s can probably be made more universal than that.</p> <p>The network library (I've called it Mixolydian) broadcasts the existance of the mixer using mDNS so the clients can discover it and show a nice list of detected hardware in the UI. It has a TCP protocol for controlling the mixer that allows multiple clients to be connected at the same time and seeing the changes in real time. It also has a separate UDP protocol for sending over real time audio meters.</p> <p>With this together there's now the components to make a digital audio mixer on any platform and add in the networking class to have a control application for it.</p> <h2>So why flexibility</h2> <p>The issue with making a 4 channel digital mixer is not everyone needs the same 4 channels. I need USB signals and multiple outputs. You might need 5 analog inputs instead and AES/EBU out only. So instead of making the exact mixer I need I made the opensource base to make any digital mixer. The protocol accounts for mixers that are significantly larger than what I made on a Teensy, the hard limit is 65535 connections in total (inputs and outputs combined).</p> <p>Since the Teensy has multiple i2s interfaces which are easily broken out into ribbon connectors and it has some extra SPDIF hardware it is possible to make a somewhat modular tiny digital mixer from this. The only thing you need is plugging in modules and connecting it together in firmware.</p> <p>There's a lot of options with this hardware. I want to put an 8x8 mixer into a 1U rack case with some led bar-graphs in the front. You can also make a 2-channel microphone interface in a desktop case with all hardware controls.</p> <h2>S/PDIF connectivity</h2> <p>So after I got USB signals running as a proof of concept I wanted to get some more audio channels to make it an actually useful audio mixer. I have ordered the Teensy audio shield which provides an unbalanced stereo input and output but while that is shipping through Europe I want to get some more development in.</p> <p>Then I remembered I have an old Behringer Ultramatch Pro in the dusty racks of decommissioned hardware. This is a stereo input/output DAC/ADC that has S/PDIF and AES/EBU connectivity in a single rack unit.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1696439160/20231004_0013.jpg" class="kg-image"><figcaption>The Behringer Ultramatch Pro SRC2496</figcaption></figure> <p>This is a nice unit for debugging since it has many blinkenlights for showing the status of the connection. Luckily getting the audio output from the mixer working through this was very easy. I just added the <code>spdif</code> output block to the audio graph and soldered a cable to pin 14 of the Teensy wired directly to the RCA input of the Ultramatch. Immediately I had audio running to my headphones plugged into the Ultramatch.</p> <p>The second part was getting the analog inputs of the Ultramatch wired as two mono input busses on the mixer so I can plug in microphones. The audio library has a nice input block called <code>spdif_async</code> that takes in any spdif signal and resamples it to match the clock of the Teensy audio pipeline.</p> <p>Sadly this wasn't as simple as just soldering down a wire to pin 15 and getting it working. It turns out that this pin expects a TTL level S/PDIF signal while the Ultramatch outputs a coax S/PDIF signal that is 0.5v peak-to-peak. After trying to breadboard together some converter circuits I saw that the AES output is supposed to be 5V peak-to-peak. I've touched the hot wire from an XLR cable to pin 15 of the Teensy and suddenly I had an S/PDIF lock and input signal in the mixer. This is without connecting up ground or the cold signal from the connection.</p> <p>I have now soldered down this single wire and taped everything down, it's wildly out of spec but works for development :)</p> <h2>Next steps</h2> <p>So short term there's a few things to do. Once I get the Teensy audio shield I'll be able to get audio I/O running using one of the i2s ports on the Teensy and I can put this all in one of those 1U rack project cases with nice connectors.</p> <p>To make this a bit more nice and integrated and professional I'm working on a PCB design that carries a Teensy module and exposes the audio connections on pinheaders and seperate PCBs for connecting CODEC chips to that for audio input and output and probably some 48V phantom power. I'm not sure yet of the design but the nice thing about opensource designs is you can always modify it to what you need.</p> <p>In the long term this needs to switch away from the really nice audio library to go beyond the 16bit 44.1KHz limitation of the library. The sample rate is not that problematic but clipping signals in a 16-bit integer audio pipeline is just way too easy.</p> <p>Hopefully this is the start of a small open source digital audio mixer ecosystem and some more people will build digital audio mixers :D</p> Can't get blinky on the BL602https://blog.brixit.nl/blinky-on-the-bl602/74ElectronicsMartijn BraamMon, 01 May 2023 16:46:41 -0000<p>One of the boards I have in my parts box is the PINE64 BL602 evaluation board. The "PineCone". It's a competitor to WiFi enabled microcontrollers like the Espressif products.</p> <p>I put it aside since when I received it there was not really any software to go along with it and I don't have anywhere near the experience to bring up a board like this.</p> <h2>Getting blinky running 3 years laters</h2> <p>This product has been out for 3 years now so lets see if it has formed a community.</p> <p>So if you are not familiar with blinky, it's the Hello World! of the microcontroller world. The goal is have a led connected to one pin of the board and toggle that pin on and off every second to make a blinking led.</p> <p>Let's check the website first. The official page for this from PINE64 itself is <a href="https://pine64.com/product/pinecone-bl602-evaluation-board/">https://pine64.com/product/pinecone-bl602-evaluation-board/</a>. Since the only official thing is the webshop which doesn't list any documentation or links to progress further on how to use this thing I consider it a dead end. Let's move on to the pine64.org side.</p> <p>The community side is the wiki page at <a href="https://wiki.pine64.org/wiki/PineCone">https://wiki.pine64.org/wiki/PineCone</a>. This has the specs again and a block diagram. It also has the datasheets for the components on the board and the schematics. Not anything I actually need to get started using this product.</p> <p>Let's compare to the Raspberry Pi Pico, a competitor that's doing quite well. When you search for this you land on the official product page <a href="https://www.raspberrypi.com/products/raspberry-pi-pico/">https://www.raspberrypi.com/products/raspberry-pi-pico/</a> that has the shop link, the specifications but more importantly it's a hub linking to the "Getting started" guide and the documentation for users.</p> <p>So lets scroll down on the PineCone wiki page since it's my only hope for docs. Thee quarters down this long wiki page is a list of flashing utilities. I don't have anything to flash yet but I look forward to figuring which of the 6 listed flashers fits my personality best /s.</p> <p>The rest of this page is links to random github repositories and blog articles. The top link being the link to the SDK for this board. It links to <a href="https://github.com/pine64/bl_iot_sdk">https://github.com/pine64/bl_iot_sdk</a> and is labeled as "compilers, linkers, and all the code to build on Windows, Linux (x86_64), and MacOS". The last activity here was 2 years ago. This seems mostly focussed on the reverse engineering effort but lets have a look.</p> <h2>bl_iot_sdk</h2> <p>This repository has a README with again more links to a lot of different places. One of these is linking to <a href="https://pine64.github.io/bl602-docs/">https://pine64.github.io/bl602-docs/</a> which is a sphinx generated documentation site. This is several pages too deep from the official pine64 website but I forgive that since it has the magical words I've been looking for:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1682954202/image.png" class="kg-image"></figure> <p>So I'm going down this path, it almost seems familiar to the way the Pi Pico sdk is used. Clone the repository and create an environment variable pointing to the checkout folder.</p> <p>Next it tells me how to compile the example apps, but I don't want to compile the example apps, those probably work and they are in-tree so those probaby Just Work(tm) without setting up the scaffolding you should do for your own projects. I just want to build a main.c that does blinky. This part of the documentation sadly stops after compiling and flashing the built-in apps.</p> <p>I have continued browsing this documentation for a bit but it does not show at all how to make a minimal blinky example and build it. The rest of the documentation is mainly using the advanced features, various flashing systems and debugging systems and image formats.</p> <p>So to compare this with the Pi Pico documentation: The first thing I find when I read the documentation of the SDK is:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1682954861/image.png" class="kg-image"><figcaption>Blinky in the Pi Pico docs</figcaption></figure> <p>Chapter 1.2 is the first chapter with actual documentation and it starts with all the things I want for getting started with yet another microcontroller board:</p> <ul><li>A very simple .c file that implements blinky without using any advanced features of the hardware.</li> <li>The output of <code>tree</code> showing exactly which folder structure I should have and where the files go</li> <li>The CMakeLists.txt that builds this application</li> </ul> <p>The BL602 documentation has shown me none of the above yet. At this point I give up and just open 20 tabs in my browser with all the linked pages from the wiki.</p> <p>All these links are random flashing utilities, porting new programming languages and porting new frameworks. Also a lot of them showing how to flash the example apps to the thing.</p> <p>PEOPLE DON'T BUY MICROCONTROLLERS TO FLASH EXISTING EXAMPLES</p> <h2>Getting ANYTHING to run</h2> <p>So there is no documentation on how to make blinky for this platform whatsoever. I give up and figure it out myself from the sdk example apps.</p> <p>There's no blinky in the examples, the simplest application I could find was the helloworld one that outputs some text over uart. This uses FreeRTOS to get that working so there's a complete lack of basic single .c file demo applications for this.</p> <h2>More issues</h2> <p>This SDK has multiple things I don't like. The most important one is that it ships prebuilt compilers in the source tree. This is not a proper SDK, this is some Android-level build mess.</p> <p>The whole point of using nice RISC-V boards is that this is a standard architecture, all Linux distributions already ship a maintained RISC-V toolchain so why would I ever use some random precompiled binaries shipped by a Chinese vendor.</p> <p>Why is this using Makefiles to build the thing instead of cmake which the Raspberry Pi foundation have already proven you can integrate way neater. This is just throwing hardware over the wall and linking to the chip vendor documentation, hoping the community can match a proper made tutorial, proper documentation and make a new SDK that's not a mess.</p> <p>I guess I just dump this thing back in the parts bin and wait some years. Quite sad since this seems to be some really nice hardware and a great alternative to the existing microcontroller platforms.</p> Sensors and PCB designhttps://blog.brixit.nl/sensors-and-pcb-design/69ElectronicsMartijn BraamThu, 02 Mar 2023 20:09:12 -0000<p>I do a lot of software development, I do enough of it to be comfortable with the process. I have worked on enough projects and have made enough pieces of software that it's quite easy to quickly spin up a new project to fix a specific issue I'm having.</p> <p>Hardware design is a whole other can of worms. I have played with things like the Arduino and later the STM32 chips, ESP8266 and RP2040. These things are quite neat from a programmers perspective. You write your code in c++ and the included toolchain figures out all the hard parts and flashes the board. Hardware design is also quite simple since it's mostly putting together different hardware modules and breakout boards. Only a basic understanding of the hardware busses is required to get stuff up and running.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677722030/PXL_20230302_015255483.jpg" class="kg-image"></figure> <p>Projects usually look like the picture above. No resistors, capacitors or other components are needed. Just a breadboard (also optional), jumper wires and the IO modules you need to make your idea work.</p> <p>Power is dealt with by the microcontroller board, the modules already include the pull-up resistors and power regulators required and the programmer is also included already.</p> <p>The software side is also simplified and abstracted to the point where it's harder to get your average javascript project working than the embedded projects. You'd never have to open the 440 page atmega 328p manual to write the firmware. You don't even need to know about the register map for GPIO pins since things like digitalWrite(pin, state) exist.</p> <p>It's easy to get a complete prototype running like this but then what?</p> <h2>Putting things in production</h2> <p>By far the easiest way to put the project in production is just shoving the breadboard in a project box and declaring it done. It works but it's not <i>neat</i>. This works if you only need one and it's for yourself.</p> <p>When you need 5? 10? 30? of the same thing then breadboarding it will become a lot less practical. In some cases it's easy enough to just stick the modules on a piece of protoboard and solder it together. When that doesn't scale another option is putting the modules on a custom PCB.</p> <p>This is exactly what I did for a project. This is the first PCB I have ever designed and put into production:</p> <figure class="kg-card kg-gallery-card"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1677723864/breadboarding.jpg" class="kg-image" width="600" height="450"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1677723765/PXL_20230302_020751553.jpg" class="kg-image" width="600" height="450"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1677723741/PXL_20220416_093229174_1.jpg" class="kg-image" width="600" height="450"></div></div></div></figure> <p> It's a PCB that <i>only</i> has pinheaders and connectors on it. This board would have a nodeMCU module and an off-the-shelf rs485 module on it. The board itself is incredibly simple but it did give me a chance to run through the complete process of going from a schematic to an actual physical product. Since it has no actual components on it it has removed a lot of worry if the board will work at all.</p> <p>In this case the board was designed because I needed 15 of this design and this makes a lot more reliable and easy to maintain with the possibility to have neat screw terminals on it for the external connections.</p> <h2>Optimizing the design more</h2> <p>The above design was simple enough to not worry about optimizing it, but another project I wanted to take on is replacing my various sensor nodes with a neater custom design.</p> <figure class="kg-card kg-gallery-card"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1677724089/PXL_20210630_124752069.jpg" class="kg-image" width="600" height="450"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1677724099/PXL_20230302_021534355.jpg" class="kg-image" width="600" height="423"></div></div></div></figure> <p>These boards have been put together around various ESP8266 dev boards, most of them are nodeMCU v3 boards, some are off-the-shelf boards that I receive data from with an rtl-sdr.</p> <p>My plan is to make the one board to rule them all. A custom designed board that makes it easy to connect my existing sensors and allows a bit of extension. The design is based around an ESP-12F module since all my existing sensor code is already written for the various ESP8266 boards. It's also in stock at LCSC so that makes it a lot easier to get fabricated.</p> <p>The design goals for this custom board is:</p> <ul><li>On-board programmer for development</li> <li>Have the ESP module hooked up so it can deep sleep properly</li> <li>Have a built in temperature sensor. Most of my external sensor boards are already temperature/humidity sensors so it makes sense to just include it since it&#x27;s a cheap chip.</li> <li>Have a screw terminal for hooking up onewire devices</li> <li>Expose a silkscreen marked SPI and I2C bus</li> <li>Be able to be powered with an USB Type-C cable or a single lithium cell.</li> <li>Have a built-in charger for the lithium cell.</li> <li>The board should be able to be assembled by <a href="https://jlcpcb.com/">JLCPCB</a> and <a href="https://aisler.net/">Aisler</a></li> </ul> <p>For implementing this I mostly looked at the nodeMCU v3 schematic as reference and also made the basic layout of the PCB similar to that board. Initially I wanted to add the very popular DS18B20 onewire temperature sensor on-board but that got replaced with the SHT30 module that also includes a humidity sensor and connects to the I2C bus instead.</p> <p>For the programming and USB part I included the CP2102 USB-to-serial converter chip that was also included on the nodeMCU board. It is probably overkill for this application though since I'm not using any of the functionality of that chip beside the RX/TX pins. I dropped the auto-reset circuit since I was worried it would interfere with the deep sleep reset circuit that I found more important.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677725133/20230301_0022.jpg" class="kg-image"></figure> <p>One simple change from the nodeMCU design is that I swapped the USB Micro-B connector for a Type-C connector which requires a bit more PCB routing to deal with the reversable plug and two extra resistors to make it up to USB spec. The Type-C spec requires a 5.1kΩ resistor to ground on both CC pins (individually, don't cheap out and connect both CC pins to the same resistor like on the Raspberry Pi)</p> <p>Most of this design work is simply reading the datasheets for the components and following the recommendations there. Some datasheets even have bits of recommended PCB layout in it to make the PCB layout easier.</p> <p>Since all the complicated ESP8266 circuitry is already handled on the SoM I used and the USB-to-serial converter is laid out, the rest is simple connectors. Except... there's power stuff to deal with.</p> <p>Most of the difficulty with this design is figuring out the power design. If the battery connection was not included it would've been relatively straightforward. Just have a power regulator that converts the 5V from the USB connector to the 3.3V for the rest of the board. This is exactly what the nodeMCU board does.</p> <p>In my case the regulator has to deal with power coming from either USB or a single Lithium bringing the input voltage range from 3.4-ish to 5V. There's also a charger chip included to charge the connected cell and a bit of circuitry to switch between the battery and USB power.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677725740/blockdiagram-mqttboard.png" class="kg-image"><figcaption>The block diagram for the sensor board</figcaption></figure> <p>The diagram above shows the final power design. It has the MCP1700 3.3V regulator to replace the LM1117 regulator from the nodeMCU design, the MCP part has a way lower quiescent current which helps with the battery life. The MCP73832 is a single cell li-ion/li-po charger that requires very few external components. In this design it's programmed to charge at 400mA which is not super fast but it should last some months on a single charge anyway.</p> <p>The magic part is this circuit for the power switching:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677726058/image.png" class="kg-image"></figure> <p>This receives 5V from the USB connector or the battery power (or both) and feeds them into the regulator to generate the 3.3v rail. The MOSFET will disconnect the battery power when 5V is supplied and the diode makes sure that the power of the battery won't ever flow back into the 5V part of the schematic.</p> <p>Since there's also a single analog input on the ESP8266 I thought it would be a good idea to use it to read the battery level. This is yet again a decision that added a bunch more complexity. The theory for this is pretty simple: you add a resistor divider to bring the 0V-4.3V of the battery to the 0V-1V range of the analog input and then you'd read the battery voltage with a bit of code. The issue of this is that the resistor divider will not only do the voltage scaling for the ADC but will also be a path for the battery to leak power.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677726538/image.png" class="kg-image"></figure> <p>This is the part that's added to fix that. With a few extra parts the resistor divider can be completely disconnected from the battery stopping the power leakage. The software on the ESP will have to first enable the measuring circuit by toggleing a gpio, reading the value with the ADC and then disableing the gpio again. Since there's already 5.1kΩ resistors on the board I'm using another one here together with a 100kΩ. This re-use of resistor values is done since some manufacturers charge extra per distinct part model used. This brings the voltage range of the battery down to about 0-0.2v. This seemed fine at the time since the ESP has a 10-bit ADC.</p> <p>With using only a fifth of the ADC range the ADC resolution is already brought down to slightly less than 8 bits. But batteries usually don't go down to zero volts. The normal voltage range is 3-4.2V giving a voltage difference of 0.06V after the voltage divider making the result slightly less than 6 bits of resolution. This does not account for ADC noise in the system yet. The result is that the final battery level graph is a bit choppy but at least it functions.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677727433/image.png" class="kg-image"></figure> <p>The last thing left is connectors. I added a footprint for some screw terminals for the OneWire connection. This footprint is basically the same as a pinheader except it has a fancy outline. For the battery I added a JST-PH connector because it seemed the most common one for batteries.</p> <p>The I2C and SPI bus don't have standardized connectors though. But, through a bit of searching I found the <a href="https://digilent.com/reference/_media/reference/pmod/pmod-interface-specification-1_2_0.pdf">pmod spec</a>. This is an open specification made by Digilent to connect modules to their FPGA development boards. It's perfect for this usecase since the interface is just pinheaders with a defined pinout and it defines the power supply as 3.3V which I already have on the board. Some of the sensors I want are even available as pmod modules.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677760255/20230301_0027.jpg" class="kg-image"><figcaption>The two pmod headers on the board</figcaption></figure> <h2>Getting the boards made</h2> <p>After laying out the PCB another complicated process begins. Getting it actually fabricated and assembled. The board has been designed that it can be hand-soldered if needed. No BGA parts are used and the <code>_handsolder</code> variants of the footprints are used for the common parts, which are slightly larger.</p> <p>To not have vendor lock-in the board is designed to be assembled by both JLCPCB and Aisler. JLCPCB is the very cheap option in this case and Aisler is quite neat since it's actually in The Netherlands. The Aisler design rules are less forgiving than JLCPCB so I used those for the board. It mostly mean that I can't use the absolutely tiny vias that JLCPCB can drill.</p> <p>For the assembly service metadata has been added to the schematic. For JLCPCB a <code>LCSC</code> column was added to the part data that contains the part code for ordering from LCSC. For Aisler the parts come from the other big parts warehouses instead like Mouser. For that the <code>MPN</code> column is added that contains the manufacturer part number.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677761073/image.png" class="kg-image"></figure> <p>With this data and the selected footprints the board can be assembled. I left out all the part numberes for the through-hole parts since those are either more expensive or impossible to assemble and also pretty easy to solder manually if required on that specific board.</p> <p>To actually get it made Aisler has a nice plugin for the PCB editor in Kicad: <a href="https://github.com/AislerHQ/PushForKiCad">Aisler Push</a>. With this it's just a single button and the Kicad file will be sent off to Aisler where their servers will generate the necessary fabrication files from it. From there it's using the Aisler website to fix up the MPN matching with actual parts from various suppliers and pressing order.</p> <p>For JLCPCB the process is more complicated. The fabrication files have to be generated manually. There's <a href="https://support.jlcpcb.com/article/194-how-to-generate-gerber-and-drill-files-in-kicad-6">a tutorial</a> for going through the various steps of generating the ~10 files you need and then those can be zipped up and uploaded to the website. Since the LCSC part codes are completely unique the assembly step of this process Just Works(tm) without having to adjust anything on the website.</p> <p>If Aisler had an option to specify the exact part match in the schematic metadata instead it would probably be the easiest option in this case so I hope that gets figured out in the future. Two weeks later I had the board :)</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677762386/20230302_0002.jpg" class="kg-image"></figure> <h2>The Firmware</h2> <p>For the firmware of this board I just re-used the one I had previously written for one of the nodeMCU based sensor boards and extended it for the sleep features. It's pretty basic firmware that does the standard WiFi connection things for the ESP module and connects to my MQTT server to push the sensor readings in a loop.</p> <p>For the battery operated boards that wouldn't be enough though. With that firmware the battery would run out in hours. There's a great series of blog posts from <a href="https://www.bakke.online/index.php/2017/05/22/reducing-wifi-power-consumption-on-esp8266-part-3/">Oppoverbakke </a>that go into great detail on how to optimize deep sleep on the ESP module. Especially the last post on <a href="https://www.bakke.online/index.php/2017/06/24/esp8266-wifi-power-reduction-avoiding-network-scan/">avoiding WiFi scanning</a> is very helpful for reducing power use.</p> <p>To avoid as many delays as possible the WiFi accesspoint mac address and channel are saved into the memory of the RTC inside the module which is the only part of the chip left powered in the deepest sleep mode. Then with that information the module will save around one second of time being awake and connecting. Another optimisation is using a static ip address instead of DHCP to save another second.</p> <p>I don't like having static ip addresses in my network. I have static DHCP leases for everything instead. This is why I extended the RTC connection code to do a DHCP request the first time and then save the received DHCP data in the RTC memory together with a counter to re-do DHCP after some time. This means that my board is still a fully DHCP compliant device without making a DHCP request every time it wakes up, this is what the lease time is for after all. Thanks to this I only have to power down the board when I need to change the IP address instead of reflashing it or building in some configuration web interface.</p> <p>During deep sleep the whole board uses 19.1µA with a half charged battery in my tests and the transmission is down to to a bit less than a second at 70mA, but the power use of the transmissions varies quite a lot.</p> <p>I had one module in production outdoors quickly to test. It sleeps for one minute in deep sleep and doesn't have any other optimisations. Looking at the data from that module it looks like it would last for about two weeks, even with the sub-zero temperatures during the night.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677763269/image.png" class="kg-image"></figure> <p>The battery life for the optimized version has yet to be seen. I have calculated it to be around 3 months but it would take a while to have conclusive results on that. This board uses a "3500mAh" 18650 cell which I assume in the calculations is actually 2000mAh.</p> <h2>Next revisions</h2> <p>During testing and programming I found out I had made a few mistakes with the board design. The most problematic one is that I put zener diodes on the UART lines between the USB-to-serial converter and the ESP module. This was to prevent power from the serial lines to flow into the CP2102 module and powering up that chip partially.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677763683/image.png" class="kg-image"></figure> <p>This did not work. When I got the boards I spend a bit of time figuring out why the programmer couldn't connect. The voltage drop through the diodes I picked is probably too much for the serial communication to work. To make things worse the diodes are completely unnecessary for this since the uart lines won't leak power in deep sleep anyway. Luckily this is easily fixable on my boards by making a solder bridge across the diodes.</p> <p>Another issue on the board is that the battery charger chip gets very hot and is too close to the temperature sensor. I also forgot to add the thermal relief specified in the layout suggestions of the part so while charging that chip is practically constantly at 80°C making the value of the temperature sensor raise by around 5°C. Since this only affects the board when the battery is charging it's not a critical fault.</p> <p>For cost optimization I skipped battery protection on the board. For my uses I just ordered a protected 18650 cell so it should be fine. But since I'm making a second revision board to fix these issues anyway I decided to include a battery protection chip this time around.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1677764022/image.png" class="kg-image"></figure> <p>The protection chip sits between the ground terminal of the battery and the actual board ground and disconnects the battery pack in case of overcurrent, short circuits, overcharge or undercharge.</p> <p>Another small change I did is add an extra pinheader to the board to access the UART lines directly, since pinheaders are basically free anyway.</p> <h2>You should make your own boards</h2> <p>Making boards is fun. I learned a lot during the process and now have some important skills to use in my other projects. Can you really call yourself a full stack programmer if you haven't designed the hardware yourself? :D</p> Reverse engineering the BMD camera HDMI controlhttps://blog.brixit.nl/reverse-engineering-the-bmd-camera-hdmi-control/613a2683aee9f6453ef1c02aElectronicsMartijn BraamThu, 09 Sep 2021 21:32:01 -0000<p>I have a Blackmagic Design 4k camera here (the bmpcc4k) and it has an interesting feature where you can control camera settings when hooking the hdmi cable from the camera to a BMD Atem mini series video switcher. It allows controlling a few settings that are not accessible in the camera settings itself like the values of the primary color corrector.</p> <p>This is the exact same thing that their more expensive cameras for studios can do by hooking up a SDI (video over coax) cable to it and hooking it into a more expensive ATEM switcher.</p> <p>The camera also has Bluetooth control which allows you to change some basic settings like resolution, audio settings and lens control. I have been able to use this but unfortunately I ran into a hardware defect where the touchscreen on the camera just stops working. At this point I did not have bluetooth switched on so I cannot even change my camera settings through that anymore. While this is a defect in the camera looking at the forums BMD did not really want to help with getting this fixed unless I pay <i>a lot</i> to get this fixed.</p> <p>The alternative is figuring out how the HDMI control works, figuring out how much more is possible over that protocol and try to enable bluetooth through that.</p> <h2>Guesses</h2> <p>So the camera is controlled over the same HDMI connection that's used to feed the signal to the atem switcher. This works even with the cheapest cables I have and cable manufacturers love reducing the amount of wires in their cables. This doesn't leave a lot of option where that data can be. All the high speed data pairs in HDMI are directional and are going in the camera->switcher direction so it's not using anything complicated inside the HDMI frame to get data across. Looking at some pinouts this basically only leaves the CEC and DDC stuff.</p> <p>DDC is the display data channel, it's an i2c bus that's used to query the information about the monitor that the cable is plugged in to. This exact same system is present in VGA, DVI, Displayport and HDMI. I guess the standard was good enough that it was just copied over to every newer one :)</p> <h2>Sniffing the DDC traffic</h2> <p>Messing with the DDC bus is usually pretty simple. On linux you can load the <code>i2c-dev</code> module and that will expose the DDC connections on all video outputs as <code>/dev/i2c-*</code> nodes. If I have my computer hooked into the HDMI input of an ATEM switcher I can dump the edid data over the bus using i2cdump for example:</p> <div class="highlight"><pre><span></span><span class="gp"># </span>Bus <span class="m">1</span> is my HDMI connector, 0x50 is the i2c address <span class="k">for</span> the EDID eeprom <span class="gp">$ </span>i2cdump -y <span class="m">1</span> 0x50 <span class="go">No size specified (using byte-data access)</span> <span class="go"> 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef</span> <span class="go">00: 00 ff ff ff ff ff ff 00 23 01 00 00 01 00 00 00 ........#?..?...</span> <span class="go">10: 26 10 01 03 80 47 28 96 0a da ff a3 58 4a a2 29 &amp;????G(???.?XJ?)</span> <span class="go">20: 17 49 4b 00 00 00 01 01 01 01 01 01 01 01 01 01 ?IK...??????????</span> <span class="go">30: 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c ???????:??q8-@X,</span> <span class="go">40: 45 00 c4 8e 21 00 00 1e 02 3a 80 d0 72 38 2d 40 E.??!..??:??r8-@</span> <span class="go">50: 58 2c 45 00 c4 8e 21 00 00 1e 00 00 00 fc 00 42 X,E.??!..?...?.B</span> <span class="go">60: 4d 44 20 48 44 4d 49 0a 20 20 20 20 00 00 00 fd MD HDMI? ...?</span> <span class="go">70: 00 32 3c 1f 44 08 00 0a 20 20 20 20 20 20 01 f7 .2&lt;?D?.? ??</span> <span class="go">80: 02 03 1f 54 49 85 84 94 93 a0 a1 a2 9f 90 23 09 ???TI?????????#?</span> <span class="go">90: 04 07 83 01 00 00 68 03 0c 00 10 00 00 1e 00 01 ????..h??.?..?.?</span> <span class="go">a0: 1d 00 72 51 d0 1e 20 6e 28 55 00 c4 8e 21 00 00 ?.rQ?? n(U.??!..</span> <span class="go">b0: 1e 01 1d 00 bc 52 d0 1e 20 b8 28 55 40 c4 8e 21 ???.?R?? ?(U@??!</span> <span class="go">c0: 00 00 1e 00 00 00 00 00 00 00 00 00 00 00 00 00 ..?.............</span> <span class="go">d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................</span> <span class="go">e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................</span> <span class="go">f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f2 ...............?</span> </pre></div> <p>Doing the same for the camera is a lot harder though. Since the direction for that is reversed the computer has to be i2c slave on the hdmi connector instead which is not something that's just easily doable.</p> <p>The other option is just splicing into the HDMI cable and finding the right wires. So that's exactly what I did.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072506/PXL_20210909_151933077.jpg" class="kg-image"><figcaption>The answer for &quot;how to get the shielding off like this&quot; is: carefully</figcaption></figure> <p>After removing a part of the outer isolation and shielding from an HDMI cable it looks like there are a bunch of high speed differential pairs with individual shielding. Those are not interesting for the data I'm looking for, they will be the clock pairs and a pair for red, green and blue pixels (or YUV in some modes). Besides those pairs there are 4 wires left over and a ground wire.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072506/PXL_20210909_154213804.jpg" class="kg-image"></figure> <p>DDC needs two wires to work, SDA and SCL for data and clock, and it needs the ground wire. The other two wires might be the +5V wire that was originally intended to power the EDID chip when the monitor is off. And a wire for hotplug detection of the connector. Since I don't care that much for the signal integrity of the cable, and these wires carry slow signals. I just decided to cut them and splice in some leftover resistor leads to heave points to attach probes to.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072506/PXL_20210909_161413183.jpg" class="kg-image"></figure> <p>Then I just connected probes to these wires and hooked it up to a CWAV USBee AX 8 channel logic analyzer. With that connected and the wire colors correctly labeled I ran a 10 second dump in the sigrok pulseview software and plugged in the camera.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072506/image.png" class="kg-image"></figure> <p>This was not what I expected. None of this looks like i2c data since there would be a clock and data pair. After looking at the HDMI pinouts some more I realized my mistake. There's 5 differential shielded pairs in my cable, one of them is used for the DDC data pair. But then what is happening on D4?</p> <p>One of the other busses in the HDMI cable is CEC, the Consume Electronics Control signalling. This is used to send commands from your TV to a set-top box or DVD player. Instead of being a two wire bus this is a single wire bidirectional signalling system that follows the AV.link standard. Let's label that wire and see what Pulseview can decode from it.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072507/image-1.png" class="kg-image"></figure> <p>Here is the view with the CEC decoder added and zoomed in on the initial little blip of data on the CEC wire. So what's happening here?</p> <p>The bottom row of the decoder shows the actual highest level decoding of the bits, in this case it's 2x the same message. CEC uses 4 bits for addressing and the first byte of a packet contains the sender and receiver address. The blue "EO..." bit in this screenshot is the End Of Message byte which is true in this message indicating there's no more data for this frame. A frame with only the addresses is a PING command as indicated by the decoder. This is a message send by the device on power-up to check if a device at address 1 (Recording_1) already exist on the bus. If such a device exists it will pull the CEC line down directly after the EOM bit. In this case that didn't happen which results in the NACK bit at the end of the message.</p> <p>So what's happening here is that either the camera or the switcher is checking the CEC bus to see if the address it wants to use already exists and not getting a ping response 2 times.</p> <p>So the next message:</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072507/image-2.png" class="kg-image"></figure> <p>So here device 1 is sending a broadcast (destination 0xF) and it contains 3 more bytes of data. This means the second byte (0x84) is the opcode. In this case it's REPORT_PHYSICAL_ADDRESS and the last 2 bytes are the arguments for the command. There seems to be 2 addressing systems in CEC, the 4 bit address that's used for routing packets and a physical address, and this is the device at CEC address 1 broadcasting that it's physical address is 0x20 which decodes to address 2.0.0.0. The camera is connected to port 2 of the atem, if I move it to port 4 it becomes 4.0.0.0.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072507/image-3.png" class="kg-image"></figure> <p>The the next command is a vendor command, so here the guessing starts again. the 0xA0 opcode that means the next 3 bytes are the vendor ID and the rest is the vendor specific command.</p> <p>The 0x7c2e0d that's received after that is the mac OID for Blackmagic Design, so we're getting somewhere at least with this. Then the next bytes are 0x01 and 0x01. Now the hard part is figuring out what that means.</p> <h2>BMD vendor commands</h2> <p>So I found that by connecting a raspberry pi to the atem switcher I can monitor the CEC traffic it gets. Sadly it doesn't get a copy of all the CEC traffic happening in thee switcher as the standard says it should do. It does get CEC broadcast data instead.</p> <p>From a quick look at some atem features that causes broadcasts to all devices, the first byte of the vendor data is a command ID and the rest is the argument. Command 0x03 is used to start/stop recording on all attached hardware where the first argument is 0x01 to start and 0x00 to stop.</p> <p>Command 0x02 is the tally status command with the bytes following it being a bitfield for the tally. From a 4 port atem mini:</p> <pre><code>The program/preview controlled by the hardware panel: Program 1, Preview 1: 0x03 0x00 Program 2, Preview 2: 0x30 0x00 Program 3, Preview 3: 0x00 0x03 Program 4, Preview 4: 0x00 0x30 Seperate program and preview tally: Program 1, Preview 3: 0x01 0x02</code></pre> <p>So the data for this command is 2 bits per input, 2 bits wasted. The whole thing is little endian.</p> <p>So my protocol docs so far:</p> <table> <thead> <tr> <th>cec vendor command</th> <th>command</th> <th>args</th> <th>notes</th> </tr> </thead> <tbody> <tr> <td>0x1f 0xa0 0x7c2e0d</td> <td>0x01</td> <td>1 byte</td> <td>unknown</td> </tr> <tr> <td>0x1f 0xa0 0x7c2e0d</td> <td>0x02</td> <td>4 bits per channel</td> <td>Tally status<br>bit 0 is preview<br>bit 1 is program<br>bit 2 and 3 are reserved</td> </tr> <tr> <td>0x1f 0xa0 0x7c2e0d</td> <td>0x03</td> <td>1 byte</td> <td>Trigger record<br>0x00 = stop<br>0x01 = start</td> </tr> </tbody> </table> <p>Interestingly, from sniffing the HDMI cable it looks like the color shading commands for the camera don't use the cec vendor command but they use just random (as far as I can tel) CEC opcodes. I have not been able to find just a list of CEC opcodes and their arguments so I can't really verify this. The CEC decoder in PulseView also doesn't seem to support all opcodes.</p> <h2>Camera control commands</h2> <table> <thead> <tr> <th>addr</th> <th>opcode</th> <th>args</th> <th>notes</th> </tr> </thead> <tbody> <tr> <td>0x01</td> <td>0x19</td> <td>?</td> <td>Lift W</td> </tr> <tr> <td>0x01</td> <td>0x19</td> <td>?</td> <td>Lift W</td> </tr> <tr> <td>0x01</td> <td>0x1e</td> <td>?</td> <td>Saturation</td> </tr> <tr> <td>0x01</td> <td>0x1d</td> <td>?</td> <td>Hue</td> </tr> <tr> <td>0x01</td> <td>0x20</td> <td>?</td> <td>Contrast</td> </tr> <tr> <td>0x01</td> <td>0x42</td> <td>u16</td> <td>Aperture</td> </tr> <tr> <td>0x01</td> <td>0x46</td> <td>i16</td> <td>Zoom</td> </tr> <tr> <td>0x01</td> <td>0x52</td> <td>i8</td> <td>Gain, signed byte<br>0xf4 = -12dB</td> </tr> <tr> <td>0x01</td> <td>0x54</td> <td>u8</td> <td>White balance<br>0x32 = 2500k, 50k per step</td> </tr> <tr> <td>0x01</td> <td>0x55</td> <td>u8</td> <td>White balance tint<br>Unknown format</td> </tr> <tr> <td>0x01</td> <td>0x57</td> <td>i8</td> <td>Shutter</td> </tr> </tbody> </table> <p>So this is a list of a few of the full CEC packets sent when shading a camera, some of the signed values are a bit weird, becoming 2 bytes when they are negative and 1 byte when they are positive. Here's the contrast message for example:</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072507/image-4.png" class="kg-image"></figure> <p>Most of these controls don't have a neat numeric value to compare against in the ATEM Software Control software so to get the exact encoding it would be required to implement that in <a href="https://openswitcher.org/">OpenSwitcher </a>first.</p> <p>Also to figure out the commands that are not implemented yet in ATEMSC I need to find a way to inject CEC commands between the camera and the atem. There's some USB solutions that might work, it's also possible with an arduino and a HDMI breakout. In either case I need to wait for parts so that will be a part 2 of this series.</p> <p>With the protocol decoded it would be possible to do a few neat things, like make a cheap (and I mean really really cheap) injector to put between the network and the HDMI cable of the camera to remotely shade one of these cameras when you're not on the ATEM mini hardware but on the older generation of hardware instead.</p> Making a backcover extension for the PinePhonehttps://blog.brixit.nl/making-a-backcover-extension-for-the-pinephone/5f22edfb7725960d859ca2d7PhonesMartijn BraamFri, 31 Jul 2020 10:16:41 -0000<p>So one of the features of the PinePhone that hasn't seen much (any?) use is the pogo pin expansion header on the back. It's 6 pogo pins that can be used to make backcovers that extend the phone functionality.</p> <p>These pogo pins give external access to battery power, usb power, a dedicated i2c bus and an interrupt pin. This is enough to make a wide range of custom attachments. The official announced attachments are a battery extension backcover (that uses the two power pins) and a keyboard backcover (that uses the i2c bus).</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072496/DSC0011-cen.jpg" class="kg-image"></figure> <h2>Making your own extension</h2> <p>Since this is an open platform, it's quite easy to make your own extension that connects to the PinePhone pogo pins. In my case I'm going to attach an <a href="https://www.melexis.com/en/product/MLX90640/Far-Infrared-Thermal-Sensor-Array">MLX90640 far-infrared thermal array</a>, which is a fancy way of saying thermal camera. It's a module that can be get relatively cheap, but it's low resolution. The sensor only has 32x24 pixels, to get a good image that has to be upscaled and combined with a visible light camera (which conveniently is built into the PinePhone) to create a nice thermal image.</p> <p>To make the physical interface, I melted 6 holes through a PinePhone backcover and put tiny nails in them that match up with the pogo pins.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072497/DSC0016.jpg" class="kg-image"><figcaption>I didn&#x27;t have 6 nails of the same size ofcourse</figcaption></figure> <p>I also made a <a href="https://www.youtube.com/watch?v=lFsQpd0bLTY">video showing me doing this</a>. Then I used some double sided tape to stick the thermal camera module to the backcover and soldered wires between the sensor and the nails. The pinout for the pogo pins is this:</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072497/rect46533.png" class="kg-image"></figure> <p>Connecting up an i2c device should be relatively straightforward, it's important to note that the i2c lines are pulled up by the phone to 3v3.</p> <p>The VBUS pin is powered by USB, I used this one for the sensor, it only has power when something is plugged into USB, and it's 5V. </p> <p>The second power pin is VBAT, which connects to the battery voltage.</p> <h2>What can you do with this?</h2> <p>Everything! The sky is the limit! except bandwith and power constraints ofcourse...</p> <p>The maximum speed of the i2c bus is 400 kHz, which is enough for a lot of applications, but don't expect to add ethernet or hdmi to this. Since i2c is a bus you can add multiple things at the same time to these pins. I just added the single thermal camera but you can just add a second i2c device to the same pins in parallel.</p> <p>If you need another bus than i2c you can probably get a bridge chip for it or add a small microcontroller to bridge another protocol. Basically any microcontroller has support for i2c and can most likely connect to whatever you want, like use gpio on the microcontroller to read from a keyboard matrix, or add some IR leds and sensors to add IrDA. </p> <h2>The software side</h2> <p>There are two ways to use your newly attached gadget. The first is i2cdev. It isn't super efficient but it gives you access to the i2c hardware directly from userspace. The only thing that's needed is <code>modprobe i2cdev</code>to make /dev/i2c* appear. The bus on the pogopins on the PinePhone is twi2, so <code>/dev/i2c-2</code> should be the right bus.</p> <p>Here's a simple python example for reading a byte from and attached i2c chip:</p> <div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">smbus2</span> <span class="kn">import</span> <span class="n">SMBus</span> <span class="c1"># Connect to /dev/i2c-2</span> <span class="n">bus</span> <span class="o">=</span> <span class="n">SMBus</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="c1"># Read register 0x42 from the i2c device at address 0x33</span> <span class="n">bus</span><span class="o">.</span><span class="n">write_byte_data</span><span class="p">(</span><span class="mh">0x33</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x42</span><span class="p">)</span> <span class="n">value</span> <span class="o">=</span> <span class="n">bus</span><span class="o">.</span><span class="n">read_byte_data</span><span class="p">(</span><span class="mh">0x33</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> </pre></div> <p>The second method is letting a kernel module deal with the hardware. This can be accomplished by recompiling the dtb/kernel with your hardware in it, which isn't very portable. Or by using device tree overlays.</p> <p>Here's an example of the dtbo for an TI HDC1000 humidity and temperature sensor:</p> <div class="highlight"><pre><span></span><span class="c1">// Definitions for dhc1000 module</span> <span class="cp">/dts-v1/</span><span class="p">;</span><span class="w"></span> <span class="cp">/plugin/</span><span class="p">;</span><span class="w"></span> <span class="nf">/</span><span class="cm"> </span><span class="p">{</span><span class="w"></span> <span class="nf">fragment</span><span class="o">@</span><span class="mi">0</span><span class="cm"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="c1">// Target the pogo pin i2c bus</span> <span class="w"> </span><span class="n">target</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&lt;&amp;</span><span class="na">i2c2</span><span class="o">&gt;</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="nf">__overlay__</span><span class="cm"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span> <span class="w"> </span><span class="c1">// Define the hdc100x device on this i2c bus</span> <span class="w"> </span><span class="nf">hdc100x</span><span class="o">@</span><span class="mi">40</span><span class="cm"> </span><span class="p">{</span><span class="w"></span> <span class="w"> </span><span class="kr">compatible</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&quot;ti,hdc1000&quot;</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="kr">reg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">&lt;</span><span class="mh">0x40</span><span class="o">&gt;</span><span class="p">;</span><span class="w"></span> <span class="w"> </span><span class="p">};</span><span class="w"></span> <span class="w"> </span> <span class="w"> </span><span class="p">};</span><span class="w"></span> <span class="p">};</span><span class="w"></span> </pre></div> <p>The loading of this device tree overlay is more complicated. It needs to be compiled first, which needs the sources of the kernel used on the PinePhone so it knows what &i2c2 means. Then the compiled dtbo file needs to be put somewhere in sysfs to make it load at runtime, or needs to be added to u-boot to be loaded at boot time. This is all a bit out of scope for this article (meaning I haven't tried it yet)</p> <p>After the dtbo is loaded, the new device should just show up in the kernel like it was always part of the phone, in this example it's an iio sensor so it shows up in /sys/bus/iio/device:$number/$parameter, so you can just query the humidity by reading some file.</p> <h2>Connecting a thermal camera</h2> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072497/DSC0015.jpg" class="kg-image"></figure> <p>One of the things I have had here in my parts bin is the MLX90640 thermal imaging sensor. I thought it would make for a great demo of what's possible with the pogo pins. Connecting it was trivial since it only needs power and i2c on the breakout board I have and this breakout board also has a voltage regular so I can power it by anything from 3 to 6 volts, perfect for the PinePhone.</p> <p>For the software side I chose the i2cdev method and used python to get data from the sensor. Only after a while I found out that there's already a kernel module available in the mainline kernel that exposes this sensor as a regular v4l2 webcam.</p> <p>The first python lib I tried was the Adafruit circuitpython one, I didn't manage to get this to work since it expects if you're running it on Linux that it's must be a raspberry pi. Then I found a module from Seeed studio which seemed easier but still didn't give me any data from the sensor after messing with it for a while. It turns out the seeed and adafruit code is very similar and probably copied from eachother and had the interfacing code replaced.</p> <p>Since both libraries weren't working I wrote a quick 'n dirty python script based on the registermap in the datasheet of the sensor and managed to get the raw sensor data from that. This code doesn't read the factory calibration data yet from the sensors eeeprom so there's quite a lot of variance in the pixels. The raw data from the pixels is also 16 bit signed data, which I just pick the 8 most significant bits from to make a simple grayscale png as output. Since somewhere around room temperature these raw values go negative there's some pure white pixels instead of useful data.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072497/pixels.png" class="kg-image"><figcaption>Left: raw pixels, top right: properly interpolated and upscaled, bottom right: more familiar color map applied</figcaption></figure> <p>The raw data from the sensor looks a bit underwhelming, it's mainly messed up by not applying calibration which should get rid of the checkerboard pattern and the data format which should get rid of the white pixels. </p> <p>The sensor actually responds with 2 extra rows of "pixels", this data is the extra data needed to get proper measurements from this like the global gain of the chip (since it does auto exposure) and the ambient chip temperature. With that data the absolute temperature for every pixel can be calculated.</p> <p>For getting real nice "FLIR-like" output from this it should also be combined with the image captured from the way higher resolution normal camera in the PinePhone itself.</p> <h2>Conclusion</h2> <p>Is this demo practical? not really, the camera works but since it's quite bulky the phone can't be put in your pocket anymore. This can probably be solved by using a 3d printed backcover instead that properly integrates the camera and keeps it aligned with the visible light camera. This also needs a lot more software work to make it a nice experience.</p> <p>It does provide a great demo of the extendability though and I can't wait to see what people will do with this feature.</p> E-paper status displayhttps://blog.brixit.nl/epaper/5ea5931a6cd0491609d8f934ElectronicsMartijn BraamMon, 27 Apr 2020 18:00:00 -0000<p>There's a project called LCD Smartie that converted a character LCD into a small status monitor for your computer. It can show system status, the info on what's playing in Winamp. E-mail headlines.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072493/4x20_hd44780_bars2.jpg" class="kg-image"><figcaption>LCD Smartie example (from lcdsmartie.sourceforge.net)</figcaption></figure> <p>I wanted to have something similar, but connected to my fileserver to show some stats, but I didn't want to have yet another permanent light on my desk, it's already a chrismas lightshow of blue, green and orange power and status lights for various devices.</p> <p>The nice solution for this would be using an e-paper display instead of an LCD. After some searching I found the <a href="https://www.waveshare.com/6inch-e-paper-hat.htm">6" display kit with raspberry pi hat</a> from Waveshare.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072493/epaper-out-of-the-box.jpg" class="kg-image"><figcaption>The kit with an image uploaded through the Windows software</figcaption></figure> <p>The nice thing about this specific board is that it also has a microUSB port for connecting it to a computer instead of a raspberry pi. Sadly there isn't really a lot of information available about this interface, it's designed to be driven by spi, i2c or i80 and the docs reflect that. The USB port seems to be mainly for updating firmware. </p> <p>Waveshare has a Windows tool available that can communicate with the display controller over USB and upload firmware, it can also send images to it with their clunky interface. I want to control it with Linux though so I have to write something new.</p> <p>The USB interface is pretty weird. The display shows up as a mass storage device with no medium inserted (like an SD card reader with no card inserted). Somehow their software can communicate with this. So the first step is <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/procmon">sysinternals procmon</a>. This is basically equivelent to running a strace in Windows. </p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072493/tcon-demo.PNG" class="kg-image"></figure> <p>This shows it's opening the virtual mass storage device from the display controller (F: in my case) and then using IOCTL_SCSI_PASS_THROUGH_DIRECT to actually control the display. </p> <p>This is the same communication protocol used by tools like smartmontools to communicate with harddisk firmware to get disk information. So the upside is that it should be possible to control this display without messing with kernel modules. The downside is that it will require root to control the display.</p> <p>With this information I was finally able to find a datasheet that had some details on the USB communication protocol called "<a href="https://www.waveshare.com/w/upload/c/c9/IT8951_USB_ProgrammingGuide_v.0.4_20161114.pdf">IT8951 USB Programming Guide</a>" This contains some details on how the protocol works and some demo code for making these calls in Windows. Armed with this information I started writing a C program that should draw to the display.</p> <p>The drawing procedure is pretty simple. You send a command to get some general information like the resolution and the address of the framebuffer. Then you use the load image command to send pixels to a specific part of the framebuffer, then you use the display command to actually trigger the display refresh. This is the part that makes the display flicker and show the new content.</p> <p>It was all pretty straightforward except that it didn't work. My display just didn't start flickering to update the contents. After scanning through the datasheet for a while I gave up and went back to reverse engineering the Windows tool. This time I used Wireshark to make a dump of all the USB traffic since procmon didn't show me the actual data in the syscalls. </p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072494/wireshark.png" class="kg-image"></figure> <p>This did show some interesting things. Mainly that I was missing a single byte in the display update command (0x94) causing the display controller to hang until it was reset. Another interesting thing I noticed is that for uploading a full-screen image (800x600) it was sending the image in strips of 800x76 pixels using seperate load-image commands and then sending a single display-image command to update the whole screen. After implementing this I was able to update my e-paper over USB from Linux!</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072494/IMG_20200426_131543--1-.jpg" class="kg-image"></figure> <p>All this is available as a simple small tool: <a href="https://git.sr.ht/~martijnbraam/it8951">it8951</a> (named after the controller used). It takes a device name, x+y coordinates and the image width,height to update on the screen and then takes raw image data on stdin to display. The nice thing about this controller is that you don't have to care at all about low level image formatting, just send a 8-bit grayscale image to it and it will be converted in the right format to the display.</p> <p>This display supports multiple modes (selected by the -m argument of my tool). Mode 0 is used to completely blank a region. It will flicker a few times and then make the region perfectly white.</p> <p>The next one is Mode 2, it will display a 16 level grayscale image based on the 256 level input image you gave it, it will not do any dithering for this. Updating the screen in this mode will cause it to flicker that region once. This is called G16 mode.</p> <p>The other nice mode is Mode 4, it is called A2 mode and will update the screen without flickering. It is very fast but it has a downside, it only renders pure black and white. </p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072494/DSC0112.jpg" class="kg-image"><figcaption>The date is drawn in G16 mode and the time in A2 mode</figcaption></figure> <p>In the example above the date is drawn in G16 mode, since it's not updated often anyway and the time is drawn in A2 mode so it can update every minute without having an annoying flicker. You can see that the date has smoother edges since it can be anti-aliased using the 16 grayscale levels, The time has crispy edges since it's only pure black. Also the A2 move leaves some ghosting behind as you can see.</p> <h1>Drawing something useful</h1> <p>Now I have the display working with a tool I can pipe data into I made a python program that uses PIL to generate a nice status display. It generates a full screen background image that has the dates and the borders on them and draws that every day at midnight with a fresh date. It also uses this moment to fully clear the display in mode 0 to get rid of ghosting artifacts. Then it will update the time every minute in A2 mode in the center. It will then check all the data feeds and if one changed it will update only that specific region in G16 mode, which is distracting, but at that point it will also notify you that there's new data. When the python script is restarted it will update all the regions one by one, which looks very satisfying.</p> <iframe width="100%" height="360" src="https://www.youtube.com/embed/uReT5aMYeug?feature=oembed"> <p>The next step is making a nice wooden frame around it so it can stand on my desk.</p> Harley Benton (Thomann) Power Attenuator test and teardownhttps://blog.brixit.nl/harley-benton-thomann-power-attenuator-test-and-teardown/5e91c26e2cc6a25f176027a0ElectronicsMartijn BraamFri, 26 Feb 2016 23:00:00 -0000<p>Thomann just started shipping their new <a href="https://www.thomann.de/gb/harley_benton_power_attenuator.htm">Harley Benton Power Attenuator</a>. It can handle an input power of<br>80 watts (it contains an 100 watt L-pad, so they added some margin). It is a simple device containing<br>only passive components. It has a control for cabinet volume and line-out volume but they are not fully<br>seperated. The line-out level is a signel tapped of after the main cabinet volume knob so you can't<br>turn the cabinet completely off and use the line out for recording / headset usage.</p> <figure class="kg-card kg-gallery-card"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072477/att1.jpg" class="kg-image" width="600" height="400"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072478/att2.jpg" class="kg-image" width="600" height="400"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072478/att3.jpg" class="kg-image" width="600" height="400"></div></div></div></figure> <h2>Measurement</h2> <p>To measure the performance of the attenuator I connected it to a USB DAC/ADC and ran a frequency sweep with the<br>tool QLoud. QLoud is a tool normally used to measure the frequency response of a speaker using a measurement microphone.</p> <p>I connected the line out signal of the DAC to the 16 Ohm speaker input of the attenuator and connected the<br>speaker output of the attenuator to the line in of the USB DAC. I also did a reference measurement by<br>connecting the input and output of my DAC/ADC directly together. The last set of measurements is connecting<br>the line signal to the input of my guitar tube amp (a Defender 5H 15 watt all-tube amp) and connecting the<br>line-out of the attenuator to the USB ADC.</p> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072478/measurements.png" class="kg-image"></figure> <h2>Taking it apart</h2> <p>I'm also curious what is inside a fully passive device for ~100 euro so I took it apart. The case is<br>very easy to open, 4 screws on the front and 4 on the back and you can just lift the side and top part off.</p> <p>The answer to my question: there is not a lot in it.</p> <p>The main part of the attenuator is the 100 watt L-pad (The huge potentiometer behind the cabinet volume knob).<br>The signal from the amp is put almost directly accross the two outer pins of the potmeter so it will dissipate<br>most of the energy. The other large part of the circuit is a bunch of 50 watt resistors (The resistors with<br>orange cooling fins). They are there to match the impedance for the amp. The signal coming from the L-pad pot<br>and the small potmeter for line-out is put straight into the line-out and speaker-out jack on the PCB. The rest<br>of the circuit on the PCB is for the MicMod circuit that is supposed to simulate a cabinet and provide a balanced<br>microphone signal. I haven't tested the MicMod output yet.</p> <figure class="kg-card kg-gallery-card"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072478/open1.jpg" class="kg-image" width="600" height="400"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072478/open2.jpg" class="kg-image" width="600" height="400"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072479/open3.jpg" class="kg-image" width="600" height="400"></div></div><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072479/open4.jpg" class="kg-image" width="600" height="400"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072479/open5.jpg" class="kg-image" width="600" height="400"></div></div></div></figure> <figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072479/schematic.jpg" class="kg-image"><figcaption>Yeah, I&#x27;m not so great with CAD software</figcaption></figure> Modding a Behringer VT911https://blog.brixit.nl/modding-a-behringer-vt911/5e91c8692cc6a25f1760282bElectronicsMartijn BraamSun, 03 May 2015 22:00:00 -0000<p>A few weeks ago I replaced my Line6 Podxt live multieffect pedal with a guitar amp, cabinet and 2 pedals.<br>The amp is a Kustom Defender 5H head and I use a Kustom 1x12 cab with it.</p> <p>To replace the multieffect pedal I bought a Behringer VT911 overdrive pedal and a<br>Digitech Digital Reverb pedal. I chose for the VT911 because it contains a tube for the distortion and it<br>only cost me € 14,90 at <a href="http://www.thomann.de/gb/behringer_vt911_vintage_tube_overdrive.htm">Thomann</a>.</p> <p>The pedal sounds fine. Not great but it's a great pedal for the price. I searched on the internet<br>for easy mods to do on the pedal to improve the sound quality and found various suggestions on a few forums.</p> <figure class="kg-card kg-gallery-card"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072484/mod1.jpg" class="kg-image" width="600" height="397"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072484/mod2.jpg" class="kg-image" width="600" height="397"></div><div class="kg-gallery-image"><img src="https://blog.brixit.nl/image/w600//static/files/blog.brixit.nl/1670072484/mod3.jpg" class="kg-image" width="600" height="397"></div></div></div></figure> <p>The first thing I did was adding a resistor and capacitor to the tube board to give the tube some extra<br>headroom. One of the major problems I had was that it doesn't really have a setting to only distort a little.<br>In the 0-1 range on the overdrive the pedal is usable but is very quiet even with the volume all the way to 10.<br>On the 1-10 range the pedal distorts so much that I wouldn't call this a overdrive pedal anymore.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072484/mod4.jpg" class="kg-image"></figure> <p>The mod on the tube board increases the headroom so the tube distorts later and has a lot less compression<br>but the drive knob doesn't have a very large usable range. On the high distortion range the pedal also<br>starts to sound very muddy. The second mod adds another resistor and capacitor parallel to the drive potmeter<br>to change it's range and frequency response.</p> <figure class="kg-card kg-image-card"><img src="https://blog.brixit.nl/image/w1000//static/files/blog.brixit.nl/1670072485/mod5.jpg" class="kg-image"></figure> <p>Here is a list of the exact values used in the mod:</p> <pre><code>Tube board: 4.7 nF capacitor between pin 1 and 7 on the tube (marked 472) 470 ohm resistor between pin 7 and ground (yellow purple black black brown) Main board: 1.0 nF capacitor connected to the outer pins of the drive pot (marked 102) 100 Kohm resistor parallel to the same pot (brown black black orange brown) </code></pre> <p>I've recorded a sample before the mod and after the mod. Judge for yourself:</p> <p></p>