element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • About Us
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Personal Blogs
  • Community Hub
  • More
Personal Blogs
Andy Clark's Blog Azure Sphere Secure IOT - I2C Compass
  • Blog
  • Documents
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Workshopshed
  • Date Created: 23 Nov 2018 11:46 PM Date Created
  • Views 3263 views
  • Likes 10 likes
  • Comments 17 comments
  • azure sphere kit
  • azure sphere
  • microsoft
  • iot
  • microsoft azure
Related
Recommended

Azure Sphere Secure IOT - I2C Compass

Workshopshed
Workshopshed
23 Nov 2018

My experiments with U-Center and the GPS module had shown that the direction indication was not very reliable. Following a link in Kevin Saye's post, I discovered a command that should tell the orientation. But my device did not seem to be generating that.

image

http://aprs.gids.nl/nmea/#hdt

 

Reading a bit further, I realised that the compass feature was provided by a  HMC5883HMC5883 connected over I2C on a second connector. So I soldered a green wire onto the SCL connector and a blue wire to the SDA. I did a test with a sample from instructables and an Arduino Uno (using 2x10k resistors pulled up to the 3.3v line) and got some readings from the sensor. Checking the HMC5883 datasheet later 2K2 resistors were recommended so I'll reduce my values if there are any issues.


imageimage

I realised from looking at this code that there is a magnetic deviation for compasses and really my project should compensate for that based on the current position. Perhaps another Azure Function is the best approach for that as it allows for adjustments over time.

 

So a bit of a problem with connecting the I2C to the Azure Sphere, the MS team have yet to support I2C in their SDK. I'd come across chips before that did not support I2C and knew that the protocol could be implemented in software with a technique called “bitbanging” i.e. you toggle a GPIO to have the desired behaviour. AlaskaResearchCubeSat had a great example bitbang I2C which looked straight forward to port.

 

I2C Hardware

As mentioned the MT3620 does support I2C, it has three comms modules that can be configured for UART, SPI or I2C. However this is not yet available via the API so I needed to look at the GPIO. For the board to be able to communicate with I2C it needs to be able send and receive on two pins. I did think about using 4 pins wired in pairs but it does not seem that is necessary.

 

The I2C protocol works by having pull up resistors and each of the devices pulls down the line to signal a zero. The output is left floating when the MCU indicates "high", that means that other devices could pull that line low still. The Azure Sphere seems to support this with the open_drain setting for GPIO_OpenAsOutput.

 

image

It also supports reading the pin whilst in this state so hence we can implement a software/bitbanged I2C.

 

Porting to Azure Sphere

My first pass at porting the Alaska bitbang version is as follows:

 

i2cbb.h

#pragma once


//I2CBus
typedef struct {
GPIO_Id scl;
int sclFd;
GPIO_Id sda;
int sdaFd;
} i2cbus_t;


//Function prototypes
void i2c_bb_setup(i2cbus_t* bus);
short i2c_bb_tx(i2cbus_t* bus, unsigned char addr, const unsigned char *dat, unsigned short len);
short i2c_bb_rx(i2cbus_t* bus, unsigned char addr, unsigned char *dest, unsigned short len);

 

i2cbb.c

#include <applibs/gpio.h>
#include <applibs/log.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include "i2cbb.h"


#define BIT0 1


//setup bit bang I2C pins
void i2c_bb_setup(i2cbus_t* bus) {
    //set pins as outputs opendrain
    bus->sclFd = GPIO_OpenAsOutput(bus->scl, GPIO_OutputMode_OpenDrain, GPIO_Value_High);
    bus->sdaFd = GPIO_OpenAsOutput(bus->sda, GPIO_OutputMode_OpenDrain, GPIO_Value_High);
}


//wait for 1/2 of I2C clock period
void i2c_bb_hc(void) {
    const struct timespec clockhalftime = { 0, 50000 };
    //wait for 0.05ms
    nanosleep(&clockhalftime, NULL);
}


int setSDA(i2cbus_t* bus, GPIO_Value_Type val) {
    int result = GPIO_SetValue(bus->sdaFd, val);
    if (result != 0) {
        Log_Debug("ERROR: Could not set SDA output value: %s (%d).\n", strerror(errno), errno);
        return -1;
    }
    return 0;
}


int setSCL(i2cbus_t* bus, GPIO_Value_Type val) {
    int result = GPIO_SetValue(bus->sclFd, val);
    if (result != 0) {
        Log_Debug("ERROR: Could not set SCL output value: %s (%d).\n", strerror(errno), errno);
        return -1;
    }
    return 0;
}


unsigned char getSDA(i2cbus_t* bus) {
    GPIO_Value_Type val;
    int result = GPIO_GetValue(bus->sdaFd, &val);
    if (result != 0) {
        Log_Debug("ERROR: Could not read SDA value: %s (%d).\n", strerror(errno), errno);
    }
    return val == GPIO_Value_High ? 1 : 0;
}


void i2c_bb_start(i2cbus_t* bus) {
    //wait for 1/2 clock first
    i2c_bb_hc();
    //pull SDA low
    setSDA(bus, GPIO_Value_Low);
    //wait for 1/2 clock for end of start
    i2c_bb_hc();
}


void i2c_bb_stop(i2cbus_t* bus) {
    //pull SDA low
    setSDA(bus, GPIO_Value_Low);
    //wait for 1/2 clock for end of start
    i2c_bb_hc();
    //float SCL
    setSCL(bus, GPIO_Value_High);
    //wait for 1/2 clock
    i2c_bb_hc();
    //float SDA
    setSDA(bus, GPIO_Value_High);
    //wait for 1/2 clock
    i2c_bb_hc();
}


//send value over I2C return 1 if slave ACKed
short i2c_bb_tx_byte(i2cbus_t* bus, unsigned char val) {
    int i;
    //shift out bits
    for (i = 0;i < 8;i++) {
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //check bit
        if (val & 0x80) {
            //float SDA
            setSDA(bus, GPIO_Value_High);
        }
        else {
            //pull SDA low
            setSDA(bus, GPIO_Value_Low);
        }
        //shift
        val <<= 1;
        //wait for 1/2 clock
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
    }
    //check ack bit
    //pull SCL low
    setSCL(bus, GPIO_Value_Low);
    //float SDA
    setSDA(bus, GPIO_Value_High);
    //wait for 1/2 clock
    i2c_bb_hc();
    //float SCL
    setSCL(bus, GPIO_Value_High);
    //wait for 1/2 clock
    i2c_bb_hc();
    //sample SDA
    val = getSDA(bus);
    //pull SCL low
    setSCL(bus, GPIO_Value_Low);
    //return sampled value
    return !val;
}
//send value over I2C return 1 if slave ACKed
unsigned char i2c_bb_rx_byte(i2cbus_t* bus, unsigned short ack) {
    unsigned char val;
    int i;
    //shift out bits
    for (i = 0;i < 8;i++) {
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //wait for 1/2 clock
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();


        //shift value to make room
        val <<= 1;


        //sample data
        unsigned char readbit;
        readbit = getSDA(bus);


        if (readbit == GPIO_Value_High) {
            val |= 1;
        }
    }


    //check ack bit
    //pull SCL low
    setSCL(bus, GPIO_Value_Low);
    //check if we are ACKing this byte
    if (ack) {
        //pull SDA low for ACK
        setSDA(bus, GPIO_Value_Low);
    }
    else {
        //float SDA for NACK
        setSDA(bus, GPIO_Value_High);
    }
    //wait for 1/2 clock
    i2c_bb_hc();
    //float SCL
    setSCL(bus, GPIO_Value_High);
    //wait for 1/2 clock
    i2c_bb_hc();
    //pull SCL low
    setSCL(bus, GPIO_Value_Low);
    //float SDA
    setSDA(bus, GPIO_Value_High);
    //return value
    return val;
}


short i2c_bb_tx(i2cbus_t* bus, unsigned char addr, const unsigned char *dat, unsigned short len) {
    short ack;
    int i;
    //send start
    i2c_bb_start(bus);
    //send address with W bit
    ack = i2c_bb_tx_byte(bus, (addr << 1));
    //send data bytes
    for (i = 0;i < len && ack;i++) {
        //transmit next byte
        ack = i2c_bb_tx_byte(bus, dat[i]);
    }
    //transmit stop
    i2c_bb_stop(bus);
    //return if slave NACKed
    return ack;
}


short i2c_bb_rx(i2cbus_t* bus, unsigned char addr, unsigned char *dest, unsigned short len) {
    int i;
    //send start
    i2c_bb_start(bus);
    //send address with R bit
    if (!i2c_bb_tx_byte(bus, (addr << 1) | BIT0)) {
        //got NACK return error
        return 0;
    }
    //receive data bytes
    for (i = 0;i < len;i++) {
        //receive next byte
        dest[i] = i2c_bb_rx_byte(bus, i == len - 1);
    }
    //transmit stop
    i2c_bb_stop(bus);
    //return if slave NACKed
    return 1;
}


This still needs testing but it does compile, the identification registers seem good candidates for the read testing as they have fixed values. As is common with most I2C devices the "read" register is selected by sending a single byte with no data.

