Quantcast
Channel: Articles on JeeLabs
Viewing all 296 articles
Browse latest View live

Making an LED blink (and fade)

$
0
0

Just like C-based code benefits from a runtime library with utility functions, Forth can benefit from a set of pre-defined words to talk to the hardware peripherals built-into every F103 µC.

This utility code has been written from scratch for Mecrisp Forth, the STM32F1 series of µC’s and some of it also for other STM32 variants. It’s still experimental, but working out nicely. All the code lives in the embello area on GitHub, in a folder called explore/1608-forth/flib/:

$ tree -d flib/
flib/
├── any
├── fsmc
├── i2c
├── mecrisp
├── pkg
├── spi
├── stm32f1
├── stm32f4
├── stm32f7
└── stm32l0

To load some key files into flash, the easiest is to launch Folie from the 1608-forth/g6s/ directory (“Generic 64-pin Serial”), which has a number of “standard” source files to include selected titbits from the above library collection:

  • launch Folie from embello/explore/1608-forth/g6s/
  • enter: !s always.fs
  • enter: !s board.fs
  • enter: !s core.fs

Here is a transcript of the entire process:

$ folie
Folie v2.7-1-g94cba5e
Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem3430DC31
? 2
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodem3430DC31]
!s always.fs
1> always.fs 3: Finished. Reset ecrisp-Stellaris RA 2.3.1 for STM32F103 by Matthias Koch
1> always.fs 11: ( always end: ) 00005060  ok.
1> always.fs 12: Redefine eraseflash.  ok.
!s board.fs
1> board.fs 3: eraseflash

Finished. Reset ecrisp-Stellaris RA 2.3.1 for STM32F103 by Matthias Koch
1> board.fs 6: ( board start: ) 00005800  ok.
7> hal.fs 14: Redefine bit.  ok.
1> board.fs 27: Redefine init.  ok.
1> board.fs 36: ( board end, size: ) 00008104 10500  ok.
!s core.fs
1> core.fs 3: <<<board>>>

Finished. Reset ecrisp-Stellaris RA 2.3.1 for STM32F103 by Matthias Koch
64 KB <g6s> 323F3565 ram/flash: 19340 30720 free ok.
1> core.fs 5: ( core start: ) 00008800  ok.
1> core.fs 12: ( core end, size: ) 0000A7E4 8164  ok.

If you now reset the board, you’ll get the Mecrisp welcome message, as well as some extra information generated by the init definition in board.fs:

!reset
Mecrisp-Stellaris RA 2.3.1 for STM32F103 by Matthias Koch
64 KB <g6s> 323F3565 ram/flash: 17688 20480 free ok.

Let’s examine this information in detail:

The always.fs/board.fs/core.fs files follow a convention outlined a while ago on the weblog, in an article about application structure. In short:

  • always.fs defines words which are considered essential for basic use
  • board.fs includes the main board- and µC-specific definitions
  • core.fs loads all sorts of goodies and can be adjusted as needed

After these have been loaded, they will remain in flash until replaced by newer versions. The Mecrisp core contains some 350 words, the above three “sends” will add another 300 or so. A number of these are just there to factor out common code in the rest of the files. There’s no need to go through all of them, you’ll see a few useful I/O and delay words below.

All words are defined as entries in the Forth “dictionary”, which is in fact simply a linear list. There are a few words which will truncate this list and remove all definitions after them:

  • eraseflash” deletes all words defined afteralways.fs
  • <<<board>>>” deletes all words defined afterboard.fs
  • <<<core>>>” deletes all words defined aftercore.fs
  • forgetram” is a bit different, it clears all RAM-based definitions

The first three of these words are “cornerstones”, i.e. they only exist to mark a position in the dictionary. You can also define your own cornerstones to add additional markers.

Note that after a reset, new definitions will be defined in RAM. They can be cleared by calling forgetram or reset, or by a hard reset of the µC. This makes it very convenient to try out stuff - if anything goes wrong, just reset and you’re back in a well-defined state.

Speaking of resets: certain failures are really unforgiving and lead to ARM exceptions. Due to some special code in board.fs, these will often show up as a stack trace, to try and help with identifying the cause of the problem. Here’s an example, addressing non-existent memory:

  ok.
$100000 @

Unhandled Interrupt 00000003 !

Stack: [1 ] 0000002A  TOS: 00100000  *>

Calltrace:
00000000 00005199 ( 0000516A + 0000002E ) ct-irq
00000001 FFFFFFF9
00000002 00001860 ( 00001860 + 00000000 ) @
00000003 00000220 ( 00000176 + 000000AA ) --- Mecrisp-Stellaris Core ---
00000004 00001860 ( 00001860 + 00000000 ) @
00000005 00000020
00000006 F7FFFBD7
00000007 0000460B ( 00004536 + 000000D4 ) interpret
00000008 00001860 ( 00001860 + 00000000 ) @
00000009 01000000 ( 0000797E + 00FF8682 ) <<<board>>>
0000000A 0000467F ( 0000464A + 00000034 ) quit

At this point, a hardware reset will be needed to get the prompt back. Shrug. C’est la vie!

Blinking an LED

Now let’s toggle the on-board LED of the Blue Pill, which is on bit 13 of port C, i.e. pin PC13.

First, you need to configure the pin as a “push-pull” (as opposed to “open-drain”) output:

omode-pp pc13 io-mode!  ok.

(omode-pp and io-mode! are defined in flib/stm32f1/io.fs - pc13 is in flib/pkg/pins64.fs)

Perhaps surprisingly, the LED will immediately turn on. That’s because the LED is connected as “active low”, i.e. it’s on when the I/O pin is “0”, which is the default value after power up.

Let’s turn it off (ios! stands for “I/O set!”):

pc13 ios!  ok.

And to turn it on again (ioc! stands for “I/O clear!”):

pc13 ioc!  ok.

You can also toggle it, using “pc13 iox!” (short for “I/O xor!”), and because Folie supports command history, you can repeat that command by pressing <up-arrow> and then <enter>.

To blink the LED, i.e. toggle it every 100 milliseconds, we can use this code:

: blink begin pc13 iox! 100 ms again ;

Loops need to be defined inside words, the above defines a word called “blink”, which toggles the pin, waits 100 ms, and loops. This definesblink, it does not execute it. To run it, type:

blink

Lo and behold: the LED is blinking!

Oops… we’ve locked ourselves out. It’s blinking, but it’s no longer listening to the keyboard! But it’s easy to get out of this loop: press CTRL-C (if using SerPlus) or press the reset button.

Note that the definition of blink is gone after the reset, since it was defined in RAM:

blink blink not found.
      ^^^^^^^^^^^^^^^^   <== reported by Mecrisp

To enter it again, press <up-arrow> a few times. But let’s make it a little more convenient:

: blink begin pc13 iox! 100 ms key? until ;

Now blink will loop until a key is pressed (when using Folie, you have to press <enter>).

Dimming an LED

As it so happens, the PC13 pin on F103 is not capable of doing hardware-based pulse-width modulation (PWM). We’ll have to dim the LED in software. Here’s an example of how to do this, by defining a couple of words to perform simple tasks, building upon each other:

pc13 constant led

: led-init             omode-pp led io-mode! ;
: led-on               led ioc! ;
: led-off              led ios! ;
: on-cycle   ( n -- )  led-on ms led-off ;
: off-cycle  ( n -- )  20 swap - ms ;
: cycle      ( n -- )  dup on-cycle off-cycle ;
: dim        ( n -- )  led-init begin dup cycle key? until drop ;

Note that Forth imposes a bottom-up design: words can only use previously-defined words.

The last word is “dim” which takes a brightness as argument. Since we want to avoid flicker, this code uses a cycle time of 20 ms, during which the LED is partly on, partly off. The “on” duration is the 0..20 argument passed on the stack. E.g. to dim the on-board LED to 5%:

1 dim

It turns out that dimming is very non-linear: even at 5%, an LED is still fairly bright.

It would be tedious to enter this code interactively and to have to re-enter it after each reset. But we can also store this in a file called e-dim.fs, and then “send” it through Folie:

!s e-dim.fs

But why stop there? If you add “forgetram” as first line, and “1 dim” as last line, then the above send will forget the old words, define new ones, and then launch dim with argument 1. This leads to a simple – nearly-interactive – session for trying things out: edit the e-dim.fs source code, save, switch to Folie, press <up-arrow> and <enter>, and watch the LED, then press <enter> to exit the dim loop, switch back to your editor, rinse and repeat…

If things go wrong: comment out the 1 dim line at the end, send again, and use the interactive prompt to figure out what’s going on. Switching back and, ehm… forth is very easy and quick.

This example used the “ms” millisecond delay to control the PWM ratio. You could also use the “us” delay, which takes a microsecond argument to control the dimming to a much finer level.

Once you have some new words you’d like to keep: move them to a well-named file, add a new include line to core.fs, and re-send core.fs. Voilá, you’ve expanded your µC’s vocabulary.

Note how different this workflow is from cross-compilation in C: it’s a much more gradual way of “growing” software. Most of the mental effort takes place in a very dynamic context, without having to re-flash the µC’s memory at all. No big cycles, no big uploads, but lots of little steps.

Next up: switching to a USB-connected setup.


Cutting the serial cord, sort of

$
0
0

There’s a hardware USB device peripheral in every F103 chip. It has been quite a long journey, but the code is now finally at a stage where it can be used as serial console for Mecrisp Forth.

You will need the Folie utility, version 2.7 or later, a Blue Pill which will be re-flashed, and a way to do so over serial or SWD - such as a second Blue Pill, configured as SerPlus unit:

Here we go:

  • make sure you have checked out a recent copy of the embello repository
  • go to the “suf/” directory: cd explore/1608-forth/suf/
  • launch Folie, and upload the usb-common.hex file: !u usb-common.hex
  • quit Folie, unplug everything, and plug the target Blue pill back in via USB
  • launch Folie again (don’t forget the “-r” flag to use raw mode this time)
  • if all went well, you should now see the new unit, and get a Forth prompt

Here is a transcript of the above six steps, from start to glorious finish:

$ git clone https://github.com/jeelabs/embello.git
Cloning into 'embello'...
remote: Counting objects: 10433, done.
remote: Compressing objects: 100% (584/584), done.
remote: Total 10433 (delta 368), reused 0 (delta 0), pack-reused 9847
Receiving objects: 100% (10433/10433), 4.34 MiB | 747.00 KiB/s, done.
Resolving deltas: 100% (6631/6631), done.
$ cd embello/explore/1608-forth/suf/
$ folie
Folie v2.7-1-g94cba5e
Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem3430DC31
? 2
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodem3430DC31]
!u usb-common.hex
  26624b +V22 #0410 R +W +E writing: 104/104 done.
$ folie -r
Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem323F3561
? 2
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodem323F3561]
  ok.

Yes, it’s that simple, when using SerPlus and once all the connections are ok.

There are (quite) a few notes to make:

  • first a nice benefit: USB is consderably faster than 115,200 Baud over serial
  • the SerPlus has hardware ID 3430DC31, as shown on MacOS in the device name
  • in this example, the attached board was empty, with a hardware ID of 323F3561
  • the upload uses usb-common.hex which works on the Blue Pill and the HyTiny
  • there are a few other pre-built firmware images for Olimexino, Maple Mini, etc
  • the difference is w.r.t. how they enable USB, not all boards use the same pins
  • the uploaded code is a minimal build: just the Mecrisp core and a USB driver
  • the eraseflash word has been redefined to avoid erasing the USB driver
  • … otherwise, you’d lose USB and end up with a serial-only Mecrisp core (!)
  • if the core or driver gets messed up, you may not be able to use USB anymore
  • the “Mecrisp Forth ... by Matthias Koch” message is no longer shown
  • entering CTRL-C in Folie will not reset the unit, there is no DTR/RTS logic
  • doing a reset in Forth will lose the USB connection and force re-enumeration
  • Folie can detect such lost connections and will try to reconnect to the same device

That re-enumerate-on-reset behaviour is still a major inconvenience on Linux and Windows, unfortunately (and not very different from an Arduino Leonardo, in fact).

Then again, automatic reconnection works very well on MacOS, as you can see below:

  ok.
rese
[disconnected] no such file or directory
[connected to /dev/cu.usbmodem323F3561]
  ok.

Note that due to the abrupt break not all the output will always be shown in the Folie console, since the USB connection is forcefully reset in mid-flight!

On Linux, the problem is that a re-enumerated USB device will not usually show up under the same name, with “/dev/ttyACM1” instead of “/dev/ttyACM0”, for example. This could perhaps be solved by looking up the /dev/serial/by-path/... names, as these do not change.

On Windows, there may be more issues (possibly even depending on the actual Win* version). Perhaps someone with good knowledge of USB / Windows / Linux can help figure this out?

For now, the only workaround seems to be: quit Folie and re-open it using the new port.

Two more caveats, caused by known bugs and/or unimplemented logic in the USB driver:

  • sending data back-to-back to the F103 can lose packets - this is avoided in Folie by never sending more than 60 bytes at a time, and then adding 2 ms delays in between
  • the output from F103 will hang when there is no open serial connection - it’s not enough to have the unit plugged in, you need to actually open it as serial port

Still, having said that: the current combination of Folie and Mecrisp really do work well!

One final step: you need to load the additional words which make life on an F103 under Forth a lot simpler. This is similar to a serial-based setup, jusr use the “g6u/” dir instead of “g6s/“:

$ cd ../g6u
$ folie -r
Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem323F3561
? 2
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodem323F3561]
!s board.fs
1> board.fs 5: ( board start: ) 00006800  ok.
6> io.fs 11: Redefine bit.  ok.
1> board.fs 37: Redefine init.  ok.
1> board.fs 45: ( board end, size: ) 00008CB8 9400  ok.
!s core.fs
1> core.fs 4: cr compiletoflash
 ok.
1> core.fs 5: ( core start: ) 00009000  ok.
1> core.fs 12: ( core end, size: ) 0000AFDC 8156  ok.
  ok.
hello 64 KB <g6u> 323F3565 ram/flash: 17452 20480 free  ok.

FYI, the “hello” word can now be used to display the second half of the default reset greeting.

The good news is that the above procedure doesn’t work only on the Blue Pill and the HyTiny. Here is a set of 19 different low-cost F103-based boards which have all been verified to work:

The main hurdle is that you need to figure out how to re-flash the unit, either over serial using Folie or some other STM32 uploader, or using SWD via an ST-Link, a BMP, OpenOCD, etc.

That’s it. Mecrisp Forth can now be used with just a USB cable plugged in. Onwards!

Working on the JeeNode Zero

$
0
0

The JeeNode v6 was created several years ago, to combine the convenience of the then-still-upcoming Arduino IDE with low-power wireless communictation:

It all worked – and still works – really well, with over half a dozen nodes running at JeeLabs, sending out a bunch of sensor readings every minute or so. Due to their ultra low-power sleep mode, these nodes tend to run unattended for one to two years on 3 AA batteries.

But the 8-bit AVR µC family is getting a bit long in the tooth, with so many interesting new developments in hardware and microcontroller architecture, in particular the enormous variety of ARM Cortex chips out there. More precise and faster ADCs, DMA, more accurate PWM, a much more sophisticated 32-bit programming model, all that with considerably more vendor and packaging choices, and yet able to sleep at even lower current consumption levels - what is there not to like about all this?

The search for a new member of the JeeNode family started quite some time ago, with a never-released “JeeNode Pico” based on the LPC824 from NXP. Unfortunately, the number of variants for this chip family (e.g. memory size and packaging) was never very large.

All the experiments with the LPC824 (and the 8-DIP LPC810) did show that there are indeed lots of interesting features in the ARM chip family, waiting to be exposed and unleashed.

Enter the STM32, the 32-bit ARM series from STMicroelectronics which has been around for several years, with new chip releases at a breathtaking pace. While most of the more advanced chips are only available in 64-, 100-, or even 144-pin packages, there is also an active line of smaller chips in 20- and 32-pin versions. For these, the STM32L series is particularly interesting from a low-power standpoint, with its “stop” and “standby” mode power consumption in the single microamp range, sometimes even lower.

And so the idea was born to try and design a new entry for the JeeNode family, which would again combine a µC with a low-end radio, i.e. the RFM69 successor of the RFM12.

Since there are no DIP versions of the STM32’s, the size can be made considerably smaller than the ATmega-based JeeNode v6. It all ended up being a unit of about 20x40 mm, with all the components on one side, and a large “main header” to support solderless breadboards:

The connector on the left is an FTDI connector, which is all that’s needed for development: power, code uploads / re-flashing, and a serial port to communicate with during actual use.

The connector at the top left is a small “aux header” with some less-used connections to the µC and the radio. These will not generally be needed in most projects.

The main header at the bottom has been organised for maximum ease of use: all the I/O pins are labeled the same as the names on the µC itself (none of that confusing D0..D13 and A0..A5 nonsense of the Arduino), and they are also all numerically sorted to make it easier to remember what goes where. The PB6 and PB7 pins are labeled SCL and SDA, respectively, since they’ll mostly be used as I2C bus anyway.

As in the original JeeNode, the logic levels for this board are 3.3V, which is the new standard. Note that, unlike the original JeeNode, most (not all!) pins are “5V tolerant”.

One last important detail to point out is that all the components are placed on the bottom of this PCB, which leaves the top maximally exposed for clear labeling and pin identification. There are two more benefits to this setup: a coin cell holder can be soldered to the top side to create a truly small self-contained node, and the entire board can be flipped over and used as module on larger project-specific “base boards”.

Some specs and comparisons

$
0
0

To get an idea of the differences between the original JeeNode v6 and the new JeeNode Zero, we can first of all view them side by side:

No more through-hole parts - there are almost no µCs available anymore in the easy-to-socket Dual Inline Plastic (DIP) package. The pins of a TQFP chip are 0.8 mm apart, less than a third of the 0.1” standard breadboard grid. With a little experience it’s still quite doable to solder these by hand, but you’ll need a fine-tipped soldering iron, tweezers, flux, wick, and patience.

No more pre-defined “JeePorts” with their 6-pin headers - all the I/O pins are now laid out in a single row, which is still compact enough to fit even in a mini-breadboard.

As you can see, the location of the FTDI header hasn’t changed, but the pinout has a slight twist: the CTS pin (2nd from the bottom in the above pictures) is used as “BOOT0” on the JeeNode Zero: it’s pulled low by default, allowing normal operation, but it needs to be pulled high during reset to enter the ROM-based serial boot loader built into every STM32 µC. Since it’s in ROM, there is no longer a boot loader in high flash memory, as in the original JeeNode. This also means it can’t be missing or damaged anymore: every STM32 µC can be re-flashed using the pins on the FTDI port - it can’t be “bricked” this way.

Here is a quick side-by-side comparison of some of the main features, but keep in mind that this is all based on a prototype build. Some details are still “subject to change”:

JN Zero rev3JeeNode v6
MicrocontrollerSTM32L052ATmega328
ArchitectureARM Cortex M0+AVR
ALU width32-bit8-bit
Performance30 MIPS16 MIPS
Clock speed32 MHz16 MHz
Hardware debugSWD (JTAG)TWI
 
Flash size64 KB32 KB
RAM size8 KB2 KB
EEPROM size2 KB1 KB
 
ADC12-bit @ 1.1 Msps10-bit @ 77 Ksps
Timers, 16-bit71 (+2x 8-bit)
USART2x1x
I2C & SPI1x each1x each
 
DAC12-bitN/A
USBdeviceN/A
DMA7-channelN/A
CRC & RNGyesN/A
Unique hardware ID96-bitN/A
 
Wireless moduleRFM69CWRFM12B
Voltage regulatormax 5.5Vmax 13V
Battery holderoptional coin cellN/A
On-board LEDrednone
Sleep current< 2 µA< 7 µA
Dimensions41x20 mm80x21 mm

Several aspects are not apparent from the above summary and require further explanation:

32-bit - A 32-bit µC is not simply doing arithmetic faster, it also means that where the ATmega needs to perform several steps, this often ends up being a single instruction on the ARM chip - this tends to decrease code size.

MIPS - Similarly, the Million Instructions Per Second figure shown above really doesn’t do justice to the fact that the ARM chip can get considerably more done in each clock cycle.

32 MHz - The ARM µC can easily switch clock speeds and even clock sources on the fly - this can be used to dramatically reduce active power consumption.

RAM - An ARM µC does not have separate address spaces for flash and RAM, as everything easily fits in one 32-bit space - there is no special code to access flash, and more importantly: C/C++ strings and constant data do not need to be copied to RAM on startup, so RAM usage can actually be substantially lower for the same code.

DMA - The Direct Memory Access controller can sample the ADC and feed the DAC at very high rates without CPU intervention, faster in fact than even a tight code loop could probably handle - also for SPI and I2C, but the setup effort for small transfers will often not be worth the trouble.

Sleep current - the values given above include the quiescent current of the voltage regulator and keep the timer or watchdog running to periodically bring the µC back to life - whereby the low-power timer on ARM has a wakeup resolution down to 30 µs, versus 16 ms for the AVR.

