├── .gitignore ├── ESP_8_BIT_GFX.cpp ├── ESP_8_BIT_GFX.h ├── ESP_8_BIT_composite.cpp ├── ESP_8_BIT_composite.h ├── LICENSE ├── README.md ├── examples ├── AnimatedGIF │ ├── AnimatedGIF.ino │ └── cat_and_galactic_squid.h ├── GFX_HelloWorld │ └── GFX_HelloWorld.ino ├── GFX_RotatedRect │ └── GFX_RotatedRect.ino ├── GFX_RotatedText │ └── GFX_RotatedText.ino ├── GFX_Screen_Fillers │ └── GFX_Screen_Fillers.ino ├── RGB332_Colors │ └── RGB332_Colors.ino └── RGB332_PulseB │ └── RGB332_PulseB.ino ├── keywords.txt └── library.properties /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ -------------------------------------------------------------------------------- /ESP_8_BIT_GFX.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Adafruit_GFX for ESP_8_BIT color composite video. 4 | 5 | NOT AN OFFICIAL ADAFRUIT GRAPHICS LIBRARY. 6 | 7 | Allows ESP32 Arduino sketches to draw to a composite video device using 8 | Adafruit's graphics API. 9 | 10 | NOTE RE:COLOR 11 | 12 | Adafruit GFX is designed for 16-bit (RGB565) color, but ESP_8_BIT video 13 | only handles 8-bit (RGB332) color. There are two ways to handle this, 14 | specified by passsing "8" or "16" into the constructor: 15 | 16 | 8 = Truncate the 16-bit color values and use the lower 8 bits directly as 17 | RGB332 color. This is faster, but caller needs to know to use 8-bit 18 | color values. A good choice when writing new code using this library. 19 | 16 = Automatically extract the most significant 3 red, 3 green, and 2 blue 20 | bits from a 16-bit RGB565 color value to generate a RGB332 color. 21 | Performing this conversion slows down the code, but the caller does not 22 | need to know about the limitations. A good choice when reusing existing 23 | Adafruit GFX code that works in 16-bit color space. 24 | 25 | An utility function RGB565toRGB332 is available to perform this conversion. 26 | 27 | NOTE RE:ASPECT RATIO 28 | 29 | Adafruit GFX assumes pixels are square, but this is not true of ESP_8_BIT 30 | which has nonsquare pixels. (4:3 aspect ratio in a 256x240 frame buffer.) 31 | Circles will look squashed as wide ovals, etc. This version of the API does 32 | not offer any way to compensate, the caller has to deal with it. 33 | 34 | 35 | 36 | Copyright (c) Roger Cheng 37 | 38 | MIT License 39 | 40 | Permission is hereby granted, free of charge, to any person obtaining a copy 41 | of this software and associated documentation files (the "Software"), to deal 42 | in the Software without restriction, including without limitation the rights 43 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 44 | copies of the Software, and to permit persons to whom the Software is 45 | furnished to do so, subject to the following conditions: 46 | The above copyright notice and this permission notice shall be included in all 47 | copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 50 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 51 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 52 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 53 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | 57 | */ 58 | 59 | #include "ESP_8_BIT_GFX.h" 60 | 61 | static const char *TAG = "ESP_8_BIT_GFX"; 62 | 63 | static const int16_t MAX_Y = 239; 64 | static const int16_t MAX_X = 255; 65 | 66 | /* 67 | * @brief Expose Adafruit GFX API for ESP_8_BIT composite video generator 68 | */ 69 | ESP_8_BIT_GFX::ESP_8_BIT_GFX(bool ntsc, uint8_t colorDepth) 70 | : Adafruit_GFX(MAX_X+1, MAX_Y+1) 71 | { 72 | _pVideo = new ESP_8_BIT_composite(ntsc); 73 | if (NULL==_pVideo) 74 | { 75 | ESP_LOGE(TAG, "Video signal generator allocation failed"); 76 | ESP_ERROR_CHECK(ESP_FAIL); 77 | } 78 | 79 | if (8 == colorDepth || 16 == colorDepth) 80 | { 81 | _colorDepth = colorDepth; 82 | } 83 | else 84 | { 85 | ESP_LOGE(TAG, "Unsupported color depth"); 86 | ESP_ERROR_CHECK(ESP_FAIL); 87 | } 88 | 89 | // Default behavior is not to copy buffer upon swap 90 | copyAfterSwap = false; 91 | 92 | // Initialize performance tracking state 93 | _perfStart = 0; 94 | _perfEnd = 0; 95 | _waitTally = 0; 96 | } 97 | 98 | /* 99 | * @brief Call once to set up the API with self-allocated frame buffer. 100 | */ 101 | void ESP_8_BIT_GFX::begin() 102 | { 103 | _pVideo->begin(); 104 | } 105 | 106 | /* 107 | * @brief Calculate performance metrics, output as INFO log. 108 | * @return Number range from 0 to 10000. Higher values indicate more time 109 | * has been spent waiting for buffer swap, implying the rest of the code 110 | * ran faster and completed more quickly. 111 | */ 112 | uint32_t ESP_8_BIT_GFX::perfData() 113 | { 114 | uint32_t fraction = getWaitFraction(); 115 | 116 | if (_perfEnd < _perfStart) 117 | { 118 | ESP_LOGE(TAG, "Performance end time is earlier than start time."); 119 | } 120 | else 121 | { 122 | uint32_t duration = _perfEnd - _perfStart; 123 | if (duration < _waitTally) 124 | { 125 | ESP_LOGE(TAG, "Overall time duration is less than tally of wait times."); 126 | } 127 | else 128 | { 129 | uint32_t frames = _pVideo->getRenderedFrameCount() - _frameStart; 130 | uint32_t swaps = _pVideo->getBufferSwapCount() - _swapStart; 131 | uint32_t wholePercent = fraction/100; 132 | uint32_t decimalPercent = fraction%100; 133 | ESP_LOGI(TAG, "Waited %d.%d%%, missed %d of %d frames", 134 | wholePercent, decimalPercent, frames-swaps, frames); 135 | } 136 | } 137 | _perfStart = 0; 138 | _perfEnd = 0; 139 | _waitTally = 0; 140 | 141 | return fraction; 142 | } 143 | 144 | /* 145 | * @brief Wait for swap of front and back buffer. Gathers performance 146 | * metrics while waiting. 147 | */ 148 | void ESP_8_BIT_GFX::waitForFrame() 149 | { 150 | // Track the old lines array in case we need to copy after swap 151 | uint8_t** oldLineArray = _pVideo->getFrameBufferLines(); 152 | // Values to track time spent waiting for swap 153 | uint32_t waitStart = xthal_get_ccount(); 154 | uint32_t waitEnd; 155 | 156 | if (waitStart < _perfEnd) 157 | { 158 | // CCount overflowed since last call, conclude this session. 159 | perfData(); 160 | } 161 | if (0 == _waitTally) 162 | { 163 | // No wait tally signifies start of new session. 164 | _perfStart = waitStart; 165 | _frameStart = _pVideo->getRenderedFrameCount(); 166 | _swapStart = _pVideo->getBufferSwapCount(); 167 | } 168 | 169 | // Wait for swap of front and back buffer 170 | _pVideo->waitForFrame(); 171 | 172 | if (copyAfterSwap) 173 | { 174 | uint8_t** newLineArray = _pVideo->getFrameBufferLines(); 175 | 176 | // This must be kept in sync with how frame buffer memory 177 | // is allocated in ESP_8_BIT_composite::frameBufferAlloc() 178 | for (uint8_t chunk = 0; chunk < 15; chunk++) 179 | { 180 | memcpy(newLineArray[chunk*16], oldLineArray[chunk*16], 256*16); 181 | } 182 | } 183 | 184 | // Core clock count after we've finished waiting 185 | waitEnd = xthal_get_ccount(); 186 | if (waitEnd < waitStart) 187 | { 188 | // CCount overflowed while we were waiting, perform calculation 189 | // ignoring the time spent waiting. 190 | _perfEnd = waitStart; 191 | perfData(); 192 | } 193 | else 194 | { 195 | // Increase tally of time we spent waiting for buffer swap 196 | _waitTally += waitEnd-waitStart; 197 | _perfEnd = waitEnd; 198 | } 199 | } 200 | 201 | /* 202 | * @brief Fraction of time in waitForFrame() in percent of percent. 203 | * @return Number range from 0 to 10000. Higher values indicate more time 204 | * has been spent waiting for buffer swap, implying the rest of the code 205 | * ran faster and completed more quickly. 206 | */ 207 | uint32_t ESP_8_BIT_GFX::getWaitFraction() 208 | { 209 | if (_perfEnd > _perfStart + 10000) 210 | { 211 | return _waitTally/((_perfEnd-_perfStart)/10000); 212 | } 213 | else 214 | { 215 | return 10000; 216 | } 217 | } 218 | 219 | /* 220 | * @brief Ends the current performance tracking session and start a new 221 | * one. Useful for isolating sections of code for measurement. 222 | * @note Sessions are still terminated whenever CPU clock counter 223 | * overflows (every ~18 seconds @ 240MHz) so some data may still be lost. 224 | * @return Number range from 0 to 10000. Higher values indicate more time 225 | * has been spent waiting for buffer swap, implying the rest of the code 226 | * ran faster and completed more quickly. 227 | */ 228 | uint32_t ESP_8_BIT_GFX::newPerformanceTrackingSession() 229 | { 230 | return perfData(); 231 | } 232 | 233 | /* 234 | * @brief Utility to convert from 16-bit RGB565 color to 8-bit RGB332 color 235 | */ 236 | uint8_t ESP_8_BIT_GFX::convertRGB565toRGB332(uint16_t color) 237 | { 238 | // Extract most significant 3 red, 3 green and 2 blue bits. 239 | return (uint8_t)( 240 | (color & 0xE000) >> 8 | 241 | (color & 0x0700) >> 6 | 242 | (color & 0x0018) >> 3 243 | ); 244 | } 245 | 246 | /* 247 | * @brief Retrieve color to use depending on _colorDepth 248 | */ 249 | uint8_t ESP_8_BIT_GFX::getColor8(uint16_t color) 250 | { 251 | switch(_colorDepth) 252 | { 253 | case 8: 254 | // Use lower 8 bits directly 255 | return (uint8_t)color; 256 | break; 257 | case 16: 258 | // Downsample from 16 to 8-bit color. 259 | return convertRGB565toRGB332(color); 260 | break; 261 | default: 262 | // Error: color depth needs to be 8 or 16. 263 | ESP_LOGE(TAG, "Invalid color depth."); 264 | 265 | // In case of error, draw everything as gray: 0x49 in RGB332 266 | return 0x49; 267 | } 268 | } 269 | 270 | /* 271 | * @brief Clamp X coordinate value within valid range 272 | */ 273 | int16_t ESP_8_BIT_GFX::clampX(int16_t inputX) 274 | { 275 | if (inputX < 0) { 276 | ESP_LOGV(TAG, "Clamping X to 0"); 277 | return 0; 278 | } 279 | 280 | if (inputX > MAX_X) { 281 | ESP_LOGV(TAG, "Clamping X to 255"); 282 | return MAX_X; 283 | } 284 | 285 | return inputX; 286 | } 287 | 288 | /* 289 | * @brief Clamp Y coordinate value within valid range 290 | */ 291 | int16_t ESP_8_BIT_GFX::clampY(int16_t inputY) 292 | { 293 | if (inputY < 0) { 294 | ESP_LOGV(TAG, "Clamping Y to 0"); 295 | return 0; 296 | } 297 | 298 | if (inputY > MAX_Y) { 299 | ESP_LOGV(TAG, "Clamping Y to 239"); 300 | return MAX_Y; 301 | } 302 | 303 | return inputY; 304 | } 305 | 306 | /* 307 | * @brief Required Adafruit_GFX override to put a pixel on screen 308 | */ 309 | void ESP_8_BIT_GFX::drawPixel(int16_t x, int16_t y, uint16_t color) 310 | { 311 | // Account for screen rotation. Copied from Adafruit_GFX.cpp 312 | int16_t t; 313 | switch (rotation) { 314 | case 1: 315 | t = x; 316 | x = WIDTH - 1 - y; 317 | y = t; 318 | break; 319 | case 2: 320 | x = WIDTH - 1 - x; 321 | y = HEIGHT - 1 - y; 322 | break; 323 | case 3: 324 | t = x; 325 | x = y; 326 | y = HEIGHT - 1 - t; 327 | break; 328 | } 329 | 330 | if (x < 0 || x > MAX_X || 331 | y < 0 || y > MAX_Y ) 332 | { 333 | // This pixel is off screen, nothing to draw. 334 | return; 335 | } 336 | 337 | startWrite(); 338 | _pVideo->getFrameBufferLines()[y][x] = getColor8(color); 339 | endWrite(); 340 | } 341 | 342 | /**************************************************************************/ 343 | /*! 344 | @brief Draw a perfectly vertical line, optimized for ESP_8_BIT 345 | @param x Top-most x coordinate 346 | @param y Top-most y coordinate 347 | @param h Height in pixels 348 | @param color Color to fill with. 349 | */ 350 | /**************************************************************************/ 351 | void ESP_8_BIT_GFX::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) 352 | { 353 | // Call ESP_8_BIT optimized fillRect with width of one 354 | fillRect(x, y, 1, h, color); 355 | } 356 | 357 | /**************************************************************************/ 358 | /*! 359 | @brief Draw a perfectly horizontal line, optimized for ESP_8_BIT 360 | @param x Left-most x coordinate 361 | @param y Left-most y coordinate 362 | @param w Width in pixels 363 | @param color Color to fill with 364 | */ 365 | /**************************************************************************/ 366 | 367 | void ESP_8_BIT_GFX::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) 368 | { 369 | // Call ESP_8_BIT optimized fillRect with height of one 370 | fillRect(x, y, w, 1, color); 371 | } 372 | 373 | /**************************************************************************/ 374 | /*! 375 | @brief Fill a rectangle completely with one color, optimized for ESP_8_BIT 376 | @param x Top left corner x coordinate 377 | @param y Top left corner y coordinate 378 | @param w Width in pixels 379 | @param h Height in pixels 380 | @param color Color to fill with 381 | */ 382 | /**************************************************************************/ 383 | void ESP_8_BIT_GFX::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) 384 | { 385 | if (h < 1) 386 | { 387 | // Don't draw anything for zero or negative height 388 | return; 389 | } 390 | 391 | if (w < 1) 392 | { 393 | // Don't draw anything for zero or negative width 394 | return; 395 | } 396 | 397 | // Account for screen rotation. 398 | int16_t t; 399 | switch (rotation) { 400 | case 1: 401 | t = x; 402 | x = WIDTH - 1 - y - h; 403 | y = t; 404 | // Swap width and height 405 | t = w; 406 | w = h; 407 | h = t; 408 | break; 409 | case 2: 410 | x = WIDTH - 1 - x - w; 411 | y = HEIGHT - 1 - y - h; 412 | break; 413 | case 3: 414 | t = x; 415 | x = y; 416 | y = HEIGHT - 1 - t - w; 417 | // Swap width and height 418 | t = w; 419 | w = h; 420 | h = t; 421 | break; 422 | } 423 | 424 | if (x+w < 0 || x > MAX_X) 425 | { 426 | // This rectangle is off screen left or right, nothing to draw. 427 | return; 428 | } 429 | 430 | if (y+h < 0 || y > MAX_Y ) 431 | { 432 | // This rectangle is off screen top or bottom, nothing to draw. 433 | return; 434 | } 435 | 436 | int16_t clampedX = clampX(x); 437 | int16_t clampedXW = clampX(x+w-1); 438 | int16_t fillWidth = clampedXW-clampedX+1; 439 | 440 | int16_t clampedY = clampY(y); 441 | int16_t clampedYH = clampY(y+h-1)+1; 442 | 443 | uint8_t color8 = getColor8(color); 444 | uint8_t** lines = _pVideo->getFrameBufferLines(); 445 | 446 | startWrite(); 447 | for(int16_t vertical = clampedY; vertical < clampedYH; vertical++) 448 | { 449 | memset(&(lines[vertical][clampedX]), color8, fillWidth); 450 | } 451 | endWrite(); 452 | } 453 | 454 | /**************************************************************************/ 455 | /*! 456 | @brief Fill the screen completely with one color, optimized for ESP_8_BIT. 457 | @param color Color to fill with 458 | */ 459 | /**************************************************************************/ 460 | void ESP_8_BIT_GFX::fillScreen(uint16_t color) 461 | { 462 | uint8_t color8 = getColor8(color); 463 | uint8_t** lines = _pVideo->getFrameBufferLines(); 464 | 465 | startWrite(); 466 | // We can't do a single memset() because it is valid for _lines to point 467 | // into non-contingous pieces of memory. (Necessary when memory is 468 | // fragmented and we can't get a big enough chunk of contiguous bytes.) 469 | for(uint8_t y = 0; y <= MAX_Y; y++) 470 | { 471 | memset(lines[y], color8, 256); 472 | } 473 | endWrite(); 474 | } 475 | -------------------------------------------------------------------------------- /ESP_8_BIT_GFX.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Adafruit_GFX for ESP_8_BIT color composite video. 4 | 5 | NOT AN OFFICIAL ADAFRUIT GRAPHICS LIBRARY. 6 | 7 | Allows ESP32 Arduino sketches to draw to a composite video device using 8 | Adafruit's graphics API. 9 | 10 | NOTE RE:COLOR 11 | 12 | Adafruit GFX is designed for 16-bit (RGB565) color, but ESP_8_BIT video 13 | only handles 8-bit (RGB332) color. There are two ways to handle this, 14 | depending on passsing "8" or "16" into the constructor: 15 | 16 | 8 = Truncate the 16-bit color values and use the lower 8 bits directly as 17 | RGB332 color. This is faster, but caller needs to know to use 8-bit 18 | color values. A good choice when writing new code using this library. 19 | 16 = Automatically extract the most significant 3 red, 3 green, and 2 blue 20 | bits from a 16-bit RGB565 color value to generate a RGB332 color. 21 | Performing this conversion slows down the code, but the caller does not 22 | need to know about the limitations. A good choice when reusing existing 23 | Adafruit GFX code that works in 16-bit color space. 24 | 25 | An utility function RGB565toRGB332 is available to perform this conversion. 26 | 27 | NOTE RE:ASPECT RATIO 28 | 29 | Adafruit GFX assumes pixels are square, but this is not true of ESP_8_BIT 30 | which has nonsquare pixels. (4:3 aspect ratio in a 256x240 frame buffer.) 31 | Circles will look squashed as wide ovals, etc. This version of the API does 32 | not offer any way to compensate, the caller has to deal with it. 33 | 34 | 35 | 36 | Copyright (c) Roger Cheng 37 | 38 | MIT License 39 | 40 | Permission is hereby granted, free of charge, to any person obtaining a copy 41 | of this software and associated documentation files (the "Software"), to deal 42 | in the Software without restriction, including without limitation the rights 43 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 44 | copies of the Software, and to permit persons to whom the Software is 45 | furnished to do so, subject to the following conditions: 46 | The above copyright notice and this permission notice shall be included in all 47 | copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 50 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 51 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 52 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 53 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 54 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 55 | SOFTWARE. 56 | 57 | */ 58 | 59 | #ifndef ESP_8_BIT_GFX_H 60 | #define ESP_8_BIT_GFX_H 61 | 62 | #include "Arduino.h" 63 | #include "Adafruit_GFX.h" 64 | #include "ESP_8_BIT_composite.h" 65 | 66 | /* 67 | * @brief Expose Adafruit GFX API for ESP_8_BIT composite video generator 68 | */ 69 | class ESP_8_BIT_GFX : public Adafruit_GFX { 70 | public: 71 | /* 72 | * @brief Constructor 73 | * @param ntsc true for NTSC, false for PAL 74 | * @param colorDepth 8 to treat color as 8-bit directly, 16 to perform 75 | * downconversion from 16-bit RGB565 color to 8-bit RGB332. 76 | */ 77 | ESP_8_BIT_GFX(bool ntsc, uint8_t colorDepth); 78 | 79 | /* 80 | * @brief Call once to set up the API with self-allocated frame buffer. 81 | */ 82 | void begin(); 83 | 84 | /* 85 | * @brief Wait for swap of front and back buffer. Gathers performance 86 | * metrics while waiting. 87 | */ 88 | void waitForFrame(); 89 | 90 | /* 91 | * @brief Fraction of time in waitForFrame() in percent of percent. 92 | * @return Number range from 0 to 10000. Higher values indicate more time 93 | * has been spent waiting for buffer swap, implying the rest of the code 94 | * ran faster and completed more quickly. 95 | */ 96 | uint32_t getWaitFraction(); 97 | 98 | /* 99 | * @brief Ends the current performance tracking session and start a new 100 | * one. Useful for isolating sections of code for measurement. 101 | * @note Sessions are still terminated whenever CPU clock counter 102 | * overflows (every ~18 seconds @ 240MHz) so some data may still be lost. 103 | * @return Number range from 0 to 10000. Higher values indicate more time 104 | * has been spent waiting for buffer swap, implying the rest of the code 105 | * ran faster and completed more quickly. 106 | */ 107 | uint32_t newPerformanceTrackingSession(); 108 | 109 | /* 110 | * @brief Utility to convert from 16-bit RGB565 color to 8-bit RGB332 color 111 | */ 112 | uint8_t convertRGB565toRGB332(uint16_t color); 113 | 114 | /* 115 | * @brief Required Adafruit_GFX override to put a pixel on screen 116 | */ 117 | void drawPixel(int16_t x, int16_t y, uint16_t color) override; 118 | 119 | /* 120 | * @brief Optional Adafruit_GFX overrides for performance 121 | */ 122 | void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) override; 123 | void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) override; 124 | void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) override; 125 | void fillScreen(uint16_t color) override; 126 | 127 | /* 128 | * @brief Set this to true if the frame buffer should be copied upon every 129 | * swap of the front/back buffer. Defaults to false. 130 | * @note Some graphics libraries act on delta from previous frame, so the 131 | * front and buffers need to be in sync to avoid visual artifacts. 132 | */ 133 | bool copyAfterSwap; 134 | private: 135 | /* 136 | * @brief Given input X-coordinate, return value clamped within valid range. 137 | */ 138 | int16_t clampX(int16_t inputX); 139 | 140 | /* 141 | * @brief Given input Y-coordinate, return value clamped within valid range. 142 | */ 143 | int16_t clampY(int16_t inputY); 144 | 145 | /* 146 | * @brief Whether to treat color as 8 or 16 bit color values 147 | */ 148 | uint8_t _colorDepth; 149 | 150 | /* 151 | * @brief Internal reference to ESP_8_BIT video generator wrapper class 152 | */ 153 | ESP_8_BIT_composite* _pVideo; 154 | 155 | /* 156 | * @brief Retrieve color to use depending on _colorDepth 157 | */ 158 | uint8_t getColor8(uint16_t color); 159 | 160 | 161 | ///////////////////////////////////////////////////////////////////////// 162 | // 163 | // Performance metric data 164 | // 165 | // The Tensilica core in an ESP32 keeps a count of clock cycles read via 166 | // xthal_get_ccount(). This is only a 32-bit unsigned value. So when the 167 | // core is running at 240MHz we have just under 18 seconds before this 168 | // value overflows. 169 | // 170 | // Rather than trying to make error-prone and expensive calculations to 171 | // account for clock count overflows, this performance tracking is 172 | // divided up into sessions. Every ~18 seconds the clock count overflow, 173 | // we start a new session. Performance data of gaps between sessions 174 | // are lost. 175 | // 176 | // Each sessions retrieves from the underlying rendering class two pieces 177 | // of data: the number of frames rendered to screen and the number of 178 | // buffer swaps performed. These are uint32_t. When they overflow, the 179 | // frame count related statistics will be nonsensical for that session. 180 | // The values should make sense again for the following session. 181 | // 182 | // Performance data is only gathered during waitForFrame(), which assumes 183 | // the application is calling waitForFrame() at high rate so we can 184 | // sample performance data. Applications that do not call waitForFrame() 185 | // frequently may experience large session gaps of lost data. If 186 | // waitForFrame() is not called for more than 18 seconds, the data will 187 | // be nonsensical. Fortunately applications that do not make frequent 188 | // frame updates are probably not concerned with performance data anyway. 189 | // 190 | // Clock cycle count is a value kept by a core. They are not synchronized 191 | // across multiple ESP32 cores. Trying to calculate from cycle counts 192 | // from different cores will result in nonsensical data. This is usually 193 | // not a problem as the typical usage has Arduino runtime pinned to a 194 | // single core. 195 | // 196 | // These metrics track the number of clocks we spend waiting, but that 197 | // includes both idle clock cycles and clock cycles consumed by other 198 | // code. Including our underlying rendering class! The percentage is 199 | // valid for relative comparisons. "Algorithm A leaves lower percentage 200 | // waiting than B, so B is faster" is a valid conclusion. However 201 | // inferring from absolute numbers are not valid. For example "We wait 202 | // 50% of the time so we have enough power for twice the work" would be 203 | // wrong. Some of that 50% wait time is used by other code and not free 204 | // for use. 205 | // 206 | // The tradeoff for the limitations above is that we have a very 207 | // lightweight performance tracker that imposes minimal overhead. But 208 | // take care interpreting its numbers! 209 | 210 | /* 211 | * @brief Number of clock counts spent waiting for frame buffer swap. 212 | */ 213 | uint32_t _waitTally; 214 | 215 | /* 216 | * @brief Clock count value at the start of a session. 217 | */ 218 | uint32_t _perfStart; 219 | 220 | /* 221 | * @brief Clock count value at the end of a session. 222 | */ 223 | uint32_t _perfEnd; 224 | 225 | /* 226 | * @brief Number of frames rendered at the start of a session 227 | */ 228 | uint32_t _frameStart; 229 | 230 | /* 231 | * @brief Number of buffer swaps performed at the start of a session 232 | */ 233 | uint32_t _swapStart; 234 | 235 | /* 236 | * @brief Calculate performance metrics, output as INFO log. 237 | * @return Number range from 0 to 10000. Higher values indicate more time 238 | * has been spent waiting for buffer swap, implying the rest of the code 239 | * ran faster and completed more quickly. 240 | */ 241 | uint32_t perfData(); 242 | }; 243 | 244 | #endif // #ifndef ESP_8_BIT_GFX_H 245 | -------------------------------------------------------------------------------- /ESP_8_BIT_composite.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020, Peter Barrett 2 | ** 3 | ** Permission to use, copy, modify, and/or distribute this software for 4 | ** any purpose with or without fee is hereby granted, provided that the 5 | ** above copyright notice and this permission notice appear in all copies. 6 | ** 7 | ** THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 8 | ** WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 9 | ** WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 10 | ** BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES 11 | ** OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 12 | ** WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 13 | ** ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 14 | ** SOFTWARE. 15 | */ 16 | 17 | /* 18 | ** Extracted from Peter Barrett's ESP_8_BIT project and adapted to Arduino 19 | ** library by Roger Cheng 20 | */ 21 | 22 | #include "ESP_8_BIT_composite.h" 23 | 24 | static const char *TAG = "ESP_8_BIT"; 25 | 26 | static ESP_8_BIT_composite* _instance_ = NULL; 27 | static int _pal_ = 0; 28 | 29 | //==================================================================================================== 30 | //==================================================================================================== 31 | // 32 | // low level HW setup of DAC/DMA/APLL/PWM 33 | // 34 | 35 | lldesc_t _dma_desc[4] = {0}; 36 | intr_handle_t _isr_handle; 37 | 38 | extern "C" 39 | void IRAM_ATTR video_isr(volatile void* buf); 40 | 41 | // simple isr 42 | void IRAM_ATTR i2s_intr_handler_video(void *arg) 43 | { 44 | if (I2S0.int_st.out_eof) 45 | video_isr(((lldesc_t*)I2S0.out_eof_des_addr)->buf); // get the next line of video 46 | I2S0.int_clr.val = I2S0.int_st.val; // reset the interrupt 47 | } 48 | 49 | static esp_err_t start_dma(int line_width,int samples_per_cc, int ch = 1) 50 | { 51 | periph_module_enable(PERIPH_I2S0_MODULE); 52 | 53 | // setup interrupt 54 | if (esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, 55 | i2s_intr_handler_video, 0, &_isr_handle) != ESP_OK) 56 | return -1; 57 | 58 | // reset conf 59 | I2S0.conf.val = 1; 60 | I2S0.conf.val = 0; 61 | I2S0.conf.tx_right_first = 1; 62 | I2S0.conf.tx_mono = (ch == 2 ? 0 : 1); 63 | 64 | I2S0.conf2.lcd_en = 1; 65 | I2S0.fifo_conf.tx_fifo_mod_force_en = 1; 66 | I2S0.sample_rate_conf.tx_bits_mod = 16; 67 | I2S0.conf_chan.tx_chan_mod = (ch == 2) ? 0 : 1; 68 | 69 | // Create TX DMA buffers 70 | for (int i = 0; i < 2; i++) { 71 | int n = line_width*2*ch; 72 | if (n >= 4092) { 73 | printf("DMA chunk too big:%d\n",n); 74 | return -1; 75 | } 76 | _dma_desc[i].buf = (uint8_t*)heap_caps_calloc(1, n, MALLOC_CAP_DMA); 77 | if (!_dma_desc[i].buf) 78 | return -1; 79 | 80 | _dma_desc[i].owner = 1; 81 | _dma_desc[i].eof = 1; 82 | _dma_desc[i].length = n; 83 | _dma_desc[i].size = n; 84 | _dma_desc[i].empty = (uint32_t)(i == 1 ? _dma_desc : _dma_desc+1); 85 | } 86 | I2S0.out_link.addr = (uint32_t)_dma_desc; 87 | 88 | // Setup up the apll: See ref 3.2.7 Audio PLL 89 | // f_xtal = (int)rtc_clk_xtal_freq_get() * 1000000; 90 | // f_out = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536); // 250 < f_out < 500 91 | // apll_freq = f_out/((o_div + 2) * 2) 92 | // operating range of the f_out is 250 MHz ~ 500 MHz 93 | // operating range of the apll_freq is 16 ~ 128 MHz. 94 | // select sdm0,sdm1,sdm2 to produce nice multiples of colorburst frequencies 95 | 96 | // see calc_freq() for math: (4+a)*10/((2 + b)*2) mhz 97 | // up to 20mhz seems to work ok: 98 | // rtc_clk_apll_enable(1,0x00,0x00,0x4,0); // 20mhz for fancy DDS 99 | 100 | if (!_pal_) { 101 | switch (samples_per_cc) { 102 | case 3: rtc_clk_apll_enable(1,0x46,0x97,0x4,2); break; // 10.7386363636 3x NTSC (10.7386398315mhz) 103 | case 4: rtc_clk_apll_enable(1,0x46,0x97,0x4,1); break; // 14.3181818182 4x NTSC (14.3181864421mhz) 104 | } 105 | } else { 106 | rtc_clk_apll_enable(1,0x04,0xA4,0x6,1); // 17.734476mhz ~4x PAL 107 | } 108 | 109 | I2S0.clkm_conf.clkm_div_num = 1; // I2S clock divider’s integral value. 110 | I2S0.clkm_conf.clkm_div_b = 0; // Fractional clock divider’s numerator value. 111 | I2S0.clkm_conf.clkm_div_a = 1; // Fractional clock divider’s denominator value 112 | I2S0.sample_rate_conf.tx_bck_div_num = 1; 113 | I2S0.clkm_conf.clka_en = 1; // Set this bit to enable clk_apll. 114 | I2S0.fifo_conf.tx_fifo_mod = (ch == 2) ? 0 : 1; // 32-bit dual or 16-bit single channel data 115 | 116 | dac_output_enable(DAC_CHANNEL_1); // DAC, video on GPIO25 117 | dac_i2s_enable(); // start DAC! 118 | 119 | I2S0.conf.tx_start = 1; // start DMA! 120 | I2S0.int_clr.val = 0xFFFFFFFF; 121 | I2S0.int_ena.out_eof = 1; 122 | I2S0.out_link.start = 1; 123 | return esp_intr_enable(_isr_handle); // start interruprs! 124 | } 125 | 126 | void video_init_hw(int line_width, int samples_per_cc) 127 | { 128 | // setup apll 4x NTSC or PAL colorburst rate 129 | start_dma(line_width,samples_per_cc,1); 130 | 131 | // Now ideally we would like to use the decoupled left DAC channel to produce audio 132 | // But when using the APLL there appears to be some clock domain conflict that causes 133 | // nasty digitial spikes and dropouts. 134 | } 135 | 136 | //==================================================================================================== 137 | //==================================================================================================== 138 | 139 | 140 | // ntsc phase representation of a rrrgggbb pixel 141 | // must be in RAM so VBL works 142 | const static DRAM_ATTR uint32_t ntsc_RGB332[256] = { 143 | 0x18181818,0x18171A1C,0x1A151D22,0x1B141F26,0x1D1C1A1B,0x1E1B1C20,0x20191F26,0x2119222A, 144 | 0x23201C1F,0x241F1E24,0x251E222A,0x261D242E,0x29241F23,0x2A232128,0x2B22242E,0x2C212632, 145 | 0x2E282127,0x2F27232C,0x31262732,0x32252936,0x342C232B,0x352B2630,0x372A2936,0x38292B3A, 146 | 0x3A30262F,0x3B2F2833,0x3C2E2B3A,0x3D2D2E3E,0x40352834,0x41342B38,0x43332E3E,0x44323042, 147 | 0x181B1B18,0x191A1D1C,0x1B192022,0x1C182327,0x1E1F1D1C,0x1F1E2020,0x201D2326,0x211C252B, 148 | 0x24232020,0x25222224,0x2621252A,0x2720272F,0x29272224,0x2A262428,0x2C25282E,0x2D242A33, 149 | 0x2F2B2428,0x302A272C,0x32292A32,0x33282C37,0x352F272C,0x362E2930,0x372D2C36,0x382C2F3B, 150 | 0x3B332930,0x3C332B34,0x3D312F3A,0x3E30313F,0x41382C35,0x42372E39,0x4336313F,0x44353443, 151 | 0x191E1E19,0x1A1D211D,0x1B1C2423,0x1C1B2628,0x1F22211D,0x20212321,0x21202627,0x221F292C, 152 | 0x24262321,0x25252525,0x2724292B,0x28232B30,0x2A2A2625,0x2B292829,0x2D282B2F,0x2E272D34, 153 | 0x302E2829,0x312E2A2D,0x322C2D33,0x332B3038,0x36332A2D,0x37322C31,0x38303037,0x392F323C, 154 | 0x3B372D31,0x3C362F35,0x3E35323B,0x3F343440,0x423B2F36,0x423A313A,0x44393540,0x45383744, 155 | 0x1A21221A,0x1B20241E,0x1C1F2724,0x1D1E2A29,0x1F25241E,0x20242622,0x22232A28,0x23222C2D, 156 | 0x25292722,0x26292926,0x27272C2C,0x28262E30,0x2B2E2926,0x2C2D2B2A,0x2D2B2E30,0x2E2A3134, 157 | 0x31322B2A,0x32312E2E,0x332F3134,0x342F3338,0x36362E2E,0x37353032,0x39343338,0x3A33363C, 158 | 0x3C3A3032,0x3D393236,0x3E38363C,0x3F373840,0x423E3337,0x433E353B,0x453C3841,0x463B3A45, 159 | 0x1A24251B,0x1B24271F,0x1D222B25,0x1E212D29,0x2029281F,0x21282A23,0x22262D29,0x23252F2D, 160 | 0x262D2A23,0x272C2C27,0x282A2F2D,0x292A3231,0x2C312C27,0x2C302F2B,0x2E2F3231,0x2F2E3435, 161 | 0x31352F2B,0x3234312F,0x34333435,0x35323739,0x3739312F,0x38383333,0x39373739,0x3A36393D, 162 | 0x3D3D3433,0x3E3C3637,0x3F3B393D,0x403A3B41,0x43423637,0x4441383B,0x453F3C42,0x463F3E46, 163 | 0x1B28291C,0x1C272B20,0x1D252E26,0x1E25302A,0x212C2B20,0x222B2D24,0x232A312A,0x2429332E, 164 | 0x26302D24,0x272F3028,0x292E332E,0x2A2D3532,0x2C343028,0x2D33322C,0x2F323532,0x30313836, 165 | 0x3238322C,0x33373430,0x34363836,0x35353A3A,0x383C3530,0x393B3734,0x3A3A3A3A,0x3B393C3E, 166 | 0x3D403734,0x3E403938,0x403E3C3E,0x413D3F42,0x44453A38,0x45443C3C,0x46433F42,0x47424147, 167 | 0x1C2B2C1D,0x1D2A2E21,0x1E293227,0x1F28342B,0x212F2E21,0x222E3125,0x242D342B,0x252C362F, 168 | 0x27333125,0x28323329,0x2A31362F,0x2B303933,0x2D373329,0x2E36352D,0x2F353933,0x30343B37, 169 | 0x333B362D,0x343B3831,0x35393B37,0x36383D3B,0x38403831,0x393F3A35,0x3B3D3E3B,0x3C3C403F, 170 | 0x3E443A35,0x3F433D39,0x4141403F,0x42414243,0x44483D39,0x45473F3D,0x47464243,0x48454548, 171 | 0x1C2E301E,0x1D2E3222,0x1F2C3528,0x202B382C,0x22333222,0x23323426,0x2530382C,0x262F3A30, 172 | 0x28373526,0x2936372A,0x2A343A30,0x2B343C34,0x2E3B372A,0x2F3A392E,0x30393C34,0x31383F38, 173 | 0x333F392E,0x343E3C32,0x363D3F38,0x373C413C,0x39433C32,0x3A423E36,0x3C41413C,0x3D404440, 174 | 0x3F473E36,0x4046403A,0x41454440,0x42444644,0x454C413A,0x464B433E,0x47494644,0x49494949, 175 | }; 176 | 177 | // PAL yuyv palette, must be in RAM 178 | const static DRAM_ATTR uint32_t pal_yuyv[] = { 179 | 0x18181818,0x1A16191E,0x1E121A26,0x21101A2C,0x1E1D1A1B,0x211B1A20,0x25171B29,0x27151C2E, 180 | 0x25231B1E,0x27201C23,0x2B1D1D2B,0x2E1A1E31,0x2B281D20,0x2E261E26,0x31221F2E,0x34202034, 181 | 0x322D1F23,0x342B2029,0x38282131,0x3A252137,0x38332126,0x3A30212B,0x3E2D2234,0x412A2339, 182 | 0x3E382229,0x4136232E,0x44322436,0x4730253C,0x453E242C,0x483C2531,0x4B382639,0x4E36273F, 183 | 0x171B1D19,0x1A181E1F,0x1D151F27,0x20121F2D,0x1E201F1C,0x201E1F22,0x241A202A,0x26182130, 184 | 0x2425201F,0x27232124,0x2A20222D,0x2D1D2332,0x2A2B2222,0x2D282327,0x3125242F,0x33222435, 185 | 0x31302424,0x332E242A,0x372A2632,0x3A282638,0x37362627,0x3A33262D,0x3D302735,0x402D283B, 186 | 0x3E3B272A,0x4039282F,0x44352938,0x46332A3D,0x4441292D,0x473E2A32,0x4B3B2B3B,0x4D382C40, 187 | 0x171D221B,0x191B2220,0x1D182329,0x1F15242E,0x1D23231E,0x1F202423,0x231D252B,0x261A2631, 188 | 0x23282520,0x26262626,0x2A22272E,0x2C202834,0x2A2E2723,0x2C2B2829,0x30282931,0x33252937, 189 | 0x30332926,0x3331292B,0x362D2A34,0x392B2B39,0x36382A29,0x39362B2E,0x3D322C36,0x3F302D3C, 190 | 0x3D3E2C2B,0x3F3B2D31,0x43382E39,0x46352F3F,0x44432E2E,0x46412F34,0x4A3E303C,0x4D3B3042, 191 | 0x1620271C,0x181E2722,0x1C1A282A,0x1F182930,0x1C26281F,0x1F232924,0x22202A2D,0x251D2B32, 192 | 0x232B2A22,0x25292B27,0x29252C30,0x2B232C35,0x29302C24,0x2C2E2C2A,0x2F2A2D32,0x32282E38, 193 | 0x2F362D27,0x32332E2D,0x36302F35,0x382D303B,0x363B2F2A,0x38393030,0x3C353138,0x3F33323E, 194 | 0x3C40312D,0x3F3E3232,0x423A333B,0x45383340,0x43463330,0x46443435,0x4940353E,0x4C3E3543, 195 | 0x15232B1E,0x18212C23,0x1B1D2D2B,0x1E1B2E31,0x1C282D20,0x1E262E26,0x22222F2E,0x24202F34, 196 | 0x222E2F23,0x242B3029,0x28283131,0x2B253137,0x28333126,0x2B31312B,0x2F2D3234,0x312B3339, 197 | 0x2F383229,0x3136332E,0x35323436,0x3730353C,0x353E342B,0x383B3531,0x3B383639,0x3E35363F, 198 | 0x3B43362E,0x3E413634,0x423D373C,0x443B3842,0x42493831,0x45473837,0x4943393F,0x4B413A45, 199 | 0x1526301F,0x17233125,0x1B20322D,0x1D1D3333,0x1B2B3222,0x1D293327,0x21253430,0x24233435, 200 | 0x21303425,0x242E342A,0x272A3532,0x2A283638,0x28363527,0x2A33362D,0x2E303735,0x302D383B, 201 | 0x2E3B372A,0x30393830,0x34353938,0x37333A3E,0x3440392D,0x373E3A32,0x3B3B3B3B,0x3D383B40, 202 | 0x3B463B30,0x3D433B35,0x41403C3D,0x443D3D43,0x424C3D33,0x44493D38,0x48463E40,0x4A433F46, 203 | 0x14283520,0x16263626,0x1A23372E,0x1D203734,0x1A2E3723,0x1D2B3729,0x20283831,0x23253937, 204 | 0x21333826,0x2331392B,0x272D3A34,0x292B3B39,0x27383A29,0x29363B2E,0x2D333C36,0x30303D3C, 205 | 0x2D3E3C2B,0x303B3D31,0x34383E39,0x36363E3F,0x34433E2E,0x36413E34,0x3A3D3F3C,0x3C3B4042, 206 | 0x3A493F31,0x3D464036,0x4043413F,0x43404244,0x414E4134,0x434C4239,0x47484342,0x4A464447, 207 | 0x132B3A22,0x16293B27,0x19253C30,0x1C233D35,0x19313C25,0x1C2E3D2A,0x202B3E32,0x22283E38, 208 | 0x20363E27,0x22343E2D,0x26303F35,0x292E403B,0x263B3F2A,0x29394030,0x2C364138,0x2F33423E, 209 | 0x2D41412D,0x2F3E4232,0x333B433B,0x35384440,0x33464330,0x35444435,0x3940453E,0x3C3E4543, 210 | 0x394C4533,0x3C494538,0x40464640,0x42434746,0x40514735,0x434F473B,0x464B4843,0x49494949, 211 | //odd 212 | 0x18181818,0x19161A1E,0x1A121E26,0x1A10212C,0x1A1D1E1B,0x1A1B2120,0x1B172529,0x1C15272E, 213 | 0x1B23251E,0x1C202723,0x1D1D2B2B,0x1E1A2E31,0x1D282B20,0x1E262E26,0x1F22312E,0x20203434, 214 | 0x1F2D3223,0x202B3429,0x21283831,0x21253A37,0x21333826,0x21303A2B,0x222D3E34,0x232A4139, 215 | 0x22383E29,0x2336412E,0x24324436,0x2530473C,0x243E452C,0x253C4831,0x26384B39,0x27364E3F, 216 | 0x1D1B1719,0x1E181A1F,0x1F151D27,0x1F12202D,0x1F201E1C,0x1F1E2022,0x201A242A,0x21182630, 217 | 0x2025241F,0x21232724,0x22202A2D,0x231D2D32,0x222B2A22,0x23282D27,0x2425312F,0x24223335, 218 | 0x24303124,0x242E332A,0x262A3732,0x26283A38,0x26363727,0x26333A2D,0x27303D35,0x282D403B, 219 | 0x273B3E2A,0x2839402F,0x29354438,0x2A33463D,0x2941442D,0x2A3E4732,0x2B3B4B3B,0x2C384D40, 220 | 0x221D171B,0x221B1920,0x23181D29,0x24151F2E,0x23231D1E,0x24201F23,0x251D232B,0x261A2631, 221 | 0x25282320,0x26262626,0x27222A2E,0x28202C34,0x272E2A23,0x282B2C29,0x29283031,0x29253337, 222 | 0x29333026,0x2931332B,0x2A2D3634,0x2B2B3939,0x2A383629,0x2B36392E,0x2C323D36,0x2D303F3C, 223 | 0x2C3E3D2B,0x2D3B3F31,0x2E384339,0x2F35463F,0x2E43442E,0x2F414634,0x303E4A3C,0x303B4D42, 224 | 0x2720161C,0x271E1822,0x281A1C2A,0x29181F30,0x28261C1F,0x29231F24,0x2A20222D,0x2B1D2532, 225 | 0x2A2B2322,0x2B292527,0x2C252930,0x2C232B35,0x2C302924,0x2C2E2C2A,0x2D2A2F32,0x2E283238, 226 | 0x2D362F27,0x2E33322D,0x2F303635,0x302D383B,0x2F3B362A,0x30393830,0x31353C38,0x32333F3E, 227 | 0x31403C2D,0x323E3F32,0x333A423B,0x33384540,0x33464330,0x34444635,0x3540493E,0x353E4C43, 228 | 0x2B23151E,0x2C211823,0x2D1D1B2B,0x2E1B1E31,0x2D281C20,0x2E261E26,0x2F22222E,0x2F202434, 229 | 0x2F2E2223,0x302B2429,0x31282831,0x31252B37,0x31332826,0x31312B2B,0x322D2F34,0x332B3139, 230 | 0x32382F29,0x3336312E,0x34323536,0x3530373C,0x343E352B,0x353B3831,0x36383B39,0x36353E3F, 231 | 0x36433B2E,0x36413E34,0x373D423C,0x383B4442,0x38494231,0x38474537,0x3943493F,0x3A414B45, 232 | 0x3026151F,0x31231725,0x32201B2D,0x331D1D33,0x322B1B22,0x33291D27,0x34252130,0x34232435, 233 | 0x34302125,0x342E242A,0x352A2732,0x36282A38,0x35362827,0x36332A2D,0x37302E35,0x382D303B, 234 | 0x373B2E2A,0x38393030,0x39353438,0x3A33373E,0x3940342D,0x3A3E3732,0x3B3B3B3B,0x3B383D40, 235 | 0x3B463B30,0x3B433D35,0x3C40413D,0x3D3D4443,0x3D4C4233,0x3D494438,0x3E464840,0x3F434A46, 236 | 0x35281420,0x36261626,0x37231A2E,0x37201D34,0x372E1A23,0x372B1D29,0x38282031,0x39252337, 237 | 0x38332126,0x3931232B,0x3A2D2734,0x3B2B2939,0x3A382729,0x3B36292E,0x3C332D36,0x3D30303C, 238 | 0x3C3E2D2B,0x3D3B3031,0x3E383439,0x3E36363F,0x3E43342E,0x3E413634,0x3F3D3A3C,0x403B3C42, 239 | 0x3F493A31,0x40463D36,0x4143403F,0x42404344,0x414E4134,0x424C4339,0x43484742,0x44464A47, 240 | 0x3A2B1322,0x3B291627,0x3C251930,0x3D231C35,0x3C311925,0x3D2E1C2A,0x3E2B2032,0x3E282238, 241 | 0x3E362027,0x3E34222D,0x3F302635,0x402E293B,0x3F3B262A,0x40392930,0x41362C38,0x42332F3E, 242 | 0x41412D2D,0x423E2F32,0x433B333B,0x44383540,0x43463330,0x44443535,0x4540393E,0x453E3C43, 243 | 0x454C3933,0x45493C38,0x46464040,0x47434246,0x47514035,0x474F433B,0x484B4643,0x49494949, 244 | }; 245 | 246 | //==================================================================================================== 247 | //==================================================================================================== 248 | 249 | uint32_t cpu_ticks() 250 | { 251 | return xthal_get_ccount(); 252 | } 253 | 254 | uint32_t us() { 255 | return cpu_ticks()/240; 256 | } 257 | 258 | // Color clock frequency is 315/88 (3.57954545455) 259 | // DAC_MHZ is 315/11 or 8x color clock 260 | // 455/2 color clocks per line, round up to maintain phase 261 | // HSYNCH period is 44/315*455 or 63.55555..us 262 | // Field period is 262*44/315*455 or 16651.5555us 263 | 264 | #define IRE(_x) ((uint32_t)(((_x)+40)*255/3.3/147.5) << 8) // 3.3V DAC 265 | #define SYNC_LEVEL IRE(-40) 266 | #define BLANKING_LEVEL IRE(0) 267 | #define BLACK_LEVEL IRE(7.5) 268 | #define GRAY_LEVEL IRE(50) 269 | #define WHITE_LEVEL IRE(100) 270 | 271 | 272 | #define P0 (color >> 16) 273 | #define P1 (color >> 8) 274 | #define P2 (color) 275 | #define P3 (color << 8) 276 | 277 | // Double-buffering: _bufferA and _bufferB will be swapped back and forth. 278 | static uint8_t** _bufferA; 279 | static uint8_t** _bufferB; 280 | 281 | // _lines may point to either _bufferA or _bufferB, depending on which is being displayed 282 | // _backBuffer points to whichever one _lines is not pointing to 283 | static uint8_t** _lines; // Front buffer currently on display 284 | static uint8_t** _backBuffer; // Back buffer waiting to be swapped to front 285 | 286 | // TRUE when _backBuffer is ready to go. 287 | static bool _swapReady; 288 | 289 | // Notification handle once front and back buffers have been swapped. 290 | static TaskHandle_t _swapCompleteNotify; 291 | 292 | // Number of swaps completed 293 | static uint32_t _swap_counter = 0; 294 | 295 | volatile int _line_counter = 0; 296 | volatile uint32_t _frame_counter = 0; 297 | 298 | int _active_lines; 299 | int _line_count; 300 | 301 | int _line_width; 302 | int _samples_per_cc; 303 | const uint32_t* _palette; 304 | 305 | float _sample_rate; 306 | 307 | int _hsync; 308 | int _hsync_long; 309 | int _hsync_short; 310 | int _burst_start; 311 | int _burst_width; 312 | int _active_start; 313 | 314 | int16_t* _burst0 = 0; // pal bursts 315 | int16_t* _burst1 = 0; 316 | 317 | static int usec(float us) 318 | { 319 | uint32_t r = (uint32_t)(us*_sample_rate); 320 | return ((r + _samples_per_cc)/(_samples_per_cc << 1))*(_samples_per_cc << 1); // multiple of color clock, word align 321 | } 322 | 323 | #define NTSC_COLOR_CLOCKS_PER_SCANLINE 228 // really 227.5 for NTSC but want to avoid half phase fiddling for now 324 | #define NTSC_FREQUENCY (315000000.0/88) 325 | #define NTSC_LINES 262 326 | 327 | #define PAL_COLOR_CLOCKS_PER_SCANLINE 284 // really 283.75 ? 328 | #define PAL_FREQUENCY 4433618.75 329 | #define PAL_LINES 312 330 | 331 | void pal_init(); 332 | 333 | void video_init(int samples_per_cc, int ntsc) 334 | { 335 | _samples_per_cc = samples_per_cc; 336 | 337 | if (ntsc) { 338 | _sample_rate = 315.0/88 * samples_per_cc; // DAC rate 339 | _line_width = NTSC_COLOR_CLOCKS_PER_SCANLINE*samples_per_cc; 340 | _line_count = NTSC_LINES; 341 | _hsync_long = usec(63.555-4.7); 342 | _active_start = usec(samples_per_cc == 4 ? 10 : 10.5); 343 | _hsync = usec(4.7); 344 | _palette = ntsc_RGB332; 345 | _pal_ = 0; 346 | } else { 347 | pal_init(); 348 | _palette = pal_yuyv; 349 | _pal_ = 1; 350 | } 351 | 352 | _active_lines = 240; 353 | video_init_hw(_line_width,_samples_per_cc); // init the hardware 354 | } 355 | 356 | //=================================================================================================== 357 | //=================================================================================================== 358 | // PAL 359 | 360 | void pal_init() 361 | { 362 | int cc_width = 4; 363 | _sample_rate = PAL_FREQUENCY*cc_width/1000000.0; // DAC rate in mhz 364 | _line_width = PAL_COLOR_CLOCKS_PER_SCANLINE*cc_width; 365 | _line_count = PAL_LINES; 366 | _hsync_short = usec(2); 367 | _hsync_long = usec(30); 368 | _hsync = usec(4.7); 369 | _burst_start = usec(5.6); 370 | _burst_width = (int)(10*cc_width + 4) & 0xFFFE; 371 | _active_start = usec(10.4); 372 | 373 | // make colorburst tables for even and odd lines 374 | _burst0 = new int16_t[_burst_width]; 375 | _burst1 = new int16_t[_burst_width]; 376 | float phase = 2*M_PI/2; 377 | for (int i = 0; i < _burst_width; i++) 378 | { 379 | _burst0[i] = BLANKING_LEVEL + sin(phase + 3*M_PI/4) * BLANKING_LEVEL/1.5; 380 | _burst1[i] = BLANKING_LEVEL + sin(phase - 3*M_PI/4) * BLANKING_LEVEL/1.5; 381 | phase += 2*M_PI/cc_width; 382 | } 383 | } 384 | 385 | void IRAM_ATTR blit_pal(uint8_t* src, uint16_t* dst) 386 | { 387 | uint32_t c,color; 388 | bool even = _line_counter & 1; 389 | const uint32_t* p = even ? _palette : _palette + 256; 390 | int left = 0; 391 | int right = 256; 392 | uint8_t mask = 0xFF; 393 | 394 | // 192 of 288 color clocks wide: roughly correct aspect ratio 395 | dst += 88; 396 | 397 | // 4 pixels over 3 color clocks, 12 samples 398 | // do the blitting 399 | for (int i = left; i < right; i += 4) { 400 | c = *((uint32_t*)(src+i)); 401 | color = p[c & mask]; 402 | dst[0^1] = P0; 403 | dst[1^1] = P1; 404 | dst[2^1] = P2; 405 | color = p[(c >> 8) & mask]; 406 | dst[3^1] = P3; 407 | dst[4^1] = P0; 408 | dst[5^1] = P1; 409 | color = p[(c >> 16) & mask]; 410 | dst[6^1] = P2; 411 | dst[7^1] = P3; 412 | dst[8^1] = P0; 413 | color = p[(c >> 24) & mask]; 414 | dst[9^1] = P1; 415 | dst[10^1] = P2; 416 | dst[11^1] = P3; 417 | dst += 12; 418 | } 419 | } 420 | 421 | void IRAM_ATTR burst_pal(uint16_t* line) 422 | { 423 | line += _burst_start; 424 | int16_t* b = (_line_counter & 1) ? _burst0 : _burst1; 425 | for (int i = 0; i < _burst_width; i += 2) { 426 | line[i^1] = b[i]; 427 | line[(i+1)^1] = b[i+1]; 428 | } 429 | } 430 | 431 | //=================================================================================================== 432 | //=================================================================================================== 433 | // ntsc tables 434 | // AA AA // 2 pixels, 1 color clock - atari 435 | // AA AB BB // 3 pixels, 2 color clocks - nes 436 | // AAA ABB BBC CCC // 4 pixels, 3 color clocks - sms 437 | 438 | // cc == 3 gives 684 samples per line, 3 samples per cc, 3 pixels for 2 cc 439 | // cc == 4 gives 912 samples per line, 4 samples per cc, 2 pixels per cc 440 | 441 | #ifdef PERF 442 | #define BEGIN_TIMING() uint32_t t = cpu_ticks() 443 | #define END_TIMING() t = cpu_ticks() - t; _blit_ticks_min = min(_blit_ticks_min,t); _blit_ticks_max = max(_blit_ticks_max,t); 444 | #define ISR_BEGIN() uint32_t t = cpu_ticks() 445 | #define ISR_END() t = cpu_ticks() - t;_isr_us += (t+120)/240; 446 | uint32_t _blit_ticks_min = 0; 447 | uint32_t _blit_ticks_max = 0; 448 | uint32_t _isr_us = 0; 449 | #else 450 | #define BEGIN_TIMING() 451 | #define END_TIMING() 452 | #define ISR_BEGIN() 453 | #define ISR_END() 454 | #endif 455 | 456 | // draw a line of game in NTSC 457 | void IRAM_ATTR blit(uint8_t* src, uint16_t* dst) 458 | { 459 | const uint32_t* p = _palette; 460 | uint32_t color,c; 461 | uint32_t mask = 0xFF; 462 | int i; 463 | 464 | BEGIN_TIMING(); 465 | if (_pal_) { 466 | blit_pal(src,dst); 467 | END_TIMING(); 468 | return; 469 | } 470 | 471 | // AAA ABB BBC CCC 472 | // 4 pixels, 3 color clocks, 4 samples per cc 473 | // each pixel gets 3 samples, 192 color clocks wide 474 | for (i = 0; i < 256; i += 4) { 475 | c = *((uint32_t*)(src+i)); 476 | color = p[c & mask]; 477 | dst[0^1] = P0; 478 | dst[1^1] = P1; 479 | dst[2^1] = P2; 480 | color = p[(c >> 8) & mask]; 481 | dst[3^1] = P3; 482 | dst[4^1] = P0; 483 | dst[5^1] = P1; 484 | color = p[(c >> 16) & mask]; 485 | dst[6^1] = P2; 486 | dst[7^1] = P3; 487 | dst[8^1] = P0; 488 | color = p[(c >> 24) & mask]; 489 | dst[9^1] = P1; 490 | dst[10^1] = P2; 491 | dst[11^1] = P3; 492 | dst += 12; 493 | } 494 | 495 | END_TIMING(); 496 | } 497 | 498 | void IRAM_ATTR burst(uint16_t* line) 499 | { 500 | if (_pal_) { 501 | burst_pal(line); 502 | return; 503 | } 504 | 505 | int i,phase; 506 | switch (_samples_per_cc) { 507 | case 4: 508 | // 4 samples per color clock 509 | for (i = _hsync; i < _hsync + (4*10); i += 4) { 510 | line[i+1] = BLANKING_LEVEL; 511 | line[i+0] = BLANKING_LEVEL + BLANKING_LEVEL/2; 512 | line[i+3] = BLANKING_LEVEL; 513 | line[i+2] = BLANKING_LEVEL - BLANKING_LEVEL/2; 514 | } 515 | break; 516 | case 3: 517 | // 3 samples per color clock 518 | phase = 0.866025*BLANKING_LEVEL/2; 519 | for (i = _hsync; i < _hsync + (3*10); i += 6) { 520 | line[i+1] = BLANKING_LEVEL; 521 | line[i+0] = BLANKING_LEVEL + phase; 522 | line[i+3] = BLANKING_LEVEL - phase; 523 | line[i+2] = BLANKING_LEVEL; 524 | line[i+5] = BLANKING_LEVEL + phase; 525 | line[i+4] = BLANKING_LEVEL - phase; 526 | } 527 | break; 528 | } 529 | } 530 | 531 | void IRAM_ATTR sync(uint16_t* line, int syncwidth) 532 | { 533 | for (int i = 0; i < syncwidth; i++) 534 | line[i] = SYNC_LEVEL; 535 | } 536 | 537 | void IRAM_ATTR blanking(uint16_t* line, bool vbl) 538 | { 539 | int syncwidth = vbl ? _hsync_long : _hsync; 540 | sync(line,syncwidth); 541 | for (int i = syncwidth; i < _line_width; i++) 542 | line[i] = BLANKING_LEVEL; 543 | if (!vbl) 544 | burst(line); // no burst during vbl 545 | } 546 | 547 | // Fancy pal non-interlace 548 | // http://martin.hinner.info/vga/pal.html 549 | void IRAM_ATTR pal_sync2(uint16_t* line, int width, int swidth) 550 | { 551 | swidth = swidth ? _hsync_long : _hsync_short; 552 | int i; 553 | for (i = 0; i < swidth; i++) 554 | line[i] = SYNC_LEVEL; 555 | for (; i < width; i++) 556 | line[i] = BLANKING_LEVEL; 557 | } 558 | 559 | uint8_t DRAM_ATTR _sync_type[8] = {0,0,0,3,3,2,0,0}; 560 | void IRAM_ATTR pal_sync(uint16_t* line, int i) 561 | { 562 | uint8_t t = _sync_type[i-304]; 563 | pal_sync2(line,_line_width/2, t & 2); 564 | pal_sync2(line+_line_width/2,_line_width/2, t & 1); 565 | } 566 | 567 | // Wait for front and back buffers to swap before starting drawing 568 | void video_sync() 569 | { 570 | if (!_lines) 571 | return; 572 | ulTaskNotifyTake(pdTRUE, portMAX_DELAY); 573 | } 574 | 575 | // Workhorse ISR handles audio and video updates 576 | extern "C" 577 | void IRAM_ATTR video_isr(volatile void* vbuf) 578 | { 579 | if (!_lines) 580 | return; 581 | 582 | ISR_BEGIN(); 583 | 584 | int i = _line_counter++; 585 | uint16_t* buf = (uint16_t*)vbuf; 586 | if (_pal_) { 587 | // pal 588 | if (i < 32) { 589 | blanking(buf,false); // pre render/black 0-32 590 | } else if (i < _active_lines + 32) { // active video 32-272 591 | sync(buf,_hsync); 592 | burst(buf); 593 | blit(_lines[i-32],buf + _active_start); 594 | } else if (i < 304) { // post render/black 272-304 595 | blanking(buf,false); 596 | } else { 597 | pal_sync(buf,i); // 8 lines of sync 304-312 598 | } 599 | } else { 600 | // ntsc 601 | if (i < _active_lines) { // active video 602 | sync(buf,_hsync); 603 | burst(buf); 604 | blit(_lines[i],buf + _active_start); 605 | 606 | } else if (i < (_active_lines + 5)) { // post render/black 607 | blanking(buf,false); 608 | 609 | } else if (i < (_active_lines + 8)) { // vsync 610 | blanking(buf,true); 611 | 612 | } else { // pre render/black 613 | blanking(buf,false); 614 | } 615 | } 616 | 617 | if (_line_counter == _line_count) { 618 | _line_counter = 0; // frame is done 619 | _frame_counter++; 620 | 621 | // Is the back buffer ready to go? 622 | if (_swapReady) { 623 | // Swap front and back buffers 624 | if (_lines == _bufferA) { 625 | _lines = _bufferB; 626 | _backBuffer = _bufferA; 627 | } else { 628 | _lines = _bufferA; 629 | _backBuffer = _bufferB; 630 | } 631 | _swapReady = false; 632 | _swap_counter++; 633 | 634 | // Signal video_sync() swap has completed 635 | vTaskNotifyGiveFromISR( 636 | _swapCompleteNotify, 637 | NULL); 638 | } 639 | } 640 | 641 | ISR_END(); 642 | } 643 | 644 | //=================================================================================================== 645 | //=================================================================================================== 646 | // Wrapper class 647 | 648 | /* 649 | * @brief Constructor for ESP_8_BIT composite video wrapper class 650 | * @param ntsc True (or nonzero) for NTSC mode, False (or zero) for PAL mode 651 | */ 652 | ESP_8_BIT_composite::ESP_8_BIT_composite(int ntsc) 653 | { 654 | _pal_ = !ntsc; 655 | if (NULL == _instance_) 656 | { 657 | _instance_ = this; 658 | } 659 | _started = false; 660 | _bufferA = NULL; 661 | _bufferB = NULL; 662 | } 663 | 664 | /* 665 | * @brief Destructor for ESP_8_BIT composite video wrapper class. 666 | */ 667 | ESP_8_BIT_composite::~ESP_8_BIT_composite() 668 | { 669 | if(_bufferA) 670 | { 671 | frameBufferFree(_bufferA); 672 | _bufferA= NULL; 673 | } 674 | if(_bufferB) 675 | { 676 | frameBufferFree(_bufferB); 677 | _bufferB= NULL; 678 | } 679 | if (_started) 680 | { 681 | // Free resources by mirroring everything allocated in start_dma() 682 | esp_intr_disable(_isr_handle); 683 | dac_i2s_disable(); 684 | dac_output_disable(DAC_CHANNEL_1); 685 | if (!_pal_) { 686 | rtc_clk_apll_enable(false,0x46,0x97,0x4,1); 687 | } else { 688 | rtc_clk_apll_enable(false,0x04,0xA4,0x6,1); 689 | } 690 | for (int i = 0; i < 2; i++) { 691 | heap_caps_free((void*)(_dma_desc[i].buf)); 692 | _dma_desc[i].buf = NULL; 693 | } 694 | // Missing: There doesn't seem to be a esp_intr_free() to go with esp_intr_alloc()? 695 | periph_module_disable(PERIPH_I2S0_MODULE); 696 | _started = false; 697 | } 698 | _lines = NULL; 699 | _backBuffer = NULL; 700 | _instance_ = NULL; 701 | } 702 | 703 | /* 704 | * @brief Check to ensure this instance is the first and only allowed instance 705 | */ 706 | void ESP_8_BIT_composite::instance_check() 707 | { 708 | if (_instance_ != this) 709 | { 710 | ESP_LOGE(TAG, "Only one instance of ESP_8_BIT_composite class is allowed."); 711 | ESP_ERROR_CHECK(ESP_FAIL); 712 | } 713 | } 714 | 715 | /* 716 | * @brief Video subsystem setup: allocate frame buffer and start engine 717 | */ 718 | void ESP_8_BIT_composite::begin() 719 | { 720 | instance_check(); 721 | 722 | if (_started) 723 | { 724 | ESP_LOGE(TAG, "begin() is only allowed to be called once."); 725 | ESP_ERROR_CHECK(ESP_FAIL); 726 | } 727 | _started = true; 728 | 729 | _bufferA = frameBufferAlloc(); 730 | _bufferB = frameBufferAlloc(); 731 | 732 | _lines = _bufferA; 733 | _backBuffer = _bufferB; 734 | 735 | // Initialize double-buffering infrastructure 736 | _swapReady = false; 737 | _swapCompleteNotify = xTaskGetCurrentTaskHandle(); 738 | 739 | // Start video signal generator 740 | video_init(4, !_pal_); 741 | } 742 | 743 | ///////////////////////////////////////////////////////////////////////////// 744 | // 745 | // Frame buffer memory allocation notes 746 | // 747 | // Architecture can tolerate each _line[i] being a separate chunk of memory 748 | // but allocating in tiny 256 byte chunks is inefficient. (16 bytes of 749 | // overhead per allocation.) On the opposite end, allocating the entire 750 | // buffer at once (256*240 = 60kB) demands a large contiguous chunk of 751 | // memory which might not exist if memory space is fragmented. 752 | // 753 | // Compromise: Allocate frame buffer in 4kB chunks. This means each 754 | // frame buffer is made of 15 4kB chunks instead of a single 60kB chunk. 755 | // 756 | // 14 extra allocations * 16 byte overhead = 224 extra bytes, worth it. 757 | 758 | const uint16_t linesPerFrame = 240; 759 | const uint16_t bytesPerLine = 256; 760 | const uint16_t linesPerChunk = 16; 761 | const uint16_t chunkSize = bytesPerLine*linesPerChunk; 762 | const uint16_t chunksPerFrame = 15; 763 | 764 | /* 765 | * @brief Allocate memory for frame buffer 766 | */ 767 | uint8_t** ESP_8_BIT_composite::frameBufferAlloc() 768 | { 769 | uint8_t** lineArray = NULL; 770 | uint8_t* lineChunk = NULL; 771 | uint8_t* lineStep = NULL; 772 | 773 | lineArray = new uint8_t*[linesPerFrame]; 774 | if ( NULL == lineArray ) 775 | { 776 | ESP_LOGE(TAG, "Frame lines array allocation fail"); 777 | ESP_ERROR_CHECK(ESP_FAIL); 778 | } 779 | 780 | for (uint8_t chunk = 0; chunk < chunksPerFrame; chunk++) 781 | { 782 | lineChunk = new uint8_t[chunkSize]; 783 | if ( NULL == lineChunk ) 784 | { 785 | ESP_LOGE(TAG, "Frame buffer chunk allocation fail"); 786 | ESP_ERROR_CHECK(ESP_FAIL); 787 | } 788 | lineStep = lineChunk; 789 | for (uint8_t lineIndex = 0; lineIndex < linesPerChunk; lineIndex++) 790 | { 791 | lineArray[(chunk*linesPerChunk)+lineIndex] = lineStep; 792 | lineStep += bytesPerLine; 793 | } 794 | } 795 | 796 | return lineArray; 797 | } 798 | 799 | /* 800 | * @brief Free memory allocated by frameBufferAlloc(); 801 | */ 802 | void ESP_8_BIT_composite::frameBufferFree(uint8_t** lineArray) 803 | { 804 | for (uint8_t chunk = 0; chunk < chunksPerFrame; chunk++) 805 | { 806 | free(lineArray[chunk*linesPerChunk]); 807 | } 808 | free(lineArray); 809 | } 810 | 811 | /* 812 | * @brief Wait for current frame to finish rendering 813 | */ 814 | void ESP_8_BIT_composite::waitForFrame() 815 | { 816 | instance_check(); 817 | 818 | _swapReady = true; 819 | 820 | video_sync(); 821 | } 822 | 823 | /* 824 | * @brief Retrieve pointer to frame buffer lines array 825 | */ 826 | uint8_t** ESP_8_BIT_composite::getFrameBufferLines() 827 | { 828 | instance_check(); 829 | 830 | return _backBuffer; 831 | } 832 | 833 | /* 834 | * @brief Number of frames sent to screen 835 | */ 836 | uint32_t ESP_8_BIT_composite::getRenderedFrameCount() 837 | { 838 | return _frame_counter; 839 | } 840 | 841 | /* 842 | * @brief Number of buffer swaps performed 843 | */ 844 | uint32_t ESP_8_BIT_composite::getBufferSwapCount() 845 | { 846 | return _swap_counter; 847 | } 848 | -------------------------------------------------------------------------------- /ESP_8_BIT_composite.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ESP_8_BIT color composite video generator wrapper class. 4 | 5 | Wrapper class adapting code extracted from Peter Barrett's ESP_8_BIT project 6 | into a standalone Arduino library. 7 | 8 | Copyright (c) Roger Cheng 9 | 10 | MIT License 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | 29 | */ 30 | 31 | #ifndef ESP_8_BIT_COMPOSITE_H 32 | #define ESP_8_BIT_COMPOSITE_H 33 | 34 | #include "Arduino.h" 35 | 36 | #ifndef ARDUINO_ARCH_ESP32 37 | #error This library requires ESP32 as it uses ESP32-specific hardware peripheral 38 | #endif 39 | 40 | #include "esp_types.h" 41 | #include "esp_err.h" 42 | #include "esp_log.h" 43 | 44 | #include "esp_attr.h" 45 | #include "esp_heap_caps.h" 46 | #include "esp_intr_alloc.h" 47 | #include "driver/periph_ctrl.h" 48 | #include "driver/dac.h" 49 | #include "driver/gpio.h" 50 | #include "driver/i2s.h" 51 | #include "rom/gpio.h" 52 | #include "rom/lldesc.h" 53 | #include "soc/gpio_reg.h" 54 | #include "soc/i2s_struct.h" 55 | #include "soc/i2s_reg.h" 56 | #include "soc/io_mux_reg.h" 57 | #include "soc/rtc.h" 58 | #include "soc/rtc_io_reg.h" 59 | #include "soc/soc.h" 60 | 61 | class ESP_8_BIT_composite 62 | { 63 | public: 64 | /* 65 | * @brief Constructor for ESP_8_BIT composite video wrapper class 66 | * @param ntsc True (or nonzero) for NTSC mode, False (or zero) for PAL mode 67 | */ 68 | ESP_8_BIT_composite(int ntsc); 69 | 70 | /* 71 | * @brief Destructor for ESP_8_BIT composite video wrapper class. This 72 | * is only useful for freeing self-allocated memory, because I don't know how 73 | * to properly tear down rossumur's ESP_8_BIT magic I wrapped. 74 | */ 75 | ~ESP_8_BIT_composite(); 76 | 77 | /* 78 | * @brief Video subsystem setup: allocate frame buffer and start engine 79 | */ 80 | void begin(); 81 | 82 | /* 83 | * @brief Wait for current frame to finish rendering 84 | */ 85 | void waitForFrame(); 86 | 87 | /* 88 | * @brief Retrieve pointer to frame buffer lines array 89 | */ 90 | uint8_t** getFrameBufferLines(); 91 | 92 | /* 93 | * @brief Number of frames sent to screen 94 | */ 95 | uint32_t getRenderedFrameCount(); 96 | 97 | /* 98 | * @brief Number of buffer swaps performed 99 | */ 100 | uint32_t getBufferSwapCount(); 101 | private: 102 | /* 103 | * @brief Check to ensure this instance is the first and only allowed instance 104 | */ 105 | void instance_check(); 106 | 107 | /* 108 | * @brief Flag to ensure begin() is called once and only once 109 | */ 110 | bool _started; 111 | 112 | /* 113 | * @brief Allocate memory for frame buffer 114 | */ 115 | uint8_t** frameBufferAlloc(); 116 | 117 | /* 118 | * @brief Free memory allocated by frameBufferAlloc(); 119 | */ 120 | void frameBufferFree(uint8_t** frameBuffer); 121 | }; 122 | 123 | #endif // ESP_8_BIT_COMPOSITE_H 124 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Roger Cheng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP_8_BIT Color Composite Video Out Library 2 | 3 | ## Status: Currently __BROKEN__ 4 | 5 | Espressif made breaking changes to ESP32 Arduino Core between v2.x 6 | and v3.x which cause compilation errors in this library. 7 | The first compiler error (but not the most serious one) is: 8 | 9 | `ESP_8_BIT_composite.cpp:45:55: error: invalid conversion from 'const volatile void*' to 'volatile void*' [-fpermissive]` 10 | 11 | I am tracking the problem as 12 | [issue #56](https://github.com/Roger-random/ESP_8_BIT_composite/issues/56) 13 | in this repository. Current workaround is to downgrade Espressif ESP32 14 | Arduino core to v2, this library was last verified to work with v2.0.14. 15 | 16 | ![Espressif esp32 arduino core version select](https://github.com/user-attachments/assets/26cf3a2a-32a8-4f73-b9c7-db97d2b97441) 17 | 18 | ## Purpose 19 | 20 | The composite video generation code from SEGA emulator of 21 | [ESP_8_BIT](https://github.com/rossumur/esp_8_bit) 22 | extracted and packaged into a standalone Arduino library so everyone can 23 | write Arduino sketches that output a color composite video signal. 24 | NTSC and PAL are both supported. 25 | 26 | __Huge thanks to Peter Barrett / rossumur for ESP_8_BIT, without which this 27 | library would not have been possible.__ 28 | 29 | For more behind-the-scenes information on how this library came to be, see 30 | [the development diary](https://newscrewdriver.com/tag/esp_8_bit/) 31 | which has all the details anyone would ever want plus even more that nobody 32 | ever asked for. 33 | 34 | ## Hardware requirement 35 | * 'Newer' ESP32 (see below) 36 | * Composite video connector to ESP32 GPIO25 video signal pin. 37 | * Display device with composite video input port. (Usually an old-school tube TV.) 38 | 39 | __ESP32 Details__ 40 | 41 | This composite video generation code is an extremely clever hack that used several 42 | ESP32 peripherals in ways they were not originally designed for. See the 43 | [original author's blog documentation](https://rossumblog.com/2020/05/10/130/) 44 | for details. It also means older versions of ESP32 could not run this code. 45 | I don't know exactly which Espressif errata is relevant. 46 | [UPDATE: [sysytwl](https://github.com/Roger-random/ESP_8_BIT_composite/issues/55) believes it is [3.7 Audio PLL frequency range is limited.](https://www.espressif.com/sites/default/files/documentation/esp32_errata_en.pdf)] 47 | 48 | Here are some data points: 49 | 50 | * Known to work 51 | * `ESP32-D0WD (revision 1)` (mine) 52 | * `ESP32-D0WDQ6 (revision 1)` (thanks [todbot](https://github.com/todbot)) 53 | * `ESP32-PICO-D4 (revision 1)` (thanks [alex1115alex](https://github.com/alex1115alex)) 54 | * Known to NOT work 55 | * `ESP32-D0WDQ6 (revision 0)` (thanks [todbot](https://github.com/todbot)) 56 | 57 | Chip identification obtained from [ESPTool](https://github.com/espressif/esptool) 58 | with the command `esptool chip_id` 59 | 60 | Pushing hardware limits in this manner may restrict this library to a subset of the 61 | ESP32 family. This library was developed and tested against the original suffix-free 62 | ESP32. Compatiblity with variants 63 | ([ESP32-S2, ESP32-S3, etc.](https://en.wikipedia.org/wiki/ESP32#ESP32-xx_family)) 64 | are untested and unknown. 65 | 66 | ## Arduino requirement 67 | * [Adafruit GFX Library](https://learn.adafruit.com/adafruit-gfx-graphics-library) 68 | available from Arduino IDE Library Manager. (Last verified to work with v1.11.9) 69 | * [Espressif Arduino Core for ESP32](https://github.com/espressif/arduino-esp32), 70 | follow installation directions at that link. (Last verified to work with v2.0.14) 71 | * (Optional) [AnimatedGIF](https://github.com/bitbank2/AnimatedGIF), 72 | for displaying animated GIF files. (Last verified to work with v1.4.7) 73 | * [Arduino IDE](https://www.arduino.cc/en/software) of course. 74 | (Last verified to work with v2.2.1) 75 | 76 | Here's an Arduino IDE screenshot of my ESP32 configuration: 77 | 78 | ![Arduino IDE board/esp32/ESP32 Dev Module](https://github.com/Roger-random/ESP_8_BIT_composite/assets/8559196/18dc5615-d18b-4d3a-899a-90e478c65577) 79 | 80 | ![Screenshot 2023-12-03 142653](https://github.com/Roger-random/ESP_8_BIT_composite/assets/8559196/80512b60-5f2d-4ddc-8fdc-19cd63a1ad70) 81 | 82 | ## Not Compatible with "Arduino ESP32 Boards by Arduino" 83 | 84 | ![arduino board manager esp32](https://github.com/Roger-random/ESP_8_BIT_composite/assets/8559196/01b60eb4-8d15-4aec-9f37-d9adfdf6e3f6) 85 | 86 | This library is __NOT compatible__ with the "Arduino ESP32 Boards by Arduino" board 87 | library. (thanks [JLBCS](https://github.com/JLBCS) for [#44](https://github.com/Roger-random/ESP_8_BIT_composite/issues/44)) 88 | Compilation will fail with the following error: 89 | ``` 90 | ESP_8_BIT_composite.h:48:10: fatal error: driver/dac.h: No such file or directory 91 | #include "driver/dac.h" 92 | ^~~~~~~~~~~~~~ 93 | ``` 94 | 95 | 96 | ## Installation 97 | 98 | This library can now be installed from within the Arduino desktop IDE via the 99 | Library Manager. Listed as "ESP_8_BIT Color Composite Video Library" 100 | 101 | It can also be installed from this GitHub repository if desired: 102 | 1. Download into a folder named "ESP_8_BIT_composite" under your Arduino IDE's 103 | `libraries` folder. 104 | 2. Restart Arduino IDE. 105 | 106 | ## Classes 107 | 108 | 1. `ESP_8_BIT_GFX` offers high-level drawing commands via the 109 | [Adafruit GFX API](https://learn.adafruit.com/adafruit-gfx-graphics-library). 110 | Easy to use, but not the highest performance. 111 | 2. `ESP_8_BIT_composite` exposes low-level frame buffer for those who prefer 112 | to manipulate bytes directly. Maximum performance, but not very easy to use. 113 | 114 | ## Examples 115 | 116 | 1. `GFX_HelloWorld` draws animated rectangles and text, both in changing 117 | colors, using the Adafruit GFX API exposed by `ESP_8_BIT_GFX`. 118 | 2. `RGB332_Colors` draws all 256 available colors directly to frame buffer 119 | allocated by `ESP_8_BIT_composite`. Draws once, no updates. 120 | 3. `RGB332_PulseB` draws 64 blocks of colors (8x8) representing different 121 | combinations of red (vertical axis) and green (horizontal axis). Uses the 122 | frame buffer of `ESP_8_BIT_composite` directly. Every second, the entire 123 | screen is redrawn with one of four possible values of blue in a pulsing cycle. 124 | 4. `GFX_Screen_Fillers` demonstrates several of the common ways to put 125 | graphics on screen. Includes the following APIS: `fillRect`, `fillCircle`, 126 | `drawFastVLine`, and `drawFastHLine`. 127 | 5. `AnimatedGIF` demonstrates how to use this video out library with the 128 | [AnimatedGIF decoder library](https://github.com/bitbank2/AnimatedGIF) 129 | by Larry Bank. Art used in this example is 130 | [Cat and Galactic Squid](https://twitter.com/MLE_Online/status/1393660363191717888) 131 | by Emily Velasco 132 | ([CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)) 133 | 6. `GFX_RotatedText` demonstrates support for Adafruit_GFX::setRotation() 134 | by rendering text in one of four orientations and one of three text sizes. 135 | 7. `GFX_RotatedRect` demonstrates support for Adafruit_GFX::setRotation() 136 | by drawing four rectangles - one in each supported orientation - on every 137 | frame. Cycles through one of four animated pameters (X/Y/Width/Height) 138 | every second. 139 | 140 | ## Screen Size 141 | 142 | * Inherited from SEGA emulator of ESP_8_BIT, the addressible screen size is __256 pixels wide 143 | and 240 pixels tall__. This means valid X values of 0 to 255 inclusive, and 144 | valid Y values of 0 to 239 inclusive. 145 | * When displayed on a standard analog TV with 4:3 aspect ratio, these pixels 146 | are not square. So `drawCircle()` will look like a squat wide oval on screen 147 | and not a perfect circle. This is inherent to the system and not considered 148 | a bug. 149 | * When displayed on a standard analog TV, the visible image will be slightly 150 | cropped due to [overscan](https://en.wikipedia.org/wiki/Overscan). This is 151 | inherent to analog televisions and not considered a bug. 152 | * The developer-friendly `ESP_8_BIT_GFX` class checks for valid coordinates 153 | and will only draw within the valid range. So if X is too large (say, 300) 154 | `drawPixel()` will ignore the command and silently do nothing. 155 | * The raw `ESP_8_BIT_composite` class gives max performance power, but with 156 | great power comes great responsibility. Caller is responsible for making sure 157 | X and Y stay within bounds when manipulating frame buffer bytes via 158 | `getFrameBufferLines()[Y][X]`. Any bugs that use out of range array index 159 | may garble the image, or trigger a memory access violation and cause your ESP32 160 | to reset, or other general memory corruption nastiness __including the 161 | potential for security vulnerabilities.__ 162 | 163 | ## 8-Bit Color 164 | 165 | Inherited from ESP_8_BIT is a fixed 8-bit color palette in 166 | [RGB332 format](https://en.wikipedia.org/wiki/List_of_monochrome_and_RGB_color_formats#8-bit_RGB_(also_known_as_3-3-2_bit_RGB)). 167 | The underlying composite video out code always works with this set of colors. 168 | (See [Examples](https://github.com/Roger-random/ESP_8_BIT_composite#examples).) 169 | * The developer-friendly `ESP_8_BIT_GFX` class constructor can be initialized 170 | in either 8-bit (native) or 16-bit (compatibility) color mode. 171 | * Adafruit GFX was written for 16-bit color in 172 | [RGB565 format](https://learn.adafruit.com/adafruit-gfx-graphics-library/coordinate-system-and-units). 173 | `ESP_8_BIT_GFX` in 16-bit mode is compatible with existing Adafruit GFX 174 | code by automatically downconverting color 175 | while drawing. The resulting colors will be approximate, but they should 176 | closely resemble the original. Using RGB332 color values while in this mode 177 | will result in wrong colors on screen due to interpretation as RGB565 colors. 178 | * In 8-bit mode, color values given to GFX APIs will be treated as native 179 | 8-bit RGB332 values. This is faster because it skips the color conversion 180 | process. Using RGB565 color values while in this mode will result in 181 | wrong colors on screen due to the higher 8 bits being ignored. 182 | * The raw `ESP_8_BIT_composite` class always works in 8-bit RGB332 color. 183 | 184 | Sample colors in 8-bit RGB332 format: 185 | |Name|RGB332 (binary)|RGB332 (hexadecimal)| 186 | |----:|:---:|:-----| 187 | |Black |0b00000000|0x00| 188 | |Blue |0b00000011|0x03| 189 | |Green |0b00011100|0x1C| 190 | |Cyan |0b00011111|0x1F| 191 | |Red |0b11100000|0xE0| 192 | |Magenta|0b11100011|0xE3| 193 | |Yellow |0b11111100|0xFC| 194 | |White |0b11111111|0xFF| 195 | 196 | ## 8-bit RGB332 Color Picker Utility 197 | 198 | [CLICK HERE](https://roger-random.github.io/RGB332_color_wheel_three.js/) 199 | for an interactive color picker web app. It shows all 256 possible 200 | 8-bit RGB332 colors in either a 201 | [HSV (hue/saturation/value)](https://en.wikipedia.org/wiki/HSL_and_HSV) 202 | color cylinder or a 203 | [RGB (red/green/blue)](https://en.wikipedia.org/wiki/RGB_color_space) 204 | color cube. 205 | 206 | ## Alternatives 207 | 208 | The intent of this library is to be easy to use, with minimum complexity 209 | for beginners. Advanced users are expected to fork this repository to add 210 | their desired features, or go to the source and fork 211 | [ESP_8_BIT](https://github.com/rossumur/esp_8_bit) directly. 212 | 213 | If this library is not a good fit for your project, please consider another 214 | library for ESP32 composite video: 215 | * __[Bitluni's Lab](https://bitluni.net/esp32-composite-video)__: one of the 216 | earliest ESP32 composite video generators that informed development of many 217 | that followed. 218 | * __[FabGL](http://www.fabglib.org/)__: 219 | This powerful graphics library didn't have composite video output when I 220 | started my project, but it has since been added. See the 221 | [author's show-and-tell thread](https://github.com/fdivitto/FabGL/discussions/192) 222 | for details. The author has a Tektronix VM700 to ensure PAL video output 223 | signal is accurate. 224 | * __[LovyanGFX](https://github.com/lovyan03/LovyanGFX)__: 225 | Another very nice graphics library that has 226 | [preliminary composite video support in a development branch](https://github.com/lovyan03/LovyanGFX/blob/develop/doc/Panel_CVBS.md). 227 | I believe this library is unique in support for NTSC-J, and documentation 228 | is in both Japanese and English. (Code comments are in Japanese.) 229 | 230 | ## Questions? 231 | 232 | Please [post to discussions](https://github.com/Roger-random/ESP_8_BIT_composite/discussions) 233 | and see if anyone knows the answer. Note there's no guarantee of an answer. 234 | 235 | ## Bugs? 236 | 237 | Please [open an issue](https://github.com/Roger-random/ESP_8_BIT_composite/issues) 238 | to see if it can be fixed. Note there's no guarantee of support. 239 | 240 | ## Tip jar 241 | 242 | Just like its predecessor ESP_8_BIT, this project is shared freely with the world. 243 | Under the MIT license, you don't owe me anything. 244 | 245 | But if you want to toss a few coins my way, you can do so by using my 246 | Amazon Associates link to buy your 247 | [ESP32 development boards](https://amzn.to/3dMdIDQ) 248 | or 249 | [composite video cables](https://amzn.to/33K9qXP). 250 | You'll pay the same price, but I get a small 251 | percentage. As an Amazon Associate I earn from qualifying purchases. 252 | -------------------------------------------------------------------------------- /examples/AnimatedGIF/AnimatedGIF.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Example application of ESP_8_BIT color composite video generator library on ESP32. 4 | Connect GPIO25 to signal line, usually the center of composite video plug. 5 | 6 | Plays an animated GIF using the AnimatedGIF library by Larry Bank [bitbank2] 7 | https://github.com/bitbank2/AnimatedGIF 8 | 9 | Lightly modified from AnimatedGIF library's example "ESP32_LEDMatrix_I2S" 10 | 11 | AnimatedGIF library and example was released under Apache 2.0 license. 12 | https://github.com/bitbank2/AnimatedGIF/blob/master/LICENSE 13 | 14 | Cat and Galactic Squid friend by Emily Velasco 15 | https://twitter.com/MLE_Online/status/1393660363191717888 16 | Released under Creative Commons Attribution-ShareAlike (CC BY-SA 4.0) license 17 | https://creativecommons.org/licenses/by-sa/4.0/ 18 | 19 | Converted to byte array via Unix/Linux command line utility xxd 20 | 21 | xxd -i cat_and_galactic_squid.gif cat_and_galactic_squid.h 22 | 23 | Then manually adding 'const' to move it out of precious dynamic memory 24 | 25 | */ 26 | 27 | #include 28 | #include 29 | #include "cat_and_galactic_squid.h" 30 | 31 | // Create an instance of the graphics library 32 | ESP_8_BIT_GFX videoOut(true /* = NTSC */, 16 /* = RGB565 colors will be downsampled to 8-bit RGB332 */); 33 | AnimatedGIF gif; 34 | 35 | // Vertical margin to compensate for aspect ratio 36 | const int margin = 29; 37 | 38 | // Draw a line of image to ESP_8_BIT_GFX frame buffer 39 | void GIFDraw(GIFDRAW *pDraw) 40 | { 41 | uint8_t *s; 42 | uint16_t *d, *usPalette, usTemp[320]; 43 | int x, y; 44 | 45 | usPalette = pDraw->pPalette; 46 | y = pDraw->iY + pDraw->y; // current line 47 | 48 | s = pDraw->pPixels; 49 | if (pDraw->ucDisposalMethod == 2) // restore to background color 50 | { 51 | for (x=0; xiWidth; x++) 52 | { 53 | if (s[x] == pDraw->ucTransparent) 54 | s[x] = pDraw->ucBackground; 55 | } 56 | pDraw->ucHasTransparency = 0; 57 | } 58 | // Apply the new pixels to the main image 59 | if (pDraw->ucHasTransparency) // if transparency used 60 | { 61 | uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; 62 | int x, iCount; 63 | pEnd = s + pDraw->iWidth; 64 | x = 0; 65 | iCount = 0; // count non-transparent pixels 66 | while(x < pDraw->iWidth) 67 | { 68 | c = ucTransparent-1; 69 | d = usTemp; 70 | while (c != ucTransparent && s < pEnd) 71 | { 72 | c = *s++; 73 | if (c == ucTransparent) // done, stop 74 | { 75 | s--; // back up to treat it like transparent 76 | } 77 | else // opaque 78 | { 79 | *d++ = usPalette[c]; 80 | iCount++; 81 | } 82 | } // while looking for opaque pixels 83 | if (iCount) // any opaque pixels? 84 | { 85 | for(int xOffset = 0; xOffset < iCount; xOffset++ ){ 86 | videoOut.drawPixel(pDraw->iX + x + xOffset, margin + y, usTemp[xOffset]); 87 | } 88 | x += iCount; 89 | iCount = 0; 90 | } 91 | // no, look for a run of transparent pixels 92 | c = ucTransparent; 93 | while (c == ucTransparent && s < pEnd) 94 | { 95 | c = *s++; 96 | if (c == ucTransparent) 97 | iCount++; 98 | else 99 | s--; 100 | } 101 | if (iCount) 102 | { 103 | x += iCount; // skip these 104 | iCount = 0; 105 | } 106 | } 107 | } 108 | else 109 | { 110 | s = pDraw->pPixels; 111 | // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) 112 | for (x=0; xiWidth; x++) 113 | { 114 | videoOut.drawPixel(x,margin + y, usPalette[*s++]); 115 | } 116 | } 117 | } /* GIFDraw() */ 118 | 119 | 120 | void setup() { 121 | videoOut.begin(); 122 | videoOut.copyAfterSwap = true; // gif library depends on data from previous buffer 123 | videoOut.fillScreen(0); 124 | videoOut.waitForFrame(); 125 | 126 | gif.begin(LITTLE_ENDIAN_PIXELS); 127 | } 128 | 129 | void loop() { 130 | if (gif.open((uint8_t *)cat_and_galactic_squid_gif, cat_and_galactic_squid_gif_len, GIFDraw)) 131 | { 132 | while (gif.playFrame(true, NULL)) 133 | { 134 | videoOut.waitForFrame(); 135 | } 136 | videoOut.waitForFrame(); 137 | gif.close(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /examples/GFX_HelloWorld/GFX_HelloWorld.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Example for ESP_8_BIT color composite video generator library on ESP32. 4 | Connect GPIO25 to signal line, usually the center of composite video plug. 5 | 6 | GFX Hello World 7 | 8 | This demonstrates using the ESP_8_BIT_GFX class, which inherits from the 9 | Adafruit GFX base class to deliver an easy to use graphics API. Draws two 10 | rectangles that cycle around the border of the screen. The amount of corners 11 | cut off from these rectangle show the amount of overscan on a particular 12 | screen. In the middle of two rectangles are a bit of text drawn using 13 | Adafruit GFX print() API. 14 | 15 | Copyright (c) Roger Cheng 16 | 17 | MIT License 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | The above copyright notice and this permission notice shall be included in all 26 | copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE. 35 | 36 | */ 37 | 38 | #include 39 | 40 | // A list of 8-bit color values that work well in a cycle. 41 | uint8_t colorCycle[] = { 42 | 0xFF, // White 43 | 0xFE, // Lowering blue 44 | 0xFD, 45 | 0xFC, // No blue 46 | 0xFD, // Raising blue 47 | 0xFE, 48 | 0xFF, // White 49 | 0xF3, // Lowering green 50 | 0xE7, 51 | 0xE3, // No green 52 | 0xE7, // Raising green 53 | 0xF3, 54 | 0xFF, // White 55 | 0x9F, // Lowering red 56 | 0x5F, 57 | 0x1F, // No red 58 | 0x5F, // Raising red 59 | 0x9F, 60 | 0xFF 61 | }; 62 | 63 | // Create an instance of the graphics library 64 | ESP_8_BIT_GFX videoOut(true /* = NTSC */, 8 /* = RGB332 color */); 65 | 66 | void setup() { 67 | // Initial setup of graphics library 68 | videoOut.begin(); 69 | } 70 | 71 | void loop() { 72 | // Wait for the next frame to minimize chance of visible tearing 73 | videoOut.waitForFrame(); 74 | 75 | // Get the current time and calculate a scaling factor 76 | unsigned long time = millis(); 77 | float partial_second = (float)(time % 1000)/1000.0; 78 | 79 | // Use time scaling factor to calculate coordinates and colors 80 | uint8_t movingX = (uint8_t)(255*partial_second); 81 | uint8_t invertX = 255-movingX; 82 | uint8_t movingY = (uint8_t)(239*partial_second); 83 | uint8_t invertY = 239-movingY; 84 | 85 | uint8_t cycle = colorCycle[(uint8_t)(17*partial_second)]; 86 | uint8_t invertC = 0xFF-cycle; 87 | 88 | // Clear screen 89 | videoOut.fillScreen(0); 90 | 91 | // Draw one rectangle 92 | videoOut.drawLine(movingX, 0, 255, movingY, cycle); 93 | videoOut.drawLine(255, movingY, invertX, 239, cycle); 94 | videoOut.drawLine(invertX, 239, 0, invertY, cycle); 95 | videoOut.drawLine(0, invertY, movingX, 0, cycle); 96 | 97 | // Draw a rectangle with inverted position and color 98 | videoOut.drawLine(invertX, 0, 255, invertY, invertC); 99 | videoOut.drawLine(255, invertY, movingX, 239, invertC); 100 | videoOut.drawLine(movingX, 239, 0, movingY, invertC); 101 | videoOut.drawLine(0, movingY, invertX, 0, invertC); 102 | 103 | // Draw text in the middle of the screen 104 | videoOut.setCursor(25, 80); 105 | videoOut.setTextColor(invertC); 106 | videoOut.setTextSize(2); 107 | videoOut.setTextWrap(false); 108 | videoOut.print("Adafruit GFX API"); 109 | videoOut.setCursor(110, 120); 110 | videoOut.setTextColor(0xFF); 111 | videoOut.print("on"); 112 | videoOut.setCursor(30, 160); 113 | videoOut.setTextColor(cycle); 114 | videoOut.print("ESP_8_BIT video"); 115 | } 116 | -------------------------------------------------------------------------------- /examples/GFX_RotatedRect/GFX_RotatedRect.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Example for ESP_8_BIT color composite video generator library on ESP32. 4 | Connect GPIO25 to signal line, usually the center of composite video plug. 5 | 6 | GFX Rotated Rectangles 7 | 8 | This demonstrates using the ESP_8_BIT_GFX class, which inherits from the 9 | Adafruit GFX base class to deliver an easy to use graphics API. Specifically 10 | tests the setRotation function, drawing rectangle in all four orientations 11 | in the same frame. Loops through different parameters of drawRect: X, Y, width, 12 | and height. 13 | 14 | Copyright (c) Roger Cheng 15 | 16 | MIT License 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | 35 | */ 36 | #include 37 | 38 | long fractionSecond; 39 | uint8_t currentState; 40 | uint8_t delta; 41 | 42 | // Create an instance of the graphics library 43 | ESP_8_BIT_GFX videoOut(true /* = NTSC */, 8 /* = RGB332 color */); 44 | 45 | void setup() { 46 | // put your setup code here, to run once: 47 | videoOut.begin(); 48 | 49 | currentState = 0; 50 | fractionSecond = millis()%1000; 51 | delta = 0; 52 | } 53 | 54 | void loop() { 55 | if (millis()%1000 < fractionSecond) 56 | { 57 | // Cycle through one of four states every second 58 | currentState = (currentState+1)%4; 59 | } 60 | fractionSecond = millis()%1000; 61 | 62 | // Calculate the fractional progress through animated delta value 63 | delta = fractionSecond/5; 64 | if(delta > 100) 65 | { 66 | delta = 200-delta; 67 | } 68 | 69 | videoOut.waitForFrame(); 70 | videoOut.fillScreen(0); 71 | for(uint8_t r = 0; r < 4; r++) 72 | { 73 | videoOut.setRotation(r); 74 | switch(currentState) 75 | { 76 | case 0: 77 | videoOut.drawRect(40+delta, 40, 20, 20, 0xFF); 78 | break; 79 | case 1: 80 | videoOut.drawRect(40, 40+delta, 20, 20, 0xFF); 81 | break; 82 | case 2: 83 | videoOut.drawRect(40, 40, 20+delta, 20, 0xFF); 84 | break; 85 | case 3: 86 | videoOut.drawRect(40, 40, 20, 20+delta, 0xFF); 87 | break; 88 | default: 89 | currentState = 0; 90 | break; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /examples/GFX_RotatedText/GFX_RotatedText.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Example for ESP_8_BIT color composite video generator library on ESP32. 4 | Connect GPIO25 to signal line, usually the center of composite video plug. 5 | 6 | GFX Rotated Text 7 | 8 | This demonstrates using the ESP_8_BIT_GFX class, which inherits from the 9 | Adafruit GFX base class to deliver an easy to use graphics API. Specifically 10 | tests the setRotation function, drawing text in one of four screen orientations 11 | supported by Adafruit GFX. Also loops through different text sizes as they use 12 | different portions of the code: drawPixel() for text size of one, and fillRect() 13 | for text size larger than one. 14 | 15 | Copyright (c) Roger Cheng 16 | 17 | MIT License 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | The above copyright notice and this permission notice shall be included in all 26 | copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE. 35 | 36 | */ 37 | 38 | #include 39 | 40 | // Create an instance of the graphics library 41 | ESP_8_BIT_GFX videoOut(true /* = NTSC */, 8 /* = RGB332 color */); 42 | unsigned long prevTime = 0; 43 | uint8_t ticks = 0; 44 | uint16_t text_width_half; 45 | uint16_t text_height_half; 46 | 47 | // Update values of text_width_half and text_height_half 48 | // Call this after changing text size with setTextSize() 49 | void updateTextWidthHeight() { 50 | int16_t x_discard; 51 | int16_t y_discard; 52 | 53 | videoOut.getTextBounds("setRotation(0)", 0, 0, 54 | &x_discard, &y_discard, &text_width_half, &text_height_half); 55 | text_width_half /= 2; 56 | text_height_half /= 2; 57 | } 58 | 59 | void setup() { 60 | prevTime = millis(); 61 | 62 | // Initial setup of graphics library 63 | videoOut.begin(); 64 | 65 | // Measure size of rendered text 66 | videoOut.setTextSize(1); 67 | updateTextWidthHeight(); 68 | } 69 | 70 | 71 | void loop() { 72 | unsigned long now = millis(); 73 | 74 | if (now > prevTime+1000) 75 | { 76 | // Update every second 77 | prevTime += 1000; 78 | ticks = (ticks+1)%12; 79 | 80 | // Rotation cycles through 0,1,2,3 using lowest three bits of ticks 81 | videoOut.setRotation(ticks & 0x03); 82 | 83 | // Text size cycles through 1,2,3 using next two lowest bits 84 | videoOut.setTextSize((ticks>>2)+1); 85 | updateTextWidthHeight(); 86 | } 87 | 88 | // Wait for the next frame to minimize chance of visible tearing 89 | videoOut.waitForFrame(); 90 | 91 | // Clear screen 92 | videoOut.fillScreen(0); 93 | 94 | // Draw text in the middle of the screen 95 | videoOut.setCursor((videoOut.width()/2)-text_width_half, (videoOut.height()/2)-text_height_half); 96 | videoOut.setTextColor(0xFF); 97 | videoOut.print("setRotation("); 98 | videoOut.print(ticks & 0x03); 99 | videoOut.print(")"); 100 | } 101 | -------------------------------------------------------------------------------- /examples/GFX_Screen_Fillers/GFX_Screen_Fillers.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Example for ESP_8_BIT color composite video generator library on ESP32. 4 | Connect GPIO25 to signal line, usually the center of composite video plug. 5 | 6 | GFX Screen Fillers 7 | 8 | This demonstrates using the ESP_8_BIT_GFX class, which inherits from the 9 | Adafruit GFX base class to deliver an easy to use graphics API. This example 10 | cycles through four common ways to fill screen area. 11 | 12 | 1. fillRect() - fills the screen with fillRect() API. 13 | 2. fillCircle() - fills the screen with fillCircle() API. 14 | 3. drawFastVLine() - fills the screen by drawing many vertical lines. 15 | 4. drawFastHLine() - fills the screen by drawing many horizontal lines. 16 | 17 | These samples should run smoothly without visible flickering or visual 18 | artifacts. If they do, one of the following performance enhancements of 19 | this library probably has a bug that requires investigation: 20 | * Double-buffering 21 | * Optimized fillRect() override 22 | * Optimized drawFastVLine() override 23 | * Optimized drawFastHLine() override 24 | 25 | Copyright (c) Roger Cheng 26 | 27 | MIT License 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy 30 | of this software and associated documentation files (the "Software"), to deal 31 | in the Software without restriction, including without limitation the rights 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 33 | copies of the Software, and to permit persons to whom the Software is 34 | furnished to do so, subject to the following conditions: 35 | The above copyright notice and this permission notice shall be included in all 36 | copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 39 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 40 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 41 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 42 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 43 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 44 | SOFTWARE. 45 | 46 | */ 47 | 48 | #include 49 | 50 | // Create an instance of the graphics library 51 | ESP_8_BIT_GFX videoOut(true /* = NTSC */, 8 /* = RGB332 color */); 52 | 53 | /////////////////////////////////////////////////////////////// 54 | // 55 | // Helpers to cycle through colors of varying bright hues. 56 | // 57 | // Technical explanation: These colors are a subset of the 8-bit 58 | // RGB332 colors supported by ESP_8_BIT_GFX. When reorganized 59 | // in HSV color model, these are colors of various hues (H) 60 | // all with full saturation (S == 1.0) and value (V == 1.0) 61 | 62 | uint8_t hues[] = { 63 | 0xE0, // Red@0 64 | 0xE4, 65 | 0xE8, 66 | 0xEC, 67 | 0xF0, 68 | 0xF4, 69 | 0xF8, 70 | 0xFC, // Yellow 71 | 0xDC, 72 | 0xBC, 73 | 0x9C, 74 | 0x7C, 75 | 0x5C, 76 | 0x3C, 77 | 0x1C, // Green 78 | 0x1D, 79 | 0x1E, 80 | 0x1F, 81 | 0x1B, 82 | 0x17, 83 | 0x13, 84 | 0x0F, 85 | 0x0B, 86 | 0x07, 87 | 0x03, // Blue 88 | 0x23, 89 | 0x43, 90 | 0x63, 91 | 0x83, 92 | 0xA3, 93 | 0xC3, 94 | 0xE3, 95 | 0xE2, 96 | 0xE1 // 33 97 | }; 98 | 99 | // Index into hues[] of the current hue 100 | uint8_t hueIndex; 101 | 102 | // Constaintly cycling through hues[] is overly stimulating 103 | // so we stay with a particular hue for a while before moving 104 | // on to the next hue. 105 | const uint8_t framesPerHue = 10; 106 | 107 | // How many frames we have stayed on the current hue, should 108 | // be incremented on each use and never exceed framesPerHue 109 | uint8_t hueFrames; 110 | 111 | // Called on each draw to select a color from hues[]. Increments 112 | // hueFrames on each call and update hueIndex as needed. 113 | uint8_t nextHue() { 114 | if (++hueFrames > framesPerHue) { 115 | hueFrames = 0; 116 | if ( ++hueIndex > 33) { 117 | hueIndex = 0; 118 | } 119 | } 120 | 121 | return hues[hueIndex]; 122 | } 123 | 124 | // Counts through stages of tests 125 | uint8_t stage; 126 | 127 | // Tracks the progress percentage to know when we need to move to next test. 128 | float previousProgress; 129 | 130 | // How many milliseconds to spend on each stage of test. 131 | uint32_t millisPerStage = 5000; 132 | 133 | void setup() { 134 | // Initial setup of graphics library 135 | videoOut.begin(); 136 | 137 | // Starting stage for tests cycle 138 | stage = 4; 139 | 140 | // Start hue cycle at beginning 141 | hueIndex = 0; 142 | hueFrames = 0; 143 | } 144 | 145 | void loop() { 146 | // Get the current time and calculate a scaling factor 147 | float progress = (float)(millis() % millisPerStage)/(float)millisPerStage; 148 | if (progress < previousProgress) 149 | { 150 | printf("Stage %d spent %.2f%% of time waiting for frame. (Higher is better.)\n", 151 | stage, (float)videoOut.newPerformanceTrackingSession()/100); 152 | if (++stage > 4) { 153 | stage = 0; 154 | } 155 | } 156 | previousProgress = progress; 157 | 158 | // Modify progress so it goes from 0.0 to 1.0 and back to 0.0 in each stage. 159 | if (progress < 0.5) 160 | { 161 | progress *= 2; 162 | } 163 | else 164 | { 165 | progress = (1-progress)*2; 166 | } 167 | 168 | // Wait for the next frame to start working on frame buffer 169 | videoOut.waitForFrame(); 170 | 171 | // Clear screen 172 | videoOut.fillScreen(0); 173 | 174 | // Draw one of four tests 175 | switch(stage) 176 | { 177 | case 0: 178 | // A rectangle that grows from the center of the screen, then shrinks. 179 | videoOut.fillRect(128*(1-progress),120*(1-progress),256*progress,240*progress, nextHue()); 180 | break; 181 | case 1: 182 | // A circle that grows from the center of the screen, then shrinks. 183 | videoOut.fillCircle(128,120,130*progress,nextHue()); 184 | break; 185 | case 2: 186 | // Draws horizontally across the screen with lots of vertical lines. 187 | // (In this graphics library, vertical lines are less efficient than horizontal lines.) 188 | hueIndex = 0; 189 | hueFrames = 0; 190 | for(int16_t x = 128*(1-progress); x < 128+(128*progress); x++) 191 | { 192 | videoOut.drawFastVLine(x, 0, 240, nextHue()); 193 | } 194 | break; 195 | case 3: 196 | // Draws verticall across the screen with lots of horizontal lines. 197 | // (In this graphics library, horizontal lines are the most efficient.) 198 | hueIndex = 0; 199 | hueFrames = 0; 200 | for(int16_t y = 120*(1-progress); y < 120+(120*progress);y++) 201 | { 202 | videoOut.drawFastHLine(0, y, 256, nextHue()); 203 | } 204 | break; 205 | case 4: 206 | // Regression test for https://github.com/Roger-random/ESP_8_BIT_composite/issues/8 207 | videoOut.fillCircle(335*progress-40, 120, 40+20*progress, nextHue()); 208 | videoOut.fillCircle(128, 319*progress-40, 40-20*progress, hues[(hueIndex+17)%34]); 209 | break; 210 | default: 211 | stage = 0; 212 | break; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /examples/RGB332_Colors/RGB332_Colors.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Example for ESP_8_BIT color composite video generator library on ESP32. 4 | Connect GPIO25 to signal line, usually the center of composite video plug. 5 | 6 | RGB332_Colors 7 | 8 | Draws 256 blocks on screen, each one filled with a RGB332 color. This 9 | represents the full palette of available colors while using this library. 10 | 11 | Also demonstrates drawing by directly manipulating bytes in frame buffer, 12 | without using any drawing library functions. This is an advanced usage 13 | scenario, see GFX_HelloWorld for a more user-friendly interface. 14 | 15 | Copyright (c) Roger Cheng 16 | 17 | MIT License 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | The above copyright notice and this permission notice shall be included in all 26 | copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE. 35 | 36 | */ 37 | 38 | #include 39 | 40 | ESP_8_BIT_composite videoOut(true /* = NTSC */); 41 | 42 | void setup() { 43 | uint8_t redChannel; 44 | uint8_t** frameBufferLines; 45 | 46 | videoOut.begin(); 47 | frameBufferLines = videoOut.getFrameBufferLines(); 48 | 49 | // Draw all the colors available in our RGB332 palette 50 | for (int y = 0; y < 240; y++) 51 | { 52 | // Y axis determines red channel component, the most significant 3 bits. 53 | redChannel = (y/30) << 5; 54 | for (int x = 0; x < 256; x++) 55 | { 56 | // X axis determines green (middle 3 bits) and blue (least significant 2 bits) 57 | frameBufferLines[y][x] = redChannel | x >> 3; 58 | } 59 | } 60 | 61 | videoOut.waitForFrame(); 62 | } 63 | 64 | void loop() { 65 | } 66 | -------------------------------------------------------------------------------- /examples/RGB332_PulseB/RGB332_PulseB.ino: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Example for ESP_8_BIT color composite video generator library on ESP32. 4 | Connect GPIO25 to signal line, usually the center of composite video plug. 5 | 6 | RGB332_PulseB 7 | 8 | Draws 8 by 8 = 64 blocks on screen, each one filled with a color. More red 9 | down the vertical axis, more green across the horizontal axis. Value of 10 | blue over the entire screen changes once per second in a pulsing cycle. 11 | 12 | Also demonstrates drawing by directly manipulating bytes in frame buffer, 13 | without using any drawing library functions. This is an advanced usage 14 | scenario, see GFX_HelloWorld for a more user-friendly interface. 15 | 16 | Copyright (c) Roger Cheng 17 | 18 | MIT License 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | The above copyright notice and this permission notice shall be included in all 27 | copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | SOFTWARE. 36 | 37 | */ 38 | 39 | #include 40 | 41 | ESP_8_BIT_composite videoOut(true /* = NTSC */); 42 | 43 | // Current value for blue channel 44 | uint8_t currentBlue; 45 | 46 | // Blue channel change for next frame 47 | uint8_t changeBlueBy; 48 | 49 | void setup() { 50 | videoOut.begin(); 51 | currentBlue = 0; 52 | } 53 | 54 | void loop() { 55 | uint8_t redChannel; 56 | uint8_t** frameBufferLines = videoOut.getFrameBufferLines(); 57 | 58 | // Wait for the next frame to minimize chance of visible tearing 59 | videoOut.waitForFrame(); 60 | 61 | // Draw all the colors available in our RGB332 palette with current blue 62 | for (int y = 0; y < 240; y++) 63 | { 64 | // Y axis determines red channel (most significant 3 bits of RGB332) 65 | redChannel = (y/30) << 5; 66 | for (int x = 0; x < 256; x++) 67 | { 68 | // X axis determines green channel (middle 3 bits of RGB332) 69 | frameBufferLines[y][x] = redChannel | (x & 0xE0) >> 3 | currentBlue; 70 | } 71 | } 72 | 73 | // Update blue channel value for next redraw, pulses 0-1-2-3-2-1-0 74 | // for the least significant 2 bits of RGB222. 75 | if (0 == currentBlue) 76 | { 77 | changeBlueBy = 1; 78 | } 79 | else if (3 <= currentBlue) 80 | { 81 | changeBlueBy = -1; 82 | } 83 | currentBlue += changeBlueBy; 84 | 85 | // Wait before next redraw 86 | delay(1000); 87 | } 88 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ESP_8_BIT_composite KEYWORD1 2 | ESP_8_BIT_GFX KEYWORD1 3 | begin KEYWORD2 4 | waitForFrame KEYWORD2 5 | getFrameBufferLines KEYWORD2 6 | convertRGB565toRGB332 KEYWORD2 7 | drawPixel KEYWORD2 8 | fillScreen KEYWORD2 9 | getWaitFraction KEYWORD2 10 | newPerformanceTrackingSession KEYWORD2 11 | copyAfterSwap KEYWORD2 12 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP_8_BIT Color Composite Video Library 2 | version=1.3.2 3 | author=Roger Cheng 4 | maintainer=Roger Cheng 5 | sentence=Generate color composite TV video out signals with an ESP32. 6 | paragraph=Using SEGA signal generation code path from ESP_8_BIT by Peter Barrett, an Arduino sketch may manipulate the frame buffer directly or optionally use the Adafruit GFX library API. Includes double-buffering mechanism to avoid visible flickering while drawing. No additional support hardware required, connect GPIO25 to composite video signal output pin. (Usually the center of the video connector, and outside is connected to ground.) 7 | category=Display 8 | url=https://github.com/Roger-random/ESP_8_BIT_composite 9 | architectures=esp32 10 | depends=Adafruit GFX Library 11 | --------------------------------------------------------------------------------