mirror of
https://github.com/im-tomu/fomu-workshop.git
synced 2024-09-19 19:00:18 +00:00
Add zig example
This commit is contained in:
parent
53cffe1761
commit
6a60a7a83d
3
riscv-zig-blink/.gitignore
vendored
Normal file
3
riscv-zig-blink/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
zig-cache/
|
||||
riscv-zig-blink
|
||||
riscv-zig-blink.bin
|
14
riscv-zig-blink/README.md
Normal file
14
riscv-zig-blink/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# riscv-zig-blink
|
||||
|
||||
Written against zig 0.5.0+518dbd30c
|
||||
|
||||
You can obtain the zig compiler via https://ziglang.org/download/
|
||||
e.g. a linux user might run:
|
||||
```
|
||||
curl -L https://ziglang.org/builds/zig-linux-x86_64-0.5.0+518dbd30c.tar.xz | tar -xJf -
|
||||
alias zig=./zig-linux-x86_64-0.5.0+518dbd30c/zig
|
||||
```
|
||||
|
||||
Run `zig build --help` from this directory for usage and options.
|
||||
|
||||
`zig build run -Drelease` will compile the demo and and run it on your connected FOMU.
|
46
riscv-zig-blink/build.zig
Normal file
46
riscv-zig-blink/build.zig
Normal file
@ -0,0 +1,46 @@
|
||||
const std = @import("std");
|
||||
const Builder = std.build.Builder;
|
||||
|
||||
pub fn build(b: *Builder) void {
|
||||
b.setPreferredReleaseMode(.ReleaseSmall);
|
||||
const mode = b.standardReleaseOptions();
|
||||
|
||||
const elf = b.addExecutable("riscv-zig-blink", "src/main.zig");
|
||||
elf.setTheTarget(.{
|
||||
.Cross = .{
|
||||
.arch = .riscv32,
|
||||
.os = .freestanding,
|
||||
.abi = .none,
|
||||
.cpu_features = .{
|
||||
.cpu = &std.Target.riscv.cpu.generic_rv32,
|
||||
.features = std.Target.Cpu.Feature.Set.empty,
|
||||
},
|
||||
},
|
||||
});
|
||||
elf.setLinkerScriptPath("ld/linker.ld");
|
||||
elf.setBuildMode(mode);
|
||||
// The ELF file contains debug symbols and can be passed to gdb for remote debugging
|
||||
if (b.option(bool, "emit-elf", "Should an ELF file be emitted in the current directory?") orelse false) {
|
||||
elf.setOutputDir(".");
|
||||
}
|
||||
|
||||
const binary = b.addSystemCommand(&[_][]const u8{
|
||||
b.option([]const u8, "objcopy", "objcopy executable to use (defaults to riscv64-unknown-elf-objcopy)") orelse "riscv64-unknown-elf-objcopy",
|
||||
"-I",
|
||||
"elf32-littleriscv",
|
||||
"-O",
|
||||
"binary",
|
||||
});
|
||||
binary.addArtifactArg(elf);
|
||||
binary.addArg("riscv-zig-blink.bin");
|
||||
b.default_step.dependOn(&binary.step);
|
||||
|
||||
const run_cmd = b.addSystemCommand(&[_][]const u8{
|
||||
"dfu-util",
|
||||
"-D",
|
||||
"riscv-zig-blink.bin",
|
||||
});
|
||||
run_cmd.step.dependOn(&binary.step);
|
||||
const run_step = b.step("run", "Upload and run the app on your FOMU");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
60
riscv-zig-blink/ld/linker.ld
Normal file
60
riscv-zig-blink/ld/linker.ld
Normal file
@ -0,0 +1,60 @@
|
||||
ENTRY(_start)
|
||||
|
||||
__DYNAMIC = 0;
|
||||
|
||||
MEMORY {
|
||||
sram : ORIGIN = 0x10000000, LENGTH = 0x00020000
|
||||
rom : ORIGIN = 0x20040000, LENGTH = 0x100000 /* 1 MBit */
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
.text :
|
||||
{
|
||||
_ftext = .;
|
||||
*(.text.start)
|
||||
*(.text .stub .text.* .gnu.linkonce.t.*)
|
||||
_etext = .;
|
||||
} > rom
|
||||
|
||||
.rodata :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_frodata = .;
|
||||
*(.rodata .rodata.* .gnu.linkonce.r.*)
|
||||
*(.rodata1)
|
||||
*(.srodata)
|
||||
. = ALIGN(4);
|
||||
_erodata = .;
|
||||
} > rom
|
||||
|
||||
.data : AT (ADDR(.rodata) + SIZEOF (.rodata))
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_fdata = .;
|
||||
*(.data .data.* .gnu.linkonce.d.*)
|
||||
*(.data1)
|
||||
*(.ramtext .ramtext.*)
|
||||
_gp = ALIGN(16);
|
||||
*(.sdata .sdata.* .gnu.linkonce.s.* .sdata2 .sdata2.*)
|
||||
_edata = ALIGN(16); /* Make sure _edata is >= _gp. */
|
||||
} > sram
|
||||
|
||||
.bss :
|
||||
{
|
||||
. = ALIGN(4);
|
||||
_fbss = .;
|
||||
*(.dynsbss)
|
||||
*(.sbss .sbss.* .gnu.linkonce.sb.*)
|
||||
*(.scommon)
|
||||
*(.dynbss)
|
||||
*(.bss .bss.* .gnu.linkonce.b.*)
|
||||
*(COMMON)
|
||||
. = ALIGN(4);
|
||||
_ebss = .;
|
||||
} > sram
|
||||
|
||||
_end = .;
|
||||
}
|
||||
|
||||
PROVIDE(_fstack = ORIGIN(sram) + LENGTH(sram) - 4);
|
75
riscv-zig-blink/src/fomu.zig
Normal file
75
riscv-zig-blink/src/fomu.zig
Normal file
@ -0,0 +1,75 @@
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
|
||||
pub const MESSIBLE = @import("./fomu/messible.zig").MESSIBLE;
|
||||
pub const REBOOT = @import("./fomu/reboot.zig").REBOOT;
|
||||
pub const RGB = @import("./fomu/rgb.zig").RGB;
|
||||
pub const TIMER0 = @import("./fomu/timer0.zig").TIMER0;
|
||||
pub const TOUCH = @import("./fomu/touch.zig").TOUCH;
|
||||
|
||||
pub const SYSTEM_CLOCK_FREQUENCY = 12000000;
|
||||
|
||||
pub const start = @import("./fomu/start.zig");
|
||||
// This forces start.zig file to be imported
|
||||
comptime {
|
||||
_ = start;
|
||||
}
|
||||
|
||||
/// Panic function that sets LED to red and flashing + prints the panic message over messible
|
||||
pub fn panic(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn {
|
||||
@setCold(true);
|
||||
|
||||
// Put LED into non-raw flashing mode
|
||||
RGB.CTRL.* = .{
|
||||
.EXE = true,
|
||||
.CURREN = true,
|
||||
.RGBLEDEN = true,
|
||||
.RRAW = false,
|
||||
.GRAW = false,
|
||||
.BRAW = false,
|
||||
};
|
||||
// Set colour to red
|
||||
RGB.setColour(255, 0, 0);
|
||||
// Enable the LED driver, and set 250 Hz mode.
|
||||
RGB.setControlRegister(.{
|
||||
.enable = true,
|
||||
.fr = .@"250",
|
||||
.quick_stop = false,
|
||||
.outskew = false,
|
||||
.output_polarity = .active_high,
|
||||
.pwm_mode = .linear,
|
||||
.BRMSBEXT = 0,
|
||||
});
|
||||
|
||||
messibleOutstream.print("PANIC: {}\r\n", .{message}) catch void;
|
||||
|
||||
while (true) {
|
||||
// TODO: Use @breakpoint() once https://reviews.llvm.org/D69390 is available
|
||||
asm volatile ("ebreak");
|
||||
}
|
||||
}
|
||||
|
||||
const OutStream = std.io.OutStream(error{});
|
||||
pub const messibleOutstream = &OutStream{
|
||||
.writeFn = struct {
|
||||
pub fn messibleWrite(self: *const OutStream, bytes: []const u8) error{}!void {
|
||||
var left: []const u8 = bytes;
|
||||
while (left.len > 0) {
|
||||
const bytes_written = MESSIBLE.write(left);
|
||||
left = left[bytes_written..];
|
||||
}
|
||||
}
|
||||
}.messibleWrite,
|
||||
};
|
||||
|
||||
const InStream = std.io.InStream(error{});
|
||||
pub const messibleInstream = &InStream{
|
||||
.writeFn = struct {
|
||||
pub fn messibleRead(self: *const InStream, buffer: []u8) error{}!usize {
|
||||
while (true) {
|
||||
const bytes_read = MESSIBLE.read(buffer);
|
||||
if (bytes_read != 0) return bytes_read;
|
||||
}
|
||||
}
|
||||
}.messibleRead,
|
||||
};
|
52
riscv-zig-blink/src/fomu/messible.zig
Normal file
52
riscv-zig-blink/src/fomu/messible.zig
Normal file
@ -0,0 +1,52 @@
|
||||
/// Messible: An Ansible for Messages
|
||||
/// https://rm.fomu.im/messible.html
|
||||
///
|
||||
/// An Ansible is a system for instant communication across vast distances,
|
||||
/// from a small portable device to a huge terminal far away. A Messible is
|
||||
/// a message- passing system from embedded devices to a host system. You can
|
||||
/// use it to get very simple printf()-style support over a debug channel.
|
||||
///
|
||||
/// The Messible assumes the host has a way to peek into the device’s memory
|
||||
/// space. This is the case with the Wishbone bridge, which allows both the
|
||||
/// device and the host to access the same memory.
|
||||
///
|
||||
/// At its core, a Messible is a FIFO. As long as the STATUS.FULL bit is 0,
|
||||
/// the device can write data into the Messible by writing into the IN.
|
||||
/// However, if this value is 1, you need to decide if you want to wait for it
|
||||
/// to empty (if the other side is just slow), or if you want to drop the
|
||||
/// message. From the host side, you need to read STATUS.HAVE to see if there
|
||||
/// is data in the FIFO. If there is, read OUT to get the most recent byte,
|
||||
/// which automatically advances the READ pointer.
|
||||
pub const MESSIBLE = struct {
|
||||
const base = 0xe0008000;
|
||||
|
||||
/// Write half of the FIFO to send data out the Messible. Writing to this register advances the write pointer automatically.
|
||||
pub const IN = @intToPtr(*volatile u8, base + 0x0);
|
||||
|
||||
/// Read half of the FIFO to receive data on the Messible. Reading from this register advances the read pointer automatically.
|
||||
pub const OUT = @intToPtr(*volatile u8, base + 0x4);
|
||||
|
||||
pub const STATUS = @intToPtr(*volatile packed struct {
|
||||
/// if more data can fit into the IN FIFO.
|
||||
FULL: bool,
|
||||
|
||||
/// if data can be read from the OUT FIFO.
|
||||
HAVE: bool,
|
||||
}, base + 0x8);
|
||||
|
||||
pub fn write(data: []const u8) usize {
|
||||
for (data) |c, i| {
|
||||
if (STATUS.*.FULL) return i;
|
||||
IN.* = c;
|
||||
}
|
||||
return data.len;
|
||||
}
|
||||
|
||||
pub fn read(dst: []u8) usize {
|
||||
for (dst) |*c, i| {
|
||||
if (!STATUS.*.HAVE) return i;
|
||||
c.* = OUT.*;
|
||||
}
|
||||
return dst.len;
|
||||
}
|
||||
};
|
22
riscv-zig-blink/src/fomu/reboot.zig
Normal file
22
riscv-zig-blink/src/fomu/reboot.zig
Normal file
@ -0,0 +1,22 @@
|
||||
/// https://rm.fomu.im/reboot.html
|
||||
pub const REBOOT = struct {
|
||||
const base = 0xe0006000;
|
||||
|
||||
/// Provides support for rebooting the FPGA.
|
||||
/// You can select which of the four images to reboot to.
|
||||
pub const CTRL = @intToPtr(*volatile packed struct {
|
||||
/// Which image to reboot to. SB_WARMBOOT supports four images that are configured at FPGA startup.
|
||||
/// The bootloader is image 0, so set these bits to 0 to reboot back into the bootloader.
|
||||
image: u2,
|
||||
|
||||
/// A reboot key used to prevent accidental reboots when writing to random areas of memory.
|
||||
/// To initiate a reboot, set this to 0b101011.
|
||||
key: u6,
|
||||
}, base + 0x0);
|
||||
|
||||
/// This sets the reset vector for the VexRiscv.
|
||||
/// This address will be used whenever the CPU is reset, for example
|
||||
/// through a debug bridge. You should update this address whenever
|
||||
/// you load a new program, to enable the debugger to run `mon reset`
|
||||
pub const ADDR = @intToPtr(*volatile u32, base + 0x4);
|
||||
};
|
140
riscv-zig-blink/src/fomu/rgb.zig
Normal file
140
riscv-zig-blink/src/fomu/rgb.zig
Normal file
@ -0,0 +1,140 @@
|
||||
/// https://rm.fomu.im/rgb.html
|
||||
pub const RGB = struct {
|
||||
const base = 0xe0006800;
|
||||
|
||||
/// This is the value for the SB_LEDDA_IP.DAT register.
|
||||
/// It is directly written into the SB_LEDDA_IP hardware block, so you
|
||||
/// should refer to http://www.latticesemi.com/view_document?document_id=50668.
|
||||
/// The contents of this register are written to the address specified
|
||||
/// in ADDR immediately upon writing this register.
|
||||
pub const DAT = @intToPtr(*volatile u8, base + 0x0);
|
||||
|
||||
/// This register is directly connected to SB_LEDDA_IP.ADDR.
|
||||
/// This register controls the address that is updated whenever DAT is written.
|
||||
/// Writing to this register has no immediate effect – data isn’t written until the DAT register is written.
|
||||
pub const ADDR = @intToPtr(*volatile u4, base + 0x4);
|
||||
|
||||
pub const Register = enum {
|
||||
PWRR = 1,
|
||||
PWRG = 2,
|
||||
PWRB = 3,
|
||||
|
||||
BCRR = 5,
|
||||
BCFR = 6,
|
||||
|
||||
CR0 = 8,
|
||||
BR = 9,
|
||||
ONR = 10,
|
||||
OFR = 11,
|
||||
};
|
||||
|
||||
pub fn setRegister(reg: Register, value: u8) void {
|
||||
ADDR.* = @enumToInt(reg);
|
||||
DAT.* = value;
|
||||
}
|
||||
|
||||
const CR0 = packed struct {
|
||||
BRMSBEXT: u2,
|
||||
|
||||
pwm_mode: enum(u1) {
|
||||
linear = 0,
|
||||
|
||||
/// The Polynomial for the LFSR is X^(8) + X^(5) + X^3 + X + 1
|
||||
LFSR = 1,
|
||||
},
|
||||
|
||||
quick_stop: bool,
|
||||
|
||||
outskew: bool,
|
||||
|
||||
/// PWM output polarity
|
||||
output_polarity: enum(u1) {
|
||||
active_high = 0,
|
||||
active_low = 1,
|
||||
},
|
||||
|
||||
/// Flick rate for PWM (in Hz)
|
||||
fr: enum(u1) {
|
||||
@"125" = 0,
|
||||
@"250" = 1,
|
||||
},
|
||||
|
||||
/// LED Driver enabled?
|
||||
enable: bool,
|
||||
};
|
||||
|
||||
pub fn setControlRegister(value: CR0) void {
|
||||
setRegister(.CR0, @bitCast(u8, value));
|
||||
}
|
||||
|
||||
pub const Breathe = packed struct {
|
||||
/// Breathe rate is in 128 ms increments
|
||||
rate: u4,
|
||||
|
||||
_pad: u1 = 0,
|
||||
|
||||
mode: enum(u1) {
|
||||
fixed = 0,
|
||||
modulate = 1,
|
||||
},
|
||||
|
||||
pwm_range_extend: bool,
|
||||
|
||||
enable: bool,
|
||||
};
|
||||
|
||||
pub fn setBreatheRegister(reg: enum {
|
||||
On,
|
||||
Off,
|
||||
}, value: Breathe) void {
|
||||
setRegister(switch (reg) {
|
||||
.On => .BCRR,
|
||||
.Off => .BCFR,
|
||||
}, @bitCast(u8, value));
|
||||
}
|
||||
|
||||
/// Control logic for the RGB LED and LEDDA hardware PWM LED block.
|
||||
pub const CTRL = @intToPtr(*volatile packed struct {
|
||||
/// Enable the fading pattern?
|
||||
/// Connected to `SB_LEDDA_IP.LEDDEXE`.
|
||||
EXE: bool,
|
||||
|
||||
/// Enable the current source?
|
||||
/// Connected to `SB_RGBA_DRV.CURREN`.
|
||||
CURREN: bool,
|
||||
|
||||
/// Enable the RGB PWM control logic?
|
||||
/// Connected to `SB_RGBA_DRV.RGBLEDEN`.
|
||||
RGBLEDEN: bool,
|
||||
|
||||
/// Enable raw control of the red LED via the RAW.R register.
|
||||
RRAW: bool,
|
||||
|
||||
/// Enable raw control of the green LED via the RAW.G register.
|
||||
GRAW: bool,
|
||||
|
||||
/// Enable raw control of the blue LED via the RAW.B register.
|
||||
BRAW: bool,
|
||||
}, base + 0x8);
|
||||
|
||||
/// Normally the hardware SB_LEDDA_IP block controls the brightness of the
|
||||
/// LED, creating a gentle fading pattern.
|
||||
/// However, by setting the appropriate bit in CTRL, it is possible to
|
||||
/// manually control the three individual LEDs.
|
||||
pub const RAW = @intToPtr(*volatile packed struct {
|
||||
/// Red
|
||||
R: bool,
|
||||
|
||||
/// Green
|
||||
G: bool,
|
||||
|
||||
/// Blue
|
||||
B: bool,
|
||||
}, base + 0xc);
|
||||
|
||||
pub fn setColour(r: u8, g: u8, b: u8) void {
|
||||
setRegister(.PWRR, r);
|
||||
setRegister(.PWRG, g);
|
||||
setRegister(.PWRB, b);
|
||||
}
|
||||
};
|
63
riscv-zig-blink/src/fomu/start.zig
Normal file
63
riscv-zig-blink/src/fomu/start.zig
Normal file
@ -0,0 +1,63 @@
|
||||
const root = @import("root");
|
||||
const std = @import("std");
|
||||
|
||||
extern var _ftext: u8;
|
||||
extern var _etext: u8;
|
||||
extern var _frodata: u8 align(4);
|
||||
extern var _erodata: u8 align(4);
|
||||
extern var _fdata: u8 align(4);
|
||||
extern var _gp: u8 align(16);
|
||||
extern var _edata: u8 align(16);
|
||||
extern var _fbss: u8 align(4);
|
||||
extern var _ebss: u8 align(4);
|
||||
extern var _end: u8;
|
||||
extern var _fstack: u8;
|
||||
|
||||
export fn _start() linksection(".text.start") callconv(.Naked) noreturn {
|
||||
asm volatile (
|
||||
\\ j over_magic
|
||||
\\ .word 0xb469075a // Magic value for config flags
|
||||
\\ .word 0x00000020 // USB_NO_RESET flag so we can attach the debugger
|
||||
\\over_magic:
|
||||
);
|
||||
|
||||
// set the stack pointer to `&_fstack`
|
||||
asm volatile (
|
||||
\\ la sp, _fstack + 4
|
||||
);
|
||||
|
||||
// zero out bss
|
||||
asm (
|
||||
\\ la a0, %[fbss]
|
||||
\\ la a1, %[ebss]
|
||||
\\bss_loop:
|
||||
\\ beq a0, a1, bss_done
|
||||
\\ sw zero, 0(a0)
|
||||
\\ add a0, a0,4
|
||||
\\ j bss_loop
|
||||
\\bss_done:
|
||||
: [ret] "=" (-> void)
|
||||
: [fbss] "m" (&_fbss),
|
||||
[ebss] "m" (&_ebss)
|
||||
);
|
||||
|
||||
// copy data from data rom (which is after rodata) to data
|
||||
asm (
|
||||
\\ la t0, %[erodata]
|
||||
\\ la t1, %[fdata]
|
||||
\\ la t2, %[edata]
|
||||
\\data_loop:
|
||||
\\ lw t3, 0(t0)
|
||||
\\ sw t3, 0(t1)
|
||||
\\ addi t0, t0, 4
|
||||
\\ addi t1, t1, 4
|
||||
\\ bltu t1, t2, data_loop
|
||||
: [ret] "=" (-> void)
|
||||
: [erodata] "m" (&_erodata),
|
||||
[fdata] "m" (&_fdata),
|
||||
[edata] "m" (&_edata)
|
||||
);
|
||||
|
||||
// call user's main
|
||||
root.main();
|
||||
}
|
109
riscv-zig-blink/src/fomu/timer0.zig
Normal file
109
riscv-zig-blink/src/fomu/timer0.zig
Normal file
@ -0,0 +1,109 @@
|
||||
/// Provides a generic Timer core.
|
||||
///
|
||||
/// The Timer is implemented as a countdown timer that can be used in various modes:
|
||||
/// - Polling: Returns current countdown value to software.
|
||||
/// - One-Shot: Loads itself and stops when value reaches 0.
|
||||
/// - Periodic: (Re-)Loads itself when value reaches 0.
|
||||
///
|
||||
/// *en* register allows the user to enable/disable the Timer. When the Timer
|
||||
/// is enabled, it is automatically loaded with the value of load register.
|
||||
///
|
||||
/// When the Timer reaches 0, it is automatically reloaded with value of reload register.
|
||||
///
|
||||
/// The user can latch the current countdown value by writing to update_value
|
||||
/// register, it will update value register with current countdown value.
|
||||
///
|
||||
/// To use the Timer in One-Shot mode, the user needs to:
|
||||
/// - Disable the timer.
|
||||
/// - Set the load register to the expected duration.
|
||||
/// - (Re-)Enable the Timer.
|
||||
///
|
||||
/// To use the Timer in Periodic mode, the user needs to:
|
||||
/// - Disable the Timer.
|
||||
/// - Set the load register to 0.
|
||||
/// - Set the reload register to the expected period.
|
||||
/// - Enable the Timer.
|
||||
///
|
||||
/// For both modes, the CPU can be advertised by an IRQ that the
|
||||
/// duration/period has elapsed. (The CPU can also do software polling with
|
||||
/// update_value and value to know the elapsed duration)
|
||||
pub const TIMER0 = struct {
|
||||
const base = 0xe0002800;
|
||||
|
||||
pub const LOAD3 = @intToPtr(*volatile u8, base + 0x0);
|
||||
pub const LOAD2 = @intToPtr(*volatile u8, base + 0x4);
|
||||
pub const LOAD1 = @intToPtr(*volatile u8, base + 0x8);
|
||||
pub const LOAD0 = @intToPtr(*volatile u8, base + 0xc);
|
||||
|
||||
/// Load value when Timer is (re-)enabled.
|
||||
/// In One-Shot mode, the value written to this register specify the
|
||||
/// Timer’s duration in clock cycles.
|
||||
pub fn load(x: u32) void {
|
||||
LOAD3.* = @truncate(u8, x >> 24);
|
||||
LOAD2.* = @truncate(u8, x >> 16);
|
||||
LOAD1.* = @truncate(u8, x >> 8);
|
||||
LOAD0.* = @truncate(u8, x);
|
||||
}
|
||||
|
||||
pub const RELOAD3 = @intToPtr(*volatile u8, base + 0x10);
|
||||
pub const RELOAD2 = @intToPtr(*volatile u8, base + 0x14);
|
||||
pub const RELOAD1 = @intToPtr(*volatile u8, base + 0x18);
|
||||
pub const RELOAD0 = @intToPtr(*volatile u8, base + 0x1c);
|
||||
|
||||
/// Reload value when Timer reaches 0.
|
||||
/// In Periodic mode, the value written to this register specify the
|
||||
/// Timer’s period in clock cycles.
|
||||
pub fn reload(x: u32) void {
|
||||
RELOAD3.* = @truncate(u8, x >> 24);
|
||||
RELOAD2.* = @truncate(u8, x >> 16);
|
||||
RELOAD1.* = @truncate(u8, x >> 8);
|
||||
RELOAD0.* = @truncate(u8, x);
|
||||
}
|
||||
|
||||
/// Enable of the Timer.
|
||||
/// Set if to 1 to enable/start the Timer and 0 to disable the Timer
|
||||
pub const EN = @intToPtr(*volatile bool, base + 0x20);
|
||||
|
||||
pub fn start() void {
|
||||
EN.* = true;
|
||||
}
|
||||
|
||||
pub fn stop() void {
|
||||
EN.* = false;
|
||||
}
|
||||
|
||||
/// Update of the current countdown value.
|
||||
/// A write to this register latches the current countdown value to value
|
||||
/// register.
|
||||
pub const UPDATE_VALUE = @intToPtr(*volatile bool, base + 0x24);
|
||||
|
||||
pub const VALUE3 = @intToPtr(*volatile u8, base + 0x28);
|
||||
pub const VALUE2 = @intToPtr(*volatile u8, base + 0x2c);
|
||||
pub const VALUE1 = @intToPtr(*volatile u8, base + 0x30);
|
||||
pub const VALUE0 = @intToPtr(*volatile u8, base + 0x34);
|
||||
|
||||
pub fn latchedValue() u32 {
|
||||
return (@as(u32, VALUE3.*) << 24) |
|
||||
(@as(u32, VALUE2.*) << 16) |
|
||||
(@as(u32, VALUE1.*) << 8) |
|
||||
(@as(u32, VALUE0.*));
|
||||
}
|
||||
|
||||
pub fn value() u32 {
|
||||
UPDATE_VALUE.* = true;
|
||||
return latchedValue();
|
||||
}
|
||||
|
||||
/// This register contains the current raw level of the Event trigger.
|
||||
/// Writes to this register have no effect.
|
||||
pub const EV_STATUS = @intToPtr(*volatile bool, base + 0x38);
|
||||
|
||||
/// When an Event occurs, the corresponding bit will be set in this
|
||||
/// register. To clear the Event, set the corresponding bit in this
|
||||
/// register.
|
||||
pub const EV_PENDING = @intToPtr(*volatile bool, base + 0x3c);
|
||||
|
||||
/// This register enables the corresponding Events. Write a 0 to this
|
||||
/// register to disable individual events.
|
||||
pub const EV_ENABLE = @intToPtr(*volatile bool, base + 0x40);
|
||||
};
|
13
riscv-zig-blink/src/fomu/touch.zig
Normal file
13
riscv-zig-blink/src/fomu/touch.zig
Normal file
@ -0,0 +1,13 @@
|
||||
/// https://rm.fomu.im/touch.html
|
||||
pub const TOUCH = struct {
|
||||
const base = 0xe0005800;
|
||||
|
||||
/// Output values for pads 1-4
|
||||
pub const TOUCH_O = @intToPtr(*volatile u4, base + 0x0);
|
||||
|
||||
/// Output enable control for pads 1-4
|
||||
pub const TOUCH_OE = @intToPtr(*volatile u4, base + 0x4);
|
||||
|
||||
/// Input value for pads 1-4
|
||||
pub const TOUCH_1 = @intToPtr(*volatile u4, base + 0x8);
|
||||
};
|
113
riscv-zig-blink/src/main.zig
Normal file
113
riscv-zig-blink/src/main.zig
Normal file
@ -0,0 +1,113 @@
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const fomu = @import("./fomu.zig");
|
||||
|
||||
pub fn panic(message: []const u8, stack_trace: ?*builtin.StackTrace) noreturn {
|
||||
@setCold(true);
|
||||
fomu.panic(message, stack_trace);
|
||||
}
|
||||
|
||||
fn rgb_init() void {
|
||||
// Turn on the RGB block and current enable, as well as enabling led control
|
||||
fomu.RGB.CTRL.* = .{
|
||||
.EXE = true,
|
||||
.CURREN = true,
|
||||
.RGBLEDEN = true,
|
||||
.RRAW = false,
|
||||
.GRAW = false,
|
||||
.BRAW = false,
|
||||
};
|
||||
|
||||
// Enable the LED driver, and set 250 Hz mode.
|
||||
// Also set quick stop, which we'll use to switch patterns quickly.
|
||||
fomu.RGB.setControlRegister(.{
|
||||
.enable = true,
|
||||
.fr = .@"250",
|
||||
.quick_stop = true,
|
||||
.outskew = false,
|
||||
.output_polarity = .active_high,
|
||||
.pwm_mode = .linear,
|
||||
.BRMSBEXT = 0,
|
||||
});
|
||||
|
||||
// Set clock register to 12 MHz / 64 kHz - 1
|
||||
fomu.RGB.setRegister(.BR, (fomu.SYSTEM_CLOCK_FREQUENCY / 64000) - 1);
|
||||
|
||||
// Blink on/off time is in 32 ms increments
|
||||
fomu.RGB.setRegister(.ONR, 1); // Amount of time to stay "on"
|
||||
fomu.RGB.setRegister(.OFR, 0); // Amount of time to stay "off"
|
||||
|
||||
fomu.RGB.setBreatheRegister(.On, .{
|
||||
.enable = true,
|
||||
.pwm_range_extend = false,
|
||||
.mode = .fixed,
|
||||
.rate = 1,
|
||||
});
|
||||
fomu.RGB.setBreatheRegister(.Off, .{
|
||||
.enable = true,
|
||||
.pwm_range_extend = false,
|
||||
.mode = .fixed,
|
||||
.rate = 1,
|
||||
});
|
||||
}
|
||||
|
||||
const Colour = struct {
|
||||
r: u8,
|
||||
g: u8,
|
||||
b: u8,
|
||||
};
|
||||
|
||||
/// Input a value 0 to 255 to get a colour value.
|
||||
/// The colours are a transition r - g - b - back to r.
|
||||
fn colourWheel(wheelPos: u8) Colour {
|
||||
var c: Colour = undefined;
|
||||
var wp = 255 - wheelPos;
|
||||
switch (wp) {
|
||||
0...84 => {
|
||||
c = .{
|
||||
.r = 255 - wp * 3,
|
||||
.g = 0,
|
||||
.b = wp * 3,
|
||||
};
|
||||
},
|
||||
85...169 => {
|
||||
wp -= 85;
|
||||
c = .{
|
||||
.r = 0,
|
||||
.g = wp * 3,
|
||||
.b = 255 - wp * 3,
|
||||
};
|
||||
},
|
||||
170...255 => {
|
||||
wp -= 170;
|
||||
c = .{
|
||||
.r = wp * 3,
|
||||
.g = 255 - wp * 3,
|
||||
.b = 0,
|
||||
};
|
||||
},
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
fn msleep(ms: usize) void {
|
||||
fomu.TIMER0.stop();
|
||||
fomu.TIMER0.reload(0);
|
||||
fomu.TIMER0.load(fomu.SYSTEM_CLOCK_FREQUENCY / 1000 * ms);
|
||||
fomu.TIMER0.start();
|
||||
while (fomu.TIMER0.value() != 0) {}
|
||||
}
|
||||
|
||||
pub fn main() noreturn {
|
||||
rgb_init();
|
||||
|
||||
var i: u8 = 0;
|
||||
while (true) : (i +%= 1) {
|
||||
const colour = colourWheel(i);
|
||||
fomu.RGB.setColour(colour.r, colour.g, colour.b);
|
||||
|
||||
msleep(80);
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
Loading…
Reference in New Issue
Block a user