├── .gitignore ├── resources ├── font.xcf ├── onacrt.jpg ├── testcardf.png ├── chipanddac.jpg ├── raspberrypi.png ├── raspberrypi1.png ├── testcardf1.png ├── youtube-thumb.jpg ├── generatesinetable.m ├── font2h.m ├── cliffs.m ├── converttoyuv.m ├── videocompressionbw.m ├── videocompression.m └── flames.m ├── ideas ├── includes ├── sine.h ├── time.h ├── jet.h ├── hsv.h ├── random.h ├── vectormath.h ├── discountadafruitgfx.h └── font.h ├── CMakeLists.txt ├── dac.pio ├── README.md ├── demos ├── cliffs.h ├── cube.h ├── lbm.h └── flames.h ├── pico-composite-PAL-colour.cpp └── colourpal.h /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | generated 3 | pico_sdk_import.cmake 4 | -------------------------------------------------------------------------------- /resources/font.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guruthree/pico-composite-PAL-colour/HEAD/resources/font.xcf -------------------------------------------------------------------------------- /resources/onacrt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guruthree/pico-composite-PAL-colour/HEAD/resources/onacrt.jpg -------------------------------------------------------------------------------- /resources/testcardf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guruthree/pico-composite-PAL-colour/HEAD/resources/testcardf.png -------------------------------------------------------------------------------- /resources/chipanddac.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guruthree/pico-composite-PAL-colour/HEAD/resources/chipanddac.jpg -------------------------------------------------------------------------------- /resources/raspberrypi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guruthree/pico-composite-PAL-colour/HEAD/resources/raspberrypi.png -------------------------------------------------------------------------------- /resources/raspberrypi1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guruthree/pico-composite-PAL-colour/HEAD/resources/raspberrypi1.png -------------------------------------------------------------------------------- /resources/testcardf1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guruthree/pico-composite-PAL-colour/HEAD/resources/testcardf1.png -------------------------------------------------------------------------------- /resources/youtube-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guruthree/pico-composite-PAL-colour/HEAD/resources/youtube-thumb.jpg -------------------------------------------------------------------------------- /resources/generatesinetable.m: -------------------------------------------------------------------------------- 1 | % 2 | clear 3 | 4 | s = 360.*(0:127)./128; 5 | v = sind(s) * 127; 6 | 7 | % return 8 | 9 | fprintf('int8_t sine_table[128] = { 0x%02x', v(1)); 10 | 11 | for ii=2:length(v) 12 | tv = round(v(ii)); 13 | 14 | if tv >= 0 15 | fprintf(', 0x%02x', tv); 16 | else 17 | fprintf(', -0x%02x', -tv); 18 | end 19 | 20 | end 21 | 22 | fprintf(' }; \n\n'); 23 | -------------------------------------------------------------------------------- /ideas: -------------------------------------------------------------------------------- 1 | possible demos routines? 2 | 3 | some sort of game? 4 | break out 5 | super hexagon 6 | warp/starfield? 7 | 8 | gameboy emulator? 9 | https://github.com/deltabeard/Peanut-GB 10 | 11 | add a button to change demo? 12 | 13 | === 14 | 15 | do proper PAL 50i rather than 25p? 16 | what keeps some digital adapters from working? 17 | 18 | code clean up 19 | buffer class 20 | options for x,y of demos on screen & their sizes 21 | 22 | double the y-resolution by using 4 bits then 4 bits? 23 | double x by changing the # of samples per pixel, and then switching to 2 bits per y/u/v? 24 | or by setting up the buffer class with individual arrays for y, u, and v? 25 | 26 | some sort of auto figuring out the correct DAC frequency? 27 | (fine tuning buttons?) 28 | -------------------------------------------------------------------------------- /includes/sine.h: -------------------------------------------------------------------------------- 1 | // sine lookup table 0 to 127 is 0 to 357.19 degrees 2 | // ranging between -127 and 127 3 | 4 | int8_t sine_table[128] = { 0x00, 0x06, 0x0c, 0x13, 0x19, 0x1f, 0x25, 0x2b, 0x31, 0x36, 0x3c, 0x41, 0x47, 0x4c, 0x51, 0x55, 0x5a, 0x5e, 0x62, 0x66, 0x6a, 0x6d, 0x70, 0x73, 0x75, 0x78, 0x7a, 0x7b, 0x7d, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7e, 0x7e, 0x7d, 0x7b, 0x7a, 0x78, 0x75, 0x73, 0x70, 0x6d, 0x6a, 0x66, 0x62, 0x5e, 0x5a, 0x55, 0x51, 0x4c, 0x47, 0x41, 0x3c, 0x36, 0x31, 0x2b, 0x25, 0x1f, 0x19, 0x13, 0x0c, 0x06, 0x00, -0x06, -0x0c, -0x13, -0x19, -0x1f, -0x25, -0x2b, -0x31, -0x36, -0x3c, -0x41, -0x47, -0x4c, -0x51, -0x55, -0x5a, -0x5e, -0x62, -0x66, -0x6a, -0x6d, -0x70, -0x73, -0x75, -0x78, -0x7a, -0x7b, -0x7d, -0x7e, -0x7e, -0x7f, -0x7f, -0x7f, -0x7e, -0x7e, -0x7d, -0x7b, -0x7a, -0x78, -0x75, -0x73, -0x70, -0x6d, -0x6a, -0x66, -0x62, -0x5e, -0x5a, -0x55, -0x51, -0x4c, -0x47, -0x41, -0x3c, -0x36, -0x31, -0x2b, -0x25, -0x1f, -0x19, -0x13, -0x0c, -0x06 }; 5 | -------------------------------------------------------------------------------- /includes/time.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | inline uint64_t time() { 28 | return to_us_since_boot(get_absolute_time()); 29 | } 30 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | # these need to be early to be honored on the first cmake 4 | #set(PICO_BOARD "pimoroni_tiny2040" CACHE STRING "" FORCE) 5 | set(PICO_BOARD "pico" CACHE STRING "" FORCE) 6 | 7 | #set(PICO_COPY_TO_RAM 1 CACHE STRING "" FORCE) 8 | 9 | include(pico_sdk_import.cmake) 10 | 11 | project(pico-composite-PAL-colour_project C CXX ASM) 12 | set(CMAKE_C_STANDARD 11) 13 | set(CMAKE_CXX_STANDARD 17) 14 | pico_sdk_init() 15 | 16 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-usage") 17 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-usage") 18 | 19 | add_executable(pico-composite-PAL-colour 20 | pico-composite-PAL-colour.cpp 21 | ) 22 | 23 | pico_generate_pio_header(pico-composite-PAL-colour ${CMAKE_CURRENT_LIST_DIR}/dac.pio OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR}/generated) 24 | 25 | include_directories(includes includes/images demos) 26 | 27 | target_include_directories(pico-composite-PAL-colour PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 28 | 29 | target_link_libraries(pico-composite-PAL-colour 30 | pico_stdlib 31 | pico_multicore 32 | hardware_pio 33 | hardware_dma 34 | pico_malloc 35 | pico_mem_ops 36 | pico_float 37 | ) 38 | pico_add_extra_outputs(pico-composite-PAL-colour) 39 | 40 | pico_enable_stdio_usb(pico-composite-PAL-colour 0) 41 | pico_enable_stdio_uart(pico-composite-PAL-colour 0) 42 | 43 | # for CPU clocks above 266 MHz, slow down access to the flash 44 | pico_define_boot_stage2(slower_boot2 ${PICO_DEFAULT_BOOT_STAGE2_FILE}) 45 | target_compile_definitions(slower_boot2 PRIVATE PICO_FLASH_SPI_CLKDIV=4) 46 | pico_set_boot_stage2(pico-composite-PAL-colour slower_boot2) 47 | -------------------------------------------------------------------------------- /includes/jet.h: -------------------------------------------------------------------------------- 1 | // This is a 63 element version of the jet colourmap produced by Octave/MATLAB 2 | 3 | /* 4 | 5 | c = colormap('jet'); 6 | c = c * 127; 7 | c = round(c); 8 | c = c(1:2:end,:); 9 | fprintf('{%d, %d, %d},\n', c') 10 | 11 | */ 12 | 13 | uint8_t __in_flash("jet") jet[64][3] = { 14 | {0, 0, 65}, 15 | {0, 0, 73}, 16 | {0, 0, 81}, 17 | {0, 0, 89}, 18 | {0, 0, 97}, 19 | {0, 0, 105}, 20 | {0, 0, 113}, 21 | {0, 0, 121}, 22 | {0, 2, 127}, 23 | {0, 10, 127}, 24 | {0, 18, 127}, 25 | {0, 26, 127}, 26 | {0, 34, 127}, 27 | {0, 42, 127}, 28 | {0, 50, 127}, 29 | {0, 58, 127}, 30 | {0, 65, 127}, 31 | {0, 73, 127}, 32 | {0, 81, 127}, 33 | {0, 89, 127}, 34 | {0, 97, 127}, 35 | {0, 105, 127}, 36 | {0, 113, 127}, 37 | {0, 121, 127}, 38 | {2, 127, 125}, 39 | {10, 127, 117}, 40 | {18, 127, 109}, 41 | {26, 127, 101}, 42 | {34, 127, 93}, 43 | {42, 127, 85}, 44 | {50, 127, 77}, 45 | {58, 127, 69}, 46 | {65, 127, 62}, 47 | {73, 127, 54}, 48 | {81, 127, 46}, 49 | {89, 127, 38}, 50 | {97, 127, 30}, 51 | {105, 127, 22}, 52 | {113, 127, 14}, 53 | {121, 127, 6}, 54 | {127, 125, 0}, 55 | {127, 117, 0}, 56 | {127, 109, 0}, 57 | {127, 101, 0}, 58 | {127, 93, 0}, 59 | {127, 85, 0}, 60 | {127, 77, 0}, 61 | {127, 69, 0}, 62 | {127, 62, 0}, 63 | {127, 54, 0}, 64 | {127, 46, 0}, 65 | {127, 38, 0}, 66 | {127, 30, 0}, 67 | {127, 22, 0}, 68 | {127, 14, 0}, 69 | {127, 6, 0}, 70 | {125, 0, 0}, 71 | {117, 0, 0}, 72 | {109, 0, 0}, 73 | {101, 0, 0}, 74 | {93, 0, 0}, 75 | {85, 0, 0}, 76 | {77, 0, 0}, 77 | {69, 0, 0} 78 | }; 79 | -------------------------------------------------------------------------------- /includes/hsv.h: -------------------------------------------------------------------------------- 1 | // This is a 63 element version of the hsv colourmap produced by Octave/MATLAB 2 | 3 | /* 4 | 5 | c = colormap('hsv'); 6 | c = c * 127; 7 | c = round(c); 8 | c = c(1:end,:); 9 | fprintf('{%d, %d, %d},\n', c') 10 | 11 | */ 12 | 13 | uint8_t __in_flash("hsv") hsv[64][3] = { 14 | {127, 0, 0}, 15 | {127, 12, 0}, 16 | {127, 24, 0}, 17 | {127, 36, 0}, 18 | {127, 48, 0}, 19 | {127, 60, 0}, 20 | {127, 71, 0}, 21 | {127, 83, 0}, 22 | {127, 95, 0}, 23 | {127, 107, 0}, 24 | {127, 119, 0}, 25 | {123, 127, 0}, 26 | {111, 127, 0}, 27 | {99, 127, 0}, 28 | {87, 127, 0}, 29 | {75, 127, 0}, 30 | {64, 127, 0}, 31 | {52, 127, 0}, 32 | {40, 127, 0}, 33 | {28, 127, 0}, 34 | {16, 127, 0}, 35 | {4, 127, 0}, 36 | {0, 127, 8}, 37 | {0, 127, 20}, 38 | {0, 127, 32}, 39 | {0, 127, 44}, 40 | {0, 127, 56}, 41 | {0, 127, 67}, 42 | {0, 127, 79}, 43 | {0, 127, 91}, 44 | {0, 127, 103}, 45 | {0, 127, 115}, 46 | {0, 127, 127}, 47 | {0, 115, 127}, 48 | {0, 103, 127}, 49 | {0, 91, 127}, 50 | {0, 79, 127}, 51 | {0, 67, 127}, 52 | {0, 56, 127}, 53 | {0, 44, 127}, 54 | {0, 32, 127}, 55 | {0, 20, 127}, 56 | {0, 8, 127}, 57 | {4, 0, 127}, 58 | {16, 0, 127}, 59 | {28, 0, 127}, 60 | {40, 0, 127}, 61 | {52, 0, 127}, 62 | {64, 0, 127}, 63 | {75, 0, 127}, 64 | {87, 0, 127}, 65 | {99, 0, 127}, 66 | {111, 0, 127}, 67 | {123, 0, 127}, 68 | {127, 0, 119}, 69 | {127, 0, 107}, 70 | {127, 0, 95}, 71 | {127, 0, 83}, 72 | {127, 0, 71}, 73 | {127, 0, 60}, 74 | {127, 0, 48}, 75 | {127, 0, 36}, 76 | {127, 0, 24}, 77 | {127, 0, 12} 78 | }; 79 | -------------------------------------------------------------------------------- /includes/random.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | // https://forums.raspberrypi.com/viewtopic.php?t=302960 28 | void seed_random_from_rosc() { 29 | uint32_t random = 0x811c9dc5; 30 | uint8_t next_byte = 0; 31 | volatile uint32_t *rnd_reg = (uint32_t *)(ROSC_BASE + ROSC_RANDOMBIT_OFFSET); 32 | 33 | for (int i = 0; i < 16; i++) { 34 | for (int k = 0; k < 8; k++) { 35 | next_byte = (next_byte << 1) | (*rnd_reg & 1); 36 | } 37 | 38 | random ^= next_byte; 39 | random *= 0x01000193; 40 | } 41 | 42 | srand(random); 43 | } 44 | 45 | inline int8_t randi(int8_t mi, int8_t ma) { 46 | return mi + rand() % (ma - mi); 47 | } 48 | -------------------------------------------------------------------------------- /resources/font2h.m: -------------------------------------------------------------------------------- 1 | % 2 | % The MIT License (MIT) 3 | % 4 | % Copyright (c) 2022 guruthree 5 | % 6 | % Permission is hereby granted, free of charge, to any person obtaining a copy 7 | % of this software and associated documentation files (the "Software"), to deal 8 | % in the Software without restriction, including without limitation the rights 9 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | % copies of the Software, and to permit persons to whom the Software is 11 | % furnished to do so, subject to the following conditions: 12 | % 13 | % The above copyright notice and this permission notice shall be included in 14 | % all copies or substantial portions of the Software. 15 | % 16 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | % THE SOFTWARE. 23 | % 24 | 25 | clear 26 | 27 | % pack the 3x5 bits of the character down into a uint16_t 28 | 29 | pkg load image % for octave 30 | 31 | im = imread('font.png'); 32 | im = im(:,:,1); 33 | 34 | charx = 3+1; 35 | chary = 5+1; 36 | 37 | 38 | across = (size(im,2)+1) / charx; 39 | down = (size(im,1)+1) / chary; 40 | 41 | ascii = 31; 42 | for jj=1:down 43 | for ii=1:across 44 | xrange = ((ii-1)*charx+1):(ii*charx-1); 45 | yrange = ((jj-1)*chary+1):(jj*chary-1); 46 | c = ~im(yrange,xrange); 47 | bits = ([(c'(:)') 0]); 48 | 49 | out = 0; 50 | for bb=1:16 51 | out = bitor(out, bitshift(bits(bb), bb-1)); 52 | end 53 | 54 | outs = dec2bin(out); 55 | while length(outs) < 16 56 | outs = ['0' outs]; 57 | end 58 | 59 | fprintf('0b%s, // %s\n', outs, ascii+((jj-1)*across+ii)) 60 | 61 | % the right most bit of bits is the top left corner of the character 62 | % the left most bit is padding 63 | 64 | % imshow(c) 65 | % pause(0.1) 66 | end 67 | end 68 | 69 | % A = 0b0101101111101010 = 23530 70 | -------------------------------------------------------------------------------- /resources/cliffs.m: -------------------------------------------------------------------------------- 1 | % 2 | % The MIT License (MIT) 3 | % 4 | % Copyright (c) 2022 guruthree 5 | % 6 | % Permission is hereby granted, free of charge, to any person obtaining a copy 7 | % of this software and associated documentation files (the "Software"), to deal 8 | % in the Software without restriction, including without limitation the rights 9 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | % copies of the Software, and to permit persons to whom the Software is 11 | % furnished to do so, subject to the following conditions: 12 | % 13 | % The above copyright notice and this permission notice shall be included in 14 | % all copies or substantial portions of the Software. 15 | % 16 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | % THE SOFTWARE. 23 | % 24 | 25 | % generate a landscape between two cliff faces to fly through 26 | 27 | clear 28 | 29 | basecliff = [80 80 80 5 1 0 1 -2 1 0 1 5 80 80 80]; 30 | % xspacing = (1:length(basecliff)) * 20; 31 | 32 | % plot(xspacing, basecliff, '.-') 33 | % axis image 34 | 35 | thecliff = zeros(100, length(basecliff)); 36 | thecliff(1,:) = basecliff; 37 | 38 | for ii=2:size(thecliff,1) 39 | 40 | for jj=1:size(thecliff,2) 41 | 42 | d = basecliff(jj) - thecliff(ii-1,jj); 43 | thecliff(ii,jj) = thecliff(ii-1,jj)+randi([-5 5])+d/8; 44 | 45 | end 46 | 47 | if mod(ii,2) == 0 48 | for jj=2:size(thecliff,2)-1 49 | thecliff(ii,jj) = thecliff(ii,jj-1) / 128 + thecliff(ii,jj) * 126 / 128 + thecliff(ii,jj+1) / 128; 50 | end 51 | end 52 | 53 | thecliff(ii,4:end-3) = thecliff(ii,4:end-3) + sin(ii/10)*2; 54 | 55 | if ii > 10 56 | surf(thecliff(ii-10:ii,:)) 57 | shading flat 58 | view([0 20]) 59 | pause(1/10) 60 | end 61 | 62 | end 63 | 64 | % surf(thecliff) 65 | % shading flat 66 | % view([-75 72]) 67 | 68 | % plot(mean(thecliff)) 69 | 70 | % original: 80, next was 75 71 | % want to bias random number up more 72 | % 80 - 75 = 5 73 | % add a portion of that difference to the random number to bias it 74 | 75 | -------------------------------------------------------------------------------- /dac.pio: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022-2023 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | .program dac 28 | 29 | .wrap_target 30 | ; nop 31 | ; pull block 32 | ; nop 33 | out pins, 8 34 | .wrap 35 | 36 | % c-sdk { 37 | 38 | // it was supposed to max out at 1, scope seams to say 1.25, so 1.35 for some margin 39 | // luckily the TV doesn't seem to be susper sensitive to exact voltage 40 | // the white voltage level should be around 128 to allow some code optimisations!!! 41 | #define DIVISIONS_PER_VOLT (255/1.2) 42 | 43 | #define PIN_COUNT 8 44 | static inline void dac_program_init(PIO pio, uint sm, uint offset, uint pin_base, float divider) { 45 | for(uint i=pin_base; i= 0 57 | out = sprintf('%s 0x%02x,', out, u); 58 | else 59 | out = sprintf('%s -0x%02x,', out, -u); 60 | end 61 | 62 | if v >= 0 63 | out = sprintf('%s 0x%02x,', out, v); 64 | else 65 | out = sprintf('%s -0x%02x,', out, -v); 66 | end 67 | end 68 | 69 | out = sprintf('%s\n', out); 70 | end 71 | 72 | % file = strrep(file, '.', ''); 73 | [~, file] = fileparts(file); 74 | 75 | fid = fopen(sprintf('%s.h', file), 'w'); 76 | fprintf(fid, 'int8_t __in_flash("%s") %s[] = {\n%s};\n\n', file, file, out); 77 | fclose(fid) 78 | 79 | function [y, u, v] = rgb2yuv(r, g, b) 80 | y = 5 * r / 16 + 9 * g / 16 + b / 8; 81 | v = (b - y) / 2; 82 | u = 13 * (r - y) / 16; 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /includes/vectormath.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022 guruthree, Blayzeing 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | // thanks to @Blayzeing for his help with this! 28 | 29 | #ifndef VECTORMATH 30 | #define VECTORMATH 31 | 32 | struct Vector3 { 33 | float x, y, z; 34 | Vector3 add(Vector3); 35 | Vector3 subtract(Vector3); 36 | Vector3 scale(float); 37 | Vector3 scalarMultiply(Vector3); 38 | float dotProduct(Vector3); 39 | }; 40 | 41 | struct Matrix3{ 42 | float ii, ij, ik, 43 | ji, jj, jk, 44 | ki, kj, kk; 45 | 46 | Matrix3 multiply(Matrix3); 47 | Vector3 preMultiply(Vector3); 48 | 49 | // Gets a rotation matrix 50 | static Matrix3 getRotationMatrix(float alpha, float beta, float gamma); 51 | 52 | // Gets a perspective scaling matrix, scaled along the z-axis 53 | static Matrix3 getPerspMatrix(Vector3 surfacePos); 54 | 55 | // Gets a camera transform matrix 56 | /*static Matrix3 getCameraMaterix(Vector3 cameraPosition, 57 | Vector3 cameraRotation, 58 | float surfaceDistanceIntoZ);*/ 59 | }; 60 | 61 | 62 | Matrix3 Matrix3::multiply(Matrix3 m) 63 | { 64 | Matrix3 output = {m.ii*ii+m.ji*ij+m.ki*ik, m.ij*ii+m.jj*ij+m.kj*ik, m.ik*ii+m.jk*ij+m.kk*ik, 65 | m.ii*ji+m.ji*jj+m.ki*jk, m.ij*ji+m.jj*jj+m.kj*jk, m.ik*ji+m.jk*jj+m.kk*jk, 66 | m.ii*ki+m.ji*kj+m.ki*kk, m.ij*ki+m.jj*kj+m.kj*kk, m.ik*ki+m.jk*kj+m.kk*kk}; 67 | return output; 68 | } 69 | Vector3 Matrix3::preMultiply(Vector3 v) 70 | { 71 | Vector3 output = {v.x*ii + v.y*ij + v.z*ik, 72 | v.x*ji + v.y*jj + v.z*jk, 73 | v.x*ki + v.y*kj + v.z*kk}; 74 | return output; 75 | } 76 | 77 | Vector3 Vector3::add(Vector3 v) 78 | { 79 | Vector3 out = {x + v.x, 80 | y + v.y, 81 | z + v.z}; 82 | return out; 83 | } 84 | Vector3 Vector3::subtract(Vector3 v) 85 | { 86 | Vector3 out = {x - v.x, 87 | y - v.y, 88 | z - v.z}; 89 | return out; 90 | } 91 | Vector3 Vector3::scale(float s) 92 | { 93 | Vector3 out = {x * s, 94 | y * s, 95 | z * s}; 96 | return out; 97 | } 98 | Vector3 Vector3::scalarMultiply(Vector3 v) 99 | { 100 | Vector3 out = {x * v.x, 101 | y * v.y, 102 | z * v.z}; 103 | return out; 104 | } 105 | float Vector3::dotProduct(Vector3 v) 106 | { 107 | return x * v.x + y * v.y + z * v.z; 108 | } 109 | 110 | 111 | // rotation matrix, angles about rotate(x, y, z) 112 | // note angles are in RADIANS 113 | Matrix3 Matrix3::getRotationMatrix(float alpha, float beta, float gamma) 114 | { 115 | Matrix3 Rz = {cosf(gamma), -sinf(gamma), 0, 116 | sinf(gamma), cosf(gamma), 0, 117 | 0, 0, 1}; 118 | Matrix3 Ry = { cosf(beta), 0, sinf(beta), 119 | 0, 1, 0, 120 | -sinf(beta), 0, cosf(beta)}; 121 | Matrix3 Rx = { 1, 0, 0, 122 | 0, cosf(alpha), -sinf(alpha), 123 | 0, sinf(alpha), cosf(alpha)}; 124 | return Rz.multiply(Ry.multiply(Rx)); 125 | } 126 | 127 | Matrix3 Matrix3::getPerspMatrix(Vector3 sp) { 128 | Matrix3 mat = { 1 , 0 , sp.x/sp.z, 129 | 0 , 1 , sp.y/sp.z, 130 | 0 , 0 , 1.0f/sp.z}; 131 | return mat; 132 | } 133 | 134 | /*Matrix3 Matrix3::getCameraMatrix(Vector3 camPos, Vector3 camRot, float d) 135 | { 136 | // Rotate the world inversely around the camera position 137 | Matrix3 invRot = rotate(camRot) 138 | }*/ 139 | 140 | #endif 141 | -------------------------------------------------------------------------------- /resources/videocompressionbw.m: -------------------------------------------------------------------------------- 1 | % 2 | % The MIT License (MIT) 3 | % 4 | % Copyright (c) 2023 guruthree 5 | % 6 | % Permission is hereby granted, free of charge, to any person obtaining a copy 7 | % of this software and associated documentation files (the "Software"), to deal 8 | % in the Software without restriction, including without limitation the rights 9 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | % copies of the Software, and to permit persons to whom the Software is 11 | % furnished to do so, subject to the following conditions: 12 | % 13 | % The above copyright notice and this permission notice shall be included in 14 | % all copies or substantial portions of the Software. 15 | % 16 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | % THE SOFTWARE. 23 | % 24 | 25 | clear 26 | clc 27 | 28 | % read in video and convert it to a header file of runlength encoded frames 29 | % this one is specialised for black and white video 30 | % the format is number of pixels white, number of pixels black 31 | 32 | folder = 'bad apple'; % source of frames, expects %05d.png for filenaming 33 | 34 | name = 'badapple'; % name of video 35 | 36 | % output folder 37 | fid = fopen([name '.h'], 'w'); 38 | % fid = 1; % stdout 39 | 40 | % output resolution 41 | xsize = 220; 42 | ysize = 110; 43 | 44 | % what frames to read in and process 45 | % framerange = 39:84; 46 | % framerange = 1:1054; 47 | % framerange = 1:5478; 48 | 49 | % show 5 seconds of video at a time skipping a few sections 50 | startframes = ([0:7:42]*5)*25+1; 51 | endframes = startframes + 124; 52 | framerange = []; 53 | for ii=1:length(startframes) 54 | framerange = [ framerange startframes(ii):endframes(ii) ]; %#ok 55 | end 56 | framerange = framerange + 34; % skip the blackframes at the start 57 | 58 | % how long each frame is 59 | framelengths = []; 60 | 61 | % loop through each frame 62 | for zz=framerange 63 | 64 | %% compress the frame 65 | 66 | % read and resize... may want to do some cropping.... 67 | im = imread(sprintf('%s/%05d.png', folder, zz)); 68 | im = imresize(im, [ysize xsize], 'box'); 69 | 70 | % make black and white 71 | im = rgb2gray(im); 72 | Y = imbinarize(im'); 73 | 74 | % turn into one big vector 75 | Y = Y(:); 76 | 77 | % the count of each colour in a row 78 | % the first index is black, the second index is white 79 | if Y(1) == false 80 | compressed = [1]; 81 | else 82 | compressed = [0; 1]; 83 | end 84 | 85 | for ii=2:size(Y,1) 86 | 87 | if Y(ii) == Y(ii-1) && compressed(end,1) < 255 88 | compressed(end,1) = compressed(end,1) + 1; 89 | elseif Y(ii) == Y(ii-1) && compressed(end,1) == 255 90 | compressed(end+1,1) = 0; %#ok 91 | compressed(end+1,1) = 1; %#ok 92 | else 93 | compressed(end+1,1) = 1; %#ok 94 | end 95 | 96 | end 97 | 98 | framelengths(end+1) = size(compressed,1); %#ok 99 | fprintf('frame %03d is %d long\n', zz , size(compressed,1)) 100 | 101 | %% reconstruct the frame 102 | 103 | % newim = zeros(0,1,'logical'); 104 | % currentcolour = false; 105 | % for ii=1:size(compressed,1) 106 | % for jj=1:compressed(ii,1) 107 | % newim(end+1,1) = currentcolour; %#ok 108 | % end 109 | % currentcolour = ~currentcolour; 110 | % end 111 | % im2 = reshape(newim', xsize, ysize)'; 112 | % im2 = im2*255; 113 | % 114 | % 115 | % subplot(1,2,1) 116 | % imshow(im) 117 | % set(gca, 'DataAspectRatio', [4 3 1]) 118 | % 119 | % subplot(1,2,2) 120 | % imshow(im2) 121 | % set(gca, 'DataAspectRatio', [4 3 1]) 122 | % 123 | % drawnow 124 | 125 | %% write out the frame 126 | 127 | frame = sprintf('%sframe%03d', name, zz); 128 | 129 | fprintf(fid, 'uint8_t __in_flash("%s") %s[] = {\n\n', frame, frame); 130 | 131 | for ii=1:size(compressed, 1) 132 | y = uint8(compressed(ii,1)); 133 | 134 | out = sprintf(' 0x%02x, \n', y); 135 | 136 | fprintf(fid, out); 137 | 138 | end 139 | 140 | fprintf(fid, '};\n\n'); 141 | 142 | end % zz 143 | 144 | %% write out metadata 145 | 146 | fprintf(fid, '#define NUM_%sFRAMES %d\n\n', upper(name), length(framelengths)); 147 | 148 | fprintf(fid, 'uint32_t __in_flash("%sframelengths") %sframelengths[NUM_%sFRAMES] = {\n', name, name, upper(name)); 149 | for ii=1:length(framelengths) 150 | fprintf(fid, ' %d,\n', framelengths(ii)); 151 | end 152 | fprintf(fid, '};\n\n'); 153 | 154 | fprintf(fid, 'uint8_t* __in_flash("%sframes") %sframes[NUM_%sFRAMES] = {\n', name, name, upper(name)); 155 | for ii=framerange 156 | fprintf(fid, ' %sframe%03d,\n', name, ii); 157 | end 158 | fprintf(fid, '};\n\n'); 159 | 160 | if fid ~= 1 161 | fclose(fid); 162 | end 163 | 164 | fprintf('done\n\n'); 165 | -------------------------------------------------------------------------------- /includes/discountadafruitgfx.h: -------------------------------------------------------------------------------- 1 | /* graphics functions ported from AdafruitGFX to the YUV format used in this project 2 | this file is therefore released under the BSD License 3 | 4 | Copyright (c) 2013 Adafruit Industries. All rights reserved. 5 | Copyright (c) 2022 guruthree 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | - Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | - Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #ifndef DISCOUNTGFX 30 | #define DISCOUNTGFX 31 | 32 | template 33 | inline void swapInt(T &a, T &b) { 34 | T t; 35 | t = a; 36 | a = b; 37 | b = t; 38 | } 39 | 40 | // void setPixelRGB(int8_t *buf, uint16_t xcoord, uint16_t ycoord, uint8_t r, uint8_t g, uint8_t b); 41 | 42 | 43 | // reference: Adafruit_GFX::writeLine() 44 | void drawLineRGB(int8_t *buf, int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t r, uint8_t g, uint8_t b) { 45 | int32_t steep = abs(y1 - y0) > abs(x1- x0); 46 | if (steep) { 47 | swapInt(x0, y0); 48 | swapInt(x1, y1); 49 | } 50 | 51 | if (x0 > x1) { 52 | swapInt(x0, x1); 53 | swapInt(y0, y1); 54 | } 55 | 56 | int32_t dx = x1 - x0; 57 | int32_t dy = abs(y1 - y0); 58 | 59 | int32_t err = dx / 2; 60 | int32_t ystep; 61 | 62 | 63 | if (y0 < y1) { 64 | ystep = 1; 65 | } 66 | else { 67 | ystep = -1; 68 | } 69 | 70 | for (; x0 <= x1; x0++) { 71 | if (steep) { 72 | setPixelRGB(buf, y0, x0, r, g, b); 73 | } 74 | else { 75 | setPixelRGB(buf, x0, y0, r, g, b); 76 | } 77 | err -= dy; 78 | if (err < 0) { 79 | y0 += ystep; 80 | err += dx; 81 | } 82 | } 83 | } 84 | 85 | 86 | // reference: Adafruit_GFX::fillTriangle 87 | void fillTriangle(int8_t *buf, int32_t x0, int32_t y0, int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t r, uint8_t g, uint8_t _b) { 88 | 89 | 90 | 91 | int16_t a, b, y, last; 92 | 93 | // Sort coordinates by Y order (y2 >= y1 >= y0) 94 | if (y0 > y1) { 95 | swapInt(y0, y1); 96 | swapInt(x0, x1); 97 | } 98 | if (y1 > y2) { 99 | swapInt(y2, y1); 100 | swapInt(x2, x1); 101 | } 102 | if (y0 > y1) { 103 | swapInt(y0, y1); 104 | swapInt(x0, x1); 105 | } 106 | 107 | if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing 108 | a = b = x0; 109 | if (x1 < a) 110 | a = x1; 111 | else if (x1 > b) 112 | b = x1; 113 | if (x2 < a) 114 | a = x2; 115 | else if (x2 > b) 116 | b = x2; 117 | // writeFastHLine(a, y0, b - a + 1, color); 118 | drawLineRGB(buf, a, y0, b, y2, r, g, _b); 119 | return; 120 | } 121 | 122 | int16_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, 123 | dx12 = x2 - x1, dy12 = y2 - y1; 124 | int32_t sa = 0, sb = 0; 125 | 126 | // For upper part of triangle, find scanline crossings for segments 127 | // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 128 | // is included here (and second loop will be skipped, avoiding a /0 129 | // error there), otherwise scanline y1 is skipped here and handled 130 | // in the second loop...which also avoids a /0 error here if y0=y1 131 | // (flat-topped triangle). 132 | if (y1 == y2) 133 | last = y1; // Include y1 scanline 134 | else 135 | last = y1 - 1; // Skip it 136 | 137 | for (y = y0; y <= last; y++) { 138 | a = x0 + sa / dy01; 139 | b = x0 + sb / dy02; 140 | sa += dx01; 141 | sb += dx02; 142 | /* longhand: 143 | a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); 144 | b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); 145 | */ 146 | if (a > b) 147 | swapInt(a, b); 148 | // writeFastHLine(a, y, b - a + 1, color); 149 | drawLineRGB(buf, a, y, b, y, r, g, _b); 150 | } 151 | 152 | // For lower part of triangle, find scanline crossings for segments 153 | // 0-2 and 1-2. This loop is skipped if y1=y2. 154 | sa = (int32_t)dx12 * (y - y1); 155 | sb = (int32_t)dx02 * (y - y0); 156 | for (; y <= y2; y++) { 157 | a = x1 + sa / dy12; 158 | b = x0 + sb / dy02; 159 | sa += dx12; 160 | sb += dx02; 161 | /* longhand: 162 | a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); 163 | b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); 164 | */ 165 | if (a > b) 166 | swapInt(a, b); 167 | // writeFastHLine(a, y, b - a + 1, color); 168 | drawLineRGB(buf, a, y, b, y, r, g, _b); 169 | } 170 | 171 | 172 | } 173 | 174 | #endif 175 | -------------------------------------------------------------------------------- /includes/font.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #include 28 | 29 | // right most bit is the top left corner of the character 30 | 31 | uint16_t __in_flash("font") font[32*3-2] = { 32 | 0b0010000010010010, // ! (ASCII: 33) 33 | 0b0000000000101101, // " 34 | 0b0101111101111101, // # 35 | 0b0111110111011111, // $ 36 | 0b0101001010100101, // % 37 | 0b0110101011001010, // & 38 | 0b0000000000001001, // ' 39 | 0b0010001001001010, // ( 40 | 0b0010100100100010, // ) 41 | 0b0000101010101000, // * 42 | 0b0000010111010000, // + 43 | 0b0001010000000000, // , 44 | 0b0000000111000000, // - 45 | 0b0010000000000000, // . 46 | 0b0001011010110100, // / 47 | 0b0111101101101111, // 0 48 | 0b0010010010010010, // 1 49 | 0b0111001010100011, // 2 50 | 0b0111100111100111, // 3 51 | 0b0100100111101101, // 4 52 | 0b0011100011001111, // 5 53 | 0b0111101111001111, // 6 54 | 0b0100100100100111, // 7 55 | 0b0111101111101111, // 8 56 | 0b0100100111101111, // 9 57 | 0b0000010000010000, // : 58 | 0b0001010000010000, // ; 59 | 0b0100010001010100, // < 60 | 0b0000111000111000, // = 61 | 0b0001010100010001, // > 62 | 0b0010000110100111, // ? 63 | 0b0010101101100111, // @ 64 | 0b0101101111101010, // A 65 | 0b0011101011101011, // B 66 | 0b0111001001001111, // C 67 | 0b0011101101101011, // D 68 | 0b0111001111001111, // E 69 | 0b0001001011001111, // F 70 | 0b0111101001001111, // G 71 | 0b0101101111101101, // H 72 | 0b0111010010010111, // I 73 | 0b0111101100100100, // J 74 | 0b0101101011101101, // K 75 | 0b0111001001001001, // L 76 | 0b0101101101111111, // M 77 | 0b0101101101101111, // N 78 | 0b0111101101101111, // O 79 | 0b0001001011101011, // P 80 | 0b0110111101101111, // Q 81 | 0b0101101011101011, // R 82 | 0b0111100111001111, // S 83 | 0b0010010010010111, // T 84 | 0b0111101101101101, // U 85 | 0b0010101101101101, // V 86 | 0b0111111101101101, // W 87 | 0b0101101010101101, // X 88 | 0b0010010101101101, // Y 89 | 0b0111001010100111, // Z 90 | 0b0110010010010110, // [ 91 | 0b0100110010011001, // backslash 92 | 0b0011010010010011, // ] 93 | 0b0000000000101010, // ^ 94 | 0b0111000000000000, // _ 95 | 0b0000000000010001, // ` 96 | 0b0110101010000000, // a 97 | 0b0111101111001000, // b 98 | 0b0011001011000000, // c 99 | 0b0111101111100000, // d 100 | 0b0110001111101010, // e 101 | 0b0001011001010000, // f 102 | 0b0110100110101010, // g 103 | 0b0101101011001001, // h 104 | 0b0010010010000010, // i 105 | 0b0010101100000100, // j 106 | 0b0101011101001001, // k 107 | 0b0010010010010010, // l 108 | 0b0101111111000000, // m 109 | 0b0101101111000000, // n 110 | 0b0111101111000000, // o 111 | 0b0001011101010000, // p 112 | 0b0100110101010000, // q 113 | 0b0001001010000000, // r 114 | 0b0001010001010000, // s 115 | 0b0010010111010000, // t 116 | 0b0111101101000000, // u 117 | 0b0011101101000000, // v 118 | 0b0111111101000000, // w 119 | 0b0101010101000000, // x 120 | 0b0011100110101000, // y 121 | 0b0111011110111000, // z 122 | 0b0100010011010100, // { 123 | 0b0010010010010010, // | 124 | 0b0001010110010001, // } 125 | 0b0000001111100000, // ~ 126 | }; 127 | 128 | // 3x5 font is packed into a uint16_t 129 | // 0b0101101111101010 for the letter A (23530) 130 | // it constructs from left (lsb) to right (msb) 131 | // the last bit is a packing bit to be ignored 132 | // 0b0 101 101 111 101 010 split up into groups of three 133 | // 010 p written out row by row 134 | // 101 135 | // 111 136 | // 101 137 | // 101 138 | 139 | void writeStr(int8_t *buf, int32_t xcoord, int32_t ycoord, std::string str, uint8_t r, uint8_t g, uint8_t b, bool twoX = false) { 140 | uint16_t val; 141 | for (uint8_t l = 0; l < str.length(); l++) { 142 | if (str[l] == ' ') { // there's no space so skip it 143 | continue; 144 | } 145 | val = font[str[l] - 33]; 146 | for (uint8_t i = 0; i < 16; i++) { 147 | if ((val >> i) & 1) { // bit is set, so draw pixel 148 | uint8_t x = i % 3; 149 | uint8_t y = (i - x) / 3; 150 | if (twoX == false) { 151 | setPixelRGB(buf, xcoord + l*4 + x, ycoord + y, r, g, b); 152 | } 153 | else { 154 | setPixelRGBtwoX(buf, xcoord + (l*4 + x)*2, ycoord + y*2, r, g, b); 155 | } 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /resources/videocompression.m: -------------------------------------------------------------------------------- 1 | % 2 | % The MIT License (MIT) 3 | % 4 | % Copyright (c) 2023 guruthree 5 | % 6 | % Permission is hereby granted, free of charge, to any person obtaining a copy 7 | % of this software and associated documentation files (the "Software"), to deal 8 | % in the Software without restriction, including without limitation the rights 9 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | % copies of the Software, and to permit persons to whom the Software is 11 | % furnished to do so, subject to the following conditions: 12 | % 13 | % The above copyright notice and this permission notice shall be included in 14 | % all copies or substantial portions of the Software. 15 | % 16 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | % THE SOFTWARE. 23 | % 24 | 25 | clear 26 | clc 27 | 28 | % read in video and convert it to a header file of runlength encoded frames 29 | 30 | folder = 'frames'; % source of frames, expects %05d.png for filenaming 31 | 32 | name = 'badger'; % name of video 33 | 34 | % output folder 35 | fid = fopen([name '.h'], 'w'); 36 | % fid = 1; % stdout 37 | 38 | % output resolution 39 | xsize = 220; 40 | ysize = 110; 41 | 42 | % what frames to read in and process 43 | framerange = 43:127; % 3.4 seconds 44 | 45 | framelengths = []; 46 | 47 | % loop through each frame 48 | for zz=framerange 49 | 50 | %% compress the frame 51 | 52 | % read and resize... may want to do some cropping.... 53 | im = imread(sprintf('%s/%05d.png', folder, zz)); 54 | im = imresize(im, [ysize xsize], 'box'); 55 | 56 | % separate the colour channels and scale them to 0-127 for pico composite 57 | R = im(:,:,1)'; 58 | G = im(:,:,2)'; 59 | B = im(:,:,3)'; 60 | 61 | % convert to one big column since that's the format for reading out 62 | % into the display buffer 63 | R = R(:); 64 | G = G(:); 65 | B = B(:); 66 | 67 | R = R(:) / 2; 68 | G = G(:) / 2; 69 | B = B(:) / 2; 70 | 71 | % if the next pixel is +- 1 pixel of the previous one, just use the 72 | % previous, kind of blocking to increase the effectiveness of runlength 73 | % encoding 74 | compressrange = 10; 75 | 76 | % we'll also consider matching intensities (i.e., luminance, Y) 77 | YUV = uint8(rgb2ycbcr([R G B])*255); 78 | 79 | for ii=2:length(R) 80 | 81 | % reuse if in range and not the first pixel of the lien 82 | if R(ii)-compressrange <= R(ii-1) && R(ii)+compressrange >= R(ii-1) && ... 83 | G(ii)-compressrange <= G(ii-1) && G(ii)+compressrange >= G(ii-1) && ... 84 | B(ii)-compressrange <= B(ii-1) && B(ii)+compressrange >= B(ii-1) && ... 85 | mod(ii-1, xsize) ~= 0 86 | 87 | % trigger a refresh if there's been a signicant change in Y 88 | if ii > 5 && abs(YUV(ii,1)-YUV(ii-5,1)) > 10 && YUV(ii-5,1) < 60 && YUV(ii,1) > 40 89 | continue 90 | end 91 | 92 | R(ii) = R(ii-1); 93 | G(ii) = G(ii-1); 94 | B(ii) = B(ii-1); 95 | end 96 | end 97 | 98 | RGB = [R G B]; 99 | 100 | % size(unique(RGB, 'rows')) % number of colours 101 | 102 | % last column is the count 103 | compressed = double([RGB(1,:) 1]); 104 | 105 | for ii=2:size(RGB,1) 106 | 107 | if RGB(ii,:) == compressed(end,1:3) & compressed(end,4) < 127 %#ok 108 | compressed(end,4) = compressed(end,4) + 1; 109 | else 110 | compressed(end+1,:) = [RGB(ii,:) 1]; %#ok 111 | end 112 | 113 | end 114 | 115 | framelengths(end+1) = size(compressed,1); %#ok 116 | fprintf('frame %03d is %d long\n', zz , size(compressed,1)) 117 | 118 | %% reconstruct the frame 119 | 120 | % newRGB = zeros(0,3); 121 | % for ii=1:size(compressed,1) 122 | % for jj=1:compressed(ii,4) 123 | % newRGB(end+1,:) = compressed(ii,1:3); 124 | % end 125 | % end 126 | % 127 | % im2 = zeros(110, 220, 3, 'uint8'); 128 | % for ii=1:3 129 | % im2(:,:,ii) = reshape(2*newRGB(:,ii)', xsize, ysize)'; 130 | % % im2(:,:,ii) = reshape(16*newRGB(:,ii)', xsize, ysize)'; 131 | % end 132 | % 133 | % 134 | % subplot(1,2,1) 135 | % imshow(im) 136 | % set(gca, 'DataAspectRatio', [4 3 1]) 137 | % 138 | % subplot(1,2,2) 139 | % imshow(im2) 140 | % set(gca, 'DataAspectRatio', [4 3 1]) 141 | % 142 | % pause(1/25) 143 | 144 | %% write out the frame 145 | 146 | frame = sprintf('%sframe%03d', name, zz); 147 | 148 | fprintf(fid, 'int8_t __in_flash("%s") %s[] = {\n\n', frame, frame); 149 | 150 | for ii=1:size(compressed, 1) 151 | r = compressed(ii,1); 152 | g = compressed(ii,2); 153 | b = compressed(ii,3); 154 | [y, u, v] = rgb2yuv(r, g, b); 155 | y = int8(y); 156 | u = int8(u); 157 | v = int8(v); 158 | 159 | out = sprintf(' 0x%02x,', y); 160 | 161 | if u >= 0 162 | out = sprintf('%s 0x%02x,', out, u); 163 | else 164 | out = sprintf('%s -0x%02x,', out, -u); 165 | end 166 | 167 | if v >= 0 168 | out = sprintf('%s 0x%02x,', out, v); 169 | else 170 | out = sprintf('%s -0x%02x,', out, -v); 171 | end 172 | 173 | out = sprintf('%s 0x%02x, \n', out, uint8(compressed(ii,4))); 174 | 175 | fprintf(fid, out); 176 | 177 | end 178 | 179 | fprintf(fid, '};\n\n'); 180 | 181 | end % zz 182 | 183 | %% write out metadata 184 | 185 | fprintf(fid, '#define NUM_%sFRAMES %d\n\n', upper(name), length(framelengths)); 186 | 187 | fprintf(fid, 'uint32_t __in_flash("%sframelengths") %sframelengths[NUM_%sFRAMES] = {\n', name, name, upper(name)); 188 | for ii=1:length(framelengths) 189 | fprintf(fid, ' %d,\n', framelengths(ii)); 190 | end 191 | fprintf(fid, '};\n\n'); 192 | 193 | fprintf(fid, 'int8_t* __in_flash("%sframes") %sframes[NUM_%sFRAMES] = {\n', name, name, upper(name)); 194 | for ii=framerange 195 | fprintf(fid, ' %sframe%03d,\n', name, ii); 196 | end 197 | fprintf(fid, '};\n\n'); 198 | 199 | if fid ~= 1 200 | fclose(fid); 201 | end 202 | 203 | fprintf('done\n\n'); 204 | 205 | function [y, u, v] = rgb2yuv(r, g, b) 206 | y = 5 * r / 16 + 9 * g / 16 + b / 8; 207 | v = (b - y) / 2; 208 | u = 13 * (r - y) / 16; 209 | end -------------------------------------------------------------------------------- /resources/flames.m: -------------------------------------------------------------------------------- 1 | % 2 | % The MIT License (MIT) 3 | % 4 | % Copyright (c) 2022 guruthree 5 | % 6 | % Permission is hereby granted, free of charge, to any person obtaining a copy 7 | % of this software and associated documentation files (the "Software"), to deal 8 | % in the Software without restriction, including without limitation the rights 9 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | % copies of the Software, and to permit persons to whom the Software is 11 | % furnished to do so, subject to the following conditions: 12 | % 13 | % The above copyright notice and this permission notice shall be included in 14 | % all copies or substantial portions of the Software. 15 | % 16 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | % THE SOFTWARE. 23 | % 24 | 25 | % algorithmic generation of a flame sprite animation 26 | 27 | function flames() 28 | pkg load statistics 29 | 30 | im1 = geneateFlames(); 31 | im2 = geneateFlames(); 32 | im3 = geneateFlames(); 33 | 34 | im = (im1 + im2 + im3); 35 | pcolor(im) 36 | % red channel is solid, green goes from 0 to 1 (red to yellow); 37 | c = zeros(10,3); 38 | c(:,1) = 1; 39 | c(:,2) = (0:9)'/9; 40 | c(1,:) = [0 0 0]; 41 | c(2,1) = 0.5; % fade darker at the edge of the fire 42 | c(3,1) = 0.7; 43 | 44 | colormap(c) 45 | shading flat 46 | caxis([0 9]) 47 | 48 | function im = geneateFlames() 49 | 50 | im = zeros(20,32); 51 | 52 | y = 1; 53 | 54 | oldx = 2 + randi([5 7]); 55 | x = randi([2 3]); 56 | im = fillim(im, y, oldx, oldx+x, 1); 57 | 58 | oldx = oldx + x + 1; 59 | x = randi([1 2]); 60 | im = fillim(im, y, oldx, oldx+x, 2); 61 | 62 | oldx = oldx + x + 1; 63 | x = randi([2 4]); 64 | im = fillim(im, y, oldx, oldx+x, 3); 65 | 66 | oldx = oldx + x + 1; 67 | x = randi([1 2]); 68 | im = fillim(im, y, oldx, oldx+x, 2); 69 | 70 | oldx = oldx + x + 1; 71 | x = randi([2 3]); 72 | im = fillim(im, y, oldx, oldx+x, 1); 73 | 74 | % flame outlines 75 | for zz=1:3 76 | for y=2:size(im,1) 77 | if zz == 1 78 | if y <= 5 79 | probs = [0.3 0.7 1]; 80 | elseif y < 12 81 | probs = [0.1 0.3 1]; 82 | else 83 | probs = [0 0.02 1]; 84 | end 85 | elseif zz == 2 86 | if y <= 5 87 | probs = [0.5 0.6 1]; 88 | elseif y < 10 89 | probs = [0 0.3 1]; 90 | else 91 | probs = [0 0.02 1]; 92 | end 93 | elseif zz == 3 94 | if y <= 5 95 | probs = [0.3 0.4 1]; 96 | elseif y < 10 97 | probs = [0 0.2 1]; 98 | else 99 | probs = [0 0.02 1]; 100 | end 101 | end 102 | 103 | % x = find(im(y-1,:) == zz, 1) 104 | x = findfirstx(im, y-1, zz); 105 | 106 | r = rand(); 107 | if r < probs(1) && im(y,x-1) == 0 && im(y,x) == 0 % expand 108 | im(y,x-1) = zz; 109 | elseif r < probs(2) && im(y,x) == 0 % unchange 110 | im(y,x) = zz; 111 | else%if r < probs(3) % contract 112 | r = rand(); 113 | if r < 1/3 || y < 8 114 | p = 1; 115 | else 116 | p = 2; 117 | end 118 | if im(y,x+p) == 0 119 | im(y,x+p) = zz; 120 | else 121 | break 122 | end 123 | end 124 | 125 | % x = find(im(y-1,:) == zz, 1, 'last') 126 | x = findlastx(im, y-1, zz); 127 | if im(y,x) == zz || im(y,x-1) == zz % shape is closed 128 | break 129 | end 130 | r = rand(); 131 | if r < probs(1) && im(y,x+1) == 0 && im(y,x) == 0 % expand 132 | im(y,x+1) = zz; 133 | elseif r < probs(2) && im(y,x) == 0 % unchange 134 | im(y,x) = zz; 135 | else%if r < probs(3) % contract 136 | r = rand(); 137 | if r < 1/3 || y < 8 138 | p = 1; 139 | else 140 | p = 2; 141 | end 142 | if im(y,x+p) == 0 143 | im(y,x-p) = zz; 144 | else 145 | break 146 | end 147 | end 148 | end 149 | end 150 | 151 | % fill in flame insides 152 | for y=2:size(im,1) 153 | % for x=1:find(im(y,:)==1,1,'last') 154 | xgoal = findlastx(im, y, 1); 155 | if xgoal > 0 156 | for x=1:xgoal 157 | if im(y,x) > 0 && im(y,x+1) == 0 158 | im(y,x+1) = im(y,x); 159 | end 160 | end 161 | end 162 | end 163 | 164 | end 165 | 166 | function im = setim(im, y, x, val) 167 | im(y,x) = val; 168 | end 169 | 170 | function im = fillim(im, y, x1, x2, val) 171 | for ii=x1:x2 172 | im = setim(im, y, ii, val); 173 | end 174 | end 175 | 176 | function x = findfirstx(im, y, val) 177 | x = 1; 178 | while im(y,x) ~= val 179 | x = x + 1; 180 | if x == size(im,2)+1 181 | % outside bounds, not found 182 | x = -1; 183 | break 184 | end 185 | end 186 | end 187 | 188 | function x = findlastx(im, y, val) 189 | x = size(im,2); 190 | while im(y,x) ~= val 191 | x = x - 1; 192 | if x == 0 193 | % outside bounds, not found 194 | x = -1; 195 | break 196 | end 197 | end 198 | end 199 | 200 | end 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pico-composite-PAL-colour # 2 | 3 | Trying to get the [Raspberry Pi RP2040](https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html) chip's PIO to generate a colour PAL composite video signal, using only a [resistor ladder](https://en.wikipedia.org/wiki/Resistor_ladder) digital-to-analogue converter (DAC). 4 | 5 | A YouTube video showing it in action: 6 | 7 | [![Raspberry Pi Pico generated colour composite PAL video demos, early 2023](resources/youtube-thumb.jpg)](https://youtu.be/7H631LBkxes) 8 | 9 | **WARNING: THERE IS A CHANCE THIS SOFTWARE MAY DAMAGE YOUR PICO. I TAKE NO RESPONSIBILITY.** 10 | I'm running the RP2040 at 312 MHz. This is a 141% overclock (or 241% of stock) with a 1.2 VREG voltage. The clock divider on the PIO is about 9. It is a lot of data to process in not a very long period of time (64 microseconds). I believe I have partially broken at least one RP2040 working on this - it no longer registers when plugged into some computers and the GPIO seems to have 5 V coming out. 11 | 12 | As for my specific hardware, I've used both a [Pimoroni Tiny2040](https://shop.pimoroni.com/products/tiny-2040?variant=39560012300371) and a regular PICO. I've built two different simple (read: less precise) resistor DACs. The original consists of one each of a 39k, 22k, 10k, 5.6k, 2.7k, 1.2k, 680, and 330 Ω resistors hooked up to GPIO pins 0 through 7. That DAC, when combined with the 75 Ω termination load of a composite signal should have a 0-1 V range. When making it I did not know colour PAL should have a peak voltage of 1.25 V, leading to my newer DAC with 33k, 18k, 10k, 5.6k, 2.2k, 1k, 560 and 270 Ω resistors. To calculate, you can use 1.25 V = 3.3 V * 75 Ω / (75 Ω + sum of 8 resistor values each roughly doubling). You will ideally want 1 V to be produced when a value of 128 is written to the DAC. The above photo shows my newer DAC, and the below photo the older DAC. 13 | 14 | I have a suspicion that when overclocked not all RP2040 chips end up at exactly the same frequencies, and not all TVs lock on to the PAL colour carrier as easily, so if you are unable to see a colour image you may need to play with the `CLOCK_SPEED` and `CLOCK_DIV` settings. In a perfect world it should have worked for me with a 319.2 MHz clock and 8.9994 divider, but in practice my PVM locks on with a 321 MHz and 9.004 divider. It was down to trial and error, multiplying the divider by 0.99, 0.98, 0.97, etc., until my PVM tried to lock onto the signal as if it was SECAM (which lives at 4.40 and 4.25 MHz), at which point I moved at smaller increments until it worked (0.9911, 0.9912 0.9913, etc.). 15 | 16 | Due to the technical limitations of RAM and DAC timings, it's been an ongoing effort to work out video resolutions. The best I've been able to get so far is a "super high" resolution of 220x110 at roughly a 7 bit colour depth. On screen the image displays as 220x220 a stretched out to a roughly 1.5:1 aspect ratio. I have a lot more appreciation for the engineers of the 80s that did better than this with a chip running 1/200th the speed! The major challenge for higher resolutions at this point is RAM, and I think a more standard 320x200 resolution is otherwise possible. 17 | 18 | ![My original passive resistor DAC and Tiny2040](resources/chipanddac.jpg) 19 | 20 | ## The TV Channels ## 21 | 22 | The way the code is set up, it will run through a collection of numbered demos, a la TV channels. After briefly displaying a classic test screen to verify the PIO DAC is functioning, the following demos show: 23 | 24 | 1. A Pi Logo 25 | 2. Tuning into BBC2 Test Card F 26 | 3. A [lattice Boltzmann](https://en.wikipedia.org/wiki/Lattice_Boltzmann_methods) simulation of flow past a cylinder showing [vortex shedding](https://en.wikipedia.org/wiki/Vortex_shedding) 27 | 4. A 3D bouncing cube, after the classic 3D Flower Box screen saver 28 | 5. Many 3D cubes, bouncing off each other 29 | 6. A fire animation, which cycles through different colour schemes 30 | 7. A synthwave inspired landscape animation 31 | 8. Classic analogue TV static (snow) 32 | 9. A portion of Weebl's Badger Badger Badger flash animation, rendered as run length encoded video 33 | 10. Bad Apple overlayed with the classic [plasma effect](https://en.wikipedia.org/wiki/Plasma_effect) 34 | 35 | ## Resources ## 36 | 37 | A bit of research was needed to understand how composite video and colour composite video work, and how I could make it work on the Pico. I found these resources quite helpful. 38 | 39 | ### Composite Video ### 40 | 41 | Details on composite video specifications... 42 | 43 | * http://martin.hinner.info/vga/pal.html 44 | * http://www.batsocks.co.uk/readme/video_timing.htm 45 | * https://www.broadcaststore.com/pdf/model/793698/TT148%20-%204053.pdf 46 | * https://elinux.org/images/e/eb/Howtocolor.pdf 47 | * https://web.archive.org/web/20150306030906/http://www.pembers.freeserve.co.uk/World-TV-Standards/Colour-Standards.html 48 | * https://web.archive.org/web/20150305012205/http://www.pembers.freeserve.co.uk/World-TV-Standards/Line-Standards.html 49 | * https://web.archive.org/web/20080201032125/http://www.rickard.gunee.com/projects/video/sx/howto.php 50 | * https://sagargv.blogspot.com/2014/07/ntsc-demystified-color-demo-with.html 51 | * http://people.ece.cornell.edu/land/courses/ece5760/video/gvworks/GV%27s%20works%20%20NTSC%20demystified%20-%20Color%20Encoding%20-%20Part%202.htm 52 | 53 | ### PICO/RP2040/Tiny2040 ### 54 | 55 | How the Pico and RP2040 work... 56 | 57 | * https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf 58 | * https://datasheets.raspberrypi.com/pico/raspberry-pi-pico-c-sdk.pdf 59 | * https://raspberrypi.github.io/pico-sdk-doxygen/ 60 | 61 | #### DMA #### 62 | 63 | Use of the RP2040's DMAs to quickly move data around was critical. These were helpful for understanding how it can be used. 64 | 65 | * https://www.youtube.com/watch?v=OenPIsmKeDI 66 | * https://forums.raspberrypi.com/viewtopic.php?t=330348 67 | * https://forums.raspberrypi.com/viewtopic.php?t=311306 68 | * https://forums.raspberrypi.com/viewtopic.php?t=319709 69 | 70 | ### Similar projects ### 71 | 72 | I'm not the first to make a microcontroller generate composite video, not by a long shot. These other projects showed me what was possible and helped convince me I could do colour composite on the RP2040. 73 | 74 | #### PICO/RP2040 #### 75 | 76 | * https://areed.me/posts/2021-07-14_implementing_composite_video_output_using_the_pi_picos_pio/ 77 | * https://github.com/obstruse/pico-composite8 78 | * http://www.breakintoprogram.co.uk/projects/pico/composite-video-on-the-raspberry-pi-pico 79 | * https://github.com/breakintoprogram/pico-mposite 80 | * https://github.com/guruthree/mac-se-video-converter/blob/main/composite.h 81 | 82 | #### Other platforms #### 83 | 84 | * https://bitluni.net/esp32-color-pal 85 | * https://github.com/bitluni/DawnOfAV/tree/master/DawnOfAV 86 | * https://github.com/rossumur/esp_8_bit 87 | * http://javiervalcarce.eu/html/arduino-tv-signal-generator-en.html 88 | * https://web.archive.org/web/20080201140901/https://www.rickard.gunee.com/projects/video/sx/gamesys.php 89 | * https://web.archive.org/web/20070707125203/http://www.ondraszek.ds.polsl.gliwice.pl/~looser/avr/index.php 90 | * https://www.electronicsforu.com/electronics-projects/tv-pattern-generator 91 | -------------------------------------------------------------------------------- /demos/cliffs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022-2023 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | #include "discountadafruitgfx.h" 31 | #include "vectormath.h" 32 | 33 | class Cliffs { 34 | 35 | private: 36 | static const uint8_t ACROSS = 15; 37 | static const uint8_t DOWN = 23; 38 | 39 | float thecliff[DOWN][ACROSS]; // base heights, to be put into the transformed verticies 40 | Vector3 vt[DOWN][ACROSS]; // transformed verticies 41 | 42 | float zoffset = 0; // shuffle the grid towards the screen 43 | float dzstep = 1.2f; // rate at which it approaches the screen 44 | uint32_t sat = 0; // where we are in the undulating landscape 45 | 46 | float basecliff[ACROSS] = {80, 79, 80, 5, 1, 0, 1, -2, 1, 0, 1, 5, 80, 79, 80}; 47 | 48 | // generate an interesting across 49 | void generateAcross(uint8_t y) { 50 | if (y == 0 || y > DOWN-1) { 51 | return; 52 | } 53 | 54 | float d; 55 | for (uint8_t x = 0; x < ACROSS; x++) { 56 | d = basecliff[x] - thecliff[y-1][x]; 57 | thecliff[y][x] = thecliff[y-1][x] + randi(-5, 5) + d/4; 58 | } 59 | 60 | for (uint8_t x = 4; x < ACROSS-3; x++) { 61 | thecliff[y][x] + sinf(sat / 10.0f)*2; 62 | } 63 | } 64 | 65 | public: 66 | Cliffs() {} 67 | 68 | void init() { 69 | for (uint8_t x = 0; x < ACROSS; x++) { 70 | for (uint8_t y = 0; y < DOWN; y++) { 71 | // this bugs out when it's 0? maybe it gets optimised out? 72 | thecliff[y][x] = basecliff[x]; 73 | } 74 | } 75 | } 76 | 77 | void step() { 78 | zoffset += dzstep; 79 | if (zoffset >= 20) { 80 | zoffset -= 20; 81 | sat++; // 82 | 83 | // the closest points are now off screen 84 | // so shuffle down and update of the furthest away geometry 85 | for (uint8_t y = 0; y < DOWN-1; y++) { 86 | for (uint8_t x = 0; x < ACROSS; x++) { 87 | thecliff[y][x] = thecliff[y+1][x]; 88 | } 89 | } 90 | // for (uint8_t x = 0; x < ACROSS; x++) { 91 | // thecliff[DOWN-1][x] = 0; 92 | // } 93 | generateAcross(DOWN-1); 94 | 95 | // add a random hill at the second to last position 96 | // (2nd last so that it won't mess up the generation of the next across) 97 | if (rand() > RAND_MAX*0.92f) { 98 | thecliff[DOWN-2][randi(4, ACROSS-4)] += 35; 99 | } 100 | } 101 | 102 | } 103 | 104 | void render(int8_t *tbuf) { 105 | // refresh verticies coordinates array to be transformed again 106 | for (uint8_t x = 0; x < ACROSS; x++) { 107 | for (uint8_t y = 0; y < DOWN; y++) { 108 | vt[y][x] = {(float(x)-ACROSS/2)*60/HORIZONTAL_DOUBLING, -(thecliff[y][x]/2 - 40), -(float(y)-3)*20 + zoffset}; 109 | } 110 | } 111 | 112 | // the angle looking down into the valley 113 | Matrix3 rot = Matrix3::getRotationMatrix(-10.0f/180.0f*M_PI, 0, 0); 114 | 115 | for (uint8_t x = 0; x < ACROSS; x++) { 116 | for (uint8_t y = 0; y < DOWN; y++) {// apply perspective 117 | vt[y][x] = rot.preMultiply(vt[y][x]); 118 | vt[y][x] = vt[y][x].scale(40.0f / (-vt[y][x].z/2.0f + 40.0f)); 119 | // scale coordinates to screen coordinates 120 | vt[y][x].x = (vt[y][x].x/2) + XRESOLUTION/2.0f; 121 | vt[y][x].y += YRESOLUTION/2.0f; 122 | } 123 | } 124 | 125 | 126 | // fill background with colour 127 | int8_t y = 0, u = 0, v = 0; 128 | rgb2yuv(50, 5, 40, y, u, v); 129 | for (int32_t xcoord = 0; xcoord < XRESOLUTION; xcoord++) { 130 | for (int32_t ycoord = 0; ycoord < YRESOLUTION; ycoord++) { 131 | setPixelYUV(tbuf, xcoord, ycoord, y, u, v); 132 | } 133 | } 134 | 135 | // draw a sun 136 | uint8_t radius = 30; 137 | int32_t sunxoffset = XRESOLUTION/2; 138 | int32_t sunyoffset = YRESOLUTION/2 - 5; 139 | for (int32_t xcoord = -radius; xcoord <= radius; xcoord++) { 140 | for (int32_t ycoord = -radius; ycoord <= 0; ycoord++) { 141 | if (sqrt(pow(xcoord*HORIZONTAL_DOUBLING, 2) + pow(ycoord/0.8, 2)) <= radius) { 142 | // y is divided by 0.8 for aspect ratio correction to make the sun look circular 143 | uint16_t g = -ycoord*10; 144 | if (g > 127) g = 127; 145 | setPixelRGB(tbuf, xcoord + sunxoffset, ycoord + sunyoffset, 127, g, 0); 146 | } 147 | } 148 | } 149 | 150 | 151 | // loop through x and y and draw the lines 152 | for (uint8_t y = 0; y < DOWN-1; y++) { 153 | 154 | // color ranges from purple 120, 20, 100 to green 50, 100, 100 155 | float distanceaway = ((float(y)-3)*20 + zoffset) / ((DOWN + 2)*20); 156 | uint8_t r = 50 + distanceaway*70; 157 | uint8_t g = 100 - distanceaway*80; 158 | 159 | for (uint8_t x = 0; x < ACROSS; x++) { 160 | 161 | // don't show any lines that are entirely outside of screen space or start and end outside of screen space 162 | if ((vt[y][x].x < 0 && vt[y][x+1].x < 0) || (vt[y][x].x > XRESOLUTION && vt[y][x+1].x > XRESOLUTION) || 163 | (vt[y][x].y < 0 && vt[y][x+1].y < 0) || (vt[y][x].y > YRESOLUTION && vt[y][x+1].y > YRESOLUTION) || 164 | (vt[y][x].x < 0 && vt[y][x+1].x > YRESOLUTION) || (vt[y][x].y < 0 && vt[y][x+1].y > YRESOLUTION)) { 165 | } 166 | else if (x < ACROSS-1) { 167 | drawLineRGB(tbuf, vt[y][x].x, vt[y][x].y, vt[y][x+1].x, vt[y][x+1].y, r, g, 100); 168 | } 169 | 170 | if ((vt[y][x].x < 0 && vt[y+1][x].x < 0) || (vt[y][x].x > XRESOLUTION && vt[y+1][x].x > XRESOLUTION) || 171 | (vt[y][x].y < 0 && vt[y+1][x].y < 0) || (vt[y][x].y > YRESOLUTION && vt[y+1][x].y > YRESOLUTION) || 172 | (vt[y][x].x < 0 && vt[y+1][x].x > YRESOLUTION) || (vt[y][x].y < 0 && vt[y+1][x].y > YRESOLUTION)) { 173 | } 174 | else { 175 | drawLineRGB(tbuf, vt[y][x].x, vt[y][x].y, vt[y+1][x].x, vt[y+1][x].y, r, g, 100); 176 | } 177 | 178 | } 179 | } 180 | } 181 | }; 182 | -------------------------------------------------------------------------------- /demos/cube.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022-2023 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | #include "discountadafruitgfx.h" 31 | #include "vectormath.h" 32 | 33 | struct TriangleDepth { 34 | float depth; 35 | uint16_t index; 36 | 37 | bool operator < (const TriangleDepth& rhs) { 38 | return depth < rhs.depth; 39 | } 40 | }; 41 | 42 | class Object { 43 | 44 | public: 45 | std::vector vt; // verticies for rendering 46 | std::vector triangles; 47 | std::vector color; 48 | 49 | Object() {} 50 | }; 51 | 52 | class TriangleRenderer { 53 | 54 | private: 55 | std::vector vt; // verticies 56 | std::vector triangles; 57 | std::vector color; 58 | 59 | std::vector tridepths; 60 | 61 | // calculate the distance away of each triangle from the screen 62 | void calculateDistances() { 63 | tridepths.clear(); 64 | for (uint16_t i = 0; i < triangles.size(); i+=3) { 65 | TriangleDepth td; 66 | 67 | // this isn't really general, it assumes triangles are in pairs making up squares 68 | if (i % 6 == 0) { 69 | td.depth = (vt[triangles[i]].z + vt[triangles[i+1]].z + vt[triangles[i+2]].z + 70 | vt[triangles[i+3]].z + vt[triangles[i+4]].z + vt[triangles[i+5]].z) / 6; 71 | } 72 | else { 73 | td.depth = (vt[triangles[i]].z + vt[triangles[i+1]].z + vt[triangles[i+2]].z + 74 | vt[triangles[i-3]].z + vt[triangles[i-2]].z + vt[triangles[i-1]].z) / 6; 75 | } 76 | 77 | td.index = i; 78 | tridepths.push_back(td); 79 | } 80 | 81 | std::sort(tridepths.begin(), tridepths.end()); 82 | 83 | } 84 | 85 | public: 86 | TriangleRenderer() {} 87 | 88 | void reset() { 89 | vt.clear(); 90 | triangles.clear(); 91 | color.clear(); 92 | } 93 | 94 | void addObject(Object &obj) { 95 | uint16_t oldvtsize = vt.size(); 96 | vt.insert(vt.end(), obj.vt.begin(), obj.vt.end()); 97 | uint16_t oldtrianglessize = triangles.size(); 98 | triangles.insert(triangles.end(), obj.triangles.begin(), obj.triangles.end()); 99 | color.insert(color.end(), obj.color.begin(), obj.color.end()); 100 | 101 | // need to offset the triangles references when adding... 102 | for (uint16_t i = oldtrianglessize; i < triangles.size(); i++) { 103 | triangles[i] += oldvtsize; 104 | } 105 | } 106 | 107 | void render(int8_t *tbuf) { 108 | calculateDistances(); 109 | 110 | for (uint16_t i = 0; i < tridepths.size(); i++) { 111 | uint16_t idx = tridepths[i].index; 112 | 113 | fillTriangle(tbuf, 114 | vt[triangles[idx]].x, vt[triangles[idx]].y, 115 | vt[triangles[idx+1]].x, vt[triangles[idx+1]].y, 116 | vt[triangles[idx+2]].x, vt[triangles[idx+2]].y, 117 | color[idx], color[idx+1], color[idx+2]); 118 | } 119 | 120 | } 121 | 122 | /* 123 | 124 | so to render: 125 | reset() 126 | addObject(object); 127 | addObject(object); 128 | render(); 129 | 130 | */ 131 | }; // end TriangleRenderer 132 | 133 | class Cube : public Object { 134 | std::vector v; // base verticies before animation 135 | Vector3 centre; 136 | float size; 137 | 138 | float xangle = 0, yangle = 0, zangle = 0; 139 | float dxangle = 0.05f, dyangle = 0.02f, dzangle = 0.001f; 140 | Vector3 d = {0.10f, 0.50f, -0.25f}; // dx, dy, dz 141 | 142 | public: 143 | Cube(float _size) { 144 | size = _size; 145 | v.reserve(8); 146 | v.push_back({-size/2, -size/2, size/2}); // front bottom left 147 | v.push_back({ size/2, -size/2, size/2}); // front bottom right 148 | v.push_back({ size/2, size/2, size/2}); // front top right 149 | v.push_back({-size/2, size/2, size/2}); // front top left 150 | v.push_back({-size/2, -size/2, -size/2}); // back bottom left 151 | v.push_back({ size/2, -size/2, -size/2}); // back bottom right 152 | v.push_back({ size/2, size/2, -size/2}); // back top right 153 | v.push_back({-size/2, size/2, -size/2}); // back top left 154 | 155 | triangles.reserve(12*3); // 6 faces * 2 triangles per face * 3 coordinates 156 | triangles = {0, 1, 2, 157 | 2, 3, 0, 158 | 4, 5, 6, 159 | 6, 7, 4, 160 | 0, 3, 4, 161 | 4, 7, 3, 162 | 1, 2, 5, 163 | 5, 6, 2, 164 | 2, 3, 6, 165 | 6, 3, 7, 166 | 1, 0, 5, 167 | 5, 0, 4 168 | }; 169 | 170 | color.reserve(12*3); // 6 faces * 2 triangles per face * RGB (3) 171 | color = {100, 0, 0, 172 | 100, 0, 0, 173 | 0, 0, 100, 174 | 0, 0, 100, 175 | 0, 100, 0, 176 | 0, 100, 0, 177 | 100, 100, 0, 178 | 100, 100, 0, 179 | 0, 100, 100, 180 | 0, 100, 100, 181 | 100, 0, 100, 182 | 100, 0, 100 183 | }; 184 | 185 | centre = {0, 0, 0}; 186 | } 187 | 188 | // animate 189 | // this also mixes in bits of rendering, because the cube bounces off the screen 190 | void step() { 191 | // rotate 192 | xangle += dxangle; 193 | yangle += dyangle; 194 | zangle += dxangle; 195 | if (xangle >= 360) xangle -= 360; 196 | if (yangle >= 360) yangle -= 360; 197 | if (zangle >= 360) zangle -= 360; 198 | 199 | // move 200 | centre = centre.add(d); 201 | 202 | // bounce off the front and back of the screen 203 | if (centre.z < -100 || centre.z >= 0) { 204 | d.z = -d.z; 205 | } 206 | 207 | Matrix3 rot = Matrix3::getRotationMatrix(xangle, yangle, zangle); 208 | 209 | vt.clear(); 210 | vt.reserve(v.size()); 211 | for (uint16_t i = 0; i < v.size(); i++) { 212 | // apply effects of movement 213 | vt.push_back(rot.preMultiply(v[i])); 214 | vt[i] = vt[i].add(centre); 215 | // apply perspective 216 | vt[i] = vt[i].scale(40.0f / (-vt[i].z/2.0 + 40.0f)); 217 | // scale coordinates to screen coordinates*/ 218 | vt[i].x = (vt[i].x/HORIZONTAL_DOUBLING) + XRESOLUTION / 2; 219 | vt[i].y += YRESOLUTION / 2; 220 | } 221 | 222 | // bounce off the sides of the screen 223 | for (uint16_t i = 0; i < v.size(); i++) { 224 | if (vt[i].x > XRESOLUTION-4) { 225 | d.x = -abs(d.x); 226 | d.z = -abs(d.z); // don't continue to grow on edge of screen 227 | break; 228 | } 229 | else if (vt[i].x < 2) { 230 | d.x = abs(d.x); 231 | d.z = -abs(d.z); 232 | break; 233 | } 234 | } 235 | 236 | // bounce off the top and bottom of the screen 237 | for (uint16_t i = 0; i < v.size(); i++) { 238 | if (vt[i].y > YRESOLUTION-4) { 239 | d.y = -abs(d.y); 240 | d.z = -abs(d.z); 241 | break; 242 | } 243 | else if (vt[i].y < 2) { 244 | d.y = abs(d.y); 245 | d.z = -abs(d.z); 246 | break; 247 | } 248 | } 249 | } 250 | 251 | void randomise() { 252 | centre = { float(randi(0, 3*XRESOLUTION/4) - 3*XRESOLUTION/8), 253 | float(randi(0, 3*YRESOLUTION/4) - 3*YRESOLUTION/8), 254 | -float(randi(10, 80)) }; 255 | dxangle = rand()*0.04f/RAND_MAX; // how fast they rotate 256 | dyangle = rand()*0.04f/RAND_MAX; 257 | dzangle = rand()*0.04f/RAND_MAX; 258 | d = {rand()*2.f/RAND_MAX-1.f, rand()*0.8f/RAND_MAX-0.4f, rand()*0.25f/RAND_MAX-0.125f}; // dx, dy, dz (speed) 259 | } 260 | 261 | void collide(Cube &other) { 262 | Vector3 t = centre.subtract(other.centre); 263 | float dist = t.dotProduct(t); 264 | if (dist < pow(size/2 + other.size/2, 2)) { 265 | // the distance is less than the difference in the sizes, collide! 266 | d = d.scale(-1); 267 | other.d = other.d.scale(-1); 268 | } 269 | } 270 | }; // end Cube 271 | -------------------------------------------------------------------------------- /demos/lbm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022-2023 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #include "jet.h" 28 | 29 | class LBM { 30 | 31 | // reference: https://hackaday.io/project/25446-lattice-boltzmann-fluid-flow-in-matlab 32 | // thanks to @ZodiusInfuser for some tweaks & discussion 33 | 34 | public: 35 | 36 | static const uint8_t NX = 44; 37 | static const uint8_t NY = 24; 38 | static const uint8_t CYL_DEFY = (NY-2)/2-1; 39 | float maxVal; 40 | uint8_t BOUND[NX][NY]; 41 | 42 | private: 43 | static const uint8_t MAX_BOUND = 255; 44 | 45 | // omega any higher and it dies? 46 | // 34x34, 1.85, 100 47 | static constexpr float OMEGA = 1.82f, DENSITY = 0.6f, TARGET_USUM = 155.0f; 48 | 49 | static constexpr float W1 = 1.0f / 9.0f; 50 | static constexpr float W2 = 1.0f / 36.0f; 51 | 52 | const uint8_t REFLECTED[8] = {4, 5, 6, 7, 0, 1, 2, 3}; 53 | 54 | float F[NX][NY][9]; 55 | // uint8_t BOUND[NX][NY]; 56 | 57 | uint8_t ON[MAX_BOUND][2]; 58 | uint8_t numBound; 59 | uint32_t ts; // time step 60 | float temp1, temp2, temp3; 61 | float BOUNCEDBACK[MAX_BOUND][8]; 62 | float ux[NX][NY], speed[NX][NY]; 63 | float rho, uy, uxx, uyy, uxy, uu; 64 | float FEQ[9]; 65 | float Zts, Zdelta; 66 | uint8_t ii,jj,kk; 67 | 68 | int doBound(void) { 69 | int nmBound = 0; 70 | for (ii = 0; ii < NX; ii++) { 71 | for (jj = 0; jj < NY; jj++) { 72 | if (BOUND[ii][jj] == 1) { 73 | nmBound++; 74 | } 75 | } 76 | } 77 | 78 | kk = 0; 79 | for (ii = 0; ii < NX; ii++) { 80 | for (jj = 0; jj < NY; jj++) { 81 | if (BOUND[ii][jj] == 1) { 82 | ON[kk][0] = ii; 83 | ON[kk][1] = jj; 84 | //printf("ON[%i] = [%i][%i] (%i?)\n", kk, ii, jj, ii+jj*NX); 85 | kk++; 86 | // uh oh if kk > MAX_BOUND 87 | } 88 | } 89 | } 90 | return nmBound; 91 | } 92 | 93 | 94 | public: 95 | LBM () {} 96 | 97 | void init() { 98 | 99 | for (ii = 0; ii < NX; ii++) { 100 | for (jj = 0; jj < NY; jj++) { 101 | for (kk = 0; kk < 9; kk++) { 102 | F[ii][jj][kk] = DENSITY / 9; 103 | } 104 | } 105 | } 106 | 107 | memset(BOUND, 0, sizeof(uint8_t) * NX * NY); 108 | // walls 109 | for (ii = 0; ii < NX; ii++) { 110 | BOUND[ii][0] = 1; 111 | } 112 | for (ii = 0; ii < NX; ii++) { 113 | BOUND[ii][NY-1] = 1; 114 | } 115 | 116 | // flow forcing 117 | for (ii = 0; ii < NX; ii++) { 118 | for (jj = 0; jj < NY; jj++) { 119 | F[ii][jj][0] = 0.0951; 120 | F[ii][jj][1] = 0.0238; 121 | F[ii][jj][2] = 0.0739; 122 | F[ii][jj][3] = 0.0143; 123 | F[ii][jj][4] = 0.0574; 124 | F[ii][jj][5] = 0.0144; 125 | F[ii][jj][6] = 0.0739; 126 | F[ii][jj][7] = 0.0238; 127 | F[ii][jj][8] = 0.2955; 128 | } 129 | } 130 | 131 | numBound = doBound(); 132 | } 133 | 134 | // place a cylinder in the flow 135 | void cylinder(uint8_t yoffset = CYL_DEFY, uint8_t xoffset = 8) { 136 | for (jj = yoffset; jj < yoffset+4; jj++) { 137 | BOUND[xoffset+1][jj] = 1; 138 | BOUND[xoffset+2][jj] = 1; 139 | } 140 | 141 | for (jj = yoffset+1; jj < yoffset+3; jj++) { 142 | BOUND[xoffset][jj] = 1; 143 | BOUND[xoffset+3][jj] = 1; 144 | } 145 | BOUND[xoffset+3][yoffset] = 1; 146 | numBound = doBound(); 147 | } 148 | 149 | 150 | // add randomness by changing the boundary conditions sometimes 151 | void randomness () { 152 | if ((float)rand()/(float)(RAND_MAX) > 0.995f) { 153 | //printf("swapping boundary\n"); 154 | if (BOUND[11][CYL_DEFY] == 1) { 155 | BOUND[11][CYL_DEFY] = 0; 156 | } 157 | else { 158 | BOUND[11][CYL_DEFY] = 1; 159 | } 160 | numBound = doBound(); 161 | } 162 | } 163 | 164 | void timestep (bool storespeed = false) { 165 | if (storespeed) { 166 | maxVal = 0; 167 | } 168 | 169 | // [2:NX 1] 170 | for (jj = 0; jj < NY; jj++) { 171 | temp1 = F[0][jj][3]; 172 | temp2 = F[0][jj][4]; 173 | temp3 = F[0][jj][5]; 174 | for (ii = 0; ii < NX-1; ii++) { 175 | F[ii][jj][3] = F[ii+1][jj][3]; 176 | F[ii][jj][4] = F[ii+1][jj][4]; 177 | F[ii][jj][5] = F[ii+1][jj][5]; 178 | } 179 | F[NX-1][jj][3] = temp1; 180 | F[NX-1][jj][4] = temp2; 181 | F[NX-1][jj][5] = temp3; 182 | } 183 | //sleep_us(100); 184 | 185 | // [NY 1:NY-1] 186 | for (ii = 0; ii < NX; ii++) { 187 | temp1 = F[ii][NY-1][3]; 188 | temp2 = F[ii][NY-1][2]; 189 | temp3 = F[ii][NY-1][1]; 190 | for (jj = NY-1; jj > 0; jj--) { 191 | F[ii][jj][3] = F[ii][jj-1][3]; 192 | F[ii][jj][2] = F[ii][jj-1][2]; 193 | F[ii][jj][1] = F[ii][jj-1][1]; 194 | } 195 | F[ii][0][3] = temp1; 196 | F[ii][0][2] = temp2; 197 | F[ii][0][1] = temp3; 198 | } 199 | //sleep_us(100); 200 | 201 | // [NX 1:NX-1] 202 | for (jj = 0; jj < NY; jj++) { 203 | temp1 = F[NX-1][jj][1]; 204 | temp2 = F[NX-1][jj][0]; 205 | temp3 = F[NX-1][jj][7]; 206 | for (ii = NX-1; ii > 0; ii--) { 207 | F[ii][jj][1] = F[ii-1][jj][1]; 208 | F[ii][jj][0] = F[ii-1][jj][0]; 209 | F[ii][jj][7] = F[ii-1][jj][7]; 210 | } 211 | F[0][jj][1] = temp1; 212 | F[0][jj][0] = temp2; 213 | F[0][jj][7] = temp3; 214 | } 215 | //sleep_us(100); 216 | 217 | // [2:NY 1] 218 | for (ii = 0; ii < NX; ii++) { 219 | temp1 = F[ii][0][5]; 220 | temp2 = F[ii][0][6]; 221 | temp3 = F[ii][0][7]; 222 | for (jj = 0; jj < NY-1; jj++) { 223 | F[ii][jj][5] = F[ii][jj+1][5]; 224 | F[ii][jj][6] = F[ii][jj+1][6]; 225 | F[ii][jj][7] = F[ii][jj+1][7]; 226 | } 227 | F[ii][NY-1][5] = temp1; 228 | F[ii][NY-1][6] = temp2; 229 | F[ii][NY-1][7] = temp3; 230 | } 231 | //sleep_us(100); 232 | 233 | for (kk = 0; kk < 8; kk++) { 234 | for (ii = 0; ii < numBound; ii++) { 235 | BOUNCEDBACK[ii][kk] = F[ON[ii][0]][ON[ii][1]][kk]; 236 | } 237 | } 238 | //sleep_us(100); 239 | 240 | memset(ux, 0, sizeof(float)*NX*NY); 241 | for (ii = 0; ii < NX; ii++) { 242 | for (jj = 0; jj < NY; jj++) { 243 | ux[ii][jj] += F[ii][jj][0]; 244 | ux[ii][jj] += F[ii][jj][1]; 245 | ux[ii][jj] += F[ii][jj][7]; 246 | ux[ii][jj] -= F[ii][jj][3]; 247 | ux[ii][jj] -= F[ii][jj][4]; 248 | ux[ii][jj] -= F[ii][jj][5]; 249 | } 250 | } 251 | //sleep_us(100); 252 | 253 | Zts = 0; 254 | for (ii = 0; ii < NX; ii++) { 255 | for (jj = 0; jj < NY; jj++) { 256 | Zts += ux[ii][jj]; 257 | } 258 | } 259 | //sleep_us(100); 260 | 261 | Zdelta = Zts - TARGET_USUM; 262 | for (ii = 0; ii < NX; ii++) { 263 | for (jj = 0; jj < NY; jj++) { 264 | ux[ii][jj] -= Zdelta / (NX*NY); 265 | } 266 | } 267 | //sleep_us(100); 268 | 269 | for (ii = 0; ii < numBound; ii++) { 270 | ux[ON[ii][0]][ON[ii][1]] = 0; 271 | } 272 | //sleep_us(100); 273 | 274 | for (ii = 0; ii < NX; ii++) { 275 | for (jj = 0; jj < NY; jj++) { 276 | rho = 0; 277 | uy = 0; 278 | if (ux[ii][jj] != 0) { 279 | for (kk = 0; kk < 9; kk++) { 280 | rho += F[ii][jj][kk]; 281 | } 282 | 283 | uy += F[ii][jj][1]; 284 | uy += F[ii][jj][2]; 285 | uy += F[ii][jj][3]; 286 | uy -= F[ii][jj][5]; 287 | uy -= F[ii][jj][6]; 288 | uy -= F[ii][jj][7]; 289 | } 290 | 291 | uxx = ux[ii][jj] * ux[ii][jj]; 292 | uyy = uy * uy; 293 | uxy = 2 * ux[ii][jj] * uy; 294 | uu = uxx + uyy; 295 | 296 | if (storespeed) { 297 | speed[ii][jj] = sqrtf(uxx + uyy); 298 | if (speed[ii][jj] > maxVal) { 299 | maxVal = speed[ii][jj]; 300 | } 301 | } 302 | 303 | FEQ[0] = W1 * (rho + 3*ux[ii][jj] + 4.5*uxx - 1.5*uu); 304 | FEQ[2] = W1 * (rho + 3*uy + 4.5*uyy - 1.5*uu); 305 | FEQ[4] = W1 * (rho - 3*ux[ii][jj] + 4.5*uxx - 1.5*uu); 306 | FEQ[6] = W1 * (rho - 3*uy + 4.5*uyy - 1.5*uu); 307 | 308 | FEQ[1] = W2 * (rho + 3*(ux[ii][jj] + uy) + 4.5*(uu + uxy) - 1.5*uu); 309 | FEQ[3] = W2 * (rho - 3*(ux[ii][jj] - uy) + 4.5*(uu - uxy) - 1.5*uu); 310 | FEQ[5] = W2 * (rho - 3*(ux[ii][jj] + uy) + 4.5*(uu + uxy) - 1.5*uu); 311 | FEQ[7] = W2 * (rho + 3*(ux[ii][jj] - uy) + 4.5*(uu - uxy) - 1.5*uu); 312 | 313 | FEQ[8] = rho; 314 | for (kk = 0; kk < 8; kk++) { 315 | FEQ[8] -= FEQ[kk]; 316 | } 317 | 318 | for (kk = 0; kk < 9; kk++) { 319 | F[ii][jj][kk] = OMEGA * FEQ[kk] + (1 - OMEGA)*F[ii][jj][kk]; 320 | } 321 | } 322 | //sleep_us(100); 323 | } 324 | 325 | for (kk = 0; kk < 8; kk++) { 326 | for (ii = 0; ii < numBound; ii++) { 327 | F[ON[ii][0]][ON[ii][1]][REFLECTED[kk]] = BOUNCEDBACK[ii][kk]; 328 | } 329 | } 330 | //sleep_us(100); 331 | ts++; 332 | } 333 | 334 | uint32_t getNumberOfTimeSteps() { 335 | return ts; 336 | } 337 | 338 | float getSpeed(uint8_t x, uint8_t y) { 339 | return speed[x][y]; 340 | } 341 | 342 | }; // end class 343 | 344 | void drawlbm(LBM &lbm, int8_t *tbuf) { 345 | 346 | uint16_t xat, speed; 347 | uint16_t yat = YRESOLUTION/2 - ((lbm.NY-2)/2)*4 + 5; // manually offset :( 348 | for (uint8_t y = 2; y < lbm.NY-2; y++) { // don't draw boundaries 349 | xat = XRESOLUTION/2 - (lbm.NX/2)*4/HORIZONTAL_DOUBLING-1; 350 | for (uint8_t x = 0; x < lbm.NX; x++) { 351 | if (!lbm.BOUND[x][y]) { 352 | speed = lbm.getSpeed(x, y) * 63.0 / lbm.maxVal; 353 | speed > 63 ? speed = 63 : 1; 354 | 355 | // simple nearest neighbour interpolation? 356 | setPixelRGBtwoX(tbuf, xat, yat, jet[speed][0], jet[speed][1], jet[speed][2]); 357 | setPixelRGBtwoX(tbuf, xat, yat+2, jet[speed][0], jet[speed][1], jet[speed][2]); 358 | #if HORIZONTAL_DOUBLING == 1 359 | setPixelRGBtwoX(tbuf, xat+2, yat, jet[speed][0], jet[speed][1], jet[speed][2]); 360 | setPixelRGBtwoX(tbuf, xat+2, yat+2, jet[speed][0], jet[speed][1], jet[speed][2]); 361 | #endif 362 | } 363 | #if HORIZONTAL_DOUBLING == 2 364 | xat += 2; 365 | #else 366 | xat += 4; 367 | #endif 368 | } 369 | yat += 4; 370 | } 371 | 372 | } 373 | -------------------------------------------------------------------------------- /demos/flames.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022-2023 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | class Flames { 28 | 29 | public: 30 | 31 | enum COLOUR_SCHEME { RED, BLUE, PURPLE }; 32 | 33 | private: 34 | 35 | static const uint8_t ACROSS = 26; 36 | static const uint8_t DOWN = 20; 37 | 38 | // the output frame is the mean of these frames 39 | static const uint8_t HISTORY = 8; 40 | 41 | uint8_t allim[HISTORY][DOWN][ACROSS]; 42 | uint8_t imat = 0; 43 | 44 | static const uint8_t NUM_COLOURS = HISTORY*3+1; 45 | // yuv colour 46 | int8_t colourmap[NUM_COLOURS][3]; 47 | COLOUR_SCHEME firecmap; 48 | 49 | // set the points in a row to val between x1 and x2 inclusive 50 | void fillim(uint8_t (&im)[ACROSS], uint8_t x1, uint8_t x2, uint8_t val) { 51 | for (uint8_t ii = x1; ii <= x2; ii++) { 52 | im[ii] = val; 53 | } 54 | } 55 | 56 | // index of the first location with a value in a row 57 | int8_t findfirstx(uint8_t (&im)[ACROSS], uint8_t val) { 58 | int8_t x = 0; 59 | while (im[x] != val) { 60 | x++; 61 | if (x == ACROSS) { 62 | return -1; 63 | } 64 | } 65 | return x; 66 | } 67 | 68 | // index of the last location with a value in a row 69 | int8_t findlastx(uint8_t (&im)[ACROSS], uint8_t val) { 70 | int8_t x = ACROSS-1; 71 | while (im[x] != val) { 72 | x--; 73 | if (x == -1) { 74 | x = -1; 75 | break; 76 | } 77 | } 78 | return x; 79 | } 80 | 81 | void generateFlames(uint8_t (&im)[DOWN][ACROSS]) { 82 | memset(im, 0, ACROSS*DOWN*sizeof(uint8_t)); 83 | uint8_t y = 0, p; 84 | 85 | // the fire starts from the bottom 86 | uint8_t oldx = 2 + randi(4, 6); 87 | uint8_t x = randi(2, 3); 88 | fillim(im[y], oldx, oldx+x, 1); 89 | 90 | oldx = oldx + x + 1; 91 | x = randi(0, 2); 92 | fillim(im[y], oldx, oldx+x, 2); 93 | 94 | oldx = oldx + x + 1; 95 | x = randi(2, 4); 96 | fillim(im[y], oldx, oldx+x, 3); 97 | 98 | oldx = oldx + x + 1; 99 | x = randi(1, 2); 100 | fillim(im[y], oldx, oldx+x, 2); 101 | 102 | oldx = oldx + x + 1; 103 | x = randi(2, 3); 104 | fillim(im[y], oldx, oldx+x, 1); 105 | 106 | // flame outlines 107 | uint64_t probs[3]; 108 | for (uint8_t zz = 1; zz <= 3; zz++) { 109 | for (y = 1; y < DOWN; y++) { 110 | if (zz == 1) { 111 | if (y <= 5) { 112 | probs[0] = 0.30f*RAND_MAX; 113 | probs[1] = 0.70f*RAND_MAX; 114 | probs[2] = 1.00f*RAND_MAX; 115 | } 116 | else if (y < 12) { 117 | probs[0] = 0.10f*RAND_MAX; 118 | probs[1] = 0.30f*RAND_MAX; 119 | probs[2] = 1.00f*RAND_MAX; 120 | } 121 | else { 122 | probs[0] = 0.00f*RAND_MAX; 123 | probs[1] = 0.02f*RAND_MAX; 124 | probs[2] = 1.00f*RAND_MAX; 125 | } 126 | } 127 | else if (zz == 2) { 128 | if (y <= 5) { 129 | probs[0] = 0.50f*RAND_MAX; 130 | probs[1] = 0.60f*RAND_MAX; 131 | probs[2] = 1.00f*RAND_MAX; 132 | } 133 | else if (y < 12) { 134 | probs[0] = 0.00f*RAND_MAX; 135 | probs[1] = 0.30f*RAND_MAX; 136 | probs[2] = 1.00f*RAND_MAX; 137 | } 138 | else { 139 | probs[0] = 0.00f*RAND_MAX; 140 | probs[1] = 0.02f*RAND_MAX; 141 | probs[2] = 1.00f*RAND_MAX; 142 | } 143 | } 144 | else if (zz == 3) { 145 | if (y <= 5) { 146 | probs[0] = 0.530f*RAND_MAX; 147 | probs[1] = 0.40f*RAND_MAX; 148 | probs[2] = 1.00f*RAND_MAX; 149 | } 150 | else if (y < 12) { 151 | probs[0] = 0.00f*RAND_MAX; 152 | probs[1] = 0.20f*RAND_MAX; 153 | probs[2] = 1.00f*RAND_MAX; 154 | } 155 | else { 156 | probs[0] = 0.00f*RAND_MAX; 157 | probs[1] = 0.02f*RAND_MAX; 158 | probs[2] = 1.00f*RAND_MAX; 159 | } 160 | } 161 | 162 | x = findfirstx(im[y-1], zz); 163 | 164 | uint64_t r = rand(); 165 | if (r < probs[0] && im[y][x-1] == 0 && im[y][x] == 0) // expand 166 | im[y][x-1] = zz; 167 | else if (r < probs[1] && im[y][x] == 0) // unchange 168 | im[y][x] = zz; 169 | else {//if (r < probs[2] // contract 170 | r = rand(); 171 | if (r < 1/3*RAND_MAX || y < 8) 172 | p = 1; 173 | else 174 | p = 2; 175 | 176 | if (im[y][x+p] == 0) 177 | im[y][x+p] = zz; 178 | else 179 | break; 180 | 181 | } 182 | 183 | x = findlastx(im[y-1], zz); 184 | if (im[y][x] == zz || im[y][x-1] == zz) // shape is closed 185 | break; 186 | r = rand(); 187 | if (r < probs[0] && im[y][x+1] == 0 && im[y][x] == 0) // expand 188 | im[y][x+1] = zz; 189 | else if (r < probs[1] && im[y][x] == 0) // unchange 190 | im[y][x] = zz; 191 | else {//if (r < probs[2] // contract 192 | r = rand(); 193 | if (r < 1/3*RAND_MAX || y < 8) 194 | p = 1; 195 | else 196 | p = 2; 197 | 198 | if (im[y][x-p] == 0) 199 | im[y][x-p] = zz; 200 | else 201 | break; 202 | } 203 | } // yy 204 | } // flame outlines 205 | 206 | // fill in flame insides 207 | for (y = 1; y < DOWN; y++) { 208 | int8_t xgoal = findlastx(im[y], 1); 209 | if (xgoal > 0) { 210 | for (x = 1; x <= xgoal; x++) { 211 | if (im[y][x] > 0 && im[y][x+1] == 0) 212 | im[y][x+1] = im[y][x]; 213 | } 214 | } 215 | } 216 | } 217 | 218 | public: 219 | 220 | Flames() {} 221 | 222 | void init() { 223 | 224 | memset(allim, 0, sizeof(allim)); 225 | 226 | for (uint8_t i = 0; i < HISTORY; i++) { 227 | generateFlames(allim[i]); 228 | } 229 | 230 | setColormap(); 231 | } 232 | 233 | void step() { 234 | imat++; 235 | if (imat == HISTORY) { 236 | imat = 0; 237 | } 238 | generateFlames(allim[imat]); 239 | } 240 | 241 | void draw(int8_t *tbuf) { 242 | uint16_t xat, yat = YRESOLUTION-10; // it's 250 lines, but 125 in the matrix... 243 | for (uint8_t y = 0; y < DOWN; y++) { 244 | xat = XRESOLUTION/2 - (ACROSS/2)*4/HORIZONTAL_DOUBLING; 245 | for (uint8_t x = 0; x < ACROSS; x++) { 246 | uint8_t val = 0; 247 | // sum up the history of fire 248 | for (uint8_t i = 0; i < HISTORY; i++) { 249 | val += allim[i][y][x]; 250 | } 251 | 252 | // simple nearest neighbour interpolation? 253 | setPixelYUVtwoX(tbuf, xat, yat-1, colourmap[val][0], colourmap[val][1], colourmap[val][2]); 254 | setPixelYUVtwoX(tbuf, xat, yat-3, colourmap[val][0], colourmap[val][1], colourmap[val][2]); 255 | 256 | #if HORIZONTAL_DOUBLING == 2 257 | xat += 2; 258 | #else 259 | setPixelYUVtwoX(tbuf, xat+2, yat-1, colourmap[val][0], colourmap[val][1], colourmap[val][2]); 260 | setPixelYUVtwoX(tbuf, xat+2, yat-3, colourmap[val][0], colourmap[val][1], colourmap[val][2]); 261 | 262 | xat += 4; 263 | #endif 264 | } 265 | yat -= 4; 266 | } 267 | } 268 | 269 | void setColormap(COLOUR_SCHEME scheme = RED) { 270 | firecmap = scheme; 271 | 272 | // calculate the colourmap first in RGB, then convert to YUV for storage and use 273 | memset(colourmap, 0, sizeof(colourmap)); 274 | 275 | switch (scheme) { 276 | case BLUE: 277 | // green inside, blue outside 278 | for (uint8_t i = 0; i < NUM_COLOURS; i++) { 279 | colourmap[i][2] = (120*(NUM_COLOURS-i))/NUM_COLOURS; 280 | } 281 | for (uint8_t i = 0; i < NUM_COLOURS; i++) { 282 | colourmap[i][1] = (120*i)/NUM_COLOURS; 283 | } 284 | for (uint8_t i = 1; i < NUM_COLOURS/3; i++) { 285 | colourmap[i][2] = (120*i)/(NUM_COLOURS/3); 286 | } 287 | break; 288 | 289 | case PURPLE: 290 | // white inside, purple outside 291 | for (uint8_t i = 0; i < NUM_COLOURS; i++) { 292 | colourmap[i][0] = 120; 293 | colourmap[i][2] = 120; 294 | } 295 | for (uint8_t i = 0; i < NUM_COLOURS; i++) { 296 | colourmap[i][1] = (120*i)/NUM_COLOURS; 297 | } 298 | for (uint8_t i = 1; i < NUM_COLOURS/2; i++) { 299 | colourmap[i][0] = (120*i)/(NUM_COLOURS/2); 300 | colourmap[i][2] = (120*i)/(NUM_COLOURS/2); 301 | } 302 | break; 303 | 304 | default: 305 | case RED: 306 | // yellow inside, red outside 307 | for (uint8_t i = 0; i < NUM_COLOURS; i++) { 308 | colourmap[i][0] = 120; 309 | } 310 | for (uint8_t i = 0; i < NUM_COLOURS; i++) { 311 | colourmap[i][1] = (120*i)/NUM_COLOURS; 312 | } 313 | for (uint8_t i = 1; i < NUM_COLOURS/3; i++) { 314 | colourmap[i][0] = (120*i)/(NUM_COLOURS/3); 315 | } 316 | break; 317 | } 318 | colourmap[0][0] = 0; 319 | colourmap[0][1] = 0; 320 | colourmap[0][2] = 0; 321 | 322 | int8_t y, u, v; 323 | for (uint8_t i = 0; i < NUM_COLOURS; i++) { 324 | rgb2yuv(colourmap[i][0], colourmap[i][1], colourmap[i][2], y, u, v); 325 | colourmap[i][0] = y; 326 | colourmap[i][1] = u; 327 | colourmap[i][2] = v; 328 | } 329 | } 330 | 331 | COLOUR_SCHEME getColormap() { 332 | return firecmap; 333 | } 334 | 335 | }; 336 | -------------------------------------------------------------------------------- /pico-composite-PAL-colour.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022-2023 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "pico/stdlib.h" 34 | #include "pico/multicore.h" 35 | #include "hardware/pio.h" 36 | #include "hardware/dma.h" 37 | #include "hardware/irq.h" 38 | #include "hardware/clocks.h" 39 | #include "hardware/regs/rosc.h" 40 | #include "hardware/vreg.h" 41 | #include "dac.pio.h" 42 | 43 | // find a CLOCKS_SPEED close to a multiple of the PAL carrier frequency 44 | // using pico-sdk/src/rp2_common/hardware_clocks/scripts/vcocalc.py 45 | // then tweak it and CLOCK_DIV until a colour picture comes through 46 | 47 | #define CLOCK_SPEED 321e6 48 | // the divider adjusted by 1.0040, 41, 42, and 43 all works 49 | //#define CLOCK_DIV (9*1.0041) 50 | #define CLOCK_DIV (12*1.004) 51 | 52 | 53 | #define DAC_FREQ float(CLOCK_SPEED / CLOCK_DIV) // this should be 54 | 55 | //#define VREG_VSEL VREG_VOLTAGE_1_15 56 | #define VREG_VSEL VREG_VOLTAGE_1_20 57 | //#define VREG_VSEL VREG_VOLTAGE_1_25 58 | //#define VREG_VSEL VREG_VOLTAGE_1_30 59 | 60 | // fast copies of uint8_t arrays, array length needs to be multiple of 4 61 | int dma_chan32; 62 | inline void dmacpy(uint8_t *dst, uint8_t *src, uint16_t size) { 63 | dma_channel_set_trans_count(dma_chan32, size / 4, false); 64 | dma_channel_set_read_addr(dma_chan32, src, false); 65 | dma_channel_set_write_addr(dma_chan32, dst, true); 66 | dma_channel_wait_for_finish_blocking(dma_chan32); 67 | // if the write addr isn't NULL, it causes some sort of memory contention slowing down the rest of the loop 68 | dma_channel_set_write_addr(dma_chan32, NULL, false); 69 | } 70 | 71 | #include "colourpal.h" 72 | 73 | // one byte y, one byte u, one byte v, repeating for 125x64, line by line 74 | #define BUF_SIZE (XRESOLUTION*YRESOLUTION*3) 75 | int8_t buf0[BUF_SIZE]; 76 | int8_t buf1[BUF_SIZE]; 77 | 78 | #include "random.h" 79 | #include "time.h" 80 | #include "font.h" 81 | #include "testcardf.h" 82 | #include "raspberrypi.h" 83 | #include "lbm.h" 84 | #include "cube.h" 85 | #include "flames.h" 86 | #include "cliffs.h" 87 | 88 | #if HORIZONTAL_DOUBLING == 1 89 | #include "badger.h" 90 | #include "mushroom1.h" 91 | #include "mushroom2.h" 92 | 93 | #include "badapple.h" 94 | #include "sine.h" 95 | #include "hsv.h" 96 | #endif 97 | 98 | ColourPal cp; 99 | #define NUM_DEMOS 10 100 | #define DEMO_DURATION 5 // seconds to show each demo for 101 | 102 | // largeish memory requirements (big arrays), so global 103 | LBM lbm; 104 | Flames fire; 105 | Cliffs cliffs; 106 | #define NUM_CUBES 8 107 | Cube* cubes[NUM_CUBES] = {new Cube(20), new Cube(20), new Cube(20), new Cube(20), 108 | new Cube(20), new Cube(20), new Cube(20), new Cube(20)}; 109 | TriangleRenderer tr; 110 | 111 | 112 | void core1_entry(); 113 | 114 | int main() { 115 | vreg_set_voltage(VREG_VSEL); 116 | set_sys_clock_khz(CLOCK_SPEED/1000.0f, true); 117 | 118 | gpio_init(18); 119 | gpio_init(19); 120 | gpio_init(20); 121 | gpio_set_dir(18, GPIO_OUT); 122 | gpio_set_dir(19, GPIO_OUT); 123 | gpio_set_dir(20, GPIO_OUT); 124 | gpio_put(18, 1); // R 125 | gpio_put(19, 1); // G 126 | gpio_put(20, 1); // B 127 | 128 | gpio_put(19, 0); // G 129 | sleep_ms(1000); 130 | gpio_put(19, 1); // G 131 | 132 | // a pin out to use to check timings 133 | // gpio_init(26); // Tiny2040 A0 134 | // gpio_set_dir(26, GPIO_OUT); 135 | // gpio_put(26, 0); 136 | 137 | // setup dma for dmacopy (faster than memcpy) 138 | dma_chan32 = dma_claim_unused_channel(true); 139 | dma_channel_config channel_config32 = dma_channel_get_default_config(dma_chan32); 140 | 141 | channel_config_set_transfer_data_size(&channel_config32, DMA_SIZE_32); // transfer 32 bits at a time 142 | channel_config_set_read_increment(&channel_config32, true); 143 | channel_config_set_write_increment(&channel_config32, true); 144 | 145 | dma_channel_configure(dma_chan32, 146 | &channel_config32, 147 | NULL, // write address 148 | NULL, // read address 149 | 0, // number of data transfers to 150 | false // start immediately 151 | ); 152 | 153 | seed_random_from_rosc(); 154 | 155 | multicore_launch_core1(core1_entry); 156 | // return 0; 157 | 158 | lbm.init(); 159 | lbm.cylinder(); 160 | 161 | for (uint8_t i = 0; i < NUM_CUBES; i++) { 162 | cubes[i]->randomise(); 163 | } 164 | 165 | fire.init(); 166 | 167 | cliffs.init(); 168 | 169 | #if HORIZONTAL_DOUBLING == 1 170 | uint32_t currentbadgerframe = 0; 171 | uint8_t mushroomat = 0; 172 | uint64_t mushroomtime; 173 | 174 | uint32_t currentbadappleframe = 0; 175 | float badapplehue = 0; 176 | 177 | int8_t plasmavals[3*3] = {5, 2, 4, 2, 4, 8, 10, 7, 6}; 178 | #endif 179 | 180 | memset(buf0, 0, BUF_SIZE); 181 | memset(buf1, 0, BUF_SIZE); 182 | bool buf = false; // buf0 183 | int8_t *tbuf; 184 | 185 | sleep_ms(1000); 186 | 187 | bool led = true; 188 | uint8_t at = 1; 189 | uint64_t numframes = 0; 190 | uint64_t demo_start_time = time(), frame_start_time; 191 | int64_t to_sleep; 192 | while (1) { 193 | frame_start_time = time(); 194 | 195 | // flash LED ahead of calculating frame 196 | gpio_put(20, led = !led); 197 | sleep_us(1000); 198 | gpio_put(20, led = !led); 199 | 200 | // at = 7; // set at here to show one specific demo 201 | 202 | // swap between two buffers 203 | if (buf) { 204 | tbuf = buf1; 205 | } 206 | else { 207 | tbuf = buf0; 208 | } 209 | buf = !buf; 210 | 211 | // loop through some cool demos 212 | if (at == 0) { 213 | memset(tbuf, 0, BUF_SIZE); 214 | writeStr(tbuf, 7, 0, "Hello World!", 100, 100, 100); 215 | 216 | drawLineRGB(tbuf, 0, 20, XRESOLUTION-1, 20, 127, 0, 0); 217 | drawLineRGB(tbuf, 0, 21, XRESOLUTION-1, 21, 127, 0, 0); 218 | drawLineRGB(tbuf, 0, 22, XRESOLUTION-1, 22, 127, 0, 0); 219 | drawLineRGB(tbuf, 0, 40, XRESOLUTION-1, 40, 0, 127, 0); 220 | drawLineRGB(tbuf, 0, 41, XRESOLUTION-1, 41, 0, 127, 0); 221 | drawLineRGB(tbuf, 0, 42, XRESOLUTION-1, 42, 0, 127, 0); 222 | drawLineRGB(tbuf, 0, 60, XRESOLUTION-1, 60, 0, 0, 127); 223 | drawLineRGB(tbuf, 0, 61, XRESOLUTION-1, 61, 0, 0, 127); 224 | drawLineRGB(tbuf, 0, 62, XRESOLUTION-1, 62, 0, 0, 127); 225 | 226 | writeStr(tbuf, 2, 20, "the quick brown fox jumps over the lazy dog 1234567890", 120, 120, 120); 227 | writeStr(tbuf, 2, 30, "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 1234567890", 120, 120, 120); 228 | writeStr(tbuf, 2, 40, "the quick brown fox jumps over the lazy dog 1234567890", 120, 0, 0); 229 | writeStr(tbuf, 2, 50, "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 1234567890", 120, 0, 0); 230 | writeStr(tbuf, 2, 60, "the quick brown fox jumps over the lazy dog 1234567890", 0, 120, 0); 231 | writeStr(tbuf, 2, 70, "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 1234567890", 0, 120, 0); 232 | writeStr(tbuf, 2, 80, "the quick brown fox jumps over the lazy dog 1234567890", 0, 0, 120); 233 | writeStr(tbuf, 2, 90, "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG 1234567890", 0, 0, 120); 234 | } 235 | else if (at == 1) { 236 | memcpy(tbuf, raspberrypipng, BUF_SIZE); 237 | } 238 | else if (at == 2) { 239 | // show BBC2 Test Card F with a tuning in animation 240 | // current time in seconds 241 | float now = (time() - demo_start_time) / 1e6; 242 | 243 | // scroll up, start by showing the bottom at the top first, then the roll up 244 | if (now < 0.3) { // 0.5 245 | memset(tbuf, 0, BUF_SIZE); 246 | int8_t yoff = 30 - 25*(cosf(now*M_PI*10/3+M_PI)+1); 247 | int8_t gap = 20; 248 | if (yoff > 0) { 249 | memcpy(tbuf, testcardfpng+BUF_SIZE-XRESOLUTION*yoff*3, XRESOLUTION*yoff*3); 250 | } 251 | memcpy(tbuf+XRESOLUTION*(yoff+gap)*3, testcardfpng, BUF_SIZE-XRESOLUTION*(yoff+gap)*3); 252 | } 253 | else if (now < 0.35) { // after the big roll, a little bump 254 | memset(tbuf, 0, BUF_SIZE); 255 | int8_t yoff = 3*fabs(sinf((now-0.3)*M_PI*20)); // t - 0.2 256 | memcpy(tbuf, testcardfpng+XRESOLUTION*yoff*3, BUF_SIZE-XRESOLUTION*yoff*3); 257 | } 258 | else { 259 | // after sync, the image is perfect 260 | memcpy(tbuf, testcardfpng, BUF_SIZE); 261 | } 262 | 263 | // flash static for a little bit, in a pulsing pattern 264 | // also fade the colours in 265 | if (now < 3) { 266 | float intensity; // intensity of the static 267 | if (now < 0.5) { 268 | intensity = cosf(2*now*M_PI)*0.4+0.6; 269 | } 270 | else if (now < 1.5) { 271 | intensity = 0.125*(cosf(2*now*M_PI)+1.125); 272 | intensity *= (1.5-now)*0.2; 273 | } 274 | else { // after 1.5 seconds, a clear picture 275 | intensity = 0; 276 | } 277 | int32_t intensityint = intensity * 256; // perform intensity adjustments as integer math 278 | int32_t t; 279 | int32_t tint = (now*2.5+0.5)*256; // the colour fades in 280 | for (int32_t ycoord = 0; ycoord < YRESOLUTION; ycoord++) { 281 | for (int32_t xcoord = 12; xcoord < XRESOLUTION-12; xcoord++) { 282 | int8_t *idx = tbuf + (ycoord * XRESOLUTION + xcoord) * 3; 283 | // the static 284 | if (now < 0.5) { 285 | t = (*idx - *idx * intensityint / 512) + (randi(-127, 127) * intensityint) / 256; 286 | } 287 | else if (now < 1.5) { 288 | t = *idx + (randi(-127, 127) * intensityint) / 256; 289 | } 290 | else { 291 | t = *idx; 292 | } 293 | if (t > 127) t = 127; 294 | if (t < 0) t = 0; 295 | *idx = t; 296 | // the colour fade 297 | *(idx+1) = (*(idx+1) * tint) / 2048; // divide by 256 * 8 298 | *(idx+2) = (*(idx+2) * tint) / 2048; 299 | } 300 | } 301 | } // now 302 | 303 | } 304 | else if (at == 3) { 305 | // show the LBM simulation 306 | memset(tbuf, 0, BUF_SIZE); 307 | 308 | // if floating point was faster, we'd calculate multiple frames between renders 309 | // lbm.timestep(); 310 | lbm.timestep(true); 311 | drawlbm(lbm, tbuf); 312 | writeStr(tbuf, 12, 1, "Lattice Boltzmann", 100, 100, 100); 313 | writeStr(tbuf, 12, 7, "CFD Simulation", 100, 100, 100); 314 | writeStr(tbuf, 12, YRESOLUTION-10, "Eddy Shedding", 100, 100, 100); 315 | 316 | char txtbuf[20]; 317 | memset(txtbuf, 0, sizeof(buf)); 318 | sprintf(txtbuf, "t = %d", lbm.getNumberOfTimeSteps()); 319 | writeStr(tbuf, 12, YRESOLUTION-5, txtbuf, 100, 100, 100); 320 | } 321 | else if (at == 4) { 322 | // show a bouncing cube 323 | memset(tbuf, 0, BUF_SIZE); 324 | drawLineRGB(tbuf, 0, 0, XRESOLUTION-1, 0, 50, 50, 50); 325 | drawLineRGB(tbuf, 0, YRESOLUTION-1, XRESOLUTION-1, YRESOLUTION-1, 50, 50, 50); 326 | drawLineRGB(tbuf, 0, 1, 0, YRESOLUTION-2, 50, 50, 50); 327 | // - 2 because some of the buffer is actualyl chopped off... 328 | drawLineRGB(tbuf, XRESOLUTION-2, 1, XRESOLUTION-2, YRESOLUTION-2, 50, 50, 50); 329 | 330 | cubes[0]->step(); 331 | 332 | tr.reset(); 333 | tr.addObject(*cubes[0]); 334 | tr.render(tbuf); 335 | writeStr(tbuf, 7, 3, " One Cube", 100, 100, 100); 336 | } 337 | else if (at == 5) { 338 | // show many bouncing cube 339 | memset(tbuf, 0, BUF_SIZE); 340 | drawLineRGB(tbuf, 0, 0, XRESOLUTION-2, 0, 50, 50, 50); 341 | drawLineRGB(tbuf, 0, YRESOLUTION-1, XRESOLUTION-2, YRESOLUTION-1, 50, 50, 50); 342 | drawLineRGB(tbuf, 0, 1, 0, YRESOLUTION-2, 50, 50, 50); 343 | // - 2 because some of the buffer is actualyl chopped off... 344 | drawLineRGB(tbuf, XRESOLUTION-2, 1, XRESOLUTION-2, YRESOLUTION-2, 50, 50, 50); 345 | 346 | for (uint8_t i = 0; i < NUM_CUBES; i++) { 347 | cubes[i]->step(); 348 | } 349 | 350 | for (uint8_t i = 0; i < NUM_CUBES; i++) { 351 | for (uint8_t j = i+1; j < NUM_CUBES; j++) { 352 | cubes[i]->collide(*cubes[j]); 353 | } 354 | } 355 | 356 | tr.reset(); 357 | for (uint8_t i = 0; i < NUM_CUBES; i++) { 358 | tr.addObject(*cubes[i]); 359 | } 360 | tr.render(tbuf); 361 | writeStr(tbuf, 7, 3, "Many Cubes", 100, 100, 100); 362 | } 363 | else if (at == 6) { 364 | // animated fire 365 | memset(tbuf, 0, BUF_SIZE); 366 | 367 | fire.step(); 368 | fire.draw(tbuf); 369 | if (fire.getColormap() == Flames::RED) 370 | writeStr(tbuf, 7, 3, "Red Flames", 100, 0, 0); 371 | else if (fire.getColormap() == Flames::PURPLE) 372 | writeStr(tbuf, 7, 3, "Purple Flames", 100, 0, 100); 373 | else if (fire.getColormap() == Flames::BLUE) 374 | writeStr(tbuf, 7, 3, "Blue Flames", 0, 0, 100); 375 | } 376 | else if (at == 7) { 377 | // flying through the cliffs 378 | memset(tbuf, 0, BUF_SIZE); 379 | 380 | cliffs.step(); 381 | cliffs.render(tbuf); 382 | writeStr(tbuf, 7, 3, "Synth Cliffs", 0, 0, 0); 383 | } 384 | #if HORIZONTAL_DOUBLING == 1 385 | else if (at == 8) { 386 | #else 387 | // the video won't currently work at low res (need to convert images) 388 | // so instead just show static 389 | else if (at == 8 || at == 9 || at == 10) { 390 | #endif 391 | // static 392 | memset(tbuf, 0, BUF_SIZE); 393 | 394 | for (int32_t ycoord = 0; ycoord < YRESOLUTION; ycoord++) { 395 | for (int32_t xcoord = 0; xcoord < XRESOLUTION; xcoord++) { 396 | setPixelYUV(tbuf, xcoord, ycoord, rand() & 127, 0, 0); 397 | } 398 | } 399 | // writeStr(tbuf, 7, 3, "Classic Snow", 0, 0, 0); 400 | } 401 | #if HORIZONTAL_DOUBLING == 1 402 | else if (at == 9) { 403 | // run length encoding video 404 | // badgers & mushrooms 405 | memset(tbuf, 0, BUF_SIZE); 406 | 407 | if (mushroomat == 0) { 408 | int8_t* currentframe = badgerframes[currentbadgerframe]; 409 | int8_t *idx = tbuf; 410 | 411 | for (uint32_t j = 0; j < badgerframelengths[currentbadgerframe]; j++) { 412 | for (uint8_t i = 0; i < currentframe[j*4 + 3]; i++) { 413 | // replicate the specified pixel that number of times 414 | *(idx++) = currentframe[j*4 + 0]; 415 | *(idx++) = currentframe[j*4 + 1]; 416 | *(idx++) = currentframe[j*4 + 2]; 417 | } 418 | } 419 | 420 | if (currentbadgerframe < NUM_BADGERFRAMES-1) { // minus one so it's for the next iteration around 421 | if (buf) { // the video is 25 fps, but display is 50, so only update every other frame 422 | currentbadgerframe++; 423 | } 424 | } 425 | else { 426 | currentbadgerframe = 0; 427 | mushroomat = 1; 428 | mushroomtime = time(); 429 | } 430 | } 431 | else { 432 | if (mushroomat == 1) { 433 | memcpy(tbuf, mushroom1png, BUF_SIZE); 434 | } 435 | else { 436 | memcpy(tbuf, mushroom2png, BUF_SIZE); 437 | } 438 | if (time() - mushroomtime > 1.65*1e6) { 439 | mushroomat = 0; 440 | } 441 | else if (time() - mushroomtime > 0.65*1e6) { 442 | mushroomat = 2; 443 | } 444 | } 445 | // writeStr(tbuf, 7, 3, "Badger Badger Badger", 127, 127, 127); 446 | } 447 | #endif 448 | #if HORIZONTAL_DOUBLING == 1 449 | else if (at == 10) { 450 | // bad apple demo 451 | // plasma video effect: 452 | // https://rosettacode.org/wiki/Plasma_effect 453 | // https://samuel-lereah.com/articles/CS/making-plasma-effects 454 | memset(tbuf, 0, BUF_SIZE); 455 | 456 | uint32_t xcoord, ycoord; // screen coordinates 457 | int8_t y, u, v; // colour at screen coordinate 458 | 459 | // combine three sine waves to make the plasma effect 460 | // the plasma intensity is mapped to hsv via a colourmap 461 | uint32_t phase1 = ((time()*64)/1000000) % 128; 462 | uint32_t phase2 = ((time()*48)/1000000) % 128; 463 | uint32_t phase3 = ((time()*24)/1000000) % 128; 464 | int32_t val1, val2, val3; 465 | uint8_t r; // the final intensity of the plasma 466 | 467 | bool currentcolour = false; // only set colour in place of white (trye) 468 | 469 | uint8_t* currentframe = badappleframes[currentbadappleframe]; 470 | uint32_t pixelnum = 0; 471 | 472 | // run through each frame, decode it, and set the current buffer to either black or white (plasma) 473 | for (uint32_t j = 0; j < badappleframelengths[currentbadappleframe]; j++) { 474 | for (uint8_t i = 0; i < currentframe[j]; i++) { 475 | 476 | if (currentcolour) { 477 | xcoord = pixelnum % XRESOLUTION; 478 | ycoord = pixelnum / YRESOLUTION; 479 | 480 | val1 = (plasmavals[0] * xcoord + plasmavals[1] * ycoord ) / plasmavals[2] + phase1; 481 | val2 = (plasmavals[3] * xcoord + plasmavals[4] * ycoord ) / plasmavals[5] + phase2; 482 | val3 = (plasmavals[6] * xcoord + plasmavals[7] * ycoord ) / plasmavals[8] + phase3; 483 | 484 | // needs to end up 0-63, sine table ranges 0-128 so mod to get in range 485 | // the table returns -127 to 127 so add + numthings*128 to remove negatives 486 | // then divide by numthings*4 to end up offset 0-63 487 | r = ((sine_table[val1 % 128] + sine_table[val2 % 128] + sine_table[val3 % 128]) + 3*128) / (3*4); 488 | rgb2yuv(hsv[r][0], hsv[r][1], hsv[r][2], y, u, v); 489 | } 490 | else { 491 | y = 0; 492 | u = 0; 493 | v = 0; 494 | } 495 | 496 | tbuf[pixelnum * 3 + 0] = currentcolour ? y : 0; // 127 : 0 497 | tbuf[pixelnum * 3 + 1] = currentcolour ? u : 0; 498 | tbuf[pixelnum * 3 + 2] = currentcolour ? v : 0; 499 | 500 | pixelnum++; 501 | } 502 | // the current colour is either black or white, we swap because black & white video 503 | // is encoded where each byte is the number of black, then number of white, etc 504 | currentcolour = !currentcolour; 505 | } 506 | 507 | if (buf) { // the video is 25 fps, but display is 50, so only update every other frame 508 | currentbadappleframe++; 509 | if (currentbadappleframe == NUM_BADAPPLEFRAMES) { 510 | currentbadappleframe = 0; 511 | } 512 | 513 | if (currentbadappleframe >= 126) { // there's a matching value to change above 514 | badapplehue += 1.0; 515 | } 516 | 517 | if (badapplehue >= 360) { 518 | badapplehue = 0; 519 | } 520 | 521 | if (currentbadappleframe % (DEMO_DURATION*25) == 0) { 522 | // cycle the plasma each time 523 | for (uint8_t i = 0; i < 9; i++) { 524 | if (rand() > 0.5) { 525 | plasmavals[i] = randi(2, 10); 526 | } 527 | else { 528 | plasmavals[i] = randi(-10, -2); 529 | } 530 | } 531 | } 532 | } 533 | // writeStr(tbuf, 7, 3, "Bad Apple", 0, 0, 0); 534 | } 535 | #endif 536 | // end of demo selection 537 | 538 | if (at > 2) { 539 | char txtbuf[20]; 540 | memset(txtbuf, 0, sizeof(buf)); 541 | sprintf(txtbuf, "%4.1f ms", ((time() - frame_start_time)/1e3)); 542 | writeStr(tbuf, XRESOLUTION-35, YRESOLUTION-5, txtbuf, 100, 100, 100); 543 | } 544 | 545 | // show the current "channel" (demo) number for a little bit after changing 546 | if (time() - demo_start_time < 2*1e6) { 547 | char txtbuf[20]; 548 | sprintf(txtbuf, "%d", at); 549 | writeStr(tbuf, XRESOLUTION-21+1, 9+1, txtbuf, 0, 0, 0, true); 550 | writeStr(tbuf, XRESOLUTION-21, 9, txtbuf, 0, 100, 0, true); 551 | } 552 | 553 | cp.setBuf(tbuf); 554 | 555 | if (time() - demo_start_time > DEMO_DURATION*1e6) { 556 | at++; 557 | demo_start_time = time(); 558 | if (at == 7) { 559 | if (fire.getColormap() == Flames::RED) 560 | fire.setColormap(Flames::PURPLE); 561 | else if (fire.getColormap() == Flames::PURPLE) 562 | fire.setColormap(Flames::BLUE); 563 | else if (fire.getColormap() == Flames::BLUE) 564 | fire.setColormap(Flames::RED); 565 | } 566 | if (at == NUM_DEMOS+1) { 567 | at = 1; 568 | } 569 | } 570 | // 50 Hz? sleep up to 20 ms to do the next frame 571 | to_sleep = 20e3 - (time() - frame_start_time); 572 | if (to_sleep > 0) { 573 | sleep_us(to_sleep); 574 | } 575 | 576 | } 577 | } 578 | 579 | // do all composite processing on the second core 580 | void core1_entry() { 581 | 582 | cp.init(); 583 | cp.start(); 584 | 585 | // should never get here, cp.start() should loop 586 | while (1) { 587 | tight_loop_contents(); 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /colourpal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * The MIT License (MIT) 4 | * 5 | * Copyright (c) 2022-2023 guruthree 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | */ 26 | 27 | #define HORIZONTAL_DOUBLING 1 // without x-resolution doubling 28 | //#define HORIZONTAL_DOUBLING 2 29 | 30 | // possible 'high' resolutions: 31 | // * 164x125: clock divider of 9, 8 samples per pixel 32 | // * 220x110: clock divider of 12, 5 samples per pixel 33 | // * 300x120: clock divider of 12, 4 samples per pixel 34 | 35 | // replace with const int? 36 | #if HORIZONTAL_DOUBLING == 2 37 | #define XRESOLUTION 110 38 | #else 39 | #define XRESOLUTION 220 // doubling 40 | #endif 41 | 42 | #define YRESOLUTION 110 // this is multipled by two for the line count 43 | #define YDATA_START 43 // line number 44 | #define YDATA_END (YDATA_START + YRESOLUTION*2) 45 | 46 | // PAL timings - these are here as consts because the division needs to process 47 | // a horizontal line is 64 microseconds 48 | // this HAS to be divisible by 4 to use 32-bit DMA transfers 49 | // just SAMPLES_PER_LINE and SAMPLES_COLOUR maybe? 50 | // replace with static constexpr? 51 | const uint32_t SAMPLES_PER_LINE = 4*((uint32_t)((64 * DAC_FREQ / 1e6)/4)); // this HAS to be a multiple of 4! 52 | const uint32_t SAMPLES_GAP = 4.7 * DAC_FREQ / 1e6; // 312 53 | const uint32_t SAMPLES_SHORT_PULSE = 2.35 * DAC_FREQ / 1e6; // the time of the little blip down mid line 54 | const uint32_t SAMPLES_HSYNC = 4.7 * DAC_FREQ / 1e6; // horizontal sync duration 55 | const uint32_t SAMPLES_BACK_PORCH = 5.7 * DAC_FREQ / 1e6; // back porch duration 56 | const uint32_t SAMPLES_FRONT_PORCH = 1.7 * DAC_FREQ / 1e6; // front porch duration 57 | const uint32_t SAMPLES_UNTIL_BURST = 5.3 * DAC_FREQ / 1e6; // burst starts at this time 58 | const uint32_t SAMPLES_BURST = 2.5 * DAC_FREQ / 1e6; // burst duration 59 | const uint32_t SAMPLES_HALFLINE = SAMPLES_PER_LINE / 2; 60 | 61 | // PAL colour carrier frequency 62 | // this ideally needs to divide in some fashion into the DAC_FREQ? 63 | const float COLOUR_CARRIER = 4433618.75; 64 | 65 | // changing this changes how stretched the pixels are on screen 66 | const uint32_t SAMPLES_PER_PIXEL = 5*HORIZONTAL_DOUBLING; 67 | // the number of samples that make up the colour data to send 68 | const uint32_t SAMPLES_COLOUR = XRESOLUTION * SAMPLES_PER_PIXEL; 69 | // the number of samples that are not colour data 70 | //const uint32_t SAMPLES_SYNC_PORCHES = SAMPLES_FRONT_PORCH + SAMPLES_HSYNC + SAMPLES_BACK_PORCH + SAMPLES_OFF + ; 71 | const uint32_t SAMPLES_SYNC_PORCHES = SAMPLES_PER_LINE - SAMPLES_COLOUR; 72 | // the delay after the colour burst before display data starts, this is where we start copying data into the scanline 73 | // this also affects colour for some reason, so compensate by changing delay in populateBurst 74 | const uint32_t SAMPLES_OFF = (SAMPLES_SYNC_PORCHES - (SAMPLES_FRONT_PORCH + SAMPLES_HSYNC + SAMPLES_BACK_PORCH)) / 2; // center the picture? 75 | // the number of samples after the colour data before the front porch starts 76 | const uint32_t SAMPLES_DEAD_SPACE = SAMPLES_SYNC_PORCHES - SAMPLES_FRONT_PORCH - SAMPLES_HSYNC - SAMPLES_BACK_PORCH - SAMPLES_OFF; // the samples at the end of signal right before front porch 77 | 78 | // this should be 32 and 32, but load on core 0 slows core 1 down so that 79 | // the extra timing from rendering one fewer pixel across is needed 80 | 81 | #if HORIZONTAL_DOUBLING == 2 82 | const uint8_t PIXELS_A = 24; // how many pixels are processed during sync 83 | #else 84 | const uint8_t PIXELS_A = 64; // without doubling 85 | #endif 86 | 87 | const uint32_t PIXELS_B = XRESOLUTION - PIXELS_A; 88 | //const uint8_t PIXELS_B = 93; // how many pixels are processed during colour 89 | //const uint8_t PIXELS_B = 100; // without doubling 90 | 91 | // convert to YUV for PAL encoding, RGB should be 0-127 92 | // no these are not the standard equations 93 | // part of that is integer maths, fine, but other wise... 94 | // v and u are swapped... 95 | inline void rgb2yuv(uint8_t r, uint8_t g, uint8_t b, int8_t &y, int8_t &u, int8_t &v) { 96 | y = 5 * r / 16 + 9 * g / 16 + b / 8; // luminance 97 | v = (b - y) / 2; 98 | u = 13 * (r - y) / 16; 99 | } 100 | 101 | inline void setPixelYUV(int8_t *buf, int32_t xcoord, int32_t ycoord, int8_t y, int8_t u, int8_t v) { 102 | if (xcoord >= XRESOLUTION || ycoord >= YRESOLUTION || xcoord < 0 || ycoord < 0) { 103 | // clipping 104 | return; 105 | } 106 | 107 | int8_t *idx = buf + (ycoord * XRESOLUTION + xcoord) * 3; 108 | *idx = y; 109 | *(idx+1) = u; 110 | *(idx+2) = v; 111 | } 112 | 113 | inline void getPixelYUV(int8_t *buf, int32_t xcoord, int32_t ycoord, int8_t &y, int8_t &u, int8_t &v) { 114 | if (xcoord >= XRESOLUTION || ycoord >= YRESOLUTION || xcoord < 0 || ycoord < 0) { 115 | // clipping 116 | return; 117 | } 118 | 119 | int8_t *idx = buf + (ycoord * XRESOLUTION + xcoord) * 3; 120 | y = *idx; 121 | u = *(idx+1) = u; 122 | v = *(idx+2) = v; 123 | } 124 | 125 | 126 | inline void setPixelRGB(int8_t *buf, int32_t xcoord, int32_t ycoord, uint8_t r, uint8_t g, uint8_t b) { 127 | if (xcoord >= XRESOLUTION || ycoord >= YRESOLUTION || xcoord < 0 || ycoord < 0) { 128 | // clipping 129 | return; 130 | } 131 | int8_t y = 0, u = 0, v = 0; 132 | rgb2yuv(r, g, b, y, u, v); 133 | 134 | setPixelYUV(buf, xcoord, ycoord, y, u, v); 135 | } 136 | 137 | // set a block of 4 pixels at once, useful for drawing bigger things 138 | inline void setPixelYUVtwoX(int8_t *buf, int32_t xcoord, int32_t ycoord, int8_t y, int8_t u, int8_t v) { 139 | if (xcoord >= XRESOLUTION-1 || ycoord >= YRESOLUTION-1 || xcoord < 0 || ycoord < 0) { 140 | // clipping 141 | return; 142 | } 143 | 144 | setPixelYUV(buf, xcoord, ycoord, y, u, v); 145 | setPixelYUV(buf, xcoord+1, ycoord, y, u, v); 146 | setPixelYUV(buf, xcoord, ycoord+1, y, u, v); 147 | setPixelYUV(buf, xcoord+1, ycoord+1, y, u, v); 148 | 149 | } 150 | inline void setPixelRGBtwoX(int8_t *buf, int32_t xcoord, int32_t ycoord, uint8_t r, uint8_t g, uint8_t b) { 151 | if (xcoord >= XRESOLUTION-1 || ycoord >= YRESOLUTION-1 || xcoord < 0 || ycoord < 0) { 152 | // clipping 153 | return; 154 | } 155 | int8_t y = 0, u = 0, v = 0; 156 | rgb2yuv(r, g, b, y, u, v); 157 | 158 | setPixelYUVtwoX(buf, xcoord, ycoord, y, u, v); 159 | } 160 | 161 | 162 | // the line of colour data being displayed 163 | // put it in its own SRAM bank for most reliable fast RAM access 164 | uint8_t __scratch_y("screenbuffer") screenbuffer_B[SAMPLES_COLOUR]; 165 | 166 | 167 | // note this does psuedo-progressive, which displays the same lines every field 168 | class ColourPal { 169 | 170 | private: 171 | // voltages 172 | const float SYNC_VOLTS = -0.3; 173 | const float BLANK_VOLTS = 0.0; // also black 174 | const float WHITE_VOLTS = 0.4; // any higher than 0.2 and integer wrapping on green since the DAC really should have been 0 to 1.25 volts 175 | const float BUSRT_VOLTS = 0.15; 176 | int32_t levelSync = 0, levelBlank, levelWhite, levelColor; // converted to DAC values 177 | 178 | // setup the DAC and DMA 179 | PIO pio; 180 | int pio_sm = 0; 181 | int pio_offset; 182 | uint8_t dma_channel_A;//, dma_channel_B; 183 | 184 | // these lines are the same every frame 185 | uint8_t line1_A[SAMPLES_SYNC_PORCHES]; 186 | uint8_t line4_A[SAMPLES_SYNC_PORCHES]; 187 | uint8_t line6odd_A[SAMPLES_SYNC_PORCHES]; 188 | uint8_t line6even_A[SAMPLES_SYNC_PORCHES]; 189 | uint8_t line6_B[SAMPLES_COLOUR]; 190 | uint8_t line1_B[SAMPLES_COLOUR]; 191 | uint8_t line3_B[SAMPLES_COLOUR]; 192 | uint8_t line4_B[SAMPLES_COLOUR]; 193 | // each PAL line is setup to be an A part and B part so that the colour data is < 4096 bytes and can be fit 194 | // within SRAM banks 5 and 6 (scratch x and y) to avoid contention with writing to one while the other 195 | // is read by the DMA... maybe... 196 | 197 | // the pre-calculated portion of the colour carrier 198 | int32_t __attribute__((__aligned__(4))) ALLSIN2[SAMPLES_COLOUR+SAMPLES_PER_PIXEL]; // aligned might not do anything here 199 | // the colour burst 200 | uint8_t burstOdd[SAMPLES_BURST]; // for odd lines 201 | uint8_t burstEven[SAMPLES_BURST]; // for even lines 202 | 203 | uint8_t colourbarsOdd_B[SAMPLES_COLOUR]; 204 | uint8_t colourbarsEven_B[SAMPLES_COLOUR]; 205 | 206 | int8_t* buf = NULL; // image data we are displaying 207 | uint32_t currentline = 1; 208 | bool oddline = true; 209 | bool led = false; 210 | 211 | public: 212 | ColourPal() {} 213 | 214 | void init() { 215 | 216 | // pre-calculate voltage levels 217 | levelBlank = (BLANK_VOLTS - SYNC_VOLTS) * DIVISIONS_PER_VOLT + 0.5; 218 | levelWhite = (WHITE_VOLTS - SYNC_VOLTS) * DIVISIONS_PER_VOLT + 0.5; 219 | levelColor = BUSRT_VOLTS * DIVISIONS_PER_VOLT + 0.5; // scaled and used to add on top of other signal 220 | 221 | // setup pio & dma copies 222 | pio = pio0; 223 | pio_offset = pio_add_program(pio, &dac_program); 224 | dac_program_init(pio, pio_sm, pio_offset, 0, CLOCK_DIV); 225 | 226 | // DMA channel for syncs 227 | dma_channel_A = dma_claim_unused_channel(true); 228 | dma_channel_config channel_configA = dma_channel_get_default_config(dma_channel_A); 229 | 230 | channel_config_set_transfer_data_size(&channel_configA, DMA_SIZE_32); // transfer 8 bits at a time 231 | channel_config_set_read_increment(&channel_configA, true); // go down the buffer as it's read 232 | channel_config_set_write_increment(&channel_configA, false); // always write the data to the same place 233 | channel_config_set_dreq(&channel_configA, pio_get_dreq(pio, pio_sm, true)); 234 | 235 | dma_channel_configure(dma_channel_A, 236 | &channel_configA, 237 | &pio->txf[pio_sm], // write address 238 | NULL, // read address 239 | SAMPLES_SYNC_PORCHES / 4, // number of data transfers to ( / 4 because 32-bit copies are faster) 240 | false // start immediately 241 | ); 242 | 243 | // pre-calculate all the lines that don't change depending on what's being shown 244 | resetLines(); 245 | // pre-calculate colour burst (includes convserion to DAC voltages) 246 | populateBurst(); 247 | 248 | // you would think the colour carrier repeats, but because the DAC frequency doesn't 249 | // we need to calculate at each position 250 | // at least we can use a PI/2 offset to find the COS 251 | memset(ALLSIN2, levelBlank, SAMPLES_COLOUR+SAMPLES_PER_PIXEL); 252 | for (uint32_t i = 0; i < SAMPLES_COLOUR+SAMPLES_PER_PIXEL; i++) { 253 | float x = (float(i)-float(SAMPLES_DEAD_SPACE)) / DAC_FREQ * 2.0 * M_PI * COLOUR_CARRIER;// + (135.0 / 180.0) * M_PI; 254 | ALLSIN2[i] = levelWhite*sinf(x); 255 | } 256 | 257 | // default simple test pattern 258 | createColourBars(); 259 | 260 | // initialise front buffer 261 | memset(screenbuffer_B, levelBlank, SAMPLES_COLOUR); 262 | } 263 | 264 | void resetLines() { 265 | // calculate the sync signals that are used repeatedly 266 | 267 | // sync is lower, blank is in the middle 268 | memset(line1_A, levelBlank, SAMPLES_SYNC_PORCHES); 269 | memset(line4_A, levelBlank, SAMPLES_SYNC_PORCHES); 270 | memset( line6odd_A, levelBlank, SAMPLES_SYNC_PORCHES); 271 | memset(line6even_A, levelBlank, SAMPLES_SYNC_PORCHES); 272 | 273 | memset(line1_B, levelSync, SAMPLES_COLOUR); // this one is mostly low 274 | memset(line3_B, levelBlank, SAMPLES_COLOUR); 275 | memset(line4_B, levelBlank, SAMPLES_COLOUR); 276 | memset(line6_B, levelBlank, SAMPLES_COLOUR); // this one is easy, nothing else needed 277 | 278 | memset(line1_A + SAMPLES_DEAD_SPACE + SAMPLES_FRONT_PORCH, levelSync, SAMPLES_HSYNC + SAMPLES_BACK_PORCH); 279 | memset(line4_A + SAMPLES_DEAD_SPACE + SAMPLES_FRONT_PORCH, levelSync, SAMPLES_SHORT_PULSE); 280 | memset( line6odd_A + SAMPLES_DEAD_SPACE + SAMPLES_FRONT_PORCH, levelSync, SAMPLES_HSYNC); 281 | memset(line6even_A + SAMPLES_DEAD_SPACE + SAMPLES_FRONT_PORCH, levelSync, SAMPLES_HSYNC); 282 | 283 | memset(line1_B + SAMPLES_HALFLINE - SAMPLES_HSYNC - SAMPLES_BACK_PORCH - SAMPLES_GAP - SAMPLES_OFF, levelBlank, SAMPLES_GAP); // 1st gap 284 | if (SAMPLES_DEAD_SPACE + SAMPLES_FRONT_PORCH < SAMPLES_GAP) { 285 | memset(line1_B + SAMPLES_COLOUR - SAMPLES_GAP + SAMPLES_FRONT_PORCH + SAMPLES_OFF, levelBlank, SAMPLES_GAP - SAMPLES_FRONT_PORCH - SAMPLES_OFF); // 2nd gap 286 | } 287 | 288 | memset(line3_B, levelSync, SAMPLES_HALFLINE - SAMPLES_HSYNC - SAMPLES_BACK_PORCH - SAMPLES_GAP - SAMPLES_OFF); 289 | memset(line3_B + SAMPLES_HALFLINE - SAMPLES_HSYNC - SAMPLES_BACK_PORCH - SAMPLES_OFF, levelSync, SAMPLES_SHORT_PULSE); 290 | 291 | memset(line4_B + SAMPLES_HALFLINE - SAMPLES_HSYNC - SAMPLES_BACK_PORCH - SAMPLES_OFF, levelSync, SAMPLES_SHORT_PULSE); // 2nd half of line 4 matches line 3 292 | 293 | } 294 | 295 | void populateBurst() { 296 | // put the colour burst in the sync data of the lines that need it 297 | 298 | // i think this is the time difference between the the colour carrier is 299 | // supposed to start and it actually starts due to sample resolution? 300 | float delay = SAMPLES_DEAD_SPACE-3; // for 220 with 5 samples 301 | // float delay = SAMPLES_DEAD_SPACE; // for 300 with 4 samples 302 | 303 | for (uint32_t i = 0; i < SAMPLES_BURST; i++) { 304 | // the + here is a really fine adjustment on the colour phase? changing it cycles the colours of the bars 305 | float x = (float(i)-delay) / DAC_FREQ * 2.0 * M_PI * COLOUR_CARRIER;// + (135.0 / 180.0) * M_PI; 306 | burstOdd[i] = (levelColor*cosf(x) + levelBlank); // odd lines, with out addition it would try and be negative 307 | burstEven[i] = (levelColor*sinf(x) + levelBlank); // even lines (sin vs cos gives the phase shift) 308 | } 309 | 310 | // remember, the line arrays don't start at the start of the actual line, but a little bit earlier at the end 311 | // of the data of the previous line, so we're delayed by SAMPLES_DEAD_SPACE (that little bit that's leftover) 312 | memcpy( line6odd_A + SAMPLES_DEAD_SPACE + SAMPLES_UNTIL_BURST, burstOdd, SAMPLES_BURST); 313 | memcpy(line6even_A + SAMPLES_DEAD_SPACE + SAMPLES_UNTIL_BURST, burstEven, SAMPLES_BURST); 314 | } 315 | 316 | void createColourBars() { 317 | 318 | memset( colourbarsOdd_B, levelBlank, SAMPLES_COLOUR); 319 | memset(colourbarsEven_B, levelBlank, SAMPLES_COLOUR); 320 | 321 | // there's a descrepency between 16 samples per pixel = 16 and 15 actual samples 322 | // so compenstate 323 | for (uint32_t i = 0; i < SAMPLES_COLOUR - SAMPLES_PER_PIXEL * 8; i++) { 324 | 325 | int8_t y, u, v; 326 | if (i < (SAMPLES_COLOUR / 8) - SAMPLES_PER_PIXEL) 327 | rgb2yuv(127, 127, 127, y, u, v); 328 | else if (i < (2 * SAMPLES_COLOUR / 8) - SAMPLES_PER_PIXEL*2) 329 | rgb2yuv(96, 96, 0, y, u, v); 330 | else if (i < (3 * SAMPLES_COLOUR / 8) - SAMPLES_PER_PIXEL*3) 331 | rgb2yuv(0, 96, 96, y, u, v); 332 | else if (i < (4 * SAMPLES_COLOUR / 8) - SAMPLES_PER_PIXEL*4) 333 | rgb2yuv(0, 96, 0, y, u, v); 334 | else if (i < (5 * SAMPLES_COLOUR / 8) - SAMPLES_PER_PIXEL*5) 335 | rgb2yuv(96, 0, 96, y, u, v); 336 | else if (i < (6 * SAMPLES_COLOUR / 8) - SAMPLES_PER_PIXEL*6) 337 | rgb2yuv(96, 0, 0, y, u, v); 338 | else if (i < (7 * SAMPLES_COLOUR / 8) - SAMPLES_PER_PIXEL*7) 339 | rgb2yuv(0, 0, 96, y, u, v); 340 | else 341 | // rgb2yuv(0, 0, 0, y, u, v); 342 | rgb2yuv(64, 64, 64, y, u, v); 343 | 344 | int32_t SIN2 = ALLSIN2[i]; 345 | int32_t COS2 = ALLSIN2[i+SAMPLES_PER_PIXEL/4]; 346 | 347 | // odd lines of fields 1 & 2 and even lines of fields 3 & 4? 348 | // +- cos is flipped...? 349 | colourbarsOdd_B[i] = levelBlank + (y * levelWhite + u * SIN2 - v * COS2) / 128; 350 | // even lines of fields 1 & 2 and odd lines of fields 3 & 4? 351 | colourbarsEven_B[i] = levelBlank + (y * levelWhite + u * SIN2 + v * COS2) / 128; 352 | } 353 | } 354 | 355 | void setBuf(int8_t *in) { 356 | while (currentline > YDATA_START-1) { 357 | sleep_us(16); 358 | } 359 | buf = in; 360 | } 361 | 362 | 363 | void start() { 364 | loop(); 365 | } 366 | 367 | inline void __time_critical_func(writepixels)(int32_t dmavfactor, uint8_t *backbuffer_B, uint32_t startpixel, uint32_t endpixel ) { 368 | // thanks to @Blayzeing and @ZodiusInfuser for some help with optimising this section 369 | // gpio_put(26, 1); // for checking timing 370 | 371 | // current colour being processed 372 | int32_t y = 0, u = 0, v = 0; 373 | 374 | // pointer to the buffer data we're displaying 375 | // note that YRESOLUTION is 1/2 the number of lines, so the offset (line #) is divided by 2 376 | // the currentline is +1 because we're preparing for the next line 377 | int8_t *idx = buf + (((currentline - YDATA_START + 1) / 2) * XRESOLUTION + startpixel) * 3; 378 | uint32_t dmai2; // position in line 379 | 380 | // pointer to colour carrier 381 | int32_t *SIN3p; 382 | int32_t *COS3p; 383 | 384 | for (uint32_t i = startpixel*SAMPLES_PER_PIXEL; i < (SAMPLES_PER_PIXEL*endpixel); i += SAMPLES_PER_PIXEL) { 385 | // a byte of y, a byte of u, a byte of v, repeat 386 | // make y, u, v out of 127 387 | y = (*idx++) + levelBlank; // would multiply by levelWhite, then divide by 128, so do nothing and leave it be 388 | u = *(idx++); 389 | v = dmavfactor * (*(idx++)); 390 | 391 | // for each pixel back to the start of the colour carrier 392 | SIN3p = &ALLSIN2[i]; 393 | COS3p = &ALLSIN2[i+SAMPLES_PER_PIXEL/4]; 394 | 395 | for (dmai2 = i; dmai2 < i + SAMPLES_PER_PIXEL; dmai2++) { 396 | // original equation: levelBlank + (y * levelWhite + u * SIN3[dmai2-i] + dmavfactor * v * SIN3[dmai2-i+9]) / 128; 397 | backbuffer_B[dmai2] = y + ((u * (*(SIN3p++)) + v * (*(COS3p++))) >> 7); 398 | } 399 | } 400 | // gpio_put(26, 0); // for checking timing 401 | } 402 | 403 | void __time_critical_func(loop)() { 404 | // declare the backbuffer locally so it hopefully ends up in the best memory bank... 405 | uint8_t backbuffer_B[SAMPLES_COLOUR]; 406 | memset( backbuffer_B, levelBlank, SAMPLES_COLOUR); 407 | int32_t dmavfactor; // multiply v by +1 or -1 depending on even or odd line 408 | 409 | while (true) { // could set this to a bool running; so that we can stop? 410 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_SYNC_PORCHES / 4, false); 411 | switch (currentline) { 412 | case 1 ... 2: 413 | dma_channel_set_read_addr(dma_channel_A, line1_A, true); 414 | dma_channel_wait_for_finish_blocking(dma_channel_A); 415 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 416 | dma_channel_set_read_addr(dma_channel_A, line1_B, true); 417 | break; 418 | 419 | case 3: 420 | dma_channel_set_read_addr(dma_channel_A, line1_A, true); // lines 1 and 3 start the same way 421 | dma_channel_wait_for_finish_blocking(dma_channel_A); 422 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 423 | dma_channel_set_read_addr(dma_channel_A, line3_B, true); 424 | break; 425 | 426 | case 4 ... 5: 427 | dma_channel_set_read_addr(dma_channel_A, line4_A, true); 428 | dma_channel_wait_for_finish_blocking(dma_channel_A); 429 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 430 | dma_channel_set_read_addr(dma_channel_A, line4_B, true); 431 | break; 432 | 433 | case 6 ... YDATA_START-2: 434 | if (oddline) { // odd 435 | dma_channel_set_read_addr(dma_channel_A, line6odd_A, true); 436 | dmavfactor = 1; // next up is even 437 | } 438 | else { 439 | dma_channel_set_read_addr(dma_channel_A, line6even_A, true); 440 | dmavfactor = -1; // next up is odd 441 | } 442 | dma_channel_wait_for_finish_blocking(dma_channel_A); 443 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 444 | dma_channel_set_read_addr(dma_channel_A, line6_B, true); 445 | break; 446 | 447 | case YDATA_START-1: // special case we need to prep for the next line where colour starts 448 | if ((YDATA_START-1) & 1) { // odd - since YDATA_START is a define the compiler here should prune the unused code path 449 | dma_channel_set_read_addr(dma_channel_A, line6odd_A, true); 450 | dmavfactor = 1; // next up is even 451 | writepixels(dmavfactor, backbuffer_B, 0, PIXELS_A); 452 | dma_channel_wait_for_finish_blocking(dma_channel_A); 453 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 454 | dma_channel_set_read_addr(dma_channel_A, line6_B, true); 455 | writepixels(dmavfactor, backbuffer_B, PIXELS_A, PIXELS_A+PIXELS_B); // 30 us 456 | } 457 | else { 458 | dma_channel_set_read_addr(dma_channel_A, line6even_A, true); 459 | dmavfactor = -1; // next up is odd 460 | writepixels(dmavfactor, backbuffer_B, 0, PIXELS_A); 461 | dma_channel_wait_for_finish_blocking(dma_channel_A); 462 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 463 | dma_channel_set_read_addr(dma_channel_A, line6_B, true); 464 | writepixels(dmavfactor, backbuffer_B, PIXELS_A, PIXELS_A+PIXELS_B); // 30 us 465 | } 466 | // we don't process the colour bars here so the first row of colourbar pixels will be garbage 467 | break; 468 | 469 | case YDATA_START ... YDATA_END-1: // this range is inclusive, so -1 to get the exact number of lines 470 | if (oddline) { // odd 471 | dma_channel_set_read_addr(dma_channel_A, line6odd_A, true); 472 | dmavfactor = 1; // next up is even 473 | } 474 | else { 475 | dma_channel_set_read_addr(dma_channel_A, line6even_A, true); 476 | dmavfactor = -1; // next up is odd 477 | } 478 | 479 | // copy the back to front buffer for the upcoming line 480 | dmacpy(screenbuffer_B, backbuffer_B, SAMPLES_COLOUR); // 3.2 us 481 | 482 | // if there's a buffer to show, compute a few lines here while we wait 483 | if (buf != NULL) { 484 | writepixels(dmavfactor, backbuffer_B, 0, PIXELS_A); // 28 us 485 | } 486 | 487 | dma_channel_wait_for_finish_blocking(dma_channel_A); // 24 us 488 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 489 | dma_channel_set_read_addr(dma_channel_A, screenbuffer_B, true); 490 | 491 | if (buf == NULL) { 492 | // no buffer set so show colour bars instead 493 | if (oddline) { // odd, next line is even 494 | dmacpy(backbuffer_B, colourbarsEven_B, SAMPLES_COLOUR); 495 | } 496 | else { 497 | dmacpy(backbuffer_B, colourbarsOdd_B, SAMPLES_COLOUR); 498 | } 499 | } 500 | else { 501 | // calculate data to show 502 | writepixels(dmavfactor, backbuffer_B, PIXELS_A, PIXELS_A+PIXELS_B); // 30 us 503 | } 504 | 505 | break; 506 | 507 | case YDATA_END ...309: 508 | if (oddline) { // odd 509 | dma_channel_set_read_addr(dma_channel_A, line6odd_A, true); 510 | dma_channel_wait_for_finish_blocking(dma_channel_A); 511 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 512 | dma_channel_set_read_addr(dma_channel_A, line6_B, true); 513 | } 514 | else { 515 | dma_channel_set_read_addr(dma_channel_A, line6even_A, true); 516 | dma_channel_wait_for_finish_blocking(dma_channel_A); 517 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 518 | dma_channel_set_read_addr(dma_channel_A, line6_B, true); 519 | } 520 | break; 521 | 522 | case 310 ... 312: 523 | dma_channel_set_read_addr(dma_channel_A, line4_A, true); 524 | dma_channel_wait_for_finish_blocking(dma_channel_A); 525 | dma_channel_set_trans_count(dma_channel_A, SAMPLES_COLOUR / 4, false); 526 | dma_channel_set_read_addr(dma_channel_A, line4_B, true); 527 | break; 528 | 529 | default: 530 | // should never get here 531 | break; 532 | } 533 | 534 | if (++currentline == 313) { 535 | currentline = 1; 536 | oddline = false; 537 | gpio_put(18, led = !led); // this really should be flickering more? 538 | memset( backbuffer_B, levelBlank, SAMPLES_COLOUR); // in case anything hangs on from an array size issue 539 | } 540 | oddline = !oddline; // setting it ahead of the next line while we wait 541 | 542 | // only continue to the beginning of the loop after all the line contents have been sent 543 | dma_channel_wait_for_finish_blocking(dma_channel_A); 544 | 545 | } // while (true) 546 | } // loop 547 | 548 | 549 | }; // end class 550 | --------------------------------------------------------------------------------