44// This is a library written for SparkFun Qwiic OLED boards that use the
55// CH1120. This driver is VERY similar to the SSD1306 driver, but with a few
66// subtle differences. Currently, our only device with the CH1120 driver is
7- // the Qwiic OLED 1.5", on which row addressing has not been verified.
8- // So, this library currently writes out the entire screen buffer each refresh
9- // instead of only the dirty bits like the SSD1306 driver does. This makes this
10- // display operate slower than the others in the library.
7+ // the Qwiic OLED 1.5".
118//
129// SparkFun sells these at its website: www.sparkfun.com
1310//
1411// Do you like this library? Help support SparkFun. Buy a board!
1512//
16- // Micro OLED https://www.sparkfun.com/products/14532
17- // Transparent OLED https://www.sparkfun.com/products/15173
18- // "Narrow" OLED https://www.sparkfun.com/products/17153
19- //
13+ // Qwiic OLED 1.5in https://www.sparkfun.com/products/29530
2014//
2115// Written by SparkFun Electronics, August 2025
2216//
7670//
7771// These macros work with the pageState_t struct type.
7872//
79- // Define unique values just outside of the screen buffer (SSD1306) page range
80- // (0 base) Note: A page is 128 bits in length
73+ // Define unique values just outside of the screen buffer (valid is 0 - 159)
8174
8275#define kPageMin -1 // outside bounds - low value
8376#define kPageMax 160 // outside bounds - high value
151144
152145#define kCmdRowStartEnd ((uint8_t )0x22 ) // Two byte command - command, start, stop
153146#define kCmdColStartEnd ((uint8_t )0x21 ) // Two byte command - command, start, stop
154- #define kCmdStartRow ((uint8_t )0xB0 ) // TODO: this is analagous to the start "page" for the SSD1306
147+ #define kCmdStartRow ((uint8_t )0xB0 )
155148#define kCmdStartColLow ((uint8_t )0x0F ) // Start Column = StartColHigh (MSB) | StartColLow (LSB)
156149#define kCmdStartColHigh ((uint8_t )0x10 )
157150#define kCmdStartLine ((uint8_t )0xA2 ) // Notice how this is different from the same cmd on the 1306 (0x40)
@@ -269,7 +262,7 @@ const std::map<uint8_t, uint8_t> scrollIntervals = {
269262// //////////////////////////////////////////////////////////////////////////////////
270263// setup defaults - called from constructors
271264//
272- // Just a bunch of member variable inits (TODO: should these be the defaults on reset or what we want them to be after init?)
265+ // Just a bunch of member variable inits
273266
274267void QwGrCH1120::setupDefaults (void )
275268{
@@ -279,9 +272,7 @@ void QwGrCH1120::setupDefaults(void)
279272 m_rop = {grROPCopy};
280273 m_i2cBus = {nullptr };
281274 m_i2cAddress = {0x3C }; // address of the device (0x3D for closed)
282- // m_initHWComPins = {kDefaultPinConfig};
283275 m_initPreCharge = {kDefaultPreCharge };
284- // m_initVCOMDeselect = {kDefaultVCOMDeselect};
285276 m_initContrast = {kDefaultContrast };
286277 m_isInitialized = {false };
287278}
@@ -412,28 +403,18 @@ void QwGrCH1120::setupOLEDDevice(bool clearDisplay){
412403 if (clearDisplay)
413404 sendDevCommand (kCmdDisplayOff );
414405
415- // sendDevCommand(kCmdRowStartEnd, kDefaultRowStart, kDefaultRowEnd);
416- // sendDevCommand(kCmdColStartEnd, kDefaultColStart, kDefaultColEnd);
417- // TODO: This may have to happen when the display is on or be broken out into its separate commands
418406 setScreenBufferAddress (kDefaultRowStart , kDefaultRowEnd );
419407
420408 sendDevCommand (kCmdStartLine , kDefaultDisplayStart );
421-
422- // ELI HAD THIS AS 0x0F, 200...
423409 sendDevCommand (kCmdContrastControl , m_initContrast);
424410 sendDevCommand (kCmdGrayMono , kDefaultMonoMode );
425411 sendDevCommand (kCmdHorizAddressing , kDefaultHorizontalAddressing );
426412 sendDevCommand (kCmdSegRemapDown );
427- // sendDevCommand(kCmdComOutScan0Last);
428413 sendDevCommand (kCmdComOutScan0First );
429414 sendDevCommand (kCmdDisplayRotation , kDefaultRotateDisplayNinety );
430- // sendDevCommand(kCmdDisplayRotation, 0x00);
431415 sendDevCommand (kCmdDisableEntireDisplay );
432416
433- // sendDevCommand(kCmdNormalDisplay);
434- // sendDevCommand(kCmdMultiplexRatio, kDefaultMultiplexRatio);
435417 sendDevCommand (kCmdDisplayOffset , kDefaultDisplayOffset );
436- // sendDevCommand(kCmdDisplayOffset, 0);
437418 sendDevCommand (kCmdDischargeFront , kDefaultDischargeFront );
438419 sendDevCommand (kCmdDischargeBack , kDefaultDischargeBack );
439420 sendDevCommand (kCmdPreCharge , m_initPreCharge);
@@ -483,25 +464,10 @@ void QwGrCH1120::setBuffer(uint8_t *pBuffer)
483464void QwGrCH1120::clearScreenBuffer (void )
484465{
485466 // Clear out the screen buffer on the device
486- // each row is m_nPages bytes wide
487- // and we have m_viewport.height rows
488- // uint8_t emptyRow[m_nPages] = {0};
489-
490- // setScreenBufferAddress(0, 0); // Warning: This function works-ish but only for even-numbered rows.
491- // so we can use it here, but do not expect it to work in all instances
492-
493- // setScreenBufferAddress(0, 32);
494-
495- // for (int i = 0; i < m_viewport.height; i++)
496- // {
497- // sendDevData((uint8_t *)emptyRow, m_nPages); // clear out row
498- // }
499467 uint8_t emptyPage[kPageMax ] = {0 };
500468
501469 for (int i = 0 ; i < kMaxPageNumber ; i++)
502470 {
503- // setScreenBufferAddress(i * kPageHeight, (i + 1) * kPageHeight);
504- // setScreenBufferAddress(i, 0);
505471 setScreenBufferAddress (0 , i);
506472 sendDevData (emptyPage, kPageMax );
507473 }
@@ -516,8 +482,6 @@ void QwGrCH1120::initBuffers(void)
516482{
517483 int i;
518484
519- // TODO: the concept of ('width' and 'height' might be sorta swapped for this as opposed to old driver)
520- // that might not matter so much since this is square 128x128
521485 // clear out the local graphics buffer
522486 if (m_pBuffer)
523487 memset (m_pBuffer, 0 , m_viewport.width * m_nPages);
@@ -560,11 +524,7 @@ void QwGrCH1120::resendGraphics(void)
560524//
561525// Flip the onscreen graphics vertically.
562526void QwGrCH1120::flipVert (bool bFlip){
563- // If we are already formatted for the flipped display, just return
564- // sendDevCommand(bFlip ? kCmdSegRemapUp : kCmdSegRemapDown);
565527 sendDevCommand (bFlip ? kCmdComOutScan0Last : kCmdComOutScan0First );
566-
567- resendGraphics ();
568528}
569529
570530// //////////////////////////////////////////////////////////////////////////////////
@@ -574,32 +534,23 @@ void QwGrCH1120::flipVert(bool bFlip){
574534// graphics data to the device/screen buffer.
575535void QwGrCH1120::flipHorz (bool bFlip){
576536
577- // sendDevCommand(kCmdDisplayOff); // TODO: verify is this necessary?
578-
579- // TODO: Implement this for the new way with individual pixel addressing.
580- // we might have to have a variable that is used by setScreenBufferAddress to account for the necessary shift
581- // when flipped
582537 if (bFlip){
583538 // If we are flipping to horizontal, we need to adjust row start and end to the end of the display memory
584539 // This is because when we horizontally flip, if our viewport is smaller than the total area, we will flip in some garbage
585- // When flipping we need to offset by the max width minus the viewport width (or height, I've kind of lost the plot on which is which with this square display in 90 degree rotation mode)
586- // uint8_t offset = kMaxCH1120Width - m_viewport.width; // TODO: Should these really be widths or heights?
587- // sendDevCommand(kCmdRowStartEnd, kDefaultRowStart + offset, kDefaultRowEnd + offset);
540+ // When flipping we need to offset by the max width minus the viewport width (maybe should be height, may have to update with non-square display)
588541 horz_flip_offset = kMaxCH1120Width - m_viewport.width ;
589542
590543 sendDevCommand (kCmdSegRemapUp );
591544 }
592545
593546 else {
594547 // If in normal mode, just set to the defaults
595- // sendDevCommand(kCmdRowStartEnd, kDefaultRowStart, kDefaultRowEnd);
596548 horz_flip_offset = 0 ;
597549
598550 sendDevCommand (kCmdSegRemapDown );
599551 }
600552
601- // sendDevCommand(kCmdDisplayOn); // TODO: verify is this necessary?
602-
553+ clearScreenBuffer ();
603554 resendGraphics ();
604555}
605556
@@ -731,41 +682,12 @@ void QwGrCH1120::displayPower(bool enable)
731682// This function sort of becomes useless because the entire page is rewritten each time now, so there isn't so much a concept of "erasing"
732683void QwGrCH1120::erase (void )
733684{
734- // Serial.println("Calling erase()");
735685 if (!m_pBuffer)
736686 return ;
737687
738- // Print all the page states and corresponding memset they will have:
739- // for (uint8_t i = 0 ; i < m_nPages; i++){
740- // Serial.print("Page ");
741- // Serial.print(i);
742- // Serial.print(" xmin: ");
743- // Serial.print(m_pageState[i].xmin);
744- // Serial.print(" xmax: ");
745- // Serial.println(m_pageState[i].xmax);
746-
747- // Serial.print("Corresponding memset: ");
748- // Serial.print("memset(m_pBuffer + ");
749- // Serial.print(i * m_viewport.width + m_pageState[i].xmin);
750- // Serial.print(", 0, ");
751- // Serial.print(m_pageState[i].xmax - m_pageState[i].xmin + 1);
752- // Serial.println(");");
753- // }
754-
755688 // Cleanup the dirty parts of each page in the graphics buffer.
756689 for (uint8_t i = 0 ; i < m_nPages; i++)
757690 {
758- // Serial.print("Erasing page ");
759- // Serial.println(i);
760-
761- // Now, print the arguments we are about to pass to memset:
762- // Serial.print("First Arg to Memset (offset into buffer): ");
763- // Serial.println(i * m_viewport.width + m_pageState[i].xmin);
764- // Serial.print("Second Arg to Memset: ");
765- // Serial.println(0);
766- // Serial.print("Third Arg to Memset: ");
767- // Serial.println(m_pageState[i].xmax - m_pageState[i].xmin + 1);
768- // m_pageState
769691 // The current "dirty" areas of the graphics [local] buffer.
770692 // Areas that haven't been sent to the screen/device but are
771693 // "dirty"
@@ -783,26 +705,15 @@ void QwGrCH1120::erase(void)
783705 memset (m_pBuffer + i * m_viewport.width + m_pageState[i].xmin , 0 ,
784706 m_pageState[i].xmax - m_pageState[i].xmin + 1 ); // add one b/c values are 0 based
785707
786- // Serial.print("setting page clean ");
787- // Serial.println(i);
788708 // clear out any pending dirty range for this page - it's erased
789709 pageSetClean (m_pageState[i]);
790710 }
791711
792712 // Indicate that the data transfer to the device should include the erase
793713 // region
794- // Serial.println("Erasing pages:");
795714 m_pendingErase = true ;
796715}
797716
798- // //TODO: remove this if it's unused
799- // void QwGrCH1120::erase(void) {
800- // // memset the entire buffer to 0
801- // memset(m_pBuffer, 0, m_nPages * m_viewport.width);
802-
803- // m_pendingErase = true;
804- // }
805-
806717// //////////////////////////////////////////////////////////////////////////////////
807718//
808719// draw_pixel()
@@ -813,7 +724,6 @@ void QwGrCH1120::erase(void)
813724
814725void QwGrCH1120::drawPixel (uint8_t x, uint8_t y, uint8_t clr)
815726{
816- // Serial.println("Calling drawPixel with x: " + String(x) + ", y: " + String(y) + ", color: " + String(clr));
817727 // quick sanity check on range
818728 if (x >= m_viewport.width || y >= m_viewport.height )
819729 return ; // out of bounds
@@ -824,8 +734,6 @@ void QwGrCH1120::drawPixel(uint8_t x, uint8_t y, uint8_t clr)
824734 (clr ? bit : 0 ), bit); // which bit to set in byte
825735
826736 // print Buffer after drawing pixel:
827- // printBuffer();
828- // rawPrintBuffer();
829737 pageCheckBounds (m_pageState[y / kByteNBits ],
830738 x); // update dirty range for page
831739}
@@ -839,7 +747,6 @@ void QwGrCH1120::drawLineHorz(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, ui
839747{
840748 // Basically we set a bit within a range in a page of our graphics buffer.
841749
842- // Serial.println("Calling Draw Line Horz with x0: " + String(x0) + ", y0: " + String(y0) + ", x1: " + String(x1) + ", y1: " + String(y1));
843750 // in range
844751 if (y0 >= m_viewport.height )
845752 return ;
@@ -1078,7 +985,6 @@ void QwGrCH1120::drawBitmap(uint8_t x0, uint8_t y0, uint8_t dst_width, uint8_t d
1078985
1079986bool QwGrCH1120::setScreenBufferAddress (uint8_t row, uint8_t column)
1080987{
1081- // Serial.println("Calling setScreenBufferAddress with row: " + String(row) + ", column: " + String(column));
1082988 if (row >= m_viewport.height || column >= m_viewport.width )
1083989 return false ;
1084990
@@ -1125,28 +1031,15 @@ void QwGrCH1120::rawPrintBuffer() {
11251031// display()
11261032//
11271033
1128- // OLD:
11291034// Send the "dirty" areas of the graphics buffer to the device's screen buffer.
11301035// Only send the areas that need to be updated. The update region is based on
11311036// new graphics to display, and any currently displayed items that need to be
11321037// erased.
11331038
1134- // NEW:
1135- // Send the ENTIRE graphics buffer to the device's screen buffer. Include things that are updated or not updated
1136- // This is necessary because we cannot directly index to pixels since the row setting command is broken.
1137- // In the future, if we ever get a row setting command that works, we can re-instate the fancy (only-update-dirty methodology)
1138-
11391039void QwGrCH1120::display ()
11401040{
1141- // sendDevData(m_pBuffer, m_nPages * m_viewport.height); // send the entire buffer to the device
1142-
11431041 // Loop over our page descriptors - if a page is dirty, send the graphics
11441042 // buffer dirty region to the device for the current page
1145-
1146- // Print the buffer and print the raw buffer
1147- // printBuffer();
1148- // rawPrintBuffer();
1149-
11501043 pageState_t transferRange;
11511044
11521045 for (int i = 0 ; i < m_nPages; i++) {
@@ -1155,14 +1048,6 @@ void QwGrCH1120::display()
11551048
11561049 transferRange = m_pageState[i];
11571050
1158- // print the transferRange:
1159- // Serial.print("Transfer range for page ");
1160- // Serial.print(i);
1161- // Serial.print(": min: ");
1162- // Serial.print(transferRange.xmin);
1163- // Serial.print(" - max: ");
1164- // Serial.println(transferRange.xmax);
1165-
11661051 // If an erase has happend, we need to transfer/include erase update range
11671052 if (m_pendingErase)
11681053 pageCheckBoundsDesc (transferRange, m_pageErase[i]);
@@ -1173,7 +1058,7 @@ void QwGrCH1120::display()
11731058
11741059 // set the start address to write the updated data to the devices screen
11751060 // buffer
1176- // TODO: should the offset be applied to the first or second arg?
1061+
11771062 // write out the xmin and xmax for each page descriptor
11781063 // setScreenBufferAddress(i, transferRange.xmin + horz_flip_offset);
11791064 setScreenBufferAddress (transferRange.xmin + horz_flip_offset, i);
0 commit comments