Initial commit
This commit is contained in:
commit
6a5d4c463c
|
@ -0,0 +1,5 @@
|
|||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"*.tcc": "cpp",
|
||||
"deque": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"vector": "cpp",
|
||||
"system_error": "cpp"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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_;
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
//**************************************************************************//
|
|
@ -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 */
|
||||
|
||||
|
||||
//**************************************************************************//
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Loading…
Reference in New Issue