i2c reading register

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
bellibelson
Posts: 6
Joined: Mon Mar 21, 2022 11:06 pm

i2c reading register

Post by bellibelson » Mon Mar 21, 2022 11:22 pm

Hello,
I am new to micropython and a bit old (71).
I have written a program for the ESP8266 in micropython. This program should also be able to read in a compass bearing.
I am trying for a week now to read the registers in the compass. I failed because I dont know much about bit operation and literals. Unfortunately I was not able to find a working piece of code to read in the bearing. The compass is a cmps12 and is working well with arduino and C.
https://www.robot-electronics.co.uk/files/cmps12.pdf
Any help would be very nice.

Derek

User avatar
OlivierLenoir
Posts: 126
Joined: Fri Dec 13, 2019 7:10 pm
Location: Picardie, FR

Re: i2c reading register

Post by OlivierLenoir » Wed Mar 23, 2022 6:12 am

Derek, I don't have this compass cmps12, but we can start with the following steps :
  • Scan I2C, to check wiring
  • Read a register
  • Decode registers
Can you scan I2C addresses with this command?

Code: Select all

from machine import Pin, I2C

# construct an I2C bus
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000)
i2c.scan()

bellibelson
Posts: 6
Joined: Mon Mar 21, 2022 11:06 pm

Re: i2c reading register

Post by bellibelson » Wed Mar 23, 2022 2:29 pm

Thanks Olivier
I got that far already. I even managed to read something but it was not the heading. I got as far as the code:

Code: Select all

import machine
import time
i2c = machine.I2C(scl=machine.Pin(5), sda=machine.Pin(4), freq = 100000)



def read_bytes(i2cAddr, regAddr, buf):
    cmd    = bytearray(1)
    cmd[0] = regAddr & 0xf
    print("cmd0=",cmd[0])
    #print("cmd",cmd[0])
    #print("i2cAd=",i2cAddr)
    i2c.writeto(i2cAddr, cmd, False)
    i2c.stop()
    i2c.start()
    i2c.readfrom_into(i2cAddr, buf)
    print(buf)
    i2c.stop()
    #print("nach write=",buf)
# ---------------------------------------------------------------------------- 

def get_heading(hires):
    """ Returns heading with or w/o tilt compensation and/or calibration,
        if available.
        NOTE: The CMPS12 has built-in tilt compensation and is pre-calibra-
        ted, therefore the parameters "tilt" and "calib" are only for
        compatibility reasons and have no effect. With "hires"=True, the
        precision is 3599/360°, otherwise 255/360°.
    """
    ADDRESS_CMPS12 = const(0x60)
    REG_BEARING_8BIT = const(0x01)
    REG_BEARING_16BIT_HB = const(0x2)
    
    
    #print ("address=",ADDRESS_CMPS12)
    #print ("reg_bearing_8bit=",REG_BEARING_8BIT)
    #print ("reg_bearing_16BIT_HB=",REG_BEARING_16BIT_HB)
    
    hd = 1
    if hires:
      buf = bytearray(2)
      read_bytes(ADDRESS_CMPS12, REG_BEARING_16BIT_HB, buf)
      print("buf=",buf)
      hd  = ((buf[0] << 8) | buf[1]) /10
    else:
      buf = bytearray(1)
      read_bytes(ADDRESS_CMPS12, REG_BEARING_8BIT, buf)
      hd  = buf[0]/255 *360
    return hd

i=3

while i < 6:
    head = get_heading(True)
    print ("heading=",head)
    time.sleep_ms(1000)
some printouts are:

cmd0= 3
bytearray(b'\x00\x00')
buf= bytearray(b'\x00\x00')
heading= 0.0

But its still not the heading. It alwas reads 0.0. But maybe somebody can see whats wrong in the code. The heading should be in the registers 0x02 and 0x03. But something has to be written in the first register first I guess.

Maybe somebody can see whats wrong in the code. Here are the registers.

I2C Mode
The compass has a 31 byte array of registers:
Register Function
0x00 Command register (write) / Software version (read)
0x01 Compass Bearing 8 bit, i.e. 0-255 for a full circle
0x02, 0x03 Compass Bearing 16 bit, i.e. 0-3599, representing 0-359.9 degrees. register 2 being the
high byte. This is calculated by the processor from quaternion outputs of the BNO055
0x04 Pitch angle - signed byte giving angle in degrees from the horizontal plane (+/- 90°)
0x05 Roll angle - signed byte giving angle in degrees from the horizontal plane (+/- 90°)
0x06, 0x07 Magnetometer X axis raw output, 16 bit signed integer (register 0x06 high byte)
0x08,0x09 Magnetometer Y axis raw output, 16 bit signed integer (register 0x08 high byte)
0x0A,0x0B Magnetometer Z axis raw output, 16 bit signed integer (register 0x0A high byte)
0x0C, 0x0D Accelerometer X axis raw output, 16 bit signed integer (register 0x0C high byte)
0x0E, 0x0F Accelerometer Y axis raw output, 16 bit signed integer (register 0x0E high byte)
0x10, 0x11 Accelerometer Z axis raw output, 16 bit signed integer (register 0x10 high byte)
0x12, 0x13 Gyro X axis raw output, 16 bit signed integer (register 0x12 high byte)
0x14, 0x15 Gyro Y axis raw output, 16 bit signed integer (register 0x14 high byte)
0x16, 0x17 Gyro Z axis raw output, 16 bit signed integer (register 0x16 high byte)
0x18,0x19 Temperature of the BNO055 in degrees centigrade (register 0x18 high byte)
0x1A, 0x1B Compass Bearing 16 bit This is the angle Bosch generate in the BNO055 (0-5759),
divide by 16 for degrees
0x1C, 0x1D Pitch angle 16 bit - signed byte giving angle in degrees from the horizontal plane (+/-
180°)
0x1E Calibration state, bits 0 and 1 reflect the calibration status (0 un-calibrated, 3 fully
calibrated)


