So about a year ago I tried to build this clock using some Servos and a Raspberry Pi Zero. It was written in Elixir using Nerves. I got about half way through and never finished it. However, I think I know just the think to motivate me to finish it: rewrite the whole thing in this awesome language called Roc! Plus calling it the Roc Clock would be super awesome :grinning_face_with_smiling_eyes:
I’m wondering if Roc is at a point now where this would be feasible without hitting any major road blocks. There’s already Rust libraries to control the GPIO pins on a Pi so I’m thinking I can just write a platform that exposes some of this to Roc. I believe the Pi is an ARM processor so I’m hoping that isn’t an issue, maybe I’ll have to compile Roc from scratch?
Anyways, let me know what you think. And thanks in advance!
Since the raspberry pi zero runs linux (right? haven't played with one in a long time), I am pretty sure this should be doable with current roc without compiler modification.
True embedded processors like an esp32 or stm32 is where you would need a custom version of the roc compiler with new targets in order to get it working (though still doable, I was messing with that a long while back)
Oh, It looks like we don't have a nightly release for linux arm, so you will at least need to compile the compiler, but you shouldn't need to modify it.
Though I guess you could also theoretically cross compile the app as well. Though that may take a bit more fiddling. Not fully sure.
Anyway. Generally story is that it should definitely be doable, but will likely have some minor extra hoops to jump through. I don't think it should have any major issues.
Agreed with Brendan, I think aside some a new compilation target it should be possible. There are known compiler bugs with highly-recursive programs but I would assume that your problem domain would not run into them.
Yes it runs Linux so hopefully I’m good there. Not sure if I’ll mess with cross compiling or just do the development on the Pi itself. Thanks for the feedback!
Just a follow up here. I looked into this some more with a raspberry pi I have laying around. Currently if you on a 32bit arm device, our nix build environment will not load. It is a known nix bug. Should still theoretically be able to use roc, but you would need to install all dependencies manually. On 64bit arm, nix will work fine.
Thanks for the update! I believe I have all the dependencies installed, but I’m getting this error now when running cargo build
43029D9D-0B49-4819-8039-33F89A3BA409.jpg
Yeah, i was looking into this more, I'll push a branch tomorrow.
I managed to get the c hello world running earlier today on 32 bit pi.
You’re the best! Thanks so much for the help, I really appreciate it. Please let me know when you get that pushed
Ok. Just pushed what I currently have to the rpi branch.
A few notes:
--profile low-mem and --features target-arm when compiling roc.-j <num> to cargo.roc glue this should at least be fixed for rust platforms. Until then, you are stuck with custom/fixing platforms.zstd and 3/4 of memory for compressed in ram swap.lld as your linker and not they system default ld. ld is slower and uses way more ram. Note: when using nix develop on my pi, I ran into an issue where cargo didn't seem to read .cargo/config.toml. So I forced use of lld by putting RUSTFLAGS="-Clink-arg=-fuse-ld=lld" before commands to build roc.Anyway. Definitely here to help as needed. Here was my full workflow:
bren077s@raspberrypi:~/roc $ uname -a
Linux raspberrypi 5.15.84-v7+ #1613 SMP Thu Jan 5 11:59:48 GMT 2023 armv7l GNU/Linux
bren077s@raspberrypi:~/roc $ RUSTFLAGS="-Clink-arg=-fuse-ld=lld" cargo run --profile low-mem --features target-arm -j 2 build examples/platform-switching/rocLovesC.roc
Finished low-mem [optimized] target(s) in 3.38s
Running `target/low-mem/roc build examples/platform-switching/rocLovesC.roc`
🔨 Rebuilding platform...
0 errors and 0 warnings found in 2157 ms while successfully building:
examples/platform-switching/rocLovesC
bren077s@raspberrypi:~/roc $ ./examples/platform-switching/rocLovesC
Roc <3 C!
Definitely takes a while to compile especially as you restrict ram more.
I probably should look at what roc features can be fully disabled. That would likely cut out a ton of depedencies and make it compile faster
Please feel free to ask as many question as needed. I should definitely be able to help as you run into issues.
very nice! I wonder if we could put this advice as a .md in the repo linked from the building from source docs, so people can discover it more easily?
also I wonder if someday it would be worth doing a nightly build targeting rpi :thinking:
Wow thanks Brendan! I can tell I’m going to like this community already 😁 I’ll give that a go later today when I get home. A nightly rpi build would be great. I’d love to help get that going if possible. How are the nightly builds set up?
Have a look at this file in the repo, and similarly-named ones:
.github/workflows/nightly_linux_x86_64.yml
I'm not sure how llvm dependencies would work with this, but it would be awesome if we could setup cross compilation of the compiler. Would make it way easier to generate a number of nightly builds on a single powerful machine.
Oh, extra note: it works to do --no-default-features --features target-arm. Reduces the number of packages to compile by about 120.
Compiling on the Pi is definitely slow :sweat_smile: After a few hours it compiled successfully, but trying to run any of the examples leads to this error
Screenshot_20230130_114318.png
This was with --no-default-feature by the way. The full command was
RUSTFLAGS="-Clink-arg=-fuse-ld=lld" cargo run --profile low-mem --no-default-features --features target-arm -j 1 build examples/platform-switching/rocLovesC.roc
Oh crap. I misguided you slightly. I thought you were on a system that only had 32bit support. You have a system running 64bit. So you want the target-aarch64 and not target-arm feature. Sorry about that.
Compiling on the Pi is definitely slow
You should be able to run most if not all of the compilation with -j 4. Though -j 2 is definitely safer if you have 1GB or less of ram. Otherwise it might randomly freeze or crash. That said, it is fine to run -j 4 and the later run -j 2 just for the memory heavy packages that lead to crashes. Still will be slow, but better.
If your pi is swapping a lot, i definitely advise enabling either zram or zswap. They should help a lot to keep thing sin memory and greatly speed up compilation. I would advise enabling them with lz4. zstd is too slow on a pi.
Also, hopefully once we get the compiler built right, you won't have to build it again. So mostly a one time cost.
Progress! But a new error now
Screenshot_20230131_075426.png
Oh yep. Cool.
Nix being a pain.
Try exiting nix and then manually running the built executable: ./target/low-mem/roc ...
I think the issue is that nix is pulling in a different version of glibc than what gets pulled in while compiling the platform.
No luck with that either.
Screenshot_20230131_101153.png
Hmm. Let me double check. I definitely hit this before and got around it.
Hmm...i guess i didn't fix it on 64bit rpi.
So, the core issue with nix is that it is using glibc version 2.35 while the rpi is at glibc version 2.31
I think when roc compiles the platform and app, it pulls in the rpi glibc instead of nix and that breaks.
For the libxcb issue, i think libxcb is related to x11 and graphics. Kinda surprise that roc with --no-default-features pulls it in. I think it is pulled in by the editor (which should be disabled), but that is all speculation and guessing.
I tried installing the library and I can see the file for it now, but still no luck. So i am quite uncertain.
Need more time to debug.
Maybe @Anton has some nix ideas.
As a note, i was able to have roc dump rocLovesC.roc as an object file and manually link it. So we have all the pieces functioning.
Nick Hallstrom said:
Progress! But a new error now
Screenshot_20230131_075426.png
Are you still inside nix when you are executing rocLovesC?
It's also possible that your cargo cache contains artifacts that were built outside of nix.
I'd recommend doing:
nix develop
cargo clean
cargo build --release -j 4
./target/release/roc run ./examples/platform-switching/rocLovesC.roc
I also recommend installing starship (outside of nix, so before nix develop), it will show when you are inside the nix shell.
I am pretty sure that won't fix this.
I don't have the tools installed to build outside of nix and I hit this.
So Everything I built had to be within nix.
Hmm, strange. I have an aarch64 pi, I can set it up on Friday to try to replicate the issue
So would I have better luck here if I tried this on a 32bit install? I went with 64 because Brendan said something about all the platforms being hard coded to 64 bit
Confirming that it didn't fix the issue
I think 64bit is definitely the better option if we can fix this.
To be fair, i know a guaranteed way to fix this. Don't use nix. Just install all the dependencies manually like you would be forced to on a 32bit pi.
Though will require a full clean and recompilation.
So i would advise manually dependency install on 64bit over switching to 32bit.
I think I’ll try installing the deps manually for now then and see if I can get that working.
Sorry that I mixed that up. I have it fully compiling and running on 32bit with manual deps install. For 64bit, I guess I only ever compiled and forgot to run.
Woohoo that did the trick!
It's working!
Yay!
Hopefully Rust and Zig platforms work as well?
Though I guess you mostly just care about rust
Rust platform works. I'll check Zig after. Right now I'm trying to compile one of the basic-cli examples and it's been compiling for about 15 minutes. Should it take that long? Any tips to get this sped up? I've already done the previous stuff like use zram.
Oh, I bet that the platform is compiling in debug mode. Which ends up being slow in low ram due to how much memory especially linking takes.
I would update the Cargo.toml for the platform to modify debug to not actually add debug info
[profile.dev]
debug = 0
strip = "symbols"
Then you also can follow the update to .cargo/config.toml to have it use lld which links faster and uses less memory:
[build]
rustflags = ["-C", "link-arg=-fuse-ld=lld"]
Hopefully those both greatly speed up the time.
Lastly, you could manually compile the platform just to see that it is actually making progress. instead of all of the updates getting eaten by the roc executable.
How do I manually compile it? I've tried similar to what I did before but it's not working:
Screen-Shot-2023-02-01-at-4.53.31-PM.png
Just cargo build. It won't have all of those features and won't produce a usable executable until final compilation with roc.
Also, you may hit issue if you directly overwrite the rust flags. When roc goes to finish compiling the platform, it won't end up using your override flag and will probably cause a full recompilation.
Though when you go back to roc for finalizing, you can alway add --prebuilt-platform=true to hopefully stop it from messing any of that.
Just got this
Screen-Shot-2023-02-01-at-5.05.01-PM.png
interesting. I get the feeling I should probably repro this locally at this point and poke around.
Also, this is the result of me trying the REPL. It starts up and then I just typed ”test” into the prompt and it hangs for a while and then outputs this
Screenshot 2023-02-01 at 8.07.09 PM.png
repl uses the wasm backend, so you probably don't have it compiled.
Given we turned off default features
Ok. Here is my understand of the issue. Some reason despite us passing a relocation mode of PIC to llvm. On aarch64, llvm is generating non-pic relocations. Thus, when linking, we hit a failure. Really confused why llvm is generating the bad relocations, but that is definitely what it looks like to me.
That said, I have a work around at least: comment out this line: https://github.com/roc-lang/roc/blob/rpi/crates/compiler/build/src/link.rs#L1191
That will create a position dependent executable, which is ok for these cases.
You must explicitly roc build and then run the app.
Also, if you at first get a segfault. cd into the platform directory and clean it. So in the case of the cli example (cd examples/cli/cli-platform && cargo clean)
repl uses the wasm backend, so you probably don't have it compiled.
We have two REPLs:
The one on the website is a Wasm build of the Roc compiler with the Wasm backend.
The one you get when you do roc repl on the command line is a native build of the Roc compiler with the LLVM backend.
That seems to have done the trick!
Screen-Shot-2023-02-02-at-1.53.05-PM.png
Next step is to try and get the LED on the pi blinking using Roc I think. I'm just going to try and modify the basic-cli platform unless there's a better way. Seems like it should be pretty straightforward to expand upon from looking through the platform code.
That sound be a fine way to do it. If types get more complex, roc glue can be very helpful.
Is there anyway I can learn more about roc glue? I keep hearing about it and I've tried roc glue --help but I'm still now sure exactly how to use it
it's work in progress
fundamentally roc glue <roc-file>.roc <rust-file>.rs will generate a rust file that maps the roc types to rust types. Thus making it much easier to use the types in rust. That said, it has a number of restrictions, especially currently. None the less, it is extremely useful.
As a simple example: cargo run glue examples/cli/cli-platform/InternalFile.roc examples/cli/cli-platform/src/file_glue.rs
....hmm.... Though this seems broken now. Did we break glue?
Maybe @Richard Feldman's PR that changed exporting and loading to support url modules?
possibly, although at this point from a glue perspective I want to focus on getting the branch working that has the "accept a .roc script" rather than fixing the current one that's on the way out :big_smile:
That's fair. Shoudn't it affect both though? It would be part of the parsing before we reach the roc file.
Ok more progress and more questions :) I just added a setPinHigh and setPinLow in Effect.roc and then implemented the rx functions in lib.rs. I'm exposing it through Stdout just for now. It compiles and seems to be working (I'm away right now so I can't actually see the LED on the Pi) but now I'm encountering a weird issue:
This works just fine.
So does this (I think, can't see the LED until I get home tonight).
I'm pretty sure I'm doing this one right but...
it segfaults.
Does that consistently segfault. On my pi, I noticed that randomly I was hitting segfaults. I would cargo clean the platform and then magically the segfault would go away. Have yet to dig into or figure out the cause.
Seemed pretty consistent. I also cargo cleaned it once and it still segfaulted after.
Interesting...i don't have any immediate thought.
can you share your repo. I'll take a look.
Here you go!
https://github.com/Billzabob/roc-clock
Thanks for taking a look! Really appreciate it
Oh, you just mixed up a type definition for your effects.
The effect is defined as U8 -> Effect {}, but the rust function of type u8 -> RocResult<{}, {}>.
So the effect should really be U8 -> Effect (Result {} {})
Also, you definitions in Stdout, should match the definition of setCwd in Env if you don't want the error to just be swallowed.
Well, i guess it isn't swapped, the full result is just returned in the success case as opposed to take the error path on errors.
My bad I should have updated you, I figured that out last night. Now I have another weird problem though.
This compiles
app "high"
packages { pf: "../src/main.roc" }
imports [pf.Stdout, pf.Stdin, pf.Task.{ Task }]
provides [main] to pf
main : Task {} []
main =
_ <- Task.attempt (Stdout.setPinHigh 23)
_ <- Task.await Stdin.line
Task.succeed {}
But this segfaults
app "high"
packages { pf: "../src/main.roc" }
imports [pf.Stdout, pf.Stdin, pf.Task.{ Task }]
provides [main] to pf
main : Task {} []
main =
_ <- Task.attempt (Stdout.setPinHigh 23)
_ <- Task.await Stdin.line
_ <- Task.attempt (Stdout.setPinLow 23)
Task.succeed {}
I just pushed those changes in case you wanted to take a look
Oh wow, that is crashing the actual compiler.
One workaround for now is to use --optimize
No idea why this fixes it, but it does
May want to change the platform cargo.toml to have a lighter release build so you don't have to wait forever on compilation.
Also, --prebuilt-platform=true is useful if you know the platform hasn't changed.
Oh weird but hey it worked! How do I change it to have a lighter release build?
Just change the release profile opt level in Cargo.toml for the platform
[profile.release]
opt-level = 1
That or setting it to 0 will case the platform to build a lot faster due to not optimizing as much/at all.
Kinda force opt to have dev level of optimization because you really just want it to compile fast.
So the code below works, almost. The issue is that as soon as this function exits, the GPIO gets reset when pin goes out of scope. The only way I can actually see the GPIO change state is if I throw a sleep after the set_high()call. Even if I make a sleep function that I expose through Roc and compose with Task.await it doesn't work, but that's what I'd like to do ideally. Is there anyways I can call Gpio::new() outside of this function and then just have the function use it, if that makes sense.
#[no_mangle]
pub extern "C" fn roc_fx_setPinHigh(pin: u8) -> RocResult<(), ()> {
use rppal::gpio::Gpio;
let mut pin = match Gpio::new() {
Ok(gpio) =>
match gpio.get(pin) {
Ok(p) => p.into_output(),
_ => return RocResult::err(()),
},
_ => return RocResult::err(()),
};
println!("setting high");
pin.set_high();
RocResult::ok(())
}
Two options come to mind:
That second option definitely sounds more appealing. Is there any examples of that anywhere? Even if I don’t go that route right now, I’d love to take a look at it
Richard talk about it at the last meet up. About 20 minutes in: https://drive.google.com/drive/folders/1OrgVPE6qGx34MT8oP2aNsop1oAs3_EVl
This repo is an old proof of concept that kinda uses the idea but was done manually: https://github.com/bhansconnect/roc-todos
There may be a better example, but I need to dig around and see. The core idea is return a tag union to the platform. The tag union contains a command, some data, and a continuation. The host matches on the command, runs an effect using the data, and then calls the continuation. This enables async state machines and simpler platform development. That said, the types right now can be complex and hard to manage. Once glue is updated, it should all get auto generated.
Thanks a bunch! I’ll give it a watch when I get a chance 👍
fyi I tried building on my aarch64 rpi yesterday but ran out of disk space. I'm going to order a larger microsd today.
Got an LED blinking from Roc:
Blinking LED Video
app "blink"
packages { pf: "../src/main.roc" }
imports [pf.Gpio, pf.Task.{ Task }]
provides [main] to pf
pin = 18
main : Task {} []
main = Task.forever blink
blink : Task {} []
blink =
_ <- Task.attempt (Gpio.setPinHigh pin)
_ <- Task.await (Gpio.sleep 1000)
_ <- Task.attempt (Gpio.setPinLow pin)
_ <- Task.await (Gpio.sleep 1000)
Task.succeed {}
Controlling LED intensity with hardware PWM:
PWM Video
app "pwm"
packages { pf: "../src/main.roc" }
imports [pf.Gpio, pf.Stdout, pf.Stdin, pf.Task.{ Task }]
provides [main] to pf
frequency = 50
main : Task {} []
main = Task.forever setPwm
setPwm : Task {} []
setPwm =
dutyCycleResult <- Task.attempt getDutyCycle
when dutyCycleResult is
Ok dutyCycle -> setDutyCycle dutyCycle
Err InvalidNumStr -> Stdout.line "Duty cycle is invalid. Try again.\n"
getDutyCycle : Task F64 [InvalidNumStr]
getDutyCycle =
_ <- Task.await (Stdout.line "Set the duty cycle from 0.0 to 1.0:")
dutyCycleStr <- Task.await Stdin.line
Task.fromResult (Str.toF64 dutyCycleStr)
setDutyCycle : F64 -> Task {} []
setDutyCycle = \dutyCycle ->
str = Num.toStr dutyCycle
result <- Task.attempt (Gpio.pwm frequency dutyCycle)
when result is
Ok {} -> Stdout.line "Duty cycle is: \(str).\n"
Err PwmFailure -> Stdout.line "Failed to set PWM"
Super cool. Video doesn't load for me. But looks really cool :D
Neither of the videos load for you? Hmmm... Do video uploads not work in Zulip?
Maybe not, it just waits a second or two and then Zulip dings, but I can't see anything changing.
Controlling a single servo with PWM:
PWM Servo
app "servo"
packages { pf: "../src/main.roc" }
imports [pf.Gpio, pf.Stdout, pf.Stdin, pf.Task.{ Task }]
provides [main] to pf
frequency = 50
main : Task {} []
main = Task.forever run
getRotationAmount : Task F64 [InvalidNumStr]
getRotationAmount =
_ <- Task.await (Stdout.line "Set the rotation amount from 0 to 180:")
rotationAmount <- Task.await Stdin.line
Task.fromResult (Str.toF64 rotationAmount)
run : Task {} []
run =
rotationAmountResult <- Task.attempt getRotationAmount
when rotationAmountResult is
Ok rotationAmount -> setRotation rotationAmount
Err InvalidNumStr -> Stdout.line "Duty cycle is invalid. Try again.\n"
setRotation : F64 -> Task {} []
setRotation = \rotationAmount ->
dutyCycle = map rotationAmount 0 180 0.05 0.1
str = Num.toStr dutyCycle
result <- Task.attempt (Gpio.pwm frequency dutyCycle)
when result is
Ok {} -> Stdout.line "Duty cycle is: \(str).\n"
Err PwmFailure -> Stdout.line "Failed to set PWM"
map = \value, inMin, inMax, outMin, outMax -> (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
@Luke Boswell I think it downloads it quietly. Check your downloads.
That was it. Thanks
Hurrah!!!
Glad this is finally coming together
Ya it's been pretty satisfying to finally get things working :grinning_face_with_smiling_eyes: Next step is getting I2C working to communicate with the servo drivers which is going to be quite a bit more complicated than the above. Wish me luck!
this is so cool!!! :heart_eyes: :heart_eyes: :heart_eyes:
is it ok if I share it on Twitter?
Thanks :grinning_face_with_smiling_eyes: And of course! I’m @Billzabob_ on Twitter
Am I messing something up with the types here? In Rust I have:
pub extern "C" fn roc_fx_readByte(address: u16) -> RocResult<u8, ()>
In Effect I have:
readByte : U16 -> Effect (Result U8 {})
Then in I2c.roc I have:
readByte : U16 -> Task U8 [ReadFailure]
readByte = \address ->
Effect.readByte address
|> Effect.map (\result -> Result.mapErr result \{} -> ReadFailure)
|> InternalTask.fromEffect
It thinks the Task is failing whenever I call it, even though in Rust I can verify that it's returning ok. If I change everything to not use RocResult and just change the type to:
pub extern "C" fn roc_fx_readByte(address: u16) -> u8 {
then it works just fine. It's driving me nuts not knowing what's going on here.
The repo is here if that helps.
you'd have to look at the llvm code that roc actually produces to know what signature it expects
it might for instance want the return value as a mutable reference that it writes the value into
though that seems unlikely in this particular case
How would I do that? Or is this something I can use Roc Glue for?
we have a --debug flag that will put a .ll file beside the generated app
you can then search that file for your exposed function
and if it's not immediately clear what the signature you find means, please post it here
I tried that and it said I need to install debugir. I tried doing that but it says I need LLVM 14 but I have LLVM 13 installed for compiling Roc from source. Do I need to upgrade LLVM?
Oh wait. Just checked out an old commit of DebugIr that was set to LLVM 13
yes that should work
A737B97E-3C7D-479C-80CA-672770A6D962.jpg
an unhelpful error
I can run it locally in a couple of hours
there are other ways to get that signature but they are harder to explain
That would be great! Thanks Folkert, really appreciate the help!
@Folkert de Vries I just realized that even though it's crashing with that error, I'm still getting the .ll file. Here's the output for readByte:
declare { [0 x i8], [1 x i8], i8, [0 x i8] } @roc_fx_readByte(i16) local_unnamed_addr
well. this is interesting and frustrating: rust will send a RocResult<u8, ()> over to roc as a u16. we "receive" it as { [0 x i8], [1 x i8], i8, [0 x i8] }, which is reasonable (if you've seen LLVM IR before), but it is _not_ valid according to the C ABI
my guess is that the signature is just UB and anything might happen from there
so what we should do is implement more of the C ABI. For now, you can receive the bits on the roc end as an U16 and then you have to do some Num.shiftRightBy and Num.intCast to get the info you want
that's the fastest way to get unstuck I think
Got the servo driver working! It was so satisfying and so much fun to get this working :grinning_face_with_smiling_eyes: Next step is to write up the 7-segment display code for them and then assemble the display.
Servo Driver Video
app "i2c"
packages { pf: "../src/main.roc" }
imports [pf.Gpio, pf.Stdout, pf.Stdin, pf.I2c, pf.Task.{ Task, await }]
provides [main] to pf
address = 0x0040
mode1 = 0
prescale = 254
sleepBit = 0x10
main =
_ <- Task.attempt init
Task.forever (Task.attempt run \_ -> Task.succeed {})
init =
_ <- await (reset)
# About 50 Hz
setPrescale 121
run =
_ <- await (Stdout.line "Enter the rotation amount (0 to 180):")
amount <- await Stdin.line
angle <- await (Task.fromResult (Str.toF64 amount))
_ <- await (setServoAngle 0 angle)
_ <- await (setServoAngle 1 angle)
_ <- await (setServoAngle 2 angle)
Task.succeed {}
setServoAngle = \servo, angle ->
register = 4 * servo + 8
count = map angle 0 180 145 500 |> Num.floor
low = Num.bitwiseAnd count 0xff |> Num.toU8
high = Num.shiftRightZfBy count 8 |> Num.toU8
_ <- await (writeRegister register low)
_ <- await (writeRegister (register + 1) high)
Task.succeed {}
setPrescale = \value ->
_ <- await sleep
_ <- await (writeRegister prescale value)
_ <- await wakeup
Gpio.sleep 5
readRegister = \register ->
_ <- await (I2c.writeBytes address [register])
bytes <- await (I2c.readBytes address 1)
Task.fromResult (List.get bytes 0)
writeRegister = \register, value ->
I2c.writeBytes address [register, value]
sleep =
oldMode <- await (readRegister mode1)
newMode = Num.bitwiseOr oldMode sleepBit
writeRegister mode1 newMode
wakeup =
oldMode <- await (readRegister mode1)
newMode = sleepBit |> bitwiseNot |> Num.bitwiseAnd oldMode
writeRegister mode1 newMode
# Surprised this isn't under Num already
bitwiseNot = \bits -> Num.bitwiseXor 0xff bits
reset = I2c.writeBytes 0 [0x06]
map = \value, inMin, inMax, outMin, outMax -> (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin
It was so satisfying and so much fun to get this working
That's how I felt when messing with roc on a little arm microcontroller that was driving a robot.
Oh nice! What were you programming the robot to do?
Most of the time was spent figuring out the python library and porting it to rust. But once that was done enough, just a number basic tasks with light sensors and encoders. So nothing too interesting was mostly just a test bed
Haha ya I get it though. Trying to explain to my friend why I was so excited about a blinking light was interesting :joy:
11 messages were moved from this topic to #off-topic > FPGA side project by Brendan Hansknecht.
Some good progress with the Roc Clock! NUMBERS! :grinning_face_with_smiling_eyes:
Counting Digits
That's not the kind of clock I was expecting but it looks super cool :)
Haha thanks! It’s also much louder than you’re average clock 😅 I’m trying to decide where I can put it where the sound of servos running in unison every minute won’t drive me insane 😂
Ok, last time, I'll stop spamming videos after this. The Roc Clock is pretty much complete! :grinning_face_with_smiling_eyes: I just need to hook it up to the actual time now, but it's suuuper fun to play with.
The code can be found here
Full Clock
Last updated: Jun 16 2026 at 16:19 UTC