SHA256
Compare commits
9
Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
9d2172fa7d | ||
|
|
aa6f246490 | ||
|
|
c1d1f8c8f2 | ||
|
|
6ca5f7f68d | ||
|
|
80a703673f | ||
|
|
ccf59b22e5 | ||
|
|
09133b1e5d | ||
|
|
d0b3d52d95 | ||
|
|
4cab6e68e6 |
No files matched your search
@@ -1,2 +1,433 @@
|
||||
# ESP32-WiFi-BME
|
||||
|
||||
API modular para adquisición ADC continua usando DMA en ESP32-S3 y transmisión binaria mediante WebSocket utilizando FreeRTOS.
|
||||
|
||||
---
|
||||
|
||||
# Arquitectura General
|
||||
|
||||
```text
|
||||
ADC -> DMA -> RingBuffer -> WebSocket
|
||||
```
|
||||
|
||||
El proyecto está dividido en APIs modulares independientes:
|
||||
|
||||
- `ADCconf`
|
||||
- `WiFiConf`
|
||||
- `WebSocketHandler`
|
||||
|
||||
Cada API encapsula una funcionalidad específica para facilitar integración, mantenimiento y reutilización.
|
||||
|
||||
---
|
||||
|
||||
# ADCconf API
|
||||
|
||||
API encargada de configurar y manejar el ADC continuo utilizando DMA mediante la API ESP-IDF.
|
||||
|
||||
## Características
|
||||
|
||||
- ADC continuo
|
||||
- DMA
|
||||
- Parseo automático de muestras
|
||||
- Callback mediante interrupciones
|
||||
- Conversión de datos crudos a `uint16_t`
|
||||
|
||||
---
|
||||
|
||||
## Configuración
|
||||
|
||||
### Inclusión
|
||||
|
||||
```cpp
|
||||
#include "ADCconf.h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Definición de buffers
|
||||
|
||||
```cpp
|
||||
#define MS_BLOQUE 10
|
||||
|
||||
adc_channel_t CHANNEL = ADC_CHANNEL_3;
|
||||
|
||||
const uint32_t HZ_ADC = 1000;
|
||||
|
||||
const uint32_t MUESTRAS_BLOQUE =
|
||||
(HZ_ADC * MS_BLOQUE) / 1000;
|
||||
|
||||
uint8_t dma_buffer[
|
||||
MUESTRAS_BLOQUE *
|
||||
SOC_ADC_DIGI_RESULT_BYTES
|
||||
] = {0};
|
||||
|
||||
uint16_t samples_buffer[
|
||||
MUESTRAS_BLOQUE
|
||||
] = {0};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Crear objeto ADC
|
||||
|
||||
```cpp
|
||||
ADC_BME adc1_3;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Configuración ADC
|
||||
|
||||
```cpp
|
||||
adc1_3.config(
|
||||
MUESTRAS_BLOQUE,
|
||||
CHANNEL,
|
||||
HZ_ADC,
|
||||
dma_buffer
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Inicializar ADC
|
||||
|
||||
```cpp
|
||||
adc1_3.setup();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Lectura de muestras
|
||||
|
||||
```cpp
|
||||
uint32_t count =
|
||||
adc1_3.read(samples_buffer);
|
||||
```
|
||||
|
||||
La función retorna:
|
||||
|
||||
- `0` si no existen datos disponibles
|
||||
- cantidad de muestras válidas si existen datos DMA
|
||||
|
||||
---
|
||||
|
||||
## Flujo Interno
|
||||
|
||||
```text
|
||||
ADC HW
|
||||
↓
|
||||
DMA Buffer
|
||||
↓
|
||||
adc_continuous_read()
|
||||
↓
|
||||
adc_continuous_parse_data()
|
||||
↓
|
||||
samples_buffer[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# WiFiConf API
|
||||
|
||||
API modular para configuración WiFi en modo STA.
|
||||
|
||||
---
|
||||
|
||||
## Características
|
||||
|
||||
- Configuración IP estática
|
||||
- Manejo de eventos WiFi
|
||||
- Reconexión automática
|
||||
- Encapsulación de configuración de red
|
||||
|
||||
---
|
||||
|
||||
## Inclusión
|
||||
|
||||
```cpp
|
||||
#include "WiFiConf.h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Crear objeto WiFi
|
||||
|
||||
```cpp
|
||||
wifi_conf sta_esp32;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuración de red
|
||||
|
||||
```cpp
|
||||
const char* SSID = "MiRed";
|
||||
const char* PASSWD = "password";
|
||||
|
||||
IPAddress local_ip(192,168,0,220);
|
||||
IPAddress gateway(192,168,0,1);
|
||||
IPAddress subnet(255,255,255,0);
|
||||
|
||||
IPAddress dns1(192,168,0,1);
|
||||
IPAddress dns2(1,1,1,1);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conexión WiFi
|
||||
|
||||
```cpp
|
||||
sta_esp32.conectar(
|
||||
SSID,
|
||||
PASSWD,
|
||||
local_ip,
|
||||
gateway,
|
||||
subnet,
|
||||
dns1,
|
||||
dns2
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Eventos Implementados
|
||||
|
||||
La API maneja automáticamente:
|
||||
|
||||
- conexión WiFi
|
||||
- desconexión
|
||||
- reconexión
|
||||
- obtención de IP
|
||||
|
||||
---
|
||||
|
||||
# WebSocketHandler API
|
||||
|
||||
API encargada de manejar eventos WebSocket.
|
||||
|
||||
---
|
||||
|
||||
## Características
|
||||
|
||||
- detección de clientes
|
||||
- conexión/desconexión
|
||||
- recepción de mensajes
|
||||
- manejo de bandera global de conexión
|
||||
|
||||
---
|
||||
|
||||
## Inclusión
|
||||
|
||||
```cpp
|
||||
#include "WebSocketHandler.h"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Crear servidor WebSocket
|
||||
|
||||
```cpp
|
||||
WebSocketsServer webSocket(81);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Inicialización
|
||||
|
||||
```cpp
|
||||
webSocket.begin();
|
||||
webSocket.onEvent(webSocketEvent);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Loop principal
|
||||
|
||||
```cpp
|
||||
webSocket.loop();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Transmisión BIN
|
||||
|
||||
```cpp
|
||||
webSocket.broadcastBIN(
|
||||
(uint8_t*)buffer,
|
||||
size
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Handler de eventos
|
||||
|
||||
```cpp
|
||||
void webSocketEvent(
|
||||
uint8_t num,
|
||||
WStype_t type,
|
||||
uint8_t * payload,
|
||||
size_t length
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Eventos soportados
|
||||
|
||||
### Cliente conectado
|
||||
|
||||
```cpp
|
||||
WStype_CONNECTED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Cliente desconectado
|
||||
|
||||
```cpp
|
||||
WStype_DISCONNECTED
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Mensajes recibidos
|
||||
|
||||
```cpp
|
||||
WStype_TEXT
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Integración con FreeRTOS
|
||||
|
||||
El sistema utiliza multitarea mediante FreeRTOS.
|
||||
|
||||
---
|
||||
|
||||
## ADCTask
|
||||
|
||||
Responsable de:
|
||||
|
||||
- adquisición ADC
|
||||
- lectura DMA
|
||||
- inserción en RingBuffer
|
||||
|
||||
---
|
||||
|
||||
## WebSocketTask
|
||||
|
||||
Responsable de:
|
||||
|
||||
- `webSocket.loop()`
|
||||
- transmisión BIN
|
||||
- extracción del RingBuffer
|
||||
|
||||
---
|
||||
|
||||
# RingBuffer
|
||||
|
||||
Se utiliza `xRingbuffer` para desacoplar:
|
||||
|
||||
```text
|
||||
Adquisición ADC
|
||||
```
|
||||
|
||||
de:
|
||||
|
||||
```text
|
||||
Transmisión WebSocket
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creación
|
||||
|
||||
```cpp
|
||||
RingbufHandle_t bufferHandle =
|
||||
xRingbufferCreate(
|
||||
SIZE_BUFFER,
|
||||
RINGBUF_TYPE_NOSPLIT
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Inserción
|
||||
|
||||
```cpp
|
||||
xRingbufferSend(
|
||||
bufferHandle,
|
||||
samples_buffer,
|
||||
count * sizeof(uint16_t),
|
||||
pdMS_TO_TICKS(1000)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recepción
|
||||
|
||||
```cpp
|
||||
uint16_t* receivedMessage =
|
||||
(uint16_t*)xRingbufferReceive(
|
||||
bufferHandle,
|
||||
&receivedMessageSize,
|
||||
pdMS_TO_TICKS(1000)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Cliente Python
|
||||
|
||||
Ejemplo de recepción de datos binarios.
|
||||
|
||||
```python
|
||||
from websocket import create_connection
|
||||
import struct
|
||||
|
||||
ws = create_connection(
|
||||
"ws://192.168.0.220:81"
|
||||
)
|
||||
|
||||
while True:
|
||||
|
||||
data = ws.recv()
|
||||
|
||||
if isinstance(data, bytes):
|
||||
|
||||
samples = struct.unpack(
|
||||
f"<{len(data)//2}H",
|
||||
data
|
||||
)
|
||||
|
||||
print(samples)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Compilación
|
||||
|
||||
El proyecto esta construido en PlatformIO, para compilar el poryecto es importante instalar sus herramientas en el sistema.
|
||||
|
||||
|
||||
# Dependencias
|
||||
|
||||
```ini
|
||||
lib_deps =
|
||||
links2004/WebSockets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Hardware Compatible
|
||||
|
||||
- ESP32-S3 DevKitC-1
|
||||
- ESP32-S3 N8
|
||||
|
||||
---
|
||||
|
||||
# Autor
|
||||
|
||||
Sebastian Mariano Flores
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "esp_adc/adc_continuous.h"
|
||||
|
||||
class ADC_BME {
|
||||
private:
|
||||
uint32_t _FRAME_SIZE;
|
||||
uint32_t _POOL_SIZE;
|
||||
uint32_t _HZ_ADC;
|
||||
adc_channel_t _channel;
|
||||
uint8_t* _dma_buffer;
|
||||
volatile bool dma_data_ready = false;
|
||||
adc_continuous_handle_t handle_ADC = NULL;
|
||||
static bool IRAM_ATTR adc_dma_callback(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data);
|
||||
public:
|
||||
void config(const uint32_t muestras_bloque, adc_channel_t channel, const uint32_t hz_adc, uint8_t* dma_buffer);
|
||||
void setup();
|
||||
uint32_t read(uint16_t* samples_buffer);
|
||||
};
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "ADCconf.h"
|
||||
|
||||
bool IRAM_ATTR ADC_BME::adc_dma_callback(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data){
|
||||
ADC_BME* adc =static_cast<ADC_BME*>(user_data);
|
||||
adc->dma_data_ready = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ADC_BME::config(const uint32_t muestras_bloque, adc_channel_t channel, const uint32_t hz_adc, uint8_t* dma_buffer){
|
||||
_FRAME_SIZE=muestras_bloque*4;
|
||||
_POOL_SIZE=_FRAME_SIZE*4;
|
||||
_channel=channel;
|
||||
_HZ_ADC=hz_adc;
|
||||
_dma_buffer=dma_buffer;
|
||||
}
|
||||
|
||||
void ADC_BME::setup(){
|
||||
adc_continuous_handle_cfg_t ADC_config = {
|
||||
.max_store_buf_size = _POOL_SIZE,
|
||||
.conv_frame_size = _FRAME_SIZE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_continuous_new_handle(&ADC_config, &handle_ADC));
|
||||
|
||||
adc_digi_pattern_config_t channel_config = {0};
|
||||
channel_config.channel = _channel;
|
||||
channel_config.atten = ADC_ATTEN_DB_12;
|
||||
channel_config.bit_width = ADC_BITWIDTH_12;
|
||||
channel_config.unit = ADC_UNIT_1;
|
||||
|
||||
adc_continuous_config_t adc_config = {
|
||||
.pattern_num = 1,
|
||||
.adc_pattern = &channel_config,
|
||||
.sample_freq_hz = _HZ_ADC,
|
||||
.conv_mode = ADC_CONV_SINGLE_UNIT_1,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(adc_continuous_config(handle_ADC, &adc_config));
|
||||
|
||||
adc_continuous_evt_cbs_t cb_config = {
|
||||
.on_conv_done = adc_dma_callback,
|
||||
.on_pool_ovf = NULL
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(handle_ADC, &cb_config, this));
|
||||
ESP_ERROR_CHECK(adc_continuous_start(handle_ADC));
|
||||
Serial.println("DMA Iniciado con sistema de bandera.");
|
||||
}
|
||||
|
||||
uint32_t ADC_BME::read(uint16_t* samples_buffer) {
|
||||
|
||||
if (!dma_data_ready) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
dma_data_ready = false;
|
||||
uint32_t ret_num = 0;
|
||||
|
||||
// Leer datos DMA
|
||||
esp_err_t ret = adc_continuous_read(handle_ADC,_dma_buffer,_FRAME_SIZE,&ret_num,0);
|
||||
|
||||
if (ret != ESP_OK)
|
||||
return 0;
|
||||
|
||||
// Buffer temporal parseado
|
||||
adc_continuous_data_t parsed_data[_FRAME_SIZE/4];
|
||||
|
||||
uint32_t parsed_count = 0;
|
||||
|
||||
// Parsear datos
|
||||
esp_err_t parse_ret =
|
||||
adc_continuous_parse_data(
|
||||
handle_ADC,
|
||||
_dma_buffer,
|
||||
ret_num,
|
||||
parsed_data,
|
||||
&parsed_count
|
||||
);
|
||||
|
||||
if (parse_ret != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t valid_samples = 0;
|
||||
// Copiar muestras limpias
|
||||
for (uint32_t i = 0; i < parsed_count; i++) {
|
||||
if (!parsed_data[i].valid)
|
||||
continue;
|
||||
|
||||
samples_buffer[valid_samples] = parsed_data[i].raw_data;
|
||||
valid_samples++;
|
||||
}
|
||||
|
||||
return valid_samples;
|
||||
}
|
||||
+74
-11
@@ -1,38 +1,101 @@
|
||||
#include "Arduino.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
|
||||
#include "ADCconf.h"
|
||||
#include "WiFiConf.h"
|
||||
#include "WebSocketHandler.h"
|
||||
|
||||
#include <WebSocketsServer.h>
|
||||
|
||||
// API ESP-IDF para ADC modo continuo (uso de DMA)
|
||||
#define MS_BLOQUE 10
|
||||
adc_channel_t CHANNEL = ADC_CHANNEL_3;
|
||||
const uint32_t HZ_ADC = 1000; // Limitado a 611 - 83333 para el ESP32-S3
|
||||
const uint32_t MUESTRAS_BLOQUE = (HZ_ADC * MS_BLOQUE) / 1000;
|
||||
uint8_t dma_buffer[MUESTRAS_BLOQUE * SOC_ADC_DIGI_RESULT_BYTES] = {0};
|
||||
uint16_t samples_buffer[MUESTRAS_BLOQUE] = {0};
|
||||
const uint32_t SIZE_BUFFER = MUESTRAS_BLOQUE*10;
|
||||
|
||||
ADC_BME adc1_3;
|
||||
|
||||
|
||||
// Conf de ESP32-S3 en modo STA
|
||||
const char* SSID="Tenda_32C850";
|
||||
const char* PASSWD="rhLcrMEcY^Xjvq66";
|
||||
IPAddress local_ip(192,168,0,220);
|
||||
IPAddress gateway(192,168,0,1);
|
||||
const char* SSID="debian";
|
||||
const char* PASSWD="aH2T9GFA";
|
||||
IPAddress local_ip(10,42,0,100);
|
||||
IPAddress gateway(40,42,0,1);
|
||||
IPAddress subnet(255,255,255,0);
|
||||
IPAddress dns1(192,168,0,100);
|
||||
IPAddress dns1(10,42,0,1);
|
||||
IPAddress dns2(1,1,1,1);
|
||||
volatile bool websocket_connected = false;
|
||||
|
||||
wifi_conf sta_esp32;
|
||||
WebSocketsServer webSocket(81);
|
||||
volatile bool websocket_connected = false;
|
||||
|
||||
void setup() {
|
||||
// FreeRTOS
|
||||
TaskHandle_t ADCTaskHandle = NULL;
|
||||
TaskHandle_t WebSocketTaskHandle = NULL;
|
||||
RingbufHandle_t bufferHandle = xRingbufferCreate(SIZE_BUFFER, RINGBUF_TYPE_NOSPLIT);
|
||||
|
||||
void ADCTask(void *parameter);
|
||||
void WebSocketTask(void *parameter);
|
||||
|
||||
void setup(){
|
||||
Serial.begin(230400);
|
||||
|
||||
sta_esp32.conectar(SSID, PASSWD, local_ip, gateway, subnet, dns1, dns2);
|
||||
|
||||
webSocket.begin();
|
||||
webSocket.onEvent(webSocketEvent);
|
||||
|
||||
adc1_3.config(MUESTRAS_BLOQUE, CHANNEL, HZ_ADC, dma_buffer);
|
||||
adc1_3.setup();
|
||||
|
||||
//Iniciar el buffer circular
|
||||
if (bufferHandle == NULL) {
|
||||
Serial.printf("Failed to create ring buffer!");
|
||||
return;
|
||||
}
|
||||
|
||||
xTaskCreatePinnedToCore( ADCTask, "ADCTask", 10000, NULL, 1, &ADCTaskHandle, 0);
|
||||
xTaskCreatePinnedToCore( WebSocketTask, "WebSocketTask", 10000, NULL, 1, &WebSocketTaskHandle, 1);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
void loop(){
|
||||
}
|
||||
|
||||
void ADCTask(void *parameter){
|
||||
for(;;) {
|
||||
|
||||
if (!websocket_connected) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t count = adc1_3.read(samples_buffer);
|
||||
if (count > 0) {
|
||||
if (xRingbufferSend(bufferHandle, samples_buffer, count * sizeof(uint16_t), pdMS_TO_TICKS(1000)) != pdTRUE) {
|
||||
Serial.println("Failed to send message to ring buffer!");
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketTask(void *parameter){
|
||||
for(;;) {
|
||||
|
||||
webSocket.loop();
|
||||
|
||||
uint8_t imagen = 0;
|
||||
webSocket.broadcastBIN(&imagen, 1);
|
||||
delay(10);
|
||||
size_t receivedMessageSize;
|
||||
uint16_t* receivedMessage = (uint16_t*)xRingbufferReceive(bufferHandle, &receivedMessageSize, pdMS_TO_TICKS(1000));
|
||||
|
||||
// 6. Check if the receive operation was successful
|
||||
if (receivedMessage != NULL) {
|
||||
webSocket.broadcastBIN((uint8_t*)receivedMessage, receivedMessageSize);
|
||||
vRingbufferReturnItem(bufferHandle, (void*)receivedMessage);
|
||||
} else {
|
||||
Serial.println("Failed t+o receive message from ring buffer!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
import time
|
||||
import struct
|
||||
|
||||
from websocket import create_connection
|
||||
|
||||
ws = create_connection("ws://192.168.0.220:81")
|
||||
i = 0
|
||||
ws = create_connection("ws://10.42.0.100:81")
|
||||
|
||||
print("Conectado")
|
||||
|
||||
i = 0
|
||||
|
||||
try:
|
||||
while True:
|
||||
data = ws.recv()
|
||||
i += 1
|
||||
|
||||
if isinstance(data, bytes):
|
||||
print(f"BIN: {list(data)}")
|
||||
# Convertir bytes -> uint16_t
|
||||
samples = struct.unpack(f"<{len(data) // 2}H", data)
|
||||
|
||||
print("ADC:", samples)
|
||||
|
||||
else:
|
||||
print(f"TXT: {data}")
|
||||
print("TXT:", data)
|
||||
|
||||
print(f"DEBUG {i}")
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("Cerrando conexión")
|
||||
|
||||
ws.close()
|
||||
Reference in new issue
Block a user