Cartographer V3: Reverse engineering

No motivation letter here.
Well, I got one Cartographer, and I want to run it on the mainline.
So, bootable config. There is either PA9/PA10, or PA11/PA12

Katapult USB/CAN Example
    Micro-controller Architecture (STMicroelectronics STM32)  --->
    Processor model (STM32F042)  --->
    Build Katapult deployment application (Do not build)  --->
    Clock Reference (24 MHz crystal)  --->
    Communication interface (USB (on PA9/PA10))  --->
    Application start offset (8KiB offset)  --->
    USB ids  --->
()  GPIO pins to set on bootloader entry
[*] Support bootloader entry on rapid double click of reset button
[ ] Enable bootloader entry on button (or gpio) state
[*] Enable Status LED
(PB5)   Status LED GPIO Pin
    Micro-controller Architecture (STMicroelectronics STM32)  --->
    Processor model (STM32F042)  --->
    Build Katapult deployment application (Do not build)  --->
    Clock Reference (24 MHz crystal)  --->
    Communication interface (CAN bus (on PA9/PA10))  --->
    Application start offset (8KiB offset)  --->
(1000000) CAN bus speed
(PA1) GPIO pins to set on bootloader entry
[*] Support bootloader entry on rapid double click of reset button
[ ] Enable bootloader entry on button (or gpio) state
[*] Enable Status LED
(PB5)   Status LED GPIO Pin
Klipper USB/CAN make menuconfig
[*] Enable extra low-level configuration options
    Micro-controller Architecture (STMicroelectronics STM32)  --->
    Processor model (STM32F042)  --->
    Bootloader offset (No bootloader)  ---> # Katapult (8KiB bootloader)
    Clock Reference (24 MHz crystal)  --->
    Communication interface (USB (on PA9/PA10))  --->
    USB ids  --->
    Optional features (to reduce code size)  --->
[ ] Optimize stepper code for 'step on both edges'
()  GPIO pins to set at micro-controller startup
....
[*] Support micro-controller based ADC (analog to digital)
[*] Support communicating with external chips via SPI bus
[*]     Support software based SPI "bit-banging"
[*] Support communicating with external chips via I2C bus
[*]     Support software based I2C "bit-banging"
[*] Support hardware PWM (pulse width modulation) # Not yet implemented in mainline
[*] Support GPIO based button reading
[ ] Support Trinamic stepper motor driver UART communication
[ ] Support 'neopixel' type LED control
[*] Support measuring fan tachometer GPIO pins
    *** LCD chips ***
[ ] Support ST7920 LCD display
[ ] Support HD44780 LCD display
    *** Accelerometer chips ***
[*] Support adxl accelerometers
[ ] Support lis2dw and lis3dh 3-axis accelerometers
[ ] Support MPU accelerometers
[ ] Support ICM20948 accelerometer
    *** External ADC type chips ***
[ ] Support thermocouple MAX sensors
[ ] Support HX711 and HX717 ADC chips
[ ] Support ADS 1220 ADC chip
    *** Other external sensor chips ***
[*] Support ldc1612 eddy current sensor
[ ] Support angle sensors
[*] Enable extra low-level configuration options
    Micro-controller Architecture (STMicroelectronics STM32)  --->
    Processor model (STM32F042)  --->
    Bootloader offset (8KiB bootloader)  --->
    Clock Reference (24 MHz crystal)  --->
    Communication interface (CAN bus (on PA9/PA10))  --->
    Optional features (to reduce code size)  --->
(1000000) CAN bus speed
[*] Optimize stepper code for 'step on both edges'
(PA1) GPIO pins to set at micro-controller startup
....
Features section same as above
Config example
[mcu carto]
#serial: /dev/serial/by-id/usb-Klipper_stm32f042x6_29000380114330394D363620-if00
#canbus_uuid: 92cf532ef122

[adxl345 carto]
cs_pin: carto:PA3
spi_bus:spi1_PA6_PA7_PA5
# spi_software_miso_pin: PA6
# spi_software_mosi_pin: PA7
# spi_software_sclk_pin: PA5

[thermistor 50k]
temperature1: 25
resistance1: 50000
temperature2: 50
resistance2: 17940
temperature3: 100
resistance3: 3090

[temperature_probe carto]
pullup_resistor: 10000
sensor_type: 50k
sensor_pin: carto:PA4
min_temp: 0
max_temp: 125

[led carto_led]
white_pin: carto:PB5
cycle_time: 0.01
initial_WHITE: 0.03

[output_pin _LDC1612_en]
pin: carto:PA15
value: 0 # enable

[static_pwm_clock ldc1612_clk_in]
pin: carto:PB4
frequency: 24000000

[probe_eddy_current carto]
sensor_type: ldc1612
frequency: 24000000
x_offset: 0
y_offset: -10
z_offset: 3
i2c_address: 42
i2c_mcu: carto
i2c_bus: i2c1_PB6_PB7
intb_pin: carto:PB3
reg_drive_current = 19
high_current_drive = True # Not yet upstream
speed: 5
lift_speed: 10
samples: 3
sample_retract_dist: 1
samples_result: median
samples_tolerance: 0.01
samples_tolerance_retries: 5

So, I do have a more or less complete pin map.
There are still some mysterious pins for me:

  • PA0 - ?
  • PA1 - HI - enables can transivier
  • PA2 - ?
  • PB0 - ?
  • PB1 - ?

Should work on mainline now 31.12.2025.
USB/CAN works for me.

Thanks.


Schematics that I can draw with the naked eye:
cartographer-v3.pdf (42.7 KB)


PR: Cartographer (STM32F042 HW PWM as CLK OUT) by nefelim4ag · Pull Request #7091 · Klipper3d/klipper · GitHub


Sketchy testing setup and bed mesh.


Well, it works.


1 Like

Hi! Only PA1 is used. When PA1 triggers analog switches, the microcontroller pins connect to the CAN transceiver.

1 Like

Maybe you already know but the firmware is open sourced here i think: GitHub - Cartographer3D/MCU-Firmware---Based-on-Klipper

2 Likes

This graph is produced over PROBE_EDDY_CURRENT_CALIBRATE data


To get a rough idea of the sensor’s resolution distribution.
There are 2.5mm and 3.5mm sensor offsets from the bed.

Less crude graph script
# File name carto_2.5mm.data
0.050000:3246332.946,0.090000:3240873.962,0.130000:3235619.030,
0.170000:3230460.204,0.210000:3225456.210,0.250000:3220565.179,
0.290000:3215833.924,0.330000:3211168.165,0.370000:3206666.099,
0.410000:3202239.178,0.450000:3197979.178,0.490000:3193769.752,
#!/usr/bin/python3

import os
import glob
import matplotlib.pyplot as plt

data_files = sorted(glob.glob("*mm.data"))

plt.figure(figsize=(10, 6))

for filename in data_files:
    # Extract postfix from filename (e.g. 30c from "carto_30c.data")
    name = os.path.basename(filename).split("_", 1)[1].replace(".data", "")

    z_values = []
    freq_values = []
    with open(filename, 'r') as f:
        content = f.read().strip().replace("\n", "")
        pairs = [p for p in content.split(",") if p]
        for pair in pairs:
            z, freq = pair.split(":")
            z_values.append(float(z))
            freq_values.append(float(freq))

    plt.plot(z_values, freq_values, label=f"{name}", marker='.', linestyle='-', linewidth=1)

