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
Experimenting with Current Sense Amplifiers
  • Challenges & Projects
  • Design Challenges
  • Experimenting with Current Sense Amplifiers
  • More
  • Cancel
Experimenting with Current Sense Amplifiers
Challenge Blog Safer 3D Printers with CSA - Blog #4 - Nearly functionally complete!
  • Blog
  • Forum
  • Documents
  • Files
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: aspork42
  • Date Created: 5 Jun 2022 3:32 AM Date Created
  • Views 462 views
  • Likes 5 likes
  • Comments 0 comments
  • Current 6 Click
  • 3D Printing
  • csa
  • MAX40080
  • Experimenting with Current Sense Amplifiers
  • safety
Related
Recommended

Safer 3D Printers with CSA - Blog #4 - Nearly functionally complete!

aspork42
aspork42
5 Jun 2022

Hello again!

I'm looking at how to use a Current Sense Amplifier to make a safer 3D printer. By monitoring the current going out to the printer's nozzle and heat bed, we can detect loose/broken, and shorted wires.

Where am I at with all this?

imageWell - I'm at two places! I'm at a pretty good place with the project where I can nearly say I've achieved all my goals. I'm also traveling in Michigan this week; away from my normal Milwaukee. I'm visiting my mom for the weekend and brought my kit along to keep things rolling. I'm now able to have the MAX40080 take all my measurements, and a Python script control a relay to cut power if a fault is detected. 

I'm working in a half-finished (mid-remodel) basement on a tiny table with minimal supplies, but making the most of it Slight smile Image to the left shows my bare-bones setup. 

Skip to the end

If you'd like to see a 3-minute video update. Slight smile 

What else is left?

I've got two lingering things that I need to work out. I has stated originally that I was going to use MQTT to control this; and so far I haven't added that in yet. I'm not super concerned about this as it was more of a bonus for monitoring & control. The second issue is something I found in testing. I have one additional relay being run via a Blink sketch on an external Arduino. this clicks on and off to simulate the 3D printer using a MOSFET to turn the nozzle on and off. It looks like when the relay shuts off, the MAX40080 is reading fast enough to see the current drop to zero before the voltage and sees this as a fault. When the current drops to zero, my device still sees 12V, without any current flowing and kills the system. This would be a condition of broken/loose wiring (using high-side current sensing). This is where I tried turning to averaging on-board the 40080 which caused a bunch of other issues; and then started trying to average in Python instead. The downside of this approach is it cuts down reaction time of the system. This means I have my next problem to solve! I also don't know if this has to do with using a relay instead of MOSFET, or using the inexpensive digital load verses how a normal resistive load would behave. 

Updates since the last blog?

In Blog #3, I was having issues talking to the device. It turned out that I was missing sending a CRC checksum. I figured this out after posting that write-up by checking a few competitor's blogs (which I've been avoiding as much as possible; to not get distracted). I think I was mis-reading the datasheet and assuming that CRC was disabled; but it was not. So add in a few more nights trying to manually calc the CRC and then messing with libraries before finally giving it a pass and just disabling and moving on. At least the data we get back from the device has a "Data Valid" bit that must check out before proceeding. 

Once I got past this barrier, I could start setting all the bits in memory that I wanted to play with. It was really starting to look like almost my entire program could be interrupt-drive, but looking at the notes below, this failed. I had to achieve this all in code.

Problems with the Datasheet (and/or chip)(and/or me)

There were some issues I ran into with the datasheet - although to be fair it may be my interpretation of the data sheet; or a misunderstanding of some features of the chip. Take for instance the High/Low range for current measurement. The data sheet only notes High/Low range for 50mv/10mv. It doesn't spell out how this corresponds to the data readings it will produce. It isn't directly even spelled out what this 50/10mv even refers to (assuming its the CSA input voltage? But that's determined by the current shunt resistor; so that doesn't even make sense). For reading back values, it turns out that it becomes up to the user to "multiply by five" the current output when going into High Current mode. So by setting this bit, my current readings were just reduced by a factor of five. This isn't noted in the Range section or the measurement sections. I work in engineering and constantly have to remind my peers that what they consider 'obvious' isn't really obvious at all to the outside world; to people who didn't design the 'thing' and haven't worked with the 'thing' daily for years. They need to make things easy and simple for the general userbase who don't have as much deep technical knowledge. 

image

Another issue is that of units. Most the datasheet doesn't seem to spell out units/ranges/scaling for setpoints. I can see the number of bits and the address, but not what the corresponding value is supposed to relate to. See images below. We have a 12-bit number for the current & voltage magnitude, and a 6-bit value for the threshold used in the interrupt. No where is it correlated how do do the conversion from volts/amps to this value. Again, there is no notes of how high/low range change the values that need to be placed into these registers. my best guess was to correlate input max/min for the given range, and then scale my 6-bit value accordingly. I could verify that my writes were being done properly, but the alerts never came. How does changing High/Low range affect these values?

image

image

I know that this will return 16 bits of data, with the current magnitude in D11-D0, but I don't know Amps, milliamps, etc; so where does my decimal go?

Digital Filter - Turning on filtering caused all sorts of interrupts and alerts. I wanted to do averaging right on the chip but ultimately had to do this in code because the chip seemed to freak out. I was getting overcurent and overvoltage interrupts like crazy. It seemed like perhaps the chip was trying to do averaging internally, and the rolling tally was counted against the alert threshold; rather than the averaged value.

Interrupts - I had wanted to use Overvoltage interrupt to tell my application to start monitoring current because the printer was running the nozzle. I couldn't figure out how to get any suitable value that fired this interrupt though (since no guidance on how to set and my playing around didn't yield anything productive). Undervoltge would then also tell me that the printer is no longer asking the nozzle to run; so don't monitor current. But I couldn't get this to fire reliably. Then Overcurrent was also going to be most useful for detecting shorted wires. But again, I couldn't get this to fire. I played around with masking the faults and basically had to mask the FIFO because it was constantly complaining about being full. So I ignore it and tell the chip not to alert me about it. 

To Fifo or Not to Fifo? The chip has a First-in-first-out buffer for measurements. When a read is performed, it goes to the next spot in this FIFO. It internally handles knowing which measurement was the last read. I also spent a decent amount of time messing with the settings for the Fifo. As noted above, I was assuming that the chip would just "read continuously" as per the setting I used. Then I could use the interrupts to tell me when I needed to know something. That would have been the fastest response time and simplest to code, but it didn't work. So then I don't understand the FIFO buffer - why not just use Single Measurement Mode and not bother with Fifo? In the end, I needed to manually read the chip and it would always complain that the FIFO was full; but only when I read; never else would it complain. So it seemed like the chip was doing single conversion mode. I really don't care about each individual measurement, I just wanted to know when they go out of bounds. But disabling the FIFO causes the readings to be blank; so I had to leave it on... why? And why was it always full? I also had to enable Roll-Over otherwise it seemed like it was still in single-conversion mode and I was getting really stale data. Even flushing the FIFO didn't resolve anything and I just got zeros for my measurements. 

Also, I found that the Fifo doesn't store voltage by default, so I had to turn that on as I need it. There is perhaps an argument on what should be 'default' here, but I'd argue that it should be storing voltage by default also. So I was getting all zeros until this was done. 

My Code

If anyone is interested, here is the complete code that was running in the video shown on this blog post.

import RPi.GPIO as GPIO
# import board
# import busio
# i2c = busio.I2C(board.SCL, board.SCA)
from smbus import SMBus
#from PyCRC.CRCCCITT import CRCCCITT

# import crc8

import time

activeInterrupt = False # to handle interrupt pin

InputRangeHigh = True # True for 5Amp limit. False for 1Amp limit

current6ClickAddress = 0x21
CurrentSenseEnablePin = 8
OvercurrentAlertPin = 6
RelayPin = 26

myMaxCurrent = 2
myMinCurrent = 0.3
myTurnOnVoltage = 10  # when to start assuming that the system is running
myTurnOffVoltage = 2

GPIO.setmode(GPIO.BCM)

