I got the recently shipped CTRL Massdrop Keyboard, which runs a SAMD51J18A.
While tinkering with it, I have had no luck getting any debug output from it nor getting it to reset.
The configuration:
SRC = led_programs.c
SRC += matrix.c
ARM_ATSAM = SAMD51J18A
MCU = cortex-m4
CUSTOM_MATRIX = yes
EXTRAKEY_ENABLE = yes # Audio control and System control
CONSOLE_ENABLE = yes # Console for debug (enables debug prints to virtser, no console support)
NKRO_ENABLE = yes # USB Nkey Rollover
#VIRTSER_ENABLE = yes # USB Serial Driver
Other than that, I've written a keymap that does the command but no output. LShift, RShift magic + D didn't seem to do anything either. At this point I'm not even sure if the magic works correctly, the (default) configuration for it looks like that:
#ifndef CONFIG_H
#define CONFIG_H
#define VENDOR_ID 0x04D8
#define PRODUCT_ID 0xEED2
#define DEVICE_VER 0x0101
#define MANUFACTURER "Massdrop Inc."
#define PRODUCT "CTRL Keyboard"
#define SERIAL_NUM "00017"
#define MATRIX_ROWS 11
#define MATRIX_COLS 8
#define PA 0
#define PB 1
#define MATRIX_ROW_PORTS PB, PB, PB, PB, PB, PB, PA, PA, PB, PB, PB
#define MATRIX_ROW_PINS 4, 5, 6, 7, 8, 9, 10, 11, 10, 11, 12
#define MATRIX_COL_PORTS PA, PA, PA, PA, PA, PA, PA, PA
#define MATRIX_COL_PINS 0, 1, 2, 3, 4, 5, 6, 7
#define MCU_HZ 48000000
#define DEBOUNCING_DELAY 5
#define IS_COMMAND() ( \
keyboard_report->mods == (MOD_BIT(KC_LSHIFT) | MOD_BIT(KC_RSHIFT)) \
)
#define FORCE_NKRO
#endif
This way I'm listening on all HIDs (I believe there are four) coming from this USB controller:
namespace Octopode.CLI {
class Program {
private static async void ReadLoop(HidDevice usbDevice) {
HidDeviceData dataPoint = await usbDevice.ReadAsync();
while (true) {
dataPoint = await usbDevice.ReadAsync();
if (dataPoint.Status != HidDeviceData.ReadStatus.Success) {
throw new InvalidOperationException($"Cannot read from USB device! State was {dataPoint.Status}");
}
foreach (var bit in dataPoint.Data) {
if(bit == 0) {
continue;
}
Console.WriteLine(bit);
}
}
}
static void Main(string[] args) {
var devices = new List<HidDevice>(DeviceEnumerator.EnumerateAllDevices().Where(x => x.Attributes.ProductId == 0xEED2 && x.Attributes.VendorId == 0x04D8));
var tasks = new List<Task>();
foreach(var device in devices) {
tasks.Add(Task.Factory.StartNew(() => { ReadLoop(device); }));
}
while(true) {
System.Threading.Thread.Sleep(1000);
}
}
}
}
Any help is highly appreciated.
Does HID_listen work?
https://www.pjrc.com/teensy/hid_listen.html
As for the reset, that's probably a setting in the reset code that isn't configured properly for the SAMD51J18A chips.
I guessed as much. HID_Listen does not work, which is why I used my HID listener just to be sure.
The misconfiguration seems to be caused by this ruleset:
/**
* \file
*
* \brief Linker script for running in internal FLASH on the SAMD51J18A
*
* Copyright (c) 2017 Microchip Technology Inc.
*
* \asf_license_start
*
* \page License
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the Licence at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* \asf_license_stop
*
*/
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
SEARCH_DIR(.)
/* Memory Spaces Definitions */
MEMORY
{
//rom (rx) : ORIGIN = 0x00000000, LENGTH = 0x00040000
rom (rx) : ORIGIN = 0x00004000, LENGTH = 0x0003C000
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00020000
bkupram (rwx) : ORIGIN = 0x47000000, LENGTH = 0x00002000
qspi (rwx) : ORIGIN = 0x04000000, LENGTH = 0x01000000
}
/* The stack size used by the application. NOTE: you need to adjust according to your application. */
STACK_SIZE = DEFINED(STACK_SIZE) ? STACK_SIZE : DEFINED(__stack_size__) ? __stack_size__ : 0x8000;
_srom = ORIGIN(rom);
_lrom = LENGTH(rom);
_erom = ORIGIN(rom) + LENGTH(rom);
/* Section Definitions */
SECTIONS
{
.text :
{
. = ALIGN(4);
_sfixed = .;
KEEP(*(.vectors .vectors.*))
*(.text .text.* .gnu.linkonce.t.*)
*(.glue_7t) *(.glue_7)
*(.rodata .rodata* .gnu.linkonce.r.*)
*(.ARM.extab* .gnu.linkonce.armextab.*)
/* Support C constructors, and C destructors in both user code
and the C library. This also provides support for C++ code. */
. = ALIGN(4);
KEEP(*(.init))
. = ALIGN(4);
__preinit_array_start = .;
KEEP (*(.preinit_array))
__preinit_array_end = .;
. = ALIGN(4);
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
. = ALIGN(4);
KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*crtend.o(.ctors))
. = ALIGN(4);
KEEP(*(.fini))
. = ALIGN(4);
__fini_array_start = .;
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
__fini_array_end = .;
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*crtend.o(.dtors))
. = ALIGN(4);
_efixed = .; /* End of text section */
} > rom
/* .ARM.exidx is sorted, so has to go in its own output section. */
PROVIDE_HIDDEN (__exidx_start = .);
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > rom
PROVIDE_HIDDEN (__exidx_end = .);
. = ALIGN(4);
_etext = .;
.relocate : AT (_etext)
{
. = ALIGN(4);
_srelocate = .;
*(.ramfunc .ramfunc.*);
*(.data .data.*);
. = ALIGN(4);
_erelocate = .;
} > ram
.bkupram (NOLOAD):
{
. = ALIGN(8);
_sbkupram = .;
*(.bkupram .bkupram.*);
. = ALIGN(8);
_ebkupram = .;
} > bkupram
.qspi (NOLOAD):
{
. = ALIGN(8);
_sqspi = .;
*(.qspi .qspi.*);
. = ALIGN(8);
_eqspi = .;
} > qspi
/* .bss section which is used for uninitialized data */
.bss (NOLOAD) :
{
. = ALIGN(4);
_sbss = . ;
_szero = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = . ;
_ezero = .;
} > ram
/* stack section */
.stack (NOLOAD):
{
. = ALIGN(8);
_sstack = .;
. = . + STACK_SIZE;
. = ALIGN(8);
_estack = .;
} > ram
. = ALIGN(4);
_end = . ;
}
I can do __NVIC_SystemReset() just fine by now, just the magic byte isn't set, tho. So I'm currently fiddling around, figuring out what ram0_end could be to work correctly.
Edit: Further inspection of the fork indicates that the Massdrop people have added this entire chipset, so it's a bit hard to trace where they failed. Poking into random memory doesn't seem to do anything good either.
Here's a fun bit of great software engineering:
https://github.com/Massdrop/qmk_firmware/blob/master/tmk_core/common/arm_atsam/bootloader.c#L19
That's why bootloader_jump() does not work at all.
Looking at it after some good rest, I figured out how this thing works on debugging. Generally the firmware implementations ignores going via HID USB reports and uses entirely serial. Looking at the amount of code that massdrop has written themselves, they probably had a good reason for that.
So reading from the device with hid_listen or similiar software wont work. However, all you need is a serial terminal and point it to the right interface, in my case it was screen /dev/cu.usbmodem14224 96000
Baudrate of 96000 was just a good guess and it seems to work just fine. Next time I should just look at the usb descriptor instead of headbutting straight into HID. 🤷♂️
Demo:

Tagging @patrickmt as he seems to develop the software for the CTRL.
Since yesterday we had quite a development. The mdloader does not flash the boot rom for safety reasons. Also there is no bootloader code in this repository, so you have no control about booting. The bootloader basically does the following:
def boot_img():
long a = read(0x40000, 64)
if irq_reset or a == 0xFFFFFFFF_FFFFFFFF:
boot_flasher()
else:
boot_rom()
Since you either have to have a broken rom or need to press the button that is wired to the CPU RESET, there is currently no easy way of doing it. Talking with Patrick, he's onto a solution, which involves resetting the CPU to a state which it would be like while booting and then jumping directly into the flashing region. That is, to the best of my knowledge, hardly possible from my side without the bootloader or flashing image (except you can dump from the keyboard).
So we'll wait on a resolution of Massdrops side. This issue can be closed for now.
@DarkMio is anything from this still needing addressed?
Bootloader jump works fine. The default debugging method still does not work, but that should be simply added with a comment on the readme of that board, that it actually is only willing to talk over serial. Otherwise good work so far.