Regards

Derek

User avatar
OlivierLenoir
Posts: 126
Joined: Fri Dec 13, 2019 7:10 pm
Location: Picardie, FR

Re: i2c reading register

Post by OlivierLenoir » Wed Mar 23, 2022 8:02 pm

Can you try this code?

Code: Select all

from machine import Pin, I2C
from ustruct import unpack

ADDRESS_CMPS12 = const(0x60)
REG_BEARING_8 = const(0x01)
REG_BEARING_16 = const(0x02)

i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000)

# unsigned char
bearing_8 = unpack('B', i2c.readfrom_mem(ADDRESS_CMPS12, REG_BEARING_8, 1))
# unsigned short
bearing_16 = unpack('>H', i2c.readfrom_mem(ADDRESS_CMPS12, REG_BEARING_16, 2))

print('bearing_8:', bearing_8)
print('bearing_16:', bearing_16)

bellibelson
Posts: 6
Joined: Mon Mar 21, 2022 11:06 pm

Re: i2c reading register

Post by bellibelson » Wed Mar 23, 2022 10:13 pm

Thanks for your code Olivier,

this is the result:

bearing_8: (0,)
bearing_16: (0,)

Maybe the code for arduino which works well can help to understand what should be done.

Code: Select all

/*****************************************
*     CMPS12 I2C example for Arduino     *
*        By James Henderson, 2014        * 
*****************************************/

#include <Wire.h>

#define CMPS12_ADDRESS 0x60  // Address of CMPS12 shifted right one bit for arduino wire library
#define ANGLE_8  1           // Register to read 8bit angle from

unsigned char high_byte, low_byte, angle8;
char pitch, roll;
unsigned int angle16;

void setup()
{
  Serial.begin(9600);  // Start serial port
  Wire.begin();
}

void loop()
{

  Wire.beginTransmission(CMPS12_ADDRESS);  //starts communication with CMPS12
  Wire.write(ANGLE_8);                     //Sends the register we wish to start reading from
  Wire.endTransmission();
 
  // Request 5 bytes from the CMPS12
  // this will give us the 8 bit bearing, 
  // both bytes of the 16 bit bearing, pitch and roll
  Wire.requestFrom(CMPS12_ADDRESS, 5);       
  
  while(Wire.available() < 5);        // Wait for all bytes to come back
  
  angle8 = Wire.read();               // Read back the 5 bytes
  high_byte = Wire.read();
  low_byte = Wire.read();
  pitch = Wire.read();
  roll = Wire.read();
  
  angle16 = high_byte;                 // Calculate 16 bit angle
  angle16 <<= 8;
  angle16 += low_byte;
    
  Serial.print("roll: ");               // Display roll data
  Serial.print(roll, DEC);
  
  Serial.print("    pitch: ");          // Display pitch data
  Serial.print(pitch, DEC);
  
  Serial.print("    angle full: ");     // Display 16 bit angle with decimal place
  Serial.print(angle16 / 10, DEC);
  Serial.print(".");
  Serial.print(angle16 % 10, DEC);
  
  Serial.print("    angle 8: ");        // Display 8bit angle
  Serial.println(angle8, DEC);
  
  delay(100);                           // Short delay before next loop
}
Thank You and Regards

Derek

User avatar
OlivierLenoir
Posts: 126
Joined: Fri Dec 13, 2019 7:10 pm
Location: Picardie, FR

Re: i2c reading register

Post by OlivierLenoir » Thu Mar 24, 2022 6:32 am

OlivierLenoir wrote:
Wed Mar 23, 2022 6:12 am
Derek, can you scan I2C addresses with this command?

Code: Select all

from machine import Pin, I2C

# construct an I2C bus
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000)
i2c.scan()
I want to know if communication is working.
Does CMPS12 powered with 3.3V?
As CMPS12 consume around 18mA, I hope there is no variation on 3.3V. Do you have equipment to check it?
Looking at C code, it's almost the same as here bellow code. I just did not convert any data.

Code: Select all

from machine import Pin, I2C
from ustruct import unpack
from utime import sleep_ms

ADDR_CMPS12 = const(0x60)
REG_BEARING_8 = const(0x01)
REG_BEARING_16 = const(0x02)

i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000)

for _ in range(10):
    angle8, angle16, pitch, roll = unpack('>BHbb', i2c.readfrom_mem(ADDR_CMPS12, REG_BEARING_8, 5))
    print('angle8:', angle8)
    print('angle16:', angle16)
    print('pitch:', pitch)
    print('roll:', roll)
    sleep_ms(200)
    

rkompass
Posts: 66
Joined: Fri Sep 17, 2021 8:25 pm

Re: i2c reading register

Post by rkompass » Thu Mar 24, 2022 6:59 am

Hello Derek,

as you are reading only 0-s there is no need for other code now.
It looks like you are still at the point where you do not have any communication with the device.
Do you know of pull-up resistors in I2C?
Did you do your successful experiments with arduino code on the same physical setup like those with micropython?
The data I found say you can power the CMPS12 with 3.3 or 5V. As almost all signal lines on the pyboard are 5V-tolerant both voltages should work. On what hardware are you doing your experiments btw.?
Once your hardware is working and the code

Code: Select all

from machine import Pin, I2C

#  i2c1 = I2C(1, freq=100000)                     # alternative, you have to know the pins
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000)    # construct an I2C bus
i2clist = i2c1.scan()                             # scan I2C bus and print results
for z in i2clist:
    print('Found Contoller on I2C-Bus 1 at:   {:s}'.format(hex(z)))
is telling you about a controller with matching address ( 0xc0 - 0xce) you can proceed with the next steps, which are software.
This is what Olivier suggested first, b.t.w..

Greetings,
Raul

bellibelson
Posts: 6
Joined: Mon Mar 21, 2022 11:06 pm

Re: i2c reading register

Post by bellibelson » Thu Mar 24, 2022 4:13 pm

Hello Olivier,

yes I can scan:
>>> from machine import Pin, I2C
>>> i2c = I2C(scl=Pin(5), sda=Pin(4), freq=100000)
>>> i2c.scan()
[96]
>>>

I also checked all wiring. I am using pull up resistors around 2K. I have connected the 5V from the ESP8266 to the compass and measure 4.2V constantly.

Your code resulted in:
angle8: 0
angle16: 0
pitch: 0
roll: 0

Regards

Derek

bellibelson
Posts: 6
Joined: Mon Mar 21, 2022 11:06 pm

Re: i2c reading register

Post by bellibelson » Thu Mar 24, 2022 4:34 pm

Hello Raul,

thanks for your message.
Yes I am using pull up resitors around 2K and checked all wiring. The arduino is wired the same.
The wiring with the ESP8266 is simple. 5V from ESP to cmps12 as it can have 5V. I measured 4.2V.
SCL and SDA conneted and 2 Resistors. I tried without resistors but no change.
This is the result from your code plus a print(i2clist):

Found Contoller on I2C-Bus 1 at: 0x60
[96]

I had to change this line i2clist = i2c.scan() to i2clist = i2c1.scan() as it threw up an error.

Here again is my program which I put together from pieces I found. The result is:

buf= bytearray(b'\x00\x00')
heading= 0.0

Code: Select all

import machine
import time
i2c = machine.I2C(scl=machine.Pin(5), sda=machine.Pin(4), freq = 100000)

def read_bytes(i2cAddr, regAddr, buf):
    cmd    = bytearray(1)
    cmd[0] = regAddr & 0xff
    i2c.writeto(i2cAddr, cmd, False)
    i2c.readfrom_into(i2cAddr, buf)
    

def get_heading(hires):
    """ Returns heading with or w/o tilt compensation and/or calibration,
        if available.
        NOTE: The CMPS12 has built-in tilt compensation and is pre-calibra-
        ted, therefore the parameters "tilt" and "calib" are only for
        compatibility reasons and have no effect. With "hires"=True, the
        precision is 3599/360°, otherwise 255/360°.
    """
    ADDRESS_CMPS12 = const(0x60)
    REG_BEARING_8BIT = const(0x01)
    REG_BEARING_16BIT_HB = const(0x2)
    
    hd = 1
    if hires:
      buf = bytearray(2)
      read_bytes(ADDRESS_CMPS12, REG_BEARING_16BIT_HB, buf)
      print("buf=",buf)
      hd  = ((buf[0] << 8) | buf[1]) /10
    else:
      buf = bytearray(1)
      read_bytes(ADDRESS_CMPS12, REG_BEARING_8BIT, buf)
      hd  = buf[0]/255 *360
    return hd

i=3
while i < 6:
    head = get_heading(True)
    print ("heading=",head)
    time.sleep_ms(1000)
Maybe I should try a different ESC8266

Regards and thanks for your help

Derek

User avatar
OlivierLenoir
Posts: 126
Joined: Fri Dec 13, 2019 7:10 pm
Location: Picardie, FR

Re: i2c reading register

Post by OlivierLenoir » Thu Mar 24, 2022 4:51 pm

Hi Derek, I would connect the 3.3V from the ESP8266 to the CMPS12.
Even if it seem to work with the 5V, I2C signal on ESP8266 should be 3.3V.

Post Reply