MSP430 I2C, and Trusting ChatGPT Wasted Weeks of My Life
Arduino is decent, but I had a craving to climb the hacker hierarchy and impress the faceless critics online. So, I decided to implement a microcontroller (MCU) from the ground up. Jokes aside, I was hoping this switch would grant me some flexibility in terms of cost management and improve PCB design to boost performance. After weighing my options, I went with the MSP430 series, specifically the MSP430F2132.
TL;DR;
Here’s the Github link with the right sequence
Hardware Communication
External devices, like sensors and controllers, that aren't part of the MCU, are referred to as peripherals. There's a whole host of protocols designed to facilitate communication between the MCU and these peripherals, including, but not limited to, I2C, UART, and SPI. Each of these has its own unique set of pros and cons.
In communication, there are two major components: the clock and data. The clock helps synchronize data. It essentially tells the devices how the bits should be counted, and this can be achieved either through a pin that supplies a steady pulse, or a rate that is agreed upon beforehand. With the clock signal, a data string like '0101 1010' would look something like this:
Brief I2C Introduction
In I2C communication, there are masters and slaves. The master provides the clock signal and initiates the communication. The slave, in turn, acknowledges and fulfills the request.
The MSP430F2132 is set as the master, and I've used the BMP180 barometric pressure/temperature sensor as the slave for a baseline. The BMP180 is popular and extensively documented, so I can confirm that the code functions properly before deploying it on other devices. Here's my setup:
I2C Protocol In a Nutshell
There are other variations of I2C communication designed to enhance speed and support a greater number of devices. Below are just the basics:
Send Start Condition: The data pin goes low before the clock pin, signaling that data transmission has begun.
Send Slave Address: This consists of nine bits of data. The first seven bits make up the slave address. The eighth bit indicates the state - read (1) or write (0). The ninth bit shows whether or not the data has been recognized by the slave.
Send/Read Data: Once the start condition has been sent, it becomes possible to read from and write to the register. The register address consists of eight bits, and just like with the start condition, the ninth bit is the ACK (Acknowledge) bit.
Send Stop Condition: The data pin goes high after the clock, signaling that the data transmission has come to a halt.
To put everything together, this is what it might look like:
When the Nightmare Began
The slave address for the BMP180 is 0x77. To read the temperature, the MCU needs to write 0x2E into the BMP180’s 0xF4 register. Once that's done, the MCU can request one byte of data from 0xF6 and another byte from 0xF7. These two bytes of data correspond to the temperature values. To convert this data into Celsius, the MCU must read the calibration data from several registers and use this data for the conversion.
A portion of the calibration data comes from registers 0xAA and 0xAB. The corresponding values for these registers are 0x1E and 0x0D, as confirmed using a Saleae logic analyzer. However, when I read from the MCU's I2C receive buffer, the values kept flipping. This marked the start of a long and arduous debugging journey.
My intuition for operations, which has been reinforced by ChatGPT and several firmware blogs, leans towards the sequence of "start, request, read, stop". My code implementation looked like this:
Here’s just a brief compilation of how ChatGPT reinforced the wrong implementation:
For two weeks, I was completely absorbed in trying to solve this problem. Since I believed the issue wasn't with the read sequence, I experimented with other variations - using interrupts, adding delays between reads, and pretty much every other variation imaginable.
After feeling somewhat defeated, I decided to give the datasheet another go. As anyone might have guessed, the correct answer is always hidden in the fine print of the datasheet. It states, 'If a master wants to receive a single byte only, the UCTXSTP bit must be set while the byte is being received.' This implies that I need to send a stop condition before the receive buffer is full. I just needed to swap one 2 lines of code in my read function:
Takeaway Lesson
Beyond admitting that I felt somewhat foolish, I learned that reading the manufacturer's datasheet, while not the most thrilling part of engineering, is the closest thing to the source of truth. ChatGPT is a good tool, but we have to take its response with a grain of salt. Not to revel too much in schadenfreude, but I did find some comfort in seeing many others, including a 'super smart CTO' from one of the companies I worked for, struggle with the same issue. This might not be the most captivating article I've ever written, but I thought it might help someone who's in the same struggle boat.