FORTH on the PicoCalc

OK, I fixed my first problem. Turns out the USB-C cable on my windows machine cannot talk to the Picocalc, maybe its too long? It works fine with my other uControllers, but will not power on Picocalc. I tested the Picocalc USB-C is working by pugging it into my Linux machine with PicoMite mmBasic, and this eventually led me to the bad cable. I’m now loading the zeptoforth-picocalc-devel functions on the linux box per instructions. I now have zeptoforth displaying on the picocalc screen, and I can execute “words” and “.s”

I’m at the step the mentions zeptocom.js, but I have not found that yet. More in a bit…

Good to hear that you have the zeptoforth PicoCalc terminal emulator up and running!

About zeptocom.js, that is mentioned in README.md and is hosted online at zeptocom.js. It is a web terminal for talking to Forth on microcontroller boards and especially zeptoforth that can be used with any browser that supports the Web Serial API (read: non-mobile Chrome/Chromium-based browsers).

I found zeptocom.js and an got it to connect. But the only functions to have affect is “reboot”. I do not see any other activity via the the USB-C port. Is this intentional? That is, my only interaction with zeptoforth is via the picocalc screen and keyboard. Is there a way to load the code from .fs? I was thinking to copy the files to the sd card, but I would have to key in all the file support code by hand. Is this the intended method?

As I presume you have already activated the PicoCalc terminal emulator, you need to first enter serial::serial-console at the REPL shown on the PicoCalc with the PicoCalc’s keyboard. That will activate being able to enter and upload code via the USB-C port through a terminal emulator such as zeptocom.js. To return to the PicoCalc terminal emulator and use the PicoCalc normally, enter picocalc-term::term-console.

And yes, after you have entered serial::serial-console on your PicoCalc you can upload code from .fs files with zeptocom.js through clicking ‘Send File’ or selecting a working directory with ‘Set Working Directory’ followed by entering #include <path relative to the working directory> (if you do not click ‘Set Working Directory’ and anything you attempt to upload contains an #include you will be prompted for a working directory).

You can also load code from the SD card as well. Follow the directions in the latest version of USING_THE_PICOCALC.md for setting up the FAT32 support for the SD card (be aware that zeptoforth does not support long file names), remove the SD card from your PicoCalc and mount it on your computer, copy your files to it, insert it in your PicoCalc, reboot your PicoCalc, execute fat32-tools import, then execute included ( path-addr path-bytes – ) (e.g. s" /FOO/BAR/BAZ.FS" included) to load your source file. You can also list files with list-dir ( path-addr path-bytes – ) (e.g. s" /FOO" list-dir) — note that an empty string will list the current directory, which can be changed with change-dir ( path-addr path-bytes – ) (e.g. s" /FOO/BAR" change-dir). For more directions consult zeptoforth/docs/words/fat32_tools.md at master · tabemann/zeptoforth · GitHub.

So which branches are these various changes in? I’m guessing the ones around picocalc-term are in the picocalc-devel branch, but what about the latest SD / FAT32 fixes? Are all those SD / FAT32 merged into the master branch now, or…?

Hehe, aside, is next step to simultaneously host both picocalc-term and serial sessions on separate threads while also accessing SD / FAT32? (g) You know, a nice weekend project!

Joking aside, though, It does appear all the multithreaded/multiprocessing entities needed to do so are present already in mainline zeptoforth, is that correct?

Can you give a brief rundown (or is there docs on it) about how the word-interpreter currently deals with multiple “threads”? I’ll take labels of the “thread/scheduling lifecycle-relevant words” but obviously docs would be nicer. ;D Is there already enough of a thread context for multiple simultaneous preemptively-scheduled both editing and calling words? Or is it more limited? What about something like a “process” context tracking threads’ allocations of global hardware and resources? How does GC fit into all of it?

Thanks for any info you can provide!

Everything is up to date in the picocalc-devel branch, but for UF2 release binaries with the latest SD / FAT32 fixes just use the latest 1.13.2 release tarball (mind you this tarball does not contain anything PicoCalc-specific, so you will still need to pull the picocalc-calc branch).

As for supporting simultaneous picocalc-term and serial REPL sessions at once, that won’t happen any time soon due to the fundamental architecture of zeptoforth, which assumes one REPL at once, and only allows one global compilation context. (This is partially because there is no practical way for multiple tasks to compile to flash at once.)