GPIO.setup(CurrentSenseEnablePin, GPIO.OUT)
GPIO.setup(RelayPin, GPIO.OUT)
GPIO.setup(OvercurrentAlertPin, GPIO.IN)


#Create Instance of SMBus to talk to device
i2cbus = SMBus(1)

#enable relay
def enableRelay():
    GPIO.output(RelayPin, GPIO.HIGH)
    
def disableRelay():
    GPIO.output(RelayPin, GPIO.LOW)   
    
def setConfig():
    global InputRangeHigh
    
    myval = i2cbus.read_word_data(current6ClickAddress, 0x00)
    print("Starting Config: ", myval)
    myval = 0x00 # disable Packet Error Checking (PEC); leave all other values at default.
    
    i2cbus.write_i2c_block_data(current6ClickAddress, 0x00, [myval, 0x00, 0xB7]) # Disable PEC
    
    #i2cbus.write_i2c_block_data(current6ClickAddress, 0x00, [0x7e, 0x00, 0xc3])
    
    # Configuration register
    myval = myval | 0b00000011 # enable Active mode with bits 0,1.
#     myval = myval | 0b00001000 # disable I2C Timeout
    myval = myval | 0b00010000 # Alert response time. 0=unfiltered. 1= four consec. readings
#     myval = myval | 0b00100000 # PEC
    if not InputRangeHigh:
#         print("Disable High Current Range")
        myval = myval | 0b01000000 # Input Range - write 1 for Low (5Amps max)

#     myval = myval | 0b10000000 # Stay HS Mode

    print("proposed config: ", hex(myval))

#     Digital averaging - makes erratic faults and readings :(
    myval = myval | 0x0000 #no average
#     myval = myval | 0x1000 #average 8
#     myval = myval | 0x2000 #average 16
#     myval = myval | 0x3000 #average 32
#     myval = myval | 0x4000 #average 64
#     myval = myval | 0x5000 #average 128

#     print("proposed config: ", hex(myval))
    
    i2cbus.write_word_data(current6ClickAddress, 0x00, myval)
#     i2cbus.write_byte_data(current6ClickAddress, 0x00, myval)
    
#     myval = i2cbus.read_word_data(current6ClickAddress, 0x00)
#     print("Config now in chip: ", hex(myval))
    
    #Fifo settings
#     myFifo = i2cbus.read_word_data(current6ClickAddress, 0x0A)
#     print("Startup Fifo config: ", hex(myFifo))
    
    myFifo = 0x0000
    #myFifo = myFifo | 0x8000 # flush
    #myFifo = myFifo | 0x0003 # disable fifo
    myFifo = myFifo | 0x0002 # fifo for V & C
    myFifo = myFifo | 0x4000 # enable rollover of Fifo
    
    i2cbus.write_word_data(current6ClickAddress,0x0A, myFifo)
    
#     myInts = i2cbus.read_byte_data(current6ClickAddress, 0x14)
#     print("Interrupts currently set: ", myInts)
    
    # configure interrupts
    myInts = 0x0 
    myInts = myInts | 0b00000001 # wake-up enable
    myInts = myInts | 0b00000010 # Conversion ready
    myInts = myInts | 0b00000100 # Overflow current
    myInts = myInts | 0b00001000 # overflow Volts
    myInts = myInts | 0b00010000 # Underflow Volts
    myInts = myInts | 0b00100000 # I2C Timeout
    myInts = myInts | 0b01000000 # Alarm overflow 
#     myInts = myInts | 0b10000000 # Overflow Mask **Need to disable this to avoid overflow interrups
#     print("Proposed Interrupts: ", myInts)
    i2cbus.write_byte_data(current6ClickAddress, 0x14, myInts)
# 
#     myInts = i2cbus.read_byte_data(current6ClickAddress, 0x14)
#     print("Interrupts currently set: ", myInts)
    
#     myFifo = i2cbus.read_word_data(current6ClickAddress, 0x0A)
#     print("New fifo on chip memory: ", hex(myFifo))
 
#     time.sleep(0.1)
#     mynewval = i2cbus.read_word_data(current6ClickAddress, 0x00)
#     print('current config: ', hex(mynewval))

