////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
//
|
//
|
// Filename: oledtest.c
|
// Filename: oledtest.c
|
//
|
//
|
// Project: OpenArty, an entirely open SoC based upon the Arty platform
|
// Project: OpenArty, an entirely open SoC based upon the Arty platform
|
//
|
//
|
// Purpose: To see whether or not we can display an image onto the OLEDrgb
|
// Purpose: To see whether or not we can display an image onto the OLEDrgb
|
// PMod. This program runs on the ZipCPU internal to the FPGA,
|
// PMod. This program runs on the ZipCPU internal to the FPGA,
|
// and commands the OLEDrgb to power on, reset, initialize, and then to
|
// and commands the OLEDrgb to power on, reset, initialize, and then to
|
// display an alternating pair of images onto the display.
|
// display an alternating pair of images onto the display.
|
//
|
//
|
//
|
//
|
// Creator: Dan Gisselquist, Ph.D.
|
// Creator: Dan Gisselquist, Ph.D.
|
// Gisselquist Technology, LLC
|
// Gisselquist Technology, LLC
|
//
|
//
|
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
//
|
//
|
// Copyright (C) 2015-2016, Gisselquist Technology, LLC
|
// Copyright (C) 2015-2016, Gisselquist Technology, LLC
|
//
|
//
|
// This program is free software (firmware): you can redistribute it and/or
|
// This program is free software (firmware): you can redistribute it and/or
|
// modify it under the terms of the GNU General Public License as published
|
// modify it under the terms of the GNU General Public License as published
|
// by the Free Software Foundation, either version 3 of the License, or (at
|
// by the Free Software Foundation, either version 3 of the License, or (at
|
// your option) any later version.
|
// your option) any later version.
|
//
|
//
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
|
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
// for more details.
|
// for more details.
|
//
|
//
|
// You should have received a copy of the GNU General Public License along
|
// You should have received a copy of the GNU General Public License along
|
// with this program. (It's in the $(ROOT)/doc directory, run make with no
|
// with this program. (It's in the $(ROOT)/doc directory, run make with no
|
// target there if the PDF file isn't present.) If not, see
|
// target there if the PDF file isn't present.) If not, see
|
// <http://www.gnu.org/licenses/> for a copy.
|
// <http://www.gnu.org/licenses/> for a copy.
|
//
|
//
|
// License: GPL, v3, as defined and found on www.gnu.org,
|
// License: GPL, v3, as defined and found on www.gnu.org,
|
// http://www.gnu.org/licenses/gpl.html
|
// http://www.gnu.org/licenses/gpl.html
|
//
|
//
|
//
|
//
|
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
//
|
//
|
//
|
//
|
|
|
#include "zipsys.h"
|
#include "zipsys.h"
|
#include "artyboard.h"
|
#include "artyboard.h"
|
|
|
void idle_task(void) {
|
void idle_task(void) {
|
while(1)
|
while(1)
|
zip_idle();
|
zip_idle();
|
}
|
}
|
|
|
extern int splash[], mug[];
|
extern int splash[], mug[];
|
|
|
#define OLED_PMODEN 0x0010001
|
#define OLED_PMODEN 0x0010001
|
#define OLED_PMODEN_OFF 0x0010000
|
#define OLED_PMODEN_OFF 0x0010000
|
#define OLED_IOPWR OLED_PMODEN
|
#define OLED_IOPWR OLED_PMODEN
|
#define OLED_VCCEN 0x0020002
|
#define OLED_VCCEN 0x0020002
|
#define OLED_VCC_DISABLE 0x0020000
|
#define OLED_VCC_DISABLE 0x0020000
|
#define OLED_RESET 0x0040000
|
#define OLED_RESET 0x0040000
|
#define OLED_RESET_CLR 0x0040004
|
#define OLED_RESET_CLR 0x0040004
|
#define OLED_FULLPOWER (OLED_PMODEN|OLED_VCCEN|OLED_RESET_CLR)
|
#define OLED_FULLPOWER (OLED_PMODEN|OLED_VCCEN|OLED_RESET_CLR)
|
#define OLED_POWER_DOWN (OLED_PMODEN_OFF|OLED_VCC_DISABLE)
|
#define OLED_POWER_DOWN (OLED_PMODEN_OFF|OLED_VCC_DISABLE)
|
#define OLED_BUSY 1
|
#define OLED_BUSY 1
|
#define OLED_DISPLAYON 0x0af
|
#define OLED_DISPLAYON 0x0af
|
|
|
|
|
#define MICROSECOND (CLOCKFREQ_HZ/1000000)
|
#define MICROSECOND (CLOCKFREQ_HZ/1000000)
|
|
|
#define OLED_DISPLAY_OFF
|
#define OLED_DISPLAY_OFF
|
|
|
|
|
|
|
/*
|
/*
|
* timer_delay()
|
* timer_delay()
|
*
|
*
|
* Using the timer peripheral, delay by a given number of counts. We'll sleep
|
* Using the timer peripheral, delay by a given number of counts. We'll sleep
|
* during this delayed time, and wait on an interrupt to wake us. As a result,
|
* during this delayed time, and wait on an interrupt to wake us. As a result,
|
* this will only work from supervisor mode.
|
* this will only work from supervisor mode.
|
*/
|
*/
|
void timer_delay(int counts) {
|
void timer_delay(int counts) {
|
// Clear the PIC. We want to exit from here on timer counts alone
|
// Clear the PIC. We want to exit from here on timer counts alone
|
zip->pic = CLEARPIC;
|
zip->pic = CLEARPIC;
|
|
|
if (counts > 10) {
|
if (counts > 10) {
|
// Set our timer to count down the given number of counts
|
// Set our timer to count down the given number of counts
|
zip->tma = counts;
|
zip->tma = counts;
|
zip->pic = EINT(SYSINT_TMA);
|
zip->pic = EINT(SYSINT_TMA);
|
zip_rtu();
|
zip_rtu();
|
zip->pic = CLEARPIC;
|
zip->pic = CLEARPIC;
|
} // else anything less has likely already passed
|
} // else anything less has likely already passed
|
}
|
}
|
|
|
void wait_on_interrupt(int mask) {
|
void wait_on_interrupt(int mask) {
|
// Clear our interrupt only, but disable all others
|
// Clear our interrupt only, but disable all others
|
zip->pic = DALLPIC|mask;
|
zip->pic = DALLPIC|mask;
|
zip->pic = EINT(mask);
|
zip->pic = EINT(mask);
|
zip_rtu();
|
zip_rtu();
|
zip->pic = DINT(mask)|mask;
|
zip->pic = DINT(mask)|mask;
|
}
|
}
|
|
|
void oled_clear(void);
|
void oled_clear(void);
|
|
|
/*
|
/*
|
* The following outlines a series of commands to send to the OLEDrgb as part
|
* The following outlines a series of commands to send to the OLEDrgb as part
|
* of an initialization sequence. The sequence itself was taken from the
|
* of an initialization sequence. The sequence itself was taken from the
|
* MPIDE demo. Single byte numbers in the sequence are just that: commands
|
* MPIDE demo. Single byte numbers in the sequence are just that: commands
|
* to send 8-bit values across the port. 17-bit values with the 16th bit
|
* to send 8-bit values across the port. 17-bit values with the 16th bit
|
* set send two bytes (bits 15-0) to the port. The OLEDrgb treats these as
|
* set send two bytes (bits 15-0) to the port. The OLEDrgb treats these as
|
* commands (first byte) with an argument (the second byte).
|
* commands (first byte) with an argument (the second byte).
|
*/
|
*/
|
const int init_sequence[] = {
|
const int init_sequence[] = {
|
// Unlock commands
|
// Unlock commands
|
0x01fd12,
|
0x01fd12,
|
// Display off
|
// Display off
|
0x0ae,
|
0x0ae,
|
// Set remap and data format
|
// Set remap and data format
|
0x01a072,
|
0x01a072,
|
// Set the start line
|
// Set the start line
|
0x01a100,
|
0x01a100,
|
// Set the display offset
|
// Set the display offset
|
0x01a200,
|
0x01a200,
|
// Normal display mode
|
// Normal display mode
|
0x0000a4,
|
0x0000a4,
|
// Set multiplex ratio
|
// Set multiplex ratio
|
0x01a83f,
|
0x01a83f,
|
// Set master configuration:
|
// Set master configuration:
|
// Use External VCC
|
// Use External VCC
|
0x01ad8e,
|
0x01ad8e,
|
// Disable power save mode
|
// Disable power save mode
|
0x01b00b,
|
0x01b00b,
|
// Set phase length
|
// Set phase length
|
0x01b131,
|
0x01b131,
|
// Set clock divide
|
// Set clock divide
|
0x01b3f0,
|
0x01b3f0,
|
// Set Second Pre-change Speed For ColorA
|
// Set Second Pre-change Speed For ColorA
|
0x018a64,
|
0x018a64,
|
// 5l) Set Set Second Pre-charge Speed of Color B
|
// 5l) Set Set Second Pre-charge Speed of Color B
|
0x018b78,
|
0x018b78,
|
// 5m) Set Second Pre-charge Speed of Color C
|
// 5m) Set Second Pre-charge Speed of Color C
|
0x018c64,
|
0x018c64,
|
// 5n) Set Pre-Charge Voltage
|
// 5n) Set Pre-Charge Voltage
|
0x01bb3a,
|
0x01bb3a,
|
// 50) Set VCOMH Deselect Level
|
// 50) Set VCOMH Deselect Level
|
0x01be3e,
|
0x01be3e,
|
// 5p) Set Master Current
|
// 5p) Set Master Current
|
0x018706,
|
0x018706,
|
// 5q) Set Contrast for Color A
|
// 5q) Set Contrast for Color A
|
0x018191,
|
0x018191,
|
// 5r) Set Contrast for Color B
|
// 5r) Set Contrast for Color B
|
0x018250,
|
0x018250,
|
// 5s) Set Contrast for Color C
|
// 5s) Set Contrast for Color C
|
0x01837D,
|
0x01837D,
|
// disable scrolling
|
// disable scrolling
|
0x02e
|
0x02e
|
};
|
};
|
|
|
/*
|
/*
|
* oled_init()
|
* oled_init()
|
*
|
*
|
* This initializes and starts up the OLED. While it sounds important, really
|
* This initializes and starts up the OLED. While it sounds important, really
|
* the majority of the work necessary to do this is really captured in the
|
* the majority of the work necessary to do this is really captured in the
|
* init_sequence[] above. This just primarily works to send that sequence to
|
* init_sequence[] above. This just primarily works to send that sequence to
|
* the PMod.
|
* the PMod.
|
*
|
*
|
* We should be able to do all of this with the DMA: wait for an OLEDrgb not
|
* We should be able to do all of this with the DMA: wait for an OLEDrgb not
|
* busy interrupt, send one value, repeat until done. For now ... we'll just
|
* busy interrupt, send one value, repeat until done. For now ... we'll just
|
* leave that as an advanced exercise.
|
* leave that as an advanced exercise.
|
*
|
*
|
*/
|
*/
|
void oled_init(void) {
|
void oled_init(void) {
|
int i;
|
int i;
|
|
|
for(i=0; i<sizeof(init_sequence); i++) {
|
for(i=0; i<sizeof(init_sequence); i++) {
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
sys->io_oled.o_ctrl = init_sequence[i];
|
sys->io_oled.o_ctrl = init_sequence[i];
|
}
|
}
|
|
|
oled_clear();
|
oled_clear();
|
|
|
// Wait 5ms
|
// Wait 5ms
|
timer_delay(CLOCKFREQ_HZ/200);
|
timer_delay(CLOCKFREQ_HZ/200);
|
|
|
// Turn on VCC and wait 100ms
|
// Turn on VCC and wait 100ms
|
sys->io_oled.o_data = OLED_VCCEN;
|
sys->io_oled.o_data = OLED_VCCEN;
|
// Wait 100 ms
|
// Wait 100 ms
|
timer_delay(CLOCKFREQ_HZ/10);
|
timer_delay(CLOCKFREQ_HZ/10);
|
|
|
// Send Display On command
|
// Send Display On command
|
sys->io_oled.o_ctrl = OLED_DISPLAYON;
|
sys->io_oled.o_ctrl = OLED_DISPLAYON;
|
}
|
}
|
|
|
/*
|
/*
|
* oled_clear()
|
* oled_clear()
|
*
|
*
|
* This should be fairly self-explanatory: it clears (sets to black) all of the
|
* This should be fairly self-explanatory: it clears (sets to black) all of the
|
* graphics memory on the OLED.
|
* graphics memory on the OLED.
|
*
|
*
|
* What may not be self-explanatory is that to send any more than three bytes
|
* What may not be self-explanatory is that to send any more than three bytes
|
* using our interface you need to send the first three bytes in o_ctrl,
|
* using our interface you need to send the first three bytes in o_ctrl,
|
* and set the next bytes (up to four) in o_a. (Another four can be placed in
|
* and set the next bytes (up to four) in o_a. (Another four can be placed in
|
* o_b.) When the word is written to o_ctrl, the command goes over the wire
|
* o_b.) When the word is written to o_ctrl, the command goes over the wire
|
* and o_a and o_b are reset. Hence we set o_a first, then o_ctrl. Further,
|
* and o_a and o_b are reset. Hence we set o_a first, then o_ctrl. Further,
|
* the '4' in the top nibble of o_ctrl indicates that we are sending 5-bytes
|
* the '4' in the top nibble of o_ctrl indicates that we are sending 5-bytes
|
* (4+5), so the OLEDrgb should see: 0x25,0x00,0x00,0x5f,0x3f.
|
* (4+5), so the OLEDrgb should see: 0x25,0x00,0x00,0x5f,0x3f.
|
*/
|
*/
|
void oled_clear(void) {
|
void oled_clear(void) {
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
sys->io_oled.o_a = 0x5f3f0000;
|
sys->io_oled.o_a = 0x5f3f0000;
|
sys->io_oled.o_ctrl = 0x40250000;
|
sys->io_oled.o_ctrl = 0x40250000;
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
}
|
}
|
|
|
/*
|
/*
|
* oled_fill
|
* oled_fill
|
*
|
*
|
* Similar to oled_clear, this fills a rectangle with a given pixel value.
|
* Similar to oled_clear, this fills a rectangle with a given pixel value.
|
*/
|
*/
|
void oled_fill(int c, int r, int w, int h, int pix) {
|
void oled_fill(int c, int r, int w, int h, int pix) {
|
int ctrl; // We'll send this value out the control/command port
|
int ctrl; // We'll send this value out the control/command port
|
|
|
if (c > 95) c = 95;
|
if (c > 95) c = 95;
|
if (c < 0) c = 0;
|
if (c < 0) c = 0;
|
if (r > 63) r = 63;
|
if (r > 63) r = 63;
|
if (r < 0) r = 0;
|
if (r < 0) r = 0;
|
if (w < 0) w = 0;
|
if (w < 0) w = 0;
|
if (h < 0) h = 0;
|
if (h < 0) h = 0;
|
if (c+w > 95) w = 95-c;
|
if (c+w > 95) w = 95-c;
|
if (r+h > 63) h = 63-r;
|
if (r+h > 63) h = 63-r;
|
|
|
// Enable a rectangle to fill
|
// Enable the fill rectangle function, rather than just the outline
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
sys->io_oled.o_ctrl = 0x12601;
|
sys->io_oled.o_ctrl = 0x12601;
|
|
|
//
|
//
|
// Now, let's build the actual copy command
|
// Now, let's build the actual copy command
|
//
|
//
|
// This is an 11 byte command, consisting of the 0x22, followed by
|
// This is an 11 byte command, consisting of the 0x22, followed by
|
// the top left column and row of our rectangle, and then the bottom
|
// the top left column and row of our rectangle, and then the bottom
|
// right column and row. That's the first five bytes. The next six
|
// right column and row. That's the first five bytes. The next six
|
// bytes are the color of the border and the color of the fill.
|
// bytes are the color of the border and the color of the fill.
|
// Here, we set both colors to be identical.
|
// Here, we set both colors to be identical.
|
//
|
//
|
ctrl = 0xa0220000 | ((c&0x07f)<<8) | (r&0x03f);
|
ctrl = 0xa0220000 | ((c&0x07f)<<8) | (r&0x03f);
|
sys->io_oled.o_a = (((c+w)&0x07f)<<24) | (((r+h)&0x03f)<<16);
|
sys->io_oled.o_a = (((c+w)&0x07f)<<24) | (((r+h)&0x03f)<<16);
|
sys->io_oled.o_a|= ((pix >> 11) & 0x01f)<< 9;
|
sys->io_oled.o_a|= ((pix >> 11) & 0x01f)<< 9;
|
sys->io_oled.o_a|= ((pix >> 5) & 0x03f) ;
|
sys->io_oled.o_a|= ((pix >> 5) & 0x03f) ;
|
sys->io_oled.o_b = ((pix ) & 0x01f)<<25;
|
sys->io_oled.o_b = ((pix ) & 0x01f)<<25;
|
sys->io_oled.o_b|= ((pix >> 11) & 0x01f)<<17;
|
sys->io_oled.o_b|= ((pix >> 11) & 0x01f)<<17;
|
sys->io_oled.o_b|= ((pix >> 5) & 0x03f)<< 8;
|
sys->io_oled.o_b|= ((pix >> 5) & 0x03f)<< 8;
|
sys->io_oled.o_b|= ((pix ) & 0x01f)<< 1;
|
sys->io_oled.o_b|= ((pix ) & 0x01f)<< 1;
|
|
|
// Make certain we had finished with the port (we should've by now)
|
// Make certain we had finished with the port
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
|
|
// and send our new command. Note that o_a and o_b were already set
|
// and send our new command. Note that o_a and o_b were already set
|
// ahead of time, and are only now being sent together with this
|
// ahead of time, and are only now being sent together with this
|
// command.
|
// command.
|
sys->io_oled.o_ctrl = ctrl;
|
sys->io_oled.o_ctrl = ctrl;
|
|
|
// To be nice to whatever routine follows, we'll wait 'til the port
|
// To be nice to whatever routine follows, we'll wait 'til the port
|
// is clear again.
|
// is clear again.
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
}
|
}
|
|
|
|
/*
|
|
* oled_show_image(int *img)
|
|
*
|
|
* This is a really simply function, for a really simple purpose: it copies
|
|
* a full size image to the device. You'll notice two versions of the routine
|
|
* below. They are roughly identical in what they do. The first version
|
|
* sets up the DMA to transfer the data, one word at a time, from RAM to
|
|
* the OLED. One byte is transferred every at every OLED interrupt. The other
|
|
* version works roughly the same way, but first waits for the OLED port to be
|
|
* clear before sending the image. The biggest difference between the two
|
|
* approaches is that, when using the DMA, the routine finishes before the
|
|
* DMA transfer is complete, whereas the second version of the routine
|
|
* returns as soon as the image transfer is complete.
|
|
*/
|
void oled_show_image(int *img) {
|
void oled_show_image(int *img) {
|
#define USE_DMA
|
#define USE_DMA
|
#ifdef USE_DMA
|
#ifdef USE_DMA
|
zip->dma.ctrl = DMACLEAR;
|
|
zip->dma.rd = img;
|
|
zip->dma.wr = &sys->io_oled.o_data;
|
|
zip->dma.len= 6144;
|
zip->dma.len= 6144;
|
|
zip->dma.rd = img;
|
|
zip->dma.wr = (int *)&sys->io_oled.o_data;
|
zip->dma.ctrl = DMAONEATATIME|DMA_CONSTDST|DMA_OLED;
|
zip->dma.ctrl = DMAONEATATIME|DMA_CONSTDST|DMA_OLED;
|
// timer_delay(256);
|
|
// zip_halt();
|
|
#else
|
#else
|
for(int i=0; i<6144; i++) {
|
for(int i=0; i<6144; i++) {
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
sys->io_oled.o_data = img[i];
|
sys->io_oled.o_data = img[i];
|
}
|
}
|
#endif
|
#endif
|
}
|
}
|
|
|
/*
|
/*
|
* entry()
|
* entry()
|
*
|
*
|
* In all (current) ZipCPU programs, the programs start with an entry()
|
* In all (current) ZipCPU programs, the programs start with an entry()
|
* function that takes no arguments. The actual bootup entry can be found
|
* function that takes no arguments. The actual bootup entry can be found
|
* in the bootstrap.c file, but that calls us here.
|
* in the bootstrap.c file, but that calls us here.
|
*
|
*
|
*/
|
*/
|
void entry(void) {
|
void entry(void) {
|
|
|
// Since we'll be returning to userspace via zip_rtu() in order to
|
// Since we'll be returning to userspace via zip_rtu() in order to
|
// wait for an interrupt, let's at least place a valid program into
|
// wait for an interrupt, let's at least place a valid program into
|
// userspace to run: the idle_task.
|
// userspace to run: the idle_task.
|
unsigned user_regs[16];
|
unsigned user_regs[16];
|
for(int i=0; i<15; i++)
|
for(int i=0; i<15; i++)
|
user_regs[i] = 0;
|
user_regs[i] = 0;
|
user_regs[15] = (unsigned int)idle_task;
|
user_regs[15] = (unsigned int)idle_task;
|
zip_restore_context(user_regs);
|
zip_restore_context(user_regs);
|
|
|
// Clear the PIC. We'll come back and use it later. We clear it here
|
// Clear the PIC. We'll come back and use it later. We clear it here
|
// partly in order to avoid a race condition later.
|
// partly in order to avoid a race condition later.
|
zip->pic = CLEARPIC;
|
zip->pic = CLEARPIC;
|
|
|
// Wait till we've had power for at least a quarter second
|
// Wait till we've had power for at least a quarter second
|
if (0) {
|
if (0) {
|
// While this appears to do the task quite nicely, it leaves
|
// While this appears to do the task quite nicely, it leaves
|
// the master_ce line high within the CPU, and so it generates
|
// the master_ce line high within the CPU, and so it generates
|
// a whole lot of debug information in our Verilator simulation,
|
// a whole lot of debug information in our Verilator simulation,
|
// busmaster_tb.
|
// busmaster_tb.
|
int pwrcount = sys->io_pwrcount;
|
int pwrcount = sys->io_pwrcount;
|
do {
|
do {
|
pwrcount = sys->io_pwrcount;
|
pwrcount = sys->io_pwrcount;
|
} while((pwrcount>0)&&(pwrcount < CLOCKFREQ_HZ/4));
|
} while((pwrcount>0)&&(pwrcount < CLOCKFREQ_HZ/4));
|
} else {
|
} else {
|
// By using the timer and sleeping instead, the simulator can
|
// By using the timer and sleeping instead, the simulator can
|
// be made to run a *lot* faster, with a *lot* less debugging
|
// be made to run a *lot* faster, with a *lot* less debugging
|
// ... junk.
|
// ... junk.
|
int pwrcount = sys->io_pwrcount;
|
int pwrcount = sys->io_pwrcount;
|
if ((pwrcount > 0)&&(pwrcount < CLOCKFREQ_HZ/4)) {
|
if ((pwrcount > 0)&&(pwrcount < CLOCKFREQ_HZ/4)) {
|
pwrcount = CLOCKFREQ_HZ/4 - pwrcount;
|
pwrcount = CLOCKFREQ_HZ/4 - pwrcount;
|
timer_delay(pwrcount);
|
timer_delay(pwrcount);
|
}
|
}
|
}
|
}
|
|
|
|
|
// If the OLED is already powered, such as might be the case if
|
// If the OLED is already powered, such as might be the case if
|
// we rebooted but the board was still hot, shut it down
|
// we rebooted but the board was still hot, shut it down
|
if (sys->io_oled.o_data & 0x07) {
|
if (sys->io_oled.o_data & 0x07) {
|
sys->io_oled.o_data = OLED_VCC_DISABLE;
|
sys->io_oled.o_data = OLED_VCC_DISABLE;
|
// Wait 100 ms
|
// Wait 100 ms
|
timer_delay(CLOCKFREQ_HZ/10);
|
timer_delay(CLOCKFREQ_HZ/10);
|
// Shutdown the entire devices power
|
// Shutdown the entire devices power
|
sys->io_oled.o_data = OLED_POWER_DOWN;
|
sys->io_oled.o_data = OLED_POWER_DOWN;
|
// Wait 100 ms
|
// Wait 100 ms
|
timer_delay(CLOCKFREQ_HZ/10);
|
timer_delay(CLOCKFREQ_HZ/10);
|
|
|
// Now let's try to restart it
|
// Now let's try to restart it
|
}
|
}
|
|
|
// 1. Power up the OLED by applying power to VCC
|
// 1. Power up the OLED by applying power to VCC
|
// This means we need to apply power to both the VCCEN line as well
|
// This means we need to apply power to both the VCCEN line as well
|
// as the PMODEN line. We'll also set the reset line low, so the
|
// as the PMODEN line. We'll also set the reset line low, so the
|
// device starts in a reset condition.
|
// device starts in a reset condition.
|
sys->io_oled.o_data = OLED_PMODEN|OLED_RESET_CLR;
|
sys->io_oled.o_data = OLED_PMODEN|OLED_RESET_CLR;
|
timer_delay(4*MICROSECOND);
|
timer_delay(4*MICROSECOND);
|
sys->io_oled.o_data = OLED_RESET;
|
sys->io_oled.o_data = OLED_RESET;
|
timer_delay(4*MICROSECOND);
|
timer_delay(4*MICROSECOND);
|
|
|
// 2. Send the Display OFF command
|
// 2. Send the Display OFF command
|
// This isn't necessary, since we already pulled RESET low.
|
// This isn't necessary, since we already pulled RESET low.
|
//
|
//
|
// sys->io_oled.o_ctrl = OLED_DISPLAY_OFF;
|
// sys->io_oled.o_ctrl = OLED_DISPLAY_OFF;
|
//
|
//
|
|
|
// However, we must hold the reset line low for at least 3us, as per
|
// However, we must hold the reset line low for at least 3us, as per
|
// the spec. We may also need to wait another 2us after that. Let's
|
// the spec. We may also need to wait another 2us after that. Let's
|
// hold reset low for 4us here.
|
// hold reset low for 4us here.
|
timer_delay(4*MICROSECOND);
|
timer_delay(4*MICROSECOND);
|
|
|
// Clear the reset condition.
|
// Clear the reset condition.
|
sys->io_oled.o_data = OLED_RESET_CLR;
|
sys->io_oled.o_data = OLED_RESET_CLR;
|
// Wait another 4us.
|
// Wait another 4us.
|
timer_delay(4*MICROSECOND);
|
timer_delay(4*MICROSECOND);
|
|
|
// 3. Initialize the display to the default settings
|
// 3. Initialize the display to the default settings
|
// This just took place during the reset cycle we just completed.
|
// This just took place during the reset cycle we just completed.
|
//
|
//
|
oled_init();
|
oled_init();
|
|
|
// 4. Clear screen
|
// 4. Clear screen
|
// 5. Apply voltage
|
// 5. Apply voltage
|
// 6. Turn on display
|
// 6. Turn on display
|
// 7. Wait 100ms
|
// 7. Wait 100ms
|
// We already stuffed this command sequence into the oled_init,
|
// We already stuffed this command sequence into the oled_init,
|
// so we're good here.
|
// so we're good here.
|
|
|
while(1) {
|
while(1) {
|
sys->io_ledctrl = 0x0f0;
|
sys->io_ledctrl = 0x0f0;
|
|
|
sys->io_oled.o_ctrl = OLED_DISPLAYON;
|
sys->io_oled.o_ctrl = OLED_DISPLAYON;
|
|
|
oled_clear();
|
oled_clear();
|
|
|
// Let's start our writes at the top left of the GDDRAM
|
// Let's start our writes at the top left of the GDDRAM
|
// (screen memory)
|
// (screen memory)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
sys->io_oled.o_ctrl = 0x2015005f; // Sets column min/max address
|
sys->io_oled.o_ctrl = 0x2015005f; // Sets column min/max address
|
|
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
;
|
;
|
sys->io_oled.o_ctrl = 0x2075003f; // Sets row min/max address
|
sys->io_oled.o_ctrl = 0x2075003f; // Sets row min/max address
|
|
while(sys->io_oled.o_ctrl & OLED_BUSY)
|
|
;
|
|
|
// Now ... finally ... we can send our image.
|
// Now ... finally ... we can send our image.
|
oled_show_image(splash);
|
oled_show_image(splash);
|
wait_on_interrupt(SYSINT_DMAC);
|
wait_on_interrupt(SYSINT_DMAC);
|
|
|
// Wait 25 seconds. The LEDs are for a fun effect.
|
// Wait 25 seconds. The LEDs are for a fun effect.
|
sys->io_ledctrl = 0x0f1;
|
sys->io_ledctrl = 0x0f1;
|
timer_delay(CLOCKFREQ_HZ*5);
|
timer_delay(CLOCKFREQ_HZ*5);
|
sys->io_ledctrl = 0x0f3;
|
sys->io_ledctrl = 0x0f3;
|
timer_delay(CLOCKFREQ_HZ*5);
|
timer_delay(CLOCKFREQ_HZ*5);
|
sys->io_ledctrl = 0x0f7;
|
sys->io_ledctrl = 0x0f7;
|
timer_delay(CLOCKFREQ_HZ*5);
|
timer_delay(CLOCKFREQ_HZ*5);
|
sys->io_ledctrl = 0x0ff;
|
sys->io_ledctrl = 0x0ff;
|
timer_delay(CLOCKFREQ_HZ*5);
|
timer_delay(CLOCKFREQ_HZ*5);
|
sys->io_ledctrl = 0x0fe;
|
sys->io_ledctrl = 0x0fe;
|
timer_delay(CLOCKFREQ_HZ*5);
|
timer_delay(CLOCKFREQ_HZ*5);
|
|
|
|
|
// Display a second image.
|
// Display a second image.
|
sys->io_ledctrl = 0x0fc;
|
sys->io_ledctrl = 0x0fc;
|
oled_show_image(mug);
|
oled_show_image(mug);
|
wait_on_interrupt(SYSINT_DMAC);
|
wait_on_interrupt(SYSINT_DMAC);
|
|
|
// Leave this one in effect for 5 seconds only.
|
// Leave this one in effect for 5 seconds only.
|
sys->io_ledctrl = 0x0f8;
|
sys->io_ledctrl = 0x0f8;
|
timer_delay(CLOCKFREQ_HZ*5);
|
timer_delay(CLOCKFREQ_HZ*5);
|
}
|
}
|
|
|
// We'll never get here, so this line is really just for form.
|
// We'll never get here, so this line is really just for form.
|
zip_halt();
|
zip_halt();
|
}
|
}
|
|
|
|
|