This program was my attempt to draw fractals on a VGA screen using Prof. Hunter Adam's VGA Library for the RaspberryPi Pico. The resources for the project include the C SDK User Guide, the RP2040 Datasheet and Prof. Hunter's website. I also used the Nature of Code series by Daniel Shiffman as a starting point for studying fractals.
According to Nature of Code's fractal page, the term fractal (from the Latin fractus, meaning “broken”) was coined by the mathematician Benoit Mandelbrot in 1975. In his seminal work “The Fractal Geometry of Nature,” he defines a fractal as “a rough or fragmented geometric shape that can be split into parts, each of which is (at least approximately) a reduced-size copy of the whole.” In short, a fractal is a shape which when divided into various parts, each part can represent the figure as a whole.
The repeated application of a rule to successive results is known as recursion. One of the most famous applications for recursion is the calculation of factorical. The factorial of a natural number is defined as:
\begin{align} n! &= n \times (n-1)! \\ 0! &= 1 \end{align}For instance, solving for 5! looks like: \begin{align} 5! &= 5 \times 4! \\ 5! &= 5 \times 4 \times 3! \\ 5! &= 5 \times 4 \times 3 \times 2! \\ 5! &= 5 \times 4 \times 3 \times 2 \times 1! \\ 5! &= 5 \times 4 \times 3 \times 2 \times 1 \\ 5! &= 120 \end{align}
Note: Recursions must always have a base case and that too at a reasonable depth, else it will cause a stack overflow error.
/*
* Parth Sarthi Sharma (pss242@cornell.edu)
* Code based on examples from Raspberry Pi Foundation.
* This code is an implementation of a tree fractal
* on the Raspberry Pi Pico to draw a pattern. The pattern is developed
* by recursively calling a function by reducing a parameter until a base condition is met.
*/
#include <stdio.h> //The standard C library
#include <math.h> //The standard math library
#include "pico/stdlib.h" //Standard library for Pico
#include "hardware/pio.h" //The hardware PIO library
#include "hardware/dma.h" //The hardware DMA library
#include "pico/time.h" //The pico time library
#include "hardware/gpio.h" //The hardware GPIO library
#include "pico/multicore.h" //The pico multicore library
#include "vga_graphics.h" //The graphics library
#define HEIGHT 480 //Height of the VGA screen
#define WIDTH 640 //Width of the VGA screen
#define PI 3.14159265358 //The value of PI
#define INC_ANGLE_PIN 2 //GPIO Pin to increase the angle
#define DEC_ANGLE_PIN 3 //GPIO Pin to decrease the angle
volatile float angle = PI / 4; //The default angle
volatile char resetFlag = 1; //Reset flag to reset the image once the angle changes
void drawTree1(float x1, float y1, float x2, float y2, float theta, float thetaOrig){ //Function to draw the right part of the tree
drawLine(x1, y1, x2, y2, WHITE); //Draw a white line
float length = sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2)); //Calculate the length of the line
length *= 0.66; //Reduce the length of the new lines to be drawn
if(length > 5){ //If the length is greater than 5
float perp = length * sin(theta); //Calculate the perpendicular
float base = length * cos(theta); //Calculate the base
drawTree1(x2, y2, x2 + perp, y2 - base, theta - thetaOrig, thetaOrig); //Draw the left branches
drawTree1(x2, y2, x2 + perp, y2 - base, theta + thetaOrig, thetaOrig); //Draw the right branches
}
}
void drawTree2(float x1, float y1, float x2, float y2, float theta, float thetaOrig){ //Function to draw the left part of the tree
drawLine(x1, y1, x2, y2, WHITE); //Draw a white line
float length = sqrt(pow((x2 - x1), 2) + pow((y2 - y1), 2)); //Calculate the length of the line
length *= 0.66; //Reduce the length of the new lines to be drawn
if(length > 5){ //If the length is greater than 5
float perp = length * sin(theta); //Calculate the perpendicular
float base = length * cos(theta); //Calculate the base
drawTree2(x2, y2, x2 - perp, y2 - base, theta - thetaOrig, thetaOrig); //Draw the left branches
drawTree2(x2, y2, x2 - perp, y2 - base, theta + thetaOrig, thetaOrig); //Draw the right branches
}
}
void changeAngle(uint gpio, uint32_t events) { //The GPIO callback function to change the angle
if(gpio == INC_ANGLE_PIN){ //If the GPIO pin is INC_ANGLE_PIN
if(angle < PI){ //If the angle is less than PI
angle += 0.01; //Increment the angle by 0.01
}
}
else{ //If the GPIO pin is DEC_ANGLE_PIN
if(angle > 0){ //If the angle is greater than 0
angle -= 0.01; //Decrement the angle by 0.01
}
}
resetFlag = 1; //Set the reset flag
}
int main(){ //The program running on core 0
stdio_init_all(); //Initialize all of the present standard stdio types that are linked into the binary
initVGA(); //Initialize the VGA screen and functions
gpio_set_irq_enabled_with_callback(DEC_ANGLE_PIN, GPIO_IRQ_EDGE_FALL, true, &changeAngle); //Enable interrupts for the GPIO pin
gpio_set_irq_enabled_with_callback(INC_ANGLE_PIN, GPIO_IRQ_EDGE_FALL, true, &changeAngle); //Enable interrupts for the GPIO pin
while(1){ //While eternity
while(!resetFlag); //Sit here until the reset flag is set
fillRect(100, 200, 380, 280, BLACK); //Clear the tree
drawTree1(WIDTH / 2, 480, WIDTH / 2, 380, angle, angle); //Draw the right tree
drawTree2(WIDTH / 2, 480, WIDTH / 2, 380, angle, angle); //Draw the left tree
resetFlag = 0; //Clear the reset flag
}
}
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(TreeFractal-project)
pico_sdk_init()
add_executable(TreeFractal)
pico_enable_stdio_usb(TreeFractal 1)
pico_enable_stdio_uart(TreeFractal 1)
pico_generate_pio_header(TreeFractal ${CMAKE_CURRENT_LIST_DIR}/hsync.pio)
pico_generate_pio_header(TreeFractal ${CMAKE_CURRENT_LIST_DIR}/vsync.pio)
pico_generate_pio_header(TreeFractal ${CMAKE_CURRENT_LIST_DIR}/rgb.pio)
target_sources(TreeFractal PRIVATE TreeFractal.c vga_graphics.c)
target_link_libraries(TreeFractal PRIVATE pico_stdlib hardware_pio hardware_dma hardware_adc hardware_irq pico_time pico_multicore)
pico_add_extra_outputs(TreeFractal)