This is a port of Henning Kralsen's UTFT library for Arduino/chipKIT to mbed, refactored to make full use of C++ inheritance and access control, in order to reduce work when implementing new drivers and at the same time make the code more readable and easier to maintain. As of now supported are SSD1289 (16-bit interface), HX8340-B (serial interface) and ST7735 (serial interface). Drivers for other controllers will be added as time and resources to acquire the displays to test the code permit.
Dependents: test SDCard capstone_display capstone_display_2 ... more
lcd_base.cpp
00001 /* 00002 * Copyright (C)2010-2012 Henning Karlsen. All right reserved. 00003 * Copyright (C)2012 Todor Todorov. 00004 * 00005 * This library is free software; you can redistribute it and/or 00006 * modify it under the terms of the GNU Lesser General Public 00007 * License as published by the Free Software Foundation; either 00008 * version 2.1 of the License, or (at your option) any later version. 00009 * 00010 * This library is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 * Lesser General Public License for more details. 00014 * 00015 * You should have received a copy of the GNU Lesser General Public 00016 * License along with this library; if not, write to: 00017 * 00018 * Free Software Foundation, Inc. 00019 * 51 Franklin St, 5th Floor, Boston, MA 02110-1301, USA 00020 * 00021 *********************************************************************/ 00022 #include "lcd_base.h" 00023 #include "helpers.h" 00024 00025 LCD::LCD( unsigned short width, unsigned short height ,PinName CS, PinName RS, PinName RESET, PinName BL, backlight_t blType, float defaultBacklight ) 00026 : _disp_width( width ), _disp_height( height ), _lcd_pin_cs( CS ), _lcd_pin_rs( RS ), _lcd_pin_reset( RESET ), _bl_type( blType ) 00027 { 00028 SetForeground(); 00029 SetBackground(); 00030 _font = &TerminusFont; 00031 if ( defaultBacklight < 0 ) _bl_pwm_default = 0; 00032 else if ( defaultBacklight > 1.0 ) _bl_pwm_default = 1.0; 00033 else _bl_pwm_default = defaultBacklight; 00034 if ( BL != NC ) 00035 { 00036 if ( blType == Constant ) 00037 { 00038 _bl_pwm = 0; 00039 _lcd_pin_bl = new DigitalOut( BL ); 00040 } 00041 else 00042 { 00043 _lcd_pin_bl = 0; 00044 _bl_pwm = new PwmOut( BL ); 00045 _bl_pwm->period_ms( 8.33 ); // 120 Hz 00046 _bl_pwm_current = _bl_pwm_default; 00047 // initially off 00048 *_bl_pwm = 0; 00049 } 00050 00051 } 00052 else 00053 { 00054 _lcd_pin_bl = 0; 00055 _bl_pwm = 0; 00056 } 00057 } 00058 00059 void LCD::Sleep( void ) 00060 { 00061 if ( _lcd_pin_bl != 0 ) 00062 *_lcd_pin_bl = LOW; 00063 else if ( _bl_pwm != 0 ) 00064 *_bl_pwm = 0; 00065 } 00066 00067 void LCD::WakeUp( void ) 00068 { 00069 if ( _lcd_pin_bl != 0 ) 00070 *_lcd_pin_bl = HIGH; 00071 else if ( _bl_pwm != 0 ) 00072 *_bl_pwm = _bl_pwm_current; 00073 } 00074 00075 inline 00076 void LCD::SetForeground( unsigned int color ) 00077 { 00078 _foreground = color; 00079 } 00080 00081 inline 00082 void LCD::SetBackground( unsigned int color ) 00083 { 00084 _background = color; 00085 } 00086 00087 void LCD::SetFont( const font_t *font ) 00088 { 00089 _font = font; 00090 } 00091 00092 inline 00093 unsigned short LCD::GetWidth( void ) 00094 { 00095 if ( _orientation == LANDSCAPE || _orientation == LANDSCAPE_REV ) return _disp_height; 00096 return _disp_width; 00097 } 00098 00099 inline 00100 unsigned short LCD::GetHeight( void ) 00101 { 00102 if ( _orientation == LANDSCAPE || _orientation == LANDSCAPE_REV ) return _disp_width; 00103 return _disp_height; 00104 } 00105 00106 inline 00107 uint8_t LCD::GetFontWidth( void ) 00108 { 00109 if ( _font != 0 ) return _font->Width; 00110 return 0; 00111 } 00112 00113 inline 00114 uint8_t LCD::GetFontHeight( void ) 00115 { 00116 if ( _font != 0 ) return _font->Height; 00117 return 0; 00118 } 00119 00120 void LCD::SetBacklightLevel( float level ) 00121 { 00122 switch ( _bl_type ) 00123 { 00124 case Direct: 00125 if ( _bl_pwm != 0 ) 00126 { 00127 *_bl_pwm = level; 00128 _bl_pwm_current = level; 00129 } 00130 break; 00131 00132 case Indirect: 00133 break; 00134 case Constant: 00135 default: 00136 break; 00137 } 00138 } 00139 00140 void LCD::FillScreen( int color ) 00141 { 00142 unsigned int rgb = color == -1 ? _background : color == -2 ? _foreground : ( unsigned int ) color; 00143 Activate(); 00144 ClearXY(); 00145 for ( int i = 0; i < ( ( _disp_width ) * ( _disp_height ) ); i++ ) 00146 SetPixelColor( rgb ); 00147 Deactivate(); 00148 } 00149 00150 inline 00151 void LCD::ClearScreen( void ) 00152 { 00153 FillScreen( -1 ); 00154 } 00155 00156 void LCD::DrawPixel( unsigned short x, unsigned short y, int color ) 00157 { 00158 Activate(); 00159 SetXY( x, y, x, y ); 00160 SetPixelColor( color == -1 ? _background : 00161 color == -2 ? _foreground : color ); 00162 Deactivate(); 00163 } 00164 00165 void LCD::DrawLine( unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, int color ) 00166 { 00167 00168 double delta, tx, ty; 00169 00170 if ( ( ( x2 - x1 ) < 0 ) ) 00171 { 00172 swap( ushort, x1, x2 ) 00173 swap( ushort, y1, y2 ) 00174 } 00175 if ( ( ( y2 - y1 ) < 0 ) ) 00176 { 00177 swap( ushort, x1, x2 ) 00178 swap( ushort, y1, y2 ) 00179 } 00180 00181 if ( y1 == y2 ) 00182 { 00183 if ( x1 > x2 ) 00184 swap( ushort, x1, x2 ) 00185 DrawHLine( x1, y1, x2 - x1, color ); 00186 } 00187 else if ( x1 == x2 ) 00188 { 00189 if ( y1 > y2 ) 00190 swap( ushort, y1, y2 ) 00191 DrawVLine( x1, y1, y2 - y1, color ); 00192 } 00193 else 00194 { 00195 unsigned int usedColor = color == -1 ? _background : color == -2 ? _foreground : ( unsigned int ) color; 00196 Activate(); 00197 if ( abs( x2 - x1 ) > abs( y2 - y1 ) ) 00198 { 00199 delta = ( double( y2 - y1 ) / double( x2 - x1 ) ); 00200 ty = double( y1 ); 00201 if ( x1 > x2 ) 00202 { 00203 for ( int i = x1; i >= x2; i-- ) 00204 { 00205 SetXY( i, int( ty + 0.5 ), i, int( ty + 0.5 ) ); 00206 SetPixelColor( usedColor ); 00207 ty = ty - delta; 00208 } 00209 } 00210 else 00211 { 00212 for ( int i = x1; i <= x2; i++ ) 00213 { 00214 SetXY( i, int( ty + 0.5 ), i, int( ty + 0.5 ) ); 00215 SetPixelColor( usedColor ); 00216 ty = ty + delta; 00217 } 00218 } 00219 } 00220 else 00221 { 00222 delta = ( float( x2 - x1 ) / float( y2 - y1 ) ); 00223 tx = float( x1 ); 00224 if ( y1 > y2 ) 00225 { 00226 for ( int i = y2 + 1; i > y1; i-- ) 00227 { 00228 SetXY( int( tx + 0.5 ), i, int( tx + 0.5 ), i ); 00229 SetPixelColor( usedColor ); 00230 tx = tx + delta; 00231 } 00232 } 00233 else 00234 { 00235 for ( int i = y1; i < y2 + 1; i++ ) 00236 { 00237 SetXY( int( tx + 0.5 ), i, int( tx + 0.5 ), i ); 00238 SetPixelColor( usedColor ); 00239 tx = tx + delta; 00240 } 00241 } 00242 } 00243 Deactivate(); 00244 } 00245 } 00246 00247 void LCD::DrawRect( unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, int color ) 00248 { 00249 if ( x1 > x2 ) swap( ushort, x1, x2 ) 00250 if ( y1 > y2 ) swap( ushort, y1, y2 ) 00251 00252 DrawHLine( x1, y1, x2 - x1, color ); 00253 DrawHLine( x1, y2, x2 - x1, color ); 00254 DrawVLine( x1, y1, y2 - y1, color ); 00255 DrawVLine( x2, y1, y2 - y1, color ); 00256 } 00257 00258 void LCD::DrawRoundRect( unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, int color ) 00259 { 00260 if ( x1 > x2 ) swap( ushort, x1, x2 ) 00261 if ( y1 > y2 ) swap( ushort, y1, y2 ) 00262 00263 if ( ( x2 - x1 ) > 4 && ( y2 - y1 ) > 4 ) 00264 { 00265 DrawPixel( x1 + 1, y1 + 1, color ); 00266 DrawPixel( x2 - 1, y1 + 1, color ); 00267 DrawPixel( x1 + 1, y2 - 1, color ); 00268 DrawPixel( x2 - 1, y2 - 1, color ); 00269 DrawHLine( x1 + 2, y1, x2 - x1 - 4, color ); 00270 DrawHLine( x1 + 2, y2, x2 - x1 - 4, color ); 00271 DrawVLine( x1, y1 + 2, y2 - y1 - 4, color ); 00272 DrawVLine( x2, y1 + 2, y2 - y1 - 4, color ); 00273 } 00274 } 00275 00276 void LCD::FillRect( unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, int color ) 00277 { 00278 if ( x1 > x2 ) swap( ushort, x1, x2 ); 00279 if ( y1 > y2 ) swap( ushort, y1, y2 ); 00280 00281 for ( int i = 0; i < ( ( y2 - y1 ) / 2 ) + 1; i++ ) 00282 { 00283 DrawHLine( x1, y1 + i, x2 - x1, color ); 00284 DrawHLine( x1, y2 - i, x2 - x1, color ); 00285 } 00286 } 00287 00288 void LCD::FillRoundRect( unsigned short x1, unsigned short y1, unsigned short x2, unsigned short y2, int color ) 00289 { 00290 if ( x1 > x2 ) swap( ushort, x1, x2 ) 00291 if ( y1 > y2 ) swap( ushort, y1, y2 ) 00292 00293 if ( ( x2 - x1 ) > 4 && ( y2 - y1 ) > 4 ) 00294 { 00295 for ( int i = 0; i < ( ( y2 - y1 ) / 2 ) + 1; i++ ) 00296 { 00297 switch ( i ) 00298 { 00299 case 0: 00300 DrawHLine( x1 + 2, y1 + i, x2 - x1 - 4, color ); 00301 DrawHLine( x1 + 2, y2 - i, x2 - x1 - 4, color ); 00302 break; 00303 00304 case 1: 00305 DrawHLine( x1 + 1, y1 + i, x2 - x1 - 2, color ); 00306 DrawHLine( x1 + 1, y2 - i, x2 - x1 - 2, color ); 00307 break; 00308 00309 default: 00310 DrawHLine( x1, y1 + i, x2 - x1, color ); 00311 DrawHLine( x1, y2 - i, x2 - x1, color ); 00312 break; 00313 } 00314 } 00315 } 00316 } 00317 00318 void LCD::DrawCircle( unsigned short x, unsigned short y, unsigned short radius, int color ) 00319 { 00320 int f = 1 - radius; 00321 int ddF_x = 1; 00322 int ddF_y = -2 * radius; 00323 int x1 = 0; 00324 int y1 = radius; 00325 unsigned int usedColor = color == -1 ? _background : color == -2 ? _foreground : ( unsigned int ) color; 00326 00327 Activate(); 00328 SetXY( x, y + radius, x, y + radius ); 00329 SetPixelColor( usedColor ); 00330 SetXY( x, y - radius, x, y - radius ); 00331 SetPixelColor( usedColor ); 00332 SetXY( x + radius, y, x + radius, y ); 00333 SetPixelColor( usedColor ); 00334 SetXY( x - radius, y, x - radius, y ); 00335 SetPixelColor( usedColor ); 00336 00337 while ( x1 < y1 ) 00338 { 00339 if ( f >= 0 ) 00340 { 00341 y1--; 00342 ddF_y += 2; 00343 f += ddF_y; 00344 } 00345 x1++; 00346 ddF_x += 2; 00347 f += ddF_x; 00348 SetXY( x + x1, y + y1, x + x1, y + y1 ); 00349 SetPixelColor( usedColor ); 00350 SetXY( x - x1, y + y1, x - x1, y + y1 ); 00351 SetPixelColor( usedColor ); 00352 SetXY( x + x1, y - y1, x + x1, y - y1 ); 00353 SetPixelColor( usedColor ); 00354 SetXY( x - x1, y - y1, x - x1, y - y1 ); 00355 SetPixelColor( usedColor ); 00356 SetXY( x + y1, y + x1, x + y1, y + x1 ); 00357 SetPixelColor( usedColor ); 00358 SetXY( x - y1, y + x1, x - y1, y + x1 ); 00359 SetPixelColor( usedColor ); 00360 SetXY( x + y1, y - x1, x + y1, y - x1 ); 00361 SetPixelColor( usedColor ); 00362 SetXY( x - y1, y - x1, x - y1, y - x1 ); 00363 SetPixelColor( usedColor ); 00364 } 00365 Deactivate(); 00366 } 00367 00368 void LCD::FillCircle( unsigned short x, unsigned short y, unsigned short radius, int color ) 00369 { 00370 unsigned int usedColor = color == -1 ? _background : color == -2 ? _foreground : ( unsigned int ) color; 00371 Activate(); 00372 for ( int y1 = -radius; y1 <= radius; y1++ ) 00373 for ( int x1 = -radius; x1 <= radius; x1++ ) 00374 if ( x1 * x1 + y1 * y1 <= radius * radius ) 00375 { 00376 SetXY( x + x1, y + y1, x + x1, y + y1 ); 00377 SetPixelColor( usedColor ); 00378 } 00379 Deactivate(); 00380 } 00381 00382 void LCD::Print( const char *str, unsigned short x, unsigned short y, int fgColor, int bgColor, unsigned short deg ) 00383 { 00384 int stl, i; 00385 00386 stl = strlen( str ); 00387 00388 if ( x == RIGHT ) 00389 x = GetWidth() - ( stl * _font->Width ); 00390 if ( x == CENTER ) 00391 x = ( GetWidth() - ( stl * _font->Width ) ) / 2; 00392 00393 for ( i = 0; i < stl; i++ ) 00394 if ( deg == 0 ) 00395 PrintChar( *str++, x + ( i * ( _font->Width ) ), y, fgColor, bgColor ); 00396 else 00397 RotateChar( *str++, x, y, i, fgColor, bgColor, deg ); 00398 } 00399 00400 void LCD::DrawBitmap( unsigned short x, unsigned short y, const bitmap_t* img, unsigned char scale ) 00401 { 00402 int tx, ty, tc, tsx, tsy; 00403 00404 Activate(); 00405 if ( scale == 1 ) 00406 { 00407 SetXY( x, y, x + img->Width - 1, y + img->Height - 1 ); 00408 00409 if ( img->Format == RGB16 ) 00410 { 00411 const unsigned short *pixel = ( const unsigned short* ) img->PixelData; 00412 for ( tc = 0; tc < ( img->Width * img->Height ); tc++ ) 00413 SetPixelColor( *pixel++, img->Format ); 00414 } 00415 else if ( img->Format == RGB18 ) 00416 { 00417 const unsigned int *pixel = ( const unsigned int* ) img->PixelData; 00418 for ( tc = 0; tc < ( img->Width * img->Height ); tc++ ) 00419 SetPixelColor( *pixel++, img->Format ); 00420 } 00421 } 00422 else 00423 { 00424 if ( img->Format == RGB16 ) 00425 { 00426 const unsigned short *pixel = ( const unsigned short* ) img->PixelData; 00427 00428 for ( ty = 0; ty < img->Height; ty++ ) 00429 { 00430 SetXY( x, y + ( ty * scale ), x + ( ( img->Width * scale ) - 1 ), y + ( ty * scale ) + scale ); 00431 for ( tsy = 0; tsy < scale; tsy++ ) 00432 { 00433 for ( tx = 0; tx < img->Width; tx++ ) 00434 { 00435 for ( tsx = 0; tsx < scale; tsx++ ) 00436 SetPixelColor( pixel[ ( ty * img->Width ) + tx ], img->Format ); 00437 } 00438 } 00439 } 00440 } 00441 else if ( img->Format == RGB18 ) 00442 { 00443 const unsigned int *pixel = ( const unsigned int* ) img->PixelData; 00444 00445 for ( ty = 0; ty < img->Height; ty++ ) 00446 { 00447 SetXY( x, y + ( ty * scale ), x + ( ( img->Width * scale ) - 1 ), y + ( ty * scale ) + scale ); 00448 for ( tsy = 0; tsy < scale; tsy++ ) 00449 { 00450 for ( tx = 0; tx < img->Width; tx++ ) 00451 { 00452 for ( tsx = 0; tsx < scale; tsx++ ) 00453 SetPixelColor( pixel[ ( ty * img->Width ) + tx ], img->Format ); 00454 } 00455 } 00456 } 00457 } 00458 } 00459 Deactivate(); 00460 } 00461 00462 void LCD::DrawBitmap( unsigned short x, unsigned short y, const bitmap_t* img, unsigned short deg, unsigned short rox, unsigned short roy ) 00463 { 00464 int tx, ty, newx, newy; 00465 double radian; 00466 radian = deg * 0.0175; 00467 00468 if ( deg == 0 ) 00469 DrawBitmap( x, y, img ); 00470 else 00471 { 00472 Activate(); 00473 00474 if ( img->Format == RGB16 ) 00475 { 00476 const unsigned short *pixel = ( const unsigned short* ) img->PixelData; 00477 00478 for ( ty = 0; ty < img->Height; ty++ ) 00479 for ( tx = 0; tx < img->Width; tx++ ) 00480 { 00481 newx = x + rox + ( ( ( tx - rox ) * cos( radian ) ) - ( ( ty - roy ) * sin( radian ) ) ); 00482 newy = y + roy + ( ( ( ty - roy ) * cos( radian ) ) + ( ( tx - rox ) * sin( radian ) ) ); 00483 00484 SetXY( newx, newy, newx, newy ); 00485 SetPixelColor( pixel[ ( ty * img->Width ) + tx ], img->Format ); 00486 } 00487 } 00488 else if ( img->Format == RGB18 ) 00489 { 00490 const unsigned int *pixel = ( const unsigned int* ) img->PixelData; 00491 00492 for ( ty = 0; ty < img->Height; ty++ ) 00493 for ( tx = 0; tx < img->Width; tx++ ) 00494 { 00495 newx = x + rox + ( ( ( tx - rox ) * cos( radian ) ) - ( ( ty - roy ) * sin( radian ) ) ); 00496 newy = y + roy + ( ( ( ty - roy ) * cos( radian ) ) + ( ( tx - rox ) * sin( radian ) ) ); 00497 00498 SetXY( newx, newy, newx, newy ); 00499 SetPixelColor( pixel[ ( ty * img->Width ) + tx ], img->Format ); 00500 } 00501 } 00502 Deactivate(); 00503 } 00504 } 00505 00506 inline 00507 void LCD::Activate( void ) 00508 { 00509 _lcd_pin_cs = LOW; 00510 } 00511 00512 inline 00513 void LCD::Deactivate( void ) 00514 { 00515 _lcd_pin_cs = HIGH; 00516 } 00517 00518 inline 00519 void LCD::WriteCmdData( unsigned short cmd, unsigned short data ) 00520 { 00521 WriteCmd( cmd ); 00522 WriteData( data ); 00523 } 00524 00525 inline 00526 void LCD::ClearXY( void ) 00527 { 00528 SetXY( 0, 0, GetWidth() - 1, GetHeight() - 1 ); 00529 } 00530 00531 void LCD::DrawHLine( unsigned short x, unsigned short y, unsigned short len, int color ) 00532 { 00533 unsigned int usedColor = color == -1 ? _background : color == -2 ? _foreground : ( unsigned int ) color; 00534 00535 Activate(); 00536 SetXY( x, y, x + len, y ); 00537 for ( int i = 0; i < len + 1; i++ ) 00538 SetPixelColor( usedColor ); 00539 Deactivate(); 00540 } 00541 00542 void LCD::DrawVLine( unsigned short x, unsigned short y, unsigned short len, int color ) 00543 { 00544 unsigned int usedColor = color == -1 ? _background : color == -2 ? _foreground : ( unsigned int ) color; 00545 00546 Activate(); 00547 SetXY( x, y, x, y + len ); 00548 for ( int i = 0; i < len; i++ ) 00549 SetPixelColor( usedColor ); 00550 Deactivate(); 00551 } 00552 00553 void LCD::PrintChar( char c, unsigned short x, unsigned short y, int fgColor, int bgColor ) 00554 { 00555 uint8_t i, ch; 00556 uint16_t j; 00557 unsigned int usedColorFG = fgColor == -1 ? _background : fgColor == -2 ? _foreground : ( unsigned int ) fgColor; 00558 unsigned int usedColorBG = bgColor == -1 ? _background : bgColor == -2 ? _foreground : ( unsigned int ) bgColor; 00559 00560 uint16_t totalCharBytes = ( _font->Width * _font->Height ) / 8; 00561 int16_t position = _font->Position[ c - _font->Offset ]; 00562 if ( position == -1 ) position = 0; // will print space character 00563 00564 Activate(); 00565 00566 SetXY( x, y, x + _font->Width - 1, y + _font->Height - 1 ); 00567 00568 for ( j = 0; j < totalCharBytes; j++ ) 00569 { 00570 ch = _font->Data[ position ]; 00571 for ( i = 0; i < 8; i++ ) 00572 { 00573 if ( ( ch & ( 1 << ( 7 - i ) ) ) != 0 ) SetPixelColor( usedColorFG ); 00574 else SetPixelColor( usedColorBG ); 00575 } 00576 position++; 00577 } 00578 Deactivate(); 00579 } 00580 00581 void LCD::RotateChar( char c, unsigned short x, unsigned short y, int pos, int fgColor, int bgColor, unsigned short deg ) 00582 { 00583 uint8_t i, j, ch; 00584 int newx, newy; 00585 double radian; 00586 radian = deg * 0.0175; 00587 00588 unsigned int usedColorFG = fgColor == -1 ? _background : fgColor == -2 ? _foreground : ( unsigned int ) fgColor; 00589 unsigned int usedColorBG = bgColor == -1 ? _background : bgColor == -2 ? _foreground : ( unsigned int ) bgColor; 00590 00591 int16_t position = _font->Position[ c - _font->Offset ]; 00592 if ( position == -1 ) position = 0; // will print space character 00593 00594 Activate(); 00595 00596 for ( j = 0; j < _font->Height; j++ ) 00597 { 00598 for ( uint16_t zz = 0; zz < ( ( double ) _font->Width / 8 ); zz++ ) 00599 { 00600 ch = _font->Data[ position + zz ]; 00601 for ( i = 0; i < 8; i++ ) 00602 { 00603 newx = x + ( ( ( i + ( zz * 8 ) + ( pos * _font->Width ) ) * cos( radian ) ) - ( ( j ) * sin( radian ) ) ); 00604 newy = y + ( ( ( j ) * cos( radian ) ) + ( ( i + ( zz * 8 ) + ( pos * _font->Width ) ) * sin( radian ) ) ); 00605 00606 SetXY( newx, newy, newx + 1, newy + 1 ); 00607 00608 if ( ( ch & ( 1 << ( 7 - i ) ) ) != 0 ) SetPixelColor( usedColorFG ); 00609 else SetPixelColor( usedColorBG ); 00610 } 00611 } 00612 position += ( _font->Width / 8 ); 00613 } 00614 00615 Deactivate(); 00616 }
Generated on Thu Jul 14 2022 02:02:54 by 1.7.2