West Slope landscapes

Simulation of procedually generated tree growth on RP2040
ECE 5730 Final Project, Fall 2022
By Zechen Wang, Ziqing Liang, Xinyue Li


Demonstration Video


Introduction

Our project, west slope, is a combination of visual and audio applications, simulating beautiful landscapes of Conrell west slope on the VGA screen and taking music as inputs to generate trees with different colors and shapes. The idea of our project came from the impact of music on plants, which is playing music helps the plants grow faster. Though scientists are still arguing about whether this theory is true or not, we were inspired to play different music to plants and generate according features on screen, combining visual and auditory senses to achieve a beautiful and immersive experience. Also, driven by the affection for our gorgeous campus, we integrated the trees with characteristics of the west slope to promote the beautiful sceneries of our Ithaca campus. Our project has modified the Fast Fourier Transform and Barnsley fern codes from course demos and imitated the code structure of the Lindenmayer system from Github. The music we played was from Youtube. Reference details are listed in the appendix section.


High Level Design

Logical Structure

The west slope project uses a RP2040 microcontroller as the main host, a VGA screen to display and a microphone to receive audio signals. Our project consists of three major functional blocks: (1) the Lindenmayer system tree generation (2) the Barnsley fern tree generation (3) the Fast Fourier Transform calculation.

We generated trees based on the Barnsley fern and Lindenmayer system algorithm. We did modifications to the generation procedure of barnsley ferns: first generate a template for all leaves and perform transformations for multiple iterations to complete a tree. Instead of appearing on the screen all at once, the modification realized beautiful-looking leaf-by-leaf animation. Moreover, in order to increase diversity and elegance, we added randomness to the matrix parameters to create diverse shapes of the Barnsley fern, and implemented three different rules to generate multiple types of L-system trees.

We processed the audio input based on the FFT algorithm that we implemented in lab 1 to obtain frequency of the music and changed the tree growth speed according to it. We implemented signal processing to reduce noise in the collected signal, using a moving average buffer and intensity thresholding. In that way environmental noises would not be counted as valid inputs, and sudden sound impulses are also smoothed to avoid drastic changes in tree growth speeds. We utilized frequent context switching between the threads to update the growth speed in real time.

random image

Background Math

Fast Fourier Transform

The Fast Fourier Transform algorithm converts signals from the time domain into the frequency domain. The audio signal is sampled over a period of time and divided into its frequency components that are single sinusoidal oscillations at distinct frequencies with certain amplitude and phase. There are two important parameters related to the FFT algorithm: sampling rate and FFT size. Sample rate is the amount of time between each value in the input. According to Nyquist's sampling theorem, for a given frequency, the sample rate is required to be twice as large as the given frequency. We define the sampling rate as 10kHz, as the upper limit of the music frequency we played was 2kHz. FFT size is the number of output frequency bins of the FFT, indicating the number of input samples needed to calculate the FFT. We set the number of samples per FFT as 1024.

Barnsley fern

The Barnsley fern is an iterated function system, which is generated using an iterations function system. The system consists of several sets of equations, each of which represents a rotation, a translation, and a scaling. The equations are as follows.

random image

The parameters A, B, C, D represent a rotation and the magnitudes of them represent the scaling. The parameters E and F represent the linear translation of the point. Also, fewer points are necessary on some parts than others. For example, the stem does not need as many points as the leaf and we want the majority of the points to generate the leaf. Therefore, each function is also given a probability of being chosen. The parameters of a classic barnsley fern are as follows.

random image

Lindenmayer system

The Lindenmayer system exploits self-similarity and recursive procedure, applying a set of rules iteratively to model the growth processes of plant development. On each iteration, a string of characters is rewritten according to the replacement rules. Each symbol is given a graphical meaning. ‘F’ means move forward and draw a line. ‘-’ means turn left at a certain angle. ‘+’ means turn right at a certain angle. ‘[’means push state. ‘]’ means pop state. The rules that we adopted in the project are as follows.

random image random image

Program Design

A high-level description of the program

program structure image

The figure above shows the high-level organization of our software design. Since we want to draw trees that are generated from the Barnsley Fern algorithm and L-system algorithm simultaneously, we divide the implementation of these two algorithms into two threads. And we put the FFT part into another independent thread. We run the FFT and Barnsley Fern thread on core 1 and the L-system thread on core 0.

One of the tricky parts that we need to deal with when combining different threads is that we want to synchronize drawing the trees of different algorithms as much as possible. It is not necessary to force the trees from different threads to end together, but it is important that trees from different threads start simultaneously when a new scene starts. The blue color descriptions in the figure show how we achieve this by setting up two signal variables. Each thread waits for the “finish signal” from the other thread in order to start together for the next scene.

Another tricky part is the bouncing between the FFT thread and the Barnsley Fern thread on core1. During the growth of the tree, we want the speed of growth to dynamically adjust to the max frequency of the music, which needs an appropriate design on when to yield the thread to the other. The figure above shows our design using red color descriptions and red and black arrows. The arrow loop in red shows we implement FFT after each pair of leaves is drawn. The arrow loop in black shows that we still need to implement FFT if the Barnsley fern trees finish before the L-system trees so that the L-system trees growing speed can still match the change of frequency of the music. All the variables determining the position and shape of the Barnsley Fern trees have to be static so that the value will not be lost when the thread yields.