hiVolt = False # if we've got power going to printer nozzle

def readStatus():
    global activeInterrupt
    myval = i2cbus.read_word_data(current6ClickAddress, 0x02) 
    print('Interrupt Alert Status: ', hex(myval))
    if(myval & 0x01):
        print("Wake up alert")
    if(myval & 0x02):
        print("Conversion ready")        
    if(myval & 0x04):
        print("Overflow Current")
    if(myval & 0x08):
        print("Overflow Voltage")
        global hiVolt
        hiVolt = True
    if(myval & 0x10):
        print("Underflow Voltage")
        hiVolt = False
    if(myval & 0x20):
        print("I2C Timeout")
    if(myval & 0x40):
        print("FIFO Alarm")
    if(myval & 0x80):
        print("FIFO Overflow")
    
#     print("Clearing alert")
    i2cbus.write_word_data(current6ClickAddress,0x02, myval) # clear alert
#     print("Cleared")
    activeInterrupt = False
    
def readOverCurrentThreshold():
    myval = i2cbus.read_byte_data(current6ClickAddress, 0x04) 
    print('Overcurrent Threshold: ', hex(myval))
    
def readVoltage():
    fifod = i2cbus.read_i2c_block_data(current6ClickAddress,0x0E,2)[::-1]
    val = 0
    for x in range(0,2):
        val = val << 8 | fifod[x]
#     print(fifod)
    dvalid = val & 0x00008000
    dsign = val & 0x00001000
    dvalue = val & 0x00000FFF
#     print("Voltage raw: ", dvalue)
    if dsign != 0 :
        dvalue = dvalue - 4096
    if dvalid != 0 :
        dvalue = dvalue / 4095.0 * 36.0
    dvalue = round(dvalue, 2)
#     print("V:", dvalue)
#     i2cbus.write_word_data(current6ClickAddress,0x0A, 0x8000) # flush FIFO

    return dvalue

def readAmps():
    global InputRangeHigh
    fifod = i2cbus.read_i2c_block_data(current6ClickAddress,0x0c,2)[::-1]
    val = 0
    for x in range(0,2):
        val = val << 8 | fifod[x]
    dvalid = val & 0x00008000
    dsign = val & 0x00001000
    dvalue = val & 0x00000FFF
    if dsign != 0 :
        dvalue = dvalue - 4096
    if dvalid != 0 :
        dvalue = dvalue / 4095
    if InputRangeHigh: dvalue = dvalue*5 #correct for high-current mode
    dvalue = round(dvalue, 3)
#     print("A:", dvalue)
    return dvalue

def setOverCurrentThreshold(ampsVal):
#     memory address is 7 bits; 0-127; ranged from 0A - Max (either 1 or 5 depending on setting)
#     Default value = 0x30
    global InputRangeHigh
    
    myval = i2cbus.read_byte_data(current6ClickAddress, 0x04) 
    print('Starting Overcurrent Threshold: ', hex(myval))    
    
#     multFactor = 0.007874
#     if InputRangeHigh: multFactor = 0.03937
#     ampsVal = ampsVal / multFactor *100
#     
    newVal = round(ampsVal*127)
    print(newVal)
    i2cbus.write_byte_data(current6ClickAddress, 0x04, newVal) 
    
    myval = i2cbus.read_byte_data(current6ClickAddress, 0x04)
    myval = round(myval/127, 2)
    if InputRangeHigh: myval = myval * 5
    
    print('Overcurrent Threshold: ', myval)

def setOverVoltageThreshold(VoltsVal):
#     memory address is 6 bits; 0-63; ranged from 0-36v
#     print("Setting Overvoltage Threshold to: ", VoltsVal)
    myval = int(round(VoltsVal / 0.5625, 0))
#     print(myval)
    i2cbus.write_byte_data(current6ClickAddress, 0x05, myval) 
    
    myval = i2cbus.read_byte_data(current6ClickAddress, 0x05) 
    print('Overvoltage Threshold now in chips memory: ', myval)

