[Testers Needed] Mbed OS Port for Teensy 4.0

Paul, thank you for the debugging help! Much appreciated. I did a bit of checking and figured out how to put together the setup that you need. First, change mbed_app.json to disable USB and enable the UART console:
Code:
{
    "target_overrides": {
        "*": {
            "platform.stdio-baud-rate": 115200,
            "platform.stdio-buffered-serial": 1,
            "target.console-usb": false,
            "target.console-uart": true
        }
    }
}

Then, CMakeLists.txt needs to be modified to disable the RTOS (making it possible to write to streams from an ISR). Change:
Code:
target_link_libraries(HelloWorld mbed-os) # Can also link to mbed-baremetal here
to
Code:
target_link_libraries(HelloWorld mbed-baremetal mbed-usb)

Now, we will edit main.cpp to have it create a USBSerial and try to write to it:
Code:
#include "mbed.h"
#include "USBSerial.h"

int main()
{
    USBSerial usbSerial(false);
    usbSerial.connect();
    FILE * usbSerialFile = fdopen(&usbSerial, "w");

    while(true) 
    {
	printf("Hello world from Mbed CE!\n");
        fprintf(usbSerialFile, "Hello over USB\n");

	ThisThread::sleep_for(1s);
    }

    return 0;
}

I tested this setup, and I'm able to add a printf inside USB_DeviceEhciInit() and see it print over the serial console.
 
Yep, your recipe worked for me
Code:
Hello world from Mbed CE!
Hello world from Mbed CE!
Hello World from inside usb_device_ehci.c, controllerId = 2
Hello world from Mbed CE!

no USB tty device

Code:
[346233.024806] xhci_hcd 0000:00:14.0: Timeout while waiting for setup device command
[346233.232462] usb 1-3.3: device not accepting address 16, error -62
[346233.232746] usb 1-3-port3: unable to enumerate USB device
[346247.760576] usb 1-3.3: new high-speed USB device number 17 using xhci_hcd
[346247.860898] usb 1-3.3: New USB device found, idVendor=16c0, idProduct=0478, bcdDevice= 1.07
[346247.860904] usb 1-3.3: New USB device strings: Mfr=0, Product=0, SerialNumber=1
[346247.860907] usb 1-3.3: SerialNumber: 0009634C
[346247.863256] hid-generic 0003:16C0:0478.005B: hidraw3: USB HID v1.11 Device [HID 16c0:0478] on usb-0000:00:14.0-3.3/input0
[346250.098890] usb 1-3.3: USB disconnect, device number 17
[346250.320727] usb 1-3.3: new high-speed USB device number 18 using xhci_hcd
[346255.632855] usb 1-3.3: device descriptor read/64, error -110
 
Applied all the stuff from msg #76 and it seems to be working (printf inside usb_device_ehci.c... not USB of course)

screenshot.png
 
I poked around inside the USB code and tried to follow what happens during enumeration.

The trail goes cold in USB_DeviceResetNotification() in usb_device_dci.c at line 420. This definitely does get run.

Code:
    /* Call device callback to notify the application that the USB bus reset signal detected.
    the deviceCallback is the second parameter of USB_DeviceInit */
    handle->deviceCallback(handle, kUSB_DeviceEventBusReset, NULL);

I'm not 100% certain what is really supposed to run next, but from searching around it sure looks like USB_DeviceClassCallback() in usb_device_class.c at line 373 ought to run.

I put this into TARGET_MIMXRT1050/usb_device_class.c near line 377, but it doesn't ever execute that printf().

Code:
usb_status_t USB_DeviceClassCallback(usb_device_handle handle, uint32_t event, void *param)
{
    usb_device_common_class_struct_t *classHandle;
    usb_status_t error = kStatus_USB_Error;

[COLOR="#FF0000"]printf("@@USB_DeviceClassCallback@@\n");[/COLOR]

    /* Get the common class handle according to the device handle. */
    error = USB_DeviceClassGetHandleByDeviceHandle(handle, &classHandle);
    if (kStatus_USB_Success != error)
    {
        return error;
    }

    if (kUSB_DeviceEventBusReset == event)
    {
        /* Initialize the control pipes */
        [COLOR="#009000"]USB_DeviceControlPipeInit(handle, classHandle);[/COLOR]

        /* Notify the classes the USB bus reset signal detected. */
        USB_DeviceClassEvent(handle, kUSB_DeviceClassEventDeviceReset, classHandle);
    }

    /* Call the application device callback function. deviceCallback is from the second parameter of
       USB_DeviceClassInit */
    error = classHandle->configList->deviceCallback(handle, event, param);
    return error;
}

I'll admit this is guesswork, but from what I'm seeing recorded by my USB protocol analyzer watching the actual communication, it sure seems like the USB_DeviceControlPipeInit() function (highlighted green above) isn't ever getting called. No control pipe init would perfectly explain the hardware sends ACK for setup+data tokens, but then nothing ever happens when the host starts sending IN or OUT tokens.
 
Last edited:
Here's another observation that might be easier for figuring out what's wrong.

If I add a printf() inside USB_DeviceClassInit() in TARGET_MIMXRT1050/usb_device_class.c near line 377, it never prints anything.

Code:
usb_status_t USB_DeviceClassInit(
    uint8_t controllerId,                              /*!< [IN] Controller ID */
    usb_device_class_config_list_struct_t *configList, /*!< [IN] Pointer to class configuration list */
    usb_device_handle *handle                          /*!< [OUT] Pointer to the device handle */
)
{
    usb_device_common_class_struct_t *classHandle;
    usb_status_t error = kStatus_USB_Error;
    uint8_t mapIndex;
    uint8_t classIndex;

[COLOR="#FF0000"]printf("--USB_DeviceClassInit--\n");[/COLOR]

    if ((NULL == handle) || (NULL == configList) || ((usb_device_callback_t)NULL == configList->deviceCallback))
    {
        return kStatus_USB_InvalidParameter;
    }

But if I add syntax error junk, I do get a compiler error, so this code definitely is being compiled into the application.

Maybe some other code that is supposed to call USB_DeviceClassInit() at startup is missing or broken?
 
I checked in the debugger on my dev kit. The line
Code:
handle->deviceCallback(handle, kUSB_DeviceEventBusReset, NULL);
goes to
Code:
usb_status_t USBPhy_DeviceCallback(usb_device_handle handle, uint32_t event, void *param)
in targets/TARGET_NXP/TARGET_MCUXpresso_MCUS/USBPhy_MCUXpresso.cpp

I can debug more tonight but that's all I have time to check now.
 
AHA! I just made a major step forward. Using this branch, I can now get the USB device to enumerate. It still cannot print text, but it's a lot closer to working. Turns out that I goofed when I fixed the ncache region in the first MR.
 
Well I finally caught up with you guys... With all of the changes from post #76 and #82 and the following CMakeLists.txt:
Code:
#
# Mbed CE Hello World Project
#
cmake_minimum_required(VERSION 3.19)
cmake_policy(VERSION 3.19)

# Initialize Mbed OS build system. 
# Note: This block must be before the project() call.
set(MBED_APP_JSON_PATH mbed_app.json)
# set(CUSTOM_TARGETS_JSON_PATH custom_targets.json) # If you need a custom target, use this line to specify the custom_targets.json

include(mbed-os/tools/cmake/app.cmake) # Load Mbed CE toolchain file and basic build system

# If you need any custom upload method configuration for your target, do that here

add_subdirectory(mbed-os) # Load Mbed OS build targets.  Must be added before any other subdirectories

project(MbedCEHelloWorld) # TODO: change this to your project name

add_executable(HelloWorld main.cpp)
[COLOR="#FF0000"]#target_link_libraries(HelloWorld mbed-os) # Can also link to mbed-baremetal here[/COLOR]
[COLOR="#00FF00"]target_link_libraries(HelloWorld mbed-baremetal mbed-usb)[/COLOR]
mbed_set_post_build(HelloWorld) # Must call this for each target to set up bin file creation, code upload, etc

mbed_finalize_build() # Make sure this is the last line of the top-level buildscript
and mbed_app.json:
Code:
{
    "target_overrides": {
        "*": {
            "platform.stdio-baud-rate": 115200,
            "platform.stdio-buffered-serial": 1,
            "target.console-usb": true,
            "target.console-uart": true
        }
    }
}
and main.cpp:
Code:
#include "mbed.h"
#include "USBSerial.h"

int main()
{
    USBSerial usbSerial(false);
    usbSerial.connect();
    FILE * usbSerialFile = fdopen(&usbSerial, "w");

    while(true) 
    {
	    printf("Hello world from Mbed CE!\n");
        fprintf(usbSerialFile, "Hello over USB\n");
	[COLOR="#00FF00"]fflush(usbSerialFile);[/COLOR]
	ThisThread::sleep_for(1s);
    }

    return 0;
}

We have this for console-uart:
Code:
Hello world from Mbed CE!                                                       
Hello world from Mbed CE!                                                       
Hello world from Mbed CE!                                                       
Hello world from Mbed CE!
and from console-usb:
Code:
Hello over USB                                                                  
Hello over USB                                                                  
Hello over USB                                                                  
Hello over USB

It's working:) At first I did not see any output from USBSerial so I went back to check my work and everything matched. I went back to reflash the T40 and I noticed that 'Hello over USB' had printed to the terminal. I cleared the screen and there was no output. So I remembered we were treating USBSerial as a file device and then I realized the output buffer needed to be flushed. Added fflush() and now have constant output...
 
I tried the bugfix update (msg #82) and can confirm USB Serial seems to be working. I can open the /dev/ttyACM0 port with Arduino Serial Monitor. :)

screenshot.png

Here's how the USB enumeration looks.

sc1.png

Here's the communication which it's printing "Hello world from Mbed CE!". Looks like inefficient use of 1 byte packets, but works.

sc2.png


and again, note to future me, commands to checkout and build and upload

Code:
cd /tmp
rm -rf mbed-ce-hello-world
git clone https://github.com/mbed-ce/mbed-ce-hello-world.git
cd mbed-ce-hello-world/
git clone git@github.com:mbed-ce/mbed-os.git
cd mbed-os/
git checkout bugfix/fix-mimxrt-noncache-region
cd ..
mkdir build
cd build/
cmake .. -GNinja -DCMAKE_BUILD_TYPE=Develop -DMBED_TARGET=TEENSY_40
ninja
~/teensy/arduino-1.8.19/hardware/tools/teensy_post_compile -file=HelloWorld -path=/tmp/mbed-ce-hello-world/build
# (press pushbutton on Teensy 4.0)
 
From the analyzer, you can pretty easily see we're getting 5 packets between each SOF. At 480 Mbit there are 8000 SOF per second. So the data rate is probably about 40 kbytes/sec. Good that it's working. But for HS USB bandwidth, pretty dismal performance.
 
Here's an attempt to port our usual lines/sec USB benchmark.

Code:
#include "mbed.h"

uint32_t counter;
uint32_t prior_counter;
uint32_t prior_clock;
uint32_t count_per_second;

int main()
{
        counter = 10000000; // starting with 8 digits gives consistent chars/line
        prior_counter = counter;
        count_per_second = 0;
        prior_clock = clock(); // https://os.mbed.com/questions/61002/Equivalent-to-Arduino-millis

        while(true) {
                printf("count=%lu, lines/sec=%lu\r\n", counter, count_per_second);
                counter = counter + 1;
                uint32_t now_clock = clock();;
                if (now_clock - prior_clock > CLOCKS_PER_SEC) {
                        prior_clock = prior_clock + CLOCKS_PER_SEC;
                        count_per_second = counter - prior_counter;
                        prior_counter = counter;
                }
        }
        return 0;
}

It seems to be working. Speed is pretty underwhelming.
 
Well, the best times I can get for USBSerial is about ~3600 lines per second with this:
Code:
#include "mbed.h"
#include "USBSerial.h"

uint32_t counter;
uint32_t prior_counter;
uint32_t prior_clock;
uint32_t count_per_second;

void msStart(void);
unsigned long  ms(void);

volatile unsigned long  _ms;

void msStart(void) {
    SysTick_Config(SystemCoreClock / 1000);
}

extern "C" void SysTick_Handler(void) {
    _ms++;
}

unsigned long ms(void) {
    return _ms;
}

USBSerial Serial(true); // Set to true for 480mb/s, false for 12mb/s

int main()
{
	ThisThread::sleep_for(1s);
	msStart();
    counter = 10000000; // starting with 8 digits gives consistent chars/line
    prior_counter = counter;
    count_per_second = 0;
    prior_clock = ms();

    while(true) {
      Serial.printf("count=%lu, lines/sec=%lu\r\n", counter, count_per_second);
      counter = counter + 1;
      uint32_t now_clock = ms();
      if (now_clock - prior_clock > 999) {
        prior_clock = prior_clock + 1000;
        count_per_second = counter - prior_counter;
        prior_counter = counter;
      }
    }
    return 0;
}
compared to ~224000 with Teensyduino.
I have made sure the HSP bit was set in the USB1 control/status port. Curious about other clock settings...
 
Well, the best times I can get for USBSerial is about ~3600 lines per second with this:
...
compared to ~224000 with Teensyduino.
I have made sure the HSP bit was set in the USB1 control/status port. Curious about other clock settings...

Not run in some time but IIRC PJRC'd sample sketch stable avg was closer to twice that over 500K on Windows and Paul on Linux saw something close to twice that. So, plenty of room to improve.

Also noted recent posts put USB buffer to DTCM - avoiding RAM2/cache confusion - though seems PJRC made buffers from RAM2/DMAMEM work to unburden DTCM ... not sure that relates to speed ...
 
As I recall (but it's been a couple years) we took a minor speed hit by putting the buffers in RAM2 (aka OCRAM), which was worthwhile to keep the DTCM available. You can game the benchmark slightly with a huge buffer in DTCM. I never did really dig into why, if it really was a matter of lower AXI bus bandwidth versus spending time in the cache maintenance operations, or maybe some other factors. We've always had the EHCI structures (QH, dTD) in DTCM.

Testing has also shown as we push to higher speeds, CPU time spent converting the 2 integers to ASCII matters. Again it's been a while, but I recall discovering the implementation in printf / sprintf is much slower than our Arduino-compatible Print class (which differs from Arduino's code in some important-for-performance ways).

When the USB device side is optimized, speed on the PC side can make a huge difference, as we saw in the earlier days of Teensy 4.0.

But how the USB driver code manages the hardware is by far the most important factor. I've recently been looking at the low level code. I believe what we have today in Teensyduino is pretty good, but still not necessarily as optimized as it could be. We're also disabling interrupts during part of the work, which would be really nice to eliminate even if it means a small performance loss. But this sort of work gets into really difficult stuff to (probably) gain only minor improvements.

Mbed OS on the other had probably has some really easy low hanging fruit for USB optimization. Something is taking a horrible 1-byte-at-a-time approach which causes terribly inefficient use of USB bandwidth by transmitting 1 byte packets rather than 512 bytes, or even just 30-some bytes for the single line that's generated all at once. I've looked at the code briefly, but it's so many directories and so many files filled with abstraction layers that I wasn't able to find the code that actually handles a C-library write() call. But I'm pretty sure somewhere in all that code is a rather dumb write() handler which loops over a buffer and commits it 1-byte-at-a-time to the USB stack, which in turn takes each byte and inefficiently puts it into its own USB packet.
 
@Paul - I agree. It takes a lot of time to filter through all of the directories and layers of the code. And I also have seen that they are using the one byte at a time packets. I have a general idea of there layout and that does not make it any easier to follow when I am still learning. Very time consuming:)

Thanks @Paul and @defragster for the input. Working on retirement:) Maybe will have more time then...
 
Interesting seeing this come together for the T_4.0
...
Thanks @Paul and @defragster for the input. Working on retirement:) Maybe will have more time then...

:) Ran the LPS sketch again (Windows 11) - sometimes slow <=200K - then toward 500K and some running at 500K to 850K. As always - can't see if the PC prints 100% as it gets far behind with a few seconds stuck in MBytes of buffers for the GUI to present.
The Teensy can dish it out faster than the PC can collect and deliver to the GUI app for screen display. It does take some time for the PC to ramp up to the full speed. So first few seconds+ can be off on starting.

Again just anectodical ref info - of course under 10K where a T_3.6 can do 25K lps just shows lots of room for improvement - BUT job one, IT WORKS!
re p#89 - going to OCRAM may have been slowing as expected - don't recall measuring that - but freeing TCM RAM for code and data and allowing for more generous buffers for overall USB function seems a good trade off where the overall perf hit only shows when USB (ab)use would eat extra cycles.
 
Re: Cortex M7 tests

Over the years I've been using the mbed online compiler (RIP) for testing. Recently I've been using mbed cli and Keil Studio. I've also used NXP MCUxpresso with the SDK for the 1170 (M7@996MHz). It looks like mbed CE supports the 1170 and NUCLEO F767ZI (M7@216MHz). I successfully built cmake/ninja mbed CE projects for MIMXRT1050_EVK,Teensy 4, 1170, and F767ZI.

