ANNEX32 WI-FI RDS

User Manual

Version beta 1.60.3

© ciccioCB 2024


 

 

COPYRIGHT

 

The Annex firmware, including the AnnexToolKit and this manual, are Copyright 2017-2024 by Francesco Ceccarella (ciccioCB).

 

The compiled object code (the .bin file) for the Annex firmware is free software: you can use or redistribute it as you please except for commercial purposes. It is not allowed to distribute or embed it into products that are sold or for any other activity making or intended to make a profit.

 

The compiled object code (the .exe file) for the AnnexToolKit utility is free software: you can use or redistribute it as you please except for commercial purposes. It is not allowed to distribute or embed it into products that are sold or for any other activity making or intended to make a profit.

 

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even

the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 

This manual is distributed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 France license (CC BY-NC-SA 3.0)

 

The above copyright notice and this permission notice shall be included in all copies or redistributions of the Software in any form.

License and credits

The base of the interpreter comes from the original project "MiniBasic" by Malcom Mclean.

Adafruit BNO055 Orientation Sensor library is written by KTOWN is Copyright © Adafruit Industries. It is released under MIT license.

TFT_eSPI display Library is Copyright ©  2017 Bodmer. It is released under FreeBSD license.

Adafruit PWM Servo Driver Library is Copyright © Adafruit. It is released under MIT license.

Arduino Library for Dallas Temperature ICs is Copyright © Miles Burton <miles@mnetcs.com>. It is released under LGPL license.

OneWire Library is Copyright 1999-2006 Dallas Semiconductor Corporation and Copyright © 2007, Jim Studt.

Adafruit DHT Humidity & Temperature Sensor Library is Copyright © Adafruit. It is released under MIT license.

ESP8266 and ESP32 Oled Driver for SSD1306 display is Copyright © 2016 by Daniel Eichhorn and Copyright © 2016 by Fabrice Weinberg

NeoPixelBus library is Copyright © Michael C. Miller.  It is released under LGPL license.

ESP AsyncTCP library is Copyright © 2016 Hristo Gochkov. It is released under LGPL license.

ESP AsyncWebServer library is Copyright © 2016 Hristo Gochkov. It is released under LGPL license.

IRremote library is Copyright © Sebastien Warin, Mark Szabo, Ken Shirriff, David Conran. It is released under LGPL license.

uRTCLib is is Copyright © 2015 Naguissa (naguissa.com@gmail.com). It is released under LGPL license.

BME280 library is written by Limor Fried/Ladyada for Adafruit Industries. It is released under BSD license,

APDS9960 library is written by Shawn Hymel for Sparkfun Electronics. It is released under Beerware license.

PID Library is written by Brett Beauregard (br3ttb@gmail.com). It  is released under MIT license.

The Javascript Editor  EditArea is  Copyright © 2008 Christophe Dolivet. It is released under BSD license.

The M5Stack library is copyright © 2017 by M5Stack. It is released under MIT license.

The MPU9250 driver is part of the M5Stack Library.

The VL53L0X driver is Copyright © 2017 Pololu. It contains code © 2016 STMicroelectronics.

Some GUI objects come from the library GUIslice Copyright © Calvin Hass that is released under MIT license.

The MFRD522 library is written by Miguel Balboa and is released as free and unencumbered software released into the public domain.

 

 

 

 

 

Contributions

A very big thank you to Robin Baker (Electroguard) for his great involvement in the project by supporting all the tests on the real hardware (bought with his money), and all the advices that allowed me to add a lot of functionality, not to mention the Huge work he did while documenting the project on the website.


 

 

Content :

Introduction: 31

Interpreter: 34

Branch labels. 34

Variables: 35

Arrays: 36

OPTION.BASE 1. 36

LBOUND(array() [, dimension]) : Returns the lower bound of the specified array dimension. 37

UBOUND(array() [, dimension]) : Returns the upper bound of the specified array dimension. 37

Scope of the variables: 38

Bases of the language. 39

OPERATORS AND PRECEDENCE.. 39

Basic internal keywords: 41

IF command : 41

FOR loop. 42

WHILE WEND loop. 43

DO LOOP loop. 44

SELECT CASE.. 45

GOTO.. 46

GOSUB.. 46

DATA.. 47

END.. 48

EXIT. 48

SUB.. 48

Logical / boolean Operations. 50

ERRORS HANDLING.. 51

ONERROR ABORT. 52

ONERROR IGNORE. 52

ONERROR SKIP [nn]. 52

ONERROR CLEAR. 52

ONERROR GOTO [label | OFF]. 52

BAS.ERRLINE. 52

BAS.ERRNUM. 52

BAS.ERRMSG$. 52

HOW the interpreter works with the HTML code and Objects : 53

HTML Objects. 57

TIMERS.. 62

EVENTS.. 62

Button Event 63

OnHtmlChange Event 63

OnHtmlReloadEvent 63

OnInfrared Event 64

OnSerial Event 64

OnSerial2 Event 64

OnTouch Event 65

OnUDP Event 65

OnWgetAsync Event 65

OnUrlMessage Event 66

OnEspNowMsg Event 69

OnEspNowError Event 70

OnMQTT Event 70

OnPlay Event 70

WiFI CONNECTIONS.. 70

PROGRAM AUTORUN.. 72

RECOVERY MODE.. 73

SLEEP mode (low energy) and RTC memory. 73

DATE - TIME timekeeper 74

Unix Time functions. 75

FAT32 File System.. 75

FILE.COPY(filename$, newfile$). 77

FILE.DELETE(filename$). 77

FILE.EXISTS(filename$). 77

FILE.RENAME(oldname$, newname$). 77

FILE.SIZE(filename$). 77

FILE.MKDIR(dirname$). 77

FILE.RMDIR(dirname$). 77

FILE.DIR$(path$). 77

FILE.READ$(filename$, [line_num] | [start, length]). 77

FILE.APPEND filename$, content$. 77

FILE.SAVE filename$, content$. 78

FILE.WRITE filename$, content$. 78

FILE.FROMBASE64 source$, dest$. 78

FILE.TOBASE64 source$, dest$. 78

FILE.SAVE_IOBUFF. 78

FILE.WRITE_IOBUFF. 78

FILE.APPEND_IOBUFF. 78

FILE.READ_IOBUFF. 78

Download files from another module or WEB server 79

FILE.DOWNLOAD url$, file_path$. 79

Notes: 79

I/O BUFFERS.. 80

Read Operations. 82

Write Operations. 82

Special operations. 83

Advanced operations. 83

Bit operations. 84

Buffer copy. 84

Code examples : 84

WIRING.. 87

DIGITAL I/O.. 88

PIN.STRENGTH 15, 2. 89

PIN SERIAL SHIFTING.. 89

PIN.SHIFTOUT pin_data, pin_clk, data [, bit_order] [, nb_bits] [, delay_us]. 90

PIN.SHIFTIN( pin_data, pin_clk [, bit_order] [, nb_bits] [, delay_us] ). 91

PIN INTERRUPTS.. 91

Analog inputs. 93

TOUCH inputs. 93

Analog outputs. 93

Hardware interfaces: 94

PWM.. 94

PWM.SETUP pin, channel, default_value,  [,frequency] [,resolution]. 95

PWM.OUT channel, value. 95

SERVO.. 95

I2S BUS.. 97

SPEAKER OUTPUT. 99

I2C BUS.. 100

PCF8574 Module. 101

ADS1115 Module. 102

MCP23017 Module. 105

SPI BUS.. 106

74HC595 Module. 109

MCP23S17 Module. 110

CAN BUS.. 112

CAN.SETUP.. 113

CAN.INIT. 114

CAN.STOP.. 114

CAN.WRITE.. 114

CAN.WRITE_IOBUFF. 115

ONCANBUS.. 115

CANBUS BUFFERS.. 117

RMT Module. 119

Key Features. 119

Memory Management and Synchronisation of RMT Channels. 120

Clock Divider. 120

RMT RAM Composition. 121

Memory Block Extension. 122

RMT Transmit Synchronisation. 123

TX Transmitter Mode. 123

RX Reception Mode. 124

RX Reception Event 124

RMT Command Functions. 124

RMT.SETUP_TX channel, pin [, clk_div] [, mem_block_num] [, idle_level] [, loop_en] [, carrier_en] [, carrier_freq_hz] [, carrier_duty_percent] [, carrier_level]. 124

RMT.WRITE channel, num_items, name [, wait]. 125

RMT.ENCODE channel, num_items, array() [, wait]. 125

RMT.SETUP_RX channel, pin [, clk_div] [, mem_block_num] [, invert] [, filter_en] [, filter_ticks_thresh] [, idle_threshold] [, rm_carrier] [, carrier_freq_hz] [, carrier_duty_percent] [, carrier_level]. 125

RMT.READ array(). 126

RMT.DECODE array(). 126

RMT.ADD_GROUP channel. 126

RMT.DEL_GROUP channel. 126

RMT.END channel. 127

Synchronisation in Groups. 127

Example 1: RMT Transmission of a Pulse Sequence. 127

Example 2: RMT Transmission of a Sinusoidal Pattern. 129

Example 3: Synchronized RMT Transmission of Sinusoidal Patterns. 131

Example 4: Infrared Signal Reception and Decoding with RMT on ESP32. 133

COUNTERS.. 135

PID controllers. 136

SOUND PLAYER.. 138

Metadata Decoding from Mp3 and streaming. 140

SPEECH SYNTHESIS with vintage C64 SAM speaker 141

SPEECH SYNTHESIS using google translate. 142

SPEECH SYNTHESIS using voiceRSS free service. 143

VS1053B Audio Decoder 144

VS1053.SETUP XCS_pin, XDCS_pin, DREQ_pin [,info_enabled] [,SPIfreq] [SCI_CLOCKF]. 146

VS1053.PLAY file$. 146

VS1053.STREAM streaming_url$. 147

VS1053.VOICE "message", "language". 147

VS1053.STOP. 147

VS1053.VOLUME vol. 147

VS1053.RESET. 147

VS1053.INIT patchFile$. 148

VS1053.INIT "/patches/vs1053b-patches-flac.cmd". 148

VS1053.WRITE register, value. 148

VS1053.READ register. 148

VS1053.MIDI_CMD cmd, data1, data2. 148

VS1053.NOTE_ON channel, note, velocity. 148

VS1053.NOTE_OFF channel, note, velocity. 148

LCD DISPLAY USING I2C.. 148

OLED DISPLAY.. 153

ST7920 LCD DISPLAY.. 155

RTC module. 157

PCA9685 (PWM / Servo) Module. 159

TM1637 display module. 160

TM1638 display module. 162

MAX7219 8-Digits 7-segment display. 164

MAX7219 Dot Matrix Display. 165

NeoPixel WS2812B led strips. 167

NEO.SETUP pin, [nb_led]. 169

NEO.STRIP led_start_pos, led_end_pos, R, G, B [, disable]. 169

NEO.STRIP led_start_pos, led_end_pos, COLOR [, disable]. 169

NEO.PIXEL led_pos, R, G, B [, disable]. 169

NEO.PIXEL led_pos, COLOR [, disable]. 169

NEO.RGB(R, G, B). 169

NEO.GETPIXEL(led_pos). 169

NEO.ROTATELEFT num_steps, [led_end_pos, led_end_pos, disable]. 169

NEO.ROTATERIGHT num_steps, [led_end_pos, led_end_pos, disable]. 169

NEO.SHIFTLEFT num_steps, [led_end_pos, led_end_pos, disable]. 169

NEO.SHIFTRIGHT num_steps, [led_end_pos, led_end_pos, disable]. 170

NEO.REFRESH. 170

NEO.DIM(COLOR , Gain). 170

NEO.LIGHTEN(COLOR , Gain). 170

NEO.DARKEN(COLOR , Gain). 170

NEO.LINEARBLEND(COLOR1, COLOR2, progress). 170

NEO.BILINEARBLEND(. 170

Upper_Left_COLOR, Upper_Right_COLOR, Lower_Left_COLOR, Lower_Right_COLOR, x, y). 170

NeoPixel based WS2812b Dot Matrix DIsplay. 171

NEOSCROLL.SETUP nb_devices, nb_lines, pin [,layout] [,width, height, orientation]. 175

NEOSCROLL.DELETE. 175

NEOSCROLL.FILL color, [x, y, width, height]. 175

NEOSCROLL.TEXT.POS x, y. 175

NEOSCROLL.TEXT.FONT font. 175

NEOSCROLL.SHOW x, y. 175

NEOSCROLL.TEXT.BRIGHTNESS brightness. 175

NEOSCROLL.BRIGHTNESS brightness. 175

NEOSCROLL.PRINT text$, color$. 176

NEOSCROLL.SPRITESHEET image$. 176

NEOSCROLL.SPRITE x, y, width, height, x_in_bmp, y_in_bmp. 177

Copy a portion of the SPRITESHEET image into the canvas using the parameters given. 177

NEOSCROLL.LIMITS [x1,] [x2], [y1], [y2]. 177

NEOSCROLL.SYNC. 177

NEOSCROLL.MODE mode. 177

NEOSCROLL.SCROLL. 177

NEOSCROLL.SCROLL. 177

Scroll the image using the current MODE and within the current LIMITS.c. 177

NEOSCROLL.OSCILLATE. 177

NEOSCROLL.OSCILLATE. 177

Oscillate the image using the current MODE and within the current LIMITS.c. 177

NEOSCROLL.X. 177

NEOSCROLL.Y. 177

HUB75 Matrix Displays - DMAMATRIX.. 181

DMAMATRIX.INIT R1, G1, B1, R2, G2, B2, A, B, C, D, E, LAT, OE, CLK [,freq_DMA] [,resolution]. 182

DMAMATRIX.SETUP nb_devices, nb_lines [,layout] [,width, height, orientation]. 182

DMAMATRIX.DELETE. 182

DMAMATRIX.FILL color, [x, y, width, height]. 182

DMAMATRIX.TEXT.POS x, y. 182

DMAMATRIX.TEXT.FONT font. 183

DMAMATRIX.TEXT.COLOR color. 183

DMAMATRIX.SHOW [x, y]. 183

DMAMATRIX.TEXT.BRIGHTNESS brightness. 183

DMAMATRIX.BRIGHTNESS brightness. 183

DMAMATRIX.PRINT text$ [, color$ | color]. 184

DMAMATRIX.SPRITESHEET image$. 184

DMAMATRIX.SPRITE x, y, width, height, x_in_bmp, y_in_bmp. 185

Copy a portion of the SPRITESHEET image into the canvas using the parameters given. 185

DMAMATRIX.LIMITS [x1,] [x2], [y1], [y2]. 185

DMAMATRIX.SYNC. 185

DMAMATRIX.MODE mode. 185

DMAMATRIX.SCROLL. 185

DMAMATRIX.SCROLL. 185

Scroll the image using the current MODE and within the current LIMITS.c. 185

DMAMATRIX.OSCILLATE. 185

DMAMATRIX.OSCILLATE. 185

Oscillate the image using the current MODE and within the current LIMITS.c. 185

DMAMATRIX.PIXEL x, y, color. 185

DMAMATRIX.LINE x1, y1, x2, y1, color. 185

DMAMATRIX.CIRCLE x, y, radius, color [,fill]. 185

DMAMATRIX.RECT x, y, w, h, color [,fill]. 185

DMAMATRIX.X. 185

DMAMATRIX.Y. 186

DMAMATRIX.POSX. 186

DMAMATRIX.POSY. 186

DMAMATRIX.PLAYGIF gif$ [,x , y]. 186

DMAMATRIX.LOADGIF gif$ [,x , y]. 186

DMAMATRIX.FRAMEGIF [do_not_show [,loop]]. 186

SD CARD ADAPTER.. 186

TFT DISPLAY ILI9341. 188

TFT DISPLAY ILI9163. 192

TFT DISPLAY ILI9486. 193

TFT DISPLAY ILI9481. 196

TFT DISPLAY ILI9488. 197

TFT DISPLAY ST7735. 198

TFT DISPLAY ST7796. 199

TFT DISPLAY ST7789. 202

OLED DISPLAY SSD1351 RGB.. 203

TFT DISPLAY GC9A01. 204

TouchScreen - Resistive. 204

TouchScreen - Capacitive. 205

TFT FONTS.. 206

QR CODES.. 210

GRAPHIC GUI for TFT. 210

GUI Objects. 211

gui.TextLine. 211

gui.Button. 211

gui.Image. 212

gui.ButtonImage. 212

gui.CheckBox. 213

gui.Slider. 214

gui.ProgressBar. 214

gui.Ramp. 214

gui.Gauge. 215

gui.Box. 215

gui.Circle. 216

gui.Rect 216

gui.Line. 217

GUI Functions. 217

gui.GetValue. 217

gui.Target 217

GUI Commands. 217

gui.INIT.. 217

gui.REDRAW... 218

gui.REFRESH.. 218

gui.AUTOREFRESH.. 218

gui.SETVALUE.. 218

gui.SETTEXT.. 218

gui.SETIMAGE.. 218

gui.SETCOLOR.. 218

gui.SETRANGE.. 219

gui.SETEVENT.. 219

gui.SETSTYLE.. 220

LCD RGB DISPLAY INTERFACE (module ESP32-8048S070C) 221

VGA.PINOUT R0, R1, R2, R3, R4, G0, G1, G2, G3, G4, G5, B0, B1, B2, B3, B4, HSYNC, VSYNC, DE, pCLK. 221

VGA DISPLAY INTERFACE.. 221

VGA.PINOUT R0, R1, R2, G0, G1, G2, B0, B1, HSYNC, VSYNC. 224

VGA.PINOUT R0, R1, R2, R3, R4, G0, G1, G2, G3, G4, G5, B0, B1, B2, B3, B4, HSYNC, VSYNC, DE, pCLK. 224

VGA.SETUP hFront,hSync, hBack, hRes, vFront, vSync, vBack, vRes, frequency  [,vClones=1] [,nb_pages=1] [,outSize=1] [,aligned]. 224

VGA.INIT mode [,nb_pages=1]. 224

VGA.DELETE. 224

VGA.STOP. 224

VGA.START. 224

VGA.SHOW. 224

VGA.SHOWPAGE page. 225

VGA.WRITEPAGE page. 225

VGA.SAVE file$. 225

VGA.FILL color. 225

