NXP Microcontroller Utilities library for K64F.
NOTE: If you ever want to chat about robotics, AI, and 3D, please reach out!
Download ConfigToolsData file for your board.
unzip -d downloads/ConfigToolsData_FRDM-K64F_v25_12 downloads/ConfigToolsData_FRDM-K64F_v25_12.zipNext, generate .dts file:
dtsbuilder --build-dts \
--input-config-tools-data-file downloads/ConfigToolsData_FRDM-K64F_v25_12.zip \
--user-board-config-file config/board_config.yaml \
--output-dts-path tmp/frdm_k64f-pinctrl.dtsidtsbuilder --query-dts \
--input-config-tools-data-file downloads/ConfigToolsData_FRDM-K64F_v25_12.zip \
--find-base-pin PTC15When the builder runs, it retrieves files for the processor:
downloads/ConfigToolsData_FRDM-K64F_v25_09/processors/MK64FN1M0xxx12/ksdk2_0/MK64FN1M0VLL12
├── DMA.js
├── module_clocks.xml
├── part_info.xml
├── peripherals_model_info.xml
├── registers
│ ├── DMAMUX.xml
│ ├── PORTA.xml
│ ├── PORTB.xml
│ ├── PORTC.xml
│ ├── PORTD.xml
│ └── registers.xml
├── resource_tables
│ ├── dmaMuxRequests.xml
│ └── interrupts.xml
└── signal_configuration.xml
The essence of dtsbuilder script is to parse a Hardware Pin-to-Signal Mapping Table, which
describes exactly how a single physical piece of silicon (in this case, pin C10) can be "morphed"
into different functional roles. Then, it constructs .dtsi file.
<pin
name="ADC1_SE4a/PTE0/SPI1_PCS1/UART1_TX/SDHC0_D1/TRACE_CLKOUT/I2C1_SDA/RTC_CLKOUT"
description="ADC1 analog channel 4;General purpose IO, Port E, bit 0;SPI1 Peripheral Chip Select 1;Transmit data;D1 - Data 1;Trace clock output;I2C1 Serial Data Line;Oscillator output"
coords="C10">In the NXP world, this is the "Truth Map" that tells us which MUX value corresponds to which peripheral function.
Key Components of the Pin Map:
- Pin Identification: Physical Pin C10, known as
PTE0(Port E, Bit 0). - The Multiplexer (ALT modes): The
<connections>tags define the available "Personalities" for this pin:- ALT 0: ADC1 (Analog input)
- ALT 1: GPIO (General Purpose I/O)
- ALT 3: UART1 TX (Serial Transmit)
- ALT 6: I2C1 SDA (Data line)
- The Configuration Logic: Inside each connection, the
<assign>tag tells us the register value needed. For example, to make this pin a UART1 TX, we must setPORTE_PCR0[MUX]to0x3. - Electrical Defaults: The
<functional_properties>section defines the hardware-supported "tweaks" likepull_enable,slew_rate, anddrive_strength.
In a Linux or Zephyr Device Tree, a pinctrl node needs to know two things: Which pin?
and What MUX value? This XML provides both.
If we were writing a pinctrl node for UART1, we would look at the UART1_TX section of the XML:
/* Generated logic from the XML */
uart1_default: uart1_default {
group1 {
psels = <NX_PSEL(UART1_TX, E, 0)>; /* Port E, Pin 0 */
drive-strength = "low"; /* From <functional_property id="drive_strength"> */
bias-pull-down; /* From <functional_property id="pull_select"> */
};
};
To generate a pinctrl file, dtsbuilder script processes this XML as follows:
- Extract the Pin Mux Value: Look for the
bit_field_valuewherebit_field="MUX". For UART1_TX on PTE0, this value is0x3. - Map Functions to Peripherals: Create a lookup table where
Peripheral=UART1andSignal=TXpoints toPin=PTE0, Mux=3. - Handle Electrical Flags:
- If
<functional_property id="pull_enable">is "enable" and<pull_select>is "up", addbias-pull-upto the DTS. - If
<slew_rate>is "fast", addslew-rate = <0>.
- If
- Detect Conflicts: Use the
<disallow>tags in the XML to warn the user if they try to use the same internal signal (likeSPI1_PCS1) on two different physical pins simultaneously.
First, the parser reads pins.
['ADC1_SE4a', 'PTE0', 'SPI1_PCS1', 'UART1_TX', 'SDHC0_D1', 'TRACE_CLKOUT', 'I2C1_SDA', 'RTC_CLKOUT']
['ADC1_SE5a', 'PTE1', 'LLWU_P0', 'SPI1_SOUT', 'UART1_RX', 'SDHC0_D0', 'TRACE_D3', 'I2C1_SCL', 'SPI1_SIN']
['ADC0_DP2', 'ADC1_SE6a', 'PTE2', 'LLWU_P1', 'SPI1_SCK', 'UART1_CTS_b', 'SDHC0_DCLK', 'TRACE_D2']
['ADC0_DM2', 'ADC1_SE7a', 'PTE3', 'SPI1_SIN', 'UART1_RTS_b', 'SDHC0_CMD', 'TRACE_D1', 'SPI1_SOUT']These lists represent the mux groups for every pin on the MCU. Each list is essentially a single physical "pad" on the chip, and the strings inside are the different functional "labels" that pad can assume.
From a parser's perspective, this is the Pin Master List.
Not all of these will go into a pinctrl file in the same way. We need to categorize them:
- Multiplexed Pins (The majority): e.g.,
['PTA1', 'UART0_RX', ...]- These are the primary targets for
pinctrl. - One label is always the "GPIO" name (starts with
PT).
- These are the primary targets for
- Dedicated Power/Ground: e.g.,
['VDD63'],['VSS64']- Ignore these for
pinctrl. They have no MUX configuration.
- Ignore these for
- Analog/Dedicated Inputs: e.g.,
['USB0_DP'],['RESET_b']- Usually "Fixed Function." We rarely need a
pinctrlentry for these unless the SoC requires an "Analog Mode" mux setting.
- Usually "Fixed Function." We rarely need a
To handle these lists effectively, the parser should follow these rules:
Rule A: Find the "Key" (The GPIO Name)
In the Kinetis/NXP ecosystem, the PTxN label is the hardware "anchor."
- Input:
['PTA2', 'UART0_TX', 'FTM0_CH7', 'JTAG_TDO', 'TRACE_SWO', 'EZP_DO'] - Logic:
base = "PTA2". From this, we know it belongs to Port A at Index 2. - Register Calculation: This tells us to modify the
PORTA_PCR2register.
Rule B: Map Function to ALT Mode.
The index of the label in the list often corresponds directly to the ALT number in the hardware.
ALT0:PTA2(Standard for some, though usually ALT1 is GPIO)ALT1:UART0_TXALT2:FTM0_CH7- ...and so on.
Note: The XML's <connections package_function="alt3"> is the ultimate source of truth for which index matches which ALT mode.
Rule C: Handling "Special" Pins
We have several entries that look like this:
['ADC0_DP0', 'ADC1_DP3']
['VREF_OUT', 'CMP1_IN5', 'CMP0_IN5', 'ADC1_SE18']
These are Analog-only pins. They don't have a PTx label, meaning they cannot be used as General Purpose I/O.
We don't usually create pinctrl nodes for these because they don't have a Mux register to configure. The
hardware connects them to the ADC/Comparator by default.
For example, get only pins that actually need a MUX configuration for UART1
routable_uart_tx = [p for p in mapping["UART1"]["TX"] if p["is_routable"]]It routable_uart_tx follows:
[{'alt_mode': 'alt3',
'base_pin': 'PTE0',
'coords': '1',
'description': 'Transmit data',
'func_label': 'UART1_TX',
'is_routable': True,
'mux_value': '0x3'},
{'alt_mode': 'alt3',
'base_pin': 'PTC4',
'coords': '76',
'description': 'Transmit data',
'func_label': 'UART1_TX',
'is_routable': True,
'mux_value': '0x3'}]This output means that for the UART1_TX signal, the MCU has two physical pin options. We can choose to route the UART transmitter to either PTE0 (Pin 1) or PTC4 (Pin 76).
This is a classic "Pin Multiplexing" scenario. Since a single peripheral signal can only be used in one place at a time, we have to decide which physical pin works best for our PCB layout.
| Field | Meaning |
|---|---|
base_pin |
The GPIO name. This tells us which Port (E or C) and Pin (0 or 4) to look for. |
coords |
The physical pin number (package lead/ball). Pin 1 vs Pin 76. |
mux_value |
The value we must write into the Pin Control Register (PCR) to "activate" this function. Both use 0x3 (ALT3). |
is_routable |
True confirms these are standard pins with MUX registers (not fixed-function power/analog pins). |
In a Device Tree, we generally define "Pin Groups." Because there are two options, we typically look at the
hardware schematic or custom .mex configuration to see which one is actually wired up.
If we chose PTE0, the DTS entry would look like this (standard NXP/Zephyr format):
uart1_tx_default: uart1_tx_default {
/* Muxing UART1_TX to Port E Pin 0 with Alt Mode 3 */
nxp,psels = <NX_PSEL(UART1_TX, E, 0, 0x3)>;
};If we chose PTC4, then
uart1_tx_default: uart1_tx_default {
/* Muxing UART1_TX to Port C Pin 4 with Alt Mode 3 */
nxp,psels = <NX_PSEL(UART1_TX, C, 4, 0x3)>;
};To build a production-ready pinctrl (Pin Control) file for a Device Tree (DTS/DTSI), parsing
the signal_configuration.xml is mandatory. To finish the job, we need to gather three
additional "ingredients" to ensure the generated code is hardware-accurate and matches the user's
specific board design. The signal_configuration.xml tells us what is possible (e.g., UART1_TX
can be on PTE0 or PTC4). We need the user's configuration file to know what is chosen.
Download MCUXpresso Config Tools, mac OS AARCH package.
However, the tool does not work for FRDM-K64F.
We can provide selection in pins.xml.
- What to look for: Look for the
<pins>or<hardware>tags in the user's project file. - Why: It contains the "Selection" (e.g., "For this project, use PTC4 for UART1_TX").
- Result: This filters our list of options down to a single physical mapping.
A pinctrl node isn't just about the Mux; it's about the electrical characteristics of the
pad. Without this, our UART might not have enough "drive" to send data, or our I2C might
fail because the pull-up resistors aren't enabled.
We need to map the parsed functional_properties to DTS properties:
pull_select="up"bias-pull-up;drive_strength="high"drive-strength = "high";slew_rate="slow"slew-rate = <1>;passive_filter="enable"nxp,passive-filter;
Next, register base addresses and offsets. The Device Tree needs to know where the PORT and GPIO modules live in the memory map.
- The Problem: The XML says "Port E, Pin 0". The hardware needs to know that Port E starts at
0x4004D000. - The Source: This is usually found in a
memory_map.xmlorprocessor.xmlwithin the NXP data pack. - The Math: (e.g., PORTE_PCR0 is at Base + 0, PCR1 is at Base + 4, etc.)
Most NXP Device Trees (especially in Zephyr) use a macro to compress the
data (e.g. PSEL logic). We need to verify which macro format our specific SoC uses.
Common Format are:
nxp,psels = <NX_PSEL(SIGNAL, PORT, PIN, MUX)>;
To generate this, you need a helper function to convert:
"PTE0"E, 0"PTC4"C, 4
The FRDM-K64F is a legendary development board, but NXP's modern Config Tools (v15/v16) have started deprecating support for older Kinetis "K" series parts in favor of MCUXpresso and newer i.MX/LPC silicon.
We need to recreate the Pin Routing Engine. To get the pinctrl file for a K64F without the
official GUI, we need to synthesize three things.
The K64F has five ports: PORTA through PORTE. In our pinctrl file, we need the
base addresses to calculate where the MUX configuration actually goes.
| Port | Base Address | Register Type |
|---|---|---|
PORTA |
0x40049000 |
PCR (Pin Control Register) |
PORTB |
0x4004A000 |
PCR |
PORTC |
0x4004B000 |
PCR |
PORTD |
0x4004C000 |
PCR |
PORTE |
0x4004D000 |
PCR |
The Formula:
Since you've already parsed the pin index (like 0 from PTE0), the memory location for the mux is:
Example: PTE0 MUX is at 0x4004D000. PTE1 is at 0x4004D004.
Since the Config Tools won't give us a pins.xml for the K64F, we'll have to create
a simplified version based on the FRDM-K64F Schematic.
For example, on the FRDM-K64F, the Virtual COM Port (UART0) is wired to PTB16 and PTB17. We can manually create a selection file for our tool.
The board_config.yaml follows:
{
"board_config": {
"UART0_RX": "PTB16",
"UART0_TX": "PTB17",
"GPIOB_22": "PTB22",
"GPIOB_21": "PTB21"
}
}The GPIOB_22 should be split into:
- Peripheral:
GPIOB - Signal:
GPIO(then find the entry wherebase_pin == PTB22)
GPIOB:
GPIO:
- base_pin: PTB22
is_routable: true
mux_value: '0x1'
alt_mode: alt1
coords: '68'
func_label: PTB22
description: General purpose IO, Port B, bit 22The UART0_RX should be split into:
- Peripheral:
UART0 - Signal:
RX(then find the entry wherebase_pin == PTB16)
UART0:
RX:
- base_pin: PTB16
is_routable: true
mux_value: '0x3'
alt_mode: alt3
coords: '62'
func_label: UART0_RX
description: Receive dataThe below is invalid because RED_LED and BLUE_LED are aliases that the signal configuration does not know about.
{
"board_config": {
"UART0_RX": "PTB16",
"UART0_TX": "PTB17",
"RED_LED": "PTB22",
"BLUE_LED": "PTB21"
}
}We can now use the parse_signal_to_pin_map results to find the MUX values for these K64F pins.
The processing pipeline follows:
- Input:
k64f_board_config(manual list via JSON). - Lookup: Searches
mapping["UART0"]["RX"]. - Match: It finds the entry where
base_pin == "PTB16". - Extract: It pulls
mux_value: "0x3"andalt_mode: "alt3". - Output: It writes the DTS line.
If the tool runs correctly, it should output something like this for the K64F's Debug UART:
/* Generated for FRDM-K64F UART0 */
&pinctrl {
uart0_default: uart0_default {
group1 {
pinmux = <K64F_PORTB_PIN16_MUX_ALT3>, /* RX */
<K64F_PORTB_PIN17_MUX_ALT3>; /* TX */
drive-strength = "low";
bias-pull-up;
};
};
};