URL
https://opencores.org/ocsvn/openarty/openarty/trunk
Subversion Repositories openarty
[/] [openarty/] [trunk/] [sim/] [verilated/] [oledsim.cpp] - Rev 58
Compare with Previous | Blame | View Log
//////////////////////////////////////////////////////////////////////////////// // // Filename: oledsim.cpp // // Project: OpenArty, an entirely open SoC based upon the Arty platform // // Purpose: The goal of this module is very specifically to simulate the // PModOLEDrgb using a GTKMM controlled window. I'm doing this on // an Linux computer with X-Windows, although one GTKMM selling point is // that it should work in Windows as well. I won't vouch for that, as I // haven't tested under windows. // // Either way, this controller only implements *some* of the OLED commands. // There were just too many commands for me to be able to write them in the // short order that I needed to get a test up and running. Therefore, this // simulator will validate all commands and assure you they are valid // commands, but it will only respond to some. For specifics, see the // do_command() section below. // // You may notice a lot of assert() calls within this code. This is half // the purpose of the code: to verify that interactions, when the take // place, are valid. The sad problem and effect of this is simply that // when bugs are present, the error/warning messages are not that complete. // If you find yourself dealing with such an error, please feel free to // explain the assert better before asserting, and then send your // contributions back to me so that others can benefit from your work. // (Don't you love the GPL?) // // 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 "oledsim.h" const int OLEDSIM::OLED_HEIGHT = 64, OLEDSIM::OLED_WIDTH = 96; const int MICROSECOND = 81, tMINRESET = 3 * MICROSECOND, // 3 uS tCYCLE = 13, // 150 * NANOSECOND, clock cycle time tAS = 4, // 40 * NANOSECOND, address setup time tAH = 4, // 40 * NANOSECOND, address hold time tCSS = 7, // 75 * NANOSECOND, chip select setup tCSH = 5, // 60 * NANOSECOND, chip select hold tCLKL = 7, // 75 * NANOSECOND, time the clock must be low tCLKH = 7; // 75 * NANOSECOND, time the clock must be high void OLEDSIM::on_realize() { Gtk::DrawingArea::on_realize(); // We'll be doing all of our drawing on an off-screen bit map. Here, // let's allocate that pixel map ... m_pix = Cairo::ImageSurface::create(Cairo::FORMAT_RGB24, OLED_WIDTH, OLED_HEIGHT); // and a graphics context to be used when drawing to it. m_gc = Cairo::Context::create(m_pix); // We'll start the pixel map filled with all black, as this is what // my device looks like when I'm not doing anything with it. m_gc->set_source_rgb(0.0,0.0,0.0); // Black m_gc->rectangle(0, 0, OLED_WIDTH, OLED_HEIGHT); m_gc->fill(); } void OLEDSIM::get_preferred_width_vfunc(int &min, int &nw) const { // GTKMM wants to know how big we want our window to be. // Let's request a window twice as big as we need, but insist that // it never be smaller than one pixel output per one pixel input. // min = OLED_WIDTH; nw = OLED_WIDTH * 2; } void OLEDSIM::get_preferred_height_vfunc(int &min, int &nw) const { // // Same thing as above, but this time for height, not width. // min = OLED_HEIGHT; nw = OLED_HEIGHT * 2; } void OLEDSIM::get_preferred_width_for_height_vfunc(int h, int &min, int &nw) const { min = OLED_WIDTH; int k = (h+(OLED_HEIGHT/2))/OLED_HEIGHT; if (k <= 0) k = 1; nw = OLED_WIDTH * k; } void OLEDSIM::get_preferred_height_for_width_vfunc(int w, int &min, int &nw) const { min = OLED_HEIGHT; int k = (w+(OLED_WIDTH/2))/OLED_WIDTH; if (k <= 0) k = 1; nw = OLED_HEIGHT * k; } /* * This is our simulation function. This is the function that gets called at * every tick of our controller within Verilator. At each tick (and not twice * per tick), the outputs are gathered and sent our way. Here, we just decode * the power and reset outputs, and send everything else to handle_io(). */ void OLEDSIM::operator()(const int iopwr, const int rstn, const int dpwr, const int csn, const int sck, const int dcn, const int mosi) { if (!iopwr) { if (m_state != OLED_OFF) { m_state = OLED_OFF; clear_to(0.0); queue_draw_area(0,0,get_width(), get_height()); } assert(!dpwr); } else if (!rstn) { if (m_state != OLED_RESET) { m_state = OLED_RESET; m_locked = true; clear_to(0.1); m_reset_clocks = 0; queue_draw_area(0,0,get_width(), get_height()); } if (m_reset_clocks < tMINRESET) m_reset_clocks++; assert(csn); assert(sck); } else if (dpwr) { if (m_state != OLED_POWERED) { m_state = OLED_POWERED; queue_draw_area(0,0,get_width(), get_height()); if (!csn) { printf("OLED-ERR: CSN=%d, SCK=%d, DCN=%d, MOSI=%d, from %d,%d,%d\n", csn, sck, dcn, mosi, m_last_csn, m_last_sck, m_last_dcn); } assert(csn); // Can't power up with SPI active. } handle_io(csn, sck, dcn, mosi); } else { if (m_state != OLED_VIO) { m_state = OLED_VIO; queue_draw_area(0,0,OLED_WIDTH, OLED_HEIGHT); } handle_io(csn, sck, dcn, mosi); } } /* handle_io() * * We only enter this function if the I/O is powered up and the device is out * of reset. The device may (or may not) be on. Our purpose here is to decode * the SPI commands into a byte sequence, kept in m_data with a length given by * m_idx. Once a command has completed, we call do_command() to actually * process the values received, the arguments, etc. and do something with them. * */ void OLEDSIM::handle_io(const int csn, const int sck, const int dcn, const int mosi) { if ((csn != m_last_csn)||(sck != m_last_sck)||(dcn != m_last_dcn)) printf("OLED: HANDLE-IO(%d,%d,%d,%d) @[%d]%d\n", csn, sck, dcn, mosi, m_idx, m_bitpos); if (csn) { // CSN is high when the chip isn't selected. if (!m_last_csn) { // If the chip was just selected, it then means that our // command just completed. Let's process it here. printf("OLED: Ending a command\n"); assert(m_idx > 0); assert((m_bitpos&7)==0); do_command(m_last_dcn, m_idx, m_data); m_bitpos = 0; m_idx = 0; for(int i=0; i<8; i++) m_data[i] = 0; assert(m_last_sck); } if (!sck) printf("OLED: CSN = %d, SCK = %d, DCN = %d, MOSI = %d, from %d, %d, %d\n", csn, sck, dcn, mosi, m_last_csn, m_last_sck, m_last_dcn); assert(sck); m_bitpos = 0; m_idx = 0; } else { if (m_last_csn) { assert((sck)&&(m_last_sck)); assert(m_last_sck); printf("OLED: Starting a command\n"); } /* if (m_last_dcn != dcn) { m_address_counts = 0; } m_address_counts++; */ if ((sck)&&(!m_last_sck)) { m_bitpos++; m_data[m_idx] = (m_data[m_idx]<<1)|mosi; printf("OLED: Accepted bit: m_data[%d] = %02x\n", m_idx, m_data[m_idx]); if (m_bitpos >= 8) { m_idx++; m_bitpos &= 7; } assert(m_idx < 3+4+4); // assert(m_address_count > tCSS); } else if ((!sck)&&(m_last_sck)) { } } m_last_csn = csn; m_last_sck = sck; m_last_dcn = dcn; } void OLEDSIM::do_command(const int dcn, const int len, char *data) { assert(len > 0); assert(len <= 11); printf("OLED: RECEIVED CMD(%02x) ", data[0]&0x0ff); if (len > 1) { printf(" - "); for(int i=1; i<len-1; i++) printf("%02x:", data[i]&0x0ff); printf("%02x", data[len-1]&0x0ff); printf("\n"); } if (dcn) { // Do something with the pixmap double dr, dg, db; if (m_format == OLED_65kCLR) { int r, g, b; assert(len == 2); r = (data[0]>>3)&0x01f; g = ((data[0]<<3)&0x038)|((data[1]>>5)&0x07); b = ((data[1] )&0x01f); dr = r / 31.0; dg = g / 63.0; db = b / 31.0; } else { printf("OLED: UNSUPPORTED COLOR FORMAT!\n"); dr = dg = db = 0.0; } set_gddram(m_col, m_row, dr, dg, db); if (!m_vaddr_inc) { m_col++; if (m_col > m_col_end) { m_col = m_col_start; m_row++; if (m_row > m_row_end) m_row = m_row_start; } } else { m_row++; if (m_row > m_row_end) { m_row = m_row_start; m_col++; if (m_col > m_col_end) m_col = m_col_start; } } } else if (m_locked) { if ((len == 2)&&((data[0]&0x0ff) == 0x0fd)&&(data[1] == 0x12)) { m_locked = false; printf("OLED: COMMANDS UNLOCKED\n"); } else { printf("OLED: COMMAND IGNORED, IC LOCKED\n"); } } else { // Command word switch((data[0])&0x0ff) { case 0x15: // Setup column start and end address assert(len == 3); assert((data[1]&0x0ff) <= 95); assert((data[2]&0x0ff) <= 95); m_col_start = data[1]&0x0ff; m_col_end = data[2]&0x0ff; assert(m_col_end >= m_col_start); m_col = m_col_start; break; case 0x75: // Setup row start and end address assert(len == 3); assert((data[1]&0x0ff) <= 63); assert((data[2]&0x0ff) <= 63); assert(m_row_end >= m_row_start); m_row_start = data[1]&0x0ff; m_row_end = data[2]&0x0ff; break; case 0x81: // Set constrast for all color "A" segment assert(len == 2); break; case 0x82: // Set constrast for all color "B" segment assert(len == 2); break; case 0x83: // Set constrast for all color "C" segment assert(len == 2); break; case 0x87: // Set master current attenuation factor assert(len == 2); break; case 0x8a: // Set second pre-charge speed, color A assert(len == 2); break; case 0x8b: // Set second pre-charge speed, color B assert(len == 2); break; case 0x8c: // Set second pre-charge speed, color C assert(len == 2); break; case 0xa0: // Set driver remap and color depth assert(len == 2); m_vaddr_inc = (data[1]&1)?true:false; // m_fliplr = (data[1]&2)?true:false; if ((data[1] & 0x0c0)==0) m_format = OLED_256CLR; else if ((data[1] & 0x0c0)==0x40) m_format = OLED_65kCLR; // else if ((data[1] & 0x0c0)==0x80) // m_format = OLED_65kCLRTWO; break; case 0xa1: // Set display start line register by row assert(len == 2); break; case 0xa2: // Set vertical offset by com assert(len == 2); break; case 0xa4: // Set display mode case 0xa5: // Fallthrough case 0xa6: // Fallthrough case 0xa7: // Fallthrough assert(len == 1); break; case 0xa8: // Set multiplex ratio assert(len == 2); break; case 0xab: // Dim Mode setting assert(len == 6); break; case 0xad: assert(len == 2); assert((data[1]&0x0fe)==0x08e); break; case 0xac: case 0xae: case 0xaf: assert(len == 1); break; case 0xb0: // Power save mode assert((len == 2)&&((data[1] == 0x1a)||(data[1] == 0x0b))); break; case 0xb1: // Phase 1 and 2 period adjustment assert(len == 2); break; case 0xb3: // Displaky clock divider/oscillator frequency assert(len == 2); break; case 0xb8: // Set gray scale table assert(0 && "Gray scale table not implemented"); break; case 0xb9: // Enable Linear Gray Scale table assert(len == 1); break; case 0xbb: // Set pre-charge level assert(len == 2); break; case 0xbc: // NOP case 0xbd: // NOP assert(len == 1); case 0xbe: // Set V_COMH assert(len == 2); break; case 0xe3: // NOP assert(len == 1); break; case 0xfd: // Set command lock assert(len == 2); if (data[1] == 0x16) { m_locked = true; printf("OLED: COMMANDS NOW LOCKED\n"); } break; case 0x21: // Draw Line assert(len == 8); break; case 0x22: // Draw Rectangle assert(len == 11); break; case 0x23: // Copy assert(len == 7); break; case 0x24: // Dim Window assert(len == 5); break; case 0x25: // Clear Window assert(len == 5); break; case 0x26: // Fill Enable/Disable assert(len == 2); // if (data[0]&1) // m_drect_fills = 1; assert((data[1] & 0x10)==0); break; case 0x27: // Continuous horizontal and vertical scrolling setup assert(len == 6); break; case 0x2e: // Deactivate scrolling assert(len == 1); // m_scrolling = false; break; case 0x2f: // Activate scrolling assert(len == 1); // m_scrolling = true; break; default: printf("OLED: UNKNOWN COMMAND, data[0] = %02x\n", data[0] & 0x0ff); assert(0); break; } } } /* * set_gddram() * * Set graphics display DRAM. * * Here is the heart of drawing on the device, or at least pixel level drawing. * The device allows other types of drawing, such as filling rectangles and * such. Here, we just handle the setting of pixels. * * You'll note that updates to the drawing area are only queued if the device * is in powered mode. * * At some point, I may wish to implement scrolling. If/when that happens, * the GDDRAM will not be affected, but the area that needs to be redrawn will * be. Hence this routine will need to be adjusted at that time. */ void OLEDSIM::set_gddram(const int col, const int row, const double dr, const double dg, const double db) { // Set our color to that given by the rgb (double) parameters. m_gc->set_source_rgb(dr, dg, db); printf("OLED: Setting pixel[%2d,%2d]\n", col, row); int drow; // dcol; drow = row + m_display_start_row; if (drow >= OLED_HEIGHT) drow -= OLED_HEIGHT; m_gc->rectangle(col, row, 1, 1); m_gc->fill(); if (m_state == OLED_POWERED) { // Need to adjust the invalidated area if scrolling is taking // place. double kw, kh; kw = get_width()/(double)OLED_WIDTH; kh = get_height()/(double)OLED_HEIGHT; queue_draw_area(col*kw, row*kh, (int)(kw+0.5), (int)(kh+0.5)); } } /* * clear_to() * * Clears the simulated device to a known grayscale value. Examples are * 0.0 for black, or 0.1 for a gray that is nearly black. Note that this * call does *not* invalidate our window. Perhaps it should, but for now that * is the responsibility of whatever function calls this function. */ void OLEDSIM::clear_to(double v) { // How do we apply this to our pixmap? m_gc->set_source_rgb(v, v, v); m_gc->rectangle(0, 0, OLED_WIDTH, OLED_HEIGHT); m_gc->fill(); } bool OLEDSIM::on_draw(CONTEXT &gc) { gc->save(); if (m_state == OLED_POWERED) { // Scrolling will be implemented here gc->set_source(m_pix, 0, 0); gc->scale(get_width()/(double)OLED_WIDTH, get_height()/(double)OLED_HEIGHT); gc->paint(); } else { if ((m_state == OLED_VIO)||(m_state == OLED_RESET)) gc->set_source_rgb(0.1,0.1,0.1); // DARK gray else gc->set_source_rgb(0.0,0.0,0.0); // Black // gc->rectangle(0, 0, OLED_WIDTH, OLED_HEIGHT); gc->rectangle(0, 0, get_width(), get_height()); gc->fill(); } gc->restore(); return true; } OLEDWIN::OLEDWIN(void) { m_sim = new OLEDSIM(); m_sim->set_size_request(OLEDSIM::OLED_WIDTH, OLEDSIM::OLED_HEIGHT); set_border_width(0); add(*m_sim); show_all(); Gtk::Window::set_title(Glib::ustring("OLED Simulator")); }