Cody Tolene 2 лет назад
Родитель
Сommit
e0371f5a0c

+ 10 - 0
firmware-update/dithering.h

@@ -0,0 +1,10 @@
+#ifndef DITHERING_H
+#define DITHERING_H
+
+#include <esp_camera.h>
+
+#include "globals.h"
+
+void dither_image(camera_fb_t *frame_buffer, CameraModel *model);
+
+#endif

+ 57 - 0
firmware-update/dithering.ino

@@ -0,0 +1,57 @@
+#include "dithering.h"
+
+void dither_image(camera_fb_t *frame_buffer, CameraModel *model)
+{
+    for (uint8_t y = 0; y < frame_buffer->height; ++y)
+    {
+        for (uint8_t x = 0; x < frame_buffer->width; ++x)
+        {
+            size_t current = (y * frame_buffer->width) + x;
+            uint8_t oldpixel = frame_buffer->buf[current];
+            uint8_t newpixel = oldpixel >= 128 ? 255 : 0;
+            frame_buffer->buf[current] = newpixel;
+            int8_t quant_error = oldpixel - newpixel;
+
+            // Apply error diffusion based on the selected algorithm
+            switch (model->ditherAlgorithm)
+            {
+            case JARVIS_JUDICE_NINKE:
+                frame_buffer->buf[(y * frame_buffer->width) + x + 1] += quant_error * 7 / 48;
+                frame_buffer->buf[(y * frame_buffer->width) + x + 2] += quant_error * 5 / 48;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 2] += quant_error * 3 / 48;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] += quant_error * 5 / 48;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x] += quant_error * 7 / 48;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] += quant_error * 5 / 48;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 2] += quant_error * 3 / 48;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x - 2] += quant_error * 1 / 48;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x - 1] += quant_error * 3 / 48;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x] += quant_error * 5 / 48;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x + 1] += quant_error * 3 / 48;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x + 2] += quant_error * 1 / 48;
+                break;
+            case STUCKI:
+                frame_buffer->buf[(y * frame_buffer->width) + x + 1] += quant_error * 8 / 42;
+                frame_buffer->buf[(y * frame_buffer->width) + x + 2] += quant_error * 4 / 42;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 2] += quant_error * 2 / 42;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] += quant_error * 4 / 42;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x] += quant_error * 8 / 42;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] += quant_error * 4 / 42;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 2] += quant_error * 2 / 42;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x - 2] += quant_error * 1 / 42;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x - 1] += quant_error * 2 / 42;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x] += quant_error * 4 / 42;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x + 1] += quant_error * 2 / 42;
+                frame_buffer->buf[(y + 2) * frame_buffer->width + x + 2] += quant_error * 1 / 42;
+                break;
+            case FLOYD_STEINBERG:
+            default:
+                // Default to Floyd-Steinberg dithering if an invalid algorithm is selected
+                frame_buffer->buf[(y * frame_buffer->width) + x + 1] += quant_error * 7 / 16;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x - 1] += quant_error * 3 / 16;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x] += quant_error * 5 / 16;
+                frame_buffer->buf[(y + 1) * frame_buffer->width + x + 1] += quant_error * 1 / 16;
+                break;
+            }
+        }
+    }
+}

+ 10 - 0
firmware-update/firmware.h

@@ -0,0 +1,10 @@
+#ifndef FIRMWARE_H
+#define FIRMWARE_H
+
+#include <esp_camera.h>
+
+#include "initialize.h"
+#include "process_image.h"
+#include "serial_commands.h"
+
+#endif

+ 27 - 0
firmware-update/firmware.ino

@@ -0,0 +1,27 @@
+#include "firmware.h"
+
+CameraModel model;
+
+// Entry point of the program.
+void setup()
+{
+    Serial.begin(115200); // Previously 230400, 115200 seems more stable.
+    initialize(&model);
+}
+
+// Main loop of the program.
+void loop()
+{
+    if (model.isStreamEnabled)
+    {
+        camera_fb_t *frame_buffer = esp_camera_fb_get();
+        if (frame_buffer)
+        {
+            process_image(frame_buffer, &model);
+            // Return the frame buffer back to the camera driver.
+            esp_camera_fb_return(frame_buffer);
+        }
+        delay(25);
+    }
+    serial_commands(&model);
+}

+ 59 - 0
firmware-update/globals.h

@@ -0,0 +1,59 @@
+#ifndef GLOBALS_H
+#define GLOBALS_H
+
+#include <stdint.h>
+
+// Define Pin numbers used by the camera.
+#define FLASH_GPIO_NUM 4
+#define HREF_GPIO_NUM 23
+#define PCLK_GPIO_NUM 22
+#define PWDN_GPIO_NUM 32
+#define RESET_GPIO_NUM -1
+#define SIOC_GPIO_NUM 27
+#define SIOD_GPIO_NUM 26
+#define VSYNC_GPIO_NUM 25
+#define XCLK_GPIO_NUM 0
+#define Y2_GPIO_NUM 5
+#define Y3_GPIO_NUM 18
+#define Y4_GPIO_NUM 19
+#define Y5_GPIO_NUM 21
+#define Y6_GPIO_NUM 36
+#define Y7_GPIO_NUM 39
+#define Y8_GPIO_NUM 34
+#define Y9_GPIO_NUM 35
+
+/**
+ * The dithering algorithms available.
+ */
+enum DitheringAlgorithm : uint8_t
+{
+    FLOYD_STEINBERG,
+    JARVIS_JUDICE_NINKE,
+    STUCKI
+};
+
+typedef struct CameraModel
+{
+    /**
+     * Flag to enable or disable dithering.
+     */
+    bool isDitheringDisabled;
+    /**
+     * Flag to represent the flash state when saving pictures to the Flipper.
+     */
+    bool isFlashEnabled;
+    /**
+     * Flag to invert pixel colors.
+     */
+    bool isInverted;
+    /**
+     * Flag to stop or start the stream.
+     */
+    bool isStreamEnabled;
+    /**
+     * Holds the currently selected dithering algorithm.
+     */
+    DitheringAlgorithm ditherAlgorithm;
+} CameraModel;
+
+#endif

+ 13 - 0
firmware-update/initialize.h

@@ -0,0 +1,13 @@
+#ifndef INITIALIZE_H
+#define INITIALIZE_H
+
+#include <esp_camera.h>
+#include <FS.h>
+
+#include "globals.h"
+
+camera_config_t config;
+
+void initialize(CameraModel *model);
+
+#endif

+ 72 - 0
firmware-update/initialize.ino

@@ -0,0 +1,72 @@
+#include "initialize.h"
+
+void initialize(CameraModel *model)
+{
+    // Set up the model defaults.
+    model->isDitheringDisabled = false;
+    model->isInverted = false;
+    model->isFlashEnabled = false;
+    model->isStreamEnabled = true;
+    model->ditherAlgorithm = FLOYD_STEINBERG;
+
+    // Set initial camera configurations for grayscale.
+    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_GRAYSCALE;
+    config.frame_size = FRAMESIZE_QQVGA;
+    config.fb_count = 1;
+
+    // Initialize camera.
+    esp_err_t err = esp_camera_init(&config);
+    if (err != ESP_OK)
+    {
+        return;
+    }
+
+    // Check if the flash is already on, if it is turn it off.
+    if (digitalRead(FLASH_GPIO_NUM) == HIGH)
+    {
+        pinMode(FLASH_GPIO_NUM, OUTPUT);
+        digitalWrite(FLASH_GPIO_NUM, LOW);
+        model->isFlashEnabled = false;
+    }
+
+    // Set global reference to the camera.
+    sensor_t *cam = esp_camera_sensor_get();
+
+    // Set up the frame buffer reference.
+    camera_fb_t *frame_buffer = esp_camera_fb_get();
+
+    // Set initial brightness.
+    cam->set_brightness(cam, 0);
+
+    // Set initial contrast.
+    cam->set_contrast(cam, 0);
+
+    // Set initial rotation.
+    cam->set_vflip(cam, true);
+    cam->set_hmirror(cam, true);
+
+    // Set initial saturation.
+    cam->set_saturation(cam, 0);
+
+    // Set initial sharpness.
+    cam->set_sharpness(cam, 0);
+}

+ 12 - 0
firmware-update/process_image.h

@@ -0,0 +1,12 @@
+#ifndef PROCESS_IMAGE_H
+#define PROCESS_IMAGE_H
+
+#include <esp_camera.h>
+#include <FS.h>
+
+#include "dithering.h"
+
+/** Process and send grayscale images back to the Flipper Zero. */
+void process_image(camera_fb_t *frame_buffer, CameraModel *model);
+
+#endif

+ 58 - 0
firmware-update/process_image.ino

@@ -0,0 +1,58 @@
+#include "process_image.h"
+
+void process_image(camera_fb_t *frame_buffer, CameraModel *model)
+{
+    // If dithering is not disabled, perform dithering on the image. Dithering is the
+    // process of approximating the look of a high-resolution grayscale image in a
+    // lower resolution by binary values (black & white), thereby representing
+    // different shades of gray.
+    if (!model->isDitheringDisabled)
+    {
+        dither_image(frame_buffer, model); // Invokes the dithering process on the frame buffer.
+    }
+
+    uint8_t flipper_y = 0;
+
+    // Iterating over specific rows of the frame buffer.
+    for (uint8_t y = 28; y < 92; ++y)
+    {
+        Serial.print("Y:");      // Print "Y:" for every new row.
+        Serial.write(flipper_y); // Send the row identifier as a byte.
+
+        // Calculate the actual y index in the frame buffer 1D array by multiplying the
+        // y value with the width of the frame buffer. This gives the starting index of
+        // the row in the 1D array.
+        size_t true_y = y * frame_buffer->width;
+
+        // Iterating over specific columns of each row in the frame buffer.
+        for (uint8_t x = 16; x < 144; x += 8)
+        { // step by 8 as we're packing 8 pixels per byte.
+            uint8_t packed_pixels = 0;
+            // Packing 8 pixel values into one byte.
+            for (uint8_t bit = 0; bit < 8; ++bit)
+            {
+                // Check the invert flag and pack the pixels accordingly.
+                if (model->isInverted)
+                {
+                    // If invert is true, consider pixel as 1 if it's more than 127.
+                    if (frame_buffer->buf[true_y + x + bit] > 127)
+                    {
+                        packed_pixels |= (1 << (7 - bit));
+                    }
+                }
+                else
+                {
+                    // If invert is false, consider pixel as 1 if it's less than 127.
+                    if (frame_buffer->buf[true_y + x + bit] < 127)
+                    {
+                        packed_pixels |= (1 << (7 - bit));
+                    }
+                }
+            }
+            Serial.write(packed_pixels); // Sending packed pixel byte.
+        }
+
+        ++flipper_y;    // Move to the next row.
+        Serial.flush(); // Ensure all data in the Serial buffer is sent before moving to the next iteration.
+    }
+}

+ 15 - 0
firmware-update/save_picture.h

@@ -0,0 +1,15 @@
+#ifndef SAVE_PICTURE_H
+#define SAVE_PICTURE_H
+
+#include <esp_camera.h>
+#include <SD_MMC.h>
+
+#include "globals.h"
+
+/**
+ * Save the current picture to the onboard SD card.
+ * @todo - Future feature.
+ */
+void save_picture();
+
+#endif

+ 69 - 0
firmware-update/save_picture.ino

@@ -0,0 +1,69 @@
+#include "save_picture.h"
+
+void save_picture()
+{
+    sensor_t *cam = esp_camera_sensor_get();
+
+    // Check if the sensor is valid.
+    if (!cam)
+    {
+        Serial.println("Failed to acquire camera sensor");
+        return;
+    }
+
+    // Set pixel format to JPEG for saving picture.
+    cam->set_pixformat(cam, PIXFORMAT_JPEG);
+
+    // Set frame size based on available PSRAM.
+    if (psramFound())
+    {
+        cam->set_framesize(cam, FRAMESIZE_UXGA);
+    }
+    else
+    {
+        cam->set_framesize(cam, FRAMESIZE_SVGA);
+    }
+
+    // Get a frame buffer from camera.
+    camera_fb_t *frame_buffer = esp_camera_fb_get();
+    if (!frame_buffer)
+    {
+        Serial.println("Camera capture failed");
+        return;
+    }
+
+    if (!SD_MMC.begin())
+    {
+        // SD Card Mount Failed.
+        Serial.println("SD Card Mount Failed");
+        esp_camera_fb_return(frame_buffer);
+        return;
+    }
+
+    // Generate a unique filename.
+    String path = "/picture";
+    path += String(millis());
+    path += ".jpg";
+
+    fs::FS &fs = SD_MMC;
+    File file = fs.open(path.c_str(), FILE_WRITE);
+
+    if (!file)
+    {
+        Serial.println("Failed to open file in writing mode");
+    }
+    else
+    {
+        if (file.write(frame_buffer->buf, frame_buffer->len) != frame_buffer->len)
+        {
+            Serial.println("Failed to write the image to the file");
+        }
+        file.close(); // Close the file in any case.
+    }
+
+    // Update framesize back to the default.
+    cam->set_framesize(cam, FRAMESIZE_QQVGA);
+
+    // Return the frame buffer back to the camera driver.
+    esp_camera_fb_return(frame_buffer);
+}

+ 12 - 0
firmware-update/serial_commands.h

@@ -0,0 +1,12 @@
+#ifndef SERIAL_COMMANDS_H
+#define SERIAL_COMMANDS_H
+
+#include <esp_camera.h>
+#include <FS.h>
+
+#include "globals.h"
+
+/** Handle the serial input commands coming from the Flipper Zero. */
+void serial_commands(CameraModel *model);
+
+#endif

+ 67 - 0
firmware-update/serial_commands.ino

@@ -0,0 +1,67 @@
+#include "serial_commands.h"
+
+void serial_commands(CameraModel *model)
+{
+    if (Serial.available() > 0)
+    {
+        char input = Serial.read();
+        sensor_t *cam = esp_camera_sensor_get();
+
+        switch (input)
+        {
+        case '>': // Toggle dithering.
+            model->isDitheringDisabled = !model->isDitheringDisabled;
+            break;
+        case '<': // Toggle invert.
+            model->isInverted = !model->isInverted;
+            break;
+        case 'b': // Remove brightness.
+            cam->set_contrast(cam, cam->status.brightness - 1);
+            break;
+        case 'B': // Add brightness.
+            cam->set_contrast(cam, cam->status.brightness + 1);
+            break;
+        case 'c': // Remove contrast.
+            cam->set_contrast(cam, cam->status.contrast - 1);
+            break;
+        case 'C': // Add contrast.
+            cam->set_contrast(cam, cam->status.contrast + 1);
+            break;
+        case 'f': // Turn the flash off.
+            model->isFlashEnabled = false;
+            pinMode(FLASH_GPIO_NUM, OUTPUT);
+            digitalWrite(FLASH_GPIO_NUM, LOW);
+            break;
+        case 'F': // Turn the flash on.
+            model->isFlashEnabled = true;
+            pinMode(FLASH_GPIO_NUM, OUTPUT);
+            digitalWrite(FLASH_GPIO_NUM, HIGH);
+            break;
+        case 'P': // Save image to the onboard SD card.
+            // @todo - Future feature.
+            // save_picture();
+            break;
+        case 'M': // Toggle Mirror.
+            cam->set_hmirror(cam, !cam->status.hmirror);
+            break;
+        case 's': // Stop stream.
+            model->isStreamEnabled = false;
+            break;
+        case 'S': // Start stream.
+            model->isStreamEnabled = true;
+            break;
+        case '0': // Use Floyd Steinberg dithering.
+            model->ditherAlgorithm = FLOYD_STEINBERG;
+            break;
+        case '1': // Use Jarvis Judice dithering.
+            model->ditherAlgorithm = JARVIS_JUDICE_NINKE;
+            break;
+        case '2': // Use Stucki dithering.
+            model->ditherAlgorithm = STUCKI;
+            break;
+        default:
+            // Do nothing.
+            break;
+        }
+    }
+}