Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41b0686aec | ||
|
|
9178ed580e | ||
|
|
60e93f3e20 | ||
|
|
7e8fcc5afa | ||
|
|
39238a505d | ||
|
|
f60b7831c8 | ||
|
|
c48cdeeea0 | ||
|
|
0be7c98dff | ||
|
|
0ed1f3dce1 | ||
|
|
f5e8e5b325 | ||
|
|
1f3d5011b2 | ||
|
|
3950b94474 | ||
|
|
99b4aac48b | ||
|
|
2bd64b566c | ||
|
|
3c93d14b33 | ||
|
|
5639edefee | ||
|
|
585c31ef39 | ||
|
|
a385a4c1e5 | ||
|
|
ef257c32b4 | ||
|
|
88cd4b5288 | ||
|
|
ca7ec56f0d | ||
|
|
74b1156d5e | ||
|
|
6ac7ee1a9d | ||
|
|
0b56452c35 | ||
|
|
b9c66ca0fd | ||
|
|
faf06a3b70 | ||
|
|
d0ac7f06b1 | ||
|
|
e9daaa3589 | ||
|
|
1393811525 | ||
|
|
2b640669a7 | ||
|
|
9d0f0a8d7a | ||
|
|
21d2fb4afa | ||
|
|
ade74986ee | ||
|
|
73722b5c87 | ||
|
|
456b474a48 | ||
|
|
f11cc4db69 | ||
|
|
8c2901da13 | ||
|
|
ca4637d14d | ||
|
|
313caf406e | ||
|
|
25052fedd6 | ||
|
|
678cd2c4b9 | ||
|
|
1231317c9c | ||
|
|
cd76f030cb | ||
|
|
36839cb190 | ||
|
|
23e68973c0 | ||
|
|
70af32eead | ||
|
|
3cc5ac14ff | ||
|
|
4fd8e41db1 | ||
|
|
5fd4736d8f | ||
|
|
67a07edccc | ||
|
|
1dc0872818 | ||
|
|
ead0728a57 | ||
|
|
14eb8d3906 | ||
|
|
4c90b295b2 | ||
|
|
43aa0427ea | ||
|
|
cc5d1779a3 | ||
|
|
0f437e4db2 | ||
|
|
3778fbb833 | ||
|
|
4b6f3d05e1 | ||
|
|
9e89dda900 | ||
|
|
91e69e2a1a | ||
|
|
21d1a285dc | ||
|
|
0e579792d1 |
+1
-1
Submodule ArduinoCore-API updated: e37df85425...ece6e68f29
@@ -4,7 +4,7 @@
|
||||
|
||||
Raspberry Pi Pico Arduino core, for all RP2040 boards
|
||||
|
||||
This is a port of the RP2040 (Raspberry Pi Pico processor) to the Arduino ecosystem. It uses the bare Raspberry Pi Pico SDK and a custom GCC 10.3/Newlib 4.0 toolchain.
|
||||
This is a port of the RP2040 (Raspberry Pi Pico processor) to the Arduino ecosystem. It uses the bare Raspberry Pi Pico SDK and a custom GCC 12.3/Newlib 4.0 toolchain.
|
||||
|
||||
# Documentation
|
||||
See https://arduino-pico.readthedocs.io/en/latest/ along with the examples for more detailed usage information.
|
||||
@@ -21,10 +21,12 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
|
||||
* Adafruit ItsyBitsy RP2040
|
||||
* Adafruit KB2040
|
||||
* Adafruit Macropad RP2040
|
||||
* Adafruit Metro RP2040
|
||||
* Adafruit QTPy RP2040
|
||||
* Adafruit STEMMA Friend RP2040
|
||||
* Adafruit Trinkey RP2040 QT
|
||||
* Arduino Nano RP2040 Connect
|
||||
* ArtronShop RP2 Nano
|
||||
* BridgeTek IDM2040-7A
|
||||
* Cytron Maker Pi RP2040
|
||||
* Cytron Maker Nano RP2040
|
||||
@@ -35,6 +37,7 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
|
||||
* ExtremeElectronics RC2040
|
||||
* Invector Labs Challenger RP2040 WiFi
|
||||
* Invector Labs Challenger RP2040 WiFi/BLE
|
||||
* Invector Labs Challenger RP2040 WiFi6/BLE
|
||||
* Invector Labs Challenger NB RP2040 WiFi
|
||||
* Invector Labs Challenger RP2040 LTE
|
||||
* Invector Labs Challenger RP2040 LoRa
|
||||
@@ -47,8 +50,11 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
|
||||
* Neko Systems BL2040 Mini
|
||||
* nullbits Bit-C PRO
|
||||
* Pimoroni PGA2040
|
||||
* Pimoroni Plasma2040
|
||||
* Pimoroni Tiny2040
|
||||
* Seeed Indicator RP2040
|
||||
* Seeed XIAO RP2040
|
||||
* Silicognition RP2040-Shim
|
||||
* Solder Party RP2040 Stamp
|
||||
* SparkFun ProMicro RP2040
|
||||
* SparkFun Thing Plus RP2040
|
||||
@@ -63,6 +69,7 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
|
||||
* WIZnet W5100S-EVB-Pico
|
||||
* WIZnet W5500-EVB-Pico
|
||||
* WIZnet WizFi360-EVB-Pico
|
||||
* Redscorp RP2040-ProMini
|
||||
* Generic (configurable flash, I/O pins)
|
||||
|
||||
# Installing via Arduino Boards Manager
|
||||
@@ -195,6 +202,7 @@ The installed tools include a version of OpenOCD (in the pqt-openocd directory)
|
||||
* Bluetooth on the PicoW (Classic and BLE) with Keyboard, Mouse, Joystick, and Virtual Serial
|
||||
* Generic Arduino USB Serial, Keyboard, Joystick, and Mouse emulation
|
||||
* WiFi (Pico W)
|
||||
* Ethernet (Wired W5500, W5100, ENC28J60)
|
||||
* HTTP client and server (WebServer)
|
||||
* SSL/TLS/HTTPS
|
||||
* Over-the-Air (OTA) upgrades
|
||||
@@ -206,7 +214,7 @@ The installed tools include a version of OpenOCD (in the pqt-openocd directory)
|
||||
* Analog stereo audio in using DMA and the built-in ADC
|
||||
* Analog stereo audio out using PWM hardware
|
||||
* USB drive mode for data loggers (SingleFileDrive)
|
||||
* Peripherals: SPI master, Wire(I2C) master/slave, dual UART, emulated EEPROM, I2S audio input, I2S audio output, Servo
|
||||
* Peripherals: SPI master/slave, Wire(I2C) master/slave, dual UART, emulated EEPROM, I2S audio input/output, Servo
|
||||
* printf (i.e. debug) output over USB serial
|
||||
|
||||
The RP2040 PIO state machines (SMs) are used to generate jitter-free:
|
||||
|
||||
+1722
-413
File diff suppressed because it is too large
Load Diff
@@ -394,6 +394,18 @@ void __USBStart() {
|
||||
}
|
||||
|
||||
|
||||
bool __USBHIDReady() {
|
||||
uint32_t start = millis();
|
||||
const uint32_t timeout = 500;
|
||||
|
||||
while (((millis() - start) < timeout) && tud_ready() && !tud_hid_ready()) {
|
||||
tud_task();
|
||||
delay(1);
|
||||
}
|
||||
return tud_hid_ready();
|
||||
}
|
||||
|
||||
|
||||
// Invoked when received GET_REPORT control request
|
||||
// Application must fill buffer report's content and return its length.
|
||||
// Return zero will cause the stack to STALL request
|
||||
|
||||
@@ -45,3 +45,6 @@ int __USBGetJoystickReportID();
|
||||
|
||||
// Called by main() to init the USB HW/SW.
|
||||
void __USBStart();
|
||||
|
||||
// Helper class for HID report sending with wait and timeout
|
||||
bool __USBHIDReady();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#define ARDUINO_PICO_MAJOR 3
|
||||
#define ARDUINO_PICO_MINOR 3
|
||||
#define ARDUINO_PICO_MINOR 6
|
||||
#define ARDUINO_PICO_REVISION 0
|
||||
#define ARDUINO_PICO_VERSION_STR "3.3.0"
|
||||
#define ARDUINO_PICO_VERSION_STR "3.6.0"
|
||||
|
||||
@@ -40,6 +40,10 @@ bool SerialUART::setRX(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_rx == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set Serial%d.RX while running", uart_get_index(_uart) + 1);
|
||||
} else {
|
||||
@@ -57,6 +61,10 @@ bool SerialUART::setTX(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_tx == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set Serial%d.TX while running", uart_get_index(_uart) + 1);
|
||||
} else {
|
||||
@@ -74,6 +82,10 @@ bool SerialUART::setRTS(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_rts == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set Serial%d.RTS while running", uart_get_index(_uart) + 1);
|
||||
} else {
|
||||
@@ -91,6 +103,10 @@ bool SerialUART::setCTS(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_cts == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set Serial%d.CTS while running", uart_get_index(_uart) + 1);
|
||||
} else {
|
||||
|
||||
@@ -130,7 +130,7 @@ size_t SerialUSB::write(const uint8_t *buf, size_t length) {
|
||||
|
||||
static uint64_t last_avail_time;
|
||||
int written = 0;
|
||||
if (tud_cdc_connected()) {
|
||||
if (tud_cdc_connected() || _ignoreFlowControl) {
|
||||
for (size_t i = 0; i < length;) {
|
||||
int n = length - i;
|
||||
int avail = tud_cdc_write_available();
|
||||
@@ -171,6 +171,9 @@ SerialUSB::operator bool() {
|
||||
return tud_cdc_connected();
|
||||
}
|
||||
|
||||
void SerialUSB::ignoreFlowControl(bool ignore) {
|
||||
_ignoreFlowControl = ignore;
|
||||
}
|
||||
|
||||
static bool _dtr = false;
|
||||
static bool _rts = false;
|
||||
|
||||
@@ -44,6 +44,8 @@ public:
|
||||
using Print::write;
|
||||
operator bool() override;
|
||||
|
||||
void ignoreFlowControl(bool ignore = true);
|
||||
|
||||
// ESP8266 compat
|
||||
void setDebugOutput(bool unused) {
|
||||
(void) unused;
|
||||
@@ -51,6 +53,7 @@ public:
|
||||
|
||||
private:
|
||||
bool _running = false;
|
||||
bool _ignoreFlowControl = false;
|
||||
};
|
||||
|
||||
extern SerialUSB Serial;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <pico/mutex.h>
|
||||
#include <lwip/pbuf.h>
|
||||
#include <lwip/udp.h>
|
||||
@@ -26,20 +27,57 @@
|
||||
#include <lwip/raw.h>
|
||||
#include <lwip/timeouts.h>
|
||||
#include <pico/cyw43_arch.h>
|
||||
#include <pico/mutex.h>
|
||||
#include <sys/lock.h>
|
||||
|
||||
extern void ethernet_arch_lwip_begin() __attribute__((weak));
|
||||
extern void ethernet_arch_lwip_end() __attribute__((weak));
|
||||
|
||||
auto_init_recursive_mutex(__lwipMutex); // Only for case with no Ethernet or PicoW, but still doing LWIP (PPP?)
|
||||
|
||||
class LWIPMutex {
|
||||
public:
|
||||
LWIPMutex() {
|
||||
cyw43_arch_lwip_begin();
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
if (rp2040.isPicoW()) {
|
||||
cyw43_arch_lwip_begin();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (ethernet_arch_lwip_begin) {
|
||||
ethernet_arch_lwip_begin();
|
||||
} else {
|
||||
recursive_mutex_enter_blocking(&__lwipMutex);
|
||||
}
|
||||
}
|
||||
|
||||
~LWIPMutex() {
|
||||
cyw43_arch_lwip_end();
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
if (rp2040.isPicoW()) {
|
||||
cyw43_arch_lwip_end();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (ethernet_arch_lwip_end) {
|
||||
ethernet_arch_lwip_end();
|
||||
} else {
|
||||
recursive_mutex_exit(&__lwipMutex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Avoid calling lwip_init multiple times
|
||||
extern void __real_lwip_init();
|
||||
void __wrap_lwip_init() {
|
||||
static bool initted = false;
|
||||
if (!initted) {
|
||||
__real_lwip_init();
|
||||
initted = true;
|
||||
}
|
||||
}
|
||||
|
||||
extern u8_t __real_pbuf_header(struct pbuf *p, s16_t header_size);
|
||||
u8_t __wrap_pbuf_header(struct pbuf *p, s16_t header_size) {
|
||||
LWIPMutex m;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
Copyright (C) 2014 BlueKitchen GmbH
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
4. Any redistribution, use, or modification is done solely for
|
||||
personal benefit and not for any commercial purpose or for
|
||||
monetary gain.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
Please inquire about commercial licensing options at
|
||||
contact@bluekitchen-gmbh.com
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
@title ATT Database Engine
|
||||
|
||||
*/
|
||||
|
||||
#ifndef ATT_DB_H
|
||||
#define ATT_DB_H
|
||||
|
||||
#if defined ENABLE_CLASSIC
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sdkoverride/bluetooth.h>
|
||||
#include "btstack_linked_list.h"
|
||||
#include "btstack_defines.h"
|
||||
#include "btstack_bool.h"
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// MARK: Attribute PDU Opcodes
|
||||
#define ATT_ERROR_RESPONSE 0x01u
|
||||
|
||||
#define ATT_EXCHANGE_MTU_REQUEST 0x02u
|
||||
#define ATT_EXCHANGE_MTU_RESPONSE 0x03u
|
||||
|
||||
#define ATT_FIND_INFORMATION_REQUEST 0x04u
|
||||
#define ATT_FIND_INFORMATION_REPLY 0x05u
|
||||
#define ATT_FIND_BY_TYPE_VALUE_REQUEST 0x06u
|
||||
#define ATT_FIND_BY_TYPE_VALUE_RESPONSE 0x07u
|
||||
|
||||
#define ATT_READ_BY_TYPE_REQUEST 0x08u
|
||||
#define ATT_READ_BY_TYPE_RESPONSE 0x09u
|
||||
#define ATT_READ_REQUEST 0x0au
|
||||
#define ATT_READ_RESPONSE 0x0bu
|
||||
#define ATT_READ_BLOB_REQUEST 0x0cu
|
||||
#define ATT_READ_BLOB_RESPONSE 0x0du
|
||||
#define ATT_READ_MULTIPLE_REQUEST 0x0eu
|
||||
#define ATT_READ_MULTIPLE_RESPONSE 0x0fu
|
||||
#define ATT_READ_BY_GROUP_TYPE_REQUEST 0x10u
|
||||
#define ATT_READ_BY_GROUP_TYPE_RESPONSE 0x11u
|
||||
|
||||
#define ATT_WRITE_REQUEST 0x12u
|
||||
#define ATT_WRITE_RESPONSE 0x13u
|
||||
|
||||
#define ATT_PREPARE_WRITE_REQUEST 0x16u
|
||||
#define ATT_PREPARE_WRITE_RESPONSE 0x17u
|
||||
#define ATT_EXECUTE_WRITE_REQUEST 0x18u
|
||||
#define ATT_EXECUTE_WRITE_RESPONSE 0x19u
|
||||
|
||||
#define ATT_HANDLE_VALUE_NOTIFICATION 0x1bu
|
||||
#define ATT_HANDLE_VALUE_INDICATION 0x1du
|
||||
#define ATT_HANDLE_VALUE_CONFIRMATION 0x1eu
|
||||
|
||||
#define ATT_READ_MULTIPLE_VARIABLE_REQ 0x20u
|
||||
#define ATT_READ_MULTIPLE_VARIABLE_RSP 0x21u
|
||||
#define ATT_MULTIPLE_HANDLE_VALUE_NTF 0x23u
|
||||
|
||||
#define ATT_WRITE_COMMAND 0x52u
|
||||
#define ATT_SIGNED_WRITE_COMMAND 0xD2u
|
||||
|
||||
|
||||
// internal additions
|
||||
// 128 bit UUID used
|
||||
#define ATT_PROPERTY_UUID128 0x200u
|
||||
// Read/Write Permission bits
|
||||
#define ATT_PROPERTY_READ_PERMISSION_BIT_0 0x0400u
|
||||
#define ATT_PROPERTY_READ_PERMISSION_BIT_1 0x0800u
|
||||
#define ATT_PROPERTY_WRITE_PERMISSION_BIT_0 0x0001u
|
||||
#define ATT_PROPERTY_WRITE_PERMISSION_BIT_1 0x0010u
|
||||
#define ATT_PROPERTY_READ_PERMISSION_SC 0x0020u
|
||||
#define ATT_PROPERTY_WRITE_PERMISSION_SC 0x0080u
|
||||
|
||||
|
||||
typedef struct att_connection {
|
||||
hci_con_handle_t con_handle;
|
||||
uint16_t mtu; // initialized to ATT_DEFAULT_MTU (23), negotiated during MTU exchange
|
||||
uint16_t max_mtu; // local maximal L2CAP_MTU, set to l2cap_max_le_mtu()
|
||||
bool mtu_exchanged;
|
||||
uint8_t encryption_key_size;
|
||||
uint8_t authenticated;
|
||||
uint8_t authorized;
|
||||
uint8_t secure_connection;
|
||||
} att_connection_t;
|
||||
|
||||
/* API_START */
|
||||
|
||||
// map ATT ERROR CODES on to att_read_callback length
|
||||
#define ATT_READ_ERROR_CODE_OFFSET 0xfe00u
|
||||
|
||||
// custom BTstack ATT Response Pending for att_read_callback
|
||||
#define ATT_READ_RESPONSE_PENDING 0xffffu
|
||||
|
||||
// internally used to signal write response pending
|
||||
#define ATT_INTERNAL_WRITE_RESPONSE_PENDING 0xfffeu
|
||||
|
||||
/**
|
||||
@brief ATT Client Read Callback for Dynamic Data
|
||||
- if buffer == NULL, don't copy data, just return size of value
|
||||
- if buffer != NULL, copy data and return number bytes copied
|
||||
If ENABLE_ATT_DELAYED_READ_RESPONSE is defined, you may return ATT_READ_RESPONSE_PENDING if data isn't available yet
|
||||
@param con_handle of hci le connection
|
||||
@param attribute_handle to be read
|
||||
@param offset defines start of attribute value
|
||||
@param buffer
|
||||
@param buffer_size
|
||||
@return size of value if buffer is NULL, otherwise number of bytes copied
|
||||
*/
|
||||
typedef uint16_t (*att_read_callback_t)(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
|
||||
/**
|
||||
@brief ATT Client Write Callback for Dynamic Data
|
||||
Each Prepared Write Request triggers a callback with transaction mode ATT_TRANSACTION_MODE_ACTIVE.
|
||||
On Execute Write, the callback will be called with ATT_TRANSACTION_MODE_VALIDATE and allows to validate all queued writes and return an application error.
|
||||
If none of the registered callbacks return an error for ATT_TRANSACTION_MODE_VALIDATE and the callback will be called with ATT_TRANSACTION_MODE_EXECUTE.
|
||||
Otherwise, all callbacks will be called with ATT_TRANSACTION_MODE_CANCEL.
|
||||
|
||||
If the additional validation step is not needed, just return 0 for all callbacks with transaction mode ATT_TRANSACTION_MODE_VALIDATE.
|
||||
|
||||
@param con_handle of hci le connection
|
||||
@param attribute_handle to be written
|
||||
@param transaction - ATT_TRANSACTION_MODE_NONE for regular writes. For prepared writes: ATT_TRANSACTION_MODE_ACTIVE, ATT_TRANSACTION_MODE_VALIDATE, ATT_TRANSACTION_MODE_EXECUTE, ATT_TRANSACTION_MODE_CANCEL
|
||||
@param offset into the value - used for queued writes and long attributes
|
||||
@param buffer
|
||||
@param buffer_size
|
||||
@param signature used for signed write commands
|
||||
@return 0 if write was ok, ATT_ERROR_PREPARE_QUEUE_FULL if no space in queue, ATT_ERROR_INVALID_OFFSET if offset is larger than max buffer
|
||||
*/
|
||||
typedef int (*att_write_callback_t)(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size);
|
||||
|
||||
// Read & Write Callbacks for handle range
|
||||
typedef struct att_service_handler {
|
||||
btstack_linked_item_t * item;
|
||||
uint16_t start_handle;
|
||||
uint16_t end_handle;
|
||||
att_read_callback_t read_callback;
|
||||
att_write_callback_t write_callback;
|
||||
btstack_packet_handler_t packet_handler;
|
||||
} att_service_handler_t;
|
||||
|
||||
// MARK: ATT Operations
|
||||
|
||||
/**
|
||||
@brief setup ATT database
|
||||
@param db
|
||||
*/
|
||||
void att_set_db(uint8_t const * db);
|
||||
|
||||
/*
|
||||
@brief set callback for read of dynamic attributes
|
||||
@param callback
|
||||
*/
|
||||
void att_set_read_callback(att_read_callback_t callback);
|
||||
|
||||
/**
|
||||
@brief set callback for write of dynamic attributes
|
||||
@param callback
|
||||
*/
|
||||
void att_set_write_callback(att_write_callback_t callback);
|
||||
|
||||
/**
|
||||
@brief debug helper, dump ATT database to stdout using log_info
|
||||
*/
|
||||
void att_dump_attributes(void);
|
||||
|
||||
/**
|
||||
@brief process ATT request against database and put response into response buffer
|
||||
@param att_connection used for mtu and security properties
|
||||
@param request_buffer, request_len: ATT request from client
|
||||
@param response_buffer for result
|
||||
@return len of data in response buffer. 0 = no response,
|
||||
ATT_READ_RESPONSE_PENDING if it was returned at least once for dynamic data (requires ENABLE_ATT_DELAYED_READ_RESPONSE)
|
||||
*/
|
||||
uint16_t att_handle_request(att_connection_t * att_connection,
|
||||
uint8_t * request_buffer,
|
||||
uint16_t request_len,
|
||||
uint8_t * response_buffer);
|
||||
|
||||
/**
|
||||
@brief setup value notification in response buffer for a given handle and value
|
||||
@param att_connection
|
||||
@param attribute_handle
|
||||
@param value
|
||||
@param value_len
|
||||
@param response_buffer for notification
|
||||
*/
|
||||
uint16_t att_prepare_handle_value_notification(att_connection_t * att_connection,
|
||||
uint16_t attribute_handle,
|
||||
const uint8_t *value,
|
||||
uint16_t value_len,
|
||||
uint8_t * response_buffer);
|
||||
|
||||
/**
|
||||
@brief setup value notification in response buffer for multiple handles and values
|
||||
@param att_connection
|
||||
@param attribute_handle
|
||||
@param value
|
||||
@param value_len
|
||||
@param response_buffer for notification
|
||||
*/
|
||||
uint16_t att_prepare_handle_value_multiple_notification(att_connection_t * att_connection,
|
||||
uint8_t num_attributes,
|
||||
const uint16_t * attribute_handles,
|
||||
const uint8_t ** values_data,
|
||||
const uint16_t * values_len,
|
||||
uint8_t * response_buffer);
|
||||
|
||||
/**
|
||||
@brief setup value indication in response buffer for a given handle and value
|
||||
@param att_connection
|
||||
@param attribute_handle
|
||||
@param value
|
||||
@param value_len
|
||||
@param response_buffer for indication
|
||||
*/
|
||||
uint16_t att_prepare_handle_value_indication(att_connection_t * att_connection,
|
||||
uint16_t attribute_handle,
|
||||
const uint8_t *value,
|
||||
uint16_t value_len,
|
||||
uint8_t * response_buffer);
|
||||
|
||||
/**
|
||||
@brief transcation queue of prepared writes, e.g., after disconnect
|
||||
@return att_connection
|
||||
*/
|
||||
void att_clear_transaction_queue(att_connection_t * att_connection);
|
||||
|
||||
// att_read_callback helpers for a various data types
|
||||
|
||||
/**
|
||||
@brief Handle read of blob like data for att_read_callback
|
||||
@param blob of data
|
||||
@param blob_size of blob
|
||||
@param offset from att_read_callback
|
||||
@param buffer from att_read_callback
|
||||
@param buffer_size from att_read_callback
|
||||
@return value size for buffer == 0 and num bytes copied otherwise
|
||||
*/
|
||||
uint16_t att_read_callback_handle_blob(const uint8_t * blob, uint16_t blob_size, uint16_t offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
|
||||
/**
|
||||
@brief Handle read of little endian unsigned 32 bit value for att_read_callback
|
||||
@param value
|
||||
@param offset from att_read_callback
|
||||
@param buffer from att_read_callback
|
||||
@param buffer_size from att_read_callback
|
||||
@return value size for buffer == 0 and num bytes copied otherwise
|
||||
*/
|
||||
uint16_t att_read_callback_handle_little_endian_32(uint32_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
|
||||
/**
|
||||
@brief Handle read of little endian unsigned 16 bit value for att_read_callback
|
||||
@param value
|
||||
@param offset from att_read_callback
|
||||
@param buffer from att_read_callback
|
||||
@param buffer_size from att_read_callback
|
||||
@return value size for buffer == 0 and num bytes copied otherwise
|
||||
*/
|
||||
uint16_t att_read_callback_handle_little_endian_16(uint16_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
|
||||
/**
|
||||
@brief Handle read of single byte for att_read_callback
|
||||
@param blob of data
|
||||
@param blob_size of blob
|
||||
@param offset from att_read_callback
|
||||
@param buffer from att_read_callback
|
||||
@param buffer_size from att_read_callback
|
||||
@return value size for buffer == 0 and num bytes copied otherwise
|
||||
*/
|
||||
uint16_t att_read_callback_handle_byte(uint8_t value, uint16_t offset, uint8_t * buffer, uint16_t buffer_size);
|
||||
|
||||
|
||||
// experimental client API
|
||||
/**
|
||||
@brief Get UUID for handle
|
||||
@param attribute_handle
|
||||
@return 0 if not found
|
||||
*/
|
||||
uint16_t att_uuid_for_handle(uint16_t attribute_handle);
|
||||
|
||||
/**
|
||||
@brief Get const value for handle
|
||||
@param attribute_handle
|
||||
@param out_value_len output variable that hold value len
|
||||
@return value
|
||||
*/
|
||||
|
||||
const uint8_t * gatt_server_get_const_value_for_handle(uint16_t attribute_handle, uint16_t * out_value_len);
|
||||
|
||||
// experimental GATT Server API
|
||||
|
||||
/**
|
||||
@brief Get handle range for primary service.
|
||||
@param uuid16
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@return false if not found
|
||||
*/
|
||||
bool gatt_server_get_handle_range_for_service_with_uuid16(uint16_t uuid16, uint16_t * start_handle, uint16_t * end_handle);
|
||||
|
||||
/**
|
||||
@brief Get handle range for included service.
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@param uuid16
|
||||
@param out_included_service_handle
|
||||
@param out_included_service_start_handle
|
||||
@param out_included_service_end_handle
|
||||
@return false if not found
|
||||
*/
|
||||
bool gatt_server_get_included_service_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16,
|
||||
uint16_t * out_included_service_handle, uint16_t * out_included_service_start_handle, uint16_t * out_included_service_end_handle);
|
||||
|
||||
/**
|
||||
@brief Get value handle for characteristic.
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@param uuid16
|
||||
@return 0 if not found
|
||||
*/
|
||||
uint16_t gatt_server_get_value_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16);
|
||||
|
||||
/**
|
||||
@brief Get descriptor handle for characteristic.
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@param characteristic_uuid16
|
||||
@param descriptor_uuid16
|
||||
@return 0 if not found
|
||||
*/
|
||||
uint16_t gatt_server_get_descriptor_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16, uint16_t descriptor_uuid16);
|
||||
|
||||
/**
|
||||
@brief Get client configuration handle for characteristic.
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@param characteristic_uuid16
|
||||
@return 0 if not found
|
||||
*/
|
||||
uint16_t gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16);
|
||||
|
||||
/**
|
||||
@brief Get server configuration handle for characteristic.
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@param characteristic_uuid16
|
||||
@param descriptor_uuid16
|
||||
@return 0 if not found
|
||||
*/
|
||||
uint16_t gatt_server_get_server_configuration_handle_for_characteristic_with_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t characteristic_uuid16);
|
||||
|
||||
|
||||
/**
|
||||
@brief Get handle range for primary service.
|
||||
@param uuid128
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@return false if not found
|
||||
*/
|
||||
bool gatt_server_get_handle_range_for_service_with_uuid128(const uint8_t * uuid128, uint16_t * start_handle, uint16_t * end_handle);
|
||||
|
||||
/**
|
||||
@brief Get value handle.
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@param uuid128
|
||||
@return 0 if not found
|
||||
*/
|
||||
uint16_t gatt_server_get_value_handle_for_characteristic_with_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128);
|
||||
|
||||
/**
|
||||
@brief Get client configuration handle.
|
||||
@param start_handle
|
||||
@param end_handle
|
||||
@param uuid128
|
||||
@return 0 if not found
|
||||
*/
|
||||
uint16_t gatt_server_get_client_configuration_handle_for_characteristic_with_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128);
|
||||
|
||||
/* API_END */
|
||||
|
||||
// non-user functionality for att_server
|
||||
|
||||
/**
|
||||
@brief Check if writes to handle should be persistent
|
||||
@param handle
|
||||
@return 1 if persistent
|
||||
*/
|
||||
bool att_is_persistent_ccc(uint16_t handle);
|
||||
|
||||
|
||||
|
||||
// auto-pts testing, returns response size
|
||||
#ifdef ENABLE_BTP
|
||||
uint16_t btp_att_get_attributes_by_uuid16(uint16_t start_handle, uint16_t end_handle, uint16_t uuid16, uint8_t * response_buffer, uint16_t response_buffer_size);
|
||||
uint16_t btp_att_get_attributes_by_uuid128(uint16_t start_handle, uint16_t end_handle, const uint8_t * uuid128, uint8_t * response_buffer, uint16_t response_buffer_size);
|
||||
uint16_t btp_att_get_attribute_value(att_connection_t * att_connection, uint16_t attribute_handle, uint8_t * response_buffer, uint16_t response_buffer_size);
|
||||
#endif
|
||||
|
||||
#if defined __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif // ATT_H
|
||||
@@ -0,0 +1,926 @@
|
||||
/*
|
||||
Copyright (C) 2015 BlueKitchen GmbH
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
4. Any redistribution, use, or modification is done solely for
|
||||
personal benefit and not for any commercial purpose or for
|
||||
monetary gain.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
Please inquire about commercial licensing options at
|
||||
contact@bluekitchen-gmbh.com
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
bluetooth.h
|
||||
|
||||
Numbers defined or derived from the official Bluetooth specification
|
||||
*/
|
||||
|
||||
#ifndef BLUETOOTH_H
|
||||
#define BLUETOOTH_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
@brief hci connection handle type
|
||||
*/
|
||||
typedef uint16_t hci_con_handle_t;
|
||||
|
||||
/**
|
||||
@brief Length of a bluetooth device address.
|
||||
*/
|
||||
#define BD_ADDR_LEN 6
|
||||
|
||||
/**
|
||||
@brief Bluetooth address
|
||||
*/
|
||||
typedef uint8_t bd_addr_t[BD_ADDR_LEN];
|
||||
|
||||
/**
|
||||
Address types
|
||||
*/
|
||||
typedef enum {
|
||||
// Public Device Address
|
||||
BD_ADDR_TYPE_LE_PUBLIC = 0,
|
||||
// Random Device Address
|
||||
BD_ADDR_TYPE_LE_RANDOM = 1,
|
||||
// Public Identity Address (Corresponds to Resolved Private Address)
|
||||
BD_ADDR_TYPE_LE_PUBLIC_IDENTITY = 2,
|
||||
// Random (static) Identity Address (Corresponds to Resolved Private Address)
|
||||
BD_ADDR_TYPE_LE_RANDOM_IDENTITY = 3,
|
||||
// internal BTstack addr types for Classic connections
|
||||
BD_ADDR_TYPE_SCO = 0xfc,
|
||||
BD_ADDR_TYPE_ACL = 0xfd,
|
||||
BD_ADDR_TYPE_UNKNOWN = 0xfe, // also used as 'invalid'
|
||||
} bd_addr_type_t;
|
||||
|
||||
/**
|
||||
Link types for BR/EDR Connections
|
||||
*/
|
||||
typedef enum {
|
||||
HCI_LINK_TYPE_SCO = 0,
|
||||
HCI_LINK_TYPE_ACL = 1,
|
||||
HCI_LINK_TYPE_ESCO = 2,
|
||||
} hci_link_type_t;
|
||||
|
||||
|
||||
/**
|
||||
@brief link key
|
||||
*/
|
||||
#define LINK_KEY_LEN 16
|
||||
#define LINK_KEY_STR_LEN (LINK_KEY_LEN*2)
|
||||
typedef uint8_t link_key_t[LINK_KEY_LEN];
|
||||
|
||||
/**
|
||||
@brief link key type
|
||||
*/
|
||||
typedef enum {
|
||||
INVALID_LINK_KEY = 0xffff,
|
||||
COMBINATION_KEY = 0, // standard pairing
|
||||
LOCAL_UNIT_KEY, // ?
|
||||
REMOTE_UNIT_KEY, // ?
|
||||
DEBUG_COMBINATION_KEY, // SSP with debug
|
||||
UNAUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P192, // SSP Simple Pairing
|
||||
AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P192, // SSP Passkey, Number confirm, OOB
|
||||
CHANGED_COMBINATION_KEY, // Link key changed using Change Connection Lnk Key
|
||||
UNAUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P256, // SSP Simpe Pairing
|
||||
AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P256, // SSP Passkey, Number confirm, OOB
|
||||
} link_key_type_t;
|
||||
|
||||
/**
|
||||
LE Privacy 1.2
|
||||
*/
|
||||
typedef enum {
|
||||
LE_PRIVACY_MODE_NETWORK = 0,
|
||||
LE_PRIVACY_MODE_DEVICE = 1,
|
||||
} le_privacy_mode_t;
|
||||
|
||||
/**
|
||||
@brief Extended Inquiry Response
|
||||
*/
|
||||
#define EXTENDED_INQUIRY_RESPONSE_DATA_LEN 240
|
||||
|
||||
/**
|
||||
@brief Inquiry modes
|
||||
*/
|
||||
typedef enum {
|
||||
INQUIRY_MODE_STANDARD = 0,
|
||||
INQUIRY_MODE_RSSI,
|
||||
INQUIRY_MODE_RSSI_AND_EIR,
|
||||
} inquiry_mode_t;
|
||||
|
||||
/**
|
||||
@brief Page Scan Types
|
||||
*/
|
||||
typedef enum {
|
||||
PAGE_SCAN_MODE_STANDARD = 0,
|
||||
PAGE_SCAN_MODE_INTERLACED,
|
||||
} page_scan_type_t;
|
||||
|
||||
/**
|
||||
@brief Inquiry Scan Types
|
||||
*/
|
||||
typedef enum {
|
||||
INQUIRY_SCAN_MODE_STANDARD = 0,
|
||||
INQUIRY_SCAN_MODE_INTERLACED,
|
||||
} inquiry_scan_type_t;
|
||||
|
||||
/**
|
||||
Link Supervision Timeout Default, 0x7d00 * 0.625ms = 20s
|
||||
*/
|
||||
#define HCI_LINK_SUPERVISION_TIMEOUT_DEFAULT 0x7D00
|
||||
|
||||
/**
|
||||
Service Type used for QoS Setup and Flow Specification
|
||||
*/
|
||||
typedef enum {
|
||||
HCI_SERVICE_TYPE_NO_TRAFFIC = 0,
|
||||
HCI_SERVICE_TYPE_BEST_EFFORT,
|
||||
HCI_SERVICE_TYPE_GUARANTEED,
|
||||
HCI_SERVICE_TYPE_INVALID,
|
||||
} hci_service_type_t;
|
||||
|
||||
/**
|
||||
HCI Transport
|
||||
*/
|
||||
|
||||
/**
|
||||
packet types - used in BTstack and over the H4 UART interface
|
||||
*/
|
||||
#define HCI_COMMAND_DATA_PACKET 0x01
|
||||
#define HCI_ACL_DATA_PACKET 0x02
|
||||
#define HCI_SCO_DATA_PACKET 0x03
|
||||
#define HCI_EVENT_PACKET 0x04
|
||||
#define HCI_ISO_DATA_PACKET 0x05
|
||||
|
||||
/**
|
||||
Other assigned numbers, Assigned_Numbers_Host Controller Interface.pdf
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
HCI_AUDIO_CODING_FORMAT_U_LAW_LOG = 0x00,
|
||||
HCI_AUDIO_CODING_FORMAT_A_LAW_LOG,
|
||||
HCI_AUDIO_CODING_FORMAT_CVSD,
|
||||
HCI_AUDIO_CODING_FORMAT_TRANSPARENT, // Indicates that the controller does not do any transcoding or resampling. This is also used for test mode.
|
||||
HCI_AUDIO_CODING_FORMAT_LINEAR_PCM,
|
||||
HCI_AUDIO_CODING_FORMAT_MSBC,
|
||||
HCI_AUDIO_CODING_FORMAT_LC3,
|
||||
HCI_AUDIO_CODING_FORMAT_G_729A,
|
||||
HCI_AUDIO_CODING_FORMAT_RFU,
|
||||
HCI_AUDIO_CODING_FORMAT_VENDOR_SPECIFIC = 0xFF
|
||||
} hci_audio_coding_format_t;
|
||||
|
||||
/**
|
||||
HCI Layer
|
||||
*/
|
||||
|
||||
//
|
||||
// Error Codes rfom Bluetooth Core Specification
|
||||
//
|
||||
|
||||
/* ENUM_START: BLUETOOTH_ERROR_CODE */
|
||||
#define ERROR_CODE_SUCCESS 0x00
|
||||
#define ERROR_CODE_UNKNOWN_HCI_COMMAND 0x01
|
||||
#define ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER 0x02
|
||||
#define ERROR_CODE_HARDWARE_FAILURE 0x03
|
||||
#define ERROR_CODE_PAGE_TIMEOUT 0x04
|
||||
#define ERROR_CODE_AUTHENTICATION_FAILURE 0x05
|
||||
#define ERROR_CODE_PIN_OR_KEY_MISSING 0x06
|
||||
#define ERROR_CODE_MEMORY_CAPACITY_EXCEEDED 0x07
|
||||
#define ERROR_CODE_CONNECTION_TIMEOUT 0x08
|
||||
#define ERROR_CODE_CONNECTION_LIMIT_EXCEEDED 0x09
|
||||
#define ERROR_CODE_SYNCHRONOUS_CONNECTION_LIMIT_TO_A_DEVICE_EXCEEDED 0x0A
|
||||
#define ERROR_CODE_ACL_CONNECTION_ALREADY_EXISTS 0x0B
|
||||
#define ERROR_CODE_COMMAND_DISALLOWED 0x0C
|
||||
#define ERROR_CODE_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES 0x0D
|
||||
#define ERROR_CODE_CONNECTION_REJECTED_DUE_TO_SECURITY_REASONS 0x0E
|
||||
#define ERROR_CODE_CONNECTION_REJECTED_DUE_TO_UNACCEPTABLE_BD_ADDR 0x0F
|
||||
#define ERROR_CODE_CONNECTION_ACCEPT_TIMEOUT_EXCEEDED 0x10
|
||||
#define ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE 0x11
|
||||
#define ERROR_CODE_INVALID_HCI_COMMAND_PARAMETERS 0x12
|
||||
#define ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION 0x13
|
||||
#define ERROR_CODE_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_LOW_RESOURCES 0x14
|
||||
#define ERROR_CODE_REMOTE_DEVICE_TERMINATED_CONNECTION_DUE_TO_POWER_OFF 0x15
|
||||
#define ERROR_CODE_CONNECTION_TERMINATED_BY_LOCAL_HOST 0x16
|
||||
#define ERROR_CODE_REPEATED_ATTEMPTS 0x17
|
||||
#define ERROR_CODE_PAIRING_NOT_ALLOWED 0x18
|
||||
#define ERROR_CODE_UNKNOWN_LMP_PDU 0x19
|
||||
#define ERROR_CODE_UNSUPPORTED_REMOTE_FEATURE_UNSUPPORTED_LMP_FEATURE 0x1A
|
||||
#define ERROR_CODE_SCO_OFFSET_REJECTED 0x1B
|
||||
#define ERROR_CODE_SCO_INTERVAL_REJECTED 0x1C
|
||||
#define ERROR_CODE_SCO_AIR_MODE_REJECTED 0x1D
|
||||
#define ERROR_CODE_INVALID_LMP_PARAMETERS_INVALID_LL_PARAMETERS 0x1E
|
||||
#define ERROR_CODE_UNSPECIFIED_ERROR 0x1F
|
||||
#define ERROR_CODE_UNSUPPORTED_LMP_PARAMETER_VALUE_UNSUPPORTED_LL_PARAMETER_VALUE 0x20
|
||||
#define ERROR_CODE_ROLE_CHANGE_NOT_ALLOWED 0x21
|
||||
#define ERROR_CODE_LMP_RESPONSE_TIMEOUT_LL_RESPONSE_TIMEOUT 0x22
|
||||
#define ERROR_CODE_LMP_ERROR_TRANSACTION_COLLISION 0x23
|
||||
#define ERROR_CODE_LMP_PDU_NOT_ALLOWED 0x24
|
||||
#define ERROR_CODE_ENCRYPTION_MODE_NOT_ACCEPTABLE 0x25
|
||||
#define ERROR_CODE_LINK_KEY_CANNOT_BE_CHANGED 0x26
|
||||
#define ERROR_CODE_REQUESTED_QOS_NOT_SUPPORTED 0x27
|
||||
#define ERROR_CODE_INSTANT_PASSED 0x28
|
||||
#define ERROR_CODE_PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED 0x29
|
||||
#define ERROR_CODE_DIFFERENT_TRANSACTION_COLLISION 0x2A
|
||||
#define ERROR_CODE_RESERVED 0x2B
|
||||
#define ERROR_CODE_QOS_UNACCEPTABLE_PARAMETER 0x2C
|
||||
#define ERROR_CODE_QOS_REJECTED 0x2D
|
||||
#define ERROR_CODE_CHANNEL_CLASSIFICATION_NOT_SUPPORTED 0x2E
|
||||
#define ERROR_CODE_INSUFFICIENT_SECURITY 0x2F
|
||||
#define ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE 0x30
|
||||
// #define ERROR_CODE_RESERVED
|
||||
#define ERROR_CODE_ROLE_SWITCH_PENDING 0x32
|
||||
// #define ERROR_CODE_RESERVED
|
||||
#define ERROR_CODE_RESERVED_SLOT_VIOLATION 0x34
|
||||
#define ERROR_CODE_ROLE_SWITCH_FAILED 0x35
|
||||
#define ERROR_CODE_EXTENDED_INQUIRY_RESPONSE_TOO_LARGE 0x36
|
||||
#define ERROR_CODE_SECURE_SIMPLE_PAIRING_NOT_SUPPORTED_BY_HOST 0x37
|
||||
#define ERROR_CODE_HOST_BUSY_PAIRING 0x38
|
||||
#define ERROR_CODE_CONNECTION_REJECTED_DUE_TO_NO_SUITABLE_CHANNEL_FOUND 0x39
|
||||
#define ERROR_CODE_CONTROLLER_BUSY 0x3A
|
||||
#define ERROR_CODE_UNACCEPTABLE_CONNECTION_PARAMETERS 0x3B
|
||||
#define ERROR_CODE_DIRECTED_ADVERTISING_TIMEOUT 0x3C
|
||||
#define ERROR_CODE_CONNECTION_TERMINATED_DUE_TO_MIC_FAILURE 0x3D
|
||||
#define ERROR_CODE_CONNECTION_FAILED_TO_BE_ESTABLISHED 0x3E
|
||||
#define ERROR_CODE_MAC_CONNECTION_FAILED 0x3F
|
||||
#define ERROR_CODE_COARSE_CLOCK_ADJUSTMENT_REJECTED_BUT_WILL_TRY_TO_ADJUST_USING_CLOCK_DRAGGING 0x40
|
||||
|
||||
// BTstack defined ERRORS, mapped into BLuetooth status code range
|
||||
|
||||
#define BTSTACK_CONNECTION_TO_BTDAEMON_FAILED 0x50
|
||||
#define BTSTACK_ACTIVATION_FAILED_SYSTEM_BLUETOOTH 0x51
|
||||
#define BTSTACK_ACTIVATION_POWERON_FAILED 0x52
|
||||
#define BTSTACK_ACTIVATION_FAILED_UNKNOWN 0x53
|
||||
#define BTSTACK_NOT_ACTIVATED 0x54
|
||||
#define BTSTACK_BUSY 0x55
|
||||
#define BTSTACK_MEMORY_ALLOC_FAILED 0x56
|
||||
#define BTSTACK_ACL_BUFFERS_FULL 0x57
|
||||
|
||||
// l2cap errors - enumeration by the command that created them
|
||||
#define L2CAP_COMMAND_REJECT_REASON_COMMAND_NOT_UNDERSTOOD 0x60
|
||||
#define L2CAP_COMMAND_REJECT_REASON_SIGNALING_MTU_EXCEEDED 0x61
|
||||
#define L2CAP_COMMAND_REJECT_REASON_INVALID_CID_IN_REQUEST 0x62
|
||||
|
||||
#define L2CAP_CONNECTION_RESPONSE_RESULT_SUCCESSFUL 0x63
|
||||
#define L2CAP_CONNECTION_RESPONSE_RESULT_PENDING 0x64
|
||||
#define L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_PSM 0x65
|
||||
#define L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_SECURITY 0x66
|
||||
#define L2CAP_CONNECTION_RESPONSE_RESULT_REFUSED_RESOURCES 0x67
|
||||
#define L2CAP_CONNECTION_RESPONSE_RESULT_ERTM_NOT_SUPPORTED 0x68
|
||||
|
||||
// should be L2CAP_CONNECTION_RTX_TIMEOUT
|
||||
#define L2CAP_CONNECTION_RESPONSE_RESULT_RTX_TIMEOUT 0x69
|
||||
#define L2CAP_CONNECTION_BASEBAND_DISCONNECT 0x6A
|
||||
#define L2CAP_SERVICE_ALREADY_REGISTERED 0x6B
|
||||
#define L2CAP_DATA_LEN_EXCEEDS_REMOTE_MTU 0x6C
|
||||
#define L2CAP_SERVICE_DOES_NOT_EXIST 0x6D
|
||||
#define L2CAP_LOCAL_CID_DOES_NOT_EXIST 0x6E
|
||||
#define L2CAP_CONNECTION_RESPONSE_UNKNOWN_ERROR 0x6F
|
||||
|
||||
#define RFCOMM_MULTIPLEXER_STOPPED 0x70
|
||||
#define RFCOMM_CHANNEL_ALREADY_REGISTERED 0x71
|
||||
#define RFCOMM_NO_OUTGOING_CREDITS 0x72
|
||||
#define RFCOMM_AGGREGATE_FLOW_OFF 0x73
|
||||
#define RFCOMM_DATA_LEN_EXCEEDS_MTU 0x74
|
||||
|
||||
#define HFP_REMOTE_REJECTS_AUDIO_CONNECTION 0x7F
|
||||
|
||||
#define SDP_HANDLE_ALREADY_REGISTERED 0x80
|
||||
#define SDP_QUERY_INCOMPLETE 0x81
|
||||
#define SDP_SERVICE_NOT_FOUND 0x82
|
||||
#define SDP_HANDLE_INVALID 0x83
|
||||
#define SDP_QUERY_BUSY 0x84
|
||||
|
||||
#define ATT_HANDLE_VALUE_INDICATION_IN_PROGRESS 0x90
|
||||
#define ATT_HANDLE_VALUE_INDICATION_TIMEOUT 0x91
|
||||
#define ATT_HANDLE_VALUE_INDICATION_DISCONNECT 0x92
|
||||
|
||||
#define GATT_CLIENT_NOT_CONNECTED 0x93
|
||||
#define GATT_CLIENT_BUSY 0x94
|
||||
#define GATT_CLIENT_IN_WRONG_STATE 0x95
|
||||
#define GATT_CLIENT_DIFFERENT_CONTEXT_FOR_ADDRESS_ALREADY_EXISTS 0x96
|
||||
#define GATT_CLIENT_VALUE_TOO_LONG 0x97
|
||||
#define GATT_CLIENT_CHARACTERISTIC_NOTIFICATION_NOT_SUPPORTED 0x98
|
||||
#define GATT_CLIENT_CHARACTERISTIC_INDICATION_NOT_SUPPORTED 0x99
|
||||
|
||||
#define BNEP_SERVICE_ALREADY_REGISTERED 0xA0
|
||||
#define BNEP_CHANNEL_NOT_CONNECTED 0xA1
|
||||
#define BNEP_DATA_LEN_EXCEEDS_MTU 0xA2
|
||||
|
||||
// OBEX ERRORS
|
||||
#define OBEX_UNKNOWN_ERROR 0xB0
|
||||
#define OBEX_CONNECT_FAILED 0xB1
|
||||
#define OBEX_DISCONNECTED 0xB2
|
||||
#define OBEX_NOT_FOUND 0xB3
|
||||
#define OBEX_NOT_ACCEPTABLE 0xB4
|
||||
#define OBEX_ABORTED 0xB5
|
||||
|
||||
#define MESH_ERROR_APPKEY_INDEX_INVALID 0xD0
|
||||
/* ENUM_END */
|
||||
|
||||
|
||||
/* ENUM_START: AVRCP_BROWSING_ERROR_CODE */
|
||||
#define AVRCP_BROWSING_ERROR_CODE_INVALID_COMMAND 0x00 // Sent if TG received a PDU that it did not understand. Valid for All.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_INVALID_PARAMETER 0x01 // Sent if the TG received a PDU with a parameter ID that it did not understand. Sent if there is only one parameter ID in the PDU. Valid for All.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_SPECIFIED_PARAMETER_NOT_FOUND 0x02 // Sent if the parameter ID is understood, but content is wrong or corrupted. Valid for All.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_INTERNAL_ERROR 0x03 // Sent if there are error conditions not covered by a more specific error code. Valid for All.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_SUCCESS 0x04 // This is the status that should be returned if the operation was successful. Valid for All except where the response CType is AV/C REJECTED.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_UID_CHANGED 0x05 // The UIDs on the device have changed. Valid for All.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_RESERVED_06 0x06 // Valid for All.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_INVALID_DIRECTION 0x07 // The Direction parameter is invalid. Valid for Change Path.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_NOT_A_DIRECTORY 0x08 // The UID provided does not refer to a folder item. Valid for Change Path.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_DOES_NOT_EXIST 0x09 // The UID provided does not refer to any currently valid. Valid for Change Path, PlayItem, AddToNowPlaying, GetItemAttributes.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_INVALID_SCOPE 0x0a // The scope parameter is invalid. Valid for GetFolderItems, PlayItem, AddToNowPlayer, GetItemAttributes,.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_RANGE_OUT_OF_BOUNDS 0x0b // The start of range provided is not valid. Valid for GetFolderItems.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_UID_IS_A_DIRECTORY 0x0c // The UID provided refers to a directory, which cannot be handled by this media player. Valid for PlayItem, AddToNowPlaying.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_MEDIA_IN_USES 0x0d // The media is not able to be used for this operation at this time. Valid for PlayItem, AddToNowPlaying.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_NOW_PLAYING_LIST_FULL 0x0e // No more items can be added to the Now Playing List. Valid for AddToNowPlaying.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_SEARCH_NOT_SUPPORTED 0x0f // The Browsed Media Player does not support search. Valid for Search.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_SEARCH_IN_PROGRESS 0x10 // A search operation is already in progress. Valid for Search.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_INVALID_PLAYER_ID 0x11 // The specified Player Id does not refer to a valid player. Valid for SetAddressedPlayer, SetBrowsedPlayer.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_PLAYER_NOT_BROWSABLE 0x12 // The Player Id supplied refers to a Media Player which does not support browsing. Valid for SetBrowsedPlayer.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_PLAYER_NOT_ADDRESSED 0x13 // The Player Id supplied refers to a player which is not currently addressed, and the command is not able to be performed if the player is not set as addressed. Valid for Search SetBrowsedPlayer.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_NO_VALID_SEARCH_RESULTS 0x14 // The Search result list does not contain valid entries, e.g. after being invalidated due to change of browsed player. Valid for GetFolderItems.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_NO_AVAILABLE_PLAYERS 0x15 // Valid for All.
|
||||
#define AVRCP_BROWSING_ERROR_CODE_ADDRESSED_PLAYER_CHANGED 0x16 // Valid for Register Notification.
|
||||
// 0x17-0xff Reserved
|
||||
/* ENUM_END */
|
||||
|
||||
// HCI roles
|
||||
typedef enum {
|
||||
HCI_ROLE_MASTER = 0,
|
||||
HCI_ROLE_SLAVE = 1,
|
||||
HCI_ROLE_INVALID = 0xff,
|
||||
} hci_role_t;
|
||||
|
||||
// packet sizes (max payload)
|
||||
#define HCI_ACL_DM1_SIZE 17
|
||||
#define HCI_ACL_DH1_SIZE 27
|
||||
#define HCI_ACL_2DH1_SIZE 54
|
||||
#define HCI_ACL_3DH1_SIZE 83
|
||||
#define HCI_ACL_DM3_SIZE 121
|
||||
#define HCI_ACL_DH3_SIZE 183
|
||||
#define HCI_ACL_DM5_SIZE 224
|
||||
#define HCI_ACL_DH5_SIZE 339
|
||||
#define HCI_ACL_2DH3_SIZE 367
|
||||
#define HCI_ACL_3DH3_SIZE 552
|
||||
#define HCI_ACL_2DH5_SIZE 679
|
||||
#define HCI_ACL_3DH5_SIZE 1021
|
||||
#define HCI_SCO_HV1_SIZE 10
|
||||
#define HCI_SCO_HV2_SIZE 20
|
||||
#define HCI_SCO_HV3_SIZE 30
|
||||
#define HCI_SCO_EV3_SIZE 30
|
||||
#define HCI_SCO_EV4_SIZE 120
|
||||
#define HCI_SCO_EV5_SIZE 180
|
||||
#define HCI_SCO_2EV3_SIZE 60
|
||||
#define HCI_SCO_2EV5_SIZE 360
|
||||
#define HCI_SCO_3EV3_SIZE 90
|
||||
#define HCI_SCO_3EV5_SIZE 540
|
||||
|
||||
#define LE_ADVERTISING_DATA_SIZE 31
|
||||
#define LE_EXTENDED_ADVERTISING_DATA_SIZE 229
|
||||
#define LE_EXTENDED_ADVERTISING_MAX_HANDLE 0xEFu
|
||||
#define LE_EXTENDED_ADVERTISING_MAX_CHUNK_LEN 251
|
||||
|
||||
// advertising event properties for extended advertising
|
||||
#define LE_ADVERTISING_PROPERTIES_CONNECTABLE (1u<<0)
|
||||
#define LE_ADVERTISING_PROPERTIES_SCANNABLE (1u<<1)
|
||||
#define LE_ADVERTISING_PROPERTIES_DIRECTED (1u<<2)
|
||||
#define LE_ADVERTISING_PROPERTIES_HIGH_DUTY_CYCLE (1u<<3)
|
||||
#define LE_ADVERTISING_PROPERTIES_LEGACY (1u<<4)
|
||||
#define LE_ADVERTISING_PROPERTIES_ANONYMOUS (1u<<5)
|
||||
#define LE_ADVERTISING_PROPERTIES_INCLUDE_TX_POWER (1u<<6)
|
||||
|
||||
|
||||
// SCO Packet Types
|
||||
#define SCO_PACKET_TYPES_NONE 0x0000
|
||||
#define SCO_PACKET_TYPES_HV1 0x0001
|
||||
#define SCO_PACKET_TYPES_HV2 0x0002
|
||||
#define SCO_PACKET_TYPES_HV3 0x0004
|
||||
#define SCO_PACKET_TYPES_EV3 0x0008
|
||||
#define SCO_PACKET_TYPES_EV4 0x0010
|
||||
#define SCO_PACKET_TYPES_EV5 0x0020
|
||||
#define SCO_PACKET_TYPES_2EV3 0x0040
|
||||
#define SCO_PACKET_TYPES_3EV3 0x0080
|
||||
#define SCO_PACKET_TYPES_2EV5 0x0100
|
||||
#define SCO_PACKET_TYPES_3EV5 0x0200
|
||||
#define SCO_PACKET_TYPES_ALL 0x03FF
|
||||
#define SCO_PACKET_TYPES_SCO 0x0007
|
||||
#define SCO_PACKET_TYPES_ESCO 0x03F8
|
||||
|
||||
// Link Policy Settings
|
||||
#define LM_LINK_POLICY_DISABLE_ALL_LM_MODES 0
|
||||
#define LM_LINK_POLICY_ENABLE_ROLE_SWITCH 1
|
||||
#define LM_LINK_POLICY_ENABLE_HOLD_MODE 2
|
||||
#define LM_LINK_POLICY_ENABLE_SNIFF_MODE 4
|
||||
|
||||
// ACL Connection Modes
|
||||
#define ACL_CONNECTION_MODE_ACTIVE 0
|
||||
#define ACL_CONNECTION_MODE_HOLD 1
|
||||
#define ACL_CONNECTION_MODE_SNIFF 2
|
||||
|
||||
/**
|
||||
Default INQ Mode
|
||||
*/
|
||||
#define GAP_IAC_GENERAL_INQUIRY 0x9E8B33L // General/Unlimited Inquiry Access Code (GIAC)
|
||||
#define GAP_IAC_LIMITED_INQUIRY 0x9E8B00L // Limited Dedicated Inquiry Access Code (LIAC)
|
||||
|
||||
/**
|
||||
SSP IO Capabilities
|
||||
*/
|
||||
#define SSP_IO_CAPABILITY_DISPLAY_ONLY 0
|
||||
#define SSP_IO_CAPABILITY_DISPLAY_YES_NO 1
|
||||
#define SSP_IO_CAPABILITY_KEYBOARD_ONLY 2
|
||||
#define SSP_IO_CAPABILITY_NO_INPUT_NO_OUTPUT 3
|
||||
#define SSP_IO_CAPABILITY_UNKNOWN 0xff
|
||||
|
||||
|
||||
/**
|
||||
SSP Authentication Requirements, see IO Capability Request Reply Command
|
||||
*/
|
||||
|
||||
// Numeric comparison with automatic accept allowed.
|
||||
#define SSP_IO_AUTHREQ_MITM_PROTECTION_NOT_REQUIRED_NO_BONDING 0x00
|
||||
|
||||
// Use IO Capabilities to deter- mine authentication procedure
|
||||
#define SSP_IO_AUTHREQ_MITM_PROTECTION_REQUIRED_NO_BONDING 0x01
|
||||
|
||||
// Numeric compar- ison with automatic accept allowed.
|
||||
#define SSP_IO_AUTHREQ_MITM_PROTECTION_NOT_REQUIRED_DEDICATED_BONDING 0x02
|
||||
|
||||
// Use IO Capabilities to determine authentication procedure
|
||||
#define SSP_IO_AUTHREQ_MITM_PROTECTION_REQUIRED_DEDICATED_BONDING 0x03
|
||||
|
||||
// Numeric Compari- son with automatic accept allowed.
|
||||
#define SSP_IO_AUTHREQ_MITM_PROTECTION_NOT_REQUIRED_GENERAL_BONDING 0x04
|
||||
|
||||
// Use IO capabilities to determine authentication procedure.
|
||||
#define SSP_IO_AUTHREQ_MITM_PROTECTION_REQUIRED_GENERAL_BONDING 0x05
|
||||
|
||||
|
||||
// OGFs
|
||||
#define OGF_LINK_CONTROL 0x01
|
||||
#define OGF_LINK_POLICY 0x02
|
||||
#define OGF_CONTROLLER_BASEBAND 0x03
|
||||
#define OGF_INFORMATIONAL_PARAMETERS 0x04
|
||||
#define OGF_STATUS_PARAMETERS 0x05
|
||||
#define OGF_TESTING 0x06
|
||||
#define OGF_LE_CONTROLLER 0x08
|
||||
#define OGF_VENDOR 0x3f
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
L2CAP Layer
|
||||
*/
|
||||
|
||||
#define L2CAP_HEADER_SIZE 4
|
||||
|
||||
// minimum signaling MTU
|
||||
#define L2CAP_MINIMAL_MTU 48
|
||||
#define L2CAP_DEFAULT_MTU 672
|
||||
|
||||
// Minimum/default MTU
|
||||
#define L2CAP_LE_DEFAULT_MTU 23
|
||||
|
||||
// L2CAP Fixed Channel IDs
|
||||
#define L2CAP_CID_SIGNALING 0x0001
|
||||
#define L2CAP_CID_CONNECTIONLESS_CHANNEL 0x0002
|
||||
#define L2CAP_CID_ATTRIBUTE_PROTOCOL 0x0004
|
||||
#define L2CAP_CID_SIGNALING_LE 0x0005
|
||||
#define L2CAP_CID_SECURITY_MANAGER_PROTOCOL 0x0006
|
||||
#define L2CAP_CID_BR_EDR_SECURITY_MANAGER 0x0007
|
||||
|
||||
// L2CAP Channels in Basic and Enhanced Retransmission Mode
|
||||
|
||||
// connection response result
|
||||
#define L2CAP_CONNECTION_RESULT_SUCCESS 0x0000
|
||||
#define L2CAP_CONNECTION_RESULT_PENDING 0x0001
|
||||
#define L2CAP_CONNECTION_RESULT_PSM_NOT_SUPPORTED 0x0002
|
||||
#define L2CAP_CONNECTION_RESULT_SECURITY_BLOCK 0x0003
|
||||
#define L2CAP_CONNECTION_RESULT_NO_RESOURCES_AVAILABLE 0x0004
|
||||
#define L2CAP_CONNECTION_RESULT_INVALID_SOURCE_CID 0x0006
|
||||
#define L2CAP_CONNECTION_RESULT_SOURCE_CID_ALREADY_ALLOCATED 0x0007
|
||||
|
||||
// L2CAP Channels in LE Credit-Based Flow-Control Mode
|
||||
|
||||
// connection response result
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_SUCCESS 0x0000
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_SPSM_NOT_SUPPORTED 0x0002
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_NO_RESOURCES_AVAILABLE 0x0004
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_INSUFFICIENT_AUTHENTICATION 0x0005
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_INSUFFICIENT_AUTHORIZATION 0x0006
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_ENCYRPTION_KEY_SIZE_TOO_SHORT 0x0007
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_INSUFFICIENT_ENCRYPTION 0x0008
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_INVALID_SOURCE_CID 0x0009
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_SOURCE_CID_ALREADY_ALLOCATED 0x000A
|
||||
#define L2CAP_CBM_CONNECTION_RESULT_UNACCEPTABLE_PARAMETERS 0x000B
|
||||
|
||||
|
||||
// L2CAP Channels in Enhanced Credit-Based Flow-Control Mode
|
||||
|
||||
// number of CIDs in single connection+reconfiguration request/response
|
||||
#define L2CAP_ECBM_MAX_CID_ARRAY_SIZE 5
|
||||
|
||||
// connection response result
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_SUCCESS 0x0000
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_SPSM_NOT_SUPPORTED 0x0002
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_SOME_REFUSED_INSUFFICIENT_RESOURCES_AVAILABLE 0x0004
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_INSUFFICIENT_AUTHENTICATION 0x0005
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_INSUFFICIENT_AUTHORIZATION 0x0006
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_ENCYRPTION_KEY_SIZE_TOO_SHORT 0x0007
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_INSUFFICIENT_ENCRYPTION 0x0008
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_SOME_REFUSED_INVALID_SOURCE_CID 0x0009
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_SOME_REFUSED_SOURCE_CID_ALREADY_ALOCATED 0x000A
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_UNACCEPTABLE_PARAMETERS 0x000B
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_REFUSED_INVALID_PARAMETERS 0x000C
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_PENDING_NO_FURTHER_INFORMATION 0x000D
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_PENDING_AUTHENTICATION 0x000E
|
||||
#define L2CAP_ECBM_CONNECTION_RESULT_ALL_PENDING_AUTHORIZATION 0x000F
|
||||
|
||||
|
||||
// Result for Reconfigure Request
|
||||
#define L2CAP_ECBM_RECONFIGURE_SUCCESS 0
|
||||
#define L2CAP_ECBM_RECONFIGURE_FAILED_MTU_REDUCTION_NOT_ALLOWED 1
|
||||
#define L2CAP_ECBM_RECONFIGURE_FAILED_MPS_REDUCTION_MULTIPLE_CHANNELS 2
|
||||
#define L2CAP_ECBM_RECONFIGURE_FAILED_DESTINATION_CID_INVALID 3
|
||||
#define L2CAP_ECBM_RECONFIGURE_FAILED_UNACCEPTABLE_PARAMETERS 4
|
||||
|
||||
/**
|
||||
SDP Protocol
|
||||
*/
|
||||
|
||||
// Device Vendor ID Sources
|
||||
#define DEVICE_ID_VENDOR_ID_SOURCE_BLUETOOTH 0x0001
|
||||
#define DEVICE_ID_VENDOR_ID_SOURCE_USB 0x0002
|
||||
|
||||
// OBEX
|
||||
#define SDP_vCard_2_1 0x01
|
||||
#define SDP_vCard_3_0 0x02
|
||||
#define SDP_vCal_1_0 0x03
|
||||
#define SDP_iCal_2_0 0x04
|
||||
#define SDP_vNote 0x05
|
||||
#define SDP_vMessage 0x06
|
||||
#define SDP_OBEXFileTypeAny 0xFF
|
||||
|
||||
/**
|
||||
RFCOMM Protocol
|
||||
*/
|
||||
|
||||
// Line Status
|
||||
#define LINE_STATUS_NO_ERROR 0x00
|
||||
#define LINE_STATUS_OVERRUN_ERROR 0x03
|
||||
#define LINE_STATUS_PARITY_ERORR 0x05
|
||||
#define LINE_STATUS_FRAMING_ERROR 0x09
|
||||
|
||||
// Modem Status Flags
|
||||
#define MODEM_STATUS_FC 0x02
|
||||
#define MODEM_STATUS_RTC 0x04
|
||||
#define MODEM_STATUS_RTR 0x08
|
||||
#define MODEM_STATUS_IC 0x40
|
||||
#define MODEM_STATUS_DV 0x80
|
||||
|
||||
typedef enum rpn_baud {
|
||||
RPN_BAUD_2400 = 0,
|
||||
RPN_BAUD_4800,
|
||||
RPN_BAUD_7200,
|
||||
RPN_BAUD_9600,
|
||||
RPN_BAUD_19200,
|
||||
RPN_BAUD_38400,
|
||||
RPN_BAUD_57600,
|
||||
RPN_BAUD_115200,
|
||||
RPN_BAUD_230400
|
||||
} rpn_baud_t;
|
||||
|
||||
typedef enum rpn_data_bits {
|
||||
RPN_DATA_BITS_5 = 0,
|
||||
RPN_DATA_BITS_6 = 0,
|
||||
RPN_DATA_BITS_7 = 0,
|
||||
RPN_DATA_BITS_8 = 0
|
||||
} rpn_data_bits_t;
|
||||
|
||||
typedef enum rpn_stop_bits {
|
||||
RPN_STOP_BITS_1_0 = 0,
|
||||
RPN_STOP_BITS_1_5
|
||||
} rpn_stop_bits_t;
|
||||
|
||||
typedef enum rpn_parity {
|
||||
RPN_PARITY_NONE = 0,
|
||||
RPN_PARITY_ODD = 1,
|
||||
RPN_PARITY_EVEN = 3,
|
||||
RPN_PARITY_MARK = 5,
|
||||
RPN_PARITY_SPACE = 7,
|
||||
} rpn_parity_t;
|
||||
|
||||
#define RPN_FLOW_CONTROL_XONXOFF_ON_INPUT 0x01
|
||||
#define RPN_FLOW_CONTROL_XONXOFF_ON_OUTPUT 0x02
|
||||
#define RPN_FLOW_CONTROL_RTR_ON_INPUT 0x04
|
||||
#define RPN_FLOW_CONTROL_RTR_ON_OUTPUT 0x08
|
||||
#define RPN_FLOW_CONTROL_RTC_ON_INPUT 0x10
|
||||
#define RPN_FLOW_CONTROL_RTC_ON_OUTPUT 0x20
|
||||
|
||||
#define RPN_PARAM_MASK_0_BAUD 0x01
|
||||
#define RPN_PARAM_MASK_0_DATA_BITS 0x02
|
||||
#define RPN_PARAM_MASK_0_STOP_BITS 0x04
|
||||
#define RPN_PARAM_MASK_0_PARITY 0x08
|
||||
#define RPN_PARAM_MASK_0_PARITY_TYPE 0x10
|
||||
#define RPN_PARAM_MASK_0_XON_CHAR 0x20
|
||||
#define RPN_PARAM_MASK_0_XOFF_CHAR 0x40
|
||||
#define RPN_PARAM_MASK_0_RESERVED 0x80
|
||||
|
||||
// @note: values are identical to rpn_flow_control_t
|
||||
#define RPN_PARAM_MASK_1_XONOFF_ON_INPUT 0x01
|
||||
#define RPN_PARAM_MASK_1_XONOFF_ON_OUTPUT 0x02
|
||||
#define RPN_PARAM_MASK_1_RTR_ON_INPUT 0x04
|
||||
#define RPN_PARAM_MASK_1_RTR_ON_OUTPUT 0x08
|
||||
#define RPN_PARAM_MASK_1_RTC_ON_INPUT 0x10
|
||||
#define RPN_PARAM_MASK_1_RTC_ON_OUTPUT 0x20
|
||||
#define RPN_PARAM_MASK_1_RESERVED_0 0x40
|
||||
#define RPN_PARAM_MASK_1_RESERVED_1 0x80
|
||||
|
||||
/**
|
||||
BNEP Protocol
|
||||
*/
|
||||
|
||||
#ifndef ETHER_ADDR_LEN
|
||||
#define ETHER_ADDR_LEN 6
|
||||
#endif
|
||||
|
||||
#ifndef ETHERTYPE_VLAN
|
||||
#define ETHERTYPE_VLAN 0x8100 /* IEEE 802.1Q VLAN tag */
|
||||
#endif
|
||||
|
||||
#define BNEP_MTU_MIN 1691
|
||||
|
||||
|
||||
/**
|
||||
PAN Profile
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
BNEP_SECURITY_NONE = 0x0000,
|
||||
BNEP_SECURITY_SERVICE_LEVEL_ENFORCED,
|
||||
BNEP_SECURITY_802_1X
|
||||
} security_description_t;
|
||||
|
||||
typedef enum {
|
||||
PAN_NET_ACCESS_TYPE_PSTN = 0x0000,
|
||||
PAN_NET_ACCESS_TYPE_ISDN,
|
||||
PAN_NET_ACCESS_TYPE_DSL,
|
||||
PAN_NET_ACCESS_TYPE_CABLE_MODEM,
|
||||
PAN_NET_ACCESS_TYPE_10MB_ETHERNET,
|
||||
PAN_NET_ACCESS_TYPE_100MB_ETHERNET,
|
||||
PAN_NET_ACCESS_TYPE_4MB_TOKEN_RING,
|
||||
PAN_NET_ACCESS_TYPE_16MB_TOKEN_RING,
|
||||
PAN_NET_ACCESS_TYPE_100MB_TOKEN_RING,
|
||||
PAN_NET_ACCESS_TYPE_FDDI,
|
||||
PAN_NET_ACCESS_TYPE_GSM,
|
||||
PAN_NET_ACCESS_TYPE_CDMA,
|
||||
PAN_NET_ACCESS_TYPE_GPRS,
|
||||
PAN_NET_ACCESS_TYPE_3G,
|
||||
PAN_NET_ACCESS_TYPE_CELULAR,
|
||||
PAN_NET_ACCESS_TYPE_OTHER = 0xFFFE,
|
||||
PAN_NET_ACCESS_TYPE_NONE
|
||||
} net_access_type_t;
|
||||
|
||||
/**
|
||||
ATT
|
||||
*/
|
||||
|
||||
// Minimum/default MTU
|
||||
#define ATT_DEFAULT_MTU 23
|
||||
|
||||
// MARK: ATT Error Codes
|
||||
#define ATT_ERROR_SUCCESS 0x00
|
||||
#define ATT_ERROR_INVALID_HANDLE 0x01
|
||||
#define ATT_ERROR_READ_NOT_PERMITTED 0x02
|
||||
#define ATT_ERROR_WRITE_NOT_PERMITTED 0x03
|
||||
#define ATT_ERROR_INVALID_PDU 0x04
|
||||
#define ATT_ERROR_INSUFFICIENT_AUTHENTICATION 0x05
|
||||
#define ATT_ERROR_REQUEST_NOT_SUPPORTED 0x06
|
||||
#define ATT_ERROR_INVALID_OFFSET 0x07
|
||||
#define ATT_ERROR_INSUFFICIENT_AUTHORIZATION 0x08
|
||||
#define ATT_ERROR_PREPARE_QUEUE_FULL 0x09
|
||||
#define ATT_ERROR_ATTRIBUTE_NOT_FOUND 0x0a
|
||||
#define ATT_ERROR_ATTRIBUTE_NOT_LONG 0x0b
|
||||
#define ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE 0x0c
|
||||
#define ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LENGTH 0x0d
|
||||
#define ATT_ERROR_UNLIKELY_ERROR 0x0e
|
||||
#define ATT_ERROR_INSUFFICIENT_ENCRYPTION 0x0f
|
||||
#define ATT_ERROR_UNSUPPORTED_GROUP_TYPE 0x10
|
||||
#define ATT_ERROR_INSUFFICIENT_RESOURCES 0x11
|
||||
#define ATT_ERROR_VALUE_NOT_ALLOWED 0x13
|
||||
|
||||
// MARK: ATT Error Codes defined by BTstack
|
||||
#define ATT_ERROR_HCI_DISCONNECT_RECEIVED 0x1f
|
||||
#define ATT_ERROR_BONDING_INFORMATION_MISSING 0x70
|
||||
#define ATT_ERROR_DATA_MISMATCH 0x7e
|
||||
#define ATT_ERROR_TIMEOUT 0x7F
|
||||
#define ATT_ERROR_WRITE_RESPONSE_PENDING 0x100
|
||||
|
||||
// MARK: ATT Error Codes from Bluetooth Core Specification Supplement, Version 9 or later
|
||||
#define ATT_ERROR_WRITE_REQUEST_REJECTED 0xFC
|
||||
#define ATT_ERROR_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_IMPROPERLY_CONFIGURED 0xFD
|
||||
#define ATT_ERROR_PROCEDURE_ALREADY_IN_PROGRESS 0xFE
|
||||
#define ATT_ERROR_OUT_OF_RANGE 0xFF
|
||||
|
||||
// MARK: ATT Error Codes from Cycling Power Service spec
|
||||
#define CYCLING_POWER_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS 0x80
|
||||
#define CYCLING_POWER_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS 0xFE
|
||||
#define CYCLING_POWER_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED 0xFD
|
||||
|
||||
// MARK: ATT Error Codes from Cycling Speed and Cadence Service spec
|
||||
#define CYCLING_SPEED_AND_CADENCE_ERROR_CODE_PROCEDURE_ALREADY_IN_PROGRESS 0x80
|
||||
#define CYCLING_SPEED_AND_CADENCE_ERROR_CODE_CCC_DESCRIPTOR_IMPROPERLY_CONFIGURED 0x81
|
||||
|
||||
|
||||
// MARK: Attribute Property Flags
|
||||
#define ATT_PROPERTY_BROADCAST 0x01
|
||||
#define ATT_PROPERTY_READ 0x02
|
||||
#define ATT_PROPERTY_WRITE_WITHOUT_RESPONSE 0x04
|
||||
#define ATT_PROPERTY_WRITE 0x08
|
||||
#define ATT_PROPERTY_NOTIFY 0x10
|
||||
#define ATT_PROPERTY_INDICATE 0x20
|
||||
#define ATT_PROPERTY_AUTHENTICATED_SIGNED_WRITE 0x40
|
||||
#define ATT_PROPERTY_EXTENDED_PROPERTIES 0x80
|
||||
|
||||
// MARK: Attribute Property Flag, BTstack extension
|
||||
// value is asked from client
|
||||
#define ATT_PROPERTY_DYNAMIC 0x100
|
||||
|
||||
// Security levels
|
||||
#define ATT_SECURITY_NONE 0
|
||||
#define ATT_SECURITY_ENCRYPTED 1
|
||||
#define ATT_SECURITY_AUTHENTICATED 2
|
||||
#define ATT_SECURITY_AUTHORIZED 3
|
||||
#define ATT_SECURITY_AUTHENTICATED_SC 4
|
||||
|
||||
// ATT Transaction Timeout of 30 seconds for Command/Response or Indication/Confirmation
|
||||
#define ATT_TRANSACTION_TIMEOUT_MS 30000
|
||||
|
||||
#define ATT_TRANSACTION_MODE_NONE 0x0
|
||||
#define ATT_TRANSACTION_MODE_ACTIVE 0x1
|
||||
#define ATT_TRANSACTION_MODE_EXECUTE 0x2
|
||||
#define ATT_TRANSACTION_MODE_CANCEL 0x3
|
||||
#define ATT_TRANSACTION_MODE_VALIDATE 0x4
|
||||
|
||||
// MARK: GATT UUIDs
|
||||
#define GATT_PRIMARY_SERVICE_UUID 0x2800
|
||||
#define GATT_SECONDARY_SERVICE_UUID 0x2801
|
||||
#define GATT_INCLUDE_SERVICE_UUID 0x2802
|
||||
#define GATT_CHARACTERISTICS_UUID 0x2803
|
||||
#define GATT_CHARACTERISTIC_EXTENDED_PROPERTIES 0x2900
|
||||
#define GATT_CHARACTERISTIC_USER_DESCRIPTION 0x2901
|
||||
#define GATT_CLIENT_CHARACTERISTICS_CONFIGURATION 0x2902
|
||||
#define GATT_SERVER_CHARACTERISTICS_CONFIGURATION 0x2903
|
||||
#define GATT_CHARACTERISTIC_PRESENTATION_FORMAT 0x2904
|
||||
#define GATT_CHARACTERISTIC_AGGREGATE_FORMAT 0x2905
|
||||
#define GATT_CLIENT_SUPPORTED_FEATURES 0x2B29
|
||||
#define GATT_SERVER_SUPPORTED_FEATURES 0x2B3A
|
||||
|
||||
#define GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NONE 0
|
||||
#define GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION 1
|
||||
#define GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_INDICATION 2
|
||||
|
||||
#define GATT_CLIENT_ANY_CONNECTION 0xffff
|
||||
#define GATT_CLIENT_ANY_VALUE_HANDLE 0x0000
|
||||
|
||||
// GAP Service and Characteristics
|
||||
#define GAP_SERVICE_UUID 0x1800
|
||||
#define GAP_DEVICE_NAME_UUID 0x2a00
|
||||
#define GAP_APPEARANCE_UUID 0x2a01
|
||||
#define GAP_PERIPHERAL_PRIVACY_FLAG 0x2a02
|
||||
#define GAP_RECONNECTION_ADDRESS_UUID 0x2a03
|
||||
#define GAP_PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS_UUID 0x2a04
|
||||
#define GAP_SERVICE_CHANGED 0x2a05
|
||||
|
||||
// Bluetooth GATT types
|
||||
|
||||
typedef struct {
|
||||
uint16_t year; // 0 - year is not known; or [1582,9999]
|
||||
uint8_t month; // 0 - month is not known; or [1,12]
|
||||
uint8_t day; // 0 - day is not known; or [1,31]
|
||||
uint8_t hours; // [0,23]
|
||||
uint8_t minutes; // [0,59]
|
||||
uint8_t seconds; // [0,59]
|
||||
} gatt_date_time_t;
|
||||
|
||||
typedef enum {
|
||||
GATT_MICROPHONE_CONTROL_MUTE_OFF = 0x00,
|
||||
GATT_MICROPHONE_CONTROL_MUTE_ON,
|
||||
GATT_MICROPHONE_CONTROL_MUTE_DISABLED
|
||||
} gatt_microphone_control_mute_t;
|
||||
|
||||
/**
|
||||
SM - LE Security Manager
|
||||
*/
|
||||
// Bluetooth Spec definitions
|
||||
typedef enum {
|
||||
SM_CODE_PAIRING_REQUEST = 0X01,
|
||||
SM_CODE_PAIRING_RESPONSE,
|
||||
SM_CODE_PAIRING_CONFIRM,
|
||||
SM_CODE_PAIRING_RANDOM,
|
||||
SM_CODE_PAIRING_FAILED,
|
||||
SM_CODE_ENCRYPTION_INFORMATION,
|
||||
SM_CODE_MASTER_IDENTIFICATION,
|
||||
SM_CODE_IDENTITY_INFORMATION,
|
||||
SM_CODE_IDENTITY_ADDRESS_INFORMATION,
|
||||
SM_CODE_SIGNING_INFORMATION,
|
||||
SM_CODE_SECURITY_REQUEST,
|
||||
SM_CODE_PAIRING_PUBLIC_KEY,
|
||||
SM_CODE_PAIRING_DHKEY_CHECK,
|
||||
SM_CODE_KEYPRESS_NOTIFICATION,
|
||||
} SECURITY_MANAGER_COMMANDS;
|
||||
|
||||
// IO Capability Values
|
||||
typedef enum {
|
||||
IO_CAPABILITY_DISPLAY_ONLY = 0,
|
||||
IO_CAPABILITY_DISPLAY_YES_NO,
|
||||
IO_CAPABILITY_KEYBOARD_ONLY,
|
||||
IO_CAPABILITY_NO_INPUT_NO_OUTPUT,
|
||||
IO_CAPABILITY_KEYBOARD_DISPLAY, // not used by secure simple pairing
|
||||
} io_capability_t;
|
||||
|
||||
// Authentication requirement flags
|
||||
#define SM_AUTHREQ_NO_BONDING 0x00
|
||||
#define SM_AUTHREQ_BONDING 0x01
|
||||
#define SM_AUTHREQ_MITM_PROTECTION 0x04
|
||||
#define SM_AUTHREQ_SECURE_CONNECTION 0x08
|
||||
#define SM_AUTHREQ_KEYPRESS 0x10
|
||||
#define SM_AUTHREQ_CT2 0x20
|
||||
|
||||
// Key distribution flags used by spec
|
||||
#define SM_KEYDIST_ENC_KEY 0x01
|
||||
#define SM_KEYDIST_ID_KEY 0x02
|
||||
#define SM_KEYDIST_SIGN 0x04
|
||||
#define SM_KEYDIST_LINK_KEY 0x08
|
||||
|
||||
// Key distribution flags used internally
|
||||
#define SM_KEYDIST_FLAG_ENCRYPTION_INFORMATION 0x01
|
||||
#define SM_KEYDIST_FLAG_MASTER_IDENTIFICATION 0x02
|
||||
#define SM_KEYDIST_FLAG_IDENTITY_INFORMATION 0x04
|
||||
#define SM_KEYDIST_FLAG_IDENTITY_ADDRESS_INFORMATION 0x08
|
||||
#define SM_KEYDIST_FLAG_SIGNING_IDENTIFICATION 0x10
|
||||
|
||||
// STK Generation Methods
|
||||
#define SM_STK_GENERATION_METHOD_JUST_WORKS 0x01
|
||||
#define SM_STK_GENERATION_METHOD_OOB 0x02
|
||||
#define SM_STK_GENERATION_METHOD_PASSKEY 0x04
|
||||
#define SM_STK_GENERATION_METHOD_NUMERIC_COMPARISON 0x08
|
||||
|
||||
// Pairing Failed Reasons
|
||||
#define SM_REASON_RESERVED 0x00
|
||||
#define SM_REASON_PASSKEY_ENTRY_FAILED 0x01
|
||||
#define SM_REASON_OOB_NOT_AVAILABLE 0x02
|
||||
#define SM_REASON_AUTHENTHICATION_REQUIREMENTS 0x03
|
||||
#define SM_REASON_CONFIRM_VALUE_FAILED 0x04
|
||||
#define SM_REASON_PAIRING_NOT_SUPPORTED 0x05
|
||||
#define SM_REASON_ENCRYPTION_KEY_SIZE 0x06
|
||||
#define SM_REASON_COMMAND_NOT_SUPPORTED 0x07
|
||||
#define SM_REASON_UNSPECIFIED_REASON 0x08
|
||||
#define SM_REASON_REPEATED_ATTEMPTS 0x09
|
||||
#define SM_REASON_INVALID_PARAMETERS 0x0a
|
||||
#define SM_REASON_DHKEY_CHECK_FAILED 0x0b
|
||||
#define SM_REASON_NUMERIC_COMPARISON_FAILED 0x0c
|
||||
#define SM_REASON_BR_EDR_PAIRING_IN_PROGRESS 0x0d
|
||||
#define SM_REASON_CROSS_TRANSPORT_KEY_DERIVATION_NOT_ALLOWED 0x0e
|
||||
#define SM_REASON_KEY_REJECTED 0x0f
|
||||
|
||||
// also, invalid parameters
|
||||
// and reserved
|
||||
|
||||
// Keypress Notifications
|
||||
#define SM_KEYPRESS_PASSKEY_ENTRY_STARTED 0x00
|
||||
#define SM_KEYPRESS_PASSKEY_DIGIT_ENTERED 0x01
|
||||
#define SM_KEYPRESS_PASSKEY_DIGIT_ERASED 0x02
|
||||
#define SM_KEYPRESS_PASSKEY_CLEARED 0x03
|
||||
#define SM_KEYPRESS_PASSKEY_ENTRY_COMPLETED 0x04
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,524 @@
|
||||
/*
|
||||
Copyright (C) 2014 BlueKitchen GmbH
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
4. Any redistribution, use, or modification is done solely for
|
||||
personal benefit and not for any commercial purpose or for
|
||||
monetary gain.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
Please inquire about commercial licensing options at
|
||||
contact@bluekitchen-gmbh.com
|
||||
|
||||
*/
|
||||
|
||||
#if defined ENABLE_CLASSIC
|
||||
|
||||
#define BTSTACK_FILE__ "hids_device.c"
|
||||
|
||||
/**
|
||||
Implementation of the GATT HIDS Device
|
||||
To use with your application, add '#import <hids.gatt>' to your .gatt file
|
||||
*/
|
||||
|
||||
#include "hids_device.h"
|
||||
|
||||
#include "ble/att_db.h"
|
||||
#include "ble/att_server.h"
|
||||
#include "bluetooth_gatt.h"
|
||||
#include "btstack_util.h"
|
||||
#include "btstack_debug.h"
|
||||
|
||||
#define HIDS_DEVICE_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS 0x80
|
||||
|
||||
// storage for 'generic' HID Device with single Input, Output, and Feature Report
|
||||
static hids_device_report_t hid_reports_generic_storage[3];
|
||||
|
||||
typedef struct {
|
||||
uint16_t con_handle;
|
||||
|
||||
uint8_t hid_country_code;
|
||||
const uint8_t * hid_descriptor;
|
||||
uint16_t hid_descriptor_size;
|
||||
|
||||
uint16_t hid_report_map_handle;
|
||||
uint8_t hid_protocol_mode;
|
||||
uint16_t hid_protocol_mode_value_handle;
|
||||
|
||||
uint16_t hid_boot_mouse_input_value_handle;
|
||||
uint16_t hid_boot_mouse_input_client_configuration_handle;
|
||||
uint16_t hid_boot_mouse_input_client_configuration_value;
|
||||
|
||||
uint16_t hid_boot_keyboard_input_value_handle;
|
||||
uint16_t hid_boot_keyboard_input_client_configuration_handle;
|
||||
uint16_t hid_boot_keyboard_input_client_configuration_value;
|
||||
|
||||
hids_device_report_t * hid_reports;
|
||||
|
||||
uint8_t hid_input_reports_num;
|
||||
uint8_t hid_output_reports_num;
|
||||
uint8_t hid_feature_reports_num;
|
||||
|
||||
uint16_t hid_control_point_value_handle;
|
||||
uint8_t hid_control_point_suspend;
|
||||
|
||||
btstack_context_callback_registration_t battery_callback;
|
||||
} hids_device_t;
|
||||
|
||||
static hids_device_t hids_device;
|
||||
|
||||
static btstack_packet_handler_t packet_handler;
|
||||
static att_service_handler_t hid_service;
|
||||
|
||||
// TODO: store hids device connection into list
|
||||
static hids_device_t * hids_device_get_instance_for_con_handle(uint16_t con_handle) {
|
||||
UNUSED(con_handle);
|
||||
return &hids_device;
|
||||
}
|
||||
|
||||
static hids_device_t * hids_device_create_instance(void) {
|
||||
memset(&hids_device, 0, sizeof(hids_device_t));
|
||||
return &hids_device;
|
||||
}
|
||||
|
||||
static hids_device_report_t * hids_device_get_report_for_client_configuration_handle(hids_device_t * device, uint16_t client_configuration_handle) {
|
||||
uint8_t pos;
|
||||
uint8_t total_reports = device->hid_input_reports_num + device->hid_output_reports_num + device->hid_feature_reports_num;
|
||||
for (pos = 0 ; pos < total_reports ; pos++) {
|
||||
if (device->hid_reports[pos].client_configuration_handle == client_configuration_handle) {
|
||||
return &device->hid_reports[pos];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static hids_device_report_t *
|
||||
hids_device_get_report_for_id_and_type(hids_device_t *device, uint16_t report_id, hid_report_type_t type) {
|
||||
uint8_t pos;
|
||||
uint8_t total_reports = device->hid_input_reports_num + device->hid_output_reports_num + device->hid_feature_reports_num;
|
||||
for (pos = 0 ; pos < total_reports ; pos++) {
|
||||
if ((device->hid_reports[pos].type == type) && (device->hid_reports[pos].id == report_id)) {
|
||||
return &device->hid_reports[pos];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void hids_device_emit_event_with_uint8(uint8_t event, hci_con_handle_t con_handle, uint8_t value) {
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packet_handler) {
|
||||
return;
|
||||
}
|
||||
uint8_t buffer[6];
|
||||
buffer[0] = HCI_EVENT_HIDS_META;
|
||||
buffer[1] = 4;
|
||||
buffer[2] = event;
|
||||
little_endian_store_16(buffer, 3, (uint16_t) con_handle);
|
||||
buffer[5] = value;
|
||||
(*packet_handler)(HCI_EVENT_PACKET, 0, buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
static void hids_device_emit_event_with_uint8_uint8_t(uint8_t event, hci_con_handle_t con_handle, uint8_t value_1, uint8_t value_2) {
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packet_handler) {
|
||||
return;
|
||||
}
|
||||
uint8_t buffer[7];
|
||||
buffer[0] = HCI_EVENT_HIDS_META;
|
||||
buffer[1] = 4;
|
||||
buffer[2] = event;
|
||||
little_endian_store_16(buffer, 3, (uint16_t) con_handle);
|
||||
buffer[5] = value_1;
|
||||
buffer[6] = value_2;
|
||||
(*packet_handler)(HCI_EVENT_PACKET, 0, buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
static void hids_device_emit_event(uint8_t event, hci_con_handle_t con_handle) {
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packet_handler) {
|
||||
return;
|
||||
}
|
||||
uint8_t buffer[5];
|
||||
buffer[0] = HCI_EVENT_HIDS_META;
|
||||
buffer[1] = 4;
|
||||
buffer[2] = event;
|
||||
little_endian_store_16(buffer, 3, (uint16_t) con_handle);
|
||||
(*packet_handler)(HCI_EVENT_PACKET, 0, buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
static void hids_device_can_send_now(void * context) {
|
||||
hci_con_handle_t con_handle = (hci_con_handle_t)(uintptr_t) context;
|
||||
// notify client
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!packet_handler) {
|
||||
return;
|
||||
}
|
||||
uint8_t buffer[5];
|
||||
buffer[0] = HCI_EVENT_HIDS_META;
|
||||
buffer[1] = 3;
|
||||
buffer[2] = HIDS_SUBEVENT_CAN_SEND_NOW;
|
||||
little_endian_store_16(buffer, 3, (uint16_t) con_handle);
|
||||
(*packet_handler)(HCI_EVENT_PACKET, 0, buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
// ATT Client Read Callback for Dynamic Data
|
||||
// - if buffer == NULL, don't copy data, just return size of value
|
||||
// - if buffer != NULL, copy data and return number bytes copied
|
||||
static uint16_t att_read_callback(hci_con_handle_t con_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) {
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return HIDS_DEVICE_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS;
|
||||
}
|
||||
|
||||
if (att_handle == instance->hid_protocol_mode_value_handle) {
|
||||
log_info("Read protocol mode");
|
||||
return att_read_callback_handle_byte(instance->hid_protocol_mode, offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
if (att_handle == instance->hid_report_map_handle) {
|
||||
log_info("Read report map");
|
||||
return att_read_callback_handle_blob(instance->hid_descriptor, instance->hid_descriptor_size, offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
if (att_handle == instance->hid_boot_mouse_input_client_configuration_handle) {
|
||||
return att_read_callback_handle_little_endian_16(instance->hid_boot_mouse_input_client_configuration_value, offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
if (att_handle == instance->hid_boot_keyboard_input_client_configuration_handle) {
|
||||
return att_read_callback_handle_little_endian_16(instance->hid_boot_keyboard_input_client_configuration_value, offset, buffer, buffer_size);
|
||||
}
|
||||
|
||||
if (att_handle == instance->hid_control_point_value_handle) {
|
||||
if (buffer && (buffer_size >= 1u)) {
|
||||
buffer[0] = instance->hid_control_point_suspend;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
hids_device_report_t * report = hids_device_get_report_for_client_configuration_handle(instance, att_handle);
|
||||
if (report != NULL) {
|
||||
return att_read_callback_handle_little_endian_16(report->client_configuration_value, offset, buffer, buffer_size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int att_write_callback(hci_con_handle_t con_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) {
|
||||
UNUSED(transaction_mode);
|
||||
UNUSED(buffer_size);
|
||||
UNUSED(offset);
|
||||
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return HIDS_DEVICE_ERROR_CODE_INAPPROPRIATE_CONNECTION_PARAMETERS;
|
||||
}
|
||||
|
||||
if (att_handle == instance->hid_boot_mouse_input_client_configuration_handle) {
|
||||
uint16_t new_value = little_endian_read_16(buffer, 0);
|
||||
instance->hid_boot_mouse_input_client_configuration_value = new_value;
|
||||
hids_device_emit_event_with_uint8(HIDS_SUBEVENT_BOOT_MOUSE_INPUT_REPORT_ENABLE, con_handle, (uint8_t) new_value);
|
||||
}
|
||||
if (att_handle == instance->hid_boot_keyboard_input_client_configuration_handle) {
|
||||
uint16_t new_value = little_endian_read_16(buffer, 0);
|
||||
instance->hid_boot_keyboard_input_client_configuration_value = new_value;
|
||||
hids_device_emit_event_with_uint8(HIDS_SUBEVENT_BOOT_KEYBOARD_INPUT_REPORT_ENABLE, con_handle, (uint8_t) new_value);
|
||||
}
|
||||
|
||||
if (att_handle == instance->hid_protocol_mode_value_handle) {
|
||||
instance->hid_protocol_mode = buffer[0];
|
||||
log_info("Set protocol mode: %u", instance->hid_protocol_mode);
|
||||
hids_device_emit_event_with_uint8(HIDS_SUBEVENT_PROTOCOL_MODE, con_handle, instance->hid_protocol_mode);
|
||||
}
|
||||
|
||||
if (att_handle == instance->hid_control_point_value_handle) {
|
||||
if (buffer_size < 1u) {
|
||||
return ATT_ERROR_INVALID_OFFSET;
|
||||
}
|
||||
instance->hid_control_point_suspend = buffer[0];
|
||||
instance->con_handle = con_handle;
|
||||
log_info("Set suspend tp: %u", instance->hid_control_point_suspend);
|
||||
if (instance->hid_control_point_suspend == 0u) {
|
||||
hids_device_emit_event(HIDS_SUBEVENT_SUSPEND, con_handle);
|
||||
} else if (instance->hid_control_point_suspend == 1u) {
|
||||
hids_device_emit_event(HIDS_SUBEVENT_EXIT_SUSPEND, con_handle);
|
||||
}
|
||||
}
|
||||
|
||||
hids_device_report_t * report = hids_device_get_report_for_client_configuration_handle(instance, att_handle);
|
||||
if (report != NULL) {
|
||||
uint16_t new_value = little_endian_read_16(buffer, 0);
|
||||
report->client_configuration_value = new_value;
|
||||
log_info("Enable Report (type %u) notifications: %x", (uint8_t) report->type, new_value);
|
||||
|
||||
switch (report->type) {
|
||||
case HID_REPORT_TYPE_INPUT:
|
||||
hids_device_emit_event_with_uint8_uint8_t(HIDS_SUBEVENT_INPUT_REPORT_ENABLE, con_handle, report->id, (uint8_t) new_value);
|
||||
break;
|
||||
case HID_REPORT_TYPE_OUTPUT:
|
||||
hids_device_emit_event_with_uint8_uint8_t(HIDS_SUBEVENT_OUTPUT_REPORT_ENABLE, con_handle, report->id, (uint8_t) new_value);
|
||||
break;
|
||||
case HID_REPORT_TYPE_FEATURE:
|
||||
hids_device_emit_event_with_uint8_uint8_t(HIDS_SUBEVENT_FEATURE_REPORT_ENABLE, con_handle, report->id, (uint8_t) new_value);
|
||||
break;
|
||||
default:
|
||||
btstack_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hids_device_init_with_storage(uint8_t hid_country_code, const uint8_t * hid_descriptor, uint16_t hid_descriptor_size,
|
||||
uint16_t num_reports, hids_device_report_t * report_storage) {
|
||||
|
||||
hids_device_t * instance = hids_device_create_instance();
|
||||
|
||||
btstack_assert(num_reports > 0);
|
||||
btstack_assert(report_storage != NULL);
|
||||
|
||||
instance->hid_country_code = hid_country_code;
|
||||
instance->hid_descriptor = hid_descriptor;
|
||||
instance->hid_descriptor_size = hid_descriptor_size;
|
||||
|
||||
// default
|
||||
instance->hid_protocol_mode = 1;
|
||||
|
||||
// get service handle range
|
||||
uint16_t start_handle = 0;
|
||||
uint16_t end_handle = 0xffff;
|
||||
int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE, &start_handle, &end_handle);
|
||||
btstack_assert(service_found != 0);
|
||||
UNUSED(service_found);
|
||||
|
||||
// get report map handle
|
||||
instance->hid_report_map_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP);
|
||||
|
||||
// get report map handle
|
||||
instance->hid_protocol_mode_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE);
|
||||
|
||||
// get value and client configuration handles for boot mouse input, boot keyboard input and report input
|
||||
instance->hid_boot_mouse_input_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT);
|
||||
instance->hid_boot_mouse_input_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT);
|
||||
|
||||
instance->hid_boot_keyboard_input_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT);
|
||||
instance->hid_boot_keyboard_input_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT);
|
||||
|
||||
instance->hid_control_point_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT);
|
||||
|
||||
log_info("hid_report_map_handle 0x%02x", instance->hid_report_map_handle);
|
||||
log_info("hid_protocol_mode_value_handle 0x%02x", instance->hid_protocol_mode_value_handle);
|
||||
log_info("hid_boot_mouse_input_value_handle 0x%02x", instance->hid_boot_mouse_input_value_handle);
|
||||
log_info("hid_boot_mouse_input_client_configuration_handle 0x%02x", instance->hid_boot_mouse_input_client_configuration_handle);
|
||||
log_info("hid_boot_keyboard_input_value_handle 0x%02x", instance->hid_boot_keyboard_input_value_handle);
|
||||
log_info("hid_boot_keyboard_input_client_configuration_handle 0x%02x", instance->hid_boot_keyboard_input_client_configuration_handle);
|
||||
log_info("hid_control_point_value_handle 0x%02x", instance->hid_control_point_value_handle);
|
||||
|
||||
instance->hid_reports = report_storage;
|
||||
|
||||
uint16_t hid_reports_num = num_reports;
|
||||
uint16_t assigned_reports_num = 0;
|
||||
uint16_t start_chr_handle = start_handle;
|
||||
|
||||
while ((start_chr_handle < end_handle) && (assigned_reports_num < hid_reports_num)) {
|
||||
// mandatory
|
||||
uint16_t chr_value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT);
|
||||
if (chr_value_handle == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// optional
|
||||
uint16_t chr_client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT);
|
||||
|
||||
// mandatory
|
||||
uint16_t report_reference_handle = gatt_server_get_descriptor_handle_for_characteristic_with_uuid16(start_chr_handle, end_handle, ORG_BLUETOOTH_CHARACTERISTIC_REPORT, ORG_BLUETOOTH_DESCRIPTOR_REPORT_REFERENCE);
|
||||
if (report_reference_handle == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// get report id and type from report reference
|
||||
uint16_t report_reference_value_len;
|
||||
const uint8_t * report_reference_value = gatt_server_get_const_value_for_handle(report_reference_handle, &report_reference_value_len);
|
||||
if (report_reference_value == NULL) {
|
||||
break;
|
||||
}
|
||||
if (report_reference_value_len != 2) {
|
||||
break;
|
||||
}
|
||||
uint8_t report_id = report_reference_value[0];
|
||||
hid_report_type_t report_type = (hid_report_type_t) report_reference_value[1];
|
||||
|
||||
// store report info
|
||||
hids_device_report_t * report = &report_storage[assigned_reports_num];
|
||||
report->value_handle = chr_value_handle;
|
||||
report->client_configuration_handle = chr_client_configuration_handle;
|
||||
report->client_configuration_value = 0;
|
||||
report->id = report_id;
|
||||
report->type = report_type;
|
||||
|
||||
switch (report->type) {
|
||||
case HID_REPORT_TYPE_INPUT:
|
||||
instance->hid_input_reports_num++;
|
||||
break;
|
||||
case HID_REPORT_TYPE_OUTPUT:
|
||||
instance->hid_output_reports_num++;
|
||||
break;
|
||||
case HID_REPORT_TYPE_FEATURE:
|
||||
instance->hid_feature_reports_num++;
|
||||
break;
|
||||
default:
|
||||
btstack_unreachable();
|
||||
return;
|
||||
}
|
||||
log_info("hid_report_value_handle 0x%02x, id %u, type %u", report->value_handle, report->id, (uint8_t)report->type);
|
||||
if (report->client_configuration_handle != 0) {
|
||||
log_info("hid_report_client_configuration_handle 0x%02x", report->client_configuration_handle);
|
||||
}
|
||||
|
||||
assigned_reports_num++;
|
||||
start_chr_handle = report_reference_handle + 1;
|
||||
}
|
||||
|
||||
// register service with ATT Server
|
||||
hid_service.start_handle = start_handle;
|
||||
hid_service.end_handle = end_handle;
|
||||
hid_service.read_callback = &att_read_callback;
|
||||
hid_service.write_callback = &att_write_callback;
|
||||
att_server_register_service_handler(&hid_service);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Set up HIDS Device
|
||||
*/
|
||||
void hids_device_init(uint8_t country_code, const uint8_t * descriptor, uint16_t descriptor_size) {
|
||||
uint16_t hid_reports_num = sizeof(hid_reports_generic_storage) / sizeof(hids_device_report_t);
|
||||
hids_device_init_with_storage(country_code, descriptor, descriptor_size, hid_reports_num, hid_reports_generic_storage);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Register callback for the HIDS Device client.
|
||||
@param callback
|
||||
*/
|
||||
void hids_device_register_packet_handler(btstack_packet_handler_t callback) {
|
||||
packet_handler = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Request can send now event to send HID Report
|
||||
Generates an HIDS_SUBEVENT_CAN_SEND_NOW subevent
|
||||
@param hid_cid
|
||||
*/
|
||||
void hids_device_request_can_send_now_event(hci_con_handle_t con_handle) {
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
instance->battery_callback.callback = &hids_device_can_send_now;
|
||||
instance->battery_callback.context = (void*)(uintptr_t) con_handle;
|
||||
att_server_register_can_send_now_callback(&instance->battery_callback, con_handle);
|
||||
}
|
||||
|
||||
uint8_t hids_device_send_input_report_for_id(hci_con_handle_t con_handle, uint16_t report_id, const uint8_t * report, uint16_t report_len) {
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
|
||||
hids_device_report_t * report_storage = hids_device_get_report_for_id_and_type(instance, report_id,
|
||||
HID_REPORT_TYPE_INPUT);
|
||||
if (report_storage == NULL) {
|
||||
return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
|
||||
}
|
||||
|
||||
return att_server_notify(con_handle, report_storage->value_handle, report, report_len);
|
||||
}
|
||||
|
||||
uint8_t hids_device_send_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len) {
|
||||
hids_device_t * device = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!device) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
|
||||
uint8_t pos;
|
||||
uint8_t total_reports = device->hid_input_reports_num + device->hid_output_reports_num + device->hid_feature_reports_num;
|
||||
for (pos = 0 ; pos < total_reports ; pos++) {
|
||||
hids_device_report_t * report_storage = &device->hid_reports[pos];
|
||||
if (report_storage->type == HID_REPORT_TYPE_INPUT) {
|
||||
return att_server_notify(con_handle, report_storage->value_handle, report, report_len);
|
||||
}
|
||||
}
|
||||
return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Send HID Boot Mouse Input Report
|
||||
*/
|
||||
uint8_t hids_device_send_boot_mouse_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len) {
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
return att_server_notify(con_handle, instance->hid_boot_mouse_input_value_handle, report, report_len);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief Send HID Boot Mouse Input Report
|
||||
*/
|
||||
uint8_t hids_device_send_boot_keyboard_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len) {
|
||||
hids_device_t * instance = hids_device_get_instance_for_con_handle(con_handle);
|
||||
if (!instance) {
|
||||
log_error("no instance for handle 0x%02x", con_handle);
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
return att_server_notify(con_handle, instance->hid_boot_keyboard_input_value_handle, report, report_len);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
Copyright (C) 2014 BlueKitchen GmbH
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
4. Any redistribution, use, or modification is done solely for
|
||||
personal benefit and not for any commercial purpose or for
|
||||
monetary gain.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
Please inquire about commercial licensing options at
|
||||
contact@bluekitchen-gmbh.com
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
@title HID Service Server
|
||||
|
||||
*/
|
||||
|
||||
#ifndef HIDS_DEVICE_H
|
||||
#define HIDS_DEVICE_H
|
||||
|
||||
#if defined ENABLE_CLASSIC
|
||||
|
||||
#include <sdkoverride/att_db.h>
|
||||
#include <stdint.h>
|
||||
#include "btstack_defines.h"
|
||||
#include "btstack_hid.h"
|
||||
#include "bluetooth.h"
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* API_START */
|
||||
|
||||
typedef struct {
|
||||
uint16_t value_handle;
|
||||
uint16_t client_configuration_handle;
|
||||
uint16_t client_configuration_value;
|
||||
|
||||
hid_report_type_t type;
|
||||
uint16_t id;
|
||||
} hids_device_report_t;
|
||||
|
||||
/**
|
||||
@text Implementation of the GATT HIDS Device
|
||||
To use with your application, add '#import <hids.gatt>' to your .gatt file
|
||||
*/
|
||||
|
||||
/**
|
||||
@brief Set up HIDS Device with single INPUT, OUTPUT and FEATURE report
|
||||
*/
|
||||
void hids_device_init(uint8_t hid_country_code, const uint8_t * hid_descriptor, uint16_t hid_descriptor_size);
|
||||
|
||||
/**
|
||||
@brief Set up HIDS Device for multiple instances of INPUT, OUTPUT and FEATURE reports
|
||||
*/
|
||||
void hids_device_init_with_storage(uint8_t hid_country_code, const uint8_t * hid_descriptor, uint16_t hid_descriptor_size,
|
||||
uint16_t num_reports, hids_device_report_t * report_storage);
|
||||
|
||||
/**
|
||||
@brief Register callback for the HIDS Device client.
|
||||
@param callback
|
||||
*/
|
||||
void hids_device_register_packet_handler(btstack_packet_handler_t callback);
|
||||
|
||||
/**
|
||||
@brief Request can send now event to send HID Report
|
||||
Generates an HIDS_SUBEVENT_CAN_SEND_NOW subevent
|
||||
@param hid_cid
|
||||
*/
|
||||
void hids_device_request_can_send_now_event(hci_con_handle_t con_handle);
|
||||
|
||||
/**
|
||||
@brief Send HID Input Report for Report ID
|
||||
@param con_handle
|
||||
@param report_id
|
||||
@param report
|
||||
@param report_len
|
||||
@returns status
|
||||
*/
|
||||
uint8_t hids_device_send_input_report_for_id(hci_con_handle_t con_handle, uint16_t report_id, const uint8_t * report, uint16_t report_len);
|
||||
|
||||
/**
|
||||
@brief Send HID Input Report for first Input Report
|
||||
@param con_handle
|
||||
@param report
|
||||
@param report_len
|
||||
@returns status
|
||||
*/
|
||||
uint8_t hids_device_send_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len);
|
||||
|
||||
/**
|
||||
@brief Send HID Boot Mouse Input Report
|
||||
@param con_handle
|
||||
@param report
|
||||
@param report_len
|
||||
@returns status
|
||||
*/
|
||||
uint8_t hids_device_send_boot_mouse_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len);
|
||||
|
||||
/**
|
||||
@brief Send HID Boot Mouse Input Report
|
||||
@param con_handle
|
||||
@param report
|
||||
@param report_len
|
||||
@returns status
|
||||
*/
|
||||
uint8_t hids_device_send_boot_keyboard_input_report(hci_con_handle_t con_handle, const uint8_t * report, uint16_t report_len);
|
||||
|
||||
/* API_END */
|
||||
|
||||
#if defined __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
BOOTSEL Button
|
||||
==============
|
||||
|
||||
The BOOTSEL button on the Pico is not connected to a standard GPIO, so
|
||||
it cannot be read using the usual ``digitalRead`` function. It **can**,
|
||||
however, be read using a special (relatively slow) method.
|
||||
|
||||
The ``BOOTSEL`` object implements a simple way of reading the BOOTSEL
|
||||
button. Simply use the object ``BOOTSEL`` as a boolean (as a conditional
|
||||
in an ``if`` or ``while``, or assigning to a ``bool``):
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
// Print "BEEP" if the BOOTSEL button is pressed
|
||||
if (BOOTSEL) {
|
||||
Serial.println("BEEP!");
|
||||
// Wait until BOOTSEL is released
|
||||
while (BOOTSEL) {
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -54,9 +54,9 @@ author = u'Earle F. Philhower, III'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'3.3.0'
|
||||
version = u'3.6.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'3.3.0'
|
||||
release = u'3.6.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
EthernetLWIP (Wired Ethernet) Support
|
||||
=====================================
|
||||
|
||||
Wired Ethernet interfaces are supported for all the internal networking
|
||||
libraries (``WiFiClient``, ``WiFiClientSecure``, ``WiFiServer``,
|
||||
``WiFiServerSecure``, ``WiFiUDP``, ``WebServer``, ``Updater``,
|
||||
``HTTPClient``, etc.).
|
||||
|
||||
Using these wired interfaces is very similar to using the Pico-W WiFi
|
||||
so most examples in the core only require minor modifications to use
|
||||
a wired interface.
|
||||
|
||||
Supported Wired Ethernet Modules
|
||||
--------------------------------
|
||||
|
||||
* Wiznet W5100(s)
|
||||
|
||||
* Wiznet W5500
|
||||
|
||||
* ENC28J60
|
||||
|
||||
|
||||
Enabling Wired Ethernet
|
||||
-----------------------
|
||||
|
||||
Simply replace the WiFi include at the top with:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
#include <W5500lwIP.h> // Or W5100lwIP.h or ENC28J60.h
|
||||
|
||||
|
||||
And add a global Ethernet object of the same type:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
Wiznet5500lwIP eth(1); // Parameter is the Chip Select pin
|
||||
|
||||
In your ``setup()`` you may adjust the SPI pins you're using to
|
||||
match your hardware (be sure they are legal for the RP2040!), or
|
||||
skip this if you're using the default ones:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
void setup() {
|
||||
SPI.setRX(0);
|
||||
SPI.setCS(1);
|
||||
SPI.setSCK(2);
|
||||
SPI.setTX(3);
|
||||
....
|
||||
}
|
||||
|
||||
And finally replace the ``WiFi.begin()`` and ``WiFi.connected()``
|
||||
calls with ``eth.begin()`` and ``eth.connected()``:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
void setup() {
|
||||
....
|
||||
// WiFi.begin(SSID, PASS)
|
||||
eth.begin();
|
||||
|
||||
//while (!WiFi.connected()) {
|
||||
while (!eth.connected()) {
|
||||
Serial.print(".");
|
||||
}
|
||||
|
||||
Serial.print("IP address: ");
|
||||
//Serial.println(WiFi.localIP());
|
||||
Serial.println(eth.localIP());
|
||||
|
||||
....
|
||||
}
|
||||
|
||||
Adjusting LWIP Polling
|
||||
----------------------
|
||||
|
||||
LWIP operates in a polling mode for the wired Ethernet devices. By default it will run
|
||||
every 20ms, meaning that on average it will take half that time (10ms) before a packet
|
||||
received in the Ethernet module is received and operated upon by the Pico. This gives
|
||||
very low CPU utilization but in some cases this latency can affect performance.
|
||||
|
||||
Adding a call to ``lwipPollingPeriod(XXX)`` (where ``XXXX`` is the polling period in
|
||||
milliseconds) can adjust this setting on the fly. Note that if you set it too low, the
|
||||
Pico may not have enough time to service the Ethernet port before the timer fires again,
|
||||
leading to a lock up and hang.
|
||||
|
||||
|
||||
Adjusting SPI Speed
|
||||
-------------------
|
||||
|
||||
By default a 4MHz clock will be used to clock data into and out of the Ethernet module.
|
||||
Depending on the module and your wiring, a higher SPI clock may increase performance (but
|
||||
too high of a clock will cause communications problems or hangs).
|
||||
|
||||
This value may be adjusted using the ``eth.setSPISpeed(hz)`` call **before** starting the
|
||||
device. (You may also use custom ``SPISettings`` instead via ``eth.setSPISettings(spis)```)
|
||||
|
||||
For example, to set the W5500 to use a 30MHZ clock:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
#include <W5500lwIP.h>
|
||||
Wiznet5500lwIP eth(1);
|
||||
|
||||
void setup() {
|
||||
eth.setSPISpeed(30000000);
|
||||
lwipPollingPeriod(3);
|
||||
...
|
||||
eth.begin();
|
||||
...
|
||||
}
|
||||
|
||||
Using the WIZnet W5100S-EVB-Pico
|
||||
--------------------------------
|
||||
|
||||
You can use the onboard Ethernet chip with these drivers by utilizing the following options:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
#include <W5100lwIP.h>
|
||||
Wiznet5100lwIP eth(17); // Note chip select is **17**
|
||||
|
||||
void setup() {
|
||||
// Set SPI to the onboard Wiznet chip
|
||||
SPI.setRX(16);
|
||||
SPI.setCS(17);
|
||||
SPI.setSCK(18);
|
||||
SPI.setTX(19);
|
||||
...
|
||||
eth.begin();
|
||||
...
|
||||
}
|
||||
|
||||
Example Code
|
||||
------------
|
||||
|
||||
The following examples allow switching between WiFi and Ethernet:
|
||||
|
||||
* ``WebServer/AdvancedWebServer``
|
||||
|
||||
* ``HTTPClient/BasicHTTPSClient``
|
||||
|
||||
Caveats
|
||||
-------
|
||||
|
||||
The same restrictions for ``WiFi`` apply to these Ethernet classes, namely:
|
||||
|
||||
* Only core 0 may run any networking related code.
|
||||
|
||||
* In FreeRTOS, only the ``setup`` and ``loop`` task can call networking libraries, not any tasks.
|
||||
|
||||
Special Thanks
|
||||
--------------
|
||||
|
||||
* LWIPEthernet classes come from the ESP8266 Arduino team
|
||||
|
||||
* Individual Ethernet drivers were written by Nicholas Humfrey
|
||||
|
||||
+2
-2
@@ -325,8 +325,8 @@ info64
|
||||
.. code:: cpp
|
||||
|
||||
FSInfo64 fsinfo;
|
||||
SDFS.info(fsinfo);
|
||||
or LittleFS(fsinfo);
|
||||
SDFS.info64(fsinfo);
|
||||
or LittleFS.info64(fsinfo);
|
||||
|
||||
Performs the same operation as ``info`` but allows for reporting greater than
|
||||
4GB for filesystem size/used/etc. Should be used with the SD and SDFS
|
||||
|
||||
@@ -40,6 +40,16 @@ bool setDATA(pin_size_t pin)
|
||||
Sets the DOUT or DIN pin of the I2S device. Any pin may be used.
|
||||
Call before ``I2S::begin()``
|
||||
|
||||
bool setMCLK(pin_size_t pin)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the MCLK pin of the I2S device and enables MCLK output. Any pin may be used.
|
||||
Call before ``I2S::begin()``
|
||||
|
||||
bool setMCLKmult(int mult)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the sample rate to MCLK multiplier value. Only multiples of 64 are valid.
|
||||
Call before ``I2S::begin()``
|
||||
|
||||
bool setBitsPerSample(int bits)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Specify how many bits per audio sample to read or write. Note that
|
||||
@@ -58,12 +68,28 @@ Sets the word clock frequency, but does not start the I2S device if not
|
||||
already running. May be called after ``I2S::begin()`` to change the
|
||||
sample rate on-the-fly.
|
||||
|
||||
bool setSysClk(int samplerate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Changes the PICO system clock to optimise for the desired samplerate.
|
||||
The clock changes to 147.6 MHz for samplerates that are a multiple of 8 kHz, and 135.6 MHz for multiples of 11.025 kHz.
|
||||
Note that using ``setSysClk()`` may affect the timing of other sysclk-dependent functions.
|
||||
Should be called before any I2S functions and any other sysclk dependent initialisations.
|
||||
|
||||
bool setLSBJFormat()
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Enables LSB-J format for I2S output. In this mode the MSB comes out at the
|
||||
same time as the LRCLK changes, and not the normal 1-cycle delay. Useful for
|
||||
DAC chips like the PT8211.
|
||||
|
||||
bool setTDMFormat()
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
Enabled TDM formatted multi-channel output. Be sure to set the number of channels to
|
||||
the expected value (8 normally) and the bits per sample to 32.
|
||||
|
||||
bool setTDMChannels(int channels)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the number of TDM channels between frame syncs. Generally should be set to 8.
|
||||
|
||||
bool swapClocks()
|
||||
~~~~~~~~~~~~~~~~~
|
||||
Certain boards are hardwired with the WCLK before the BCLK, instead of the normal
|
||||
|
||||
@@ -30,6 +30,7 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
|
||||
|
||||
Analog I/O <analog>
|
||||
Digital I/O <digital>
|
||||
BOOTSEL Button <bootsel>
|
||||
EEPROM <eeprom>
|
||||
I2S Audio <i2s>
|
||||
PWM Audio <pwm>
|
||||
@@ -50,6 +51,7 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
|
||||
FreeRTOS SMP (multicore) <freertos>
|
||||
|
||||
WiFi (Pico-W Support) <wifi>
|
||||
Ethernet (Wired) <ethernet>
|
||||
WiFiClient <wificlient>
|
||||
WiFiServer <wifiserver>
|
||||
WiFiUDP <wifiudp>
|
||||
|
||||
+1
-1
@@ -10,5 +10,5 @@ In addition, it contains code from additional open source projects:
|
||||
* The `Pico-SDK <https://github.com/raspberrypi/pico-sdk>`_ and `Pico-Extras <https://github.com/raspberrypi/pico-extras>`_ are by Raspberry Pi (Trading) Ltd. and licensed under the BSD 3-Clause license.
|
||||
* `Arduino-Pico <https://github.com/earlephilhower/arduino-pico>`_ core files are licenses under the LGPL.
|
||||
* `LittleFS <https://github.com/ARMmbed/littlefs>`_ library written by ARM Limited and released under the `BSD 3-clause license <https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md>`_ .
|
||||
* `UF2CONV.PY <https://github.com/microsoft/uf2>`_ is by Microsoft Corporatio and licensed under the MIT license.
|
||||
* `UF2CONV.PY <https://github.com/microsoft/uf2>`_ is by Microsoft Corporation and licensed under the MIT license.
|
||||
* Some filesystem code taken from the `ESP8266 Arduino Core <https://github.com/esp8266/Arduino>`_ and licensed under the LGPL.
|
||||
|
||||
+12
-12
@@ -90,7 +90,7 @@ Signing requires the generation of an RSA-2048 key (other bit lengths are suppor
|
||||
Automatic Signing
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The simplest way of implementing signing is to use the automatic mode, which presently is only possible on Linux and Mac due to some of the tools not being available for Windows. This mode uses the IDE to configure the source code to enable sigining verification with a given public key, and signs binaries as part of the standard build process using a given public key.
|
||||
The simplest way of implementing signing is to use the automatic mode, which presently is only possible on Linux and Mac due to some of the tools not being available for Windows. This mode uses the IDE to configure the source code to enable signing verification with a given public key, and signs binaries as part of the standard build process using a given public key.
|
||||
|
||||
To enable this mode, just include `private.key` and `public.key` in the sketch `.ino` directory. The IDE will call a helper script (`tools/signing.py`) before the build begins to create a header to enable key validation using the given public key, and to actually do the signing after the build process, generating a `sketch.bin.signed` file. When OTA is enabled (ArduinoOTA, Web, or HTTP), the binary will automatically only accept signed updates.
|
||||
|
||||
@@ -140,11 +140,11 @@ Compile the sketch normally and, once a `.bin` file is available, sign it using
|
||||
Compression
|
||||
-----------
|
||||
|
||||
The eboot bootloader incorporates a GZIP decompressor, built for very low code requirements. For applications, this optional decompression is completely transparent. For uploading compressed filesystems, the application must be built with `ATOMIC_FS_UPDATE` defined because, otherwise, eboot will not be involved in writing the filesystem.
|
||||
The bootloader incorporates a GZIP decompressor, built for very low code requirements. For applications, this optional decompression is completely transparent.
|
||||
|
||||
No changes to the application are required. The `Updater` class and `eboot` bootloader (which performs actual application overwriting on update) automatically search for the `gzip` header in the uploaded binary, and if found, handle it.
|
||||
No changes to the application are required. The `Updater` class and bootloader (which performs actual application overwriting on update) automatically search for the `gzip` header in the uploaded binary, and if found, handle it.
|
||||
|
||||
Compress an application `.bin` file or filesystem package using any `gzip` available, at any desired compression level (`gzip -9` is recommended because it provides the maximum compression and uncompresses as fast as any other compressino level). For example:
|
||||
Compress an application `.bin` file or filesystem package using any `gzip` available, at any desired compression level (`gzip -9` is recommended because it provides the maximum compression and uncompresses as fast as any other compression level). For example:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
@@ -185,11 +185,11 @@ Uploading modules wirelessly from Arduino IDE is intended for the following typi
|
||||
|
||||
- Only if modules are accessible on the same network as the computer with the Arduino IDE.
|
||||
|
||||
- For all IDE uploads,m the Pico W and the computer must be connected to the same network.
|
||||
- For all IDE uploads, the Pico W and the computer must be connected to the same network.
|
||||
|
||||
To upload wirelessly from the IDE:
|
||||
|
||||
1. Build a sketch starts ``WiFi`` and includes the appropriare calls to ``ArduinoOTA`` (see the examples for reference). These include the ``ArduinoOTA.begin()`` call in ``setup()`` and periodically calling ``ArduinoOTA.handle();`` from the ``loop()``
|
||||
1. Build a sketch starts ``WiFi`` and includes the appropriate calls to ``ArduinoOTA`` (see the examples for reference). These include the ``ArduinoOTA.begin()`` call in ``setup()`` and periodically calling ``ArduinoOTA.handle();`` from the ``loop()``
|
||||
|
||||
2. Upload using standard USB connection the first time.
|
||||
|
||||
@@ -200,7 +200,7 @@ To upload wirelessly from the IDE:
|
||||
Password Protection
|
||||
-------------------
|
||||
|
||||
Protecting your OTA uploads with password is really straightforward. All you need to do, is to include the following statement in your code:
|
||||
Protecting your OTA uploads with a password is really straightforward. All you need to do, is to include the following statement in your code:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
@@ -208,11 +208,11 @@ Protecting your OTA uploads with password is really straightforward. All you nee
|
||||
|
||||
Where ``123`` is a sample password that you should replace with your own.
|
||||
|
||||
Before implementing it in your sketch, it is a good idea to check how it works using *BasicOTA.ino* sketch available under *File > Examples > ArduinoOTA*. Go ahead, open *BasicOTA.ino*, uncomment the above statement that is already there, and upload the sketch. To make troubleshooting easier, do not modify example sketch besides what is absolutely required. This is including original simple ``123`` OTA password. Then attempt to upload sketch again (using OTA). After compilation is complete, once upload is about to begin, you should see prompt for password.
|
||||
Before implementing it in your sketch, it is a good idea to check how it works using *BasicOTA.ino* sketch available under *File > Examples > ArduinoOTA*. Go ahead, open *BasicOTA.ino*, uncomment the above statement that is already there, and upload the sketch. To make troubleshooting easier, do not modify example sketch besides what is absolutely required. This includes the original simple ``123`` OTA password. Then attempt to upload a sketch again (using OTA). After compilation is complete, once upload is about to begin, you should see a prompt for password.
|
||||
|
||||
Enter the password and upload should be initiated as usual with the only difference being ``Authenticating...OK`` message visible in upload log.
|
||||
Enter the password and upload should be initiated as usual with the only difference being ``Authenticating...OK`` message visible in the upload log.
|
||||
|
||||
You will not be prompted for a reentering the same password next time. Arduino IDE will remember it for you. You will see prompt for password only after reopening IDE, or if you change it in your sketch, upload the sketch and then try to upload it again.
|
||||
You will not be prompted for a reentering the same password next time. Arduino IDE will remember it for you. You will see a prompt for password only after reopening IDE, or if you change it in your sketch, upload the sketch and then try to upload it again.
|
||||
|
||||
Please note, it is possible to reveal password entered previously in Arduino IDE, if IDE has not been closed since last upload. This can be done by enabling *Show verbose output during: upload* in *File > Preferences* and attempting to upload the module.
|
||||
|
||||
@@ -289,7 +289,7 @@ Simple updater downloads the file every time the function is called.
|
||||
Advanced updater
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
Its possible to point the update function to a script on the server. If a version string argument is given, it will be sent to the server. The server side script can use this string to check whether an update should be performed.
|
||||
It's possible to point the update function to a script on the server. If a version string argument is given, it will be sent to the server. The server side script can use this string to check whether an update should be performed.
|
||||
|
||||
The server-side script can respond as follows: - response code 200, and send the firmware image, - or response code 304 to notify Pico that no update is required.
|
||||
|
||||
@@ -305,7 +305,7 @@ The server-side script can respond as follows: - response code 200, and send the
|
||||
Serial.println("[update] Update no Update.");
|
||||
break;
|
||||
case HTTP_UPDATE_OK:
|
||||
Serial.println("[update] Update ok."); // may not be called since we reboot the RP2040
|
||||
Serial.println("[update] Update ok.");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -81,8 +81,8 @@ overhead).
|
||||
Hardware Identification
|
||||
-----------------------
|
||||
|
||||
bool isPicoW()
|
||||
~~~~~~~~~~~~~~
|
||||
bool rp2040.isPicoW()
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
Returns the core's best guess if this code is running on a Raspberry Pi Pico W.
|
||||
This would let you, possibly, use the same UF2 for Pico and PicoW by simply not
|
||||
doing any WiFi calls.
|
||||
|
||||
@@ -45,3 +45,13 @@ For applications where an IRQ driven serial port is not appropriate, use
|
||||
|
||||
For detailed information about the Serial ports, see the
|
||||
Arduino `Serial Reference <https://www.arduino.cc/reference/en/language/functions/communication/serial/>`_ .
|
||||
|
||||
|
||||
RP2040 Specific SerialUSB methods
|
||||
---------------------------------
|
||||
|
||||
void Serial.ignoreFlowControl(bool ignore)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
In some cases, the target application will not assert the DTR virtual line, thus preventing writing operations to succeed.
|
||||
|
||||
For this reason, the SerialUSB::ignoreFlowControl() method disables the connection's state verification, enabling the program to write on the port, even though the data might be lost.
|
||||
|
||||
+25
-4
@@ -1,5 +1,5 @@
|
||||
SPI (Serial Peripheral Interface)
|
||||
=================================
|
||||
SPI Master (Serial Peripheral Interface)
|
||||
========================================
|
||||
|
||||
The RP2040 has two hardware SPI interfaces, ``spi0 (SPI)`` and ``spi1 (SPI1)``.
|
||||
These interfaces are supported by the ``SPI`` library in master mode.
|
||||
@@ -20,5 +20,26 @@ The Arduino `SPI documentation <https://www.arduino.cc/en/reference/SPI>`_ gives
|
||||
a detailed overview of the library, except for the following RP2040-specific
|
||||
changes:
|
||||
|
||||
* ``SPI.begin(bool hwCS)`` can take an options ``hwCS`` parameter. By passing in ``true`` for ``hwCS`` the sketch does not need to worry about asserting and deasserting the ``CS`` pin between transactions. The default is ``false`` and requires the sketch to handle the CS pin itself, as is the standard way in Arduino.
|
||||
* The interrupt calls (``usingInterrupt``, ``notUsingInterrupt``, ``attachInterrupt``, and ``detachInterrpt``) are not implemented.
|
||||
* ``SPI.begin(bool hwCS)`` can take an options ``hwCS`` parameter.
|
||||
By passing in ``true`` for ``hwCS`` the sketch does not need to worry
|
||||
about asserting and deasserting the ``CS`` pin between transactions.
|
||||
The default is ``false`` and requires the sketch to handle the CS
|
||||
pin itself, as is the standard way in Arduino.
|
||||
|
||||
* The interrupt calls (``attachInterrupt``, and ``detachInterrpt``) are not implemented.
|
||||
|
||||
|
||||
SPI Slave (SPISlave)
|
||||
====================
|
||||
|
||||
Slave mode operation is also supported on either SPI interface. Two callbacks are
|
||||
needed in your app, set through ``SPISlave.onDataRecv`` and ``SPISlave.onDataSent``,
|
||||
in order to consunme the received data and provide data to transmit.
|
||||
|
||||
* The callbacks operate at IRQ time and may be called very frequently at high SPI frequencies. So, make then small, fast, and with no memory allocations or locking.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
See the SPItoMyself example for a complete Master and Slave application.
|
||||
|
||||
+2
-2
@@ -40,9 +40,9 @@ Please note that WiFi on the Pico W is a work-in-progress and there are some imp
|
||||
|
||||
* Combined STA/AP mode is not supported
|
||||
|
||||
* Multicore is supported, but only one core may run ``WiFi`` code.
|
||||
* Multicore is supported, but only core 0 may run ``WiFi`` related code.
|
||||
|
||||
* FreeRTOS is not yet supported due to the requirement for a very different LWIP implementation. PRs always appreciated!
|
||||
* FreeRTOS is supported only on core 0 and from within ``setup`` and ``loop`, not tasks, due to the requirement for a very different LWIP implementation. PRs always appreciated!
|
||||
|
||||
The WiFi library borrows much work from the `ESP8266 Arduino Core <https://github.com/esp8266/Arduino>`__ , especially the ``WiFiClient`` and ``WiFiServer`` classes.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
||||
source [find interface/picoprobe.cfg]
|
||||
source [find target/rp2040.cfg]
|
||||
@@ -152,6 +152,8 @@
|
||||
-Wl,--wrap=realloc
|
||||
-Wl,--wrap=free
|
||||
|
||||
-Wl,--wrap=lwip_init
|
||||
|
||||
-Wl,--wrap=pbuf_header
|
||||
-Wl,--wrap=pbuf_free
|
||||
-Wl,--wrap=pbuf_alloc
|
||||
|
||||
@@ -73,11 +73,9 @@ AudioBufferManager::~AudioBufferManager() {
|
||||
if (_running) {
|
||||
_running = false;
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
dma_channel_set_irq0_enabled(_channelDMA[i], false);
|
||||
dma_channel_cleanup(_channelDMA[i]);
|
||||
__channelMap[_channelDMA[i]] = nullptr;
|
||||
dma_channel_abort(_channelDMA[i]);
|
||||
dma_channel_unclaim(_channelDMA[i]);
|
||||
dma_channel_acknowledge_irq0(_channelDMA[i]);
|
||||
__channelCount--;
|
||||
}
|
||||
if (!__channelCount) {
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
#include "HID_Bluetooth.h"
|
||||
|
||||
//setup the report map.
|
||||
//more generic function to be used with BLE & BT Classis
|
||||
void __SetupHIDreportmap(void (*WeakMouse)(), void (*WeakKeyboard)(), void (*WeakJoystick)(), bool absMouse, uint16_t *report_size, uint8_t **reportmap) {
|
||||
//allocate memory for the HID report descriptors. We don't use them, but need the size here.
|
||||
uint8_t desc_hid_report_mouse[] = { TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(1)) };
|
||||
uint8_t desc_hid_report_absmouse[] = { TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(1)) };
|
||||
uint8_t desc_hid_report_joystick[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(1)) };
|
||||
uint8_t desc_hid_report_keyboard[] = { TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(1)), TUD_HID_REPORT_DESC_CONSUMER(HID_REPORT_ID(2)) };
|
||||
int size = 0;
|
||||
|
||||
//enable to debug the individual report maps
|
||||
#if 0
|
||||
Serial.printf("Report mouse: %d bytes\n", sizeof(desc_hid_report_mouse));
|
||||
for (uint16_t i = 0; i < sizeof(desc_hid_report_mouse); i++) {
|
||||
Serial.print(desc_hid_report_mouse[i], HEX);
|
||||
Serial.print(" ");
|
||||
if (i % 4 == 3) {
|
||||
Serial.print("\n");
|
||||
}
|
||||
}
|
||||
Serial.printf("Report absmouse: %d bytes\n", sizeof(desc_hid_report_absmouse));
|
||||
for (uint16_t i = 0; i < sizeof(desc_hid_report_absmouse); i++) {
|
||||
Serial.print(desc_hid_report_absmouse[i], HEX);
|
||||
Serial.print(" ");
|
||||
if (i % 4 == 3) {
|
||||
Serial.print("\n");
|
||||
}
|
||||
}
|
||||
Serial.printf("Report kbd: %d bytes\n", sizeof(desc_hid_report_keyboard));
|
||||
for (uint16_t i = 0; i < sizeof(desc_hid_report_keyboard); i++) {
|
||||
Serial.print(desc_hid_report_keyboard[i], HEX);
|
||||
Serial.print(" ");
|
||||
if (i % 4 == 3) {
|
||||
Serial.print("\n");
|
||||
}
|
||||
}
|
||||
Serial.printf("Report joystick: %d bytes\n", sizeof(desc_hid_report_joystick));
|
||||
for (uint16_t i = 0; i < sizeof(desc_hid_report_joystick); i++) {
|
||||
Serial.print(desc_hid_report_joystick[i], HEX);
|
||||
Serial.print(" ");
|
||||
if (i % 4 == 3) {
|
||||
Serial.print("\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//accumulate the size of all used HID report descriptors
|
||||
if (WeakKeyboard) {
|
||||
size += sizeof(desc_hid_report_keyboard);
|
||||
}
|
||||
if (WeakMouse && absMouse == false) {
|
||||
size += sizeof(desc_hid_report_mouse);
|
||||
} else if (WeakMouse && absMouse == true) {
|
||||
size += sizeof(desc_hid_report_absmouse);
|
||||
}
|
||||
if (WeakJoystick) {
|
||||
size += sizeof(desc_hid_report_joystick);
|
||||
}
|
||||
|
||||
//no HID used at all
|
||||
if (size == 0) {
|
||||
*report_size = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
//allocate the "real" HID report descriptor
|
||||
*reportmap = (uint8_t *)malloc(size);
|
||||
if (*reportmap) {
|
||||
*report_size = size;
|
||||
|
||||
//now copy the descriptors
|
||||
|
||||
//1.) keyboard descriptor, if requested
|
||||
if (WeakKeyboard) {
|
||||
memcpy(*reportmap, desc_hid_report_keyboard, sizeof(desc_hid_report_keyboard));
|
||||
}
|
||||
|
||||
//2.) mouse descriptor, if necessary. Additional offset & new array is necessary if there is a keyboard.
|
||||
if (WeakMouse && absMouse == false) {
|
||||
//determine if we need an offset (USB keyboard is installed)
|
||||
if (WeakKeyboard) {
|
||||
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(3)) };
|
||||
memcpy(*reportmap + sizeof(desc_hid_report_keyboard), desc_local, sizeof(desc_local));
|
||||
} else {
|
||||
memcpy(*reportmap, desc_hid_report_mouse, sizeof(desc_hid_report_mouse));
|
||||
}
|
||||
} else if (WeakMouse && absMouse == true) {
|
||||
//determine if we need an offset (USB keyboard is installed)
|
||||
if (WeakKeyboard) {
|
||||
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(3)) };
|
||||
memcpy(*reportmap + sizeof(desc_hid_report_keyboard), desc_local, sizeof(desc_local));
|
||||
} else {
|
||||
memcpy(*reportmap, desc_hid_report_absmouse, sizeof(desc_hid_report_absmouse));
|
||||
}
|
||||
}
|
||||
|
||||
//3.) joystick descriptor. 2 additional checks are necessary for mouse and/or keyboard
|
||||
if (WeakJoystick) {
|
||||
uint8_t reportid = 1;
|
||||
int offset = 0;
|
||||
if (WeakKeyboard) {
|
||||
reportid += 2;
|
||||
offset += sizeof(desc_hid_report_keyboard);
|
||||
}
|
||||
if (WeakMouse && absMouse == false) {
|
||||
reportid++;
|
||||
offset += sizeof(desc_hid_report_mouse);
|
||||
} else if (WeakMouse && absMouse == true) {
|
||||
reportid++;
|
||||
offset += sizeof(desc_hid_report_absmouse);
|
||||
}
|
||||
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(reportid)) };
|
||||
memcpy(*reportmap + offset, desc_local, sizeof(desc_local));
|
||||
}
|
||||
|
||||
//enable for debugging the final report map
|
||||
#if 0
|
||||
Serial.begin(115200);
|
||||
Serial.printf("Final map: %d bytes\n", size);
|
||||
for (uint16_t i = 0; i < size; i++) {
|
||||
Serial.print(*reportmap[i], HEX);
|
||||
Serial.print(" ");
|
||||
if (i % 4 == 3) {
|
||||
Serial.print("\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
Serial.println("No report map pointer provided!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//get Class of Device number for starting HID, type depends on activated libraries
|
||||
uint16_t __BTGetCOD() {
|
||||
//mouse only
|
||||
if (__BTInstallMouse && !__BTInstallKeyboard && !__BTInstallJoystick) {
|
||||
return 0x2580;
|
||||
}
|
||||
//keyboard only
|
||||
if (__BTInstallKeyboard && !__BTInstallMouse && !__BTInstallJoystick) {
|
||||
return 0x2540;
|
||||
}
|
||||
//joystick only
|
||||
if (__BTInstallJoystick && !__BTInstallKeyboard && !__BTInstallMouse) {
|
||||
return 0x2508;
|
||||
}
|
||||
//any other combination will return "combo device"
|
||||
return 0x25C0;
|
||||
}
|
||||
|
||||
//get Class of Device number for starting HID, type depends on activated libraries
|
||||
uint16_t __BLEGetAppearance() {
|
||||
//mouse only
|
||||
if (__BLEInstallMouse && !__BLEInstallKeyboard && !__BLEInstallJoystick) {
|
||||
return 0x03C2;
|
||||
}
|
||||
//keyboard only
|
||||
if (__BLEInstallKeyboard && !__BLEInstallMouse && !__BLEInstallJoystick) {
|
||||
return 0x03C1;
|
||||
}
|
||||
//joystick only
|
||||
if (__BLEInstallJoystick && !__BLEInstallMouse && !__BLEInstallKeyboard) {
|
||||
return 0x03C4;
|
||||
}
|
||||
//any other combination will return "generic HID"
|
||||
return 0x03C0;
|
||||
}
|
||||
|
||||
//keyboard report id is always 1 (compatibility with iOS)
|
||||
int __BTGetKeyboardReportID() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
//
|
||||
int __BTGetMouseReportID() {
|
||||
return __BTInstallKeyboard ? 3 : 1;
|
||||
}
|
||||
|
||||
int __BTGetJoystickReportID() {
|
||||
int i = 1;
|
||||
if (__BTInstallKeyboard) {
|
||||
i += 2;
|
||||
}
|
||||
if (__BTInstallMouse) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int __BLEGetKeyboardReportID() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __BLEGetMouseReportID() {
|
||||
return __BLEInstallKeyboard ? 3 : 1;
|
||||
}
|
||||
|
||||
int __BLEGetFeatureReportID() {
|
||||
int feature = 1;
|
||||
if (__BLEInstallKeyboard) {
|
||||
feature += 2;
|
||||
}
|
||||
if (__BLEInstallMouse) {
|
||||
feature ++;
|
||||
}
|
||||
if (__BLEInstallJoystick) {
|
||||
feature ++;
|
||||
}
|
||||
return feature;
|
||||
}
|
||||
|
||||
int __BLEGetJoystickReportID() {
|
||||
int i = 1;
|
||||
if (__BLEInstallKeyboard) {
|
||||
i += 2;
|
||||
}
|
||||
if (__BLEInstallMouse) {
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
@@ -5,3 +5,39 @@
|
||||
#ifdef ENABLE_BLE
|
||||
#include "PicoBluetoothBLEHID.h"
|
||||
#endif
|
||||
|
||||
#pragma once
|
||||
|
||||
//necessary to implement the absolute mouse descriptor define,
|
||||
//remove if merged into TinyUSB
|
||||
#include <sdkoverride/tusb_absmouse.h>
|
||||
|
||||
|
||||
//override weak declarations to include HID report to report map.
|
||||
//done in each library (KeyboardBT,...)
|
||||
extern void __BTInstallKeyboard() __attribute__((weak));
|
||||
extern void __BTInstallJoystick() __attribute__((weak));
|
||||
extern void __BTInstallMouse() __attribute__((weak));
|
||||
|
||||
//override weak declarations to include HID report to report map.
|
||||
//done in each library (KeyboardBLE,...)
|
||||
extern void __BLEInstallKeyboard() __attribute__((weak));
|
||||
extern void __BLEInstallJoystick() __attribute__((weak));
|
||||
extern void __BLEInstallMouse() __attribute__((weak));
|
||||
|
||||
//setup the report map.
|
||||
//more generic function to be used with BLE & BT Classis
|
||||
void __SetupHIDreportmap(void (*WeakMouse)(), void (*WeakKeyboard)(), void (*WeakJoystick)(), bool absMouse, uint16_t *report_size, uint8_t **reportmap);
|
||||
|
||||
//get Class of Device number for starting HID, type depends on activated libraries
|
||||
uint16_t __BTGetCOD();
|
||||
//get Class of Device number for starting HID, type depends on activated libraries
|
||||
uint16_t __BLEGetAppearance();
|
||||
int __BTGetKeyboardReportID();
|
||||
int __BTGetMouseReportID();
|
||||
int __BTGetJoystickReportID();
|
||||
|
||||
int __BLEGetKeyboardReportID();
|
||||
int __BLEGetMouseReportID();
|
||||
int __BLEGetJoystickReportID();
|
||||
int __BLEGetFeatureReportID();
|
||||
|
||||
@@ -21,17 +21,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sdkoverride/bluetooth.h>
|
||||
#include <_needsbt.h>
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
#include <pico/cyw43_arch.h>
|
||||
#include <class/hid/hid_device.h>
|
||||
|
||||
#include "HID_Bluetooth.h"
|
||||
|
||||
// The BTStack has redefinitions of this USB enum (same values, just redefined), so hide it to allow easy compilation
|
||||
#define HID_REPORT_TYPE_INPUT HID_REPORT_TYPE_INPUT_BT
|
||||
#define HID_REPORT_TYPE_OUTPUT HID_REPORT_TYPE_OUTPUT_BT
|
||||
#define HID_REPORT_TYPE_FEATURE HID_REPORT_TYPE_FEATURE_BT
|
||||
#define hid_report_type_t hid_report_type_t_bt
|
||||
#include <sdkoverride/hids_device.h>
|
||||
#include <sdkoverride/att_db.h>
|
||||
#include <btstack.h>
|
||||
#undef hid_report_type_t
|
||||
#undef HID_REPORT_TYPE_FEATURE
|
||||
@@ -43,7 +48,9 @@
|
||||
#include <btstack_event.h>
|
||||
#include <ble/gatt-service/battery_service_server.h>
|
||||
#include <ble/gatt-service/device_information_service_server.h>
|
||||
#include <ble/gatt-service/hids_device.h>
|
||||
//#include <ble/gatt-service/hids_device.h>
|
||||
|
||||
|
||||
|
||||
class PicoBluetoothBLEHID_;
|
||||
extern PicoBluetoothBLEHID_ PicoBluetoothBLEHID;
|
||||
@@ -100,8 +107,20 @@ public:
|
||||
// Setup device information service
|
||||
device_information_service_server_init();
|
||||
|
||||
// Setup HID Device service
|
||||
hids_device_init(0, hidDescriptor, hidDescriptorSize);
|
||||
// Setup HID Device service, depending on activated reports
|
||||
uint8_t numreports = 1; //start with 1 (feature report)
|
||||
if (__BLEInstallKeyboard) {
|
||||
numreports += 2; //add keycodes + consumer keys
|
||||
}
|
||||
if (__BLEInstallMouse) {
|
||||
numreports += 1;
|
||||
}
|
||||
if (__BLEInstallJoystick) {
|
||||
numreports += 1;
|
||||
}
|
||||
//allocate memory for hid reports
|
||||
_reportStorage = (hids_device_report_t *) malloc(sizeof(hids_device_report_t) * numreports);
|
||||
hids_device_init_with_storage(0, hidDescriptor, hidDescriptorSize, numreports, _reportStorage);
|
||||
|
||||
// Setup advertisements
|
||||
uint16_t adv_int_min = 0x0030;
|
||||
@@ -134,6 +153,9 @@ public:
|
||||
}
|
||||
|
||||
void packetHandler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
uint8_t result;
|
||||
uint8_t reportID;
|
||||
|
||||
if (type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
@@ -174,10 +196,32 @@ public:
|
||||
case HIDS_SUBEVENT_CAN_SEND_NOW:
|
||||
switch (_protocol_mode) {
|
||||
case 0:
|
||||
hids_device_send_boot_keyboard_input_report(_con_handle, (const uint8_t *)_sendReport, _sendReportLen);
|
||||
//We cannot distinguish between kbd & mouse in boot mode.
|
||||
//If both are activated, we cannot send
|
||||
if (__BLEInstallKeyboard && !__BLEInstallMouse) {
|
||||
hids_device_send_boot_keyboard_input_report(_con_handle, &(((const uint8_t *)_sendReport)[1]), _sendReportLen);
|
||||
}
|
||||
if (__BLEInstallMouse && !__BLEInstallKeyboard) {
|
||||
hids_device_send_boot_mouse_input_report(_con_handle, &(((const uint8_t *)_sendReport)[1]), _sendReportLen);
|
||||
}
|
||||
if (__BLEInstallMouse && __BLEInstallJoystick) {
|
||||
printf("Error: BLE HID in boot mode, but mouse & keyboard are active\n");
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
hids_device_send_input_report(_con_handle, (const uint8_t *)_sendReport, _sendReportLen);
|
||||
reportID = ((const uint8_t *)_sendReport)[0];
|
||||
result = hids_device_send_input_report_for_id(_con_handle, (uint16_t)reportID, &(((const uint8_t *)_sendReport)[1]), _sendReportLen - 1);
|
||||
if (result) {
|
||||
Serial.printf("Error sending %d - report ID: %d\n", result, reportID);
|
||||
}
|
||||
//else Serial.printf("Sent report for ID: %d\n",reportID);
|
||||
#if 0
|
||||
Serial.printf("Sending report for ID %d, len: %d:\n", reportID, _sendReportLen);
|
||||
for (uint8_t i = 0; i < _sendReportLen; i++) {
|
||||
Serial.printf("0x%02X - ", ((const uint8_t *)_sendReport)[i]);
|
||||
}
|
||||
Serial.println("");
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -208,6 +252,8 @@ public:
|
||||
}
|
||||
|
||||
bool send(void *rpt, int len) {
|
||||
//wait for another report to be sent
|
||||
while (_needToSend);
|
||||
_needToSend = true;
|
||||
_sendReport = rpt;
|
||||
_sendReportLen = len;
|
||||
@@ -236,6 +282,9 @@ public:
|
||||
async_context_release_lock(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
uint8_t *_attdb = nullptr;
|
||||
int _attdbLen = 0;
|
||||
|
||||
private:
|
||||
bool _running = false;
|
||||
|
||||
@@ -280,10 +329,17 @@ private:
|
||||
}
|
||||
uint8_t *_advData = nullptr;
|
||||
uint8_t _advDataLen = 0;
|
||||
hids_device_report_t *_reportStorage = nullptr;
|
||||
|
||||
void _buildAttdb(const char *hidName) {
|
||||
free(_attdb);
|
||||
_attdbLen = sizeof(_attdb_head) + 8 + strlen(hidName) + sizeof(_attdb_tail);
|
||||
//add up all different parts of ATT DB
|
||||
_attdbLen = sizeof(_attdb_head) + 8 + strlen(hidName) + sizeof(_attdb_tail) + sizeof(_attdb_batt_hidhead) + sizeof(_attdb_char);
|
||||
//reports
|
||||
_attdbLen += sizeof(_attdb_kbd_report) + sizeof(_attdb_mouse_report) + sizeof(_attdb_joystick_report);
|
||||
//additional boot characteristics
|
||||
_attdbLen += sizeof(_attdb_kbd_boot) + sizeof(_attdb_mouse_boot);
|
||||
|
||||
_attdb = (uint8_t *) malloc(_attdbLen);
|
||||
memcpy(_attdb, _attdb_head, sizeof(_attdb_head));
|
||||
// 0x0003 VALUE CHARACTERISTIC-GAP_DEVICE_NAME - READ -'HID Mouse'
|
||||
@@ -300,10 +356,35 @@ private:
|
||||
_attdb[i++] = 0x2a;
|
||||
memcpy(_attdb + i, hidName, strlen(hidName));
|
||||
i += strlen(hidName);
|
||||
|
||||
memcpy(_attdb + i, _attdb_batt_hidhead, sizeof(_attdb_batt_hidhead));
|
||||
i += sizeof(_attdb_batt_hidhead);
|
||||
|
||||
//1.) KBD report mode
|
||||
memcpy(_attdb + i, _attdb_kbd_report, sizeof(_attdb_kbd_report));
|
||||
i += sizeof(_attdb_kbd_report);
|
||||
|
||||
//2.) mouse report mode
|
||||
memcpy(_attdb + i, _attdb_mouse_report, sizeof(_attdb_mouse_report));
|
||||
i += sizeof(_attdb_mouse_report);
|
||||
|
||||
//3.) joystick report mode
|
||||
memcpy(_attdb + i, _attdb_joystick_report, sizeof(_attdb_joystick_report));
|
||||
i += sizeof(_attdb_joystick_report);
|
||||
//4.) report characteristics
|
||||
memcpy(_attdb + i, _attdb_char, sizeof(_attdb_char));
|
||||
i += sizeof(_attdb_char);
|
||||
|
||||
//5.) KBD boot mode (always included)
|
||||
memcpy(_attdb + i, _attdb_kbd_boot, sizeof(_attdb_kbd_boot));
|
||||
i += sizeof(_attdb_kbd_boot);
|
||||
|
||||
//6.) mouse boot mode (always included)
|
||||
memcpy(_attdb + i, _attdb_mouse_boot, sizeof(_attdb_mouse_boot));
|
||||
i += sizeof(_attdb_mouse_boot);
|
||||
//7.) tail (report)
|
||||
memcpy(_attdb + i, _attdb_tail, sizeof(_attdb_tail));
|
||||
}
|
||||
uint8_t *_attdb = nullptr;
|
||||
int _attdbLen = 0;
|
||||
|
||||
static constexpr const uint8_t _attdb_head[] = {
|
||||
// ATT DB Version
|
||||
@@ -315,7 +396,7 @@ private:
|
||||
0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x02, 0x03, 0x00, 0x00, 0x2a,
|
||||
};
|
||||
|
||||
static constexpr const uint8_t _attdb_tail[] = {
|
||||
static constexpr const uint8_t _attdb_batt_hidhead[] = {
|
||||
// #import <battery_service.gatt> -- BEGIN
|
||||
// Specification Type org.bluetooth.service.battery_service
|
||||
// https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.battery_service.xml
|
||||
@@ -333,6 +414,7 @@ private:
|
||||
// #import <battery_service.gatt> -- END
|
||||
// add Device ID Service
|
||||
|
||||
|
||||
// #import <device_information_service.gatt> -- BEGIN
|
||||
// Specification Type org.bluetooth.service.device_information
|
||||
// https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.device_information.xml
|
||||
@@ -386,7 +468,6 @@ private:
|
||||
0x08, 0x00, 0x02, 0x01, 0x1a, 0x00, 0x50, 0x2a,
|
||||
// #import <device_information_service.gatt> -- END
|
||||
|
||||
// #import <hids.gatt> -- BEGIN
|
||||
// Specification Type org.bluetooth.service.human_interface_device
|
||||
// https://www.bluetooth.com/api/gatt/xmlfile?xmlFileName=org.bluetooth.service.human_interface_device.xml
|
||||
// Human Interface Device 1812
|
||||
@@ -397,6 +478,9 @@ private:
|
||||
// 0x001d VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_PROTOCOL_MODE - DYNAMIC | READ | WRITE_WITHOUT_RESPONSE
|
||||
// READ_ANYBODY, WRITE_ANYBODY
|
||||
0x08, 0x00, 0x06, 0x01, 0x1d, 0x00, 0x4e, 0x2a,
|
||||
};
|
||||
|
||||
static constexpr const uint8_t _attdb_kbd_report[] = {
|
||||
// 0x001e CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
|
||||
0x0d, 0x00, 0x02, 0x00, 0x1e, 0x00, 0x03, 0x28, 0x1a, 0x1f, 0x00, 0x4d, 0x2a,
|
||||
// 0x001f VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
|
||||
@@ -405,9 +489,10 @@ private:
|
||||
// 0x0020 CLIENT_CHARACTERISTIC_CONFIGURATION
|
||||
// READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
|
||||
0x0a, 0x00, 0x0f, 0xf1, 0x20, 0x00, 0x02, 0x29, 0x00, 0x00,
|
||||
// fixed report id = 1, type = Input (1)
|
||||
// fixed report id = 1, type = Input (1); keycodes
|
||||
// 0x0021 REPORT_REFERENCE-READ-1-1
|
||||
0x0a, 0x00, 0x02, 0x00, 0x21, 0x00, 0x08, 0x29, 0x1, 0x1,
|
||||
|
||||
// 0x0022 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
|
||||
0x0d, 0x00, 0x02, 0x00, 0x22, 0x00, 0x03, 0x28, 0x1a, 0x23, 0x00, 0x4d, 0x2a,
|
||||
// 0x0023 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
|
||||
@@ -416,9 +501,12 @@ private:
|
||||
// 0x0024 CLIENT_CHARACTERISTIC_CONFIGURATION
|
||||
// READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
|
||||
0x0a, 0x00, 0x0f, 0xf1, 0x24, 0x00, 0x02, 0x29, 0x00, 0x00,
|
||||
// fixed report id = 2, type = Output (2)
|
||||
// 0x0025 REPORT_REFERENCE-READ-2-2
|
||||
0x0a, 0x00, 0x02, 0x00, 0x25, 0x00, 0x08, 0x29, 0x2, 0x2,
|
||||
// fixed report id = 2, type = Input (1) consumer
|
||||
// 0x0025 REPORT_REFERENCE-READ-2-1
|
||||
0x0a, 0x00, 0x02, 0x00, 0x25, 0x00, 0x08, 0x29, 0x2, 0x1,
|
||||
};
|
||||
|
||||
static constexpr const uint8_t _attdb_mouse_report[] = {
|
||||
// 0x0026 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
|
||||
0x0d, 0x00, 0x02, 0x00, 0x26, 0x00, 0x03, 0x28, 0x1a, 0x27, 0x00, 0x4d, 0x2a,
|
||||
// 0x0027 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
|
||||
@@ -427,59 +515,88 @@ private:
|
||||
// 0x0028 CLIENT_CHARACTERISTIC_CONFIGURATION
|
||||
// READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
|
||||
0x0a, 0x00, 0x0f, 0xf1, 0x28, 0x00, 0x02, 0x29, 0x00, 0x00,
|
||||
// fixed report id = 3, type = Feature (3)
|
||||
// 0x0029 REPORT_REFERENCE-READ-3-3
|
||||
0x0a, 0x00, 0x02, 0x00, 0x29, 0x00, 0x08, 0x29, 0x3, 0x3,
|
||||
// 0x002a CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ
|
||||
0x0d, 0x00, 0x02, 0x00, 0x2a, 0x00, 0x03, 0x28, 0x02, 0x2b, 0x00, 0x4b, 0x2a,
|
||||
// 0x002b VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ
|
||||
// fixed report id = 3, type = Input (1) mouse
|
||||
// 0x0029 REPORT_REFERENCE-READ-3-1
|
||||
0x0a, 0x00, 0x02, 0x00, 0x29, 0x00, 0x08, 0x29, 0x3, 0x1,
|
||||
};
|
||||
static constexpr const uint8_t _attdb_joystick_report[] = {
|
||||
|
||||
// 0x002a CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
|
||||
0x0d, 0x00, 0x02, 0x00, 0x2a, 0x00, 0x03, 0x28, 0x1a, 0x2b, 0x00, 0x4d, 0x2a,
|
||||
// 0x002b VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | NOTIFY | ENCRYPTION_KEY_SIZE_16
|
||||
// READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
|
||||
0x08, 0x00, 0x0b, 0xf5, 0x2b, 0x00, 0x4d, 0x2a,
|
||||
// 0x002c CLIENT_CHARACTERISTIC_CONFIGURATION
|
||||
// READ_ANYBODY, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
|
||||
0x0a, 0x00, 0x0f, 0xf1, 0x2c, 0x00, 0x02, 0x29, 0x00, 0x00,
|
||||
// fixed report id = 4, type = Input (1) gamepad
|
||||
// 0x002d REPORT_REFERENCE-READ-4-1
|
||||
0x0a, 0x00, 0x02, 0x00, 0x2d, 0x00, 0x08, 0x29, 0x4, 0x1,
|
||||
};
|
||||
|
||||
static constexpr const uint8_t _attdb_char[] = {
|
||||
// 0x002e CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | ENCRYPTION_KEY_SIZE_16
|
||||
0x0d, 0x00, 0x02, 0x00, 0x2e, 0x00, 0x03, 0x28, 0x0a, 0x2f, 0x00, 0x4d, 0x2a,
|
||||
// 0x002f VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT - DYNAMIC | READ | WRITE | ENCRYPTION_KEY_SIZE_16
|
||||
// READ_ENCRYPTED, WRITE_ENCRYPTED, ENCRYPTION_KEY_SIZE=16
|
||||
0x08, 0x00, 0x0b, 0xf5, 0x2f, 0x00, 0x4d, 0x2a,
|
||||
// fixed report id = 5, type = Feature (3)
|
||||
// 0x0030 REPORT_REFERENCE-READ-5-3
|
||||
0x0a, 0x00, 0x02, 0x00, 0x30, 0x00, 0x08, 0x29, 0x5, 0x3,
|
||||
// 0x0031 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ
|
||||
0x0d, 0x00, 0x02, 0x00, 0x31, 0x00, 0x03, 0x28, 0x02, 0x32, 0x00, 0x4b, 0x2a,
|
||||
// 0x0032 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP - DYNAMIC | READ
|
||||
// READ_ANYBODY
|
||||
0x08, 0x00, 0x02, 0x01, 0x2b, 0x00, 0x4b, 0x2a,
|
||||
// 0x002c CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
|
||||
0x0d, 0x00, 0x02, 0x00, 0x2c, 0x00, 0x03, 0x28, 0x1a, 0x2d, 0x00, 0x22, 0x2a,
|
||||
// 0x002d VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
|
||||
0x08, 0x00, 0x02, 0x01, 0x32, 0x00, 0x4b, 0x2a,
|
||||
};
|
||||
|
||||
static constexpr const uint8_t _attdb_kbd_boot[] = {
|
||||
// 0x0033 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
|
||||
0x0d, 0x00, 0x02, 0x00, 0x33, 0x00, 0x03, 0x28, 0x1a, 0x34, 0x00, 0x22, 0x2a,
|
||||
// 0x0034 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
|
||||
// READ_ANYBODY, WRITE_ANYBODY
|
||||
0x08, 0x00, 0x0a, 0x01, 0x2d, 0x00, 0x22, 0x2a,
|
||||
// 0x002e CLIENT_CHARACTERISTIC_CONFIGURATION
|
||||
0x08, 0x00, 0x0a, 0x01, 0x34, 0x00, 0x22, 0x2a,
|
||||
// 0x0035 CLIENT_CHARACTERISTIC_CONFIGURATION
|
||||
// READ_ANYBODY, WRITE_ANYBODY
|
||||
0x0a, 0x00, 0x0e, 0x01, 0x2e, 0x00, 0x02, 0x29, 0x00, 0x00,
|
||||
// 0x002f CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE
|
||||
0x0d, 0x00, 0x02, 0x00, 0x2f, 0x00, 0x03, 0x28, 0x0e, 0x30, 0x00, 0x32, 0x2a,
|
||||
// 0x0030 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE
|
||||
0x0a, 0x00, 0x0e, 0x01, 0x35, 0x00, 0x02, 0x29, 0x00, 0x00,
|
||||
|
||||
|
||||
// 0x0036 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE
|
||||
0x0d, 0x00, 0x02, 0x00, 0x36, 0x00, 0x03, 0x28, 0x0e, 0x37, 0x00, 0x32, 0x2a,
|
||||
// 0x0037 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT - DYNAMIC | READ | WRITE | WRITE_WITHOUT_RESPONSE
|
||||
// READ_ANYBODY, WRITE_ANYBODY
|
||||
0x08, 0x00, 0x0e, 0x01, 0x30, 0x00, 0x32, 0x2a,
|
||||
// 0x0031 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
|
||||
0x0d, 0x00, 0x02, 0x00, 0x31, 0x00, 0x03, 0x28, 0x1a, 0x32, 0x00, 0x33, 0x2a,
|
||||
// 0x0032 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
|
||||
0x08, 0x00, 0x0e, 0x01, 0x37, 0x00, 0x32, 0x2a,
|
||||
};
|
||||
|
||||
static constexpr const uint8_t _attdb_mouse_boot[] = {
|
||||
// 0x0038 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
|
||||
0x0d, 0x00, 0x02, 0x00, 0x38, 0x00, 0x03, 0x28, 0x1a, 0x39, 0x00, 0x33, 0x2a,
|
||||
// 0x0039 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT - DYNAMIC | READ | WRITE | NOTIFY
|
||||
// READ_ANYBODY, WRITE_ANYBODY
|
||||
0x08, 0x00, 0x0a, 0x01, 0x32, 0x00, 0x33, 0x2a,
|
||||
// 0x0033 CLIENT_CHARACTERISTIC_CONFIGURATION
|
||||
0x08, 0x00, 0x0a, 0x01, 0x39, 0x00, 0x33, 0x2a,
|
||||
// 0x003a CLIENT_CHARACTERISTIC_CONFIGURATION
|
||||
// READ_ANYBODY, WRITE_ANYBODY
|
||||
0x0a, 0x00, 0x0e, 0x01, 0x33, 0x00, 0x02, 0x29, 0x00, 0x00,
|
||||
0x0a, 0x00, 0x0e, 0x01, 0x3a, 0x00, 0x02, 0x29, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static constexpr const uint8_t _attdb_tail[] = {
|
||||
// bcdHID = 0x101 (v1.0.1), bCountryCode 0, remote wakeable = 0 | normally connectable 2
|
||||
// 0x0034 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ
|
||||
0x0d, 0x00, 0x02, 0x00, 0x34, 0x00, 0x03, 0x28, 0x02, 0x35, 0x00, 0x4a, 0x2a,
|
||||
// 0x0035 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ -'01 01 00 02'
|
||||
// 0x003b CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ
|
||||
0x0d, 0x00, 0x02, 0x00, 0x3b, 0x00, 0x03, 0x28, 0x02, 0x3c, 0x00, 0x4a, 0x2a,
|
||||
// 0x003c VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION - READ -'01 01 00 02'
|
||||
// READ_ANYBODY
|
||||
0x0c, 0x00, 0x02, 0x00, 0x35, 0x00, 0x4a, 0x2a, 0x01, 0x01, 0x00, 0x02,
|
||||
// 0x0036 CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE
|
||||
0x0d, 0x00, 0x02, 0x00, 0x36, 0x00, 0x03, 0x28, 0x04, 0x37, 0x00, 0x4c, 0x2a,
|
||||
// 0x0037 VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE
|
||||
0x0c, 0x00, 0x02, 0x00, 0x3c, 0x00, 0x4a, 0x2a, 0x01, 0x01, 0x00, 0x02,
|
||||
// 0x003d CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE
|
||||
0x0d, 0x00, 0x02, 0x00, 0x3d, 0x00, 0x03, 0x28, 0x04, 0x3e, 0x00, 0x4c, 0x2a,
|
||||
// 0x003e VALUE CHARACTERISTIC-ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT - DYNAMIC | WRITE_WITHOUT_RESPONSE
|
||||
// WRITE_ANYBODY
|
||||
0x08, 0x00, 0x04, 0x01, 0x37, 0x00, 0x4c, 0x2a,
|
||||
// #import <hids.gatt> -- END
|
||||
// 0x0038 PRIMARY_SERVICE-GATT_SERVICE
|
||||
0x0a, 0x00, 0x02, 0x00, 0x38, 0x00, 0x00, 0x28, 0x01, 0x18,
|
||||
// 0x0039 CHARACTERISTIC-GATT_DATABASE_HASH - READ
|
||||
0x0d, 0x00, 0x02, 0x00, 0x39, 0x00, 0x03, 0x28, 0x02, 0x3a, 0x00, 0x2a, 0x2b,
|
||||
// 0x003a VALUE CHARACTERISTIC-GATT_DATABASE_HASH - READ -''
|
||||
// READ_ANYBODY
|
||||
0x18, 0x00, 0x02, 0x00, 0x3a, 0x00, 0x2a, 0x2b, 0xc2, 0x10, 0xf5, 0x75, 0xf3, 0x9f, 0x50, 0xb6, 0x83, 0xc7, 0xfa, 0xac, 0xa6, 0x1b, 0x7d, 0x32,
|
||||
0x08, 0x00, 0x04, 0x01, 0x3e, 0x00, 0x4c, 0x2a,
|
||||
// END
|
||||
0x00, 0x00
|
||||
0x00, 0x00,
|
||||
};
|
||||
|
||||
volatile bool _needToSend = false;
|
||||
void *_sendReport;
|
||||
uint8_t _sendReportID;
|
||||
int _sendReportLen;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <sdkoverride/bluetooth.h>
|
||||
#include <_needsbt.h>
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
@@ -32,6 +33,8 @@
|
||||
#define HID_REPORT_TYPE_OUTPUT HID_REPORT_TYPE_OUTPUT_BT
|
||||
#define HID_REPORT_TYPE_FEATURE HID_REPORT_TYPE_FEATURE_BT
|
||||
#define hid_report_type_t hid_report_type_t_bt
|
||||
#include <sdkoverride/hids_device.h>
|
||||
#include <sdkoverride/att_db.h>
|
||||
#include <btstack.h>
|
||||
#undef hid_report_type_t
|
||||
#undef HID_REPORT_TYPE_FEATURE
|
||||
|
||||
@@ -6,7 +6,19 @@
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
// Example works with either Wired or WiFi Ethernet, define one of these values to 1, other to 0
|
||||
#define USE_WIFI 1
|
||||
#define USE_WIRED 0
|
||||
|
||||
#if USE_WIFI
|
||||
#include <WiFi.h>
|
||||
#elif USE_WIRED
|
||||
#include <W5500lwIP.h> // Or W5100lwIP.h or ENC28J60lwIP.h
|
||||
Wiznet5500lwIP eth(1 /* chip select */); // or Wiznet5100lwIP or ENC28J60lwIP
|
||||
#endif
|
||||
|
||||
#include <HTTPClient.h>
|
||||
|
||||
#ifndef STASSID
|
||||
@@ -34,8 +46,33 @@ void setup() {
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
#if USE_WIFI
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFiMulti.addAP(ssid, pass);
|
||||
#elif USE_WIRED
|
||||
// Set up SPI pinout to match your HW
|
||||
SPI.setRX(0);
|
||||
SPI.setCS(1);
|
||||
SPI.setSCK(2);
|
||||
SPI.setTX(3);
|
||||
|
||||
// Start the Ethernet port
|
||||
if (!eth.begin()) {
|
||||
Serial.println("No wired Ethernet hardware detected. Check pinouts, wiring.");
|
||||
while (1) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for connection
|
||||
while (eth.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(eth.localIP());
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
const char *jigsaw_cert = R"EOF(
|
||||
@@ -74,8 +111,12 @@ N6K5xrmaof185pVCxACPLc/BoKyUwMeC8iXCm00=
|
||||
static int cnt = 0;
|
||||
|
||||
void loop() {
|
||||
// wait for WiFi connection
|
||||
if ((WiFiMulti.run() == WL_CONNECTED)) {
|
||||
#if USE_WIFI
|
||||
// wait for WiFi connection
|
||||
if ((WiFiMulti.run() == WL_CONNECTED)) {
|
||||
#elif USE_WIRED
|
||||
if (eth.connected()) {
|
||||
#endif
|
||||
HTTPClient https;
|
||||
switch (cnt) {
|
||||
case 0:
|
||||
|
||||
@@ -282,11 +282,7 @@ HTTPUpdateResult HTTPUpdate::handleUpdate(HTTPClient& http, const String& curren
|
||||
_cbEnd();
|
||||
}
|
||||
|
||||
#ifdef ATOMIC_FS_UPDATE
|
||||
if (_rebootOnUpdate) {
|
||||
#else
|
||||
if (_rebootOnUpdate && !spiffs) {
|
||||
#endif
|
||||
rp2040.restart();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
This example demonstrates I2S output with the optional MCLK signal.
|
||||
Note: System clock sppeds used here may not be compatible with all other libraries
|
||||
requiring specific sys_clks, particularly Pico PIO USB.
|
||||
|
||||
Original 17 November 2016
|
||||
by Sandeep Mistry
|
||||
modified for RP2040 by Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
bool setBCLK(pin_size_t pin);
|
||||
- This assigns two adjacent pins - the pin after this one (one greater)
|
||||
is the WS (word select) signal, which toggles before the sample for
|
||||
each channel is sent
|
||||
|
||||
bool setDATA(pin_size_t pin);
|
||||
- Sets the DOUT pin, can be any valid GPIO pin
|
||||
|
||||
|
||||
modified for MCLK by Richard Palmer June 2023
|
||||
*/
|
||||
|
||||
#include <I2S.h>
|
||||
|
||||
// Create the I2S port using a PIO state machine
|
||||
I2S i2s(OUTPUT);
|
||||
|
||||
// GPIO pin numbers
|
||||
#define pDOUT 19
|
||||
#define pBCLK 20
|
||||
#define pWS (pBCLK+1)
|
||||
#define pMCLK 22 // optional MCLK pin
|
||||
|
||||
const int frequency = 440; // frequency of square wave in Hz
|
||||
const int sampleRate = 16000; // minimum for many i2s DACs
|
||||
const int bitsPerSample = 16;
|
||||
const int amplitude = 1 << (bitsPerSample - 2); // amplitude of square wave = 1/2 of maximum
|
||||
|
||||
#define MCLK_MUL 256 // depends on audio hardware. Suits common hardware.
|
||||
|
||||
const int halfWavelength = sampleRate / (2 * frequency); // half wavelength of square wave
|
||||
|
||||
int16_t sample = amplitude; // current sample value
|
||||
int count = 0;
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
digitalWrite(LED_BUILTIN, 1);
|
||||
|
||||
// set the system clock to a compatible value to the samplerate
|
||||
i2s.setSysClk(sampleRate); // best to do this before starting anything clock-dependent
|
||||
|
||||
Serial.begin(115200);
|
||||
while (!Serial) {
|
||||
delay(10);
|
||||
}
|
||||
Serial.println("I2S with MCLK - square wave");
|
||||
|
||||
i2s.setBCLK(pBCLK);
|
||||
i2s.setDATA(pDOUT);
|
||||
i2s.setMCLK(pMCLK); // must be run before setFrequency() and i2s.begin()
|
||||
i2s.setMCLKmult(MCLK_MUL); // also before i2s.begin()
|
||||
i2s.setBitsPerSample(16);
|
||||
|
||||
// start I2S at the sample rate with 16-bits per sample
|
||||
if (!i2s.begin(sampleRate)) {
|
||||
Serial.println("Failed to initialize I2S!");
|
||||
while (100); // do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (count % halfWavelength == 0) {
|
||||
// invert the sample every half wavelength count multiple to generate square wave
|
||||
sample = -1 * sample;
|
||||
}
|
||||
|
||||
// write the same sample twice, once for left and once for the right channel
|
||||
i2s.write(sample);
|
||||
i2s.write(sample);
|
||||
|
||||
// increment the counter for the next sample
|
||||
count++;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
This example just sends out TDM data on 8 channels with their value
|
||||
equal to the channel number.
|
||||
Released to the public domain by Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
*/
|
||||
|
||||
#include <I2S.h>
|
||||
|
||||
// Create the I2S port using a PIO state machine
|
||||
I2S i2s(OUTPUT);
|
||||
|
||||
// GPIO pin numbers
|
||||
#define pBCLK 20
|
||||
#define pWS (pBCLK+1)
|
||||
#define pDOUT 22
|
||||
|
||||
const int sampleRate = 1000;
|
||||
|
||||
void setup() {
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
digitalWrite(LED_BUILTIN, 1);
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.println("I2S TDM test");
|
||||
|
||||
i2s.setBCLK(pBCLK);
|
||||
i2s.setDATA(pDOUT);
|
||||
i2s.setBitsPerSample(32);
|
||||
i2s.setTDMFormat();
|
||||
i2s.setTDMChannels(8);
|
||||
|
||||
// start I2S at the sample rate with 16-bits per sample
|
||||
if (!i2s.begin(sampleRate)) {
|
||||
Serial.println("Failed to initialize I2S!");
|
||||
while (1); // do nothing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t x = 0;
|
||||
while (1) {
|
||||
i2s.write(x & 0x07);
|
||||
if (!(x % 10000)) {
|
||||
Serial.println(x);
|
||||
}
|
||||
x++;
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,16 @@ end KEYWORD2
|
||||
|
||||
setBCLK KEYWORD2
|
||||
setDATA KEYWORD2
|
||||
setMCLK KEYWORD2
|
||||
setBitsPerSample KEYWORD2
|
||||
setFrequency KEYWORD2
|
||||
setBuffers KEYWORD2
|
||||
setLSBJFormat KEYWORD2
|
||||
setTDMFormat KEYWORD2
|
||||
setTDMChannels KEYWORD2
|
||||
swapClocks KEYWORD2
|
||||
setMCLKmult KEYWORD2
|
||||
setSysClk KEYWORD2
|
||||
|
||||
read8 KEYWORD2
|
||||
read16 KEYWORD2
|
||||
|
||||
+86
-12
@@ -21,6 +21,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "I2S.h"
|
||||
#include "pio_i2s.pio.h"
|
||||
#include <pico/stdlib.h>
|
||||
|
||||
|
||||
I2S::I2S(PinMode direction) {
|
||||
@@ -30,6 +31,8 @@ I2S::I2S(PinMode direction) {
|
||||
_isOutput = direction == OUTPUT;
|
||||
_pinBCLK = 26;
|
||||
_pinDOUT = 28;
|
||||
_pinMCLK = 25;
|
||||
_MCLKenabled = false;
|
||||
#ifdef PIN_I2S_BCLK
|
||||
_pinBCLK = PIN_I2S_BCLK;
|
||||
#endif
|
||||
@@ -52,7 +55,10 @@ I2S::I2S(PinMode direction) {
|
||||
_bufferWords = 0;
|
||||
_silenceSample = 0;
|
||||
_isLSBJ = false;
|
||||
_isTDM = false;
|
||||
_tdmChannels = 8;
|
||||
_swapClocks = false;
|
||||
_multMCLK = 256;
|
||||
}
|
||||
|
||||
I2S::~I2S() {
|
||||
@@ -67,6 +73,14 @@ bool I2S::setBCLK(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool I2S::setMCLK(pin_size_t pin) {
|
||||
if (_running || (pin > 28)) {
|
||||
return false;
|
||||
}
|
||||
_pinMCLK = pin;
|
||||
return true;
|
||||
}
|
||||
bool I2S::setDATA(pin_size_t pin) {
|
||||
if (_running || (pin > 29)) {
|
||||
return false;
|
||||
@@ -96,12 +110,41 @@ bool I2S::setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample)
|
||||
bool I2S::setFrequency(int newFreq) {
|
||||
_freq = newFreq;
|
||||
if (_running) {
|
||||
float bitClk = _freq * _bps * 2.0 /* channels */ * 2.0 /* edges per clock */;
|
||||
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
|
||||
if (_MCLKenabled) {
|
||||
int bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * 2.0 /* edges per clock */;
|
||||
pio_sm_set_clkdiv_int_frac(_pio, _sm, clock_get_hz(clk_sys) / bitClk, 0);
|
||||
} else {
|
||||
float bitClk = _freq * _bps * (_isTDM ? (double)_tdmChannels : 2.0) /* channels */ * 2.0 /* edges per clock */;
|
||||
pio_sm_set_clkdiv(_pio, _sm, (float)clock_get_hz(clk_sys) / bitClk);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I2S::setSysClk(int samplerate) { // optimise sys_clk for desired samplerate
|
||||
if (samplerate % 11025 == 0) {
|
||||
set_sys_clock_khz(I2SSYSCLK_44_1, false); // 147.6 unsuccessful - no I2S no USB
|
||||
return true;
|
||||
}
|
||||
if (samplerate % 8000 == 0) {
|
||||
set_sys_clock_khz(I2SSYSCLK_8, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool I2S::setMCLKmult(int mult) {
|
||||
if (_running || !_isOutput) {
|
||||
return false;
|
||||
}
|
||||
if ((mult % 64) == 0) {
|
||||
_MCLKenabled = true;
|
||||
_multMCLK = mult;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool I2S::setLSBJFormat() {
|
||||
if (_running || !_isOutput) {
|
||||
return false;
|
||||
@@ -110,6 +153,22 @@ bool I2S::setLSBJFormat() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I2S::setTDMFormat() {
|
||||
if (_running || !_isOutput) {
|
||||
return false;
|
||||
}
|
||||
_isTDM = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I2S::setTDMChannels(int channels) {
|
||||
if (_running || !_isOutput) {
|
||||
return false;
|
||||
}
|
||||
_tdmChannels = channels;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I2S::swapClocks() {
|
||||
if (_running || !_isOutput) {
|
||||
return false;
|
||||
@@ -136,15 +195,25 @@ void I2S::onReceive(void(*fn)(void)) {
|
||||
}
|
||||
}
|
||||
|
||||
void I2S::MCLKbegin() {
|
||||
int off = 0;
|
||||
_i2sMCLK = new PIOProgram(&pio_i2s_mclk_program);
|
||||
_i2sMCLK->prepare(&_pioMCLK, &_smMCLK, &off); // not sure how to use the same PIO
|
||||
pio_i2s_MCLK_program_init(_pioMCLK, _smMCLK, off, _pinMCLK);
|
||||
int mClk = _multMCLK * _freq * 2.0 /* edges per clock */;
|
||||
pio_sm_set_clkdiv_int_frac(_pioMCLK, _smMCLK, clock_get_hz(clk_sys) / mClk, 0);
|
||||
pio_sm_set_enabled(_pioMCLK, _smMCLK, true);
|
||||
}
|
||||
|
||||
bool I2S::begin() {
|
||||
_running = true;
|
||||
_hasPeeked = false;
|
||||
_isHolding = 0;
|
||||
int off = 0;
|
||||
if (!_swapClocks) {
|
||||
_i2s = new PIOProgram(_isOutput ? (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program) : &pio_i2s_in_program);
|
||||
_i2s = new PIOProgram(_isOutput ? (_isTDM ? &pio_tdm_out_program : (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program)) : &pio_i2s_in_program);
|
||||
} else {
|
||||
_i2s = new PIOProgram(_isOutput ? (_isLSBJ ? &pio_lsbj_out_swap_program : &pio_i2s_out_swap_program) : &pio_i2s_in_swap_program);
|
||||
_i2s = new PIOProgram(_isOutput ? (_isTDM ? &pio_tdm_out_swap_program : (_isLSBJ ? &pio_lsbj_out_swap_program : &pio_i2s_out_swap_program)) : &pio_i2s_in_swap_program);
|
||||
}
|
||||
if (!_i2s->prepare(&_pio, &_sm, &off)) {
|
||||
_running = false;
|
||||
@@ -153,7 +222,9 @@ bool I2S::begin() {
|
||||
return false;
|
||||
}
|
||||
if (_isOutput) {
|
||||
if (_isLSBJ) {
|
||||
if (_isTDM) {
|
||||
pio_tdm_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks, _tdmChannels);
|
||||
} else if (_isLSBJ) {
|
||||
pio_lsbj_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
|
||||
} else {
|
||||
pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
|
||||
@@ -162,6 +233,9 @@ bool I2S::begin() {
|
||||
pio_i2s_in_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps, _swapClocks);
|
||||
}
|
||||
setFrequency(_freq);
|
||||
if (_MCLKenabled) {
|
||||
MCLKbegin();
|
||||
}
|
||||
if (_bps == 8) {
|
||||
uint8_t a = _silenceSample & 0xff;
|
||||
_silenceSample = (a << 24) | (a << 16) | (a << 8) | a;
|
||||
@@ -189,6 +263,11 @@ bool I2S::begin() {
|
||||
|
||||
void I2S::end() {
|
||||
if (_running) {
|
||||
if (_MCLKenabled) {
|
||||
pio_sm_set_enabled(_pioMCLK, _smMCLK, false);
|
||||
delete _i2sMCLK;
|
||||
_i2sMCLK = nullptr;
|
||||
}
|
||||
pio_sm_set_enabled(_pio, _sm, false);
|
||||
_running = false;
|
||||
delete _arb;
|
||||
@@ -204,13 +283,8 @@ int I2S::available() {
|
||||
} else {
|
||||
auto avail = _arb->available();
|
||||
avail *= 4; // 4 samples per 32-bits
|
||||
if (_bps < 24) {
|
||||
if (_isOutput) {
|
||||
// 16- and 8-bit can have holding bytes available
|
||||
avail += (32 - _isHolding) / 8;
|
||||
} else {
|
||||
avail += _isHolding / 8;
|
||||
}
|
||||
if (_bps < 24 && !_isOutput) {
|
||||
avail += _isHolding / 8;
|
||||
}
|
||||
return avail;
|
||||
}
|
||||
|
||||
+17
-2
@@ -30,11 +30,16 @@ public:
|
||||
|
||||
bool setBCLK(pin_size_t pin);
|
||||
bool setDATA(pin_size_t pin);
|
||||
bool setMCLK(pin_size_t pin);
|
||||
bool setBitsPerSample(int bps);
|
||||
bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0);
|
||||
bool setFrequency(int newFreq);
|
||||
bool setLSBJFormat();
|
||||
bool setTDMFormat();
|
||||
bool setTDMChannels(int channels);
|
||||
bool swapClocks();
|
||||
bool setMCLKmult(int mult);
|
||||
bool setSysClk(int samplerate);
|
||||
|
||||
bool begin(long sampleRate) {
|
||||
setFrequency(sampleRate);
|
||||
@@ -110,14 +115,19 @@ public:
|
||||
private:
|
||||
pin_size_t _pinBCLK;
|
||||
pin_size_t _pinDOUT;
|
||||
pin_size_t _pinMCLK;
|
||||
int _bps;
|
||||
int _freq;
|
||||
int _multMCLK;
|
||||
size_t _buffers;
|
||||
size_t _bufferWords;
|
||||
int32_t _silenceSample;
|
||||
bool _isLSBJ;
|
||||
bool _isTDM;
|
||||
int _tdmChannels;
|
||||
bool _isOutput;
|
||||
bool _swapClocks;
|
||||
bool _MCLKenabled;
|
||||
|
||||
bool _running;
|
||||
|
||||
@@ -132,9 +142,14 @@ private:
|
||||
int _isHolding = 0;
|
||||
|
||||
void (*_cb)();
|
||||
void MCLKbegin();
|
||||
|
||||
AudioBufferManager *_arb;
|
||||
PIOProgram *_i2s;
|
||||
PIO _pio;
|
||||
int _sm;
|
||||
PIOProgram *_i2sMCLK;
|
||||
PIO _pio, _pioMCLK;
|
||||
int _sm, _smMCLK;
|
||||
|
||||
static const int I2SSYSCLK_44_1 = 135600; // 44.1, 88.2 kHz sample rates
|
||||
static const int I2SSYSCLK_8 = 147600; // 8k, 16, 32, 48, 96, 192 kHz
|
||||
};
|
||||
|
||||
@@ -19,7 +19,16 @@
|
||||
; License along with this library; if not, write to the Free Software
|
||||
; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
.program pio_i2s_mclk
|
||||
; PIO SM clock is MCLK x 2
|
||||
; MCLK/BCLK sync is maintained by ensuring IN/OUT PIO SM clock dividers
|
||||
; are x128, 384, 256, 512 or 768 multiples of MCLK SM divider as required.
|
||||
|
||||
loop_mclk:
|
||||
set pins, 1 ; toggle mclk
|
||||
set pins, 0
|
||||
; Loop back to beginning...
|
||||
|
||||
.program pio_i2s_out
|
||||
.side_set 2 ; 0 = bclk, 1=wclk
|
||||
|
||||
@@ -63,6 +72,38 @@ right:
|
||||
; Loop back to beginning...
|
||||
|
||||
|
||||
.program pio_tdm_out
|
||||
.side_set 2 ; 0 = bclk, 1 = wclk
|
||||
; The C code should place (number of bits - 1) in Y and update SHIFTCTRL
|
||||
; to be 32 (as per the TDM specs)
|
||||
; +----- WCLK
|
||||
; |+---- BCLK
|
||||
mov x, y side 0b11
|
||||
bitloop:
|
||||
out pins, 1 side 0b00
|
||||
jmp x-- bitloop side 0b01
|
||||
|
||||
lastbit:
|
||||
out pins, 1 side 0b10
|
||||
; Loop back to the beginning
|
||||
|
||||
|
||||
.program pio_tdm_out_swap
|
||||
.side_set 2 ; 0 = wclk, 1 = bclk
|
||||
; The C code should place (number of bits - 1) in Y and update SHIFTCTRL
|
||||
; to be 32 (as per the TDM specs)
|
||||
; +----- BCLK
|
||||
; |+---- WCLK
|
||||
mov x, y side 0b11
|
||||
bitloop:
|
||||
out pins, 1 side 0b00
|
||||
jmp x-- bitloop side 0b10
|
||||
|
||||
lastbit:
|
||||
out pins, 1 side 0b01
|
||||
; Loop back to the beginning
|
||||
|
||||
|
||||
|
||||
.program pio_lsbj_out
|
||||
.side_set 2 ; 0 = bclk, 1=wclk
|
||||
@@ -157,6 +198,14 @@ right:
|
||||
|
||||
% c-sdk {
|
||||
|
||||
static inline void pio_i2s_MCLK_program_init(PIO pio, uint sm, uint offset, uint MCLK_pin) {
|
||||
pio_gpio_init(pio, MCLK_pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, MCLK_pin, 1, true);
|
||||
pio_sm_config sm_config = pio_i2s_mclk_program_get_default_config(offset);
|
||||
sm_config_set_set_pins(&sm_config, MCLK_pin, 1);
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
}
|
||||
|
||||
static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
@@ -178,6 +227,33 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
|
||||
}
|
||||
|
||||
static inline void pio_tdm_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap, uint channels) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
pio_gpio_init(pio, clock_pin_base + 1);
|
||||
|
||||
pio_sm_config sm_config = swap ? pio_tdm_out_swap_program_get_default_config(offset) : pio_tdm_out_program_get_default_config(offset);
|
||||
|
||||
sm_config_set_out_pins(&sm_config, data_pin, 1);
|
||||
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
|
||||
sm_config_set_out_shift(&sm_config, false, true, 32);
|
||||
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
|
||||
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
|
||||
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
|
||||
pio_sm_set_pins(pio, sm, 0); // clear pins
|
||||
|
||||
// Can't set constant > 31, so push and pop/mov
|
||||
pio_sm_put_blocking(pio, sm, bits * channels - 2);
|
||||
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
|
||||
pio_sm_exec(pio, sm, pio_encode_mov(pio_y, pio_osr));
|
||||
// Need to make OSR believe there's nothing left to shift out, or the 1st word will be the count we just passed in, not a sample
|
||||
pio_sm_exec(pio, sm, pio_encode_out(pio_osr, 32));
|
||||
}
|
||||
|
||||
|
||||
static inline void pio_lsbj_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
|
||||
@@ -8,6 +8,34 @@
|
||||
#include "hardware/pio.h"
|
||||
#endif
|
||||
|
||||
// ------------ //
|
||||
// pio_i2s_mclk //
|
||||
// ------------ //
|
||||
|
||||
#define pio_i2s_mclk_wrap_target 0
|
||||
#define pio_i2s_mclk_wrap 1
|
||||
|
||||
static const uint16_t pio_i2s_mclk_program_instructions[] = {
|
||||
// .wrap_target
|
||||
0xe001, // 0: set pins, 1
|
||||
0xe000, // 1: set pins, 0
|
||||
// .wrap
|
||||
};
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
static const struct pio_program pio_i2s_mclk_program = {
|
||||
.instructions = pio_i2s_mclk_program_instructions,
|
||||
.length = 2,
|
||||
.origin = -1,
|
||||
};
|
||||
|
||||
static inline pio_sm_config pio_i2s_mclk_program_get_default_config(uint offset) {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
sm_config_set_wrap(&c, offset + pio_i2s_mclk_wrap_target, offset + pio_i2s_mclk_wrap);
|
||||
return c;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ----------- //
|
||||
// pio_i2s_out //
|
||||
// ----------- //
|
||||
@@ -78,6 +106,68 @@ static inline pio_sm_config pio_i2s_out_swap_program_get_default_config(uint off
|
||||
}
|
||||
#endif
|
||||
|
||||
// ----------- //
|
||||
// pio_tdm_out //
|
||||
// ----------- //
|
||||
|
||||
#define pio_tdm_out_wrap_target 0
|
||||
#define pio_tdm_out_wrap 3
|
||||
|
||||
static const uint16_t pio_tdm_out_program_instructions[] = {
|
||||
// .wrap_target
|
||||
0xb822, // 0: mov x, y side 3
|
||||
0x6001, // 1: out pins, 1 side 0
|
||||
0x0841, // 2: jmp x--, 1 side 1
|
||||
0x7001, // 3: out pins, 1 side 2
|
||||
// .wrap
|
||||
};
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
static const struct pio_program pio_tdm_out_program = {
|
||||
.instructions = pio_tdm_out_program_instructions,
|
||||
.length = 4,
|
||||
.origin = -1,
|
||||
};
|
||||
|
||||
static inline pio_sm_config pio_tdm_out_program_get_default_config(uint offset) {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
sm_config_set_wrap(&c, offset + pio_tdm_out_wrap_target, offset + pio_tdm_out_wrap);
|
||||
sm_config_set_sideset(&c, 2, false, false);
|
||||
return c;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---------------- //
|
||||
// pio_tdm_out_swap //
|
||||
// ---------------- //
|
||||
|
||||
#define pio_tdm_out_swap_wrap_target 0
|
||||
#define pio_tdm_out_swap_wrap 3
|
||||
|
||||
static const uint16_t pio_tdm_out_swap_program_instructions[] = {
|
||||
// .wrap_target
|
||||
0xb822, // 0: mov x, y side 3
|
||||
0x6001, // 1: out pins, 1 side 0
|
||||
0x1041, // 2: jmp x--, 1 side 2
|
||||
0x6801, // 3: out pins, 1 side 1
|
||||
// .wrap
|
||||
};
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
static const struct pio_program pio_tdm_out_swap_program = {
|
||||
.instructions = pio_tdm_out_swap_program_instructions,
|
||||
.length = 4,
|
||||
.origin = -1,
|
||||
};
|
||||
|
||||
static inline pio_sm_config pio_tdm_out_swap_program_get_default_config(uint offset) {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
sm_config_set_wrap(&c, offset + pio_tdm_out_swap_wrap_target, offset + pio_tdm_out_swap_wrap);
|
||||
sm_config_set_sideset(&c, 2, false, false);
|
||||
return c;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ------------ //
|
||||
// pio_lsbj_out //
|
||||
// ------------ //
|
||||
@@ -217,6 +307,13 @@ static inline pio_sm_config pio_i2s_in_swap_program_get_default_config(uint offs
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline void pio_i2s_MCLK_program_init(PIO pio, uint sm, uint offset, uint MCLK_pin) {
|
||||
pio_gpio_init(pio, MCLK_pin);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, MCLK_pin, 1, true);
|
||||
pio_sm_config sm_config = pio_i2s_mclk_program_get_default_config(offset);
|
||||
sm_config_set_set_pins(&sm_config, MCLK_pin, 1);
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
}
|
||||
static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
@@ -232,6 +329,26 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
|
||||
pio_sm_set_pins(pio, sm, 0); // clear pins
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
|
||||
}
|
||||
static inline void pio_tdm_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap, uint channels) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
pio_gpio_init(pio, clock_pin_base + 1);
|
||||
pio_sm_config sm_config = swap ? pio_tdm_out_swap_program_get_default_config(offset) : pio_tdm_out_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&sm_config, data_pin, 1);
|
||||
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
|
||||
sm_config_set_out_shift(&sm_config, false, true, 32);
|
||||
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
|
||||
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
|
||||
pio_sm_set_pins(pio, sm, 0); // clear pins
|
||||
// Can't set constant > 31, so push and pop/mov
|
||||
pio_sm_put_blocking(pio, sm, bits * channels - 2);
|
||||
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
|
||||
pio_sm_exec(pio, sm, pio_encode_mov(pio_y, pio_osr));
|
||||
// Need to make OSR believe there's nothing left to shift out, or the 1st word will be the count we just passed in, not a sample
|
||||
pio_sm_exec(pio, sm, pio_encode_out(pio_osr, 32));
|
||||
}
|
||||
static inline void pio_lsbj_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits, bool swap) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
|
||||
@@ -40,7 +40,7 @@ Joystick_::Joystick_(void) {
|
||||
void Joystick_::send_now(void) {
|
||||
CoreMutex m(&__usb_mutex);
|
||||
tud_task();
|
||||
if (tud_hid_ready()) {
|
||||
if (__USBHIDReady()) {
|
||||
tud_hid_n_report(0, __USBGetJoystickReportID(), &data, sizeof(data));
|
||||
}
|
||||
tud_task();
|
||||
|
||||
@@ -25,18 +25,22 @@
|
||||
|
||||
#include "JoystickBLE.h"
|
||||
#include <Arduino.h>
|
||||
#include <HID_Bluetooth.h>
|
||||
#include <PicoBluetoothBLEHID.h>
|
||||
|
||||
//================================================================================
|
||||
//================================================================================
|
||||
// Joystick/Gamepad
|
||||
|
||||
// Weak function override to add our descriptor to the list
|
||||
void __BLEInstallJoystick() { /* noop */ }
|
||||
|
||||
JoystickBLE_::JoystickBLE_(void) {
|
||||
// Member vars set in base constructor
|
||||
}
|
||||
|
||||
#define REPORT_ID 0x01
|
||||
static const uint8_t desc_joystick[] = {TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(REPORT_ID))};
|
||||
uint8_t *desc_joystickBLE;
|
||||
uint16_t desc_joystickBLE_length;
|
||||
|
||||
void JoystickBLE_::begin(const char *localName, const char *hidName) {
|
||||
if (!localName) {
|
||||
@@ -45,7 +49,10 @@ void JoystickBLE_::begin(const char *localName, const char *hidName) {
|
||||
if (!hidName) {
|
||||
hidName = localName;
|
||||
}
|
||||
PicoBluetoothBLEHID.startHID(localName, hidName, 0x03c4, desc_joystick, sizeof(desc_joystick));
|
||||
|
||||
__SetupHIDreportmap(__BLEInstallMouse, __BLEInstallKeyboard, __BLEInstallJoystick, false, &desc_joystickBLE_length, &desc_joystickBLE);
|
||||
|
||||
PicoBluetoothBLEHID.startHID(localName, hidName, __BLEGetAppearance(), desc_joystickBLE, desc_joystickBLE_length);
|
||||
}
|
||||
|
||||
void JoystickBLE_::end() {
|
||||
@@ -57,7 +64,13 @@ void JoystickBLE_::setBattery(int lvl) {
|
||||
}
|
||||
|
||||
void JoystickBLE_::send_now() {
|
||||
PicoBluetoothBLEHID.send(&data, sizeof(data));
|
||||
//insert report ID; not part of the hid_gamepad_report_t
|
||||
uint8_t *report = (uint8_t *)malloc(sizeof(hid_gamepad_report_t) +1);
|
||||
if (report) {
|
||||
report[0] = __BLEGetJoystickReportID();
|
||||
memcpy(&report[1], (uint8_t*)&data, sizeof(data));
|
||||
PicoBluetoothBLEHID.send(report, sizeof(data) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
JoystickBLE_ JoystickBLE;
|
||||
|
||||
@@ -25,18 +25,22 @@
|
||||
|
||||
#include "JoystickBT.h"
|
||||
#include <Arduino.h>
|
||||
#include <HID_Bluetooth.h>
|
||||
#include <PicoBluetoothHID.h>
|
||||
|
||||
//================================================================================
|
||||
//================================================================================
|
||||
// Joystick/Gamepad
|
||||
|
||||
// Weak function override to add our descriptor to the list
|
||||
void __BTInstallJoystick() { /* noop */ }
|
||||
|
||||
JoystickBT_::JoystickBT_() {
|
||||
// HID_Joystick sets up all the member vars
|
||||
}
|
||||
|
||||
#define REPORT_ID 0x01
|
||||
static const uint8_t desc_joystick[] = {TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(REPORT_ID))};
|
||||
uint8_t *desc_joystickBT;
|
||||
uint16_t desc_joystickBT_length;
|
||||
|
||||
void JoystickBT_::begin(const char *localName, const char *hidName) {
|
||||
if (!localName) {
|
||||
@@ -45,7 +49,10 @@ void JoystickBT_::begin(const char *localName, const char *hidName) {
|
||||
if (!hidName) {
|
||||
hidName = localName;
|
||||
}
|
||||
PicoBluetoothHID.startHID(localName, hidName, 0x2508, 33, desc_joystick, sizeof(desc_joystick));
|
||||
|
||||
__SetupHIDreportmap(__BTInstallMouse, __BTInstallKeyboard, __BTInstallJoystick, false, &desc_joystickBT_length, &desc_joystickBT);
|
||||
|
||||
PicoBluetoothHID.startHID(localName, hidName, __BTGetCOD(), 33, desc_joystickBT, desc_joystickBT_length);
|
||||
}
|
||||
|
||||
void JoystickBT_::end() {
|
||||
@@ -54,7 +61,7 @@ void JoystickBT_::end() {
|
||||
|
||||
//immediately send an HID report
|
||||
void JoystickBT_::send_now() {
|
||||
PicoBluetoothHID.send(REPORT_ID, &data, sizeof(data));
|
||||
PicoBluetoothHID.send(__BTGetJoystickReportID(), &data, sizeof(data));
|
||||
}
|
||||
|
||||
JoystickBT_ JoystickBT;
|
||||
|
||||
@@ -40,7 +40,7 @@ Keyboard_::Keyboard_(void) {
|
||||
void Keyboard_::sendReport(KeyReport* keys) {
|
||||
CoreMutex m(&__usb_mutex);
|
||||
tud_task();
|
||||
if (tud_hid_ready()) {
|
||||
if (__USBHIDReady()) {
|
||||
tud_hid_keyboard_report(__USBGetKeyboardReportID(), keys->modifiers, keys->keys);
|
||||
}
|
||||
tud_task();
|
||||
@@ -49,7 +49,7 @@ void Keyboard_::sendReport(KeyReport* keys) {
|
||||
void Keyboard_::sendConsumerReport(uint16_t key) {
|
||||
CoreMutex m(&__usb_mutex);
|
||||
tud_task();
|
||||
if (tud_hid_ready()) {
|
||||
if (__USBHIDReady()) {
|
||||
tud_hid_report(__USBGetKeyboardReportID() + 1, &key, sizeof(key));
|
||||
}
|
||||
tud_task();
|
||||
|
||||
@@ -22,19 +22,22 @@
|
||||
|
||||
#include "KeyboardBLE.h"
|
||||
#include "KeyboardLayout.h"
|
||||
#include <HID_Bluetooth.h>
|
||||
#include <PicoBluetoothBLEHID.h>
|
||||
|
||||
//================================================================================
|
||||
//================================================================================
|
||||
// Keyboard
|
||||
|
||||
// Weak function override to add our descriptor to the list
|
||||
void __BLEInstallKeyboard() { /* noop */ }
|
||||
|
||||
KeyboardBLE_::KeyboardBLE_(void) {
|
||||
// Base class clears the members we care about
|
||||
}
|
||||
|
||||
#define REPORT_ID 0x01
|
||||
|
||||
static const uint8_t desc_keyboard[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID))};
|
||||
uint8_t *desc_keyboardBLE;
|
||||
uint16_t desc_keyboardBLE_length;
|
||||
|
||||
void KeyboardBLE_::begin(const char *localName, const char *hidName, const uint8_t *layout) {
|
||||
if (!localName) {
|
||||
@@ -44,7 +47,10 @@ void KeyboardBLE_::begin(const char *localName, const char *hidName, const uint8
|
||||
hidName = localName;
|
||||
}
|
||||
_asciimap = layout;
|
||||
PicoBluetoothBLEHID.startHID(localName, hidName, 0x03c1, desc_keyboard, sizeof(desc_keyboard));
|
||||
|
||||
__SetupHIDreportmap(__BLEInstallMouse, __BLEInstallKeyboard, __BLEInstallJoystick, false, &desc_keyboardBLE_length, &desc_keyboardBLE);
|
||||
|
||||
PicoBluetoothBLEHID.startHID(localName, hidName, __BLEGetAppearance(), desc_keyboardBLE, desc_keyboardBLE_length);
|
||||
}
|
||||
|
||||
void KeyboardBLE_::end(void) {
|
||||
@@ -60,12 +66,21 @@ void KeyboardBLE_::sendReport(KeyReport* keys) {
|
||||
data.modifier = keys->modifiers;
|
||||
data.reserved = 0;
|
||||
memcpy(data.keycode, keys->keys, sizeof(data.keycode));
|
||||
PicoBluetoothBLEHID.send(&data, sizeof(data));
|
||||
|
||||
//stitch in report id
|
||||
static uint8_t report[sizeof(hid_keyboard_report_t) +1];
|
||||
report[0] = __BLEGetKeyboardReportID();
|
||||
memcpy(&report[1], (uint8_t*)&data, sizeof(hid_keyboard_report_t));
|
||||
PicoBluetoothBLEHID.send(&report, sizeof(hid_keyboard_report_t) +1);
|
||||
}
|
||||
|
||||
void KeyboardBLE_::sendConsumerReport(uint16_t key) {
|
||||
(void) key;
|
||||
// TODO - Need some BLE-specific code to send 2nd report
|
||||
uint8_t report[3];
|
||||
|
||||
report[0] = __BLEGetKeyboardReportID() + 1; //consumer report id
|
||||
report[1] = key & 0xFF;
|
||||
report[2] = (key >> 8) & 0xFF;
|
||||
PicoBluetoothBLEHID.send(&report, 3);
|
||||
}
|
||||
|
||||
KeyboardBLE_ KeyboardBLE;
|
||||
|
||||
@@ -22,19 +22,22 @@
|
||||
|
||||
#include "KeyboardBT.h"
|
||||
#include "KeyboardLayout.h"
|
||||
#include <HID_Bluetooth.h>
|
||||
#include <PicoBluetoothHID.h>
|
||||
|
||||
//================================================================================
|
||||
//================================================================================
|
||||
// Keyboard
|
||||
|
||||
// Weak function override to add our descriptor to the list
|
||||
void __BTInstallKeyboard() { /* noop */ }
|
||||
|
||||
KeyboardBT_::KeyboardBT_(void) {
|
||||
// Base class clears the members we care about
|
||||
}
|
||||
|
||||
#define REPORT_ID 0x01
|
||||
|
||||
static const uint8_t desc_keyboard[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID)), TUD_HID_REPORT_DESC_CONSUMER(HID_REPORT_ID(REPORT_ID + 1))};
|
||||
uint8_t *desc_keyboardBT;
|
||||
uint16_t desc_keyboardBT_length;
|
||||
|
||||
static void _hidReportCB(uint16_t cid, hid_report_type_t report_type, uint16_t report_id, int report_size, uint8_t *report) {
|
||||
(void) cid;
|
||||
@@ -56,7 +59,10 @@ void KeyboardBT_::begin(const char *localName, const char *hidName, const uint8_
|
||||
// Required because the hid_report_type_t overlap in BTStack and TUSB
|
||||
auto *fcn = (void (*)(short unsigned int, hid_report_type_t_bt, short unsigned int, int, unsigned char*))_hidReportCB;
|
||||
hid_device_register_report_data_callback(fcn);
|
||||
PicoBluetoothHID.startHID(localName, hidName, 0x2540, 33, desc_keyboard, sizeof(desc_keyboard));
|
||||
|
||||
__SetupHIDreportmap(__BTInstallMouse, __BTInstallKeyboard, __BTInstallJoystick, false, &desc_keyboardBT_length, &desc_keyboardBT);
|
||||
|
||||
PicoBluetoothHID.startHID(localName, hidName, __BTGetCOD(), 33, desc_keyboardBT, desc_keyboardBT_length);
|
||||
}
|
||||
|
||||
void KeyboardBT_::end(void) {
|
||||
@@ -68,11 +74,11 @@ void KeyboardBT_::sendReport(KeyReport* keys) {
|
||||
data.modifier = keys->modifiers;
|
||||
data.reserved = 0;
|
||||
memcpy(data.keycode, keys->keys, sizeof(data.keycode));
|
||||
PicoBluetoothHID.send(REPORT_ID, &data, sizeof(data));
|
||||
PicoBluetoothHID.send(__BLEGetKeyboardReportID(), &data, sizeof(data));
|
||||
}
|
||||
|
||||
void KeyboardBT_::sendConsumerReport(uint16_t key) {
|
||||
PicoBluetoothHID.send(REPORT_ID + 1, &key, sizeof(key));
|
||||
PicoBluetoothHID.send(__BLEGetKeyboardReportID() + 1, &key, sizeof(key));
|
||||
}
|
||||
|
||||
KeyboardBT_ KeyboardBT;
|
||||
|
||||
@@ -268,7 +268,11 @@ bool MDNSResponder::_parseQuery(const MDNSResponder::stcMDNS_MsgHeader& p_MsgHea
|
||||
remote = m_pUDPContext->getRemoteAddress();
|
||||
local = WiFi.localIP();
|
||||
netmask = WiFi.subnetMask();
|
||||
#if LWIP_IPV6
|
||||
if (ip4_addr_netcmp(&remote.u_addr.ip4, &local.u_addr.ip4, &netmask.u_addr.ip4))
|
||||
#else
|
||||
if (ip4_addr_netcmp(&remote, &local, &netmask))
|
||||
#endif
|
||||
// ip_info IPInfo_Local;
|
||||
// ip_info IPInfo_Remote;
|
||||
// if (((IPInfo_Remote.ip.addr = m_pUDPContext->getRemoteAddress()))
|
||||
|
||||
@@ -1539,15 +1539,6 @@ bool MDNSResponder::stcMDNSServiceQuery::stcAnswer::removeIP4Address(
|
||||
return bResult;
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::stcMDNSServiceQuery::stcAnswer::findIP4Address (const)
|
||||
*/
|
||||
const MDNSResponder::stcMDNSServiceQuery::stcAnswer::stcIP4Address*
|
||||
MDNSResponder::stcMDNSServiceQuery::stcAnswer::findIP4Address(
|
||||
const IPAddress& p_IPAddress) const {
|
||||
return (stcIP4Address*)(((const stcAnswer*)this)->findIP4Address(p_IPAddress));
|
||||
}
|
||||
|
||||
/*
|
||||
MDNSResponder::stcMDNSServiceQuery::stcAnswer::findIP4Address
|
||||
*/
|
||||
|
||||
@@ -44,7 +44,7 @@ Mouse_::Mouse_(void) {
|
||||
void Mouse_::move(int x, int y, signed char wheel) {
|
||||
CoreMutex m(&__usb_mutex);
|
||||
tud_task();
|
||||
if (tud_hid_ready()) {
|
||||
if (__USBHIDReady()) {
|
||||
tud_hid_mouse_report(__USBGetMouseReportID(), _buttons, limit_xy(x), limit_xy(y), wheel, 0);
|
||||
}
|
||||
tud_task();
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/* Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
Benjamin Aigner <beni@asterics-foundation.org> <aignerb@technikum-wien.at> */
|
||||
/* Released to the public domain */
|
||||
|
||||
#define ENABLE_LOG_INFO
|
||||
#define ENABLE_LOG_DEBUG
|
||||
|
||||
#define USE_MOUSE
|
||||
#define USE_KBD
|
||||
#define USE_JOYSTICK
|
||||
|
||||
#ifdef USE_MOUSE
|
||||
#include <MouseBLE.h>
|
||||
#endif
|
||||
#ifdef USE_KBD
|
||||
#include <KeyboardBLE.h>
|
||||
#endif
|
||||
#ifdef USE_JOYSTICK
|
||||
#include <JoystickBLE.h>
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
//If activated nothing happens until the serial port is opened
|
||||
//while(!Serial);
|
||||
|
||||
|
||||
#if (defined(USE_KBD) || defined(USE_JOYSTICK)) && defined(USE_MOUSE)
|
||||
MouseBLE.begin("BLE Composite");
|
||||
#elif defined(USE_MOUSE)
|
||||
MouseBLE.begin("BLE Mouse");
|
||||
#endif
|
||||
|
||||
#ifdef USE_KBD
|
||||
KeyboardBLE.begin("BLE KBD");
|
||||
#endif
|
||||
|
||||
#ifdef USE_JOYSTICK
|
||||
JoystickBLE.begin("BLE JOY");
|
||||
#endif
|
||||
|
||||
Serial.printf("Press BOOTSEL to start action\n");
|
||||
#ifdef USE_MOUSE
|
||||
Serial.println("First the mouse moves");
|
||||
#endif
|
||||
#ifdef USE_KBD
|
||||
Serial.println("Then \"Hi\" will be printed");
|
||||
#endif
|
||||
#ifdef USE_JOYSTICK
|
||||
Serial.println("Then joystick buttons & axis are changed");
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (BOOTSEL) {
|
||||
#ifdef USE_MOUSE
|
||||
Serial.println("ACTION!!!");
|
||||
float r = 100;
|
||||
float ox = 0.0;
|
||||
float oy = 0.0;
|
||||
for (float a = 0; a < 2.0 * 3.14159; a += 0.1) {
|
||||
float ax = r * cos(a);
|
||||
float ay = r * sin(a);
|
||||
float dx = ax - ox;
|
||||
float dy = ay - oy;
|
||||
MouseBLE.move(dx, dy, 0);
|
||||
ox = ax;
|
||||
oy = ay;
|
||||
delay(10);
|
||||
}
|
||||
MouseBLE.setBattery(random(0, 101)); // Set between 0...100%
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
#ifdef USE_KBD
|
||||
KeyboardBLE.print("Hi");
|
||||
#endif
|
||||
|
||||
#ifdef USE_JOYSTICK
|
||||
JoystickBLE.button(1, true);
|
||||
JoystickBLE.X(0);
|
||||
JoystickBLE.send_now();
|
||||
delay(1000);
|
||||
|
||||
JoystickBLE.button(1, false);
|
||||
JoystickBLE.X(512);
|
||||
JoystickBLE.send_now();
|
||||
#endif
|
||||
|
||||
while (BOOTSEL) {
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,15 +21,19 @@
|
||||
|
||||
#include "MouseBLE.h"
|
||||
#include <sdkoverride/tusb_absmouse.h>
|
||||
#include <HID_Bluetooth.h>
|
||||
#include <PicoBluetoothBLEHID.h>
|
||||
|
||||
// Weak function override to add our descriptor to the list
|
||||
void __BLEInstallMouse() { /* noop */ }
|
||||
|
||||
MouseBLE_::MouseBLE_(bool absolute) : HID_Mouse(absolute) {
|
||||
_running = false;
|
||||
}
|
||||
|
||||
#define REPORT_ID 0x01
|
||||
const uint8_t desc_mouse[] = {TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID))};
|
||||
const uint8_t desc_absmouse[] = {TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(REPORT_ID))};
|
||||
uint8_t *desc_mouseBLE;
|
||||
uint16_t desc_mouseBLE_length;
|
||||
|
||||
void MouseBLE_::begin(const char *localName, const char *hidName) {
|
||||
if (!localName) {
|
||||
localName = "PicoW BLE Mouse";
|
||||
@@ -37,7 +41,10 @@ void MouseBLE_::begin(const char *localName, const char *hidName) {
|
||||
if (!hidName) {
|
||||
hidName = localName;
|
||||
}
|
||||
PicoBluetoothBLEHID.startHID(localName, hidName, 0x03c2, _absolute ? desc_absmouse : desc_mouse, _absolute ? sizeof(desc_absmouse) : sizeof(desc_mouse));
|
||||
|
||||
__SetupHIDreportmap(__BLEInstallMouse, __BLEInstallKeyboard, __BLEInstallJoystick, _absolute, &desc_mouseBLE_length, &desc_mouseBLE);
|
||||
|
||||
PicoBluetoothBLEHID.startHID(localName, hidName, __BLEGetAppearance(), desc_mouseBLE, desc_mouseBLE_length);
|
||||
_running = true;
|
||||
}
|
||||
|
||||
@@ -61,6 +68,8 @@ void MouseBLE_::setAbsolute(bool absolute) {
|
||||
}
|
||||
|
||||
void MouseBLE_::move(int x, int y, signed char wheel) {
|
||||
static uint8_t report[sizeof(hid_abs_mouse_report_t) +1];
|
||||
|
||||
if (!_absolute) {
|
||||
hid_mouse_report_t data;
|
||||
data.buttons = _buttons;
|
||||
@@ -68,7 +77,10 @@ void MouseBLE_::move(int x, int y, signed char wheel) {
|
||||
data.y = limit_xy(y);
|
||||
data.wheel = wheel;
|
||||
data.pan = 0;
|
||||
PicoBluetoothBLEHID.send(&data, sizeof(data));
|
||||
|
||||
report[0] = __BLEGetMouseReportID();
|
||||
memcpy(&report[1], (uint8_t*)&data, sizeof(data));
|
||||
PicoBluetoothBLEHID.send(report, sizeof(data) + 1);
|
||||
} else {
|
||||
hid_abs_mouse_report_t data;
|
||||
data.buttons = _buttons;
|
||||
@@ -76,7 +88,10 @@ void MouseBLE_::move(int x, int y, signed char wheel) {
|
||||
data.y = limit_xy(y);
|
||||
data.wheel = wheel;
|
||||
data.pan = 0;
|
||||
PicoBluetoothBLEHID.send(&data, sizeof(data));
|
||||
|
||||
report[0] = __BLEGetMouseReportID();
|
||||
memcpy(&report[1], (uint8_t*)&data, sizeof(data));
|
||||
PicoBluetoothBLEHID.send(report, sizeof(data) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,18 @@
|
||||
|
||||
#include "MouseBT.h"
|
||||
#include <sdkoverride/tusb_absmouse.h>
|
||||
#include <HID_Bluetooth.h>
|
||||
#include <PicoBluetoothHID.h>
|
||||
|
||||
// Weak function override to add our descriptor to the list
|
||||
void __BTInstallMouse() { /* noop */ }
|
||||
|
||||
MouseBT_::MouseBT_(bool absolute) : HID_Mouse(absolute) {
|
||||
_running = false;
|
||||
}
|
||||
|
||||
#define REPORT_ID 0x01
|
||||
const uint8_t desc_mouse[] = {TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(REPORT_ID))};
|
||||
const uint8_t desc_absmouse[] = {TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(REPORT_ID))};
|
||||
uint8_t *desc_mouseBT;
|
||||
uint16_t desc_mouseBT_length;
|
||||
|
||||
void MouseBT_::begin(const char *localName, const char *hidName) {
|
||||
if (!localName) {
|
||||
@@ -38,7 +41,10 @@ void MouseBT_::begin(const char *localName, const char *hidName) {
|
||||
if (!hidName) {
|
||||
hidName = localName;
|
||||
}
|
||||
PicoBluetoothHID.startHID(localName, hidName, 0x2580, 33, _absolute ? desc_absmouse : desc_mouse, _absolute ? sizeof(desc_absmouse) : sizeof(desc_mouse));
|
||||
|
||||
__SetupHIDreportmap(__BTInstallMouse, __BTInstallKeyboard, __BTInstallJoystick, _absolute, &desc_mouseBT_length, &desc_mouseBT);
|
||||
|
||||
PicoBluetoothHID.startHID(localName, hidName, __BTGetCOD(), 33, desc_mouseBT, desc_mouseBT_length);
|
||||
_running = true;
|
||||
}
|
||||
|
||||
@@ -63,7 +69,7 @@ void MouseBT_::move(int x, int y, signed char wheel) {
|
||||
data.y = limit_xy(y);
|
||||
data.wheel = wheel;
|
||||
data.pan = 0;
|
||||
PicoBluetoothHID.send(REPORT_ID, &data, sizeof(data));
|
||||
PicoBluetoothHID.send(__BTGetMouseReportID(), &data, sizeof(data));
|
||||
} else {
|
||||
hid_abs_mouse_report_t data;
|
||||
data.buttons = _buttons;
|
||||
@@ -71,7 +77,7 @@ void MouseBT_::move(int x, int y, signed char wheel) {
|
||||
data.y = limit_xy(y);
|
||||
data.wheel = wheel;
|
||||
data.pan = 0;
|
||||
PicoBluetoothHID.send(REPORT_ID, &data, sizeof(data));
|
||||
PicoBluetoothHID.send(__BTGetMouseReportID(), &data, sizeof(data));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,8 +101,8 @@ public:
|
||||
info.blockSize = _fs.vol()->bytesPerCluster();
|
||||
info.pageSize = 0; // TODO ?
|
||||
info.maxPathLength = 255; // TODO ?
|
||||
info.totalBytes = _fs.vol()->clusterCount() * info.blockSize;
|
||||
info.usedBytes = info.totalBytes - (_fs.vol()->freeClusterCount() * _fs.vol()->bytesPerCluster());
|
||||
info.totalBytes = (uint64_t)_fs.vol()->clusterCount() * (uint64_t)info.blockSize;
|
||||
info.usedBytes = info.totalBytes - ((uint64_t)_fs.vol()->freeClusterCount() * (uint64_t)_fs.vol()->bytesPerCluster());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ SPI1 KEYWORD1
|
||||
#######################################
|
||||
begin KEYWORD2
|
||||
end KEYWORD2
|
||||
beginTransaction KEYWORD2
|
||||
endTransaction KEYWORD2
|
||||
SPISettings KEYWORD2
|
||||
transfer KEYWORD2
|
||||
setBitOrder KEYWORD2
|
||||
setDataMode KEYWORD2
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "SPI.h"
|
||||
#include <hardware/spi.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/structs/iobank0.h>
|
||||
#include <hardware/irq.h>
|
||||
|
||||
#ifdef USE_TINYUSB
|
||||
// For Serial when selecting TinyUSB. Can't include in the core because Arduino IDE
|
||||
@@ -177,7 +179,8 @@ void SPIClassRP2040::transfer(const void *txbuf, void *rxbuf, size_t count) {
|
||||
}
|
||||
|
||||
void SPIClassRP2040::beginTransaction(SPISettings settings) {
|
||||
DEBUGSPI("SPI::beginTransaction(clk=%lu, bo=%s\n", _spis.getClockFreq(), (_spis.getBitOrder() == MSBFIRST) ? "MSB" : "LSB");
|
||||
noInterrupts(); // Avoid possible race conditions if IRQ comes in while main app is in middle of this
|
||||
DEBUGSPI("SPI::beginTransaction(clk=%lu, bo=%s)\n", settings.getClockFreq(), (settings.getBitOrder() == MSBFIRST) ? "MSB" : "LSB");
|
||||
if (_initted && settings == _spis) {
|
||||
DEBUGSPI("SPI: Reusing existing initted SPI\n");
|
||||
} else {
|
||||
@@ -188,12 +191,39 @@ void SPIClassRP2040::beginTransaction(SPISettings settings) {
|
||||
}
|
||||
DEBUGSPI("SPI: initting SPI\n");
|
||||
spi_init(_spi, _spis.getClockFreq());
|
||||
DEBUGSPI("SPI: actual baudrate=%u\n", spi_get_baudrate(_spi));
|
||||
_initted = true;
|
||||
}
|
||||
// Disable any IRQs that are being used for SPI
|
||||
io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl;
|
||||
DEBUGSPI("SPI: IRQ masks before = %08x %08x %08x %08x\n", (unsigned)irq_ctrl_base->inte[0], (unsigned)irq_ctrl_base->inte[1], (unsigned)irq_ctrl_base->inte[2], (unsigned)irq_ctrl_base->inte[3]);
|
||||
for (auto entry : _usingIRQs) {
|
||||
int gpio = entry.first;
|
||||
|
||||
// There is no gpio_get_irq, so manually twiddle the register
|
||||
io_rw_32 *en_reg = &irq_ctrl_base->inte[gpio / 8];
|
||||
uint32_t val = ((*en_reg) >> (4 * (gpio % 8))) & 0xf;
|
||||
_usingIRQs.insert_or_assign(gpio, val);
|
||||
DEBUGSPI("SPI: GPIO %d = %d\n", gpio, val);
|
||||
(*en_reg) ^= val << (4 * (gpio % 8));
|
||||
}
|
||||
DEBUGSPI("SPI: IRQ masks after = %08x %08x %08x %08x\n", (unsigned)irq_ctrl_base->inte[0], (unsigned)irq_ctrl_base->inte[1], (unsigned)irq_ctrl_base->inte[2], (unsigned)irq_ctrl_base->inte[3]);
|
||||
interrupts();
|
||||
}
|
||||
|
||||
void SPIClassRP2040::endTransaction(void) {
|
||||
noInterrupts(); // Avoid race condition so the GPIO IRQs won't come back until all state is restored
|
||||
DEBUGSPI("SPI::endTransaction()\n");
|
||||
// Re-enablke IRQs
|
||||
for (auto entry : _usingIRQs) {
|
||||
int gpio = entry.first;
|
||||
int mode = entry.second;
|
||||
gpio_set_irq_enabled(gpio, mode, true);
|
||||
}
|
||||
io_irq_ctrl_hw_t *irq_ctrl_base = get_core_num() ? &iobank0_hw->proc1_irq_ctrl : &iobank0_hw->proc0_irq_ctrl;
|
||||
(void) irq_ctrl_base;
|
||||
DEBUGSPI("SPI: IRQ masks = %08x %08x %08x %08x\n", (unsigned)irq_ctrl_base->inte[0], (unsigned)irq_ctrl_base->inte[1], (unsigned)irq_ctrl_base->inte[2], (unsigned)irq_ctrl_base->inte[3]);
|
||||
interrupts();
|
||||
}
|
||||
|
||||
bool SPIClassRP2040::setRX(pin_size_t pin) {
|
||||
@@ -205,6 +235,10 @@ bool SPIClassRP2040::setRX(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_RX == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set SPI%s.RX while running", spi_get_index(_spi) ? "1" : "");
|
||||
} else {
|
||||
@@ -222,6 +256,10 @@ bool SPIClassRP2040::setCS(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_CS == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set SPI%s.CS while running", spi_get_index(_spi) ? "1" : "");
|
||||
} else {
|
||||
@@ -239,6 +277,10 @@ bool SPIClassRP2040::setSCK(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_SCK == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set SPI%s.SCK while running", spi_get_index(_spi) ? "1" : "");
|
||||
} else {
|
||||
@@ -256,6 +298,10 @@ bool SPIClassRP2040::setTX(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_TX == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set SPI%s.TX while running", spi_get_index(_spi) ? "1" : "");
|
||||
} else {
|
||||
@@ -275,6 +321,7 @@ void SPIClassRP2040::begin(bool hwCS) {
|
||||
gpio_set_function(_TX, GPIO_FUNC_SPI);
|
||||
// Give a default config in case user doesn't use beginTransaction
|
||||
beginTransaction(_spis);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void SPIClassRP2040::end() {
|
||||
@@ -295,11 +342,13 @@ void SPIClassRP2040::end() {
|
||||
void SPIClassRP2040::setBitOrder(BitOrder order) {
|
||||
_spis = SPISettings(_spis.getClockFreq(), order, _spis.getDataMode());
|
||||
beginTransaction(_spis);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void SPIClassRP2040::setDataMode(uint8_t uc_mode) {
|
||||
_spis = SPISettings(_spis.getClockFreq(), _spis.getBitOrder(), uc_mode);
|
||||
beginTransaction(_spis);
|
||||
endTransaction();
|
||||
}
|
||||
|
||||
void SPIClassRP2040::setClockDivider(uint8_t uc_div) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <Arduino.h>
|
||||
#include <api/HardwareSPI.h>
|
||||
#include <hardware/spi.h>
|
||||
#include <map>
|
||||
|
||||
class SPIClassRP2040 : public arduino::HardwareSPI {
|
||||
public:
|
||||
@@ -60,12 +61,12 @@ public:
|
||||
void setDataMode(uint8_t uc_mode) __attribute__((deprecated));
|
||||
void setClockDivider(uint8_t uc_div) __attribute__((deprecated));
|
||||
|
||||
// Unimplemented
|
||||
// List of GPIO IRQs to disable during a transaction
|
||||
virtual void usingInterrupt(int interruptNumber) override {
|
||||
(void) interruptNumber;
|
||||
_usingIRQs.insert({interruptNumber, 0});
|
||||
}
|
||||
virtual void notUsingInterrupt(int interruptNumber) override {
|
||||
(void) interruptNumber;
|
||||
_usingIRQs.erase(interruptNumber);
|
||||
}
|
||||
virtual void attachInterrupt() override { /* noop */ }
|
||||
virtual void detachInterrupt() override { /* noop */ }
|
||||
@@ -83,6 +84,8 @@ private:
|
||||
bool _hwCS;
|
||||
bool _running; // SPI port active
|
||||
bool _initted; // Transaction begun
|
||||
|
||||
std::map<int, int> _usingIRQs;
|
||||
};
|
||||
|
||||
extern SPIClassRP2040 SPI;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// Shows how to use SPISlave on a single device.
|
||||
// Core0 runs as an SPI master and initiates a transmission to the slave
|
||||
// Core1 runs the SPI Slave mode and provides a unique reply to messages from the master
|
||||
//
|
||||
// Released to the public domain 2023 by Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
#include <SPI.h>
|
||||
#include <SPISlave.h>
|
||||
|
||||
// Wiring:
|
||||
// Master RX GP0 <-> GP11 Slave TX
|
||||
// Master CS GP1 <-> GP9 Slave CS
|
||||
// Master CK GP2 <-> GP10 Slave CK
|
||||
// Master TX GP3 <-> GP8 Slave RX
|
||||
|
||||
SPISettings spisettings(1000000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
// Core 0 will be SPI master
|
||||
void setup() {
|
||||
SPI.setRX(0);
|
||||
SPI.setCS(1);
|
||||
SPI.setSCK(2);
|
||||
SPI.setTX(3);
|
||||
SPI.begin(true);
|
||||
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
int transmits = 0;
|
||||
void loop() {
|
||||
char msg[42];
|
||||
memset(msg, 0, sizeof(msg));
|
||||
sprintf(msg, "What's up? This is transmission %d", transmits);
|
||||
Serial.printf("\n\nM-SEND: '%s'\n", msg);
|
||||
SPI.beginTransaction(spisettings);
|
||||
SPI.transfer(msg, sizeof(msg));
|
||||
SPI.endTransaction();
|
||||
Serial.printf("M-RECV: '%s'\n", msg);
|
||||
transmits++;
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
// Core 1 will be SPI slave
|
||||
|
||||
volatile bool recvBuffReady = false;
|
||||
char recvBuff[42] = "";
|
||||
int recvIdx = 0;
|
||||
void recvCallback(uint8_t *data, size_t len) {
|
||||
memcpy(recvBuff + recvIdx, data, len);
|
||||
recvIdx += len;
|
||||
if (recvIdx == sizeof(recvBuff)) {
|
||||
recvBuffReady = true;
|
||||
recvIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int sendcbs = 0;
|
||||
// Note that the buffer needs to be long lived, the SPISlave doesn't copy it. So no local stack variables, only globals or heap(malloc/new) allocations.
|
||||
char sendBuff[42];
|
||||
void sentCallback() {
|
||||
memset(sendBuff, 0, sizeof(sendBuff));
|
||||
sprintf(sendBuff, "Slave to Master Xmission %d", sendcbs++);
|
||||
SPISlave1.setData((uint8_t*)sendBuff, sizeof(sendBuff));
|
||||
}
|
||||
|
||||
// Note that we use SPISlave1 here **not** because we're running on
|
||||
// Core 1, but because SPI0 is being used already. You can use
|
||||
// SPISlave or SPISlave1 on any core.
|
||||
void setup1() {
|
||||
SPISlave1.setRX(8);
|
||||
SPISlave1.setCS(9);
|
||||
SPISlave1.setSCK(10);
|
||||
SPISlave1.setTX(11);
|
||||
// Ensure we start with something to send...
|
||||
sentCallback();
|
||||
// Hook our callbacks into the slave
|
||||
SPISlave1.onDataRecv(recvCallback);
|
||||
SPISlave1.onDataSent(sentCallback);
|
||||
SPISlave1.begin(spisettings);
|
||||
delay(3000);
|
||||
Serial.println("S-INFO: SPISlave started");
|
||||
}
|
||||
|
||||
void loop1() {
|
||||
if (recvBuffReady) {
|
||||
Serial.printf("S-RECV: '%s'\n", recvBuff);
|
||||
recvBuffReady = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map SPI
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Instances (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
SPISlave KEYWORD1
|
||||
SPISlave1 KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
begin KEYWORD2
|
||||
end KEYWORD2
|
||||
SPISettings KEYWORD2
|
||||
setRX KEYWORD2
|
||||
setTX KEYWORD2
|
||||
setSCK KEYWORD2
|
||||
setCS KEYWORD2
|
||||
setData KEYWORD2
|
||||
onDataRecv KEYWORD2
|
||||
onDataSent KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
SPI_CLOCK_DIV4 LITERAL1
|
||||
SPI_CLOCK_DIV16 LITERAL1
|
||||
SPI_CLOCK_DIV64 LITERAL1
|
||||
SPI_CLOCK_DIV128 LITERAL1
|
||||
SPI_CLOCK_DIV2 LITERAL1
|
||||
SPI_CLOCK_DIV8 LITERAL1
|
||||
SPI_CLOCK_DIV32 LITERAL1
|
||||
SPI_CLOCK_DIV64 LITERAL1
|
||||
SPI_MODE0 LITERAL1
|
||||
SPI_MODE1 LITERAL1
|
||||
SPI_MODE2 LITERAL1
|
||||
SPI_MODE3 LITERAL1
|
||||
@@ -0,0 +1,10 @@
|
||||
name=SPISlave
|
||||
version=1.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Enables the communication as a slave SPI devices.
|
||||
paragraph=
|
||||
category=Signal Input/Output
|
||||
url=https://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
SPI Slave library for the Raspberry Pi Pico RP2040
|
||||
|
||||
Copyright (c) 2023 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
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 "SPISlave.h"
|
||||
#include <hardware/spi.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/structs/iobank0.h>
|
||||
#include <hardware/irq.h>
|
||||
|
||||
#ifdef USE_TINYUSB
|
||||
// For Serial when selecting TinyUSB. Can't include in the core because Arduino IDE
|
||||
// will not link in libraries called from the core. Instead, add the header to all
|
||||
// the standard libraries in the hope it will still catch some user cases where they
|
||||
// use these libraries.
|
||||
// See https://github.com/earlephilhower/arduino-pico/issues/167#issuecomment-848622174
|
||||
#include <Adafruit_TinyUSB.h>
|
||||
#endif
|
||||
|
||||
SPISlaveClass::SPISlaveClass(spi_inst_t *spi, pin_size_t rx, pin_size_t cs, pin_size_t sck, pin_size_t tx) {
|
||||
_spi = spi;
|
||||
_running = false;
|
||||
_initted = false;
|
||||
_spis = SPISettings();
|
||||
_RX = rx;
|
||||
_TX = tx;
|
||||
_SCK = sck;
|
||||
_CS = cs;
|
||||
_recvCB = nullptr;
|
||||
_sentCB = nullptr;
|
||||
_dataOut = nullptr;
|
||||
_dataLeft = 0;
|
||||
}
|
||||
|
||||
inline spi_cpol_t SPISlaveClass::cpol(SPISettings _spis) {
|
||||
switch (_spis.getDataMode()) {
|
||||
case SPI_MODE0:
|
||||
return SPI_CPOL_0;
|
||||
case SPI_MODE1:
|
||||
return SPI_CPOL_0;
|
||||
case SPI_MODE2:
|
||||
return SPI_CPOL_1;
|
||||
case SPI_MODE3:
|
||||
return SPI_CPOL_1;
|
||||
}
|
||||
// Error
|
||||
return SPI_CPOL_0;
|
||||
}
|
||||
|
||||
inline spi_cpha_t SPISlaveClass::cpha(SPISettings _spis) {
|
||||
switch (_spis.getDataMode()) {
|
||||
case SPI_MODE0:
|
||||
return SPI_CPHA_0;
|
||||
case SPI_MODE1:
|
||||
return SPI_CPHA_1;
|
||||
case SPI_MODE2:
|
||||
return SPI_CPHA_0;
|
||||
case SPI_MODE3:
|
||||
return SPI_CPHA_1;
|
||||
}
|
||||
// Error
|
||||
return SPI_CPHA_0;
|
||||
}
|
||||
|
||||
bool SPISlaveClass::setRX(pin_size_t pin) {
|
||||
constexpr uint32_t valid[2] = { __bitset({0, 4, 16, 20}) /* SPI0 */,
|
||||
__bitset({8, 12, 24, 28}) /* SPI1 */
|
||||
};
|
||||
if ((!_running) && ((1 << pin) & valid[spi_get_index(_spi)])) {
|
||||
_RX = pin;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_RX == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set SPI%s.RX while running", spi_get_index(_spi) ? "1" : "");
|
||||
} else {
|
||||
panic("FATAL: Attempting to set SPI%s.RX to illegal pin %d", spi_get_index(_spi) ? "1" : "", pin);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SPISlaveClass::setCS(pin_size_t pin) {
|
||||
constexpr uint32_t valid[2] = { __bitset({1, 5, 17, 21}) /* SPI0 */,
|
||||
__bitset({9, 13, 25, 29}) /* SPI1 */
|
||||
};
|
||||
if ((!_running) && ((1 << pin) & valid[spi_get_index(_spi)])) {
|
||||
_CS = pin;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_CS == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set SPI%s.CS while running", spi_get_index(_spi) ? "1" : "");
|
||||
} else {
|
||||
panic("FATAL: Attempting to set SPI%s.CS to illegal pin %d", spi_get_index(_spi) ? "1" : "", pin);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SPISlaveClass::setSCK(pin_size_t pin) {
|
||||
constexpr uint32_t valid[2] = { __bitset({2, 6, 18, 22}) /* SPI0 */,
|
||||
__bitset({10, 14, 26}) /* SPI1 */
|
||||
};
|
||||
if ((!_running) && ((1 << pin) & valid[spi_get_index(_spi)])) {
|
||||
_SCK = pin;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_SCK == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set SPI%s.SCK while running", spi_get_index(_spi) ? "1" : "");
|
||||
} else {
|
||||
panic("FATAL: Attempting to set SPI%s.SCK to illegal pin %d", spi_get_index(_spi) ? "1" : "", pin);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SPISlaveClass::setTX(pin_size_t pin) {
|
||||
constexpr uint32_t valid[2] = { __bitset({3, 7, 19, 23}) /* SPI0 */,
|
||||
__bitset({11, 15, 27}) /* SPI1 */
|
||||
};
|
||||
if ((!_running) && ((1 << pin) & valid[spi_get_index(_spi)])) {
|
||||
_TX = pin;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_TX == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set SPI%s.TX while running", spi_get_index(_spi) ? "1" : "");
|
||||
} else {
|
||||
panic("FATAL: Attempting to set SPI%s.TX to illegal pin %d", spi_get_index(_spi) ? "1" : "", pin);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SPISlaveClass::_handleIRQ() {
|
||||
// Attempt to read out all RX FIFO datra and return to callback
|
||||
uint8_t buff[8]; // SPI FIFO 8 deep max
|
||||
int cnt;
|
||||
for (cnt = 0; (cnt < 8) && spi_is_readable(_spi); cnt++) {
|
||||
buff[cnt] = spi_get_hw(_spi)->dr;
|
||||
}
|
||||
if (cnt && _recvCB) {
|
||||
_recvCB(buff, cnt);
|
||||
}
|
||||
// Attempt to send as many ytes to the TX FIFO as we have/are free
|
||||
while (spi_is_writable(_spi)) {
|
||||
for (; _dataLeft && spi_is_writable(_spi); _dataLeft--) {
|
||||
spi_get_hw(_spi)->dr = *(_dataOut++);
|
||||
}
|
||||
if (!_dataLeft && _sentCB) {
|
||||
_sentCB();
|
||||
}
|
||||
}
|
||||
// Disable the TX FIFO IRQ if there is still no data to send or we'd always be stuck in an IRQ
|
||||
// Will be re-enabled once user does a setData
|
||||
if (!_dataLeft) {
|
||||
spi_get_hw(_spi)->imsc = 2 | 4; // RTIM + RXIM
|
||||
}
|
||||
}
|
||||
|
||||
void SPISlaveClass::_irq0() {
|
||||
SPISlave._handleIRQ();
|
||||
}
|
||||
|
||||
void SPISlaveClass::_irq1() {
|
||||
SPISlave1._handleIRQ();
|
||||
}
|
||||
|
||||
void SPISlaveClass::setData(const uint8_t *data, size_t len) {
|
||||
_dataOut = data;
|
||||
_dataLeft = len;
|
||||
if (_initted) {
|
||||
spi_get_hw(_spi)->imsc = 2 | 4 | 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SPISlaveClass::begin(SPISettings spis) {
|
||||
DEBUGSPI("SPISlave::begin(%d), rx=%d, cs=%d, sck=%d, tx=%d\n", hwCS, _RX, _CS, _SCK, _TX);
|
||||
gpio_set_function(_RX, GPIO_FUNC_SPI);
|
||||
gpio_set_function(_CS, GPIO_FUNC_SPI);
|
||||
gpio_set_function(_SCK, GPIO_FUNC_SPI);
|
||||
gpio_set_function(_TX, GPIO_FUNC_SPI);
|
||||
if (_initted) {
|
||||
DEBUGSPI("SPISlave: deinitting currently active SPI\n");
|
||||
spi_deinit(_spi);
|
||||
}
|
||||
DEBUGSPI("SPISlave: initting SPI\n");
|
||||
spi_init(_spi, _spis.getClockFreq());
|
||||
DEBUGSPI("SPISlave: actual baudrate=%u\n", spi_get_baudrate(_spi));
|
||||
spi_set_slave(_spi, true);
|
||||
spi_set_format(_spi, 8, cpol(spis), cpha(spis), SPI_MSB_FIRST);
|
||||
|
||||
// Install our IRQ handler
|
||||
if (_spi == spi0) {
|
||||
irq_set_exclusive_handler(SPI0_IRQ, _irq0);
|
||||
} else {
|
||||
irq_set_exclusive_handler(SPI1_IRQ, _irq1);
|
||||
}
|
||||
|
||||
// Set to get IRQs on transmit and receive
|
||||
spi_get_hw(_spi)->imsc = 2 | 4 | 8 ; // RTIM + RXIM + TXIM (not RORIM)
|
||||
_initted = true;
|
||||
irq_set_enabled(_spi == spi0 ? SPI0_IRQ : SPI1_IRQ, true);
|
||||
}
|
||||
|
||||
void SPISlaveClass::end() {
|
||||
DEBUGSPI("SPISlave::end()\n");
|
||||
if (_initted) {
|
||||
DEBUGSPI("SPISlave: deinitting currently active SPI\n");
|
||||
if (_spi == spi0) {
|
||||
irq_remove_handler(SPI0_IRQ, _irq0);
|
||||
} else {
|
||||
irq_remove_handler(SPI1_IRQ, _irq1);
|
||||
}
|
||||
spi_deinit(_spi);
|
||||
_initted = false;
|
||||
}
|
||||
gpio_set_function(_RX, GPIO_FUNC_SIO);
|
||||
gpio_set_function(_CS, GPIO_FUNC_SIO);
|
||||
gpio_set_function(_SCK, GPIO_FUNC_SIO);
|
||||
gpio_set_function(_TX, GPIO_FUNC_SIO);
|
||||
}
|
||||
|
||||
#ifndef __SPI0_DEVICE
|
||||
#define __SPI0_DEVICE spi0
|
||||
#endif
|
||||
#ifndef __SPI1_DEVICE
|
||||
#define __SPI1_DEVICE spi1
|
||||
#endif
|
||||
|
||||
SPISlaveClass SPISlave(__SPI0_DEVICE, PIN_SPI0_MISO, PIN_SPI0_SS, PIN_SPI0_SCK, PIN_SPI0_MOSI);
|
||||
SPISlaveClass SPISlave1(__SPI1_DEVICE, PIN_SPI1_MISO, PIN_SPI1_SS, PIN_SPI1_SCK, PIN_SPI1_MOSI);
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
SPI Slave library for the Raspberry Pi Pico RP2040
|
||||
|
||||
Copyright (c) 2023 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <api/HardwareSPI.h>
|
||||
#include <hardware/spi.h>
|
||||
#include <functional>
|
||||
|
||||
typedef std::function<void(uint8_t *data, size_t len)> SPISlaveRecvHandler;
|
||||
typedef std::function<void(void)> SPISlaveSentHandler;
|
||||
|
||||
class SPISlaveClass {
|
||||
public:
|
||||
SPISlaveClass(spi_inst_t *spi, pin_size_t rx, pin_size_t cs, pin_size_t sck, pin_size_t tx);
|
||||
|
||||
// Assign pins, call before begin()
|
||||
bool setRX(pin_size_t pin);
|
||||
bool setCS(pin_size_t pin);
|
||||
bool setSCK(pin_size_t pin);
|
||||
bool setTX(pin_size_t pin);
|
||||
|
||||
void begin(SPISettings spis);
|
||||
void end();
|
||||
|
||||
// May be set before the initial begin(). If not, then as soon as
|
||||
// begin() is called you will get a callback.
|
||||
void setData(const uint8_t * data, size_t len);
|
||||
inline void setData(const char * data) {
|
||||
setData((const uint8_t *)data, strlen(data));
|
||||
}
|
||||
|
||||
// NOTE: These two callbacks are called from an IRQ context
|
||||
// NOTE: They should also be called before begin()
|
||||
|
||||
// Called when bytes are available to be read from the SPI slave reception buffer
|
||||
void onDataRecv(SPISlaveRecvHandler cb) {
|
||||
_recvCB = cb;
|
||||
}
|
||||
// Called when there is space in the SPI transmission buffer
|
||||
void onDataSent(SPISlaveSentHandler cb) {
|
||||
_sentCB = cb;
|
||||
}
|
||||
|
||||
private:
|
||||
// Naked IRQ callbacks, will thunk to real object ones below
|
||||
static void _irq0();
|
||||
static void _irq1();
|
||||
|
||||
public:
|
||||
void _handleIRQ();
|
||||
|
||||
private:
|
||||
spi_cpol_t cpol(SPISettings _spis);
|
||||
spi_cpha_t cpha(SPISettings _spis);
|
||||
uint8_t reverseByte(uint8_t b);
|
||||
uint16_t reverse16Bit(uint16_t w);
|
||||
void adjustBuffer(const void *s, void *d, size_t cnt, bool by16);
|
||||
|
||||
spi_inst_t *_spi;
|
||||
SPISettings _spis;
|
||||
pin_size_t _RX, _TX, _SCK, _CS;
|
||||
bool _running; // SPI port active
|
||||
bool _initted; // Transaction begun
|
||||
|
||||
SPISlaveRecvHandler _recvCB;
|
||||
SPISlaveSentHandler _sentCB;
|
||||
|
||||
// The current data to be pumped into the transmit FIFO
|
||||
const uint8_t *_dataOut;
|
||||
size_t _dataLeft;
|
||||
|
||||
// Received data will be returned in small chunks directly from a local buffer in _handleIRQ()
|
||||
};
|
||||
|
||||
extern SPISlaveClass SPISlave;
|
||||
extern SPISlaveClass SPISlave1;
|
||||
@@ -123,6 +123,7 @@ bool UpdaterClass::begin(size_t size, int command) {
|
||||
|
||||
//initialize
|
||||
_startAddress = updateStartAddress;
|
||||
_currentAddress = _startAddress; // Only used in the FS upload case
|
||||
_size = size;
|
||||
_bufferSize = 4096;
|
||||
_buffer = new uint8_t[_bufferSize];
|
||||
|
||||
@@ -28,7 +28,17 @@
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Example works with either Wired or WiFi Ethernet, define one of these values to 1, other to 0
|
||||
#define USE_WIFI 1
|
||||
#define USE_WIRED 0
|
||||
|
||||
#if USE_WIFI
|
||||
#include <WiFi.h>
|
||||
#elif USE_WIRED
|
||||
#include <W5500lwIP.h> // Or W5100lwIP.h or ENC28J60lwIP.h
|
||||
Wiznet5500lwIP eth(1 /* chip select */); // or Wiznet5100lwIP or ENC28J60lwIP
|
||||
#endif
|
||||
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <LEAmDNS.h>
|
||||
@@ -58,13 +68,13 @@ void handleRoot() {
|
||||
temp.printf("<html>\
|
||||
<head>\
|
||||
<meta http-equiv='refresh' content='5'/>\
|
||||
<title>Pico-W Demo</title>\
|
||||
<title>" BOARD_NAME " Demo</title>\
|
||||
<style>\
|
||||
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
||||
</style>\
|
||||
</head>\
|
||||
<body>\
|
||||
<h1>Hello from the Pico W!</h1>\
|
||||
<h1>Hello from the " BOARD_NAME "!</h1>\
|
||||
<p>Uptime: %02d:%02d:%02d</p>\
|
||||
<p>Free Memory: %d</p>\
|
||||
<p>Page Count: %d</p>\
|
||||
@@ -117,6 +127,8 @@ void setup(void) {
|
||||
pinMode(led, OUTPUT);
|
||||
digitalWrite(led, 0);
|
||||
Serial.begin(115200);
|
||||
|
||||
#if USE_WIFI
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
@@ -130,8 +142,32 @@ void setup(void) {
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
#elif USE_WIRED
|
||||
// Set up SPI pinout to match your HW
|
||||
SPI.setRX(0);
|
||||
SPI.setCS(1);
|
||||
SPI.setSCK(2);
|
||||
SPI.setTX(3);
|
||||
|
||||
// Start the Ethernet port
|
||||
if (!eth.begin()) {
|
||||
Serial.println("No wired Ethernet hardware detected. Check pinouts, wiring.");
|
||||
while (1) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for connection
|
||||
while (eth.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(eth.localIP());
|
||||
#endif
|
||||
|
||||
if (MDNS.begin("picow")) {
|
||||
Serial.println("MDNS responder started");
|
||||
|
||||
@@ -61,7 +61,7 @@ void loop() {
|
||||
// This will send a string to the server
|
||||
Serial.println("sending data to server");
|
||||
if (client.connected()) {
|
||||
client.println("hello from ESP8266");
|
||||
client.println("hello from RP2040");
|
||||
}
|
||||
|
||||
// wait for data to be available
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For WiFiNINA
|
||||
# Syntax Coloring Map For WiFi
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
@@ -7,7 +7,6 @@
|
||||
#######################################
|
||||
|
||||
WiFi KEYWORD1
|
||||
WiFiPico KEYWORD1
|
||||
WiFiUdp KEYWORD1
|
||||
WiFiClient KEYWORD1
|
||||
WiFiSSLClient KEYWORD1
|
||||
@@ -51,6 +50,7 @@ remoteIP KEYWORD2
|
||||
remotePort KEYWORD2
|
||||
mode KEYWORD2
|
||||
addAP KEYWORD2
|
||||
hostByName KEYWORD2
|
||||
|
||||
beginAP KEYWORD2
|
||||
beginEnterprise KEYWORD2
|
||||
|
||||
@@ -9,4 +9,4 @@ url=http://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
includes=WiFi.h
|
||||
dot_a_linkage=true
|
||||
depends=MD5Header
|
||||
depends=MD5Header,Updater,LWIPEthernet
|
||||
|
||||
@@ -583,9 +583,9 @@ uint8_t WiFiClass::reasonCode() {
|
||||
@return 1 if aIPAddrString was successfully converted to an IP address,
|
||||
else 0
|
||||
*/
|
||||
|
||||
// Note that there is now a global FCN for name lookup to use all Ethernet ports, no need to call WiFi.hostByName, just ::hostByName
|
||||
int WiFiClass::hostByName(const char* aHostname, IPAddress& aResult, int timeout_ms) {
|
||||
return _wifi.hostByName(aHostname, aResult, timeout_ms);
|
||||
return ::hostByName(aHostname, aResult, timeout_ms);
|
||||
}
|
||||
|
||||
// TODO
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <lwIP_CYW43.h>
|
||||
#include "WiFi.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
@@ -113,7 +113,7 @@ WiFiClient& WiFiClient::operator=(const WiFiClient& other) {
|
||||
|
||||
int WiFiClient::connect(const char* host, uint16_t port) {
|
||||
IPAddress remote_addr;
|
||||
if (WiFi.hostByName(host, remote_addr, _timeout)) {
|
||||
if (::hostByName(host, remote_addr, _timeout)) {
|
||||
return connect(remote_addr, port);
|
||||
}
|
||||
return 0;
|
||||
@@ -360,7 +360,6 @@ void WiFiClient::stopAllExcept(WiFiClient* except) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WiFiClient::keepAlive(uint16_t idle_sec, uint16_t intv_sec, uint8_t count) {
|
||||
_client->keepAlive(idle_sec, intv_sec, count);
|
||||
}
|
||||
@@ -380,28 +379,3 @@ uint16_t WiFiClient::getKeepAliveInterval() const {
|
||||
uint8_t WiFiClient::getKeepAliveCount() const {
|
||||
return _client->getKeepAliveCount();
|
||||
}
|
||||
|
||||
//bool WiFiClient::hasPeekBufferAPI () const
|
||||
//{
|
||||
// return true;
|
||||
//}
|
||||
|
||||
// return a pointer to available data buffer (size = peekAvailable())
|
||||
// semantic forbids any kind of read() before calling peekConsume()
|
||||
//const char* WiFiClient::peekBuffer ()
|
||||
//{
|
||||
// return _client? _client->peekBuffer(): nullptr;
|
||||
//}
|
||||
|
||||
// return number of byte accessible by peekBuffer()
|
||||
//size_t WiFiClient::peekAvailable ()
|
||||
//{
|
||||
// return _client? _client->peekAvailable(): 0;
|
||||
//}
|
||||
|
||||
// consume bytes after use (see peekBuffer)
|
||||
//void WiFiClient::peekConsume (size_t consume)
|
||||
//{
|
||||
// if (_client)
|
||||
// _client->peekConsume(consume);
|
||||
//}
|
||||
|
||||
@@ -240,7 +240,7 @@ int WiFiClientSecureCtx::connect(IPAddress ip, uint16_t port) {
|
||||
|
||||
int WiFiClientSecureCtx::connect(const char* name, uint16_t port) {
|
||||
IPAddress remote_addr;
|
||||
if (!WiFi.hostByName(name, remote_addr)) {
|
||||
if (!::hostByName(name, remote_addr)) {
|
||||
DEBUG_BSSL("connect: Name lookup failure\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1457,7 +1457,7 @@ int WiFiClientSecureCtx::getLastSSLError(char *dest, size_t len) {
|
||||
|
||||
bool WiFiClientSecure::probeMaxFragmentLength(const char* name, uint16_t port, uint16_t len) {
|
||||
IPAddress remote_addr;
|
||||
if (!WiFi.hostByName(name, remote_addr)) {
|
||||
if (!::hostByName(name, remote_addr)) {
|
||||
DEBUG_BSSL("probeMaxFragmentLength: Can't resolve host\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include "WiFiClient.h"
|
||||
#include <LwipEthernet.h>
|
||||
#include <bearssl/bearssl.h>
|
||||
#include "BearSSLHelpers.h"
|
||||
#include "CertStoreBearSSL.h"
|
||||
@@ -247,7 +248,7 @@ public:
|
||||
}
|
||||
int connect(const char *host, uint16_t port, const char *rootCABuff, const char *cli_cert, const char *cli_key) {
|
||||
IPAddress ip;
|
||||
if (WiFi.hostByName(host, ip, _timeout)) {
|
||||
if (::hostByName(host, ip, _timeout)) {
|
||||
return connect(ip, port, rootCABuff, cli_cert, cli_key);
|
||||
} else {
|
||||
return 0;
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
|
||||
void begin(const char *server, int timeout = 3600) {
|
||||
IPAddress addr;
|
||||
if (WiFi.hostByName(server, addr)) {
|
||||
if (::hostByName(server, addr)) {
|
||||
begin(addr, timeout);
|
||||
}
|
||||
_running = true;
|
||||
@@ -66,8 +66,8 @@ public:
|
||||
|
||||
void begin(const char *s1, const char *s2, int timeout = 3600) {
|
||||
IPAddress a1, a2;
|
||||
if (WiFi.hostByName(s1, a1)) {
|
||||
if (WiFi.hostByName(s2, a2)) {
|
||||
if (::hostByName(s1, a1)) {
|
||||
if (::hostByName(s2, a2)) {
|
||||
begin(a1, a2, timeout);
|
||||
} else {
|
||||
begin(a1, timeout);
|
||||
|
||||
@@ -133,7 +133,7 @@ void WiFiUDP::stop() {
|
||||
|
||||
int WiFiUDP::beginPacket(const char *host, uint16_t port) {
|
||||
IPAddress remote_addr;
|
||||
if (WiFi.hostByName(host, remote_addr)) {
|
||||
if (::hostByName(host, remote_addr)) {
|
||||
return beginPacket(remote_addr, port);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -37,9 +37,9 @@ bool getDefaultPrivateGlobalSyncValue();
|
||||
template <typename T>
|
||||
inline void esp_delay(const uint32_t timeout_ms, T&& blocked, const uint32_t intvl_ms) {
|
||||
const auto start_ms = millis();
|
||||
(void) intvl_ms;
|
||||
while ((((uint32_t)millis() - start_ms) < timeout_ms) && blocked()) {
|
||||
sys_check_timeouts();
|
||||
delay(intvl_ms);
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,9 +611,10 @@ protected:
|
||||
_rx_buf_offset += size;
|
||||
} else if (!_rx_buf->next) {
|
||||
DEBUGV(":c0 %d, %d\r\n", size, _rx_buf->tot_len);
|
||||
pbuf_free(_rx_buf);
|
||||
auto head = _rx_buf;
|
||||
_rx_buf = 0;
|
||||
_rx_buf_offset = 0;
|
||||
pbuf_free(head);
|
||||
} else {
|
||||
DEBUGV(":c %d, %d, %d\r\n", size, _rx_buf->len, _rx_buf->tot_len);
|
||||
auto head = _rx_buf;
|
||||
|
||||
@@ -56,6 +56,10 @@ bool TwoWire::setSDA(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_sda == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set Wire%s.SDA while running", i2c_hw_index(_i2c) ? "1" : "");
|
||||
} else {
|
||||
@@ -73,6 +77,10 @@ bool TwoWire::setSCL(pin_size_t pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_scl == pin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_running) {
|
||||
panic("FATAL: Attempting to set Wire%s.SCL while running", i2c_hw_index(_i2c) ? "1" : "");
|
||||
} else {
|
||||
@@ -108,11 +116,27 @@ void TwoWire::begin() {
|
||||
}
|
||||
|
||||
static void _handler0() {
|
||||
#if defined(__WIRE0_DEVICE)
|
||||
if (__WIRE0_DEVICE == i2c0) {
|
||||
Wire.onIRQ();
|
||||
} else {
|
||||
Wire1.onIRQ();
|
||||
}
|
||||
#else
|
||||
Wire.onIRQ();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _handler1() {
|
||||
#if defined(__WIRE1_DEVICE)
|
||||
if (__WIRE1_DEVICE == i2c0) {
|
||||
Wire.onIRQ();
|
||||
} else {
|
||||
Wire1.onIRQ();
|
||||
}
|
||||
#else
|
||||
Wire1.onIRQ();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Slave mode
|
||||
|
||||
@@ -66,6 +66,27 @@ public:
|
||||
*/
|
||||
uint16_t readFrame(uint8_t* buffer, uint16_t bufsize);
|
||||
|
||||
// ------------- Dummy handler, actually run in the SDK async context -------------
|
||||
void handlePackets() {
|
||||
}
|
||||
|
||||
// ------------- Dummy handler for linkage, but never called at runtime -------------
|
||||
uint16_t readFrameSize() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ------------- Dummy handler for linkage, but never called at runtime -------------
|
||||
void discardFrame(uint16_t ign) {
|
||||
(void) ign;
|
||||
}
|
||||
|
||||
// ------------- Dummy handler for linkage, but never called at runtime -------------
|
||||
uint16_t readFrameData(uint8_t *ign1, uint16_t ign2) {
|
||||
(void) ign1;
|
||||
(void) ign2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool interruptIsPossible() {
|
||||
return true;
|
||||
}
|
||||
@@ -94,6 +115,10 @@ public:
|
||||
_timeout = timeout;
|
||||
}
|
||||
|
||||
constexpr bool needsSPI() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// LWIP netif for the IRQ packet processing
|
||||
static netif *_netif;
|
||||
protected:
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Library (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
lwipPollingPeriod KEYWORD2
|
||||
setSPISpeed KEYWORD2
|
||||
setSPISettings KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -6,5 +6,6 @@ sentence=Helper for ethernet drivers
|
||||
paragraph=Example repository for Ethernet drivers
|
||||
category=Communication
|
||||
url=https://github.com/esp8266/Arduino
|
||||
architectures=esp8266,rp2040
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
depends=WiFi,Updater
|
||||
|
||||
@@ -1,14 +1,179 @@
|
||||
/*
|
||||
LwipEthernet.cpp
|
||||
|
||||
Handles the async context for wired Ethernet
|
||||
|
||||
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 <LwipEthernet.h>
|
||||
//#include <SPI.h>
|
||||
#include <lwip/timeouts.h>
|
||||
#include <lwip/dns.h>
|
||||
#include <pico/mutex.h>
|
||||
#include <pico/cyw43_arch.h>
|
||||
#include <pico/async_context_threadsafe_background.h>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
//#ifndef ETHERNET_SPI_CLOCK_DIV
|
||||
//#define ETHERNET_SPI_CLOCK_DIV SPI_CLOCK_DIV4 // 4MHz (SPI.h)
|
||||
//#endif
|
||||
bool __ethernetContextInitted = false;
|
||||
|
||||
void SPI4EthInit() {
|
||||
//SPI.begin();
|
||||
// SPI.setClockDivider(ETHERNET_SPI_CLOCK_DIV);
|
||||
// SPI.setBitOrder(MSBFIRST);
|
||||
// SPI.setDataMode(SPI_MODE0);
|
||||
// Async context that pumps the ethernet controllers
|
||||
static async_context_threadsafe_background_t lwip_ethernet_async_context_threadsafe_background;
|
||||
static async_when_pending_worker_t always_pending_update_timeout_worker;
|
||||
static async_at_time_worker_t ethernet_timeout_worker;
|
||||
|
||||
static async_context_t *_context = nullptr;
|
||||
|
||||
// Theoretically support multiple interfaces
|
||||
static std::map<int, std::function<void(void)>> _handlePacketList;
|
||||
|
||||
void ethernet_arch_lwip_begin() {
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
if (rp2040.isPicoW()) {
|
||||
cyw43_arch_lwip_begin();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
async_context_acquire_lock_blocking(&lwip_ethernet_async_context_threadsafe_background.core);
|
||||
}
|
||||
|
||||
void ethernet_arch_lwip_end() {
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
if (rp2040.isPicoW()) {
|
||||
cyw43_arch_lwip_end();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
async_context_release_lock(&lwip_ethernet_async_context_threadsafe_background.core);
|
||||
}
|
||||
|
||||
int __addEthernetPacketHandler(std::function<void(void)> _packetHandler) {
|
||||
static int id = 0xdead;
|
||||
ethernet_arch_lwip_begin();
|
||||
_handlePacketList.insert({id, _packetHandler});
|
||||
ethernet_arch_lwip_end();
|
||||
return id++;
|
||||
}
|
||||
|
||||
void __removeEthernetPacketHandler(int id) {
|
||||
ethernet_arch_lwip_begin();
|
||||
_handlePacketList.erase(id);
|
||||
ethernet_arch_lwip_end();
|
||||
}
|
||||
|
||||
static volatile bool _dns_lookup_pending = false;
|
||||
|
||||
static void _dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) {
|
||||
(void) name;
|
||||
if (!_dns_lookup_pending) {
|
||||
return;
|
||||
}
|
||||
if (ipaddr) {
|
||||
*(IPAddress *)callback_arg = IPAddress(ipaddr);
|
||||
}
|
||||
_dns_lookup_pending = false; // resume hostByName
|
||||
}
|
||||
|
||||
int hostByName(const char* aHostname, IPAddress& aResult, int timeout_ms) {
|
||||
ip_addr_t addr;
|
||||
aResult = static_cast<uint32_t>(0xffffffff);
|
||||
|
||||
if (aResult.fromString(aHostname)) {
|
||||
// Host name is a IP address use it!
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if LWIP_IPV4 && LWIP_IPV6
|
||||
err_t err = dns_gethostbyname_addrtype(aHostname, &addr, &_dns_found_callback, &aResult, LWIP_DNS_ADDRTYPE_DEFAULT);
|
||||
#else
|
||||
err_t err = dns_gethostbyname(aHostname, &addr, &_dns_found_callback, &aResult);
|
||||
#endif
|
||||
if (err == ERR_OK) {
|
||||
aResult = IPAddress(&addr);
|
||||
} else if (err == ERR_INPROGRESS) {
|
||||
_dns_lookup_pending = true;
|
||||
uint32_t now = millis();
|
||||
while ((millis() - now < (uint32_t)timeout_ms) && _dns_lookup_pending) {
|
||||
delay(1);
|
||||
}
|
||||
_dns_lookup_pending = false;
|
||||
if (aResult.isSet()) {
|
||||
err = ERR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (err == ERR_OK) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static async_context_t *lwip_ethernet_init_default_async_context(void) {
|
||||
async_context_threadsafe_background_config_t config = async_context_threadsafe_background_default_config();
|
||||
if (async_context_threadsafe_background_init(&lwip_ethernet_async_context_threadsafe_background, &config)) {
|
||||
return &lwip_ethernet_async_context_threadsafe_background.core;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// This will only be called under the protection of the async context mutex, so no re-entrancy checks needed
|
||||
static void ethernet_timeout_reached(__unused async_context_t *context, __unused async_at_time_worker_t *worker) {
|
||||
assert(worker == ðernet_timeout_worker);
|
||||
for (auto handlePacket : _handlePacketList) {
|
||||
handlePacket.second();
|
||||
}
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
if (!rp2040.isPicoW()) {
|
||||
sys_check_timeouts();
|
||||
}
|
||||
#else
|
||||
sys_check_timeouts();
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t _pollingPeriod = 20;
|
||||
static void update_next_timeout(async_context_t *context, async_when_pending_worker_t *worker) {
|
||||
assert(worker == &always_pending_update_timeout_worker);
|
||||
worker->work_pending = true;
|
||||
async_context_add_at_time_worker_in_ms(context, ðernet_timeout_worker, _pollingPeriod);
|
||||
}
|
||||
|
||||
void __startEthernetContext() {
|
||||
if (__ethernetContextInitted) {
|
||||
return;
|
||||
}
|
||||
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
|
||||
if (rp2040.isPicoW()) {
|
||||
_context = cyw43_arch_async_context();
|
||||
} else {
|
||||
_context = lwip_ethernet_init_default_async_context();
|
||||
}
|
||||
#else
|
||||
_context = lwip_ethernet_init_default_async_context();
|
||||
#endif
|
||||
always_pending_update_timeout_worker.work_pending = true;
|
||||
always_pending_update_timeout_worker.do_work = update_next_timeout;
|
||||
ethernet_timeout_worker.do_work = ethernet_timeout_reached;
|
||||
async_context_add_when_pending_worker(_context, &always_pending_update_timeout_worker);
|
||||
__ethernetContextInitted = true;
|
||||
}
|
||||
|
||||
void lwipPollingPeriod(int ms) {
|
||||
if (ms > 0) {
|
||||
// No need for mutexes, this is an atomic 32b write
|
||||
_pollingPeriod = ms;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,53 +20,22 @@
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//#include <ESP8266WiFi.h> // tcp API
|
||||
//#include <debug.h>
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
#include <lwIP_CYW43.h>
|
||||
//#include <W5100lwIP.h>
|
||||
//#include <W5500lwIP.h>
|
||||
//#include <ENC28J60lwIP.h>
|
||||
void ethernet_arch_lwip_begin() __attribute__((weak));
|
||||
void ethernet_arch_lwip_end() __attribute__((weak));
|
||||
|
||||
// One of them is to be declared in the main sketch
|
||||
// and passed to ethInitDHCP() or ethInitStatic():
|
||||
// Wiznet5500lwIP eth(CSPIN);
|
||||
// Wiznet5100lwIP eth(CSPIN);
|
||||
// ENC28J60lwIP eth(CSPIN);
|
||||
// Internal Ethernet helper functions
|
||||
void __startEthernetContext();
|
||||
|
||||
void SPI4EthInit();
|
||||
int __addEthernetPacketHandler(std::function<void(void)> _packetHandler);
|
||||
void __removeEthernetPacketHandler(int id);
|
||||
|
||||
template<class EthImpl>
|
||||
bool ethInitDHCP(EthImpl& eth) {
|
||||
SPI4EthInit();
|
||||
// Used by WiFiClient to get DNS lookup
|
||||
int hostByName(const char *aHostname, IPAddress &aResult, int timeout_ms = 5000);
|
||||
|
||||
if (!eth.begin()) {
|
||||
// hardware not responding
|
||||
// DEBUGV("ethInitDHCP: hardware not responding\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class EthImpl>
|
||||
bool ethInitStatic(EthImpl& eth, IPAddress IP, IPAddress gateway, IPAddress netmask, IPAddress dns1,
|
||||
IPAddress dns2 = IPADDR_NONE) {
|
||||
SPI4EthInit();
|
||||
|
||||
if (!eth.config(IP, gateway, netmask, dns1, dns2)) {
|
||||
// invalid arguments
|
||||
// DEBUGV("ethInitStatic: invalid arguments\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eth.begin()) {
|
||||
// hardware not responding
|
||||
// DEBUGV("ethInitStatic: hardware not responding\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// Set the LWIP polling time (default 50ms). Lower polling times == lower latency but higher CPU usage
|
||||
void lwipPollingPeriod(int ms);
|
||||
|
||||
@@ -40,24 +40,24 @@
|
||||
#include <lwip/inet_chksum.h>
|
||||
#include <lwip/apps/sntp.h>
|
||||
|
||||
//#include <user_interface.h> // wifi_get_macaddr()
|
||||
|
||||
#include "SPI.h"
|
||||
//#include "Schedule.h"
|
||||
#include "LwipIntf.h"
|
||||
#include "LwipEthernet.h"
|
||||
#include "wl_definitions.h"
|
||||
|
||||
#ifndef DEFAULT_MTU
|
||||
#define DEFAULT_MTU 1500
|
||||
#endif
|
||||
|
||||
|
||||
extern "C" void cyw43_hal_generate_laa_mac(__unused int idx, uint8_t buf[6]);
|
||||
|
||||
template<class RawDev>
|
||||
class LwipIntfDev: public LwipIntf, public RawDev {
|
||||
public:
|
||||
LwipIntfDev(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1) :
|
||||
RawDev(cs, spi, intr), _mtu(DEFAULT_MTU), _intrPin(intr), _started(false), _default(false) {
|
||||
RawDev(cs, spi, intr), _spiUnit(spi), _mtu(DEFAULT_MTU), _intrPin(intr), _started(false), _default(false) {
|
||||
memset(&_netif, 0, sizeof(_netif));
|
||||
}
|
||||
|
||||
@@ -109,6 +109,14 @@ public:
|
||||
|
||||
int hostByName(const char* aHostname, IPAddress& aResult, int timeout);
|
||||
|
||||
inline void setSPISpeed(int mhz) {
|
||||
setSPISettings(SPISettings(mhz, MSBFIRST, SPI_MODE0));
|
||||
}
|
||||
|
||||
void setSPISettings(SPISettings s) {
|
||||
_spiSettings = s;
|
||||
}
|
||||
|
||||
// ESP8266WiFi API compatibility
|
||||
|
||||
wl_status_t status();
|
||||
@@ -121,12 +129,13 @@ protected:
|
||||
static err_t netif_init_s(netif* netif);
|
||||
static err_t linkoutput_s(netif* netif, struct pbuf* p);
|
||||
static void netif_status_callback_s(netif* netif);
|
||||
|
||||
public:
|
||||
// called on a regular basis or on interrupt
|
||||
err_t handlePackets();
|
||||
|
||||
protected:
|
||||
// members
|
||||
|
||||
SPIClass& _spiUnit;
|
||||
SPISettings _spiSettings = SPISettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
netif _netif;
|
||||
|
||||
uint16_t _mtu;
|
||||
@@ -141,65 +150,14 @@ protected:
|
||||
volatile int _ping_ttl;
|
||||
static u8_t _pingCB(void *arg, struct raw_pcb *pcb, struct pbuf *p, const ip_addr_t *addr);
|
||||
|
||||
// DNS lookup callback
|
||||
bool _dns_lookup_pending = false;
|
||||
typedef struct {
|
||||
IPAddress *ip;
|
||||
LwipIntfDev<RawDev> *wifi;
|
||||
} _dns_cb_t;
|
||||
static void _dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg);
|
||||
// Packet handler number
|
||||
int _phID = -1;
|
||||
};
|
||||
|
||||
|
||||
template<class RawDev>
|
||||
void LwipIntfDev<RawDev>::_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) {
|
||||
(void) name;
|
||||
_dns_cb_t *cb = (_dns_cb_t *)callback_arg;
|
||||
if (!cb->wifi->_dns_lookup_pending) {
|
||||
return;
|
||||
}
|
||||
if (ipaddr) {
|
||||
*(cb->ip) = IPAddress(ipaddr);
|
||||
}
|
||||
cb->wifi->_dns_lookup_pending = false; // resume hostByName
|
||||
}
|
||||
|
||||
template<class RawDev>
|
||||
int LwipIntfDev<RawDev>::hostByName(const char* aHostname, IPAddress& aResult, int timeout_ms) {
|
||||
ip_addr_t addr;
|
||||
aResult = static_cast<uint32_t>(0xffffffff);
|
||||
|
||||
if (aResult.fromString(aHostname)) {
|
||||
// Host name is a IP address use it!
|
||||
return 1;
|
||||
}
|
||||
|
||||
_dns_cb_t cb = { &aResult, this };
|
||||
#if LWIP_IPV4 && LWIP_IPV6
|
||||
err_t err = dns_gethostbyname_addrtype(aHostname, &addr, &_dns_found_callback, &cb, LWIP_DNS_ADDRTYPE_DEFAULT);
|
||||
#else
|
||||
err_t err = dns_gethostbyname(aHostname, &addr, &_dns_found_callback, &cb);
|
||||
#endif
|
||||
if (err == ERR_OK) {
|
||||
aResult = IPAddress(&addr);
|
||||
} else if (err == ERR_INPROGRESS) {
|
||||
_dns_lookup_pending = true;
|
||||
uint32_t now = millis();
|
||||
while ((millis() - now < (uint32_t)timeout_ms) && _dns_lookup_pending) {
|
||||
sys_check_timeouts();
|
||||
delay(10);
|
||||
}
|
||||
_dns_lookup_pending = false;
|
||||
if (aResult.isSet()) {
|
||||
err = ERR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (err == ERR_OK) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ::hostByName(aHostname, aResult, timeout_ms);
|
||||
}
|
||||
|
||||
template<class RawDev>
|
||||
@@ -293,7 +251,16 @@ bool LwipIntfDev<RawDev>::config(const IPAddress& localIP, const IPAddress& gate
|
||||
}
|
||||
extern char wifi_station_hostname[];
|
||||
template<class RawDev>
|
||||
boolean LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu) {
|
||||
bool LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu) {
|
||||
lwip_init();
|
||||
__startEthernetContext();
|
||||
|
||||
if (RawDev::needsSPI()) {
|
||||
_spiUnit.begin();
|
||||
// Set SPI clocks/etc. per request, doesn't seem to be direct way other than a fake transaction
|
||||
_spiUnit.beginTransaction(_spiSettings);
|
||||
_spiUnit.endTransaction();
|
||||
}
|
||||
if (mtu) {
|
||||
_mtu = mtu;
|
||||
}
|
||||
@@ -343,6 +310,8 @@ boolean LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu
|
||||
return false;
|
||||
}
|
||||
|
||||
_phID = __addEthernetPacketHandler(std::bind(&LwipIntfDev<RawDev>::handlePackets, this));
|
||||
|
||||
if (localIP().v4() == 0) {
|
||||
// IP not set, starting DHCP
|
||||
_netif.flags |= NETIF_FLAG_UP;
|
||||
@@ -383,24 +352,15 @@ boolean LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
if (_intrPin < 0
|
||||
&& !schedule_recurrent_function_us(
|
||||
[&]() {
|
||||
this->handlePackets();
|
||||
return true;
|
||||
},
|
||||
100)) {
|
||||
netif_remove(&_netif);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class RawDev>
|
||||
void LwipIntfDev<RawDev>::end() {
|
||||
__removeEthernetPacketHandler(_phID);
|
||||
|
||||
RawDev::end();
|
||||
|
||||
netif_remove(&_netif);
|
||||
memset(&_netif, 0, sizeof(_netif));
|
||||
_started = false;
|
||||
@@ -415,11 +375,7 @@ wl_status_t LwipIntfDev<RawDev>::status() {
|
||||
template<class RawDev>
|
||||
err_t LwipIntfDev<RawDev>::linkoutput_s(netif* netif, struct pbuf* pbuf) {
|
||||
LwipIntfDev* lid = (LwipIntfDev*)netif->state;
|
||||
|
||||
if (pbuf->len != pbuf->tot_len || pbuf->next) {
|
||||
Serial.println("ERRTOT\r\n");
|
||||
}
|
||||
|
||||
ethernet_arch_lwip_begin();
|
||||
uint16_t len = lid->sendFrame((const uint8_t*)pbuf->payload, pbuf->len);
|
||||
|
||||
#if PHY_HAS_CAPTURE
|
||||
@@ -428,7 +384,7 @@ err_t LwipIntfDev<RawDev>::linkoutput_s(netif* netif, struct pbuf* pbuf) {
|
||||
/*success*/ len == pbuf->len);
|
||||
}
|
||||
#endif
|
||||
|
||||
ethernet_arch_lwip_end();
|
||||
return len == pbuf->len ? ERR_OK : ERR_MEM;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
This sketch establishes a TCP connection to a "quote of the day" service.
|
||||
It sends a "hello" message, and then prints received data.
|
||||
*/
|
||||
|
||||
#include <ENC28J60lwIP.h>
|
||||
|
||||
const char* host = "djxmmx.net";
|
||||
const uint16_t port = 17;
|
||||
|
||||
ENC28J60lwIP eth(1 /* chip select */);
|
||||
|
||||
void setup() {
|
||||
// Set up SPI pinout to match your HW
|
||||
SPI.setRX(0);
|
||||
SPI.setCS(1);
|
||||
SPI.setSCK(2);
|
||||
SPI.setTX(3);
|
||||
|
||||
Serial.begin(115200);
|
||||
delay(5000);
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
Serial.println("Starting Ethernet port");
|
||||
|
||||
// Start the Ethernet port
|
||||
if (!eth.begin()) {
|
||||
Serial.println("No wired Ethernet hardware detected. Check pinouts, wiring.");
|
||||
while (1) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
while (!eth.connected()) {
|
||||
Serial.print(".");
|
||||
delay(500);
|
||||
}
|
||||
|
||||
Serial.println("");
|
||||
Serial.println("Ethernet connected");
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(eth.localIP());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
static bool wait = false;
|
||||
|
||||
Serial.print("connecting to ");
|
||||
Serial.print(host);
|
||||
Serial.print(':');
|
||||
Serial.println(port);
|
||||
|
||||
// Use WiFiClient class to create TCP connections
|
||||
WiFiClient client;
|
||||
if (!client.connect(host, port)) {
|
||||
Serial.println("connection failed");
|
||||
delay(5000);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will send a string to the server
|
||||
Serial.println("sending data to server");
|
||||
if (client.connected()) {
|
||||
client.println("hello from RP2040");
|
||||
}
|
||||
|
||||
// wait for data to be available
|
||||
unsigned long timeout = millis();
|
||||
while (client.available() == 0) {
|
||||
if (millis() - timeout > 5000) {
|
||||
Serial.println(">>> Client Timeout !");
|
||||
client.stop();
|
||||
delay(60000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Read all the lines of the reply from server and print them to Serial
|
||||
Serial.println("receiving from remote server");
|
||||
// not testing 'client.connected()' since we do not need to send data here
|
||||
while (client.available()) {
|
||||
char ch = static_cast<char>(client.read());
|
||||
Serial.print(ch);
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
Serial.println();
|
||||
Serial.println("closing connection");
|
||||
client.stop();
|
||||
|
||||
if (wait) {
|
||||
delay(300000); // execute once every 5 minutes, don't flood remote service
|
||||
}
|
||||
wait = true;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Library (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
ENC28J60lwIP KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -0,0 +1,10 @@
|
||||
name=lwIP_enc28j60
|
||||
version=1
|
||||
author=Nicholas Humfrey
|
||||
maintainer=esp8266/Arduino
|
||||
sentence=Ethernet driver
|
||||
paragraph=ENC28J60 ethernet drivers for lwIP and esp8266 Arduino from https://github.com/njh/EtherSia/tree/master/src/enc28j60.cpp
|
||||
category=Communication
|
||||
url=https://github.com/esp8266/Arduino
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <LwipIntfDev.h>
|
||||
#include <utility/enc28j60.h>
|
||||
#include <LwipEthernet.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
using ENC28J60lwIP = LwipIntfDev<ENC28J60>;
|
||||
@@ -0,0 +1,701 @@
|
||||
/*
|
||||
Copyright (c) 2012-2013, Thingsquare, http://www.thingsquare.com/.
|
||||
Copyright (c) 2016, Nicholas Humfrey
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
// original sources: https://github.com/njh/EtherSia/tree/master/src/enc28j60.cpp
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "enc28j60.h"
|
||||
|
||||
#define DEBUG 0
|
||||
#if DEBUG
|
||||
#define PRINTF(...) printf(__VA_ARGS__)
|
||||
#else
|
||||
#define PRINTF(...) \
|
||||
do \
|
||||
{ \
|
||||
(void)0; \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define EIE 0x1b
|
||||
#define EIR 0x1c
|
||||
#define ESTAT 0x1d
|
||||
#define ECON2 0x1e
|
||||
#define ECON1 0x1f
|
||||
|
||||
#define ESTAT_CLKRDY 0x01
|
||||
#define ESTAT_TXABRT 0x02
|
||||
|
||||
#define ECON1_RXEN 0x04
|
||||
#define ECON1_TXRTS 0x08
|
||||
|
||||
#define ECON2_AUTOINC 0x80
|
||||
#define ECON2_PKTDEC 0x40
|
||||
|
||||
#define EIR_TXIF 0x08
|
||||
|
||||
#define ERXTX_BANK 0x00
|
||||
|
||||
#define ERDPTL 0x00
|
||||
#define ERDPTH 0x01
|
||||
#define EWRPTL 0x02
|
||||
#define EWRPTH 0x03
|
||||
#define ETXSTL 0x04
|
||||
#define ETXSTH 0x05
|
||||
#define ETXNDL 0x06
|
||||
#define ETXNDH 0x07
|
||||
#define ERXSTL 0x08
|
||||
#define ERXSTH 0x09
|
||||
#define ERXNDL 0x0a
|
||||
#define ERXNDH 0x0b
|
||||
#define ERXRDPTL 0x0c
|
||||
#define ERXRDPTH 0x0d
|
||||
|
||||
#define RX_BUF_START 0x0000
|
||||
#define RX_BUF_END 0x0fff
|
||||
|
||||
#define TX_BUF_START 0x1200
|
||||
|
||||
/* MACONx registers are in bank 2 */
|
||||
#define MACONX_BANK 0x02
|
||||
|
||||
#define MACON1 0x00
|
||||
#define MACSTAT1 0x01
|
||||
#define MACON3 0x02
|
||||
#define MACON4 0x03
|
||||
#define MABBIPG 0x04
|
||||
#define MAIPGL 0x06
|
||||
#define MAIPGH 0x07
|
||||
#define MAMXFLL 0x0a
|
||||
#define MAMXFLH 0x0b
|
||||
#define MACON2 0x10
|
||||
#define MACSTAT2 0x11
|
||||
#define MICMD 0x12
|
||||
#define MIREGADR 0x14
|
||||
#define MIRDL 0x18
|
||||
#define MIRDH 0x19
|
||||
|
||||
/* MICMD Register Bit Definitions */
|
||||
#define MICMD_MIISCAN 0x02
|
||||
#define MICMD_MIIRD 0x01
|
||||
|
||||
#define MACON1_TXPAUS 0x08
|
||||
#define MACON1_RXPAUS 0x04
|
||||
#define MACON1_MARXEN 0x01
|
||||
|
||||
#define MACON3_PADCFG_FULL 0xe0
|
||||
#define MACON3_TXCRCEN 0x10
|
||||
#define MACON3_FRMLNEN 0x02
|
||||
#define MACON3_FULDPX 0x01
|
||||
|
||||
#define MAX_MAC_LENGTH 1518
|
||||
|
||||
#define MAADRX_BANK 0x03
|
||||
#define MAADR1 0x04 /* MAADR<47:40> */
|
||||
#define MAADR2 0x05 /* MAADR<39:32> */
|
||||
#define MAADR3 0x02 /* MAADR<31:24> */
|
||||
#define MAADR4 0x03 /* MAADR<23:16> */
|
||||
#define MAADR5 0x00 /* MAADR<15:8> */
|
||||
#define MAADR6 0x01 /* MAADR<7:0> */
|
||||
#define MISTAT 0x0a
|
||||
#define EREVID 0x12
|
||||
|
||||
/* MISTAT Register Bit Definitions */
|
||||
#define MISTAT_BUSY 0x01
|
||||
|
||||
#define EPKTCNT_BANK 0x01
|
||||
#define ERXFCON 0x18
|
||||
#define EPKTCNT 0x19
|
||||
|
||||
#define ERXFCON_UCEN 0x80
|
||||
#define ERXFCON_ANDOR 0x40
|
||||
#define ERXFCON_CRCEN 0x20
|
||||
#define ERXFCON_MCEN 0x02
|
||||
#define ERXFCON_BCEN 0x01
|
||||
|
||||
// The ENC28J60 SPI Interface supports clock speeds up to 20 MHz
|
||||
static const SPISettings spiSettings(20000000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
ENC28J60::ENC28J60(int8_t cs, SPIClass& spi, int8_t intr) : _bank(ERXTX_BANK), _cs(cs), _spi(spi) {
|
||||
(void)intr;
|
||||
}
|
||||
|
||||
void ENC28J60::enc28j60_arch_spi_select(void) {
|
||||
SPI.beginTransaction(spiSettings);
|
||||
digitalWrite(_cs, LOW);
|
||||
}
|
||||
|
||||
void ENC28J60::enc28j60_arch_spi_deselect(void) {
|
||||
digitalWrite(_cs, HIGH);
|
||||
SPI.endTransaction();
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uint8_t ENC28J60::is_mac_mii_reg(uint8_t reg) {
|
||||
/* MAC or MII register (otherwise, ETH register)? */
|
||||
switch (_bank) {
|
||||
case MACONX_BANK:
|
||||
return reg < EIE;
|
||||
case MAADRX_BANK:
|
||||
return reg <= MAADR2 || reg == MISTAT;
|
||||
case ERXTX_BANK:
|
||||
case EPKTCNT_BANK:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uint8_t ENC28J60::readreg(uint8_t reg) {
|
||||
uint8_t r;
|
||||
enc28j60_arch_spi_select();
|
||||
SPI.transfer(0x00 | (reg & 0x1f));
|
||||
if (is_mac_mii_reg(reg)) {
|
||||
/* MAC and MII registers require that a dummy byte be read first. */
|
||||
SPI.transfer(0);
|
||||
}
|
||||
r = SPI.transfer(0);
|
||||
enc28j60_arch_spi_deselect();
|
||||
return r;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void ENC28J60::writereg(uint8_t reg, uint8_t data) {
|
||||
enc28j60_arch_spi_select();
|
||||
SPI.transfer(0x40 | (reg & 0x1f));
|
||||
SPI.transfer(data);
|
||||
enc28j60_arch_spi_deselect();
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void ENC28J60::setregbitfield(uint8_t reg, uint8_t mask) {
|
||||
if (is_mac_mii_reg(reg)) {
|
||||
writereg(reg, readreg(reg) | mask);
|
||||
} else {
|
||||
enc28j60_arch_spi_select();
|
||||
SPI.transfer(0x80 | (reg & 0x1f));
|
||||
SPI.transfer(mask);
|
||||
enc28j60_arch_spi_deselect();
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void ENC28J60::clearregbitfield(uint8_t reg, uint8_t mask) {
|
||||
if (is_mac_mii_reg(reg)) {
|
||||
writereg(reg, readreg(reg) & ~mask);
|
||||
} else {
|
||||
enc28j60_arch_spi_select();
|
||||
SPI.transfer(0xa0 | (reg & 0x1f));
|
||||
SPI.transfer(mask);
|
||||
enc28j60_arch_spi_deselect();
|
||||
}
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void ENC28J60::setregbank(uint8_t new_bank) {
|
||||
writereg(ECON1, (readreg(ECON1) & 0xfc) | (new_bank & 0x03));
|
||||
_bank = new_bank;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void ENC28J60::writedata(const uint8_t* data, int datalen) {
|
||||
int i;
|
||||
enc28j60_arch_spi_select();
|
||||
/* The Write Buffer Memory (WBM) command is 0 1 1 1 1 0 1 0 */
|
||||
SPI.transfer(0x7a);
|
||||
for (i = 0; i < datalen; i++) {
|
||||
SPI.transfer(data[i]);
|
||||
}
|
||||
enc28j60_arch_spi_deselect();
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void ENC28J60::writedatabyte(uint8_t byte) {
|
||||
writedata(&byte, 1);
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
int ENC28J60::readdata(uint8_t* buf, int len) {
|
||||
int i;
|
||||
enc28j60_arch_spi_select();
|
||||
/* THe Read Buffer Memory (RBM) command is 0 0 1 1 1 0 1 0 */
|
||||
SPI.transfer(0x3a);
|
||||
for (i = 0; i < len; i++) {
|
||||
buf[i] = SPI.transfer(0);
|
||||
}
|
||||
enc28j60_arch_spi_deselect();
|
||||
return i;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
uint8_t ENC28J60::readdatabyte(void) {
|
||||
uint8_t r;
|
||||
readdata(&r, 1);
|
||||
return r;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
void ENC28J60::softreset(void) {
|
||||
enc28j60_arch_spi_select();
|
||||
/* The System Command (soft reset) is 1 1 1 1 1 1 1 1 */
|
||||
SPI.transfer(0xff);
|
||||
enc28j60_arch_spi_deselect();
|
||||
_bank = ERXTX_BANK;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
//#if DEBUG
|
||||
uint8_t ENC28J60::readrev(void) {
|
||||
uint8_t rev;
|
||||
setregbank(MAADRX_BANK);
|
||||
rev = readreg(EREVID);
|
||||
switch (rev) {
|
||||
case 2:
|
||||
return 1;
|
||||
case 6:
|
||||
return 7;
|
||||
default:
|
||||
return rev;
|
||||
}
|
||||
}
|
||||
//#endif
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
bool ENC28J60::reset(void) {
|
||||
PRINTF("enc28j60: resetting chip\n");
|
||||
|
||||
pinMode(_cs, OUTPUT);
|
||||
digitalWrite(_cs, HIGH);
|
||||
SPI.begin();
|
||||
|
||||
/*
|
||||
6.0 INITIALIZATION
|
||||
|
||||
Before the ENC28J60 can be used to transmit and receive packets,
|
||||
certain device settings must be initialized. Depending on the
|
||||
application, some configuration options may need to be
|
||||
changed. Normally, these tasks may be accomplished once after
|
||||
Reset and do not need to be changed thereafter.
|
||||
|
||||
6.1 Receive Buffer
|
||||
|
||||
Before receiving any packets, the receive buffer must be
|
||||
initialized by programming the ERXST and ERXND pointers. All
|
||||
memory between and including the ERXST and ERXND addresses will be
|
||||
dedicated to the receive hardware. It is recommended that the
|
||||
ERXST pointer be programmed with an even address.
|
||||
|
||||
Applications expecting large amounts of data and frequent packet
|
||||
delivery may wish to allocate most of the memory as the receive
|
||||
buffer. Applications that may need to save older packets or have
|
||||
several packets ready for transmission should allocate less
|
||||
memory.
|
||||
|
||||
When programming the ERXST pointer, the ERXWRPT registers will
|
||||
automatically be updated with the same values. The address in
|
||||
ERXWRPT will be used as the starting location when the receive
|
||||
hardware begins writing received data. For tracking purposes, the
|
||||
ERXRDPT registers should additionally be programmed with the same
|
||||
value. To program ERXRDPT, the host controller must write to
|
||||
ERXRDPTL first, followed by ERXRDPTH. See Section 7.2.4 “Freeing
|
||||
Receive Buffer Space for more information
|
||||
|
||||
6.2 Transmission Buffer
|
||||
|
||||
All memory which is not used by the receive buffer is considered
|
||||
the transmission buffer. Data which is to be transmitted should be
|
||||
written into any unused space. After a packet is transmitted,
|
||||
however, the hardware will write a seven-byte status vector into
|
||||
memory after the last byte in the packet. Therefore, the host
|
||||
controller should leave at least seven bytes between each packet
|
||||
and the beginning of the receive buffer. No explicit action is
|
||||
required to initialize the transmission buffer.
|
||||
|
||||
6.3 Receive Filters
|
||||
|
||||
The appropriate receive filters should be enabled or disabled by
|
||||
writing to the ERXFCON register. See Section 8.0 “Receive Filters
|
||||
for information on how to configure it.
|
||||
|
||||
6.4 Waiting For OST
|
||||
|
||||
If the initialization procedure is being executed immediately
|
||||
following a Power-on Reset, the ESTAT.CLKRDY bit should be polled
|
||||
to make certain that enough time has elapsed before proceeding to
|
||||
modify the MAC and PHY registers. For more information on the OST,
|
||||
see Section 2.2 “Oscillator Start-up Timer.
|
||||
*/
|
||||
|
||||
softreset();
|
||||
|
||||
/* Workaround for erratum #2. */
|
||||
delayMicroseconds(1000);
|
||||
|
||||
/* Wait for OST */
|
||||
PRINTF("waiting for ESTAT_CLKRDY\n");
|
||||
while ((readreg(ESTAT) & ESTAT_CLKRDY) == 0) { };
|
||||
PRINTF("ESTAT_CLKRDY\n");
|
||||
|
||||
setregbank(ERXTX_BANK);
|
||||
/* Set up receive buffer */
|
||||
writereg(ERXSTL, RX_BUF_START & 0xff);
|
||||
writereg(ERXSTH, RX_BUF_START >> 8);
|
||||
writereg(ERXNDL, RX_BUF_END & 0xff);
|
||||
writereg(ERXNDH, RX_BUF_END >> 8);
|
||||
writereg(ERDPTL, RX_BUF_START & 0xff);
|
||||
writereg(ERDPTH, RX_BUF_START >> 8);
|
||||
writereg(ERXRDPTL, RX_BUF_END & 0xff);
|
||||
writereg(ERXRDPTH, RX_BUF_END >> 8);
|
||||
|
||||
/* Receive filters */
|
||||
setregbank(EPKTCNT_BANK);
|
||||
writereg(ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_MCEN);
|
||||
|
||||
/*
|
||||
6.5 MAC Initialization Settings
|
||||
|
||||
Several of the MAC registers require configuration during
|
||||
initialization. This only needs to be done once; the order of
|
||||
programming is unimportant.
|
||||
|
||||
1. Set the MARXEN bit in MACON1 to enable the MAC to receive
|
||||
frames. If using full duplex, most applications should also set
|
||||
TXPAUS and RXPAUS to allow IEEE defined flow control to function.
|
||||
|
||||
2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3. Most
|
||||
applications should enable automatic padding to at least 60 bytes
|
||||
and always append a valid CRC. For convenience, many applications
|
||||
may wish to set the FRMLNEN bit as well to enable frame length
|
||||
status reporting. The FULDPX bit should be set if the application
|
||||
will be connected to a full-duplex configured remote node;
|
||||
otherwise, it should be left clear.
|
||||
|
||||
3. Configure the bits in MACON4. For conformance to the IEEE 802.3
|
||||
standard, set the DEFER bit.
|
||||
|
||||
4. Program the MAMXFL registers with the maximum frame length to
|
||||
be permitted to be received or transmitted. Normal network nodes
|
||||
are designed to handle packets that are 1518 bytes or less.
|
||||
|
||||
5. Configure the Back-to-Back Inter-Packet Gap register,
|
||||
MABBIPG. Most applications will program this register with 15h
|
||||
when Full-Duplex mode is used and 12h when Half-Duplex mode is
|
||||
used.
|
||||
|
||||
6. Configure the Non-Back-to-Back Inter-Packet Gap register low
|
||||
byte, MAIPGL. Most applications will program this register with
|
||||
12h.
|
||||
|
||||
7. If half duplex is used, the Non-Back-to-Back Inter-Packet Gap
|
||||
register high byte, MAIPGH, should be programmed. Most
|
||||
applications will program this register to 0Ch.
|
||||
|
||||
8. If Half-Duplex mode is used, program the Retransmission and
|
||||
Collision Window registers, MACLCON1 and MACLCON2. Most
|
||||
applications will not need to change the default Reset values. If
|
||||
the network is spread over exceptionally long cables, the default
|
||||
value of MACLCON2 may need to be increased.
|
||||
|
||||
9. Program the local MAC address into the MAADR1:MAADR6 registers.
|
||||
*/
|
||||
|
||||
setregbank(MACONX_BANK);
|
||||
|
||||
/* Turn on reception and IEEE-defined flow control */
|
||||
setregbitfield(MACON1, MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS);
|
||||
|
||||
/* Set padding, crc, full duplex */
|
||||
setregbitfield(MACON3, MACON3_PADCFG_FULL | MACON3_TXCRCEN | MACON3_FULDPX | MACON3_FRMLNEN);
|
||||
|
||||
/* Don't modify MACON4 */
|
||||
|
||||
/* Set maximum frame length in MAMXFL */
|
||||
writereg(MAMXFLL, MAX_MAC_LENGTH & 0xff);
|
||||
writereg(MAMXFLH, MAX_MAC_LENGTH >> 8);
|
||||
|
||||
/* Set back-to-back inter packet gap */
|
||||
writereg(MABBIPG, 0x15);
|
||||
|
||||
/* Set non-back-to-back packet gap */
|
||||
writereg(MAIPGL, 0x12);
|
||||
|
||||
/* Set MAC address */
|
||||
setregbank(MAADRX_BANK);
|
||||
writereg(MAADR6, _localMac[5]);
|
||||
writereg(MAADR5, _localMac[4]);
|
||||
writereg(MAADR4, _localMac[3]);
|
||||
writereg(MAADR3, _localMac[2]);
|
||||
writereg(MAADR2, _localMac[1]);
|
||||
writereg(MAADR1, _localMac[0]);
|
||||
|
||||
/*
|
||||
6.6 PHY Initialization Settings
|
||||
|
||||
Depending on the application, bits in three of the PHY module’s
|
||||
registers may also require configuration. The PHCON1.PDPXMD bit
|
||||
partially controls the device’s half/full-duplex
|
||||
configuration. Normally, this bit is initialized correctly by the
|
||||
external circuitry (see Section 2.6 “LED Configuration). If the
|
||||
external circuitry is not present or incorrect, however, the host
|
||||
controller must program the bit properly. Alternatively, for an
|
||||
externally configurable system, the PDPXMD bit may be read and the
|
||||
FULDPX bit be programmed to match.
|
||||
|
||||
For proper duplex operation, the PHCON1.PDPXMD bit must also match
|
||||
the value of the MACON3.FULDPX bit.
|
||||
|
||||
If using half duplex, the host controller may wish to set the
|
||||
PHCON2.HDLDIS bit to prevent automatic loopback of the data which
|
||||
is transmitted. The PHY register, PHLCON, controls the outputs of
|
||||
LEDA and LEDB. If an application requires a LED configuration
|
||||
other than the default, PHLCON must be altered to match the new
|
||||
requirements. The settings for LED operation are discussed in
|
||||
Section 2.6 “LED Configuration. The PHLCON register is shown in
|
||||
Register 2-2 (page 9).
|
||||
*/
|
||||
|
||||
/* Don't worry about PHY configuration for now */
|
||||
|
||||
/* Turn on autoincrement for buffer access */
|
||||
setregbitfield(ECON2, ECON2_AUTOINC);
|
||||
|
||||
/* Turn on reception */
|
||||
writereg(ECON1, ECON1_RXEN);
|
||||
|
||||
return true;
|
||||
}
|
||||
/*---------------------------------------------------------------------------*/
|
||||
bool ENC28J60::begin(const uint8_t* address, netif *net) {
|
||||
_localMac = address;
|
||||
_netif = net;
|
||||
|
||||
bool ret = reset();
|
||||
uint8_t rev = readrev();
|
||||
|
||||
PRINTF("ENC28J60 rev. B%d\n", rev);
|
||||
|
||||
return ret && rev != 255;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
uint16_t ENC28J60::sendFrame(const uint8_t* data, uint16_t datalen) {
|
||||
uint16_t dataend;
|
||||
|
||||
/*
|
||||
1. Appropriately program the ETXST pointer to point to an unused
|
||||
location in memory. It will point to the per packet control
|
||||
byte. In the example, it would be programmed to 0120h. It is
|
||||
recommended that an even address be used for ETXST.
|
||||
|
||||
2. Use the WBM SPI command to write the per packet control byte,
|
||||
the destination address, the source MAC address, the
|
||||
type/length and the data payload.
|
||||
|
||||
3. Appropriately program the ETXND pointer. It should point to the
|
||||
last byte in the data payload. In the example, it would be
|
||||
programmed to 0156h.
|
||||
|
||||
4. Clear EIR.TXIF, set EIE.TXIE and set EIE.INTIE to enable an
|
||||
interrupt when done (if desired).
|
||||
|
||||
5. Start the transmission process by setting
|
||||
ECON1.TXRTS.
|
||||
*/
|
||||
|
||||
setregbank(ERXTX_BANK);
|
||||
/* Set up the transmit buffer pointer */
|
||||
writereg(ETXSTL, TX_BUF_START & 0xff);
|
||||
writereg(ETXSTH, TX_BUF_START >> 8);
|
||||
writereg(EWRPTL, TX_BUF_START & 0xff);
|
||||
writereg(EWRPTH, TX_BUF_START >> 8);
|
||||
|
||||
/* Write the transmission control register as the first byte of the
|
||||
output packet. We write 0x00 to indicate that the default
|
||||
configuration (the values in MACON3) will be used. */
|
||||
writedatabyte(0x00); /* MACON3 */
|
||||
|
||||
writedata(data, datalen);
|
||||
|
||||
/* Write a pointer to the last data byte. */
|
||||
dataend = TX_BUF_START + datalen;
|
||||
writereg(ETXNDL, dataend & 0xff);
|
||||
writereg(ETXNDH, dataend >> 8);
|
||||
|
||||
/* Clear EIR.TXIF */
|
||||
clearregbitfield(EIR, EIR_TXIF);
|
||||
|
||||
/* Don't care about interrupts for now */
|
||||
|
||||
/* Send the packet */
|
||||
setregbitfield(ECON1, ECON1_TXRTS);
|
||||
while ((readreg(ECON1) & ECON1_TXRTS) > 0)
|
||||
;
|
||||
|
||||
#if DEBUG
|
||||
if ((readreg(ESTAT) & ESTAT_TXABRT) != 0) {
|
||||
uint16_t erdpt;
|
||||
uint8_t tsv[7];
|
||||
erdpt = (readreg(ERDPTH) << 8) | readreg(ERDPTL);
|
||||
writereg(ERDPTL, (dataend + 1) & 0xff);
|
||||
writereg(ERDPTH, (dataend + 1) >> 8);
|
||||
readdata(tsv, sizeof(tsv));
|
||||
writereg(ERDPTL, erdpt & 0xff);
|
||||
writereg(ERDPTH, erdpt >> 8);
|
||||
PRINTF("enc28j60: tx err: %d: %02x:%02x:%02x:%02x:%02x:%02x\n"
|
||||
" tsv: %02x%02x%02x%02x%02x%02x%02x\n",
|
||||
datalen, 0xff & data[0], 0xff & data[1], 0xff & data[2], 0xff & data[3],
|
||||
0xff & data[4], 0xff & data[5], tsv[6], tsv[5], tsv[4], tsv[3], tsv[2], tsv[1],
|
||||
tsv[0]);
|
||||
} else {
|
||||
PRINTF("enc28j60: tx: %d: %02x:%02x:%02x:%02x:%02x:%02x\n", datalen, 0xff & data[0],
|
||||
0xff & data[1], 0xff & data[2], 0xff & data[3], 0xff & data[4], 0xff & data[5]);
|
||||
}
|
||||
#endif
|
||||
|
||||
// sent_packets++;
|
||||
// PRINTF("enc28j60: sent_packets %d\n", sent_packets);
|
||||
return datalen;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
uint16_t ENC28J60::readFrame(uint8_t* buffer, uint16_t bufsize) {
|
||||
readFrameSize();
|
||||
return readFrameData(buffer, bufsize);
|
||||
}
|
||||
|
||||
uint16_t ENC28J60::readFrameSize() {
|
||||
uint8_t n;
|
||||
|
||||
uint8_t nxtpkt[2];
|
||||
uint8_t status[2];
|
||||
uint8_t length[2];
|
||||
|
||||
setregbank(EPKTCNT_BANK);
|
||||
n = readreg(EPKTCNT);
|
||||
|
||||
if (n == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
PRINTF("enc28j60: EPKTCNT 0x%02x\n", n);
|
||||
|
||||
setregbank(ERXTX_BANK);
|
||||
/* Read the next packet pointer */
|
||||
nxtpkt[0] = readdatabyte();
|
||||
nxtpkt[1] = readdatabyte();
|
||||
_next = (nxtpkt[1] << 8) + nxtpkt[0];
|
||||
|
||||
PRINTF("enc28j60: nxtpkt 0x%02x%02x\n", _nxtpkt[1], _nxtpkt[0]);
|
||||
|
||||
length[0] = readdatabyte();
|
||||
length[1] = readdatabyte();
|
||||
_len = (length[1] << 8) + length[0];
|
||||
|
||||
PRINTF("enc28j60: length 0x%02x%02x\n", length[1], length[0]);
|
||||
|
||||
status[0] = readdatabyte();
|
||||
status[1] = readdatabyte();
|
||||
|
||||
/* This statement is just to avoid a compiler warning: */
|
||||
(void)status[0];
|
||||
PRINTF("enc28j60: status 0x%02x%02x\n", status[1], status[0]);
|
||||
|
||||
return _len;
|
||||
}
|
||||
|
||||
void ENC28J60::discardFrame(uint16_t framesize) {
|
||||
(void)framesize;
|
||||
(void)readFrameData(nullptr, 0);
|
||||
}
|
||||
|
||||
uint16_t ENC28J60::readFrameData(uint8_t* buffer, uint16_t framesize) {
|
||||
if (framesize < _len) {
|
||||
buffer = nullptr;
|
||||
|
||||
/* flush rx fifo */
|
||||
for (uint16_t i = 0; i < _len; i++) {
|
||||
readdatabyte();
|
||||
}
|
||||
} else {
|
||||
readdata(buffer, _len);
|
||||
}
|
||||
|
||||
/* Read an additional byte at odd lengths, to avoid FIFO corruption */
|
||||
if ((_len % 2) != 0) {
|
||||
readdatabyte();
|
||||
}
|
||||
|
||||
/* Errata #14 */
|
||||
if (_next == RX_BUF_START) {
|
||||
_next = RX_BUF_END;
|
||||
} else {
|
||||
_next = _next - 1;
|
||||
}
|
||||
writereg(ERXRDPTL, _next & 0xff);
|
||||
writereg(ERXRDPTH, _next >> 8);
|
||||
|
||||
setregbitfield(ECON2, ECON2_PKTDEC);
|
||||
|
||||
if (!buffer) {
|
||||
PRINTF("enc28j60: rx err: flushed %d\n", _len);
|
||||
return 0;
|
||||
}
|
||||
PRINTF("enc28j60: rx: %d: %02x:%02x:%02x:%02x:%02x:%02x\n", _len, 0xff & buffer[0],
|
||||
0xff & buffer[1], 0xff & buffer[2], 0xff & buffer[3], 0xff & buffer[4],
|
||||
0xff & buffer[5]);
|
||||
|
||||
// received_packets++;
|
||||
// PRINTF("enc28j60: received_packets %d\n", received_packets);
|
||||
|
||||
return _len;
|
||||
}
|
||||
|
||||
uint16_t ENC28J60::phyread(uint8_t reg) {
|
||||
// ( https://github.com/JAndrassy/EthernetENC/tree/master/src/utility/enc28j60.h )
|
||||
|
||||
setregbank(MACONX_BANK);
|
||||
writereg(MIREGADR, reg);
|
||||
writereg(MICMD, MICMD_MIIRD);
|
||||
// wait until the PHY read completes
|
||||
while (readreg(MISTAT) & MISTAT_BUSY) {
|
||||
delayMicroseconds(15);
|
||||
}
|
||||
writereg(MICMD, 0);
|
||||
return (readreg(MIRDL) | readreg(MIRDH) << 8);
|
||||
}
|
||||
|
||||
bool ENC28J60::isLinked() {
|
||||
// ( https://github.com/JAndrassy/EthernetENC/tree/master/src/utility/enc28j60.h )
|
||||
|
||||
return !!(phyread(MACSTAT2) & 0x400);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
Header file for direct Ethernet frame access to the ENC28J60 controller
|
||||
@file enc28j60.h
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright (c) 2012-2013, Thingsquare, http://www.thingsquare.com/.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
// original sources: https://github.com/njh/EtherSia/tree/master/src/enc28j60.h
|
||||
|
||||
#ifndef ENC28J60_H
|
||||
#define ENC28J60_H
|
||||
|
||||
#include <SPI.h>
|
||||
|
||||
/**
|
||||
Send and receive Ethernet frames directly using a ENC28J60 controller.
|
||||
*/
|
||||
class ENC28J60 {
|
||||
public:
|
||||
/**
|
||||
Constructor that uses the default hardware SPI pins
|
||||
@param cs the Arduino Chip Select / Slave Select pin (default 10 on Uno)
|
||||
*/
|
||||
ENC28J60(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1);
|
||||
|
||||
/**
|
||||
Initialise the Ethernet controller
|
||||
Must be called before sending or receiving Ethernet frames
|
||||
|
||||
@param address the local MAC address for the Ethernet interface
|
||||
@return Returns true if setting up the Ethernet interface was successful
|
||||
*/
|
||||
bool begin(const uint8_t* address, netif *net);
|
||||
|
||||
/**
|
||||
Send an Ethernet frame
|
||||
@param data a pointer to the data to send
|
||||
@param datalen the length of the data in the packet
|
||||
@return the number of bytes transmitted
|
||||
*/
|
||||
virtual uint16_t sendFrame(const uint8_t* data, uint16_t datalen);
|
||||
|
||||
/**
|
||||
Read an Ethernet frame
|
||||
@param buffer a pointer to a buffer to write the packet to
|
||||
@param bufsize the available space in the buffer
|
||||
@return the length of the received packet
|
||||
or 0 if no packet was received
|
||||
*/
|
||||
virtual uint16_t readFrame(uint8_t* buffer, uint16_t bufsize);
|
||||
|
||||
/**
|
||||
Check physical link
|
||||
@return true when physical link is up
|
||||
*/
|
||||
bool isLinked();
|
||||
|
||||
/**
|
||||
Report whether ::isLinked() API is implemented
|
||||
@return true when ::isLinked() API is implemented
|
||||
*/
|
||||
constexpr bool isLinkDetectable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool needsSPI() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
netif *_netif;
|
||||
protected:
|
||||
static constexpr bool interruptIsPossible() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
Read an Ethernet frame size
|
||||
@return the length of data do receive
|
||||
or 0 if no frame was received
|
||||
*/
|
||||
uint16_t readFrameSize();
|
||||
|
||||
/**
|
||||
discard an Ethernet frame
|
||||
@param framesize readFrameSize()'s result
|
||||
*/
|
||||
void discardFrame(uint16_t framesize);
|
||||
|
||||
/**
|
||||
Read an Ethernet frame data
|
||||
readFrameSize() must be called first,
|
||||
its result must be passed into framesize parameter
|
||||
@param buffer a pointer to a buffer to write the frame to
|
||||
@param framesize readFrameSize()'s result
|
||||
@return the length of the received frame
|
||||
or 0 if a problem occurred
|
||||
*/
|
||||
uint16_t readFrameData(uint8_t* frame, uint16_t framesize);
|
||||
|
||||
private:
|
||||
uint8_t is_mac_mii_reg(uint8_t reg);
|
||||
uint8_t readreg(uint8_t reg);
|
||||
void writereg(uint8_t reg, uint8_t data);
|
||||
void setregbitfield(uint8_t reg, uint8_t mask);
|
||||
void clearregbitfield(uint8_t reg, uint8_t mask);
|
||||
void setregbank(uint8_t new_bank);
|
||||
void writedata(const uint8_t* data, int datalen);
|
||||
void writedatabyte(uint8_t byte);
|
||||
int readdata(uint8_t* buf, int len);
|
||||
uint8_t readdatabyte(void);
|
||||
void softreset(void);
|
||||
uint8_t readrev(void);
|
||||
bool reset(void);
|
||||
|
||||
void enc28j60_arch_spi_init(void);
|
||||
uint8_t enc28j60_arch_spi_write(uint8_t data);
|
||||
uint8_t enc28j60_arch_spi_read(void);
|
||||
void enc28j60_arch_spi_select(void);
|
||||
void enc28j60_arch_spi_deselect(void);
|
||||
|
||||
// Previously defined in contiki/core/sys/clock.h
|
||||
void clock_delay_usec(uint16_t dt);
|
||||
|
||||
uint16_t phyread(uint8_t reg);
|
||||
|
||||
uint8_t _bank;
|
||||
int8_t _cs;
|
||||
SPIClass& _spi;
|
||||
|
||||
const uint8_t* _localMac;
|
||||
|
||||
/* readFrame*() state */
|
||||
uint16_t _next, _len;
|
||||
};
|
||||
|
||||
#endif /* ENC28J60_H */
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
This sketch establishes a TCP connection to a "quote of the day" service.
|
||||
It sends a "hello" message, and then prints received data.
|
||||
*/
|
||||
|
||||
#include <W5100lwIP.h>
|
||||
|
||||
const char* host = "djxmmx.net";
|
||||
const uint16_t port = 17;
|
||||
|
||||
Wiznet5100lwIP eth(1 /* chip select */);
|
||||
|
||||
void setup() {
|
||||
// Set up SPI pinout to match your HW
|
||||
SPI.setRX(0);
|
||||
SPI.setCS(1);
|
||||
SPI.setSCK(2);
|
||||
SPI.setTX(3);
|
||||
|
||||
Serial.begin(115200);
|
||||
delay(5000);
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
Serial.println("Starting Ethernet port");
|
||||
|
||||
// Start the Ethernet port
|
||||
if (!eth.begin()) {
|
||||
Serial.println("No wired Ethernet hardware detected. Check pinouts, wiring.");
|
||||
while (1) {
|
||||
delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
while (!eth.connected()) {
|
||||
Serial.print(".");
|
||||
delay(500);
|
||||
}
|
||||
|
||||
Serial.println("");
|
||||
Serial.println("Ethernet connected");
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(eth.localIP());
|
||||
}
|
||||
|
||||
void loop() {
|
||||
static bool wait = false;
|
||||
|
||||
Serial.print("connecting to ");
|
||||
Serial.print(host);
|
||||
Serial.print(':');
|
||||
Serial.println(port);
|
||||
|
||||
// Use WiFiClient class to create TCP connections
|
||||
WiFiClient client;
|
||||
if (!client.connect(host, port)) {
|
||||
Serial.println("connection failed");
|
||||
delay(5000);
|
||||
return;
|
||||
}
|
||||
|
||||
// This will send a string to the server
|
||||
Serial.println("sending data to server");
|
||||
if (client.connected()) {
|
||||
client.println("hello from RP2040");
|
||||
}
|
||||
|
||||
// wait for data to be available
|
||||
unsigned long timeout = millis();
|
||||
while (client.available() == 0) {
|
||||
if (millis() - timeout > 5000) {
|
||||
Serial.println(">>> Client Timeout !");
|
||||
client.stop();
|
||||
delay(60000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Read all the lines of the reply from server and print them to Serial
|
||||
Serial.println("receiving from remote server");
|
||||
// not testing 'client.connected()' since we do not need to send data here
|
||||
while (client.available()) {
|
||||
char ch = static_cast<char>(client.read());
|
||||
Serial.print(ch);
|
||||
}
|
||||
|
||||
// Close the connection
|
||||
Serial.println();
|
||||
Serial.println("closing connection");
|
||||
client.stop();
|
||||
|
||||
if (wait) {
|
||||
delay(300000); // execute once every 5 minutes, don't flood remote service
|
||||
}
|
||||
wait = true;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Library (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
W5100lwIP KEYWORD1
|
||||
Wiznet5100lwIP KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -0,0 +1,10 @@
|
||||
name=lwIP_w5100
|
||||
version=1
|
||||
author=Nicholas Humfrey
|
||||
maintainer=esp8266/Arduino
|
||||
sentence=Ethernet driver
|
||||
paragraph=Wiznet5100 ethernet drivers for lwIP and esp8266 Arduino from https://github.com/njh/W5100MacRaw
|
||||
category=Communication
|
||||
url=https://github.com/esp8266/Arduino
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user