AML-S905X-CC with GP2Y0A21YK0F IR Distance Sensor

From AliExpress, I’ve purchased a few of the below IR distance sensors.



I intend to read the measurements from this device via the AML-S905X-CC.

I understand these distance sensors output a DC voltage that represents distance. I’m not sure what my exact model will output, but based on this tutorial I found, the ranges are 0.4v to 3.2v or something like that.

Also, based on documentation here concerning the AML-S905X-CC, it is able to safely measure voltages up to 1.8v, and it is critical to not exceed 1.8v.

This means I’ll need a voltage divider circuit to bring the maximum voltage that my device outputs down to 1.8v exactly. This also means that I’ll need to map the new voltages that are output to distances via a customized mathematical formula. Which means I’ll need to build a testing setup to adjust distances and take several measurements, to produce an accurate formula.

All this is in pursuit of improving my tank bot, to have multiple types of sensors that can be utilized.

Ok, first challenge I’ve realized is that there are two SAR ADC pins that can read voltages, and I want to use four sensors. This means I’ll need to share the two SAR ADC pins somehow. Initial thoughts:

  1. Create a way to turn off / on the IR distance sensors via GPIO outputs somehow. I’m thinking low current solid-state relays or something similar. Not sure how much the devices will like this frequent turning off/on or how long the device takes to initialize. This is probably not the best approach.

  2. Figure out how to switch between reading one value and another. The approach here would be the same as #1, figure out how to use a low current solid state relay or something similar to turn one output off and another on, and vice versa.

Solid state relay? I guess that would work, but it’s overkill. A plain old CMOS analog switch will multiplex the sensors (controlled by a few gpio outputs) and then you only need a single divider. CD4051 is the one I remember, gives you 8 inputs with, of course, a 3 bit select code. Or 4 inputs with 2 select bits and the other tied low. If the sensors really stay under 3.3V you shouldn’t even need any level shifters for the control lines…

1 Like

You can just power and unpower parts of the circuit with a GPIO. It depends on the current draw of the sensor. GPIOs provide a few milliamps of current and if that is sufficient, you can use them to power and unpower the voltage divider and then use the SAR ADCs to determine the voltage.

You have to design the circuit so that the unpowered section does not affect the voltage on the powered section which would need some electrical engineering design that is beyond the scope of what we can assist with.

@mmaney Your suggestion seemed very solid, I ordered several CD4051 and have them now. I went with this because of the switching frequency that the IC is capable of. If I read the data sheet correctly, at 5v input it can switch at something like 9 kHz on the low-end, which is plenty fast. @librecomputer I was worried with how acccurate the device would be if I powered it on/off rapidly. Also, I’m starting to run out of GPIOs :sweat_smile:

So I have given this GP2Y0A21YK0F IR Distance Sensor 5v input and have played around, reading the output on the yellow wire with a multimeter. The maximum voltage I’m seeing output is 3.1v.

I’ve hooked up the distance sensor through the CD4051BE integrated circuit, the maximum voltage coming out is 2.16v

These are my plans thus far. The picture only shows one distance sensor, but with this setup you can have up to 8, all using just one of the Le Potato’s SAR ADC input pins.


Here are the reference links that helped me to figure this stuff out:

… output is 3.1v.
… voltage coming out is 2.16v

My immediate thought was that this seemed like far too much loss. Then I saw that you’re loading the output with only 600 ohms! That’s an awfully low load - is there something about the IR sensor that calls for such a heavy load? A lot of the loss is probably in the 4051’s on resistance, and that varies from device to device, with temperature, even somewhat between the eight channels… I think I’d start with something nearer 10K total, which will give you nearer to that 3.2 raw output, so you’ll need a different ratio.

The other issue that I glossed over (or just ignored, probably) was that the ADC runs to the beat of its own drum, and that will limit how long it can be from switching the input select (GPIO pins) to getting an accurate reading from the converter. I don’t know what that’s set to by default. For now you can probably just throw a few msec delay between the GPIO change and reading the converted value, but that is going to limit the supportable sampling rate, especially when you add more sensors to sample.

that 2.16v was with no resistance at all. I was just measuring the voltage coming out of pin 3 with a multimeter. I will do some more experiments with more resistance and see what happens. I’m pretty novice with electronics, so I am really appreciative of your comments.

– some hours later –

This sensor is funny, or this IC is funny, I’m not sure which. The math just doesn’t work out, but the multimeter does not lie. Below are a few tests, with this IR sensor connected to the IC, and these voltage dividers connected to pin3. Thinking I’m going to settle with 100ohms and 8k2ohms for the divider. I think I’ll design a simple PCB for this IC, because I probably can’t continue to just glue and ziptie ICs with wires soldered to pins all over my project :joy:

Immediate thought: connect the input to 3.3V and stop waving your hand in front of the sensor (or whatever). That should also eliminate any potential issues with loading the sensor. That 2.16 output unloaded makes me wonder if the sensor wasn’t giving you the expected 3.2. Or something…

Longer range, get yourself a solderless breadboard - you’ll need one if you’re going to continue messing about with interfacing sensors and so forth. (That’s a set I got recently, probably because the seller had a couple other things I needed.)

1 Like

Ok, I have figured out my mystery with the voltage divider. It’s because I left pins A, B, and C on the CD4051 open rather than properly setting them high / low. After doing this, it’s working exactly as expected with no noticeable voltage drop. Total noob mistake, live and learn I guess.

I designed a breakout board for these sensors to connect to the Le Potato SAR ADCs using LibrePCB, ordered the boards through LibrePCB’s integrated ordering process, and have soldered them up and done some testing.

Final resistor values for the voltage divider are 4k7 and 5k6, with the 5k6 on the ground side. This produces a max of 1.63v on the output pin.

I am now ready to connect this to the Le Potato.

Here is the breakout board on GitHub, you should be able to open it with LibrePCB. The schematic does not contain any actual parts, but you should be able to order the bare board and solder all the things you need onto it. I licensed the project with the MIT license so anyone can do whatever they want with it.

Here are some screenshots and pictures of the board.



Just hooked this board up to the Le Potato, exciting!

I’m getting this sort of output.

The below measurements are with the sensor not blocked. I.e. distance being measured is “infinite”. While I took these readings, the multimeter (which I also had hooked up to measure the same) was bouncing around between 0.08v and 0.15v

# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
79
# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
94
# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
105
# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
118
# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
168

The below measurements are with the sensor being held by my soldering third-arm, and the third-arm holding a piece of paper above the sensor at a fixed distance. The multimeter was reading between 1.192v and 1.196v.

# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
2641
# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
2608
# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
2602
# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
2644
# cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
2652

I need to figure out what these numbers output by the Le Potato mean and how to correlate them to actual voltages. Or, maybe the correlation between these numbers and actual voltages doesn’t matter, and I can create a formula to convert these numbers to distances.

After some more tinkering, the maximum value from the SARADC seems to be 4095, which suggests a 12 bit value.

Assuming the ADC value is 0 at 0v, and assuming the ADC value is 4095 at 1.8v, one can assume the following:

1.8 ÷ 4095 = 0.00043956

This would indicate each integer within the 12 bit range represents 0.00043956v. This allows us to create the following formula by rearranging things with algebra.

Using this formula, solving voltage for 2641, we get 1.16v, which is nearly what the multimeter was reading for that SARADC value.

Though I’m still not totally sold on the need to convert to a voltage… I think I can work with the raw SAR ADC values. I basically just need to map them to distances.

The SARADC is 1.8V!!! Do not put anything over 1.8V. We have mentioned this repeatedly. Your calculation is incorrect.

1 Like

Ah yes, my mistake. I will correct the post. I don’t know why I wrote 3.8, it should have been 1.8

1 Like

Wayne: Yep, it’s a 12 bit value and a 1.8V nominal reference voltage. At least for the distance sensor there’s little point in converting the AD output into volts other than verifying that it’s working as expected (as in the above) or if you had a calibration on hand expressed in volts.

The sketchy datasheet that’s all I’ve been able to find for the AML chips (it’s little more than a list of registers) doesn’t mention it, so I’m not sure where I did find the 1.8V and 12 bit info. It has some sort of always running (as configured by DT and/or driver init?) sample/convert controller which will surely complicate your life when you get to using additional sensors through that mux. The datasheet lists the registers that you would need to frob to control it, but doesn’t explain how it all works, hence how you should change things.

But perhaps I’m being unfair - the AML chips aren’t really intended as embedded devices, unlike the old TI SOC that BeagleBone uses, so it should perhaps be expected that its documentation is more of a gloss than useful. I mention it because the Sitara also has a similar ADC control machine, and it is documented, which is why I can sort of understand the AML’s version of that.

Just for comparison, the Tech Ref Manual for the Sitara series is over 5000 pages, with chip-specific documents in the 300 page range each.

1 Like

@mmaney Thanks, it worked out. :slight_smile: I took the IC you suggested and built a PCB around it for the robot. All working great!

I am still using the exact same circuit as above in the LibrePCB file, though I’ve iterated several times on the layout of the circuit to make the board fit exactly with pre-existing mounts on the little plastic chassis I’m working with, and within the space constraints I have.

Below is functional code for using the AML-S905X-CC with this little breakout board I’ve designed, which utilizes the CD4051 and can support up to 8 of these gp2y0a21yk0f IR Distance Sensors, utilizing only one of the Le Potato’s SAR ADC pins.

