 Okay. So welcome to the first session. Just, I'm going to start with a quick disclaimer. So one of the talks had to change at the very last moment. The talk that changed was the intelligent K nearest neighbors by a partner, and that is now substituted with the Python object model by Michael Ford. I hope everybody enjoys it still. The talk that we're about to see now is an introduction to hardware drivers in MicroPython by Carlos. Thank you. Thank you. Good morning, everybody. So a little bit about myself. I'm Carlos. I am an embedded software engineer, and I've worked on a couple of different industries from small embedded devices to rather large ones. Now I'm very happy to work at the MicroRid Educational Foundation, where we use an embedded device that runs MicroPython to kind of inspire and enable the next generation to create and use technology. So my academic background is actually in electronics. There you learn to design circuits and programming languages like C and C++ at the same time. So it's very easy to see the correlation from the code that you write and the things happening in the hardware. But we all love Python. And with any other higher-level language, there's a little bit more space between the code and the lower layers. So I'm hoping that on this talk, we can cover a little bit on the hardware layers and understand that a little bit better. Right. So MicroPython. Could I get by a show of hands? How many of you have used MicroPython before? Okay. So a few. MicroPython, it's a lean and efficient re-implementation of Python 3. It's been done by Demian George from the ground up to be run on memory-constrained devices. The cool thing about MicroPython is that you have Python running on the target. So it includes the parser, the compiler, and the VM. Python comes with batteries included. And MicroPython has to be a little bit more conscious about the things that it puts into the target. So batteries are not included, but they can be easily installed. It can run in small things like devices with 16 kilobytes of RAM and 256 kilobytes of RAM, which is tiny. It's incredible. But you can also run it on a really wide range of different platforms and boards. One thing I would like to emphasize is that this presentation is actually not exclusive to MicroPython. A lot of the things that we're going to cover here are basically applicable to any Python implementation or really any other language. We have a few examples here, things like run MicroPython like the MicroVido or the PyBoards. We have circuit Python like this circular board over there and things like the RaspberryPy, which basically runs CPython, but it has libraries that let you interface with hardware. So let's say you have your Python board, you write some code, and it's all fantastic. But what do you do next? Well, we can add some electronics. And this is where the fun begins, because electronics let you do more things in the real world. You can get something, you can get modules like the ones shown here, where like everything is ready for you to just plug in some cables into your board, or you can build it on yourself. If you're lucky, maybe there is a MicroPython library already published in PyPI. In those cases, we can use things like UPEAP to install it. But if there are no drivers or something doesn't work, what do we do? Well, that's where the real fun begins. But before we do all that, let's first check that there isn't a library available already, and that we have all the information that we need. So the first thing that you do is go to Google and just get the part number, and you can put a word like Python or MicroPython next to it, and you should be getting a few good results. But if Google is your friend, GitHub is your best friend. Just get the part number put in there and filter by Python, and hopefully you'll be getting some libraries that way. If you cannot find anything that's Python specific, have a look at other implementations. Those are really good for reference. But don't forget to look for the official information. So going to Google, just put the part number, hopefully the manufacturer will be one of the first results. Sometimes you'll find data sheets in other websites, and that's fine. Just make sure that you're getting the latest version of the data sheet. Those don't always are sometimes old. And don't forget to make sure that you still visit the official product page, because that will contain additional information, additional code, things like erasas and application notes. Right, cool. So we have our board, we have a module, we've connected some wires, we type some code, and something happens. But how does the code actually make something happen in the real world? Well, let's look at an example. In this case, it's basically just a snippet from a constructor for an accelerometer driver. And it starts by initializing something called I2C, it reads something, compares that with a number, it then writes to something else, and then compares that to what it does and and operation. And if this is the first time you're looking at drivers in MicroPython, this can be a little bit confusing. So let's take a step back and have a look at the software stack. We have our Python code at the top, and it gets interpreted and run by MicroPython. In this case, MicroPython is implemented in C. And within MicroPython, we have something called a hardware abstraction layer. These are usually C and C++ libraries from the manufacturer of the microcontroller you're using. And that's where the magic happens, that's what lets you configure and use the hardware. In this case, we have the hardware as the green box, and you can see we have a lot of small boxes underneath, well, inside it. They have, you know, weird acronyms. And what these are, are blocks of secretry that you can set your microcontroller that are in charge for a specific hardware feature. What this means, really, is that you can use these peripherals, these blocks we call them peripherals, to communicate with a sensor, to read an analog sensor, to output sound on a speaker, to move a server or put data on an SD card. Great, okay, so we know that we have to do something with these registers, but how do we do that? Well, for that, we have to look at the memory map of the device. So this comes from the data sheet of a microcontroller, and it tells you things like, you know, the code is meant to live in that specific memory range, and RAM is in that specific memory range. But the part that's important for us is this one, the peripherals. We look at the peripherals documentation, and we have a table. This table has a list of peripherals, it's a little bit small, so let's zoom in. And each one of those peripherals will have a specific memory address. Then we look at the peripheral information. Oh, before we do that, let's take one example. Let's have a look at the GPIO peripheral. Let's say you want to connect an LED to your board and turn it on and off. We look into documentation for this peripheral, and we find a table like this. This is basically a register table. Each one of these registers, you can think of them as a word in memory that can do something with hardware. We'll have a look at the first one, and we go into the documentation, and now this time we find another table. Now, our register in this architecture is 32 bits, and we can see right at the top that we have 32 bits, and we have a table that tells you what each one of those bits does. So let's say we have a cable connected to your board connected to pin zero. In this case, pin zero is represented by bit zero, and we look at the table and it says that if we write a one into that bit, the pin driver is going to go high. And if we do that, the LED will turn on. On the other hand, it also tells us that if we write a zero into that bit, the pin will go low, and that will turn off the LED. And it's really that simple. All we do is we look for the right register in the right peripheral, we write some bits, and we read some bits from there, and that's what makes our hardware do stuff. Fantastic. So we have, you know, our microbyte on board, we write some code, and we understand how the code that we are writing is controlling the microcontroller. But we actually have an external sensor, and we want to talk to that sensor. So how do we do that? Well, there is mostly two ways to communicate in between electronic devices. You have analog signals and digital signals. With something like a temperature sensor, it has a voltage output. If the temperature goes higher, the voltage goes higher, and if the temperature goes low, the voltage goes low. We can read that voltage and interpret that inside the microcontroller. But these days, most electronic devices, especially inside a PCB or a motherboard, they communicate using digital signals. In this case, when the voltage is low, it means a zero, and when the voltage is high, it means a one, and you can send data that way. We are going to talk about these two different digital protocols. The first one is the UR protocol. UR stands for universal synchronous receiver transmitter. Basically, we have two devices, and there are two cables. These cables are unidirectional data signals, which means data only goes in one direction in this cable. So one from device one to device two, and one from device two to device one. There are all the optional signals to control this a little bit, but we're not going to talk about it. One of the things that you have to do with UR is that the bowdrate, which is the speed at which the data is transferred, and other configuration bits are usually pretty fine by the application. When you're designing your system, you already know the things that you need to apply to each device. When you're building a new board, UR is one of the most commonly first peripherals you interface with, because it's an easy way to get data out of it. The signal is idle, so it's not doing anything, and that device wants to send some data. The first thing that they will do is that they will send the start bit, so it goes from high to low for one cycle, then it will actually send the bits that it wants to send through, and at that point, there will be an optional priority bit. This is actually used for error correction. In this specific example, we're not using it, so it's not here. And the last thing we do is we just do this look in Python. It's quite simple as well. We initialize an instance of the UR class. In this case, the first argument is just telling which peripherals inside the microcontroller we're using, because you can have more than one. And then all the settings that we've discussed are just parameters passed to the constructor. Then we use the instance and we can use methods like write and read to get the data. The second protocol is the I2C protocol. It stands for I2C protocol, and it's a little bit different. In this case, we have a master device at the top in blue, and we can have quite a few slave devices. We still have two signals, but in this case, all of the devices here are connected to the same two signals. The way that we can talk to each one of these individual slaves is because each one of them has an address, which is basically an ID. So when we start a transmission, we start with that data that goes in this bus, can be configured. The most commons are 100 kilohertz and 400 kilohertz. And there are all the protocols based on this as well. So let's have a look at the signal again. This time it's a little bit different. A few more things happening here. So when the signals are idle, you have both signals high, and all the messages are started by the master. So when the master wants to send something, first in the blue is that they'll send the signal, and it starts sending the clock. With the clock, it sends the address of the slave it wants to talk. So it basically is just saying, hello, I want to talk to this device. And right after that, the last bit, it's called the read or write bit. This will actually tell the device if it expects to read or write from it. Then the master will stop controlling the data signal, and the slave will take control. At this point, it will send an acknowledged bit, which is what I need to do next. And in this case, it's going to send data back. Then it's going to drop control of the signal, and the master is going to send an acknowledged. And when both are ready, they'll just let the lines go high, and it goes back to idle. And what does this look like in Python? It's quite simple as well. We initialize an I2C class. We give it the frequency, and we can use methods like read from to actually perform this operation. And the third protocol is the SPI protocol. It stands for serial peripheral interface. This one is a little bit different. In I2C, we have two bus signals. In SPI, we have three bus signals, and then we have an additional one called a slave select. But let's look at the common bus first. We have the clock, just like I2C, but then we have two data signals. One is called the master out, slave in, and the other one is called the master in, slave out. And these are unidirectional signals. They send data in just one direction. The thing about the slave select is that you need a pin or a cable for each one of the slaves you've got. So if you have one slave, you need four pins in total. If you have five, sorry, if you have two, you need five, and so on. So let's look at the signal. Well, in this case, the first thing that the master wants to do when transmission starts is to drag the slave select down. This means that one specific slave and say hello, I'm about to send you something. And immediately after, it sends the clock and the data. And in this case, there is no star bits or anything like that. Data just goes straight through. And when the slave is ready to send data back, it will just do that. It will do that in the misalign. And as I mentioned before, these are unidirectional signals. It is a synchronous protocol meaning that it starts with a master, but both devices can be higher than I2C. And because you don't have all that boilerplate on a single two-wire bus, it can be quite a little bit quicker to send data as well. How do we do this in Python? Quite similar as well. So we initialize the ASPI class. We give it some configuration data as constructor parameters, and then we can use methods like send, receive. Right, okay. So now we know that we can actually control these signals and buses to communicate with the sensor. But what do we need to send to the sensor, and what is the sensor going to send back to us? Well, let's think of it as, say, you're writing a Python application, and you want to talk to an rest API point. What do you do? Well, you look at the documentation. And in hardware, the documentation are data sheets. Now, the first time I looked at a data sheet, I thought it was quite scary. These are usually written for hardware and software people, so there's a lot of things that perhaps you don't understand or you've never seen before. It's like, you know, what you can ignore and what you should be reading. So we go to the manufacturer website. In this case, we look at an accelerometer device with a lot of PDF. It happens to be 60 pages. It's not huge. It's not small. And we open it. We have this first page. This is actually, you know, you can think of it as a marketing page. It will contain the summary of what the device does, the features, and a couple of things. But it's a really good introduction, so make sure you're reading it. If you have an index, there will be a few pages like that one that has information about the package. And, you know, you can skip that. We may have, you know, 5 or 10 pages with a lot of tables and numbers and voltages and currents and drawings of signals. Those are usually for the hardware developers, the hardware engineers. So, you know, you can skip those as well if you want to. We go to the next few pages. And in this case, we have, like, eight or 10 pages of just text. And, you know, make sure you read the introduction. It gives you a good idea of, you know, how to control the device. If there's a section like how to initialize it, you will need that as well. But then just, you know, pick the sections that are going to use the features that you want to use. A lot of the times you get a component that can do quite a lot of things, but you're only interested in doing one thing, so there's no need to read everything. Although, you know, if you have some free time before you get there, it works. If you know how it works already, then it's fine. You can skip it, but, you know, it's a good reminder. And then we get a register list. And this is familiar again, because if you remember, we were looking at registers for controlling the microcontroller. And in this case, the sensor has a very similar architecture. You will have a bunch of registers, and you write some data or read some data from those, and that's what's going to make the hardware do things. So, familiarize yourself with most of this just to get an idea of what's inside, but you don't need to read everything again. Just like the previous section, just find the registers that you need to use, and you can skip the others. In this case, there's almost 30 pages of registers, so pick the ones that you're going to use, stick to those. And then the last few pages have a few more mechanical drawings and things like that, so you don't need to worry about that. Okay, so we have the microcontroller, we have some data, we have the microcontroller and we have the microcontroller, and we know more is what we expect back. But what happens if we don't get back what we think we're going to get back? Well, at this point, we may have to start debugging. Right, so one of the first things you should probably do, it's to double check all the connections on your power supply. The second thing you should be doing, it's to triple check all the connections on the power supply. After that, you should make sure that your devices are all on the same logic levels, especially when you're buying things like modules, sometimes they are designed to work at 5 volts and your device may be 3.3 or the other way around, and it's always good to check this before you wire things because if you don't, you can fry some chips. Another thing you can do is to actually reduce the speed of the data that you're transferring. If it works in that case, then you know there's probably an issue with drifting or anything in the hardware. Then we can, well, then we should probably just try to replicate using the smallest possible example. This is just good practice in general, but it's quite useful in hardware as well. If it still doesn't work, make sure you search for a RATS as an application nodes. A lot of the times there will be bugs on the silicon and it's good to know what those are, sometimes they will include workarounds, really they all should include workarounds, and application nodes may actually give you to communicate with your device, so that's useful as well. If it still doesn't work, make sure you challenge all of your assumptions. When I was showing you a data sheet before, there was a page for I2C. Now most I2C devices will stick to the standard, but sometimes they don't, and if they don't, you'll find it on that page. So keep that in mind. Okay, but one of the most useful things when you're trying to debug is to actually get some data out of your system. We can try to, you know, the all you could do something like a UART port and a UART to USB bridge. You can also use the hardware debugger. Now hardware debuggers can be quite complex. Debugging in hardware is a little bit different than debugging in software, and when you're debugging in MicroPython, really what you're debugging is the C source in MicroPython that's interpreting your code. If you're familiar with C, that's fine. But even if you don't want to look into that, it still gives you like registered data, so you can actually get some hardware being configured and get a good handle of like the data that's coming in and out. I've given a couple of recommendations here. The first one can be used for commercial products, so just make sure you read the license. The second one is a little bit more expensive, but it's really cool. Check it out, I recommend it. And another thing you could do is actually check if your board comes with a debugger. A lot of the times boards will have like an additional board that has the same processor that you're using, and that might have a debugger in that board, and that can still be quite cheap. Something else you can do is use a logic analyzer, and I will cover that on the next slide. And if you don't have anything else, then you can just use LEDs. If your LED turns on at this point, then your code is there. You can do fancy things like getting out Morse code if you want to, but just an LED lighting up, it's still a good indication that your code is doing something. And that's why I recommend this on our presentation. Mostly because the first example is something that I would normally recommend. It's what I would say cheap for a professional tool, quite expensive for a hobby tool. But thanks to the open source community, now we have things like SIGROC. SIGROC is an open source signal analysis software, and you can buy devices that are SIGDROC compatible for as little as 10 pounds. What an logic analyzer is, it's basically what you're looking for. You can plug those cables in the bus that you're trying to debug, and it will give you a really nice viewing of what's going on. It will decode the data, so you don't have to look at the ones and zeros to figure out what's happening, and you can see what's actually happening down the wire. Right. Okay. So in summary, we've learned that there are peripherals inside your microcontroller. Those are the things that control SIGROC, and it writes and reads registers from these peripherals to do something with the hardware. We have learned a little bit about the different protocols that we can use to talk from your microcontroller to your device, and that there's quite a few different options, and we've looked into data sheets, just kind of getting a general sense of the kind of things that may be useful and the kind of things that you can ignore. We've looked at a few debugging tips, and I hope this is helpful. Thank you. If you are writing Python code, there's basically plugins for most of the ideas out there. So PyCharm has a micro-Python plugin, Visual Studio Code has a micro-Python plugin. I would just say use whatever you're comfortable and see if there's a plugin that lets you use your micro-Python device with it. Pages of PDF data sheet show. Is there any effort in the electronics industry to make some sort of machine-readable equivalent of a data sheet? That is a very interesting point, and I believe there is a start-up that I actually kind of aiming to that. This is quite useful, especially for buying parts. There is a lot of info in the data sheets, and retailers have to spend quite a bit of time extracting the data so that you can search by specific parameters. I don't know if there is a company or a product or a software that is actually aiming to do that for the developer side, it would be very interesting if there was. Do you remember the name of the stuff? No, but I can look it up, find me afterwards and we can find it. This question is more about Micro-Python than about the hardware implementation. I've spent a little time, I've invested a little time in Arduino. Pursuade me to convert to Micro-Python. Do you like Python? Use Micro-Python. I really cannot say how easy it is to get started in Micro-Python, in Python in general. I spent quite a lot of years writing C code and a few years writing Python code. If you want to prototype something quickly or you want to prove an idea really quickly, it's very easy to do in Python and Micro-Python makes that possible with hardware, which for me it's a really huge advantage. If you compare the Arduino code with the Micro-Python code, the Micro-Python code is going to be easier to understand. There's a few disadvantages as well, like Arduino code gets compiled so it can be a little bit more efficient or maybe there's not as many things in memory. You spend more time reading code than you do writing it, so the fact that it's really a great advantage. Thank you. Just a quick question. With the Python you written with Micro-Python, have you shipped any of those devices to production? Mainly for prototyping. In the company I'm currently working for the Micro-Python educational foundation we have the BBC Micro-Python, which is an embedded device that can run Micro-Python between other things. This was actually a volunteering community effort from the Python organization. There's people in this room that have invested lots of time as well to make sure that Micro-Python is easy to use for education. It ships with basically every Micro-Python device. I think there was a talk last year in Python that talks about Micro-Python as a YouTube channel that has a lot of good examples as well. Does Micro-Python support a scheduler so that it has async, IO, or threading? Right. There's ways. It depends about the ports of Micro-Python that you use and using the latest version of Micro-Python. There are some support for things like async. It's not fully supported, but you can do some things there already. Okay. Everybody, let's please thank Carlos for the amazing presentation.