From ebb6518ec3ee56f874c8ddbd8113342779c625d2 Mon Sep 17 00:00:00 2001 From: Toby Jaffey Date: Mon, 8 Dec 2025 21:20:57 +0000 Subject: [PATCH] Lissajous, showing floating point arithmetic --- README.md | 1 + apps/Makefile | 2 + apps/lissajous/Makefile | 27 +++++++++ apps/lissajous/lissajous.c | 118 +++++++++++++++++++++++++++++++++++++ precompiled/lissajous.bin | Bin 0 -> 13352 bytes 5 files changed, 148 insertions(+) create mode 100644 apps/lissajous/Makefile create mode 100644 apps/lissajous/lissajous.c create mode 100755 precompiled/lissajous.bin diff --git a/README.md b/README.md index eb5b303..988aff6 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Although based on a fully fledged CPU emulator, uvm32 is intended for executing * [host-arduino](host-arduino) vm host as Arduino sketch (tested on Arduino Uno ATmega328P, uses 9950 bytes of flash/1254 bytes RAM) * [apps/helloworld](apps/helloworld) C hello world program * [apps/conio](apps/conio) C console IO demo + * [apps/lissajous](apps/lissajour) C console lissajous curve (showing softfp, floating point) * [apps/hello-asm](apps/hello-asm) Minimal hello world assembly * [apps/fib](apps/fib) C fibonacci series program (iterative and recursive) * [apps/sketch](apps/sketch) C Arduino/Wiring/Processing type program in `setup()` and `loop()` style diff --git a/apps/Makefile b/apps/Makefile index 133cabb..abe6621 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -4,6 +4,7 @@ all: docker build -t ${DOCKER_IMAGE} . (cd sketch && make) (cd helloworld && make) + (cd lissajous && make) (cd conio && make) (cd zig-mandel && make) (cd zigtris && make) @@ -13,6 +14,7 @@ all: clean: (cd sketch && make clean) (cd helloworld && make clean) + (cd lissajous && make clean) (cd conio && make clean) (cd zig-mandel && make clean) (cd zigtris && make clean) diff --git a/apps/lissajous/Makefile b/apps/lissajous/Makefile new file mode 100644 index 0000000..7843727 --- /dev/null +++ b/apps/lissajous/Makefile @@ -0,0 +1,27 @@ +PROJECT:=lissajous + +DOCKER_IMAGE=riscv-dev +DOCKER_CMD:=docker run --rm -v ${PWD}../../../:/data -w /data/apps/${PROJECT} ${DOCKER_IMAGE} +PREFIX:=${DOCKER_CMD} riscv64-unknown-elf- +CFLAGS+=-I../../common +CFLAGS+=-fno-stack-protector +CFLAGS+=-static-libgcc -fdata-sections -ffunction-sections +CFLAGS+=-g -Os -march=rv32ima_zicsr -mabi=ilp32 -static +LDFLAGS:= -T ../linker.ld -nostdlib -Wl,--gc-sections +LIBS:= -lgcc # needed for softfp + +SRCS=${PROJECT}.c ../crt0.S + +all: + ${PREFIX}gcc -o ${PROJECT}.elf ${CFLAGS} ${LDFLAGS} ${SRCS} ${LIBS} + $(PREFIX)objcopy ${PROJECT}.elf -O binary ${PROJECT}.bin + +disasm: all + $(PREFIX)objdump -S -d -f ${PROJECT}.elf + +test: all + ../../host/host ${PWD}/${PROJECT}.bin + +clean: + rm -f ${PROJECT}.o ${PROJECT}.elf ${PROJECT}.bin + diff --git a/apps/lissajous/lissajous.c b/apps/lissajous/lissajous.c new file mode 100644 index 0000000..92da9ab --- /dev/null +++ b/apps/lissajous/lissajous.c @@ -0,0 +1,118 @@ +#define USE_MAIN +#include "uvm32_target.h" + +// https://github.com/shastro/CodeGolf-Lissajous/blob/master/lissa.c +// https://github.com/nicbk/donut-embedded/blob/ad8061ece9a2c156694c39af12f109ea6778da52/donut.c#L57 + +#define PI 3.14159265358979323846 // First 21 digits of pi + +void movecursor(int x, int y) { + print("\033["); + printdec(y); + print(";"); + printdec(x); + print("f"); +} + +void sleep(uint32_t ms) { + uint32_t start = millis(); + while (millis() < start + ms) { + yield(); + } +} + +// Absolute value function for doubles +double abs_c(double x) { + if (x < 0) { + return -x; + } else { + return x; + } +} + +// Recursive factorial capable of calculating a max of approximately 10^18 in size +unsigned long long fac(unsigned char x) { + if (x == 0) { + return 1; + } else { + return x * fac(x - 1); + } +} + +// Exponential function +// Ignores edge cases such as indeterminate forms for simplicity +double pow_c(double x, unsigned char n) { + if (n == 0) { + return 1; + } else { + double product = x; + + for (int i = 1; i < n; ++i) { + product *= x; + } + + return product; + } +} + +// MacLaurin series approximation of sin(x) from -PI/2 to PI/2 +// with eight terms, and extended to the domain of all +// real numbers with modular arithmetic. +double sin(double x) { + double translation = 0; + if (x > -PI / 2) { + translation = (int)((x + PI / 2) / PI); + } else { + translation = (int)((x - PI / 2) / PI); + } + + x -= PI * translation; + if ((int)abs_c(translation) % 2 == 1) { + x = -x; + } + + double sum = 0; + + for (int i = 0; i < 9; ++i) { + sum += pow_c(-1, i) * pow_c(x, 2 * i + 1) / fac(2 * i + 1); + } + + return sum; +} + +// cos(x) function derived from sin(x) +double cos(double x) { + return sin(PI / 2 - x); +} + +void main(void) { + int freq1 = 45; + int freq2 = 90; + + for (int i = 0; i < 300; i++) { + putc('\n'); + } + print("\033[H"); // Cursor at Corner of Screen + + float x = 0; + float y = 0; + float angle = 0; + float beta = 0.0; + + while (1) { + uint32_t framestart = millis(); + for (angle = 0; angle < 2 * PI; angle += 0.15) { + movecursor(x, y); + x = 40 * cos(freq1 * angle + beta) + 60; + y = 20 * sin(freq2 * angle) + 30; + print("#"); + } + // wait for next frame + while (millis() < framestart + 20) { + yield(); + } + beta += 0.05; + + print("\033[2J"); + } +} diff --git a/precompiled/lissajous.bin b/precompiled/lissajous.bin new file mode 100755 index 0000000000000000000000000000000000000000..6381bb00d12952ea1ef290701eb70d6c7520906c GIT binary patch literal 13352 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 literal 0 HcmV?d00001