Any plans for DSP/arm_math.h lib ? (mbed-dsp)
 
As an update, I went ahead and merged the latest USB fix into mbed-ce master branch, so you won't need to use any special branches anymore.

The next thing I want to do is add upload method support, so that the build system can automatically flash code onto the Teensy instead of you having to run a command. For that, I do have a question: should I be using "teensy_post_compile", or "teensy_loader_cli"? teensy_loader_cli seems to be called out in the documentation, but Paul's example commands use e.g.
Code:
teensy_post_compile -file=HelloWorld -path=/tmp/mbed-ce-hello-world/build

Also, is there a binary download of either of these executables that I can point Mbed users to? Or will I need to have them build them from source?

Regarding USB stuff, I regret to say that I can't really help with the speed issues, as USB is not my area of expertise. I really appreciate any help that people can provide with this. That said, I agree that Mbed's code can be a little labyrinthine in places due to having lots layers for different pieces of hardware. For understanding how it works, I find that an SCIDE like VS Code or CLion is invaluable, because it lets you just ctrl-click on a function to jump to its definition. Since the IDE is aware of all compile flags and defines, it knows exactly which parts of the code are in use and how they're configured. Mbed CE supports both these IDEs natively, the setup instructions are in the hello world project.

Last thing: my next immediate project is getting Teensy 4.1 support operational and getting Ethernet running for it. I've ordered a board and will start this weekend!
 