I am utilizing these GPIO pins to these corresponding A/B/C controls on the CD4051.

CD4051 A: pin 11
CD4051 B: pin 13
CD4051 C: pin 15

Below is the demo script.


from gpiod import Chip, LINE_REQ_DIR_OUT
from time import sleep

multiplex_gpio = {
    "A": {"chip":"0","line":8},
    "B": {"chip":"0","line":9},
    "C": {"chip":"0","line":10}
}

multiplex_channels = {
    "0": {"a":0, "b":0, "c":0},
    "1": {"a":1, "b":0, "c":0},
    "2": {"a":0, "b":1, "c":0},
    "3": {"a":1, "b":1, "c":0},
    "4": {"a":0, "b":0, "c":1},
    "5": {"a":1, "b":0, "c":1},
    "6": {"a":0, "b":1, "c":1},
    "7": {"a":1, "b":1, "c":1}
}

# Multiplex "A" gpio
multiplex_a_chip = Chip(multiplex_gpio["A"]["chip"])
multiplex_a_lines = multiplex_a_chip.get_lines([multiplex_gpio["A"]["line"]])
multiplex_a_lines.request(consumer="",type=LINE_REQ_DIR_OUT)
# Set pin to low.
multiplex_a_lines.set_values([0])

# Multiplex "B" gpio
multiplex_b_chip = Chip(multiplex_gpio["B"]["chip"])
multiplex_b_lines = multiplex_b_chip.get_lines([multiplex_gpio["B"]["line"]])
multiplex_b_lines.request(consumer="",type=LINE_REQ_DIR_OUT)
# Set pin to low.
multiplex_b_lines.set_values([0])

# Multiplex "C" gpio
multiplex_c_chip = Chip(multiplex_gpio["C"]["chip"])
multiplex_c_lines = multiplex_c_chip.get_lines([multiplex_gpio["C"]["line"]])
multiplex_c_lines.request(consumer="",type=LINE_REQ_DIR_OUT)
# Set pin to low.
multiplex_c_lines.set_values([0])

def get_multiplex_channel_value(channel:str, file="/sys/bus/iio/devices/iio:device0/in_voltage0_raw"):
    multiplex_a_lines.set_values([multiplex_channels[channel]["a"]])
    multiplex_b_lines.set_values([multiplex_channels[channel]["b"]])
    multiplex_c_lines.set_values([multiplex_channels[channel]["c"]])
    sleep(0.01)
    return read_file(file)

def read_file(file):
    openFile = open(file,"r")
    content = openFile.read()
    openFile.close()
    return content

def main():
    while True:
        sleep(1)
        for i in range(0,3):
            str_i = str(i)
            value = get_multiplex_channel_value(str_i)
            print(f"Channel {str_i} = {(value)}")

if __name__ == "__main__":
    main()

Here is what the output of the demo script looks like:

Channel 0 = 63

Channel 1 = 679

Channel 2 = 113

Channel 0 = 327

Channel 1 = 457

Channel 2 = 1632

Channel 0 = 335

Channel 1 = 426

Channel 2 = 548

Channel 0 = 604

Channel 1 = 455

Channel 2 = 624

And of course the fun stuff, some pictures!

This evening, I’ve used the IR Distance Sensor with the final voltage divider as outlined above, through the CD4051 IC, and have taken several measurements using the AML-S905X-CC onboard SAR ADC.

Below are two sets of calibration data. One of a white surface, one of a black surface, in CSV format. Only the high values are used, not low, as the idea is for the robot to avoid crashing into things, so higher values are preferred.

Distance (inches),White High ADC,Black High ADC,Average High ADC
3,3756,3740,3748
4,2979,3096,3037.5
5,2476,2534,2505
6,2126,2158,2142
7,1832,1868,1850
8,1691,1641,1666
9,1550,1509,1529.5
10,1429,1389,1409
11,1300,1249,1274.5
12,1230,1173,1201.5
13,1190,1050,1120
14,1126,1031,1078.5
15,1061,1000,1030.5
16,980,972,976

For the SAR ADC output to inches, I used help from ChatGPT. Based on the data, the following equation can be used to deduce the distance.

Here is the python function that will convert the SAR ADC output to inches

import math

def adc_reading_to_distance(ADC):
    # Constants derived from calibration
    m_abs = 0.7846  # Absolute value of the slope
    ln_A = 9.0624   # Intercept from regression

    ln_ADC = math.log(ADC)
    ln_D = (ln_A - ln_ADC) / m_abs

    D = math.exp(ln_D)

    return D  # Distance in inches

Note that this function will not work correctly for distances less than 3 inches.