Commit 1e07632d authored by Clement Remi's avatar Clement Remi
Browse files

Merge branch 'ohmpi_1.5' into 'master'

Ohmpi 1.5

See merge request !4
parents c8140e7e f3b95b59
......@@ -7,4 +7,149 @@
7 10 8 9
8 11 9 10
9 12 10 11
10 13 11 12
\ No newline at end of file
10 13 11 12
11 14 12 13
12 15 13 14
13 16 14 15
14 17 15 16
15 18 16 17
16 19 17 18
17 20 18 19
18 21 19 20
19 22 20 21
20 23 21 22
21 24 22 23
22 25 23 24
23 26 24 25
24 27 25 26
25 28 26 27
26 29 27 28
27 30 28 29
28 31 29 30
29 32 30 31
1 7 3 5
2 8 4 6
3 9 5 7
4 10 6 8
5 11 7 9
6 12 8 10
7 13 9 11
8 14 10 12
9 15 11 13
10 16 12 14
11 17 13 15
12 18 14 16
13 19 15 17
14 20 16 18
15 21 17 19
16 22 18 20
17 23 19 21
18 24 20 22
19 25 21 23
20 26 22 24
21 27 23 25
22 28 24 26
23 29 25 27
24 30 26 28
25 31 27 29
26 32 28 30
1 10 4 7
2 11 5 8
3 12 6 9
4 13 7 10
5 14 8 11
6 15 9 12
7 16 10 13
8 17 11 14
9 18 12 15
10 19 13 16
11 20 14 17
12 21 15 18
13 22 16 19
14 23 17 20
15 24 18 21
16 25 19 22
17 26 20 23
18 27 21 24
19 28 22 25
20 29 23 26
21 30 24 27
22 31 25 28
23 32 26 29
1 13 5 9
2 14 6 10
3 15 7 11
4 16 8 12
5 17 9 13
6 18 10 14
7 19 11 15
8 20 12 16
9 21 13 17
10 22 14 18
11 23 15 19
12 24 16 20
13 25 17 21
14 26 18 22
15 27 19 23
16 28 20 24
17 29 21 25
18 30 22 26
19 31 23 27
20 32 24 28
1 16 6 11
2 17 7 12
3 18 8 13
4 19 9 14
5 20 10 15
6 21 11 16
7 22 12 17
8 23 13 18
9 24 14 19
10 25 15 20
11 26 16 21
12 27 17 22
13 28 18 23
14 29 19 24
15 30 20 25
16 31 21 26
17 32 22 27
1 19 7 13
2 20 8 14
3 21 9 15
4 22 10 16
5 23 11 17
6 24 12 18
7 25 13 19
8 26 14 20
9 27 15 21
10 28 16 22
11 29 17 23
12 30 18 24
13 31 19 25
14 32 20 26
1 22 8 15
2 23 9 16
3 24 10 17
4 25 11 18
5 26 12 19
6 27 13 20
7 28 14 21
8 29 15 22
9 30 16 23
10 31 17 24
11 32 18 25
1 25 9 17
2 26 10 18
3 27 11 19
4 28 12 20
5 29 13 21
6 30 14 22
7 31 15 23
8 32 16 24
1 28 10 19
2 29 11 20
3 30 12 21
4 31 13 22
5 32 14 23
1 31 11 21
2 32 12 22
"""
created on January 6, 2020
Update December 2021
Update april 2021
Ohmpi.py is a program to control a low-cost and open hardward resistivity meter OhmPi that has been developed by Rémi CLEMENT(INRAE),Vivien DUBOIS(INRAE),Hélène GUYARD(IGE), Nicolas FORQUET (INRAE), and Yannick FARGIER (IFSTTAR).
"""
print('OHMPI start' )
print('\033[1m'+'\033[31m'+' ________________________________')
print('| _ | | | || \/ || ___ \_ _|')
print('| | | | |_| || . . || |_/ / | |' )
print('| | | | _ || |\/| || __/ | |')
print('\ \_/ / | | || | | || | _| |_')
print(' \___/\_| |_/\_| |_/\_| \___/ ')
print('\033[0m')
print('OhmPi start' )
print('Vers: 1.51')
print('Import library')
import RPi.GPIO as GPIO
import time
from datetime import datetime
import board
import busio
import numpy
import os
import sys
import time , board, busio, numpy, os, sys, json, glob,os.path,adafruit_tca9548a
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
import pandas as pd
import os.path
import json
"""
display start time
"""
from pandas import DataFrame
from datetime import datetime
from adafruit_mcp230xx.mcp23008 import MCP23008
from adafruit_mcp230xx.mcp23017 import MCP23017
import digitalio
from digitalio import Direction
from gpiozero import CPUTemperature
current_time = datetime.now()
print(current_time.strftime("%Y-%m-%d %H:%M:%S"))
"""
hardware parameters
"""
R_ref = 50 # reference resistance value in ohm
coef_p0 = 2.5 # slope for current conversion for ADS.P0, measurement in V/V
coef_p1 = 2.5 # slope for current conversion for ADS.P1, measurement in V/V
coef_p2 = 2.5 # slope for current conversion for ADS.P2, measurement in V/V
coef_p3 = 2.5 # slope for current conversion for ADS.P3, measurement in V/V
export_path = "/home/pi/Desktop/measurement.csv"
R_shunt = 0.2# reference resistance value in ohm
coef_p2 = 2.50# slope for current conversion for ADS.P2, measurement in V/V
coef_p3 = 2.50 # slope for current conversion for ADS.P3, measurement in V/V
offset_p2= 0
offset_p3= 0
integer=10
meas=numpy.zeros((3,integer))
"""
import parameters
"""
with open('ohmpi_param.json') as json_file:
pardict = json.load(json_file)
i2c = busio.I2C(board.SCL, board.SDA) #activation du protocle I2C
mcp = MCP23008(i2c, address=0x20) #connexion I2C MCP23008, injection de courant
ads_current = ADS.ADS1115(i2c, gain=16,data_rate=860, address=0X48)# connexion ADS1115, pour la mesure de courant
ads_voltage = ADS.ADS1115(i2c, gain=2/3,data_rate=860, address=0X49)# connexion ADS1115, pour la mesure de courant
#initialisation desvoie pour la polarité
pin0 = mcp.get_pin(0)
pin0.direction = Direction.OUTPUT
pin1 = mcp.get_pin(1)
pin1.direction = Direction.OUTPUT
pin0.value = False
pin1.value = False
# Initialisation MUX
Elec_A= adafruit_tca9548a.TCA9548A(i2c, 0X76)
Elec_B= adafruit_tca9548a.TCA9548A(i2c, 0X71)
Elec_M= adafruit_tca9548a.TCA9548A(i2c, 0X74)
Elec_N= adafruit_tca9548a.TCA9548A(i2c, 0X70)
"""
functions
"""
# function swtich_mux select the right channels for the multiplexer cascade for electrodes A, B, M and N.
def switch_mux(quadripole):
path2elec = numpy.loadtxt("path2elec.txt", delimiter=" ", dtype=bool)
quadmux = numpy.loadtxt("quadmux.txt", delimiter=" ", dtype=int)
def switch_mux_on(quadripole):
elec_adress=[0x76,0X71,0x74,0x70]
for i in range(0,4):
tca= adafruit_tca9548a.TCA9548A(i2c, elec_adress[i]) #choose MUX A B M or N
if quadripole[i] < 17:
nb_i2C=7
a=quadripole[i]
elif quadripole[i] > 16 and quadripole[i] < 33:
nb_i2C=6
a=quadripole[i]-16
elif quadripole[i] > 32 and quadripole[i] < 49:
nb_i2C=5
a=quadripole[i]-32
elif quadripole[i] > 48 and quadripole[i] < 65:
nb_i2C=4
a=quadripole[i]-48
mcp2 = MCP23017(tca[nb_i2C])
mcp2.get_pin(a-1).direction=digitalio.Direction.OUTPUT
mcp2.get_pin(a-1).value=True
def switch_mux_off(quadripole):
elec_adress=[0x76,0X71,0x74,0x70]
for i in range(0,4):
tca= adafruit_tca9548a.TCA9548A(i2c, elec_adress[i]) #choose MUX A B M or N
if quadripole[i] < 17:
nb_i2C=7
a=quadripole[i]
elif quadripole[i] > 16 and quadripole[i] < 33:
nb_i2C=6
a=quadripole[i]-16
elif quadripole[i] > 32 and quadripole[i] < 49:
nb_i2C=5
a=quadripole[i]-32
elif quadripole[i] > 48 and quadripole[i] < 65:
nb_i2C=4
a=quadripole[i]-48
mcp2 = MCP23017(tca[nb_i2C])
mcp2.get_pin(a-1).direction=digitalio.Direction.OUTPUT
mcp2.get_pin(a-1).value=False
#function to switch off mux
def ZERO_mux(nb_elec):
elec_adress=[0x76,0X71,0x74,0x70]
for i in range(0,4):
for j in range(0,5) :
GPIO.output(int(quadmux[i,j]), bool(path2elec[quadripole[i]-1,j]))
tca= adafruit_tca9548a.TCA9548A(i2c, elec_adress[i]) #choose MUX A B M or N
for y in range(0,nb_elec):
qd=y+1
if qd < 17:
nb_i2C=7
a=qd
elif qd > 16 and qd < 33:
nb_i2C=6
a=qd-16
elif qd > 32 and qd < 49:
nb_i2C=5
a=qd-32
elif qd > 48 and qd < 65:
nb_i2C=4
a=qd-48
mcp2 = MCP23017(tca[nb_i2C])
mcp2.get_pin(a-1).direction=digitalio.Direction.OUTPUT
mcp2.get_pin(a-1).value= False
# function to find rows with identical values in different columns
def find_identical_in_line(array_object):
......@@ -70,12 +161,12 @@ def find_identical_in_line(array_object):
if any(temp > 1):
output.append(i)
return output
#
# read quadripole file and apply tests
def read_quad(filename, nb_elec):
output = numpy.loadtxt(filename, delimiter=" ",dtype=int) # load quadripole file
# locate lines where the electrode index exceeds the maximum number of electrodes
test_index_elec = numpy.array(numpy.where(output > 32))
test_index_elec = numpy.array(numpy.where(output > nb_elec))
# locate lines where an electrode is referred twice
test_same_elec = find_identical_in_line(output)
# if statement with exit cases (rajouter un else if pour le deuxième cas du ticket #2)
......@@ -90,77 +181,79 @@ def read_quad(filename, nb_elec):
else:
return output
def gain_auto(channel):
gain=2/3
if ((abs(channel.voltage)<2.040) and (abs(channel.voltage)>=1.023)):
gain=2
elif ((abs(channel.voltage)<1.023) and (abs(channel.voltage)>=0.508)):
gain=4
elif ((abs(channel.voltage)<0.508) and (abs(channel.voltage)>=0.250)):
gain=8
elif abs(channel.voltage)<0.256:
gain=16
#print(gain)
return gain
# perform a measurement
def run_measurement(nb_stack, injection_deltat, Rref, coefp0, coefp1, coefp2, coefp3, elec_array):
i2c = busio.I2C(board.SCL, board.SDA) # I2C protocol setup
ads = ADS.ADS1115(i2c, gain=2/3) # I2C communication setup
def run_measurement(nb_stack, injection_deltat, R_shunt, coefp2, coefp3, elec_array):
start_time=time.time()
# inner variable initialization
sum_I=0
sum_Vmn=0
sum_Ps=0
# GPIO initialization
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(7, GPIO.OUT)
GPIO.setup(8, GPIO.OUT)
# resistance measurement
# injection courant and measure
mcp = MCP23008(i2c, address=0x20)
pin0 = mcp.get_pin(0)
pin0.direction = Direction.OUTPUT
pin1 = mcp.get_pin(1)
pin1.direction = Direction.OUTPUT
pin0.value = False
pin1.value = False
for n in range(0,3+2*nb_stack-1) :
# current injection
if (n % 2) == 0:
GPIO.output(7, GPIO.HIGH) # polarity n°1
pin1.value = True
pin0.value = False # current injection polarity n°1
else:
GPIO.output(7, GPIO.LOW) # polarity n°2
GPIO.output(8, GPIO.HIGH) # current injection
pin0.value = True
pin1.value = False# injection de courant polarity n°2
start_delay=time.time()
time.sleep(injection_deltat) # delay depending on current injection duration
ads = ADS.ADS1115(i2c, gain=2/3,data_rate=860)# select gain
ads = ADS.ADS1115(i2c, gain=gain_auto(AnalogIn(ads,ADS.P0)),data_rate=860)
Ia1 = AnalogIn(ads,ADS.P0).voltage * coefp0 # reading current value on ADS channel A0
ads = ADS.ADS1115(i2c, gain=2/3,data_rate=860)# select gain
ads = ADS.ADS1115(i2c, gain=gain_auto(AnalogIn(ads,ADS.P1)),data_rate=860)
Ib1 = AnalogIn(ads,ADS.P1).voltage * coefp1 # reading current value on ADS channel A1
ads = ADS.ADS1115(i2c, gain=2/3,data_rate=860)# select gain
ads = ADS.ADS1115(i2c, gain=gain_auto(AnalogIn(ads,ADS.P2)),data_rate=860)
Vm1 = AnalogIn(ads,ADS.P2).voltage * coefp2# reading voltage value on ADS channel A2
ads = ADS.ADS1115(i2c, gain=2/3,data_rate=860)# select gain
ads = ADS.ADS1115(i2c, gain=gain_auto(AnalogIn(ads,ADS.P3)),data_rate=860)
Vn1 = AnalogIn(ads,ADS.P3).voltage * coefp3# reading voltage value on ADS channel A3
GPIO.output(8, GPIO.LOW)# stop current injection
time.sleep(injection_deltat) # Dead time equivalent to the duration of the current injection pulse
I1= (Ia1 - Ib1)/Rref
sum_I=sum_I+I1
Vmn1= (Vm1 - Vn1)
for k in range(0,integer):
meas[0,k] = ((AnalogIn(ads_current,ADS.P0).voltage/50)/R_shunt)*1000 # reading current value on ADS channel A0
meas[1,k] = AnalogIn(ads_voltage,ADS.P0).voltage * coefp2*1000
meas[2,k] = AnalogIn(ads_voltage,ADS.P1).voltage * coefp3*1000 # reading voltage value on ADS channel A2
pin1.value = False; pin0.value = False# stop current injection
end_delay=time.time()
sum_I=sum_I+(numpy.mean(meas[0,:]))
Vmn1=((numpy.mean(meas[1,:]))-(numpy.mean(meas[2,:])))
if (n % 2) == 0:
sum_Vmn=sum_Vmn-Vmn1
sum_Ps=sum_Ps+Vmn1
sum_Vmn=sum_Vmn-Vmn1
sum_Ps=sum_Ps+Vmn1
else:
sum_Vmn=sum_Vmn+Vmn1
sum_Ps=sum_Ps+Vmn1
sum_Vmn=sum_Vmn+Vmn1
sum_Ps=sum_Ps+Vmn1
end_calc=time.time()
cpu = CPUTemperature()
time.sleep((end_delay-start_delay)-(end_calc-end_delay))
# return averaged values
output = pd.DataFrame({
# cpu= CPUTemperature()
output = DataFrame({
"time":[datetime.now()],
"A":elec_array[0],
"B":elec_array[1],
"M":elec_array[2],
"N":elec_array[3],
"Vmn":[sum_Vmn/(3+2*nb_stack-1)],
"I":[sum_I/(3+2*nb_stack-1)],
"R":[sum_Vmn/(3+2*nb_stack-1)/(sum_I/(3+2*nb_stack-1))],
"Ps":[sum_Ps/(3+2*nb_stack-1)],
"nbStack":[nb_stack]
"Vmn [mV]":[(sum_Vmn/(3+2*nb_stack-1))],
"I [mA]":[(sum_I/(3+2*nb_stack-1))],
"R [ohm]":[( (sum_Vmn/(3+2*nb_stack-1)/(sum_I/(3+2*nb_stack-1))))],
# "Rab [KOhm]":[(Tab*2.47)/(sum_I/(3+2*nb_stack-1))/1000],
# "Tx [V]":[Tx*2.47],
"Ps [mV]":[(sum_Ps/(3+2*nb_stack-1))],
"nbStack":[nb_stack],
"CPU temp [°C]":[cpu.temperature],
# "Hardware temp [°C]":[read_temp()-8],
"Time [S]":[(-start_time+time.time())]
# "Rcontact[ohm]":[Rc],
# "Rsoil[ohm]":[Rsoil],
# "Rab_theory [Ohm]":[(Rc*2+Rsoil)]
# Dead time equivalent to the duration of the current injection pulse
})
output=output.round(2)
print(output.to_string())
time.sleep(1)
return output
# save data
......@@ -175,45 +268,28 @@ def append_and_save(path, last_measurement):
with open(path, 'a') as f:
last_measurement.to_csv(f, header=True)
"""
Initialization of GPIO channels
"""
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
"""
Initialization of multiplexer channels
"""
pinList = [12,16,20,21,26,18,23,24,25,19,6,13,4,17,27,22,10,9,11,5] # List of GPIOs enabled for relay cards (electrodes)
for i in pinList:
GPIO.setup(i, GPIO.OUT)
GPIO.output(i, GPIO.HIGH)
"""
Main loop
"""
N=read_quad("ABMN.txt",pardict.get("nb_electrodes")) # load quadripole file
N=read_quad("dd.txt",pardict.get("nb_electrodes")) # load quadripole file
if N.ndim == 1:
N = N.reshape(1, 4)
ZERO_mux(pardict.get("nb_electrodes"))
for g in range(0,pardict.get("nbr_meas")): # for time-lapse monitoring
for i in range(0,N.shape[0]): # loop over quadripoles
# call the switch_mux function to switch to the right electrodes
switch_mux(N[i,])
switch_mux_on(N[i,])
# run a measurement
current_measurement = run_measurement(pardict.get("stack"), pardict.get("injection_duration"), R_ref, coef_p0, coef_p1, coef_p2, coef_p3, N[i,])
# save data and print in a text file
current_measurement = run_measurement(pardict.get("stack"), pardict.get("injection_duration"), R_shunt, coef_p2, coef_p3, N[i,])
switch_mux_off(N[i,])
#save data and print in a text file
append_and_save(pardict.get("export_path"), current_measurement)
# reset multiplexer channels
GPIO.output(12, GPIO.HIGH); GPIO.output(16, GPIO.HIGH); GPIO.output(20, GPIO.HIGH); GPIO.output(21, GPIO.HIGH); GPIO.output(26, GPIO.HIGH)
GPIO.output(18, GPIO.HIGH); GPIO.output(23, GPIO.HIGH); GPIO.output(24, GPIO.HIGH); GPIO.output(25, GPIO.HIGH); GPIO.output(19, GPIO.HIGH)
GPIO.output(6, GPIO.HIGH); GPIO.output(13, GPIO.HIGH); GPIO.output(4, GPIO.HIGH); GPIO.output(17, GPIO.HIGH); GPIO.output(27, GPIO.HIGH)
GPIO.output(22, GPIO.HIGH); GPIO.output(10, GPIO.HIGH); GPIO.output(9, GPIO.HIGH); GPIO.output(11, GPIO.HIGH); GPIO.output(5, GPIO.HIGH)
print(i+1,'/',N.shape[0])
print('end of sequence')
ZERO_mux(pardict.get("nb_electrodes"))
time.sleep(pardict.get("sequence_delay")) #waiting next measurement (time-lapse)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Fritzing Bill of Materials</title>
<style type="text/css">
.meta {
font-size: small;
margin: 0.4em 0;
}
table {
border-collapse: collapse;
}
th {
font-weight: bold;
text-align: left;
border-bottom: 1px solid black;
padding: 0.1em 1.5em 0.2em 0.1em;
}
td {
border-bottom: 1px solid #CCC;
padding: 0.5em 1.5em 0.5em 0.1em;
}
.props {
font-size: smaller;
}
</style>
</head>
<body>
<h1>Bill of Materials: card_measure_raspberry_v3.04.fzz</h1>
<p class="meta">C:/Users/remi.clement/Documents/28_ohmpi_all_git/PCB_file_measurement card/card_measure_raspberry_v3.04.fzz</p>
<p class="meta">vendredi, décembre 18 2020, 21:47:29</p>
<h2>Assembly List</h2>
<table>
<thead>
<tr>
<th>Label</th>
<th>Part Type</th>
<th>Properties</th>
</tr>
</thead>
<tbody>
<tr>
<td>C1</td>
<td>Ceramic Capacitor</td>
<td class="props">boîtier 100 mil [THT, multilayer]; capacité 100nF; tension 50V</td>
</tr><tr>
<td>C2</td>
<td>Ceramic Capacitor</td>
<td class="props">boîtier 100 mil [THT, multilayer]; capacité 100nF; tension 50V</td>
</tr><tr>
<td>C3</td>
<td>Ceramic Capacitor</td>
<td class="props">boîtier 100 mil [THT, multilayer]; capacité 100nF; tension 50V</td>
</tr><tr>
<td>C4</td>
<td>Ceramic Capacitor</td>
<td class="props">boîtier 100 mil [THT, multilayer]; capacité 100nF; tension 50V</td>
</tr><tr>
<td>Composant1</td>
<td>Adafruit ADS1115 16Bit I2C ADC</td>
<td class="props">variante variant 2; numéro du composant Adafruit #1115</td>
</tr><tr>
<td>Fuse1</td>
<td>Fuse </td>
<td class="props">variante variant 1; datasheet https://www.mouser.fr/datasheet/2/240/Littelfuse_Fuse_251_253_Datasheet_pdf-522535.pdf; manufacturer little fuse</td>
</tr><tr>
<td>J6</td>
<td>Screw terminal - 14 pins</td>
<td class="props">Taille du trou 1.0mm,0.508mm; espacement des broches 0.137in (3.5mm); boîtier THT; broches 14</td>
</tr><tr>
<td>LM358-1</td>
<td>LM358</td>
<td class="props">boîtier dip08; chip lm358</td>
</tr><tr>
<td>LM358-2</td>
<td>LM358</td>