Initial commit
This commit is contained in:
5
espmusicmouse/.gitignore
vendored
Normal file
5
espmusicmouse/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
7
espmusicmouse/.vscode/extensions.json
vendored
Normal file
7
espmusicmouse/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
]
|
||||||
|
}
|
||||||
11
espmusicmouse/.vscode/settings.json
vendored
Normal file
11
espmusicmouse/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"*.tcc": "cpp",
|
||||||
|
"deque": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"unordered_map": "cpp",
|
||||||
|
"unordered_set": "cpp",
|
||||||
|
"vector": "cpp",
|
||||||
|
"system_error": "cpp"
|
||||||
|
}
|
||||||
|
}
|
||||||
11
espmusicmouse/doc.md
Normal file
11
espmusicmouse/doc.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Connections
|
||||||
|
-----------
|
||||||
|
|
||||||
|
- SDA purple 23
|
||||||
|
- SCK green 22
|
||||||
|
- MOSI orange 18
|
||||||
|
- MISO yellow 19
|
||||||
|
- IRQ grey
|
||||||
|
- GND black
|
||||||
|
- RST blue 5
|
||||||
|
- 3.3V red
|
||||||
48
espmusicmouse/lib/leds/LedControl.cpp
Normal file
48
espmusicmouse/lib/leds/LedControl.cpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include "LedControl.h"
|
||||||
|
#include "driver/rmt.h"
|
||||||
|
#include "esp32_digital_led_lib.h"
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
LedStrip::LedStrip(int numLeds, int pin)
|
||||||
|
: numLeds_(numLeds), pin_(pin)
|
||||||
|
{
|
||||||
|
cfg = {.rmtChannel = 0, .gpioNum = pin, .ledType = LED_SK6812W_V1, .brightLimit = 24, .numPixels = numLeds};
|
||||||
|
strands[0] = &cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LedStrip::begin()
|
||||||
|
{
|
||||||
|
digitalLeds_initDriver();
|
||||||
|
gpioSetup(cfg.gpioNum, OUTPUT, LOW);
|
||||||
|
|
||||||
|
int rc = digitalLeds_addStrands(strands, 1);
|
||||||
|
if (rc)
|
||||||
|
Serial.println("LEDs: Error during init");
|
||||||
|
}
|
||||||
|
|
||||||
|
void LedStrip::clear()
|
||||||
|
{
|
||||||
|
digitalLeds_resetPixels(strands, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LedStrip::setColor(int led, int r, int g, int b, int w)
|
||||||
|
{
|
||||||
|
strands[0]->pixels[led] = pixelFromRGBW(r, g, b, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LedStrip::transmit()
|
||||||
|
{
|
||||||
|
digitalLeds_drawPixels(strands, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LedStrip::setAll(int r, int g, int b, int w)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numLeds_; ++i)
|
||||||
|
setColor(i, r, g, b, w);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LedStrip::setRange(int begin, int end, int r, int g, int b, int w)
|
||||||
|
{
|
||||||
|
for (int i = begin; i < min(end, numLeds_); ++i)
|
||||||
|
setColor(i, r, g, b, w);
|
||||||
|
}
|
||||||
22
espmusicmouse/lib/leds/LedControl.h
Normal file
22
espmusicmouse/lib/leds/LedControl.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#include "esp32_digital_led_lib.h"
|
||||||
|
|
||||||
|
|
||||||
|
class LedStrip
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LedStrip(int numLeds, int pin);
|
||||||
|
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
void setColor(int led, int r, int g, int b, int w);
|
||||||
|
void transmit();
|
||||||
|
void clear();
|
||||||
|
void setAll(int r, int g, int b, int w);
|
||||||
|
void setRange(int begin, int end, int r, int g, int b, int w);
|
||||||
|
int numLeds() const { return numLeds_; }
|
||||||
|
private:
|
||||||
|
strand_t cfg;
|
||||||
|
strand_t *strands[1];
|
||||||
|
int numLeds_;
|
||||||
|
int pin_;
|
||||||
|
};
|
||||||
472
espmusicmouse/lib/leds/esp32_digital_led_lib.cpp
Normal file
472
espmusicmouse/lib/leds/esp32_digital_led_lib.cpp
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
/*
|
||||||
|
* Library for driving digital RGB(W) LEDs using the ESP32's RMT peripheral
|
||||||
|
*
|
||||||
|
* Modifications Copyright (c) 2017-2019 Martin F. Falatic
|
||||||
|
*
|
||||||
|
* Portions modified using FastLED's ClocklessController as a reference
|
||||||
|
* Copyright (c) 2018 Samuel Z. Guyer
|
||||||
|
* Copyright (c) 2017 Thomas Basler
|
||||||
|
*
|
||||||
|
* Based on public domain code created 19 Nov 2016 by Chris Osborn <fozztexx@fozztexx.com>
|
||||||
|
* http://insentricity.com
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 "esp32_digital_led_lib.h"
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ARDUINO)
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "esp_intr.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/rmt.h"
|
||||||
|
#include "driver/periph_ctrl.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "soc/rmt_struct.h"
|
||||||
|
#elif defined(ESP_PLATFORM)
|
||||||
|
#include <esp_intr.h>
|
||||||
|
#include <driver/gpio.h>
|
||||||
|
#include <driver/rmt.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <soc/dport_reg.h>
|
||||||
|
#include <soc/gpio_sig_map.h>
|
||||||
|
#include <soc/rmt_struct.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h> // memset, memcpy, etc. live here!
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
|
||||||
|
|
||||||
|
#if DEBUG_ESP32_DIGITAL_LED_LIB
|
||||||
|
extern char * digitalLeds_debugBuffer;
|
||||||
|
extern int digitalLeds_debugBufferSz;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static DRAM_ATTR const uint16_t MAX_PULSES = 32; // A channel has a 64 "pulse" buffer - we use half per pass
|
||||||
|
static DRAM_ATTR const uint16_t DIVIDER = 4; // 8 still seems to work, but timings become marginal
|
||||||
|
static DRAM_ATTR const double RMT_DURATION_NS = 12.5; // Minimum time of a single RMT duration based on clock ns
|
||||||
|
|
||||||
|
|
||||||
|
// Considering the RMT_INT_RAW_REG (raw int status) and RMT_INT_ST_REG (masked int status) registers (each 32-bit):
|
||||||
|
// Where op = {raw, st, ena, clr} and n = {0..7}
|
||||||
|
// Every three bits = RMT.int_<op>.ch<n>_tx_end, RMT.int_<op>.ch<n>_rx_end, RMT.int_<op>.ch<n>_err
|
||||||
|
// The final 8 bits are RMT.int_<op>.ch<n>_tx_thr_event
|
||||||
|
|
||||||
|
// LUT for mapping bits in RMT.int_<op>.ch<n>_tx_thr_event
|
||||||
|
static DRAM_ATTR const uint32_t tx_thr_event_offsets [] = {
|
||||||
|
static_cast<uint32_t>(1) << (24 + 0),
|
||||||
|
static_cast<uint32_t>(1) << (24 + 1),
|
||||||
|
static_cast<uint32_t>(1) << (24 + 2),
|
||||||
|
static_cast<uint32_t>(1) << (24 + 3),
|
||||||
|
static_cast<uint32_t>(1) << (24 + 4),
|
||||||
|
static_cast<uint32_t>(1) << (24 + 5),
|
||||||
|
static_cast<uint32_t>(1) << (24 + 6),
|
||||||
|
static_cast<uint32_t>(1) << (24 + 7),
|
||||||
|
};
|
||||||
|
|
||||||
|
// LUT for mapping bits in RMT.int_<op>.ch<n>_tx_end
|
||||||
|
static DRAM_ATTR const uint32_t tx_end_offsets [] = {
|
||||||
|
static_cast<uint32_t>(1) << (0 + 0) * 3,
|
||||||
|
static_cast<uint32_t>(1) << (0 + 1) * 3,
|
||||||
|
static_cast<uint32_t>(1) << (0 + 2) * 3,
|
||||||
|
static_cast<uint32_t>(1) << (0 + 3) * 3,
|
||||||
|
static_cast<uint32_t>(1) << (0 + 4) * 3,
|
||||||
|
static_cast<uint32_t>(1) << (0 + 5) * 3,
|
||||||
|
static_cast<uint32_t>(1) << (0 + 6) * 3,
|
||||||
|
static_cast<uint32_t>(1) << (0 + 7) * 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint32_t duration0:15;
|
||||||
|
uint32_t level0:1;
|
||||||
|
uint32_t duration1:15;
|
||||||
|
uint32_t level1:1;
|
||||||
|
};
|
||||||
|
uint32_t val;
|
||||||
|
} rmtPulsePair;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t * buf_data;
|
||||||
|
uint16_t buf_pos, buf_len, buf_half, buf_isDirty;
|
||||||
|
rmtPulsePair pulsePairMap[2];
|
||||||
|
bool isProcessing;
|
||||||
|
} digitalLeds_stateData;
|
||||||
|
|
||||||
|
double randDouble()
|
||||||
|
{
|
||||||
|
return double(esp_random()>>16) / (UINT16_MAX + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pixelColor_t adjustByUniformFactor(pixelColor_t * color, double adjFactor) {
|
||||||
|
color->r = uint8_t(color->r * (1.0 - adjFactor));
|
||||||
|
color->g = uint8_t(color->g * (1.0 - adjFactor));
|
||||||
|
color->b = uint8_t(color->b * (1.0 - adjFactor));
|
||||||
|
color->w = uint8_t(color->w * (1.0 - adjFactor));
|
||||||
|
return *color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const static int MAX_RMT_CHANNELS = 8;
|
||||||
|
static strand_t * strandDataPtrs[MAX_RMT_CHANNELS] = {nullptr}; // Indexed by RMT channel
|
||||||
|
|
||||||
|
// Forward declarations of local functions
|
||||||
|
static void copyHalfBlockToRmt(strand_t * pStrand);
|
||||||
|
static void rmtInterruptHandler(void *arg);
|
||||||
|
|
||||||
|
|
||||||
|
static xSemaphoreHandle gRmtSem = nullptr;
|
||||||
|
static intr_handle_t gRmtIntrHandle = nullptr;
|
||||||
|
|
||||||
|
static int gToProcess = 0;
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(ARDUINO) && ARDUINO >= 100
|
||||||
|
void espPinMode(int pinNum, int pinDir) {
|
||||||
|
// Enable GPIO32 or 33 as output. Device-dependent
|
||||||
|
// (only works if these aren't used for external XTAL).
|
||||||
|
// https://esp32.com/viewtopic.php?t=9151#p38282
|
||||||
|
if (pinNum == 32 || pinNum == 33) {
|
||||||
|
uint64_t gpioBitMask = (pinNum == 32) ? 1ULL<<GPIO_NUM_32 : 1ULL<<GPIO_NUM_33;
|
||||||
|
gpio_mode_t gpioMode = (pinDir == OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
|
||||||
|
gpio_config_t io_conf;
|
||||||
|
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||||
|
io_conf.mode = gpioMode;
|
||||||
|
io_conf.pin_bit_mask = gpioBitMask;
|
||||||
|
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||||
|
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||||
|
gpio_config(&io_conf);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pinMode(pinNum, pinDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
void gpioSetup(int gpioNum, int gpioMode, int gpioVal) {
|
||||||
|
#if defined(ARDUINO) && ARDUINO >= 100
|
||||||
|
espPinMode(gpioNum, gpioMode);
|
||||||
|
digitalWrite (gpioNum, gpioVal);
|
||||||
|
#elif defined(ESP_PLATFORM)
|
||||||
|
gpio_num_t gpioNumNative = static_cast<gpio_num_t>(gpioNum);
|
||||||
|
gpio_mode_t gpioModeNative = static_cast<gpio_mode_t>(gpioMode);
|
||||||
|
gpio_pad_select_gpio(gpioNumNative);
|
||||||
|
gpio_set_direction(gpioNumNative, gpioModeNative);
|
||||||
|
gpio_set_level(gpioNumNative, gpioVal);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int digitalLeds_initDriver()
|
||||||
|
{
|
||||||
|
#if DEBUG_ESP32_DIGITAL_LED_LIB
|
||||||
|
snprintf(digitalLeds_debugBuffer, digitalLeds_debugBufferSz, "digitalLeds_initDriver\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
esp_err_t rc = ESP_OK;
|
||||||
|
|
||||||
|
if (gRmtIntrHandle == nullptr) { // Only on first run
|
||||||
|
// Sem is created here
|
||||||
|
gRmtSem = xSemaphoreCreateBinary();
|
||||||
|
xSemaphoreGive(gRmtSem);
|
||||||
|
rc = esp_intr_alloc(ETS_RMT_INTR_SOURCE, 0, rmtInterruptHandler, nullptr, &gRmtIntrHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int digitalLeds_addStrands(strand_t * strands [], int numStrands)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < numStrands; i++) {
|
||||||
|
int rmtChannel = strands[i]->rmtChannel;
|
||||||
|
strand_t * pStrand = strands[i];
|
||||||
|
strandDataPtrs[rmtChannel] = pStrand;
|
||||||
|
|
||||||
|
ledParams_t ledParams = ledParamsAll[pStrand->ledType];
|
||||||
|
|
||||||
|
pStrand->pixels = static_cast<pixelColor_t*>(malloc(pStrand->numPixels * sizeof(pixelColor_t)));
|
||||||
|
if (pStrand->pixels == nullptr) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pStrand->_stateVars = static_cast<digitalLeds_stateData*>(malloc(sizeof(digitalLeds_stateData)));
|
||||||
|
if (pStrand->_stateVars == nullptr) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
digitalLeds_stateData * pState = static_cast<digitalLeds_stateData*>(pStrand->_stateVars);
|
||||||
|
|
||||||
|
pState->buf_len = (pStrand->numPixels * ledParams.bytesPerPixel);
|
||||||
|
pState->buf_data = static_cast<uint8_t*>(malloc(pState->buf_len));
|
||||||
|
if (pState->buf_data == nullptr) {
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RMT configuration for transmission
|
||||||
|
rmt_config_t rmt_tx;
|
||||||
|
rmt_tx.channel = static_cast<rmt_channel_t>(rmtChannel);
|
||||||
|
rmt_tx.gpio_num = static_cast<gpio_num_t>(pStrand->gpioNum);
|
||||||
|
rmt_tx.rmt_mode = RMT_MODE_TX;
|
||||||
|
rmt_tx.mem_block_num = 1;
|
||||||
|
rmt_tx.clk_div = DIVIDER;
|
||||||
|
rmt_tx.tx_config.loop_en = false;
|
||||||
|
rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_LOW;
|
||||||
|
rmt_tx.tx_config.carrier_en = false;
|
||||||
|
rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW;
|
||||||
|
rmt_tx.tx_config.idle_output_en = true;
|
||||||
|
rmt_config(&rmt_tx);
|
||||||
|
|
||||||
|
// RMT config for transmitting a '0' bit val to this LED strand
|
||||||
|
pState->pulsePairMap[0].level0 = 1;
|
||||||
|
pState->pulsePairMap[0].level1 = 0;
|
||||||
|
pState->pulsePairMap[0].duration0 = ledParams.T0H / (RMT_DURATION_NS * DIVIDER);
|
||||||
|
pState->pulsePairMap[0].duration1 = ledParams.T0L / (RMT_DURATION_NS * DIVIDER);
|
||||||
|
|
||||||
|
// RMT config for transmitting a '0' bit val to this LED strand
|
||||||
|
pState->pulsePairMap[1].level0 = 1;
|
||||||
|
pState->pulsePairMap[1].level1 = 0;
|
||||||
|
pState->pulsePairMap[1].duration0 = ledParams.T1H / (RMT_DURATION_NS * DIVIDER);
|
||||||
|
pState->pulsePairMap[1].duration1 = ledParams.T1L / (RMT_DURATION_NS * DIVIDER);
|
||||||
|
|
||||||
|
pState->isProcessing = false;
|
||||||
|
|
||||||
|
// Set interrupts
|
||||||
|
rmt_set_tx_thr_intr_en(static_cast<rmt_channel_t>(rmtChannel), true, MAX_PULSES); // sets rmt_set_tx_wrap_en and RMT.tx_lim_ch<n>.limit
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalLeds_resetPixels(strands, numStrands);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int digitalLeds_removeStrands(strand_t * strands [], int numStrands)
|
||||||
|
{
|
||||||
|
digitalLeds_resetPixels(strands, numStrands);
|
||||||
|
|
||||||
|
for (int i = 0; i < numStrands; i++) {
|
||||||
|
int rmtChannel = strands[i]->rmtChannel;
|
||||||
|
strand_t * pStrand = strandDataPtrs[rmtChannel];
|
||||||
|
if (pStrand) {
|
||||||
|
strandDataPtrs[rmtChannel] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int digitalLeds_resetPixels(strand_t * strands [], int numStrands)
|
||||||
|
{
|
||||||
|
// TODO: The input is strands for convenience - the point is to get indicies of strands to draw
|
||||||
|
// Could just pass the channel numbers, but would it be slower to construct that list?
|
||||||
|
|
||||||
|
for (int i = 0; i < numStrands; i++) {
|
||||||
|
int rmtChannel = strands[i]->rmtChannel;
|
||||||
|
strand_t * pStrand = strandDataPtrs[rmtChannel];
|
||||||
|
memset(pStrand->pixels, 0, pStrand->numPixels * sizeof(pixelColor_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalLeds_drawPixels(strands, numStrands);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int IRAM_ATTR digitalLeds_drawPixels(strand_t * strands [], int numStrands)
|
||||||
|
{
|
||||||
|
// TODO: The input is strands for convenience - the point is to get indicies of strands to draw
|
||||||
|
// Could just pass the channel numbers, but would it be slower to construct that list?
|
||||||
|
|
||||||
|
if (numStrands == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gToProcess = numStrands;
|
||||||
|
|
||||||
|
xSemaphoreTake(gRmtSem, portMAX_DELAY);
|
||||||
|
|
||||||
|
for (int i = 0; i < numStrands; i++) {
|
||||||
|
int rmtChannel = strands[i]->rmtChannel;
|
||||||
|
strand_t * pStrand = strandDataPtrs[rmtChannel];
|
||||||
|
|
||||||
|
digitalLeds_stateData * pState = static_cast<digitalLeds_stateData*>(pStrand->_stateVars);
|
||||||
|
ledParams_t ledParams = ledParamsAll[pStrand->ledType];
|
||||||
|
|
||||||
|
pState->isProcessing = true;
|
||||||
|
|
||||||
|
// Pack pixels into transmission buffer
|
||||||
|
if (ledParams.bytesPerPixel == 3) {
|
||||||
|
for (uint16_t i = 0; i < pStrand->numPixels; i++) {
|
||||||
|
// Color order is translated from RGB to GRB
|
||||||
|
pState->buf_data[0 + i * 3] = pStrand->pixels[i].g;
|
||||||
|
pState->buf_data[1 + i * 3] = pStrand->pixels[i].r;
|
||||||
|
pState->buf_data[2 + i * 3] = pStrand->pixels[i].b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ledParams.bytesPerPixel == 4) {
|
||||||
|
for (uint16_t i = 0; i < pStrand->numPixels; i++) {
|
||||||
|
// Color order is translated from RGBW to GRBW
|
||||||
|
pState->buf_data[0 + i * 4] = pStrand->pixels[i].g;
|
||||||
|
pState->buf_data[1 + i * 4] = pStrand->pixels[i].r;
|
||||||
|
pState->buf_data[2 + i * 4] = pStrand->pixels[i].b;
|
||||||
|
pState->buf_data[3 + i * 4] = pStrand->pixels[i].w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pState->buf_pos = 0;
|
||||||
|
pState->buf_half = 0;
|
||||||
|
|
||||||
|
rmt_set_tx_intr_en(static_cast<rmt_channel_t>(rmtChannel), true);
|
||||||
|
|
||||||
|
copyHalfBlockToRmt(pStrand);
|
||||||
|
if (pState->buf_pos < pState->buf_len) { // Fill the other half of the buffer block
|
||||||
|
copyHalfBlockToRmt(pStrand);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starts RMT, which will end up giving us the semaphore back
|
||||||
|
// Immediately starts transmitting
|
||||||
|
rmt_set_tx_intr_en(static_cast<rmt_channel_t>(rmtChannel), true);
|
||||||
|
rmt_tx_start(static_cast<rmt_channel_t>(rmtChannel), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give back semaphore after drawing is done
|
||||||
|
xSemaphoreTake(gRmtSem, portMAX_DELAY);
|
||||||
|
xSemaphoreGive(gRmtSem);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static IRAM_ATTR void copyHalfBlockToRmt(strand_t * pStrand)
|
||||||
|
{
|
||||||
|
// This fills half an RMT block
|
||||||
|
// When wraparound is happening, we want to keep the inactive half of the RMT block filled
|
||||||
|
digitalLeds_stateData * pState = static_cast<digitalLeds_stateData*>(pStrand->_stateVars);
|
||||||
|
ledParams_t ledParams = ledParamsAll[pStrand->ledType];
|
||||||
|
|
||||||
|
uint16_t i, j, offset, len, byteval;
|
||||||
|
|
||||||
|
offset = pState->buf_half * MAX_PULSES;
|
||||||
|
pState->buf_half = !pState->buf_half;
|
||||||
|
|
||||||
|
len = pState->buf_len - pState->buf_pos;
|
||||||
|
if (len > (MAX_PULSES / 8))
|
||||||
|
len = (MAX_PULSES / 8);
|
||||||
|
|
||||||
|
if (!len) {
|
||||||
|
if (!pState->buf_isDirty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Clear the channel's data block and return
|
||||||
|
for (i = 0; i < MAX_PULSES; i++) {
|
||||||
|
RMTMEM.chan[pStrand->rmtChannel].data32[i + offset].val = 0;
|
||||||
|
}
|
||||||
|
pState->buf_isDirty = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pState->buf_isDirty = 1;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
byteval = pState->buf_data[i + pState->buf_pos];
|
||||||
|
|
||||||
|
// Shift bits out, MSB first, setting RMTMEM.chan[n].data32[x] to
|
||||||
|
// the rmtPulsePair value corresponding to the buffered bit value
|
||||||
|
for (j = 0; j < 8; j++, byteval <<= 1) {
|
||||||
|
int bitval = (byteval >> 7) & 0x01;
|
||||||
|
int data32_idx = i * 8 + offset + j;
|
||||||
|
RMTMEM.chan[pStrand->rmtChannel].data32[data32_idx].val = pState->pulsePairMap[bitval].val;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the reset bit by stretching duration1 for the final bit in the stream
|
||||||
|
if (i + pState->buf_pos == pState->buf_len - 1) {
|
||||||
|
RMTMEM.chan[pStrand->rmtChannel].data32[i * 8 + offset + 7].duration1 =
|
||||||
|
ledParams.TRS / (RMT_DURATION_NS * DIVIDER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the remainder of the channel's data not set above
|
||||||
|
for (i *= 8; i < MAX_PULSES; i++) {
|
||||||
|
RMTMEM.chan[pStrand->rmtChannel].data32[i + offset].val = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pState->buf_pos += len;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static IRAM_ATTR void rmtInterruptHandler(void *arg)
|
||||||
|
{
|
||||||
|
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
|
||||||
|
|
||||||
|
for (int rmtChannel = 0; rmtChannel < MAX_RMT_CHANNELS; rmtChannel++) {
|
||||||
|
strand_t * pStrand = strandDataPtrs[rmtChannel];
|
||||||
|
if (pStrand == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalLeds_stateData * pState = static_cast<digitalLeds_stateData*>(pStrand->_stateVars);
|
||||||
|
if (!pState->isProcessing) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RMT.int_st.val & tx_thr_event_offsets[rmtChannel]) {
|
||||||
|
// We got an RMT.int_st.ch<n>_tx_thr_event interrupt because RMT.tx_lim_ch<n>.limit was crossed
|
||||||
|
RMT.int_clr.val |= tx_thr_event_offsets[rmtChannel]; // set RMT.int_clr.ch<n>_tx_thr_event (reset interrupt bit)
|
||||||
|
copyHalfBlockToRmt(pStrand);
|
||||||
|
}
|
||||||
|
else if (RMT.int_st.val & tx_end_offsets[rmtChannel]) {
|
||||||
|
// We got an RMT.int_st.ch<n>_tx_end interrupt with a zero-length entry which means we're done
|
||||||
|
RMT.int_clr.val |= tx_end_offsets[rmtChannel]; // set RMT.int_clr.ch<n>_tx_end (reset interrupt bit)
|
||||||
|
//gpio_matrix_out(static_cast<gpio_num_t>(pStrand->gpioNum), 0x100, 0, 0); // only useful if rmt_config keeps getting called
|
||||||
|
pState->isProcessing = false;
|
||||||
|
gToProcess--;
|
||||||
|
if (gToProcess == 0) {
|
||||||
|
xSemaphoreGiveFromISR(gRmtSem, &xHigherPriorityTaskWoken);
|
||||||
|
if (xHigherPriorityTaskWoken == pdTRUE) { // Perform cleanup if we're all done
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//**************************************************************************//
|
||||||
155
espmusicmouse/lib/leds/esp32_digital_led_lib.h
Normal file
155
espmusicmouse/lib/leds/esp32_digital_led_lib.h
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* Library for driving digital RGB(W) LEDs using the ESP32's RMT peripheral
|
||||||
|
*
|
||||||
|
* Modifications Copyright (c) 2017-2019 Martin F. Falatic
|
||||||
|
*
|
||||||
|
* Portions modified using FastLED's ClocklessController as a reference
|
||||||
|
* Copyright (c) 2018 Samuel Z. Guyer
|
||||||
|
* Copyright (c) 2017 Thomas Basler
|
||||||
|
*
|
||||||
|
* Based on public domain code created 19 Nov 2016 by Chris Osborn <fozztexx@fozztexx.com>
|
||||||
|
* http://insentricity.com
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 ESP32_DIGITAL_LED_LIB_H
|
||||||
|
#define ESP32_DIGITAL_LED_LIB_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define DEBUG_ESP32_DIGITAL_LED_LIB 0
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct __attribute__ ((packed)) {
|
||||||
|
uint8_t b, g, r, w; // Little-endian ordered
|
||||||
|
};
|
||||||
|
uint32_t raw32;
|
||||||
|
} pixelColor_t;
|
||||||
|
|
||||||
|
inline pixelColor_t pixelFromRGB(uint8_t r, uint8_t g, uint8_t b)
|
||||||
|
{
|
||||||
|
pixelColor_t v;
|
||||||
|
v.r = r;
|
||||||
|
v.g = g;
|
||||||
|
v.b = b;
|
||||||
|
v.w = 0;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline pixelColor_t pixelFromRGBhex(uint8_t r, uint8_t g, uint8_t b)
|
||||||
|
{
|
||||||
|
pixelColor_t v;
|
||||||
|
v.r = r;
|
||||||
|
v.g = g;
|
||||||
|
v.b = b;
|
||||||
|
v.w = 0;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline pixelColor_t pixelFromRGBW(uint8_t r, uint8_t g, uint8_t b, uint8_t w)
|
||||||
|
{
|
||||||
|
pixelColor_t v;
|
||||||
|
v.r = r;
|
||||||
|
v.g = g;
|
||||||
|
v.b = b;
|
||||||
|
v.w = w;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline pixelColor_t pixelFromRGBWhex(uint8_t r, uint8_t g, uint8_t b, uint8_t w)
|
||||||
|
{
|
||||||
|
// The value is of the form 0xWWRRGGBB
|
||||||
|
pixelColor_t v;
|
||||||
|
v.r = r;
|
||||||
|
v.g = g;
|
||||||
|
v.b = b;
|
||||||
|
v.w = w;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int rmtChannel;
|
||||||
|
int gpioNum;
|
||||||
|
int ledType;
|
||||||
|
int brightLimit;
|
||||||
|
int numPixels;
|
||||||
|
pixelColor_t * pixels;
|
||||||
|
void * _stateVars;
|
||||||
|
} strand_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int bytesPerPixel;
|
||||||
|
uint32_t T0H;
|
||||||
|
uint32_t T1H;
|
||||||
|
uint32_t T0L;
|
||||||
|
uint32_t T1L;
|
||||||
|
uint32_t TRS;
|
||||||
|
} ledParams_t;
|
||||||
|
|
||||||
|
enum led_types {
|
||||||
|
LED_WS2812_V1,
|
||||||
|
LED_WS2812B_V1,
|
||||||
|
LED_WS2812B_V2,
|
||||||
|
LED_WS2812B_V3,
|
||||||
|
LED_WS2813_V1,
|
||||||
|
LED_WS2813_V2,
|
||||||
|
LED_WS2813_V3,
|
||||||
|
LED_SK6812_V1,
|
||||||
|
LED_SK6812W_V1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ledParams_t ledParamsAll[] = { // Still must match order of `led_types`
|
||||||
|
[LED_WS2812_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 700, .T0L = 800, .T1L = 600, .TRS = 50000}, // Various
|
||||||
|
[LED_WS2812B_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 900, .T0L = 900, .T1L = 350, .TRS = 50000}, // Older datasheet
|
||||||
|
[LED_WS2812B_V2] = { .bytesPerPixel = 3, .T0H = 400, .T1H = 850, .T0L = 850, .T1L = 400, .TRS = 50000}, // 2016 datasheet
|
||||||
|
[LED_WS2812B_V3] = { .bytesPerPixel = 3, .T0H = 450, .T1H = 850, .T0L = 850, .T1L = 450, .TRS = 50000}, // cplcpu test
|
||||||
|
[LED_WS2813_V1] = { .bytesPerPixel = 3, .T0H = 350, .T1H = 800, .T0L = 350, .T1L = 350, .TRS = 300000}, // Older datasheet
|
||||||
|
[LED_WS2813_V2] = { .bytesPerPixel = 3, .T0H = 270, .T1H = 800, .T0L = 800, .T1L = 270, .TRS = 300000}, // 2016 datasheet
|
||||||
|
[LED_WS2813_V3] = { .bytesPerPixel = 3, .T0H = 270, .T1H = 630, .T0L = 630, .T1L = 270, .TRS = 300000}, // 2017-05 WS datasheet
|
||||||
|
[LED_SK6812_V1] = { .bytesPerPixel = 3, .T0H = 300, .T1H = 600, .T0L = 900, .T1L = 600, .TRS = 80000}, // Various, all consistent
|
||||||
|
[LED_SK6812W_V1] = { .bytesPerPixel = 4, .T0H = 300, .T1H = 600, .T0L = 900, .T1L = 600, .TRS = 80000}, // Various, all consistent
|
||||||
|
};
|
||||||
|
|
||||||
|
extern void espPinMode(int pinNum, int pinDir);
|
||||||
|
extern void gpioSetup(int gpioNum, int gpioMode, int gpioVal);
|
||||||
|
extern double randDouble();
|
||||||
|
extern pixelColor_t adjustByUniformFactor(pixelColor_t * color, double adjFactor);
|
||||||
|
|
||||||
|
extern int digitalLeds_initDriver();
|
||||||
|
extern int digitalLeds_addStrands(strand_t * strands [], int numStrands);
|
||||||
|
extern int digitalLeds_removeStrands(strand_t * strands [], int numStrands);
|
||||||
|
extern int digitalLeds_drawPixels(strand_t * strands [], int numStrands);
|
||||||
|
extern int digitalLeds_resetPixels(strand_t * strands [], int numStrands);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* ESP32_DIGITAL_LED_LIB_H */
|
||||||
|
|
||||||
|
|
||||||
|
//**************************************************************************//
|
||||||
561
espmusicmouse/lib/nfc/rc522.c
Normal file
561
espmusicmouse/lib/nfc/rc522.c
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "soc/gpio_struct.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
#include "rc522.h"
|
||||||
|
|
||||||
|
static const char *TAG = "ESP-RC522";
|
||||||
|
|
||||||
|
struct rc522
|
||||||
|
{
|
||||||
|
bool running;
|
||||||
|
rc522_config_t *config;
|
||||||
|
spi_device_handle_t spi;
|
||||||
|
TaskHandle_t task_handle;
|
||||||
|
bool scan_started;
|
||||||
|
bool tag_was_present_last_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct rc522 *rc522_handle_t;
|
||||||
|
|
||||||
|
static rc522_handle_t hndl = NULL;
|
||||||
|
|
||||||
|
#define rc522_fw_version() rc522_read(0x37)
|
||||||
|
|
||||||
|
bool rc522_is_inited()
|
||||||
|
{
|
||||||
|
return hndl != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_spi_init()
|
||||||
|
{
|
||||||
|
if (!hndl || !hndl->config)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Fail to init SPI. Invalid handle");
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hndl->spi)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "SPI already initialized");
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_bus_config_t buscfg = {
|
||||||
|
.miso_io_num = hndl->config->miso_io,
|
||||||
|
.mosi_io_num = hndl->config->mosi_io,
|
||||||
|
.sclk_io_num = hndl->config->sck_io,
|
||||||
|
.quadwp_io_num = -1,
|
||||||
|
.quadhd_io_num = -1};
|
||||||
|
|
||||||
|
spi_device_interface_config_t devcfg = {
|
||||||
|
.clock_speed_hz = 5000000,
|
||||||
|
.mode = 0,
|
||||||
|
.spics_io_num = hndl->config->sda_io,
|
||||||
|
.queue_size = 7,
|
||||||
|
.flags = SPI_DEVICE_HALFDUPLEX};
|
||||||
|
|
||||||
|
esp_err_t err = spi_bus_initialize(hndl->config->spi_host_id, &buscfg, 0);
|
||||||
|
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = spi_bus_add_device(hndl->config->spi_host_id, &devcfg, &hndl->spi);
|
||||||
|
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
spi_bus_free(hndl->config->spi_host_id);
|
||||||
|
hndl->spi = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_write_n(uint8_t addr, uint8_t n, uint8_t *data)
|
||||||
|
{
|
||||||
|
uint8_t *buffer = (uint8_t *)malloc(n + 1);
|
||||||
|
buffer[0] = (addr << 1) & 0x7E;
|
||||||
|
|
||||||
|
for (uint8_t i = 1; i <= n; i++)
|
||||||
|
{
|
||||||
|
buffer[i] = data[i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_transaction_t t;
|
||||||
|
memset(&t, 0, sizeof(t));
|
||||||
|
|
||||||
|
t.length = 8 * (n + 1);
|
||||||
|
t.tx_buffer = buffer;
|
||||||
|
|
||||||
|
esp_err_t ret = spi_device_transmit(hndl->spi, &t);
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_write(uint8_t addr, uint8_t val)
|
||||||
|
{
|
||||||
|
return rc522_write_n(addr, 1, &val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t *rc522_read_n(uint8_t addr, uint8_t n)
|
||||||
|
{
|
||||||
|
if (n <= 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
spi_transaction_t t;
|
||||||
|
memset(&t, 0, sizeof(t));
|
||||||
|
|
||||||
|
uint8_t *buffer = (uint8_t *)malloc(n);
|
||||||
|
|
||||||
|
t.flags = SPI_TRANS_USE_TXDATA;
|
||||||
|
t.length = 8;
|
||||||
|
t.tx_data[0] = ((addr << 1) & 0x7E) | 0x80;
|
||||||
|
t.rxlength = 8 * n;
|
||||||
|
t.rx_buffer = buffer;
|
||||||
|
|
||||||
|
esp_err_t ret = spi_device_transmit(hndl->spi, &t);
|
||||||
|
assert(ret == ESP_OK);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t rc522_read(uint8_t addr)
|
||||||
|
{
|
||||||
|
uint8_t *buffer = rc522_read_n(addr, 1);
|
||||||
|
uint8_t res = buffer[0];
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_set_bitmask(uint8_t addr, uint8_t mask)
|
||||||
|
{
|
||||||
|
return rc522_write(addr, rc522_read(addr) | mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_clear_bitmask(uint8_t addr, uint8_t mask)
|
||||||
|
{
|
||||||
|
return rc522_write(addr, rc522_read(addr) & ~mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t rc522_antenna_on()
|
||||||
|
{
|
||||||
|
esp_err_t ret;
|
||||||
|
|
||||||
|
if (~(rc522_read(0x14) & 0x03))
|
||||||
|
{
|
||||||
|
ret = rc522_set_bitmask(0x14, 0x03);
|
||||||
|
|
||||||
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc522_write(0x26, 0x60); // 43dB gain
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rc522_task(void *arg);
|
||||||
|
|
||||||
|
esp_err_t rc522_init(rc522_config_t *config)
|
||||||
|
{
|
||||||
|
if (!config)
|
||||||
|
{
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hndl)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Already initialized");
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(hndl = calloc(1, sizeof(struct rc522))))
|
||||||
|
{
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(hndl->config = calloc(1, sizeof(rc522_config_t))))
|
||||||
|
{
|
||||||
|
rc522_destroy();
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy config considering defaults
|
||||||
|
hndl->config->callback = config->callback;
|
||||||
|
hndl->config->miso_io = config->miso_io == 0 ? RC522_DEFAULT_MISO : config->miso_io;
|
||||||
|
hndl->config->mosi_io = config->mosi_io == 0 ? RC522_DEFAULT_MOSI : config->mosi_io;
|
||||||
|
hndl->config->sck_io = config->sck_io == 0 ? RC522_DEFAULT_SCK : config->sck_io;
|
||||||
|
hndl->config->sda_io = config->sda_io == 0 ? RC522_DEFAULT_SDA : config->sda_io;
|
||||||
|
hndl->config->spi_host_id = config->spi_host_id == 0 ? RC522_DEFAULT_SPI_HOST : config->spi_host_id;
|
||||||
|
hndl->config->scan_interval_ms = config->scan_interval_ms < 50 ? RC522_DEFAULT_SCAN_INTERVAL_MS : config->scan_interval_ms;
|
||||||
|
hndl->config->task_stack_size = config->task_stack_size == 0 ? RC522_DEFAULT_TACK_STACK_SIZE : config->task_stack_size;
|
||||||
|
hndl->config->task_priority = config->task_priority == 0 ? RC522_DEFAULT_TACK_STACK_PRIORITY : config->task_priority;
|
||||||
|
|
||||||
|
esp_err_t err = rc522_spi_init();
|
||||||
|
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
rc522_destroy();
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- RW test ------------
|
||||||
|
const uint8_t test_addr = 0x24, test_val = 0x25;
|
||||||
|
for (uint8_t i = test_val; i < test_val + 2; i++)
|
||||||
|
{
|
||||||
|
if ((err = rc522_write(test_addr, i)) != ESP_OK || rc522_read(test_addr) != i)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "RW test fail");
|
||||||
|
rc522_destroy();
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ------- End of RW test --------
|
||||||
|
|
||||||
|
rc522_write(0x01, 0x0F);
|
||||||
|
rc522_write(0x2A, 0x8D);
|
||||||
|
rc522_write(0x2B, 0x3E);
|
||||||
|
rc522_write(0x2D, 0x1E);
|
||||||
|
rc522_write(0x2C, 0x00);
|
||||||
|
rc522_write(0x15, 0x40);
|
||||||
|
rc522_write(0x11, 0x3D);
|
||||||
|
|
||||||
|
rc522_antenna_on();
|
||||||
|
|
||||||
|
hndl->running = true;
|
||||||
|
if (xTaskCreate(rc522_task, "rc522_task", hndl->config->task_stack_size, NULL, hndl->config->task_priority, &hndl->task_handle) != pdTRUE)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Fail to create rc522 task");
|
||||||
|
rc522_destroy();
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Fail to create timer");
|
||||||
|
rc522_destroy();
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Initialized (firmware: 0x%x)", rc522_fw_version());
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t rc522_sn_to_u64(uint8_t *sn)
|
||||||
|
{
|
||||||
|
if (!sn)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t result = 0;
|
||||||
|
for (int i = 4; i >= 0; i--)
|
||||||
|
{
|
||||||
|
result |= ((uint64_t)sn[i] << (i * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns pointer to dynamically allocated array of two element */
|
||||||
|
static uint8_t *rc522_calculate_crc(uint8_t *data, uint8_t n)
|
||||||
|
{
|
||||||
|
rc522_clear_bitmask(0x05, 0x04);
|
||||||
|
rc522_set_bitmask(0x0A, 0x80);
|
||||||
|
|
||||||
|
rc522_write_n(0x09, n, data);
|
||||||
|
|
||||||
|
rc522_write(0x01, 0x03);
|
||||||
|
|
||||||
|
uint8_t i = 255;
|
||||||
|
uint8_t nn = 0;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
nn = rc522_read(0x05);
|
||||||
|
i--;
|
||||||
|
|
||||||
|
if (!(i != 0 && !(nn & 0x04)))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *res = (uint8_t *)malloc(2);
|
||||||
|
|
||||||
|
res[0] = rc522_read(0x22);
|
||||||
|
res[1] = rc522_read(0x21);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t *rc522_card_write(uint8_t cmd, uint8_t *data, uint8_t n, uint8_t *res_n)
|
||||||
|
{
|
||||||
|
uint8_t *result = NULL;
|
||||||
|
uint8_t irq = 0x00;
|
||||||
|
uint8_t irq_wait = 0x00;
|
||||||
|
uint8_t last_bits = 0;
|
||||||
|
uint8_t nn = 0;
|
||||||
|
|
||||||
|
if (cmd == 0x0E)
|
||||||
|
{
|
||||||
|
irq = 0x12;
|
||||||
|
irq_wait = 0x10;
|
||||||
|
}
|
||||||
|
else if (cmd == 0x0C)
|
||||||
|
{
|
||||||
|
irq = 0x77;
|
||||||
|
irq_wait = 0x30;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_write(0x02, irq | 0x80);
|
||||||
|
rc522_clear_bitmask(0x04, 0x80);
|
||||||
|
rc522_set_bitmask(0x0A, 0x80);
|
||||||
|
rc522_write(0x01, 0x00);
|
||||||
|
|
||||||
|
rc522_write_n(0x09, n, data);
|
||||||
|
|
||||||
|
rc522_write(0x01, cmd);
|
||||||
|
|
||||||
|
if (cmd == 0x0C)
|
||||||
|
{
|
||||||
|
rc522_set_bitmask(0x0D, 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t i = 1000;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
nn = rc522_read(0x04);
|
||||||
|
i--;
|
||||||
|
|
||||||
|
if (!(i != 0 && (((nn & 0x01) == 0) && ((nn & irq_wait) == 0))))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_clear_bitmask(0x0D, 0x80);
|
||||||
|
|
||||||
|
if (i != 0)
|
||||||
|
{
|
||||||
|
if ((rc522_read(0x06) & 0x1B) == 0x00)
|
||||||
|
{
|
||||||
|
if (cmd == 0x0C)
|
||||||
|
{
|
||||||
|
nn = rc522_read(0x0A);
|
||||||
|
last_bits = rc522_read(0x0C) & 0x07;
|
||||||
|
|
||||||
|
if (last_bits != 0)
|
||||||
|
{
|
||||||
|
*res_n = (nn - 1) + last_bits;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*res_n = nn;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = (uint8_t *)malloc(*res_n);
|
||||||
|
|
||||||
|
for (i = 0; i < *res_n; i++)
|
||||||
|
{
|
||||||
|
result[i] = rc522_read(0x09);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t *rc522_request(uint8_t *res_n)
|
||||||
|
{
|
||||||
|
uint8_t *result = NULL;
|
||||||
|
rc522_write(0x0D, 0x07);
|
||||||
|
|
||||||
|
uint8_t req_mode = 0x26;
|
||||||
|
result = rc522_card_write(0x0C, &req_mode, 1, res_n);
|
||||||
|
|
||||||
|
if (*res_n * 8 != 0x10)
|
||||||
|
{
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t *rc522_anticoll()
|
||||||
|
{
|
||||||
|
uint8_t res_n;
|
||||||
|
|
||||||
|
rc522_write(0x0D, 0x00);
|
||||||
|
uint8_t *result = rc522_card_write(0x0C, (uint8_t[]){0x93, 0x20}, 2, &res_n);
|
||||||
|
|
||||||
|
if (result && res_n != 5)
|
||||||
|
{ // all cards/tags serial numbers is 5 bytes long (?)
|
||||||
|
free(result);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t *rc522_get_tag()
|
||||||
|
{
|
||||||
|
uint8_t *result = NULL;
|
||||||
|
uint8_t *res_data = NULL;
|
||||||
|
uint8_t res_data_n;
|
||||||
|
|
||||||
|
res_data = rc522_request(&res_data_n);
|
||||||
|
|
||||||
|
if (res_data != NULL)
|
||||||
|
{
|
||||||
|
free(res_data);
|
||||||
|
|
||||||
|
result = rc522_anticoll();
|
||||||
|
|
||||||
|
if (result != NULL)
|
||||||
|
{
|
||||||
|
uint8_t buf[] = {0x50, 0x00, 0x00, 0x00};
|
||||||
|
uint8_t *crc = rc522_calculate_crc(buf, 2);
|
||||||
|
|
||||||
|
buf[2] = crc[0];
|
||||||
|
buf[3] = crc[1];
|
||||||
|
|
||||||
|
free(crc);
|
||||||
|
|
||||||
|
res_data = rc522_card_write(0x0C, buf, 4, &res_data_n);
|
||||||
|
free(res_data);
|
||||||
|
|
||||||
|
rc522_clear_bitmask(0x08, 0x08);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_start(rc522_start_args_t start_args)
|
||||||
|
{
|
||||||
|
esp_err_t err = rc522_init(&start_args);
|
||||||
|
return err != ESP_OK ? err : rc522_start2();
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_start2()
|
||||||
|
{
|
||||||
|
if (!hndl)
|
||||||
|
{
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
hndl->scan_started = true;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rc522_pause()
|
||||||
|
{
|
||||||
|
if (!hndl)
|
||||||
|
{
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hndl->scan_started)
|
||||||
|
{
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
hndl->scan_started = false;
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rc522_destroy()
|
||||||
|
{
|
||||||
|
if (!hndl)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc522_pause(); // stop timer
|
||||||
|
hndl->running = false; // task will delete itself
|
||||||
|
|
||||||
|
if (hndl->spi)
|
||||||
|
{
|
||||||
|
spi_bus_remove_device(hndl->spi);
|
||||||
|
spi_bus_free(hndl->config->spi_host_id);
|
||||||
|
hndl->spi = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(hndl->config);
|
||||||
|
hndl->config = NULL;
|
||||||
|
|
||||||
|
free(hndl);
|
||||||
|
hndl = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool last_time_no_tag = false;
|
||||||
|
|
||||||
|
static void rc522_task(void *arg)
|
||||||
|
{
|
||||||
|
while (hndl->running)
|
||||||
|
{
|
||||||
|
if (!hndl->scan_started)
|
||||||
|
{
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *serial_no = rc522_get_tag();
|
||||||
|
|
||||||
|
if (serial_no && !hndl->tag_was_present_last_time)
|
||||||
|
{
|
||||||
|
last_time_no_tag = false;
|
||||||
|
rc522_tag_callback_t cb = hndl->config->callback;
|
||||||
|
if (cb)
|
||||||
|
{
|
||||||
|
cb(serial_no);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serial_no == NULL && !last_time_no_tag)
|
||||||
|
{
|
||||||
|
hndl->config->callback(NULL);
|
||||||
|
last_time_no_tag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((hndl->tag_was_present_last_time = (serial_no != NULL)))
|
||||||
|
{
|
||||||
|
free(serial_no);
|
||||||
|
serial_no = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delay_interval_ms = hndl->config->scan_interval_ms;
|
||||||
|
|
||||||
|
if (hndl->tag_was_present_last_time)
|
||||||
|
{
|
||||||
|
delay_interval_ms *= 2; // extra scan-bursting prevention
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(delay_interval_ms / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
92
espmusicmouse/lib/nfc/rc522.h
Normal file
92
espmusicmouse/lib/nfc/rc522.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
|
||||||
|
#define RC522_DEFAULT_MISO (25)
|
||||||
|
#define RC522_DEFAULT_MOSI (23)
|
||||||
|
#define RC522_DEFAULT_SCK (19)
|
||||||
|
#define RC522_DEFAULT_SDA (22)
|
||||||
|
#define RC522_DEFAULT_SPI_HOST (VSPI_HOST)
|
||||||
|
#define RC522_DEFAULT_SCAN_INTERVAL_MS (125)
|
||||||
|
#define RC522_DEFAULT_TACK_STACK_SIZE (4 * 1024)
|
||||||
|
#define RC522_DEFAULT_TACK_STACK_PRIORITY (4)
|
||||||
|
|
||||||
|
typedef void(*rc522_tag_callback_t)(uint8_t*);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int miso_io; /*<! MFRC522 MISO gpio (Default: 25) */
|
||||||
|
int mosi_io; /*<! MFRC522 MOSI gpio (Default: 23) */
|
||||||
|
int sck_io; /*<! MFRC522 SCK gpio (Default: 19) */
|
||||||
|
int sda_io; /*<! MFRC522 SDA gpio (Default: 22) */
|
||||||
|
spi_host_device_t spi_host_id; /*<! Default VSPI_HOST (SPI3) */
|
||||||
|
rc522_tag_callback_t callback; /*<! Scanned tags handler */
|
||||||
|
uint16_t scan_interval_ms; /*<! How fast will ESP32 scan for nearby tags, in miliseconds. Default: 125ms */
|
||||||
|
size_t task_stack_size; /*<! Stack size of rc522 task (Default: 4 * 1024) */
|
||||||
|
uint8_t task_priority; /*<! Priority of rc522 task (Default: 4) */
|
||||||
|
} rc522_config_t;
|
||||||
|
|
||||||
|
typedef rc522_config_t rc522_start_args_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize RC522 module.
|
||||||
|
* To start scanning tags - call rc522_resume or rc522_start2 function.
|
||||||
|
* @param config Configuration
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_init(rc522_config_t* config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Convert serial number (array of 5 bytes) to uint64_t number
|
||||||
|
* @param sn Serial number
|
||||||
|
* @return Serial number in number representation. If fail, 0 will be retured
|
||||||
|
*/
|
||||||
|
uint64_t rc522_sn_to_u64(uint8_t* sn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if RC522 is inited
|
||||||
|
* @return true if RC522 is inited
|
||||||
|
*/
|
||||||
|
bool rc522_is_inited();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This function will call rc522_init function and immediately start to scan tags by calling rc522_resume function.
|
||||||
|
* NOTE: This function will be refactored in future to just start scanning without
|
||||||
|
* initialization (same as rc522_resume). For initialization rc522_init will be required to call before this function.
|
||||||
|
* @param start_args Configuration
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_start(rc522_start_args_t start_args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start to scan tags. If already started, ESP_OK will just be returned.
|
||||||
|
* NOTE: This function is implemented because in time of implementation rc522_start function is intented for
|
||||||
|
* initialization and scanning in once. In future, when rc522_start gonna be refactored to just start to scan tags
|
||||||
|
* without initialization, this function will be just alias of rc522_start.
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_start2();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start to scan tags. If already started, ESP_OK will just be returned.
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
#define rc522_resume() rc522_start2()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Pause scan tags. If already paused, ESP_OK will just be returned.
|
||||||
|
* @return ESP_OK on success
|
||||||
|
*/
|
||||||
|
esp_err_t rc522_pause();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy RC522 and free all resources
|
||||||
|
*/
|
||||||
|
void rc522_destroy();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
345
espmusicmouse/lib/rotary_encoder/rotary_encoder.c
Normal file
345
espmusicmouse/lib/rotary_encoder/rotary_encoder.c
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019 David Antliff
|
||||||
|
* Copyright 2011 Ben Buxton
|
||||||
|
*
|
||||||
|
* This file is part of the esp32-rotary-encoder component.
|
||||||
|
*
|
||||||
|
* esp32-rotary-encoder is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* esp32-rotary-encoder 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with esp32-rotary-encoder. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file rotary_encoder.c
|
||||||
|
* @brief Driver implementation for the ESP32-compatible Incremental Rotary Encoder component.
|
||||||
|
*
|
||||||
|
* Based on https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
|
||||||
|
* Original header follows:
|
||||||
|
*
|
||||||
|
* Rotary encoder handler for arduino. v1.1
|
||||||
|
*
|
||||||
|
* Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
|
||||||
|
* Contact: bb@cactii.net
|
||||||
|
*
|
||||||
|
* A typical mechanical rotary encoder emits a two bit gray code
|
||||||
|
* on 3 output pins. Every step in the output (often accompanied
|
||||||
|
* by a physical 'click') generates a specific sequence of output
|
||||||
|
* codes on the pins.
|
||||||
|
*
|
||||||
|
* There are 3 pins used for the rotary encoding - one common and
|
||||||
|
* two 'bit' pins.
|
||||||
|
*
|
||||||
|
* The following is the typical sequence of code on the output when
|
||||||
|
* moving from one step to the next:
|
||||||
|
*
|
||||||
|
* Position Bit1 Bit2
|
||||||
|
* ----------------------
|
||||||
|
* Step1 0 0
|
||||||
|
* 1/4 1 0
|
||||||
|
* 1/2 1 1
|
||||||
|
* 3/4 0 1
|
||||||
|
* Step2 0 0
|
||||||
|
*
|
||||||
|
* From this table, we can see that when moving from one 'click' to
|
||||||
|
* the next, there are 4 changes in the output code.
|
||||||
|
*
|
||||||
|
* - From an initial 0 - 0, Bit1 goes high, Bit0 stays low.
|
||||||
|
* - Then both bits are high, halfway through the step.
|
||||||
|
* - Then Bit1 goes low, but Bit2 stays high.
|
||||||
|
* - Finally at the end of the step, both bits return to 0.
|
||||||
|
*
|
||||||
|
* Detecting the direction is easy - the table simply goes in the other
|
||||||
|
* direction (read up instead of down).
|
||||||
|
*
|
||||||
|
* To decode this, we use a simple state machine. Every time the output
|
||||||
|
* code changes, it follows state, until finally a full steps worth of
|
||||||
|
* code is received (in the correct order). At the final 0-0, it returns
|
||||||
|
* a value indicating a step in one direction or the other.
|
||||||
|
*
|
||||||
|
* It's also possible to use 'half-step' mode. This just emits an event
|
||||||
|
* at both the 0-0 and 1-1 positions. This might be useful for some
|
||||||
|
* encoders where you want to detect all positions.
|
||||||
|
*
|
||||||
|
* If an invalid state happens (for example we go from '0-1' straight
|
||||||
|
* to '1-0'), the state machine resets to the start until 0-0 and the
|
||||||
|
* next valid codes occur.
|
||||||
|
*
|
||||||
|
* The biggest advantage of using a state machine over other algorithms
|
||||||
|
* is that this has inherent debounce built in. Other algorithms emit spurious
|
||||||
|
* output with switch bounce, but this one will simply flip between
|
||||||
|
* sub-states until the bounce settles, then continue along the state
|
||||||
|
* machine.
|
||||||
|
* A side effect of debounce is that fast rotations can cause steps to
|
||||||
|
* be skipped. By not requiring debounce, fast rotations can be accurately
|
||||||
|
* measured.
|
||||||
|
* Another advantage is the ability to properly handle bad state, such
|
||||||
|
* as due to EMI, etc.
|
||||||
|
* It is also a lot simpler than others - a static state table and less
|
||||||
|
* than 10 lines of logic.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rotary_encoder.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
#define TAG "rotary_encoder"
|
||||||
|
|
||||||
|
//#define ROTARY_ENCODER_DEBUG
|
||||||
|
|
||||||
|
// Use a single-item queue so that the last value can be easily overwritten by the interrupt handler
|
||||||
|
#define EVENT_QUEUE_LENGTH 1
|
||||||
|
|
||||||
|
#define TABLE_ROWS 7
|
||||||
|
|
||||||
|
#define DIR_NONE 0x0 // No complete step yet.
|
||||||
|
#define DIR_CW 0x10 // Clockwise step.
|
||||||
|
#define DIR_CCW 0x20 // Anti-clockwise step.
|
||||||
|
|
||||||
|
// Create the half-step state table (emits a code at 00 and 11)
|
||||||
|
#define R_START 0x0
|
||||||
|
#define H_CCW_BEGIN 0x1
|
||||||
|
#define H_CW_BEGIN 0x2
|
||||||
|
#define H_START_M 0x3
|
||||||
|
#define H_CW_BEGIN_M 0x4
|
||||||
|
#define H_CCW_BEGIN_M 0x5
|
||||||
|
|
||||||
|
static const uint8_t _ttable_half[TABLE_ROWS][TABLE_COLS] = {
|
||||||
|
// 00 01 10 11 // BA
|
||||||
|
{H_START_M, H_CW_BEGIN, H_CCW_BEGIN, R_START}, // R_START (00)
|
||||||
|
{H_START_M | DIR_CCW, R_START, H_CCW_BEGIN, R_START}, // H_CCW_BEGIN
|
||||||
|
{H_START_M | DIR_CW, H_CW_BEGIN, R_START, R_START}, // H_CW_BEGIN
|
||||||
|
{H_START_M, H_CCW_BEGIN_M, H_CW_BEGIN_M, R_START}, // H_START_M (11)
|
||||||
|
{H_START_M, H_START_M, H_CW_BEGIN_M, R_START | DIR_CW}, // H_CW_BEGIN_M
|
||||||
|
{H_START_M, H_CCW_BEGIN_M, H_START_M, R_START | DIR_CCW}, // H_CCW_BEGIN_M
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create the full-step state table (emits a code at 00 only)
|
||||||
|
# define F_CW_FINAL 0x1
|
||||||
|
# define F_CW_BEGIN 0x2
|
||||||
|
# define F_CW_NEXT 0x3
|
||||||
|
# define F_CCW_BEGIN 0x4
|
||||||
|
# define F_CCW_FINAL 0x5
|
||||||
|
# define F_CCW_NEXT 0x6
|
||||||
|
|
||||||
|
static const uint8_t _ttable_full[TABLE_ROWS][TABLE_COLS] = {
|
||||||
|
// 00 01 10 11 // BA
|
||||||
|
{R_START, F_CW_BEGIN, F_CCW_BEGIN, R_START}, // R_START
|
||||||
|
{F_CW_NEXT, R_START, F_CW_FINAL, R_START | DIR_CW}, // F_CW_FINAL
|
||||||
|
{F_CW_NEXT, F_CW_BEGIN, R_START, R_START}, // F_CW_BEGIN
|
||||||
|
{F_CW_NEXT, F_CW_BEGIN, F_CW_FINAL, R_START}, // F_CW_NEXT
|
||||||
|
{F_CCW_NEXT, R_START, F_CCW_BEGIN, R_START}, // F_CCW_BEGIN
|
||||||
|
{F_CCW_NEXT, F_CCW_FINAL, R_START, R_START | DIR_CCW}, // F_CCW_FINAL
|
||||||
|
{F_CCW_NEXT, F_CCW_FINAL, F_CCW_BEGIN, R_START}, // F_CCW_NEXT
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t _process(rotary_encoder_info_t * info)
|
||||||
|
{
|
||||||
|
uint8_t event = 0;
|
||||||
|
if (info != NULL)
|
||||||
|
{
|
||||||
|
// Get state of input pins.
|
||||||
|
uint8_t pin_state = (gpio_get_level(info->pin_b) << 1) | gpio_get_level(info->pin_a);
|
||||||
|
|
||||||
|
// Determine new state from the pins and state table.
|
||||||
|
#ifdef ROTARY_ENCODER_DEBUG
|
||||||
|
uint8_t old_state = info->table_state;
|
||||||
|
#endif
|
||||||
|
info->table_state = info->table[info->table_state & 0xf][pin_state];
|
||||||
|
|
||||||
|
// Return emit bits, i.e. the generated event.
|
||||||
|
event = info->table_state & 0x30;
|
||||||
|
#ifdef ROTARY_ENCODER_DEBUG
|
||||||
|
ESP_EARLY_LOGD(TAG, "BA %d%d, state 0x%02x, new state 0x%02x, event 0x%02x",
|
||||||
|
pin_state >> 1, pin_state & 1, old_state, info->table_state, event);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _isr_rotenc(void * args)
|
||||||
|
{
|
||||||
|
rotary_encoder_info_t * info = (rotary_encoder_info_t *)args;
|
||||||
|
uint8_t event = _process(info);
|
||||||
|
bool send_event = false;
|
||||||
|
|
||||||
|
switch (event)
|
||||||
|
{
|
||||||
|
case DIR_CW:
|
||||||
|
++info->state.position;
|
||||||
|
info->state.direction = ROTARY_ENCODER_DIRECTION_CLOCKWISE;
|
||||||
|
send_event = true;
|
||||||
|
break;
|
||||||
|
case DIR_CCW:
|
||||||
|
--info->state.position;
|
||||||
|
info->state.direction = ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE;
|
||||||
|
send_event = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (send_event && info->queue)
|
||||||
|
{
|
||||||
|
rotary_encoder_event_t queue_event =
|
||||||
|
{
|
||||||
|
.state =
|
||||||
|
{
|
||||||
|
.position = info->state.position,
|
||||||
|
.direction = info->state.direction,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
BaseType_t task_woken = pdFALSE;
|
||||||
|
xQueueOverwriteFromISR(info->queue, &queue_event, &task_woken);
|
||||||
|
if (task_woken)
|
||||||
|
{
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if (info)
|
||||||
|
{
|
||||||
|
info->pin_a = pin_a;
|
||||||
|
info->pin_b = pin_b;
|
||||||
|
info->table = &_ttable_full[0]; //enable_half_step ? &_ttable_half[0] : &_ttable_full[0];
|
||||||
|
info->table_state = R_START;
|
||||||
|
info->state.position = 0;
|
||||||
|
info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
|
||||||
|
|
||||||
|
// configure GPIOs
|
||||||
|
gpio_pad_select_gpio(info->pin_a);
|
||||||
|
gpio_set_pull_mode(info->pin_a, GPIO_PULLUP_ONLY);
|
||||||
|
gpio_set_direction(info->pin_a, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_intr_type(info->pin_a, GPIO_INTR_ANYEDGE);
|
||||||
|
|
||||||
|
gpio_pad_select_gpio(info->pin_b);
|
||||||
|
gpio_set_pull_mode(info->pin_b, GPIO_PULLUP_ONLY);
|
||||||
|
gpio_set_direction(info->pin_b, GPIO_MODE_INPUT);
|
||||||
|
gpio_set_intr_type(info->pin_b, GPIO_INTR_ANYEDGE);
|
||||||
|
|
||||||
|
// install interrupt handlers
|
||||||
|
gpio_isr_handler_add(info->pin_a, _isr_rotenc, info);
|
||||||
|
gpio_isr_handler_add(info->pin_b, _isr_rotenc, info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "info is NULL");
|
||||||
|
err = ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if (info)
|
||||||
|
{
|
||||||
|
info->table = enable ? &_ttable_half[0] : &_ttable_full[0];
|
||||||
|
info->table_state = R_START;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "info is NULL");
|
||||||
|
err = ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if (info)
|
||||||
|
{
|
||||||
|
gpio_num_t temp = info->pin_a;
|
||||||
|
info->pin_a = info->pin_b;
|
||||||
|
info->pin_b = temp;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "info is NULL");
|
||||||
|
err = ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if (info)
|
||||||
|
{
|
||||||
|
gpio_isr_handler_remove(info->pin_a);
|
||||||
|
gpio_isr_handler_remove(info->pin_b);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "info is NULL");
|
||||||
|
err = ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueHandle_t rotary_encoder_create_queue(void)
|
||||||
|
{
|
||||||
|
return xQueueCreate(EVENT_QUEUE_LENGTH, sizeof(rotary_encoder_event_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if (info)
|
||||||
|
{
|
||||||
|
info->queue = queue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "info is NULL");
|
||||||
|
err = ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if (info && state)
|
||||||
|
{
|
||||||
|
// make a snapshot of the state
|
||||||
|
state->position = info->state.position;
|
||||||
|
state->direction = info->state.direction;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "info and/or state is NULL");
|
||||||
|
err = ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info)
|
||||||
|
{
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if (info)
|
||||||
|
{
|
||||||
|
info->state.position = 0;
|
||||||
|
info->state.direction = ROTARY_ENCODER_DIRECTION_NOT_SET;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "info is NULL");
|
||||||
|
err = ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
172
espmusicmouse/lib/rotary_encoder/rotary_encoder.h
Normal file
172
espmusicmouse/lib/rotary_encoder/rotary_encoder.h
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019 David Antliff
|
||||||
|
* Copyright 2011 Ben Buxton
|
||||||
|
*
|
||||||
|
* This file is part of the esp32-rotary-encoder component.
|
||||||
|
*
|
||||||
|
* esp32-rotary-encoder is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* esp32-rotary-encoder 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with esp32-rotary-encoder. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file rotary_encoder.h
|
||||||
|
* @brief Interface definitions for the ESP32-compatible Incremental Rotary Encoder component.
|
||||||
|
*
|
||||||
|
* This component provides a means to interface with a typical rotary encoder such as the EC11 or LPD3806.
|
||||||
|
* These encoders produce a quadrature signal on two outputs, which can be used to track the position and
|
||||||
|
* direction as movement occurs.
|
||||||
|
*
|
||||||
|
* This component provides functions to initialise the GPIOs and install appropriate interrupt handlers to
|
||||||
|
* track a single device's position. An event queue is used to provide a way for a user task to obtain
|
||||||
|
* position information from the component as it is generated.
|
||||||
|
*
|
||||||
|
* Note that the queue is of length 1, and old values will be overwritten. Using a longer queue is
|
||||||
|
* possible with some minor modifications however newer values are lost if the queue overruns. A circular
|
||||||
|
* buffer where old values are lost would be better (maybe StreamBuffer in FreeRTOS 10.0.0?).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ROTARY_ENCODER_H
|
||||||
|
#define ROTARY_ENCODER_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef int32_t rotary_encoder_position_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enum representing the direction of rotation.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
ROTARY_ENCODER_DIRECTION_NOT_SET = 0, ///< Direction not yet known (stationary since reset)
|
||||||
|
ROTARY_ENCODER_DIRECTION_CLOCKWISE,
|
||||||
|
ROTARY_ENCODER_DIRECTION_COUNTER_CLOCKWISE,
|
||||||
|
} rotary_encoder_direction_t;
|
||||||
|
|
||||||
|
// Used internally
|
||||||
|
///@cond INTERNAL
|
||||||
|
#define TABLE_COLS 4
|
||||||
|
typedef uint8_t table_row_t[TABLE_COLS];
|
||||||
|
///@endcond
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Struct represents the current state of the device in terms of incremental position and direction of last movement
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rotary_encoder_position_t position; ///< Numerical position since reset. This value increments on clockwise rotation, and decrements on counter-clockewise rotation. Counts full or half steps depending on mode. Set to zero on reset.
|
||||||
|
rotary_encoder_direction_t direction; ///< Direction of last movement. Set to NOT_SET on reset.
|
||||||
|
} rotary_encoder_state_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Struct carries all the information needed by this driver to manage the rotary encoder device.
|
||||||
|
* The fields of this structure should not be accessed directly.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
gpio_num_t pin_a; ///< GPIO for Signal A from the rotary encoder device
|
||||||
|
gpio_num_t pin_b; ///< GPIO for Signal B from the rotary encoder device
|
||||||
|
QueueHandle_t queue; ///< Handle for event queue, created by ::rotary_encoder_create_queue
|
||||||
|
const table_row_t * table; ///< Pointer to active state transition table
|
||||||
|
uint8_t table_state; ///< Internal state
|
||||||
|
volatile rotary_encoder_state_t state; ///< Device state
|
||||||
|
} rotary_encoder_info_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Struct represents a queued event, used to communicate current position to a waiting task
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
rotary_encoder_state_t state; ///< The device state corresponding to this event
|
||||||
|
} rotary_encoder_event_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialise the rotary encoder device with the specified GPIO pins and full step increments.
|
||||||
|
* This function will set up the GPIOs as needed,
|
||||||
|
* Note: this function assumes that gpio_install_isr_service(0) has already been called.
|
||||||
|
* @param[in, out] info Pointer to allocated rotary encoder info structure.
|
||||||
|
* @param[in] pin_a GPIO number for rotary encoder output A.
|
||||||
|
* @param[in] pin_b GPIO number for rotary encoder output B.
|
||||||
|
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||||
|
*/
|
||||||
|
esp_err_t rotary_encoder_init(rotary_encoder_info_t * info, gpio_num_t pin_a, gpio_num_t pin_b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enable half-stepping mode. This generates twice as many counted steps per rotation.
|
||||||
|
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||||
|
* @param[in] enable If true, count half steps. If false, only count full steps.
|
||||||
|
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||||
|
*/
|
||||||
|
esp_err_t rotary_encoder_enable_half_steps(rotary_encoder_info_t * info, bool enable);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reverse (flip) the sense of the direction.
|
||||||
|
* Use this if clockwise/counterclockwise are not what you expect.
|
||||||
|
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||||
|
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||||
|
*/
|
||||||
|
esp_err_t rotary_encoder_flip_direction(rotary_encoder_info_t * info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the interrupt handlers installed by ::rotary_encoder_init.
|
||||||
|
* Note: GPIOs will be left in the state they were configured by ::rotary_encoder_init.
|
||||||
|
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||||
|
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||||
|
*/
|
||||||
|
esp_err_t rotary_encoder_uninit(rotary_encoder_info_t * info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a queue handle suitable for use as an event queue.
|
||||||
|
* @return A handle to a new queue suitable for use as an event queue.
|
||||||
|
*/
|
||||||
|
QueueHandle_t rotary_encoder_create_queue(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the driver to use the specified queue as an event queue.
|
||||||
|
* It is recommended that a queue constructed by ::rotary_encoder_create_queue is used.
|
||||||
|
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||||
|
* @param[in] queue Handle to queue suitable for use as an event queue. See ::rotary_encoder_create_queue.
|
||||||
|
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||||
|
*/
|
||||||
|
esp_err_t rotary_encoder_set_queue(rotary_encoder_info_t * info, QueueHandle_t queue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the current position of the rotary encoder.
|
||||||
|
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||||
|
* @param[in, out] state Pointer to an allocated rotary_encoder_state_t struct that will
|
||||||
|
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||||
|
*/
|
||||||
|
esp_err_t rotary_encoder_get_state(const rotary_encoder_info_t * info, rotary_encoder_state_t * state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset the current position of the rotary encoder to zero.
|
||||||
|
* @param[in] info Pointer to initialised rotary encoder info structure.
|
||||||
|
* @return ESP_OK if successful, ESP_FAIL or ESP_ERR_* if an error occurred.
|
||||||
|
*/
|
||||||
|
esp_err_t rotary_encoder_reset(rotary_encoder_info_t * info);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // ROTARY_ENCODER_H
|
||||||
31
espmusicmouse/platformio.ini
Normal file
31
espmusicmouse/platformio.ini
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
;PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
data_dir = data
|
||||||
|
default_envs = esp32
|
||||||
|
|
||||||
|
|
||||||
|
[env:esp32]
|
||||||
|
platform = espressif32
|
||||||
|
#platform_packages =
|
||||||
|
# framework-arduinoespressif32@https://github.com/espressif/arduino-esp32
|
||||||
|
board = esp-wrover-kit
|
||||||
|
board_upload.flash_size = "4MB"
|
||||||
|
#platform = espressif8266
|
||||||
|
#board = esp_wroom_02
|
||||||
|
#build_flags = -Wl,-Teagle.flash.2m1m.ld
|
||||||
|
build_flags = -DPLATFORM_ESP32
|
||||||
|
framework = arduino
|
||||||
|
monitor_port = /dev/ttyUSB0
|
||||||
|
upload_port = /dev/ttyUSB0
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
miguelbalboa/MFRC522
|
||||||
88
espmusicmouse/src/main.cpp
Normal file
88
espmusicmouse/src/main.cpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "rc522.h"
|
||||||
|
#include "SPI.h"
|
||||||
|
#include <MFRC522.h>
|
||||||
|
#include "LedControl.h"
|
||||||
|
#include "rotary_encoder.h"
|
||||||
|
|
||||||
|
MFRC522 rfid(5); // Instance of the class
|
||||||
|
|
||||||
|
MFRC522::MIFARE_Key key;
|
||||||
|
|
||||||
|
LedStrip led(144, 13);
|
||||||
|
|
||||||
|
void tag_handler(uint8_t *sn)
|
||||||
|
{
|
||||||
|
// serial number is always 5 bytes long
|
||||||
|
if (sn != nullptr)
|
||||||
|
{
|
||||||
|
|
||||||
|
Serial.printf("Tag: %#x %#x %#x %#x %#x\n",
|
||||||
|
sn[0], sn[1], sn[2], sn[3], sn[4]);
|
||||||
|
if (sn[4] == 0x30)
|
||||||
|
{
|
||||||
|
Serial.println("Fuchs");
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////led.setAll(0, 0, 254, 0);
|
||||||
|
led.setRange(led.numLeds() - 40, led.numLeds(), 0, 0, 243, 0);
|
||||||
|
}
|
||||||
|
if (sn[4] == 0xf0)
|
||||||
|
{
|
||||||
|
Serial.println("Eule");
|
||||||
|
//led.setAll(0, 0, 0, 254);
|
||||||
|
led.setRange(led.numLeds() - 40, led.numLeds(), 0, 0, 0, 254);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
led.clear();
|
||||||
|
Serial.println("Nichts");
|
||||||
|
}
|
||||||
|
led.transmit();
|
||||||
|
}
|
||||||
|
QueueHandle_t event_queue;
|
||||||
|
|
||||||
|
rotary_encoder_info_t info;
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(115200);
|
||||||
|
led.begin();
|
||||||
|
digitalWrite(5, 1);
|
||||||
|
|
||||||
|
const rc522_start_args_t start_args = {
|
||||||
|
19, // MISO
|
||||||
|
18, // MOSI
|
||||||
|
22, // SCK
|
||||||
|
23, // SDA
|
||||||
|
VSPI_HOST,
|
||||||
|
&tag_handler,
|
||||||
|
125, // scan_interval_ms
|
||||||
|
8 * 1024, // stacksize
|
||||||
|
4 // task priority
|
||||||
|
};
|
||||||
|
rc522_start(start_args);
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||||
|
ESP_ERROR_CHECK(rotary_encoder_init(&info, GPIO_NUM_12, GPIO_NUM_14));
|
||||||
|
ESP_ERROR_CHECK(rotary_encoder_enable_half_steps(&info, false));
|
||||||
|
event_queue = rotary_encoder_create_queue();
|
||||||
|
ESP_ERROR_CHECK(rotary_encoder_set_queue(&info, event_queue));
|
||||||
|
|
||||||
|
pinMode (2, OUTPUT);
|
||||||
|
digitalWrite(2, HIGH);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
rotary_encoder_event_t event = {0};
|
||||||
|
if (xQueueReceive(event_queue, &event, 1000 / portTICK_PERIOD_MS) == pdTRUE)
|
||||||
|
{
|
||||||
|
Serial.printf("Event: position %d, direction %s\n", event.state.position,
|
||||||
|
event.state.direction ? (event.state.direction == ROTARY_ENCODER_DIRECTION_CLOCKWISE ? "CW" : "CCW") : "NOT_SET");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
Serial.println(touchRead(15)); // get value of Touch 0 pin = GPIO 4
|
||||||
|
delay(500);
|
||||||
|
//digitalWrite(2, HIGH);
|
||||||
|
}
|
||||||
64
espmusicmouse/src/mfrc_version.h
Normal file
64
espmusicmouse/src/mfrc_version.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <MFRC522.h>
|
||||||
|
|
||||||
|
#define RST_PIN 5 // Configurable, see typical pin layout above
|
||||||
|
#define SS_PIN 23 // Configurable, see typical pin layout above
|
||||||
|
|
||||||
|
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance
|
||||||
|
|
||||||
|
//*****************************************************************************************//
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
SPI.begin(22, 19, 18, 23); // Init SPI bus
|
||||||
|
mfrc522.PCD_Init(); // Init MFRC522 card
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Helper routine to dump a byte array as hex values to Serial.
|
||||||
|
*/
|
||||||
|
void PrintHex(byte *buffer, byte bufferSize) {
|
||||||
|
for (byte i = 0; i < bufferSize; i++) {
|
||||||
|
Serial.print(buffer[i] < 0x10 ? " 0" : " ");
|
||||||
|
Serial.print(buffer[i], HEX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//*****************************************************************************************//
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
|
||||||
|
// Look for new cards
|
||||||
|
if ( !mfrc522.PICC_IsNewCardPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( !mfrc522.PICC_ReadCardSerial()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//mfrc522.PICC_DumpDetailsToSerial(&(mfrc522.uid)); //dump some details about the card
|
||||||
|
|
||||||
|
Serial.print("NewCard");
|
||||||
|
PrintHex(mfrc522.uid.uidByte, mfrc522.uid.size);
|
||||||
|
Serial.println("");
|
||||||
|
|
||||||
|
// Check if Card was removed
|
||||||
|
bool cardRemoved = false;
|
||||||
|
int counter = 0;
|
||||||
|
bool current, previous;
|
||||||
|
previous = !mfrc522.PICC_IsNewCardPresent();
|
||||||
|
|
||||||
|
while(!cardRemoved){
|
||||||
|
current =!mfrc522.PICC_IsNewCardPresent();
|
||||||
|
|
||||||
|
if (current && previous) counter++;
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
cardRemoved = (counter>2);
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("Card was removed");
|
||||||
|
delay(500); //change value if you want to read cards faster
|
||||||
|
mfrc522.PICC_HaltA();
|
||||||
|
mfrc522.PCD_StopCrypto1();
|
||||||
|
}
|
||||||
BIN
figures/fox.blend
Normal file
BIN
figures/fox.blend
Normal file
Binary file not shown.
72139
figures/fox.obj
Normal file
72139
figures/fox.obj
Normal file
File diff suppressed because it is too large
Load Diff
BIN
figures/owl.blend
Normal file
BIN
figures/owl.blend
Normal file
Binary file not shown.
81170
figures/owl.obj
Normal file
81170
figures/owl.obj
Normal file
File diff suppressed because it is too large
Load Diff
BIN
musicmouse.FCStd
Normal file
BIN
musicmouse.FCStd
Normal file
Binary file not shown.
Reference in New Issue
Block a user