Skip to content

Add support for the MSP430FR5969 platform#1581

Open
nvt wants to merge 12 commits into
contiki-ng:masterfrom
nvt:msp430fr5969-support
Open

Add support for the MSP430FR5969 platform#1581
nvt wants to merge 12 commits into
contiki-ng:masterfrom
nvt:msp430fr5969-support

Conversation

@nvt
Copy link
Copy Markdown
Member

@nvt nvt commented May 7, 2026

This PR introduces a new MSP430 platform: the MSP430FR5969 LaunchPad. This platform lacks a radio, but comes with 64 kB of FRAM, which makes it suitable for some intermittent computing scenarios.

There is also a corresponding port of the platform to Contiki-NG in contiki-ng/contiki-ng#3143.

nvt added 4 commits May 9, 2026 14:31
The eUSCI on FR5xxx parts uses a different register layout than the
older USCI: word-mode CTLW0/CTLW1/BRW/MCTLW and per-module IE/IFG/IV.
This adds a UART-mode implementation with SWRST handling on word
writes, UCOS16 oversampling baud-rate calculation, and a combined
per-module interrupt vector that auto-clears IV on read. IE/IFG
writes re-evaluate the CPU interrupt line so newly enabled flags
fire and cleared flags actually deflag.
The FR5969 uses the Clock System (CS) module, distinct from the FLL-based
UCS on F5xxx parts. CS selects the DCO directly via DCOFSEL/DCORSEL
lookup tables (per SLAS704G Table 5-15) with no FLL locking. The module
implements SELM/SELS/SELA source selection and DIVM/DIVS/DIVA dividers
with separate MCLK and SMCLK so DIVM != DIVS works, plus CSCTL0 password
protection with lenient re-lock on non-A5 writes (matching driverlib's
CSCTL0_H = 0 idiom; no PUC, per real silicon). POR reset values follow
SLAU367P §3.3: 8 MHz DCO with /8 dividers giving 1 MHz MCLK after reset.
First FRAM-based MSP430 in MSPSim. No radio is wired up; targets
non-networked applications and intermittent-computing research.

- FRAM emulation: FRAMController + FRAMSegment, hasFRAM branch in
  MSP430Core (Flash + FlashSegment vs FRAMController + FRAMSegment),
  and IOPort SEL0/SEL1/SELC for the FR5xxx port-mapping syntax.
- MSP430FR5969Config: 64 KB main FRAM at 0x4400, 768 B info FRAM,
  2 KB SRAM, eUSCI_A0/A1 + eUSCI_B0, CRC16, MPY32, 5 IO ports
  (P1/P2 with edge interrupts), 64-vector IVT, 20-bit MSP430X.
- FR5969Node: eUSCI_A0 backchannel, Button on P4.5 (S1, active-low),
  port listeners.
- Cooja: Msp430FR5969Mote/MoteType/LED, MspDefaultSerial falls back
  to "USCI A0" when the legacy "USART 1" lookup fails.
- Drop the redundant DEBUG-gated TCTL write log from Timer.java; the
  same call is already traced via the surrounding peripheral logging.
- Split the MoteInterfaceHandler constructor call onto its own line
  so a thrown exception points at the constructor, not the surrounding
  add().
@nvt nvt force-pushed the msp430fr5969-support branch from ca29b4d to 20641d6 Compare May 9, 2026 12:34
nvt added 8 commits May 20, 2026 18:51
The Watchdog class hardcoded both fields of the F1xxx/F2xxx/F4xxx WDTCTL:
WDTISx as a 2-bit field at bits 0..1 with delays {32768, 8192, 512, 64},
and WDTSSEL as a single-bit field at bit 2 selecting SMCLK or ACLK.

F5xxx/FR5xxx WDT_A widens both: WDTISx is 3 bits (0..7) with intervals
from 2^31 down to 2^6 cycles, and WDTSSEL is 2 bits at bits 5..6 selecting
SMCLK / ACLK / VLOCLK / X_CLK.

With the old code, WDTIS__8192K (=0x02, meaning 2^23 ~= 1 s at 8 MHz on
FR5969) was interpreted as 512 cycles, so the watchdog reset the node
every ~64 us after watchdog_start() and prevented any etimer- or
CTimer-driven Contiki-NG code from running on MSP430FR5969. Likewise,
firmware that selected ACLK with WDTSSEL__ACLK (0x20) was silently read
as SMCLK because 0x20 & 0x04 == 0, and VLOCLK could not be modelled at
all.

Move the delay table, WDTISx mask, and WDTSSEL decoding onto
MSP430Config so each chip config supplies the right layout. Override
all three in MSP430FR5969Config with the FR5xxx values. The legacy
single-arg Watchdog constructor is kept for callers that don't go
through MSP430Config. Replace the boolean sourceACLK with a
SMCLK/ACLK/VLOCLK enum so scheduleTimer() can pick the right time
base; VLOCLK is treated as ~10 kHz (the documented nominal for the
FR5xxx CS module). Unknown WDTSSEL values (e.g. X_CLK) fall back to
the SMCLK cycle-counter path so behaviour stays defined.
Java class names must begin with an upper-case letter, and the project's
established style for MSP430 classes is "MSP430..." rather than
"Msp430...". Rename:

  eUSCI_A           -> EUsciA       (PascalCase; keeps the eUSCI and A
                                     distinction from TI's nomenclature)
  Msp430FR5969Mote      -> MSP430FR5969Mote
  Msp430FR5969MoteType  -> MSP430FR5969MoteType
  Msp430FR5969LED       -> MSP430FR5969LED

Comments and log strings that name TI's hardware peripheral (e.g.
"eUSCI_A0") are left unchanged since they reference the datasheet
identifier, not a Java symbol.
Two related fixes that let active-low buttons (and any other chip that
drives a port pin) work correctly across a CPU reset:

* Button.<init> now sets the IOPort pin to the released level.
  Previously the chip relied on the IOPort's default LOW initial state,
  which matched a polarity=true (active-high) button but left a
  polarity=false (active-low, e.g. MSP-EXP430FR5969 S1 on P4.5)
  indistinguishable from a pressed button. The first press read
  pinState=LOW and produced no LO->LO transition, so no falling-edge
  interrupt was raised.

* IOPort.reset() no longer wipes pinState[] / IN. On real silicon a POR
  resets internal registers (DIR, IE, IES, IFG, ...) but does not reset
  externally driven pin levels - if a button is held down, the pin is
  still low after reset. The previous implementation cleared pinState
  to LOW, which combined with the Button issue above meant the released
  pin state set in the Button constructor was lost as soon as the CPU
  was reset before the firmware ran. Reset now leaves pinState
  untouched (initialising only null entries to LOW on first reset) and
  rebuilds IN from it.
P3 and P4 on the FR5969 have port-interrupt logic just like P1/P2 - the
PBIES/PBIE/PBIFG registers are at 0x238/0x23A/0x23C and PnIV at 0x22E/0x23E.
The previous config declared P3/P4 with only IN/OUT/DIR/REN/SEL*/SELC, so
firmware writes from gpio_hal_arch_pin_cfg_set() and friends (called by
button_hal_init for S1 on P4.5) hit voidIO and produced
VOID_IO_WRITE/VOID_IO_READ warnings. The ports were also created with
interrupt=0, so even if the registers existed IOPort.setPinState() would
not have raised an interrupt on a pin transition.

Add IES/IE/IFG/IV_L/IV_H to the P3 and P4 port specs and use the actual
port-interrupt vectors (P3=41, P4=40, both PORTx_VECTOR / 2) when
constructing the IOPort instances.
Per SLAU367P §3.3.7 (table 3-10), CSCTL6 holds the *REQEN bits
(ACLKREQEN / MCLKREQEN / SMCLKREQEN / MODCLKREQEN) and its reset value
is 0007h - the first three REQEN bits are enabled out of POR. The
emulator was resetting CSCTL6 to 0000h instead, which would cause any
firmware that reads CSCTL6 to observe an incorrect "no clock requests
asserted" state. Also fix two comments that called CSCTL6 the "Fault
flags" register and grouped LFXTOFFG / HFXTOFFG (which actually live in
CSCTL5) underneath a "// CSCTL6: Fault flags" header.

