I’ve been trying to read the tcn75 and tmp102 temp sensors with a pic16f877a microcontroller for quite some time and thus decided to write down my findings. Although the I²C interface is pretty mature, the PIC microcontroller have dedicated port for it it and there is plenty of documentation out there on the topic it was surprisingly difficult for me to get things running.
Part of the problem is that I’ve decided to do it in assembly, so that I can exactly see what is going on under the hood. Also it seems that the microchip datasheets are not very newbie friendly either 🙂
Table of Contents
The I²C interface was invented by Philips Semiconductor in 1982 for communication between integrated circuits – hence the name Inter-Integrated Circuit or I-squared-C. It is a synchronous interface – meaning it has both data and clock lines and the data line is synchronised with the clock. The standard defines many conditions which together comprise a protocol for communication. In I²C all entities (integrated circuits) are connected to a common two-wire bus. There is a master (or multiple masters) which always initiates the communication and slave (or multiple slaves) which responds to the master. It is important that a transfer of data is always initiated by a master. In this article I will speak about only a simple case where there are only one master and one slave on the bus. The master is a PIC16F877A microcontroller and the slave is a temperature sensor: TCN75 or TMP102.
As I already said there are many conditions in the I²C standard: idle, a start, a stop, repeated start, acknowledge, not acknowledge, data transfer. Unlike a asynchronous serial communication where signals are strictly defined in terms of length and any deviation will lead to incorrect data at the receiver side, in the synchronous case of I²C the data is transferred along with a clock signal. This means that the transfer rate may vary in a conversation without spoiling the data. So the conditions are defined as changes in both the data and clock lines.
A conversation always starts with the master asserting a start condition. This is a transition of the data line from high to low while the clock line stays high. It is a good time to say that the data line is abbreviated SDA and the clock line SCL. Let’s see how it looks like (from the microchip’s datasheet):
You can see clearly the start condition – SDA transition from high to low while the SCL is high.
It is important that before a start is issued the bus is idle. This simply means that both the SDA and SCL lines are high. This is actually the normal state of the bus because by design both lines should be connected to the VCC through a pull-up resistors. So when there is no activity both lines are high due to the pull-ups.
When a communication has finished the bus should be left again in idle state. This is why we have the stop condition which is defined as low to high transition of the SDA line while the clock is high:
Repeated Start or Restart Condition
Since in I²C many devices can be connected to the bus at the same time whenever a stop condition occurs the bus is idle and another transfer may begin. What if we want to send more data without releasing the bus? For example if we are writing to an EEPROM memory? Well in this case we can use the repeated start condition. It occurs when the SDA line goes high while the SCL stays low, then the SCL goes high and the SDA goes low while the SCL still stays high. Despite at some point of time both lines are high (which means idle) the bus is not released since there was no stop condition issued on the bus prior to the repeated start one. This way the master still holds the bus and can transmit more data:
Acknowledge – Not Acknowledge Conditions
When the master transmits one byte to the slave the later responds with an acknowledge condition. At the end of the eight bit transfer the slave is allowed to drive the SDA line low which indicates an acknowledge condition or ACK. This is an “active choice” since if the slave doesn’t do anything, the bus will stay naturally high (because of the pull-up resistors) which in turn means Not Acknowledge or NACK.
Last but not least is the actual data transfer. To transmit data, the SDA line is driven either high or low – high is logical 1 and low is logical 0. The SDA line should be held that way for one clock pulse with the raising clock including. In other words if we want to send a logical 1 we should pull the SDA line high, after that pull the SCL line high while keeping the SDA high, then wait for one clock pulse and finally while still keeping SDA high release the SCL line. While the SCL is low we change to the next bit and repeat the procedure until all eight bits are transferred. After that as we already saw the slave has one bit time to acknowledge the reception.
Let’s now put the theory into practice. We will be reading the temperature from a TMP102 sensor. It is a nice temp sensor which can be accessed via an I²C interface. It has several registers inside, of which we are interested in the temperature one – holding the current temperature. The TMP102 sensor has one address pin which allows multiple sensors to be connected to the same bus. The address changes based on what the pin is connected to. For example if the address pin is connected to ground then the sensor has address 00 if the pin is connected to VCC then the sensor is located at 01 and so on. Looking at the specification we can see how a typical communication looks like.
- First the master should wait for idle bus
- Then it asserts a start condition
- After that the master sends 7bit sensor address (in our case this would be 1001000 in tmp 102 the firs part of the address is always 10010) + an eighth bit indicating whether we are reading from or writing to the sensor. In this case we send a zero meaning we are writing to the sensor. We are writing the identifier of the register we are going to read – the temperature register.
- The master checks for an acknowledgement from the slave
- Then sends another byte – the actual identifier of the register. The temp register is at 0x00 address
- The master checks for acknowledgement from the slave again
- And then asserts a stop condition on the bus
Now the second part of the communication – getting the temperature
- The master sends again a start condition
- Then the 7 bit address of the slave + the eight bit this time flipped to 1 – meaning we are reading from the slave
- Wait for acknowledgement from the slave
- The master turns now into a receiver and reads a byte from the slave – this is the most significant byte holding the temperature
- The master sends an acknowledgement to the slave
- The slave sends the second byte holding the temperature
- The master acknowledges it
- The master terminates the communication by asserting a stop condition.
Next: How to implement it in assembly is described in the second part of this article.