Real-time 3D on an ATmega328

Recently I bought an Arduino Nano for less than € 5.- and a really tiny OLED display.
I wondered if you could display real-time 3D graphics on the OLED display and if so, how fast would it be.
After some tests the result was surprisingly good that means by far not that bad as expected.

Generate your own sketch

Drag and drop your own 3D model in the area below and copy & paste the generated sketch to your IDE.

Be careful which 3D model you use e.g. if you run the code on an Arduino Nano there is limited memory. The Arduino Nano has 2kb of sram and 30kb of flash memory.

sketch succesfull generated
save sketch
wireframe.ino
//**************************************************************************** // // sketch displaying an animated wire frame representation of a // 3d model on a mini oled screen ( SSDI1306 ) via i2c. // hidden surface removal is used; non-visible polgons are not displayed. // code is made for arduino nano - 2kb(!) ram & 30kb sram - just for curiousity // purpose; is it possible to display 3d graphics and if so how fast // will it run. // // 2018 / version 0.02 // arjan westerdiep / www.drububu.com // // generate an arduino sketch with your own 3D model at drububu.com // //**************************************************************************** #include <Wire.h> #include <avr/pgmspace.h> //**************************************************************************** // slave address // find your device at http://playground.arduino.cc/Main/I2cScanner //**************************************************************************** #define SLAVE 0x3C //**************************************************************************** // //**************************************************************************** #define OBJECT_SIZE 140 #define ROTATE_X_AXIS true #define ROTATE_Y_AXIS true #define ROTATE_Z_AXIS true #define SHOW_FRAMES_PER_SECOND true #define PROFILING false //**************************************************************************** // set to true if backside of object is displayed instead of front. //**************************************************************************** #define FLIP_NORMAL true //**************************************************************************** // //**************************************************************************** #define WIDTH 128 #define HEIGHT 64 #define ROWS 8 #define OFFSET_HORIZONTAL 64 #define OFFSET_VERTICAL 32 //**************************************************************************** // //**************************************************************************** #if OBJECT_SIZE >= ( WIDTH - 2 ) || OBJECT_SIZE >= ( HEIGHT - 2 ) #define isMeshInsideScreen false #else #define isMeshInsideScreen true #endif //**************************************************************************** // //**************************************************************************** #if ROTATE_X_AXIS byte _frameXAxis = 0; #endif #if ROTATE_Y_AXIS byte _frameYAxis = 0; #endif #if ROTATE_Z_AXIS byte _frameZAxis = 0; #endif //**************************************************************************** // //**************************************************************************** byte _bitmapData[ WIDTH * ( HEIGHT >> 3 ) ]; byte _lookupMinimumcolumn[ ROWS ]; byte _lookupMaximumcolumn[ ROWS ]; byte _minimumRow = 0; byte _maximumRow = ROWS - 1; word _lookupOledRefresh[ ROWS ]; #if PROFILING unsigned long _timePreprocessing = 0; unsigned long _timeScanConvert = 0; unsigned long _timeRendering = 0; #endif //**************************************************************************** // frames per second code //**************************************************************************** #if SHOW_FRAMES_PER_SECOND #define FPS_ROW_POSITION 7 word _totalOfFrameRates = 0.0; byte _numberOfFrames = 0; unsigned long _time = millis(); //*************************************************************************** // //*************************************************************************** const static byte _lookupFps[ 65 ] PROGMEM = { 14, 17, 17, 14, 0, // 0 0, 17, 31, 16, 0, // 1 29, 21, 21, 23, 0, // 2 21, 21, 21, 31, 0, // 3 12, 10, 9, 31, 0, // 4 23, 21, 21, 29, 0, // 5 31, 21, 21, 29, 0, // 6 1, 25, 5, 3, 0, // 7 31, 21, 21, 31, 0, // 8 23, 21, 21, 31, 0, // 9 31, 5, 5, 5, 0, // F 31, 5, 5, 7, 0, // P 23, 21, 21, 29, 0 // S }; #endif //******************************************************************************* // sine & cosine data //******************************************************************************* const static word _lookupSineCosine[ 256 ] PROGMEM = { 0x7FFE, 0x82FD, 0x85FD, 0x88FD, 0x8BFD, 0x8EFD, 0x91FC, 0x94FC, 0x97FB, 0x9AFA, 0x9DFA, 0xA0F9, 0xA3F8, 0xA6F7, 0xA9F6, 0xACF5, 0xAFF4, 0xB2F3, 0xB5F1, 0xB8F0, 0xBAEF, 0xBDED, 0xC0EB, 0xC2EA, 0xC5E8, 0xC8E6, 0xCAE5, 0xCDE3, 0xCFE1, 0xD1DF, 0xD4DD, 0xD6DA, 0xD8D8, 0xDAD6, 0xDDD4, 0xDFD1, 0xE1CF, 0xE3CD, 0xE5CA, 0xE6C8, 0xE8C5, 0xEAC2, 0xEBC0, 0xEDBD, 0xEFBA, 0xF0B8, 0xF1B5, 0xF3B2, 0xF4AF, 0xF5AC, 0xF6A9, 0xF7A6, 0xF8A3, 0xF9A0, 0xFA9D, 0xFA9A, 0xFB97, 0xFC94, 0xFC91, 0xFD8E, 0xFD8B, 0xFD88, 0xFD85, 0xFD82, 0xFD7F, 0xFD7B, 0xFD78, 0xFD75, 0xFD72, 0xFD6F, 0xFC6C, 0xFC69, 0xFB66, 0xFA63, 0xFA60, 0xF95D, 0xF85A, 0xF757, 0xF654, 0xF551, 0xF44E, 0xF34B, 0xF148, 0xF045, 0xEF43, 0xED40, 0xEB3D, 0xEA3B, 0xE838, 0xE635, 0xE533, 0xE330, 0xE12E, 0xDF2C, 0xDD29, 0xDA27, 0xD825, 0xD623, 0xD420, 0xD11E, 0xCF1C, 0xCD1A, 0xCA18, 0xC817, 0xC515, 0xC213, 0xC012, 0xBD10, 0xBA0E, 0xB80D, 0xB50C, 0xB20A, 0xAF09, 0xAC08, 0xA907, 0xA606, 0xA305, 0xA004, 0x9D03, 0x9A03, 0x9702, 0x9401, 0x9101, 0x8E00, 0x8B00, 0x8800, 0x8500, 0x8200, 0x7F00, 0x7B00, 0x7800, 0x7500, 0x7200, 0x6F00, 0x6C01, 0x6901, 0x6602, 0x6303, 0x6003, 0x5D04, 0x5A05, 0x5706, 0x5407, 0x5108, 0x4E09, 0x4B0A, 0x480C, 0x450D, 0x430E, 0x4010, 0x3D12, 0x3B13, 0x3815, 0x3517, 0x3318, 0x301A, 0x2E1C, 0x2C1E, 0x2920, 0x2723, 0x2525, 0x2327, 0x2029, 0x1E2C, 0x1C2E, 0x1A30, 0x1833, 0x1735, 0x1538, 0x133B, 0x123D, 0x1040, 0x0E43, 0x0D45, 0x0C48, 0x0A4B, 0x094E, 0x0851, 0x0754, 0x0657, 0x055A, 0x045D, 0x0360, 0x0363, 0x0266, 0x0169, 0x016C, 0x006F, 0x0072, 0x0075, 0x0078, 0x007B, 0x007E, 0x0082, 0x0085, 0x0088, 0x008B, 0x008E, 0x0191, 0x0194, 0x0297, 0x039A, 0x039D, 0x04A0, 0x05A3, 0x06A6, 0x07A9, 0x08AC, 0x09AF, 0x0AB2, 0x0CB5, 0x0DB8, 0x0EBA, 0x10BD, 0x12C0, 0x13C2, 0x15C5, 0x17C8, 0x18CA, 0x1ACD, 0x1CCF, 0x1ED1, 0x20D4, 0x23D6, 0x25D8, 0x27DA, 0x29DD, 0x2CDF, 0x2EE1, 0x30E3, 0x33E5, 0x35E6, 0x38E8, 0x3BEA, 0x3DEB, 0x40ED, 0x43EF, 0x45F0, 0x48F1, 0x4BF3, 0x4EF4, 0x51F5, 0x54F6, 0x57F7, 0x5AF8, 0x5DF9, 0x60FA, 0x63FA, 0x66FB, 0x69FC, 0x6CFC, 0x6FFD, 0x72FD, 0x75FD, 0x78FD, 0x7BFD }; //**************************************************************************** // //**************************************************************************** #define NUMBER_OF_VERTEX_INDICES 660 #define NUMBER_OF_POLYGON_INDICES 350 //**************************************************************************** // lookup entries unique segments ( = bitfield ( word ( 2 bytes ) ) ) // maximum 97 unique segments per chunck //**************************************************************************** #define LOOKUP_ENTRIES_UNIQUE_SEGMENTS 7 //**************************************************************************** // lookup entries transformed vertices ( = bitfield ( word ( 2 bytes ) ) ) // maximum 95 unique vertices per chunck //**************************************************************************** #define LOOKUP_ENTRIES_TRANSFORMED_VERTICES 6 word _lookupTransformedVerticesBitfield[ LOOKUP_ENTRIES_TRANSFORMED_VERTICES ]; char _lookupTransformedVertices[ 190 ]; //**************************************************************************** // lookup vertex ( & normal ) data //**************************************************************************** const static word _lookupVertices[ 660 ] PROGMEM = { 0x0033, 0x7F7F, 0x0000, 0x1B76, 0x007F, 0x2375, 0x007F, 0x2171, 0x007F, 0x1E6E, 0x007F, 0x196C, 0x007F, 0x146B, 0x007F, 0x116C, 0x007F, 0x0E6D, 0x007F, 0x0C6E, 0x007F, 0x0870, 0x007F, 0x0674, 0x007F, 0x047A, 0x007F, 0x047D, 0x007F, 0x0480, 0x007F, 0x0486, 0x007F, 0x058C, 0x007F, 0x0890, 0x007F, 0x0B92, 0x007F, 0x0F94, 0x007F, 0x1495, 0x007F, 0x1695, 0x007F, 0x1B94, 0x007F, 0x1E91, 0x007F, 0x218E, 0x007F, 0x238C, 0x007F, 0x2487, 0x007F, 0x2583, 0x007F, 0x2580, 0x007F, 0x247A, 0x007F, 0x1C78, 0x007F, 0x1C7C, 0x007F, 0x1D81, 0x007F, 0x1C86, 0x007F, 0x1B89, 0x007F, 0x198C, 0x007F, 0x168E, 0x007F, 0x128E, 0x007F, 0x0F8C, 0x007F, 0x0E8B, 0x007F, 0x0D88, 0x007F, 0x0C84, 0x007F, 0x0C80, 0x007F, 0x0C7C, 0x007F, 0x0D78, 0x007F, 0x0E76, 0x007F, 0x0F74, 0x007F, 0x1272, 0x007F, 0x1572, 0x007F, 0x1773, 0x007F, 0x1974, 0x007F, 0x000B, 0x7F7F, 0x0000, 0x476C, 0x007F, 0x406C, 0x007F, 0x4089, 0x007F, 0x326C, 0x007F, 0x296C, 0x007F, 0x2994, 0x007F, 0x3094, 0x007F, 0x3077, 0x007F, 0x3D94, 0x007F, 0x4794, 0x007F, 0x000D, 0x7F7F, 0x0000, 0x4F6C, 0x007F, 0x4F72, 0x007F, 0x5872, 0x007F, 0x588E, 0x007F, 0x4F8E, 0x007F, 0x4F94, 0x007F, 0x6994, 0x007F, 0x698E, 0x007F, 0x608E, 0x007F, 0x6072, 0x007F, 0x6972, 0x007F, 0x696C, 0x007F, 0x0021, 0x7F7F, 0x0000, 0x8778, 0x007F, 0x8794, 0x007F, 0x8E94, 0x007F, 0x8E78, 0x007F, 0x8E76, 0x007F, 0x8E74, 0x007F, 0x8D71, 0x007F, 0x8A6F, 0x007F, 0x876D, 0x007F, 0x836C, 0x007F, 0x806B, 0x007F, 0x7D6C, 0x007F, 0x7A6C, 0x007F, 0x766E, 0x007F, 0x7371, 0x007F, 0x7273, 0x007F, 0x7176, 0x007F, 0x7079, 0x007F, 0x7094, 0x007F, 0x7894, 0x007F, 0x7879, 0x007F, 0x7877, 0x007F, 0x7975, 0x007F, 0x7A73, 0x007F, 0x7C72, 0x007F, 0x7E72, 0x007F, 0x8072, 0x007F, 0x8272, 0x007F, 0x8373, 0x007F, 0x8574, 0x007F, 0x8675, 0x007F, 0x8677, 0x007F, 0x002E, 0x7F7F, 0x0000, 0x9E6E, 0x007F, 0x9F75, 0x007F, 0xA273, 0x007F, 0xA573, 0x007F, 0xA772, 0x007F, 0xAA72, 0x007F, 0xAA8E, 0x007F, 0xA68E, 0x007F, 0xA38D, 0x007F, 0xA08D, 0x007F, 0x9E8B, 0x007F, 0x9C88, 0x007F, 0x9C86, 0x007F, 0x9B83, 0x007F, 0x9B80, 0x007F, 0x9B7E, 0x007F, 0x9C7C, 0x007F, 0x9C7A, 0x007F, 0x9D78, 0x007F, 0x9B6F, 0x007F, 0x9970, 0x007F, 0x9773, 0x007F, 0x9575, 0x007F, 0x9478, 0x007F, 0x937B, 0x007F, 0x937E, 0x007F, 0x9381, 0x007F, 0x9383, 0x007F, 0x9385, 0x007F, 0x9488, 0x007F, 0x9489, 0x007F, 0x958B, 0x007F, 0x968E, 0x007F, 0x988F, 0x007F, 0x9991, 0x007F, 0x9C92, 0x007F, 0xA094, 0x007F, 0xA394, 0x007F, 0xA694, 0x007F, 0xB294, 0x007F, 0xB26C, 0x007F, 0xA86C, 0x007F, 0xA56C, 0x007F, 0xA26D, 0x007F, 0xA06D, 0x007F, 0x002E, 0x7F7F, 0x0000, 0xC38C, 0x007F, 0xC28B, 0x007F, 0xBD92, 0x007F, 0xC093, 0x007F, 0xC294, 0x007F, 0xC594, 0x007F, 0xC894, 0x007F, 0xD594, 0x007F, 0xD56C, 0x007F, 0xCD6C, 0x007F, 0xCD7D, 0x007F, 0xCB7D, 0x007F, 0xCA7D, 0x007F, 0xC87C, 0x007F, 0xC67B, 0x007F, 0xC579, 0x007F, 0xC06C, 0x007F, 0xB66C, 0x007F, 0xBE7B, 0x007F, 0xBE7C, 0x007F, 0xC07D, 0x007F, 0xC17E, 0x007F, 0xC27F, 0x007F, 0xC080, 0x007F, 0xBE81, 0x007F, 0xBC82, 0x007F, 0xBB84, 0x007F, 0xBA85, 0x007F, 0xBA87, 0x007F, 0xB989, 0x007F, 0xB98B, 0x007F, 0xBA8C, 0x007F, 0xBA8E, 0x007F, 0xBC90, 0x007F, 0xC288, 0x007F, 0xC286, 0x007F, 0xC385, 0x007F, 0xC484, 0x007F, 0xC683, 0x007F, 0xC883, 0x007F, 0xCD83, 0x007F, 0xCD8E, 0x007F, 0xC88E, 0x007F, 0xC78E, 0x007F, 0xC58D, 0x007F, 0x000C, 0x7F7F, 0x0000, 0xF094, 0x007F, 0xFC6C, 0x007F, 0xF07A, 0x007F, 0xEB8D, 0x007F, 0xE57A, 0x007F, 0xF46C, 0x007F, 0xF274, 0x007F, 0xE474, 0x007F, 0xE16C, 0x007F, 0xD96C, 0x007F, 0xE594, 0x007F, 0x0005, 0x7F7F, 0x00FE, 0x2D86, 0x007F, 0x2D7A, 0x007F, 0x527A, 0x007F, 0x5286, 0x007F, 0x005F, 0x7F7F, 0x00FE, 0xD3B7, 0x007F, 0xCCA3, 0x007F, 0xD79E, 0x007F, 0xE095, 0x007F, 0xE68B, 0x007F, 0xE87E, 0x007F, 0xE672, 0x007F, 0xE068, 0x007F, 0xD75F, 0x007F, 0xCC5A, 0x007F, 0xC458, 0x007F, 0xB958, 0x007F, 0xAF5A, 0x007F, 0xA460, 0x007F, 0x9B68, 0x007F, 0x9373, 0x007F, 0x8B7F, 0x007F, 0x938C, 0x007F, 0x9C96, 0x007F, 0xA69E, 0x007F, 0xB1A3, 0x007F, 0xBFA5, 0x007F, 0xBFBA, 0x007F, 0xB5B9, 0x007F, 0xA2B4, 0x007F, 0x99AF, 0x007F, 0x91A8, 0x007F, 0x89A0, 0x007F, 0x8297, 0x007F, 0x7F93, 0x007F, 0x7B97, 0x007F, 0x74A0, 0x007F, 0x6CA8, 0x007F, 0x64AF, 0x007F, 0x5BB4, 0x007F, 0x48B9, 0x007F, 0x3EBA, 0x007F, 0x2AB7, 0x007F, 0x1AAE, 0x007F, 0x0CA2, 0x007F, 0x0491, 0x007F, 0x007E, 0x007F, 0x046C, 0x007F, 0x0C5B, 0x007F, 0x194F, 0x007F, 0x2A46, 0x007F, 0x3643, 0x007F, 0x4543, 0x007F, 0x5245, 0x007F, 0x5B49, 0x007F, 0x5960, 0x007F, 0x4E5A, 0x007F, 0x4758, 0x007F, 0x3E58, 0x007F, 0x315A, 0x007F, 0x265F, 0x007F, 0x1D68, 0x007F, 0x1772, 0x007F, 0x157E, 0x007F, 0x178B, 0x007F, 0x1D95, 0x007F, 0x269E, 0x007F, 0x31A3, 0x007F, 0x3EA5, 0x007F, 0x4CA3, 0x007F, 0x579E, 0x007F, 0x6196, 0x007F, 0x6A8C, 0x007F, 0x727F, 0x007F, 0x6A73, 0x007F, 0x6268, 0x007F, 0x644E, 0x007F, 0x6C55, 0x007F, 0x745D, 0x007F, 0x7862, 0x007F, 0x7B67, 0x007F, 0x7F6C, 0x007F, 0x8267, 0x007F, 0x8562, 0x007F, 0x895D, 0x007F, 0x9155, 0x007F, 0x984E, 0x007F, 0xA049, 0x007F, 0xA946, 0x007F, 0xB244, 0x007F, 0xBF43, 0x007F, 0xD346, 0x007F, 0xE44F, 0x007F, 0xF15B, 0x007F, 0xF96C, 0x007F, 0xFD7E, 0x007F, 0xF991, 0x007F, 0xF1A2, 0x007F, 0xE3AE, 0x007F, 0x000D, 0x7F7F, 0x00FE, 0xAB86, 0x007F, 0xAB7A, 0x007F, 0xB67A, 0x007F, 0xB66F, 0x007F, 0xC26F, 0x007F, 0xC27A, 0x007F, 0xCD7A, 0x007F, 0xCD86, 0x007F, 0xC286, 0x007F, 0xC292, 0x007F, 0xB692, 0x007F, 0xB686, 0x007F }; //**************************************************************************** // polygon data //**************************************************************************** const static word _lookupPolygons[ 350 ] PROGMEM = { 0x3301, 0x0034, 0x0001, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, 0x0808, 0x0909, 0x0A0A, 0x0B0B, 0x0C0C, 0x0D0D, 0x0E0E, 0x0F0F, 0x1010, 0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616, 0x1717, 0x1818, 0x1919, 0x1A1A, 0x1B1B, 0x1C1C, 0x1D1D, 0x0002, 0x1E01, 0x1F1E, 0x201F, 0x2120, 0x2221, 0x2322, 0x2423, 0x2524, 0x2625, 0x2726, 0x2827, 0x2928, 0x2A29, 0x2B2A, 0x2C2B, 0x2D2C, 0x2E2D, 0x2F2E, 0x302F, 0x3130, 0x3231, 0x3332, 0x0A01, 0x000A, 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, 0x0808, 0x0909, 0x0A0A, 0x0C01, 0x000C, 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, 0x0808, 0x0909, 0x0A0A, 0x0B0B, 0x0C0C, 0x2001, 0x0020, 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, 0x0808, 0x0909, 0x0A0A, 0x0B0B, 0x0C0C, 0x0D0D, 0x0E0E, 0x0F0F, 0x1010, 0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616, 0x1717, 0x1818, 0x1919, 0x1A1A, 0x1B1B, 0x1C1C, 0x1D1D, 0x1E1E, 0x1F1F, 0x2020, 0x2E01, 0x002F, 0x0001, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, 0x0808, 0x0909, 0x0A0A, 0x0B0B, 0x0C0C, 0x0D0D, 0x0E0E, 0x0F0F, 0x1010, 0x1111, 0x1212, 0x1313, 0x0002, 0x1401, 0x1514, 0x1615, 0x1716, 0x1817, 0x1918, 0x1A19, 0x1B1A, 0x1C1B, 0x1D1C, 0x1E1D, 0x1F1E, 0x201F, 0x2120, 0x2221, 0x2322, 0x2423, 0x2524, 0x2625, 0x2726, 0x2827, 0x2928, 0x2A29, 0x2B2A, 0x2C2B, 0x2D2C, 0x2E2D, 0x2F01, 0x0031, 0x0101, 0x0002, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, 0x0808, 0x0909, 0x0A0A, 0x0B0B, 0x0C0C, 0x0D0D, 0x0E0E, 0x0F0F, 0x1010, 0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616, 0x0017, 0x0017, 0x1817, 0x1918, 0x1A19, 0x1B1A, 0x1C1B, 0x1D1C, 0x1E1D, 0x1F1E, 0x201F, 0x2120, 0x2221, 0x2322, 0x0003, 0x2402, 0x2523, 0x2624, 0x2725, 0x2826, 0x2927, 0x2A28, 0x2B29, 0x2C2A, 0x2D2B, 0x2E2C, 0x2F2D, 0x0C01, 0x000D, 0x0101, 0x0002, 0x0303, 0x0404, 0x0505, 0x0003, 0x0602, 0x0706, 0x0807, 0x0908, 0x0A09, 0x0B0A, 0x0C0B, 0x0401, 0x0004, 0x0101, 0x0202, 0x0303, 0x0404, 0x6101, 0x0063, 0x0001, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, 0x0808, 0x0909, 0x0A0A, 0x0B0B, 0x0C0C, 0x0D0D, 0x0E0E, 0x0F0F, 0x1010, 0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616, 0x0002, 0x1701, 0x1817, 0x1918, 0x1A19, 0x1B1A, 0x1C1B, 0x1D1C, 0x1E1D, 0x1F1E, 0x201F, 0x2120, 0x2221, 0x2322, 0x2423, 0x2524, 0x2625, 0x2726, 0x2827, 0x2928, 0x2A29, 0x2B2A, 0x2C2B, 0x2D2C, 0x2E2D, 0x2F2E, 0x302F, 0x3130, 0x3231, 0x0032, 0x3433, 0x3534, 0x3635, 0x3736, 0x3836, 0x3937, 0x3A38, 0x3B39, 0x3C3A, 0x3D3B, 0x3E3C, 0x3F3D, 0x403E, 0x413F, 0x4240, 0x4341, 0x4442, 0x4543, 0x4644, 0x4745, 0x4846, 0x4947, 0x0033, 0x4A32, 0x4B48, 0x4C49, 0x4D4A, 0x4E4B, 0x4F4C, 0x504D, 0x514E, 0x524F, 0x5350, 0x5451, 0x5552, 0x5653, 0x5754, 0x5855, 0x5956, 0x5A57, 0x5B58, 0x5C59, 0x5D5A, 0x5E5B, 0x5F5C, 0x605D, 0x615E, 0x0C01, 0x000C, 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606, 0x0707, 0x0808, 0x0909, 0x0A0A, 0x0B0B, 0x0C0C }; //**************************************************************************** // //**************************************************************************** void initialize(){ Wire.begin(); TWBR = 12; while( !Serial ); delay( 50 ); Wire.beginTransmission( SLAVE ); Wire.write( 0x00 ); Wire.write( 0xAE ); // set display Off Wire.write( 0xD5 ); // set display clock divide ratio/oscillator frequency Wire.write( 0x80 ); Wire.write( 0xA8 ); // set multiplex ratio Wire.write( 0x3F ); Wire.write( 0xD3 ); // set display offset Wire.write( 0x00 ); Wire.write( 0x40 ); // set display start line Wire.write( 0x8D ); // set charge pump Wire.write( 0x14 ); // VCC generated by internal DC/DC circuit Wire.write( 0xA1 ); // set segment re-map Wire.write( 0xC0 ); // Wire.write( 0xC8 ); // Wire.write( 0x12 ); Wire.write( 0x81 ); // set contrast control Wire.write( 0x88 ); // 0 ... 255 Wire.write( 0xD9 ); // set pre-changed period Wire.write( 0xF1 ); Wire.write( 0xDB ); // set VCOMH Deselected level Wire.write( 0x40 ); Wire.write( 0xA4 ); // set entire display on/off Wire.write( 0xA6 ); // set normal display Wire.write( 0x20 ); // set memory address mode Wire.write( 0x00 ); // horizontal Wire.write( 0xAF ); // set display on Wire.endTransmission(); } //**************************************************************************** // //**************************************************************************** void clearScreen() { int a, entries; byte lookup[ 16 ] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; setOledPosition( 0, 0 ); entries = ( WIDTH * ( HEIGHT >> 3 ) ) >> 4; for( a = 0; a < entries; a += 1 ){ Wire.beginTransmission( SLAVE ); Wire.write( 0x40 ); Wire.write( &lookup[ 0 ], 16 ); Wire.endTransmission(); } memset( _bitmapData, 0, sizeof( _bitmapData ) ); } //**************************************************************************** // //**************************************************************************** void setup() { byte row; Serial.begin( 115200 ); initialize(); for( row = 0; row < ROWS; row += 1 ) { _lookupMinimumcolumn[ row ] = WIDTH - 1; _lookupMaximumcolumn[ row ] = 0; _lookupOledRefresh [ row ] = 0; } Wire.setClock( 400000L ); clearScreen(); } //**************************************************************************** // //**************************************************************************** void loop() { render(); #if SHOW_FRAMES_PER_SECOND drawFps(); #endif // delay( 1000 ); // clearScreen(); #if PROFILING Serial.println( F(" - profiling -----------------------") ); Serial.print( F(" time preprocessing : ") ); Serial.println( _timePreprocessing, DEC ); Serial.print( F(" time scanconversion: ") ); Serial.println( _timeScanConvert, DEC ); Serial.print( F(" time rendering : ") ); Serial.println( _timeRendering, DEC ); Serial.print( F(" ") ); #endif } //**************************************************************************** // //**************************************************************************** void setOledPosition( byte row, byte collum ) { byte oledSetPosition[ 7 ] = { 0x00, // [ 0 ] command stream 0x22, // [ 1 ] ver row, // [ 2 ] vertical start 7, // [ 3 ] vertical end 0x21, // [ 4 ] hor collum, // [ 5 ] start collum 0x7F // [ 6 ] end collum }; Wire.beginTransmission( SLAVE ); Wire.write( &oledSetPosition[ 0 ], 7 ); Wire.endTransmission(); } //**************************************************************************** // //**************************************************************************** void render() { bool isVisible; word i0, i1, indexLookupVertices, indexLookupPolygons, vertex, segmentLookup[ LOOKUP_ENTRIES_UNIQUE_SEGMENTS ]; byte a, b, row, rowStart, rowEnd, width, offset, column, vertexIndex, polygon, spanHeight, spanWidth, rowData, minimumRow, maximumRow, indexNormal, numberOfSegments, numberOfVertices, numberOfUniqueVertices, numberOfPolygons, lookupMinimumcolumn[ ROWS ], lookupMaximumcolumn[ ROWS ], *pBitmapData; int xs, ys, xe, ye, x0, y0, x1, y1, x, y, z, tmp, entries, deltaX, deltaY, bitmapDataAdres; char *pTransformedVertex; word bitfield, lookupOledRefresh[ ROWS ]; const word *pLookupSineCosine, *pLookupVertices, *pLookupPolygons; byte oledSetPosition[ 6 ] = { 0x22, // [ 0 ] ver 0, // [ 1 ] vertical start 7, // [ 2 ] vertical end 0x21, // [ 3 ] hor 0, // [ 4 ] start collum 0x7F // [ 5 ] end collum }; #if PROFILING unsigned long tt; #endif //*************************************************************************** // //*************************************************************************** #if ROTATE_X_AXIS int sinXAxis, cosXAxis; #endif #if ROTATE_Y_AXIS int sinYAxis, cosYAxis; #endif #if ROTATE_Z_AXIS int sinZAxis, cosZAxis; #endif //*************************************************************************** // //*************************************************************************** memset( lookupMinimumcolumn, WIDTH - 1, sizeof( lookupMinimumcolumn ) ); memset( lookupMaximumcolumn, 0, sizeof( lookupMaximumcolumn ) ); memset( lookupOledRefresh , 0, sizeof( lookupOledRefresh ) ); minimumRow = ROWS - 1; maximumRow = 0; indexLookupVertices = 0; indexLookupPolygons = 0; pTransformedVertex = &_lookupTransformedVertices[ 0 ]; pLookupVertices = &_lookupVertices[ 0 ]; pLookupPolygons = &_lookupPolygons[ 0 ]; pBitmapData = &_bitmapData[ 0 ]; //*************************************************************************** // initialize rotation variables //*************************************************************************** pLookupSineCosine = &_lookupSineCosine[ 0 ]; #if ROTATE_X_AXIS i0 = pgm_read_word( pLookupSineCosine + _frameXAxis ); sinXAxis = ( ( i0 >> 8 ) & 255 ) - 127; cosXAxis = ( i0 & 255 ) - 127; #endif #if ROTATE_Y_AXIS i0 = pgm_read_word( pLookupSineCosine + ( 255 - _frameYAxis ) ); sinYAxis = ( ( i0 >> 8 ) & 255 ) - 127; cosYAxis = ( i0 & 255 ) - 127; #endif #if ROTATE_Z_AXIS i0 = pgm_read_word( pLookupSineCosine + _frameZAxis ); sinZAxis = ( ( i0 >> 8 ) & 255 ) - 127; cosZAxis = ( i0 & 255 ) - 127; #endif //*************************************************************************** // //*************************************************************************** do { numberOfUniqueVertices = ( byte )( pgm_read_word( pLookupVertices + indexLookupVertices ) & 255 ); //************************************************************************** // number of transformed vertices per chunk is equals to // numberOfUniqueVertices. number of bitfields never exceeds // numberOfUniqueVertices / 16. //************************************************************************** a = ( numberOfUniqueVertices >> 4 ) + 1 - !( numberOfUniqueVertices % 16 ); memset( _lookupTransformedVerticesBitfield, 0, a << 1 ); //************************************************************************** // number of polygons per chunk // number of unique segments indices per chunk //************************************************************************** i0 = pgm_read_word( pLookupPolygons + indexLookupPolygons ); numberOfPolygons = ( byte )( i0 & 255 ); b = ( byte )( ( i0 >> 8 ) & 255 ); a = ( b >> 4 ) + 1 - !( b % 16 ); memset( segmentLookup, 0, a << 1 ); //************************************************************************** // //************************************************************************** indexLookupVertices += 1; indexLookupPolygons += 1; //************************************************************************** // // loop polygons // // the arduino nano has 2kb ram. // the 3D model data is stored in sram ( 30kb ) and retrieved in // chunks. per chunk a couple of polygons are drawn. // //************************************************************************** for( polygon = 0; polygon < numberOfPolygons; polygon += 1 ) { i0 = pgm_read_word( pLookupPolygons + indexLookupPolygons ); numberOfVertices = i0 & 255; indexLookupPolygons += 1; isVisible = true; //************************************************************************* // // preproces polygon; transform polygon normal & vertices // //************************************************************************* #if PROFILING tt = micros(); #endif for( vertex = 0; vertex < numberOfVertices + 1; vertex += 1 ) { //************************************************************************ // get vertex index //************************************************************************ if( vertex != 0 ) { vertexIndex = pgm_read_word( pLookupPolygons + indexLookupPolygons + vertex - 1 ) & 255; } else { vertexIndex = ( i0 >> 8 ) & 255; } i1 = 1 << ( vertexIndex % 16 ); //************************************************************************ // check if verter ( or polygon normal ) is transformed previously // if alreay calculated do not re-calculate transformation //************************************************************************ if( !( _lookupTransformedVerticesBitfield[ vertexIndex >> 4 ] & i1 ) ) { //*********************************************************************** // set bit ( vertex is processed ) //*********************************************************************** _lookupTransformedVerticesBitfield[ vertexIndex >> 4 ] |= i1; //*********************************************************************** // transform/rotate // fixed point math //*********************************************************************** tmp = indexLookupVertices + ( vertexIndex << 1 ); i0 = pgm_read_word( pLookupVertices + tmp ); i1 = pgm_read_word( pLookupVertices + tmp + 1 ); x = ( char )( ( ( i0 >> 8 ) & 255 ) - 127 ); y = ( char )( ( i0 & 255 ) - 127 ); z = ( char )( ( i1 & 255 ) - 127 ); #if ROTATE_X_AXIS tmp = y; y = ( y * cosXAxis - z * sinXAxis ) >> 7; z = ( tmp * sinXAxis + z * cosXAxis ) >> 7; #endif #if ROTATE_Y_AXIS tmp = x; x = ( z * sinYAxis + x * cosYAxis ) >> 7; z = ( z * cosYAxis - tmp * sinYAxis ) >> 7; #endif #if ROTATE_Z_AXIS tmp = x; x = ( x * cosZAxis - y * sinZAxis ) >> 7; y = ( tmp * sinZAxis + y * cosZAxis ) >> 7; #endif //*********************************************************************** // if position is vertex data use vertical position to set // minimum & maximum row. //*********************************************************************** if( vertex != 0 ) { x = ( WIDTH >> 1 ) - ( ( x * OBJECT_SIZE ) >> 8 ) - 64; y = ( HEIGHT >> 1 ) - ( ( y * OBJECT_SIZE ) >> 8 ); //********************************************************************** // horizontal correction ( screen is 0 ... WIDTH ) // width = 128, char maximum is 127 // correction is undone during drawing //********************************************************************** *( pTransformedVertex + ( vertexIndex << 1 ) ) = ( char )max( -117, min( x, 127 ) ); *( pTransformedVertex + ( vertexIndex << 1 ) + 1 ) = ( char )max( -127, min( y, 127 ) ); row = max( 0, min( ROWS - 1, y >> 3 ) ); minimumRow = min( row, minimumRow ); maximumRow = max( row, maximumRow ); x = max( 0, min( *( pTransformedVertex + ( vertexIndex << 1 ) ) + 64, WIDTH - 1 ) ); if( x < lookupMinimumcolumn[ row ] ) { lookupMinimumcolumn[ row ] = x; } if( x > lookupMaximumcolumn[ row ] ) { lookupMaximumcolumn[ row ] = x; } } else { *( pTransformedVertex + ( vertexIndex << 1 ) ) = z; } } //************************************************************************ // if position is polygon normal check sign z position ( stored in // [ 0 ] ) for visibility; hidden surface removal. //************************************************************************ if( vertex == 0 && ( *( pTransformedVertex + ( vertexIndex << 1 ) ) <= 0 ) == FLIP_NORMAL ) { isVisible = false; break; } } // end loop polygon vertices #if PROFILING _timePreprocessing += micros() - tt; #endif //************************************************************************* // // // scanconvert polygon if visible // // //************************************************************************* if( isVisible ) { #if PROFILING tt = micros(); #endif //************************************************************************ // scan convert segments polygons //************************************************************************ for( vertex = 0; vertex < numberOfVertices ; vertex += 1 ) { //*********************************************************************** // start position becomes previous end //*********************************************************************** i0 = pgm_read_word( pLookupPolygons + indexLookupPolygons + vertex ); //*********************************************************************** // check if segment is already drawn // if segment index == 0 no need to draw segment. // segment == 0 is used for poly lists to hide the segment which // closes the polygon which doesn''t like nice on the screen. //*********************************************************************** row = ( i0 >> 8 ) & 255; if( row == 0 ) { continue; } xs = 1 << ( ( row - 1 ) % 16 ); xe = ( row - 1 ) >> 4 ; //*********************************************************************** // check if segment is drawn previously; avoid multiple draws // of the same polygon segment //*********************************************************************** if( !( segmentLookup[ xe ] & xs ) ) { //********************************************************************** // set bitfield bit; do not proces segment a next time //********************************************************************** segmentLookup[ xe ] |= xs; //********************************************************************** // init start & end position segment //********************************************************************** i0 = ( i0 & 255 ) << 1; x0 = *( pTransformedVertex + i0 ) + 64; y0 = *( pTransformedVertex + i0 + 1 ); i0 = ( pgm_read_word( pLookupPolygons + indexLookupPolygons + ( ( vertex + 1 ) % numberOfVertices ) ) & 255 ) << 1; x1 = *( pTransformedVertex + i0 ) + 64; y1 = *( pTransformedVertex + i0 + 1 ); //********************************************************************** // //********************************************************************** deltaX = x1 - x0; deltaY = y1 - y0; //********************************************************************** // //********************************************************************** #if !isMeshInsideScreen if( ( deltaX < 0 && x1 < WIDTH && x0 >= 0 ) || ( deltaX >= 0 && x0 < WIDTH && x1 >= 0 ) ) { if( ( deltaY < 0 && y1 < HEIGHT && y0 >= 0 ) || ( deltaY >= 0 && y0 < HEIGHT && y1 >= 0 ) ) { #endif //********************************************************************** // // delta y line greater delta x line; draw scanlines vertical // per byte ( 0 ... 255 ) // // .....+............ scanline 0 // .....|+........... scanline 1 // .....||........... scanline 2 // .....+|........... scanline 3 // ......+........... scanline 4 // //********************************************************************** if( abs( deltaY ) >= abs( deltaX ) ) { entries = abs( deltaX ); deltaY = ( ( y1 - y0 ) << 8 ) / ( entries == 0 ? 1 : entries ); y0 <<= 8; ys = ( y0 + 128 ) >> 8; for( a = 0; a <= entries ; a += 1 ) { ye = x0 == x1 ? y1 : ( ( y0 + deltaY ) + 128 ) / 256; #if !isMeshInsideScreen if( x0 >= 0 && x0 < WIDTH ) { #endif spanHeight = max( 1, abs( ys - ye ) + ( x0 == x1 ? 1 : 0 ) ); y = ys - ( ys <= ye ? 0 : ( spanHeight - 1 ) ); //******************************************************************* // check if scanline horizontal start & end position is in screen //******************************************************************* #if !isMeshInsideScreen if( y < HEIGHT && ( y + spanHeight ) >= 0 ) { #endif spanHeight = y + spanHeight; y = max( 0, y ); spanHeight = min( spanHeight - y, HEIGHT - y ); rowStart = max( 0, min( y >> 3, ROWS - 1 ) ); rowEnd = max( 0, min( ( y + spanHeight - 1 ) >> 3, ROWS - 1 ) ); bitmapDataAdres = ( rowStart * WIDTH ) + x0; for( row = rowStart; row <= rowEnd; row += 1, bitmapDataAdres += WIDTH ) { i0 = row == rowStart ? y % 8: 0; i1 = min( min( spanHeight, ( y + spanHeight ) - ( row * 8 ) ), 8 - i0 ); rowData = ( ( 1 << i1 ) - 1 ) << i0; *( pBitmapData + bitmapDataAdres ) |= rowData; if( x0 < lookupMinimumcolumn[ row ] ) { lookupMinimumcolumn[ row ] = x0; } if( x0 > lookupMaximumcolumn[ row ] ) { lookupMaximumcolumn[ row ] = x0; } lookupOledRefresh[ row ] |= 1 << ( x0 >> 3 ); } #if !isMeshInsideScreen } // end check vertical interval } // end check horizontal minimum & maximum #endif //******************************************************************** // //******************************************************************** ys = ye; x0 += ( x1 < x0 ) ? -1 : 1; y0 += deltaY; //******************************************************************** // //******************************************************************** #if !isMeshInsideScreen if( ( deltaY >= 0 && y >= ( HEIGHT - 1 ) ) || ( deltaY < 0 && y <= 0 ) ) { break; } #endif } // end loop entries } // end check delta y //********************************************************************** // // delta x line greater delta y line; draw scanlines horizontal // // .....+------+...... scanline 0 // ..........+------+. scanline 1 // //********************************************************************** else { entries = abs( deltaY ); deltaX = ( ( ( x1 - x0 ) << 8 ) / ( entries == 0 ? 1 : entries ) ); x0 <<= 8; xs = ( x0 + 128 ) >> 8; //********************************************************************* // loop vertical //********************************************************************* for( a = 0; a <= entries ; a += 1 ) { xe = y0 == y1 ? x1 : ( ( x0 + deltaX ) + 128 ) / 256; //******************************************************************** // check if scanline vertical position is in screen //******************************************************************** #if !isMeshInsideScreen if( y0 >= 0 && y0 < HEIGHT ) { #endif spanWidth = max( 1, abs( xs - xe ) + ( y0 == y1 ? 1 : 0 ) ); x = xs - ( xs <= xe ? 0 : ( spanWidth - 1 ) ); //******************************************************************* // check if scanline horizontal start & end position is in screen //******************************************************************* #if !isMeshInsideScreen if( x >= 0 && x < WIDTH && ( x + spanWidth ) >= 0 ) { #endif spanWidth = x + spanWidth; x = max( 0, x ); spanWidth = min( spanWidth - x, WIDTH - x ); row = y0 >> 3; bitmapDataAdres = ( row * WIDTH ) + x; rowData = 1 << ( y0 % 8); if( ( rowData & 129 ) != 0 ) { if( x < lookupMinimumcolumn[ row ] ) { lookupMinimumcolumn[ row ] = x ; } if( ( x + spanWidth - 1 ) > lookupMaximumcolumn[ row ] ) { lookupMaximumcolumn[ row ] = x + spanWidth - 1; } } //****************************************************************** // loop vertical //****************************************************************** for( b = 0; b < spanWidth; b += 1, x += 1 ) { *( pBitmapData + bitmapDataAdres + b ) |= rowData; lookupOledRefresh[ row ] |= 1 << ( x >> 3 ); } #if !isMeshInsideScreen } } #endif //******************************************************************* // //******************************************************************* xs = xe; y0 += ( y1 < y0 ) ? -1 : 1; x0 += deltaX; //******************************************************************** // //******************************************************************** #if !isMeshInsideScreen if( ( deltaX >= 0 && x >= ( WIDTH - 1 ) ) || ( deltaX < 0 && x <= 0 ) ) { break; } #endif } // end loop entries } // end check delta x #if !isMeshInsideScreen } } #endif } // end is segment already drawn check } // end loop vertices #if PROFILING _timeScanConvert += micros() - tt; #endif } // end check is polygon visible //************************************************************************* // update indices //************************************************************************* indexLookupPolygons += numberOfVertices; } // end loop polygons indexLookupVertices += numberOfUniqueVertices << 1; } while( indexLookupPolygons < NUMBER_OF_POLYGON_INDICES ); //*************************************************************************** // // draw & remove data per row // render content bitmapdata to oled screen. // // min +------+ max // previous drawn row // min +--+ max // current row // // previous drawn row is still visible on screen. // previous minimum is compared againt current row minimum. the lowest // value is used to remove previous drawn row ( and draw ) current row. // same applies for maximum. // // draw from top row to bottom row; fps is drawn at bottom row // bottom row is drawn last so time between fps draw and 3d object // draw is minimumal ( less flickering ) // //*************************************************************************** #if PROFILING tt = micros(); #endif a = max( _maximumRow, maximumRow ) ; for( row = min( _minimumRow, minimumRow ) ; row <= a ; row += 1 ) { xs = min( _lookupMinimumcolumn[ row ], lookupMinimumcolumn[ row ] ) ; xe = max( _lookupMaximumcolumn[ row ], lookupMaximumcolumn[ row ] ) + 1; if( xs < xe ) { //************************************************************************* // display minimum and maximum area needed for oled screen removal. //************************************************************************* // *( pBitmapData + ( row * WIDTH ) + lookupMinimumcolumn[ row ] ) |= 255; // *( pBitmapData + ( row * WIDTH ) + lookupMaximumcolumn[ row ] ) |= 255; oledSetPosition[ 1 ] = row; oledSetPosition[ 2 ] = row; oledSetPosition[ 4 ] = xs ; Wire.beginTransmission( SLAVE ); Wire.write( 0x00 ); Wire.write( &oledSetPosition[ 0 ], 6 ); Wire.endTransmission(); column = xs; width = 8 - ( column & 7 ); bitfield = _lookupOledRefresh[ row ] | lookupOledRefresh[ row ]; i1 = row * WIDTH; do { //******************************************************************** // draw span //******************************************************************** i0 = bitfield >> ( column >> 3 ); if( i0 & 1 ) { //******************************************************************* // check lsb if oled position needs to be set //******************************************************************* if( ( bitfield & 1 ) == 0 ) { oledSetPosition[ 4 ] = column ; Wire.beginTransmission( SLAVE ); Wire.write( 0x00 ); Wire.write( &oledSetPosition[ 3 ], 3 ); Wire.endTransmission(); } //******************************************************************* // send data in packages of 8, 16 or 24 // ( 32 is one too many for 32 bytes since 0x40 is send aswell ) //******************************************************************* switch( i0 & 7 ) { case 7: spanWidth = 16; break; case 3: spanWidth = 8; break; default: spanWidth = 0; break; }; Wire.beginTransmission( SLAVE ); Wire.write( 0x40 ); Wire.write( ( pBitmapData + i1 + column ), min( width + spanWidth, xe - column ) ); Wire.endTransmission(); //******************************************************************* // lsb is used as data send //******************************************************************* bitfield |= 1; } //******************************************************************** // skip span(s) //******************************************************************** else { switch( ~i0 & 127 ) { case 127: spanWidth = 48; break; case 63: spanWidth = 40; break; case 31: spanWidth = 32; break; case 15: spanWidth = 24; break; case 7: spanWidth = 16; break; case 3: spanWidth = 8; break; default: spanWidth = 0; break; }; //******************************************************************* // lsb is used as no-data send. // if 0 - zero - and data needs to be send on same row adjust // oled screen position //******************************************************************* bitfield -= bitfield & 1; } column += width + spanWidth; width = 8; } while( column < xe ); //************************************************************************* // //************************************************************************* #if SHOW_FRAMES_PER_SECOND if( row != FPS_ROW_POSITION ) { memset( ( pBitmapData + ( row * WIDTH ) + xs ), 0, xe - xs ); } #else memset( ( pBitmapData + ( row * WIDTH ) + xs ), 0, xe - xs ); #endif _lookupMinimumcolumn[ row ] = lookupMinimumcolumn[ row ]; _lookupMaximumcolumn[ row ] = lookupMaximumcolumn[ row ]; _lookupOledRefresh [ row ] = lookupOledRefresh [ row ]; } } //*************************************************************************** // //*************************************************************************** #if PROFILING _timeRendering += micros() - tt; #endif //*************************************************************************** // //*************************************************************************** _minimumRow = minimumRow; _maximumRow = maximumRow; //*************************************************************************** // //*************************************************************************** #if ROTATE_Y_AXIS _frameYAxis = ( _frameYAxis + 3 ) & 255; #endif #if ROTATE_X_AXIS _frameXAxis = ( _frameXAxis + 1 ) & 255; #endif #if ROTATE_Z_AXIS _frameZAxis = ( _frameZAxis + 1 ) & 255; #endif } //**************************************************************************** // // draw frames per second // //**************************************************************************** #if SHOW_FRAMES_PER_SECOND void drawFps() { byte a, i, fps, index, *pBitmapData; word bitmapPos; const byte *pLookupFps; pBitmapData = &_bitmapData[ 0 ]; pLookupFps = &_lookupFps[ 0 ]; index = WIDTH - 1 - 30; _numberOfFrames = ( _numberOfFrames + 1 ) % 32; fps = 1000 / ( millis() - _time ); _time = millis(); if( !_numberOfFrames ) { _totalOfFrameRates = ( _totalOfFrameRates / 32 ) << 4; _numberOfFrames = 16; } else { _totalOfFrameRates += fps; } fps = byte( _totalOfFrameRates / _numberOfFrames ); bitmapPos = ( FPS_ROW_POSITION * WIDTH ) + index; for( a = ( fps > 99 ? 0 : fps > 9 ? 5 : 10 ); a < 30; a += 1 ) { if( !( a % 5 ) ) { i = a / 5; if( i < 3 ) { i = ( ( fps / ( i == 0 ? 100 : i == 1 ? 10 : 1 ) ) % 10 ) * 5; } else { i = 50 + ( i - 3 ) * 5; } } *( pBitmapData + bitmapPos + a ) |= pgm_read_byte( pLookupFps + i + ( a % 5 ) ); } //************************************************************************** // //************************************************************************** byte oledSetPosition[ 7 ] = { 0x00, // [ 0 ] command stream 0x22, // [ 1 ] ver FPS_ROW_POSITION, // [ 2 ] vertical start FPS_ROW_POSITION, // [ 3 ] vertical end 0x21, // [ 4 ] hor index, // [ 5 ] start collum 0x7F // [ 6 ] end collum }; Wire.beginTransmission( SLAVE ); Wire.write( &oledSetPosition[ 0 ], 7 ); Wire.endTransmission(); for( a = 0; a < 30; a += 24 ) { Wire.beginTransmission( SLAVE ); Wire.write( 0x40 ); Wire.write( ( pBitmapData + bitmapPos + a ), min( 24, 30 - a ) ); Wire.endTransmission(); } //************************************************************************** // clear bitmapdata //************************************************************************** index = min( _lookupMinimumcolumn[ FPS_ROW_POSITION ], index ); memset( ( pBitmapData + ( FPS_ROW_POSITION * WIDTH ) + index ), 0, WIDTH - index ); } #endif
copy to clipboard