Skip to content

File OledController.cpp

File List > fw > rbcx-coprocessor > src > OledController.cpp

Go to the documentation of this file.

#include "OledController.hpp"
#include "Dispatcher.hpp"
#include "string.h"
#include "utils/Debug.hpp"
#include <math.h>
#include <stdlib.h>
#include <string.h> // For memcpy

// OLED OLED height in pixels
static uint8_t oled_height = 64;

// OLED width in pixels
static uint8_t oled_width = 128;

// Screenbuffer
static uint8_t OLED_Buffer[OLED_BUFFER_SIZE];

// Screen object
static OLED_t OLED;

void oledDispatch(const CoprocReq_OledReq& request) {
    switch (request.which_oledCmd) {
    case CoprocReq_OledReq_init_tag:
        oledInit(request.oledCmd.init);
        break;

    case CoprocReq_OledReq_fill_tag:
        oledFill(OLED_COLOR(request.oledCmd.fill));
        break;

    case CoprocReq_OledReq_update_tag:
        oledUpdateScreen();
        break;

    case CoprocReq_OledReq_drawPixel_tag: {
        const auto& drawPixel = request.oledCmd.drawPixel;
        oledDrawPixel(drawPixel.x, drawPixel.y, OLED_COLOR(drawPixel.color));
        break;
    }
    case CoprocReq_OledReq_writeString_tag: {

        const auto& writeString = request.oledCmd.writeString;
        char text[33];
        strcpy(text, writeString.text);
        FontDef* font;
        switch (writeString.font) {
        case CoprocReq_OledFont_OLED_FONT_6X8:
            font = &Font_6x8;
            break;
        case CoprocReq_OledFont_OLED_FONT_7X10:
            font = &Font_7x10;
            break;
        case CoprocReq_OledFont_OLED_FONT_11X18:
            font = &Font_11x18;
            break;
        case CoprocReq_OledFont_OLED_FONT_16X26:
            font = &Font_16x26;
            break;
        default:
            font = &Font_7x10;
        }
        oledWriteString(text, *font, OLED_COLOR(writeString.color));
        break;
    }
    case CoprocReq_OledReq_setCursor_tag: {
        const auto& setCursor = request.oledCmd.setCursor;
        oledSetCursor(setCursor.x, setCursor.y);
        break;
    }

    case CoprocReq_OledReq_drawLine_tag: {
        const auto& drawLine = request.oledCmd.drawLine;
        oledDrawLine(drawLine.x1, drawLine.y1, drawLine.x2, drawLine.y2,
            OLED_COLOR(drawLine.color));
        break;
    }

    case CoprocReq_OledReq_drawArc_tag: {
        const auto& drawArc = request.oledCmd.drawArc;
        oledDrawArc(drawArc.x, drawArc.y, drawArc.radius, drawArc.start_angle,
            drawArc.sweep, OLED_COLOR(drawArc.color));
        break;
    }

    case CoprocReq_OledReq_drawCircle_tag: {
        const auto& drawCircle = request.oledCmd.drawCircle;
        oledDrawCircle(drawCircle.x, drawCircle.y, drawCircle.radius,
            OLED_COLOR(drawCircle.color));
        break;
    }

    case CoprocReq_OledReq_drawRectangle_tag: {
        const auto& drawRectangle = request.oledCmd.drawRectangle;
        oledDrawRectangle(drawRectangle.x1, drawRectangle.y1, drawRectangle.x2,
            drawRectangle.y2, OLED_COLOR(drawRectangle.color));
        break;
    }
    };
}

bool oledTestConnection() { return I2Cdev_IsDeviceReady(OLED_I2C_ADDR, 1, 10); }

// Initialize the oled screen
void oledInit(const CoprocReq_OledInit& init) {

    if (oledTestConnection()) {
        // Init OLED
        oledSetDisplayOn(false); //display off

        oledWriteCommand(0x20); //Set Memory Addressing Mode
        oledWriteCommand(
            0x00); // 00b,Horizontal Addressing Mode; 01b,Vertical Addressing Mode;
        // 10b,Page Addressing Mode (RESET); 11b,Invalid

        oledWriteCommand(
            0xB0); //Set Page Start Address for Page Addressing Mode,0-7

        if (init.rotate) {
            oledWriteCommand(0xC0); // Mirror vertically
        } else {
            oledWriteCommand(0xC8); //Set COM Output Scan Direction
        }

        oledWriteCommand(0x00); //---set low column address
        oledWriteCommand(0x10); //---set high column address

        oledWriteCommand(0x40); //--set start line address - CHECK

        oledSetContrast(0xFF);

        if (init.rotate) {
            oledWriteCommand(0xA0); // Mirror horizontally
        } else {
            oledWriteCommand(0xA1); //--set segment re-map 0 to 127 - CHECK
        }

        if (init.inverseColor) {
            oledWriteCommand(0xA7); //--set inverse color
        } else {
            oledWriteCommand(0xA6); //--set normal color
        }

        oledWriteCommand(0xA8); //--set multiplex ratio(1 to 64) - CHECK

        if (init.height == 32) {
            oledWriteCommand(0x1F); //
        } else if (init.height == 64) {
            oledWriteCommand(0x3F); //
        } else {
            //TO DO warning message
        }

        oledWriteCommand(
            0xA4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content

        oledWriteCommand(0xD3); //-set display offset - CHECK
        oledWriteCommand(0x00); //-not offset

        oledWriteCommand(
            0xD5); //--set display clock divide ratio/oscillator frequency
        oledWriteCommand(0xF0); //--set divide ratio

        oledWriteCommand(0xD9); //--set pre-charge period
        oledWriteCommand(0x22); //

        oledWriteCommand(0xDA); //--set com pins hardware configuration - CHECK

        if (init.height == 32) {
            oledWriteCommand(0x02);
        } else if (init.height == 64) {
            oledWriteCommand(0x12);
        } else {
            //TO DO warning message
        }

        oledWriteCommand(0xDB); //--set vcomh
        oledWriteCommand(0x20); //0x20,0.77xVcc

        oledWriteCommand(0x8D); //--set DC-DC enable
        oledWriteCommand(0x14); //
        oledSetDisplayOn(true); //--turn on OLED panel

        // Clear screen
        oledFill(Black);

        // Flush buffer to screen
        oledUpdateScreen();

        // Set default values for screen object
        OLED.CurrentX = 0;
        OLED.CurrentY = 0;

    } else {
        DEBUG("Oled not connected-init\n");
        CoprocStat status = {
            .which_payload = CoprocStat_faultStat_tag,
            .payload = {
                .faultStat = {
                    .which_fault = CoprocStat_FaultStat_oledFault_tag,
                },
            },
        };
        dispatcherEnqueueStatus(status);
    }
}

// Fill the whole screen with the given color
void oledFill(OLED_COLOR color) {
    /* Set memory */
    uint32_t i;

    for (i = 0; i < sizeof(OLED_Buffer); i++) {
        OLED_Buffer[i] = (color == Black) ? 0x00 : 0xFF;
    }
}

// Write the screenbuffer with changed to the screen
void oledUpdateScreen(void) {
    if (!oledTestConnection()) {
        DEBUG("Oled not connected\n");
        CoprocStat status = {
            .which_payload = CoprocStat_faultStat_tag,
            .payload = {
                .faultStat = {
                    .which_fault = CoprocStat_FaultStat_oledFault_tag,
                },
            },
        };
        dispatcherEnqueueStatus(status);
    }
    // Write data to each page of RAM. Number of pages
    // depends on the screen height:
    //
    //  * 32px   ==  4 pages
    //  * 64px   ==  8 pages
    //  * 128px  ==  16 pages
    for (uint8_t i = 0; i < oled_height / 8; i++) {
        oledWriteCommand(0xB0 + i); // Set the current RAM page address.
        oledWriteCommand(0x00);
        oledWriteCommand(0x10);
        oledWriteData(&OLED_Buffer[oled_width * i], oled_width);
    }
}

//    Draw one pixel in the screenbuffer
//    X => X Coordinate
//    Y => Y Coordinate
//    color => Pixel color
void oledDrawPixel(uint8_t x, uint8_t y, OLED_COLOR color) {
    if (x >= oled_width || y >= oled_height) {
        // Don't write outside the buffer
        return;
    }

    // Check if pixel should be inverted
    if (OLED.Inverted) {
        color = (OLED_COLOR)!color;
    }

    // Draw in the right color
    if (color == White) {
        OLED_Buffer[x + (y / 8) * oled_width] |= 1 << (y % 8);
    } else {
        OLED_Buffer[x + (y / 8) * oled_width] &= ~(1 << (y % 8));
    }
}

// Draw 1 char to the screen buffer
// ch       => char om weg te schrijven
// Font     => Font waarmee we gaan schrijven
// color    => Black or White
char oledWriteChar(char ch, FontDef Font, OLED_COLOR color) {
    uint32_t i, b, j;

    // Check if character is valid
    if (ch < 32 || ch > 126)
        return 0;

    // Check remaining space on current line
    if (oled_width < (OLED.CurrentX + Font.FontWidth)
        || oled_height < (OLED.CurrentY + Font.FontHeight)) {
        // Not enough space on current line
        return 0;
    }

    // Use the font to write
    for (i = 0; i < Font.FontHeight; i++) {
        b = Font.data[(ch - 32) * Font.FontHeight + i];
        for (j = 0; j < Font.FontWidth; j++) {
            if ((b << j) & 0x8000) {
                oledDrawPixel(
                    OLED.CurrentX + j, (OLED.CurrentY + i), (OLED_COLOR)color);
            } else {
                oledDrawPixel(
                    OLED.CurrentX + j, (OLED.CurrentY + i), (OLED_COLOR)!color);
            }
        }
    }

    // The current space is now taken
    OLED.CurrentX += Font.FontWidth;

    // Return written char for validation
    return ch;
}

// Write full string to screenbuffer
char oledWriteString(const char* str, FontDef Font, OLED_COLOR color) {
    // Write until null-byte
    while (*str) {
        if (oledWriteChar(*str, Font, color) != *str) {
            // Char could not be written
            return *str;
        }

        // Next char
        str++;
    }

    // Everything ok
    return *str;
}

// Position the cursor
void oledSetCursor(uint8_t x, uint8_t y) {
    OLED.CurrentX = x;
    OLED.CurrentY = y;
}

// Draw line by Bresenhem's algorithm
void oledDrawLine(
    uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, OLED_COLOR color) {
    int32_t deltaX = abs(x2 - x1);
    int32_t deltaY = abs(y2 - y1);
    int32_t signX = ((x1 < x2) ? 1 : -1);
    int32_t signY = ((y1 < y2) ? 1 : -1);
    int32_t error = deltaX - deltaY;
    int32_t error2;

    oledDrawPixel(x2, y2, color);
    while ((x1 != x2) || (y1 != y2)) {
        oledDrawPixel(x1, y1, color);
        error2 = error * 2;
        if (error2 > -deltaY) {
            error -= deltaY;
            x1 += signX;
        } else {
            /*nothing to do*/
        }

        if (error2 < deltaX) {
            error += deltaX;
            y1 += signY;
        } else {
            /*nothing to do*/
        }
    }
    return;
}
//Draw polyline
void oledPolyline(
    const OLED_VERTEX* par_vertex, uint16_t par_size, OLED_COLOR color) {
    uint16_t i;
    if (par_vertex != 0) {
        for (i = 1; i < par_size; i++) {
            oledDrawLine(par_vertex[i - 1].x, par_vertex[i - 1].y,
                par_vertex[i].x, par_vertex[i].y, color);
        }
    } else {
        /*nothing to do*/
    }
    return;
}
/*Convert Degrees to Radians*/
static float oledDegToRad(float par_deg) { return par_deg * 3.14 / 180.0; }
/*Normalize degree to [0;360]*/
static uint16_t oledNormalizeTo0_360(uint16_t par_deg) {
    uint16_t loc_angle;
    if (par_deg <= 360) {
        loc_angle = par_deg;
    } else {
        loc_angle = par_deg % 360;
        loc_angle = ((par_deg != 0) ? par_deg : 360);
    }
    return loc_angle;
}
/*DrawArc. Draw angle is beginning from 4 quart of trigonometric circle (3pi/2)
 * start_angle in degree
 * sweep in degree
 */
void oledDrawArc(uint8_t x, uint8_t y, uint8_t radius, uint16_t start_angle,
    uint16_t sweep, OLED_COLOR color) {
#define CIRCLE_APPROXIMATION_SEGMENTS 36
    float approx_degree;
    uint32_t approx_segments;
    uint8_t xp1, xp2;
    uint8_t yp1, yp2;
    uint32_t count = 0;
    uint32_t loc_sweep = 0;
    float rad;

    loc_sweep = oledNormalizeTo0_360(sweep);

    count = (oledNormalizeTo0_360(start_angle) * CIRCLE_APPROXIMATION_SEGMENTS)
        / 360;
    approx_segments = (loc_sweep * CIRCLE_APPROXIMATION_SEGMENTS) / 360;
    approx_degree = loc_sweep / (float)approx_segments;
    while (count < approx_segments) {
        rad = oledDegToRad(count * approx_degree);
        xp1 = x + (int8_t)(sin(rad) * radius);
        yp1 = y + (int8_t)(cos(rad) * radius);
        count++;
        if (count != approx_segments) {
            rad = oledDegToRad(count * approx_degree);
        } else {
            rad = oledDegToRad(loc_sweep);
        }
        xp2 = x + (int8_t)(sin(rad) * radius);
        yp2 = y + (int8_t)(cos(rad) * radius);
        oledDrawLine(xp1, yp1, xp2, yp2, color);
    }

    return;
}
//Draw circle by Bresenhem's algorithm
void oledDrawCircle(
    uint8_t par_x, uint8_t par_y, uint8_t par_r, OLED_COLOR par_color) {
    int32_t x = -par_r;
    int32_t y = 0;
    int32_t err = 2 - 2 * par_r;
    int32_t e2;

    if (par_x >= oled_width || par_y >= oled_height) {
        return;
    }

    do {
        oledDrawPixel(par_x - x, par_y + y, par_color);
        oledDrawPixel(par_x + x, par_y + y, par_color);
        oledDrawPixel(par_x + x, par_y - y, par_color);
        oledDrawPixel(par_x - x, par_y - y, par_color);
        e2 = err;
        if (e2 <= y) {
            y++;
            err = err + (y * 2 + 1);
            if (-x == y && e2 <= x) {
                e2 = 0;
            } else {
                /*nothing to do*/
            }
        } else {
            /*nothing to do*/
        }
        if (e2 > x) {
            x++;
            err = err + (x * 2 + 1);
        } else {
            /*nothing to do*/
        }
    } while (x <= 0);

    return;
}

//Draw rectangle
void oledDrawRectangle(
    uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, OLED_COLOR color) {
    oledDrawLine(x1, y1, x2, y1, color);
    oledDrawLine(x2, y1, x2, y2, color);
    oledDrawLine(x2, y2, x1, y2, color);
    oledDrawLine(x1, y2, x1, y1, color);

    return;
}

void oledSetContrast(const uint8_t value) {
    const uint8_t kSetContrastControlRegister = 0x81;
    oledWriteCommand(kSetContrastControlRegister);
    oledWriteCommand(value);
}

void oledSetDisplayOn(const bool on) {
    uint8_t value;
    if (on) {
        value = 0xAF; // Display on
        OLED.DisplayOn = true;
    } else {
        value = 0xAE; // Display off
        OLED.DisplayOn = false;
    }
    oledWriteCommand(value);
}

bool oledGetDisplayOn() { return OLED.DisplayOn; }

// Send a byte to the command register
void oledWriteCommand(uint8_t byte) {
    I2Cdev_Mem_Write(OLED_I2C_ADDR, 0x00, 1, &byte, 1, HAL_MAX_DELAY);
}

// Send data
void oledWriteData(uint8_t* buffer, size_t buff_size) {
    I2Cdev_Mem_Write(OLED_I2C_ADDR, 0x40, 1, buffer, buff_size, HAL_MAX_DELAY);
}

/* Fills the Screenbuffer with values from a given buffer of a fixed length */
OLED_Error_t oledFillBuffer(uint8_t* buf, uint32_t len) {
    OLED_Error_t ret = OLED_ERR;
    if (len <= OLED_BUFFER_SIZE) {
        memcpy(OLED_Buffer, buf, len);
        ret = OLED_OK;
    }
    return ret;
}