├── .github └── FUNDING.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bitmap.c ├── bitmap.h ├── charset.c ├── charset.h ├── config.h ├── cvideo.c ├── cvideo.h ├── cvideo_data.pio ├── cvideo_sync.pio ├── graphics.c ├── graphics.h ├── hardware ├── breadboard │ ├── colour │ │ ├── Composite Video Colour #1.0.fzz │ │ ├── Composite Video Colour Breadboard.png │ │ └── Composite Video Colour Schematic.png │ └── mono │ │ ├── Composite Video Mono #1.0.fzz │ │ ├── Composite Video Mono #1.1.fzz │ │ ├── Composite Video Mono Breadboard.png │ │ └── Composite Video Mono Schematic.png └── pcb │ ├── bom │ └── Pico-mposite.csv │ ├── gerber │ └── Pico-mposite.zip │ └── schematic │ └── Pico-mposite.pdf ├── images ├── bitmap.png ├── breadboard.jpeg ├── demo_cube.jpeg ├── demo_cube_colour.jpeg ├── demo_mandlebrot.jpeg ├── demo_mandlebrot_colour.jpeg ├── demo_splash.jpeg ├── demo_splash_colour.jpeg └── video_output.jpeg ├── main.c ├── main.h ├── pico_sdk_import.cmake ├── terminal.c └── terminal.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: breakintoprogram 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | .* 3 | /build/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Title: Pico-mposite Makefile 3 | # Description: Makefile 4 | # Author: Dean Belfield 5 | # Created: 31/01/2021 6 | # Last Updated: 26/09/2024 7 | # 8 | # Modinfo: 9 | # 01/02/2022: Added this header comment, fixed typo in executable filename, added extra target sources 10 | # 19/02/2022: Added terminal.c 11 | # 26/09/2024: Updated build files so that the project can be built more easily 12 | 13 | # 14 | # See the official documentation https://www.raspberrypi.com/documentation/microcontrollers/c_sdk.html 15 | # for guidance on setting up the Pico C/C++ SDK. 16 | # 17 | 18 | cmake_minimum_required(VERSION 3.13) 19 | include(pico_sdk_import.cmake) 20 | project(mposite_project C CXX ASM) 21 | set(CMAKE_C_STANDARD 11) 22 | set(CMAKE_CXX_STANDARD 17) 23 | pico_sdk_init() 24 | 25 | add_executable(pico-mposite main.c cvideo.c graphics.c charset.c bitmap.c terminal.c) 26 | 27 | pico_generate_pio_header(pico-mposite ${CMAKE_CURRENT_LIST_DIR}/cvideo_sync.pio) 28 | pico_generate_pio_header(pico-mposite ${CMAKE_CURRENT_LIST_DIR}/cvideo_data.pio) 29 | 30 | target_link_libraries( 31 | pico-mposite PRIVATE 32 | pico_stdlib 33 | pico_mem_ops 34 | hardware_pio 35 | hardware_dma 36 | hardware_irq 37 | pico_bootrom 38 | ) 39 | 40 | pico_add_extra_outputs(pico-mposite) 41 | 42 | add_custom_command( 43 | OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/cvideo_sync.pio.h 44 | DEPENDS ${CMAKE_CURRENT_LIST_DIR}/cvideo_sync.pio 45 | COMMAND Pioasm ${CMAKE_CURRENT_LIST_DIR}/cvideo_sync.pio ${CMAKE_CURRENT_LIST_DIR}/generated/cvideo_sync.pio.h 46 | ) 47 | 48 | add_custom_command( 49 | OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/cvideo_data.pio.h 50 | DEPENDS ${CMAKE_CURRENT_LIST_DIR}/cvideo_data.pio 51 | COMMAND Pioasm ${CMAKE_CURRENT_LIST_DIR}/cvideo_data.pio ${CMAKE_CURRENT_LIST_DIR}/generated/cvideo_data.pio.h 52 | ) 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Dean Belfield 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pico-mposite 2 | 3 | ### Summary 4 | Hardware and firmware for the Raspberry Pi Pico to add a composite video output using GPIO. 5 | 6 | ![Splash Screen](/images/demo_splash_colour.jpeg) 7 | 8 | ### Why am I doing this? 9 | The code has evolved from a demo I built when the Pico was first released. I wanted a simple project that combined the use of DMA and the Pico PIO cores. Getting the Pico to output a composite video signal was a good candidate for that. 10 | 11 | I decided the project had legs so updated the original mono version to add colour. This required a rewrite of my original demo and new hardware, yet both versions can still be built from this code. And both versions support a simple serial graphics terminal for homebrew 8-bit systems like my BSX and the RC2014. The capabilities of that may be expanded in future. 12 | 13 | And I've also created PCB layouts for both versions. Files are available for the mono version, with the colour version to follow once I've tested it. 14 | 15 | ### Versions 16 | There are two versions of the hardware and firmware 17 | 18 | #### Mono 19 | - 16 shades of grey 20 | 21 | Uses a single resistor ladder to convert a 5-bit binary number on the Pico GPIO to a voltage between 0v and 1v. This is used to directly drive the composite video signal. 22 | 23 | #### Colour 24 | - 256 colours (322 RGB) 25 | 26 | Uses three resistor ladders and an AD724 PAL/NTSC encoder chip. 27 | 28 | Both monochrome and colour versions of the circut support resolutions of 256x192, 320x192 and 640x192. 29 | 30 | For more details, see [my blog post detailing the build](http://www.breakintoprogram.co.uk/projects/pico/composite-video-on-the-raspberry-pi-pico). 31 | 32 | ### Hardware 33 | 34 | #### Build on a breadboard 35 | The breadboard files can be found in the folder [/hardware/breadboard/](/hardware/breadboard/) 36 | 37 | #### Build on a PCB 38 | The PCB gerber files, schematics and BOM can be found in the folder [/hardware/pcb/](/hardware/pcb/) 39 | 40 | ### Firmware 41 | The code to use two PIO state machines. The first state machine creates a blank PAL(ish) video signal using a handful of 32-byte hard-coded lookup tables fetched via DMA, each pulse being 2us wide. At the point where pixel data needs to be injected into that signal a second state machine kicks in and writes that data out to the GPIO pins at a higher frequency, whilst the first state machine executes NOPs. 42 | 43 | This allows for the horizontal video resolution to be tweaked independantly of the sync pulses, and is required for the colour version. 44 | 45 | The firmware includes a handful of extras to get folk started on projects based upon this; some graphics primitives, and a handful of rolling demos. 46 | 47 | The graphics primitives include: 48 | 49 | - Plot and Line 50 | - Circle, Triangle and Polygon (Wireframe and Filled) 51 | - Print 52 | - Clear Screen, Vsync and Border 53 | - Scroll and Blit 54 | 55 | There is also a terminal mode. This requires a serial connection to the UART on pins 12 and 13 of the Pico. Remember the Pico is not 5V tolerant; the sample circuits uses a resistor divider circuit to drop a 5V TTL serial connection to 3.3V. This is very much work-in-progress. 56 | 57 | ### Configuring for compilation 58 | In config.h there are a couple of compilation options: 59 | - opt_colour: 60 | - Set to 0 to build firmware for the mono version 61 | - Set to 1 to build firmware for the colour version 62 | - opt_terminal 63 | - Set to 0 to just run rolling demos 64 | - Set to 1 to build the serial terminal 65 | 66 | ### Building 67 | Make sure that you have set an environment variable to the Pico SDK, substituting the path with the location of the SDK files on your computer. 68 | ```shell 69 | export PICO_SDK_PATH=/home/dev/pico/pico-sdk-2.0.0 70 | ``` 71 | 72 | To build, execute these commands inside the `build` folder. 73 | ```shell 74 | cmake .. 75 | make 76 | ``` 77 | This should create the file `pico-mposite.uf2` that you can upload to your Pico. -------------------------------------------------------------------------------- /bitmap.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Demonstration Bitmap 3 | // Author: Dean Belfield 4 | // Created: 01/02/2022 5 | // Last Updated: 26/02/2024 6 | // 7 | // Description: 8 | // 9 | // Modinfo: 10 | // 20/02/2022: Removed reference to graphics.h 11 | // 26/09/2024: Externed variables 12 | 13 | #pragma once 14 | 15 | #include "config.h" 16 | 17 | extern unsigned char const sample_bitmap[192][256]; 18 | -------------------------------------------------------------------------------- /charset.c: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Spectrum Character Set 3 | // Author: 4 | // Created: 01/02/2022 5 | // Last Updated: 01/02/2022 6 | // 7 | // Description: 8 | // 9 | // Taken from the 48K rom 10 | // 11 | // Modinfo: 12 | // 13 | 14 | unsigned char const charset[768] = { 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 17 | 0x00, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x24, 0x7e, 0x24, 0x24, 0x7e, 0x24, 0x00, 19 | 0x00, 0x08, 0x3e, 0x28, 0x3e, 0x0a, 0x3e, 0x08, 20 | 0x00, 0x62, 0x64, 0x08, 0x10, 0x26, 0x46, 0x00, 21 | 0x00, 0x10, 0x28, 0x10, 0x2a, 0x44, 0x3a, 0x00, 22 | 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 23 | 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, 0x04, 0x00, 24 | 0x00, 0x20, 0x10, 0x10, 0x10, 0x10, 0x20, 0x00, 25 | 0x00, 0x00, 0x14, 0x08, 0x3e, 0x08, 0x14, 0x00, 26 | 0x00, 0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x10, 28 | 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 30 | 0x00, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 31 | 0x00, 0x3c, 0x46, 0x4a, 0x52, 0x62, 0x3c, 0x00, 32 | 0x00, 0x18, 0x28, 0x08, 0x08, 0x08, 0x3e, 0x00, 33 | 0x00, 0x3c, 0x42, 0x02, 0x3c, 0x40, 0x7e, 0x00, 34 | 0x00, 0x3c, 0x42, 0x0c, 0x02, 0x42, 0x3c, 0x00, 35 | 0x00, 0x08, 0x18, 0x28, 0x48, 0x7e, 0x08, 0x00, 36 | 0x00, 0x7e, 0x40, 0x7c, 0x02, 0x42, 0x3c, 0x00, 37 | 0x00, 0x3c, 0x40, 0x7c, 0x42, 0x42, 0x3c, 0x00, 38 | 0x00, 0x7e, 0x02, 0x04, 0x08, 0x10, 0x10, 0x00, 39 | 0x00, 0x3c, 0x42, 0x3c, 0x42, 0x42, 0x3c, 0x00, 40 | 0x00, 0x3c, 0x42, 0x42, 0x3e, 0x02, 0x3c, 0x00, 41 | 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 42 | 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x10, 0x20, 43 | 0x00, 0x00, 0x04, 0x08, 0x10, 0x08, 0x04, 0x00, 44 | 0x00, 0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 45 | 0x00, 0x00, 0x10, 0x08, 0x04, 0x08, 0x10, 0x00, 46 | 0x00, 0x3c, 0x42, 0x04, 0x08, 0x00, 0x08, 0x00, 47 | 0x00, 0x3c, 0x4a, 0x56, 0x5e, 0x40, 0x3c, 0x00, 48 | 0x00, 0x3c, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x00, 49 | 0x00, 0x7c, 0x42, 0x7c, 0x42, 0x42, 0x7c, 0x00, 50 | 0x00, 0x3c, 0x42, 0x40, 0x40, 0x42, 0x3c, 0x00, 51 | 0x00, 0x78, 0x44, 0x42, 0x42, 0x44, 0x78, 0x00, 52 | 0x00, 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x7e, 0x00, 53 | 0x00, 0x7e, 0x40, 0x7c, 0x40, 0x40, 0x40, 0x00, 54 | 0x00, 0x3c, 0x42, 0x40, 0x4e, 0x42, 0x3c, 0x00, 55 | 0x00, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42, 0x00, 56 | 0x00, 0x3e, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 57 | 0x00, 0x02, 0x02, 0x02, 0x42, 0x42, 0x3c, 0x00, 58 | 0x00, 0x44, 0x48, 0x70, 0x48, 0x44, 0x42, 0x00, 59 | 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7e, 0x00, 60 | 0x00, 0x42, 0x66, 0x5a, 0x42, 0x42, 0x42, 0x00, 61 | 0x00, 0x42, 0x62, 0x52, 0x4a, 0x46, 0x42, 0x00, 62 | 0x00, 0x3c, 0x42, 0x42, 0x42, 0x42, 0x3c, 0x00, 63 | 0x00, 0x7c, 0x42, 0x42, 0x7c, 0x40, 0x40, 0x00, 64 | 0x00, 0x3c, 0x42, 0x42, 0x52, 0x4a, 0x3c, 0x00, 65 | 0x00, 0x7c, 0x42, 0x42, 0x7c, 0x44, 0x42, 0x00, 66 | 0x00, 0x3c, 0x40, 0x3c, 0x02, 0x42, 0x3c, 0x00, 67 | 0x00, 0xfe, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 68 | 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c, 0x00, 69 | 0x00, 0x42, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, 70 | 0x00, 0x42, 0x42, 0x42, 0x42, 0x5a, 0x24, 0x00, 71 | 0x00, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x00, 72 | 0x00, 0x82, 0x44, 0x28, 0x10, 0x10, 0x10, 0x00, 73 | 0x00, 0x7e, 0x04, 0x08, 0x10, 0x20, 0x7e, 0x00, 74 | 0x00, 0x0e, 0x08, 0x08, 0x08, 0x08, 0x0e, 0x00, 75 | 0x00, 0x00, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 76 | 0x00, 0x70, 0x10, 0x10, 0x10, 0x10, 0x70, 0x00, 77 | 0x00, 0x10, 0x38, 0x54, 0x10, 0x10, 0x10, 0x00, 78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 79 | 0x00, 0x1c, 0x22, 0x78, 0x20, 0x20, 0x7e, 0x00, 80 | 0x00, 0x00, 0x38, 0x04, 0x3c, 0x44, 0x3c, 0x00, 81 | 0x00, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x3c, 0x00, 82 | 0x00, 0x00, 0x1c, 0x20, 0x20, 0x20, 0x1c, 0x00, 83 | 0x00, 0x04, 0x04, 0x3c, 0x44, 0x44, 0x3c, 0x00, 84 | 0x00, 0x00, 0x38, 0x44, 0x78, 0x40, 0x3c, 0x00, 85 | 0x00, 0x0c, 0x10, 0x18, 0x10, 0x10, 0x10, 0x00, 86 | 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x38, 87 | 0x00, 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x00, 88 | 0x00, 0x10, 0x00, 0x30, 0x10, 0x10, 0x38, 0x00, 89 | 0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x24, 0x18, 90 | 0x00, 0x20, 0x28, 0x30, 0x30, 0x28, 0x24, 0x00, 91 | 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0c, 0x00, 92 | 0x00, 0x00, 0x68, 0x54, 0x54, 0x54, 0x54, 0x00, 93 | 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, 94 | 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 95 | 0x00, 0x00, 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 96 | 0x00, 0x00, 0x3c, 0x44, 0x44, 0x3c, 0x04, 0x06, 97 | 0x00, 0x00, 0x1c, 0x20, 0x20, 0x20, 0x20, 0x00, 98 | 0x00, 0x00, 0x38, 0x40, 0x38, 0x04, 0x78, 0x00, 99 | 0x00, 0x10, 0x38, 0x10, 0x10, 0x10, 0x0c, 0x00, 100 | 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 101 | 0x00, 0x00, 0x44, 0x44, 0x28, 0x28, 0x10, 0x00, 102 | 0x00, 0x00, 0x44, 0x54, 0x54, 0x54, 0x28, 0x00, 103 | 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 104 | 0x00, 0x00, 0x44, 0x44, 0x44, 0x3c, 0x04, 0x38, 105 | 0x00, 0x00, 0x7c, 0x08, 0x10, 0x20, 0x7c, 0x00, 106 | 0x00, 0x0e, 0x08, 0x30, 0x08, 0x08, 0x0e, 0x00, 107 | 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 108 | 0x00, 0x70, 0x10, 0x0c, 0x10, 0x10, 0x70, 0x00, 109 | 0x00, 0x14, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 110 | 0x3c, 0x42, 0x99, 0xa1, 0xa1, 0x99, 0x42, 0x3c 111 | }; 112 | -------------------------------------------------------------------------------- /charset.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Spectrum Character Set 3 | // Author: 4 | // Created: 01/02/2022 5 | // Last Updated: 26/09/2024 6 | // 7 | // Description: 8 | // 9 | // Taken from the Spectrum 48K rom 10 | // 11 | // Modinfo: 12 | // 26/09/2024: Externed variables 13 | 14 | #pragma once 15 | 16 | extern unsigned char const charset[768]; 17 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Defines 3 | // Author: Dean Belfield 4 | // Created: 01/03/2022 5 | // Last Updated: 27/09/2024 6 | // 7 | // Modinfo: 8 | // 27//09/2024: Version 1.3 9 | 10 | #pragma once 11 | 12 | #define version "1.3" 13 | #define opt_colour 0 // Set to 0 for monochrome board, 1 for colour board 14 | #define opt_terminal 0 // Set to 1 to just run the terminal software after boot screen -------------------------------------------------------------------------------- /cvideo.c: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Video Output 3 | // Description: The composite video stuff 4 | // Author: Dean Belfield 5 | // Created: 26/01/2021 6 | // Last Updated: 27/09/2024 7 | // 8 | // Modinfo: 9 | // 15/02/2021: Border buffers now have horizontal sync pulse set correctly 10 | // Decreased RAM usage by updating the image buffer scanline on the fly during the horizontal interrupt 11 | // Fixed logic error in cvideo_dma_handler; initial memcpy done twice 12 | // 31/01/2022: Refactored to use less memory 13 | // Split the video generation into two state machines; sync and data 14 | // 01/02/2022: Added a handful of graphics primitives 15 | // 02/02/2022: Split main loop out into main.c 16 | // 04/02/2022: Added set_border 17 | // 05/02/2022: Added support for colour, fixed bug in video generation 18 | // 20/02/2022: Bitmap is now dynamically allocated; added two higher resolution video modes 19 | // 25/02/2022: Lengthened HSYNC to 12us 20 | // 27/09/2024: PIO state machines now started simultaneously 21 | 22 | #include 23 | 24 | #include "memory.h" 25 | #include "pico/stdlib.h" 26 | 27 | #include "hardware/pio.h" 28 | #include "hardware/dma.h" 29 | #include "hardware/irq.h" 30 | 31 | #include "charset.h" // The character set 32 | #include "cvideo.h" 33 | #include "graphics.h" 34 | #include "cvideo_sync.pio.h" // The assembled PIO code 35 | #include "cvideo_data.pio.h" 36 | 37 | PIO pio_0; // The PIO that this uses 38 | uint offset_0; // Program offsets 39 | uint offset_1; 40 | 41 | uint dma_channel_0; // DMA channel for transferring sync data to PIO 42 | uint dma_channel_1; // DMA channel for transferring pixel data data to PIO 43 | uint vline; // Current PAL(ish) video line being processed 44 | uint bline; // Line in the bitmap to fetch 45 | 46 | uint vblank_count; // Vblank counter 47 | 48 | unsigned char * bitmap; // Bitmap buffer 49 | 50 | int width = 256; // Bitmap dimensions 51 | int height = 192; 52 | 53 | /* 54 | * The sync tables consist of 32 entries, each one corresponding to a 2us slice of the 64us 55 | * horizontal sync. The value 0x00 is reserved as a control byte for the horizontal sync; 56 | * cvideo_sync will not write to the GPIO for that block of 0x00's. 57 | * 58 | * All sync pulses are active low 59 | */ 60 | 61 | // Horizontal sync with gap for pixel data 62 | // 63 | unsigned short hsync[32] = { 64 | HSLO, HSLO, HSHI, HSHI, HSHI, HSHI, BORD, BORD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, BORD, BORD, BORD, 66 | }; 67 | 68 | // Horizontal sync for top and bottom borders 69 | // 70 | unsigned short border[32] = { 71 | HSLO, HSLO, HSHI, HSHI, HSHI, HSHI, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, 72 | BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, 73 | }; 74 | 75 | // Vertical sync (long/long) 76 | // 77 | unsigned short vsync_ll[32] = { 78 | VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSHI, // Long sync pulse 79 | VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSHI, // Long sync pulse 80 | }; 81 | 82 | // Vertical sync (short/short) 83 | // 84 | unsigned short vsync_ss[32] = { 85 | VSLO, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, // Short sync pulse 86 | VSLO, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, // Short sync pulse 87 | }; 88 | 89 | // Vertical sync (long/short) 90 | // 91 | unsigned short vsync_ls[32] = { 92 | VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSHI, // Long sync pulse 93 | VSLO, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, // Short sync pulse 94 | }; 95 | 96 | /* 97 | * The main routine sets up the whole shebang 98 | */ 99 | int initialise_cvideo(void) { 100 | pio_0 = pio0; // Assign the PIO 101 | 102 | // Load up the PIO programs 103 | // 104 | offset_0 = pio_add_program(pio_0, &cvideo_sync_program); 105 | offset_1 = pio_add_program(pio_0, &cvideo_data_program); 106 | 107 | dma_channel_0 = dma_claim_unused_channel(true); // Claim a DMA channel for the sync 108 | dma_channel_1 = dma_claim_unused_channel(true); // And one for the pixel data 109 | 110 | vline = 1; // Initialise the video scan line counter to 1 111 | bline = 0; // And the index into the bitmap pixel buffer to 0 112 | vblank_count = 0; // And the vblank counter 113 | 114 | // Initialise the first PIO (video sync) 115 | // 116 | pio_sm_set_enabled(pio_0, sm_sync, false); // Disable the PIO state machine 117 | pio_sm_clear_fifos(pio_0, sm_sync); // Clear the PIO FIFO buffers 118 | cvideo_sync_initialise_pio( // Initialise the PIO (function in cvideo.pio) 119 | pio_0, // The PIO to attach this state machine to 120 | sm_sync, // The state machine number 121 | offset_0, // And offset 122 | gpio_base, // Start pin in the GPIO 123 | gpio_count, // Number of pins 124 | piofreq_0 // State machine clock frequency 125 | ); 126 | cvideo_configure_pio_dma( // Configure the DMA 127 | pio_0, // The PIO to attach this DMA to 128 | sm_sync, // The state machine number 129 | dma_channel_0, // The DMA channel 130 | DMA_SIZE_16, // Size of each transfer 131 | 32, // Number of bytes to transfer 132 | cvideo_dma_handler // The DMA handler 133 | ); 134 | 135 | bitmap = malloc(width * height); // Allocate the bitmap memory 136 | 137 | // Initialise the second PIO (pixel data) 138 | // 139 | cvideo_data_initialise_pio( 140 | pio_0, 141 | sm_data, 142 | offset_1, 143 | gpio_base, 144 | gpio_count, 145 | piofreq_1_256 146 | ); 147 | 148 | // Initialise the DMA 149 | // 150 | cvideo_configure_pio_dma( 151 | pio_0, 152 | sm_data, 153 | dma_channel_1, // On DMA channel 1 154 | DMA_SIZE_8, // Size of each transfer 155 | width, // The bitmap width 156 | NULL // But there is no DMA interrupt for the pixel data 157 | ); 158 | 159 | irq_set_exclusive_handler( // Set up the PIO IRQ handler 160 | PIO0_IRQ_0, // The IRQ # 161 | cvideo_pio_handler // And handler routine 162 | ); 163 | pio0_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS; // Just for IRQ 0 (triggered by irq set 0 in PIO) 164 | irq_set_enabled(PIO0_IRQ_0, true); // Enable it 165 | 166 | set_border(0); // Set the border colour 167 | cls(0); // Clear the screen 168 | 169 | // Start the PIO state machines 170 | // 171 | pio_enable_sm_mask_in_sync(pio_0, (1u << sm_data) | (1u << sm_sync)); 172 | return 0; 173 | } 174 | 175 | // Set the graphics mode 176 | // mode - The graphics mode (0 = 256x192, 1 = 320 x 192, 2 = 640 x 192) 177 | // 178 | int set_mode(int mode) { 179 | double dfreq; 180 | 181 | wait_vblank(); 182 | 183 | switch(mode) { // Get the video mode 184 | case 1: 185 | width = 320; // Set screen width and 186 | dfreq = piofreq_1_320; // pixel dot frequency accordingly 187 | break; 188 | case 2: 189 | width = 640; 190 | dfreq = piofreq_1_640; 191 | break; 192 | default: 193 | width = 256; 194 | dfreq = piofreq_1_256; 195 | break; 196 | 197 | } 198 | 199 | if(bitmap != NULL) { 200 | free(bitmap); 201 | } 202 | bitmap = malloc(width * height); // Allocate the bitmap memory 203 | 204 | cvideo_configure_pio_dma( // Reconfigure the DMA 205 | pio_0, 206 | sm_data, 207 | dma_channel_1, // On DMA channel 1 208 | DMA_SIZE_8, // Size of each transfer 209 | width, // The bitmap width 210 | NULL // But there is no DMA interrupt for the pixel data 211 | ); 212 | 213 | pio_0->sm[sm_data].clkdiv = (uint32_t) (dfreq * (1 << 16)); 214 | 215 | return 0; 216 | } 217 | 218 | // Set the border colour 219 | // - colour: Border colour 220 | // 221 | void set_border(unsigned char colour) { 222 | if(colour > colour_max) { 223 | return; 224 | } 225 | unsigned short c = BORD | (colour_base + colour); 226 | 227 | for(int i = 6; i <32; i++) { // Skip the first three hsync values 228 | if(hsync[i] & BORD) { // If the border bit is set in the hsync 229 | hsync[i] = c; // Then write out the new colour (with the BORD bit set) 230 | } 231 | border[i] = c; // We can just write the values out to the border table 232 | } 233 | } 234 | 235 | // Wait for vblank 236 | // 237 | void wait_vblank(void) { 238 | uint c = vblank_count; // Get the current vblank count 239 | while(c == vblank_count) { // Wait until it changes 240 | sleep_us(4); // Need to sleep for a minimum of 4us 241 | } 242 | } 243 | 244 | // The PIO interrupt handler 245 | // This sets up the DMA for cvideo_data with pixel data and is triggered by the irq set 0 246 | // instruction at the end of the PIO 247 | // 248 | void cvideo_pio_handler(void) { 249 | if(bline >= height) { 250 | bline = 0; 251 | } 252 | dma_channel_set_read_addr(dma_channel_1, &bitmap[width * bline++], true); // Line up the next block of pixels 253 | hw_set_bits(&pio0->irq, 1u); // Reset the IRQ 254 | } 255 | 256 | // The DMA interrupt handler 257 | // This feeds the state machine cvideo_sync with data for the PAL(ish) video signal 258 | // 259 | void cvideo_dma_handler(void) { 260 | 261 | // Switch condition on the vertical scanline number (vline) 262 | // Each statement does a dma_channel_set_read_addr to point the PIO to the next data to output 263 | // 264 | switch(vline) { 265 | 266 | // First deal with the vertical sync scanlines 267 | // Also on scanline 3, preload the first pixel buffer scanline 268 | // 269 | case 1 ... 2: 270 | dma_channel_set_read_addr(dma_channel_0, vsync_ll, true); 271 | break; 272 | case 3: 273 | dma_channel_set_read_addr(dma_channel_0, vsync_ls, true); 274 | break; 275 | case 4 ... 5: 276 | case 310 ... 312: 277 | dma_channel_set_read_addr(dma_channel_0, vsync_ss, true); 278 | break; 279 | 280 | // Then the border scanlines 281 | // 282 | case 6 ... 68: 283 | case 261 ... 309: 284 | dma_channel_set_read_addr(dma_channel_0, border, true); 285 | break; 286 | 287 | // Now point the dma at the first buffer for the pixel data, 288 | // and preload the data for the next scanline 289 | // 290 | default: 291 | dma_channel_set_read_addr(dma_channel_0, hsync, true); 292 | break; 293 | } 294 | 295 | // Increment and wrap the counters 296 | // 297 | if(vline++ >= 312) { // If we've gone past the bottom scanline then 298 | vline = 1; // Reset the scanline counter 299 | vblank_count++; 300 | } 301 | 302 | // Finally, clear the interrupt request ready for the next horizontal sync interrupt 303 | // 304 | dma_hw->ints0 = 1u << dma_channel_0; 305 | } 306 | 307 | // Configure the PIO DMA 308 | // Parameters: 309 | // - pio: The PIO to attach this to 310 | // - sm: The state machine number 311 | // - dma_channel: The DMA channel 312 | // - transfer_size: Size of each DMA bus transfer (DMA_SIZE_8, DMA_SIZE_16 or DMA_SIZE_32) 313 | // - buffer_size_words: Number of bytes to transfer 314 | // - handler: Address of the interrupt handler, or NULL for no interrupts 315 | // 316 | void cvideo_configure_pio_dma(PIO pio, uint sm, uint dma_channel, uint transfer_size, size_t buffer_size, irq_handler_t handler) { 317 | dma_channel_config c = dma_channel_get_default_config(dma_channel); 318 | channel_config_set_transfer_data_size(&c, transfer_size); 319 | channel_config_set_read_increment(&c, true); 320 | channel_config_set_dreq(&c, pio_get_dreq(pio, sm, true)); 321 | dma_channel_configure(dma_channel, &c, 322 | &pio->txf[sm], // Destination pointer 323 | NULL, // Source pointer 324 | buffer_size, // Size of buffer 325 | true // Start flag (true = start immediately) 326 | ); 327 | if(handler != NULL) { 328 | dma_channel_set_irq0_enabled(dma_channel, true); 329 | irq_set_exclusive_handler(DMA_IRQ_0, handler); 330 | irq_set_enabled(DMA_IRQ_0, true); 331 | } 332 | } -------------------------------------------------------------------------------- /cvideo.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Video Output 3 | // Author: Dean Belfield 4 | // Created: 26/01/2021 5 | // Last Updated: 26/09/2024 6 | // 7 | // Modinfo: 8 | // 31/01/2022: Tweaks to reflect code changes 9 | // 02/02/2022: Added initialise_cvideo 10 | // 04/02/2022: Added set_border 11 | // 05/02/2022: Added support for colour composite board 12 | // 20/02/2022: Bitmap is now dynamically allocated 13 | // 01/03/2022: Tweaked sync parameters for colour version 14 | // 26/09/2024: Externed variables 15 | 16 | #pragma once 17 | 18 | #include "config.h" 19 | 20 | #define piofreq_0 5.25f // Clock frequence of state machine for PIO handling sync 21 | #define piofreq_1_256 7.00f // Clock frequency of state machine for PIO handling pixel data at various resolutions 22 | #define piofreq_1_320 5.60f 23 | #define piofreq_1_640 2.80f 24 | 25 | #define sm_sync 0 // State machine number in the PIO for the sync data 26 | #define sm_data 1 // State machine number in the PIO for the pixel data 27 | 28 | #if opt_colour == 0 29 | #define colour_base 0x10 // Start colour; for monochrome version this relates to black level voltage 30 | #define colour_max 0x0f // Last available colour 31 | #define HSLO 0x0001 32 | #define HSHI 0x000d 33 | #define VSLO HSLO 34 | #define VSHI HSHI 35 | #define BORD 0x8000 36 | #define gpio_base 0 37 | #define gpio_count 5 38 | #else 39 | #define colour_base 0x00 40 | #define colour_max 0xFF 41 | #define HSLO 0x4200 42 | #define HSHI 0x4000 43 | #define VSLO 0x4100 44 | #define VSHI 0x4000 45 | #define BORD 0x8000 46 | #define gpio_base 0 47 | #define gpio_count 10 48 | #endif 49 | 50 | extern unsigned char * bitmap; 51 | 52 | extern int width; 53 | extern int height; 54 | 55 | int initialise_cvideo(void); 56 | int set_mode(int mode); 57 | 58 | void cvideo_configure_pio_dma(PIO pio, uint sm, uint dma_channel, uint transfer_size, size_t buffer_size, irq_handler_t handler); 59 | 60 | void cvideo_pio_handler(void); 61 | void cvideo_dma_handler(void); 62 | 63 | void wait_vblank(void); 64 | void set_border(unsigned char colour); 65 | -------------------------------------------------------------------------------- /cvideo_data.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Pico-mposite PIO code 3 | ; Description: Generate a burst of pixels to inject into the PAL(ish) video sync scaffold 4 | ; Author: Dean Belfield 5 | ; Created: 31/01/2021 6 | ; Last Updated: 26/09/2024 7 | ; 8 | ; Modinfo: 9 | ; 01/02/2022: Tweaked comments 10 | ; 04/02/2022: Border colour reset improved 11 | ; 07/02/2022: Added wrap back in 12 | ; 24/02/2022: Removed sm_config_set_set_pins and sm_config_set_in_pins 13 | ; 26/09/2024: Set input pins for non-zero pin_base 14 | 15 | .program cvideo_data 16 | 17 | .wrap_target 18 | 19 | wait 1 irq 4 ; Wait for IRQ 4 from cvideo_sync 20 | mov Y, pins ; The GPIO pins are still set to border colour, so store that in Y 21 | 22 | loop: 23 | out X, 8 ; Get 8 bits from DMA via Output Shift Register (OSR) to X 24 | mov pins, X ; Move X to pins as set up in cvideo_initialise_pio 25 | jmp !OSRE loop ; Loop unti no more pixel data 26 | mov pins, Y ; Reset the border colour 27 | irq set 0 ; Trigger the PIO interrupt to set up the DMA for the next scanline 28 | 29 | .wrap ; Loop back to wrap_target 30 | 31 | % c-sdk { 32 | // 33 | // Initialise the PIO 34 | // Parameters: 35 | // - pio: The PIO to attach this to 36 | // - sm: The state machine number 37 | // - offset: The instruction memory offset the program is loaded at 38 | // - pin_base: The number of the first GPIO pin to use in the PIO 39 | // - pin_count: The number of consecutive GPIO pins to write to 40 | // - freq: The frequency of the PIO state machine 41 | // 42 | void cvideo_data_initialise_pio(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, double freq) { 43 | for(uint i=pin_base; ism[sm].clkdiv = (uint32_t) (freq * (1 << 16)); 53 | } 54 | %} 55 | -------------------------------------------------------------------------------- /cvideo_sync.pio: -------------------------------------------------------------------------------- 1 | ; 2 | ; Title: Pico-mposite PIO code 3 | ; Description: Generate a PAL(ish) video signal scaffold 4 | ; Author: Dean Belfield 5 | ; Created: 26/01/2021 6 | ; Last Updated: 24/02/2022 7 | ; 8 | ; Modinfo: 9 | ; 15/02/2021: Refactored to use wrap 10 | ; 31/01/2022: Modified to use 32 byte sync tables 11 | ; 05/02/2022: Modified to use 16 bit values in sync tables, tweaked timings 12 | ; 24/02/2022: Removed sm_config_set_set_pins 13 | 14 | .program cvideo_sync 15 | 16 | .wrap_target ; This loop needs to last ~64us 17 | 18 | loop_1: 19 | out X, 16 [15] ; Get 16 bits from DMA via Output Shift Register (OSR) to X 20 | jmp !X skip_1 ; If the data byte is 0, then skip to the next loop 21 | mov pins, X [14] ; Move data to pins as set up in cvideo_initialise_pio 22 | jmp loop_1 [15] ; Loop 23 | 24 | skip_1: 25 | irq set 4 [30] ; Set the IRQ to trigger the state machine cvideo_data 26 | 27 | loop_2: 28 | out X, 16 [15] ; Get 16 bits from DMA 29 | nop [15] ; Just padding to time this out 30 | jmp !X loop_2 [15] ; Loop until we hit data again 31 | 32 | .wrap ; Loop back to wrap_target 33 | 34 | % c-sdk { 35 | // 36 | // Initialise the PIO 37 | // Parameters: 38 | // - pio: The PIO to attach this to 39 | // - sm: The state machine number 40 | // - offset: The instruction memory offset the program is loaded at 41 | // - pin_base: The number of the first GPIO pin to use in the PIO 42 | // - pin_count: The number of consecutive GPIO pins to write to 43 | // - freq: The frequency of the PIO state machine 44 | // 45 | void cvideo_sync_initialise_pio(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, double freq) { 46 | for(uint i=pin_base; ism[sm].clkdiv = (uint32_t) (freq * (1 << 16)); 55 | } 56 | %} -------------------------------------------------------------------------------- /graphics.c: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Graphics Primitives 3 | // Description: A hacked-together composite video output for the Raspberry Pi Pico 4 | // Author: Dean Belfield 5 | // Created: 01/02/2021 6 | // Last Updated: 02/03/2022 7 | // 8 | // Modinfo: 9 | // 03/02/2022: Fixed bug in print_char, typos in comments 10 | // 05/02/2022: Added support for colour 11 | // 07/02/2022: Added filled primitives 12 | // 08/07/2022: Optimised filled circle drawing 13 | // 20/02/2022: Added scroll_up, bitmap now initialised in cvideo.c 14 | // 02/03/2022: Added blit 15 | 16 | #include 17 | 18 | #include "memory.h" 19 | 20 | #include "hardware/pio.h" 21 | #include "hardware/dma.h" 22 | #include "hardware/irq.h" 23 | 24 | #include "charset.h" // The character set 25 | #include "cvideo.h" 26 | 27 | #include "graphics.h" 28 | 29 | // Clear the screen 30 | // - c: Background colour to fill screen with 31 | // 32 | void cls(unsigned char c) { 33 | memset(bitmap, colour_base + c, height * width); 34 | } 35 | 36 | // Scroll the screen up 37 | // - c: Background colour to fill blank area with 38 | // - rows: Number of pixel rows to scroll up by 39 | // 40 | void scroll_up(unsigned char c, int rows) { 41 | memcpy(bitmap, &bitmap[width * rows], (height - rows) * width); 42 | memset(&bitmap[width * (height - rows)], colour_base + c, rows * width); 43 | } 44 | 45 | // Print a character 46 | // - x: X position on screen (pixels) 47 | // - y: Y position on screen (pixels) 48 | // - c: Character to print (ASCII 32 to 127) 49 | // - bc: Background colour 50 | // - fc: Foreground colour 51 | // 52 | void print_char(int x, int y, int c, unsigned char bc, unsigned char fc) { 53 | int char_index; 54 | unsigned char * ptr; 55 | 56 | if(c >= 32 && c < 128) { 57 | char_index = (c - 32) * 8; 58 | ptr = &bitmap[width * y + x + 7]; 59 | for(int row = 0; row < 8; row++) { 60 | unsigned char data = charset[char_index + row]; 61 | for(int bit = 0; bit < 8; bit ++) { 62 | *(ptr- bit) = data & 1 << bit ? colour_base + fc : colour_base + bc; 63 | } 64 | ptr += width; 65 | } 66 | } 67 | } 68 | 69 | // Print a string 70 | // - x: X position on screen (pixels) 71 | // - y: Y position on screen (pixels) 72 | // - s: Zero terminated string 73 | // - bc: Background 74 | // - fc: Foreground colour ( 75 | // 76 | void print_string(int x, int y, char *s, unsigned char bc, unsigned char fc) { 77 | for(int i = 0; i < strlen(s); i++) { 78 | print_char(x + i * 8, y, s[i], bc, fc); 79 | } 80 | } 81 | 82 | // Plot a point 83 | // - x: X position on screen 84 | // - y: Y position on screen 85 | // - c: Pixel colour 86 | // 87 | void plot(int x, int y, unsigned char c) { 88 | if(x >= 0 && x < width && y >= 0 && y < height) { 89 | bitmap[width * y + x] = colour_base + c; 90 | } 91 | } 92 | 93 | // Draw a line 94 | // - x1, y1: Coordinates of start point 95 | // - x2, y2: Coordinates of last point 96 | // - c: Pixel colour 97 | // 98 | // Draw a line 99 | // - x1, y1: Coordinates of start point 100 | // - x2, y2: Coordinates of last point 101 | // - c: Pixel colour 102 | // 103 | void draw_line(int x1, int y1, int x2, int y2, unsigned char c) { 104 | int dx, dy, sx, sy, e, xp, yp, i; 105 | 106 | dx = x2 - x1; // Horizontal length 107 | dy = y2 - y1; // Vertical length 108 | 109 | sx = (dx > 0) - (dx < 0); // Sign DX 110 | sy = (dy > 0) - (dy < 0); // Sign DY 111 | 112 | dx *= sx; // Abs DX 113 | dy *= sy; // Abs DY 114 | 115 | if(dx == 0 && dy == 0) { // For zero length lines 116 | plot(xp, yp, c); // Just plot a point 117 | return; 118 | } 119 | 120 | xp = x1; // Start pixel coordinates 121 | yp = y1; 122 | 123 | if(dx > dy) { // If the line is longer than taller... 124 | e = dx >> 1; 125 | dy -= dx; 126 | for(i = 0; i < dx; i++) { 127 | plot(xp, yp, c); 128 | e += dy; 129 | if(e > 0) { 130 | yp += sy; 131 | } 132 | else { 133 | e += dx; 134 | } 135 | xp += sx; 136 | } 137 | } 138 | else { // If the line is taller than longer... 139 | e = dy >> 1; 140 | dx -= dy; 141 | for(i = 0; i < dy; i++) { 142 | plot(xp, yp, c); 143 | e += dx; 144 | if(e > 0) { 145 | xp += sx; 146 | } 147 | else { 148 | e += dy; 149 | } 150 | yp += sy; 151 | } 152 | } 153 | } 154 | 155 | // Draw a circle 156 | // - x: X Coordinate 157 | // - y: Y Coordinate 158 | // - r: Radius 159 | // - c: Pixel colour 160 | // - filled: Set to false to draw wireframe, true for filled 161 | // 162 | void draw_circle(int x, int y, int r, unsigned char c, bool filled) { 163 | int xp = 0; 164 | int yp = r; 165 | int d = 3 - r * 2; 166 | while (yp >= xp) { // The circle routine only plots an octant 167 | if(filled) { 168 | draw_horizontal_line(y + xp, x - yp, x + yp, c); 169 | draw_horizontal_line(y - xp, x - yp, x + yp, c); 170 | } 171 | else { 172 | plot(x + xp, y + yp, c); // So use symmetry to draw a complete circle 173 | plot(x + xp, y - yp, c); 174 | plot(x - xp, y + yp, c); 175 | plot(x - xp, y - yp, c); 176 | plot(x + yp, y + xp, c); 177 | plot(x + yp, y - xp, c); 178 | plot(x - yp, y + xp, c); 179 | plot(x - yp, y - xp, c); 180 | } 181 | xp++; 182 | if (d > 0) { 183 | if(filled) { // We only need to draw these bits when the Y coordinate changes 184 | draw_horizontal_line(y + yp, x - xp, x + xp, c); 185 | draw_horizontal_line(y - yp, x - xp, x + xp, c); 186 | } 187 | yp--; 188 | d += 4 * (xp - yp) + 10; 189 | } 190 | else { 191 | d += 4 * xp + 6; 192 | } 193 | } 194 | } 195 | 196 | // Draw a polygon 197 | // - x1 ... x3: X coordinates 198 | // - y1 ... y3: Y coordinates 199 | // - c: Pixel colour 200 | // - filled: Set to false to draw wireframe, true for filled 201 | // 202 | void draw_polygon(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, unsigned char c, bool filled) { 203 | if(filled) { 204 | draw_triangle(x1, y1, x2, y2, x4, y4, c, true); 205 | draw_triangle(x2, y2, x3, y3, x4, y4, c, true); 206 | } 207 | else { 208 | draw_line(x1, y1, x2, y2, c); 209 | draw_line(x2, y2, x3, y3, c); 210 | draw_line(x3, y3, x4, y4, c); 211 | draw_line(x4, y4, x1, y1, c); 212 | } 213 | } 214 | 215 | // Draw a triangle 216 | // - x1 ... x3: X coordinates 217 | // - y1 ... y3: Y coordinates 218 | // - c: Pixel colour 219 | // - filled: Set to false to draw wireframe, true for filled 220 | // 221 | void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, unsigned char c, bool filled) { 222 | if(!filled) { 223 | draw_line(x1, y1, x2, y2, c); 224 | draw_line(x2, y2, x3, y3, c); 225 | draw_line(x3, y3, x1, y1, c); 226 | return; 227 | } 228 | 229 | struct Line line1; 230 | struct Line line2; 231 | 232 | int i; 233 | 234 | // 235 | // First sort the points by Y ascending 236 | // 237 | if(y1 > y3) { // a 238 | swap(&x1, &x3); 239 | swap(&y1, &y3); 240 | } 241 | if(y1 > y2) { 242 | swap(&x1, &x2); //b 243 | swap(&y1, &y2); 244 | } 245 | if(y2 > y3) { // c 246 | swap(&x2, &x3); 247 | swap(&y2, &y3); 248 | } 249 | 250 | // Now draw the longest line (a->c) and (a->b) together 251 | // 252 | init_line(&line1, x1, y1, x3, y3); // a 253 | init_line(&line2, x1, y1, x2, y2); // b 254 | 255 | while(line2.h > 0) { 256 | draw_horizontal_line(line1.yp, line1.xp, line2.xp, c); 257 | step_line(&line1); 258 | step_line(&line2); 259 | line1.yp++; 260 | line2.h--; 261 | } 262 | 263 | // And finish with line b->c 264 | // 265 | init_line(&line2, x2, y2, x3, y3); // c 266 | 267 | while(line2.h > 0) { 268 | draw_horizontal_line(line1.yp, line1.xp, line2.xp, c); 269 | step_line(&line1); 270 | step_line(&line2); 271 | line1.yp++; 272 | line2.h--; 273 | } 274 | 275 | draw_horizontal_line(line1.yp, line1.xp, line2.xp, c); 276 | } 277 | 278 | // Optimised horizontal line 279 | // - y1: Y coordinate 280 | // - x1: First X coordinate 281 | // - X2: Second X coordinate 282 | // - c: Colour 283 | // 284 | void draw_horizontal_line(int y1, int x1, int x2, int c) { 285 | if(x1 > x2) { // Always draw the line from left to right 286 | swap(&x2, &x1); 287 | } 288 | if(x1 < 0) { // If x1 < 0 289 | if(x2 < 0) { // If x2 is also < 0 290 | return; // Don't need to draw 291 | } 292 | x1 = 0; // Clip x1 to 0 293 | } 294 | if(x2 > width) { // if x2 > width 295 | if(x1 > width) { // if x1 is also > width 296 | return; // Don't need to draw 297 | } 298 | x2 = width; // Clip x2 to width 299 | } 300 | // for(int i = x1; i <= x2; i++) { // This is slow... 301 | // plot(i, y1, c); // so we'll use memset to fill the line in memory 302 | // } 303 | memset(&bitmap[width * y1 + x1], colour_base + c, x2 - x1 + 1); 304 | } 305 | 306 | // Swap two numbers 307 | // - a: Integer 1 308 | // - b: Integer 2 309 | // 310 | void swap(int *a, int *b) { 311 | int t = *a; 312 | *a = *b; 313 | *b = t; 314 | } 315 | 316 | // Iniitialise a line structure 317 | // Used for filled triangle routine 318 | // - line: Pointer to a line structure 319 | // - x1, y1: Coordinates of start point 320 | // - x2, y2: Coordinates of last point 321 | // 322 | void init_line(struct Line *line, int x1, int y1, int x2, int y2) { 323 | line->dx = x2 - x1; 324 | line->dy = y2 - y1; 325 | line->sx = (line->dx > 0) - (line->dx < 0); // Sign DX 326 | line->sy = (line->dy > 0) - (line->dy < 0); // Sign DY 327 | line->dx *= line->sx; // Abs DX 328 | line->dy *= line->sy; // Abs DY 329 | line->xp = x1; // Start pixel coordinates 330 | line->yp = y1; 331 | line->quad = line->dx > line->dy; // True if line is longer than taller 332 | line->h = line->dy; 333 | if(line->quad) { 334 | line->e = line->dx >> 1; 335 | line->dy -= line->dx; 336 | } 337 | else { 338 | line->e = line->dy >> 1; 339 | line->dx -= line->dy; 340 | } 341 | } 342 | 343 | // Do one step of the line 344 | // Used for filled triangle routine 345 | // - line: Pointer to a line structure 346 | // Returns 347 | // - true if the line has incremented onto the next pixel row 348 | // 349 | void step_line(struct Line *line) { 350 | if(line->quad) { 351 | while(true) { 352 | line->e += line->dy; 353 | if(line->e > 0) { 354 | line->xp += line->sx; 355 | break; 356 | } 357 | else { 358 | line->xp += line->sx; 359 | line->e += line->dx; 360 | } 361 | } 362 | } 363 | else { 364 | line->e += line->dx; 365 | if(line->e > 0) { 366 | line->xp += line->sx; 367 | } 368 | else { 369 | line->e += line->dy; 370 | } 371 | } 372 | } 373 | 374 | // Blit (non-scaling) 375 | // - data: Source data 376 | // - sx, sy: Source X and Y in array of pixels 377 | // - sw, sh: Source width and height 378 | // - dx, dy: Destination X and Y on screen 379 | // 380 | void blit(const void * data, int sx, int sy, int sw, int sh, int dx, int dy) { 381 | void * src = (void *)data + (sw * sy) + sx; 382 | void * dst = bitmap + (width * dy) + dx; 383 | for(int i = 0; i < sh; i++) { 384 | memcpy(dst, src, sw); 385 | dst += width; 386 | src += sw; 387 | } 388 | } -------------------------------------------------------------------------------- /graphics.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Graphics Primitives 3 | // Author: Dean Belfield 4 | // Created: 01/02/2022 5 | // Last Updated: 02/03/2022 6 | // 7 | // Modinfo: 8 | // 07/02/2022: Added support for filled primitives 9 | // 20/02/2022: Added scroll_up, bitmap now initialised in cvideo.c 10 | // 02/03/2022: Added blit 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | #define rgb(r,g,b) (((b&6)<<5)|(g<<3)|r) 17 | 18 | struct Line { 19 | int dx, dy, sx, sy, e, xp, yp, h; 20 | bool quad; 21 | }; 22 | 23 | void cls(unsigned char c); 24 | void scroll_up(unsigned char c, int rows); 25 | 26 | void print_char(int x, int y, int c, unsigned char bc, unsigned char fc); 27 | void print_string(int x, int y, char *s, unsigned char bc, unsigned char fc); 28 | 29 | void plot(int x, int y, unsigned char c); 30 | void draw_line(int x1, int y1, int x2, int y2, unsigned char c); 31 | void draw_horizontal_line(int y1, int x1, int x2, int c); 32 | void draw_circle(int x, int y, int r, unsigned char c, bool filled); 33 | void draw_polygon(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, unsigned char c, bool filled); 34 | void draw_triangle(int x1, int y1, int x2, int y2, int x3, int y3, unsigned char c, bool filled); 35 | 36 | void swap(int *a, int *b); 37 | void init_line(struct Line *line, int x1, int y1, int x2, int y2); 38 | void step_line(struct Line *line); 39 | 40 | void blit(const void * data, int sx, int sy, int sw, int sh, int dx, int dy); 41 | -------------------------------------------------------------------------------- /hardware/breadboard/colour/Composite Video Colour #1.0.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/breadboard/colour/Composite Video Colour #1.0.fzz -------------------------------------------------------------------------------- /hardware/breadboard/colour/Composite Video Colour Breadboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/breadboard/colour/Composite Video Colour Breadboard.png -------------------------------------------------------------------------------- /hardware/breadboard/colour/Composite Video Colour Schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/breadboard/colour/Composite Video Colour Schematic.png -------------------------------------------------------------------------------- /hardware/breadboard/mono/Composite Video Mono #1.0.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/breadboard/mono/Composite Video Mono #1.0.fzz -------------------------------------------------------------------------------- /hardware/breadboard/mono/Composite Video Mono #1.1.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/breadboard/mono/Composite Video Mono #1.1.fzz -------------------------------------------------------------------------------- /hardware/breadboard/mono/Composite Video Mono Breadboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/breadboard/mono/Composite Video Mono Breadboard.png -------------------------------------------------------------------------------- /hardware/breadboard/mono/Composite Video Mono Schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/breadboard/mono/Composite Video Mono Schematic.png -------------------------------------------------------------------------------- /hardware/pcb/bom/Pico-mposite.csv: -------------------------------------------------------------------------------- 1 | "Reference","Value","Datasheet","Footprint","Qty","DNP" 2 | "D1","1N5817","http://www.vishay.com/docs/88525/1n5817.pdf","Diode_THT:D_DO-41_SOD81_P10.16mm_Horizontal","1","" 3 | "J1","PJRAN1X1U04X","","Connector_Imported:SWITCHCRAFT_PJRAN1X1U04X","1","" 4 | "J2,J3","Conn_01x02_Socket","~","-- mixed values --","2","" 5 | "R1","110Ω","~","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1","" 6 | "R2,R8,R9,R10,R11","440Ω","~","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","5","" 7 | "R3,R4,R5,R6,R7","220Ω","~","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","5","" 8 | "R12","3.3kΩ","~","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1","" 9 | "R13","2.2kΩ","~","Resistor_THT:R_Axial_DIN0207_L6.3mm_D2.5mm_P10.16mm_Horizontal","1","" 10 | "SW1","SW_Push","~","Button_Switch_THT:SW_PUSH_6mm_H8mm","1","" 11 | "U1","Pico","","MCU_RaspberryPi_and_Boards:RPi_Pico_SMD_TH","1","" 12 | -------------------------------------------------------------------------------- /hardware/pcb/gerber/Pico-mposite.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/pcb/gerber/Pico-mposite.zip -------------------------------------------------------------------------------- /hardware/pcb/schematic/Pico-mposite.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/hardware/pcb/schematic/Pico-mposite.pdf -------------------------------------------------------------------------------- /images/bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/bitmap.png -------------------------------------------------------------------------------- /images/breadboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/breadboard.jpeg -------------------------------------------------------------------------------- /images/demo_cube.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/demo_cube.jpeg -------------------------------------------------------------------------------- /images/demo_cube_colour.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/demo_cube_colour.jpeg -------------------------------------------------------------------------------- /images/demo_mandlebrot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/demo_mandlebrot.jpeg -------------------------------------------------------------------------------- /images/demo_mandlebrot_colour.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/demo_mandlebrot_colour.jpeg -------------------------------------------------------------------------------- /images/demo_splash.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/demo_splash.jpeg -------------------------------------------------------------------------------- /images/demo_splash_colour.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/demo_splash_colour.jpeg -------------------------------------------------------------------------------- /images/video_output.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/breakintoprogram/pico-mposite/8b9f2998ef2dbf94b42c980072f49782639c1819/images/video_output.jpeg -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Video Output 3 | // Description: A hacked-together composite video output for the Raspberry Pi Pico 4 | // Author: Dean Belfield 5 | // Created: 02/02/2021 6 | // Last Updated: 01/03/2022 7 | // 8 | // Modinfo: 9 | // 04/02/2022: Demos now set the border colour 10 | // 05/02/2022: Added support for colour 11 | // 20/02/2022: Added demo_terminal 12 | // 01/03/2022: Added colour to the demos 13 | 14 | #include 15 | #include 16 | 17 | #include "memory.h" 18 | #include "pico/stdlib.h" 19 | 20 | #include "hardware/pio.h" 21 | #include "hardware/dma.h" 22 | #include "hardware/irq.h" 23 | 24 | #include "bitmap.h" 25 | #include "graphics.h" 26 | #include "cvideo.h" 27 | #include "terminal.h" 28 | 29 | #include "main.h" 30 | 31 | // Cube corner points 32 | // 33 | int shape_pts[8][8] = { 34 | { -20, 20, 20 }, 35 | { 20, 20, 20 }, 36 | { -20, -20, 20 }, 37 | { 20, -20, 20 }, 38 | { -20, 20, -20 }, 39 | { 20, 20, -20 }, 40 | { -20, -20, -20 }, 41 | { 20, -20, -20 }, 42 | }; 43 | 44 | // Cube polygons (lines between corners + colour) 45 | // 46 | #if opt_colour == 0 47 | 48 | int shape[6][5] = { 49 | { 0,1,3,2, 1 }, 50 | { 6,7,5,4, 2 }, 51 | { 1,5,7,3, 3 }, 52 | { 2,6,4,0, 4 }, 53 | { 2,3,7,6, 5 }, 54 | { 0,4,5,1, 6 }, 55 | }; 56 | 57 | unsigned char col_mandelbrot[16] = { 58 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 59 | }; 60 | 61 | #else 62 | 63 | int shape[6][5] = { 64 | { 0,1,3,2, col_red }, 65 | { 6,7,5,4, col_green }, 66 | { 1,5,7,3, col_blue }, 67 | { 2,6,4,0, col_magenta }, 68 | { 2,3,7,6, col_cyan }, 69 | { 0,4,5,1, col_yellow }, 70 | }; 71 | 72 | unsigned char col_mandelbrot[16] = { 73 | rgb(0,0,0), 74 | rgb(1,0,0), 75 | rgb(2,0,0), 76 | rgb(3,0,0), 77 | rgb(4,0,0), 78 | rgb(5,0,0), 79 | rgb(6,0,0), 80 | rgb(7,0,0), 81 | rgb(7,0,0), 82 | rgb(7,1,0), 83 | rgb(7,2,0), 84 | rgb(7,3,0), 85 | rgb(7,4,0), 86 | rgb(7,5,0), 87 | rgb(7,6,0), 88 | rgb(7,7,0) 89 | }; 90 | 91 | #endif 92 | 93 | // The main loop 94 | // 95 | int main() { 96 | initialise_cvideo(); // Initialise the composite video stuff 97 | // 98 | // And then just loop doing your thing 99 | // 100 | while(true) { 101 | demo_splash(); 102 | #if opt_terminal == 1 103 | demo_terminal(); 104 | #else 105 | demo_spinny_cube(); 106 | demo_mandlebrot(); 107 | #endif 108 | } 109 | } 110 | 111 | void demo_splash() { 112 | cls(0); 113 | blit(&sample_bitmap, 0, 0, 256, 192, (width - 256) / 2, 0); 114 | #if opt_colour == 0 115 | set_border(col_black); 116 | print_string(60, 8, "Pico-mposite v"version, col_black, col_white); 117 | print_string(64, 24, "By Dean Belfield", col_black, col_white); 118 | print_string(24, 180, "www.breakintoprogram.co.uk", col_black, col_white); 119 | #else 120 | set_border(col_blue); 121 | print_string(60, 8, "Pico-mposite v"version, col_blue, col_white); 122 | print_string(64, 24, "By Dean Belfield", col_green, col_white); 123 | print_string(24, 180, "www.breakintoprogram.co.uk", col_red, col_white); 124 | #endif 125 | sleep_ms(10000); 126 | } 127 | 128 | // Demo: Spinning 3D cube 129 | // 130 | void demo_spinny_cube() { 131 | double the = 0; 132 | double psi = 0; 133 | double phi = 0; 134 | 135 | set_border(col_white); 136 | 137 | for(int i = 0; i < 1000; i++) { 138 | wait_vblank(); 139 | cls(col_white); 140 | #if opt_colour == 0 141 | print_string(0, 180, "Pico-mposite Graphics Primitives", 15, 0); 142 | #else 143 | print_string(0, 180, "Pico-mposite Graphics Primitives", col_blue, col_white); 144 | #endif 145 | draw_circle(128, 96, 80, i >= 500 ? col_grey : col_black, i >= 500); 146 | render_spinny_cube(0, 0, the, psi, phi, i >= 500); 147 | the += 0.01; 148 | psi += 0.03; 149 | phi -= 0.02; 150 | } 151 | } 152 | 153 | // Demo: Mandlebrot set 154 | // 155 | void demo_mandlebrot() { 156 | cls(col_black); 157 | set_border(col_black); 158 | render_mandlebrot(); 159 | #if opt_colour == 0 160 | print_string(16, 180, "Pico-mposite Mandlebrot Demo", 0, 15); 161 | #else 162 | print_string(16, 180, "Pico-mposite Mandlebrot Demo", col_red, col_white); 163 | #endif 164 | sleep_ms(10000); 165 | } 166 | 167 | // Draw a 3D cube 168 | // xo: X position in view 169 | // yo: Y position in view 170 | // the, psi, phi: Rotation angles 171 | // colour: Pixel colour 172 | // 173 | void render_spinny_cube(int xo, int yo, double the, double psi, double phi, bool filled) { 174 | int i; 175 | double x, y, z, xx, yy, zz; 176 | int a[8], b[8]; 177 | int xd =0, yd = 0; 178 | int x1, y1, x2, y2, x3, y3, x4, y4; 179 | double sd = 512, od = 256; 180 | 181 | for(i = 0; i < 8 ; i++) { 182 | xx = shape_pts[i][0]; 183 | yy = shape_pts[i][1]; 184 | zz = shape_pts[i][2]; 185 | 186 | y = yy * cos(phi) - zz * sin(phi); 187 | zz = yy * sin(phi) + zz * cos(phi); 188 | x = xx * cos(the) - zz * sin(the); 189 | zz = xx * sin(the) + zz * cos(the); 190 | xx = x * cos(psi) - y * sin(psi); 191 | yy = x * sin(psi) + y * cos(psi); 192 | 193 | xx += xo + xd; 194 | yy += yo + yd; 195 | 196 | a[i] = 128 + xx * sd / (od - zz); 197 | b[i] = 96 + yy * sd / (od - zz); 198 | } 199 | 200 | for(i = 0; i < 6; i++) { 201 | x1 = a[shape[i][0]]; 202 | x2 = a[shape[i][1]]; 203 | x3 = a[shape[i][2]]; 204 | x4 = a[shape[i][3]]; 205 | y1 = b[shape[i][0]]; 206 | y2 = b[shape[i][1]]; 207 | y3 = b[shape[i][2]]; 208 | y4 = b[shape[i][3]]; 209 | 210 | if(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2) <= 0) { 211 | draw_polygon(x1, y1, x2, y2, x3, y3, x4, y4, shape[i][4], filled); 212 | } 213 | } 214 | } 215 | 216 | // Draw a Mandlebrot 217 | // 218 | void render_mandlebrot(void) { 219 | int k = 0; 220 | float i , j , r , x , y; 221 | for(y = 0; y < height; y++) { 222 | for(x = 0; x < width; x++) { 223 | plot(x, y, col_mandelbrot[k]); 224 | for(i = k = r = 0; j = r * r - i * i - 2 + x / 100, i = 2 * r * i + (y - 96) / 70, j * j + i * i < 11 && k++ < 15; r = j); 225 | } 226 | } 227 | } 228 | 229 | // Simple terminal output from UART 230 | // 231 | void demo_terminal(void) { 232 | initialise_terminal(); // Initialise the UART 233 | set_mode(2); 234 | set_border(col_terminal_border); 235 | cls(col_terminal_bg); 236 | terminal(); // And do the terminal 237 | set_mode(0); 238 | } -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Video Output 3 | // Author: Dean Belfield 4 | // Created: 26/01/2021 5 | // Last Updated: 01/03/2022 6 | // 7 | // Modinfo: 8 | // 20/02/2022: Added demo_terminal 9 | // 01/03/2022: Added colour to the demos 10 | 11 | #pragma once 12 | 13 | #include "config.h" 14 | 15 | #if opt_colour == 0 16 | #define col_black 0x00 17 | #define col_grey 0x0C 18 | #define col_white 0x0F 19 | #else 20 | #define col_black rgb(0,0,0) 21 | #define col_grey rgb(6,6,6) 22 | #define col_white rgb(7,7,7) 23 | #define col_red rgb(7,0,0) 24 | #define col_green rgb(0,7,0) 25 | #define col_blue rgb(0,0,7) 26 | #define col_yellow rgb(7,7,0) 27 | #define col_magenta rgb(7,0,7) 28 | #define col_cyan rgb(0,7,7) 29 | #endif 30 | 31 | int main(void); 32 | 33 | void demo_splash(void); 34 | void demo_spinny_cube(void); 35 | void demo_mandlebrot(void); 36 | void demo_terminal(void); 37 | 38 | void render_spinny_cube(int xo, int yo, double the, double psi, double phi, bool filled); 39 | void render_mandlebrot(void); -------------------------------------------------------------------------------- /pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /terminal.c: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Terminal Emulation 3 | // Description: Simple terminal emulation 4 | // Author: Dean Belfield 5 | // Created: 19/02/2022 6 | // Last Updated: 03/03/2022 7 | // 8 | // Modinfo: 9 | // 03/03/2022: Added colour 10 | 11 | #include "pico/stdlib.h" 12 | 13 | #include "hardware/pio.h" 14 | #include "hardware/dma.h" 15 | #include "hardware/irq.h" 16 | #include "hardware/uart.h" 17 | 18 | #include "cvideo.h" 19 | #include "graphics.h" 20 | 21 | #include "terminal.h" 22 | 23 | int terminal_x; 24 | int terminal_y; 25 | 26 | void initialise_terminal(void) { 27 | uart_init(uart0, 115200); 28 | gpio_set_function(12, GPIO_FUNC_UART); // TX 29 | gpio_set_function(13, GPIO_FUNC_UART); // RX 30 | } 31 | 32 | // Handle carriage returns 33 | // 34 | void cr(void) { 35 | terminal_x = 0; 36 | terminal_y += 8; 37 | if(terminal_y >= height) { 38 | terminal_y -= 8; 39 | scroll_up(15, 8); 40 | } 41 | } 42 | 43 | // Advance one character position 44 | // 45 | void fs(void) { 46 | terminal_x += 8; 47 | if(terminal_x >= width) { 48 | cr(); 49 | } 50 | } 51 | 52 | // Backspace 53 | // 54 | void bs(void) { 55 | terminal_x -= 8; 56 | if(terminal_x < 0) { 57 | terminal_x = 0; 58 | } 59 | } 60 | 61 | // The terminal loop 62 | // 63 | void terminal(void) { 64 | terminal_x = 0; 65 | terminal_y = 0; 66 | while(true) { 67 | print_char(terminal_x, terminal_y, '_', col_terminal_cursor, col_terminal_bg); 68 | char c = uart_getc(uart0); // Get the character from the UART (blocking) 69 | if(c >= 32) { // Output printable characters 70 | print_char(terminal_x, terminal_y, c, col_terminal_fg, col_terminal_bg); 71 | fs(); 72 | } 73 | else { // Else deal with the important control characters 74 | print_char(terminal_x, terminal_y, ' ', col_terminal_fg, col_terminal_bg); 75 | switch(c) { 76 | case 0x08: // Backspace 77 | bs(); 78 | break; 79 | case 0x0D: // CR 80 | cr(); 81 | break; 82 | case 0x03: // Ctrl+C // Quit the terminal loop on these codes 83 | case 0x1B: // ESC 84 | return; 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /terminal.h: -------------------------------------------------------------------------------- 1 | // 2 | // Title: Pico-mposite Terminal Emulation 3 | // Author: Dean Belfield 4 | // Created: 19/02/2022 5 | // Last Updated: 03/03/2022 6 | // 7 | // Modinfo: 8 | // 03/03/2022: Added colour 9 | 10 | #pragma once 11 | 12 | #include "config.h" 13 | 14 | #if opt_colour == 0 15 | #define col_terminal_bg 15 16 | #define col_terminal_fg 0 17 | #define col_terminal_border 7 18 | #define col_terminal_cursor 15 19 | #else 20 | #define col_terminal_bg rgb(0,0,0) 21 | #define col_terminal_fg rgb(7,7,0) 22 | #define col_terminal_border rgb(0,0,2) 23 | #define col_terminal_cursor rgb(7,7,7) 24 | #endif 25 | 26 | void initialise_terminal(void); 27 | void terminal(void); --------------------------------------------------------------------------------