MD380 hardware

At the moment, this document is just an unstructured collection of notes, started by DL4YHF 2017-01-15.
It will eventually make it into an additional 'hardware description', besides the lots of details that you will find in the MD380 Tools already.

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

Contents

  1. Introduction
    1. Measurements (transmit spectra, power consumption, ..)
  2. Internals
    1. The C5000 'baseband chip'
    2. Audio signal paths and the 'baseband' chip
    3. The display and its connection to the CPU
    4. The keyboard and its connection to the CPU
    5. DMA channels and -streams
    6. CPU pin / port usage
    7. Hardware timer usage
    8. A/D converter usage

1. Introduction


To microcontroller nuts like myself: Don't miss Travis Goodspeed's 'Reverse Engineering the Tytera MD380' at www.qsl.net/kb9mwr/projects/dv/dmr/Reverse Engineering the Tytera MD380.pdf. That article also contains a part of the circuit diagram. The complete schematics are available online, also some photos from the inside for the FCC registration.

1.1 Measurements

DMR transmit spectra measured before and hard- and software modification:
Wideband spectrum (span = 100 MHz)

Narrow band spectrum (span = 100 kHz)
Compared with some other low-cost handheld transceivers, the TX spectrum is remarkably clean.

Power consumption (measured directly at the battery terminals at 8 V): With the original firmware, during idle, with backlight off: circa 70 mA

2. Internals


2.1 The C5000 'baseband chip' and its connections to the CPU


The datasheet for the HR_C5000 is mostly in Chinese. As Travis before, I tried to let the 'big brother' translate some of the chapters..
results here. The chip's block diagram on page 3 is quite informative:

Block diagram from the HR_C5000 datasheet

The HR_C5000 performs the DMR encoding (Tier I/II), contains an A/D converter for the microphone, a D/A converter for the speaker, an I2S (digital audio) interface to access the audio codec (note: that's not the vocoder). The 'RF' (IF) ports use quadrature signals for transmission and reception. It is designed to co-operate with an external (!) 'VoCoder', connected via SPI (!). In this case, the vocoder is the CPU itself (STM32F405). From the HR_C5000's point of view, the CPU 'looks like' an AMBE-chip. For analog FM, there's a path from the audio codec to the modulator (FM TX), and (for FM RX) a path from the FM discriminator to the codec, from codec to I2S, and from I2S (pins I2S_FS → PA15, I2S_CK → PC10, I2S_TX → PC12, I2S_RX → PC11) to the CPU (2016-12-27: beware, this wasn't verified yet).

The STM32F4 reference manual (RM0090) calls their equivalent 'SAI' (serial audio interface).
The STM32F405 datasheet ("DocID022152 Rev 8", page 56) tells us that...
  - pin PA15 can be configured as 'I2S3_WS' ('word strobe' ~~ 'frame sync'),
  - pin PC10 can be configured as 'I2S3_CK' (digital audio clock),
  - pin PC12 can be configured as 'I2S3_SD' (serial audio data out),
  - pin PC11 can be configured as 'I2S3ext_SD' (serial audio data in).
If more details about the control registers inside the HR_C5000 were known, we could use the STM32F4 as a DSP (the CPU is powerful enough, as users of the famous STM32F4xx-'discovery'-boards will confirm). Unfortunately there's no obvious signal path between the DUC (digital up-converter), DDC (digital down-converter), and the HR_C5000's audio codec (which would have to be a fast stereo DAC for that purpose). If there was, we could play real 'software defined radio' with this rig.

2.1 Audio signal paths in the RT3 / MD380 and the 'baseband' chip

The already mentioned MD380 schematics (MD-380UHF-RF-schematic.pdf) shows most, but not all details (and some CPU connections are wrong). At least the analog signal paths are easy to follow, and appeared to be correct.

2.1.1 Audio signal path during reception


  HR_C5000 pin 74 'LINEOUT' → U203 → 'VOL_OUT'
    → SW401 ('vol-switch' with old-fashioned potentiometer) → U204 (audio power amplifier)
        → 'EXT_SPK+', 'SPK-' -> Speaker.

It was a mystery to me how the CPU (STM32F405) 'knows' the position of the analog volume potentiometer.
In the schematics, the pot's wiper is only AC coupled. But instead (not shown in the schematics), it's also DC-coupled through a low-pass filter to an analog input of the CPU. Carlos, EB4FBZ, described this at github.com/travisgoodspeed/md380tools/issues/98 .



2.1.2 Audio signal path for message beeps / local signalling tones


The schematic diagram (from 'Peter', 2014-11-08) doesn't show the entire truth about the signal path for message beeps: When 'Tones/Alerts' are enabled in the 'Radio Settings' menu, they are audible in the speaker with a constant volume, regardless of the analog potentiometer between 'VOL_OUT' and the speaker PA (U204).
But the schematic diagram only shows these audio-related outputs from the CPU:
  STM32F405 PC8, 'BEEP' → 2nd order RC lowpass → ?? "VOL_OUT" → volume pot ?? → audio amp → speaker
  STM32F405 PC8, 'BEEP' → 2nd order RC lowpass → "2T/5T/DTMF_OUT" (enters the HR_C5000, thus only for TX)
  STM32F405 PC7 (no label) → another RC lowpass → "CTC/DCS_OUT" (modulates the transmitter, not for the speaker)

Guesswork: Another inacuracy in the schematics. More likely:
The low-pass filtered 'BEEP' is routed directly to the audio amplifier, bypassing the volume control pot.
A test with a rectangular wave (generated on PC8) confirmed that such a signal path (bypassing the pot) exists.
To generate a tone locally for output to the speaker, ...
The above findings were incorporated in isr_handlers.c (a contribution to the MD380-tools), along with a simple Morse generator which was later used to announce the current channel name for visually impaired hams.

2.1.3 Audio signal paths during transmission


For voice:
  Microphone → U103 → 'MIC_OUT' → HR_C5000 pin 8 'MIC1_P'

For DTMF- and other signalling tones:
  STM32F405 PC8, 'BEEP' → 2nd order RC lowpass → 2T/5T/DTMF_OUT' → HR_C5000 pin 5 'MIC2_P'

The signal paths inside the HR_C5000 from 'MIC_P1' (here: voice) and 'MIC_P2' (here: DMTF & Co) to the modulator (for FM), or to the vocoder interface (for DMR), are quite complex. More on that in the list of registers, translated from the chinese HR_C5000 datasheet. Some of the details were not fully understood at the time of this writing, but experiments with the MD380 Tools (and the patched firmware) gradually shed more light on this.

With a modified variant of the md380-tools ( tool2.py with "c5write" to invoke c5000poke() via command line), various registers were modified during transmission, in FM and DMR-simplex. This way, I tried to find out which of those registers (and bits) really affect the signal path, and if / when those registers were overwritten by the remaining parts of the 'original' firmware. Conclusions:
The ugly details (and how to control the 'mic gain' with a modified firmware) are in md380_fw.html#mic_gain_control.


< ... to be continued ... >

2.3 The display and its connection to the CPU


There are different, and slightly incompatible displays used in the RT3/RT8 and MD-380/MD-390.
The modified firmware (md380tools) makes a distinction of cases in applet/dispinit.c, based on information read from a special area in the SPI Flash.
The display in the author's RT-3 was labelled 'HG01802008-B'. It is connected to the CPU via an 8-bit port, with some signals configured as FSMC (static memory interface):

     PD14 : LCD_D0, J101 (connector) pin 5  
     PD15 : LCD_D1, J101 pin 6
     PD0  : LCD_D2, J101 pin 7
     PD1  : LCD_D3, J101 pin 8
     PE7  : LCD_D4, J101 pin 9
     PE8  : LCD_D5, J101 pin 10
     PE9  : LCD_D6, J101 pin 11
     PE10 : LCD_D7, J101 pin 12
     PD5  : LCD_WR, J101 pin 17
     PD4  : LCD_RD, J101 pin 13
     PD6  : LCD_CS, J101 pin 16
     PD12 : LCD_RS (register select), J101 pin 14
     PD13 : LCD_RST, J101 pin 15
The framebuffer is in the display itself. The display controller is fairly compatible with an ST7735 (the actual chip is an HX8302A, for which no sufficient datasheet was found yet). The pin numbers shown for 'J101' above apply to the 24-pin flat ribbon cable between main board and keyboard/display PCB.
Only when the display image is modified, there is 'activity' on this 8-bit interface between CPU and display. Unfortunately, such a data transfer caused some radio interference that can even be picked up with another radio, switched to SSB or FM, located near the RT3 / MD-380.
Obviously the manufacturer tried to reduce RFI emitting from the cable between CPU and display board by connecting 1 kOhm series resistors in the eight data lines. Also, to make the 'display QRM' less apparent, the firmware hides the display of the current time (and thus avoids periodic transfers) when the squelch is open in FM.