This node speaks Forth (and C)

$
0
0

If you’ve been following the weblog for a while, you may have noticed that there’s a strong Forth wind blowing nowadays at JeeLabs. The JeeNode Zero is designed for use with Forth.

Setting up a Mecrisp Forth unit is very simple if you use the latest release of Folie, i.e. version 2.8 or later, since it includes all the necessary firmware:

$ folie
Folie v2.8
? Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem3430DC31
? 2
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodem3430DC31]
!u
These firmware images are built-in:
  1: F103-BMP         50096b  crc:F87A
  2: F103-Blink         724b  crc:4967
  3: F103-Mecrisp     20500b  crc:A585
  4: F103-SerPlus      7052b  crc:7DD0
  5: L052-Blink         504b  crc:54D5
  6: L052-Mecrisp     20500b  crc:7F8D
Use '!u <n>' to upload a specific one.
!u 6
  20500b .+V31 #0417 R .+W .+E161* writing: 81/81 done.
Mecrisp-Stellaris RA 2.3.2 with M0 core for STM32L053C8 by Matthias Koch
Erase block at  00005004  from Flash
Erase block at  00005080  from Flash
Finished. Reset Mecrisp-Stellaris RA 2.3.2 with M0 core for STM32L053C8 by Matthias Koch
  ok.

Note: you have to add a “-r” cmdline argument for raw mode when not using the SerPlus, as well as setting the BOOT0 jumper to “1” and pressing RESET to enter ROM boot loader mode.

As you can see, there are several firmware images built into Folie, so you’ll need to make sure that you send the right one.

With Mecrisp, the very first launch will always generate a duplicate welcome message, because it is set up to clear the rest of flash memory as very first post-re-flash step.

That’s all there is to it. As with the F103 setup, you need to load additional code to make this environment easier to use, i.e. adding L052-specific drivers for various bits and bobs of the L052’s built-in hardware peripherals.

The setup is the same as for F103 boards, but now from the jz3 folder:

  • change dir to embello/explore/1608-forth/jz3
  • launch folie
  • enter !s always.fs
  • enter !s board.fs
  • enter !s code.fs

Here is a transcript of the entire process:

$ cd embello/explore/1608-forth/jz3
$ folie
Folie v2.8
Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem3430DC31
? 2
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodem3430DC31]
  ok.
!s always.fs
1> always.fs 3:
1> always.fs 4: Finished. Reset Mecrisp-Stellaris RA 2.3.2 with M0 core for STM32L053C8 by Matthias Koch
1> always.fs 11: ( flash use: ) 00005064  ok.
1> always.fs 12: Redefine eraseflash.  ok.
!s board.fs
1> board.fs 4: eraseflash

Finished. Reset Mecrisp-Stellaris RA 2.3.2 with M0 core for STM32L053C8 by Matthias Koch
1> board.fs 6: ( board start: ) 00005100  ok.
1> board.fs 35: Redefine init.  ok.
1> board.fs 63: ( board end, size: ) 000080A8 12200  ok.
!s core.fs
1> core.fs 3: <<<board>>>

Finished. Reset Mecrisp-Stellaris RA 2.3.2 with M0 core for STM32L053C8 by Matthias Koch
64 KB <jnz> 39460C30 ram/flash: 6816 32512 free ok.
1> core.fs 4: cr compiletoflash
 ok.
1> core.fs 5: ( core start: ) 00008100  ok.
1> core.fs 16: ( core end, size: ) 0000AF08 11784  ok.
  ok.
!reset
Mecrisp-Stellaris RA 2.3.2 with M0 core for STM32L053C8 by Matthias Koch
64 KB <jnz> 39460C30 ram/flash: 4928 20608 free ok.

The final reset produces the Mecrisp welcome message, followed by the hello information. This example loads quite a few things (including graphics code and a font, and a 1 KB RAM buffer for an OLED). It leaves ≈ 5 KB of RAM and 20 KB of flash free for application use.

To try a little blink demo, enter this (you’ll need a reset to get out of it again):

: blink begin led iox! 500 ms again ; blink

But hey, there’s nothing preventing anyone from using the JeeNode Zero with C or C++ …

In which case you’ll need to set up the gcc toolchain, as described previously. You’ll also need a few files to get a “make” build working with an proper linker load map. The easiest way is to clone the embello repository from GitHub and look at the explore/1651-l052 subdirectory.

The blink code lives in a subdirectory (unimaginatively called “blink”) and is very simple:

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>

int main (void) {
    rcc_periph_clock_enable(RCC_GPIOB);
    gpio_mode_setup(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO5);
    for (;;) {
        for (int i = 0; i < 100000; ++i)
            __asm("");  // idle loop, takes a fraction of a second
        gpio_toggle(GPIOB, GPIO5);
    }
    return 0;
}

The Makefile is also simple, deferring all the real work to a few files in the parent directory:

BINARY = blink
OBJS =
LDSCRIPT = ../stm32l0xx8.ld
SCRIPT_DIR=..

default: $(BINARY).bin
include ../Makefile.include

Typing “make” will build the firmware image, called … “blink.bin“:

$ make
  CC      blink.c
  LD      blink.elf
  OBJCOPY blink.bin

The final step is to launch Folie and upload this firmware image with “!u blink.bin“:

$ folie
Folie v2.8
? Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem3430DC31
? 2
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodem3430DC31]
!u blink.bin
  504b .+V31 #0417 R .+W .+E4* writing: 2/2 done.

And that’s it: the LED on pin PB5 will start blinking. Amazing what technology can do, eh?

Taking the JNZ rev3 for a spin

$
0
0

Let’s make the JeeNode Zero do some simple things. Like blinking LEDs in a continuous loop:

The code for this was actually written step by step, and later saved in a file called e-leds.fs:

forgetram

\ configure one pin as push-pull output
: out ( pin -- )  OMODE-PP swap io-mode! ;

\ configure all the LED pins as outputs
: setup  PA0 out  PA1 out  PA2 out  PA3 out  PA4 out  PA5 out ;

\ turn one pin on for 100 milliseconds
: blip ( pin -- )  dup ios!  100 ms  ioc! ;

\ blink LEDs in a loop, until new input is received from Folie
: go
  begin
    PA0 blip  PA1 blip  PA2 blip  PA3 blip  PA4 blip  PA5 blip
  key? until ;

setup go  \ press <enter> to quit

Let’s go through these lines of code in some more detail:

  • forgetram– Clear all previously defined words from RAM. It’s not strictly needed, but avoids generating a bunch of “Redefine …” warnings.
  • : out ... ;– Define a new word “out” which sets up a pin supplied as argument on the stack to be an output (OMODE) in push-pull mode (PP), using a utility word (io-mode!) which takes two arguments - the “swap” is needed to get these arguments in the proper order.
  • ( pin -- )– This is a stack comment - everything between “(” and “)” is ignored by Mecrisp Forth, this example documents the stack use of “out”.
  • : setup ... ;– Uses the “out” word to set up all 6 pins with a LED attached. There are 6 LEDs in the above setup (using a little eBay module with current limit resistors).
  • : blip ... ;– Set a specified pin to “1”, wait 0.1 seconds, and clear it to “0” again.
  • : go ... ;– This is the main loop, it sequences through each of the six LED pins, and repeats. At the end of each cycle, it checks for new incoming commands from Folie and quits the loop when that happens.
  • setup go– Start the ball rolling, i.e. initialise everything and loop. Note that you don’t have to include the actual calls here, you can also enter them manually after the above definitions have been loaded.

Speaking of loading, there are two ways to develop with Folie: either enter code interactively and try it out until working as intended, or save definitions in a file and use Folie’s “send”, by entering “!s <file>”. Interactive mode is best when you’re not yet sure about the code and need to try things out, but it’s volatile since everything typed in will be gone after a reset. When saved on file, you can edit and extend as needed, then resend. The forgetram at the start and the setup go at the end help streamline this approach. A mix of both is often used: define the more stable words in a file and resend when changed, then call them interactively.

The above demo code illustrates how to use the essential words defined in flib/stm32l0/io.fs. Here is a summary of all the pin modes you can set with io-mode!, taken from that same file:

IMODE-FLOAT  \ input, floating
IMODE-HIGH   \ input, pull up
IMODE-LOW    \ input, pull down
IMODE-ADC    \ input, analog

OMODE-AF-PP  \ alternate function, push-pull
OMODE-AF-OD  \ alternate function, open drain
OMODE-PP     \ output, push-pull
OMODE-OD     \ output, open drain

OMODE-WEAK   \ add to OMODE-* for 400 KHz iso 10 MHz drive
OMODE-SLOW   \ add to OMODE-* for 2 MHz iso 10 MHz drive
OMODE-FAST   \ add to OMODE-* for 35 MHz iso 10 MHz drive

Now let’s try something a bit more advanced. We’ll attach a BME280 sensor, load a Forth driver for it, and use that to initialise the sensor and get readings twice a second:

The code for this is small, because the driver in flib/i2c/bme280.fs does all the hard work:

forgetram

\ include ../flib/i2c/bme280.fs

\ use PA13 and PA14 to supply power to the BME280 sensor
: bme-power
  OMODE-PP PA14 io-mode!  PA14 ioc!  \ set PA14 to "0", acting as ground
  OMODE-PP PA13 io-mode!  PA13 ios!  \ set PA13 to "1", acting as +3.3V
;

\ configure I2C and the BME280 sensor attached to it
: setup  i2c-init bme-power bme-init . bme-calib ;

\ print BME readings every 500 ms, until new input is received from Folie
: go
  begin
    bme-data bme-calc
    cr . . .
    500 ms
  key? until ;

\ the delay is a hack to force a timeout in Folie before the loop starts
1234 ms setup go

Some notes:

  • this demo is available on GitHub at e-sensor.fs, so if you’re in the “1608/jz3/” directory, you can upload it using “!s e-sensor.fs” in Folie
  • the driver include has been commented out, because it’s already included by core.fs, which is stored permanently into flash
  • given the way the breakout board pins are laid out, we can avoid using wire jumpers - instead, the “GND” and “VIN” pins are connected to to I/O pins, which we then set up as fixed outputs set to “0” and “1” respectively - see the definition of bme-power above
  • : setup ... ;” does 3 things: 1) enable the BME power pins (bme-power), 2) init the BME sensor (bme-init) – the return value is non-zero if there was an error, we simply print it out using “.” – and 3) set up the BME280’s calibration data (bme-calib)
  • : go ... ;” is again the main loop for this demo, but now it requests a sensor reading (bme-data) and calculates actual temperature / humidity / pressure values, returned as three values on the Forth data stack
  • the “cr . . .” code pops these three values in reverse order, and prints them on a line
  • the final “1234 ms setup go” is a bit of a hack: the delay (which has to be slightly over 1 second to trigger Folie’s timeout) generates an error in Folie, causing it to abort the send, but since it’s the last line of the file anyway, we don’t care: the go loop will already have started, and will terminate when we hit <enter>

Here is some sample output, with a new reading printed out every 500 milliseconds:

  ok.
!s e-sensor.fs
1> e-sensor.fs 23: 1234 ms setup go  (timeout)
0
2176 102972 4635
2176 102975 4634
2176 102975 4634
2175 102964 4634
2175 102964 4634

As you can see, it’s currently 21.76°C here at JeeLabs, the barometric pressure is 1029.72 hPa, and the relative humidity is 46.35%. Due to the excellent factory calibration of the BME280, we are getting realistic and reliable measurements!

JNZ testing and availability

$
0
0

At the time of writing this (end Dec 2016), there have been three prototyping rounds for the new JeeNode Zero. The current “rev3” board is getting close to the intended final release - the STM32L052 µC is a good choice, and due to STM’s impressive chip compatibility, the same PCB can be used with a variety of other lower-cost or higher-performance chips.

There’s an optional CR2032 coin cell holder on board, which sets the stage for a very small self-contained Wireless Sensor Node. The lifetime for autonomous use will depend entirely on how well the sleep mode is implemented (looking great so far), the sensor’s own quiescent current, and of course the duty-cycle requirements for radio use.

There have been some experiments with on-board sensors, but due to the huge range of available sensor types, the variety of expected use cases, and the relatively quick pace of innovation in that area, the JeeNode won’t be fitted directly with sensors on the board itself. Some novel ideas are being explored right now, but it’s too early to tell how they will turn out.

The current rev3 board has been tested as a “fridge node” at JeeLabs, i.e. placed inside the fridge, reporting temperature and humidity over wireless - and it was really surprising to see just how wide the temperature swings of a 20-year old unit were: over 5°C!

So what’s the next step?

There are four tasks ahead: 1) gaining more experience with these nodes, 2) finalising the board design, 3) building some test jigs, and 4) getting production and assembly runs going.

Several dozen rev3 boards have been assembled so far, loaded up with firmware, and verified to work - including this set of twenty sample units:

All units include a CR2032 coin cell battery holder (except that one in the front: we ran out of components during the last assembly run), since this will be a major use case for these nodes.

If you’re interested in this development, and would like to try them out and help come up with the final adjustments needed to make these maximally useful in a truly wide range of home monitoring + automation scenarios, you can now order one from the JeeLabs shop.

The price of the new JeeNode Zero will be identical to the classic JeeNode: €18.50, quantity 1. Due to the very limited availability of these initial boards, please order only a single unit.

The units in this first batch were all manually solder-pasted, picked-and-placed, reflowed using a neat vapour-phase reflow oven, flashed w/ Mecrisp Forth, and verified to receive RF.

To use the JeeNode Zero at this stage, you should be prepared to re-flash it with new firmware if important fixes or improvements are made in the next few weeks. For this, you will need to assemble a SerPlus using a Blue Pill or HyTiny board (or equivalent). Which in turn requires either an FTDI serial interface, a Black Magic Probe, or an ST-Link to upload the SerPlus code.

For wireless trials, to be described in an upcoming article, you’ll need another RFM69-based node to talk to, set to 868 MHz in “native” mode (the JNZ’s RF69 driver has no compatibility mode). A JeeNode v6 or JeeLink will be fine for this once you select the correct library that uses the “native” packet format.

While these requirements may seem daunting to some, please keep in mind that the current rev3 batch is really intended only for people who want to be actively involved in this phase of the process. Now is the time to push it in various new directions, and come up with ways to improve on the choices made so far. The more this design gets a beating now, the more useful and flexible it can be in the years ahead, as building block for numerous fun tasks in the house or local surrounds.

The first three revisions are a good example: components and connections have changed, a serious bug with serial-vs-untethered use has been found and fixed, and slightly adjusted pin choices for the main header have increased flexibility. But there might still be more tricks to increase the (re-) usability of GPIO pins or bugs lurking in deeply hidden places.

Although the Chinese New Year festivities may throw a spanner in the works, the plan is to have the JeeNode Zero in production by the end of Februrary 2017. Exciting times ahead!

Hey, there's a radio in there!

$
0
0

The Forth driver for the RFM69 on the JeeNode Zero is in flib/spi/rf69.fs. It is normally included in core.fs and therefore always available from flash memory. It’s very simple to use: call rf69-init to intitialise the RFM69 module, and then call either rf69-recv to poll for new incoming packets or rf69-send to send out a single packet. The current driver does not use any interrupt pins, so only the RFM69’s 4 SPI pins plus power need to be hooked up.

Here’s an example of how to receive one packet, followed by a dump to show the payload data:

rf69-init  ok.
rf69-recv . 9  ok.
rf.buf hex. 20001E94  ok.
rf.buf 9 dump
20001E90   01 00 00 00 C0 01 81 80   06 8F 7A F1 80 7A 7A 7A   ........ ..z..zzz
20001EA0   7A 7A 7A 7A 7A 7A 7A 7A   7A 7A 7A 7A 7A 7A 7A 7A   zzzzzzzz zzzzzzzz
 ok.

This is not very useful, other than to illustrate that the driver works. Here is a better example based on rf69-listen - it configures a different frequency (868.600 MHz) and net group to listen to, and then dumps each incoming packet in hex format, including reception details:

8686 rf69.freq !  ok.
6 rf69.group !  ok.
rf69-listen
RF69 21EE06C00102BEC0010A 8101D119CC029A7AE980
RF69 21EE06A202006EC00107 818002A07AEA80
RF69 21EE06C3010108C00107 8101CF19CE8080

The format of these reported packets is as follows:

This is the public API of the RF69 driver:

   0 variable rf.rssi
   0 variable rf.lna
   0 variable rf.afc
  66 buffer:  rf.buf

8683 variable rf69.freq
  42 variable rf69.group
  61 variable rf69.nodeid

: rf69-init ( -- )  \ init RFM69 with current rf69.group and rf69.freq values
: rf69-recv ( -- b )  \ check whether a packet has been received, return #bytes
: rf69-send ( addr count hdr -- )  \ send out one packet
: rf69-power ( n -- )  \ change TX power level (0..31)
: rf69-sleep ( -- )  \ put radio module to sleep
: rf69-listen ( -- )  \ init RFM69 and report incoming packets until key press
: rf69-txtest ( n -- )  \ send out a test packet with the number as ASCII chars
: rf69. ( -- )  \ print out all the RF69 registers

The rf69-listen word is a wrapper around rf69-recv to format the above packet messages:

: rf69-listen ( -- )
  rf69-init cr
  begin
    rf69-recv ?dup if
      ." RF69 " rf69-info
      dup 0 do
        rf.buf i + c@ h.2
        i 1 = if 2- h.2 space then
      loop  cr
    then
  key? until ;

Likewise, the rf69-txtest is a small wrapper to generated a test message, the payload is an integer, converted to ASCII text:

: rf69-txtest ( n -- ) rf69-init  16 rf-power  0 <# #s #> 0 rf69-send ;

We can enter this on a second node to send out one test packet:

12345 rf69-txtest

The result will then be reported as follows by that first listening node:

RF69 21EE0650060016C03D05 3132333435

For debugging, there’s rf69. (note the trailing dot), which dumps all RFM69 register settings:

rf69.
     0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
00: -- 10 00 02 8A 05 C3 D9 26 40 41 60 02 92 F5 20
10: 24 9F 09 1A 40 B0 7B 9B 08 42 42 40 80 06 5C 00
20: 00 FC 8D 00 A2 00 07 D9 46 C4 00 00 00 05 88 2D
30: 06 00 00 00 00 00 00 D0 42 00 00 00 8F 12 00 00
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00
50: 14 05 88 08 00 00 01 00 1B 09 55 80 70 33 CA 08 ok.

And if you want to experiment with different register settings, there’s nothing simpler than entering some interactive commands to change or read out the RFM69 on the fly:

%1000 $4E rf!  ok.
$4F rf@ . 142  ok.

This starts a temperature measurement by setting bit 3 of register $4E, and then reads out the result from register $4F. This has to be calibrated before it can be turned into a temperature value, but at least it shows how you can dynamically set a register and read out another one.


Ultra low-power and coin cells

$
0
0

Let’s explore the current consumption of a JeeNode Zero a bit and see how we can reduce it. The following measurements were all made with a low-cost Voltcraft VC170-1 multimeter, in series with the +5V power feed from SerPlus to JNZ - using the 400 mA range for now:

  • power consumption, running just the Mecrisp core: 4.75 mA

Once we load the always.fs, board.fs, and core.fs code in flash, and reset the board, power consumption will go up, because init in board.fs turns the on-board LED on:

  • Mecrisp core, including on-board LED: 6.40 mA

The RFM69 powers up in standby mode, so let’s put it to sleep, and turn off that LED:

led-off  \ toggle the LED to turn it off
rf69-init rf69-sleep
  • LED off, RF69 in sleep mode: 3.29 mA

So this is essentially the L052 µC, running at 16 MHz - it’s running in an idle loop, with a periodic “system tick” interrupt triggered once every millisecond.

We can lower the power consumption by putting the µC processor to sleep between interrupts, as it’s not doing anything useful. Since it wakes up every time, we need to do this in a loop:

: coma  begin sleep key? until ;  coma

The sleep word is defined in multi.fs, it uses the ARM’s “WFI” instruction.

  • LED off, RF69 in sleep mode, µC also in sleep: 1.38 mA

Not bad. We’re still in a context which is very responsive: the µC is running at 16 MHz, and instantly responds to every interrupt. If we were to make this the default interactive mode, we’d probably hardly notice the difference. A powerful 32-bit µC, running at less than 2 mA!

The next changes are more invasive. We can take the system clock down, but it’ll become harder to keep a full interactive session going, because it won’t be able to handle the UART communication at 115,200 baud anymore.

Let’s start by blinking the LED briefly every 10 seconds, to verify that the board is still alive:

: blips
  led-off  rf69-init rf69-sleep  \ led off, radio sleep
  2.1MHz 1000 systick-hz  \ slow down the clock, adjust systick accordingly
  begin
    led-on sleep led-off  \ a very short 1ms LED blip, but still visible
    9999 0 do sleep loop  \ count 9999 additional 1ms ticks, doing nothing
  again ;

blips

The code has to be inside a word definition now, because it contains a loop. Since the only source of interrupts is the systick timer running at 1000 Hz, we can simply count off those interrupts: each call to sleep will keep the µC in sleep mode until the next interrupt occurs.

The 2.1MHz clock is less accurate, and is in fact just one of the many settings of the built-in MSI (medium-speed internal) clock, which can run from 65 KHz to 4.2 MHz, in powers of 2.

  • LED off, RF69 and µC in sleep, running at 2.1 MHz: 0.43 mA

Note that to get the µC out of this state, we’ll need to hit CTRL-C to reset it.

You can actually predict the µC’s current consumption at lower clock rates: as we’ve seen, it draws 3.29 mA at 16 MHz (when in run mode, without WFI sleep). That’s ≈ 0.21 mA/MHz. And guess what: at 2.1 MHz, that’s … 0.43 mA - exactly as measured!

So now we have a power consumption of about 430 µA, at the cost of no longer being able to communicate with it at 115,200 baud. A lower rate could still be used, but it’s not really useful to go there since we’d lose the ability again when chasing even lower power modes anyway.

Does this mean we can go lower still? Oh, sure …

The next step is to actually stop the µC and most of its clock-based peripherals altogether. Power consumption is very much determined by the number of gates toggling, so what we need to do is switch most of the µC’s internal clocks off. Switching them off completely would be a problem though, because then the µC loses all sense of time and wouldn’t be able to come back without external pin change. Note that this also disables USARTs, SPI, I2C, and timers.

Fortunately, the STM32L series has a special low-power timer. On the L052, it’s driven by an ultra low-power (and even less accurate) internal clock, running at approximately 37 KHz.

The driver code for these very low-power modes is in flib/stm32l0/sleep.fs– including a very convenient wrapper for stopping the µC for 100 ms, 1 s, or 10 s – so let’s rewrite the code a bit:

: lp-blips
  led-off  rf69-init rf69-sleep  \ led off, radio sleep
  2.1MHz 1000 systick-hz  \ slow down the clock, adjust systick accordingly
  lptim-init              \ initialise the low-power timer
  begin
    led-on sleep led-off  \ a very short 1ms LED blip, but still visible
    stop10s               \ enter stop mode for approx 10 seconds
  again ;

lp-blips

Sure enough, the LED is still blinking ever so briefly once every 10 seconds. And the power reduction is dramatic - we need to switch the multimeter to its 400 µA range to measure it:

  • LED off, RF69 sleep, µC in stop mode, waking up at 2.1 MHz: 6.5 µA

The trick here, and the reason the µC was set to use the 2.1 MHz clock, is that this is how stop mode normally terminates. In other words: when coming out of stop mode, the µC defaults to resuming with its MSI clock running at 2.1 MHz, so the systick rate of 1000 Hz will be correct.

From here on, reducing power draw becomes a lot harder. We’ve shut down all major power consumers - many smaller factors remain, but they’re not always obvious or easy to eliminate.

Let’s go after one such source of current leakage for now: after reset all the I/O pins are set to input mode with a weak pull-up resistor. This is not the optimal setting during stop mode. We can set the pins to “input-ADC” mode to disable as much of the internal circuitry as possible:

: highz-gpio
  IMODE-ADC PA0  io-mode!
  IMODE-ADC PA1  io-mode!
  IMODE-ADC PA2  io-mode!
  IMODE-ADC PA3  io-mode!
  IMODE-ADC PA4  io-mode!
  IMODE-ADC PA5  io-mode!
  IMODE-ADC PA6  io-mode!
  IMODE-ADC PA7  io-mode!
  IMODE-ADC PA8  io-mode!
  IMODE-ADC PA9  io-mode!
  IMODE-ADC PA10 io-mode!
  IMODE-ADC PA11 io-mode!
  IMODE-ADC PA12 io-mode!
  IMODE-ADC PA13 io-mode!
  IMODE-ADC PA14 io-mode!
\ IMODE-ADC PA15 io-mode!   \ SSEL
  IMODE-ADC PB0  io-mode!
  IMODE-ADC PB1  io-mode!
  IMODE-ADC PB3  io-mode!
  IMODE-ADC PB4  io-mode!
\ IMODE-ADC PB5  io-mode!   \ LED
  IMODE-ADC PB6  io-mode!
  IMODE-ADC PB7  io-mode!
  IMODE-ADC PC14 io-mode!
  IMODE-ADC PC15 io-mode! ;

With these ultra-low current levels, it’s very easy to run afoul on some unexpected side effects. In this example we obviously have to keep the LED pin enabled so it can still toggle, but we must also keep the radio select pull-up enabled to prevent the pin from floating in stop mode.

The improvement is fairly substantial - as you can see on the multimeter display below:

Getting a complete circuit down to ultra low-power consumption levels can be a fairly long and winding road. It requires identifying all the weak spots. App Note 4445 shows what’s possible:

Note that, while Standby mode looks tempting, it can only wake up with a reset. Stop mode is much more convenient, because the µC can resume where it left off - with registers and RAM intact. For completeness: on a JNZ, the regulator adds 0.5 µA, and the radio another 0.1 µA.

But hey, we’re already drawing less than the original JeeNode v6. With a 230 mAh coin cell and ignoring self-discharge plus the power needed to drive the radio, the estimated lifetime would be over 9 years. Even if the average ends up quite a bit higher, that’s not a bad start!

A high-resolution sensor node

$
0
0

The JeeNode Zero is intended as sensor node in a Wireless Sensor Network. This requires:

  • a sensor we can read out periodically, such as the BME280
  • being able to sleep with a very low current, as recently described
  • the ability to run the node unattended, i.e. without FTDI cable
  • formatting and sending the collected sensor readings over RF

So far, all development has been performed through FTDI. To run unattended, we’ll need to cut that umbilical cord and make the node start up by itself from a battery.

Battery-powered operation is essentially just a matter of placing a CR2032 coin cell in the on-board coin cell holder, but – as so often – there’s a bit more to it than that:

  • you can’t have both FTDI connected and a coin cell inserted at the same time, because they share one circuit - that’d put 3.3V across the 3.0V coin cell, it’s not meant to charge!
  • there are some differences when running on 3.0V, especially with the A/D and D/A converters, which both operate in ratiometric mode, i.e. relative to the supply voltage
  • it is no longer possible to keep the code in RAM, everything has to be stored in flash memory to survive the power cycle while switching to battery use
  • we lose the ability to run a command interactively, so the node needs to be set up to automatically launch its built-in “application” code
  • it’d be nice if we could still reconnect the node to FTDI (with the coin cell removed)

A trick which can be quite useful during development, is to connect the JeeNode Zero to FTDI with all pins except the 5V power feed, using a modified 6-pin “stacking” header:

This way, a coin cell can be used as power source, while keeping the serial interface available for uploads, resets, and debugging (this is a JNZ rev1, which had a BME280 on top).

Mecrisp Forth supports auto-execution by re-defining init, which is always automatically started up after a reset. We might therefore consider implementing our node as follows:

compiletoflash

: main
  <application code ...>
;

: init  init main ;

Note the call to initinside our redefined init. This is extremely important, since we have to make sure that all the original initialisations are still carried out. Within a redefinition of “x”, any reference to “x” is always treated as a call to its previous definition (not as recursion!).

But there’s a problem… what if the code has a bug, or we simply want to replace it?

With the above code, we have effectively shut down Forth’s default interactive interpreter. This code is no longer listening for incoming serial data! To make matters worse, adding a check using key? is not good enough: if the code crashes and hangs (which you can expect to happen all the time during development), then it won’t reach that key check either.

One solution is to include a special word called “unattended”, which is defined in board.fs:

: init  init unattended main ;

This has the following effect:

  • unattended checks whether the RX pin is connected, and if so, it exits its caller, which is init in this case - as a result, main will not even be started when FTDI is connected
  • main is still assumed to be written as a loop with “begin ... key? again”, so that when main is started interactively, it can easily be stopped
  • as always with FTDI connected, runaway code can be aborted with a reset (i.e. CTRL-C in SerPlus), so crashes and hangs are no big deal
  • if unattended does not detect a serial connection, it replaces the key? handler to check for a re-connect of the serial receive pin and treat that as an incoming char / key press

The end effect is that main will only run automatically when not plugged into FTDI. When plugged in, you will need to start it by typing main. In addition, when an unattended running JeeNode Zero is plugged in, it’ll react as if a key has been pressed, and exit its main loop.

It is probably still possible to lock yourself out of the command prompt, but the chance of this happening is now much lower. You can always get a JeeNode Zero back to interactive mode by re-flashing a new copy of Mecrisp Forth onto it, using Folie’s built-in “upload” command (!u).

The final step is to get the data into a packet and send it out using rf69-send. The mechanism which is starting to become standard practice at JeeLabs, is to use a variable integer encoding which compresses ints to a fairly compact binary format. The utility words in flib/any/varint.fs make this relatively easy - although the API and code for this may look a bit dense:

: send-packet ( vprev vcc tint lux humi pres temp -- )
  2 <pkt  hwid u+>  n+> 6 0 do u+> loop  pkt>rf ;

Basically, we’re sending out a packet of type “2”, followed by the node’s unique hardware ID, followed by a signed int and 6 unsigned ints - all popped in reverse from the data stack. The result is then broadcast as RF packet. Further details will be covered in a future article.

The full implementation of this first sensor node can be found in jz3/main.fs– by changing the DEBUG value to 1, you can make it report sensor values over serial instead of over RF.

To load this application in flash for unattended remote use, send final.fs to the JNZ – this includes main as well as the special init override, with unattended taking care of the details.

Note: this was coded before the sleep mode power savings were ready. In this implementation, the node still draws about 1.7 mA, too much for long term coin cell battery use. But it worked fine as initial test for a fridge node and ran for two weeks before the coin cell went dead.

Here are some results from that early test run of the fridge node:

Temperature and humidity are both reported with an impressive resolution of two decimal places. The big dip is the cooler in action. The tiny blip was caused by opening the door.

Some more values - including the internal temperature sensor of the STM32L052 µC itself. It’s surprisingly usable due to the pre-calibrated parameters in ROM:

Another very useful feature of the STM32L052, is that it is possible to accurately estimate the voltage at which it is running - by measuring the internal bandgap voltage in reference to Vcc and then performing a little inverse calculation. Again, these values are of a surprisingly good quality, due to the µC’s built-in calibration parameters for the bandgap:

The first graph is measured right after coming out of sleep mode, when the coin cell has presumably recovered from the TX power drain, while the second graph shows the measurements made right after the TX packet send completes. As you can see, coin cells have a hard time with 15..25 mA current pulses. These tests were made with a rev1 JeeNode Zero, which had a 22 µF supply capacitor.

The glitches in the left-side graph might be caused by not waiting long enough for the ADC to stabilise after coming out of stop mode (it used the 16 MHz HSI clock). Needs investigation.

Note how the battery voltage pattern is almost identical to the temperature curve in the fridge. At these low temperatures, batteries tend to have a very difficult time generating electricity!

Remote development over WiFi

$
0
0

There’s a fascinating option for the JeeNode Zero, called the esp-link by Thorsten von Eicken. He also created a little board with a switching regulator, the ESP8266, and an FTDI header, and calls that a “WiFi-link”. The result is essentially like a “BUB/SerPlus over WiFi”:

The esp-link firmware turns the ESP8266 into a transparent bridge from WiFi to FTDI serial, with the ability to control the DTR and RTS signals (RTS reuses the CTS pin next to GND for this purpose, just like the modified BUB and the SerPlus).

The WiFi-link includes an efficient on-board switching regulator, so that the combination of WiFi-link and JeeNode Zero can easily be powered off a single supply (both boards will work with anything from 3.6V to at most 5.5V). Such as this LiPo-based “USB power bank”:

This gives you remote console access to Forth on a JeeNode Zero, including the ability to reset it and send files. Here is an example, setting up the node to report incoming RF69 packets:

$ folie -p 192.168.188.58:23
[connected to 192.168.188.58:23]
  ok.
!reset
Mecrisp-Stellaris RA 2.3.2 with M0 core for STM32L053C8 by Matthias Koch
64 KB <jnz> 32390236 ram/flash: 4928 20608 free ok.
  ok.
8686 rf69.freq !  ok.
6 rf69.group !  ok.
rf69-listen
RF69 21EE0600000000C00107 8103FE42E08080
RF69 21EE06DA010208C00107 8103FD42E68080
RF69 21EE06D801FDDEC00107 8103FD42E78080

It’s really no different from using a direct USB-to-serial connection with a SerPlus. The only change is how Folie is told to set up a socket-based Telnet connection. And since Folie and the esp-link both support Telnet escapes, CTRL-C will force a remote hardware reset of the JNZ.

Please do keep in mind that anyone with access to your WLAN will be able to gain full control over the attached unit, JeeNode Zero or otherwise.

Here is the schematic (PDF) of the WiFi-link board:

Other configurations are also possible: the esp-link project on GitHub is extremely well documented – esp-link is in fact capable of a lot more than just acting as serial gateway. There’s a web server built-in, including a console to talk directly to the attached board:

The settings used in combination with a JeeNode are as follows (responsive layouts, yeay!):

There are still some rough edges in combination with Folie, but the latest revision of esp-link includes full support for remote re-flashing. In other words: total control over the attached JeeNode Zero, for uploads, hard resets, and serial I/O - all through a single Telnet session.

Goodbye 2016, and hello 2017!

$
0
0

Many very surprising things have happened (or failed to be resolved) in the world in 2016. But as far as this weblog is concerned, it can be summarised as: 52 weekly posts, 92 articles.

2016

A year ago, I wrote about my New Year’s resolutions for 2016. Commitments … tricky stuff!

Resolutions, like most intentions, are easy to make. Looking back to see how they ended up after a year is another story. I’ve never done this in public before (there’s no place to hide!). Let’s get it over with. Swallow my pride a bit and realise that a few things did work out ok:

  • Lower night-time power consumption from 100W to 50W - not met.

It’s between 90 and 100 Watt. A slight reduction, as our kitchen stove was replaced with a modern unit, drawing 0.5W idle instead of the previous 8W. I haven’t really chased down the causes of the “vampire” current consumption - probably should have. There must be ways to find out what’s drawing so much. Our 20-year old fridge might well be part of the problem.

Due to our solar panels, we’re again net producers of electricity for 2016 (around 1000 KWh), but not wasting it is still a worthy goal in my book.

  • Reduce gas consumption by 20%, down from 1600 m3 - partly met.

Looks like our past year’s gas consumption will end up being around 1500 m3. That wasn’t so hard: a little more clothing in the winter, and the thermostat a notch further down by default.

  • I will not buy anything with a computer in it - ouch.

This was clearly placing the bar very high for a gadget freak like me. I did succeed beyond my own expectations: I’ve not bought a new laptop or mobile device - NOTHING over €50 with a computer in it, in fact (eh… except a Silhouette cutting plotter for SMD stencils, last month).

I did skirt the issue by buying several FPGA boards all through the year, and in the past few months I did buy a couple of C.H.I.P. boards (great!) and an Odroid C2 (64-bit ARM: yawn).

I’ve also purchased a variety of STM32 µC chips in the last quarter of 2016 - but these were in fact exempt, falling under the category “used for the R&D of new JeeLabs products”.

  • I will create - and release - a number of complete hardware products - sorta, kinda.

The JeeNode Zero was born end 2016 and made available (still in very limited quantity). There’s also another associated board being prepared for production.

  • I will design - and release - a working implementation of […] - not really.

While Folie was indeed designed from scratch and released as open source in 2016, it isn’t quite up to the very ambitious ”… an infrastructure which interoperates with both existing and new nodes”. It’s a good starting point and I intend to take it further - that’s all I can add.

  • I will continue to write articles for inclusion in the Jee Book - yes, yes, and no.

Yes, I am continuing my writing, and yes it is intended for inclusion in the Jee Book. But no, the current published version hasn’t been updated with any new material for well over a year now. I do want to get back to this at some point, and will probably split things up in multiple volumes, since the material is starting to become too extensive for a single book.

2017

Nah. No big words. No big resolutions. Just some personal observations and intentions:

  • JeeNode Zero - my top priority: it deserves to reach the official “Release 1.0” status

  • Forth development - absolutely fantastic for all the code running on remote JeeNodes

  • Infrastructure - oh yes, more important than ever: managing the WSN, not individual nodes

  • Wireless - waves which we can’t directly observe are magic: I plan to dive a lot deeper into RF

  • FPGAs - literally the fabric of computing: far too much fun and way too important to ignore

  • Retro computing - reproducing the self-contained worlds of the past: it keeps intriguing me

  • Electronics - the most accessible branch of physics: an infinite source of joy and learning

  • Carbon footprint - well, ok, one resolution which I’d like to uphold in 2017: no flying

  • Energy consumption - a recurring theme, but it’s getting harder to reduce it further

Speaking of consumption: I’ve just discovered that… I’m about to completely run out of bits! Have placed an order for fresh bits and bytes, expected to arrive at JeeLabs on February 1st.

All my best wishes to you and yours for 2017,
– Jean-Claude Wippler

Using Linux instead of a SerPlus

$
0
0

Uploading code to an STM32 ARM µC is a bit of a chicken-and-egg problem. The traditional setup uses JTAG (or more likely: 2-wire SWD), but this requires a JTAG programmer such as an ST-Link or a Black Magic Probe.

As you will know by now, all STM32 µC’s also include a serial boot loader in ROM, which requires toggling the RESET and BOOT0 pins in just the right sequence. You can do this manually, but that can become very tedious after a few times.

SerPlus was created for just this reason: a custom-programmed F103 board, which talks to the host over USB, and to a target board over serial, with DTR tied to RESET and RTS to BOOT0:

But that requires a SerPlus, onto which you also need to flash the proper firmware. Catch 22!

There’s another option, if you have a USB-serial interface, such as a modified BUB, which connects DTR and RTS to pins 1 and 5 on the FTDI connector, respectively (for orientation: pin 6 is ground). In that case, we can use a standard OSS utility called ser2net to perform the Telnet <=> RTS/DTR translation for us (as well as selecting even parity during uploads):

This is in fact a pretty convenient setup to use from any O/S: use Folie on your development machine, whatever it is, right next to your source files, and connect to Linux over the network:

folie -p 192.168.1.123:2000

This sets up a network connection (assuming here that the Linux box has IP address 192.168.1.123), and connects to port 2000.

Before we can use this, we’ll need to do a bit of one-time setup on the Linux box:

  1. install ser2net with: sudo apt-get install ser2net

  2. edit the ser2net configuration file (/etc/ser2net.conf), commenting out all the existing settings by inserting a “#” in front, and adding one line with the following text in it:

    2000:telnet:600:/dev/ttyUSB0:115200 8DATABITS NONE 1STOPBIT
    

    (replace “ttyUSB0” by whatever you need for your USB interface board)

  3. after saving, reload the configuration: sudo /etc/init.d/ser2net reload

That’s it, the Folie command shown above will now connect over the network:

$ folie -p 192.168.1.123:2000
[connected to 192.168.1.123:2000]
Mecrisp-Stellaris RA 2.3.3 with M0 core for STM32L053C8 by Matthias Koch
64 KB <jnz> 3238022C ram/flash: 4920 14192 free ok

You now have the equivalent of a networked SerPlus - which in turn would be an excellent opportunity to get and re-flash a Blue Pill and turn it into a spare real SerPlus for local use!

Forth over USB on STM32F103

$
0
0

Mecrisp Forth and its GitHub mirror make a wonderful interactive environment for a range of powerful ARM-based 32-bit µCs. There are a gazillion low-cost STM32F103-based boards available on eBay and AliExpress, such as this one, for example - often called the “Blue Pill”:

But… out of the box, Mecrisp Forth only supports a serial port as console.

Wouldn’t it be nice if we could load an extended version of Mecrisp Forth onto this board, which does console I/O over USB? After all, the connector is there!

If you’ve been following this weblog for a while, you know that such a project was started quite some time ago, based on Eckhart Köppen’s work, and that it hit some snags along the way.

Well, the good news is that all the major bugs have been fixed!

You can now install a USB-enabled version of Mecrisp Forth on a number of different F103-based boards, and it’s quite easy to adapt the code for new boards. The differences are only about how USB enumeration is triggered: some boards needs a specific GPIO pin to be pulled high or low, before USB can be used. Other boards have a fixed 1.5 kΩ pull-up resistor on board and need a different trick in the code.

You can find all the pre-compiled USB images in the suf/ directory of Embello on GitHub - where “suf” stands for “Serial USB Forth”. On many boards, usb-common.hex should work.

The result is very effective: just plug in the board and a serial USB device will appear, to which you can connect using Folie or picocom. This should work on Windows, MacOS, Linux, etc.

There are a few differences to be aware of when using the USB-enabled builds:

  • eraseflash has been redefined to not erase the USB driver itself
  • a reset (software and hardware) will cause the board to disconnect and re-connect
  • output will eventually be discarded if nothing is listening on the host side
  • the USB driver needs an extra 6 KB of flash memory, on top of Mecrisp’s 20 KB
  • the serial port is no longer initialised on startup
  • the welcome message is not shown after a reset or reconnect

Note that when SerPlus is not in the loop, you need to start Folie with the -r (raw) flag.

The connection speed is considerably higher than serial, but what is even more important is that the driver implements bi-directional throttling (i.e. back-pressure): you can send a large Forth source file to the board, and it won’t overrun the interpreter loop. Similarly, you can send output back at maximum speed without losing a single byte.

A quick test, sending some 65 KB of Forth source code to a Blue Pill took 8 seconds. That’s 8 KB/sec, and around 250 lines/second. It may not sound like much, but it’s considerably faster than the ok-prompt-throttled rate you will get with a 115,200 baud USART-based connection.

And as will be described in the next article, Mecrisp Forth does some quite amazing things while it’s reading in all those source lines…

Performance and I/O toggling

$
0
0

With all those source lines being sent to Mecrisp, it’s time to look a little more under the hood.

Let’s start by pointing out that this article focuses on an STM32F103 µC running at 72 MHz, which is an ARM Cortex M3. Performance figures for the JeeNode Zero’s STM32L052 µC will differ, because it runs at a lower clock speed and because it’s a “simpler” ARM Cortex M0+.

Many Forth implementations use a “threaded interpreter” design, but Mecrisp is different in that it compiles Forth directly to ARM machine code. The code in embello includes a “micros” word, which returns the time of the systick hardware counter as microseconds. We can use this to measure the duration of operations - here’s how to find out how long “nop” takes:

: a micros nop micros swap - . ; a 1  ok.

So that was quick: < 1 µs! - and not so surprising, considering that the clock runs at 72 MHz.

Let’s go through the above code, just so that the logic is made very clear:

  • : a” - starts the definition of a word called “a”
  • micros” - pushes the current µs counter value on the stack
  • nop” - does nothing, even though it takes a clock cycle to do so
  • micros” - pushes the current µs counter value on the stack
  • swap” - reverses the top two items on the stack
  • -” - replaces the top two items on the stack with their difference
  • .” (yes, that’s a single period) - pops the top value and prints it
  • ;” - ends the definition of “a”
  • a” - calls a
  • 1” - is printed by Forth, i.e. the output from calling “.
  • ok.” - tells us Forth is ready for the next command

It’s very important to compile the code into a definition and then run it, otherwise we’ll be measuring the lookup and compilation time. Interesting, but not what we’re after here:

micros nop micros swap - . 2483  ok.

Now we can have a look at raw performance. Let’s run an empty loop a million times:

: a micros 1000000 0 do loop micros swap - . ; a 83386  ok.

That’s 83.4 ns per loop iteration, i.e. 12 MHz. Since the system clock is 72 MHz, we can deduce that Mecrisp compiles the empty loop to something which takes 6 clock cycles per iteration. It generated this code in just a few milliseconds, by the way - i.e. unnoticeble in interactive use.

More interesting, is how fast we can toggle an I/O pin. Let’s take PC13, the LED on a Blue Pill:

: a micros 1000000 0 do pc13 iox! loop micros swap - . ; a 236240  ok.

That’s 236 ns per iteration, so the pin toggles over 4 million times per second. If we discount for the 6-cycle loop overhead, the pin toggling itself takes 153 ns.

Actually, iox! is still a bit slower than just setting or clearing an I/O pin:

: a micros 1000000 0 do pc13 ios! loop micros swap - . ; a 166752  ok.

Again, without the loop overhead, setting a pin takes a mere 83 nanoseconds. Not too shabby for a self-contained µC board with an interactive prompt - all implemented in 20 KB of code!

This super performance is only possible because Matthias Koch used his expertise to help the compiler perform optimally on io@, ios!, ioc!, and iox! - so now everyone benefits from it.

Now let’s dive in deeper… a lot deeper. There’s a disassembler (written in Forth, what else?), which we can load into flash memory with the following commands (it uses about 6 KB):

compiletoflash
!s ../flib/mecrisp/disassembler-m3.fs
compiletoram

With this, we can examine the actual machine code generated for “a” by Mecrisp’s compiler:

: a micros nop micros swap - . ;  ok.
see a
0000CDE0: B500  push { lr }
0000CDE2: F7FA  bl  00006E12  --> micros
0000CDE4: F816
0000CDE6: F7F7  bl  00004762  --> nop
0000CDE8: FCBC
0000CDEA: F7FA  bl  00006E12  --> micros
0000CDEC: F812
0000CDEE: CF08  ldmia r7 { r3 }
0000CDF0: 1AF6  subs r6 r6 r3
0000CDF2: F7F7  bl  0000434E  --> .
0000CDF4: FAAC
0000CDF6: BD00  pop { pc }

That’s probably a bit too much assembler code to take in, so let’s start a bit simpler:

: a ( n -- n ) 17 + ;  ok.
see a
20000454: 3611  adds r6 #11
20000456: 4770  bx lr

This word takes the top of the stack and adds 17 to it. It gets compiled into a routine with just two machine instructions (the stack top is always in register 6): add 17 (hex 11) and return.

Things are about to get a bit more interesting:

: b ( n -- n ) 7 + 10 + ;  ok.
see b
20000470: 3607  adds r6 #7
20000472: 360A  adds r6 #A
20000474: 4770  bx lr

No surprises yet: we first added 7 and then we added 10 (hex A) more. Now watch this:

: c ( n -- n ) 7 10 + + ;  ok.
see c
20000498: 3611  adds r6 #11
2000049A: 4770  bx lr

Mecrisp does constant folding: the compiler can keep track of the topmost few items on the stack, and decide to optimise things if they are constants! (calculating 7+10 at compile time)

To show some more code generation at work, here are three equivalent definitions:

: d ( n -- n ) 2 * ;  ok.
see d
200004B4: 2302  movs r3 #2
200004B6: 435E  muls r6 r3
200004B8: 4770  bx lr

: e ( n -- n ) 2* ;  ok.
see e
200004D0: 0076  lsls r6 r6 #1
200004D2: 4770  bx lr

: f ( n -- n ) dup + ;  ok.
see f
200004E4: 19B6  adds r6 r6 r6
200004E6: 4770  bx lr

To see how constant folding actually works, we can look at the definition of bit, which takes a number on the stack and returns an int with just that bit set:

: bit ( u -- u ) 1 swap lshift  1-foldable ;
see bit
000058DE: 2301  movs r3 #1
000058E0: 40B3  lsls r3 r6
000058E2: 461E  mov r6 r3
000058E4: 4770  bx lr

The special trick here is that we’ve defined this word as “1-foldable”, meaning: if the topmost 1 elements (i.e. just the top) of the stack is known to be a constant, then instead of compiling the code, Mecrisp will execute that code on the constant value right away, and use the resulting value instead. It’s optimising away the operation, helped by the unique property that run-time and compile-time contexts are very similar in Forth. Here is an example of using bit:

: g ( u -- u ) 3 bit and ;  ok.
see g
0000CEF8: F016  ands r6 r6 #8
0000CEFA: 0608
0000CEFC: 4770  bx lr

This really only scratches the surface of what Mecrisp is doing under the hood. Here are a few more examples - you’ll have to know something about ARM machine code and the assembly mnemonics, but as you can see, it’s all very compact:

: aa ( -- ) 10 0 do loop ;  ok.
see aa
0000CE8E: B500  push { lr }
0000CE90: F847  str r6 [ r7 #-4 ]!
0000CE92: 6D04
0000CE94: B430  push { r4  r5 }
0000CE96: 2400  movs r4 #0
0000CE98: 250A  movs r5 #A
0000CE9A: CF40  ldmia r7 { r6 }
0000CE9C: 3401  adds r4 #1
0000CE9E: 42AC  cmp r4 r5
0000CEA0: D1FC  bne 0000CE9C
0000CEA2: BC30  pop { r4  r5 }
0000CEA4: BD00  pop { pc }

: bb ( n -- n ) if 2 else 3 then ;  ok.
see bb
0000CEB2: B500  push { lr }
0000CEB4: 2E00  cmp r6 #0
0000CEB6: CF40  ldmia r7 { r6 }
0000CEB8: D003  beq 0000CEC2
0000CEBA: F847  str r6 [ r7 #-4 ]!
0000CEBC: 6D04
0000CEBE: 2602  movs r6 #2
0000CEC0: E002  b 0000CEC8
0000CEC2: F847  str r6 [ r7 #-4 ]!
0000CEC4: 6D04
0000CEC6: 2603  movs r6 #3
0000CEC8: BD00  pop { pc }

: cc ( -- ) 2 3 * 4 + drop ;  ok.
see cc
0000CED6: 4770  bx lr

: dd ( -- ) 2 3 * 4 + . ;  ok.
see dd
0000CEE2: F847  str r6 [ r7 #-4 ]!
0000CEE4: 6D04
0000CEE6: 260A  movs r6 #A
0000CEE8: B500  push { lr }
0000CEEA: F7F7  bl  0000434E  --> .
0000CEEC: FA30
0000CEEE: BD00  pop { pc }

It may seem overdone to have Mecrisp generate machine code and do all the optimisation on the fly, but when the lower levels are so performant, it becomes feasible to construct an entire application on top. Even if deeply nested and layered as many tiny “words” calling each other.

Which is a good thing in ultra low-power designs: the sooner the µC is done with its periodic tasks, the sooner it can return to its sub-µA sleep mode.

The best of both worlds: compiler-grade performance, with interpreter-style instant coding!


A few design changes in rev4

$
0
0

There are not really that many changes from rev3 to rev4 of the JeeNode Zero. The main one was to extend the main header by one more pin, and to move the radio module to other pins so that all of PA0..PA7 are now free to use - these have the most fexibility for general use (up to eight 1 Msps 12-bit ADC’s!), and also bring out the DAC (D-to-A converter), which is on PA4.

The schematic for rev4 is as follows, the final design files will be added to the wiki later:

The RFM69 radio is now connected to the following alternate SPI pins:

PA14 = NSEL
PB3  = SCLK
PB4  = MISO
PB5  = MOSI

Two of the DIO pins are also permanently connected to dedicated µC pins:

DIO0 = PB0
DIO1 = PB1

The DIO2/DIO3/DIO5 pins are available on the 5-pin “aux” header, as well as PC14 and PC15, from the microcontroller. This allows manually connecting two out of three to the PC14/PC15 pins, just next to them, but you could also wire them up to the main header with short wires.

The radio’s RESET pin is no longer connected, a wire could be attached directly to the module.

PB6 and PB7 - which support I2C - have been flipped, so that the main header now has a more common “GND +3.3V SCL SDA” order, for directly attaching some matching breakout boards.

Two 10 kΩ pull-ups are now permanently connected to PB6 and PB7 for I2C bus use.

By maxing out all the I/O pins, you can assign up to 18 pins of this board to your own projects: 14 on the main header, 2 on the aux header, and 2 on the FTDI header. Those last two are only available if you don’t need to use serial communications, of course.

The board dimensions have changed slightly - rev4 been lengthened a bit, with the FTDI header moved 0.1” outward so it no longer sits between the main and aux header, but just left of them.

There’s a new connector for I2C + power, which will be used to connect future small boards to the JeeNode Zero. The layout is compatible with the four centre pins of existing I2C-based “JeePlugs”, so one thing you could definitely do is wire ‘em up (and even daisy-chain a few).

Apart from the slightly different pin choices for the RFM69 (or 2.4 GHz RFM7x) module, rev4 should be fully compatible with the software that’s already been written for rev3.

Coming next: the adjusted JeeNode Zero board layout and silkscreen…

Making the silkscreen useful

$
0
0

The JeeNode Zero takes a different approach from most other boards: components are placed on what is considered the bottom of the PCB, with the top free for extensive silk screen labels.

Here is the bottom side of the JeeNode Zero rev4:

And here is the top, with as much information squeezed in as possible:

(both of these images were rendered by the excellent open-source pcb-stackup utility)

As you can see, most of the information needed to use this board in a project is clearly labeled and documented on the PCB, so you won’t have to search for additional docs in many cases.

The top of the board is also where a new side-sliding coin cell holder can be mounted, as well as well as the location for the new “µPlug” connector pads.

The LED is a special one, mounted on the bottom but pointing sideways, with a small cutout in the PCB to try and make the LED visible from the top as well.

Also new in this revision is provision for an optional “UFL” miniature antenna connector, to allow using something other than a 𝛌/4 wire antenna with the JNZ. Note that these connectors are usually rated for only up to five insertions before they break or lose their springiness.

There was not enough room left on this board for mounting holes, but as a compromise, small quarter-hole circular cutouts have been added to the FTDI side of the board, so that you can at least lock the board in place with a few stand-offs and M3 screws+nuts. The other way to fasten this board, is to mount it upside down and treat it as a large through-hole component.

A board made for tinkering

$
0
0

“Sans paroles” - instead of a thousand words, this picture:

Analog over wireless

$
0
0

Let’s put the JeeNode Zero to work a bit, i.e. let’s repeatedly measure an analog voltage through its ADC, send the results over to another node via the radio, and display the received readings.

We’re going to need a test setup with two wireless nodes. Something like this, for example:

The top unit is an F103-based Blue Pill with an RFM69 on top, and a SerPlus on the left for programming (it can also be used directly over USB when a USB-enabled Mecrisp is installed). The bottom unit is an L052-based JeeNode Zero, with a SerPlus on the left as USB interface.

We’ll use the JNZ as sensor/transmitter and the F103 as receiver.

This example assumes that everything is properly connected, and that suitable always.fs, board.fs, and core.fs sources have been installed on both nodes, including the RF69 driver.

The first step is to try out the ADC, by reading the voltage level on PA0 a few times:

$ folie
Folie v2.10
Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem3430DC31
  3: /dev/cu.usbmodemC92AED31
? 2
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodem3430DC31]
Mecrisp-Stellaris RA 2.3.3 with M0 core for STM32L053C8 by Matthias Koch
64 KB <jz3> 3238022C ram/flash: 4920 19200 free ok.
adc-init  ok.
pa0 adc . 0  ok.
pa0 adc . 15  ok.
pa0 adc . 744  ok.

Ok, that seems to work (touching the PA0 pin helps produce a varying signal).

Next step is to do this repeatedly in a loop:

: forever begin cr pa0 adc . 1000 ms again ; forever
274
10
13
!reset
Mecrisp-Stellaris RA 2.3.3 with M0 core for STM32L053C8 by Matthias Koch
64 KB <jz3> 3238022C ram/flash: 4920 19200 free ok.
  ok.

Ok, that works. Note that we had to force a hardware reset to get the prompt back, since the JNZ was placed in an infinite loop. This is the easiest approach and good enough for now.

Now we’ll send it via the RF69 driver, using its default 868.3 MHz and net group 42 settings:

adc-init rf-init  ok.
: forever begin pa0 adc rf-txtest 1000 ms again ; forever

This uses rf-txtest, which takes one value off the stack, converts it to a text string, and sends that as message in an RF packet.

Note that we have to re-initialise the ADC, since we’ve reset the µC and therefore also its ADC.

Hmm - maybe it’s working, but how can we tell? Well, by keeping this running and connecting a second terminal to the F103. The rf-listen command is a quick way to see incoming data:

$ folie
Folie v2.10
? Select the serial port:
  1: /dev/cu.Bluetooth-Incoming-Port
  2: /dev/cu.usbmodem3430DC31
  3: /dev/cu.usbmodemC92AED31
? 3
Enter '!help' for additional help, or ctrl-d to quit.
[connected to /dev/cu.usbmodemC92AED31]
Mecrisp-Stellaris RA 2.3.3 for STM32F103 by Matthias Koch
64 KB <g6s> 32212433 ram/flash: 17688 20480 free ok.
  ok.
rf-listen
RF69 21EB2AB601FE1E803D02 3139
RF69 21EB2AB401FD0E803D01 30
RF69 21EB2AB701FD56803D03 313137
RF69 21EB2AB701FDA4803D03 313733
 ok.
  ok.

As you can see, there are many $30..$39 hex bytes in the received data payload (the first block of hex info is metadata, as described in this article). These are the “0”..“9” ASCII digits, in hex.

To turn this information into more meaningful values, we need to decode this data, i.e. make assumptions about the packet format and interpret it as ASCII-encoded digits in this case.

We can’t use the rf-listen code for this, so we’ll need to implement something similar instead. This is fairly easy to do, starting from this original code on GitHub:

: rf-listen ( -- )  \ init RFM69 and report incoming packets until key press
  rf-init cr
  0 rf.last !
  begin
    rf-recv ?dup if
      ." RF69 " rf-info
      dup 0 do
        rf.buf i + c@ h.2
        i 1 = if 2- h.2 space then
      loop  cr
    then
  key? until ;

Here is a modified version which will print the packet as string:

: rxtest ( -- )
  rf-init
  begin
    rf-recv ?dup if
      cr  rf.buf 2+  swap 2-  type
    then
  again ;

This calls rf-recv in an infinite loop. Whenever the result (the number of bytes received) is not zero, we set up a call to type with two arguments: the address to start printing as string and its length. But because the first two bytes of the received data contain a header we don’t care about, we need to skip them by adding 2 to the address and subtracting 2 from the length.

If we save this definition to a file called rxtest.fs, we can then load and use it as follows:

!s rxtest.fs
rxtest
0
30
116
85
12

That’s it for an example of remote sensing - the JeeNode Zero is taking ADC measurements once a second and sending this data wirelessly to another F103-based node, which then prints out all received results. There’s a lot which could be improved upon, but hey - it’s a start!

Sending multiple values

$
0
0

The previous article showed how to send a value wirelessly from one node to another, as text.

While it’s a good example to start off with due to the simplicity, this doesn’t really scale. It’s tedious to build up text packets, they lead to larger packet payloads, and this all adds to the complexity on both ends. It’s better to send binary data, using an encoding which compresses small values a little (which also happens with ASCII: small integers take fewer bytes).

Also, we really need to be able to send multiple values per packet, not just one.

Sending individual bytes would be easy: just store them one after another in a buffer and send the buffer as payload. But not all values are in the range -128..+127 (or 0..255 if unsigned). And using 4 bytes per value is a bit wasteful for values which do happen to be relatively small, even though this could transfer values from -2,197,815,296 .. +2,197,815,295.

There are a few ways around it. One approach been used in various projects at JeeLabs is “varint-coding” - a loosely defined term which means that you store a variable number of bytes for each value, depending on its magnitude.

The varint format was described a while ago on the weblog, see this article. Unfortunately, it suffers from being limited to non-negative integers. Not so great when sending temperatures.

A slightly extended version of varints has now been implemented in Forth, which adds support for negative values and is nevertheless quite easy to encode and decode in other programming languages. See the new Varint documentation page for details.

This provides us with a tool to send multiple values, so let’s extend the previous example to send four ADC readings every second, measured on pins PA0..PA3.

Let’s start off with a quick interactive check that the ADC works:

adc-init  ok.
PA0 adc . 11  ok.
PA1 adc . 641  ok.
PA2 adc . 640  ok.
PA3 adc . 677  ok.

The varint package convention is that the first value is a “packet type ID” - this lets us verify that we’re decoding the proper data, and allows us to deal with different packet formats later.

The first task is to make sure that we have the varint package loaded - and while we’re at it, let’s store that code in flash memory (the usual approach would be to add this to core.fs):

compiletoflash  ok.
!s ../flib/any/varint.fs
compiletoram  ok.

If you get a bunch of “Redefine” warnings, then varint was already present. That’s fine.

Now we can “assemble” a packet. Let’s use ID 6. To try this out, we need to have rf-listen running on the receiving F103 node. Then we can enter the following on the sending JNZ:

adc-init rf-init
6 <pkt  pa0 adc +pkt pa1 adc +pkt pa2 adc +pkt pa3 adc +pkt  pkt>rf

The result of repeating this a few times shows up as follows on the F103:

rf-listen
RF69 21EB2AB701FDE2803D08 8C900A8C0A9C0BB0
RF69 21EB2ABB01FD26803D08 8C800A920AA00BB4
RF69 21EB2AB901FEC2803D08 8C800A920A9A0BAE

Only 8 bytes of payload, but nearly undecypherable. We’re going to have to decode this stuff. Luckily, that’s trivial - all we need to do is replace type with var. from the varint package:

: rxtestv ( -- )
  rf-init
  begin
    rf-recv ?dup if
      cr  rf.buf 2+  swap 2-  var.
    then
  again ;

As before, we can save this to file (”rxtestv.fs”) and start up the receiver as follows:

!s rxtestv.fs
rxtestv

Then, after sending a few more packets from the JeeNode Zero, we’ll get something like:

6 8 641 654 742
6 57 652 660 716
6 0 646 668 732

As expected: a packet format ID of 6, and then 4 values from the ADC channels. Success!

Viewing all 296 articles
Browse latest View live