Jump to content

Using ugfx on a Teensy 3.1 or Teensy LC


crteensy

Recommended Posts

Hi,

I'm new to ugfx and considered writing my own graphics library after trying u8lib and adafruit_gfx. Before I really do that I'd like to give it a try with ugfx. I already found the wiki and looked for buffers and paging. I'm not sure if I really need that, but the Teensy hardware I'd like to use doesn't have enough RAM for a whole frame. So my first question is:

Is it possible to have a paged frame buffer (1/4th or 1/8th) with ugfx? If not, is the window display driver model a suitable workaround? (Would you recommend not to use a paged buffer?)

I have applications that use only one color channel of an OLED display (dimmable red, for eyes adapted to darkness). My canvas can essentially be monochrome in such a case, and the mapping form monochrome to dimmed color is done by the display driver. Is that possible with ugfx? Are monochrome (or less-than-8-bit in general) colors packed in memory or does a pixel always occupy at least one byte?

Regards

Christoph

Link to comment
Share on other sites

  • Replies 51
  • Created
  • Last Reply

Top Posters In This Topic

Most graphics libraries require a full frame to be retained in memory. This however is not a requirement for ugfx (except for some monochrome displays) and is one of the primary advantages of ugfx over other libraries.

If you are using a color display ugfx knows how to do all the drawing without a framebuffer in ram so there is no need to be worrying about paging.

If you are in a very tightly memory confined processor, do not use the GWIN module. Use the GDISP api directly. You should also not enable scrolling as that allocates an extra buffer to cache the scrolling operation.

Link to comment
Share on other sites

Thank you, so for now I'll just try to write a driver that works without a framebuffer.

How can I mix C++ into my ugfx display driver? Is there a commonly recommended, documented way of doing that? I've gotten ugfx to compile with teensyduino up to the point where I need to write the actual display driver. Now it gets tricky because it's common to call C functions in C++, but not the other way around.

Link to comment
Share on other sites

When I create a gdisp_lld_config.h, what defines does ugfx look for in that file? I can't seem to find a list of common flags or options apart from the pixel format. I'd like to write a driver for the SSD1351. How can I bring that chip, gdisp_lld_config.h and the model descriptions at http://wiki.ugfx.org/index.php?title=Di ... iver_Model together?

Link to comment
Share on other sites

Hello crteensy and welcome to the community!

Thank you for giving uGFX a try before you start rolling your own - this is exactly how uGFX started ;-)

About the C++ question: From the uGFX point of view there is no problem if you want to use C++ in your board files. uGFX is 100% pure C and if you can get your toolchain to compile your C++ code then I don't see any problems there.

About the gdisp_lld_config.h: Those are not documented because they are driver dependent. Some controller chips allow hardware fillings and some don't, some need buffer flushing and others don't. Hence we didn't bother to implement a standard list of available options because there will always some special one pop up that is not yet there.

When you're using an existing driver simply copy the existing config file from the drivers config file. If you write your own driver then you're free to do it as you like. Of course it would make sense to stick with what's already there when it comes to naming.

Probably we should create a small list anyway (somewhere in the wiki).

To your frame buffer question: As inmarket already said uGFX itself does not care about that. It all comes down to the drivers implementation. When it comes to high-level user API then you might want to take a look at the Pixmap feature of the GDISP module: https://bitbucket.org/Tectu/ugfx/src/a5 ... ?at=master

I hope that clears up things a bit. Please let us know should you have any further questions.

~ Tectu

Link to comment
Share on other sites

I don't quite understand what you say about the defines in gdisp_lld_config.h. Take for example line 17 in ugfx/drivers/gdisp/PCD8544/gdisp_lld_config.h:

#define GDISP_HARDWARE_FLUSH			TRUE		// This controller requires flushing

By the comment this line seems to tell ugfx something about the controller. OTOH, you say

If you write your own driver then you're free to do it as you like.

but I'm certainly not free to do as I like when I want ugfx to understand my controller features. Or am I getting this totally wrong?

Link to comment
Share on other sites

Sorry for being unclear. You are right, there are certain configs that the GDISP module uses to communicate with the driver. What I meant to say is that you don't have to implement any of these as they are usually completely optional. For starting you really just need the init() and the setPixel() routine.

You can find a list of the currently implemented GDISP <--> Driver config parameters here: /src/gdisp/gdisp_driver.h

This is indeed not properly documented and we should write a proper HowTo on writing a new display driver. It has just not made it up to the high priority part of the ToDo list so far.

~ Tectu

Link to comment
Share on other sites

For starting you really just need the init() and the setPixel() routine.

You mean gdisp_lld_init() and gdisp_lld_draw_pixel() in the point-and-block-model?

When I want to mix with the Window model, can I just add functions like e.g. gdisp_lld_write_start(), gdisp_lld_write_color() and gdisp_lld_write_stop()? This is not 100% clear in the source (src/gdisp/gdisp_driver.h, lines 127..135):

Either GDISP_HARDWARE_STREAM_WRITE or GDISP_HARDWARE_DRAWPIXEL must be provided by the driver

Is that an exclusive or?

Either way, if I'm understanding this correctly I have to define GDISP_HARDWARE_DRAWPIXEL and/or GDISP_HARDWARE_STREAM_WRITE in my gdisp_lld_config.h for ugfx to understand how to draw on my display. If that is the case then I think I'm slowly getting it...

Some more questions:

- What effect should GDISP_HARDWARE_FLUSH have on my display and when exactly is gdisp_lld_flush() called? Is the purpose of this function to push all pending pixel writes or other commands to the display before any further drawing is done?

- How does ugfx know which driver to use? In the demos (ugfx/demos/modules/gdisp/basics/main.c or gfxconf.h) I see no sign of an actual display being used.

Link to comment
Share on other sites

For most controllers for embedded platforms it is the window model that the controller uses. Usually set pixel, area fill etc routines are only needed to handle unusual controller bugs and these routines are defined in addition to the normal window mode calls. It is only when you start to get to pc emulation layers and very big displays with powerful processors that you see controllers that are not window mode.

You are right, the purpose of gdisp_lld_config.h is to tell gdisp what routines to expect in the driver and how to call them.

The easiest way to port a new controller chip is to find an existing one that is similar and make the changes to it. Given you are implementing the ssd1351 I would suggest starting with another ssd or ili driver with a similar electrical interface.

I would not suggest using c++ in the driver, not because it isn't possible as it is possible, but because it raises all the memory management issues that c++ represents in a place where that extra complexity is simply not worth the trouble. That is particularly true for such a small processor as you are using.

With regard to flushing, most color controllers don't need flushing as operations are handled directly by the controller. Flushing is typically only needed where a ram buffer has to be used (like many of the monochrome controllers) because the controller does not support single pixel level operations. When flushing is required the gdisp api supports 3 models of operation...

1. A flush occurs on every high level gdisp api call

2. A flush is performed on a periodic timer, and/or

3. The use program directly controls flushing using gdispFlush.

Which mode is used is controlled by the application gfxconf.h file.

Reading the code for a few existing drivers will quickly fall into a pattern.

In terms of not seeing reference to a particular driver in the demo's, that is correct. There is a special bit of include file magic happening that means as long as you are only using one controller there is no need to mention it by name. The driver linked at compile time is the one used.

If you want to have more than one display in the same application then that magic doesn't work and you need to specify the list of drivers in your gfxconf.h. See the multiple display gdisp demo for more details.

This same approach of auto detecting which driver to use provided there is only one of them for each device class is also used for mouse/touch, keyboard and will eventually be used for all device support in ugfx. This development work is relatively recent however so some device classes don't use it yet eg audio.

To see the parts that make it work there are some defines at the very top of each gdisp driver specifying the driver name. Everything else is handled by the "magic" gdisp/driver.h

Link to comment
Share on other sites

Yes, starting with a similar driver (I picked the SSD1306) seems to be a good starting point. However, I'm having one problem during the linking stage (I solved some others, they were easier to fix):

The linker complains

disp.c:(.text._gdispInit+0x40): undefined reference to `GDISPVMT_OnlyOne'

I have not added any define in gfxconf.h, so ugfx should default to an external declaration of

extern const GDISPVMT const		GDISPVMT_OnlyOne[1];

in _gdispInit. I can't find the place where it is actually defined.

Link to comment
Share on other sites

Turns out the undefined reference was related to C/C++ mixing - nevermind.

This is going to be the hardest part of my afternoon because I need teensyduino functions and classes, which are mainly written in C++. I can't see a way of keeping them apart completely, but I'll do my best to make a clean split. C++ does not by itself mean that memory usage will be higher, or that hidden things happen. Of course, if I try to use a bunch of std::vectors then I'll run into problems. But bad code can be written just as easily in C.

Link to comment
Share on other sites

C++ does not by itself mean that memory usage will be higher, or that hidden things happen. Of course, if I try to use a bunch of std::vectors then I'll run into problems. But bad code can be written just as easily in C.

Looks like you know what you're doing. Getting this display working shouldn't be a problem for you then :)

Let us know when we can help.

~ Tectu

Link to comment
Share on other sites

OK, at least I have a compiling set of files now, at least for the Teensy LC.

When I try to compile for Teensy 3.1, something in my build setup is hiding _ebss, which is defined in the linker script for both platforms (LC and 3.1 have different linker scripts, but both scripts define _ebss). I'll also post this problem in PJRC's forum where the teensyduino people are.

Here are my files for a dummy driver that calls the C++ interface via the board header.

ugfxconf.h:

#ifndef _GFXCONF_H
#define _GFXCONF_H

#define GFX_USE_OS_RAW32 TRUE
#define GFX_NO_OS_INIT TRUE
#define GFX_USE_GDISP TRUE

#endif /* _GFXCONF_H */

os.c:

#include 
#include

systemticks_t gfxSystemTicks(void)
{
return millis();
}

systemticks_t gfxMillisecondsToTicks(delaytime_t ms)
{
return ms;
}

gdisp_lld_config.h:

#ifndef GDISP_LLD_CONFIG_H
#define GDISP_LLD_CONFIG_H

#if GFX_USE_GDISP

/*===========================================================================*/
/* Driver hardware support. */
/*===========================================================================*/

#define GDISP_HARDWARE_FLUSH TRUE // This controller requires flushing
#define GDISP_HARDWARE_DRAWPIXEL TRUE
#define GDISP_HARDWARE_PIXELREAD FALSE
#define GDISP_HARDWARE_CONTROL FALSE
#define GDISP_HARDWARE_FILLS FALSE

#define GDISP_LLD_PIXELFORMAT GDISP_PIXELFORMAT_RGB565
// This controller supports a special gdispControl() to inverse the display.
// Pass a parameter of 1 for inverse and 0 for normal.
#define GDISP_CONTROL_INVERSE (GDISP_CONTROL_LLD+0)

#endif /* GFX_USE_GDISP */

#endif // GDISP_LLD_CONFIG_H

gdisp_lld_ssd1351.c: minimal, but should provide the required interface; the low-level magic will be in the board file as suggested

#include 

#if GFX_USE_GDISP

#define GDISP_DRIVER_VMT GDISPVMT_SSD1351
#include "gdisp_lld_config.h"
#include "src/gdisp/gdisp_driver.h"

#include "board_ssd1351.h"

/*===========================================================================*/
/* Driver local definitions. */
/*===========================================================================*/

#ifndef GDISP_SCREEN_HEIGHT
#define GDISP_SCREEN_HEIGHT 128
#endif
#ifndef GDISP_SCREEN_WIDTH
#define GDISP_SCREEN_WIDTH 128
#endif
#ifndef GDISP_INITIAL_CONTRAST
#define GDISP_INITIAL_CONTRAST 100
#endif
#ifndef GDISP_INITIAL_BACKLIGHT
#define GDISP_INITIAL_BACKLIGHT 100
#endif

#define GDISP_FLG_NEEDFLUSH (GDISP_FLG_DRIVER<<0)

/*===========================================================================*/
/* Driver local functions. */
/*===========================================================================*/

// Some common routines and macros
#define RAM(g) ((uint8_t *)g->priv)
#define write_cmd2(g, cmd1, cmd2) { write_cmd(g, cmd1); write_cmd(g, cmd2); }
#define write_cmd3(g, cmd1, cmd2, cmd3) { write_cmd(g, cmd1); write_cmd(g, cmd2); write_cmd(g, cmd3); }

// Some common routines and macros
#define delay(us) gfxSleepMicroseconds(us)
#define delayms(ms) gfxSleepMilliseconds(ms)

#define xyaddr(x, y) (SSD1306_PAGE_OFFSET + (x) + ((y)>>3)*SSD1306_PAGE_WIDTH)
#define xybit(y) (1<<((y)&7))


/*===========================================================================*/
/* Driver exported functions. */
/*===========================================================================*/

LLDSPEC bool_t gdisp_lld_init(GDisplay *g)
{
init_board(g);
return TRUE;
}

#if GDISP_HARDWARE_DRAWPIXEL
LLDSPEC void gdisp_lld_draw_pixel(GDisplay *g)
{
}
#endif

#if GDISP_HARDWARE_FLUSH
LLDSPEC void gdisp_lld_flush(GDisplay *g)
{
}
#endif

#endif // GFX_USE_GDISP

board_ssd1351.h: just declarations, most of them are probably not necessary

#ifndef BOARD_SSD1351_H
#define BOARD_SSD1351_H

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

void init_board(GDisplay *g);

void post_init_board(GDisplay *g);

void setpin_reset(GDisplay *g, bool_t state);

void acquire_bus(GDisplay *g);

void release_bus(GDisplay *g);

void write_cmd(GDisplay *g, uint8_t cmd);

void write_data(GDisplay *g, uint8_t* data, uint16_t length);

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // BOARD_SSD1351_H

board_ssd1351.cpp: currently just dummy implementations to see what happens

#include 

#include
#include "board_ssd1351.h"

void init_board(GDisplay *g)
{
(void) g;
Serial.println("board init");
}

void post_init_board(GDisplay *g)
{
(void) g;
}

void setpin_reset(GDisplay *g, bool_t state)
{
(void) g;
(void) state;
}

void acquire_bus(GDisplay *g)
{
(void) g;
}

void release_bus(GDisplay *g)
{
(void) g;
}

void write_cmd(GDisplay *g, uint8_t cmd)
{
(void) g;
(void) cmd;
}

void write_data(GDisplay *g, uint8_t* data, uint16_t length)
{
(void) g;
(void) data;
(void) length;
}

That linker issue is surely a bit disturbing, but I can keep on writing useful code for the LC.

Link to comment
Share on other sites

Looks like ugfx is calling my init_board() just fine: I can draw pixels "manually" at any coordinate using my board functions), but it doesn't use my gdisp_lld_draw_pixel() function to actually draw anything. Any ideas?

A wild guess: Make sure that you disable all the filling, hardware clear and streaming features in the drivers config file. When you try to draw something the GDISP module will actually try to optimize it as far as possible using hardware fills etc. If you have these functions linked but empty nothing will happen.

If that is not the problem can you please elaborate how you "manually draw pixels"?

Sorry for not answering your linked question yet. Inmarket and I are currently both very busy. We'll get back to you ASAP!

~ Tectu

Link to comment
Share on other sites

Of course you need see code - here are my current driver files.

gdisp_lld_config.h:

#ifndef GDISP_LLD_CONFIG_H
#define GDISP_LLD_CONFIG_H

#if GFX_USE_GDISP

/*===========================================================================*/
/* Driver hardware support. */
/*===========================================================================*/

#define GDISP_HARDWARE_DRAWPIXEL TRUE
#define GDISP_LLD_PIXELFORMAT GDISP_PIXELFORMAT_RGB565

#endif /* GFX_USE_GDISP */

#endif // GDISP_LLD_CONFIG_H

gdisp_lld_ssd1351.c: (see gdisp_lld_draw_pixel(), please)

#include 

#if GFX_USE_GDISP

#define GDISP_DRIVER_VMT GDISPVMT_SSD1351
#include "gdisp_lld_config.h"
#include "src/gdisp/gdisp_driver.h"

#include "board_ssd1351.h"

/*===========================================================================*/
/* Driver local definitions. */
/*===========================================================================*/

#ifndef GDISP_SCREEN_HEIGHT
#define GDISP_SCREEN_HEIGHT 128
#endif
#ifndef GDISP_SCREEN_WIDTH
#define GDISP_SCREEN_WIDTH 128
#endif
#ifndef GDISP_INITIAL_CONTRAST
#define GDISP_INITIAL_CONTRAST 100
#endif
#ifndef GDISP_INITIAL_BACKLIGHT
#define GDISP_INITIAL_BACKLIGHT 100
#endif

#define GDISP_FLG_NEEDFLUSH (GDISP_FLG_DRIVER<<0)

/*===========================================================================*/
/* Driver local functions. */
/*===========================================================================*/

// none

/*===========================================================================*/
/* Driver exported functions. */
/*===========================================================================*/

LLDSPEC bool_t gdisp_lld_init(GDisplay *g)
{
init_board(g);
return TRUE;
}

#if GDISP_HARDWARE_DRAWPIXEL
LLDSPEC void gdisp_lld_draw_pixel(GDisplay *g)
{
board_drawPixel(g, g->p.x, g->p.y, g->p.color); <- // NOT called, apparently
}
#endif

#endif // GFX_USE_GDISP

board_ssd1351.h: declares my board_drawPixel() function

#ifndef BOARD_SSD1351_H
#define BOARD_SSD1351_H

// pin numbers for teensyduino code
#define SSD1351_DC 14
#define SSD1351_R 15
#define SSD1351_CS 16

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

void init_board(GDisplay *g);

void board_drawPixel(GDisplay* g, coord_t x, coord_t y, color_t c);

void post_init_board(GDisplay *g);

void setpin_reset(GDisplay *g, bool_t state);

void acquire_bus(GDisplay *g);

void release_bus(GDisplay *g);

void write_cmd(GDisplay *g, uint8_t cmd);

void write_data(GDisplay *g, uint8_t data);
void write_datap(GDisplay *g, const uint8_t* data, uint16_t length);

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // BOARD_SSD1351_H

and the board_ssd1351.cpp: implements board_drawPixel(), which draws on the display I connected to my controller. I have verified that the function works; I can draw pixels when I call it in my main().

#include 
#include

#include
#include "board_ssd1351.h"

static SPISettings settings(12000000, MSBFIRST, SPI_MODE0);

void select()
{
digitalWriteFast(SSD1351_CS, 0);
}

void deselect()
{
digitalWriteFast(SSD1351_CS, 1);
}

void command()
{
digitalWriteFast(SSD1351_DC, 0);
}

void data()
{
digitalWriteFast(SSD1351_DC, 1);
}

void write_data(GDisplay *g, uint8_t data); // see below

void home(GDisplay *g)
{
acquire_bus(g);
write_cmd(g, 0x15); // set column address
write_data(g, 0x00); // start
write_data(g, 0x7F); // end

write_cmd(g, 0x75); // set row address
write_data(g, 0x00); // start
write_data(g, 0x7F); // end

write_cmd(g, 0x5C); // write to RAM
release_bus(g);
}

void init_board(GDisplay *g)
{
pinMode(SSD1351_R, OUTPUT);
setpin_reset(g, TRUE);
pinMode(SSD1351_CS, OUTPUT);
deselect();
pinMode(SSD1351_DC, OUTPUT);
delay(50);
setpin_reset(g, FALSE);
delay(50);

acquire_bus(g);
write_cmd(g, 0xFD); // set command lock
write_data(g, 0x12); // unlock OLED driver IC

write_cmd(g, 0xFD); // set command lock
write_data(g, 0xB1); // make commands A1, B1, B3, BB, BE, C1 accesible in unlocked state

write_cmd(g, 0xAE); // sleep mode ON (display off)

write_cmd(g, 0xB3); // Front clock divider / osc freq
write_data(g, 0xF1); // Osc = 0xF; div = 2

write_cmd(g, 0xCA); // set MUX ratio
write_data(g, 127);

write_cmd(g, 0xA0); // Set re-map / color depth
write_data(g, 0b01110100);
// [0] : address increment (0: horizontal, 1: vertical, reset 0)
// [1] : column remap (0: 0..127, 1: 127..0, reset 0)
// [2] : color remap (0: A->B->C, 1: C->B->A, reset 0)
// [3] : reserved
// [4] : column scan direction (0: top->down, 1: bottom->up, reset 0)
// [5] : odd/even split COM (0: disable, 1: enable, reset 1)
// [6..7] : color depth (00,01: 65k, 10: 262k, 11: 262k format 2)

write_cmd(g, 0x15); // Set Column address
write_data(g, 0x00); // start
write_data(g, 0x7F); // end

write_cmd(g, 0x75); // set row address
write_data(g, 0x00); // start
write_data(g, 0x7F); // end

write_cmd(g, 0xA1); // set display start line
write_data(g, 0x00); // 0

write_cmd(g, 0xA2); // set display offset
write_data(g, 0x00); // 0

write_cmd(g, 0xB5); // set GPIO
write_data(g, 0x00); // both HiZ, input disabled

write_cmd(g, 0xAB); // function select
write_data(g, 0x01); // enable internal VDD regulator

write_cmd(g, 0xB1); // set reset / pre-charge period
write_data(g, 0x32); // phase 2: 3 DCLKs, phase 1: 5 DCLKs

write_cmd(g, 0xBE); // set VComH voltage
write_data(g, 0x05); // 0.82*Vcc

write_cmd(g, 0xBB); // set pre-charge voltage
write_data(g, 0x17); // 0.6*Vcc

write_cmd(g, 0xA6); // set display mode: reset to normal display

write_cmd(g, 0xC1); // set contrast current for A,B,C
write_data(g, 0xC8);
write_data(g, 0x80);
write_data(g, 0xC8);

write_cmd(g, 0xC7); // master contrast current control
write_data(g, 0x0F); // no change

write_cmd(g, 0xB4); // set segment low voltage
write_data(g, 0xA0); // external VSL
write_data(g, 0xB5); // hard value
write_data(g, 0x55); // hard value

write_cmd(g, 0xB6); // set second pre-charge period
write_data(g, 0x01); // 1 DCLKs

write_cmd(g, 0xAF); // sleep mode OFF (display on)
// write_cmd(g, 0x5C); // write to RAM
release_bus(g);

delay(50);

home(g);

Serial.println("board init");
}

void post_init_board(GDisplay *g)
{
(void) g;
}

void setpin_reset(GDisplay *g, bool_t state)
{
(void) g;
(void) state;
if(state)
{
digitalWriteFast(SSD1351_R, 0);
}
else
{
digitalWriteFast(SSD1351_R, 1);
}
}

void acquire_bus(GDisplay *g)
{
SPI.beginTransaction(settings);
select();
(void) g;
}

void release_bus(GDisplay *g)
{
deselect();
SPI.endTransaction();
(void) g;
}

void write_cmd(GDisplay *g, uint8_t cmd)
{
(void) g;
(void) cmd;
command();
SPI.transfer(cmd);
data();
}

void write_data(GDisplay *g, uint8_t data)
{
SPI.transfer(data);
}

void write_data(GDisplay *g, const uint8_t* data, uint16_t length)
{
(void) g;
(void) data;
(void) length;
for (uint8_t i = 0; i < length; i++)
while(length)
{
SPI.transfer(data[i]);
}
}

void write_datap(GDisplay *g, const uint8_t* data, uint16_t length)
{
write_data(g, data, length);
}

void board_drawPixel(GDisplay* g, coord_t x, coord_t y, color_t c)
{
Serial.printf("pixel @ %d %d : %4x\n", x, y, c);

acquire_bus(g);

write_cmd(g, 0x15);
write_data(g, x);
write_data(g, 127);

write_cmd(g, 0x75);
write_data(g, y);
write_data(g, 127);

write_cmd(g, 0x5C);
write_data(g, c >> 8);
write_data(g, c & 0xFF);

release_bus(g);
}

main.cpp: this is where I draw "manually" in loop()

#include 
#include
#include

#include

void setup()
{
// pinMode(LED_BUILTIN, OUTPUT);
while(!Serial.available());
while(Serial.available())
{
Serial.read();
}
Serial.println("Start");
SPI.begin();

coord_t width, height;
coord_t i, j;

// Initialize and clear the display
gfxInit();

// Get the screen size
width = gdispGetWidth();
height = gdispGetHeight();

// Code Here
gdispDrawBox(10, 10, width/2, height/2, Yellow);
gdispFillArea(width/2, height/2, width/2-10, height/2-10, Blue);
gdispDrawLine(5, 30, width-50, height-40, Red);

for(i = 5, j = 0; i < width && j < height; i += 7, j += i/20)
gdispDrawPixel(i, j, White);

// while(TRUE) {
// gfxSleepMilliseconds(500);
// }
}

void loop()
{
static uint8_t x = 0;
static uint8_t y = 0;
// board_drawPixel(nullptr, x, y, Red); // <- WORKS
gdispDrawPixel(x, y, Red); // <- nothing happens
x++;
if (x >= 0x80)
{
x = 0;
y++;
if (y >= 0x80)
{
y = 0;
}
}
}

Link to comment
Share on other sites

There are a few conceptual errors here (although I wouldn't have thought they would have stopped it from working). I will fix these up for you in the next few hours and add it to the master repository.

The errors mainly relate to the division of code between the driver and the board file. The driver should have all the code relating to the commands that get sent to the controller. It should contain all your calls to write_cmd for example. The only thing that is supposed to be in the board file is the electrical stuff eg how to get a byte to the controller (in your case via spi and some data lines).

The board h file is now quiet slim and it should contain the functions as static functions in the h file itself. The reason for this is to allow for multiple display drivers being linked into the same program.

Also, your controller is not a point and block controller, it is a window controller. Defining a set pixel routine will be a very slow way of talking to the controller. The ssd1306 controller was probably a bad example start with as that is a monochrome display and as mentioned before monochrome displays have particular problems. A much better controller to start with will be the ILI9341 which is conceptually a very similar controller.

I will post again when I have the code ready in the master repository.

Link to comment
Share on other sites

I see. Thanks for your effort! With that explanation I might have come up with a suitable solution as well, but if you want to work on it that's great.

Even with the ILI9341 I would have probably made the same mistake. There was no sign of the static-inline-ness in the board header being that important, so I would have pushed as much logic into the cpp file as I have now. Also I'm not aiming for speed now, just for a working implementation. The one pixel function looked more attractive than three for streaming...

Link to comment
Share on other sites

Hm...I'm currently rewriting my board_ssd1351.h to contain static inline functions. This is a header included by the C compiler.

My low-level interfacing contains C++ calls, and I really don't fancy rewriting tested C++ code in C. So I need to add extern "C" declarations for the C++ leftovers to board_ssd1351.h, which cannot be static inline. The C++ file will then include that header just like it does now. I have the impression that in the end the driver will not call those functions, and the problem remains - just shifted towards a different set of functions.

It's also odd that my C++ board_init() function gets called, but the board_drawPixel() does not get called - even though they are in the same compilation unit.

Link to comment
Share on other sites

I have put all the higher level commands in gdisp_lld_ssd1351.c, with as many static inline functions in board_ssd1351.h as possible. This seems to match the pattern of the drivers you have in ugfx with the exception of some C++ in board_ssd1351.cpp. I can not include the main Arduino in board_ssd1351.h, because then the compiler complains about conflicting types for the standard int types (uint32_t). I don't know what exact parts of the libraries (ugfx and arduino) are fighting here... this is the reason why there is no low level I/O in the board header.

However, my gdisp_lld_init() is called by ugfx, I can see the output on my terminal. This means that the C++ part of that function works correctly. However, I still don't get any pixels drawn by gdisp_lld_draw_pixel(). It should also show debugging output just to be sure, bot nothing - neither on the display nor on the terminal. Is it possible that some default (empty) implementation of gdisp_lld_draw_pixel() is used by ugfx?

gfxconf.h:

#ifndef _GFXCONF_H
#define _GFXCONF_H

#define GFX_USE_OS_RAW32 TRUE
#define GFX_NO_OS_INIT TRUE
#define GFX_USE_GDISP TRUE

#endif /* _GFXCONF_H */

gdisp_lld_config.h:

#ifndef GDISP_LLD_CONFIG_H
#define GDISP_LLD_CONFIG_H

#if GFX_USE_GDISP

/*===========================================================================*/
/* Driver hardware support. */
/*===========================================================================*/

#define GDISP_HARDWARE_DRAWPIXEL TRUE
#define GDISP_LLD_PIXELFORMAT GDISP_PIXELFORMAT_RGB565

#endif /* GFX_USE_GDISP */

#endif // GDISP_LLD_CONFIG_H

gdisp_lld_ssd1351.c:

#include 

#if GFX_USE_GDISP

#define GDISP_DRIVER_VMT GDISPVMT_SSD1351
#include "gdisp_lld_config.h"
#include "src/gdisp/gdisp_driver.h"

#include "board_ssd1351.h"

/*===========================================================================*/
/* Driver local definitions. */
/*===========================================================================*/

#ifndef GDISP_SCREEN_HEIGHT
#define GDISP_SCREEN_HEIGHT 128
#endif
#ifndef GDISP_SCREEN_WIDTH
#define GDISP_SCREEN_WIDTH 128
#endif
#ifndef GDISP_INITIAL_CONTRAST
#define GDISP_INITIAL_CONTRAST 100
#endif
#ifndef GDISP_INITIAL_BACKLIGHT
#define GDISP_INITIAL_BACKLIGHT 100
#endif

#define GDISP_FLG_NEEDFLUSH (GDISP_FLG_DRIVER<<0)

/*===========================================================================*/
/* Driver local functions. */
/*===========================================================================*/

// none

/*===========================================================================*/
/* Driver exported functions. */
/*===========================================================================*/

LLDSPEC bool_t gdisp_lld_init(GDisplay *g)
{
init_board_pins(g);
acquire_bus(g);
write_cmd(g, 0xFD); // set command lock
write_data(g, 0x12); // unlock OLED driver IC

write_cmd(g, 0xFD); // set command lock
write_data(g, 0xB1); // make commands A1, B1, B3, BB, BE, C1 accesible in unlocked state

write_cmd(g, 0xAE); // sleep mode ON (display off)

write_cmd(g, 0xB3); // Front clock divider / osc freq
write_data(g, 0xF1); // Osc = 0xF; div = 2

write_cmd(g, 0xCA); // set MUX ratio
write_data(g, 127);

write_cmd(g, 0xA0); // Set re-map / color depth
write_data(g, 0b01110100);
// [0] : address increment (0: horizontal, 1: vertical, reset 0)
// [1] : column remap (0: 0..127, 1: 127..0, reset 0)
// [2] : color remap (0: A->B->C, 1: C->B->A, reset 0)
// [3] : reserved
// [4] : column scan direction (0: top->down, 1: bottom->up, reset 0)
// [5] : odd/even split COM (0: disable, 1: enable, reset 1)
// [6..7] : color depth (00,01: 65k, 10: 262k, 11: 262k format 2)

write_cmd(g, 0x15); // Set Column address
write_data(g, 0x00); // start
write_data(g, 0x7F); // end

write_cmd(g, 0x75); // set row address
write_data(g, 0x00); // start
write_data(g, 0x7F); // end

write_cmd(g, 0xA1); // set display start line
write_data(g, 0x00); // 0

write_cmd(g, 0xA2); // set display offset
write_data(g, 0x00); // 0

write_cmd(g, 0xB5); // set GPIO
write_data(g, 0x00); // both HiZ, input disabled

write_cmd(g, 0xAB); // function select
write_data(g, 0x01); // enable internal VDD regulator

write_cmd(g, 0xB1); // set reset / pre-charge period
write_data(g, 0x32); // phase 2: 3 DCLKs, phase 1: 5 DCLKs

write_cmd(g, 0xBE); // set VComH voltage
write_data(g, 0x05); // 0.82*Vcc

write_cmd(g, 0xBB); // set pre-charge voltage
write_data(g, 0x17); // 0.6*Vcc

write_cmd(g, 0xA6); // set display mode: reset to normal display

write_cmd(g, 0xC1); // set contrast current for A,B,C
write_data(g, 0xC8);
write_data(g, 0x80);
write_data(g, 0xC8);

write_cmd(g, 0xC7); // master contrast current control
write_data(g, 0x0F); // no change

write_cmd(g, 0xB4); // set segment low voltage
write_data(g, 0xA0); // external VSL
write_data(g, 0xB5); // hard value
write_data(g, 0x55); // hard value

write_cmd(g, 0xB6); // set second pre-charge period
write_data(g, 0x01); // 1 DCLKs

write_cmd(g, 0xAF); // sleep mode OFF (display on)

write_cmd(g, 0x5C); // write to RAM

// clear display
uint16_t i = 0;
for (i = 0; i < 128*128; i++)
{
write_data(g, 0);
write_data(g, 0);
}

release_bus(g);
return TRUE;
}

#if GDISP_HARDWARE_DRAWPIXEL
LLDSPEC void gdisp_lld_draw_pixel(GDisplay *g)
{
coord_t x = g->p.x;
coord_t y = g->p.y;
color_t c = g->p.color;
print_draw_pixel(x, y, c); // debugging output
acquire_bus(g);

write_cmd(g, SSD1351_SET_COLUMN_ADDRESS);
write_data(g, x);
write_data(g, 127);

write_cmd(g, SSD1351_SET_ROW_ADDRESS);
write_data(g, y);
write_data(g, 127);

write_cmd(g, SSD1351_WRITE_RAM);
write_data(g, c >> 8);
write_data(g, c & 0xFF);

release_bus(g);
}
#endif // GDISP_HARDWARE_DRAWPIXEL

#endif // GFX_USE_GDISP

board_ssd1351.h:

#ifndef BOARD_SSD1351_H
#define BOARD_SSD1351_H

#include "ssd1351.h"

// pin numbers
#define SSD1351_DC 14
#define SSD1351_R 15
#define SSD1351_CS 16

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

void select();

void deselect();

void command();

void data();

void init_board_pins(GDisplay* g);

void acquire_bus(GDisplay *g);

void release_bus(GDisplay *g);

void write_cmd(GDisplay *g, uint8_t cmd);

void write_data(GDisplay *g, uint8_t data);

static inline void post_init_board(GDisplay *g)
{
(void) g;
}

void print_draw_pixel(coord_t x, coord_t y, color_t c); // for debugging output

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // BOARD_SSD1351_H

board_ssd1351.cpp:

#include 
#include

#include
#include "board_ssd1351.h"

static SPISettings settings(12000000, MSBFIRST, SPI_MODE0);

static inline void setpin_reset(GDisplay *g, bool_t state)
{
(void) g;
(void) state;
if(state)
{
digitalWriteFast(SSD1351_R, 0);
}
else
{
digitalWriteFast(SSD1351_R, 1);
}
}

void select()
{
digitalWriteFast(SSD1351_CS, 0);
}

void deselect()
{
digitalWriteFast(SSD1351_CS, 1);
}

void command()
{
digitalWriteFast(SSD1351_DC, 0);
}

void data()
{
digitalWriteFast(SSD1351_DC, 1);
}

void init_board_pins(GDisplay* g)
{
Serial.println("board init");
pinMode(SSD1351_R, OUTPUT);
setpin_reset(g, TRUE);
pinMode(SSD1351_CS, OUTPUT);
deselect();
pinMode(SSD1351_DC, OUTPUT);
delay(50);
setpin_reset(g, FALSE);
delay(50);
}

void acquire_bus(GDisplay *g)
{
SPI.beginTransaction(settings);
select();
(void) g;
}

void release_bus(GDisplay *g)
{
deselect();
SPI.endTransaction();
(void) g;
}

void write_cmd(GDisplay *g, uint8_t cmd)
{
(void) g;
(void) cmd;
command();
SPI.transfer(cmd);
data();
}

void write_data(GDisplay *g, uint8_t data)
{
SPI.transfer(data);
}

void write_data(GDisplay *g, const uint8_t* data, uint16_t length)
{
(void) g;
(void) data;
(void) length;
for (uint8_t i = 0; i < length; i++)
while(length)
{
SPI.transfer(data[i]);
}
}

void print_draw_pixel(coord_t x, coord_t y, color_t c)
{
Serial.printf("pixel @ %d %d : %4x\n", x, y, c);
}

ssd1351.h (not really used yet):

#ifndef SSD1351_H
#define SSD1351_H

#define SSD1351_SET_COLUMN_ADDRESS 0x15
#define SSD1351_SET_ROW_ADDRESS 0x75
#define SSD1351_WRITE_RAM 0x5C
#define SSD1351_READ_RAM 0x5D
#define SSD1351_SET_REMAP 0xA0

#define SSD1351_SET_DISPLAY_START 0xA1
#define SSD1351_SET_DISPLAY_OFFSET 0xA2
#define SSD1351_SET_DISPLAY_MODE_ALL_OFF 0xA4
#define SSD1351_SET_DISPLAY_MODE_ALL_ON 0xA5
#define SSD1351_SET_DISPLAY_MODE_RESET 0xA6
#define SSD1351_SET_DISPLAY_MODE_INVERT 0xA7
#define SSD1351_SET_FUNCTION_SELECT 0xAB
#define SSD1351_SET_SLEEP_ON 0xAE
#define SSD1351_SET_SLEEP_OFF 0xAF
#define SSD1351_SET_RESET_PRECHARGE 0xB1

#define SSD1351_DISPLAY_ENHANCEMENT 0xB2
#define SSD1351_CLOCKDIV_OSCFREQ 0xB3
#define SSD1351_SET_VSL 0xB4
#define SSD1351_SET_GPIO 0xB5
#define SSD1351_SET_SECOND_PRECHARGE 0xB6

#define SSD1351_LUT_GRAYSCALE 0xB8
#define SSD1351_USE_BUILTIN_LUT 0xB9
#define SSD1351_SET_PRECHARGE 0xBB
#define SSD1351_SET_VCOMH 0xBE

#define SSD1351_SET_CONTRAST 0xC1
#define SSD1351_MASTER_CONTRAST_CURRENT_CONTROL 0xC7
#define SSD1351_SET_MUX_RATIO 0xCA
#define SSD1351_SET_COMMAND_LOCK 0xFD

#endif // SSD1351_H

I really have the impression that some empty pixel drawing routine is picked over mine; yet when I comment it out, the linker complains about it being missing.

Link to comment
Share on other sites

Regarding those static inline functions:

I could replace the "normal" (non-static, non-inline) function declarations such as

void select(); // defined in board_ssd1351.cpp

with wrappers:

void board_select(); // defined in board_ssd1351.cpp
static inline void select()
{
board_select();
}

but I really can't imagine how on earth can that have a positive effect on the outcome. Testing this with all functions rewritten as above also shows no difference at all.

Link to comment
Share on other sites

Added streaming functions to gdisp_lld_ssd1351.c, but that didn't help:

#if GDISP_HARDWARE_STREAM_WRITE
LLDSPEC void gdisp_lld_write_start(GDisplay* g)
{
coord_t x = g->p.x;
coord_t y = g->p.y;
coord_t sx = g->p.cx;
coord_t sy = g->p.cy;

acquire_bus(g);

write_cmd(g, SSD1351_SET_COLUMN_ADDRESS);
write_data(g, x);
write_data(g, x + sx - 1);

write_cmd(g, SSD1351_SET_ROW_ADDRESS);
write_data(g, y);
write_data(g, y - sy - 1);

write_cmd(g, SSD1351_WRITE_RAM);
}

LLDSPEC void gdisp_lld_write_color(GDisplay* g)
{
color_t c = g->p.color;
write_data(g, c >> 8);
write_data(g, c & 0xFF);
}

LLDSPEC void gdisp_lld_write_stop(GDisplay* g)
{
release_bus(g);
}
#endif // GDISP_HARDWARE_STREAM_WRITE

Also enabled hardware streaming in gdisp_lld_config.h:

#ifndef GDISP_LLD_CONFIG_H
#define GDISP_LLD_CONFIG_H

#if GFX_USE_GDISP

/*===========================================================================*/
/* Driver hardware support. */
/*===========================================================================*/

#define GDISP_HARDWARE_DRAWPIXEL TRUE
#define GDISP_HARDWARE_STREAM_WRITE TRUE
#define GDISP_LLD_PIXELFORMAT GDISP_PIXELFORMAT_RGB565

#endif /* GFX_USE_GDISP */

#endif // GDISP_LLD_CONFIG_H

Also no change when I remove GDISP_HARDWARE_DRAWPIXEL

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...