Closed brauliobarahona closed 4 years ago
The code was initially written for an ESP8266 with the genuine micropython from micropython.org. This esp8266 supports true IRQ, when called with the option hard=True
, in contrast to a callback, which is way slower. The setup line in my application code, which samples at 250/s, is below.
self.irq_pin.irq(handler=_sample, trigger=Pin.IRQ_FALLING, hard=True)
But this application can also be set to lower sample rates. Since the PyCom variant of Micropython does not support the hard IRQ mode, sample rates are limited by the callback mechanism. This is more convenient for coding, but bad in matters of speed. In that case, polling or using a timer based mechanism using read_rev should be faster, because sampling and starting the callback are then overlapping.
I just recalled that I had a test & discussion about Pin interrupt latency at earlier times: https://forum.pycom.io/topic/936/pin-interrupt-latency In that test, made wit a LoPy, the latency was 0.3-0.9ms, which still would be good for a 250 samples/s test loop. So I grab a Lopy4 and see, what's up now. Give me a few hours.
So it was faster. It turned out that I had done that before. Test item is LoP4, Firmware 1.20.1.r1 homebrew. That is one without PyBytes. The sample code is below. Sampling rate of 250 is possible, given that the device is not too busy. 512 samples were collected in 2035 ms. The time between the alert pulse and the ack in the wait loop is about 1.5 ms, giving your code an average of 2.5 ms to deal with data. That's not a lot. Timing snapshot: https://hidrive.ionos.com/lnk/UU4mK5Z5 Channel 0 is the ADS1115 trigger, Channel 1 is the ack response by the script.
from machine import I2C, Pin, Timer
import ads1x15
from array import array
import utime
i2c = I2C(baudrate=400000) # Default pins P9 and P10
addr = 72
gain = 1
ads = ads1x15.ADS1115(i2c, addr, gain)
index_put = 0
_BUFFERSIZE = const(512)
data = array("h", [0] * _BUFFERSIZE)
irq_pin = Pin("P11", Pin.IN, Pin.PULL_UP)
ack_pin = Pin("P8", Pin.OUT, value=1)
#
# Interrupt service routine zum messen
# diese wird vom Timer-interrupt aktiviert
#
def sample_auto(x, adc=ads.alert_read, data=data):
global index_put
if index_put < _BUFFERSIZE:
data[index_put] = adc()
index_put += 1
ads.conversion_start(5, 1) # 250 samples/s, channel 1
irq_pin.callback(handler=sample_auto, trigger=Pin.IRQ_FALLING)
start = utime.ticks_ms()
try:
index = index_put
while index_put < _BUFFERSIZE:
if index != index_put:
ack_pin(0)
ack_pin(1)
index = index_put
except:
pass
irq_pin.callback(handler=None)
end = utime.ticks_ms()
#
# at that point data contains 512 samples acquired at the given rate
#
print("Time for sampling (ms): ", index_put, end - start)
for _ in range(1, _BUFFERSIZE):
print(data[_], data[_] - data[_ - 1])
So, it yet does not work in my setup: Pycom MicroPython 1.20.1.r1 [69dd8b5d-dirty] on 2019-11-22; LoPy4 with ESP32
- interacting through REPL (Atom-pymakr on MacOS). After adjusting the I2C
set up to my own, it simply hangs. Let me try and get further...
Maybe the I2C address of your device is different. What is the result to i2c.scan. And yes, the test code will not terminate until 512 samples have been read. P.S.: So you are crafting your own firmware images too?
yes, as mentioned the setup of the I2C I adjust accordingly ... here:
>>> i2c.scan()
[8, 30, 41, 64, 69, 72, 73, 88, 96]
#
# Interrupt service routine zum messen
# diese wird vom Timer-interrupt aktiviert
#
def sample_auto(x, adc=ads.alert_read, data=data):
global index_put
if index_put < _BUFFERSIZE:
data[index_put] = adc()
index_put += 1
This callback seems to be a problem for me ... yesterday I was debugging line by line and I noticed this little issue. However, that should not be the problem. But in any a case index_put
(nor index
in your new version) update.
start = utime.ticks_ms()
try:
index = index_put
while index_put < _BUFFERSIZE:
if index != index_put:
ack_pin(0)
ack_pin(1)
index = index_put
except:
pass
This code runs well for me. I used it to create the diagram. But I have the code in a separate script file, while you seem to inject it via pymakr. That makes a difference with respect to BUFFERSIZE. When executed from a script file, it behaves well. When you inject code via pymakr, it actually runs at REPL level, which treats symbols starting with different. You could simply omit the _
hey, yes, indeed I tried that but the bug in my case is elsewhere, it seems that the callback is simply not calling the handler ... I switched now to Pycom MicroPython 1.20.1 [v1.11-12f4ce0]
and start from scratch. I need to understand better the I2C
setup and the Pin
class ...
Why don't you simply copy the script into the file system of your Lopy and call it with import filename
?
In principle, you initialise these pins
irq_pin = Pin("P11", Pin.IN, Pin.PULL_UP)
ack_pin = Pin("P8", Pin.OUT, value=1)
then set up a function to read from the adc sample_auto()
, and a callback irq_pin.callback()
where the callback function is sample_auto
and it is trigger by Pin.IRQ_FALLING
... is this callback supposed to be immediately executed or only when you call ack_pin()
?
Forget ack_pin. That was just added for signalling. The callback is triggered when the sensor signals that the data is ready by toggling the alert line. For the LoPy to respond, you need in addition to the i2c bus a connection between the alert output of the ADC to P11.
Why don't you simply copy the script into the file system of your Lopy and call it with
import filename
?
same result, it sort of hangs ... I say this because I modified the buffer to a small number, and it seems to take too long, when I stop it data
has not recorded any values
Did you connect P11 to the alert output of the ADS1115?
Did you connect P11 to the alert output of the ADS1115?
nope, I am trying to figure that out, for now I simply think my I2C
set up is fine because I can read what I think are the correct sensor values with ads.alert_read()
Without the link between ADS1115 alert and the LoPy P11 the callback function will not be called.
ok, now P11 is physically connected to my adc ALERT/RDY - but conversion_start
and write_register
throw an I2C bus error when conversion_start
is called ...
Check if gnd sda and scl are still connected. You need these for data transfer. Edit: use i2c.scan() to check, whether I2c is still working. Maybe you touched the addr pin at the chip and changed the address.
indeed, i2c
was broken (had a typo) ... now I am back to the previous state: the callback is not working, but in principle now the P11 is connected to ALERT/RDY :/
What kind of board are you using? Does it use an ADS1115? During our last conversion I tested the above script again with by breakouts, and it worked fine.
huy, yes, I hope so, so the LoPy4 + Pysense V1.1 + ADS1115 + some other stuff ... can you point me to the documentation of i2c.scan()
?
at the moment this is how I define the callback
def sample_auto(x, adc=ads.alert_read, data=data):
# global index_put
# if index_put < BUFFERSIZE:
data[index_put] = adc()
index_put += 1
however, it is never called ... after the program runs, the connection to the ADC seems to be working
>>> ads.alert_read()
4739
however, data
has not been populated.
is there a serious documention? :) - for example, describing in detail the outputs of i2c.scan()
?
Sorry, I know this looks half wrong:
at the moment this is how I define the callback
def sample_auto(x, adc=ads.alert_read, data=data): # global index_put # if index_put < BUFFERSIZE: data[index_put] = adc() index_put += 1
but I tried different stuff and to me it is clear that sample_auto
is never called.
You must not comment index_put, because it is also used by the "main" code to tell, whether the data has been filled. A call to i2c.scan() just returns a list of addresses for the devices on the I2C bus. It should return the address of you ADC adapter.
the key steps in your snippet, once everything is instantiated are two as far as I understand
(1) establish the callback
irq_pin.callback(handler=sample_auto, trigger=Pin.IRQ_FALLING)
(2) start the conversion
ads.conversion_start(0, 0) # 250 samples/s, channel 1
conversion will then go on, perform the callback (which in principle populates data
) until when? in principle until index_put < BUFFERSIZE
? then conversion stops when the callback is removed? i.e.
irq_pin.callback(handler=None)
You must not comment index_put, because it is also used by the "main" code to tell, whether the data has been filled. A call to i2c.scan() just returns a list of addresses for the devices on the I2C bus. It should return the address of you ADC adapter.
indeed, I thought so, but the callback is simply not called - a print()
statement could tell, right?
so, besides testing how well and how fast can I sample from the ADC I am mainly interested in controlling to some extend the sampling rate and data recording rate, thus I wanted to pass a time object to the call back to have a neat time stamp next to the sensor values ... however, at the moment this seems rather cumbersome ... since I do not really need such a fast sampling rate in any case, I think can live just calling ads.alert_read()
- will probably need to move on like this.
conversion will then go on, perform the callback (which in principle populates data) until when?
Conversion will go infinite, but putting data into the buffer will stop when index_put == BUFFERSIZE, and that's what the main loop is waiting for. irq_pin.callback(handler=None)
stops the callback being called, to avoid trouble when the main code has finished.
If you do not use interrupt or timer base conversion, I recommend to use the read() or read_rev() method. The difference:
read() starts a conversion, waits until it is finished and then it returns the result.
read_alert() assumes that a conversion is finished and just reads the result.
read_rev() reads the result and starts the next conversion. So it performs the actions of read(), but in opposite order. It saves some time, because there is not waiting in the call. But is up to the calling code not to call it again toot early.
I am testing your sample code for continuous sampling trigger by the ADC, however, there are a couple of things that are not clear. One of them is the
irq
attribute ofPin
. Is the linesupposed to work on ESP32 (i.e. LoPy4 + pysense)? In my case, it does not:
That said, I modified a few lines to instantiate the
I2C
bus, and I can manage to run your example using thecallback
method inPin
. However, it does not seem to actually change the data rate of the ADC (I get around 34 sps, no matter the rate -- though I am executing this in REPL). You mentioned you reach 252 sps? Were you able to test different rates?Help appreciated.