Launching RAM-based applications

Status
Not open for further replies.

jcw

Active member
I'd like to reproduce a workflow on the Teensy 4 which I've been using on STM32's: place a "BIOS-like" application in flash, with a variety of libraries and drivers included, which checks for the presence of an application in RAM at a pre-defined location. This second (partial) application is built, linked, and uploaded independently. Because it can hook into the flash-based code, it can stay small and provide near-interactive build/upload development cycles. BTW, this is not about upload speed (USB2 is more than plenty), but about all the other little delays in the complete cycle - which tend to add up.

The build process works, I have two images, and loading the flash-based part is completely standard and effortless. The puzzle is how to get the RAM based part into the Teensy and then reset it so the combination can start up.

As first iteration, I've tried using the command-line "teensy_reboot" plus "teensy_loader_cli" apps. Unfortunately, it appears that the uploader refuses to load into the DTCM or RAM2 areas. See https://github.com/PaulStoffregen/teensy_loader_cli/blob/master/teensy_loader_cli.c#L914 and https://github.com/PaulStoffregen/teensy_loader_cli/blob/master/teensy_loader_cli.c#L937 for what appears to be the cause of this.

There are other ways to skin this cat (e.g. add HEX loading into my own code), but perhaps there's a simpler approach?
IOW, is there a mechanism to load a HEX file into the Teensy's RAM with the current tools?
 
To follow up, I'm not able to find the source code for "teensy_reset" and "teensy_serial" - are these online somewhere?
 
I would suggest searching the forum for "over the air updates", it seems similar to what you're trying to accomplish and has been asked many times.
 
I don't know where the source code for teensy_reset is and I don't know what teensy_serial is supposed to do, but I can help with info on how to reset a teensy:

First thing you need to do is to start the bootloader. Depending on the USB mode this can be done by the following procedure

  1. All USB-Modes which provide a CDC interface (i.e. USB-Serial, Multiple serial, combined modes where one of the modes is Serial etc)
    -> Set the baud rate to 134 to start the bootloader
  2. All USB-Modes which provide a SerEMU interface (i.e. all the hid modes, combined modes etc):
    The SerEmu interface generates a HID device. To start the bootloader you need to write a feature report with the following content to this HID device 0x00, 0xA9, 0x45, 0xC2, 0x6B.

As soon as the board is in bootloading mode (where it also presents itself as HID device) you can reset the board by sending a normal HID report with the content 0x00 0xFF 0xFF 0xFF to it.

If you are familiar with C# you find some code here: https://github.com/luni64/TeensySha...e034/src/TeensySharp/Implementation/Teensy.cs
If you prefer plain C you can have a look at the sources of Koromix TyTools https://github.com/Koromix/tytools
Both links also show how to upload firmware to the boards if this of any use for your project.

There are also some python implementations around but I don't have links
 
Thank you @brtaylor - that's quite a bit of reading but it looks like it will get me up to date on all the options and solutions so far. Perfect. I also suspect that the HalfKay loader is flash-only and won't let me upload to RAM, so there's little point extending the "teensy_loader_cli app". So far, it looks like one solution is to have the flash-based app wait for and perform the hex loading before jumping to what it just stored in RAM.
 
Thanks @luni, that should help a lot to streamline the process once I get the uploads going. Appreciated.
 
Ok, the basic mechanism works, but it's rather nasty: I have to reserve a generous area in ITCM as it looks like code cannot (?) execute in DTCM or in RAM2 (a bit surprising, perhaps due to a no-execute setting in the MPU?). The way to do this is not hard, but it bloats the flash upload with useless bytes:

Code:
FASTRUN uint8_t iSpace [16*1024];

So the main application in flash starts by reading HEX data from USB serial, and loads it into that area (the code must be linked to match those addresses), then jumps to it. The RAM-based app then turns around and accesses the API exposed by the main flash app (it can be any API, that's the whole point). Since I'm using C++ on both sides, the interface consists of objects with virtual member functions.

The workflow is not quite as smooth as on STM32 yet: I have to reset and get the app running again (via HalfKay), wait for the serial port to reappear, send the hex file, and then it all starts running (similar to a regular upload, but not affecting flash). It's a bit convoluted, with still several delays in the whole process. But it's a start ...

If the running application could catch an interrupt from incoming serial USB data, I could force a quick reset back into the same app and speed things up.

I'll look at the suggestions in #4, Python could indeed be a good option since it provides low-level USB access out of the box. All this RAM trickery may not make much sense yet, I can clarify later if this leads to anything useable. Anyway, progress. Any further tips welcome.
 
If the running application could catch an interrupt from incoming serial USB data, I could force a quick reset back into the same app and speed things up.
If you are willing to hack the core you can trigger a reset (or something else) by adding code to the location where TD detects the reboot request via baud rate 134 (https://github.com/luni64/cores/blob/314918d6ca301af062a1f07b6b68118382dadef2/teensy4/usb.c#L688)

E.g this:
Code:
#ifdef CDC_STATUS_INTERFACE
	// 0x2021 is CDC_SET_LINE_CODING
	if (setup.wRequestAndType == 0x2021 && setup.wIndex == CDC_STATUS_INTERFACE) {
		memcpy(usb_cdc_line_coding, endpoint0_buffer, 7);
		printf("usb_cdc_line_coding, baud=%u\n", usb_cdc_line_coding[0]);
		if (usb_cdc_line_coding[0] == 134) {
			usb_start_sof_interrupts(NUM_INTERFACE);
			usb_reboot_timer = 80; // TODO: 10 if only 12 Mbit/sec
		}
	[B][COLOR="#FF0000"]	else if (usb_cdc_line_coding[0] == 135) {
			SCB_AIRCR = 0x05FA0004;
		}[/COLOR][/B]
	}
#endif

It will reset the board if the baud rate is switched to 135. I tested this with PUTTY and it works. However, for a real life application you probably want some more elaborate code to prevent multiple resets until the PC app/script sets the baud rate back to != 135;

---
However, resetting the board with python is really simple and, if I understand correctly, you'd need some script to upload your hex file anyway. Here proove of concept code without any error checking, hardcoded com-port (windows) and assuming only one teensy on the bus:
Code:
import hid
import serial
import time

ser = serial.Serial('COM10',baudrate=134)       # start bootloader
time.sleep(1)                                   # give it some time to boot up HalfKay (better spin until the bootloader device appears on the bus)

device = hid.Device(0x16c0,0x478)               # connect to the bootloader  
device.write(bytes([0x00, 0xFF, 0xFF, 0xFF]))   # send reset report

time.sleep(1)                                   # give it some time to boot up the bios (better spin...)
ser = serial.Serial('COM10')                    # connect to the bios via serial

#upload code to ram and start....
 
Last edited:
Ok, the basic mechanism works, but it's rather nasty: I have to reserve a generous area in ITCM as it looks like code cannot (?) execute in DTCM or in RAM2 (a bit surprising, perhaps due to a no-execute setting in the MPU?). The way to do this is not hard, but it bloats the flash upload with useless bytes:
...

Started a post to note NOEXEC ...

See : {install}\hardware\teensy\avr\cores\teensy4\startup.c :: FLASHMEM void configure_cache(void)
>> It associates cache and sets usage restrictions.

The other note was that RAM2/DMAMEM will survive a warm restart and it not given an 'init' reset pass like RAM1 is.
 
Thanks, that explains the NOEXEC. The devil is in the detail, here's the workflow I have so far, which is already workable:

* one shell window open on the flash-based core "bios" app, using cmd-line PlatformIO
* one shell window open on the RAM-based app I'm currently working on, also cmd-line PIO for now
* one shell window open, running "teensy_serialmon", which nicely auto-reconnects

A change in the bios-app is simply a 100% standard build + upload.
A change in the ram-app is done as a build followed by "cat blah.hex >/dev/teensy-usbport".
The bios reads hex from serial on startup, then launches what it loaded.
On return (assuming it's working code, the bios does a µC reset on itself.
There's a clear screen just after the hex load, before the ram-app starts.
I haven't worked on the steps needed after a crashed (i.e.non-reset) app yet.

The result is virtually instant (sub-second) reload. My plan is to automate this further so that those first two screens each use file-change notification to automatically build and upload on file change of any of the main source files. At this point, it all essentially becomes interactive: a file save will trigger it all, with build errors showing in the respective screen, and output showing in the serial output window.

The point of all this is to put more and more of the application code in Flash, with just a tiny part in RAM while working on it.
Switching to tinker with another part is a matter of switching to another little ram-based build area.
This is only for use during development. At the end of the day, it's still a complete app in the "bios", i.e. std flash-based.
There are tons of pesky little details to make this all work seamlessly, but it's getting there, now also on Teensy.
 
To follow up ... in what I have so far, the Teensy can be in one of four states:

1. started up, and waiting for hex input
2. running the ram-application code (it'll go back to 1 if the app ends normally)
3. in boot-loader mode (for whatever reason)
4. crashed, or otherwise unresponsive to USB

State 4 is unavoidable and needs a button press or a careful watchdog setup, which take it to state 3.
State 1 and 2 alternate "when all goes as planned" (read: rarely, during development).
Only state 1 allows me to quickly upload a fresh RAM app build using "cat".

As I understand it, state 2 can be forced to state 3 via the set-to-134-baud mechanism.
And state 3 can be taken to state 1 by sending the special message with the Teensy in this HID mode.
I'd rather not hack the core. Will look into Python's USB options a bit more. Thanks @luni for the tips & links.
 
If you're on an older version, you might want to update to Teensyduino 1.54 for automatic fault recovery. It can't recover from every type of crash, but it does catch a lot of the common problems like use of a null pointer.
 
Yes, I'm on 1.54 - great, so state 4 should be rare. Good to know.
And "teensy_loader_cli --mcu=TEENSY41 -b" will also get me from state 3 to 1.
Looks like that should cover all the cases. Perfect.

Update - returning to an earlier point from #9: configure_cache makes RAM2 "NOEXEC". I'm new to the Teensy, so I haven't followed this forum long enough yet, maybe there's good reason, but I would vote for allowing executable code in RAM2. I can probably change the MPU settings in the app after startup, but perhaps this restriction could be lifted with no impact on existing code? It would allow using RAM2 for code loaded/created/launched by an application. Including JIT stuff and such (who knows, someone might want to explore that one day).
 
Last edited:
Have you tried GUI TyCommander? { mentioned p#4 } Not sure it is perfect in this case - but it is generally, and has features that might work with states 1 and 3.

#1 >> Send / Send File :: might replace 'cat' using GUI.
#3 >> it has a 'Reset' button for the attached Teensy

#0 >> can also perform FLASH Uploads for "State #0" - replacing TeensyLoader. It has command line tool or the GUI takes commands from Arduino when integrated.

State #2 >> has an excellent SerMon for capturing output and providing user input like IDE SerMon - but better. It can connect one or many Teensy units.

All in one program doesn't get in the way of itself (holding COM port open) switching between tasks.

#4 >> No help for that except when it shows Teensy not reachable and time for a Button press.
 
Oh, wow, thanks for that. I've looked at the C# link, but then forgot to look a bit better at TyTools.
I'm aiming for all cmd-line/scripted, i.e. just editing and (auto-) saving files, everything else automatic.
My workflow is based on (cmd-line) PlatformIO which will also upload, so there is some overlap.
 
TyCommander is the coolest thing about Teensy that PJRC doesn't do - @koromix made an awesome tool!

Keeping the IDE active and usable fits the forum and being ready for Paul's Beta stuff. But evolved github.com/Defragster/Tset to work with SublimeText editor and with 'F7' it triggers a command line build like the IDE - captures the console output and if all is good uploads with TeensyLoader, or TyCommander if 'integrated to the IDE' - then TyCommander in either case for Serial Monitor as more often than not I end up with more than one Teensy online - there were 7 before TD 1.54 released as all the versions of LittleFS Media were online for testing and maybe a Teensy MicroMod.

So that GUI makes a great SerMon - and supports Dual Serial too so one 'port' can have Serial turned off to allow command line tool 'cat' upload perhaps, and the other for debug spew.
 
I'm trying to limit dependencies. The Teensy setup in PlatformIO already includes "teensy_{reboot,restart,serialmon}". There's also "teensy_loader_cli" which is fine for regular uploads, but won't handle my RAM-loading needs. That's fine, since my own "bios" app now starts up as a hex-to-ram loader.

Just out of curiosity, I've also confirmed that "stty -f /dev/tty.usbmodem77046101 134" will start the Teensy boot loader (if no other serial conn is open).

Since PlatformIO already depends on Python3 and pyserial, that's currently my tool of choice for further automating things. Again, mostly to understand how the reset, reboot, and upload processes work precisely, I tried to find a way to take the Teensy 4.1 from bootloader mode to restarting the app. No luck so far with this code:

Code:
import hid
d = hid.device()
d.open(0x16c0,0x478)
r = d.write([0x00,0xFF,0xFF,0xFF])
print(r)

That write always returns -1, and the board stays in boot-loader mode.

I've also tried alternatives, gleaned from various projects mentioned in this thread:
r = d.write([0xFF,0xFF,0xFF]+1085*[0]) # from loader_cli
r = d.send_feature_report([0x00,0xA9,0x45,0xC2,0x6B]) # from tycmd

None of this is a show-stopper, but I'd be very interested in what the "bare essentials" are to go from the in-boot-mode to the running state.
 
Yep, that's the first thing I tried:

AttributeError: module 'hid' has no attribute 'Device'

I'm on MacOS. I don't know how to get at version info, this is all I have:

>>> hid.__file__
'/usr/local/lib/python3.9/site-packages/hid.cpython-39-darwin.so'

Update: I've updated "hid" via "pip3", now hid.Device is known, but the write still fails with -1 (and leads to errors in hid's error handling). This might be MacOS specific. The board stays in boot mode.
 
Last edited:
Frankly, I don't know much about python. I thought the hid.xxx was simply fully qualifying the name.
Anyway, try to use

Code:
d.write([B][COLOR="#FF0000"]bytes[/COLOR][/B]([0x00, 0xFF, 0xFF, 0xFF]))

it didn't work here without the bytes(...). I assumed it otherwise interprets the array as array of ints.

r = d.send_feature_report([0x00,0xA9,0x45,0xC2,0x6B]) # from tycmd
This would start the bootloader if the board has a SerEmu HID interface which doesn't help here.
 
Yes, good catch. I tried w/ bytes() as well, same result alas. It's unfortunate (but understandable) that the HalfKay sources are not available, now it's down to guesswork on what it's expecting. The teensy_loader_cli loader sends a large packet starting with 3 0xFF's - see https://github.com/PaulStoffregen/teensy_loader_cli/blob/master/teensy_loader_cli.c#L1182-L1190

So "teensy_loader_cli --mcu=TEENSY41 -b" does the trick (and is enough for me to keep going), but I'm stumped as to why I can't reproduce this simple action with either the "hid" or the "usb.core" module in Python. Still, thanks for your help - many coins have dropped for me in the past few days :)

Update - this could be the culprit (in teensy_loader_cli), in which case it's indeed MacOS-specific:

Code:
			// Mac OS-X - removing this call to usb_claim_interface() might allow
			// this to work, even though it is a clear misuse of the libusb API.
			// normally Apple's IOKit should be used on Mac OS-X
			#if !defined(MACOSX)
			r = usb_claim_interface(h, 0);
			if (r < 0) {
				usb_close(h);
				printf_verbose("Unable to claim interface, check USB permissions\n");
				continue;
			}
			#endif

IOW, the Python modules probably claim the interface, which I can confirm will generate an error.

The only way I've found so far to reliably get the application started on MacOS from either running or boot mode, is to compile the "teensy_loader_cli" app with MACOSX and LIBUSB both set. As indicated in the Makefile, this is non-standard, but when using IOKit iso libusb, there's no way to reset (neither hard nor soft reset work).

So my conclusion for now is that I need to launch "teensy_loader_cli --mcu=TEENSY41 -s -b" to always get back to running mode (state 1). It's an awful mess, but whatever ... it looks like this is workable, without adding new dependencies.
 
Last edited:
Status
Not open for further replies.
Back
Top