In the following part, our program design for FFT, Barnsley Fern, and L-system will be described.

FFT Thread

This thread mainly carries out the following processes:

We notice that because noise occurs, the max frequency sometimes changes rapidly, which affects the growth of the trees. We implement signal processing to reduce noise in the collected signal by using a moving average buffer and intensity thresholding. In this way, environmental noises would not be counted as valid inputs and sudden sound impulses are also smoothed to avoid drastic changes in tree growth speeds.

Program related to L-system

In this part of the program, we implement the algorithm of the L-system to generate trees. We refer to the code from https://github.com/telephil9/lsystem/blob/master/lsystem.c. Based on the l-system algorithm, trees are generated following the command of a string. Each character in that command string determines the action of the tree growth, for example, either rotate an angle or go forward. We need to generate the string before drawing the trees.

There are different types of trees, and each type is generated by one or more rules which determine what character needs to be substituted by what kind of string. We follow the rules to get the next generation of the string from the current string. We use Struct in C to store a single rule, and one rule will point to another rule if that type of tree needs multiple rules to generate. We write the rules for all types of trees and at the end of every scene, we use a random number to decide which type of tree will be drawn in the next scene. After the type of tree is decided, the rules will be updated accordingly.

Also, we implement a push-pop data structure in our program. Whenever we need to store the current state (the position or the angle), we need to malloc a space in the heap to push the state. Also, the heap space needs to be freed whenever we pop the top state.

In the while 1 loop in the thread, we:


The tricky parts in implementing the L-system contain dealing with characters, character arrays, memory, and pointers in C. Some bugs can be really tricky and we spent a lot of time debugging the generation of the command string and the push pop structure.

Barnsley Fern thread

We write this part of the program based on the demo code provided in the “Hunter-Adams-RP2040-demos”. In the demo code, the Barnsley Fern generates “at once” using the four transition functions, but we want to display the growing procedure of the tree, so we need to modify the program.

According to the Barnsley Fern algorithm, when applying the third transition function on each point (x,y) of a picture, each point will locate in a new position in the new picture, and the new picture looks like rotating the previous picture to the left. Similarly, when applying the fourth transition function on each point of a picture, the new picture looks like it rotates the previous picture to the right. In this way, we can turn the Barnsley Fern picture to the left and right leaves by applying the third and fourth transition functions to every point in the original Barnsley Fern points. We also modify some parameters in transition function two, so that the leaves will have different shapes.

The following shows the steps for generating Barnsley Fern trees:

for each tree:

Yield to the other thread if the l-system has not finished.

Program to drive VGA

The program goal for driving VGA is to generate the VSYNC, HSYNC, and three analog color signals that match the VGA protocol. We use the VGA driver code provided by the course to generate these signals.

At the highest level, we use the API functions provided in the “vga_graphics.c” to modify the pixel color character array that stores each pixel data with 3 bits corresponding to R, G, and B. Each character in the array contains data for two pixels.

The other part of the VGA drive program includes:

Things we tried that did not work

We successfully make all the functions work, and the demo worked just as we expected. But in the proposal, we mentioned some extra functions to add if time permits, and finally, we do not have time to add those functions. We thought about adding snow to the scene based on the “boids” experiment, but since we did not store any points that we drew, we are afraid that the extra snow might destroy the picture.


Hardware Design

VGA

VGA hardware image

Driving a VGA screen requires the RP2040 to output two digital signals (VSYNC and HSYNC) and three analog color signals (Blue, Green, and Red). According to the VGA protocol, HSYNC tells the screen when to move to a new row of pixels while VSYNC tells the screen when to start a new frame. Blue, Green, and Red are analog signals expected to be between 0-0.7V, and the voltage of each signal represents the intensity of the corresponding color for that particular pixel. Since the RP2040 output voltage is 3.3V and there is a 70-ohm resistor within the VGA to the ground, a 330-ohm divider should be added between RP2040 and VGA, so that the voltage into the VGA could be divided to meet the standard.

In our design, we map GPIO port 17 and GPIO port 16 to output the VSYNC and HSYNC signals respectively. We map GPIO port 18, GPIO port 19, and GPIO port 20 to output signals for Red, Green and Blue.

Microphone

Micro hardware image

We use the MAX4466 electric microphone to collect audio sounds from the air. We connect the GND and VCC of the microphone to the GND and VCC of the RP2040 and connect the output of the microphone to GPIO26 of the RP2040. The analog input gathered from the GPIO26 will be sent to the ADC FIFO, and the program will process the data in the ADC FIFO.


Results and Analysis

There are a few metrics that are worth mentioning in terms of program performance. In terms of speed of execution, we are not experiencing any delays during our animation, and in fact, we had too manually add some delays to control the speed of growth. With multithreading implemented in our program, we believe that the cost of the generation processes should be well within our computation limits for our current program design.