However, multiple tasks can do console IO on different inputs and outputs at once. This is useful for debugging (e.g. user IO on picocalc-term in one task but debugging data simultaneously dumped to serial in another task).

Note that multitasking in zeptoforth is not like multitasking with multiple processes on Linux; rather tasks in zeptoforth are in between Linux threads and Linux processes, as they share a single addressing space like Linux threads, but like Linux processes each can have separate console IO redirection from one another and have separate blocks of memory to use independent of one another (aka ‘RAM dictionaries’) and like. Tasks inherit their console redirection from their parent task when they are initialized. Their RAM dictionaries’ sizes, along with their data and return stack sizes, are specified by the user when they are spawned as well.

There is no concept of ‘devices’ aside from the console and flash memory in the traditional Unix sense; rather all non-console, non-flash IO is explicitly managed by the user rather than being baked into the kernel. However, many IO interfaces (e.g. UART’s, SPI, I2C, PWM, ADC’s, PIO) have API’s built into the full and full_usb builds for them so the user does not have to write their own device drivers or allocate memory for them. Others, such as display drivers, FAT32 filesystems and their underlying devices, and networking interfaces are purely ‘user’ affairs as implemented (but FAT32 filesystems have a convenience concept of a ‘current filesystem’ and a ‘current directory’).

Also, the only multitasking synchronization/communication construct that is completely, inseparably ‘baked in’ is task notifications. All other constructs, such as locks, semaphores, queue channels, rendezvous channels, bidirectional channels, and byte streams are managed by the user, and do not exist in any kind of global namespace (aside from the addresses as which their memory is alloted).

By the way, tasks in zeptoforth are thoroughly documented in zeptoforth/docs/words/task.md at master · tabemann/zeptoforth · GitHub.

It should be noted that there is no garbage collection in zeptoforth (it is a Forth after all), unlike a language I created a while back (which is now on the backburner for Reasons) called zeptoscript, which is a high-level, dynamically-typed, garbage-collected layer on top of zeptoforth.

Opinion question for you all!

Currently zeptoforth generates BEL characters on errors but the zeptoforth PicoCalc terminal emulator ignores them. Should I make a sound driver for the purpose of making those BEL characters audible?

I ask because on some of the systems at my work that I VNC into BEL characters are generated not infrequently, and these really annoy me when they are turned into sound by TightVNC. I don’t hear zeptoforth’s BEL characters, OTOH, because the terminal emulator I use on my home machine is not configured to make them audible — rather it just makes the title of the window light up in the status bar under XFCE. I am wondering if having BEL’s be audible would annoy you guys here.

Note that BEL’s can be turned off with:

false bel-enabled !

so if you don’t want audible BEL’s you could turn them off permanently with:

compile-to-flash
: init-bel-disabled ( -- ) false bel-enabled ! ;
initializer init-bel-disabled
reboot

What are your guys thoughts on this?

Personally, I would turn such sounds off immediately… but I would actually really like to see the code that implements them because generating sound with ZF on the Picocalc is a big interest of mine and I’d like to see how that problem is approached by somebody who actually knows what they are doing XD.

As long as they can be disabled (and made permanent by initializer in flash), I’m fine with enabling them, but would also disable them first thing. I’ve got my terms set up like you, visual bells only.

That said, when unexpected needs arise, better to have but disabled, than not have at all.

My problem at the moment is that I have no real way of testing sound at all because I don’t have speakers set up to be hooked up to the board I’m testing with (I have speakers hooked up to a different board that I use to play Christmas tunes around Christmastime, but I don’t want to undo those), so I don’t want to start blindly implementing code I really can’t even tell whether it works or not. (I have done that for some things such as the keycode to escape sequence mapping though, but if that doesn’t work it just won’t work rather than potentially being very annoying.)

Just before I saw you post this I even thought of implementing visual bells in the form of flashing the screen, but I would leave them disabled by default because I can picture them being very annoying as well to someone who wouldn’t want them.

And that said, I have decided that when I do implement audible bells they will be opt-in — even though by default BEL characters are generated, by default BEL characters would be inaudible.

I don’t know why, but having someone say this in the middle of summer made me feel very seen.

An update from me - I don’t seem to be able to load the ipv4 stack after switching to a full install from a full_usb - it’s very late tonight so I’ll try to recreate the error tomorrow on a bare pico 2w to help narrow it down.

Also, my favourite and most useful word I’ve defined so far is : zedx disable-echo 2dup zed included enable-echo ; for a bit more of an interactive-feeling experience. I may well see if I can add a native save-exit-load option to zeptoed at some point!

I have now implemented (optional, opt-in) visual bells for anyone misguided enough to want their PicoCalc’s display to flash whenever they cause an uncaught exception to occur.

They can be enabled (they are disabled by default) by executing:

true picocalc-term::visual-bell-enabled!

If you are sick of them and want them off again you can execute:

false picocalc-term::visual-bell-enabled!

If you really are a misguided person and you want them permanently on you can execute:

compile-to-flash
: init-enable-visual-bell true picocalc-term::visual-bell-enabled! ;
initializer init-enable-visual-bell
reboot

Once my PicoCalc arrives I will implement audible bells, which will likewise be optional and opt-in.

Your zedx is definitely a neat concept, but I should note that echoing has a specific purpose ‐-- if there’s an error it tells you the line of code on which the error occurred, as that is where echoing will stop, whereas without echoing if there’s an error you have no idea where your error was.

Thanks for all the info, and I’ll definitely look into the doc you mentioned. I’m familiar with how at least a few different Forth impls handle threading, there are a lot of variations – I’ll just say yours straddling the thread/process line isn’t “unusual”.

It should be noted that there is no garbage collection in zeptoforth (it is a Forth after all), …

Actually Anton Ertl’s had an example GC system for Forth available for a long time. There’s nothing about Forth that precludes/discourages GC systems, gotta handle address-space / alloc fragmentation somehow.

http://www.complang.tuwien.ac.at/projects/forth.html

Personally, I think Forth’s nature lends itself more to address realms and sized, tagged pools, but understand that requires diligence many won’t expend. I’ve also seen really elegant RTGC (but alas, painfully complex) that was almost “dev cognitive-load-free”, but never felt any urge to rederive what it was doing. :person_shrugging: Needs must.

Thanks for all the info, and I’ll definitely look into the doc you mentioned. I’m familiar with how at least a few different Forth impls handle threading, there are a lot of variations – I’ll just say yours straddling the thread/process line isn’t “unusual”.

The key motivation for this design is to ensure that tasks can act separately without stomping all over one another. For instance, having each task have a separate RAM dictionary space allows each task to allot space without worrying about how other tasks are simultaneously allotting space. This enables each task to use RAM dictionary space as essentially a third stack (that grows upward). This also enables each word to separately use <# # #> rather freely without worrying about what other tasks are doing.

It should be noted that there is no garbage collection in zeptoforth (it is a Forth after all), …

Actually Anton Ertl’s had an example GC system for Forth available for a long time. There’s nothing about Forth that precludes/discourages GC systems, gotta handle address-space / alloc fragmentation somehow.

Forth Research at Institut für Computersprachen

The thing, though, is that GC is not well-suited to embedded systems at all, and zeptoforth was created specifically for microcontrollers. Yes, I did create zeptoscript on top of it, but zeptoscript was more of an experiment w.r.t. whether I could create a high-level Forthy language rather than something really meant for practical usage.

Personally, I think Forth’s nature lends itself more to address realms and sized, tagged pools, but understand that requires diligence many won’t expend. I’ve also seen really elegant RTGC (but alas, painfully complex) that was almost “dev cognitive-load-free”, but never felt any urge to rederive what it was doing. :person_shrugging: Needs must.

zeptoforth provides mechanisms for pools and heaps, but it is entirely up to the user to utilize these. There is no global heap, it should be noted ─ rather, tasks have to create heaps manually, and multiple heaps may exist side by side (for instance, this is the case when using zeptoed while the line editor is enabled ─ zeptoed creates a heap for its general storage while the line editor creates a small heap for storing its history). If the user wants multiple tasks to share a pool or heap, it is up to them to protect that pool or heap from tasks simultaneously allocating, resizing, or freeing memory.

If you add the word “small” before embedded, I’ll concede that’s often the case, but “small” and “embedded” are not synonymous. In medium and large embedded systems, “above the MMU line” if you will, GC’s are used, and again, in some cases are the only deterministically-long-term-reliable approach available.

It’s also important to recognize that “Garbage Collection systems” often hide in plain sight without ever being labeled such – for example, UNIX process resource mgmt is a fairly-classic albeit basic example of a mark/sweep garbage collection system. Once you set durable resource mgmt distributed across multiple entities as a goal, it’s actually difficult to solve certain classes of problems without instantiating some form of “classic GC system” (whether aware of it or not).

A significant portion of the time, the devs don’t even recognize they’ve implemented a classic type of GC system, usually due to how they’ve spread out the abstractions involved. Only when you step back and look at the types and relationships of abstractions present, do the GC systems “leap out” and become impossible to unsee. ;D

I note that when I speak of “embedded” systems I refer to microcontrollers, FPGA’s, direct implementation in hardware, and like rather than systems on chips with MMU’s. For instance, if it runs Linux with paged memory management it is not what I typically think of as “embedded”.

Of course, there are many MMU-ful Linux-based systems that run in embedded applications, and I would give that they are “large embedded”. But a common theme with these is that they often do not have the tight timing requirements imposed on many microcontroller and FPGA-based solutions, and indeed when large embedded systems do have tight timing requirements they often pair an SoC with a microcontroller or FPGA onto which they offload timing-critical functionality (or pair a (generally) larger core for non-realtime functionality with another core on the same die for realtime functionality).

As for GC, the matter with it is that one inevitably runs into non-deterministic pauses, which bedevil those who attempt to use MicroPython or like for anything really timing-dependent unless they are very careful to avoid allocations ─ and what is needed to avoid allocations often negates much of the point of using Python or like in the first place.

The reason why this is less of an issue in large embedded is precisely because large embedded is typically done with less in the way of tight timing requirements. There is nothing about large embedded that makes pauses go away, it is just that large embedded is commonly done for applications where they are less important, with timing-critical functionality being offloaded onto small embedded.

Support for sound on the PicoCalc has arrived!

The compilation instructions and USING_THE_PICOCALC.md have been updated. Now you have to load extra/rp_common/picocalc_sound.fs before loading extra/rp_common/picocalc_term_common.fs.

This support currently is in the form of supporting generating tones for periods of time. There is now the word picocalc-sound::play-tone-for-duration ( duration-in-ticks D: pitch-in-hz – ). The pitch is a S31.32 fixed-point value in Hz, which can be specified via the x,y notation, e.g. 1047,0 is C6. The duration is in ticks, which are 100 microsecond increments.

Currently the left and right speakers are controlled together; this is a side effect of the fact that PWM is used to generate tones, and they both fall on the same PWM slice and thus have the same top value and clock divider.

There is also a convenience word picocalc-sound::beep ( – ) which generates what is currently a 125 millisecond tone at 1397 Hz, i.e. F6. This may change if people have other preferences.

Accordingly, there is now an optional, opt-in audible bell. It can be enabled by executing:

true picocalc-term::audible-bell-enabled!

If you decide this is too annoying, you can disable it again by executing:

false picocalc-term::audible-bell-enabled!

It can be permanently enabled if you decide you really like your PicoCalc beeping at you with:

compile-to-flash
: do-enable-audible-bell true picocalc-term::audible-bell-enabled! ;
initializer do-enable-audible-bell
reboot

Edit (opinion question):

I am not entirely happy with my choice of 1397 Hz, i.e. F6, for the beep sound. However, I could not find any other notes I really liked better. Does anyone have any suggestions of a pitch to use?

Another edit:

I have switched to 1245 Hz, i.e. D6#, which I do like better than 1397 Hz. If anyone has any suggestions, though, throw them out!

Yet another update:

I have removed flashing the LED on the Pico, Pico 2, and Pimoroni Pico Plus 2 due to the conflict between the LED GPIO on those and the CS GPIO for the CYW43439 on the Pico W, Pico 2 W, and Pico Plus 2 W, which is also GPIO 25.

1 Like

Where can I find information non the construct ::
I searched the documentation but did not come across it in the DOC directory. I see it use in the MD files as in serial::serial-console and picocalc-term::term-console, but I don’t seem to find explanation. Forgive me if this is already obvious, forth words only had 3 characters when I learned it. Any web reference would be fine.