http://www.aliexpress.com/item/0-96-Inch-Yellow-and-Blue-I2C-IIC-Serial-128X64-OLED-LCD-LED-Display-Module-for/2053302733.html
We can't run doom on them, but lets see how they perform for other real time functions.
I ordered a few - $4 each is a good price. And they are the same size as the vocore. It makes for a nice screen what doesn't increase the vocore size. Adafruit sells them for $20+, but in many varieties.
I decided the screen needed to work similar to a video card. Some shared memory that if you update it updates the display. This means, write a deamon that displays whatever is in the shared memory. Since these have 1 bit per pixel (on or off) then its quite simple.
The code for the software graphics deamon.
- Code: Select all
/* SSD1306 display deamon
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <string.h>
#include "deamon_functions.h"
#define FILEPATH "/tmp/graphics"
#define NUMCHARS (1025)
#define FILESIZE (NUMCHARS * sizeof(unsigned char))
#define refreshRate 15000
int main (int argc, char *argv[]){
int fd, result,i;
unsigned char *map; // mapped array of bytes
unsigned char oldmap[1024];
//open file for writting
fd = open(FILEPATH, O_RDWR | O_CREAT | O_TRUNC, (mode_t)0600);
if (fd == -1){
perror("Error opening file for writting");
exit(EXIT_FAILURE);
}
//stretch file size to size of graphics memory
result = lseek(fd, FILESIZE-1, SEEK_SET);
if (result == -1){
close(fd);
perror("Error stretching file size");
exit(EXIT_FAILURE);
}
//need to write to file to give it size
result = write(fd, "\0", 1);
if (result != 1){
close(fd);
perror("Error writting last byte to file");
exit(EXIT_FAILURE);
}
//Map the file
map = mmap(0, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED){
close(fd);
perror("Error mmapping file");
exit(EXIT_FAILURE);
}
/* i2c stuff
*
* */
//Open OLED i2c file
i2c_oled_fd = open("/dev/i2c-0", O_RDWR);
if (i2c_oled_fd < 0) {
perror("Error opening i2c file");
exit(EXIT_FAILURE);
}
if (ioctl(i2c_oled_fd, I2C_SLAVE, deviceI2CAddress) < 0) {
printf("Error ioctl");
exit(EXIT_FAILURE);
}
// Power up Display
init();
memcpy(oldmap,map,1024);
data(map,1024);
//setup the refresh timer vars
int16_t loc;
clock_t begin, end;
double time_spent;
//keep updating the display forever
//can put in exit commands if you want
while (1){
begin=clock();
//we don't want to waste cpu power if there is no changes.
if (map[1024]==1){
//for each row (8 pixels to a row = 64/8 = 8 rows)
//change if you have a different size screen
for (i=0;i<8;i++){
//memory buffer location is colums*row
loc=i*128;
//check if any memory has changed for that row
if (memcmp(map+loc,oldmap+loc,128)){
//update display with only that row
//this saves a lot of time updating the display.
//if updating all pixels refresh rate is very slow (i2c speed issue)
display_block(i,0,127,map);
//copy the row memory to the comparison memory
memcpy(oldmap+loc,map+loc,128);
}
}
//work done, set ready to recieve.
map[1024]=0;
}
//lets make the fresh rate every 15ms to save CPU overload
//make this time bigger if you want to use less cpu
//15ms is about as fast as you can go
end=clock();
time_spent = (double)(end-begin)/CLOCKS_PER_SEC*1000000;
if (time_spent<refreshRate){
usleep(refreshRate-time_spent);
}
}
close(i2c_oled_fd);
//unmap file
if (munmap(map, FILESIZE) == -1){
perror("Error un-mapping failed");
}
close(fd);
return 0;
}
Code for the deamon_functions.h was translated from adafruit code. I translated some of this code https://github.com/the-raspberry-pi-guy/OLED
https://github.com/adafruit/Adafruit_SSD1306
- Code: Select all
#define I2C_DEVID (0x78 >>1); // Translate Address
#define CMD_COMMAND 0x80 // Single command
#define CMD_CONT_CMD 0x00 // Continous command
#define CMD_DATA 0x40 // Single Data
#define CMD_CONT_DATAT 0xC0 // Continuous Data
#define CMD_NORMAL 0xA0 //
#define CMD_SETPAGE 0x22 //
#define SET_LOW_COLUMN 0x00
#define SET_HIGH_COLUMN 0x10
#define SET_MEMORY_MODE 0x20
#define SET_COL_ADDRESS 0x21
#define SET_PAGE_ADDRESS 0x22
#define RIGHT_HORIZ_SCROLL 0x26
#define LEFT_HORIZ_SCROLL 0x27
#define VERT_AND_RIGHT_HORIZ_SCROLL 0x29
#define VERT_AND_LEFT_HORIZ_SCROLL 0x2A
#define DEACTIVATE_SCROLL 0x2E
#define ACTIVATE_SCROLL 0x2F
#define SET_START_LINE 0x40
#define SET_CONTRAST 0x81
#define CHARGE_PUMP 0x8D
#define SEG_REMAP 0xA0
#define SET_REVERSE 0xA1
#define SET_VERT_SCROLL_AREA 0xA3
#define DISPLAY_ALL_ON_RESUME 0xA4
#define DISPLAY_ALL_ON 0xA5
#define NORMAL_DISPLAY 0xA6
#define INVERT_DISPLAY 0xA7
#define DISPLAY_OFF 0xAE
#define DISPLAY_ON 0xAF
#define COM_SCAN_INC 0xC0
#define COM_SCAN_DEC 0xC8
#define SET_DISPLAY_OFFSET 0xD3
#define SET_COM_PINS 0xDA
#define SET_VCOM_DETECT 0xDB
#define SET_DISPLAY_CLOCK_DIV 0xD5
#define SET_PRECHARGE 0xD9
#define SET_MULTIPLEX 0xA8
#define MEMORY_MODE_HORIZ 0x00
#define MEMORY_MODE_VERT 0x01
#define MEMORY_MODE_PAGE 0x02
// Global variables
unsigned char buffer[4];
int i2c_oled_fd;
int deviceI2CAddress = I2C_DEVID;
int rows = 64;
int cols = 128;
int buffer_rows=64, buffer_cols=128;
int bytes_per_col;
unsigned char *bitmap_data;
int col_offset = 0;
void send_command(unsigned char cmd){
unsigned char buf[3];
buf[0]=0x80;
buf[1]=cmd;
write(i2c_oled_fd,buf,2);
}
void data(unsigned char * bytes, int length){
int max_xfer = 32;
int len = max_xfer;
int count=0;
unsigned char b[max_xfer+1];
b[0]=0x40;
while (count<length){
if (length-count<max_xfer){
len=length-count;
}
memcpy(b+1,bytes+count,len);
write(i2c_oled_fd,b,len+1);
count+=max_xfer;
}
}
void init(){
//initialise display
send_command(DISPLAY_OFF);
send_command(SET_DISPLAY_CLOCK_DIV);
send_command(0x80);
if (buffer_rows == 64){
send_command(SET_MULTIPLEX);
send_command(0x3F);
send_command(SET_COM_PINS);
send_command(0x12);
}else{
send_command(SET_MULTIPLEX);
send_command(0x1F);
send_command(SET_COM_PINS);
send_command(0x02);
}
send_command(SET_DISPLAY_OFFSET);
send_command(0x00);
send_command(SET_START_LINE | 0x00);
send_command(CHARGE_PUMP);
send_command(0x14);
send_command(SET_MEMORY_MODE);
send_command(0x00);
send_command(SEG_REMAP | 0x01);
send_command(COM_SCAN_DEC);
send_command(SET_CONTRAST);
send_command(0x8f);
send_command(SET_PRECHARGE);
send_command(0xF1);
send_command(SET_VCOM_DETECT);
send_command(0x40);
send_command(DISPLAY_ALL_ON_RESUME);
send_command(NORMAL_DISPLAY);
send_command(DISPLAY_ON);
}
void display_block(int row, int col_start, int col_end, unsigned char *map){
int page_start = row;
int page_end = row;
int len = col_end-col_start;
int data_start = (page_start*buffer_cols)+col_start;
send_command(SET_MEMORY_MODE);
send_command(MEMORY_MODE_VERT);
send_command(SET_PAGE_ADDRESS);
send_command(page_start);
send_command(page_end);
send_command(SET_COL_ADDRESS);
send_command(col_start);
send_command(col_end);
data(map + data_start, len);
}
Now we have a shared memory file at /tmp/graphics which we can update with our other software to put stuff on the screen.
When nothing changes in the /tmp/graphics memory map, the CPU usage is very close to 0.
So lets make a bouncing ball app that relies on constant screen updates and see how much the CPU is suffering.
Lets hope it runs smooth!
- Code: Select all
/* Bouncing ball code for OLED Deamon on openWRT
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <string.h>
#include "font5x8.h"
#include "display_functions.h"
#define FILEPATH "/tmp/graphics"
#define NUMCHARS (1025)
#define FILESIZE (NUMCHARS * sizeof(unsigned char))
void bounce(unsigned char * map, int speed){
int ball_x, ball_y, radius=1;
int dy, dx;
int window_width=128;
int window_height=64;
ball_x = window_width /2;
ball_y = window_height /2;
dx = dy = speed;
while (1){
//wait for ready to send
while (map[1024]==1){
usleep(1000); //1ms sleep to let other things do work
}
draw_circle(ball_x,ball_y,radius, 0, map);
if (ball_y < radius+17 || ball_y > window_height - radius -2){
dy *= -1;
}
if (ball_x < radius || ball_x > window_width - radius -2){
dx *= -1;
}
ball_x += dx;
ball_y += dy;
draw_circle(ball_x,ball_y,radius, 1, map);
map[1024]=1;
}
}
int main (int argc, char *argv[]){
int fd, i;
unsigned char *map; //graphics memory map
bytes_per_col = rows / 8;
//open mmap file
fd = open(FILEPATH, O_RDWR);
if (fd == -1){
perror("Error opening file");
exit(EXIT_FAILURE);
}
map = mmap(0, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED){
close(fd);
perror("Error mmapping file");
exit(EXIT_FAILURE);
}
//clear screen
for (i=0;i<1024;i++){
map[i]=0x00;
}
map[1024]=1;
//wait for ready to send
while (map[1024]==1){
usleep(1000);
}
//start the bouncing ball forever loop
bounce(map, atoi(argv[1]));
//unmap memory
if (munmap(map, FILESIZE) == 1){
perror("Error unmapping file");
}
close(fd);
return 0;
}
and display_functions.h
- Code: Select all
#define I2C_DEVID (0x78 >>1); // Translate Address
#define CMD_COMMAND 0x80 // Single command
#define CMD_CONT_CMD 0x00 // Continous command
#define CMD_DATA 0x40 // Single Data
#define CMD_CONT_DATAT 0xC0 // Continuous Data
#define CMD_NORMAL 0xA0 //
#define CMD_SETPAGE 0x22 //
#define SET_LOW_COLUMN 0x00
#define SET_HIGH_COLUMN 0x10
#define SET_MEMORY_MODE 0x20
#define SET_COL_ADDRESS 0x21
#define SET_PAGE_ADDRESS 0x22
#define RIGHT_HORIZ_SCROLL 0x26
#define LEFT_HORIZ_SCROLL 0x27
#define VERT_AND_RIGHT_HORIZ_SCROLL 0x29
#define VERT_AND_LEFT_HORIZ_SCROLL 0x2A
#define DEACTIVATE_SCROLL 0x2E
#define ACTIVATE_SCROLL 0x2F
#define SET_START_LINE 0x40
#define SET_CONTRAST 0x81
#define CHARGE_PUMP 0x8D
#define SEG_REMAP 0xA0
#define SET_REVERSE 0xA1
#define SET_VERT_SCROLL_AREA 0xA3
#define DISPLAY_ALL_ON_RESUME 0xA4
#define DISPLAY_ALL_ON 0xA5
#define NORMAL_DISPLAY 0xA6
#define INVERT_DISPLAY 0xA7
#define DISPLAY_OFF 0xAE
#define DISPLAY_ON 0xAF
#define COM_SCAN_INC 0xC0
#define COM_SCAN_DEC 0xC8
#define SET_DISPLAY_OFFSET 0xD3
#define SET_COM_PINS 0xDA
#define SET_VCOM_DETECT 0xDB
#define SET_DISPLAY_CLOCK_DIV 0xD5
#define SET_PRECHARGE 0xD9
#define SET_MULTIPLEX 0xA8
#define MEMORY_MODE_HORIZ 0x00
#define MEMORY_MODE_VERT 0x01
#define MEMORY_MODE_PAGE 0x02
// Global variables
unsigned char buffer[4];
int i2c_oled_fd;
int deviceI2CAddress = I2C_DEVID;
int rows = 64;
int cols = 128;
int buffer_rows=64, buffer_cols=128;
int bytes_per_col;
unsigned char bitmap_data[1024];
int col_offset = 0;
//Functions
int printHex( unsigned char * packet, int n){
int j;
for (j=0;j<n;j++){
printf("%02X ", ((unsigned char *) packet)[j] );
}
printf("\n\n");
return 1;
}
void send_command(unsigned char cmd){
unsigned char buf[3];
buf[0]=0x80;
buf[1]=cmd;
write(i2c_oled_fd,buf,2);
}
void send_dat(unsigned char dat)
{
buffer[0]=0x40;
buffer[1]=dat;
write(i2c_oled_fd,buffer,2);
}
void data(unsigned char * bytes, int length){
//clock_t begin, end;
//double time_spent;
//begin=clock();
int max_xfer = 32;
int len = max_xfer;
int count=0;
unsigned char b[max_xfer+1];
b[0]=0x40;
while (count<length){
if (length-count<max_xfer){
len=length-count;
}
//printHex(b,len+1);
memcpy(b+1,bytes+count,len);
write(i2c_oled_fd,b,len+1);
count+=max_xfer;
}
//end=clock();
//time_spent = (double)(end-begin)/CLOCKS_PER_SEC;
//printf("%f\n",time_spent);
}
int draw_pixel(int x, int y, int on, unsigned char * map){
if (x<0 || x>=cols || y<0 || y>=buffer_rows){
return 1;
}
if (on){
map[x+(y/8)*128] |= (1 << (y&7));
}else{
map[x+(y/8)*128] &= ~(1 << (y&7));
}
return 0;
}
void drawLine(int x0, int y0, int x1, int y1, int on, unsigned char * map){
int dx = abs(x0-x1);
int dy = abs(y0-y1);
int xdir = (x1-x0)/dx;
int ydir = (y1-y0)/dy;
if (dy>dx){
int xstep = (dx*1000)/dy;
int i;
for (i=0; i<=dy; i++){
draw_pixel((x0+(xstep*i/1000*xdir)),(y0+i*ydir),on,map);
}
}else{
int ystep = (dx*1000)/dy;
int i;
for (i=0; i<=dx; i++){
draw_pixel((x0+i*xdir),(y0+(ystep*i/1000*ydir)),on,map);
}
}
}
void draw_circle(int x0, int y0, int r, int on, unsigned char *map){
int f = 1 - r;
int ddf_x = 1;
int ddf_y = -2 * r;
int x = 0;
int y = r;
draw_pixel(x0,y0+r, on, map);
draw_pixel(x0,y0-r, on, map);
draw_pixel(x0+r,y0, on, map);
draw_pixel(x0-r,y0, on, map);
while (x<y){
if (f >= 0){
y--;
ddf_y += 2;
f += ddf_y;
}
x++;
ddf_x += 2;
f += ddf_x;
draw_pixel(x0 + x,y0 + y, on, map);
draw_pixel(x0 - x,y0 + y, on, map);
draw_pixel(x0 + x,y0 - y, on, map);
draw_pixel(x0 - x,y0 - y, on, map);
draw_pixel(x0 + y,y0 + x, on, map);
draw_pixel(x0 - y,y0 + x, on, map);
draw_pixel(x0 + y,y0 - x, on, map);
draw_pixel(x0 - y,y0 - x, on, map);
}
}
void invert_display(){
send_command(INVERT_DISPLAY);
}
void flip_display(int flipped){
if (flipped){
send_command(COM_SCAN_INC);
send_command(SEG_REMAP | 0x00);
}else{
send_command(COM_SCAN_DEC);
send_command(SET_COM_PINS);
send_command(0x02);
}
}
void normal_display(){
send_command(NORMAL_DISPLAY);
}
void set_contrast_level(int contrast){
send_command(SET_CONTRAST);
send_command(contrast);
}
void clear_screen(){
int i;
unsigned char buf[17]={0x00};
buf[0]=0x40;
for ( i=0; i < 64; i++) {
write(i2c_oled_fd,buf,17);
}
}
void init(){
//initialise display
send_command(DISPLAY_OFF);
send_command(SET_DISPLAY_CLOCK_DIV);
send_command(0x80);
if (buffer_rows == 64){
send_command(SET_MULTIPLEX);
send_command(0x3F);
send_command(SET_COM_PINS);
send_command(0x12);
}else{
send_command(SET_MULTIPLEX);
send_command(0x1F);
send_command(SET_COM_PINS);
send_command(0x02);
}
send_command(SET_DISPLAY_OFFSET);
send_command(0x00);
send_command(SET_START_LINE | 0x00);
send_command(CHARGE_PUMP);
send_command(0x14);
send_command(SET_MEMORY_MODE);
send_command(0x00);
send_command(SEG_REMAP | 0x01);
send_command(COM_SCAN_DEC);
send_command(SET_CONTRAST);
send_command(0x8f);
send_command(SET_PRECHARGE);
send_command(0xF1);
send_command(SET_VCOM_DETECT);
send_command(0x40);
send_command(DISPLAY_ALL_ON_RESUME);
send_command(NORMAL_DISPLAY);
send_command(DISPLAY_ON);
}
void draw_text(int x, int y, char * string, unsigned char * map){
int i,j,r;
int p;
unsigned char mask;
for (i=0;i<strlen(string);i++){
p = (int)string[i] * font_cols;
for (j=0;j<font_cols;j++){//col in range(0,font_cols):
mask = font_bytes[p];
p++;
for (r=0;r<font_rows;r++){//row in ange(0,8):
draw_pixel(x,y+r,mask & 0x1, map);
mask >>= 1;
}
x++;
}
}
}
void draw_text2(int x, int y, char * string, int size, int space, int on, unsigned char * map){
int i,j,r,mask,p,py,sy,sx,px;
for (i=0;i<strlen(string);i++){
p = string[i] * font_cols;
for (j=0;j<font_cols;j++){
mask = font_bytes[p];
p++;
py = y;
for (r=0;r<8;r++){
for (sy=0;sy<size;sy++){
px = x;
for (sx=0;sx<size;sx++){
draw_pixel(px,py,mask & on, map);
px++;
}
py++;
}
mask >>= 1;
}
x += size;
}
x += space;
}
}
Yeah, thats a lot of CPU to render at a smooth 60FPS. But it works very smooth! Now for some miniature game making.
Displaying CPU usage on the screen used less than 5% cpu.Only large CPU usage on things that need constant rendering. Which is ok.