AVR #5. I2C Master PART 1 || Configuration

This is the 5th tutorial in the AVR series using the xplained mini development board, and today we will start with the I2C Master series. In this series, we will develop the code for the I2c master across few tutorials, covering the Write and Read operations to the slave device.

In this particular tutorial we will see the configuration to initialize the I2C bus, and how to write the data to the slave device. To keep the things simple and this tutorial as small as possible, I am not going to connect any slave device to the bus. We will simply check using the oscilloscope, if the master is able to transmit the data. Later in the upcoming tutorials, we will connect an actual slave device and control it using the I2C.


The I2C required only 2 wires to communicate to another device. The 2 lines are named as SCL (Clock) and SDA (Data). The ATtiny817 has dedicated pins PA1 and PA2 for the I2C communication.

As I mentioned I am not going to connect any slave device and instead connect the oscilloscope to check the data sent by the master. Below is the image showing the connection between the dev board and the logic analyzer.

i have connected the SDA (PA1) to the channel 1 and SCL (PA2) to the channel 0 of the analyzer.

Initialize the I2C

We will start with the host Control A Register (MCTRLA). I will only cover the data (bits) that we will modify in these registers. The rest of the bits are kept to their default states.

The ENABLE bit is used to enable the TWI, so we will set it to 1.

void I2C_Init (void)
	TWI0.MCTRLA = TWI_ENABLE_bm;  // EN -> 0x01

Next is the host control B Register (MCTRLB)

The MCMD (bits 1:0) are the command bits. The host can modify these bits to issue commands like stop, repeated start, ACK or NACK. We will use these command bits later in our code, but we will keep them to 0 during the initialization.

The ACKACT, Acknowledge Action bit is used to store the response (ACK or NACK), the command bits will send to the slave. When we will set the command bits to send the response to th slave, the response will be fetched from this ACKACT bit. During the initialization, we need to set this bit to 1.

TWI0.MCTRLB = TWI_ACKACT_NACK_gc;  // NACK enabled -> 0x04

Next is the Baud Register (MBAUD). The value in this register determines the frequency of the I2C clock (Fscl).

The value for the Baud Register for a particular I2C clock frequency can be calculated using the formula below.

In the above formula, we know the fCLK_PER (Peripheral Clock) = 5MHz. We want the fSCL (I2C Clock) of 100 KHz. The value of tR can be found in the Electrical characteristics of the controller, and for the fSCL <= 100KHz, tR = 1000nS.

Once we put all the values in the formula, the Baud Register value is 17.5 as shown below.

We can’t use the decimal value for a 8 bit Register, so let’s use the value 18.

TWI0.MBAUD = 18;

Now we need to set the Respective pins for the I2C, and this can be done in the Port Multiplexer (PORTMUX).

The Control B Register (CTRLB)controls the multiplexing for different peripherals. To set the respective pins for the TWI communication, we need to write a 1 to the 4th position in this register.

PORTMUX.CTRLB = 1<<4;  // TWI0 communication

The final initialization code is shown below.

void I2C_Init (void)
	TWI0.MSTATUS = 0x00;  // clear status register
	TWI0.MBAUD = 18;
	PORTMUX.CTRLB = 1<<4;  // TWI0 communication

Master Writes Data

Now that the I2C is initialized, the master will transmit the data to the slave. As I mentioned in the beginning, today’s tutorial does not have any slave connected to the bus, so we will only check if the master is able to send the data. We will monitor the bus with an oscilloscope or logic analyzer.

uint8_t I2C_Write (uint8_t slaveAddr7b, uint8_t *data, uint8_t size)

The Write functions takes 3 parameters :

  • @slaveAddr7b, The 7 bit slave address.
  • @*data, The pointer to the data array that need to be transmitted.
  • @size, The size of the data.

Below the image shows the I2C frame. The master generates the START condition and then send the slave address followed by the Read/Write bit.

The ATtiny817 does not have the means to generate a separate Start condition. Basically when the master writes the address, the start is generated automatically.

		uint8_t write_address = (slaveAddr7 << 1) & 0xFE;
		TWI0.MADDR = write_address;
		while (((TWI0.MSTATUS)&TWI_WIF_bm) == 0);  // wait for the transmission to complete

Since the master is performing the write operation, the R/W bit will be set to 0. So we will shift the 7bit slave address to the left by 1 place and add the 0 to the lsb.

Then write the Address to the Address Register (MADDR), and wait for the write operation to complete.

The Write Interrupt Flag bit in the status register is set when the ongoing write (data/address) is complete. We wait for this bit to set after performing any type of write to the slave device.

After the master send the address on the bus, the respective slave (whose address matches with the address sent by the master) sends the ACK response.

This response is received in the 4th bit of the status register (MSTATUS).

If the address matches with the slave, the slave sends the ACK response and this bit will be reset to 0. So we will check if this bit before writing the data to the slave.

		if (((TWI0.MSTATUS)&(TWI_ACKACT_bm)) == 0)  // if received ACK from slave

If the slave ACKnowledged the address, the master will start transmitting data. After each data byte received, the slave will send an ACK response. Once the master has transmitted all the data bytes, it will send the STOP condition.

		if (((TWI0.MSTATUS)&(TWI_ACKACT_bm)) == 0)  // if received ACK from slave
			uint8_t indx = 0;  // keep track of bytes written
			while ((((TWI0.MSTATUS)&(TWI_ACKACT_bm)) == 0) && (indx < size))  // if recvd ACK from slave and we still have the data to write

				TWI0.MDATA = data[indx++];  // copy data to the data register
				while (((TWI0.MSTATUS)&(TWI_WIF_bm)) == 0);  // wait for the transmission to complete
				if (indx == size)  // all the data has been transmitted
					TWI0.MCTRLB = TWI_MCMD_STOP_gc;  // send stop 
					return 0;  // success

The indx variable is defined to keep track of number of bytes written to the device. The write is performed in a while loop, which will only run if the master receives the ACK from slave and the indx value is less than the size parameter. This check is performed after writing each byte to the slave device.

To write the data, we copy the data byte into the Data Register (MDATA), then wait for the WIF (Write Interrupt Flag) in the status register to set. This indicates that the data byte has been transmitted successfully.

Once the value of the indx variable is equal to the size parameter, which means all the data bytes has been transmitted, the master will send a STOP condition by modifying the command bits (MCMD) of the Control B Register.

The main code

Below is the code for the main function.

uint8_t datatosend[] = "Hello world";

int main(void)

    I2C_Write(0x15, datatosend, 11);

The dattosend array contains the data we will send to the slave.

In the main function, we will first initialize the clock and then initialize the I2C. I have provided a delay of 100ms for the I2C to initialize properly.

In the while loop we will transmit the data every 1 second. The slave address, 0x15, is just a random address as there is no slave device connected to the bus.


Below the image shows the I2C frame captured by the Logic analyzer.

the frame shows the data as follows:

  • The START bit is represented by the Green dot.
  • The master then send the device address (0x15) with a write request.
  • As there is no device on the bus, it receives the NACK response.
  • The master continues to send the data (“Hello world”) and receives the NACK response after every byte.
  • In the end the master send the STOP condition, which is represented by the orange dot.

Check out the Video Below


You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.


Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add controllerstech.com to your ad blocking whitelist or disable your adblocking software.