tree generation screen image


A second very important metrics is accuracy. When dealing with trees we are generating, better accuracy could mean that we do not want any weird-shaped trees appearing in our animations. In order to achieve that, we have extensively tested both generation systems and identified some of the parameters where we could carefully introduce some randomness.

The other component involving accuracy in our system is the microphone. Since the microphone itself is very vulnerable to environmental noises, we implemented some signal processing on the FFT results of input to get better frequency readings. With the sensor properly soldered and attached to our system, we manually set a threshold to eliminate frequency elements with low intensities. We also included a moving average buffer to smoothen any drastic changes between samples, so a few bad readings wouldn't affect the output too much overall.


Conclusions

After four weeks of work, we were able to achieve all the goals we initially set for this project. With a VGA screen connected to our RP2040 microcontroller, we can see procedurally generated trees growing on Cornell's west slope with randomness added in every iteration. With additional microphone input, we are able to control the speed of tree growth while listening to any of your favorite music, but be careful not to get too attracted and waste the entire afternoon just watching trees growing!

Since some of these features may be too resource-consuming, we may not be able to implement all of them on the RP2040 as extension of this project, but it may be interesting to try out a few.

Lastly, for intellectual property considerations, we fully respect the IP owned by other entities, and we will try our best to reference any code we borrowed from other individuals in code comments and in this report. There are also concept-related references that we used in this project, which indirectly or directly contributed to our system design. All referenced materials will be included in the appendix section. We have complied with all lab safety rules during our project development phase, and no public use/wireless transmission component was involved in this project.



Appendix A

The group approves this report for inclusion on the course website.

The group approves the video for inclusion on the course youtube channel.


Additional appendices

Parts List

Total: $0 additional cost

Group Members

Zechen Wang

zw652@cornell.edu

Participated in Barnsley Fern generation, linear transformation function design, multithreading, synchronization, digital signal processing on microphone inputs

Ziqing Liang

zl599@cornell.edu

Participated in Barnsley Fern generation and debugging for L-system implementation, multithreading and synchronization

Xinyue Li

xl767@cornell.edu

Participated in Barnsley Fern generation, L-system implementation, debugging multithreading and synchronization

References

[1] ECE 4760/5730 Course Demo Code By V. Hunter Adams
[2] Raspberry Pi Pico Datasheet
[3] Microphone Datasheet
[4] Fast Fourier Transform Reference
[5] FFT Sample Code
[6] Barnsley Fern Sample Code
[7] L-System Implementation Reference
[8] VGA Connection Guide
[9] RP2040 Protothread Documentation
[10] This HTML Template provided by Joseph Skovira

Code Appendix


/**
 * West Slope Project
 * By: Zechen Wang (zw652), Ziqing Liang (zl599), Xinyue Li (xl767)
 * Course: ECE 4760/5730, Cornell University
 * Instructor: Hunter Adams (vha3)
 * 
 * This program animates the growth of procedurally generated trees
 * growing on Cornell's West Slope.
 *
 * HARDWARE CONNECTIONS
 *  - GPIO 16 ---> VGA Hsync
 *  - GPIO 17 ---> VGA Vsync
 *  - GPIO 18 ---> 330 ohm resistor ---> VGA Red
 *  - GPIO 19 ---> 330 ohm resistor ---> VGA Green
 *  - GPIO 20 ---> 330 ohm resistor ---> VGA Blue
 *  - RP2040 GND ---> VGA GND
 *
 *  REFERENCES:
 *  Microphone FFT, Barnsley Fern: https://github.com/vha3/Hunter-Adams-RP2040-Demos
 *  L-System Impelementaion: https://github.com/telephil9/lsystem/blob/master/lsystem.c
 *
 */

// Include the VGA grahics library
#include "vga_graphics.h"
// Include standard libraries
#include 
#include 
#include 
#include 
// Include Pico libraries
#include "pico/stdlib.h"
#include "pico/divider.h"
#include "pico/multicore.h"
// Include hardware libraries
#include "hardware/pio.h"
#include "hardware/dma.h"
#include "hardware/adc.h"
#include "hardware/irq.h"
#include "hardware/clocks.h"
#include "hardware/pll.h"
// Include protothreads
#include "pt_cornell_rp2040_v1.h"

// === the fixed point macros ========================================
typedef signed int fix15 ;
#define multfix15(a,b) ((fix15)((((signed long long)(a))*((signed long long)(b)))>>15))
#define float2fix15(a) ((fix15)((a)*32768.0)) // 2^15
#define fix2float15(a) ((float)(a)/32768.0)
#define absfix15(a) abs(a) 
#define int2fix15(a) ((fix15)(a << 15))
#define fix2int15(a) ((int)(a >> 15))
#define char2fix15(a) (fix15)(((fix15)(a)) << 15)
#define divfix(a,b) (fix15)(div_s64s64( (((signed long long)(a)) << 15), ((signed long long)(b))))
#define sqrtfix(a) (float2fix15(sqrt(fix2float15(a))))
#define PI 3.1415926