Pin tests on T4.0 with mbed CE:

digital tests (D0 - D23): inpin.mode(PullUp) and then jumpering each pin to GND and confirming pin valuue 0 -- all ok

PWM tests: only pin D22 worked with PWM (default 50 hz). all other flexPWM pins (D0-D9, D23) crashed the T4.0

Analog in (ADC): test with 3v3 or GND, only A4 and A5 worked, A0-A3 crashed the T4.0. Didn't test A6-A9

SPI works at various clock speeds (D10 - D13) max 42MHz

I2C works at 100khz and 400khz (D18/D19)
 
Last edited:
Exciting news! I have spent the last couple days working on a Teensy 4.1 port, including Ethernet support, and have just about got it running! I am able to send and receive packets on the network, and I can use the sockets example to make HTTP requests. Mbed's networking library is pretty powerful, so happy to bring in support for it!

Note that using TLS sockets in that same example doesn't work yet, for some reason it fails to parse the hardcoded CA certificate in Mbed's example. Currently scratching my head over that one...

And yeah, I will take a look at the pin mappings in the next week or two. I know that needs some work.
 
Note that using TLS sockets in that same example doesn't work yet, for some reason it fails to parse the hardcoded CA certificate in Mbed's example. Currently scratching my head over that one...

So do TLS sockets work on the 1060 EVK and/or 1050 EVK? My earlier Ethernet testing on those EVK boards was using the lwIP library, and I only did standalone testing of the mbedTLS lib.

CA certificates have an expiration date. Is that causing TLS parse error? I don't know if self-signed certificates will work.
 
I tried the Teensy 4.1 port with my T4.1 and home ethernet
Code:
           git clone --recursive https://github.com/mbed-ce/mbed-os-example-sockets.git
           in build/
           cmake .. -GNinja -DCMAKE_BUILD_TYPE=Develop -DMBED_TARGET=TEENSY_41
           ninja
           teensy_loader_cli -mmcu=TEENSY41 -w mbed-os-example-sockets.hex

looks like it worked
Code:
            IP address: 192.168.1.141
            Netmask: 255.255.255.0
            Gateway: 192.168.1.1
            Resolve hostname ifconfig.io
            ifconfig.io address is 172.64.194.16
            Opening connection to remote port 80
            Sending message:
            GET / HTTP/1.1
            Host: ifconfig.io
            Connection: close
            sent 56 bytes
            Complete message sent
            received 100 bytes:
            HTTP/1.1 200 OK
            Demo concluded successfully

? trying https/443 (maybe)
Code:
in build/mbed-os/mbed-target-config.h, try
#define MBED_CONF_APP_USE_TLS_SOCKET 1
...
get
Error: _socket.set_root_ca_cert() returned -3003

NSAPI_ERROR_PARAMETER           = -3003

Doesn't look like certificate has expired:
Code:
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 33554617 (0x20000b9)
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
        Validity
            Not Before: May 12 18:46:00 2000 GMT
            Not After : May 12 23:59:00 2025 GMT
        Subject: C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
...
 
Last edited:
Yeah, that's what I get too, and I do see it on the 1050 EVK as well. I can try to debug through it in the next couple days, it looks like it's dying in the ASN.1 parse function?
 
Ethernt MAC address

Each Teensy 3* and Teensy 4* comes with a unique number set in the OTP efuses. The teensy core uses those bytes to define the Ethernet MAC and a USB serial number.

Code:
Teensy 4
             uint32_t m1 = HW_OCOTP_MAC1;   // T4 MAC
             uint32_t m2 = HW_OCOTP_MAC0;
             mac[0] = m1 >> 8;
             mac[1] = m1 >> 0;
             mac[2] = m2 >> 24;
             mac[3] = m2 >> 16;
             mac[4] = m2 >> 8;
             mac[5] = m2 >> 0;
My Teensy 4.1 MAC address 04:e9:e5:01:86:c4

usb_init_serialnumber() in hardware/teensy/avr/cores/teensy4/usb_desc.c
 
@MultipleMonomials - I see that USB Host was supported with Mbed 2.0 with LPC1768. But I cannot find any thing in Mbed-CE. Has this not been added in yet?

Thanks...
 
Back
Top