plt.title("Sensor Frequency vs. Z Height")
plt.xlabel("Z Height (mm)")
plt.ylabel("Frequency (Hz)")
plt.legend(title="File name")
plt.xticks([i / 10 for i in range(0, 450, 10)])
plt.grid(True)
plt.tight_layout()
plt.show()
"""

pairs = re.findall(r'([\d\.]+):([\d\.]+)', data)
Z = [float(z) for z, _ in pairs]
freq = [float(f) for _, f in pairs]

plt.figure(figsize=(10, 6))
plt.plot(Z, freq, marker='.', linestyle='-', linewidth=1)
plt.title("Z vs Frequency")
plt.xlabel("Z mm")
plt.ylabel("Frequency")
plt.xticks(np.arange(min(Z), max(Z) + 1, 2.0))
plt.grid(True)
plt.tight_layout()
plt.show()
3 Likes

I hypothesized that the temperature drift is not distributed equally over the frequency vs Z height range.

Graphs






Well, that explains why the eddy probe feels unusable with temperature drift with adequate z values, like 2-3 mm (in my opinion, that was adequate).
So, it seems that from a homing/probing perspective, it may make sense to use the lowest Z offset possible and probably calibrate it around some temperature mid-point.

It looks like higher temperature → lower frequency.
And that happens, the MCU frequency is also lower:

Hmmm.

But MCU is the source of the frequency for the LDC1612, so the thermal drift can be amplified by the oscillator drift, and so the MCU drift.

Let’s assume my measurements are more or less accurate (they are not precise).
(Probably it would be better to simply heat the carto with a hairdryer, instead of gradually heating the hotend, bed, and rerun of probe calibration).
And calculate the frequency difference for several points:

carto_30c.data:0.210000:3138434.518
carto_40c.data:0.210000:3137909.099
carto_50c.data:0.210000:3137099.625
carto_60c.data:0.210000:3136939.962
carto_70c.data:0.210000:3136616.542
(3136616.542 - 3138434.518) / 40 / 3.13 MHz * 10e6 = -14.52 ppm/K

carto_30c.data:1.010000:3094070.230
carto_40c.data:1.010000:3093155.628
carto_50c.data:1.010000:3092323.150
carto_60c.data:1.010000:3091842.108
carto_70c.data:1.010000:3091162.108
(3091162.108 - 3094070.230) / 40 / 3.09 MHz * 10e6 = -23.52 ppm/K

carto_30c.data:2.010000:3059523.843
carto_40c.data:2.010000:3058685.717
carto_50c.data:2.010000:3057828.124
carto_60c.data:2.010000:3057085.690
carto_70c.data:2.010000:3056527.810
(3056527.810- 3059523.843) / 40 / 3.05 MHz * 10e6 = -24.55 ppm/K

carto_30c.data:3.050000:3037486.479
carto_40c.data:3.050000:3036630.494
carto_50c.data:3.050000:3035752.677
carto_60c.data:3.050000:3034956.218
carto_70c.data:3.050000:3034506.623
(3034506.623 - 3037486.479) / 40 / 3.03 MHz * 10e6 = -24.58 ppm/K

I enabled the MCU temperature report and heated it separately.
Those values are from the Klipper log.

39.8C 47998659
78.5C 47998365
84.6C 47998481 # That is weird, so I ignored it :D
(47998365 - 47998659) / 38.7 / 48 Mhz * 10e6 = -0.158 ppm/K

Alas, that’s not the case, the oscillator seems to have excellent temperature stability.

LDC1612’s datasheet and application manuals (https://www.ti.com/lit/an/snoa950/snoa950.pdf) stated that a change in the IDRIVE current would shift the frequency output.
And we want to avoid hitting the 1.8V.

Because my sensor want IDRIVE 19 at mid-air, 20 at being close to the bed, and 21 when heated, there are data about that.
(With enabled IDRIVE boost).
20 and 21 will hit 1.8v at distance > 4mm.
19 would hit <1.2v at distance ~ 2.5 + 3 mm. Where 2.5 is my current nozzle offset and 3 is z_offset.

G90
G0 X205 Y202.5 Z0
LDC_SET_DRIVE_CURRENT CHIP=carto VALUE=19 # It is a custom command
PROBE_EDDY_CURRENT_CALIBRATE CHIP=carto

Repeated 5 times with different currents.

Graphs



It seems insignificant to my taste.
Like, if we do overdrive it a little, it should perform okayish as long as the current is constant and vice versa.
I should say, that increasing the current increase the overall frequency range a little. Not sure if it worth it.

3260338 - 2989696 = 270642 # IDRIVE 17 -0.82%
3260961 - 2989560 = 271401 # IDRIVE 18 -0.54% 
3261636 - 2988780 = 272856 # IDRIVE 19 base
3262351 - 2986903 = 275448 # IDRIVE 20 +0.95%
3262883 - 2985053 = 277830 # IDRIVE 21 +1.82%