Commit | Line | Data |
---|---|---|
35089dc7 JM |
1 | /* |
2 | This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl). | |
3 | Smoothie 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. | |
4 | Smoothie 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. | |
5 | You 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 | ||
37 | VikiLCD::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 | ||
63 | VikiLCD::~VikiLCD() { | |
64 | delete this->i2c; | |
65 | } | |
66 | ||
67 | ||
68 | void 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. | |
140 | void 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 | |
148 | void 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) | |
157 | void 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 | ||
185 | uint8_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 | ||
197 | int 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 | ||
205 | void 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 | ||
213 | void 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 | ||
221 | void 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) | |
229 | void VikiLCD::noDisplay() { | |
230 | displaycontrol &= ~LCD_DISPLAYON; | |
231 | command(LCD_DISPLAYCONTROL | displaycontrol); | |
232 | } | |
233 | void VikiLCD::display() { | |
234 | displaycontrol |= LCD_DISPLAYON; | |
235 | command(LCD_DISPLAYCONTROL | displaycontrol); | |
236 | } | |
237 | ||
238 | // Turns the underline cursor on/off | |
239 | void VikiLCD::noCursor() { | |
240 | displaycontrol &= ~LCD_CURSORON; | |
241 | command(LCD_DISPLAYCONTROL | displaycontrol); | |
242 | } | |
243 | void VikiLCD::cursor() { | |
244 | displaycontrol |= LCD_CURSORON; | |
245 | command(LCD_DISPLAYCONTROL | displaycontrol); | |
246 | } | |
247 | ||
248 | // Turn on and off the blinking cursor | |
249 | void VikiLCD::noBlink() { | |
250 | displaycontrol &= ~LCD_BLINKON; | |
251 | command(LCD_DISPLAYCONTROL | displaycontrol); | |
252 | } | |
253 | void VikiLCD::blink() { | |
254 | displaycontrol |= LCD_BLINKON; | |
255 | command(LCD_DISPLAYCONTROL | displaycontrol); | |
256 | } | |
257 | ||
258 | // These commands scroll the display without changing the RAM | |
259 | void VikiLCD::scrollDisplayLeft(void) { | |
260 | command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT); | |
261 | } | |
262 | void VikiLCD::scrollDisplayRight(void) { | |
263 | command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT); | |
264 | } | |
265 | ||
266 | // This is for text that flows Left to Right | |
267 | void VikiLCD::leftToRight(void) { | |
268 | displaymode |= LCD_ENTRYLEFT; | |
269 | command(LCD_ENTRYMODESET | displaymode); | |
270 | } | |
271 | ||
272 | // This is for text that flows Right to Left | |
273 | void VikiLCD::rightToLeft(void) { | |
274 | displaymode &= ~LCD_ENTRYLEFT; | |
275 | command(LCD_ENTRYMODESET | displaymode); | |
276 | } | |
277 | ||
278 | // This will 'right justify' text from the cursor | |
279 | void VikiLCD::autoscroll(void) { | |
280 | displaymode |= LCD_ENTRYSHIFTINCREMENT; | |
281 | command(LCD_ENTRYMODESET | displaymode); | |
282 | } | |
283 | ||
284 | // This will 'left justify' text from the cursor | |
285 | void VikiLCD::noAutoscroll(void) { | |
286 | displaymode &= ~LCD_ENTRYSHIFTINCREMENT; | |
287 | command(LCD_ENTRYMODESET | displaymode); | |
288 | } | |
289 | ||
290 | void VikiLCD::command(uint8_t value) { | |
291 | send(value, 0); | |
292 | } | |
293 | ||
294 | void VikiLCD::write(char value) { | |
295 | send(value, 1); | |
296 | } | |
297 | ||
298 | // Allows to set the backlight, if the LCD backpack is used | |
299 | void 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. | |
310 | void 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 | |
384 | uint32_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 | } |