Implementing i2c on stm32f3discovery

So I am trying to make a few functions that helps to interact with the i2c interface on the stm32f3discovery. I am having problems with the following code. It all compiles and runs but when I run a while loop checking if the txis register (checks if the txdr is empty) is set it just hangs on that line and the register is apparently never set. If I take that line out then it hangs on the while loop checking if the tc (transfer complete) register is clear.
my_api.rs

pub fn new(rcc: &stm32f3::stm32f303::RCC, gpiob: &stm32f3::stm32f303::GPIOB, i2c: &stm32f3::stm32f303::I2C1) -> Self {
    rcc.apb1enr.write(|w| w.i2c1en().set_bit()); //enable i2c1 clock
    rcc.ahbenr.write(|w| w.iopaen().set_bit()); //enable gpioa clock

    gpiob.moder.write(|w| w.moder8().bits(0b10).moder9().bits(0b10)); //alternate function mode
    gpiob.pupdr.write(|w| unsafe{w.pupdr8().bits(0b01).pupdr9().bits(0b01)}); //pull up resister
    gpiob.otyper.write(|w| w.ot8().set_bit().ot9().set_bit()); //open drain output
    gpiob.ospeedr.write(|w| w.ospeedr8().bits(0b11).ospeedr9().bits(0b11)); //high speed
    gpiob.afrh.write(|w| w.afrh8().bits(0b0100).afrh9().bits(0b0100)); //alternate function 4

    i2c.cr1.write(|w| w.pe().clear_bit());

    i2c.timingr.write(|w| {
        w.presc().bits(1); // all settings from page 849 on port mapping
        w.scll().bits(0x13); // standard mode at 8MHz cpu and 100kHz i2c
        w.sclh().bits(0xF);
        w.sdadel().bits(0x2);
        w.scldel().bits(0x4)
    });

    i2c.cr1.write(|w| {
        w.nostretch().clear_bit();
        w.txie().set_bit(); //enable interrupt registers
        w.rxie().set_bit()
    });
    i2c.cr1.write(|w| w.pe().set_bit()); //enable preipheral


    i2c_func{}
}

pub fn read(&self, i2c: &stm32f3::stm32f303::I2C1, device_address: u16, register_address: u8, request_length: u8, rx_data: &mut [u8]) {

    i2c.cr2.write(|w| {
        w.sadd().bits(device_address); //set device address
        w.nbytes().bits(1); //amount of bytes to send
        w.rd_wrn().clear_bit(); //set as a write operation
        w.autoend().clear_bit()
    });

    i2c.cr2.write(|w| w.start().set_bit()); //send start signal
    //hangs on this line
    while i2c.cr2.read().start().bit_is_set() {} //wait for txis to register to be set

    i2c.txdr.write(|w| w.txdata().bits(register_address)); // Send the address of the register that we want to read: IRA_REG_M


    while i2c.isr.read().txe().bit_is_clear() {} // Wait until transfer complete


    i2c.cr2.modify(|_, w| {
        w.nbytes().bits(request_length); //set 
        w.rd_wrn().set_bit();
        w.autoend().set_bit()
    });

    i2c.cr2.write(|w| w.start().set_bit());

    for count in 0..request_length{
        // Wait until we have received the contents of the register
        while i2c.isr.read().rxne().bit_is_clear() {}

        // Broadcast STOP (automatic because of `AUTOEND = 1`)
        rx_data[count as usize] = i2c.rxdr.read().rxdata().bits()
    }
}

main.rs

#![no_std]
#![no_main]

// pick a panicking behavior
extern crate panic_halt; // you can put a breakpoint on `rust_begin_unwind` to catch panics
// extern crate panic_abort; // requires nightly
// extern crate panic_itm; // logs messages over ITM; requires ITM support
// extern crate panic_semihosting; // logs messages to the host stderr; requires a debugger

use cortex_m_rt::entry;
use cortex_m_semihosting::{debug, hprintln};
use stm32f3::stm32f303;


mod my_api;

#[entry]
fn main() -> ! {
    //hprintln!("Starting Setup").unwrap();

    let periph = stm32f303::Peripherals::take().unwrap();

    let gpioe = periph.GPIOE;
    let rcc = periph.RCC;
    let tim6 = periph.TIM6;
    let gpiob = periph.GPIOB;
    let i2c1 = periph.I2C1;


    let i2c = my_api::i2c_mod::i2c_func::new(&rcc, &gpiob, &i2c1);

    let mut rx_data: [u8; 10] = [0,0,0,0,0,0,0,0,0,0];
    i2c.read(&i2c1, 0b001_1110, 0x0A, 1, &mut rx_data);

    hprintln!("{}", rx_data[0]).unwrap();

    loop {}
}

A common mistake is wrong address. Are you sure your address is correct? In 7 bit addressing, you may need to shift the address to left by one bit. (The least significant bit is Read/Write bit.)

I would recommend using an oscilloscope/logic analyzer for troubleshooting. When we work on embedded systems, we are blind without one.

2 Likes

Thank you, Ive replaced the address with a bit shift to the left and it is still getting stuck in the same place.

I have managed to get an oscillisope and it turns out the clock (scl line) isn't running but the data line (sda) does send data. I also noticed that I enabled gpioa clock not gpiob but that does not fix the issue.

After enabling the gpiob clock, do you now see clock pulses on the scl line? The master pulses 9 times. The first 8 pulses are for data and the 9th is for ACK/NACK. When an ACK is detected, TXIS is set. Maybe your device is not giving you an ACK? You can verify that with your scope. If there is no ACK, you may want to change the code and wait until either TXIS or NACKF is set.

note - I have changed the code a little to what it is now. The only two changes are checking if the start bit has changed rather than the tc bit. Im sure they do the same thing but it just makes me make sure thats not the issue.

Thank you for the reply
I also only have one probe for the oscilliscope I can only how you the output of one line.

I see the 9 peaks when I send the start signal, I also get an output on the sda line but it always seems to be the same no matter what device address or register address i enter. I also think I am getting am ACK signal.


This is from the sda line and it never changes no matter the value of the device address, register address or amount of bits to be read. I do have the 9 peaks on the scl line although as I am a new user I can only add one photo.

The tc register then becomes clear so it moves onto sending the register address but this time neither the scl or sda line do anything, which would explain why the txe register is never set. Do i have to set a register to actually send the data I does it do it automatically?

Thank you for your help

