/* * The MIT License (MIT) * * Author: Daniel Mosquera * Copyright (c) 2013 Daniel Mosquera * * Author: Thomas Ingleby <thomas.c.ingleby@intel.com> * Copyright (c) 2014 Intel Corporation. * * Contributions: Jon Trulson <jtrulson@ics.com> * Sergey Kiselev <sergey.kiselev@intel.com> * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include <string> #include <stdexcept> #include <unistd.h> #include "hd44780_bits.h" #include "lcm1602.h" using namespace upm; Lcm1602::Lcm1602(int bus_in, int addr_in, bool isExpander, uint8_t numColumns, uint8_t numRows) : m_i2c_lcd_control(new mraa::I2c(bus_in)), m_gpioRS(0), m_gpioEnable(0), m_gpioD0(0), m_gpioD1(0), m_gpioD2(0), m_gpioD3(0), m_numColumns(numColumns), m_numRows(numRows) { mraa::Result error = mraa::SUCCESS; m_name = "Lcm1602 (I2C)"; m_isI2C = true; m_lcd_control_address = addr_in; error = m_i2c_lcd_control->address(m_lcd_control_address); if (error != mraa::SUCCESS) { throw std::invalid_argument(std::string(__FUNCTION__) + ": I2c.address() failed"); return; } // default display control m_displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; // if we are not dealing with an expander (say via a derived class // like Jhd1313m1), then we do not want to execute the rest of the // code below. Rather, the derived class's constructor should // follow up with any setup required -- we will only initialize // the I2C context and bail. if (!isExpander) return; usleep(50000); expandWrite(LCD_BACKLIGHT); usleep(100000); write4bits(0x03 << 4); usleep(4500); write4bits(0x30); usleep(4500); write4bits(0x30); usleep(150); // Put into 4 bit mode write4bits(0x20); m_displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; // Set numeber of lines command(LCD_FUNCTIONSET | 0x0f); command(LCD_DISPLAYCONTROL | m_displayControl); clear(); // Set entry mode. m_entryDisplayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; command(LCD_ENTRYMODESET | m_entryDisplayMode); home(); } Lcm1602::Lcm1602(uint8_t rs, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, uint8_t numColumns, uint8_t numRows) : m_i2c_lcd_control(0), m_gpioRS(new mraa::Gpio(rs)), m_gpioEnable(new mraa::Gpio(enable)), m_gpioD0(new mraa::Gpio(d0)), m_gpioD1(new mraa::Gpio(d1)), m_gpioD2(new mraa::Gpio(d2)), m_gpioD3(new mraa::Gpio(d3)), m_numColumns(numColumns), m_numRows(numRows) { mraa::Result error = mraa::SUCCESS; m_name = "Lcm1602 (4-bit GPIO)"; m_isI2C = false; // setup our gpios m_gpioRS->dir(mraa::DIR_OUT); m_gpioEnable->dir(mraa::DIR_OUT); m_gpioD0->dir(mraa::DIR_OUT); m_gpioD1->dir(mraa::DIR_OUT); m_gpioD2->dir(mraa::DIR_OUT); m_gpioD3->dir(mraa::DIR_OUT); // set RS and Enable low to begin issuing commands m_gpioRS->write(0); m_gpioEnable->write(0); // wait to stabilize usleep(100000); // set 4bit mode // These steps are adapted from the HD44780 datasheet, figure 24 // try 1 write4bits(0x03); usleep(4500); // try 2 write4bits(0x03); usleep(4500); // try 3 write4bits(0x03); usleep(150); // Finally, put into 4 bit mode write4bits(0x02); // Set number of lines command(LCD_FUNCTIONSET | LCD_2LINE | LCD_4BITMODE | LCD_5x8DOTS); m_displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; command(LCD_DISPLAYCONTROL | m_displayControl); usleep(2000); clear(); // Set entry mode. m_entryDisplayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; command(LCD_ENTRYMODESET | m_entryDisplayMode); home(); } Lcm1602::~Lcm1602() { // clean up after ourselves if (m_isI2C) { delete m_i2c_lcd_control; } else { delete m_gpioRS; delete m_gpioEnable; delete m_gpioD0; delete m_gpioD1; delete m_gpioD2; delete m_gpioD3; } } /* * ************** * virtual area * ************** */ mraa::Result Lcm1602::write(std::string msg) { mraa::Result error = mraa::SUCCESS; for (std::string::size_type i = 0; i < msg.size(); ++i) { error = data(msg[i]); } return error; } mraa::Result Lcm1602::setCursor(int row, int column) { mraa::Result error = mraa::SUCCESS; column = column % m_numColumns; uint8_t offset = column; switch (m_numRows) { case 1: // Single row displays with more than 8 columns usually have their // DDRAM split in two halves. The first half starts at address 00. // The second half starts at address 40. E.g. 16x2 DDRAM mapping: // 00 01 02 03 04 05 06 07 40 41 42 43 44 45 46 47 if (m_numColumns > 8) { offset = (column % (m_numColumns / 2)) + (column / (m_numColumns / 2)) * 0x40; } break; case 2: // this should work for any display with two rows // DDRAM mapping: // 00 .. 27 // 40 .. 67 offset += row * 0x40; break; case 4: if (m_numColumns == 16) { // 16x4 display // DDRAM mapping: // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F // 40 41 42 43 43 45 46 47 48 49 4A 4B 4C 4D 4E 4F // 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F // 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F int row_addr[] = { 0x00, 0x40, 0x10, 0x50 }; offset += row_addr[row]; } else { // 20x4 display // DDRAM mapping: // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 // 40 41 42 43 43 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 // 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 // 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 int row_addr[] = { 0x00, 0x40, 0x14, 0x54 }; offset += row_addr[row]; } break; } return command(LCD_CMD | offset); } mraa::Result Lcm1602::clear() { mraa::Result ret; ret = command(LCD_CLEARDISPLAY); usleep(2000); // this command takes awhile return ret; } mraa::Result Lcm1602::home() { mraa::Result ret; ret = command(LCD_RETURNHOME); usleep(2000); // this command takes awhile return ret; } mraa::Result Lcm1602::createChar(uint8_t charSlot, uint8_t charData[]) { mraa::Result error = mraa::SUCCESS; charSlot &= 0x07; // only have 8 positions we can set error = command(LCD_SETCGRAMADDR | (charSlot << 3)); if (error == mraa::SUCCESS) { for (int i = 0; i < 8; i++) { error = data(charData[i]); } } return error; } mraa::Result Lcm1602::displayOn() { m_displayControl |= LCD_DISPLAYON; return command(LCD_DISPLAYCONTROL | m_displayControl); } mraa::Result Lcm1602::displayOff() { m_displayControl &= ~LCD_DISPLAYON; return command(LCD_DISPLAYCONTROL | m_displayControl); } mraa::Result Lcm1602::cursorOn() { m_displayControl |= LCD_CURSORON; return command(LCD_DISPLAYCONTROL | m_displayControl); } mraa::Result Lcm1602::cursorOff() { m_displayControl &= ~LCD_CURSORON; return command(LCD_DISPLAYCONTROL | m_displayControl); } mraa::Result Lcm1602::cursorBlinkOn() { m_displayControl |= LCD_BLINKON; return command(LCD_DISPLAYCONTROL | m_displayControl); } mraa::Result Lcm1602::cursorBlinkOff() { m_displayControl &= ~LCD_BLINKON; return command(LCD_DISPLAYCONTROL | m_displayControl); } mraa::Result Lcm1602::scrollDisplayLeft() { return command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT); } mraa::Result Lcm1602::scrollDisplayRight() { return command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT); } mraa::Result Lcm1602::entryLeftToRight() { m_entryDisplayMode |= LCD_ENTRYLEFT; return command(LCD_ENTRYMODESET | m_entryDisplayMode); } mraa::Result Lcm1602::entryRightToLeft() { m_entryDisplayMode &= ~LCD_ENTRYLEFT; return command(LCD_ENTRYMODESET | m_entryDisplayMode); } mraa::Result Lcm1602::autoscrollOn() { m_entryDisplayMode |= LCD_ENTRYSHIFTINCREMENT; return command(LCD_ENTRYMODESET | m_entryDisplayMode); } mraa::Result Lcm1602::autoscrollOff() { m_entryDisplayMode &= ~LCD_ENTRYSHIFTINCREMENT; return command(LCD_ENTRYMODESET | m_entryDisplayMode); } mraa::Result Lcm1602::command(uint8_t cmd) { return send(cmd, 0); } mraa::Result Lcm1602::data(uint8_t cmd) { return send(cmd, LCD_RS); // 1 } /* * ************** * private area * ************** */ mraa::Result Lcm1602::send(uint8_t value, int mode) { mraa::Result ret = mraa::SUCCESS; uint8_t h; uint8_t l; if (m_isI2C) { h = value & 0xf0; l = (value << 4) & 0xf0; ret = write4bits(h | mode); ret = write4bits(l | mode); return ret; } // else, gpio (4 bit) // register select m_gpioRS->write(mode); h = value >> 4; l = value & 0x0f; ret = write4bits(h); ret = write4bits(l); return ret; } mraa::Result Lcm1602::write4bits(uint8_t value) { mraa::Result ret = mraa::SUCCESS; if (m_isI2C) { ret = expandWrite(value); ret = pulseEnable(value); return ret; } // else gpio ret = m_gpioD0->write( ((value >> 0) & 0x01) ); ret = m_gpioD1->write( ((value >> 1) & 0x01) ); ret = m_gpioD2->write( ((value >> 2) & 0x01) ); ret = m_gpioD3->write( ((value >> 3) & 0x01) ); ret = pulseEnable(value); // value is ignored here for gpio return ret; } mraa::Result Lcm1602::expandWrite(uint8_t value) { // invalid for gpio if (!m_isI2C) return mraa::ERROR_INVALID_RESOURCE; uint8_t buffer = value | LCD_BACKLIGHT; return m_i2c_lcd_control->writeByte(buffer); } mraa::Result Lcm1602::pulseEnable(uint8_t value) { mraa::Result ret = mraa::SUCCESS; if (m_isI2C) { ret = expandWrite(value | LCD_EN); usleep(1); ret = expandWrite(value & ~LCD_EN); usleep(50); return ret; } // else gpio ret = m_gpioEnable->write(0); usleep(1); ret = m_gpioEnable->write(1); usleep(1); // must be > 450ns ret = m_gpioEnable->write(0); usleep(100); // must be >37us return ret; }