mbed Paint for the RA8875 display with based touch screen.

Dependencies:   menu mbed RA8875

mPaint

mbed Paint - a demo for the (480x272) RA8875 Display with touchscreen.

Touch Screen

Having had several of the 4.3" WQVGA displays (that use the RA8875), I created the RA8875 driver library (initially derived derived from the work of others). Absent for some time was support for the touch-screen interface. Support first appeared with the contributions of others, and then a major update (due to a recent acquisition of a touch-screen version). This is now much more fully developed and it is part of the standard library.

Demo the Touch Screen

How to demonstrate the touch screen support? mPaint was created for just that purpose.

Screen Shots

Here's a couple of screen shots - you can capture the "canvas" (left) or the composite image (right). As you see, the composite picks up the menu information, however it remains subdued because of the transparency setting.

/media/uploads/WiredHome/mpaint_screen01.png/media/uploads/WiredHome/mpaint_screen02.png

Program Build Info

I'm sometimes a bit skeptical of the reported metrics (perhaps because most of my mbed applications have Ethernet), but here is the reported information from the build of this program.

mPaint Build Infoblinky Build Info
/media/uploads/WiredHome/mpaint_buildinfo.png/media/uploads/WiredHome/blinky_buildinfo.png
RA8875 Graphics library is the primary user.blinky is almost all "startup code" and standard libs

How does mPaint and the graphics library do this in about 1 kB RAM?

The answer is that the display is used as a "write-only" memory, and it has enough RAM hosted in the RA8875 for two full screens (in the WQVGA mode).

mPaint features

  • RGB color selection using touch, with a visible sample.
  • Pen size selection.
    From a limited set.
  • Tool choices.
    Dot, Line, Joined lines.
  • Save your creation.
    Select File | Save..., and it reads the display memory as it creates the BMP file on the local file system.
    This is rather slow (due to both the display read and the local file system).
  • Sorry, no undo.
    If you don't like what "ink" you put down, you can draw over it (much like other paint programs).

main.cpp

Committer:
WiredHome
Date:
2020-03-29
Revision:
11:fe3e6b51f4ee
Parent:
10:52b0f7ccbc40

File content as of revision 11:fe3e6b51f4ee:

/// mPaint is a simple drawing program, used to explore the touch
/// APIs of the RA8875 display library.
///
/// @note Copyright © 2015 by Smartware Computing, all rights reserved.
///     Individuals may use this application for evaluation or non-commercial
///     purposes. Within this restriction, changes may be made to this application
///     as long as this copyright notice is retained. The user shall make
///     clear that their work is a derived work, and not the original.
///     Users of this application and sources accept this application "as is" and
///     shall hold harmless Smartware Computing, for any undesired results while
///     using this application - whether real or imagined.
///
/// @author David Smart, Smartware Computing
//
//
#include "mbed.h"       // tested with v112
#include "RA8875.h"     // tested with v102
#include "menu.h"       // v3

#define DEBUG "mPaint"
// ...
// INFO("Stuff to show %d", var); // new-line is automatically appended
//
#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
#define INFO(x, ...) std::printf("[INF %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define WARN(x, ...) std::printf("[WRN %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define ERR(x, ...)  std::printf("[ERR %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#else
#define INFO(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#define HexDump(a, b, c)
#endif

// Define this for 800x480 panel, undefine for 480x272
//#define BIG_SCREEN

// Define this for Cap touch panel, undefine for resistive
//#define CAP_TOUCH

#ifdef CAP_TOUCH
RA8875 lcd(p5, p6, p7, p12, NC, p9,p10,p13, "tft"); // SPI:{MOSI,MISO,SCK,/ChipSelect,/reset}, I2C:{SDA,SCL,/IRQ}, name
#else
RA8875 lcd(p5, p6, p7, p12, NC, "tft"); // SPI:{MOSI,MISO,SCK,/ChipSelect,/reset}, name
// LocalFileSystem local("local");      // if not otherwise needed; access to calibration file for resistive touch.
#endif

#define PC_BAUD 460800  // I like the serial communications to be very fast

// // // // // // // // // // // // // // // // // // // // // // // //
// End of Configuration Section
// // // // // // // // // // // // // // // // // // // // // // // //

#ifdef BIG_SCREEN
#define LCD_W 800
#define LCD_H 480
#define LCD_C 8         // color - bits per pixel
#define DEF_RADIUS 50   // default radius of the fingerprint
#define BL_NORM 25      // Backlight Normal setting (0 to 255)
#else
#define LCD_W 480
#define LCD_H 272
#define LCD_C 8         // color - bits per pixel
#define DEF_RADIUS 20   // default radius of the fingerprint
#define BL_NORM 25      // Backlight Normal setting (0 to 255)
#endif

// A monitor port for the SW developer.
Serial pc(USBTX, USBRX);

LocalFileSystem local("local");

// list of tools (dots, lines, joined lines).
typedef enum {
    dot,                            // draw dots at the point
    line,                           // connected line from touch(point) to release(point)
    join                            // connected lines while held(point)
} tooltype_t;                       // what tool are we using to draw

color_t rgb = Black;                // the composite color value to draw in.
uint8_t rgbVal[3] = { 0, 0, 0 };    // hosts each of the individual values
uint32_t pensize = 1;               // pensize is user selectable within a small range.
tooltype_t selectedtooltype = dot;  // 0:dot, 1:line, 2:join
point_t origin = { 0, 0};           // tracks origin when drawing a line

// 0           200          400   500    600   700    800
// |     |      |     |      |     |      |     |      |
// 0         100        200        300        400        500
// |          |          |          |          |          |
// +---------------------------------------------------+ 0
// | File Edit Pen Tools    [smpl](o)  [rrr][ggg][bbb] |
// +---------------------------------------------------+
// |                                                   | 16  32
// |      canvas                                       |
// |                                                   |
// |                                                   |
// |                                                   |
// |                                                   |
// |                                                   |
// |                                                   |
// |                                                   |
// |                                                   |
// +---------------------------------------------------+ 255 463
// | (xxx,yyy) - (xxx,yyy)              rgb (RR,GG,BB) |
// +---------------------------------------------------+ 271 479
// 0                                                  479
// 0                                                  799

// Adjust the following if using the 800x480 v 480x272 display
#if LCD_W == 800
const rect_t RGBList[] = {   // regions on the display for special tools
    { 425,0, 425+50,30 },    // show selected color
    { 500,0, 500+80,30 },    // R
    { 600,0, 600+80,30 },    // G
    { 700,0, 700+80,30 },    // B
};
const rect_t canvas_rect = {        // the drawing surface
    0,32, LCD_W-1,LCD_H-1
};
#else
const rect_t RGBList[] = {   // regions on the display for special tools
    { 249,0, 249+50,15 },    // show selected color
    { 309,0, 309+50,15 },    // R
    { 369,0, 369+50,15 },    // G
    { 429,0, 429+50,15 },    // B
};
const rect_t canvas_rect = {        // the drawing surface
    0,16, LCD_W-1,LCD_H-1
};
#endif


// File             Pen            Tools
//  New...           Pensize 1      Dot
//  Save...          Pensize 2      Line
//  Calibrate        Pensize 4
//  Reset            Pensize 6
//                   Pensize 8
//
Menu::post_fnc_action_t File(uint32_t v);
Menu::post_fnc_action_t File_New(uint32_t v);
Menu::post_fnc_action_t File_Save(uint32_t v);
Menu::post_fnc_action_t File_Save_All(uint32_t v);
Menu::post_fnc_action_t File_Cal(uint32_t v);
Menu::post_fnc_action_t File_Reset(uint32_t v);
Menu::post_fnc_action_t Edit(uint32_t v);
Menu::post_fnc_action_t Edit_Clear(uint32_t v);
Menu::post_fnc_action_t Tools(uint32_t v);
Menu::post_fnc_action_t Tools_Type(uint32_t v);
Menu::post_fnc_action_t PenSize(uint32_t v);
Menu::post_fnc_action_t HideMenu(uint32_t v);

// Some APIs
extern "C" void mbed_reset();
int GetScreenCapture(void);
void CalibrateTS(void);
void ShowSampleRGB(void);
void InitDisplay(void);

Menu::menu_item_t file_menu[] = {
    // Prompt, onPress, onHold, OnRelease, parameter, child menu
    { "New...", File_New, NULL, NULL, 0, NULL },
    { "Save...", File_Save, NULL, NULL, 0, NULL },
    { "Save all", File_Save_All, NULL, NULL, 0, NULL },
    { "Calibrate", File_Cal, NULL, NULL, 0, NULL },
    { "Reset", NULL, NULL, File_Reset, 0, NULL },
    { NULL, NULL, NULL, NULL, 0, NULL },
};

Menu::menu_item_t pen_menu[] = {
    { "1 pix",  NULL, NULL, PenSize, 1, NULL },
    { "2 pix",  NULL, NULL, PenSize, 2, NULL },
    { "4 pix",  NULL, NULL, PenSize, 4, NULL },
    { "6 pix",  NULL, NULL, PenSize, 6, NULL },
    { "8 pix",  NULL, NULL, PenSize, 8, NULL },
    { NULL, NULL, NULL, NULL, 0, NULL },
};

Menu::menu_item_t tools_menu[] = {
    { "point", NULL, NULL, Tools_Type, dot, NULL },
    { "line",  NULL, NULL, Tools_Type, line, NULL },
    { "join",  NULL, NULL, Tools_Type, join, NULL },
    { NULL, NULL, NULL, NULL, 0, NULL },
};

Menu::menu_item_t menudata[] = {
    { "File", File, NULL, NULL, 0, file_menu },
    { "Pen",  NULL, NULL, NULL, 0, pen_menu },
    { "Tools", NULL, NULL, NULL, 0, tools_menu },
    { "Hide", NULL, NULL, HideMenu, 0, NULL },
    { NULL, NULL, NULL, NULL, 0, NULL },
};

Menu menu(lcd, menudata);

Menu::post_fnc_action_t File(uint32_t v)
{
    (void)v;
    INFO("File");
    return Menu::no_action;
}
Menu::post_fnc_action_t File_New(uint32_t v)
{
    (void)v;
    INFO("File_New");
    InitDisplay();
    return Menu::no_action;
}
Menu::post_fnc_action_t File_Save(uint32_t v)
{
    (void)v;
    INFO("File_Save");
    RA8875::LayerMode_T l = lcd.GetLayerMode();
    lcd.SetLayerMode(RA8875::ShowLayer0);
    GetScreenCapture();
    lcd.SetLayerMode(RA8875::TransparentMode);
    return Menu::close_menu;
}
Menu::post_fnc_action_t File_Save_All(uint32_t v)
{
    (void)v;
    INFO("File_Save_All");
    GetScreenCapture();
    return Menu::close_menu;
}
Menu::post_fnc_action_t File_Cal(uint32_t v)
{
    (void)v;
    INFO("Tools_Cal");
    CalibrateTS();
    return Menu::no_action;
}
Menu::post_fnc_action_t File_Reset(uint32_t v)
{
    (void)v;
    INFO("rebooting now...");
    wait_ms(1000);
    mbed_reset();
    return Menu::no_action;
}
Menu::post_fnc_action_t Edit(uint32_t v)
{
    (void)v;
    INFO("Edit");
    return Menu::no_action;
}
Menu::post_fnc_action_t Tools(uint32_t v)
{
    (void)v;
    INFO("Tools");
    return Menu::no_action;
}
Menu::post_fnc_action_t PenSize(uint32_t v)
{
    pensize = v;
    if (pensize < 1)
        pensize = 1;
    else if (pensize > 8)
        pensize = 8;
    INFO("PenSize(%d)", pensize);
    ShowSampleRGB();
    return Menu::close_menu;
}
Menu::post_fnc_action_t Tools_Type(uint32_t v)
{
    switch (v) {
        case dot:
        case line:
        case join:
            selectedtooltype = (tooltype_t)v;
            break;
        default:
            break;
    }
    ShowSampleRGB();
    return Menu::close_menu;
}
Menu::post_fnc_action_t HideMenu(uint32_t v)
{
    (void)v;
    return Menu::close_menu;
}

void ShowSampleRGB(void)
{
    loc_t middle = (RGBList[0].p1.y + RGBList[0].p2.y)/2;
    lcd.fillrect(RGBList[0], Black);
    if (selectedtooltype == dot) {
        lcd.fillcircle((RGBList[0].p1.x + RGBList[0].p2.x)/2,
                       middle, pensize, rgb);
    } else {
        lcd.fillrect(RGBList[0], rgb);
    }
}

void CalibrateTS(void)
{
    FILE * fh;
    tpMatrix_t matrix;
    RetCode_t r;

    r = lcd.TouchPanelCalibrate("Calibrate the touch panel", &matrix);
    INFO("  ret: %d", r);
    if (r == noerror) {
        fh = fopen("/local/tpcal.cfg", "wb");
        if (fh) {
            fwrite(&matrix, sizeof(tpMatrix_t), 1, fh);
            fclose(fh);
            INFO("  tp cal written.");
        } else {
            WARN("  couldn't open tpcal file.");
        }
    } else {
        ERR("error return: %d", r);
    }
}


void InitTS(void)
{
    FILE * fh;
    tpMatrix_t matrix;

    fh = fopen("/local/tpcal.cfg", "rb");
    if (fh) {
        fread(&matrix, sizeof(tpMatrix_t), 1, fh);
        fclose(fh);
        lcd.TouchPanelSetMatrix(&matrix);
        INFO("  tp cal loaded.");
    } else {
        CalibrateTS();
    }
}


void InitDisplay(void)
{
    lcd.SelectDrawingLayer(CANVAS);
    lcd.cls();
    lcd.foreground(Blue);
    lcd.background(Black);
}

int GetScreenCapture(void)
{
    char fqfn[50];
    int i = 0;

    INFO("Screen Capture... ");
    for (i=1; i< 100; i++) {
        snprintf(fqfn, sizeof(fqfn), "/local/Screen%02d.bmp", i);
        FILE * fh = fopen(fqfn, "rb");
        if (!fh) {
            INFO("Saving as %s", fqfn);
            lcd.PrintScreen(0,0,lcd.width(),lcd.height(),fqfn);
            INFO(" as /local/Screen%02d.bmp", i);
            return i;
        } else {
            fclose(fh);     // close this and try the next
        }
    }
    return 0;
}

void ShowRGBSelectors(void)
{
    uint16_t curLayer = lcd.GetDrawingLayer();
    lcd.SelectDrawingLayer(MENUS);
    lcd.fillrect(RGBList[1], Red);
    lcd.fillrect(RGBList[2], Green);
    lcd.fillrect(RGBList[3], Blue);
    lcd.SelectDrawingLayer(curLayer);
}

bool SeeIfUserSelectingRGBValues(point_t p, TouchCode_t touchcode)
{
    static bool wasIn = false;

    // See if the touch is setting new RGB values
    for (int i=1; i<=3; i++) {
        if (lcd.Intersect(RGBList[i], p)) {
            uint8_t mag = (255 * (p.x - RGBList[i].p1.x)) / (RGBList[i].p2.x - RGBList[i].p1.x);
            wasIn = true;
            if (touchcode == touch)
                menu.Show();
            else if (touchcode == release)
                menu.Hide();
            rgbVal[i-1] = mag;
            // update the RGB values
            lcd.SelectDrawingLayer(MENUS);
            lcd.SetTextCursor(lcd.width() - 10*lcd.fontwidth(), lcd.height() - lcd.fontheight());
            lcd.foreground(Blue);
            lcd.printf("(%02X,%02X,%02X)", rgbVal[0], rgbVal[1], rgbVal[2]);
            // show sample
            rgb = RGB(rgbVal[0], rgbVal[1], rgbVal[2]);
            ShowSampleRGB();
            //
            lcd.SelectDrawingLayer(CANVAS);
            lcd.foreground(rgb);
            return true;
        }
    }
    if (wasIn)
        menu.Hide();
    return false;
}

int sgn(int x)
{
    if (sgn < 0)
        return -1;
    else if (sgn > 0)
        return 1;
    else
        return 0;
}



void SeeIfUserDrawingOnCanvas(point_t p, TouchCode_t touchcode)
{
    if (lcd.Intersect(canvas_rect, p)) {
        switch (selectedtooltype) {
            case dot:
                lcd.fillcircle(p, (pensize == 1) ? pensize : pensize/2, rgb);
                break;
            case line:
                if (touchcode == touch) {
                    lcd.fillcircle(p, 1, rgb);
                    origin = p;
                    INFO("Origin @ (%3d,%3d)", p.x, p.y);
                } else if (touchcode == release) {
                    lcd.ThickLine(origin, p, pensize, rgb);
                }
                break;
            case join:
                if (touchcode == touch) {
                    lcd.fillcircle(p, 1, rgb);
                    origin = p;
                    INFO("Origin @ (%3d,%3d)", p.x, p.y);
                } else if (touchcode == release) {
                    lcd.ThickLine(origin, p, pensize, rgb);
                } else if (touchcode == held) {
                    lcd.ThickLine(origin, p, pensize, rgb);
                    origin = p;
                    INFO("  held @ (%3d,%3d)", p.x, p.y);
                }
                break;
            default:
                break;
        }
    }
}


int main()
{
    pc.baud(460800);    // I like a snappy terminal, so crank it up!
    pc.printf("\r\nRA8875 Menu - Build " __DATE__ " " __TIME__ "\r\n");

    INFO("Turning on display");
    lcd.init(LCD_W,LCD_H,LCD_C);
    lcd.TouchPanelInit();
    lcd.frequency(10000000);
    lcd.SetTextFontSize(2);
#ifndef CAP_TOUCH
    InitTS();               // resistive touch calibration
#endif
    InitDisplay();
    menu.init();
    ShowRGBSelectors();

    INFO("processing loop...");

    for (;;) {
        point_t p;
        TouchCode_t touchcode = lcd.TouchPanelReadable(&p);

        if (touchcode != no_touch) {
            //int curLayer = lcd.GetDrawingLayer();
            // This is nice feedback, but certainly slows the drawing.
            //lcd.SelectDrawingLayer(MENUS);
            //lcd.foreground(Blue);
            //lcd.SetTextCursor(0, lcd.height() - 16);
            //lcd.printf("(%3d,%3d) - (%3d,%3d)", origin.x, origin.y, p.x, p.y);
            //lcd.SelectDrawingLayer(curLayer);

            bool menuHandledIt = menu.HandledTouch(p, touchcode);
            if (menuHandledIt) {
                // menu handled it
            } else if (SeeIfUserSelectingRGBValues(p, touchcode)) {
                // that handled it.
            } else {
                SeeIfUserDrawingOnCanvas(p, touchcode);
            }
        } else {
            //non-touch
        }
    }
}

#include <stdarg.h>
//Custom override for error()
void error(const char* format, ...)
{
    char sprintf_buffer[128];

    va_list arg;
    va_start(arg, format);
    vsprintf(sprintf_buffer, format, arg);
    va_end(arg);

    fprintf(stderr, "SW err: %s", sprintf_buffer);
}

//            Reset_Handler
//            NMI_Handler
//            HardFault_Handler
//            MemManage_Handler
//            BusFault_Handler
//            UsageFault_Handler
extern "C" void HardFault_Handler()
{
    printf("\r\n\r\nHard Fault!\r\n");
    wait_ms(500);
    NVIC_SystemReset();
}
extern "C" void NMI_Handler()
{
    printf("\r\n\r\nNMI Fault!\r\n");
    wait_ms(500);
    NVIC_SystemReset();
}
extern "C" void MemManage_Handler()
{
    printf("\r\n\r\nMemManage Fault!\r\n");
    wait_ms(500);
    NVIC_SystemReset();
}
extern "C" void BusFault_Handler()
{
    printf("\r\n\r\nBusFault Fault!\r\n");
    wait_ms(500);
    NVIC_SystemReset();
}
extern "C" void UsageFault_Handler()
{
    printf("\r\n\r\nUsageFault Fault!\r\n");
    wait_ms(500);
    NVIC_SystemReset();
}