Thomas Spurden's blogZola2021-01-09T00:00:00+00:00https://thomas.spurden.name/atom.xmlCapturing the Gameboy LCD with an FPGA (Part 2) - Compression2021-01-09T00:00:00+00:002021-01-09T00:00:00+00:00https://thomas.spurden.name/blog/capturing-gb-lcd-part2/<p><img src="https://thomas.spurden.name/blog/capturing-gb-lcd-part2/mario-intro.gif" alt="Mario" /></p>
<aside><p>This GIF is about 16% too slow (50fps, should be 60fps) due to the low precision of the gif
frame duration.</p>
</aside>
<p>In <a href="https://thomas.spurden.name/blog/capturing-gb-lcd/">Part 1</a> I explain how to capture the
pixels being sent from the Gameboy GPU to the LCD. The FPGA board I'm using
(<a href="http://papilio.cc/index.php?n=Papilio.PapilioPro">Papilio Pro</a>) is sending the captured data back to the PC via 2Mbaud serial
(over USB). This isn't fast enough to stream frames in real time so only a about
15 frames would be captured and transmitted before the buffer on the FPGA
overflowed.</p>
<p>So I implemented a simple lossless compressor to reduce the required data rate.
The compressor is line-based: it chooses a reference line from the previous
frame and only transmits the differences between that reference line and the
current line. This introduces a fixed overhead of one byte per line to encode
the location of the reference line and how the differences are encoded. But
generally the lines are compressed by much more than one byte!</p>
<p>See the <a href="https://git.sr.ht/%7Eshiny/gbcap">gbcap</a> project for the code!</p>
<h2 id="compression-details">Compression details</h2>
<p>The compressor can encode offsets in x & y between -4 and 4 pixels. This allows
the reference line to be between 4 lines above and 4 lines below the line being
compressed in the previous frame. It also allows the reference line to be
shifted left or right by up to 4 pixels (0 pixels are shifted in to fill the
gap).</p>
<p>A per-line prefix byte determines the encoding used for the line, of which there
are three:</p>
<ul>
<li>A prefix byte of <code>0xFF</code> means the line could not be compressed, there is no
reference line and the next 40 bytes are the line data verbatim</li>
</ul>
<p>Otherwise the 7 least-significant bits of the prefix byte encode the offset of
the reference line, and the most-significant bit encodes whether the match is
exact. The offset is encoded in the range 0..89 (so these two encodings do not
overlap with the <code>0xFF</code> encoding) by biasing both offsets by 4 to place them
in the range 0..9, multiplying the x offset by 9 and adding the y offset (that
is: ((x + 4) * 9) + (y + 4)).</p>
<ul>
<li>
<p>For an exact match (most-significant bit is set) the reference line is output
verbatim.</p>
</li>
<li>
<p>For a non-exact match (most-significant bit is not set) the prefix byte is
followed by a 5-byte bitmask which encodes which of the 40 bytes of the
reference line should be output. For every 0 in the mask a byte is read from the
compressed stream and output, for every 1 in the mask a byte is read from the
reference line and output.</p>
</li>
</ul>
<p>The compressor selects the reference line with the highest number of matches. If
there aren't any reference lines with more than 5 matching bytes then the
compressor will output a no-match (<code>0xFF</code> prefix) line as that consumes fewer
bytes than transmitting the mask and non-matching bytes in that case.</p>
Capturing the Gameboy LCD with an FPGA (Part 1)2020-08-08T00:00:00+00:002020-08-08T00:00:00+00:00https://thomas.spurden.name/blog/capturing-gb-lcd/<p><img src="https://thomas.spurden.name/blog/capturing-gb-lcd/title.png" alt="Super Mario Land 2: 6 Golden Coins title screen" />
<img src="https://thomas.spurden.name/blog/capturing-gb-lcd/jump.png" alt="Mario jumping" />
<img src="https://thomas.spurden.name/blog/capturing-gb-lcd/rock.png" alt="Mario about to dodge a rock" />
<img src="https://thomas.spurden.name/blog/capturing-gb-lcd/mario_underwater.png" alt="Mario underwater" /></p>
<p><img src="https://thomas.spurden.name/blog/capturing-gb-lcd/lemmings.png" alt="Lemmings title screen" />
<img src="https://thomas.spurden.name/blog/capturing-gb-lcd/mario_and_yoshi.png" alt="Mario and Yoshi title screen" />
<img src="https://thomas.spurden.name/blog/capturing-gb-lcd/xenon_ii.png" alt="Xenon II" />
<img src="https://thomas.spurden.name/blog/capturing-gb-lcd/mario-fly.gif" alt="Mario flying" /></p>
<p>Update: see also <a href="https://thomas.spurden.name/blog/capturing-gb-lcd-part2/">Part 2</a>.</p>
<p>There are 6 important outputs from the Gameboy main processor to the LCD for
capturing the pixels:</p>
<ul>
<li>Pixel data <code>D0</code> and <code>D1</code> (50: <code>LD0</code> / <code>DATA0</code>, 51: <code>LD1</code> / <code>DATA1</code>)</li>
<li>Pixel clock <code>CLK</code> (53: <code>CP</code> / <code>CLOCK</code>)</li>
<li>Line latch <code>CPL</code> (55: <code>CPL</code> / <code>DATALCH</code>)</li>
<li>Horizontal sync <code>HSYNC</code> (54: <code>ST</code> / <code>HORSYNC</code>)</li>
<li>Vertical sync <code>VSYNC</code> (57: <code>S</code> / <code>VERTSYN</code>)</li>
</ul>
<p>In brackets I've put the names and pin numbers for these signals from the
<a href="http://www.devrs.com/gb/files/gameboy1.gif">Gameboy schematic</a>. You can tell these are the ones required as they are the
ones connected to the ICD2-R chip on the SGB (<a href="http://www.devrs.com/gb/files/sgb.gif">SGB schematic</a>). Although only
<code>D0</code>, <code>D1</code>, <code>CLK</code> and <code>CPL</code> are strictly required. </p>
<p>Don't forget that the Gameboy is 5 volt TTL: do NOT connect these directly to a
non-5v tolerant input! I am using a <a href="http://papilio.cc/index.php?n=Papilio.PapilioPro">Papilio Pro</a> FPGA development board with an
<a href="http://papilio.cc/index.php?n=Papilio.16-bitIOBufferWing">IO Buffer wing</a> to handle the logic level conversion. I have soldered an old
IDE ribbon cable to the pins of the ribbon cable which connects the main board
to the LCD board of the Gameboy.</p>
<figure>
<a href="gb_and_fpga.jpg"><img src="gb_and_fpga.jpg" alt="Gameboy connected to FPGA" /></a>
<figcaption>Gameboy connected to FPGA</figcaption>
</figure>
<p>The pins to connect to are shown quite well in this image from the
<a href="http://www.flashingleds.net/nintendOscope/nintendoscope.html">Nintendoscope</a> project:</p>
<figure>
<a href="nintendoscope_pinout.jpg"><img src="nintendoscope_pinout.jpg" alt="LCD ribbon cable pins" /></a>
<figcaption>Original source <a href="http://www.flashingleds.net/nintendOscope/nintendoscope.html">Nintendoscope</a></figcaption>
</figure>
<p>For testing I captured the signals using my <a href="http://papilio.cc/index.php?n=Papilio.PapilioPro">Papilio Pro</a> as a logic analyser
with the <a href="http://dangerousprototypes.com/docs/Open_Bench_Logic_Sniffer">Open Bench Logic Sniffer</a> bitfile and <a href="https://github.com/tcrs/sump-dump">sump-dump</a>:</p>
<pre style="background-color:#fdf6e3;">
<code>./sump-dump /dev/ttyUSB1 groups 1 trigger 0x20=0x20 divisor 10 vcd data=0x3 vcd clk=0x4 vcd hsync=0x10 vcd vsync=0x20 vcd cpl=0x40 vcd c=0x80
</code></pre>
<p>The signal buffer isn't large enough to capture a whole frame, it can only store
about a third of a frame.</p>
<figure>
<a href="waves-2frames.png"><img src="waves-2frames.png" alt="Logic analyser capture of two lines of pixels" /></a>
<figcaption>First two lines of a frame (<a href="6coins-title.vcd">vcd</a>)</figcaption>
</figure>
<figure>
<a href="waves-end-of-frame.png"><img src="waves-end-of-frame.png" alt="Logic analyser capture of the end of a frame" /></a>
<figcaption>Last few lines of a frame (<a href="frame-end.vcd">vcd</a>)</figcaption>
</figure>
<aside><p>These captures are both from the title screen of "Super Mario Land 2: 6 Golden
Coins"</p>
</aside>
<p>The behaviour of these signals is:</p>
<ul>
<li>Frame starts on the rising edge of <code>VSYNC</code>, which is held high for the first
line of the frame.</li>
<li>Each line of the frame starts on the rising edge of <code>HSYNC</code>.</li>
<li>Pixels are generated from left-to-right from the Gameboy CPU.</li>
<li>Pixel data is shifted into a 159 pixel (159x2 bits) shift register from <code>D0</code>
& <code>D1</code> on the rising edge of <code>CLK</code>.</li>
<li>Note that <code>CLK</code> does not run at a constant rate, some pixels are generated
but the corresponding pulse of <code>CLK</code> is suppressed so they are discarded.
This is how the Gameboy implements fine (1-7) pixel background and window
offsets (and possibly other effects).</li>
<li>A whole line of pixels is latched into the LCD drivers on the rising edge of
<code>CPL</code>. The pixels latched are the 159 from the shift register plus the values
from <code>D0</code> and <code>D1</code> (to make up 160 pixels). This also signals the end of each
line, making <code>HSYNC</code> a bit redundant.</li>
<li>At the end of each frame there are a few lines with no <code>CLK</code> pulses but a
<code>CPL</code> pulse, these don't have any effect on the display (as they will just
latch exactly the same data into the LCD drivers each time). This could be
used to detect the end of frame without having to use <code>VSYNC</code>.</li>
<li>In the logic captures I have looked at there is 160 rising edges of <code>CLK</code>, so
the first pixel generated (which always seems to be when <code>HSYNC</code> is high) is
actually discarded as it shifts off the end of the shift register before the
row is latched into the LCD drivers.</li>
</ul>
<p>The pixel data changes <em>very</em> soon after the rising edge of <code>CLK</code> for the next
pixel - so soon that to the logic analyser it appears to happen at the same time
as the rising edge. So I actually capture the <code>D0</code> & <code>D1</code> values from the cycle
<em>before</em> the rising edge is detected.</p>
<p>Capturing <code>D0</code> & <code>D1</code> on the falling edge of <code>CLK</code> seems to be widely reported
as the correct thing to do, but this will capture the whole frame shifted left
by one pixel. It is definitely easier though, so if you are happy with 159
pixels it is a decent approach.</p>
<h2 id="hardware">Hardware</h2>
<p>Used <a href="https://m-labs.hk/gateware/migen/">migen</a> to build hardware, available here: <a href="https://git.sr.ht/%7Eshiny/gbcap">gbcap</a>. It's set up to build
for the Papilio Pro, will require some changes to build with other FPGA boards.</p>
<p>This code sets up the pin assignment (from <code>gbcap.py</code>):</p>
<pre style="background-color:#fdf6e3;">
<code> plat.add_extension([
('gb_lcd', 0,
Subsignal('vsync', Pins('B:5')),
Subsignal('hsync', Pins('B:4')),
Subsignal('cpl', Pins('B:6')),
Subsignal('clk', Pins('B:2')),
Subsignal('pixel', Pins('B:0', 'B:1')))])
</code></pre>
<p>i.e. <code>D0</code> should be connected to pin 0 of the B wing header, and so on.</p>
<p>Once the bitfile is programmed onto your FPGA it will wait for a byte to be
received over the UART and then start capturing frames. You can grab these
using the <code>recv.py</code> script in the git repo, it will dump out pgm files.</p>
<p>The UART is only running at 2Mbaud in the current design, which is not fast
enough to stream the Gameboy frame captures in real-time (that would require
60 * 160 * 144 * 2 = 2764800 bits per second), so you will only get a few frames
before it stops due to FIFO overflow. <strong>Update: I now have real-time streaming,
see <a href="https://thomas.spurden.name/blog/capturing-gb-lcd-part2/">Part 2</a></strong>.</p>
<h3 id="previous-work">Previous Work</h3>
<ul>
<li><a href="https://github.com/svendahlstrand/game-boy-lcd-sniffing">lcd sniffing</a></li>
<li><a href="http://www.flashingleds.net/nintendOscope/nintendoscope.html">nintendoscope</a></li>
<li><a href="http://blog.kevtris.org/blogfiles/Nitty%20Gritty%20Gameboy%20VRAM%20Timing.txt">nitty gritty gameboy vram timing</a></li>
</ul>
Run dhcp on all ethernet devices2020-07-10T00:00:00+00:002020-07-10T00:00:00+00:00https://thomas.spurden.name/blog/dhcp-all-ethernet/<p>My laptop doesn't have a built-in ethernet controller (WiFi only). I want all
Ethernet devices to be dhcp-ed automatically, as soon as they appear (USB
tethering phone, USB-to-ethernet adaptor). But I don't want to mess up the
configuration for my WiFi card.</p>
<p>Lightly modified <code>/usr/lib/systemd/system/dhcpcd.service</code> (Arch Linux)</p>
<pre style="background-color:#fdf6e3;">
<code>[Unit]
Description=dhcpcd on all en* interfaces
Wants=network.target
Before=network.target
[Service]
Type=forking
PIDFile=/run/dhcpcd/pid
# Only allow ethernet devices, ignore unrenamed devices as udev
# will (try to) rename them whilst dhcpcd is fiddling with them
ExecStart=/usr/bin/dhcpcd -q -b --allowinterfaces 'en*' --denyinterfaces 'eth*'
ExecStop=/usr/bin/dhcpcd -x
[Install]
WantedBy=multi-user.target
</code></pre>
<p>Put this in <code>/etc/systemd/system/dhcpcd-all-en.service</code> and run</p>
<pre style="background-color:#fdf6e3;">
<code>systemctl daemon-reload
systemctl enable --now dhcpcd-all-en
</code></pre>
<p>Enjoy!</p>
Autologin and start X112020-04-13T00:00:00+00:002020-04-13T00:00:00+00:00https://thomas.spurden.name/blog/autostartx/<p>I have full disk encryption enabled and so don't want to have to type my disk
encryption password and then log in as my user immediately afterwards.</p>
<p>Install <a href="https://github.com/joukewitteveen/xlogin">xlogin</a></p>
<p>For Arch Linux it's in the <a href="https://aur.archlinux.org/packages/xlogin/">AUR</a></p>
<pre style="background-color:#fdf6e3;">
<code>systemctl enable xlogin@username
</code></pre>
<p>I found that this would not work, X wouldn't actually start. Examining the logs
in <code>/var/log/Xorg.0.log</code> (or <code>/var/log/Xorg.0.log.old</code> if you manually start X
before digging around) I could see that X11 couldn't find any display outputs.
So I added a kludge: in <code>/etc/systemd/system/x@.service.d/wait.conf</code> (which you
will need to create):</p>
<pre style="background-color:#fdf6e3;">
<code>[Service]
ExecStartPre=/bin/sleep 3
</code></pre>
<p>There's probably a better way to do this - maybe the x service needs some
dependency it doesn't have? But this has worked fine for me for a long time.</p>
Building a custom keyboard2019-07-18T00:00:00+00:002019-07-18T00:00:00+00:00https://thomas.spurden.name/blog/custom-keyboard/<p>I made a custom keyboard design, the <a href="https://github.com/tcrs/ergosnap">ErgoSnap</a>.</p>
<figure>
<a href="ergosnap_v1.1.jpg"><img src="ergosnap_v1.1.jpg" alt="Final constructed ErgoSnap rev1.1 board" /></a>
</figure>
<h2 id="backstory">Backstory</h2>
<p>A while ago I built an <a href="https://keeb.io/products/iris-keyboard-split-ergonomic-keyboard">Iris keyboard</a> from a kit because I wanted to try
a split layout. I'm pretty happy with it but after using it for a few months
decided I would like a few more keys. I had a look at what else was available,
and after spending a lot of time on the internet decided that the layout of the
<a href="https://github.com/omkbd/ErgoDash">ErgoDash</a> looked good. I liked the construction of the
<a href="https://github.com/reversebias/mitosis-hardware">Mitosis</a> using a single PCB design for both the mounting plate and the
actual PCB the switches are soldered to; however I didn't want a wireless
keyboard and wanted to stick with the <a href="https://qmk.fm/">QMK</a> firmware. So I decided to adapt
the ErgoDash design to use the Mitosis construction.</p>
<h2 id="construction">Construction</h2>
<p>Since I was designing my own keyboard I can tailor it to my requirements:</p>
<ul>
<li>Tenting built in. Just need two M6 mounting holes on the inside edge of the board</li>
<li>Don't care about any kind of lighting</li>
</ul>
<p>I chose to use the diode mounting holes in the keyswitches - these are wider
than the LED mounting holes (but in the same location on the keyswitch). They
are in the Cherry MX <a href="http://switches-sensors.zf.com/wp-content/uploads/2012/07/Keymodule_MX_EN.pdf">datasheet</a> (found by searching around on the internet). I'm
using <a href="https://zealpc.net/products/zealio">Zealios</a> and the ones I've got definitely have the diode holes. This
simplified routing as the diode overlaps the switch.</p>
<p>I created a custom component and footprint with the keyswitch, diode and cutouts
for snapout section (for the mounting plate PCB). Added a chin at the top for
the ProMicro, used the reversible ProMicro footprint from <a href="https://github.com/Biacco42/Ergo42">Ergo42</a>.</p>
<p>Ordered PCBs from JLC PCB:</p>
<figure>
<a href="keyboard-rev1-top-both.jpg"><img src="keyboard-rev1-top-both.jpg" alt="Top view of both halves of the revision 1 ErgoSnap PCB" /></a>
</figure>
<figure>
<a href="keyboard-rev1-side-view.jpg"><img src="keyboard-rev1-side-view.jpg" alt="Side view of stacked Rev 1 ErgoSnap PCB with a couple of keyswitches" /></a>
</figure>
<p>First set of PCBs arrived and looked good, but...</p>
<figure>
<a href="keyboard-rev1-diode-holes.jpg"><img src="keyboard-rev1-diode-holes.jpg" alt="Closeup of keyswitch and PCB showing misaligned holes for diode legs" /></a>
</figure>
<p>the holes for the diode legs were not the right distance apart! Whoops. Luckily
PCBs are pretty cheap so a couple of weeks later I had revised boards. Only a
few mistakes remain now. See the <a href="https://github.com/tcrs/ergosnap">ErgoSnap</a> github repo for more info.</p>
Getting started using the STM32 platform2018-02-28T00:00:00+00:002018-02-28T00:00:00+00:00https://thomas.spurden.name/blog/stm-start/<p>See <a href="https://github.com/tcrs/stm-start">this GitHub repo</a> for the related code
(and a copy of this post).</p>
<p>I have an <a href="http://wiki.seeedstudio.com/wiki/Arch_Max">Arch Max</a> STM32F4 development board. Although it <a href="https://developer.mbed.org/platforms/Seeed-Arch-Max/">supports
mbed</a> I like to understand how things work...</p>
<p>The <a href="http://www.st.com/resource/en/datasheet/dm00037051.pdf">datasheet</a> and <a href="http://www.st.com/resource/en/reference_manual/dm00031020.pdf">reference manual</a> have all the required
low-level hardware info. This guide will get you started compiling C programs to
directly twiddle with the stuff from the reference manual.</p>
<p>I experimented with two ways to get started on the board: CMSIS and libopencm3.
I preferred libopencm3 and that's what I'm using now, but have included examples
using both. The toolchain and debugging stuff is the same between the two.</p>
<h2 id="toolchain">Toolchain</h2>
<p>On Arch Linux, just install the <code>arm-none-eabi-gcc</code> and <code>arm-none-eabi-newlib</code>
packages. Most other distros probably offer similarly named packages.</p>
<h1 id="openocd">OpenOCD</h1>
<p>An <code>openocd</code> config file is provided in the <a href="https://github.com/Seeed-Studio/Arch_Max.git">Arch Max git repo</a>.
Then running <code>openocd -f Arch_Max/arch_max.cfg</code> with the board connected to a
USB port should pick it up and provide a GDB server on port <code>3333</code>. I have to
run OpenOCD as root as it needs access to the <code>/dev/hidraw0</code> device node to
communicate with the board.</p>
<h1 id="gdb">GDB</h1>
<p>Once OpenOCD is running and connected to the board via USB <code>gdb</code> can be
connected and used to run and debug binaries. </p>
<pre style="background-color:#fdf6e3;">
<code><span style="color:#657b83;">arm-none-eabi-gdb -x load.gdb binary.elf
</span></code></pre>
<p>The gdb script <code>load.gdb</code> will connect to the OpenOCD gdb server, reset the
board and load the elf file into memory. You can continue (<code>c</code>) at the GDB
prompt to just run the program. For debugging you can set breakpoints etc before
running too.</p>
<p>TOOD:</p>
<ul>
<li>how to program an image into flash</li>
</ul>
<h1 id="examples">Examples</h1>
<p>Both the libopencm3 and the CMSIS examples will produce a binary which when run
on the Arch Max board will blink the LED next to the ethernet port (GPIO B3).</p>
<p>The Makefiles in both examples will link an elf executable file, which is
suitable for debugging and loading via GDB (see above). A further <code>objcopy -O binary</code> step (also shown in the Makefiles) provides a file suitable for flashing
onto the chip's flash memory (I think - I've not got around to actually trying
this yet!).</p>
<h2 id="using-libopencm3">Using libopencm3</h2>
<p>The <a href="http://libopencm3.org/">libopencm3</a> project provide an free software library which is
in many ways similar to CMSIS, but I found it easier to get started with and it
is what I use for projects now. Note that despite the name it also supports a
large range of Cortex-M4 devices!</p>
<p>There is an awk script (<code>libopencm3/scripts/gnlink.awk</code>) which can generate the
required C flags and linker script for a device, see
<code>example-libopencm3/Makefile</code> for usage.</p>
<p>The generated linker script sets the executable entry point to <code>reset_handler</code>
which can be found in <code>libopencm3/lib/cm3/vector.c</code>. This is pretty minimal:</p>
<ul>
<li>clears the BSS</li>
<li>makes sure the stack is aligned correctly</li>
<li>calls pre_main, which may do chip-specific init. For the STM32F4 boards it
just enabled access to the floating point unit</li>
<li>calls global constructors</li>
<li>calls main() - provided by you!</li>
<li>calls global destructors</li>
</ul>
<p>Note that the clocks are left in their reset state. In the example I have
set them up (using the <code>rcc_clock_setup_hse_3v3</code> function) to run at 168MHz. The
settings there are correct for the Arch Max (I hope!) as it has a high-speed
external (hse) 8MHz oscillator.</p>
<p>Using the functions in libopencm3 to do simple things like toggling the GPIO is
probably rather wasteful as they each incur a function call overhead. If you are
worried about this then you can build the libopencm3 library and your binary
with link-time optimisation (LTO), which will inline any small functions the
compiler thinks is wise.</p>
<h2 id="using-cmsis">Using CMSIS</h2>
<p>I found it very difficult to find the CMSIS code. It turns out the best way to
get hold of it for a platform with mbed support (which is quite a lot, including
the Arch Max) is to just grab the mbed source code from <a href="https://github.com/ARMmbed/mbed-os.git">mbed-os GitHub
repo</a>. The repository contains lots of stuff for mbed and mbed-os, but the
interesting CMSIS code is in the <code>targets</code> folder.</p>
<p>Each supported chip has a folder containing a chip-specific startup assembly
file. For the Arch Max this is in <code>mbed-os/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F407xG/device/TOOLCHAIN_GCC_ARM/startup_stm32f407xx.S</code>
This assembly file contains the entry point (<code>Reset_Handler</code>) and quite well
documented with comments - it:</p>
<ul>
<li>Sets up the stack</li>
<li>Copies data segment from flash to RAM</li>
<li>Calls <code>SystemInit</code> to init clock and other basic hardware init</li>
<li>Calls <code>_start</code> (which is the libc entry point that will call main)</li>
<li>Provides the interrupt vector table
<ul>
<li>Default handler just enters an infinite loop</li>
<li>Set up to have a weak symbol for each interrupt, aliased to the default one</li>
<li>Can easily provide an interrupt handler by just creating a function of the
correct name (e.g. <code>EXTI0_IRQHandler</code>)</li>
</ul>
</li>
</ul>
<p>In the same folder as the startup <code>.S</code> file there should be a linker script
(<code>.ld</code>) which should be passed to the linker with the <code>-T</code> option. This is also
quite well commented.</p>
<p>An implementation of <code>SystemInit</code> for the Arch Max is provided
(<code>mbed-os/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F407xG/TARGET_ARCH_MAX/system_stm32f4xx.c</code>)
which sets up the system clock and various other bits and bobs. This
implementation calls into the HAL library (sources for which can be found at
<code>mbed-os/targets/TARGET_STM/TARGET_STM32F4/device/</code>). I have provided a chopped
down version <code>system_light.c</code> which does not depend on the HAL library.</p>
<p>The mbed files for your board should have some clues as to what pins are
connected to the odd LEDs etc. which are probably on your board. For the Arch
Max that was in
<code>mbed-os/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F407xG/TARGET_ARCH_MAX/PinNames.h</code></p>
<ul>
<li>at the bottom <code>LED1</code> is set to <code>PB_3</code>.</li>
</ul>
<p>The provided <code>Makefile</code> shows the compiler/linker options and include paths required.</p>
<p>See example in the <code>example-cmsis</code> folder (note you'll need that checkout of
<code>mbed-os</code> - see the readme in the folder).</p>
<p><a href="https://sergeev.io/notes/cortex_cmsis/">Good overview of CMSIS on the LPC devices</a></p>
<p><a href="http://regalis.com.pl/en/arm-cortex-stm32-gnulinux/">Very useful tutorial</a></p>
<p><a href="http://jeremyherbert.net/get/stm32f4_getting_started">Useful tutorial on making a blinky</a></p>
<p><a href="http://theanine.io/notes/cortex_cmsis/">Tutorial not really checked out yet</a></p>