Teensy 3.6 Dev Board Beginner Code Failing

I recently decided i wanted to try using Rust for embedded programming rather than C, and only have a Teensy 3.6 board available for learning this process. I am fairly new to Rust so this has already been a massive learning curve, plus the inner workings of CPUs have always been elusive until now. There are not many tutorials / help forum posts relating to the teensy 3.6, and those that are available are multiple years old.

I have spent a few weeks on following these tutorials and writing code that I thought should work, however after uploading my code, nothing happens. The current goal is just to get the onboard LED to turn on so I know my code is running correctly. I tried writing the code from scratch following a tutorial I found online which never worked, and I recently found out about svd2rust crates for accessing peripherals.

I am now attempting to write the same program to turn on the onboard LED, but using the K66 PAC. This has made a bit more sense to me, however the program still seems to fail. My current code using the PAC is below.

#![no_std]
#![no_main]

// allows us to define our entry point for the program
use cortex_m_rt::entry;
use k66;

// defines panic behaviour so we dont have to
extern crate panic_halt;

// our entry point main function, it loops infinitely
#[entry]
fn main() -> ! {
    // first extract the peripherals for the processor
    let k66_periph = k66::Peripherals::take().unwrap();
    // first we need to disable the watchdog timer.
    // to do this we need the unlock and stctrlh register addresses 
    let wd = k66_periph.WDOG;
    // unlock the watchdog
    wd.unlock.write(|w| unsafe { w.wdogunlock().bits(0xC520) });
    wd.unlock.write(|w| unsafe { w.wdogunlock().bits(0xD928) });
    // now its unlocked, disable the watchdog
    wd.stctrlh.write(|w| w.wdogen().clear_bit());

    // next enable clock gate for portc
    let sim = k66_periph.SIM;
    // enable portc
    sim.scgc5.write(|w| w.portc().set_bit());

    // next we set pin 13 to gpio mode
    let portc = k66_periph.PORTC;
    // set pin 13 to run in gpio mode
    portc.pcr5.write(|w| w.mux()._001());

    // next we write to the pin
    let pin13 = k66_periph.GPIOC;
    pin13.pdor.write(|w| w.pdo5()._1());

    loop{}
}

for reference, the onboard LED is connected to Port C on bit 6, and requires a logic high output on the pin in order to be illuminated. I have also setup the memory.x file for the cortex-m-rt crate and am targeting the correct target (thumbv7em-none-eabihf). finally, the processor on the board is the MK66FX1M0VMD18 for reference. I have been attempting this for weeks and any help will be greatly appreciated.

Does the C equivalent work?

Yes, the C program works fine

Excellent. Hardware works. That leaves build process, crate / RTL, and upload as possible problems.

I believe most / all ARM processors use memory mapped I/O. Were I in your shoes I would write the equivalent program using low level Rust primitives like those in ptr::core. If that works then the build and upload are correct and the crate or the way you are using the crate is the problem. If that does not work then the build and/or upload could be the problem.

The build process seems to be working, using objdump i get this output

Disassembly of section .text:

00000400 <__stext>:
     400:       movs    r4, #0
     402:       mvns    r4, r4
     404:       mov     lr, r4
     406:       bl      0x4d8 <__pre_init>      @ imm = #206
     40a:       mov     lr, r4
     40c:       ldr     r0, [pc, #56]           @ 0x448 <$d.8>
     40e:       ldr     r1, [pc, #60]           @ 0x44c <$d.8+0x4>
     410:       movs    r2, #0
     412:       cmp     r1, r0
     414:       beq     0x41a <__stext+0x1a>    @ imm = #2
     416:       stm     r0!, {r2}
     418:       b       0x412 <__stext+0x12>    @ imm = #-10
     41a:       ldr     r0, [pc, #52]           @ 0x450 <$d.8+0x8>
     41c:       ldr     r1, [pc, #52]           @ 0x454 <$d.8+0xc>
     41e:       ldr     r2, [pc, #56]           @ 0x458 <$d.8+0x10>
     420:       cmp     r1, r0
     422:       beq     0x42a <__stext+0x2a>    @ imm = #4
     424:       ldm     r2!, {r3}
     426:       stm     r0!, {r3}
     428:       b       0x420 <__stext+0x20>    @ imm = #-12
     42a:       ldr     r0, [pc, #48]           @ 0x45c <$d.8+0x14>
     42c:       mov.w   r1, #15728640
     430:       ldr     r2, [r0]
     432:       orr.w   r2, r2, r1
     436:       str     r2, [r0]
     438:       dsb     sy
     43c:       isb     sy
     440:       push    {lr}
     442:       bl      0x460 <main>            @ imm = #26
     446:       udf     #0

00000448 <$d.8>:
     448: 00 00 00 20   .word   0x20000000
     44c: 00 00 00 20   .word   0x20000000
     450: 00 00 00 20   .word   0x20000000
     454: 00 00 00 20   .word   0x20000000
     458: f4 04 00 00   .word   0x000004f4
     45c: 88 ed 00 e0   .word   0xe000ed88

00000460 <main>:
     460:       push    {r7, lr}
     462:       mov     r7, sp
     464:       bl      0x46a <basic_teensy::__cortex_m_rt_main::hdeac4641251fc268> @ imm = #2
     468:       trap

0000046a <basic_teensy::__cortex_m_rt_main::hdeac4641251fc268>:
     46a:       sub     sp, #24
     46c:       movw    r0, #50464
     470:       movs    r1, #1
     472:       str     r0, [sp]
     474:       movw    r0, #8192
     478:       movt    r0, #16389
     47c:       str     r0, [sp, #4]
     47e:       movw    r0, #55592
     482:       str     r0, [sp]
     484:       nop
     486:       nop
     488:       ldr     r0, [sp, #4]
     48a:       bic     r0, r0, #1
     48e:       str     r0, [sp, #4]
     490:       movw    r0, #28672
     494:       movt    r0, #16388
     498:       str     r0, [sp, #8]
     49a:       ldr     r0, [sp, #8]
     49c:       orr     r0, r0, #2048
     4a0:       str     r0, [sp, #8]
     4a2:       movw    r0, #45076
     4a6:       movt    r0, #16388
     4aa:       str     r0, [sp, #12]
     4ac:       ldr     r0, [sp, #12]
     4ae:       bfi     r0, r1, #8, #3
     4b2:       str     r0, [sp, #12]
     4b4:       movw    r0, #61504
     4b8:       movt    r0, #16399
     4bc:       add.w   r1, r0, #20
     4c0:       str     r1, [sp, #16]
     4c2:       ldr     r1, [sp, #16]
     4c4:       str     r0, [sp, #20]
     4c6:       orr     r0, r1, #32
     4ca:       str     r0, [sp, #16]
     4cc:       ldr     r0, [sp, #20]
     4ce:       orr     r0, r0, #32
     4d2:       str     r0, [sp, #20]
     4d4:       b       0x4d4 <basic_teensy::__cortex_m_rt_main::hdeac4641251fc268+0x6a> @ imm = #-4
000004d6 <UsageFault>:
     4d6:       b       0x4d6 <UsageFault>      @ imm = #-4

000004d8 <__pre_init>:
     4d8:       bx      lr

000004da <HardFaultTrampoline>:
     4da:       mov     r0, lr
     4dc:       movs    r1, #4
     4de:       tst     r0, r1
     4e0:       bne     0x4ea <HardFaultTrampoline+0x10> @ imm = #6
     4e2:       mrs     r0, msp
     4e6:       b.w     0x4f2 <HardFault_>      @ imm = #8
     4ea:       mrs     r0, psp
     4ee:       b.w     0x4f2 <HardFault_>      @ imm = #0

000004f2 <HardFault_>:
     4f2:       b       0x4f2 <HardFault_>      @ imm = #-4

above is the output from attempting to write the same program using the core::ptr primitives. I don't know if i did this correctly so here is my code

#[entry]
fn main() -> ! {
// first we write to disable the watchdog
    let wd_unlock = &mut 0x4005_200E as *mut i32;
    let wd_ctrl = &mut 0x4005_2000 as *mut i32;
    // unlock register location is 0x4005_200E
    // write values are 0xC520 then 0xD928
    unsafe {
        write_volatile(wd_unlock, 0xC520);
        write_volatile(wd_unlock, 0xD928);

        // wait for the watchdog to unlock
        __nop();
        __nop();

        // read the value of the control register
        let mut value = read_volatile(wd_ctrl);
        // disable the watchdog
        write_volatile(wd_ctrl, value & !0x1);


        // next enable the port clock
        let sim_scgc5 = &mut 0x4004_7000 as *mut i32;
        // just need to toggle the 12th bit
        value = read_volatile(sim_scgc5);
        write_volatile(sim_scgc5, value | (0x1 << 11));


        // next set the pin to gpio
        let portc_pcr5 = &mut 0x4004_B014 as *mut i32;
        // collect current register value
        value = read_volatile(portc_pcr5);
        // adjust the value to reset the mux mode
        value &= !(0x7 << 8);
        // write the mode to the register
        write_volatile(portc_pcr5, value | 0x1 << 8);


        // finally drive the pin high
        let pin13_pddr = &mut 0x400F_F054 as *mut i32;
        let pin13_pdor = &mut 0x400F_F040 as *mut i32;
        // define direction as output on bit 6 (portc5)
        value = read_volatile(pin13_pddr);
        write_volatile(pin13_pddr, value | 0x1 << 5);
        // define the output as high
        value = read_volatile(pin13_pdor);
        write_volatile(pin13_pdor, value | 0x1 << 5);
    }

    loop{}
}

I know uploading also works, using the teensy loader with verbose output shows that the final outputs are being uploaded to the board.

Teensy Loader, Command Line, Version 2.2
Read "teensy.hex": 1464 bytes, 0.1% usage
Waiting for Teensy device...
 (hint: press the reset button)
Found HalfKay Bootloader
Read "teensy.hex": 1464 bytes, 0.1% usage
Programming..
Booting

any suggestions?

From what I can tell those are both 16 bits. Try u16 instead of i32.

It looks like everything else is 32 bits. You might try the unsigned datatype but I doubt that will make a difference.

defining wd_unlock as

let wd_unlock = &mut 0x4005_200E as *mut u16;

caused the error

casting `&mut i32` as `*mut u16` is invalid

is there a defined way of allowing a 16 bit register to be accessed using this method?

:wink:

For those, I think you just want to drop the &mut, since you're trying to directly convert the address into a pointer.

I made the changes suggested by you and LegionMammal, my code now looks like this,

#![feature(stdsimd)]

#![no_std]
#![no_main]

// allows us to define our entry point for the program
use cortex_m_rt::entry;

// used to wait for the watchdog to unlock
use core::arch::arm::__nop;

// for the core::ptr method
use core::ptr::{read_volatile, write_volatile};

// defines panic behaviour so i dont have to
extern crate panic_halt;

// our entry point main function, it loops infinitely
#[entry]
fn main() -> ! {
    unsafe {
        // first we write to disable the watchdog
        let wd_unlock = 0x4005_200E as *mut u16;
        let wd_ctrl = 0x4005_2000 as *mut u16;
        // write values are 0xC520 then 0xD928 to unlock
        write_volatile(wd_unlock, 0xC520_u16);
        write_volatile(wd_unlock, 0xD928_u16);

        // wait for the watchdog to unlock
        __nop();
        __nop();

        // read the value of the control register
        let value = read_volatile(wd_ctrl);
        // disable the watchdog
        write_volatile(wd_ctrl, value & !0x1);


        // next enable the port clock
        let sim_scgc5 = 0x4004_8038 as *mut i32;
        // just need to toggle the 12th bit
        let mut value = read_volatile(sim_scgc5);
        write_volatile(sim_scgc5, value | (0x1 << 11));


        // next set the pin to gpio
        let portc_pcr5 = 0x4004_B014 as *mut i32;
        // collect current register value
        value = read_volatile(portc_pcr5);
        // adjust the value to reset the mux mode
        value &= !(0x7 << 8);
        // write the mode to the register
        write_volatile(portc_pcr5, value | 0x1 << 8);


        // finally drive the pin high
        let pin13_pddr = 0x400F_F094 as *mut i32;
        let pin13_pdor = 0x400F_F080 as *mut i32;
        // define direction as output on bit 6 (portc5)
        value = read_volatile(pin13_pddr);
        write_volatile(pin13_pddr, value | 0x1 << 5);
        // define the output as high
        value = read_volatile(pin13_pdor);
        write_volatile(pin13_pdor, value | 0x1 << 5);
    }

    loop{}
}

However, this still doesnt work. Any idea whether any additional code is required to properly initialize the board?

Is preparing the watchdog necessary? I assume there's a way to disable it with a fuse and that the Teensy was shipped with it disabled.

If preparing the watchdog is not necessary I suggest trying without that bit of code.

Going through the documentation relating tothe watchdog, it seems that the watchdog timer is enabled by default (The WDOGEN bit is set to 1 on reset), and there is no pinout related to the operation of the watchdog., there is a separate EWM module, but this is disabled by default.