BASIC Interpreter Hidden In ESP32 Silicon

We’ve been keeping up with the ongoing software developed for the ESP32 WiFi chip, and that means a lot of flashing, hooking up random wires, and rebooting. Along the way, we stumbled on an Easter egg: the ESP32 processor has a built-in BASIC interpreter to fall back on.

That’s a cool little hack to find, but we couldn’t find some crucial functions that would have made it a lot more useful. Still, it’s great fun to play around in real-time with the chip. And you’ll absolutely get an LED blinking faster in ESP32 BASIC than you will on an Arduino!

Getting to BASIC

At first, we thought that the key to getting into the BASIC shell was simply blocking the onboard flash program ROM, because we discovered it by connecting to GPIO 12, labeled as the MOSI pin for “HSPI”. But a little verification with ‘scope and continuity tester verifies that no data is going across this pin at bootup, and the SPI lines for the flash aren’t even broken out from the module. It looks like whatever boot ROM the ESP32 has inside it is testing for a high voltage on GPIO 12. Don’t ask us.

Anyway, the short version of the story is: pull GPIO 12 high and hit reset. Connect to the ESP32 over serial, and hit enter to stop it from continually rebooting. Falling back to built-in command interpreter. Woot. Set your terminal program up to send only a linefeed at the end of each line and you’re off! Don’t have an ESP32 on hand to try this? I recorded a screencast of my adventures so at least you can join me for the ride.

Blinking an LED, BASIC Style

What’s the first thing you do when faced with a command prompt? Type “help”, right? Well, that works. The ESP32 comes back with “A very Basic ROM console. Available commands/functions:” and then lists out everything you need to get started. Now, our BASIC is a bit rusty, dating from the Tandy Color Computer days, but we quickly whipped up a few demos and frankly, we had a lot of fun.

ESP32 ROM Basic (c) 2016 Espressif Shanghai
Derived from TinyBasic Plus by Mike Field and Scott Lawrence

Espressif was nice enough to add iodir and ioset commands to the BASIC, and they let you set the data direction of any pin (input or output) and either read or write to it. That means blinking LEDs is just a few words away.

> list

5 IODIR 32,1
10 FOR I = 1 TO 10
20 PRINT "Hello Hackaday!"
30 GOSUB 100
50 END
110 IOSET 32,1
120 DELAY 200
130 IOSET 32,0
140 DELAY 100

> run
Hello Hackaday!

And there was looping and blinking. It’s easier than getting started with Arduino!

Mysteries and Missing

But that’s also about as far as we got. There is a print command, and an input command, but we couldn’t figure out how to handle strings, so we never got further than

10 PRINT "Enter your favorite number"
30 IF F = 42 GOTO 100
40 PRINT "Really? I prefer"
50 PRINT F+1
60 END
100 PRINT "That’s my favorite number too."
110 END

There’s an rseed command, and the help lists a rnd but it looks like a syntax error when we try to use it in a function. ioget, which should read the logic voltage on a pin, doesn’t seem to work either. Going down the list, we were bummed out to find that many of the functions that we’d like to teach a young child programming just weren’t there.

BASIC Without Peek and Poke?

We couldn’t get peek and poke working for the life of us. It appears that they weren’t implemented, which is a shame because those two commands are practically the best feature of BASIC for exploring around the insides of a new chip in real-time. While you don’t often have cause for twiddling individual bits inside your desktop computer, interactively flipping those little silicon switches is a fantastic tool when dealing with a microcontroller with memory-mapped hardware peripherals.

Peek and poke are the raison d’être for BASIC. Without them, it’s just a toy language.

Late Edit: [Sprite_tm] wrote in on the BASIC. I had it all wrong. Peek and poke work! I just didn’t guess the syntax correctly. Indeed, [Sprite_tm] confirms that they did make use of them during the initial chip check-out phase. How cool is that?

The following code enables GPIO 4 as output, turns it on and then off again, and then prints out the value of the GPIOs inputs in hex. We toggled input voltage on another GPIO and watched the bits bobble. The rest is a simple matter of software.

5 POKE &H3FF44020, 16
10 POKE &H3FF44004, 16
20 DELAY 200
20 POKE &H3FF44004, 0
30 DELAY 200
40 PHEX PEEK(&H3FF4403C)
50 GOTO 10

For what any of these numbers mean, see the memory map in the technical docs (PDF). Have fun!

These Are Not The Droids…

Anyway, the big news is that the Espressif team has gotten most of the Arduino core up and working, and we have a full review coming out soon. They’re also continuing to work like crazy on development of the C libraries that real programmers will use to run these chips, so it’s probably not a top priority for them to implement peek inside an Easter egg. But we had a fun afternoon dredging up BASIC skills.

115 thoughts on “BASIC Interpreter Hidden In ESP32 Silicon

  1. “Espressif team has gotten most of the Arduino core up and working, and we have a full review coming out soon. They’re also continuing to work like crazy on development of the C libraries that real programmers will use to run these chips”. Real programmers hehehe.
    Can’t wait to receive my esp32

  2. I never got around to working on “Input” when I was last working on it, so that doesn’t quite work. Also, i think rnd needs parens since it’s a function eg. 10 A = rnd() When I was working on this (for Arduino platforms) Peek and Poke were… interesting since you have EEProm, Program ROM and Program RAM, all separate… so what do Peek and Poke do? (I should note that I had no involvement that I know of in the port to this platform… but I’m super happy to see it in there! :D

    1. Thanks for the added insight Scott! And thanks for all your work on Tiny Basic Plus which made this EasterEgg possible. Sorry I cropped your name out of the screen grab above, but you are clearly cited on the “about” readout from the ESP32 (you’re immortalized in silicon now — the next best thing to carbonite).

    2. “What should peek and poke do?”

      For microcontrollers? Poke into the memory-mapped registers that control the hardware peripherals. With peek and poke and a good datasheet, you could do nearly anything (the hard way).

      EEPROM and flash/program ROM are much less interesting — and maybe should stay unwriteable. Program RAM is fun for diagnostics (and creating random crashes) so that’d be cool too, if you’re really taking orders. :)

      1. Well virtualisation of a processor is it having an abstraction layer, so it would be syntactically correct to refer to programmers on higher high level and interpreted languages as virtual programmers. I guess even more so if the language has it’s own virtual machine, like java.

  3. (not sure if my first reply went through or not)

    I added the “plus” stuff to TinyBasic, but I had no involvement with the ESP32 port of it, so i can’t speak to the specifics of that platform. I need to get one for myself though. :D

    When I was working on TinyBasic Plus, I didn’t finish “Input”. It wasn’t working when I started with the code and I never had a chance to get in to figure out why.

    iirc, rnd is a function so you need to do something like 10 A=rnd()

    And Peek and Poke are interesting. On the Arduino, there are 3 different chunks of memory, so it was unclear which it should be used for; EEProm, program ROM, and RAM. I honestly can’t remember the state of it when I finished. I did let you save and load from EEProm or SD card though… so i treated EE like it was mass storage, and for storing autostart settings.

    On the AVR platform, the amount of RAM was too restrictive to do anything useful once you added any support libraries (like SD+Fat etc) that would make it a more useful learning machine computer, so i wandered away and created a even tighter BASIC-derived language. (BLuB)… both are all on Github. I hope to see the changes for ESP32 on there at some point (if they’re not already) and see improvements as per the review above done by someone with a lot more time than I have… :D

    1. I wonder if the BASIC on this gizmo can control a large number of addressable RGB LEDs and poll a big switch matrix, with the ability to work with 1 to all switches closed?

      Does TinyBasic Plus do arrays?

        1. You could do it, but it would all be POKEs of op-codes into RAM then save that as an executable. Either just poke in the binary image, or write a meta-compiler in BASIC that reads Forth source for Forth and builds a Forth compiler.

    1. This x10! Forth is _actually_ the perfect language for getting in and exploring a system at a low level. Peek and poke are for suckers when you have @ and !.

      But Forth is insane, and only a few people know it / use it. So they went with BASIC. I can respect that.

      Seriously on Forth: It’s a tricky language to implement on ESP8266/ESP32 because you really want to retain interoperability with the Espressif C libs for things like WiFi and etc. So you’re basically stuck writing a C Forth rather than an assmebly Forth, with all of the overhead that comes with C function calling. See punyforth for instance.

      Mecrisp-Stellaris Forth on the ESPs, with library functionality, would rock. I don’t know how to do it.

    1. You should check out the ARMs used on the BeagleBone boards. They’re TI Sitara ARM Cortex A8s with a pair of “PRU”s. These PRUs are 200 MIPS RISC processors for I/O. You can toggle a pin in software to make a 100MHz square wave. :-)

  4. What’s wrong with BASIC? Its in English. Its obvious how it works. Simple. I’ve used it for over 20 years both professionally and as a hobby and I’ve never needed to peek or poke. Or use Github.

    1. Nothing wrong with modern BASIC like freeBASIC or VisualBASIC. and the like. But this old school BASIC is really outdated. But if one want an interactive language Python has all the modern facilities and it is not harder to learn than old school BASIC or newer BASIC. There is microPython for embedded platform.

      1. I figured out how to patch the millennium bug in PC BIOSes in one line of QBASIC in 1996, and was bitterly, bitterly disappointed at how easy it turned out to be (And how few machines really really had it) because I wanted to make my fortune. :-D

        Anyway, have my doubts you could do that in Python…. but it was mostly an illustration of “Look, you can do real world important stuff in BASIC.”

      2. microPython is about 20 times the size (80KB) of a TinyBasic implementation (4KB). Not something you can easily hide as an easter egg in a microcontroller.

        TinyBASIC is probably only meant to be a toy. But it is enough to see that the system is functioning. It’s difficult to extend without modifying the original implementation and flashing it.
        FORTH is arguably as expressive as microPython because it is easily extended, and would fit into a similar foot print. But FORTH doesn’t have the wide appeal as BASIC or Python does.

    2. I started with QBasic, but now I work with C, C++, or C# depending on the project. I’ve got that QB nostalgia, but after trying out QB64 I honestly can’t remember why I loved it so much.

      The syntax is inefficient to write and read. What can be done in one line of C takes a whole subroutine in Basic. Most Basic implementations I’ve worked with (QBasic, PBasic, and TRS-80 Basic) have no real debugger so when something goes wrong you have to manually walk through hundreds of lines of code to realize you typed a < instead of <=.

      Then there's the complete and total lack of OOP. Everything has to be written as a single static algorithm, so most AI models are completely out. You don't have classes or even namespaces, so there's no modular design to anything you write, so everything has to be written from scratch by you unless you start with prewritten code and write around it.

      Shoot, some implementations don't even have arrays, much less linked lists, search trees, or other dynamic data structures. IIRC, TRS-80 Basic didn't even have variable scopes (or was that PBasic?) so everything was global. This got me into the habit of declaring all variables at the top of the program so I could keep track of what names were already in use.

      Then there's the performance issue. You probably could implement FFT in Basic for a 16-bit MCU, but now you're dealing with the massive overhead of the interpreter.

      Someone using Basic professionally in 2016 is pretty much unheard of (unless you count VB, but VB6 is garbage and VB.NET is barely Basic anymore). I'd love to know what you use it for, and how you deal with the absence of such basic tools.

      1. I use BASIC to test stepper motors attached to the LPT, with TIP122s running the motor coils. Running DOS 5 and QB4.5 none the less. I also used it as a control system for various experiments in the past, less today with Chinese Arduinos so inexpensive. With GOTOs I got about 500 000 samples per second when doing reads on LPT. When I tried more modern programming approach (loops) the sample rate dropped to less than 100 000. Use subroutines and keep your programs short: less then 300 lines is often enough even for the largest reasonable tasks. I don’t do OOP anyway. It is a solution looking for a problem.

  5. Hehe, seems the little easter egg is public now…

    The interpreter actually is triggered in the failure routine for the flash load routines, so if you manage to make the ESP32 read no data or crap from flash, you will get it. Normally, without any input, the watchdog resets the chip so it won’t go sitting around looking pretty in the BASIC interpreter when you do not need it.

    I think ‘input’ does not do what you want because if I recall correctly, this BASIC does not do strings. It should work for integers, though. Also, ‘peek’ and ‘poke’ do work, and peek and poke in the address range of the ESP32 memory. So it’s pretty powerful: you can reach ROM, RAM as well as all peripherals with it.

    As said before, rnd and ioget are functions, so use them as ‘print rnd()’ and ‘print ioget()’..The other functions are peek, abs and usr. Especially usr is fun: it can call machine language routines. ioget() actually works, but it requires the pin you want to inspect as an argument, eg ‘print ioget(0)’,

    Part of the reason for writing that (aside from the fact that, hey, ROM BASIC!) was that it may make testing easier. That’s why eg we also implemented hex numbers (&habc) and binary numbers (&h1011). In the end, I don’t think we used the interpreter past initial chip bringup, but it’s there.

    @bleullama and Mike Field: Thanks for making this interpreter. Modifying and expanding this was not something we allocated a lot of time for (I think I got it done in a day or two), your code was small enough to fit into the space we had left in the ROM and readable enough to expand without too much hassle.

    1. @Sprite_tm – I’m Mike Field, who I originally ported/wrote tinyBasic from 68000 assembly to the C on the AVR/ Arduio a few years back I thought it might be good for just waggling pins and so on while testing on a breadboard or board bring-up, as when working on a product it is often helpful to to test before you had a “real” design to flash into it.

      I never thought anybody would run with it as much as Scott (@bleullama) has, and now really amazed that you have. For it to be in the ROM of the next-gen ESP module is pretty neat. I’m dead chuffed.

      I’ve got quite a few of the older but still great ESP8266 modules for various projects, so I can’t wait for ESP32 to start shipping, allowing me to play with one, and have a warm fuzzy glow knowing that I helped just a teeny-tiny bit in the development of it.

      1. …and I’m Gordon Brandly, who only just found this now in 2022 thanks to a sharp-eyed colleague (thanks Dmitri!). I wrote the 68000 version of Tiny BASIC that Mike ported, and my own work was a port / translation of the original Tiny BASIC published in Dr. Dobb’s Journal long, long ago. I was told about your porting work back in 2014, Mike, and I always meant to have a closer look at it but, well, life kinda sorta got in the way… :-)

        I’m finally checking out this interesting ESP32 after hearing about it for years, and I was amazed to learn about this page. I’m only chiming in here to give a bit more of this Basic’s backstory, and to marvel at how just far and wide Tiny BASIC has spread since its origins in the 1970s!

    2. OK, I got rnd(10) and ioget(0) working. That’s great. I was missing the parentheses, which don’t seem to be needed elsewhere? (I don’t speak this language.)

      And holy crap! Peek does work, but you have to print or assign it to a variable. (I don’t speak this language.) I was trying to have it work without assignment and it fails.

      peek(&h3ff4403c) is a syntax error, but a = peek(&h3ff4403c) works. That’s rad! Off I go to blink LEDs the hard way.

      1. > (I don’t speak this language.)

        Kids these days!

        Of course PEEK just by itself is an error, what are you expecting it to do with the peek? If you’re not PRINTing it or putting it in a variable, what’s it for? Or to put it another way, PEEK is a function! And BASIC is a bit different from C, in that it’s a halfway house between speaking English and going to a full stack-oriented brain.

      1. Desert island aside, it wouldn’t be difficult, Brainfuck and assembler aren’t too far apart anyway, BF is mostly asm with some bits added just to make it perverse. It’s a simple stack machine, would probably be one of the easiest CPUs you could build.

  6. So if it is “embedded in silicone” – which would make it un-eraseable – isn’t this a huge security risk?
    Or is it just in the “empty” chip on delivery and will be erased once you program it?

    1. Why is it a security risk? It’s not like you’re making something execute code that didn’t before (the entire *point* of a microcontroller is to execute code!) and if you’re in a position to activate basic then you’ve already got full control of the hardware.

      It’s not like there’s internal flash you can read out via this interpreter anyway.

      1. What’s under threat, and needs the security, is the proprietary code a company would program into it. Say Company X incorporate an ESP32 into their expensive widget. It uses some clever code, that Company X spent a lot of money on researching and developing.

        There’s no way of a competitor reading out this code, because microcontrollers have security bits that prevent it. Specifically for that reason, code security. X aren’t worried about people re-programming the micro, they’re worried about their code being stolen.

        But with this BASIC thing, Company Y’s spies could get hold of a Widget, boot into BASIC, and use PEEK to examine and dump all the memory to the serial output. Then they have the proprietary software, at least the object code, which could be good enough to clone the product even though they don’t understand it.

        Of course there’s internal flash, where else does your code go? Sprite_tm informs us that PEEK can read all the chip’s memory.

        Fortunately Igrr’s pointed out that, apparently, this isn’t possible because the security bit also affects the BASIC. Good job, or else this would be a bit of a hole. I suppose whoever’s in charge of the security bit at Espressif will have had to check out all proposed additions to the ROM, and checked this one.

        1. Most other microcontrollers give you no physical access to the flash. The only way to access it (without decapping) is to get the microcontroller to tell you what’s in it. An interpreter in that instance *may* be a security risk.

          Except the ESP chips are NOT like other most other microcontrollers. The flash containing the firmware is a separate jellybean chip. It’s not encrypted. You don’t need to rely on the ESP to read it out; just hook another serial flash reader to the chip and go.

          1. pelrun: Well, the whole point is that with the ESP32, the program in external flash chip *can* be encrypted, with keys stored in ESP32 OTP memory (and can be made accessible only by on-the-fly-decryption unit, not by an application).

  7. BASIC in the ESP32 ROM, now that is an interesting road to travel. As mentioned before, it brings back memories of the Tandy and Li-Chen Wang’s Tiny Basic. I still use BASIC in one form or another, including TechBasic for creating BLE and WiFi based programs directly on my iPad.

    Now, is this an Ester Egg, oversight, or a Trojan Horse? We are all aware of back doors to software, is this a hardware back door? For the first version of this chip, I don’t think anyone would be mass producing a commercial product anyway, but this default boot option should not appear in the next version. Anyone can “peek” and “poke” and decompile your code in flash. It might not matter to a hobbyist, but if you are putting some proprietary firmware in it, it has to be secure.

    The OTP bit for UART download does not remove access to ROM. I’m betting you could access that area of ROM and read out the memory through bit banged I2C, SPI or software UART.

    1. The trick is that this interpreter can not run from ROM directly. It can only run if copied into IRAM. Which you can not do if UART download mode is disabled and flash encryption/secure boot is enabled.

      1. Aye, the Basic interpreter in ROM is even scrambled so it won’t even render ROP gadgets. Add to that that the Basic interpreter itself also checks that fuse: I really did not want to turn a whimsical addition to turn into a security threat whatsoever.

        1. Glad to hear you took the necessary precautions. For you sake you’re lucky it came out this way. Now the public needs to be educated on this so that the don’t leave this feature open for someone to exploit in the field.

          I am really looking forward to testing out this BASIC feature. It does open up a whole new avenue for people to explore.

          It would be nice to see the next ESP32 with camera interface ROM support to make it a complete goto SoC. Great work so far, keep it up!

          1. Not sure what you mean by ROM support, but ESP32 does have camera interface, compatible with OV7xxx/5xxx and similar. This is achieved using an I2S peripheral in parallel input mode.

      2. That is good to hear. I don’t see any information about the camera interface in the TRM.

        When I mentioned ROM support I was thinking more along the lines of DMA with FIFO buffer accessible in ROM through an API or any other dedicated commands to reduce overhead (better facilitation of streaming video).

        Please direct me to where I can find out more about the camera interface with I2S.

  8. Tried this and now I can’t program the device – it reboots, appears to load the program but only does this on a reboot –
    rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
    configsip: 0, SPIWP:0xee
    mode:DIO, clock div:1
    entry 0x400806a8

  9. Wow! This post has a life of its own.

    Back in the mid 80s while working a contract job, I ported “Tiny Basic for the 6800” from Dr Dobbs to a 6809 and turned it into a 2 KB debug monitor in UVEPROM (the thing used to store programs before flash) to debug the product I was doing. I liked that Tiny Basic used BCD floating point, eliminating truncation/rounding errors for 5, etc. due to not having to translate from binary to decimal (since it was pure decimal). I also liked using variables to store debug addresses and being able to “call” (i.e. “gosub”) into my code to test fragments that I was having trouble with. I used “@” for indirection so that I didn’t need Peek/Poke. This first ran on a modified Radio Shack Coco2. I spent a small eternity doing that, but it was fun.

    So why did I do it? Development systems were way too expensive then and it’s what us dirt poor developers had to do to survive. Even before the DoD’s ARPANET transformed into “the Internet”. Of course, I cheated and used my day-job employer’s 6809 assembler I learned when it ran on a PDP11. When the assembler was ported to the “PC” later, I ran it on my 8 MHz NEC V20 powered PC with 640KB of RAM! Yes, I am Methuselah.

  10. searching the docs for “tinybasic” leads to only one hit, on a copyright credit page,…/esp32/COPYRIGHT.html…
    which links to this:
    which does not appear to match the documentation in the article. e.g. no IODIR IOGET or IOSET keywords are mentioned.
    Ah… it’s old. This DOES show up in the docs up to release 4.3…/api-guides/romconsole.html…
    but NOT in 4.4 or for the current release.
    Can anyone confirm that it’s available in the current releases?

    1. Yeah, the version in the ESP32 is a version that is modified; e.g. all the IO* commands are not in the upstream version. BASIC is in ROM, so it exists in current ESP32 chips, but some customers got an error which triggered the BASIC interpreter and stopped the ESP from rebooting, so I think all chips ship with the disable-BASIC-fuse blown, so I don’t think you can get into the interpreter anymore. Ah well, it was fun while it lasted.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.