feat: 全量同步 254 个常用的 Arduino 扩展库文件

This commit is contained in:
yczpf2019
2026-01-24 16:05:38 +08:00
parent c665ba662b
commit 397b9a23a3
6878 changed files with 2732224 additions and 1 deletions

View File

@@ -0,0 +1,153 @@
/*----------------------------------------------------------------------*
* M5Stack I2C Common Library v1.0 *
* *
* This work is licensed under the GNU Lesser General Public *
* License v2.1 *
* https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html *
*----------------------------------------------------------------------*/
#include "CommUtil.h"
#include "../M5Core2.h"
extern M5Core2 M5;
//debug for message of I2C ( bypass message to serial)
//#define I2C_DEBUG_TO_SERIAL
CommUtil::CommUtil() {
}
// Wire.h read and write protocols
bool CommUtil::writeCommand(uint8_t address, uint8_t subAddress) {
bool function_result = false;
Wire.beginTransmission(address); // Initialize the Tx buffer
Wire.write(subAddress); // Put slave register address in Tx buffer
function_result = (Wire.endTransmission() == 0); // Send the Tx buffer
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("writeCommand:send to 0x%02x [0x%02x] result:%s\n", address, subAddress, function_result ? "OK" : "NG");
#endif
return (function_result);
}
// Wire.h read and write protocols
bool CommUtil::writeByte(uint8_t address, uint8_t subAddress, uint8_t data) {
bool function_result = false;
Wire.beginTransmission(address); // Initialize the Tx buffer
Wire.write(subAddress); // Put slave register address in Tx buffer
Wire.write(data); // Put data in Tx buffer
function_result = (Wire.endTransmission() == 0); // Send the Tx buffer
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("writeByte:send to 0x%02x [0x%2x] data=0x%02x result:%s\n", address, subAddress, data, function_result ? "OK" : "NG");
#endif
return (function_result);
}
// Wire.h read and write protocols
bool CommUtil::writeBytes(uint8_t address, uint8_t subAddress, uint8_t *data, uint8_t length) {
bool function_result = false;
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("writeBytes:send to 0x%02x [0x%02x] data=",address,subAddress);
#endif
Wire.beginTransmission(address); // Initialize the Tx buffer
Wire.write(subAddress); // Put slave register address in Tx buffer
for(int i = 0; i < length; i++) {
Wire.write(*(data+i)); // Put data in Tx buffer
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("%02x ", *(data+i));
#endif
}
function_result = (Wire.endTransmission() == 0); // Send the Tx buffer
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("result:%s\n", function_result ? "OK" : "NG");
#endif
return function_result; // Send the Tx buffer
}
bool CommUtil::readByte(uint8_t address, uint8_t *result) {
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("readByte :read from 0x%02x requestByte=1 receive=", address);
#endif
if ( Wire.requestFrom(address, (uint8_t)1)) {
*result = Wire.read(); // Fill Rx buffer with result
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("%02x\n", result);
#endif
return true;
}
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("none\n");
#endif
return false;
}
bool CommUtil::readByte(uint8_t address, uint8_t subAddress,uint8_t *result) {
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("readByte :read from 0x%02x [0x%02x] requestByte=1 receive=", address, subAddress);
#endif
Wire.beginTransmission(address); // Initialize the Tx buffer
Wire.write(subAddress); // Put slave register address in Tx buffer
if (Wire.endTransmission(false) == 0 && Wire.requestFrom(address, (uint8_t)1)) {
*result = Wire.read(); // Fill Rx buffer with result
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("%02x\n",*result);
#endif
return true;
}
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("none\n");
#endif
return false;
}
bool CommUtil::readBytes(uint8_t address, uint8_t subAddress, uint8_t count,uint8_t * dest) {
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("readBytes:read from 0x%02x [0x%02x] requestByte=%d receive=", address, subAddress, count);
#endif
Wire.beginTransmission(address); // Initialize the Tx buffer
Wire.write(subAddress); // Put slave register address in Tx buffer
uint8_t i = 0;
if (Wire.endTransmission(false) == 0 && Wire.requestFrom(address, (uint8_t)count)) {
while (Wire.available()) {
dest[i++] = Wire.read();// Put read results in the Rx buffer
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("%02x ", dest[i-1]);
#endif
}
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf(" (len:%d)\n", i);
#endif
return true;
}
#ifdef I2C_DEBUG_TO_SERIAL
Serial.printf("none\n");
#endif
return false;
}
bool CommUtil::readBytes(uint8_t address, uint8_t count,uint8_t * dest) {
uint8_t i = 0;
if (Wire.requestFrom(address, (uint8_t)count)) {
while (Wire.available()) {
// Put read results in the Rx buffer
dest[i++] = Wire.read();
}
return true;
}
return false;
}
void CommUtil::scanID(bool *result) {
for(int i=0x00;i<=0x7f;i++) {
*(result+i)=writeCommand(i,0x00);
}
}

View File

@@ -0,0 +1,27 @@
/*----------------------------------------------------------------------*
* M5Stack I2C Common Library v1.0 *
* *
* This work is licensed under the GNU Lesser General Public *
* License v2.1 *
* https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html *
*----------------------------------------------------------------------*/
#ifndef CommUtil_h
#define CommUtil_h
#include <Arduino.h>
#include <Wire.h>
class CommUtil {
public:
CommUtil();
bool writeCommand(uint8_t address, uint8_t subAddress);
bool writeByte(uint8_t address, uint8_t subAddress, uint8_t data);
bool writeBytes(uint8_t address, uint8_t subAddress, uint8_t *data,uint8_t length);
bool readByte(uint8_t address, uint8_t *result);
bool readByte(uint8_t address, uint8_t subAddress,uint8_t *result);
bool readBytes(uint8_t address, uint8_t count,uint8_t * dest);
bool readBytes(uint8_t address, uint8_t subAddress, uint8_t count, uint8_t * dest);
void scanID(bool *result);
private:
};
#endif

View File

@@ -0,0 +1,27 @@
#ifndef _CONFIG_H_
#define _CONFIG_H_
#define TFT M5Display::instance
#define BUTTONS M5Buttons::instance
// Screen
#define TFT_LED_PIN 32
#define TFT_DC_PIN 27
#define TFT_CS_PIN 14
#define TFT_MOSI_PIN 23
#define TFT_CLK_PIN 18
#define TFT_RST_PIN 33
#define TFT_MISO_PIN 19
// SD card
#define TFCARD_CS_PIN 4
// UART
#define USE_SERIAL Serial
// Core2 defines
#define M5Stack_M5Core2
#define TFT_eSPI_TOUCH_EMULATION
#define TOUCH M5Touch::instance
#endif /* CONFIG_H */

View File

@@ -0,0 +1,155 @@
// Change the width and height if required (defined in portrait mode)
// or use the constructor to over-ride defaults
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
// Color definitions for backwards compatibility with old sketches
// use colour definitions like TFT_BLACK to make sketches more portable
#define ILI9341_BLACK 0x0000 /* 0, 0, 0 */
#define ILI9341_NAVY 0x000F /* 0, 0, 128 */
#define ILI9341_DARKGREEN 0x03E0 /* 0, 128, 0 */
#define ILI9341_DARKCYAN 0x03EF /* 0, 128, 128 */
#define ILI9341_MAROON 0x7800 /* 128, 0, 0 */
#define ILI9341_PURPLE 0x780F /* 128, 0, 128 */
#define ILI9341_OLIVE 0x7BE0 /* 128, 128, 0 */
#define ILI9341_LIGHTGREY 0xC618 /* 192, 192, 192 */
#define ILI9341_DARKGREY 0x7BEF /* 128, 128, 128 */
#define ILI9341_BLUE 0x001F /* 0, 0, 255 */
#define ILI9341_GREEN 0x07E0 /* 0, 255, 0 */
#define ILI9341_CYAN 0x07FF /* 0, 255, 255 */
#define ILI9341_RED 0xF800 /* 255, 0, 0 */
#define ILI9341_MAGENTA 0xF81F /* 255, 0, 255 */
#define ILI9341_YELLOW 0xFFE0 /* 255, 255, 0 */
#define ILI9341_WHITE 0xFFFF /* 255, 255, 255 */
#define ILI9341_ORANGE 0xFD20 /* 255, 165, 0 */
#define ILI9341_GREENYELLOW 0xAFE5 /* 173, 255, 47 */
#define ILI9341_PINK 0xF81F
#define BLACK 0x0000 /* 0, 0, 0 */
#define NAVY 0x000F /* 0, 0, 128 */
#define DARKGREEN 0x03E0 /* 0, 128, 0 */
#define DARKCYAN 0x03EF /* 0, 128, 128 */
#define MAROON 0x7800 /* 128, 0, 0 */
#define PURPLE 0x780F /* 128, 0, 128 */
#define OLIVE 0x7BE0 /* 128, 128, 0 */
#define LIGHTGREY 0xC618 /* 192, 192, 192 */
#define DARKGREY 0x7BEF /* 128, 128, 128 */
#define BLUE 0x001F /* 0, 0, 255 */
#define GREEN 0x07E0 /* 0, 255, 0 */
#define CYAN 0x07FF /* 0, 255, 255 */
#define RED 0xF800 /* 255, 0, 0 */
#define MAGENTA 0xF81F /* 255, 0, 255 */
#define YELLOW 0xFFE0 /* 255, 255, 0 */
#define WHITE 0xFFFF /* 255, 255, 255 */
#define ORANGE 0xFD20 /* 255, 165, 0 */
#define GREENYELLOW 0xAFE5 /* 173, 255, 47 */
#define PINK 0xF81F
// Delay between some initialisation commands
#define TFT_INIT_DELAY 0x80 // Not used unless commandlist invoked
// Generic commands used by TFT_eSPI.cpp
#define TFT_NOP 0x00
#define TFT_SWRST 0x01
#define TFT_CASET 0x2A
#define TFT_PASET 0x2B
#define TFT_RAMWR 0x2C
#define TFT_RAMRD 0x2E
#define TFT_IDXRD 0xDD // ILI9341 only, indexed control register read
#define TFT_MADCTL 0x36
#define TFT_MAD_MY 0x80
#define TFT_MAD_MX 0x40
#define TFT_MAD_MV 0x20
#define TFT_MAD_ML 0x10
#define TFT_MAD_BGR 0x08
#define TFT_MAD_MH 0x04
#define TFT_MAD_RGB 0x00
#ifdef TFT_RGB_ORDER
#if (TFT_RGB_ORDER == 1)
#define TFT_MAD_COLOR_ORDER TFT_MAD_RGB
#else
#define TFT_MAD_COLOR_ORDER TFT_MAD_BGR
#endif
#else
#define TFT_MAD_COLOR_ORDER TFT_MAD_BGR
#endif
#define TFT_INVOFF 0x20
#define TFT_INVON 0x21
// All ILI9341 specific commands some are used by init()
#define ILI9341_NOP 0x00
#define ILI9341_SWRESET 0x01
#define ILI9341_RDDID 0x04
#define ILI9341_RDDST 0x09
#define ILI9341_SLPIN 0x10
#define ILI9341_SLPOUT 0x11
#define ILI9341_PTLON 0x12
#define ILI9341_NORON 0x13
#define ILI9341_RDMODE 0x0A
#define ILI9341_RDMADCTL 0x0B
#define ILI9341_RDPIXFMT 0x0C
#define ILI9341_RDIMGFMT 0x0A
#define ILI9341_RDSELFDIAG 0x0F
#define ILI9341_INVOFF 0x20
#define ILI9341_INVON 0x21
#define ILI9341_GAMMASET 0x26
#define ILI9341_DISPOFF 0x28
#define ILI9341_DISPON 0x29
#define ILI9341_CASET 0x2A
#define ILI9341_PASET 0x2B
#define ILI9341_RAMWR 0x2C
#define ILI9341_RAMRD 0x2E
#define ILI9341_PTLAR 0x30
#define ILI9341_VSCRDEF 0x33
#define ILI9341_MADCTL 0x36
#define ILI9341_VSCRSADD 0x37
#define ILI9341_PIXFMT 0x3A
#define ILI9341_WRDISBV 0x51
#define ILI9341_RDDISBV 0x52
#define ILI9341_WRCTRLD 0x53
#define ILI9341_FRMCTR1 0xB1
#define ILI9341_FRMCTR2 0xB2
#define ILI9341_FRMCTR3 0xB3
#define ILI9341_INVCTR 0xB4
#define ILI9341_DFUNCTR 0xB6
#define ILI9341_PWCTR1 0xC0
#define ILI9341_PWCTR2 0xC1
#define ILI9341_PWCTR3 0xC2
#define ILI9341_PWCTR4 0xC3
#define ILI9341_PWCTR5 0xC4
#define ILI9341_VMCTR1 0xC5
#define ILI9341_VMCTR2 0xC7
#define ILI9341_RDID4 0xD3
#define ILI9341_RDINDEX 0xD9
#define ILI9341_RDID1 0xDA
#define ILI9341_RDID2 0xDB
#define ILI9341_RDID3 0xDC
#define ILI9341_RDIDX 0xDD // TBC
#define ILI9341_GMCTRP1 0xE0
#define ILI9341_GMCTRN1 0xE1
#define ILI9341_MADCTL_MY 0x80
#define ILI9341_MADCTL_MX 0x40
#define ILI9341_MADCTL_MV 0x20
#define ILI9341_MADCTL_ML 0x10
#define ILI9341_MADCTL_RGB 0x00
#define ILI9341_MADCTL_BGR 0x08
#define ILI9341_MADCTL_MH 0x04

View File

@@ -0,0 +1,86 @@
// // This is the command sequence that initialises the ILI9341 driver
// //
// // This setup information uses simple 8 bit SPI writecommand() and writedata() functions
// //
// // See ST7735_Setup.h file for an alternative format
{
writecommand(0xC8);
writedata(0xFF);
writedata(0x93);
writedata(0x42);
writecommand(ILI9341_PWCTR1);
writedata(0x12);
writedata(0x12);
writecommand(ILI9341_PWCTR2);
writedata(0x03);
writecommand(0xB0);
writedata(0xE0);
writecommand(0xF6);
writedata(0x00);
writedata(0x01);
writedata(0x01);
writecommand(ILI9341_MADCTL); // Memory Access Control
#ifdef M5STACK
writedata(TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_COLOR_ORDER); // Rotation 0 (portrait mode)
#else
writedata(TFT_MAD_MX | TFT_MAD_COLOR_ORDER); // Rotation 0 (portrait mode)
#endif
writecommand(ILI9341_PIXFMT);
writedata(0x55);
writecommand(ILI9341_DFUNCTR); // Display Function Control
writedata(0x08);
writedata(0x82);
writedata(0x27);
writecommand(ILI9341_GMCTRP1); //Set Gamma
writedata(0x00);
writedata(0x0C);
writedata(0x11);
writedata(0x04);
writedata(0x11);
writedata(0x08);
writedata(0x37);
writedata(0x89);
writedata(0x4C);
writedata(0x06);
writedata(0x0C);
writedata(0x0A);
writedata(0x2E);
writedata(0x34);
writedata(0x0F);
writecommand(ILI9341_GMCTRN1); //Set Gamma
writedata(0x00);
writedata(0x0B);
writedata(0x11);
writedata(0x05);
writedata(0x13);
writedata(0x09);
writedata(0x33);
writedata(0x67);
writedata(0x48);
writedata(0x07);
writedata(0x0E);
writedata(0x0B);
writedata(0x2E);
writedata(0x33);
writedata(0x0F);
writecommand(ILI9341_SLPOUT); //Exit Sleep
spi_end();
delay(120);
spi_begin();
writecommand(ILI9341_DISPON); //Display on
}

View File

