User avatar
DougieLawson
Posts: 34166
Joined: Sun Jun 16, 2013 11:19 pm
Location: Basingstoke, UK
Contact: Website

Building a dtoverlay for MCP342x ADCs

Sun Nov 04, 2018 11:33 am

I've got the weather station with its two MCP3427s at 0x69 and 0x6A.

If I modprobe MCP3422 then echo mcp3427 0x69 > /sys/class/i2c-adapter/i2c-1/new_device and echo mcp3427 0x6a > /sys/class/i2c-adapter/i2c-1/new_device

Those two ADCs then appear as /sys/bus/iio/devices/iio:device2 and /sys/bus/iio/devices/iio:device3

What I need is a dtoverlay that will do that stuff for me, but I'm not sure where to start with an mcp3422.dts to create a mcp3422.dtbo. [I sense a reading assignment.]

I've already started using

Code: Select all

dtoverlay=i2c-sensor,bmp180
dtoverlay=i2c-sensor,htu21
as that makes the weather-station python code much easier to write with the heavy decoding work for those two sensors is done in their kernel drivers.

I'd like to get the MCP3427s working the same way, then I can create a pull request against the weather-station repo with my simplified code for all four sensors.

Does it make sense to create a new mcp3422 overlay or is it better to add some arcane incantations to the i2c-sensor one?
Microprocessor, Raspberry Pi & Arduino Hacker
Mainframe database troubleshooter
MQTT Evangelist
Twitter: @DougieLawson

2012-18: 1B*5, 2B*2, B+, A+, Z, ZW, 3Bs*3, 3B+

Any DMs sent on Twitter will be answered next month.

PhilE
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 1902
Joined: Mon Sep 29, 2014 1:07 pm
Location: Cambridge

Re: Building a dtoverlay for MCP342x ADCs

Sun Nov 04, 2018 3:23 pm

The boiler plate for such simple overlays dwarfs the actual DT changes, so it makes sense group them into collections like i2c-rtc and i2c-sensor. Having said that, I'm not sure an ADC is strictly a sensor, and there are 8 variants of the MCP342x, so perhaps a standalone overlay is reasonable. Besides, the multiple variants provide plenty of opportunities for arcane incantations in standalone form.

I'm happy to do as much or as little as you want - it depends how much of a learning opportunity you feel like.

User avatar
DougieLawson
Posts: 34166
Joined: Sun Jun 16, 2013 11:19 pm
Location: Basingstoke, UK
Contact: Website

Re: Building a dtoverlay for MCP342x ADCs

Sun Nov 04, 2018 3:50 pm

Can you give me a hint? I'd like to learn how this DT stuff hangs together as you appear to be generating more and more of these things and you're clearly an expert.
Microprocessor, Raspberry Pi & Arduino Hacker
Mainframe database troubleshooter
MQTT Evangelist
Twitter: @DougieLawson

2012-18: 1B*5, 2B*2, B+, A+, Z, ZW, 3Bs*3, 3B+

Any DMs sent on Twitter will be answered next month.

PhilE
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 1902
Joined: Mon Sep 29, 2014 1:07 pm
Location: Cambridge

Re: Building a dtoverlay for MCP342x ADCs

Sun Nov 04, 2018 5:02 pm

OK - let's start with the simplest overlay, one dedicated to the MCP3427 at address 0x69.

An overlay is a container of patches (fragments) to a base DTB. Each fragment has a target (a location to apply it) and a group of properties and subnodes to put there. Properties can overwrite existing properties or be completely new; it is possible to overwrite an existing subnode, but the runtime overlay application in the kernel will rename one of them and complain, so it is best not to. If you need to modify a subnode, use a separate fragment with the subnode as the target.

I2C devices are instantiated by creating subnodes within the node for the relevant I2C controller. We'll use the one labelled "i2c_arm". Assuming i2c_arm is the same as i2c1 (which it is on all current Pis), a clean DTB contains a node like this:

Code: Select all

	i2c_arm: i2c1: [email protected] {
		compatible = "brcm,bcm2835-i2c";
		reg = <0x7e804000 0x1000>;
		interrupts = <2 21>;
		clocks = <&clocks BCM2835_CLOCK_VPU>;
		#address-cells = <1>;
		#size-cells = <0>;
		status = "disabled";
		pinctrl-names = "default";
		pinctrl-0 = <&i2c1_pins>;
		clock-frequency = <100000>;
	};
The overlay needs to add the following before the closing brace (effectively - the braces are really part of the source syntax, and overlays are applied in the Flattened Device Tree format generated by the dtc compiler):

Code: Select all

		[email protected] {
			compatible = "microchip,mcp3427";
			reg = <0x69>;
		};
The two 69s convey the I2C address - the "reg" property is the important one, while the one in the node name ("[email protected]") serves to disambiguate in case there are multiple "adc" nodes (as you will have, eventually). The "compatible" property is a way of locating and configuring a suitable driver.

The other change required is to ensure that i2c_arm/i2c is enabled by setting the "status" property to "okay". This is what "dtparam=i2c_arm=on" does, but it makes sense to build that into any overlay attempting to use i2c_arm.

Writing these two changes in fragment form gives the following overlay:

Code: Select all

/dts-v1/;
/plugin/;

/ {
	compatible = "brcm,bcm2835";

	[email protected] {
		target = <&i2c_arm>;
		__overlay__ {
			#address-cells = <1>;
			#size-cells = <0>;
			status = "okay";

			[email protected] {
				compatible = "microchip,mcp3427";
				reg = <0x69>;
			};
		};
	};
};
A few things to note:
* The /dts-v1/ and /plugin/ lines, the top-level node "/" and the "compatible" property are required by all overlays.
* Because both changes are to the same node (the one with the label "i2c_arm"), they can be combined into a single fragment.
* Although the "#address-cells" and "#size-cells" properties already exist in the base DTB, the compiler can't see them so issues a warning about the content of the "reg" property. You are not expected to understand this, just copy it from another overlay.
* The semi-colons after the braces are easily forgotten if you are used to C-like languages.

Call the overlay "mcp3427-overlay.dts" and compile it with:

Code: Select all

$ dtc [email protected] -I dts -O dtb -o mcp3427.dtbo mcp3427-overlay.dts
You will see a warning about a missing "reg" property, but that is because the compiler doesn't really understand overlays very well - you can ignore it.

Copy mp3427.dtbo into /boot/overlays, add "dtoverlay=mcp3427" to config.txt and reboot. Alternatively, run the following to load it immediately:

Code: Select all

$ sudo dtoverlay ./mcp3427.dtbo
Remove the overlay with "sudo dtoverlay -r", or by deleting the config.txt setting and rebooting.

User avatar
DougieLawson
Posts: 34166
Joined: Sun Jun 16, 2013 11:19 pm
Location: Basingstoke, UK
Contact: Website

Re: Building a dtoverlay for MCP342x ADCs

Sun Nov 04, 2018 8:50 pm

Thanks Phil.

I got a long way with this; I think I've worked out the way to add both 0X69 & 0X6A (although they're the wrong way round).

Only remaining problem is my devices don't appear until I modprobe the driver.

Code: Select all

/dts-v1/;
/plugin/;

/ {
        compatible = "brcm,bcm2835";

        [email protected] {
                target = <&i2c_arm>;
                __overlay__ {
                        #address-cells = <2>;
                        #size-cells = <0>;
                        status = "okay";

                        [email protected] {
                                compatible = "microchip,mcp3422";
                                reg = <0x6A>;
                        };
                        [email protected] {
                                compatible = "microchip,mcp3422";
                                reg = <0x69>;
                        };


                };
        };
};

Code: Select all

[email protected]:~ # find /sys -name "*iio*"
/sys/kernel/debug/iio
/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0040/iio:device0
/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0077/iio:device1
/sys/bus/iio
/sys/bus/iio/devices/iio:device1
/sys/bus/iio/devices/iio:device0
[email protected]:~ # modprobe mcp3422
[email protected]:~ # find /sys -name "*iio*"
/sys/kernel/debug/iio
/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0040/iio:device0
/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0069/iio:device2
/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0077/iio:device1
/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-006a/iio:device3
/sys/bus/iio
/sys/bus/iio/devices/iio:device3
/sys/bus/iio/devices/iio:device1
/sys/bus/iio/devices/iio:device2
/sys/bus/iio/devices/iio:device0
[email protected]:~ #
I can run with that (for the moment) as I'll add a line to /etc/modules to get the driver loaded.

The best thing is the python code to read those two ADCs is lots simpler.

Code: Select all

#!/usr/bin/python3
#import struct, array, time, i2c_base

CHANNEL_0 = 0
CHANNEL_1 = 1

#CMD_ZERO = b"\x00"
#CMD_RESET = b"\x06"
#CMD_LATCH = b"\x04"
#CMD_CONVERSION = b"\x08"
#CMD_READ_CH0_16BIT = b"\x88"
#CMD_READ_CH1_16BIT = b"\xA8"

#msleep = lambda x: time.sleep(x/1000.0)

class MCP342X(object):
    def __init__(self, address = 0x69):
#        self.dev = i2c_base.i2c(address, 1)
        self.max = 32767.0 #15 bits
        self.vref = 2.048
#        self.tolerance_percent = 0.5
        if (address == 0x69):
            self.filename= '/sys/bus/iio/devices/iio:device2'

        else:
            self.filename= '/sys/bus/iio/devices/iio:device3'
#        self.reset()

#    def reset(self):
#        self.dev.write(CMD_ZERO)
#        self.dev.write(CMD_RESET)
#        msleep(1)

#    def latch(self):
#        self.dev.write(CMD_ZERO)
#        self.dev.write(CMD_LATCH)
#        msleep(1)

#    def conversion(self):
#        self.dev.write(CMD_ZERO)
#        self.dev.write(CMD_CONVERSION)
#        msleep(1)

    def configure(self, channel = 0):
        self.raw = str(self.filename + '/in_voltage' + str(channel) + '_raw')
        self.scale = str(self.filename + '/in_voltage' + str(channel) + '_scale')
#            self.dev.write(CMD_READ_CH1_16BIT)
#            self.dev.write(CMD_READ_CH0_16BIT)
#        msleep(300)

    def read(self, channel = None):
        self.configure(channel)
        with open(self.raw) as raw:
            raw_data = float(raw.read())
        with open(self.scale) as scale:
            scale_data = float(scale.read())
        result = raw_data * 16

        return result

if __name__ == "__main__":
    adc_main = MCP342X(address = 0x69) # ADC on the main HAT board
#    adc_main.conversion()

    adc_air = MCP342X(address = 0x6A) # ADC on the snap off part with the AIR sensors
#    adc_air.conversion()

    print("MAIN CH0: %s" % adc_main.read(CHANNEL_0)) # wind vane
    print("MAIN CH1: %s" % adc_main.read(CHANNEL_1)) # not populated

    print("AIR CH0: %s" % adc_air.read(CHANNEL_0)) # air quality
    print("AIR CH1: %s" % adc_air.read(CHANNEL_1)) # not populated
I like writing code with lots of hashes to take lines out. It's no good for the bean counters who use KLOCs as a metric for programmer productivity. There were 77 lines (including blanks) in the old python module, there's 39 lines (including blanks) in the new version (12 of those are new lines of code).
Microprocessor, Raspberry Pi & Arduino Hacker
Mainframe database troubleshooter
MQTT Evangelist
Twitter: @DougieLawson

2012-18: 1B*5, 2B*2, B+, A+, Z, ZW, 3Bs*3, 3B+

Any DMs sent on Twitter will be answered next month.

PhilE
Raspberry Pi Engineer & Forum Moderator
Raspberry Pi Engineer & Forum Moderator
Posts: 1902
Joined: Mon Sep 29, 2014 1:07 pm
Location: Cambridge

Re: Building a dtoverlay for MCP342x ADCs

Sun Nov 04, 2018 9:54 pm

Only remaining problem is my devices don't appear until I modprobe the driver.
Assuming you have all the usual Raspbian udev rules, the only explanationI can think of for the lack of auto-loading is that the module is blacklisted. Is that possible? If not, for diagnostic purposes you could try the runtime loading method I described above (remove the dtoverlay setting from config.txt). If that still doesn't work, run "sudo udevadm monitor" while repeating the runtime loading (rebooting first would be advisable).

User avatar
DougieLawson
Posts: 34166
Joined: Sun Jun 16, 2013 11:19 pm
Location: Basingstoke, UK
Contact: Website

Re: Building a dtoverlay for MCP342x ADCs

Sun Nov 04, 2018 10:06 pm

I can't remember if that machine has had any udev rule hacking (it's a long time since I built that one). There's no blacklisting at all (except for those silly CUPS modules) because I always remove it all when I'm enabling the IPv6 stuff.

I'm happy with a line in /etc/modules - there's already lines for the BMP180 and HTU21D in there.

Thanks for your explanation. When I get my 40mm and 22mm drain-pipe I'll look at doing a full scale re-install with Stretch 2018-10-09 then I'll build the stuff I need to do a pull request for the weather-station repo.
Microprocessor, Raspberry Pi & Arduino Hacker
Mainframe database troubleshooter
MQTT Evangelist
Twitter: @DougieLawson

2012-18: 1B*5, 2B*2, B+, A+, Z, ZW, 3Bs*3, 3B+

Any DMs sent on Twitter will be answered next month.

Return to “Device Tree”