"To move the address pointer to a random register location, first issue a “write” to that register location with no data byte following the command. For example, to move the address pointer to register 10, send 0x3C 0x0A."

image

From Honeywell HMC5883L datasheet.

 

Note that the output is in the form of a vector so that needs converting to a bearing using the X and Y values and Arctan function. The AdaFruit library used by the Instructables article has a good example of that. I'll like wrap the call to the compass up with a separate function so you don't need to see the low level I2C functionality. I quite like the AdaFruit universal sensor approach but that seems a bit overkill to copy here.

 

Unfortunately I've got a work trip coming up next week so there will be a pause in the project. Knowing my luck the SDK team will have enabled the I2C functionality before I've had a chance to finish this port.

 

Further reference

Embedded System I2C Tutorial - Embedded Systems Learning Academy

https://courses.engr.illinois.edu/ece437/fa2018/ref/I2C_Serial_Protocol_Tutorial.pdf

  • Sign in to reply

Top Comments

  • Workshopshed
    Workshopshed over 6 years ago in reply to Workshopshed +3
    I programmed the Azure Sphere to write to the same configuration registers I'd seen on the arduino sample and managed to get a snapshot. The thing I noticed was that the code was sending a NAK after the…
  • DAB
    DAB over 6 years ago +2
    Nice update. I know how it is when work interferes with fun. DAB
  • Workshopshed
    Workshopshed over 6 years ago in reply to shabaz +2
    Cheers, Shabaz, actually from the desktop, I know that the USB ground is connected to the probe ground on the Picoscope but both devices are coming from the same computer so the ground should be ok...…
