Merge pull request #191 from wolfmanjm/fix/m17-m18
[clinton/Smoothieware.git] / src / modules / utils / panel / panels / VikiLCD.cpp
CommitLineData
35089dc7
JM
1/*
2This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3Smoothie is free software: 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.
4Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
5You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6*/
7#include "VikiLCD.h"
8
9// if this is defined we use the R/W poll mode instead of fixed delays
10// However at the slower I2C frequency required for Viki long cables it is slower than fixed delay
11// taken from LiquidCrystalFast.cpp and implemented for Viki LCD here by Jim Morris
12//#define USE_FASTMODE
13
14//MCP23017 - Adafruit RGB LCD Shield and VikiLCD
15// bit pattern for the burstBits function is
16//
17// B7 B6 B5 B4 B3 B2 B1 B0 A7 A6 A5 A4 A3 A2 A1 A0 - MCP23017
18// RS RW EN D4 D5 D6 D7 LB LG LR BZ B4 B3 B2 B1 B0
19// 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
20#define M17_BIT_RS 0x8000
21#define M17_BIT_RW 0x4000
22#define M17_BIT_EN 0x2000
23#define M17_BIT_D4 0x1000
24#define M17_BIT_D5 0x0800
25#define M17_BIT_D6 0x0400
26#define M17_BIT_D7 0x0200
27#define M17_BIT_LB 0x0100
28#define M17_BIT_LG 0x0080
29#define M17_BIT_LR 0x0040
30#define M17_BIT_BZ 0x0020 //Added a buzzer on this pin
31#define M17_BIT_B4 0x0010
32#define M17_BIT_B3 0x0008
33#define M17_BIT_B2 0x0004
34#define M17_BIT_B1 0x0002
35#define M17_BIT_B0 0x0001
36
37VikiLCD::VikiLCD() {
38 // Default values
39 this->i2c_address = MCP23017_ADDRESS;
40 this->backlightval = 0x00;
41 this->displaycontrol = 0x00;
42 this->displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; // in case they forget to call begin() at least we have somethin
43 this->displaymode = 0x00;
44 this->_numlines = 4;
45
46 // I2C com
47// this->i2c = new mbed::I2C(P0_27, P0_28);
48 this->i2c = new mbed::I2C(p9, p10); // P0_0, P0_1
49
50 i2c->frequency(60000);
51
52 // configure the pins to use
53 this->encoder_a_pin.from_string(THEKERNEL->config->value( panel_checksum, encoder_a_pin_checksum)->by_default("nc")->as_string())->as_input();
54
55 this->encoder_b_pin.from_string(THEKERNEL->config->value( panel_checksum, encoder_b_pin_checksum)->by_default("nc")->as_string())->as_input();
56
57 this->button_pause_pin.from_string(THEKERNEL->config->value( panel_checksum, button_pause_pin_checksum)->by_default("nc")->as_string())->as_input();
58
59 paused= false;
60 button_pause.pin(&this->button_pause_pin)->up_attach( this, &VikiLCD::on_pause_release);
61}
62
63VikiLCD::~VikiLCD() {
64 delete this->i2c;
65}
66
67
68void VikiLCD::init(){
69 // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
70 // according to datasheet, we need at least 40ms after power rises above 2.7V
71 // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
72
73 char data[2];
74
75 // Setup
76 this->displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS;
77 this->backlightval = LCD_NOBACKLIGHT;
78
79 wait_ms(50);
80
81 // now set up input/output pins in MCP23017
82 data[0]= MCP23017_IODIRA;
83 data[1]= 0x1F; // buttons input, all others output
84 i2c->write(this->i2c_address, data, 2);
85
86
87 // set the button pullups
88 data[0]= MCP23017_GPPUA;
89 data[1]= 0x1F;
90 i2c->write(this->i2c_address, data, 2);
91
92 data[0]= MCP23017_IODIRB;
93 data[1]= 0x00; // all pins output
94 i2c->write(this->i2c_address, data, 2);
95
96 // turn leds off
97 setBacklight(0);
98
99 //put the LCD into 4 bit mode
100 // start with a non-standard command to make it realize we're speaking 4-bit here
101 // per LCD datasheet, first command is a single 4-bit burst, 0011.
102 //-----
103 // we cannot assume that the LCD panel is powered at the same time as
104 // the arduino, so we have to perform a software reset as per page 45
105 // of the HD44780 datasheet - (kch)
106 //-----
107
108 // bit pattern for the burstBits function is
109 //
110 // B7 B6 B5 B4 B3 B2 B1 B0 A7 A6 A5 A4 A3 A2 A1 A0 - MCP23017
111 // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
112 // RS RW EN D4 D5 D6 D7 B G R B4 B3 B2 B1 B0
113 for (uint8_t i=0;i < 3;i++) {
114 burstBits8b((M17_BIT_EN|M17_BIT_D5|M17_BIT_D4) >> 8);
115 burstBits8b((M17_BIT_D5|M17_BIT_D4) >> 8);
116 }
117 burstBits8b((M17_BIT_EN|M17_BIT_D5) >> 8);
118 burstBits8b(M17_BIT_D5 >> 8);
119
120
121 wait_ms(5); // this shouldn't be necessary, but sometimes 16MHz is stupid-fast.
122
123 command(LCD_FUNCTIONSET | displayfunction); // then send 0010NF00 (N=lines, F=font)
124 wait_ms(5); // for safe keeping...
125 command(LCD_FUNCTIONSET | displayfunction); // ... twice.
126 wait_ms(5); // done!
127
128 // turn on the LCD with our defaults. since these libs seem to use personal preference, I like a cursor.
129 displaycontrol = (LCD_DISPLAYON|LCD_BACKLIGHT);
130 display();
131 // clear it off
132 clear();
133
134 displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
135 // set the entry mode
136 command(LCD_ENTRYMODESET | displaymode);
137}
138
139// we use this to burst bits to the GPIO chip whenever we need to. avoids repetitive code.
140void VikiLCD::burstBits8b(uint8_t value) {
141 char data[2];
142 data[0] = MCP23017_GPIOB;
143 data[1]= value;
144 i2c->write(this->i2c_address, data, 2);
145}
146
147// value byte order is BA
148void VikiLCD::burstBits16(uint16_t value) {
149 char data[3];
150 data[0] = MCP23017_GPIOA;
151 data[1]= value&0xFF;
152 data[2]= value>>8;
153 i2c->write(this->i2c_address, data, 3);
154}
155
156// cycle the buzzer pin at a certain frequency (hz) for a certain duration (ms)
157void VikiLCD::buzz(long duration, uint16_t freq) {
158 char data[2];
159 int currentRegister = 0;
160 // read gpio register
161 data[0] = MCP23017_GPIOA;
162 i2c->write(this->i2c_address, data, 1);
163 i2c->read(this->i2c_address, data, 1); // Read from selected Register
164 currentRegister= data[0];
165
166 duration *=1000; //convert from ms to us
167 long period = 1000000 / freq; // period in us
168 long elapsed_time = 0;
169 while (elapsed_time < duration) {
170 data[0]= MCP23017_GPIOA;
171 data[1]= currentRegister |= M17_BIT_BZ;
172 i2c->write(this->i2c_address, data, 2);
173
174 wait_us(period / 2);
175
176 data[0]= MCP23017_GPIOA;
177 data[1]= currentRegister &= ~M17_BIT_BZ;
178 i2c->write(this->i2c_address, data, 2);
179
180 wait_us(period / 2);
181 elapsed_time += (period);
182 }
183}
184
185uint8_t VikiLCD::readButtons(void) {
186 char data[2];
187 data[0] = MCP23017_GPIOA;
188 i2c->write(this->i2c_address, data, 1);
189 i2c->read(this->i2c_address, data, 1); // Read from selected Register
190
191 // check the button pause
192 button_pause.check_signal();
193
194 return (~data[0]) & ALL_BUTTON_BITS;
195}
196
197int VikiLCD::readEncoderDelta() {
198 static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
199 static uint8_t old_AB = 0;
200 old_AB <<= 2; //remember previous state
201 old_AB |= ( this->encoder_a_pin.get() + ( this->encoder_b_pin.get() * 2 ) ); //add current state
202 return enc_states[(old_AB&0x0f)];
203}
204
205void VikiLCD::clear()
206{
207 command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
208#ifndef USE_FASTMODE
209 wait_us(2000); // this command takes a long time!
210#endif
211}
212
213void VikiLCD::home()
214{
215 command(LCD_RETURNHOME); // set cursor position to zero
216#ifndef USE_FASTMODE
217 wait_us(2000); // this command takes a long time!
218#endif
219}
220
221void VikiLCD::setCursor(uint8_t col, uint8_t row)
222{
223 int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
224 if ( row > _numlines ) row = _numlines - 1; // we count rows starting w/0
225 command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
226}
227
228// Turn the display on/off (quickly)
229void VikiLCD::noDisplay() {
230 displaycontrol &= ~LCD_DISPLAYON;
231 command(LCD_DISPLAYCONTROL | displaycontrol);
232}
233void VikiLCD::display() {
234 displaycontrol |= LCD_DISPLAYON;
235 command(LCD_DISPLAYCONTROL | displaycontrol);
236}
237
238// Turns the underline cursor on/off
239void VikiLCD::noCursor() {
240 displaycontrol &= ~LCD_CURSORON;
241 command(LCD_DISPLAYCONTROL | displaycontrol);
242}
243void VikiLCD::cursor() {
244 displaycontrol |= LCD_CURSORON;
245 command(LCD_DISPLAYCONTROL | displaycontrol);
246}
247
248// Turn on and off the blinking cursor
249void VikiLCD::noBlink() {
250 displaycontrol &= ~LCD_BLINKON;
251 command(LCD_DISPLAYCONTROL | displaycontrol);
252}
253void VikiLCD::blink() {
254 displaycontrol |= LCD_BLINKON;
255 command(LCD_DISPLAYCONTROL | displaycontrol);
256}
257
258// These commands scroll the display without changing the RAM
259void VikiLCD::scrollDisplayLeft(void) {
260 command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
261}
262void VikiLCD::scrollDisplayRight(void) {
263 command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
264}
265
266// This is for text that flows Left to Right
267void VikiLCD::leftToRight(void) {
268 displaymode |= LCD_ENTRYLEFT;
269 command(LCD_ENTRYMODESET | displaymode);
270}
271
272// This is for text that flows Right to Left
273void VikiLCD::rightToLeft(void) {
274 displaymode &= ~LCD_ENTRYLEFT;
275 command(LCD_ENTRYMODESET | displaymode);
276}
277
278// This will 'right justify' text from the cursor
279void VikiLCD::autoscroll(void) {
280 displaymode |= LCD_ENTRYSHIFTINCREMENT;
281 command(LCD_ENTRYMODESET | displaymode);
282}
283
284// This will 'left justify' text from the cursor
285void VikiLCD::noAutoscroll(void) {
286 displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
287 command(LCD_ENTRYMODESET | displaymode);
288}
289
290void VikiLCD::command(uint8_t value) {
291 send(value, 0);
292}
293
294void VikiLCD::write(char value) {
295 send(value, 1);
296}
297
298// Allows to set the backlight, if the LCD backpack is used
299void VikiLCD::setBacklight(uint8_t status) {
300 // LED turns on when bit is cleared
301 _backlightBits = M17_BIT_LB|M17_BIT_LG|M17_BIT_LR; // all off
302 if (status & LED_RED) _backlightBits &= ~M17_BIT_LR; // red on
303 if (status & LED_GREEN) _backlightBits &= ~M17_BIT_LG; // green on
304 if (status & LED_BLUE) _backlightBits &= ~M17_BIT_LB; // blue on
305
306 burstBits16(_backlightBits);
307}
308
309// write either command or data, burst it to the expander over I2C.
310void VikiLCD::send(uint8_t value, uint8_t mode) {
311#ifdef USE_FASTMODE
312 // polls for ready. not sure on I2C this is any faster
313
314 // set Data pins as input
315 char data[2];
316 data[0]= MCP23017_IODIRB;
317 data[1]= 0x1E;
318 i2c->write(this->i2c_address, data, 2);
319 uint8_t b= _backlightBits >> 8;
320 burstBits8b((M17_BIT_RW>>8)|b); // RW hi,RS lo
321 char busy;
322 data[0] = MCP23017_GPIOB;
323 do {
324 burstBits8b(((M17_BIT_RW|M17_BIT_EN)>>8)|b); // EN hi
325 i2c->write(this->i2c_address, data, 1);
326 i2c->read(this->i2c_address, &busy, 1); // Read D7
327 burstBits8b((M17_BIT_RW>>8)|b); // EN lo
328 burstBits8b(((M17_BIT_RW|M17_BIT_EN)>>8)|b); // EN hi
329 burstBits8b((M17_BIT_RW>>8)|b); // EN lo
330 } while ((busy&(M17_BIT_D7>>8)) != 0);
331
332 // reset data bits as output
333 data[0]= MCP23017_IODIRB;
334 data[1]= 0x00;
335 i2c->write(this->i2c_address, data, 2);
336 burstBits8b(b); // RW lo
337
338#else
339// wait_us(320);
340#endif
341
342 // BURST SPEED, OH MY GOD
343 // the (now High Speed!) I/O expander pinout
344 // B7 B6 B5 B4 B3 B2 B1 B0 A7 A6 A5 A4 A3 A2 A1 A0 - MCP23017
345 // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
346 // RS RW EN D4 D5 D6 D7 B G R B4 B3 B2 B1 B0
347
348 // n.b. RW bit stays LOW to write
349 uint8_t buf = _backlightBits >> 8;
350 // send high 4 bits
351 if (value & 0x10) buf |= M17_BIT_D4 >> 8;
352 if (value & 0x20) buf |= M17_BIT_D5 >> 8;
353 if (value & 0x40) buf |= M17_BIT_D6 >> 8;
354 if (value & 0x80) buf |= M17_BIT_D7 >> 8;
355
356 if (mode) buf |= (M17_BIT_RS|M17_BIT_EN) >> 8; // RS+EN
357 else buf |= M17_BIT_EN >> 8; // EN
358
359 burstBits8b(buf);
360
361 // resend w/ EN turned off
362 buf &= ~(M17_BIT_EN >> 8);
363 burstBits8b(buf);
364
365 // send low 4 bits
366 buf = _backlightBits >> 8;
367 // send high 4 bits
368 if (value & 0x01) buf |= M17_BIT_D4 >> 8;
369 if (value & 0x02) buf |= M17_BIT_D5 >> 8;
370 if (value & 0x04) buf |= M17_BIT_D6 >> 8;
371 if (value & 0x08) buf |= M17_BIT_D7 >> 8;
372
373 if (mode) buf |= (M17_BIT_RS|M17_BIT_EN) >> 8; // RS+EN
374 else buf |= M17_BIT_EN >> 8; // EN
375
376 burstBits8b(buf);
377
378 // resend w/ EN turned off
379 buf &= ~(M17_BIT_EN >> 8);
380 burstBits8b(buf);
381}
382
383// We pause the system
384uint32_t VikiLCD::on_pause_release(uint32_t dummy){
385 if(!paused) {
386 THEKERNEL->pauser->take();
387 paused= true;
388 }else{
389 THEKERNEL->pauser->release();
390 paused= false;
391 }
392 return 0;
393}