VGA.COPY src, dest [x, y, w, h [dest_x, dest_y]. 225

VGA.PIXEL x, y, color. 225

VGA.LINE x1, y1, x2, y2, color [,thickness=1]. 225

VGA.CIRCLE x, y, radius, color [,fill]. 225

VGA.RECT x, y, w, h, color [,fill]. 225

VGA.TRIANGLE x1, y1, x2, y2, x3, y3, color [,fill=1]. 225

VGA.NEEDLE x, y, length, angle, color [,thickness=1]. 225

VGA.NEXT x, y, length, angle, color [,thickness=1]. 225

VGA.TEXT.POS x, y. 225

VGA.TEXT.FONT font. 226

VGA.TEXT.COLOR color [,background]. 226

VGA.TEXT.SIZE size. 226

VGA.PRINT var [var$]. 226

VGA.TEXT.ALIGN align. 226

VGA.TEXT.PADDING width. 226

VGA.TEXT.DRAW "text", x, y [,font]. 226

VGA.IMAGE file$ [, x, y]. 226

VGA.SPRITESHEET image$. 227

VGA.SPRITE x, y, width, height, x_in_bmp, y_in_bmp. 227

VGA.SETSPRITE id, width, height, x_in_bmp, y_in_bmp [, nextframe_delta_x, nextframe_delta_y]. 227

VGA.DRAWSPRITE id, x, y, [frame=0]. 227

VGA.REMOVESPRITE id, src, dest. 227

VGA.HIDESPRITE id, visibility. 227

VGA GUI (experimental) 227

VGAGUI.SETSPRITE x_in_bmp, y_in_bmp, width, height, nextframe_delta_x, nextframe_delta_y. 229

VGAGUI.SPRITE(x, y, width, height, frame_on, frame_off, [,toggle=0] [,group]). 229

VGAGUI.ARC(x, y, width, height, value [,thickness=10]). 230

VGAGUI.TEXTAREA(x, y, width, height, "text", [,font] [,alignment] [,color_text] [,color_back] [,color_frame] [,margin] ). 231

VGAGUI.IGNORE obj. 231

VGAGUI.GETTEXT obj, var$. 232

VGAGUI.SETVALUE obj, value. 232

VGAGUI.SETTEXT obj, text$. 232

VGAGUI.SETCOLOR object, col1 [,col2 [,col3 [,col4]]]. 232

VGAGUI.SETSTYLE object, prop1 [,prop2 [,prop3 [,prop4]]]. 233

USB HID INTERFACE (Mouse, Keyboard, Gamepad) - ESP32-S3 only. 234

How use the USB devices. 235

INFRARED INTERFACE.. 237

ULTRASONIC DISTANCE SENSOR HC-SR04. 241

DHT xx Temperature / Humidity Sensors. 242

DS18B20 Temperature Sensors. 243

TEMPR$(pin_number, [ID], [resolution]). 244

BNO055 Absolute Orientation Sensor 245

BME280 Combined humidity and pressure sensor 247

BME680 Combined gas, pressure, temperature & humidity sensor 249

BME680.TEMP. 251

BME680.PRESS. 251

BME680.HUM. 251

BME680.GASRES. 251

BME680.STATUS. 251

BME680.AVAIL. 251

BME680.SETSLEEPMODE. 251

BME680.SETFORCEDMODE. 251

BME680.SETOVERSAMPLING(ovs_temp, ovs_press, ovs_hum). 251

BME680.SETIIRFILTER(filter). 252

BME680.SETGASON(temperature, time). 252

BME680.SETGASOFF. 252

HDC1080 High Accuracy Digital Humidity Sensor with Temperature Sensor 253

CCS811 Air Quality Sensor 254

APDS9960 Digital Proximity, Ambient Light, RGB and Gesture Sensor 257

RFID MFRC522 RFID cards reader 260

Writing NUID for UID changeable card (4 byte UID version) 264

VL53L0X TOF (Time Of Flight) Distance Sensor 264

HX711 - Weight Measurement Module. 266

SI5351 Clock Generator Module. 268

SI5351.INIT [capacitor [,crystal]]. 270

SI5351.CALIB correction. 270

SI5351.SETFREQ out_nb, frequency. 270

SI5351.SETFREQ_MAN out_nb, frequency, pll. 270

SI5351.STRENGTH out_nb, strength. 270

SI5351.PHASE out_nb, phase. 270

SI5351.RESET_PLL pll_nb. 270

SI5351.ENABLE out_nb, enable. 270

SI5351.INVERT out_nb, invert. 270

SI5351.LOAD filename$. 271

STEP MOTOR.. 271

STEPPER.SETUP stepper_id, pin_step, pin_dir. 273

STEPPER.SETPARAM stepper_id, speed, acceleration. 273

STEPPER.SETPOSITION stepper_id, position. 273

STEPPER.MOVE stepper_id, position. 273

STEPPER.MOVETO stepper_id, position. 273

STEPPER.STOP stepper_id. 273

STEPPER.FORCESTOP stepper_id. 273

STEPPER.RUNFWD stepper_id. 273

STEPPER.RUNBKD stepper_id. 273

STEPPER.GETPOSITION(stepper_id). 274

STEPPER.GETTARGET(stepper_id). 274

MPU9250. 274

MPU6500  / MPU6050. 276

MPU6886 (For M5 Atom) 278

IMU FUSION FUNCTIONS.. 280

ETHERNET Module W5500. 282

FTP.. 286

BAS.FTP$. 286

Server data requests  (GET, POST and PUT) 287

-      WGET$(server$, port, [,header] [,content_type$]). 288

-      WGET$(url$ [,header] [,content_type$]). 288

-      WPOST$(server$, body$, port [,header] [,content_type$] ). 288

-      WPOST$(url$, body$ [,header] [,content_type$]). 288

-      WPUT$(server$, body$, port [,header] [,content_type$] ). 288

-      WPUT$(url$, body$ [,header] [,content_type$]). 288

-      WGETASYNC[(] server$, port, [,header] [)]. 288

-      WGETASYNC[(] url$,[,header] [)]. 288

MQTT. 290

Ret = MQTT.Setup(server$ [,debug]). 292

Ret = MQTT.Certif(cert_pem$ [,client_cert_pem$] [,client_key_pem$]). 292

Ret = MQTT.PSK(psk_hint_key$). 292

Ret = MQTT.LWT(topic$, message$ [,Qos] [,retain]). 292

Ret = MQTT.Connect(login$, pass$ [,id$]). 292

Ret = MQTT.Connect("", "" [,id$]). 292

Ret = MQTT.Disconnect[()]. 292

Ret = MQTT.Publish(topic$, message$ [,Qos] [,retain]). 292

Ret = MQTT.Subscribe(topic$ [,Qos]). 292

Ret = MQTT.UnSubscribe(topic$). 292

Ret = MQTT.Connected[()]. 292

Ret = MQTT.Status[()]. 293

ESP-NOW... 295

EspNow.Begin. 296

EspNow.Stop. 296

EspNow.Add_Peer(MAC_add$ [,interface] [,channel]). 296

EspNow.Del_Peer. 296

EspNow.Write(msg$). 296

EspNow.Write(msg$, MAC_add$). 296

EspNow.READ$. 297

ESPNow.REMOTE$. 297

ESPNow.ERROR$. 297

OnEspNowMsg label. 297

OnEspNowError label. 297

BLUETOOTH Low Energy (BLE) 306

Overview.. 306

Operating Modes. 306

UUIDs and Characteristics. 307

Communication Details. 307

BLE Commands / Functions. 307

Event Handling. 308

TELEGRAM (messenger) support 311

LORA.. 314

LoRa.Setup ss, reset, dio0. 316

LoRa.Begin(freq). 316

LoRa.End. 316

LoRa.BeginPacket. 316

LoRa.Print. 316

LoRa.EndPacket. 316

LoRa.Receive. 316

LoRa.RSSI. 316

LoRa.SNR. 316

LoRa.Idle. 316

LoRa.Sleep. 316

LoRa.TXpower pow. 316

LoRa.SyncWord word. 317

LoRa.EnableCRC enable. 317

OnLora. 317

LoRa.Message$. 317

Modbus. 319

MODBUS.CONNECT IP$, [port] [,timeout] [,idleTimeout]. 319

MODBUS.DISCONNECT. 320

MODBUS.REQUEST token, serverID, functionCode [,p1] [,p2] [,p3]. 320

Modbus RTU Support 323

MODBUS.SetupRTU RX_pin, TX_pin, RE_DE_pin, ["BBBB,P,D,S"]. 323

MODBUS.requestRTU ….. (see the details in the chapter above) 323

MODBUS RTU wiring. 325

Regular Expressions (RegEx) 326

Patterns. 327

Magic characters. 327

Repetition. 328

Anchor to start and/or end of string. 329

Captures. 329

Frontier patterns. 330

Multiple matches. 330

M5 Tough. 331

M5Tough.BatLevel. 333

M5Tough.BatVoltage. 333

M5Tough.BatCurrent. 333

M5Tough.VinVoltage. 333

M5Tough.VinCurrent. 333

M5Tough.VBusVoltage. 333

M5Tough.VBusCurrent. 333

M5Tough.BatChgCurrent. 333

M5Tough.BatPower. 333

M5Tough.AxpTemp. 333

M5Tough.ApsVoltage. 333

M5Tough.AxpState. 333

M5Tough.TftPower power. 333

M5Tough.SpeakerPower power. 333

M5Tough.SetBusPowerMode mode. 334

M5Tough.PowerOff sec. 334

M5Tough.LightSleep sec. 334

M5Tough.DeepSleep sec. 334

ANNEXCAM.. 334

Functionalities enabled in the ANNEXCAM version. 337

Camera Functions / commands. 338

Using AnnexCam in output page. 341

Control of the camera using URL. 342

Face Recognition. 343

Image / video reception from Annex. 344

ANNEXEPAPER for LILYGO T5 4.7” E-paper module. 346

image......................................................................................................................................... 347

GRAPHIC GUI for E-PAPER.. 349

Functionalities enabled in the E-PAPER version. 356

PEEK and POKE FUNCTIONS.. 358

BAS.PEEK(addr). 358

BAS.PEEK16(addr). 358

BAS.PEEK8(addr). 358

BAS.POKE addr, data. 358

BAS.POKE16 addr, data. 358

BAS.POKE8 addr, data. 358

CONVERSION FUNCTIONS.. 359

CONVERT.DEGC_TO_F(degC). 360

CONVERT.F_TO_DEGC(degF). 360

CONVERT.TO_IEEE754(num). 360

CONVERT.FROM_IEEE754(iee754_bin). 360

CONVERT.MAP(number, fromLow, fromHigh, toLow, toHigh). 360

CONVERT.TO_BCD(number). 360

CONVERT.FROM_BCD(number). 360

CONVERT.LIMITS(number, min, max). 360

BAS CONSTANTS.. 360

BAS.VER. 361

BAS.VER$. 361

BAS.ERRLINE. 361

BAS.ERRNUM. 361

BAS.ERRMSG$. 361

BAS.FILENAME$. 361

BAS.RTCMEM$. 361

BAS.SSID$. 361

BAS.PASSWORD$. 361

BAS.LOAD. 361

BAS.RESETREASON. 362

BAS.WAKEUPREASON. 363

BAS.DEVICE. 364

BAS.TFT. 365

OPTION COMMANDS.. 365

OPTION.BASE 0 | 1. 366

OPTION.CPUFREQ 80|160|240. 366

OPTION.ES8388. 366

OPTION.MAC mac$. 366

OPTION.LOWRAM value. 366

OPTION.NTPSYNC. 366

OPTION.WDT time. 366

OPTION.WDTRESET. 366

OPTION.WLOG value. 366

OPTION.TOUCH value. 367

OPTION.I2S BCLK_pin, WSEL_pin, DOUT_pin. 367

OPTION.PSRAM limit. 367

HALL Sensor (Internal): 367

BAS.HALL. 367

FUNCTIONS: 367

NUMERICAL FUNCTIONS.. 367

ABS(number) 368

ACOS(number) 368

ADC(pin) 368

APDS9960.SETUP (mode) 368

APDS9960.READGESTURE.. 368

APDS9960.AMBIENT. 368

APDS9960.RED.. 368

APDS9960.GREEN.. 368

APDS9960.BLUE.. 369

APDS9960.PROXIMITY.. 369

APDS9960.GESTUREGAIN (gain) 369

APDS9960.GESTURELED (intensity) 369

ASC(string$) 369

ASIN(number) 369

ATAN(number) 369

ATAN2(x, y) 369

BAS.VER.. 369

BAS.ERRLINE.. 370

BAS.ERRNUM.. 370

BME280.SETUP(address) 370

BME280.ALT(qnh) 370

BME280.HUM.. 370

BME280.QFE.. 370

BME280.QNH(altitude) 370

BME280.TEMP.. 370

BNO055.SETUP( address) 370

BNO055.HEADING.. 370

BNO055.PITCH.. 370

BNO055.ROLL. 370

BNO055.VECTOR ( param, axis) 371

BNO055.CALIB [(param)] 371

CINT(number) 372

CONVERT.DEGC_TO_F(degC) 372

CONVERT.F_TO_DEGC(degF) 372

CONVERT.TO_IEEE754(num) 372

CONVERT.FROM_IEEE754(ieee754_bin) 372

CONVERT.MAP(number, fromLow, fromHigh, toLow, toHigh) 372

CONVERT.TO_BCD(number) 372

CONVERT.FROM_BCD(number) 372

COS(number) 372

COUNTER.COUNT (cnt) 372

COUNTER.PERIOD (cnt) 372

DATEUNIX(date$) 372

DHT.TEMP.. 373

DHT.HUM.. 373

DHT.HEATINDEX.. 373

DISTANCE(pin_trig, pin_echo) 373

EMAIL from$, to$, subject$, message$. 373

ESPNOW.ADD_PEER(MAC_add$ [,interface] [,channel]) 373

ESPNOW.BEGIN.. 373

ESPNOW.DEL_PEER(MAC_add$) 373

ESPNOW.STOP.. 373

ESPNOW.WRITE( msg$) 373

ESPNOW.WRITE( msg$,MAC_add$) 373

EXP(number) 373

FIX(number) 374

FILE.DELETE(filename$) 374

FILE.EXISTS(filename$) 374

FILE.SIZE(filename$) 374

FLASHFREE.. 374

FUSION.ANGLE(axis) 374

INSTR([start], string$, pattern$) 374

I2C.LEN.. 374

I2C.READ.. 374

I2C.READREGBYTE (i2c_address, register) 375

I2C.END.. 375

INT(number) 375

LEN(string$) 375

LOG(number) 375

MILLIS.. 375

MQTT.Setup(server$ [,debug]) 375

MQTT.Certif(cert_pem$ [,client_cert_pem$] [,client_key_pem$]) 376

MQTT.PSK(psk_hint_key$) 376

MQTT.LWT(topic$, message$ [,Qos, [,retain]) 376

MQTT.Connect(login$, pass$ [,id$]) 376

MQTT.Connect("", "" [,id$]) 376

MQTT.Disconnect[()] 376

MQTT.Publish(topic$, message$ [,Qos] [,retain]) 376

MQTT.Subscribe(topic$ [,Qos]) 376

MQTT.UnSubscribe(topic$) 376

MQTT.Connected[()] 376

MQTT.Status[()] 376

NEO.GETPIXEL(pos) 377

NEO.RGB(R, G, B) 377

PI 377

PID1.COMPUTE( current_value, target_value) 377

PIN(pin_number) 377

PIN.TOUCH(pin_number) 377

PING(host$) 377

POW(x, y) 377

RAMFREE.. 377

RFID.SETUP(CS_pin, RST_pin) 378

RFID.SETGAIN(gain) 378

RFID.SETKEY(key$) 378

RFID.RESET. 378

RFID.AWAKE.. 378

RFID.SETNUID(NUID$) 378

RFID.WRITE(block, data$) 379

RND(number) 379

SERIAL.LEN.. 379

SERIAL2.LEN.. 379

SGN(number) 379

SIN(number) 379

SPI.BYTE(byte) 379

SQR(number) 379

TAN(number) 379

TFT.RGB(r,g,b) 379

TIMEUNIX(time$) 379

TM1638.BUTTONS.. 380

TOUCH.X.. 380

TOUCH.Y.. 380

VAL(string$) 380

WIFI.CHANNEL. 380

WIFI.MODE.. 380

WIFI.NETWORKS  ( network$ ) 380

WIFI.RSSI 381

WIFI.STATUS.. 381

WORD.COUNT( string$ [,delimiter$]) 381

WORD.FIND( string$, find$ [,delimiter$]) 381

STRING FUNCTIONS.. 382

BAS.ERRMSG$. 383

BAS.FILENAME$. 383

BAS.FTP$( host$, login$, password$, file$, folder$) 383

BAS.PASSWORD$. 383

BAS.RTCMEM$. 383

BAS.SSID$. 383

BAS.VER$. 383

BIN$(number) 383

BUTTON$(name$, label [, id] ) 383

CHECKBOX$( variable [,id]) 383

CHR$(number) 384

CSSID$(object_id, object_style) 384

DATE$[(format)] 384

ESPNOW.ERROR$. 384

ESPNOW.READ$. 384

ESPNOW.REMOTE$. 384

FILE.DIR$[(path$)] 384

FILE.READ$(filename$,[line_num] | [start, length]) 384

HEX$(number) 384

HtmlEventButton$. 384

HtmlEventVar$. 385

IMAGE$(path [,id]) 385

IMAGEBUTTON$(path, label [,id]) 385

IP$. 385

IR.GET$[ (param) ] 385

JSON$(string$, field$) 385

LCASE$(string$) 385

LED$(variable [,id]) 386

LEFT$(string$, num) 386

LISTBOX$(variable$, "option1, option2, option3, ..." [, height]  [,id]) 386

MAC$[ (id) ] 386

METER$(variable, min, max [,id]) 386

MID$(string$, start [,num]) 386

MQTT.Message$. 387

MQTT.Topic$. 387

OCT$(number) 387

PASSWORD$(variable [, id] ) 387

REPLACE$(expression$, find$, replacewith$) 387

RFID.NUID$. 387

RFID.TYPE$. 388

RFID.READ$(block [,key_b]) 388

RIGHT$(string$, num) 388

RTC.DATE$[(format)] 388

RTC.TIME$. 389

SERIAL.CHR$. 389

SERIAL.INPUT$. 389

SERIAL2.CHR$. 389

SERIAL2.INPUT$. 389

SLIDER$(variable, min, max [,step] [,id]) 389

SPACE$(number) 389

SPI.STRING$(data$, len) 389

SPI.HEX$(datahex$, len) 389

STR$ (number [,format$ [,toint]]) 390

STRING$(num, char$) 392

TEMPR$(pin_number [,ID]) 392

TEXTAREA$(variable [, id] ) 393

TEXTBOX$(variable [, id] ) 393

TRIM$(string$) 393

TIME$. 393

UCASE$(string$) 393

UDP.READ$. 393

UDP.REMOTE$. 393

UNIXDATE$(value [,format]) 393

UNIXTIME$(value) 393

URLMSGGET$ ([arg$]) 394

WGET$( http_server$, port [,header] ) 394

WGET$( url$, [,header] ) 394

WGETRESULT$. 394

WORD$(string$, position [,delimiter$]) 394

WORD.DELETE$(string$, position [delimiter$]) 394

WORD.EXTRACT$(string$, lead$, trail$) 395

WORD.GETPARAM$( setting$, parameter$  [,separator$]) 395

WPOST$(server$, body$, port [,header]) 395

WPOST$(url$, body$,  [,header]) 395

COMMANDS: 395

AUTOREFRESH interval 396

BAS.LOAD filename$. 396

BAS.RTCMEM$ = val$. 396

CLS.. 396

CSS style_code$. 396

COMMAND cmd$. 396

COUNTER.RESET cnt 396

COUNTER.SETUP cnt, pin [,mode] 396

CSSEXTERNAL file$. 397

DATA const1 [,const2] ... 397

DHT.SETUP pin, model 398

EMAIL.SETUP server$, port, user_name$, password$ [, debug] 398

EMAILASYNC from$, to$, subject$, message$. 398

FILE.FROMBASE64 source$, dest$. 398

FILE.SAVE filename$, content$. 398

FILE.TOBASE64 source$, dest$. 398

FUSION.INIT. 398

FUSION.MADGWICK ax, ay, az, gx, gy, gz. 399

FUSION.MADGWICK ax, ay, az, gx, gy, gz, mx, my, mz. 399

FUSION.MAHONY ax, ay, az, gx, gy, gz, mx, my, mz. 400

FUSION.BETA =. 400

FUSION.ZETA =. 400

FUSION.KI =. 400

FUSION.KP =. 400

HTML code$. 400

I2C.SETUP sda_pin, scl_pin [,freq ] 400

I2C.BEGIN address. 400

I2C.END.. 401

I2C.REQFROM address, length. 401

I2C.READREGARRAY i2c_address, register, nb_of_bytes, Array() 401

I2C.WRITE value. 401

I2C.WRITEREGBYTE i2c_address,register, value. 402

I2C.WRITEREGARRAY i2c_address, register, nb_of_bytes, Array() 402

INCR var [, increment] 402

INPUT.TIMEOUT timeout 402

INPUT["prompt$";] variable. 402

INTERRUPT pin_no, {OFF | label} [, mode] 403

IR.INIT pin_rx | OFF [, pin_tx] 403

IR.SEND type, code$, bits. 403

JSCALL javaCode$. 403

JSCRIPT script$. 403

JSEXTERNAL file$. 403

LCD.INIT address, cols, rows. 404

LCD.CLS.. 404

LCD.PRINT x, y, text$. 404

LOCAL var1 [,var2], ... 404

MAXDISPLAY.SETUP CS_pin. 404

MAXDISPLAY.PRINT msg$ [,‘brightness] 404

MAXSCROLL.SETUP nb_devices, CS_pin. 404

MAXSCROLL.PRINT msg$. 404

MAXSCROLL.NEXT msg$. 405

MAXSCROLL.TEXT msg$. 405

MAXSCROLL.SHOW pos [, brightness] 405

MAXSCROLL.SCROLL [brightness] 405

MAXSCROLL.OSCILLATE [brightness] 405

NEO.PIXEL led_pos, R, G, B [, disable] 405

NEO.PIXEL led_pos, COLOR [, disable] 405

NEO.SETUP pin [,nb_led] 405

NEO.STRIP led_start_pos, led_end_pos, R, G, B [, disable] 405

NEO.STRIP led_start_pos, led_end_pos, COLOR [, disable] 406

NEOSCROLL.SETUP nb_devices, pin [,serpentine] 406

NEOSCROLL.PRINT msg$. 406

NEOSCROLL.NEXT msg$. 406

NEOSCROLL.COLORS col$. 406

NEOSCROLL. NEXTCOLORS col$. 406

NEOSCROLL.SHOW pos [, brightness] 406

NEOSCROLL.TEXT msg$. 406

NEOSCROLL.SCROLL [‘brightness] 406

NEOSCROLL.OSCILLATE [‘brightness] 406

OLED.CLS.. 407

OLED.INIT orientation. 407

OLED.REFRESH fmt 407

OLED.COLOR color 407

OLED.PIXEL x, y. 407

OLED.LINE x1, y1, x2, y2. 407

OLED.RECT x,y, width, height [,fill] 407

OLED.CIRCLE x, y, radius [, fill] 407

OLED.FONT font_num.. 408

OLED.PRINT x, y, text$ [background] 408

OLED.IMAGE x, y, image$. 408

OLED.BMP x, y, image$. 408

ONERROR ABORT or ONERROR IGNORE or ONERROR SKIP [nn] or ONERROR CLEAR or ONERROR GOTO label 408

ONESPNOWERROR [label | OFF] 408

ONESPNOWMSG [label | OFF] 408

ONGESTURE [label | OFF] 408

ONHTMLCHANGE [label | OFF] 409

ONHTMLRELOAD [label | OFF] 409

ONINFRARED label 409

ONMQTT label 409

ONRFID label 409

ONSERIAL [label | OFF] 409

ONSERIAL2 [label | OFF] 409

ONTOUCH [label | OFF] 409

ONUDP [label | OFF] 409

ONURLMESSAGE [label | OFF] 409

ONWGETASYNC [label | OFF] 409

OPTION.CPUFREQ 80|160|240. 409

OPTION.LOWRAM value. 410

PAUSE delay. 410

PCA9685.SETUP addr 410

PCA9685.SETFREQ freq. 410

PCA9685.PWM pin, value. 410

PID1.INIT Kp, Ki, Kd. 410

PID1.LIMITS min, max. 410

PID1.PERIOD msec. 410

PID1.PARAMS Kp, Ki, Kd. 410

PID1.SETMODE mode. 410

PIN(pin_number) = val 410

PIN.DAC pin_number, value. 411

PIN.MODE pin_number, mode [,PULLUP | PULLDOWN ] 411

PLAY.MP3 mp3$. 411

PLAY.STREAM stream$ [,buffer] 411

PLAY.SETUP dest [,buffer] [,mono] 411

PLAY.SPEAK message$ [, phonetic] 412

PLAY.STOP.. 412

PLAY.VOICE "message", "language" [, "filename"] [, action] 412

PLAY.VOLUME volume. 412

PLAY.WAV.. 412

PRINT expression[[,; ]expression] ... 412

PRINT2 expression [[,; ]expression] ... 412

PWM.SETUP pin, chan, default,  [,freq] [,resol] 413

PWM.SETUP pin, OFF. 413

PWM.OUT chan, value. 413

READ var1 [,var2] ... 413

REBOOT. 413

REFRESH.. 413

RESTORE [label] 413

RTC.SETTIME Year, Month, Day, Hours, Minutes, Seconds. 413

SERIAL.BYTE ch1 [,ch2] . . . 413

SERIAL2.BYTE ch1 [,ch2] . . . 414

SERIAL.MODE baudrate [, bits, parity, stop] 414

SERIAL2.MODE baudrate, pin_tx, pin rx  [, bits, parity, stop] [, TXbuffer, RXbuffer] 414

SETTIME Year, Month, Day, Hours, Minutes, Seconds. 414

SLEEP value [,pin, level] 414

SOCKET client, msg$. 414

SPI.CSPIN pin [, polarity] 414

SPI.SETUP speed [,data_mode [, bit_order]] 415

SPI.STOP.. 415

ST7920.INIT CS_pin. 415

ST7920.CLS.. 415

ST7920.REFRESH fmt 415

ST7920.COLOR color 415

ST7920.PIXEL x, y. 415

ST7920.LINE x1, y1, x2, y2. 415

ST7920.RECT x,y, width, height [,fill] 415

ST7920.CIRCLE x, y, radius [, fill] 415

ST7920.FONT font_num.. 416

ST7920.PRINT x, y, text$ [background] 416

ST7920.IMAGE x, y, image$. 416

ST7920.BMP x, y, image$. 416

TM1637.PRINT msg$ [, brightness ] 416

TM1637.SETUP data_pin, clock_pin [, bit_delay] [, display_type] 416

TM1638.PRINT msg$ [, brightness ]] 416

TM1638.SETUP data_pin, clock_pin, strobe_pin. 416

TM1638.LEDS val 416

TFT.BMP filename$, [x, y [, back_color] ] 417

TFT.BRIGHTNESS val 417

TFT.CIRCLE x, y, radius,color [, fill] 417

TFT.FILL color 417

TFT.IMAGE filename$, [x, y [, back_color] ] 417

TFT.INIT orientation. 418

TFT.JPG filename$, [x, y [, scale] ] 418

TFT.LINE x1, y1, x2, y2, col 418

TFT.PIXEL x, y, col 418

TFT.PRINT expression [[,; ]expression] ... 419

TFT.RECT x, y, width, height, color [ [,fill] ,[round_radius] ] 419

TFT.SETFREQ freq. 419

TFT.TEXT.COLOR color [,backcolor] 419

TFT.TEXT.POS x, y. 419

TFT.TEXT.SIZE size. 419

TIMER0 interval, label 419

TIMER1 interval, label 419

TOUCH.CALIB.. 420

UDP.BEGIN port 420

UDP.REPLY msg$ [,port] 420

UDP.STOP.. 420

UDP.WRITE ip, port, msg$. 420

URLMSGRETURN msg$ [,content_type$] 420

WAIT. 420

WGETASYNC server$, port [,header] 420

WGETASYNC url$, port [,header] 421

WIFI.APMODE SSID$, password$ [, channel] [, IP$ , MASK$] 421

WIFI.AWAKE.. 421

WIFI.CONNECT SSID$, password$ [, BSSID$] [, IP$ , MASK$ [, GATEWAY$]] 421

WIFI.POWER pow.. 421

WIFI.SCAN.. 421

WIFI.SLEEP.. 421

WLOG [text$ | num] 422

WORD.DELPARAM setting$, parameter$, [,separator$] 422

WORD.SETPARAM  setting$, parameter$, value$ [,separator$] 423

BASIC KEYWORDS.. 423

CASE.. 424

DIM array(size) [, …] 424

DO.. 424

ELSE.. 424

ELSEIF. 424

END [IF | SELECT | SUB] 424

ENDIF. 424

EXIT {DO | FOR | SUB} 424

FOR.. 424

GOSUB [label | lab$] 424

GOTO [label | lab$] 424

IF. 424

LET var = expression. 424

LOOP.. 424

NEXT. 424

OFF. 425

OUTPUT. 425

PULLUP.. 425

PULLDOWN.. 425

REM.. 425

RETURN.. 425

SELECT. 425

SPECIAL. 425

STEP.. 425

SUB.. 425

THEN.. 425

TO.. 425

UNTIL. 425

WEND.. 425

WHILE.. 425

 

 

Introduction:

Annex32 WI-Fi RDS (Rapid Development Suite) is a version of the "BASIC" language developed to run on low cost ESP-32 WIFI devices.

Annex32 is specifically for the ESP32 range of devices, whose implemented features can vary greatly.

To offer some standardisation, Annex32 caters in particular to M5stack devices, which include a micro-SD card slot, TFT display, speaker, 3 user buttons plus a reset button, and a lipo battery, all self-contained in a plastic case offering expansion pin access and designed to accept ‘stackable’ expansion modules.

All drivers needed for the M5stack features are already included in the Annex32 firmware, and pre-configured for the M5stack so that features such as TFT display and SDcard work by default.

Similar functionality could be built using alternative TFT display and SD card reader etc, if preferred.

Please refer to the original M5Stack schematics for more details.

 

However, M5stack and its hardware features merely offer a convenient standardised feature set, they are not mandatory - Annex32 works with any ESP32 devices, with or without hardware expansion modules.

Obviously appropriate hardware is needed for any required features - eg: an OLED display could be used, but scripts written for TFT displays will need modifying for the different display.

 

Annex32 can use the internal flash disk space, or an external SD card.

The internal and the external (SDcard) space are mutually exclusive and cannot be accessed at the same time.

By default Annex32 will use the SD, if available, otherwise it will use the internal flash disk space (FATFS).

Both use the same type file system (FAT32), enabling the use of long file names and directories.

Depending on the module flash memory size (4, 8 or 16MB), the internal disk space can be from ~1MB to 13MB.

Using the ESP32 partition scheme it is possible to freely define this space, but modifying it will wipe out all existing files already stored.

 

Annex32 Wi-Fi RDS takes from the original concept of Annex WI-FI RDS for ESP8266 from which it shares essentially the IDE interface and the same command syntax as much as possible.

It should be straightforward switching to Annex32 if coming from Annex, and the same programs should run without (or with minimum) modifications (eg: pin numbers).

Annex32 Wi-Fi RDS benefits from the powerful H/W architecture of the ESP32 using both cores and the RAM memory available. In addition, for modules equipped with PSRAM memory extension, Annex32 can make available to the users this additional RAM space (up to 4MBytes).

 

Functionalities:

-       Includes an internal IDE so can be programmed directly using your web browser (even from your phone/tablet) without any additional utility.

-       Syntax highlighting with context-sensitive Help

-       A programmable web server which includes a file server

-       Supports OTA (over the air) update.

-       Support async events (interrupts, timers, web access, UDP, ….)

-       Breakpoints, immediate execution of commands, display of variables, single step.

-       A basic interpreter with floating point variables (double precision) and string variables, multi-dimensional arrays (float and string), user defined subroutines.

-       Access to any available I/O pin for input/output, PWM and Servo.

-       Errors Handling .

-       Support TCP (HTTP) GET and POST for communications

-       Support for UDP for communications.

-       Support for sending Emails using SMTP SSL servers

-       Support for AJAX communications (GET, POST, PUT) Synchronous and Asynchronous

-       Support for ESP-NOW communications

-       Support for MQTT communications

-       Support for MODBUS communications

-       Support for FTP communications

-       Support for Bluetooth Low Energy (BLE) communications

-       Support for Telegram communications

-       Support for RJ45 wired ethernet using W5500 module

-       Accompanying utility suite includes Flasher, File Manager, HTML Converter, Backup/Restore to bin or zip, integrated Serial Port Monitor, OTA (over the air) update server and UDP Console.

-       IMU / AHRS Fusion algorithms 6 DOF and 9 DOF (Madgwick and Mahony)

-       Play MP3 or WAV sound files or streaming using a speaker or an external I2S DAC

-       Text to Speech using a speaker or an external I2S DAC

-       Support for regular expressions (regex)

 

The following devices are supported directly with dedicated commands / functions :

-       DHT11, DHT21 or DHT22 Temperature / Humidity Sensors

-       DS18B20 Temperature sensor

-       LCD HD44780 with I2C interface module (1, 2 or 4 lines with 16 or 20 chars per line)

-       LCD Display based on chipset ST7920 with 128x64 pixels monochrome

-       OLED Display based on chipset SSD1306 or SH1106 with 128x64 pixels monochrome

-       TFT Display at 16 bits colors based on the following chipset:

-       ILI9341 with 320x240 pixels

-       ILI9163 with several resolutions

-       ST7735 with several resolutions

-       ST7796 with 480x320 pixels

-       ILI9481 with 480x320 pixels

-       ILI9486 with 480x320 pixels

-       ILI9488 with 480x320 pixels

-       ILI7789 with several resolutions

-       SSD1351 with 128x128 pixels

-       GC9A01 with 240x240 pixels

-       TM1637 4 and 6 digits 7-segments display

-       TM1638 8 digits 7-segments display including 8 leds and 8 buttons

-       MAX7219 8 digits 7-segments display

-       MAX7219 8x8 dot matrix display modules

-       Neopixel WS2812 led strips

-       Neopixel WS2812 8x8 dot matrix display

-       PCA9685 PWM/SERVO module

-       Infrared interface with many RC protocols (transmission and reception)

-       RTC module (DS1307 or DS3231)

-       HC-SR04 ultrasonic sensor for distance measurement

-       BNO055 Absolute Orientation Sensor

-       MPU9250 / MPU6500 IMU units

-       MPU6886 IMU unit

-       BME280 Combined humidity and pressure sensor

-       BME680 Combined gas, pressure, temperature & humidity sensor

-       HDC1080 High Accuracy Digital Humidity Sensor with Temperature Sensor

-       CCS811 Air Quality Sensor

-       APDS9960 Digital Proximity, Ambient Light, RGB and Gesture Sensor

-       W5500 RJ45 wired Ethernet interface

-       VL53L0X TOF (Time Of Flight) Distance Sensor

-       RFID MFRC522 cards reader

-       HX711 - Weight Measurement Module

-       SI5351 Clock Generator Module

-       Any compatible I2S DAC

-       Lora SX127x modules

-       STEP Motors

-       VGA output for ESP32–S3

-       RGB TFT output for ESP32-S3

 

Many ESP32 modules / units  are supported and can be configured using the “CONFIG” menu:

-       Almost all the ESP32 modules including ESP32 devkit, ESP32 wemos mini, ESP32 lolin lite, ...

-       M5Stack

-       M5 Atom

-       M5 Atom matrix

-       M5 Atom Echo

-       ESP32-CAM

-       M5CAMERA

-       ODROID GO

-       M5Tough

-       WIFI LORA 32

-       ESP32-2432S028 (module with a 240x320 2.8” TFT with resistive touchscreen)

-       ESP32-3248S035R (module with 320x480 3.5” TFT with resistive touchscreen)

 

ESP32-3248S035C (module with 320x480 3.5” TFT with capacitive touchscreen)

In addition to the ESP32, Annex now extends its support to other family members, including the ESP32-C3, ESP32-S2, and ESP32-S3. This support encompasses both direct USB connection and variants with USB to serial chip. For all of these modules, Annex offers compatibility across different versions, considering the specific type of flash memory installed, including DIO, QIO, and OPI, as well as the presence of PSRAM, with* options for QIO and OPI configurations. As a result of this diverse range of variants, Annex provides distinct firmware releases tailored to each particular configuration. This ensures optimal performance and seamless integration across the ESP32 series.

 

The following modules equipped with ESP32-S3 are also supported and can be configured using the “CONFIG” menu:

-       ESP32-4848S040 (module with a 480x480 4” RGB TFT with capacitive touchscreen)

-       ESP32-8048S070C  (module with a 800x480 7” RGB TFT with capacitive touchscreen)

 

 

 

 

 

Interpreter:

The basic interpreter works by reading a script file saved to the esp local disk filing system.

This is the default mode if no external SDcard(s) are connected to the ESP32.

In addition, Annex32 can use an external SDcard as file system permitting up to 16Gbytes of disk space.

During the startup, if an external SDcard is detected it will be automatically connected and used as the default file system, in which case the internal filing system will not be used.

Because the ESP32 contains a good quantity of RAM,  the user script is copied from the disk into a dedicated area in the RAM memory where it is executed, together with the list of the program lines, the branch labels and the list of the user defined subroutines..

This uses more RAM compared to other approaches, but allows faster program execution.

Another performance consideration is that the ESP32 must be capable of executing several activities in the background (web server, file server, etc..) so needs sufficient free memory for running such tasks, and those parallel tasks will obviously have an impact on script performance..

So performance-wise, the interpreter is not particularly fast, but it should be fast enough for most tasks you may require. In particular it is around 2 times faster than Annex for ESP8266, considering that many tasks can run in parallel without any appreciable performance impact (such as playing music in the background).

 

Basic program lines :

A typical script line should comply with the following syntax :

[label:] command [argument1 [,argument2 …..]]

 

Script lines may contain several commands on the same line if separated by the colon character ":".

[label:] command1 [argument1 [,argument2 …..]]: command2 [argument1 [,argument2 …..]]

It must be noted that use of several commands on the same line is not recommended and will cause program errors if the line contains GOSUB or user defined subroutine calls.

 

All program jumps (eg: GOTO, GOSUB) are referenced by their branch label names - line numbers are not referenced in scripts, they are merely available in the editor as a programming convenience if wished, and for error references.

 

NOTE : The gosub and the call to user defined subroutines must be used alone on the script line.

Branch labels

Branch labels should not be named the same as a command name, and must follow the same format as variables (see below).

A branch label definition must begin the line, and a colon (":") must terminate the label definition.

Any references to the defined label (GOTOs and GOSUBs etc) do not use a colon.

Example :

 

b = 10

a = 20 : c = 30

GOSUB LABEL1

END

LABEL1:  print "Label1"

RETURN

 

 

Variables:

The interpreter has 2 types of variables:

-          Floating Point (double precision)

-          String

Floating point variables can store numbers with decimal points; they can also store integer numbers with a precision equivalent to 32bits.

Strings contain sequences of characters (example "my program") and must be terminated by "$".

The strings are not limited in size, they are only limited by the amount of memory available.

NOTE: The string variables cannot contain the character with ASCII code 0 (zero) because it is used internally as an end of string delimiter.

 

The variables are defined as any name starting with an alpha character (a, b, ..z) followed by any alphanumeric character (a..z, 0..9); it can also include the "_" (underscore).

The case is don’t care, so  ‘’Num"  is equivalent to "nuM".

The variable name length is limited to 31 characters maximum, including the "$" for the strings.

There are no limits in terms of number of variables; the only limit is the RAM memory available.

Example:

 

NUM = 10.56

myString$ = "this is My String"

this_is_my_value$  = "ESP8266"

number = 8826621

 

Numeric variables and string variables are managed separately so the same name can be used; this means that A and A$ are different variables that can coexist at the same time (even if this could lead to confusion).

 

Constants:

The numeric constants can have the following format :

A = 5 : Z = 1.5

B = 1.23456E5   -> same as 123456

C = 1.23456E+5  -> same as 123456

D = 1.23456E-3  -> same as 0.00123456

 

The string constants are simply defined as a text between quotes:

A$ = "This is my string" : B$ = "another string"

 

The strings can include the character " (quote) simply typing it two times :

A$ = "this is ""MY"" string"

 

The | (vertical bar) can also be used as a string literal.

This permit to include the " (quote) easily inside a string constant :

A$ = |this is a "string" constant|

 

The hexadecimal constants can be defined simply prefixing it with &h :

E = &hABCD -> equivalent of decimal 43981  (hexadecimal constant)

F = &hA0   -> equivalent of decimal 160

 

The binary constants can be defined simply prefixing it with &b :

E = &b00000101  -> equivalent of decimal 5  (binary constant)

F = &b10000001   -> equivalent of decimal 129

 

The octal constants can be defined simply prefixing it with &o :

E = &o377  -> equivalent of decimal 255 (octal constant)

F = &o17   -> equivalent of decimal 15

 

Arrays:

Arrays are defined using the DIM command.

Their names follow the same rules as the regular variables and are followed by parenthesis (brackets) containing the index. The subscript starts from 0, but you can adjust the lower limit using

OPTION.BASE 1.

 

The scope of the Arrays is always global (see next paragraph).

Example:

DIM A(100)              define a floating point array with 101 elements (index from 0 to 100)

DIM ABC$(50)          define a string array with 51 elements (index from 0 to 50)

A(15) = 1234.5678

ABC$(49) = "Hi friend!"

 

The arrays can have up to 5 subscripts (dimensions), examples:

DIM A(50,50)  -> create a floating point array with 51*51 elements (2601)

DIM J$(4, 4, 4)  -> create a string array with 5 * 5 * 5 elements (125)

 

If the command  OPTION.BASE 1  is executed the subscripts start from 1 and an error will be raised when trying to use the index 0.

This line OPTION.BASE 1 must be present in the code before array declaration.

In this case

DIM A(100)              define a floating point array with 100 elements (index from 1 to 100)

DIM ABC$(50)          define a string array with 50 elements (index from 1 to 50)

 

Notice that declaring a multi-dimensional array with multiple subscripts uses elements for every possible[1] [2] [3]  combination of subscripts, whereas in practice it may be preferable to declare multiple arrays with the same subscript, eg:

users=4

DIM Name$(users)

DIM Address$(users)

DIM Tel$(users)

Which only uses  5 + 5 + 5 elements (15)

 

NOTE:

The numerical Arrays are always initialised at 0 with the command DIM.

The string Arrays are always initialised as null string with the command DIM.

There are no limits to the number of arrays or their size, the only restriction is the RAM memory available.

 

The arrays can be re-dimensioned using the same command DIM.

In this case all the existing elements will maintain the previous value except the new elements that will be initialised at 0 or null string.

 

Example :

DIM A(5)      ' all the elements are initialised at 0

A(0) = 123

Print A(0)   ' print 123

Dim A(10)

Print A(0)  ' print the same value 123

Print A(10) ' print 0

 

In addition the elements of the arrays can be initialised with a given value during the command DIM.

Example :

DIM A(5) = 0, 1, 2, 3, 4, 5   ' set A(0)= 0, A(1)= 1, A(2)=2, ….

If the command  OPTION.BASE 1  is executed before

DIM A(5) = 0, 1, 2, 3, 4, 5   ' set A(1)= 0, A(2)= 1, A(3)=2, ….

 

The same can be done with string arrays.

Example :

DIM A$(5) = "zero", "one", "two", "three", "four", "five"

 

Two additional functions can be used to determine the bound limits of arrays:

LBOUND(array() [, dimension]) : Returns the lower bound of the specified array dimension.
UBOUND(array() [, dimension]) : Returns the upper bound of the specified array dimension.

 

Example :

OPTION.BASE 0

DIM A(100)

print LBOUND(a()) ' print 0 (option.base 0)

print UBOUND(a()) ' print 100

 

OPTION.BASE 1

DIM A(10, 20)

print LBOUND(a(), 1) ' print 1 (option.base 1)

print LBOUND(a(), 1) ' print 1 (option.base 1)

print UBOUND(a(), 1) ' print 10

print UBOUND(a(), 2) ' print 20

 

 

 

Scope of the variables:

Variables and arrays defined in the main code are global, therefore any variable is accessible from any part of the code after it has been previously defined there.

Variables and arrays defined  inside “user defined” subroutine (SUB) are visible only inside that sub and inside all the code called by that subroutine; their content (and their memory space) is removed at the end of the SUB

The LOCAL command permits defining local variables inside of "user defined" subroutines; this permits to use the same name of an “already existing” variable locally without modifying the original.

As for all the variables defined inside SUB, they will disappear at the end of the subroutine.

 

Example:

A = 10

B = 20

C = 30

mysub "Hello"

PRINT A,B, C

END

 

SUB mysub(a$)

  LOCAL A,B

  A = 123

  B = 456

  C = 789

  D = 8888

  PRINT A$, D

END SUB

 

In this example, calling the user-defined subroutine "mysub" will not modify the content of the global variables A and B (defined locally) but will modify the content of the variable C (not defined locally) and the variable D will disappear at the end of the SUB.

 

Bases of the language

The keywords recognized by the interpreter can be defined into 3 classes:

     Operators

     Commands

     Functions

 

The Operators are symbols that tell the compiler to perform specific mathematical or logical manipulations.

Commands and Functions both execute an action, but functions also return a data value.

For example PRINTis a command and SIN() is a function whereas the ‘+’ in a = b + 5 is an operator.

The string functions are always followed by the "$" symbol if they return a string value.

In addition to commands and functions there are all the internal interpreter internal commands that are part of the language itself.

 

OPERATORS AND PRECEDENCE

The following operators are available. These are listed in the following tables by order of precedence. Operators on the same line are processed with a left to right precedence.

 

Arithmetic operators:

^

Power

* /  \  MOD

Multiplication, division, integer division and modulo (remainder of the division)

+ -

Addition and subtraction

 

Shift operators:

x << y    

x >> y

These operate in a special way. << means that the value returned will be the value of x shifted by y bits to the left while >> means the same only right shifted. They are integer functions and any bits shifted off are discarded and any bits introduced are set to zero.

For more information about the kinds of bitwise shifts, see Bitwise shifts.

 

Logical operators:

<>    <   >   <=

  >=    =

Not Equal, less than, greater than, less than or equal to,

greater than or equal to, equal

AND  OR  NOT XOR

Conjunction, disjunction, negation, Exclusive OR

 

String operators:

<>    <   >   <=

  >=    =

Not Equal, less than, greater than, less than or equal to,

greater than or equal to, equal

+   &

Add strings together

 

Bitwise operators:

AND OR  XOR NOT

Binary AND, binary OR, binary exclusive OR, binary negation

For more information about the bitwise operators, see Bitwise Operators

 

 

The operators AND, OR and XOR are integer bitwise operators. For example PRINT (3 AND 6) will output 2.

 

Expressions beginning with open parenthesis ‘(‘ are always considered numerical but the parser is able to determine if an expression is true or false even if the expression represents a string.

Each expression representing a comparison, returns a numerical value of 1 if the expression is true or 0 if false.

For example 10 = 10 represents a value of 1 whereas 10 = 5 represents a value of 0.

 

The same logic is applied for string expressions where "abc" = "abc" represents a value of 1 and "abc" = "def"  represents a value of 0.

This is very useful in the IF command and also in other expressions.

For example the following code :

 

 

A$ = "on"

If A$ = "on" then

   pin(4) = 1

Else

  pin(4) = 0

End if

 

 

Can be replaced by

pin(4) = (a$ = "on")

 

The strings can also be compared to determine the alphabetical order.

To see whether a string is greater than another, Annex uses the so-called “ASCII” order.

In other words, strings are compared letter-by-letter.

 

For example:

("Z" > "A")  is true

("Glow" > "Glee")  is true

("Bee" > "Be")  is true

("Bas" > "Bat")  is false

The algorithm to compare two strings is simple:

 

Compare the first character of both strings.

If the first character from the first string is greater (or less) than the other string, then the first string is greater (or less) than the second. We’re done.

Otherwise, if both strings’ first characters are the same, compare the second characters the same way.

Repeat until the end of either string.

If both strings end at the same length, then they are equal. Otherwise, the longer string is greater.

In the examples above, the comparison "Z" > "A" gets to a result at the first step while the strings"Glow" and "Glee" are compared character-by-character:

 

-       G is the same as G.

-       l is the same as l.

-       o is greater than e. Stop here. The first string is greater.

 

The comparison algorithm given above is roughly equivalent to the one used in dictionaries or phone books, but it’s not exactly the same.

For instance, case matters. A capital letter "A" is not equal to the lowercase "a". Which one is greater?

The lowercase "a". Why? Because the lowercase character has a greater index in the ASCII table.

 

Basic internal keywords:

 

IF command :

The IF can have the following syntax :

1)    IF expression THEN statement

2)    IF expression THEN statement1 ELSE statement 2

