====== Placa de desenvolupament ESP32 ======
El xip ESP32 de Espressif és molt popular per a integrar en dispositius IoT i sistemes "embedded" (incrustats o embeguts). Disposa de funcions de comunicacions necessàries com Wifi, Bluetooth, BLE (Bluetooth Low Emission), NFC i una capacitat de còmput moderada. I se li poden connectar dispositius com càmeres o d'altres.
L'exemple que treballarem farà una captura de foto amb la càmera i l'enviarà via BLE (Bluetooth Low Emission).
Aquest codi d'exemple està pensat per a enviar dades i que una aplicació Android la rebi, la podeu trobar a [[Android Bluetooth]].
\\
===== Placa de desenvolupament =====
Emprarem una placa de desenvolupament Freenove ESP32-S3-WROOM-1 (hi ha moltes variants).
Per fer-la funcionar es necessita [[https://www.arduino.cc/en/software/|Arduino IDE]], i configurar-la amb el perfil "ESP32S3 Dev Module" i els paràmetres següents:
{{arduino-esp32-freenove-config.png?400}}
\\
===== Codi C : captura amb càmera i enviament BLE =====
El pinatge de connexionat amb la càmera que hi ha al codi està configurat per a la placa Freenove esmentada. Encara que sigui el mateix processador, cada placa de desenvolupament té la seva pròpia implementació del hardware extern com la càmera.
/*
* ESP32-S3 FREENOVE: Captura foto amb botó BOOT i envia per BLE
* Necessita la llibreria ESP32-Camera
*/
#include
#include
#include
#include
#include // Necessitaràs instal·lar aquesta llibreria
// Configuració dels pins de la càmera (ajusta segons la teva placa)
// Configuració típica per ESP32-S3 amb càmera OV2640
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5
#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define XCLK_GPIO_NUM 15
#define PCLK_GPIO_NUM 13
#define Y9_GPIO_NUM 16
#define Y8_GPIO_NUM 17
#define Y7_GPIO_NUM 18
#define Y6_GPIO_NUM 12
#define Y5_GPIO_NUM 10
#define Y4_GPIO_NUM 8
#define Y3_GPIO_NUM 9
#define Y2_GPIO_NUM 11
#define LED_PIN 43 // LED per indicar captura
#define BOOT_BUTTON 0 // Botó BOOT
// UUIDs del servei BLE
#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"
// Variables globals
String deviceName = "ESP32-Camera-9";
BLEServer *pServer = NULL;
BLECharacteristic *pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
bool isCapturing = false;
// Configuració de la càmera
camera_config_t config;
// Variables per al botó
bool lastButtonState = HIGH;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;
// Callbacks BLE
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Dispositiu BLE connectat");
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Dispositiu BLE desconnectat");
// Reiniciem la publicitat per permetre noves connexions
BLEDevice::startAdvertising();
}
};
/*
* Versió simplificada - Envia foto per BLE com a text base64
*/
void setup() {
Serial.begin(115200);
Serial.println("\n=== ESP32-S3 FREENOVE Camera BLE (Base64) ===");
pinMode(LED_PIN, OUTPUT);
pinMode(BOOT_BUTTON, INPUT_PULLUP);
initBLE();
if (!initCamera()) {
Serial.println("Error camera!");
while(1) delay(1000);
}
Serial.println("Sistema llest. Prem BOOT per foto!");
}
void loop() {
// Gestió connexió BLE
if (!deviceConnected && oldDeviceConnected) {
delay(500);
pServer->startAdvertising();
oldDeviceConnected = deviceConnected;
}
if (deviceConnected && !oldDeviceConnected) {
oldDeviceConnected = deviceConnected;
}
// Botó BOOT
if (digitalRead(BOOT_BUTTON) == LOW && !isCapturing) {
delay(50); // Debounce simple
if (digitalRead(BOOT_BUTTON) == LOW) {
takeAndSendPhotoBase64();
}
}
delay(10);
}
// Funció per inicialitzar BLE
void initBLE() {
BLEDevice::init(deviceName);
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
BLEService *pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE |
BLECharacteristic::PROPERTY_NOTIFY |
BLECharacteristic::PROPERTY_INDICATE
);
pService->start();
// Configurar publicitat
BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // funcions que ajuden amb iPhone
pAdvertising->setMinPreferred(0x12);
BLEDevice::startAdvertising();
Serial.println("BLE inicialitzat. Nom del dispositiu: ESP32-Camera");
Serial.println("Connecta't des del teu dispositiu BLE");
}
// Funció per inicialitzar la càmera
bool initCamera() {
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
// Qualitat de la imatge (ajusta segons la memòria disponible)
config.frame_size = FRAMESIZE_SVGA; // 800x600
config.jpeg_quality = 10; // 0-63 (més baix = millor qualitat)
config.fb_count = 1;
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Error inicialitzant càmera: 0x%x", err);
return false;
}
// Configuració addicional de la càmera
sensor_t *s = esp_camera_sensor_get();
if (s != NULL) {
// Ajustos automàtics
s->set_brightness(s, 0); // -2 to 2
s->set_contrast(s, 0); // -2 to 2
s->set_saturation(s, 0); // -2 to 2
s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect)
s->set_whitebal(s, 1); // 0 = disable, 1 = enable
s->set_awb_gain(s, 1); // 0 = disable, 1 = enable
s->set_wb_mode(s, 0); // 0 to 4
s->set_exposure_ctrl(s, 1); // 0 = disable, 1 = enable
s->set_aec2(s, 0); // 0 = disable, 1 = enable
s->set_ae_level(s, 0); // -2 to 2
s->set_aec_value(s, 1000); // 0 to 1200
s->set_gain_ctrl(s, 1); // 0 = disable, 1 = enable
s->set_agc_gain(s, 5); // 0 to 30
s->set_gainceiling(s, (gainceiling_t)6); // 0 to 6
s->set_bpc(s, 0); // 0 = disable, 1 = enable
s->set_wpc(s, 1); // 0 = disable, 1 = enable
s->set_raw_gma(s, 1); // 0 = disable, 1 = enable
s->set_lenc(s, 1); // 0 = disable, 1 = enable
s->set_hmirror(s, 0); // 0 = disable, 1 = enable
s->set_vflip(s, 1); // 0 = disable, 1 = enable
s->set_dcw(s, 1); // 0 = disable, 1 = enable
s->set_colorbar(s, 0); // 0 = disable, 1 = enable
}
Serial.println("Càmera inicialitzada correctament");
return true;
}
// Funció per capturar i enviar com base64
void takeAndSendPhotoBase64() {
if (!deviceConnected) {
Serial.println("Connecta primer un dispositiu BLE");
return;
}
Serial.println("Capturant foto...");
isCapturing = true;
digitalWrite(LED_PIN, HIGH);
// Capturar
camera_fb_t *fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Error captura");
digitalWrite(LED_PIN, LOW);
isCapturing = false;
return;
}
Serial.printf("Foto: %d bytes\n", fb->len);
// Convertir a base64
String base64Image = base64::encode(fb->buf, fb->len);
Serial.printf("Base64: %d caràcters\n", base64Image.length());
// Enviar per BLE (fragmentat)
sendBase64BLE(base64Image);
// Alliberar
esp_camera_fb_return(fb);
digitalWrite(LED_PIN, LOW);
isCapturing = false;
Serial.println("Foto enviada!");
}
// Enviar base64 per BLE fragmentat
void sendBase64BLE(String &base64Data) {
const int CHUNK_SIZE = 100; // Caràcters per paquet
// Enviar mida total
uint32_t totalSize = base64Data.length();
pCharacteristic->setValue((uint8_t*)&totalSize, sizeof(totalSize));
pCharacteristic->notify();
delay(20);
// Enviar fragmentat
for (size_t i = 0; i < base64Data.length(); i += CHUNK_SIZE) {
String chunk = base64Data.substring(i, min(i + CHUNK_SIZE, base64Data.length()));
pCharacteristic->setValue(chunk.c_str());
pCharacteristic->notify();
delay(20); // Pausa per evitar sobrecàrrega
if (i % 1000 == 0 || i + CHUNK_SIZE >= base64Data.length()) {
int percent = (i * 100) / base64Data.length();
Serial.printf("Enviant BLE: %d%%\n", percent);
}
}
// Marcar final
//pCharacteristic->setValue("END");
//pCharacteristic->notify();
}
\\