Hello!
I'm having trouble with my I2C slave code for both a slave receiver and slave writer. I'm pretty new to Arduino software development, although I do have a background in embedded firmware with C. I'm trying to use the code below to test my master transmitter/receiver code for the ATmega644.
I've already tried testing the program and even tried to see a waveform appear on the oscilloscope, but I don't see anything. Is the implementation below faulty? I've tried looking through other questions relating to slave transmitter/receiver, but I couldn't find anything helpful. Any help would be appreciated.
#include <Wire.h>
volatile byte data[4] = {0, 0, 0, 0}; // dummy register for i2c to access
volatile byte addr_pointer = 0xFF; // points to address in dummy buffer
void setup() {
digitalWrite(SDA, LOW); // turn off internal pull-up resistors (they already exist externally)
digitalWrite(SCL, LOW); // turn off internal pull-up resistors (they already exist externally)
Wire.begin(0x20); // begin device as slave @ address 0x20
Wire.onRequest(requestEvent); // master receive event (read)
Wire.onReceive(receiveEvent); // master transmit event (write)
}
void loop() {
delay(2000);
for (int i = 0; i < 4; i++) {
Serial.print(data[i], HEX);
}
Serial.println();
}
void requestEvent() {
while (Wire.available()) { // loop until master sends NACK
Wire.write(data[addr_pointer]); // send data to I2C bus
addr_pointer = addr_pointer + 1; // increment register value
}
}
void receiveEvent(int howMany) {
addr_pointer = Wire.read(); // get base address from I2C bus
while(Wire.available() > 0) { // loop through all data (should account for repeated restart?)
data[addr_pointer] = Wire.read(); // receive and store data in dummy reg
addr_pointer = addr_pointer + 1; // increment register pointer
}
}
Here is the Master Transmitter firmware (in C).
/*
* File: i2c.c
* Author: Shadi Zogheib
*
* Created on July 9, 2021, 1:51 PM
*/
#include <avr/io.h>
#include <util/twi.h>
#include "i2c.h"
#define CLEAR_TWINT (TWCR = (1 << TWINT) | (1 << TWEN))
#define SET_ACK (TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN))
#define SET_NACK (TWCR = (1 << TWINT) | (1 << TWEN))
#define I2C_START (TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN))
#define I2C_STOP (TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN))
/* Initialize I2C protocol parameters */
void i2c_init() {
TWBR = 8; // Set I2C clock to 50kHz for TWPS = 0
TWCR = (1 << TWEN); // Enable I2C protocol on MCU pins
}
/* This function is called when data must be written
* to the I2C bus. The parameters are:
* data <-- pointer to data array
* ack <-- ACK value for the specific operation
* nack <-- NACK value for the specific operation
* Returns 0 for NACK, 1 for ACK. */
int8_t load_reg(int8_t* data, uint8_t ack, uint8_t nack) {
// Clear TWINT flag
CLEAR_TWINT;
// Load register with data to transmit
TWDR = *data;
// Wait for transmission completion
while ((TW_STATUS != ack) && (TW_STATUS != nack));
// Check if NACK is received, return 0 if NACK
if (TW_STATUS == nack)
return 0;
return 1;
}
/* This function is called when data must be read
* from the I2C bus. The parameters are:
* data <-- pointer to data array
* last_byte <-- last byte indicator */
void load_data(int8_t* data, uint8_t last_byte) {
// If last byte, set NACK, otherwise, set ACK for more data (and clear TWINT)
if (last_byte)
SET_NACK;
else
SET_ACK;
// Wait for data to fully arrive over I2C bus
while ((TW_STATUS != TW_MR_DATA_ACK) && (TW_STATUS != TW_MR_DATA_ACK));
// Load data field with data from TWDR register
*data = TWDR;
}
/* Write byte(s) to the specified device's register.
* This function takes the following parameters:
* device <-- I2C slave ID
* addr <-- register to access
* data <-- 1-2 bytes to transmit
* valid <-- defines valid data bytes:
* (0) Transmit data to lower byte of register
* (1) Transmit data to upper byte of register
* (2) Transmit data to both bytes of register
* Ack_status flag indicates if function should continue. */
void i2c_write(int8_t device, int8_t addr_base, int16_t data, uint8_t valid) {
// Safeguard the rest of the function
if (valid > 2)
return;
int8_t ack_status;
int8_t sla = device + TW_WRITE;
// Update address pointer
int8_t addr = addr_base + (int8_t)(valid & 1);
// Start I2C communication process
I2C_START;
while (TW_STATUS != TW_START);
// Load TWDR register with slave ID
ack_status = load_reg(&sla, TW_MT_SLA_ACK, TW_MT_SLA_NACK);
// Load TWDR register with register base address
if (ack_status)
ack_status = load_reg(&addr, TW_MT_DATA_ACK, TW_MT_DATA_NACK);
// Load TWDR register with only the valid data to be written
int8_t byte;
if (ack_status && !(valid & 1)) {
byte = (int8_t)(data & 0x00FF);
ack_status = load_reg(&byte, TW_MT_DATA_ACK, TW_MT_DATA_NACK);
}
if (ack_status && (valid > 0)) {
byte = (int8_t)(data >> 8);
ack_status = load_reg(&byte, TW_MT_DATA_ACK, TW_MT_DATA_NACK);
}
// Stop I2C communication process
I2C_STOP;
}
/* Incremental read from the specified device's register.
* This function takes the following parameters:
* device <-- I2C slave ID
* addr <-- register to access
* data <-- byte to return
* data_size <-- size of data
* Ack_status flag indicates if function should continue. */
void i2c_read(int8_t device, int8_t addr, int8_t* data, uint8_t data_size) {
// Safeguard the rest of the function
if (data_size == 0)
return;
int8_t ack_status;
int8_t sla = device + TW_WRITE;
// Start I2C communication process
I2C_START;
while (TW_STATUS != TW_START);
// Load TWDR register with slave ID (master transmitter mode)
ack_status = load_reg(&sla, TW_MT_SLA_ACK, TW_MT_SLA_NACK);
// Load TWDR register with register base address
if (ack_status)
ack_status = load_reg(&addr, TW_MT_DATA_ACK, TW_MT_DATA_NACK);
// Do not continue if NACK is received
if (!ack_status) {
I2C_STOP;
return;
}
// Perform repeated start
I2C_START;
while (TW_STATUS != TW_REP_START);
// Load TWDR register with the same slave ID (switch to master receiver mode)
sla = device + TW_READ;
ack_status = load_reg(&sla, TW_MR_SLA_ACK, TW_MR_SLA_NACK);
// Load data field with data from TWDR register
uint8_t i = 0;
uint8_t last_byte;
while ((i < data_size) && (ack_status)) {
last_byte = ((i) == (data_size - 1)) ? (1) : (0);
load_data(&data[i], last_byte);
i = i + 1;
}
// Stop I2C communication process
I2C_STOP;
}