/* * OLED.cpp * * Created on: 29Aug.,2017 * Author: Ben V. Brown */ #include #include #include #include "Translation.h" #include "cmsis_os.h" #include "../../configuration.h" const uint8_t *OLED::currentFont; // Pointer to the current font used for // rendering to the buffer uint8_t *OLED::firstStripPtr; // Pointers to the strips to allow for buffer // having extra content uint8_t *OLED::secondStripPtr; // Pointers to the strips bool OLED::inLeftHandedMode; // Whether the screen is in left or not (used for // offsets in GRAM) OLED::DisplayState OLED::displayState; uint8_t OLED::fontWidth, OLED::fontHeight; int16_t OLED::cursor_x, OLED::cursor_y; uint8_t OLED::displayOffset; uint8_t OLED::screenBuffer[16 + (OLED_WIDTH * 2) + 10]; // The data buffer /*Setup params for the OLED screen*/ /*http://www.displayfuture.com/Display/datasheet/controller/SSD1307.pdf*/ /*All commands are prefixed with 0x80*/ /*Data packets are prefixed with 0x40*/ uint8_t OLED_Setup_Array[] = { /**/ 0x80, 0xAE, /*Display off*/ 0x80, 0xD5, /*Set display clock divide ratio / osc freq*/ 0x80, 0x52, /*Divide ratios*/ 0x80, 0xA8, /*Set Multiplex Ratio*/ 0x80, 0x0F, /*16 == max brightness,39==dimmest*/ 0x80, 0xC0, /*Set COM Scan direction*/ 0x80, 0xD3, /*Set vertical Display offset*/ 0x80, 0x00, /*0 Offset*/ 0x80, 0x40, /*Set Display start line to 0*/ 0x80, 0xA0, /*Set Segment remap to normal*/ 0x80, 0x8D, /*Charge Pump*/ 0x80, 0x14, /*Charge Pump settings*/ 0x80, 0xDA, /*Set VCOM Pins hardware config*/ 0x80, 0x02, /*Combination 2*/ 0x80, 0x81, /*Contrast*/ 0x80, 0x33, /*^51*/ 0x80, 0xD9, /*Set pre-charge period*/ 0x80, 0xF1, /*Pre charge period*/ 0x80, 0xDB, /*Adjust VCOMH regulator ouput*/ 0x80, 0x30, /*VCOM level*/ 0x80, 0xA4, /*Enable the display GDDR*/ 0x80, 0XA6, /*Normal display*/ 0x80, 0x20, /*Memory Mode*/ 0x80, 0x00, /*Wrap memory*/ 0x80, 0xAF /*Display on*/ }; // Setup based on the SSD1307 and modified for the SSD1306 const uint8_t REFRESH_COMMANDS[17] = { 0x80, 0xAF, 0x80, 0x21, 0x80, 0x20, 0x80, 0x7F, 0x80, 0xC0, 0x80, 0x22, 0x80, 0x00, 0x80, 0x01, 0x40 }; void OLED::initialize() { cursor_x = cursor_y = 0; currentFont = USER_FONT_12; fontWidth = 12; inLeftHandedMode = false; firstStripPtr = &screenBuffer[FRAMEBUFFER_START]; secondStripPtr = &screenBuffer[FRAMEBUFFER_START + OLED_WIDTH]; fontHeight = 16; displayOffset = 0; memcpy(&screenBuffer[0], &REFRESH_COMMANDS[0], sizeof(REFRESH_COMMANDS)); HAL_Delay(50); HAL_GPIO_WritePin(OLED_RESET_GPIO_Port, OLED_RESET_Pin, GPIO_PIN_SET); HAL_Delay(50); // Set the display to be ON once the settings block is sent and send the // initialisation data to the OLED. setDisplayState(DisplayState::ON); FRToSI2C::Transmit(DEVICEADDR_OLED, &OLED_Setup_Array[0], sizeof(OLED_Setup_Array)); } void OLED::set_framebuffer(uint8_t *buffer) { if (buffer == NULL) { firstStripPtr = &screenBuffer[FRAMEBUFFER_START]; secondStripPtr = &screenBuffer[FRAMEBUFFER_START + OLED_WIDTH]; return; } firstStripPtr = &buffer[0]; secondStripPtr = &buffer[OLED_WIDTH]; } /* * Prints a char to the screen. * UTF font handling is done using the two input chars. * Precursor is the command char that is used to select the table. */ void OLED::drawChar(char c) { if (c == '\x01' && cursor_y == 0) { // 0x01 is used as new line char cursor_x = 0; cursor_y = 8; return; } else if (c == 0) { return; } uint16_t index = c - 2; //First index is \x02 uint8_t *charPointer; charPointer = ((uint8_t*) currentFont) + ((fontWidth * (fontHeight / 8)) * index); drawArea(cursor_x, cursor_y, fontWidth, fontHeight, charPointer); cursor_x += fontWidth; } void OLED::presentSecondScreenBufferAnimatedBack() { uint8_t *firstBackStripPtr = &firstStripPtr[0]; uint8_t *secondBackStripPtr = &secondStripPtr[0]; set_framebuffer(NULL); uint32_t totalDuration = 50; uint32_t duration = 0; uint32_t start = xTaskGetTickCount(); uint8_t offset = 0; while (duration <= totalDuration) { duration = xTaskGetTickCount() - start; uint8_t progress = (duration * OLED_WIDTH) / totalDuration; for (uint8_t i = OLED_WIDTH - 1; i > progress; i--) { firstStripPtr[i] = firstStripPtr[(i - progress) + offset]; secondStripPtr[i] = secondStripPtr[(i - progress) + offset]; } offset = progress; for (uint8_t i = 0; i < progress; i++) { firstStripPtr[i] = firstBackStripPtr[(i - progress) + OLED_WIDTH]; secondStripPtr[i] = secondBackStripPtr[(i - progress) + OLED_WIDTH]; } refresh(); osDelay(40); } } void OLED::presentSecondScreenBufferAnimated() { uint8_t *firstBackStripPtr = &firstStripPtr[0]; uint8_t *secondBackStripPtr = &secondStripPtr[0]; set_framebuffer(NULL); uint32_t totalDuration = 50; uint32_t duration = 0; uint32_t start = xTaskGetTickCount(); uint8_t offset = 0; while (duration < totalDuration) { duration = xTaskGetTickCount() - start; uint8_t progress = (duration * OLED_WIDTH) / totalDuration; for (uint8_t i = 0; i < OLED_WIDTH - progress; i++) { firstStripPtr[i] = firstStripPtr[i + progress - offset]; secondStripPtr[i] = secondStripPtr[i + progress - offset]; } offset = progress; for (uint8_t i = OLED_WIDTH - progress; i < OLED_WIDTH; i++) { firstStripPtr[i] = firstBackStripPtr[i - (OLED_WIDTH - progress)]; secondStripPtr[i] = secondBackStripPtr[i - (OLED_WIDTH - progress)]; } refresh(); osDelay(40); } } void OLED::setRotation(bool leftHanded) { #ifdef MODEL_TS80 leftHanded = !leftHanded; #endif if (inLeftHandedMode == leftHanded) { return; } // send command struct again with changes if (leftHanded) { OLED_Setup_Array[11] = 0xC8; // c1? OLED_Setup_Array[19] = 0xA1; } else { OLED_Setup_Array[11] = 0xC0; OLED_Setup_Array[19] = 0xA0; } FRToSI2C::Transmit(DEVICEADDR_OLED, (uint8_t*) OLED_Setup_Array, sizeof(OLED_Setup_Array)); inLeftHandedMode = leftHanded; screenBuffer[5] = inLeftHandedMode ? 0 : 32; // display is shifted by 32 in left handed // mode as driver ram is 128 wide screenBuffer[7] = inLeftHandedMode ? 95 : 0x7F; // End address of the ram segment we are writing to (96 wide) screenBuffer[9] = inLeftHandedMode ? 0xC8 : 0xC0; } // print a string to the current cursor location void OLED::print(const char *str) { while (str[0]) { drawChar(str[0]); str++; } } void OLED::setFont(uint8_t fontNumber) { if (fontNumber == 1) { // small font currentFont = USER_FONT_6x8; fontHeight = 8; fontWidth = 6; } else if (fontNumber == 2) { currentFont = ExtraFontChars; fontHeight = 16; fontWidth = 12; } else { currentFont = USER_FONT_12; fontHeight = 16; fontWidth = 12; } } uint8_t OLED::getFont() { if (currentFont == USER_FONT_6x8) return 1; else if (currentFont == ExtraFontChars) return 2; else return 0; } inline void stripLeaderZeros(char *buffer, uint8_t places) { //Removing the leading zero's by swapping them to SymbolSpace // Stop 1 short so that we dont blank entire number if its zero for (int i = 0; i < (places-1); i++) { if (buffer[i] == 2) { buffer[i] = SymbolSpace[0]; } else { return; } } } // maximum places is 5 void OLED::printNumber(uint16_t number, uint8_t places, bool noLeaderZeros) { char buffer[7] = { 0 }; if (places >= 5) { buffer[5] = 2 + number % 10; number /= 10; } if (places > 4) { buffer[4] = 2 + number % 10; number /= 10; } if (places > 3) { buffer[3] = 2 + number % 10; number /= 10; } if (places > 2) { buffer[2] = 2 + number % 10; number /= 10; } if (places > 1) { buffer[1] = 2 + number % 10; number /= 10; } buffer[0] = 2 + number % 10; if (noLeaderZeros) stripLeaderZeros(buffer, places); print(buffer); } void OLED::debugNumber(int32_t val) { if (abs(val) > 99999) { OLED::print(SymbolSpace); // out of bounds return; } if (val >= 0) { OLED::print(SymbolSpace); OLED::printNumber(val, 5); } else { OLED::print(SymbolMinus); OLED::printNumber(-val, 5); } } void OLED::drawSymbol(uint8_t symbolID) { // draw a symbol to the current cursor location setFont(2); drawChar(symbolID + 2); setFont(0); } // Draw an area, but y must be aligned on 0/8 offset void OLED::drawArea(int16_t x, int8_t y, uint8_t wide, uint8_t height, const uint8_t *ptr) { // Splat this from x->x+wide in two strides if (x <= -wide) return; // cutoffleft if (x > 96) return; // cutoff right uint8_t visibleStart = 0; uint8_t visibleEnd = wide; // trimming to draw partials if (x < 0) { visibleStart -= x; // subtract negative value == add absolute value } if (x + wide > 96) { visibleEnd = 96 - x; } if (y == 0) { // Splat first line of data for (uint8_t xx = visibleStart; xx < visibleEnd; xx++) { firstStripPtr[xx + x] = ptr[xx]; } } if (y == 8 || height == 16) { // Splat the second line for (uint8_t xx = visibleStart; xx < visibleEnd; xx++) { secondStripPtr[x + xx] = ptr[xx + (height == 16 ? wide : 0)]; } } } // Draw an area, but y must be aligned on 0/8 offset // For data which has octets swapped in a 16-bit word. void OLED::drawAreaSwapped(int16_t x, int8_t y, uint8_t wide, uint8_t height, const uint8_t *ptr) { // Splat this from x->x+wide in two strides if (x <= -wide) return; // cutoffleft if (x > 96) return; // cutoff right uint8_t visibleStart = 0; uint8_t visibleEnd = wide; // trimming to draw partials if (x < 0) { visibleStart -= x; // subtract negative value == add absolute value } if (x + wide > 96) { visibleEnd = 96 - x; } if (y == 0) { // Splat first line of data for (uint8_t xx = visibleStart; xx < visibleEnd; xx += 2) { firstStripPtr[xx + x] = ptr[xx + 1]; firstStripPtr[xx + x + 1] = ptr[xx]; } } if (y == 8 || height == 16) { // Splat the second line for (uint8_t xx = visibleStart; xx < visibleEnd; xx += 2) { secondStripPtr[x + xx] = ptr[xx + 1 + (height == 16 ? wide : 0)]; secondStripPtr[x + xx + 1] = ptr[xx + (height == 16 ? wide : 0)]; } } } void OLED::fillArea(int16_t x, int8_t y, uint8_t wide, uint8_t height, const uint8_t value) { // Splat this from x->x+wide in two strides if (x <= -wide) return; // cutoffleft if (x > 96) return; // cutoff right uint8_t visibleStart = 0; uint8_t visibleEnd = wide; // trimming to draw partials if (x < 0) { visibleStart -= x; // subtract negative value == add absolute value } if (x + wide > 96) { visibleEnd = 96 - x; } if (y == 0) { // Splat first line of data for (uint8_t xx = visibleStart; xx < visibleEnd; xx++) { firstStripPtr[xx + x] = value; } } if (y == 8 || height == 16) { // Splat the second line for (uint8_t xx = visibleStart; xx < visibleEnd; xx++) { secondStripPtr[x + xx] = value; } } } void OLED::drawFilledRect(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, bool clear) { // Draw this in 3 sections // This is basically a N wide version of vertical line // Step 1 : Draw in the top few pixels that are not /8 aligned // LSB is at the top of the screen uint8_t mask = 0xFF; if (y0) { mask = mask << (y0 % 8); for (uint8_t col = x0; col < x1; col++) if (clear) firstStripPtr[(y0 / 8) * 96 + col] &= ~mask; else firstStripPtr[(y0 / 8) * 96 + col] |= mask; } // Next loop down the line the total number of solids if (y0 / 8 != y1 / 8) for (uint8_t col = x0; col < x1; col++) for (uint8_t r = (y0 / 8); r < (y1 / 8); r++) { // This gives us the row index r if (clear) firstStripPtr[(r * 96) + col] = 0; else firstStripPtr[(r * 96) + col] = 0xFF; } // Finally draw the tail mask = ~(mask << (y1 % 8)); for (uint8_t col = x0; col < x1; col++) if (clear) firstStripPtr[(y1 / 8) * 96 + col] &= ~mask; else firstStripPtr[(y1 / 8) * 96 + col] |= mask; } void OLED::drawHeatSymbol(uint8_t state) { // Draw symbol 14 // Then draw over it, the bottom 5 pixels always stay. 8 pixels above that are // the levels masks the symbol nicely state /= 31; // 0-> 8 range // Then we want to draw down (16-(5+state) uint8_t cursor_x_temp = cursor_x; drawSymbol(14); drawFilledRect(cursor_x_temp, 0, cursor_x_temp + 12, 2 + (8 - state), true); }