3)    IF expression THEN

Statements

            ELSE

Statements

            END IF

4)    IF expression THEN

                              Statements

                  ELSEIF expression THEN

                              Statements

                  ELSEIF ……..

                              ………

            ELSE

Statements

            END IF

Example:

IF a > 100 THEN print "a"

 

IF b <a THEN print "b" ELSE print "a"

 

IF c > d THEN

   print "C"

   print "is greater"

ELSE

   print "D"

   print "is greater"

END IF  ' (can also be ENDIF without space between END and IF)

 

IF d = a THEN

   print "d"

   print "is like a"

ELSEIF d = b

   print "d"

   print "is like b"

ELSEIF d = c

   print "d"

   print "is like c"

ELSE

   print "d"

   print "is unknown"

END IF  ' (can also be ENDIF without space between END and IF)

 

When the conditional is all on one line it does not need terminating with an END IF

Example

IFa=2 THEN PRINT "ok" ELSE PRINT "not ok"

 

The AND , OR  keywords can be used between the expressions as long as they are in parenthesis.

Example:

IF (a=1) AND (b=2) THEN PRINT "ok"

 

Or

 

IF ((a=2) AND (b=3) AND (c = 3)) OR (d=4) THEN PRINT "ok"

 

 

The IF can be nested

Example:

 

IF a=2 THEN

  IF b = 2 THEN

    IF c = 3 THEN

      PRINT "ok"

    END IF

  END IF

END IF

 

The “THEN” keyword can eventually be removed, even if this is not recommended.

Example:

 

IF a > 100 print "a" else print "b"

 

FOR loop

The FOR loop can have the following syntax :

 

FOR variable=init_value to end_value [step value]

   Statements

NEXT variable

 

The ‘step’ value can be positive or negative

Example:

 

FOR i=1 to 5

  Print i

NEXT i

 

Will print 1, 2, 3, 4, 5

 

FOR i=1 to 3 step 0.5

  Print i

NEXT i

 

Will print 1, 1.5, 2, 2.5, 3

 

FOR i=3 to 1 step -0.5

  Print i

NEXT i

 

Will print 3, 2.5, 2, 1.5, 1

 

The command EXIT FOR can be used to exit from the loop at any time:

 

FOR i=1 to 50

  IF i=10 THEN EXIT FOR

  Print i

NEXT i

Print "end of loop"

 

Optionally, the variable in the NEXT statement can be omitted.

This means that this program is valid :

 

FOR i=1 to 5

  Print i

NEXT

WHILE WEND loop

The WHILE WEND loop can have the following syntax :

WHILE expression

   Statements

WEND

The loop is iterated as long as the expression is true

 

Example:

i = 0

WHILE i < 3

   Print i

   i = i + 1

WEND

 

Will print 0, 1, 2

 

DO LOOP loop

The DOLOOP can have one of the following 4 syntax :

 

DO WHILE expression

     Statements

LOOP

 

DO UNTIL expression

     Statements

LOOP

 

DO

     Statements

LOOP WHILE expression

 

DO

     Statements

LOOP UNTIL expression

 

The command EXIT DO can be used to exit from the loop at any time

 

Example

i = 0

DO

Print i

i = i + 0.5

LOOP UNTIL i >3

Will print 0, 0.5, 1, 1.5, 2, 2.5, 3

 

i = 0

DO

Print i

i = i + 0.5

IF i > 2 THEN EXIT DO

LOOP UNTIL i >3

Will print 0, 0.5, 1, 1.5, 2

 

SELECT CASE

The SELECTcan have the following syntax:

 

SELECT CASE expression

   CASE exp1 [: Statements]

       Statements

   CASE exp2 TO exp3 [: Statements]

       Statements

   CASE exp4 [,exp5], ... [: Statements]

       Statements

   CASE ELSE

       Statements

END SELECT

 

Example:

 

a = 4

SELECT CASE a

   CASE 1

     PRINT "case 1"

   CASE 2 : PRINT "case 2"

   CASE 3 : PRINT "case 3" : PRINT "can continue on same line"

   CASE 4 : PRINT "case 4"

     PRINT "can continue also on next line"

   CASE ELSE:

     PRINT "case else"

END SELECT

 

Multiple cases:

a = 4

SELECT CASE a

   CASE1       : PRINT "case 1"

   CASE 2, 3, 5 : PRINT "case 2 or 3 or 5"

   CASE4       : PRINT "case 4"

   CASE 6 TO 8  : PRINT "case 6 to 8"

   CASE 9 TO 20 : PRINT "case 9 to 20"

   CASE ELSE:

     PRINT "case else"

END SELECT

 

The SELECT CASE can also handle string content:

SELECT CASE a$

   CASE "a" :

     PRINT "case a"

   CASE "a", "b", "c", "d" :

 PRINT "case a, b, c, or d"

   CASE "e" TO "h" :

 PRINT "case e to h"

   CASE ELSE:

     PRINT "case else"

END SELECT

 

GOTO

The GOTOcan have the following syntax :

GOTO [LABEL | LAB$]

 

Example

a = 5

   IF a > 5 THEN GOTO LABEL1

END

....

 

LABEL1:

PRINT "This is label1"

....

 

The goto must be considered as an obsolete command and is provided just for backward compatibility with old style Basic programs.

 

GOSUB

The GOSUBcan have the following syntax :

GOSUB [LABEL | LAB$]

The called function must terminate with the command RETURN

 

Example

a = 5

   IF a > 5 THEN GOSUB LABEL1

END

....

 

LABEL1:

PRINT "This is label1"

RETURN

 

DATA

The command DATA is used to store constant information in the program code, and is associated with the command READ. Each DATA-line can contain one or more constants separated by commas. Expressions containing variables  will be also evaluated here.

The goal of the DATA is to avoid repetitive variable assignation lines, in particular for arrays.

The DATA values will be read from left to right, beginning with the first line containing a DATA statement. Each time a READ instruction is executed the saved DATA position of the last READ is advanced to the next value. Strings must be written in quotes like string constants. The command RESTORE resets the pointer of the current DATA position, so the next READ will read from the first DATA found from the beginning of the program.

In case READ uses the wrong variable type the error message "Type mismatch" appears while referring to the line number containing the READ statement that triggered the condition.

DATA lines may be scattered throughout the whole program code, but for the sake of clarity they would be better kept together at the beginning of the program.

 

The DATA can have the following syntax :

DATA const1 [,const2] …..

The constants can be Numerical or String.

 

Example :

DATA 1, 55.88, "constant", 99

READ A, B, C$, D

PRINT A, B, C$, D

 

Example without DATA:

dim colors$(5)

colors$(1) = "Red"

colors$(2) = "Green"

colors$(3) = "Blue"

colors$(4) = "Yellow"

colors$(5) = "Magenta"

 

Same example but using  DATA:

DATA "Red", "Green", "Blue", "Yellow", "Magenta"

dim colors$(5)

For i=1 to 5

  Read colors$(i)

Next i

 

The command RESTORE can optionally define a label to set the DATA pointer to a specific point

 

Example

data 0, 1, 2, 3, 4, 5

block2:

data 10, 11, 12, 13, 14, 15

block3:

data 20, 21, 22, 23, 24, 25

block4:

data 30, 31, 32, 33, 34, 35

 

restore block3

for z = 0 to 5

  read a

  print a,

next z

restore block2

print " "

for z = 0 to 5

  read a

  print a,

next z

print "----------"

 

END

Define the end of the program. With this command the program stops.

It can also be :

END IF -> close the IF command

END SELECT -> closes the SELECT CASE command

END SUB -> closes the user defined SUB

 

EXIT

Permit to exit from a loop or a user defined SUB.

The syntax is :

EXIT DO  -> exit from a DO loop

EXIT FOR -> exit from a FOR loop

EXIT SUB -> exit from a user defined SUB.

SUB

Define a user-defined subroutine, which the script can use like a command or function.

User-defined subroutines are effectively additional commands, so cannot be used as branch labels.

Permit to create a user defined command with optional parameters.

The syntax is SUB subname[(arg1 [,arg2] …)]

The variables are passed by reference; this means that the arguments, if modified inside the subroutine, will modify the original variable. This can be useful to return values from the subroutine (acting like a function).

It is possible to pass arrays using the syntax array_name().

Using the LOCAL command will permit to define local variables (useful to avoid to modify existing global variables).

 

Example 1 : routine cube

 

SUB cube(x)

  PRINT X ^3

END SUB

 

cube 3 ' will print 27

 

 

Example 2: routine cube with returning argument

 

SUB cube(x,y)

  y = x ^3    ' the value is returned using the 2nd argument

END SUB

 

ret = 0

cube 5, ret

PRINT ret ' will print 125

 

 

Example 3: routine with local variables and returning argument

 

SUB left_trim(s$, ret$)

  LOCAL i

  i = 1

  DO UNTIL i = len(s$)

    IF mid$(s$, i, 1) <> " " THEN EXIT DO

    i = i + 1

  LOOP

  ret$ = mid$(s$, i)

END SUB

 

z$ = ""

FOR i = 1 to 3

  left_trim "  remove space from left ", z$

  PRINT  z$ + "--"

NEXT i

 

Will print

remove space from left          --

remove space from left          --

remove space from left          --

As you can see in this example, the variable i in the FOR loop is not modified by the LOCAL variable i in the subroutine.

 

Example 4: pass arrays

 

SUB pass_array(f(), c$())

  Dim myArray(10)

  myArray(0) = 456 

  Print f(0), c$(0), myArray(0)

  f(1) = 123

  c$(1) = "myText"

END SUB

 

Dim alpha(10)

Dim beta$(10)

alpha(0) = 456

beta$(0) = "testme"

Pass_array alpha(), beta$()

Print alpha(1), beta$(1)

 

In this example, the array alfa() is passed locally to the array f() and the array beta$() is passed locally to the array c$().

Modifying locally these arrays change the value of the original one as their content is passed by reference.

The array “myArray” will disappear at the end of the SUB

 

 

 

Logical / boolean Operations

As the numerical variables are stored internally as double precision floating numbers, it is possible to store numbers with a precision equivalent to 32 bits.

Several boolean operators are available to manipulate these numbers..

 

The first operator is the bit shift; it can be shift left << or shift right >>

This operator permits to shift the number of a specified number of positions to left or right.

 

Example

A = 1

Print A << 3 ' will print 8

 

A = 16

Print A >> 2 ' will print 4

 

The operators AND , OR , XOR are also available :

 

A = 24

A = 15

Print A AND B ' will print 8

 

A = 24

A = 15

Print A OR B ' will print 31

 

A = 24

A = 15

Print A XOR B ' will print 23

 

The unary operator NOT is also available. It inverts all the bits from 0 to 1:

A = 0

Print Hex$(NOT A) ' will print FFFFFFFF

 

For a 32 bits number, assuming 4 bytes ABCD where A is the MSB and D the LSB, the bytes can be extracted as follows :

 

VAR = &h12345678 ' this is a 32 bits variable

 

D = VAR AND &hFF

C = (VAR >>  8) AND &hFF

B = (VAR >> 16) AND &hFF

A = (VAR >> 24) AND &hFF

 

For more information, see Bitwise Operators

 

ERRORS HANDLING

Annex allows to control and manage errors that occur during the execution of the code.

This is managed with the command ONERROR.

This command defines what action is taken when an error occurs, and applies to all errors, including syntax errors.

It can be used in different ways, as specified in the table below:

 

FUNCTIONS / COMMANDS

DESCRIPTION

ONERROR ABORT

Displays the error message then aborts the program.

This is the normal behaviour and is the default when a program starts running.

ONERROR IGNORE

Any error will be simply ignored.

As this can make it very difficult to debug a program it should be used wisely.

ONERROR SKIP [nn]

Ignore an error in the next command(s) executed after the current command (the number of skipped commands depends on whether the number ‘nn’ is specified).

'nn' is optional, the default is  1  if not specified.

After the number of skipped commands has completed (with an error or not) the behaviour will revert to ONERROR ABORT.

ONERROR CLEAR

Reset the eventual pending error

ONERROR GOTO [label | OFF]

Jumps to the error handling routine defined by the label.

It can be removed (hence reverting to ONERROR ABORT) replacing the label with OFF.

Using RETURN inside the error handling routine will continue the execution on the line following the error.

 

When an error occurs, the following constants are available :

 

CONSTANT

DESCRIPTION

BAS.ERRLINE

Returns the line number where the error happened. Value of 0 means no error.

It is reset to 0 with the command ONERROR CLEAR or  running the program or with the command ONERROR IGNORE or ONERROR SKIP.

BAS.ERRNUM

Returns a number where non zero means that there was an error.

It is reset to 0 with the command ONERROR CLEAR or  running the program or with the command ONERROR IGNORE or ONERROR SKIP.

BAS.ERRMSG$

Return a string representing the error message that would have normally been displayed on the console. It is reset to “No Error” running the program or with the command ONERROR CLEAR or ONERROR IGNORE or ONERROR SKIP.

 

Example of error handling using the command ONERROR GOTO :

 

ONERROR GOTO Error_Handler

Print "start"

Print 3/0  ' this generates a divide by zero error

Print space$(60000) ' this generates an out of memory error

End

 

Error_Handler:

Print "Error text "; BAS.ErrMsg$

Print "Error num  "; BAS.ErrNum

Print "Error line "; BAS.ErrLine

Return ' returns to the line following the error

 

 

HOW the interpreter works with the HTML code and Objects :

When a client connects to the module using its IP address, the module will redirect automatically to the url ‘/output?menu’, which sends an empty html page present on the module.

That page contains a bunch of javascript code permitting to interface the page with the module using javascript.

 

image

This page will automatically open a websocket connection with the module; the "squared led" indicates if the connection was successful (green) or not (red).

A mechanism of ping - pong has been implemented into the javascript in order to hold the connection alive all the time. If the connection is lost, the page will try to reconnect automatically without any manual action.

The button "reconnect" permits to force the reconnection if the automatic reconnection fails.

 

As soon as the connection is done with the module, the html page is ready to send and receive messages to / from the module.

Initially the page is empty but its content can be easily filled.

 

To send HTML code to the page, the command HTML is used.

The syntax is : HTML  HTML code.

For example the line

HTML "Hello, world <br>This is my first html content<br>"

 

Will give this result :

image

Continuing with the HTML command, the content can be improved :

HTML "Textbox: <input type='text'><br>"

 

image

 

Continuing again:

HTML "Button:  <button type='button'>Click Here</button>"

 

image

All the html code can be combined and sent with just one HTML command; this is much faster:

 

a$ = "Hello, world <br>This is my first html content<br>"

a$ = a$ + "Textbox: <input type='text'><br>"

a$ = a$ +  "Button:  <button type='button'>Click Here</button>"

HTML a$

 

 

To clear the content of the page, the command is:

CLS

image

 

Now we can try another example

CLS

a$ = "Now style me, please<br>"

a$ = a$ + "Button1:  <button id='but1' type='button'>ON</button> "

a$ = a$ + "Button2:  <button id='but2' type='button'>OFF</button>"

HTML a$

image

 

Now we will try to style the buttons using css.

This can be done using  command CSS CSSID$()

For example the line

CSS CSSID$("but1", "background-color: red;")

Will give this result :

image

 

Combining with the style for the other button:

 

a$ = a$ + cssid$("but1", "background-color: red;")

a$ = a$ + cssid$("but2", "background-color: green;")

CSS a$

 

image

 

A set of functions is included to simplify the creation of HTML pages as we will see later, so no need to worry if you are not familiar with writing HTML code.

 

Now we will mention an important ‘event’ that can be used to automatically fill the content of the page each time a client connects to the module : OnHtmlReload.

This ‘event’ defines a place where the program will jump to as soon as a Websocket connection request is accepted.

Let’s clarify with an example :

OnHtmlReload Fill_Page    ‘will jump to Fill_Page when the page is reloaded

gosub Fill_Page  'load the page for the first time

Wait         ‘pause waiting for the event

Fill_Page:   ‘place where the page begins to be created

CLS

a$ = "Now style me, please<br>"

a$ = a$ + "Button1:  <button id='but1' type='button'>ON</button> "

a$ = a$ + "Button2:  <button id='but2' type='button'>OFF</button>"

HTML a$

a$ = cssid$("but1", "background-color: red;")

a$ = a$ + cssid$("but2", "background-color: green;")

HTML a$

RETURN

 

The result will be:

image

Now try to play with the button "Reconnect"; you’ll see that, at each time the page reconnects to the module, the HTML content is built and sent again. This ensures that each time a client connects to the module it will receive the correct content. At the same time, if other clients are already connected, the content of all the pages will be refreshed simultaneously. This ensures a synchronized content between all the clients.

 

HTML Objects

As said previously, in order to simplify the creation of HTML pages there are several functions available which can generate the html code automatically.

Let’s start with the button.

A button is an object that is used to trigger an action each time it is pressed on the web page.

The function is BUTTON$.

Let’s explain with an example:

 

CLS

HTML BUTTON$("Button1", jump1)

 

Wait         'pause waiting for the event

 

Jump1:

PRINT "Clicked on Button1"

Return

 

 

 

The result will be:

image

 

Try clicking on the button then checking the result in the terminal console; the message "Clicked on Button1" will be shown at each click.

image

 

To style the button, we need to modify the syntax of the BUTTON$ command slightly; in fact we need to add another parameter to give the button an ID:

 

CLS

HTML BUTTON$("Button1", jump1, "but1")   ' "but1" is the ID

 

Wait         'pause waiting for the event

 

Jump1:

PRINT "Clicked on Button1"

CSS cssid$("but1", "background-color: red;") 'the same ID is used here

Return

 

Clicking on the button now will change its color to red

image

 

Now we can now introduce the LED object. The LED object is a circle that can be filled in red or green depending on the content of a variable. The function is LED$

As usual, let’s start with an example:

 

 

CLS

led = 1    ‘this is the variable associated with the LED. With 0 the led is red, with 1 the led is green

HTML LED$(led)

 

The result will be:

image

 

Let’s also add a button :

 

CLS

led = 0

a$ = BUTTON$("Button1", jump1, "but1")   ' "but1" is the ID

a$ = a$ + LED$(led)

HTML a$

 

Wait         'pause waiting for the event

 

Jump1:

PRINT "Clicked on Button1"

led = 1 - led ' invert the variable

REFRESH ' refresh (update) the variables between the code and the html

Return

 

 

The result will be:

image

 

Clicking on the button will toggle the led between red and green colors.

 

The command REFRESH permits to update (synchronize) the variables in the code with the corresponding objects variables on the web page. It should be run each time a variable is modified.

As a simpler alternative, the command AUTOREFRESH will regularly sync the variables.

The command must be run with the desired refresh timing.

Example

AutoRefresh 500   will refresh the variables each 500 milliseconds.

The interval should not be less than 300 milliseconds (otherwise the module will be too busy).

 

The example :

 

CLS

led = 0

a$ = BUTTON$("Button1", jump1, "but1")   ' "but1" is the ID

a$ = a$ + LED$(led)

HTML a$

AutoRefresh 300   'sync each 300 milliseconds

Wait         'pause waiting for the event

 

Jump1:

PRINT "Clicked on Button1"

led = 1 - led ' invert the variable

 

Return

 

The result will be the same as the previous example.

 

Now it’s time to introduce another object; the TEXTBOX with the corresponding function TEXTBOX$.

The TEXTBOX will display a ‘text box’ on the web page which is linked with a variable. When the variable is modified in the code, the TEXTBOX content will be updated on the web page, and vice-versa.

This lets us introduce another ‘event’, the OnHtmlChange command.

This ‘event’ defines a branch for the program to jump to whenever a variable is modified inside the web page.

As usual, let’s start with an example:

 

 

CLS

text$ = "Change me, please"

HTML TEXTBOX$(text$)

OnHtmlChange Jump1  'will jump to Jump1 when a variable changes on the web page

Wait         'pause waiting for the event

 

Jump1:

Print text$ 'print the content of the variable inside the terminal console

Return

 

 

image

 

Try now to change the content of the textbox and press "Enter" on the keyboard.

Let’s see the result in the terminal console:

image

image

 

 

With the concepts already learned you’ll be able to use the other objects using the similar logic.

Refer to the pages below to understand the syntax of each object.

 

TIMERS

A timer is an "object" that permits the execution of a particular action at regular intervals.

When the given time expires, the normal execution of the program is interrupted while control is passed to the "timer interrupt routine" until after the execution of the return command.

Then the program continues from the point where it was interrupted.

Let’s explain with an example :

 

timer0 1000, mytimer

wait

 

mytimer:

  wlog "mytimer " + time$

return

 

Annex WI-Fi Basic implements 2 timers, Timer0 and Timer1.

The Timer0 has a higher priority against Timer1.

EVENTS[4] 

Many of the actions are not executed directly by basic commands but can be executed as asynchronous events.

An  "event" is simply an action that can be executed when something happens.

For example, pin change interrupts are asynchronous events which can happen at any time without user control.

In order to manage the events, a list of commands "ONxxxx" is provided. These commands define the place where the normal execution of the program will branch to when the event occurs.

So, when the "event" happens, the basic interpreter interrupts the normal execution of the code and "jumps" to the location defined by the corresponding command "ONxxx". As soon as the code associated with the "event" is terminated with the command "return", the basic interpreter continues from the previous interrupted location.

Button Event

This is a special event that happens every time aBUTTON$ object is clicked in the HTML pages.

When this happens, a special variable HtmlEventButton$ is created containing the name of the button that was clicked.

This is useful to determine the button within a group of buttons.

Let’s see an example:

 

CLS

HTML Button$("ON", buttonEvent) + " " + Button$("OFF", buttonEvent)

wait

 

buttonEvent:

print "You clicked on "; HtmlEventButton$

return

 

OnHtmlChange Event

This event is triggered when an object present in the HTML output page changes its value.

It is useful to make actions when something changes in the HTML Pages.

When this event happens, a special variable HtmlEventVar$ is created containing the name of the variable that changed its value.

This is useful to determine the object that generated the event.

Let’s see an example :

 

CLS

text$ = "Change me, please"

HTML TEXTBOX$(text$)

OnHtmlChange Jump1  'will jump to Jump1 when a variable changes on the web page

Wait         'pause waiting for the event

 

Jump1:

Print text$ 'print the content of the variable inside the terminal console

Return

 

Note that the special variable HtmlEventVar$ is only created when the OnHtmlChange event populates it due to a html object change, therefore it will cause an error if tested for before an object is changed unless specifically defined beforehand, eg: HtmlEventVar$ = “”  

 

OnHtmlReloadEvent

This event is triggered when a Websocket connection request is accepted.

This can be used to automatically fill the content of the WEB page each time a client connects to the module.

Let’s see an example :

 

CLS

OnHtmlReload Fill_Page   'will jump to Fill_Page when the page is reloaded

gosub Fill_Page  'load the page for the first time

Wait         'pause waiting for the event

Fill_Page:   'place where the page begins to be created

CLS

a$ = "Now style me, please<br>"

a$ = a$ + "Button1:  <button id='but1' type='button'>ON</button> "

a$ = a$ + "Button2:  <button id='but2' type='button'>OFF</button>"

HTML a$

a$ = cssid$("but1", "background-color: red;")

a$ = a$ + cssid$("but2", "background-color: green;")

HTML a$

Return

 

OnInfrared Event

This event is triggered when a code is received by the infrared receiver.

Refer to chapter INFRARED INTERFACE for more details.

 

OnSerial Event

This event is triggered when a message is received on the serial port.

Example:

 

print "Ram Available "; ramfree
onserial rec1
wait

rec1:
'print serial.input$
print serial.chr$;
return

 

 

 

OnSerial2 Event

This event is triggered when a message is received on the serial port #2.

Example

 

serial2.mode 9600, 2, 5 ' set serial port #2 to 9600 pin 2 TX, pin 5 RX
print2 "Ram Available "; ramfree
onserial2 rec2
wait

rec2:
print serial2.input$
return

 

 

OnTouch Event

This event is triggered when the TFT screen is touched.

Refer to the chapter TouchScreen for more details.

 

OnUDP Event

This event is triggered when a UDP message is received.

Example:

 

udp.begin 5001  'set the UDP commmunication using port 5001
onudp goudp
'Write several messages to the port
for i
= 0 to 100
  
udp.write "192.168.1.44", 5001, "Hello " + str$(i)
next i
wait

goudp:
v$
= udp.read$ 'receive the UDP data

print v$
return

 

 

OnWgetAsync Event

This event is triggered when a WgetAsync message is received.

This is associated with the command WGETASYNC.

The goal of the WGETASYNC command is to start a html get request without the module having to wait for the answer.

Because the response is async, this command specifies the location where the program should branch to when a message is received.

Example:

 

ONWGETASYNC answer_done

WGETASYNC("www.fakeresponse.com/api/?sleep=5", 80)

For i = 0 to 10000

  ' a kind of sleep just to demonstrate that the code continue to run

  Print i

Next i

Wait

answer_done:

Print WGETRESULT$

Return

 

OnUrlMessage Event

This event is triggered as soon as a web client requests for a web page with the url composed with http://local_ip/msg?param=value. This kind of request is typically called an AJAX request as it permits to exchange in both directions between the client (the web browser) and the server (the ESP module).

In fact, in the url request, the client can send parameters in the form of couples of "param=value" separated by the character "&". For example, if the client wants to send 2 parameters, it can send the following request :

http://local_ip/msg?param1=value1&param2=value2.

As soon as this message is received by the ESP module, the event OnUrlMessage is triggered; this means that the program will continue from the location defined by the command OnUrlMessage.

As soon as the message is received, the parameters sent by the client can be got with the function UrlMsgGet$ and a message can be sent back to the client with the command UrlMsgReturn.

Let’s see an example :

 

onUrlMessage urlAjax

wait

 

urlAjax:

wlog "message received " + UrlMsgGet$("a") + " " + UrlMsgGet$("b")

UrlMsgReturn "Message sent back " + time$

print UrlMsgGet$("b"), ramfree

return

 

Now using another web browser window, let’s type the following url :

http://esp_local_ip/msg?a=10&b=20

As you can see in the following picture, the message is received by the ESP module

image

 

 

 

At the same time, the client receives the message sent back from the ESP module

image

 

If the program is stopped, the module will answer with the message "STOPPED"

image

Now, let’s see a more complete example :

cls

' this is the default value for pwm out

R = 512

G = 512

B = 512

'Setup the pwm channels

PWM.SETUP 12, 1, R, 10000, 10

PWM.SETUP 15, 1, G, 10000, 10

PWM.SETUP 13, 1, B, 10000, 10

'Set the default values

PWM.OUT 1, R

PWM.OUT 2, G

PWM.OUT 3, B

 

' these are the sliders

a$ = ""

a$ = a$ + |R <input type="range" id="dimmer_R" oninput="setPWM()" onclick="setPWM()" min="0" max="1023" value="| & str$(R) & |"/><br>|

a$ = a$ + |G <input type="range" id="dimmer_G" oninput="setPWM()" onclick="setPWM()" min="0" max="1023" value="| & str$(G) & |"/><br>|

a$ = a$ + |B <input type="range" id="dimmer_B" oninput="setPWM()" onclick="setPWM()" min="0" max="1023" value="| & str$(B) & |"/><br>|

a$ = a$ + |<input type='text' id="txbox" value='---'>|

html a$

'this is the javascript "AJAX" code

fun$ =    |function setPWM() {|

fun$ = fun$ & |r=_$("dimmer_R").value;|

fun$ = fun$ & |g=_$("dimmer_G").value;|

fun$ = fun$ & |b=_$("dimmer_B").value;|

fun$ = fun$ & |var xmlHttp = new XMLHttpRequest();|

fun$ = fun$ & |xmlHttp.open("GET", "msg?r=" + r +"&g=" + g +"&b=" + b, false);|

fun$ = fun$ & |xmlHttp.send(null);|

fun$ = fun$ & |r = xmlHttp.responseText;|

fun$ = fun$ & |_$("txbox").value = r;|

fun$ = fun$ & |return r;}|

 

' this is where the javascript code is inserted into the html

jscript fun$

 

'this is where the prog will jump on slider change

onUrlMessage message

wait

 

message:

print UrlMsgGet$()

 

PWM.OUT 1, val(UrlMsgGet$("r"))

PWM.OUT 2, val(UrlMsgGet$("g"))

PWM.OUT 3, val(UrlMsgGet$("b"))

UrlMsgReturn UrlMsgGet$()

return

 

Open the input page in another window and run the program

 

image

Using an external RGB led, you’ll be able to directly control its color.

You’ll see how the exchanges can be fast using AJAX exchanges. This program uses javascript embedded into the code. The javascript works with the function XMLHttpRequest.

A good reference for this function is here AJAX - Send a Request To a Server

OnEspNowMsg Event

This event is triggered when a ESP-NOW message is received.

Example:

 

espnow.begin  ' init the ESP-NOW

onEspNowMsg message ' set the place where jump in case of message reception

wait

 

message:

print "Message Received!"

return

 

OnEspNowError Event

This event is triggered when a ESP-NOW transmission error occurs.

This happens, in particular, when the receiver device has not received the message.

 

espnow.begin  ' init the ESP-NOW

espnow.add_peer "60:01:94:51:D0:7D" ' set the MAC address of the receiver

onEspNowError status ' set the place where jump in case of TX error

espnow.write "TX message" ' send the message

wait

 

status:

print "TX error on "; espnow.error$  ' print the error

return

 

OnMQTT Event

This event is generated when a MQTT message is received or an MQTT event happens

Example:

 

....

onmqtt mqtt_msg

 

wait

' receive messages from the server

mqtt_msg:

print "TOPIC  : "; mqtt.topic$

print "MESSAGE: "; mqtt.message$

return

 

OnPlay Event[5] 

This event is generated when a “metadata” is decoded when playing mp3 or streaming a web radio.

 

WiFI CONNECTIONS

At startup, the module will try to connect to the router using any parameters specified in the page “Config”.

If no parameters are specified in the “Config” page, or the connection is unsuccessful, it will default to AP (Access Point) mode with IP address 192.168.4.1 with the SSID composed of ESP(+ mac address).

If the connection is successful, the module will use the IP address defined in the “Config” page or, if no IP address is specified, the IP will be given automatically by the Router DHCP server.

After the module has connected to the router it will try to reconnect automatically if the connection is lost.

 

There are several commands / functions available to manage the WIFI.

 

The first function is WIFI.STATUS which permits to get the status of the connection.

print WIFI.STATUS ’ print 3 if connected, 6 if disconnected

 

The first useful command is WIFI.CONNECT SSID$, password$ [, BSSID$] [, IP$ , MASK$ [, GATEWAY$]]

 

This command allows you to connect to any WIFI network (STA mode) overriding the parameters defined into the’ “Config” page. This function is async so the connection is done in background, while the program continues to run.

Is then possible to check the status of the connection using the function WIFI.STATUS

Example :

WIFI.CONNECT "HOMENET", "MyPassword"

print "connecting"
While WIFI.STATUS <> 3
 
Print "."
 
pause 500
wend

 

Using the optional parameter BSSID$, will enable the connection to a specific WiFi access point.

The BSSID represents the MAC address of the WiFi access point (the router) and it is defined as 6 bytes in hex format separated by colon, i.e. AA:BB:CC:12:34:56.

 

For stand alone configuration or for ESP-NOW applications, there is another command that puts the module in AP mode.

This command is WIFI.APMODE SSID$, password$ [, channel] [, IP$ , MASK$]

The result is immediate and the status can be checked using the function WIFI.MODE (see below).

The channel is optional and is 1 by default.

IMPORTANT : the password must be at least 9 characters

 

It is eventually possible to control the output power of the module with the command WIFI.POWER pow

WIFI.POWER 5 ’ set the output power at 5 dBm.

 

The module can also be put in WiFi sleep mode. This mode permits to turn off the WiFi reducing the power requirements of the module; this is very useful for battery oriented applications or for applications where the WiFi is not required.

To put the module in “modem-sleep”, the command to execute is WIFI.SLEEP.

The module will stay in that mode until the execution of the command WIFI.AWAKE.

After this command, the module will reconnect automatically to the router (the command WIFI.CONNECT is not required).

 

Another function available is WIFI.CHANNEL that shows the current Radio Channel used by the WIFI.

 

Using the function WIFI.RSSI is it possible to get the intensity of the signal received (RSSI)  

 

It is also possible to scan for the WiFi networks accessible around the module.

This can be done using the command WIFI.SCAN and the function WIFI.NETWORKS(network$).

Example :
WIFI.SCAN
While WIFI.NETWORKS(A$) < 0
Wend
Print a$

The result will be :

Vodaphone, 00:50:56:C0:00:08, -50, 5

Orange, 00:50:56:C0:32:07, -70, 5

Xxxx,  00:50:56:C0:86:CA,-78, 12

 

These information represent, in the order :

SSID, BSSID(mac address), RSSI(signal intensity), Channel Radio

 

The function WIFI.MODE returns the current mode of the WIFI connection as below:

 

VALUE

MEANING

0

The WIFI is in sleep mode

1

The WIFI is in STATION mode

2

The WIFI is in AP mode

3

The WIFI in AP+STA mode

 

The WIFI in AP+STA mode can be obtained by configuring the module in AP mode and then using the command WIFI.CONNECT in the program.

 

Using a “fake” SSID / password (example WIFI.CONNECT "A", "" ) can be used to switch the WIFI into the AP+STA mode. This can be useful for mixed ESP32 / ESP8266 ESP-NOW operations.

 

Another Wifi related command is OPTION.MAC mac$ that permits to modify the MAC address of the module.

This is very important for the ESP Now functionality.

Example :

OPTION.MAC "AA:BB:CC:DD:EE:FF"

 

In addition, the functions BAS.SSID$ and BAS.PASSWORD$ returns respectively the login and the password used for the STATION wifi connection.

PROGRAM AUTORUN

If a program is defined to run automatically (“Autorun File” in the config page), the WiFi connection process is slightly different.

If the option “Fast boot” in the config page is selected, the program will be executed immediately and the WiFi will be powered ON after a little delay ( 0.1 sec ).

If the command WIFI.SLEEP is executed during the very beginning of the program ( for example as the first line of the program) the WiFi will be simply disabled without using any power.

This enhances the use of the module in low power applications (i.e. on battery).

The WiFi connection can then be restored later using the commands WIFI.CONNECT or WIFI.APMODE.

 

If the command WIFI.SLEEP is not executed at the beginning of the program, the WiFi connection will be established by default as described in the previous chapter (WiFI CONNECTIONS).

 

The function BAS.RESETREASON can be used at the beginning of the program to understand the reasons for the restart of the module enabling it to take the appropriate actions.

In addition, the function BAS.WAKEUPREASON can be used to determine the cause of the wakeup from sleep if the module was in deep sleep mode.

RECOVERY MODE

In case of any IP or Autorun problem preventing the module from being accessed, it is possible to temporarily bypass the IP settings of the module and disable the Autorun file by connecting the serial TX and RX pins together (GPIO1 to GPIO3) during the startup phase (power up).

This could happen if, for example, a wrong IP address has been set.

Doing this action when restarting the module will put it in AP mode with the IP address at 192.168.4.1, just like a module that has not been configured.

A message “Recovery Mode” will be printed on the console, but none of the existing files on the module will be modified, including the internal configuration parameters.

In this mode it will be possible to gain access to the module for changing such correct wrong IP parameters using the configuration page.

When the TX/RX link is removed, the module can be rebooted to the configured settings at the next restart.

 

SLEEP mode (low energy) and RTC memory

The module can be put into low energy mode to minimise as much as possible the power requirements.

This mode is called deep sleep and should reduce the power consumption to a few µA but this is a function of  each ESP32 module as the power requirement includes the different components installed on the module.

When the module is put into deep sleep all the module activities are stopped, all the memory content of the module is lost except for the RTC memory (this is a special memory block inside the module that holds its content even if the module is reset, but not when the module is powered OFF).

At the end of the sleep period, the module restarts and reloads the program defined as autorun from the beginning (from the first line).

 

To put the module in deep sleep the following command is available :

 

SLEEP value [, pin, level]

This command puts the ESP32 in deep sleep (low energy) for 'value' seconds.

At the end of the period, the unit will reboot and reload the default basic program.

Example

' Sleeps for 600 seconds (10 minutes)

The period can go from 1 second  to several years (1 year = 31,536,000 seconds)

 

Optionally, it is possible to wake up the module using an external signal sent on an input pin

In this case the pin and the level must be specified in addition to the time value.

Example

' Sleeps for 3600 seconds (1 Hour) or until the pin 32 goes to high

SLEEP 3600, 32, 1

 

Only RTC IO can be used as a source for external wake up.

They are pins: 0,2,4,12-15,25-27,32-39.

Level is 1 for wakeup on High and 0 for wakeup on Low

 

Optionally, this command can be also used to wake up the module using the capacitive touch on an input pin.

In this case the command SLEEP maintains the same syntax but level defines the threshold value for the pin.

Only the following pins can be used for that purpose (capacitive touch) : 0, 2, 4, 12, 13, 14, 15, 27 32, 33

Level must be > 1 to enable the touch (0 and 1 are reserved for the wake up on Low or Wake up on High).

Example

' Sleeps for 3600 seconds (1 Hour) or until the pin 15 is touched

SLEEP 3600, 15, 40 ' Threshold at 40

 

The RTC memory will survive after the wake up permitting to take trace of the actions done before the sleep.

 

This memory can be set as below :

BAS.RTCMEM$ = "data to be saved during deep sleep"

 

And can be read as below :

A$ = BAS.RTCMEM$

 

Note : the RTC memory can hold up to 7680 bytes

DATE - TIME timekeeper

The ESP module normally synchronises its date and time from either of two NTP time servers ("pool.ntp.org" and "time.nist.gov"). Optionally an alternative (eg: intranet) time server can be defined using the [CONFIG] page.

Using these servers the ESP doesn’t require any date/time setting (except the configuration of the Time Zone and DST done using the [CONFIG] page).

 

The timezone is defined as a string likeCET-1CEST,M3.5.0,M10.5.0/3 that describes how the local time must be managed in terms of time shift and DST (summer / winter time).

 

A complete list of timezone strings can be found here : https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv

 

An internal timekeeper has been included if no time server is available, e.g. no available internet access.

This timekeeper starts from 01/01/1970 00:00:00 and counts the seconds since the power on of the module.

If internet connection becomes available later, the internal timekeeper will sync its time with the NTP servers.

 

The time can be sync with the NTP time server at any moment using the command OPTION.NTPSYNC.

 

This time and date can be manually set using the command SETTIME.

The Syntax is :

SETTIME year, month, day, hours, minutes, seconds

 

Example

Set the date to 02 September 2017 at 13:58:12

 

SETTIME 17, 9, 2, 13, 58, 12

 

The time and date can also be manually synchronised to the computer using the "Time Sync" button in the File Manager window of the computer utility ‘tool’ if it has a websocket connection.

 

 

 

WARNING:

In both cases of manual setting, the time and date will default back to 1970 defaults at the next module restart, so will require setting again.

 

For more information about the Time Zones and DST, please consult the following page :

Time Zone and DST

 

It is also possible to connect an RTC (DS1307 or DS3231) to the module.

See the chapter “RTC Module” for more details.

Unix Time functions

The following functions use the “Unix Time Stamp” format :

DATEUNIX(date$), TIMEUNIX(time$), UNIXDATE$(value [,format]), UNIXTIME$(value)

 

The “Unix Time Stamp” is a way to track time as a running total of seconds.

This count starts at the Unix Epoch on January 1st, 1970 at UTC.

Therefore, the unix time is merely the number of seconds between a particular date and the Unix Epoch.

In synthesys :

-       DATEUNIX("01/01/18") returns the number of seconds from 01/01/1970 to the specified date 01/01/2018  (1514764800)

-       TIMEUNIX("12:30:55") returns the number of second since midnight (45055)

-       UNIXDATE$("1532773308") returns 28/07/18

-       UNIXTIME$(1532773308) returns 10:21:48

FAT32 File System

Annex32 includes a FATFS file system hosted on the flash memory chip.

It “emulates” a disk file system enabling it to save and load files in a transparent way.

Depending on the size of the flash chip, the following free space is available :

 

Flash Chip size

Free space available

4M

1MB

[6] 8M

5MB

16M

13MB

 

Annex32 can also use an SD CARD connected as described in the chapter SD CARD ADAPTER.

 

Both the internal FATFS and the SD CARD utilise the FAT32 file system

This means that there are no particular limitations in terms of filename length and directories, compared to the SPIFFS file system limitations hosted in the ESP8266.

Unlike normal variables, filenames and folders are case sensitive.

Annex32 supports SD CARDS up to 16GB.

 

The internal and the external (SDcard) space are mutually exclusive and cannot be accessed at the same time.

By default Annex32 will use the SD, if available, otherwise it will use the internal flash disk space (FATFS).

 

Both the internal FATFS and external SD CARD share the same command and functions.

 

All the file related functions share the same prefix FILE. followed by the specific function.

 

FUNCTIONS / COMMANDS

DESCRIPTION

FILE.COPY(filename$, newfile$)

Copy the file filename$ into the file newfile$

Returns 1 in case of success or 0 if error

FILE.DELETE(filename$)

Delete the file specified by filename$

Returns 1 in case of success or 0 if error

FILE.EXISTS(filename$)

Returns 1 if filename$ exists, otherwise returns 0

FILE.RENAME(oldname$, newname$)

Rename the file oldname$ to  newname$

Returns 1 in case of success or 0 if error

FILE.SIZE(filename$)

Returns the size of the file (in bytes) if the file exist, otherwise returns  -1

FILE.MKDIR(dirname$)

Create a directory specified by dirname$

Returns 1 in case of success or 0 if error

FILE.RMDIR(dirname$)

Remote the directory specified by dirname$

Returns 1 in case of success or 0 if error

FILE.DIR$(path$)

Will search for files and return the names of entries found.

path$ represents the directory name.

path$ can include wildcards characters as ‘*’, ‘.’ and ‘?

The function will return the first entry found.

To retrieve subsequent entries use the function with no arguments. ie, FILE.DIR$.

The return of an empty string indicates that there are no more entries to retrieve.

FILE.READ$(filename$, [line_num] | [start, length])
 

Returns the content of the file filename$.

Specifying line_num, only the corresponding line is read from the file.

If start and length options are specified, the file is read from the start position for length characters, otherwise the complete file is read in one go

The line number starts from 1.

If the line is not existing (reached the end of file), the function will return “_EOF_” to indicate the end of the file.

FILE.APPEND filename$, content$

Append the content of content$ to the file filename$.

If the file does not exist, it will be created.

The file can be read back using the function FILE.READ$(filename$)

File size is only limited by available disk space (internal FFAT or external SD card)

FILE.SAVE filename$, content$
 

Save the content of content$ to the file filename$.

The file can be read back using the function FILE.READ$(filename$)

File size is only limited by available disk space (internal FFAT or external SD card)

FILE.WRITE filename$, content$

Same functionalities as the previous command.

Implemented for homogeneity with other commands

FILE.FROMBASE64 source$, dest$

Convert the file defined ‘source$’ into the file defined in ‘dest$’.

The source file can be in any format but must be encoded in base64 format. Useful for wokwi to store any file in text format

FILE.TOBASE64 source$, dest$

Convert the file defined ‘source$’ into the file defined in ‘dest$’.

The source file can be in any format and will be encoded in base64 format.

FILE.SAVE_IOBUFF

See the chapter I/O buffer for more details

[7] FILE.WRITE_IOBUFF

See the chapter I/O buffer for more details

[8] FILE.APPEND_IOBUFF

See the chapter I/O buffer for more details

FILE.READ_IOBUFF

See the chapter I/O buffer for more details

 

Examples:

 

List all the files in the directory /html

d$ = FILE.DIR$("/html")

While D$ <> ""

  wlog d$

  d$ = FILE.DIR$

Wend

 

File operations

file.save "/test.bas", "The quick brown fox "

wlog "exists", file.exists("/test.bas")

wlog "size", file.size("/test.bas")

file.append "/test.bas", "jumps over the lazy dog"

wlog "size", file.size("/test.bas")

wlog "copy", file.copy("/test.bas", "/AAA.bas")

wlog "size", file.size("/AAA.bas")

wlog "rename", file.rename("/AAA.bas", "/BBB.bas")

wlog "size", file.size("/BBB.bas")

wlog "size", file.size("/AAA.bas")

wlog "read", file.read$("/test.bas")

wlog "delete", file.delete("/BBB.bas")

 

Download files from another module or WEB server

 

The command

FILE.DOWNLOAD url$, file_path$

retrieves a file from a specified URL (url$) and saves it to the local file path (file_path$). This can be used both as a standalone command or as a function that returns a status code, indicating the success or failure of the download operation.

     url$: A string specifying the URL of the file to download. This should be a valid HTTP or HTTPS URL.

     file_path$: A string specifying the local path where the downloaded file should be saved.

When used as a function,FILE.DOWNLOAD returns an integer value that indicates the status of the download:

     1: Download completed successfully.

     -1: Not enough space available to download the file.

     -2: Failed to open the file for writing.

     -3: Failed to create an HTTP client.

     Other HTTP error codes: Various HTTP error codes that may be returned by the server (e.g., 404 for "Not Found", 500 for "Internal Server Error").

This command is also useful for downloading (copying) a file from another Annex RDS module. By providing the appropriate URL, you can easily transfer files between modules, which is especially useful for distributing updates or configurations across multiple devices.

Notes:

     The function handles both HTTP and HTTPS URLs, using a secure client

     The function checks for available space before attempting the download and will abort if there is insufficient space.

     All operations are logged to the serial output for debugging purposes.

Example 1: download from the WEB

FILE.DOWNLOAD "https://updates.cicciocb.com/annex-logo.png", "/logo.png"

or

Wlog FILE.DOWNLOAD("http://updates.cicciocb.com/annex_bee_new.png", "/logo2.png")

 

Example 1: download from another Annex RDS Module with IP: 192.168.1.181

FILE.DOWNLOAD "http://192.168.1.181/program1.bas", "/program1.bas"

or

Wlog FILE.DOWNLOAD("http://192.168.1.181/program1.bas", "/program1.bas")

 

 

print file.download ("https://updates.cicciocb.com/annex-logo.png", "/logo.png")

I/O BUFFERS

The I/O BUFFER is a functionality that gives the capability to hold and manage binary data.

In short, the I/O buffer is a block of RAM memory that can be exchanged as a block or read and written byte per byte.  It overcomes the limitation of strings, which are unable to include the character ASCII 0 (NUL).

It has a defined length and can be freely dimensioned and cleared.

It can be used in the code using the IOBUFF keyword, and Annex exposes 5 I/O buffers numbered from 0 to 4.

The I/O buffers can have any size within the limits of the free RAM memory available.

The main goal of this functionality is to interface with all the functions that require exchanges using binary data.

In the current implementation it can be used with :

-       Files

-       Serial Ports

-       SPI

-       I2C

-       UDP

 

As it is essentially a block  of memory, the first command is IOBUFF.DIM(buff_num, size) that defines its size.

buff_num can span from 0 (first buffer) to 4 (last buffer)

size can span from 0 to the maximum RAM memory available

Example:

IOBUFF.DIM(0, 1000) 'dimension the I/O buffer 0 with 1000 bytes

 

The I/O buffer can be filled with a given set of data directly using the function IOBUFF.DIM

Example:

IOBUFF.DIM(0, 10) = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

IOBUFF.DIM(1, 5) = &h12, &hAA, &h50, &O377, &B10101010

 

As soon as the buffer is dimensioned, the given amount of RAM is reserved for the buffer.

When not required anymore, it can be removed with the command IOBUFF.DESTROY(buff_num)

Example:

IOBUFF.DESTROY(0) 'remove the buffer releasing the memory reserved

 

Note : The I/O buffers are automatically removed each time the program is run.

 

It is possible to know the size of the buffer using the function IOBUFF.LEN(buff_num)

Example

Print IOBUFF.LEN(buff_num) 'print the length of the buffer

 

The I/O buffer can be read byte per byte using the function IOBUFF.READ(buff_num, position)

position can span from 0 (first byte) and the buffer length - 1 (last byte)

 

Example:

Print IOBUFF.READ(0, 4) 'print the byte 4 from the I/O buffer 0

A = IOBUFF.READ(0, 7) ' read in the variable A the byte 7 from the I/O buffer 0

The I/O buffer can be written byte per byte using the command IOBUFF.WRITE(buff_num, position, value)

position can span from 0 (first byte) and the length - 1 (last byte)

value can span from 0 to 255 (byte)

 

The I/O buffers communicate with the other modules using the following syntax:

-   xxxx.READ_IOBUFF(buff_num)

Receive data in the buffer buff_num

 

-   xxxx.WRITE_IOBUFF(buff_num, start, size)

Transmit (send) data from the buffer buff_num starting from the position ‘start’ for ‘size’ bytes

 

-   xxxx.REPLY_IOBUFF(buff_num, start, size)

Reply to the sender data from the buffer buff_num starting from the position ‘start’ for ‘size’ bytes

 

Where xxxx can be :

UDP

SERIAL

SERIAL2

FILE

I2C

SPI

 

Detailed syntax :

UDP.READ_IOBUFF(buff_num)

SERIAL.READ_IOBUFF(buff_num)

SERIAL2.READ_IOBUFF(buff_num)

FILE.READ_IOBUFF(buff_num), filename$ [, position, nb_of_bytes_to_read]

I2C.READ_IOBUFF(buff_num), address, register, nb_of_bytes_to_read

SPI.READ_IOBUFF(buff_num), nb_of_bytes_to_read

 

UDP.WRITE_IOBUFF(buff_num [, start [, size]]), IP$, port

SERIAL.WRITE_IOBUFF(buff_num [, start [, size]])

SERIAL2.WRITE_IOBUFF(buff_num [, start [, size]])

 

FILE.SAVE_IOBUFF(buff_num [, start [, size]]), filename$

FILE.WRITE_IOBUFF(buff_num [, start [, size]]), filename$

FILE.APPEND_IOBUFF(buff_num [, start [, size]]), filename$

 

I2C.WRITE_IOBUFF(buff_num [, start [, size]]), address, register

SPI.WRITE_IOBUFF(buff_num [, start [, size]])

 

UDP.REPLY_IOBUFF(buff_num [, start [, size]]) [,port]

SPI.REPLY_IOBUFF(buff_num [, start [, size]]), (buff_num_reception)

 

The IOBUFFER can be used for sending or receiving data.

Read Operations

When used for receiving data, the syntax is always.READ_IOBUFF(buff_num).

 

When receiving data, it is not necessary to dimension the buffer before as it will be automatically dimensioned depending on the size of the data received. If the buffer was already containing some data, these will be flushed and replaced by the new data.

 

For example, the following command receives all the data available from the serial port 2 in the buffer 3 :

SERIAL2.READ_IOBUFF(3)

 

This command receives the data coming from an UDP connection in the buffer 1:

UDP.READ_IOBUFF(1)

 

Additionally some other arguments may be required.

This command reads 512 bytes from the file data.bin starting from the file position 123 in the buffer 0:

FILE.READ_IOBUFF(0), “/data.bin”, 123, 512

 

This command reads 8 bytes from an I2C device with address 63 from the register 19 in the buffer 4 :

I2C.READ_IOBUFF(4), 63, 19, 8

 

This command reads 32 bytes from the SPI bus in the buffer 2 :

SPI.READ_IOBUFF(2), 32

 

Write Operations

When used for sending data, the syntax is always .WRITE_IOBUFF(buff_num [, start [, size]])

 

When sending data, it is possible to send the entire buffer or only a part of it.

Specifying the optional arguments start and size it is possible to define the part of the buffer to be sent; otherwise, if omitted, the entire buffer will be transferred.

 

For example, the following command sends 10 bytes from the buffer 1 starting from the position 45 to the serial port :

SERIAL.WRITE_IOBUFF(1, 45, 10)

 

This command sends the complete buffer 1 to the serial port 2

SERIAL2.WRITE_IOBUFF(1)

 

This command sends 8 bytes from the buffer 2 starting from the position 128 to the SPI bus

SPI.WRITE_IOBUFF(2, 128, 8)

 

Additionally some other arguments may be required.

 

This command sends 12 bytes from the buffer 1 starting from the position 64 to the UDP on the address 192.168.1.89 and port 8080 :

UDP.WRITE_IOBUFF(2, 128, 8), “192.168.1.89”, 8080

 

This command sends the entire buffer 2 on the same UDP device :

UDP.WRITE_IOBUFF(2), “192.168.1.89”, 8080

 

This command writes the buffer 1 to the file data.bin

FILE.WRITE_IOBUFF(1), “data.bin”

 

This command has the same result and is provided for compatibility with the existing syntax

FILE.SAVE_IOBUFF(1), “data.bin”

 

This command appends 36 bytes from the buffer 0 starting from the position 25 to data.bin

FILE.APPEND_IOBUFF(0, 25, 36), “data.bin”

 

This command sends the buffer 2 to the I2C device with address 63 and register 19 :

I2C.WRITE_IOBUFF(2), 63, 19

 

The same operation but sending only 4 bytes starting from position 0:

I2C.WRITE_IOBUFF(2, 0, 4), 63, 19

 

Special operations

The syntax .REPLY_IOBUFF(buff_num [, start [, size]]) defines some kind of special operations.

 

For example, this command sends the buffer 0 back to the UDP message sender:

UDP.REPLY_IOBUFF(0)

This is the equivalent of UDP.REPLY message$ but with the IOBUFFER

 

Optionally it is also possible specify part of the buffer and the destination port (eg: 5001) as below:

UDP.REPLY_IOBUFF(0, 2, 6), 5001

 

When used with the SPI bus, it transmits and receives at the same time.

As this operation requires 2 buffers, both must be specified.

For example, this command sends the buffer 0 and receive into the buffer 2:

SPI.REPLY_IOBUFF(0), (2)

This command sends 4 bytes from the buffer 0 starting from the position 89 and receive 4 bytes in the buffer 3:

SPI.REPLY_IOBUFF(0, 89, 4), (3)

 

Advanced operations

Several other functions / commands are available for advanced users.

These enable bit, string and hex operations

 

conversion from hex string :

IObuff.FromHex(buff_num, var$ [, pos])

var$ must contain a hex string in the form of "aabbcc1235"

pos is the position where the HEX must be included in the buffer  (0 by default)

A part of the string can be converted in combination with mid$

 

conversion from string:

IObuff.FromString(buff_num, var$ [, pos])

var$ must contain a text string in the form of "This is a text string"

pos is the position where the HEX must be included in the buffer  (0 by default)

A part of the string can be converted in combination with mid$

 

conversion to hex string:

A$ = IObuff.ToHex$(buff_num, [, start [, size]])

returns an hex string in the form of "aabbcc1235"

start and size are optional and define the start and length (like MID$ but the 1st byte is 0)

 

conversion to string:

A$ = IObuff.ToString$(buff_num, [, start [, size]])

returns a text string in the form of "This is a string"

start and size are optional and define the start and length (like MID$ but the 1st byte is 0)

Bit operations

a = IObuff.bit(buff_num, position, bit)

returns the value of the bit of the byte at the position of the buff_num

returns 0 or 1

 

IObuff.setbit(buff_num, position, bit)

set the bit of the byte at the position of the buff_num

 

IObuff.clearbit(buff_num, position, bit)

clear the bit of the byte at the position of the buff_num

 

IObuff.togglebit(buff_num, position, bit)

toggle the bit of the byte at the position of the buff_num

 

Buffer copy

IObuff.copy(dest_buff_num [,pos]) , (source_buff_num, [, start [, size]])

Copy the from source_buff to dest_buff

pos is the position where the source must be copied in the source (0 by default)

start and size define what must be copied (have the same meaning as in .WRITE_IOBUFF)

Code examples :

 

UDP - use the remote controller APP for IOS devices (iphone and Ipad)

 

image

 

 

' I/O buffers example using the RCWController

' available in the IOS app store.

' It uses by default the port 10000

' The APP sends a block of 10 bytes that

' will be printed in the console on the same line

udp.begin 10000

 

' define the place where jump on message reception

onudp received

wait

 

received:

' read the incoming data in the buffer 0

udp.read_iobuff(0)

size = iobuff.len(0)

print "received "; size; " bytes"

for z = 0 to 9

  ' read and print 1 byte at the time on the same line

  print iobuff.read(0, z),

next z

Print ' print an empty line

return

 

 

File read and transfer to the serial port by blocks

 

' I/O BUFFERS example using files

' read a file in blocks of 512 characters

' and send them to the serial port (print)

fileName$ = "/data8.txt"

block_size = 512 ' size of the block to be read

file_size = file.size(fileName$)

print "File size "; file_size

print file_size

for z = 0 to file_size - 1 step block_size

  file.read_iobuff(0), fileName$, z, block_size

  ' send the block on the serial port (print)

  serial.write_iobuff(0)

next 

 

Serial port data logger

 

' I/O BUFFERS example to create a serial data logger

' receive bytes from the serial port and

' write them into the file /mylog.txt

' all the characters will be recorded

' including the ASCII 0 (NUL)

filename$ = "/mylog.txt"

 

' define the place where jump on message reception

onserial received

wait

 

received:

' waits for 10 millisec giving time to receive all the data

pause 10

' read the incoming data in the buffer 0

serial.read_iobuff(0)

size = iobuff.len(0)

print "received "; size; " bytes"

' appends the received data to the file

file.append_iobuff(0), filename$

return

 

 

WIRING

image

 

This diagram shows pin mapping for the popular ESP32 DEV Board module.

(*) pins GPIO6 to GPIO11 are not available.

 

 

Annex 32, as it supports by default the M5stack wiring, assumes the following pins already allocated/dedicated

 

PIN

FUNCTION

DESCRIPTION

32

PWM BL TFT

Backlight TFT display

33

RST TFT

RST pin TFT

27

D/C TFT

D/C pin TFT

14

CS TFT

CS pin TFT

23

SPI MOSI

SPI MOSI pin (shared with SD and TFT)

19

SPI MISO

SPI MISO pin (shared with SD and TFT)

18

SPI SCK

SPI CLOCK pin (shared with SD and TFT)

4

CS SDCARD

CS pin SDCARD

0

CS TFT TOUCH

CS pin Touchscreen (from the TFT)

 

 

 

3

RX0

Serial Port RX pin

1

TX0

Serial Port TX pin

 

 

 

25

SPEAKER

Speaker or mono audio output

21

SDA I2C

I2C SDA pin

22

SCL I2C

I2C SCL pin

 

 

 

2

I2S DATA

Audio DAC I2S DATA pin

5

I2S BCLK

Audio DAC I2S BCLK pin

26

I2S LRCK

Audio DAC I2S LRCK pin

16

PSRAM

Optional PSRAM

17

PSRAM

Optional PSRAM

 

 

image

 

DIGITAL I/O

 

Pin numbers correspond directly to the ESP32 GPIO pin numbering.

The function of the pin (input / output) must be defined before using the function PIN.MODE as below :

 

To define the pin 5 as input :

PIN.MODE 15, INPUT

 

To define the pin 4 as input with a pullup:

PIN.MODE 4, INPUT, PULLUP

 

To define the pin 4 as input with a pulldown:

PIN.MODE 4, INPUT, PULLDOWN

 

To define the pin 2 as output

PIN.MODE 2, OUTPUT

 

To define the pin 2 as output open collector

PIN.MODE 2, OUTPUT, 1

 

Pins may also serve other functions, like Serial, I2C, SPI.

These functions are normally activated by the corresponding library.

 

The value from a pîn can be read as shown below :

A = PIN(5) read from GPIO5 pin

 

The pin value can be set as below

PIN(2)= 0 set 0 on the GPIO2 pin

 

The pin value (0 or 1) can also be easily toggled by subtracting it from 1 (because 1-0=1 and 1-1=0), eg:

PIN(2)= 1 - PIN(2) toggles the value of GPIO2 pin

 

This part is applicable only to the “classic” ESP32 as the other members of the family have different H/W characteristics and

Although pin numbers can be from 0 to 39, gpio pins 6 to 11 should not be used because they are connected to flash memory chips on most modules. Trying to use these pins as IOs will likely cause the program to crash.

 

Pins 34 to 39 are INPUT only and cannot be configured as PULLUP or PULLDOWN.

Pins 0 to 33 can be INPUT, OUTPUT INPUT, PULLUP or INPUT, PULLDOWN.

Note:

If the module is equipped with PSRAM, the gpio pins 16 and 17 are reserved and must not be used.

 

The command PIN.STRENGTH can be used to define the drive capability of the output pins from a scale from 0 to 3.

For example, the following command set the pin 15 with a drive capability of 2.

PIN.STRENGTH 15, 2

The ESP32 provides four drive strength levels for its GPIO pins:

         0: Weakest drive strength, approximately 5 mA.

         1: Low drive strength, approximately 10 mA.

         2: Medium drive strength, approximately 20 mA.

         3: Maximum drive strength, approximately 40 mA.

PIN SERIAL SHIFTING

Annex  supports two commands for serially shifting data:

-       PIN.SHIFTOUT for sending data

-       PIN.SHIFTIN for receiving data.

 Both commands allow control over bit order, bit count, and timing delays.

These commands are in particular useful to control external shift registers or simple SPI bus devices.

The PIN.SHIFTOUT command is used to send data out serially through a specified data pin. It shifts out data bits one at a time, synchronised with a clock signal. The command allows you to specify the order in which bits are shifted (least significant bit first or most significant bit first), the number of bits to shift, and the timing delay between each bit. This command drives the data pin high or low according to the current bit in the data, and toggles the clock pin to indicate when each bit should be read by the receiving device.

Syntax:

PIN.SHIFTOUT pin_data, pin_clk, data [, bit_order] [, nb_bits] [, delay_us]

Parameters:

     pin_data:

     Description: GPIO pin number used for the data line.

     pin_clk:

     Description: GPIO pin number used for the clock line that synchronises the data transfer.

     data :

     Description: Data to be shifted out.

     Bit_order (optional):

     Description: Specifies the bit order for shifting data. Possible values:

     0 (default) : LSBFIRST Least Significant Bit first.

     1           : MSBFIRST Most Significant Bit first.

     Nb_bits (optional):

     Description: Number of bits to shift out from the data. Default is 8 bits.

     Delay_us (optional):

     Description: Delay in microseconds between each bit shift. Default is 1 microsecond.

Details:

     This command uses critical section management to ensure atomic operations during the data shift process. However, because it relies on software delays, the timing may not be very precise, particularly  for delays shorter than 10 microseconds

     Bit Order: The bit order determines whether the least significant bit (LSBFIRST) or the most significant bit (MSBFIRST) is shifted out first.

     Number of Bits: Specifies how many bits from the data value will be shifted out. If not specified, the default is 8 bits.

     Delay: Introduces a delay between the setting of the data pin and toggling of the clock pin to control the timing of the data shift.

Example:

 

PIN.SHIFTOUT 13, 14, 0xFF   ' Shift out 8 bits from data 0xFF

PIN.SHIFTOUT 13, 14, 0xFF, 1' Shift out 8 bits from data 0xFF with MSBFIRST

PIN.SHIFTOUT 13, 14, 0xFFFF, 1, 16, 2  ' Shift out 16 bits from data 0xFFFF with MSBFIRST, 2 µs delay

 

 

The PIN.SHIFTIN function is used to receive data serially through a specified data pin. It shifts in data bits one at a time, using a clock signal to determine when to sample the data pin. Similar to PIN.SHIFTOUT, it allows you to specify the bit order, the number of bits to shift, and the timing delay between each bit. This  function reads the state of the data pin in sync with the clock signal, capturing each bit as it arrives and storing it in a variable.

Syntax:

PIN.SHIFTIN( pin_data, pin_clk [, bit_order] [, nb_bits] [, delay_us] )

Parameters:

     pin_data:

     Description: GPIO pin number used for the data line.

     pin_clk :

     Description: GPIO pin number used for the clock line that synchronises the data reading.

     Bit_order (optional):

     Description: Specifies the bit order for shifting data. Possible values:

     0 (default) : LSBFIRST Least Significant Bit first.

     1           : MSBFIRST Most Significant Bit first.

     Nb_bits (optional):

     Description: Number of bits to read from the data line. Default is 8 bits.

     Delay_us (optional):

     Description: Delay in microseconds between each bit read. Default is 1 microsecond.

Details:

     This command uses critical section management to ensure atomic operations during the data shift process. However, because it relies on software delays, the timing may not be very precise, particularly  for delays shorter than 10 microseconds

     Bit Order: Determines whether the least significant bit (LSBFIRST) or the most significant bit (MSBFIRST) is read first.

     Number of Bits: Specifies how many bits will be read from the data line. If not specified, the default is 8 bits.

     Delay: Introduces a delay between each bit read, allowing for control over timing and synchronisation.

Example:

 

A = PIN.SHIFTIN(21, 22) ' Shift in 8 bits from data pin 21 with clock pin 22

B = PIN.SHIFTIN(13, 14, 1) ' Shift in 8 bits from data pin 13 with clock pin 14, reading MSBFIRST

C = PIN.SHIFTIN(13, 14, 1, 16, 2)  ' Shift in 16 bits from data pin 13 with clock pin 14, reading MSBFIRST, 2 µs delay

 

PIN INTERRUPTS

The INTERRUPT command permits to trigger an event when the signal on an input pin changes.

The interrupt is triggered BOTH when the signal goes from LOW to HIGH and HIGH to LOW.

Therefore a momentary pulse actually generates 2 interrupts which need testing for Hi or Lo as appropriate.

Example:

 

pin.mode 12, input   ' set pin 12 as input
interrupt 12, change_input  ' set interrupt on pin 12

wait

change_input:
if pin(12) = 0 then return   ' if the pin is low, returns back
print "The pin changed to HIGH"
Return

 

It is possible to add an optional parameter, mode, that specify if the interrupt must be generated on the rising edge, the falling edge or on change:

Syntax:

INTERRUPT pin_no, {OFF | label} [, mode]

 

Parameters:

- pin_no: The input pin number for which the interrupt is being defined. It must be an integer value ranging from 0 to the maximum pin number supported by the specific variant of the ESP32 (-s2, -c3, -s3).

- label: The branch label to which the program will jump when the designated input pin signal changes. It must be a valid label in the program's context.

- OFF: Use this keyword to remove the interrupt associated with the specified input pin.

- mode (optional): An integer parameter or keyword specifying the trigger condition for the interrupt. It can take one of the following values:

  - 1, RISING: Rising edge trigger

  - 2, FALLING: Falling edge trigger

  - 3, CHANGE: Any change in signal trigger (default if 'mode' parameter is omitted)

 

Examples:

-       To set up an interrupt that jumps to the 'PIN5_CHANGE' label when a rising edge is detected on pin 5:

 

INTERRUPT 5, PIN5_CHANGE, RISING

 

-        To remove the interrupt associated with pin 5:

INTERRUPT 5, OFF

 

-       To set up an interrupt that jumps to the 'PIN13_TRANSITION' label when any change is detected on pin 13:

INTERRUPT 13, PIN13_TRANSITION , CHANGE

 

-       To set up an interrupt without specifying the trigger mode (defaulting to any change in signal) that jumps to the 'PIN7_UPDATE' label when pin 7 signal changes:

INTERRUPT 7, PIN7_UPDATE

 

Note:

-       Interrupts can provide a way to respond to external events asynchronously.

-       The optional 'mode' parameter allows you to customize the trigger condition for the interrupt to match your specific application requirements.

The input pin (pin_no) must be previously configured as an input before setting up the interrupt.

Ensure that the specified 'pin_no' and 'label' are valid within the scope of your program.

The maximum pin number supported by the ESP32 variant should be considered based on the specific model (-s2, -c3, -s3).

Analog inputs

Annex32  has 8 ADC pins with 12 bits resolution which are available to users.

The function ADC(pin) can be used to read voltage on the pins defined in the table below.

 

GPIO Pins Available as Analog Input

32

33

34

35

36

37

38

39

 

To read the voltage applied at the pin, the function ADC can be used as below :

 

print ADC(39)' read voltage from the pin 39

 

The voltage range is  0 ... 3.3V and the corresponding range returned by the function is 0 … 4095.

 

NOTE: When using the function ADC,the pin is automatically configured as an Analog Input

TOUCH inputs

Annex32 supports an additional ESP32 feature, the capacitive touch.

With this feature, it is possible to activate inputs with your fingers with just one wire attached to the pin.

The function PIN.TOUCH(pin) can be used to read the touch value on the pins defined in the table below.

 

GPIO Pins Available as Touch Input

0

2

4

12

13

14

15

27

32

33

 

The function PIN.TOUCH(pin) returns a value that drops as soon as the pin is touched.

Normal values are around 70 when not touched and lower than 20 when touched.

 

'Pin Touch example

'Place a wire on pin 13 and look how the value changes when touching it

while 1

  print pin.touch(13)

  pause 100

wend

end

 

Analog outputs

Annex32  has 2 DAC output pins with 8 bits resolution which are available to users.

This function is available only on  the pin GPIO25 and GPIO26

The function PIN.DAC pin, value can be used to set the output voltage on the pin.

 

The output voltage is approximately 0V @ value=0 and 3.3V @ value=255

 

'DAC output example

PIN.DAC 25, 128 'Set the pin 25 at ~1.65V

PIN.DAC 26, 64  'Set the pin 26 at ~0.82V

 

NOTE: When using the command PIN.DAC,the pin is automatically configured as an Analog Output

Hardware interfaces:

The ESP32 contains several H/W interfaces that can be controlled by Annex32 WI-Fi using specific commands and functions.

PWM

This functionality permits to control the output duty cycle of any pin, acting like an analog output.

There are 16 channels available where each channel can be connected to any output pin.

 

To use it, the function must first be configured using the command PWM.SETUP and then the value can be set using the command PWM.OUT.

 

The frequency and the resolution can be defined individually for each channel.

The resolution can be from 1 to 15 bits.

The maximal frequency is 80000000 / 2^resolution

 

This table resumes the maximal frequency available in function of the resolutions and the associated range:

 

RESOLUTION (BITS)

MAX FREQUENCY

VALUE RANGE

1

40000000

0 ... 1

2

20000000

0 ... 3

3

10000000

0 ... 7

4

5000000

0 .. 15

5

2500000

0 .. 31

6

1250000

 0 … 63

7

625000

0 … 127

8

312500

0 … 255

9

156250

0 … 511

10

78125

0 … 1023

11

39063

0 … 2047

12

19531

0 … 4095

13

9766

0 … 8191

14

4883

0 … 16383

15

2441

0 … 32767

 

All the output pins can be used for the PWM (the pins from GPIO0 to GPIO33).

As there are 16 channels, up to 16 individual output pins can be used.

If using the M5Stack, the channels 0 and 7 are already reserved and attached to the pins 25 and 32.

In this case the channels 0 and 7 must be avoided.

 

To setup an output pin as a PWM output the following command must be used :

PWM.SETUP pin, channel, default_value,  [,frequency] [,resolution]

 

For example, to define a PWM output at 5KHz with 12 bits of resolution on the pin GPIO5 the command is :

PWM.SETUP 5, 1, 2048, 5000, 12 ‘ pin 5, channel 1, output value at 2048 (50%), 5KHz, 12 bits

As the resolution is set at 12 bits, the range will be from 0 to 4095 (hence 2048 is 50%).

 

To define a PWM output at 10KHz with 8 bits on the pin GPIO22, the command is :

PWM.SETUP 22, 2, 128  ‘ pin 22, channel 2, value 128 ( 50%) , freq 10 KHz (default), resolution 8 bits (default)

 

As soon as the command PWM.SETUP is done, the PWM output can simply be changed with the command :

 

PWM.OUT channel, value

 

For example, the command

PWM.OUT 1, 1000

Set the channel 1 (associated with the pin 5 in the previous command) at 1000.

 

And the command

PWM.OUT 2, 10

Set the channel 2 (associated with the pin 22 in the previous command) at 10

 

To disconnect the pin from the PWM output the command is :

PWM.SETUP pin, OFF.

 

For example the command

PWM.SETUP 5, OFF

Disconnect the pin 5 fro the PWM

 

NOTE for the M5stack:

The channel 0 is dedicated to the internal speaker (pin 25)

The channel 7 is dedicated to the TFT backlight (pin 32)

 

 

SERVO

This functionality exposes the ability to control RC (hobby) servo motors.

There are no special commands dedicated as the servo can simply be used by setting a PWM pin with a 50Hz frequency.

For example, the following command :

PWM.SETUP 17, 1, 150, 50, 12

Defines the pin 17 with the pwm channel 1 with a default value of 150 (frequency at 50 Hz and resolution at 12 bits).

 

The output can then be set with the command

PWM.OUT 1, 307‘ channel 1 set at 90°

 

 

A typical servo motor expects to be updated every 20 ms with a pulse between 1 ms and 2 ms, or in other words, between a 5 and 10% duty cycle on a 50 Hz waveform. With a 1.5 ms pulse, the servo motor will be at the natural 90 degree position. With a 1 ms pulse, the servo will be at the 0 degree position, and with a 2 ms pulse, the servo will be at 180 degrees. You can obtain the full range of motion by updating the servo with any value in between.

image

Using a 12 bits resolution (max value = 4095 for 20 msec pulse (1/50 Hz)), the theoretical values should be :

  0° -> 1 msec -> 1/20 * 4096 = 205

90° -> 1.5 msec -> 1.5/20 * 4096 = 307

180° -> 2 msec -> 2/20 * 4096 = 409

 

Generating Tones

This section introduces the functionality of generating tones using the PWM.TONE command.

This command provides the means to create tones using the PWM.

 

Syntax

The PWM.TONE command follows a concise syntax:

 

PWM.TONE channel, frequency [, duration_ms]

 

-       channel: Selects a specific PWM channel for tone output.

-       frequency: Specifies the frequency of the generated tone in Hertz (Hz).

-       duration_ms: (Optional) Defines the duration of the tone in milliseconds (ms). If omitted, the tone will play indefinitely.

 

Practical Examples

 

Example 1: Single Tone

To generate a continuous tone at 1000 Hz on channel 1, use:

 

PWM.TONE 1, 1000

This command establishes a steady 1000 Hz tone on channel 1.

 

Example 2: Timed Tone

For a tone lasting 200 ms at 2000 Hz on channel 2, apply:

 

PWM.TONE 2, 2000, 200

Channel 2 will produce a tone at 2000 Hz for a duration of 200 milliseconds.

 

Example 3: Stopping a Tone

To halt an ongoing tone on channel 1, use the following command

 

PWM.TONE 1, 0  command

 

Prior to using PWM.TONE, ensure proper channel setup using the PWM.SETUP command, for example using PWM.SETUP 5, 1, 0. (pin 5, channel1, default output at 0)

 

Be mindful of the channel's frequency range and resolution limits.

I2S BUS

I²S (Inter-IC Sound), is an electrical serial bus interface standard used for connecting digital audio devices together. It is used to communicate PCM audio data between integrated circuits in an electronic device.

The I²S bus separates clock and serial data signals, resulting in a lower jitter than is typical of communications systems that recover the clock from the data stream.

Despite the name similarity, I²S is unrelated to the bidirectional I²C (I2C) bus.

 

The bus consists of three lines:

Bit clock line

-       Typically called "bit clock (BCLK)".  PIN GPIO5

Word clock line

-       Typically called "left-right clock (LRCLK)"  or “Word Select (WSEL)”. PIN GPIO26

Data line

-       Typically called "serial data (SD)". Can also be called (SDIN, SDOUT or DATA). PIN GPIO2

 

The typical use of the I2S is to connect an external DAC to provide a High Quality stereo sound output.

 

Using the PLAY.xxx commands, it will be possible to play MP3 and WAV files directly from the disk.

 

Any generic I2S DAC can be used.

Annex32 has been successfully tested with the PC5102A and the UDA1334A

 

imageUDA1334A I2S DAC

 

 

 

image

 

imagePCM5102A I2S DAC

 

NOTE:

This module requires the following setting (solder joints)

Component side:

Next to the sck connection

Back side:

1 set to L

2 set to L

3 set to H

4 set to L

 

image

 

It is now also possible to use the CODEC ES8388 using the command OPTION.ES8388

SPEAKER OUTPUT

The M5stack contains an internal speaker connected, via an audio amplifier, to the pin GPIO25.

This permits the generation of audio signals using the internal 8 bits DAC.

Using the PLAY.xxx commands, it will be possible to play MP3 and WAV files directly from the disk.

 

It is possible to connect an earphone directly on the output between the pin GPIO25 and the ground but the best is to connect a little audio amplifier.

 

NOTE: it is recommended to put a capacitor ( ~100nF) between the GPIO25 and the audio amplifier in order to remove the DC component from the audio signal.

 

I2C BUS

The I²C bus allows the module to connect to I²C devices.

 

I²C uses only two bidirectional open-drain lines, Serial Data Line (SDA) and Serial Clock Line (SCL), pulled up with resistors (typically 4.7K to 10K).

 

The I²C has a 7 bit address space permitting, theoretically, to connect up to 126 I/O devices.

 

The maximal number of nodes is limited by the address space and also by the total bus capacitance of 400 pF, which restricts practical communication distances to a few meters. The relatively high impedance and low noise immunity requires a common ground potential, which again restricts practical use to communication within the same PC board or small system of boards.

 

The current implementation is master mode @ 100Khz by default.

 

The SDA and SCL pins can be freely defined using the command I2C.SETUP sda_pin, scl_pin.

For example, to define pins 21(SDA) and 22(SCL) the command is :

I2C.SETUP 21, 22

 

It is important to provide correct pullup resistors on these lines; values between 4.7K to 10K should be appropriate.

 

The commands available are :

I2C.BEGIN, I2C.END, I2C.REQFROM, I2C.SETUP, I2C.WRITE

The functions available are :

I2C.LEN, I2C.READ, I2C.END

 

There are also other advanced functions / commands to simplify exchanges with the i2c bus.

The advanced commands available are :

I2C.READREGARRAY, I2C.WRITEREGBYTE,I2C.WRITEREGARRAY

 

The advanced functions available are :

I2C.READREGBYTE[9] [10] 

 

The I2C bus can also be used with the IO Buffers ( look at the dedicated chapter)

 

As all the devices can have a "not well" determined address, please find here a little i2c scanner program which returns the address of all the devices found connected to the bus

'I2C Address Scanner

'print in the console the address of the devices found

I2C.SETUP 21, 22  ' set I2C port on pins 21 and 22

 

for i = 0 to 120

  i2c.begin i

  if i2c.end = 0 then

     print "found "; i , hex$(i)

     pause 10

  end if

next i

 

end

 

 

PCF8574 Module

This is an example of connection of a module with PCF8574 bought on Ebay at less than 2€ 

 

image

This drawing shows how this module must be connected to the ESP32.

It provides 8 digital inputs or outputs.

image

This is an example of code that "drives" this module:

I2C.SETUP 21, 22  ' set I2C port on pins 21 and 22

device_address = 32  'set to module i2c address

'Write from 0 to 255 on the module

'Each pin will blink at different frequency

For i = 0 to 255

PCF8574_write i

Next i

 

'Read all the inputs

'The result is printed into the serial console

' put all the inputs at pullup state

PCF8574_write 255

 

r = 0

For i = 0 to 1000

  PCF8574_read r

  Print r

Next i

End

 

sub PCF8574_write(x)

  i2c.begin device_address

  i2c.write x

  i2c.end

end sub

 

sub PCF8574_read(x)

  i2c.begin device_address

  i2c.reqfrom device_address, 1

  x = i2c.read

  i2c.end

end sub

 
ADS1115 Module

This is another example of connection of a module with ADS1115 bought on Ebay at less than 2€.

This is a 16 Bit ADC 4 channel Module with Programmable Gain Amplifier.

imageimage

 

Because the module already contains two 10K I2C pullups, no external resistors are required

 

image

 

As this device is quite simple to interface, it can be directly driven using a “driver” written in basic.

' ADS1115 Driver for Annex

' datasheet http://www.ti.com/lit/ds/symlink/ads1115.pdf

' ADS1115 Registers

ADS1115_ADDRESS = &h48

ADS1115_CONV_REG = 0 : ADS1115_CONF_REG = 1

ADS1115_HI_T_REG = 2 : ADS1115_LO_T_REG = 3

 

i2c.setup 21, 22 ' set I2C bus

 

' Set the ADS1115 with :

'    AINp = AIN0 and AINn = AIN1

'    FSR = ±4.096 V

'    16 SPS

ADS1115_setup 0, 1, 1

 

' scale in volt

scale = 4.096 / 32768

 

v = 0

for i = 0 to 100000

 ADS1115_read v  ' read from the module

 print v * scale

next i

 

end

 

'---------------------------------------------------------

' INPUT MULTIPLEX :

' AINp is the input positive

' AINn is the input negative

'0 : AINp = AIN0 and AINn = AIN1

'1 : AINp = AIN0 and AINn = AIN3

'2 : AINp = AIN1 and AINn = AIN3

'3 : AINp = AIN2 and AINn = AIN3

'4 : AINp = AIN0 and AINn = GND

'5 : AINp = AIN1 and AINn = GND

'6 : AINp = AIN2 and AINn = GND

'7 : AINp = AIN3 and AINn = GND

 

'GAIN

'0 : FSR = ±6.144 V

'1 : FSR = ±4.096 V

'2 : FSR = ±2.048 V

'3 : FSR = ±1.024 V

'4 : FSR = ±0.512 V

'5 : FSR = ±0.256 V

'6 : FSR = ±0.256 V

'7 : FSR = ±0.256 V

 

'DATA RATE

'0 : 8 SPS

'1 : 16 SPS

'2 : 32 SPS

'3 : 64 SPS

'4 : 128 SPS

'5 : 250 SPS

'6 : 475 SPS

'7 : 860 SPS

sub ADS1115_setup(inp_mux, gain, rate)

 local conf

 conf = (inp_mux << 12) or (gain << 9) or (rate << 5) or 3 ' + disable comp

 'use the IO Buffer 0 for writing

 iobuff.dim(0,2) =  (conf and &hff00) >> 8 , conf and &hff

 i2c.write_iobuff(0), ADS1115_ADDRESS, ADS1115_CONF_REG

end sub

 

sub ADS1115_read(ret)

 'use the IO Buffer 0 for reading

 i2c.read_iobuff(0), ADS1115_ADDRESS, ADS1115_CONV_REG, 2

 if iobuff.len(0) = 0 then

   print "No communication"

   ret = 0

 else

   ret = iobuff.read(0, 0) << 8 + iobuff.read(0, 1)

 end if

 if ret > 32768 then ret = ret - 65536

end sub

 

 

MCP23017 Module

This is another example for connecting an I2C module, an MCP20S17 bought on Ebay at less than 2€.

This module provides 16 GPIO pins that can be used as digital inputs or outputs.

image

 

Because the module already contains two 10K I2C pullups, no external resistors are required

 

image

 

As this device is quite simple to interface, it can be directly driven using a “driver” written in basic.

' MCP23017 Driver for Annex

' datasheet http://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf

I2C.SETUP 21, 22  ' set I2C port on pins 21 and 22

device_address = 32  'set to module i2c address

 

'MCP23017 internal registers

IODIRA   = 0  : IODIRB   = 1 : IPOLA   = 2  : IPOLB   = 3

GPINTENA = 4  : GPINTENB = 5 : DEFVALA = 6  : DEFVALB = 7

INTCONA  = 8  : INTCONB  = 9 : IOCONA  = 10 : IOCONB  = 11

GPPUA    = 12 : GPPUB   = 13 : INTFA   = 14 : INTFB   = 15

INTCAPA  = 16 : INTCAPB = 17 : GPIOA   = 18 : GPIOB   = 19

OLATA    = 20 : OLATB   = 21

 

i2C.WriteRegByte device_address, IOCONA, &h08 ' init MCP23S17 with bit HAEN

i2C.WriteRegByte device_address, IODIRA, &hFF ' all PORT A pins as input

i2C.WriteRegByte device_address, GPPUA,  &hff ' set PORT A pullup on all pins

i2C.WriteRegByte device_address, IODIRB, &h00 ' all PORT B pins as output

 

r = 0

for z = 0 to 255

  I2C.WriteRegByte device_address, GPIOB, z  ' pulse all GPIOB pins

  r = i2C.ReadRegByte(device_address, GPIOA) ' read  all GPIOA pins

  print r

  pause 100

next z

 

 

SPI BUS

The SPI bus allows the module to connect to SPI devices.

 

The Serial Peripheral Interface bus (SPI) is a synchronous serial communication interface used for short distance communication between devices.

SPI devices communicate in full duplex mode using a master-slave architecture where the ESP32 is the master. The ESP32 generates the frame for reading and writing.

Multiple slave devices are supported through selection with individual chip select (CS) lines.

The SPI bus utilise four logic signals:

 

SIGNAL

DESCRIPTION

I/O PIN

SCLK

Serial Clock (output from the ESP32)

GPIO18

MISO

Master Input Slave Output (data input to the ESP32)

GPIO19

MOSI

Master Output Slave Input (data output from the ESP32)

GPIO23

CS

Chip Select (often active low, output from the ESP32)

Any output pin, controlled automatically

 

Because these pins are allocated by default, they may not not be available, by default, to be used as generic GPIO pins.

For that the command SPI.STOP enable to recover the control on these I/O pins

 

CS pin

As many devices can be connected in parallel, sharing the same SCLK, MISO and MOSI signals, each device is controlled individually using an individual CS signal.

As Annex32 implements multitasking, in order to guarantee that the CS signal is generated in phase with the data to be transferred, the CS pin is managed automatically.

The command SPI.CSPIN pin [, polarity] permits the pin associated with the device to be controlled. Additionally, it permits to define the polarity  as 0 = active low (default) and 1 = active high.

 

SPI Mode: Polarity and Clock Phase

The SPI interface defines no protocol for data exchange, limiting overhead and allowing for high speed data streaming. Clock polarity (CPOL) and clock phase (CPHA) can be specified as ‘0’ or ‘1’ to form four unique modes to provide flexibility in communication between master and slave as shown below :

 

image

If CPOL and CPHA are both ‘0’ (defined as Mode 0) data is sampled at the leading rising edge of the clock. Mode 0 is by far the most common mode for SPI bus slave communication.

If CPOL is ‘1’ and CPHA is ‘0’ (Mode 2), data is sampled at the leading falling edge of the clock. Likewise, CPOL = ‘0’ and CPHA = ‘1’ (Mode 1) results in data sampled on the trailing falling edge and CPOL = ‘1’ with CPHA = ‘1’ (Mode 3) results in data sampled on the trailing rising edge.

The table below summarizes the available modes.

 

Mode

CPOL

CPHA

0

0

0

1

0

1

2

1

0

3

1

1

 

The data can also be sent MSB first or LSB first.

This is defined as bit order and is MSB first by default

 

Even if the chip is able to achieve 80Mhz, the maximum realistic SPI speed is 40Mhz.

 

The commands available are :

SPI.SETUP speed [,data_mode [, bit_order]]

SPI.CSPIN pin [, polarity]

The functions available are :

ret = SPI.BYTE(byte)

a$ = SPI.STRING$(data$, len)

a$ = SPI.HEX$(datahex$, len)

As said previously,  because the ESP32 uses multitasking, it is impossible to warrant the exclusive use of the SPI bus during the execution of the script (it could be used by the SD card or the TFT, for example).

For this reason, the CS pin is managed internally by Annex directly by the SPI functions.

This is defined with the command SPI.CSPIN pin [, polarity]

The optional parameter  polarity  defines if the CS signal must be active low (0 = default) or active high (1).

This command will set the pin automatically as output.

 

The SPI bus can also be used with the IO Buffers ( look at the dedicated chapter)

 

Look at the examples below for more details:

74HC595 Module

This is an example of connection of a module with 74HC595 bought on Ebay at less than 2€ 

image

 

This drawing shows how this module must be connected to the ESP8266.

It provides 8 digital outputs.

image

This is an example of code that "drives" this module:

 

'Write from 0 to 255 on the module

'Each pin will blink at different frequency

 

spi.setup 100000  ' set the SPI port at 100KHz

SPI.CSPIN 15, 1 ' defines the pin 15 as CS active high

for i = 0 to 255

  r = spi.byte(i)

next i

 

end

 

 

MCP23S17 Module

This is another example for connecting an SPI module, an MCP23S17 bought on Ebay at less than 2€.

This module provides 16 GPIO pins that can be used as digital inputs or outputs. 

imageimage

 

image

 

As this device is quite simple to interface, it can be directly driven using a “driver” written in basic.

This is an example using the SPI pins and the GPIO15 as CS signal

 

' MCP23S17 Driver for Annex

' datasheet http://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf

 

spi.setup 1000000

 

'MCP23S17 SPI address

MCP23S17_ADDR = &h40  ' assumes A2, A1, A0 to GND

'MCP23S17 internal registers

IODIRA   = 0  : IODIRB   = 1 : IPOLA   = 2  : IPOLB   = 3

GPINTENA = 4  : GPINTENB = 5 : DEFVALA = 6  : DEFVALB = 7

INTCONA  = 8  : INTCONB  = 9 : IOCONA  = 10 : IOCONB  = 11

GPPUA    = 12 : GPPUB   = 13 : INTFA   = 14 : INTFB   = 15

INTCAPA  = 16 : INTCAPB = 17 : GPIOA   = 18 : GPIOB   = 19

OLATA    = 20 : OLATB   = 21

 

MCP23S17_WRITE IOCONA, &h08 ' init MCP23S17 with bit HAEN

MCP23S17_WRITE IODIRA, &h00 ' all PORT A pins as output

MCP23S17_WRITE IODIRB, &hff ' all PORT B pins as input

MCP23S17_WRITE GPPUB , &hff ' all PORT B pins as pullup

 

v = 0

for i = 0 to 255

 for z = 0 to 255

   MCP23S17_WRITE GPIOA, z ' pulse all GPIOA pins

   MCP23S17_READ  GPIOB, v ' read  all GPIOB pins

   print v

 next z

next i

End

 

' function for read / write the MCP23S17

sub MCP23S17_WRITE(register, value)

 SPI.CSPIN 15

 a = SPI.byte(MCP23S17_ADDR)

 a = SPI.byte(register)

 a = SPI.byte(value)

end sub

 

sub MCP23S17_READ(register, value)

 local a

 SPI.CSPIN 15

 a = SPI.byte(MCP23S17_ADDR or 1)

 a = SPI.byte(register)

 value = SPI.byte(0)

end sub

CAN BUS

A Controller Area Network (CAN bus) is a robust vehicle bus standard designed to allow microcontrollers and devices to communicate with each other's applications without a host computer. It is a message-based protocol, designed originally for multiplex electrical wiring within automobiles to save on copper, but can also be used in many other contexts.

 

The Annex32 implements the support for standard CAN 2.0A (11-bit identifier) and CAN 2.0B (29-bit identifier).

 

To interface with an external CAN BUS, the ESP32  requires the use of a CAN  transceiver like the TJA1050, the MCP2551 or the SN65HVD230.

 

For example this module is based on the chip TJA1050 and can be bought for around 1€ on the internet.

image

 

Annex32 supports the transmission and reception of CAN BUS frames.

The bus can be initialised in NORMAL mode, NO ACK mode and LISTEN ONLY mode

The following BUS speeds are supported 25, 50, 100, 125, 250, 500, 800, 1000 (Kbits / sec).

CAN.SETUP

To use the CAN BUS, it must be initialised before using the function CAN.SETUP :

ret = CAN.SETUP(speed, pin_tx, pin_rx, [can_mode [, filter_code, filter_mask]])

where :

speed        is the bus speed in Kbit/sec and can be 25, 50, 100, 125, 250, 800, 1000

pin_tx               is the pin of the ESP32 used for the TX signal (connected to the transceiver)

pin_rx               is the pin of the ESP32 used for the RX signal (connected to the transceiver)

can_mode     can be 0 (NORMAL), 1 (NO ACK) or 2 (LISTEN ONLY). Optional, defaults to 0 (NORMAL)

filter_code  is the reception filter code. Optional, defaults to &h00000000 (32 bits)

filter_mask  is the reception filter mask. Optional, defaults to &hFFFFFFFF (32 bits)

 

The function returns 0 if OK or another value in case of error

 

can_mode represents how the ESP32 interfaces with the BUS

 

can_mode

DESCRIPTION

NORMAL

Normal operating mode where CAN controller can send/receive/acknowledge messages

NO ACK

Transmission does not require acknowledgment. Use this mode for self testing

LISTEN ONLY

The CAN controller will not influence the bus (No transmissions or acknowledgments) but can receive messages

 

filter_code and filter_mask can be used to filter messages of a particular ID.

[11] 

Example:

ret = CAN.SETUP(500, 4, 5, 1) 'Set the bus at 500Kb/sec in NO ACK mode using the pins 4 and 5

CAN.INIT

Alternatively, the CAN BUS can be initialised using the function CAN.INIT; this function has the same functionality such as the CAN.SETUP but gives more control on the BUS timing (for advanced users) :

ret = CAN.INIT(pin_tx, pin_rx, can_mode, brp, tset_1, tseg_2, sjw, triple_sampling, [filter_code, filter_mask])

where :

pin_tx               is the pin of the ESP32 used for the TX signal (connected to the transceiver)

pin_rx               is the pin of the ESP32 used for the RX signal (connected to the transceiver)

can_mode     can be 0 (NORMAL), 1 (NO ACK) or 2 (LISTEN ONLY)

brp          is the Baudrate prescaler (i.e., APB clock divider) can be any even number from 2 to 128

tset_1       is the Timing segment 1 (Number of time quanta, between 1 to 16)

tset_2       is the Timing segment 2 (Number of time quanta, 1 to 8)

sjw          is the Synchronization Jump Width (Max time quanta jump for synchronize from 1 to 4)

triple_samp  can be 1 to enable the triple sampling when the CAN controller samples a bit

filter_code  is the reception filter code. Optional, defaults to &h00000000 (32 bits)

filter_mask  is the reception filter mask. Optional, defaults to &hFFFFFFFF (32 bits)

 

The function returns 0 if OK or another value in case of error

[12] 

Example:

PRINT CAN.INIT(4, 5, 2, 8, 15, 4, 3, 0) 'Set the bus at 500Kb/sec in LISTEN ONLY mode using the pins 4 and 5

CAN.STOP

The function CAN.STOP permits to stop the driver disconnecting it from the BUS :

ret = CAN.STOP

The function returns 0 if OK or another value in case of error

CAN.WRITE

The function CAN.WRITE permits to write a message on the BUS; the message can be composed from 0 to 8 bytes.

ret = CAN.WRITE( id, can_flags [,b0 [,b1 [,b2 [,b3 [,b4 [,b5 [,b6 [,b7 ]]]]]]]])

where :

id                        is the ID of the message to be sent; must be 11 bits or 29 bits for extended messages

can_flags       is used to indicate the type of message transmitted/received.

b0 ... b7        message bytes. Any combination from 0 to 8 bytes can be specified

 

The function  returns 0 if OK or 1 of message not sent correctly

 

can_flags represents the type of message transmitted/received.

 

can_flags

DESCRIPTION

0

No message flags (Standard Frame Format)

1

Extended Frame Format (29bit ID)

2

Message is a Remote Transmit Request

4

Transmit as a Single Shot Transmission

8

Transmit as a Self Reception Request

16

 Message's Data length code is larger than 8. This will break compliance with CAN2.0B

 

 

Example:

PRINT CAN.WRITE( &h12345678, 1, 10, 20, 30) 'Send an extended message with ID &h12345678 composed of 3 bytes (10, 20, 30)

CAN.WRITE_IOBUFF

The message can also be sent using the IOBUFFERS with the function CAN.WRITE_IOBUFF :

ret = CAN.WRITE_IOBUFF(bufnum)

 

The function  returns 0 if OK or 1 of message not sent correctly

 

The iobuffer must be set before and must contains from 5 to 13 bytes structured as below :

 

IOBUFFER BYTE

DESCRIPTION

0

Message ID Byte 0 (Most significant byte)

1

Message ID Byte 1

2

Message ID Byte 2

3

Message ID Byte 3 (Least significant byte)

4

Can_flags defined as the previous table

5 to 12

Message bytes ( can span from 5 to 12 for message bytes 0 to 8)

 

ONCANBUS

Another event, ONCANBUS , has been included to determine when a CAN message has been received.

ONCANBUS  canbus_received

 

Inside this event, it is possible to read and analyse the content of the message received.

When this event occurs, the following functions are available :

 

CAN.IDENT

The function CAN.IDENT that returns the ID of the message received

Example:

id = CAN.IDENT

 

CAN.FLAGS

The function CAN.FLAGS that returns the flags of the message received

Example:

flags = CAN.FLAGS

 

CAN.LEN

The function CAN.LEN that returns the length of the message received (the content bytes)

Example:

ret = CAN.LEN

 

CAN.BYTE

The content of the message can then be read byte per byte using the function CAN.BYTE :

ret = CAN.BYTE(num)   

Returns the content of the byte num from the message received (the content bytes from 0 to 7)

 

CAN.READ_IOBUFF

The message can also be received using the IOBUFFERS with the function CAN.READ_IOBUFF :

ret = CAN.READ_IOBUFF(bufnum) 

Returns the number of bytes received (the content)

 

This function “copies” the content of the received buffer into the buffer bufnum without any impact on the function CAN.BYTE.

 

Example:

'CANBUS example

'init the canbus

'print can.setup(500, 26, 5, 1) ' speed, pin_tx, pin_rx

' speed can be 25, 50, 100, 125, 250, 500 or 800 Kbits/sec

 

'can.setup can also have other optional arguments

'print can.setup(500, 4, 5, 0, &h00000000, &hffffffff) ' speed, pin_tx, pin_rx, mode, filter_code, filter_mask

' the mode can be 0:normal, 1:no_ack_tx, 2:listen_only

 

print can.init(26, 5, 1,  8, 15, 4, 3, 1, &h0, &hffffffff) ' pin_tx, pin_rx, mode, brp, tset_1, tseg_2, sjw, triple_sampling, filter_code, filter_mask

 

'prepare an iobuffer containing an iobuffer to be sent

iobuff.dim(0, 13)  = &h12, &h34, &h56, &h78, 0    , &h33, &h44, &h55, &h66, &h77, &h88, &h99, &haa

'                     id    id     id   id   flag   b0    b1    b2    b3    b4    b5    b6    b7

 

'defines the place where jumps when receiving CAN messages

ONCANBUS canbussi

 

for z = 0 to 1000

  ' the message can be sent using iobuffers

  'print can.write_iobuff(0)

 

  'or directly using the command write

  print "tx "; can.write( z,  0,     0, 1,2,3,4,5,6,7,8)

  '                       id  flag   bytes 0 - 7

  pause 1000

next z

 

print "end sending"

 

wait

 

'can message reception routine

canbussi:

'the message can be extracted using these functions

print "canbussi "; hex$(can.ident); " "; can.flags; " "; can.len; " bytes ";

 

'the content can be read byte per byte

for k = 0 to 7

  print can.byte(k),

next k

print

 

'or the message can be routed into an iobuffer and extracted byte per byte

'print can.read_iobuff(1)

'print iobuff.len(1)

'print iobuff.read(1, 0),  iobuff.read(1, 1),  iobuff.read(1, 2),  iobuff.read(1, 3), iobuff.read(1, 4)

return

 

 CANBUS BUFFERS

In addition to the previous functions,Annex implements the support for up-to 32 (from 0 to 31) dedicated reception buffers that can be read at any pace individually.

Each buffer has its own filter and mask and it can be polled without requiring the use of the interrupt.

The filter / mask defined in the CAN.SETUP function will act as a global filter and will be applicable to all the buffers.

When a new data arrives, it will be filtered and stored in the corresponding buffer.

If an existing data is already present, it will not be overridden (the new one will be discarded), the reason is to avoid any change of data during the processing phase.

To accept to receive new data, the buffer must be released with the command CAN.BUF_CLEAR(buf_num)

 

These are several additional functions :

 

CAN..BUF_FILTER

ret =CAN.BUF_FILTER(buf_no, code [,mask]) 'set the filter

 

example

ret =CAN.BUF_FILTER(0, &h1234)  'defines the ID &h1234 for the buffer 0

 

ret =  CAN.BUF_FILTER(0, &h1234, &hf)  'defines the ID &h1234 for the buffer 0  with a mask of &hf .In this case the addresses accepted will be from &h1230 to &h123f (last 4 bits at 1)

 

CAN.BUF_IDENT

ret = CAN.BUF_IDENT(buf_no)  'returns the ident 

 

CAN.BUF_LEN

ret =CAN.BUF_LEN(buf_no) 'returns the length of the data received in the buffer

 

CAN.BUF_FLAGS

ret = CAN.BUF_FLAGS(buf_no)  'returns the flags 

 

CAN.BUF_BYTE

ret = CAN.BUF_BYTE(buf_no, byte_pos)  'returns the byte 

 

CAN.BUF_CLEAR

ret = CAN.BUF_CLEAR(buf_no)  'clear the buffer and returns 0

 

The CAN.BUF_LEN can be used to determine if new data has arrived and, when the processing of the data is terminated, the buffer can be released using CAN.BUF_CLEAR

 

 

Example:

'init the canbus

 

'can.setup can also have other optional arguments

print can.setup(500, 26, 5, 0, &h00000000, &hffffffff) ' speed, pin_tx, pin_rx, mode, filter_code, filter_mask

' the mode can be 0:normal, 1:no_ack_tx, 2:listen_only

 

print can.BUF_FILTER(0, &h123)

print can.BUF_FILTER(1, &h456)

print can.BUF_FILTER(2, &h789)

 

for z = 0 to 10000000

  if (can.BUF_LEN(0)) then

    print hex$(can.BUF_IDENT(0)), can.BUF_FLAGS(0), can.BUF_LEN(0), can.BUF_BYTE(0, 0), can.BUF_clear(0)

  end if

  if (can.BUF_LEN(1)) then 

    print hex$(can.BUF_IDENT(1)), can.BUF_FLAGS(1), can.BUF_LEN(1), can.BUF_BYTE(1, 0), can.BUF_clear(1)

  end if

  if (can.BUF_LEN(2)) then 

    print hex$(can.BUF_IDENT(2)), can.BUF_FLAGS(2), can.BUF_LEN(2), can.BUF_BYTE(2, 0), can.BUF_clear(2)

  end if

next z

 

wait

 

RMT Module

The Remote Control Peripheral (RMT) module on the ESP32 is a versatile and powerful feature designed to handle protocols that require precise timing, such as infrared remote control transmissions, LED strip control, and other time-sensitive communication protocols. Unlike typical digital I/O operations, the RMT module can generate and decode signals with highly accurate timing, allowing it to handle various transmission standards with minimal CPU involvement.

Key Features

     Precise timing control for generating and receiving signals

     Flexible channel configuration

     Support for continuous transmission using circular buffer mode

     Carrier modulation and demodulation capabilities

 

The number of RMT channels, their configuration for transmission (TX) and reception (RX), and the available buffer sizes depend on the specific ESP32 chip variant:

ESP32 Variant

Total RMT Channels

Buffer Size per Channel

Total RMT RAM

Memory Management

Channel Sync Capability

RX demodulation Capability

ESP32

8

64 x 32-bit memory blocks

512 x 32-bit

Each channel can use up to 64 memory blocks. Channels can share memory blocks if required. Memory blocks are dynamically allocated to channels.

NO

NO

ESP32-S2

4

48 x 32-bit memory blocks

192 x 32-bit

Each channel can use up to 48 memory blocks. Memory is dynamically allocated, and multiple blocks can be assigned to a single channel.

YES

YES

ESP32-S3

8


 

4 TX Channels (0-3)

 

4 RX Channels (4-7)

48 x 32-bit memory blocks

384 x 32-bit

Channels 0-3 are TX only, and Channels 4-7 are RX only. Each channel can use up to 48 memory blocks.

Memory allocation is dynamic, and blocks can be shared among channels to optimise usage.

YES

YES

ESP32-C3

4


 

2 TX Channels (0-1)

 

2 RX Channels (2-3)

48 x 32-bit memory blocks

192 x 32-bit

Channels 0-1 are TX only, and Channels 2-3 are RX only.

Each channel can use up to 48 memory blocks. Memory allocation is dynamic, and blocks can be shared among channels to optimise usage.

YES

YES

Memory Management and Synchronisation of RMT Channels

The RMT module on each ESP32 chip variant uses a flexible memory management system that allows memory blocks to be dynamically assigned to individual channels based on the application's needs:

  1. Memory Blocks: Each channel is assigned a specific number of memory items, which store 32-bit wide data per item. Depending on the variant, each channel can use up to a maximum number of  48 or 64 items (a block). By default each channel is associated with a single block.
  2. Dynamic Allocation: The allocation of memory blocks to channels is dynamic, allowing a channel to be configured to use multiple memory blocks based on the protocol requirements. For example, a channel may need more memory blocks to handle longer data sequences or more complex communication protocols.
  3. Channel Synchronisation: Some ESP32 variants support the synchronisation between different RMT channels. This feature enables precise coordination of signal generation across multiple channels, which is especially useful for controlling multiple devices simultaneously or handling complex communication protocols that require synchronised timing.
Clock Divider

The RMT module operates with a clock divider that scales the main system clock down to a lower frequency suitable for precise timing operations. The default clock source for the RMT module is derived from the system clock running at 80 MHz. The clock divider adjusts this frequency to meet the specific timing requirements of the application.

     The clock divider value determines how many system clock cycles are used to produce one RMT clock cycle. The divider starts at 80, meaning the RMT clock is derived by dividing the 80 MHz system clock by the divider value. This scaling allows for various timing configurations, enabling the RMT module to handle different pulse widths and frequencies.

For example, with a clock divider of 80, the RMT clock frequency is imageproviding a time resolution of 1 microsecond per tick. Adjusting the divider allows for finer control over timing and signal generation.

RMT RAM Composition

The RMT module utilises dedicated RAM to store and process signal data.

Each entry in the RMT RAM is a 32-bit word divided into four fields, structured as follows:

Field

Description

Bits

level0

Level of the first pulse (high/low)

1 bit

duration0

Duration of the first pulse in ticks

15 bits

level1

Level of the second pulse (high/low)

1 bit

duration1

Duration of the second pulse in ticks

15 bits

 

image

Each memory block consists of these four fields, totaling 32 bits (4 bytes) per item. This structure allows the RMT module to precisely control pulse lengths and signal states, enabling accurate signal generation and decoding. Each RMT item represents a single pulse, with the following fields:

     Level0: The state of the signal for the first half of the pulse.

     Duration0: The length of time the signal remains at Level0. The value is set in ticks, where each tick is determined by the clock divider.

     Level1: The state of the signal for the second half of the pulse.

     Duration1: The length of time the signal remains at Level1. Similar to Duration0, this is set in ticks.

The number of items used depends on the number of pulses that need to be sent or received. Each channel can store multiple items, up to the limit specified in the table above.

The minimum value for the duration is zero (0) and is interpreted as a transmission end-marker.

The ‘duration’ fields represent the length of the pulse in terms of clock ticks. By specifying the duration, you determine how long the signal should be held at a particular level before transitioning to the next state. This allows for the precise timing and modulation of signals, crucial for tasks such as generating complex waveforms or encoding data.

For example, using a clock divider of 80, each tick corresponds to 1 microsecond.

Therefore, the Duration0 and Duration1 fields directly represent pulse lengths in microseconds.

Memory Block Extension

     All ESP32 variants allow for extending the memory blocks allocated to certain RMT channels by reducing the total number of available channels. This feature enables users to configure longer, more complex signal patterns by increasing the buffer size for specific channels. For example, on the ESP32, you can configure:

     8 channels with 64 items each

     4 channels with 128 items each

     2 channels with 256 items each

     This flexibility allows users to balance between the number of channels and the complexity of signals they can generate or receive. By reallocating memory, it is possible to tailor the RMT peripheral to meet the specific needs of the application, whether it involves fewer channels with longer signal patterns or more channels with shorter patterns.

 

 

RMT Transmit Synchronisation

Transmit synchronisation in the context of RMT (Remote Control Transceiver) refers to the ability to start multiple transmit channels simultaneously with precise timing. This feature is crucial for applications that require coordinated output across multiple channels, such as:

     Controlling complex LED matrices or strips

     Generating multi-channel waveforms

     Implementing protocols that require precise timing across multiple signal lines

The goal of transmit synchronisation is to ensure that multiple RMT channels begin their transmission at exactly the same moment, eliminating any timing discrepancies between channels.

Different ESP32 variants have varying levels of support for transmit synchronisation:

  1. ESP32:
    This earlier variant does not have built-in hardware support for TX synchronisation.
  2. ESP32-S2, ESP32-S3 and ESP32-C3:
    These newer variants include hardware support for TX synchronisation. They feature a synchronisation manager that can coordinate the start of transmission across multiple TX channels with high precision. This hardware-based approach allows for more accurate and reliable synchronised transmissions.

 

TX Transmitter Mode

In the transmitter mode, the RMT module of the ESP32 provides versatile capabilities for transmitting data. Key features include:

     Idle Level (idle_level): Set the signal level (high or low) on the RMT output when it is idle. This ensures a consistent signal state between transmissions.

     Loop Enable (loop_en): Enable or disable continuous looping of the data transmission.

     Carrier Signal: The RMT module supports the use of a carrier signal to modulate the output. This carrier signal can be customised with the following parameters:

     Enable Carrier Signal (carrier_en): Activate the carrier signal to modulate the output as required.

     Carrier Frequency (carrier_freq_hz): Set a specific frequency (e.g., 38 kHz), which is commonly used in IR communication.

     Duty Cycle Percentage (carrier_duty_percent): Adjust the duty cycle of the carrier signal to define the proportion of time the signal is high within each period. This can be set to values such as 33% or any other percentage based on application needs.

     Carrier Level (carrier_level): Specify the level (high or low) of the RMT output when the carrier is active, determining whether the carrier signal starts with a high or low output.

 

RX Reception Mode

In the reception mode, the RMT module provides several configurable parameters to optimise signal reception and processing:

     Enable Filter (filter_en): Activates a filter on the input of the RMT receiver to remove noise and unwanted signals.

     Filter Threshold (filter_ticks_thresh): Sets the threshold for the filter in terms of the number of ticks. Pulses shorter than this value will be filtered out. The acceptable range for this value is from 0 to 255 ticks.

     Idle Threshold (idle_threshold): Defines a pulse length threshold in ticks that will cause the RMT receiver to enter an idle state. Pulses longer than this threshold will be ignored, helping to manage longer, non-relevant signals.

Additionally, the RMT module can optionally demodulate the input signal using the following parameters:

     Enable Carrier Demodulation (rm_carrier): Activates the carrier demodulation feature to process modulated signals.

     Carrier Frequency (carrier_freq_hz): Specifies the frequency of the carrier signal in Hz, used for demodulation. This allows the receiver to correctly interpret signals modulated at specific frequencies.

     Carrier Duty Cycle (carrier_duty_percent): Sets the duty cycle of the carrier signal in percentage, which helps in accurately decoding the modulated signal.

     Carrier Level (carrier_level): Defines the level of the RMT input where the carrier is modulated, indicating whether the carrier signal affects the input when active.

RX Reception Event

The RMT uses the OnInfrared event for the reception of RX data. This event is triggered immediately when the RMT module receives incoming data.

RMT Command Functions
RMT.SETUP_TX channel, pin [, clk_div] [, mem_block_num] [, idle_level] [, loop_en] [, carrier_en] [, carrier_freq_hz] [, carrier_duty_percent] [, carrier_level]

     Purpose: Configures an RMT channel for transmission.

     Arguments:

     channel: RMT channel number (integer)

     pin: GPIO pin number (integer)

     clk_div: Clock divider (optional integer, default is 80)

     mem_block_num: Number of memory blocks (optional integer, default is 1)

     idle_level: Idle level for the channel (optional integer, default is RMT_IDLE_LEVEL_LOW)

     loop_en: Enable looping (optional boolean, default is false (0))

     carrier_en: Enable carrier signal (optional boolean, default is false (0))

     carrier_freq_hz: Carrier frequency in Hz (optional integer, default is 38000)

     carrier_duty_percent: Carrier duty cycle percentage (optional integer, default is 33)

     carrier_level: Carrier level (optional integer, default is RMT_CARRIER_LEVEL_HIGH)

     Description: Sets up the RMT channel for transmission with specified parameters. Uninstalls and reconfigures the RMT driver.

RMT.WRITE channel, num_items, name [, wait]

     Purpose: Sends data items on an RMT channel.

     Arguments:

     channel: RMT channel number (integer)

     num_items: Number of items to write (integer)

     name: Integer array containing the data items, where each element is a 32-bit word that encodes levels and periods.

     wait: Wait for transmission to complete (optional boolean, default is true)

     Description: Writes an array of RMT items to the specified channel. The name parameter represents an integer array where each element is a 32-bit word combining both the levels (1-bit) and periods (15-bits each for high and low durations) required for transmission. Optionally waits for the transmission to complete.

RMT.ENCODE channel, num_items, array() [, wait]

     Purpose: Encodes data items for transmission on an RMT channel.

     Arguments:

     channel: RMT channel number (integer)

     num_items: Number of items to encode (integer)

     array: A two-dimensional array containing the data to encode:

     array[x][0]: Level 0

     array[x][1]: Duration 0

     array[x][2]: Level 1

     array[x][3]: Duration 1

     wait: Wait for transmission to complete (optional boolean, default is true)

     Description: Encodes data for transmission from a two-dimensional array where each row represents a data item to be transmitted. The array must be defined beforehand with levels and durations structured as shown. The encoded data is then written to the specified channel, and it optionally waits for transmission to complete.

RMT.SETUP_RX channel, pin [, clk_div] [, mem_block_num] [, invert] [, filter_en] [, filter_ticks_thresh] [, idle_threshold] [, rm_carrier] [, carrier_freq_hz] [, carrier_duty_percent] [, carrier_level]

     Purpose: Configures an RMT channel for reception.

     Arguments:

     channel: RMT channel number (integer)

     pin: GPIO pin number (integer)

     clk_div: Clock divider (optional integer, default is 80)

     mem_block_num: Number of memory blocks (optional integer, default is 1)

     invert: Inverts the polarity of the signal (optional integer, default is false (0))

     filter_en: Enable filter (optional boolean, default is true (1))

     filter_ticks_thresh: Filter ticks threshold (optional integer, default is 100)

     idle_threshold: Idle threshold (optional integer, default is 65535)

     rm_carrier: Remove carrier signal (optional boolean, default is true)

     carrier_freq_hz: Carrier frequency in Hz (optional integer, default is 38000)

     carrier_duty_percent: Carrier duty cycle percentage (optional integer, default is 33)

     carrier_level: Carrier level (optional integer, default is RMT_CARRIER_LEVEL_HIGH)

     Description: Configures the RMT channel for reception with specified parameters. Uninstalls and reconfigures the RMT driver, sets up a ring buffer for received data, and starts the reception.

RMT.READ array()

     Purpose: Reads data items from an RMT channel.

     Arguments:

     array: Integer array where the received data will be stored, with each element being a 32-bit word that encodes levels and periods.

     Description: Reads data from the RMT ring buffer and stores it in an integer array (array). The array will contain 32-bit words, with each word representing the levels (1-bit) and periods (15-bits each for high and low durations) that were received. The array must be pre-defined before use.

RMT.DECODE array()

     Purpose: Decodes received data from an RMT channel.

     Arguments:

     array: A two-dimensional array to store the decoded data:

     array[x][0]: Level 0

     array[x][1]: Duration 0

     array[x][2]: Level 1

     array[x][3]: Duration 1

     Description: Decodes data from the RMT ring buffer into a two-dimensional array format where each row corresponds to a decoded item with levels and durations structured as shown. The array must be pre-defined before use.

RMT.ADD_GROUP channel

     Purpose: Adds an RMT channel to a group for synchronised transmission.

     Arguments:

     channel: RMT channel number (integer)

     Description: Adds the specified RMT channel to a group. This is used to define a set of channels that should transmit signals simultaneously. The transmission is synchronised, starting only when all channels in the group are ready.

RMT.DEL_GROUP channel

     Purpose: Removes an RMT channel from a group.

     Arguments:

     channel: RMT channel number (integer)

     Description: Removes the specified RMT channel from a group. This action stops the channel from being part of a synchronised transmission group.

RMT.END channel

     Purpose: Terminates (unistall) an RMT channel.

     Arguments:

     channel: RMT channel number (integer)

     Description: Uninstall the specified RMT channel. This action stops the channel from being active and frees the corresponding GPIO pin.

 

Synchronisation in Groups

The RMT.ADD_GROUP andRMT.DEL_GROUP commands manage the synchronisation of multiple RMT channels for transmission. By adding channels to a group, the user specifies which channels should start transmitting at the same time. The transmission begins only when all channels in the group are ready, ensuring synchronised signal output across multiple channels. When a writing command is given on the last channel of the group, the synchronised transmission can commence, providing coordinated timing for applications that require multiple simultaneous signals.

 

Example 1: RMT Transmission of a Pulse Sequence

This example demonstrates how to use the RMT (Remote Control) module on an ESP32 to transmit a sequence of pulses using the RMT.ENCODE command. Let's break down the example to understand what each part does and how it works.

 

' RMT example: set a sequence of 3 pulses of 1ms, 2ms, 3ms with 20msec between

dim a(10,5)

a(0,0) = 1 : a(0,1) = 1000: a(0,2) = 0 : a(0,3) = 20000

a(1,0) = 1 : a(1,1) = 2000: a(1,2) = 0 : a(1,3) = 20000

a(2,0) = 1 : a(2,1) = 3000: a(2,2) = 0 : a(2,3) = 20000

a(3,0) = 0 : a(3,1) = 0   : a(3,2) = 0 : a(3,3) = 0

 

 

' channel, pin, clk_div, num_blocks, idle_level, loop_en, carrier_en, carrier_freq, carrier_duty_percent, carrier_level

RMT.Setup_TX 0, 47, 80, 1, 0, 1, 1, 38000, 33, 1

RMT.ENCODE 0, 3, a()

end

 

Pulse Sequence Setup

The sequence of pulses is defined using a 2-dimensional array named a in the example, where each row represents one RMT item containing pulse levels and durations.

dim a(10,5)

a(0,0) = 1 : a(0,1) = 1000: a(0,2) = 0 : a(0,3) = 20000

a(1,0) = 1 : a(1,1) = 2000: a(1,2) = 0 : a(1,3) = 20000

a(2,0) = 1 : a(2,1) = 3000: a(2,2) = 0 : a(2,3) = 20000

a(3,0) = 0 : a(3,1) = 0   : a(3,2) = 0 : a(3,3) = 0

     a(n, m): Represents a 2-dimensional array where:

     a(0,0) to a(3,3) define the pulse sequence.

     Column Definitions:

     a(x,0): Level0 - The logic level of the pulse for the first phase.

     a(x,1): Duration0 - Duration (in ticks) for Level0.

     a(x,2): Level1 - The logic level of the pulse for the second phase.

     a(x,3): Duration1 - Duration (in ticks) for Level1.

Explanation of the Sequence

     a(0,0) to a(0,3): First pulse:

     Level0 = 1 (High)

     Duration0 = 1000 ticks (1ms)

     Level1 = 0 (Low)

     Duration1 = 20000 ticks (20ms gap)

     a(1,0) to a(1,3): Second pulse:

     Level0 = 1 (High)

     Duration0 = 2000 ticks (2ms)

     Level1 = 0 (Low)

     Duration1 = 20000 ticks (20ms gap)

     a(2,0) to a(2,3): Third pulse:

     Level0 = 1 (High)

     Duration0 = 3000 ticks (3ms)

     Level1 = 0 (Low)

     Duration1 = 20000 ticks (20ms gap)

     a(3,0) to a(3,3): Terminates the sequence:

     All fields set to 0, indicating the end of transmission.

RMT Setup for Transmission

RMT.Setup_TX 0, 47, 80, 1, 0, 1, 1, 38000, 33, 1

The RMT.Setup_TX command configures the transmitter with specific parameters:

     0: Channel number (0).

     47: GPIO pin number (47).

     80: Clock divider, resulting in a 1 MHz RMT clock.

     1: Number of memory blocks.

     0: Idle level set to low.

     1: Loop enable, which allows the transmission to repeat indefinitely.

     1: Carrier signal enabled.

     38000: Carrier frequency in Hz (38 kHz).

     33: Carrier duty cycle (33%).

     1: Carrier level set to high when the carrier is active.

Encoding and Transmitting the Pulse Sequence

RMT.ENCODE 0, 3, a()

The RMT.ENCODEcommand encodes the pulse sequence for transmission with the following parameters:

     0: Channel number.

     3: Number of items to encode (3 pulses).

     a(): The name of the array containing the pulse sequence.

Explanation of the Functionality

This example sets up the RMT module to send a series of three pulses, each with increasing duration, followed by a 20 ms gap. The RMT transmitter is configured to loop this sequence indefinitely.

By using the RMT.ENCODE command, the user specifies the pulse sequence data, which the ESP32 will transmit on GPIO pin 47. The transmission repeats in a loop until explicitly stopped.

Key Points

     Customizable Pulse Sequences: The example shows how to define complex sequences of pulses with varying durations and levels.

     Loop Transmission: By setting the loop enable parameter to 1, the transmission repeats indefinitely, which is useful for applications that require a constant signal output.

     Carrier and Idle Control: The carrier is enabled with a frequency of 38 kHz and a 33% duty cycle, suitable for applications like IR communication.

 

Example 2: RMT Transmission of a Sinusoidal Pattern

This example configures the ESP32's RMT module to transmit a sinusoidal signal pattern using 256 data items. The waveform is generated using the sin() function to create a sequence of pulses that simulate a sine wave, which is useful for applications like signal generation, testing, or remote control emulation.

' RMT demo for generating a sinusoidal signal pattern using 256 data items

dim s(256, 4)

for z = 0 to 256

  s(z,0) = 1

  s(z,1) = sin(z/256*PI*2) * 500 + 501

  s(z,2) = 0

  s(z,3) = 5000

next z

' channel, pin, clk_div, num_blocks, idle_level, loop_en, carrier_en, carrier_freq, carrier_duty_percent, carrier_level

RMT.Setup_TX 0, 47, 80, 6, 0, 1

rmt.ENCODE 0, 256, s(), 0

end

 

Explanation of the Example

  1. Define the Data Array:

     A 2-dimensional array s(256, 4) is defined to store 256 items, where each item represents a part of the sinusoidal waveform.

     Each item contains four fields:

     s(z,0): Level of the RMT output (1 or 0).

     s(z,1): Duration of the high or low level in clock ticks (calculated using a sine function).

     s(z,2): Level of the RMT output after the initial duration (0 in this case).

     s(z,3): Duration of the low level after the initial pulse (fixed at 5000 ticks).

  1. Populate the Array with Sinusoidal Data:

     The for loop iterates from 0 to 256 to generate a sinusoidal pattern.

     The duration s(z,1) is calculated using the sin() function to create a value that oscillates between 1 and 1001, simulating a sine wave. This is done by multiplying the sine function's result by 500 and adding 501 to ensure a minimum duration.

  1. RMT Setup for Transmission:

     RMT.Setup_TX initialises the RMT transmitter on channel 0, using GPIO pin 47, a clock divider (clk_div) of 80, and allocates 6 memory blocks (num_blocks = 6).

     Idle_level is set to 0

     Loop mode (loop_en) is enabled to continuously transmit the configured data.

  1. Encode and Transmit the Data: