 The story so far. I am porting physics to the ESP8266. I Am successfully boot at the kernel. I have a file system with userland binaries in it I'm loading the binaries and enough stuff is working that I'm actually getting a login prompt Which is nice. We don't have any interrupts yet, which is showing up on the list of things to do and and Frankly right now the boot up speed is Too slow, so if I hit the reset button on the board Here it's booting So it's loaded in it. It's now going through the startup procedure and It's painful frankly. This is unusably slow. I Think that half the trouble is that it's doing lots of swapping to Flash and I think the NAND flash is rather slow But we need to do something about this So while that continues The simplest thing to do is to simply increase the clock speed now There are two clocks on the system. One is the CPU clock, which is set here This Can be set to various different values. I'm not sure if it can be scaled arbitrarily I Have found some really useful example code Here Which configures all this and I also need this file So here are the various configurations you can use This is the main clock. This is the secondary clock The secondary clock the peripheral clock is used to control the speed of the peripherals obviously enough and I don't know what the default peripheral clock speed is If it's slow that will slow down all access to the NAND flash NAND flash is a SPI device so It's serial input and output so that will be Approximately 10 clocks per byte So the we I'm using the default value of 52, which is this top one and not touching anything else So let's see if we can crank this all the way up And here we have our getty our startup banner and our login prompt. We don't have Any TTY input yet, so we can't do anything, but it's got there. It took several minutes to do so All right, so that would be CPU clock Here we have to configure the UARTs own clock which is based on I thought this was going to be based on the CPU clock actually, but maybe it's the peripheral clock Well, there is one way to find that out. She's pretty straightforward Okay, so this should let me type correctly here we go So this should do exactly the same thing with nothing's changed. You've just fiddled with the code slightly so can we put this Okay, we've got see we've got serial output. So that is Working so let's change this to 80 and see what it does and the answer is it's garbage So I'm going to guess that that's based on the peripheral clock Okay, let's take a look at the code in here that actually sets up the clocks there is a surprising amount of it so in order to configure the peripheral clock you need to configure a PLL a Programmable logic loop Essentially a programmable digital clock and Some of the more exotic configurations Require this some configurations allow us to require overclocking This I think doubles the clock speed. So Actually, there is one thing I want to do first. Let's try this So this should keep the UART based at the 56 megahertz clock. Yeah, okay That's working. So we've managed to increase the CPU clock, but we've left the peripheral clock as it is So we should be able to crank the CPU clock up as much as we like but I Don't honestly think we care about that too much. I'm not entirely certain. I understand this code So that all these clock rates it's setting the CPU clock to 80 so unless that is Then modified by the PLL code, which is up here so if we want to Increase the peripheral clock speed. We want to set it 80 then we do need to Set up the PLL Although if we go through here If you set them if you set the 52 it never calls set up PLL. Is there anything in here? I don't think there is there isn't okay, so if we go for 80 Then we have to Set up the PLL and set clock speed to 80 And then configure the UART to the peripheral clock speed so Set up the PLL. That is this code this is reading from the system configuration whether What clock crystal is being used with these modules? It's always 26 megahertz apparently So we we can just ignore this this codes commented it's out so this should configure the clock to 80 you can also get 160 Okay, this is configuring Yeah, what this is doing is it sending values out to the I squared C bus and there'll be a whole bunch of random peripherals attached to this I believe that four comma one is configuring the Peripheral clock and four comma to the CPU clock Maybe so according to the documentation this sets it to 80 megahertz Now that will fail to build because we don't have the I squared C right reg Defined here. It's not. I think that's to find here this is is Pico I squared C right reg asm But this code is calling Just right reg interesting Okay, so this is defined to just call the ROM routine Which is the one we want to be honest The Pico 66 configuration doesn't use ROM routines where possible But we want to use them where possible and is it called Right reg Yes, it is and it's at that address So we want to steal this Stick it in here and void Ron I see you right reg and we want to Add a reference here Let the capital R and right reg I see right reg equals here this file is the The startup code this gets loaded into instruction RAM by the ROM bootloader We run it and then discard it So it's a good place to put this kind of configuration so with luck this will configure the peripheral clock to 80 megahertz and It that means that the ui should work or not Well, it's printing something it's running so why Do we need to do this as well? Yes, we do Okay, so we should now have faster The peripheral clock is a bit faster Can we get it faster? Still I can't actually tell whether this is noticeably faster or not I've done some timings So ideally we want it as high as possible. So All right, so this is configuring the CPU clock Including this thing for overclocking The CPU clock needs to be higher than the peripheral clock Otherwise, there's you know, no point. It's not exceptionally faster. So if you change this to a C1 189 Does this work? Apparently it does Okay, okay, let's try the CPU clock now There's this thing which sets bits to do with overclocking which clearly this doubles the System clock so we have to This this is commented out so this is setting up the PLL We've got the code to do that But then it's just setting the CPU clock to 80 which we are doing So we should be running at 189 megahertz for the CPU clock Do we need? Overclocking bit No Well, it's not noticeably faster Is this as high? This is as high as we can get the peripheral clock, but we could try and get the CPU clock higher Okay, I need to try and duplicate what this does In my own code And that's of course is just doing nothing. Okay, deport base adro is Find somewhere here It is this address All right, that is just a fixed address so we can do do this looking at our Linker script it is defined Here as deport D ropes egg And why those are extern? We're not using those All that or any of that That's probably used by the bootloader Null Interesting. I should go look up what those are okay, um So we've got this which means that we can just deal the relevant bit of code so if we want looks like 346 is Is he actually use the clock speed anywhere? No, it doesn't So in fact these all work at the same Speed so we've configured this to hopefully 189 and we don't need anything else assuming this is right So I think the only thing we can really do is Do this to Enable the overclock bit and try to see Okay, that's interesting So the you aren't working But it's failed to Talk to the flash so enabling this has in fact broken Flash access It's does seem to be tinkering with the bottom bit Of that register Okay, I do not know what this is doing. There's a reference to this code So let's look it up and see what there is it's Very Russian who can figure the flash he wasn't aware that was a thing Do we need to set the flash to high speed mode This is the exception handler stuff we dealt with ages ago Wi-Fi stuff. We're not using set PLL Set 80 megahertz PLL CPU That's writing both values 136 is 8 8 and 1 4 5 is 9 1 and according to this table 9 1 is 80 megahertz Doc this allows you to overclock the system Okay, nothing terribly useful there But I am curious about this SPI flash Config so This does look like a speed parameter and there's another one here Okay, well, let's just try copying this over and see what it does Probably want both of these and honestly the flash idle thing. Okay, so flash chip is Actually defined here We don't have flash config Config I Don't think that's in wrong 4568 so this disassembly doesn't know what it is Well, let's add that right SPI flash config at that address and the other one who was Read mode config is at this address And I should do a look up and find out what these see if anyone actually knows what these do And we do need some prototype SPI frag and the other one was Is here. All right, what does this do? And these are not in the Jurisding ah The naming scheme used by this code uses capital SPI But we're using the other one so this is whoops What's this? Okay, that got nowhere so let's Comment out one of these Both of these and see if If That works hang on no ampersand there would have been a warning produced, but it Warnings aren't very helpful this code because it just like scrolls off the top instantly. Okay, that works So let's turn these back on and see if it makes a difference. I Wonder if the flash is actually running in some really slow mode And that's not better See this is setting it to five I mode five SPI modes. I don't think this is the kind of mode that it's talking about Yeah, I think this is it so Only goes up to threes Well, we can set it to five and see what happens That's no faster When all else fails try random web searches. I don't see anything particularly obvious It's just a reference to the thing we've already seen What about SPI flash config again? Nothing much Let's try it spelt correctly Aha, that's irrelevant Okay, this is This is all spurious stuff. This is talking about a flash tool So SPI read mode Config no nothing Okay, I have no idea whether that helped It's still looking dead slow We've improved the clock speed At least I think I've improved the clock speed It's not helping but Uh, I will have to go away and do a bunch of research to figure out what's going on with this And I'm not going to do that online Okay subject change A thing that occurred to me As soon as I finished the last video is that the stuff I did to implement trim Is all wrong like completely hopelessly wrong What's happening is Uh device zero is the the hard drive the flash It gets routed through the block device layer The block device layer then calls the callbacks that are set here Transfer cb is the the only one so far This then actually does the underlying read or write Now the thing that's wrong is that the block device layer is translating between the Uh the sector that's been asked for And the sector on the underlying physical device And my ioxal routine here is not Minor here can refer to any partition on the disk But this is always Working in terms of absolute sectors on the on the flash partition So The sector we are in fact trimming is just random garbage I'm amazed it hasn't actually corrupted anything. Well, maybe it has and we haven't noticed So I'm going to have to Undo things and add Add support for the trim ioxal to the block device layer rather than Bodging it in so Instead of dev flash ioxal We want Trim cb and this is Nothing This is block op lba okay, we want to Add our callback here That's going to fail because the block device does not have a trim So we're going to have to add it so dev block dev h So we create a function prototype and Add the field to the block dev structure. This should build now yep And now we need to add into the block dev code itself Here in ioxal We're going to have to put in Shift width equals four tab space equals eight So if it's flush We do that if it's trim We fail And if it's anything else We fail so What we're going to have to do here is do the translation which happens in this code I'm wondering the best way to do this um I think I need to create a Right all this wants is input is the partition Which we get take from the miner. So that just wants to minor In return we want a simple success fail code Which we want to be an int this code Goes up here This goes here Fast So this will translate block op According to the value of partition. Where is the partition used? right Partish the partition variable is only used by The translation code so we can actually lose this completely Here we say if We've failed to translate The minor fail Okay So that should build It does not build Because I have not Done that Okay, so this needs to be if defined config trim. Oh Uh, okay, this isn't right So it's read the partition table We return one if Translation fails That looks right actually What have I done wrong? so the Uh, the minor device is each device is referred to by 16 bit value The top 8 bit is the major which indicates what device it is. That's the offset into here the The bottom 8 bits Is Uh It can be anything depending on what the The major device wants but the block device driver Uses the top 4 bits to indicate what block device it is so this is pulling out the Uh, the top 4 bits from the minor and getting a reference to the block device to put into block dev This is pulling out the bottom 4 bits that tells us what partition it is so yeah one on error zero on failure Have I broken trim? Don't think so Uh trim eye octal. Have I broken eye octal? Hmm That has not printed anything here Is he actually calling block dev transfer? No, it's not Is it calling block dev eye octal? No, it's not Okay Thanks to the magic of undo buffers. Let us unwind all this code And just double check that this is working Okay, we've broken something else Good Uh probably I know what's happened. I know what's happened. I changed the header and didn't Clean So let's try this Because these the size of block dev changed but not all the yep, there we go It's still not printing anything we haven't saved Uh, not all the source files have been rebuilt with the updated block dev size So heaven knows what was going on. Okay. We're translating lots of stuff. Good So down here in eye octal I believe that this code Is safe to common out for all Eye octals I don't think there's any situation where we we're where we can be called With a invalid minor So what we want to do is call trim But first we need to set up the We need to set up lba now char data so The argument for eye octal is a pointer to user code Normally we don't have to worry about using user and kernel code But we're an eye octal in generic code So we do have to think about such things So that's happening in syscall fs.c So I'm what I'm wondering here is whether the value is actually read for us before being passed on to the block device driver I Don't think it Is So di octal is in dev i o dot c di octal Is just being passed straight through So we need to so The block number is a Long in user code So you get l Do we need to check that? Probably not. It's pretty harmless So we fetch the lba We translate it According to the minor If we successfully translated it And let's actually put this here If we successfully translate it We just call trim. So trim should be being called with the correctly translated lba sector Avoid value not to return. Yes This wants to return an error code Uh dev flash here our trim wants to return zero. This is translate lba So let's see what it says This is it translating It'll only start trimming later on So partition two is the file system partition one is swap So you can see it reading stuff and then swapping it out Right now it's reading and trimming the swap It's probably now garbage collecting Okay, I reckon that's working Or at least working more So what is there to check in? dev Dev I think that's all So we've got the block dev stuff New Translate function Okay Let's commit that Okay, uh Now In terms of the boot process We need to add some files that were missing from the file system Remount is one That's the one that was preventing the file system from being mounted read write I don't know how I managed to avoid getting that in but let's just do that A set date Is here as well So that will allow that to be called Set date won't actually do anything in this Don't actually know what it does Okay, it prompts for the time I believe Given that the next thing we have to work on is Uh tty that's no bad thing So can we update the flash? Yes, we can Let's write that But we can't because this is still in use Now we write it Okay Right while that is running Let's take a look at the tty stuff Now Our tty is here. It's pretty simple All we do is that whenever a character shows up to be written We uh, we simply use the rom routine to write it out. We do not want to use the rom routine. We want to use We could use the rom routine But we need a interrupt driven tty so that we can receive characters For this to work. We're going to need interrupt handling So in fact, we might as well just work on the timer first And I have not done any look any research on how timers work Luckily, I've got the Arduino stuff here and there it does have some timer code So we should just be able to replicate some of this code and see what it does We seem to be using what I suspect to be a countdown timer as the actual Timer Timer one timer zero two timers And be a configurable timer and the system timer. I think timer zero is going to be our system timer We have the interrupt handler here Which If a user timer is set then it turns interrupts all the way off calls it and puts it back again That's not complicated and To set it up. All we do is we attach our ISR I assume it's configured somewhere or not okay, so Attaching happens via ETS ISR attach which sounds like a rom routine ETS ISR attach Yep, and we are That's not the same thing as an exception handler all right So ram one is the argument so that we get to pass in A arbitrary argument That's going to be your star user And then an exception frame Which would allow us to do preemption if you wanted to do preemption, which we don't So empty ISR And let's do ETS ISR attach zero I num timer ISR null Okay, that should be enough To write that wrote Does that build of course it doesn't because we're missing some stuff So this is our peripheral file and I know that the definitions here Do not necessarily match the The ones the Arduino uses so Let's see is don't think there's anything Hey, it's a random number generator Yeah, I don't think this has got anything we terribly care about So what we're going to have to do is go over here to our rom routine and Copy some of this in We also want ISR attach which is defined here And int handler is Here that's nothing exciting So I think that is that's wrong. So I think that's all we need No, it's not Yes, our interrupt handler is not does not Take a null it takes a struct the exception frame And we now need to add the routine address So with luck, this will actually call our interrupt handler. We won't be able to tell it's done anything But everything is running Okay, so let's now do something incredibly stupid Which is Which is It's called IRQ flags to disable interrupts printer character Turn the Mac on again the This architecture has nested Interrupt priorities that was faster. That was lots faster interesting It's got nested Interrupt priorities So while the timer ISR is running other interrupts can be executed So we use this to turn them all off completely because We don't care about any of that Okay, now let's see what happens Hopefully lots of cues no cues this suggests that the this Is not being executed So do we actually have to configure The comparator and timer not interrupt is six Yeah Timer not read timer not write Do we need to Enable it? Yes, we do So that calls ETS into enable Which is here So we want to There's mask and unmask Here So here in main after enabling the interrupt we want to We want to unmask it Like that. We also want our addresses Which are here ETS ISR unmask And we want mask two which is at f9 8 k cues Now the system should actually be running. It's going to be spam solid printing cues But this should all be happening sometime in the background I don't know how to configure Hang on. It says be a bit cleverer. Shall we so that should slow down the rate of cue production I want to see if the system is continuing to run even though we're taking interrupt So I want the cues to be slow enough I don't think that's working Okay, all that's doing Is printing cues nothing else I wonder if we actually need to clear the Interrupt so here is the interrupt handler all this does is Yes at the minimum this does nothing If time and all user cb is not set then the interrupt handler is completely empty so So interesting It may be the k put char is doing bad things. So let's just turn that off We don't need the that anymore which we know that the interrupt handler is being taken And nothing happens All right Let's leave it masked and it runs Let's try unmasking it but not attaching it So this will be taking the default interrupt handler in the ROM and it doesn't run Okay, I think we have to configure this somehow I think what's happening is that The timer is not being set And as a result the system spending all of its time running the timer And I was expecting to see calls to this somewhere, but Well when in doubt let's read the documentation This is a It's not a comparator So this is Writing to special register compare zero Cycle count compare registers That's not very helpful Timer interrupt maybe yes Okay, what does this do? Period interrupts from 32 bit counter 32 bit comparators Seek out increments in every processor clock cycle Timer interrupts are cleared by writing see compare So I think We need to Read the c count special register Add on a constant and then write to See compare for the next timer Okay well has them By the time you'll read special register c count equals address c count To few operand. Oh Okay, so this would read c count Then we want to write back Write to special register c compare zero a value Which is c count plus A million and do an e-sync whatever an e-sync does one e-six is a float Writing is how the long way is an integer Okay, so this should trigger one every million cycles. It's running now Okay, so now let's do our Put char queue There you go Interrupts are running Uh that has stopped because we're doing garbage collection. I believe Should come back to life in a bit. I hope Or it's just crashed and hung I think it's crashed and hung fantastic, um I know I might know I probably don't know We're doing all our flash reads and writes with the With interrupts turned off And this is a cycle counter I wonder if what's happening Is that when we are touching the flash with the interrupts off the c count here Is incrementing up past c compare the timer is not firing And then when Interrupts turned back on again We have to wait for the clock timer To cycle all the way around our 32 bit counter Before it fires again It doesn't really explain why nothing else is happening It did just seem to hang And then crash Double exception in rom code Not very helpful Let's try it without the kput char Trying to write to the serial port from inside an interrupt handler is a really stupid idea But this will tell us If this runs normally, then this is what the problem was I have to say Okay, that that is running better So i'm going to put this down to Uh I'm going to put that crash down to running kput jav From inside the interrupt handler All sorts of terrible things could have been happening It might even have been turning interrupts back on again Okay, so we have a working interrupt handler We can adjust the period by changing this value here So we want to call Now there's our code We want to call Timer interrupt And in fact, this is not really the right place to initialize both these things This now i remember this should be happening I don't like that. I think that should have reached the prompt by now This should be happening in device init Which should be in here Where is device init called? Right device init is called from the physics core. So we should do that Yeah, you see here. We're initializing the flash So this Should go Here This should go here And all of this Should go Here I think that's not working Let's try that with interrupt mask shall we and set up our Okay, this is our stopwatch So it's loading init It should start printing messages soon. It doesn't seem much better to be honest We have just written back the file system. There we go Right, so that's about 30 seconds so Problem is the timing is so variable due to garbage collection that i'm not sure this test is valid So we need to Well, this is a cycle counter. So we need to know How fast the cpu is running which we have in here So i'm going to have to switch these things out to other code Put these in globals.h as hash defines They are just advisory That's been way more than 30 seconds Yep, that clock doesn't have a second hand very useful Okay, so that's a minute and a half and we haven't hit the prompt yet So uh somewhere in here we have Ticks per sec, which is the clock speed Which is defined It's used in start dot c to configure Yeah So in order to configure this we want so It's a cycle counter therefore it's A clock cpu megahertz per second So per second per tick it is this many So it will be this And on every Interrupt we want to call timer interrupt Very uncomplicated. However, this isn't working Let's stop some tracing in So we want to read page p The right page p And let's try this this wants to be cpu clock And that's no longer a thing Okay, lots of block reads It is doing file systemy things That looks like garbage collection to be honest When it's it goes long spews of read and writes with similar numbers I don't know it could be anything The numbers do look kind of weird So one thing I am relying on with this Is ISR attached to To have the cshim in place in order to call timer functions Well interrupt handlers like it's got we have to save all the registers and stuff like that So I'm hoping that's happening I didn't see anything in the Arduino code to say it wasn't So here is our interrupt handler. It's just a normal function This is an annotation to tell it not to put it in the flash Because with the Arduino code it wants to be able to take interrupts while Doing manual flash operations, but we don't care about that Or at least we don't care about that yet It is doing a lot of Stuff Okay, let's turn that off and let's do some slightly Let's do that instead shall we as we'll give us a higher level view of what's going on Or it won't Did I break something? I don't think I broke anything Have we corrupted the file system? Is my trim code broken? I think we've corrupted the file system somehow intriguing Very intriguing Okay, well I don't think there's anything wrong with this code This is the translation code Here is a octal We've fetched the block number It's a long it's 32 bits in this platform. We translate it and we call trim It's not really anything to it Block dev needs to be set up correctly, but we've done that That happens here From the miner Well, let's write that flash back again And give it another try If it works, we will see what happens I'm still unhappy about the behavior with the clock Timer interrupt is not complicated It's in process dot c It increments the the per process timers that indicate That tell the system how long the process has been running for We're just looking at this in swap Are we actually So most of our swap code runs with interrupt on Because we don't have to worry about preemption This is the code that does preemption if it's there Which is not for us So in swap happens here swap in So this is the simple swap routine so It does appear to be Running swap in within swap not set. Is that correct? I'm not sure that's correct. So let's stick some tracing In our own swapper Like this and the flash is finished. So let's run it Okay, so we've got system calls Okay, swap out has happened in swap. He seems to be zero a lot of the time I suppose if the u-data block gets modified during swap going out then and we get a prompt really quickly Interesting Very interesting. I wonder if what we were seeing was just A garbage collection time But I think I want to Inquire about that You can see the fork is pretty slow. So this is a swap in as we trim lots of stuff Long pause probably caused by swap out I mean there's lots of space for it now This copy routine seems to be called During garbage collections a lot. So Let's do that. So lots of copies Honestly, I am thinking that the flash is just not worth it I think it's too slow and too problematic Swapping to sd card is fine as they seem to be way faster So I'm annoyed that I never got the sd card code to work. That's better Yes, it's the trailing colon in the mode line that makes a difference Um, I don't like the way that's just sort of stopped It does seem to be that the it's the clock code that makes the difference So this is the Timer code So we read C count What is that static? I think that's just yeah, that's definitely a garbage collection So here's a disassembly of our isr. We're reading c count We're loading the pre-computed constant from here. We're writing it back to c compare zero esync Call timer interrupt Turn interrupts back on I think return and that stopped again I hope this isn't a recurrent of That crash we were seeing I would be expecting Uh See if this was going back to user code, I'd expect to be seeing a message to say so Is it actually finishing a swap? I think that what I need to do is to sit down and try and make the sd card stuff work somehow Uh, my board actually has a little screen on it, which we hooked up via I think it would be hooked up via spi But I think it may be happening in another set of pins. It's very poorly documented So it's Possible it's a board problem I suggest order another one without the screen on it. I mean, it's not like they're expensive Um Because I think trying to swap to the internal flash is just going to be a recipe for disaster The interesting thing is the while it's talking to the flash I do see the little blue light on the top of the device flicker But that blue blue light shouldn't be attached to any of these gpi opens So I don't know why it's doing that I was hoping today that I would get as far as trying to make the tcy work That will require a bit of research and probably stealing more code from the Arduino source base But the main thing it needs is the ability to reliably get to a program that tries to use the tcy And I don't think it's going to do that So I think I'm going to need to spend some serious time on the sd card Yeah, this is Stopped again Blue light is not flickering. Oh, there we go See this looks like file system stuff. Do you see all the random? No, no, sorry. That's more garbage collection file system stuff if it was modifying Uh, well, if it was deleting files, we would see Random trim requests. These are consecutive. So this is a garbage collection Okay, so that's swapped in a process, but then it's just stopped It's not returned to Uh It's not returned to user mode because we would be seeing the syscall exit trace So it's produced this message, but it's not proceeding further Interesting Swapper happens from switching Which is in tricks.s Switching is here Here is where it calls swapper So this is when it's rescheduling the process This does slightly look like a recurrence of our old problem It could be That we've run out of stack again because now of course We've got the Now, of course, we've got the interrupt handler which could randomly start allocating stack at any moment and If that overflows it will randomly scribble over bits of kernel workspace I think this architecture uses lots of stack So it's there we go login prompt. I think it just ran out of stack We've got way too many of these weird little buffers and stacks The ublock is now one and a half k the swap stack is now one and a half k Uh, this is all going to add up Where have we got to in terms of memory usage? We've got everything between heap start and c o o so that is Under a kilobyte free We do have a big chunk of memory above the The ROM workspace Where the boot stack used to go which we're not using So we may be able to Reclaim some of that that would be nice But ideally I would like to be able to I Have as many buffers as possible The other thing which could be wrong Is this stuff if I have accidentally Configured something in some very slow mode um What does this show up? references to The sdk It does always seem to be setting this wb trap null So I think that could be right Okay, let's turn this off Turn this off and we want to Lose that and lose that um our Clock is running. I can fix that for a start. This is a A Generic pointer and let's see what this does. Yeah, um So we've got This segment which is ROM workspace Why can't it oh, we can't create var run new temp because we never created the Var run that's why but it is Creating the mtap file. There we go The problem is that the actual usage of this workspace is not very well understood So I have to be careful about how much Of it I actually use in case I write on top of something else But it's well known that there is at least several k's worth of stack So we could trivially use that as the swapper stack for example That would save, you know, one and a half k from the kernel workspace, which is enough for three whole buffers Okay, I'm going to call this done Uh done done some major fixing. We've done some investigation Uh the timer works, although it's not obvious that it has worked Um So we're in a good position to try and get the tty stuff working But I think that I will have to go and investigate how the sd card works And order another esp8266 board Fun I hope you've enjoyed this video and as usual, please let me know what you think in the comments