Separately, the SEL_HFXTCLK case in getSourceFrequency() returned
LFXTCLK_FREQ (32 kHz) as a "default to LFXT if HFXT not configured"
fallback. The MSP-EXP430FR5969 LaunchPad has no HFXT crystal and the
emulator does not model the oscillator, so any firmware that routes
MCLK/SMCLK/ACLK to HFXTCLK is asking for a clock the simulator cannot
deliver - and silently giving them 32 kHz hides the misconfiguration.
Return 0 Hz instead and log a one-shot warning. Downstream timing
(Timer divides, UART baud rates) will break loudly, surfacing the
issue at first use instead of pretending the firmware ran correctly.
The FR5969 uses FRCTL_A, but FRAMController was modelled mostly on the
older FRCTL (ch.7). Bring the controller in line with FRCTL_A:

* Honour FRCTL0.WPROT. Add FRAMController.attemptWrite(), called from
  FRAMSegment.write(), so writes are blocked when WPROT=1 and the
  Write Protection Detection flag WPIFG (GCCTL1 bit 4) is raised on
  the violation, per §8.2.3.1. Before this change FRAMSegment.write()
  always landed; firmware (or a runaway write) could overwrite FRAM
  with WPROT engaged, contrary to the spec.

* FRCTL0: widen NWAITS from the FRCTL 3-bit field (mask 0x70) to the
  FRCTL_A 4-bit field (mask 0xF0). Declare AUTO (bit 3) for
  completeness; the emulator still does not act on it.

* GCCTL0: bit 4 is WPIE and bit 3 is ACCTEIE in FRCTL_A. Add both;
  drop the FRCTL-only FRLPMPWR constant (bit 1 is reserved in FRCTL_A).

* GCCTL1: bit positions in FRCTL_A are WPIFG (4), ACCTEIFG (3),
  UBDIFG (2), CBDIFG (1). The old code put UBDIFG at bit 0 (FRCTL
  layout) - latent bug because nothing in the emulator sets UBDIFG
  today. Move UBDIFG to bit 2 and add ACCTEIFG.

* Per §8.6.3 all four IFG bits clear on write-0 (write-1 has no
  effect). The old code did W1C for CBDIFG/UBDIFG; switch to W0C
  across the board. Verified with a probe: writing 1 to WPIFG is a
  no-op, writing 0 clears it.

The Memory.set()/get() debug path deliberately bypasses WPROT so the
ELF loader and Cooja debugger can still poke FRAM after WPROT has been
engaged.
The port-config strings declared PxSELC at offset 0x16/0x17 from each
port-group base, which is the layout in SLAU367P §12's generic Px
register table but not the one the FR5969 device actually implements.
Per the msp430fr5969 device header, PASELC sits at 0x210, PBSELC at
0x230, and PJSELC at 0x330 - i.e. offset 0x10/0x11 from each group's
base.

Silent today because IOPort.writePort() has no case for SELC, so writes
landed on a stored-but-unused slot regardless of the address. Fixing
the offset keeps the config aligned with the device map and removes a
trap for any future SELC handling in IOPort. Also add a SELC entry for
PJ (PJSELC=0x330) which was missing entirely.
The field is the absolute address of WDTCTL, not the WDT_A module base
- the two only coincide on F1xxx/F2xxx parts where the module is just
the WDTCTL register. On F5xxx/FR5xxx WDTCTL sits at module-base + 0xC,
so configs like MSP430FR5969Config rightly set watchdogOffset=0x15C
even though BASEADDRESS_WDT_A__ is 0x150 in the device header.

Add a doc comment so the next reader does not file the same "naming
inconsistency" report.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant