URL
https://opencores.org/ocsvn/openarty/openarty/trunk
Subversion Repositories openarty
Compare Revisions
- This comparison shows the changes necessary to convert path
/
- from Rev 34 to Rev 35
- ↔ Reverse comparison
Rev 34 → Rev 35
/openarty/trunk/sw/board/oledtest.c
1,3 → 1,45
//////////////////////////////////////////////////////////////////////////////// |
// |
// Filename: oledtest.c |
// |
// 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 |
// PMod. This program runs on the ZipCPU internal to the FPGA, |
// and commands the OLEDrgb to power on, reset, initialize, and then to |
// display an alternating pair of images onto the display. |
// |
// |
// Creator: Dan Gisselquist, Ph.D. |
// Gisselquist Technology, LLC |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// Copyright (C) 2015-2016, Gisselquist Technology, LLC |
// |
// 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 |
// by the Free Software Foundation, either version 3 of the License, or (at |
// your option) any later version. |
// |
// This program is distributed in the hope that it will be useful, but WITHOUT |
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// 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 |
// target there if the PDF file isn't present.) If not, see |
// <http://www.gnu.org/licenses/> for a copy. |
// |
// License: GPL, v3, as defined and found on www.gnu.org, |
// http://www.gnu.org/licenses/gpl.html |
// |
// |
//////////////////////////////////////////////////////////////////////////////// |
// |
// |
|
#include "artyboard.h" |
#include "zipsys.h" |
|
15,8 → 57,7
#define OLED_VCC_DISABLE 0x0020000 |
#define OLED_RESET 0x0040000 |
#define OLED_RESET_CLR 0x0040004 |
#define OLED_PWRRESET (OLED_PMODEN|OLED_RESET) // 5 |
#define OLED_FULLPOWER (OLED_PMODEN|OLED_VCCEN|OLED_RESET_CLR) // 3->7 |
#define OLED_FULLPOWER (OLED_PMODEN|OLED_VCCEN|OLED_RESET_CLR) |
#define OLED_POWER_DOWN (OLED_PMODEN_OFF|OLED_VCC_DISABLE) |
#define OLED_BUSY 1 |
#define OLED_DISPLAYON 0x0af |
26,51 → 67,15
|
#define OLED_DISPLAY_OFF |
|
const int init_sequence[] = { |
// Unlock commands |
0x01fd12, |
// Display off |
0x0ae, |
// Set remap and data format |
0x01a072, |
// Set the start line |
0x01a100, |
// Set the display offset |
0x01a200, |
// Normal display mode |
0x0000a4, |
// Set multiplex ratio |
0x01a83f, |
// Set master configuration: |
// Use External VCC |
0x01ad8e, |
// Disable power save mode |
0x01b00b, |
// Set phase length |
0x01b131, |
// Set clock divide |
0x01b3f0, |
// Set Second Pre-change Speed For ColorA |
0x018a64, |
// 5l) Set Set Second Pre-charge Speed of Color B |
0x018b78, |
// 5m) Set Second Pre-charge Speed of Color C |
0x018c64, |
// 5n) Set Pre-Charge Voltage |
0x01bb3a, |
// 50) Set VCOMH Deselect Level |
0x01be3e, |
// 5p) Set Master Current |
0x018706, |
// 5q) Set Contrast for Color A |
0x018191, |
// 5r) Set Contrast for Color B |
0x018250, |
// 5s) Set Contrast for Color C |
0x01837D, |
// disable scrolling |
0x02e }; |
|
|
/* |
* timer_delay() |
* |
* 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, |
* this will only work from supervisor mode. |
*/ |
void timer_delay(int counts) { |
// Clear the PIC. We want to exit from here on timer counts alone |
zip->pic = CLEARPIC; |
84,8 → 89,76
} // else anything less has likely already passed |
} |
|
|
void oled_clear(void); |
|
/* |
* 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 |
* 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 |
* set send two bytes (bits 15-0) to the port. The OLEDrgb treats these as |
* commands (first byte) with an argument (the second byte). |
*/ |
const int init_sequence[] = { |
// Unlock commands |
0x01fd12, |
// Display off |
0x0ae, |
// Set remap and data format |
0x01a072, |
// Set the start line |
0x01a100, |
// Set the display offset |
0x01a200, |
// Normal display mode |
0x0000a4, |
// Set multiplex ratio |
0x01a83f, |
// Set master configuration: |
// Use External VCC |
0x01ad8e, |
// Disable power save mode |
0x01b00b, |
// Set phase length |
0x01b131, |
// Set clock divide |
0x01b3f0, |
// Set Second Pre-change Speed For ColorA |
0x018a64, |
// 5l) Set Set Second Pre-charge Speed of Color B |
0x018b78, |
// 5m) Set Second Pre-charge Speed of Color C |
0x018c64, |
// 5n) Set Pre-Charge Voltage |
0x01bb3a, |
// 50) Set VCOMH Deselect Level |
0x01be3e, |
// 5p) Set Master Current |
0x018706, |
// 5q) Set Contrast for Color A |
0x018191, |
// 5r) Set Contrast for Color B |
0x018250, |
// 5s) Set Contrast for Color C |
0x01837D, |
// disable scrolling |
0x02e |
}; |
|
/* |
* oled_init() |
* |
* 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 |
* init_sequence[] above. This just primarily works to send that sequence to |
* the PMod. |
* |
* 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 |
* leave that as an advanced exercise. |
* |
*/ |
void oled_init(void) { |
int i; |
|
95,15 → 168,11
sys->io_oled.o_ctrl = init_sequence[i]; |
} |
|
// 5u) Clear Screen |
oled_clear(); |
|
zip->tma = (CLOCKFREQ_HZ/200); |
zip->pic = EINT(SYSINT_TMA); |
zip_rtu(); |
zip->pic = CLEARPIC; |
// Wait 5ms |
timer_delay(CLOCKFREQ_HZ/200); |
|
|
// Turn on VCC and wait 100ms |
sys->io_oled.o_data = OLED_VCCEN; |
// Wait 100 ms |
110,9 → 179,23
timer_delay(CLOCKFREQ_HZ/10); |
|
// Send Display On command |
sys->io_oled.o_ctrl = 0xaf; |
} |
sys->io_oled.o_ctrl = OLED_DISPLAYON; |
} |
|
/* |
* oled_clear() |
* |
* This should be fairly self-explanatory: it clears (sets to black) all of the |
* graphics memory on the OLED. |
* |
* 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, |
* 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 |
* 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 |
* (4+5), so the OLEDrgb should see: 0x25,0x00,0x00,0x5f,0x3f. |
*/ |
void oled_clear(void) { |
while(sys->io_oled.o_ctrl & OLED_BUSY) |
; |
122,6 → 205,11
; |
} |
|
/* |
* oled_fill |
* |
* 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) { |
int ctrl; // We'll send this value out the control/command port |
|
139,20 → 227,31
; |
sys->io_oled.o_ctrl = 0x12601; |
|
// |
// Now, let's build the actual copy command |
// |
// 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 |
// 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. |
// Here, we set both colors to be identical. |
// |
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|= ((pix >> 11) & 0x01f)<< 9; |
sys->io_oled.o_a|= ((pix >> 5) & 0x03f) ; |
sys->io_oled.o_b = ((pix >> 11) & 0x01f)<<17; |
sys->io_oled.o_b = ((pix ) & 0x01f)<<25; |
sys->io_oled.o_b|= ((pix >> 11) & 0x01f)<<17; |
sys->io_oled.o_b|= ((pix >> 5) & 0x03f)<< 8; |
sys->io_oled.o_b|= ((pix ) & 0x01f)<< 1; |
|
// Make certain we had finished with the port (should've ...) |
// Make certain we had finished with the port (we should've by now) |
while(sys->io_oled.o_ctrl & OLED_BUSY) |
; |
|
// and send our new command |
// 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 |
// command. |
sys->io_oled.o_ctrl = ctrl; |
|
// To be nice to whatever routine follows, we'll wait 'til the port |
161,7 → 260,20
; |
} |
|
|
/* |
* entry() |
* |
* In all (current) ZipCPU programs, the programs start with an entry() |
* function that takes no arguments. The actual bootup entry can be found |
* in the bootstram directory, but that calls us here. |
* |
*/ |
void entry(void) { |
|
// 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 |
// userspace to run: the idle_task. |
unsigned user_regs[16]; |
for(int i=0; i<15; i++) |
user_regs[i] = 0; |
168,15 → 280,24
user_regs[15] = (unsigned int)idle_task; |
zip_restore_context(user_regs); |
|
// Clear the PIC. We'll come back and use it later. |
// Clear the PIC. We'll come back and use it later. We clear it here |
// partly in order to avoid a race condition later. |
zip->pic = CLEARPIC; |
|
if (0) { // 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) { |
// While this appears to do the task quite nicely, it leaves |
// the master_ce line high within the CPU, and so it generates |
// a whole lot of debug information in our Verilator simulation, |
// busmaster_tb. |
int pwrcount = sys->io_pwrcount; |
do { |
pwrcount = sys->io_pwrcount; |
} while((pwrcount>0)&&(pwrcount < CLOCKFREQ_HZ/4)); |
} else { |
// By using the timer and sleeping instead, the simulator can |
// be made to run a *lot* faster, with a *lot* less debugging |
// ... junk. |
int pwrcount = sys->io_pwrcount; |
if ((pwrcount > 0)&&(pwrcount < CLOCKFREQ_HZ/4)) { |
pwrcount = CLOCKFREQ_HZ/4 - pwrcount; |
230,36 → 351,29
oled_init(); |
|
// 4. Clear screen |
// sys->io_oled.o_ctrl = OLED_CLEAR_SCREEN; |
// 5. Apply voltage |
// 6. Turn on display |
// 7. Wait 100ms |
// We already stuffed this command sequence into the oled_init, |
// so we're good here. |
|
// Wait for the command to complete |
while(sys->io_oled.o_ctrl & OLED_BUSY) |
; |
|
// 5. Apply power to VCCEN |
sys->io_oled.o_data = OLED_FULLPOWER; |
|
// 6. Delay 100ms |
timer_delay(CLOCKFREQ_HZ/10); |
|
while(1) { |
// 7. Send Display On command |
// sys->io_oled.o_ctrl = OLED_CLEAR_SCREEN; // ?? What command is this? |
sys->io_ledctrl = 0x0ff; |
sys->io_ledctrl = 0x0f0; |
|
sys->io_oled.o_ctrl = OLED_DISPLAYON; |
|
oled_clear(); |
|
// Let's start our writes at the top left of the GDDRAM |
// (screen memory) |
while(sys->io_oled.o_ctrl & OLED_BUSY) |
; |
sys->io_oled.o_ctrl = 0x2015005f; |
sys->io_oled.o_ctrl = 0x2015005f; // Sets column min/max address |
|
while(sys->io_oled.o_ctrl & OLED_BUSY) |
; |
sys->io_oled.o_ctrl = 0x2075003f; |
sys->io_oled.o_ctrl = 0x2075003f; // Sets row min/max address |
|
sys->io_ledctrl = 0x0fe; |
// Now ... finally ... we can send our image. |
for(int i=0; i<6144; i++) { |
while(sys->io_oled.o_ctrl & OLED_BUSY) |
266,14 → 380,22
; |
sys->io_oled.o_data = splash[i]; |
} |
sys->io_ledctrl = 0x0fc; |
|
// Wait 25 seconds. The LEDs are for a fun effect. |
sys->io_ledctrl = 0x0f1; |
timer_delay(CLOCKFREQ_HZ*5); |
sys->io_ledctrl = 0x0f3; |
timer_delay(CLOCKFREQ_HZ*5); |
sys->io_ledctrl = 0x0f7; |
timer_delay(CLOCKFREQ_HZ*5); |
sys->io_ledctrl = 0x0ff; |
timer_delay(CLOCKFREQ_HZ*5); |
sys->io_ledctrl = 0x0fe; |
timer_delay(CLOCKFREQ_HZ*5); |
|
|
// Display a second image. |
sys->io_ledctrl = 0x0fc; |
for(int i=0; i<6144; i++) { |
while(sys->io_oled.o_ctrl & OLED_BUSY) |
; |
280,10 → 402,12
sys->io_oled.o_data = mug[i]; |
} |
|
sys->io_ledctrl = 0x0f0; |
// Leave this one in effect for 5 seconds only. |
sys->io_ledctrl = 0x0f8; |
timer_delay(CLOCKFREQ_HZ*5); |
} |
|
// We'll never get here, so this line is really just for form. |
zip_halt(); |
} |
|