Welcome to the blog post I wish I had read two days ago.
From now on I will make it a rule to get one peripheral working at a time. Why? Because it turns out if you don’t; they might interfere with each other in a bad way.
This particular one saved us a PCB spin. If I did not get the devkit, this is one of the things I could not foresee. That’s why my new rule is to test one peripheral at the time, then together!
Maybe it is getting a screen working, reading from an SD card or simply getting I2C working. Get them working one at a time, then together. Oh, and did I say I2C is simple? Let me take you on my ~20 hour journey getting it to work in my setup!
This post will be written in retrospect, I’m sorry if it bores you.
Getting off to a flying start
I’m using the STM32CubeMX and its Hardware Abstraction Layers. Maybe you’ve seen some of mine for the Wiring / Arduino? Anyways, the great thing about HALs is that you can fly rather than crawl from the get-go. And so I did.
Within a few hours of receiving my devkit I had the LCD up and running. I got going with the SD card and the excellent FatFs library. I even got USB FS up and running so I could upload files to the SD card via USB. Admittedly, it’s too slow right now to really be usable for much more than a proof of concept – but I got it going!
I was on a roll!
All that cool stuff was working, even simultaneously. You see, I just added and added things to the project as I went. It was all good until I tried getting, what I thought would be really simple, I2C working. As you know by the introduction; it was a mistake to think it. Sure, I’ve gotten I2C to work many times, even on the almost exact same chip (STM32F103RCT)!
Altitude hurts on the way down
The fun was over. So. Very. Over.
I love HAL9000 as much as the next guy (I’m lying, never even seen the movie) but I got high hopes when I ran the generated initialization code from CubeMX and got the HAL_OK message back from it. I could feel the breeze. But then!
I tried firing off a few commands to the I2C device I had connected. It did not work so my debugging started. These are some things to consider when I2C is not working, let’s go!
- Do you have the wires connected correctly?
- Is there suitable pullups on the SDA and SCL lines?
- Are you using the correct address?
- Does the API shift the address you tell it?
- Does the datasheet of your device list the R/!W bit as part of the address?
- Are your devices at the same ground potential?
Needless to say, I got of these wrong at some point. To my defense though, my setup was actually correct with respect to the hardware but hours of debugging will show you; there be dragons!
After loosing a third of my hair, I decided to build a quick little I2C debugging unit with some Arduinolike nano boards. I started off by getting two of them communicating to each other. Then I tried to use the slave with my devkit setup where it is the master. It got me closer I though, because now I got the next error message in the pipeline.
Imagine my frustration. I had a working slave. I knew that for a fact.
One fact I did miss out on was that part in my I2C checklist about whether or not the API you’re using shifts the address. Turns out it did, so I eventually figured it out. Did it help? No.
The timeout usually means that the master cannot get the slave to allow a transmit, so here is where all the fun googling started…
Turns out the analog filter can hang up the I2C bus on the device I’m using. That’s no fun! Neither was it trying to parse through that list of operations you needed to do. Long story short – it fixed nothing for me.
This is the point where I instead of throwing anything around me anywhere, I sat down. Started a new project with one goal in mind. Get that damned I2C working.
- Fire up CubeMX
- Configure device with I2C1
- Export code to System Workbench
- Add a test transmit and upload code
I now knew the peripheral, and my way of using it was correct! The code, the exact same code – worked! How come?
Well, this led me on a path of much more datasheet study, and after while I got this weird feeling. The SDA pin was marked as an alternative function for the FSMC peripheral (that drives the LCD which I had working, and running). Though, my current use for the peripheral did not need – and indeed advertised that it did not use – the SDA pin (NADV). Did it use it? Yes. Did it work forcing the configuration bit off? No.
The name of this section saved my ass this time. I could simply regenerate my code to use two other pins (with no alternate function I’ll touch) and it worked! Though, I had to spend some time cleaning up all my debugging code and poking around. Even that ERRATA fix is out of my code for now.
This has been the craziest experience for me in terms of something simple blowing up in my face as something seemingly impossible!
Divide and conquer!