[https://photos.app.goo.gl/m3xEEi8cQXZ9m77j7](The photos of the sda and scl lines)

For those who are reading for help, @lonesometraveler messaged on google photos saying that it is sending all 0's with a nack at the end.

After checking the nack register you are correct and it is reading a nack. But I still dont understand why it is sending all 0's as the address. Ive tried resetting all of the timing settings just in case and they seem to be working fine.

I use stm32f3xx-hal for STM32FDISCOVERY. It is pretty easy to do I2C. Maybe you want to try stm32f3xx-hal first? You can then look at the library’s source code and compare it with your implementation.

I have had a look through it and can't see much difference in terms of what is going on. I might have to give in and do that but I thought it might be a fun project to help me understand i2c a bit better. I have just been looking at the registers as it runs through the code and the line that enters in the sadd(device address), rd_wrn(read or write bit), and the nbytes(number of bytes to send/recieve) are all reset after the start bit is sent. Just wandering if that sends any bells off in your head as to what could be the cause. If not I think I might just have to use the stm32f3xx-hal

Isn't that because you do write instead of modify?

https://rust-embedded.github.io/book/start/registers.html#writing

1 Like

Good catch! Mind that write modifies the whole register, putting default values to bits that you don't set explicitly.

Right bit of an update. So that was the issue as to why it wasn't sending the address it all. Although once I had updated the sensors still wasn't responding to anything and i was just getting a nack back. I plugged in another i2c device and it worked as it should. After a while I have realized that the lsm303dlhc is connected to the pb6 and pb7 pins and not pb8 & pb9 which i had the i2c set up on. After changing the setup settings to use pb6 & pb7 it all works perfectly.

Thank you so much @lonesometraveler you have absolutely saved me as I would have gotten so lost without you.

1 Like

For those people looking for answers this is my final solution

pub fn new(rcc: &stm32f3::stm32f303::RCC, gpiob: &stm32f3::stm32f303::GPIOB, i2c:     &stm32f3::stm32f303::I2C1) -> Self {
        rcc.ahbenr.modify(|_,w| w.iopben().set_bit()); //enable gpiob clock
        rcc.apb1enr.modify(|_,w| w.i2c1en().set_bit()); //enable i2c1 clock
        rcc.apb1rstr.modify(|_,w| w.i2c1rst().set_bit());
        rcc.apb1rstr.modify(|_,w| w.i2c1rst().clear_bit());

        gpiob.moder.modify(|_,w| w.moder6().bits(0b10).moder7().bits(0b10)); //alternate function mode
        gpiob.pupdr.modify(|_,w| unsafe{w.pupdr6().bits(0b01).pupdr7().bits(0b01)}); //pull up resister
        gpiob.otyper.modify(|_,w| w.ot6().set_bit().ot7().set_bit()); //open drain output
        gpiob.ospeedr.modify(|_,w| w.ospeedr6().bits(0b11).ospeedr7().bits(0b11)); //high speed
        gpiob.afrl.modify(|_,w| w.afrl6().bits(0b0100).afrl7().bits(0b0100)); //alternate function 4

        i2c.cr1.modify(|_,w| w.pe().clear_bit());

        i2c.timingr.modify(|_,w| {
            w.presc().bits(0); // all settings from page 849 on port mapping
            w.scll().bits(9); // standard mode at 8MHz cpu and 100kHz i2c
            w.sclh().bits(4);
            w.sdadel().bits(1);
            w.scldel().bits(3)
        });

        i2c.cr1.write(|w| {
            w.anfoff().clear_bit(); //enable analogue filter
            w.nostretch().clear_bit();
            w.txie().set_bit(); //enable interrupt registers
            w.rxie().set_bit()
        });

        i2c.cr1.modify(|_,w| w.pe().set_bit()); //enable preipheral

        i2c_func{}
    }

    pub fn read(&self, i2c: &stm32f3::stm32f303::I2C1, device_address: u8, register_address: u8, request_length: u8, rx_data: &mut [u8]) {
        let mut _test_register: bool = false;
        i2c.cr2.modify(|_,w| {
            w.sadd().bits(u16::from(device_address << 1)); //set device address
            w.nbytes().bits(1); //amount of bytes to send
            w.rd_wrn().clear_bit(); //set as a read operation
            w.autoend().clear_bit()
        });

        i2c.cr2.modify(|_,w| w.start().set_bit()); //send start signal

        while i2c.isr.read().txis().bit_is_set() {} //wait for txis to register to be set

        i2c.txdr.modify(|_,w| w.txdata().bits(register_address)); // Send the address of the register that we want to read: IRA_REG_M

        while i2c.isr.read().txe().bit_is_clear() {
            _test_register = i2c.isr.read().nackf().bits();
        } // Wait until transfer complete
    
        
        i2c.cr2.modify(|_, w| {
            w.nbytes().bits(request_length); //set 
            w.rd_wrn().set_bit();
            w.autoend().set_bit()
        });

        i2c.cr2.modify(|_,w| w.start().set_bit());
        
        for count in 0..request_length{
            // Wait until we have received the contents of the register
            while i2c.isr.read().rxne().bit_is_clear() {}
    
            // Broadcast STOP (automatic because of `AUTOEND = 1`)
            rx_data[count as usize] = i2c.rxdr.read().rxdata().bits()
        }
    }

    pub fn write(&self, i2c: &stm32f3::stm32f303::I2C1, device_address: u8, register_address: u8, tx_data: u8) {
        let mut _test_register: bool = false;
        i2c.cr2.modify(|_,w| {
            w.sadd().bits(u16::from(device_address << 1)); //set device address
            w.nbytes().bits(2); //amount of bytes to send
            w.rd_wrn().clear_bit(); //set as a read operation
            w.autoend().clear_bit()
        });

        i2c.cr2.modify(|_,w| w.start().set_bit()); //send start signal

        while i2c.isr.read().txis().bit_is_set() {} //wait for txis to register to be set

        i2c.txdr.modify(|_,w| w.txdata().bits(register_address)); // Send the address of the register that we want to read: IRA_REG_M

        while i2c.isr.read().txe().bit_is_clear() {
            _test_register = i2c.isr.read().nackf().bits();
        } // Wait until transfer complete
    
        i2c.txdr.modify(|_,w| w.txdata().bits(tx_data)); // Send the address of the register that we want to read: IRA_REG_M

        while i2c.isr.read().txe().bit_is_clear() {
            _test_register = i2c.isr.read().nackf().bits();
        } // Wait until transfer complete
        
        i2c.cr2.modify(|_,w| w.stop().set_bit()); //send start signal
    }