1 /*
2  * The MIT License (MIT)
3  *
4  * Author: Daniel Mosquera
5  * Copyright (c) 2013 Daniel Mosquera
6  *
7  * Author: Thomas Ingleby <thomas.c.ingleby@intel.com>
8  * Copyright (c) 2014 Intel Corporation.
9  *
10  * Contributions: Jon Trulson <jtrulson@ics.com>
11  *                Sergey Kiselev <sergey.kiselev@intel.com>
12  *
13  * Permission is hereby granted, free of charge, to any person
14  * obtaining a copy of this software and associated documentation
15  * files (the "Software"), to deal in the Software without
16  * restriction, including without limitation the rights to use, copy,
17  * modify, merge, publish, distribute, sublicense, and/or sell copies
18  * of the Software, and to permit persons to whom the Software is
19  * furnished to do so, subject to the following conditions:
20  *
21  * The above copyright notice and this permission notice shall be
22  * included in all copies or substantial portions of the Software.
23  *
24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
28  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
29  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
30  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31  * SOFTWARE.
32  */
33 
34 #include <string>
35 #include <stdexcept>
36 #include <unistd.h>
37 
38 #include "hd44780_bits.h"
39 #include "lcm1602.h"
40 
41 using namespace upm;
42 
Lcm1602(int bus_in,int addr_in,bool isExpander,uint8_t numColumns,uint8_t numRows)43 Lcm1602::Lcm1602(int bus_in, int addr_in, bool isExpander,
44                  uint8_t numColumns, uint8_t numRows) :
45   m_i2c_lcd_control(new mraa::I2c(bus_in)),
46   m_gpioRS(0), m_gpioEnable(0), m_gpioD0(0),
47   m_gpioD1(0), m_gpioD2(0), m_gpioD3(0),
48   m_numColumns(numColumns), m_numRows(numRows)
49 {
50     mraa::Result error = mraa::SUCCESS;
51     m_name = "Lcm1602 (I2C)";
52     m_isI2C = true;
53 
54     m_lcd_control_address = addr_in;
55 
56     error = m_i2c_lcd_control->address(m_lcd_control_address);
57     if (error != mraa::SUCCESS) {
58         throw std::invalid_argument(std::string(__FUNCTION__) +
59                                     ": I2c.address() failed");
60         return;
61     }
62 
63     // default display control
64     m_displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
65 
66     // if we are not dealing with an expander (say via a derived class
67     // like Jhd1313m1), then we do not want to execute the rest of the
68     // code below.  Rather, the derived class's constructor should
69     // follow up with any setup required -- we will only initialize
70     // the I2C context and bail.
71 
72     if (!isExpander)
73       return;
74 
75     usleep(50000);
76     expandWrite(LCD_BACKLIGHT);
77     usleep(100000);
78 
79     write4bits(0x03 << 4);
80     usleep(4500);
81     write4bits(0x30);
82     usleep(4500);
83     write4bits(0x30);
84     usleep(150);
85 
86     // Put into 4 bit mode
87     write4bits(0x20);
88 
89     m_displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
90     // Set numeber of lines
91     command(LCD_FUNCTIONSET | 0x0f);
92     command(LCD_DISPLAYCONTROL | m_displayControl);
93     clear();
94 
95     // Set entry mode.
96     m_entryDisplayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
97     command(LCD_ENTRYMODESET | m_entryDisplayMode);
98 
99     home();
100 }
101 
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)102 Lcm1602::Lcm1602(uint8_t rs,  uint8_t enable, uint8_t d0,
103                  uint8_t d1, uint8_t d2, uint8_t d3,
104                  uint8_t numColumns, uint8_t numRows) :
105   m_i2c_lcd_control(0),
106   m_gpioRS(new mraa::Gpio(rs)), m_gpioEnable(new mraa::Gpio(enable)),
107   m_gpioD0(new mraa::Gpio(d0)), m_gpioD1(new mraa::Gpio(d1)),
108   m_gpioD2(new mraa::Gpio(d2)), m_gpioD3(new mraa::Gpio(d3)),
109   m_numColumns(numColumns), m_numRows(numRows)
110 {
111     mraa::Result error = mraa::SUCCESS;
112     m_name = "Lcm1602 (4-bit GPIO)";
113     m_isI2C = false;
114 
115     // setup our gpios
116 
117     m_gpioRS->dir(mraa::DIR_OUT);
118     m_gpioEnable->dir(mraa::DIR_OUT);
119 
120     m_gpioD0->dir(mraa::DIR_OUT);
121     m_gpioD1->dir(mraa::DIR_OUT);
122     m_gpioD2->dir(mraa::DIR_OUT);
123     m_gpioD3->dir(mraa::DIR_OUT);
124 
125 
126     // set RS and Enable low to begin issuing commands
127     m_gpioRS->write(0);
128     m_gpioEnable->write(0);
129 
130     // wait to stabilize
131     usleep(100000);
132 
133     // set 4bit mode
134 
135     // These steps are adapted from the HD44780 datasheet, figure 24
136 
137     // try 1
138     write4bits(0x03);
139     usleep(4500);
140 
141     // try 2
142     write4bits(0x03);
143     usleep(4500);
144 
145     // try 3
146     write4bits(0x03);
147     usleep(150);
148 
149     // Finally, put into 4 bit mode
150     write4bits(0x02);
151 
152     // Set number of lines
153     command(LCD_FUNCTIONSET | LCD_2LINE | LCD_4BITMODE | LCD_5x8DOTS);
154     m_displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
155     command(LCD_DISPLAYCONTROL | m_displayControl);
156     usleep(2000);
157     clear();
158 
159     // Set entry mode.
160     m_entryDisplayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
161     command(LCD_ENTRYMODESET | m_entryDisplayMode);
162 
163     home();
164 }
165 
~Lcm1602()166 Lcm1602::~Lcm1602()
167 {
168   // clean up after ourselves
169   if (m_isI2C)
170     {
171       delete m_i2c_lcd_control;
172     }
173   else
174     {
175       delete m_gpioRS;
176       delete m_gpioEnable;
177 
178       delete m_gpioD0;
179       delete m_gpioD1;
180       delete m_gpioD2;
181       delete m_gpioD3;
182     }
183 }
184 
185 /*
186  * **************
187  *  virtual area
188  * **************
189  */
190 mraa::Result
write(std::string msg)191 Lcm1602::write(std::string msg)
192 {
193     mraa::Result error = mraa::SUCCESS;
194     for (std::string::size_type i = 0; i < msg.size(); ++i) {
195         error = data(msg[i]);
196     }
197     return error;
198 }
199 
200 mraa::Result
setCursor(int row,int column)201 Lcm1602::setCursor(int row, int column)
202 {
203     mraa::Result error = mraa::SUCCESS;
204     column = column % m_numColumns;
205     uint8_t offset = column;
206 
207     switch (m_numRows)
208     {
209         case 1:
210             // Single row displays with more than 8 columns usually have their
211             // DDRAM split in two halves. The first half starts at address 00.
212             // The second half starts at address 40. E.g. 16x2 DDRAM mapping:
213             // 00 01 02 03 04 05 06 07 40 41 42 43 44 45 46 47
214             if (m_numColumns > 8)
215             {
216                 offset = (column % (m_numColumns / 2)) +
217                          (column / (m_numColumns / 2)) * 0x40;
218             }
219             break;
220         case 2:
221             // this should work for any display with two rows
222             // DDRAM mapping:
223             // 00 .. 27
224             // 40 .. 67
225             offset += row * 0x40;
226             break;
227         case 4:
228             if (m_numColumns == 16)
229             {
230                  // 16x4 display
231                  // DDRAM mapping:
232                  // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
233                  // 40 41 42 43 43 45 46 47 48 49 4A 4B 4C 4D 4E 4F
234                  // 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
235                  // 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F
236                  int row_addr[] = { 0x00, 0x40, 0x10, 0x50 };
237                  offset += row_addr[row];
238              }
239              else
240              {
241                  // 20x4 display
242                  // DDRAM mapping:
243                  // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13
244                  // 40 41 42 43 43 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53
245                  // 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27
246                  // 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67
247 		 int row_addr[] = { 0x00, 0x40, 0x14, 0x54 };
248                  offset += row_addr[row];
249              }
250              break;
251     }
252 
253     return command(LCD_CMD | offset);
254 }
255 
256 mraa::Result
clear()257 Lcm1602::clear()
258 {
259     mraa::Result ret;
260     ret = command(LCD_CLEARDISPLAY);
261     usleep(2000); // this command takes awhile
262     return ret;
263 }
264 
265 mraa::Result
home()266 Lcm1602::home()
267 {
268     mraa::Result ret;
269     ret = command(LCD_RETURNHOME);
270     usleep(2000); // this command takes awhile
271     return ret;
272 }
273 
274 mraa::Result
createChar(uint8_t charSlot,uint8_t charData[])275 Lcm1602::createChar(uint8_t charSlot, uint8_t charData[])
276 {
277     mraa::Result error = mraa::SUCCESS;
278     charSlot &= 0x07; // only have 8 positions we can set
279     error = command(LCD_SETCGRAMADDR | (charSlot << 3));
280     if (error == mraa::SUCCESS) {
281         for (int i = 0; i < 8; i++) {
282           error = data(charData[i]);
283         }
284     }
285 
286     return error;
287 }
288 
displayOn()289 mraa::Result Lcm1602::displayOn()
290 {
291   m_displayControl |= LCD_DISPLAYON;
292   return command(LCD_DISPLAYCONTROL | m_displayControl);
293 }
294 
displayOff()295 mraa::Result Lcm1602::displayOff()
296 {
297   m_displayControl &= ~LCD_DISPLAYON;
298   return command(LCD_DISPLAYCONTROL | m_displayControl);
299 }
300 
cursorOn()301 mraa::Result Lcm1602::cursorOn()
302 {
303   m_displayControl |= LCD_CURSORON;
304   return command(LCD_DISPLAYCONTROL | m_displayControl);
305 }
306 
cursorOff()307 mraa::Result Lcm1602::cursorOff()
308 {
309   m_displayControl &= ~LCD_CURSORON;
310   return command(LCD_DISPLAYCONTROL | m_displayControl);
311 }
312 
cursorBlinkOn()313 mraa::Result Lcm1602::cursorBlinkOn()
314 {
315   m_displayControl |= LCD_BLINKON;
316   return command(LCD_DISPLAYCONTROL | m_displayControl);
317 }
318 
cursorBlinkOff()319 mraa::Result Lcm1602::cursorBlinkOff()
320 {
321   m_displayControl &= ~LCD_BLINKON;
322   return command(LCD_DISPLAYCONTROL | m_displayControl);
323 }
324 
scrollDisplayLeft()325 mraa::Result Lcm1602::scrollDisplayLeft()
326 {
327   return command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
328 }
329 
scrollDisplayRight()330 mraa::Result Lcm1602::scrollDisplayRight()
331 {
332   return command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
333 }
334 
entryLeftToRight()335 mraa::Result Lcm1602::entryLeftToRight()
336 {
337   m_entryDisplayMode |= LCD_ENTRYLEFT;
338   return command(LCD_ENTRYMODESET | m_entryDisplayMode);
339 }
340 
entryRightToLeft()341 mraa::Result Lcm1602::entryRightToLeft()
342 {
343   m_entryDisplayMode &= ~LCD_ENTRYLEFT;
344   return command(LCD_ENTRYMODESET | m_entryDisplayMode);
345 }
346 
autoscrollOn()347 mraa::Result Lcm1602::autoscrollOn()
348 {
349   m_entryDisplayMode |= LCD_ENTRYSHIFTINCREMENT;
350   return command(LCD_ENTRYMODESET | m_entryDisplayMode);
351 }
352 
autoscrollOff()353 mraa::Result Lcm1602::autoscrollOff()
354 {
355   m_entryDisplayMode &= ~LCD_ENTRYSHIFTINCREMENT;
356   return command(LCD_ENTRYMODESET | m_entryDisplayMode);
357 }
358 
command(uint8_t cmd)359 mraa::Result Lcm1602::command(uint8_t cmd)
360 {
361   return send(cmd, 0);
362 }
363 
data(uint8_t cmd)364 mraa::Result Lcm1602::data(uint8_t cmd)
365 {
366   return send(cmd, LCD_RS); // 1
367 }
368 
369 
370 /*
371  * **************
372  *  private area
373  * **************
374  */
375 mraa::Result
send(uint8_t value,int mode)376 Lcm1602::send(uint8_t value, int mode)
377 {
378     mraa::Result ret = mraa::SUCCESS;
379     uint8_t h;
380     uint8_t l;
381 
382     if (m_isI2C)
383       {
384         h = value & 0xf0;
385         l = (value << 4) & 0xf0;
386         ret = write4bits(h | mode);
387         ret = write4bits(l | mode);
388         return ret;
389       }
390 
391     // else, gpio (4 bit)
392 
393     // register select
394     m_gpioRS->write(mode);
395 
396     h = value >> 4;
397     l = value & 0x0f;
398 
399     ret = write4bits(h);
400     ret = write4bits(l);
401     return ret;
402 }
403 
404 mraa::Result
write4bits(uint8_t value)405 Lcm1602::write4bits(uint8_t value)
406 {
407     mraa::Result ret = mraa::SUCCESS;
408 
409     if (m_isI2C)
410       {
411         ret = expandWrite(value);
412         ret = pulseEnable(value);
413         return ret;
414       }
415 
416     // else gpio
417     ret = m_gpioD0->write( ((value >> 0) & 0x01) );
418     ret = m_gpioD1->write( ((value >> 1) & 0x01) );
419     ret = m_gpioD2->write( ((value >> 2) & 0x01) );
420     ret = m_gpioD3->write( ((value >> 3) & 0x01) );
421 
422     ret = pulseEnable(value); // value is ignored here for gpio
423 
424     return ret;
425 }
426 
427 mraa::Result
expandWrite(uint8_t value)428 Lcm1602::expandWrite(uint8_t value)
429 {
430     // invalid for gpio
431     if (!m_isI2C)
432         return mraa::ERROR_INVALID_RESOURCE;
433 
434     uint8_t buffer = value | LCD_BACKLIGHT;
435     return m_i2c_lcd_control->writeByte(buffer);
436 }
437 
438 mraa::Result
pulseEnable(uint8_t value)439 Lcm1602::pulseEnable(uint8_t value)
440 {
441     mraa::Result ret = mraa::SUCCESS;
442 
443     if (m_isI2C)
444       {
445         ret = expandWrite(value | LCD_EN);
446         usleep(1);
447         ret = expandWrite(value & ~LCD_EN);
448         usleep(50);
449         return ret;
450       }
451 
452     // else gpio
453 
454     ret = m_gpioEnable->write(0);
455     usleep(1);
456     ret = m_gpioEnable->write(1);
457     usleep(1); // must be > 450ns
458     ret = m_gpioEnable->write(0);
459     usleep(100); // must be >37us
460 
461     return ret;
462 }
463