Author: P. Mather
As promised here is the last of the CFunction tutorials. This time the objective is to write a loadable display driver. Many months ago, the first displays I looked at were the monochrome SSD1306 OLED displays and the Nokia 5110 LCD displays.
These have been somewhat superseded by the huge choice of colour TFT displays but they still offer an option where the requirement is a small but visible and cheap display technology that can be connected using SPI. This tutorial will develop a fully worked loadable driver for the SSD1306 OLED display.
The Micromite firmware from V4.7 provides hooks to allow loadable drivers to be written for displays in C that can then be accessed by the standard Basic drawing commands: TEXT, BOX, CIRCLE etc.
There are three routines that need to be written as part of a CFunction display driver:
First, there must be a “main” program that initialises the display and hooks the drawing routines into the Micromite firmware.
Second, there needs to be a drawing routine to draw a filled rectangular box on the display.
Finally, there needs to be a drawing routine to write a two colour bitmap to the display.
It is that simple
Everything else is created in the Micromite firmware using just the two drawing routines. So a single pixel is a rectangle with length and height both set to 1.
A horizontal or vertical line is a rectangle with one dimension set to 1 and the other set to the length of the line.
A diagonal line is a series of pixels.
A circle outline is also a series of pixels
A filled circle is a set of filled rectangles calculated to fill out the circular area of the screen.
A text character is a bitmap
etc.
The calling sequence for the two drawing routines is completely defined and must be followed by any loadable driver:
DrawRectangle(int x1, int y1, int x2, int y2, int c)
draws a rectangle where the top left hand corner is at x1,y1 and the bottom right hand corner is at x2,y2 using the colour provided in c.
DrawBitmap(int x, int y, int width, int height, int scale, int fc, int bc, unsigned char bitmap[]){
draws a rectangle where the top left hand corner is at x,y. It takes the information contained in the array “bitmap” which is “width” bits wide and “height” bits tall and where a 1 in the bitmap means use the colour fc and a 0 means use the colour bc. The main use is of course for text. The scale parameter says for each bit in the bitmap create an area on the display scale * scale in the specified colour.
Note that the display driver is the only bit of code that needs to understand the workings of the particular display. The Micromite firmware just needs to know that it can call DrawRectangle and/or DrawBitmap and magically things will appear on the display. We will provide the Micromite firmware with information about the orientation of the display and its height and width so that it can integrate touch capability but other than that the driver is the only code that manages the display and the hardware interface to it.
So given the above we can look at how to implement a driver for the SSD1306.
Colour displays use multiple bits of information to describe the colour of each pixel. However, in general monochrome displays use one bit per pixel and so typically a transfer of one byte of information to the display will update 8 pixels. This means if I want to change a single pixel I need to know the current value of the 7 other pixels which are linked in the same byte of input. I could do this by reading the display memory but in many cases of SPI displays they do not provide read capability so this is impossible. This means I need to keep a memory map of the current state of the display in my microprocessor memory.
In the case of the SSD1306 display it is 128 pixels wide and 64 pixels high so I need to maintain a memory map of 1024 bytes (128*64/8) to track the current state of the display.
The SSD1306 (in common with many monochrome display drivers) organises the display as 128*8 bytes so byte zero relates to pixels 0,0 0,1 0,2 0,3 0,4 0,5 0,6 0,7 i.e. a line 8-pixels long running down the screen starting at the top left hand corner (0,0).
It supports a mechanism for positioning the writing point (0-127,0-7) to define which set of 8 pixels will be updated.
To get the SSD1306 to display anything at all it needs some configuration data written to it. All display controllers need initialisation and they are all different. Google and the controller datasheet are your friend in sorting out the requirements for a specific controller. In the case of the SSD1306 it is quite straightforward particularly compared to some of the colour display controllers.
So to summarise this introduction:
We need to write three routines for the SSD1306: initialisation, drawrectangle, drawbitmap.
We need to handle the hardware mechanics of talking to the display (SPI)
We need to create and maintain some persistent memory to use as a memory map of the display.
Lets get started on the driver. First we need to set up some information at the top of the CFunction that will be used by the code:
/*******************************************************************************
*
* Driver for SSD1306 Display written as CFunctions
*
* Author: Peter Mather 2015 with acknowledgements to Peter Carnegie & Geoff Graham
*
*
* This CFunction MUST be compiled with Optimization Level 1, -O1
* -O2,-O3,-Os will compile successfully, but generate exceptions at runtime.
*
* When Generating the CFunction, use MERGE CFunction mode, and name the CFunction
* SSD1306
*
* Entry point is function long long main(long long *MyAddress,
* long long *DC,
* long long *RST
* long long *CS
* long long *size //zero for 0.96", non-zero for 1.3"
* long long *orientation)
*
* V1.0 2015-10-15 Peter Mather
*
******************************************************************************/
#define Version 100 //Version 1.00
#define _SUPPRESS_PLIB_WARNING // required for XC1.33 Later compiler versions will need PLIB to be installed
#include <plib.h> // the pre Harmony peripheral libraries
#include "../cfunctions.h"
#define LANDSCAPE 1
#define PORTRAIT 2
#define RLANDSCAPE 3
#define RPORTRAIT 4
#define screenwidth 128
#define screenheight 64
#define screenrows screenheight/8
#define update screenwidth*screenrows //location for status to force screen write
#define displaysize update+1 //location for screen size
// SPI pin numbers and registers
#define SPI_INP_PIN (HAS_44PINS ? 41 : 14)
#define SPI_OUT_PIN (HAS_44PINS ? 20 : 3)
#define SPI_CLK_PIN (HAS_44PINS ? 14 : 25)
#define SPI_PPS_OPEN PPSInput(2, SDI1, RPB5); PPSOutput(2, RPA1, SDO1)
#define SPI_PPS_CLOSE PPSOutput(2, RPA1, NULL)
#define SPICON *(volatile unsigned int *)(0xbf805800) //SPI config 1 register
#define SPISTAT *(volatile unsigned int *)(0xbf805810) //SPI status register
#define SPIBUF *(volatile unsigned int *)(0xbf805820) //SPI I/O buffer
#define SPIBRG *(volatile unsigned int *)(0xbf805830) //SPI clock register
#define SPICON2 *(volatile unsigned int *)(0xbf805840) //SPI config 2 register
#define SPIsend(a) {int j;SPIBUF=a; while((SPISTAT & 0x80)==0); j=SPIBUF;}
We tell the compiler that we want access to Microchips peripheral library “plib” and that we also want to access the Micromite internal routines as defined in “cfunctions.h”.
Then we set up some “defines”. These are simply string substitutions that can be used to make the code more readable. So when I use the value “screenwidth” the compiler will automatically substitute 128.
This version of the CFunction will only run on the MX170 but we need to know if it is running on a 44-pin chip or 28-pin one. This is needed because the pin numbers of the SPI pins are different on each.
HAS_44PINS is defined in cfunctions.h and reads the chips ID code to work out which chip is in use. This allows us to set SPI_INP_PIN etc. to the correct pin number for the part.
We then define the SPI registers that will be used by the code:
“define SPIBUF *(volatile unsigned int *)(0xbf805820)”
This says that when we use SPIBUF in our code we will actually be talking directly to memory location &HBF805820 which is the location in the PIC’s memory map of the I/O buffer for SPI read/write. The “volatile” syntax is used to tell the compiler that the value may change without being changed in the code itself and therefore optimisations must be done without any assumptions of the value.
Finally we define the macro SPIsend. Again the string is just substituted by the compiler but in this case it is a block of code that writes to the SPI I/O register, waits for the write to complete, and then reads the buffer to clear it. This is our main way of outputting to the display.
Having set up these definitions we can start to look at the main program.
//CFunction Driver_SSD1306
long long main(long long *MyAddress, long long *CD, long long *RST, long long *CS,long long *size, long long *orientation){
int i,DrawRectangleVectorOffset,DrawBitmapVectorOffset,updatedisplayVectorOffset,HorizontalRes=screenwidth,VerticalRes=scree nheight;
unsigned int consave=0,brgsave=0,con2save;
Option->LCD_Reset=*RST;
Option->LCD_CD=*CD;
Option->LCD_CS=*CS;
Option->DISPLAY_ORIENTATION=*orientation;
//Get some persistent memory
unsigned char * p = GetMemory(screenwidth*screenrows+256);
//Save the address for other CFunctions
StartOfCFuncRam=(unsigned int)(p);
p[displaysize]=*size;
ExtCfg(Option->LCD_Reset,EXT_DIG_OUT,0);ExtCfg(Option->LCD_Reset,EXT_BOOT_RESERVED,0);
PinSetBit(Option->LCD_Reset, LATSET);
ExtCfg(Option->LCD_CD,EXT_DIG_OUT,0);ExtCfg(Option->LCD_CD,EXT_BOOT_RESERVED,0);
PinSetBit(Option->LCD_CD, LATSET);
ExtCfg(Option->LCD_CS,EXT_DIG_OUT,0);ExtCfg(Option->LCD_CS,EXT_BOOT_RESERVED,0);
PinSetBit(Option->LCD_CS, LATSET);
if(ExtCurrentConfig[SPI_OUT_PIN] == EXT_RESERVED) { //already open
brgsave=SPIBRG;
consave=SPICON;
con2save=SPICON2;
}
ExtCfg(SPI_OUT_PIN, EXT_DIG_OUT, 0); ExtCfg(SPI_OUT_PIN, EXT_BOOT_RESERVED, 0);
ExtCfg(SPI_INP_PIN, EXT_DIG_IN, 0); ExtCfg(SPI_INP_PIN, EXT_BOOT_RESERVED, 0);
ExtCfg(SPI_CLK_PIN, EXT_DIG_OUT, 0); ExtCfg(SPI_CLK_PIN, EXT_BOOT_RESERVED, 0);
SPI_PPS_OPEN;
SPICON=0x8060;
SPIBRG=2;
SPICON2=0xC00;// this is defined in IOPorts.h
if(!brgsave){ //save my settings
brgsave=SPIBRG;
consave=SPICON;
con2save=SPICON2;
}
The function call “main” follows the pattern from all the other examples we have looked at. It takes in an address which is used to define its own position in memory in a specific use, the pin numbers of the three control pins on the SSD1306: command/data, reset, chip select, a size code for the display which is used to correct for the different wiring of the 0.96″ and 1.3″ variants, and the orientation of the display: landscape, portrait, reverse landscape, reverse portrait.
We then define some variables we will use in the “main” routine.
Next we store the main parameters in special locations Geoff has provided specifically for display use .e.g. “Option->LCD_Reset=*RST;”
The Option structure is defined in cfunctions.h
We then allocate some memory we can use for the display’s memory map using GetMemory.
This is permanently allocated so will still be there after the “main” function terminates. We save the address of the memory in “StartOfCFuncRam” so it can be accessed by the actual drawing routines. We defined the pointer to the memory as a pointer to a character array so we can access the memory as a simple array of characters.
“p[displaysize]=*size;” saves the size code of our display in this array after the memory map.
Next we set up the I/O pins in the same way we saw in the previous tutorials however in this case we additionally give them a status of EXT_BOOT_RESERVED. This protects them from being de-allocated by a “NEW” command or an “EDIT” allowing the graphics to be used at all times including from the command line. The function “main” will be called from Basic by MM.STARTUP so that the display is always initialised after a reset or power-up.
It is possible that a user program is also using the SPI port. We can check this by looking for the status “EXT_RESERVED” on one of the I/O pins.
“if(ExtCurrentConfig[SPI_OUT_PIN] == EXT_RESERVED)”
If this is case we then save the user configuration so that it can be restored before we exit.
We can then set up the SPI port in the configuration we need for the SSD1306 and if the port wasn’t in use store our own settings: “brgsave=SPIBRG;”
//Calculate the address vectors
if ((unsigned int)&DrawRectangleMEM < (unsigned int)&main){
DrawRectangleVectorOffset=*MyAddress - ((unsigned int)&main - (unsigned int)&DrawRectangleMEM);
}else{
DrawRectangleVectorOffset=*MyAddress + ((unsigned int)&DrawRectangleMEM - (unsigned int)&main);
}
if ((unsigned int)&DrawBitmapMEM < (unsigned int)&main){
DrawBitmapVectorOffset=*MyAddress - ((unsigned int)&main - (unsigned int)&DrawBitmapMEM);
}else{
DrawBitmapVectorOffset=*MyAddress + ((unsigned int)&DrawBitmapMEM - (unsigned int)&main);
}
if ((unsigned int)&updatedisplay < (unsigned int)&main){
updatedisplayVectorOffset=*MyAddress - ((unsigned int)&main - (unsigned int)&updatedisplay);
}else{
updatedisplayVectorOffset=*MyAddress + ((unsigned int)&updatedisplay - (unsigned int)&main);
}
The next part of the code is exactly the same as we saw in tutorial 2. We calculate the location in memory of the routines that will be called by the Micromite firmware.
We have talked about “drawrectangle” and “drawbitmap” above. “updatedisplay” uses another feature of the Micromite firmware. This is a routine which will be called at the end of every Basic statement in the same routine Basic uses to check for Ctrl-C and for interrupts. We will use it to do the actual physical updating of the display rather than doing it in the draw routines. This optimises screen update as follows:
Drawing a filled circle will call drawrectangle multiple times. By putting the screen update after completion of the CIRCLE Basic statement rather than after each drawrectangle I/O to the screen is minimised. Remember there are 8 pixels represented by each byte so we want to minimise the chance of writing the same byte multiple times by waiting until the complete circle is drawn.
Next we are going to initialise the screen itself so a helper function is useful
void spi_write_command(unsigned char data){
PinSetBit(Option->LCD_CD, LATCLR);
PinSetBit(Option->LCD_CS, LATCLR);
SPIsend(data);
PinSetBit(Option->LCD_CS, LATSET);
}
spi_write_command sets the display to receive a command byte by setting CD low. It then sets chip-select low and outputs the command byte before returning chip-select high.
Using this we can then send the commands needed to initialise the screen:
//Reset the SSD1963
PinSetBit(Option->LCD_Reset,LATSET);
uSec(10000);
PinSetBit(Option->LCD_Reset,LATCLR);
uSec(10000);
PinSetBit(Option->LCD_Reset,LATSET);
uSec(10000);
spi_write_command(0xAE); //DISPLAYOFF)
spi_write_command(0xD5); //DISPLAYCLOCKDIV
spi_write_command(0x80); //the suggested ratio 0x80
spi_write_command(0xA8); //MULTIPLEX
spi_write_command(0x3F); //
spi_write_command(0xD3); //DISPLAYOFFSET
spi_write_command(0x0); //no offset
spi_write_command(0x40); //STARTLINE
spi_write_command(0x8D); //CHARGEPUMP
spi_write_command(0x14) ;
spi_write_command(0x20); //MEMORYMODE
spi_write_command(0x00); //0x0 act like ks0108
spi_write_command(0xA1); //SEGREMAP OR 1
spi_write_command(0xC8); //COMSCANDEC
spi_write_command(0xDA); //COMPINS
spi_write_command(0x12) ;
spi_write_command(0x81); //SETCONTRAST
spi_write_command(0xCF) ;
spi_write_command(0xd9); //SETPRECHARGE
spi_write_command(0xF1) ;
spi_write_command(0xDB); //VCOMDETECT
spi_write_command(0x40) ;
spi_write_command(0xA4); //DISPLAYALLON_RESUME
spi_write_command(0xA6); //NORMALDISPLAY
spi_write_command(0xAF); //DISPLAYON
First we toggle the reset line to set things back to a defined state. Then we send a sequence of commands that are required by the SSD1306 controller. As stated above Google and the controller datasheet are the only ways of deriving the appropriate commands for any given controller.
Next we need to tell the Micromite firmware about the size of the display :
if(Option->DISPLAY_ORIENTATION&1){
HRes=HorizontalRes;
VRes=VerticalRes;
} else {
VRes=HorizontalRes;
HRes=VerticalRes;
}
HRes and VRes are defined in cfunctions.h
Finally we need to set the locations of our drawrectangle, drawbitmap, and updatedisplay routines for access by Basic, clear the screen and restore the SPI configuration. We return the address of the memory map to Basic so that we can inspect it if required using PEEK.
//Set the DrawRectangle vector to point to our function
DrawRectangleVector=DrawRectangleVectorOffset;
//Set the DrawBitmap vector to point to our function
DrawBitmapVector=DrawBitmapVectorOffset;
//Set the end of Basic statement vector to point to our routine
CFuncmInt=updatedisplayVectorOffset;
//CLS
DrawRectangle(0,0,HRes-1,VRes-1,0);
SPIBRG=brgsave; //restore user (or my) setup
SPICON=consave;
SPICON2=con2save;
return (unsigned int)p;
That is all we need in the main routine. Next post I’ll look at the actual drawing routines. I’ll put the complete code at the end of the thread. In this post we look at the first of the three routines that we have told the Micromite firmware about and which it will use to actually write to the display in response to the various Basic drawing commands: BOX, CIRCLE, TEXT etc.
First we will look at “updatedisplay” and its helper function OLED_setxy.
void OLED_setxy(int x,int y){ //Set the cursor to column x and row (page) y
unsigned char* p=(void *)(unsigned int)(StartOfCFuncRam);
if(p[displaysize])x+=2;
spi_write_command(0xB0+y) ; //set page address
spi_write_command(0x10 | ((x>>4) & 0x0F)) ; //set high col address
spi_write_command(x & 0x0f) ; //set low col address
}
void updatedisplay(void){
int m,n;
char* p=(void *)(unsigned int)(StartOfCFuncRam);
if(p[update]){
unsigned int consave=0,brgsave=0,con2save;
p[update]=0;
brgsave=SPIBRG; //save any user SPI setup
consave=SPICON;
con2save=SPICON2;
SPICON=0x8060;
SPIBRG=2;
SPICON2=0xC00;
for(n=0;n<screenrows;n++){
OLED_setxy(0,n);
PinSetBit(Option->LCD_CS, LATCLR);
PinSetBit(Option->LCD_CD, LATSET);
for(m=n*screenwidth;m<(n+1)*screenwidth;m++){
SPIsend(p[m]);
}
PinSetBit(Option->LCD_CS, LATCLR);
}
SPIBRG=brgsave; //restore user (or my) setup
SPICON=consave;
SPICON2=con2save;
}
}
As stated previously this is the only routine that actually outputs to the display hardware. It is called after every Basic statement irrespective of whether that statement was a drawing command so the first thing it needs to do is establish if a drawing command has been used and exit immediately if not.
char* p=(void *)(unsigned int)(StartOfCFuncRam);
if(p[update]){
These two statements set up access to the permanent memory area and then test whether a flag has been set in p[update]. We will see later that this flag is set in both drawrectangle and drawbitmap. If it isn’t set we can exit immediately with no further processing minimising the overhead on Basic.
If p[update] is set then we know that the screen needs updating.
In this case the first thing we do is to set up some local variables and zero the flag to mark that the update is going to be done. We then save any user SPI setup and replace it with our own.
unsigned int consave=0,brgsave=0,con2save;
p[update]=0;
brgsave=SPIBRG; //save any user SPI setup
consave=SPICON;
con2save=SPICON2;
SPICON=0x8060;
SPIBRG=2;
SPICON2=0xC00;
We could then do all sorts of optimisations to work out which bits of the display need updating but there are only 1024 bytes needed to update the complete display and we are running the SPI bus at 6Mbps (assuming 48MHz CPU) so the time taken to completely refresh the display is less than 2msec. Given this it is easiest to just do a complete refresh.
As previously mentioned the display is divided into 8 rows of 8 vertical pixels (64 total) and 128 columns. We therefore set up a loop to update each row:
for(n=0;n<screenrows;n++){
At the beginning of the loop we need to call OLED_setxy to position the write cursor. This sends specific commands to the display as specified by the datasheet to do this.
Then we need to tell the display that the next bytes sent will be data by setting the command/data pin high “PinSetBit(Option->LCD_CD, LATSET);” and select the controller “PinSetBit(Option->LCD_CS, LATCLR);”
We can then run a loop outputting each byte for that row in turn
for(m=n*screenwidth;m<(n+1)*screenwidth;m++){
SPIsend(p[m]);
}
Note how we are using the row number “n” multiplied by the screenwidth to get the correct offset into the memory map display for that row of data.
At the end of each row we disable the chip ready for the next cursor positioning statement.
Finally when both loops have finished we can restore the user’s SPI setup.
Next post we look at the drawrectangle function.
drawrectangle is the most important of the drawing primitives and is used for all output other than TEXT or GUI BITMAP.
long long DrawRectangleMEM(int x1, int y1, int x2, int y2, int c){
unsigned char* p=(void *)(unsigned int)(StartOfCFuncRam);
int i,j,loc,t;
unsigned char mask;
if(x2 <= x1) { t = x1; x1 = x2; x2 = t; }
if(y2 <= y1) { t = y1; y1 = y2; y2 = t; }
if(x1 < 0) x1 = 0; if(x1 >= screenwidth) x1 = screenwidth - 1;
if(x2 < 0) x2 = 0; if(x2 >= screenwidth) x2 = screenwidth - 1;
if(y1 < 0) y1 = 0; if(y1 >= screenheight) y1 = screenheight - 1;
if(y2 < 0) y2 = 0; if(y2 >= screenheight) y2 = screenheight - 1;
for(i=x1;i<=x2;i++){
for(j=y1;j<=y2;j++){
loc=i+(j/8)*screenwidth; //get the byte address for this bit
mask=1<<(j % 8); //get the bit position for this bit
if(c){
p[loc]|=mask;
} else {
p[loc]&=(~mask);
}
}
}
p[update]=1;
}
The code uses the defined calling sequence discussed in the first post on the thread.
long long DrawRectangleMEM(int x1, int y1, int x2, int y2, int c){
This defines the top left and bottom right coordinates of a rectangle and its colour. In the case of our monochrome display we will treat a colour of 0 as black and any other value as white.
As always we start by linking into the permanent shared memory
unsigned char* p=(void *)(unsigned int)(StartOfCFuncRam);
giving us access to the in-processor memory map.
The next two statements make sure x2 is bigger than x1 and swap them round if not before doing the same for y2 and y1.
Then we need to check that the limits of the rectangle fall within our screen space and truncate them if not. Some displays don’t mind coordinates outside the drawing area, others crash. In our case an invalid coordinate could cause us to write outside of the memory map crashing the Micromite! It is good practice to include the checks in all cases.
Next we set up two loops for the x and y extents of the rectangle and for each coordinate within the rectangle calculate the position in the memory map in which it is represented and the bit position within the byte
loc=i+(j/8)*screenwidth; //get the byte address for this bit
mask=1<<(j % 8); //get the bit position for this bit
Then based upon the colour we either “OR” the bit into the memory map or “AND” it out of the memory map.
if(c){
p[loc]|=mask;
} else {
p[loc]&=(~mask);
}
Once we have done this for all locations within the rectangle we then set the status marker to tell the updatedisplay routine that it needs to refresh the display. Note however, as discussed above, this will only happen at the end of the complete Basic statement which may contain many calls to drawrectangle.
This all looks good but at the moment it only caters for the default orientation i.e. landscape. The SSD1306 doesn’t support any sort of hardware rotation so we need a bit of code to do this for us by remapping the X,Y coordinates of the rectangle based on the required orientation. The code as-is works for landscape so we will need three conditionals to cater for portrait, reverse landscape and reverse portrait.
if(Option->DISPLAY_ORIENTATION==PORTRAIT){
t=x1;
x1=screenwidth-y2-1;
y2=t;
t=x2;
x2=screenwidth-y1-1;
y1=t;
}
if(Option->DISPLAY_ORIENTATION==RLANDSCAPE){
x1=screenwidth-x1-1;
x2=screenwidth-x2-1;
y1=screenheight-y1-1;
y2=screenheight-y2-1;
}
if(Option->DISPLAY_ORIENTATION==RPORTRAIT){
t=y1;
y1=screenheight-x1-1;
x1=t;
t=y2;
y2=screenheight-x2-1;
x2=t;
}
Nothing very complicated here but it took a few minutes of trial and error and writing coordinate mappings on the back of old envelopes to get right. If we look at the RLANDSCAPE clause we can see the code is just swapping each x and y coordinate to the opposite end of its axis.
Next drawbitmap
drawbitmap is the more complicated of the two drawing routines because it has to cater for scaling the bitmap as well as just outputting it.
void DrawBitmapMEM(int x1, int y1, int width, int height, int scale, int fc, int bc, unsigned char *bitmap){
int i, j, k, m, x, y,t, loc;
unsigned char omask, amask;
unsigned char* p=(void *)(unsigned int)(StartOfCFuncRam);
for(i = 0; i < height; i++) { // step thru the font scan line by line
for(j = 0; j < scale; j++) { // repeat lines to scale the font
for(k = 0; k < width; k++) { // step through each bit in a scan line
for(m = 0; m < scale; m++) { // repeat pixels to scale in the x axis
x=x1 + k * scale + m ;
y=y1 + i * scale + j ;
loc=x+(y/8)*screenwidth; //get the byte address for this bit
omask=1<<(y % 8); //get the bit position for this bit
amask=~omask;
if(x >= 0 && x < screenwidth && y >= 0 && y < screenheight) { // if the coordinates are valid
if((bitmap[((i * width) + k)/8] >> (((height * width) - ((i * width) + k) - 1) %8)) & 1) {
if(fc){
p[loc]|=omask;
} else {
p[loc]&=amask;
}
} else {
if(bc){
p[loc]|=omask;
} else {
p[loc]&=amask;
}
}
}
}
}
}
}
p[update]=1;
}
As before the calling sequence is completely specified and must be followed exactly:
void DrawBitmapMEM(int x1, int y1, int width, int height, int scale, int fc, int bc, unsigned char *bitmap)
This defines the top left of the area of the screen in which the bitmap will be displayed and the height and width of the bitmap. Using the scale parameter then defines the size of the bitmap on the screen: width * scale by height * scale.
As before, we first get access to the processor memory map and then there are four loops within loops that do the main work. This code was “acquired” from Geoff’s Micromite source and I have then adapted it for the various flavours of displays.
The loops are commented to explain how they step through the on-screen display area. In the middle of the four loops is the code that actually gets the “bit” from the bitmap and outputs the required information, in this case by updating the memory map, but, in the case of a colour display, it would be a direct write to the TFT display controller.
First we need to calculate the screen x,y coordinates from the loop counters:
x=x1 + k * scale + m ;
y=y1 + i * scale + j ;
Then we need to convert these into a byte/bit location in the memory map
loc=x+(y/8)*screenwidth; //get the byte address for this bit
omask=1<<(y % 8); //get the bit position for this bit
amask=~omask; //create a mask for ANDing this bit out
Next we need to make sure the x,y coordinates are within the display area:
if(x >= 0 && x < screenwidth && y >= 0 && y < screenheight) // if the coordinates are valid
If they are we can locate the relevant bit in the bitmap and test whether it is a one or a zero:
if((bitmap[((i * width) + k)/8] >> (((height * width) – ((i * width) + k) – 1) %8)) & 1) {
This tells us whether to output the foreground colour (fc) or the background colour (bc). In our monochrome example these can only be zero or non-zero so based on this we can update our memory map:
if(fc){
p[loc]|=omask;
} else {
p[loc]&=amask;
}
or:
if(bc){
p[loc]|=omask;
} else {
p[loc]&=amask;
}
Finally as all the loops terminate we set the status byte to say a screen update is required.
As with the drawrectangle code this version only deals with the default orientation: landscape.
The code to generalise this is the same as draw rectangle but we only need to do it for one coordinate pair:
if(Option->DISPLAY_ORIENTATION==PORTRAIT){
t=x;
x=VRes-y-1;
y=t;
}
if(Option->DISPLAY_ORIENTATION==RLANDSCAPE){
x=HRes-x-1;
y=VRes-y-1;
}
if(Option->DISPLAY_ORIENTATION==RPORTRAIT){
t=y;
y=HRes-x-1;
x=t;
}
This code is inserted once the x,y coordinates have been calculated in the inner loop.
Next post we will look at putting this all together.
I’ve attached the complete self-contained MPLabX project directory for this driver so that if you are up and running with MPLabX and XC32 v1.33 you can build it yourself.
Once this is compiled and the .ELF file run through CFGEN a simple MM.STARTUP routine is required to load the driver. The complete Basic program is given below.
After “LIBRARY SAVE” this takes just 4K of memory.
This concludes the set of CFunction tutorials. Please let me have comments and questions and I’ll do my best to answer. Hopefully over the three tutorials you have seen that writing CFunctions is fairly easy but more importantly is incredibly powerful in allowing the scope of Micromite Basic to be expanded in many different ways.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
sub mm.startup dim myaddr%=peek(cfunaddr SSD1306) dim i%=SSD1306(myaddr%,21,22,15,1,1) 'Address of CFunction, DCpin, RSTpin, CSpin, Size (1 for 1.3", 0 for 0.96"), orientation end sub ' ' CFunction SSD1306 00000141 'DrawRectangleMEM 3C029D00 8C43008C 8C690000 8C420090 80420015 24030002 14430009 8FAC0010 2402007F 00471823 00451023 00803821 00C02821 00403021 10000013 00602021 24030003 14430008 24030004 2402007F 00442023 00463023 2402003F 00452823 10000009 00473823 14430007 2402003F 00441823 00461023 00E03021 00403821 00A02021 00602821 0086102A 14400005 00A7102A 00801021 00C02021 00403021 00A7102A 14400005 28820000 00A01021 00E02821 00403821 28820000 0002200B 2402007F 28830080 0043200A 28C30000 0003300B 28C30080 0043300A 28A20000 0002280B 2402003F 28A30040 0043280A 28EA0000 000A380B 28EA0040 00EA100B 00405021 00C4102A 10400025 0145682A 10000028 24020001 24430007 28470000 0047180A 000338C3 000739C0 00E43821 00021FC3 00031F42 00434021 31080007 01031823 006B1804 11800006 306300FF 01273821 90E80000 00681825 10000006 A0E30000 01273821 00031827 90E80000 01031824 A0E30000 24420001 0142182A 1060FFE6 24430007 24840001 00C4102A 10400004 24020001 03E00008 A1220400 240B0001 11A0FFDC 00A01021 1000FFF7 24840001 03E00008 A1220400 'DrawBitmapMEM 27BDFFC0 AFBE003C AFB70038 AFB60034 AFB50030 AFB4002C AFB30028 AFB20024 AFB10020 AFB0001C AFA40040 AFA7004C 8FA80050 8FB70058 8FB6005C 3C029D00 8C42008C 18E00090 8C530000 0100A821 AFA5000C 00061023 AFA20010 70E61002 2442FFFF AFA20004 AFA60014 0000A021 AFA00008 24100001 3C079D00 24090002 240B0003 1000007C 240D0004 8CE50090 80B80015 17090007 00401821 8CE30098 8C630000 2463FFFF 00402821 10000013 006C1823 170B000A 00000000 8CE30094 8C630000 2463FFFF 00621823 8CE50098 8CA50000 24A5FFFF 10000008 00AC2823 170D0006 01402821 8CE50094 8CA50000 24A5FFFF 00A22823 01401821 2C780080 5300002C 24840001 04A00029 28B80040 53000028 24840001 24B80007 28BE0000 00BEC00A 0018C0C3 0018C1C0 03031821 0005C7C3 0018C742 00B82821 30A50007 00B82823 00B02804 30A500FF 0005F027 92580000 0311C024 1300000C 33DE00FF 8FB80054 13000005 02631821 90780000 00B82825 1000000E A0650000 90650000 03C5F024 1000000A A07E0000 12E00005 02631821 90780000 00B82825 10000004 A0650000 90650000 03C5F024 A07E0000 24840001 1488FFB5 24420001 25CE0001 25EFFFFF 11C60010 0335C821 01D41021 24430007 28520000 0072100B 000290C3 02D29021 000F17C3 00021742 01E28821 32310007 02228823 02308804 03201021 1000FFA2 00002021 8FA20000 24420001 AFA20000 14480004 258C0001 10000008 8FA30008 AFA00000 18C0FFF7 01805021 8FB90040 8FAF0004 1000FFE4 00007021 24630001 AFA30008 8FB8000C 0315C021 AFB8000C 8FA20004 8FA30010 00431021 AFA20004 8FB80014 8FA20008 8FA3004C 10430005 0298A021 1D00FFEA 8FAC000C 1000FFEF 8FA30008 24020001 A2620400 8FBE003C 8FB70038 8FB60034 8FB50030 8FB4002C 8FB30028 8FB20024 8FB10020 8FB0001C 03E00008 27BD0040 'spi_write_command 27BDFFE0 AFBF001C AFB10018 AFB00014 309100FF 3C109D00 8E030090 8E02001C 8064002C 0040F809 24050005 8E030090 8E02001C 8064002D 0040F809 24050005 3C02BF80 AC515820 3C03BF80 8C625810 30420080 1040FFFD 3C02BF80 8C425820 3C029D00 8C430090 8C42001C 8064002D 0040F809 24050006 8FBF001C 8FB10018 8FB00014 03E00008 27BD0020 'main 27BDFFC0 AFBF003C AFB70038 AFB60034 AFB50030 AFB4002C AFB30028 AFB20024 AFB10020 AFB0001C 00808821 3C109D00 8E020090 8CC30000 A043002E 8E020090 8CA30000 A043002C 8E020090 8CE30000 A043002D 8E020090 8FA30054 8C630000 A0430015 8E02003C 0040F809 24040500 00409021 8E02008C AC520000 8FA20050 8C420000 A2420401 8E030090 8E020010 8064002E 24050008 0040F809 00003021 8E030090 8E020010 8064002E 24050065 0040F809 00003021 8E030090 8E02001C 8064002E 0040F809 24050006 8E030090 8E020010 8064002C 24050008 0040F809 00003021 8E030090 8E020010 8064002C 24050065 0040F809 00003021 8E030090 8E02001C 8064002C 0040F809 24050006 8E030090 8E020010 8064002D 24050008 0040F809 00003021 8E030090 8E020010 8064002D 24050065 0040F809 00003021 8E030090 8E02001C 8064002D 0040F809 24050006 3C02BF81 8C44F220 7C84D800 3C030661 3463A053 1083000B 24020050 3C02BF81 8C43F220 7C63D800 3C020660 3442A053 00621026 24030050 2404000C 0082180B 00601021 3C039D00 8C630088 00431021 8C430000 24020064 54620007 00009821 3C02BF80 8C535830 8C545800 8C575840 10000003 3C029D00 0000A021 3C029D00 8C420010 3C03BF81 8C65F220 7CA5D800 3C030661 3463A053 10A3000B 24040014 3C03BF81 8C64F220 7C84D800 3C030660 3463A053 00832026 24030014 24050003 00A4180B 00602021 24050008 0040F809 00003021 3C029D00 8C420010 3C03BF81 8C65F220 7CA5D800 3C030661 3463A053 10A3000B 24040014 3C03BF81 8C64F220 7C84D800 3C030660 3463A053 00832026 24030014 24050003 00A4180B 00602021 24050065 0040F809 00003021 3C029D00 8C420010 3C03BF81 8C65F220 7CA5D800 3C030661 3463A053 10A3000B 24040029 3C03BF81 8C64F220 7C84D800 3C030660 3463A053 00832026 24030029 2405000E 00A4180B 00602021 24050002 0040F809 00003021 3C029D00 8C420010 3C03BF81 8C65F220 7CA5D800 3C030661 3463A053 10A3000B 24040029 3C03BF81 8C64F220 7C84D800 3C030660 3463A053 00832026 24030029 2405000E 00A4180B 00602021 24050065 0040F809 00003021 3C029D00 8C420010 3C03BF81 8C65F220 7CA5D800 3C030661 3463A053 10A3000B 2404000E 3C03BF81 8C64F220 7C84D800 3C030660 3463A053 00832026 2403000E 24050019 00A4180B 00602021 24050008 0040F809 00003021 3C029D00 8C420010 3C03BF81 8C65F220 7CA5D800 3C030661 3463A053 10A3000B 2404000E 3C03BF81 8C64F220 7C84D800 3C030660 3463A053 00832026 2403000E 24050019 00A4180B 00602021 24050065 0040F809 00003021 3C02BF81 8C43FA84 24040001 7C831804 AC43FA84 3C02BF81 8C43FB04 24040003 7C831804 AC43FB04 3C02BF80 34038060 AC435800 24030002 AC435830 24030C00 AC435840 56600005 3C029D00 8C535830 8C545800 8C575840 3C029D00 24420504 3C159D00 26B50000 02A2182B 10600004 02A2A823 8E230000 10000003 02A3A821 8E230000 02A3A821 3C169D00 26D601BC 02C2182B 10600004 02C2B023 8E230000 10000003 02C3B021 8E230000 02C3B021 3C039D00 24630C88 0062202B 10800004 00621023 8E240000 10000003 00448821 8E240000 00448821 3C109D00 8E030090 8E02001C 8064002E 0040F809 24050006 8E020004 0040F809 24042710 8E030090 8E02001C 8064002E 0040F809 24050005 8E020004 0040F809 24042710 8E030090 8E02001C 8064002E 0040F809 24050006 8E020004 0040F809 24042710 240400AE 0411FE94 00000000 240400D5 0411FE91 00000000 24040080 0411FE8E 00000000 240400A8 0411FE8B 00000000 2404003F 0411FE88 00000000 240400D3 0411FE85 00000000 00002021 0411FE82 00000000 24040040 0411FE7F 00000000 2404008D 0411FE7C 00000000 24040014 0411FE79 00000000 24040020 0411FE76 00000000 00002021 0411FE73 00000000 240400A1 0411FE70 00000000 240400C8 0411FE6D 00000000 240400DA 0411FE6A 00000000 24040012 0411FE67 00000000 24040081 0411FE64 00000000 240400CF 0411FE61 00000000 240400D9 0411FE5E 00000000 240400F1 0411FE5B 00000000 240400DB 0411FE58 00000000 24040040 0411FE55 00000000 240400A4 0411FE52 00000000 240400A6 0411FE4F 00000000 240400AF 0411FE4C 00000000 8E020090 90420015 30420001 10400008 3C029D00 8C430094 24040080 AC640000 8C420098 24030040 10000007 AC430000 8C430098 24040080 AC640000 8C420094 24030040 AC430000 3C029D00 8C430048 AC750000 8C43004C AC760000 8C4300A4 AC710000 8C430048 8C440094 8C860000 8C420098 8C470000 AFA00010 8C620000 00002021 00002821 24C6FFFF 0040F809 24E7FFFF 3C02BF80 AC535830 AC545800 AC575840 02401021 00001821 8FBF003C 8FB70038 8FB60034 8FB50030 8FB4002C 8FB30028 8FB20024 8FB10020 8FB0001C 03E00008 27BD0040 'OLED_setxy 27BDFFE8 AFBF0014 AFB00010 00808021 3C029D00 8C42008C 8C420000 90430401 24820002 0043800B 24A4FFB0 308400FF 0411FE08 00000000 7E041900 34840010 0411FE04 00000000 3204000F 0411FE01 00000000 8FBF0014 8FB00010 03E00008 27BD0018 'updatedisplay 27BDFFC8 AFBF0034 AFB70030 AFB6002C AFB50028 AFB40024 AFB30020 AFB2001C AFB10018 AFB00014 3C029D00 8C42008C 8C530000 82620400 10400039 3C02BF80 A2600400 8C555830 8C565800 8C575840 34038060 AC435800 24030002 AC435830 24030C00 AC435840 00009021 3C119D00 3C10BF80 24140008 00002021 02402821 0411FFC6 00000000 8E230090 8E22001C 8064002D 0040F809 24050005 8E230090 8E22001C 8064002C 0040F809 24050006 001219C0 26520001 001211C0 0062102A 1040000C 26640080 02601821 80620000 AE025820 8E025810 30420080 1040FFFD 00000000 8E025820 24630001 5464FFF8 80620000 8E230090 8E22001C 8064002D 0040F809 24050005 1654FFDB 26730080 3C02BF80 AC555830 AC565800 AC575840 8FBF0034 8FB70030 8FB6002C 8FB50028 8FB40024 8FB30020 8FB2001C 8FB10018 8FB00014 03E00008 27BD0038 End CFunction |
Leave a Reply