In attempt to read out the contents of the framebuffer, thin wires were attached to LCD_RD, LCD_WR, LCD_RS, LCD_CS to check the timing with an oscilloscope. There are five 22-Ohm SMD resistors on the keyboard PCB where these control signals can be tapped.

2.4 The keyboard and its connection to the CPU


The keyboard consists of a matrix of rows and columns. Some of the keyboard signals are shared with the LCD interface (see previous chapter). The original Tytera firmware uses several of subroutines for the multiplexed polling, invoked from an extra task.
In the D13.020 firmware, these are called from 'kb_handler_sub1' at 0x0804F7DC (code address). Shortly before returning, that function configures the I/O ports to access the LCD again ('ConfigureLCDPort' at 0x08051F22).

     PA6  : multiplexer output, CPU to keyboard "K1", J101 (connector) pin 2 
     PD2  : multiplexer output, CPU to keyboard "K2", J101 pin 3
     PD3  : multiplexer output, CPU to keyboard "K3", J101 pin 4 (unused?)
     PD14 : keyboard to CPU, shared with LCD_D0, J101 pin 5
     PD15 : keyboard to CPU, shared with LCD_D1, J101 pin 6
     PD0  : keyboard to CPU, shared with LCD_D2, J101 pin 7
     PD1  : keyboard to CPU, shared with LCD_D3, J101 pin 8
     PE7  : keyboard to CPU, shared with LCD_D4, J101 pin 9
     PE8  : keyboard to CPU, shared with LCD_D5, J101 pin 10
     PE9  : keyboard to CPU, shared with LCD_D6, J101 pin 11
     PE10 : keyboard to CPU, shared with LCD_D7, J101 pin 12

To avoid shorting the LCD data lines when pressing multiple keys, there's a 1 kOhm resistor in series with each of the keyboard signals listed above. "K1" selects the upper and lower row, "K2" selects the two rows in the middle:

Keyboard matrix on MD380 PCB. Click to magnify.
As long as no key is pressed, the multiplexer outputs ("K1".."K2") are permanently high. The original firmware only starts a 10-ms keyboard polling cycle when a key was pressed. For that purpose, if the interface isn't occupied by the LCD, both keyboard multiplexer outputs (K1, K2) are actively driven 'high', the LCD data lines are configured as inputs (with weak pull-down resistors)

2.5 DMA channels and -streams

Some of the transfers between CPU and peripherals use DMA. To find out their purpose, the DMA interrupt handlers were analysed in the disassembly listing, and the 'channel' numbers of the active streams were inspected at runtime (using tool2.py bindump32 DMA1_S2CR, DMA1_S5CR, DMA2_S3CR). Result: DMA stream usage in an RT3/MD380 with firmware based on D013.020:
(The STM32F4 reference manual, RM0090 Rev 13, lists the peripherals for DMA transfers on pages 309 (DMA1) and 305 (DMA2). The channel numbers were read from an RT3/MD-380, bits 27..25 in the stream control registers, same result during RX and TX.)
For peripheral-to-memory transfers, the DMA stream usage can be validated by examining the peripheral's address in the DMAx_SyPAR registers ("PAR" = "Peripheral Address Register").

The SPI3 interrupt handler can be studied in the disassembled firmware (pretty tough to understand, despite the Radare2 annotations).
Details about the signal paths to and from SPI3 / I2S3 ('digital audio') will eventually be added in the firmware description.

DMA2 is mainly used to read A/D conversion results. Register dump (from tool2.py) for DMA2:
40026400: 00000030 00000000 00000000 00000000 (DMA2LISR,DMA2HISR,DMA2LIFCR,DMA2HIFCR)
40026410: 00002D01 00000002 4001204C 2001E51C (DMA2S0CR,DMA2S0NDTR,DMA2S0PAR,DMA2S0M0AR)
40026420: 00000000 0000001F 00000000 00000000 (DMA2S0M1AR,DMA2S0FCR,DMA2S1CR,DMA2S1NDTR)
40026430: 00000000 00000000 00000000 00000021 (DMA2S1PAR,DMA2S1M0AR,DMA2S1M1AR,DMA2S1FCR)
40026440: 00000000 00000000 00000000 00000000 (DMA2S2CR,DMA2S2NDTR,DMA2S2PAR,DMA2S2M0AR)
40026450: 00000000 00000021 02002D19 0000006B (DMA2S2M1AR,DMA2S2FCR,DMA2S3CR,DMA2S3NDTR)
40026460: 4001214C 2001BF80 00000000 00000017 (DMA2S3PAR,DMA2S3M0AR,DMA2S3M1AR,DMA2S3FCR)
40026470: 00000000 00000000 00000000 00000000 (DMA2S4CR,DMA2S4NDTR,DMA2S4PAR,DMA2S4M0AR)
40026480: 00000000 00000021 00000000 00000000 (DMA2S4M1AR,DMA2S4FCR,DMA2S5CR,DMA2S5NDTR)
40026490: 00000000 00000000 00000000 00000021 (DMA2S5PAR,DMA2S5M0AR,DMA2S5M1AR,DMA2S5FCR)
400264A0: 00000000 00000000 00000000 00000000 (DMA2S6CR,DMA2S6NDTR,DMA2S6PAR,DMA2S6M0AR)
400264B0: 00000000 00000021 00000000 00000000 (DMA2S6M1AR,DMA2S6FCR,DMA2S7CR,DMA2S7NDTR)
400264C0: 00000000 00000000 00000000 00000021 (DMA2S7PAR,DMA2S7M0AR,DMA2S7M1AR,DMA2S7FCR)
DMA2, stream 0 is configured for channel 0 by the Tytera firmware:
 DMA2S0CR   = 0x00002D01 : Bits 27..25 ("CHSEL") = 000bin means "channel 0" is selected for this stream.
 DMA2S0PAR  = 0x4001204C = address of ADC1's "DATA" register (conversion result, source for DMA transfers)
 DMA2S0M0AR = 0x2001E51C = destination address in RAM (array structure matching ADC1 settings)
The interrupt handler for DMA2, stream 0 is obviously not in use (at least not in Firmware D13.020). Instead, the 'digitized results' from ADC1's 'regular channel sequence' (with six entries in D13.020) are processed in various tasks and handlers as explained in the firmware description.


DMA2, stream 3 is configured for channel 1 by the Tytera firmware:
 DMA2S3CR   = 0x02002D19 : Bits 27..25 ("CHSEL") = 001bin means "channel 1"
                           (more on this "stream channel" below). 
 DMA2S3PAR  = 0x4001214C = address of ADC2's "DATA" register (conversion result, source for DMA transfers)
 DMA2S3M0AR = 0x2001BF80 = destination address in RAM (array structure matching ADC2 settings)
According to RM0090 Rev 13, page 310, DMA2 / stream 3 / channel 1 means this stream is connected to ADC2. The contents of "DMA2S3PAR" (Peripheral Address Register for stream 3) confirms this.
Watching 256 32-bit locations in that address range only revealed 'flickering bits' when receiving in FM.
This is possibly 'digitized FM receive audio' to detect signalling tones (CTCSS, DTMF).
Processing takes place in the interrupt handler, DMA2_Stream3_IRQHandler.

See also: A/D converter usage.

2.6 CPU pin / port usage

This chapter contains some of the 'verified' CPU pins, and their purposes.
The list is incomplete; most of the pins listed below were used in own additions to the firmware, or may be affected by them (e.g. multi-channel PWM from timer 8).
GPIO register hexdumps were made with a modified descendant of the md380-tools (tool2.py).
Each line of a hex dump shows four 32-bit registers, with the address in the leftmost column.
Register names in parenthesis were added for convenience.
To find the configured pin functions, some hex register values were converted to binary, then broken up into bitgroups (for 'MODER'- and 'AFR' registers). Double-quoted names are taken from the schematics; as we know, these must be taken with a grain of salt !
The alternate functions are only listed in the STM32F405 datasheet, not in the reference manual.
Abbreviations:
  GPI = general-purpose input (reset state)
  GPO = general-purpose output
  AF = Alternate Function (as configured per AFRL,AFRH)
  AN = Analog

2.6.1 Port A


$ python tool2.py hexdump32 GPIOA 40
40020000: 96954FFF 00000000 ABEAA000 00001000 (MODER,OTYPER,OSPEEDR,PUPDR)
40020010: 00000AC0 00000280 00000000 00000000 (IDR,  ODR,   BSRR,   LCKR)
40020020: 00000000 600AA000                   (AFRL, AFRH)
                   |  ||___ PA11 : AF #10
                 PA15 |____ PA12 : AF #10
Decoded from the MODER- and AFR-registers for GPIO A:

GPIOA_MODER : hex 96954FFF, binary below, in TWO BIT - groups :
        __________|      |__________________________
   ____|                                            |____
   10 01  01 10  10 01  01 01  01 00  11 11  11 11  11 11 
   \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|_PA0 : Analog, "volume pot pos" (ADC123_IN0)
    |  |   |  |   |  |   |  |   |  |   |  |   |  |   |____PA1 : Analog, "BATT" (ADC123_IN1)
    |  |   |  |   |  |   |  |   |  |   |  |   |  PA2 : Analog, "QT_DQT_IN" (ADC123_IN2))
    |  |   |  |   |  |   |  |   |  |   |  |   |__PA3 : Analog, "VOX"       (ADC123_IN3) 
    |  |   |  |   |  |   |  |   |  |   |  |______PA4 : Analog, "APC/TV"    (ADC123_IN4/DAC_OUT1) 
    |  |   |  |   |  |   |  |   |  |   |_________PA5 : Analog, "MOD2_BIAS" (ADC123_IN5/DAC_OUT2)
    |  |   |  |   |  |   |  |   |  PA6 : GPI, "K1"
    |  |   |  |   |  |   |  |   |__PA7 : GPO, "POW_C"
    |  |   |  |   |  |   |  |______PA8 : GPO, "SAVE"
    |  |   |  |   |  |   |_________PA9 : GPO, "VCOVCC_SW"
    |  |   |  |   |  |_____________PA10: GPO, "DMR_SW"
    |  |   |  |   |___________PA11 : AF #10, OTG_FS_DM, MD380: "USB_D-"
    |  |   |  |_______________PA12 : AF #10, OTG_FS_DP, MD380: "USB_D+"
    |  |   |____PA13 : GPO, "W/N_SW"
    |  |________PA14 : GPO, "MICPWR_SW"
    |___________PA15 : AF #6, I2S3_WS, MD380: "I2S_FS"

2.6.2 Port B


$ python tool2.py hexdump32 GPIOB 40
40020400: A905AA9F 000000C0 AA0AAAA0 A8500000 (MODER,OTYPER,OSPEEDR,PUPDR)
40020410: 000015F8 00001100 00000000 00000000 (IDR,  ODR,   BSRR,   LCKR)
40020420: 44555000 55500000                   (AFRL, AFRH)
          |   |    | |_PB13
          PB7 PB3  PB15
Decoded from the MODER- and AFR-registers for GPIO B:

GPIOB_MODER : hex A905AA9F, binary below, in TWO BIT - groups :
        __________|      |__________________________
   ____|                                            |____
   10 10  10 01  00 00  01 01  10 10  10 10  10 01  11 11 
   \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|_PB0 : Analog, "RSSI"
    |  |   |  |   |  |   |  |   |  |   |  |   |  |   |____PB1 : Analog, "BUSY"
    |  |   |  |   |  |   |  |   |  |   |  |   |  PB2 : GPO, "FM_SW"
    |  |   |  |   |  |   |  |   |  |   |  |   |__PB3 : AF #5, SPI1_SCK, "FLASH_SCLK"
    |  |   |  |   |  |   |  |   |  |   |  PB4 : AF #5, SPI1_MISO, "FLASH_SDO"
    |  |   |  |   |  |   |  |   |  |   |__PB5 : AF #5, SPI1_MOSI, "FLASH_SDI"
    |  |   |  |   |  |   |  |   |  PB6 : AF #4, I2C1_SCL, "SCL"
    |  |   |  |   |  |   |  |   |__PB7 : AF #4, I2C1_SDA, "SDA"
    |  |   |  |   |  |   |  |_PB8  : GPO, "SPK_C" (speaker control)
    |  |   |  |   |  |   |____PB9  : GPO, "AFCO"  (switches audio PA)
    |  |   |  |   |  |________PB10 : GPI, "ECN2" (EnCoder bit 2 ?)
    |  |   |  |   |___________PB11 : GPI, "ECN3" (EnCoder bit 3 ?)
    |  |   |  |_______________PB12 : GPO, MD380: "V_CS"  (to HR_C5000)
    |  |   |____PB13 : AF #5, SPI2_SCK/I2S2_CK,  "V_SCLK" (to HR_C5000)
    |  |________PB14 : AF #5, SPI2_MISO,         "V_SDO" (from HR_C5000)
    |___________PB15 : AF #5, SPI2_MOSI/I2S2_SD, "V_SDI" (to HR_C5000)

