From b9bac4f039aaeb8fee4b2f49bef148818408ef74 Mon Sep 17 00:00:00 2001 From: DaveHodder67 Date: Wed, 1 Jul 2015 16:00:05 +0100 Subject: importing the real code (!) --- Makefile | 58 ++++++++++++++ README.md | 66 +++++++++------- Vagrantfile | 24 +++--- bootstrap.sh | 58 +++++++++++--- include/app.h | 84 ++++++++++++++++++++ include/app_defs.h | 65 +++++++++++++++ resources/Launchpad Pro-1.0.154.syx | Bin 0 -> 114219 bytes src/app.c | 92 ++++++++++++++++++++++ stm32_flash.ld | 143 +++++++++++++++++++++++++++++++++ tools/hextosyx.cpp | 152 ++++++++++++++++++++++++++++++++++++ 10 files changed, 690 insertions(+), 52 deletions(-) create mode 100644 Makefile create mode 100644 include/app.h create mode 100644 include/app_defs.h create mode 100755 resources/Launchpad Pro-1.0.154.syx create mode 100644 src/app.c create mode 100644 stm32_flash.ld create mode 100644 tools/hextosyx.cpp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8c91f24 --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +BUILDDIR = build + +TOOLS = tools + +SOURCES += src/app.c + +OBJECTS = $(addprefix $(BUILDDIR)/, $(addsuffix .o, $(basename $(SOURCES)))) + +INCLUDES += -Iinclude \ +-I\ + +SYX = $(BUILDDIR)/launchpad_pro.syx +LIB = lib/launchpad_pro.a +ELF = $(BUILDDIR)/launchpad_pro.elf +HEX = $(BUILDDIR)/launchpad_pro.hex +HEXTOSYX = $(BUILDDIR)/hextosyx + +HOST_CC = g++ +CC = arm-none-eabi-gcc +LD = arm-none-eabi-gcc +OBJCOPY = arm-none-eabi-objcopy + +CFLAGS = -Os -g -Wall -I.\ +-D_STM32F103RBT6_ -D_STM3x_ -D_STM32x_ -mthumb -mcpu=cortex-m3 \ +-fsigned-char -DSTM32F10X_MD -DUSE_STDPERIPH_DRIVER -DHSE_VALUE=6000000UL \ +-DCMSIS -DUSE_GLOBAL_CONFIG -g3 -ffunction-sections -std=c99 -mlittle-endian \ +$(INCLUDES) -o + +LDSCRIPT = stm32_flash.ld + +LDFLAGS += -T$(LDSCRIPT) -u _start -u _Minimum_Stack_Size -mcpu=cortex-m3 -mthumb -specs=nano.specs -specs=nosys.specs -nostdlib -Wl,-static -g3 -N -nostartfiles -Wl,--gc-sections + +all: $(SYX) + +# build the final sysex file from the ELF +$(SYX): $(HEX) $(HEXTOSYX) + ./$(HEXTOSYX) $(HEX) $(SYX) + +# build the tool for conversion of ELF files to sysex ready for upload to the unit +$(HEXTOSYX): + $(HOST_CC) -Ofast -std=c++0x -I./$(TOOLS)/libintelhex/include ./$(TOOLS)/libintelhex/src/intelhex.cc $(TOOLS)/hextosyx.cpp -o $(HEXTOSYX) + +$(HEX): $(ELF) + $(OBJCOPY) -O ihex $< $@ + +$(ELF): $(OBJECTS) + $(LD) $(LDFLAGS) -o $@ $(OBJECTS) $(LIB) + +$(BUILDDIR)/%.o: %.c + mkdir -p $(dir $@) + $(CC) -c $(CFLAGS) $< -o $@ + +$(BUILDDIR)/%.o: %.s + mkdir -p $(dir $@) + $(CC) -c $(CFLAGS) $< -o $@ + +clean: + rm -rf $(BUILDDIR) diff --git a/README.md b/README.md index 5424389..f5f0968 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # Launchpad Pro Open source firmware for the Novation Launchpad Pro grid controller! By customising this code, you can: -- Implement your own unique standalone modes +- Implement your own unique standalone apps - Create chorders, sequencers, light shows, games and more -- Learn a little about firmware development! +- Learn a little about software development! -You definitley need *some* C programming experience, but we've deliberately kept much of the firmwarey nastiness tucked away, to make the process a little friendlier. +You'll definiteley need *some* C programming experience, but we've deliberately kept much of the firmwarey nastiness tucked away, to make the process a little friendlier. # Philosophy -We could have released the full source for the factory shipping firmware, but we've decided not to for a variety of reasons. Instead, we created a simplified framework for developing "apps" on Launchpad, which comprises a build environment, application entry points / API, and a library of low level source code. Our reasoning is as follows: +We could have released the full source for the factory shipping firmware, but we decided not to for a variety of reasons. Instead, we created a simplified framework for developing "apps" on Launchpad, which comprises a build environment, application entry points / API, and a library of low level source code. Our reasoning is as follows: -- There is no value in customising low level routines such as LED multiplexing or ADC scanning - this code has been carefully tweaked over many months to deliver the best results, and is not something you'd want to tweak. I'm prepared to be convinced otherwise! -- There is very little value in customising main() or other low level features, and again these things are hard to do well. -- If we shipped the application firmware as-is, we'd have a support nightmare on our hands (imagine the support calls - my "Launchpad Pro is behaving strangely..."). Instead, we wanted to create a clear boundary between "normal" usage with Ableton, and custom firmware. As such, Ableton integration has been removed from this firmware, as has the setup / navigation functionality. -- If we left the Ableton integration and menu structure in place, open firmware developers would have to work around it. They would also potentially consume precious RAM/CPU resources. I've a feeling this isn't what you'd want, but again, we're interested to hear your feedback. +- There is no value in customising low level routines such as LED multiplexing or ADC scanning - this code has been carefully tweaked over many months to deliver the best results, and is not something you'd want to mess with. +- There is very little value in customising main() or other low level features, and again these things are hard to do well. Interrupt priorities? No. +- If we shipped the application firmware as-is, we'd have a support nightmare on our hands (imagine the phonecalls - my "Launchpad Pro is behaving strangely..."). Instead, we wanted to create a clear boundary between "normal" usage with Ableton, and custom firmware. As such, Ableton integration has been removed from this firmware, as has the setup / navigation functionality. +- If we left the Ableton integration and menu structure in place, open firmware developers would have to work around it. They would also potentially consume precious RAM/CPU resources. I've a feeling this isn't what you'd want, but we're interested to hear your feedback. - Licensing requirements for the CMSIS library version we use are ambiguous. Yes, we could port to the public version, but why bother, given the above reasoning - I'd prefer to spend my time on good documentation and examples. As such, all the CMSIS code is compiled into launchpad_pro.a, and we do not need to distribute the headers. I'm sure you'll have feedback for us, so please do get in touch! I'm [blogging the process too](http://dvhdr.tumblr.com/) if you'd like to read my musings. @@ -21,29 +21,45 @@ I'm sure you'll have feedback for us, so please do get in touch! I'm [blogging # Installation This project uses [Vagrant](https://www.vagrantup.com/) to manage the build environment. As such, you need to: -1. Install [Vagrant](https://www.vagrantup.com/) -2. Install [VirtualBox](https://www.virtualbox.org/wiki/Downloads)(or another virtualisation platform of your choice) -3. `vagrant up` +1. Clone this repository +2. Install [Vagrant](https://www.vagrantup.com/) +3. Install [VirtualBox](https://www.virtualbox.org/wiki/Downloads) +4. `vagrant up` to create a brand new development virtual machine, all configured and ready to build the firmware! # Building -SSH into the Vagrant "box" by doing `vagrant up` -At the command prompt, simply type `make` +You can build the app in one of two ways. In the spirit of experimenation, I've created a full Eclipse development environment for you to use. However, you might prefer to do things on the command line. (if you do, you might also want to run your Vagrant box headless, which you can do by hacking the Vagrantfile). -This will generate the firmware launchpad_pro.syx file in the build directory. You can then upload this to your Launchpad Pro from the host! +# To use the command line interface: +1. SSH into the Vagrant "box" by doing `vagrant ssh` +2. At the command prompt, simply type `make` + +# To build using the Eclipse GUI + +1. Log in to the Ubuntu GUI (the password is, as is the convention, "vagrant"). +2. Launch Eclipse from the doodah on the top left (it's a bit like Spotlight) +3. This is the annoying bit. I can't figure out how to store the project in source control, so you need to import it. +4. In Eclipse, choose "File->Import..." +5. Under "C/C++", choose "Makefile project with existing code" +6. Give the project any name you like (launchpad?) +7. Under "Existing code location" choose /vagrant +8. Hit Finish. You should now see your project on the left! +9. Click the build icon at the top, and wait while the project builds. + +Either of the above methods will generate the firmware launchpad_pro.syx file in the /build directory. You can then upload this to your Launchpad Pro from the host! # Uploading to a Launchpad Pro -Now you've got some nice new code to run. To upload it to your Launchpad Pro, you'll need a sysex tool for your host platform (I'd love to get it working from the virtual machine, but that's for later). I recommend [Sysex Librarian](http://www.snoize.com/SysExLibrarian/) on OS X, and [MIDI OX](http://www.midiox.com/) on Windows. On Linux, I'll bet you know better than I do! +Now you've got some nice new code to run! To upload it to your Launchpad Pro, you'll need a sysex tool for your host platform (I'd love to get it working from the virtual machine, but that's for later). I recommend [Sysex Librarian](http://www.snoize.com/SysExLibrarian/) on OS X, and [MIDI OX](http://www.midiox.com/) on Windows. On Linux, I'll bet you already have a tool in mind. -I won't describe how to use these tools, I'm sure you already know - and if you don't, their documentation is better than mine! Here's what you need to do: +I won't describe how to use these tools, I'm sure you already know - and if you don't, their documentation is superior to mine! Here's what you need to do: 1. Unplug your Launchpad Pro -2. Hold the "setup" button down while connecting it to your host via USB (ensure it's connected to the host, and not to a virtual machine!) +2. Hold the "Setup" button down while connecting it to your host via USB (ensure it's connected to the host, and not to a virtual machine!) 3. The unit will start up in "bootloader" mode -4. "play" your launchpad_pro.syx file to the device - it should breifly scroll "updateing..." across the grid +4. Send your launchpad_pro.syx file to the device MIDI port - it should briefly scroll "updating..." across the grid. 5. Wait for the update to complete, and for the device to reboot! # Bricked it! -Don't worry - even if you upload toxic nonsense to the device, you cannot brick it - the bootloader is stored in a protected area of flash. If your new firmware doesn't boot, you'll get stuck at step (3) above. Simply repeat the process with the shipping firmware image (Launchpad Pro-1.0.154.syx) to restore your unit to the factory defaults. +Don't worry - even if you upload toxic nonsense to the device, you cannot brick it - the bootloader is stored in a protected area of flash. If your new firmware doesn't boot, you'll get stuck at step (3) above, or with a crashed unit. Simply repeat the above process with the shipping firmware image (resources/Launchpad Pro-1.0.154.syx) to restore your unit to the factory defaults. Better yet, fix the bugs :) # API The most crucial parts of the API are: @@ -51,18 +67,14 @@ The most crucial parts of the API are: - Recieving messages from the pads and buttons - Writing colours to the LEDs - Sending and receiving messages from the MIDI ports -- Receiving a tick message to drive timing based code - -Up for debate: +- Receiving a tick message to drive timer based code -- USB MIDI support (why not just implement on the host in programmer mode using the factory firmware?) -- Flash memory read / write access -- Setup button behaviour +The best way to learn about these is to read the documentation in app.h, and to study the example code! # Debugging -We decided not to support or encourage using a JLink or similar for debugging, as opening a Launchpad Pro to fit a debugging header can easily damage the FSR (force sensitive resistor) sheet. +We decided not to support or encourage using a hardware debugger, as opening a Launchpad Pro to fit a debugging header can easily damage the FSR (force sensitive resistor) sheet. Instead, you're going to have to do things the old fashioned way - by blinking LEDs or sending MIDI messages. FWIW, that's the way I've developed this version of the firmware - dogfooding all the way ;) -If you want to test code in detail, we suggest developing it on a host (we'd love to release our simulator environment at a later stage, but there are currently no plans to do so). +If you want to test code in detail, we suggest developing it on a host. I want to develop a host app which presents the same API to app.c, but which forwards all messages to and from the hardware Launchpad Pro via MIDI - that way you'll be able to debug most code on the host very nicely. diff --git a/Vagrantfile b/Vagrantfile index 19a4ea9..81f83fd 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -21,11 +21,6 @@ Vagrant.configure(2) do |config| config.vm.provision :shell, path: "bootstrap.sh" - # use the desktop! - #config.vm.provider :virtualbox do |vb| - # vb.gui = true - #end - # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine. In the example below, # accessing "localhost:8080" will access port 80 on the guest machine. @@ -50,14 +45,17 @@ Vagrant.configure(2) do |config| # backing providers for Vagrant. These expose provider-specific options. # Example for VirtualBox: # - # config.vm.provider "virtualbox" do |vb| - # # Display the VirtualBox GUI when booting the machine - # vb.gui = true - # - # # Customize the amount of memory on the VM: - # vb.memory = "1024" - # end - # + config.vm.provider "virtualbox" do |vb| + # Display the VirtualBox GUI when booting the machine + vb.gui = true + + # Customize the amount of memory on the VM: + vb.memory = "2048" + + # let’s have something usable for proper development + vb.cpus = 2 + end + # View the documentation for the provider you are using for more # information on available options. diff --git a/bootstrap.sh b/bootstrap.sh index 111b248..63991d5 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -1,25 +1,59 @@ #!/usr/bin/env bash # setup as described here: http://gnuarmeclipse.livius.net/blog/toolchain-install/#GNULinux +# We're using Luna - need to update plugin install locations below if switching to Kepler etc. +ECLIPSE_URL=http://mirror.netcologne.de/eclipse//technology/epp/downloads/release/luna/SR2/eclipse-cpp-luna-SR2-linux-gtk-x86_64.tar.gz -# tools for running 32-bit binaries +# latest GCC-ARM version +GCC_ARM_URL=https://launchpad.net/gcc-arm-embedded/4.8/4.8-2014-q3-update/+download/gcc-arm-none-eabi-4_8-2014q3-20140805-linux.tar.bz2 + +# install tools for running 32-bit binaries apt-get update sudo apt-get install -y lib32z1 lib32ncurses5 lib32bz2-1.0 -# eclipse IDE -# sudo apt-get install -y eclipse - -# download the compiler -wget -O /tmp/gcc-arm.tar.bz2 https://launchpad.net/gcc-arm-embedded/4.9/4.9-2015-q1-update/+download/gcc-arm-none-eabi-4_9-2015q1-20150306-linux.tar.bz2 +# install Java +sudo apt-get install -y openjdk-7-jdk + +# download Eclipse +wget -O /tmp/eclipse.tar.gz $ECLIPSE_URL + +# Extract files +sudo tar zxvf /tmp/eclipse.tar.gz -C /opt/ +# Change ownership to root +sudo chown -R root:root /opt/eclipse/ + +# Create launch script in /usr/bin +echo '#!/bin/sh' | sudo tee /usr/bin/eclipse +echo 'export ECLIPSE_HOME="/opt/eclipse"' | sudo tee -a /usr/bin/eclipse +echo '$ECLIPSE_HOME/eclipse $*' | sudo tee -a /usr/bin/eclipse +sudo chmod 755 /usr/bin/eclipse + +# Create menu entry +echo "[Desktop Entry]" | sudo tee /usr/share/applications/eclipse.desktop +echo "Name=Eclipse IDE" | sudo tee -a /usr/share/applications/eclipse.desktop +echo "Comment=Integrated Development Environment" | sudo tee -a /usr/share/applications/eclipse.desktop +echo "TryExec=/usr/bin/eclipse" | sudo tee -a /usr/share/applications/eclipse.desktop +echo "Exec=/usr/bin/eclipse" | sudo tee -a /usr/share/applications/eclipse.desktop +echo "Icon=/opt/eclipse/icon.xpm" | sudo tee -a /usr/share/applications/eclipse.desktop +echo "Categories=Development;IDE;Java;" | sudo tee -a /usr/share/applications/eclipse.desktop +echo "Terminal=false" | sudo tee -a /usr/share/applications/eclipse.desktop +echo "Type=Application" | sudo tee -a /usr/share/applications/eclipse.desktop +echo "StartupNotify=true" | sudo tee -a /usr/share/applications/eclipse.desktop + +# Delete the eclipse tarball +rm /tmp/eclipse.tar.gz + +# download the gcc-arm tools +wget -O /tmp/gcc-arm.tar.bz2 $GCC_ARM_URL # expand and add it to the path (yes, I know it says don't ever do this - but this is a Vagrant box ;) -cd /usr/local -sudo tar xjf /tmp/gcc-arm.tar.bz2 - -# cd to the /vagrant dir on login -echo "cd /vagrant" >> /home/vagrant/.bashrc +sudo tar xjf /tmp/gcc-arm.tar.bz2 -C /usr/local # set up path to arm gcc on login -echo "export PATH=/usr/local/gcc-arm-none-eabi-4_9-2015q1/bin:$PATH" >> /home/vagrant/.bashrc +sudo echo "export PATH=/usr/local/gcc-arm-none-eabi-4_8-2014q3/bin:$PATH" >> /etc/profile.d/env.sh +# Delete the gcc-arm tarball +rm /tmp/gcc-arm.tar.bz2 +# helpfully cd to the /vagrant dir on login +echo "cd /vagrant" >> /home/vagrant/.bashrc diff --git a/include/app.h b/include/app.h new file mode 100644 index 0000000..51eef58 --- /dev/null +++ b/include/app.h @@ -0,0 +1,84 @@ +// +// app.h +// + +#ifndef LAUNCHPAD_APP_H +#define LAUNCHPAD_APP_H + +// ____________________________________________________________________________ +// +#include "app_defs.h" + +// ____________________________________________________________________________ +// +// Interface to the hardware (implemented in launchpad_pro.a library) +// ____________________________________________________________________________ + +/** + * Set an LED's RGB value. This function is safe to call from any + * of the app functions below, at any time. + */ +void hal_plot_led(u8 type, u8 index, u8 red, u8 green, u8 blue); + +/** + * Send a MIDI message to either USB port or to the DIN output. + */ +void hal_send_midi(u8 port, u8 status, u8 d1, u8 d2); + +/** + * Send system exclusive to USB or DIN. Messages must be correctly formatted + * (F0 ... F7) and must not exceed 320 bytes. + */ +void hal_send_sysex(u8 port, const u8* data, u16 length); + +// ____________________________________________________________________________ +// +// Interface from the hardware (implemented in app.c) +// ____________________________________________________________________________ + +/** + * Called on startup, this is a good place to do any initialisation. + */ +void app_init(); + +/** + * 1kHz (1ms) timer. You can set LEDs and send MIDI out from this function, + * but you will get LED tearing as there is (currently) no double buffering. + * + * You will get jitter. + */ +void app_timer_event(); + +/** + * Called when a MIDI message is received from USB or DIN. + */ +void app_midi_event(u8 port, u8 status, u8 d1, u8 d2); + +/** + * As above, but for system exclusive messages. Low level hardware buffering sets + * a maximum message size of 320 bytes, messages larger than this will not work. + */ +void app_sysex_event(u8 port, u8 * data, u16 count); + +/** + * Called when a MIDI DIN breakout cable is connected or disconnected. Note that + * you can still write MIDI events to the DIN ports even if no cable is connected. + */ +void app_cable_event(u8 type, u8 value); + +/** + * Called when the user presses or releases any button or pad on the control surface. + */ +void app_surface_event(u8 type, u8 index, u8 value); + +/** + * Called when the low level pad scanning reports an aftertouch (pad pressure) event. + * A pad press event will always come first. Note that the factory firmware sets a + * threshold to prevent excessive aftertouch after the initial hit, at the expense of + * dynamic range. This firmware does not - the full range of the pad is transmitted, + * with the side effect that aftertouch onset is more rapid. + */ +void app_aftertouch_event(u8 index, u8 value); + + +#endif diff --git a/include/app_defs.h b/include/app_defs.h new file mode 100644 index 0000000..b339ed1 --- /dev/null +++ b/include/app_defs.h @@ -0,0 +1,65 @@ + +#ifndef APP_TYPES_H +#define APP_TYPES_H + +// ____________________________________________________________________________ +// +// Types +// ____________________________________________________________________________ + +typedef signed long s32; +typedef signed short s16; +typedef signed char s8; + +typedef unsigned long u32; +typedef unsigned short u16; +typedef unsigned char u8; + +// ____________________________________________________________________________ +// +// App structure +// ____________________________________________________________________________ + +#define TYPEPAD 0 +#define TYPESETUP 1 + +#define MAXLED 63 + +// ____________________________________________________________________________ +// +// Useful MIDI constants +// ____________________________________________________________________________ + +#define NOTEON 0x90 +#define NOTEOFF 0x80 +#define POLYAFTERTOUCH 0xA0 +#define CC 0xB0 +#define CHANNELAFTERTOUCH 0xD0 +#define SONGPOSITIONPOINTER 0xF2 +#define MIDITIMINGCLOCK 0xF8 +#define MIDISTART 0xFA +#define MIDICONTINUE 0xFB +#define MIDISTOP 0xFC + +// ____________________________________________________________________________ +// +// MIDI ports +// ____________________________________________________________________________ + +// USB MIDI: "Standalone" port +#define USBSTANDALONE 0 + +// USB MIDI: "MIDI" port +#define USBMIDI 1 + +// MIDI DIN port +#define DINMIDI 2 + + +// MIDI Jack Socket Switch IDs + +#define MIDI_IN_CABLE 0 +#define MIDI_OUT_CABLE 1 + +// ____________________________________________________________________________ +#endif diff --git a/resources/Launchpad Pro-1.0.154.syx b/resources/Launchpad Pro-1.0.154.syx new file mode 100755 index 0000000..b6bf226 Binary files /dev/null and b/resources/Launchpad Pro-1.0.154.syx differ diff --git a/src/app.c b/src/app.c new file mode 100644 index 0000000..662007e --- /dev/null +++ b/src/app.c @@ -0,0 +1,92 @@ +//______________________________________________________________________________ +// +// Headers +//______________________________________________________________________________ + +#include "app.h" + +//______________________________________________________________________________ + +void app_surface_event(u8 type, u8 index, u8 value) +{ + switch (type) + { + case TYPEPAD: + { + // example - light / extinguish pad LEDs, send MIDI + hal_plot_led(TYPEPAD, index, value, value, value); + hal_send_midi(USBSTANDALONE, NOTEON | 0, index, value); + } + break; + + case TYPESETUP: + { + // example - light the setup LED + hal_plot_led(TYPESETUP, 0, value, value, value); + } + break; + } +} + +void app_midi_event(u8 port, u8 status, u8 d1, u8 d2) +{ + // example - MIDI interface functionality for USB "MIDI" port -> DIN port + if (port == USBMIDI) + { + hal_send_midi(DINMIDI, status, d1, d2); + } + + // // example -MIDI interface functionality for DIN -> USB "MIDI" port port + if (port == DINMIDI) + { + hal_send_midi(USBMIDI, status, d1, d2); + } +} + +void app_sysex_event(u8 port, u8 * data, u16 count) +{ +} + +void app_aftertouch_event(u8 index, u8 value) +{ + // example - send poly aftertouch to MIDI ports + hal_send_midi(USBMIDI, POLYAFTERTOUCH | 0, index, value); + + // example - set LED to white, brightness in proportion to pressure + hal_plot_led(TYPEPAD, index, value/2, value/2, value/2); +} + +void app_cable_event(u8 type, u8 value) +{ + // example - light the Setup LED to indicate cable connection + if (type == MIDI_IN_CABLE) + { + hal_plot_led(TYPESETUP, 0, 0, value, 0); + } + else if (type == MIDI_OUT_CABLE) + { + hal_plot_led(TYPESETUP, 0, value, 0, 0); + } +} + +void app_timer_event() +{ +} + +//______________________________________________________________________________ + +void app_init() +{ + // example - light the LEDs to say hello! + for (int i=0; i < 10; ++i) + { + for (int j=0; j < 10; ++j) + { + u8 r = i < 5 ? (MAXLED * (5-i))/5 : 0; + u8 g = i < 5 ? (MAXLED * i)/5 : (MAXLED * (10-i))/5; + u8 b = i < 5 ? 0 : (MAXLED * (i-5))/5; + + hal_plot_led(TYPEPAD, j*10 + i, r, b, g); + } + } +} diff --git a/stm32_flash.ld b/stm32_flash.ld new file mode 100644 index 0000000..c66fd66 --- /dev/null +++ b/stm32_flash.ld @@ -0,0 +1,143 @@ +/* Entry Point */ +ENTRY(Reset_Handler) + +/* Highest address of the user mode stack */ +_estack = 0x20005000; /* end of 20K RAM */ + +/* Generate a link error if heap and stack don't fit into RAM */ +_Min_Heap_Size = 0; /* required amount of heap */ +_Min_Stack_Size = 0x200; /* required amount of stack */ +_Reserve_Stack_Value = 4; + +/* Specify the memory areas. Set FLASH ORIGIN to 0x08000000 for debugging. */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08006400, LENGTH = 128K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K + MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K +} + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into FLASH */ + .isr_vector : + { + . = ALIGN(4); + KEEP(*(.isr_vector)) /* Startup code */ + . = ALIGN(4); + } >FLASH + + /* The program code and other data goes into FLASH */ + .text : + { + . = ALIGN(4); + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + *(.glue_7) /* glue arm to thumb code */ + *(.glue_7t) /* glue thumb to arm code */ + + KEEP (*(.init)) + KEEP (*(.fini)) + + . = ALIGN(4); + _etext = .; /* define a global symbols at end of code */ + } >FLASH + + + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH + .ARM : { + __exidx_start = .; + *(.ARM.exidx*) + __exidx_end = .; + } >FLASH + + .ARM.attributes : { *(.ARM.attributes) } > FLASH + + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array*)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } >FLASH + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array*)) + PROVIDE_HIDDEN (__init_array_end = .); + } >FLASH + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array*)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } >FLASH + + /* used by the startup to initialize data */ + _sidata = .; + + /* Initialized data sections goes into RAM, load LMA copy after code */ + .data : AT ( _sidata ) + { + . = ALIGN(4); + _sdata = .; /* create a global symbol at data start */ + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + + . = ALIGN(4); + _edata = .; /* define a global symbol at data end */ + } >RAM + + /* Uninitialized data section */ + . = ALIGN(4); + .bss : + { + /* This is used by the startup in order to initialize the .bss secion */ + _sbss = .; /* define a global symbol at bss start */ + __bss_start__ = _sbss; + *(.bss) + *(.bss*) + *(COMMON) + + . = ALIGN(4); + _ebss = .; /* define a global symbol at bss end */ + __bss_end__ = _ebss; + } >RAM + + PROVIDE ( end = _ebss ); + PROVIDE ( _end = _ebss ); + + /* User_heap_stack section, used to check that there is enough RAM left */ + /* also reserve space for the stack overun protection value */ + ._user_heap_stack : + { + . = ALIGN(4); + . = . + _Min_Heap_Size; + . = ALIGN(4); + . = . + _Reserve_Stack_Value ; + . = ALIGN(4); + . = . + _Min_Stack_Size; + . = ALIGN(4); + } >RAM + + /* MEMORY_bank1 section, code must be located here explicitly */ + .memory_b1_text : + { + *(.mb1text) /* .mb1text sections (code) */ + *(.mb1text*) /* .mb1text* sections (code) */ + *(.mb1rodata) /* read-only data (constants) */ + *(.mb1rodata*) + } >MEMORY_B1 + + /* Remove information from the standard libraries */ + /DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + } +} diff --git a/tools/hextosyx.cpp b/tools/hextosyx.cpp new file mode 100644 index 0000000..fd761b2 --- /dev/null +++ b/tools/hextosyx.cpp @@ -0,0 +1,152 @@ +#include +#include + +// Ported from the original Delphi version. +// CLI parameters for ID, ByteWidth and BaseAddress removed for simplicity + +#include "intelhex.h" + +static const int ByteWidth = 32; +static const int ID = 0x0051; + +static const unsigned char RESET[] = {0xf0, 0x00, 0x20, 0x29, 0x00, 0x71}; + +// must match unpacking code in the bootloader, obviously +static void eight_to_seven(unsigned char * Output, const int OOffset, + intelhex::hex_data& Input, const unsigned long IOffset) +{ + for (int i = 0; i < 7; ++i) + { + if (!Input.is_set(IOffset + i)) + { + // pad unset addresses + Input.set(IOffset + i, 0xff); + } + } + + // seven bytes of eight-bit data converted to + // eight bytes of seven-bit data + // render 8-bit words as 16-bit words + Output[OOffset+0] = Input[IOffset+0] >> 1; + Output[OOffset+1] = (Input[IOffset+0] << 6) + (Input[IOffset+1] >> 2); + Output[OOffset+2] = (Input[IOffset+1] << 5) + (Input[IOffset+2] >> 3); + Output[OOffset+3] = (Input[IOffset+2] << 4) + (Input[IOffset+3] >> 4); + Output[OOffset+4] = (Input[IOffset+3] << 3) + (Input[IOffset+4] >> 5); + Output[OOffset+5] = (Input[IOffset+4] << 2) + (Input[IOffset+5] >> 6); + Output[OOffset+6] = (Input[IOffset+5] << 1) + (Input[IOffset+6] >> 7); + Output[OOffset+7] = (Input[IOffset+6]); + + for (int i = 0; i < 8; ++i) + { + Output[OOffset+i] &= 0x7f; + } +} + +static void write_block(intelhex::hex_data& data, std::ofstream& ofs, const unsigned long addr, const unsigned char type) +{ + // packet header + ofs.write(reinterpret_cast(RESET), 5); + ofs.put(type); + + int incount = 0; + int outcount = 0; + int outn = 1 + (ByteWidth * 8) / 7; + + // payload + unsigned char payload[41]; + + while (incount < ByteWidth) + { + eight_to_seven(payload, outcount, data, addr+incount); + + incount += 7; + outcount += 8; + } + + ofs.write(reinterpret_cast(payload), outn); + ofs.put(0xf7); +} + +static void write_header(intelhex::hex_data& data, std::ofstream& ofs, size_t BaseAddress) +{ + // human-readable version number & header block + ofs.write(reinterpret_cast(RESET), 6); + ofs.put(ID >> 8); + ofs.put(ID & 0x7f); + + ofs.put(data[BaseAddress + 0x132] >> 4); + ofs.put(data[BaseAddress + 0x132] & 0x0f); + ofs.put(data[BaseAddress + 0x131] >> 4); + ofs.put(data[BaseAddress + 0x131] & 0x0f); + ofs.put(data[BaseAddress + 0x130] >> 4); + ofs.put(data[BaseAddress + 0x130] & 0x0f); + + ofs.put(0xf7); +} + +static void write_checksum(intelhex::hex_data& data, std::ofstream& ofs) +{ + // device doesn't respect the checksum, but we still need this block! + ofs.write(reinterpret_cast(RESET), 5); + unsigned char payload[19]; + + const char *FIRMWARE = "Firmware"; + + payload[0] = 0x76; + payload[1] = 0x00; + for (int i = 0; i < 8; ++i) + { + payload[i+2] = FIRMWARE[i]; + } + payload[10] = 0x00; + payload[11] = 0x00; + payload[12] = 0x00; + payload[13] = 0x00; + payload[14] = 0x00; + payload[15] = 0x00; + payload[16] = 0x00; + payload[17] = 0x00; + payload[18] = 0xf7; + + ofs.write(reinterpret_cast(payload), 19); +} + +int main(int argc, char *argv[]) +{ + std::cout << "converting " << argv[1] << " to sysex file: " << argv[2] << std::endl; + + // read the hex file input + intelhex::hex_data data; + data.load(argv[1]); + + size_t BaseAddress = data.min_address(); + size_t MaxAddress = data.max_address(); + + std::cout << "max addr: " << std::hex << data.max_address() << " min_addr: " << std::hex << data.min_address() << std::endl; + + // create output file + std::ofstream ofs(argv[2] , std::ios::out | std::ios::binary); + if( !ofs ) + return -1; + + write_header(data, ofs, BaseAddress); + + // payload blocks... + unsigned long i = BaseAddress + ByteWidth; + + while (i < MaxAddress) + { + write_block(data, ofs, i, 0x72); + + i += ByteWidth; + } + + write_block(data, ofs, BaseAddress, 0x73); + + // footer/checksum block + write_checksum(data, ofs); + + ofs.close(); + + return 0; +} \ No newline at end of file -- cgit v1.2.1