jamesmunns / teensy3-rs

Rust on the Teensy3
74 stars 11 forks source link

Undefined references when using TwoWire classes #22

Open TheZoq2 opened 7 years ago

TheZoq2 commented 7 years ago

I am trying to use get i2c communication to work but i'm getting a linker error when I use any of the functions.

In the compiled documentation, I have found the struct teensy3_sys::TwoWire which I assume is generated from the arduino TwoWire class defined in hardware/teensy/avr/Wire or hardware/arduino/avr/libraries/Wire/

I have also found bindings::Wire, bindings::Wire1 and bindings::Wire2. They all have the following type signature

pub static mut Wire: TwoWire

I assume they are the same instances of TwoWire you use in regular arduino code.

Because of this, I assume that the following code should compile and work fine

pub fn begin_master()
{
    unsafe {
        bindings::Wire1.begin();
    }
}

However, if I add a call to begin_master to my main program, I get the following linker error:

error: linking with `arm-none-eabi-gcc` failed: exit code: 1
  |
  = note: "arm-none-eabi-gcc" "-L" "/home/frans/.xargo/lib/rustlib/thumbv7em-none-eabihf/lib" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/release/deps/teensy3_rs_demo-b14e9a4b080b8a28.0.o" "-o" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/release/deps/teensy3_rs_demo-b14e9a4b080b8a28" "-Wl,--gc-sections" "-nodefaultlibs" "-L" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/release/deps" "-L" "/home/frans/Documents/rust/teensy/playground/target/release/deps" "-L" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/release/build/teensy3-sys-f07c06b4a4fe31a2/out" "-L" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/release/build/teensy3-sys-f07c06b4a4fe31a2/out" "-L" "/home/frans/.xargo/lib/rustlib/thumbv7em-none-eabihf/lib" "-Wl,-Bstatic" "/tmp/rustc.s3B0REQ5hrXx/libteensy3_sys-55e4c1751d1dc539.rlib" "-Tteensy3-sys.ld" "-Wl,--gc-sections,--defsym=__rtc_localtime=0" "-Wl,--start-group" "-Wl,--end-group" "-lm" "-lnosys" "-lc" "-lgcc" "-mcpu=cortex-m4" "-mthumb" "-Os" "--specs=nano.specs" "-mfloat-abi=hard" "-mfpu=fpv4-sp-d16" "-Wl,-Bdynamic"
  = note: /home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/release/deps/teensy3_rs_demo-b14e9a4b080b8a28.0.o: In function `main':
          teensy3_rs_demo.cgu-0.rs:(.text.main+0x1a): undefined reference to `begin'
          collect2: error: ld returned 1 exit status

error: aborting due to previous error

error: Could not compile `teensy3-rs-demo`.

I also tried bindings::TwoWire_begin(&mut bindings::Wire1); which gives me the same error.

Looking at the arduino source, I don't see why there would be a function called begin, if anythign it should be TwoWire::begin. I also don't see bindings for the internal c code that TwoWire wraps around libraries/Wire/src/utility/twi.c.

This is when trying to compile for teensy3.5, I have not tried 3.2

jamesmunns commented 7 years ago

Hey @TheZoq2, check out the contents of teensy3-rs-demo/target/thumb.../release/build/teensy3-sys-.../out/bindings.rs. This is where the generated bindings get put.

You'll see something like this:

#[repr(C)]
pub struct TwoWire {
    pub _base: Stream,
    pub __bindgen_anon_1: TwoWire_I2C_Hardware_t,
    pub port: *mut KINETIS_I2C_t,
    pub hardware: *const TwoWire_I2C_Hardware_t,
    pub rxBuffer: [u8; 32usize],
    pub rxBufferIndex: u8,
    pub rxBufferLength: u8,
    pub txAddress: u8,
    pub txBuffer: [u8; 33usize],
    pub txBufferIndex: u8,
    pub txBufferLength: u8,
    pub transmitting: u8,
    pub slave_mode: u8,
    pub irqcount: u8,
    pub sda_pin_index: u8,
    pub scl_pin_index: u8,
    pub user_onRequest: ::core::option::Option<unsafe extern "C" fn()>,
    pub user_onReceive: ::core::option::Option<unsafe extern "C" fn(arg1:
                                                                        c_types::c_int)>,
}

// ...

impl TwoWire {
    #[inline]
    pub unsafe fn begin(&mut self) { TwoWire_begin(self) }
    #[inline]
    pub unsafe fn begin1(&mut self, address: u8) {
        TwoWire_begin1(self, address)
    }
    #[inline]
    pub unsafe fn begin2(&mut self, address: c_types::c_int) {
        TwoWire_begin2(self, address)
    }

// ...

    #[inline]
    pub unsafe fn new(myport: *mut KINETIS_I2C_t,
                      myhardware: *const TwoWire_I2C_Hardware_t) -> Self {
        let mut __bindgen_tmp = ::core::mem::uninitialized();
        TwoWire_TwoWire(&mut __bindgen_tmp, myport, myhardware);
        __bindgen_tmp
    }
}

Based on this, I think you will need something like this:

unsafe {
    // you need to replace the "..." here with something
    let mut i = bindings::KINETIS_I2C_t{ ... };
    let mut hw = bindings::TwoWire_I2C_Hardware_t{ ... };

    // something like this, not 100% sure about the syntax here
    let mut x = bindings::TwoWire::new(&mut i, &mut hw);
    x.begin();
    // ...
}

Let me know if this helps.

jamesmunns commented 7 years ago

Also to clarify, in the Arduino environment, they create an instance of the TwoWire class "implicitly". I just looked at the source, and I saw this in WireKinetis.cpp:778:

#ifdef WIRE_IMPLEMENT_WIRE // This is set for the Teensy 3.x family
TwoWire Wire(KINETIS_I2C0, TwoWire::i2c0_hardware);
void i2c0_isr(void) { Wire.isr(); }
#endif

This means that the C++ class is creating an instance of TwoWire named Wire. I honestly have no idea how to access it. In C, I would do something like this:

extern TwoWire Wire;

// ...

Wire.begin();

I'm not sure how to do that in Rust (if it is possible). If you can't find a way to do it, you can follow my instructions in the message above on how to create a new instance of TwoWire in rust. The C++ code is helpful to tell you what to put in those { ... }s I mentioned above:

#define KINETIS_I2C0        (*(KINETIS_I2C_t *)0x40066000)
const TwoWire::I2C_Hardware_t TwoWire::i2c0_hardware = {
    SIM_SCGC4, SIM_SCGC4_I2C0,
#if defined(__MKL26Z64__) || defined(__MK20DX128__) || defined(__MK20DX256__)
    18, 17, 255, 255, 255,
    2, 2, 0, 0, 0,
    19, 16, 255, 255, 255,
    2, 2, 0, 0, 0,
#elif defined(__MK64FX512__) || defined(__MK66FX1M0__)
    18, 17, 34, 8, 48,
    2, 2, 5, 7, 2,
    19, 16, 33, 7, 47,
    2, 2, 5, 7, 2,
#endif
    IRQ_I2C0
};
jamesmunns commented 7 years ago

Oh. Derp. From bindings.rs:

extern "C" {
    #[link_name = "Wire"]
    pub static mut Wire: TwoWire;
}

That would do it. Try this:

pub fn begin_master()
{
    unsafe {
        bindings::Wire.begin();
    }
}
jamesmunns commented 7 years ago

Okay. I've come full circle. I think I now understand your actual question. I've also decided to stop answering questions at 1am.

I don't know why the linker is having an issue with a missing Wire1. For every target other than TeensyLC, it should exist. Something (gcc?) is probably stripping the symbol out, since it thinks it is unused. There are a couple fixes for that, but I would have to test them.

I still think making a new instance as I described above would work, but probably shouldn't be necessary. I will shut up now until I have time to actually test my suggestions locally. Sorry for blowing up your inbox with notifications. Hopefully some of this actually helped, and I wasn't just telling you things you already know :)

TheZoq2 commented 7 years ago

I don't know why the linker is having an issue with a missing Wire1. For every target other than TeensyLC, it should exist. Something (gcc?) is probably stripping the symbol out, since it thinks it is unused. There are a couple fixes for that, but I would have to test them

Yep, GCC stripping them out sounds like a pretty good theory. Also, it's not just Wire1, Wire and Wire2 don't work either.

I still think making a new instance as I described above would work, but probably shouldn't be necessary.

I'll look into that and see if I can figure it out.

Sorry for blowing up your inbox with notifications. Hopefully some of this actually helped, and I wasn't just telling you things you already know :)

No worries about the spam, it gave me some ideas about things to try. Though you could always edit the message if you think people are annoyed by the spam :P

Edit: I'm not sure how to properly initialize the two_wire class. Specifically, i'm not sure how to set the _base member.

TheZoq2 commented 7 years ago

I did some more thinking and testing about this and I'm not sure if the issue is with the Wire constants.

Wouldn't the linker complain about TwoWire::begin and not just begin if that was the case? Or is rust/bindgen doing some magic in the background?

Also, I noticed that the SPI functions in the crate have the same issue.

let spi = teensy3::spi::Spi{};
spi.begin();

I also tested

    unsafe {
        bindings::SPIClass_begin();
    }

Which gives the same error:

  = note: "arm-none-eabi-gcc" "-L" "/home/frans/.xargo/lib/rustlib/thumbv7em-none-eabihf/lib" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/debug/deps/teensy3_rs_demo-a6c1a5face9da051.0.o" "-o" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/debug/deps/teensy3_rs_demo-a6c1a5face9da051" "-Wl,--gc-sections" "-nodefaultlibs" "-L" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/debug/deps" "-L" "/home/frans/Documents/rust/teensy/playground/target/debug/deps" "-L" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/debug/build/teensy3-sys-f07c06b4a4fe31a2/out" "-L" "/home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/debug/build/teensy3-sys-f07c06b4a4fe31a2/out" "-L" "/home/frans/.xargo/lib/rustlib/thumbv7em-none-eabihf/lib" "-Wl,-Bstatic" "/tmp/rustc.V97TmF8YLyZW/libteensy3_sys-c0adf1102c30f343.rlib" "-Tteensy3-sys.ld" "-Wl,--gc-sections,--defsym=__rtc_localtime=0" "-Wl,--start-group" "-Wl,--end-group" "-lm" "-lnosys" "-lc" "-lgcc" "-mcpu=cortex-m4" "-mthumb" "-Os" "--specs=nano.specs" "-mfloat-abi=hard" "-mfpu=fpv4-sp-d16" "-Wl,-Bdynamic"
  = note: /home/frans/Documents/rust/teensy/playground/target/thumbv7em-none-eabihf/debug/deps/teensy3_rs_demo-a6c1a5face9da051.0.o: In function `teensy3_rs_demo::main':
          /home/frans/Documents/rust/teensy/playground/src/main.rs:63: undefined reference to `begin'
          collect2: error: ld returned 1 exit status

In the bindings.rs file it seems like bindgen generates functions for all methods of a class and prefixes them with ClassName_ but it also puts [#link_name = "..."] before it

extern "C" {
    #[link_name = "begin"]
    pub fn SPI2Class_begin();
}

To me, the link_name = "begin" thing would cause the undefined reference to begin error since the linker looks for begin instead of SpiClass::begin(). Could this be a bindgen issue?

patches11 commented 6 years ago

@TheZoq2 Did you ever get further along with this? I'm attempting to integrate OctoWS2811 and having very similar problems

TheZoq2 commented 6 years ago

I did not, I lost interest in using rust on teensys for a while and then started using it on a "blue pill" board instead

On Fri, 11 May 2018, 22:44 Patrick Brown, notifications@github.com wrote:

@TheZoq2 https://github.com/TheZoq2 Did you ever get further along with this? I'm attempting to integrate OctoWS2811 and having very similar problems

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/jamesmunns/teensy3-rs/issues/22#issuecomment-388480731, or mute the thread https://github.com/notifications/unsubscribe-auth/AFERLaEyfg9TpXpAKURKhOl9LFPa7WH7ks5txfhGgaJpZM4NKfou .

patches11 commented 6 years ago

Alright, thanks for the response!