Parents
  • shabaz
    shabaz over 6 years ago

    Hi Andy,

     

    If you're running this on a laptop, it could be worth unplugging it from the PSU temporarily, just to check if it is being caused by current through the ground connection.

    Also, it's a pain when APIs use High and Low for open drain, because it's never completely clear if the high means floating high, or pulled to ground (because in open drain, when the signal is asserted, it is pulled low, whereas with normal push-pull, when it is asserted, it is pulled high). So it is easier if they'd use the term asserted.

    But it sounds like you've already checked that since you connected an LED. And if you've stepped through the code it seems like all is well there.. I think it could be worth replacing the 10k with 2.2k, just in case the load is too high.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Workshopshed
    Workshopshed over 6 years ago in reply to shabaz

    Cheers, Shabaz, actually from the desktop, I know that the USB ground is connected to the probe ground on the Picoscope but both devices are coming from the same computer so the ground should be ok... ? The example where I did get it to record was when the scope ground was disconnected. The Picoscope still resets the bus even when no probes are connected so I think the only way I'm going to get that to play is with something like a USB 3 PCI (or higher current USB 2 PCI) card.

     

    I am now getting some form of communication with the device but it's not quite right yet.

    There's three convenient status registers that return fixed values. 72  52 51

     

    I can get these to return properly but only if I read the middle register twice. If I try to read multiple bytes it all goes wrong. Reading other registers does not seem too predictable either although I think the status register returns ok. That's also a single byte and you get a 1 or 0 depending on if the magnetic readings are ready.

     

    Setup

        const unsigned char msg1[2] = { HMC5883_REGISTER_MAG_CRA_REG_M, 0x00 }; // 8 average samples, 15Hz, Normal Bias
        const unsigned char msg2[2] = { HMC5883_REGISTER_MAG_CRB_REG_M, HMC5883_MAGGAIN_4_7 }; // Gain
        const unsigned char msg3[2] = { HMC5883_REGISTER_MAG_MR_REG_M, 0x00 }; // Enable Magnetometer, Continuous mode, Low Speed I2C 
        i2c_bb_tx(&i2cbus1, HMC5883_ADDRESS_MAG, &msg1[0], 2);
        i2c_bb_tx(&i2cbus1, HMC5883_ADDRESS_MAG, &msg2[0], 2);
        i2c_bb_tx(&i2cbus1, HMC5883_ADDRESS_MAG, &msg3[0], 2);

     

    Reading

    unsigned char rec[15];        
             i2c_bb_rx(&i2cbus1, HMC5883_ADDRESS_MAG, HMC5883_REGISTER_MAG_IRA_REG_M, &rec[0], 1);
             i2c_bb_rx(&i2cbus1, HMC5883_ADDRESS_MAG, HMC5883_REGISTER_MAG_IRB_REG_M, &rec[1], 1);
             i2c_bb_rx(&i2cbus1, HMC5883_ADDRESS_MAG, HMC5883_REGISTER_MAG_IRB_REG_M, &rec[1], 1); // Why twice?
             i2c_bb_rx(&i2cbus1, HMC5883_ADDRESS_MAG, HMC5883_REGISTER_MAG_IRC_REG_M, &rec[2], 1);
    Log_Debug("Reading: %d  %d %d \n", rec[0], rec[1],rec[2]);

     

    Changing the clock speed makes no difference. Changing the resistors makes no difference. I've can confirm that the correct address (with the lowest bit as R/!W) is getting sent.

     

    I have tweaked the code to match the data sheet although I guess I've got a timing or signal issue somewhere.

    https://cdn-shop.adafruit.com/datasheets/HMC5883L_3-Axis_Digital_Compass_IC.pdf

     

    #include <applibs/gpio.h>
    #include <applibs/log.h>
    #include <stdbool.h>
    #include <errno.h>
    #include <string.h>
    #include <time.h>
    #include "i2cbb.h"
    
    
    #define BIT0 1
    
    
    //Port from https://github.com/AlaskaResearchCubeSat/bit-bang-I2C
    
    
    /// <summary>
    ///     setup bit bang I2C pins
    /// </summary>
    /// <returns>0 on success, or -1 on failure</returns>
    int i2c_bb_setup(i2cbus_t* bus) {
        //set pins as outputs opendrain
        bus->sclFd = GPIO_OpenAsOutput(bus->scl, GPIO_OutputMode_OpenDrain, GPIO_Value_High);
        if (bus->sclFd < 0) {
            Log_Debug("ERROR: Could not open SCL GPIO: %s (%d).\n", strerror(errno), errno);
            return -1;
        }
        bus->sdaFd = GPIO_OpenAsOutput(bus->sda, GPIO_OutputMode_OpenDrain, GPIO_Value_High);
        if (bus->sdaFd < 0) {
            Log_Debug("ERROR: Could not open SDA GPIO: %s (%d).\n", strerror(errno), errno);
            return -1;
        }
        return 0;
    }
    
    
    //wait for 1/2 of I2C clock period (50000 = 10Khz clock)
    void i2c_bb_hc(void) {
        const struct timespec clockhalftime = { 0, 50000 };
        //wait for 0.05ms
        nanosleep(&clockhalftime, NULL);
    }
    
    
    int setSDA(i2cbus_t* bus, GPIO_Value_Type val) {
        int result = GPIO_SetValue(bus->sdaFd, val);
        if (result != 0) {
            Log_Debug("ERROR: Could not set SDA output value: %s (%d).\n", strerror(errno), errno);
            return -1;
        }
        return 0;
    }
    
    
    int setSCL(i2cbus_t* bus, GPIO_Value_Type val) {
        int result = GPIO_SetValue(bus->sclFd, val);
        if (result != 0) {
            Log_Debug("ERROR: Could not set SCL output value: %s (%d).\n", strerror(errno), errno);
            return -1;
        }
        return 0;
    }
    
    
    unsigned char getSDA(i2cbus_t* bus) {
        GPIO_Value_Type val;
        int result = GPIO_GetValue(bus->sdaFd, &val);
        if (result != 0) {
            Log_Debug("ERROR: Could not read SDA value: %s (%d).\n", strerror(errno), errno);
        }
        return val;
    }
    
    
    void i2c_bb_start(i2cbus_t* bus) {
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock first
        i2c_bb_hc();
        //pull SDA low
        setSDA(bus, GPIO_Value_Low);
        //wait for 1/2 clock for end of start
        i2c_bb_hc();
    }
    
    
    void i2c_bb_stop(i2cbus_t* bus) {
        i2c_bb_hc();
        //pull SDA low
        setSDA(bus, GPIO_Value_Low);
        //wait for 1/2 clock for end of start
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
        //float SDA
        setSDA(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
    }
    
    
    //send value over I2C return 1 if slave ACKed
    short i2c_bb_tx_byte(i2cbus_t* bus, unsigned char val) {
        int i;
        //shift out bits
        for (i = 0;i < 8;i++) {
            //pull SCL low
            setSCL(bus, GPIO_Value_Low);
            //check bit
            if (val & 0x80) {
                //float SDA
                setSDA(bus, GPIO_Value_High);
            }
            else {
                //pull SDA low
                setSDA(bus, GPIO_Value_Low);
            }
            //shift
            val <<= 1;
            //wait for 1/2 clock
            i2c_bb_hc();
            //float SCL
            setSCL(bus, GPIO_Value_High);
            //wait for 1/2 clock
            i2c_bb_hc();
        }
        //check ack bit
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //float SDA
        setSDA(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
        //sample SDA
        val = getSDA(bus);
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //return sampled value
        return !val;
    }
    //send value over I2C return 1 if slave ACKed
    unsigned char i2c_bb_rx_byte(i2cbus_t* bus, unsigned short ack) {
        unsigned char val;
        int i;
        val = 0;
    
    
        //shift out bits
        for (i = 0;i < 8;i++) {
            //pull SCL low
            setSCL(bus, GPIO_Value_Low);
            //wait for 1/2 clock
            i2c_bb_hc();
            //float SCL
            setSCL(bus, GPIO_Value_High);
            //wait for 1/2 clock
            i2c_bb_hc();
    
    
            //shift value to make room
            val <<= 1;
    
    
            //sample data
            unsigned char readbit;
            readbit = getSDA(bus);
    
    
            if (readbit == GPIO_Value_High) {
                val |= 1;
            }
        }
    
    
        //check ack bit
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //check if we are ACKing this byte
        if (ack) {
            //pull SDA low for ACK
            setSDA(bus, GPIO_Value_Low);
        }
        else {
            //float SDA for NACK
            setSDA(bus, GPIO_Value_High);
        }
        //wait for 1/2 clock
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //float SDA
        setSDA(bus, GPIO_Value_High);
        //return value
        return val;
    }
    
    
    short i2c_bb_tx(i2cbus_t* bus, unsigned char addr, const unsigned char *dat, unsigned short len) {
        short ack;
        int i;
        //send start
        i2c_bb_start(bus);
        //send address with W bit
        ack = i2c_bb_tx_byte(bus, (addr << 1));
        //send data bytes
        for (i = 0;i < len && ack;i++) {
            //transmit next byte
            ack = i2c_bb_tx_byte(bus, dat[i]);
        }
        //transmit stop
        i2c_bb_stop(bus);
        //return if slave NACKed
        return ack;
    }
    
    
    short i2c_bb_rx(i2cbus_t* bus, unsigned char addr, unsigned char reg, unsigned char *dest, unsigned short len) {
        int i;
        //send start
        i2c_bb_start(bus);
        //send address with W bit
        if (!i2c_bb_tx_byte(bus, (addr << 1))) {
            //got NACK return error
            return 0;
        }
        //send register to read from
        if (!i2c_bb_tx_byte(bus, reg)) {
            //got NACK return error
            return 0;
        }
        i2c_bb_stop(bus);
    
    
        i2c_bb_start(bus);
        //send address with R bit
        if (!i2c_bb_tx_byte(bus, (addr << 1) | BIT0)) {
            //got NACK return error
            return 0;
        }
        //receive data bytes
        for (i = 0;i < len;i++) {
            //receive next byte
            dest[i] = i2c_bb_rx_byte(bus, i == len - 1);
        }
        //transmit stop
        i2c_bb_stop(bus);
        //return if slave NACKed
        return 1;
    }

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • Workshopshed
    Workshopshed over 6 years ago in reply to shabaz

    Cheers, Shabaz, actually from the desktop, I know that the USB ground is connected to the probe ground on the Picoscope but both devices are coming from the same computer so the ground should be ok... ? The example where I did get it to record was when the scope ground was disconnected. The Picoscope still resets the bus even when no probes are connected so I think the only way I'm going to get that to play is with something like a USB 3 PCI (or higher current USB 2 PCI) card.

     

    I am now getting some form of communication with the device but it's not quite right yet.

    There's three convenient status registers that return fixed values. 72  52 51

     

    I can get these to return properly but only if I read the middle register twice. If I try to read multiple bytes it all goes wrong. Reading other registers does not seem too predictable either although I think the status register returns ok. That's also a single byte and you get a 1 or 0 depending on if the magnetic readings are ready.

     

    Setup

        const unsigned char msg1[2] = { HMC5883_REGISTER_MAG_CRA_REG_M, 0x00 }; // 8 average samples, 15Hz, Normal Bias
        const unsigned char msg2[2] = { HMC5883_REGISTER_MAG_CRB_REG_M, HMC5883_MAGGAIN_4_7 }; // Gain
        const unsigned char msg3[2] = { HMC5883_REGISTER_MAG_MR_REG_M, 0x00 }; // Enable Magnetometer, Continuous mode, Low Speed I2C 
        i2c_bb_tx(&i2cbus1, HMC5883_ADDRESS_MAG, &msg1[0], 2);
        i2c_bb_tx(&i2cbus1, HMC5883_ADDRESS_MAG, &msg2[0], 2);
        i2c_bb_tx(&i2cbus1, HMC5883_ADDRESS_MAG, &msg3[0], 2);

     

    Reading

    unsigned char rec[15];        
             i2c_bb_rx(&i2cbus1, HMC5883_ADDRESS_MAG, HMC5883_REGISTER_MAG_IRA_REG_M, &rec[0], 1);
             i2c_bb_rx(&i2cbus1, HMC5883_ADDRESS_MAG, HMC5883_REGISTER_MAG_IRB_REG_M, &rec[1], 1);
             i2c_bb_rx(&i2cbus1, HMC5883_ADDRESS_MAG, HMC5883_REGISTER_MAG_IRB_REG_M, &rec[1], 1); // Why twice?
             i2c_bb_rx(&i2cbus1, HMC5883_ADDRESS_MAG, HMC5883_REGISTER_MAG_IRC_REG_M, &rec[2], 1);
    Log_Debug("Reading: %d  %d %d \n", rec[0], rec[1],rec[2]);

     

    Changing the clock speed makes no difference. Changing the resistors makes no difference. I've can confirm that the correct address (with the lowest bit as R/!W) is getting sent.

     

    I have tweaked the code to match the data sheet although I guess I've got a timing or signal issue somewhere.

    https://cdn-shop.adafruit.com/datasheets/HMC5883L_3-Axis_Digital_Compass_IC.pdf

     

    #include <applibs/gpio.h>
    #include <applibs/log.h>
    #include <stdbool.h>
    #include <errno.h>
    #include <string.h>
    #include <time.h>
    #include "i2cbb.h"
    
    
    #define BIT0 1
    
    
    //Port from https://github.com/AlaskaResearchCubeSat/bit-bang-I2C
    
    
    /// <summary>
    ///     setup bit bang I2C pins
    /// </summary>
    /// <returns>0 on success, or -1 on failure</returns>
    int i2c_bb_setup(i2cbus_t* bus) {
        //set pins as outputs opendrain
        bus->sclFd = GPIO_OpenAsOutput(bus->scl, GPIO_OutputMode_OpenDrain, GPIO_Value_High);
        if (bus->sclFd < 0) {
            Log_Debug("ERROR: Could not open SCL GPIO: %s (%d).\n", strerror(errno), errno);
            return -1;
        }
        bus->sdaFd = GPIO_OpenAsOutput(bus->sda, GPIO_OutputMode_OpenDrain, GPIO_Value_High);
        if (bus->sdaFd < 0) {
            Log_Debug("ERROR: Could not open SDA GPIO: %s (%d).\n", strerror(errno), errno);
            return -1;
        }
        return 0;
    }
    
    
    //wait for 1/2 of I2C clock period (50000 = 10Khz clock)
    void i2c_bb_hc(void) {
        const struct timespec clockhalftime = { 0, 50000 };
        //wait for 0.05ms
        nanosleep(&clockhalftime, NULL);
    }
    
    
    int setSDA(i2cbus_t* bus, GPIO_Value_Type val) {
        int result = GPIO_SetValue(bus->sdaFd, val);
        if (result != 0) {
            Log_Debug("ERROR: Could not set SDA output value: %s (%d).\n", strerror(errno), errno);
            return -1;
        }
        return 0;
    }
    
    
    int setSCL(i2cbus_t* bus, GPIO_Value_Type val) {
        int result = GPIO_SetValue(bus->sclFd, val);
        if (result != 0) {
            Log_Debug("ERROR: Could not set SCL output value: %s (%d).\n", strerror(errno), errno);
            return -1;
        }
        return 0;
    }
    
    
    unsigned char getSDA(i2cbus_t* bus) {
        GPIO_Value_Type val;
        int result = GPIO_GetValue(bus->sdaFd, &val);
        if (result != 0) {
            Log_Debug("ERROR: Could not read SDA value: %s (%d).\n", strerror(errno), errno);
        }
        return val;
    }
    
    
    void i2c_bb_start(i2cbus_t* bus) {
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock first
        i2c_bb_hc();
        //pull SDA low
        setSDA(bus, GPIO_Value_Low);
        //wait for 1/2 clock for end of start
        i2c_bb_hc();
    }
    
    
    void i2c_bb_stop(i2cbus_t* bus) {
        i2c_bb_hc();
        //pull SDA low
        setSDA(bus, GPIO_Value_Low);
        //wait for 1/2 clock for end of start
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
        //float SDA
        setSDA(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
    }
    
    
    //send value over I2C return 1 if slave ACKed
    short i2c_bb_tx_byte(i2cbus_t* bus, unsigned char val) {
        int i;
        //shift out bits
        for (i = 0;i < 8;i++) {
            //pull SCL low
            setSCL(bus, GPIO_Value_Low);
            //check bit
            if (val & 0x80) {
                //float SDA
                setSDA(bus, GPIO_Value_High);
            }
            else {
                //pull SDA low
                setSDA(bus, GPIO_Value_Low);
            }
            //shift
            val <<= 1;
            //wait for 1/2 clock
            i2c_bb_hc();
            //float SCL
            setSCL(bus, GPIO_Value_High);
            //wait for 1/2 clock
            i2c_bb_hc();
        }
        //check ack bit
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //float SDA
        setSDA(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
        //sample SDA
        val = getSDA(bus);
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //return sampled value
        return !val;
    }
    //send value over I2C return 1 if slave ACKed
    unsigned char i2c_bb_rx_byte(i2cbus_t* bus, unsigned short ack) {
        unsigned char val;
        int i;
        val = 0;
    
    
        //shift out bits
        for (i = 0;i < 8;i++) {
            //pull SCL low
            setSCL(bus, GPIO_Value_Low);
            //wait for 1/2 clock
            i2c_bb_hc();
            //float SCL
            setSCL(bus, GPIO_Value_High);
            //wait for 1/2 clock
            i2c_bb_hc();
    
    
            //shift value to make room
            val <<= 1;
    
    
            //sample data
            unsigned char readbit;
            readbit = getSDA(bus);
    
    
            if (readbit == GPIO_Value_High) {
                val |= 1;
            }
        }
    
    
        //check ack bit
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //check if we are ACKing this byte
        if (ack) {
            //pull SDA low for ACK
            setSDA(bus, GPIO_Value_Low);
        }
        else {
            //float SDA for NACK
            setSDA(bus, GPIO_Value_High);
        }
        //wait for 1/2 clock
        i2c_bb_hc();
        //float SCL
        setSCL(bus, GPIO_Value_High);
        //wait for 1/2 clock
        i2c_bb_hc();
        //pull SCL low
        setSCL(bus, GPIO_Value_Low);
        //float SDA
        setSDA(bus, GPIO_Value_High);
        //return value
        return val;
    }
    
    
    short i2c_bb_tx(i2cbus_t* bus, unsigned char addr, const unsigned char *dat, unsigned short len) {
        short ack;
        int i;
        //send start
        i2c_bb_start(bus);
        //send address with W bit
        ack = i2c_bb_tx_byte(bus, (addr << 1));
        //send data bytes
        for (i = 0;i < len && ack;i++) {
            //transmit next byte
            ack = i2c_bb_tx_byte(bus, dat[i]);
        }
        //transmit stop
        i2c_bb_stop(bus);
        //return if slave NACKed
        return ack;
    }
    
    
    short i2c_bb_rx(i2cbus_t* bus, unsigned char addr, unsigned char reg, unsigned char *dest, unsigned short len) {
        int i;
        //send start
        i2c_bb_start(bus);
        //send address with W bit
        if (!i2c_bb_tx_byte(bus, (addr << 1))) {
            //got NACK return error
            return 0;
        }
        //send register to read from
        if (!i2c_bb_tx_byte(bus, reg)) {
            //got NACK return error
            return 0;
        }
        i2c_bb_stop(bus);
    
    
        i2c_bb_start(bus);
        //send address with R bit
        if (!i2c_bb_tx_byte(bus, (addr << 1) | BIT0)) {
            //got NACK return error
            return 0;
        }
        //receive data bytes
        for (i = 0;i < len;i++) {
            //receive next byte
            dest[i] = i2c_bb_rx_byte(bus, i == len - 1);
        }
        //transmit stop
        i2c_bb_stop(bus);
        //return if slave NACKed
        return 1;
    }

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
  • shabaz
    shabaz over 6 years ago in reply to Workshopshed

    Hey Andy,

     

    I'm so sorry, I missed seeing this earlier : (

    Let me take a look today, get back to you this afternoon.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz over 6 years ago in reply to Workshopshed

    Definitely strange behaviour with the double-write.. I've hunted around for any bit-banging I2C stuff I wrote in the past, and came up with this. I wrote it for some Atmel chip originally, but could be easily modified. With this code, I confirmed at the time that I2C writes successfully worked. I don't think I ever used it for I2C reads, but fingers crossed that should be fine I hope. Maybe it is worth comparing with your code in case anything is significantly different, or just copy-pasting this code and making some tweaks. To use it for I2C writes, the API is:

     

    i2c_start();
    i2c_write(0x3c); // write to the IC's address
    i2c_write(0xxx); // write first data
    i2c_write(0xxx); // write next data, etc
    i2c_stop();

     

    For reads, there is the i2c_read() function, it returns one byte.

     

    The chip I was using didn't have open drain capability, and I'd used this hack on each line (only SCL shown, I did the same thing for SDA), you don't need this of course, this is just to explain why I've got stuff like SCL_READ and SCL_PULL definitions in the code:

    image

    Anyway everything is #define'd in the code, so things like #define SCL_SET_LOW etc can be modified to suit your setup.

     

    /**********************************************
     * I2C library version 1
     * Intended for ATmega32U2 processor
     *
     * pins:
     * PORT D 0 - SCL_PULL
     * PORT D 1 - SDA_PULL
     * PORT D 2 - SCL_READ
     * PORT D 3 - SDA_READ
    
     * PORT D 6 - LED output
     * PORT D 7 - Switch output
     * 
     **********************************************/
    
    
    #include <iom32u2.h>
    #include <intrinsics.h>
    #include <avr_macros.h>
    #include <stdio.h>
    
    // bits
    #define BIT0 0x01
    #define BIT1 0x02
    #define BIT2 0x04
    #define BIT3 0x08
    #define BIT4 0x10
    #define BIT5 0x20
    #define BIT6 0x40
    #define BIT7 0x80
    
    // port D stuff
    #define SCL_PULL BIT0
    #define SDA_PULL BIT1
    #define SCL_READ BIT2
    #define SDA_READ BIT3
    #define LED BIT6
    #define BUTTON BIT7
    
    // inputs
    #define SWITCH_ON (PIND & BUTTON) == 0
    #define SWITCH_OFF (PIND & BUTTON) != 0
    #define SCL_IS_HIGH (PIND & SCL_READ) !=0
    #define SDA_IS_HIGH (PIND & SDA_READ) !=0
    
    // outputs
    #define SCL_SET_LOW PORTD |= SCL_PULL
    #define SCL_SET_HIGH PORTD &= ~SCL_PULL
    #define SDA_SET_LOW PORTD |= SDA_PULL
    #define SDA_SET_HIGH PORTD &= ~SDA_PULL
    
    #define LED_ON PORTD |= LED
    #define LED_OFF PORTD &= ~LED
    
    void
    i2c_usec_delay(void)
    {
      __delay_cycles(8);
    
    }
    
    
    void
    delay_10ms(unsigned int val)
    {
      unsigned int i;
      for (i=0; i<val; i++)
      {
      __delay_cycles(25000);
      }
    }
    
    void
    init(void)
    {
      DDRD=0x43; // Set pin 6 (LED) and the first two pins, i.e. the SCL_PULL and SDA_PULL pins as outputs
      SDA_SET_HIGH;
      SCL_SET_HIGH;
    }
    
    void
    i2c_start(void)
    {
      i2c_usec_delay();
      SDA_SET_LOW;
      i2c_usec_delay();
      SCL_SET_LOW;
      i2c_usec_delay();
    }
    
    void i2c_stop(void)
    {
      // SDA should already be low for us to begin the stop condition
      // SCL should already be high.
      SDA_SET_LOW;
      SCL_SET_HIGH;
      i2c_usec_delay();
      SDA_SET_HIGH;
      // lets wait longer after a stop condition
      delay_10ms(1);
    }
    
    void i2c_write(unsigned char val)
    {
      unsigned char i;
      for (i=0; i<8; i++)
      {
      if (val & 0x80)
      {
      SDA_SET_HIGH;
      }
      else
      {
      SDA_SET_LOW;
      }
      i2c_usec_delay();
      SCL_SET_HIGH;
      i2c_usec_delay();
      SCL_SET_LOW;
      i2c_usec_delay();
      val <<= 1;
      }
      // ok now lets allow an ack to be received
      SDA_SET_HIGH;
      i2c_usec_delay();
      SCL_SET_HIGH;
      i2c_usec_delay();
      SCL_SET_LOW;
      i2c_usec_delay();
    }
      
    unsigned char
    i2c_read(void)
    {
      unsigned char i;
      unsigned char val=0;
      
      for (i=0; i<8; i++)
      {
      val<<=1;
      SCL_SET_HIGH;
      i2c_usec_delay();
      if (SDA_IS_HIGH)
      {
      val |= 0x01;
      }
      SCL_SET_LOW;
      i2c_usec_delay();
      }
      return val;
    }

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Workshopshed
    Workshopshed over 6 years ago in reply to shabaz

    I had thought about having separate read and write lines but the Azure Sphere seems to cope with that aspect. Cheers for the code sample, it's useful to have another version to cross reference against.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube