API guide¶
How to do the common jobs with the driver: initialise, draw, use the controller's drawing commands, set brightness, read the switch and handle errors.
Every snippet on this page is compiled by the host CI from
tests/docs/api_tasks.cpp, with Display standing for
isf15acp4::SmartDisplay<MySpi> and colors for isf15acp4::colors. The API reference
lists every member.
The shape of the API¶
One SmartDisplay<Port> object per button. You draw into a GraphicsContext and hand it to the display; or you
call the display's own drawing and window functions, which talk to the controller directly.
| Task | Calls |
|---|---|
| Start | Initialize(), Reset(), IsInitialized() |
| Draw a frame | CreateGraphicsContext(), then GraphicsContext drawing, then Present() |
| Draw on the controller | DrawLine(), DrawRectangle(), ClearWindow(), DrawPixel(), FillScreen() |
| Write pixels yourself | SetWindow(), SetFullWindow(), WritePixels(), DrawCanvas() |
| Brightness and power | DisplayOn(), DisplayOff(), DisplayDim(), SetMasterCurrent() |
| Scroll | ConfigureScroll() |
| Switch | IsPressed(), and ButtonUI for events |
| Errors | GetLastError(), GetErrorFlags(), HasError(), ClearErrorFlags() |
Every call that talks to the display returns bool and never throws. Check it: false means a parameter was out
of range or the port reported a failure, and an Error flag says which.
Initialise¶
Pick the product variant and graphics backend in SmartDisplayConfig, then call Initialize():
const isf15acp4::SmartDisplayConfig config{
.variant = isf15acp4::ProductVariant::Isf15acp4,
.graphics = isf15acp4::GraphicsBackend::BuiltinCanvas,
.enable_vcc_on_init = true,
};
isf15acp4::SmartDisplay<MySpi> display(&spi, config);
return display.Initialize(config.enable_vcc_on_init);
Initialize() brings the port up, pulses RES, enables VCC (unless you pass false), sends the variant's
initialisation table (which ends with display on) and selects the full window. Reset() runs the same sequence
again, for example after a supply brown-out. Configuration lists every field.
Draw a frame¶
auto gfx = display.CreateGraphicsContext(); // holds a 12,288-byte frame: keep it alive
gfx.Clear(colors::Black);
gfx.FillRect({0, 0, 95, 10}, colors::Blue);
gfx.DrawString(2, 2, "SPINDLE", colors::White, colors::Blue);
char text[16];
std::snprintf(text, sizeof(text), "%lu RPM", static_cast<unsigned long>(rpm));
gfx.DrawString(2, 24, text, colors::Cyan);
return display.Present(gfx); // full-window upload
Drawing into the context changes RAM on your MCU only; Present() sends the frame as one 12,288-byte data
transaction. At 6 MHz that is about 16 ms of SPI time, so a full frame costs at most about 60 frames a second
before your drawing time. Text uses the 5 x 7 font, 6 pixels per character: 16 characters across.
Graphics covers the backends, colours and images.
Draw with the controller¶
The SSD1331 draws lines and rectangles itself from a few command bytes, which is much less SPI traffic than a frame:
if (!display.ClearWindow({0, 0, 95, 63})) { // 25h
return false;
}
if (!display.DrawRectangle({10, 10, 85, 53}, colors::Orange, true)) { // 26h fill on, 22h, fill off
return false;
}
return display.DrawLine({0, 63}, {95, 0}, colors::White); // 21h
| Call | Command | Bytes |
|---|---|---|
DrawLine() |
21h |
8 |
DrawRectangle() |
22h |
11 |
| filled | 26h, 22h, 26h |
17 |
ClearWindow() |
25h |
5 |
DrawPixel() |
15h, 75h, data |
8 |
FillScreen() |
A full frame | 12,294 |
These draw straight into the display RAM. A later Present() overwrites them with the canvas contents.
Write pixels yourself¶
// An 8 x 8 red square at (40, 28): window, then 64 pixels.
isf15acp4::Color565 block[8 * 8];
for (auto& px : block) {
px = colors::Red;
}
if (!display.SetWindow({40, 28, 47, 35})) { // 15h / 75h, inclusive bounds
return false;
}
if (!display.WriteColors(block, 8 * 8)) { // high byte first on any CPU
return false;
}
return display.SetFullWindow(); // back to 96 x 64 for Present()
SetWindow() takes inclusive bounds in display coordinates and returns false with Error::OutOfRange outside
the visible area (0..95, 0..63 on the ISF15ACP4); on the smaller variants the driver adds the RAM column offset.
WriteColors() needs Initialize() first and sends each Color565 pixel high byte first, the SSD1331's
65k-colour order. WritePixels() sends raw bytes unchanged, two per pixel already in that format, and needs an even
byte count. Restore the full window before the next Present(); DrawCanvas() and Present()
set it themselves.
Brightness and power¶
if (!display.DisplayOff()) { // AEh: sleep, GRAM kept
return false;
}
if (!display.SetMasterCurrent(0x08)) { // 87h: 0x00 (dimmest) .. 0x0F (brightest)
return false;
}
if (!display.DisplayOn()) { // AFh: normal display
return false;
}
return display.DisplayDim(); // ACh: dim mode, for an idle screen
| Call | Command | Effect |
|---|---|---|
DisplayOn() |
AFh |
Normal display |
DisplayOff() |
AEh |
Sleep; the RAM keeps its contents |
DisplayDim() |
ACh |
Dim mode, levels from the init table |
SetMasterCurrent() |
87h |
Drive current scale, 0 to 15 |
The master current scales all three colours from 0 (dimmest) to 15 (brightest, the reset value).
Lower brightness also slows the OLED's ageing. SetMasterCurrent() rejects values above 0x0F with
Error::OutOfRange.
Scroll¶
// Horizontal scroll of all 64 rows by 1 column, no vertical offset, fastest interval (code 0).
if (!display.ConfigureScroll(1, 0, 64, 0, 0, true)) { // 2Eh, 27h, then 2Fh
return false;
}
// ... later: stop, and rewrite the display RAM from the frame.
return display.StopScroll(gfx); // 2Eh, then Present(gfx)
ConfigureScroll(h_offset, start_row, rows, v_offset, interval, activate) stops any running scroll (2Eh), sends
the SSD1331 scroll setup (27h) and, with activate, starts it (2Fh): the datasheet forbids changing the
parameters while scrolling runs. The interval code is 0 to 3: a step every 6, 10, 100 or 200 frames.
Stopping leaves the display RAM shifted, and the datasheet says to rewrite it: StopScroll(gfx) sends 2Eh and
then Present(gfx). StopScroll() without a context only sends 2Eh; redraw yourself afterwards.
Read the switch¶
IsPressed() returns your port's GpioRead(CtrlPin::Switch): the raw contact, with bounce. ButtonUI turns it
into events:
// Call every 10-20 ms from your loop; elapsed_ms is the time since the previous call.
switch (ui.Update(display.IsPressed(), elapsed_ms)) {
case isf15acp4::ButtonEvent::Pressed: std::printf("down, press #%lu\n",
static_cast<unsigned long>(ui.PressCount())); break;
case isf15acp4::ButtonEvent::Click: std::printf("click\n"); break;
case isf15acp4::ButtonEvent::LongPress: std::printf("held 800 ms\n"); break;
case isf15acp4::ButtonEvent::Released: std::printf("released after a long press\n"); break;
case isf15acp4::ButtonEvent::None: break;
}
| Event | When |
|---|---|
Pressed |
The contact closed (counted in PressCount()) |
Click |
Released before the long-press time |
LongPress |
Held for the long-press time (default 800 ms); reported once per press |
Released |
Released after a long press |
ButtonUI(debounce_ms, long_press_ms) defaults to 25 ms and 800 ms. It samples the contact once per debounce
period, so call Update() more often than that.
Handle errors¶
if (!display.Initialize()) {
switch (display.GetLastError()) {
case isf15acp4::Error::NotInitialized: std::printf("port: EnsureInitialized() failed\n"); break;
case isf15acp4::Error::SpiWrite: std::printf("port: WriteBytes() failed\n"); break;
case isf15acp4::Error::InvalidParam: std::printf("no port (null bus pointer)\n"); break;
default: std::printf("error 0x%04x\n", display.GetErrorFlags()); break;
}
}
if (display.HasError(isf15acp4::Error::OutOfRange)) { // flags accumulate until cleared
display.ClearErrorFlags(static_cast<uint16_t>(isf15acp4::Error::OutOfRange));
}
std::printf("driver %s\n", Display::GetDriverVersion());
Error:: |
Set when |
|---|---|
SpiWrite |
Your Begin*() or WriteBytes() returned false |
NotInitialized |
EnsureInitialized() failed, or pixels before Initialize() |
InvalidParam |
No port, an odd pixel byte count, a wrong-size canvas |
OutOfRange |
A window off the panel, a master current above 15 |
GetLastError() is the most recent error; GetErrorFlags() is every error since the last ClearErrorFlags().
Error::GpioControl and Error::PowerSequence exist in the enum but nothing sets them yet. The display sends
nothing back, so the driver cannot tell a missing or unpowered module from a working one:
Troubleshooting starts there.