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

cpu/stm_common: Refactor and fix implementation for i2c_2 #10608

Merged
merged 1 commit into from
Dec 20, 2018

Conversation

MrKevinWeiss
Copy link
Contributor

Contribution description

This PR fixes many bugs discovered by the HiL.

  • Refactors i2c_2 to match the structure of i2c_1 better.
  • Corrects functionality issues (such as nack recovery).
  • Allows the common implementation of read_regs and write_regs.
  • Documents constraints of hardware.
  • Reduces code size (about 180 bytes)
  • Matches error messages with API.

Testing procedure

Test on stm32f1 f2 f4 or l1 board
One can run the robot-test from the HiL branch or run term in tests/periph_i2c/
Or run manually against your favorite i2c device. No everything will be supported but everything should at least give correct error messages and not lock up the bus.
*If nucleo-f103rb is used it needs external pullups.

  1. Basic read regs with 1,2,3 bytes
  2. Basic write regs with 1,2,3 bytes
  3. Test split frame (NOSTOP, NOSTOP|NOSTART, NOSTART) reads
  4. Test split frame writes
  5. Repeated start write (write_bytes+NOSTOP, write_bytes)
  6. Getting an address nack then read a proper register.

Issues/PRs references

helps with some stm32 based boards for #3366
checks some boxes in #9518

@MrKevinWeiss MrKevinWeiss self-assigned this Dec 13, 2018
@MrKevinWeiss MrKevinWeiss added Type: bug The issue reports a bug / The PR fixes a bug (including spelling errors) Platform: ARM Platform: This PR/issue effects ARM-based platforms Impact: minor The PR is small in size and might only require a quick look of a knowledgeable reviewer CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Area: cpu Area: CPU/MCU ports labels Dec 13, 2018
Copy link
Member

@maribu maribu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

 cpu/stm32_common/include/periph_cpu_common.h |   5 +
 cpu/stm32_common/periph/i2c_2.c              | 383 ++++++++++-----------------
 2 files changed, 143 insertions(+), 245 deletions(-)

Very nice! :-)

_i2c_init(i2c, i2c_config[dev].clk, ccr);

#if defined(CPU_FAM_STM32F4)
/* make sure the analog filters don't hang -> see errata sheet 2.14.7 */
if (i2c->SR2 & I2C_SR2_BUSY) {
DEBUG("[i2c] init: line busy after reset, toggle pins now\n");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you remove all the DEBUG() messages from i2c_init()? They don't produce any machine code unless ENABLE_DEBUG is set to true. So I don't see any harm in having them, and they could be extremely valuable when hunting a bug during I2C initialization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because if you do enable debug it doesn't actually work and seems to screw up the shell, I think it has something to do with the uart being initialized after the i2c but I didn't really look into it. I wrote something above the enable debug to indicate that. It may only have been for the nucleo-f103rb though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the problem is also there with F4 and the workaround could be, as you noticed, to initialize uart before i2c. That's what I did when debugging this driver some months ago.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice for debugging to have stdio working as soon as possible during boot. But that is unrelated to this PR. Keeping those DEBUG statements will bite the next person trying to debug I2C unless someone would change the boot up process to initialize stdio sooner. So I agree that removing them is the better alternative.

Copy link
Contributor Author

@MrKevinWeiss MrKevinWeiss Dec 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, I opened an issue #10614 to document this concern. I also did write that comment explaining it, so hopefully it would be more a nibble than a bite.

@@ -187,9 +187,6 @@ int i2c_release(i2c_t dev)
{
assert(dev < I2C_NUMOF);

uint16_t tick = TICK_TIMEOUT;
while ((i2c_config[dev].dev->SR2 & I2C_SR2_BUSY) && tick--) {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let my try to understand this.

  • Previously i2c_release() would have for TICK_TIMEOUT = 0xffff loop iterations for the device to become idle. If the devices remained busy, the bus was released anyway.
  • i2c_release() should only be called from the thread currently that has the mutex locked via i2c_acquire().
  • Any data transfer is done blocking, so the thread that acquired the device will only be able to call i2c_release(), when the bus is idle anyway
    • Thus, this loop should never spin at all and can be safely removed. Right?

And also: If the relatively slow I2C bus was busy doing some reasonable stuff (e.g. a data transfer), a delay of 0xffff loop iterations won't be enough for that transfer to complete anyway. So there would be no benefit of waiting in any case, right?

If my understanding is right, I agree that removing those line is a good thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the bus was still transferring the delay should actually allow it to finish (depending on clock speeds). However, this implementation ensures that, when an i2c stop is issued, the function will only end if the bus is free. This removes the requirement to check if the line is busy before doing anything (which is needed for repeated start calls).

So yes, it is essentially not doing anything valuable (assuming the api is being followed correctly) and thus should be removed.

@MrKevinWeiss
Copy link
Contributor Author

@maribu Thanks for the review, I did want some feedback on the removing of the busy timeout in the release function. It is a good clarification.

@maribu
Copy link
Member

maribu commented Dec 13, 2018

I can confirm that the SHT3X driver (using I2C) on the bluepill (STM32F103) just works fine with your I2C implementation :-) The driver does not use every function the I2C does define (e.g. the convenience functions for register access are not used) - so this test does not cover all your code.

@MrKevinWeiss
Copy link
Contributor Author

Thank you very much for testing, I can also volunteer @leandrolanzieri for more test too 😆

@MrKevinWeiss
Copy link
Contributor Author

if the stm32f1 fix gets merged I will have to rebase again on top of that. Just a note to self.


#define ENABLE_DEBUG (0)
/* DEBUG statements in the init causes the code to lock up */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be removed if #10615 is merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, will do if it is merged before this one, otherwise I will remove it with the other PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MrKevinWeiss any chance to remove this today? And then we can merge.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought I forgot something, done!

@leandrolanzieri
Copy link
Contributor

Tested on nucleo-f410rb and works as intended:

  • Basic read regs with 1,2,3 bytes
  • Basic write regs with 1,2,3 bytes
  • Test split frame (NOSTOP, NOSTOP|NOSTART, NOSTART) reads
  • Test split frame writes
  • Repeated start write (write_bytes+NOSTOP, write_bytes)
  • Getting an address nack then read a proper register.

@leandrolanzieri leandrolanzieri added the Reviewed: 3-testing The PR was tested according to the maintainer guidelines label Dec 14, 2018
@MrKevinWeiss
Copy link
Contributor Author

@aabadie or @maribu, well now that all the testing has been done...

clocked out on the line however they get ignored in the firmware.*/
if ((i2c->SR1 & I2C_SR1_RXNE) && (length == 1)) {
((uint8_t*)data)[i] = i2c->DR;
ret = _stop(i2c);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be made return _stop(i2c); because ret is returned anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, the less lines the better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@aabadie
Copy link
Contributor

aabadie commented Dec 18, 2018

Can you provide the corresponding shell commands of tests/periph_i2c for testing:

1. Basic read regs with 1,2,3 bytes
2. Basic write regs with 1,2,3 bytes
3. Test split frame (NOSTOP, NOSTOP|NOSTART, NOSTART) reads 
4. Test split frame writes 
5. Repeated start write (write_bytes+NOSTOP, write_bytes)
6. Getting an address nack then read a proper register.

I'd like to give a try to this one on nucleo-l152re

@MrKevinWeiss
Copy link
Contributor Author

@aabadie The thing is you need a sensor or something to interface to. If you have a nucleo-103rb you can flash the PHiLIP firmware, drag and drop the firmware and hook up the scl and sda pins.

If you want to use a sensor then let me know what it is and I can tell you the commands.

@MrKevinWeiss
Copy link
Contributor Author

We can generalize certain things for PHiLIP = 85 and = 152, for a sensor is the address of the sensor and would be a register in the sensor (preferable a few) that is readable and writable. Then we could go:

i2c_acquire 0
i2c_read_regs 0 <addr> <reg> 1 0
i2c_read_regs 0 <addr> <reg> 2 0
i2c_read_regs 0 <addr> <reg> 3 0
i2c_write_regs 0 <addr> <reg> 0 42
i2c_write_regs 0 <addr> <reg>+1 0 41 40
i2c_write_regs 0 <addr> <reg>+3 0 39 38 37
/* To confirm read that the register have changed
i2c_read_regs 0 <addr> <reg> 6 0

i2c_read_bytes 0 <addr> 1 4
i2c_read_bytes 0 <addr> 1 12
i2c_read_bytes 0 <addr> 1 8

i2c_read_bytes 0 <addr> 1 4
i2c_read_bytes 0 <addr> 1 12
i2c_read_bytes 0 <addr> 1 8

i2c_write_bytes 0 <addr> 4 <reg>
i2c_write_bytes 0 <addr> 12 35
i2c_write_bytes 0 <addr>  8 34
/* To confirm read that the register have changed
i2c_read_regs 0 <addr> <reg> 2 0

/* Repeated start write */
i2c_write_bytes 0 <addr> 4 <reg>
i2c_write_bytes 0 <addr> 0 33
/* This shouldn't fail but you may not get the correct information unless the sensor supports it */

i2c_read_regs 0 <addr>+1 <reg> 1 0
/* expect nack */
i2c_read_regs 0 <addr> <reg> 1 0 
/* expect success */

That is the basics of it. There is also a test.py in the folder but to use it you should pip3 install riot_pal and there may be some other issues like setting the python path correctly. Or you can get robot framework pushed though and just call

BOARD=nucleo-l152re PHILIP_PORT=<port of PHiLIP> make robot-test -C tests/periph_i2c

Copy link
Contributor

@aabadie aabadie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on a nucleo-l152re and it works. I found small code style issues and I also had an assertion error with one of the commands you gave in your previous comment. See below.

static inline int _stop(I2C_TypeDef *dev);
static inline int _wait_ready(I2C_TypeDef *dev);
static int _start(I2C_TypeDef *dev, uint8_t address_byte, uint8_t flags,
size_t length);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style: this should be aligned with I2C_TypeDef.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup yup.

int i2c_write_regs(i2c_t dev, uint16_t address, uint16_t reg, const void *data,
size_t length, uint8_t flags)
static int _start(I2C_TypeDef *i2c, uint8_t address_byte, uint8_t flags,
size_t length)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style: this should be aligned with I2C_TypeDef.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks will do

assert(i2c != NULL);
assert((i2c->SR2 & I2C_SR2_BUSY) || !(flags & I2C_NOSTART));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a failed assertion on this line when brainlessly testing using tests/periph_i2c on nucleo-l152re with an hts221 sensor plugged. With the following commands list:

> i2c_acquire 0
> i2c_read_bytes 0 0x5f 1 12

as suggested in #10608 (comment)

Would it make more sense to have a return with a dedicated error value instead of crashing the application in this case ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup I don't mind that, I was trying to keep the binary size down but I can see how that would be annoying when using it (it happened to me a few times)

@MrKevinWeiss
Copy link
Contributor Author

@aabadie All your comments have been addressed!


if ((flags & I2C_REG16) || (flags & I2C_ADDR10)) {
if ((flags & I2C_ADDR10) ||
(!(i2c->SR2 & I2C_SR2_BUSY) && (flags & I2C_NOSTART)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing ) here, gives compilation error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...shhhhhh, nothing happened here:sweat_smile:

Refactors i2c_2 to match the structure of i2c_1 better.
Corrects functionality issues.
Allows the common implementation of read_regs and write_regs.
Documents constraints of hardware.
Matches error messages with API.
Copy link
Contributor

@aabadie aabadie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and tested. Thanks for updating @MrKevinWeiss.

ACK

@aabadie aabadie merged commit ab061c4 into RIOT-OS:master Dec 20, 2018
@MrKevinWeiss
Copy link
Contributor Author

Thanks to everyone for reviewing, testing & merging!

@MrKevinWeiss MrKevinWeiss deleted the pr/fix/stmi2c2 branch January 8, 2019 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: cpu Area: CPU/MCU ports CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Impact: minor The PR is small in size and might only require a quick look of a knowledgeable reviewer Platform: ARM Platform: This PR/issue effects ARM-based platforms Reviewed: 3-testing The PR was tested according to the maintainer guidelines Type: bug The issue reports a bug / The PR fixes a bug (including spelling errors)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants