Hello All!
I am using an Attiny85 as an I2C driver for an MPU9150. In an effort to minimize code size I am trying to write the code in plain C.
I am using the USI_TWI_Master.c library. I have also incorporated the TinyWireM functions into my code to avoid adding the library to the makefile...
For the time being, I only have an LED to debug the attiny, but as best I can tell, any time I attempt to read from a specific register, the return value returned is the same as the register address. In this code, I am attempting to read to the WH0_AM_I register off the MPU9150 returns the register address (0x75) instead of the device address (0x68).
I have never used the USI before, and this is actually my first experience writing real C for an AVR. I have been trying to build the .hex file on my mint distro.
Here is the file containing my main function (sorry, the main function is at the bottom because I didn't want to make function prototypes). The code is configured to turn on the IO port to flash an LED if the I2C read returns the read address:
#include "USI_TWI_Master.h"
#include <avr/interrupt.h>
#define F_CPU 1000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <inttypes.h>
//Device I2C Address
#define MPU6050 0x68
#define AK8975 0x0C
//MPU6050 Configuration Registers
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define ACCEL_CONFIG 0x1C
#define INT_PIN_CFG 0x37
#define USER_CTRL 0x6A
#define PWR_MGMT_1 0x6B
#define PWR_MGMT_2 0x6C
//MPU6050 Data Registers
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define WHO_AM_I 0x75
//AK8975 Configuration Registers
#define MAG_ST1 0x02
#define MAG_ST2 0x09
#define MAG_CNTL 0x0A
//AK8975 Data Registers
#define MAG_XOUT_L 0x03
#define MAG_XOUT_H 0x04
#define MAG_YOUT_L 0x05
#define MAG_YOUT_H 0x06
#define MAG_ZOUT_L 0x07
#define MAG_ZOUT_H 0x08
#define USI_SEND 0 // indicates sending to TWI
#define USI_RCVE 1 // indicates receiving from TWI
#define USI_BUF_SIZE 16 // bytes in message buffer
// Initialize Class Variables //////////////////////////////////////////////////
uint8_t USI_Buf[USI_BUF_SIZE]; // holds I2C send and receive data
uint8_t USI_BufIdx = 0; // current number of bytes in the send buff
uint8_t USI_LastRead = 0; // number of bytes read so far
uint8_t USI_BytesAvail = 0; // number of bytes requested but not read
// Public Methods //////////////////////////////////////////////////////////////
void begin(void){ // initialize I2C lib
USI_TWI_Master_Initialise();
}
void beginTransmission(uint8_t slaveAddr){ // setup address & write bit
USI_BufIdx = 0;
USI_Buf[USI_BufIdx] = (slaveAddr<<TWI_ADR_BITS) | USI_SEND;
}
void send(uint8_t data){ // buffers up data to send
if (USI_BufIdx >= USI_BUF_SIZE) return; // dont blow out the buffer
USI_BufIdx++; // inc for next byte in buffer
USI_Buf[USI_BufIdx] = data;
}
uint8_t endTransmission(void){ // actually sends the buffer
uint8_t xferOK = FALSE;
uint8_t errorCode = 0;
xferOK = USI_TWI_Start_Read_Write(USI_Buf,USI_BufIdx+1); // core func that does the work
USI_BufIdx = 0;
if (xferOK) return 0;
else { // there was an error
errorCode = USI_TWI_Get_State_Info(); // this function returns the error number
return errorCode;
}
}
uint8_t requestFrom(uint8_t slaveAddr, uint8_t numBytes){ // setup for receiving from slave
uint8_t xferOK = FALSE;
uint8_t errorCode = 0;
USI_LastRead = 0;
USI_BytesAvail = numBytes; // save this off in a global
numBytes++; // add extra byte to transmit header
USI_Buf[0] = (slaveAddr<<TWI_ADR_BITS) | USI_RCVE; // setup address & Rcve bit
xferOK = USI_TWI_Start_Read_Write(USI_Buf,numBytes); // core func that does the work
// USI_Buf now holds the data read
if (xferOK) return 0;
else { // there was an error
errorCode = USI_TWI_Get_State_Info(); // this function returns the error number
return errorCode;
}
}
uint8_t receive(void){ // returns the bytes received one at a time
USI_LastRead++; // inc first since first uint8_t read is in USI_Buf[1]
return USI_Buf[USI_LastRead];
}
uint8_t available(void){ // the bytes available that haven't been read yet
return USI_BytesAvail - (USI_LastRead);
}
//Writes a single register on the I2C bus
uint8_t writeReg(uint8_t devAddr, uint8_t regAddr, uint8_t value){
beginTransmission(devAddr);
send(regAddr);
send(value);
return endTransmission();
}
//Reads a single register on the I2C bus
uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
uint8_t value;
beginTransmission(devAddr);
send(regAddr);
endTransmission();
beginTransmission(devAddr);
requestFrom(devAddr, 1);
value = receive();
endTransmission();
return value;
}
/*
Perfoms two's complement on an array of unsigned bytes.
Returns signed 2 byte value.
Can be used for Big & Little Endian encoded values.
*/
void twosComplement(uint8_t * in, uint8_t length, int16_t * out, uint8_t invert){
uint8_t i;
for ( i = 0; i < length; i++){
if (invert == 0)
out[i] = in[2 * i] << 8 | in[(2 * i) + 1];
else
out[i] = in[(2 * i) + 1] << 8 | in[2 * i];
}
}
int main(void){
begin();
if(readReg(MPU6050, WHO_AM_I)==WHO_AM_I){
DDRB = 0xFF;
PORTB = 0xFF;
}
return 0;
}
As far as I can tell, the attiny is running at an internal 1Mhz, although eventually I would like to use the internal 8Mhz...
Any ideas on this one??!!
although there still is no error checking so it might do the wrong thing.
It's also not a good idea set all outputs high in case you got that value, one single pin should be enough (remember that you have a device connected to some of these pins).
However, the LED still turns on, indicating that the returned value from the I2C bus is equal to register address instead of the value stored to the registers address.
Also, if I unplug power from the sensor, or unplug the SDA and SCL bus, the LED still lights.
Why would there even BE a return value if there is no activity on the bus???
As for linking the library to the my "tinyAHRS.c" file, I thought the makefile was supposed to handle this?
I was struggling to understand the avr-ar function to link the .o object files. If someone had a good example that would be great.
Regardless, if the library was not properly linked, wouldn't I get compiler errors for calling functions that are not declared inside of my .c file?
I did write a couple bash scripts to generate .hex and .o files and to upload the .hex to the attiny85 using an arduino as an ISP.
I have changed the makefile and added the softuart library so I can do a little serial-debug. Although this has only confirmed my original suspicion: the readReg() function is returning the address of the register instead of the contents of the register. Even if it the sensor is not plugged in...
#include "USI_TWI_Master.h"
#include <avr/interrupt.h>
#define F_CPU 1000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <inttypes.h>
#include "softuart.h"
//Device I2C Address
#define MPU6050 0x68
#define AK8975 0x0C
//MPU6050 Configuration Registers
#define CONFIG 0x1A
#define GYRO_CONFIG 0x1B
#define ACCEL_CONFIG 0x1C
#define INT_PIN_CFG 0x37
#define USER_CTRL 0x6A
#define PWR_MGMT_1 0x6B
#define PWR_MGMT_2 0x6C
//MPU6050 Data Registers
#define ACCEL_XOUT_H 0x3B
#define ACCEL_XOUT_L 0x3C
#define ACCEL_YOUT_H 0x3D
#define ACCEL_YOUT_L 0x3E
#define ACCEL_ZOUT_H 0x3F
#define ACCEL_ZOUT_L 0x40
#define TEMP_OUT_H 0x41
#define TEMP_OUT_L 0x42
#define GYRO_XOUT_H 0x43
#define GYRO_XOUT_L 0x44
#define GYRO_YOUT_H 0x45
#define GYRO_YOUT_L 0x46
#define GYRO_ZOUT_H 0x47
#define GYRO_ZOUT_L 0x48
#define WHO_AM_I 0x75
//AK8975 Configuration Registers
#define MAG_ST1 0x02
#define MAG_ST2 0x09
#define MAG_CNTL 0x0A
//AK8975 Data Registers
#define MAG_XOUT_L 0x03
#define MAG_XOUT_H 0x04
#define MAG_YOUT_L 0x05
#define MAG_YOUT_H 0x06
#define MAG_ZOUT_L 0x07
#define MAG_ZOUT_H 0x08
#define USI_SEND 0 // indicates sending to TWI
#define USI_RCVE 1 // indicates receiving from TWI
#define USI_BUF_SIZE 16 // bytes in message buffer
// Initialize Class Variables //////////////////////////////////////////////////
uint8_t USI_Buf[USI_BUF_SIZE]; // holds I2C send and receive data
uint8_t USI_BufIdx = 0; // current number of bytes in the send buff
uint8_t USI_LastRead = 0; // number of bytes read so far
uint8_t USI_BytesAvail = 0; // number of bytes requested but not read
// Public Methods //////////////////////////////////////////////////////////////
void begin(void){ // initialize I2C lib
USI_TWI_Master_Initialise();
}
void beginTransmission(uint8_t slaveAddr){ // setup address & write bit
USI_BufIdx = 0;
USI_Buf[USI_BufIdx] = (slaveAddr<<TWI_ADR_BITS) | USI_SEND;
}
void send(uint8_t data){ // buffers up data to send
if (USI_BufIdx >= USI_BUF_SIZE) return; // dont blow out the buffer
USI_BufIdx++; // inc for next byte in buffer
USI_Buf[USI_BufIdx] = data;
}
uint8_t endTransmission(void){ // actually sends the buffer
uint8_t xferOK = FALSE;
uint8_t errorCode = 0;
xferOK = USI_TWI_Start_Read_Write(USI_Buf,USI_BufIdx+1); // core func that does the work
USI_BufIdx = 0;
if (xferOK) return 0;
else { // there was an error
errorCode = USI_TWI_Get_State_Info(); // this function returns the error number
return errorCode;
}
}
uint8_t requestFrom(uint8_t slaveAddr, uint8_t numBytes){ // setup for receiving from slave
uint8_t xferOK = FALSE;
uint8_t errorCode = 0;
USI_LastRead = 0;
USI_BytesAvail = numBytes; // save this off in a global
numBytes++; // add extra byte to transmit header
USI_Buf[0] = (slaveAddr<<TWI_ADR_BITS) | USI_RCVE; // setup address & Rcve bit
xferOK = USI_TWI_Start_Read_Write(USI_Buf,numBytes); // core func that does the work
// USI_Buf now holds the data read
if (xferOK) return 0;
else { // there was an error
errorCode = USI_TWI_Get_State_Info(); // this function returns the error number
return errorCode;
}
}
uint8_t receive(void){ // returns the bytes received one at a time
USI_LastRead++; // inc first since first uint8_t read is in USI_Buf[1]
return USI_Buf[USI_LastRead];
}
uint8_t available(void){ // the bytes available that haven't been read yet
return USI_BytesAvail - (USI_LastRead);
}
//Writes a single register on the I2C bus
uint8_t writeReg(uint8_t devAddr, uint8_t regAddr, uint8_t value){
beginTransmission(devAddr);
send(regAddr);
send(value);
return endTransmission();
}
//Reads a single register on the I2C bus
uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
uint8_t value;
beginTransmission(devAddr);
send(regAddr);
endTransmission();
requestFrom(devAddr, 1);
value = receive();
return value;
}
/*
Perfoms two's complement on an array of unsigned bytes.
Returns signed 2 byte value.
Can be used for Big & Little Endian encoded values.
*/
void twosComplement(uint8_t * in, uint8_t length, int16_t * out, uint8_t invert){
uint8_t i;
for ( i = 0; i < length; i++){
if (invert == 0)
out[i] = in[2 * i] << 8 | in[(2 * i) + 1];
else
out[i] = in[(2 * i) + 1] << 8 | in[2 * i];
}
}
#define PRINT_NL softuart_putchar( '\n' )
// Define function to write an integer to serial out
#define PRINT_INT_BUFSIZE 16
void printInt(int32_t i) {
char buf[PRINT_INT_BUFSIZE];
int8_t sign = 1;
int8_t len = 0;
if (i < 0) { // look for the sign
sign = -1;
i = -i;
}
// fill buffer with digits (in reverse order)
do {
buf[len++] = 48 + i % 10; // ASCII digits start at 48
} while ((i /= 10) > 0 && len < PRINT_INT_BUFSIZE);
if (sign < 0) { // don't forget to add the sign
buf[len] = '-';
} else {
len--;
}
// reverse output of the buffer
do {
softuart_putchar(buf[len--]);
} while (len >= 0);
}
int main(void){
softuart_init();
softuart_turn_rx_off();
sei();
while(1){
begin();
int i = readReg(MPU6050, WHO_AM_I);
printInt(i);
PRINT_NL;
}
return 0; /* never reached */
}
Although this has only confirmed my original suspicion: the readReg() function is returning the address of the register instead of the contents of the register. Even if it the sensor is not plugged in...
That's because you don't do error handling. If you change the code for readReg() as below, you can handle the errors (but you have to insert it into the routine where I placed the comments).
uint8_t readReg(uint8_t devAddr, uint8_t regAddr){
uint8_t value = 0;
uint8_t error = 0;
beginTransmission(devAddr);
send(regAddr);
if (error = endTransmission()) {
// handle the error
}
if (! (error = requestFrom(devAddr, 1))) {
value = receive();
} else {
// error condition, act accordingly
}
return value;
}
Im also trying to get an attiny to read from the mpu6050; there is example code on the playground you use with the larger arduinos and it uses Wire.h, does anyone know how to transcribe the read write and error functions to match the TinyWire Libraries?
This is a link to my other question with the sample code. thanks