@@ -0,0 +1,82 @@
// This is the command sequence that rotates the ILI9341 driver coordinate frame
rotation = m % 8; // Limit the range of values to 0-7
writecommand(TFT_MADCTL);
switch (rotation) {
case 0:
#ifdef M5STACK
writedata(TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_COLOR_ORDER);
#else
writedata(TFT_MAD_MX | TFT_MAD_COLOR_ORDER);
#endif
_width = _init_width;
_height = _init_height;
break;
case 1:
#ifdef M5STACK
writedata(TFT_MAD_COLOR_ORDER);
#else
writedata(TFT_MAD_MV | TFT_MAD_COLOR_ORDER);
#endif
_width = _init_height;
_height = _init_width;
break;
case 2:
#ifdef M5STACK
writedata(TFT_MAD_MV | TFT_MAD_MX | TFT_MAD_COLOR_ORDER);
#else
writedata(TFT_MAD_MY | TFT_MAD_COLOR_ORDER);
#endif
_width = _init_width;
_height = _init_height;
break;
case 3:
#ifdef M5STACK
writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_COLOR_ORDER);
#else
writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_COLOR_ORDER);
#endif
_width = _init_height;
_height = _init_width;
break;
// These next rotations are for bottom up BMP drawing
case 4:
#ifdef M5STACK
writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_COLOR_ORDER);
#else
writedata(TFT_MAD_MX | TFT_MAD_MY | TFT_MAD_COLOR_ORDER);
#endif
_width = _init_width;
_height = _init_height;
break;
case 5:
#ifdef M5STACK
writedata(TFT_MAD_MY | TFT_MAD_COLOR_ORDER);
#else
writedata(TFT_MAD_MV | TFT_MAD_MX | TFT_MAD_COLOR_ORDER);
#endif
_width = _init_height;
_height = _init_width;
break;
case 6:
#ifdef M5STACK
writedata(TFT_MAD_MV | TFT_MAD_COLOR_ORDER);
#else
writedata(TFT_MAD_COLOR_ORDER);
#endif
_width = _init_width;
_height = _init_height;
break;
case 7:
#ifdef M5STACK
writedata(TFT_MAD_MX | TFT_MAD_COLOR_ORDER);
#else
writedata(TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_COLOR_ORDER);
#endif
_width = _init_height;
_height = _init_width;
break;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,985 @@
/***************************************************
Arduino TFT graphics library targeted at ESP8266
and ESP32 based boards.
This is a standalone library that contains the
hardware driver, the graphics functions and the
proportional fonts.
The larger fonts are Run Length Encoded to reduce
their FLASH footprint.
****************************************************/
// Stop fonts etc being loaded multiple times
#ifndef _In_eSPIH_
#define _In_eSPIH_
#define TFT_ESPI_VERSION "1.4.21"
//#define ESP32 //Just used to test ESP32 options
// Include header file that defines the fonts loaded, the TFT drivers
// available and the pins to be used
#include "In_eSPI_Setup.h"
#ifndef TAB_COLOUR
#define TAB_COLOUR 0
#endif
// If the frequency is not defined, set a default
#ifndef SPI_FREQUENCY
#define SPI_FREQUENCY 20000000
#endif
// If the frequency is not defined, set a default
#ifndef SPI_READ_FREQUENCY
#define SPI_READ_FREQUENCY SPI_FREQUENCY
#endif
#if defined(ST7789_DRIVER) || defined(ST7789_2_DRIVER)
#define TFT_SPI_MODE SPI_MODE3
#else
#define TFT_SPI_MODE SPI_MODE0
#endif
// If the frequency is not defined, set a default
#ifndef SPI_TOUCH_FREQUENCY
#define SPI_TOUCH_FREQUENCY 2500000
#endif
// Use GLCD font in error case where user requests a smooth font file
// that does not exist (this is a temporary fix to stop ESP32 reboot)
#ifdef SMOOTH_FONT
#ifndef LOAD_GLCD
#define LOAD_GLCD
#endif
#endif
// Only load the fonts defined in User_Setup.h (to save space)
// Set flag so RLE rendering code is optionally compiled
#ifdef LOAD_GLCD
#include <Fonts/glcdfont.c>
#endif
#ifdef LOAD_FONT2
#include <Fonts/Font16.h>
#endif
#ifdef LOAD_FONT4
#include <Fonts/Font32rle.h>
#define LOAD_RLE
#endif
#ifdef LOAD_FONT6
#include <Fonts/Font64rle.h>
#ifndef LOAD_RLE
#define LOAD_RLE
#endif
#endif
#ifdef LOAD_FONT7
#include <Fonts/Font7srle.h>
#ifndef LOAD_RLE
#define LOAD_RLE
#endif
#endif
#ifdef LOAD_FONT8
#include <Fonts/Font72rle.h>
#ifndef LOAD_RLE
#define LOAD_RLE
#endif
#elif defined LOAD_FONT8N
#define LOAD_FONT8
#include <Fonts/Font72x53rle.h>
#ifndef LOAD_RLE
#define LOAD_RLE
#endif
#endif
#include <Arduino.h>
#include <Print.h>
#include <pgmspace.h>
#include <SPI.h>
#ifdef ESP32
#include "soc/spi_reg.h"
#ifdef USE_HSPI_PORT
#define SPI_PORT HSPI
#else
#define SPI_PORT VSPI
#endif
#endif
#ifdef SMOOTH_FONT
// Call up the SPIFFS FLASH filing system for the anti-aliased fonts
#define FS_NO_GLOBALS
#include <FS.h>
#ifdef ESP32
#include "SPIFFS.h"
#endif
#endif
#ifndef TFT_DC
#define DC_C // No macro allocated so it generates no code
#define DC_D // No macro allocated so it generates no code
#else
#if defined (ESP8266) && (TFT_DC == 16)
#define DC_C digitalWrite(TFT_DC, LOW)
#define DC_D digitalWrite(TFT_DC, HIGH)
#elif defined (ESP32)
#if defined (ESP32_PARALLEL)
#define DC_C GPIO.out_w1tc = (1 << TFT_DC)
#define DC_D GPIO.out_w1ts = (1 << TFT_DC)
#else
#if TFT_DC >= 32
#ifdef RPI_ILI9486_DRIVER // RPi display needs a slower DC change
#define DC_C GPIO.out1_w1ts.val = (1 << (TFT_DC - 32)); \
GPIO.out1_w1tc.val = (1 << (TFT_DC - 32))
#define DC_D GPIO.out1_w1tc.val = (1 << (TFT_DC - 32)); \
GPIO.out1_w1ts.val = (1 << (TFT_DC - 32))
#else
#define DC_C GPIO.out1_w1tc.val = (1 << (TFT_DC - 32))//;GPIO.out1_w1tc.val = (1 << (TFT_DC - 32))
#define DC_D GPIO.out1_w1ts.val = (1 << (TFT_DC - 32))//;GPIO.out1_w1ts.val = (1 << (TFT_DC - 32))
#endif
#else
#if TFT_DC >= 0
#ifdef RPI_ILI9486_DRIVER // RPi display needs a slower DC change
#define DC_C GPIO.out_w1ts = (1 << TFT_DC); \
GPIO.out_w1tc = (1 << TFT_DC)
#define DC_D GPIO.out_w1tc = (1 << TFT_DC); \
GPIO.out_w1ts = (1 << TFT_DC)
#else
#define DC_C GPIO.out_w1tc = (1 << TFT_DC)//;GPIO.out_w1tc = (1 << TFT_DC)
#define DC_D GPIO.out_w1ts = (1 << TFT_DC)//;GPIO.out_w1ts = (1 << TFT_DC)
#endif
#else
#define DC_C
#define DC_D
#endif
#endif
#endif
#else
#define DC_C GPOC=dcpinmask
#define DC_D GPOS=dcpinmask
#endif
#endif
#if defined (TFT_SPI_OVERLAP)
#undef TFT_CS
#define SPI1U_WRITE (SPIUMOSI | SPIUSSE | SPIUCSSETUP | SPIUCSHOLD)
#define SPI1U_READ (SPIUMOSI | SPIUSSE | SPIUCSSETUP | SPIUCSHOLD | SPIUDUPLEX)
#else
#ifdef ESP8266
#define SPI1U_WRITE (SPIUMOSI | SPIUSSE)
#define SPI1U_READ (SPIUMOSI | SPIUSSE | SPIUDUPLEX)
#endif
#endif
#ifndef TFT_CS
#define CS_L // No macro allocated so it generates no code
#define CS_H // No macro allocated so it generates no code
#else
#if defined (ESP8266) && (TFT_CS == 16)
#define CS_L digitalWrite(TFT_CS, LOW)
#define CS_H digitalWrite(TFT_CS, HIGH)
#elif defined (ESP32)
#if defined (ESP32_PARALLEL)
#define CS_L // The TFT CS is set permanently low during init()
#define CS_H
#else
#if TFT_CS >= 32
#ifdef RPI_ILI9486_DRIVER // RPi display needs a slower CS change
#define CS_L GPIO.out1_w1ts.val = (1 << (TFT_CS - 32)); \
GPIO.out1_w1tc.val = (1 << (TFT_CS - 32))
#define CS_H GPIO.out1_w1tc.val = (1 << (TFT_CS - 32)); \
GPIO.out1_w1ts.val = (1 << (TFT_CS - 32))
#else
#define CS_L GPIO.out1_w1tc.val = (1 << (TFT_CS - 32)); GPIO.out1_w1tc.val = (1 << (TFT_CS - 32))
#define CS_H GPIO.out1_w1ts.val = (1 << (TFT_CS - 32))//;GPIO.out1_w1ts.val = (1 << (TFT_CS - 32))
#endif
#else
#if TFT_CS >= 0
#ifdef RPI_ILI9486_DRIVER // RPi display needs a slower CS change
#define CS_L GPIO.out_w1ts = (1 << TFT_CS); GPIO.out_w1tc = (1 << TFT_CS)
#define CS_H GPIO.out_w1tc = (1 << TFT_CS); GPIO.out_w1ts = (1 << TFT_CS)
#else
#define CS_L GPIO.out_w1tc = (1 << TFT_CS);GPIO.out_w1tc = (1 << TFT_CS)
#define CS_H GPIO.out_w1ts = (1 << TFT_CS)//;GPIO.out_w1ts = (1 << TFT_CS)
#endif
#else
#define CS_L
#define CS_H
#endif
#endif
#endif
#else
#define CS_L GPOC=cspinmask
#define CS_H GPOS=cspinmask
#endif
#endif
// Use single register write for CS_L and DC_C if pins are both in range 0-31
#ifdef ESP32
#ifdef TFT_CS
#if (TFT_CS >= 0) && (TFT_CS < 32) && (TFT_DC >= 0) && (TFT_DC < 32)
#ifdef RPI_ILI9486_DRIVER // RPi display needs a slower CD and DC change
#define CS_L_DC_C GPIO.out_w1tc = ((1 << TFT_CS) | (1 << TFT_DC)); \
GPIO.out_w1tc = ((1 << TFT_CS) | (1 << TFT_DC))
#else
#define CS_L_DC_C GPIO.out_w1tc = ((1 << TFT_CS) | (1 << TFT_DC)); GPIO.out_w1tc = ((1 << TFT_CS) | (1 << TFT_DC))
#endif
#else
#define CS_L_DC_C CS_L; DC_C
#endif
#else
#define CS_L_DC_C CS_L; DC_C
#endif
#else // ESP8266
#define CS_L_DC_C CS_L; DC_C
#endif
// chip select signal for touchscreen
#ifndef TOUCH_CS
#define T_CS_L // No macro allocated so it generates no code
#define T_CS_H // No macro allocated so it generates no code
#else
#define T_CS_L digitalWrite(TOUCH_CS, LOW)
#define T_CS_H digitalWrite(TOUCH_CS, HIGH)
#endif
#ifdef TFT_WR
#if defined (ESP32)
#define WR_L GPIO.out_w1tc = (1 << TFT_WR)
#define WR_H GPIO.out_w1ts = (1 << TFT_WR)
#else
#define WR_L GPOC=wrpinmask
#define WR_H GPOS=wrpinmask
#endif
#endif
#ifdef ESP8266
// Concatenate two 16 bit values for the SPI 32 bit register write
#define SPI_32(H,L) ( (H)<<16 | (L) )
#define COL_32(H,L) ( (H)<<16 | (L) )
#else
#if defined (ESP32_PARALLEL) || defined (ILI9488_DRIVER)
#define SPI_32(H,L) ( (H)<<16 | (L) )
#else
#define SPI_32(H,L) ( ((H)<<8 | (H)>>8) | (((L)<<8 | (L)>>8)<<16 ) )
#endif
// Swap byte order for concatenated 16 bit colors
// AB CD -> DCBA for 32 bit register write
#define COL_32(H,L) ( ((H)<<8 | (H)>>8) | (((L)<<8 | (L)>>8)<<16 ) )
#endif
#if defined (ESP32) && defined (ESP32_PARALLEL)
// Mask for the 8 data bits to set pin directions
#define dir_mask ((1 << TFT_D0) | (1 << TFT_D1) | (1 << TFT_D2) | (1 << TFT_D3) | (1 << TFT_D4) | (1 << TFT_D5) | (1 << TFT_D6) | (1 << TFT_D7))
// Data bits and the write line are cleared to 0 in one step
#define clr_mask (dir_mask | (1 << TFT_WR))
// A lookup table is used to set the different bit patterns, this uses 1kByte of RAM
#define set_mask(C) xset_mask[C] // 63fps Sprite rendering test 33% faster, graphicstest only 1.8% faster than shifting in real time
// Real-time shifting alternative to above to save 1KByte RAM, 47 fps Sprite rendering test
/*#define set_mask(C) ((C&0x80)>>7)<<TFT_D7 | ((C&0x40)>>6)<<TFT_D6 | ((C&0x20)>>5)<<TFT_D5 | ((C&0x10)>>4)<<TFT_D4 | \
((C&0x08)>>3)<<TFT_D3 | ((C&0x04)>>2)<<TFT_D2 | ((C&0x02)>>1)<<TFT_D1 | ((C&0x01)>>0)<<TFT_D0
//*/
// Write 8 bits to TFT
#define tft_Write_8(C) GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t)C); WR_H
// Write 16 bits to TFT
#ifdef PSEUDO_8_BIT
#define tft_Write_16(C) WR_L;GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t)(C >> 0)); WR_H
#else
#define tft_Write_16(C) GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t)(C >> 8)); WR_H; \
GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t)(C >> 0)); WR_H
#endif
// 16 bit write with swapped bytes
#define tft_Write_16S(C) GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t) (C >> 0)); WR_H; \
GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t) (C >> 8)); WR_H
// Write 32 bits to TFT
#define tft_Write_32(C) GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t) (C >> 24)); WR_H; \
GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t) (C >> 16)); WR_H; \
GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t) (C >> 8)); WR_H; \
GPIO.out_w1tc = clr_mask; GPIO.out_w1ts = set_mask((uint8_t) (C >> 0)); WR_H
#ifdef TFT_RD
#define RD_L GPIO.out_w1tc = (1 << TFT_RD)
//#define RD_L digitalWrite(TFT_WR, LOW)
#define RD_H GPIO.out_w1ts = (1 << TFT_RD)
//#define RD_H digitalWrite(TFT_WR, HIGH)
#endif
#elif defined (ILI9488_DRIVER) // 16 bit colour converted to 3 bytes for 18 bit RGB
// Write 8 bits to TFT
#define tft_Write_8(C) spi.transfer(C)
// Convert 16 bit colour to 18 bit and write in 3 bytes
#define tft_Write_16(C) spi.transfer((C & 0xF800)>>8); \
spi.transfer((C & 0x07E0)>>3); \
spi.transfer((C & 0x001F)<<3)
// Convert swapped byte 16 bit colour to 18 bit and write in 3 bytes
#define tft_Write_16S(C) spi.transfer(C & 0xF8); \
spi.transfer((C & 0xE000)>>11 | (C & 0x07)<<5); \
spi.transfer((C & 0x1F00)>>5)
// Write 32 bits to TFT
#define tft_Write_32(C) spi.write32(C)
#elif defined (RPI_ILI9486_DRIVER)
#define tft_Write_8(C) spi.transfer(0); spi.transfer(C)
#define tft_Write_16(C) spi.write16(C)
#define tft_Write_16S(C) spi.write16(C<<8 | C>>8)
#define tft_Write_32(C) spi.write32(C)
#elif defined ESP8266
#define tft_Write_8(C) spi.write(C)
#define tft_Write_16(C) spi.write16(C)
#define tft_Write_32(C) spi.write32(C)
#else // ESP32 using SPI with 16 bit color display
// ESP32 low level SPI writes for 8, 16 and 32 bit values
// to avoid the function call overhead
// Write 8 bits
#define tft_Write_8(C) \
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 8-1); \
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), C); \
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR); \
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
// Write 16 bits with corrected endianess for 16 bit colours
#define tft_Write_16(C) \
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 16-1); \
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), C<<8 | C>>8); \
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR); \
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
// Write 16 bits
#define tft_Write_16S(C) \
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 16-1); \
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), C); \
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR); \
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
// Write 32 bits
#define tft_Write_32(C) \
WRITE_PERI_REG(SPI_MOSI_DLEN_REG(SPI_PORT), 32-1); \
WRITE_PERI_REG(SPI_W0_REG(SPI_PORT), C); \
SET_PERI_REG_MASK(SPI_CMD_REG(SPI_PORT), SPI_USR); \
while (READ_PERI_REG(SPI_CMD_REG(SPI_PORT))&SPI_USR);
#endif
#if !defined (ESP32_PARALLEL)
// Read from display using SPI or software SPI
#if defined (ESP8266) && defined (TFT_SDA_READ)
// Use a bit banged function call for ESP8266 and bi-directional SDA pin
#define SCLK_L GPOC=sclkpinmask
#define SCLK_H GPOS=sclkpinmask
#else
// Use a SPI read transfer
#define tft_Read_8() spi.transfer(0)
#endif
// Make sure TFT_MISO is defined if not used to avoid an error message
#ifndef TFT_MISO
#define TFT_MISO -1
#endif
#endif
#ifdef LOAD_GFXFF
// We can include all the free fonts and they will only be built into
// the sketch if they are used
#include <Fonts/GFXFF/gfxfont.h>
// Call up any user custom fonts
// #include <User_Setups/User_Custom_Fonts.h>
// New custom font file #includes
#include <Fonts/Custom/Orbitron_Light_24.h> // CF_OL24
#include <Fonts/Custom/Orbitron_Light_32.h> // CF_OL32
#include <Fonts/Custom/Roboto_Thin_24.h> // CF_RT24
#include <Fonts/Custom/Satisfy_24.h> // CF_S24
#include <Fonts/Custom/Yellowtail_32.h> // CF_Y32
// Original Adafruit_GFX "Free Fonts"
#include <Fonts/GFXFF/TomThumb.h> // TT1
#include <Fonts/GFXFF/FreeMono9pt7b.h> // FF1 or FM9
#include <Fonts/GFXFF/FreeMono12pt7b.h> // FF2 or FM12
#include <Fonts/GFXFF/FreeMono18pt7b.h> // FF3 or FM18
#include <Fonts/GFXFF/FreeMono24pt7b.h> // FF4 or FM24
#include <Fonts/GFXFF/FreeMonoOblique9pt7b.h> // FF5 or FMO9
#include <Fonts/GFXFF/FreeMonoOblique12pt7b.h> // FF6 or FMO12
#include <Fonts/GFXFF/FreeMonoOblique18pt7b.h> // FF7 or FMO18
#include <Fonts/GFXFF/FreeMonoOblique24pt7b.h> // FF8 or FMO24
#include <Fonts/GFXFF/FreeMonoBold9pt7b.h> // FF9 or FMB9
#include <Fonts/GFXFF/FreeMonoBold12pt7b.h> // FF10 or FMB12
#include <Fonts/GFXFF/FreeMonoBold18pt7b.h> // FF11 or FMB18
#include <Fonts/GFXFF/FreeMonoBold24pt7b.h> // FF12 or FMB24
#include <Fonts/GFXFF/FreeMonoBoldOblique9pt7b.h> // FF13 or FMBO9
#include <Fonts/GFXFF/FreeMonoBoldOblique12pt7b.h> // FF14 or FMBO12
#include <Fonts/GFXFF/FreeMonoBoldOblique18pt7b.h> // FF15 or FMBO18
#include <Fonts/GFXFF/FreeMonoBoldOblique24pt7b.h> // FF16 or FMBO24
// Sans serif fonts
#include <Fonts/GFXFF/FreeSans9pt7b.h> // FF17 or FSS9
#include <Fonts/GFXFF/FreeSans12pt7b.h> // FF18 or FSS12
#include <Fonts/GFXFF/FreeSans18pt7b.h> // FF19 or FSS18
#include <Fonts/GFXFF/FreeSans24pt7b.h> // FF20 or FSS24
#include <Fonts/GFXFF/FreeSansOblique9pt7b.h> // FF21 or FSSO9
#include <Fonts/GFXFF/FreeSansOblique12pt7b.h> // FF22 or FSSO12
#include <Fonts/GFXFF/FreeSansOblique18pt7b.h> // FF23 or FSSO18
#include <Fonts/GFXFF/FreeSansOblique24pt7b.h> // FF24 or FSSO24
#include <Fonts/GFXFF/FreeSansBold9pt7b.h> // FF25 or FSSB9
#include <Fonts/GFXFF/FreeSansBold12pt7b.h> // FF26 or FSSB12
#include <Fonts/GFXFF/FreeSansBold18pt7b.h> // FF27 or FSSB18
#include <Fonts/GFXFF/FreeSansBold24pt7b.h> // FF28 or FSSB24
#include <Fonts/GFXFF/FreeSansBoldOblique9pt7b.h> // FF29 or FSSBO9
#include <Fonts/GFXFF/FreeSansBoldOblique12pt7b.h> // FF30 or FSSBO12
#include <Fonts/GFXFF/FreeSansBoldOblique18pt7b.h> // FF31 or FSSBO18
#include <Fonts/GFXFF/FreeSansBoldOblique24pt7b.h> // FF32 or FSSBO24
// Serif fonts
#include <Fonts/GFXFF/FreeSerif9pt7b.h> // FF33 or FS9
#include <Fonts/GFXFF/FreeSerif12pt7b.h> // FF34 or FS12
#include <Fonts/GFXFF/FreeSerif18pt7b.h> // FF35 or FS18
#include <Fonts/GFXFF/FreeSerif24pt7b.h> // FF36 or FS24
#include <Fonts/GFXFF/FreeSerifItalic9pt7b.h> // FF37 or FSI9
#include <Fonts/GFXFF/FreeSerifItalic12pt7b.h> // FF38 or FSI12
#include <Fonts/GFXFF/FreeSerifItalic18pt7b.h> // FF39 or FSI18
#include <Fonts/GFXFF/FreeSerifItalic24pt7b.h> // FF40 or FSI24
#include <Fonts/GFXFF/FreeSerifBold9pt7b.h> // FF41 or FSB9
#include <Fonts/GFXFF/FreeSerifBold12pt7b.h> // FF42 or FSB12
#include <Fonts/GFXFF/FreeSerifBold18pt7b.h> // FF43 or FSB18
#include <Fonts/GFXFF/FreeSerifBold24pt7b.h> // FF44 or FSB24
#include <Fonts/GFXFF/FreeSerifBoldItalic9pt7b.h> // FF45 or FSBI9
#include <Fonts/GFXFF/FreeSerifBoldItalic12pt7b.h> // FF46 or FSBI12
#include <Fonts/GFXFF/FreeSerifBoldItalic18pt7b.h> // FF47 or FSBI18
#include <Fonts/GFXFF/FreeSerifBoldItalic24pt7b.h> // FF48 or FSBI24
#endif // #ifdef LOAD_GFXFF
//These enumerate the text plotting alignment (reference datum point)
#define TL_DATUM 0 // Top left (default)
#define TC_DATUM 1 // Top centre
#define TR_DATUM 2 // Top right
#define ML_DATUM 3 // Middle left
#define CL_DATUM 3 // Centre left, same as above
#define MC_DATUM 4 // Middle centre
#define CC_DATUM 4 // Centre centre, same as above
#define MR_DATUM 5 // Middle right
#define CR_DATUM 5 // Centre right, same as above
#define BL_DATUM 6 // Bottom left
#define BC_DATUM 7 // Bottom centre
#define BR_DATUM 8 // Bottom right
#define L_BASELINE 9 // Left character baseline (Line the 'A' character would sit on)
#define C_BASELINE 10 // Centre character baseline
#define R_BASELINE 11 // Right character baseline
// New color definitions use for all my libraries
#define TFT_BLACK 0x0000 /* 0, 0, 0 */
#define TFT_NAVY 0x000F /* 0, 0, 128 */
#define TFT_DARKGREEN 0x03E0 /* 0, 128, 0 */
#define TFT_DARKCYAN 0x03EF /* 0, 128, 128 */
#define TFT_MAROON 0x7800 /* 128, 0, 0 */
#define TFT_PURPLE 0x780F /* 128, 0, 128 */
#define TFT_OLIVE 0x7BE0 /* 128, 128, 0 */
#define TFT_LIGHTGREY 0xC618 /* 192, 192, 192 */
#define TFT_DARKGREY 0x7BEF /* 128, 128, 128 */
#define TFT_BLUE 0x001F /* 0, 0, 255 */
#define TFT_GREEN 0x07E0 /* 0, 255, 0 */
#define TFT_CYAN 0x07FF /* 0, 255, 255 */
#define TFT_RED 0xF800 /* 255, 0, 0 */
#define TFT_MAGENTA 0xF81F /* 255, 0, 255 */
#define TFT_YELLOW 0xFFE0 /* 255, 255, 0 */
#define TFT_WHITE 0xFFFF /* 255, 255, 255 */
#define TFT_ORANGE 0xFDA0 /* 255, 180, 0 */
#define TFT_GREENYELLOW 0xB7E0 /* 180, 255, 0 */
#define TFT_PINK 0xFC9F
// Next is a special 16 bit colour value that encodes to 8 bits
// and will then decode back to the same 16 bit value.
// Convenient for 8 bit and 16 bit transparent sprites.
#define TFT_TRANSPARENT 0x0120
// Swap any type
template <typename T> static inline void
swap_coord(T& a, T& b) { T t = a; a = b; b = t; }
#ifndef min
// Return minimum of two numbers, may already be defined
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
// This structure allows sketches to retrieve the user setup parameters at runtime
// by calling getSetup(), zero impact on code size unless used, mainly for diagnostics
typedef struct
{
String version = TFT_ESPI_VERSION;
int16_t esp;
uint8_t trans;
uint8_t serial;
uint8_t overlap;
#if defined (ESP32)
#if defined (USE_HSPI_PORT)
uint8_t port = HSPI;
#else
uint8_t port = VSPI;
#endif
#endif
uint16_t tft_driver; // Hexadecimal code
uint16_t tft_width; // Rotation 0 width and height
uint16_t tft_height;
uint8_t r0_x_offset; // Offsets, not all used yet
uint8_t r0_y_offset;
uint8_t r1_x_offset;
uint8_t r1_y_offset;
uint8_t r2_x_offset;
uint8_t r2_y_offset;
uint8_t r3_x_offset;
uint8_t r3_y_offset;
int8_t pin_tft_mosi;
int8_t pin_tft_miso;
int8_t pin_tft_clk;
int8_t pin_tft_cs;
int8_t pin_tft_dc;
int8_t pin_tft_rd;
int8_t pin_tft_wr;
int8_t pin_tft_rst;
int8_t pin_tft_d0;
int8_t pin_tft_d1;
int8_t pin_tft_d2;
int8_t pin_tft_d3;
int8_t pin_tft_d4;
int8_t pin_tft_d5;
int8_t pin_tft_d6;
int8_t pin_tft_d7;
int8_t pin_tch_cs;
int16_t tft_spi_freq;
int16_t tft_rd_freq;
int16_t tch_spi_freq;
} setup_t;
// This is a structure to conveniently hold information on the default fonts
// Stores pointer to font character image address table, width table and height
// Create a null set in case some fonts not used (to prevent crash)
const uint8_t widtbl_null[1] = {0};
PROGMEM const uint8_t chr_null[1] = {0};
PROGMEM const uint8_t* const chrtbl_null[1] = {chr_null};
typedef struct {
const uint8_t *chartbl;
const uint8_t *widthtbl;
uint8_t height;
uint8_t baseline;
} fontinfo;
// Now fill the structure
const PROGMEM fontinfo fontdata [] = {
#ifdef LOAD_GLCD
{ (const uint8_t *)font, widtbl_null, 0, 0 },
#else
{ (const uint8_t *)chrtbl_null, widtbl_null, 0, 0 },
#endif
// GLCD font (Font 1) does not have all parameters
{ (const uint8_t *)chrtbl_null, widtbl_null, 8, 7 },
#ifdef LOAD_FONT2
{ (const uint8_t *)chrtbl_f16, widtbl_f16, chr_hgt_f16, baseline_f16},
#else
{ (const uint8_t *)chrtbl_null, widtbl_null, 0, 0 },
#endif
// Font 3 current unused
{ (const uint8_t *)chrtbl_null, widtbl_null, 0, 0 },
#ifdef LOAD_FONT4
{ (const uint8_t *)chrtbl_f32, widtbl_f32, chr_hgt_f32, baseline_f32},
#else
{ (const uint8_t *)chrtbl_null, widtbl_null, 0, 0 },
#endif
// Font 5 current unused
{ (const uint8_t *)chrtbl_null, widtbl_null, 0, 0 },
#ifdef LOAD_FONT6
{ (const uint8_t *)chrtbl_f64, widtbl_f64, chr_hgt_f64, baseline_f64},
#else
{ (const uint8_t *)chrtbl_null, widtbl_null, 0, 0 },
#endif
#ifdef LOAD_FONT7
{ (const uint8_t *)chrtbl_f7s, widtbl_f7s, chr_hgt_f7s, baseline_f7s},
#else
{ (const uint8_t *)chrtbl_null, widtbl_null, 0, 0 },
#endif
#ifdef LOAD_FONT8
{ (const uint8_t *)chrtbl_f72, widtbl_f72, chr_hgt_f72, baseline_f72}
#else
{ (const uint8_t *)chrtbl_null, widtbl_null, 0, 0 }
#endif
};
typedef uint16_t (*getColorCallback)(uint16_t x, uint16_t y);
// Class functions and variables
class TFT_eSPI : public Print {
public:
TFT_eSPI(int16_t _W = TFT_WIDTH, int16_t _H = TFT_HEIGHT);
void init(uint8_t tc = TAB_COLOUR), begin(uint8_t tc = TAB_COLOUR); // Same - begin included for backwards compatibility
// These are virtual so the TFT_eSprite class can override them with sprite specific functions
virtual void drawPixel(int32_t x, int32_t y, uint32_t color),
drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size),
drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color),
drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color),
drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color),
fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
virtual int16_t drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font),
drawChar(uint16_t uniCode, int32_t x, int32_t y),
height(void),
width(void);
// The TFT_eSprite class inherits the following functions
void setWindow(int32_t xs, int32_t ys, int32_t xe, int32_t ye),
pushColor(uint16_t color),
pushColor(uint16_t color, uint32_t len),
pushColors(uint16_t *data, uint32_t len, bool swap = true), // With byte swap option
pushColors(uint8_t *data, uint32_t len),
fillScreen(uint32_t color);
void drawRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color),
drawRoundRect(int32_t x0, int32_t y0, int32_t w, int32_t h, int32_t radius, uint32_t color),
fillRoundRect(int32_t x0, int32_t y0, int32_t w, int32_t h, int32_t radius, uint32_t color),
setRotation(uint8_t r),
invertDisplay(boolean i),
drawCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color),
drawCircleHelper(int32_t x0, int32_t y0, int32_t r, uint8_t cornername, uint32_t color),
fillCircle(int32_t x0, int32_t y0, int32_t r, uint32_t color),
fillCircleHelper(int32_t x0, int32_t y0, int32_t r, uint8_t cornername, int32_t delta, uint32_t color),
drawEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color),
fillEllipse(int16_t x0, int16_t y0, int32_t rx, int32_t ry, uint16_t color),
drawTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color),
fillTriangle(int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint32_t color),
drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color),
drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color),
drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t fgcolor, uint16_t bgcolor),
setBitmapColor(uint16_t fgcolor, uint16_t bgcolor), // For 1bpp sprites
setPivot(int16_t x, int16_t y),
setCursor(int16_t x, int16_t y),
setCursor(int16_t x, int16_t y, uint8_t font),
setTextColor(uint16_t color),
setTextColor(uint16_t fgcolor, uint16_t bgcolor),
setTextSize(uint8_t size),
setTextWrap(boolean wrapX, boolean wrapY = false),
setTextDatum(uint8_t datum),
setTextPadding(uint16_t x_width),
#ifdef LOAD_GFXFF
setFreeFont(const GFXfont *f = NULL),
setTextFont(uint8_t font),
#else
setFreeFont(uint8_t font),
setTextFont(uint8_t font),
#endif
spiwrite(uint8_t),
writecommand(uint8_t c),
writedata(uint8_t d),
commandList(const uint8_t *addr);
uint8_t readcommand8(uint8_t cmd_function, uint8_t index = 0);
uint16_t readcommand16(uint8_t cmd_function, uint8_t index = 0);
uint32_t readcommand32(uint8_t cmd_function, uint8_t index = 0);
// Read the colour of a pixel at x,y and return value in 565 format
uint16_t readPixel(int32_t x0, int32_t y0);
void setCallback(getColorCallback getCol);
// The next functions can be used as a pair to copy screen blocks (or horizontal/vertical lines) to another location
// Read a block of pixels to a data buffer, buffer is 16 bit and the array size must be at least w * h
void readRect(int32_t x0, int32_t y0, int32_t w, int32_t h, uint16_t *data);
// Write a block of pixels to the screen
void pushRect(int32_t x0, int32_t y0, int32_t w, int32_t h, uint16_t *data);
// These are used to render images or sprites stored in RAM arrays
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, uint16_t *data);
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, uint16_t *data, uint16_t transparent);
// These are used to render images stored in FLASH (PROGMEM)
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, const uint16_t *data, uint16_t transparent);
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, const uint16_t *data);
// These are used by pushSprite for 1 and 8 bit colours
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data, bool bpp8 = true);
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data, uint8_t transparent, bool bpp8 = true);
// Swap the byte order for pushImage() - corrects endianness
void setSwapBytes(bool swap);
bool getSwapBytes(void);
// This next function has been used successfully to dump the TFT screen to a PC for documentation purposes
// It reads a screen area and returns the RGB 8 bit colour values of each pixel
// Set w and h to 1 to read 1 pixel's colour. The data buffer must be at least w * h * 3 bytes
void readRectRGB(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data);
uint8_t getRotation(void),
getTextDatum(void),
color16to8(uint16_t color565); // Convert 16 bit colour to 8 bits
int16_t getCursorX(void),
getCursorY(void);
int16_t getPivotX(void),
getPivotY(void);
uint16_t fontsLoaded(void),
color565(uint8_t red, uint8_t green, uint8_t blue), // Convert 8 bit red, green and blue to 16 bits
color8to16(uint8_t color332); // Convert 8 bit colour to 16 bits
int16_t drawNumber(long long_num, int32_t poX, int32_t poY, uint8_t font),
drawNumber(long long_num, int32_t poX, int32_t poY),
drawFloat(float floatNumber, uint8_t decimal, int32_t poX, int32_t poY, uint8_t font),
drawFloat(float floatNumber, uint8_t decimal, int32_t poX, int32_t poY),
// Handle char arrays
drawString(const char *string, int32_t poX, int32_t poY, uint8_t font),
drawString(const char *string, int32_t poX, int32_t poY),
drawCentreString(const char *string, int32_t dX, int32_t poY, uint8_t font), // Deprecated, use setTextDatum() and drawString()
drawRightString(const char *string, int32_t dX, int32_t poY, uint8_t font), // Deprecated, use setTextDatum() and drawString()
// Handle String type
drawString(const String& string, int32_t poX, int32_t poY, uint8_t font),
drawString(const String& string, int32_t poX, int32_t poY),
drawCentreString(const String& string, int32_t dX, int32_t poY, uint8_t font), // Deprecated, use setTextDatum() and drawString()
drawRightString(const String& string, int32_t dX, int32_t poY, uint8_t font); // Deprecated, use setTextDatum() and drawString()
int16_t textWidth(const char *string, uint8_t font),
textWidth(const char *string),
textWidth(const String& string, uint8_t font),
textWidth(const String& string),
fontHeight(int16_t font),
fontHeight(void);
void setAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h);
// Compatibility additions
void startWrite(void); // Begin SPI transaction
void writeColor(uint16_t color, uint32_t len); // Write colours without transaction overhead
void endWrite(void); // End SPI transaction
uint16_t decodeUTF8(uint8_t *buf, uint16_t *index, uint16_t remaining);
uint16_t decodeUTF8(uint8_t c);
size_t write(uint8_t);
#ifdef TFT_SDA_READ
#if defined (ESP8266) && defined (TFT_SDA_READ)
uint8_t tft_Read_8(void);
#endif
void begin_SDA_Read(void);
void end_SDA_Read(void);
#endif
#ifdef USE_M5_FONT_CREATOR
int32_t getUnicodeFontIndex(uint32_t unicode);
#endif
// Set or get an arbitrary library attribute or configuration option
void setAttribute(uint8_t id = 0, uint8_t a = 0);
uint8_t getAttribute(uint8_t id = 0);
void getSetup(setup_t& tft_settings); // Sketch provides the instance to populate
static SPIClass& getSPIinstance(void);
int32_t cursor_x, cursor_y, padX;
uint32_t textcolor, textbgcolor;
uint32_t bitmap_fg, bitmap_bg;
uint8_t textfont, // Current selected font
textsize, // Current font size multiplier
textdatum, // Text reference datum
rotation; // Display rotation (0-7, second four are mirrored)
int16_t _xpivot; // x pivot point coordinate
int16_t _ypivot; // x pivot point coordinate
uint8_t decoderState = 0; // UTF8 decoder state
uint16_t decoderBuffer; // Unicode code-point buffer
private:
inline void spi_begin() __attribute__((always_inline));
inline void spi_end() __attribute__((always_inline));
inline void spi_begin_read() __attribute__((always_inline));
inline void spi_end_read() __attribute__((always_inline));
void readAddrWindow(int32_t xs, int32_t ys, int32_t w, int32_t h);
uint8_t tabcolor,
colstart = 0, rowstart = 0; // some ST7735 displays need this changed
volatile uint32_t *dcport, *csport;
uint32_t cspinmask, dcpinmask, wrpinmask, sclkpinmask;
#if defined(ESP32_PARALLEL)
uint32_t xclr_mask, xdir_mask, xset_mask[256];
#endif
uint32_t lastColor = 0xFFFF;
protected:
getColorCallback getColor = nullptr;
uint8_t _smooth_bpp;
int32_t win_xe, win_ye;
int32_t _init_width, _init_height; // Display w/h as input, used by setRotation()
int32_t _width, _height; // Display w/h as modified by current rotation
int32_t addr_row, addr_col;
uint32_t fontsloaded;
uint8_t glyph_ab, // glyph delta Y (height) above baseline
glyph_bb; // glyph delta Y (height) below baseline
bool isDigits; // adjust bounding box for numbers to reduce visual jiggling
bool textwrapX, textwrapY; // If set, 'wrap' text at right and optionally bottom edge of display
bool _swapBytes; // Swap the byte order for TFT pushImage()
bool locked, inTransaction; // Transaction and mutex lock flags for ESP32
bool _booted; // init() or begin() has already run once
bool _cp437; // If set, use correct CP437 charset (default is ON)
bool _utf8; // If set, use UTF-8 decoder in print stream 'write()' function (default ON)
uint32_t _lastColor; // Buffered value of last colour used
#ifdef LOAD_GFXFF
GFXfont *gfxFont;
#endif
// Load the Touch extension
#ifdef TOUCH_CS
#include "Extensions/Touch.h"
#endif
// Load the Anti-aliased font extension
#ifdef SMOOTH_FONT
// #include "Extensions/Smooth_font.h"
// Coded by Bodmer 10/2/18, see license in root directory.
// This is part of the TFT_eSPI class and is associated with anti-aliased font functions
public:
// These are for the new antialiased fonts
void loadFont(String fontName, fs::FS &ffs);
void loadFont(String fontName, bool flash = true);
void unloadFont( void );
bool getUnicodeIndex(uint16_t unicode, uint16_t *index);
virtual uint16_t alphaBlend(uint8_t alpha, uint16_t fgc, uint16_t bgc);
virtual void drawGlyph(uint16_t code);
void showFont(uint32_t td);
// This is for the whole font
typedef struct
{
uint16_t gCount; // Total number of characters
uint16_t yAdvance; // Line advance
uint16_t spaceWidth; // Width of a space character
int16_t ascent; // Height of top of 'd' above baseline, other characters may be taller
int16_t descent; // Offset to bottom of 'p', other characters may have a larger descent
uint16_t maxAscent; // Maximum ascent found in font
uint16_t maxDescent; // Maximum descent found in font
} fontMetrics;
fontMetrics gFont = { 0, 0, 0, 0, 0, 0, 0 };
// These are for the metrics for each individual glyph (so we don't need to seek this in file and waste time)
uint16_t* gUnicode = NULL; //UTF-16 code, the codes are searched so do not need to be sequential
uint8_t* gHeight = NULL; //cheight
uint8_t* gWidth = NULL; //cwidth
uint8_t* gxAdvance = NULL; //setWidth
int16_t* gdY = NULL; //topExtent
int8_t* gdX = NULL; //leftExtent
uint32_t* gBitmap = NULL; //file pointer to greyscale bitmap
bool fontLoaded = false; // Flags when a anti-aliased font is loaded
fs::File fontFile;
private:
void loadMetrics(uint16_t gCount);
uint32_t readInt32(void);
fs::FS &fontFS = SPIFFS;
bool spiffs = true;
#endif
}; // End of class TFT_eSPI
// Load the Button Class
// #include "Extensions/Button.h"
// Load the Sprite Class
// #include "Extensions/Sprite.h"
#endif

View File

@@ -0,0 +1,287 @@
// USER DEFINED SETTINGS
// Set driver type, fonts to be loaded, pins used and SPI control method etc
//
// See the User_Setup_Select.h file if you wish to be able to define multiple
// setups and then easily select which setup file is used by the compiler.
//
// If this file is edited correctly then all the library example sketches should
// run without the need to make any more changes for a particular hardware setup!
// Note that some sketches are designed for a particular TFT pixel width/height
// ##################################################################################
//
// Section 1. Call up the right driver file and any options for it
//
// ##################################################################################
// Only define one driver, the other ones must be commented out
#define ILI9341_DRIVER
//#define ST7735_DRIVER // Define additional parameters below for this display
//#define ILI9163_DRIVER // Define additional parameters below for this display
//#define S6D02A1_DRIVER
//#define RPI_ILI9486_DRIVER // 20MHz maximum SPI
//#define HX8357D_DRIVER
//#define ILI9481_DRIVER
//#define ILI9486_DRIVER
//#define ILI9488_DRIVER // WARNING: Do not connect ILI9488 display SDO to MISO if other devices share the SPI bus (TFT SDO does NOT tristate when CS is high)
//#define ST7789_DRIVER // Full configuration option, define additional parameters below for this display
//#define ST7789_2_DRIVER // Minimal configuration option, define additional parameters below for this display
//#define R61581_DRIVER
//#define RM68140_DRIVER
#include "ILI9341_Defines.h"
#define TFT_DRIVER 0x9341
// Some displays support SPI reads via the MISO pin, other displays have a single
// bi-directional SDA pin and the library will try to read this via the MOSI line.
// To use the SDA line for reading data from the TFT uncomment the following line:
#define TFT_SDA_READ // This option is for ESP32 ONLY, tested with ST7789 display only
// For ST7789 and ILI9341 ONLY, define the colour order IF the blue and red are swapped on your display
// Try ONE option at a time to find the correct colour order for your display
// #define TFT_RGB_ORDER TFT_RGB // Colour order Red-Green-Blue
// #define TFT_RGB_ORDER TFT_BGR // Colour order Blue-Green-Red
// For M5Stack ESP32 module with integrated ILI9341 display ONLY, remove // in line below
#define M5STACK
// For ST7789, ST7735 and ILI9163 ONLY, define the pixel width and height in portrait orientation
// #define TFT_WIDTH 80
// #define TFT_WIDTH 128
// #define TFT_WIDTH 240 // ST7789 240 x 240 and 240 x 320
// #define TFT_HEIGHT 160
// #define TFT_HEIGHT 128
// #define TFT_HEIGHT 240 // ST7789 240 x 240
// #define TFT_HEIGHT 320 // ST7789 240 x 320
// For ST7735 ONLY, define the type of display, originally this was based on the
// colour of the tab on the screen protector film but this is not always true, so try
// out the different options below if the screen does not display graphics correctly,
// e.g. colours wrong, mirror images, or tray pixels at the edges.
// Comment out ALL BUT ONE of these options for a ST7735 display driver, save this
// this User_Setup file, then rebuild and upload the sketch to the board again:
// #define ST7735_INITB
// #define ST7735_GREENTAB
// #define ST7735_GREENTAB2
// #define ST7735_GREENTAB3
// #define ST7735_GREENTAB128 // For 128 x 128 display
// #define ST7735_GREENTAB160x80 // For 160 x 80 display (BGR, inverted, 26 offset)
// #define ST7735_REDTAB
// #define ST7735_BLACKTAB
// #define ST7735_REDTAB160x80 // For 160 x 80 display with 24 pixel offset
// If colours are inverted (white shows as black) then uncomment one of the next
// 2 lines try both options, one of the options should correct the inversion.
// #define TFT_INVERSION_ON
// #define TFT_INVERSION_OFF
// If a backlight control signal is available then define the TFT_BL pin in Section 2
// below. The backlight will be turned ON when tft.begin() is called, but the library
// needs to know if the LEDs are ON with the pin HIGH or LOW. If the LEDs are to be
// driven with a PWM signal or turned OFF/ON then this must be handled by the user
// sketch. e.g. with digitalWrite(TFT_BL, LOW);
// #define TFT_BACKLIGHT_ON HIGH // HIGH or LOW are options
// ##################################################################################
//
// Section 2. Define the pins that are used to interface with the display here
//
// ##################################################################################
// We must use hardware SPI, a minimum of 3 GPIO pins is needed.
// Typical setup for ESP8266 NodeMCU ESP-12 is :
//
// Display SDO/MISO to NodeMCU pin D6 (or leave disconnected if not reading TFT)
// Display LED to NodeMCU pin VIN (or 5V, see below)
// Display SCK to NodeMCU pin D5
// Display SDI/MOSI to NodeMCU pin D7
// Display DC (RS/AO)to NodeMCU pin D3
// Display RESET to NodeMCU pin D4 (or RST, see below)
// Display CS to NodeMCU pin D8 (or GND, see below)
// Display GND to NodeMCU pin GND (0V)
// Display VCC to NodeMCU 5V or 3.3V
//
// The TFT RESET pin can be connected to the NodeMCU RST pin or 3.3V to free up a control pin
//
// The DC (Data Command) pin may be labeled AO or RS (Register Select)
//
// With some displays such as the ILI9341 the TFT CS pin can be connected to GND if no more
// SPI devices (e.g. an SD Card) are connected, in this case comment out the #define TFT_CS
// line below so it is NOT defined. Other displays such at the ST7735 require the TFT CS pin
// to be toggled during setup, so in these cases the TFT_CS line must be defined and connected.
//
// The NodeMCU D0 pin can be used for RST
//
//
// Note: only some versions of the NodeMCU provide the USB 5V on the VIN pin
// If 5V is not available at a pin you can use 3.3V but backlight brightness
// will be lower.
// ###### EDIT THE PIN NUMBERS IN THE LINES FOLLOWING TO SUIT YOUR ESP8266 SETUP ######
// For NodeMCU - use pin numbers in the form PIN_Dx where Dx is the NodeMCU pin designation
//#define TFT_CS PIN_D8 // Chip select control pin D8
//#define TFT_DC PIN_D3 // Data Command control pin
//#define TFT_RST PIN_D4 // Reset pin (could connect to NodeMCU RST, see next line)
//#define TFT_RST -1 // Set TFT_RST to -1 if the display RESET is connected to NodeMCU RST or 3.3V
//#define TFT_BL PIN_D1 // LED back-light (only for ST7789 with backlight control pin)
//#define TOUCH_CS PIN_D2 // Chip select pin (T_CS) of touch screen
//#define TFT_WR PIN_D2 // Write strobe for modified Raspberry Pi TFT only
// ###### FOR ESP8266 OVERLAP MODE EDIT THE PIN NUMBERS IN THE FOLLOWING LINES ######
// Overlap mode shares the ESP8266 FLASH SPI bus with the TFT so has a performance impact
// but saves pins for other functions. It is best not to connect MISO as some displays
// do not tristate that line wjen chip select is high!
// On NodeMCU 1.0 SD0=MISO, SD1=MOSI, CLK=SCLK to connect to TFT in overlap mode
// On NodeMCU V3 S0 =MISO, S1 =MOSI, S2 =SCLK
// In ESP8266 overlap mode the following must be defined
//#define TFT_SPI_OVERLAP
// In ESP8266 overlap mode the TFT chip select MUST connect to pin D3
//#define TFT_CS PIN_D3
//#define TFT_DC PIN_D5 // Data Command control pin
//#define TFT_RST PIN_D4 // Reset pin (could connect to NodeMCU RST, see next line)
//#define TFT_RST -1 // Set TFT_RST to -1 if the display RESET is connected to NodeMCU RST or 3.3V
// ###### EDIT THE PIN NUMBERS IN THE LINES FOLLOWING TO SUIT YOUR ESP32 SETUP ######
// For ESP32 Dev board (only tested with ILI9341 display)
// The hardware SPI can be mapped to any pins
//#define TFT_MISO 19
//#define TFT_MOSI 23
//#define TFT_SCLK 18
//#define TFT_CS 15 // Chip select control pin
//#define TFT_DC 2 // Data Command control pin
//#define TFT_RST 4 // Reset pin (could connect to RST pin)
//#define TFT_RST -1 // Set TFT_RST to -1 if display RESET is connected to ESP32 board RST
//#define TFT_BL 32 // LED back-light (only for ST7789 with backlight control pin)
//#define TOUCH_CS 21 // Chip select pin (T_CS) of touch screen
//#define TFT_WR 22 // Write strobe for modified Raspberry Pi TFT only
// For the M5Stack module use these #define lines
#define TFT_MISO 38
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS 5 // Chip select control pin
#define TFT_DC 15 // Data Command control pin
#define TFT_RST -1 // Reset pin (could connect to Arduino RESET pin)
#define TFT_BL -1 // LED back-light (required for M5Stack)
// ###### EDIT THE PINs BELOW TO SUIT YOUR ESP32 PARALLEL TFT SETUP ######
// The library supports 8 bit parallel TFTs with the ESP32, the pin
// selection below is compatible with ESP32 boards in UNO format.
// Wemos D32 boards need to be modified, see diagram in Tools folder.
// Only ILI9481 and ILI9341 based displays have been tested!
// Parallel bus is only supported on ESP32
// Uncomment line below to use ESP32 Parallel interface instead of SPI
//#define ESP32_PARALLEL
// The ESP32 and TFT the pins used for testing are:
//#define TFT_CS 33 // Chip select control pin (library pulls permanently low
//#define TFT_DC 15 // Data Command control pin - must use a pin in the range 0-31
//#define TFT_RST 32 // Reset pin, toggles on startup
//#define TFT_WR 4 // Write strobe control pin - must use a pin in the range 0-31
//#define TFT_RD 2 // Read strobe control pin
//#define TFT_D0 12 // Must use pins in the range 0-31 for the data bus
//#define TFT_D1 13 // so a single register write sets/clears all bits.
//#define TFT_D2 26 // Pins can be randomly assigned, this does not affect
//#define TFT_D3 25 // TFT screen update performance.
//#define TFT_D4 17
//#define TFT_D5 16
//#define TFT_D6 27
//#define TFT_D7 14
// ##################################################################################
//
// Section 3. Define the fonts that are to be used here
//
// ##################################################################################
// Comment out the #defines below with // to stop that font being loaded
// The ESP8366 and ESP32 have plenty of memory so commenting out fonts is not
// normally necessary. If all fonts are loaded the extra FLASH space required is
// about 17Kbytes. To save FLASH space only enable the fonts you need!
#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
// Comment out the #define below to stop the SPIFFS filing system and smooth font code being loaded
// this will save ~20kbytes of FLASH
#define SMOOTH_FONT
// ##################################################################################
//
// Section 4. Other options
//
// ##################################################################################
// Define the SPI clock frequency, this affects the graphics rendering speed. Too
// fast and the TFT driver will not keep up and display corruption appears.
// With an ILI9341 display 40MHz works OK, 80MHz sometimes fails
// With a ST7735 display more than 27MHz may not work (spurious pixels and lines)
// With an ILI9163 display 27 MHz works OK.
// The RPi typically only works at 20MHz maximum.
// #define SPI_FREQUENCY 1000000
// #define SPI_FREQUENCY 5000000
// #define SPI_FREQUENCY 10000000
// #define SPI_FREQUENCY 20000000
// #define SPI_FREQUENCY 27000000 // Actually sets it to 26.67MHz = 80/3
#define SPI_FREQUENCY 40000000 // Maximum to use SPIFFS
// #define SPI_FREQUENCY 80000000
// Optional reduced SPI frequency for reading TFT
#define SPI_READ_FREQUENCY 16000000
// The XPT2046 requires a lower SPI clock rate of 2.5MHz so we define that here:
// #define SPI_TOUCH_FREQUENCY 2500000
// The ESP32 has 2 free SPI ports i.e. VSPI and HSPI, the VSPI is the default.
// If the VSPI port is in use and pins are not accessible (e.g. TTGO T-Beam)
// then uncomment the following line:
//#define USE_HSPI_PORT
// Comment out the following #define if "SPI Transactions" do not need to be
// supported. When commented out the code size will be smaller and sketches will
// run slightly faster, so leave it commented out unless you need it!
// Transaction support is needed to work with SD library but not needed with TFT_SdFat
// Transaction support is required if other SPI devices are connected.
// Transactions are automatically enabled by the library for an ESP32 (to use HAL mutex)
// so changing it here has no effect
// #define SUPPORT_TRANSACTIONS
#define USE_M5_FONT_CREATOR

View File

@@ -0,0 +1,724 @@
#include "M5Button.h"
// Button class
/* static */ std::vector<Button*> Button::instances;
Button::Button(int16_t x_, int16_t y_, int16_t w_, int16_t h_,
bool rot1_ /* = false */, const char* name_ /* = "" */,
ButtonColors off_ /*= {NODRAW, NODRAW, NODRAW} */,
ButtonColors on_ /* = {NODRAW, NODRAW, NODRAW} */,
uint8_t datum_ /* = BUTTON_DATUM */, int16_t dx_ /* = 0 */,
int16_t dy_ /* = 0 */, uint8_t r_ /* = 0xFF */
)
: Zone(x_, y_, w_, h_, rot1_) {
_pin = 0xFF;
_invert = false;
_dbTime = 0;
strncpy(_name, name_, 15);
off = off_;
on = on_;
datum = datum_;
dx = dx_;
dy = dy_;
r = r_;
init();
}
Button::Button(uint8_t pin_, uint8_t invert_, uint32_t dbTime_,
String hw_ /* = "hw" */, int16_t x_ /* = 0 */,
int16_t y_ /* = 0 */, int16_t w_ /* = 0 */, int16_t h_ /* = 0 */,
bool rot1_ /* = false */, const char* name_ /* = "" */,
ButtonColors off_ /*= {NODRAW, NODRAW, NODRAW} */,
ButtonColors on_ /* = {NODRAW, NODRAW, NODRAW} */,
uint8_t datum_ /* = BUTTON_DATUM */, int16_t dx_ /* = 0 */,
int16_t dy_ /* = 0 */, uint8_t r_ /* = 0xFF */
)
: Zone(x_, y_, w_, h_, rot1_) {
_pin = pin_;
_invert = invert_;
_dbTime = dbTime_;
strncpy(_name, name_, 15);
off = off_;
on = on_;
datum = datum_;
dx = dx_;
dy = dy_;
r = r_;
init();
}
Button::~Button() {
for (int i = 0; i < instances.size(); ++i) {
if (instances[i] == this) {
BUTTONS->delHandlers(nullptr, this, nullptr);
instances.erase(instances.begin() + i);
return;
}
}
}
Button::operator bool() { return _state; }
bool Button::operator==(const Button& b) { return (this == &b); }
bool Button::operator!=(const Button& b) { return (this != &b); }
bool Button::operator==(Button* b) { return (this == b); }
bool Button::operator!=(Button* b) { return (this != b); }
void Button::init() {
_state = _tapWait = _pressing = _manuallyRead = false;
_time = _lastChange = _pressTime = millis();
_hold_time = -1;
_textFont = _textSize = 0;
_freeFont = nullptr;
drawFn = nullptr;
_compat = 0;
drawZone = Zone();
tapTime = TAP_TIME;
dbltapTime = DBLTAP_TIME;
longPressTime = LONGPRESS_TIME;
repeatDelay = REPEAT_DELAY;
repeatInterval = REPEAT_INTERVAL;
strncpy(_label, _name, 16);
if (_pin != 0xFF) pinMode(_pin, INPUT_PULLUP);
instances.push_back(this);
draw();
}
int16_t Button::instanceIndex() {
for (int16_t i = 0; i < instances.size(); ++i) {
if (instances[i] == this) return i;
}
return -1;
}
bool Button::read(bool manualRead /* = true */) {
if (manualRead) _manuallyRead = true;
event = Event();
if (_changed) {
_changed = false;
_lastChange = _time;
if (!_state && !_cancelled && postReleaseEvents()) return _state;
} else {
if (!_cancelled && timeoutEvents()) return _state;
if (!_state) _cancelled = false;
}
// Do actual read from the pin if this is a hardware button
_time = millis();
uint8_t newState = false;
if (_pin != 0xFF) {
newState = (digitalRead(_pin));
newState = _invert ? !newState : newState;
if (newState != _state && _time - _lastChange >= _dbTime) {
if (newState) fingerDown();
if (!newState) fingerUp();
}
}
return _state;
}
void Button::fingerDown(Point pos /* = Point() */,
uint8_t finger /* = 0 */) {
_finger = finger;
_currentPt[finger] = _fromPt[finger] = pos;
if (!_state && !_currentPt[1 - finger]) {
// other finger not here
_state = true;
_changed = true;
_pressTime = _time;
_lastChange = _time;
draw();
}
BUTTONS->fireEvent(finger, E_TOUCH, pos, pos, 0, this, nullptr);
}
void Button::fingerUp(uint8_t finger /* = 0 */) {
uint32_t duration = _time - _pressTime;
_finger = finger;
_toPt[finger] = _currentPt[finger];
_currentPt[finger] = Point();
if (_state && !_currentPt[1 - finger]) {
// other finger not here
_state = false;
_changed = true;
_lastChange = _time;
draw();
}
BUTTONS->fireEvent(finger, E_RELEASE, _fromPt[finger], _toPt[finger],
duration, this, nullptr);
}
void Button::fingerMove(Point pos, uint8_t finger) {
BUTTONS->fireEvent(finger, E_MOVE, _currentPt[finger], pos,
_time - _lastChange, this, nullptr);
_currentPt[finger] = pos;
}
bool Button::postReleaseEvents() {
uint32_t duration = _time - _pressTime;
if (_toPt[_finger] && !contains(_toPt[_finger])) {
BUTTONS->fireEvent(_finger, E_DRAGGED, _fromPt[_finger], _toPt[_finger],
duration, this, nullptr);
_tapWait = false;
_pressing = false;
_longPressing = false;
return true;
}
if (duration <= tapTime) {
if (_tapWait) {
BUTTONS->fireEvent(_finger, E_DBLTAP, _fromPt[_finger], _toPt[_finger],
duration, this, nullptr);
_tapWait = false;
_pressing = false;
_longPressing = false;
return true;
}
_tapWait = true;
} else if (_pressing) {
BUTTONS->fireEvent(_finger, _longPressing ? E_LONGPRESSED : E_PRESSED,
_fromPt[_finger], _toPt[_finger], duration, this,
nullptr);
_pressing = false;
_longPressing = false;
return true;
}
return false;
}
bool Button::timeoutEvents() {
uint32_t duration = _time - _pressTime;
if (_tapWait && duration >= dbltapTime) {
BUTTONS->fireEvent(_finger, E_TAP, _fromPt[_finger], _toPt[_finger],
duration, this, nullptr);
_tapWait = false;
_pressing = false;
return true;
}
if (!_state) return false;
if ((!_pressing && duration > tapTime) ||
(repeatDelay && duration > repeatDelay &&
_time - _lastRepeat > repeatInterval)) {
BUTTONS->fireEvent(_finger, E_PRESSING, _fromPt[_finger],
_currentPt[_finger], duration, this, nullptr);
_lastRepeat = _time;
_pressing = true;
return true;
}
if (longPressTime && !_longPressing && duration > longPressTime) {
BUTTONS->fireEvent(_finger, E_LONGPRESSING, _fromPt[_finger],
_currentPt[_finger], duration, this, nullptr);
_longPressing = true;
return true;
}
return false;
}
void Button::cancel() {
_cancelled = true;
_tapWait = false;
draw(off);
}
char* Button::getName() { return _name; }
bool Button::isPressed() { return _state; }
bool Button::isReleased() { return !_state; }
bool Button::wasPressed() { return _state && _changed; }
bool Button::wasReleased() {
return (!_state && _changed && millis() - _pressTime < _hold_time);
}
bool Button::wasReleasefor(uint32_t ms) {
_hold_time = ms;
return (!_state && _changed && millis() - _pressTime >= ms);
}
bool Button::pressedFor(uint32_t ms) {
return (_state && _time - _lastChange >= ms) ? 1 : 0;
}
bool Button::pressedFor(uint32_t ms, uint32_t continuous_time) {
if (_state && _time - _lastChange >= ms &&
_time - _lastLongPress >= continuous_time) {
_lastLongPress = _time;
return true;
}
return false;
}
bool Button::releasedFor(uint32_t ms) {
return (!_state && _time - _lastChange >= ms);
}
uint32_t Button::lastChange() { return (_lastChange); }
void Button::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */) {
BUTTONS->addHandler(fn, eventMask, this, nullptr);
}
void Button::delHandlers(void (*fn)(Event&) /* = nullptr */) {
BUTTONS->delHandlers(fn, this, nullptr);
}
// visual things for Button
void Button::draw() {
if (_state)
draw(on);
else
draw(off);
}
void Button::erase(uint16_t color /* = BLACK */) {
draw({color, NODRAW, NODRAW});
}
void Button::draw(ButtonColors bc) {
_hidden = false;
// use locally set draw function if aplicable, global one otherwise
if (drawFn) {
drawFn(*this, bc);
} else if (BUTTONS->drawFn) {
BUTTONS->drawFn(*this, bc);
}
}
void Button::hide(uint16_t color /* = NODRAW */) {
_hidden = true;
if (color != NODRAW) erase(color);
}
char* Button::label() { return _label; }
void Button::setLabel(const char* label_) { strncpy(_label, label_, 50); }
void Button::setFont(const GFXfont* freeFont_) {
_freeFont = freeFont_;
_textFont = 1;
}
void Button::setFont(uint8_t textFont_ /* = 0 */) {
_freeFont = nullptr;
_textFont = textFont_;
}
void Button::setTextSize(uint8_t textSize_ /* = 0 */) { _textSize = textSize_; }
// M5Buttons class
/* static */ M5Buttons* M5Buttons::instance;
/* static */ void M5Buttons::drawFunction(Button& b, ButtonColors bc) {
if (bc.bg == NODRAW && bc.outline == NODRAW && bc.text == NODRAW) return;
Zone z = (b.drawZone) ? b.drawZone : b;
if (z.rot1) z.rotate(TFT->rotation);
uint8_t r = (b.r == 0xFF) ? min(z.w, z.h) / 4 : b.r;
if (bc.bg != NODRAW) {
if (r >= 2) {
TFT->fillRoundRect(z.x, z.y, z.w, z.h, r, bc.bg);
} else {
TFT->fillRect(z.x, z.y, z.w, z.h, bc.bg);
}
}
if (bc.outline != NODRAW) {
if (r >= 2) {
TFT->drawRoundRect(z.x, z.y, z.w, z.h, r, bc.outline);
} else {
TFT->drawRect(z.x, z.y, z.w, z.h, bc.outline);
}
}
if (bc.text != NODRAW && bc.text != bc.bg && strlen(b._label)) {
// figure out where to put the text
uint16_t tx, ty;
tx = z.x + (z.w / 2);
ty = z.y + (z.h / 2);
if (!b._compat) {
uint8_t margin = max(r / 2, 6);
switch (b.datum) {
case TL_DATUM:
case ML_DATUM:
case BL_DATUM:
tx = z.x + margin;
break;
case TR_DATUM:
case MR_DATUM:
case BR_DATUM:
tx = z.x + z.w - margin;
break;
}
switch (b.datum) {
case TL_DATUM:
case TC_DATUM:
case TR_DATUM:
ty = z.y + margin;
break;
case BL_DATUM:
case BC_DATUM:
case BR_DATUM:
ty = z.y + z.h - margin;
break;
}
}
// Save state
uint8_t tempdatum = TFT->getTextDatum();
uint16_t tempPadding = TFT->padX;
if (!b._compat) TFT->pushState();
// Actual drawing of text
TFT->setTextColor(bc.text);
if (b._textSize)
TFT->setTextSize(b._textSize);
else
TFT->setTextSize(BUTTONS->_textSize);
if (b._textFont) {
if (b._freeFont)
TFT->setFreeFont(b._freeFont);
else
TFT->setTextFont(b._textFont);
} else {
if (BUTTONS->_freeFont)
TFT->setFreeFont(BUTTONS->_freeFont);
else
TFT->setTextFont(BUTTONS->_textFont);
}
TFT->setTextDatum(b.datum);
TFT->setTextPadding(0);
TFT->drawString(b._label, tx + b.dx, ty + b.dy);
// Set state back
if (!b._compat) {
TFT->popState();
} else {
TFT->setTextDatum(tempdatum);
TFT->setTextPadding(tempPadding);
}
}
}
M5Buttons::M5Buttons() {
if (!instance) instance = this;
drawFn = drawFunction;
_freeFont = BUTTON_FREEFONT;
_textFont = BUTTON_TEXTFONT;
_textSize = BUTTON_TEXTSIZE;
}
Button* M5Buttons::which(Point& p) {
if (!Button::instances.size()) return nullptr;
for (int i = Button::instances.size() - 1; i >= 0; --i) {
Button* b = Button::instances[i];
// Always return button when i == 0 --> background
if (!i || (b->_pin == 0xFF && !b->_hidden && b->contains(p))) return b;
}
return nullptr;
}
void M5Buttons::draw() {
for (auto button : Button::instances) button->draw();
}
void M5Buttons::update() {
#ifdef _M5TOUCH_H_
for (auto gesture : Gesture::instances) gesture->_detected = false;
BUTTONS->event = Event();
if (TOUCH->wasRead || _leftovers) {
_finger[TOUCH->point0finger].current = TOUCH->point[0];
_finger[1 - TOUCH->point0finger].current = TOUCH->point[1];
_leftovers = true;
for (uint8_t i = 0; i < 2; i++) {
if (i == 1) _leftovers = false;
Finger& fi = _finger[i];
Point& curr = fi.current;
Point prev = fi.previous;
fi.previous = fi.current;
if (curr == prev) continue;
if (!prev && curr) {
// A new touch happened
fi.startTime = millis();
fi.startPoint = curr;
fi.button = BUTTONS->which(curr);
if (fi.button) {
fi.button->fingerDown(curr, i);
return;
}
} else if (prev && !curr) {
// Finger removed
uint16_t duration = millis() - fi.startTime;
for (auto gesture : Gesture::instances) {
if (gesture->test(fi.startPoint, prev, duration)) {
BUTTONS->fireEvent(i, E_GESTURE, fi.startPoint, prev, duration,
nullptr, gesture);
if (fi.button) fi.button->cancel();
break;
}
}
if (fi.button) {
fi.button->fingerUp(i);
return;
}
} else {
// Finger moved
if (fi.button) {
fi.button->fingerMove(curr, i);
return;
}
}
}
}
#endif /* _M5TOUCH_H_ */
for (auto button : Button::instances) {
if (!button->_manuallyRead) button->read(false);
}
}
void M5Buttons::setFont(const GFXfont* freeFont_) {
_freeFont = freeFont_;
_textFont = 1;
}
void M5Buttons::setFont(uint8_t textFont_) {
_freeFont = nullptr;
_textFont = textFont_;
}
void M5Buttons::setTextSize(uint8_t textSize_) { _textSize = textSize_; }
void M5Buttons::fireEvent(uint8_t finger, uint16_t type, Point& from,
Point& to, uint16_t duration, Button* button,
Gesture* gesture) {
Event e;
e.finger = finger;
e.type = type;
e.from = from;
e.to = to;
e.duration = duration;
e.button = button;
e.gesture = gesture;
if (button) button->event = e;
event = e;
for (auto h : _eventHandlers) {
if (!(h.eventMask & e.type)) continue;
if (h.button && h.button != e.button) continue;
if (h.gesture && h.gesture != e.gesture) continue;
h.fn(e);
}
}
void M5Buttons::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */,
Button* button /* = nullptr */,
Gesture* gesture /* = nullptr */
) {
EventHandler handler;
handler.fn = fn;
handler.eventMask = eventMask;
handler.button = button;
handler.gesture = gesture;
_eventHandlers.push_back(handler);
}
void M5Buttons::delHandlers(void (*fn)(Event&) /* = nullptr */,
Button* button /* = nullptr */,
Gesture* gesture /* = nullptr */
) {
for (int i = _eventHandlers.size() - 1; i >= 0; --i) {
if (fn && fn != _eventHandlers[i].fn) continue;
if (button && _eventHandlers[i].button != button) continue;
if (gesture && _eventHandlers[i].gesture != gesture) continue;
_eventHandlers.erase(_eventHandlers.begin() + i);
}
}
// Gesture class
std::vector<Gesture*> Gesture::instances;
Gesture::Gesture(Zone fromZone_, Zone toZone_, const char* name_ /* = "" */,
uint16_t minDistance_ /* = GESTURE_MINDIST */,
int16_t direction_ /* = INVALID_VALUE */,
uint8_t plusminus_ /* = PLUSMINUS */, bool rot1_ /* = false */,
uint16_t maxTime_ /* = GESTURE_MAXTIME */
) {
fromZone = fromZone_;
toZone = toZone_;
strncpy(_name, name_, 15);
minDistance = minDistance_;
direction = direction_;
plusminus = plusminus_;
rot1 = rot1_;
maxTime = maxTime_;
_detected = false;
instances.push_back(this);
}
Gesture::Gesture(const char* name_ /* = "" */,
uint16_t minDistance_ /* = GESTURE_MINDIST */,
int16_t direction_ /* = INVALID_VALUE */,
uint8_t plusminus_ /* = PLUSMINUS */, bool rot1_ /* = false */,
uint16_t maxTime_ /* = GESTURE_MAXTIME */
) {
fromZone = ANYWHERE;
toZone = ANYWHERE;
strncpy(_name, name_, 15);
minDistance = minDistance_;
direction = direction_;
plusminus = plusminus_;
rot1 = rot1_;
maxTime = maxTime_;
_detected = false;
instances.push_back(this);
}
Gesture::~Gesture() {
for (int i = 0; i < instances.size(); ++i) {
if (instances[i] == this) {
instances.erase(instances.begin() + i);
BUTTONS->delHandlers(nullptr, nullptr, this);
return;
}
}
}
Gesture::operator bool() { return _detected; }
int16_t Gesture::instanceIndex() {
for (int16_t i = 0; i < instances.size(); ++i) {
if (instances[i] == this) return i;
}
return -1;
}
char* Gesture::getName() { return _name; }
bool Gesture::test(Point& from, Point& to, uint16_t duration) {
if (from.distanceTo(to) < minDistance) return false;
if (fromZone && !fromZone.contains(from)) return false;
if (toZone && !toZone.contains(to)) return false;
if (direction != INVALID_VALUE &&
!from.isDirectionTo(to, direction, plusminus, rot1))
return false;
if (duration > maxTime) return false;
_detected = true;
return true;
}
bool Gesture::wasDetected() { return _detected; }
void Gesture::addHandler(void (*fn)(Event&), uint16_t eventMask /* = E_ALL */) {
BUTTONS->addHandler(fn, eventMask, nullptr, this);
}
void Gesture::delHandlers(void (*fn)(Event&) /* = nullptr */) {
BUTTONS->delHandlers(fn, nullptr, this);
}
// Event class
Event::Event() {
finger = type = duration = 0;
from = to = Point();
button = nullptr;
gesture = nullptr;
}
Event::operator uint16_t() { return type; }
const char* Event::typeName() {
const char* unknown = "E_UNKNOWN";
const char* none = "E_NONE";
const char* eventNames[NUM_EVENTS] = {
"E_TOUCH", "E_RELEASE", "E_MOVE", "E_GESTURE",
"E_TAP", "E_DBLTAP", "E_DRAGGED", "E_PRESSED",
"E_PRESSING", "E_LONGPRESSED", "E_LONGPRESSING"};
if (!type) return none;
for (uint8_t i = 0; i < NUM_EVENTS; i++) {
if ((type >> i) & 1) return eventNames[i];
}
return unknown;
}
const char* Event::objName() {
const char* empty = "";
if (gesture) return gesture->getName();
if (button) return button->getName();
return empty;
};
uint16_t Event::direction(bool rot1 /* = false */) {
return from.directionTo(to, rot1);
}
bool Event::isDirection(int16_t wanted, uint8_t plusminus /* = PLUSMINUS */,
bool rot1 /* = false */) {
return from.isDirectionTo(to, wanted, plusminus, rot1);
}
uint16_t Event::distance() { return from.distanceTo(to); }
// TFT_eSPI_Button compatibility mode
TFT_eSPI_Button::TFT_eSPI_Button() : Button(0, 0, 0, 0) { _compat = true; }
void TFT_eSPI_Button::initButton(TFT_eSPI* gfx, int16_t x, int16_t y,
uint16_t w, uint16_t h, uint16_t outline,
uint16_t fill, uint16_t textcolor,
char* label_, uint8_t textsize) {
initButtonUL(gfx, x - (w / 2), y - (h / 2), w, h, outline, fill, textcolor,
label_, textsize);
}
void TFT_eSPI_Button::initButtonUL(TFT_eSPI* gfx, int16_t x_, int16_t y_,
uint16_t w_, uint16_t h_, uint16_t outline,
uint16_t fill, uint16_t textcolor,
char* label_, uint8_t textsize_) {
x = x_;
y = y_;
w = w_;
h = h_;
off = {fill, textcolor, outline};
on = {textcolor, fill, outline};
setTextSize(textsize_);
strncpy(_label, label_, 9);
}
void TFT_eSPI_Button::setLabelDatum(int16_t dx_, int16_t dy_,
uint8_t datum_ /* = MC_DATUM */) {
dx = dx_;
dy = dy_;
datum = datum_;
}
void TFT_eSPI_Button::drawButton(bool inverted /* = false */,
String long_name /* = "" */) {
char oldLabel[51];
if (long_name != "") {
strncpy(oldLabel, _label, 50);
strncpy(_label, long_name.c_str(), 50);
}
draw(inverted ? on : off);
if (long_name != "") strncpy(_label, oldLabel, 50);
}
bool TFT_eSPI_Button::contains(int16_t x, int16_t y) {
return Button::contains(x, y);
}
void TFT_eSPI_Button::press(bool p) {
if (p)
fingerDown();
else
fingerUp();
}
bool TFT_eSPI_Button::justPressed() { return wasPressed(); }
bool TFT_eSPI_Button::justReleased() { return wasReleased(); }

View File

@@ -0,0 +1,982 @@
/*
== M5Button: Buttons, Gestures and Events ==
* Hardware button support that is 100% Arduino Button Library compatible.
* Buttons on the screen, either as labels above the original M5Stack's
hardware buttons or anywhere on the touch screen of the Core2.
* Zone and Point objects to work with screen locations and areas. Functions
for distance, direction and more.
* Touch gestures that are processed before the buttons, so you can still
use gestures when the screen is full of buttons.
* Buttons and gestures send events that you can attach handler routines to,
or poll in a loop. Events include tap, doubletap, pressed, dragged and
more. Support for key repeat.
* Extensive screen rotation support, including support for buttons and
gestures that stay referenced to the physical screen regardless of rotation.
* Intuitive, consistent and well-documented API.
* Emulation of the (much less feature-rich) TFT_eSPI_Button class. This
goes together well with M5Touch's emulation of the TFT_eSPI resistive
touch screen interface to run a lot of existing programs without
modification.
This library was written for the M5Stack series of devices, but was made to
be general enough to be produce pretty visual buttons with any TFT_eSPI
display. Its more advanced features need the M5Touch interface, although
other input methods could be implemented.
== Point and Zone: Describing Points and Areas on the Screen ==
The Point and Zone classes allow you to create variables that hold a point
or an area on the screen.
Point(x, y)
Holds a point on the screen. Has members x and y that hold the
coordinates of a touch. Values -1 for x and y indicate an invalid value,
and that's what a point starts out with if you declare it without
parameters. The 'valid()' method tests if a point is valid. If you
explicitly evaluate a Point as a boolean ("if (p) ..."), you also get
whether the point is valid, so this is equivalent to writing "if
(p.valid()) ...".
Zone(x, y, w, h)
Holds a rectangular area on the screen. Members x, y, w and h are for the
x and y coordinate of the top-left corner and the width and height of the
rectangle.
The 'set' method allows you to change the properties of an existing Point
or Zone. Using the 'in' or 'contains' method you can test if a point lies
in a zone.
The PointAndZone library also provides the low-level support for direction
from one point to another and for screen rotation translations.
The documentation in src/utility/PointAndZone.h provides more details about
rotation and examples covering most of the above.
== Buttons ==
You can create classic Arduino buttons that act on the voltage on a pin of
the controller. On the M5Stack Core2, you can also create buttons that act
on touch within a given rectangle on the screen. If you want, that same
rectangle will also be used for a graphical representation of the button
that will show a text label in a colored background with a colored outline.
The colors of background, text, and outline can be configured, both for the
'off' and the 'on' state of the button.
Whether on the M5Stack with hardware buttons or on the Core2 with a touch
screen, buttons are special forms of the 'Zone' object, meaning that all
functions that apply to 'Zone' objects also work on buttons. On the M5Stack
with buttons, while this zone cannot be used for touch input, it can still
be used to display a button that responds to the button state.
== Hardware Buttons ==
For hardware buttons, use the classic Arduino way of setting up the button,
by providing the pin, whether the pin is inverted and the debounce time.
#include <M5Stack.h>
Button myButton(39, true, 10);
void setup() {
M5.begin();
}
void loop() {
M5.update();
if (myButton.wasPressed()) Serial.print("* ");
}
This would set up 'myButton' with the inverse state of pin 39, and a
debounce time of 10 milliseconds. Because pin 39 is the left button on an
M5Stack with buttons, this sketch will output a star to the serial port
every time you release the button. (And because pin 39 is the interrupt
wire for the touch screen on the Core2, it also happens to output a star on
that device every time you touch the screen.)
Note that the sketch uses 'M5.update()' instead of 'myButton.read()'. You
don't need to read() your buttons explicitly anymore. All buttons created
with M5Button are automatically read by 'M5.update()'. (Unless you read
them with 'myButton.read()', in which case 'M5.update()' stops doing that
to avoid you missing things.)
The next sections will describe buttons and gestures on the touch screen,
but if you have an M5Stack device without a touch screen: keep reading
because many events work on hardware buttons too. Hardware buttons can have
responsive representation on the screen, we'll get to that also.
== Buttons Using the Touch Screen ==
Note: It may make sense to also read the documentation in the M5Touch.h
file, as tells you about the touch sensor and the lower-level touch
interface that is underneath the M5Button library.
To have a button that reacts to the touch sensor, all you need to do is
create a variable for the Button and provide the coordinates (x, y, width
and height). These buttons can be used in two ways. You can either use them
the way you would a normal Arduino button, or you can provide handler
functions to process various events for the button. We'll talk about the
events later, but here's the same simple sketch from above again but now it
defines a 100x50 pixel touch button near the top-right of the screen. Note
that this button does not show anything on the sreen just yet.
#include <M5Core2.h>
Button myButton(10, 10, 200, 100);
void setup() {
M5.begin();
}
void loop() {
M5.update();
if (myButton.wasPressed()) Serial.print("* ");
}
'wasPressed()' will only be true once when you press the button. You can
also use the other Arduino button functions such as 'isPressed()' that is
true as soon and as long as the button is touched. Note that the buttons
only become pressed if the touch starts within the button, not if you swipe
over it, and that they will stay pressed as long as the finger touches,
even if it leaves the button area. You may want read about the events
further down to distinguish between different kinds of button-presses.
On the Core2 the three buttons M5.BtnA, M5.BtnB and M5.BtnC from the older
M5Stack units come already implemented as touch buttons that lie just below
the screen where the three circles are.
== Buttons with visual appearance ==
If you want you button to show on the screen, all you need to do is provide
a set of three colors for the background of the button, the text printed on
it and the outline of the button. Using yet the same skech again:
#include <M5Core2.h>
Button myButton(10, 10, 200, 100, false, "I'm a button !",
{BLACK, WHITE, WHITE});
void setup() {
M5.begin();
}
void loop() {
M5.update();
if (myButton.wasPressed()) Serial.print("* ");
}
As you can see the colors are provided in {curly braces}, that's because
they are one variable, of the 'ButtonColors' type. Especialy if you're
going to define a bunch of buttons, you're better off replacing the button
line by:
ButtonColors col = {BLACK, WHITE, WHITE};
Button myButton(10, 10, 200, 100, false, "I'm a button !", col);
The order there is background, text, outline. If you do not want any of
these components drawn, simply put NODRAW in that position. The thing we are
defining here is what the button draws in its 'off' state. Since we haven
specified anything to draw in the 'on' state, the button just stays like it
is, regardless of whether it's pressed. Thus, if we say
ButtonColors onCol = {BLACK, WHITE, WHITE};
ButtonColors offCol = {RED, WHITE, WHITE};
Button myButton(10, 10, 200, 100, false, "I'm a button !", onCol, offCol);
the button background would turn red if the button was pressed. The button
colors can also be addressed directly. "myButton.on.bg = BLUE;" will turn
the background blue in the on state. The other two properties of the
ButtonColors variable are predicatably called 'text' and 'outline'.
If you run the sketches you will see the text is placed in the center of
the button and the buttons have visually please round edges. The corner
radius defaults to a quarter of the shortest side of the button. You can
change all this with the remaining parameters when setting up the button:
Button myButton(10, 10, 200, 100, false, "I'm a button !", onCol, offCol,
TL_DATUM, 0, 10, 0);
These last parameters indicate where to put the label in the TFT_eSPI
standard datum values, top-left in this case. The values after that are the
dx and dy, meaning the offsets from the default position. In this case
that's no left-right offset and 10 pixels down. Negative values move the
other way. The last value is the corner radius. In this case it would draw
an ugly 1980's rectangular button.
You can make a button draw its current state with "myButton.draw()", or all
buttons do that with "M5.Buttons.draw()". You can also call draw with a
ButtonColors variable so "myButton.draw({BLUE, WHITE, WHITE})" draws it
with those colors. Until the next state-change comes along that is, if you
have colors for the new state defined.
Note that the text provided here is the name of the buttton. A button
always keeps the same name, but the label (that which is shown) can change,
but initialises to the name. Use 'myButton.setLabel("New text!")' to change
it.
With "myButton.hide()" you can make a button temporarily invisible to the
touch sensor. You can specify an optional color value to draw over the
button if you want to make it visually disappear also. myButton.draw() makes
it visible to the touch sensor again, even if you have no colors defined, so
nothing shows on the screen. "MyButton.erase()" only paints over the button,
in a color you can specify (default black).
== Visual Buttons (Labels) with Hardware Buttons ==
You can have a visual representation of the state of a hardware button on
the screen, for example right above the hardware buttons of the original
M5Stack. We'll call these buttons "labels", but they're regular buttons
that just respond to a physical button insetad of the touch sensor. If you
want to display a label on the screen that responds to the state of a
hardware button, just set up a hardware button up as usual, but then follow
the parameter list with "hw" (in quotes), followed by the parameters of the
touch button below.
The hardware buttons in the older M5Stack devices are already set up to
display labels: all you need is supply colors. Their initialization (in
M5Stack.h in this library) looks like this:
Button BtnA = Button(BUTTON_A_PIN, true, DEBOUNCE_MS,
"hw", 3, 218, 102, 21, true, "BtnA");
Button BtnB = Button(BUTTON_B_PIN, true, DEBOUNCE_MS,
"hw", 109, 218, 102, 21, true, "BtnB");
Button BtnC = Button(BUTTON_C_PIN, true, DEBOUNCE_MS,
"hw", 215, 218, 102, 21, true, "BtnC");
As you can see: its just a hardware button that has a zone to display the
label. So the sketch below is all that is needed to show repsonsive labels
on the M5Stack:
#include <M5Stack.h>
void setup() {
M5.BtnA.off = M5.BtnB.off = M5.BtnC.off = {BLUE, WHITE, NODRAW};
M5.BtnA.on = M5.BtnB.on = M5.BtnC.on = {RED, WHITE, NODRAW};
M5.begin();
M5.Buttons.draw();
}
void loop() {
M5.update();
}
If you looked closely you might have noticed that the mysterious fifth
argument has changed from 'false' to 'true'. This argument is called
'rot1', and it determines that the location of this Zone or Button is
specified in rotation one, i.e. the normal default screen rotation. What
that means is that no matter what rotation you set the display to, these
button will always stay in the same place. The documentation in
src/utility/PointAndZone.h has more details if you want to know more about
this. You will only ever need rot1 if you need multiple screen rotations
AND you want objects to stay in the same physical place regardless.
== M5.Buttons ==
Apart from the class "Button" that you use to create buttons of your own,
there is an instance called "M5.Buttons" (plural), that is used to talk to
the M5Button library for things that involve all buttons. For instance:
"M5.Buttons.setFont" sets a font for all buttons, and you can use
"M5.Buttons.addHandler" to add a handler that gets events for all buttons
(and gestures).
== Events ==
Buttons (and gestures, but we'll get to those later) have a set of simple
functions to see if they are pressed or not. These Arduino-compatible
functions work fine for that purpose. But if you want to detect whether a
button received a short tap, or even a double-tap on many buttons
simultaneously, you find yourself writing quite a bit of code for that.
Events are M5Button's way of making it easier to react to events on
hardware buttons or the touch screen. For this you need to define one or
more event handler functions. This is done like this:
void myHandler(Event& e) { ... }
It's important to do it exactly this way, only changing the name of the
function. You can then set things up so that this function receives events.
Here's an events-based sketch for the Core2. We'll base it on the same
buton we've seen before.
#include <M5Core2.h>
ButtonColors onCol = {BLACK, WHITE, WHITE};
ButtonColors offCol = {RED, WHITE, WHITE};
Button myButton(10, 10, 200, 100, false, "I'm a button !", onCol, offCol);
void setup() {
M5.begin();
myButton.addHandler(touched, E_TOUCH);
myButton.addHandler(released, E_RELEASE);
}
void loop() {
M5.update();
}
void touched(Event& e) {
Serial.println("Touched!");
}
void released(Event& e) {
Serial.println("Released!");
}
Note that the function names "touched" and "released" are provided to
addHandler without the parenthesis. Here's two ways you can set up a handler
function to receive events:
M5.Buttons.addHandler(myHandler);
- or -
myButton.addHandler(myHandler);
The first form receives all the events relating to all buttons and gestures,
the second form only receives the events for that specific button. After the
name of the function, without the brackets, you can specify which events the
function needs to receive. You can add together (or "bitwise or") the names
of the events if you want a handler function to reive multiple events.
The Event object that is passed to the handler function contains all sorts
of information about the event: where on the screen it started, where it
ended, the duration, etc. etc.
Let's first look at all the possible events and when they are fired. The
first three events always happen when a finger touches the display.
E_TOUCH, E_MOVE and E_RELEASE
The E_TOUCH and E_RELEASE events fire when a button is pressed and
released. On a touch sensor, E_MOVE will fire every time it detects the
finger has moved. These events cannot be prevented from firing, like most
of the other ones. So every time your finger touches the display it will
fire E_TOUCH and then E_MOVEs until finally, when you release your
finger, an E_RELEASE.
E_PRESSING and E_LONGPRESSING
There are also events that happen while the button is still pressed.
These are E_PRESSING and E_LONGPRESSING. E_PRESSING happens as soon as
M5Button is sure that's not just a short tap (more later). The maximum
time for a tap is settable, but defaults to 150 ms. So if the button is
still held 150 ms after E_TOUCH, E_PRESSING fires. Just once, unless you
have set up a key repeat, more about that later too. Then at some point
you might get a E_LONGPRESSING, if you have set up a duration for that to
happen.
E_TAP, E_DBLTAP, E_PRESSED, E_LONGPRESSED and E_DRAGGED
Unless the keypress is cancelled (more later), exactly one of these events
will fire after the button has been released, after E_RELEASE has fired.
Think of these as final decisions on what kind of keypress this was.
(E_TAP takes a tiny bit longer before it fires because M5Button needs to
make sure it wasn't a doubletap, in that case E_DBLTAP wil fire instead.)
So tap and doubletap are sort of obvious, E_LONGPRESSED fires if the key
was pressed more that the set time in ms. E_DRAGGED fires if the finger
has moved outside of the button area when it was released. E_PRESSED is
fires in all other cases.
E_GESTURE
Doesn't really fit in with the others, but is the event that gets fired
when a gesture is detected.
If at any point after the pressing of a button, "myButton.cancel()" is
called, no further high-level events for that button will fire. What that
means is nothing other than possible E_MOVEs and one E_RELEASE event will
fire for that button until it is released and then pressed again. This is
used internally when a gesture is detected, so that when a touch gesture
starts on a button, there won't be an E_PRESSED, or any of the others.
The second thing to look at more closely is the 'Event' object itself. When
you set up a handler function like this
void myhandler(Event& e) {
what that means is you're creating a function that recives a (reference to)
an event. That event has all sorts of properties that we can look at.
e.type
The type of event, such as E_TOUCH or E_TAP from above. The event
itself, when you evaluate it, also returns the type. What that means is
that "if (e.type == E_TAP) .." is equivalent with "if (e == E_TAP) .."
e.finger
0 or 1, whether this is the first or second finger detected on the
touch screen. Left at zero on the M5Stack with buttons.
e.from and e.to
Points that say from where to where this event happened. Left at
invalid for the M5Stack with buttons.
e.duration
Duration of the event in milliseconds.
e.button
Pointer to the button attached to the event. What that means is that you
can use all the methods for button as long as you precede them with
"e.button->". Note the '->' there because this is a pointer to an
object.
e.gesture
e.gesture is a pointer to the gesture attached to the event, and may be
null if the event is not a gesture. So unless you know for sure this
event is a gesture (because handler attached to that gesture or because
you asked for E_GESTURE events only), this pointer needs to be tested
using "if (e.gesture)" before using -> methods on it, oterwise your
program will crash.
other methods
Additionally, you can ask for the name of the event as text by using
"e.typeName()" and get the name of the gesture or button with
"e.objName()". "e.direction()" gives the direction, for instance of a
gesture or of an E_RELEASE event, where it gives direction between
E_TOUCH and E_RELEASE. "e.isDirectionTo(0,30)" will output true if the
swipe was upwards, plus or minus 30 degrees.
When you add a handler function you can also specify what events it should
receive by supplying it as the second argument after the handler function.
If you want to register multiple events for the same function, don't
register the handler twice, but simply add (or bitwise or) the event
values. The default value there is the pseudo-event E_ALL, which is simply
a value with all the event bits turned on. You can also subtract event type
values from E_ALL to exclude them.
Here are some examples of ways to add a handler function:
button1.addHandler(b1Function, E_TOUCH + E_RELEASE);
b1Function only get these two events for button1.
M5.Buttons.addHandler(btnHandle, E_ALL - E_MOVE);
btnHandle gets all events, except E_MOVE.
swipeUp.addHandler(nextPage);
Handler nextPage is called when swipeUp gesture detected.
Note that all handler functions must be of the "void someName(Event& e)"
type, even if they plan to completely ignore the event that is passed to
them.
If your event reads data or calls functions in e.button or e.gesture,
remember that these are pointers. Without going into too much detail, it
means it must do so with the -> notation, so to read the button x position,
you would say "e.button->x".
Please have a look at the example sketch (see below) to understand how this
all works and run the sketch to see all the events printed to the serial
port.
== Taps, Doubletaps, Longpresses and Key Repeat ==
Some features are best explained with some examples:
myButton.tapTime = 0;
Turns off detection of taps and doubletaps, the button will fire
E_PRESSING immediately when pressed. Any other value makes that the
maximum time a tap can take in milliseconds, and thus the wait tme
before "E_PRESSING" fires.
mybutton.tapWait = 0;
Turns off detection of doubletaps only. Any other value makes that the
wait before an E_TAP fires, because M5Button is still waiting to see if
it's maybe a doubletap.
mybutton.longPressTime = 700;
Sets up the button to fire an E_LONGPRESSING after 700 ms, and then fire
E_LONGPRESSED instead of E_PRESSED when the button is released. By
default this is set to zero, meaning longpress detection is off.
myButton.repeatDelay = 500;
myButton.repeatInterval = 250;
Makes the button repeat the sending of its E_PRESSING event every 250
milliseconds if key is held for 500 ms.
== In Loop vs. Event Handlers ==
Button and Gesture objects have an 'event' method that returns the event
that was detected this time around by 'M5.update()'. Each event comes in
it's own rotation of 'M5.update()', so if you prefer to detect events this
way and not with handler routines that's fine too.
If nothing was detected, the event type will be set to E_NONE with a value
of 0, so you can do "if (myButton.event) ...". 'M5.Buttons.event' has the
event detected this time around, regardless of what button or gesture it was
attached to. This example prints a star to serial if it is doubletapped.
#include <M5Core2.h>
Button myButton(50,70,220, 100, false, "Button",
{YELLOW, BLACK, NODRAW},
{RED, BLACK, NODRAW} );
void setup() {
M5.begin();
M5.Buttons.setFont(FSS18);
M5.Buttons.draw();
}
void loop() {
M5.update();
if (myButton.event == E_DBLTAP) Serial.print("* ");
}
== M5.background ==
Only one button can become pressed for any spot on the touch screen. If you
define overlapping buttons, the first defined button for the overlap become
pressed and gets all subsequent events.
One special button, "M5.background", was defined before any others, and it
has the size of the entire touch sensor. This means it gets all events
where the first touch was not within any of the defined buttons.
== Gestures on the Touch Screen ==
Whenever a finger is released from the touch screen and before any
higher-level button events are fired, the library first checks whether this
was perhaps a gesture. When you define gestures, you can optionally specify
the zone in which the gesture must start, the zone in which it must end, the
minimum distance the finger must have travelled, the direction it has
travelled in and the maximum time the gesture may take.
Gesture exampleGesture(fromZone, toZone, "exampleName", minimumDistance,
direction, plusminus, ro1, maxTime)
Where fromZone and toZone can be valid zones or the word "ANYWHERE". If you
want to specify neither fromZone nor toZone, you can also leave them off
completely. The minimum distance defaults to 75 pixels. The direction
(default: don't care) is in compass degrees (so 180 is down), but the
compiler defines DIR_UP, DIR_DOWN, DIR_LEFT and DIR_RIGHT are provided for
convenience. The plusminus deines how many degress off-course the gesture
may be, and the rot1 flag defines whether this direction is relative to the
current rotation, or as seen in rotation 1. maxTime is in milliseconds as
usual and defaults to 500 ms. DIR_ANY can be used for direction if you need
to specify it in order provide a rot1 or maximum time value.
here are a few examples of valid gesture definitions:
Gesture swipeDown("swipe down", 100, DIR_DOWN, 30);
Down (plus or minus 30 degrees) for at least 100 pixels within 500 ms
Gesture fromTop(Zone(0, 0, 360, 30), ANYWHERE, "from top", 100, DIR_DOWN, 30);
The same but starting from within the top 30 pixels. (If you make that
too narrow you may miss the swipe because the sensor 'sees' only once
every 13 ms or so.
(Note that if you defined both these gestures in this order the second one
would never fire because any swipe that matched number two would first match
number one and fire that one instead.)
Gestures have a 'wasDetected()' method if you want to detect them in the
main loop, or you attach a handler the same way you would for a button,
with "myGesture.addhandler(myHandler)"
#include <M5Core2.h>
Gesture swipeDown("swipe down", DIR_DOWN, 30);
void setup() {
M5.begin();
}
void loop() {
M5.update();
if (swipeDown.wasDetected()) Serial.println("Swiped down!");
}
== Advanced Hints and Tricks
## drawFn
If you look at the source code for the library you will see that the
drawing of the button is done by a static function in the M5Buttons
object. It's defined as
void M5Buttons::drawFunction(Button& b, ButtonColors bc)
If you make your own function that takes the same arguments but that does
something different, you can make the library use it by saying
"M5.Buttons.drawFn = myFunction". You can even do that on a per-button
basis with "myButton.drawFn = myFunction".
## drawZone
A Button instance _is_ also a Zone object, in that it descends from it.
Which means a Button has all the methods of a Zone object, as well as its
own. But it contains another zone, called drawZone. This allows you to
have the visual representation happen somewhere else than where the
button is on the touch sensor. Normally this is set to "invalid zone",
but if you set it to a valid screen area, the button will be drawn there.
This is used internally to put the optional labels for the off-screen
buttons on the Core2 on the screen just above their touch areas.
## Drawing is Non-Invasive
This library uses a brand-new feature of the M5Display object --
M5.Lcd.popState() and M5.lcd.pushState() -- that allows it to save and
reload the complete display driver state before and after drawing a
button. What that means is that you can draw to the display without
worrying that the button drawing will mess with your font setting, cursor
position or anything else that is display-related.
## TFT_ePI_Button Emulation
This libary also defines an object called TFT_eSPI_Button, which is the
old way of doing buttons that comes as an optional extra with the display
library. Together with M5Touch's emulation of the TFT_eSPI touch
interface (written for the older resistive touch-screens), you can use it
to run software made for those APIs. Do not use either for new code: the
native interfaces are much more powerful.
## Buttons and Variable Scope
Buttons come into existence and are drawn in their initial state when
their variables are defined and are not detected anymore when their
variables are removed from memory when the function they were defined in
returns. Except for global buttons - defined outside any functions: their
variables always exist. The programmer has to take responsability for
erasing expired buttons off the screen because Button doesnt know what is
supposed to be in the background. If you're not clearing the entire
screen anyway, this can be done with "myButton.erase(BLACK)" if the
background is to be black.
*/
#ifndef _M5BUTTON_H_
#define _M5BUTTON_H_
class Gesture;
#include <Arduino.h>
#include <Free_Fonts.h>
#include <M5Display.h>
#include "PointAndZone.h"
#include "utility/Config.h"
#ifdef M5Stack_M5Core2
#include <M5Touch.h>
#endif /* M5Stack_M5Core2 */
#define BUTTON_FREEFONT FSS9
#define BUTTON_TEXTFONT 1
#define BUTTON_TEXTSIZE 1
#define BUTTON_DATUM MC_DATUM
#define TAP_TIME 150
#define DBLTAP_TIME 300
#define LONGPRESS_TIME 0
#define REPEAT_DELAY 0
#define REPEAT_INTERVAL 200
#define GESTURE_MAXTIME 500
#define GESTURE_MINDIST 75
#define ANYWHERE Zone()
#define NUM_EVENTS 11
#define E_TOUCH 0x0001
#define E_RELEASE 0x0002
#define E_MOVE 0x0004
#define E_GESTURE 0x0008
#define E_TAP 0x0010
#define E_DBLTAP 0x0020
#define E_DRAGGED 0x0040
#define E_PRESSED 0x0080
#define E_PRESSING 0x0100
#define E_LONGPRESSED 0x0200
#define E_LONGPRESSING 0x0400
#define E_ALL 0x0FFF
#define NODRAW 0x0120 // Special color value: transparent
struct ButtonColors {
uint16_t bg;
uint16_t text;
uint16_t outline;
};
class Button;
class Event;
#ifdef _M5TOUCH_H_
struct Finger {
Point current, previous, startPoint, tapPoint;
uint32_t startTime, tapTime;
Button* button;
};
#endif
class Event {
public:
Event();
operator uint16_t();
const char* typeName();
const char* objName();
uint16_t direction(bool rot1 = false);
bool isDirection(int16_t wanted, uint8_t plusminus = PLUSMINUS,
bool rot1 = false);
uint16_t distance();
uint8_t finger;
uint16_t type;
Point from, to;
uint16_t duration;
Button* button;
Gesture* gesture;
};
class Button : public Zone {
public:
static std::vector<Button*> instances;
Button(int16_t x_, int16_t y_, int16_t w_, int16_t h_, bool rot1_ = false,
const char* name_ = "", ButtonColors off_ = {NODRAW, NODRAW, NODRAW},
ButtonColors on_ = {NODRAW, NODRAW, NODRAW},
uint8_t datum_ = BUTTON_DATUM, int16_t dx_ = 0, int16_t dy_ = 0,
uint8_t r_ = 0xFF);
Button(uint8_t pin_, uint8_t invert_, uint32_t dbTime_, String hw = "hw",
int16_t x_ = 0, int16_t y_ = 0, int16_t w_ = 0, int16_t h_ = 0,
bool rot1_ = false, const char* name_ = "",
ButtonColors off_ = {NODRAW, NODRAW, NODRAW},
ButtonColors on_ = {NODRAW, NODRAW, NODRAW},
uint8_t datum_ = BUTTON_DATUM, int16_t dx_ = 0, int16_t dy_ = 0,
uint8_t r_ = 0xFF);
~Button();
operator bool();
bool operator==(const Button& b);
bool operator!=(const Button& b);
bool operator==(Button* b);
bool operator!=(Button* b);
int16_t instanceIndex();
bool read(bool manualRead = true);
void fingerDown(Point pos = Point(), uint8_t finger = 0);
void fingerUp(uint8_t finger = 0);
void fingerMove(Point pos, uint8_t finger);
void cancel();
bool isPressed();
bool isReleased();
bool wasPressed();
bool wasReleased();
bool pressedFor(uint32_t ms);
bool pressedFor(uint32_t ms, uint32_t continuous_time);
bool releasedFor(uint32_t ms);
bool wasReleasefor(uint32_t ms);
void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
void delHandlers(void (*fn)(Event&) = nullptr);
char* getName();
uint32_t lastChange();
Event event;
uint16_t userData;
uint16_t tapTime, dbltapTime, longPressTime;
uint16_t repeatDelay, repeatInterval;
protected:
void init();
bool postReleaseEvents();
bool timeoutEvents();
friend class M5Buttons;
char _name[16];
uint8_t _pin;
uint16_t _dbTime;
bool _invert;
bool _changed, _state, _tapWait, _pressing;
bool _longPressing, _cancelled, _manuallyRead;
uint8_t _setState;
uint32_t _time, _lastRepeat;
uint32_t _lastChange, _lastLongPress, _pressTime, _hold_time;
uint8_t _finger;
Point _fromPt[2], _toPt[2], _currentPt[2];
// visual stuff
public:
void draw(ButtonColors bc);
void draw();
void hide(uint16_t color = NODRAW);
void erase(uint16_t color = BLACK);
void setLabel(const char* label_);
void setFont(const GFXfont* freeFont_);
void setFont(uint8_t textFont_ = 0);
void setTextSize(uint8_t textSize_ = 0);
char* label();
ButtonColors off, on;
Zone drawZone;
uint8_t datum, r;
int16_t dx, dy;
void (*drawFn)(Button& b, ButtonColors bc);
protected:
bool _hidden;
bool _compat; // For TFT_eSPI_Button emulation
char _label[51];
uint8_t _textFont;
const GFXfont* _freeFont;
uint8_t _textSize;
};
class Gesture {
public:
static std::vector<Gesture*> instances;
Gesture(Zone fromZone_, Zone toZone_, const char* name_ = "",
uint16_t minDistance_ = GESTURE_MINDIST,
int16_t direction_ = INVALID_VALUE, uint8_t plusminus_ = PLUSMINUS,
bool rot1_ = false, uint16_t maxTime_ = GESTURE_MAXTIME);
Gesture(const char* name_ = "", uint16_t minDistance_ = GESTURE_MINDIST,
int16_t direction_ = INVALID_VALUE, uint8_t plusminus_ = PLUSMINUS,
bool rot1_ = false, uint16_t maxTime_ = GESTURE_MAXTIME);
~Gesture();
operator bool();
int16_t instanceIndex();
bool test(Point& from, Point& to, uint16_t duration);
bool wasDetected();
void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL);
void delHandlers(void (*fn)(Event&) = nullptr);
char* getName();
Zone fromZone;
Zone toZone;
Event event;
int16_t direction;
uint8_t plusminus;
bool rot1;
uint16_t maxTime, minDistance;
protected:
friend class M5Buttons;
bool _detected;
char _name[16];
};
struct EventHandler {
uint16_t eventMask;
Button* button;
Gesture* gesture;
void (*fn)(Event&);
};
class M5Buttons {
public:
static M5Buttons* instance;
static void drawFunction(Button& b, ButtonColors bc);
M5Buttons();
Button* which(Point& p);
void draw();
void update();
void setFont(const GFXfont* freeFont_);
void setFont(uint8_t textFont_);
void setTextSize(uint8_t textSize_);
void (*drawFn)(Button& b, ButtonColors bc);
void fireEvent(uint8_t finger, uint16_t type, Point& from, Point& to,
uint16_t duration, Button* button, Gesture* gesture);
void addHandler(void (*fn)(Event&), uint16_t eventMask = E_ALL,
Button* button = nullptr, Gesture* gesture = nullptr);
void delHandlers(void (*fn)(Event&), Button* button, Gesture* gesture);
Event event;
protected:
std::vector<EventHandler> _eventHandlers;
uint8_t _textFont;
const GFXfont* _freeFont;
uint8_t _textSize;
bool _leftovers;
#ifdef _M5TOUCH_H_
protected:
Finger _finger[2];
#endif
};
// TFT_eSPI_Button compatibility emulation
class TFT_eSPI_Button : public Button {
public:
TFT_eSPI_Button();
void initButton(TFT_eSPI* gfx, int16_t x, int16_t y, uint16_t w, uint16_t h,
uint16_t outline, uint16_t fill, uint16_t textcolor,
char* label_, uint8_t textsize_);
void initButtonUL(TFT_eSPI* gfx, int16_t x_, int16_t y_, uint16_t w_,
uint16_t h_, uint16_t outline, uint16_t fill,
uint16_t textcolor, char* label_, uint8_t textsize_);
void setLabelDatum(int16_t x_delta, int16_t y_delta,
uint8_t datum = MC_DATUM);
void drawButton(bool inverted = false, String long_name = "");
bool contains(int16_t x, int16_t y);
void press(bool p);
bool isPressed();
bool justPressed();
bool justReleased();
};
#endif /* _M5BUTTON_H_ */

View File

@@ -0,0 +1,218 @@
/*
* M5Timer.cpp
*
* M5Timer - A timer library for Arduino.
* Author: mromani@ottotecnica.com
* Copyright (c) 2010 OTTOTECNICA Italy
*
* This library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This library 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. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser
* General Public License along with this library; if not,
* write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "M5Timer.h"
// Select time function:
//static inline unsigned long elapsed() { return micros(); }
static inline unsigned long elapsed() { return millis(); }
M5Timer::M5Timer() {
unsigned long current_millis = elapsed();
for (int i = 0; i < MAX_TIMERS; i++) {
enabled[i] = false;
callbacks[i] = 0; // if the callback pointer is zero, the slot is free, i.e. doesn't "contain" any timer
prev_millis[i] = current_millis;
numRuns[i] = 0;
}
numTimers = 0;
}
void M5Timer::run() {
int i;
unsigned long current_millis;
// get current time
current_millis = elapsed();
for (i = 0; i < MAX_TIMERS; i++) {
toBeCalled[i] = DEFCALL_DONTRUN;
// no callback == no timer, i.e. jump over empty slots
if (callbacks[i] != 0) {
// is it time to process this timer ?
// see http://arduino.cc/forum/index.php/topic,124048.msg932592.html#msg932592
if (current_millis - prev_millis[i] >= delays[i]) {
// update time
//prev_millis[i] = current_millis;
prev_millis[i] += delays[i];
// check if the timer callback has to be executed
if (enabled[i] == true) {
// "run forever" timers must always be executed
if (maxNumRuns[i] == RUN_FOREVER) {
toBeCalled[i] = DEFCALL_RUNONLY;
}else if (numRuns[i] < maxNumRuns[i]) {
// other timers get executed the specified number of times
toBeCalled[i] = DEFCALL_RUNONLY;
numRuns[i]++;
// after the last run, delete the timer
if (numRuns[i] >= maxNumRuns[i]) {
toBeCalled[i] = DEFCALL_RUNANDDEL;
}
}
}
}
}
}
for (i = 0; i < MAX_TIMERS; i++) {
switch (toBeCalled[i]) {
case DEFCALL_DONTRUN:
break;
case DEFCALL_RUNONLY:
callbacks[i]();
break;
case DEFCALL_RUNANDDEL:
callbacks[i]();
deleteTimer(i);
break;
}
}
}
// find the first available slot
// return -1 if none found
int M5Timer::findFirstFreeSlot() {
int i;
// all slots are used
if (numTimers >= MAX_TIMERS) {
return -1;
}
// return the first slot with no callback (i.e. free)
for (i = 0; i < MAX_TIMERS; i++) {
if (callbacks[i] == 0) {
return i;
}
}
// no free slots found
return -1;
}
int M5Timer::setTimer(long d, timer_callback f, int n) {
int freeTimer;
freeTimer = findFirstFreeSlot();
if (freeTimer < 0) {
return -1;
}
if (f == NULL) {
return -1;
}
delays[freeTimer] = d;
callbacks[freeTimer] = f;
maxNumRuns[freeTimer] = n;
enabled[freeTimer] = true;
prev_millis[freeTimer] = elapsed();
numTimers++;
return freeTimer;
}
int M5Timer::setInterval(long d, timer_callback f) {
return setTimer(d, f, RUN_FOREVER);
}
int M5Timer::setTimeout(long d, timer_callback f) {
return setTimer(d, f, RUN_ONCE);
}
void M5Timer::deleteTimer(int timerId) {
if (timerId >= MAX_TIMERS) {
return;
}
// nothing to delete if no timers are in use
if (numTimers == 0) {
return;
}
// don't decrease the number of timers if the
// specified slot is already empty
if (callbacks[timerId] != NULL) {
callbacks[timerId] = 0;
enabled[timerId] = false;
toBeCalled[timerId] = DEFCALL_DONTRUN;
delays[timerId] = 0;
numRuns[timerId] = 0;
// update number of timers
numTimers--;
}
}
// function contributed by code@rowansimms.com
void M5Timer::restartTimer(int numTimer) {
if (numTimer >= MAX_TIMERS) {
return;
}
prev_millis[numTimer] = elapsed();
}
boolean M5Timer::isEnabled(int numTimer) {
if (numTimer >= MAX_TIMERS) {
return false;
}
return enabled[numTimer];
}
void M5Timer::enable(int numTimer) {
if (numTimer >= MAX_TIMERS) {
return;
}
enabled[numTimer] = true;
}
void M5Timer::disable(int numTimer) {
if (numTimer >= MAX_TIMERS) {
return;
}
enabled[numTimer] = false;
}
void M5Timer::toggle(int numTimer) {
if (numTimer >= MAX_TIMERS) {
return;
}
enabled[numTimer] = !enabled[numTimer];
}
int M5Timer::getNumTimers() {
return numTimers;
}

View File

@@ -0,0 +1,120 @@
/*
* M5Timer.h
*
* M5Timer - A timer library for Arduino.
* Author: mromani@ottotecnica.com
* Copyright (c) 2010 OTTOTECNICA Italy
*
* This library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at
* your option) any later version.
*
* This library 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. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser
* General Public License along with this library; if not,
* write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef M5Timer_H
#define M5Timer_H
#include <functional>
#include <Arduino.h>
typedef std::function<void(void)> timer_callback;
class M5Timer {
public:
// maximum number of timers
const static int MAX_TIMERS = 10;
// setTimer() constants
const static int RUN_FOREVER = 0;
const static int RUN_ONCE = 1;
// constructor
M5Timer();
// this function must be called inside loop()
void run();
// call function f every d milliseconds
int setInterval(long d, timer_callback f);
// call function f once after d milliseconds
int setTimeout(long d, timer_callback f);
// call function f every d milliseconds for n times
int setTimer(long d, timer_callback f, int n);
// destroy the specified timer
void deleteTimer(int numTimer);
// restart the specified timer
void restartTimer(int numTimer);
// returns true if the specified timer is enabled
boolean isEnabled(int numTimer);
// enables the specified timer
void enable(int numTimer);
// disables the specified timer
void disable(int numTimer);
// enables the specified timer if it's currently disabled,
// and vice-versa
void toggle(int numTimer);
// returns the number of used timers
int getNumTimers();
// returns the number of available timers
int getNumAvailableTimers() { return MAX_TIMERS - numTimers; };
private:
// deferred call constants
const static int DEFCALL_DONTRUN = 0; // don't call the callback function
const static int DEFCALL_RUNONLY = 1; // call the callback function but don't delete the timer
const static int DEFCALL_RUNANDDEL = 2; // call the callback function and delete the timer
// find the first available slot
int findFirstFreeSlot();
// value returned by the millis() function
// in the previous run() call
unsigned long prev_millis[MAX_TIMERS];
// pointers to the callback functions
timer_callback callbacks[MAX_TIMERS];
// delay values
long delays[MAX_TIMERS];
// number of runs to be executed for each timer
int maxNumRuns[MAX_TIMERS];
// number of executed runs for each timer
int numRuns[MAX_TIMERS];
// which timers are enabled
boolean enabled[MAX_TIMERS];
// deferred function call (sort of) - N.B.: this array is only used in run()
int toBeCalled[MAX_TIMERS];
// actual number of timers in use
int numTimers;
};
#endif

View File

@@ -0,0 +1,252 @@
#include "MPU6886.h"
#include <math.h>
#include <Arduino.h>
MPU6886::MPU6886(){
}
void MPU6886::I2C_Read_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *read_Buffer){
Wire1.beginTransmission(driver_Addr);
Wire1.write(start_Addr);
Wire1.endTransmission(false);
uint8_t i = 0;
Wire1.requestFrom(driver_Addr,number_Bytes);
//! Put read results in the Rx buffer
while (Wire1.available()) {
read_Buffer[i++] = Wire1.read();
}
}
void MPU6886::I2C_Write_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *write_Buffer){
Wire1.beginTransmission(driver_Addr);
Wire1.write(start_Addr);
Wire1.write(*write_Buffer);
Wire1.endTransmission();
}
int MPU6886::Init(void){
unsigned char tempdata[1];
unsigned char regdata;
Wire1.begin(21,22);
I2C_Read_NBytes(MPU6886_ADDRESS, MPU6886_WHOAMI, 1, tempdata);
if(tempdata[0] != 0x19)
return -1;
delay(1);
regdata = 0x00;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_PWR_MGMT_1, 1, &regdata);
delay(10);
regdata = (0x01<<7);
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_PWR_MGMT_1, 1, &regdata);
delay(10);
regdata = (0x01<<0);
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_PWR_MGMT_1, 1, &regdata);
delay(10);
regdata = 0x10;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_ACCEL_CONFIG, 1, &regdata);
delay(1);
regdata = 0x18;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_GYRO_CONFIG, 1, &regdata);
delay(1);
regdata = 0x01;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_CONFIG, 1, &regdata);
delay(1);
regdata = 0x05;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_SMPLRT_DIV, 1,&regdata);
delay(1);
regdata = 0x00;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_ENABLE, 1, &regdata);
delay(1);
regdata = 0x00;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_ACCEL_CONFIG2, 1, &regdata);
delay(1);
regdata = 0x00;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_USER_CTRL, 1, &regdata);
delay(1);
regdata = 0x00;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_FIFO_EN, 1, &regdata);
delay(1);
regdata = 0x22;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_PIN_CFG, 1, &regdata);
delay(1);
regdata = 0x01;
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_ENABLE, 1, &regdata);
delay(100);
getGres();
getAres();
return 0;
}
void MPU6886::getAccelAdc(int16_t* ax, int16_t* ay, int16_t* az){
uint8_t buf[6];
I2C_Read_NBytes(MPU6886_ADDRESS,MPU6886_ACCEL_XOUT_H,6,buf);
*ax=((int16_t)buf[0]<<8)|buf[1];
*ay=((int16_t)buf[2]<<8)|buf[3];
*az=((int16_t)buf[4]<<8)|buf[5];
}
void MPU6886::getGyroAdc(int16_t* gx, int16_t* gy, int16_t* gz){
uint8_t buf[6];
I2C_Read_NBytes(MPU6886_ADDRESS,MPU6886_GYRO_XOUT_H,6,buf);
*gx=((uint16_t)buf[0]<<8)|buf[1];
*gy=((uint16_t)buf[2]<<8)|buf[3];
*gz=((uint16_t)buf[4]<<8)|buf[5];
}
void MPU6886::getTempAdc(int16_t *t){
uint8_t buf[2];
I2C_Read_NBytes(MPU6886_ADDRESS,MPU6886_TEMP_OUT_H,2,buf);
*t=((uint16_t)buf[0]<<8)|buf[1];
}
//!俯仰航向横滚pitchyawroll指三维空间中飞行器的旋转状态。
void MPU6886::getAhrsData(float *pitch,float *roll,float *yaw){
float accX = 0;
float accY = 0;
float accZ = 0;
float gyroX = 0;
float gyroY = 0;
float gyroZ = 0;
getGyroData(&gyroX,&gyroY,&gyroZ);
getAccelData(&accX,&accY,&accZ);
MahonyAHRSupdateIMU(gyroX * DEG_TO_RAD, gyroY * DEG_TO_RAD, gyroZ * DEG_TO_RAD, accX, accY, accZ,pitch,roll,yaw);
}
void MPU6886::getGres(){
switch (Gyscale)
{
// Possible gyro scales (and their register bit settings) are:
case GFS_250DPS:
gRes = 250.0/32768.0;
break;
case GFS_500DPS:
gRes = 500.0/32768.0;
break;
case GFS_1000DPS:
gRes = 1000.0/32768.0;
break;
case GFS_2000DPS:
gRes = 2000.0/32768.0;
break;
}
}
void MPU6886::getAres(){
switch (Acscale)
{
// Possible accelerometer scales (and their register bit settings) are:
// 2 Gs (00), 4 Gs (01), 8 Gs (10), and 16 Gs (11).
// Here's a bit of an algorith to calculate DPS/(ADC tick) based on that 2-bit value:
case AFS_2G:
aRes = 2.0/32768.0;
break;
case AFS_4G:
aRes = 4.0/32768.0;
break;
case AFS_8G:
aRes = 8.0/32768.0;
break;
case AFS_16G:
aRes = 16.0/32768.0;
break;
}
}
void MPU6886::SetGyroFsr(Gscale scale)
{
//return IIC_Write_Byte(MPU_GYRO_CFG_REG,scale<<3);//设置陀螺仪满量程范围
unsigned char regdata;
regdata = (scale<<3);
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_GYRO_CONFIG, 1, &regdata);
delay(10);
Gyscale = scale;
getGres();
}
void MPU6886::SetAccelFsr(Ascale scale)
{
unsigned char regdata;
regdata = (scale<<3);
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_ACCEL_CONFIG, 1, &regdata);
delay(10);
Acscale = scale;
getAres();
}
void MPU6886::getAccelData(float* ax, float* ay, float* az){
int16_t accX = 0;
int16_t accY = 0;
int16_t accZ = 0;
getAccelAdc(&accX,&accY,&accZ);
*ax = (float)accX * aRes;
*ay = (float)accY * aRes;
*az = (float)accZ * aRes;
}
void MPU6886::getGyroData(float* gx, float* gy, float* gz){
int16_t gyroX = 0;
int16_t gyroY = 0;
int16_t gyroZ = 0;
getGyroAdc(&gyroX,&gyroY,&gyroZ);
*gx = (float)gyroX * gRes;
*gy = (float)gyroY * gRes;
*gz = (float)gyroZ * gRes;
}
void MPU6886::getTempData(float *t){
int16_t temp = 0;
getTempAdc(&temp);
*t = (float)temp / 326.8 + 25.0;
}

View File

@@ -0,0 +1,98 @@
/*
Note: The MPU6886 is an I2C sensor and uses the Arduino Wire library.
Because the sensor is not 5V tolerant, we are using a 3.3 V 8 MHz Pro Mini or
a 3.3 V Teensy 3.1. We have disabled the internal pull-ups used by the Wire
library in the Wire.h/twi.c utility file. We are also using the 400 kHz fast
I2C mode by setting the TWI_FREQ to 400000L /twi.h utility file.
*/
#ifndef _MPU6886_H_
#define _MPU6886_H_
#include <Wire.h>
#include <Arduino.h>
#include "MahonyAHRS.h"
#define MPU6886_ADDRESS 0x68
#define MPU6886_WHOAMI 0x75
#define MPU6886_ACCEL_INTEL_CTRL 0x69
#define MPU6886_SMPLRT_DIV 0x19
#define MPU6886_INT_PIN_CFG 0x37
#define MPU6886_INT_ENABLE 0x38
#define MPU6886_ACCEL_XOUT_H 0x3B
#define MPU6886_ACCEL_XOUT_L 0x3C
#define MPU6886_ACCEL_YOUT_H 0x3D
#define MPU6886_ACCEL_YOUT_L 0x3E
#define MPU6886_ACCEL_ZOUT_H 0x3F
#define MPU6886_ACCEL_ZOUT_L 0x40
#define MPU6886_TEMP_OUT_H 0x41
#define MPU6886_TEMP_OUT_L 0x42
#define MPU6886_GYRO_XOUT_H 0x43
#define MPU6886_GYRO_XOUT_L 0x44
#define MPU6886_GYRO_YOUT_H 0x45
#define MPU6886_GYRO_YOUT_L 0x46
#define MPU6886_GYRO_ZOUT_H 0x47
#define MPU6886_GYRO_ZOUT_L 0x48
#define MPU6886_USER_CTRL 0x6A
#define MPU6886_PWR_MGMT_1 0x6B
#define MPU6886_PWR_MGMT_2 0x6C
#define MPU6886_CONFIG 0x1A
#define MPU6886_GYRO_CONFIG 0x1B
#define MPU6886_ACCEL_CONFIG 0x1C
#define MPU6886_ACCEL_CONFIG2 0x1D
#define MPU6886_FIFO_EN 0x23
//#define G (9.8)
#define RtA 57.324841
#define AtR 0.0174533
#define Gyro_Gr 0.0010653
class MPU6886 {
public:
enum Ascale {
AFS_2G = 0,
AFS_4G,
AFS_8G,
AFS_16G
};
enum Gscale {
GFS_250DPS = 0,
GFS_500DPS,
GFS_1000DPS,
GFS_2000DPS
};
Gscale Gyscale = GFS_2000DPS;
Ascale Acscale = AFS_8G;
public:
MPU6886();
int Init(void);
void getAccelAdc(int16_t* ax, int16_t* ay, int16_t* az);
void getGyroAdc(int16_t* gx, int16_t* gy, int16_t* gz);
void getTempAdc(int16_t *t);
void getAccelData(float* ax, float* ay, float* az);
void getGyroData(float* gx, float* gy, float* gz);
void getTempData(float *t);
void SetGyroFsr(Gscale scale);
void SetAccelFsr(Ascale scale);
void getAhrsData(float *pitch,float *roll,float *yaw);
public:
float aRes, gRes;
private:
private:
void I2C_Read_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *read_Buffer);
void I2C_Write_NBytes(uint8_t driver_Addr, uint8_t start_Addr, uint8_t number_Bytes, uint8_t *write_Buffer);
void getGres();
void getAres();
};
#endif

View File

@@ -0,0 +1,254 @@
//=====================================================================================================
// MahonyAHRS.c
//=====================================================================================================
//
// Madgwick's implementation of Mayhony's AHRS algorithm.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date Author Notes
// 29/09/2011 SOH Madgwick Initial release
// 02/10/2011 SOH Madgwick Optimised for reduced CPU load
//
//=====================================================================================================
//---------------------------------------------------------------------------------------------------
// Header files
#include "MahonyAHRS.h"
#include "Arduino.h"
#include <math.h>
//---------------------------------------------------------------------------------------------------
// Definitions
#define sampleFreq 25.0f // sample frequency in Hz
#define twoKpDef (2.0f * 1.0f) // 2 * proportional gain
#define twoKiDef (2.0f * 0.0f) // 2 * integral gain
//#define twoKiDef (0.0f * 0.0f)
//---------------------------------------------------------------------------------------------------
// Variable definitions
volatile float twoKp = twoKpDef; // 2 * proportional gain (Kp)
volatile float twoKi = twoKiDef; // 2 * integral gain (Ki)
volatile float q0 = 1.0, q1 = 0.0, q2 = 0.0, q3 = 0.0; // quaternion of sensor frame relative to auxiliary frame
volatile float integralFBx = 0.0f, integralFBy = 0.0f, integralFBz = 0.0f; // integral error terms scaled by Ki
//---------------------------------------------------------------------------------------------------
// Function declarations
//float invSqrt(float x);
//====================================================================================================
// Functions
//---------------------------------------------------------------------------------------------------
// AHRS algorithm update
void MahonyAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz) {
float recipNorm;
float q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
float hx, hy, bx, bz;
float halfvx, halfvy, halfvz, halfwx, halfwy, halfwz;
float halfex, halfey, halfez;
float qa, qb, qc;
// Use IMU algorithm if magnetometer measurement invalid (avoids NaN in magnetometer normalisation)
if((mx == 0.0f) && (my == 0.0f) && (mz == 0.0f)) {
//MahonyAHRSupdateIMU(gx, gy, gz, ax, ay, az);
return;
}
// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {
// Normalise accelerometer measurement
recipNorm = sqrt(ax * ax + ay * ay + az * az);
ax *= recipNorm;
ay *= recipNorm;
az *= recipNorm;
// Normalise magnetometer measurement
recipNorm = sqrt(mx * mx + my * my + mz * mz);
mx *= recipNorm;
my *= recipNorm;
mz *= recipNorm;
// Auxiliary variables to avoid repeated arithmetic
q0q0 = q0 * q0;
q0q1 = q0 * q1;
q0q2 = q0 * q2;
q0q3 = q0 * q3;
q1q1 = q1 * q1;
q1q2 = q1 * q2;
q1q3 = q1 * q3;
q2q2 = q2 * q2;
q2q3 = q2 * q3;
q3q3 = q3 * q3;
// Reference direction of Earth's magnetic field
hx = 2.0f * (mx * (0.5f - q2q2 - q3q3) + my * (q1q2 - q0q3) + mz * (q1q3 + q0q2));
hy = 2.0f * (mx * (q1q2 + q0q3) + my * (0.5f - q1q1 - q3q3) + mz * (q2q3 - q0q1));
bx = sqrt(hx * hx + hy * hy);
bz = 2.0f * (mx * (q1q3 - q0q2) + my * (q2q3 + q0q1) + mz * (0.5f - q1q1 - q2q2));
// Estimated direction of gravity and magnetic field
halfvx = q1q3 - q0q2;
halfvy = q0q1 + q2q3;
halfvz = q0q0 - 0.5f + q3q3;
halfwx = bx * (0.5f - q2q2 - q3q3) + bz * (q1q3 - q0q2);
halfwy = bx * (q1q2 - q0q3) + bz * (q0q1 + q2q3);
halfwz = bx * (q0q2 + q1q3) + bz * (0.5f - q1q1 - q2q2);
// Error is sum of cross product between estimated direction and measured direction of field vectors
halfex = (ay * halfvz - az * halfvy) + (my * halfwz - mz * halfwy);
halfey = (az * halfvx - ax * halfvz) + (mz * halfwx - mx * halfwz);
halfez = (ax * halfvy - ay * halfvx) + (mx * halfwy - my * halfwx);
// Compute and apply integral feedback if enabled
if(twoKi > 0.0f) {
integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki
integralFBy += twoKi * halfey * (1.0f / sampleFreq);
integralFBz += twoKi * halfez * (1.0f / sampleFreq);
gx += integralFBx; // apply integral feedback
gy += integralFBy;
gz += integralFBz;
}
else {
integralFBx = 0.0f; // prevent integral windup
integralFBy = 0.0f;
integralFBz = 0.0f;
}
// Apply proportional feedback
gx += twoKp * halfex;
gy += twoKp * halfey;
gz += twoKp * halfez;
}
// Integrate rate of change of quaternion
gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors
gy *= (0.5f * (1.0f / sampleFreq));
gz *= (0.5f * (1.0f / sampleFreq));
qa = q0;
qb = q1;
qc = q2;
q0 += (-qb * gx - qc * gy - q3 * gz);
q1 += (qa * gx + qc * gz - q3 * gy);
q2 += (qa * gy - qb * gz + q3 * gx);
q3 += (qa * gz + qb * gy - qc * gx);
// Normalise quaternion
recipNorm = sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 *= recipNorm;
q1 *= recipNorm;
q2 *= recipNorm;
q3 *= recipNorm;
}
//---------------------------------------------------------------------------------------------------
// IMU algorithm update
void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az,float *pitch,float *roll,float *yaw) {
float recipNorm;
float halfvx, halfvy, halfvz;
float halfex, halfey, halfez;
float qa, qb, qc;
// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {
// Normalise accelerometer measurement
recipNorm = invSqrt(ax * ax + ay * ay + az * az);
ax *= recipNorm;
ay *= recipNorm;
az *= recipNorm;
// Estimated direction of gravity and vector perpendicular to magnetic flux
halfvx = q1 * q3 - q0 * q2;
halfvy = q0 * q1 + q2 * q3;
halfvz = q0 * q0 - 0.5f + q3 * q3;
// Error is sum of cross product between estimated and measured direction of gravity
halfex = (ay * halfvz - az * halfvy);
halfey = (az * halfvx - ax * halfvz);
halfez = (ax * halfvy - ay * halfvx);
// Compute and apply integral feedback if enabled
if(twoKi > 0.0f) {
integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki
integralFBy += twoKi * halfey * (1.0f / sampleFreq);
integralFBz += twoKi * halfez * (1.0f / sampleFreq);
gx += integralFBx; // apply integral feedback
gy += integralFBy;
gz += integralFBz;
}
else {
integralFBx = 0.0f; // prevent integral windup
integralFBy = 0.0f;
integralFBz = 0.0f;
}
// Apply proportional feedback
gx += twoKp * halfex;
gy += twoKp * halfey;
gz += twoKp * halfez;
}
// Integrate rate of change of quaternion
gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors
gy *= (0.5f * (1.0f / sampleFreq));
gz *= (0.5f * (1.0f / sampleFreq));
qa = q0;
qb = q1;
qc = q2;
q0 += (-qb * gx - qc * gy - q3 * gz);
q1 += (qa * gx + qc * gz - q3 * gy);
q2 += (qa * gy - qb * gz + q3 * gx);
q3 += (qa * gz + qb * gy - qc * gx);
// Normalise quaternion
recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 *= recipNorm;
q1 *= recipNorm;
q2 *= recipNorm;
q3 *= recipNorm;
*pitch = asin(-2 * q1 * q3 + 2 * q0* q2); // pitch
*roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1); // roll
*yaw = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3); //yaw
*pitch *= RAD_TO_DEG;
*yaw *= RAD_TO_DEG;
// Declination of SparkFun Electronics (40°05'26.6"N 105°11'05.9"W) is
// 8° 30' E ± 0° 21' (or 8.5°) on 2016-07-19
// - http://www.ngdc.noaa.gov/geomag-web/#declination
*yaw -= 8.5;
*roll *= RAD_TO_DEG;
///Serial.printf("%f %f %f \r\n", pitch, roll, yaw);
}
//---------------------------------------------------------------------------------------------------
// Fast inverse square-root
// See: http://en.wikipedia.org/wiki/Fast_inverse_square_root
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
float invSqrt(float x) {
float halfx = 0.5f * x;
float y = x;
long i = *(long*)&y;
i = 0x5f3759df - (i>>1);
y = *(float*)&i;
y = y * (1.5f - (halfx * y * y));
return y;
}
#pragma GCC diagnostic pop
//====================================================================================================
// END OF CODE
//====================================================================================================

View File

@@ -0,0 +1,33 @@
//=====================================================================================================
// MahonyAHRS.h
//=====================================================================================================
//
// Madgwick's implementation of Mayhony's AHRS algorithm.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date Author Notes
// 29/09/2011 SOH Madgwick Initial release
// 02/10/2011 SOH Madgwick Optimised for reduced CPU load
//
//=====================================================================================================
#ifndef MahonyAHRS_h
#define MahonyAHRS_h
//----------------------------------------------------------------------------------------------------
// Variable declaration
extern volatile float twoKp; // 2 * proportional gain (Kp)
extern volatile float twoKi; // 2 * integral gain (Ki)
//volatile float q0, q1, q2, q3; // quaternion of sensor frame relative to auxiliary frame
//---------------------------------------------------------------------------------------------------
// Function declarations
void MahonyAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
//void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az);
void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az,float *pitch,float *roll,float *yaw);
float invSqrt(float x);
#endif
//=====================================================================================================
// End of file
//=====================================================================================================

View File

@@ -0,0 +1,183 @@
#include "PointAndZone.h"
// Point class
Point::Point(int16_t x_ /* = INVALID_VALUE */,
int16_t y_ /* = INVALID_VALUE */) {
x = x_;
y = y_;
}
bool Point::operator==(const Point& p) { return (Equals(p)); }
bool Point::operator!=(const Point& p) { return (!Equals(p)); }
Point::operator char*() {
if (valid()) {
sprintf(_text, "(%d, %d)", x, y);
} else {
strncpy(_text, "(invalid)", 12);
}
return _text;
}
Point::operator bool() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
void Point::set(int16_t x_ /* = INVALID_VALUE */,
int16_t y_ /* = INVALID_VALUE */) {
x = x_;
y = y_;
}
bool Point::Equals(const Point& p) { return (x == p.x && y == p.y); }
bool Point::in(Zone& z) { return (z.contains(x, y)); }
bool Point::valid() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
uint16_t Point::distanceTo(const Point& p) {
int16_t dx = x - p.x;
int16_t dy = y - p.y;
return sqrt(dx * dx + dy * dy) + 0.5;
}
uint16_t Point::directionTo(const Point& p, bool rot1 /* = false */) {
// 57.29578 =~ 180/pi, see https://stackoverflow.com/a/62486304
uint16_t a = (uint16_t)(450.5 + (atan2(p.y - y, p.x - x) * 57.29578));
#ifdef TFT
if (rot1) {
if (TFT->rotation < 4) {
a = (((TFT->rotation + 3) % 4) * 90) + a;
} else {
a = (((TFT->rotation + 1) % 4) * 90) - a;
}
}
#endif /* TFT */
a = (360 + a) % 360;
return a;
}
bool Point::isDirectionTo(const Point& p, int16_t wanted,
uint8_t plusminus /* = PLUSMINUS */,
bool rot1 /* = false */) {
uint16_t a = directionTo(p, rot1);
return (min(abs(wanted - a), 360 - abs(wanted - a)) <= plusminus);
}
void Point::rotate(uint8_t m) {
if (m == 1 || !valid()) return;
int16_t normal_x = x;
int16_t normal_y = y;
int16_t inv_x = HIGHEST_X - x;
int16_t inv_y = HIGHEST_Y - y;
// inv_y can be negative for area below screen of M5Core2
switch (m) {
case 0:
x = inv_y;
y = normal_x;
break;
case 2:
x = normal_y;
y = inv_x;
break;
case 3:
x = inv_x;
y = inv_y;
break;
// rotations 4-7 are mirrored
case 4:
x = inv_y;
y = inv_x;
break;
case 5:
x = normal_x;
y = inv_y;
break;
case 6:
x = normal_y;
y = normal_x;
break;
case 7:
x = inv_x;
y = normal_y;
break;
}
}
// Zone class
Zone::Zone(int16_t x_ /* = INVALID_VALUE */, int16_t y_ /* = INVALID_VALUE */,
int16_t w_ /* = 0 */, int16_t h_ /* = 0 */, bool rot1_ /* = false */
) {
set(x_, y_, w_, h_, rot1_);
}
Zone::operator bool() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
void Zone::set(int16_t x_ /* = INVALID_VALUE */,
int16_t y_ /* = INVALID_VALUE */,
int16_t w_ /* = 0 */, int16_t h_ /* = 0 */,
bool rot1_ /* = false */) {
x = x_;
y = y_;
w = w_;
h = h_;
rot1 = rot1_;
}
bool Zone::valid() { return !(x == INVALID_VALUE && y == INVALID_VALUE); }
bool Zone::contains(const Point& p) { return contains(p.x, p.y); }
bool Zone::contains(int16_t x_, int16_t y_) {
#ifdef TFT
if (rot1 && TFT->rotation != 1) {
Zone t = *this;
t.rotate(TFT->rotation);
return (y_ >= t.y && y_ <= t.y + t.h && x_ >= t.x && x_ <= t.x + t.w);
}
#endif /* TFT */
return (y_ >= y && y_ <= y + h && x_ >= x && x_ <= x + w);
}
void Zone::rotate(uint8_t m) {
if (m == 1) return;
int16_t normal_x = x;
int16_t normal_y = y;
int16_t inv_x = TFT_WIDTH - 1 - x - w;
int16_t inv_y = TFT_HEIGHT - 1 - y - h; // negative for area below screen
switch (m) {
case 0:
x = inv_y;
y = normal_x;
break;
case 2:
x = normal_y;
y = inv_x;
break;
case 3:
x = inv_x;
y = inv_y;
break;
// rotations 4-7 are mirrored
case 4:
x = inv_y;
y = inv_x;
break;
case 5:
x = normal_x;
y = inv_y;
break;
case 6:
x = normal_y;
y = normal_x;
break;
case 7:
x = inv_x;
y = normal_y;
break;
}
if (!(m % 2)) std::swap(w, h);
}

View File

@@ -0,0 +1,191 @@
/*
== The PointAndZone Library ==
This library was written to supply Point and Zone, common primitives for
M5Display and M5Button, libraries originally written for the M5Stack series
of devices. They could be useful for many other applications, especially
anything based on a TFT_eSPI display driver.
== Point and Zone: Describing Points and Areas on the Screen ==
The Point and Zone classes allow you to create variables that hold a point
or an area on the screen.
Point(x, y)
Holds a point on the screen. Has members x and y that hold the coordinates
of a touch. Values INVALID_VALUE for x and y indicate an invalid value,
and that's what a point starts out with if you declare it without
parameters. The 'valid()' method tests if a point is valid. If you
explicitly evaluate a Point as a boolean ("if (p) ..."), you also get
whether the point is valid, so this is equivalent to writing "if
(p.valid()) ...".
Zone(x, y, w, h)
Holds a rectangular area on the screen. Members x, y, w and h are for the
x and y coordinate of the top-left corner and the width and height of the
rectangle.
The 'set' method allows you to change the properties of an existing Point
or Zone. Using the 'in' or 'contains' method you can test if a point lies
in a zone.
The PointAndZone library also provides the low-level support for direction
from one point to another and for screen rotation translations.
== Looking for Directions? ==
This library allows you to find the distance in pixels between two Point
objects with "A.distanceTo(B)". Using "A.directionTo(B)" will output a
compass course in degrees from A to B. That is, if A lies directly above A
on the screen the output will be 0, if B lies to the left of A, the output
will be 270. You can also test whether a direction matches some other
direction by using "A.isDirectionTo(B, 0, 30)". What this does is take the
direction from A to B and output 'true' if it is 0, plus or minus 30
degrees. (So either between 330 and 359 or between 0 and 30).
Do note that unlike in math, on a display the Y-axis points downwards. So
pixel coordinates (10, 10) are at direction 135 deg from (0, 0).
As a last argument to the direction functions, you can supply 'rot1' again
(default is 'false'). Just like in zones and buttons, what that means is
that the direction is output as if the rotation 1 coordinate system was
used.
Distance and direction functionality is used in Gesture recognition in the
M5Button highler level library. Its 'Event' objects have methods that look
very much like these, except the 'To' in the name is missing because Events
have a starting and ending point so you can just print
"myEvent.direction()" or state "if (myEvent.isDirection(0,30) ..."
== Some Examples ==
Point a;
Point b(50, 120);
Serial.println(a.valid()); // 0
Serial.println(a); // (invalid)
a.set(10, 30);
Serial.println(a); // (10,30)
Serial.println(a.valid()); // 1
Serial.println(b.y); // 120
Serial.println(a.distanceTo(b)); // 98
Serial.println(a.directionTo(b)); // 156
Serial.println(a.isDirectionTo(b, 0, 30)); // 0
Zone z(0,0,100, 100);
Serial.println(z.w); // 100
Serial.println(z.contains(a)); // 1
Serial.println(b.in(z)); // 0
== Screen Rotation and the 'rot1' Property ==
TL;DR: just set it to 'false' if you don't need anything special,
otherwise read the rest of this section.
The TFT_eSPI library allows you to rotate the screen. On M5Stack devices,
you do this with "M5.Lcd.SetRotation", supplying a number between 0 and 7.
Numbers 0-3 are the obvious rotations, with 1 being the default. 4-7 are
the other 4 mirrored, so you would not be using these unless you want to
look at the display through a mirror for a homebrew heads-up display or
mini-teleprompter.
The M5Touch library for the Core2 device rotates the data it reads from the
sensor to match the display using the 'rotate' function on the Point
objects it reads. So if the display is upside down (rotation 3), the (0,
0)-point is diagonally accross from where it is in rotation 1. (And the
area that was below the screen at y 240-279 now has negative y-values.)
Zones (and buttons, because they are extensions of zones) have a 'rot1'
property. This would normally set to false, meaning the coordinates are
treated as specified in the current rotation. If it is set to 'true'
however, the library treats the cordinates as if they are specified in
rotation 1 and internally rotates them before evaluating whether a Point is
in a Zone. The Button object also rotates the coordinates before drawing
the button.
So by setting 'rot1' to true, you can create zones and buttons that stay in
the same place even if the screen is rotated. On the Core2, this is used to
define the BtnA through BtnC virtual below-screen buttons, which should
always be in the area below the screen where the circles are printed,
regardless of rotation.
== Porting ==
To use this library on other devices, simply replace these two lines
#include <M5Display.h> // so that we can get the rotation
#include "utility/Config.h" // Defines 'TFT', a pointer to the display
by whatever you need to do to make the symbol 'TFT' be a pointer to a
TFT_eSPI-derived display device that has a 'rotation' variable. If you
don't need rotation just delete the lines: the direction functions and the
'contains' function will now simply ignore their 'rot1' parameters.
*/
#ifndef _POINTANDZONE_H_
#define _POINTANDZONE_H_
#include <Arduino.h>
#include <M5Display.h> // so that we can get the rotation
#include "utility/Config.h" // Defines 'TFT', a pointer to the display
#define INVALID_VALUE -32768
#define PLUSMINUS 45 // default value for isDirectionTo
#define DIR_UP 0
#define DIR_RIGHT 90
#define DIR_DOWN 180
#define DIR_LEFT 270
#define DIR_ANY INVALID_VALUE
#define HIGHEST_X 319 // Can't trust TFT_WIDTH, driver is portrait
#define HIGHEST_Y 239
class Zone;
class Point {
public:
Point(int16_t x_ = INVALID_VALUE, int16_t y_ = INVALID_VALUE);
bool operator==(const Point& p);
bool operator!=(const Point& p);
explicit operator bool();
operator char*();
void set(int16_t x_ = INVALID_VALUE, int16_t y_ = INVALID_VALUE);
bool valid();
bool in(Zone& z);
bool Equals(const Point& p);
uint16_t distanceTo(const Point& p);
uint16_t directionTo(const Point& p, bool rot1 = false);
bool isDirectionTo(const Point& p, int16_t wanted,
uint8_t plusminus = PLUSMINUS, bool rot1 = false);
void rotate(uint8_t m);
int16_t x, y;
private:
char _text[12];
};
class Zone {
public:
Zone(int16_t x_ = INVALID_VALUE, int16_t y_ = INVALID_VALUE, int16_t w_ = 0,
int16_t h_ = 0, bool rot1_ = false);
explicit operator bool();
bool valid();
void set(int16_t x_ = INVALID_VALUE, int16_t y_ = INVALID_VALUE,
int16_t w_ = 0 , int16_t h_ = 0, bool rot1_ = false);
bool contains(const Point& p);
bool contains(int16_t x, int16_t y);
void rotate(uint8_t m);
int16_t x, y, w, h;
bool rot1;
};
#endif /* _POINTANDZONE_H_ */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,186 @@
#ifndef _SPRITE_H_
#define _SPRITE_H_
/***************************************************************************************
// The following class creates Sprites in RAM, graphics can then be drawn in the Sprite
// and rendered quickly onto the TFT screen. The class inherits the graphics functions
// from the TFT_eSPI class. Some functions are overridden by this class so that the
// graphics are written to the Sprite rather than the TFT.
***************************************************************************************/
#include <Arduino.h>
#include <FS.h>
#include <SPI.h>
#include "In_eSPI.h"
typedef enum {
JPEG_DIV_ESPRITE_NONE,
JPEG_DIV_ESPRITE_2,
JPEG_DIV_ESPRITE_4,
JPEG_DIV_ESPRITE_8,
JPEG_DIV_ESPRITE_MAX
} jpeg_div_eSprite_t;
class TFT_eSprite : public TFT_eSPI {
public:
TFT_eSprite(TFT_eSPI *tft);
// Create a sprite of width x height pixels, return a pointer to the RAM area
// Sketch can cast returned value to (uint16_t*) for 16 bit depth if needed
// RAM required is 1 byte per pixel for 8 bit colour depth, 2 bytes for 16 bit
void* createSprite(int16_t width, int16_t height, uint8_t frames = 1);
// Delete the sprite to free up the RAM
void deleteSprite(void);
// Select the frame buffer for graphics
void* frameBuffer(int8_t f);
// Set or get the colour depth to 8 or 16 bits. Can be used to change depth an existing
// sprite, but clears it to black, returns a new pointer if sprite is re-created.
void* setColorDepth(int8_t b);
int8_t getColorDepth(void);
void setBitmapColor(uint16_t c, uint16_t b);
void drawPixel(int32_t x, int32_t y, uint32_t color);
void drawChar(int32_t x, int32_t y, uint16_t c, uint32_t color, uint32_t bg, uint8_t size),
fillSprite(uint32_t color),
fillScreen(uint32_t color),
// Define a window to push 16 bit colour pixels into in a raster order
// Colours are converted to 8 bit if depth is set to 8
setWindow(int32_t x0, int32_t y0, int32_t x1, int32_t y1),
pushColor(uint32_t color),
pushColor(uint32_t color, uint16_t len),
pushEmptyColor(),
// Push a pixel preformatted as a 8 or 16 bit colour (avoids conversion overhead)
writeColor(uint16_t color),
writeColors(uint16_t* color, uint16_t len),
// Set the scroll zone, top left corner at x,y with defined width and height
// The colour (optional, black is default) is used to fill the gap after the scroll
setScrollRect(int32_t x, int32_t y, int32_t w, int32_t h, uint16_t color = TFT_BLACK),
// Scroll the defined zone dx,dy pixels. Negative values left,up, positive right,down
// dy is optional (default is then no up/down scroll).
// The sprite coordinate frame does not move because pixels are moved
scroll(int16_t dx, int16_t dy = 0),
drawLine(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint32_t color),
drawFastVLine(int32_t x, int32_t y, int32_t h, uint32_t color),
drawFastHLine(int32_t x, int32_t y, int32_t w, uint32_t color),
fillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
// Set the sprite text cursor position for print class (does not change the TFT screen cursor)
//setCursor(int16_t x, int16_t y);
// Set the rotation of the Sprite (for 1bpp Sprites only)
void setRotation(uint8_t rotation);
uint8_t getRotation(void);
void getSprite2Buff( uint16_t *ptr, int32_t x, int32_t y, int32_t w, int32_t h);
// Push a rotated copy of Sprite to TFT with optional transparent colour
bool pushRotated(int16_t angle, int32_t transp = -1);
// Push a rotated copy of Sprite to another different Sprite with optional transparent colour
bool pushRotated(TFT_eSprite *spr, int16_t angle, int32_t transp = -1);
// Set and get the pivot point for this Sprite
void setPivot(int16_t x, int16_t y);
int16_t getPivotX(void),
getPivotY(void);
// Get the bounding box for a rotated copy of this Sprite
void getRotatedBounds(float sina, float cosa, int16_t w, int16_t h, int16_t xp, int16_t yp,
int16_t *min_x, int16_t *min_y, int16_t *max_x, int16_t *max_y);
// Read the colour of a pixel at x,y and return value in 565 format
uint16_t readPixel(int32_t x0, int32_t y0);
// Write an image (colour bitmap) to the sprite
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, uint16_t *data);
void pushImage(int32_t x0, int32_t y0, int32_t w, int32_t h, const uint16_t *data);
void pushInSprite(TFT_eSprite *spr,int32_t srcX, int32_t srcY, int32_t srcW, int32_t srcH,
int32_t desX, int32_t desY);
// Swap the byte order for pushImage() - corrects different image endianness
void setSwapBytes(bool swap);
bool getSwapBytes(void);
// Push the sprite to the TFT screen, this fn calls pushImage() in the TFT class.
// Optionally a "transparent" colour can be defined, pixels of that colour will not be rendered
void pushSprite(int32_t x, int32_t y);
void pushSprite(int32_t x, int32_t y, uint16_t transparent);
int16_t drawChar(uint16_t uniCode, int32_t x, int32_t y, uint8_t font),
drawChar(uint16_t uniCode, int32_t x, int32_t y);
// Return the width and height of the sprite
int16_t width(void),
height(void);
// Used by print class to print text to cursor position
size_t write(uint8_t);
// Functions associated with anti-aliased fonts
void drawGlyph(uint16_t code);
void printToSprite(String string);
void printToSprite(char *cbuffer, uint16_t len);
int16_t printToSprite(int16_t x, int16_t y, uint16_t index);
void setPsram( bool enabled );
void drawColorBitmap(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data, uint32_t color, uint32_t bkcolor);
void drawColorBitmapAlpha(int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t *data, uint32_t color, uint32_t bkcolor);
void drawJpg(const uint8_t *jpg_data, size_t jpg_len, uint16_t x = 0,
uint16_t y = 0, uint16_t maxWidth = 0, uint16_t maxHeight = 0,
uint16_t offX = 0, uint16_t offY = 0,
jpeg_div_eSprite_t scale = JPEG_DIV_ESPRITE_NONE);
void drawJpgFile(fs::FS &fs, const char *path, uint16_t x = 0, uint16_t y = 0,
uint16_t maxWidth = 0, uint16_t maxHeight = 0,
uint16_t offX = 0, uint16_t offY = 0,
jpeg_div_eSprite_t scale = JPEG_DIV_ESPRITE_NONE);
private:
TFT_eSPI *_tft;
// Reserve memory for the Sprite and return a pointer
void* callocSprite(int16_t width, int16_t height, uint8_t frames = 1);
protected:
uint8_t _bpp; // bits per pixel (1, 8 or 16)
uint16_t *_img; // pointer to 16 bit sprite
uint8_t *_img8; // pointer to 8 bit sprite
uint8_t *_img8_1; // pointer to frame 1
uint8_t *_img8_2; // pointer to frame 2
int16_t _xpivot; // x pivot point coordinate
int16_t _ypivot; // y pivot point coordinate
bool _created; // A Sprite has been created and memory reserved
bool _gFont = false;
bool _usePsram = true; // use Psram if available
// int32_t _icursor_x, _icursor_y;
uint8_t _rotation = 0;
int32_t _xs, _ys, _xe, _ye, _xptr, _yptr; // for setWindow
int32_t _sx, _sy; // x,y for scroll zone
uint32_t _sw, _sh; // w,h for scroll zone
uint32_t _scolor; // gap fill colour for scroll zone
boolean _iswapBytes; // Swap the byte order for Sprite pushImage()
int32_t _iwidth, _iheight; // Sprite memory image bit width and height (swapped during rotations)
int32_t _dwidth, _dheight; // Real display width and height (for <8bpp Sprites)
int32_t _bitwidth; // Sprite image bit width for drawPixel (for <8bpp Sprites, not swapped)
};
#endif

View File