def setUnderVoltageThreshold(VoltsVal):
#     memory address is 6 bits; 0-63; ranged from 0-36v
#     print("Setting Overvoltage Threshold to: ", VoltsVal)
    myval = int(round(VoltsVal / 0.5625, 0))
#     print(myval)
    i2cbus.write_byte_data(current6ClickAddress, 0x06, myval) 
    
    myval = i2cbus.read_byte_data(current6ClickAddress, 0x06) 
    print('Undervoltage Threshold now in chips memory: ', myval)

def AlertPinCallback(myfault):
    global activeInterrupt
    activeInterrupt = True
    print("**Alert Pin Went High**")

GPIO.add_event_detect(OvercurrentAlertPin, GPIO.RISING)
GPIO.add_event_callback(OvercurrentAlertPin, AlertPinCallback)

def setup():
    global activeInterrupt
    
    global myMaxCurrent
    global myMinCurrent
    global myTurnOnVoltage 
    global myTurnOffVoltage 
#     print("active alert status: ", activeInterrupt)
    enableRelay()
    #enable the current sense pin on Current6Click
    GPIO.output(CurrentSenseEnablePin, GPIO.HIGH)
#     time.sleep(0.5)
#     time.sleep(3)
    setConfig()
 
#     readOverCurrentThreshold()
    setOverCurrentThreshold(.1) # Amps
    setOverVoltageThreshold(3) # set over-voltage limit (Volts)
#     setUnderVoltageThreshold(5) # set under-voltage level
#     print("active alert status: ", activeInterrupt)
    
def main():
    lastPrint = time.time()
    global activeInterrupt
    
    global myMaxCurrent
    global myMinCurrent
    global myTurnOnVoltage 
    global myTurnOffVoltage
    
    print("Program starting")
    running = True
    while running:
        readCount = 0
        while (readVoltage() > myTurnOnVoltage):
            SamplesToAverage = 1
            myV = 0
            for x in range(SamplesToAverage):
#                 readCount += 1
                myV += readVoltage()
            myV = round(myV/SamplesToAverage,2)
            myA = 0
            for x in range(SamplesToAverage):
                myA += readAmps()
            myA = round(myA/SamplesToAverage,2)
            
            readCount += 1
            
            if(myA > myMaxCurrent):
                print("!! Overcurrent Alert!! Shorted wires?")
                print("Readings during fault: V:", myV, " A:", myA, "read/s:", readCount)
                disableRelay()
                time.sleep(.1)
                break
                    
            if(myA < myMinCurrent and myV> myTurnOnVoltage):
                print("Yo dude - fix ya wires! I'm undercurrent")
                print("Readings during fault: V:", myV, " A:", myA, "read/s:", readCount)
                disableRelay()
                time.sleep(.1)
                break

            if(time.time() > lastPrint+1):
                print("V:", myV, " A:", myA, "read/s:", readCount)
                lastPrint = time.time()
                readCount = 0

            if(activeInterrupt):
                readStatus()
                
        if(readVoltage() < myTurnOffVoltage):
            time.sleep(4) # sleep for four seconds
            enableRelay()



try:
    if __name__ =="__main__":
        setup()
        main()

except KeyboardInterrupt:
    print("Keyboard interrupt detected")
    #catch any Cntl+C exits
    pass

except Exception as e:
    print("Exception")
    print(e)
    pass

finally:
    print("cleaning up")
    GPIO.cleanup()

What have I learned?

I'm not someone who does stuff like this every day. In fact very rarely do I do things at a low-level like this without a library and example code; which may explain how I'm running into one after another roadblock to overcome. Every time I get past one issue, I run into another and spend a couple of nights on it. Other competitors don't seem to be having this, but I haven't read too many of their blogs in detail. So perhaps there is enough info in the data sheet, but wasn't clear enough to me. At any rate, I've got nearly a functionally complete system based on the goals I've set forth. I have two more days and will continue to persevere and see what else I can find to overcome the last major hurdle.

I've certainly enjoyed the challenge so far and as always, needed to really push myself to gain the knowledge and do things I haven't done before. 

Video time!

See video demo here -

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

  • Sign in to reply
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