/////////////////////////// ADC configuration ////////////////////////////////
// ADC Channel and pin
#define ADC_CHAN 0
#define ADC_PIN 26
// Number of samples per FFT
#define NUM_SAMPLES 1024
// Number of samples per FFT, minus 1
#define NUM_SAMPLES_M_1 1023
// Length of short (16 bits) minus log2 number of samples (10)
#define SHIFT_AMOUNT 6
// Log2 number of samples
#define LOG2_NUM_SAMPLES 10
// Sample rate (Hz)
#define Fs 10000.0
// ADC clock rate (unmutable!)
#define ADCCLK 48000000.0

// DMA channels for sampling ADC (VGA driver uses 0 and 1)
int sample_chan = 2 ;
int control_chan = 3 ;

// Max and min macros
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a> 1) & 0x5555) | ((m & 0x5555) << 1);
        // swap consecutive pairs
        mr = ((mr >> 2) & 0x3333) | ((mr & 0x3333) << 2);
        // swap nibbles ...
        mr = ((mr >> 4) & 0x0F0F) | ((mr & 0x0F0F) << 4);
        // swap bytes
        mr = ((mr >> 8) & 0x00FF) | ((mr & 0x00FF) << 8);
        // shift down mr
        mr >>= SHIFT_AMOUNT ;
        // don't swap that which has already been swapped
        if (mr<=m) continue ;
        // swap the bit-reveresed indices
        tr = fr[m] ;
        fr[m] = fr[mr] ;
        fr[mr] = tr ;
        ti = fi[m] ;
        fi[m] = fi[mr] ;
        fi[mr] = ti ;
    }
    //////////////////////////////////////////////////////////////////////////
    ////////////////////////// Danielson-Lanczos //////////////////////////////
    //////////////////////////////////////////////////////////////////////////
    // Adapted from code by:
    // Tom Roberts 11/8/89 and Malcolm Slaney 12/15/94 malcolm@interval.com
    // Length of the FFT's being combined (starts at 1)
    L = 1 ;
    // Log2 of number of samples, minus 1
    k = LOG2_NUM_SAMPLES - 1 ;
    // While the length of the FFT's being combined is less than the number
    // of gathered samples . . .
    while (L < NUM_SAMPLES) {
        // Determine the length of the FFT which will result from combining two FFT's
        istep = L<<1 ;
        // For each element in the FFT's that are being combined . . .
        for (m=0; m>= 1 ;                          // divide by two
            wi >>= 1 ;                          // divide by two
            // i gets the index of one of the FFT elements being combined
            for (i=m; i>1 ;
                qi = fi[i]>>1 ;
                // compute the new values at each index
                fr[j] = qr - tr ;
                fi[j] = qi - ti ;
                fr[i] = qr + tr ;
                fi[i] = qi + ti ;
            }
        }
        --k ;
        L = istep ;
    }
}

volatile bool finish_fern = false;
volatile bool finish_ls = false; 
volatile int sleeptime_ls;
volatile int sleeptime_fern = 200;
// Will be used to write dynamic text to screen
static char freqtext[40];

////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////// Stuff for Barnsley fern ////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
// Maximum number of iterations
#define max_count 1000

// State transition equations
fix15 f1y_coeff_1 = float2fix15(0.16) ; ;
#define F1x(a,b) 0 ;
#define F1y(a,b) ((fix15)(multfix15(a,f1y_coeff_1)))

fix15 f2x_coeff_1 = float2fix15(0.82) ;
fix15 f2x_coeff_2 = float2fix15(0.08) ;
#define F2x(a,b) ((fix15)(multfix15(f2x_coeff_1,a) + multfix15(f2x_coeff_2,b)))
fix15 f2y_coeff_1 = float2fix15(-0.08) ;
fix15 f2y_coeff_2 = float2fix15(0.85) ;
fix15 f2y_coeff_3 = float2fix15(1.6) ;
#define F2y(a,b) ((fix15)(multfix15(f2y_coeff_1,a) + multfix15(f2y_coeff_2,b) + f2y_coeff_3))

fix15 f3x_coeff_1 = float2fix15(0.2) ;
fix15 f3x_coeff_2 = float2fix15(0.26) ;
#define F3x(a,b) ((fix15)(multfix15(f3x_coeff_1,a) - multfix15(f3x_coeff_2,b)))
fix15 f3y_coeff_1 = float2fix15(0.23) ;
fix15 f3y_coeff_2 = float2fix15(0.22) ;
fix15 f3y_coeff_3 = float2fix15(1.6) ;
#define F3y(a,b) ((fix15)(multfix15(f3y_coeff_1,a) + multfix15(f3y_coeff_2,b) + f3y_coeff_3))

fix15 f4x_coeff_1 = float2fix15(-0.15) ;
fix15 f4x_coeff_2 = float2fix15(0.28) ;
#define F4x(a,b) ((fix15)(multfix15(f4x_coeff_1,a) + multfix15(f4x_coeff_2,b)))
fix15 f4y_coeff_1 = float2fix15(0.26) ;
fix15 f4y_coeff_2 = float2fix15(0.24) ;
fix15 f4y_coeff_3 = float2fix15(0.44) ;
#define F4y(a,b) ((fix15)(multfix15(f4y_coeff_1,a) + multfix15(f4y_coeff_2,b) + f4y_coeff_3))

// Probability thresholds (rearrange for faster execution, check lowest range last)
#define F1_THRESH 21474835
#define F2_THRESH 1846835936
#define F3_THRESH 1997159792

fix15 vga_scale = float2fix15(30) ;

//////////////////////////////////////////////////////////////////////////////////
////////////////////////////// L-System //////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
typedef struct Lsystem Lsystem;
typedef struct Rule Rule;
typedef struct State State;

struct Lsystem
{
  char* name;
  char* axiom;
  //pointer to the first rule
  Rule* rules;
  int linelen;
  float initangle;
  float leftangle;
  float rightangle;
};

struct Rule
{
    char  pred;
  char* succ;
  Rule* next;
};

struct State
{
  int x;
  int y;
  float angle;
  State* prev;
}Sta;

//void* emalloc(ulong size);
//global variables
struct Lsystem lsystem;
Lsystem* ls = &lsystem;

// state point to the State at top of stack
State* state = &Sta;

//current angle and position
int   x_cur;
int   y_cur;
float   angle_cur;
char *curgen;

void pushstate(void)
{
  //struct State s;
  State* ptr_s = (State*)malloc(sizeof(State));
  ptr_s->x = x_cur;
  ptr_s->y = y_cur;
  ptr_s->angle = angle_cur;
  ptr_s->prev = state;
  state = ptr_s;
}

void popstate(void)
{
  struct State s;
  State* ptr_s = &s;
  ptr_s = state;
  x_cur = ptr_s->x;
  y_cur = ptr_s->y;
  angle_cur = ptr_s->angle;
  state = state->prev;
  free(ptr_s);
}

//search in the rule successively for the substitute for char c
char* production(char c)
{
  Rule* r;
  //start from the pointer to the first rule (ls->rules)
  for (r = ls->rules; r != NULL; r = r->next)
    if (r->pred == c)
      return r->succ;
  return NULL;
}

//update current generation(the string s) and curgen 
void nextgen()
{
  char* s;
  char* p, * q;
  s = (char*)malloc(10000);
  s[0] = '\0';
  //int num = 0;
  p = curgen;
  for (p = curgen; *p != '\0'; p++) {
    //q is char* or NULL
    q = production(*p);
    if (q)
      strcat(s, q);
    //q is NULL 
    else {
      size_t len = strlen(s);
      s[len] = *p;
      s[len + 1] = '\0';
    }   
  }
  //update current generation 
  strcpy(curgen, s);
  free(s);
}

void forward(char color)
{
  int x1, y1;
  x1 = x_cur + ls->linelen * cos(angle_cur * (PI / 180.0));
  y1 = y_cur + ls->linelen * sin(angle_cur * (PI / 180.0));
  drawLine((short)x_cur, (short)y_cur, (short)x1, (short)y1, color);
  x_cur = x1;
  y_cur = y1;
}

void rotate(float angle_delta)
{
  angle_cur += angle_delta;
  if (angle_cur >= 360.0)
    angle_cur -= 360.0;
  if (angle_cur <= 0.0)
    angle_cur += 360.0;
}


// ==================================================
// === users audio input thread
// ==================================================
int fft_arr[3]; 
//FFT thread
static PT_THREAD (protothread_fft(struct pt *pt))
{
    // Indicate beginning of thread
    PT_BEGIN(pt) ;
    // Start the ADC channel
    dma_start_channel_mask((1u << sample_chan)) ;
    // Start the ADC
    adc_run(true) ;

    // Declare some static variables
    static int i ;                  // incrementing loop variable

    static fix15 max_fr ;           // temporary variable for max freq calculation
    static int max_fr_dex ;         // index of max frequency

    // Write some text to VGA
    setTextColor(WHITE) ;
    setCursor(65, 0) ;
    setTextSize(1) ;


    while(1) {
        // Wait for NUM_SAMPLES samples to be gathered
        // Measure wait time with timer. THIS IS BLOCKING
        dma_channel_wait_for_finish_blocking(sample_chan);

        // Copy/window elements into a fixed-point array
        for (i=0; i>1); i++) {  
            // get the approx magnitude
            fr[i] = abs(fr[i]); 
            fi[i] = abs(fi[i]);
            // reuse fr to hold magnitude
            fr[i] = max(fr[i], fi[i]) + 
                    multfix15(min(fr[i], fi[i]), zero_point_4); 

            // Keep track of maximum
            if (fr[i] > max_fr && i>4 && fr[i]>15000) {
                max_fr = fr[i] ;
                max_fr_dex = i ;
            }
        }
        // fillRect(10, 20, 176, 30, BLACK); // red box
        // setTextColor(WHITE);
        // char max_amp[40];
        // sprintf(max_amp, "%d", (int)max_fr) ;
        // setCursor(10, 20) ;
        // setTextSize(2) ;
        // writeString(max_amp) ;
        // Compute max frequency in Hz
        fft_arr[2] = fft_arr[1];
        fft_arr[1] = fft_arr[0];
        fft_arr[0] = max_fr_dex * (Fs/NUM_SAMPLES) ;

        // average buffer
        max_freqency = round((fft_arr[0] + fft_arr[1] + fft_arr[2]) / 3);
        fillRect(10, 20, 176, 30, BLACK); // black box
        sprintf(freqtext, "%d", (int)max_freqency) ;
        setCursor(10, 20) ;
        setTextSize(2) ;
        writeString(freqtext) ;    

        // // Update the FFT display
        // for (int i=5; i<(NUM_SAMPLES>>1); i++) {
        //     drawVLine(59+i, 50, 429, BLACK);
        //     height = fix2int15(multfix15(fr[i], int2fix15(36))) ;
        //     drawVLine(59+i, 479-height, height, WHITE);
        // }
        PT_YIELD_usec(1000);
    }
    PT_END(pt) ;
}

// thread for L-System trees
static PT_THREAD (protothread_lsys(struct pt *pt))
{
    // Mark beginning of thread
    PT_BEGIN(pt);

  //initialize rules
  struct Rule r1;
  struct Rule r2;
  Rule *ptr_r2 = &r2;
  Rule *ptr_r1 = &r1;
    // ----------------------
    // UNUSED RULES
  // ptr_r1->pred = 'X';
  // ptr_r1->succ = "F + [[X]- X] - F[-FX] + X";
  // ptr_r1->succ = "F-[[X]+X]+F[+FX]-X";
  // ptr_r2->pred = 'F';
  // ptr_r2->succ = "FF";
  // ptr_r2->next = NULL;
  // ptr_r1->next = ptr_r2;
  // //initialize L system
  // //ls is the pointer to struct Lsystem lsystem
  // ls->axiom = "X";
  // ls->linelen = 5;
  // ls->initangle = -90;
  // ls->leftangle = 30;
  // ls->rightangle = -30;
  // ls->rules = ptr_r1;
    // ----------------------
  ptr_r1->pred = 'F';
  //ptr_r1->succ = "F + [[X]- X] - F[-FX] + X";
  ptr_r1->succ = "F[+F]F[-F]F";
  ptr_r1->next = NULL;
  //initialize L system
  //ls is the pointer to struct Lsystem lsystem
  ls->axiom = "F";
  ls->linelen = 3;
  ls->initangle = -90;
  ls->leftangle = -30;
  ls->rightangle = 30;
  ls->rules = ptr_r1;
    char color_ls = 2;
    int iteration = 4;
    while(1) {
        //draw background picture
        drawLine((short)0, (short)480, (short)540, (short)360, WHITE);
        drawLine((short)540, (short)180, (short)580, (short)30, WHITE);
        drawLine((short)580, (short)30, (short)620, (short)180, WHITE);
        drawLine((short)580, (short)30, (short)567, (short)180, WHITE);
        drawLine((short)580, (short)30, (short)594, (short)180, WHITE);
        drawCircle((short)580, (short)250, (short)20, WHITE) ;
        drawRect((short) 540, (short) 180, (short) 80, (short)300, WHITE);
        finish_ls = false;
        //initialize position
      x_cur = 270;
      y_cur = 420;
      angle_cur = ls->initangle;
      //initialize state
      state->x = x_cur;
      state->y = y_cur;
      state->angle = angle_cur;
      state->prev = NULL;
        //generate command string curgen
        //initialize curgen on heap (axiom X)
        curgen = (char*)malloc(10000);
        strcpy(curgen,ls->axiom);
        for (int i = 0; i < iteration; i++) {
            printf("iteration%d,curgen=%s\n", i, curgen);
            nextgen();
        }
        for (char* s = curgen; *s!='\0'; s++) {
            switch (*s) {
            case 'X':
                break;
            case 'F':
                 if(max_freqency<=100.0){
            sleeptime_ls = 25000;
            }
            else if(max_freqency<=200.0){
            sleeptime_ls = 8000;
            }
            else if(max_freqency<=400.0){
            sleeptime_ls = 6000;
            }
            else if(max_freqency<=800.0){
            sleeptime_ls = 4000;
            }
            else if(max_freqency<=1000.0){
            sleeptime_ls = 2000;
            }
            else if(max_freqency<=1500.0){
            sleeptime_ls = 1000;
            }
            else{
            sleeptime_ls = 500;
            }
                forward(color_ls);
                setTextColor(WHITE) ;
                // static char sleeptime_str[40];
                // sprintf(sleeptime_str, "%d", (int)sleeptime_ls) ;
                // setCursor(250, 20) ;
                // setTextSize(2) ;
                // writeString(sleeptime_str);
                // setCursor(250,40);
                // setTextSize(2) ;
                // writeString(freqtext) ;
                sleep_us(sleeptime_ls);
                break;
            case '-':
                rotate(ls->leftangle);
                break;
            case '+':
                rotate(ls->rightangle);
                break;
            case '[':
                pushstate();
                break;
            case ']':
                popstate();
                break;
            }
        }
        free(curgen);
        //update parameters: color length rules angle 
        //rules:
        int rule_num = rand()%3;
        //tree type 
        //d
        if(rule_num ==0){
            iteration = 6;
            ls->initangle = -90;
            ls->linelen = rand()%2+2;
            ls->axiom = "X";
            ptr_r1->pred = 'X';
          ptr_r1->succ = "F[+X]F[-X]+X";
          ptr_r2->pred = 'F';
          ptr_r2->succ = "FF";
          ptr_r2->next = NULL;
          ptr_r1->next = ptr_r2;
        }
        //e
        else if(rule_num ==1){
            iteration = 6;
            ls->initangle = -90;
            ls->linelen = rand()%3+2;
            ls->axiom = "X";
            ptr_r1->pred = 'X';
          ptr_r1->succ = "F[+X][-X]FX";
          ptr_r2->pred = 'F';
          ptr_r2->succ = "FF";
          ptr_r2->next = NULL;
          ptr_r1->next = ptr_r2;
        }
        //a
        else{
            iteration = 4;
            ls->initangle = -90;
            ls->linelen = rand()%2+3;
            ls->axiom = "F";
            ptr_r1->pred = 'F';
          ptr_r1->succ = "F[+F]F[-F]F";
          ptr_r1->next = NULL;
        }
      ls->leftangle = -rand()%11-25;
      ls->rightangle = rand()%11+25;
        color_ls = rand()%7+1;
        if(color_ls==4){
            color_ls = 3;
        }
        finish_ls = true;
        while(!finish_fern);
        sleep_ms(1000);
        fillRect(0,0,640,480,BLACK);
     // NEVER exit while
    } // END WHILE(1)
  PT_END(pt);
} // animation thread


// thread for barnsley fern 
static PT_THREAD (protothread_fern(struct pt *pt))
{
    // Mark beginning of thread
    PT_BEGIN(pt);
    static fix15 x_old ;
    static fix15 y_old ;
    static fix15 x_new ;
    static fix15 y_new ;

    static fix15 scaled_x ;
    static fix15 scaled_y ;
    

    // uint32_t start_time ;
    // uint32_t end_time ;

    // start_time = time_us_32() ;
    // static fix15 scaled_x_left[max_count];
    // static fix15 scaled_y_left[max_count];
    // static fix15 scaled_x_right[max_count];
    // static fix15 scaled_y_right[max_count];

    // start generating trees
    static int num_trees = 2;
    static int tree_x = 160;
    static int tree_y = 0;

    //loop variables 
    //pixel points
    static int i;
    static int j;
    //tree number 
    static int tree;
    static int leaf;
    static int vga_scale_int;
    static int test;
    static fix15 scaled_x_left[max_count];
    static fix15 scaled_y_left[max_count];
    static fix15 scaled_x_right[max_count];
    static fix15 scaled_y_right[max_count];

    static int max_l;
    static float scale_current;
    static float scale_factor;
    static int x_offset;
    static int x_offset_increment;
    static int y_offset;
    static int y_offset_increment;
    static fix15 x_shrinked_left;
    static fix15 x_shrinked_right;
    static fix15 y_shrinked_left;
    static fix15 y_shrinked_right;
    while(1) {
        finish_fern = false;
        for( tree = 0; tree < num_trees; tree++){
            x_old = 0 ;
            y_old = 0 ;
            max_l = 16;
            scale_current = 1;
            scale_factor = 0.8;
            x_offset = 0;
            x_offset_increment = 10;
            y_offset = 460;
            y_offset_increment = 80;
        // randomize F2 function to generate different leaves
        f2x_coeff_1 = float2fix15((float)(rand() % 20 + 70) / 100.0);   //  0.70 - 0.90
        f2x_coeff_2 = float2fix15((float)(rand() % 7 ) / 100.0);   // 0 - 0.06
        f2y_coeff_1 = float2fix15((float)(rand() % 21 - 20) / 100.0);   // -0.2 - 0
        f2y_coeff_3 = float2fix15((float)(rand() % 100 + 100) / 100.0); //  1.00 - 2.00
        vga_scale_int = rand() % 20 + 15;
        vga_scale = int2fix15(vga_scale_int);  
        //generate left leaves and right leaves model
        for (i=0; i>15) + x_offset + tree_x, y_offset-(y_shrinked_left >>15)-tree_y, GREEN) ;
                drawPixel((x_shrinked_right>>15) + x_offset + tree_x, y_offset-(y_shrinked_right>>15)-tree_y, GREEN) ;
                sleep_us(100);
            }

            // growth speed control
            if(max_freqency<=10.0){
            sleeptime_fern = 60000;
            }
            else if(max_freqency<=200.0){
            sleeptime_fern = 6000;
            }
            else if(max_freqency<=400.0){
            sleeptime_fern = 1000;
            }
            else if(max_freqency<=800.0){
            sleeptime_fern = 500;
            }
            else if(max_freqency<=1000.0){
            sleeptime_fern = 50;
            }
            else if(max_freqency<=1500.0){
            sleeptime_fern = 10;
            }
            else{
            sleeptime_fern = 5;
            }
            //sleep_ms(sleeptime_fern);
            x_offset += x_offset_increment;
            y_offset -= y_offset_increment;
            y_offset_increment = round(y_offset_increment * scale_factor);
            scale_current *= scale_factor;
            PT_YIELD_usec(10*sleeptime_fern);        
        }

        tree_x += 260;
        tree_y += 70;
    }
    finish_fern = true;
    while(finish_ls==false){
        PT_YIELD_usec(2000);
    }
    //PT_YIELD_usec(2000000);
    sleep_ms(1000);
    tree_x = 160;
    tree_y = 0;
    // NEVER exit while
    } // END WHILE(1)
    PT_END(pt);
    } // animation thread

// ========================================
// === core 1 main -- started in main below
// ========================================
void core1_main(){
  // Add animation thread
  pt_add_thread(protothread_fern);
  pt_add_thread(protothread_fft);
  // Start the scheduler
  pt_schedule_start ;

}

// ========================================
// === main
// ========================================
// USE ONLY C-sdk library
int main(){
  // initialize stio
  stdio_init_all() ;
  // initialize VGA
  initVGA() ;
  srand(12345);

    ///////////////////////////////////////////////////////////////////////////////
    // ============================== ADC CONFIGURATION ==========================
    //////////////////////////////////////////////////////////////////////////////
    // Init GPIO for analogue use: hi-Z, no pulls, disable digital input buffer.
    adc_gpio_init(ADC_PIN);

    // Initialize the ADC harware
    // (resets it, enables the clock, spins until the hardware is ready)
    adc_init() ;

    // Select analog mux input (0...3 are GPIO 26, 27, 28, 29; 4 is temp sensor)
    adc_select_input(ADC_CHAN) ;

    // Setup the FIFO
    adc_fifo_setup(
        true,    // Write each completed conversion to the sample FIFO
        true,    // Enable DMA data request (DREQ)
        1,       // DREQ (and IRQ) asserted when at least 1 sample present
        false,   // We won't see the ERR bit because of 8 bit reads; disable.
        true     // Shift each sample to 8 bits when pushing to FIFO
    );

    // Divisor of 0 -> full speed. Free-running capture with the divider is
    // equivalent to pressing the ADC_CS_START_ONCE button once per `div + 1`
    // cycles (div not necessarily an integer). Each conversion takes 96
    // cycles, so in general you want a divider of 0 (hold down the button
    // continuously) or > 95 (take samples less frequently than 96 cycle
    // intervals). This is all timed by the 48 MHz ADC clock. This is setup
    // to grab a sample at 10kHz (48Mhz/10kHz - 1)
    adc_set_clkdiv(ADCCLK/Fs);


    // Populate the sine table and Hann window table
    int ii;
    for (ii = 0; ii < NUM_SAMPLES; ii++) {
        Sinewave[ii] = float2fix15(sin(6.283 * ((float) ii) / (float)NUM_SAMPLES));
        window[ii] = float2fix15(0.5 * (1.0 - cos(6.283 * ((float) ii) / ((float)NUM_SAMPLES))));
    }

    /////////////////////////////////////////////////////////////////////////////////
    // ============================== ADC DMA CONFIGURATION =========================
    /////////////////////////////////////////////////////////////////////////////////

    // Channel configurations
    dma_channel_config c2 = dma_channel_get_default_config(sample_chan);
    dma_channel_config c3 = dma_channel_get_default_config(control_chan);


    // ADC SAMPLE CHANNEL
    // Reading from constant address, writing to incrementing byte addresses
    channel_config_set_transfer_data_size(&c2, DMA_SIZE_8);
    channel_config_set_read_increment(&c2, false);
    channel_config_set_write_increment(&c2, true);
    // Pace transfers based on availability of ADC samples
    channel_config_set_dreq(&c2, DREQ_ADC);
    // Configure the channel
    dma_channel_configure(sample_chan,
        &c2,            // channel config
        sample_array,   // dst
        &adc_hw->fifo,  // src
        NUM_SAMPLES,    // transfer count
        false            // don't start immediately
    );

    // CONTROL CHANNEL
    channel_config_set_transfer_data_size(&c3, DMA_SIZE_32);      // 32-bit txfers
    channel_config_set_read_increment(&c3, false);                // no read incrementing
    channel_config_set_write_increment(&c3, false);               // no write incrementing
    channel_config_set_chain_to(&c3, sample_chan);                // chain to sample chan

    dma_channel_configure(
        control_chan,                         // Channel to be configured
        &c3,                                // The configuration we just created
        &dma_hw->ch[sample_chan].write_addr,  // Write address (channel 0 read address)
        &sample_address_pointer,                   // Read address (POINTER TO AN ADDRESS)
        1,                                  // Number of transfers, in this case each is 4 byte
        false                               // Don't start immediately.
    );

  // start core 1 
  multicore_reset_core1();
  multicore_launch_core1(&core1_main);

  // add threads
  pt_add_thread(protothread_lsys);

  // start scheduler
  pt_schedule_start ;
}