@@ -0,0 +1,874 @@
/*-
* MIT License
*
* Copyright (c) 2019 kikuchan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <rom/miniz.h>
#include "pngle.h"
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifdef PNGLE_DEBUG
#define debug_printf(...) fprintf(stderr, __VA_ARGS__)
#else
#define debug_printf(...) ((void)0)
#endif
#define PNGLE_ERROR(s) (pngle->error = (s), pngle->state = PNGLE_STATE_ERROR, -1)
#define PNGLE_CALLOC(a, b, name) (debug_printf("[pngle] Allocating %zu bytes for %s\n", (size_t)(a) * (size_t)(b), (name)), calloc((size_t)(a), (size_t)(b)))
#define PNGLE_UNUSED(x) (void)(x)
typedef enum {
PNGLE_STATE_ERROR = -2,
PNGLE_STATE_EOF = -1,
PNGLE_STATE_INITIAL = 0,
PNGLE_STATE_FIND_CHUNK_HEADER,
PNGLE_STATE_HANDLE_CHUNK,
PNGLE_STATE_CRC,
} pngle_state_t;
typedef enum {
// Supported chunks
// Filter chunk names by following command to (re)generate hex constants;
// % perl -ne 'chomp; s/.*\s*\/\/\s*//; print "\tPNGLE_CHUNK_$_ = 0x" . unpack("H*") . "UL, // $_\n";'
PNGLE_CHUNK_IHDR = 0x49484452UL, // IHDR
PNGLE_CHUNK_PLTE = 0x504c5445UL, // PLTE
PNGLE_CHUNK_IDAT = 0x49444154UL, // IDAT
PNGLE_CHUNK_IEND = 0x49454e44UL, // IEND
PNGLE_CHUNK_tRNS = 0x74524e53UL, // tRNS
PNGLE_CHUNK_gAMA = 0x67414d41UL, // gAMA
} pngle_chunk_t;
// typedef struct _pngle_t pngle_t; // declared in pngle.h
struct _pngle_t {
pngle_ihdr_t hdr;
uint_fast8_t channels; // 0 indicates IHDR hasn't been processed yet
// PLTE chunk
size_t n_palettes;
uint8_t *palette;
// tRNS chunk
size_t n_trans_palettes;
uint8_t *trans_palette;
// parser state (reset on every chunk header)
pngle_state_t state;
uint32_t chunk_type;
uint32_t chunk_remain;
mz_ulong crc32;
// decompression state (reset on IHDR)
tinfl_decompressor inflator; // 11000 bytes
uint8_t lz_buf[TINFL_LZ_DICT_SIZE]; // 32768 bytes
uint8_t *next_out; // NULL indicates IDAT hasn't been processed yet
size_t avail_out;
// scanline decoder (reset on every set_interlace_pass() call)
uint8_t *scanline_ringbuf;
size_t scanline_ringbuf_size;
size_t scanline_ringbuf_cidx;
int_fast8_t scanline_remain_bytes_to_render;
int_fast8_t filter_type;
uint32_t drawing_x;
uint32_t drawing_y;
// interlace
uint_fast8_t interlace_pass;
const char *error;
#ifndef PNGLE_NO_GAMMA_CORRECTION
uint8_t *gamma_table;
double display_gamma;
#endif
pngle_init_callback_t init_callback;
pngle_draw_callback_t draw_callback;
pngle_done_callback_t done_callback;
void *user_data;
};
// magic
static const uint8_t png_sig[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
static uint32_t interlace_off_x[8] = { 0, 0, 4, 0, 2, 0, 1, 0 };
static uint32_t interlace_off_y[8] = { 0, 0, 0, 4, 0, 2, 0, 1 };
static uint32_t interlace_div_x[8] = { 1, 8, 8, 4, 4, 2, 2, 1 };
static uint32_t interlace_div_y[8] = { 1, 8, 8, 8, 4, 4, 2, 2 };
static inline uint8_t read_uint8(const uint8_t *p)
{
return *p;
}
static inline uint32_t read_uint32(const uint8_t *p)
{
return (p[0] << 24)
| (p[1] << 16)
| (p[2] << 8)
| (p[3] << 0)
;
}
static inline uint32_t U32_CLAMP_ADD(uint32_t a, uint32_t b, uint32_t top)
{
uint32_t v = a + b;
if (v < a) return top; // uint32 overflow
if (v > top) return top; // clamp
return v;
}
void pngle_reset(pngle_t *pngle)
{
if (!pngle) return ;
pngle->state = PNGLE_STATE_INITIAL;
pngle->error = "No error";
if (pngle->scanline_ringbuf) free(pngle->scanline_ringbuf);
if (pngle->palette) free(pngle->palette);
if (pngle->trans_palette) free(pngle->trans_palette);
#ifndef PNGLE_NO_GAMMA_CORRECTION
if (pngle->gamma_table) free(pngle->gamma_table);
#endif
pngle->scanline_ringbuf = NULL;
pngle->palette = NULL;
pngle->trans_palette = NULL;
#ifndef PNGLE_NO_GAMMA_CORRECTION
pngle->gamma_table = NULL;
#endif
pngle->channels = 0; // indicates IHDR hasn't been processed yet
pngle->next_out = NULL; // indicates IDAT hasn't been processed yet
// clear them just in case...
memset(&pngle->hdr, 0, sizeof(pngle->hdr));
pngle->n_palettes = 0;
pngle->n_trans_palettes = 0;
tinfl_init(&pngle->inflator);
}
pngle_t *pngle_new()
{
pngle_t *pngle = (pngle_t *)PNGLE_CALLOC(1, sizeof(pngle_t), "pngle_t");
if (!pngle) return NULL;
pngle_reset(pngle);
return pngle;
}
void pngle_destroy(pngle_t *pngle)
{
if (pngle) {
pngle_reset(pngle);
free(pngle);
}
}
const char *pngle_error(pngle_t *pngle)
{
if (!pngle) return "Uninitialized";
return pngle->error;
}
uint32_t pngle_get_width(pngle_t *pngle)
{
if (!pngle) return 0;
return pngle->hdr.width;
}
uint32_t pngle_get_height(pngle_t *pngle)
{
if (!pngle) return 0;
return pngle->hdr.height;
}
pngle_ihdr_t *pngle_get_ihdr(pngle_t *pngle)
{
if (!pngle) return NULL;
if (pngle->channels == 0) return NULL;
return &pngle->hdr;
}
static int is_trans_color(pngle_t *pngle, uint16_t *value, size_t n)
{
if (pngle->n_trans_palettes != 1) return 0; // false (none or indexed)
for (size_t i = 0; i < n; i++) {
if (value[i] != (pngle->trans_palette[i * 2 + 0] * 0x100 + pngle->trans_palette[i * 2 + 1])) return 0; // false
}
return 1; // true
}
static inline void scanline_ringbuf_push(pngle_t *pngle, uint8_t value)
{
pngle->scanline_ringbuf[pngle->scanline_ringbuf_cidx] = value;
pngle->scanline_ringbuf_cidx = (pngle->scanline_ringbuf_cidx + 1) % pngle->scanline_ringbuf_size;
}
static inline uint16_t get_value(pngle_t *pngle, size_t *ridx, int *bitcount, int depth)
{
uint16_t v;
switch (depth) {
case 1:
case 2:
case 4:
if (*bitcount >= 8) {
*bitcount = 0;
*ridx = (*ridx + 1) % pngle->scanline_ringbuf_size;
}
*bitcount += depth;
uint8_t mask = ((1UL << depth) - 1);
uint8_t shift = (8 - *bitcount);
return (pngle->scanline_ringbuf[*ridx] >> shift) & mask;
case 8:
v = pngle->scanline_ringbuf[*ridx];
*ridx = (*ridx + 1) % pngle->scanline_ringbuf_size;
return v;
case 16:
v = pngle->scanline_ringbuf[*ridx];
*ridx = (*ridx + 1) % pngle->scanline_ringbuf_size;
v = v * 0x100 + pngle->scanline_ringbuf[*ridx];
*ridx = (*ridx + 1) % pngle->scanline_ringbuf_size;
return v;
}
return 0;
}
static int pngle_draw_pixels(pngle_t *pngle, size_t scanline_ringbuf_xidx)
{
uint16_t v[4]; // MAX_CHANNELS
int bitcount = 0;
uint8_t pixel_depth = (pngle->hdr.color_type & 1) ? 8 : pngle->hdr.depth;
uint16_t maxval = (1UL << pixel_depth) - 1;
int n_pixels = pngle->hdr.depth == 16 ? 1 : (8 / pngle->hdr.depth);
for (; n_pixels-- > 0 && pngle->drawing_x < pngle->hdr.width; pngle->drawing_x = U32_CLAMP_ADD(pngle->drawing_x, interlace_div_x[pngle->interlace_pass], pngle->hdr.width)) {
for (uint_fast8_t c = 0; c < pngle->channels; c++) {
v[c] = get_value(pngle, &scanline_ringbuf_xidx, &bitcount, pngle->hdr.depth);
}
// color type: 0000 0111
// ^-- indexed color (palette)
// ^--- Color
// ^---- Alpha channel
if (pngle->hdr.color_type & 2) {
// color
if (pngle->hdr.color_type & 1) {
// indexed color: type 3
// lookup palette info
uint16_t pidx = v[0];
if (pidx >= pngle->n_palettes) return PNGLE_ERROR("Color index is out of range");
v[0] = pngle->palette[pidx * 3 + 0];
v[1] = pngle->palette[pidx * 3 + 1];
v[2] = pngle->palette[pidx * 3 + 2];
// tRNS as an indexed alpha value table (for color type 3)
v[3] = pidx < pngle->n_trans_palettes ? pngle->trans_palette[pidx] : maxval;
} else {
// true color: 2, and 6
v[3] = (pngle->hdr.color_type & 4) ? v[3] : is_trans_color(pngle, v, 3) ? 0 : maxval;
}
} else {
// alpha, tRNS, or opaque
v[3] = (pngle->hdr.color_type & 4) ? v[1] : is_trans_color(pngle, v, 1) ? 0 : maxval;
// monochrome
v[1] = v[2] = v[0];
}
if (pngle->draw_callback) {
uint8_t rgba[4] = {
(v[0] * 255 + maxval / 2) / maxval,
(v[1] * 255 + maxval / 2) / maxval,
(v[2] * 255 + maxval / 2) / maxval,
(v[3] * 255 + maxval / 2) / maxval
};
#ifndef PNGLE_NO_GAMMA_CORRECTION
if (pngle->gamma_table) {
for (int i = 0; i < 3; i++) {
rgba[i] = pngle->gamma_table[v[i]];
}
}
#endif
pngle->draw_callback(pngle, pngle->drawing_x, pngle->drawing_y
, MIN(interlace_div_x[pngle->interlace_pass] - interlace_off_x[pngle->interlace_pass], pngle->hdr.width - pngle->drawing_x)
, MIN(interlace_div_y[pngle->interlace_pass] - interlace_off_y[pngle->interlace_pass], pngle->hdr.height - pngle->drawing_y)
, rgba
);
}
}
return 0;
}
static inline int paeth(int a, int b, int c)
{
int p = a + b - c;
int pa = abs(p - a);
int pb = abs(p - b);
int pc = abs(p - c);
if (pa <= pb && pa <= pc) return a;
if (pb <= pc) return b;
return c;
}
static int set_interlace_pass(pngle_t *pngle, uint_fast8_t pass)
{
pngle->interlace_pass = pass;
uint_fast8_t bytes_per_pixel = (pngle->channels * pngle->hdr.depth + 7) / 8; // 1 if depth <= 8
size_t scanline_pixels = (pngle->hdr.width - interlace_off_x[pngle->interlace_pass] + interlace_div_x[pngle->interlace_pass] - 1) / interlace_div_x[pngle->interlace_pass];
size_t scanline_stride = (scanline_pixels * pngle->channels * pngle->hdr.depth + 7) / 8;
pngle->scanline_ringbuf_size = scanline_stride + bytes_per_pixel * 2; // 2 rooms for c/x and a
if (pngle->scanline_ringbuf) free(pngle->scanline_ringbuf);
if ((pngle->scanline_ringbuf = PNGLE_CALLOC(pngle->scanline_ringbuf_size, 1, "scanline ringbuf")) == NULL) return PNGLE_ERROR("Insufficient memory");
pngle->drawing_x = interlace_off_x[pngle->interlace_pass];
pngle->drawing_y = interlace_off_y[pngle->interlace_pass];
pngle->filter_type = -1;
pngle->scanline_ringbuf_cidx = 0;
pngle->scanline_remain_bytes_to_render = -1;
return 0;
}
static int setup_gamma_table(pngle_t *pngle, uint32_t png_gamma)
{
#ifndef PNGLE_NO_GAMMA_CORRECTION
if (pngle->gamma_table) free(pngle->gamma_table);
if (pngle->display_gamma <= 0) return 0; // disable gamma correction
if (png_gamma == 0) return 0;
uint8_t pixel_depth = (pngle->hdr.color_type & 1) ? 8 : pngle->hdr.depth;
uint16_t maxval = (1UL << pixel_depth) - 1;
pngle->gamma_table = PNGLE_CALLOC(1, maxval + 1, "gamma table");
if (!pngle->gamma_table) return PNGLE_ERROR("Insufficient memory");
for (int i = 0; i < maxval + 1; i++) {
pngle->gamma_table[i] = (uint8_t)floor(pow(i / (double)maxval, 100000.0 / png_gamma / pngle->display_gamma) * 255.0 + 0.5);
}
debug_printf("[pngle] gamma value = %d\n", png_gamma);
#else
PNGLE_UNUSED(pngle);
PNGLE_UNUSED(png_gamma);
#endif
return 0;
}
static int pngle_on_data(pngle_t *pngle, const uint8_t *p, int len)
{
const uint8_t *ep = p + len;
uint_fast8_t bytes_per_pixel = (pngle->channels * pngle->hdr.depth + 7) / 8; // 1 if depth <= 8
while (p < ep) {
if (pngle->drawing_x >= pngle->hdr.width) {
// New row
pngle->drawing_x = interlace_off_x[pngle->interlace_pass];
pngle->drawing_y = U32_CLAMP_ADD(pngle->drawing_y, interlace_div_y[pngle->interlace_pass], pngle->hdr.height);
pngle->filter_type = -1; // Indicate new line
}
if (pngle->drawing_x >= pngle->hdr.width || pngle->drawing_y >= pngle->hdr.height) {
if (pngle->interlace_pass == 0 || pngle->interlace_pass >= 7) return len; // Do nothing further
// Interlace: Next pass
if (set_interlace_pass(pngle, pngle->interlace_pass + 1) < 0) return -1;
debug_printf("[pngle] interlace pass changed to: %d\n", pngle->interlace_pass);
continue; // This is required because "No filter type bytes are present in an empty pass".
}
if (pngle->filter_type < 0) {
if (*p > 4) {
debug_printf("[pngle] Invalid filter type is found; 0x%02x\n", *p);
return PNGLE_ERROR("Invalid filter type is found");
}
pngle->filter_type = (int_fast8_t)*p++; // 0 - 4
// push sentinel bytes for new line
for (uint_fast8_t i = 0; i < bytes_per_pixel; i++) {
scanline_ringbuf_push(pngle, 0);
}
continue;
}
size_t cidx = pngle->scanline_ringbuf_cidx;
size_t bidx = (pngle->scanline_ringbuf_cidx + bytes_per_pixel) % pngle->scanline_ringbuf_size;
size_t aidx = (pngle->scanline_ringbuf_cidx + pngle->scanline_ringbuf_size - bytes_per_pixel) % pngle->scanline_ringbuf_size;
// debug_printf("[pngle] cidx = %zd, bidx = %zd, aidx = %zd\n", cidx, bidx, aidx);
uint8_t c = pngle->scanline_ringbuf[cidx]; // left-up
uint8_t b = pngle->scanline_ringbuf[bidx]; // up
uint8_t a = pngle->scanline_ringbuf[aidx]; // left
uint8_t x = *p++; // target
// debug_printf("[pngle] c = 0x%02x, b = 0x%02x, a = 0x%02x, x = 0x%02x\n", c, b, a, x);
// Reverse the filter
switch (pngle->filter_type) {
case 0: break; // None
case 1: x += a; break; // Sub
case 2: x += b; break; // Up
case 3: x += (a + b) / 2; break; // Average
case 4: x += paeth(a, b, c); break; // Paeth
}
scanline_ringbuf_push(pngle, x); // updates scanline_ringbuf_cidx
if (pngle->scanline_remain_bytes_to_render < 0) pngle->scanline_remain_bytes_to_render = bytes_per_pixel;
if (--pngle->scanline_remain_bytes_to_render == 0) {
size_t xidx = (pngle->scanline_ringbuf_cidx + pngle->scanline_ringbuf_size - bytes_per_pixel) % pngle->scanline_ringbuf_size;
if (pngle_draw_pixels(pngle, xidx) < 0) return -1;
pngle->scanline_remain_bytes_to_render = -1; // reset
}
}
return len;
}
static int pngle_handle_chunk(pngle_t *pngle, const uint8_t *buf, size_t len)
{
size_t consume = 0;
switch (pngle->chunk_type) {
case PNGLE_CHUNK_IHDR:
// parse IHDR
consume = 13;
if (len < consume) return 0;
debug_printf("[pngle] Parse IHDR\n");
pngle->hdr.width = read_uint32(buf + 0);
pngle->hdr.height = read_uint32(buf + 4);
pngle->hdr.depth = read_uint8 (buf + 8);
pngle->hdr.color_type = read_uint8 (buf + 9);
pngle->hdr.compression = read_uint8 (buf + 10);
pngle->hdr.filter = read_uint8 (buf + 11);
pngle->hdr.interlace = read_uint8 (buf + 12);
debug_printf("[pngle] width : %d\n", pngle->hdr.width );
debug_printf("[pngle] height : %d\n", pngle->hdr.height );
debug_printf("[pngle] depth : %d\n", pngle->hdr.depth );
debug_printf("[pngle] color_type : %d\n", pngle->hdr.color_type );
debug_printf("[pngle] compression: %d\n", pngle->hdr.compression);
debug_printf("[pngle] filter : %d\n", pngle->hdr.filter );
debug_printf("[pngle] interlace : %d\n", pngle->hdr.interlace );
/*
Color Allowed Interpretation channels
Type Bit Depths
0 1,2,4,8,16 Each pixel is a grayscale sample. 1 channels (Brightness)
2 8,16 Each pixel is an R,G,B triple. 3 channels (R, G, B)
3 1,2,4,8 Each pixel is a palette index; 1 channels (palette info)
a PLTE chunk must appear.
4 8,16 Each pixel is a grayscale sample, 2 channels (Brightness, Alpha)
followed by an alpha sample.
6 8,16 Each pixel is an R,G,B triple, 4 channels (R, G, B, Alpha)
followed by an alpha sample.
*/
// 111
// ^-- indexed color (palette)
// ^--- Color
// ^---- Alpha channel
switch (pngle->hdr.color_type) {
case 0: pngle->channels = 1; if (pngle->hdr.depth != 1 && pngle->hdr.depth != 2 && pngle->hdr.depth != 4 && pngle->hdr.depth != 8 && pngle->hdr.depth != 16) return PNGLE_ERROR("Invalid bit depth"); break; // grayscale
case 2: pngle->channels = 3; if ( pngle->hdr.depth != 8 && pngle->hdr.depth != 16) return PNGLE_ERROR("Invalid bit depth"); break; // truecolor
case 3: pngle->channels = 1; if (pngle->hdr.depth != 1 && pngle->hdr.depth != 2 && pngle->hdr.depth != 4 && pngle->hdr.depth != 8 ) return PNGLE_ERROR("Invalid bit depth"); break; // indexed color
case 4: pngle->channels = 2; if ( pngle->hdr.depth != 8 && pngle->hdr.depth != 16) return PNGLE_ERROR("Invalid bit depth"); break; // grayscale + alpha
case 6: pngle->channels = 4; if ( pngle->hdr.depth != 8 && pngle->hdr.depth != 16) return PNGLE_ERROR("Invalid bit depth"); break; // truecolor + alpha
default:
return PNGLE_ERROR("Incorrect IHDR info");
}
if (pngle->hdr.compression != 0) return PNGLE_ERROR("Unsupported compression type in IHDR");
if (pngle->hdr.filter != 0) return PNGLE_ERROR("Unsupported filter type in IHDR");
// interlace
if (set_interlace_pass(pngle, pngle->hdr.interlace ? 1 : 0) < 0) return -1;
// callback
if (pngle->init_callback) pngle->init_callback(pngle, pngle->hdr.width, pngle->hdr.height);
break;
case PNGLE_CHUNK_IDAT:
// parse & decode IDAT chunk
if (len < 1) return 0;
debug_printf("[pngle] Reading IDAT (len %zd / chunk remain %u)\n", len, pngle->chunk_remain);
size_t in_bytes = len;
size_t out_bytes = pngle->avail_out;
//debug_printf("[pngle] in_bytes %zd, out_bytes %zd, next_out %p\n", in_bytes, out_bytes, pngle->next_out);
// XXX: tinfl_decompress always requires (next_out - lz_buf + avail_out) == TINFL_LZ_DICT_SIZE
tinfl_status status = tinfl_decompress(&pngle->inflator, (const mz_uint8 *)buf, &in_bytes, pngle->lz_buf, (mz_uint8 *)pngle->next_out, &out_bytes, TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_PARSE_ZLIB_HEADER);
//debug_printf("[pngle] tinfl_decompress\n");
//debug_printf("[pngle] => in_bytes %zd, out_bytes %zd, next_out %p, status %d\n", in_bytes, out_bytes, pngle->next_out, status);
if (status < TINFL_STATUS_DONE) {
// Decompression failed.
debug_printf("[pngle] tinfl_decompress() failed with status %d!\n", status);
return PNGLE_ERROR("Failed to decompress the IDAT stream");
}
pngle->next_out += out_bytes;
pngle->avail_out -= out_bytes;
// debug_printf("[pngle] => avail_out %zd, next_out %p\n", pngle->avail_out, pngle->next_out);
if (status == TINFL_STATUS_DONE || pngle->avail_out == 0) {
// Output buffer is full, or decompression is done, so write buffer to output file.
// XXX: This is the only chance to process the buffer.
uint8_t *read_ptr = pngle->lz_buf;
size_t n = TINFL_LZ_DICT_SIZE - (size_t)pngle->avail_out;
// pngle_on_data() usually returns n, otherwise -1 on error
if (pngle_on_data(pngle, read_ptr, n) < 0) return -1;
// XXX: tinfl_decompress always requires (next_out - lz_buf + avail_out) == TINFL_LZ_DICT_SIZE
pngle->next_out = pngle->lz_buf;
pngle->avail_out = TINFL_LZ_DICT_SIZE;
}
consume = in_bytes;
break;
case PNGLE_CHUNK_PLTE:
consume = 3;
if (len < consume) return 0;
memcpy(pngle->palette + pngle->n_palettes * 3, buf, 3);
debug_printf("[pngle] PLTE[%zd]: (%d, %d, %d)\n"
, pngle->n_palettes
, pngle->palette[pngle->n_palettes * 3 + 0]
, pngle->palette[pngle->n_palettes * 3 + 1]
, pngle->palette[pngle->n_palettes * 3 + 2]
);
pngle->n_palettes++;
break;
case PNGLE_CHUNK_IEND:
consume = 0;
break;
case PNGLE_CHUNK_tRNS:
switch (pngle->hdr.color_type) {
case 3: consume = 1; break;
case 0: consume = 2 * 1; break;
case 2: consume = 2 * 3; break;
default:
return PNGLE_ERROR("tRNS chunk is prohibited on the color type");
}
if (len < consume) return 0;
memcpy(pngle->trans_palette + pngle->n_trans_palettes, buf, consume);
pngle->n_trans_palettes++;
break;
case PNGLE_CHUNK_gAMA:
consume = 4;
if (len < consume) return 0;
if (setup_gamma_table(pngle, read_uint32(buf)) < 0) return -1;
break;
default:
// unknown chunk
consume = len;
debug_printf("[pngle] Unknown chunk; %zd bytes discarded\n", consume);
break;
}
return consume;
}
static int pngle_feed_internal(pngle_t *pngle, const uint8_t *buf, size_t len)
{
if (!pngle) return -1;
switch (pngle->state) {
case PNGLE_STATE_ERROR:
return -1;
case PNGLE_STATE_EOF:
return len;
case PNGLE_STATE_INITIAL:
// find PNG header
if (len < sizeof(png_sig)) return 0;
if (memcmp(png_sig, buf, sizeof(png_sig))) return PNGLE_ERROR("Incorrect PNG signature");
debug_printf("[pngle] PNG signature found\n");
pngle->state = PNGLE_STATE_FIND_CHUNK_HEADER;
return sizeof(png_sig);
case PNGLE_STATE_FIND_CHUNK_HEADER:
if (len < 8) return 0;
pngle->chunk_remain = read_uint32(buf);
pngle->chunk_type = read_uint32(buf + 4);
pngle->crc32 = mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)(buf + 4), 4);
debug_printf("[pngle] Chunk '%.4s' len %u\n", buf + 4, pngle->chunk_remain);
pngle->state = PNGLE_STATE_HANDLE_CHUNK;
// initialize & sanity check
switch (pngle->chunk_type) {
case PNGLE_CHUNK_IHDR:
if (pngle->chunk_remain != 13) return PNGLE_ERROR("Invalid IHDR chunk size");
if (pngle->channels != 0) return PNGLE_ERROR("Multiple IHDR chunks are not allowed");
break;
case PNGLE_CHUNK_IDAT:
if (pngle->chunk_remain <= 0) return PNGLE_ERROR("Invalid IDAT chunk size");
if (pngle->channels == 0) return PNGLE_ERROR("No IHDR chunk is found");
if (pngle->hdr.color_type == 3 && pngle->palette == NULL) return PNGLE_ERROR("No PLTE chunk is found");
if (pngle->next_out == NULL) {
// Very first IDAT
pngle->next_out = pngle->lz_buf;
pngle->avail_out = TINFL_LZ_DICT_SIZE;
}
break;
case PNGLE_CHUNK_PLTE:
if (pngle->chunk_remain <= 0) return PNGLE_ERROR("Invalid PLTE chunk size");
if (pngle->channels == 0) return PNGLE_ERROR("No IHDR chunk is found");
if (pngle->palette) return PNGLE_ERROR("Too many PLTE chunk");
switch (pngle->hdr.color_type) {
case 3: // indexed color
break;
case 2: // truecolor
case 6: // truecolor + alpha
// suggested palettes
break;
default:
return PNGLE_ERROR("PLTE chunk is prohibited on the color type");
}
if (pngle->chunk_remain % 3) return PNGLE_ERROR("Invalid PLTE chunk size");
if (pngle->chunk_remain / 3 > MIN(256, (1UL << pngle->hdr.depth))) return PNGLE_ERROR("Too many palettes in PLTE");
if ((pngle->palette = PNGLE_CALLOC(pngle->chunk_remain / 3, 3, "palette")) == NULL) return PNGLE_ERROR("Insufficient memory");
pngle->n_palettes = 0;
break;
case PNGLE_CHUNK_IEND:
if (pngle->next_out == NULL) return PNGLE_ERROR("No IDAT chunk is found");
if (pngle->chunk_remain > 0) return PNGLE_ERROR("Invalid IEND chunk size");
break;
case PNGLE_CHUNK_tRNS:
if (pngle->chunk_remain <= 0) return PNGLE_ERROR("Invalid tRNS chunk size");
if (pngle->channels == 0) return PNGLE_ERROR("No IHDR chunk is found");
if (pngle->trans_palette) return PNGLE_ERROR("Too many tRNS chunk");
switch (pngle->hdr.color_type) {
case 3: // indexed color
if (pngle->chunk_remain > (1UL << pngle->hdr.depth)) return PNGLE_ERROR("Too many palettes in tRNS");
break;
case 0: // grayscale
if (pngle->chunk_remain != 2) return PNGLE_ERROR("Invalid tRNS chunk size");
break;
case 2: // truecolor
if (pngle->chunk_remain != 6) return PNGLE_ERROR("Invalid tRNS chunk size");
break;
default:
return PNGLE_ERROR("tRNS chunk is prohibited on the color type");
}
if ((pngle->trans_palette = PNGLE_CALLOC(pngle->chunk_remain, 1, "trans palette")) == NULL) return PNGLE_ERROR("Insufficient memory");
pngle->n_trans_palettes = 0;
break;
default:
break;
}
return 8;
case PNGLE_STATE_HANDLE_CHUNK:
len = MIN(len, pngle->chunk_remain);
int consumed = pngle_handle_chunk(pngle, buf, len);
if (consumed > 0) {
if (pngle->chunk_remain < (uint32_t)consumed) return PNGLE_ERROR("Chunk data has been consumed too much");
pngle->chunk_remain -= consumed;
pngle->crc32 = mz_crc32(pngle->crc32, (const mz_uint8 *)buf, consumed);
}
if (pngle->chunk_remain <= 0) pngle->state = PNGLE_STATE_CRC;
return consumed;
case PNGLE_STATE_CRC:
if (len < 4) return 0;
uint32_t crc32 = read_uint32(buf);
if (crc32 != pngle->crc32) {
debug_printf("[pngle] CRC: %08x vs %08x => NG\n", crc32, (uint32_t)pngle->crc32);
return PNGLE_ERROR("CRC mismatch");
}
debug_printf("[pngle] CRC: %08x vs %08x => OK\n", crc32, (uint32_t)pngle->crc32);
pngle->state = PNGLE_STATE_FIND_CHUNK_HEADER;
// XXX:
if (pngle->chunk_type == PNGLE_CHUNK_IEND) {
pngle->state = PNGLE_STATE_EOF;
if (pngle->done_callback) pngle->done_callback(pngle);
debug_printf("[pngle] DONE\n");
}
return 4;
default:
break;
}
return PNGLE_ERROR("Invalid state");
}
int pngle_feed(pngle_t *pngle, const void *buf, size_t len)
{
size_t pos = 0;
pngle_state_t last_state = pngle->state;
while (pos < len) {
int r = pngle_feed_internal(pngle, (const uint8_t *)buf + pos, len - pos);
if (r < 0) return r; // error
if (r == 0 && last_state == pngle->state) break;
last_state = pngle->state;
pos += r;
}
return pos;
}
void pngle_set_display_gamma(pngle_t *pngle, double display_gamma)
{
if (!pngle) return ;
#ifndef PNGLE_NO_GAMMA_CORRECTION
pngle->display_gamma = display_gamma;
#else
PNGLE_UNUSED(display_gamma);
#endif
}
void pngle_set_init_callback(pngle_t *pngle, pngle_init_callback_t callback)
{
if (!pngle) return ;
pngle->init_callback = callback;
}
void pngle_set_draw_callback(pngle_t *pngle, pngle_draw_callback_t callback)
{
if (!pngle) return ;
pngle->draw_callback = callback;
}
void pngle_set_done_callback(pngle_t *pngle, pngle_done_callback_t callback)
{
if (!pngle) return ;
pngle->done_callback = callback;
}
void pngle_set_user_data(pngle_t *pngle, void *user_data)
{
if (!pngle) return ;
pngle->user_data = user_data;
}
void *pngle_get_user_data(pngle_t *pngle)
{
if (!pngle) return NULL;
return pngle->user_data;
}
/* vim: set ts=4 sw=4 noexpandtab: */

View File

@@ -0,0 +1,86 @@
/*-
* MIT License
*
* Copyright (c) 2019 kikuchan
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef __PNGLE_H__
#define __PNGLE_H__
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
// Main Pngle object
typedef struct _pngle_t pngle_t;
// Callback signatures
typedef void (*pngle_init_callback_t)(pngle_t *pngle, uint32_t w, uint32_t h);
typedef void (*pngle_draw_callback_t)(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t rgba[4]);
typedef void (*pngle_done_callback_t)(pngle_t *pngle);
// ----------------
// Basic interfaces
// ----------------
pngle_t *pngle_new();
void pngle_destroy(pngle_t *pngle);
void pngle_reset(pngle_t *pngle); // clear its internal state (not applied to pngle_set_* functions)
const char *pngle_error(pngle_t *pngle);
int pngle_feed(pngle_t *pngle, const void *buf, size_t len); // returns -1: On error, 0: Need more data, n: n bytes eaten
uint32_t pngle_get_width(pngle_t *pngle);
uint32_t pngle_get_height(pngle_t *pngle);
void pngle_set_init_callback(pngle_t *png, pngle_init_callback_t callback);
void pngle_set_draw_callback(pngle_t *png, pngle_draw_callback_t callback);
void pngle_set_done_callback(pngle_t *png, pngle_done_callback_t callback);
void pngle_set_display_gamma(pngle_t *pngle, double display_gamma); // enables gamma correction by specifying display gamma, typically 2.2. No effect when gAMA chunk is missing
void pngle_set_user_data(pngle_t *pngle, void *user_data);
void *pngle_get_user_data(pngle_t *pngle);
// ----------------
// Debug interfaces
// ----------------
typedef struct _pngle_ihdr_t {
uint32_t width;
uint32_t height;
uint8_t depth;
uint8_t color_type;
uint8_t compression;
uint8_t filter;
uint8_t interlace;
} pngle_ihdr_t;
// Get IHDR information
pngle_ihdr_t *pngle_get_ihdr(pngle_t *pngle);
#ifdef __cplusplus
}
#endif
#endif /* __PNGLE_H__ */

View File

@@ -0,0 +1,868 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 Richard Moore
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
* heavily inspired and compared against.
*
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
*/
#include "qrcode.h"
#include <stdlib.h>
#include <string.h>
//#pragma mark - Error Correction Lookup tables
#if LOCK_VERSION == 0
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
{ 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, // Medium
{ 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, // Low
{ 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, // High
{ 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, // Quartile
};
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
// Version: (note that index 0 is for padding, and is set to an illegal value)
// 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
{ 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
{ 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
{ 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
{ 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
};
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
// 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
// 32, 33, 34, 35, 36, 37, 38, 39, 40
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
};
// @TODO: Put other LOCK_VERSIONS here
#elif LOCK_VERSION == 3
static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = {
26, 15, 44, 36
};
static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = {
1, 1, 2, 2
};
static const uint16_t NUM_RAW_DATA_MODULES = 567;
#else
#error Unsupported LOCK_VERSION (add it...)
#endif
static int max(int a, int b) {
if (a > b) { return a; }
return b;
}
/*
static int abs(int value) {
if (value < 0) { return -value; }
return value;
}
*/
//#pragma mark - Mode testing and conversion
static int8_t getAlphanumeric(char c) {
if (c >= '0' && c <= '9') { return (c - '0'); }
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
switch (c) {
case ' ': return 36;
case '$': return 37;
case '%': return 38;
case '*': return 39;
case '+': return 40;
case '-': return 41;
case '.': return 42;
case '/': return 43;
case ':': return 44;
}
return -1;
}
static bool isAlphanumeric(const char *text, uint16_t length) {
while (length != 0) {
if (getAlphanumeric(text[--length]) == -1) { return false; }
}
return true;
}
static bool isNumeric(const char *text, uint16_t length) {
while (length != 0) {
char c = text[--length];
if (c < '0' || c > '9') { return false; }
}
return true;
}
//#pragma mark - Counting
// We store the following tightly packed (less 8) in modeInfo
// <=9 <=26 <= 40
// NUMERIC ( 10, 12, 14);
// ALPHANUMERIC ( 9, 11, 13);
// BYTE ( 8, 16, 16);
static char getModeBits(uint8_t version, uint8_t mode) {
// Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits
// hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2))
unsigned int modeInfo = 0x7bbb80a;
#if LOCK_VERSION == 0 || LOCK_VERSION > 9
if (version > 9) { modeInfo >>= 9; }
#endif
#if LOCK_VERSION == 0 || LOCK_VERSION > 26
if (version > 26) { modeInfo >>= 9; }
#endif
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
if (result == 15) { result = 16; }
return result;
}
//#pragma mark - BitBucket
typedef struct BitBucket {
uint32_t bitOffsetOrWidth;
uint16_t capacityBytes;
uint8_t *data;
} BitBucket;
/*
void bb_dump(BitBucket *bitBuffer) {
printf("Buffer: ");
for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) {
printf("%02x", bitBuffer->data[i]);
if ((i % 4) == 3) { printf(" "); }
}
printf("\n");
}
*/
static uint16_t bb_getGridSizeBytes(uint8_t size) {
return (((size * size) + 7) / 8);
}
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
return ((bits + 7) / 8);
}
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
bitBuffer->bitOffsetOrWidth = 0;
bitBuffer->capacityBytes = capacityBytes;
bitBuffer->data = data;
memset(data, 0, bitBuffer->capacityBytes);
}
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
bitGrid->bitOffsetOrWidth = size;
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
bitGrid->data = data;
memset(data, 0, bitGrid->capacityBytes);
}
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
uint32_t offset = bitBuffer->bitOffsetOrWidth;
for (int8_t i = length - 1; i >= 0; i--, offset++) {
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
}
bitBuffer->bitOffsetOrWidth = offset;
}
/*
void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) {
for (int8_t i = length - 1; i >= 0; i--, offset++) {
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
}
}
*/
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
uint8_t mask = 1 << (7 - (offset & 0x07));
if (on) {
bitGrid->data[offset >> 3] |= mask;
} else {
bitGrid->data[offset >> 3] &= ~mask;
}
}
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
uint8_t mask = 1 << (7 - (offset & 0x07));
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
if (on ^ invert) {
bitGrid->data[offset >> 3] |= mask;
} else {
bitGrid->data[offset >> 3] &= ~mask;
}
}
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
}
//#pragma mark - Drawing Patterns
// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical
// properties, calling applyMask(m) twice with the same value is equivalent to no change at all.
// This means it is possible to apply a mask, undo it, and try another mask. Note that a final
// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.).
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t y = 0; y < size; y++) {
for (uint8_t x = 0; x < size; x++) {
if (bb_getBit(isFunction, x, y)) { continue; }
bool invert = 0;
switch (mask) {
case 0: invert = (x + y) % 2 == 0; break;
case 1: invert = y % 2 == 0; break;
case 2: invert = x % 3 == 0; break;
case 3: invert = (x + y) % 3 == 0; break;
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
}
bb_invertBit(modules, x, y, invert);
}
}
}
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
bb_setBit(modules, x, y, on);
bb_setBit(isFunction, x, y, true);
}
// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y).
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
uint8_t size = modules->bitOffsetOrWidth;
for (int8_t i = -4; i <= 4; i++) {
for (int8_t j = -4; j <= 4; j++) {
uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm
int16_t xx = x + j, yy = y + i;
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
}
}
}
}
// Draws a 5*5 alignment pattern, with the center module at (x, y).
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
for (int8_t i = -2; i <= 2; i++) {
for (int8_t j = -2; j <= 2; j++) {
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
}
}
}
// Draws two copies of the format bits (with its own error correction code)
// based on the given mask and this object's error correction level field.
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
uint8_t size = modules->bitOffsetOrWidth;
// Calculate error correction code and pack bits
uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3
uint32_t rem = data;
for (int i = 0; i < 10; i++) {
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
}
data = data << 10 | rem;
data ^= 0x5412; // uint15
// Draw first copy
for (uint8_t i = 0; i <= 5; i++) {
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
}
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
for (int8_t i = 9; i < 15; i++) {
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
}
// Draw second copy
for (int8_t i = 0; i <= 7; i++) {
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
}
for (int8_t i = 8; i < 15; i++) {
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
}
setFunctionModule(modules, isFunction, 8, size - 8, true);
}
// Draws two copies of the version bits (with its own error correction code),
// based on this object's version field (which only has an effect for 7 <= version <= 40).
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
int8_t size = modules->bitOffsetOrWidth;
#if LOCK_VERSION != 0 && LOCK_VERSION < 7
return;
#else
if (version < 7) { return; }
// Calculate error correction code and pack bits
uint32_t rem = version; // version is uint6, in the range [7, 40]
for (uint8_t i = 0; i < 12; i++) {
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
}
uint32_t data = version << 12 | rem; // uint18
// Draw two copies
for (uint8_t i = 0; i < 18; i++) {
bool bit = ((data >> i) & 1) != 0;
uint8_t a = size - 11 + i % 3, b = i / 3;
setFunctionModule(modules, isFunction, a, b, bit);
setFunctionModule(modules, isFunction, b, a, bit);
}
#endif
}
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
uint8_t size = modules->bitOffsetOrWidth;
// Draw the horizontal and vertical timing patterns
for (uint8_t i = 0; i < size; i++) {
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
}
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
drawFinderPattern(modules, isFunction, 3, 3);
drawFinderPattern(modules, isFunction, size - 4, 3);
drawFinderPattern(modules, isFunction, 3, size - 4);
#if LOCK_VERSION == 0 || LOCK_VERSION > 1
if (version > 1) {
// Draw the numerous alignment patterns
uint8_t alignCount = version / 7 + 2;
uint8_t step;
if (version != 32) {
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2
} else { // C-C-C-Combo breaker!
step = 26;
}
uint8_t alignPositionIndex = alignCount - 1;
uint8_t alignPosition[alignCount];
alignPosition[0] = 6;
uint8_t size = version * 4 + 17;
for (uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) {
alignPosition[alignPositionIndex--] = pos;
}
for (uint8_t i = 0; i < alignCount; i++) {
for (uint8_t j = 0; j < alignCount; j++) {
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
continue; // Skip the three finder corners
} else {
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
}
}
}
}
#endif
// Draw configuration data
drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor
drawVersion(modules, isFunction, version);
}
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
// data area of this QR Code symbol. Function modules need to be marked off before this is called.
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
uint32_t bitLength = codewords->bitOffsetOrWidth;
uint8_t *data = codewords->data;
uint8_t size = modules->bitOffsetOrWidth;
// Bit index into the data
uint32_t i = 0;
// Do the funny zigzag scan
for (int16_t right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
if (right == 6) {
right = 5;
}
for (uint8_t vert = 0; vert < size; vert++) { // Vertical counter
for (int j = 0; j < 2; j++) {
uint8_t x = right - j; // Actual x coordinate
bool upwards = ((right & 2) == 0) ^ (x < 6);
uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
i++;
}
// If there are any remainder bits (0 to 7), they are already
// set to 0/false/white when the grid of modules was initialized
}
}
}
}
//#pragma mark - Penalty Calculation
#define PENALTY_N1 3
#define PENALTY_N2 3
#define PENALTY_N3 40
#define PENALTY_N4 10
// Calculates and returns the penalty score based on state of this QR Code's current modules.
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
// @TODO: This can be optimized by working with the bytes instead of bits.
static uint32_t getPenaltyScore(BitBucket *modules) {
uint32_t result = 0;
uint8_t size = modules->bitOffsetOrWidth;
// Adjacent modules in row having same color
for (uint8_t y = 0; y < size; y++) {
bool colorX = bb_getBit(modules, 0, y);
for (uint8_t x = 1, runX = 1; x < size; x++) {
bool cx = bb_getBit(modules, x, y);
if (cx != colorX) {
colorX = cx;
runX = 1;
} else {
runX++;
if (runX == 5) {
result += PENALTY_N1;
} else if (runX > 5) {
result++;
}
}
}
}
// Adjacent modules in column having same color
for (uint8_t x = 0; x < size; x++) {
bool colorY = bb_getBit(modules, x, 0);
for (uint8_t y = 1, runY = 1; y < size; y++) {
bool cy = bb_getBit(modules, x, y);
if (cy != colorY) {
colorY = cy;
runY = 1;
} else {
runY++;
if (runY == 5) {
result += PENALTY_N1;
} else if (runY > 5) {
result++;
}
}
}
}
uint16_t black = 0;
for (uint8_t y = 0; y < size; y++) {
uint16_t bitsRow = 0, bitsCol = 0;
for (uint8_t x = 0; x < size; x++) {
bool color = bb_getBit(modules, x, y);
// 2*2 blocks of modules having same color
if (x > 0 && y > 0) {
bool colorUL = bb_getBit(modules, x - 1, y - 1);
bool colorUR = bb_getBit(modules, x, y - 1);
bool colorL = bb_getBit(modules, x - 1, y);
if (color == colorUL && color == colorUR && color == colorL) {
result += PENALTY_N2;
}
}
// Finder-like pattern in rows and columns
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
// Needs 11 bits accumulated
if (x >= 10) {
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
result += PENALTY_N3;
}
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
result += PENALTY_N3;
}
}
// Balance of black and white modules
if (color) {
black++;
}
}
}
// Find smallest k such that (45-5k)% <= dark/total <= (55+5k)%
uint16_t total = size * size;
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
result += PENALTY_N4;
}
return result;
}
//#pragma mark - Reed-Solomon Generator
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
// Russian peasant multiplication
// See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication
uint16_t z = 0;
for (int8_t i = 7; i >= 0; i--) {
z = (z << 1) ^ ((z >> 7) * 0x11D);
z ^= ((y >> i) & 1) * x;
}
return z;
}
static void rs_init(uint8_t degree, uint8_t *coeff) {
memset(coeff, 0, degree);
coeff[degree - 1] = 1;
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
// drop the highest term, and store the rest of the coefficients in order of descending powers.
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
uint16_t root = 1;
for (uint8_t i = 0; i < degree; i++) {
// Multiply the current product by (x - r^i)
for (uint8_t j = 0; j < degree; j++) {
coeff[j] = rs_multiply(coeff[j], root);
if (j + 1 < degree) {
coeff[j] ^= coeff[j + 1];
}
}
root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D)
}
}
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
// Compute the remainder by performing polynomial division
//for (uint8_t i = 0; i < degree; i++) { result[] = 0; }
//memset(result, 0, degree);
for (uint8_t i = 0; i < length; i++) {
uint8_t factor = data[i] ^ result[0];
for (uint8_t j = 1; j < degree; j++) {
result[(j - 1) * stride] = result[j * stride];
}
result[(degree - 1) * stride] = 0;
for (uint8_t j = 0; j < degree; j++) {
result[j * stride] ^= rs_multiply(coeff[j], factor);
}
}
}
//#pragma mark - QrCode
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
int8_t mode = MODE_BYTE;
if (isNumeric((char*)text, length)) {
mode = MODE_NUMERIC;
bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC));
uint16_t accumData = 0;
uint8_t accumCount = 0;
for (uint16_t i = 0; i < length; i++) {
accumData = accumData * 10 + ((char)(text[i]) - '0');
accumCount++;
if (accumCount == 3) {
bb_appendBits(dataCodewords, accumData, 10);
accumData = 0;
accumCount = 0;
}
}
// 1 or 2 digits remaining
if (accumCount > 0) {
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
}
} else if (isAlphanumeric((char*)text, length)) {
mode = MODE_ALPHANUMERIC;
bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC));
uint16_t accumData = 0;
uint8_t accumCount = 0;
for (uint16_t i = 0; i < length; i++) {
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
accumCount++;
if (accumCount == 2) {
bb_appendBits(dataCodewords, accumData, 11);
accumData = 0;
accumCount = 0;
}
}
// 1 character remaining
if (accumCount > 0) {
bb_appendBits(dataCodewords, accumData, 6);
}
} else {
bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE));
for (uint16_t i = 0; i < length; i++) {
bb_appendBits(dataCodewords, (char)(text[i]), 8);
}
}
//bb_setBits(dataCodewords, length, 4, getModeBits(version, mode));
return mode;
}
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
// See: http://www.thonky.com/qr-code-tutorial/structure-final-message
#if LOCK_VERSION == 0
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
#else
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc];
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc];
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
#endif
uint8_t blockEccLen = totalEcc / numBlocks;
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
uint8_t result[data->capacityBytes];
memset(result, 0, sizeof(result));
uint8_t coeff[blockEccLen];
rs_init(blockEccLen, coeff);
uint16_t offset = 0;
uint8_t *dataBytes = data->data;
// Interleave all short blocks
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
uint16_t index = i;
uint8_t stride = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
result[offset++] = dataBytes[index];
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
if (blockNum == numShortBlocks) { stride++; }
#endif
index += stride;
}
}
// Version less than 5 only have short blocks
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
{
// Interleave long blocks
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
uint8_t stride = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
result[offset++] = dataBytes[index];
if (blockNum == 0) {
stride++;
}
index += stride;
}
}
#endif
// Add all ecc blocks, interleaved
uint8_t blockSize = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
if (blockNum == numShortBlocks) {
blockSize++;
}
#endif
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
dataBytes += blockSize;
}
memcpy(data->data, result, data->capacityBytes);
data->bitOffsetOrWidth = moduleCount;
}
// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits)
// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc)
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
//#pragma mark - Public QRCode functions
uint16_t qrcode_getBufferSize(uint8_t version) {
return bb_getGridSizeBytes(4 * version + 17);
}
// @TODO: Return error if data is too big.
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
uint8_t size = version * 4 + 17;
qrcode->version = version;
qrcode->size = size;
qrcode->ecc = ecc;
qrcode->modules = modules;
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
#if LOCK_VERSION == 0
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
#else
version = LOCK_VERSION;
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits];
#endif
struct BitBucket codewords;
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
// Place the data code words into the buffer
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
if (mode < 0) {
return -1;
}
qrcode->mode = mode;
// Add terminator and pad up to a byte if applicable
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
if (padding > 4) {
padding = 4;
}
bb_appendBits(&codewords, 0, padding);
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
// Pad with alternate bytes until data capacity is reached
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
bb_appendBits(&codewords, padByte, 8);
}
BitBucket modulesGrid;
bb_initGrid(&modulesGrid, modules, size);
BitBucket isFunctionGrid;
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
// Draw function patterns, draw all codewords, do masking
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
performErrorCorrection(version, eccFormatBits, &codewords);
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
// Find the best (lowest penalty) mask
uint8_t mask = 0;
int32_t minPenalty = INT32_MAX;
for (uint8_t i = 0; i < 8; i++) {
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
applyMask(&modulesGrid, &isFunctionGrid, i);
int penalty = getPenaltyScore(&modulesGrid);
if (penalty < minPenalty) {
mask = i;
minPenalty = penalty;
}
applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR
}
qrcode->mask = mask;
// Overwrite old format bits
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
// Apply the final choice of mask
applyMask(&modulesGrid, &isFunctionGrid, mask);
return 0;
}
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
}
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
if (x >= qrcode->size || y >= qrcode->size) {
return false;
}
uint32_t offset = y * qrcode->size + x;
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
}
/*
uint8_t qrcode_getHexLength(QRCode *qrcode) {
return ((qrcode->size * qrcode->size) + 7) / 4;
}
void qrcode_getHex(QRCode *qrcode, char *result) {
}
*/

View File

@@ -0,0 +1,95 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2017 Richard Moore
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
* heavily inspired and compared against.
*
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
*/
#ifndef __QRCODE_H_
#define __QRCODE_H_
#ifndef __cplusplus
typedef unsigned char bool;
static const bool false = 0;
static const bool true = 1;
#endif
#include <stdint.h>
// QR Code Format Encoding
#define MODE_NUMERIC 0
#define MODE_ALPHANUMERIC 1
#define MODE_BYTE 2
// Error Correction Code Levels
#define ECC_LOW 0
#define ECC_MEDIUM 1
#define ECC_QUARTILE 2
#define ECC_HIGH 3
// If set to non-zero, this library can ONLY produce QR codes at that version
// This saves a lot of dynamic memory, as the codeword tables are skipped
#ifndef LOCK_VERSION
#define LOCK_VERSION 0
#endif
typedef struct QRCode {
uint8_t version;
uint8_t size;
uint8_t ecc;
uint8_t mode;
uint8_t mask;
uint8_t *modules;
} QRCode;
#ifdef __cplusplus
extern "C"{
#endif /* __cplusplus */
uint16_t qrcode_getBufferSize(uint8_t version);
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __QRCODE_H_ */

View File

@@ -0,0 +1,239 @@
// Implementation of Sebastian Madgwick's "...efficient orientation filter
// for... inertial/magnetic sensor arrays"
// (see http://www.x-io.co.uk/category/open-source/ for examples & more details)
// which fuses acceleration, rotation rate, and magnetic moments to produce a
// quaternion-based estimate of absolute device orientation -- which can be
// converted to yaw, pitch, and roll. Useful for stabilizing quadcopters, etc.
// The performance of the orientation filter is at least as good as conventional
// Kalman-based filtering algorithms but is much less computationally
// intensive---it can be performed on a 3.3 V Pro Mini operating at 8 MHz!
#include "quaternionFilters.h"
// These are the free parameters in the Mahony filter and fusion scheme, Kp
// for proportional feedback, Ki for integral
#define Kp 2.0f * 5.0f
#define Ki 0.0f
static float GyroMeasError = PI * (40.0f / 180.0f);
// gyroscope measurement drift in rad/s/s (start at 0.0 deg/s/s)
// static float GyroMeasDrift = PI * (0.0f / 180.0f);
// There is a tradeoff in the beta parameter between accuracy and response
// speed. In the original Madgwick study, beta of 0.041 (corresponding to
// GyroMeasError of 2.7 degrees/s) was found to give optimal accuracy.
// However, with this value, the LSM9SD0 response time is about 10 seconds
// to a stable initial quaternion. Subsequent changes also require a
// longish lag time to a stable output, not fast enough for a quadcopter or
// robot car! By increasing beta (GyroMeasError) by about a factor of
// fifteen, the response time constant is reduced to ~2 sec. I haven't
// noticed any reduction in solution accuracy. This is essentially the I
// coefficient in a PID control sense; the bigger the feedback coefficient,
// the faster the solution converges, usually at the expense of accuracy.
// In any case, this is the free parameter in the Madgwick filtering and
// fusion scheme.
static float beta = sqrt(3.0f / 4.0f) * GyroMeasError; // Compute beta
// Compute zeta, the other free parameter in the Madgwick scheme usually
// set to a small or zero value
// static float zeta = sqrt(3.0f / 4.0f) * GyroMeasDrift;
// Vector to hold integral error for Mahony method
static float eInt[3] = { 0.0f, 0.0f, 0.0f };
// Vector to hold quaternion
static float q[4] = { 1.0f, 0.0f, 0.0f, 0.0f };
void MadgwickQuaternionUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float deltat){
// short name local variable for readability
float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];
float norm;
float hx, hy, _2bx, _2bz;
float s1, s2, s3, s4;
float qDot1, qDot2, qDot3, qDot4;
// Auxiliary variables to avoid repeated arithmetic
float _2q1mx;
float _2q1my;
float _2q1mz;
float _2q2mx;
float _4bx;
float _4bz;
float _2q1 = 2.0f * q1;
float _2q2 = 2.0f * q2;
float _2q3 = 2.0f * q3;
float _2q4 = 2.0f * q4;
float _2q1q3 = 2.0f * q1 * q3;
float _2q3q4 = 2.0f * q3 * q4;
float q1q1 = q1 * q1;
float q1q2 = q1 * q2;
float q1q3 = q1 * q3;
float q1q4 = q1 * q4;
float q2q2 = q2 * q2;
float q2q3 = q2 * q3;
float q2q4 = q2 * q4;
float q3q3 = q3 * q3;
float q3q4 = q3 * q4;
float q4q4 = q4 * q4;
// Normalise accelerometer measurement
norm = sqrt(ax * ax + ay * ay + az * az);
if (norm == 0.0f) {
return; // handle NaN
}
norm = 1.0f / norm;
ax *= norm;
ay *= norm;
az *= norm;
// Normalise magnetometer measurement
norm = sqrt(mx * mx + my * my + mz * mz);
if (norm == 0.0f) {
return; // handle NaN
}
norm = 1.0f / norm;
mx *= norm;
my *= norm;
mz *= norm;
// Reference direction of Earth's magnetic field
_2q1mx = 2.0f * q1 * mx;
_2q1my = 2.0f * q1 * my;
_2q1mz = 2.0f * q1 * mz;
_2q2mx = 2.0f * q2 * mx;
hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 +
_2q2 * mz * q4 - mx * q3q3 - mx * q4q4;
hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4;
_2bx = sqrt(hx * hx + hy * hy);
_2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4;
_4bx = 2.0f * _2bx;
_4bz = 2.0f * _2bz;
// Gradient decent algorithm corrective step
s1 = -_2q3 * (2.0f * q2q4 - _2q1q3 - ax) + _2q2 * (2.0f * q1q2 + _2q3q4 - ay) - _2bz * q3 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
s2 = _2q4 * (2.0f * q2q4 - _2q1q3 - ax) + _2q1 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q2 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + _2bz * q4 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
s3 = -_2q1 * (2.0f * q2q4 - _2q1q3 - ax) + _2q4 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q3 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
s4 = _2q2 * (2.0f * q2q4 - _2q1q3 - ax) + _2q3 * (2.0f * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
norm = sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4); // normalise step magnitude
norm = 1.0f / norm;
s1 *= norm;
s2 *= norm;
s3 *= norm;
s4 *= norm;
// Compute rate of change of quaternion
qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz) - beta * s1;
qDot2 = 0.5f * (q1 * gx + q3 * gz - q4 * gy) - beta * s2;
qDot3 = 0.5f * (q1 * gy - q2 * gz + q4 * gx) - beta * s3;
qDot4 = 0.5f * (q1 * gz + q2 * gy - q3 * gx) - beta * s4;
// Integrate to yield quaternion
q1 += qDot1 * deltat;
q2 += qDot2 * deltat;
q3 += qDot3 * deltat;
q4 += qDot4 * deltat;
norm = sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4); // normalise quaternion
norm = 1.0f / norm;
q[0] = q1 * norm;
q[1] = q2 * norm;
q[2] = q3 * norm;
q[3] = q4 * norm;
}
// Similar to Madgwick scheme but uses proportional and integral filtering on
// the error between estimated reference vectors and measured ones.
void MahonyQuaternionUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float deltat){
// short name local variable for readability
float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];
float norm;
float hx, hy, bx, bz;
float vx, vy, vz, wx, wy, wz;
float ex, ey, ez;
float pa, pb, pc;
// Auxiliary variables to avoid repeated arithmetic
float q1q1 = q1 * q1;
float q1q2 = q1 * q2;
float q1q3 = q1 * q3;
float q1q4 = q1 * q4;
float q2q2 = q2 * q2;
float q2q3 = q2 * q3;
float q2q4 = q2 * q4;
float q3q3 = q3 * q3;
float q3q4 = q3 * q4;
float q4q4 = q4 * q4;
// Normalise accelerometer measurement
norm = sqrt(ax * ax + ay * ay + az * az);
if (norm == 0.0f) {
return; // Handle NaN
}
norm = 1.0f / norm; // Use reciprocal for division
ax *= norm;
ay *= norm;
az *= norm;
// Normalise magnetometer measurement
norm = sqrt(mx * mx + my * my + mz * mz);
if (norm == 0.0f) {
return; // Handle NaN
}
norm = 1.0f / norm; // Use reciprocal for division
mx *= norm;
my *= norm;
mz *= norm;
// Reference direction of Earth's magnetic field
hx = 2.0f * mx * (0.5f - q3q3 - q4q4) + 2.0f * my * (q2q3 - q1q4) + 2.0f * mz * (q2q4 + q1q3);
hy = 2.0f * mx * (q2q3 + q1q4) + 2.0f * my * (0.5f - q2q2 - q4q4) + 2.0f * mz * (q3q4 - q1q2);
bx = sqrt((hx * hx) + (hy * hy));
bz = 2.0f * mx * (q2q4 - q1q3) + 2.0f * my * (q3q4 + q1q2) + 2.0f * mz * (0.5f - q2q2 - q3q3);
// Estimated direction of gravity and magnetic field
vx = 2.0f * (q2q4 - q1q3);
vy = 2.0f * (q1q2 + q3q4);
vz = q1q1 - q2q2 - q3q3 + q4q4;
wx = 2.0f * bx * (0.5f - q3q3 - q4q4) + 2.0f * bz * (q2q4 - q1q3);
wy = 2.0f * bx * (q2q3 - q1q4) + 2.0f * bz * (q1q2 + q3q4);
wz = 2.0f * bx * (q1q3 + q2q4) + 2.0f * bz * (0.5f - q2q2 - q3q3);
// Error is cross product between estimated direction and measured direction of gravity
ex = (ay * vz - az * vy) + (my * wz - mz * wy);
ey = (az * vx - ax * vz) + (mz * wx - mx * wz);
ez = (ax * vy - ay * vx) + (mx * wy - my * wx);
if (Ki > 0.0f){
eInt[0] += ex; // accumulate integral error
eInt[1] += ey;
eInt[2] += ez;
}else{
eInt[0] = 0.0f; // prevent integral wind up
eInt[1] = 0.0f;
eInt[2] = 0.0f;
}
// Apply feedback terms
gx = gx + Kp * ex + Ki * eInt[0];
gy = gy + Kp * ey + Ki * eInt[1];
gz = gz + Kp * ez + Ki * eInt[2];
// Integrate rate of change of quaternion
pa = q2;
pb = q3;
pc = q4;
q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * deltat);
q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * deltat);
q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * deltat);
q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * deltat);
// Normalise quaternion
norm = sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);
norm = 1.0f / norm;
q[0] = q1 * norm;
q[1] = q2 * norm;
q[2] = q3 * norm;
q[3] = q4 * norm;
}
const float * getQ () {
return q;
}

View File

@@ -0,0 +1,14 @@
#ifndef _QUATERNIONFILTERS_H_
#define _QUATERNIONFILTERS_H_
#include <Arduino.h>
void MadgwickQuaternionUpdate(float ax, float ay, float az, float gx, float gy,
float gz, float mx, float my, float mz,
float deltat);
void MahonyQuaternionUpdate(float ax, float ay, float az, float gx, float gy,
float gz, float mx, float my, float mz,
float deltat);
const float * getQ();
#endif // _QUATERNIONFILTERS_H_