2.6.3 Port C


When reverse engineering the interrupt handler for timer 8, four of that timer's capture/compare channels (CC1/2/3/4) were enabled in TIM8_CCER. One of them was the 'beeper' output (PC8). The 'GPIOC' registers listed below were examined, to find out on which pins the other Capture/Compare channels were assigned.

$ python tool2.py hexdump32 GPIOC 40
40020800: 06A665C0 00000080 0AAA8A00 00000000 (MODER,OTYPER,OSPEEDR,PUPDR)
             |__ bits 17..16 for PC8 = "BEEP" : 10bin = Alternate function
40020810: 00000047 00000080 00000000 00000000 (IDR,  ODR,   BSRR,   LCKR)
40020820: 08000000 00065603                   (AFRL, AFRH)
                          |__ bits 3..0 for PC8: 0011bin = AF3 = TIM8_CH3 
                              (confirms that Tytera's "Beep" is PWM'd)
Decoded from the MODER- and AFR-registers:

GPIOC_MODER : hex 06A665C0, binary below, in TWO BIT - groups :
        __________|      |__________________________
   ____|                                            |____
   00 00  01 10  10 10  01 10  01 10  01 01  11 00  00 00 
   \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|_PC0 / "TIME_SLOT_INTER":GPI
    |  |   |  |   |  |   |  |   |  |   |  |   |  |   |____PC1 / "SYS_INTER" : GPI
    |  |   |  |   |  |   |  |   |  |   |  |   |  PC2 / "RF_TX_INTER" : GPI
    |  |   |  |   |  |   |  |   |  |   |  |   |__PC3 / "T/5T" : analog (ADC123_IN13)
    |  |   |  |   |  |   |  |   |  |   |  PC4 / "RF_APC_SW": GPO
    |  |   |  |   |  |   |  |   |  |   |__PC5 / "5TC" : GPO
    |  |   |  |   |  |   |  |   |  PC6 / "LAMP" : originally GPO, now AF #8,USART6_TX 
    |  |   |  |   |  |   |  |   |__PC7 / "CTC/DCS_OUT": GPO (!)
    |  |   |  |   |  |   |  |_PC8  / "BEEP"  : AF #3, TIM8_CH3
    |  |   |  |   |  |   |____PC9  / "5RC"   : GPO
    |  |   |  |   |  |________PC10 / "I2S_CK": AF #6, I2S3_CK
    |  |   |  |   |___________PC11 / "I2S_RX": AF #5, I2S3ext_SD
    |  |   |  |_______________PC12 / "I2S_TX": AF #6, I2S3_SD
    |  |   |____PC13 / "BSHIFT" : GPO, serial clock from STM32 to C5000 'user' SPI
    |  |________PC14 / "32.768K_OUT": GPI (not connected ?)
    |___________PC15 / "32.768K_OUT": GPI (not connected ?)

2.6.4 Port D


$ python tool2.py hexdump32 GPIOD 40
40020C00: A6455A0A 00000000 FF8ABFAF 00100050 (MODER,OTYPER,OSPEEDR,PUPDR)
40020C10: 00003FFC 00002BC0 00000000 00000000 (IDR,  ODR,   BSRR,   LCKR)
40020C20: 00CC00CC CC0C0000                   (AFRL, AFRH)
            |    |    |_ PD12 : AF #12
           PD5   |_ PD0 : AF #12
Decoded from the MODER- and AFR-registers for GPIO D:

GPIOD_MODER : hex A6455A0A, binary below, in TWO BIT - groups :
        __________|      |__________________________
   ____|                                            |____
   10 10  01 10  01 00  01 01  01 01  10 10  00 00  10 10 
   \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|_PD0 : AF #12, FSMC_D2, "LCD_D2" 
    |  |   |  |   |  |   |  |   |  |   |  |   |  |   |____PD1 : AF #12, FSMC_D3, "LCD_D3"
    |  |   |  |   |  |   |  |   |  |   |  |   |  PD2 : GPI, MD380: "K2" (some key?)
    |  |   |  |   |  |   |  |   |  |   |  |   |__PD3 : GPI, MD380: "K3" (other key?)
    |  |   |  |   |  |   |  |   |  |   |  PD4 : AF #12, FSMC_NOE, "LCD_RD"
    |  |   |  |   |  |   |  |   |  |   |__PD5 : AF #12, FSMC_NWE, "LCD_WR"
    |  |   |  |   |  |   |  |   |  PD6 : GPO, MD380: "LCD_CS"
    |  |   |  |   |  |   |  |   |__PD7 : GPO, MD380: "FLASH_CS0"
    |  |   |  |   |  |   |  |______PD8 : GPO, MD380: "FLASH_CS1"     
    |  |   |  |   |  |   |_________PD9 : GPO, MD380: "FLASH_CS2"
    |  |   |  |   |  |________PD10 : GPI (!), MD380: "PLL_LD" (lock-detect?)
    |  |   |  |   |___________PD11 : GPO,     MD380: "PLL_CS"
    |  |   |  |_______________PD12 : AF #12, FSMC_A17, "LCD_RS"
    |  |   |____PD13 : GPO,      MD380: "LCD_RST"
    |  |________PD14 : AF #12, FSMC_D0, "LCD_D0"
    |___________PD15 : AF #12, FSMC_D1, "LCD_D1"  (all plausible, ok.)

2.6.5 Port E


$ python tool2.py hexdump32 GPIOE 40
40021000: 042A9515 00000000 083FEA2A 51400000 (MODER,OTYPER,OSPEEDR,PUPDR)
40021010: 00005834 00000034 00000000 00000000 (IDR,  ODR,   BSRR,   LCKR)
40021020: C0000000 00000CCC                   (AFRL, AFRH)
          |             | |_ PE8 ..
          PE7:AF#12     |___ PE10
Decoded from the MODER- and AFR-registers for GPIO E:

GPIOE_MODER : hex 042A9515, binary below, in TWO BIT - groups :
        __________|      |__________________________
   ____|                                            |____
   00 00  01 00  00 10  10 10  10 01  01 01  00 01  01 01 
   \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|  \| \|_PE0 : GPO, "RX_LED"
    |  |   |  |   |  |   |  |   |  |   |  |   |  |   |____PE1 : GPO, "TX_LED"(!)
    |  |   |  |   |  |   |  |   |  |   |  |   |  PE2 : GPO, "DMR_CS"
    |  |   |  |   |  |   |  |   |  |   |  |   |__PE3 : GPI, "DMR_DATA_IN DMR_CLI+PLL_CLK"??
    |  |   |  |   |  |   |  |   |  |   |  PE4 : GPO, "DMR_SDO"
    |  |   |  |   |  |   |  |   |  |   |__PE5 : GPO, "PLL+DMR_SDI"
    |  |   |  |   |  |   |  |   |  PE6 : GPO, MD380: "DMR_SLEEP"
    |  |   |  |   |  |   |  |   |__PE7 : AF #12, FSMC_D4, "LCD_D4"
    |  |   |  |   |  |   |  |______PE8 : AF #12, FSMC_D5, "LCD_D5"
    |  |   |  |   |  |   |_________PE9 : AF #12, FSMC_D6, "LCD_D6"
    |  |   |  |   |  |_____________PE10: AF #12, FSMC_D7, "LCD_D7"
    |  |   |  |   |___________PE11 : GPI, MD380: "PTT_KEY"
    |  |   |  |_______________PE12 : GPI, MD380: "EXT_PTT"
    |  |   |____PE13 : GPO,               MD380: "FM_MUTE"
    |  |________PE14 : GPI,  MD380: "ECN0" (EnCoder bit 0 ?)
    |___________PE15 : GPI,  MD380: "ECN1" (EnCoder bit 1 ?)

Conclusion: Even though FOUR 'Capture/Compare' channels are enabled in TIMER8, only channel 3 is connected to a pin (PC8). All other compare-channels are used 'internally', if used at all.

2.7 Hardware timer usage


Timer1 (counter register at 0x40010024) : not seen 'alive' yet.
Not seen accessed in the disassembled part of the firmware yet.

Timer2 (counter register at 0x40000024) : not seen 'alive' yet.
Has an interrupt handler.

Timer3 (counter register at 0x40000424) : not seen 'alive' yet.
Has an interrupt handler which accesses 'idle_control_struct' (see disassembly).

Timer4 (counter register at 0x40000824) : starts counting on TX in DMR, ca 12 bit.
Has an interrupt handler, which accesses 'radio_status_1', etc.

Timer5 (counter register at 0x40000C24) : not seen 'alive' yet.
Also has its own interrupt handler. Posts some event to the RTOS kernel.

Timer6 (counter register at 0x40001024) : 0x0002 when idle, alive with a few nonzero bits when sending DMR.
Has an interrupt handler that writes to the D/A converter.

Timer7 (counter register at 0x40001424) : not seen 'alive' yet.
Has an interrupt handler. Also accesses 'radio_status_1'.

Timer8 (counter register at 0x40010424) : counts from 0x00 to 0xFF in the original FW.
Has an interrupt handler to generate Tytera's "beeps".
In the experimental firmware, used as audio PWM output for the Morse generator.

Timer9,10,11,12,13,14 :
No accesses seen in the original firmware (D13.020) yet.

2.8 A/D converter usage

The STM32F405 has three A/D converters. Details are in the Reference Manual (RM0090 Rev 13 pages 390 to 435). Each ADC can sample a configurable number of input channels. For the 'regular sequence', input channels are selected via registers ADC_SQR1 to ADC_SQR3 (RM0090 page 425). ADC_SQR1 bits 23..20 defines the 'regular channel sequence length', i.e. the number of conversions (and thus channels) in a cycle. Each channel selector occupies 5 bits in an 'SQR' register. ADC_SQR3(!) contains the channel selectors for the 1st to 6th conversion in a cycle. ADCs configured for multiple input channels are usually 'drained' via DMA, because the STM32F4's ADC doesn't have individual registers to store the result for each channel.
In the MD380 / RT3, the ADC's input channels seemed to be configured only once after power-on. Some registers were read out via USB. The results are shown in the next chapters.

2.8.1 ADC 1

ADC1 performs 6 conversions in the 'regular channel sequence', continuously scanning. Configuration registers, read out via USB:

  ADC1CR1  = 0x00000100 = 0000 0000 0000 0000 0000 0001 0000 0000 binary
      (RM0090 page 419)                               |
                                bit 8 : Scan mode enabled

  ADC1CR2  = 0x03030303 = 0000 0011 0000 0011 0000 0011 0000 0011 binary
                          |||| |||| |||| |||| rsvd |||| |||| ||||
                                                      |_ bit 8 : DMA enable


  ADC1SQR3 = 0x0201A029 = 0000 0010 0000 0001 1010 0000 0010 1001 binary
                            |_SQ6||_SQ5| |_SQ4||_SQ3||_SQ2||_SQ1|
                               |     |      |     |     |     |
          converted channels:  1     0      3     8     1     5
                               |     |      |     |     |     |
          CPU pin names :     PA1   PA0    PA3   PB0   PA1  ?PA5?
                               |     |      |     |     |     |
          suspect sig names: BATT  VOL_POT VOX   RSSI  BATT  MOD2_BIAS

The conversion results from ADC1 are read out via DMA2, stream 0, and stored in RAM at DMA2S0M0AR (=0x2001E51C in FW D13.020).
Watching the 6 * 16-bit locations at that address showed very noisy readings, frequently updated in any 'radio state'. The 5th of the 6 array elements (zero-based array index 4) seemed to be the indicator for the analog volume pot (position). Let's try to break this down:
The channel selector (bitgroup SQ5 shown above) is zero, i.e. the 5th array element is 'ADC123_IN0'. Only the STM32F495 datasheet ("DocID022152" Rev4 page 47), not the reference manual, contains the difficult-to-find info that 'ADC123_IN0' may be on PA0 for this particular chip. If the MD380 circuit was correct, it would show PA0 is connected to a low-pass filtered tap of the analog volume control pot (instead, the diagram shows PA0 driving the red TX LED, which is wrong).
See also: Analog inputs in the MD380 firmware description.

2.8.2 ADC 2

ADC2 only seems to convert a single channel (ADC2_SQR1 bits 23..20 = 0), channel #13 (ADC2_SQR3 bits 4..0).
This appears to be the 'FM receive audio' signal on PC3 / ADC123_IN13. Drained via DMA2, stream 3.

2.8.3 ADC 3

ADC3 also only converts a single channel in the MD380 (ADC3_SQR1 bits 23..20 = 0), channel #2 (ADC3_SQR3 bits 4..0).
The purpose of PA2 / ADC123_IN2 / 'QT_DQT_IN' was not pursued at the time of this writing.
With a strong FM signal (RX), the conversion result (ADC3_DAT) was around 0x007F, with 'more noise' in the LSBits without a signal.