Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undefined references when using TwoWire classes #22

Open
TheZoq2 opened this issue Apr 27, 2017 · 9 comments
Open

Undefined references when using TwoWire classes #22

TheZoq2 opened this issue Apr 27, 2017 · 9 comments

Comments

@TheZoq2
Copy link

TheZoq2 commented Apr 27, 2017

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
Copy link
Owner

jamesmunns commented Apr 27, 2017

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
Copy link
Owner

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
Copy link
Owner

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
Copy link
Owner

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
Copy link
Author

TheZoq2 commented Apr 28, 2017

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
Copy link
Author

TheZoq2 commented Apr 29, 2017

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
Copy link

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

@TheZoq2
Copy link
Author

TheZoq2 commented May 11, 2018 via email

@patches11
Copy link

Alright, thanks for the response!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants