feat: 全量同步 254 个常用的 Arduino 扩展库文件
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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(); }
|
||||
@@ -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_ */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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, ®data);
|
||||
delay(10);
|
||||
|
||||
regdata = (0x01<<7);
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_PWR_MGMT_1, 1, ®data);
|
||||
delay(10);
|
||||
|
||||
regdata = (0x01<<0);
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_PWR_MGMT_1, 1, ®data);
|
||||
delay(10);
|
||||
|
||||
regdata = 0x10;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_ACCEL_CONFIG, 1, ®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x18;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_GYRO_CONFIG, 1, ®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x01;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_CONFIG, 1, ®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x05;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_SMPLRT_DIV, 1,®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x00;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_ENABLE, 1, ®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x00;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_ACCEL_CONFIG2, 1, ®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x00;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_USER_CTRL, 1, ®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x00;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_FIFO_EN, 1, ®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x22;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_PIN_CFG, 1, ®data);
|
||||
delay(1);
|
||||
|
||||
regdata = 0x01;
|
||||
I2C_Write_NBytes(MPU6886_ADDRESS, MPU6886_INT_ENABLE, 1, ®data);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//!俯仰,航向,横滚:pitch,yaw,roll,指三维空间中飞行器的旋转状态。
|
||||
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, ®data);
|
||||
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, ®data);
|
||||
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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
//====================================================================================================
|
||||
@@ -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
|
||||
//=====================================================================================================
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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: */
|
||||
@@ -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__ */
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
*/
|
||||
@@ -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_ */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user