MD380 firmware overview

    ('informal' and most likely outdated, by DL4YHF, 2017-04-04)

The first part of this file contains a 'heavily linked' overview of the patches for the MD380 / RT3 firmware.
Anything in this file is an 'interpretation' of DL4YHF, which may be utterly wrong.

The second part contains the description of firmware modifications tried by DL4YHF.
For a complete and up-to-date documentation, build your own (using Doxygen or similar) from the source, and see what's in the most recent distribution of Travis Goodspeed's MD380 Tools !

The third part contains fragments of information about the original firmware, with the intention of 'trying to understand' which hardware resources (timers, audio interface, etc) are still available for own experiments.

The fourth part describes a few of the author's experiments with own additions to the experimental firmware.

See also: RT3/MD-380 hardware, RT3/MD-380 index (by DL4YHF)


  1. C sourcecode overview
  2. Diagnostic functions
    1. Accessing C5000 registers via USB
  3. Disassembling parts of the original firmware
    1. Intro, disassembling with Radare2
    2. The interrupt vector table
    3. Processing of analog inputs (volume pot, battery voltage, etc)
    4. Hooked functions (in the original firmware)
  4. Own experiments with / additions to the experimental firmware
    1. Dimmed 'ambient' backlight for the display
    2. Morse code output (PWM, via speaker)
    3. An own menu (independent of the original firmware)
      1. Implementing a simple LCD driver
      2. Using bitmap fonts from the original firmware

1. C sourcecode overview

The links to the C sources below only work after copying this file into ?/md380tools/docs !
   |__applet/config.h : 
   |              > Configures our patches, either to temporarily enable
   |              > unstable features or to disable standard features when 
   |              > porting to a new target application version.
   |__applet/inc  : 'Library configuration file' by ST. Not very interesting.
   |                No MD380 specific functions in these stm32_.. header files.
   |__applet/lib  : STM32F4xx peripheral library from STM. Not very interesting,
   |    |            some say quite bloated. No MD380 specific functions in here.  
   |    |___ startup_stm32f4xx.s  interrupt vector table and 'early startup' code
   |                 First calls SystemInit(), then main(), as CMSIS wants us to :o)
   |__applet/src  : Contains most interesting parts of the patched firmware .
        |___ addl_config.c
        |___ aes.c
        |___ ambe.c         AMBE2+ Hook Functions
        |___ app_menu.c     alternative menu, opened via red BACK button
        |___ beep.c         beep_process Hook Functions
        |___ console.c      simple text console for netmon
        |___ debug.c        debug_printf(), etc
        |___ dispflip.c     display Hook functions
        |___ dispinit.c     display_reset(), display_init() 
        |___ display.c      high-level drawing functions
        |___ dmesg.c        Kernel logging functions and buffers
        |___ dmr.c          hooks some of the DMR packet handler functions
        |___ etsi.c
        |___ gfx.c          Graphics wrapper functions
        |___ i2c.c          i2c Hooks and diagnostic functions
        |___ irq_handlers.c interrupt handlers, first used for 'dimmed backlight'
        |___ keyb.c         implements a few hotkey functions
        |___ lastheard.c
        |___ lcd_driver.c   Much faster LCD driver for text output
        |___ main.c         MD380Tools Main Application
        |___ mbox.c         RTOS mailbox test
        |___ menu.c         Menu hooks and extensions
        |___ netmon.c       'highly volatile' network monitor, controlled via hotkeys
        |___ os.c           Hook functions for the real time OS
        |___ printf.c       subroutines for lightweight 'printf'
        |___ radiostate.c
        |___ rtc_timer.c    wrapper functions for the "RTC Timer"-Task
        |___ sms.c
        |___ spiflash.c     SPI-Flash Hook functions
        |___ stm32f4xx_it.c default interrupt handlers for STM32F4xx
        |___ syslog.c
        |___ system_stm32f4xx.c Low-level CPU initialisation
        |___ usb.c          USB Hook functions, optional C5000 register access via USB, etc
        |___ usersdb.c      implements the DMR user database, find_dmr(), etc
        |___ util.c         mkascii(), uli2w(), strhex(), wstrhex() 

2. Diagnostic functions

To use some of these diagnostic functions, some of the macros in config.h must be defined, for example CONFIG_SPIC5000 to read and (for the brave-hearted) write registers in the C5000 as explained below.

2.1 Accessing C5000 registers via USB

For this feature, CONFIG_SPIC5000 must be defined in config.h .
In the md380tools pulled from Github in 2016-12, this was already the case.

At the time of this writing (2016-12), c5000_spi0_readreg() was still the original code, for which no C sourcecode existed.
How to access registers inside the HR_C5000 chip is described in the datasheet, see english translation, chapter 8, 'data read and write'.

In the 'hooked' usb.c : usb_dnld_hook(), c5000_spi0_readreg() and c5000_spi0_writereg() can be called. After switching to libusb, this can be achieved via Python script, by calling c5000() in :

[email protected]
$ cd c:/tools/md380tools 

[email protected] /c/tools/md380tools
$ c:/tools/libusb-win32/bin/amd64/testlibusb-win.exe  # DL4YHF: for windows, tickle USB
                                                      # before accessing USB from Python

[email protected] /c/tools/md380tools
$ python c5000    # Prints some DMR registers...
[0x00]=0x00     [0x01]=0xb0     [0x02]=0x00     [0x03]=0x00
[0x04]=0x00     [0x05]=0x00     [0x06]=0x21     [0x07]=0x0b
[0x08]=0xb8     [0x09]=0x00     [0x0a]=0x00     [0x0b]=0x28
[0x0c]=0x33     [0x0d]=0x8c     [0x0e]=0x44     [0x0f]=0xc8
[0x10]=0x4f     [0x11]=0x80     [0x12]=0x0a     [0x13]=0x22
[0x14]=0x8d     [0x15]=0x0d     [0x16]=0x28     [0x17]=0x33
[0x18]=0xef     [0x19]=0x00     [0x1a]=0xff     [0x1b]=0xff
[0x1c]=0xff     [0x1d]=0xff     [0x1e]=0xf1     [0x1f]=0x10
[0x20]=0x00     [0x21]=0x00     [0x22]=0x07     [0x23]=0x3b
[0x24]=0xf8     [0x25]=0x3e     [0x26]=0xff     [0x27]=0x40
[0x28]=0xff     [0x29]=0x00     [0x2a]=0x0b     [0x2b]=0x00
[0x2c]=0x00     [0x2d]=0x00     [0x2e]=0x04     [0x2f]=0x0b
[0x30]=0x00     [0x31]=0x17     [0x32]=0x02     [0x33]=0xff
[0x34]=0xe0     [0x35]=0x28     [0x36]=0xf0     [0x37]=0xc2
[0x38]=0x00     [0x39]=0x02     [0x3a]=0x00     [0x3b]=0x00
[0x3c]=0x00     [0x3d]=0x0a     [0x3e]=0x28     [0x3f]=0x10
[0x40]=0x43     [0x41]=0x40     [0x42]=0x60     [0x43]=0x00
[0x44]=0x22     [0x45]=0x00     [0x46]=0x00     [0x47]=0x22
[0x48]=0x00     [0x49]=0x00     [0x4a]=0x00     [0x4b]=0x00
[0x4c]=0x00     [0x4d]=0x00     [0x4e]=0xf3     [0x4f]=0x09
[0x50]=0x00     [0x51]=0x08     [0x52]=0x01     [0x53]=0x00
[0x54]=0x00     [0x55]=0x00     [0x56]=0x00     [0x57]=0x00
[0x58]=0x00     [0x59]=0x00     [0x5a]=0x00     [0x5b]=0x00
[0x5c]=0x00     [0x5d]=0x00     [0x5e]=0x00     [0x5f]=0x00
[0x60]=0x00     [0x61]=0x33     [0x62]=0xef     [0x63]=0x00
[0x64]=0x00     [0x65]=0x0a     [0x66]=0x00     [0x67]=0x00
[0x68]=0x00     [0x69]=0x00     [0x6a]=0x00     [0x6b]=0x00
[0x6c]=0x00     [0x6d]=0x20     [0x6e]=0xc0     [0x6f]=0x29
[0x70]=0x00     [0x71]=0x49     [0x72]=0x00     [0x73]=0x00
[0x74]=0x00     [0x75]=0x00     [0x76]=0x00     [0x77]=0x00
[0x78]=0x00     [0x79]=0x00     [0x7a]=0x00     [0x7b]=0x00
[0x7c]=0x00     [0x7d]=0x00     [0x7e]=0x00     [0x7f]=0x00
[0x80]=0x00     [0x81]=0x08     [0x82]=0x00     [0x83]=0x00
[0x84]=0x00     [0x85]=0x00     [0x86]=0x00
For example, the author wanted to know the 'microphone gain' setting. The datasheet (partially translated from Chinese to English) says 'MicGain' is a bitgroup in register 0x0F.
The md380-tool (Python script invoked above) showed register 0x0f contained 0xC8 .
0xC8 = binary 1100 1000 (in C5000 register 0x0F)
              |||| ||||__ bit  0    : not specified in the HR_C5000 datasheet
              |||| |\\___ bits 2..1 = MicVol .   00    = 0 dB (which is the DEFAULT value)
              \\\\_\_____ bits 7..4 = ADLinVol.  11001 = (12 - 25*1.5) dB = -25.5 dB
At the time of this writing, it wasn't clear where and why the MD380 firmware modifies the 5 bits in 'ADLinVol'. The read-out value (ADLinVol=0b11001) is different from the default. The original firmware possibly initialises this register based on the settings in the original CPS'es 'Test Mode' screen.
With a modified Python script (, the value in register 0x0F was inspected or overwritten while transmitting.
Surprisingly, the 'MicVol' bits did not affect the voice modulation (neither in FM nor in DMR), but the 'ADLinVol' bits did. The (original) firmware seems to switch between DTMF ("Beep") and voice (input from the microphone) by switching bits in register 0x0E.
The patched firmware (2016-12-17) sets...
0x44 = binary 0100 0100 (in C5000 register 0x0E, while idle, or receiving FM, or sending FM or DMR voice except the 'gap')
              |||| ||||__ bit 0 : "Switch1" - purpose unknown by the time of this writing (2016-12)
              |||| |||___ bit 1 : "Mic2 enable". Here: 0 when transmitting voice, 1 when sending DTMF in FM
              .|.. ||____ bit 2 : "Mic1 enable". Here: 1 when sending voice
               |   |_____ bit 3 : "LineOutEnable. Here: 0, since we don't want to hear anything in the speaker.
               |_________ bit 6 : "HPMute". Seems to be always set to mute the (unused?) headphone output.

0x42 = binary 0100 0010 (in C5000 register 0x0E, while sending DTMF in FM )
              ||||  ||___ bit 1 : "Mic2 enable". Here: 1 when sending DTMF in FM
              ....  |____ bit 2 : "Mic1 enable". Here: 0 to disable the microphone

0x4C = binary 0100 1100 (in C5000 register 0x0E, while receiving DMR voice, with the "squelch open")
              |||| |||___ bit 1 : "Mic2 enable". Here: 0 when transmitting voice, 1 when sending DTMF in FM
              .... ||____ bit 2 : "Mic1 enable". Here: 0 when sending DTMF in FM, 1 when sending voice
                   |_____ bit 3 : "LineOutEnable. Here: 1, to have an audio signal in the speaker.

After 'understanding' C5000 reg 0x0E, what about the 'mic gain' settings via reg 0x0F ?
While transmitting in FM and DMR-simplex, different values were poked into register 0x0F, and the md380tool's "mic bargraph" was used to adjust the amplitude of a 1 kHz test signal to let the bargraph just touch the point between 'gray' and 'green area' in the colour legend.
The following voltages were necessary to reach that point:
   Value in reg 0x0F    |  0xC8   |  0xD8  |    0xE0   |  0xE8  | 0xF0   | 0xF8   |
   Voltage at Mic-Input | 12 mVpp | 9 mVpp |  7.2 mVpp | 6 mVpp | no modulation ! |
The reason for not being able to use the highest gain settings (reg_0E: 0xF0 or 0xF8) was unknown. It may be an improperly biased analog input (similar to an 'overdriven' op-amp), etc.
Anyway, the distance between 0xC8 (=default gain in the stock firmware) and 0xE8 (=highest possible gain in the author's RT3) is 6 dB. That's worth for being 'tried out' with a modified firmware... which was later implemented by KG5RKI by overwriting register 0x0F in added in rtc_timer.c : f_4225_hook().
See also: audio signal paths during transmission.

3. Disassembling parts of the firmware

3.1 Intro, disassembling with Radare2

Before adding adding features that use certain hardware resources (for example, PWM-capable timers), it's necessary to find out if those resources aren't used by the already existing firmware.
In traditional software development for embedded systems, there should be a nicely documented or 'self-documenting'(?) pile of C sources, which can be extended easily.
In the partially-reverse-engineered MD380 firmware, things are different. Thanks to the effort Travis and a Merry Band of Reverse Engineers (see PoC||GTFO issue 10, chapter 8), the hood of The Engine That Runs The World has already been lifted (firmware been read out and unscrambled). But to understand how a certain part of the engine works, it must be disassembled (or even decompiled into functions with 'speaking names', which would be a daunting task).

This chapter describes a half-hearted attempt to disassemble parts of the original firmware (the one used for a non-GPS radio, RT3). Primary goals: Much work has already been done by Travis and contributors, see ?/md380tools/annotations/d13.020 for example.
The disassembler used for this purpose is not the old-familiar 'objdump' (for Cortex-M, arm-none-eabi-objdump) but a utility named 'Radare2'. Even though being familar with ARM/Cortex-M development (but only "forward engineering"), it took some time to get used to it. Radare2 can be installed from
> Radare is an open source reversing framework. It comes with a ton of options,
> functionality, and a somewhat daunting learning curve.     (..)

There at least three very different ebooks on Radare/Radare2. For a beginner (in Radare) like myself, 'radare2-explorations.pdf' was the easiest to read.
'radare2book.pdf' fills the gap (read at least 'Basic Radare Usage' in it).
Radare commands are composed of single characters (!), like seek, print, alterate/analyse; often followed by more characters, like afl ("list all functions", with addresses, flags, sizes, and names).

Radare2 was installed on the windows 'workhorse' under c:/tools/Radare2.

Next, loosely based on

c:\tools\md380tools\annotations\d13.020>radare2.exe -a arm -m 0x0800C000 -b 16 -i flash.r flash.img
    ( "flash.img" had been copied and renamed from firmware/unwrapped/D013.020.img)

To produce a half-way readable "listing" of the original firmware, with a few annotations of function names and registers, a Radare2 script was stitched together based on the already existing files (flash.r, cpu.r).
The result was saved as md380tools/annotations/d13.020/disasm_yhf.r . A copy of the modified Radare2 script may still be available at DL4YHF's website ( disasm_yhf.r ).
The output (disassembly listing) contains most exception- and interrupt handlers.
For very curious readers (familiar with Cortex-M assembly), the result is

this disassembly listing
(The file 'listing.htm' contains some of the disassembled original functions,
garnished with HTML anchors so it can easily be linked from other documents.
The listing does not include anything about the software AMBE vocoder.)

The number of annotations in the listing will hopefully increase. In January 2017, it was "subject to frequent changes".
If you are interested in the subject, get in touch via the md380tools group.

Structure of the above disassembly listing (at the time of this writing... also "subject to change"):
  1. Overview of all functions currently in the listing (with 'clickable' links injected by another script)
  2. Hex dump of the interrupt vector table (see next chapter for details)
  3. Disassembly of the startup code (Reset_Handler(), SystemInit(), etc)
  4. Disassembly of exception- and interrupt service handlers
  5. Disassembly of other functions, one by one, whose purpose is (almost) known
  6. List of annotated symbols, along with their hex addresses and (for some) size in bytes

Principal workflow to generate the above disassembly listing:
  1. Let disam_yhf.bat call Radare2.exe, telling it to process disasm_yhf.r
  2. Examine the result (listing.txt or listing.htm)
  3. Modify disasm_yhf.r if necessary (this is a rather cryptic Radare2 script),
    then go back to step 1
  4. When 'satisfied' with the result, convert to html (via,
    garnished with 'anchors' and links, and upload to the website
New annotations in disasm_yhf.r may eventually find their way into the 'master' at Github (md380tools/applet/src/symols_d13.020 ?).

3.2 The interrupt vector table (VT)

The STM32F4xx Reference Manual ("RM0090", "DocID108909 Rev 7", page 374) shows the (interrupt-) vector table beginning at 0x0000 0000. But since the firmware image starts at 0x0800C000 (i.e. internal flash), the vector table must have been remapped to some address inside that range (start=0x0800C000, size=0xF3000 bytes). Remapping takes place in the custom bootloader shortly after power-up, because the first part of the internal flash is occupied by Tytera's "custom" firmware bootloader.
On first glance, the STM32F40x didn't seem to have a VTOR register (Vector Table Offset Register), at least the Reference Manual (ST's RM0090, DocID108909 Rev 7) doesn't mention it. All it says on page 70 is that certain devices can boot from Flash "bank 1" or "bank 2". The "Reference Manual" only mentions a 'SYSCFG memory remap register' on page 286, with only TWO BITS:
Two bits are used to configure the type of memory accessible at address 0x0000 0000.
These bits are used to select the physical remap by software and so, bypass the BOOT pins.

A full-text search for 'SYSCFG' in RM0090 showed it's base address is 0x40013800 (RM0090 page 66).
'SYSCFG_MEMRM' is at offset 0x00 (RM0090 page 291), so let's ask for the contents of SYSCFG_MEMRM:

[email protected] /c/tools/md380tools
$ python hexdump 0x40013800
Dumping memory from 0x40013800.
00 00 00 00 00 00 00 00  22 02 00 00 00 00 00 00
|_________| |_________|  |_________| |_________|
     |           |            |           |
MEMRM=0x00000000 means 'Main Flash memory is mapped at 0x00000000'.
With that setting, the first couple of bytes @ 0x00000000 should contain the same as the begin of the 'Main Flash' (0x08000000, not 0x0800C000) !
Let's see if our poor man's debugger (patched USB handler) confirms if Flash is being 'mirrored' to the begin of the address space:
[email protected] /c/tools/md380tools
$ python hexdump32 0x00000000

00000000: 20001A30 08005615 08005429 0800542B
00000010: 0800542D 0800542F 08005431 00000000
00000020: 00000000 00000000 00000000 08005433
00000030: 08005435 00000000 08003083 08005437

$ python hexdump32 0x08000000

08000000: 20001A30 08005615 08005429 0800542B
08000010: 0800542D 0800542F 08005431 00000000
08000020: 00000000 00000000 00000000 08005433
08000030: 08005435 00000000 08003083 08005437
08000040: 08005661 08005665 08005669 0800545B
All these vectors (except the first, which is the "initial stack pointer") point into the bootloader !
Thus they can't belong to the 'application firmware'. There must be some magic that the STM32F4xx "Reference Manual" doesn't tell that allows the application firmware to have its own vector table.
After wondering why even a FULL TEXT SEARCH for "VTOR" in the "Reference Manual" (RM0090) didn't reveal anything, the specification of SCB (System Control Block) and VTOR (Vector Table Offset Register) was finally found in an extra document, titled "PM0214 / Programming manual" / "DocID022708 Rev 5". Aaargh !
'VTOR' is at address 0xE000ED08, and only accessable in privileged mode. Let's try if our 'debugger' can access it:
[email protected] /c/tools/md380tools
$ python hexdump32 0xE000ED00

E000ED00: 410FC241 00000853 0800C000 FA050300  ← CPUID, ICSR, VTOR,  AIRCR
E000ED10: 00000000 00000200 00000000 00000000  ← SCR,   CCR,  SHPR1, SHPR2
E000ED20: F0F00000 00000000 00000000 00000000  ← SHPR3, SHCRS, CFSR, HFSR 
E000ED30: 00000000 E000ED34 E000ED38 00000000  ← ? ? ?, MMAR, BFAR,  AFSR
We're lucky, the USB command handler runs in privileged mode, and the 32-bit values in VTOR, 0x0800C000, is just as expected:
The exception- and vector table begins at the start of the firmware image.
Can we trust the values read from the 'SCB' registers via ? The first 32-bit register in SCB, CPUID, should contain 411FC231 (according to PM0214 Rev 5 page 243). 'Not quite right' but this wouldn't be the first bug in a datasheet.

Ok, so VTOR contains 0x0800C000, which is the application firmware's vector table in Flash, and we already know the VT has not been remapped to internal RAM (which would also be possible on the STM32F4).

Let list the vector table (in 32-bit DWORDs) to find the addresses of dozens of interrupt- and exception handlers in the patched firmware. Underlined entries were added manually to indicate differences between original firmware (here: unwrapped/d13.020.img):
[email protected] /c/tools/md380tools
$ python hexdump32 0x0800C000

0800C000: 2001F170 0809AF01 08093EF1 08093EF9  ← initial SP, RESET,   NMI,  HardFault 
0800C010: 08093F01 08093F09 08093F11 00000000  ← MemManage, BusFault, UsageFault, rsvd
0800C020: 080F9245 00000000 00000000 08093F19  ← rsvd?!, rsvd,    rsvd, SVCall 
0800C030: 08093F1B 00000000 08043E1F 08093F1D  ← DebugMonitor, rsvd,  PendSV,     SysTick
0800C040: 080FC5AD 080FC5B1 080FC5B5 08094239  ← Watchdog, PVD_EXTI,  TAMP_STAMP, RTC_WKUP
0800C050: 080FC5BD 080FC5C1 0809403F 08094023  ← Flash,  RCC,     EXTI0, EXTI1
0800C060: 08093F9D 08093F89 080FC5D5 080FC5D9  ← EXTI2,  EXTI3,   EXTI4, DMA1_S0
0800C070: 080FC5DD 0804E9A1 080FC5E5 080FC5E9  ← DMA1_S1,DMA1_S2,DMA1_S3,DMA1_S4
0800C080: 0804E9A9 080FC5F1 080FC5F5 080FC5F9  ← DMA1_S5,DMA1_S6, ADC, CAN1_TX
0800C090: 080FC5FD 080FC601 080FC605 080FC609  ← CAN1_RX0, CAN1_RX1, CAN1_SCE, EXTI9_5
0800C0A0: 080FC60D 080FC611 080FC615 080FC619  ← TIM1_BRK_TIM9, TIM1_UP_TIM10, TIM1_TRG_COM_TIM11, TIM1_CC
0800C0B0: 08049D5F 08094109 0809408D 080FC629  ← TIM2,   TIM3,    TIM3,  I2C1_EV
0800C0C0: 080FC62D 080FC631 080FC635 080FC639  ← I2C1_ER,I2C2_EV, I2C2_ER, SPI1
0800C0D0: 080FC63D 080FC641 080FC645 080FC649  ← SPI2,   USART1,  USART2, USART3
0800C0E0: 080FC64D 080FC651 08093F3F 080FC659  ← EXTI15_10, RTC_Alarm, OTG_FS_WKUP, TIM8_BRK_TIM12 
0800C0F0: 08094229 080FC661 080FC665 080FC669  ← TIM8_UP_TIM3, TIM8_TRG_COM_TIM1, TIM8_CC, DMA1_S7
0800C100: 080FC66D 080FC671 08094079 0804E999  ← FSMC,   SDIO, TIM5, SPI3
0800C110: 080FC67D 080FC681 08094139 080941E9  ← UART4,  UART5,  TIM6_DAC, TIM7,
0800C120: 080FC68D 080FC691 080FC695 08094271  ← DMA2_S0, DMA2_S1, DMA2_S2, DMA2_S3,
0800C130: 080FC69D 080FC6A1 080FC6A5 080FC6A9  ← DMA2_S4, ETH,    ETH_WKUP, CAN2_TX
0800C140: 080FC6AD 080FC6B1 080FC6B5 08093F71  ← CAN2_RX0, CAN2_RX1, CAN2_SCE, USB_OTG_FS
0800C150: 080FC6BD 080FC6C1 080FC6C5 080FC6C9  ← DMA2_S5, DMA2_S6, DMA2_S7, USART6
0800C160: 080FC6CD 080FC6D1 080FC6D5 080FC6D9  ← I2C3_EV,I2C3_ER, OTG_HS_EP1_OUT, OTG_HS_EP1_IN 
0800C170: 080FC6DD 080FC6E1 080FC6E5 080FC6E9  ← OTG_HS_WKUP, OTG_HS, DCMI, CRYPTO
0800C180: 080FC6ED 080FC6F1                    ← HASH_RNG, FPU

The vector table layout is in the STM32F4 Reference Manual ("RM0090" Rev 7). Beware, RM0090 describes various ('slightly incompatible') chips; the one to use here is 'Table 61. Vector table for STM32F405xx/07xx ..' on pages 368..372.
Bit 0 in a table entry (data=address) indicates a handler written in Thumb code. Since this Cortex-M only 'understands' Thumb, all entries in the VT must be odd. The handler functions themselves must start at even addresses. DL4YHF's Radare2 script uses a kluge to find out the even handler addresses to disassemble them.
The 'reserved' interrupt vector at 0x0800C020 is (ab-)used as storage for the original reset vector - see patches in patches/d13.020/

From the above list it's obvious that the original firmware uses different default handlers for each interrupt. For example, the MD380 will never need a CAN-receive-interrupt. This gave some confidence that also some of those timer interrupts are also just dummies.
The interrupt vector table itself appeared to be implemented in applet/lib/startup_stm32f4xx.s, which was referenced from the makefile. We could populate it with our own interrupt handlers, but at the time of this writing (2016-12), too many of the original firmware's interrupt handlers would need to be patched in.
For that reason, the patch (in only modifes 'a few words' but not the entire interrupt vector table.
The map file (applet/ from 2016-12-30) started at Flash-address 0809B000, i.e. our own compiled and linked part of the firmware doesn't even get close to the interrupt vector table.
To install own interrupt handlers (later), the VT could be copied into RAM (at runtime), and any new vector be installed "dynamically" to avoid even more script-controlled patches.

3.3 Processing of analog inputs (volume pot, battery voltage, etc)

For own extensions (described in later chapters of this file), it was necessary to 'tap' some of the analog inputs. As explained in the hardware description, the following analog inputs (on ADC1) are copied via DMA into a 6-element array in the internal RAM:
In DL4YHF's disassembly listing, the array with the 6 'raw' ADC readings was named 'adc1_6channel_dma_buffer' (RAM address 0x2001e51c).
Follow the links in the listing to a few functions that reference this array to find some of the places where these values are processed (finding all details requires more patience or doggedness). The following guesswork applies to firmware D13.020:

3.4 Hooked functions (in the original firmware)

3.4.1 f_4315_hook()

The origin of this function name was unknown (perhaps just a number, or part of an address in an older firmware). In D13.020, two calls to f_4315_hook() were patched at addresses 0x080202D0 and 0x080202FC, replacing the original calls to F_4314().
These functions are related with the display output. By intercepting these calls, the netmon, text console, and possibly a few other 'own' displays can prevent updates of the framebuffer (inside the LCD) by the original firmware, and draw something else instead (e.g. console or an optional 'own menu').
Because the original firmware also 'draws' something on the display, by calling subroutines like gfx_set_fg_color(), gfx_blockfill(), and various flavours of gfx_drawtext(), it's safe to access the display from here.

3.4.2 kb_handler_hook()

Replaces the original keyboard handler (kb_handler), which in turn is called from this function. In D13.020, called from the main loop at 0x0804ebd2 (via 'biglist_pollsubsys_maybe').
The orignal function (kb_handler at 0x0804f94c) scans the multiplexed keyboard matrix though various subroutines (quite obfuscated, and no reason to find out how it works yet). The result of this 'lowest level' of polling the keyboard is stored in variable 'kb_row_col_pressed'.
Keys are debounced via software, and distinction is made between short and long key pressing times. Depending on which keys were pressed (and how long they were pressed), different subroutines are called from the original kb_handler(), including a table-driven conversion of the key into a keycode (variable kb_keycode). Further actions don't seem to take place in the original kb_handler(), so the hooked function (kb_handler_hook, only exists in the patched firmware), can intercept certain keystrokes. That happens in applet/src/keyb.c .
Functions called from kb_handler_hook() besides the 'original' kb_handler():