-
Notifications
You must be signed in to change notification settings - Fork 40
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't test the PR, but I tried to implement this before (and got stuck at the clock setup part) and I noticed some discrepancies.
src/i2c.rs
Outdated
self.i2c.cr1.modify(|_, w| w.start().set_bit()); | ||
busy_wait!(self.i2c, sb); | ||
|
||
self.i2c.dr.write(|w| unsafe { w.dr().bits(addr & 0b1111_1110) }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should addr
be shifted by 1, or is that already by convention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen implementations doing it both ways, but Arduino and RIOT OS do shift it themselves. I think we also need to
src/i2c.rs
Outdated
type Error = Error; | ||
|
||
fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { | ||
// TODO support transfers of more than 255 bytes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this TODO was left from the stm32f30x-hal
implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, but it's still valid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you say that? I didn't see anything suggesting this wouldn't work for larger transfers.
src/i2c.rs
Outdated
bytes: &[u8], | ||
buffer: &mut [u8], | ||
) -> Result<(), Error> { | ||
// TODO support transfers of more than 255 bytes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
src/i2c.rs
Outdated
busy_wait!(self.i2c, addr); | ||
let _ = self.i2c.sr2.read(); | ||
|
||
for byte in buffer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like "method 1" in the manual, but "method 2" is supposed to work better for polling.
It also looks like it might be missing setting the ACK
flag (it should be set before checking rx_ne
for all the received bytes except the last).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the hint, gonna fix it.
I haven't yet tested read part at all, so it probably doesn't work.
ok, I've tested writing and reading 1, 2, 3 and 8 bytes in standard (50kHz, 100kHz) and fast modes (50kHz, 100kHz, 200kHz, 300kHz, 400kHz), except Fm with 16:9 duty cycle. |
All modes (Sm, Fm 1:1, Fm 16:9) work. |
src/i2c.rs
Outdated
|
||
self.send_start_and_wait()?; | ||
|
||
match buffer.len() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
such a horror show
I consider it to be almost finished. At least the I²C itself works properly. @japaric @lnicola @wose @therealprof could you please give it a try with the peripherals you have at hand? |
@ilya-epifanov I tested This might be a stupid question, but how can I share an I2C peripheral with multiple drivers? |
@lnicola Not at all a stupid question and topic of this issue rust-embedded/embedded-hal#35 |
@ilya-epifanov Can you add a I'd send a PR for that and to avoid the duplication between |
@ilya-epifanov Since you beat me to it, I of course had to create a new BSP crate for the Nucleo-F103RB in order to being able to test it... I've now done a crude porting of my SSD1302 code over to your HAL driver. It more or less works but I'm easily able to freeze it; will have to check what exactly is foul here after cleaning it up. |
@ilya-epifanov The freeze happens somewhere in I2C land, either due to the driver not properly handling a condition or the device keeping the bus hostage; maybe I'll find time to hook it up to the scope later. I've pushed my example code to a new feature branch for the time being: https://github.com/therealprof/nucleo-f103rb/tree/features/blocking-i2c-hal-test |
@lnicola I've added |
@therealprof thank you! I'll try to hook up an SSD1306 and give it a spin |
@ilya-epifanov I know. I was trying to use a driver that (currently, at least) is sending a command, closing the transaction, then starting a new one to read back the response. |
I've just done a few more tests and added another example not relying on serial input to output stuff on the SSD1306. Tests have shown that any speed below around 370kHz is somewhat uncritical but at 400kHz (in 1:1 ratio mode) it'll hang reliably. Also it is not very robust against I2C problems, if I wiggle or disconnect SDA/SCL it'll also hang. |
@lnicola That won't work. |
@ilya-epifanov I just tested your implementation with the BH1750 sensor. The driver uses the Write and Read Traits. Worked without issues. |
I believe there're a number of sensors which will spill their data just by issuing a read without prior write (which is typically used to specify a "register" from which should be read), like the HIH6130. |
@therealprof Could you please take a look at SR1 and SR2 registers when the thing hangs? Those should have addresses |
As a general protection mechanism we need timeouts for I propose the following: let mut i2c = I2c::i2c1(
p.device.I2C1,
(scl, sda),
&mut afio.mapr,
i2c::Mode::Fast { frequency: 400000, duty_cycle: i2c::DutyCycle::Ratio16to9 },
clocks,
&mut rcc.apb1,
);
let mut i2c_1 = i2c.channel("__ timeouts, additional settings, address might go here as well? __");
let si5351 = SI5351::new(&mut i2c_1, false, 25_000_000).unwrap();
let mut i2c_2 = i2c.channel("__ timeouts, additional settings, address might go here as well? __");
let _ = ssd1306::init(&mut i2c_2);
let _ = ssd1306::pos(&mut i2c_2, 0, 0);
let _ = ssd1306::print_bytes(&mut i2c_2, b"Send ASCII over serial for some action"); |
@ilya-epifanov I believe you mean 0x14 and 0x18 if you're interested in the SRs, offsets 0x00 and 0x04 are the CRs. However I'm happy to provide both after putting so much work in it to hook it up. ;)
Two more observations by hooking up the scope:
|
I've added timeouts to the busy waits which at least don't lock up the whole MCU in case of errors. Also, I'm resetting the peripheral after any errors (which might be a little bit brute force approach). |
@ilya-epifanov I checked and it works totally fine now. Not sure the timeout was actually needed but it really is very robust now, I can even disconnect SDA or SCL or induce errors and it will recover just fine. @japaric Please consider merging this. |
src/gpio.rs
Outdated
@@ -314,6 +335,27 @@ macro_rules! gpio { | |||
unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << (16 + $i))) } | |||
} | |||
} | |||
|
|||
impl<MODE> OutputPin for $PXi<Alternate<MODE>> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add UnsafeOutputPin
with all methods marked unsafe
?
Impls like this are necessary to implement workarounds like 2.14.1 in this errata sheet: http://www.st.com/content/ccc/resource/technical/document/errata_sheet/f5/50/c9/46/56/db/4a/f6/CD00197763.pdf/files/CD00197763.pdf/jcr:content/translations/en.CD00197763.pdf.
Also it makes it much more convenient to initlialize pins to proper value on startup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate on how marking the method as unsafe helps in working around the errata?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that Alternate-mode pins should allow regular I/O in safe code, but it's needed for various workarounds. Hence I propose to make such impls unsafe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I hadn't notice this were the Alternate pins. I agree that this should not be public and safe. But, since this is working around a errata, would be possible to keep this private? I'm not 100% sure it's a good idea to expose this to users, regardless of safety.
src/i2c.rs
Outdated
apb.rstr().modify(|_, w| w.$i2cXrst().clear_bit()); | ||
|
||
// 25 ms bus timeout @ ~10 clks per busy_wait cycle | ||
let bus_timeout = (clocks.sysclk().0 / 400).max(10); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't assume DWT to be enabled here
src/i2c.rs
Outdated
let bus_timeout = (clocks.sysclk().0 / 400).max(10); | ||
let pclk1 = clocks.pclk1().0; | ||
|
||
assert!(mode.get_frequency() <= 400_000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it's a good idea, since I've unintentionally overclocked the bus to 600kHz in standard mode once and it worked fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, the only reason I have something similar in the f30x-hal crate is because I don't know how to compute the different parameters for frequencies higher than 1 MHz -- the reference manual only covered frequencies up to that number.
src/i2c.rs
Outdated
assert!(mode.get_frequency() <= 400_000); | ||
|
||
let mut i2c = I2c { i2c, pins, bus_timeout, mode, pclk1 }; | ||
i2c.init(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather compute proper values for the registers and save them here instead of doing every time we init()
after communication errors, but I'll lose the typesafe register API then.
@therealprof is the binary any different? Can you reproduce it reliably? |
Yes, the binary is quite different. ARMv7-M obviously allows to embed load/store addresses directly in the code whereas with ARMv6-M they're all at the end of the function. I've put it here: https://gist.github.com/therealprof/b43b0d6ca39a2a1eb04bb3abbfc431a3 |
Something isn't playing ball with the timeout implementation it seems. It actually works fine with ARMv7 if I lower the speed (e.g. to 380kHz) or use 16:9 ratio at 400kHz. I don't quite understand this assumption:
Not only can the |
NB: The issue in ARMv7-M mode also vanishes with a faster clock setup. |
Is it though? For instance, does i2cdev (the Linux subsystem, not the crate) have a timeout by default? I'm concerned that (a) there's no way to opt out of the timeout and (b) it's not configurable. I'm not sure that the I2C spec says that the I2C slaves has to answer in X us so I don't think a hardcoded value is the proper choice here; some applications might want to allow the slave to hold the bus for a long period, maybe? |
@japaric @ilya-epifanov There is explicitly no maximum hold, low, high, or set up time in the I2C specification. |
Is the timeout issue the only remaining thing here? |
It would be great to get this PR moving again. I've been using @ilya-epifanov's fork for a while now which works great, but it feels bad using a Git dependency in Cargo.toml. I understand the need to get this HAL right, but it seems to be in a pretty good place to me. Why not merge now and fix forward if anything comes up? |
@jamwaffles The interim conclusion seems to be that the timeout behaviour is not acceptable by @japaric and the only way to get this implemented with timeout would be to do the non-blocking implementation and offer different blocking implementations (with and without timeout) based on the non-blocking one which @ilya-epifanov wanted to implement but seemingly didn't get around to. Maybe it would be acceptable to put in the blocking implementation without the timeout and then switch implementations once the non-blocking is available? |
Guys, sorry for the delay. I've extracted the blocking part into
|
@ilya-epifanov would be neat if you could update this, so it has no conflicts. Then I could use it at least locally :) |
@kellerkindt Here you go! |
implemented method 2 from the reference manual for reading, fixed wrong frequency in Fm with 1:1 duty cycle
resetting the bus after errors
@japaric @therealprof @lnicola Is it possible to merge this PR? It's non-blocking now but only exposes the blocking API. |
@ilya-epifanov Don't have write permissions. |
@ilya-epifanov Me neither. I'd like to see this merged, even though I hope a future version will offer a real non-blocking (that is, supporting |
|
||
fn send_addr_and_wait(&self, addr: u8, read: bool) -> NbResult<(), Error> { | ||
self.nb.send_addr(addr, read); | ||
busy_wait_cycles!(self.nb.wait_after_sent_addr(), self.addr_timeout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have an infinite loop here in release mode. It's working in debug mode. Strange, seems that DWT::get_cycle_count()
always returns 0. I don't know where to search mode.
That's hanging here: https://github.com/TeXitoi/bmp380-test/blob/master/src/bmp280.rs#L365
(gdb) bt
#0 _$LT$stm32f103xx_hal..i2c..BlockingI2c$LT$stm32f103xx..I2C1$C$$u20$PINS$GT$$GT$::send_addr_and_wait::h8e287e53a5adee35 (read=false, self=<optimized out>, addr=<optimized out>)
at /home/texitoi/.cargo/git/checkouts/stm32f103xx-hal-7d0db38b85df9102/3a8d64d/src/i2c.rs:213
#1 _$LT$stm32f103xx_hal..i2c..BlockingI2c$LT$stm32f103xx..I2C1$C$$u20$PINS$GT$$GT$::write_without_stop::h99cb76b55b93dcc7 (self=0x20004f34, addr=<optimized out>, bytes=...)
at /home/texitoi/.cargo/git/checkouts/stm32f103xx-hal-7d0db38b85df9102/3a8d64d/src/i2c.rs:374
#2 0x080012fa in _$LT$stm32f103xx_hal..i2c..BlockingI2c$LT$stm32f103xx..I2C1$C$$u20$PINS$GT$$u20$as$u20$embedded_hal..blocking..i2c..WriteRead$GT$::write_read::h949b7ab3b3bfbb31 (
self=<optimized out>, addr=118, bytes=..., buffer=...)
at /home/texitoi/.cargo/git/checkouts/stm32f103xx-hal-7d0db38b85df9102/3a8d64d/src/i2c.rs:461
#3 0x08000bb6 in _$LT$bmp380_test..bmp280..BME280$LT$I2C$C$$u20$D$GT$$GT$::read_register::hd6e0c68d133d7431 (self=<optimized out>, register=244) at src/bmp280.rs:436
#4 _$LT$bmp380_test..bmp280..BME280$LT$I2C$C$$u20$D$GT$$GT$::configure::h0afac1b198eb57dc (
self=<optimized out>) at src/bmp280.rs:365
#5 _$LT$bmp380_test..bmp280..BME280$LT$I2C$C$$u20$D$GT$$GT$::init::h4bc76ebdfc030868 (
self=0x20004f34) at src/bmp280.rs:330
#6 0x08000576 in bmp380_test::main::h02b25e9e644215b2 () at src/main.rs:47
#7 0x08000790 in main () at <entry macros>:3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's working using Ratio16to9
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BlockingI2c
trait actually assumes you have DWT enabled, I'll add a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be great to add an example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even better: asking for &mut std32f103xx::DWT
at the BlockingI2c
creation to enable DWT
if needed.
It'd be nice if this was merged. I currently have to use the fork because my project needs i2c. |
This PR is the most wanted feature. Even if it is not perfect right now, I think we need to merge it as is because it will be better that the current state of the crate. @therealprof what do you think of merging it, and follow up PRs can then improve it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, let's get this moving finally.
Could somebody with knowledge of this discussion create a tracking issue listing the items to be improved? |
WIP, only 7-bit address standard mode writes have been tested