Compare commits
28 Commits
ldq_branch
...
master
Author | SHA1 | Date |
---|---|---|
ZhaoJie | d6806c5cf9 | 1 year ago |
Lingzijie | 2282c1242d | 1 year ago |
Lingzijie | 5f4057aff7 | 1 year ago |
Lingzijie | 1cd2362ed3 | 1 year ago |
ZhaoJie | 1d4a8e7725 | 1 year ago |
ZhaoJie | c45b3f7924 | 1 year ago |
Lingzijie | 33bb0e8357 | 1 year ago |
Lingzijie | 1c532b9d3e | 1 year ago |
ZhaoJie | bec1f0e297 | 1 year ago |
ZhaoJie | 37bade9cb3 | 1 year ago |
pgt6qx4ec | d3c5d007a8 | 1 year ago |
pgt6qx4ec | f91b779475 | 1 year ago |
pgt6qx4ec | 76e9ddf771 | 1 year ago |
pgt6qx4ec | d75ae74a58 | 1 year ago |
pgt6qx4ec | 8ea4421edb | 1 year ago |
pgt6qx4ec | e71e1b45c3 | 1 year ago |
pgt6qx4ec | b17f1ec3e6 | 1 year ago |
pgt6qx4ec | d17f89c464 | 1 year ago |
p2hr7zy58 | 9be34b5e36 | 1 year ago |
p2hr7zy58 | 44b714781a | 1 year ago |
p2hr7zy58 | 4e42799330 | 1 year ago |
p2hr7zy58 | 6a154d6971 | 1 year ago |
p2hr7zy58 | 5eb82e9af9 | 1 year ago |
ZhaoJie | a6468e4142 | 1 year ago |
ZhaoJie | 872e7a9c58 | 1 year ago |
ZhaoJie | 2c928565e0 | 1 year ago |
ZhaoJie | 34bdec05c7 | 1 year ago |
pgt6qx4ec | 84756dc619 | 1 year ago |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 172 KiB |
@ -0,0 +1,171 @@
|
|||||||
|
# EspSoftwareSerial
|
||||||
|
|
||||||
|
## Implementation of the Arduino software serial library for the ESP8266 / ESP32 family
|
||||||
|
|
||||||
|
This fork implements interrupt service routine best practice.
|
||||||
|
In the receive interrupt, instead of blocking for whole bytes
|
||||||
|
at a time - voiding any near-realtime behavior of the CPU - only level
|
||||||
|
change and timestamp are recorded. The more time consuming phase
|
||||||
|
detection and byte assembly are done in the main code.
|
||||||
|
|
||||||
|
Except at high bitrates, depending on other ongoing activity,
|
||||||
|
interrupts in particular, this software serial adapter
|
||||||
|
supports full duplex receive and send. At high bitrates (115200bps)
|
||||||
|
send bit timing can be improved at the expense of blocking concurrent
|
||||||
|
full duplex receives, with the
|
||||||
|
`EspSoftwareSerial::UART::enableIntTx(false)` function call.
|
||||||
|
|
||||||
|
The same functionality is given as the corresponding AVR library but
|
||||||
|
several instances can be active at the same time. Speed up to 115200 baud
|
||||||
|
is supported. Besides a constructor compatible to the AVR SoftwareSerial class,
|
||||||
|
and updated constructor that takes no arguments exists, instead the `begin()`
|
||||||
|
function can handle the pin assignments and logic inversion.
|
||||||
|
It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer.
|
||||||
|
This way, it is a better drop-in replacement for the hardware serial APIs on the ESP MCUs.
|
||||||
|
|
||||||
|
Please note that due to the fact that the ESPs always have other activities
|
||||||
|
ongoing, there will be some inexactness in interrupt timings. This may
|
||||||
|
lead to inevitable, but few, bit errors when having heavy data traffic
|
||||||
|
at high baud rates.
|
||||||
|
|
||||||
|
This library supports ESP8266, ESP32, ESP32-S2 and ESP32-C3 devices.
|
||||||
|
|
||||||
|
## Resource optimization
|
||||||
|
|
||||||
|
The memory footprint can be optimized to just fit the amount of expected
|
||||||
|
incoming asynchronous data.
|
||||||
|
For this, the `EspSoftwareSerial::UART` constructor provides two arguments. First, the
|
||||||
|
octet buffer capacity for assembled received octets can be set. Read calls are
|
||||||
|
satisfied from this buffer, freeing it in return.
|
||||||
|
Second, the signal edge detection buffer of 32bit fields can be resized.
|
||||||
|
One octet may require up to to 10 fields, but fewer may be needed,
|
||||||
|
depending on the bit pattern. Any read or write calls check this buffer
|
||||||
|
to assemble received octets, thus promoting completed octets to the octet
|
||||||
|
buffer, freeing fields in the edge detection buffer.
|
||||||
|
|
||||||
|
Look at the swsertest.ino example. There, on reset, ASCII characters ' ' to 'z'
|
||||||
|
are sent. This happens not as a block write, but in a single write call per
|
||||||
|
character. As the example uses a local loopback wire, every outgoing bit is
|
||||||
|
immediately received back. Therefore, any single write call causes up to
|
||||||
|
10 fields - depending on the exact bit pattern - to be occupied in the signal
|
||||||
|
edge detection buffer. In turn, as explained before, each single write call
|
||||||
|
also causes received bit assembly to be performed, promoting these bits from
|
||||||
|
the signal edge detection buffer to the octet buffer as soon as possible.
|
||||||
|
Explaining by way of contrast, if during a a single write call, perhaps because
|
||||||
|
of using block writing, more than a single octet is received, there will be a
|
||||||
|
need for more than 10 fields in the signal edge detection buffer.
|
||||||
|
The necessary capacity of the octet buffer only depends on the amount of incoming
|
||||||
|
data until the next read call.
|
||||||
|
|
||||||
|
For the swsertest.ino example, this results in the following optimized
|
||||||
|
constructor arguments to spend only the minimum RAM on buffers required:
|
||||||
|
|
||||||
|
The octet buffer capacity (`bufCapacity`) is 95 (93 characters net plus two tolerance).
|
||||||
|
The signal edge detection buffer capacity (`isrBufCapacity`) is 11, as each
|
||||||
|
single octet can have up to 11 bits on the wire,
|
||||||
|
which are immediately received during the write, and each
|
||||||
|
write call causes the signal edge detection to promote the previously sent and
|
||||||
|
received bits to the octet buffer.
|
||||||
|
|
||||||
|
In a more generalized scenario, calculate the bits (use message size in octets
|
||||||
|
times 10) that may be asynchronously received to determine the value for
|
||||||
|
`isrBufCapacity` in the constructor. Also use the number of received octets
|
||||||
|
that must be buffered for reading as the value of `bufCapacity`.
|
||||||
|
The more frequently your code calls write or read functions, the greater the
|
||||||
|
chances are that you can reduce the `isrBufCapacity` footprint without losing data,
|
||||||
|
and each time you call read to fetch from the octet buffer, you reduce the
|
||||||
|
need for space there.
|
||||||
|
|
||||||
|
## EspSoftwareSerial::Config and parity
|
||||||
|
The configuration of the data stream is done via a `EspSoftwareSerial::Config`
|
||||||
|
argument to `begin()`. Word lengths can be set to between 5 and 8 bits, parity
|
||||||
|
can be N(one), O(dd) or E(ven) and 1 or 2 stop bits can be used. The default is
|
||||||
|
`SWSERIAL_8N1` using 8 bits, no parity and 1 stop bit but any combination can
|
||||||
|
be used, e.g. `SWSERIAL_7E2`. If using EVEN or ODD parity, any parity errors
|
||||||
|
can be detected with the `readParity()` and `parityEven()` or `parityOdd()`
|
||||||
|
functions respectively. Note that the result of `readParity()` always applies
|
||||||
|
to the preceding `read()` or `peek()` call, and is undefined if they report
|
||||||
|
no data or an error.
|
||||||
|
|
||||||
|
To allow flexible 9-bit and data/addressing protocols, the additional parity
|
||||||
|
modes MARK and SPACE are also available. Furthermore, the parity mode can be
|
||||||
|
individually set in each call to `write()`.
|
||||||
|
|
||||||
|
This allows a simple implementation of protocols where the parity bit is used to
|
||||||
|
distinguish between data and addresses/commands ("9-bit" protocols). First set
|
||||||
|
up EspSoftwareSerial::UART with parity mode SPACE, e.g. `SWSERIAL_8S1`. This will add a
|
||||||
|
parity bit to every byte sent, setting it to logical zero (SPACE parity).
|
||||||
|
|
||||||
|
To detect incoming bytes with the parity bit set (MARK parity), use the
|
||||||
|
`readParity()` function. To send a byte with the parity bit set, just add
|
||||||
|
`MARK` as the second argument when writing, e.g. `write(ch, SWSERIAL_PARITY_MARK)`.
|
||||||
|
|
||||||
|
## Checking for correct pin selection / configuration
|
||||||
|
In general, most pins on the ESP8266 and ESP32 devices can be used by EspSoftwareSerial,
|
||||||
|
however each device has a number of pins that have special functions or require careful
|
||||||
|
handling to prevent undesirable situations, for example they are connected to the
|
||||||
|
on-board SPI flash memory or they are used to determine boot and programming modes
|
||||||
|
after powerup or brownouts. These pins are not able to be configured by this library.
|
||||||
|
|
||||||
|
The exact list for each device can be found in the
|
||||||
|
[ESP32 data sheet](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf)
|
||||||
|
in sections 2.2 (Pin Descriptions) and 2.4 (Strapping pins). There is a discussion
|
||||||
|
dedicated to the use of GPIO12 in this
|
||||||
|
[note about GPIO12](https://github.com/espressif/esp-idf/tree/release/v3.2/examples/storage/sd_card#note-about-gpio12).
|
||||||
|
Refer to the `isValidPin()`, `isValidRxPin()` and `isValidTxPin()`
|
||||||
|
functions in the `EspSoftwareSerial::GpioCapabilities` class for the GPIO restrictions
|
||||||
|
enforced by this library by default.
|
||||||
|
|
||||||
|
The easiest and safest method is to test the object returned at runtime, to see if
|
||||||
|
it is valid. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
#define MYPORT_TX 12
|
||||||
|
#define MYPORT_RX 13
|
||||||
|
|
||||||
|
EspSoftwareSerial::UART myPort;
|
||||||
|
|
||||||
|
[...]
|
||||||
|
|
||||||
|
Serial.begin(115200); // Standard hardware serial port
|
||||||
|
|
||||||
|
myPort.begin(38400, SWSERIAL_8N1, MYPORT_RX, MYPORT_TX, false);
|
||||||
|
if (!myPort) { // If the object did not initialize, then its configuration is invalid
|
||||||
|
Serial.println("Invalid EspSoftwareSerial pin configuration, check config");
|
||||||
|
while (1) { // Don't continue with invalid configuration
|
||||||
|
delay (1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using and updating EspSoftwareSerial in the esp8266com/esp8266 Arduino build environment
|
||||||
|
|
||||||
|
EspSoftwareSerial is both part of the BSP download for ESP8266 in Arduino,
|
||||||
|
and it is set up as a Git submodule in the esp8266 source tree,
|
||||||
|
specifically in `.../esp8266/libraries/SoftwareSerial` when using a Github
|
||||||
|
repository clone in your Arduino sketchbook hardware directory.
|
||||||
|
This supersedes any version of EspSoftwareSerial installed for instance via
|
||||||
|
the Arduino library manager, it is not required to install EspSoftwareSerial
|
||||||
|
for the ESP8266 separately at all, but doing so has ill effect.
|
||||||
|
|
||||||
|
The responsible maintainer of the esp8266 repository has kindly shared the
|
||||||
|
following command line instructions to use, if one wishes to manually
|
||||||
|
update EspSoftwareSerial to a newer release than pulled in via the ESP8266 Arduino BSP:
|
||||||
|
|
||||||
|
To update esp8266/arduino EspSoftwareSerial submodule to lastest master:
|
||||||
|
|
||||||
|
Clean it (optional):
|
||||||
|
```shell
|
||||||
|
$ rm -rf libraries/SoftwareSerial
|
||||||
|
$ git submodule update --init
|
||||||
|
```
|
||||||
|
Now update it:
|
||||||
|
```shell
|
||||||
|
$ cd libraries/SoftwareSerial
|
||||||
|
$ git checkout master
|
||||||
|
$ git pull
|
||||||
|
```
|
@ -0,0 +1,71 @@
|
|||||||
|
#include "SoftwareSerial.h"
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D8 (5)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D7 (23)
|
||||||
|
#define D6 (19)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
EspSoftwareSerial::UART swSer;
|
||||||
|
#ifdef ESP8266
|
||||||
|
auto logSer = EspSoftwareSerial::UART(-1, TX);
|
||||||
|
auto hwSer = Serial;
|
||||||
|
#else
|
||||||
|
auto logSer = Serial;
|
||||||
|
auto hwSer = Serial1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr uint32_t TESTBPS = 115200;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
delay(2000);
|
||||||
|
#ifdef ESP8266
|
||||||
|
hwSer.begin(TESTBPS, ::SERIAL_8N1);
|
||||||
|
hwSer.swap();
|
||||||
|
#else
|
||||||
|
hwSer.begin(TESTBPS, ::SERIAL_8N1, D6, D5);
|
||||||
|
#endif
|
||||||
|
logSer.begin(115200);
|
||||||
|
logSer.println(PSTR("\nOne Wire Half Duplex Bitpattern and Datarate Test"));
|
||||||
|
swSer.begin(TESTBPS, EspSoftwareSerial::SWSERIAL_8N1, D6, D5);
|
||||||
|
swSer.enableIntTx(true);
|
||||||
|
logSer.println(PSTR("Tx on swSer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t val = 0xff;
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
swSer.write((uint8_t)0x00);
|
||||||
|
swSer.write(val);
|
||||||
|
swSer.write(val);
|
||||||
|
auto start = ESP.getCycleCount();
|
||||||
|
int rxCnt = 0;
|
||||||
|
while (ESP.getCycleCount() - start < ESP.getCpuFreqMHz() * 1000000 / 10) {
|
||||||
|
if (hwSer.available()) {
|
||||||
|
auto rxVal = hwSer.read();
|
||||||
|
if ((!rxCnt && rxVal) || (rxCnt && rxVal != val)) {
|
||||||
|
logSer.printf(PSTR("Rx bit error: tx = 0x%02x, rx = 0x%02x\n"), val, rxVal);
|
||||||
|
}
|
||||||
|
++rxCnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rxCnt != 3) {
|
||||||
|
logSer.printf(PSTR("Rx cnt error, tx = 0x%02x\n"), val);
|
||||||
|
}
|
||||||
|
++val;
|
||||||
|
if (!val) {
|
||||||
|
logSer.println("Starting over");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
// circular_mp_test.cpp : This file contains the 'main' function. Program execution begins and ends there.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <vector>
|
||||||
|
#include "circular_queue/circular_queue_mp.h"
|
||||||
|
|
||||||
|
struct qitem
|
||||||
|
{
|
||||||
|
// produer id
|
||||||
|
int id;
|
||||||
|
// monotonic increasing value
|
||||||
|
int val = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int TOTALMESSAGESTARGET = 60000000;
|
||||||
|
// reserve one thread as consumer
|
||||||
|
const auto THREADS = std::thread::hardware_concurrency() / 2 - 1;
|
||||||
|
const int MESSAGES = TOTALMESSAGESTARGET / THREADS;
|
||||||
|
circular_queue<std::thread> threads(THREADS);
|
||||||
|
circular_queue_mp<qitem> queue(threads.capacity()* MESSAGES / 10);
|
||||||
|
std::vector<int> checks(threads.capacity());
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
std::cerr << "Utilizing " << THREADS << " producer threads" << std::endl;
|
||||||
|
for (int i = 0; i < threads.capacity(); ++i)
|
||||||
|
{
|
||||||
|
threads.push(std::thread([i]() {
|
||||||
|
for (int c = 0; c < MESSAGES;)
|
||||||
|
{
|
||||||
|
// simulate some load
|
||||||
|
auto start = std::chrono::system_clock::now();
|
||||||
|
while (std::chrono::system_clock::now() - start < 1us);
|
||||||
|
if (queue.push({ i, c }))
|
||||||
|
{
|
||||||
|
++c;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//std::cerr << "queue full" << std::endl;
|
||||||
|
//std::this_thread::sleep_for(10us);
|
||||||
|
}
|
||||||
|
//if (0 == c % 10000) std::this_thread::sleep_for(10us);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
for (int o = 0; o < threads.available() * MESSAGES; ++o)
|
||||||
|
{
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
while (!queue.available())
|
||||||
|
{
|
||||||
|
auto starvedFor = std::chrono::system_clock::now() - now;
|
||||||
|
if (starvedFor > 20s) std::cerr << "queue starved for > 20s" << std::endl;
|
||||||
|
//std::this_thread::sleep_for(20ms);
|
||||||
|
}
|
||||||
|
auto item = queue.pop();
|
||||||
|
if (checks[item.id] != item.val)
|
||||||
|
{
|
||||||
|
std::cerr << "item mismatch" << std::endl;
|
||||||
|
}
|
||||||
|
checks[item.id] = item.val + 1;
|
||||||
|
if (0 == item.val % 1000) std::this_thread::sleep_for(100us);
|
||||||
|
}
|
||||||
|
while (threads.available())
|
||||||
|
{
|
||||||
|
auto thread = threads.pop();
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,279 @@
|
|||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
// On ESP8266:
|
||||||
|
// Local EspSoftwareSerial loopback, connect D5 (rx) and D6 (tx).
|
||||||
|
// For local hardware loopback, connect D5 to D8 (tx), D6 to D7 (rx).
|
||||||
|
// For hardware send/sink, connect D7 (rx) and D8 (tx).
|
||||||
|
// Hint: The logger is run at 9600bps such that enableIntTx(true) can remain unchanged. Blocking
|
||||||
|
// interrupts severely impacts the ability of the EspSoftwareSerial devices to operate concurrently
|
||||||
|
// and/or in duplex mode.
|
||||||
|
// Operating in software serial full duplex mode, runs at 19200bps and few errors (~2.5%).
|
||||||
|
// Operating in software serial half duplex mode (both loopback and repeater),
|
||||||
|
// runs at 57600bps with nearly no errors.
|
||||||
|
// Operating loopback in full duplex, and repeater in half duplex, runs at 38400bps with nearly no errors.
|
||||||
|
// On ESP32:
|
||||||
|
// For EspSoftwareSerial or hardware send/sink, connect D5 (rx) and D6 (tx).
|
||||||
|
// Hardware Serial2 defaults to D4 (rx), D3 (tx).
|
||||||
|
// For local hardware loopback, connect D5 (rx) to D3 (tx), D6 (tx) to D4 (rx).
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D8 (5)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D7 (23)
|
||||||
|
#define D6 (19)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Pick only one of HWLOOPBACK, HWSOURCESWSINK, or HWSOURCESINK
|
||||||
|
//#define HWLOOPBACK 1
|
||||||
|
//#define HWSOURCESWSINK 1
|
||||||
|
//#define HWSOURCESINK 1
|
||||||
|
#define HALFDUPLEX 1
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
constexpr int IUTBITRATE = 19200;
|
||||||
|
#else
|
||||||
|
constexpr int IUTBITRATE = 19200;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
constexpr EspSoftwareSerial::Config swSerialConfig = EspSoftwareSerial::SWSERIAL_8E1;
|
||||||
|
constexpr SerialConfig hwSerialConfig = ::SERIAL_8E1;
|
||||||
|
#elif defined(ESP32)
|
||||||
|
constexpr EspSoftwareSerial::Config swSerialConfig = EspSoftwareSerial::SWSERIAL_8E1;
|
||||||
|
constexpr uint32_t hwSerialConfig = ::SERIAL_8E1;
|
||||||
|
#else
|
||||||
|
constexpr unsigned swSerialConfig = 3;
|
||||||
|
#endif
|
||||||
|
constexpr bool invert = false;
|
||||||
|
|
||||||
|
constexpr int BLOCKSIZE = 16; // use fractions of 256
|
||||||
|
|
||||||
|
unsigned long start;
|
||||||
|
const char effTxTxt[] PROGMEM = "eff. tx: ";
|
||||||
|
const char effRxTxt[] PROGMEM = "eff. rx: ";
|
||||||
|
int txCount;
|
||||||
|
int rxCount;
|
||||||
|
int expected;
|
||||||
|
int rxErrors;
|
||||||
|
int rxParityErrors;
|
||||||
|
constexpr int ReportInterval = IUTBITRATE / 8;
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#if defined(HWLOOPBACK) || defined(HWSOURCESWSINK)
|
||||||
|
HardwareSerial& hwSerial(Serial);
|
||||||
|
EspSoftwareSerial::UART serialIUT;
|
||||||
|
EspSoftwareSerial::UART logger;
|
||||||
|
#elif defined(HWSOURCESINK)
|
||||||
|
HardwareSerial& serialIUT(Serial);
|
||||||
|
EspSoftwareSerial::UART logger;
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART serialIUT;
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#endif
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#if defined(HWLOOPBACK) || defined (HWSOURCESWSINK)
|
||||||
|
HardwareSerial& hwSerial(Serial2);
|
||||||
|
EspSoftwareSerial::UART serialIUT;
|
||||||
|
#elif defined(HWSOURCESINK)
|
||||||
|
HardwareSerial& serialIUT(Serial2);
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART serialIUT;
|
||||||
|
#endif
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART serialIUT(14, 12);
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#if defined(HWLOOPBACK) || defined(HWSOURCESINK) || defined(HWSOURCESWSINK)
|
||||||
|
Serial.begin(IUTBITRATE, hwSerialConfig, ::SERIAL_FULL, 1, invert);
|
||||||
|
Serial.swap();
|
||||||
|
Serial.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
logger.begin(9600, EspSoftwareSerial::SWSERIAL_8N1, -1, TX);
|
||||||
|
#else
|
||||||
|
logger.begin(9600);
|
||||||
|
#endif
|
||||||
|
#if !defined(HWSOURCESINK)
|
||||||
|
serialIUT.begin(IUTBITRATE, swSerialConfig, D5, D6, invert, 2 * BLOCKSIZE);
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
serialIUT.enableIntTx(false);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#if defined(HWLOOPBACK) || defined(HWSOURCESWSINK)
|
||||||
|
Serial2.begin(IUTBITRATE, hwSerialConfig, D4, D3, invert);
|
||||||
|
Serial2.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
#elif defined(HWSOURCESINK)
|
||||||
|
serialIUT.begin(IUTBITRATE, hwSerialConfig, D5, D6, invert);
|
||||||
|
serialIUT.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
#endif
|
||||||
|
#if !defined(HWSOURCESINK)
|
||||||
|
serialIUT.begin(IUTBITRATE, swSerialConfig, D5, D6, invert, 2 * BLOCKSIZE);
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
serialIUT.enableIntTx(false);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
logger.begin(9600);
|
||||||
|
#else
|
||||||
|
#if !defined(HWSOURCESINK)
|
||||||
|
serialIUT.begin(IUTBITRATE);
|
||||||
|
#endif
|
||||||
|
logger.begin(9600);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
logger.println(PSTR("Loopback example for EspEspSoftwareSerial"));
|
||||||
|
|
||||||
|
start = micros();
|
||||||
|
txCount = 0;
|
||||||
|
rxCount = 0;
|
||||||
|
rxErrors = 0;
|
||||||
|
rxParityErrors = 0;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char c = 0;
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
char block[BLOCKSIZE];
|
||||||
|
#endif
|
||||||
|
char inBuf[BLOCKSIZE];
|
||||||
|
for (int i = 0; i < BLOCKSIZE; ++i) {
|
||||||
|
#ifndef HALFDUPLEX
|
||||||
|
#ifdef HWSOURCESWSINK
|
||||||
|
hwSerial.write(c);
|
||||||
|
#else
|
||||||
|
serialIUT.write(c);
|
||||||
|
#endif
|
||||||
|
#ifdef HWLOOPBACK
|
||||||
|
int avail = hwSerial.available();
|
||||||
|
while ((0 == (i % 8)) && avail > 0) {
|
||||||
|
int inCnt = hwSerial.read(inBuf, min(avail, min(BLOCKSIZE, hwSerial.availableForWrite())));
|
||||||
|
hwSerial.write(inBuf, inCnt);
|
||||||
|
avail -= inCnt;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
block[i] = c;
|
||||||
|
#endif
|
||||||
|
c = (c + 1) % 256;
|
||||||
|
++txCount;
|
||||||
|
}
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
#ifdef HWSOURCESWSINK
|
||||||
|
hwSerial.write(block, BLOCKSIZE);
|
||||||
|
#else
|
||||||
|
serialIUT.write(block, BLOCKSIZE);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef HWSOURCESINK
|
||||||
|
#if defined(ESP8266)
|
||||||
|
if (serialIUT.hasOverrun()) { logger.println(PSTR("serialIUT.overrun")); }
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
if (serialIUT.overflow()) { logger.println(PSTR("serialIUT.overflow")); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int inCnt;
|
||||||
|
uint32_t deadlineStart;
|
||||||
|
|
||||||
|
#ifdef HWLOOPBACK
|
||||||
|
// starting deadline for the first bytes to become readable
|
||||||
|
deadlineStart = ESP.getCycleCount();
|
||||||
|
inCnt = 0;
|
||||||
|
while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 24 * ESP.getCpuFreqMHz()) {
|
||||||
|
int avail = hwSerial.available();
|
||||||
|
inCnt += hwSerial.read(&inBuf[inCnt], min(avail, min(BLOCKSIZE - inCnt, hwSerial.availableForWrite())));
|
||||||
|
if (inCnt >= BLOCKSIZE) { break; }
|
||||||
|
// wait for more outstanding bytes to trickle in
|
||||||
|
if (avail) deadlineStart = ESP.getCycleCount();
|
||||||
|
}
|
||||||
|
hwSerial.write(inBuf, inCnt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// starting deadline for the first bytes to come in
|
||||||
|
deadlineStart = ESP.getCycleCount();
|
||||||
|
inCnt = 0;
|
||||||
|
while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 8 * ESP.getCpuFreqMHz()) {
|
||||||
|
int avail;
|
||||||
|
if (0 != (swSerialConfig & 070))
|
||||||
|
avail = serialIUT.available();
|
||||||
|
else
|
||||||
|
avail = serialIUT.read(inBuf, BLOCKSIZE);
|
||||||
|
for (int i = 0; i < avail; ++i)
|
||||||
|
{
|
||||||
|
unsigned char r;
|
||||||
|
if (0 != (swSerialConfig & 070))
|
||||||
|
r = serialIUT.read();
|
||||||
|
else
|
||||||
|
r = inBuf[i];
|
||||||
|
if (expected == -1) { expected = r; }
|
||||||
|
else {
|
||||||
|
expected = (expected + 1) % (1UL << (5 + swSerialConfig % 4));
|
||||||
|
}
|
||||||
|
if (r != expected) {
|
||||||
|
++rxErrors;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
#ifndef HWSOURCESINK
|
||||||
|
if (serialIUT.readParity() != (static_cast<bool>(swSerialConfig & 010) ? serialIUT.parityOdd(r) : serialIUT.parityEven(r)))
|
||||||
|
{
|
||||||
|
++rxParityErrors;
|
||||||
|
}
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
// current ESP8266 API does not flag parity errors separately
|
||||||
|
if (serialIUT.hasRxError())
|
||||||
|
{
|
||||||
|
++rxParityErrors;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
++rxCount;
|
||||||
|
++inCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inCnt >= BLOCKSIZE) { break; }
|
||||||
|
// wait for more outstanding bytes to trickle in
|
||||||
|
if (avail) deadlineStart = ESP.getCycleCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t interval = micros() - start;
|
||||||
|
if (txCount >= ReportInterval && interval) {
|
||||||
|
uint8_t wordBits = (5 + swSerialConfig % 4) + static_cast<bool>(swSerialConfig & 070) + 1 + ((swSerialConfig & 0300) ? 1 : 0);
|
||||||
|
logger.println(String(PSTR("tx/rx: ")) + txCount + PSTR("/") + rxCount);
|
||||||
|
const long txCps = txCount * (1000000.0 / interval);
|
||||||
|
const long rxCps = rxCount * (1000000.0 / interval);
|
||||||
|
logger.print(String(FPSTR(effTxTxt)) + wordBits * txCps + PSTR("bps, ")
|
||||||
|
+ effRxTxt + wordBits * rxCps + PSTR("bps, ")
|
||||||
|
+ rxErrors + PSTR(" errors (") + 100.0 * rxErrors / (!rxErrors ? 1 : rxCount) + PSTR("%)"));
|
||||||
|
if (0 != (swSerialConfig & 070))
|
||||||
|
{
|
||||||
|
logger.print(PSTR(" (")); logger.print(rxParityErrors); logger.println(PSTR(" parity errors)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.println();
|
||||||
|
}
|
||||||
|
txCount = 0;
|
||||||
|
rxCount = 0;
|
||||||
|
rxErrors = 0;
|
||||||
|
rxParityErrors = 0;
|
||||||
|
expected = -1;
|
||||||
|
// resync
|
||||||
|
delay(1000UL * 12 * BLOCKSIZE / IUTBITRATE * 16);
|
||||||
|
serialIUT.flush();
|
||||||
|
start = micros();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
#include "SoftwareSerial.h"
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D6 (12)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D6 (19)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
EspSoftwareSerial::UART swSer1;
|
||||||
|
EspSoftwareSerial::UART swSer2;
|
||||||
|
|
||||||
|
void checkSwSerial(EspSoftwareSerial::UART* ss) {
|
||||||
|
byte ch;
|
||||||
|
while (!Serial.available());
|
||||||
|
ss->enableTx(true);
|
||||||
|
while (Serial.available()) {
|
||||||
|
ch = Serial.read();
|
||||||
|
ss->write(ch);
|
||||||
|
}
|
||||||
|
ss->enableTx(false);
|
||||||
|
// wait 1 second for the reply from EspSoftwareSerial if any
|
||||||
|
delay(1000);
|
||||||
|
if (ss->available()) {
|
||||||
|
Serial.print(PSTR("\nResult:"));
|
||||||
|
while (ss->available()) {
|
||||||
|
ch = (byte)ss->read();
|
||||||
|
Serial.print(ch < 0x10 ? PSTR(" 0") : PSTR(" "));
|
||||||
|
Serial.print(ch, HEX);
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
delay(2000);
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println(PSTR("\nOne Wire Half Duplex Serial Tester"));
|
||||||
|
swSer1.begin(115200, EspSoftwareSerial::SWSERIAL_8N1, D6, D6, false, 256);
|
||||||
|
// high speed half duplex, turn off interrupts during tx
|
||||||
|
swSer1.enableIntTx(false);
|
||||||
|
swSer2.begin(115200, EspSoftwareSerial::SWSERIAL_8N1, D5, D5, false, 256);
|
||||||
|
// high speed half duplex, turn off interrupts during tx
|
||||||
|
swSer2.enableIntTx(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
Serial.println(PSTR("\n\nTesting on swSer1"));
|
||||||
|
Serial.print(PSTR("Enter something to send using swSer1."));
|
||||||
|
checkSwSerial(&swSer1);
|
||||||
|
|
||||||
|
Serial.println(PSTR("\n\nTesting on swSer2"));
|
||||||
|
Serial.print(PSTR("Enter something to send using swSer2."));
|
||||||
|
checkSwSerial(&swSer2);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
// On ESP8266:
|
||||||
|
// Runs up to 115200bps at 80MHz, 250000bps at 160MHz, with nearly zero errors.
|
||||||
|
// This example is currently not ported to ESP32, which is based on FreeRTOS.
|
||||||
|
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BAUD_RATE 115200
|
||||||
|
#define MAX_FRAMEBITS (1 + 8 + 1 + 2)
|
||||||
|
|
||||||
|
EspSoftwareSerial::UART testSerial;
|
||||||
|
|
||||||
|
// Becomes set from ISR / IRQ callback function.
|
||||||
|
std::atomic<bool> rxPending(false);
|
||||||
|
|
||||||
|
void IRAM_ATTR receiveHandler() {
|
||||||
|
rxPending.store(true);
|
||||||
|
esp_schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.setDebugOutput(false);
|
||||||
|
Serial.swap();
|
||||||
|
testSerial.begin(BAUD_RATE, EspSoftwareSerial::SWSERIAL_8N1, RX, TX);
|
||||||
|
// Only half duplex this way, but reliable TX timings for high bps
|
||||||
|
testSerial.enableIntTx(false);
|
||||||
|
testSerial.onReceive(receiveHandler);
|
||||||
|
|
||||||
|
testSerial.println(PSTR("\nSoftware serial onReceive() event test started"));
|
||||||
|
|
||||||
|
for (char ch = ' '; ch <= 'z'; ch++) {
|
||||||
|
testSerial.write(ch);
|
||||||
|
}
|
||||||
|
testSerial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
#ifdef ESP8266
|
||||||
|
bool isRxPending = rxPending.load();
|
||||||
|
if (isRxPending) {
|
||||||
|
rxPending.store(false);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
bool isRxPending = m_isrOverflow.exchange(false);
|
||||||
|
#endif
|
||||||
|
auto avail = testSerial.available();
|
||||||
|
if (isRxPending && !avail) {
|
||||||
|
// event fired on start bit, wait until first stop bit of longest frame
|
||||||
|
delayMicroseconds(1 + MAX_FRAMEBITS * 1000000 / BAUD_RATE);
|
||||||
|
avail = testSerial.available();
|
||||||
|
}
|
||||||
|
if (!avail) {
|
||||||
|
// On development board, idle power draw at USB:
|
||||||
|
// with yield() 77mA, 385mW (160MHz: 82mA, 410mW)
|
||||||
|
// with esp_suspend() 20mA, 100mW (at 160MHz, too)
|
||||||
|
//yield();
|
||||||
|
esp_suspend();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// try to force to half-duplex
|
||||||
|
decltype(avail) prev_avail;
|
||||||
|
do {
|
||||||
|
delayMicroseconds(1 + MAX_FRAMEBITS * 1000000 / BAUD_RATE);
|
||||||
|
prev_avail = avail;
|
||||||
|
} while (prev_avail != (avail = testSerial.available()));
|
||||||
|
while (avail > 0) {
|
||||||
|
testSerial.write(testSerial.read());
|
||||||
|
avail = testSerial.available();
|
||||||
|
}
|
||||||
|
testSerial.println();
|
||||||
|
}
|
@ -0,0 +1,199 @@
|
|||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
// On ESP8266:
|
||||||
|
// EspSoftwareSerial loopback for remote source (loopback.ino), or hardware loopback.
|
||||||
|
// Connect source D5 (rx) to local D8 (tx), source D6 (tx) to local D7 (rx).
|
||||||
|
// Hint: The logger is run at 9600bps such that enableIntTx(true) can remain unchanged. Blocking
|
||||||
|
// interrupts severely impacts the ability of the EspSoftwareSerial devices to operate concurrently
|
||||||
|
// and/or in duplex mode.
|
||||||
|
// On ESP32:
|
||||||
|
// For software or hardware loopback, connect source rx to local D8 (tx), source tx to local D7 (rx).
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D8 (5)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D7 (23)
|
||||||
|
#define D6 (19)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HWLOOPBACK 1
|
||||||
|
#define HALFDUPLEX 1
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
constexpr int IUTBITRATE = 19200;
|
||||||
|
#else
|
||||||
|
constexpr int IUTBITRATE = 19200;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
constexpr EspSoftwareSerial::Config swSerialConfig = EspSoftwareSerial::SWSERIAL_8E1;
|
||||||
|
constexpr SerialConfig hwSerialConfig = ::SERIAL_8E1;
|
||||||
|
#elif defined(ESP32)
|
||||||
|
constexpr EspSoftwareSerial::Config swSerialConfig = EspSoftwareSerial::SWSERIAL_8E1;
|
||||||
|
constexpr uint32_t hwSerialConfig = ::SERIAL_8E1;
|
||||||
|
#else
|
||||||
|
constexpr unsigned swSerialConfig = 3;
|
||||||
|
#endif
|
||||||
|
constexpr bool invert = false;
|
||||||
|
|
||||||
|
constexpr int BLOCKSIZE = 16; // use fractions of 256
|
||||||
|
|
||||||
|
unsigned long start;
|
||||||
|
const char bitRateTxt[] PROGMEM = "Effective data rate: ";
|
||||||
|
int rxCount;
|
||||||
|
int seqErrors;
|
||||||
|
int parityErrors;
|
||||||
|
int expected;
|
||||||
|
constexpr int ReportInterval = IUTBITRATE / 8;
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#if defined(HWLOOPBACK)
|
||||||
|
HardwareSerial& repeater(Serial);
|
||||||
|
EspSoftwareSerial::UART logger;
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART repeater;
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#endif
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#if defined(HWLOOPBACK)
|
||||||
|
HardwareSerial& repeater(Serial2);
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART repeater;
|
||||||
|
#endif
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART repeater(14, 12);
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#if defined(HWLOOPBACK)
|
||||||
|
repeater.begin(IUTBITRATE, hwSerialConfig, ::SERIAL_FULL, 1, invert);
|
||||||
|
repeater.swap();
|
||||||
|
repeater.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
logger.begin(9600, EspSoftwareSerial::SWSERIAL_8N1, -1, TX);
|
||||||
|
#else
|
||||||
|
repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, invert, 4 * BLOCKSIZE);
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
repeater.enableIntTx(false);
|
||||||
|
#endif
|
||||||
|
logger.begin(9600);
|
||||||
|
#endif
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#if defined(HWLOOPBACK)
|
||||||
|
repeater.begin(IUTBITRATE, hwSerialConfig, D7, D8, invert);
|
||||||
|
repeater.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
#else
|
||||||
|
repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, invert, 4 * BLOCKSIZE);
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
repeater.enableIntTx(false);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
logger.begin(9600);
|
||||||
|
#else
|
||||||
|
repeater.begin(IUTBITRATE);
|
||||||
|
logger.begin(9600);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
logger.println(PSTR("Repeater example for EspEspSoftwareSerial"));
|
||||||
|
start = micros();
|
||||||
|
rxCount = 0;
|
||||||
|
seqErrors = 0;
|
||||||
|
parityErrors = 0;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
#ifdef HWLOOPBACK
|
||||||
|
#if defined(ESP8266)
|
||||||
|
if (repeater.hasOverrun()) { logger.println(PSTR("repeater.overrun")); }
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
if (repeater.overflow()) { logger.println(PSTR("repeater.overflow")); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
char block[BLOCKSIZE];
|
||||||
|
#endif
|
||||||
|
// starting deadline for the first bytes to come in
|
||||||
|
uint32_t deadlineStart = ESP.getCycleCount();
|
||||||
|
int inCnt = 0;
|
||||||
|
while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 24 * ESP.getCpuFreqMHz()) {
|
||||||
|
int avail = repeater.available();
|
||||||
|
for (int i = 0; i < avail; ++i)
|
||||||
|
{
|
||||||
|
int r = repeater.read();
|
||||||
|
if (r == -1) { logger.println(PSTR("read() == -1")); }
|
||||||
|
if (expected == -1) { expected = r; }
|
||||||
|
else {
|
||||||
|
expected = (expected + 1) % (1UL << (5 + swSerialConfig % 4));
|
||||||
|
}
|
||||||
|
if (r != expected) {
|
||||||
|
++seqErrors;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
#ifndef HWLOOPBACK
|
||||||
|
if (repeater.readParity() != (static_cast<bool>(swSerialConfig & 010) ? repeater.parityOdd(r) : repeater.parityEven(r)))
|
||||||
|
{
|
||||||
|
++parityErrors;
|
||||||
|
}
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
// current ESP8266 API does not flag parity errors separately
|
||||||
|
if (repeater.hasRxError())
|
||||||
|
{
|
||||||
|
++parityErrors;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
++rxCount;
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
block[inCnt] = r;
|
||||||
|
#else
|
||||||
|
repeater.write(r);
|
||||||
|
#endif
|
||||||
|
if (++inCnt >= BLOCKSIZE) { break; }
|
||||||
|
}
|
||||||
|
if (inCnt >= BLOCKSIZE) { break; }
|
||||||
|
// wait for more outstanding bytes to trickle in
|
||||||
|
if (avail) deadlineStart = ESP.getCycleCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
repeater.write(block, inCnt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (rxCount >= ReportInterval) {
|
||||||
|
auto end = micros();
|
||||||
|
unsigned long interval = end - start;
|
||||||
|
long cps = rxCount * (1000000.0 / interval);
|
||||||
|
long seqErrorsps = seqErrors * (1000000.0 / interval);
|
||||||
|
logger.print(String(FPSTR(bitRateTxt)) + 10 * cps + PSTR("bps, ")
|
||||||
|
+ seqErrorsps + PSTR("cps seq. errors (") + 100.0 * seqErrors / rxCount + PSTR("%)"));
|
||||||
|
#ifndef HWLOOPBACK
|
||||||
|
if (0 != (swSerialConfig & 070))
|
||||||
|
{
|
||||||
|
logger.print(PSTR(" (")); logger.print(parityErrors); logger.println(PSTR(" parity errors)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
logger.println();
|
||||||
|
}
|
||||||
|
start = end;
|
||||||
|
rxCount = 0;
|
||||||
|
seqErrors = 0;
|
||||||
|
parityErrors = 0;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
// On ESP8266:
|
||||||
|
// At 80MHz runs up 57600bps, and at 160MHz CPU frequency up to 115200bps with only negligible errors.
|
||||||
|
// Connect pin 13 to 15.
|
||||||
|
// For verification and as a example for how to use SW serial on the USB to PC connection,
|
||||||
|
// which allows the use of HW Serial on GPIO13 and GPIO15 instead, #define SWAPSERIAL below.
|
||||||
|
// Notice how the bitrates are also swapped then between RX/TX and GPIO13/GPIO15.
|
||||||
|
// Builtin debug output etc. must be stopped on HW Serial in this case, as it would interfere with the
|
||||||
|
// external communication on GPIO13/GPIO15.
|
||||||
|
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D8 (5)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D7 (23)
|
||||||
|
#define D6 (19)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#define BAUD_RATE 57600
|
||||||
|
#else
|
||||||
|
#define BAUD_RATE 57600
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef SWAPSERIAL
|
||||||
|
|
||||||
|
#ifndef SWAPSERIAL
|
||||||
|
auto& usbSerial = Serial;
|
||||||
|
EspSoftwareSerial::UART testSerial;
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART usbSerial;
|
||||||
|
auto& testSerial = Serial;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
#ifndef SWAPSERIAL
|
||||||
|
usbSerial.begin(115200);
|
||||||
|
// Important: the buffer size optimizations here, in particular the isrBufSize (11) that is only sufficiently
|
||||||
|
// large to hold a single word (up to start - 8 data - parity - stop), are on the basis that any char written
|
||||||
|
// to the loopback EspSoftwareSerial adapter gets read before another write is performed.
|
||||||
|
// Block writes with a size greater than 1 would usually fail. Do not copy this into your own project without
|
||||||
|
// reading the documentation.
|
||||||
|
testSerial.begin(BAUD_RATE, EspSoftwareSerial::SWSERIAL_8N1, D7, D8, false, 95, 11);
|
||||||
|
#else
|
||||||
|
testSerial.begin(115200);
|
||||||
|
testSerial.setDebugOutput(false);
|
||||||
|
testSerial.swap();
|
||||||
|
usbSerial.begin(BAUD_RATE, EspSoftwareSerial::SWSERIAL_8N1, RX, TX, false, 95);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
usbSerial.println(PSTR("\nSoftware serial test started"));
|
||||||
|
|
||||||
|
for (char ch = ' '; ch <= 'z'; ch++) {
|
||||||
|
testSerial.write(ch);
|
||||||
|
}
|
||||||
|
testSerial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
while (testSerial.available() > 0) {
|
||||||
|
usbSerial.write(testSerial.read());
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
while (usbSerial.available() > 0) {
|
||||||
|
testSerial.write(usbSerial.read());
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map for EspSoftwareSerial
|
||||||
|
# (esp8266)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
EspSoftwareSerial KEYWORD1
|
||||||
|
SoftwareSerial KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
begin KEYWORD2
|
||||||
|
baudRate KEYWORD2
|
||||||
|
setTransmitEnablePin KEYWORD2
|
||||||
|
enableIntTx KEYWORD2
|
||||||
|
overflow KEYWORD2
|
||||||
|
available KEYWORD2
|
||||||
|
peek KEYWORD2
|
||||||
|
read KEYWORD2
|
||||||
|
flush KEYWORD2
|
||||||
|
write KEYWORD2
|
||||||
|
enableRx KEYWORD2
|
||||||
|
enableTx KEYWORD2
|
||||||
|
listen KEYWORD2
|
||||||
|
end KEYWORD2
|
||||||
|
isListening KEYWORD2
|
||||||
|
stopListening KEYWORD2
|
||||||
|
onReceive KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
SW_SERIAL_UNUSED_PIN LITERAL1
|
||||||
|
SWSERIAL_5N1 LITERAL1
|
||||||
|
SWSERIAL_6N1 LITERAL1
|
||||||
|
SWSERIAL_7N1 LITERAL1
|
||||||
|
SWSERIAL_8N1 LITERAL1
|
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "EspSoftwareSerial",
|
||||||
|
"version": "8.1.0",
|
||||||
|
"description": "Implementation of the Arduino software serial for ESP8266/ESP32.",
|
||||||
|
"keywords": [
|
||||||
|
"serial", "io", "softwareserial"
|
||||||
|
],
|
||||||
|
"repository":
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/plerup/espsoftwareserial"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Dirk Kaar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Peter Lerup"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "LGPL-2.1+",
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": [
|
||||||
|
"espressif8266", "espressif32"
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
name=EspSoftwareSerial
|
||||||
|
version=8.1.0
|
||||||
|
author=Dirk Kaar, Peter Lerup
|
||||||
|
maintainer=Dirk Kaar <dok@dok-net.net>
|
||||||
|
sentence=Implementation of the Arduino software serial for ESP8266/ESP32.
|
||||||
|
paragraph=
|
||||||
|
category=Signal Input/Output
|
||||||
|
url=https://github.com/plerup/espsoftwareserial/
|
||||||
|
architectures=esp8266,esp32
|
@ -0,0 +1,621 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
SoftwareSerial.cpp - Implementation of the Arduino software serial for ESP8266/ESP32.
|
||||||
|
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
|
||||||
|
Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SoftwareSerial.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
using namespace EspSoftwareSerial;
|
||||||
|
|
||||||
|
#ifndef ESP32
|
||||||
|
uint32_t UARTBase::m_savedPS = 0;
|
||||||
|
#else
|
||||||
|
portMUX_TYPE UARTBase::m_interruptsMux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ALWAYS_INLINE_ATTR inline void IRAM_ATTR UARTBase::disableInterrupts()
|
||||||
|
{
|
||||||
|
#ifndef ESP32
|
||||||
|
m_savedPS = xt_rsil(15);
|
||||||
|
#else
|
||||||
|
taskENTER_CRITICAL(&m_interruptsMux);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE_ATTR inline void IRAM_ATTR UARTBase::restoreInterrupts()
|
||||||
|
{
|
||||||
|
#ifndef ESP32
|
||||||
|
xt_wsr_ps(m_savedPS);
|
||||||
|
#else
|
||||||
|
taskEXIT_CRITICAL(&m_interruptsMux);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint8_t BYTE_ALL_BITS_SET = ~static_cast<uint8_t>(0);
|
||||||
|
|
||||||
|
UARTBase::UARTBase() {
|
||||||
|
}
|
||||||
|
|
||||||
|
UARTBase::UARTBase(int8_t rxPin, int8_t txPin, bool invert)
|
||||||
|
{
|
||||||
|
m_rxPin = rxPin;
|
||||||
|
m_txPin = txPin;
|
||||||
|
m_invert = invert;
|
||||||
|
}
|
||||||
|
|
||||||
|
UARTBase::~UARTBase() {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::setRxGPIOPinMode() {
|
||||||
|
if (m_rxValid) {
|
||||||
|
pinMode(m_rxPin, m_rxGPIOHasPullUp && m_rxGPIOPullUpEnabled ? INPUT_PULLUP : INPUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::setTxGPIOPinMode() {
|
||||||
|
if (m_txValid) {
|
||||||
|
pinMode(m_txPin, m_txGPIOOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin, int8_t txPin,
|
||||||
|
bool invert) {
|
||||||
|
if (-1 != rxPin) m_rxPin = rxPin;
|
||||||
|
if (-1 != txPin) m_txPin = txPin;
|
||||||
|
m_oneWire = (m_rxPin == m_txPin);
|
||||||
|
m_invert = invert;
|
||||||
|
m_dataBits = 5 + (config & 07);
|
||||||
|
m_parityMode = static_cast<Parity>(config & 070);
|
||||||
|
m_stopBits = 1 + ((config & 0300) ? 1 : 0);
|
||||||
|
m_pduBits = m_dataBits + static_cast<bool>(m_parityMode) + m_stopBits;
|
||||||
|
m_bitTicks = (microsToTicks(1000000UL) + baud / 2) / baud;
|
||||||
|
m_intTxEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::beginRx(bool hasPullUp, int bufCapacity, int isrBufCapacity) {
|
||||||
|
m_rxGPIOHasPullUp = hasPullUp;
|
||||||
|
m_rxReg = portInputRegister(digitalPinToPort(m_rxPin));
|
||||||
|
m_rxBitMask = digitalPinToBitMask(m_rxPin);
|
||||||
|
m_buffer.reset(new circular_queue<uint8_t>((bufCapacity > 0) ? bufCapacity : 64));
|
||||||
|
if (m_parityMode)
|
||||||
|
{
|
||||||
|
m_parityBuffer.reset(new circular_queue<uint8_t>((m_buffer->capacity() + 7) / 8));
|
||||||
|
m_parityInPos = m_parityOutPos = 1;
|
||||||
|
}
|
||||||
|
m_isrBuffer.reset(new circular_queue<uint32_t, UARTBase*>((isrBufCapacity > 0) ?
|
||||||
|
isrBufCapacity : m_buffer->capacity() * (2 + m_dataBits + static_cast<bool>(m_parityMode))));
|
||||||
|
if (m_buffer && (!m_parityMode || m_parityBuffer) && m_isrBuffer) {
|
||||||
|
m_rxValid = true;
|
||||||
|
setRxGPIOPinMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::beginTx() {
|
||||||
|
#if !defined(ESP8266)
|
||||||
|
m_txReg = portOutputRegister(digitalPinToPort(m_txPin));
|
||||||
|
#endif
|
||||||
|
m_txBitMask = digitalPinToBitMask(m_txPin);
|
||||||
|
m_txValid = true;
|
||||||
|
if (!m_oneWire) {
|
||||||
|
setTxGPIOPinMode();
|
||||||
|
digitalWrite(m_txPin, !m_invert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::end()
|
||||||
|
{
|
||||||
|
enableRx(false);
|
||||||
|
m_txValid = false;
|
||||||
|
if (m_buffer) {
|
||||||
|
m_buffer.reset();
|
||||||
|
}
|
||||||
|
m_parityBuffer.reset();
|
||||||
|
if (m_isrBuffer) {
|
||||||
|
m_isrBuffer.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t UARTBase::baudRate() {
|
||||||
|
return 1000000UL / ticksToMicros(m_bitTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::setTransmitEnablePin(int8_t txEnablePin) {
|
||||||
|
if (-1 != txEnablePin) {
|
||||||
|
m_txEnableValid = true;
|
||||||
|
m_txEnablePin = txEnablePin;
|
||||||
|
pinMode(m_txEnablePin, OUTPUT);
|
||||||
|
digitalWrite(m_txEnablePin, LOW);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_txEnableValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableIntTx(bool on) {
|
||||||
|
m_intTxEnabled = on;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableRxGPIOPullUp(bool on) {
|
||||||
|
m_rxGPIOPullUpEnabled = on;
|
||||||
|
setRxGPIOPinMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableTxGPIOOpenDrain(bool on) {
|
||||||
|
m_txGPIOOpenDrain = on;
|
||||||
|
setTxGPIOPinMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableTx(bool on) {
|
||||||
|
if (m_txValid && m_oneWire) {
|
||||||
|
if (on) {
|
||||||
|
enableRx(false);
|
||||||
|
setTxGPIOPinMode();
|
||||||
|
digitalWrite(m_txPin, !m_invert);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setRxGPIOPinMode();
|
||||||
|
enableRx(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableRx(bool on) {
|
||||||
|
if (m_rxValid && on != m_rxEnabled) {
|
||||||
|
if (on) {
|
||||||
|
m_rxLastBit = m_pduBits - 1;
|
||||||
|
// Init to stop bit level and current tick
|
||||||
|
m_isrLastTick = (microsToTicks(micros()) | 1) ^ m_invert;
|
||||||
|
if (m_bitTicks >= microsToTicks(1000000UL / 74880UL))
|
||||||
|
attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast<void (*)(void*)>(rxBitISR), this, CHANGE);
|
||||||
|
else
|
||||||
|
attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast<void (*)(void*)>(rxBitSyncISR), this, m_invert ? RISING : FALLING);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
detachInterrupt(digitalPinToInterrupt(m_rxPin));
|
||||||
|
}
|
||||||
|
m_rxEnabled = on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int UARTBase::read() {
|
||||||
|
if (!m_rxValid) { return -1; }
|
||||||
|
if (!m_buffer->available()) {
|
||||||
|
rxBits();
|
||||||
|
if (!m_buffer->available()) { return -1; }
|
||||||
|
}
|
||||||
|
auto val = m_buffer->pop();
|
||||||
|
if (m_parityBuffer)
|
||||||
|
{
|
||||||
|
m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos;
|
||||||
|
m_parityOutPos <<= 1;
|
||||||
|
if (!m_parityOutPos)
|
||||||
|
{
|
||||||
|
m_parityOutPos = 1;
|
||||||
|
m_parityBuffer->pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UARTBase::read(uint8_t* buffer, size_t size) {
|
||||||
|
if (!m_rxValid) { return 0; }
|
||||||
|
int avail;
|
||||||
|
if (0 == (avail = m_buffer->pop_n(buffer, size))) {
|
||||||
|
rxBits();
|
||||||
|
avail = m_buffer->pop_n(buffer, size);
|
||||||
|
}
|
||||||
|
if (!avail) return 0;
|
||||||
|
if (m_parityBuffer) {
|
||||||
|
uint32_t parityBits = avail;
|
||||||
|
while (m_parityOutPos >>= 1) ++parityBits;
|
||||||
|
m_parityOutPos = (1 << (parityBits % 8));
|
||||||
|
m_parityBuffer->pop_n(nullptr, parityBits / 8);
|
||||||
|
}
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UARTBase::readBytes(uint8_t* buffer, size_t size) {
|
||||||
|
if (!m_rxValid || !size) { return 0; }
|
||||||
|
size_t count = 0;
|
||||||
|
auto start = millis();
|
||||||
|
do {
|
||||||
|
auto readCnt = read(&buffer[count], size - count);
|
||||||
|
count += readCnt;
|
||||||
|
if (count >= size) break;
|
||||||
|
if (readCnt) {
|
||||||
|
start = millis();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
optimistic_yield(1000UL);
|
||||||
|
}
|
||||||
|
} while (millis() - start < _timeout);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UARTBase::available() {
|
||||||
|
if (!m_rxValid) { return 0; }
|
||||||
|
rxBits();
|
||||||
|
int avail = m_buffer->available();
|
||||||
|
if (!avail) {
|
||||||
|
optimistic_yield(10000UL);
|
||||||
|
}
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::lazyDelay() {
|
||||||
|
// Reenable interrupts while delaying to avoid other tasks piling up
|
||||||
|
if (!m_intTxEnabled) { restoreInterrupts(); }
|
||||||
|
const auto expired = microsToTicks(micros()) - m_periodStart;
|
||||||
|
const int32_t remaining = m_periodDuration - expired;
|
||||||
|
const uint32_t ms = remaining > 0 ? ticksToMicros(remaining) / 1000UL : 0;
|
||||||
|
if (ms > 0)
|
||||||
|
{
|
||||||
|
delay(ms);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
optimistic_yield(10000UL);
|
||||||
|
}
|
||||||
|
// Assure that below-ms part of delays are not elided
|
||||||
|
preciseDelay();
|
||||||
|
// Disable interrupts again if applicable
|
||||||
|
if (!m_intTxEnabled) { disableInterrupts(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR UARTBase::preciseDelay() {
|
||||||
|
uint32_t ticks;
|
||||||
|
do {
|
||||||
|
ticks = microsToTicks(micros());
|
||||||
|
} while ((ticks - m_periodStart) < m_periodDuration);
|
||||||
|
m_periodDuration = 0;
|
||||||
|
m_periodStart = ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR UARTBase::writePeriod(
|
||||||
|
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit) {
|
||||||
|
preciseDelay();
|
||||||
|
if (dutyCycle)
|
||||||
|
{
|
||||||
|
#if defined(ESP8266)
|
||||||
|
if (16 == m_txPin) {
|
||||||
|
GP16O = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOS = m_txBitMask;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
*m_txReg = *m_txReg | m_txBitMask;
|
||||||
|
#endif
|
||||||
|
m_periodDuration += dutyCycle;
|
||||||
|
if (offCycle || (withStopBit && !m_invert)) {
|
||||||
|
if (!withStopBit || m_invert) {
|
||||||
|
preciseDelay();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lazyDelay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offCycle)
|
||||||
|
{
|
||||||
|
#if defined(ESP8266)
|
||||||
|
if (16 == m_txPin) {
|
||||||
|
GP16O = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOC = m_txBitMask;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
*m_txReg = *m_txReg & ~m_txBitMask;
|
||||||
|
#endif
|
||||||
|
m_periodDuration += offCycle;
|
||||||
|
if (withStopBit && m_invert) lazyDelay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UARTBase::write(uint8_t byte) {
|
||||||
|
return write(&byte, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UARTBase::write(uint8_t byte, Parity parity) {
|
||||||
|
return write(&byte, 1, parity);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UARTBase::write(const uint8_t* buffer, size_t size) {
|
||||||
|
return write(buffer, size, m_parityMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IRAM_ATTR UARTBase::write(const uint8_t* buffer, size_t size, Parity parity) {
|
||||||
|
if (m_rxValid) { rxBits(); }
|
||||||
|
if (!m_txValid) { return -1; }
|
||||||
|
|
||||||
|
if (m_txEnableValid) {
|
||||||
|
digitalWrite(m_txEnablePin, HIGH);
|
||||||
|
}
|
||||||
|
// Stop bit: if inverted, LOW, otherwise HIGH
|
||||||
|
bool b = !m_invert;
|
||||||
|
uint32_t dutyCycle = 0;
|
||||||
|
uint32_t offCycle = 0;
|
||||||
|
if (!m_intTxEnabled) {
|
||||||
|
// Disable interrupts in order to get a clean transmit timing
|
||||||
|
disableInterrupts();
|
||||||
|
}
|
||||||
|
const uint32_t dataMask = ((1UL << m_dataBits) - 1);
|
||||||
|
bool withStopBit = true;
|
||||||
|
m_periodDuration = 0;
|
||||||
|
m_periodStart = microsToTicks(micros());
|
||||||
|
for (size_t cnt = 0; cnt < size; ++cnt) {
|
||||||
|
uint8_t byte = pgm_read_byte(buffer + cnt) & dataMask;
|
||||||
|
// push LSB start-data-parity-stop bit pattern into uint32_t
|
||||||
|
// Stop bits: HIGH
|
||||||
|
uint32_t word = ~0UL;
|
||||||
|
// inverted parity bit, performance tweak for xor all-bits-set word
|
||||||
|
if (parity && m_parityMode)
|
||||||
|
{
|
||||||
|
uint32_t parityBit;
|
||||||
|
switch (parity)
|
||||||
|
{
|
||||||
|
case PARITY_EVEN:
|
||||||
|
// from inverted, so use odd parity
|
||||||
|
parityBit = byte;
|
||||||
|
parityBit ^= parityBit >> 4;
|
||||||
|
parityBit &= 0xf;
|
||||||
|
parityBit = (0x9669 >> parityBit) & 1;
|
||||||
|
break;
|
||||||
|
case PARITY_ODD:
|
||||||
|
// from inverted, so use even parity
|
||||||
|
parityBit = byte;
|
||||||
|
parityBit ^= parityBit >> 4;
|
||||||
|
parityBit &= 0xf;
|
||||||
|
parityBit = (0x6996 >> parityBit) & 1;
|
||||||
|
break;
|
||||||
|
case PARITY_MARK:
|
||||||
|
parityBit = 0;
|
||||||
|
break;
|
||||||
|
case PARITY_SPACE:
|
||||||
|
// suppresses warning parityBit uninitialized
|
||||||
|
default:
|
||||||
|
parityBit = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
word ^= parityBit;
|
||||||
|
}
|
||||||
|
word <<= m_dataBits;
|
||||||
|
word |= byte;
|
||||||
|
// Start bit: LOW
|
||||||
|
word <<= 1;
|
||||||
|
if (m_invert) word = ~word;
|
||||||
|
for (int i = 0; i <= m_pduBits; ++i) {
|
||||||
|
bool pb = b;
|
||||||
|
b = word & (1UL << i);
|
||||||
|
if (!pb && b) {
|
||||||
|
writePeriod(dutyCycle, offCycle, withStopBit);
|
||||||
|
withStopBit = false;
|
||||||
|
dutyCycle = offCycle = 0;
|
||||||
|
}
|
||||||
|
if (b) {
|
||||||
|
dutyCycle += m_bitTicks;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
offCycle += m_bitTicks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withStopBit = true;
|
||||||
|
}
|
||||||
|
writePeriod(dutyCycle, offCycle, true);
|
||||||
|
if (!m_intTxEnabled) {
|
||||||
|
// restore the interrupt state if applicable
|
||||||
|
restoreInterrupts();
|
||||||
|
}
|
||||||
|
if (m_txEnableValid) {
|
||||||
|
digitalWrite(m_txEnablePin, LOW);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::flush() {
|
||||||
|
if (!m_rxValid) { return; }
|
||||||
|
m_buffer->flush();
|
||||||
|
if (m_parityBuffer)
|
||||||
|
{
|
||||||
|
m_parityInPos = m_parityOutPos = 1;
|
||||||
|
m_parityBuffer->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UARTBase::overflow() {
|
||||||
|
bool res = m_overflow;
|
||||||
|
m_overflow = false;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UARTBase::peek() {
|
||||||
|
if (!m_rxValid) { return -1; }
|
||||||
|
if (!m_buffer->available()) {
|
||||||
|
rxBits();
|
||||||
|
if (!m_buffer->available()) return -1;
|
||||||
|
}
|
||||||
|
auto val = m_buffer->peek();
|
||||||
|
if (m_parityBuffer) m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::rxBits() {
|
||||||
|
#ifdef ESP8266
|
||||||
|
if (m_isrOverflow.load()) {
|
||||||
|
m_overflow = true;
|
||||||
|
m_isrOverflow.store(false);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (m_isrOverflow.exchange(false)) {
|
||||||
|
m_overflow = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_isrBuffer->for_each(m_isrBufferForEachDel);
|
||||||
|
|
||||||
|
// A stop bit can go undetected if leading data bits are at same level
|
||||||
|
// and there was also no next start bit yet, so one word may be pending.
|
||||||
|
// Check that there was no new ISR data received in the meantime, inserting an
|
||||||
|
// extraneous stop level bit out of sequence breaks rx.
|
||||||
|
if (m_rxLastBit < m_pduBits - 1) {
|
||||||
|
const uint32_t detectionTicks = (m_pduBits - 1 - m_rxLastBit) * m_bitTicks;
|
||||||
|
if (!m_isrBuffer->available() && microsToTicks(micros()) - m_isrLastTick > detectionTicks) {
|
||||||
|
// Produce faux stop bit level, prevents start bit maldetection
|
||||||
|
// tick's LSB is repurposed for the level bit
|
||||||
|
rxBits(((m_isrLastTick + detectionTicks) | 1) ^ m_invert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::rxBits(const uint32_t isrTick) {
|
||||||
|
const bool level = (m_isrLastTick & 1) ^ m_invert;
|
||||||
|
|
||||||
|
// error introduced by edge value in LSB of isrTick is negligible
|
||||||
|
uint32_t ticks = isrTick - m_isrLastTick;
|
||||||
|
m_isrLastTick = isrTick;
|
||||||
|
|
||||||
|
uint32_t bits = ticks / m_bitTicks;
|
||||||
|
if (ticks % m_bitTicks > (m_bitTicks >> 1)) ++bits;
|
||||||
|
while (bits > 0) {
|
||||||
|
// start bit detection
|
||||||
|
if (m_rxLastBit >= (m_pduBits - 1)) {
|
||||||
|
// leading edge of start bit?
|
||||||
|
if (level) break;
|
||||||
|
m_rxLastBit = -1;
|
||||||
|
--bits;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// data bits
|
||||||
|
if (m_rxLastBit < (m_dataBits - 1)) {
|
||||||
|
uint8_t dataBits = min(bits, static_cast<uint32_t>(m_dataBits - 1 - m_rxLastBit));
|
||||||
|
m_rxLastBit += dataBits;
|
||||||
|
bits -= dataBits;
|
||||||
|
m_rxCurByte >>= dataBits;
|
||||||
|
if (level) { m_rxCurByte |= (BYTE_ALL_BITS_SET << (8 - dataBits)); }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// parity bit
|
||||||
|
if (m_parityMode && m_rxLastBit == (m_dataBits - 1)) {
|
||||||
|
++m_rxLastBit;
|
||||||
|
--bits;
|
||||||
|
m_rxCurParity = level;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// stop bits
|
||||||
|
// Store the received value in the buffer unless we have an overflow
|
||||||
|
// if not high stop bit level, discard word
|
||||||
|
if (bits >= static_cast<uint32_t>(m_pduBits - 1 - m_rxLastBit) && level) {
|
||||||
|
m_rxCurByte >>= (sizeof(uint8_t) * 8 - m_dataBits);
|
||||||
|
if (!m_buffer->push(m_rxCurByte)) {
|
||||||
|
m_overflow = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (m_parityBuffer)
|
||||||
|
{
|
||||||
|
if (m_rxCurParity) {
|
||||||
|
m_parityBuffer->pushpeek() |= m_parityInPos;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_parityBuffer->pushpeek() &= ~m_parityInPos;
|
||||||
|
}
|
||||||
|
m_parityInPos <<= 1;
|
||||||
|
if (!m_parityInPos)
|
||||||
|
{
|
||||||
|
m_parityBuffer->push();
|
||||||
|
m_parityInPos = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_rxLastBit = m_pduBits - 1;
|
||||||
|
// reset to 0 is important for masked bit logic
|
||||||
|
m_rxCurByte = 0;
|
||||||
|
m_rxCurParity = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR UARTBase::rxBitISR(UARTBase* self) {
|
||||||
|
const bool level = *self->m_rxReg & self->m_rxBitMask;
|
||||||
|
const uint32_t curTick = microsToTicks(micros());
|
||||||
|
const bool empty = !self->m_isrBuffer->available();
|
||||||
|
|
||||||
|
// Store level and tick in the buffer unless we have an overflow
|
||||||
|
// tick's LSB is repurposed for the level bit
|
||||||
|
if (!self->m_isrBuffer->push((curTick | 1U) ^ !level)) self->m_isrOverflow.store(true);
|
||||||
|
// Trigger rx callback only when receiver is starved
|
||||||
|
if (empty) self->m_rxHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR UARTBase::rxBitSyncISR(UARTBase* self) {
|
||||||
|
bool level = self->m_invert;
|
||||||
|
const uint32_t start = microsToTicks(micros());
|
||||||
|
uint32_t wait = self->m_bitTicks;
|
||||||
|
const bool empty = !self->m_isrBuffer->available();
|
||||||
|
|
||||||
|
// Store level and tick in the buffer unless we have an overflow
|
||||||
|
// tick's LSB is repurposed for the level bit
|
||||||
|
if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ !level)) self->m_isrOverflow.store(true);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < self->m_pduBits; ++i) {
|
||||||
|
while (microsToTicks(micros()) - start < wait) {};
|
||||||
|
wait += self->m_bitTicks;
|
||||||
|
|
||||||
|
// Store level and tick in the buffer unless we have an overflow
|
||||||
|
// tick's LSB is repurposed for the level bit
|
||||||
|
if (static_cast<bool>(*self->m_rxReg & self->m_rxBitMask) != level)
|
||||||
|
{
|
||||||
|
if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ level)) self->m_isrOverflow.store(true);
|
||||||
|
level = !level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Trigger rx callback only when receiver is starved
|
||||||
|
if (empty) self->m_rxHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::onReceive(const Delegate<void(), void*>& handler) {
|
||||||
|
disableInterrupts();
|
||||||
|
m_rxHandler = handler;
|
||||||
|
restoreInterrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::onReceive(Delegate<void(), void*>&& handler) {
|
||||||
|
disableInterrupts();
|
||||||
|
m_rxHandler = std::move(handler);
|
||||||
|
restoreInterrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if __GNUC__ < 12
|
||||||
|
// The template member functions below must be in IRAM, but due to a bug GCC doesn't currently
|
||||||
|
// honor the attribute. Instead, it is possible to do explicit specialization and adorn
|
||||||
|
// these with the IRAM attribute:
|
||||||
|
// Delegate<>::operator (), circular_queue<>::available,
|
||||||
|
// circular_queue<>::available_for_push, circular_queue<>::push_peek, circular_queue<>::push
|
||||||
|
|
||||||
|
template void IRAM_ATTR delegate::detail::DelegateImpl<void*, void>::operator()() const;
|
||||||
|
template size_t IRAM_ATTR circular_queue<uint32_t, UARTBase*>::available() const;
|
||||||
|
template bool IRAM_ATTR circular_queue<uint32_t, UARTBase*>::push(uint32_t&&);
|
||||||
|
template bool IRAM_ATTR circular_queue<uint32_t, UARTBase*>::push(const uint32_t&);
|
||||||
|
#endif // __GNUC__ < 12
|
||||||
|
|
@ -0,0 +1,449 @@
|
|||||||
|
/*
|
||||||
|
SoftwareSerial.h - Implementation of the Arduino software serial for ESP8266/ESP32.
|
||||||
|
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
|
||||||
|
Copyright (c) 2018-2019 Dirk O. Kaar. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __SoftwareSerial_h
|
||||||
|
#define __SoftwareSerial_h
|
||||||
|
|
||||||
|
#include "circular_queue/circular_queue.h"
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
namespace EspSoftwareSerial {
|
||||||
|
|
||||||
|
// Interface definition for template argument of BasicUART
|
||||||
|
class IGpioCapabilities {
|
||||||
|
public:
|
||||||
|
static constexpr bool isValidPin(int8_t pin);
|
||||||
|
static constexpr bool isValidInputPin(int8_t pin);
|
||||||
|
static constexpr bool isValidOutputPin(int8_t pin);
|
||||||
|
// result is only defined for a valid Rx pin
|
||||||
|
static constexpr bool hasPullUp(int8_t pin);
|
||||||
|
};
|
||||||
|
|
||||||
|
class GpioCapabilities : private IGpioCapabilities {
|
||||||
|
public:
|
||||||
|
static constexpr bool isValidPin(int8_t pin) {
|
||||||
|
#if defined(ESP8266)
|
||||||
|
return (pin >= 0 && pin <= 16) && !isFlashInterfacePin(pin);
|
||||||
|
#elif defined(ESP32)
|
||||||
|
// Remove the strapping pins as defined in the datasheets, they affect bootup and other critical operations
|
||||||
|
// Remmove the flash memory pins on related devices, since using these causes memory access issues.
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf,
|
||||||
|
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32/_images/esp32-devkitC-v4-pinout.jpg
|
||||||
|
return (pin == 1) || (pin >= 3 && pin <= 5) ||
|
||||||
|
(pin >= 12 && pin <= 15) ||
|
||||||
|
(!psramFound() && pin >= 16 && pin <= 17) ||
|
||||||
|
(pin >= 18 && pin <= 19) ||
|
||||||
|
(pin >= 21 && pin <= 23) || (pin >= 25 && pin <= 27) || (pin >= 32 && pin <= 39);
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf,
|
||||||
|
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/_images/esp32-s2_saola1-pinout.jpg
|
||||||
|
return (pin >= 1 && pin <= 21) || (pin >= 33 && pin <= 44);
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf,
|
||||||
|
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/_images/esp32-c3-devkitm-1-v1-pinout.jpg
|
||||||
|
return (pin >= 0 && pin <= 1) || (pin >= 3 && pin <= 7) || (pin >= 18 && pin <= 21);
|
||||||
|
#else
|
||||||
|
return pin >= 0;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
return pin >= 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr bool isValidInputPin(int8_t pin) {
|
||||||
|
return isValidPin(pin)
|
||||||
|
#if defined(ESP8266)
|
||||||
|
&& (pin != 16)
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr bool isValidOutputPin(int8_t pin) {
|
||||||
|
return isValidPin(pin)
|
||||||
|
#if defined(ESP32)
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
&& (pin < 34)
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
&& (pin <= 45)
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
// no restrictions
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
// result is only defined for a valid Rx pin
|
||||||
|
static constexpr bool hasPullUp(int8_t pin) {
|
||||||
|
#if defined(ESP32)
|
||||||
|
return !(pin >= 34 && pin <= 39);
|
||||||
|
#else
|
||||||
|
(void)pin;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Parity : uint8_t {
|
||||||
|
PARITY_NONE = 000,
|
||||||
|
PARITY_EVEN = 020,
|
||||||
|
PARITY_ODD = 030,
|
||||||
|
PARITY_MARK = 040,
|
||||||
|
PARITY_SPACE = 070,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Config {
|
||||||
|
SWSERIAL_5N1 = PARITY_NONE,
|
||||||
|
SWSERIAL_6N1,
|
||||||
|
SWSERIAL_7N1,
|
||||||
|
SWSERIAL_8N1,
|
||||||
|
SWSERIAL_5E1 = PARITY_EVEN,
|
||||||
|
SWSERIAL_6E1,
|
||||||
|
SWSERIAL_7E1,
|
||||||
|
SWSERIAL_8E1,
|
||||||
|
SWSERIAL_5O1 = PARITY_ODD,
|
||||||
|
SWSERIAL_6O1,
|
||||||
|
SWSERIAL_7O1,
|
||||||
|
SWSERIAL_8O1,
|
||||||
|
SWSERIAL_5M1 = PARITY_MARK,
|
||||||
|
SWSERIAL_6M1,
|
||||||
|
SWSERIAL_7M1,
|
||||||
|
SWSERIAL_8M1,
|
||||||
|
SWSERIAL_5S1 = PARITY_SPACE,
|
||||||
|
SWSERIAL_6S1,
|
||||||
|
SWSERIAL_7S1,
|
||||||
|
SWSERIAL_8S1,
|
||||||
|
SWSERIAL_5N2 = 0200 | PARITY_NONE,
|
||||||
|
SWSERIAL_6N2,
|
||||||
|
SWSERIAL_7N2,
|
||||||
|
SWSERIAL_8N2,
|
||||||
|
SWSERIAL_5E2 = 0200 | PARITY_EVEN,
|
||||||
|
SWSERIAL_6E2,
|
||||||
|
SWSERIAL_7E2,
|
||||||
|
SWSERIAL_8E2,
|
||||||
|
SWSERIAL_5O2 = 0200 | PARITY_ODD,
|
||||||
|
SWSERIAL_6O2,
|
||||||
|
SWSERIAL_7O2,
|
||||||
|
SWSERIAL_8O2,
|
||||||
|
SWSERIAL_5M2 = 0200 | PARITY_MARK,
|
||||||
|
SWSERIAL_6M2,
|
||||||
|
SWSERIAL_7M2,
|
||||||
|
SWSERIAL_8M2,
|
||||||
|
SWSERIAL_5S2 = 0200 | PARITY_SPACE,
|
||||||
|
SWSERIAL_6S2,
|
||||||
|
SWSERIAL_7S2,
|
||||||
|
SWSERIAL_8S2,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This class is compatible with the corresponding AVR one, however,
|
||||||
|
/// the constructor takes no arguments, for compatibility with the
|
||||||
|
/// HardwareSerial class.
|
||||||
|
/// Instead, the begin() function handles pin assignments and logic inversion.
|
||||||
|
/// It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer.
|
||||||
|
/// Bitrates up to at least 115200 can be used.
|
||||||
|
class UARTBase : public Stream {
|
||||||
|
public:
|
||||||
|
UARTBase();
|
||||||
|
/// Ctor to set defaults for pins.
|
||||||
|
/// @param rxPin the GPIO pin used for RX
|
||||||
|
/// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX
|
||||||
|
UARTBase(int8_t rxPin, int8_t txPin = -1, bool invert = false);
|
||||||
|
UARTBase(const UARTBase&) = delete;
|
||||||
|
UARTBase& operator= (const UARTBase&) = delete;
|
||||||
|
virtual ~UARTBase();
|
||||||
|
/// Configure the UARTBase object for use.
|
||||||
|
/// @param baud the TX/RX bitrate
|
||||||
|
/// @param config sets databits, parity, and stop bit count
|
||||||
|
/// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor
|
||||||
|
/// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor
|
||||||
|
/// @param invert true: uses invert line level logic
|
||||||
|
/// @param bufCapacity the capacity for the received bytes buffer
|
||||||
|
/// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous
|
||||||
|
/// bit receive buffer, a suggested size is bufCapacity times the sum of
|
||||||
|
/// start, data, parity and stop bit count.
|
||||||
|
void begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin, int8_t txPin, bool invert);
|
||||||
|
|
||||||
|
uint32_t baudRate();
|
||||||
|
/// Transmit control pin.
|
||||||
|
void setTransmitEnablePin(int8_t txEnablePin);
|
||||||
|
/// Enable (default) or disable interrupts during tx.
|
||||||
|
void enableIntTx(bool on);
|
||||||
|
/// Enable (default) or disable internal rx GPIO pull-up.
|
||||||
|
void enableRxGPIOPullUp(bool on);
|
||||||
|
/// Enable or disable (default) tx GPIO output mode.
|
||||||
|
void enableTxGPIOOpenDrain(bool on);
|
||||||
|
|
||||||
|
bool overflow();
|
||||||
|
|
||||||
|
int available() override;
|
||||||
|
#if defined(ESP8266)
|
||||||
|
int availableForWrite() override {
|
||||||
|
#else
|
||||||
|
int availableForWrite() {
|
||||||
|
#endif
|
||||||
|
if (!m_txValid) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int peek() override;
|
||||||
|
int read() override;
|
||||||
|
/// @returns The verbatim parity bit associated with the last successful read() or peek() call
|
||||||
|
bool readParity()
|
||||||
|
{
|
||||||
|
return m_lastReadParity;
|
||||||
|
}
|
||||||
|
/// @returns The calculated bit for even parity of the parameter byte
|
||||||
|
static bool parityEven(uint8_t byte) {
|
||||||
|
byte ^= byte >> 4;
|
||||||
|
byte &= 0xf;
|
||||||
|
return (0x6996 >> byte) & 1;
|
||||||
|
}
|
||||||
|
/// @returns The calculated bit for odd parity of the parameter byte
|
||||||
|
static bool parityOdd(uint8_t byte) {
|
||||||
|
byte ^= byte >> 4;
|
||||||
|
byte &= 0xf;
|
||||||
|
return (0x9669 >> byte) & 1;
|
||||||
|
}
|
||||||
|
/// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout
|
||||||
|
int read(uint8_t* buffer, size_t size)
|
||||||
|
#if defined(ESP8266)
|
||||||
|
override
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
/// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout
|
||||||
|
int read(char* buffer, size_t size) {
|
||||||
|
return read(reinterpret_cast<uint8_t*>(buffer), size);
|
||||||
|
}
|
||||||
|
/// @returns The number of bytes read into buffer, up to size. Times out if the limit set through
|
||||||
|
/// Stream::setTimeout() is reached.
|
||||||
|
size_t readBytes(uint8_t* buffer, size_t size) override;
|
||||||
|
/// @returns The number of bytes read into buffer, up to size. Times out if the limit set through
|
||||||
|
/// Stream::setTimeout() is reached.
|
||||||
|
size_t readBytes(char* buffer, size_t size) override {
|
||||||
|
return readBytes(reinterpret_cast<uint8_t*>(buffer), size);
|
||||||
|
}
|
||||||
|
void flush() override;
|
||||||
|
size_t write(uint8_t byte) override;
|
||||||
|
size_t write(uint8_t byte, Parity parity);
|
||||||
|
size_t write(const uint8_t* buffer, size_t size) override;
|
||||||
|
size_t write(const char* buffer, size_t size) {
|
||||||
|
return write(reinterpret_cast<const uint8_t*>(buffer), size);
|
||||||
|
}
|
||||||
|
size_t write(const uint8_t* buffer, size_t size, Parity parity);
|
||||||
|
size_t write(const char* buffer, size_t size, Parity parity) {
|
||||||
|
return write(reinterpret_cast<const uint8_t*>(buffer), size, parity);
|
||||||
|
}
|
||||||
|
operator bool() const {
|
||||||
|
return (-1 == m_rxPin || m_rxValid) && (-1 == m_txPin || m_txValid) && !(-1 == m_rxPin && m_oneWire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable or enable interrupts on the rx pin.
|
||||||
|
void enableRx(bool on);
|
||||||
|
/// One wire control.
|
||||||
|
void enableTx(bool on);
|
||||||
|
|
||||||
|
// AVR compatibility methods.
|
||||||
|
bool listen() { enableRx(true); return true; }
|
||||||
|
void end();
|
||||||
|
bool isListening() { return m_rxEnabled; }
|
||||||
|
bool stopListening() { enableRx(false); return true; }
|
||||||
|
|
||||||
|
/// onReceive sets a callback that will be called in interrupt context
|
||||||
|
/// when data is received.
|
||||||
|
/// More precisely, the callback is triggered when UARTBase detects
|
||||||
|
/// a new reception, which may not yet have completed on invocation.
|
||||||
|
/// Reading - never from this interrupt context - should therefore be
|
||||||
|
/// delayed at least for the duration of one incoming word.
|
||||||
|
void onReceive(const Delegate<void(), void*>& handler);
|
||||||
|
/// onReceive sets a callback that will be called in interrupt context
|
||||||
|
/// when data is received.
|
||||||
|
/// More precisely, the callback is triggered when UARTBase detects
|
||||||
|
/// a new reception, which may not yet have completed on invocation.
|
||||||
|
/// Reading - never from this interrupt context - should therefore be
|
||||||
|
/// delayed at least for the duration of one incoming word.
|
||||||
|
void onReceive(Delegate<void(), void*>&& handler);
|
||||||
|
|
||||||
|
[[deprecated("function removed; semantics of onReceive() changed; check the header file.")]]
|
||||||
|
void perform_work();
|
||||||
|
|
||||||
|
using Print::write;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void beginRx(bool hasPullUp, int bufCapacity, int isrBufCapacity);
|
||||||
|
void beginTx();
|
||||||
|
// Member variables
|
||||||
|
int8_t m_rxPin = -1;
|
||||||
|
int8_t m_txPin = -1;
|
||||||
|
bool m_invert = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// It's legal to exceed the deadline, for instance,
|
||||||
|
// by enabling interrupts.
|
||||||
|
void lazyDelay();
|
||||||
|
// Synchronous precise delay
|
||||||
|
void preciseDelay();
|
||||||
|
// If withStopBit is set, either cycle contains a stop bit.
|
||||||
|
// If dutyCycle == 0, the level is not forced to HIGH.
|
||||||
|
// If offCycle == 0, the level remains unchanged from dutyCycle.
|
||||||
|
void writePeriod(
|
||||||
|
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit);
|
||||||
|
// safely set the pin mode for the Rx GPIO pin
|
||||||
|
void setRxGPIOPinMode();
|
||||||
|
// safely set the pin mode for the Tx GPIO pin
|
||||||
|
void setTxGPIOPinMode();
|
||||||
|
/* check m_rxValid that calling is safe */
|
||||||
|
void rxBits();
|
||||||
|
void rxBits(const uint32_t isrTick);
|
||||||
|
static void disableInterrupts();
|
||||||
|
static void restoreInterrupts();
|
||||||
|
|
||||||
|
static void rxBitISR(UARTBase* self);
|
||||||
|
static void rxBitSyncISR(UARTBase* self);
|
||||||
|
|
||||||
|
static inline uint32_t IRAM_ATTR microsToTicks(uint32_t micros) ALWAYS_INLINE_ATTR {
|
||||||
|
return micros << 1;
|
||||||
|
}
|
||||||
|
static inline uint32_t ticksToMicros(uint32_t ticks) ALWAYS_INLINE_ATTR {
|
||||||
|
return ticks >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Member variables
|
||||||
|
volatile uint32_t* m_rxReg;
|
||||||
|
uint32_t m_rxBitMask;
|
||||||
|
#if !defined(ESP8266)
|
||||||
|
volatile uint32_t* m_txReg;
|
||||||
|
#endif
|
||||||
|
uint32_t m_txBitMask;
|
||||||
|
int8_t m_txEnablePin = -1;
|
||||||
|
uint8_t m_dataBits;
|
||||||
|
bool m_oneWire;
|
||||||
|
bool m_rxValid = false;
|
||||||
|
bool m_rxEnabled = false;
|
||||||
|
bool m_txValid = false;
|
||||||
|
bool m_txEnableValid = false;
|
||||||
|
/// PDU bits include data, parity and stop bits; the start bit is not counted.
|
||||||
|
uint8_t m_pduBits;
|
||||||
|
bool m_intTxEnabled;
|
||||||
|
bool m_rxGPIOHasPullUp = false;
|
||||||
|
bool m_rxGPIOPullUpEnabled = true;
|
||||||
|
bool m_txGPIOOpenDrain = false;
|
||||||
|
Parity m_parityMode;
|
||||||
|
uint8_t m_stopBits;
|
||||||
|
bool m_lastReadParity;
|
||||||
|
bool m_overflow = false;
|
||||||
|
uint32_t m_bitTicks;
|
||||||
|
uint8_t m_parityInPos;
|
||||||
|
uint8_t m_parityOutPos;
|
||||||
|
int8_t m_rxLastBit; // 0 thru (m_pduBits - m_stopBits - 1): data/parity bits. -1: start bit. (m_pduBits - 1): stop bit.
|
||||||
|
uint8_t m_rxCurByte = 0;
|
||||||
|
std::unique_ptr<circular_queue<uint8_t> > m_buffer;
|
||||||
|
std::unique_ptr<circular_queue<uint8_t> > m_parityBuffer;
|
||||||
|
uint32_t m_periodStart;
|
||||||
|
uint32_t m_periodDuration;
|
||||||
|
#ifndef ESP32
|
||||||
|
static uint32_t m_savedPS;
|
||||||
|
#else
|
||||||
|
static portMUX_TYPE m_interruptsMux;
|
||||||
|
#endif
|
||||||
|
// the ISR stores the relative bit times in the buffer. The inversion corrected level is used as sign bit (2's complement):
|
||||||
|
// 1 = positive including 0, 0 = negative.
|
||||||
|
std::unique_ptr<circular_queue<uint32_t, UARTBase*> > m_isrBuffer;
|
||||||
|
const Delegate<void(uint32_t&&), UARTBase*> m_isrBufferForEachDel { [](UARTBase* self, uint32_t&& isrTick) { self->rxBits(isrTick); }, this };
|
||||||
|
std::atomic<bool> m_isrOverflow { false };
|
||||||
|
uint32_t m_isrLastTick;
|
||||||
|
bool m_rxCurParity = false;
|
||||||
|
Delegate<void(), void*> m_rxHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< class GpioCapabilities > class BasicUART : public UARTBase {
|
||||||
|
static_assert(std::is_base_of<IGpioCapabilities, GpioCapabilities>::value,
|
||||||
|
"template argument is not derived from IGpioCapabilities");
|
||||||
|
public:
|
||||||
|
BasicUART() : UARTBase() {
|
||||||
|
}
|
||||||
|
/// Ctor to set defaults for pins.
|
||||||
|
/// @param rxPin the GPIO pin used for RX
|
||||||
|
/// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX
|
||||||
|
BasicUART(int8_t rxPin, int8_t txPin = -1, bool invert = false) :
|
||||||
|
UARTBase(rxPin, txPin, invert) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the BasicUART object for use.
|
||||||
|
/// @param baud the TX/RX bitrate
|
||||||
|
/// @param config sets databits, parity, and stop bit count
|
||||||
|
/// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor
|
||||||
|
/// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor
|
||||||
|
/// @param invert true: uses invert line level logic
|
||||||
|
/// @param bufCapacity the capacity for the received bytes buffer
|
||||||
|
/// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous
|
||||||
|
/// bit receive buffer, a suggested size is bufCapacity times the sum of
|
||||||
|
/// start, data, parity and stop bit count.
|
||||||
|
void begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin, int8_t txPin, bool invert,
|
||||||
|
int bufCapacity = 64, int isrBufCapacity = 0) {
|
||||||
|
UARTBase::begin(baud, config, rxPin, txPin, invert);
|
||||||
|
if (GpioCapabilities::isValidInputPin(rxPin)) {
|
||||||
|
beginRx(GpioCapabilities:: hasPullUp(rxPin), bufCapacity, isrBufCapacity);
|
||||||
|
}
|
||||||
|
if (GpioCapabilities::isValidOutputPin(txPin)) {
|
||||||
|
beginTx();
|
||||||
|
}
|
||||||
|
enableRx(true);
|
||||||
|
}
|
||||||
|
void begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin, int8_t txPin) {
|
||||||
|
begin(baud, config, rxPin, txPin, m_invert);
|
||||||
|
}
|
||||||
|
void begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin) {
|
||||||
|
begin(baud, config, rxPin, m_txPin, m_invert);
|
||||||
|
}
|
||||||
|
void begin(uint32_t baud, Config config = SWSERIAL_8N1) {
|
||||||
|
begin(baud, config, m_rxPin, m_txPin, m_invert);
|
||||||
|
}
|
||||||
|
void setTransmitEnablePin(int8_t txEnablePin) {
|
||||||
|
UARTBase::setTransmitEnablePin(
|
||||||
|
GpioCapabilities::isValidOutputPin(txEnablePin) ? txEnablePin : -1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using UART = BasicUART< GpioCapabilities >;
|
||||||
|
|
||||||
|
}; // namespace EspSoftwareSerial
|
||||||
|
|
||||||
|
using SoftwareSerial = EspSoftwareSerial::UART;
|
||||||
|
using namespace EspSoftwareSerial;
|
||||||
|
|
||||||
|
#if __GNUC__ < 12
|
||||||
|
// The template member functions below must be in IRAM, but due to a bug GCC doesn't currently
|
||||||
|
// honor the attribute. Instead, it is possible to do explicit specialization and adorn
|
||||||
|
// these with the IRAM attribute:
|
||||||
|
// Delegate<>::operator (), circular_queue<>::available,
|
||||||
|
// circular_queue<>::available_for_push, circular_queue<>::push_peek, circular_queue<>::push
|
||||||
|
|
||||||
|
extern template void delegate::detail::DelegateImpl<void*, void>::operator()() const;
|
||||||
|
extern template size_t circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::available() const;
|
||||||
|
extern template bool circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::push(uint32_t&&);
|
||||||
|
extern template bool circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::push(const uint32_t&);
|
||||||
|
#endif // __GNUC__ < 12
|
||||||
|
|
||||||
|
#endif // __SoftwareSerial_h
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,567 @@
|
|||||||
|
/*
|
||||||
|
MultiDelegate.h - A queue or event multiplexer based on the efficient Delegate
|
||||||
|
class
|
||||||
|
Copyright (c) 2019-2020 Dirk O. Kaar. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __MULTIDELEGATE_H
|
||||||
|
#define __MULTIDELEGATE_H
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
#include <atomic>
|
||||||
|
#else
|
||||||
|
#include "circular_queue/ghostl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#include <interrupts.h>
|
||||||
|
using esp8266::InterruptLock;
|
||||||
|
#elif defined(ARDUINO)
|
||||||
|
class InterruptLock {
|
||||||
|
public:
|
||||||
|
InterruptLock() {
|
||||||
|
noInterrupts();
|
||||||
|
}
|
||||||
|
~InterruptLock() {
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
#include <mutex>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE = false, typename... P>
|
||||||
|
struct CallP
|
||||||
|
{
|
||||||
|
static R execute(Delegate& del, P... args)
|
||||||
|
{
|
||||||
|
return del(std::forward<P...>(args...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, bool ISQUEUE, typename... P>
|
||||||
|
struct CallP<Delegate, void, ISQUEUE, P...>
|
||||||
|
{
|
||||||
|
static bool execute(Delegate& del, P... args)
|
||||||
|
{
|
||||||
|
del(std::forward<P...>(args...));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE = false>
|
||||||
|
struct Call
|
||||||
|
{
|
||||||
|
static R execute(Delegate& del)
|
||||||
|
{
|
||||||
|
return del();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, bool ISQUEUE>
|
||||||
|
struct Call<Delegate, void, ISQUEUE>
|
||||||
|
{
|
||||||
|
static bool execute(Delegate& del)
|
||||||
|
{
|
||||||
|
del();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace delegate
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32, typename... P>
|
||||||
|
class MultiDelegatePImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MultiDelegatePImpl() = default;
|
||||||
|
~MultiDelegatePImpl()
|
||||||
|
{
|
||||||
|
*this = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl(const MultiDelegatePImpl&) = delete;
|
||||||
|
MultiDelegatePImpl& operator=(const MultiDelegatePImpl&) = delete;
|
||||||
|
|
||||||
|
MultiDelegatePImpl(MultiDelegatePImpl&& md)
|
||||||
|
{
|
||||||
|
first = md.first;
|
||||||
|
last = md.last;
|
||||||
|
unused = md.unused;
|
||||||
|
nodeCount = md.nodeCount;
|
||||||
|
md.first = nullptr;
|
||||||
|
md.last = nullptr;
|
||||||
|
md.unused = nullptr;
|
||||||
|
md.nodeCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl(const Delegate& del)
|
||||||
|
{
|
||||||
|
add(del);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl(Delegate&& del)
|
||||||
|
{
|
||||||
|
add(std::move(del));
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl& operator=(MultiDelegatePImpl&& md)
|
||||||
|
{
|
||||||
|
first = md.first;
|
||||||
|
last = md.last;
|
||||||
|
unused = md.unused;
|
||||||
|
nodeCount = md.nodeCount;
|
||||||
|
md.first = nullptr;
|
||||||
|
md.last = nullptr;
|
||||||
|
md.unused = nullptr;
|
||||||
|
md.nodeCount = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl& operator=(std::nullptr_t)
|
||||||
|
{
|
||||||
|
if (last)
|
||||||
|
last->mNext = unused;
|
||||||
|
if (first)
|
||||||
|
unused = first;
|
||||||
|
while (unused)
|
||||||
|
{
|
||||||
|
auto to_delete = unused;
|
||||||
|
unused = unused->mNext;
|
||||||
|
delete(to_delete);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl& operator+=(const Delegate& del)
|
||||||
|
{
|
||||||
|
add(del);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl& operator+=(Delegate&& del)
|
||||||
|
{
|
||||||
|
add(std::move(del));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct Node_t
|
||||||
|
{
|
||||||
|
~Node_t()
|
||||||
|
{
|
||||||
|
mDelegate = nullptr; // special overload in Delegate
|
||||||
|
}
|
||||||
|
Node_t* mNext = nullptr;
|
||||||
|
Delegate mDelegate;
|
||||||
|
};
|
||||||
|
|
||||||
|
Node_t* first = nullptr;
|
||||||
|
Node_t* last = nullptr;
|
||||||
|
Node_t* unused = nullptr;
|
||||||
|
size_t nodeCount = 0;
|
||||||
|
|
||||||
|
// Returns a pointer to an unused Node_t,
|
||||||
|
// or if none are available allocates a new one,
|
||||||
|
// or nullptr if limit is reached
|
||||||
|
Node_t* IRAM_ATTR get_node_unsafe()
|
||||||
|
{
|
||||||
|
Node_t* result = nullptr;
|
||||||
|
// try to get an item from unused items list
|
||||||
|
if (unused)
|
||||||
|
{
|
||||||
|
result = unused;
|
||||||
|
unused = unused->mNext;
|
||||||
|
}
|
||||||
|
// if no unused items, and count not too high, allocate a new one
|
||||||
|
else if (nodeCount < QUEUE_CAPACITY)
|
||||||
|
{
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
result = new (std::nothrow) Node_t;
|
||||||
|
#else
|
||||||
|
result = new Node_t;
|
||||||
|
#endif
|
||||||
|
if (result)
|
||||||
|
++nodeCount;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recycle_node_unsafe(Node_t* node)
|
||||||
|
{
|
||||||
|
node->mDelegate = nullptr; // special overload in Delegate
|
||||||
|
node->mNext = unused;
|
||||||
|
unused = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef ARDUINO
|
||||||
|
std::mutex mutex_unused;
|
||||||
|
#endif
|
||||||
|
public:
|
||||||
|
class iterator : public std::iterator<std::forward_iterator_tag, Delegate>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Node_t* current = nullptr;
|
||||||
|
Node_t* prev = nullptr;
|
||||||
|
const Node_t* stop = nullptr;
|
||||||
|
|
||||||
|
iterator(MultiDelegatePImpl& md) : current(md.first), stop(md.last) {}
|
||||||
|
iterator() = default;
|
||||||
|
iterator(const iterator&) = default;
|
||||||
|
iterator& operator=(const iterator&) = default;
|
||||||
|
iterator& operator=(iterator&&) = default;
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return current && stop;
|
||||||
|
}
|
||||||
|
bool operator==(const iterator& rhs) const
|
||||||
|
{
|
||||||
|
return current == rhs.current;
|
||||||
|
}
|
||||||
|
bool operator!=(const iterator& rhs) const
|
||||||
|
{
|
||||||
|
return !operator==(rhs);
|
||||||
|
}
|
||||||
|
Delegate& operator*() const
|
||||||
|
{
|
||||||
|
return current->mDelegate;
|
||||||
|
}
|
||||||
|
Delegate* operator->() const
|
||||||
|
{
|
||||||
|
return ¤t->mDelegate;
|
||||||
|
}
|
||||||
|
iterator& operator++() // prefix
|
||||||
|
{
|
||||||
|
if (current && stop != current)
|
||||||
|
{
|
||||||
|
prev = current;
|
||||||
|
current = current->mNext;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
current = nullptr; // end
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
iterator& operator++(int) // postfix
|
||||||
|
{
|
||||||
|
iterator tmp(*this);
|
||||||
|
operator++();
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
return iterator(*this);
|
||||||
|
}
|
||||||
|
iterator end() const
|
||||||
|
{
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Delegate* add(const Delegate& del)
|
||||||
|
{
|
||||||
|
return add(Delegate(del));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Delegate* add(Delegate&& del)
|
||||||
|
{
|
||||||
|
if (!del)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
#else
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_unused);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Node_t* item = ISQUEUE ? get_node_unsafe() :
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
new (std::nothrow) Node_t;
|
||||||
|
#else
|
||||||
|
new Node_t;
|
||||||
|
#endif
|
||||||
|
if (!item)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
item->mDelegate = std::move(del);
|
||||||
|
item->mNext = nullptr;
|
||||||
|
|
||||||
|
if (last)
|
||||||
|
last->mNext = item;
|
||||||
|
else
|
||||||
|
first = item;
|
||||||
|
last = item;
|
||||||
|
|
||||||
|
return &item->mDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator erase(iterator it)
|
||||||
|
{
|
||||||
|
if (!it)
|
||||||
|
return end();
|
||||||
|
#ifdef ARDUINO
|
||||||
|
InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
#else
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_unused);
|
||||||
|
#endif
|
||||||
|
auto to_recycle = it.current;
|
||||||
|
|
||||||
|
if (last == it.current)
|
||||||
|
last = it.prev;
|
||||||
|
it.current = it.current->mNext;
|
||||||
|
if (it.prev)
|
||||||
|
{
|
||||||
|
it.prev->mNext = it.current;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = it.current;
|
||||||
|
}
|
||||||
|
if (ISQUEUE)
|
||||||
|
recycle_node_unsafe(to_recycle);
|
||||||
|
else
|
||||||
|
delete to_recycle;
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase(const Delegate* const del)
|
||||||
|
{
|
||||||
|
auto it = begin();
|
||||||
|
while (it)
|
||||||
|
{
|
||||||
|
if (del == &(*it))
|
||||||
|
{
|
||||||
|
erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
R operator()(P... args)
|
||||||
|
{
|
||||||
|
auto it = begin();
|
||||||
|
if (!it)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
static std::atomic<bool> fence(false);
|
||||||
|
// prevent recursive calls
|
||||||
|
#if defined(ARDUINO) && !defined(ESP32)
|
||||||
|
if (fence.load()) return {};
|
||||||
|
fence.store(true);
|
||||||
|
#else
|
||||||
|
if (fence.exchange(true)) return {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
R result;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
result = CallP<Delegate, R, ISQUEUE, P...>::execute(*it, args...);
|
||||||
|
if (result && ISQUEUE)
|
||||||
|
it = erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
// running callbacks might last too long for watchdog etc.
|
||||||
|
optimistic_yield(10000);
|
||||||
|
#endif
|
||||||
|
} while (it);
|
||||||
|
|
||||||
|
fence.store(false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, typename R = void, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32>
|
||||||
|
class MultiDelegateImpl : public MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>::MultiDelegatePImpl;
|
||||||
|
|
||||||
|
R operator()()
|
||||||
|
{
|
||||||
|
auto it = this->begin();
|
||||||
|
if (!it)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
static std::atomic<bool> fence(false);
|
||||||
|
// prevent recursive calls
|
||||||
|
#if defined(ARDUINO) && !defined(ESP32)
|
||||||
|
if (fence.load()) return {};
|
||||||
|
fence.store(true);
|
||||||
|
#else
|
||||||
|
if (fence.exchange(true)) return {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
R result;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
result = Call<Delegate, R, ISQUEUE>::execute(*it);
|
||||||
|
if (result && ISQUEUE)
|
||||||
|
it = this->erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
// running callbacks might last too long for watchdog etc.
|
||||||
|
optimistic_yield(10000);
|
||||||
|
#endif
|
||||||
|
} while (it);
|
||||||
|
|
||||||
|
fence.store(false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> class MultiDelegate;
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P>
|
||||||
|
class MultiDelegate<Delegate, R(P...), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY, P...>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY, P...>::MultiDelegatePImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY>
|
||||||
|
class MultiDelegate<Delegate, R(), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegateImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegateImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>::MultiDelegateImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P>
|
||||||
|
class MultiDelegate<Delegate, void(P...), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegatePImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY, P...>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegatePImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY, P...>::MultiDelegatePImpl;
|
||||||
|
|
||||||
|
void operator()(P... args)
|
||||||
|
{
|
||||||
|
auto it = this->begin();
|
||||||
|
if (!it)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static std::atomic<bool> fence(false);
|
||||||
|
// prevent recursive calls
|
||||||
|
#if defined(ARDUINO) && !defined(ESP32)
|
||||||
|
if (fence.load()) return;
|
||||||
|
fence.store(true);
|
||||||
|
#else
|
||||||
|
if (fence.exchange(true)) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
CallP<Delegate, void, ISQUEUE, P...>::execute(*it, args...);
|
||||||
|
if (ISQUEUE)
|
||||||
|
it = this->erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
// running callbacks might last too long for watchdog etc.
|
||||||
|
optimistic_yield(10000);
|
||||||
|
#endif
|
||||||
|
} while (it);
|
||||||
|
|
||||||
|
fence.store(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY>
|
||||||
|
class MultiDelegate<Delegate, void(), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegateImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegateImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY>::MultiDelegateImpl;
|
||||||
|
|
||||||
|
void operator()()
|
||||||
|
{
|
||||||
|
auto it = this->begin();
|
||||||
|
if (!it)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static std::atomic<bool> fence(false);
|
||||||
|
// prevent recursive calls
|
||||||
|
#if defined(ARDUINO) && !defined(ESP32)
|
||||||
|
if (fence.load()) return;
|
||||||
|
fence.store(true);
|
||||||
|
#else
|
||||||
|
if (fence.exchange(true)) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Call<Delegate, void, ISQUEUE>::execute(*it);
|
||||||
|
if (ISQUEUE)
|
||||||
|
it = this->erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
// running callbacks might last too long for watchdog etc.
|
||||||
|
optimistic_yield(10000);
|
||||||
|
#endif
|
||||||
|
} while (it);
|
||||||
|
|
||||||
|
fence.store(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The MultiDelegate class template can be specialized to either a queue or an event multiplexer.
|
||||||
|
It is designed to be used with Delegate, the efficient runtime wrapper for C function ptr and C++ std::function.
|
||||||
|
@tparam Delegate specifies the concrete type that MultiDelegate bases the queue or event multiplexer on.
|
||||||
|
@tparam ISQUEUE modifies the generated MultiDelegate class in subtle ways. In queue mode (ISQUEUE == true),
|
||||||
|
the value of QUEUE_CAPACITY enforces the maximum number of simultaneous items the queue can contain.
|
||||||
|
This is exploited to minimize the use of new and delete by reusing already allocated items, thus
|
||||||
|
reducing heap fragmentation. In event multiplexer mode (ISQUEUE = false), new and delete are
|
||||||
|
used for allocation of the event handler items.
|
||||||
|
If the result type of the function call operator of Delegate is void, calling a MultiDelegate queue
|
||||||
|
removes each item after calling it; a Multidelegate event multiplexer keeps event handlers until
|
||||||
|
explicitly removed.
|
||||||
|
If the result type of the function call operator of Delegate is non-void, in a MultiDelegate queue
|
||||||
|
the type-conversion to bool of that result determines if the item is immediately removed or kept
|
||||||
|
after each call: if true is returned, the item is removed. A Multidelegate event multiplexer keeps event
|
||||||
|
handlers until they are explicitly removed.
|
||||||
|
@tparam QUEUE_CAPACITY is only used if ISQUEUE == true. Then, it sets the maximum capacity that the queue dynamically
|
||||||
|
allocates from the heap. Unused items are not returned to the heap, but are managed by the MultiDelegate
|
||||||
|
instance during its own lifetime for efficiency.
|
||||||
|
*/
|
||||||
|
template< typename Delegate, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32>
|
||||||
|
class MultiDelegate : public delegate::detail::MultiDelegate<Delegate, typename Delegate::target_type, ISQUEUE, QUEUE_CAPACITY>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using delegate::detail::MultiDelegate<Delegate, typename Delegate::target_type, ISQUEUE, QUEUE_CAPACITY>::MultiDelegate;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __MULTIDELEGATE_H
|
@ -0,0 +1,384 @@
|
|||||||
|
/*
|
||||||
|
circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial.
|
||||||
|
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __circular_queue_h
|
||||||
|
#define __circular_queue_h
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "Delegate.h"
|
||||||
|
using std::min;
|
||||||
|
#else
|
||||||
|
#include "ghostl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(ESP32) && !defined(ESP8266)
|
||||||
|
#define IRAM_ATTR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
#undef ALWAYS_INLINE_ATTR
|
||||||
|
#define ALWAYS_INLINE_ATTR __attribute__((always_inline))
|
||||||
|
#else
|
||||||
|
#define ALWAYS_INLINE_ATTR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO).
|
||||||
|
This implementation is lock-free between producer and consumer for the available(), peek(),
|
||||||
|
pop(), and push() type functions.
|
||||||
|
*/
|
||||||
|
template< typename T, typename ForEachArg = void >
|
||||||
|
class circular_queue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
@brief Constructs a valid, but zero-capacity dummy queue.
|
||||||
|
*/
|
||||||
|
circular_queue() : m_bufSize(1)
|
||||||
|
{
|
||||||
|
m_inPos.store(0);
|
||||||
|
m_outPos.store(0);
|
||||||
|
}
|
||||||
|
/*!
|
||||||
|
@brief Constructs a queue of the given maximum capacity.
|
||||||
|
*/
|
||||||
|
circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize])
|
||||||
|
{
|
||||||
|
m_inPos.store(0);
|
||||||
|
m_outPos.store(0);
|
||||||
|
}
|
||||||
|
circular_queue(circular_queue&& cq) :
|
||||||
|
m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load())
|
||||||
|
{}
|
||||||
|
~circular_queue()
|
||||||
|
{
|
||||||
|
m_buffer.reset();
|
||||||
|
}
|
||||||
|
circular_queue(const circular_queue&) = delete;
|
||||||
|
circular_queue& operator=(circular_queue&& cq)
|
||||||
|
{
|
||||||
|
m_bufSize = cq.m_bufSize;
|
||||||
|
m_buffer = cq.m_buffer;
|
||||||
|
m_inPos.store(cq.m_inPos.load());
|
||||||
|
m_outPos.store(cq.m_outPos.load());
|
||||||
|
}
|
||||||
|
circular_queue& operator=(const circular_queue&) = delete;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Get the numer of elements the queue can hold at most.
|
||||||
|
*/
|
||||||
|
size_t capacity() const
|
||||||
|
{
|
||||||
|
return m_bufSize - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Resize the queue. The available elements in the queue are preserved.
|
||||||
|
This is not lock-free and concurrent producer or consumer access
|
||||||
|
will lead to corruption.
|
||||||
|
@return True if the new capacity could accommodate the present elements in
|
||||||
|
the queue, otherwise nothing is done and false is returned.
|
||||||
|
*/
|
||||||
|
bool capacity(const size_t cap);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Discard all data in the queue.
|
||||||
|
*/
|
||||||
|
void flush()
|
||||||
|
{
|
||||||
|
m_outPos.store(m_inPos.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Get a snapshot number of elements that can be retrieved by pop.
|
||||||
|
*/
|
||||||
|
size_t IRAM_ATTR available() const
|
||||||
|
{
|
||||||
|
int avail = static_cast<int>(m_inPos.load() - m_outPos.load());
|
||||||
|
if (avail < 0) avail += m_bufSize;
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Get the remaining free elementes for pushing.
|
||||||
|
*/
|
||||||
|
size_t IRAM_ATTR available_for_push() const
|
||||||
|
{
|
||||||
|
int avail = static_cast<int>(m_outPos.load() - m_inPos.load()) - 1;
|
||||||
|
if (avail < 0) avail += m_bufSize;
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Peek at the next element pop will return without removing it from the queue.
|
||||||
|
@return An rvalue copy of the next element that can be popped. If the queue is empty,
|
||||||
|
return an rvalue copy of the element that is pending the next push.
|
||||||
|
*/
|
||||||
|
T peek() const
|
||||||
|
{
|
||||||
|
const auto outPos = m_outPos.load(std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
return m_buffer[outPos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Peek at the next pending input value.
|
||||||
|
@return A reference to the next element that can be pushed.
|
||||||
|
*/
|
||||||
|
T& IRAM_ATTR pushpeek()
|
||||||
|
{
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
return m_buffer[inPos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Release the next pending input value, accessible by pushpeek(), into the queue.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
bool IRAM_ATTR push()
|
||||||
|
{
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||||
|
const size_t next = (inPos + 1) % m_bufSize;
|
||||||
|
if (next == m_outPos.load(std::memory_order_relaxed)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
m_inPos.store(next, std::memory_order_release);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Move the rvalue parameter into the queue.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
bool IRAM_ATTR push(T&& val)
|
||||||
|
{
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||||
|
const size_t next = (inPos + 1) % m_bufSize;
|
||||||
|
if (next == m_outPos.load(std::memory_order_relaxed)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_buffer[inPos] = std::move(val);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
m_inPos.store(next, std::memory_order_release);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Push a copy of the parameter into the queue.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
inline bool IRAM_ATTR push(const T& val) ALWAYS_INLINE_ATTR
|
||||||
|
{
|
||||||
|
T v(val);
|
||||||
|
return push(std::move(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
/*!
|
||||||
|
@brief Push copies of multiple elements from a buffer into the queue,
|
||||||
|
in order, beginning at buffer's head.
|
||||||
|
@return The number of elements actually copied into the queue, counted
|
||||||
|
from the buffer head.
|
||||||
|
*/
|
||||||
|
size_t push_n(const T* buffer, size_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Pop the next available element from the queue.
|
||||||
|
@return An rvalue copy of the popped element, or a default
|
||||||
|
value of type T if the queue is empty.
|
||||||
|
*/
|
||||||
|
T pop();
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
/*!
|
||||||
|
@brief Pop multiple elements in ordered sequence from the queue to a buffer.
|
||||||
|
If buffer is nullptr, simply discards up to size elements from the queue.
|
||||||
|
@return The number of elements actually popped from the queue to
|
||||||
|
buffer.
|
||||||
|
*/
|
||||||
|
size_t pop_n(T* buffer, size_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Iterate over and remove each available element from queue,
|
||||||
|
calling back fun with an rvalue reference of every single element.
|
||||||
|
*/
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
void for_each(const Delegate<void(T&&), ForEachArg>& fun);
|
||||||
|
#else
|
||||||
|
void for_each(Delegate<void(T&&), ForEachArg> fun);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief In reverse order, iterate over, pop and optionally requeue each available element from the queue,
|
||||||
|
calling back fun with a reference of every single element.
|
||||||
|
Requeuing is dependent on the return boolean of the callback function. If it
|
||||||
|
returns true, the requeue occurs.
|
||||||
|
*/
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
bool for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun);
|
||||||
|
#else
|
||||||
|
bool for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t m_bufSize;
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
std::unique_ptr<T[]> m_buffer;
|
||||||
|
#else
|
||||||
|
std::unique_ptr<T> m_buffer;
|
||||||
|
#endif
|
||||||
|
std::atomic<size_t> m_inPos;
|
||||||
|
std::atomic<size_t> m_outPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
bool circular_queue<T, ForEachArg>::capacity(const size_t cap)
|
||||||
|
{
|
||||||
|
if (cap + 1 == m_bufSize) return true;
|
||||||
|
else if (available() > cap) return false;
|
||||||
|
std::unique_ptr<T[] > buffer(new T[cap + 1]);
|
||||||
|
const auto available = pop_n(buffer, cap);
|
||||||
|
m_buffer.reset(buffer);
|
||||||
|
m_bufSize = cap + 1;
|
||||||
|
m_inPos.store(available, std::memory_order_relaxed);
|
||||||
|
m_outPos.store(0, std::memory_order_relaxed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
size_t circular_queue<T, ForEachArg>::push_n(const T* buffer, size_t size)
|
||||||
|
{
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||||
|
const auto outPos = m_outPos.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos;
|
||||||
|
blockSize = min(size, blockSize);
|
||||||
|
if (!blockSize) return 0;
|
||||||
|
int next = (inPos + blockSize) % m_bufSize;
|
||||||
|
|
||||||
|
auto dest = m_buffer.get() + inPos;
|
||||||
|
std::copy_n(std::make_move_iterator(buffer), blockSize, dest);
|
||||||
|
size = min(size - blockSize, outPos > 1 ? static_cast<size_t>(outPos - next - 1) : 0);
|
||||||
|
next += size;
|
||||||
|
dest = m_buffer.get();
|
||||||
|
std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest);
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
m_inPos.store(next, std::memory_order_release);
|
||||||
|
return blockSize + size;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
T circular_queue<T, ForEachArg>::pop()
|
||||||
|
{
|
||||||
|
const auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||||
|
if (m_inPos.load(std::memory_order_relaxed) == outPos) return {};
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
|
||||||
|
auto val = std::move(m_buffer[outPos]);
|
||||||
|
|
||||||
|
m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
size_t circular_queue<T, ForEachArg>::pop_n(T* buffer, size_t size) {
|
||||||
|
size_t avail = size = min(size, available());
|
||||||
|
if (!avail) return 0;
|
||||||
|
const auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||||
|
size_t n = min(avail, static_cast<size_t>(m_bufSize - outPos));
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if (buffer) {
|
||||||
|
buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer);
|
||||||
|
avail -= n;
|
||||||
|
std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
void circular_queue<T, ForEachArg>::for_each(const Delegate<void(T&&), ForEachArg>& fun)
|
||||||
|
#else
|
||||||
|
void circular_queue<T, ForEachArg>::for_each(Delegate<void(T&&), ForEachArg> fun)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
while (outPos != inPos)
|
||||||
|
{
|
||||||
|
fun(std::move(m_buffer[outPos]));
|
||||||
|
outPos = (outPos + 1) % m_bufSize;
|
||||||
|
m_outPos.store(outPos, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
bool circular_queue<T, ForEachArg>::for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun)
|
||||||
|
#else
|
||||||
|
bool circular_queue<T, ForEachArg>::for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
auto inPos0 = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_acquire);
|
||||||
|
auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed);
|
||||||
|
if (outPos == inPos0) return false;
|
||||||
|
auto pos = inPos0;
|
||||||
|
auto outPos1 = inPos0;
|
||||||
|
const auto posDecr = circular_queue<T, ForEachArg>::m_bufSize - 1;
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
pos = (pos + posDecr) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||||
|
T&& val = std::move(circular_queue<T, ForEachArg>::m_buffer[pos]);
|
||||||
|
if (fun(val))
|
||||||
|
{
|
||||||
|
outPos1 = (outPos1 + posDecr) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||||
|
if (outPos1 != pos) circular_queue<T, ForEachArg>::m_buffer[outPos1] = std::move(val);
|
||||||
|
}
|
||||||
|
} while (pos != outPos);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
circular_queue<T, ForEachArg>::m_outPos.store(outPos1, std::memory_order_release);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __circular_queue_h
|
@ -0,0 +1,310 @@
|
|||||||
|
/*
|
||||||
|
circular_queue_mp.h - Implementation of a lock-free circular queue for EspSoftwareSerial.
|
||||||
|
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __circular_queue_mp_h
|
||||||
|
#define __circular_queue_mp_h
|
||||||
|
|
||||||
|
#include "circular_queue.h"
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#include <interrupts.h>
|
||||||
|
using esp8266::InterruptLock;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO).
|
||||||
|
This implementation is lock-free between producers and consumer for the available(), peek(),
|
||||||
|
pop(), and push() type functions.
|
||||||
|
*/
|
||||||
|
template< typename T, typename ForEachArg = void >
|
||||||
|
class circular_queue_mp : protected circular_queue<T, ForEachArg>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
circular_queue_mp() : circular_queue<T, ForEachArg>()
|
||||||
|
{
|
||||||
|
m_inPos_mp.store(0);
|
||||||
|
m_concurrent_mp.store(0);
|
||||||
|
}
|
||||||
|
circular_queue_mp(const size_t capacity) : circular_queue<T, ForEachArg>(capacity)
|
||||||
|
{
|
||||||
|
m_inPos_mp.store(0);
|
||||||
|
m_concurrent_mp.store(0);
|
||||||
|
}
|
||||||
|
circular_queue_mp(circular_queue_mp<T, ForEachArg>&& cq) : circular_queue<T, ForEachArg>(std::move(cq))
|
||||||
|
{
|
||||||
|
m_inPos_mp.store(cq.m_inPos_mp.load());
|
||||||
|
m_concurrent_mp.store(cq.m_concurrent_mp.load());
|
||||||
|
}
|
||||||
|
circular_queue_mp& operator=(circular_queue_mp&& cq)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::operator=(std::move(cq));
|
||||||
|
m_inPos_mp.store(cq.m_inPos_mp.load());
|
||||||
|
m_concurrent_mp.store(cq.m_concurrent_mp.load());
|
||||||
|
}
|
||||||
|
circular_queue_mp& operator=(const circular_queue_mp&) = delete;
|
||||||
|
|
||||||
|
using circular_queue<T, ForEachArg>::capacity;
|
||||||
|
using circular_queue<T, ForEachArg>::flush;
|
||||||
|
using circular_queue<T, ForEachArg>::peek;
|
||||||
|
using circular_queue<T, ForEachArg>::pop;
|
||||||
|
using circular_queue<T, ForEachArg>::pop_n;
|
||||||
|
using circular_queue<T, ForEachArg>::for_each;
|
||||||
|
using circular_queue<T, ForEachArg>::for_each_rev_requeue;
|
||||||
|
|
||||||
|
T& pushpeek() = delete;
|
||||||
|
bool push() = delete;
|
||||||
|
|
||||||
|
inline size_t IRAM_ATTR available() const ALWAYS_INLINE_ATTR
|
||||||
|
{
|
||||||
|
return circular_queue<T, ForEachArg>::available();
|
||||||
|
}
|
||||||
|
inline size_t IRAM_ATTR available_for_push() const ALWAYS_INLINE_ATTR
|
||||||
|
{
|
||||||
|
return circular_queue<T, ForEachArg>::available_for_push();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Resize the queue. The available elements in the queue are preserved.
|
||||||
|
This is not lock-free and concurrent producer or consumer access
|
||||||
|
will lead to corruption.
|
||||||
|
@return True if the new capacity could accommodate the present elements in
|
||||||
|
the queue, otherwise nothing is done and false is returned.
|
||||||
|
*/
|
||||||
|
bool capacity(const size_t cap);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Move the rvalue parameter into the queue, guarded
|
||||||
|
for multiple concurrent producers.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
bool push(T&& val);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Push a copy of the parameter into the queue, guarded
|
||||||
|
for multiple concurrent producers.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
inline bool IRAM_ATTR push(const T& val) ALWAYS_INLINE_ATTR
|
||||||
|
{
|
||||||
|
T v(val);
|
||||||
|
return push(std::move(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Push copies of multiple elements from a buffer into the queue,
|
||||||
|
in order, beginning at buffer's head. This is safe for
|
||||||
|
multiple producers.
|
||||||
|
@return The number of elements actually copied into the queue, counted
|
||||||
|
from the buffer head.
|
||||||
|
*/
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
size_t push_n(const T* buffer, size_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::atomic<size_t> m_inPos_mp;
|
||||||
|
std::atomic<int> m_concurrent_mp;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
bool circular_queue_mp<T, ForEachArg>::capacity(const size_t cap)
|
||||||
|
{
|
||||||
|
if (cap + 1 == circular_queue<T, ForEachArg>::m_bufSize) return true;
|
||||||
|
else if (!circular_queue<T, ForEachArg>::capacity(cap)) return false;
|
||||||
|
m_inPos_mp.store(circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_relaxed),
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
m_concurrent_mp.store(0, std::memory_order_relaxed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
bool IRAM_ATTR circular_queue_mp<T, ForEachArg>::push(T&& val)
|
||||||
|
{
|
||||||
|
size_t inPos_mp;
|
||||||
|
size_t next;
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
class InterruptLock {
|
||||||
|
public:
|
||||||
|
InterruptLock() {
|
||||||
|
noInterrupts();
|
||||||
|
}
|
||||||
|
~InterruptLock() {
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
#else
|
||||||
|
++m_concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
inPos_mp = m_inPos_mp.load(std::memory_order_relaxed);
|
||||||
|
next = (inPos_mp + 1) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||||
|
if (next == circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed)) {
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_inPos_mp.store(next, std::memory_order_relaxed);
|
||||||
|
m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) + 1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load();
|
||||||
|
concurrent_mp = m_concurrent_mp.load();
|
||||||
|
if (1 == concurrent_mp)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_inPos_mp.compare_exchange_weak(inPos_mp, next));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
circular_queue<T, ForEachArg>::m_buffer[inPos_mp] = std::move(val);
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
if (1 == m_concurrent_mp.load(std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load(std::memory_order_relaxed);
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) - 1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load();
|
||||||
|
concurrent_mp = m_concurrent_mp.load();
|
||||||
|
if (1 == concurrent_mp)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
size_t circular_queue_mp<T, ForEachArg>::push_n(const T* buffer, size_t size)
|
||||||
|
{
|
||||||
|
const auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed);
|
||||||
|
size_t inPos_mp;
|
||||||
|
size_t next;
|
||||||
|
size_t blockSize;
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
#else
|
||||||
|
++m_concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
inPos_mp = m_inPos_mp.load(std::memory_order_relaxed);
|
||||||
|
blockSize = (outPos > inPos_mp) ? outPos - 1 - inPos_mp : (outPos == 0) ? circular_queue<T, ForEachArg>::m_bufSize - 1 - inPos_mp : circular_queue<T, ForEachArg>::m_bufSize - inPos_mp;
|
||||||
|
blockSize = min(size, blockSize);
|
||||||
|
if (!blockSize)
|
||||||
|
{
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
next = (inPos_mp + blockSize) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||||
|
m_inPos_mp.store(next, std::memory_order_relaxed);
|
||||||
|
m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) + 1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int concurrent_mp = m_concurrent_mp.load();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load();
|
||||||
|
concurrent_mp = m_concurrent_mp.load();
|
||||||
|
if (1 == concurrent_mp)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_inPos_mp.compare_exchange_weak(inPos_mp, next));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto dest = circular_queue<T, ForEachArg>::m_buffer.get() + inPos_mp;
|
||||||
|
std::copy_n(std::make_move_iterator(buffer), blockSize, dest);
|
||||||
|
size = min(size - blockSize, outPos > 1 ? static_cast<size_t>(outPos - next - 1) : 0);
|
||||||
|
next += size;
|
||||||
|
dest = circular_queue<T, ForEachArg>::m_buffer.get();
|
||||||
|
std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest);
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
if (1 == m_concurrent_mp.load(std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load(std::memory_order_relaxed);
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) - 1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load();
|
||||||
|
concurrent_mp = m_concurrent_mp.load();
|
||||||
|
if (1 == concurrent_mp)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return blockSize + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __circular_queue_mp_h
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
ghostl.h - Implementation of a bare-bones, mostly no-op, C++ STL shell
|
||||||
|
that allows building some Arduino ESP8266/ESP32
|
||||||
|
libraries on Aruduino AVR.
|
||||||
|
Copyright (c) 2019 Dirk O. Kaar. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __ghostl_h
|
||||||
|
#define __ghostl_h
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_SAMD)
|
||||||
|
#include <atomic>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using size_t = decltype(sizeof(char));
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
#if !defined(ARDUINO_ARCH_SAMD)
|
||||||
|
typedef enum memory_order {
|
||||||
|
memory_order_relaxed,
|
||||||
|
memory_order_acquire,
|
||||||
|
memory_order_release,
|
||||||
|
memory_order_seq_cst
|
||||||
|
} memory_order;
|
||||||
|
template< typename T > class atomic {
|
||||||
|
private:
|
||||||
|
T value;
|
||||||
|
public:
|
||||||
|
atomic() {}
|
||||||
|
atomic(T desired) { value = desired; }
|
||||||
|
void store(T desired, std::memory_order = std::memory_order_seq_cst) volatile noexcept { value = desired; }
|
||||||
|
T load(std::memory_order = std::memory_order_seq_cst) const volatile noexcept { return value; }
|
||||||
|
};
|
||||||
|
inline void atomic_thread_fence(std::memory_order order) noexcept {}
|
||||||
|
template< typename T > T&& move(T& t) noexcept { return static_cast<T&&>(t); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template< typename T, size_t long N > struct array
|
||||||
|
{
|
||||||
|
T _M_elems[N];
|
||||||
|
decltype(sizeof(0)) size() const { return N; }
|
||||||
|
T& operator[](decltype(sizeof(0)) i) { return _M_elems[i]; }
|
||||||
|
const T& operator[](decltype(sizeof(0)) i) const { return _M_elems[i]; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T > class unique_ptr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using pointer = T*;
|
||||||
|
unique_ptr() noexcept : ptr(nullptr) {}
|
||||||
|
unique_ptr(pointer p) : ptr(p) {}
|
||||||
|
pointer operator->() const noexcept { return ptr; }
|
||||||
|
T& operator[](decltype(sizeof(0)) i) const { return ptr[i]; }
|
||||||
|
void reset(pointer p = pointer()) noexcept
|
||||||
|
{
|
||||||
|
delete ptr;
|
||||||
|
ptr = p;
|
||||||
|
}
|
||||||
|
T& operator*() const { return *ptr; }
|
||||||
|
private:
|
||||||
|
pointer ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T > using function = T*;
|
||||||
|
using nullptr_t = decltype(nullptr);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct identity {
|
||||||
|
typedef T type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T&& forward(typename identity<T>::type& t) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<typename identity<T>::type&&>(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __ghostl_h
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,489 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
|
||||||
|
<style>
|
||||||
|
*{
|
||||||
|
margin:0px;
|
||||||
|
padding:0px;
|
||||||
|
}
|
||||||
|
#myMap{
|
||||||
|
position:absolute;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--
|
||||||
|
<style>
|
||||||
|
.bm_bottomLeftOverlay{
|
||||||
|
display:none !important
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script type='text/javascript'>
|
||||||
|
var map;
|
||||||
|
var infobox;
|
||||||
|
var key;
|
||||||
|
var zoom = 15;
|
||||||
|
var center =[39.92, 116.40];
|
||||||
|
var disablePanning = false;
|
||||||
|
var disableZooming = false;
|
||||||
|
var maxZoom = 17;
|
||||||
|
var minZoom = 5;
|
||||||
|
var showLocateMeButton = true;
|
||||||
|
var showMapTypeSelector= true;
|
||||||
|
var showScalebar= true;
|
||||||
|
var showZoomButtons =true;
|
||||||
|
var mapType = "road";
|
||||||
|
|
||||||
|
function GetMap() {
|
||||||
|
map = new Microsoft.Maps.Map('#myMap', {
|
||||||
|
//credentials: 'AmxEDXs-Yhwj3Uv5WvVB4Q6YvISdMjqrC-pPOw0rNKMu_5rrksVmAJkpcv5HJwJS',
|
||||||
|
zoom: zoom,
|
||||||
|
showDashboard: true,
|
||||||
|
showLocateMeButton:showLocateMeButton,
|
||||||
|
showMapTypeSelector:showMapTypeSelector,
|
||||||
|
showTermsLink: false,
|
||||||
|
//enableHighDpi:true,
|
||||||
|
enableClickableLogo: false,
|
||||||
|
//mapTypeId: Microsoft.Maps.MapTypeId.aerial,
|
||||||
|
//mapTypeId: Microsoft.Maps.MapTypeId.road,
|
||||||
|
maxZoom: maxZoom,
|
||||||
|
minZoom: minZoom,
|
||||||
|
disablePanning:disablePanning,
|
||||||
|
disableZooming:disableZooming,
|
||||||
|
//customMapStyle: myStyle,
|
||||||
|
liteMode: true,
|
||||||
|
showScalebar: showScalebar,
|
||||||
|
showZoomButtons: showZoomButtons,
|
||||||
|
center:realLocation(center),
|
||||||
|
});
|
||||||
|
setMapType(mapType);
|
||||||
|
|
||||||
|
infobox = new Microsoft.Maps.Infobox(map.getCenter(), {
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
infobox.setMap(map);
|
||||||
|
|
||||||
|
Microsoft.Maps.Events.addHandler(map, 'click', function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.mapClicked(JSON.stringify([e.location.latitude, e.location.longitude]));
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify([e.location.latitude, e.location.longitude]));
|
||||||
|
});
|
||||||
|
|
||||||
|
Microsoft.Maps.Events.addHandler(map, 'viewchangeend', function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.mapViewChangedEnd(JSON.stringify([map.getCenter()
|
||||||
|
.latitude, map.getCenter()
|
||||||
|
.longitude
|
||||||
|
]), map.getZoom(), JSON.stringify(map.getBounds()
|
||||||
|
.bounds));
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify([map.getCenter().latitude,
|
||||||
|
map.getCenter().longitude]),
|
||||||
|
map.getZoom(),
|
||||||
|
JSON.stringify(map.getBounds().bounds));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMapType(type) {
|
||||||
|
var mapTypeId;
|
||||||
|
switch (type) {
|
||||||
|
case 'aerial':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.aerial;
|
||||||
|
break;
|
||||||
|
case 'canvasDark':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.canvasDark;
|
||||||
|
break;
|
||||||
|
case 'canvasLight':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.canvasLight;
|
||||||
|
break;
|
||||||
|
case 'birdseye':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.birdseye;
|
||||||
|
break;
|
||||||
|
case 'grayscale':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.grayscale;
|
||||||
|
break;
|
||||||
|
case 'streetside':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.streetside;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.road;
|
||||||
|
}
|
||||||
|
map.setView({
|
||||||
|
mapTypeId: mapTypeId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setZoom(z) {
|
||||||
|
map.setView({
|
||||||
|
zoom: z
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getZoom() {
|
||||||
|
return map.getZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCenter(location) {
|
||||||
|
map.setView({
|
||||||
|
center: realLocation(location)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCenter() {
|
||||||
|
return [map.getCenter()
|
||||||
|
.latitude, map.getCenter()
|
||||||
|
.longitude
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//视图的右上角和左下角纬度+经度
|
||||||
|
function getBounds() {
|
||||||
|
return map.getBounds()
|
||||||
|
.bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPushpin(location, id, options) {
|
||||||
|
//options = {title: title,subTitle: subtitle,text: text, color: 'blue',icon: 'poi-custom.png',anchor: new Microsoft.Maps.Point(12, 39), draggable: draggable, };
|
||||||
|
var pin = new Microsoft.Maps.Pushpin(realLocation(location), options);
|
||||||
|
pin.id = id;
|
||||||
|
map.entities.push(pin);
|
||||||
|
Microsoft.Maps.Events.addHandler(pin, 'click', function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.pushpinClicked(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
});
|
||||||
|
Microsoft.Maps.Events.addHandler(pin, 'dragend', function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.pushpinDragEnd(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function realLocation(location) {
|
||||||
|
return new Microsoft.Maps.Location(location[0], location[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPolyline(locations, id, options) {
|
||||||
|
//options = { strokeColor: 'red',strokeThickness: 3, strokeDashArray: [10,10] };
|
||||||
|
var bingLocations = [];
|
||||||
|
for (var i = 0; i < locations.length; i++) {
|
||||||
|
bingLocations.push(realLocation(locations[i]));
|
||||||
|
}
|
||||||
|
var polyline = new Microsoft.Maps.Polyline(bingLocations, options);
|
||||||
|
polyline.id = id;
|
||||||
|
map.entities.push(polyline);
|
||||||
|
Microsoft.Maps.Events.addHandler(polyline, 'click', function(e) {
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.polylineClicked(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPolylineLocation(locations, id) {
|
||||||
|
var bingLocations = [];
|
||||||
|
for (var i = 0; i < locations.length; i++) {
|
||||||
|
bingLocations.push(realLocation(locations[i]));
|
||||||
|
}
|
||||||
|
for (var i = map.entities.getLength() - 1; i >= 0; i--) {
|
||||||
|
var line = map.entities.get(i);
|
||||||
|
if (line.id == id) {
|
||||||
|
line.setLocations(bingLocations);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPolygon(locations, id, options) {
|
||||||
|
//options = { fillColor: '#ff000022', strokeColor: '#00ff00', strokeThickness: 4, strokeDashArray: [10,5] };
|
||||||
|
|
||||||
|
var bingLocations = [];
|
||||||
|
for (var i = 0; i < locations.length; i++) {
|
||||||
|
bingLocations.push(realLocation(locations[i]));
|
||||||
|
}
|
||||||
|
var polygon = new Microsoft.Maps.Polygon(bingLocations, options);
|
||||||
|
polygon.id = id;
|
||||||
|
map.entities.push(polygon);
|
||||||
|
Microsoft.Maps.Events.addHandler(polygon, 'click', function(e) {
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.polygonClicked(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPolygonRegular(center, distance, sides, id, options, angle) {
|
||||||
|
//options = { fillColor: '#ff000022', strokeColor: '#00ff00', strokeThickness: 4, strokeDashArray: [10,5] };
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.SpatialMath', function() {
|
||||||
|
var locations = Microsoft.Maps.SpatialMath.getRegularPolygon(realLocation(center), distance, sides, Microsoft.Maps.SpatialMath.DistanceUnits.Kilometers,angle);
|
||||||
|
var polygon = new Microsoft.Maps.Polygon(locations, options);
|
||||||
|
polygon.id = id;
|
||||||
|
map.entities.push(polygon);
|
||||||
|
Microsoft.Maps.Events.addHandler(polygon, 'click', function(e) {
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.polygonClicked(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showInfobox(location, title, description, actions) {
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
location: realLocation(location),
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
//maxWidth: 500,
|
||||||
|
showCloseButton:true,
|
||||||
|
actions: null,
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (actions instanceof Array && actions.length > 0) {
|
||||||
|
var realActions = [];
|
||||||
|
for (var i = 0; i < actions.length; i++) {
|
||||||
|
var option = {};
|
||||||
|
option.label = actions[i];
|
||||||
|
option.eventHandler = function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.actionClicked(e.srcElement.innerText);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(e.srcElement.innerText);
|
||||||
|
//hideInfobox();
|
||||||
|
};
|
||||||
|
realActions.push(option);
|
||||||
|
|
||||||
|
}
|
||||||
|
options.actions = realActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
infobox.setOptions(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideInfobox() {
|
||||||
|
infobox.setOptions({
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var directionsManager;
|
||||||
|
|
||||||
|
function calculateDirections(locations,mode) {
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.Directions', function() {
|
||||||
|
directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);
|
||||||
|
// Set Route Mode to driving
|
||||||
|
var routeMode;
|
||||||
|
if (mode == 'walking'){
|
||||||
|
routeMode = Microsoft.Maps.Directions.RouteMode.walking;
|
||||||
|
}else if (mode == 'transit'){
|
||||||
|
routeMode = Microsoft.Maps.Directions.RouteMode.transit;
|
||||||
|
}else if (mode == 'truck'){
|
||||||
|
routeMode = Microsoft.Maps.Directions.RouteMode.truck;
|
||||||
|
}else {
|
||||||
|
routeMode = Microsoft.Maps.Directions.RouteMode.driving;
|
||||||
|
}
|
||||||
|
|
||||||
|
directionsManager.setRequestOptions({
|
||||||
|
distanceUnit: Microsoft.Maps.Directions.DistanceUnit.km,
|
||||||
|
routeMode: routeMode,
|
||||||
|
//routeDraggable: false,
|
||||||
|
});
|
||||||
|
for (var i = 0; i < locations.length; i++) {
|
||||||
|
var waypoint1 = new Microsoft.Maps.Directions.Waypoint({
|
||||||
|
address: "",
|
||||||
|
location: realLocation(locations[i])
|
||||||
|
});
|
||||||
|
directionsManager.addWaypoint(waypoint1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the element in which the itinerary will be rendered
|
||||||
|
directionsManager.setRenderOptions({ itineraryContainer: document.getElementById('printoutPanel') });
|
||||||
|
|
||||||
|
directionsManager.setRenderOptions({
|
||||||
|
drivingPolylineOptions: {
|
||||||
|
strokeColor: 'lightblue',
|
||||||
|
strokeThickness: 1
|
||||||
|
},
|
||||||
|
waypointPushpinOptions: {
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Microsoft.Maps.Events.addHandler(directionsManager, 'directionsError', directionsError);
|
||||||
|
Microsoft.Maps.Events.addHandler(directionsManager, 'directionsUpdated', directionsUpdated);
|
||||||
|
|
||||||
|
directionsManager.calculateDirections();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function directionsUpdated(e) {
|
||||||
|
console.log(e);
|
||||||
|
//Get the current route index.
|
||||||
|
var routeIdx = directionsManager.getRequestOptions()
|
||||||
|
.routeIndex;
|
||||||
|
var total = e.routeSummary.length;
|
||||||
|
|
||||||
|
//var options = [];
|
||||||
|
//for (var i = 0; i < e.routeSummary.length; i++) {
|
||||||
|
//var option = {};
|
||||||
|
//option.distance = e.route[routeIdx].distance;
|
||||||
|
//option.time = e.routeSummary[routeIdx].time;
|
||||||
|
//option.timeWithTraffic = e.routeSummary[routeIdx].timeWithTraffic;
|
||||||
|
//options.push(option);
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.directionsUpdated(total, routeIdx+1, JSON.stringify(e.route[routeIdx]));
|
||||||
|
}
|
||||||
|
console.log(total, routeIdx+1, e.route[routeIdx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directionsError(e) {
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.directionsError(JSON.stringify(e));
|
||||||
|
}
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearRoute() {
|
||||||
|
directionsManager.clearAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(id) {
|
||||||
|
for (var i = map.entities.getLength() - 1; i >= 0; i--) {
|
||||||
|
var pin = map.entities.get(i);
|
||||||
|
if (pin.id == id) {
|
||||||
|
map.entities.removeAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAll() {
|
||||||
|
map.entities.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAddress(location) {
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.Search', function() {
|
||||||
|
var searchManager = new Microsoft.Maps.Search.SearchManager(map);
|
||||||
|
var reverseGeocodeRequestOptions = {
|
||||||
|
location: realLocation(location),
|
||||||
|
callback: function(answer, userData) {
|
||||||
|
var ad = answer.address.formattedAddress;
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.gotAddress(ad);
|
||||||
|
}
|
||||||
|
console.log(ad);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
searchManager.reverseGeocode(reverseGeocodeRequestOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocation(address) {
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.Search', function() {
|
||||||
|
var searchManager = new Microsoft.Maps.Search.SearchManager(map);
|
||||||
|
var requestOptions = {
|
||||||
|
//bounds: map.getBounds(),
|
||||||
|
where: address,
|
||||||
|
callback: function(answer, userData) {
|
||||||
|
|
||||||
|
var result = [];
|
||||||
|
for (var i = 0; i < answer.results.length; i++) {
|
||||||
|
result.push([answer.results[i].name, [answer.results[i].location.latitude, answer.results[i].location.longitude]]);
|
||||||
|
}
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.gotLocation(JSON.stringify(result));
|
||||||
|
}
|
||||||
|
console.log(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
searchManager.geocode(requestOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDistance(location1, location2) {
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.SpatialMath', function() {
|
||||||
|
var dis = Microsoft.Maps.SpatialMath.getDistanceTo(realLocation(location1), realLocation(location2), Microsoft.Maps.SpatialMath.DistanceUnits.Kilometers);
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.gotDistance(dis);
|
||||||
|
}
|
||||||
|
console.log(dis);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadJS() {
|
||||||
|
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.src = 'http://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=' + key;
|
||||||
|
script.async = true;
|
||||||
|
script.defer = true;
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryVariable(variable) {
|
||||||
|
var query = window.location.search.substring(1);
|
||||||
|
var vars = query.split("&");
|
||||||
|
for (var i = 0; i < vars.length; i++) {
|
||||||
|
var pair = vars[i].split("=");
|
||||||
|
if (pair[0] == variable) {
|
||||||
|
return pair[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
var watchId;
|
||||||
|
function startTracking() {
|
||||||
|
|
||||||
|
watchId = navigator.geolocation.watchPosition(function(position) {
|
||||||
|
var loc = [ position.coords.latitude, position.coords.longitude];
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.locationUpdated(JSON.stringify(loc));
|
||||||
|
}
|
||||||
|
console.log("locationUpdated", loc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function stopTracking() {
|
||||||
|
// Cancel the geolocation updates.
|
||||||
|
navigator.geolocation.clearWatch(watchId);
|
||||||
|
}
|
||||||
|
//loadJS(getQueryVariable('key'));
|
||||||
|
//loadJS('AmxEDXs-Yhwj3Uv5WvVB4Q6YvISdMjqrC-pPOw0rNKMu_5rrksVmAJkpcv5HJwJS');
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='printoutPanel' style='display:none'></div>
|
||||||
|
<div id='myMap'></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
After Width: | Height: | Size: 894 B |
After Width: | Height: | Size: 889 B |
After Width: | Height: | Size: 839 B |
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
[{"permissions":["android.permission.ACCESS_FINE_LOCATION"],"conditionalPermissions":{},"conditionalBroadcastReceivers":{},"libraries":[],"native":[],"broadcastReceiver":[],"assets":["pin3.png","pin2.png","bingmap.html","pin1.png"],"activities":[],"broadcastReceivers":[],"type":"cn.kevinkun.BingMap.BingMap","androidMinSdk":["7"]}]
|
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
[{"categoryString":"EXTENSION","dateBuilt":"2023-09-21","nonVisible":"true","iconName":"https://res.cloudinary.com/andromedaviewflyvipul/image/upload/c_scale,h_20,w_20/v1571472765/ktvu4bapylsvnykoyhdm.png","methods":[{"deprecated":"false","name":"GetVoicesList","description":"Gets a list of voices in dictionary form","params":[]},{"deprecated":"false","name":"TextToSpeech","description":"Converts text to speech and saves audio file to output path","params":[{"name":"text","type":"text"},{"name":"outputhFile","type":"text"},{"name":"voiceName","type":"text"},{"name":"voiceGender","type":"text"},{"name":"voiceLang","type":"text"}]}],"blockProperties":[{"rw":"write-only","deprecated":"false","name":"ApiKey","description":"Set API key","type":"text"},{"rw":"write-only","deprecated":"false","name":"ResourceRegion","description":"Set resource region","type":"text"}],"helpUrl":"https://sunnythedeveloper.in/microsofttts-convert-text-to-audio-using-microsoft-texttospeech-api/","licenseName":"https://creativecommons.org/licenses/by-sa/4.0/","type":"com.sunny.tts.MicrosoftTTS","androidMinSdk":7,"versionName":"1","version":"4","external":"true","showOnPalette":"true","name":"MicrosoftTTS","helpString":"<p>Extension to convert Text to Speech using Microsoft Text-To-Speech API <br> Developed by Sunny Gupta<\/p>\n","events":[{"deprecated":"false","name":"GotVoicesList","description":"Event raised after gettting voices list. Each list element is a dictionary containing voice information.","params":[{"name":"reponseList","type":"list"}]},{"deprecated":"false","name":"GotError","description":"Event raised when error occurs","params":[{"name":"methodName","type":"text"},{"name":"errorMsg","type":"text"}]},{"deprecated":"false","name":"GotSpeechFile","description":"Event raised when text was successfully converted to audio and file was saved","params":[{"name":"outputFile","type":"text"}]}],"properties":[]}]
|
@ -0,0 +1,2 @@
|
|||||||
|
type=external
|
||||||
|
rush-version=1.2.4
|
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
[{"assets":[],"activities":[],"permissions":["android.permission.INTERNET"],"type":"com.sunny.tts.MicrosoftTTS","androidMinSdk":[7]}]
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,10 @@
|
|||||||
|
<xml xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<block type="component_event" id="Nu],`$R_Zo+h]%t0u=E{" x="undefined" y="undefined">
|
||||||
|
<mutation component_type="Button" instance_name="按钮1" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮1</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_closeScreen" id="_5tFHD60tzn_~.}x1DrY"></block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<yacodeblocks ya-version="172" language-version="22"></yacodeblocks>
|
||||||
|
</xml>
|
@ -0,0 +1,41 @@
|
|||||||
|
<xml xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<block type="component_set_get" id="D7C,mwN}q=UqZ*a5_PK=" x="-411" y="32">
|
||||||
|
<mutation component_type="Button" set_or_get="get" property_name="Enabled" is_generic="false" instance_name="按钮_避障"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_避障</field>
|
||||||
|
<field name="PROP">Enabled</field>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="1E~cny;xyoBW`6/m=u_i" x="-415" y="87">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_实时定位" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_实时定位</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_openAnotherScreen" id="$]%v|uY7Rfv~rk!)@z(3">
|
||||||
|
<value name="SCREEN">
|
||||||
|
<block type="text" id=".gh[SARAdd)M$4]M~[y%">
|
||||||
|
<field name="TEXT">dingwei</field>
|
||||||
|
</block>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="20%u[R^-3+ouiy8=/CZF" x="-420" y="187">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_拨打电话" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_拨打电话</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_openAnotherScreen" id="JMzPot8U;!E~]~U4SNd+">
|
||||||
|
<value name="SCREEN">
|
||||||
|
<block type="text" id="rD6.uP~:nv;itN019[.z">
|
||||||
|
<field name="TEXT">calling</field>
|
||||||
|
</block>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="7H89A@N/?oES:zQX.*:v" x="-411" y="296">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_关闭" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_关闭</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_closeScreen" id="r1-697xyD#}ga{a..=0l"></block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<yacodeblocks ya-version="172" language-version="22"></yacodeblocks>
|
||||||
|
</xml>
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net","ai2.appinventor.mit.edu"],"YaVersion":"224","Source":"Form","Properties":{"$Name":"Screen1","$Type":"Form","$Version":"30","ActionBar":"True","AlignHorizontal":"3","AppName":"ruangong","ShowListsAsJson":"False","Theme":"AppTheme.Light.DarkActionBar","Title":"\u907f\u969c\u76d1\u6d4b","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c404","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","Height":"378","Width":"-2","Uuid":"-1446214067","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"7","Text":"\u907f\u969c","Uuid":"-644814633"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"7","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"-18939228"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"7","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"2062084651"},{"$Name":"\u5217\u8868\u9009\u62e9\u6846_\u84dd\u7259","$Type":"ListPicker","$Version":"9","Text":"\u8fde\u63a5\u8bbe\u5907","Uuid":"-1183153014"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c403","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"-2055779801","$Components":[{"$Name":"\u6807\u7b7e_\u8ddd\u79bb","$Type":"Label","$Version":"5","Text":"\u8ddd\u79bb\uff1a","Uuid":"1009622951"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ddd\u79bb","$Type":"TextBox","$Version":"6","ReadOnly":"True","Uuid":"-2128440146"},{"$Name":"\u64ad\u62a5","$Type":"CheckBox","$Version":"2","Text":"\u81ea\u52a8\u64ad\u62a5","Uuid":"405481945"}]},{"$Name":"\u5782\u76f4\u5e03\u5c401","$Type":"VerticalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"3","Height":"-1040","Width":"-2","Uuid":"2081953051","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"177806287","$Components":[{"$Name":"\u6807\u7b7e_\u8ba1\u65f6","$Type":"Label","$Version":"5","Text":"\u8ba1\u65f6\uff1a","Uuid":"-911991431"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ba1\u65f6","$Type":"TextBox","$Version":"6","NumbersOnly":"True","Text":"1","Uuid":"-152203493"},{"$Name":"\u6309\u94ae2","$Type":"Button","$Version":"7","Text":"\u786e\u5b9a","Uuid":"-444567807"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c405","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"2","AlignVertical":"3","Width":"-2","Uuid":"-1161251788","$Components":[{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"7","Text":"\u5173\u95ed","Uuid":"1124974886"}]}]},{"$Name":"\u84dd\u7259\u5ba2\u6237\u7aef1","$Type":"BluetoothClient","$Version":"8","Uuid":"-1413619977"},{"$Name":"\u8ba1\u65f6\u56681","$Type":"Clock","$Version":"4","Uuid":"530919694"},{"$Name":"\u6587\u672c\u8bed\u97f3\u8f6c\u6362\u56681","$Type":"TextToSpeech","$Version":"6","Uuid":"-1761536897"},{"$Name":"\u77ed\u4fe1\u6536\u53d1\u56681","$Type":"Texting","$Version":"5","Uuid":"-1268134884"}]}}
|
||||||
|
|#
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net"],"YaVersion":"172","Source":"Form","Properties":{"$Name":"Screen1","$Type":"Form","$Version":"23","AppName":"ruangong","Title":"Screen1","Uuid":"0","$Components":[{"$Name":"\u5217\u8868\u663e\u793a\u68461","$Type":"ListView","$Version":"5","Uuid":"1220671722"},{"$Name":"\u6309\u94ae1","$Type":"Button","$Version":"6","Text":"\u5173\u95ed","Uuid":"-644814633"}]}}
|
||||||
|
|#
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net"],"YaVersion":"172","Source":"Form","Properties":{"$Name":"Screen1","$Type":"Form","$Version":"23","AlignHorizontal":"3","AlignVertical":"2","AppName":"ruangong","Title":"Screen1","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c401","$Type":"HorizontalArrangement","$Version":"3","AlignHorizontal":"3","AlignVertical":"2","Uuid":"177113749","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"6","Text":"\u907f\u969c","Uuid":"-644814633"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"6","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"-18939228"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"6","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"2062084651"}]},{"$Name":"\u5782\u76f4\u5e03\u5c401","$Type":"VerticalArrangement","$Version":"3","AlignHorizontal":"2","AlignVertical":"2","Uuid":"-1007398740","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"3","Uuid":"177806287","$Components":[{"$Name":"\u6807\u7b7e_\u8ba1\u65f6","$Type":"Label","$Version":"4","Text":"\u8ba1\u65f6\uff1a","Uuid":"-911991431"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ba1\u65f6","$Type":"TextBox","$Version":"5","Uuid":"-152203493"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c403","$Type":"HorizontalArrangement","$Version":"3","Uuid":"-2055779801","$Components":[{"$Name":"\u6807\u7b7e_\u8ddd\u79bb","$Type":"Label","$Version":"4","Text":"\u8ddd\u79bb\uff1a","Uuid":"1009622951"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ddd\u79bb","$Type":"TextBox","$Version":"5","Uuid":"-2128440146"}]},{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"6","Text":"\u5173\u95ed","Uuid":"1124974886"}]}]}}
|
||||||
|
|#
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net","ai2.appinventor.mit.edu"],"YaVersion":"224","Source":"Form","Properties":{"$Name":"calling","$Type":"Form","$Version":"30","ActionBar":"True","AlignHorizontal":"3","AppName":"ruangong","ShowListsAsJson":"False","Theme":"AppTheme.Light.DarkActionBar","Title":"\u62e8\u6253\u7535\u8bdd","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c401","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","Height":"-1040","Width":"-2","Uuid":"497758912","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"7","Text":"\u907f\u969c","Uuid":"-1955423566"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"7","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"479692292"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"7","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"-1181431767"}]},{"$Name":"\u5782\u76f4\u5e03\u5c401","$Type":"VerticalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"-1614689866","$Components":[{"$Name":"\u5217\u8868\u9009\u62e9\u6846_\u7535\u8bdd\u5217\u8868","$Type":"ListPicker","$Version":"9","ElementsFromString":"123,19968538899","Text":"\u9009\u62e9\u7d27\u6025\u8054\u7cfb\u4eba","Uuid":"-1795360827"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"2","AlignVertical":"3","Height":"-1040","Width":"-2","Uuid":"923044023","$Components":[{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"7","Text":"\u5173\u95ed","Uuid":"2024754233"}]},{"$Name":"\u7535\u8bdd\u62e8\u53f7\u5668_\u62e8\u6253\u7535\u8bdd","$Type":"PhoneCall","$Version":"3","Uuid":"-1369827363"}]}}
|
||||||
|
|#
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,41 @@
|
|||||||
|
<xml xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<block type="component_event" id="l0r/-ENMfb_}qL~ed0!E" x="-472" y="-89">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_避障" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_避障</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_openAnotherScreen" id="=fZ9R$eQnx9W=T/0WSns">
|
||||||
|
<value name="SCREEN">
|
||||||
|
<block type="text" id="9b{~^NYOdkLaS]=}B?0n">
|
||||||
|
<field name="TEXT">Screen1</field>
|
||||||
|
</block>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<block type="component_set_get" id="]6?;)6zP7hTG8D.)LDl$" x="-468" y="8">
|
||||||
|
<mutation component_type="Button" set_or_get="get" property_name="Enabled" is_generic="false" instance_name="按钮_实时定位"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_实时定位</field>
|
||||||
|
<field name="PROP">Enabled</field>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="20%u[R^-3+ouiy8=/CZF" x="-475" y="56">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_拨打电话" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_拨打电话</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_openAnotherScreen" id="JMzPot8U;!E~]~U4SNd+">
|
||||||
|
<value name="SCREEN">
|
||||||
|
<block type="text" id="rD6.uP~:nv;itN019[.z">
|
||||||
|
<field name="TEXT">calling</field>
|
||||||
|
</block>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="7H89A@N/?oES:zQX.*:v" x="-472" y="152">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_关闭" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_关闭</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_closeScreen" id="r1-697xyD#}ga{a..=0l"></block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<yacodeblocks ya-version="172" language-version="22"></yacodeblocks>
|
||||||
|
</xml>
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net","ai2.appinventor.mit.edu"],"YaVersion":"224","Source":"Form","Properties":{"$Name":"dingwei","$Type":"Form","$Version":"30","ActionBar":"True","AlignHorizontal":"3","AppName":"ruangong","ShowListsAsJson":"False","Theme":"AppTheme.Light.DarkActionBar","Title":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c401","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"-655869917","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"7","Text":"\u907f\u969c","Uuid":"1440681744"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"7","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"652290387"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"7","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"1956743508"},{"$Name":"\u5217\u8868\u9009\u62e9\u6846_\u84dd\u7259","$Type":"ListPicker","$Version":"9","Text":"\u8fde\u63a5\u8bbe\u5907","Uuid":"1464195969"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c403","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Height":"-1060","Width":"-2","Uuid":"1743176679","$Components":[{"$Name":"Web\u6d4f\u89c8\u68461","$Type":"WebViewer","$Version":"10","Uuid":"177751501"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"785425026","$Components":[{"$Name":"\u5237\u65b0","$Type":"Button","$Version":"7","Text":"\u5237\u65b0","Uuid":"-990679099"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c404","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"2","AlignVertical":"3","Height":"-1020","Width":"-2","Uuid":"1670022820","$Components":[{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"7","Text":"\u5173\u95ed","Uuid":"-1949803666"}]},{"$Name":"\u84dd\u7259\u5ba2\u6237\u7aef1","$Type":"BluetoothClient","$Version":"8","Uuid":"985772490"},{"$Name":"\u8ba1\u65f6\u56681","$Type":"Clock","$Version":"4","TimerInterval":"40000","Uuid":"-415083639"},{"$Name":"\u4f4d\u7f6e\u4f20\u611f\u56681","$Type":"LocationSensor","$Version":"3","Uuid":"-939539378"},{"$Name":"Web\u5ba2\u6237\u7aef1","$Type":"Web","$Version":"8","Uuid":"-1721494346"},{"$Name":"\u77ed\u4fe1\u6536\u53d1\u56681","$Type":"Texting","$Version":"5","Uuid":"-459111995"}]}}
|
||||||
|
|#
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net"],"YaVersion":"172","Source":"Form","Properties":{"$Name":"dingwei","$Type":"Form","$Version":"23","AlignHorizontal":"3","AlignVertical":"2","AppName":"ruangong","Title":"dingwei","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c401","$Type":"HorizontalArrangement","$Version":"3","AlignHorizontal":"3","Uuid":"-655869917","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"6","Text":"\u907f\u969c","Uuid":"1440681744"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"6","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"652290387"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"6","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"1956743508"}]},{"$Name":"\u5782\u76f4\u5e03\u5c401","$Type":"VerticalArrangement","$Version":"3","AlignHorizontal":"2","Uuid":"525050555","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"3","Uuid":"785425026","$Components":[{"$Name":"\u6807\u7b7e_\u8ba1\u65f6\u63d0\u793a","$Type":"Label","$Version":"4","Text":"\u8ba1\u65f6\uff1a","Uuid":"-1133475828"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ba1\u65f6","$Type":"TextBox","$Version":"5","Uuid":"2119337786"}]},{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"6","Text":"\u5173\u95ed","Uuid":"139342436"}]}]}}
|
||||||
|
|#
|
@ -0,0 +1,19 @@
|
|||||||
|
#
|
||||||
|
#Tue Nov 14 13:34:00 UTC 2023
|
||||||
|
source=../src
|
||||||
|
name=ruangong
|
||||||
|
defaultfilescope=App
|
||||||
|
main=appinventor.ai_dustinzrm.ruangong.Screen1
|
||||||
|
color.accent=&HFFFF4081
|
||||||
|
sizing=Responsive
|
||||||
|
assets=../assets
|
||||||
|
theme=AppTheme.Light.DarkActionBar
|
||||||
|
showlistsasjson=False
|
||||||
|
useslocation=False
|
||||||
|
aname=ruangong
|
||||||
|
actionbar=True
|
||||||
|
color.primary=&HFF3F51B5
|
||||||
|
build=../build
|
||||||
|
versionname=1.0
|
||||||
|
versioncode=1
|
||||||
|
color.primary.dark=&HFF303F9F
|
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
Arduino.h - Main include file for the Arduino SDK
|
||||||
|
Copyright (c) 2005-2013 Arduino Team. All right reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef Arduino_h
|
||||||
|
#define Arduino_h
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "esp_arduino_version.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "esp8266-compat.h"
|
||||||
|
#include "soc/gpio_reg.h"
|
||||||
|
|
||||||
|
#include "stdlib_noniso.h"
|
||||||
|
#include "binary.h"
|
||||||
|
|
||||||
|
#define PI 3.1415926535897932384626433832795
|
||||||
|
#define HALF_PI 1.5707963267948966192313216916398
|
||||||
|
#define TWO_PI 6.283185307179586476925286766559
|
||||||
|
#define DEG_TO_RAD 0.017453292519943295769236907684886
|
||||||
|
#define RAD_TO_DEG 57.295779513082320876798154814105
|
||||||
|
#define EULER 2.718281828459045235360287471352
|
||||||
|
|
||||||
|
#define SERIAL 0x0
|
||||||
|
#define DISPLAY 0x1
|
||||||
|
|
||||||
|
#define LSBFIRST 0
|
||||||
|
#define MSBFIRST 1
|
||||||
|
|
||||||
|
//Interrupt Modes
|
||||||
|
#define RISING 0x01
|
||||||
|
#define FALLING 0x02
|
||||||
|
#define CHANGE 0x03
|
||||||
|
#define ONLOW 0x04
|
||||||
|
#define ONHIGH 0x05
|
||||||
|
#define ONLOW_WE 0x0C
|
||||||
|
#define ONHIGH_WE 0x0D
|
||||||
|
|
||||||
|
#define DEFAULT 1
|
||||||
|
#define EXTERNAL 0
|
||||||
|
|
||||||
|
#ifndef __STRINGIFY
|
||||||
|
#define __STRINGIFY(a) #a
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// can't define max() / min() because of conflicts with C++
|
||||||
|
#define _min(a,b) ((a)<(b)?(a):(b))
|
||||||
|
#define _max(a,b) ((a)>(b)?(a):(b))
|
||||||
|
#define _abs(x) ((x)>0?(x):-(x)) // abs() comes from STL
|
||||||
|
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
|
||||||
|
#define _round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5)) // round() comes from STL
|
||||||
|
#define radians(deg) ((deg)*DEG_TO_RAD)
|
||||||
|
#define degrees(rad) ((rad)*RAD_TO_DEG)
|
||||||
|
#define sq(x) ((x)*(x))
|
||||||
|
|
||||||
|
// ESP32xx runs FreeRTOS... disabling interrupts can lead to issues, such as Watchdog Timeout
|
||||||
|
#define sei() portENABLE_INTERRUPTS()
|
||||||
|
#define cli() portDISABLE_INTERRUPTS()
|
||||||
|
#define interrupts() sei()
|
||||||
|
#define noInterrupts() cli()
|
||||||
|
|
||||||
|
#define clockCyclesPerMicrosecond() ( (long int)getCpuFrequencyMhz() )
|
||||||
|
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
|
||||||
|
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
|
||||||
|
|
||||||
|
#define lowByte(w) ((uint8_t) ((w) & 0xff))
|
||||||
|
#define highByte(w) ((uint8_t) ((w) >> 8))
|
||||||
|
|
||||||
|
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
|
||||||
|
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
|
||||||
|
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
|
||||||
|
#define bitToggle(value, bit) ((value) ^= (1UL << (bit)))
|
||||||
|
#define bitWrite(value, bit, bitvalue) ((bitvalue) ? bitSet(value, bit) : bitClear(value, bit))
|
||||||
|
|
||||||
|
// avr-libc defines _NOP() since 1.6.2
|
||||||
|
#ifndef _NOP
|
||||||
|
#define _NOP() do { __asm__ volatile ("nop"); } while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define bit(b) (1UL << (b))
|
||||||
|
#define _BV(b) (1UL << (b))
|
||||||
|
|
||||||
|
#define digitalPinToTimer(pin) (0)
|
||||||
|
#define analogInPinToBit(P) (P)
|
||||||
|
#if SOC_GPIO_PIN_COUNT <= 32
|
||||||
|
#define digitalPinToPort(pin) (0)
|
||||||
|
#define digitalPinToBitMask(pin) (1UL << digitalPinToGPIONumber(pin))
|
||||||
|
#define portOutputRegister(port) ((volatile uint32_t*)GPIO_OUT_REG)
|
||||||
|
#define portInputRegister(port) ((volatile uint32_t*)GPIO_IN_REG)
|
||||||
|
#define portModeRegister(port) ((volatile uint32_t*)GPIO_ENABLE_REG)
|
||||||
|
#elif SOC_GPIO_PIN_COUNT <= 64
|
||||||
|
#define digitalPinToPort(pin) ((digitalPinToGPIONumber(pin)>31)?1:0)
|
||||||
|
#define digitalPinToBitMask(pin) (1UL << (digitalPinToGPIONumber(pin)&31))
|
||||||
|
#define portOutputRegister(port) ((volatile uint32_t*)((port)?GPIO_OUT1_REG:GPIO_OUT_REG))
|
||||||
|
#define portInputRegister(port) ((volatile uint32_t*)((port)?GPIO_IN1_REG:GPIO_IN_REG))
|
||||||
|
#define portModeRegister(port) ((volatile uint32_t*)((port)?GPIO_ENABLE1_REG:GPIO_ENABLE_REG))
|
||||||
|
#else
|
||||||
|
#error SOC_GPIO_PIN_COUNT > 64 not implemented
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NOT_A_PIN -1
|
||||||
|
#define NOT_A_PORT -1
|
||||||
|
#define NOT_AN_INTERRUPT -1
|
||||||
|
#define NOT_ON_TIMER 0
|
||||||
|
|
||||||
|
typedef bool boolean;
|
||||||
|
typedef uint8_t byte;
|
||||||
|
typedef unsigned int word;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
void setup(void);
|
||||||
|
void loop(void);
|
||||||
|
|
||||||
|
// The default is using Real Hardware random number generator
|
||||||
|
// But when randomSeed() is called, it turns to Psedo random
|
||||||
|
// generator, exactly as done in Arduino mainstream
|
||||||
|
long random(long);
|
||||||
|
long random(long, long);
|
||||||
|
// Calling randomSeed() will make random()
|
||||||
|
// using pseudo random like in Arduino
|
||||||
|
void randomSeed(unsigned long);
|
||||||
|
// Allow the Application to decide if the random generator
|
||||||
|
// will use Real Hardware random generation (true - default)
|
||||||
|
// or Pseudo random generation (false) as in Arduino MainStream
|
||||||
|
void useRealRandomGenerator(bool useRandomHW);
|
||||||
|
#endif
|
||||||
|
long map(long, long, long, long, long);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void init(void);
|
||||||
|
void initVariant(void);
|
||||||
|
void initArduino(void);
|
||||||
|
|
||||||
|
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);
|
||||||
|
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout);
|
||||||
|
|
||||||
|
uint8_t shiftIn(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder);
|
||||||
|
void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "WCharacter.h"
|
||||||
|
#include "WString.h"
|
||||||
|
#include "Stream.h"
|
||||||
|
#include "Printable.h"
|
||||||
|
#include "Print.h"
|
||||||
|
#include "IPAddress.h"
|
||||||
|
#include "Client.h"
|
||||||
|
#include "Server.h"
|
||||||
|
#include "Udp.h"
|
||||||
|
#include "HardwareSerial.h"
|
||||||
|
#include "Esp.h"
|
||||||
|
#include "esp32/spiram.h"
|
||||||
|
|
||||||
|
// Use float-compatible stl abs() and round(), we don't use Arduino macros to avoid issues with the C++ libraries
|
||||||
|
using std::abs;
|
||||||
|
using std::isinf;
|
||||||
|
using std::isnan;
|
||||||
|
using std::max;
|
||||||
|
using std::min;
|
||||||
|
using std::round;
|
||||||
|
|
||||||
|
uint16_t makeWord(uint16_t w);
|
||||||
|
uint16_t makeWord(uint8_t h, uint8_t l);
|
||||||
|
|
||||||
|
#define word(...) makeWord(__VA_ARGS__)
|
||||||
|
|
||||||
|
size_t getArduinoLoopTaskStackSize(void);
|
||||||
|
#define SET_LOOP_TASK_STACK_SIZE(sz) size_t getArduinoLoopTaskStackSize() { return sz;}
|
||||||
|
|
||||||
|
// allows user to bypass esp_spiram_test()
|
||||||
|
#define BYPASS_SPIRAM_TEST(bypass) bool testSPIRAM(void) { if (bypass) return true; else return esp_spiram_test(); }
|
||||||
|
|
||||||
|
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L);
|
||||||
|
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L);
|
||||||
|
|
||||||
|
extern "C" bool getLocalTime(struct tm * info, uint32_t ms = 5000);
|
||||||
|
extern "C" void configTime(long gmtOffset_sec, int daylightOffset_sec,
|
||||||
|
const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);
|
||||||
|
extern "C" void configTzTime(const char* tz,
|
||||||
|
const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);
|
||||||
|
|
||||||
|
void setToneChannel(uint8_t channel = 0);
|
||||||
|
void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0);
|
||||||
|
void noTone(uint8_t _pin);
|
||||||
|
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
#include "io_pin_remap.h"
|
||||||
|
|
||||||
|
#endif /* _ESP32_CORE_ARDUINO_H_ */
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Client.h - Base class that provides Client
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef client_h
|
||||||
|
#define client_h
|
||||||
|
#include "Print.h"
|
||||||
|
#include "Stream.h"
|
||||||
|
#include "IPAddress.h"
|
||||||
|
|
||||||
|
class Client: public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual int connect(IPAddress ip, uint16_t port) =0;
|
||||||
|
virtual int connect(const char *host, uint16_t port) =0;
|
||||||
|
virtual size_t write(uint8_t) =0;
|
||||||
|
virtual size_t write(const uint8_t *buf, size_t size) =0;
|
||||||
|
virtual int available() = 0;
|
||||||
|
virtual int read() = 0;
|
||||||
|
virtual int read(uint8_t *buf, size_t size) = 0;
|
||||||
|
virtual int peek() = 0;
|
||||||
|
virtual void flush() = 0;
|
||||||
|
virtual void stop() = 0;
|
||||||
|
virtual uint8_t connected() = 0;
|
||||||
|
virtual operator bool() = 0;
|
||||||
|
protected:
|
||||||
|
uint8_t* rawIPAddress(IPAddress& addr)
|
||||||
|
{
|
||||||
|
return addr.raw_address();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,450 @@
|
|||||||
|
/*
|
||||||
|
Esp.cpp - ESP31B-specific APIs
|
||||||
|
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Esp.h"
|
||||||
|
#include "esp_sleep.h"
|
||||||
|
#include "esp_spi_flash.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <soc/soc.h>
|
||||||
|
#include <esp_partition.h>
|
||||||
|
extern "C" {
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_image_format.h"
|
||||||
|
}
|
||||||
|
#include <MD5Builder.h>
|
||||||
|
|
||||||
|
#include "soc/spi_reg.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#ifdef ESP_IDF_VERSION_MAJOR // IDF 4+
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
|
||||||
|
#include "esp32/rom/spi_flash.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x1000 // Flash offset containing flash size and spi mode
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#include "esp32s2/rom/spi_flash.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x1000
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#include "esp32s3/rom/spi_flash.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x0000 // Esp32s3 is located at 0x0000
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#include "esp32c3/rom/spi_flash.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x0000 // Esp32c3 is located at 0x0000
|
||||||
|
#else
|
||||||
|
#error Target CONFIG_IDF_TARGET is not supported
|
||||||
|
#endif
|
||||||
|
#else // ESP32 Before IDF 4.0
|
||||||
|
#include "rom/spi_flash.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x1000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// REG_SPI_BASE is not defined for S3/C3 ??
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#ifndef REG_SPI_BASE
|
||||||
|
#define REG_SPI_BASE(i) (DR_REG_SPI1_BASE + (((i)>1) ? (((i)* 0x1000) + 0x20000) : (((~(i)) & 1)* 0x1000 )))
|
||||||
|
#endif // REG_SPI_BASE
|
||||||
|
#endif // TARGET
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-defined Literals
|
||||||
|
* usage:
|
||||||
|
*
|
||||||
|
* uint32_t = test = 10_MHz; // --> 10000000
|
||||||
|
*/
|
||||||
|
|
||||||
|
unsigned long long operator"" _kHz(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _MHz(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1000 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _GHz(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1000 * 1000 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _kBit(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _MBit(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _GBit(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _kB(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _MB(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _GB(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EspClass ESP;
|
||||||
|
|
||||||
|
void EspClass::deepSleep(uint32_t time_us)
|
||||||
|
{
|
||||||
|
esp_deep_sleep(time_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspClass::restart(void)
|
||||||
|
{
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getHeapSize(void)
|
||||||
|
{
|
||||||
|
multi_heap_info_t info;
|
||||||
|
heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
|
||||||
|
return info.total_free_bytes + info.total_allocated_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFreeHeap(void)
|
||||||
|
{
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getMinFreeHeap(void)
|
||||||
|
{
|
||||||
|
return heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getMaxAllocHeap(void)
|
||||||
|
{
|
||||||
|
return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getPsramSize(void)
|
||||||
|
{
|
||||||
|
if(psramFound()){
|
||||||
|
multi_heap_info_t info;
|
||||||
|
heap_caps_get_info(&info, MALLOC_CAP_SPIRAM);
|
||||||
|
return info.total_free_bytes + info.total_allocated_bytes;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFreePsram(void)
|
||||||
|
{
|
||||||
|
if(psramFound()){
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getMinFreePsram(void)
|
||||||
|
{
|
||||||
|
if(psramFound()){
|
||||||
|
return heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getMaxAllocPsram(void)
|
||||||
|
{
|
||||||
|
if(psramFound()){
|
||||||
|
return heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t sketchSize(sketchSize_t response) {
|
||||||
|
esp_image_metadata_t data;
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
if (!running) return 0;
|
||||||
|
const esp_partition_pos_t running_pos = {
|
||||||
|
.offset = running->address,
|
||||||
|
.size = running->size,
|
||||||
|
};
|
||||||
|
data.start_addr = running_pos.offset;
|
||||||
|
esp_image_verify(ESP_IMAGE_VERIFY, &running_pos, &data);
|
||||||
|
if (response) {
|
||||||
|
return running_pos.size - data.image_len;
|
||||||
|
} else {
|
||||||
|
return data.image_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getSketchSize () {
|
||||||
|
return sketchSize(SKETCH_SIZE_TOTAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
String EspClass::getSketchMD5()
|
||||||
|
{
|
||||||
|
static String result;
|
||||||
|
if (result.length()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
uint32_t lengthLeft = getSketchSize();
|
||||||
|
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
if (!running) {
|
||||||
|
log_e("Partition could not be found");
|
||||||
|
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
const size_t bufSize = SPI_FLASH_SEC_SIZE;
|
||||||
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[bufSize]);
|
||||||
|
uint32_t offset = 0;
|
||||||
|
if(!buf.get()) {
|
||||||
|
log_e("Not enough memory to allocate buffer");
|
||||||
|
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
MD5Builder md5;
|
||||||
|
md5.begin();
|
||||||
|
while( lengthLeft > 0) {
|
||||||
|
size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
|
||||||
|
if (!ESP.flashRead(running->address + offset, reinterpret_cast<uint32_t*>(buf.get()), (readBytes + 3) & ~3)) {
|
||||||
|
log_e("Could not read buffer from flash");
|
||||||
|
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
md5.add(buf.get(), readBytes);
|
||||||
|
lengthLeft -= readBytes;
|
||||||
|
offset += readBytes;
|
||||||
|
}
|
||||||
|
md5.calculate();
|
||||||
|
result = md5.toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFreeSketchSpace () {
|
||||||
|
const esp_partition_t* _partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
if(!_partition){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _partition->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t EspClass::getChipRevision(void)
|
||||||
|
{
|
||||||
|
esp_chip_info_t chip_info;
|
||||||
|
esp_chip_info(&chip_info);
|
||||||
|
return chip_info.revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * EspClass::getChipModel(void)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG);
|
||||||
|
uint32_t pkg_ver = chip_ver & 0x7;
|
||||||
|
switch (pkg_ver) {
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDQ6 :
|
||||||
|
if (getChipRevision() == 3)
|
||||||
|
return "ESP32-D0WDQ6-V3";
|
||||||
|
else
|
||||||
|
return "ESP32-D0WDQ6";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDQ5 :
|
||||||
|
if (getChipRevision() == 3)
|
||||||
|
return "ESP32-D0WD-V3";
|
||||||
|
else
|
||||||
|
return "ESP32-D0WD";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32D2WDQ5 :
|
||||||
|
return "ESP32-D2WD";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD2 :
|
||||||
|
return "ESP32-PICO-D2";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD4 :
|
||||||
|
return "ESP32-PICO-D4";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32PICOV302 :
|
||||||
|
return "ESP32-PICO-V3-02";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDR2V3 :
|
||||||
|
return "ESP32-D0WDR2-V3";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
uint32_t pkg_ver = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_3_REG, EFUSE_PKG_VERSION);
|
||||||
|
switch (pkg_ver) {
|
||||||
|
case 0:
|
||||||
|
return "ESP32-S2";
|
||||||
|
case 1:
|
||||||
|
return "ESP32-S2FH16";
|
||||||
|
case 2:
|
||||||
|
return "ESP32-S2FH32";
|
||||||
|
default:
|
||||||
|
return "ESP32-S2 (Unknown)";
|
||||||
|
}
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return "ESP32-S3";
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
return "ESP32-C3";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t EspClass::getChipCores(void)
|
||||||
|
{
|
||||||
|
esp_chip_info_t chip_info;
|
||||||
|
esp_chip_info(&chip_info);
|
||||||
|
return chip_info.cores;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * EspClass::getSdkVersion(void)
|
||||||
|
{
|
||||||
|
return esp_get_idf_version();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ESP_getFlashChipId(void)
|
||||||
|
{
|
||||||
|
uint32_t id = g_rom_flashchip.device_id;
|
||||||
|
id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFlashChipSize(void)
|
||||||
|
{
|
||||||
|
uint32_t id = (ESP_getFlashChipId() >> 16) & 0xFF;
|
||||||
|
return 2 << (id - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFlashChipSpeed(void)
|
||||||
|
{
|
||||||
|
esp_image_header_t fhdr;
|
||||||
|
if(flashRead(ESP_FLASH_IMAGE_BASE, (uint32_t*)&fhdr, sizeof(esp_image_header_t)) && fhdr.magic != ESP_IMAGE_HEADER_MAGIC) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return magicFlashChipSpeed(fhdr.spi_speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlashMode_t EspClass::getFlashChipMode(void)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
uint32_t spi_ctrl = REG_READ(PERIPHS_SPI_FLASH_CTRL);
|
||||||
|
#else
|
||||||
|
uint32_t spi_ctrl = REG_READ(SPI_CTRL_REG(0));
|
||||||
|
#endif
|
||||||
|
/* Not all of the following constants are already defined in older versions of spi_reg.h, so do it manually for now*/
|
||||||
|
if (spi_ctrl & BIT(24)) { //SPI_FREAD_QIO
|
||||||
|
return (FM_QIO);
|
||||||
|
} else if (spi_ctrl & BIT(20)) { //SPI_FREAD_QUAD
|
||||||
|
return (FM_QOUT);
|
||||||
|
} else if (spi_ctrl & BIT(23)) { //SPI_FREAD_DIO
|
||||||
|
return (FM_DIO);
|
||||||
|
} else if (spi_ctrl & BIT(14)) { // SPI_FREAD_DUAL
|
||||||
|
return (FM_DOUT);
|
||||||
|
} else if (spi_ctrl & BIT(13)) { //SPI_FASTRD_MODE
|
||||||
|
return (FM_FAST_READ);
|
||||||
|
} else {
|
||||||
|
return (FM_SLOW_READ);
|
||||||
|
}
|
||||||
|
return (FM_DOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::magicFlashChipSize(uint8_t byte)
|
||||||
|
{
|
||||||
|
switch(byte & 0x0F) {
|
||||||
|
case 0x0: // 8 MBit (1MB)
|
||||||
|
return (1_MB);
|
||||||
|
case 0x1: // 16 MBit (2MB)
|
||||||
|
return (2_MB);
|
||||||
|
case 0x2: // 32 MBit (4MB)
|
||||||
|
return (4_MB);
|
||||||
|
case 0x3: // 64 MBit (8MB)
|
||||||
|
return (8_MB);
|
||||||
|
case 0x4: // 128 MBit (16MB)
|
||||||
|
return (16_MB);
|
||||||
|
default: // fail?
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::magicFlashChipSpeed(uint8_t byte)
|
||||||
|
{
|
||||||
|
switch(byte & 0x0F) {
|
||||||
|
case 0x0: // 40 MHz
|
||||||
|
return (40_MHz);
|
||||||
|
case 0x1: // 26 MHz
|
||||||
|
return (26_MHz);
|
||||||
|
case 0x2: // 20 MHz
|
||||||
|
return (20_MHz);
|
||||||
|
case 0xf: // 80 MHz
|
||||||
|
return (80_MHz);
|
||||||
|
default: // fail?
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlashMode_t EspClass::magicFlashChipMode(uint8_t byte)
|
||||||
|
{
|
||||||
|
FlashMode_t mode = (FlashMode_t) byte;
|
||||||
|
if(mode > FM_SLOW_READ) {
|
||||||
|
mode = FM_UNKNOWN;
|
||||||
|
}
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashEraseSector(uint32_t sector)
|
||||||
|
{
|
||||||
|
return spi_flash_erase_sector(sector) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning: These functions do not work with encrypted flash
|
||||||
|
bool EspClass::flashWrite(uint32_t offset, uint32_t *data, size_t size)
|
||||||
|
{
|
||||||
|
return spi_flash_write(offset, (uint32_t*) data, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashRead(uint32_t offset, uint32_t *data, size_t size)
|
||||||
|
{
|
||||||
|
return spi_flash_read(offset, (uint32_t*) data, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::partitionEraseRange(const esp_partition_t *partition, uint32_t offset, size_t size)
|
||||||
|
{
|
||||||
|
return esp_partition_erase_range(partition, offset, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::partitionWrite(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size)
|
||||||
|
{
|
||||||
|
return esp_partition_write(partition, offset, data, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::partitionRead(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size)
|
||||||
|
{
|
||||||
|
return esp_partition_read(partition, offset, data, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t EspClass::getEfuseMac(void)
|
||||||
|
{
|
||||||
|
uint64_t _chipmacid = 0LL;
|
||||||
|
esp_efuse_mac_get_default((uint8_t*) (&_chipmacid));
|
||||||
|
return _chipmacid;
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
Esp.h - ESP31B-specific APIs
|
||||||
|
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ESP_H
|
||||||
|
#define ESP_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <esp_partition.h>
|
||||||
|
#include <hal/cpu_hal.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AVR macros for WDT managment
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
WDTO_0MS = 0, //!< WDTO_0MS
|
||||||
|
WDTO_15MS = 15, //!< WDTO_15MS
|
||||||
|
WDTO_30MS = 30, //!< WDTO_30MS
|
||||||
|
WDTO_60MS = 60, //!< WDTO_60MS
|
||||||
|
WDTO_120MS = 120, //!< WDTO_120MS
|
||||||
|
WDTO_250MS = 250, //!< WDTO_250MS
|
||||||
|
WDTO_500MS = 500, //!< WDTO_500MS
|
||||||
|
WDTO_1S = 1000,//!< WDTO_1S
|
||||||
|
WDTO_2S = 2000,//!< WDTO_2S
|
||||||
|
WDTO_4S = 4000,//!< WDTO_4S
|
||||||
|
WDTO_8S = 8000 //!< WDTO_8S
|
||||||
|
} WDTO_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FM_QIO = 0x00,
|
||||||
|
FM_QOUT = 0x01,
|
||||||
|
FM_DIO = 0x02,
|
||||||
|
FM_DOUT = 0x03,
|
||||||
|
FM_FAST_READ = 0x04,
|
||||||
|
FM_SLOW_READ = 0x05,
|
||||||
|
FM_UNKNOWN = 0xff
|
||||||
|
} FlashMode_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SKETCH_SIZE_TOTAL = 0,
|
||||||
|
SKETCH_SIZE_FREE = 1
|
||||||
|
} sketchSize_t;
|
||||||
|
|
||||||
|
class EspClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EspClass() {}
|
||||||
|
~EspClass() {}
|
||||||
|
void restart();
|
||||||
|
|
||||||
|
//Internal RAM
|
||||||
|
uint32_t getHeapSize(); //total heap size
|
||||||
|
uint32_t getFreeHeap(); //available heap
|
||||||
|
uint32_t getMinFreeHeap(); //lowest level of free heap since boot
|
||||||
|
uint32_t getMaxAllocHeap(); //largest block of heap that can be allocated at once
|
||||||
|
|
||||||
|
//SPI RAM
|
||||||
|
uint32_t getPsramSize();
|
||||||
|
uint32_t getFreePsram();
|
||||||
|
uint32_t getMinFreePsram();
|
||||||
|
uint32_t getMaxAllocPsram();
|
||||||
|
|
||||||
|
uint8_t getChipRevision();
|
||||||
|
const char * getChipModel();
|
||||||
|
uint8_t getChipCores();
|
||||||
|
uint32_t getCpuFreqMHz(){ return getCpuFrequencyMhz(); }
|
||||||
|
inline uint32_t getCycleCount() __attribute__((always_inline));
|
||||||
|
const char * getSdkVersion();
|
||||||
|
|
||||||
|
void deepSleep(uint32_t time_us);
|
||||||
|
|
||||||
|
uint32_t getFlashChipSize();
|
||||||
|
uint32_t getFlashChipSpeed();
|
||||||
|
FlashMode_t getFlashChipMode();
|
||||||
|
|
||||||
|
uint32_t magicFlashChipSize(uint8_t byte);
|
||||||
|
uint32_t magicFlashChipSpeed(uint8_t byte);
|
||||||
|
FlashMode_t magicFlashChipMode(uint8_t byte);
|
||||||
|
|
||||||
|
uint32_t getSketchSize();
|
||||||
|
String getSketchMD5();
|
||||||
|
uint32_t getFreeSketchSpace();
|
||||||
|
|
||||||
|
bool flashEraseSector(uint32_t sector);
|
||||||
|
bool flashWrite(uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
bool flashRead(uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
|
||||||
|
bool partitionEraseRange(const esp_partition_t *partition, uint32_t offset, size_t size);
|
||||||
|
bool partitionWrite(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
bool partitionRead(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
|
||||||
|
uint64_t getEfuseMac();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t ARDUINO_ISR_ATTR EspClass::getCycleCount()
|
||||||
|
{
|
||||||
|
return cpu_hal_get_cycle_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern EspClass ESP;
|
||||||
|
|
||||||
|
#endif //ESP_H
|
@ -0,0 +1,424 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#include "FirmwareMSC.h"
|
||||||
|
|
||||||
|
#if CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
#include "firmware_msc_fat.h"
|
||||||
|
|
||||||
|
#ifndef USB_FW_MSC_VENDOR_ID
|
||||||
|
#define USB_FW_MSC_VENDOR_ID "ESP32" //max 8 chars
|
||||||
|
#endif
|
||||||
|
#ifndef USB_FW_MSC_PRODUCT_ID
|
||||||
|
#define USB_FW_MSC_PRODUCT_ID "Firmware MSC"//max 16 chars
|
||||||
|
#endif
|
||||||
|
#ifndef USB_FW_MSC_PRODUCT_REVISION
|
||||||
|
#define USB_FW_MSC_PRODUCT_REVISION "1.0" //max 4 chars
|
||||||
|
#endif
|
||||||
|
#ifndef USB_FW_MSC_VOLUME_NAME
|
||||||
|
#define USB_FW_MSC_VOLUME_NAME "ESP32-FWMSC" //max 11 chars
|
||||||
|
#endif
|
||||||
|
#ifndef USB_FW_MSC_SERIAL_NUMBER
|
||||||
|
#define USB_FW_MSC_SERIAL_NUMBER 0x00000000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ESP_EVENT_DEFINE_BASE(ARDUINO_FIRMWARE_MSC_EVENTS);
|
||||||
|
esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait);
|
||||||
|
esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg);
|
||||||
|
|
||||||
|
//General Variables
|
||||||
|
static uint8_t * msc_ram_disk = NULL;
|
||||||
|
static fat_boot_sector_t * msc_boot = NULL;
|
||||||
|
static uint8_t * msc_table = NULL;
|
||||||
|
static uint16_t msc_table_sectors = 0;
|
||||||
|
static uint16_t msc_total_sectors = 0;
|
||||||
|
static bool mcs_is_fat16 = false;
|
||||||
|
|
||||||
|
//Firmware Read
|
||||||
|
static const esp_partition_t* msc_run_partition = NULL;
|
||||||
|
static uint16_t fw_start_sector = 0;
|
||||||
|
static uint16_t fw_end_sector = 0;
|
||||||
|
static size_t fw_size = 0;
|
||||||
|
static fat_dir_entry_t * fw_entry = NULL;
|
||||||
|
|
||||||
|
//Firmware Write
|
||||||
|
typedef enum {
|
||||||
|
MSC_UPDATE_IDLE,
|
||||||
|
MSC_UPDATE_STARTING,
|
||||||
|
MSC_UPDATE_RUNNING,
|
||||||
|
MSC_UPDATE_END
|
||||||
|
} msc_update_state_t;
|
||||||
|
|
||||||
|
static const esp_partition_t* msc_ota_partition = NULL;
|
||||||
|
static msc_update_state_t msc_update_state = MSC_UPDATE_IDLE;
|
||||||
|
static uint16_t msc_update_start_sector = 0;
|
||||||
|
static uint32_t msc_update_bytes_written = 0;
|
||||||
|
static fat_dir_entry_t * msc_update_entry = NULL;
|
||||||
|
|
||||||
|
static uint32_t get_firmware_size(const esp_partition_t* partition){
|
||||||
|
esp_image_metadata_t data;
|
||||||
|
const esp_partition_pos_t running_pos = {
|
||||||
|
.offset = partition->address,
|
||||||
|
.size = partition->size,
|
||||||
|
};
|
||||||
|
data.start_addr = running_pos.offset;
|
||||||
|
esp_image_verify(ESP_IMAGE_VERIFY, &running_pos, &data);
|
||||||
|
return data.image_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get number of sectors required based on the size of the firmware and OTA partition
|
||||||
|
static size_t msc_update_get_required_disk_sectors(){
|
||||||
|
size_t data_sectors = 16;
|
||||||
|
size_t total_sectors = 0;
|
||||||
|
msc_run_partition = esp_ota_get_running_partition();
|
||||||
|
msc_ota_partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
if(msc_run_partition){
|
||||||
|
fw_size = get_firmware_size(msc_run_partition);
|
||||||
|
data_sectors += FAT_SIZE_TO_SECTORS(fw_size);
|
||||||
|
log_d("APP size: %u (%u sectors)", fw_size, FAT_SIZE_TO_SECTORS(fw_size));
|
||||||
|
} else {
|
||||||
|
log_w("APP partition not found. Reading disabled");
|
||||||
|
}
|
||||||
|
if(msc_ota_partition){
|
||||||
|
data_sectors += FAT_SIZE_TO_SECTORS(msc_ota_partition->size);
|
||||||
|
log_d("OTA size: %u (%u sectors)", msc_ota_partition->size, FAT_SIZE_TO_SECTORS(msc_ota_partition->size));
|
||||||
|
} else {
|
||||||
|
log_w("OTA partition not found. Writing disabled");
|
||||||
|
}
|
||||||
|
msc_table_sectors = fat_sectors_per_alloc_table(data_sectors, false);
|
||||||
|
total_sectors = data_sectors + msc_table_sectors + 2;
|
||||||
|
if(total_sectors > 0xFF4){
|
||||||
|
log_d("USING FAT16");
|
||||||
|
mcs_is_fat16 = true;
|
||||||
|
total_sectors -= msc_table_sectors;
|
||||||
|
msc_table_sectors = fat_sectors_per_alloc_table(data_sectors, true);
|
||||||
|
total_sectors += msc_table_sectors;
|
||||||
|
} else {
|
||||||
|
log_d("USING FAT12");
|
||||||
|
mcs_is_fat16 = false;
|
||||||
|
}
|
||||||
|
log_d("FAT sector size: %u", DISK_SECTOR_SIZE);
|
||||||
|
log_d("FAT data sectors: %u", data_sectors);
|
||||||
|
log_d("FAT table sectors: %u", msc_table_sectors);
|
||||||
|
log_d("FAT total sectors: %u (%uKB)", total_sectors, (total_sectors * DISK_SECTOR_SIZE) / 1024);
|
||||||
|
return total_sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
//setup the ramdisk and add the firmware download file
|
||||||
|
static bool msc_update_setup_disk(const char * volume_label, uint32_t serial_number){
|
||||||
|
msc_total_sectors = msc_update_get_required_disk_sectors();
|
||||||
|
uint8_t ram_sectors = msc_table_sectors + 2;
|
||||||
|
msc_ram_disk = (uint8_t*)calloc(ram_sectors, DISK_SECTOR_SIZE);
|
||||||
|
if(!msc_ram_disk){
|
||||||
|
log_e("Failed to allocate RAM Disk: %u bytes", ram_sectors * DISK_SECTOR_SIZE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fw_start_sector = ram_sectors;
|
||||||
|
fw_end_sector = fw_start_sector;
|
||||||
|
msc_boot = fat_add_boot_sector(msc_ram_disk, msc_total_sectors, msc_table_sectors, fat_file_system_type(mcs_is_fat16), volume_label, serial_number);
|
||||||
|
msc_table = fat_add_table(msc_ram_disk, msc_boot, mcs_is_fat16);
|
||||||
|
//fat_dir_entry_t * label = fat_add_label(msc_ram_disk, volume_label);
|
||||||
|
if(msc_run_partition){
|
||||||
|
fw_entry = fat_add_root_file(msc_ram_disk, 0, "FIRMWARE", "BIN", fw_size, 2, mcs_is_fat16);
|
||||||
|
fw_end_sector = FAT_SIZE_TO_SECTORS(fw_size) + fw_start_sector;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void msc_update_delete_disk(){
|
||||||
|
fw_entry = NULL;
|
||||||
|
fw_size = 0;
|
||||||
|
fw_end_sector = 0;
|
||||||
|
fw_start_sector = 0;
|
||||||
|
msc_table = NULL;
|
||||||
|
msc_boot = NULL;
|
||||||
|
msc_table_sectors = 0;
|
||||||
|
msc_total_sectors = 0;
|
||||||
|
msc_run_partition = NULL;
|
||||||
|
msc_ota_partition = NULL;
|
||||||
|
msc_update_state = MSC_UPDATE_IDLE;
|
||||||
|
msc_update_start_sector = 0;
|
||||||
|
msc_update_bytes_written = 0;
|
||||||
|
msc_update_entry = NULL;
|
||||||
|
free(msc_ram_disk);
|
||||||
|
msc_ram_disk = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//filter out entries to only include BINs in the root folder
|
||||||
|
static fat_dir_entry_t * msc_update_get_root_bin_entry(uint8_t index){
|
||||||
|
fat_dir_entry_t * entry = (fat_dir_entry_t *)(msc_ram_disk + ((msc_boot->sectors_per_alloc_table+1) * DISK_SECTOR_SIZE) + (index * sizeof(fat_dir_entry_t)));
|
||||||
|
fat_lfn_entry_t * lfn = (fat_lfn_entry_t*)entry;
|
||||||
|
|
||||||
|
//empty entry
|
||||||
|
if(entry->file_magic == 0){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//long file name
|
||||||
|
if(lfn->attr == 0x0F && lfn->type == 0x00 && lfn->first_cluster == 0x0000){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//only files marked as archives
|
||||||
|
if(entry->file_attr != FAT_FILE_ATTR_ARCHIVE){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//deleted
|
||||||
|
if(entry->file_magic == 0xE5 || entry->file_magic == 0x05){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//not bins
|
||||||
|
if(memcmp("BIN", entry->file_extension, 3)){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get an empty bin (the host will add an entry for file about to be written with size of zero)
|
||||||
|
static fat_dir_entry_t * msc_update_find_new_bin(){
|
||||||
|
for(uint8_t i=16; i;){
|
||||||
|
i--;
|
||||||
|
fat_dir_entry_t * entry = msc_update_get_root_bin_entry(i);
|
||||||
|
if(entry && entry->file_size == 0){
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get a bin starting from particular sector
|
||||||
|
static fat_dir_entry_t * msc_update_find_bin(uint16_t sector){
|
||||||
|
for(uint8_t i=16; i; ){
|
||||||
|
i--;
|
||||||
|
fat_dir_entry_t * entry = msc_update_get_root_bin_entry(i);
|
||||||
|
if(entry && entry->data_start_sector == (sector - msc_boot->sectors_per_alloc_table)){
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//write the new data and erase the flash blocks when necessary
|
||||||
|
static esp_err_t msc_update_write(const esp_partition_t *partition, uint32_t offset, void *data, size_t size){
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if((offset & (SPI_FLASH_SEC_SIZE-1)) == 0){
|
||||||
|
err = esp_partition_erase_range(partition, offset, SPI_FLASH_SEC_SIZE);
|
||||||
|
log_v("ERASE[0x%08X]: %s", offset, (err != ESP_OK)?"FAIL":"OK");
|
||||||
|
if(err != ESP_OK){
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return esp_partition_write(partition, offset, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
//called when error was encountered while updating
|
||||||
|
static void msc_update_error(){
|
||||||
|
log_e("UPDATE_ERROR: %u", msc_update_bytes_written);
|
||||||
|
arduino_firmware_msc_event_data_t p;
|
||||||
|
p.error.size = msc_update_bytes_written;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_ERROR_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
msc_update_state = MSC_UPDATE_IDLE;
|
||||||
|
msc_update_entry = NULL;
|
||||||
|
msc_update_bytes_written = 0;
|
||||||
|
msc_update_start_sector = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//called when all firmware bytes have been received
|
||||||
|
static void msc_update_end(){
|
||||||
|
log_d("UPDATE_END: %u", msc_update_entry->file_size);
|
||||||
|
msc_update_state = MSC_UPDATE_END;
|
||||||
|
size_t ota_size = get_firmware_size(msc_ota_partition);
|
||||||
|
if(ota_size != msc_update_entry->file_size){
|
||||||
|
log_e("OTA SIZE MISMATCH %u != %u", ota_size, msc_update_entry->file_size);
|
||||||
|
msc_update_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!ota_size || esp_ota_set_boot_partition(msc_ota_partition) != ESP_OK){
|
||||||
|
log_e("ENABLING OTA PARTITION FAILED");
|
||||||
|
msc_update_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
arduino_firmware_msc_event_data_t p;
|
||||||
|
p.end.size = msc_update_entry->file_size;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_END_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t msc_write(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize){
|
||||||
|
//log_d("lba: %u, offset: %u, bufsize: %u", lba, offset, bufsize);
|
||||||
|
if(lba < fw_start_sector){
|
||||||
|
//write to sectors that are in RAM
|
||||||
|
memcpy(msc_ram_disk + (lba * DISK_SECTOR_SIZE) + offset, buffer, bufsize);
|
||||||
|
if(msc_ota_partition && lba == (fw_start_sector - 1)){
|
||||||
|
//monitor the root folder table
|
||||||
|
if(msc_update_state <= MSC_UPDATE_RUNNING){
|
||||||
|
fat_dir_entry_t * update_entry = msc_update_find_new_bin();
|
||||||
|
if(update_entry) {
|
||||||
|
if(msc_update_entry) {
|
||||||
|
log_v("REPLACING ENTRY");
|
||||||
|
} else {
|
||||||
|
log_v("ASSIGNING ENTRY");
|
||||||
|
}
|
||||||
|
if(msc_update_state <= MSC_UPDATE_STARTING){
|
||||||
|
msc_update_state = MSC_UPDATE_STARTING;
|
||||||
|
msc_update_bytes_written = 0;
|
||||||
|
msc_update_start_sector = 0;
|
||||||
|
}
|
||||||
|
msc_update_entry = update_entry;
|
||||||
|
} else if(msc_update_state == MSC_UPDATE_RUNNING){
|
||||||
|
if(!msc_update_entry && msc_update_start_sector){
|
||||||
|
msc_update_entry = msc_update_find_bin(msc_update_start_sector);
|
||||||
|
}
|
||||||
|
if(msc_update_entry && msc_update_bytes_written >= msc_update_entry->file_size){
|
||||||
|
msc_update_end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(msc_ota_partition && lba >= msc_update_start_sector){
|
||||||
|
//handle writes to the region where the new firmware will be uploaded
|
||||||
|
arduino_firmware_msc_event_data_t p;
|
||||||
|
if(msc_update_state <= MSC_UPDATE_STARTING && buffer[0] == 0xE9){
|
||||||
|
msc_update_state = MSC_UPDATE_RUNNING;
|
||||||
|
msc_update_start_sector = lba;
|
||||||
|
msc_update_bytes_written = 0;
|
||||||
|
log_d("UPDATE_START: %u (0x%02X)", lba, lba - msc_boot->sectors_per_alloc_table);
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_START_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
if(msc_update_write(msc_ota_partition, ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset, buffer, bufsize) == ESP_OK){
|
||||||
|
log_v("UPDATE_WRITE: %u %u", ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset, bufsize);
|
||||||
|
msc_update_bytes_written = ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset + bufsize;
|
||||||
|
p.write.offset = ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset;
|
||||||
|
p.write.size = bufsize;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_WRITE_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
} else {
|
||||||
|
msc_update_error();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if(msc_update_state == MSC_UPDATE_RUNNING){
|
||||||
|
if(msc_update_entry && msc_update_entry->file_size && msc_update_bytes_written < msc_update_entry->file_size && (msc_update_bytes_written + bufsize) >= msc_update_entry->file_size){
|
||||||
|
bufsize = msc_update_entry->file_size - msc_update_bytes_written;
|
||||||
|
}
|
||||||
|
if(msc_update_write(msc_ota_partition, ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset, buffer, bufsize) == ESP_OK){
|
||||||
|
log_v("UPDATE_WRITE: %u %u", ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset, bufsize);
|
||||||
|
msc_update_bytes_written = ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset + bufsize;
|
||||||
|
p.write.offset = ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset;
|
||||||
|
p.write.size = bufsize;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_WRITE_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
if(msc_update_entry && msc_update_entry->file_size && msc_update_bytes_written >= msc_update_entry->file_size){
|
||||||
|
msc_update_end();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msc_update_error();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bufsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t msc_read(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize){
|
||||||
|
//log_d("lba: %u, offset: %u, bufsize: %u", lba, offset, bufsize);
|
||||||
|
if(lba < fw_start_sector){
|
||||||
|
memcpy(buffer, msc_ram_disk + (lba * DISK_SECTOR_SIZE) + offset, bufsize);
|
||||||
|
} else if(msc_run_partition && lba < fw_end_sector){
|
||||||
|
//read the currently running firmware
|
||||||
|
if(esp_partition_read(msc_run_partition, ((lba - fw_start_sector) * DISK_SECTOR_SIZE) + offset, buffer, bufsize) != ESP_OK){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memset(buffer, 0, bufsize);
|
||||||
|
}
|
||||||
|
return bufsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool msc_start_stop(uint8_t power_condition, bool start, bool load_eject){
|
||||||
|
//log_d("power: %u, start: %u, eject: %u", power_condition, start, load_eject);
|
||||||
|
arduino_firmware_msc_event_data_t p;
|
||||||
|
p.power.power_condition = power_condition;
|
||||||
|
p.power.start = start;
|
||||||
|
p.power.load_eject = load_eject;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_POWER_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static volatile TaskHandle_t msc_task_handle = NULL;
|
||||||
|
static void msc_task(void *pvParameters){
|
||||||
|
for (;;) {
|
||||||
|
if(msc_update_state == MSC_UPDATE_END){
|
||||||
|
delay(100);
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
msc_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
FirmwareMSC::FirmwareMSC():msc(){}
|
||||||
|
|
||||||
|
FirmwareMSC::~FirmwareMSC(){
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FirmwareMSC::begin(){
|
||||||
|
if(msc_ram_disk){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!msc_update_setup_disk(USB_FW_MSC_VOLUME_NAME, USB_FW_MSC_SERIAL_NUMBER)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!msc_task_handle){
|
||||||
|
xTaskCreateUniversal(msc_task, "msc_disk", 1024, NULL, 2, (TaskHandle_t*)&msc_task_handle, 0);
|
||||||
|
if(!msc_task_handle){
|
||||||
|
msc_update_delete_disk();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msc.vendorID(USB_FW_MSC_VENDOR_ID);
|
||||||
|
msc.productID(USB_FW_MSC_PRODUCT_ID);
|
||||||
|
msc.productRevision(USB_FW_MSC_PRODUCT_REVISION);
|
||||||
|
msc.onStartStop(msc_start_stop);
|
||||||
|
msc.onRead(msc_read);
|
||||||
|
msc.onWrite(msc_write);
|
||||||
|
msc.mediaPresent(true);
|
||||||
|
msc.begin(msc_boot->fat12_sector_num, DISK_SECTOR_SIZE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FirmwareMSC::end(){
|
||||||
|
msc.end();
|
||||||
|
if(msc_task_handle){
|
||||||
|
vTaskDelete(msc_task_handle);
|
||||||
|
msc_task_handle = NULL;
|
||||||
|
}
|
||||||
|
msc_update_delete_disk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FirmwareMSC::onEvent(esp_event_handler_t callback){
|
||||||
|
onEvent(ARDUINO_FIRMWARE_MSC_ANY_EVENT, callback);
|
||||||
|
}
|
||||||
|
void FirmwareMSC::onEvent(arduino_firmware_msc_event_t event, esp_event_handler_t callback){
|
||||||
|
arduino_usb_event_handler_register_with(ARDUINO_FIRMWARE_MSC_EVENTS, event, callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINO_USB_MSC_ON_BOOT
|
||||||
|
FirmwareMSC MSC_Update;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_USB_MSC_ENABLED */
|
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "USBMSC.h"
|
||||||
|
|
||||||
|
#if CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
|
||||||
|
#include "esp_event.h"
|
||||||
|
|
||||||
|
ESP_EVENT_DECLARE_BASE(ARDUINO_FIRMWARE_MSC_EVENTS);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARDUINO_FIRMWARE_MSC_ANY_EVENT = ESP_EVENT_ANY_ID,
|
||||||
|
ARDUINO_FIRMWARE_MSC_START_EVENT = 0,
|
||||||
|
ARDUINO_FIRMWARE_MSC_WRITE_EVENT,
|
||||||
|
ARDUINO_FIRMWARE_MSC_END_EVENT,
|
||||||
|
ARDUINO_FIRMWARE_MSC_ERROR_EVENT,
|
||||||
|
ARDUINO_FIRMWARE_MSC_POWER_EVENT,
|
||||||
|
ARDUINO_FIRMWARE_MSC_MAX_EVENT,
|
||||||
|
} arduino_firmware_msc_event_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
size_t offset;
|
||||||
|
size_t size;
|
||||||
|
} write;
|
||||||
|
struct {
|
||||||
|
uint8_t power_condition;
|
||||||
|
bool start;
|
||||||
|
bool load_eject;
|
||||||
|
} power;
|
||||||
|
struct {
|
||||||
|
size_t size;
|
||||||
|
} end;
|
||||||
|
struct {
|
||||||
|
size_t size;
|
||||||
|
} error;
|
||||||
|
} arduino_firmware_msc_event_data_t;
|
||||||
|
|
||||||
|
class FirmwareMSC {
|
||||||
|
private:
|
||||||
|
USBMSC msc;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FirmwareMSC();
|
||||||
|
~FirmwareMSC();
|
||||||
|
bool begin();
|
||||||
|
void end();
|
||||||
|
void onEvent(esp_event_handler_t callback);
|
||||||
|
void onEvent(arduino_firmware_msc_event_t event, esp_event_handler_t callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
#if ARDUINO_USB_MSC_ON_BOOT
|
||||||
|
extern FirmwareMSC MSC_Update;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_MSC_ENABLED */
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* FunctionalInterrupt.cpp
|
||||||
|
*
|
||||||
|
* Created on: 8 jul. 2018
|
||||||
|
* Author: Herman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FunctionalInterrupt.h"
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
typedef void (*voidFuncPtr)(void);
|
||||||
|
typedef void (*voidFuncPtrArg)(void*);
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARDUINO_ISR_ATTR interruptFunctional(void* arg)
|
||||||
|
{
|
||||||
|
InterruptArgStructure* localArg = (InterruptArgStructure*)arg;
|
||||||
|
if (localArg->interruptFunction)
|
||||||
|
{
|
||||||
|
localArg->interruptFunction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode)
|
||||||
|
{
|
||||||
|
// use the local interrupt routine which takes the ArgStructure as argument
|
||||||
|
__attachInterruptFunctionalArg (digitalPinToGPIONumber(pin), (voidFuncPtrArg)interruptFunctional, new InterruptArgStructure{intRoutine}, mode, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
void cleanupFunctional(void* arg)
|
||||||
|
{
|
||||||
|
delete (InterruptArgStructure*)arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* FunctionalInterrupt.h
|
||||||
|
*
|
||||||
|
* Created on: 8 jul. 2018
|
||||||
|
* Author: Herman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CORE_CORE_FUNCTIONALINTERRUPT_H_
|
||||||
|
#define CORE_CORE_FUNCTIONALINTERRUPT_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct InterruptArgStructure {
|
||||||
|
std::function<void(void)> interruptFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The extra set of parentheses here prevents macros defined
|
||||||
|
// in io_pin_remap.h from applying to this declaration.
|
||||||
|
void (attachInterrupt)(uint8_t pin, std::function<void(void)> intRoutine, int mode);
|
||||||
|
|
||||||
|
#endif /* CORE_CORE_FUNCTIONALINTERRUPT_H_ */
|
@ -0,0 +1,411 @@
|
|||||||
|
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#include "USB.h"
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "HWCDC.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/ringbuf.h"
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
#include "soc/periph_defs.h"
|
||||||
|
#include "hal/usb_serial_jtag_ll.h"
|
||||||
|
|
||||||
|
ESP_EVENT_DEFINE_BASE(ARDUINO_HW_CDC_EVENTS);
|
||||||
|
|
||||||
|
static RingbufHandle_t tx_ring_buf = NULL;
|
||||||
|
static xQueueHandle rx_queue = NULL;
|
||||||
|
static uint8_t rx_data_buf[64] = {0};
|
||||||
|
static intr_handle_t intr_handle = NULL;
|
||||||
|
static volatile bool initial_empty = false;
|
||||||
|
static xSemaphoreHandle tx_lock = NULL;
|
||||||
|
|
||||||
|
// workaround for when USB CDC is not connected
|
||||||
|
static uint32_t tx_timeout_ms = 0;
|
||||||
|
static bool tx_timeout_change_request = false;
|
||||||
|
|
||||||
|
static esp_event_loop_handle_t arduino_hw_cdc_event_loop_handle = NULL;
|
||||||
|
|
||||||
|
static esp_err_t arduino_hw_cdc_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, BaseType_t *task_unblocked){
|
||||||
|
if(arduino_hw_cdc_event_loop_handle == NULL){
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return esp_event_isr_post_to(arduino_hw_cdc_event_loop_handle, event_base, event_id, event_data, event_data_size, task_unblocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t arduino_hw_cdc_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg){
|
||||||
|
if (!arduino_hw_cdc_event_loop_handle) {
|
||||||
|
esp_event_loop_args_t event_task_args = {
|
||||||
|
.queue_size = 5,
|
||||||
|
.task_name = "arduino_hw_cdc_events",
|
||||||
|
.task_priority = 5,
|
||||||
|
.task_stack_size = 2048,
|
||||||
|
.task_core_id = tskNO_AFFINITY
|
||||||
|
};
|
||||||
|
if (esp_event_loop_create(&event_task_args, &arduino_hw_cdc_event_loop_handle) != ESP_OK) {
|
||||||
|
log_e("esp_event_loop_create failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(arduino_hw_cdc_event_loop_handle == NULL){
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return esp_event_handler_register_with(arduino_hw_cdc_event_loop_handle, event_base, event_id, event_handler, event_handler_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hw_cdc_isr_handler(void *arg) {
|
||||||
|
portBASE_TYPE xTaskWoken = 0;
|
||||||
|
uint32_t usbjtag_intr_status = 0;
|
||||||
|
arduino_hw_cdc_event_data_t event = {0};
|
||||||
|
usbjtag_intr_status = usb_serial_jtag_ll_get_intsts_mask();
|
||||||
|
|
||||||
|
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {
|
||||||
|
// Interrupt tells us the host picked up the data we sent.
|
||||||
|
if (usb_serial_jtag_ll_txfifo_writable() == 1) {
|
||||||
|
// We disable the interrupt here so that the interrupt won't be triggered if there is no data to send.
|
||||||
|
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
if(!initial_empty){
|
||||||
|
initial_empty = true;
|
||||||
|
// First time USB is plugged and the application has not explicitly set TX Timeout, set it to default 100ms.
|
||||||
|
// Otherwise, USB is still unplugged and the timeout will be kept as Zero in order to avoid any delay in the
|
||||||
|
// application whenever it uses write() and the TX Queue gets full.
|
||||||
|
if (!tx_timeout_change_request) {
|
||||||
|
tx_timeout_ms = 100;
|
||||||
|
}
|
||||||
|
//send event?
|
||||||
|
//ets_printf("CONNECTED\n");
|
||||||
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_CONNECTED_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
||||||
|
}
|
||||||
|
size_t queued_size;
|
||||||
|
uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpToFromISR(tx_ring_buf, &queued_size, 64);
|
||||||
|
// If the hardware fifo is avaliable, write in it. Otherwise, do nothing.
|
||||||
|
if (queued_buff != NULL) { //Although tx_queued_bytes may be larger than 0. We may have interrupt before xRingbufferSend() was called.
|
||||||
|
//Copy the queued buffer into the TX FIFO
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
usb_serial_jtag_ll_write_txfifo(queued_buff, queued_size);
|
||||||
|
usb_serial_jtag_ll_txfifo_flush();
|
||||||
|
vRingbufferReturnItemFromISR(tx_ring_buf, queued_buff, &xTaskWoken);
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
//send event?
|
||||||
|
//ets_printf("TX:%u\n", queued_size);
|
||||||
|
event.tx.len = queued_size;
|
||||||
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_TX_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) {
|
||||||
|
// read rx buffer(max length is 64), and send avaliable data to ringbuffer.
|
||||||
|
// Ensure the rx buffer size is larger than RX_MAX_SIZE.
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
|
||||||
|
uint32_t rx_fifo_len = usb_serial_jtag_ll_read_rxfifo(rx_data_buf, 64);
|
||||||
|
uint32_t i=0;
|
||||||
|
for(i=0; i<rx_fifo_len; i++){
|
||||||
|
if(rx_queue == NULL || !xQueueSendFromISR(rx_queue, rx_data_buf+i, &xTaskWoken)){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//send event?
|
||||||
|
//ets_printf("RX:%u/%u\n", i, rx_fifo_len);
|
||||||
|
event.rx.len = i;
|
||||||
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_RX_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_BUS_RESET) {
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_BUS_RESET);
|
||||||
|
initial_empty = false;
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
//ets_printf("BUS_RESET\n");
|
||||||
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_BUS_RESET_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xTaskWoken == pdTRUE) {
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ARDUINO_ISR_ATTR cdc0_write_char(char c) {
|
||||||
|
if(xPortInIsrContext()){
|
||||||
|
xRingbufferSendFromISR(tx_ring_buf, (void*) (&c), 1, NULL);
|
||||||
|
} else {
|
||||||
|
xRingbufferSend(tx_ring_buf, (void*) (&c), 1, tx_timeout_ms / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
HWCDC::HWCDC() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
HWCDC::~HWCDC(){
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
HWCDC::operator bool() const
|
||||||
|
{
|
||||||
|
return initial_empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::onEvent(esp_event_handler_t callback){
|
||||||
|
onEvent(ARDUINO_HW_CDC_ANY_EVENT, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::onEvent(arduino_hw_cdc_event_t event, esp_event_handler_t callback){
|
||||||
|
arduino_hw_cdc_event_handler_register_with(ARDUINO_HW_CDC_EVENTS, event, callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::begin(unsigned long baud)
|
||||||
|
{
|
||||||
|
if(tx_lock == NULL) {
|
||||||
|
tx_lock = xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
|
//RX Buffer default has 256 bytes if not preset
|
||||||
|
if(rx_queue == NULL) {
|
||||||
|
if (!setRxBufferSize(256)) {
|
||||||
|
log_e("HW CDC RX Buffer error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TX Buffer default has 256 bytes if not preset
|
||||||
|
if (tx_ring_buf == NULL) {
|
||||||
|
if (!setTxBufferSize(256)) {
|
||||||
|
log_e("HW CDC TX Buffer error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | USB_SERIAL_JTAG_INTR_BUS_RESET);
|
||||||
|
if(!intr_handle && esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0, hw_cdc_isr_handler, NULL, &intr_handle) != ESP_OK){
|
||||||
|
isr_log_e("HW USB CDC failed to init interrupts");
|
||||||
|
end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
usb_serial_jtag_ll_txfifo_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::end()
|
||||||
|
{
|
||||||
|
//Disable tx/rx interrupt.
|
||||||
|
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
|
||||||
|
esp_intr_free(intr_handle);
|
||||||
|
intr_handle = NULL;
|
||||||
|
if(tx_lock != NULL) {
|
||||||
|
vSemaphoreDelete(tx_lock);
|
||||||
|
tx_lock = NULL;
|
||||||
|
}
|
||||||
|
setRxBufferSize(0);
|
||||||
|
setTxBufferSize(0);
|
||||||
|
if (arduino_hw_cdc_event_loop_handle) {
|
||||||
|
esp_event_loop_delete(arduino_hw_cdc_event_loop_handle);
|
||||||
|
arduino_hw_cdc_event_loop_handle = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::setTxTimeoutMs(uint32_t timeout){
|
||||||
|
tx_timeout_ms = timeout;
|
||||||
|
// it registers that the user has explicitly requested to use a value as TX timeout
|
||||||
|
// used for the workaround with unplugged USB and TX Queue Full that causes a delay on every write()
|
||||||
|
tx_timeout_change_request = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WRITING
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t HWCDC::setTxBufferSize(size_t tx_queue_len){
|
||||||
|
if(tx_ring_buf){
|
||||||
|
vRingbufferDelete(tx_ring_buf);
|
||||||
|
tx_ring_buf = NULL;
|
||||||
|
}
|
||||||
|
if(!tx_queue_len){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
tx_ring_buf = xRingbufferCreate(tx_queue_len, RINGBUF_TYPE_BYTEBUF);
|
||||||
|
if(!tx_ring_buf){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return tx_queue_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HWCDC::availableForWrite(void)
|
||||||
|
{
|
||||||
|
if(tx_ring_buf == NULL || tx_lock == NULL){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t a = xRingbufferGetCurFreeSize(tx_ring_buf);
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HWCDC::write(const uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
if(buffer == NULL || size == 0 || tx_ring_buf == NULL || tx_lock == NULL){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t max_size = xRingbufferGetMaxItemSize(tx_ring_buf);
|
||||||
|
size_t space = xRingbufferGetCurFreeSize(tx_ring_buf);
|
||||||
|
size_t to_send = size, so_far = 0;
|
||||||
|
|
||||||
|
if(space > size){
|
||||||
|
space = size;
|
||||||
|
}
|
||||||
|
// Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
|
||||||
|
if(xRingbufferSend(tx_ring_buf, (void*) (buffer), space, 0) != pdTRUE){
|
||||||
|
size = 0;
|
||||||
|
} else {
|
||||||
|
to_send -= space;
|
||||||
|
so_far += space;
|
||||||
|
// Now trigger the ISR to read data from the ring buffer.
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
|
||||||
|
while(to_send){
|
||||||
|
if(max_size > to_send){
|
||||||
|
max_size = to_send;
|
||||||
|
}
|
||||||
|
// Blocking method, Sending data to ringbuffer, and handle the data in ISR.
|
||||||
|
if(xRingbufferSend(tx_ring_buf, (void*) (buffer+so_far), max_size, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE){
|
||||||
|
size = so_far;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
so_far += max_size;
|
||||||
|
to_send -= max_size;
|
||||||
|
// Now trigger the ISR to read data from the ring buffer.
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HWCDC::write(uint8_t c)
|
||||||
|
{
|
||||||
|
return write(&c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::flush(void)
|
||||||
|
{
|
||||||
|
if(tx_ring_buf == NULL || tx_lock == NULL){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UBaseType_t uxItemsWaiting = 0;
|
||||||
|
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
|
||||||
|
if(uxItemsWaiting){
|
||||||
|
// Now trigger the ISR to read data from the ring buffer.
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
}
|
||||||
|
while(uxItemsWaiting){
|
||||||
|
delay(5);
|
||||||
|
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
|
||||||
|
}
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* READING
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t HWCDC::setRxBufferSize(size_t rx_queue_len){
|
||||||
|
if(rx_queue){
|
||||||
|
vQueueDelete(rx_queue);
|
||||||
|
rx_queue = NULL;
|
||||||
|
}
|
||||||
|
if(!rx_queue_len){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
rx_queue = xQueueCreate(rx_queue_len, sizeof(uint8_t));
|
||||||
|
if(!rx_queue){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return rx_queue_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HWCDC::available(void)
|
||||||
|
{
|
||||||
|
if(rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return uxQueueMessagesWaiting(rx_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HWCDC::peek(void)
|
||||||
|
{
|
||||||
|
if(rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c;
|
||||||
|
if(xQueuePeek(rx_queue, &c, 0)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HWCDC::read(void)
|
||||||
|
{
|
||||||
|
if(rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c = 0;
|
||||||
|
if(xQueueReceive(rx_queue, &c, 0)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HWCDC::read(uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
if(rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c = 0;
|
||||||
|
size_t count = 0;
|
||||||
|
while(count < size && xQueueReceive(rx_queue, &c, 0)){
|
||||||
|
buffer[count++] = c;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEBUG
|
||||||
|
*/
|
||||||
|
|
||||||
|
void HWCDC::setDebugOutput(bool en)
|
||||||
|
{
|
||||||
|
if(en) {
|
||||||
|
uartSetDebug(NULL);
|
||||||
|
ets_install_putc1((void (*)(char)) &cdc0_write_char);
|
||||||
|
} else {
|
||||||
|
ets_install_putc1(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINO_USB_MODE
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT//Serial used for USB CDC
|
||||||
|
HWCDC Serial;
|
||||||
|
#else
|
||||||
|
HWCDC USBSerial;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_CDC_ENABLED */
|
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "Stream.h"
|
||||||
|
|
||||||
|
ESP_EVENT_DECLARE_BASE(ARDUINO_HW_CDC_EVENTS);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARDUINO_HW_CDC_ANY_EVENT = ESP_EVENT_ANY_ID,
|
||||||
|
ARDUINO_HW_CDC_CONNECTED_EVENT = 0,
|
||||||
|
ARDUINO_HW_CDC_BUS_RESET_EVENT,
|
||||||
|
ARDUINO_HW_CDC_RX_EVENT,
|
||||||
|
ARDUINO_HW_CDC_TX_EVENT,
|
||||||
|
ARDUINO_HW_CDC_MAX_EVENT,
|
||||||
|
} arduino_hw_cdc_event_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
size_t len;
|
||||||
|
} rx;
|
||||||
|
struct {
|
||||||
|
size_t len;
|
||||||
|
} tx;
|
||||||
|
} arduino_hw_cdc_event_data_t;
|
||||||
|
|
||||||
|
class HWCDC: public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HWCDC();
|
||||||
|
~HWCDC();
|
||||||
|
|
||||||
|
void onEvent(esp_event_handler_t callback);
|
||||||
|
void onEvent(arduino_hw_cdc_event_t event, esp_event_handler_t callback);
|
||||||
|
|
||||||
|
size_t setRxBufferSize(size_t);
|
||||||
|
size_t setTxBufferSize(size_t);
|
||||||
|
void setTxTimeoutMs(uint32_t timeout);
|
||||||
|
void begin(unsigned long baud=0);
|
||||||
|
void end();
|
||||||
|
|
||||||
|
int available(void);
|
||||||
|
int availableForWrite(void);
|
||||||
|
int peek(void);
|
||||||
|
int read(void);
|
||||||
|
size_t read(uint8_t *buffer, size_t size);
|
||||||
|
size_t write(uint8_t);
|
||||||
|
size_t write(const uint8_t *buffer, size_t size);
|
||||||
|
void flush(void);
|
||||||
|
|
||||||
|
inline size_t read(char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return read((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
inline size_t write(const char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
inline size_t write(const char * s)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) s, strlen(s));
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
operator bool() const;
|
||||||
|
void setDebugOutput(bool);
|
||||||
|
uint32_t baudRate(){return 115200;}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#if ARDUINO_USB_MODE
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT//Serial used for USB CDC
|
||||||
|
extern HWCDC Serial;
|
||||||
|
#else
|
||||||
|
extern HWCDC USBSerial;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_IDF_TARGET_ESP32C3 */
|
@ -0,0 +1,623 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
#include "io_pin_remap.h"
|
||||||
|
#include "HardwareSerial.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "driver/uart.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#ifndef ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE
|
||||||
|
#define ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE 2048
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINO_SERIAL_EVENT_TASK_PRIORITY
|
||||||
|
#define ARDUINO_SERIAL_EVENT_TASK_PRIORITY (configMAX_PRIORITIES-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE
|
||||||
|
#define ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SOC_RX0
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define SOC_RX0 3
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define SOC_RX0 44
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#define SOC_RX0 20
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SOC_TX0
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define SOC_TX0 1
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define SOC_TX0 43
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#define SOC_TX0 21
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void serialEvent(void) __attribute__((weak));
|
||||||
|
void serialEvent(void) {}
|
||||||
|
|
||||||
|
#if SOC_UART_NUM > 1
|
||||||
|
|
||||||
|
#ifndef RX1
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define RX1 9
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#define RX1 18
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#define RX1 18
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define RX1 15
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TX1
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define TX1 10
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#define TX1 17
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#define TX1 19
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define TX1 16
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void serialEvent1(void) __attribute__((weak));
|
||||||
|
void serialEvent1(void) {}
|
||||||
|
#endif /* SOC_UART_NUM > 1 */
|
||||||
|
|
||||||
|
#if SOC_UART_NUM > 2
|
||||||
|
#ifndef RX2
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define RX2 16
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define RX2 19
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TX2
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define TX2 17
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define TX2 20
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void serialEvent2(void) __attribute__((weak));
|
||||||
|
void serialEvent2(void) {}
|
||||||
|
#endif /* SOC_UART_NUM > 2 */
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT //Serial used for USB CDC
|
||||||
|
HardwareSerial Serial0(0);
|
||||||
|
#else
|
||||||
|
HardwareSerial Serial(0);
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 1
|
||||||
|
HardwareSerial Serial1(1);
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 2
|
||||||
|
HardwareSerial Serial2(2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void serialEventRun(void)
|
||||||
|
{
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT //Serial used for USB CDC
|
||||||
|
if(Serial0.available()) serialEvent();
|
||||||
|
#else
|
||||||
|
if(Serial.available()) serialEvent();
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 1
|
||||||
|
if(Serial1.available()) serialEvent1();
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 2
|
||||||
|
if(Serial2.available()) serialEvent2();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
#define HSERIAL_MUTEX_LOCK() do {} while (xSemaphoreTake(_lock, portMAX_DELAY) != pdPASS)
|
||||||
|
#define HSERIAL_MUTEX_UNLOCK() xSemaphoreGive(_lock)
|
||||||
|
#else
|
||||||
|
#define HSERIAL_MUTEX_LOCK()
|
||||||
|
#define HSERIAL_MUTEX_UNLOCK()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
HardwareSerial::HardwareSerial(int uart_nr) :
|
||||||
|
_uart_nr(uart_nr),
|
||||||
|
_uart(NULL),
|
||||||
|
_rxBufferSize(256),
|
||||||
|
_txBufferSize(0),
|
||||||
|
_onReceiveCB(NULL),
|
||||||
|
_onReceiveErrorCB(NULL),
|
||||||
|
_onReceiveTimeout(false),
|
||||||
|
_rxTimeout(2),
|
||||||
|
_rxFIFOFull(0),
|
||||||
|
_eventTask(NULL)
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
,_lock(NULL)
|
||||||
|
#endif
|
||||||
|
,_rxPin(-1)
|
||||||
|
,_txPin(-1)
|
||||||
|
,_ctsPin(-1)
|
||||||
|
,_rtsPin(-1)
|
||||||
|
{
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(_lock == NULL){
|
||||||
|
_lock = xSemaphoreCreateMutex();
|
||||||
|
if(_lock == NULL){
|
||||||
|
log_e("xSemaphoreCreateMutex failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
HardwareSerial::~HardwareSerial()
|
||||||
|
{
|
||||||
|
end();
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(_lock != NULL){
|
||||||
|
vSemaphoreDelete(_lock);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HardwareSerial::_createEventTask(void *args)
|
||||||
|
{
|
||||||
|
// Creating UART event Task
|
||||||
|
xTaskCreateUniversal(_uartEventTask, "uart_event_task", ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE, this, ARDUINO_SERIAL_EVENT_TASK_PRIORITY, &_eventTask, ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE);
|
||||||
|
if (_eventTask == NULL) {
|
||||||
|
log_e(" -- UART%d Event Task not Created!", _uart_nr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::_destroyEventTask(void)
|
||||||
|
{
|
||||||
|
if (_eventTask != NULL) {
|
||||||
|
vTaskDelete(_eventTask);
|
||||||
|
_eventTask = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::onReceiveError(OnReceiveErrorCb function)
|
||||||
|
{
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
// function may be NULL to cancel onReceive() from its respective task
|
||||||
|
_onReceiveErrorCB = function;
|
||||||
|
// this can be called after Serial.begin(), therefore it shall create the event task
|
||||||
|
if (function != NULL && _uart != NULL && _eventTask == NULL) {
|
||||||
|
_createEventTask(this);
|
||||||
|
}
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout)
|
||||||
|
{
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
// function may be NULL to cancel onReceive() from its respective task
|
||||||
|
_onReceiveCB = function;
|
||||||
|
|
||||||
|
// setting the callback to NULL will just disable it
|
||||||
|
if (_onReceiveCB != NULL) {
|
||||||
|
// When Rx timeout is Zero (disabled), there is only one possible option that is callback when FIFO reaches 120 bytes
|
||||||
|
_onReceiveTimeout = _rxTimeout > 0 ? onlyOnTimeout : false;
|
||||||
|
|
||||||
|
// in case that onReceive() shall work only with RX Timeout, FIFO shall be high
|
||||||
|
// this is a work around for an IDF issue with events and low FIFO Full value (< 3)
|
||||||
|
if (_onReceiveTimeout) {
|
||||||
|
uartSetRxFIFOFull(_uart, 120);
|
||||||
|
log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method can be called after Serial.begin(), therefore it shall create the event task
|
||||||
|
if (_uart != NULL && _eventTask == NULL) {
|
||||||
|
_createEventTask(this); // Create event task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function allow the user to define how many bytes will trigger an Interrupt that will copy RX FIFO to the internal RX Ringbuffer
|
||||||
|
// ISR will also move data from FIFO to RX Ringbuffer after a RX Timeout defined in HardwareSerial::setRxTimeout(uint8_t symbols_timeout)
|
||||||
|
// A low value of FIFO Full bytes will consume more CPU time within the ISR
|
||||||
|
// A high value of FIFO Full bytes will make the application wait longer to have byte available for the Stkech in a streaming scenario
|
||||||
|
// Both RX FIFO Full and RX Timeout may affect when onReceive() will be called
|
||||||
|
bool HardwareSerial::setRxFIFOFull(uint8_t fifoBytes)
|
||||||
|
{
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
// in case that onReceive() shall work only with RX Timeout, FIFO shall be high
|
||||||
|
// this is a work around for an IDF issue with events and low FIFO Full value (< 3)
|
||||||
|
if (_onReceiveCB != NULL && _onReceiveTimeout) {
|
||||||
|
fifoBytes = 120;
|
||||||
|
log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes.");
|
||||||
|
}
|
||||||
|
bool retCode = uartSetRxFIFOFull(_uart, fifoBytes); // Set new timeout
|
||||||
|
if (fifoBytes > 0 && fifoBytes < SOC_UART_FIFO_LEN - 1) _rxFIFOFull = fifoBytes;
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
return retCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// timout is calculates in time to receive UART symbols at the UART baudrate.
|
||||||
|
// the estimation is about 11 bits per symbol (SERIAL_8N1)
|
||||||
|
bool HardwareSerial::setRxTimeout(uint8_t symbols_timeout)
|
||||||
|
{
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
|
||||||
|
// Zero disables timeout, thus, onReceive callback will only be called when RX FIFO reaches 120 bytes
|
||||||
|
// Any non-zero value will activate onReceive callback based on UART baudrate with about 11 bits per symbol
|
||||||
|
_rxTimeout = symbols_timeout;
|
||||||
|
if (!symbols_timeout) _onReceiveTimeout = false; // only when RX timeout is disabled, we also must disable this flag
|
||||||
|
|
||||||
|
bool retCode = uartSetRxTimeout(_uart, _rxTimeout); // Set new timeout
|
||||||
|
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
return retCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::eventQueueReset()
|
||||||
|
{
|
||||||
|
QueueHandle_t uartEventQueue = NULL;
|
||||||
|
if (_uart == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uartGetEventQueue(_uart, &uartEventQueue);
|
||||||
|
if (uartEventQueue != NULL) {
|
||||||
|
xQueueReset(uartEventQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::_uartEventTask(void *args)
|
||||||
|
{
|
||||||
|
HardwareSerial *uart = (HardwareSerial *)args;
|
||||||
|
uart_event_t event;
|
||||||
|
QueueHandle_t uartEventQueue = NULL;
|
||||||
|
uartGetEventQueue(uart->_uart, &uartEventQueue);
|
||||||
|
if (uartEventQueue != NULL) {
|
||||||
|
for(;;) {
|
||||||
|
//Waiting for UART event.
|
||||||
|
if(xQueueReceive(uartEventQueue, (void * )&event, (portTickType)portMAX_DELAY)) {
|
||||||
|
hardwareSerial_error_t currentErr = UART_NO_ERROR;
|
||||||
|
switch(event.type) {
|
||||||
|
case UART_DATA:
|
||||||
|
if(uart->_onReceiveCB && uart->available() > 0 &&
|
||||||
|
((uart->_onReceiveTimeout && event.timeout_flag) || !uart->_onReceiveTimeout) )
|
||||||
|
uart->_onReceiveCB();
|
||||||
|
break;
|
||||||
|
case UART_FIFO_OVF:
|
||||||
|
log_w("UART%d FIFO Overflow. Consider adding Hardware Flow Control to your Application.", uart->_uart_nr);
|
||||||
|
currentErr = UART_FIFO_OVF_ERROR;
|
||||||
|
break;
|
||||||
|
case UART_BUFFER_FULL:
|
||||||
|
log_w("UART%d Buffer Full. Consider increasing your buffer size of your Application.", uart->_uart_nr);
|
||||||
|
currentErr = UART_BUFFER_FULL_ERROR;
|
||||||
|
break;
|
||||||
|
case UART_BREAK:
|
||||||
|
log_w("UART%d RX break.", uart->_uart_nr);
|
||||||
|
currentErr = UART_BREAK_ERROR;
|
||||||
|
break;
|
||||||
|
case UART_PARITY_ERR:
|
||||||
|
log_w("UART%d parity error.", uart->_uart_nr);
|
||||||
|
currentErr = UART_PARITY_ERROR;
|
||||||
|
break;
|
||||||
|
case UART_FRAME_ERR:
|
||||||
|
log_w("UART%d frame error.", uart->_uart_nr);
|
||||||
|
currentErr = UART_FRAME_ERROR;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log_w("UART%d unknown event type %d.", uart->_uart_nr, event.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (currentErr != UART_NO_ERROR) {
|
||||||
|
if(uart->_onReceiveErrorCB) uart->_onReceiveErrorCB(currentErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert, unsigned long timeout_ms, uint8_t rxfifo_full_thrhd)
|
||||||
|
{
|
||||||
|
if(0 > _uart_nr || _uart_nr >= SOC_UART_NUM) {
|
||||||
|
log_e("Serial number is invalid, please use numers from 0 to %u", SOC_UART_NUM - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(_lock == NULL){
|
||||||
|
log_e("MUTEX Lock failed. Can't begin.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
// First Time or after end() --> set default Pins
|
||||||
|
if (!uartIsDriverInstalled(_uart)) {
|
||||||
|
switch (_uart_nr) {
|
||||||
|
case UART_NUM_0:
|
||||||
|
if (rxPin < 0 && txPin < 0) {
|
||||||
|
rxPin = SOC_RX0;
|
||||||
|
txPin = SOC_TX0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#if SOC_UART_NUM > 1 // may save some flash bytes...
|
||||||
|
case UART_NUM_1:
|
||||||
|
if (rxPin < 0 && txPin < 0) {
|
||||||
|
rxPin = RX1;
|
||||||
|
txPin = TX1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 2 // may save some flash bytes...
|
||||||
|
case UART_NUM_2:
|
||||||
|
if (rxPin < 0 && txPin < 0) {
|
||||||
|
rxPin = RX2;
|
||||||
|
txPin = TX2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map logical pins to GPIO numbers
|
||||||
|
rxPin = digitalPinToGPIONumber(rxPin);
|
||||||
|
txPin = digitalPinToGPIONumber(txPin);
|
||||||
|
|
||||||
|
if(_uart) {
|
||||||
|
// in this case it is a begin() over a previous begin() - maybe to change baud rate
|
||||||
|
// thus do not disable debug output
|
||||||
|
end(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDF UART driver keeps Pin setting on restarting. Negative Pin number will keep it unmodified.
|
||||||
|
_uart = uartBegin(_uart_nr, baud ? baud : 9600, config, rxPin, txPin, _rxBufferSize, _txBufferSize, invert, rxfifo_full_thrhd);
|
||||||
|
if (!baud) {
|
||||||
|
// using baud rate as zero, forces it to try to detect the current baud rate in place
|
||||||
|
uartStartDetectBaudrate(_uart);
|
||||||
|
time_t startMillis = millis();
|
||||||
|
unsigned long detectedBaudRate = 0;
|
||||||
|
while(millis() - startMillis < timeout_ms && !(detectedBaudRate = uartDetectBaudrate(_uart))) {
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
end(false);
|
||||||
|
|
||||||
|
if(detectedBaudRate) {
|
||||||
|
delay(100); // Give some time...
|
||||||
|
_uart = uartBegin(_uart_nr, detectedBaudRate, config, rxPin, txPin, _rxBufferSize, _txBufferSize, invert, rxfifo_full_thrhd);
|
||||||
|
} else {
|
||||||
|
log_e("Could not detect baudrate. Serial data at the port must be present within the timeout for detection to be possible");
|
||||||
|
_uart = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// create a task to deal with Serial Events when, for example, calling begin() twice to change the baudrate,
|
||||||
|
// or when setting the callback before calling begin()
|
||||||
|
if (_uart != NULL && (_onReceiveCB != NULL || _onReceiveErrorCB != NULL) && _eventTask == NULL) {
|
||||||
|
_createEventTask(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set UART RX timeout
|
||||||
|
uartSetRxTimeout(_uart, _rxTimeout);
|
||||||
|
|
||||||
|
// Set UART FIFO Full depending on the baud rate.
|
||||||
|
// Lower baud rates will force to emulate byte-by-byte reading
|
||||||
|
// Higher baud rates will keep IDF default of 120 bytes for FIFO FULL Interrupt
|
||||||
|
// It can also be changed by the application at any time
|
||||||
|
if (!_rxFIFOFull) { // it has not being changed before calling begin()
|
||||||
|
// set a default FIFO Full value for the IDF driver
|
||||||
|
uint8_t fifoFull = 1;
|
||||||
|
if (baud > 57600 || (_onReceiveCB != NULL && _onReceiveTimeout)) {
|
||||||
|
fifoFull = 120;
|
||||||
|
}
|
||||||
|
uartSetRxFIFOFull(_uart, fifoFull);
|
||||||
|
_rxFIFOFull = fifoFull;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rxPin = rxPin;
|
||||||
|
_txPin = txPin;
|
||||||
|
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::updateBaudRate(unsigned long baud)
|
||||||
|
{
|
||||||
|
uartSetBaudRate(_uart, baud);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::end(bool fullyTerminate)
|
||||||
|
{
|
||||||
|
// default Serial.end() will completely disable HardwareSerial,
|
||||||
|
// including any tasks or debug message channel (log_x()) - but not for IDF log messages!
|
||||||
|
if(fullyTerminate) {
|
||||||
|
_onReceiveCB = NULL;
|
||||||
|
_onReceiveErrorCB = NULL;
|
||||||
|
if (uartGetDebug() == _uart_nr) {
|
||||||
|
uartSetDebug(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_rxFIFOFull = 0;
|
||||||
|
|
||||||
|
uartDetachPins(_uart, _rxPin, _txPin, _ctsPin, _rtsPin);
|
||||||
|
_rxPin = _txPin = _ctsPin = _rtsPin = -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
delay(10);
|
||||||
|
uartEnd(_uart);
|
||||||
|
_uart = 0;
|
||||||
|
_destroyEventTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::setDebugOutput(bool en)
|
||||||
|
{
|
||||||
|
if(_uart == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(en) {
|
||||||
|
uartSetDebug(_uart);
|
||||||
|
} else {
|
||||||
|
if(uartGetDebug() == _uart_nr) {
|
||||||
|
uartSetDebug(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int HardwareSerial::available(void)
|
||||||
|
{
|
||||||
|
return uartAvailable(_uart);
|
||||||
|
}
|
||||||
|
int HardwareSerial::availableForWrite(void)
|
||||||
|
{
|
||||||
|
return uartAvailableForWrite(_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HardwareSerial::peek(void)
|
||||||
|
{
|
||||||
|
if (available()) {
|
||||||
|
return uartPeek(_uart);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HardwareSerial::read(void)
|
||||||
|
{
|
||||||
|
uint8_t c = 0;
|
||||||
|
if (uartReadBytes(_uart, &c, 1, 0) == 1) {
|
||||||
|
return c;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read characters into buffer
|
||||||
|
// terminates if size characters have been read, or no further are pending
|
||||||
|
// returns the number of characters placed in the buffer
|
||||||
|
// the buffer is NOT null terminated.
|
||||||
|
size_t HardwareSerial::read(uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
return uartReadBytes(_uart, buffer, size, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides Stream::readBytes() to be faster using IDF
|
||||||
|
size_t HardwareSerial::readBytes(uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
return uartReadBytes(_uart, buffer, length, (uint32_t)getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::flush(void)
|
||||||
|
{
|
||||||
|
uartFlush(_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::flush(bool txOnly)
|
||||||
|
{
|
||||||
|
uartFlushTxOnly(_uart, txOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HardwareSerial::write(uint8_t c)
|
||||||
|
{
|
||||||
|
uartWrite(_uart, c);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HardwareSerial::write(const uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
uartWriteBuf(_uart, buffer, size);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
uint32_t HardwareSerial::baudRate()
|
||||||
|
|
||||||
|
{
|
||||||
|
return uartGetBaudRate(_uart);
|
||||||
|
}
|
||||||
|
HardwareSerial::operator bool() const
|
||||||
|
{
|
||||||
|
return uartIsDriverInstalled(_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::setRxInvert(bool invert)
|
||||||
|
{
|
||||||
|
uartSetRxInvert(_uart, invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// negative Pin value will keep it unmodified
|
||||||
|
bool HardwareSerial::setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t rtsPin)
|
||||||
|
{
|
||||||
|
if(_uart == NULL) {
|
||||||
|
log_e("setPins() shall be called after begin() - nothing done\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// map logical pins to GPIO numbers
|
||||||
|
rxPin = digitalPinToGPIONumber(rxPin);
|
||||||
|
txPin = digitalPinToGPIONumber(txPin);
|
||||||
|
ctsPin = digitalPinToGPIONumber(ctsPin);
|
||||||
|
rtsPin = digitalPinToGPIONumber(rtsPin);
|
||||||
|
|
||||||
|
// uartSetPins() checks if pins are valid for each function and for the SoC
|
||||||
|
bool retCode = uartSetPins(_uart, rxPin, txPin, ctsPin, rtsPin);
|
||||||
|
if (retCode) {
|
||||||
|
_txPin = _txPin >= 0 ? txPin : _txPin;
|
||||||
|
_rxPin = _rxPin >= 0 ? rxPin : _rxPin;
|
||||||
|
_rtsPin = _rtsPin >= 0 ? rtsPin : _rtsPin;
|
||||||
|
_ctsPin = _ctsPin >= 0 ? ctsPin : _ctsPin;
|
||||||
|
} else {
|
||||||
|
log_e("Error when setting Serial port Pins. Invalid Pin.\n");
|
||||||
|
}
|
||||||
|
return retCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enables or disables Hardware Flow Control using RTS and/or CTS pins (must use setAllPins() before)
|
||||||
|
bool HardwareSerial::setHwFlowCtrlMode(uint8_t mode, uint8_t threshold)
|
||||||
|
{
|
||||||
|
return uartSetHwFlowCtrlMode(_uart, mode, threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the uart mode in the esp32 uart for use with RS485 modes (HwFlowCtrl must be disabled and RTS pin set)
|
||||||
|
bool HardwareSerial::setMode(uint8_t mode)
|
||||||
|
{
|
||||||
|
return uartSetMode(_uart, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HardwareSerial::setRxBufferSize(size_t new_size) {
|
||||||
|
|
||||||
|
if (_uart) {
|
||||||
|
log_e("RX Buffer can't be resized when Serial is already running.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_size <= SOC_UART_FIFO_LEN) {
|
||||||
|
log_e("RX Buffer must be higher than %d.\n", SOC_UART_FIFO_LEN); // ESP32, S2, S3 and C3 means higher than 128
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rxBufferSize = new_size;
|
||||||
|
return _rxBufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HardwareSerial::setTxBufferSize(size_t new_size) {
|
||||||
|
|
||||||
|
if (_uart) {
|
||||||
|
log_e("TX Buffer can't be resized when Serial is already running.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_size <= SOC_UART_FIFO_LEN) {
|
||||||
|
log_e("TX Buffer must be higher than %d.\n", SOC_UART_FIFO_LEN); // ESP32, S2, S3 and C3 means higher than 128
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_txBufferSize = new_size;
|
||||||
|
return _txBufferSize;
|
||||||
|
}
|
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
HardwareSerial.h - Hardware serial library for Wiring
|
||||||
|
Copyright (c) 2006 Nicholas Zambetti. All right reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
Modified 28 September 2010 by Mark Sproul
|
||||||
|
Modified 14 August 2012 by Alarus
|
||||||
|
Modified 3 December 2013 by Matthijs Kooijman
|
||||||
|
Modified 18 December 2014 by Ivan Grokhotkov (esp8266 platform support)
|
||||||
|
Modified 31 March 2015 by Markus Sattler (rewrite the code for UART0 + UART1 support in ESP8266)
|
||||||
|
Modified 25 April 2015 by Thomas Flayols (add configuration different from 8N1 in ESP8266)
|
||||||
|
Modified 13 October 2018 by Jeroen Döll (add baudrate detection)
|
||||||
|
Baudrate detection example usage (detection on Serial1):
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(100);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Serial1.begin(0, SERIAL_8N1, -1, -1, true, 11000UL); // Passing 0 for baudrate to detect it, the last parameter is a timeout in ms
|
||||||
|
|
||||||
|
unsigned long detectedBaudRate = Serial1.baudRate();
|
||||||
|
if(detectedBaudRate) {
|
||||||
|
Serial.printf("Detected baudrate is %lu\n", detectedBaudRate);
|
||||||
|
} else {
|
||||||
|
Serial.println("No baudrate detected, Serial1 will not work!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pay attention: the baudrate returned by baudRate() may be rounded, eg 115200 returns 115201
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HardwareSerial_h
|
||||||
|
#define HardwareSerial_h
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <functional>
|
||||||
|
#include "Stream.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "HWCDC.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UART_NO_ERROR,
|
||||||
|
UART_BREAK_ERROR,
|
||||||
|
UART_BUFFER_FULL_ERROR,
|
||||||
|
UART_FIFO_OVF_ERROR,
|
||||||
|
UART_FRAME_ERROR,
|
||||||
|
UART_PARITY_ERROR
|
||||||
|
} hardwareSerial_error_t;
|
||||||
|
|
||||||
|
typedef std::function<void(void)> OnReceiveCb;
|
||||||
|
typedef std::function<void(hardwareSerial_error_t)> OnReceiveErrorCb;
|
||||||
|
|
||||||
|
class HardwareSerial: public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HardwareSerial(int uart_nr);
|
||||||
|
~HardwareSerial();
|
||||||
|
|
||||||
|
// setRxTimeout sets the timeout after which onReceive callback will be called (after receiving data, it waits for this time of UART rx inactivity to call the callback fnc)
|
||||||
|
// param symbols_timeout defines a timeout threshold in uart symbol periods. Setting 0 symbol timeout disables the callback call by timeout.
|
||||||
|
// Maximum timeout setting is calculacted automatically by IDF. If set above the maximum, it is ignored and an error is printed on Serial0 (check console).
|
||||||
|
// Examples: Maximum for 11 bits symbol is 92 (SERIAL_8N2, SERIAL_8E1, SERIAL_8O1, etc), Maximum for 10 bits symbol is 101 (SERIAL_8N1).
|
||||||
|
// For example symbols_timeout=1 defines a timeout equal to transmission time of one symbol (~11 bit) on current baudrate.
|
||||||
|
// For a baudrate of 9600, SERIAL_8N1 (10 bit symbol) and symbols_timeout = 3, the timeout would be 3 / (9600 / 10) = 3.125 ms
|
||||||
|
bool setRxTimeout(uint8_t symbols_timeout);
|
||||||
|
|
||||||
|
// setRxFIFOFull(uint8_t fifoBytes) will set the number of bytes that will trigger UART_INTR_RXFIFO_FULL interrupt and fill up RxRingBuffer
|
||||||
|
// This affects some functions such as Serial::available() and Serial.read() because, in a UART flow of receiving data, Serial internal
|
||||||
|
// RxRingBuffer will be filled only after these number of bytes arrive or a RX Timeout happens.
|
||||||
|
// This parameter can be set to 1 in order to receive byte by byte, but it will also consume more CPU time as the ISR will be activates often.
|
||||||
|
bool setRxFIFOFull(uint8_t fifoBytes);
|
||||||
|
|
||||||
|
// onReceive will setup a callback that will be called whenever an UART interruption occurs (UART_INTR_RXFIFO_FULL or UART_INTR_RXFIFO_TOUT)
|
||||||
|
// UART_INTR_RXFIFO_FULL interrupt triggers at UART_FULL_THRESH_DEFAULT bytes received (defined as 120 bytes by default in IDF)
|
||||||
|
// UART_INTR_RXFIFO_TOUT interrupt triggers at UART_TOUT_THRESH_DEFAULT symbols passed without any reception (defined as 10 symbos by default in IDF)
|
||||||
|
// onlyOnTimeout parameter will define how onReceive will behave:
|
||||||
|
// Default: true -- The callback will only be called when RX Timeout happens.
|
||||||
|
// Whole stream of bytes will be ready for being read on the callback function at once.
|
||||||
|
// This option may lead to Rx Overflow depending on the Rx Buffer Size and number of bytes received in the streaming
|
||||||
|
// false -- The callback will be called when FIFO reaches 120 bytes and also on RX Timeout.
|
||||||
|
// The stream of incommig bytes will be "split" into blocks of 120 bytes on each callback.
|
||||||
|
// This option avoid any sort of Rx Overflow, but leaves the UART packet reassembling work to the Application.
|
||||||
|
void onReceive(OnReceiveCb function, bool onlyOnTimeout = false);
|
||||||
|
|
||||||
|
// onReceive will be called on error events (see hardwareSerial_error_t)
|
||||||
|
void onReceiveError(OnReceiveErrorCb function);
|
||||||
|
|
||||||
|
// eventQueueReset clears all events in the queue (the events that trigger onReceive and onReceiveError) - maybe usefull in some use cases
|
||||||
|
void eventQueueReset();
|
||||||
|
|
||||||
|
void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1, bool invert=false, unsigned long timeout_ms = 20000UL, uint8_t rxfifo_full_thrhd = 112);
|
||||||
|
void end(bool fullyTerminate = true);
|
||||||
|
void updateBaudRate(unsigned long baud);
|
||||||
|
int available(void);
|
||||||
|
int availableForWrite(void);
|
||||||
|
int peek(void);
|
||||||
|
int read(void);
|
||||||
|
size_t read(uint8_t *buffer, size_t size);
|
||||||
|
inline size_t read(char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return read((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
// Overrides Stream::readBytes() to be faster using IDF
|
||||||
|
size_t readBytes(uint8_t *buffer, size_t length);
|
||||||
|
size_t readBytes(char *buffer, size_t length)
|
||||||
|
{
|
||||||
|
return readBytes((uint8_t *) buffer, length);
|
||||||
|
}
|
||||||
|
void flush(void);
|
||||||
|
void flush( bool txOnly);
|
||||||
|
size_t write(uint8_t);
|
||||||
|
size_t write(const uint8_t *buffer, size_t size);
|
||||||
|
inline size_t write(const char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
inline size_t write(const char * s)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) s, strlen(s));
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
uint32_t baudRate();
|
||||||
|
operator bool() const;
|
||||||
|
|
||||||
|
void setDebugOutput(bool);
|
||||||
|
|
||||||
|
void setRxInvert(bool);
|
||||||
|
|
||||||
|
// Negative Pin Number will keep it unmodified, thus this function can set individual pins
|
||||||
|
// SetPins shall be called after Serial begin()
|
||||||
|
bool setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin = -1, int8_t rtsPin = -1);
|
||||||
|
// Enables or disables Hardware Flow Control using RTS and/or CTS pins (must use setAllPins() before)
|
||||||
|
bool setHwFlowCtrlMode(uint8_t mode = HW_FLOWCTRL_CTS_RTS, uint8_t threshold = 64); // 64 is half FIFO Length
|
||||||
|
// Used to set RS485 modes such as UART_MODE_RS485_HALF_DUPLEX for Auto RTS function on ESP32
|
||||||
|
bool setMode(uint8_t mode);
|
||||||
|
size_t setRxBufferSize(size_t new_size);
|
||||||
|
size_t setTxBufferSize(size_t new_size);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int _uart_nr;
|
||||||
|
uart_t* _uart;
|
||||||
|
size_t _rxBufferSize;
|
||||||
|
size_t _txBufferSize;
|
||||||
|
OnReceiveCb _onReceiveCB;
|
||||||
|
OnReceiveErrorCb _onReceiveErrorCB;
|
||||||
|
// _onReceive and _rxTimeout have be consistent when timeout is disabled
|
||||||
|
bool _onReceiveTimeout;
|
||||||
|
uint8_t _rxTimeout, _rxFIFOFull;
|
||||||
|
TaskHandle_t _eventTask;
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
SemaphoreHandle_t _lock;
|
||||||
|
#endif
|
||||||
|
int8_t _rxPin, _txPin, _ctsPin, _rtsPin;
|
||||||
|
|
||||||
|
void _createEventTask(void *args);
|
||||||
|
void _destroyEventTask(void);
|
||||||
|
static void _uartEventTask(void *args);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern void serialEventRun(void) __attribute__((weak));
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
|
||||||
|
#ifndef ARDUINO_USB_CDC_ON_BOOT
|
||||||
|
#define ARDUINO_USB_CDC_ON_BOOT 0
|
||||||
|
#endif
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT //Serial used for USB CDC
|
||||||
|
#if !ARDUINO_USB_MODE
|
||||||
|
#include "USB.h"
|
||||||
|
#include "USBCDC.h"
|
||||||
|
#endif
|
||||||
|
extern HardwareSerial Serial0;
|
||||||
|
#else
|
||||||
|
extern HardwareSerial Serial;
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 1
|
||||||
|
extern HardwareSerial Serial1;
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 2
|
||||||
|
extern HardwareSerial Serial2;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // HardwareSerial_h
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* base64.cpp
|
||||||
|
*
|
||||||
|
* Created on: 09.12.2015
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Markus Sattler. All rights reserved.
|
||||||
|
* This file is part of the ESP31B core for Arduino.
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2.1 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
extern "C" {
|
||||||
|
#include "libb64/cdecode.h"
|
||||||
|
#include "libb64/cencode.h"
|
||||||
|
}
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert input data to base64
|
||||||
|
* @param data const uint8_t *
|
||||||
|
* @param length size_t
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String base64::encode(const uint8_t * data, size_t length)
|
||||||
|
{
|
||||||
|
size_t size = base64_encode_expected_len(length) + 1;
|
||||||
|
char * buffer = (char *) malloc(size);
|
||||||
|
if(buffer) {
|
||||||
|
base64_encodestate _state;
|
||||||
|
base64_init_encodestate(&_state);
|
||||||
|
int len = base64_encode_block((const char *) &data[0], length, &buffer[0], &_state);
|
||||||
|
len = base64_encode_blockend((buffer + len), &_state);
|
||||||
|
|
||||||
|
String base64 = String(buffer);
|
||||||
|
free(buffer);
|
||||||
|
return base64;
|
||||||
|
}
|
||||||
|
return String("-FAIL-");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert input data to base64
|
||||||
|
* @param text const String&
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String base64::encode(const String& text)
|
||||||
|
{
|
||||||
|
return base64::encode((uint8_t *) text.c_str(), text.length());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef CORE_BASE64_H_
|
||||||
|
#define CORE_BASE64_H_
|
||||||
|
|
||||||
|
class base64
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static String encode(const uint8_t * data, size_t length);
|
||||||
|
static String encode(const String& text);
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* CORE_BASE64_H_ */
|
@ -0,0 +1,534 @@
|
|||||||
|
/*
|
||||||
|
binary.h - Definitions for binary constants
|
||||||
|
Copyright (c) 2006 David A. Mellis. All right reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef Binary_h
|
||||||
|
#define Binary_h
|
||||||
|
|
||||||
|
#define B0 0
|
||||||
|
#define B00 0
|
||||||
|
#define B000 0
|
||||||
|
#define B0000 0
|
||||||
|
#define B00000 0
|
||||||
|
#define B000000 0
|
||||||
|
#define B0000000 0
|
||||||
|
#define B00000000 0
|
||||||
|
#define B1 1
|
||||||
|
#define B01 1
|
||||||
|
#define B001 1
|
||||||
|
#define B0001 1
|
||||||
|
#define B00001 1
|
||||||
|
#define B000001 1
|
||||||
|
#define B0000001 1
|
||||||
|
#define B00000001 1
|
||||||
|
#define B10 2
|
||||||
|
#define B010 2
|
||||||
|
#define B0010 2
|
||||||
|
#define B00010 2
|
||||||
|
#define B000010 2
|
||||||
|
#define B0000010 2
|
||||||
|
#define B00000010 2
|
||||||
|
#define B11 3
|
||||||
|
#define B011 3
|
||||||
|
#define B0011 3
|
||||||
|
#define B00011 3
|
||||||
|
#define B000011 3
|
||||||
|
#define B0000011 3
|
||||||
|
#define B00000011 3
|
||||||
|
#define B100 4
|
||||||
|
#define B0100 4
|
||||||
|
#define B00100 4
|
||||||
|
#define B000100 4
|
||||||
|
#define B0000100 4
|
||||||
|
#define B00000100 4
|
||||||
|
#define B101 5
|
||||||
|
#define B0101 5
|
||||||
|
#define B00101 5
|
||||||
|
#define B000101 5
|
||||||
|
#define B0000101 5
|
||||||
|
#define B00000101 5
|
||||||
|
#define B110 6
|
||||||
|
#define B0110 6
|
||||||
|
#define B00110 6
|
||||||
|
#define B000110 6
|
||||||
|
#define B0000110 6
|
||||||
|
#define B00000110 6
|
||||||
|
#define B111 7
|
||||||
|
#define B0111 7
|
||||||
|
#define B00111 7
|
||||||
|
#define B000111 7
|
||||||
|
#define B0000111 7
|
||||||
|
#define B00000111 7
|
||||||
|
#define B1000 8
|
||||||
|
#define B01000 8
|
||||||
|
#define B001000 8
|
||||||
|
#define B0001000 8
|
||||||
|
#define B00001000 8
|
||||||
|
#define B1001 9
|
||||||
|
#define B01001 9
|
||||||
|
#define B001001 9
|
||||||
|
#define B0001001 9
|
||||||
|
#define B00001001 9
|
||||||
|
#define B1010 10
|
||||||
|
#define B01010 10
|
||||||
|
#define B001010 10
|
||||||
|
#define B0001010 10
|
||||||
|
#define B00001010 10
|
||||||
|
#define B1011 11
|
||||||
|
#define B01011 11
|
||||||
|
#define B001011 11
|
||||||
|
#define B0001011 11
|
||||||
|
#define B00001011 11
|
||||||
|
#define B1100 12
|
||||||
|
#define B01100 12
|
||||||
|
#define B001100 12
|
||||||
|
#define B0001100 12
|
||||||
|
#define B00001100 12
|
||||||
|
#define B1101 13
|
||||||
|
#define B01101 13
|
||||||
|
#define B001101 13
|
||||||
|
#define B0001101 13
|
||||||
|
#define B00001101 13
|
||||||
|
#define B1110 14
|
||||||
|
#define B01110 14
|
||||||
|
#define B001110 14
|
||||||
|
#define B0001110 14
|
||||||
|
#define B00001110 14
|
||||||
|
#define B1111 15
|
||||||
|
#define B01111 15
|
||||||
|
#define B001111 15
|
||||||
|
#define B0001111 15
|
||||||
|
#define B00001111 15
|
||||||
|
#define B10000 16
|
||||||
|
#define B010000 16
|
||||||
|
#define B0010000 16
|
||||||
|
#define B00010000 16
|
||||||
|
#define B10001 17
|
||||||
|
#define B010001 17
|
||||||
|
#define B0010001 17
|
||||||
|
#define B00010001 17
|
||||||
|
#define B10010 18
|
||||||
|
#define B010010 18
|
||||||
|
#define B0010010 18
|
||||||
|
#define B00010010 18
|
||||||
|
#define B10011 19
|
||||||
|
#define B010011 19
|
||||||
|
#define B0010011 19
|
||||||
|
#define B00010011 19
|
||||||
|
#define B10100 20
|
||||||
|
#define B010100 20
|
||||||
|
#define B0010100 20
|
||||||
|
#define B00010100 20
|
||||||
|
#define B10101 21
|
||||||
|
#define B010101 21
|
||||||
|
#define B0010101 21
|
||||||
|
#define B00010101 21
|
||||||
|
#define B10110 22
|
||||||
|
#define B010110 22
|
||||||
|
#define B0010110 22
|
||||||
|
#define B00010110 22
|
||||||
|
#define B10111 23
|
||||||
|
#define B010111 23
|
||||||
|
#define B0010111 23
|
||||||
|
#define B00010111 23
|
||||||
|
#define B11000 24
|
||||||
|
#define B011000 24
|
||||||
|
#define B0011000 24
|
||||||
|
#define B00011000 24
|
||||||
|
#define B11001 25
|
||||||
|
#define B011001 25
|
||||||
|
#define B0011001 25
|
||||||
|
#define B00011001 25
|
||||||
|
#define B11010 26
|
||||||
|
#define B011010 26
|
||||||
|
#define B0011010 26
|
||||||
|
#define B00011010 26
|
||||||
|
#define B11011 27
|
||||||
|
#define B011011 27
|
||||||
|
#define B0011011 27
|
||||||
|
#define B00011011 27
|
||||||
|
#define B11100 28
|
||||||
|
#define B011100 28
|
||||||
|
#define B0011100 28
|
||||||
|
#define B00011100 28
|
||||||
|
#define B11101 29
|
||||||
|
#define B011101 29
|
||||||
|
#define B0011101 29
|
||||||
|
#define B00011101 29
|
||||||
|
#define B11110 30
|
||||||
|
#define B011110 30
|
||||||
|
#define B0011110 30
|
||||||
|
#define B00011110 30
|
||||||
|
#define B11111 31
|
||||||
|
#define B011111 31
|
||||||
|
#define B0011111 31
|
||||||
|
#define B00011111 31
|
||||||
|
#define B100000 32
|
||||||
|
#define B0100000 32
|
||||||
|
#define B00100000 32
|
||||||
|
#define B100001 33
|
||||||
|
#define B0100001 33
|
||||||
|
#define B00100001 33
|
||||||
|
#define B100010 34
|
||||||
|
#define B0100010 34
|
||||||
|
#define B00100010 34
|
||||||
|
#define B100011 35
|
||||||
|
#define B0100011 35
|
||||||
|
#define B00100011 35
|
||||||
|
#define B100100 36
|
||||||
|
#define B0100100 36
|
||||||
|
#define B00100100 36
|
||||||
|
#define B100101 37
|
||||||
|
#define B0100101 37
|
||||||
|
#define B00100101 37
|
||||||
|
#define B100110 38
|
||||||
|
#define B0100110 38
|
||||||
|
#define B00100110 38
|
||||||
|
#define B100111 39
|
||||||
|
#define B0100111 39
|
||||||
|
#define B00100111 39
|
||||||
|
#define B101000 40
|
||||||
|
#define B0101000 40
|
||||||
|
#define B00101000 40
|
||||||
|
#define B101001 41
|
||||||
|
#define B0101001 41
|
||||||
|
#define B00101001 41
|
||||||
|
#define B101010 42
|
||||||
|
#define B0101010 42
|
||||||
|
#define B00101010 42
|
||||||
|
#define B101011 43
|
||||||
|
#define B0101011 43
|
||||||
|
#define B00101011 43
|
||||||
|
#define B101100 44
|
||||||
|
#define B0101100 44
|
||||||
|
#define B00101100 44
|
||||||
|
#define B101101 45
|
||||||
|
#define B0101101 45
|
||||||
|
#define B00101101 45
|
||||||
|
#define B101110 46
|
||||||
|
#define B0101110 46
|
||||||
|
#define B00101110 46
|
||||||
|
#define B101111 47
|
||||||
|
#define B0101111 47
|
||||||
|
#define B00101111 47
|
||||||
|
#define B110000 48
|
||||||
|
#define B0110000 48
|
||||||
|
#define B00110000 48
|
||||||
|
#define B110001 49
|
||||||
|
#define B0110001 49
|
||||||
|
#define B00110001 49
|
||||||
|
#define B110010 50
|
||||||
|
#define B0110010 50
|
||||||
|
#define B00110010 50
|
||||||
|
#define B110011 51
|
||||||
|
#define B0110011 51
|
||||||
|
#define B00110011 51
|
||||||
|
#define B110100 52
|
||||||
|
#define B0110100 52
|
||||||
|
#define B00110100 52
|
||||||
|
#define B110101 53
|
||||||
|
#define B0110101 53
|
||||||
|
#define B00110101 53
|
||||||
|
#define B110110 54
|
||||||
|
#define B0110110 54
|
||||||
|
#define B00110110 54
|
||||||
|
#define B110111 55
|
||||||
|
#define B0110111 55
|
||||||
|
#define B00110111 55
|
||||||
|
#define B111000 56
|
||||||
|
#define B0111000 56
|
||||||
|
#define B00111000 56
|
||||||
|
#define B111001 57
|
||||||
|
#define B0111001 57
|
||||||
|
#define B00111001 57
|
||||||
|
#define B111010 58
|
||||||
|
#define B0111010 58
|
||||||
|
#define B00111010 58
|
||||||
|
#define B111011 59
|
||||||
|
#define B0111011 59
|
||||||
|
#define B00111011 59
|
||||||
|
#define B111100 60
|
||||||
|
#define B0111100 60
|
||||||
|
#define B00111100 60
|
||||||
|
#define B111101 61
|
||||||
|
#define B0111101 61
|
||||||
|
#define B00111101 61
|
||||||
|
#define B111110 62
|
||||||
|
#define B0111110 62
|
||||||
|
#define B00111110 62
|
||||||
|
#define B111111 63
|
||||||
|
#define B0111111 63
|
||||||
|
#define B00111111 63
|
||||||
|
#define B1000000 64
|
||||||
|
#define B01000000 64
|
||||||
|
#define B1000001 65
|
||||||
|
#define B01000001 65
|
||||||
|
#define B1000010 66
|
||||||
|
#define B01000010 66
|
||||||
|
#define B1000011 67
|
||||||
|
#define B01000011 67
|
||||||
|
#define B1000100 68
|
||||||
|
#define B01000100 68
|
||||||
|
#define B1000101 69
|
||||||
|
#define B01000101 69
|
||||||
|
#define B1000110 70
|
||||||
|
#define B01000110 70
|
||||||
|
#define B1000111 71
|
||||||
|
#define B01000111 71
|
||||||
|
#define B1001000 72
|
||||||
|
#define B01001000 72
|
||||||
|
#define B1001001 73
|
||||||
|
#define B01001001 73
|
||||||
|
#define B1001010 74
|
||||||
|
#define B01001010 74
|
||||||
|
#define B1001011 75
|
||||||
|
#define B01001011 75
|
||||||
|
#define B1001100 76
|
||||||
|
#define B01001100 76
|
||||||
|
#define B1001101 77
|
||||||
|
#define B01001101 77
|
||||||
|
#define B1001110 78
|
||||||
|
#define B01001110 78
|
||||||
|
#define B1001111 79
|
||||||
|
#define B01001111 79
|
||||||
|
#define B1010000 80
|
||||||
|
#define B01010000 80
|
||||||
|
#define B1010001 81
|
||||||
|
#define B01010001 81
|
||||||
|
#define B1010010 82
|
||||||
|
#define B01010010 82
|
||||||
|
#define B1010011 83
|
||||||
|
#define B01010011 83
|
||||||
|
#define B1010100 84
|
||||||
|
#define B01010100 84
|
||||||
|
#define B1010101 85
|
||||||
|
#define B01010101 85
|
||||||
|
#define B1010110 86
|
||||||
|
#define B01010110 86
|
||||||
|
#define B1010111 87
|
||||||
|
#define B01010111 87
|
||||||
|
#define B1011000 88
|
||||||
|
#define B01011000 88
|
||||||
|
#define B1011001 89
|
||||||
|
#define B01011001 89
|
||||||
|
#define B1011010 90
|
||||||
|
#define B01011010 90
|
||||||
|
#define B1011011 91
|
||||||
|
#define B01011011 91
|
||||||
|
#define B1011100 92
|
||||||
|
#define B01011100 92
|
||||||
|
#define B1011101 93
|
||||||
|
#define B01011101 93
|
||||||
|
#define B1011110 94
|
||||||
|
#define B01011110 94
|
||||||
|
#define B1011111 95
|
||||||
|
#define B01011111 95
|
||||||
|
#define B1100000 96
|
||||||
|
#define B01100000 96
|
||||||
|
#define B1100001 97
|
||||||
|
#define B01100001 97
|
||||||
|
#define B1100010 98
|
||||||
|
#define B01100010 98
|
||||||
|
#define B1100011 99
|
||||||
|
#define B01100011 99
|
||||||
|
#define B1100100 100
|
||||||
|
#define B01100100 100
|
||||||
|
#define B1100101 101
|
||||||
|
#define B01100101 101
|
||||||
|
#define B1100110 102
|
||||||
|
#define B01100110 102
|
||||||
|
#define B1100111 103
|
||||||
|
#define B01100111 103
|
||||||
|
#define B1101000 104
|
||||||
|
#define B01101000 104
|
||||||
|
#define B1101001 105
|
||||||
|
#define B01101001 105
|
||||||
|
#define B1101010 106
|
||||||
|
#define B01101010 106
|
||||||
|
#define B1101011 107
|
||||||
|
#define B01101011 107
|
||||||
|
#define B1101100 108
|
||||||
|
#define B01101100 108
|
||||||
|
#define B1101101 109
|
||||||
|
#define B01101101 109
|
||||||
|
#define B1101110 110
|
||||||
|
#define B01101110 110
|
||||||
|
#define B1101111 111
|
||||||
|
#define B01101111 111
|
||||||
|
#define B1110000 112
|
||||||
|
#define B01110000 112
|
||||||
|
#define B1110001 113
|
||||||
|
#define B01110001 113
|
||||||
|
#define B1110010 114
|
||||||
|
#define B01110010 114
|
||||||
|
#define B1110011 115
|
||||||
|
#define B01110011 115
|
||||||
|
#define B1110100 116
|
||||||
|
#define B01110100 116
|
||||||
|
#define B1110101 117
|
||||||
|
#define B01110101 117
|
||||||
|
#define B1110110 118
|
||||||
|
#define B01110110 118
|
||||||
|
#define B1110111 119
|
||||||
|
#define B01110111 119
|
||||||
|
#define B1111000 120
|
||||||
|
#define B01111000 120
|
||||||
|
#define B1111001 121
|
||||||
|
#define B01111001 121
|
||||||
|
#define B1111010 122
|
||||||
|
#define B01111010 122
|
||||||
|
#define B1111011 123
|
||||||
|
#define B01111011 123
|
||||||
|
#define B1111100 124
|
||||||
|
#define B01111100 124
|
||||||
|
#define B1111101 125
|
||||||
|
#define B01111101 125
|
||||||
|
#define B1111110 126
|
||||||
|
#define B01111110 126
|
||||||
|
#define B1111111 127
|
||||||
|
#define B01111111 127
|
||||||
|
#define B10000000 128
|
||||||
|
#define B10000001 129
|
||||||
|
#define B10000010 130
|
||||||
|
#define B10000011 131
|
||||||
|
#define B10000100 132
|
||||||
|
#define B10000101 133
|
||||||
|
#define B10000110 134
|
||||||
|
#define B10000111 135
|
||||||
|
#define B10001000 136
|
||||||
|
#define B10001001 137
|
||||||
|
#define B10001010 138
|
||||||
|
#define B10001011 139
|
||||||
|
#define B10001100 140
|
||||||
|
#define B10001101 141
|
||||||
|
#define B10001110 142
|
||||||
|
#define B10001111 143
|
||||||
|
#define B10010000 144
|
||||||
|
#define B10010001 145
|
||||||
|
#define B10010010 146
|
||||||
|
#define B10010011 147
|
||||||
|
#define B10010100 148
|
||||||
|
#define B10010101 149
|
||||||
|
#define B10010110 150
|
||||||
|
#define B10010111 151
|
||||||
|
#define B10011000 152
|
||||||
|
#define B10011001 153
|
||||||
|
#define B10011010 154
|
||||||
|
#define B10011011 155
|
||||||
|
#define B10011100 156
|
||||||
|
#define B10011101 157
|
||||||
|
#define B10011110 158
|
||||||
|
#define B10011111 159
|
||||||
|
#define B10100000 160
|
||||||
|
#define B10100001 161
|
||||||
|
#define B10100010 162
|
||||||
|
#define B10100011 163
|
||||||
|
#define B10100100 164
|
||||||
|
#define B10100101 165
|
||||||
|
#define B10100110 166
|
||||||
|
#define B10100111 167
|
||||||
|
#define B10101000 168
|
||||||
|
#define B10101001 169
|
||||||
|
#define B10101010 170
|
||||||
|
#define B10101011 171
|
||||||
|
#define B10101100 172
|
||||||
|
#define B10101101 173
|
||||||
|
#define B10101110 174
|
||||||
|
#define B10101111 175
|
||||||
|
#define B10110000 176
|
||||||
|
#define B10110001 177
|
||||||
|
#define B10110010 178
|
||||||
|
#define B10110011 179
|
||||||
|
#define B10110100 180
|
||||||
|
#define B10110101 181
|
||||||
|
#define B10110110 182
|
||||||
|
#define B10110111 183
|
||||||
|
#define B10111000 184
|
||||||
|
#define B10111001 185
|
||||||
|
#define B10111010 186
|
||||||
|
#define B10111011 187
|
||||||
|
#define B10111100 188
|
||||||
|
#define B10111101 189
|
||||||
|
#define B10111110 190
|
||||||
|
#define B10111111 191
|
||||||
|
#define B11000000 192
|
||||||
|
#define B11000001 193
|
||||||
|
#define B11000010 194
|
||||||
|
#define B11000011 195
|
||||||
|
#define B11000100 196
|
||||||
|
#define B11000101 197
|
||||||
|
#define B11000110 198
|
||||||
|
#define B11000111 199
|
||||||
|
#define B11001000 200
|
||||||
|
#define B11001001 201
|
||||||
|
#define B11001010 202
|
||||||
|
#define B11001011 203
|
||||||
|
#define B11001100 204
|
||||||
|
#define B11001101 205
|
||||||
|
#define B11001110 206
|
||||||
|
#define B11001111 207
|
||||||
|
#define B11010000 208
|
||||||
|
#define B11010001 209
|
||||||
|
#define B11010010 210
|
||||||
|
#define B11010011 211
|
||||||
|
#define B11010100 212
|
||||||
|
#define B11010101 213
|
||||||
|
#define B11010110 214
|
||||||
|
#define B11010111 215
|
||||||
|
#define B11011000 216
|
||||||
|
#define B11011001 217
|
||||||
|
#define B11011010 218
|
||||||
|
#define B11011011 219
|
||||||
|
#define B11011100 220
|
||||||
|
#define B11011101 221
|
||||||
|
#define B11011110 222
|
||||||
|
#define B11011111 223
|
||||||
|
#define B11100000 224
|
||||||
|
#define B11100001 225
|
||||||
|
#define B11100010 226
|
||||||
|
#define B11100011 227
|
||||||
|
#define B11100100 228
|
||||||
|
#define B11100101 229
|
||||||
|
#define B11100110 230
|
||||||
|
#define B11100111 231
|
||||||
|
#define B11101000 232
|
||||||
|
#define B11101001 233
|
||||||
|
#define B11101010 234
|
||||||
|
#define B11101011 235
|
||||||
|
#define B11101100 236
|
||||||
|
#define B11101101 237
|
||||||
|
#define B11101110 238
|
||||||
|
#define B11101111 239
|
||||||
|
#define B11110000 240
|
||||||
|
#define B11110001 241
|
||||||
|
#define B11110010 242
|
||||||
|
#define B11110011 243
|
||||||
|
#define B11110100 244
|
||||||
|
#define B11110101 245
|
||||||
|
#define B11110110 246
|
||||||
|
#define B11110111 247
|
||||||
|
#define B11111000 248
|
||||||
|
#define B11111001 249
|
||||||
|
#define B11111010 250
|
||||||
|
#define B11111011 251
|
||||||
|
#define B11111100 252
|
||||||
|
#define B11111101 253
|
||||||
|
#define B11111110 254
|
||||||
|
#define B11111111 255
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
cbuf.cpp - Circular buffer implementation
|
||||||
|
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cbuf.h"
|
||||||
|
|
||||||
|
cbuf::cbuf(size_t size) :
|
||||||
|
next(NULL), _size(size+1), _buf(new char[size+1]), _bufend(_buf + size + 1), _begin(_buf), _end(_begin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
cbuf::~cbuf()
|
||||||
|
{
|
||||||
|
delete[] _buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::resizeAdd(size_t addSize)
|
||||||
|
{
|
||||||
|
return resize(_size + addSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::resize(size_t newSize)
|
||||||
|
{
|
||||||
|
|
||||||
|
size_t bytes_available = available();
|
||||||
|
newSize += 1;
|
||||||
|
// not lose any data
|
||||||
|
// if data can be lost use remove or flush before resize
|
||||||
|
if((newSize < bytes_available) || (newSize == _size)) {
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *newbuf = new char[newSize];
|
||||||
|
char *oldbuf = _buf;
|
||||||
|
|
||||||
|
if(!newbuf) {
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_buf) {
|
||||||
|
read(newbuf, bytes_available);
|
||||||
|
memset((newbuf + bytes_available), 0x00, (newSize - bytes_available));
|
||||||
|
}
|
||||||
|
|
||||||
|
_begin = newbuf;
|
||||||
|
_end = newbuf + bytes_available;
|
||||||
|
_bufend = newbuf + newSize;
|
||||||
|
_size = newSize;
|
||||||
|
|
||||||
|
_buf = newbuf;
|
||||||
|
delete[] oldbuf;
|
||||||
|
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::available() const
|
||||||
|
{
|
||||||
|
if(_end >= _begin) {
|
||||||
|
return _end - _begin;
|
||||||
|
}
|
||||||
|
return _size - (_begin - _end);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::size()
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::room() const
|
||||||
|
{
|
||||||
|
if(_end >= _begin) {
|
||||||
|
return _size - (_end - _begin) - 1;
|
||||||
|
}
|
||||||
|
return _begin - _end - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cbuf::peek()
|
||||||
|
{
|
||||||
|
if(empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<int>(*_begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::peek(char *dst, size_t size)
|
||||||
|
{
|
||||||
|
size_t bytes_available = available();
|
||||||
|
size_t size_to_read = (size < bytes_available) ? size : bytes_available;
|
||||||
|
size_t size_read = size_to_read;
|
||||||
|
char * begin = _begin;
|
||||||
|
if(_end < _begin && size_to_read > (size_t) (_bufend - _begin)) {
|
||||||
|
size_t top_size = _bufend - _begin;
|
||||||
|
memcpy(dst, _begin, top_size);
|
||||||
|
begin = _buf;
|
||||||
|
size_to_read -= top_size;
|
||||||
|
dst += top_size;
|
||||||
|
}
|
||||||
|
memcpy(dst, begin, size_to_read);
|
||||||
|
return size_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cbuf::read()
|
||||||
|
{
|
||||||
|
if(empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char result = *_begin;
|
||||||
|
_begin = wrap_if_bufend(_begin + 1);
|
||||||
|
return static_cast<int>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::read(char* dst, size_t size)
|
||||||
|
{
|
||||||
|
size_t bytes_available = available();
|
||||||
|
size_t size_to_read = (size < bytes_available) ? size : bytes_available;
|
||||||
|
size_t size_read = size_to_read;
|
||||||
|
if(_end < _begin && size_to_read > (size_t) (_bufend - _begin)) {
|
||||||
|
size_t top_size = _bufend - _begin;
|
||||||
|
memcpy(dst, _begin, top_size);
|
||||||
|
_begin = _buf;
|
||||||
|
size_to_read -= top_size;
|
||||||
|
dst += top_size;
|
||||||
|
}
|
||||||
|
memcpy(dst, _begin, size_to_read);
|
||||||
|
_begin = wrap_if_bufend(_begin + size_to_read);
|
||||||
|
return size_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::write(char c)
|
||||||
|
{
|
||||||
|
if(full()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*_end = c;
|
||||||
|
_end = wrap_if_bufend(_end + 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::write(const char* src, size_t size)
|
||||||
|
{
|
||||||
|
size_t bytes_available = room();
|
||||||
|
size_t size_to_write = (size < bytes_available) ? size : bytes_available;
|
||||||
|
size_t size_written = size_to_write;
|
||||||
|
if(_end >= _begin && size_to_write > (size_t) (_bufend - _end)) {
|
||||||
|
size_t top_size = _bufend - _end;
|
||||||
|
memcpy(_end, src, top_size);
|
||||||
|
_end = _buf;
|
||||||
|
size_to_write -= top_size;
|
||||||
|
src += top_size;
|
||||||
|
}
|
||||||
|
memcpy(_end, src, size_to_write);
|
||||||
|
_end = wrap_if_bufend(_end + size_to_write);
|
||||||
|
return size_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cbuf::flush()
|
||||||
|
{
|
||||||
|
_begin = _buf;
|
||||||
|
_end = _buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::remove(size_t size)
|
||||||
|
{
|
||||||
|
size_t bytes_available = available();
|
||||||
|
if(size >= bytes_available) {
|
||||||
|
flush();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t size_to_remove = (size < bytes_available) ? size : bytes_available;
|
||||||
|
if(_end < _begin && size_to_remove > (size_t) (_bufend - _begin)) {
|
||||||
|
size_t top_size = _bufend - _begin;
|
||||||
|
_begin = _buf;
|
||||||
|
size_to_remove -= top_size;
|
||||||
|
}
|
||||||
|
_begin = wrap_if_bufend(_begin + size_to_remove);
|
||||||
|
return available();
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
cbuf.h - Circular buffer implementation
|
||||||
|
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __cbuf_h
|
||||||
|
#define __cbuf_h
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
class cbuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
cbuf(size_t size);
|
||||||
|
~cbuf();
|
||||||
|
|
||||||
|
size_t resizeAdd(size_t addSize);
|
||||||
|
size_t resize(size_t newSize);
|
||||||
|
size_t available() const;
|
||||||
|
size_t size();
|
||||||
|
|
||||||
|
size_t room() const;
|
||||||
|
|
||||||
|
inline bool empty() const
|
||||||
|
{
|
||||||
|
return _begin == _end;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool full() const
|
||||||
|
{
|
||||||
|
return wrap_if_bufend(_end + 1) == _begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
int peek();
|
||||||
|
size_t peek(char *dst, size_t size);
|
||||||
|
|
||||||
|
int read();
|
||||||
|
size_t read(char* dst, size_t size);
|
||||||
|
|
||||||
|
size_t write(char c);
|
||||||
|
size_t write(const char* src, size_t size);
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
size_t remove(size_t size);
|
||||||
|
|
||||||
|
cbuf *next;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
inline char* wrap_if_bufend(char* ptr) const
|
||||||
|
{
|
||||||
|
return (ptr == _bufend) ? _buf : ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t _size;
|
||||||
|
char* _buf;
|
||||||
|
const char* _bufend;
|
||||||
|
char* _begin;
|
||||||
|
char* _end;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif//__cbuf_h
|
@ -0,0 +1,4 @@
|
|||||||
|
#define ARDUINO_ESP32_GIT_VER 0x725146d1
|
||||||
|
#define ARDUINO_ESP32_GIT_DESC 2.0.12
|
||||||
|
#define ARDUINO_ESP32_RELEASE_2_0_12
|
||||||
|
#define ARDUINO_ESP32_RELEASE "2_0_12"
|
@ -0,0 +1,281 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal-adc.h"
|
||||||
|
#include "driver/adc.h"
|
||||||
|
#include "esp_adc_cal.h"
|
||||||
|
|
||||||
|
#if SOC_DAC_SUPPORTED //ESP32, ESP32S2
|
||||||
|
#include "soc/dac_channel.h"
|
||||||
|
#include "soc/sens_reg.h"
|
||||||
|
#include "soc/rtc_io_reg.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEFAULT_VREF 1100
|
||||||
|
|
||||||
|
static uint8_t __analogAttenuation = 3;//11db
|
||||||
|
static uint8_t __analogWidth = ADC_WIDTH_MAX - 1; //3 for ESP32/ESP32C3; 4 for ESP32S2
|
||||||
|
static uint8_t __analogReturnedWidth = SOC_ADC_MAX_BITWIDTH; //12 for ESP32/ESP32C3; 13 for ESP32S2
|
||||||
|
static uint8_t __analogClockDiv = 1;
|
||||||
|
static adc_attenuation_t __pin_attenuation[SOC_GPIO_PIN_COUNT];
|
||||||
|
|
||||||
|
static uint16_t __analogVRef = 0;
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
static uint8_t __analogVRefPin = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline uint16_t mapResolution(uint16_t value)
|
||||||
|
{
|
||||||
|
uint8_t from = __analogWidth + 9;
|
||||||
|
if (from == __analogReturnedWidth) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (from > __analogReturnedWidth) {
|
||||||
|
return value >> (from - __analogReturnedWidth);
|
||||||
|
}
|
||||||
|
return value << (__analogReturnedWidth - from);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __analogSetClockDiv(uint8_t clockDiv){
|
||||||
|
if(!clockDiv){
|
||||||
|
clockDiv = 1;
|
||||||
|
}
|
||||||
|
__analogClockDiv = clockDiv;
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
adc_set_clk_div(__analogClockDiv);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void __analogSetAttenuation(adc_attenuation_t attenuation)
|
||||||
|
{
|
||||||
|
__analogAttenuation = attenuation & 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
void __analogSetWidth(uint8_t bits){
|
||||||
|
if(bits < 9){
|
||||||
|
bits = 9;
|
||||||
|
} else if(bits > 12){
|
||||||
|
bits = 12;
|
||||||
|
}
|
||||||
|
__analogWidth = bits - 9;
|
||||||
|
adc1_config_width(__analogWidth);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void __analogInit(){
|
||||||
|
static bool initialized = false;
|
||||||
|
if(initialized){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
__analogSetClockDiv(__analogClockDiv);
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
__analogSetWidth(__analogWidth + 9);//in bits
|
||||||
|
#endif
|
||||||
|
for(int i=0; i<SOC_GPIO_PIN_COUNT; i++){
|
||||||
|
__pin_attenuation[i] = ADC_ATTENDB_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void __analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation)
|
||||||
|
{
|
||||||
|
int8_t channel = digitalPinToAnalogChannel(pin);
|
||||||
|
if(channel < 0 || attenuation > 3){
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if(channel > (SOC_ADC_MAX_CHANNEL_NUM - 1)){
|
||||||
|
adc2_config_channel_atten(channel - SOC_ADC_MAX_CHANNEL_NUM, attenuation);
|
||||||
|
} else {
|
||||||
|
adc1_config_channel_atten(channel, attenuation);
|
||||||
|
}
|
||||||
|
__analogInit();
|
||||||
|
if((__pin_attenuation[pin] != ADC_ATTENDB_MAX) || (attenuation != __analogAttenuation)){
|
||||||
|
__pin_attenuation[pin] = attenuation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool __adcAttachPin(uint8_t pin){
|
||||||
|
int8_t channel = digitalPinToAnalogChannel(pin);
|
||||||
|
if(channel < 0){
|
||||||
|
log_e("Pin %u is not ADC pin!", pin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
__analogInit();
|
||||||
|
int8_t pad = digitalPinToTouchChannel(pin);
|
||||||
|
if(pad >= 0){
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
uint32_t touch = READ_PERI_REG(SENS_SAR_TOUCH_ENABLE_REG);
|
||||||
|
if(touch & (1 << pad)){
|
||||||
|
touch &= ~((1 << (pad + SENS_TOUCH_PAD_OUTEN2_S))
|
||||||
|
| (1 << (pad + SENS_TOUCH_PAD_OUTEN1_S))
|
||||||
|
| (1 << (pad + SENS_TOUCH_PAD_WORKEN_S)));
|
||||||
|
WRITE_PERI_REG(SENS_SAR_TOUCH_ENABLE_REG, touch);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#if SOC_DAC_SUPPORTED
|
||||||
|
else if(pin == DAC_CHANNEL_1_GPIO_NUM){
|
||||||
|
CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_XPD_DAC | RTC_IO_PDAC1_DAC_XPD_FORCE);//stop dac1
|
||||||
|
} else if(pin == DAC_CHANNEL_2_GPIO_NUM){
|
||||||
|
CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_XPD_DAC | RTC_IO_PDAC2_DAC_XPD_FORCE);//stop dac2
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pinMode(pin, ANALOG);
|
||||||
|
__analogSetPinAttenuation(pin, (__pin_attenuation[pin] != ADC_ATTENDB_MAX)?__pin_attenuation[pin]:__analogAttenuation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __analogReadResolution(uint8_t bits)
|
||||||
|
{
|
||||||
|
if(!bits || bits > 16){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
__analogReturnedWidth = bits;
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
__analogSetWidth(bits); // hadware from 9 to 12
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t __analogRead(uint8_t pin)
|
||||||
|
{
|
||||||
|
int8_t channel = digitalPinToAnalogChannel(pin);
|
||||||
|
int value = 0;
|
||||||
|
esp_err_t r = ESP_OK;
|
||||||
|
if(channel < 0){
|
||||||
|
log_e("Pin %u is not ADC pin!", pin);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
__adcAttachPin(pin);
|
||||||
|
if(channel > (SOC_ADC_MAX_CHANNEL_NUM - 1)){
|
||||||
|
channel -= SOC_ADC_MAX_CHANNEL_NUM;
|
||||||
|
r = adc2_get_raw( channel, __analogWidth, &value);
|
||||||
|
if ( r == ESP_OK ) {
|
||||||
|
return mapResolution(value);
|
||||||
|
} else if ( r == ESP_ERR_INVALID_STATE ) {
|
||||||
|
log_e("GPIO%u: %s: ADC2 not initialized yet.", pin, esp_err_to_name(r));
|
||||||
|
} else if ( r == ESP_ERR_TIMEOUT ) {
|
||||||
|
log_e("GPIO%u: %s: ADC2 is in use by Wi-Fi. Please see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html#adc-limitations for more info", pin, esp_err_to_name(r));
|
||||||
|
} else {
|
||||||
|
log_e("GPIO%u: %s", pin, esp_err_to_name(r));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = adc1_get_raw(channel);
|
||||||
|
return mapResolution(value);
|
||||||
|
}
|
||||||
|
return mapResolution(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t __analogReadMilliVolts(uint8_t pin){
|
||||||
|
int8_t channel = digitalPinToAnalogChannel(pin);
|
||||||
|
if(channel < 0){
|
||||||
|
log_e("Pin %u is not ADC pin!", pin);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!__analogVRef){
|
||||||
|
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
|
||||||
|
log_d("eFuse Two Point: Supported");
|
||||||
|
__analogVRef = DEFAULT_VREF;
|
||||||
|
}
|
||||||
|
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
|
||||||
|
log_d("eFuse Vref: Supported");
|
||||||
|
__analogVRef = DEFAULT_VREF;
|
||||||
|
}
|
||||||
|
if(!__analogVRef){
|
||||||
|
__analogVRef = DEFAULT_VREF;
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
if(__analogVRefPin){
|
||||||
|
esp_adc_cal_characteristics_t chars;
|
||||||
|
if(adc_vref_to_gpio(ADC_UNIT_2, __analogVRefPin) == ESP_OK){
|
||||||
|
__analogVRef = __analogRead(__analogVRefPin);
|
||||||
|
esp_adc_cal_characterize(1, __analogAttenuation, __analogWidth, DEFAULT_VREF, &chars);
|
||||||
|
__analogVRef = esp_adc_cal_raw_to_voltage(__analogVRef, &chars);
|
||||||
|
log_d("Vref to GPIO%u: %u", __analogVRefPin, __analogVRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint8_t unit = 1;
|
||||||
|
if(channel > (SOC_ADC_MAX_CHANNEL_NUM - 1)){
|
||||||
|
unit = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t adc_reading = __analogRead(pin);
|
||||||
|
|
||||||
|
uint8_t atten = __analogAttenuation;
|
||||||
|
if (__pin_attenuation[pin] != ADC_ATTENDB_MAX){
|
||||||
|
atten = __pin_attenuation[pin];
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_adc_cal_characteristics_t chars = {};
|
||||||
|
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, __analogWidth, __analogVRef, &chars);
|
||||||
|
|
||||||
|
static bool print_chars_info = true;
|
||||||
|
if(print_chars_info)
|
||||||
|
{
|
||||||
|
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
|
||||||
|
log_i("ADC%u: Characterized using Two Point Value: %u\n", unit, chars.vref);
|
||||||
|
}
|
||||||
|
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
|
||||||
|
log_i("ADC%u: Characterized using eFuse Vref: %u\n", unit, chars.vref);
|
||||||
|
}
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
else if(__analogVRef != DEFAULT_VREF){
|
||||||
|
log_i("ADC%u: Characterized using Vref to GPIO%u: %u\n", unit, __analogVRefPin, chars.vref);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else {
|
||||||
|
log_i("ADC%u: Characterized using Default Vref: %u\n", unit, chars.vref);
|
||||||
|
}
|
||||||
|
print_chars_info = false;
|
||||||
|
}
|
||||||
|
return esp_adc_cal_raw_to_voltage((uint32_t)adc_reading, &chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
|
||||||
|
void __analogSetVRefPin(uint8_t pin){
|
||||||
|
if(pin <25 || pin > 27){
|
||||||
|
pin = 0;
|
||||||
|
}
|
||||||
|
__analogVRefPin = pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
int __hallRead() //hall sensor using idf read
|
||||||
|
{
|
||||||
|
pinMode(36, ANALOG);
|
||||||
|
pinMode(39, ANALOG);
|
||||||
|
__analogSetWidth(12);
|
||||||
|
return hall_sensor_read();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern uint16_t analogRead(uint8_t pin) __attribute__ ((weak, alias("__analogRead")));
|
||||||
|
extern uint32_t analogReadMilliVolts(uint8_t pin) __attribute__ ((weak, alias("__analogReadMilliVolts")));
|
||||||
|
extern void analogReadResolution(uint8_t bits) __attribute__ ((weak, alias("__analogReadResolution")));
|
||||||
|
extern void analogSetClockDiv(uint8_t clockDiv) __attribute__ ((weak, alias("__analogSetClockDiv")));
|
||||||
|
extern void analogSetAttenuation(adc_attenuation_t attenuation) __attribute__ ((weak, alias("__analogSetAttenuation")));
|
||||||
|
extern void analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation) __attribute__ ((weak, alias("__analogSetPinAttenuation")));
|
||||||
|
|
||||||
|
extern bool adcAttachPin(uint8_t pin) __attribute__ ((weak, alias("__adcAttachPin")));
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
extern void analogSetVRefPin(uint8_t pin) __attribute__ ((weak, alias("__analogSetVRefPin")));
|
||||||
|
extern void analogSetWidth(uint8_t bits) __attribute__ ((weak, alias("__analogSetWidth")));
|
||||||
|
extern int hallRead() __attribute__ ((weak, alias("__hallRead")));
|
||||||
|
#endif
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
Arduino.h - Main include file for the Arduino SDK
|
||||||
|
Copyright (c) 2005-2013 Arduino Team. All right reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MAIN_ESP32_HAL_ADC_H_
|
||||||
|
#define MAIN_ESP32_HAL_ADC_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ADC_0db,
|
||||||
|
ADC_2_5db,
|
||||||
|
ADC_6db,
|
||||||
|
ADC_11db,
|
||||||
|
ADC_ATTENDB_MAX
|
||||||
|
} adc_attenuation_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get ADC value for pin
|
||||||
|
* */
|
||||||
|
uint16_t analogRead(uint8_t pin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get MilliVolts value for pin
|
||||||
|
* */
|
||||||
|
uint32_t analogReadMilliVolts(uint8_t pin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the resolution of analogRead return values. Default is 12 bits (range from 0 to 4096).
|
||||||
|
* If between 9 and 12, it will equal the set hardware resolution, else value will be shifted.
|
||||||
|
* Range is 1 - 16
|
||||||
|
*
|
||||||
|
* Note: compatibility with Arduino SAM
|
||||||
|
*/
|
||||||
|
void analogReadResolution(uint8_t bits);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the divider for the ADC clock.
|
||||||
|
* Default is 1
|
||||||
|
* Range is 1 - 255
|
||||||
|
* */
|
||||||
|
void analogSetClockDiv(uint8_t clockDiv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the attenuation for all channels
|
||||||
|
* Default is 11db
|
||||||
|
* */
|
||||||
|
void analogSetAttenuation(adc_attenuation_t attenuation);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the attenuation for particular pin
|
||||||
|
* Default is 11db
|
||||||
|
* */
|
||||||
|
void analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attach pin to ADC (will also clear any other analog mode that could be on)
|
||||||
|
* */
|
||||||
|
bool adcAttachPin(uint8_t pin);
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
/*
|
||||||
|
* Sets the sample bits and read resolution
|
||||||
|
* Default is 12bit (0 - 4095)
|
||||||
|
* Range is 9 - 12
|
||||||
|
* */
|
||||||
|
void analogSetWidth(uint8_t bits);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set pin to use for ADC calibration if the esp is not already calibrated (25, 26 or 27)
|
||||||
|
* */
|
||||||
|
void analogSetVRefPin(uint8_t pin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get value for HALL sensor (without LNA)
|
||||||
|
* connected to pins 36(SVP) and 39(SVN)
|
||||||
|
* */
|
||||||
|
int hallRead();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MAIN_ESP32_HAL_ADC_H_ */
|
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal-bt.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
bool btInUse(){ return true; }
|
||||||
|
#else
|
||||||
|
// user may want to change it to free resources
|
||||||
|
__attribute__((weak)) bool btInUse(){ return true; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp_bt.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_BTDM_CONTROLLER_MODE_BTDM
|
||||||
|
#define BT_MODE ESP_BT_MODE_BTDM
|
||||||
|
#elif defined(CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY)
|
||||||
|
#define BT_MODE ESP_BT_MODE_CLASSIC_BT
|
||||||
|
#else
|
||||||
|
#define BT_MODE ESP_BT_MODE_BLE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool btStarted(){
|
||||||
|
return (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool btStart(){
|
||||||
|
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE){
|
||||||
|
esp_bt_controller_init(&cfg);
|
||||||
|
while(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE){}
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED){
|
||||||
|
if (esp_bt_controller_enable(BT_MODE)) {
|
||||||
|
log_e("BT Enable failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log_e("BT Start failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool btStop(){
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED){
|
||||||
|
if (esp_bt_controller_disable()) {
|
||||||
|
log_e("BT Disable failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED);
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED){
|
||||||
|
if (esp_bt_controller_deinit()) {
|
||||||
|
log_e("BT deint failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(1);
|
||||||
|
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log_e("BT Stop failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // CONFIG_BT_ENABLED
|
||||||
|
bool btStarted()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool btStart()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool btStop()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONFIG_BT_ENABLED
|
||||||
|
|
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef _ESP32_ESP32_HAL_BT_H_
|
||||||
|
#define _ESP32_ESP32_HAL_BT_H_
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool btStarted();
|
||||||
|
bool btStart();
|
||||||
|
bool btStop();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _ESP32_ESP32_HAL_BT_H_ */
|
@ -0,0 +1,262 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_attr.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "soc/rtc.h"
|
||||||
|
#include "soc/rtc_cntl_reg.h"
|
||||||
|
#include "soc/apb_ctrl_reg.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "esp32-hal-cpu.h"
|
||||||
|
|
||||||
|
#include "esp_system.h"
|
||||||
|
#ifdef ESP_IDF_VERSION_MAJOR // IDF 4+
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
|
||||||
|
#include "freertos/xtensa_timer.h"
|
||||||
|
#include "esp32/rom/rtc.h"
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#include "freertos/xtensa_timer.h"
|
||||||
|
#include "esp32s2/rom/rtc.h"
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#include "freertos/xtensa_timer.h"
|
||||||
|
#include "esp32s3/rom/rtc.h"
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#include "esp32c3/rom/rtc.h"
|
||||||
|
#else
|
||||||
|
#error Target CONFIG_IDF_TARGET is not supported
|
||||||
|
#endif
|
||||||
|
#else // ESP32 Before IDF 4.0
|
||||||
|
#include "rom/rtc.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct apb_change_cb_s {
|
||||||
|
struct apb_change_cb_s * prev;
|
||||||
|
struct apb_change_cb_s * next;
|
||||||
|
void * arg;
|
||||||
|
apb_change_cb_t cb;
|
||||||
|
} apb_change_t;
|
||||||
|
|
||||||
|
|
||||||
|
static apb_change_t * apb_change_callbacks = NULL;
|
||||||
|
static xSemaphoreHandle apb_change_lock = NULL;
|
||||||
|
|
||||||
|
static void initApbChangeCallback(){
|
||||||
|
static volatile bool initialized = false;
|
||||||
|
if(!initialized){
|
||||||
|
initialized = true;
|
||||||
|
apb_change_lock = xSemaphoreCreateMutex();
|
||||||
|
if(!apb_change_lock){
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void triggerApbChangeCallback(apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb){
|
||||||
|
initApbChangeCallback();
|
||||||
|
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
|
||||||
|
apb_change_t * r = apb_change_callbacks;
|
||||||
|
if( r != NULL ){
|
||||||
|
if(ev_type == APB_BEFORE_CHANGE )
|
||||||
|
while(r != NULL){
|
||||||
|
r->cb(r->arg, ev_type, old_apb, new_apb);
|
||||||
|
r=r->next;
|
||||||
|
}
|
||||||
|
else { // run backwards through chain
|
||||||
|
while(r->next != NULL) r = r->next; // find first added
|
||||||
|
while( r != NULL){
|
||||||
|
r->cb(r->arg, ev_type, old_apb, new_apb);
|
||||||
|
r=r->prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool addApbChangeCallback(void * arg, apb_change_cb_t cb){
|
||||||
|
initApbChangeCallback();
|
||||||
|
apb_change_t * c = (apb_change_t*)malloc(sizeof(apb_change_t));
|
||||||
|
if(!c){
|
||||||
|
log_e("Callback Object Malloc Failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
c->next = NULL;
|
||||||
|
c->prev = NULL;
|
||||||
|
c->arg = arg;
|
||||||
|
c->cb = cb;
|
||||||
|
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
|
||||||
|
if(apb_change_callbacks == NULL){
|
||||||
|
apb_change_callbacks = c;
|
||||||
|
} else {
|
||||||
|
apb_change_t * r = apb_change_callbacks;
|
||||||
|
// look for duplicate callbacks
|
||||||
|
while( (r != NULL ) && !((r->cb == cb) && ( r->arg == arg))) r = r->next;
|
||||||
|
if (r) {
|
||||||
|
log_e("duplicate func=%8p arg=%8p",c->cb,c->arg);
|
||||||
|
free(c);
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c->next = apb_change_callbacks;
|
||||||
|
apb_change_callbacks-> prev = c;
|
||||||
|
apb_change_callbacks = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool removeApbChangeCallback(void * arg, apb_change_cb_t cb){
|
||||||
|
initApbChangeCallback();
|
||||||
|
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
|
||||||
|
apb_change_t * r = apb_change_callbacks;
|
||||||
|
// look for matching callback
|
||||||
|
while( (r != NULL ) && !((r->cb == cb) && ( r->arg == arg))) r = r->next;
|
||||||
|
if ( r == NULL ) {
|
||||||
|
log_e("not found func=%8p arg=%8p",cb,arg);
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// patch links
|
||||||
|
if(r->prev) r->prev->next = r->next;
|
||||||
|
else { // this is first link
|
||||||
|
apb_change_callbacks = r->next;
|
||||||
|
}
|
||||||
|
if(r->next) r->next->prev = r->prev;
|
||||||
|
free(r);
|
||||||
|
}
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t calculateApb(rtc_cpu_freq_config_t * conf){
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return APB_CLK_FREQ;
|
||||||
|
#else
|
||||||
|
if(conf->freq_mhz >= 80){
|
||||||
|
return 80 * MHZ;
|
||||||
|
}
|
||||||
|
return (conf->source_freq_mhz * MHZ) / conf->div;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us); //private in IDF
|
||||||
|
|
||||||
|
bool setCpuFrequencyMhz(uint32_t cpu_freq_mhz){
|
||||||
|
rtc_cpu_freq_config_t conf, cconf;
|
||||||
|
uint32_t capb, apb;
|
||||||
|
//Get XTAL Frequency and calculate min CPU MHz
|
||||||
|
rtc_xtal_freq_t xtal = rtc_clk_xtal_freq_get();
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
if(xtal > RTC_XTAL_FREQ_AUTO){
|
||||||
|
if(xtal < RTC_XTAL_FREQ_40M) {
|
||||||
|
if(cpu_freq_mhz <= xtal && cpu_freq_mhz != xtal && cpu_freq_mhz != (xtal/2)){
|
||||||
|
log_e("Bad frequency: %u MHz! Options are: 240, 160, 80, %u and %u MHz", cpu_freq_mhz, xtal, xtal/2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if(cpu_freq_mhz <= xtal && cpu_freq_mhz != xtal && cpu_freq_mhz != (xtal/2) && cpu_freq_mhz != (xtal/4)){
|
||||||
|
log_e("Bad frequency: %u MHz! Options are: 240, 160, 80, %u, %u and %u MHz", cpu_freq_mhz, xtal, xtal/2, xtal/4);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(cpu_freq_mhz > xtal && cpu_freq_mhz != 240 && cpu_freq_mhz != 160 && cpu_freq_mhz != 80){
|
||||||
|
if(xtal >= RTC_XTAL_FREQ_40M){
|
||||||
|
log_e("Bad frequency: %u MHz! Options are: 240, 160, 80, %u, %u and %u MHz", cpu_freq_mhz, xtal, xtal/2, xtal/4);
|
||||||
|
} else {
|
||||||
|
log_e("Bad frequency: %u MHz! Options are: 240, 160, 80, %u and %u MHz", cpu_freq_mhz, xtal, xtal/2);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
//check if cpu supports the frequency
|
||||||
|
if(cpu_freq_mhz == 240){
|
||||||
|
//Check if ESP32 is rated for a CPU frequency of 160MHz only
|
||||||
|
if (REG_GET_BIT(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_CPU_FREQ_RATED) &&
|
||||||
|
REG_GET_BIT(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_CPU_FREQ_LOW)) {
|
||||||
|
log_e("Can not switch to 240 MHz! Chip CPU frequency rated for 160MHz.");
|
||||||
|
cpu_freq_mhz = 160;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
//Get current CPU clock configuration
|
||||||
|
rtc_clk_cpu_freq_get_config(&cconf);
|
||||||
|
//return if frequency has not changed
|
||||||
|
if(cconf.freq_mhz == cpu_freq_mhz){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//Get configuration for the new CPU frequency
|
||||||
|
if(!rtc_clk_cpu_freq_mhz_to_config(cpu_freq_mhz, &conf)){
|
||||||
|
log_e("CPU clock could not be set to %u MHz", cpu_freq_mhz);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//Current APB
|
||||||
|
capb = calculateApb(&cconf);
|
||||||
|
//New APB
|
||||||
|
apb = calculateApb(&conf);
|
||||||
|
|
||||||
|
//Call peripheral functions before the APB change
|
||||||
|
if(apb_change_callbacks){
|
||||||
|
triggerApbChangeCallback(APB_BEFORE_CHANGE, capb, apb);
|
||||||
|
}
|
||||||
|
//Make the frequency change
|
||||||
|
rtc_clk_cpu_freq_set_config_fast(&conf);
|
||||||
|
if(capb != apb){
|
||||||
|
//Update REF_TICK (uncomment if REF_TICK is different than 1MHz)
|
||||||
|
//if(conf.freq_mhz < 80){
|
||||||
|
// ESP_REG(APB_CTRL_XTAL_TICK_CONF_REG) = conf.freq_mhz / (REF_CLK_FREQ / MHZ) - 1;
|
||||||
|
// }
|
||||||
|
//Update APB Freq REG
|
||||||
|
rtc_clk_apb_freq_update(apb);
|
||||||
|
//Update esp_timer divisor
|
||||||
|
esp_timer_impl_update_apb_freq(apb / MHZ);
|
||||||
|
}
|
||||||
|
//Update FreeRTOS Tick Divisor
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
#else
|
||||||
|
uint32_t fcpu = (conf.freq_mhz >= 80)?(conf.freq_mhz * MHZ):(apb);
|
||||||
|
_xt_tick_divisor = fcpu / XT_TICK_PER_SEC;
|
||||||
|
#endif
|
||||||
|
//Call peripheral functions after the APB change
|
||||||
|
if(apb_change_callbacks){
|
||||||
|
triggerApbChangeCallback(APB_AFTER_CHANGE, capb, apb);
|
||||||
|
}
|
||||||
|
log_d("%s: %u / %u = %u Mhz, APB: %u Hz", (conf.source == RTC_CPU_FREQ_SRC_PLL)?"PLL":((conf.source == RTC_CPU_FREQ_SRC_APLL)?"APLL":((conf.source == RTC_CPU_FREQ_SRC_XTAL)?"XTAL":"8M")), conf.source_freq_mhz, conf.div, conf.freq_mhz, apb);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getCpuFrequencyMhz(){
|
||||||
|
rtc_cpu_freq_config_t conf;
|
||||||
|
rtc_clk_cpu_freq_get_config(&conf);
|
||||||
|
return conf.freq_mhz;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getXtalFrequencyMhz(){
|
||||||
|
return rtc_clk_xtal_freq_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getApbFrequency(){
|
||||||
|
rtc_cpu_freq_config_t conf;
|
||||||
|
rtc_clk_cpu_freq_get_config(&conf);
|
||||||
|
return calculateApb(&conf);
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef _ESP32_HAL_CPU_H_
|
||||||
|
#define _ESP32_HAL_CPU_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef enum { APB_BEFORE_CHANGE, APB_AFTER_CHANGE } apb_change_ev_t;
|
||||||
|
|
||||||
|
typedef void (* apb_change_cb_t)(void * arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb);
|
||||||
|
|
||||||
|
bool addApbChangeCallback(void * arg, apb_change_cb_t cb);
|
||||||
|
bool removeApbChangeCallback(void * arg, apb_change_cb_t cb);
|
||||||
|
|
||||||
|
//function takes the following frequencies as valid values:
|
||||||
|
// 240, 160, 80 <<< For all XTAL types
|
||||||
|
// 40, 20, 10 <<< For 40MHz XTAL
|
||||||
|
// 26, 13 <<< For 26MHz XTAL
|
||||||
|
// 24, 12 <<< For 24MHz XTAL
|
||||||
|
bool setCpuFrequencyMhz(uint32_t cpu_freq_mhz);
|
||||||
|
|
||||||
|
uint32_t getCpuFrequencyMhz(); // In MHz
|
||||||
|
uint32_t getXtalFrequencyMhz(); // In MHz
|
||||||
|
uint32_t getApbFrequency(); // In Hz
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _ESP32_HAL_CPU_H_ */
|
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
|
||||||
|
#ifndef SOC_DAC_SUPPORTED
|
||||||
|
#define NODAC
|
||||||
|
#else
|
||||||
|
#include "soc/dac_channel.h"
|
||||||
|
#include "driver/dac_common.h"
|
||||||
|
|
||||||
|
void ARDUINO_ISR_ATTR __dacWrite(uint8_t pin, uint8_t value)
|
||||||
|
{
|
||||||
|
if(pin < DAC_CHANNEL_1_GPIO_NUM || pin > DAC_CHANNEL_2_GPIO_NUM){
|
||||||
|
return;//not dac pin
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t channel = pin - DAC_CHANNEL_1_GPIO_NUM;
|
||||||
|
dac_output_enable(channel);
|
||||||
|
dac_output_voltage(channel, value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARDUINO_ISR_ATTR __dacDisable(uint8_t pin)
|
||||||
|
{
|
||||||
|
if(pin < DAC_CHANNEL_1_GPIO_NUM || pin > DAC_CHANNEL_2_GPIO_NUM){
|
||||||
|
return;//not dac pin
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t channel = pin - DAC_CHANNEL_1_GPIO_NUM;
|
||||||
|
dac_output_disable(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void dacWrite(uint8_t pin, uint8_t value) __attribute__ ((weak, alias("__dacWrite")));
|
||||||
|
extern void dacDisable(uint8_t pin) __attribute__ ((weak, alias("__dacDisable")));
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Arduino.h - Main include file for the Arduino SDK
|
||||||
|
Copyright (c) 2005-2013 Arduino Team. All right reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MAIN_ESP32_HAL_DAC_H_
|
||||||
|
#define MAIN_ESP32_HAL_DAC_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
void dacWrite(uint8_t pin, uint8_t value);
|
||||||
|
void dacDisable(uint8_t pin);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MAIN_ESP32_HAL_DAC_H_ */
|
@ -0,0 +1,236 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal-gpio.h"
|
||||||
|
#include "hal/gpio_hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
|
||||||
|
// It fixes lack of pin definition for S3 and for any future SoC
|
||||||
|
// this function works for ESP32, ESP32-S2 and ESP32-S3 - including the C3, it will return -1 for any pin
|
||||||
|
#if SOC_TOUCH_SENSOR_NUM > 0
|
||||||
|
#include "soc/touch_sensor_periph.h"
|
||||||
|
|
||||||
|
int8_t digitalPinToTouchChannel(uint8_t pin)
|
||||||
|
{
|
||||||
|
int8_t ret = -1;
|
||||||
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
|
for (uint8_t i = 0; i < SOC_TOUCH_SENSOR_NUM; i++) {
|
||||||
|
if (touch_sensor_channel_io_map[i] == pin) {
|
||||||
|
ret = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// No Touch Sensor available
|
||||||
|
int8_t digitalPinToTouchChannel(uint8_t pin)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SOC_ADC_SUPPORTED
|
||||||
|
#include "soc/adc_periph.h"
|
||||||
|
|
||||||
|
int8_t digitalPinToAnalogChannel(uint8_t pin)
|
||||||
|
{
|
||||||
|
uint8_t channel = 0;
|
||||||
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
|
for (uint8_t i = 0; i < SOC_ADC_PERIPH_NUM; i++) {
|
||||||
|
for (uint8_t j = 0; j < SOC_ADC_MAX_CHANNEL_NUM; j++) {
|
||||||
|
if (adc_channel_io_map[i][j] == pin) {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
channel++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t analogChannelToDigitalPin(uint8_t channel)
|
||||||
|
{
|
||||||
|
if (channel >= (SOC_ADC_PERIPH_NUM * SOC_ADC_MAX_CHANNEL_NUM)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t adc_unit = (channel / SOC_ADC_MAX_CHANNEL_NUM);
|
||||||
|
uint8_t adc_chan = (channel % SOC_ADC_MAX_CHANNEL_NUM);
|
||||||
|
return adc_channel_io_map[adc_unit][adc_chan];
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// No Analog channels availible
|
||||||
|
int8_t analogChannelToDigitalPin(uint8_t channel)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (*voidFuncPtr)(void);
|
||||||
|
typedef void (*voidFuncPtrArg)(void*);
|
||||||
|
typedef struct {
|
||||||
|
voidFuncPtr fn;
|
||||||
|
void* arg;
|
||||||
|
bool functional;
|
||||||
|
} InterruptHandle_t;
|
||||||
|
static InterruptHandle_t __pinInterruptHandlers[SOC_GPIO_PIN_COUNT] = {0,};
|
||||||
|
|
||||||
|
#include "driver/rtc_io.h"
|
||||||
|
|
||||||
|
extern void ARDUINO_ISR_ATTR __pinMode(uint8_t pin, uint8_t mode)
|
||||||
|
{
|
||||||
|
#ifdef RGB_BUILTIN
|
||||||
|
if (pin == RGB_BUILTIN){
|
||||||
|
__pinMode(RGB_BUILTIN-SOC_GPIO_PIN_COUNT, mode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!GPIO_IS_VALID_GPIO(pin)) {
|
||||||
|
log_e("Invalid pin selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_hal_context_t gpiohal;
|
||||||
|
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
|
||||||
|
|
||||||
|
gpio_config_t conf = {
|
||||||
|
.pin_bit_mask = (1ULL<<pin), /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
|
||||||
|
.mode = GPIO_MODE_DISABLE, /*!< GPIO mode: set input/output mode */
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE, /*!< GPIO pull-up */
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE, /*!< GPIO pull-down */
|
||||||
|
.intr_type = gpiohal.dev->pin[pin].int_type /*!< GPIO interrupt type - previously set */
|
||||||
|
};
|
||||||
|
if (mode < 0x20) {//io
|
||||||
|
conf.mode = mode & (INPUT | OUTPUT);
|
||||||
|
if (mode & OPEN_DRAIN) {
|
||||||
|
conf.mode |= GPIO_MODE_DEF_OD;
|
||||||
|
}
|
||||||
|
if (mode & PULLUP) {
|
||||||
|
conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||||
|
}
|
||||||
|
if (mode & PULLDOWN) {
|
||||||
|
conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(gpio_config(&conf) != ESP_OK)
|
||||||
|
{
|
||||||
|
log_e("GPIO config failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void ARDUINO_ISR_ATTR __digitalWrite(uint8_t pin, uint8_t val)
|
||||||
|
{
|
||||||
|
#ifdef RGB_BUILTIN
|
||||||
|
if(pin == RGB_BUILTIN){
|
||||||
|
//use RMT to set all channels on/off
|
||||||
|
const uint8_t comm_val = val != 0 ? RGB_BRIGHTNESS : 0;
|
||||||
|
neopixelWrite(RGB_BUILTIN, comm_val, comm_val, comm_val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
gpio_set_level((gpio_num_t)pin, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int ARDUINO_ISR_ATTR __digitalRead(uint8_t pin)
|
||||||
|
{
|
||||||
|
return gpio_get_level((gpio_num_t)pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ARDUINO_ISR_ATTR __onPinInterrupt(void * arg) {
|
||||||
|
InterruptHandle_t * isr = (InterruptHandle_t*)arg;
|
||||||
|
if(isr->fn) {
|
||||||
|
if(isr->arg){
|
||||||
|
((voidFuncPtrArg)isr->fn)(isr->arg);
|
||||||
|
} else {
|
||||||
|
isr->fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void cleanupFunctional(void* arg);
|
||||||
|
|
||||||
|
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional)
|
||||||
|
{
|
||||||
|
static bool interrupt_initialized = false;
|
||||||
|
|
||||||
|
// makes sure that pin -1 (255) will never work -- this follows Arduino standard
|
||||||
|
if (pin >= SOC_GPIO_PIN_COUNT) return;
|
||||||
|
|
||||||
|
if(!interrupt_initialized) {
|
||||||
|
esp_err_t err = gpio_install_isr_service((int)ARDUINO_ISR_FLAG);
|
||||||
|
interrupt_initialized = (err == ESP_OK) || (err == ESP_ERR_INVALID_STATE);
|
||||||
|
}
|
||||||
|
if(!interrupt_initialized) {
|
||||||
|
log_e("GPIO ISR Service Failed To Start");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if new attach without detach remove old info
|
||||||
|
if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg)
|
||||||
|
{
|
||||||
|
cleanupFunctional(__pinInterruptHandlers[pin].arg);
|
||||||
|
}
|
||||||
|
__pinInterruptHandlers[pin].fn = (voidFuncPtr)userFunc;
|
||||||
|
__pinInterruptHandlers[pin].arg = arg;
|
||||||
|
__pinInterruptHandlers[pin].functional = functional;
|
||||||
|
|
||||||
|
gpio_set_intr_type((gpio_num_t)pin, (gpio_int_type_t)(intr_type & 0x7));
|
||||||
|
if(intr_type & 0x8){
|
||||||
|
gpio_wakeup_enable((gpio_num_t)pin, (gpio_int_type_t)(intr_type & 0x7));
|
||||||
|
}
|
||||||
|
gpio_isr_handler_add((gpio_num_t)pin, __onPinInterrupt, &__pinInterruptHandlers[pin]);
|
||||||
|
|
||||||
|
|
||||||
|
//FIX interrupts on peripherals outputs (eg. LEDC,...)
|
||||||
|
//Enable input in GPIO register
|
||||||
|
gpio_hal_context_t gpiohal;
|
||||||
|
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
|
||||||
|
gpio_hal_input_enable(&gpiohal, pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type)
|
||||||
|
{
|
||||||
|
__attachInterruptFunctionalArg(pin, userFunc, arg, intr_type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int intr_type) {
|
||||||
|
__attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, NULL, intr_type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __detachInterrupt(uint8_t pin)
|
||||||
|
{
|
||||||
|
gpio_isr_handler_remove((gpio_num_t)pin); //remove handle and disable isr for pin
|
||||||
|
gpio_wakeup_disable((gpio_num_t)pin);
|
||||||
|
|
||||||
|
if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg)
|
||||||
|
{
|
||||||
|
cleanupFunctional(__pinInterruptHandlers[pin].arg);
|
||||||
|
}
|
||||||
|
__pinInterruptHandlers[pin].fn = NULL;
|
||||||
|
__pinInterruptHandlers[pin].arg = NULL;
|
||||||
|
__pinInterruptHandlers[pin].functional = false;
|
||||||
|
|
||||||
|
gpio_set_intr_type((gpio_num_t)pin, GPIO_INTR_DISABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
|
||||||
|
extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite")));
|
||||||
|
extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead")));
|
||||||
|
extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt")));
|
||||||
|
extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void * arg, int mode) __attribute__ ((weak, alias("__attachInterruptArg")));
|
||||||
|
extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt")));
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
Arduino.h - Main include file for the Arduino SDK
|
||||||
|
Copyright (c) 2005-2013 Arduino Team. All right reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MAIN_ESP32_HAL_GPIO_H_
|
||||||
|
#define MAIN_ESP32_HAL_GPIO_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
|
||||||
|
#if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
#define NUM_OUPUT_PINS 46
|
||||||
|
#define PIN_DAC1 17
|
||||||
|
#define PIN_DAC2 18
|
||||||
|
#else
|
||||||
|
#define NUM_OUPUT_PINS 34
|
||||||
|
#define PIN_DAC1 25
|
||||||
|
#define PIN_DAC2 26
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LOW 0x0
|
||||||
|
#define HIGH 0x1
|
||||||
|
|
||||||
|
//GPIO FUNCTIONS
|
||||||
|
#define INPUT 0x01
|
||||||
|
// Changed OUTPUT from 0x02 to behave the same as Arduino pinMode(pin,OUTPUT)
|
||||||
|
// where you can read the state of pin even when it is set as OUTPUT
|
||||||
|
#define OUTPUT 0x03
|
||||||
|
#define PULLUP 0x04
|
||||||
|
#define INPUT_PULLUP 0x05
|
||||||
|
#define PULLDOWN 0x08
|
||||||
|
#define INPUT_PULLDOWN 0x09
|
||||||
|
#define OPEN_DRAIN 0x10
|
||||||
|
#define OUTPUT_OPEN_DRAIN 0x13
|
||||||
|
#define ANALOG 0xC0
|
||||||
|
|
||||||
|
//Interrupt Modes
|
||||||
|
#define DISABLED 0x00
|
||||||
|
#define RISING 0x01
|
||||||
|
#define FALLING 0x02
|
||||||
|
#define CHANGE 0x03
|
||||||
|
#define ONLOW 0x04
|
||||||
|
#define ONHIGH 0x05
|
||||||
|
#define ONLOW_WE 0x0C
|
||||||
|
#define ONHIGH_WE 0x0D
|
||||||
|
|
||||||
|
|
||||||
|
#define digitalPinIsValid(pin) GPIO_IS_VALID_GPIO(pin)
|
||||||
|
#define digitalPinCanOutput(pin) GPIO_IS_VALID_OUTPUT_GPIO(pin)
|
||||||
|
|
||||||
|
#define digitalPinToRtcPin(pin) ((RTC_GPIO_IS_VALID_GPIO(pin))?rtc_io_number_get(pin):-1)
|
||||||
|
#define digitalPinToDacChannel(pin) (((pin) == DAC_CHANNEL_1_GPIO_NUM)?0:((pin) == DAC_CHANNEL_2_GPIO_NUM)?1:-1)
|
||||||
|
|
||||||
|
void pinMode(uint8_t pin, uint8_t mode);
|
||||||
|
void digitalWrite(uint8_t pin, uint8_t val);
|
||||||
|
int digitalRead(uint8_t pin);
|
||||||
|
|
||||||
|
void attachInterrupt(uint8_t pin, void (*)(void), int mode);
|
||||||
|
void attachInterruptArg(uint8_t pin, void (*)(void*), void * arg, int mode);
|
||||||
|
void detachInterrupt(uint8_t pin);
|
||||||
|
|
||||||
|
int8_t digitalPinToTouchChannel(uint8_t pin);
|
||||||
|
int8_t digitalPinToAnalogChannel(uint8_t pin);
|
||||||
|
int8_t analogChannelToDigitalPin(uint8_t channel);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MAIN_ESP32_HAL_GPIO_H_ */
|
@ -0,0 +1,841 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "esp_attr.h"
|
||||||
|
#include "rom/gpio.h"
|
||||||
|
#include "soc/gpio_sig_map.h"
|
||||||
|
#include "hal/gpio_types.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/ringbuf.h"
|
||||||
|
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
#include "driver/periph_ctrl.h"
|
||||||
|
#include "soc/i2c_reg.h"
|
||||||
|
#include "soc/i2c_struct.h"
|
||||||
|
#include "hal/i2c_ll.h"
|
||||||
|
#include "esp32-hal-log.h"
|
||||||
|
#include "esp32-hal-i2c-slave.h"
|
||||||
|
|
||||||
|
#define I2C_SLAVE_USE_RX_QUEUE 0 // 1: Queue, 0: RingBuffer
|
||||||
|
|
||||||
|
#if SOC_I2C_NUM > 1
|
||||||
|
#define I2C_SCL_IDX(p) ((p==0)?I2CEXT0_SCL_OUT_IDX:((p==1)?I2CEXT1_SCL_OUT_IDX:0))
|
||||||
|
#define I2C_SDA_IDX(p) ((p==0)?I2CEXT0_SDA_OUT_IDX:((p==1)?I2CEXT1_SDA_OUT_IDX:0))
|
||||||
|
#else
|
||||||
|
#define I2C_SCL_IDX(p) I2CEXT0_SCL_OUT_IDX
|
||||||
|
#define I2C_SDA_IDX(p) I2CEXT0_SDA_OUT_IDX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define I2C_TXFIFO_WM_INT_ENA I2C_TXFIFO_EMPTY_INT_ENA
|
||||||
|
#define I2C_RXFIFO_WM_INT_ENA I2C_RXFIFO_FULL_INT_ENA
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum {
|
||||||
|
I2C_SLAVE_EVT_RX, I2C_SLAVE_EVT_TX
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct i2c_slave_struct_t {
|
||||||
|
i2c_dev_t * dev;
|
||||||
|
uint8_t num;
|
||||||
|
int8_t sda;
|
||||||
|
int8_t scl;
|
||||||
|
i2c_slave_request_cb_t request_callback;
|
||||||
|
i2c_slave_receive_cb_t receive_callback;
|
||||||
|
void * arg;
|
||||||
|
intr_handle_t intr_handle;
|
||||||
|
TaskHandle_t task_handle;
|
||||||
|
xQueueHandle event_queue;
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
xQueueHandle rx_queue;
|
||||||
|
#else
|
||||||
|
RingbufHandle_t rx_ring_buf;
|
||||||
|
#endif
|
||||||
|
xQueueHandle tx_queue;
|
||||||
|
uint32_t rx_data_count;
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
xSemaphoreHandle lock;
|
||||||
|
#endif
|
||||||
|
} i2c_slave_struct_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint32_t event : 2;
|
||||||
|
uint32_t stop : 1;
|
||||||
|
uint32_t param : 29;
|
||||||
|
};
|
||||||
|
uint32_t val;
|
||||||
|
} i2c_slave_queue_event_t;
|
||||||
|
|
||||||
|
static i2c_slave_struct_t _i2c_bus_array[SOC_I2C_NUM] = {
|
||||||
|
{ &I2C0, 0, -1, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
, NULL
|
||||||
|
#endif
|
||||||
|
},
|
||||||
|
#if SOC_I2C_NUM > 1
|
||||||
|
{ &I2C1, 1, -1, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
, NULL
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#if CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
#define I2C_SLAVE_MUTEX_LOCK()
|
||||||
|
#define I2C_SLAVE_MUTEX_UNLOCK()
|
||||||
|
#else
|
||||||
|
#define I2C_SLAVE_MUTEX_LOCK() if(i2c->lock){xSemaphoreTake(i2c->lock, portMAX_DELAY);}
|
||||||
|
#define I2C_SLAVE_MUTEX_UNLOCK() if(i2c->lock){xSemaphoreGive(i2c->lock);}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------- HAL_LL (Missing Functions) ------------------------------------------------
|
||||||
|
typedef enum {
|
||||||
|
I2C_STRETCH_CAUSE_MASTER_READ,
|
||||||
|
I2C_STRETCH_CAUSE_TX_FIFO_EMPTY,
|
||||||
|
I2C_STRETCH_CAUSE_RX_FIFO_FULL,
|
||||||
|
I2C_STRETCH_CAUSE_MAX
|
||||||
|
} i2c_stretch_cause_t;
|
||||||
|
|
||||||
|
static inline i2c_stretch_cause_t i2c_ll_stretch_cause(i2c_dev_t *hw)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return hw->sr.stretch_cause;
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
return hw->status_reg.stretch_cause;
|
||||||
|
#else
|
||||||
|
return I2C_STRETCH_CAUSE_MAX;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void i2c_ll_set_stretch(i2c_dev_t *hw, uint16_t time)
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
typeof(hw->scl_stretch_conf) scl_stretch_conf;
|
||||||
|
scl_stretch_conf.val = 0;
|
||||||
|
scl_stretch_conf.slave_scl_stretch_en = (time > 0);
|
||||||
|
scl_stretch_conf.stretch_protect_num = time;
|
||||||
|
scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||||
|
hw->scl_stretch_conf.val = scl_stretch_conf.val;
|
||||||
|
if(time > 0){
|
||||||
|
//enable interrupt
|
||||||
|
hw->int_ena.val |= I2C_SLAVE_STRETCH_INT_ENA;
|
||||||
|
} else {
|
||||||
|
//disable interrupt
|
||||||
|
hw->int_ena.val &= (~I2C_SLAVE_STRETCH_INT_ENA);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void i2c_ll_stretch_clr(i2c_dev_t *hw)
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
hw->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool i2c_ll_slave_addressed(i2c_dev_t *hw)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return hw->sr.slave_addressed;
|
||||||
|
#else
|
||||||
|
return hw->status_reg.slave_addressed;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool i2c_ll_slave_rw(i2c_dev_t *hw)//not exposed by hal_ll
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return hw->sr.slave_rw;
|
||||||
|
#else
|
||||||
|
return hw->status_reg.slave_rw;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------- PRIVATE (Function Prototypes) ------------------------------------------------
|
||||||
|
static void i2c_slave_free_resources(i2c_slave_struct_t * i2c);
|
||||||
|
static void i2c_slave_delay_us(uint64_t us);
|
||||||
|
static void i2c_slave_gpio_mode(int8_t pin, gpio_mode_t mode);
|
||||||
|
static bool i2c_slave_check_line_state(int8_t sda, int8_t scl);
|
||||||
|
static bool i2c_slave_attach_gpio(i2c_slave_struct_t * i2c, int8_t sda, int8_t scl);
|
||||||
|
static bool i2c_slave_detach_gpio(i2c_slave_struct_t * i2c);
|
||||||
|
static bool i2c_slave_set_frequency(i2c_slave_struct_t * i2c, uint32_t clk_speed);
|
||||||
|
static bool i2c_slave_send_event(i2c_slave_struct_t * i2c, i2c_slave_queue_event_t* event);
|
||||||
|
static bool i2c_slave_handle_tx_fifo_empty(i2c_slave_struct_t * i2c);
|
||||||
|
static bool i2c_slave_handle_rx_fifo_full(i2c_slave_struct_t * i2c, uint32_t len);
|
||||||
|
static size_t i2c_slave_read_rx(i2c_slave_struct_t * i2c, uint8_t * data, size_t len);
|
||||||
|
static void i2c_slave_isr_handler(void* arg);
|
||||||
|
static void i2c_slave_task(void *pv_args);
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================================================================================================
|
||||||
|
//-------------------------------------- Public Functions -------------------------------------------------------------
|
||||||
|
//=====================================================================================================================
|
||||||
|
|
||||||
|
esp_err_t i2cSlaveAttachCallbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg){
|
||||||
|
if(num >= SOC_I2C_NUM){
|
||||||
|
log_e("Invalid port num: %u", num);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
i2c_slave_struct_t * i2c = &_i2c_bus_array[num];
|
||||||
|
I2C_SLAVE_MUTEX_LOCK();
|
||||||
|
i2c->request_callback = request_callback;
|
||||||
|
i2c->receive_callback = receive_callback;
|
||||||
|
i2c->arg = arg;
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cSlaveInit(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len) {
|
||||||
|
if(num >= SOC_I2C_NUM){
|
||||||
|
log_e("Invalid port num: %u", num);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sda < 0 || scl < 0) {
|
||||||
|
log_e("invalid pins sda=%d, scl=%d", sda, scl);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!frequency){
|
||||||
|
frequency = 100000;
|
||||||
|
} else if(frequency > 1000000){
|
||||||
|
frequency = 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_i("Initialising I2C Slave: sda=%d scl=%d freq=%d, addr=0x%x", sda, scl, frequency, slaveID);
|
||||||
|
|
||||||
|
i2c_slave_struct_t * i2c = &_i2c_bus_array[num];
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(!i2c->lock){
|
||||||
|
i2c->lock = xSemaphoreCreateMutex();
|
||||||
|
if (i2c->lock == NULL) {
|
||||||
|
log_e("RX queue create failed");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
I2C_SLAVE_MUTEX_LOCK();
|
||||||
|
i2c_slave_free_resources(i2c);
|
||||||
|
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
i2c->rx_queue = xQueueCreate(rx_len, sizeof(uint8_t));
|
||||||
|
if (i2c->rx_queue == NULL) {
|
||||||
|
log_e("RX queue create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
i2c->rx_ring_buf = xRingbufferCreate(rx_len, RINGBUF_TYPE_BYTEBUF);
|
||||||
|
if (i2c->rx_ring_buf == NULL) {
|
||||||
|
log_e("RX RingBuf create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
i2c->tx_queue = xQueueCreate(tx_len, sizeof(uint8_t));
|
||||||
|
if (i2c->tx_queue == NULL) {
|
||||||
|
log_e("TX queue create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c->event_queue = xQueueCreate(16, sizeof(i2c_slave_queue_event_t));
|
||||||
|
if (i2c->event_queue == NULL) {
|
||||||
|
log_e("Event queue create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
xTaskCreate(i2c_slave_task, "i2c_slave_task", 4096, i2c, 20, &i2c->task_handle);
|
||||||
|
if(i2c->task_handle == NULL){
|
||||||
|
log_e("Event thread create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frequency == 0) {
|
||||||
|
frequency = 100000L;
|
||||||
|
}
|
||||||
|
frequency = (frequency * 5) / 4;
|
||||||
|
|
||||||
|
if (i2c->num == 0) {
|
||||||
|
periph_module_enable(PERIPH_I2C0_MODULE);
|
||||||
|
#if SOC_I2C_NUM > 1
|
||||||
|
} else {
|
||||||
|
periph_module_enable(PERIPH_I2C1_MODULE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_ll_slave_init(i2c->dev);
|
||||||
|
i2c_ll_set_fifo_mode(i2c->dev, true);
|
||||||
|
i2c_ll_set_slave_addr(i2c->dev, slaveID, false);
|
||||||
|
i2c_ll_set_tout(i2c->dev, I2C_LL_MAX_TIMEOUT);
|
||||||
|
i2c_slave_set_frequency(i2c, frequency);
|
||||||
|
|
||||||
|
if (!i2c_slave_check_line_state(sda, scl)) {
|
||||||
|
log_e("bad pin state");
|
||||||
|
ret = ESP_FAIL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_slave_attach_gpio(i2c, sda, scl);
|
||||||
|
|
||||||
|
if (i2c_ll_is_bus_busy(i2c->dev)) {
|
||||||
|
log_w("Bus busy, reinit");
|
||||||
|
ret = ESP_FAIL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_ll_disable_intr_mask(i2c->dev, I2C_LL_INTR_MASK);
|
||||||
|
i2c_ll_clr_intsts_mask(i2c->dev, I2C_LL_INTR_MASK);
|
||||||
|
i2c_ll_set_fifo_mode(i2c->dev, true);
|
||||||
|
|
||||||
|
if (!i2c->intr_handle) {
|
||||||
|
uint32_t flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_SHARED;
|
||||||
|
if(i2c->num == 0) {
|
||||||
|
ret = esp_intr_alloc(ETS_I2C_EXT0_INTR_SOURCE, flags, &i2c_slave_isr_handler, i2c, &i2c->intr_handle);
|
||||||
|
#if SOC_I2C_NUM > 1
|
||||||
|
} else {
|
||||||
|
ret = esp_intr_alloc(ETS_I2C_EXT1_INTR_SOURCE, flags, &i2c_slave_isr_handler, i2c, &i2c->intr_handle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
log_e("install interrupt handler Failed=%d", ret);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_ll_txfifo_rst(i2c->dev);
|
||||||
|
i2c_ll_rxfifo_rst(i2c->dev);
|
||||||
|
i2c_ll_slave_enable_rx_it(i2c->dev);
|
||||||
|
i2c_ll_set_stretch(i2c->dev, 0x3FF);
|
||||||
|
i2c_ll_update(i2c->dev);
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
i2c_slave_free_resources(i2c);
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cSlaveDeinit(uint8_t num){
|
||||||
|
if(num >= SOC_I2C_NUM){
|
||||||
|
log_e("Invalid port num: %u", num);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_slave_struct_t * i2c = &_i2c_bus_array[num];
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(!i2c->lock){
|
||||||
|
log_e("Lock is not initialized! Did you call i2c_slave_init()?");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
I2C_SLAVE_MUTEX_LOCK();
|
||||||
|
i2c_slave_free_resources(i2c);
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i2cSlaveWrite(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms) {
|
||||||
|
if(num >= SOC_I2C_NUM){
|
||||||
|
log_e("Invalid port num: %u", num);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t to_queue = 0, to_fifo = 0;
|
||||||
|
i2c_slave_struct_t * i2c = &_i2c_bus_array[num];
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(!i2c->lock){
|
||||||
|
log_e("Lock is not initialized! Did you call i2c_slave_init()?");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(!i2c->tx_queue){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
I2C_SLAVE_MUTEX_LOCK();
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
i2c_ll_slave_disable_tx_it(i2c->dev);
|
||||||
|
if (i2c_ll_get_txfifo_len(i2c->dev) < SOC_I2C_FIFO_LEN) {
|
||||||
|
i2c_ll_txfifo_rst(i2c->dev);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
to_fifo = i2c_ll_get_txfifo_len(i2c->dev);
|
||||||
|
if(len < to_fifo){
|
||||||
|
to_fifo = len;
|
||||||
|
}
|
||||||
|
i2c_ll_write_txfifo(i2c->dev, (uint8_t*)buf, to_fifo);
|
||||||
|
buf += to_fifo;
|
||||||
|
len -= to_fifo;
|
||||||
|
//reset tx_queue
|
||||||
|
xQueueReset(i2c->tx_queue);
|
||||||
|
//write the rest of the bytes to the queue
|
||||||
|
if(len){
|
||||||
|
to_queue = uxQueueSpacesAvailable(i2c->tx_queue);
|
||||||
|
if(len < to_queue){
|
||||||
|
to_queue = len;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < to_queue; i++) {
|
||||||
|
if (xQueueSend(i2c->tx_queue, &buf[i], timeout_ms / portTICK_RATE_MS) != pdTRUE) {
|
||||||
|
xQueueReset(i2c->tx_queue);
|
||||||
|
to_queue = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//no need to enable TX_EMPTY if tx_queue is empty
|
||||||
|
if(to_queue){
|
||||||
|
i2c_ll_slave_enable_tx_it(i2c->dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return to_queue + to_fifo;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=====================================================================================================================
|
||||||
|
//-------------------------------------- Private Functions ------------------------------------------------------------
|
||||||
|
//=====================================================================================================================
|
||||||
|
|
||||||
|
static void i2c_slave_free_resources(i2c_slave_struct_t * i2c){
|
||||||
|
i2c_slave_detach_gpio(i2c);
|
||||||
|
i2c_ll_set_slave_addr(i2c->dev, 0, false);
|
||||||
|
i2c_ll_disable_intr_mask(i2c->dev, I2C_LL_INTR_MASK);
|
||||||
|
i2c_ll_clr_intsts_mask(i2c->dev, I2C_LL_INTR_MASK);
|
||||||
|
|
||||||
|
if (i2c->intr_handle) {
|
||||||
|
esp_intr_free(i2c->intr_handle);
|
||||||
|
i2c->intr_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i2c->task_handle){
|
||||||
|
vTaskDelete(i2c->task_handle);
|
||||||
|
i2c->task_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
if (i2c->rx_queue) {
|
||||||
|
vQueueDelete(i2c->rx_queue);
|
||||||
|
i2c->rx_queue = NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (i2c->rx_ring_buf) {
|
||||||
|
vRingbufferDelete(i2c->rx_ring_buf);
|
||||||
|
i2c->rx_ring_buf = NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (i2c->tx_queue) {
|
||||||
|
vQueueDelete(i2c->tx_queue);
|
||||||
|
i2c->tx_queue = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i2c->event_queue) {
|
||||||
|
vQueueDelete(i2c->event_queue);
|
||||||
|
i2c->event_queue = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c->rx_data_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_set_frequency(i2c_slave_struct_t * i2c, uint32_t clk_speed)
|
||||||
|
{
|
||||||
|
if (i2c == NULL) {
|
||||||
|
log_e("no control buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(clk_speed > 1100000UL){
|
||||||
|
clk_speed = 1100000UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust Fifo thresholds based on frequency
|
||||||
|
uint32_t a = (clk_speed / 50000L) + 2;
|
||||||
|
log_d("Fifo thresholds: rx_fifo_full = %d, tx_fifo_empty = %d", SOC_I2C_FIFO_LEN - a, a);
|
||||||
|
|
||||||
|
i2c_clk_cal_t clk_cal;
|
||||||
|
#if SOC_I2C_SUPPORT_APB
|
||||||
|
i2c_ll_cal_bus_clk(APB_CLK_FREQ, clk_speed, &clk_cal);
|
||||||
|
i2c_ll_set_source_clk(i2c->dev, I2C_SCLK_APB); /*!< I2C source clock from APB, 80M*/
|
||||||
|
#elif SOC_I2C_SUPPORT_XTAL
|
||||||
|
i2c_ll_cal_bus_clk(XTAL_CLK_FREQ, clk_speed, &clk_cal);
|
||||||
|
i2c_ll_set_source_clk(i2c->dev, I2C_SCLK_XTAL); /*!< I2C source clock from XTAL, 40M */
|
||||||
|
#endif
|
||||||
|
i2c_ll_set_txfifo_empty_thr(i2c->dev, a);
|
||||||
|
i2c_ll_set_rxfifo_full_thr(i2c->dev, SOC_I2C_FIFO_LEN - a);
|
||||||
|
i2c_ll_set_bus_timing(i2c->dev, &clk_cal);
|
||||||
|
i2c_ll_set_filter(i2c->dev, 3);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_delay_us(uint64_t us)
|
||||||
|
{
|
||||||
|
uint64_t m = esp_timer_get_time();
|
||||||
|
if (us) {
|
||||||
|
uint64_t e = (m + us);
|
||||||
|
if (m > e) { //overflow
|
||||||
|
while ((uint64_t)esp_timer_get_time() > e);
|
||||||
|
}
|
||||||
|
while ((uint64_t)esp_timer_get_time() < e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_gpio_mode(int8_t pin, gpio_mode_t mode)
|
||||||
|
{
|
||||||
|
gpio_config_t conf = {
|
||||||
|
.pin_bit_mask = 1LL << pin,
|
||||||
|
.mode = mode,
|
||||||
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE
|
||||||
|
};
|
||||||
|
gpio_config(&conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_check_line_state(int8_t sda, int8_t scl)
|
||||||
|
{
|
||||||
|
if (sda < 0 || scl < 0) {
|
||||||
|
return false;//return false since there is nothing to do
|
||||||
|
}
|
||||||
|
// if the bus is not 'clear' try the cycling SCL until SDA goes High or 9 cycles
|
||||||
|
gpio_set_level(sda, 1);
|
||||||
|
gpio_set_level(scl, 1);
|
||||||
|
i2c_slave_gpio_mode(sda, GPIO_MODE_INPUT | GPIO_MODE_DEF_OD);
|
||||||
|
i2c_slave_gpio_mode(scl, GPIO_MODE_INPUT | GPIO_MODE_DEF_OD);
|
||||||
|
gpio_set_level(scl, 1);
|
||||||
|
|
||||||
|
if (!gpio_get_level(sda) || !gpio_get_level(scl)) { // bus in busy state
|
||||||
|
log_w("invalid state sda(%d)=%d, scl(%d)=%d", sda, gpio_get_level(sda), scl, gpio_get_level(scl));
|
||||||
|
for (uint8_t a=0; a<9; a++) {
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
if (gpio_get_level(sda) && gpio_get_level(scl)) { // bus recovered
|
||||||
|
log_w("Recovered after %d Cycles",a);
|
||||||
|
gpio_set_level(sda,0); // start
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
for (uint8_t a=0;a<9; a++) {
|
||||||
|
gpio_set_level(scl,1);
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
gpio_set_level(scl,0);
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
}
|
||||||
|
gpio_set_level(scl,1);
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
gpio_set_level(sda,1); // stop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gpio_set_level(scl, 0);
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
gpio_set_level(scl, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gpio_get_level(sda) || !gpio_get_level(scl)) { // bus in busy state
|
||||||
|
log_e("Bus Invalid State, Can't init sda=%d, scl=%d",gpio_get_level(sda),gpio_get_level(scl));
|
||||||
|
return false; // bus is busy
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_attach_gpio(i2c_slave_struct_t * i2c, int8_t sda, int8_t scl)
|
||||||
|
{
|
||||||
|
if (i2c == NULL) {
|
||||||
|
log_e("no control block");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((sda < 0)||( scl < 0)) {
|
||||||
|
log_e("bad pins sda=%d, scl=%d",sda,scl);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c->scl = scl;
|
||||||
|
gpio_set_level(scl, 1);
|
||||||
|
i2c_slave_gpio_mode(scl, GPIO_MODE_INPUT_OUTPUT_OD);
|
||||||
|
gpio_matrix_out(scl, I2C_SCL_IDX(i2c->num), false, false);
|
||||||
|
gpio_matrix_in(scl, I2C_SCL_IDX(i2c->num), false);
|
||||||
|
|
||||||
|
i2c->sda = sda;
|
||||||
|
gpio_set_level(sda, 1);
|
||||||
|
i2c_slave_gpio_mode(sda, GPIO_MODE_INPUT_OUTPUT_OD);
|
||||||
|
gpio_matrix_out(sda, I2C_SDA_IDX(i2c->num), false, false);
|
||||||
|
gpio_matrix_in(sda, I2C_SDA_IDX(i2c->num), false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_detach_gpio(i2c_slave_struct_t * i2c)
|
||||||
|
{
|
||||||
|
if (i2c == NULL) {
|
||||||
|
log_e("no control Block");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (i2c->scl >= 0) {
|
||||||
|
gpio_matrix_out(i2c->scl, 0x100, false, false);
|
||||||
|
gpio_matrix_in(0x30, I2C_SCL_IDX(i2c->num), false);
|
||||||
|
i2c_slave_gpio_mode(i2c->scl, GPIO_MODE_INPUT);
|
||||||
|
i2c->scl = -1; // un attached
|
||||||
|
}
|
||||||
|
if (i2c->sda >= 0) {
|
||||||
|
gpio_matrix_out(i2c->sda, 0x100, false, false);
|
||||||
|
gpio_matrix_in(0x30, I2C_SDA_IDX(i2c->num), false);
|
||||||
|
i2c_slave_gpio_mode(i2c->sda, GPIO_MODE_INPUT);
|
||||||
|
i2c->sda = -1; // un attached
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_send_event(i2c_slave_struct_t * i2c, i2c_slave_queue_event_t* event)
|
||||||
|
{
|
||||||
|
bool pxHigherPriorityTaskWoken = false;
|
||||||
|
if(i2c->event_queue) {
|
||||||
|
if(xQueueSendFromISR(i2c->event_queue, event, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){
|
||||||
|
//log_e("event_queue_full");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxHigherPriorityTaskWoken;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_handle_tx_fifo_empty(i2c_slave_struct_t * i2c)
|
||||||
|
{
|
||||||
|
bool pxHigherPriorityTaskWoken = false;
|
||||||
|
uint32_t d = 0, moveCnt = i2c_ll_get_txfifo_len(i2c->dev);
|
||||||
|
while (moveCnt > 0) { // read tx queue until Fifo is full or queue is empty
|
||||||
|
if(xQueueReceiveFromISR(i2c->tx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) == pdTRUE){
|
||||||
|
i2c_ll_write_txfifo(i2c->dev, (uint8_t*)&d, 1);
|
||||||
|
moveCnt--;
|
||||||
|
} else {
|
||||||
|
i2c_ll_slave_disable_tx_it(i2c->dev);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxHigherPriorityTaskWoken;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_handle_rx_fifo_full(i2c_slave_struct_t * i2c, uint32_t len)
|
||||||
|
{
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
uint32_t d = 0;
|
||||||
|
#else
|
||||||
|
uint8_t data[SOC_I2C_FIFO_LEN];
|
||||||
|
#endif
|
||||||
|
bool pxHigherPriorityTaskWoken = false;
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
while (len > 0) {
|
||||||
|
i2c_ll_read_rxfifo(i2c->dev, (uint8_t*)&d, 1);
|
||||||
|
if(xQueueSendFromISR(i2c->rx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){
|
||||||
|
log_e("rx_queue_full");
|
||||||
|
} else {
|
||||||
|
i2c->rx_data_count++;
|
||||||
|
}
|
||||||
|
if (--len == 0) {
|
||||||
|
len = i2c_ll_get_rxfifo_cnt(i2c->dev);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if(len){
|
||||||
|
i2c_ll_read_rxfifo(i2c->dev, data, len);
|
||||||
|
if(xRingbufferSendFromISR(i2c->rx_ring_buf, (void*) data, len, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){
|
||||||
|
log_e("rx_ring_buf_full");
|
||||||
|
} else {
|
||||||
|
i2c->rx_data_count += len;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return pxHigherPriorityTaskWoken;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_isr_handler(void* arg)
|
||||||
|
{
|
||||||
|
bool pxHigherPriorityTaskWoken = false;
|
||||||
|
i2c_slave_struct_t * i2c = (i2c_slave_struct_t *) arg; // recover data
|
||||||
|
|
||||||
|
uint32_t activeInt = i2c_ll_get_intsts_mask(i2c->dev);
|
||||||
|
i2c_ll_clr_intsts_mask(i2c->dev, activeInt);
|
||||||
|
uint8_t rx_fifo_len = i2c_ll_get_rxfifo_cnt(i2c->dev);
|
||||||
|
bool slave_rw = i2c_ll_slave_rw(i2c->dev);
|
||||||
|
|
||||||
|
if(activeInt & I2C_RXFIFO_WM_INT_ENA){ // RX FiFo Full
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len);
|
||||||
|
i2c_ll_slave_enable_rx_it(i2c->dev);//is this necessary?
|
||||||
|
}
|
||||||
|
|
||||||
|
if(activeInt & I2C_TRANS_COMPLETE_INT_ENA){ // STOP
|
||||||
|
if(rx_fifo_len){ //READ RX FIFO
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len);
|
||||||
|
}
|
||||||
|
if(i2c->rx_data_count){ //WRITE or RepeatedStart
|
||||||
|
//SEND RX Event
|
||||||
|
i2c_slave_queue_event_t event;
|
||||||
|
event.event = I2C_SLAVE_EVT_RX;
|
||||||
|
event.stop = !slave_rw;
|
||||||
|
event.param = i2c->rx_data_count;
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event);
|
||||||
|
//Zero RX count
|
||||||
|
i2c->rx_data_count = 0;
|
||||||
|
}
|
||||||
|
if(slave_rw){ // READ
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
if(i2c->dev->status_reg.scl_main_state_last == 6){
|
||||||
|
//SEND TX Event
|
||||||
|
i2c_slave_queue_event_t event;
|
||||||
|
event.event = I2C_SLAVE_EVT_TX;
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
//reset TX data
|
||||||
|
i2c_ll_txfifo_rst(i2c->dev);
|
||||||
|
uint8_t d;
|
||||||
|
while (xQueueReceiveFromISR(i2c->tx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) == pdTRUE) ;//flush partial write
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
if(activeInt & I2C_SLAVE_STRETCH_INT_ENA){ // STRETCH
|
||||||
|
i2c_stretch_cause_t cause = i2c_ll_stretch_cause(i2c->dev);
|
||||||
|
if(cause == I2C_STRETCH_CAUSE_MASTER_READ){
|
||||||
|
//on C3 RX data dissapears with repeated start, so we need to get it here
|
||||||
|
if(rx_fifo_len){
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len);
|
||||||
|
}
|
||||||
|
//SEND TX Event
|
||||||
|
i2c_slave_queue_event_t event;
|
||||||
|
event.event = I2C_SLAVE_EVT_TX;
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event);
|
||||||
|
//will clear after execution
|
||||||
|
} else if(cause == I2C_STRETCH_CAUSE_TX_FIFO_EMPTY){
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_tx_fifo_empty(i2c);
|
||||||
|
i2c_ll_stretch_clr(i2c->dev);
|
||||||
|
} else if(cause == I2C_STRETCH_CAUSE_RX_FIFO_FULL){
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len);
|
||||||
|
i2c_ll_stretch_clr(i2c->dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(activeInt & I2C_TXFIFO_WM_INT_ENA){ // TX FiFo Empty
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_tx_fifo_empty(i2c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pxHigherPriorityTaskWoken){
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t i2c_slave_read_rx(i2c_slave_struct_t * i2c, uint8_t * data, size_t len){
|
||||||
|
if(!len){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
uint8_t d = 0;
|
||||||
|
BaseType_t res = pdTRUE;
|
||||||
|
for(size_t i=0; i<len; i++) {
|
||||||
|
if(data){
|
||||||
|
res = xQueueReceive(i2c->rx_queue, &data[i], 0);
|
||||||
|
} else {
|
||||||
|
res = xQueueReceive(i2c->rx_queue, &d, 0);
|
||||||
|
}
|
||||||
|
if (res != pdTRUE) {
|
||||||
|
log_e("Read Queue(%u) Failed", i);
|
||||||
|
len = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (data)?len:0;
|
||||||
|
#else
|
||||||
|
size_t dlen = 0,
|
||||||
|
to_read = len,
|
||||||
|
so_far = 0,
|
||||||
|
available = 0;
|
||||||
|
uint8_t * rx_data = NULL;
|
||||||
|
|
||||||
|
vRingbufferGetInfo(i2c->rx_ring_buf, NULL, NULL, NULL, NULL, &available);
|
||||||
|
if(available < to_read){
|
||||||
|
log_e("Less available than requested. %u < %u", available, len);
|
||||||
|
to_read = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(to_read){
|
||||||
|
dlen = 0;
|
||||||
|
rx_data = (uint8_t *)xRingbufferReceiveUpTo(i2c->rx_ring_buf, &dlen, 0, to_read);
|
||||||
|
if(!rx_data){
|
||||||
|
log_e("Receive %u Failed", to_read);
|
||||||
|
return so_far;
|
||||||
|
}
|
||||||
|
if(data){
|
||||||
|
memcpy(data+so_far, rx_data, dlen);
|
||||||
|
}
|
||||||
|
vRingbufferReturnItem(i2c->rx_ring_buf, rx_data);
|
||||||
|
so_far+=dlen;
|
||||||
|
to_read-=dlen;
|
||||||
|
}
|
||||||
|
return (data)?so_far:0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_task(void *pv_args)
|
||||||
|
{
|
||||||
|
i2c_slave_struct_t * i2c = (i2c_slave_struct_t *)pv_args;
|
||||||
|
i2c_slave_queue_event_t event;
|
||||||
|
size_t len = 0;
|
||||||
|
bool stop = false;
|
||||||
|
uint8_t * data = NULL;
|
||||||
|
for(;;){
|
||||||
|
if(xQueueReceive(i2c->event_queue, &event, portMAX_DELAY) == pdTRUE){
|
||||||
|
// Write
|
||||||
|
if(event.event == I2C_SLAVE_EVT_RX){
|
||||||
|
len = event.param;
|
||||||
|
stop = event.stop;
|
||||||
|
data = (len > 0)?(uint8_t*)malloc(len):NULL;
|
||||||
|
|
||||||
|
if(len && data == NULL){
|
||||||
|
log_e("Malloc (%u) Failed", len);
|
||||||
|
}
|
||||||
|
len = i2c_slave_read_rx(i2c, data, len);
|
||||||
|
if(i2c->receive_callback){
|
||||||
|
i2c->receive_callback(i2c->num, data, len, stop, i2c->arg);
|
||||||
|
}
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
// Read
|
||||||
|
} else if(event.event == I2C_SLAVE_EVT_TX){
|
||||||
|
if(i2c->request_callback){
|
||||||
|
i2c->request_callback(i2c->num, i2c->arg);
|
||||||
|
}
|
||||||
|
i2c_ll_stretch_clr(i2c->dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "stdint.h"
|
||||||
|
#include "stddef.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
typedef void (*i2c_slave_request_cb_t) (uint8_t num, void * arg);
|
||||||
|
typedef void (*i2c_slave_receive_cb_t) (uint8_t num, uint8_t * data, size_t len, bool stop, void * arg);
|
||||||
|
esp_err_t i2cSlaveAttachCallbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg);
|
||||||
|
|
||||||
|
esp_err_t i2cSlaveInit(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len);
|
||||||
|
esp_err_t i2cSlaveDeinit(uint8_t num);
|
||||||
|
size_t i2cSlaveWrite(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
@ -0,0 +1,343 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal-i2c.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#endif
|
||||||
|
#include "esp_attr.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "soc/i2c_periph.h"
|
||||||
|
#include "hal/i2c_hal.h"
|
||||||
|
#include "hal/i2c_ll.h"
|
||||||
|
#include "driver/i2c.h"
|
||||||
|
|
||||||
|
typedef volatile struct {
|
||||||
|
bool initialized;
|
||||||
|
uint32_t frequency;
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
SemaphoreHandle_t lock;
|
||||||
|
#endif
|
||||||
|
} i2c_bus_t;
|
||||||
|
|
||||||
|
static i2c_bus_t bus[SOC_I2C_NUM];
|
||||||
|
|
||||||
|
bool i2cIsInit(uint8_t i2c_num){
|
||||||
|
if(i2c_num >= SOC_I2C_NUM){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return bus[i2c_num].initialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cInit(uint8_t i2c_num, int8_t sda, int8_t scl, uint32_t frequency){
|
||||||
|
if(i2c_num >= SOC_I2C_NUM){
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(bus[i2c_num].lock == NULL){
|
||||||
|
bus[i2c_num].lock = xSemaphoreCreateMutex();
|
||||||
|
if(bus[i2c_num].lock == NULL){
|
||||||
|
log_e("xSemaphoreCreateMutex failed");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//acquire lock
|
||||||
|
if(xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE){
|
||||||
|
log_e("could not acquire lock");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(bus[i2c_num].initialized){
|
||||||
|
log_e("bus is already initialized");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!frequency){
|
||||||
|
frequency = 100000UL;
|
||||||
|
} else if(frequency > 1000000UL){
|
||||||
|
frequency = 1000000UL;
|
||||||
|
}
|
||||||
|
log_i("Initialising I2C Master: sda=%d scl=%d freq=%d", sda, scl, frequency);
|
||||||
|
|
||||||
|
i2c_config_t conf = { };
|
||||||
|
conf.mode = I2C_MODE_MASTER;
|
||||||
|
conf.scl_io_num = (gpio_num_t)scl;
|
||||||
|
conf.sda_io_num = (gpio_num_t)sda;
|
||||||
|
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
||||||
|
conf.master.clk_speed = frequency;
|
||||||
|
conf.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL; //Any one clock source that is available for the specified frequency may be choosen
|
||||||
|
|
||||||
|
esp_err_t ret = i2c_param_config((i2c_port_t)i2c_num, &conf);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
log_e("i2c_param_config failed");
|
||||||
|
} else {
|
||||||
|
ret = i2c_driver_install((i2c_port_t)i2c_num, conf.mode, 0, 0, 0);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
log_e("i2c_driver_install failed");
|
||||||
|
} else {
|
||||||
|
bus[i2c_num].initialized = true;
|
||||||
|
bus[i2c_num].frequency = frequency;
|
||||||
|
//Clock Stretching Timeout: 20b:esp32, 5b:esp32-c3, 24b:esp32-s2
|
||||||
|
i2c_set_timeout((i2c_port_t)i2c_num, I2C_LL_MAX_TIMEOUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//release lock
|
||||||
|
xSemaphoreGive(bus[i2c_num].lock);
|
||||||
|
#endif
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cDeinit(uint8_t i2c_num){
|
||||||
|
esp_err_t err = ESP_FAIL;
|
||||||
|
if(i2c_num >= SOC_I2C_NUM){
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//acquire lock
|
||||||
|
if(bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE){
|
||||||
|
log_e("could not acquire lock");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(!bus[i2c_num].initialized){
|
||||||
|
log_e("bus is not initialized");
|
||||||
|
} else {
|
||||||
|
err = i2c_driver_delete((i2c_port_t)i2c_num);
|
||||||
|
if(err == ESP_OK){
|
||||||
|
bus[i2c_num].initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//release lock
|
||||||
|
xSemaphoreGive(bus[i2c_num].lock);
|
||||||
|
#endif
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cWrite(uint8_t i2c_num, uint16_t address, const uint8_t* buff, size_t size, uint32_t timeOutMillis){
|
||||||
|
esp_err_t ret = ESP_FAIL;
|
||||||
|
i2c_cmd_handle_t cmd = NULL;
|
||||||
|
if(i2c_num >= SOC_I2C_NUM){
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//acquire lock
|
||||||
|
if(bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE){
|
||||||
|
log_e("could not acquire lock");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(!bus[i2c_num].initialized){
|
||||||
|
log_e("bus is not initialized");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
|
||||||
|
//short implementation does not support zero size writes (example when scanning) PR in IDF?
|
||||||
|
//ret = i2c_master_write_to_device((i2c_port_t)i2c_num, address, buff, size, timeOutMillis / portTICK_RATE_MS);
|
||||||
|
|
||||||
|
ret = ESP_OK;
|
||||||
|
uint8_t cmd_buff[I2C_LINK_RECOMMENDED_SIZE(1)] = { 0 };
|
||||||
|
cmd = i2c_cmd_link_create_static(cmd_buff, I2C_LINK_RECOMMENDED_SIZE(1));
|
||||||
|
ret = i2c_master_start(cmd);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
ret = i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
if(size){
|
||||||
|
ret = i2c_master_write(cmd, buff, size, true);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = i2c_master_stop(cmd);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
ret = i2c_master_cmd_begin((i2c_port_t)i2c_num, cmd, timeOutMillis / portTICK_RATE_MS);
|
||||||
|
|
||||||
|
end:
|
||||||
|
if(cmd != NULL){
|
||||||
|
i2c_cmd_link_delete_static(cmd);
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//release lock
|
||||||
|
xSemaphoreGive(bus[i2c_num].lock);
|
||||||
|
#endif
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cRead(uint8_t i2c_num, uint16_t address, uint8_t* buff, size_t size, uint32_t timeOutMillis, size_t *readCount){
|
||||||
|
esp_err_t ret = ESP_FAIL;
|
||||||
|
if(i2c_num >= SOC_I2C_NUM){
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//acquire lock
|
||||||
|
if(bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE){
|
||||||
|
log_e("could not acquire lock");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(!bus[i2c_num].initialized){
|
||||||
|
log_e("bus is not initialized");
|
||||||
|
} else {
|
||||||
|
ret = i2c_master_read_from_device((i2c_port_t)i2c_num, address, buff, size, timeOutMillis / portTICK_RATE_MS);
|
||||||
|
if(ret == ESP_OK){
|
||||||
|
*readCount = size;
|
||||||
|
} else {
|
||||||
|
*readCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//release lock
|
||||||
|
xSemaphoreGive(bus[i2c_num].lock);
|
||||||
|
#endif
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cWriteReadNonStop(uint8_t i2c_num, uint16_t address, const uint8_t* wbuff, size_t wsize, uint8_t* rbuff, size_t rsize, uint32_t timeOutMillis, size_t *readCount){
|
||||||
|
esp_err_t ret = ESP_FAIL;
|
||||||
|
if(i2c_num >= SOC_I2C_NUM){
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//acquire lock
|
||||||
|
if(bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE){
|
||||||
|
log_e("could not acquire lock");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(!bus[i2c_num].initialized){
|
||||||
|
log_e("bus is not initialized");
|
||||||
|
} else {
|
||||||
|
ret = i2c_master_write_read_device((i2c_port_t)i2c_num, address, wbuff, wsize, rbuff, rsize, timeOutMillis / portTICK_RATE_MS);
|
||||||
|
if(ret == ESP_OK){
|
||||||
|
*readCount = rsize;
|
||||||
|
} else {
|
||||||
|
*readCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//release lock
|
||||||
|
xSemaphoreGive(bus[i2c_num].lock);
|
||||||
|
#endif
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cSetClock(uint8_t i2c_num, uint32_t frequency){
|
||||||
|
esp_err_t ret = ESP_FAIL;
|
||||||
|
if(i2c_num >= SOC_I2C_NUM){
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//acquire lock
|
||||||
|
if(bus[i2c_num].lock == NULL || xSemaphoreTake(bus[i2c_num].lock, portMAX_DELAY) != pdTRUE){
|
||||||
|
log_e("could not acquire lock");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(!bus[i2c_num].initialized){
|
||||||
|
log_e("bus is not initialized");
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
if(bus[i2c_num].frequency == frequency){
|
||||||
|
ret = ESP_OK;
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
if(!frequency){
|
||||||
|
frequency = 100000UL;
|
||||||
|
} else if(frequency > 1000000UL){
|
||||||
|
frequency = 1000000UL;
|
||||||
|
}
|
||||||
|
// Freq limitation when using different clock sources
|
||||||
|
#define I2C_CLK_LIMIT_REF_TICK (1 * 1000 * 1000 / 20) /*!< Limited by REF_TICK, no more than REF_TICK/20*/
|
||||||
|
#define I2C_CLK_LIMIT_APB (80 * 1000 * 1000 / 20) /*!< Limited by APB, no more than APB/20*/
|
||||||
|
#define I2C_CLK_LIMIT_RTC (20 * 1000 * 1000 / 20) /*!< Limited by RTC, no more than RTC/20*/
|
||||||
|
#define I2C_CLK_LIMIT_XTAL (40 * 1000 * 1000 / 20) /*!< Limited by RTC, no more than XTAL/20*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t character; /*!< I2C source clock characteristic */
|
||||||
|
uint32_t clk_freq; /*!< I2C source clock frequency */
|
||||||
|
} i2c_clk_alloc_t;
|
||||||
|
|
||||||
|
// i2c clock characteristic, The order is the same as i2c_sclk_t.
|
||||||
|
static i2c_clk_alloc_t i2c_clk_alloc[I2C_SCLK_MAX] = {
|
||||||
|
{0, 0},
|
||||||
|
#if SOC_I2C_SUPPORT_APB
|
||||||
|
{0, I2C_CLK_LIMIT_APB}, /*!< I2C APB clock characteristic*/
|
||||||
|
#endif
|
||||||
|
#if SOC_I2C_SUPPORT_XTAL
|
||||||
|
{0, I2C_CLK_LIMIT_XTAL}, /*!< I2C XTAL characteristic*/
|
||||||
|
#endif
|
||||||
|
#if SOC_I2C_SUPPORT_RTC
|
||||||
|
{I2C_SCLK_SRC_FLAG_LIGHT_SLEEP | I2C_SCLK_SRC_FLAG_AWARE_DFS, I2C_CLK_LIMIT_RTC}, /*!< I2C 20M RTC characteristic*/
|
||||||
|
#endif
|
||||||
|
#if SOC_I2C_SUPPORT_REF_TICK
|
||||||
|
{I2C_SCLK_SRC_FLAG_AWARE_DFS, I2C_CLK_LIMIT_REF_TICK}, /*!< I2C REF_TICK characteristic*/
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
i2c_sclk_t src_clk = I2C_SCLK_DEFAULT;
|
||||||
|
ret = ESP_OK;
|
||||||
|
for (i2c_sclk_t clk = I2C_SCLK_DEFAULT + 1; clk < I2C_SCLK_MAX; clk++) {
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
if (clk == I2C_SCLK_RTC) { // RTC clock for s3 is unaccessable now.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (frequency <= i2c_clk_alloc[clk].clk_freq) {
|
||||||
|
src_clk = clk;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(src_clk == I2C_SCLK_MAX){
|
||||||
|
log_e("clock source could not be selected");
|
||||||
|
ret = ESP_FAIL;
|
||||||
|
} else {
|
||||||
|
i2c_hal_context_t hal;
|
||||||
|
hal.dev = I2C_LL_GET_HW(i2c_num);
|
||||||
|
i2c_hal_set_bus_timing(&(hal), frequency, src_clk);
|
||||||
|
bus[i2c_num].frequency = frequency;
|
||||||
|
//Clock Stretching Timeout: 20b:esp32, 5b:esp32-c3, 24b:esp32-s2
|
||||||
|
i2c_set_timeout((i2c_port_t)i2c_num, I2C_LL_MAX_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
end:
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
//release lock
|
||||||
|
xSemaphoreGive(bus[i2c_num].lock);
|
||||||
|
#endif
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cGetClock(uint8_t i2c_num, uint32_t * frequency){
|
||||||
|
if(i2c_num >= SOC_I2C_NUM){
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
if(!bus[i2c_num].initialized){
|
||||||
|
log_e("bus is not initialized");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
*frequency = bus[i2c_num].frequency;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// modified Nov 2017 by Chuck Todd <StickBreaker> to support Interrupt Driven I/O
|
||||||
|
// modified Nov 2021 by Hristo Gochkov <Me-No-Dev> to support ESP-IDF API
|
||||||
|
|
||||||
|
#ifndef _ESP32_HAL_I2C_H_
|
||||||
|
#define _ESP32_HAL_I2C_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
|
||||||
|
esp_err_t i2cInit(uint8_t i2c_num, int8_t sda, int8_t scl, uint32_t clk_speed);
|
||||||
|
esp_err_t i2cDeinit(uint8_t i2c_num);
|
||||||
|
esp_err_t i2cSetClock(uint8_t i2c_num, uint32_t frequency);
|
||||||
|
esp_err_t i2cGetClock(uint8_t i2c_num, uint32_t * frequency);
|
||||||
|
esp_err_t i2cWrite(uint8_t i2c_num, uint16_t address, const uint8_t* buff, size_t size, uint32_t timeOutMillis);
|
||||||
|
esp_err_t i2cRead(uint8_t i2c_num, uint16_t address, uint8_t* buff, size_t size, uint32_t timeOutMillis, size_t *readCount);
|
||||||
|
esp_err_t i2cWriteReadNonStop(uint8_t i2c_num, uint16_t address, const uint8_t* wbuff, size_t wsize, uint8_t* rbuff, size_t rsize, uint32_t timeOutMillis, size_t *readCount);
|
||||||
|
bool i2cIsInit(uint8_t i2c_num);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _ESP32_HAL_I2C_H_ */
|
@ -0,0 +1,272 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "driver/ledc.h"
|
||||||
|
|
||||||
|
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||||
|
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||||
|
#else
|
||||||
|
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//Use XTAL clock if possible to avoid timer frequency error when setting APB clock < 80 Mhz
|
||||||
|
//Need to be fixed in ESP-IDF
|
||||||
|
#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK
|
||||||
|
#define LEDC_DEFAULT_CLK LEDC_USE_XTAL_CLK
|
||||||
|
#else
|
||||||
|
#define LEDC_DEFAULT_CLK LEDC_AUTO_CLK
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LEDC_MAX_BIT_WIDTH SOC_LEDC_TIMER_BIT_WIDE_NUM
|
||||||
|
|
||||||
|
/*
|
||||||
|
* LEDC Chan to Group/Channel/Timer Mapping
|
||||||
|
** ledc: 0 => Group: 0, Channel: 0, Timer: 0
|
||||||
|
** ledc: 1 => Group: 0, Channel: 1, Timer: 0
|
||||||
|
** ledc: 2 => Group: 0, Channel: 2, Timer: 1
|
||||||
|
** ledc: 3 => Group: 0, Channel: 3, Timer: 1
|
||||||
|
** ledc: 4 => Group: 0, Channel: 4, Timer: 2
|
||||||
|
** ledc: 5 => Group: 0, Channel: 5, Timer: 2
|
||||||
|
** ledc: 6 => Group: 0, Channel: 6, Timer: 3
|
||||||
|
** ledc: 7 => Group: 0, Channel: 7, Timer: 3
|
||||||
|
** ledc: 8 => Group: 1, Channel: 0, Timer: 0
|
||||||
|
** ledc: 9 => Group: 1, Channel: 1, Timer: 0
|
||||||
|
** ledc: 10 => Group: 1, Channel: 2, Timer: 1
|
||||||
|
** ledc: 11 => Group: 1, Channel: 3, Timer: 1
|
||||||
|
** ledc: 12 => Group: 1, Channel: 4, Timer: 2
|
||||||
|
** ledc: 13 => Group: 1, Channel: 5, Timer: 2
|
||||||
|
** ledc: 14 => Group: 1, Channel: 6, Timer: 3
|
||||||
|
** ledc: 15 => Group: 1, Channel: 7, Timer: 3
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint8_t channels_resolution[LEDC_CHANNELS] = {0};
|
||||||
|
|
||||||
|
uint32_t ledcSetup(uint8_t chan, uint32_t freq, uint8_t bit_num)
|
||||||
|
{
|
||||||
|
if(chan >= LEDC_CHANNELS || bit_num > LEDC_MAX_BIT_WIDTH){
|
||||||
|
log_e("No more LEDC channels available! (maximum %u) or bit width too big (maximum %u)", LEDC_CHANNELS, LEDC_MAX_BIT_WIDTH);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t group=(chan/8), timer=((chan/2)%4);
|
||||||
|
|
||||||
|
ledc_timer_config_t ledc_timer = {
|
||||||
|
.speed_mode = group,
|
||||||
|
.timer_num = timer,
|
||||||
|
.duty_resolution = bit_num,
|
||||||
|
.freq_hz = freq,
|
||||||
|
.clk_cfg = LEDC_DEFAULT_CLK
|
||||||
|
};
|
||||||
|
if(ledc_timer_config(&ledc_timer) != ESP_OK)
|
||||||
|
{
|
||||||
|
log_e("ledc setup failed!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
channels_resolution[chan] = bit_num;
|
||||||
|
return ledc_get_freq(group,timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ledcWrite(uint8_t chan, uint32_t duty)
|
||||||
|
{
|
||||||
|
if(chan >= LEDC_CHANNELS){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t group=(chan/8), channel=(chan%8);
|
||||||
|
|
||||||
|
//Fixing if all bits in resolution is set = LEDC FULL ON
|
||||||
|
uint32_t max_duty = (1 << channels_resolution[chan]) - 1;
|
||||||
|
|
||||||
|
if((duty == max_duty) && (max_duty != 1)){
|
||||||
|
duty = max_duty + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ledc_set_duty(group, channel, duty);
|
||||||
|
ledc_update_duty(group, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ledcRead(uint8_t chan)
|
||||||
|
{
|
||||||
|
if(chan >= LEDC_CHANNELS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint8_t group=(chan/8), channel=(chan%8);
|
||||||
|
return ledc_get_duty(group,channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ledcReadFreq(uint8_t chan)
|
||||||
|
{
|
||||||
|
if(!ledcRead(chan)){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint8_t group=(chan/8), timer=((chan/2)%4);
|
||||||
|
return ledc_get_freq(group,timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ledcWriteTone(uint8_t chan, uint32_t freq)
|
||||||
|
{
|
||||||
|
if(chan >= LEDC_CHANNELS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(!freq){
|
||||||
|
ledcWrite(chan, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t group=(chan/8), timer=((chan/2)%4);
|
||||||
|
|
||||||
|
ledc_timer_config_t ledc_timer = {
|
||||||
|
.speed_mode = group,
|
||||||
|
.timer_num = timer,
|
||||||
|
.duty_resolution = 10,
|
||||||
|
.freq_hz = freq,
|
||||||
|
.clk_cfg = LEDC_DEFAULT_CLK
|
||||||
|
};
|
||||||
|
|
||||||
|
if(ledc_timer_config(&ledc_timer) != ESP_OK)
|
||||||
|
{
|
||||||
|
log_e("ledcSetup failed!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
channels_resolution[chan] = 10;
|
||||||
|
|
||||||
|
uint32_t res_freq = ledc_get_freq(group,timer);
|
||||||
|
ledcWrite(chan, 0x1FF);
|
||||||
|
return res_freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ledcWriteNote(uint8_t chan, note_t note, uint8_t octave){
|
||||||
|
const uint16_t noteFrequencyBase[12] = {
|
||||||
|
// C C# D Eb E F F# G G# A Bb B
|
||||||
|
4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902
|
||||||
|
};
|
||||||
|
|
||||||
|
if(octave > 8 || note >= NOTE_MAX){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint32_t noteFreq = (uint32_t)noteFrequencyBase[note] / (uint32_t)(1 << (8-octave));
|
||||||
|
return ledcWriteTone(chan, noteFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ledcAttachPin(uint8_t pin, uint8_t chan)
|
||||||
|
{
|
||||||
|
if(chan >= LEDC_CHANNELS){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t group=(chan/8), channel=(chan%8), timer=((chan/2)%4);
|
||||||
|
uint32_t duty = ledc_get_duty(group,channel);
|
||||||
|
|
||||||
|
ledc_channel_config_t ledc_channel = {
|
||||||
|
.speed_mode = group,
|
||||||
|
.channel = channel,
|
||||||
|
.timer_sel = timer,
|
||||||
|
.intr_type = LEDC_INTR_DISABLE,
|
||||||
|
.gpio_num = pin,
|
||||||
|
.duty = duty,
|
||||||
|
.hpoint = 0
|
||||||
|
};
|
||||||
|
ledc_channel_config(&ledc_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ledcDetachPin(uint8_t pin)
|
||||||
|
{
|
||||||
|
pinMatrixOutDetach(pin, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ledcChangeFrequency(uint8_t chan, uint32_t freq, uint8_t bit_num)
|
||||||
|
{
|
||||||
|
if(chan >= LEDC_CHANNELS || bit_num > LEDC_MAX_BIT_WIDTH){
|
||||||
|
log_e("LEDC channel not available! (maximum %u) or bit width too big (maximum %u)", LEDC_CHANNELS, LEDC_MAX_BIT_WIDTH);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
uint8_t group=(chan/8), timer=((chan/2)%4);
|
||||||
|
|
||||||
|
ledc_timer_config_t ledc_timer = {
|
||||||
|
.speed_mode = group,
|
||||||
|
.timer_num = timer,
|
||||||
|
.duty_resolution = bit_num,
|
||||||
|
.freq_hz = freq,
|
||||||
|
.clk_cfg = LEDC_DEFAULT_CLK
|
||||||
|
};
|
||||||
|
|
||||||
|
if(ledc_timer_config(&ledc_timer) != ESP_OK)
|
||||||
|
{
|
||||||
|
log_e("ledcChangeFrequency failed!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
channels_resolution[chan] = bit_num;
|
||||||
|
return ledc_get_freq(group,timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||||
|
static int cnt_channel = LEDC_CHANNELS;
|
||||||
|
static uint8_t analog_resolution = 8;
|
||||||
|
static int analog_frequency = 1000;
|
||||||
|
void analogWrite(uint8_t pin, int value) {
|
||||||
|
// Use ledc hardware for internal pins
|
||||||
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
|
int8_t channel = -1;
|
||||||
|
if (pin_to_channel[pin] == 0) {
|
||||||
|
if (!cnt_channel) {
|
||||||
|
log_e("No more analogWrite channels available! You can have maximum %u", LEDC_CHANNELS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cnt_channel--;
|
||||||
|
channel = cnt_channel;
|
||||||
|
} else {
|
||||||
|
channel = analogGetChannel(pin);
|
||||||
|
}
|
||||||
|
log_v("GPIO %d - Using Channel %d, Value = %d", pin, channel, value);
|
||||||
|
if(ledcSetup(channel, analog_frequency, analog_resolution) == 0){
|
||||||
|
log_e("analogWrite setup failed (freq = %u, resolution = %u). Try setting different resolution or frequency");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ledcAttachPin(pin, channel);
|
||||||
|
pin_to_channel[pin] = channel + 1;
|
||||||
|
ledcWrite(channel, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t analogGetChannel(uint8_t pin) {
|
||||||
|
return pin_to_channel[pin] - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void analogWriteFrequency(uint32_t freq) {
|
||||||
|
if (cnt_channel != LEDC_CHANNELS) {
|
||||||
|
for (int channel = LEDC_CHANNELS - 1; channel >= cnt_channel; channel--) {
|
||||||
|
if (ledcChangeFrequency(channel, freq, analog_resolution) == 0){
|
||||||
|
log_e("analogWrite frequency cant be set due to selected resolution! Try to adjust resolution first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
analog_frequency = freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
void analogWriteResolution(uint8_t bits) {
|
||||||
|
if(bits > LEDC_MAX_BIT_WIDTH) {
|
||||||
|
log_w("analogWrite resolution width too big! Setting to maximum %u bits)", LEDC_MAX_BIT_WIDTH);
|
||||||
|
bits = LEDC_MAX_BIT_WIDTH;
|
||||||
|
}
|
||||||
|
if (cnt_channel != LEDC_CHANNELS) {
|
||||||
|
for (int channel = LEDC_CHANNELS - 1; channel >= cnt_channel; channel--) {
|
||||||
|
if (ledcChangeFrequency(channel, analog_frequency, bits) == 0){
|
||||||
|
log_e("analogWrite resolution cant be set due to selected frequency! Try to adjust frequency first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
analog_resolution = bits;
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef _ESP32_HAL_LEDC_H_
|
||||||
|
#define _ESP32_HAL_LEDC_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NOTE_C, NOTE_Cs, NOTE_D, NOTE_Eb, NOTE_E, NOTE_F, NOTE_Fs, NOTE_G, NOTE_Gs, NOTE_A, NOTE_Bb, NOTE_B, NOTE_MAX
|
||||||
|
} note_t;
|
||||||
|
|
||||||
|
//channel 0-15 resolution 1-16bits freq limits depend on resolution
|
||||||
|
uint32_t ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits);
|
||||||
|
void ledcWrite(uint8_t channel, uint32_t duty);
|
||||||
|
uint32_t ledcWriteTone(uint8_t channel, uint32_t freq);
|
||||||
|
uint32_t ledcWriteNote(uint8_t channel, note_t note, uint8_t octave);
|
||||||
|
uint32_t ledcRead(uint8_t channel);
|
||||||
|
uint32_t ledcReadFreq(uint8_t channel);
|
||||||
|
void ledcAttachPin(uint8_t pin, uint8_t channel);
|
||||||
|
void ledcDetachPin(uint8_t pin);
|
||||||
|
uint32_t ledcChangeFrequency(uint8_t channel, uint32_t freq, uint8_t resolution_bits);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _ESP32_HAL_LEDC_H_ */
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue