Raspberry Pi & Python I2C Deep Dive with TonyD! @adafruit LIVE

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everybody it's tony from adafruit and i want to talk a little bit about the i squared c protocol so i've been playing with some devices like some analog to digital converters and they use i squared c to communicate with the raspberry pi and i thought it'd be interesting to do kind of a deep dive and look at what the i squared c protocol looks like and how to use it in a python program uh both at kind of a high level and maybe a little bit lower level so let's kind of dive right in and i'll show you the setup i've got uh on the workbench here so i just have the raspberry pi and the ads 1115 analog to digital converter i just did a little library on this one this week so there's a new guide out you can check to use this and basically it takes in an analog signal which could be anything like from a sensor or i have a little potentiometer hooked up to it right here that i can change and it just gives you a digital value so it'll convert that analog value into a digital value it's like a number uh this one's 16 bit so it goes from like zero to 65 536 uh well actually it's signed value so it's like 32 767 but either way it gives you a digital value that you can use to interpret that analog signal so if it's like a temperature sensor you could say uh you know high value probably equates to a higher temperature and i've got it hooked up to a little logic analyzer and we'll kind of come back to this uh in a second and so i thought it'd be kind of neat to just look at the the protocol a little bit and we'll use this logic analyzer which basically this is almost like an oscilloscope but more for reading uh protocols and digital signals so it'll sample the i squared c communication bus at a pretty high rate and it will show you what uh what bit values it sees whether it's a high or low value and if i jump real quick to the desktop i basically got up right here this is info on the i squared c protocol so it's the inner integrated circuit communication protocol uh it's basically a way for two chips to talk to each other and it's a serial protocol which kind of means you have a clock and you have a data line uh and there are different serial protocols that the raspberry pi can use so i squared c is one of them uh there's spy uh which is like a serial serial protocol uh there's just the serial port itself which can talk to like the kernel as it's booting up and so i squared c is kind of interesting uh it's a nice little protocol that there's only two real wires that you need there's a clock line and a data line and just with those two wires you can have multiple devices and we'll kind of see how that works in a second when we look at the protocol versus something like the spy protocol you need a clock line a data inline a data outline a chip select line and i wouldn't say that one protocol is better than the other so as we look at i squared c you'll see it has some advantages but it also has some disadvantages compared to other protocols so it's kind of neat to just know about all these protocols and kind of know when's kind of the right time to use them and if you're talking to a chip like an analog digital converter like this one you might just have to use i squared c for example there are some devices that can use multiple protocols in most cases for a little sensor it's going to stick with one protocol so the wikipedia page is actually pretty good and gives you a little bit of a description of kind of how it works um but i thought it would be kind of fun for us to take the celie logic analyzer here and let's switch to a better view with everything so let's do maybe this piece you kind of see the workbench so celia is just this little um box right here a really nice little logic analyzer by the way you can pick them up in the shop this is the pro 8 logic analyzer that i have it's maybe a little fancier than most people need so they have a 4 channel analyzer an 8 channel this is the pro version which samples a little bit faster rate it uses usb 3.0 and whether you need a logic analyzer sometimes a good question so if you're just using chips and talking you know connecting devices to a raspberry pi you probably don't need a logic analyzer if you're working with new hardware and you're maybe writing a library to talk to a device that you haven't used before then you definitely would have a lot of benefit from a logic analyzer like this one so definitely think about it i like because the software is pretty easy to use it's cross-platform uh and it's relatively inexpensive i think this one's about 500 bucks but a base kind of eight channel one is about 200 bucks or so which uh in the grand scheme of test equipment is pretty inexpensive unfortunately uh so let's see so i've got the uh software set up here the celia software and i have just two channels connected so like i was saying before the i squared c protocol it just uses two main wires the clock and the data line there's also power and ground that you usually need to give to a device uh but usually you don't count those so that's exactly the two channels i have shown here so i have the clock channel on the top and the data channel on the bottom here and the nice thing about celia software is you can actually add a little analyzer to your protocol to your um to what you're measuring and it'll actually decode the i squared c protocol for us so real fast let me run something on the raspberry pi so i have the raspberry pi connected here and i'm connected to it just with ssh and i'll run the little uh ads 1115 kind of demo program i have on here and this is just going to read analog values uh and print them out so if i run the simple test and so this is just printing out all four channels of this and analog to digital converter channel 0 is the one i have hooked up to the potentiometer so when i like crank it up you can see the value increases and when i turn it down it decreases you know all the way down to pretty much zero so you can kind of see the full range here i have the gain kind of tweaked a little bit so one neat thing about this adc is that you can change the gain to let you look at a signal that might be at a low level so my max value here goes uh from 0 to 3.3 volts which equals about 26 000 or so here roughly uh but that's just kind of showing you the value so so i'm measuring that data and kind of what's happening behind the scenes is i have a little python script it's calling a library that i wrote that's making i squared c communication calls to the chip right now and so i'll use the celia software here and i'll just start up uh the analyzer i have it sampling for two seconds and at a relatively slow rate because you'll see the clock speed for the i squared c communication protocol by default it's pretty slow it's only about 100 kilohertz so i'm sampling at 2 million samples per second which you know means i could pick up about a megahertz signal so i should definitely be able to pick up this signal now i run this and you see oh it looks like nothing but you need to zoom out so this is showing the full two seconds of capture and so my program every half a second is pulling in a new sample so you can kind of see in that two seconds i've got four samples here so everything kind of looks good there now i can zoom in to these signals and we kind of see what's going on here with the protocol so i can go through and again like i said before the top channel here uh is the clock and so this is basically the raspberry pi is sending a signal that goes high and low at a certain frequency and then the the analog to digital converter is kind of picking that up and understanding okay you know when it goes high when it goes low i need to send a bit or receive a bit in certain ways the data channel is the second one here and so we'll just kind of dive in and see so the nice thing about celia software you can see it's actually doing some decoding here so it's telling me that it set up a write of value 144 and it got an ack bit back and you can see this little green dot here so this green dot is actually really important this is the start bit that tells the i squared c bus that something's about to happen that a device is actually going to send or receive data maybe before we get too far into this digital uh part of the protocol one real quick note too on just the physical protocol for i squared c so you know these two lines uh there are pull up resistors on these lines that will by default if nothing is actually using the bus these will read at a high signal and so that's what you see in the logic analyzer here uh when the line is up at this higher level here that means that it's at like 3.3 volts so it's being pulled up by those pull-up resistors when a device wants to send a bit it actually pulls the line down to ground and so that way like multiple devices can be connected to it and they just have to pull the line down to ground and they'll be able to send a value whereas like each device doesn't have to actually push the value up and down uh and so that's kind of what you see here that you know by default before nothing happens we're at a high level and then to to signal the start of an i squared c uh communication transaction a start bit is sent and so the way that works is the data line is pulled down and then the clock line is pulled down after that so you can kind of see the order here and that's just a very specific kind of timing thing that has to say okay now uh device is going to be sent some data and that's kind of what happens at the start here and there's also an end bit so when we get to the end here uh you can see this little red dot and it's kind of the inverse there so you say okay you uh let the clock line go back up to a high level and then you let the data line go back up to high level so in between here is kind of a whole i squared c transaction and so there are a lot of things that can happen in an ice core c transaction so you can read data you can write data one big thing with i squared c is because there aren't individual chip select lines you need to be able to say okay i want to talk to this specific device and if you have multiple devices on the bus you need some way to identify devices and so the way i squared c works is each device has an address and it can be like a 7-bit an 8-bit or i think even sometimes a 10-bit address for most cases it's a 7-bit address like we're dealing with here and so when like the raspberry pi wants to talk to this uh adc it needs to start talking to the i squared c bus and it first needs to send the address of the device that it wants to talk to and that's what this first right that we see here is this right of 144 plus the ax so you can see uh if we zoom in a little bit we'll just look at each of the bits so the clock line again is at the top here uh and you can if we count like one two three four five six seven eight so up to here is the first byte of data and then there's actually a ninth uh bit or that's clocked out where that's the acknowledgement so what's happening here is uh the very start the raspberry pi uh the code that's running on it says okay i want to talk to the ads analog to digital converter and in the data sheet for that device you can look up and see kind of what the address is so the address is a hex value of four eight so it actually needs to send that hex value of the address out to the uh i squared c bus so it clocks out all of the bits uh at least eight bits of that address and then the data line is actually gonna go up high and low to indicate that value now the weird thing you might notice so it's setting up a write so it's writing a value of 144 and let's actually look at a calculator here we'll put 144 in unfortunately salis software it seems to only work in decimal i'd love if it had like a hex mode where it could give me the value of this but if i have a little calculator here so we can see a decimal value of 144 is equal to 90 in hexadecimal and so that's a little weird because it's kind of like okay um i should expect to see a value of 48 but the the what we actually see here is it's a 7-bit address and the 8th bit is a value that indicates if this is a read or a write to that device so what we need to do is actually shift the bits over one place because that very last bit is telling us if it's a read or a write if it's a zero it's a right and if it's a one it's a read so when we shift it over now we actually see if i convert hexadecimal to value of 48 and so that's what this right of 144 is doing so it's basically sending the seven bits of the address ox48 and then a zero to say hey i'm going to write a value to the device here and then the ninth bit so let's see one two three four five six seven eight nine so this bit right here is the acknowledgement where it's after you clock out a byte of data the receiving device then can acknowledge that and say okay i received that byte and the way that works in i squared c is that the receiving device holds the data line low so your raspberry pi is clocking out the data and then it waits on that eighth bit and it actually lets go of the data line and then your device that you're talking to this ads receiver if it acknowledges that byte then it actually pulls the data line low and so we don't really see any change here but actually what's happening is that the ads device is pulling that line low and it's acknowledging it and so that's why the celia software here says okay we did it right and we received an act back and so that's kind of a nice thing about the i squared c protocol that um you know it's it's actually letting you know that the device is responding that you're getting data back uh whereas like the spy protocol if you're just blasting out data you don't really have any uh acknowledgement you know you kind of have to build that into the protocol so you probably need to read some values back or maybe have a separate line or something that tells you it's been acknowledged uh and then the other cool thing too that we see here about i squared c is that it has this concept of addresses uh so you can have multiple devices they're all sharing those same lines uh each device doesn't need like a specific chip select line um you know just with these clock and data lines you can have up to seven bits of devices like 128 devices on here and so that's kind of the first byte that we see is okay we're doing a write to this device and now let's kind of scooch over and see what the next value is so the celia decoded it and you kind of see you know the clock is still clocking out data you can also see here if i hover over here and i can't move the mouse precursor but right above the mouse cursor it's kind of showing me the length of time so it's like five nanoseconds uh for this clock low pulse and then five nanoseconds for the clock uh high pulse and it's actually telling me that it's running at 100 kilohertz and so that's another interesting thing about the i squared c bus is it's a fixed speed bus um you know this is in contrast to the spy bus where you can control the speed of that as a spy master so like the raspberry pi it could run a spy bus you know multiple megahertz so if you have like a little lcd display and you want to update it at a fast rate you know that's a really good case of using the spy bus versus i squared c by default does 100 kilohertz there's a high speed mode where it can do 400 kilohertz i think there's even faster modes where you can do like a megahertz or more but most devices that we deal with uh just run at that default 100 kilohertz rate and so again it's a trade-off in that you know maybe the protocol is a little simpler you have less pins you need to deal with but you don't have control over the speed in a lot of cases like this um but so again we can see now we're clocking out data we've got a byte of data here now the value of the celia decoded is a value of one and then again that last little clock here here's the ack bit that's telling us you know it was pulled low so the device responded and said that it received this value of one now the value of one you have to go into the data sheet now for the ads 1115 to understand what it means this is specifying the register of the device i want to talk to and a value of one is actually the configuration register and so that's a register on the device that says how the adc should be configured like what gain it should use how many samples it should take per second and so that's kind of what i'm actually doing here you saw previously you know we told this device we're going to do a write now we specified in the next byte the register we want to write to so this is the config register and then we're actually sending two bytes of data following this to the config register because all the registers on this device are 16 bits so that's two bytes of data that we need to see that we need to send and so that's exactly what's happening here so first we're sending a value of 195 we're getting that ack back and then we're sending a value of 131 and we're getting an ack back there and so that's just sending the configuration so my python script has to tell the adc you know what uh gain what sample rate it needs to use it has to flip some bits to say okay do a single shot capture uh but that's kind of the the first transaction here that we can see you know we're doing a right to this device uh we're specifying the register and then we're specifying the value we want to write to that so there's basically three bytes of data that we're writing to this device now if we go over more and you'll see kind of this big delay in between the uh different sends of or the different kind of transactions and that's really just the python code on the raspberry pi it's maybe not that big i mean eight milliseconds that's pretty small but you can see in the grand scheme of things like compared to how fast the actual bits on the bus get clocked out you know this is pretty long and that's just my python program you know it's it's doing other processing in between here and you can kind of see uh how you know if you compare this to like maybe an arduino for example that's not running a full-blown operating system you know there could be a lot less delay between different i squared c transactions because there's less stuff that you might be doing in between here but let's take a look at the next transaction here so we'll kind of zoom in and see okay so this next one again it starts with a start a start bit so you know we pull the data line low and then the clock line low and again we're writing to uh we're writing a value 144 and getting an act back so that's the device address so that's a hex value of 48 once we do the conversions and drop that last bit also interesting to see this is another write that we're doing here so now we're basically saying oops let me scroll over here so so we're doing a write and we're doing a value of zero and then this little red circle is telling us now we're sending a stop bit so we're basically just sending one byte of data here so we're telling the device here's the register that we're going to read from next and this comes down to the protocol that the device wants to understand so you know to talk to this adc to pull a value from it you first have to write a byte that says okay now switch to the actual adc output which is register zero and then we're going to do some reads after this and so now we can see another i squared c transaction starts right after this one so that's where this little green circle is again and in this case it's a little bit different so we can see the very first thing we do is we're writing a value of 145 instead of 144 like we saw over here we're getting the act back and so let's just kind of decode that and and you'll probably see uh pretty quickly kind of what the difference is so if i put 145 here now we can see okay if i shift this over one place we get a hex value of 48 again so we're talking to that device that has address 48 but if i go back and i put 145 in we can see that eighth bit is a value of one and so that's that read write bit and so now that bit is set to one and so this i squared c transaction is a read and so that way the device uh the adc knows that okay hey the raspberry pi is talking to me but now it's going to do a read and so it actually looks to see okay what was the register that was previously set in this previous write command and then it's actually going to send out the value of that register and so now you can see that we clock out another two bytes in the clock here but it's not the raspberry pi that's actually changing the data line here and unfortunately you can't really tell which device is pulling the line low uh just kind of the an artifact of just having these two lines but you can see that it's getting a value back it's getting u which actually this is getting it's converting uh the value to ascii so it's it's just a little bit of a weird um kind of quirk of how celia software works uh but it's getting one value and then it's getting another value and we're acting that value again so like let's kind of zoom in on the uh the value we're getting back so like we're getting back a value of u so and let's actually look at this uh hex value here so like let's see this is um let's see if it tells us the actual raw value that doesn't really tell us the raw value that's kind of nice so we can zoom in there so okay this this is giving us a value back the very last bit is again that ack and because it's the device talking to the raspberry pi now it's the raspberry pi that's actually pulling the line low here so you know this ack goes kind of both ways here and then the next bit the next byte that reads it gets a value of 215 here and you see a knack value and unfortunately the software kind of goes crazy when i put the mouse over it but there's a difference between an ack and a knack so in this case the ack is the raspberry pi telling the device you know okay yes i received that byte now and and it does that by holding the data line low for that final ninth um clock kind of cycle a knack bit is the opposite when the device actually holds the line uh or lets the line go high again and so we actually see in this last clock cycle it's high and that's sending a knack which is like a negative acknowledgement so this is actually the raspberry pi telling the device okay i read that second byte and i'm done reading bytes like don't send any more bytes of data or don't expect any more bytes of data to be sent here so it's a little subtle difference but it's kind of a way for the master that initiated the communication to say okay i only want to read two bytes of data so it reads one byte sends the ack reads the second byte and then sends a negative ack or knack to say okay i don't want to read that data anymore and then it sends the i squared c stop bit again here where it says okay now we're going to let the clock line go high and then the data line go high after that to acknowledge the end of that so what's happening here in this kind of in these two transactions is we're reading the adc value so to do that we first do a write to say here's the register i'm going to read and then we do a read of two bytes because it's a 16 bit register and we pull down those two bytes of data and then my program can kind of interpret that raw value to say okay you know i need to do two's complement conversion on this and you know basically turn it into a signed integer value and that's what we see when it's printed out here so you know as i like change the potentiometer this value changes and you know it's getting back a different value from that register that it's reading here and so that's kind of all you see for the rest of um you know this program so you know here it's doing a write to the config register again uh basically every time it does a read it's doing a config right to say okay i'm going to start a single shot read again and so that's kind of at the low level what we're seeing uh going on here so it's you know it's it's interesting just to to be able to look at that um kind of wire protocol here and and see the the raw things that are happening um i think what i'll do now is maybe we'll jump in and we'll look at the actual code that's making this work on the raspberry pi uh and so let's jump back to the pi and i have over here some code that i wrote earlier and maybe we'll kind of dive into this so there are two things i'll show as far as how to talk the i squared c protocol uh from python with raspberry pi i'll show kind of a high level way to talk the i squared c protocol that uses a library called the sm bus library and then i'll show a lower level thing something i'm kind of working on now it's actually just using the raw kind of linux device that's exposed for the i squared c communication bus and maybe before i dive too much into the code i think i had shown this before but there are some interesting tools in linux that let you look at the i squared c bus and kind of see what devices are there because you know we saw earlier it's a bus where each device has an address assigned to it and it turns out there are kind of some neat ways that you can query the entire bus to see how many devices and what devices are there so if you install the i2c tools package this i2c dash tools you just need to app to get install that there's a program called i2c detect you need to run it as root so we'll say itc detect we give it the dash y option because normally it asks are you sure you want to scan the bus and yeah i do want to scan the bus and you have to specify the bus number and we'll look at this in a second but i can run this command and it's actually scanning the entire i squared three c bus uh the way that scanning works from what i understand is it actually does a it sends out a command kind of like we saw here that says hey i'm going to do a write you know it sets up a write to every single possible i squared c bus and it looks to see if it gets an ack bit back so it looks to see if some device holds the data line low and if it sees that then it knows okay there must be a device listening on that address and so that's kind of how that tool works uh behind the scenes but it's telling us you know here's the device with address 48 just like we saw kind of from the the raw protocol and the code that's running there too uh the actual scripty bus in linux so it's exposed under this i2c dash some number so on the raspberry pi uh there's just one i squared c bus that we can talk to so it's this i squared c dash one and so that one is kind of the bus number to get this so if you just have a fresh raspberry pi image you do need to be careful you need to run the raspberry raspi config tool and enable i squared c so i won't go through it all but i'll just kind of show you this is a super common thing that people miss where you know you're talking to a library that uses i squared c if you forget to enable this so it's in the advanced options you go under i squared c and you want to enable it i'm just going to cancel out of this but if you forget to do this then you won't see any of these i squared c devices you know you'll just get nothing if you try to query them so enable the bus restart the pi and then you'll actually see these i squared c devices so there's this device under the dev node that gives you this i squared c interface now the the code actually talked to this thing there's a python library uh called sm bus and sm bus is actually a subset from what i understand of i squared c so uh i think they're like devices on motherboards things like temperature monitors actually use i squared c protocol but they use this subset called sm bus that basically doesn't have all the capabilities it uses the same kind of protocol like we saw you know there's a clock line data line but it's more register focused so it's talking to devices that have a register that it wants to read a value from uh or write a value to you know with i squared c you actually could just write like a raw single byte if you want but this smbs protocol is more to deal with like a device that registers the difference really doesn't matter much you know most cases a lot of devices that we use are register based and so we can use the sm bus kind of libraries and protocol but there are some devices uh like i was actually just dealing with a little humidity sensor the sht31 that doesn't use registers and so we'll kind of look at that at the end here when i look at the kind of raw way to interact with i squared c devices but it's just something to be aware of that you might see sm bus and whenever you see that you really just need to think okay that's i squared c but a little subset of i squared c now the main library that everyone uses is this library called python-sm bus and it's actually part of that i2c tools package so you know when i apt-get install i2c tools it's actually the python library is a part of this now in raspbian to actually install this library you really want to install the python dash sm bus apt-get package and so this package will install the sm bus library for the version of python on the raspberry pi and um i wanted to kind of figure out and see you know where is this library like let's kind of look at the code for it and it's kind of an interesting thing so when you search for it it's actually this lm sensors.org unfortunately it looks like their website is down so we'll just go to the internet archive and this must be a temporary thing but it kind of tells you about this is a little thing for kind of linux hardware monitoring and they have their code it's in a subversion repository i did a search and actually someone mirrored it on github and so it's a little bit easier i think sometimes to look at a github repository but this is basically a mirror of that code and you can actually see here's the pi sm bus folder and here is the actual implementation of that python smbus library and i mention all of this because unfortunately the documentation for this library is not the best in the world so um i guess yeah there is some documentation here and let's see if if they actually show the python docs um because it's one frustrating thing i see a lot of people you know you you might be coming from like you know maybe a windows world or something where a lot of libraries are documented really well and you kind of start playing with raspberry pi and i squared scene it's like okay where are the docs where are the docs on this thing uh and unfortunately yeah i really couldn't find good docs on the i squared c um python library so you know if they're out there maybe you know post in the comments or something of this video later i'd love to see what's out there but unfortunately that kind of means the best documentation is the code here and so the code for this is it's a python c extension which is pretty advanced if you're new to python this is going to be pretty confusing unfortunately so this is c code that exposes a python class and i won't get too deep into this um just the interesting thing is if you kind of browse through this code you'll see all of the functions that it exposes like this read word data this is how it's implemented in the c code here and so really this library is just a wrapper around a library that linux provides you already so like this re read word data function this is exposed as a python function you can't really tell the parameters it sees here this is just the way python c extensions work you have to actually parse out the parameters because everything is dynamic in python in c it's not dynamic and so that's where you kind of do that conversion in your c extension so we're saying that you know i need to read two integers here this i i uh and so it's going to parse out the address and the command but all it's really doing is actually just calling this i squared c s m bus read word data function and this is a c function it's provided in a library that's installed on your linux operating system and so that's all that this python library is it's really just a thin wrapper around that c library and so that's maybe where you want to start when you want to look at documentation for how to use the python smbus library look at this page this is from kernel.org which is kind of the home page of the linux kernel and it's the documentation for the sm bus protocol and so this actually goes through and says okay these are the c functions that uh the kernel kind of user interface exposes and so we can see like here's an i squared c s and bus read byte function here's a smbus read word data function which as we just saw in the python library here's the read word data function so it's calling this function which is actually here and now we can actually see a little bit more useful documentation and it's telling us okay this operation it's like read byte but it reads two bytes of data because a word is two bytes uh on on these systems and the really cool thing about these docs is that it actually tells you this is what it's going to do over the wire with the i squared c protocol so uh and it at back at the top it tells you the keys here so you know it's going to start with a start byte so if we look back at the read word data so this s is telling me hey i'm going to send a start start bit then i'm going to send the address then i'm going to send a a bit that says this is a write and then it's going to wait for an acknowledgement then it's actually going to send a byte that says okay here's the register i want to talk to uh and then it's going to send it's going to wait for an acknowledgement and then it's going to send another uh stop start bit and it's going to say okay you know here's the device address now i'm going to do a read wait for the acknowledgement and then read my two bytes of data with an acknowledgement and a negative acknowledgement a knack at the end then the p is the end bit so that was exactly what we saw with our low-level communication here from the sali from the celia so you know we saw that start bit we saw it doing a write with the register and then we saw it doing a read and the two bytes of data coming back and so that's one really nice thing about the documentation for this is that it's maybe a little scarce and hard to track down but at least kind of tells you here's exactly what's happening here and so based on that you kind of know okay when i look at my data sheet from my device you know it's telling me to read the adc value i need to do like a write to the register and then i need to do a read of two bytes and so i can go back and see okay i really want to call this smbus read word data function and so that we kind of bubble back up to python then so this python code is just a real light wrapper around that kernel functionality uh and so now if i jump back to i made a really simple python example here uh that's using that sm bus library so i'm just importing the library um i have a little bit of configuration at the top here to say okay which bus i want to talk to so you know like we saw before there's that i2c dash one some devices have multiple i squared c buses so like the beaglebone black for example has bus zero and one so just something to be aware of you know you might have multiple buses uh so we specify the bus we specify the address so that ox 48 that we saw now to use this library you basically create an instance of the bus so you pass in the bus number you want to talk to and then i do a read word data call so like we just looked at before i passed the address of the device i want to talk to so what i said up here that ox 48 and then the value i want to read from the register ox01 which is the config register for this adc and then i just do an and by fff this is just with python like i've kind of said before all of the numeric values in python are signed 64-bit integers and so when i want to deal with uh a smaller type i want to just and do a boolean and to say okay i only care about the lower 16 bits of that data so ffff is basically 16 bits that are all set to one so we're just pulling out the 16 bit response i technically don't even really need it here it's just i get in the habit when i'm writing code in python that's talking to devices and doing embedded stuff i i like to be very explicit and say okay you know i'm expecting to get back a 16-bit value so those are the only 16 bits i'm going to talk to or care about i'm just going to mask out everything else and it's smart to do that because you might forget to do this and then maybe you actually are reading data you don't expect and you're getting maybe higher than a 16 bit value and things just start subtly breaking so you see this a lot in my code and that's just because you know this is effectively converting to a 16 bit value as a side tangent something i want to do some days i want to sit down and write a little library for python that's like you know an embedded helper because there's no reason that python can't have smaller data types like a 16-bit integer you know an 8-bit integer you can actually see and one really useful thing if you're maybe wanting to go from beginner to more intermediate and advanced python programmer start reading the source code to python you know you can find it online and it's actually installed um i think it's in the site packages folder yeah what the heck let's let's let's take a look and see if we can find it so if i go on my raspberry pi i should be able to go up a value actually here let's go to i don't want to go to the home folder yeah i think let's see it's under oh where do they put this stuff i think it's lib uh no maybe it's not there oh i think it's under the user yeah let's see user lib maybe python27 yeah and so this is actually where you start to see all of the source go to python because most of python is written in python um and everything that's not written in python is just c uh and so if you dig deep into this i forget exactly where it's at but you can actually see where they implement the numeric type in python and it's kind of fascinating but anyways to come back to the tangent so i think it'd be cool someday to sit down and write a python type that's you know like i want food to be you know a uint 16 or something like that that would actually do all of this masking and stuff for you um so something for a rainy day but anyways coming back to this so so this code i'm calling that read word data function i'm getting the value back and i'm just going to print it out so let's actually run this code it's a couple directories up so we'll call the pseudo python i2c read.pi and so it's going to read the value of the config register and it's also going to read the raw adc value and print that out here too so that's what the rest of this code is doing so i'm actually after i read that data i'm doing a write so i'm going to call right word data and you know we can go back and we can see uh the kernel functions again so let's look at like right word data this is probably yeah here we go right word data so you know this is going to do an i squared c right and of two bytes of data so a word of data and here i'm basically just setting the config register to explicit bit values that i want this just comes back to the data sheet for the device so when i want to configure it to do a single shot read i need to send some bits that look like this so i call this right word data i pass the address of the device again i pass it the register i want to talk to register one which is the config register and then i pass it the 16 bits of data that i want it to set to that register and so that's what it's doing here uh it's going to make an i squared c right for this register for that data and then right after that i do a read again and this time i read from register 0 which on this device is the actual adc value so after i write the config i do a read and then i just i do a little bit of bit manipulation so this is another thing i don't like about the sm bus library you know because it has a concept of reading words which are two bytes suddenly you have to deal with endian-ness uh and the endianness is kind of the byte order so you know uh a 16-bit value has two bytes and so there's a low byte and a high byte and it just comes down to where how the data is packed so you could put like the most significant bits you know these are the highest order bits uh that's that control the value the most you could put that first and then you could put the low order bits after that uh and that's big endian or it could be little endian where you have the low order uh byte first and then the high order byte uh the most significant bits after that and that's little endian and it comes down to what processor you're running on and what endianness it expects and it can change if you're running on different processor the raspberry pi for all of your user code like this is little endian so your uh least significant bite is first and your most significant bite is after that unfortunately a lot of devices you talk to like this adc are big endians so they send their most significant bite first and then their least significant byte after that and so that's what i'm doing here is i'm actually swapping the byte order to say okay now you know i uh because i'm running on a little endian device and it's talking to a big endian device i need to actually explicitly flip those bits now if i just talk to the device at a byte level and i ignore like sending words of data and things like that then i don't really have to care so much about the endian order i mean if i want to look at a 16-bit value i still have to take those two bytes and put them together in some way and so i do need to know the endianness there but it's just something to be aware of and to be very careful about you know when you start dealing with more than one byte of data you do have to think about what's the endian-ness of the devices you're talking to and you'll usually see pretty quickly if you get it wrong that like your values are way out of whack and they're you know they're super high when they should be low and you know vice versa but that's all i do here is i just do that conversion and then print out the raw value here so we kind of see that it was giving me a raw dc value of 128 here and so that's really all there is to using that sm bus library i mean it's it's not that hard um as far as you know there's not a lot of setup you need to do you just create an instance of the bus and just call read and write functions on it so there's read word right word uh like we saw here there's read byte write byte you can read a list so there's like these block data function calls uh and so you can read a whole list of data here uh and so that's you know good good to be aware of that that's kind of the high level interface for this uh and then i also i have a little wrapper that i put around this and it's in our kind of python gpio library and so all of the libraries that we publish with python code they use my little wrapper here and this was mostly just to hide platform differences so like i try to detect uh what bus you're using so that you don't always have to explicitly say you know i need to use bus one so like on the raspberry pi i can assume that uh bus one is kind of the default and actually it changed like very early revisions of the pi uh had the i squared c buzz zero so it's weird little things i can kind of smooth out there like on the beaglebone black it has multiple i squared c device uh buses so i pick one and say okay i'll default to bus one but i basically just have a light wrapper around all this so you know i have all these functions here like i want to read you know read u8 so read an unsigned 8-bit value and right here i'm actually just using smbus library i'm calling that read byte of data but i put in these nicer higher level functions like if i want to read a signed 8-bit value i'll do the read and then i'll actually do the conversion to from an unsigned to assigned value so kind of smooth over some of the the issues that you might run into here but at the end of the day i'm just using that same uh smbus library so you know you can see i'm importing it and just using it here i've just added extra functions to it so something i wanted to dive into a little bit deeper then is you know okay so what is the sm bus library actually doing um you know like we saw here it's calling these linux functions uh that are making these escorts yes and bus reads and writes but there's actually something another level deeper that we can look at and it's it's really the raw device here this uh dev i squared c uh zero bus or uh one bus so you know this is a device in the linux uh file system here you can actually open this and you can read and write bytes to it like you were reading and writing to a file and linux will actually interpret that as sending and receiving data over the i squared c bus and it will actually make i squared c protocol commands and calls for you you know just based on that and you can see documentation for this so again if you go to kernel.org and you look up the i squared c dev interface here and maybe when this video goes up on youtube i'll put in the description just direct links to this so you can look and see but this is a really good little piece of documentation to check out just in general all of the documentation from kernel.org from the linux kernel is super useful to read um you know it's it assumes a little bit of knowledge about linux so you know for a very beginner it might be kind of hard to read but as soon as you start wondering like how do these things work go straight to the kernel.org docs because they're they're pretty high quality and so this actually tells us about this i squared c device so it tells us it's a character device that you can read and write data to you can open it just like a file they give sample code here uh that's you know using the open function this is c code and it's you know you can do reads and writes here uh now the interesting thing is like we saw the i squared c protocol is not it's more transaction based so it's not so much of like you know i'm just going to write some data and read some data like i might need to write a register and then read a few bytes of data from that register and so that doesn't fit super well with linux's kind of character device kind of functionality you know there is no like transaction where you can do a read and write at the same time or things like that and so the way they work around that is this i o control function and i o control is kind of a way to talk to a device driver at a lower level it's almost like calling a custom function so a device driver like the i squared c driver can say okay i'm going to expose a character device you can read and write to but i'm also going to expose some extra functions that you might want to call and you do that using this i o control function and i won't go too deep in the details here but using this you can actually do things like construct an i squared c transaction that says okay i want to write a byte of data and then i want to read a few bytes of data back and so there's this i o control function where you you pass it a special read write value and then you have to pass it a structure that you construct that says here are the individual calls that i want to make in this transaction and they go through and they give a little bit of c code there's actually some good code that i saw let's see sean cross i2c device read so let's see if i can find this i can't yeah here we go this example for tickling the i squared c bus so this is a really good example uh from sean cross where it really just shows using the raw i squared c uh device and using that i o control function and this is c code and so it's showing you how to construct you know a couple messages like i want to do a write and a read and pull back data and so this is good to start looking at okay how to talk to these i squared c devices at a lower level and what i did is you can actually convert this code into python code and so python has some libraries that let you interoperate with c functions and so i did that actually as an interesting example that we'll look at here so python actually gives you that i o control function so i can call that from python code now the c structures that i need to build up you'll see some ugly code here and things get a little crazy because to interoperate with c code in python you have to use a library called c types and c types it's actually included in python it's a pretty core part of python because a lot of python is just talking to c functions and so this lets you actually construct parts of memory that are compatible with c libraries and c code so i in my python code i can actually construct a bit of memory that has the same format as the c structure that the i o control function expects and so that's what i actually did here in some python code and maybe i won't get super deep into it because c types in general is really complex because the problem is like python is a dynamic language that doesn't care about types c is all about types and strict you know type checking and things so you have to bridge that gap using c types and so things get ugly uh like i have to actually explicitly define these i squared c structures uh which the kernel defines um and you can see in the documentation here like this i squared c read write i o control data struct you know i basically have to redefine that here and so this is made up there are two fields there's one field called messages it's a pointer to another structure uh it's actually an array of structures and so this structure has like the address the flags what data you want to send so this gets a little crazy but it it's not once you understand c types and you are a little more familiar with python and c code it doesn't look that crazy as far as you know it's you're just mapping the c definition to a python definition here um so once i have those structures defined though then i can actually go in and make that i o control function call so all the way down at the bottom i do that but before i get there i actually have to construct some data that i'm going to send to it and so that's what i do here so first i actually open up that dev i squared c device i read it i open it for reading and writing as a binary object and so once i have that open then i need to construct these two messages that i want to send it so i basically i need to first send a write that says this is the register that i'm going to read from and so i send it a message so in this message i say the address the device i want to talk to so that ox48 like we saw flags this just says what kind of operation this is and so there are explicit values that i pulled out of the headers from the linux kernel that tell you kind of what these values are and so you can say okay i need to do uh there's a read operation type you can also specify like if i have a 10-bit address instead of a seven-bit address lots of little ways to tweak the behavior uh by default the value of zero is going to do a write uh and then the second message i'm actually going to do a read and so i send it a different flag value here but after i set the flag i then need to set the data that i'm going to send in this write and so i'm going to send one byte now i have to send it as a an array or a buffer and so i need to construct that first i actually need to use the c types library to create an unsigned integer 8-bit value and i have to do this because you know c types is letting you interoperate with c code you know there's no guarantee in python that your number is an eight bit value uh so by creating this u n eight you know i'm basically telling c types go make a bit of memory that is an unsigned 8-bit value and give it a value zero and then i basically have to send a pointer to that value and this pointer is like a c types function that gives you a pointer to another c type value so all of this ugly code is basically just emulating um the c code that we see here so you know i'm basically saying okay i need to set the address set the flags to zero and then actually send it a value that i've set before so this input buffer of data that it has so i send one message that's going to write the register uh the config the register that i want to read from and then i construct another message this one's going to be a read and here i'm actually going to read two bytes of data and i actually need to create a buffer in memory of two bytes and that's what this does and then i need to set that in my message struct and you have to do some funky casting here you know if you've ever used c types much it it's ugly and you know it's it's just again unfortunately how to convert between kind of the python world and the c world there are better libraries by the way if you do a lot of c interop there's a library called c ffi c form function interface and that is a really nice library unfortunately it's not included with python you have to install it but you can actually just copy and paste your c header that defines all your functions in your structs into a string send it into that library and it will do all of this junk for you so you know i'm actually doing this the hard way because ultimately i want to write a pure python library to talk the i squared c protocol because one big thing i kind of forgot to mention that python sm bus library only works with python 2 unfortunately so it's owned by that i squared c tools package you know this lm sensors library for whatever reason as far as i can tell they don't have a python 3 version of it and one unfortunate thing in python 3 they change the format of c extensions so this thing doesn't work anymore which is a problem for us unfortunately because all of our libraries are written to use python smbus and so that's kind of what made me start looking at things at a lower level is to see okay how can i make i squared c stuff work in python 3 and i think a smart way to do it it's not actually the best way but a smart way to do it is to write pure python code where there's no c extension it's just python and pure in the sense that i'm not relying on any libraries i'm just using built-in python stuff so the c types library is built in i o control is built in that's all i need to talk i squared c and so when i do that then i can actually make this work with python 3 because python 3 also has the i o control library function it also has c types built into it there's sometimes little differences in those libraries but you can kind of smooth those over in your python code you know i'm not actually building a c extension and the nice thing is because i'm not building a c extension you don't need to have the python developer tools installed like all the headers you don't need to have the c compiler installed you know you can just do a pip install that's doesn't depend on anything really for your library now i said that this is not actually the best way because there is a little bit overhead when i'm doing this so you know you can see all this code i'm doing like i'm creating bits of memory to hold like a byte of data i'm creating you know two bytes of data as a buffer that's overhead like it takes time python has to do this it's going to call some functions to allocate this memory versus the c code here you know this memory might have been allocated on the stack and so it's just there as soon as the program runs you know there's there's a lot less overhead here so so it's going to be a little bit slower and i think that's an okay trade off so you know it's like the classic like speed versus kind of functionality trade-off um you know in my case for all these libraries we publish we want them to be easy to use so like if you don't have to compile a c extension if you can use it with python 3 that's a big win you know speed is not necessarily our highest priority for these um especially for stuff like i squared c because you saw the bus runs at 100 kilohertz the you know with a gigahertz processor you're going to fly through a lot of operations in the time it takes for the i squared c bus to return some data so even though there is a little bit of overhead here i think in the grand scheme of things it's okay and you know the famous quote premature optimization is the root of all evil that's that always holds true so you know your first inclination might be oh you know this is not going to work i need the fastest stuff possible but try it first you know try the simple way um maybe the the easier to kind of maintain way and in most cases it's probably going to work and there definitely are cases where it's not going to work you know you might be in a tight loop where you're reading data constantly and in that case yeah go optimize that path like you know maybe go into that loop and don't use this library you know write a c function and python makes it really easy to talk to c functions and so call your c function there that's faster and more optimized but in this case anyway so coming back to this you know that's why i'm looking at the i squared c protocol at such a low level here because i think it would be interesting to have a little library that that actually just talks this low level kind of linux i squared c uh protocol so anyways coming back to what we were talking about before uh ultimately we've got the two messages we constructed now i just need to build this i o control um kind of transaction that just has those two messages uh and so you know then then i make that i o control call right here and so i have to pass in the file descriptor for this file that was opened here uh i pass it the i o control function call i want to make and this is actually just a hex value so it's um let's see this is yeah this 0707 and so the device driver for i squared c will then interpret that as it's going to do a transaction of multiple messages and i'm passing in this request that has these messages that i built so once this call happens it's going to see it's going to unpack this array of messages and say okay i need to do a write with one byte of data and then i need to do a read with two bytes read two bytes of data and return them back and the way the data is actually returned this buffer that i pass in right here this kind of result type this will actually be set with the value that was read here so let's let's run this function let's run this example let's run this raw itc read and you can see it gives me back a value so ox 2097 now let's go back and let's run my read value here so this this was the previous example so this is the sm bus example and you can see it's returning the raw adc value of 2097 and then that raw i2c read this is the kind of raw i o control version and it's returning the same value so that's good like that's kind of telling us hey we did the right thing here uh and it's it's kind of showing us that you can make these low level i o control function calls uh even from like pure python here and you can get data back uh that kind of matches the data that you expect here so like i said before this is kind of the low level most people don't need to get this low of detail but it's good to know that you can um you know like i said before that this sm bus library it deals with like words and bytes and everything has to have a register in most cases to write to and not every i squared c device uh deals with that protocol not every i squared c device is an sm bus device for example so if you do have a device where it doesn't deal with registers for example you might need to drop down a little bit lower and do things like these i o control calls uh or you know you can actually just read and write uh bytes of data to this i squared c device but again it's you know not something i wouldn't jump to this first you know when you're talking to an i squared c device try and use the higher level abstractions like the sm bus library um you know and then once you run into a roadblock with that then go a little bit lower because as you can see in the difference in code here like there's a lot of stuff going on there's a lot of boilerplate a lot of things to get wrong and one big thing too when you're interoperating with c code you don't have a lot of protections as you do in python so you know if you get uh the length of your buffer wrong it will happily overflow memory and you know you could seg fault and have all kinds of fun stuff uh exploding on you so it's it's not fun but it is possible and you know you can definitely do this kind of interop in python um so i think that's all i wanted to cover as far as like you know a bit of a super deep dive uh maybe just to kind of summarize so talk a little bit about like what i squared c is uh the wikipedia page is all right it's a little long but you know you can kind of google around and search and see it's just a serial protocol for two chips to talk to each other and it has some advantages and disadvantages uh compared to other serial protocols and we looked at on the actual wire like what's happening with an example of an i squared c device so using the little celia logic analyzer you can see the i squared c transactions and the start bits and stop bits and the clock and the data and we kind of talked a little bit about the addresses and the different types of transactions you can do like reads and writes uh and then we kind of dove into how to talk to this stuff from python using that smbus library um you know here and like here's the the github code for it um and so this is you know mostly the the high level interface that most people are going to want to use uh and then we looked at the low level stuff of like okay what does the actual linux kernel expose to talk to these um i squared c devices and how can you even talk to that from python and so you can see just using that i o control and the c types library it is possible to do that i guess anyways any questions or anything i don't see a lot of questions in the chat right now but you know maybe fire them off and i'm happy to answer them um but yeah i hope that this is a good kind of overview of i squared c and how you might use it in python um you know it's it's it's kind of interesting i think too just to look at all the different kind of protocols at a low level and kind of see what's happening here um you know i mentioned at the start the spy protocol so the linux kernel does have a low-level interface to that so let's see let's search like linux spydev and so yeah again on kernel.org so this tells you all about how to talk to a spy device and maybe a future video i could go in and get a little more deeper dive into that one but just like i squared c it's got the same thing uh with i o control and you can read and write characters and things like that so it's you know sometimes interesting to go down to that lower level i don't see a lot of questions so i think that's probably it we'll wrap it up and that was the deep dive with python and the raspberry pi so thanks a lot for watching uh subscribe to youtube.com adafruit and you'll see a lot of cool projects and things uh subscribe to twitch.tv adafruit and you'll see um i usually stream to twitch uh and you know keep kind of up to date on what's going on uh definitely check out the twitch channel on the youtube channel you'll probably see some more from ladyada she's streaming a lot this month for the raspberry pi zero contest so if you haven't seen it uh go to the see hackaday zero contest so with the raspberry pi zero there's a contest going on if you come up with an idea for a fun project you might be able to win a raspberry pi zero and then if you build a fun project also uh there's some cool prizes you can get for that so check it out uh thanks a lot for watching and i guess i'll see people uh probably in a week we'll do another stream on some stuff
Info
Channel: Adafruit Industries
Views: 44,365
Rating: undefined out of 5
Keywords: adafruit, electronics, diy, arduino, hardware, opensource, projects, raspberry, computer, raspberrypi, microcontrollers, limor, limorfried, ladyada
Id: kxaFbDY-wH0
Channel Id: undefined
Length: 63min 12sec (3792 seconds)
Published: Sat Feb 13 2016
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.