After my Android tablet provided good service cutting up onions an immediate need arose to weigh the ingredients for the onion tart. With a HX711 load cell amplifier , a load cell, an Arduino, and AndroWish the tablet is even more useful.
See Also - Interfacing PS2 Keyboard with Arduino and Androwish
Here is the Arduino program
#include "HX711.h" #include <EEPROM.h> #include <avr/wdt.h> // HX711.DOUT - pin 3 (digital) // HX711.PD_SCK - pin 2 (digital) HX711 scale(3, 2); // parameter "gain" left out // the default value 128 is used by the library int run; // when true, measure in main loop int nrd; // number measurements/actions (1..9) to average int inChar; // char from serial line byte inByte; // byte from/to EEPROM long lVal; // used e.g. for R/W of scale value to EEPROM double dVal; // used for calibration void setup() { run = 0; nrd = 5; Serial.begin(9600); lVal = 0; EEPROM.get(0, inByte); lVal |= inByte; EEPROM.get(1, inByte); lVal <<= 8; lVal |= inByte; EEPROM.get(2, inByte); lVal <<= 8; lVal |= inByte; EEPROM.get(3, inByte); lVal <<= 8; lVal |= inByte; scale.set_scale(lVal); // use -680000 for 3 kg load cell scale.tare(nrd); scale.power_down(); } void wrcal(long val) { byte b; b = val >> 24; EEPROM.update(0, b); b = val >> 16; EEPROM.update(1, b); b = val >> 8; EEPROM.update(2, b); b = val; EEPROM.update(3, b); } void loop() { if (Serial.available() > 0) { inChar = Serial.read(); if (inChar == -1) { return; } switch (inChar) { case 'S': // STOP case 's': if (run) { scale.power_down(); } run = 0; break; case '?': // PRINT CALIBRATION SCALE Serial.print('C'); lVal = scale.get_scale(); Serial.println(lVal); break; case 'C': // SET CALIBRATION SCALE case 'c': lVal = Serial.parseInt(); if (lVal) { scale.set_scale(lVal); wrcal(lVal); } Serial.print('C'); lVal = scale.get_scale(); Serial.println(lVal); break; case 'T': // TARE case 't': if (!run) { scale.power_up(); } scale.tare(nrd); if (!run) { scale.power_down(); } break; case 'V': // MEASURE, GIVE RAW VALUE case 'v': if (!run) { scale.power_up(); } lVal = scale.read_average(nrd) - scale.get_offset(); if (!run) { scale.power_down(); } Serial.print('V'); Serial.println(lVal); break; case 'M': // MEASURE, GIVE GRAMS case 'm': if (!run) { scale.power_up(); Serial.println(scale.get_units(nrd) * 1000.0, 1); scale.power_down(); } break; case 'R': // RUN case 'r': if (!run) { scale.power_up(); } run = 1; break; case 'X': // REBOOT case 'x': wdt_enable(WDTO_15MS); while (1) { } break; case 'Y': // DO CALIBRATION GIVEN GRAMS case 'y': dVal = Serial.parseFloat() / 1000.0; if (dVal) { if (!run) { scale.power_up(); } lVal = scale.read_average(nrd) - scale.get_offset(); if (!run) { scale.power_down(); } scale.set_scale(lVal / dVal); lVal = scale.get_scale(); wrcal(lVal); Serial.print('C'); Serial.println(lVal); } break; default: // # MEASUREMENTS FOR AVERAGING if (inChar >= '1' && inChar <= '9') { nrd = inChar - '0'; } break; } } if (run) { Serial.println(scale.get_units(nrd) * 1000.0, 1); } }
And this is the Tcl script for AndroWish which can be run with undroidwish, too.
JM 2018/9/1 - I had to remove NONBLOCK when running with undroidwish under Windows.
and to COM4 where my Arduino was connected (-:
# AndroWish script to interface Arduino with HX711 scale amplifier and load cell # # Wiring: # # +--------------+ +-------------+ +-----------+ +------+ # | Android | | +5V|---| HX711 E+|---| | # | Device |--USB--| Arduino GND|---| E-|---| Load | # | w/ AndroWish | | D3|---|DOUT A-|---| Cell | # +--------------+ | D2|---|PC_SCK A+|---| | # +-------------+ +-----------+ +------+ wm attributes . -fullscreen 1 catch {borg screenorientation landscape} sdltk screensaver 0 option add *Radiobutton.borderWidth 3 option add *Radiobutton.highlightThickness 0 option add *Button.borderWidth 3 option add *Button.highlightThickness 0 # Open/reopen serial line to Arduino proc reopen {} { after cancel reopen if {[sdltk android]} { if {[catch { set dev [lindex [usbserial] 0] set ::SCALE(chan) [usbserial $dev] fconfigure $::SCALE(chan) -blocking 0 -translation binary \ -mode 9600,n,8,1 fileevent $::SCALE(chan) readable readv set ::SCALE(value) "online" puts -nonewline $::SCALE(chan) "S" } err]} { sdltk log verbose $err set ::SCALE(value) "offline" after 1000 reopen .run configure -background [lindex [.run configure -background] 3] return } } else { if {[catch { set ::SCALE(chan) [open "/dev/ttyUSB0" {RDWR NONBLOCK}] fconfigure $::SCALE(chan) -translation binary -mode 9600,n,8,1 fileevent $::SCALE(chan) readable readv set ::SCALE(value) "online" } err]} { sdltk log verbose $err set ::SCALE(value) "offline" after 1000 reopen .run configure -background [lindex [.run configure -background] 3] return } } sendc 5 S } # Send command string to Arduino proc sendc args { foreach cmd $args { if {[catch { puts -nonewline $::SCALE(chan) $cmd flush $::SCALE(chan) } err]} { sdltk log verbose $err catch {close $::SCALE(chan)} set ::SCALE(value) "offline" after 1000 reopen watchdog -1 return } elseif {$cmd eq "R"} { watchdog 1 } elseif {$cmd eq "S"} { watchdog -1 } elseif {$cmd eq "T"} { set ::SCALE(value) [format "%7.1fg" 0] } elseif {[string match {[1-9]} $cmd]} { set ::SCALE(avg) $cmd } } } # Monitor continuous measurement proc watchdog {{on 0}} { after cancel watchdog if {$on > 0} { if {[.run cget -background] ne "green3"} { .run configure -background green3 \ -activebackground green3 } after 2000 watchdog } else { .run configure -background [lindex [.run configure -background] 3] \ -activebackground [lindex [.run configure -activebackground] 3] } } # Read data from Arduino proc readv {} { if {[eof $::SCALE(chan)]} { sdltk log verbose "EOF detected" catch {close $::SCALE(chan)} set ::SCALE(value) "offline" after 1000 reopen watchdog -1 return } if {[catch {gets $::SCALE(chan) v} err]} { sdltk log verbose $err catch {close $::SCALE(chan)} set ::SCALE(value) "offline" after 1000 reopen watchdog -1 return } if {($v ne "") && [scan $v %g vv]} { set ::SCALE(value) [format "%7.1fg" $vv] after cancel watchdog after 2000 watchdog } } # Stop Arduino and exit proc stop_and_exit {} { sendc S exit } # Start calibration on Arduino proc docalib {} { sendc "Y$::SCALE(calg)\r" set ::SCALE(caldone) 1 } # Cancel calibration proc calcancel {} { set ::SCALE(caldone) -1 } # Calibration dialog proc calib {} { toplevel .calib wm title .calib "Calibration" wm transient .calib . wm protocol .calib WM_DELETE_WINDOW calcancel label .calib.l1 -text "1. Remove everything from the load cell." \ -font {Helvetica -25} -anchor w label .calib.l2 -text "2. Tare the load cell by pressing this button. " \ -font {Helvetica -25} -anchor w button .calib.tare -text "Tare" -command {sendc T} -font {Helvetica -30} \ -width 8 label .calib.l3 -text "3. Place a known weight on the load cell." \ -font {Helvetica -25} -anchor w label .calib.l4 -text "4. Enter that weight in grams here: " \ -font {Helvetica -25} -anchor w entry .calib.grams -width 10 -textvariable ::SCALE(calg) -font {Courier -30} label .calib.l5 -text "5. Perform calibration by pressing this button. " \ -font {Helvetica -25} -anchor w button .calib.cal -text "Calibrate" -command docalib -font {Helvetica -30} \ -width 8 grid .calib.l1 -row 0 -column 0 -pady 20 -sticky w -padx 10 grid .calib.l2 -row 1 -column 0 -pady 20 -sticky w -padx 10 grid .calib.tare -row 1 -column 1 -padx 20 grid .calib.l3 -row 2 -column 0 -pady 20 -sticky w -padx 10 grid .calib.l4 -row 3 -column 0 -pady 20 -sticky w -padx 10 grid .calib.grams -row 3 -column 1 -padx 20 grid .calib.l5 -row 4 -column 0 -pady 20 -sticky w -padx 10 grid .calib.cal -row 4 -column 1 -padx 20 bind .calib <Escape> {calcancel ; break} bind .calib <Key-q> {calcancel ; break} bind .calib <Key-Q> {calcancel ; break} ::tk::PlaceWindow .calib ::tk::SetFocusGrab .calib .calib sendc S 9 vwait ::SCALE(caldone) ::tk::RestoreFocusGrab .calib .calib destroy } # Main screen label .weight -textvariable SCALE(value) -font {Courier -160} grid .weight -row 0 -column 0 -columnspan 9 -sticky news button .tare -text "Tare" -command {sendc T} -font {Helvetica -30} button .run -text "Run" -command {sendc R R} -font {Helvetica -30} button .stop -text "Stop" -command {sendc S} -font {Helvetica -30} button .single -text "Meas." -command {sendc M} -font {Helvetica -30} grid .tare .run .stop .single -row 1 -rowspan 2 -sticky news foreach i {1 2 3 4 5} { radiobutton .m$i -text $i -command [list sendc $i] -indicatoron 0 \ -value $i -variable ::SCALE(avg) -font {Helvetica -30} grid .m$i -row 1 -column [expr {$i + 3}] -sticky news } foreach i {6 7 8 9} { radiobutton .m$i -text $i -command [list sendc $i] -indicatoron 0 \ -value $i -variable ::SCALE(avg) -font {Helvetica -30} grid .m$i -row 2 -column [expr {$i - 2}] -sticky news } button .cal -text "Cal." -command calib -font {Helvetica -30} grid .cal -row 2 -column 8 -sticky news grid columnconfigure . 0 -uniform largebutton grid columnconfigure . 1 -uniform largebutton grid columnconfigure . 2 -uniform largebutton grid columnconfigure . 3 -uniform largebutton grid columnconfigure . 4 -uniform smallbutton grid columnconfigure . 5 -uniform smallbutton grid columnconfigure . 6 -uniform smallbutton grid columnconfigure . 7 -uniform smallbutton grid columnconfigure . 8 -uniform smallbutton grid rowconfigure . 0 -weight 12 grid rowconfigure . 1 -weight 1 -uniform button grid rowconfigure . 2 -weight 1 -uniform button grid columnconfigure . all -weight 1 # Keyboard shortcuts to buttons etc. bind all <Key-0> {.tare invoke} bind all <Key-t> {.tare invoke} bind all <Key-T> {.tare invoke} bind all <Key-r> {.run invoke} bind all <Key-R> {.run invoke} bind all <Key-s> {.stop invoke} bind all <Key-S> {.stop invoke} bind all <Key-m> {.single invoke} bind all <Key-M> {.single invoke} foreach i {1 2 3 4 5 6 7 8 9} { bind all <Key-$i> [list .m$i invoke] } bind all <Key-q> stop_and_exit bind all <Key-Q> stop_and_exit bind all <Escape> stop_and_exit bind all <Break> stop_and_exit reopen